Pangenalan Doodle Draw Cepet: carane nggawe kanca karo R, C ++ lan jaringan saraf

Pangenalan Doodle Draw Cepet: carane nggawe kanca karo R, C ++ lan jaringan saraf

Hey Habr!

Musim gugur pungkasan, Kaggle dadi tuan rumah kompetisi kanggo nggolongake gambar sing digambar tangan, Quick Draw Doodle Recognition, sing kalebu tim R-ilmuwan: Artem Klevtsova, Manajer Philippa и Andrey Ogurtsov. Kita ora bakal njlèntrèhaké kompetisi kanthi rinci; sing wis rampung ing publikasi anyar.

Wektu iki ora bisa ditindakake kanthi tani medali, nanging akeh pengalaman sing migunani, mula aku bakal ngandhani masarakat babagan sawetara perkara sing paling menarik lan migunani ing Kagle lan ing saben dinane. Antarane topik sing dibahas: urip angel tanpa OpenCV, parsing JSON (conto iki nliti integrasi kode C++ menyang skrip utawa paket ing R nggunakake Rcpp), parameterisasi skrip lan dockerisasi solusi pungkasan. Kabeh kode saka pesen ing wangun cocok kanggo eksekusi kasedhiya ing repositori.

Isi:

  1. Muat data kanthi efisien saka CSV menyang MonetDB
  2. Nyiapake batch
  3. Iterator kanggo unloading batch saka database
  4. Pilih Arsitektur Model
  5. Parameterisasi skrip
  6. Dockerization saka skrip
  7. Nggunakake macem-macem GPU ing Google Cloud
  8. Tinimbang kesimpulan

1. Efisien mbukak data saka CSV menyang database MonetDB

Data ing kompetisi iki diwenehake ora ing wangun gambar siap, nanging ing wangun 340 file CSV (siji file kanggo saben kelas) ngemot JSONs karo titik koordinat. Kanthi nyambungake titik kasebut karo garis, kita entuk gambar pungkasan kanthi ukuran 256x256 piksel. Uga kanggo saben rekaman ana label sing nuduhake manawa gambar kasebut diakoni kanthi bener dening klasifikasi sing digunakake nalika dataset diklumpukake, kode rong huruf negara panggonan penulis gambar, pengenal unik, stempel wektu. lan jeneng kelas sing cocog karo jeneng berkas. Versi data asli sing disederhanakake bobote 7.4 GB ing arsip lan kira-kira 20 GB sawise dibongkar, data lengkap sawise dibongkar njupuk 240 GB. Penyelenggara mesthekake yen versi loro kasebut ngasilake gambar sing padha, tegese versi lengkap ora ana. Ing kasus apa wae, nyimpen 50 yuta gambar ing file grafis utawa ing wangun array langsung dianggep ora nguntungake, lan kita mutusake kanggo nggabungake kabeh file CSV saka arsip. train_simplified.zip menyang database karo generasi sakteruse saka gambar saka ukuran sing dibutuhake "ing fly" kanggo saben kumpulan.

Sistem sing wis kabukten dipilih minangka DBMS MonetDB, yaiku implementasine kanggo R minangka paket MonetDBLite. Paket kalebu versi embedded saka server database lan ngijini sampeyan kanggo Pick munggah server langsung saka sesi R lan bisa karo ana. Nggawe database lan nyambungake menyang iku dileksanakake karo siji printah:

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

Kita kudu nggawe rong tabel: siji kanggo kabeh data, liyane kanggo informasi layanan babagan file sing diundhuh (migunani yen ana sing salah lan proses kudu diterusake sawise ndownload sawetara file):

Nggawe tabel

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

Cara paling cepet kanggo mbukak data menyang database yaiku nyalin file CSV langsung nggunakake perintah SQL COPY OFFSET 2 INTO tablename FROM path USING DELIMITERS ',','n','"' NULL AS '' BEST EFFORTngendi tablename - jeneng Tabel lan path - path menyang file. Nalika nggarap arsip, ditemokake yen implementasine dibangun ing unzip ing R ora bisa bener karo sawetara file saka arsip, supaya kita digunakake sistem unzip (nggunakake parameter getOption("unzip")).

Fungsi kanggo nulis menyang database

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

