د چټک ډراو ډوډل پیژندنه: څنګه د R، C++ او عصبي شبکو سره ملګري پیدا کړئ

د چټک ډراو ډوډل پیژندنه: څنګه د R، C++ او عصبي شبکو سره ملګري پیدا کړئ

اې حبره!

تېر مني، کاګل د لاسي انځورونو د طبقه بندي کولو لپاره د سیالۍ کوربه توب وکړ، Quick Draw Doodle Recognition، په کوم کې چې د نورو په منځ کې، د R-ساینس پوهانو یوې ډلې برخه اخیستې وه: Artem Klevtsova, د فیلیپا مدیر и اندری اوګورتسوف. موږ به د سیالۍ تفصیل په تفصیل سره بیان نه کړو؛ چې دمخه ترسره شوې وروستۍ خپرونه.

دا ځل د مډال کرنې سره کار نه دی شوی، مګر ډیره ارزښتناکه تجربه ترلاسه شوه، نو زه غواړم د کاګل او ورځني کار په اړه د یو شمیر خورا په زړه پورې او ګټورو شیانو په اړه ټولنې ته ووایم. د بحث شویو موضوعاتو په منځ کې: پرته له ستونزمن ژوند OpenCV, JSON پارس کول (دا مثالونه د R په کارولو سره په سکریپټونو یا کڅوړو کې د C++ کوډ ادغام معاینه کوي Rcpp)، د سکریپټونو پیرامیټریزیشن او د وروستي حل ډاکریزیشن. د پیغام ټول کوډ په داسې بڼه کې شتون لري چې د اجرا کولو لپاره مناسب دی ذخیره.

محتويات:

  1. په مؤثره توګه د CSV څخه MonetDB ته ډاټا بار کړئ
  2. د بستونو چمتو کول
  3. د ډیټابیس څخه د بستونو د پورته کولو لپاره تکرار کونکي
  4. د ماډل جوړښت غوره کول
  5. د سکریپټ پیرامیټریزیشن
  6. د سکریپټونو ډاکر کول
  7. په ګوګل کلاوډ کې د ډیری GPUs کارول
  8. پر ځای د يو پایلې

1. په مؤثره توګه د CSV څخه ډیټا د MonetDB ډیټابیس ته پورته کړئ

په دې سیالۍ کې ډاټا د چمتو شوي عکسونو په بڼه نه، بلکې د 340 CSV فایلونو په بڼه (د هر ټولګي لپاره یوه فایل) چې JSONs لري د نقطې همغږۍ سره. د دې نقطو سره د لینونو سره وصل کولو سره، موږ د 256x256 پکسلز اندازه کولو وروستی انځور ترلاسه کوو. همدارنګه د هر ریکارډ لپاره یو لیبل شتون لري چې دا په ګوته کوي چې ایا عکس د کټګورۍ لخوا په سمه توګه پیژندل شوی و چې د ډیټاسیټ راټولولو په وخت کې کارول شوی و، د انځور د لیکوال د استوګنې هیواد دوه خطي کوډ، یو ځانګړی پیژندونکی، یو مهال ویش او د ټولګي نوم چې د فایل نوم سره سمون لري. د اصلي معلوماتو ساده نسخه په آرشیف کې 7.4 GB وزن لري او د پیک کولو وروسته نږدې 20 GB دی، د پیک کولو وروسته بشپړ معلومات 240 GB ته رسیږي. تنظیم کونکو ډاډ ترلاسه کړ چې دواړه نسخې ورته نقاشي بیا تولیدوي ، پدې معنی چې بشپړ نسخه بې ځایه وه. په هر حالت کې، په ګرافیک فایلونو کې یا د صفونو په بڼه د 50 ملیون عکسونو ذخیره کول سمدلاسه بې ګټې وګڼل شول، او موږ پریکړه وکړه چې د آرشیف څخه ټول CSV فایلونه یوځای کړو. train_simplified.zip ډیټابیس ته د هرې بستې لپاره "په الوتنه کې" د اړتیا وړ اندازې عکسونو راتلونکي نسل سره.

