త్వరిత డ్రా డూడుల్ గుర్తింపు: R, C++ మరియు న్యూరల్ నెట్‌వర్క్‌లతో స్నేహం చేయడం ఎలా

త్వరిత డ్రా డూడుల్ గుర్తింపు: R, C++ మరియు న్యూరల్ నెట్‌వర్క్‌లతో స్నేహం చేయడం ఎలా

హే హబ్ర్!

చివరి పతనం, కాగ్లే చేతితో గీసిన చిత్రాలను వర్గీకరించడానికి ఒక పోటీని నిర్వహించింది, క్విక్ డ్రా డూడుల్ రికగ్నిషన్, ఇందులో ఇతరులతో పాటు, R-శాస్త్రవేత్తల బృందం పాల్గొంది: ఆర్టెమ్ క్లెవ్ట్సోవా, ఫిలిప్పా మేనేజర్ и ఆండ్రీ ఓగుర్ట్సోవ్. మేము పోటీని వివరంగా వివరించము; ఇది ఇప్పటికే జరిగింది ఇటీవలి ప్రచురణ.

ఈసారి అది పతక వ్యవసాయంతో పని చేయలేదు, కానీ చాలా విలువైన అనుభవం సంపాదించబడింది, కాబట్టి నేను కాగ్లేలో మరియు రోజువారీ పనిలో చాలా ఆసక్తికరమైన మరియు ఉపయోగకరమైన విషయాల గురించి సమాజానికి చెప్పాలనుకుంటున్నాను. చర్చించిన అంశాలలో: కష్టమైన జీవితం లేకుండా OpenCV, JSON పార్సింగ్ (ఈ ఉదాహరణలు C++ కోడ్‌ని స్క్రిప్ట్‌లు లేదా ప్యాకేజీలలోకి R ఉపయోగించి ఏకీకరణను పరిశీలిస్తాయి Rcpp), స్క్రిప్ట్‌ల పారామిటరైజేషన్ మరియు తుది పరిష్కారం యొక్క డాకరైజేషన్. అమలు చేయడానికి అనువైన రూపంలో సందేశం నుండి మొత్తం కోడ్ అందుబాటులో ఉంది రిపోజిటరీలు.

విషయ సూచిక:

  1. CSV నుండి MonetDBలోకి డేటాను సమర్థవంతంగా లోడ్ చేయండి
  2. బ్యాచ్‌లను సిద్ధం చేస్తోంది
  3. డేటాబేస్ నుండి బ్యాచ్‌లను అన్‌లోడ్ చేయడానికి ఇటరేటర్‌లు
  4. మోడల్ ఆర్కిటెక్చర్‌ను ఎంచుకోవడం
  5. స్క్రిప్ట్ పారామిటరైజేషన్
  6. స్క్రిప్ట్‌ల డాకరైజేషన్
  7. Google క్లౌడ్‌లో బహుళ GPUలను ఉపయోగించడం
  8. ముగింపుకు బదులుగా

1. MonetDB డేటాబేస్‌లోకి CSV నుండి డేటాను సమర్థవంతంగా లోడ్ చేయండి

ఈ పోటీలోని డేటా రెడీమేడ్ ఇమేజ్‌ల రూపంలో కాకుండా, పాయింట్ కోఆర్డినేట్‌లతో JSONలను కలిగి ఉన్న 340 CSV ఫైల్‌ల (ప్రతి తరగతికి ఒక ఫైల్) రూపంలో అందించబడుతుంది. ఈ పాయింట్‌లను లైన్‌లతో కనెక్ట్ చేయడం ద్వారా, మేము 256x256 పిక్సెల్‌లను కొలిచే తుది చిత్రాన్ని పొందుతాము. ప్రతి రికార్డ్‌కు డేటాసెట్‌ను సేకరించిన సమయంలో ఉపయోగించిన వర్గీకరణదారు ద్వారా చిత్రాన్ని సరిగ్గా గుర్తించారో లేదో సూచించే లేబుల్ ఉంది, చిత్రం యొక్క రచయిత నివాస దేశం యొక్క రెండు-అక్షరాల కోడ్, ప్రత్యేక ఐడెంటిఫైయర్, టైమ్‌స్టాంప్ మరియు ఫైల్ పేరుకు సరిపోలే తరగతి పేరు. అసలైన డేటా యొక్క సరళీకృత సంస్కరణ ఆర్కైవ్‌లో 7.4 GB మరియు అన్‌ప్యాక్ చేసిన తర్వాత సుమారు 20 GB బరువు ఉంటుంది, అన్‌ప్యాక్ చేసిన తర్వాత పూర్తి డేటా 240 GB తీసుకుంటుంది. నిర్వాహకులు రెండు వెర్షన్‌లు ఒకే డ్రాయింగ్‌లను పునరుత్పత్తి చేశారని నిర్ధారించారు, అంటే పూర్తి వెర్షన్ అనవసరంగా ఉంది. ఏది ఏమైనప్పటికీ, 50 మిలియన్ చిత్రాలను గ్రాఫిక్ ఫైల్‌లలో లేదా శ్రేణుల రూపంలో నిల్వ చేయడం వెంటనే లాభదాయకం కాదని భావించబడింది మరియు మేము ఆర్కైవ్ నుండి అన్ని CSV ఫైల్‌లను విలీనం చేయాలని నిర్ణయించుకున్నాము train_simplified.zip ప్రతి బ్యాచ్‌కి అవసరమైన సైజు "ఆన్ ది ఫ్లై" యొక్క తదుపరి తరం చిత్రాలతో డేటాబేస్‌లోకి.

బాగా నిరూపితమైన వ్యవస్థ DBMSగా ఎంపిక చేయబడింది మోనెట్ డిబి, అంటే ప్యాకేజీగా R కోసం అమలు MonetDBLite. ప్యాకేజీ డేటాబేస్ సర్వర్ యొక్క పొందుపరిచిన సంస్కరణను కలిగి ఉంటుంది మరియు R సెషన్ నుండి నేరుగా సర్వర్‌ను ఎంచుకొని దానితో పని చేయడానికి మిమ్మల్ని అనుమతిస్తుంది. డేటాబేస్ను సృష్టించడం మరియు దానికి కనెక్ట్ చేయడం ఒక ఆదేశంతో నిర్వహించబడుతుంది:

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

మేము రెండు పట్టికలను సృష్టించాలి: ఒకటి మొత్తం డేటా కోసం, మరొకటి డౌన్‌లోడ్ చేసిన ఫైల్‌ల గురించి సేవా సమాచారం కోసం (ఏదైనా తప్పు జరిగితే మరియు అనేక ఫైల్‌లను డౌన్‌లోడ్ చేసిన తర్వాత ప్రక్రియను పునఃప్రారంభించవలసి వస్తే ఉపయోగకరంగా ఉంటుంది):

పట్టికలు సృష్టిస్తోంది

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