Yen sampeyan kudu ngowahi tabel sadurunge nulis menyang database, iku cukup kanggo pass ing pitakonan preprocess fungsi sing bakal ngowahi data.

Kode kanggo ngunggah data kanthi urutan menyang database:

Nulis data menyang database

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

Wektu loading data bisa beda-beda gumantung saka karakteristik kacepetan drive sing digunakake. Ing kasus kita, maca lan nulis ing siji SSD utawa saka flash drive (sumber file) menyang SSD (DB) njupuk kurang saka 10 menit.

Butuh sawetara detik maneh kanggo nggawe kolom kanthi label kelas integer lan kolom indeks (ORDERED INDEX) kanthi nomer baris sing bakal dadi conto observasi nalika nggawe kumpulan:

Nggawe Kolom Tambahan lan Indeks

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

Kanggo ngatasi masalah nggawe batch kanthi cepet, kita kudu entuk kacepetan maksimal kanggo ngekstrak baris acak saka tabel. doodles. Kanggo iki, kita nggunakake 3 trik. Kaping pisanan yaiku nyuda dimensi saka jinis sing nyimpen ID pengamatan. Ing set data asli, jinis sing dibutuhake kanggo nyimpen ID yaiku bigint, nanging jumlah pengamatan ndadekake bisa pas pengenal, padha karo nomer ordinal, menyang jinis int. Panelusuran luwih cepet ing kasus iki. Trik kapindho yaiku nggunakake ORDERED INDEX - kita teka kaputusan iki empiris, wis liwat kabeh kasedhiya opsi. Katelu yaiku nggunakake pitakon parameter. Inti saka metode kasebut yaiku nglakokake perintah kasebut sapisan PREPARE kanthi nggunakake ekspresi sing disiapake nalika nggawe pirang-pirang pitakon saka jinis sing padha, nanging nyatane ana kauntungan dibandhingake karo sing prasaja. SELECT pranyata ana ing jangkoan kesalahan statistik.

Proses upload data nggunakake ora luwih saka 450 MB RAM. Yaiku, pendekatan sing diterangake ngidini sampeyan mindhah set data kanthi bobot puluhan gigabyte ing meh kabeh hardware anggaran, kalebu sawetara piranti papan siji, sing apik banget.

Kabeh sing isih ana yaiku ngukur kacepetan njupuk data (acak) lan ngevaluasi skala nalika njupuk sampel saka macem-macem ukuran:

benchmark database

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)

Pangenalan Doodle Draw Cepet: carane nggawe kanca karo R, C ++ lan jaringan saraf

2. Nyiapake kumpulan

Proses persiapan batch kabeh kalebu langkah-langkah ing ngisor iki:

  1. Parsing sawetara JSON sing ngemot vektor strings kanthi koordinat titik.
  2. Drawing garis colored adhedhasar koordinat titik ing gambar saka ukuran sing dibutuhake (contone, 256 Γ— 256 utawa 128 Γ— 128).
  3. Ngonversi gambar sing diasilake dadi tensor.

Minangka bagΓ©an saka kompetisi antarane kernels Python, masalah iki ditanggulangi utamanΓ© nggunakake OpenCV. Salah sawijining analog sing paling gampang lan paling jelas ing R bakal katon kaya iki:

Ngleksanakake Konversi JSON menyang Tensor ing 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)
}

Nggambar ditindakake nggunakake alat R standar lan disimpen menyang PNG sementara sing disimpen ing RAM (ing Linux, direktori R sementara ana ing direktori /tmp, dipasang ing RAM). Berkas iki banjur diwaca minangka array telung dimensi kanthi angka saka 0 nganti 1. Iki penting amarga BMP sing luwih konvensional bakal diwaca dadi array mentah kanthi kode warna hex.

Ayo nyoba asil:

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

Pangenalan Doodle Draw Cepet: carane nggawe kanca karo R, C ++ lan jaringan saraf

Batch dhewe bakal dibentuk kaya ing ngisor iki:

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

Implementasi iki katon ora optimal kanggo kita, amarga pambentukan kumpulan gedhe mbutuhake wektu sing ora sopan, lan kita mutusake kanggo njupuk kauntungan saka pengalaman kolega kanthi nggunakake perpustakaan sing kuat. OpenCV. Ing wektu iku ora ana paket siap kanggo R (ora ana saiki), supaya implementasi minimal saka fungsi sing dibutuhake ditulis ing C ++ kanthi integrasi menyang kode R nggunakake Rcpp.