یو ښه ثابت سیسټم د DBMS په توګه غوره شوی MonetDB، د بیلګې په توګه د 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. د څو JSONs تجزیه کول د نقطو همغږي سره د تارونو ویکتورونه لري.
  2. د اړتیا وړ اندازې په عکس کې د نقطو همغږي پراساس رنګین کرښې رسم کړئ (د مثال په توګه 256 × 256 یا 128 × 128).
  3. پایله لرونکي عکسونه په ټینسر بدلول.

د Python kernels ترمنځ د سیالۍ د یوې برخې په توګه، ستونزه په ابتدايي توګه په کارولو سره حل شوه 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 ته خوندي کیږي (په لینکس کې ، لنډمهاله 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 څخه د ویکتور څو اړخیزه پروسس تنظیم کولو لپاره. د دې کڅوړې لخوا چمتو شوي سرلیک فایلونه کارول شوي. د ډیر مشهور څخه RcppParallel کڅوړه، د نورو شیانو په منځ کې، یو جوړ شوی لوپ مداخله میکانیزم لري.

دا د یادولو وړ ده xtensor د خدای په توګه وګرځید: د دې حقیقت سربیره چې دا پراخه فعالیت او لوړ فعالیت لري ، د دې پراختیا کونکي خورا ځواب ویونکي وګرځیدل او پوښتنو ته یې سمدستي او په تفصیل سره ځواب ورکړ. د دوی په مرسته، دا ممکنه وه چې د OpenCV میټریکونو بدلونونه په xtensor ټینسرونو کې پلي کړي، په بیله بیا د 3-dimensional image tensors سره د سم ابعاد په 4-dimensional ټینسر کې د یوځای کولو لاره (بچ پخپله).

د 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. په اتوماتيک ډول د لارو او بیرغونو موندلو لپاره، موږ د لینکس یو مشهور استعمال کارولی 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 د REPOZITORIA. کوډ په څو دندو ویشل شوی دی:

  • 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. د ډیټابیس څخه د بستونو د پورته کولو لپاره تکرار کونکي

R د ډیټا پروسس کولو لپاره ښه شهرت لري چې په RAM کې فټ کیږي، پداسې حال کې چې Python د تکراري ډیټا پروسس کولو لخوا مشخص شوی، تاسو ته اجازه درکوي په اسانۍ او طبیعي توګه د کور څخه بهر محاسبې پلي کړئ (د بهرنۍ حافظې په کارولو سره محاسبه). زموږ لپاره د بیان شوې ستونزې په شرایطو کې یو کلاسیک او اړونده بیلګه ژورې عصبي شبکې دي چې د تدریجي نزول میتود لخوا روزل شوي په هر مرحله کې د تدریجي نږدې لید سره د لیدونو کوچنۍ برخې یا مینی بیچ په کارولو سره.

په Python کې لیکل شوي ژورې زده کړې چوکاټونه ځانګړي ټولګي لري چې د ډیټا پراساس تکرار کونکي پلي کوي: میزونه، په فولډر کې انځورونه، بائنری فارمیټونه، او نور. تاسو کولی شئ چمتو شوي انتخابونه وکاروئ یا د ځانګړو کارونو لپاره خپل ځان ولیکئ. په R کې موږ کولی شو د Python کتابتون له ټولو ځانګړتیاوو څخه ګټه واخلو کیرا د ورته نوم کڅوړې په کارولو سره د دې مختلف شالیدونو سره ، کوم چې په پایله کې د کڅوړې په سر کې کار کوي جالوالی. وروستنۍ د جلا اوږدې مقالې مستحق دي؛ دا نه یوازې تاسو ته اجازه درکوي د R څخه د Python کوډ پرمخ بوځي، بلکه تاسو ته اجازه درکوي د R او Python سیشنونو ترمنځ توکي انتقال کړئ، په اتوماتيک ډول ټول اړین ډول تبادلې ترسره کوي.

موږ د MonetDBLite په کارولو سره په RAM کې د ټولو ډیټا ذخیره کولو اړتیا لرې کړه ، ټول "عصبي شبکې" کار به په پایتون کې د اصلي کوډ لخوا ترسره شي ، موږ باید یوازې د ډیټا په اړه یو تکرار لیکو ، ځکه چې هیڅ چمتو نه دی. په R یا Python کې د داسې حالت لپاره. د دې لپاره په اصل کې یوازې دوه اړتیاوې شتون لري: دا باید بیچونه په نه ختمیدونکي لوپ کې بیرته راولي او د تکرارونو ترمینځ خپل حالت خوندي کړي (په R کې وروستی د بندونو په کارولو سره په ساده ډول پلي کیږي). پخوا، دا اړینه وه چې په واضح ډول R arrays د تکرار کونکي دننه numpy arrays ته واړوي، مګر د کڅوړې اوسنی نسخه کیرا دا پخپله کوي.

د روزنې او اعتبار معلوماتو لپاره تکرار کوونکی په لاندې ډول وګرځید:

د روزنې او اعتبار معلوماتو لپاره تکرارونکی

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] ته اندازه کړي، کوم چې د چمتو شوي روزنې پرمهال کارول کیده کیرا ماډلونه

