Передавання масивів NumPy до моделі
| Поле | Значення |
|---|---|
| Категорія | Моделі та інференс |
| Складність | Середній |
| Орієнтовний час читання | 10-15 minutes |
| Мітки | numpy, pytorch, tensor, io |
Якщо ви інтегруєте Neat в існуючий стек для виконання висновків, це межа взаємодії, яка вам потрібна: як дані хоста перетворюються на Neat Tensor, і як Neat Tensor знову перетворюється на дані хоста. Правильна реалізація на початковому етапі запобігає типовим помилкам інтеграції — неправильне розташування, непомітна примусова зміна типу даних, непередбачуване створення псевдонімів між двома середовищами.
Тут також дві мови найбільше розходяться. Користувачі Python використовують NumPy/PyTorch; користувачі C++ використовують OpenCV. Концепції перетворення ідентичні, але назви та типи API відрізняються, тому наведений нижче опис для кожної мови має значення. В результаті ви перетворите дані хоста на Neat тензор, перевірите його корисне навантаження без копіювання та створите копію, якою можна безпечно користуватися після звільнення вихідного буфера.
Покроковий огляд
Оберніть дані хоста у тензор
Перший крок перетворює дані, які у вас вже є, на Neat Tensor. Ви явно вказуєте розміщення зображення (RGB), щоб середовище виконання правильно інтерпретувало байти, а не робило припущення. copy=True (або вибір пам’яті ЦП у C++) визначає, чи володіє тензор своїми байтами, чи використовує псевдонім вихідного джерела — явне володіння є безпечним варіантом за замовчуванням, коли вихідний буфер може бути змінений або звільнений.
simaai::neat::from_cv_mat(mat, ImageSpec::PixelFormat::RGB, TensorMemory::CPU) перетворю є cv::Mat на тензор, що зберігається в пам’яті ЦП.
// from_cv_mat converts a cv::Mat into a CPU-backed Neat Tensor.
simaai::neat::Tensor tensor = simaai::neat::from_cv_mat(
rgb, simaai::neat::ImageSpec::PixelFormat::RGB, simaai::neat::TensorMemory::CPU);
Перевірка корисного навантаження
Після того, як дані перетворено на тензор, їх можна знову зчитати. Це друга половина процесу взаємодії: перед передачею даних далі необхідно підтвердити, чи збереглися форма та кількість байтів після перетворення.
tensor.map_read() повертає Mapping, що надає доступ до вказівника на необроблені data та size_bytes. Це представлення даних у пам’яті тензора, без копіювання, тому в прикладі можна безпосередньо обчислити контрольну суму перших байтів.
// map_read yields a Mapping with a raw pointer and size in bytes.
simaai::neat::Mapping mapped = tensor.map_read();
Отримання власної копії
Нарешті, створіть дані, які повністю відокремлені від початкового буфера джерела, і їх можна безпечно зберігати після того, як вхідні дані будуть видалені. Це копія, яку ви передаєте довготривалим споживачам.
tensor.clone() копіює дані в нову область пам’яті, що належить ЦП, незалежно від cv::Mat, з якої вони були отримані.
// clone() copies into CPU-owned storage, detached from the cv::Mat buffer.
simaai::neat::Tensor owned = tensor.clone();
if (owned.dense_bytes_tight() == 0U)
throw std::runtime_error("cloned tensor is empty");
Запуск
Запустіть команди Python і C++ (попередньо зібрані) з кореневої теки встановлення Neat (теки, яка містить share/ і lib/); запустіть команди збірка з вихідного коду з кореневої теки репозиторію. Для цього розділу не потрібен архів моделі.
C++ (prebuilt):
./lib/sima-neat/tutorials/tutorial_009_pass_numpy_to_model \
--width 128 --height 96
C++ (build from source):
./build.sh --target tutorial_009_pass_numpy_to_model
./build/tutorials-standalone/tutorial_009_pass_numpy_to_model \
--width 128 --height 96
Очікуваний результат (C++):
tensor_rank=3
tensor_bytes=36864
head_checksum=4342
clone_bytes=36864
[OK] 009_pass_numpy_to_model
Очікуваний результат (Python, якщо встановлено torch):
numpy_roundtrip_shape=(96, 128, 3)
torch_roundtrip_shape=(96, 128, 3)
(Якщо torch не встановлено, під час збірки Python буде виведено torch_roundtrip_skipped=True замість рядка torch.) Щоб інтегрувати вихідний код C++ цього розділу у власний проєкт за допомогою спеціального файлу CMakeLists.txt (додаткова тека не потрібна), див. розділ Як запускати навчальні матеріали на головній сторінці.
На практиці
Поверхня взаємодії, узагальнена для швидкого довідника після завершення демо нстрації циклічного обміну даними.
API перетворення
- NumPy:
pyneat.Tensor.from_numpy(array, copy=..., image_format=...)(вхідні дані);tensor.to_numpy(copy=...)(вихідні дані). - PyTorch:
pyneat.Tensor.from_torch(tensor, copy=..., image_format=...)(вхідні дані);tensor.to_torch(copy=...)(вихідні дані). - OpenCV (C++):
simaai::neat::from_cv_mat(mat, pixel_format, memory)(вхідні дані);tensor.map_read()для представлення без копіювання;tensor.clone()для створення незалежної копії.
Копіювання проти представлення
copy=True(Python) /clone()(C++) надає дані, відокремлені від джерела, що дозволяє безпечно зберігати їх після звільнення або зміни джерела.copy=False/map_read()надає представлення, яке є псевдонімом джерела. Це д ешевше, але дійсне лише до тих пір, поки джерело існує та не змінюється.
Розташування та тип даних
- Завжди передавайте явний
image_format/PixelFormatдля даних зображення, щоб розташування інтерпретувалося, а не визначалося автоматично. - Neat не виконує неявне приведення типів даних — перед передачею даних узгодьте тип даних тензора з контрактом вхідних даних моделі.
Повний початковий код
Показати повні програми
// Convert a cv::Mat into a Neat Tensor, map it read-only, and clone it.
//
// Usage:
// tutorial_009_pass_numpy_to_model [--width 128] [--height 96]
#include "neat.h"
#include <opencv2/core.hpp>
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <string>
namespace {
bool get_arg(int argc, char** argv, const std::string& key, std::string& out) {
for (int i = 1; i + 1 < argc; ++i) {
if (key == argv[i]) {
out = argv[i + 1];
return true;
}
}
return false;
}
int parse_int_arg(int argc, char** argv, const std::string& key, int def) {
std::string value;
if (!get_arg(argc, argv, key, value))
return def;
return std::stoi(value);
}
} // namespace
int main(int argc, char** argv) {
try {
const int width = parse_int_arg(argc, argv, "--width", 128);
const int height = parse_int_arg(argc, argv, "--height", 96);
cv::Mat rgb(height, width, CV_8UC3, cv::Scalar(7, 17, 27));
if (!rgb.isContinuous())
rgb = rgb.clone();
// CORE LOGIC
// from_cv_mat converts a cv::Mat into a CPU-backed Neat Tensor.
simaai::neat::Tensor tensor = simaai::neat::from_cv_mat(
rgb, simaai::neat::ImageSpec::PixelFormat::RGB, simaai::neat::TensorMemory::CPU);
// map_read yields a Mapping with a raw pointer and size in bytes.
simaai::neat::Mapping mapped = tensor.map_read();
std::uint64_t checksum = 0;
const auto* bytes = static_cast<const std::uint8_t*>(mapped.data);
const std::size_t n = std::min<std::size_t>(mapped.size_bytes, 256);
for (std::size_t i = 0; i < n; ++i)
checksum += bytes[i];
if (tensor.shape.size() != 3U || mapped.size_bytes == 0U)
throw std::runtime_error("mapped tensor is empty or rank is wrong");
// CORE LOGIC
// clone() copies into CPU-owned storage, detached from the cv::Mat buffer.
simaai::neat::Tensor owned = tensor.clone();
if (owned.dense_bytes_tight() == 0U)
throw std::runtime_error("cloned tensor is empty");
std::cout << "tensor_rank=" << tensor.shape.size() << "\n";
std::cout << "tensor_bytes=" << mapped.size_bytes << "\n";
std::cout << "head_checksum=" << checksum << "\n";
std::cout << "clone_bytes=" << owned.dense_bytes_tight() << "\n";
std::cout << "[OK] 009_pass_numpy_to_model\n";
return 0;
} catch (const std::exception& e) {
std::cerr << "[FAIL] " << e.what() << "\n";
return 1;
}
}