Iyara Fa idanimọ Doodle: bii o ṣe le ṣe awọn ọrẹ pẹlu R, C++ ati awọn nẹtiwọọki nkankikan

Iyara Fa idanimọ Doodle: bii o ṣe le ṣe awọn ọrẹ pẹlu R, C++ ati awọn nẹtiwọọki nkankikan

Hey Habr!

Igba Irẹdanu Ewe ti o kẹhin, Kaggle gbalejo idije kan lati ṣe iyasọtọ awọn aworan iyaworan, Iyara Fa Doodle idanimọ, ninu eyiti, laarin awọn miiran, ẹgbẹ kan ti awọn onimọ-jinlẹ R ti kopa: Artem Klevtsova, Philippa Manager и Andrey Ogurtsov. A kii yoo ṣe apejuwe idije ni awọn alaye; ti o ti ṣe tẹlẹ ninu to šẹšẹ atejade.

Ni akoko yii ko ṣiṣẹ pẹlu ogbin medal, ṣugbọn ọpọlọpọ awọn iriri ti o niyelori ni a gba, nitorina Emi yoo fẹ lati sọ fun agbegbe nipa nọmba awọn ohun ti o wuni julọ ati ti o wulo lori Kagle ati ni iṣẹ ojoojumọ. Lara awọn ero ti a sọrọ: igbesi aye ti o nira laisi OpenCV, JSON parsing (awọn apẹẹrẹ ṣe ayẹwo isọpọ ti koodu C ++ sinu awọn iwe afọwọkọ tabi awọn idii ni R nipa lilo Rcpp), parameterization ti awọn iwe afọwọkọ ati dockerization ti ik ojutu. Gbogbo koodu lati ifiranṣẹ ni fọọmu ti o dara fun ipaniyan wa ninu awọn ibi ipamọ.

Awọn akoonu:

  1. Ṣe fifuye data daradara lati CSV sinu MonetDB
  2. Ngbaradi awọn ipele
  3. Iterators fun unloading batches lati awọn database
  4. Yiyan Awoṣe Architecture
  5. parameterization akosile
  6. Dockerization ti awọn iwe afọwọkọ
  7. Lilo awọn GPU pupọ lori awọsanma Google
  8. Dipo ti pinnu

1. Mu data daradara lati CSV sinu aaye data MonetDB

Awọn data ninu idije yii ko pese ni irisi awọn aworan ti a ti ṣetan, ṣugbọn ni irisi awọn faili 340 CSV (faili kan fun kilasi kọọkan) ti o ni awọn JSON pẹlu awọn ipoidojuko ojuami. Nipa sisopọ awọn aaye wọnyi pẹlu awọn ila, a gba aworan ipari ti o ni awọn piksẹli 256x256. Paapaa fun igbasilẹ kọọkan aami kan wa ti o nfihan boya a ti mọ aworan naa ni deede nipasẹ olupilẹṣẹ ti a lo ni akoko ti a ti gba data data, koodu lẹta meji ti orilẹ-ede ibugbe ti onkọwe aworan naa, idanimọ alailẹgbẹ, akoko tamp. ati orukọ kilasi ti o baamu orukọ faili naa. Ẹya ti o rọrun ti data atilẹba ṣe iwọn 7.4 GB ninu ile-ipamọ ati isunmọ 20 GB lẹhin ṣiṣi silẹ, data kikun lẹhin ṣiṣi silẹ gba 240 GB. Awọn oluṣeto ṣe idaniloju pe awọn ẹya mejeeji tun ṣe awọn iyaworan kanna, afipamo pe ẹya kikun jẹ apọju. Ni eyikeyi idiyele, titoju awọn aworan miliọnu 50 ni awọn faili ayaworan tabi ni irisi awọn akojọpọ ni a ro lẹsẹkẹsẹ alailere, ati pe a pinnu lati dapọ gbogbo awọn faili CSV lati ile-ipamọ naa. train_simplified.zip sinu ibi ipamọ data pẹlu iran atẹle ti awọn aworan ti iwọn ti a beere “lori fo” fun ipele kọọkan.

Eto ti a fihan daradara ni a yan bi DBMS MonetDB, eyun imuse fun R bi package MonetDLite. Apo naa pẹlu ẹya ifibọ ti olupin data data ati gba ọ laaye lati gbe olupin naa taara lati igba R ki o ṣiṣẹ pẹlu rẹ nibẹ. Ṣiṣẹda data data ati sisopọ si rẹ ni a ṣe pẹlu aṣẹ kan:

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

A yoo nilo lati ṣẹda awọn tabili meji: ọkan fun gbogbo data, ekeji fun alaye iṣẹ nipa awọn faili ti a ṣe igbasilẹ (wulo ti nkan kan ba jẹ aṣiṣe ati ilana naa ni lati tun bẹrẹ lẹhin igbasilẹ awọn faili pupọ):