بهرنی فعالیت د دلیل ډول چک کول، یو جدول لري 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 او د لینک له لارې ترمیم - پرته له دې کڅوړې "چپس" 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 لرئ، تاسو کولی شئ د ډیټابیس عملیات په جدي توګه ګړندۍ کړئ دا ورته رام ته لیږدولو سره (زموږ د دندې لپاره 32 GB کافی دی). په لینکس کې، ویش د ډیفالټ په واسطه نصب شوی /dev/shm، تر نیمایي پورې د رام ظرفیت نیول. تاسو کولی شئ د سمون له لارې نور روښانه کړئ /etc/fstabلکه ریکارډ ترلاسه کولو لپاره tmpfs /dev/shm tmpfs defaults,size=25g 0 0. ډاډ ترلاسه کړئ چې ریبوټ وکړئ او د کمانډ په چلولو سره پایله وګورئ df -h.

د ازموینې ډیټا لپاره تکرار خورا ساده ښکاري ، ځکه چې د ازموینې ډیټا سیټ په بشپړ ډول په رام کې فټ کیږي:

د ازموینې ډاټا لپاره تکرار کوونکی

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د هغه ځانګړتیاوې چې په کې بحث شوي دي دا پیغام دا د معیاري په توګه شامل دي کیرا او، په دې اساس، د 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)
}

د دې طریقې زیانونه څرګند دي. زه غواړم ډیری ماډلونه و ازمومم، مګر برعکس، زه نه غواړم هر جوړښت په لاسي ډول بیا ولیکم. موږ د دې فرصت څخه هم بې برخې شوي یو چې د ماډل وزنونه وکاروو چې دمخه روزل شوي په imagenet کې. د معمول په څیر، د اسنادو مطالعه مرسته وکړه. فعالیت 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)

اوس دا ستونزمنه نده چې یو نړیوال فعالیت ولیکئ ترڅو کوم چمتو شوي ترلاسه کړئ کیرا په امیجنټ کې روزل شوي وزن سره یا پرته ماډلونه:

د چمتو شوي جوړښتونو بارولو لپاره فعالیت

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(). موږ هیڅکله دا فعالیت ندی اضافه کړی، ځکه چې پدې مرحله کې دا لا دمخه روښانه وه چې دا د رنګ انځورونو سره کار کولو لپاره خورا ګټور و.

موږ ډیری تجربې د Mobilenet نسخه 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/ د دې په مرسته، سکریپټونه د ساده کمانډونو سره پیل شوي لکه 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 له اوسني نسخې څخه کیرا د R په کارولو کې نشي کولی د بدلونونو له امله چې په R کڅوړه کې په پام کې نه نیول شوي، موږ د دوی د سمولو لپاره انتظار کوو.

دې کړنالرې دا ممکنه کړې چې په RStudio کې د سکریپټونو ډیر دودیز لانچ په پرتله د مختلف ماډلونو سره تجربې د پام وړ ګړندۍ کړي (موږ کڅوړه د احتمالي بدیل په توګه یادونه کوو tfruns). مګر اصلي ګټه د دې لپاره د RStudio نصبولو پرته په ډاکر یا په ساده ډول په سرور کې د سکریپټونو لانچ په اسانۍ اداره کولو وړتیا ده.

6. د سکریپټونو Dockerization

موږ د ټیم غړو ترمینځ د روزنې ماډلونو او په بادل کې د ګړندي ځای په ځای کولو لپاره د چاپیریال د وړتیا ډاډ ترلاسه کولو لپاره ډاکر کارولی. تاسو کولی شئ د دې وسیلې سره آشنا پیل وکړئ ، کوم چې د R برنامې لپاره نسبتا غیر معمولي دی دا د خپرونو لړۍ یا ویډیو کورس.

ډاکر تاسو ته اجازه درکوي دواړه خپل عکسونه له سکریچ څخه جوړ کړئ او نور عکسونه د خپل ځان جوړولو لپاره د اساس په توګه وکاروئ. کله چې د شته اختیارونو تحلیل کول، موږ دې پایلې ته ورسیدو چې د NVIDIA، CUDA + cuDNN ډرایورانو او Python کتابتونونو نصب کول د عکس خورا پراخه برخه ده، او موږ پریکړه وکړه چې رسمي عکس د اساس په توګه واخلو. tensorflow/tensorflow:1.12.0-gpu، هلته د اړینو R کڅوړو اضافه کول.

وروستی ډاکر فایل داسې ښکاري:

ډکولر

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. په ګوګل کلاوډ کې د ډیری GPUs کارول

د سیالۍ یو له ځانګړتیاوو څخه خورا شور لرونکی معلومات و (د سرلیک عکس وګورئ، د ODS سست څخه د @Leigh.plt څخه پور اخیستل شوی). لوی بیچونه د دې سره مبارزه کې مرسته کوي ، او د 1 GPU سره په کمپیوټر کې له تجربو وروسته ، موږ پریکړه وکړه چې په بادل کې په څو GPUs کې د روزنې ماډلونه ماسټر کړو. کارول شوی GoogleCloud (د اساساتو لپاره ښه لارښود) د شته تشکیلاتو د لوی انتخاب له امله، مناسب قیمتونه او $300 بونس. د لالچ څخه، ما د SSD او یو ټن رام سره د 4xV100 مثال امر وکړ، او دا یوه لویه تېروتنه وه. دا ډول ماشین په چټکۍ سره پیسې خوري؛ تاسو کولی شئ د ثابت پایپ لاین پرته مات شوي تجربې ته لاړ شئ. د تعلیمي موخو لپاره، دا غوره ده چې K80 واخلئ. مګر د رام لوی مقدار په کار کې راغلی - کلاوډ SSD د دې فعالیت سره اغیزه ونکړ ، نو ډیټابیس ورته لیږدول شوی dev/shm.

ترټولو لوی دلچسپي د کوډ ټوټه ده چې د ډیری GPUs کارولو مسؤلیت لري. لومړی ، ماډل په 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
  )
})

بیا نه جوړ شوی (دا مهم دی) ماډل د موجود GPUs ورکړل شوي شمیر ته کاپي شوی ، او یوازې له هغې وروسته دا ترتیب شوی:

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

د وروستي څخه پرته د ټولو پرتونو یخولو کلاسیک تخنیک ، د وروستي پرت روزنه ، د څو GPUs لپاره د بشپړ ماډل غیر کنګل کول او بیا روزنه نشي پلي کیدی.

روزنه پرته له کارولو څخه څارل شوې. ټینسربورډ، د هر دورې وروسته د معلوماتي نومونو سره د لاګ ثبتولو او موډلونو خوندي کولو پورې محدود کول:

کال بیکونه

# Шаблон имени файла лога
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. د یوې پایلې پر ځای

