ProHoster > blog > Gweinyddiaeth > Adnabyddiaeth Gyflym Doodle: sut i wneud ffrindiau â rhwydweithiau R, C ++ a niwral
Adnabyddiaeth Gyflym Doodle: sut i wneud ffrindiau â rhwydweithiau R, C ++ a niwral
Hei Habr!
Y cwymp diwethaf, cynhaliodd Kaggle gystadleuaeth i ddosbarthu lluniau wedi'u tynnu â llaw, Quick Draw Doodle Recognition, lle cymerodd tîm o R-wyddonwyr ran, ymhlith eraill: Artem Klevtsova, Rheolwr Philippa и Andrey Ogurtsov. Ni fyddwn yn disgrifio’r gystadleuaeth yn fanwl; mae hynny eisoes wedi’i wneud yn cyhoeddiad diweddar.
Y tro hwn nid oedd yn gweithio gyda ffermio medalau, ond enillwyd llawer o brofiad gwerthfawr, felly hoffwn ddweud wrth y gymuned am nifer o'r pethau mwyaf diddorol a defnyddiol ar Kagle ac mewn gwaith bob dydd. Ymhlith y pynciau a drafodwyd: bywyd anodd hebddo OpenCV, dosrannu JSON (mae'r enghreifftiau hyn yn archwilio integreiddio cod C++ i sgriptiau neu becynnau yn R gan ddefnyddio Rcpp), parameterization o sgriptiau a dockerization o'r ateb terfynol. Mae'r holl god o'r neges ar ffurf sy'n addas i'w weithredu ar gael yn storfeydd.
1. Llwytho data o CSV yn effeithlon i gronfa ddata MonetDB
Darperir y data yn y gystadleuaeth hon nid ar ffurf delweddau parod, ond ar ffurf 340 o ffeiliau CSV (un ffeil ar gyfer pob dosbarth) sy'n cynnwys JSONs gyda chyfesurynnau pwynt. Trwy gysylltu'r pwyntiau hyn â llinellau, rydym yn cael delwedd derfynol yn mesur 256x256 picsel. Hefyd ar gyfer pob cofnod mae label yn nodi a gafodd y llun ei adnabod yn gywir gan y dosbarthwr a ddefnyddiwyd ar yr adeg y casglwyd y set ddata, cod dwy lythyren o wlad breswyl awdur y llun, dynodwr unigryw, stamp amser ac enw dosbarth sy'n cyfateb i enw'r ffeil. Mae fersiwn symlach o'r data gwreiddiol yn pwyso 7.4 GB yn yr archif a thua 20 GB ar ôl dadbacio, mae'r data llawn ar ôl dadbacio yn cymryd 240 GB. Sicrhaodd y trefnwyr fod y ddau fersiwn yn atgynhyrchu'r un lluniadau, gan olygu nad oedd angen y fersiwn llawn. Beth bynnag, roedd storio 50 miliwn o ddelweddau mewn ffeiliau graffig neu ar ffurf araeau yn cael ei ystyried yn amhroffidiol ar unwaith, a gwnaethom benderfynu uno'r holl ffeiliau CSV o'r archif train_simplified.zip i mewn i'r gronfa ddata gyda'r genhedlaeth ddilynol o ddelweddau o'r maint gofynnol “ar y hedfan” ar gyfer pob swp.
Dewiswyd system sydd wedi'i phrofi'n dda fel y DBMS MonetDB, sef gweithrediad ar gyfer R fel pecyn MonetDBLite. Mae'r pecyn yn cynnwys fersiwn wedi'i fewnosod o weinydd y gronfa ddata ac yn caniatáu ichi godi'r gweinydd yn uniongyrchol o sesiwn R a gweithio gydag ef yno. Mae creu cronfa ddata a chysylltu ag ef yn cael ei berfformio gydag un gorchymyn:
con <- DBI::dbConnect(drv = MonetDBLite::MonetDBLite(), Sys.getenv("DBDIR"))
Bydd angen i ni greu dau dabl: un ar gyfer yr holl ddata, y llall ar gyfer gwybodaeth gwasanaeth am ffeiliau wedi'u llwytho i lawr (defnyddiol os aiff rhywbeth o'i le a rhaid ailddechrau'r broses ar ôl lawrlwytho sawl ffeil):
Y ffordd gyflymaf i lwytho data i'r gronfa ddata oedd copïo ffeiliau CSV yn uniongyrchol gan ddefnyddio SQL - gorchymyn COPY OFFSET 2 INTO tablename FROM path USING DELIMITERS ',','n','"' NULL AS '' BEST EFFORTlle tablename - enw bwrdd a path - y llwybr i'r ffeil. Wrth weithio gyda'r archif, darganfuwyd bod y gweithredu adeiledig unzip nid yw yn R yn gweithio'n gywir gyda nifer o ffeiliau o'r archif, felly fe wnaethom ddefnyddio'r system unzip (gan ddefnyddio'r paramedr getOption("unzip")).
Swyddogaeth ar gyfer ysgrifennu i'r gronfa ddata
#' @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))
}
Os oes angen i chi drawsnewid y tabl cyn ei ysgrifennu i'r gronfa ddata, mae'n ddigon i basio'r ddadl i mewn preprocess swyddogaeth a fydd yn trawsnewid y data.
Cod ar gyfer llwytho data yn olynol i'r gronfa ddata:
Ysgrifennu data i'r gronfa ddata
# Список файлов для записи
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
Gall amser llwytho data amrywio yn dibynnu ar nodweddion cyflymder y gyriant a ddefnyddir. Yn ein hachos ni, mae darllen ac ysgrifennu o fewn un SSD neu o yriant fflach (ffeil ffynhonnell) i SSD (DB) yn cymryd llai na 10 munud.
Mae'n cymryd ychydig eiliadau mwy i greu colofn gyda label dosbarth cyfanrif a cholofn mynegai (ORDERED INDEX) gyda rhifau llinellau ar gyfer samplu arsylwadau wrth greu sypiau:
Creu Colofnau a Mynegai Ychwanegol
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)"))
I ddatrys y broblem o greu swp ar y hedfan, roedd angen i ni gyflawni'r cyflymder uchaf o dynnu rhesi ar hap o'r bwrdd doodles. Ar gyfer hyn fe wnaethom ddefnyddio 3 tric. Y cyntaf oedd lleihau dimensiwn y math sy'n storio'r ID arsylwi. Yn y set ddata wreiddiol, y math sydd ei angen i storio'r ID yw bigint, ond mae nifer yr arsylwadau yn ei gwneud hi'n bosibl ffitio eu dynodwyr, sy'n hafal i'r rhif trefnol, i'r math int. Mae'r chwiliad yn llawer cyflymach yn yr achos hwn. Yr ail gamp oedd defnyddio ORDERED INDEX — daethom i’r penderfyniad hwn yn empirig, ar ôl mynd drwy bopeth a oedd ar gael opsiynau. Y trydydd oedd defnyddio ymholiadau paramedr. Hanfod y dull yw gweithredu'r gorchymyn unwaith PREPARE gyda defnydd dilynol o fynegiant parod wrth greu criw o ymholiadau o'r un math, ond mewn gwirionedd mae mantais o'i gymharu ag un syml SELECT troi allan i fod o fewn yr ystod o wallau ystadegol.
Nid yw'r broses o uwchlwytho data yn defnyddio mwy na 450 MB o RAM. Hynny yw, mae'r dull a ddisgrifir yn caniatáu ichi symud setiau data sy'n pwyso degau o gigabeit ar bron unrhyw galedwedd cyllideb, gan gynnwys rhai dyfeisiau bwrdd sengl, sy'n eithaf cŵl.
Y cyfan sydd ar ôl yw mesur cyflymder adalw data (ar hap) a gwerthuso’r raddfa wrth samplu sypiau o wahanol feintiau:
Meincnod cronfa ddata
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)
2. Paratoi sypiau
Mae'r broses gyfan o baratoi swp yn cynnwys y camau canlynol:
Dosrannu sawl JSON sy'n cynnwys fectorau llinynnau gyda chyfesurynnau pwyntiau.
Tynnu llinellau lliw yn seiliedig ar gyfesurynnau pwyntiau ar ddelwedd o'r maint gofynnol (er enghraifft, 256×256 neu 128×128).
Trosi'r delweddau canlyniadol yn tensor.
Fel rhan o'r gystadleuaeth ymhlith cnewyllyn Python, datryswyd y broblem yn bennaf gan ddefnyddio OpenCV. Byddai un o'r analogau symlaf ac amlycaf yn R yn edrych fel hyn:
Gweithredu Trosi JSON i Tensor yn R
r_process_json_str <- function(json, line.width = 3,
color = TRUE, scale = 1) {
# Парсинг JSON
coords <- jsonlite::fromJSON(json, simplifyMatrix = FALSE)
tmp <- tempfile()
# Удаляем временный файл по завершению функции
on.exit(unlink(tmp))
png(filename = tmp, width = 256 * scale, height = 256 * scale, pointsize = 1)
# Пустой график
plot.new()
# Размер окна графика
plot.window(xlim = c(256 * scale, 0), ylim = c(256 * scale, 0))
# Цвета линий
cols <- if (color) rainbow(length(coords)) else "#000000"
for (i in seq_along(coords)) {
lines(x = coords[[i]][[1]] * scale, y = coords[[i]][[2]] * scale,
col = cols[i], lwd = line.width)
}
dev.off()
# Преобразование изображения в 3-х мерный массив
res <- png::readPNG(tmp)
return(res)
}
r_process_json_vector <- function(x, ...) {
res <- lapply(x, r_process_json_str, ...)
# Объединение 3-х мерных массивов картинок в 4-х мерный в тензор
res <- do.call(abind::abind, c(res, along = 0))
return(res)
}
Perfformir lluniadu gan ddefnyddio offer R safonol a'i gadw i PNG dros dro sydd wedi'i storio yn RAM (ar Linux, mae cyfeirlyfrau R dros dro wedi'u lleoli yn y cyfeiriadur /tmp, wedi'i osod mewn RAM). Yna darllenir y ffeil hon fel arae tri dimensiwn gyda rhifau'n amrywio o 0 i 1. Mae hyn yn bwysig oherwydd byddai BMP mwy confensiynol yn cael ei ddarllen i mewn i arae amrwd gyda chodau lliw hecs.
Roedd y gweithrediad hwn yn ymddangos yn is-optimaidd i ni, gan fod ffurfio sypiau mawr yn cymryd amser anweddus o hir, a phenderfynom fanteisio ar brofiad ein cydweithwyr trwy ddefnyddio llyfrgell bwerus. OpenCV. Ar y pryd nid oedd pecyn parod ar gyfer R (nid oes un nawr), felly ychydig iawn o weithredu'r swyddogaeth ofynnol oedd wedi'i ysgrifennu yn C++ gydag integreiddio i god R gan ddefnyddio Rcpp.
I ddatrys y broblem, defnyddiwyd y pecynnau a'r llyfrgelloedd canlynol:
OpenCV ar gyfer gweithio gyda delweddau a thynnu llinellau. Wedi defnyddio llyfrgelloedd system wedi'u gosod ymlaen llaw a ffeiliau pennawd, yn ogystal â chysylltiadau deinamig.
xtensor ar gyfer gweithio gydag araeau a thenorau aml-ddimensiwn. Fe wnaethom ddefnyddio ffeiliau pennawd sydd wedi'u cynnwys yn y pecyn R o'r un enw. Mae'r llyfrgell yn caniatáu ichi weithio gydag araeau aml-ddimensiwn, yn nhrefn y prif resi a'r brif golofn.
ndjson am dosrannu JSON. Defnyddir y llyfrgell hon yn xtensor yn awtomatig os yw'n bresennol yn y prosiect.
RcppThread ar gyfer trefnu prosesu aml-edau fector gan JSON. Wedi defnyddio'r ffeiliau pennyn a ddarperir gan y pecyn hwn. O fwy poblogaidd RcppParallel Mae gan y pecyn, ymhlith pethau eraill, fecanwaith torri dolen adeiledig.
Dylid nodi bod xtensor Trodd allan i fod yn fendith: yn ogystal â'r ffaith bod ganddo ymarferoldeb helaeth a pherfformiad uchel, daeth ei ddatblygwyr yn eithaf ymatebol ac atebodd gwestiynau'n brydlon ac yn fanwl. Gyda'u cymorth, roedd yn bosibl trawsnewid matricsau OpenCV yn denorau xtensor, yn ogystal â ffordd o gyfuno tensorau delwedd 3-dimensiwn i mewn i densor 4-dimensiwn o'r dimensiwn cywir (y swp ei hun).
Deunyddiau ar gyfer dysgu Rcpp, xtensor ac RcppThread
I lunio ffeiliau sy'n defnyddio ffeiliau system a chysylltiadau deinamig â llyfrgelloedd sydd wedi'u gosod ar y system, gwnaethom ddefnyddio'r mecanwaith ategyn a weithredwyd yn y pecyn Rcpp. I ddod o hyd i lwybrau a baneri yn awtomatig, fe wnaethom ddefnyddio cyfleustodau Linux poblogaidd pkg-config.
Gweithredu'r ategyn Rcpp ar gyfer defnyddio'r llyfrgell OpenCV
Rhoddir y cod gweithredu ar gyfer dosrannu JSON a chynhyrchu swp i'w drosglwyddo i'r model o dan y sbwyliwr. Yn gyntaf, ychwanegwch gyfeiriadur prosiect lleol i chwilio am ffeiliau pennawd (angen ndjson):
// [[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;
}
Dylid gosod y cod hwn yn y ffeil src/cv_xt.cpp a chrynhoi gyda'r gorchymyn Rcpp::sourceCpp(file = "src/cv_xt.cpp", env = .GlobalEnv); hefyd yn ofynnol ar gyfer gwaith nlohmann/json.hpp o ystorfa. Rhennir y cod yn sawl swyddogaeth:
to_xt — swyddogaeth dempled ar gyfer trawsnewid matrics delwedd (cv::Mat) i tensor xt::xtensor;
parse_json — mae'r ffwythiant yn dosrannu llinyn JSON, yn echdynnu cyfesurynnau pwyntiau, gan eu pacio mewn fector;
ocv_draw_lines — o'r fector pwyntiau canlyniadol, yn tynnu llinellau amryliw;
process — yn cyfuno'r swyddogaethau uchod a hefyd yn ychwanegu'r gallu i raddfa'r ddelwedd ganlyniadol;
cpp_process_json_str - deunydd lapio dros y swyddogaeth process, sy'n allforio'r canlyniad i R-gwrthrych (arae aml-ddimensiwn);
cpp_process_json_vector - deunydd lapio dros y swyddogaeth cpp_process_json_str, sy'n eich galluogi i brosesu fector llinyn mewn modd aml-edau.
I dynnu llinellau aml-liw, defnyddiwyd y model lliw HSV, ac yna trosi i RGB. Gadewch i ni brofi'r canlyniad:
Fel y gallwch weld, roedd y cynnydd mewn cyflymder yn arwyddocaol iawn, ac nid yw'n bosibl dal i fyny â chod C ++ trwy gyfochrog â chod R.
3. Iterators ar gyfer dadlwytho sypiau o'r gronfa ddata
Mae gan R enw haeddiannol fel iaith ar gyfer prosesu data sy'n cyd-fynd â RAM, tra bod Python yn cael ei nodweddu'n fwy gan brosesu data ailadroddus, sy'n eich galluogi i weithredu cyfrifiadau y tu allan i'r craidd yn hawdd ac yn naturiol (cyfrifiadau gan ddefnyddio cof allanol). Enghraifft glasurol a pherthnasol i ni yng nghyd-destun y broblem a ddisgrifiwyd yw rhwydweithiau niwral dwfn a hyfforddwyd gan y dull disgyniad graddiant gyda brasamcan o'r graddiant ar bob cam gan ddefnyddio cyfran fechan o arsylwadau, neu swp bach.
Mae gan fframweithiau dysgu dwfn a ysgrifennwyd yn Python ddosbarthiadau arbennig sy'n gweithredu iterwyr yn seiliedig ar ddata: tablau, lluniau mewn ffolderi, fformatau deuaidd, ac ati Gallwch ddefnyddio opsiynau parod neu ysgrifennu eich rhai eich hun ar gyfer tasgau penodol. Yn R gallwn fanteisio ar holl nodweddion y llyfrgell Python keras gyda'i backends amrywiol gan ddefnyddio'r pecyn o'r un enw, sydd yn ei dro yn gweithio ar ben y pecyn tawelu. Mae'r olaf yn haeddu erthygl hir ar wahân; mae nid yn unig yn caniatáu ichi redeg cod Python o R, ond mae hefyd yn caniatáu ichi drosglwyddo gwrthrychau rhwng sesiynau R a Python, gan berfformio'r holl drawsnewidiadau math angenrheidiol yn awtomatig.
Cawsom wared ar yr angen i storio'r holl ddata yn RAM trwy ddefnyddio MonetDBLite, bydd yr holl waith “rhwydwaith niwral” yn cael ei berfformio gan y cod gwreiddiol yn Python, mae'n rhaid i ni ysgrifennu iterator dros y data, gan nad oes dim yn barod ar gyfer sefyllfa o'r fath naill ai yn R neu Python. Yn y bôn, dim ond dau ofyniad sydd ar ei gyfer: rhaid iddo ddychwelyd sypiau mewn dolen ddiddiwedd ac arbed ei gyflwr rhwng iteriadau (mae'r olaf yn R yn cael ei weithredu yn y ffordd symlaf gan ddefnyddio cau). Yn flaenorol, roedd yn ofynnol trosi araeau R yn araeau numpy y tu mewn i'r iterator yn benodol, ond fersiwn gyfredol y pecyn keras yn ei wneud ei hun.
Daeth yr iterator ar gyfer data hyfforddi a dilysu fel a ganlyn:
Iterator ar gyfer hyfforddiant a data dilysu
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)
}
}
Mae'r ffwythiant yn cymryd fel mewnbwn newidyn gyda chysylltiad i'r gronfa ddata, nifer y llinellau a ddefnyddiwyd, nifer y dosbarthiadau, maint swp, graddfa (scale = 1 yn cyfateb i ddelweddau rendro o 256x256 picsel, scale = 0.5 — 128x128 picsel), dangosydd lliw (color = FALSE yn pennu rendrad mewn graddlwyd pan gaiff ei ddefnyddio color = TRUE caiff pob strôc ei dynnu mewn lliw newydd) a dangosydd rhagbrosesu ar gyfer rhwydweithiau sydd wedi'u hyfforddi ymlaen llaw ar imagenet. Mae angen yr olaf er mwyn graddio gwerthoedd picsel o'r cyfwng [0, 1] i'r cyfwng [-1, 1], a ddefnyddiwyd wrth hyfforddi'r cyfwng a gyflenwir keras modelau.
Mae'r swyddogaeth allanol yn cynnwys gwirio math dadl, tabl data.table gyda rhifau llinell cymysg ar hap o samples_index a rhifau swp, rhifydd ac uchafswm nifer y sypiau, yn ogystal â mynegiad SQL ar gyfer dadlwytho data o'r gronfa ddata. Yn ogystal, fe wnaethom ddiffinio analog cyflym o'r swyddogaeth y tu mewn keras::to_categorical(). Fe wnaethon ni ddefnyddio bron yr holl ddata ar gyfer hyfforddiant, gan adael hanner y cant i'w ddilysu, felly roedd maint yr epoc wedi'i gyfyngu gan y paramedr steps_per_epoch pan y'i gelwir keras::fit_generator(), a'r cyflwr if (i > max_i) gweithio ar gyfer yr iterator dilysu yn unig.
Yn y ffwythiant mewnol, caiff mynegeion rhes eu hadalw ar gyfer y swp nesaf, dadlwythir cofnodion o'r gronfa ddata gyda'r rhifydd swp yn cynyddu, dosrannu JSON (swyddogaeth cpp_process_json_vector(), wedi'i ysgrifennu yn C++) a chreu araeau sy'n cyfateb i luniau. Yna fectorau un-poeth gyda labeli dosbarth yn cael eu creu, araeau gyda gwerthoedd picsel a labeli yn cael eu cyfuno i mewn i restr, sef y gwerth dychwelyd. Er mwyn cyflymu gwaith, fe wnaethom ddefnyddio creu mynegeion mewn tablau data.table ac addasu trwy'r ddolen - heb y pecyn “sglodion” hyn data.tabl Mae'n eithaf anodd dychmygu gweithio'n effeithiol gydag unrhyw swm sylweddol o ddata yn R.
Mae canlyniadau mesuriadau cyflymder ar liniadur Craidd i5 fel a ganlyn:
Os oes gennych chi ddigon o RAM, gallwch chi gyflymu gweithrediad y gronfa ddata o ddifrif trwy ei drosglwyddo i'r un RAM hwn (mae 32 GB yn ddigon ar gyfer ein tasg). Yn Linux, mae'r rhaniad wedi'i osod yn ddiofyn /dev/shm, meddiannu hyd at hanner y capasiti RAM. Gallwch amlygu mwy trwy olygu /etc/fstabi gael record fel tmpfs /dev/shm tmpfs defaults,size=25g 0 0. Gwnewch yn siŵr eich bod yn ailgychwyn a gwirio'r canlyniad trwy redeg y gorchymyn df -h.
Mae'r iterator ar gyfer data prawf yn edrych yn llawer symlach, gan fod set ddata'r prawf yn ffitio'n gyfan gwbl i RAM:
Iterator ar gyfer data prawf
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. Detholiad o bensaernïaeth fodel
Y bensaernïaeth gyntaf a ddefnyddiwyd oedd symudolnet v1, y mae nodweddion y rhain yn cael eu trafod yn hyn neges. Mae wedi'i gynnwys fel safon keras ac, yn unol â hynny, ar gael yn y pecyn o'r un enw ar gyfer R. Ond wrth geisio ei ddefnyddio gyda delweddau un sianel, trodd peth rhyfedd allan: rhaid i'r tensor mewnbwn bob amser fod â'r dimensiwn (batch, height, width, 3), hynny yw, ni ellir newid nifer y sianeli. Nid oes unrhyw gyfyngiad o'r fath yn Python, felly fe wnaethom ruthro ac ysgrifennu ein gweithrediad ein hunain o'r bensaernïaeth hon, gan ddilyn yr erthygl wreiddiol (heb y cwymp sydd yn fersiwn keras):
Mae anfanteision y dull hwn yn amlwg. Rwyf am brofi llawer o fodelau, ond i'r gwrthwyneb, nid wyf am ailysgrifennu pob pensaernïaeth â llaw. Cawsom hefyd ein hamddifadu o'r cyfle i ddefnyddio pwysau modelau a hyfforddwyd ymlaen llaw ar imagenet. Yn ôl yr arfer, roedd astudio'r ddogfennaeth o gymorth. Swyddogaeth get_config() caniatáu i chi gael disgrifiad o'r model mewn ffurf sy'n addas i'w olygu (base_model_conf$layers - rhestr R rheolaidd), a'r swyddogaeth from_config() yn perfformio'r trawsnewidiad cefn i wrthrych model:
Nawr nid yw'n anodd ysgrifennu swyddogaeth gyffredinol i gael dim o'r cyflenwad a gyflenwir keras modelau gyda neu heb bwysau wedi'u hyfforddi ar imagenet:
Swyddogaeth ar gyfer llwytho pensaernïaeth parod
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)
}
Wrth ddefnyddio delweddau un sianel, ni ddefnyddir unrhyw bwysau wedi'u hyfforddi ymlaen llaw. Gallai hyn fod yn sefydlog: defnyddio'r swyddogaeth get_weights() cael y pwysau model ar ffurf rhestr o araeau R, newid dimensiwn elfen gyntaf y rhestr hon (trwy gymryd un sianel lliw neu gyfartaleddu'r tri), ac yna llwythwch y pwysau yn ôl i'r model gyda'r swyddogaeth set_weights(). Ni wnaethom byth ychwanegu'r swyddogaeth hon, oherwydd ar hyn o bryd roedd yn amlwg eisoes ei bod yn fwy cynhyrchiol gweithio gyda lluniau lliw.
Fe wnaethom gynnal y rhan fwyaf o'r arbrofion gan ddefnyddio fersiynau mobilenet 1 a 2, yn ogystal ag resnet34. Perfformiodd pensaernïaeth fwy modern fel SE-ResNeXt yn dda yn y gystadleuaeth hon. Yn anffodus, nid oedd gennym weithrediadau parod ar gael inni, ac ni wnaethom ysgrifennu ein rhai ein hunain (ond byddwn yn bendant yn ysgrifennu).
5. Parameterization o sgriptiau
Er hwylustod, dyluniwyd yr holl god ar gyfer dechrau hyfforddiant fel un sgript, wedi'i baramedroli gan ddefnyddio docopt fel a ganlyn:
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)
Pecyn docopt cynrychioli’r gweithredu http://docopt.org/ ar gyfer R. Gyda'i help, mae sgriptiau'n cael eu lansio gyda gorchmynion syml fel Rscript bin/train_nn.R -m resnet50 -c -d /home/andrey/doodle_db neu ./bin/train_nn.R -m resnet50 -c -d /home/andrey/doodle_db, os ffeil train_nn.R yn weithredadwy (bydd y gorchymyn hwn yn dechrau hyfforddi'r model resnet50 ar ddelweddau tri lliw yn mesur 128x128 picsel, rhaid lleoli'r gronfa ddata yn y ffolder /home/andrey/doodle_db). Gallwch ychwanegu cyflymder dysgu, math optimizer, ac unrhyw baramedrau customizable eraill at y rhestr. Yn y broses o baratoi'r cyhoeddiad, mae'n troi allan bod y bensaernïaeth mobilenet_v2 o'r fersiwn gyfredol keras mewn defnydd R Ni ddylai oherwydd newidiadau na chymerwyd i ystyriaeth yn y pecyn R, rydym yn aros iddynt ei drwsio.
Roedd y dull hwn yn ei gwneud hi'n bosibl cyflymu arbrofion yn sylweddol gyda modelau gwahanol o gymharu â lansiad mwy traddodiadol sgriptiau yn RStudio (nodwn y pecyn fel dewis arall posibl tfruns). Ond y brif fantais yw'r gallu i reoli lansiad sgriptiau yn Docker yn hawdd neu'n syml ar y gweinydd, heb osod RStudio ar gyfer hyn.
6. Dockerization o sgriptiau
Fe wnaethom ddefnyddio Docker i sicrhau hygludedd yr amgylchedd ar gyfer modelau hyfforddi rhwng aelodau'r tîm ac ar gyfer eu defnyddio'n gyflym yn y cwmwl. Gallwch chi ddechrau dod yn gyfarwydd â'r offeryn hwn, sy'n gymharol anarferol i raglennydd R, gyda hwn cyfres o gyhoeddiadau neu cwrs fideo.
Mae Docker yn caniatáu ichi greu eich delweddau eich hun o'r dechrau a defnyddio delweddau eraill fel sail ar gyfer creu rhai eich hun. Wrth ddadansoddi'r opsiynau sydd ar gael, daethom i'r casgliad bod gosod gyrwyr NVIDIA, CUDA + cuDNN a llyfrgelloedd Python yn rhan eithaf swmpus o'r ddelwedd, a phenderfynom gymryd y ddelwedd swyddogol fel sail. tensorflow/tensorflow:1.12.0-gpu, gan ychwanegu'r pecynnau R angenrheidiol yno.
Er hwylustod, rhoddwyd y pecynnau a ddefnyddiwyd mewn newidynnau; mae mwyafrif y sgriptiau ysgrifenedig yn cael eu copïo y tu mewn i'r cynwysyddion yn ystod y gwasanaeth. Fe wnaethom hefyd newid y plisgyn gorchymyn i /bin/bash er hwylustod defnyddio cynnwys /etc/os-release. Roedd hyn yn osgoi'r angen i nodi'r fersiwn OS yn y cod.
Yn ogystal, ysgrifennwyd sgript bash fach sy'n eich galluogi i lansio cynhwysydd gyda gorchmynion amrywiol. Er enghraifft, gallai'r rhain fod yn sgriptiau ar gyfer hyfforddi rhwydweithiau niwral a osodwyd yn flaenorol y tu mewn i'r cynhwysydd, neu gragen orchymyn ar gyfer dadfygio a monitro gweithrediad y cynhwysydd:
Sgript i lansio'r cynhwysydd
#!/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}
Os yw'r sgript bash hon yn cael ei redeg heb baramedrau, bydd y sgript yn cael ei alw y tu mewn i'r cynhwysydd train_nn.R gyda gwerthoedd diofyn; os mai "bash" yw'r ddadl leoliadol gyntaf, yna bydd y cynhwysydd yn dechrau'n rhyngweithiol gyda chragen gorchymyn. Ym mhob achos arall, amnewidir gwerthoedd dadleuon lleoliadol: CMD="Rscript /app/train_nn.R $@".
Mae'n werth nodi bod y cyfeiriaduron gyda data ffynhonnell a chronfa ddata, yn ogystal â'r cyfeiriadur ar gyfer arbed modelau hyfforddedig, wedi'u gosod y tu mewn i'r cynhwysydd o'r system westeiwr, sy'n eich galluogi i gael mynediad at ganlyniadau'r sgriptiau heb driniaethau diangen.
7. Defnyddio GPUs lluosog ar Google Cloud
Un o nodweddion y gystadleuaeth oedd y data swnllyd iawn (gweler y llun teitl, wedi ei fenthyg o @Leigh.plt o ODS slack). Mae sypiau mawr yn helpu i frwydro yn erbyn hyn, ac ar ôl arbrofion ar gyfrifiadur personol gydag 1 GPU, fe benderfynon ni feistroli modelau hyfforddi ar sawl GPU yn y cwmwl. Wedi defnyddio GoogleCloud (arweiniad da i'r pethau sylfaenol) oherwydd y dewis mawr o gyfluniadau sydd ar gael, prisiau rhesymol a bonws o $300. Allan o drachwant, archebais enghraifft 4xV100 gyda SSD a thunnell o RAM, ac roedd hynny'n gamgymeriad mawr. Mae peiriant o'r fath yn bwyta arian yn gyflym; gallwch chi fynd i arbrofi heb unrhyw biblinell brofedig. At ddibenion addysgol, mae'n well cymryd y K80. Ond daeth y swm mawr o RAM yn ddefnyddiol - ni wnaeth yr SSD cwmwl argraff ar ei berfformiad, felly trosglwyddwyd y gronfa ddata i dev/shm.
O ddiddordeb mwyaf yw'r darn cod sy'n gyfrifol am ddefnyddio GPUs lluosog. Yn gyntaf, mae'r model yn cael ei greu ar y CPU gan ddefnyddio rheolwr cyd-destun, yn union fel yn Python:
Ni ellid gweithredu'r dechneg glasurol o rewi pob haen ac eithrio'r un olaf, hyfforddi'r haen olaf, dadrewi ac ailhyfforddi'r model cyfan ar gyfer sawl GPU.
Roedd hyfforddiant yn cael ei fonitro heb ei ddefnyddio. bwrdd tensor, gan gyfyngu ein hunain i gofnodi logiau ac arbed modelau gydag enwau llawn gwybodaeth ar ôl pob cyfnod:
Nid yw nifer o broblemau yr ydym wedi dod ar eu traws wedi’u goresgyn eto:
в keras nid oes swyddogaeth parod ar gyfer chwilio'n awtomatig am y gyfradd ddysgu optimaidd (analog lr_finder yn y llyfrgell cyflym.ai); Gyda pheth ymdrech, mae'n bosibl trosglwyddo gweithrediadau trydydd parti i R, er enghraifft, hyn;
o ganlyniad i'r pwynt blaenorol, nid oedd yn bosibl dewis y cyflymder hyfforddi cywir wrth ddefnyddio sawl GPU;
mae diffyg saernïaeth rhwydwaith niwral modern, yn enwedig y rhai sydd wedi'u hyfforddi ymlaen llaw ar imagenet;
polisi dim un cylch a chyfraddau dysgu gwahaniaethol (roedd anelio cosin ar ein cais ni gweithredu, diolch skeydan).
Pa bethau defnyddiol a ddysgwyd o’r gystadleuaeth hon:
Ar galedwedd pŵer cymharol isel, gallwch weithio gyda chyfeintiau data gweddus (llawer gwaith maint RAM) heb boen. Bag plastig data.tabl yn arbed cof oherwydd addasu tablau yn eu lle, sy'n osgoi eu copïo, a phan gânt eu defnyddio'n gywir, mae ei alluoedd bron bob amser yn dangos y cyflymder uchaf ymhlith yr holl offer sy'n hysbys i ni ar gyfer ieithoedd sgriptio. Mae arbed data mewn cronfa ddata yn caniatáu ichi, mewn llawer o achosion, beidio â meddwl o gwbl am yr angen i wasgu'r set ddata gyfan i RAM.
Gellir disodli swyddogaethau araf yn R â rhai cyflym yn C ++ gan ddefnyddio'r pecyn Rcpp. Os yn ychwanegol at ddefnydd RcppThread neu RcppParallel, rydym yn cael gweithrediadau traws-lwyfan aml-edau, felly nid oes angen i parallelize y cod ar y lefel R.
Pecyn Rcpp Gellir ei ddefnyddio heb wybodaeth ddifrifol am C++, amlinellir yr isafswm gofynnol yma. Ffeiliau pennawd ar gyfer nifer o lyfrgelloedd C cŵl fel xtensor ar gael ar CRAN, hynny yw, mae seilwaith yn cael ei ffurfio ar gyfer gweithredu prosiectau sy'n integreiddio cod C ++ perfformiad uchel parod yn R. Cyfleustra ychwanegol yw tynnu sylw at gystrawen a dadansoddwr cod C ++ statig yn RStudio.
docopt yn eich galluogi i redeg sgriptiau hunangynhwysol gyda pharamedrau. Mae hwn yn gyfleus i'w ddefnyddio ar weinydd pell, gan gynnwys. dan docker. Yn RStudio, mae'n anghyfleus cynnal oriau lawer o arbrofion gyda hyfforddi rhwydweithiau niwral, ac nid yw gosod y DRhA ar y gweinydd ei hun bob amser yn gyfiawn.
Mae Docker yn sicrhau hygludedd cod ac atgynhyrchu canlyniadau rhwng datblygwyr â gwahanol fersiynau o'r OS a llyfrgelloedd, yn ogystal â rhwyddineb gweithredu ar weinyddion. Gallwch chi lansio'r biblinell hyfforddi gyfan gydag un gorchymyn yn unig.
Mae Google Cloud yn ffordd gyfeillgar i'r gyllideb i arbrofi ar galedwedd drud, ond mae angen i chi ddewis ffurfweddiadau yn ofalus.
Mae mesur cyflymder darnau cod unigol yn ddefnyddiol iawn, yn enwedig wrth gyfuno R a C ++, a gyda'r pecyn mainc - hefyd yn hawdd iawn.
Ar y cyfan, roedd y profiad hwn yn werth chweil ac rydym yn parhau i weithio i ddatrys rhai o'r materion a godwyd.