Kanggo ngatasi masalah kasebut, paket lan perpustakaan ing ngisor iki digunakake:

  1. OpenCV kanggo nggarap gambar lan nggambar garis. Pustaka sistem sing wis diinstal lan file header sing wis diinstal, uga panyambungan dinamis.

  2. xtensor kanggo nggarap array multidimensi lan tensor. Kita nggunakake file header sing kalebu ing paket R kanthi jeneng sing padha. Pustaka ngidini sampeyan nggarap array multidimensi, ing urutan utama baris lan kolom.

  3. ndjson kanggo parsing JSON. Pustaka iki digunakake ing xtensor kanthi otomatis yen ana ing proyek kasebut.

  4. RcppThread kanggo ngatur pangolahan multi-threaded saka vektor saka JSON. Gunakake file header sing diwenehake dening paket iki. Saka luwih populer RcppParalel Paket kasebut, ing antarane, duwe mekanisme interupsi loop sing dibangun.

Wigati dicathet xtensor diaktifake dadi anugerah: saliyane kasunyatan sing nduweni fungsi ekstensif lan kinerja dhuwur, pangembang dadi cukup responsif lan njawab pitakonan sakcepete lan rinci. Kanthi bantuan kasebut, bisa ngetrapake transformasi matriks OpenCV dadi tensor xtensor, uga cara kanggo nggabungake tensor gambar 3 dimensi dadi tensor 4 dimensi saka dimensi sing bener (batch kasebut dhewe).

Materi sinau Rcpp, xtensor lan 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

Kanggo ngumpulake file sing nggunakake file sistem lan link dinamis karo perpustakaan sing diinstal ing sistem, kita nggunakake mekanisme plugin sing diimplementasikake ing paket kasebut. Rcpp. Kanggo nemokake dalan lan panji kanthi otomatis, kita nggunakake sarana Linux sing populer pkg-config.

Implementasi plugin Rcpp kanggo nggunakake perpustakaan 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)
  ))
})

Minangka asil saka operasi plugin, nilai ing ngisor iki bakal diganti sajrone proses kompilasi:

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"

Kode implementasine kanggo parsing JSON lan ngasilake batch kanggo transmisi menyang model diwenehi ing spoiler. Pisanan, tambahake direktori proyek lokal kanggo nggoleki file header (dibutuhake kanggo ndjson):

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

Implementasi JSON kanggo konversi tensor ing 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;
}

Kode iki kudu diselehake ing file src/cv_xt.cpp lan ngumpulake karo printah Rcpp::sourceCpp(file = "src/cv_xt.cpp", env = .GlobalEnv); uga dibutuhake kanggo karya nlohmann/json.hpp saka gudang. Kode kasebut dipΓ©rang dadi sawetara fungsi:

  • to_xt - fungsi cithakan kanggo ngowahi matriks gambar (cv::Mat) menyang tensor xt::xtensor;

  • parse_json - fungsi parses string JSON, ngekstrak koordinat titik, ngemas menyang vektor;

  • ocv_draw_lines - saka asil vektor titik, ndudohke garis multi-colored;

  • process - nggabungake fungsi ing ndhuwur lan uga nambah kemampuan kanggo skala gambar sing diasilake;

  • cpp_process_json_str - pambungkus liwat fungsi process, sing ngekspor asil menyang obyek R (array multidimensi);

  • cpp_process_json_vector - pambungkus liwat fungsi cpp_process_json_str, sing ngidini sampeyan ngolah vektor senar ing mode multi-Utas.

Kanggo nggambar garis multi-warna, model warna HSV digunakake, banjur konversi menyang RGB. Ayo nyoba asil:

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

Pangenalan Doodle Draw Cepet: carane nggawe kanca karo R, C ++ lan jaringan saraf
Perbandingan kacepetan implementasine ing R lan 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") 

Pangenalan Doodle Draw Cepet: carane nggawe kanca karo R, C ++ lan jaringan saraf

Nalika sampeyan bisa ndeleng, Tambah kacepetan dadi banget pinunjul, lan iku ora bisa kanggo nyekel munggah karo C ++ kode dening parallelizing R kode.

