Fanekena Doodle Draw haingana: fomba fanaovana namana amin'ny R, C ++ ary tambajotra neural

Fanekena Doodle Draw haingana: fomba fanaovana namana amin'ny R, C ++ ary tambajotra neural

Hey Habr!

Tamin'ny fararano lasa teo, dia nampiantrano fifaninanana hanasokajiana ny sary vita tanana, Quick Draw Doodle Recognition i Kaggle, izay anisan'ny nandraisan'ny ekipan'ny R-scientists: Artem Klevtsova, Philippa Manager ΠΈ Andrey Ogurtsov. Tsy holazainay amin'ny antsipiriany ny fifaninanana fa efa natao tao famoahana vao haingana.

Tamin'ity indray mitoraka ity dia tsy nahomby tamin'ny fiompiana medaly, fa traikefa sarobidy maro no azo, noho izany dia te-hilaza amin'ny fiaraha-monina momba ny zavatra mahaliana sy mahasoa maro momba an'i Kagle sy amin'ny asa andavanandro aho. Anisan’ny noresahina: fiainana sarotra tsy misy OpenCV, JSON parsing (ireo ohatra ireo dia mandinika ny fampidirana ny code C++ amin'ny script na fonosana amin'ny R mampiasa Rcpp), famaritana ny script sy ny dockerization ny vahaolana farany. Ny kaody rehetra avy amin'ny hafatra amin'ny endrika sahaza ho an'ny famonoana dia misy ao repository.

Hevitra ato Anatiny:

  1. Ampidiro am-pahombiazana ny angona avy amin'ny CSV mankany amin'ny MonetDB
  2. Manomana andiany
  3. Iterators amin'ny famoahana batch avy amin'ny angon-drakitra
  4. Mifidy Architecture Modely
  5. Famaritana ny script
  6. Dockerization ny script
  7. Mampiasa GPU marobe amin'ny Google Cloud
  8. Raha tokony ny famaranana

1. Ampidiro amin'ny fomba mahomby ny angona avy amin'ny CSV mankany amin'ny angona MonetDB

Ny angon-drakitra amin'ity fifaninanana ity dia tsy omena amin'ny endrika sary efa vita, fa amin'ny endrika rakitra CSV 340 (rakitra iray ho an'ny kilasy tsirairay) misy JSON misy koordinate. Amin'ny fampifandraisana ireo teboka amin'ny tsipika ireo dia mahazo sary farany mirefy 256x256 piksel. Ho an'ny firaketana tsirairay ihany koa dia misy etikety manondro raha nahafantatra tsara ilay sary tamin'ny mpanasokajy nampiasaina tamin'ny fotoana nanangonana ny angon-drakitra, fehezan-dalΓ na misy litera roa amin'ny firenena onenan'ny mpanoratra ny sary, famantarana tokana, marika famantaranandro ary anaran-kilasy mifanaraka amin'ny anaran'ny rakitra. Ny dikan-teny notsorina tamin'ny angona tany am-boalohany dia milanja 7.4 GB ao amin'ny arisiva ary eo amin'ny 20 GB eo ho eo aorian'ny famoahana azy, ny angon-drakitra feno aorian'ny famongorana dia mahatratra 240 GB. Ny mpikarakara dia niantoka fa ny dikan-teny roa dia naverin'ny sary iray mitovy, midika izany fa ny dikan-teny feno dia miverimberina. Na ahoana na ahoana, ny fitehirizana sary 50 tapitrisa amin'ny rakitra sary na amin'ny endrika array dia noheverina ho tsy mahasoa avy hatrany, ary nanapa-kevitra izahay fa hanambatra ny rakitra CSV rehetra avy amin'ny arisiva. train_simplified.zip ao amin'ny angon-drakitra miaraka amin'ny taranaka sary manaraka ny habe ilaina "amin'ny sidina" ho an'ny andiany tsirairay.

Rafitra voaporofo tsara no nofidiana ho DBMS MonetDB, izany hoe fampiharana ho an'ny R ho fonosana MonetDBLite. Ny fonosana dia misy dikan-teny mipetaka amin'ny mpizara database ary ahafahanao maka ny mpizara mivantana avy amin'ny fivoriana R ary miara-miasa aminy ao. Ny famoronana angon-drakitra sy ny fampifandraisana azy dia atao amin'ny baiko iray:

con <- DBI::dbConnect(drv = MonetDBLite::MonetDBLite(), Sys.getenv("DBDIR"))

Mila mamorona latabatra roa isika: ny iray ho an'ny angon-drakitra rehetra, ny iray ho an'ny fampahalalana momba ny serivisy momba ny rakitra alaina (ilaina raha misy tsy mety ary tsy maintsy averina ny dingana rehefa avy misintona rakitra maromaro):

Famoronana tabilao

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"
    )
  )
}

Ny fomba haingana indrindra hampidirana angona ao amin'ny tahiry dia ny mandika mivantana ny rakitra CSV amin'ny fampiasana SQL - command COPY OFFSET 2 INTO tablename FROM path USING DELIMITERS ',','n','"' NULL AS '' BEST EFFORTizay tablename - anarana latabatra sy path - ny lalana mankany amin'ny rakitra. Raha niasa tamin'ny arsiva, dia hita fa ny naorina-in fampiharana unzip amin'ny R dia tsy mandeha tsara amin'ny rakitra maromaro avy amin'ny arisiva, noho izany dia nampiasa ny rafitra izahay unzip (mampiasa ny parameter getOption("unzip")).

Asa fanoratana amin'ny angon-drakitra

#' @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))
}

Raha mila manova ny latabatra ianao alohan'ny hanoratana azy amin'ny angon-drakitra, dia ampy ny mandalo amin'ny adihevitra preprocess asa izay hanova ny angona.

Kaody ho an'ny fametrahana angon-drakitra misesy ao anaty angon-drakitra:

Manoratra angona amin'ny angona

# Бписок Ρ„Π°ΠΉΠ»ΠΎΠ² для записи
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->SSD

Mety hiovaova arakaraka ny toetran'ny hafainganam-pandehan'ny fiara ampiasaina ny fotoana fandefasana angona. Amin'ity tranga ity, ny famakiana sy fanoratana ao anatin'ny SSD iray na avy amin'ny kapila tselatra (rakitra loharano) mankany amin'ny SSD (DB) dia mitaky latsaky ny 10 minitra.

Mitaky segondra vitsy vao mamorona tsanganana misy marika kilasy integer sy tsanganana fanondro (ORDERED INDEX) miaraka amin'ny laharan'ny tsipika izay hanaovana santionany ny fandinihana rehefa mamorona andiany:

Mamorona Tsanganana sy Fanondroana fanampiny

message("Generate lables")
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)"))

Mba hamahana ny olana amin'ny famoronana batch amin'ny lalitra, dia mila hahatratra ny hafainganam-pandeha ambony indrindra ny fakana andalana kisendrasendra avy amin'ny latabatra. doodles. Ho an'izany dia nampiasa tricks 3 izahay. Ny voalohany dia ny fampihenana ny refin'ny karazana mitahiry ny ID tsikaritra. Ao amin'ny fitambaran'ny angona tany am-boalohany, ny karazana ilaina hitahirizana ny ID dia bigint, fa ny isan'ny fandinihana dia ahafahana mampifanaraka ny famantarana azy ireo, mitovy amin'ny isa ordinal, amin'ny karazana int. Haingana kokoa ny fikarohana amin'ity tranga ity. Ny fika faharoa dia ny fampiasana ORDERED INDEX - Tonga tamin'io fanapahan-kevitra io izahay, rehefa nandalo izay rehetra azo atao safidy. Ny fahatelo dia ny fampiasana fanontaniana voatokana. Ny fototry ny fomba dia ny fanatanterahana ny baiko indray mandeha PREPARE miaraka amin'ny fampiasana fitenenana voaomana manaraka rehefa mamorona andiana fanontaniana mitovy karazana, fa raha ny marina dia misy tombony raha oharina amin'ny tsotra SELECT nivadika ho ao anatin'ny fetran'ny fahadisoana ara-statistika.

Ny dingan'ny fampiakarana angon-drakitra dia mandany tsy mihoatra ny 450 MB an'ny RAM. Izany hoe, ny fomba voalaza dia ahafahanao mamindra angona milanja gigabytes am-polony amin'ny saika fitaovana rehetra momba ny tetibola, ao anatin'izany ny fitaovana tokana tokana, izay tena mahafinaritra.

Ny hany sisa tavela dia ny fandrefesana ny hafainganan'ny fakana angona (kisendrasendra) sy ny fanombantombanana ny scaling rehefa maka santionany amin'ny habe samihafa:

Taratasy benchmark

library(ggplot2)

set.seed(0)
# ΠŸΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ ΠΊ Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ…
con <- DBI::dbConnect(MonetDBLite::MonetDBLite(), Sys.getenv("DBDIR"))

# Ѐункция для ΠΏΠΎΠ΄Π³ΠΎΡ‚ΠΎΠ²ΠΊΠΈ запроса Π½Π° сторонС сСрвСра
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)
}

# Ѐункция для извлСчСния Π΄Π°Π½Π½Ρ‹Ρ…
fetch_data <- function(rs, batch_size) {
  ids <- sample(seq_len(n), batch_size)
  res <- DBI::dbFetch(DBI::dbBind(rs, as.list(ids)))
  return(res)
}

# ΠŸΡ€ΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ Π·Π°ΠΌΠ΅Ρ€Π°
res_bench <- bench::press(
  batch_size = 2^(4:10),
  {
    rs <- prep_sql(batch_size)
    bench::mark(
      fetch_data(rs, batch_size),
      min_iterations = 50L
    )
  }
)
# ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ Π±Π΅Π½Ρ‡ΠΌΠ°Ρ€ΠΊΠ°
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
#        <dbl> <bch:tm> <bch:tm> <bch:tm>     <dbl>   <bch:tm> <int>
# 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("median time, s") +
  theme_minimal()

DBI::dbDisconnect(con, shutdown = TRUE)

Fanekena Doodle Draw haingana: fomba fanaovana namana amin'ny R, C ++ ary tambajotra neural

2. Manomana andiany

Ny dingana fanomanana batch manontolo dia ahitana ireto dingana manaraka ireto:

  1. Famakafakana JSON maromaro misy vectors amin'ny tady miaraka amin'ny fandrindrana teboka.
  2. Manao tsipika miloko mifototra amin'ny fandrindrana ny teboka amin'ny sary amin'ny habeny ilaina (ohatra, 256 Γ— 256 na 128 Γ— 128).
  3. Manova ny sary ho lasa tensor.

Ao anatin'ny fifaninanana eo amin'ny kernel Python, ny olana dia voavaha voalohany indrindra amin'ny fampiasana OpenCV. Ny iray amin'ireo analogue tsotra sy mazava indrindra amin'ny R dia toa izao:

Fampiharana ny JSON ho Tensor Conversion ao amin'ny R

r_process_json_str <- function(json, line.width = 3, 
                               color = TRUE, scale = 1) {
  # ΠŸΠ°Ρ€ΡΠΈΠ½Π³ JSON
  coords <- jsonlite::fromJSON(json, simplifyMatrix = FALSE)
  tmp <- tempfile()
  # УдаляСм Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹ΠΉ Ρ„Π°ΠΉΠ» ΠΏΠΎ Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½ΠΈΡŽ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ
  on.exit(unlink(tmp))
  png(filename = tmp, width = 256 * scale, height = 256 * scale, pointsize = 1)
  # ΠŸΡƒΡΡ‚ΠΎΠΉ Π³Ρ€Π°Ρ„ΠΈΠΊ
  plot.new()
  # Π Π°Π·ΠΌΠ΅Ρ€ ΠΎΠΊΠ½Π° Π³Ρ€Π°Ρ„ΠΈΠΊΠ°
  plot.window(xlim = c(256 * scale, 0), ylim = c(256 * scale, 0))
  # Π¦Π²Π΅Ρ‚Π° Π»ΠΈΠ½ΠΈΠΉ
  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()
  # ΠŸΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Π½ΠΈΠ΅ изобраТСния Π² 3-Ρ… ΠΌΠ΅Ρ€Π½Ρ‹ΠΉ массив
  res <- png::readPNG(tmp)
  return(res)
}

r_process_json_vector <- function(x, ...) {
  res <- lapply(x, r_process_json_str, ...)
  # ОбъСдинСниС 3-Ρ… ΠΌΠ΅Ρ€Π½Ρ‹Ρ… массивов ΠΊΠ°Ρ€Ρ‚ΠΈΠ½ΠΎΠΊ Π² 4-Ρ… ΠΌΠ΅Ρ€Π½Ρ‹ΠΉ Π² Ρ‚Π΅Π½Π·ΠΎΡ€
  res <- do.call(abind::abind, c(res, along = 0))
  return(res)
}

Ny sary dia atao amin'ny fampiasana fitaovana R mahazatra ary voatahiry ao amin'ny PNG vonjimaika voatahiry ao amin'ny RAM (amin'ny Linux, ny lahatahiry R vonjimaika dia hita ao amin'ny lahatahiry. /tmp, napetraka ao amin'ny RAM). Ity rakitra ity dia vakiana toy ny laharan-tsarimihetsika telo misy isa manomboka amin'ny 0 ka hatramin'ny 1. Zava-dehibe izany satria ny BMP mahazatra kokoa dia ho vakiana ao anaty laharan-kira manta misy kaody loko hex.

Andeha hojerentsika ny valiny:

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))

Fanekena Doodle Draw haingana: fomba fanaovana namana amin'ny R, C ++ ary tambajotra neural

Ny batch mihitsy dia hiforona toy izao manaraka izao:

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

Ity fampiharana ity dia toa tsy dia tsara loatra taminay, satria ny fananganana andiany lehibe dia mitaky fotoana lava be, ary nanapa-kevitra izahay ny hanararaotra ny traikefan'ny mpiara-miasa aminay amin'ny fampiasana tranomboky matanjaka. OpenCV. Tamin'izany fotoana izany dia tsy nisy fonosana efa vonona ho an'ny R (tsy misy ankehitriny), ka ny fampiharana kely indrindra amin'ny fampiasa ilaina dia nosoratana tamin'ny C ++ miaraka amin'ny fampidirana ao amin'ny kaody R mampiasa Rcpp.

Mba hamahana ny olana dia ireto fonosana sy trano famakiam-boky manaraka ireto no nampiasaina:

  1. OpenCV ho an'ny miasa amin'ny sary sy ny fanaovana tsipika. Famakiam-boky rafitra efa napetraka mialoha sy rakitra lohapejy, ary koa fampifandraisana mavitrika.

  2. xtensor ho an'ny miasa miaraka amin'ny array sy tensor multidimensional. Nampiasa fisie lohan-doha tafiditra ao amin'ny fonosana R mitovy anarana izahay. Ny trano famakiam-boky dia ahafahanao miasa miaraka amin'ny array multidimensional, na amin'ny laharana lehibe na tsanganana lehibe.

  3. ndjson ho an'ny famafazana JSON. Ity tranomboky ity dia ampiasaina amin'ny xtensor ho azy raha misy ao amin'ny tetikasa.

  4. RcppThread ho an'ny fandaminana ny fanodinana maromaro amin'ny vector avy amin'ny JSON. Nampiasa ny rakitra lohapejy nomen'ity fonosana ity. Avy amin'ny malaza kokoa RcppParallel Ny fonosana, ankoatry ny zavatra hafa, dia manana rafitra fanelanelanana an-dalamby.

Tsara ny manamarika izany xtensor Nivadika ho fanomezam-boninahitra an'Andriamanitra: ankoatry ny zava-misy fa manana fiasa be dia be sy fampisehoana avo lenta, ny mpamorona azy dia tena namaly ary namaly fanontaniana haingana sy tamin'ny antsipiriany. Noho ny fanampian'izy ireo dia azo natao ny nampihatra ny fiovan'ny matrices OpenCV ho lasa xtensor tensor, ary koa fomba iray hanambatra ny tensor sary 3-dimensional ho lasa tensor 4-dimensional amin'ny refy marina (ny batch mihitsy).

Fitaovana fianarana Rcpp, xtensor ary RcppThread

https://thecoatlessprofessor.com/programming/unofficial-rcpp-api-documentation

https://docs.opencv.org/4.0.1/d7/dbd/group__imgproc.html

https://xtensor.readthedocs.io/en/latest/

https://xtensor.readthedocs.io/en/latest/file_loading.html#loading-json-data-into-xtensor

https://cran.r-project.org/web/packages/RcppThread/vignettes/RcppThread-vignette.pdf

Mba hanangonana rakitra mampiasa rakitra rafitra sy fifandraisana mavitrika amin'ny tranomboky napetraka ao amin'ny rafitra dia nampiasa ny mekanika plugin napetraka tao amin'ny fonosana izahay. Rcpp. Mba hahitana lalana sy saina ho azy dia nampiasa fitaovana Linux malaza izahay pkg-config.

Fampiharana ny plugin Rcpp amin'ny fampiasana ny tranomboky OpenCV

