1. Маълумотро аз CSV ба базаи MonetDB самаранок бор кунед
Маълумот дар ин озмун на дар шакли тасвирҳои тайёр, балки дар шакли 340 файли CSV (як файл барои ҳар як синф) дорои JSON-ҳо бо координатҳои нуқтаҳо пешниҳод карда мешаванд. Бо пайваст кардани ин нуқтаҳо бо хатҳо, мо тасвири ниҳоии андозаи 256x256 пиксел мегирем. Инчунин барои ҳар як сабт нишонае мавҷуд аст, ки нишон медиҳад, ки оё расм аз ҷониби таснифоте, ки дар вақти ҷамъоварии маълумот истифода шудааст, дуруст эътироф шудааст ё не, рамзи ду ҳарфӣ аз кишвари истиқомати муаллифи расм, идентификатори беназир, тамғаи вақт ва номи синф, ки ба номи файл мувофиқат мекунад. Вазни нусхаи соддакардашудаи маълумоти аслӣ дар бойгонӣ 7.4 ГБ ва пас аз кушодан тақрибан 20 ГБ аст, маълумоти пурра пас аз кушодан 240 ГБ-ро мегирад. Созмондиҳандагон кафолат доданд, ки ҳарду версия як расмҳоро такрор мекунанд, яъне версияи пурраи он зиёдатист. Дар ҳар сурат, нигоҳ доштани 50 миллион тасвир дар файлҳои графикӣ ё дар шакли массивҳо фавран зиёновар ҳисобида шуд ва мо тасмим гирифтем, ки ҳамаи файлҳои CSV-ро аз бойгонӣ якҷоя кунем. train_simplified.zip ба махзани маълумот бо тавлиди минбаъдаи тасвирҳои андозаи зарурии "дар парвоз" барои ҳар як партия.
Системаи хуб исботшуда ҳамчун DBMS интихоб карда шуд MonetDB, яъне татбиқи R ҳамчун баста MonetDBLite. Маҷмӯа версияи дохилии сервери пойгоҳи додаҳоро дар бар мегирад ва ба шумо имкон медиҳад, ки серверро мустақиман аз сеанси R гиред ва бо он дар он ҷо кор кунед. Эҷоди пойгоҳи додаҳо ва пайвастшавӣ ба он бо як фармон иҷро карда мешавад:
con <- DBI::dbConnect(drv = MonetDBLite::MonetDBLite(), Sys.getenv("DBDIR"))
Ба мо лозим меояд, ки ду ҷадвал эҷод кунем: яке барои ҳама маълумот, дигаре барои маълумоти хидматрасонӣ дар бораи файлҳои зеркашида (фоиданок аст, агар ягон хатогӣ рӯй диҳад ва пас аз зеркашии якчанд файл раванд дубора оғоз карда шавад):
Роҳи зудтарини боркунии маълумот ба пойгоҳи додаҳо ин нусхабардории мустақиман файлҳои CSV бо истифода аз фармони SQL буд COPY OFFSET 2 INTO tablename FROM path USING DELIMITERS ',','n','"' NULL AS '' BEST EFFORTки дар tablename - номи ҷадвал ва path - роҳ ба файл. Ҳангоми кор бо архив маълум шуд, ки татбиқи дарунсохт unzip дар R бо як қатор файлҳои бойгонӣ дуруст кор намекунад, бинобар ин мо системаро истифода бурдем unzip (бо истифода аз параметр getOption("unzip")).
Функсияи навиштан ба базаи маълумот
#' @title Извлечение и загрузка файлов
#'
#' @description
#' Извлечение CSV-файлов из ZIP-архива и загрузка их в базу данных
#'
#' @param con Объект подключения к базе данных (класс `MonetDBEmbeddedConnection`).
#' @param tablename Название таблицы в базе данных.
#' @oaram zipfile Путь к ZIP-архиву.
#' @oaram filename Имя файла внури ZIP-архива.
#' @param preprocess Функция предобработки, которая будет применена извлечённому файлу.
#' Должна принимать один аргумент `data` (объект `data.table`).
#'
#' @return `TRUE`.
#'
upload_file <- function(con, tablename, zipfile, filename, preprocess = NULL) {
# Проверка аргументов
checkmate::assert_class(con, "MonetDBEmbeddedConnection")
checkmate::assert_string(tablename)
checkmate::assert_string(filename)
checkmate::assert_true(DBI::dbExistsTable(con, tablename))
checkmate::assert_file_exists(zipfile, access = "r", extension = "zip")
checkmate::assert_function(preprocess, args = c("data"), null.ok = TRUE)
# Извлечение файла
path <- file.path(tempdir(), filename)
unzip(zipfile, files = filename, exdir = tempdir(),
junkpaths = TRUE, unzip = getOption("unzip"))
on.exit(unlink(file.path(path)))
# Применяем функция предобработки
if (!is.null(preprocess)) {
.data <- data.table::fread(file = path)
.data <- preprocess(data = .data)
data.table::fwrite(x = .data, file = path, append = FALSE)
rm(.data)
}
# Запрос к БД на импорт CSV
sql <- sprintf(
"COPY OFFSET 2 INTO %s FROM '%s' USING DELIMITERS ',','n','"' NULL AS '' BEST EFFORT",
tablename, path
)
# Выполнение запроса к БД
DBI::dbExecute(con, sql)
# Добавление записи об успешной загрузке в служебную таблицу
DBI::dbExecute(con, sprintf("INSERT INTO upload_log(file_name, uploaded) VALUES('%s', true)",
filename))
return(invisible(TRUE))
}
Агар ба шумо лозим аст, ки ҷадвалро пеш аз навиштани он ба базаи маълумот табдил диҳед, дар аргумент гузаштан кифоя аст preprocess функсияе, ки маълумотро тағир медиҳад.
Код барои пай дар пай бор кардани маълумот ба базаи маълумот:
Навиштани маълумот ба базаи маълумот
# Список файлов для записи
files <- unzip(zipfile, list = TRUE)$Name
# Список исключений, если часть файлов уже была загружена
to_skip <- DBI::dbGetQuery(con, "SELECT file_name FROM upload_log")[[1L]]
files <- setdiff(files, to_skip)
if (length(files) > 0L) {
# Запускаем таймер
tictoc::tic()
# Прогресс бар
pb <- txtProgressBar(min = 0L, max = length(files), style = 3)
for (i in seq_along(files)) {
upload_file(con = con, tablename = "doodles",
zipfile = zipfile, filename = files[i])
setTxtProgressBar(pb, i)
}
close(pb)
# Останавливаем таймер
tictoc::toc()
}
# 526.141 sec elapsed - копирование SSD->SSD
# 558.879 sec elapsed - копирование USB->SSD
Вақти боркунии маълумот вобаста ба хусусиятҳои суръати диски истифодашаванда метавонад фарқ кунад. Дар ҳолати мо, хондан ва навиштан дар дохили як SSD ё аз диски флеш (файли манбаъ) ба SSD (DB) камтар аз 10 дақиқа вақтро мегирад.
Барои сохтани сутун бо нишони синфи бутун ва сутуни индекс (ORDERED INDEX) бо рақамҳои сатр, ки аз рӯи онҳо мушоҳидаҳо ҳангоми ташкили партияҳо интихоб карда мешаванд:
Эҷоди сутунҳо ва индекси иловагӣ
message("Generate lables")
invisible(DBI::dbExecute(con, "ALTER TABLE doodles ADD label_int int"))
invisible(DBI::dbExecute(con, "UPDATE doodles SET label_int = dense_rank() OVER (ORDER BY word) - 1"))
message("Generate row numbers")
invisible(DBI::dbExecute(con, "ALTER TABLE doodles ADD id serial"))
invisible(DBI::dbExecute(con, "CREATE ORDERED INDEX doodles_id_ord_idx ON doodles(id)"))
Барои ҳалли масъалаи эҷоди партия дар парвоз, ба мо лозим буд, ки суръати максималии истихроҷи сатрҳои тасодуфиро аз ҷадвал ба даст орем. doodles. Барои ин мо аз 3 ҳилла истифода бурдем. Аввалан, кам кардани андозагирии намуде буд, ки ID-и мушоҳидаро нигоҳ медорад. Дар маҷмӯи маълумоти аслӣ, навъи лозим барои нигоҳ доштани ID аст bigint, аммо шумораи мушоҳидаҳо имкон медиҳад, ки идентификаторҳои онҳо, ки ба рақами тартиби баробаранд, ба намуд мувофиқ карда шаванд. int. Дар ин ҳолат ҷустуҷӯ хеле тезтар аст. Ҳиллаи дуюм истифода бурдан буд ORDERED INDEX — мо тамоми имко-ниятхоро аз назар гузаронда, ба таври тачрибавй ба ин карор омадем имконоти. Сеюм ин истифодаи дархостҳои параметрӣ буд. Мохияти усул як маротиба ичро кардани фармон аст PREPARE бо истифодаи минбаъдаи ифодаи омодашуда ҳангоми эҷоди як қатор дархостҳои як навъ, аммо дар асл дар муқоиса бо як оддӣ бартарӣ вуҷуд дорад SELECT дар доираи хатохои статистикй баромад.
Дар доираи рақобат байни ядроҳои Python, мушкилот пеш аз ҳама бо истифода аз он ҳал карда шуданд Опенчв. Яке аз аналогҳои соддатарин ва возеҳтарин дар R чунин хоҳад буд:
Татбиқи JSON ба табдили тензор дар 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)
}
Тарҳ бо истифода аз асбобҳои стандартии R иҷро карда мешавад ва дар PNG-и муваққатии дар RAM захирашуда захира карда мешавад (дар Linux, директорияҳои муваққатии R дар директория ҷойгиранд. /tmp, дар RAM насб карда шудааст). Сипас ин файл ҳамчун массиви сеченака бо рақамҳои аз 0 то 1 хонда мешавад. Ин муҳим аст, зеро BMP маъмултар ба массиви хом бо рамзҳои ранги шонздаҳӣ хонда мешавад.
Ин татбиқ барои мо ғайримуқаррарӣ менамуд, зеро ташаккули порчаҳои калон вақти бениҳоят тӯлонӣ мегирад ва мо тасмим гирифтем, ки аз таҷрибаи ҳамкасбони худ бо истифода аз китобхонаи пурқувват истифода барем. Опенчв. Дар он вақт бастаи тайёр барои R вуҷуд надошт (ҳоло вуҷуд надорад), бинобар ин татбиқи ҳадди ақали функсияҳои зарурӣ дар C++ бо ҳамгироӣ ба рамзи R бо истифода аз Rcpp.
Барои ҳалли мушкилот, бастаҳо ва китобхонаҳои зерин истифода шуданд:
Опенчв барои кор бо тасвирҳо ва хатҳои кашидан. Китобхонаҳои система ва файлҳои сарлавҳаи қаблан насбшуда, инчунин пайвасти динамикӣ истифода мешаванд.
кстензор барои кор бо массивҳои бисёрченака ва тензорҳо. Мо файлҳои сарлавҳаро истифода бурдем, ки дар бастаи R бо ҳамон ном дохил карда шудаанд. Китобхона ба шумо имкон медиҳад, ки бо массивҳои бисёрченака ҳам дар сатри асосӣ ва ҳам дар сутуни асосӣ кор кунед.
нджсон барои таҳлили 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 аз он анбор. Рамз ба якчанд вазифаҳо тақсим мешавад:
Машгулият бе истифода назорат карда шуд. тензорборд, худро бо сабти гузоришҳо ва захира кардани моделҳо бо номҳои иттилоотӣ пас аз ҳар як давра маҳдуд мекунем: