کوئیک ڈرا ڈوڈل ریکگنیشن: R، C++ اور نیورل نیٹ ورکس کے ساتھ دوستی کیسے کریں۔

کوئیک ڈرا ڈوڈل ریکگنیشن: R، C++ اور نیورل نیٹ ورکس کے ساتھ دوستی کیسے کریں۔

ارے حبر!

گزشتہ موسم خزاں میں، Kaggle نے ہاتھ سے تیار کردہ تصویروں کی درجہ بندی کرنے کے لیے ایک مقابلے کی میزبانی کی، Quick Draw Doodle Recognition، جس میں، دوسروں کے علاوہ، R-scientists کی ایک ٹیم نے حصہ لیا: Artem Klevtsova, فلیپا مینیجر и آندرے اوگرٹسوف. ہم مقابلہ کی تفصیل سے وضاحت نہیں کریں گے؛ یہ پہلے ہی ہو چکا ہے۔ حالیہ اشاعت.

اس بار میڈل فارمنگ کے ساتھ کام نہیں ہوا، لیکن بہت قیمتی تجربہ حاصل ہوا، اس لیے میں کمیونٹی کو کاگلے اور روزمرہ کے کاموں میں بہت سی دلچسپ اور مفید چیزوں کے بارے میں بتانا چاہوں گا۔ زیر بحث موضوعات میں سے: بغیر مشکل زندگی OpenCV, JSON parsing (یہ مثالیں R میں C++ کوڈ کے اسکرپٹس یا پیکیجز میں انضمام کی جانچ کرتی ہیں آر سی پی پی)، اسکرپٹ کا پیرامیٹرائزیشن اور حتمی حل کی ڈاکرائزیشن۔ میسج کے تمام کوڈ اس فارم میں دستیاب ہیں جو عملدرآمد کے لیے موزوں ہیں۔ ذخیرے.

فہرست:

  1. CSV سے MonetDB میں ڈیٹا کو مؤثر طریقے سے لوڈ کریں۔
  2. بیچوں کی تیاری
  3. ڈیٹا بیس سے بیچوں کو اتارنے کے لیے تکرار کرنے والے
  4. ایک ماڈل آرکیٹیکچر کا انتخاب
  5. اسکرپٹ پیرامیٹرائزیشن
  6. اسکرپٹ کی ڈاکرائزیشن
  7. گوگل کلاؤڈ پر متعدد GPUs کا استعمال
  8. اس کے بجائے کسی نتیجے کے

1. مؤثر طریقے سے CSV سے ڈیٹا کو MonetDB ڈیٹا بیس میں لوڈ کریں۔

اس مقابلے کا ڈیٹا ریڈی میڈ امیجز کی شکل میں نہیں بلکہ 340 CSV فائلز (ہر کلاس کے لیے ایک فائل) کی شکل میں فراہم کیا گیا ہے جس میں پوائنٹ کوآرڈینیٹ کے ساتھ JSONs ہیں۔ ان پوائنٹس کو لائنوں سے جوڑ کر، ہمیں 256x256 پکسلز کی پیمائش کی ایک حتمی تصویر ملتی ہے۔ اس کے علاوہ ہر ریکارڈ کے لیے ایک لیبل ہوتا ہے جس سے یہ ظاہر ہوتا ہے کہ آیا ڈیٹاسیٹ کو جمع کرنے کے وقت استعمال کیے گئے درجہ بندی کرنے والے کے ذریعے تصویر کو صحیح طریقے سے پہچانا گیا تھا، تصویر کے مصنف کے ملک کا دو حرفی کوڈ، ایک منفرد شناخت کنندہ، ٹائم اسٹیمپ اور ایک کلاس کا نام جو فائل کے نام سے ملتا ہے۔ آرکائیو میں اصل ڈیٹا کے ایک آسان ورژن کا وزن 7.4 جی بی اور پیک کھولنے کے بعد تقریباً 20 جی بی ہوتا ہے، پیک کھولنے کے بعد مکمل ڈیٹا 240 جی بی تک لے جاتا ہے۔ منتظمین نے اس بات کو یقینی بنایا کہ دونوں ورژن ایک ہی ڈرائنگ کو دوبارہ پیش کریں، یعنی مکمل ورژن بے کار تھا۔ کسی بھی صورت میں، 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 آرکائیو سے متعدد فائلوں کے ساتھ صحیح طریقے سے کام نہیں کرتا ہے، لہذا ہم نے سسٹم کا استعمال کیا۔ 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 کو ذخیرہ کرنے کے لیے درکار قسم ہے۔ 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 میں سب سے آسان اور واضح analogues میں سے ایک اس طرح نظر آئے گا:

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 کوڈ میں انضمام آر سی پی پی.