Ṣiṣẹda tabili

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

Ọna ti o yara ju lati gbe data sinu ibi ipamọ data ni lati daakọ awọn faili CSV taara ni lilo SQL - pipaṣẹ COPY OFFSET 2 INTO tablename FROM path USING DELIMITERS ',','n','"' NULL AS '' BEST EFFORTnibo tablename - tabili orukọ ati path - ọna si faili naa. Lakoko ti o n ṣiṣẹ pẹlu ile ifi nkan pamosi, o ṣe awari pe imuse ti a ṣe sinu unzip ni R ko ṣiṣẹ ni deede pẹlu nọmba awọn faili lati ile-ipamọ, nitorinaa a lo eto naa unzip (lilo paramita getOption("unzip")).

Iṣẹ fun kikọ si 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))
}

Ti o ba nilo lati yi tabili pada ṣaaju kikọ si ibi ipamọ data, o to lati kọja ninu ariyanjiyan naa preprocess iṣẹ ti yoo yi data pada.

Koodu fun ikojọpọ data lẹsẹsẹ sinu database:

Kikọ data si 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

Akoko ikojọpọ data le yatọ si da lori awọn abuda iyara ti awakọ ti a lo. Ninu ọran wa, kika ati kikọ laarin SSD kan tabi lati kọnputa filasi (faili orisun) si SSD (DB) ko gba to iṣẹju mẹwa 10.

Yoo gba iṣẹju diẹ diẹ sii lati ṣẹda iwe kan pẹlu aami kilasi odidi ati iwe atọka kan (ORDERED INDEX) pẹlu awọn nọmba laini nipasẹ eyiti awọn akiyesi yoo jẹ apẹẹrẹ nigbati o ṣẹda awọn ipele:

Ṣiṣẹda Afikun Awọn ọwọn ati Atọka

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

Lati yanju iṣoro ti ṣiṣẹda ipele kan lori fifo, a nilo lati ṣaṣeyọri iyara ti o pọju ti yiyo awọn ori ila laileto lati tabili doodles. Fun eyi a lo awọn ẹtan 3. Ohun akọkọ ni lati dinku iwọn iwọn ti iru ti o tọju ID akiyesi. Ninu ipilẹ data atilẹba, iru ti o nilo lati tọju ID naa jẹ bigint, ṣugbọn nọmba awọn akiyesi jẹ ki o ṣee ṣe lati baamu awọn idamọ wọn, dogba si nọmba ordinal, sinu iru. int. Awọn search jẹ Elo yiyara ninu apere yi. Ẹtan keji ni lati lo ORDERED INDEX — a wá si yi ipinnu empirically, ntẹriba lọ nipasẹ gbogbo wa awọn aṣayan. Ẹkẹta ni lati lo awọn ibeere paramita. Koko-ọrọ ti ọna naa ni lati ṣiṣẹ aṣẹ ni ẹẹkan PREPARE pẹlu lilo atẹle ti ikosile ti o pese silẹ nigbati o ṣẹda opo awọn ibeere ti iru kanna, ṣugbọn ni otitọ anfani wa ni lafiwe pẹlu ọkan ti o rọrun. SELECT ti jade lati wa laarin iwọn aṣiṣe iṣiro.

Ilana ikojọpọ data ko gba diẹ sii ju 450 MB ti Ramu. Iyẹn ni, ọna ti a ṣapejuwe gba ọ laaye lati gbe awọn iwe data ti o ṣe iwọn mewa ti gigabytes lori fere eyikeyi ohun elo isuna, pẹlu diẹ ninu awọn ẹrọ igbimọ kan, eyiti o dara julọ.

Gbogbo ohun ti o ku ni lati wiwọn iyara gbigba data (aileto) ati ṣe iṣiro igbelowọn nigbati iṣapẹẹrẹ awọn ipele ti awọn titobi oriṣiriṣi:

Aṣepari aaye data

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)

Iyara Fa idanimọ Doodle: bii o ṣe le ṣe awọn ọrẹ pẹlu R, C++ ati awọn nẹtiwọọki nkankikan

2. Ngbaradi awọn ipele

Gbogbo ilana igbaradi ipele ni awọn igbesẹ wọnyi:

  1. Ṣiṣayẹwo ọpọlọpọ awọn JSON ti o ni awọn ọna asopọ ti awọn okun pẹlu awọn ipoidojuko ti awọn aaye.
  2. Yiya awọn ila awọ ti o da lori awọn ipoidojuko ti awọn aaye lori aworan ti iwọn ti a beere (fun apẹẹrẹ, 256 × 256 tabi 128 × 128).
  3. Yiyipada awọn aworan abajade sinu tensor.

Gẹgẹbi apakan ti idije laarin awọn kernel Python, iṣoro naa ni a yanju ni akọkọ nipa lilo OpenCV. Ọkan ninu awọn afọwọṣe ti o rọrun julọ ati ti o han julọ ni R yoo dabi eyi:

Ṣiṣe JSON si Iyipada Tensor ni 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)
}

Iyaworan ni a ṣe ni lilo awọn irinṣẹ R boṣewa ati fipamọ si PNG igba diẹ ti o fipamọ sinu Ramu (lori Linux, awọn ilana R igba diẹ wa ninu itọsọna naa /tmp, agesin ni Ramu). Lẹhinna a ka faili yii bi titobi onisẹpo mẹta pẹlu awọn nọmba ti o wa lati 0 si 1. Eyi ṣe pataki nitori pe BMP ti aṣa diẹ sii yoo ka sinu akojọpọ aise pẹlu awọn koodu awọ hex.

Jẹ ki a ṣe idanwo abajade:

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

Iyara Fa idanimọ Doodle: bii o ṣe le ṣe awọn ọrẹ pẹlu R, C++ ati awọn nẹtiwọọki nkankikan

Awọn ipele funrararẹ yoo ṣẹda bi atẹle:

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

Imuse yii dabi ẹnipe o dara julọ fun wa, niwọn bi dida awọn ipele nla gba akoko pipẹ ti ko tọ, ati pe a pinnu lati lo anfani ti iriri awọn ẹlẹgbẹ wa nipa lilo ile-ikawe ti o lagbara. OpenCV. Ni akoko yẹn ko si package ti a ti ṣetan fun R (ko si ni bayi), nitorinaa imuse kekere ti iṣẹ ṣiṣe ti o nilo ni a kọ sinu C ++ pẹlu iṣọpọ sinu koodu R nipa lilo Rcpp.

Lati yanju iṣoro naa, awọn idii ati awọn ile-ikawe wọnyi ni a lo:

  1. OpenCV fun ṣiṣẹ pẹlu awọn aworan ati awọn ila iyaworan. Ti a lo awọn ile-ikawe eto ti a ti fi sii tẹlẹ ati awọn faili akọsori, bakanna bi sisopọ ti o ni agbara.

  2. xtensor fun ṣiṣẹ pẹlu multidimensional orun ati tenors. A lo awọn faili akọsori ti o wa ninu apo R ti orukọ kanna. Ile-ikawe naa ngbanilaaye lati ṣiṣẹ pẹlu awọn akojọpọ onidiwọn, mejeeji ni pataki ila ati aṣẹ pataki ọwọn.

  3. ndjson fun itupalẹ JSON. Ile-ikawe yii ni a lo ninu xtensor laifọwọyi ti o ba jẹ bayi ni ise agbese.

  4. RcppThread fun siseto olona-asapo processing ti a fekito lati JSON. Lo awọn faili akọsori ti a pese nipasẹ package yii. Lati diẹ gbajumo RcppParallel Apo naa, laarin awọn ohun miiran, ni ẹrọ idalọwọduro lupu ti a ṣe sinu.

O ṣe akiyesi pe xtensor ti jade lati jẹ ọlọrun-ọlọrun: ni afikun si otitọ pe o ni iṣẹ ṣiṣe lọpọlọpọ ati iṣẹ ṣiṣe giga, awọn olupilẹṣẹ rẹ wa ni idahun oyimbo ati dahun awọn ibeere ni kiakia ati ni awọn alaye. Pẹlu iranlọwọ wọn, o ṣee ṣe lati ṣe awọn iyipada ti OpenCV matrices sinu awọn tenors xtensor, bakanna bi ọna lati ṣajọpọ awọn ẹmu aworan onisẹpo 3 sinu tensor onisẹpo 4 ti iwọn to pe (ipele funrararẹ).

Awọn ohun elo fun kikọ Rcpp, xtensor ati 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

Lati ṣajọ awọn faili ti o lo awọn faili eto ati sisopọ agbara pẹlu awọn ile ikawe ti a fi sori ẹrọ, a lo ẹrọ itanna ti a ṣe imuse ninu package Rcpp. Lati wa awọn ọna ati awọn asia laifọwọyi, a lo ohun elo Linux olokiki kan pkg-atunto.

Imuse ohun itanna Rcpp fun lilo ile-ikawe 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)
  ))
})

Bi abajade iṣẹ ohun itanna naa, awọn iye wọnyi yoo rọpo lakoko ilana ikojọpọ:

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"

Koodu imuse fun sisọ JSON ati ṣiṣẹda ipele kan fun gbigbe si awoṣe ni a fun labẹ apanirun. Ni akọkọ, ṣafikun iwe ilana iṣẹ akanṣe agbegbe kan lati wa awọn faili akọsori (nilo fun ndjson):

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

Imuse ti JSON si tensor iyipada ni 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;
}

