Перейти до основного вмісту

Передавання масивів 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 на тензор, що зберігається в пам’яті ЦП.

tutorials/009_pass_numpy_to_model/pass_numpy_to_model.cpp
// 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. Це представлення даних у пам’яті тензора, без копіювання, тому в прикладі можна безпосередньо обчислити контрольну суму перших байтів.

tutorials/009_pass_numpy_to_model/pass_numpy_to_model.cpp
// map_read yields a Mapping with a raw pointer and size in bytes.
simaai::neat::Mapping mapped = tensor.map_read();

Отримання власної копії​

Нарешті, створіть дані, які повністю відокремлені від початкового буфера джерела, і їх можна безпечно зберігати після того, як вхідні дані будуть видалені. Це копія, яку ви передаєте довготривалим споживачам.

tensor.clone() копіює дані в нову область пам’яті, що належить ЦП, незалежно від cv::Mat, з якої вони були отримані.

tutorials/009_pass_numpy_to_model/pass_numpy_to_model.cpp
// 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 не виконує неявне приведення типів даних — перед передачею даних узгодьте тип даних тензора з контрактом вхідних даних моделі.

Повний початковий код​

Показати повні програми
tutorials/009_pass_numpy_to_model/pass_numpy_to_model.cpp
// 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;
}
}

Джерело​