3. Iterators kanggo unloading kumpulan saka database

R wis reputasi uga-pantes kanggo Processing data sing mathuk ing RAM, nalika Python luwih ditondoi dening Processing data iteratif, ngijini sampeyan kanggo gampang lan alami ngleksanakake petungan metu-saka-inti (pitungan nggunakake memori external). Conto klasik lan relevan kanggo kita ing konteks masalah sing diterangake yaiku jaringan saraf jero sing dilatih kanthi metode turunan gradien kanthi perkiraan gradien ing saben langkah nggunakake bagean cilik saka pengamatan, utawa mini-batch.

Kerangka sinau jero sing ditulis ing Python duwe kelas khusus sing ngleksanakake iterator adhedhasar data: tabel, gambar ing folder, format binar, lsp. Sampeyan bisa nggunakake opsi sing wis siap utawa nulis dhewe kanggo tugas tartamtu. Ing R kita bisa njupuk kauntungan saka kabeh fitur saka perpustakaan Python keras karo macem-macem backends nggunakake paket saka jeneng sing padha, kang siji dianggo ing ndhuwur paket njlentrehake. Sing terakhir pantes artikel dawa kapisah; iku ora mung ngijini sampeyan kanggo mbukak kode Python saka R, nanging uga ngijini sampeyan kanggo nransfer obyek antarane R lan Python, kanthi otomatis nindakake kabeh konversi jinis perlu.

Kita nyingkirake kabutuhan kanggo nyimpen kabeh data ing RAM kanthi nggunakake MonetDBite, kabeh karya "jaringan saraf" bakal ditindakake kanthi kode asli ing Python, kita mung kudu nulis iterator liwat data, amarga ora ana sing siap. kanggo kahanan kuwi ing salah siji R utawa Python. Ana mung rong syarat: kudu ngasilake batch ing daur ulang tanpa wates lan nyimpen negara ing antarane pengulangan (sing terakhir ing R diimplementasikake kanthi cara sing paling gampang nggunakake penutupan). Sadurunge, diwajibake kanthi jelas ngowahi array R dadi array numpy ing iterator, nanging versi paket saiki keras nindakake dhewe.

Iterator kanggo latihan lan validasi data dadi kaya ing ngisor iki:

Iterator kanggo data latihan lan validasi

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

Fungsi kasebut minangka input variabel kanthi sambungan menyang database, jumlah baris sing digunakake, jumlah kelas, ukuran batch, skala (scale = 1 cocog karo gambar rendering 256x256 piksel, scale = 0.5 β€” 128x128 piksel), indikator warna (color = FALSE nemtokake rendering ing grayscale nalika digunakake color = TRUE saben stroke digambar ing werna anyar) lan indikator preprocessing kanggo jaringan sing wis dilatih ing imagenet. Sing terakhir dibutuhake kanggo ngukur nilai piksel saka interval [0, 1] nganti interval [-1, 1], sing digunakake nalika latihan sing diwenehake. keras model.

Fungsi eksternal ngemot pamriksa jinis argumen, tabel data.table karo nomer baris campuran acak saka samples_index lan nomer batch, counter lan jumlah maksimum batch, uga expression SQL kanggo unloading data saka database. Kajaba iku, kita nemtokake analog cepet saka fungsi ing njero keras::to_categorical(). Kita nggunakake meh kabeh data kanggo latihan, ninggalake setengah persen kanggo validasi, supaya ukuran jaman diwatesi dening parameter steps_per_epoch nalika diarani keras::fit_generator(), lan kahanan if (i > max_i) mung bisa kanggo iterator validasi.

Ing fungsi internal, indeks baris dijupuk kanggo kumpulan sabanjure, cathetan dibongkar saka database kanthi counter batch nambah, parsing JSON (fungsi cpp_process_json_vector(), ditulis ing C++) lan nggawe array sing cocog karo gambar. Banjur vektor siji-panas karo label kelas digawe, array karo nilai piksel lan label digabungake menyang dhaftar, kang Nilai bali. Kanggo nyepetake karya, kita nggunakake nggawe indeks ing tabel data.table lan modifikasi liwat link - tanpa paket "chip" iki data.tabel Iku cukup angel mbayangno nggarap kanthi efektif karo jumlah data sing signifikan ing R.

Asil pangukuran kacepetan ing laptop Core i5 kaya ing ngisor iki:

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)

Pangenalan Doodle Draw Cepet: carane nggawe kanca karo R, C ++ lan jaringan saraf

Yen sampeyan duwe jumlah RAM sing cukup, sampeyan bisa kanthi serius nyepetake operasi database kanthi nransfer menyang RAM sing padha (32 GB cukup kanggo tugas kita). Ing Linux, partisi dipasang kanthi standar /dev/shm, manggoni nganti setengah kapasitas RAM. Sampeyan bisa nyorot liyane kanthi nyunting /etc/fstabkanggo entuk rekor kaya tmpfs /dev/shm tmpfs defaults,size=25g 0 0. Dadi manawa kanggo urip maneh lan mriksa asil kanthi mbukak printah df -h.

Iterator kanggo data tes katon luwih gampang, amarga set data tes cocog karo RAM:

Iterator kanggo data tes

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. Pamilihan arsitektur model

Arsitektur pisanan sing digunakake yaiku mobilenet v1, fitur kang rembugan ing iki pesen. Iku kalebu minangka standar keras lan, patut, kasedhiya ing paket kanthi jeneng sing padha kanggo R. Nanging nalika nyoba nggunakake gambar saluran siji, ana sing aneh: tensor input kudu duwe ukuran. (batch, height, width, 3), yaiku, jumlah saluran ora bisa diganti. Ora ana watesan ing Python, mula kita cepet-cepet nulis implementasine arsitektur iki, miturut artikel asli (tanpa dropout ing versi keras):

Arsitektur Mobilenet v1

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

Kerugian saka pendekatan iki jelas. Aku pengin nyoba akeh model, nanging ing nalisir, aku ora pengin nulis ulang saben arsitektur kanthi manual. Kita uga ora duwe kesempatan kanggo nggunakake bobot model sing wis dilatih ing imagenet. Kaya biasane, sinau dokumentasi mbantu. Fungsi get_config() ngidini sampeyan entuk katrangan model ing wangun sing cocog kanggo nyunting (base_model_conf$layers - dhaptar R biasa), lan fungsi from_config() nindakake konversi mbalikke menyang obyek model:

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)

Saiki ora angel nulis fungsi universal kanggo entuk apa wae sing diwenehake keras model nganggo utawa tanpa bobot sing dilatih ing imagenet:

Fungsi kanggo ngemot arsitektur siap-digawe

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

Nalika nggunakake gambar saluran siji, ora ana bobot sing wis dilatih. Iki bisa diatasi: nggunakake fungsi kasebut get_weights() entuk bobot model ing wangun dhaptar array R, ganti dimensi unsur pisanan saka dhaptar iki (kanthi njupuk siji saluran warna utawa rata-rata kabeh telu), banjur muat bobot bali menyang model kanthi fungsi set_weights(). Kita ora tau nambahake fungsi iki, amarga ing tahap iki wis jelas manawa luwih produktif kanggo nggarap gambar warna.

Kita nindakake akeh eksperimen nggunakake mobilenet versi 1 lan 2, uga resnet34. Arsitektur luwih modern kayata SE-ResNeXt tampil apik ing kompetisi iki. Sayange, kita ora duwe implementasine siap-digawe ing pembuangan kita, lan kita ora nulis dhewe (nanging kita mesthi bakal nulis).

5. Parameterisasi naskah

Kanggo penak, kabeh kode kanggo miwiti latihan dirancang minangka siji script, parameterized nggunakake docopt kaya mangkene:

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)

Paket docopt makili implementasine http://docopt.org/ kanggo R. Kanthi bantuan, script dibukak karo printah prasaja kaya Rscript bin/train_nn.R -m resnet50 -c -d /home/andrey/doodle_db utawa ./bin/train_nn.R -m resnet50 -c -d /home/andrey/doodle_db, yen file train_nn.R bisa dieksekusi (prentah iki bakal miwiti latihan model resnet50 ing gambar telung werna ukuran 128x128 piksel, database kudu dumunung ing folder /home/andrey/doodle_db). Sampeyan bisa nambah kacepetan sinau, jinis pangoptimal, lan paramèter liyane sing bisa disesuaikan menyang dhaptar. Ing proses nyiapake publikasi, ternyata arsitektur mobilenet_v2 saka versi saiki keras ing R nggunakake ora bisa amarga owah-owahan ora dijupuk menyang akun ing paket R, kita nunggu wong-wong mau kanggo ndandani iku.