Koodu yii yẹ ki o gbe sinu faili naa src/cv_xt.cpp ati ṣajọ pẹlu aṣẹ Rcpp::sourceCpp(file = "src/cv_xt.cpp", env = .GlobalEnv); tun nilo fun iṣẹ nlohmann/json.hpp ati bẹbẹ lọ ibi ipamọ. Awọn koodu ti pin si awọn iṣẹ pupọ:

  • to_xt - iṣẹ apẹrẹ fun yiyipada matrix aworan kan (cv::Mat) si tensor xt::xtensor;

  • parse_json - iṣẹ naa n ṣalaye okun JSON kan, yọkuro awọn ipoidojuko ti awọn aaye, iṣakojọpọ wọn sinu fekito kan;

  • ocv_draw_lines - lati abajade fekito ti awọn aaye, fa awọn ila awọ-pupọ;

  • process - daapọ awọn iṣẹ ti o wa loke ati tun ṣafikun agbara lati ṣe iwọn aworan abajade;

  • cpp_process_json_str - wrapper lori iṣẹ process, eyi ti o ṣe okeere esi si ohun R-ohun (multidimensional orun);

  • cpp_process_json_vector - wrapper lori iṣẹ cpp_process_json_str, eyi ti o faye gba o lati lọwọ a okun fekito ni olona-asapo mode.

Lati fa awọn laini awọ-pupọ, awoṣe awọ HSV ti lo, atẹle nipa iyipada si RGB. Jẹ ki a ṣe idanwo abajade:

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

Iyara Fa idanimọ Doodle: bii o ṣe le ṣe awọn ọrẹ pẹlu R, C++ ati awọn nẹtiwọọki nkankikan
Ifiwera ti iyara awọn imuse ni R ati 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") 

Iyara Fa idanimọ Doodle: bii o ṣe le ṣe awọn ọrẹ pẹlu R, C++ ati awọn nẹtiwọọki nkankikan

Gẹgẹbi o ti le rii, ilosoke iyara ti jade lati jẹ pataki pupọ, ati pe ko ṣee ṣe lati ni ibamu pẹlu koodu C ++ nipa sisọ koodu R.

3. Iterators fun unloading batches lati awọn database

R ni orukọ ti o tọ si fun sisẹ data ti o baamu ni Ramu, lakoko ti Python jẹ ẹya diẹ sii nipasẹ sisẹ data aṣetunṣe, gbigba ọ laaye lati ni irọrun ati nipa ti ara ṣe awọn iṣiro-jade ti mojuto (awọn iṣiro lilo iranti ita). Apeere Ayebaye ati ti o yẹ fun wa ni aaye ti iṣoro ti a ṣalaye jẹ awọn nẹtiwọọki ti iṣan ti o jinlẹ ti ikẹkọ nipasẹ ọna isunmọ gradient pẹlu isunmọ ti gradient ni igbesẹ kọọkan nipa lilo ipin kekere ti awọn akiyesi, tabi kekere-ipele.

Awọn ilana ikẹkọ ti o jinlẹ ti a kọ sinu Python ni awọn kilasi pataki ti o ṣe awọn atunbere ti o da lori data: awọn tabili, awọn aworan ninu awọn folda, awọn ọna kika alakomeji, bbl O le lo awọn aṣayan ti a ti ṣetan tabi kọ tirẹ fun awọn iṣẹ-ṣiṣe kan pato. Ni R a le ya awọn anfani ti gbogbo awọn ẹya ara ẹrọ ti awọn Python ìkàwé keira pẹlu awọn ẹhin oriṣiriṣi rẹ nipa lilo package ti orukọ kanna, eyiti o ṣiṣẹ lori oke package naa atunse. Awọn igbehin ye kan lọtọ gun article; kii ṣe nikan gba ọ laaye lati ṣiṣe koodu Python lati R, ṣugbọn tun gba ọ laaye lati gbe awọn nkan laarin awọn akoko R ati Python, ṣiṣe gbogbo awọn iyipada iru pataki laifọwọyi.

A yọkuro iwulo lati ṣafipamọ gbogbo data sinu Ramu nipasẹ lilo MonetDLite, gbogbo iṣẹ “nẹtiwọọki aifọkanbalẹ” yoo ṣee ṣe nipasẹ koodu atilẹba ni Python, a kan ni lati kọ itusilẹ lori data naa, nitori pe ko si nkankan ti o ṣetan. fun iru ipo ni boya R tabi Python. Awọn ibeere meji nikan ni o wa fun rẹ: o gbọdọ da awọn ipele pada ni lupu ailopin ati fi ipo rẹ pamọ laarin awọn atunbere (igbehin ni R ni imuse ni ọna ti o rọrun julọ nipa lilo awọn pipade). Ni iṣaaju, o nilo lati yi awọn ọna R pada ni gbangba si awọn ọna nọmba ti inu aṣetunṣe, ṣugbọn ẹya lọwọlọwọ ti package keira ṣe funrararẹ.