Rcpp::registerPlugin("opencv", function() {
  # Π’ΠΎΠ·ΠΌΠΎΠΆΠ½Ρ‹Π΅ названия ΠΏΠ°ΠΊΠ΅Ρ‚Π°
  pkg_config_name <- c("opencv", "opencv4")
  # Π‘ΠΈΠ½Π°Ρ€Π½Ρ‹ΠΉ Ρ„Π°ΠΉΠ» ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ pkg-config
  pkg_config_bin <- Sys.which("pkg-config")
  # ΠŸΡ€ΠΎΠ²Ρ€Π΅ΠΊΠ° наличия ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ Π² систСмС
  checkmate::assert_file_exists(pkg_config_bin, access = "x")
  # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° наличия Ρ„Π°ΠΉΠ»Π° настроСк OpenCV для 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)
  ))
})

Vokatry ny fiasan'ny plugin, ireto soatoavina manaraka ireto dia hosoloina mandritra ny dingan'ny fanangonana:

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"

Ny fehezan-dalΓ na fampiharana ho an'ny famafana ny JSON sy ny famoronana andiana ho an'ny fandefasana amin'ny modely dia omena eo ambanin'ny mpandroba. Voalohany, ampio lahatahiry tetikasa eo an-toerana hitadiavana rakitra lohateny (ilaina amin'ny ndjson):

Sys.setenv("PKG_CXXFLAGS" = paste0("-I", normalizePath(file.path("src"))))

Fampiharana ny JSON amin'ny fiovam-po tensor ao amin'ny 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;
}

Ity kaody ity dia tokony hapetraka ao anaty rakitra src/cv_xt.cpp ary atambatra miaraka amin'ny baiko Rcpp::sourceCpp(file = "src/cv_xt.cpp", env = .GlobalEnv); ilaina ihany koa amin'ny asa nlohmann/json.hpp avy amin'ny repository. Ny code dia mizara ho asa maromaro:

  • to_xt - asa vita amin'ny modely hanovana sary matrix (cv::Mat) amin'ny tensor xt::xtensor;

  • parse_json - Ny fiasa dia mametaka tady JSON, manala ny koordinate amin'ny teboka, mametraka azy ireo ho vector;

  • ocv_draw_lines - avy amin'ny zezika vokarin'ny teboka, manao tsipika miloko maro;

  • process - manambatra ireo fiasa voalaza etsy ambony ary manampy koa ny fahafahana manitsy ny sary azo;

  • cpp_process_json_str - wrapper eo ambonin'ny asa process, izay manondrana ny vokatra ho amin'ny R-object (array multidimensional);

  • cpp_process_json_vector - wrapper eo ambonin'ny asa cpp_process_json_str, izay ahafahanao manodina vector tady amin'ny maody maromaro misy kofehy.

Mba hanaovana tsipika miloko maro, dia nampiasaina ny modely loko HSV, arahin'ny fiovam-po ho RGB. Andeha hojerentsika ny valiny:

arr <- cpp_process_json_str(tmp_data[4, drawing])
dim(arr)
# [1] 256 256   3
plot(magick::image_read(arr))

Fanekena Doodle Draw haingana: fomba fanaovana namana amin'ny R, C ++ ary tambajotra neural
Fampitahana ny hafainganan'ny fampiharana amin'ny R sy 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
)
# ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ Π±Π΅Π½Ρ‡ΠΌΠ°Ρ€ΠΊΠ°
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
#   <chr>                <bch:tm>   <bch:tm>  <bch:tm>     <dbl>   <bch:tm>  <int>
# 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)
# ΠŸΡ€ΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ Π·Π°ΠΌΠ΅Ρ€Π°
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
#    <chr>             <dbl> <bch:tm> <bch:tm> <bch:tm>     <dbl>   <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") 

Fanekena Doodle Draw haingana: fomba fanaovana namana amin'ny R, C ++ ary tambajotra neural

Araka ny hitanao dia tena zava-dehibe tokoa ny fitomboan'ny hafainganam-pandeha, ary tsy azo atao ny manatratra ny kaody C ++ amin'ny fampitoviana ny kaody R.

3. Iterators amin'ny famoahana batch avy amin'ny tahiry

R dia manana laza mendrika amin'ny fanodinana angon-drakitra mifanaraka amin'ny RAM, raha toa kosa i Python dia miavaka amin'ny fanodinana angon-drakitra miverimberina, mamela anao hampihatra mora foana ny kajikajy ivelan'ny fototra (kajy mampiasa fitadidiana ivelany). Ny ohatra mahazatra sy manan-danja ho antsika amin'ny tontolon'ny olana voalaza dia ny tambajotra neural lalina nampiofanina tamin'ny fomba gradient descent miaraka amin'ny fanombanana ny gradient isaky ny dingana amin'ny fampiasana ampahany kely amin'ny fandinihana, na mini-batch.

Ny rafitra fianarana lalina nosoratana amin'ny Python dia manana kilasy manokana izay manatanteraka iterators mifototra amin'ny angona: latabatra, sary ao anaty lahatahiry, endrika binary, sns. Azonao atao ny mampiasa safidy efa vita na manoratra ny anao manokana ho an'ny asa manokana. Ao amin'ny R dia afaka manararaotra ny endri-javatra rehetra ao amin'ny tranomboky Python isika keras miaraka amin'ny lamosiny isan-karazany mampiasa ny fonosana mitovy anarana, izay miasa eo an-tampon'ny fonosana mamerina. Ity farany dia mendrika lahatsoratra lava mitokana; tsy vitan'ny hoe mamela anao mihazakazaka Python code avy amin'ny R, fa koa mamela anao hamindra zavatra eo amin'ny R sy Python sessions, mandeha ho azy ny karazana fiovam-po ilaina rehetra.