Pendekatan iki ndadekake bisa nyepetake eksperimen kanthi model sing beda-beda dibandhingake karo peluncuran skrip sing luwih tradisional ing RStudio (kita nyathet paket kasebut minangka alternatif sing bisa ditindakake. tfruns). Nanging kauntungan utama yaiku kemampuan kanggo gampang ngatur peluncuran skrip ing Docker utawa mung ing server, tanpa nginstal RStudio kanggo iki.

6. Dockerization saka script

Kita nggunakake Docker kanggo njamin portabilitas lingkungan kanggo model latihan ing antarane anggota tim lan penyebaran kanthi cepet ing awan. Sampeyan bisa miwiti njaluk kenalan karo alat iki, kang relatif mboten umum kanggo programmer R, karo iki seri publikasi utawa kursus video.

Docker ngidini sampeyan nggawe gambar dhewe saka awal lan nggunakake gambar liyane minangka basis kanggo nggawe dhewe. Nalika nganalisa pilihan sing kasedhiya, kita entuk kesimpulan yen nginstal NVIDIA, driver CUDA + cuDNN lan perpustakaan Python minangka bagean gambar sing cukup akeh, lan kita mutusake kanggo njupuk gambar resmi minangka basis. tensorflow/tensorflow:1.12.0-gpu, nambahake paket R sing perlu ana.

File docker pungkasan katon kaya iki:

file docker

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

Kanggo penak, paket sing digunakake dilebokake ing variabel; akeh saka script ditulis sing disalin nang wadhah sak perakitan. Kita uga ngganti cangkang printah kanggo /bin/bash kanggo ease saka nggunakake isi /etc/os-release. Iki nyingkiri kabutuhan kanggo nemtokake versi OS ing kode.

Kajaba iku, skrip bash cilik ditulis sing ngidini sampeyan mbukak wadhah kanthi macem-macem perintah. Contone, iki bisa dadi skrip kanggo latihan jaringan saraf sing sadurunge diselehake ing wadhah kasebut, utawa cangkang perintah kanggo debugging lan ngawasi operasi wadhah kasebut:

Script kanggo miwiti wadhah

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

Yen script bash iki mbukak tanpa paramèter, script bakal disebut nang wadhah train_nn.R kanthi nilai standar; yen argumen posisional pisanan yaiku "bash", wadhah kasebut bakal diwiwiti kanthi interaktif karo cangkang perintah. Ing kabeh kasus liyane, nilai-nilai argumen posisi diganti: CMD="Rscript /app/train_nn.R $@".

Wigati dicathet yen direktori karo data sumber lan database, uga direktori kanggo nyimpen model sing dilatih, dipasang ing wadhah saka sistem host, sing ngidini sampeyan ngakses asil skrip tanpa manipulasi sing ora perlu.

7. Nggunakake sawetara GPUs ing Google Cloud

Salah sawijining fitur kompetisi yaiku data sing rame banget (ndeleng gambar judhul, dipinjam saka @Leigh.plt saka ODS slack). Gedhe kelompok mbantu pertempuran iki, lan sawise nyobi ing PC karo 1 GPU, kita mutusakΓ© kanggo Master model latihan ing sawetara GPUs ing mΓ©ga. GoogleCloud digunakake (guide apik kanggo dhasar) amarga pilihan akeh konfigurasi kasedhiya, prices cukup lan $ 300 bonus. Saka srakah, Aku dhawuh conto 4xV100 karo SSD lan ton saka RAM, lan iku kesalahan gedhe. Mesin kasebut cepet mangan dhuwit; sampeyan bisa nyuwil eksperimen tanpa pipa sing wis kabukten. Kanggo tujuan pendidikan, luwih becik njupuk K80. Nanging jumlah RAM sing akeh banget migunani - SSD awan ora ngematake kinerja, mula database ditransfer menyang dev/shm.

Sing paling menarik yaiku fragmen kode sing tanggung jawab kanggo nggunakake macem-macem GPU. Kaping pisanan, model digawe ing CPU nggunakake manajer konteks, kaya ing 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
  )
})