Atunṣe fun ikẹkọ ati data afọwọsi ti jade lati jẹ bi atẹle:

Iterator fun ikẹkọ ati data afọwọsi

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

Iṣẹ naa gba bi titẹ oniyipada kan pẹlu asopọ si data data, awọn nọmba ti awọn laini ti a lo, nọmba awọn kilasi, iwọn ipele, iwọn (scale = 1 ni ibamu si awọn aworan fifunni ti awọn piksẹli 256x256, scale = 0.5 — 128x128 awọn piksẹli), atọka awọ (color = FALSE pato Rendering ni grẹyscale nigba ti lo color = TRUE kọọkan ọpọlọ ti wa ni kale ni titun kan awọ) ati ki o kan preprocessing Atọka fun awọn nẹtiwọki kọkọ-oṣiṣẹ lori imagenet. A nilo igbehin lati le ṣe iwọn awọn iye piksẹli lati aarin [0, 1] si aarin [-1, 1], eyiti a lo nigbati ikẹkọ ohun ti a pese keira awọn awoṣe.

Iṣẹ ita ni ṣiṣayẹwo iru ariyanjiyan, tabili kan data.table pẹlu laileto adalu ila awọn nọmba lati samples_index ati ipele awọn nọmba, counter ati awọn ti o pọju nọmba ti batches, bi daradara bi ohun SQL ikosile fun unloading data lati awọn database. Ni afikun, a ṣe asọye afọwọṣe iyara ti iṣẹ inu keras::to_categorical(). A lo gbogbo data fun ikẹkọ, nlọ idaji ida kan fun afọwọsi, nitorinaa iwọn epoch ni opin nipasẹ paramita steps_per_epoch nigbati a npe ni keras::fit_generator(), ati ipo naa if (i > max_i) nikan sise fun afọwọsi iterator.

Ninu iṣẹ inu, awọn atọka ila ni a gba pada fun ipele ti o tẹle, awọn igbasilẹ ti wa ni ṣiṣi silẹ lati ibi ipamọ data pẹlu iṣiro ipele ti n pọ si, JSON parsing (iṣẹ cpp_process_json_vector(), ti a kọ sinu C ++) ati ṣiṣẹda awọn akojọpọ ti o baamu si awọn aworan. Lẹhinna awọn olutọpa gbona ọkan pẹlu awọn aami kilasi ni a ṣẹda, awọn akojọpọ pẹlu awọn iye piksẹli ati awọn aami ni idapo sinu atokọ kan, eyiti o jẹ iye ipadabọ. Lati mu iyara ṣiṣẹ, a lo awọn ẹda ti awọn atọka ninu awọn tabili data.table ati iyipada nipasẹ ọna asopọ - laisi package “awọn eerun” wọnyi tabili O jẹ ohun ti o nira lati fojuinu ṣiṣẹ ni imunadoko pẹlu eyikeyi iye pataki ti data ni R.

Awọn abajade ti awọn wiwọn iyara lori kọǹpútà alágbèéká Core i5 jẹ atẹle yii:

Aṣepari atunbere

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)

Iyara Fa idanimọ Doodle: bii o ṣe le ṣe awọn ọrẹ pẹlu R, C++ ati awọn nẹtiwọọki nkankikan

Ti o ba ni iye Ramu ti o to, o le ṣe iyara iṣẹ ti database nipa gbigbe si Ramu kanna (32 GB ti to fun iṣẹ-ṣiṣe wa). Ni Lainos, ipin naa ti gbe nipasẹ aiyipada /dev/shm, occupying soke si idaji awọn Ramu agbara. O le ṣe afihan diẹ sii nipa ṣiṣatunṣe /etc/fstablati gba igbasilẹ bii tmpfs /dev/shm tmpfs defaults,size=25g 0 0. Rii daju lati tun atunbere ati ṣayẹwo abajade nipa ṣiṣe pipaṣẹ naa df -h.

Oluṣeto fun data idanwo dabi irọrun pupọ, niwọn igba ti data idanwo naa baamu patapata sinu Ramu:

Iterator fun igbeyewo data

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. Asayan ti faaji awoṣe

Ni igba akọkọ ti faaji lo wà mobilenet v1, awọn ẹya ara ẹrọ ti eyi ti wa ni sísọ ni eyi ifiranṣẹ. O ti wa ni bi bošewa keira ati, ni ibamu, wa ninu package ti orukọ kanna fun R. Ṣugbọn nigbati o ba n gbiyanju lati lo pẹlu awọn aworan ikanni kan, ohun ajeji kan jade: tensor titẹ sii gbọdọ nigbagbogbo ni iwọn. (batch, height, width, 3), iyẹn ni, nọmba awọn ikanni ko le yipada. Ko si iru aropin bẹ ni Python, nitorinaa a yara ati kọ imuse tiwa ti faaji yii, ni atẹle nkan atilẹba (laisi sisọ silẹ ti o wa ninu ẹya keras):