اس مسئلے کو حل کرنے کے لیے درج ذیل پیکیجز اور لائبریریاں استعمال کی گئیں۔

  1. OpenCV تصاویر اور ڈرائنگ لائنوں کے ساتھ کام کرنے کے لیے۔ استعمال شدہ پہلے سے نصب شدہ سسٹم لائبریریوں اور ہیڈر فائلوں کے ساتھ ساتھ متحرک لنکنگ۔

  2. ایکسٹینسر کثیر جہتی صفوں اور ٹینسر کے ساتھ کام کرنے کے لیے۔ ہم نے اسی نام کے R پیکیج میں شامل ہیڈر فائلوں کا استعمال کیا۔ لائبریری آپ کو کثیر جہتی صفوں کے ساتھ کام کرنے کی اجازت دیتی ہے، قطار میجر اور کالم میجر دونوں ترتیب میں۔

  3. ndjson JSON کو پارس کرنے کے لیے۔ یہ لائبریری اس میں استعمال ہوتی ہے۔ ایکسٹینسر اگر یہ پروجیکٹ میں موجود ہے تو خود بخود۔

  4. آر سی پی پی تھریڈ JSON سے ویکٹر کی ملٹی تھریڈڈ پروسیسنگ کو منظم کرنے کے لیے۔ اس پیکیج کے ذریعہ فراہم کردہ ہیڈر فائلوں کا استعمال کیا گیا۔ زیادہ مقبول سے آر سی پی پی پارالل پیکیج میں، دوسری چیزوں کے علاوہ، ایک بلٹ ان لوپ انٹرپٹ میکانزم ہے۔

یہ بات قابل ذکر ہونا چاہئے کہ ایکسٹینسر ایک گڈ ایسنڈ ثابت ہوا: اس حقیقت کے علاوہ کہ اس میں وسیع فعالیت اور اعلیٰ کارکردگی ہے، اس کے ڈویلپر کافی جوابدہ نکلے اور سوالات کے جوابات فوری اور تفصیل سے دیے۔ ان کی مدد سے، اوپن سی وی میٹرکس کی ایکسٹینسر ٹینسر میں تبدیلیوں کو لاگو کرنا ممکن تھا، نیز 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

ایسی فائلوں کو مرتب کرنے کے لیے جو سسٹم فائلز کا استعمال کرتی ہیں اور سسٹم پر نصب لائبریریوں کے ساتھ ڈائنامک لنکنگ کا استعمال کرتی ہیں، ہم نے پیکیج میں نافذ پلگ ان میکانزم کا استعمال کیا۔ آر سی پی پی. خود بخود راستے اور جھنڈے تلاش کرنے کے لیے، ہم نے ایک مقبول لینکس یوٹیلیٹی کا استعمال کیا۔ 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 کی زیادہ خصوصیت تکراری ڈیٹا پروسیسنگ ہے، جس سے آپ آسانی سے اور قدرتی طور پر آؤٹ آف کور کیلکولیشنز (بیرونی میموری کا استعمال کرتے ہوئے حسابات) کو لاگو کر سکتے ہیں۔ بیان کردہ مسئلے کے تناظر میں ہمارے لیے ایک کلاسک اور متعلقہ مثال گہرے عصبی نیٹ ورکس ہیں جن کی تربیت گریڈیئنٹ ڈیسنٹ طریقہ سے کی جاتی ہے جس میں مشاہدات کے ایک چھوٹے سے حصے، یا منی بیچ کا استعمال کرتے ہوئے ہر قدم پر میلان کا تخمینہ لگایا جاتا ہے۔

پائتھون میں لکھے گئے ڈیپ لرننگ فریم ورکس میں خصوصی کلاسز ہیں جو ڈیٹا کی بنیاد پر تکرار کرنے والوں کو لاگو کرتی ہیں: ٹیبلز، فولڈرز میں تصویریں، بائنری فارمیٹس وغیرہ۔ آپ مخصوص کاموں کے لیے ریڈی میڈ آپشنز استعمال کر سکتے ہیں یا خود لکھ سکتے ہیں۔ R میں ہم Python لائبریری کی تمام خصوصیات سے فائدہ اٹھا سکتے ہیں۔ کیرا اسی نام کے پیکیج کا استعمال کرتے ہوئے اس کے مختلف بیک اینڈ کے ساتھ، جو بدلے میں پیکج کے اوپر کام کرتا ہے۔ جال لگانا. مؤخر الذکر ایک الگ طویل مضمون کا مستحق ہے۔ یہ نہ صرف آپ کو R سے Python کوڈ چلانے کی اجازت دیتا ہے، بلکہ آپ کو R اور Python سیشنز کے درمیان اشیاء کو منتقل کرنے کی بھی اجازت دیتا ہے، خود بخود تمام ضروری قسم کے تبادلوں کو انجام دیتا ہے۔

ہم نے MonetDBLite کا استعمال کرتے ہوئے تمام ڈیٹا کو RAM میں ذخیرہ کرنے کی ضرورت سے چھٹکارا حاصل کر لیا، تمام "نیورل نیٹ ورک" کا کام Python میں اصل کوڈ کے ذریعے کیا جائے گا، ہمیں صرف ڈیٹا پر ایک تکراری لکھنا ہے، کیونکہ کچھ بھی تیار نہیں ہے۔ R یا Python میں ایسی صورت حال کے لیے۔ اس کے لیے بنیادی طور پر صرف دو تقاضے ہیں: اسے لامتناہی لوپ میں بیچوں کو واپس کرنا چاہیے اور تکرار کے درمیان اپنی حالت کو بچانا چاہیے (R میں مؤخر الذکر بندش کا استعمال کرتے ہوئے آسان ترین طریقے سے لاگو کیا جاتا ہے)۔ اس سے پہلے، R arrays کو واضح طور پر iterator کے اندر 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 اور لنک کے ذریعے ترمیم - ان پیکج "چپس" کے بغیر ڈیٹا ٹیبل 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 کافی ہے)۔ لینکس میں، پارٹیشن بطور ڈیفالٹ نصب ہوتا ہے۔ /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جس کی خصوصیات میں بحث کی گئی ہے۔ یہ پیغام یہ معیاری کے طور پر شامل ہے۔ کیرا اور، اس کے مطابق، R کے لیے اسی نام کے پیکج میں دستیاب ہے۔ لیکن جب اسے سنگل چینل امیجز کے ساتھ استعمال کرنے کی کوشش کی گئی، تو ایک عجیب بات نکلی: ان پٹ ٹینسر کا طول و عرض ہمیشہ ہونا چاہیے۔ (batch, height, width, 3)، یعنی چینلز کی تعداد کو تبدیل نہیں کیا جا سکتا۔ Python میں ایسی کوئی حد نہیں ہے، اس لیے ہم نے جلدی کی اور اصل مضمون کے بعد (کیراس ورژن میں ڈراپ آؤٹ کے بغیر) اس فن تعمیر کا اپنا نفاذ لکھا۔

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)

اب فراہم کردہ میں سے کسی کو حاصل کرنے کے لیے عالمگیر فنکشن لکھنا مشکل نہیں ہے۔ کیرا امیج نیٹ پر تربیت یافتہ وزن کے ساتھ یا بغیر ماڈل:

ریڈی میڈ آرکیٹیکچرز کو لوڈ کرنے کا فنکشن

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 arrays کی فہرست کی شکل میں ماڈل کا وزن حاصل کریں، اس فہرست کے پہلے عنصر کے طول و عرض کو تبدیل کریں (ایک کلر چینل لے کر یا تینوں کا اوسط لے کر)، اور پھر وزن کو فنکشن کے ساتھ ماڈل میں واپس لوڈ کریں۔ 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/ اس کی مدد سے، اسکرپٹ کو آسان کمانڈز کے ساتھ لانچ کیا جاتا ہے۔ 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 کے استعمال میں نہیں کرنا چاہئے آر پیکج میں تبدیلیوں کو مدنظر نہ رکھنے کی وجہ سے، ہم ان کے ٹھیک ہونے کا انتظار کر رہے ہیں۔

اس نقطہ نظر نے RStudio میں اسکرپٹ کے زیادہ روایتی لانچ کے مقابلے میں مختلف ماڈلز کے ساتھ تجربات کو نمایاں طور پر تیز کرنا ممکن بنایا (ہم پیکیج کو ایک ممکنہ متبادل کے طور پر نوٹ کرتے ہیں۔ tfruns)۔ لیکن بنیادی فائدہ یہ ہے کہ اس کے لیے RStudio کو انسٹال کیے بغیر، Docker میں یا صرف سرور پر اسکرپٹ کے اجراء کا آسانی سے انتظام کرنا ہے۔

6. اسکرپٹ کی ڈاکرائزیشن