Nesorintsika ny filana hitahiry ny angon-drakitra rehetra ao amin'ny RAM amin'ny alΓ lan'ny MonetDBite, ny asa "tambajotra neural" rehetra dia hatao amin'ny alΓ lan'ny kaody tany am-boalohany ao amin'ny Python, mila manoratra iterare amin'ny data isika, satria tsy misy vonona. ho an'ny toe-javatra toy izany amin'ny R na Python. Tsy misy afa-tsy roa ihany ny fepetra takiana amin'izany: tsy maintsy mamerina batch amin'ny tadivavarana tsy misy farany izy ary mitahiry ny toerany eo anelanelan'ny famerimberenana (ity farany amin'ny R dia ampiharina amin'ny fomba tsotra indrindra amin'ny alΓ lan'ny fanakatonana). Teo aloha dia ilaina ny mamadika mazava ny R arrays ho numpy arrays ao anatin'ny iterator, fa ny dikan-teny ankehitriny amin'ny fonosana. keras manao izany ny tenany.

Ny iteratera ho an'ny angon-drakitra fanofanana sy fanamarinana dia toy izao manaraka izao:

Iterator ho an'ny angona fanofanana sy fanamarinana

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)
  }
}

Ny fiasa dia mandray ho toy ny fampidirana ny fari-piainana misy fifandraisana amin'ny angon-drakitra, ny isan'ny andalana ampiasaina, ny isan'ny kilasy, ny haben'ny batch, ny habeny (scale = 1 mifanaraka amin'ny famoahana sary 256x256 piksel, scale = 0.5 β€” 128x128 teboka), famantarana loko (color = FALSE mamaritra ny fanaovana amin'ny grayscale rehefa ampiasaina color = TRUE ny kapoka tsirairay dia voasintona amin'ny loko vaovao) ary famantarana mialoha ny fanodinana ho an'ny tambajotra efa voaofana mialoha amin'ny imagenet. Ity farany dia ilaina mba hanesorana ny sandan'ny pixel manomboka amin'ny elanelana [0, 1] mankany amin'ny elanelana [-1, 1], izay nampiasaina tamin'ny fanofanana ireo nomena. keras modely.

Ny fiasa ivelany dia misy fanamarinana karazana argument, latabatra data.table miaraka amin'ny isa andalana mifangaro kisendrasendra avy amin'ny samples_index ary ny laharan'ny batch, ny isa sy ny isa ambony indrindra amin'ny andiany, ary koa ny fomba fiteny SQL amin'ny famoahana angona avy amin'ny angona. Fanampin'izay, namaritra analogue haingana ny fiasa ao anatiny izahay keras::to_categorical(). Saika nampiasa ny angona rehetra izahay ho an'ny fanofanana, namela antsasaky ny isan-jato ho an'ny fanamarinana, ka ny haben'ny vanim-potoana dia noferan'ny parameter steps_per_epoch rehefa antsoina keras::fit_generator(), ary ny fepetra if (i > max_i) dia niasa ho an'ny mpizara validation ihany.

Ao amin'ny asa anatiny, ny index row dia alaina ho an'ny andiany manaraka, ny rakitra dia alefa avy amin'ny angon-drakitra miaraka amin'ny fitomboan'ny batch, ny JSON parsing (asa cpp_process_json_vector(), voasoratra ao amin'ny C++) ary mamorona array mifanaraka amin'ny sary. Avy eo dia noforonina ny vectors mafana iray misy marika kilasy, ny arrays miaraka amin'ny sanda pixel sy ny marika dia atambatra ao anaty lisitra, izay ny sanda miverina. Mba hanafainganana ny asa dia nampiasa ny famoronana indexes amin'ny tabilao izahay data.table ary fanovana amin'ny alΓ lan'ny rohy - tsy misy ireto fonosana "chips" ireto data.table Sarotra ny maka sary an-tsaina hoe miasa tsara amin'ny angon-drakitra manan-danja ao amin'ny R.

Ny vokatry ny fandrefesana hafainganam-pandeha amin'ny solosaina finday Core i5 dia toy izao manaraka izao:

benchmark iterator

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]]

# Π˜Π½Π΄Π΅ΠΊΡΡ‹ для ΠΎΠ±ΡƒΡ‡Π°ΡŽΡ‰Π΅ΠΉ Π²Ρ‹Π±ΠΎΡ€ΠΊΠΈ
train_ind <- sample(ind, floor(length(ind) * 0.995))
# Π˜Π½Π΄Π΅ΠΊΡΡ‹ для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΎΡ‡Π½ΠΎΠΉ Π²Ρ‹Π±ΠΎΡ€ΠΊΠΈ
val_ind <- ind[-train_ind]
rm(ind)
# ΠšΠΎΡΡ„Ρ„ΠΈΡ†ΠΈΠ΅Π½Ρ‚ ΠΌΠ°ΡΡˆΡ‚Π°Π±Π°
scale <- 0.5

# ΠŸΡ€ΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ Π·Π°ΠΌΠ΅Ρ€Π°
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
    )
  }
)
# ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ Π±Π΅Π½Ρ‡ΠΌΠ°Ρ€ΠΊΠ°
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
#        <dbl> <bch:tm> <bch:tm> <bch:tm>     <dbl>   <bch:tm> <int>
# 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("median time, s") +
    theme_minimal()

DBI::dbDisconnect(con, shutdown = TRUE)

Fanekena Doodle Draw haingana: fomba fanaovana namana amin'ny R, C ++ ary tambajotra neural