یو شمیر ستونزې چې موږ ورسره مخ یو لا تر اوسه نه دي حل شوي:

  • в کیرا د غوره زده کړې نرخ (انالوګ lr_finder په کتابتون کې fast.ai); د یو څه هڅو سره، دا ممکنه ده چې د دریمې ډلې پلي کول R ته پورټ کړئ، د بیلګې په توګه، دا;
  • د تیرې نقطې په پایله کې، دا ممکنه نه وه چې د روزنې سم سرعت غوره کړئ کله چې د څو GPUs کارول؛
  • د عصري عصبي شبکې جوړښتونو نشتوالی شتون لري، په ځانګړې توګه هغه چې په امیجنټ کې مخکې روزل شوي؛
  • هیڅ یو سایکل پالیسي او د تبعیض زده کړې نرخونه (کوزین انیل کول زموږ په غوښتنه وه پلي شوي، مننه سکیډان).

د دې سیالۍ څخه کوم ګټور شیان زده کړل:

  • په نسبتا ټیټ بریښنا هارډویر کې، تاسو کولی شئ د درد پرته د ډیټا حجمونو (ډیری ځله د RAM اندازه) سره کار وکړئ. پلاستیکي کڅوړه data.table د جدولونو د ځای په ځای کولو سره حافظه خوندي کوي ، کوم چې د کاپي کولو څخه مخنیوی کوي ، او کله چې په سمه توګه وکارول شي ، د دې وړتیا تقریبا تل د ټولو وسیلو په مینځ کې ترټولو لوړ سرعت ښیې چې موږ ته د سکریپټ ژبې لپاره پیژندل شوي. په ډیټابیس کې د معلوماتو خوندي کول تاسو ته اجازه درکوي ، په ډیری قضیو کې ، په RAM کې د ټول ډیټا سیټ د مینځلو اړتیا په اړه فکر مه کوئ.
  • په R کې ورو فنکشنونه د کڅوړې په کارولو سره په C++ کې د ګړندي کارونو سره بدل کیدی شي Rcpp. که د کارولو سربیره RcppThread او یا RcppParallel، موږ د کراس پلیټ فارم څو-تریډ شوي تطبیقونه ترلاسه کوو ، نو د R په کچه کوډ موازي کولو ته اړتیا نشته.
  • بسته Rcpp د C++ د جدي پوهې پرته کارول کیدی شي، اړین لږ تر لږه په ګوته شوی دلته. د یو شمیر غوره C-کتابتونونو لپاره د سرلیک فایلونه لکه xtensor په CRAN کې شتون لري، دا د پروژو پلي کولو لپاره زیربنا رامینځته کیږي چې چمتو شوي لوړ فعالیت C++ کوډ په R کې مدغم کړي. اضافي اسانتیا د نحو روښانه کول او په RStudio کې د جامد C++ کوډ تحلیل کونکی دی.
  • docopt تاسو ته اجازه درکوي د پیرامیټونو سره په ځان کې موجود سکریپټونه چل کړئ. دا په ریموټ سرور کې د کارولو لپاره مناسب دی، په شمول. د ډاکر لاندې. په RStudio کې، د عصبي شبکو د روزنې سره د څو ساعتونو تجربو ترسره کول ستونزمن دي، او پخپله سرور کې د IDE نصب کول تل د توجیه وړ ندي.
  • ډاکر د OS او کتابتونونو مختلف نسخو سره د پراختیا کونکو تر مینځ د کوډ پورټ وړتیا او د پایلو بیا تولید تضمین کوي ​​، په بیله بیا په سرورونو کې د اجرا کولو اسانتیا. تاسو کولی شئ د روزنې ټول پایپ لاین یوازې د یوې کمانډ سره پیل کړئ.
  • ګوګل کلاوډ د ګران هارډویر تجربه کولو لپاره د بودیجې دوستانه لاره ده، مګر تاسو اړتیا لرئ چې ترتیبات په احتیاط سره غوره کړئ.
  • د انفرادي کوډ ټوټې سرعت اندازه کول خورا ګټور دي، په ځانګړې توګه کله چې د R او C++ سره یوځای کول، او د کڅوړې سره بنچ - هم خورا اسانه.

په ټولیز ډول دا تجربه ډیره ګټوره وه او موږ د ځینو راپورته شویو مسلو د حل لپاره کار ته دوام ورکوو.

سرچینه: www.habr.com

Add a comment