Mobilenet v1 faaji

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

Awọn aila-nfani ti ọna yii jẹ kedere. Mo fẹ lati ṣe idanwo ọpọlọpọ awọn awoṣe, ṣugbọn ni ilodi si, Emi ko fẹ lati tun kọ faaji kọọkan pẹlu ọwọ. A tun ni anfani lati lo awọn iwuwo ti awọn awoṣe ti a ti kọ tẹlẹ lori imagenet. Gẹgẹbi igbagbogbo, ikẹkọ awọn iwe-ipamọ ṣe iranlọwọ. Išẹ get_config() gba ọ laaye lati gba apejuwe ti awoṣe ni fọọmu ti o dara fun ṣiṣatunṣe (base_model_conf$layers - a deede R akojọ), ati awọn iṣẹ from_config() ṣe iyipada iyipada si nkan awoṣe:

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)

Bayi ko nira lati kọ iṣẹ gbogbo agbaye lati gba eyikeyi ninu awọn ti a pese keira awọn awoṣe pẹlu tabi laisi awọn iwuwo ikẹkọ lori imagenet:

Išẹ fun ikojọpọ setan-ṣe architectures

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

Nigba lilo awọn aworan ikanni ẹyọkan, ko si awọn iwuwo ti a ti kọ tẹlẹ ti a lo. Eyi le ṣe atunṣe: lilo iṣẹ naa get_weights() gba awọn iwọn awoṣe ni irisi atokọ ti awọn akojọpọ R, yi iwọn ti ipin akọkọ ti atokọ yii pada (nipa gbigbe ikanni awọ kan tabi aropin gbogbo awọn mẹta), ati lẹhinna gbe awọn iwuwo pada sinu awoṣe pẹlu iṣẹ naa. set_weights(). A ko ṣafikun iṣẹ-ṣiṣe yii, nitori ni ipele yii o ti han tẹlẹ pe o ni iṣelọpọ diẹ sii lati ṣiṣẹ pẹlu awọn aworan awọ.

A ṣe pupọ julọ awọn idanwo ni lilo awọn ẹya alagbekanet 1 ati 2, bakanna bi resnet34. Awọn ayaworan ode oni diẹ sii bii SE-ResNeXt ṣe daradara ni idije yii. Laanu, a ko ni awọn imuse ti a ṣe ni isọnu wa, ati pe a ko kọ tiwa (ṣugbọn a yoo kọ dajudaju).

5. Parameterization ti awọn iwe afọwọkọ

Fun irọrun, gbogbo koodu fun ikẹkọ ti o bẹrẹ jẹ apẹrẹ bi iwe afọwọkọ kan, parameterized ni lilo docopt ni ọna atẹle:

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)

Apoti docopt duro imuse http://docopt.org/ fun R. Pẹlu iranlọwọ rẹ, awọn iwe afọwọkọ ti ṣe ifilọlẹ pẹlu awọn aṣẹ ti o rọrun bi Rscript bin/train_nn.R -m resnet50 -c -d /home/andrey/doodle_db tabi ./bin/train_nn.R -m resnet50 -c -d /home/andrey/doodle_db, ti faili train_nn.R jẹ ṣiṣe (aṣẹ yii yoo bẹrẹ ikẹkọ awoṣe resnet50 lori awọn aworan awọ mẹta ti o ni iwọn awọn piksẹli 128x128, ibi ipamọ data gbọdọ wa ninu folda naa /home/andrey/doodle_db). O le ṣafikun iyara ikẹkọ, iru iṣapeye, ati eyikeyi awọn aye isọdi miiran si atokọ naa. Ni awọn ilana ti ngbaradi awọn atejade, o wa ni jade wipe awọn faaji mobilenet_v2 lati awọn ti isiyi ti ikede keira ni R lilo ko ṣee ṣe nitori awọn ayipada ti a ko ṣe akiyesi ni package R, a n duro de wọn lati ṣatunṣe rẹ.

Ọna yii jẹ ki o ṣee ṣe lati yara awọn adanwo ni pataki pẹlu awọn awoṣe oriṣiriṣi ni akawe si ifilọlẹ aṣa diẹ sii ti awọn iwe afọwọkọ ni RStudio (a ṣe akiyesi package bi yiyan ti o ṣeeṣe tfruns). Ṣugbọn anfani akọkọ ni agbara lati ni irọrun ṣakoso ifilọlẹ awọn iwe afọwọkọ ni Docker tabi nirọrun lori olupin, laisi fifi RStudio sori eyi.

6. Dockerization ti awọn iwe afọwọkọ

A lo Docker lati rii daju gbigbe ti agbegbe fun awọn awoṣe ikẹkọ laarin awọn ọmọ ẹgbẹ ẹgbẹ ati fun imuṣiṣẹ ni iyara ni awọsanma. O le bẹrẹ acquainted pẹlu yi ọpa, eyi ti o jẹ jo dani fun ohun R pirogirama, pẹlu eyi jara ti jẹ ti tabi fidio dajudaju.

