
Sveiki, Habr!
PagÄjuÅ”ajÄ gada rudenÄ« Kaggle norisinÄjÄs zÄ«mÄto attÄlu klasifikÄcijas konkurss Quick Draw Doodle Recognition, kurÄ piedalÄ«jÄs arÄ« R komandas dalÄ«bnieki, , un . NeplÄnojam detalizÄti aprakstÄ«t sacensÄ«bu, tas jau tika izdarÄ«ts .
Å oreiz medaļu laimÄÅ”ana nesanÄca, bet tika iegÅ«ta liela vÄrtÄ«ga pieredze, tÄpÄc vÄlamies dalÄ«ties ar dažu no interesantÄkajÄm un noderÄ«gÄkajÄm tÄmÄm Kaggle un ikdienas darbÄ. ApskatÄ«tÄs tÄmas ietver: grÅ«to dzÄ«vi bez OpenCV, JSON parsÄÅ”anu (Å”ajos piemÄros tiek apsvÄrta C++ koda integrÄcija R skriptos vai paketÄs, izmantojot Rcpp), skriptu parametrizÄciju un gala risinÄjuma dockerizÄciju. Visi kodu piemÄri ir pieejami izpildei .
Saturs:
1. EfektÄ«va datu ielÄde no CSV uz MonetDB datu bÄzi
Dati Å”ajÄ sacensÄ«bÄ tiek nodroÅ”inÄti nevis kÄ gatavi attÄli, bet kÄ 340 CSV faili (pa vienam failam uz katru klasi), kas satur JSON ar punktu koordinÄtÄm. Savienojot Å”os punktus ar lÄ«nijÄm, mÄs iegÅ«stam gala attÄlu izmÄrÄ 256x256 pikseļi. ArÄ« katram ierakstam ir norÄdÄ«ta birka, vai attÄls tika pareizi atpazÄ«ts ar tobrÄ«d izmantoto klasifikatoru, divu burtu kods valsts, kurÄ dzÄ«vo zÄ«mÄtÄja autors, unikÄls identifikators, laikspiedols un klases nosaukums, kas atbilst faila nosaukumam. VienkÄrÅ”otÄ versija sver 7.4 GB arhivÄtÄ formÄtÄ un apmÄram 20 GB pÄc atpakotÅ”anas, pilni dati pÄc atpakotÅ”anas aizÅem 240 GB. Organizatori garantÄja, ka abas versijas reproducÄ tos paÅ”us zÄ«mÄjumus, tÄtad pilna versija ir lieka. JebkurÄ gadÄ«jumÄ ir atzÄ«ts, ka 50 miljons zÄ«mÄjumu glabÄÅ”ana grafiskajos failos vai masÄ«vos nav rentabla, un mÄs izlemÄm apvienot visus CSV failus no arhÄ«va train_simplified.zip datu bÄzÄ ar sekojoÅ”u attÄlu Ä£enerÄÅ”anu vajadzÄ«gajos izmÄros āuz vietasā katrai partijai.
KÄ DBMS tika izvÄlÄta labi sevi pierÄdÄ«jusi MonetDB, proti tam ir realizÄcija R kÄ pakotne . Pakotne ietver iebÅ«vÄto datu bÄzes servera versiju un ļauj palaist serveri tieÅ”i no R sesijas un strÄdÄt ar to. Datu bÄzes izveidoÅ”ana un pieslÄgÅ”anÄs notiek ar vienu komandu:
con <- DBI::dbConnect(drv = MonetDBLite::MonetDBLite(), Sys.getenv("DBDIR"))Mums bÅ«s nepiecieÅ”ams izveidot divas tabulas: vienu visiem datiem, otru - pakalpojuma informÄcijai par ielÄdÄtajiem failiem (tas noderÄs, ja kaut kas noies greizi un procesa turpinÄÅ”ana bÅ«s nepiecieÅ”ama pÄc vairÄku failu ielÄdes):
Tabulu izveide
if (!DBI::dbExistsTable(con, "doodles")) {
DBI::dbCreateTable(
con = con,
name = "doodles",
fields = c(
"countrycode" = "char(2)",
"drawing" = "text",
"key_id" = "bigint",
"recognized" = "bool",
"timestamp" = "timestamp",
"word" = "text"
)
)
}
if (!DBI::dbExistsTable(con, "upload_log")) {
DBI::dbCreateTable(
con = con,
name = "upload_log",
fields = c(
"id" = "serial",
"file_name" = "text UNIQUE",
"uploaded" = "bool DEFAULT false"
)
)
}ÄtrÄkais veids, kÄ ielÄdÄt datus datu bÄzÄ, izrÄdÄ«jÄs CSV failu tieÅ”a kopÄÅ”ana, izmantojot SQL komandu COPY OFFSET 2 INTO tablename FROM path USING DELIMITERS ',', 'n', '"' NULL AS '' BEST EFFORT, kur tablename ā tabulas nosaukums un ceļŔ ā ceļŔ uz failu. StrÄdÄjot ar arhÄ«vu, tika atklÄts, ka iebÅ«vÄtÄ realizÄcija unzip R nepareizi strÄdÄ ar vairÄkiem arhÄ«va failiem, tÄpÄc mÄs izmantojÄm sistÄmas unzip (izmantojot parametru getOption("unzip")).
Funkcija datu ierakstīŔanai bÄzÄ
#' @title ŠŠ·Š²Š»ŠµŃение Šø загŃŃŠ·ŠŗŠ° ŃŠ°Š¹Š»Š¾Š²
#'
#' @description
#' ŠŠ·Š²Š»ŠµŃение CSV-ŃŠ°Š¹Š»Š¾Š² ŠøŠ· ZIP-аŃŃ
ива Šø загŃŃŠ·ŠŗŠ° ŠøŃ
в Š±Š°Š·Ń ГаннŃŃ
#'
#' @param con ŠŠ±ŃŠµŠŗŃ ŠæŠ¾Š“ŠŗŠ»ŃŃŠµŠ½ŠøŃ Šŗ базе ГаннŃŃ
(клаŃŃ `MonetDBEmbeddedConnection`).
#' @param tablename ŠŠ°Š·Š²Š°Š½ŠøŠµ ŃŠ°Š±Š»ŠøŃŃ Š² базе ГаннŃŃ
.
#' @oaram zipfile ŠŃŃŃ Šŗ ZIP-аŃŃ
ивŃ.
#' @oaram filename ŠŠ¼Ń ŃŠ°Š¹Š»Š° внŃŃŠø ZIP-аŃŃ
ива.
#' @param preprocess Š¤ŃŠ½ŠŗŃŠøŃ ŠæŃŠµŠ“Š¾Š±ŃŠ°Š±Š¾ŃŠŗŠø, ŠŗŠ¾ŃŠ¾ŃŠ°Ń Š±ŃŠ“ŠµŃ ŠæŃŠøŠ¼ŠµŠ½ŠµŠ½Š° извлеŃŃŠ½Š½Š¾Š¼Ń ŃŠ°Š¹Š»Ń.
#' ŠŠ¾Š»Š¶Š½Š° ŠæŃŠøŠ½ŠøŠ¼Š°ŃŃ Š¾Š“ŠøŠ½ Š°ŃŠ³ŃŠ¼ŠµŠ½Ń `data` (Š¾Š±ŃŠµŠŗŃ `data.table`).
#'
#' @return `TRUE`.
#'
upload_file <- function(con, tablename, zipfile, filename, preprocess = NULL) {
# ŠŃŠ¾Š²ŠµŃŠŗŠ° Š°ŃŠ³ŃŠ¼ŠµŠ½ŃŠ¾Š²
checkmate::assert_class(con, "MonetDBEmbeddedConnection")
checkmate::assert_string(tablename)
checkmate::assert_string(filename)
checkmate::assert_true(DBI::dbExistsTable(con, tablename))
checkmate::assert_file_exists(zipfile, access = "r", extension = "zip")
checkmate::assert_function(preprocess, args = c("data"), null.ok = TRUE)
# ŠŠ·Š²Š»ŠµŃение ŃŠ°Š¹Š»Š°
path <- file.path(tempdir(), filename)
unzip(zipfile, files = filename, exdir = tempdir(),
junkpaths = TRUE, unzip = getOption("unzip"))
on.exit(unlink(file.path(path)))
# ŠŃŠøŠ¼ŠµŠ½ŃŠµŠ¼ ŃŃŠ½ŠŗŃŠøŃ ŠæŃŠµŠ“Š¾Š±ŃŠ°Š±Š¾ŃŠŗŠø
if (!is.null(preprocess)) {
.data <- data.table::fread(file = path)
.data <- preprocess(data = .data)
data.table::fwrite(x = .data, file = path, append = FALSE)
rm(.data)
}
# ŠŠ°ŠæŃŠ¾Ń Šŗ ŠŠ на импоŃŃ CSV
sql <- sprintf(
"COPY OFFSET 2 INTO %s FROM '%s' USING DELIMITERS ',','n','"' NULL AS '' BEST EFFORT",
tablename, path
)
# ŠŃполнение Š·Š°ŠæŃоŃа Šŗ ŠŠ
DBI::dbExecute(con, sql)
# ŠŠ¾Š±Š°Š²Š»ŠµŠ½ŠøŠµ Š·Š°ŠæŠøŃŠø об ŃŃŠæŠµŃŠ½Š¾Š¹ загŃŃŠ·ŠŗŠµ в ŃŠ»ŃжебнŃŃ ŃŠ°Š±Š»ŠøŃŃ
DBI::dbExecute(con, sprintf("INSERT INTO upload_log(file_name, uploaded) VALUES('%s', true)",
filename))
return(invisible(TRUE))
}Ja ir nepiecieÅ”ams pÄrvÄrst tabulu pirms ierakstīŔanas datu bÄzÄ, pietiek nodot argumentÄ preprocess funkciju, kas pÄrvÄrÅ” datus.
Kods sekvenÄu datu ielÄdei bÄzÄ:
Datu ierakstīŔana bÄzÄ
# Š”ŠæŠøŃŠ¾Šŗ ŃŠ°Š¹Š»Š¾Š² Š“Š»Ń Š·Š°ŠæŠøŃŠø
files <- unzip(zipfile, list = TRUE)$Name
# Š”ŠæŠøŃŠ¾Šŗ ŠøŃŠŗŠ»ŃŃŠµŠ½ŠøŠ¹, ŠµŃŠ»Šø ŃŠ°ŃŃŃ ŃŠ°Š¹Š»Š¾Š² ŃŠ¶Šµ Š±ŃŠ»Š° загŃŃŠ¶ŠµŠ½Š°
to_skip <- DBI::dbGetQuery(con, "SELECT file_name FROM upload_log")[[1L]]
files <- setdiff(files, to_skip)
if (length(files) > 0L) {
# ŠŠ°ŠæŃŃŠŗŠ°ŠµŠ¼ ŃŠ°Š¹Š¼ŠµŃ
tictoc::tic()
# ŠŃŠ¾Š³ŃŠµŃŃ Š±Š°Ń
pb <- txtProgressBar(min = 0L, max = length(files), style = 3)
for (i in seq_along(files)) {
upload_file(con = con, tablename = "doodles",
zipfile = zipfile, filename = files[i])
setTxtProgressBar(pb, i)
}
close(pb)
# ŠŃŃŠ°Š½Š°Š²Š»ŠøŠ²Š°ŠµŠ¼ ŃŠ°Š¹Š¼ŠµŃ
tictoc::toc()
}
# 526.141 sec elapsed - ŠŗŠ¾ŠæŠøŃŠ¾Š²Š°Š½ŠøŠµ SSD->SSD
# 558.879 sec elapsed - ŠŗŠ¾ŠæŠøŃŠ¾Š²Š°Š½ŠøŠµ USB->SSDDatu ielÄdes laiks var atŔķirties atkarÄ«bÄ no izmantotÄ krÄtuves Ätruma raksturlielumiem. MÅ«su gadÄ«jumÄ lasīŔana un rakstīŔana no viena SSD vai no zibatmiÅas (sÄkotnÄjais fails) uz SSD (datu bÄzi) aizÅem mazÄk par 10 minÅ«tÄm.
VÄl dažas sekundes ir nepiecieÅ”amas, lai izveidotu kolonnu ar veselu skaitļu klases marÄ·i un indeksa kolonnu (ORDERED INDEX) ar rindu numuriem, pÄc kuriem tiks veikta novÄrojumu izvÄle, veidojot partijas:
Papildu kolonnu un indeksa izveide
message("Generate labels")
invisible(DBI::dbExecute(con, "ALTER TABLE doodles ADD label_int int"))
invisible(DBI::dbExecute(con, "UPDATE doodles SET label_int = dense_rank() OVER (ORDER BY word) - 1"))
message("Generate row numbers")
invisible(DBI::dbExecute(con, "ALTER TABLE doodles ADD id serial"))
invisible(DBI::dbExecute(con, "CREATE ORDERED INDEX doodles_id_ord_idx ON doodles(id)"))Lai izpildÄ«tu uzdevumu izveidot partiju "uz vietas", mums bija jÄsasniedz maksimÄla Ätruma nejauÅ”o rindu izgūŔanai no tabulas. doodles. TÄdÄļ mÄs izmantojÄm 3 trikus. Pirmais bija samazinÄt datu tipa izmÄru, kurÄ tiek glabÄts novÄrojumu ID. SÄkotnÄjÄ datu kopÄ ID glabÄÅ”anai nepiecieÅ”ams tips bigint, bet novÄrojumu skaits ļauj ietilpinÄt to identifikatorus, kas ir vienÄdi ar secÄ«bas numuru, tipÄ int. Å ajÄ gadÄ«jumÄ meklÄÅ”ana notiek ievÄrojami ÄtrÄk. Otrais triks bija izmantot ORDERED INDEX ā Å”o risinÄjumu mÄs atradÄm empÄ«riski, izmÄÄ£inot visus pieejamos . TreÅ”ais sastÄvÄja no parametrizÄtiem pieprasÄ«jumiem. Metodes bÅ«tÄ«ba ir vienreizÄji izpildÄ«t komandu PREPARE , sekojoÅ”i izmantojot sagatavoto izteiksmi, veidojot virkni viendabÄ«gu pieprasÄ«jumu, taÄu praksÄ ieguvums salÄ«dzinÄjumÄ ar vienkÄrÅ”u SELECT izrÄdÄ«jÄs statistiski nenozÄ«mÄ«gs.
Datu ievadīŔanas process patÄrÄ ne vairÄk kÄ 450 MB RAM. TÄtad, aprakstÄ«tais pieejas ļauj apstrÄdÄt datu kopas, kuru svars ir desmitiem gigabaitu, praktiski uz jebkura budžeta dzelžiem, ieskaitot dažus viensaldus, kas ir diezgan forÅ”i.
Paliek veikt Ätruma mÄrÄ«jumus (nejauÅ”o) datu izgūŔanai un novÄrtÄt mÄrogojamÄ«bu, izvÄloties partijas dažÄda lieluma:
Datu bÄzes tests
library(ggplot2)
set.seed(0)
# PieslÄgÅ”anÄs pie datu bÄzes
con <- DBI::dbConnect(MonetDBLite::MonetDBLite(), Sys.getenv("DBDIR"))
# Funkcija, lai sagatavotu vaicÄjumu servera pusÄ
prep_sql <- function(batch_size) {
sql <- sprintf("PREPARE SELECT id FROM doodles WHERE id IN (%s)",
paste(rep("?", batch_size), collapse = ","))
res <- DBI::dbSendQuery(con, sql)
return(res)
}
# Funkcija datu iegūŔanai
fetch_data <- function(rs, batch_size) {
ids <- sample(seq_len(n), batch_size)
res <- DBI::dbFetch(DBI::dbBind(rs, as.list(ids)))
return(res)
}
# MÄÄ£inÄjumu veikÅ”ana
res_bench <- bench::press(
batch_size = 2^(4:10),
{
rs <- prep_sql(batch_size)
bench::mark(
fetch_data(rs, batch_size),
min_iterations = 50L
)
}
)
# Benchmarks parametri
cols <- c("batch_size", "min", "median", "max", "itr/sec", "total_time", "n_itr")
res_bench[, cols]
# batch_size min median max `itr/sec` total_time n_itr
#
# 1 16 23.6ms 54.02ms 93.43ms 18.8 2.6s 49
# 2 32 38ms 84.83ms 151.55ms 11.4 4.29s 49
# 3 64 63.3ms 175.54ms 248.94ms 5.85 8.54s 50
# 4 128 83.2ms 341.52ms 496.24ms 3.00 16.69s 50
# 5 256 232.8ms 653.21ms 847.44ms 1.58 31.66s 50
# 6 512 784.6ms 1.41s 1.98s 0.740 1.1m 49
# 7 1024 681.7ms 2.72s 4.06s 0.377 2.16m 49
ggplot(res_bench, aes(x = factor(batch_size), y = median, group = 1)) +
geom_point() +
geom_line() +
ylab("vidÄjais laiks, s") +
theme_minimal()
DBI::dbDisconnect(con, shutdown = TRUE) 
2. BatÄu sagatavoÅ”ana
Visu batÄu sagatavoÅ”anas process sastÄv no Å”Ädiem posmiem:
- VairÄku JSON parsÄÅ”ana, kas satur virknes vektorus ar punktu koordinÄtÄm.
- KrÄsainu lÄ«niju uzzÄ«mÄÅ”ana pÄc punktu koordinÄtÄm attÄlÄ ar nepiecieÅ”amo izmÄru (piemÄram, 256Ć256 vai 128Ć128).
- IegÅ«to attÄlu pÄrvÄrÅ”ana tensorÄ.
SacensÄ«bu ietvaros starp Python kernel-iem uzdevums galvenokÄrt tika risinÄts ar lÄ«dzekļiem OpenCV. Viens no acÄ«mredzamÄkajiem lÄ«dzÄ«giem risinÄjumiem R bÅ«tu Å”Äds:
JSON pÄrvÄrÅ”anas Ä«stenoÅ”ana tensorÄ R
r_process_json_str <- function(json, line.width = 3,
color = TRUE, scale = 1) {
# JSON parsÄÅ”ana
coords <- jsonlite::fromJSON(json, simplifyMatrix = FALSE)
tmp <- tempfile()
# DzÄÅ”am pagaidu failu funkcijas beigÄs
on.exit(unlink(tmp))
png(filename = tmp, width = 256 * scale, height = 256 * scale, pointsize = 1)
# TukŔa diagramma
plot.new()
# Diagrammas loga izmÄrs
plot.window(xlim = c(256 * scale, 0), ylim = c(256 * scale, 0))
# LÄ«niju krÄsas
cols <- if (color) rainbow(length(coords)) else "#000000"
for (i in seq_along(coords)) {
lines(x = coords[[i]][[1]] * scale, y = coords[[i]][[2]] * scale,
col = cols[i], lwd = line.width)
}
dev.off()
# AttÄla pÄrvÄrÅ”ana 3D masÄ«vÄ
res <- png::readPNG(tmp)
return(res)
}
r_process_json_vector <- function(x, ...) {
res <- lapply(x, r_process_json_str, ...)
# Apvienojam 3D attÄlu masÄ«vus 4D tenzorÄ
res <- do.call(abind::abind, c(res, along = 0))
return(res)
}ZÄ«mÄÅ”ana tiek veikta, izmantojot standarta R rÄ«kus, saglabÄjot pagaidu PNG failÄ, kas tiek glabÄts RAM (Linux pagaidu direktorijas R atrodas katalogÄ /tmp, kas piesprausts RAM). PÄc tam Å”is fails tiek nolasÄ«ts kÄ trÄ«sdimensionÄls masÄ«vs ar skaitļiem diapazonÄ no 0 lÄ«dz 1. Tas ir svarÄ«gi, jo vispÄrpieÅemtais BMP tiktu nolasÄ«ts kÄ raw-masÄ«vs ar hex krÄsu kodiem.
PÄrbaudÄ«sim rezultÄtu:
zip_file <- file.path("data", "train_simplified.zip")
csv_file <- "cat.csv"
unzip(zip_file, files = csv_file, exdir = tempdir(),
junkpaths = TRUE, unzip = getOption("unzip"))
tmp_data <- data.table::fread(file.path(tempdir(), csv_file), sep = ",",
select = "drawing", nrows = 10000)
arr <- r_process_json_str(tmp_data[4, drawing])
dim(arr)
# [1] 256 256 3
plot(magick::image_read(arr)) 
PaÅ”nÄ«kt vÄlas veidot partiju Å”Ädi:
res <- r_process_json_vector(tmp_data[1:4, drawing], scale = 0.5)
str(res)
# num [1:4, 1:128, 1:128, 1:3] 1 1 1 1 1 1 1 1 1 1 ...
# - attr(*, "dimnames")=List of 4
# ..$ : NULL
# ..$ : NULL
# ..$ : NULL
# ..$ : NULLÅ Ä« Ä«stenoÅ”ana mums Ŕķita neefektÄ«va, jo liela partiju veidoÅ”ana prasa nenormÄli daudz laika, un mÄs nolÄmÄm izmantot kolÄÄ£u pieredzi, izmantojot jaudÄ«gu bibliotÄku OpenCV. TajÄ laikÄ gatava pakete R nebija (nav arÄ« tagad), tÄpÄc tika uzrakstÄ«ta minimÄla nepiecieÅ”amÄ funkcionalitÄte C++ ar integrÄciju R kodÄ, izmantojot Rcpp.
Lai atrisinÄtu uzdevumu, tika izmantotas Å”Ädas paketes un bibliotÄkas:
OpenCV attÄlu apstrÄdei un lÄ«niju zÄ«mÄÅ”anai. Tika izmantotas iepriekÅ” instalÄtas sistÄmas bibliotÄkas un galvenes, kÄ arÄ« dinamisÄ saistīŔana.
xtensor darbam ar daudzdimensiju masÄ«viem un tenzoriem. MÄs izmantojÄm galvenes failus, kas iekļauti attiecÄ«gajÄ R paketÄ. BibliotÄka ļauj strÄdÄt ar daudzdimensiju masÄ«viem, gan row major, gan column major secÄ«bÄ.
ndjson JSON parsÄÅ”anai. Å Ä« bibliotÄka tiek izmantota xtensor automÄtiski, ja tÄ ir pieejama projektÄ.
RcppThread daudzvÄ«du apstrÄdei JSON vektoriem. MÄs izmantojÄm galvenes failus, ko nodroÅ”ina Ŕī pakete. SalÄ«dzinoÅ”i ar populÄrÄko RcppParallel paketi, starp citu, ir iebÅ«vÄts cikla pÄrtraukÅ”anas mehÄnisms (interrupt).
JÄatzÄ«mÄ, ka xtensor izrÄdÄ«jÄs kÄ Ä«sts atklÄjums: ne tikai tam ir plaÅ”s funkcionalitÄtes klÄsts un augsta veiktspÄja, bet arÄ« tÄ izstrÄdÄtÄji izrÄdÄ«jÄs diezgan atsaucÄ«gi un operatÄ«vi un detalizÄti atbildÄja uz raduÅ”Äm jautÄjumiem. Ar viÅu palÄ«dzÄ«bu izdevÄs realizÄt OpenCV matricas pÄrvÄrÅ”anu xtensor tenzoros, kÄ arÄ« trÄ«s dimensiju attÄlu tenzoru apvienoÅ”anu Äetru dimensiju tenzorÄ ar pareizu dimensiju (proti, partiju).
MateriÄli Rcpp, xtensor un RcppThread apguvei
Lai kompilÄtu failus, kas izmanto sistÄmas failus un dinamiski saistÄ«tÄs bibliotÄkas, kas instalÄtas sistÄmÄ, mÄs izmantojÄm spraudÅa mehÄnisma, kas Ä«stenots paketÄ, Rcpp. AutomÄtiskai ceļu un karogu atraÅ”anai izmantojÄm populÄro linux rÄ«ku pkg-config.
Rcpp spraudÅa Ä«stenoÅ”ana, lai izmantotu OpenCV bibliotÄku
Rcpp::registerPlugin("opencv", function() {
# IespÄjamie paketes nosaukumi
pkg_config_name <- c("opencv", "opencv4")
# pkg-config rÄ«ka binÄrais fails
pkg_config_bin <- Sys.which("pkg-config")
# PÄrbaude, vai rÄ«ks ir pieejams sistÄmÄ
checkmate::assert_file_exists(pkg_config_bin, access = "x")
# PÄrbaude, vai OpenCV konfigurÄcijas fails ir pieejams pkg-config
check <- sapply(pkg_config_name,
function(pkg) system(paste(pkg_config_bin, pkg)))
if (all(check != 0)) {
stop("OpenCV config for the pkg-config not found", call. = FALSE)
}
pkg_config_name <- pkg_config_name[check == 0]
list(env = list(
PKG_CXXFLAGS = system(paste(pkg_config_bin, "--cflags", pkg_config_name),
intern = TRUE),
PKG_LIBS = system(paste(pkg_config_bin, "--libs", pkg_config_name),
intern = TRUE)
))
})KompilÄcijas procesÄ spraudÅa darbÄ«bas rezultÄtÄ tiks aizvietoti Å”Ädi vÄrtÄ«bas:
Rcpp:::.plugins$opencv()$env
# $PKG_CXXFLAGS
# [1] "-I/usr/include/opencv"
#
# $PKG_LIBS
# [1] "-lopencv_shape -lopencv_stitching -lopencv_superres -lopencv_videostab -lopencv_aruco -lopencv_bgsegm -lopencv_bioinspired -lopencv_ccalib -lopencv_datasets -lopencv_dpm -lopencv_face -lopencv_freetype -lopencv_fuzzy -lopencv_hdf -lopencv_line_descriptor -lopencv_optflow -lopencv_video -lopencv_plot -lopencv_reg -lopencv_saliency -lopencv_stereo -lopencv_structured_light -lopencv_phase_unwrapping -lopencv_rgbd -lopencv_viz -lopencv_surface_matching -lopencv_text -lopencv_ximgproc -lopencv_calib3d -lopencv_features2d -lopencv_flann -lopencv_xobjdetect -lopencv_objdetect -lopencv_ml -lopencv_xphoto -lopencv_highgui -lopencv_videoio -lopencv_imgcodecs -lopencv_photo -lopencv_imgproc -lopencv_core"JSON parsÄÅ”anas un partijas veidoÅ”anas kods modelim ir sniegts zem spoileris. IepriekÅ” pievienojam lokÄlo projekta direktoriju galvene failu meklÄÅ”anai (nepiecieÅ”a ndjson):
Sys.setenv("PKG_CXXFLAGS" = paste0("-I", normalizePath(file.path("src"))))JSON pÄrveidoÅ”anas implementÄcija uz C++
// [[Rcpp::plugins(cpp14)]]
// [[Rcpp::plugins(opencv)]]
// [[Rcpp::depends(xtensor)]]
// [[Rcpp::depends(RcppThread)]]
#include <xtensor/xjson.hpp>
#include <xtensor/xadapt.hpp>
#include <xtensor/xview.hpp>
#include <xtensor-r/rtensor.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <Rcpp.h>
#include <RcppThread.h>
// Š”ŠøŠ½Š¾Š½ŠøŠ¼Ń Š“Š»Ń ŃŠøŠæŠ¾Š²
using RcppThread::parallelFor;
using json = nlohmann::json;
using points = xt::xtensor<double,2>; // ŠŠ·Š²Š»ŠµŃŃŠ½Š½Ńе ŠøŠ· JSON ŠŗŠ¾Š¾ŃŠ“инаŃŃ ŃŠ¾Ńек
using strokes = std::vector<points>; // ŠŠ·Š²Š»ŠµŃŃŠ½Š½Ńе ŠøŠ· JSON ŠŗŠ¾Š¾ŃŠ“инаŃŃ ŃŠ¾Ńек
using xtensor3d = xt::xtensor<double, 3>; // Š¢ŠµŠ½Š·Š¾Ń Š“Š»Ń Ń
ŃŠ°Š½ŠµŠ½ŠøŃ маŃŃŠøŃŃ ŠøŠ·Š¾Š¾Š±ŃŠ°Š¶ŠµŠ½ŠøŃ
using xtensor4d = xt::xtensor<double, 4>; // Š¢ŠµŠ½Š·Š¾Ń Š“Š»Ń Ń
ŃŠ°Š½ŠµŠ½ŠøŃ множеŃŃŠ²Š° ŠøŠ·Š¾Š±ŃŠ°Š¶ŠµŠ½ŠøŠ¹
using rtensor3d = xt::rtensor<double, 3>; // ŠŠ±ŃŃŃŠŗŠ° Š“Š»Ń ŃŠŗŃпоŃŃŠ° в R
using rtensor4d = xt::rtensor<double, 4>; // ŠŠ±ŃŃŃŠŗŠ° Š“Š»Ń ŃŠŗŃпоŃŃŠ° в R
// Š”ŃŠ°ŃŠøŃŠµŃкие конŃŃŠ°Š½ŃŃ
// Š Š°Š·Š¼ŠµŃ ŠøŠ·Š¾Š±ŃŠ°Š¶ŠµŠ½ŠøŃ в ŠæŠøŠŗŃŠµŠ»ŃŃ
const static int SIZE = 256;
// Тип линии
// Дм. https://en.wikipedia.org/wiki/Pixel_connectivity#2-dimensional
const static int LINE_TYPE = cv::LINE_4;
// Š¢Š¾Š»ŃŠøŠ½Š° линии в ŠæŠøŠŗŃŠµŠ»ŃŃ
const static int LINE_WIDTH = 3;
// ŠŠ»Š³Š¾ŃŠøŃŠ¼ ŃŠµŃайза
// https://docs.opencv.org/3.1.0/da/d54/group__imgproc__transform.html#ga5bb5a1fea74ea38e1a5445ca803ff121
const static int RESIZE_TYPE = cv::INTER_LINEAR;
// Шаблон Š“Š»Ń ŠŗŠ¾Š½Š²ŠµŃŃŠøŃŠ¾Š²Š°Š½ŠøŃ OpenCV-маŃŃŠøŃŃ Š² ŃŠµŠ½Š·Š¾Ń
template <typename T, int NCH, typename XT=xt::xtensor<T,3,xt::layout_type::column_major>>
XT to_xt(const cv::Mat_<cv::Vec<T, NCH>>& src) {
// Š Š°Š·Š¼ŠµŃŠ½Š¾ŃŃŃ ŃŠµŠ»ŠµŠ²Š¾Š³Š¾ ŃŠµŠ½Š·Š¾Ńа
std::vector<int> shape = {src.rows, src.cols, NCH};
// ŠŠ±Ńее ŠŗŠ¾Š»ŠøŃеŃŃŠ²Š¾ ŃŠ»ŠµŠ¼ŠµŠ½Ńов в маŃŃŠøŠ²Šµ
size_t size = src.total() * NCH;
// ŠŃŠµŠ¾Š±ŃŠ°Š·Š¾Š²Š°Š½ŠøŠµ cv::Mat в xt::xtensor
XT res = xt::adapt((T*) src.data, size, xt::no_ownership(), shape);
return res;
}
// ŠŃŠµŠ¾Š±ŃŠ°Š·Š¾Š²Š°Š½ŠøŠµ JSON в ŃŠæŠøŃŠ¾Šŗ ŠŗŠ¾Š¾ŃŠ“ŠøŠ½Š°Ń ŃŠ¾Ńек
strokes parse_json(const std::string& x) {
auto j = json::parse(x);
// Š ŠµŠ·ŃŠ»ŃŃŠ°Ń паŃŃŠøŠ½Š³Š° Голжен бŃŃŃ Š¼Š°ŃŃŠøŠ²Š¾Š¼
if (!j.is_array()) {
throw std::runtime_error("'x' must be JSON array.");
}
strokes res;
res.reserve(j.size());
for (const auto& a: j) {
// ŠŠ°Š¶Š“ŃŠ¹ ŃŠ»ŠµŠ¼ŠµŠ½Ń маŃŃŠøŠ²Š° Голжен бŃŃŃ 2-Š¼ŠµŃŠ½Ńм маŃŃŠøŠ²Š¾Š¼
if (!a.is_array() || a.size() != 2) {
throw std::runtime_error("'x' must include only 2d arrays.");
}
// ŠŠ·Š²Š»ŠµŃение Š²ŠµŠŗŃоŃа ŃŠ¾Ńек
auto p = a.get<points>();
res.push_back(p);
}
return res;
}
// ŠŃŃŠøŃовка линий
// Š¦Š²ŠµŃŠ° HSV
cv::Mat ocv_draw_lines(const strokes& x, bool color = true) {
// ŠŃŃ
Š¾Š“Š½ŃŠ¹ ŃŠøŠæ маŃŃŠøŃŃ
auto stype = color ? CV_8UC3 : CV_8UC1;
// ŠŃŠ¾Š³Š¾Š²ŃŠ¹ ŃŠøŠæ маŃŃŠøŃŃ
auto dtype = color ? CV_32FC3 : CV_32FC1;
auto bg = color ? cv::Scalar(0, 0, 255) : cv::Scalar(255);
auto col = color ? cv::Scalar(0, 255, 220) : cv::Scalar(0);
cv::Mat img = cv::Mat(SIZE, SIZE, stype, bg);
// ŠŠ¾Š»ŠøŃеŃŃŠ²Š¾ линий
size_t n = x.size();
for (const auto& s: x) {
// ŠŠ¾Š»ŠøŃеŃŃŠ²Š¾ ŃŠ¾Ńек в линии
size_t n_points = s.shape()[1];
for (size_t i = 0; i < n_points - 1; ++i) {
// Š¢Š¾ŃŠŗŠ° Š½Š°ŃŠ°Š»Š° ŃŃŃŠøŃ
а
cv::Point from(s(0, i), s(1, i));
// Š¢Š¾ŃŠŗŠ° Š¾ŠŗŠ¾Š½ŃŠ°Š½ŠøŃ ŃŃŃŠøŃ
а
cv::Point to(s(0, i + 1), s(1, i + 1));
// ŠŃŃŠøŃовка линии
cv::line(img, from, to, col, LINE_WIDTH, LINE_TYPE);
}
if (color) {
// ŠŠµŠ½Ńем ŃŠ²ŠµŃ линии
col[0] += 180 / n;
}
}
if (color) {
// ŠŠµŠ½Ńем ŃŠ²ŠµŃовое ŠæŃеГŃŃŠ°Š²Š»ŠµŠ½ŠøŠµ на RGB
cv::cvtColor(img, img, cv::COLOR_HSV2RGB);
}
// ŠŠµŠ½Ńем ŃŠ¾ŃŠ¼Š°Ń ŠæŃŠµŠ“ŃŃŠ°Š²Š»ŠµŠ½ŠøŃ на float32 Ń Š“ŠøŠ°ŠæŠ°Š·Š¾Š½Š¾Š¼ [0, 1]
img.convertTo(img, dtype, 1 / 255.0);
return img;
}
// ŠŠ±ŃŠ°Š±Š¾ŃŠŗŠ° JSON Šø полŃŃŠµŠ½ŠøŠµ ŃŠµŠ½Š·Š¾Ńа Ń Š“Š°Š½Š½ŃŠ¼Šø ŠøŠ·Š¾Š±ŃŠ°Š¶ŠµŠ½ŠøŃ
xtensor3d process(const std::string& x, double scale = 1.0, bool color = true) {
auto p = parse_json(x);
auto img = ocv_draw_lines(p, color);
if (scale != 1) {
cv::Mat out;
cv::resize(img, out, cv::Size(), scale, scale, RESIZE_TYPE);
cv::swap(img, out);
out.release();
}
xtensor3d arr = color ? to_xt<double,3>(img) : to_xt<double,1>(img);
return arr;
}
// [[Rcpp::export]]
rtensor3d cpp_process_json_str(const std::string& x,
double scale = 1.0,
bool color = true) {
xtensor3d res = process(x, scale, color);
return res;
}
// [[Rcpp::export]]
rtensor4d cpp_process_json_vector(const std::vector<std::string>& x,
double scale = 1.0,
bool color = false) {
size_t n = x.size();
size_t dim = floor(SIZE * scale);
size_t channels = color ? 3 : 1;
xtensor4d res({n, dim, dim, channels});
parallelFor(0, n, [&x, &res, scale, color](int i) {
xtensor3d tmp = process(x[i], scale, color);
auto view = xt::view(res, i, xt::all(), xt::all(), xt::all());
view = tmp;
});
return res;
}Å is kods jÄievieto failÄ src/cv_xt.cpp un jÄkompilÄ ar komandu Rcpp::sourceCpp(file = "src/cv_xt.cpp", env = .GlobalEnv); turklÄt darbosies arÄ« nlohmann/json.hpp no . Kods sadalÄ«ts vairÄkÄs funkcijÄs:
to_xtā pielÄgojuma funkcija, lai attÄla matricas (cv::Mat) pÄrveidotu tensorÄxt::xtensor;parse_jsonā funkcija analizÄ JSON virkni, iegÅ«st koordinÄtas un iepako tÄs vektorÄ;ocv_draw_linesā no iegÅ«tÄ punktu vektora zÄ«mÄ daudzkrÄsainas lÄ«nijas;processā apvieno iepriekÅ” aprakstÄ«tÄs funkcijas un pievieno iespÄju iegÅ«tÄ attÄla mÄrogoÅ”anai;cpp_process_json_strā apvalks pÄr funkcijuprocess, kas eksportÄ rezultÄtu R objektÄ (daudzdimensiju masÄ«vÄ);cpp_process_json_vectorā apvalks pÄr funkcijucpp_process_json_str, kas ļauj apstrÄdÄt virkÅu vektoru daudzprotokolÄ.
DaudzkrÄsaino lÄ«niju zÄ«mÄÅ”anai tika izmantots HSV krÄsu modelis ar sekojoÅ”u pÄrvÄrÅ”anu RGB. PÄrbaudÄ«sim rezultÄtu:
arr <- cpp_process_json_str(tmp_data[4, drawing])
dim(arr)
# [1] 256 256 3
plot(magick::image_read(arr)) 
SalÄ«dzinoÅ”Ä Ätruma izpilde uz R un C++
res_bench <- bench::mark(
r_process_json_str(tmp_data[4, drawing], scale = 0.5),
cpp_process_json_str(tmp_data[4, drawing], scale = 0.5),
check = FALSE,
min_iterations = 100
)
# Benchmark parameters
cols <- c("expression", "min", "median", "max", "itr/sec", "total_time", "n_itr")
res_bench[, cols]
# expression min median max `itr/sec` total_time n_itr
#
# 1 r_process_json_str 3.49ms 3.55ms 4.47ms 273. 490ms 134
# 2 cpp_process_json_str 1.94ms 2.02ms 5.32ms 489. 497ms 243
library(ggplot2)
# Measurement execution
res_bench <- bench::press(
batch_size = 2^(4:10),
{
.data <- tmp_data[sample(seq_len(.N), batch_size), drawing]
bench::mark(
r_process_json_vector(.data, scale = 0.5),
cpp_process_json_vector(.data, scale = 0.5),
min_iterations = 50,
check = FALSE
)
}
)
res_bench[, cols]
# expression batch_size min median max `itr/sec` total_time n_itr
# <bch:tm> <bch:tm> <bch:tm> <int>
# 1 r 16 50.61ms 53.34ms 54.82ms 19.1 471.13ms 9
# 2 cpp 16 4.46ms 5.39ms 7.78ms 192. 474.09ms 91
# 3 r 32 105.7ms 109.74ms 212.26ms 7.69 6.5s 50
# 4 cpp 32 7.76ms 10.97ms 15.23ms 95.6 522.78ms 50
# 5 r 64 211.41ms 226.18ms 332.65ms 3.85 12.99s 50
# 6 cpp 64 25.09ms 27.34ms 32.04ms 36.0 1.39s 50
# 7 r 128 534.5ms 627.92ms 659.08ms 1.61 31.03s 50
# 8 cpp 128 56.37ms 58.46ms 66.03ms 16.9 2.95s 50
# 9 r 256 1.15s 1.18s 1.29s 0.851 58.78s 50
# 10 cpp 256 114.97ms 117.39ms 130.09ms 8.45 5.92s 50
# 11 r 512 2.09s 2.15s 2.32s 0.463 1.8m 50
# 12 cpp 512 230.81ms 235.6ms 261.99ms 4.18 11.97s 50
# 13 r 1024 4s 4.22s 4.4s 0.238 3.5m 50
# 14 cpp 1024 410.48ms 431.43ms 462.44ms 2.33 21.45s 50
ggplot(res_bench, aes(x = factor(batch_size), y = median,
group = expression, color = expression)) +
geom_point() +
geom_line() +
ylab("median time, s") +
theme_minimal() +
scale_color_discrete(name = "", labels = c("cpp", "r")) +
theme(legend.position = "bottom") 
KÄ redzam, Ätruma pieaugums ir patieÅ”Äm ievÄrojams, un nav iespÄjams panÄkt C++ kodu, izmantojot R koda paralelizÄciju.
3. Iteratori partiju izsaukÅ”anai no datubÄzÄm
R ir pelnÄ«jusi cieÅu kÄ valoda datu apstrÄdei, kas ietilpst RAM, bet Python ir raksturÄ«gÄka iteratÄ«vÄ datu apstrÄde, kas ļauj viegli un dabiski Ä«stenot out-of-core aprÄÄ·inus (aprÄÄ·inus, kas izmanto ÄrÄjo atmiÅu). Klasisks un mums aktuÄls piemÄrs Å”Ädiem aprÄÄ·iniem ir dziļÄs neironu tÄ«klu apmÄcÄ«ba, izmantojot gradienta nolaiÅ”anÄs metodi, apejot gradienta tuvinÄjumu katrÄ solÄ« uz nelielÄm novÄrojumu porcijÄm jeb mini-batch.
DziÄ¼Ä mÄcīŔanÄs ietvari, kas uzrakstÄ«ti Python, satur Ä«paÅ”as klases, kas realizÄ datu iterÄtus: tabulas, attÄlus mapÄs, binÄros formÄtos utt. Var izmantot gatavas variants vai arÄ« rakstÄ«t savus specifiskÄm uzdevumiem. R mÄs varam izmantot visas Python bibliotÄkas iespÄjas keras ar tÄs dažÄdajiem backend, izmantojot vienÄdas nosaukuma pakotni, kas darbojas virs pakotnes reticulate. PÄdÄjais pelnÄ«jis atseviŔķu lielu rakstu; tas ne tikai ļauj izpildÄ«t Python kodu no R, bet arÄ« nodroÅ”ina objektu pÄrsÅ«tīŔanu starp R un Python sesijÄm, automÄtiski veicot visus nepiecieÅ”amos tipa pÄrveidojumus.
Mums vairs nav jÄglabÄ visi dati RAM, pateicoties MonetDBLite izmantoÅ”anai, visa "neironu tÄ«kla" darbÄ«ba tiks veikta ar oriÄ£inÄlo Python kodu, mums atliek tikai uzrakstÄ«t datu iterÄtoru, jo gatavu variantu Å”Ädai situÄcijai nav ne R, ne Python. PrasÄ«bas tam faktiski ir divas: tam jÄatgriež batch bezgalÄ«gÄ ciklÄ un jÄsaglabÄ savs stÄvoklis starp iterÄcijÄm (pÄdÄjÄ R tiek vienkÄrÅ”i realizÄta, izmantojot closures). AgrÄk bija nepiecieÅ”ams iekÅ”ÄjÄ iterÄtorÄ skaidri pÄrvÄrst R masÄ«vus numpy masÄ«vos, bet aktuÄlÄ pakotnes versija keras to dara pati.
MÄs iegÅ«stam Å”Ädu iterÄtoru mÄcÄ«bu un validÄcijas datiem:
MÄcÄ«bu un validÄcijas datu iterÄtors
train_generator <- function(db_connection = con,
samples_index,
num_classes = 340,
batch_size = 32,
scale = 1,
color = FALSE,
imagenet_preproc = FALSE) {
# ŠŃŠ¾Š²ŠµŃŠŗŠ° Š°ŃŠ³ŃŠ¼ŠµŠ½ŃŠ¾Š²
checkmate::assert_class(con, "DBIConnection")
checkmate::assert_integerish(samples_index)
checkmate::assert_count(num_classes)
checkmate::assert_count(batch_size)
checkmate::assert_number(scale, lower = 0.001, upper = 5)
checkmate::assert_flag(color)
checkmate::assert_flag(imagenet_preproc)
# ŠŠµŃŠµŠ¼ŠµŃŠøŠ²Š°ŠµŠ¼, ŃŃŠ¾Š±Ń Š±ŃŠ°ŃŃ Šø ŃŠ“алŃŃŃ ŠøŃŠæŠ¾Š»ŃŠ·Š¾Š²Š°Š½Š½Ńе инГекŃŃ Š±Š°ŃŃŠµŠ¹ по поŃŃŠ“ŠŗŃ
dt <- data.table::data.table(id = sample(samples_index))
# ŠŃоŃŃŠ°Š²Š»Ńем Š½Š¾Š¼ŠµŃа баŃŃŠµŠ¹
dt[, batch := (.I - 1L) %/% batch_size + 1L]
# ŠŃŃŠ°Š²Š»Ńем ŃŠ¾Š»Ńко ŠæŠ¾Š»Š½ŃŠµ баŃŃŠø Šø ŠøŠ½Š“ŠµŠŗŃŠøŃŃŠµŠ¼
dt <- dt[, if (.N == batch_size) .SD, keyby = batch]
# Š£ŃŃŠ°Š½Š°Š²Š»ŠøŠ²Š°ŠµŠ¼ ŃŃŃŃŃŠøŠŗ
i <- 1
# ŠŠ¾Š»ŠøŃеŃŃŠ²Š¾ баŃŃŠµŠ¹
max_i <- dt[, max(batch)]
# ŠŠ¾Š“Š³Š¾ŃŠ¾Š²ŠŗŠ° вŃŃŠ°Š¶ŠµŠ½ŠøŃ Š“Š»Ń Š²ŃŠ³ŃŃŠ·ŠŗŠø
sql <- sprintf(
"PREPARE SELECT drawing, label_int FROM doodles WHERE id IN (%s)",
paste(rep("?", batch_size), collapse = ",")
)
res <- DBI::dbSendQuery(con, sql)
# ŠŠ½Š°Š»Š¾Š³ keras::to_categorical
to_categorical <- function(x, num) {
n <- length(x)
m <- numeric(n * num)
m[x * n + seq_len(n)] <- 1
dim(m) <- c(n, num)
return(m)
}
# ŠŠ°Š¼Ńкание
function() {
# ŠŠ°Ńинаем новŃŃ ŃŠæŠ¾Ń
Ń
if (i > max_i) {
dt[, id := sample(id)]
data.table::setkey(dt, batch)
# Š”Š±ŃŠ°ŃŃŠ²Š°ŠµŠ¼ ŃŃŃŃŃŠøŠŗ
i <<- 1
max_i <<- dt[, max(batch)]
}
# ID Š“Š»Ń Š²ŃŠ³ŃŃŠ·ŠŗŠø ГаннŃŃ
batch_ind <- dt[batch == i, id]
# ŠŃгŃŃŠ·ŠŗŠ° ГаннŃŃ
batch <- DBI::dbFetch(DBI::dbBind(res, as.list(batch_ind)), n = -1)
# Š£Š²ŠµŠ»ŠøŃŠøŠ²Š°ŠµŠ¼ ŃŃŃŃŃŠøŠŗ
i <<- i + 1
# ŠŠ°ŃŃŠøŠ½Š³ JSON Šø ŠæŠ¾Š“Š³Š¾ŃŠ¾Š²ŠŗŠ° маŃŃŠøŠ²Š°
batch_x <- cpp_process_json_vector(batch$drawing, scale = scale, color = color)
if (imagenet_preproc) {
# ŠØŠŗŠ°Š»ŠøŃŠ¾Š²Š°Š½ŠøŠµ c ŠøŠ½ŃŠµŃвала [0, 1] на ŠøŠ½ŃеŃвал [-1, 1]
batch_x <- (batch_x - 0.5) * 2
}
batch_y <- to_categorical(batch$label_int, num_classes)
result <- list(batch_x, batch_y)
return(result)
}
}Funkcija pieÅem mainÄ«go ar savienojumu ar DB, izmantoto rindu numurus, klaÅ”u skaitu, batch izmÄru, mÄrogu (scale = 1 atbilst attÄlu zÄ«mÄÅ”anai ar izmÄru 256x256 pikseļi, scale = 0.5 - 128x128 pikseļi), krÄsu indikators (color = FALSE nosaka zÄ«mÄÅ”anu pelÄkajos toÅos, izmantojot krÄsa = TRUE katrs vilciena posms tiek uzzÄ«mÄts ar jaunu krÄsu) un priekÅ”apstrÄdes indikators tÄ«klam, kas iepriekÅ” apmÄcÄ«ts ar imagenet. PÄdÄjais nepiecieÅ”ams, lai pÄrskalotu pikseļu vÄrtÄ«bas no intervÄla [0, 1] uz intervÄlu [-1, 1], kas tika izmantots apmÄcÄ«bÄ. keras modeļiem.
ÄrÄjÄ funkcija satur argumentu tipu pÄrbaudi, tabulu data.table ar nejauÅ”i sajauktiem rindu numuriem no samples_index un partiju numuriem, skaitÄ«tÄju un maksimÄlo partiju skaitu, kÄ arÄ« SQL izteiksmi datu izgūŔanai no DB. Papildus mÄs definÄjÄm Ätru analogu funkcijai keras::to_categorical(). MÄs izmantojÄm gandrÄ«z visus datus apmÄcÄ«bai, atstÄjot pusprocentu validÄcijai, tÄpÄc epohas lielums tika ierobežots ar parametru steps_per_epoch aicinÄjuma laikÄ, tÄdÄļ visi keras::fit_generator(), un nosacÄ«jums if (i > max_i) tika aktivizÄts tikai validÄcijas iteratoram.
IekÅ”ÄjÄ funkcijÄ notiek rindu indeksu atlasīŔana nÄkamajai partijai, ierakstu izgūŔana no DB ar partiju skaitÄ«tÄja palielinÄÅ”anu, JSON parsÄÅ”ana (funkcija cpp_process_json_vector(), uzrakstÄ«ta C++) un attÄlu atbilstoÅ”u masÄ«vu izveide. Tad tiek izveidoti one-hot vektori ar klases atzÄ«mÄm, masÄ«vi ar pikseļu vÄrtÄ«bÄm un atzÄ«mes tiek apvienotas sarakstÄ, kas ir atgriezamÄ vÄrtÄ«ba. Lai paÄtrinÄtu darbu, tika izmantota indeksu izveide tabulÄs data.table un atsauces modificÄÅ”ana ā bez Ŕīm "fīŔkÄm" pakete data.table uz vietas diezgan grÅ«ti iedomÄties efektÄ«vu darbu ar ievÄrojamiem datu apjomiem R.
VeiktspÄjas mÄrÄ«jumu rezultÄti uz klÄpjdatora ar Core i5 izskatÄs Å”Ädi:
Iteratora salīdzinoŔais tests
library(Rcpp)
library(keras)
library(ggplot2)
source("utils/rcpp.R")
source("utils/keras_iterator.R")
con <- DBI::dbConnect(drv = MonetDBLite::MonetDBLite(), Sys.getenv("DBDIR"))
ind <- seq_len(DBI::dbGetQuery(con, "SELECT count(*) FROM doodles")[[1L]])
num_classes <- DBI::dbGetQuery(con, "SELECT max(label_int) + 1 FROM doodles")[[1L]]
# Indeksi mÄcÄ«bu paraugam
train_ind <- sample(ind, floor(length(ind) * 0.995))
# Indeksi pÄrbaudes paraugam
val_ind <- ind[-train_ind]
rm(ind)
# MÄrogoÅ”anas koeficients
scale <- 0.5
# MÄrīŔanas veikÅ”ana
res_bench <- bench::press(
batch_size = 2^(4:10),
{
it1 <- train_generator(
db_connection = con,
samples_index = train_ind,
num_classes = num_classes,
batch_size = batch_size,
scale = scale
)
bench::mark(
it1(),
min_iterations = 50L
)
}
)
# Benchmarks parametri
cols <- c("batch_size", "min", "median", "max", "itr/sec", "total_time", "n_itr")
res_bench[, cols]
# batch_size min median max `itr/sec` total_time n_itr
#
# 1 16 25ms 64.36ms 92.2ms 15.9 3.09s 49
# 2 32 48.4ms 118.13ms 197.24ms 8.17 5.88s 48
# 3 64 69.3ms 117.93ms 181.14ms 8.57 5.83s 50
# 4 128 157.2ms 240.74ms 503.87ms 3.85 12.71s 49
# 5 256 359.3ms 613.52ms 988.73ms 1.54 30.5s 47
# 6 512 884.7ms 1.53s 2.07s 0.674 1.11m 45
# 7 1024 2.7s 3.83s 5.47s 0.261 2.81m 44
ggplot(res_bench, aes(x = factor(batch_size), y = median, group = 1)) +
geom_point() +
geom_line() +
ylab("mediana laiks, s") +
theme_minimal()
DBI::dbDisconnect(con, shutdown = TRUE) 
Ja ir pietiekami daudz RAM, datubÄzes darbu var ievÄrojami paÄtrinÄt, pÄrvietojot to uz Å”o paÅ”u RAM (mÅ«su uzdevumam pietiek ar 32 GB). LinuxÄ pÄc noklusÄjuma tiek montÄts nodalÄ«jums /dev/shm, kas aizÅem lÄ«dz puscieto disku apjoma. Var norÄdÄ«t arÄ« vairÄk, rediÄ£Äjot /etc/fstab, lai iegÅ«tu ierakstu formÄtÄ tmpfs /dev/shm tmpfs defaults,size=25g 0 0. ObligÄti restartÄjiet un pÄrbaudiet rezultÄtu, izpildot komandu df -h.
Iterators testÄÅ”anas datiem izskatÄs daudz vienkÄrÅ”Äk, jo testÄÅ”anas datu kopums pilnÄ«bÄ ietilpst RAM:
Iterators testÄÅ”anas datiem
test_generator <- function(dt,
batch_size = 32,
scale = 1,
color = FALSE,
imagenet_preproc = FALSE) {
# ŠŃŠ¾Š²ŠµŃŠŗŠ° Š°ŃŠ³ŃŠ¼ŠµŠ½ŃŠ¾Š²
checkmate::assert_data_table(dt)
checkmate::assert_count(batch_size)
checkmate::assert_number(scale, lower = 0.001, upper = 5)
checkmate::assert_flag(color)
checkmate::assert_flag(imagenet_preproc)
# ŠŃоŃŃŠ°Š²Š»Ńем Š½Š¾Š¼ŠµŃа баŃŃŠµŠ¹
dt[, batch := (.I - 1L) %/% batch_size + 1L]
data.table::setkey(dt, batch)
i <- 1
max_i <- dt[, max(batch)]
# ŠŠ°Š¼Ńкание
function() {
batch_x <- cpp_process_json_vector(dt[batch == i, drawing],
scale = scale, color = color)
if (imagenet_preproc) {
# ŠØŠŗŠ°Š»ŠøŃŠ¾Š²Š°Š½ŠøŠµ c ŠøŠ½ŃŠµŃвала [0, 1] на ŠøŠ½ŃеŃвал [-1, 1]
batch_x <- (batch_x - 0.5) * 2
}
result <- list(batch_x)
i <<- i + 1
return(result)
}
}4. Modeļa arhitektÅ«ras izvÄle
PirmÄ no izmantotajÄm arhitektÅ«rÄm bija , kuras Ä«patnÄ«bas ir izskatÄ«tas ziÅojumÄ. TÄ klÄtesoÅ”ie standarta komplektÄ keras un, attiecÄ«gi, pieejams vienÄ un tajÄ paÅ”Ä paketÄ R. TaÄu, mÄÄ£inot to izmantot ar vienkanÄlu attÄliem, izrÄdÄ«jÄs dÄ«vaini: ieejas tenzora izmÄram vienmÄr jÄbÅ«t formÄtÄ (batch, height, width, 3), tas nozÄ«mÄ, ka kanÄlu skaitu nevar mainÄ«t. Python Å”Ädu ierobežojumu neuzliek, tÄpÄc mÄs steigÄ uzrakstÄ«jÄm savu Ŕīs arhitektÅ«ras realizÄciju, sekojot oriÄ£inÄlajam rakstam (bez izslÄgÅ”anas, kas ir keras versijÄ):
Mobilenet v1 arhitektūra
library(keras)
top_3_categorical_accuracy <- custom_metric(
name = "top_3_categorical_accuracy",
metric_fn = function(y_true, y_pred) {
metric_top_k_categorical_accuracy(y_true, y_pred, k = 3)
}
)
layer_sep_conv_bn %
layer_batch_normalization() %>%
layer_activation_relu() %>%
layer_conv_2d(
filters = filters * alpha,
kernel_size = c(1, 1),
strides = c(1, 1)
) %>%
layer_batch_normalization() %>%
layer_activation_relu()
}
get_mobilenet_v1 <- function(input_shape = c(224, 224, 1),
num_classes = 340,
alpha = 1,
depth_multiplier = 1,
optimizer = optimizer_adam(lr = 0.002),
loss = "categorical_crossentropy",
metrics = c("categorical_crossentropy",
top_3_categorical_accuracy)) {
inputs <- layer_input(shape = input_shape)
outputs %
layer_conv_2d(filters = 32, kernel_size = c(3, 3), strides = c(2, 2), padding = "same") %>%
layer_batch_normalization() %>%
layer_activation_relu() %>%
layer_sep_conv_bn(filters = 64, strides = c(1, 1)) %>%
layer_sep_conv_bn(filters = 128, strides = c(2, 2)) %>%
layer_sep_conv_bn(filters = 128, strides = c(1, 1)) %>%
layer_sep_conv_bn(filters = 256, strides = c(2, 2)) %>%
layer_sep_conv_bn(filters = 256, strides = c(1, 1)) %>%
layer_sep_conv_bn(filters = 512, strides = c(2, 2)) %>%
layer_sep_conv_bn(filters = 512, strides = c(1, 1)) %>%
layer_sep_conv_bn(filters = 512, strides = c(1, 1)) %>%
layer_sep_conv_bn(filters = 512, strides = c(1, 1)) %>%
layer_sep_conv_bn(filters = 512, strides = c(1, 1)) %>%
layer_sep_conv_bn(filters = 512, strides = c(1, 1)) %>%
layer_sep_conv_bn(filters = 1024, strides = c(2, 2)) %>%
layer_sep_conv_bn(filters = 1024, strides = c(1, 1)) %>%
layer_global_average_pooling_2d() %>%
layer_dense(units = num_classes) %>%
layer_activation_softmax()
model % compile(
optimizer = optimizer,
loss = loss,
metrics = metrics
)
return(model)
}Å Ä« pieeja acÄ«mredzami ir nepilnÄ«ga. Gribas pÄrbaudÄ«t daudz modeļu, bet negribas katru arhitektÅ«ru pÄrrakstÄ«t manuÄli. MÄs arÄ« bijÄm izslÄgti no iespÄjas izmantot modeļu svarus, kas jau iepriekÅ” apmÄcÄ«ti uz imagenet. Parasti palÄ«dz dokumentÄcijas izpÄte. Funkcija get_config() ļauj iegÅ«t modeļa aprakstu rediÄ£ÄjamÄ formÄtÄ (base_model_conf$layers ā parasts saraksts), bet funkcija from_config() veic atpakaļ pÄrveidoÅ”anu modelÄjamÄ objektÄ:
base_model_conf <- get_config(base_model)
base_model_conf$layers[[1]]$config$batch_input_shape[[4]] <- 1L
base_model <- from_config(base_model_conf)Tagad nav grÅ«ti uzrakstÄ«t universÄlu funkciju jebkuras no piegÄdÄtajÄm keras modeļiem ar apmÄcÄ«tiem imagenet svaru vai bez tiem:
Funkcija gatavo arhitektÅ«ru ielÄdei
get_model <- function(name = "mobilenet_v2",
input_shape = NULL,
weights = "imagenet",
pooling = "avg",
num_classes = NULL,
optimizer = keras::optimizer_adam(lr = 0.002),
loss = "categorical_crossentropy",
metrics = NULL,
color = TRUE,
compile = FALSE) {
# Argumentu pÄrbaude
checkmate::assert_string(name)
checkmate::assert_integerish(input_shape, lower = 1, upper = 256, len = 3)
checkmate::assert_count(num_classes)
checkmate::assert_flag(color)
checkmate::assert_flag(compile)
# Iegūstam objektu no keras paketes
model_fun <- get0(paste0("application_", name), envir = asNamespace("keras"))
# PÄrbaudÄm objekta esamÄ«bu paketÄ
if (is.null(model_fun)) {
stop("Model ", shQuote(name), " netika atrasts.", call. = FALSE)
}
base_model <- model_fun(
input_shape = input_shape,
include_top = FALSE,
weights = weights,
pooling = pooling
)
# Ja attÄls nav krÄsains, mainÄm ieejas dimensiju
if (!color) {
base_model_conf <- keras::get_config(base_model)
base_model_conf$layers[[1]]$config$batch_input_shape[[4]] <- 1L
base_model <- keras::from_config(base_model_conf)
}
predictions <- keras::get_layer(base_model, "global_average_pooling2d_1")$output
predictions <- keras::layer_dense(predictions, units = num_classes, activation = "softmax")
model <- keras::keras_model(
inputs = base_model$input,
outputs = predictions
)
if (compile) {
keras::compile(
object = model,
optimizer = optimizer,
loss = loss,
metrics = metrics
)
}
return(model)
}Izmantojot vienkanÄla attÄlus, iepriekÅ” apmÄcÄ«tie svari netiek izmantoti. To varÄtu labot: izmantojot funkciju get_weights() saÅemt modeļa svarus saraksta veidÄ no R masÄ«viem, mainÄ«t pirmÄ elementa dimensiju (Åemot kÄdu vienu krÄsu kanÄlu vai vidÄjot visus trÄ«s), un pÄc tam ielÄdÄt svarus atpakaļ modelÄ« ar funkciju set_weights(). MÄs Å”o funkcionalitÄti tÄ arÄ« neieviesÄm, jo Å”ajÄ posmÄ bija skaidrs, ka produktÄ«vÄk ir strÄdÄt ar krÄsainiem attÄliem.
LielÄko daļu eksperimentu mÄs veicÄm, izmantojot mobilenet versijas 1 un 2, kÄ arÄ« resnet34. Å ajÄ sacensÄ«bÄ labi parÄdÄ«jÄs modernÄkas arhitektÅ«ras, piemÄram, SE-ResNeXt. DiemžÄl mÅ«su rÄ«cÄ«bÄ nebija gatavu realizÄciju, bet mÄs paÅ”reizÄjo izstrÄdÄsim (bet noteikti izstrÄdÄsim).
5. Skriptu parametru iestatīŔana
Lai atvieglotu darbu, viss kods apmÄcÄ«bas uzsÄkÅ”anai tika sakÄrtots vienÄ skriptÄ, ko parametrizÄ ar Å”eit:
doc <- '
Usage:
train_nn.R --help
train_nn.R --list-models
train_nn.R [options]
Options:
-h --help RÄdÄ«t Å”o ziÅojumu.
-l --list-models Saraksts ar pieejamajiem modeļiem.
-m --model= Neironu tÄ«kla modeļa nosaukums [noklusÄjums: mobilenet_v2].
-b --batch-size= Partijas izmÄrs [noklusÄjums: 32].
-s --scale-factor= MÄrogoÅ”anas faktors [noklusÄjums: 0.5].
-c --color Izmantot krÄsainas lÄ«nijas [noklusÄjums: FALSE].
-d --db-dir= CeļŔ uz datu bÄzes katalogu [noklusÄjums: Sys.getenv("db_dir")].
-r --validate-ratio= ApstiprinÄÅ”anas paraugu attiecÄ«ba [noklusÄjums: 0.995].
-n --n-gpu= GPU skaits [noklusÄjums: 1].
'
args <- docopt::docopt(doc)Pakete docopt ir realizÄcija R valodÄ. TÄ palÄ«dz palaist skriptus ar vienkÄrÅ”Äm komandÄm, piemÄram, Rscript bin/train_nn.R -m resnet50 -c -d /home/andrey/doodle_db vai ./bin/train_nn.R -m resnet50 -c -d /home/andrey/doodle_db, ja fails train_nn.R ir izpildÄms (Ŕī komanda uzsÄks modeļa apmÄcÄ«bu resnet50 trÄ«s kanÄlu attÄlos ar izmÄriem 128x128 pikseļu, datu bÄze jÄnovieto mapÄ /home/andrey/doodle_db). SarakstÄ var pievienot mÄcīŔanÄs Ätrumu, optimizatora veidu un jebkurus citus konfigurÄjamos parametrus. SagatavoÅ”anÄs publikÄcijai noskaidrojÄs, ka arhitektÅ«ra mobilenet_v2 no aktuÄlÄs versijas keras R izmantoÅ”ana ir saistÄ«ta ar R pakotnÄ neiekļautÄm izmaiÅÄm ā gaidÄm, kamÄr tÄs novÄrsÄ«s.
Å Ä« pieeja ļÄva bÅ«tiski paÄtrinÄt eksperimentus ar dažÄdiem modeļiem salÄ«dzinÄjumÄ ar tradicionÄlo skriptu palaiÅ”anu RStudio (kÄ iespÄjamu alternatÄ«vu var minÄt pakotni ). Bet galvenÄ priekÅ”rocÄ«ba ir iespÄja viegli pÄrvaldÄ«t skriptu izpildi Docker vai vienkÄrÅ”i uz servera, neinstalÄjot RStudio.
6. Skriptu DockerizÄcija
MÄs izmantojÄm Docker, lai nodroÅ”inÄtu vides pÄrnesamÄ«bu modeļu apmÄcÄ«bai starp komandas locekļiem un Ätru izvietoÅ”anu mÄkoÅos. SÄkt iepazīŔanos ar Å”o relativi neierasto instrumentu R programmÄtÄjam var ar publikÄciju sÄriju vai ar .
Docker ļauj gan izveidot savus attÄlus "no nulles", gan izmantot citus attÄlus kÄ pamatu saviem. AnalizÄjot pieejamos variantus, mÄs secinÄjÄm, ka NVIDIA, CUDA+cuDNN un Python bibliotÄku instalÄÅ”ana ir diezgan apjomÄ«ga daļa no attÄla, tÄpÄc nolÄmÄm izmantot oficiÄlo attÄlu tensorflow/tensorflow:1.12.0-gpu, pievienojot nepiecieÅ”amos R paketus.
GalÄ«gais Docker fails izskatÄ«jÄs Å”Ädi:
Dockerfile
FROM tensorflow/tensorflow:1.12.0-gpu
MAINTAINER Artem Klevtsov
SHELL ["/bin/bash", "-c"]
ARG LOCALE="en_US.UTF-8"
ARG APT_PKG="libopencv-dev r-base r-base-dev littler"
ARG R_BIN_PKG="futile.logger checkmate data.table rcpp rapidjsonr dbi keras jsonlite curl digest remotes"
ARG R_SRC_PKG="xtensor RcppThread docopt MonetDBLite"
ARG PY_PIP_PKG="keras"
ARG DIRS="/db /app /app/data /app/models /app/logs"
RUN source /etc/os-release &&
echo "deb https://cloud.r-project.org/bin/linux/ubuntu ${UBUNTU_CODENAME}-cran35/" > /etc/apt/sources.list.d/cran35.list &&
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E084DAB9 &&
add-apt-repository -y ppa:marutter/c2d4u3.5 &&
add-apt-repository -y ppa:timsc/opencv-3.4 &&
apt-get update &&
apt-get install -y locales &&
locale-gen ${LOCALE} &&
apt-get install -y --no-install-recommends ${APT_PKG} &&
ln -s /usr/lib/R/site-library/littler/examples/install.r /usr/local/bin/install.r &&
ln -s /usr/lib/R/site-library/littler/examples/install2.r /usr/local/bin/install2.r &&
ln -s /usr/lib/R/site-library/littler/examples/installGithub.r /usr/local/bin/installGithub.r &&
echo 'options(Ncpus = parallel::detectCores())' >> /etc/R/Rprofile.site &&
echo 'options(repos = c(CRAN = "https://cloud.r-project.org"))' >> /etc/R/Rprofile.site &&
apt-get install -y $(printf "r-cran-%s " ${R_BIN_PKG}) &&
install.r ${R_SRC_PKG} &&
pip install ${PY_PIP_PKG} &&
mkdir -p ${DIRS} &&
chmod 777 ${DIRS} &&
rm -rf /tmp/downloaded_packages/ /tmp/*.rds &&
rm -rf /var/lib/apt/lists/*
COPY utils /app/utils
COPY src /app/src
COPY tests /app/tests
COPY bin/*.R /app/
ENV DBDIR="/db"
ENV CUDA_HOME="/usr/local/cuda"
ENV PATH="/app:${PATH}"
WORKDIR /app
VOLUME /db
VOLUME /app
CMD bash
Lai atvieglotu lietoto pakotÅu pÄrvaldÄ«bu, tÄs tika izdalÄ«tas mainÄ«gajos; galvenÄ daļa uzrakstÄ«tÄs skriptu tiek kopÄta konteineru iekÅ”ienÄ to izveides laikÄ. MÄs arÄ« mainÄ«jÄm komandrindas vidi uz /bin/bash lai atvieglotu satura izmantoÅ”anu /etc/os-release. Tas novÄrsa nepiecieÅ”amÄ«bu norÄdÄ«t operÄtÄjsistÄmas versiju kodÄ.
Papildu tika uzrakstÄ«ts neliels bash skripts, kas ļauj palaist konteineru ar dažÄdÄm komandÄm. PiemÄram, tas varÄtu bÅ«t skripti neironu tÄ«klu apmÄcÄ«bai, kas iepriekÅ” tika ievietoti konteinerÄ, vai komandrindas vide, lai debugotu un uzraudzÄ«tu konteineru darbu:
Skripts konteineram palaiŔanai
#!/bin/sh
DBDIR=${PWD}/db
LOGSDIR=${PWD}/logs
MODELDIR=${PWD}/models
DATADIR=${PWD}/data
ARGS="--runtime=nvidia --rm -v ${DBDIR}:/db -v ${LOGSDIR}:/app/logs -v ${MODELDIR}:/app/models -v ${DATADIR}:/app/data"
if [ -z "$1" ]; then
CMD="Rscript /app/train_nn.R"
elif [ "$1" = "bash" ]; then
ARGS="${ARGS} -ti"
else
CMD="Rscript /app/train_nn.R $@"
fi
docker run ${ARGS} doodles-tf ${CMD}Ja, ja palaidzot Å”o bash skriptu bez parametriem, tiks izsaukts skripts ar noklusÄjuma vÄrtÄ«bÄm iekÅ” konteitera; ja pirmais pozicionÄlais arguments ir "bash", tad konteineris tiks palaists interaktÄ«vajÄ režīmÄ ar komandu apstrÄdÄtÄju. Visos citos gadÄ«jumos notiek pozicionÄlo argumentu vÄrtÄ«bu aizvietoÅ”ana: train_nn.R CMD="Rscript /app/train_nn.R $@" Ir vÄrts pievÄrst uzmanÄ«bu, ka direktorijas ar izejas datiem un datu bÄzi, kÄ arÄ« direktorija, kurÄ tiek glabÄtas apmÄcÄ«tÄs modeļi, tiek montÄtas iekÅ” konteitera no viesmaŔīnas, kas ļauj piekļūt skriptu darba rezultÄtiem bez liekÄm manipulÄcijÄm..
7. Daudzu GPU izmantoŔana Google Cloud
Viena no konkursa Ä«paŔībÄm bija diezgan trokÅ”Åainie dati (skat. virsrakstu attÄlu, ko aizguvusi no @Leigh.plt no ODS Slack). Ar lielu partiju palÄ«dzÄ«bu mÄs izdevÄ«gi risinÄjÄm Å”o problÄmu, un pÄc eksperimentiem uz datora ar 1 GPU, nolÄmÄm apgÅ«t modeļu apmÄcÄ«bu uz vairÄkiem GPU mÄkonÄ«. IzmantojÄm Google Cloud (
labs ceļvedis par pamatiemdev/shm VislielÄko interesi rada koda fragments, kas atbild par vairÄku GPU izmantoÅ”anu. Vispirms modelis tiek izveidots CPU uz CPU, izmantojot konteksta menedžeri, tÄpat kÄ PythonÄ:.
with(tensorflow::tf$device("/cpu:0"), { model_cpu <- get_model( name = model_name, input_shape = input_shape, weights = weights, metrics =(top_3_categorical_accuracy, compile = FALSE ) })
Tad nekompilÄtais (tas ir svarÄ«gi) modelis tiek kopÄts uz norÄdÄ«to pieejamo GPU skaitu, un tikai pÄc tam tiek kompilÄts:model <- keras::multi_gpu_model(model_cpu, gpus = n_gpu) keras::compile( object = model, optimizer = keras::optimizer_adam(lr = 0.0004), loss = "categorical_crossentropy", metrics = c(top_3_categorical_accuracy) )
Klasisko pieeju ar visu slÄÅu sasaldÄÅ”anu, izÅemot pÄdÄjo, pÄdÄjÄ slÄÅa apmÄcÄ«bu, atsaldÄÅ”anu un modeļa apmÄcīŔanu kopumÄ vairÄkiem GPU, realizÄt neizdevÄs.ApmÄcÄ«bas procesu uzraudzÄ«jÄm bez
tensorboard tensorboard, ierobežojoties ar žurnÄlu ierakstiem un modeļu saglabÄÅ”anu ar informatÄ«vÄm nosaukumiem pÄc katras epochas:
AtgriezeniskÄs saites
# Шаблон имени ŃŠ°Š¹Š»Š° лога
log_file_tmpl <- file.path("logs", sprintf(
"%s_%d_%dch_%s.csv",
model_name,
dim_size,
channels,
format(Sys.time(), "%Y%m%d%H%M%OS")
))
# Шаблон имени ŃŠ°Š¹Š»Š° моГели
model_file_tmpl <- file.path("models", sprintf(
"%s_%d_%dch_{epoch:02d}_{val_loss:.2f}.h5",
model_name,
dim_size,
channels
))
callbacks_list <- list(
keras::callback_csv_logger(
filename = log_file_tmpl
),
keras::callback_early_stopping(
monitor = "val_loss",
min_delta = 1e-4,
patience = 8,
verbose = 1,
mode = "min"
),
keras::callback_reduce_lr_on_plateau(
monitor = "val_loss",
factor = 0.5, # ŃŠ¼ŠµŠ½ŃŃŠ°ŠµŠ¼ lr в 2 ŃŠ°Š·Š°
patience = 4,
verbose = 1,
min_delta = 1e-4,
mode = "min"
),
keras::callback_model_checkpoint(
filepath = model_file_tmpl,
monitor = "val_loss",
save_best_only = FALSE,
save_weights_only = FALSE,
mode = "min"
)
)8. AizvÄrums
VairÄku problÄmu, ar kurÄm saskÄrÄmies, risinÄÅ”ana vÄl nav izdevusies:
- par keras nav gatavas funkcijas automÄtiskai optimÄlÄs mÄcÄ«bu Ätruma meklÄÅ”anai (analogs
lr_finderbibliotÄkÄ fast.ai); nedaudz piepÅ«loties, var pÄrvietot uz R treÅ”o puÅ”u realizÄcijas, piemÄram, ; - kÄ iepriekÅ” minÄtÄ punkta sekas, neizdevÄs atrast pareizo mÄcÄ«bu Ätrumu, izmantojot vairÄkus GPU;
- trÅ«kst mÅ«sdienu neironu tÄ«klu arhitektÅ«ru, it Ä«paÅ”i priekÅ”apmÄcÄ«tas uz imagenet;
- nav one cycle policy un diskriminatÄ«vie mÄcÄ«bu Ätrumi (cosine annealing pÄc mÅ«su lÅ«guma bija , paldies ).
Ko noderÄ«gu izdevÄs gÅ«t no Ŕī konkursa:
- Uz salÄ«dzinoÅ”i vÄjÄm iekÄrtÄm var bez grÅ«tÄ«bÄm strÄdÄt ar pietiekami ievÄrojamÄm (daudz lielÄkÄm par RAM izmÄru) datu apjomiem. Pakete data.table ietaupa atmiÅu, veicot in-place modifikÄciju tabulÄm, kas ļauj izvairÄ«ties no to kopÄÅ”anas, un, pareizi izmantojot tÄs iespÄjas, gandrÄ«z vienmÄr demonstrÄ visaugstÄko Ätrumu starp visiem zinÄmajiem rÄ«kiem skriptu valodÄm. Datu saglabÄÅ”ana datu bÄzÄ Ä¼auj daudzÄs situÄcijÄs vispÄr nedomÄt par nepiecieÅ”amÄ«bu iekļaut visu datu kopu RAM.
- LÄnas funkcijas R var aizstÄt ar ÄtrÄm C++ izmantojot paketi Rcpp. Ja papildus izmantosim RcppThread vai RcppParallel, iegÅ«stam multiplatformiskas daudzplÅ«smu realizÄcijas, tÄpÄc R lÄ«meÅa kods nav jÄparalÄ.
- PaketÄ Rcpp var lietot bez nopietnÄm zinÄÅ”anÄm par C++, nepiecieÅ”amais minimums ir izklÄstÄ«ts . Virsrakstu faili vairÄkiem kruta C bibliotÄkÄm, piemÄram, xtensor ir pieejami CRAN, tas nozÄ«mÄ, ka tiek veidota infrastruktÅ«ra projektu Ä«stenoÅ”anai, kas integrÄ R gatavo augstas veiktspÄjas C++ kodu. Papildu ÄrtÄ«ba ā sintakse krÄsoÅ”ana un statiskais koda analizators C++ RStudio.
- docopt ļauj izpildÄ«t patstÄvÄ«gus skriptus ar parametriem. Tas ir Ärti izmantot attÄlinÄtajos serveros, tostarp Docker. RStudio veikt daudzu stundu eksperimentus ar neironu tÄ«klu apmÄcÄ«bu nav Ärti, un pati IDE uzstÄdīŔana serverÄ« ne vienmÄr ir pamatota.
- Docker nodroÅ”ina koda pÄrnesamÄ«bu un rezultÄtu reproducÄjamÄ«bu starp izstrÄdÄtÄjiem ar dažÄdÄm OS un bibliotÄru versijÄm, kÄ arÄ« vienkÄrŔību palaiÅ”anai uz serveriem. Visa mÄcīŔanÄs procesa palaiÅ”ana iespÄjama ar vienu komandu.
- Google Cloud ir budžeta veids, kÄ eksperimentÄt ar dÄrgu aparatÅ«ru, bet ir jÄizvÄlas konfigurÄcijas pÄrdomÄti.
- Ä»oti noderÄ«gi ir izmÄrÄ«t atseviŔķu koda fragmentu veiktspÄju, Ä«paÅ”i apvienojot R un C++, bet ar pakotni bench ā vÄl jo vairÄk, tas ir ļoti viegli.
KopumÄ Å”Ä« pieredze bija ļoti noderÄ«ga, un mÄs turpinÄm strÄdÄt pie dažÄm no izteiktajÄm problÄmÄm.
Avots: habr.com