ہم نے ٹیم کے اراکین کے درمیان ٹریننگ ماڈلز اور کلاؤڈ میں تیزی سے تعیناتی کے لیے ماحول کی پورٹیبلٹی کو یقینی بنانے کے لیے Docker کا استعمال کیا۔ آپ اس ٹول سے واقف ہونا شروع کر سکتے ہیں، جو کہ R پروگرامر کے لیے نسبتاً غیر معمولی ہے۔ اس اشاعتوں کا سلسلہ یا ویڈیو کورس.

Docker آپ دونوں کو شروع سے اپنی خود کی تصاویر بنانے کی اجازت دیتا ہے اور دوسری تصاویر کو اپنی تخلیق کی بنیاد کے طور پر استعمال کرتا ہے۔ دستیاب اختیارات کا تجزیہ کرتے ہوئے، ہم اس نتیجے پر پہنچے کہ 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}

اگر یہ bash اسکرپٹ پیرامیٹرز کے بغیر چلایا جاتا ہے، تو اسکرپٹ کو کنٹینر کے اندر بلایا جائے گا۔ train_nn.R پہلے سے طے شدہ اقدار کے ساتھ؛ اگر پہلی پوزیشنی دلیل "bash" ہے، تو کنٹینر ایک کمانڈ شیل کے ساتھ انٹرایکٹو شروع کرے گا۔ دیگر تمام صورتوں میں، پوزیشنی دلائل کی اقدار کو بدل دیا جاتا ہے: CMD="Rscript /app/train_nn.R $@".

یہ بات قابل غور ہے کہ سورس ڈیٹا اور ڈیٹا بیس کے ساتھ ڈائرکٹریز کے ساتھ ساتھ تربیت یافتہ ماڈلز کو محفوظ کرنے کے لیے ڈائرکٹری کو میزبان سسٹم سے کنٹینر کے اندر نصب کیا جاتا ہے جس کی مدد سے آپ غیر ضروری ہیرا پھیری کے بغیر اسکرپٹ کے نتائج تک رسائی حاصل کر سکتے ہیں۔

7. گوگل کلاؤڈ پر متعدد GPUs کا استعمال

مقابلے کی خصوصیات میں سے ایک بہت شور والا ڈیٹا تھا (ٹائٹل تصویر دیکھیں، ODS slack سے @Leigh.plt سے لی گئی)۔ بڑے بیچز اس کا مقابلہ کرنے میں مدد کرتے ہیں، اور 1 GPU والے PC پر تجربات کے بعد، ہم نے کلاؤڈ میں کئی GPUs پر ٹریننگ ماڈلز میں مہارت حاصل کرنے کا فیصلہ کیا۔ استعمال شدہ GoogleCloud (بنیادی باتوں کے لیے اچھی گائیڈ) دستیاب کنفیگریشنز، مناسب قیمتوں اور $300 بونس کے بڑے انتخاب کی وجہ سے۔ لالچ میں، میں نے SSD اور ایک ٹن RAM کے ساتھ 4xV100 مثال کا آرڈر دیا، اور یہ ایک بڑی غلطی تھی۔ ایسی مشین تیزی سے پیسہ کھا جاتی ہے؛ آپ ثابت شدہ پائپ لائن کے بغیر تجربہ کر سکتے ہیں۔ تعلیمی مقاصد کے لیے K80 لینا بہتر ہے۔ لیکن بڑی مقدار میں RAM کام آئی - کلاؤڈ SSD اپنی کارکردگی سے متاثر نہیں ہوا، اس لیے ڈیٹا بیس کو منتقل کر دیا گیا۔ dev/shm.

سب سے زیادہ دلچسپی ایک سے زیادہ GPUs استعمال کرنے کے لیے ذمہ دار کوڈ کا ٹکڑا ہے۔ سب سے پہلے، ماڈل سی پی یو پر ایک سیاق و سباق مینیجر کا استعمال کرتے ہوئے بنایا گیا ہے، بالکل اسی طرح جیسے ازگر میں:

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 لائبریری میں quick.ai); کچھ کوشش کے ساتھ، تیسرے فریق کے نفاذ کو R پر پورٹ کرنا ممکن ہے، مثال کے طور پر، یہ;
  • پچھلے نقطہ کے نتیجے میں، کئی GPUs کا استعمال کرتے وقت تربیت کی درست رفتار کا انتخاب کرنا ممکن نہیں تھا۔
  • جدید نیورل نیٹ ورک آرکیٹیکچرز کی کمی ہے، خاص طور پر وہ جو امیج نیٹ پر پہلے سے تربیت یافتہ ہیں۔
  • کوئی ایک سائیکل پالیسی اور امتیازی سیکھنے کی شرح نہیں (کوزائن اینیلنگ ہماری درخواست پر تھی لاگو کیا، شکریہ سکیڈان).