Raha manana RAM ampy ianao, dia azonao atao ny manafaingana ny fiasan'ny angon-drakitra amin'ny alΓ lan'ny famindrana azy amin'io RAM io ihany (32 GB dia ampy ho an'ny asantsika). Ao amin'ny Linux, ny fisarahana dia napetraka amin'ny alΓ lan'ny default /dev/shm, mibodo hatramin'ny antsasaky ny fahafahan'ny RAM. Azonao atao ny manasongadina bebe kokoa amin'ny alΓ lan'ny fanitsiana /etc/fstabmba hahazoana rakitra tahaka tmpfs /dev/shm tmpfs defaults,size=25g 0 0. Ataovy azo antoka ny hamerina indray ary jereo ny valiny amin'ny alΓ lan'ny fandefasana ny baiko df -h.

Toa tsotra kokoa ny mpizara ho an'ny angona fitsapana, satria mifanaraka tanteraka amin'ny RAM ny angona fitsapana:

Iterator ho an'ny angona fitsapana

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. Fifantenana ny maritrano modely

Ny maritrano voalohany nampiasaina dia mobilenet v1, ny endri-javatra izay resahina ao izany hafatra. Tafiditra ho fenitra keras ary, araka izany, dia hita ao amin'ny fonosana mitovy anarana ho an'ny R. Saingy rehefa manandrana mampiasa azy amin'ny sary tokana tokana, dia nisy zavatra hafahafa niseho: ny tensor fampidirana dia tsy maintsy manana ny refy foana. (batch, height, width, 3), izany hoe tsy azo ovaina ny isan'ny fantsona. Tsy misy fetra toy izany ao amin'ny Python, noho izany dia nihazakazaka izahay ary nanoratra ny fampiharana manokana an'ity maritrano ity, manaraka ny lahatsoratra tany am-boalohany (tsy misy ny fialana amin'ny dikan-teny mafy):

Mobilenet v1 architecture

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 <- function(object, 
                              filters,
                              alpha = 1,
                              depth_multiplier = 1,
                              strides = c(2, 2)) {

  # NB! depth_multiplier !=  resolution multiplier
  # https://github.com/keras-team/keras/issues/10349

  layer_depthwise_conv_2d(
    object = object,
    kernel_size = c(3, 3), 
    strides = strides,
    padding = "same",
    depth_multiplier = depth_multiplier
  ) %>%
  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 <- inputs %>%
    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 <- keras_model(
      inputs = inputs,
      outputs = outputs
    )

    model %>% compile(
      optimizer = optimizer,
      loss = loss,
      metrics = metrics
    )

    return(model)
}

Miharihary ny fatiantoka amin’io fomba fiasa io. Te-hitsapa modely maro aho, fa ny mifanohitra amin'izany, tsy te-hamerina ny maritrano tsirairay amin'ny tanana aho. Nesorina taminay ihany koa ny fahafahana mampiasa ny lanjan'ny modely efa voaofana mialoha amin'ny imagenet. Toy ny mahazatra, nanampy ny fandalinana ny antontan-taratasy. asa get_config() ahafahanao mahazo famaritana ny maodely amin'ny endrika sahaza ho an'ny fanitsiana (base_model_conf$layers - lisitra R mahazatra), ary ny fiasa from_config() manao ny fiovam-po mivadika ho zavatra modely:

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)

Amin'izao fotoana izao dia tsy sarotra ny manoratra asa manerantany mba hahazoana ny iray amin'ireo nomena keras modely misy na tsy misy lanja voaofana amin'ny imagenet:

Fampiasa amin'ny fametrahana ny maritrano efa vita

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) {
  # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ²
  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)

  # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ ΠΈΠ· ΠΏΠ°ΠΊΠ΅Ρ‚Π° keras
  model_fun <- get0(paste0("application_", name), envir = asNamespace("keras"))
  # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° наличия ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π° Π² ΠΏΠ°ΠΊΠ΅Ρ‚Π΅
  if (is.null(model_fun)) {
    stop("Model ", shQuote(name), " not found.", call. = FALSE)
  }

  base_model <- model_fun(
    input_shape = input_shape,
    include_top = FALSE,
    weights = weights,
    pooling = pooling
  )

  # Если ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ Π½Π΅ Ρ†Π²Π΅Ρ‚Π½ΠΎΠ΅, мСняСм Ρ€Π°Π·ΠΌΠ΅Ρ€Π½ΠΎΡΡ‚ΡŒ Π²Ρ…ΠΎΠ΄Π°
  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)
}

Rehefa mampiasa sary amin'ny fantsona tokana dia tsy misy lanja efa voaomana mialoha. Azo fehezina izany: mampiasa ny fiasa get_weights() alaivo ny lanjan'ny maodely amin'ny endrika lisitry ny R arrays, ovay ny refin'ny singa voalohany amin'ity lisitra ity (amin'ny alΓ lan'ny fakana fantsona loko iray na amin'ny salan'isa telo), ary avereno ao amin'ny modely miaraka amin'ny fiasa ny lanja. set_weights(). Tsy nanampy an'io fiasa io mihitsy izahay, satria tamin'ity dingana ity dia efa mazava fa mamokatra kokoa ny miasa amin'ny sary miloko.

Nanao ny ankamaroan'ny andrana izahay tamin'ny fampiasana mobilenet version 1 sy 2, ary koa ny resnet34. Ny maritrano maoderina kokoa toa ny SE-ResNeXt dia nahavita tsara tamin'ity fifaninanana ity. Indrisy anefa fa tsy nanana fampiharana efa vonona izahay, ary tsy nanoratra ny anay (fa hanoratra tokoa izahay).

5. Famaritana ny script

Ho fanamorana, ny kaody rehetra hanombohana fiofanana dia natao ho script tokana, ampiasaina amin'ny parameter docopt toy izao manaraka izao:

doc <- '
Usage:
  train_nn.R --help
  train_nn.R --list-models
  train_nn.R [options]

Options:
  -h --help                   Show this message.
  -l --list-models            List available models.
  -m --model=<model>          Neural network model name [default: mobilenet_v2].
  -b --batch-size=<size>      Batch size [default: 32].
  -s --scale-factor=<ratio>   Scale factor [default: 0.5].
  -c --color                  Use color lines [default: FALSE].
  -d --db-dir=<path>          Path to database directory [default: Sys.getenv("db_dir")].
  -r --validate-ratio=<ratio> Validate sample ratio [default: 0.995].
  -n --n-gpu=<number>         Number of GPUs [default: 1].