Docker gba ọ laaye lati ṣẹda awọn aworan tirẹ lati ibere ati lo awọn aworan miiran bi ipilẹ fun ṣiṣẹda tirẹ. Nigbati o ba n ṣe itupalẹ awọn aṣayan ti o wa, a wa si ipari pe fifi sori ẹrọ NVIDIA, CUDA + cuDNN awakọ ati awọn ile-ikawe Python jẹ apakan iwọntunwọnsi ti aworan naa, ati pe a pinnu lati ya aworan osise bi ipilẹ. tensorflow/tensorflow:1.12.0-gpu, fifi awọn idii R pataki nibẹ.

Faili docker ikẹhin dabi eyi:

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

Fun irọrun, awọn idii ti a lo ni a fi sinu awọn oniyipada; ọpọlọpọ awọn iwe afọwọkọ ti a kọ ni a daakọ inu awọn apoti lakoko apejọ. A tun yi ikarahun aṣẹ pada si /bin/bash fun irọrun ti lilo akoonu /etc/os-release. Eyi yago fun iwulo lati pato ẹya OS ninu koodu naa.

Ni afikun, a kọ iwe afọwọkọ bash kekere ti o fun ọ laaye lati ṣe ifilọlẹ eiyan kan pẹlu awọn aṣẹ pupọ. Fun apẹẹrẹ, iwọnyi le jẹ awọn iwe afọwọkọ fun ikẹkọ awọn nẹtiwọọki nkankikan ti a ti gbe tẹlẹ sinu eiyan naa, tabi ikarahun aṣẹ fun ṣiṣatunṣe ati abojuto iṣẹ ti eiyan naa:

Akosile lati lọlẹ awọn eiyan

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

Ti iwe afọwọkọ bash yii ba ṣiṣẹ laisi awọn paramita, iwe afọwọkọ naa yoo pe ni inu eiyan naa train_nn.R pẹlu awọn iye aiyipada; Ti ariyanjiyan ipo akọkọ jẹ “bash”, lẹhinna eiyan naa yoo bẹrẹ ni ibaraenisepo pẹlu ikarahun aṣẹ kan. Ni gbogbo awọn ọran miiran, awọn iye ti awọn ariyanjiyan ipo ti rọpo: CMD="Rscript /app/train_nn.R $@".

O tọ lati ṣe akiyesi pe awọn ilana pẹlu data orisun ati data data, ati itọsọna fun fifipamọ awọn awoṣe ikẹkọ, ti wa ni inu inu eiyan lati inu eto agbalejo, eyiti o fun ọ laaye lati wọle si awọn abajade ti awọn iwe afọwọkọ laisi awọn ifọwọyi ti ko wulo.

7. Lilo ọpọ GPUs lori Google awọsanma

Ọkan ninu awọn ẹya ti idije naa ni data ariwo pupọ (wo aworan akọle, ya lati @Leigh.plt lati Ọlẹ ODS). Awọn ipele nla ṣe iranlọwọ lati koju eyi, ati lẹhin awọn idanwo lori PC pẹlu 1 GPU, a pinnu lati ṣakoso awọn awoṣe ikẹkọ lori ọpọlọpọ awọn GPUs ninu awọsanma. GoogleCloud ti a lo (ti o dara guide to awọn ipilẹ) nitori awọn ti o tobi asayan ti wa atunto, reasonable owo ati $300 ajeseku. Ninu okanjuwa, Mo paṣẹ fun apẹẹrẹ 4xV100 pẹlu SSD ati pupọ ti Ramu, ati pe iyẹn jẹ aṣiṣe nla kan. Iru ẹrọ bẹ njẹ owo ni kiakia; o le lọ fọ idanwo laisi opo gigun ti a fihan. Fun awọn idi eto-ẹkọ, o dara julọ lati mu K80 naa. Ṣugbọn iye nla ti Ramu wa ni ọwọ - awọsanma SSD ko ṣe iwunilori pẹlu iṣẹ rẹ, nitorinaa a gbe data data si dev/shm.

Ti iwulo nla julọ ni ajẹkù koodu ti o ni iduro fun lilo awọn GPU pupọ. Ni akọkọ, awoṣe ti ṣẹda lori Sipiyu nipa lilo oluṣakoso ọrọ, gẹgẹ bi 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
  )
})

Lẹhinna awoṣe ti ko ṣe akopọ (eyi ṣe pataki) jẹ daakọ si nọmba ti a fun ti awọn GPU ti o wa, ati lẹhin iyẹn nikan ni o ṣe akopọ:

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