డేటాబేస్‌లోకి డేటాను లోడ్ చేయడానికి వేగవంతమైన మార్గం SQL - కమాండ్‌ని ఉపయోగించి నేరుగా CSV ఫైల్‌లను కాపీ చేయడం COPY OFFSET 2 INTO tablename FROM path USING DELIMITERS ',','n','"' NULL AS '' BEST EFFORTపేరు tablename - పట్టిక పేరు మరియు path - ఫైల్‌కి మార్గం. ఆర్కైవ్‌తో పని చేస్తున్నప్పుడు, అంతర్నిర్మిత అమలు కనుగొనబడింది unzip ఆర్కైవ్ నుండి అనేక ఫైల్‌లతో R సరిగ్గా పని చేయదు, కాబట్టి మేము సిస్టమ్‌ను ఉపయోగించాము unzip (పరామితిని ఉపయోగించి getOption("unzip")).

డేటాబేస్కు వ్రాయడానికి ఫంక్షన్

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

మీరు దానిని డేటాబేస్కు వ్రాసే ముందు పట్టికను మార్చవలసి వస్తే, వాదనలో పాస్ చేస్తే సరిపోతుంది preprocess డేటాను మార్చే ఫంక్షన్.

డేటాబేస్‌లోకి డేటాను వరుసగా లోడ్ చేయడానికి కోడ్:

డేటాబేస్కు డేటా రాయడం

# Список файлов для записи
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

ఉపయోగించిన డ్రైవ్ యొక్క వేగ లక్షణాలపై ఆధారపడి డేటా లోడింగ్ సమయం మారవచ్చు. మా విషయంలో, ఒక SSD లోపల లేదా ఫ్లాష్ డ్రైవ్ (సోర్స్ ఫైల్) నుండి SSD (DB)కి చదవడం మరియు వ్రాయడం 10 నిమిషాల కంటే తక్కువ సమయం పడుతుంది.

పూర్ణాంకం క్లాస్ లేబుల్ మరియు ఇండెక్స్ కాలమ్‌తో నిలువు వరుసను సృష్టించడానికి మరికొన్ని సెకన్లు పడుతుంది (ORDERED INDEX) బ్యాచ్‌లను సృష్టించేటప్పుడు పరిశీలనలు నమూనా చేయబడే లైన్ సంఖ్యలతో:

అదనపు నిలువు వరుసలు మరియు సూచికను సృష్టిస్తోంది

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

ఫ్లైలో బ్యాచ్‌ను సృష్టించే సమస్యను పరిష్కరించడానికి, మేము టేబుల్ నుండి యాదృచ్ఛిక వరుసలను సంగ్రహించే గరిష్ట వేగాన్ని సాధించాల్సిన అవసరం ఉంది doodles. దీని కోసం మేము 3 ఉపాయాలు ఉపయోగించాము. మొదటిది అబ్జర్వేషన్ IDని నిల్వ చేసే రకం డైమెన్షియాలిటీని తగ్గించడం. అసలు డేటా సెట్‌లో, IDని నిల్వ చేయడానికి అవసరమైన రకం bigint, కానీ పరిశీలనల సంఖ్య వాటి ఐడెంటిఫైయర్‌లను, ఆర్డినల్ సంఖ్యకు సమానంగా, రకానికి సరిపోయేలా చేస్తుంది int. ఈ సందర్భంలో శోధన చాలా వేగంగా ఉంటుంది. రెండవ ఉపాయం ఉపయోగించడం ORDERED INDEX — అందుబాటులో ఉన్న అన్నింటి ద్వారా మేము అనుభవపూర్వకంగా ఈ నిర్ణయానికి వచ్చాము ఎంపికలు. మూడవది పారామీటరైజ్డ్ ప్రశ్నలను ఉపయోగించడం. పద్ధతి యొక్క సారాంశం ఆదేశాన్ని ఒకసారి అమలు చేయడం PREPARE ఒకే రకమైన ప్రశ్నల సమూహాన్ని సృష్టించేటప్పుడు సిద్ధం చేసిన వ్యక్తీకరణ యొక్క తదుపరి ఉపయోగంతో, కానీ నిజానికి ఒక సాధారణ దానితో పోల్చితే ప్రయోజనం ఉంది SELECT గణాంక లోపం పరిధిలో ఉన్నట్లు తేలింది.

డేటాను అప్‌లోడ్ చేసే ప్రక్రియ 450 MB కంటే ఎక్కువ RAMని వినియోగించదు. అంటే, వివరించిన విధానం కొన్ని సింగిల్-బోర్డ్ పరికరాలతో సహా దాదాపు ఏదైనా బడ్జెట్ హార్డ్‌వేర్‌లో పదుల గిగాబైట్ల బరువున్న డేటాసెట్‌లను తరలించడానికి మిమ్మల్ని అనుమతిస్తుంది, ఇది చాలా బాగుంది.

(యాదృచ్ఛిక) డేటాను తిరిగి పొందే వేగాన్ని కొలవడం మరియు వివిధ పరిమాణాల బ్యాచ్‌లను నమూనా చేసేటప్పుడు స్కేలింగ్‌ను అంచనా వేయడం మాత్రమే మిగిలి ఉంది:

డేటాబేస్ బెంచ్‌మార్క్

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)

త్వరిత డ్రా డూడుల్ గుర్తింపు: R, C++ మరియు న్యూరల్ నెట్‌వర్క్‌లతో స్నేహం చేయడం ఎలా

2. బ్యాచ్‌లను సిద్ధం చేస్తోంది

మొత్తం బ్యాచ్ తయారీ ప్రక్రియ క్రింది దశలను కలిగి ఉంటుంది:

  1. పాయింట్ల కోఆర్డినేట్‌లతో స్ట్రింగ్‌ల వెక్టర్‌లను కలిగి ఉన్న అనేక JSONలను అన్వయించడం.
  2. అవసరమైన పరిమాణంలో (ఉదాహరణకు, 256×256 లేదా 128×128) చిత్రంపై పాయింట్ల కోఆర్డినేట్‌ల ఆధారంగా రంగుల గీతలను గీయడం.
  3. ఫలిత చిత్రాలను టెన్సర్‌గా మారుస్తోంది.

పైథాన్ కెర్నల్స్ మధ్య పోటీలో భాగంగా, సమస్య ప్రాథమికంగా ఉపయోగించి పరిష్కరించబడింది OpenCV. R లో సరళమైన మరియు అత్యంత స్పష్టమైన అనలాగ్‌లలో ఒకటి ఇలా ఉంటుంది:

R లో JSON నుండి టెన్సర్ మార్పిడిని అమలు చేస్తోంది

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

డ్రాయింగ్ ప్రామాణిక R సాధనాలను ఉపయోగించి నిర్వహించబడుతుంది మరియు RAMలో నిల్వ చేయబడిన తాత్కాలిక PNGకి సేవ్ చేయబడుతుంది (Linuxలో, తాత్కాలిక R డైరెక్టరీలు డైరెక్టరీలో ఉన్నాయి /tmp, RAMలో మౌంట్ చేయబడింది). ఈ ఫైల్ 0 నుండి 1 వరకు ఉన్న సంఖ్యలతో త్రిమితీయ శ్రేణిగా చదవబడుతుంది. ఇది చాలా ముఖ్యమైనది ఎందుకంటే మరింత సాంప్రదాయ BMP హెక్స్ కలర్ కోడ్‌లతో ముడి శ్రేణిలో చదవబడుతుంది.

ఫలితాన్ని పరీక్షిద్దాం:

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

త్వరిత డ్రా డూడుల్ గుర్తింపు: R, C++ మరియు న్యూరల్ నెట్‌వర్క్‌లతో స్నేహం చేయడం ఎలా

బ్యాచ్ ఈ క్రింది విధంగా ఏర్పడుతుంది:

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

పెద్ద బ్యాచ్‌ల ఏర్పాటుకు అసభ్యకరంగా ఎక్కువ సమయం పడుతుంది కాబట్టి, ఈ అమలు మాకు అనుకూలంగా అనిపించింది మరియు శక్తివంతమైన లైబ్రరీని ఉపయోగించడం ద్వారా మా సహోద్యోగుల అనుభవాన్ని సద్వినియోగం చేసుకోవాలని మేము నిర్ణయించుకున్నాము. OpenCV. ఆ సమయంలో R కోసం రెడీమేడ్ ప్యాకేజీ లేదు (ఇప్పుడు ఏదీ లేదు), కాబట్టి అవసరమైన కార్యాచరణ యొక్క కనీస అమలు C++లో R కోడ్‌లో ఏకీకరణతో వ్రాయబడింది Rcpp.

సమస్యను పరిష్కరించడానికి, క్రింది ప్యాకేజీలు మరియు లైబ్రరీలు ఉపయోగించబడ్డాయి:

  1. OpenCV చిత్రాలు మరియు డ్రాయింగ్ లైన్లతో పని చేయడానికి. ముందుగా ఇన్‌స్టాల్ చేయబడిన సిస్టమ్ లైబ్రరీలు మరియు హెడర్ ఫైల్‌లు, అలాగే డైనమిక్ లింక్‌లను ఉపయోగించారు.

  2. xtensor మల్టీడైమెన్షనల్ శ్రేణులు మరియు టెన్సర్‌లతో పని చేయడం కోసం. మేము అదే పేరుతో ఉన్న R ప్యాకేజీలో చేర్చబడిన హెడర్ ఫైల్‌లను ఉపయోగించాము. లైబ్రరీ మీరు రో మేజర్ మరియు కాలమ్ మేజర్ ఆర్డర్‌లో బహుమితీయ శ్రేణులతో పని చేయడానికి మిమ్మల్ని అనుమతిస్తుంది.

  3. ndjson JSONని అన్వయించడం కోసం. ఈ లైబ్రరీలో ఉపయోగించబడుతుంది xtensor అది ప్రాజెక్ట్‌లో ఉన్నట్లయితే స్వయంచాలకంగా.

  4. RcppThread JSON నుండి వెక్టర్ యొక్క బహుళ-థ్రెడ్ ప్రాసెసింగ్‌ను నిర్వహించడం కోసం. ఈ ప్యాకేజీ అందించిన హెడర్ ఫైల్‌లను ఉపయోగించారు. మరింత ప్రజాదరణ నుండి Rcpp సమాంతర ప్యాకేజీ, ఇతర విషయాలతోపాటు, అంతర్నిర్మిత లూప్ అంతరాయ యంత్రాంగాన్ని కలిగి ఉంది.

ఇది గమనించదగ్గ విలువ xtensor దైవానుగ్రహంగా మారింది: ఇది విస్తృతమైన కార్యాచరణ మరియు అధిక పనితీరును కలిగి ఉండటంతో పాటు, దాని డెవలపర్లు చాలా ప్రతిస్పందించారు మరియు ప్రశ్నలకు వెంటనే మరియు వివరంగా సమాధానమిచ్చారు. వారి సహాయంతో, ఓపెన్‌సివి మాత్రికలను ఎక్స్‌టెన్సర్ టెన్సర్‌లుగా మార్చడం, అలాగే 3-డైమెన్షనల్ ఇమేజ్ టెన్సర్‌లను సరైన డైమెన్షన్ (బ్యాచ్ కూడా) యొక్క 4-డైమెన్షనల్ టెన్సర్‌గా కలపడం సాధ్యమైంది.

Rcpp, xtensor మరియు 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

సిస్టమ్ ఫైల్‌లను ఉపయోగించే ఫైల్‌లను కంపైల్ చేయడానికి మరియు సిస్టమ్‌లో ఇన్‌స్టాల్ చేయబడిన లైబ్రరీలతో డైనమిక్ లింక్ చేయడానికి, మేము ప్యాకేజీలో అమలు చేయబడిన ప్లగిన్ మెకానిజంను ఉపయోగించాము. Rcpp. మార్గాలు మరియు ఫ్లాగ్‌లను స్వయంచాలకంగా కనుగొనడానికి, మేము ప్రముఖ Linux యుటిలిటీని ఉపయోగించాము pkg-config.

OpenCV లైబ్రరీని ఉపయోగించడం కోసం Rcpp ప్లగ్ఇన్ అమలు

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

ప్లగ్ఇన్ యొక్క ఆపరేషన్ ఫలితంగా, సంకలన ప్రక్రియలో క్రింది విలువలు భర్తీ చేయబడతాయి:

Rcpp:::.plugins$opencv()$env

# $PKG_CXXFLAGS
# [1] "-I/usr/include/opencv"
#
# $PKG_LIBS
# [1] "-lopencv_shape -lopencv_stitching -lopencv_superres -lopencv_videostab -lopencv_aruco -lopencv_bgsegm -lopencv_bioinspired -lopencv_ccalib -lopencv_datasets -lopencv_dpm -lopencv_face -lopencv_freetype -lopencv_fuzzy -lopencv_hdf -lopencv_line_descriptor -lopencv_optflow -lopencv_video -lopencv_plot -lopencv_reg -lopencv_saliency -lopencv_stereo -lopencv_structured_light -lopencv_phase_unwrapping -lopencv_rgbd -lopencv_viz -lopencv_surface_matching -lopencv_text -lopencv_ximgproc -lopencv_calib3d -lopencv_features2d -lopencv_flann -lopencv_xobjdetect -lopencv_objdetect -lopencv_ml -lopencv_xphoto -lopencv_highgui -lopencv_videoio -lopencv_imgcodecs -lopencv_photo -lopencv_imgproc -lopencv_core"

JSONని అన్వయించడం మరియు మోడల్‌కు ప్రసారం చేయడానికి బ్యాచ్‌ని రూపొందించడం కోసం అమలు కోడ్ స్పాయిలర్ క్రింద ఇవ్వబడింది. ముందుగా, హెడర్ ఫైల్‌ల కోసం శోధించడానికి స్థానిక ప్రాజెక్ట్ డైరెక్టరీని జోడించండి (ndjson కోసం అవసరం):

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

C++లో JSON నుండి టెన్సర్ మార్పిడికి అమలు

// [[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;
}

ఈ కోడ్ ఫైల్‌లో ఉంచాలి src/cv_xt.cpp మరియు ఆదేశంతో కంపైల్ చేయండి Rcpp::sourceCpp(file = "src/cv_xt.cpp", env = .GlobalEnv); పని కోసం కూడా అవసరం nlohmann/json.hpp నుండి రెపోజిటోరియా. కోడ్ అనేక విధులుగా విభజించబడింది:

  • to_xt — ఇమేజ్ మ్యాట్రిక్స్‌ని మార్చడానికి టెంప్లేట్ చేసిన ఫంక్షన్ (cv::Mat) ఒక టెన్సర్‌కి xt::xtensor;

  • parse_json - ఫంక్షన్ JSON స్ట్రింగ్‌ను అన్వయిస్తుంది, పాయింట్ల కోఆర్డినేట్‌లను సంగ్రహిస్తుంది, వాటిని వెక్టర్‌లోకి ప్యాక్ చేస్తుంది;

  • ocv_draw_lines - పాయింట్ల ఫలిత వెక్టర్ నుండి, బహుళ-రంగు పంక్తులను గీస్తుంది;

  • process — పై ఫంక్షన్‌లను మిళితం చేస్తుంది మరియు ఫలిత చిత్రాన్ని స్కేల్ చేసే సామర్థ్యాన్ని కూడా జోడిస్తుంది;

  • cpp_process_json_str - ఫంక్షన్ మీద రేపర్ process, ఇది ఫలితాన్ని R-ఆబ్జెక్ట్ (బహుళ డైమెన్షనల్ అర్రే)కి ఎగుమతి చేస్తుంది;

  • cpp_process_json_vector - ఫంక్షన్ మీద రేపర్ cpp_process_json_str, ఇది స్ట్రింగ్ వెక్టర్‌ను మల్టీ-థ్రెడ్ మోడ్‌లో ప్రాసెస్ చేయడానికి మిమ్మల్ని అనుమతిస్తుంది.

బహుళ-రంగు పంక్తులను గీయడానికి, HSV రంగు నమూనా ఉపయోగించబడింది, తర్వాత RGBకి మార్చబడింది. ఫలితాన్ని పరీక్షిద్దాం:

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

త్వరిత డ్రా డూడుల్ గుర్తింపు: R, C++ మరియు న్యూరల్ నెట్‌వర్క్‌లతో స్నేహం చేయడం ఎలా
R మరియు 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") 

త్వరిత డ్రా డూడుల్ గుర్తింపు: R, C++ మరియు న్యూరల్ నెట్‌వర్క్‌లతో స్నేహం చేయడం ఎలా

మీరు చూడగలిగినట్లుగా, వేగం పెరుగుదల చాలా ముఖ్యమైనదిగా మారింది మరియు R కోడ్‌ను సమాంతరంగా చేయడం ద్వారా C++ కోడ్‌ని పొందడం సాధ్యం కాదు.

3. డేటాబేస్ నుండి బ్యాచ్‌లను అన్‌లోడ్ చేయడానికి ఇటరేటర్లు

RAMలో సరిపోయే డేటాను ప్రాసెస్ చేయడంలో R మంచి అర్హతను కలిగి ఉంది, అయితే పైథాన్ పునరుక్తి డేటా ప్రాసెసింగ్ ద్వారా మరింత వర్ణించబడుతుంది, ఇది మిమ్మల్ని సులభంగా మరియు సహజంగా అవుట్-కోర్ లెక్కలను (బాహ్య మెమరీని ఉపయోగించి గణనలు) అమలు చేయడానికి అనుమతిస్తుంది. వివరించిన సమస్య సందర్భంలో మాకు ఒక క్లాసిక్ మరియు సంబంధిత ఉదాహరణ అనేది గ్రేడియంట్ డీసెంట్ పద్ధతి ద్వారా శిక్షణ పొందిన డీప్ న్యూరల్ నెట్‌వర్క్‌లు, ఇది ప్రతి దశలో గ్రేడియంట్ యొక్క ఉజ్జాయింపుతో పరిశీలనల యొక్క చిన్న భాగాన్ని లేదా మినీ-బ్యాచ్‌ని ఉపయోగిస్తుంది.

పైథాన్‌లో వ్రాయబడిన డీప్ లెర్నింగ్ ఫ్రేమ్‌వర్క్‌లు డేటా ఆధారంగా ఇటరేటర్‌లను అమలు చేసే ప్రత్యేక తరగతులను కలిగి ఉంటాయి: పట్టికలు, ఫోల్డర్‌లలోని చిత్రాలు, బైనరీ ఫార్మాట్‌లు మొదలైనవి. మీరు రెడీమేడ్ ఎంపికలను ఉపయోగించవచ్చు లేదా నిర్దిష్ట పనుల కోసం మీ స్వంతంగా వ్రాయవచ్చు. R లో మనం పైథాన్ లైబ్రరీ యొక్క అన్ని ఫీచర్ల ప్రయోజనాన్ని పొందవచ్చు keras అదే పేరుతో ఉన్న ప్యాకేజీని ఉపయోగించి దాని వివిధ బ్యాకెండ్‌లతో, ఇది ప్యాకేజీ పైన పని చేస్తుంది రెటిక్యులేట్. రెండోది ప్రత్యేక సుదీర్ఘ కథనానికి అర్హమైనది; ఇది R నుండి పైథాన్ కోడ్‌ని అమలు చేయడానికి మిమ్మల్ని అనుమతించడమే కాకుండా, R మరియు పైథాన్ సెషన్‌ల మధ్య వస్తువులను బదిలీ చేయడానికి మిమ్మల్ని అనుమతిస్తుంది, అవసరమైన అన్ని రకాల మార్పిడులను స్వయంచాలకంగా నిర్వహిస్తుంది.

MonetDBLiteని ఉపయోగించడం ద్వారా మొత్తం డేటాను RAMలో నిల్వ చేయవలసిన అవసరాన్ని మేము వదిలించుకున్నాము, అన్ని "న్యూరల్ నెట్‌వర్క్" పనిని పైథాన్‌లోని అసలు కోడ్ ద్వారా నిర్వహించబడుతుంది, మేము డేటాపై ఇటరేటర్‌ను వ్రాయవలసి ఉంటుంది, ఎందుకంటే సిద్ధంగా ఏమీ లేదు. R లేదా పైథాన్‌లో అటువంటి పరిస్థితికి. దీనికి తప్పనిసరిగా రెండు అవసరాలు మాత్రమే ఉన్నాయి: ఇది అంతులేని లూప్‌లో బ్యాచ్‌లను తిరిగి ఇవ్వాలి మరియు పునరావృతాల మధ్య దాని స్థితిని సేవ్ చేయాలి (R లో రెండోది మూసివేతలను ఉపయోగించి సరళమైన మార్గంలో అమలు చేయబడుతుంది). ఇంతకు ముందు, ఇటరేటర్ లోపల R శ్రేణులను నంపీ శ్రేణులుగా స్పష్టంగా మార్చడం అవసరం, కానీ ప్యాకేజీ యొక్క ప్రస్తుత వెర్షన్ keras స్వయంగా చేస్తుంది.

శిక్షణ మరియు ధ్రువీకరణ డేటా కోసం ఇటరేటర్ ఈ క్రింది విధంగా మారింది:

శిక్షణ మరియు ధ్రువీకరణ డేటా కోసం ఇటరేటర్

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

ఫంక్షన్ డేటాబేస్‌కు కనెక్షన్‌తో వేరియబుల్‌ను ఇన్‌పుట్‌గా తీసుకుంటుంది, ఉపయోగించిన పంక్తుల సంఖ్యలు, తరగతుల సంఖ్య, బ్యాచ్ పరిమాణం, స్కేల్ (scale = 1 256x256 పిక్సెల్‌ల రెండరింగ్ ఇమేజ్‌లకు అనుగుణంగా ఉంటుంది, scale = 0.5 — 128x128 పిక్సెల్‌లు), రంగు సూచిక (color = FALSE ఉపయోగించినప్పుడు గ్రేస్కేల్‌లో రెండరింగ్‌ను నిర్దేశిస్తుంది color = TRUE ప్రతి స్ట్రోక్ కొత్త రంగులో డ్రా చేయబడింది) మరియు ఇమేజ్‌నెట్‌లో ముందుగా శిక్షణ పొందిన నెట్‌వర్క్‌ల కోసం ప్రీప్రాసెసింగ్ సూచిక. పిక్సెల్ విలువలను విరామం [0, 1] నుండి విరామం [-1, 1] వరకు స్కేల్ చేయడానికి రెండోది అవసరం, ఇది సరఫరా చేసిన వారికి శిక్షణ ఇచ్చేటప్పుడు ఉపయోగించబడింది. keras నమూనాలు.

బాహ్య ఫంక్షన్ ఆర్గ్యుమెంట్ టైప్ చెకింగ్, టేబుల్‌ని కలిగి ఉంటుంది data.table నుండి యాదృచ్ఛికంగా మిశ్రమ లైన్ సంఖ్యలతో samples_index మరియు బ్యాచ్ సంఖ్యలు, కౌంటర్ మరియు గరిష్ట బ్యాచ్‌ల సంఖ్య, అలాగే డేటాబేస్ నుండి డేటాను అన్‌లోడ్ చేయడానికి SQL వ్యక్తీకరణ. అదనంగా, మేము లోపల ఫంక్షన్ యొక్క వేగవంతమైన అనలాగ్‌ను నిర్వచించాము keras::to_categorical(). మేము శిక్షణ కోసం దాదాపు మొత్తం డేటాను ఉపయోగించాము, ధృవీకరణ కోసం సగం శాతం వదిలివేసాము, కాబట్టి యుగ పరిమాణం పారామీటర్ ద్వారా పరిమితం చేయబడింది steps_per_epoch పిలిచినప్పుడు keras::fit_generator(), మరియు పరిస్థితి if (i > max_i) ధృవీకరణ ఇటరేటర్ కోసం మాత్రమే పని చేసింది.

అంతర్గత ఫంక్షన్‌లో, తదుపరి బ్యాచ్ కోసం వరుస సూచికలు తిరిగి పొందబడతాయి, బ్యాచ్ కౌంటర్ పెరుగుతున్నప్పుడు డేటాబేస్ నుండి రికార్డ్‌లు అన్‌లోడ్ చేయబడతాయి, JSON పార్సింగ్ (ఫంక్షన్ cpp_process_json_vector(), C++)లో వ్రాయబడింది మరియు చిత్రాలకు అనుగుణంగా శ్రేణులను సృష్టించడం. అప్పుడు క్లాస్ లేబుల్‌లతో వన్-హాట్ వెక్టర్స్ సృష్టించబడతాయి, పిక్సెల్ విలువలు మరియు లేబుల్‌లతో కూడిన శ్రేణులు జాబితాగా మిళితం చేయబడతాయి, ఇది రిటర్న్ విలువ. పనిని వేగవంతం చేయడానికి, మేము పట్టికలలో సూచికల సృష్టిని ఉపయోగించాము data.table మరియు లింక్ ద్వారా సవరణ - ఈ ప్యాకేజీ "చిప్స్" లేకుండా డేటా. టేబుల్ R లో ఏదైనా ముఖ్యమైన డేటాతో సమర్థవంతంగా పని చేయడం ఊహించడం చాలా కష్టం.

కోర్ i5 ల్యాప్‌టాప్‌లో వేగ కొలతల ఫలితాలు క్రింది విధంగా ఉన్నాయి:

ఇటరేటర్ బెంచ్‌మార్క్

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)

త్వరిత డ్రా డూడుల్ గుర్తింపు: R, C++ మరియు న్యూరల్ నెట్‌వర్క్‌లతో స్నేహం చేయడం ఎలా

మీకు తగినంత మొత్తంలో RAM ఉంటే, మీరు అదే RAMకి బదిలీ చేయడం ద్వారా డేటాబేస్ యొక్క ఆపరేషన్‌ను తీవ్రంగా వేగవంతం చేయవచ్చు (మా పనికి 32 GB సరిపోతుంది). Linuxలో, విభజన డిఫాల్ట్‌గా మౌంట్ చేయబడింది /dev/shm, RAM సామర్థ్యంలో సగం వరకు ఆక్రమించడం. మీరు సవరించడం ద్వారా మరిన్నింటిని హైలైట్ చేయవచ్చు /etc/fstabవంటి రికార్డు పొందడానికి tmpfs /dev/shm tmpfs defaults,size=25g 0 0. ఆదేశాన్ని అమలు చేయడం ద్వారా రీబూట్ చేసి, ఫలితాన్ని తనిఖీ చేయాలని నిర్ధారించుకోండి df -h.

పరీక్ష డేటా కోసం ఇటరేటర్ చాలా సరళంగా కనిపిస్తుంది, ఎందుకంటే పరీక్ష డేటాసెట్ పూర్తిగా RAMకి సరిపోతుంది:

పరీక్ష డేటా కోసం ఇటరేటర్

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. మోడల్ ఆర్కిటెక్చర్ ఎంపిక

మొదటి వాస్తుశిల్పం ఉపయోగించబడింది మొబైల్ నెట్ v1, వీటిలో లక్షణాలు చర్చించబడ్డాయి సందేశం. ఇది ప్రామాణికంగా చేర్చబడింది keras మరియు, తదనుగుణంగా, R కోసం అదే పేరుతో ఉన్న ప్యాకేజీలో అందుబాటులో ఉంది. కానీ దానిని సింగిల్-ఛానల్ చిత్రాలతో ఉపయోగించడానికి ప్రయత్నిస్తున్నప్పుడు, ఒక వింత విషయం బయటపడింది: ఇన్‌పుట్ టెన్సర్ ఎల్లప్పుడూ పరిమాణాన్ని కలిగి ఉండాలి (batch, height, width, 3), అంటే, ఛానెల్‌ల సంఖ్య మార్చబడదు. పైథాన్‌లో అలాంటి పరిమితి లేదు, కాబట్టి మేము ఈ ఆర్కిటెక్చర్ యొక్క మా స్వంత అమలును త్వరితంగా వ్రాసాము, అసలు కథనాన్ని అనుసరించి (కేరాస్ వెర్షన్‌లో డ్రాప్ అవుట్ లేకుండా):

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

ఈ విధానం యొక్క ప్రతికూలతలు స్పష్టంగా ఉన్నాయి. నేను చాలా మోడళ్లను పరీక్షించాలనుకుంటున్నాను, కానీ దీనికి విరుద్ధంగా, నేను ప్రతి నిర్మాణాన్ని మాన్యువల్‌గా తిరిగి వ్రాయకూడదనుకుంటున్నాను. ఇమేజ్‌నెట్‌లో ముందుగా శిక్షణ పొందిన మోడల్‌ల బరువులను ఉపయోగించుకునే అవకాశం కూడా మాకు లేకుండా పోయింది. ఎప్పటిలాగే, డాక్యుమెంటేషన్ అధ్యయనం సహాయపడింది. ఫంక్షన్ get_config() సవరించడానికి అనువైన రూపంలో మోడల్ యొక్క వివరణను పొందడానికి మిమ్మల్ని అనుమతిస్తుంది (base_model_conf$layers - ఒక సాధారణ R జాబితా), మరియు ఫంక్షన్ from_config() మోడల్ ఆబ్జెక్ట్‌గా రివర్స్ మార్పిడిని చేస్తుంది:

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)

ఇప్పుడు సరఫరా చేయబడిన వాటిలో దేనినైనా పొందేందుకు యూనివర్సల్ ఫంక్షన్‌ను వ్రాయడం కష్టం కాదు keras ఇమేజ్‌నెట్‌లో శిక్షణ పొందిన బరువులు ఉన్న లేదా లేని మోడల్‌లు:

రెడీమేడ్ ఆర్కిటెక్చర్‌లను లోడ్ చేయడం కోసం ఫంక్షన్

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

సింగిల్-ఛానల్ చిత్రాలను ఉపయోగిస్తున్నప్పుడు, ముందుగా శిక్షణ పొందిన బరువులు ఉపయోగించబడవు. ఇది పరిష్కరించబడుతుంది: ఫంక్షన్ ఉపయోగించి get_weights() మోడల్ బరువులను R శ్రేణుల జాబితా రూపంలో పొందండి, ఈ జాబితా యొక్క మొదటి మూలకం యొక్క పరిమాణాన్ని మార్చండి (ఒక రంగు ఛానెల్‌ని తీసుకోవడం లేదా మూడింటిని సగటున తీసుకోవడం ద్వారా), ఆపై బరువులను ఫంక్షన్‌తో మోడల్‌లోకి తిరిగి లోడ్ చేయండి set_weights(). మేము ఈ కార్యాచరణను ఎప్పుడూ జోడించలేదు, ఎందుకంటే ఈ దశలో రంగు చిత్రాలతో పని చేయడం మరింత ఉత్పాదకత అని ఇప్పటికే స్పష్టమైంది.

మేము మొబైల్‌నెట్ వెర్షన్‌లు 1 మరియు 2, అలాగే resnet34ని ఉపయోగించి చాలా ప్రయోగాలు చేసాము. SE-ResNeXt వంటి మరిన్ని ఆధునిక నిర్మాణాలు ఈ పోటీలో బాగా పనిచేశాయి. దురదృష్టవశాత్తు, మా వద్ద సిద్ధంగా ఉన్న అమలులు లేవు మరియు మేము మా స్వంతంగా వ్రాయలేదు (కానీ మేము ఖచ్చితంగా వ్రాస్తాము).

5. స్క్రిప్ట్‌ల పారామిటరైజేషన్

సౌలభ్యం కోసం, శిక్షణను ప్రారంభించడానికి అన్ని కోడ్‌లు ఒకే స్క్రిప్ట్‌గా రూపొందించబడ్డాయి, ఉపయోగించి పారామితి చేయబడింది docopt క్రింది విధంగా:

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)

ప్యాకేజీ docopt అమలును సూచిస్తుంది http://docopt.org/ R కోసం. దాని సహాయంతో, స్క్రిప్ట్‌లు వంటి సాధారణ ఆదేశాలతో ప్రారంభించబడతాయి Rscript bin/train_nn.R -m resnet50 -c -d /home/andrey/doodle_db లేదా ./bin/train_nn.R -m resnet50 -c -d /home/andrey/doodle_db, ఫైల్ అయితే train_nn.R ఎక్జిక్యూటబుల్ (ఈ ఆదేశం మోడల్‌కు శిక్షణ ఇవ్వడం ప్రారంభిస్తుంది resnet50 128x128 పిక్సెల్‌లను కొలిచే మూడు-రంగు చిత్రాలపై, డేటాబేస్ తప్పనిసరిగా ఫోల్డర్‌లో ఉండాలి /home/andrey/doodle_db) మీరు నేర్చుకునే వేగం, ఆప్టిమైజర్ రకం మరియు ఏవైనా ఇతర అనుకూలీకరించదగిన పారామితులను జాబితాకు జోడించవచ్చు. ప్రచురణను సిద్ధం చేసే ప్రక్రియలో, అది వాస్తుశిల్పం అని తేలింది mobilenet_v2 ప్రస్తుత వెర్షన్ నుండి keras R ఉపయోగంలో కాదు R ప్యాకేజీలో మార్పులను పరిగణలోకి తీసుకోని కారణంగా, వారు దాన్ని సరిచేయడానికి మేము వేచి ఉన్నాము.

ఈ విధానం RStudioలో స్క్రిప్ట్‌ల యొక్క సాంప్రదాయిక లాంచ్‌తో పోలిస్తే విభిన్న నమూనాలతో ప్రయోగాలను గణనీయంగా వేగవంతం చేయడం సాధ్యపడింది (మేము ప్యాకేజీని సాధ్యమైన ప్రత్యామ్నాయంగా గమనించాము tfrons) కానీ ప్రధాన ప్రయోజనం ఏమిటంటే, దీని కోసం RStudioని ఇన్‌స్టాల్ చేయకుండా డాకర్‌లో లేదా సర్వర్‌లో స్క్రిప్ట్‌ల లాంచ్‌ను సులభంగా నిర్వహించగల సామర్థ్యం.

6. స్క్రిప్ట్‌ల డాకరైజేషన్

బృంద సభ్యుల మధ్య శిక్షణ నమూనాల కోసం మరియు క్లౌడ్‌లో వేగవంతమైన విస్తరణ కోసం పర్యావరణం యొక్క పోర్టబిలిటీని నిర్ధారించడానికి మేము డాకర్‌ని ఉపయోగించాము. మీరు ఈ సాధనంతో పరిచయం పొందడం ప్రారంభించవచ్చు, ఇది R ప్రోగ్రామర్‌కు చాలా అసాధారణమైనది ప్రచురణల శ్రేణి లేదా వీడియో కోర్సు.

స్క్రాచ్ నుండి మీ స్వంత చిత్రాలను రూపొందించడానికి మరియు మీ స్వంత చిత్రాలను రూపొందించడానికి ఇతర చిత్రాలను ఉపయోగించడానికి డాకర్ మిమ్మల్ని అనుమతిస్తుంది. అందుబాటులో ఉన్న ఎంపికలను విశ్లేషించేటప్పుడు, NVIDIA, CUDA+cuDNN డ్రైవర్లు మరియు పైథాన్ లైబ్రరీలను ఇన్‌స్టాల్ చేయడం అనేది ఇమేజ్‌లో చాలా పెద్ద భాగం అని మేము నిర్ధారణకు వచ్చాము మరియు మేము అధికారిక చిత్రాన్ని ప్రాతిపదికగా తీసుకోవాలని నిర్ణయించుకున్నాము. tensorflow/tensorflow:1.12.0-gpu, అక్కడ అవసరమైన R ప్యాకేజీలను జోడించడం.

చివరి డాకర్ ఫైల్ ఇలా ఉంది:

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

సౌలభ్యం కోసం, ఉపయోగించిన ప్యాకేజీలు వేరియబుల్స్‌లో ఉంచబడ్డాయి; అసెంబ్లీ సమయంలో చాలా వరకు వ్రాసిన స్క్రిప్ట్‌లు కంటైనర్‌ల లోపల కాపీ చేయబడతాయి. మేము కమాండ్ షెల్‌ను కూడా మార్చాము /bin/bash కంటెంట్ సౌలభ్యం కోసం /etc/os-release. ఇది కోడ్‌లో OS సంస్కరణను పేర్కొనవలసిన అవసరాన్ని నివారించింది.

అదనంగా, వివిధ ఆదేశాలతో కంటైనర్‌ను ప్రారంభించేందుకు మిమ్మల్ని అనుమతించే చిన్న బాష్ స్క్రిప్ట్ వ్రాయబడింది. ఉదాహరణకు, ఇవి మునుపు కంటైనర్‌లో ఉంచబడిన న్యూరల్ నెట్‌వర్క్‌లకు శిక్షణ ఇవ్వడానికి స్క్రిప్ట్‌లు కావచ్చు లేదా కంటైనర్ యొక్క ఆపరేషన్‌ను డీబగ్గింగ్ చేయడానికి మరియు పర్యవేక్షించడానికి కమాండ్ షెల్ కావచ్చు:

కంటైనర్‌ను ప్రారంభించేందుకు స్క్రిప్ట్

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

ఈ బాష్ స్క్రిప్ట్‌ని పారామీటర్‌లు లేకుండా రన్ చేస్తే, స్క్రిప్ట్ కంటైనర్ లోపల పిలువబడుతుంది train_nn.R డిఫాల్ట్ విలువలతో; మొదటి స్థాన వాదన "బాష్" అయితే, కంటైనర్ కమాండ్ షెల్‌తో ఇంటరాక్టివ్‌గా ప్రారంభమవుతుంది. అన్ని ఇతర సందర్భాలలో, స్థాన వాదనల విలువలు ప్రత్యామ్నాయంగా ఉంటాయి: CMD="Rscript /app/train_nn.R $@".

సోర్స్ డేటా మరియు డేటాబేస్ ఉన్న డైరెక్టరీలు, అలాగే శిక్షణ పొందిన మోడళ్లను సేవ్ చేసే డైరెక్టరీ, హోస్ట్ సిస్టమ్ నుండి కంటైనర్‌లో మౌంట్ చేయబడిందని గమనించాలి, ఇది అనవసరమైన అవకతవకలు లేకుండా స్క్రిప్ట్‌ల ఫలితాలను యాక్సెస్ చేయడానికి మిమ్మల్ని అనుమతిస్తుంది.

7. Google క్లౌడ్‌లో బహుళ GPUలను ఉపయోగించడం

పోటీ యొక్క లక్షణాలలో ఒకటి చాలా ధ్వనించే డేటా (శీర్షిక చిత్రాన్ని చూడండి, ODS స్లాక్ నుండి @Leigh.plt నుండి తీసుకోబడింది). పెద్ద బ్యాచ్‌లు దీనిని ఎదుర్కోవడంలో సహాయపడతాయి మరియు 1 GPUతో PCలో ప్రయోగాలు చేసిన తర్వాత, మేము క్లౌడ్‌లోని అనేక GPUలలో శిక్షణ నమూనాలను ప్రావీణ్యం చేయాలని నిర్ణయించుకున్నాము. ఉపయోగించబడిన GoogleCloud (ప్రాథమిక అంశాలకు మంచి మార్గదర్శి) అందుబాటులో ఉన్న కాన్ఫిగరేషన్‌ల యొక్క పెద్ద ఎంపిక, సహేతుకమైన ధరలు మరియు $300 బోనస్ కారణంగా. అత్యాశతో, నేను SSD మరియు టన్ను RAMతో 4xV100 ఉదాహరణను ఆర్డర్ చేసాను మరియు అది పెద్ద తప్పు. అటువంటి యంత్రం త్వరగా డబ్బును తింటుంది; మీరు నిరూపితమైన పైప్‌లైన్ లేకుండా ప్రయోగాలు చేయవచ్చు. విద్యా ప్రయోజనాల కోసం, K80 తీసుకోవడం మంచిది. కానీ పెద్ద మొత్తంలో RAM ఉపయోగపడింది - క్లౌడ్ SSD దాని పనితీరుతో ఆకట్టుకోలేదు, కాబట్టి డేటాబేస్ బదిలీ చేయబడింది dev/shm.

బహుళ GPUలను ఉపయోగించడం కోసం బాధ్యత వహించే కోడ్ భాగం గొప్ప ఆసక్తిని కలిగిస్తుంది. మొదట, పైథాన్‌లో మాదిరిగానే కాంటెక్స్ట్ మేనేజర్‌ని ఉపయోగించి CPUలో మోడల్ సృష్టించబడుతుంది:

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

అప్పుడు కంపైల్ చేయని (ఇది ముఖ్యమైనది) మోడల్ అందుబాటులో ఉన్న GPUల సంఖ్యకు కాపీ చేయబడుతుంది మరియు ఆ తర్వాత మాత్రమే ఇది కంపైల్ చేయబడుతుంది:

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

చివరిది తప్ప అన్ని లేయర్‌లను స్తంభింపజేయడం, చివరి లేయర్‌కు శిక్షణ ఇవ్వడం, అనేక GPUల కోసం మొత్తం మోడల్‌ను అన్‌ఫ్రీజ్ చేయడం మరియు మళ్లీ శిక్షణ ఇవ్వడం వంటి క్లాసిక్ టెక్నిక్ అమలు చేయడం సాధ్యం కాలేదు.

శిక్షణ ఉపయోగం లేకుండా పర్యవేక్షించబడింది. టెన్సర్‌బోర్డ్, లాగ్‌లను రికార్డ్ చేయడానికి మరియు ప్రతి యుగం తర్వాత ఇన్ఫర్మేటివ్ పేర్లతో మోడల్‌లను సేవ్ చేయడానికి మమ్మల్ని పరిమితం చేసుకోవడం:

కాల్‌బ్యాక్‌లు

# Шаблон имени файла лога
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. ముగింపుకు బదులుగా

మేము ఎదుర్కొన్న అనేక సమస్యలు ఇంకా అధిగమించబడలేదు:

  • в keras సరైన అభ్యాస రేటు (అనలాగ్) కోసం స్వయంచాలకంగా శోధించడానికి రెడీమేడ్ ఫంక్షన్ లేదు lr_finder లైబ్రరీలో fast.ai); కొంత ప్రయత్నంతో, థర్డ్-పార్టీ ఇంప్లిమెంటేషన్‌లను R కి పోర్ట్ చేయడం సాధ్యమవుతుంది, ఉదాహరణకు, ;
  • మునుపటి పాయింట్ యొక్క పర్యవసానంగా, అనేక GPUలను ఉపయోగిస్తున్నప్పుడు సరైన శిక్షణ వేగాన్ని ఎంచుకోవడం సాధ్యం కాదు;
  • ఆధునిక న్యూరల్ నెట్‌వర్క్ ఆర్కిటెక్చర్‌ల కొరత ఉంది, ముఖ్యంగా ఇమేజ్‌నెట్‌లో ముందుగా శిక్షణ పొందినవి;
  • ఎవరూ సైకిల్ విధానం మరియు వివక్షత లేని అభ్యాస రేట్లు (కొసైన్ ఎనియలింగ్ మా అభ్యర్థన మేరకు అమలు, ధన్యవాదాలు స్కీడాన్).

ఈ పోటీ నుండి ఏమి ఉపయోగకరమైన విషయాలు నేర్చుకున్నాయి:

  • సాపేక్షంగా తక్కువ-పవర్ హార్డ్‌వేర్‌లో, మీరు నొప్పి లేకుండా మంచి (ర్యామ్ కంటే చాలా రెట్లు ఎక్కువ) డేటా వాల్యూమ్‌లతో పని చేయవచ్చు. ప్లాస్టిక్ సంచి డేటా. టేబుల్ పట్టికల యొక్క ఇన్-ప్లేస్ సవరణ కారణంగా మెమరీని ఆదా చేస్తుంది, ఇది వాటిని కాపీ చేయడాన్ని నివారిస్తుంది మరియు సరిగ్గా ఉపయోగించినప్పుడు, దాని సామర్థ్యాలు దాదాపు ఎల్లప్పుడూ స్క్రిప్టింగ్ భాషల కోసం మనకు తెలిసిన అన్ని సాధనాలలో అత్యధిక వేగాన్ని ప్రదర్శిస్తాయి. డేటాబేస్‌లో డేటాను సేవ్ చేయడం వలన, అనేక సందర్భాల్లో, మొత్తం డేటాసెట్‌ను RAMలోకి స్క్వీజ్ చేయాల్సిన అవసరం గురించి అస్సలు ఆలోచించకూడదు.
  • ప్యాకేజీని ఉపయోగించి R లోని స్లో ఫంక్షన్‌లను C++లో ఫాస్ట్ వాటితో భర్తీ చేయవచ్చు Rcpp. ఉపయోగించడానికి అదనంగా ఉంటే RcppThread లేదా Rcpp సమాంతర, మేము క్రాస్-ప్లాట్‌ఫారమ్ బహుళ-థ్రెడ్ ఇంప్లిమెంటేషన్‌లను పొందుతాము, కాబట్టి R స్థాయిలో కోడ్‌ను సమాంతరంగా ఉంచాల్సిన అవసరం లేదు.
  • ప్యాకేజీ Rcpp C++ గురించి తీవ్రమైన జ్ఞానం లేకుండా ఉపయోగించవచ్చు, అవసరమైన కనీసము వివరించబడింది ఇక్కడ. వంటి అనేక కూల్ C-లైబ్రరీల కోసం హెడర్ ఫైల్‌లు xtensor CRANలో అందుబాటులో ఉంది, అంటే, రెడీమేడ్ హై-పెర్ఫార్మెన్స్ C++ కోడ్‌ని R లోకి అనుసంధానించే ప్రాజెక్ట్‌ల అమలు కోసం ఒక మౌలిక సదుపాయాలు ఏర్పడుతున్నాయి. అదనపు సౌలభ్యం సింటాక్స్ హైలైటింగ్ మరియు RStudioలో స్టాటిక్ C++ కోడ్ ఎనలైజర్.
  • docopt పారామితులతో స్వీయ-నియంత్రణ స్క్రిప్ట్‌లను అమలు చేయడానికి మిమ్మల్ని అనుమతిస్తుంది. ఇది రిమోట్ సర్వర్‌లో ఉపయోగించడానికి అనుకూలమైనది, incl. డాకర్ కింద. RStudioలో, శిక్షణ నాడీ నెట్‌వర్క్‌లతో అనేక గంటలపాటు ప్రయోగాలు చేయడం అసౌకర్యంగా ఉంటుంది మరియు సర్వర్‌లోనే IDEని ఇన్‌స్టాల్ చేయడం ఎల్లప్పుడూ సమర్థించబడదు.
  • డాకర్ OS మరియు లైబ్రరీల యొక్క విభిన్న వెర్షన్‌లతో డెవలపర్‌ల మధ్య కోడ్ పోర్టబిలిటీ మరియు ఫలితాల పునరుత్పత్తిని నిర్ధారిస్తుంది, అలాగే సర్వర్‌లలో సులభంగా అమలు చేయబడుతుంది. మీరు కేవలం ఒక ఆదేశంతో మొత్తం శిక్షణ పైప్‌లైన్‌ను ప్రారంభించవచ్చు.
  • Google క్లౌడ్ ఖరీదైన హార్డ్‌వేర్‌పై ప్రయోగాలు చేయడానికి బడ్జెట్-స్నేహపూర్వక మార్గం, కానీ మీరు కాన్ఫిగరేషన్‌లను జాగ్రత్తగా ఎంచుకోవాలి.
  • వ్యక్తిగత కోడ్ శకలాల వేగాన్ని కొలవడం చాలా ఉపయోగకరంగా ఉంటుంది, ప్రత్యేకించి R మరియు C++ మరియు ప్యాకేజీతో కలపడం బెంచ్ - కూడా చాలా సులభం.

మొత్తంమీద ఈ అనుభవం చాలా బహుమతిగా ఉంది మరియు మేము లేవనెత్తిన కొన్ని సమస్యలను పరిష్కరించడానికి పని చేస్తూనే ఉన్నాము.

మూలం: www.habr.com

ఒక వ్యాఖ్యను జోడించండి