'
args <- docopt::docopt(doc)

fonosana docopt maneho ny fampiharana http://docopt.org/ ho an'ny R. Miaraka amin'ny fanampiany, ny script dia natomboka tamin'ny baiko tsotra toy ny Rscript bin/train_nn.R -m resnet50 -c -d /home/andrey/doodle_db na ./bin/train_nn.R -m resnet50 -c -d /home/andrey/doodle_db, raha fichier train_nn.R dia azo tanterahina (ity baiko ity dia hanomboka hampiofana ny modely resnet50 amin'ny sary miloko telo mirefy 128x128 piksel, ny angon-drakitra dia tsy maintsy hita ao amin'ny lahatahiry /home/andrey/doodle_db). Azonao atao ny manampy ny hafainganan'ny fianarana, ny karazana optimizer, ary ny masontsivana hafa azo zahana amin'ny lisitra. Nandritra ny fanomanana ny famoahana dia hita fa ny maritrano mobilenet_v2 avy amin'ny dikan-teny ankehitriny keras amin'ny fampiasana R tsy afaka noho ny fanovana tsy noraisina tao amin'ny fonosana R dia miandry azy ireo hanamboatra azy izahay.

Io fomba fiasa io dia nahafahana nanafaingana be ny andrana tamin'ny maodely samihafa raha oharina amin'ny fandefasana script mahazatra kokoa ao amin'ny RStudio (marihinay ny fonosana ho safidy azo atao. tfruns). Fa ny tombony lehibe indrindra dia ny fahafahana mitantana mora foana ny fandefasana script ao amin'ny Docker na amin'ny mpizara fotsiny, tsy misy fametrahana RStudio ho an'izany.

6. Dockerization ny script

Nampiasa Docker izahay mba hiantohana ny fahafahan'ny tontolo iainana amin'ny fanofanana modely eo amin'ny mpikambana ao amin'ny ekipa sy ny fametrahana haingana ao amin'ny rahona. Azonao atao ny manomboka mifankazatra amin'ity fitaovana ity, izay tsy mahazatra ho an'ny programmer R, miaraka amin'ny izany andian-dahatsoratra na taranja video.

Docker dia mamela anao hamorona ny sarinao manokana avy amin'ny scratch ary hampiasa sary hafa ho fototry ny famoronana anao manokana. Rehefa nandinika ireo safidy misy izahay dia tonga tamin'ny fanatsoahan-kevitra fa ny fametrahana ny NVIDIA, ny mpamily CUDA + cuDNN ary ny tranomboky Python dia ampahany be dia be amin'ny sary, ary nanapa-kevitra izahay fa haka ny sary ofisialy ho fototra. tensorflow/tensorflow:1.12.0-gpu, manampy ny fonosana R ilaina ao.

Toy izao ny rakitra docker farany:

dockerfile

FROM tensorflow/tensorflow:1.12.0-gpu

MAINTAINER Artem Klevtsov <[email protected]>

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

Ho fanamorana, ny fonosana ampiasaina dia napetraka ao anaty faribolana; ny ankamaroan'ny soratra voasoratra dia adika ao anatin'ny fitoeran-javatra mandritra ny fivoriambe. Nanova ny baiko shell koa izahay /bin/bash ho fanamorana ny fampiasana votoaty /etc/os-release. Izany dia nanalavitra ny filana mamaritra ny dikan-teny OS amin'ny code.

Fanampin'izany, nosoratana ny script bash kely izay ahafahanao manangana container misy baiko isan-karazany. Ohatra, ireo dia mety ho sora-baventy hanofanana tambajotra neural izay napetraka teo aloha tao anatin'ny kaontenera, na shell baiko ho an'ny debugging sy fanaraha-maso ny fiasan'ny container:

Soraty ny fandefasana ny container

#!/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}

Raha toa ka mandeha tsy misy masontsivana ity script bash ity dia hantsoina ao anatin'ny container ny script train_nn.R miaraka amin'ny sanda mahazatra; raha "bash" ny tohan-kevitra voalohany amin'ny toerana, dia hanomboka hifanerasera amin'ny akorandriaka baiko ilay fitoeran-javatra. Amin'ny tranga hafa rehetra, ny soatoavin'ny tohan-kevitra positional dia soloina: CMD="Rscript /app/train_nn.R $@".

Tsara ny manamarika fa ny lahatahiry miaraka amin'ny angon-drakitra loharano sy ny angon-drakitra, ary koa ny lahatahiry ho an'ny fitahirizana modely voaofana, dia apetraka ao anatin'ny kaontenera avy amin'ny rafitra mpampiantrano, izay ahafahanao miditra amin'ny valin'ny script tsy misy fanodikodinana tsy ilaina.

7. Mampiasa GPU maro amin'ny Google Cloud

Anisan'ny mampiavaka ny fifaninanana ny angon-drakitra mitabataba be (jereo ny sarin'ny lohateny, nindramina tao @ Leigh.plt avy amin'ny ODS slack). Ny andiany lehibe dia manampy amin'ny ady amin'izany, ary taorian'ny fanandramana tamin'ny PC misy GPU 1, dia nanapa-kevitra ny hifehy modely fanofanana amin'ny GPU maromaro ao anaty rahona izahay. GoogleCloud nampiasa (tari-dalana tsara amin'ny fototra) noho ny fifantenana be dia be amin'ny fanamafisana misy, ny vidiny mirary ary ny bonus $300. Noho ny fitiavam-bola dia nanafatra ohatra 4xV100 miaraka amin'ny SSD sy RAM iray taonina aho, ary fahadisoana lehibe izany. Mandany vola haingana ny milina toy izany; afaka manao fanandramana ianao raha tsy misy fantsona voaporofo. Ho an'ny tanjona fanabeazana dia tsara kokoa ny maka ny K80. Saingy ny habetsaky ny RAM dia azo ampiasaina - ny rahona SSD dia tsy nanaitra ny fahombiazany, ka nafindra tany dev/shm.

Ny tena mahaliana indrindra dia ny sombin-kaody tompon'andraikitra amin'ny fampiasana GPU maro. Voalohany, ny modely dia noforonina amin'ny CPU amin'ny fampiasana mpitantana ny contexte, toy ny ao amin'ny 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
  )
})