Banjur model sing ora dikompilasi (iki penting) disalin menyang sawetara GPU sing kasedhiya, lan mung sawise dikompilasi:

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

Teknik klasik pembekuan kabeh lapisan kajaba sing pungkasan, latihan lapisan pungkasan, unfreezing lan latihan maneh kabeh model kanggo sawetara GPU ora bisa dileksanakake.

Latihan dipantau tanpa nggunakake. papan tensor, mbatesi awake dhewe kanggo ngrekam log lan nyimpen model kanthi jeneng informatif sawise saben jaman:

Telpon balik

# Π¨Π°Π±Π»ΠΎΠ½ ΠΈΠΌΠ΅Π½ΠΈ Ρ„Π°ΠΉΠ»Π° Π»ΠΎΠ³Π°
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. Tinimbang kesimpulan

Sawetara masalah sing kita alami durung ditanggulangi:

  • Π² keras ora ana fungsi sing wis siap kanggo nggoleki kanthi otomatis tingkat sinau sing optimal (analog lr_finder ing perpustakaan cepet.ai); Kanthi sawetara gaweyan, bisa port implementasine pihak katelu kanggo R, contone, iki;
  • minangka akibat saka titik sadurunge, ora bisa milih kacepetan latihan sing bener nalika nggunakake sawetara GPU;
  • ana kekurangan arsitektur jaringan saraf modern, utamane sing wis dilatih ing imagenet;
  • ora ana siji siklus kabijakan lan tarif sinau diskriminatif (cosinus annealing ana ing panyuwunan kita dipun ginakaken, matur nuwun skeydan).

Apa sing migunani sing disinaoni saka kompetisi iki:

  • Ing hardware relatif kurang daya, sampeyan bisa nggarap prayoga (kaping pirang-pirang ukuran RAM) volume data tanpa pain. Kantong plastik data.tabel nyimpen memori amarga owah-owahan ing-panggonan tabel, kang ngindari nyalin, lan nalika digunakake bener, Kapabilitas meh tansah nduduhake kacepetan paling dhuwur antarane kabeh alat dikenal kanggo basa scripting. Nyimpen data ing basis data ngidini sampeyan, ing pirang-pirang kasus, ora mikir babar pisan babagan kudu ngempet kabeh dataset menyang RAM.
  • Fungsi alon ing R bisa diganti karo sing cepet ing C ++ nggunakake paket Rcpp. Yen saliyane nggunakake RcppThread utawa RcppParalel, kita entuk implementasi multi-threaded lintas-platform, dadi ora perlu kanggo parallelize kode ing tingkat R.
  • paket Rcpp bisa digunakake tanpa kawruh serius babagan C ++, minimal sing dibutuhake wis mbatesi kene. File header kanggo sawetara perpustakaan C kelangan kaya xtensor kasedhiya ing CRAN, yaiku, infrastruktur lagi dibentuk kanggo implementasi proyek sing nggabungake kode C ++ kinerja dhuwur sing siap digawe menyang R. Penak tambahan yaiku nyorot sintaks lan penganalisa kode C ++ statis ing RStudio.
  • docopt ngidini sampeyan mbukak skrip mandiri kanthi paramΓ¨ter. Iki trep kanggo nggunakake ing server remot, kalebu. ing docker. Ing RStudio, ora trep kanggo nindakake eksperimen pirang-pirang jam kanthi latihan jaringan saraf, lan nginstal IDE ing server dhewe ora mesthi dibenerake.
  • Docker njamin portabilitas kode lan reproduktifitas asil antarane pangembang kanthi versi OS lan perpustakaan sing beda-beda, uga gampang dieksekusi ing server. Sampeyan bisa miwiti kabeh pipa latihan kanthi mung siji printah.
  • Google Cloud minangka cara sing ramah anggaran kanggo eksperimen ing hardware sing larang, nanging sampeyan kudu milih konfigurasi kanthi ati-ati.
  • Ngukur kacepetan fragmen kode individu migunani banget, utamane nalika nggabungake R lan C++, lan nganggo paket. bangku - uga gampang banget.

Sakabèhé pengalaman iki banget maringi hadiah lan kita terus bisa kanggo mutusake masalah sawetara masalah wungu.

Source: www.habr.com

Add a comment