اس مقابلے سے کون سی مفید چیزیں سیکھی گئیں:

  • نسبتاً کم طاقت والے ہارڈ ویئر پر، آپ بغیر درد کے ڈیٹا کی مہذب (ریم کے سائز سے کئی گنا زیادہ) حجم کے ساتھ کام کر سکتے ہیں۔ پلاسٹک بیگ ڈیٹا ٹیبل جدولوں کی جگہ جگہ ترمیم کی وجہ سے میموری کو بچاتا ہے، جو ان کو کاپی کرنے سے گریز کرتا ہے، اور جب صحیح طریقے سے استعمال کیا جائے تو، اس کی صلاحیتیں تقریباً ہمیشہ ان تمام ٹولز میں سب سے زیادہ رفتار کا مظاہرہ کرتی ہیں جو اسکرپٹنگ زبانوں کے لیے ہمارے لیے مشہور ہیں۔ ڈیٹا بیس میں ڈیٹا محفوظ کرنا آپ کو، بہت سے معاملات میں، پورے ڈیٹاسیٹ کو RAM میں نچوڑنے کی ضرورت کے بارے میں بالکل نہیں سوچنے دیتا ہے۔
  • R میں سست فنکشنز کو پیکج کا استعمال کرتے ہوئے C++ میں تیز فنکشنز سے تبدیل کیا جا سکتا ہے۔ آر سی پی پی. اگر استعمال کرنے کے علاوہ آر سی پی پی تھریڈ یا آر سی پی پی پارالل، ہمیں کراس پلیٹ فارم ملٹی تھریڈڈ نفاذات ملتے ہیں، لہذا R سطح پر کوڈ کو متوازی کرنے کی ضرورت نہیں ہے۔
  • پیکج آر سی پی پی C++ کی سنجیدہ معلومات کے بغیر استعمال کیا جا سکتا ہے، مطلوبہ کم از کم بیان کیا گیا ہے۔ یہاں. کئی ٹھنڈی سی لائبریریوں کے لیے ہیڈر فائلیں جیسے ایکسٹینسر CRAN پر دستیاب ہے، یعنی ان منصوبوں کے نفاذ کے لیے ایک بنیادی ڈھانچہ تشکیل دیا جا رہا ہے جو کہ تیار شدہ اعلیٰ کارکردگی والے C++ کوڈ کو R میں ضم کرتا ہے۔ اضافی سہولت نحو کو نمایاں کرنا اور RStudio میں ایک جامد C++ کوڈ تجزیہ کار ہے۔
  • docopt آپ کو پیرامیٹرز کے ساتھ خود ساختہ اسکرپٹ چلانے کی اجازت دیتا ہے۔ یہ ریموٹ سرور پر استعمال کے لیے آسان ہے، بشمول۔ ڈاکر کے تحت. RStudio میں، اعصابی نیٹ ورکس کی تربیت کے ساتھ کئی گھنٹوں کے تجربات کرنے میں تکلیف ہوتی ہے، اور خود سرور پر IDE انسٹال کرنا ہمیشہ جائز نہیں ہوتا۔
  • ڈوکر OS اور لائبریریوں کے مختلف ورژن والے ڈویلپرز کے درمیان کوڈ پورٹیبلٹی اور نتائج کی تولیدی صلاحیت کو یقینی بناتا ہے، نیز سرورز پر عمل درآمد میں آسانی۔ آپ پوری ٹریننگ پائپ لائن کو صرف ایک کمانڈ سے لانچ کر سکتے ہیں۔
  • گوگل کلاؤڈ مہنگے ہارڈ ویئر پر تجربہ کرنے کا بجٹ کے موافق طریقہ ہے، لیکن آپ کو کنفیگریشنز کو احتیاط سے منتخب کرنے کی ضرورت ہے۔
  • انفرادی کوڈ کے ٹکڑوں کی رفتار کی پیمائش بہت مفید ہے، خاص طور پر جب R اور C++ کو ملایا جائے، اور پیکیج کے ساتھ بینچ - بھی بہت آسان.

مجموعی طور پر یہ تجربہ بہت فائدہ مند تھا اور ہم اٹھائے گئے کچھ مسائل کو حل کرنے کے لیے کام کرتے رہتے ہیں۔

ماخذ: www.habr.com

نیا تبصرہ شامل کریں