Avy eo ny modely tsy voatambatra (zava-dehibe) dia adika amin'ny isa nomena ny GPU, ary aorian'izay dia natambatra:

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)
)

Ny teknika mahazatra amin'ny fanamainana ny sosona rehetra afa-tsy ny farany, ny fanofanana ny sosona farany, ny fanalefahana ary ny famerenana ny modely manontolo ho an'ny GPU maromaro dia tsy azo ampiharina.

Nojerena tsy nampiasaina ny fiofanana. tensorboard, mametra ny tenantsika amin'ny firaketana diary sy mitahiry ireo maodely misy anarana mampahafantatra isaky ny vanim-potoana tsirairay:

Callbacks

# Π¨Π°Π±Π»ΠΎΠ½ ΠΈΠΌΠ΅Π½ΠΈ Ρ„Π°ΠΉΠ»Π° Π»ΠΎΠ³Π°
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. Raha tokony ho fehin-kevitra

Betsaka ny olana sedrainay tsy mbola voavaha:

  • Π² keras Tsy misy fiasa efa vita ho an'ny fikarohana ho azy ny tahan'ny fianarana tsara indrindra (analogue lr_finder ao amin'ny tranomboky fast.ai); Miaraka amin'ny ezaka kely, azo atao ny mandefa fampiharana avy amin'ny antoko fahatelo amin'ny R, ohatra, izany;
  • vokatry ny teboka teo aloha dia tsy azo natao ny nisafidy ny hafainganam-pandehan'ny fiofanana marina rehefa mampiasa GPU maromaro;
  • misy ny tsy fahampian'ny rafitra tambajotra neural maoderina, indrindra ireo efa niofana mialoha tamin'ny imagenet;
  • tsy misy politikan'ny tsingerina sy ny tahan'ny fianarana manavakavaka (nangataka ny cosine annealing ampiharina, Misaotra anao skeydan).

Inona no zavatra mahasoa nianarana tamin'ity fifaninanana ity:

  • Amin'ny fitaovana matanjaka kely dia afaka miasa miaraka amin'ny angon-drakitra mendrika (imbetsaka ny haben'ny RAM) tsy misy fanaintainana ianao. Kitapo plastika data.table mitahiry fitadidiana noho ny fanovana latabatra eo amin'ny toerana, izay misoroka ny kopia azy ireo, ary rehefa ampiasaina araka ny tokony ho izy, ny fahaizany dia saika mampiseho ny hafainganam-pandeha ambony indrindra amin'ireo fitaovana rehetra fantatray momba ny fiteny fanoratana. Ny fitahirizana angon-drakitra ao anaty angon-drakitra dia mamela anao, amin'ny tranga maro, tsy hieritreritra mihitsy momba ny filΓ na hanindry ny angona manontolo ao amin'ny RAM.
  • Ny fiasa miadana amin'ny R dia azo soloina amin'ny haingana amin'ny C ++ amin'ny fampiasana ny fonosana Rcpp. Raha ankoatra ny fampiasana RcppThread na RcppParallel, dia mahazo fampiharana maromaro misy kofehy mifamatotra amin'ny sehatra, ka tsy ilaina ny mampitovy ny kaody amin'ny ambaratonga R.
  • Package Rcpp azo ampiasaina tsy misy fahalalana matotra momba ny C ++, ny kely indrindra takiana dia voasoritra eto. Lohateny rakitra ho an'ny tranomboky C mangatsiatsiaka toy ny xtensor azo alaina ao amin'ny CRAN, izany hoe, misy fotodrafitrasa atsangana ho an'ny fanatanterahana ny tetikasa izay mampiditra ny kaody C++ avo lenta vita amin'ny R. Ny fanampim-panazavana fanampiny dia ny fanasongadinana ny syntax ary ny mpanadihady kaody C++ static ao amin'ny RStudio.
  • docopt mamela anao hampandeha sora-baventy manana mari-pamantarana. Ity dia mety amin'ny fampiasana amin'ny mpizara lavitra, anisan'izany. ambany docker. Ao amin'ny RStudio, sarotra ny manao andrana mandritra ny ora maro amin'ny fanofanana tambajotra neural, ary ny fametrahana ny IDE amin'ny server dia tsy voamarina foana.
  • Docker dia miantoka ny fampitana kaody sy ny famerenana ny valiny eo amin'ny mpamorona miaraka amin'ny dikan-teny samihafa amin'ny OS sy ny tranomboky, ary koa ny fanamorana ny famonoana amin'ny mpizara. Azonao atao ny manomboka ny fantsona fanofanana manontolo amin'ny baiko iray monja.
  • Google Cloud dia fomba mora amin'ny tetibola hanandramana fitaovana lafo vidy, saingy mila misafidy tsara ianao.
  • Ny fandrefesana ny hafainganam-pandehan'ny sombin-kaody tsirairay dia tena ilaina, indrindra rehefa manambatra ny R sy C ++, ary miaraka amin'ny fonosana dabilio - tena mora ihany koa.

Amin'ny ankapobeny dia tena nahafa-po ity traikefa ity ary manohy miasa izahay hamahana ny sasany amin'ireo olana nipoitra.

Source: www.habr.com

Add a comment