Ilana Ayebaye ti didi gbogbo awọn fẹlẹfẹlẹ ayafi ti o kẹhin, ikẹkọ ipele ti o kẹhin, ṣiṣi silẹ ati atunkọ gbogbo awoṣe fun ọpọlọpọ awọn GPU ko le ṣe imuse.

A ṣe abojuto ikẹkọ laisi lilo. tensorboard, diwọn ara wa si awọn igbasilẹ igbasilẹ ati awọn awoṣe fifipamọ pẹlu awọn orukọ alaye lẹhin akoko kọọkan:

Ipepada

# Шаблон имени файла лога
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. Dipo ipari

Ọ̀pọ̀lọpọ̀ ìṣòro tí a ti kojú kò tíì borí:

  • в keira ko si iṣẹ ti o ṣetan fun wiwa laifọwọyi fun oṣuwọn ẹkọ ti o dara julọ (analogue lr_finder ninu ile -ikawe sare.ai); Pẹlu igbiyanju diẹ, o ṣee ṣe lati gbe awọn imuse ti ẹnikẹta si R, fun apẹẹrẹ, eyi;
  • Bi abajade ti aaye ti tẹlẹ, ko ṣee ṣe lati yan iyara ikẹkọ to pe nigba lilo awọn GPU pupọ;
  • aini awọn ile-iṣẹ nẹtiwọọki neural ti ode oni, paapaa awọn ti o ti kọkọ tẹlẹ lori imagenet;
  • ko si eto eto ọmọ kan ati awọn oṣuwọn ikẹkọ iyasoto (pipalẹ cosine wa ni ibeere wa imuse, o ṣeun skydan).

Awọn nkan iwulo wo ni a kọ lati inu idije yii:

  • Lori ohun elo agbara kekere, o le ṣiṣẹ pẹlu bojumu (ọpọlọpọ igba iwọn Ramu) awọn iwọn data laisi irora. Apo olora tabili fi iranti pamọ nitori iyipada ibi ti awọn tabili, eyiti o yago fun didakọ wọn, ati nigba lilo bi o ti tọ, awọn agbara rẹ nigbagbogbo n ṣafihan iyara ti o ga julọ laarin gbogbo awọn irinṣẹ ti a mọ si awọn ede kikọ. Fifipamọ data ni aaye data gba ọ laaye, ni ọpọlọpọ igba, lati ma ronu rara nipa iwulo lati fun pọ gbogbo dataset sinu Ramu.
  • Awọn iṣẹ ti o lọra ni R le rọpo pẹlu awọn ti o yara ni C ++ nipa lilo package Rcpp. Ti o ba wa ni afikun si lilo RcppThread tabi RcppParallel, a gba awọn imuse ti ọpọlọpọ-asapo agbelebu, nitorina ko si ye lati ṣe afiwe koodu ni ipele R.
  • Package Rcpp le ṣee lo laisi imọ pataki ti C ++, o kere ju ti a beere ni ti ṣe ilana nibi. Awọn faili akọsori fun nọmba awọn ile-ikawe C ti o dara bi xtensor ti o wa lori CRAN, iyẹn ni, awọn amayederun ti n ṣe agbekalẹ fun imuse awọn iṣẹ akanṣe ti o ṣepọ koodu C ++ iṣẹ ṣiṣe giga ti a ti ṣetan sinu R. Irọrun afikun jẹ afihan sintasi ati olutupalẹ koodu C ++ aimi ni RStudio.
  • docopt faye gba o lati ṣiṣe awọn iwe afọwọkọ ti ara ẹni pẹlu awọn paramita. Eyi rọrun fun lilo lori olupin latọna jijin, pẹlu. labẹ docker. Ni RStudio, ko ṣe aibalẹ lati ṣe ọpọlọpọ awọn wakati ti awọn adanwo pẹlu awọn nẹtiwọọki neural ikẹkọ, ati fifi IDE sori olupin funrararẹ kii ṣe idalare nigbagbogbo.
  • Docker ṣe idaniloju gbigbe koodu ati isọdọtun ti awọn abajade laarin awọn olupilẹṣẹ pẹlu awọn ẹya oriṣiriṣi ti OS ati awọn ile-ikawe, ati irọrun ti ipaniyan lori awọn olupin. O le ṣe ifilọlẹ gbogbo opo gigun ti epo ikẹkọ pẹlu aṣẹ kan.
  • Google Cloud jẹ ọna ore-isuna lati ṣe idanwo lori ohun elo gbowolori, ṣugbọn o nilo lati yan awọn atunto ni pẹkipẹki.
  • Wiwọn iyara ti awọn ajẹkù koodu kọọkan wulo pupọ, paapaa nigba apapọ R ati C ++, ati pẹlu package ibujoko - tun rọrun pupọ.

Lapapọ iriri yii jẹ ere pupọ ati pe a tẹsiwaju lati ṣiṣẹ lati yanju diẹ ninu awọn ọran ti o dide.

orisun: www.habr.com

Fi ọrọìwòye kun