Документация Engee
Notebook

Встраиваемый код из свёрточной нейросети

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

Введение

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

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

Подготовка

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

Генерация контролируемого, сбалансированного датасета с известными свойствами (размер 64×64, нормализация в диапазон [-1,1]) позволяет изолированно проверить каждый этап конвейера без влияния внешних факторов.

Установим нужные библиотеки и инициализируем генератор случайных чисел, чтобы наш эксперимент был легко возпроизводимым:

In [ ]:
# Установка необходимых пакетов
neededLibs = ["Flux", "BSON", "ImageTransformations", "Random"]
for lib in neededLibs
    try
        eval(Meta.parse("using $lib"))
    catch ex
        Pkg.add(lib)
        eval(Meta.parse("using $lib"))
    end
end
In [ ]:
Random.seed!(5);

Синтезируем набор данных

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

In [ ]:
include("$(@__DIR__)/_scripts/generate_shape_dataset.jl")
generate_shape_dataset(samples_per_class=200, test_samples=30, img_size=64)
Датасет сгенерирован:
  200 изображений каждого класса для обучения
  30 тестовых изображений
  Размер изображений: 64 x 64

На этом этапе мы стараемся сгенерировать достаточно разнообразный датасет (с поворотами треугольников), но при этом не переусложнять код, например мы не стали делать аугментацию в процессе обучения. В целом, этот этап оказался наименее проблемным.

Взгляд на учебный набор данных

Вот образцы объектов из нашего учебного набора данных:

In [ ]:
include("$(@__DIR__)/_scripts/show_dataset_samples.jl")
DATA_DIR = "$(@__DIR__)/учебные данные";
gr()
show_dataset_samples(DATA_DIR, samples_per_class=10)
Out[0]:
No description has been provided for this image

Обучение и анализ модели

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

Мониторинг метрик precision/recall по классам и ранняя остановка по валидационной точности помогают вовремя обнаружить переобучение и выбрать лучшую модель для последующего экспорта.

In [ ]:
include("$(@__DIR__)/_scripts/train_model.jl");
DATA_DIR = "$(@__DIR__)/учебные данные";
model, classes = train_model(DATA_DIR; epochs=100, imsize=64, batch_size=32, lr=0.0005, test_split=0.25, patience_limit=8);
Размер батча: 32, Скорость обучения: 0.0005
Доля тестовой выборки: 25.0%
Найдено классов: 3: ["квадрат", "круг", "треугольник"]

=== Распределение классов ===

Всего изображений: 600 (64×64)
  квадрат: 200 изображений (33.3%)
  круг: 200 изображений (33.3%)
  треугольник: 200 изображений (33.3%)

=== Разделение данных ===
  Тренировочных: 450 (75.0%)
  Тестовых: 150 (25.0%)
┌ Warning: `Flux.params(m...)` is deprecated. Use `Flux.trainable(model)` for parameter collection,
│ and the explicit `gradient(m -> loss(m, x, y), model)` for gradient computation.
└ @ Flux /opt/depot/demos/packages/Flux/c30bR/src/deprecations.jl:93
Параметров модели: 16035

=== Обучение ===
  Эпоха  1/100, Train Loss: 1.2926, Train Acc: 39.1%, Test Acc: 36.7% ★  (precision/recall по классам: квадрат: 31.7%/40.0%, круг: 46.9%/30.0%, треугольник: 43.6%/48.0%)
  Эпоха  2/100, Train Loss: 1.1301, Train Acc: 50.9%, Test Acc: 43.3% ★  (precision/recall по классам: квадрат: 51.7%/60.0%, круг: 48.8%/42.0%, треугольник: 51.0%/50.0%)
  Эпоха  3/100, Train Loss: 1.0281, Train Acc: 60.7%, Test Acc: 55.3% ★  (precision/recall по классам: квадрат: 49.2%/62.0%, круг: 40.8%/40.0%, треугольник: 52.6%/40.0%)
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
  Эпоха  4/100, Train Loss: 0.9695, Train Acc: 67.6%, Test Acc: 63.3% ★  (precision/recall по классам: квадрат: 60.4%/58.0%, круг: 51.1%/48.0%, треугольник: 60.0%/66.0%)
  Эпоха  5/100, Train Loss: 0.9029, Train Acc: 61.6%, Test Acc: 57.3%    (precision/recall по классам: квадрат: 63.0%/68.0%, круг: 37.0%/34.0%, треугольник: 58.0%/58.0%)
  Эпоха  6/100, Train Loss: 0.8281, Train Acc: 72.7%, Test Acc: 66.0% ★  (precision/recall по классам: квадрат: 67.7%/84.0%, круг: 51.1%/46.0%, треугольник: 58.1%/50.0%)
  Эпоха  7/100, Train Loss: 0.7604, Train Acc: 77.6%, Test Acc: 75.3% ★  (precision/recall по классам: квадрат: 71.4%/90.0%, круг: 65.0%/52.0%, треугольник: 74.5%/70.0%)
  Эпоха  8/100, Train Loss: 0.7174, Train Acc: 80.4%, Test Acc: 79.3% ★  (precision/recall по классам: квадрат: 72.4%/84.0%, круг: 57.5%/46.0%, треугольник: 69.2%/72.0%)
  Эпоха  9/100, Train Loss: 0.6826, Train Acc: 83.1%, Test Acc: 78.0%    (precision/recall по классам: квадрат: 73.4%/94.0%, круг: 46.8%/44.0%, треугольник: 59.0%/46.0%)
  Эпоха 10/100, Train Loss: 0.6091, Train Acc: 73.8%, Test Acc: 67.3%    (precision/recall по классам: квадрат: 76.7%/92.0%, круг: 67.6%/50.0%, треугольник: 67.9%/72.0%)
  Эпоха 11/100, Train Loss: 0.5658, Train Acc: 85.1%, Test Acc: 82.0% ★  (precision/recall по классам: квадрат: 85.7%/96.0%, круг: 72.5%/58.0%, треугольник: 74.1%/80.0%)
  Эпоха 12/100, Train Loss: 0.5198, Train Acc: 87.1%, Test Acc: 84.7% ★  (precision/recall по классам: квадрат: 88.7%/94.0%, круг: 62.2%/56.0%, треугольник: 67.3%/70.0%)
  Эпоха 13/100, Train Loss: 0.5058, Train Acc: 63.6%, Test Acc: 59.3%    (precision/recall по классам: квадрат: 94.0%/94.0%, круг: 68.8%/66.0%, треугольник: 73.1%/76.0%)
  Эпоха 14/100, Train Loss: 0.5111, Train Acc: 84.9%, Test Acc: 84.0%    (precision/recall по классам: квадрат: 90.9%/100.0%, круг: 72.0%/72.0%, треугольник: 75.6%/68.0%)
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
  Эпоха 15/100, Train Loss: 0.462, Train Acc: 74.9%, Test Acc: 69.3%    (precision/recall по классам: квадрат: 92.6%/100.0%, круг: 78.0%/78.0%, треугольник: 82.6%/76.0%)
  Эпоха 16/100, Train Loss: 0.4234, Train Acc: 95.1%, Test Acc: 94.0% ★  (precision/recall по классам: квадрат: 92.3%/96.0%, круг: 76.5%/78.0%, треугольник: 83.0%/78.0%)
  Эпоха 17/100, Train Loss: 0.3856, Train Acc: 82.9%, Test Acc: 80.0%    (precision/recall по классам: квадрат: 98.0%/100.0%, круг: 78.7%/74.0%, треугольник: 76.9%/80.0%)
  Эпоха 18/100, Train Loss: 0.3667, Train Acc: 96.7%, Test Acc: 94.7% ★  (precision/recall по классам: квадрат: 96.0%/96.0%, круг: 69.5%/82.0%, треугольник: 82.9%/68.0%)
  Эпоха 19/100, Train Loss: 0.3262, Train Acc: 97.1%, Test Acc: 95.3% ★  (precision/recall по классам: квадрат: 92.2%/94.0%, круг: 81.2%/78.0%, треугольник: 82.4%/84.0%)
  Эпоха 20/100, Train Loss: 0.2852, Train Acc: 98.4%, Test Acc: 96.7% ★  (precision/recall по классам: квадрат: 98.0%/98.0%, круг: 79.6%/86.0%, треугольник: 87.0%/80.0%)
  Эпоха 21/100, Train Loss: 0.2544, Train Acc: 96.4%, Test Acc: 92.0%    (precision/recall по классам: квадрат: 96.0%/96.0%, круг: 81.0%/94.0%, треугольник: 97.6%/82.0%)
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
  Эпоха 22/100, Train Loss: 0.2461, Train Acc: 81.6%, Test Acc: 80.7%    (precision/recall по классам: квадрат: 94.3%/100.0%, круг: 88.5%/92.0%, треугольник: 97.8%/88.0%)
  Эпоха 23/100, Train Loss: 0.2229, Train Acc: 99.3%, Test Acc: 98.0% ★  (precision/recall по классам: квадрат: 98.0%/96.0%, круг: 87.8%/86.0%, треугольник: 84.6%/88.0%)
  Эпоха 24/100, Train Loss: 0.1788, Train Acc: 99.1%, Test Acc: 97.3%    (precision/recall по классам: квадрат: 96.0%/96.0%, круг: 84.0%/84.0%, треугольник: 88.0%/88.0%)
  Эпоха 25/100, Train Loss: 0.1765, Train Acc: 99.6%, Test Acc: 97.3%    (precision/recall по классам: квадрат: 98.0%/98.0%, круг: 86.8%/92.0%, треугольник: 93.6%/88.0%)
  Эпоха 26/100, Train Loss: 0.1426, Train Acc: 96.4%, Test Acc: 92.7%    (precision/recall по классам: квадрат: 100.0%/98.0%, круг: 90.4%/94.0%, треугольник: 93.9%/92.0%)
  Эпоха 27/100, Train Loss: 0.1478, Train Acc: 97.3%, Test Acc: 94.0%    (precision/recall по классам: квадрат: 98.0%/100.0%, круг: 93.9%/92.0%, треугольник: 94.0%/94.0%)
  Эпоха 28/100, Train Loss: 0.1513, Train Acc: 99.3%, Test Acc: 97.3%    (precision/recall по классам: квадрат: 98.0%/100.0%, круг: 93.9%/92.0%, треугольник: 94.0%/94.0%)
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
  Эпоха 29/100, Train Loss: 0.1345, Train Acc: 99.6%, Test Acc: 98.0%    (precision/recall по классам: квадрат: 100.0%/98.0%, круг: 96.0%/96.0%, треугольник: 96.1%/98.0%)
  Эпоха 30/100, Train Loss: 0.0976, Train Acc: 95.1%, Test Acc: 89.3%    (precision/recall по классам: квадрат: 92.6%/100.0%, круг: 89.4%/84.0%, треугольник: 89.8%/88.0%)
  Эпоха 31/100, Train Loss: 0.1058, Train Acc: 89.6%, Test Acc: 83.3%    (precision/recall по классам: квадрат: 98.0%/100.0%, круг: 95.8%/92.0%, треугольник: 94.1%/96.0%)

  ⏹ Ранний останов после 31 эпох (нет улучшения 8 эпох)

Загружена лучшая модель (Test Acc: 98.0%)

=== Результаты ===
  Лучшая точность на тесте: 98.0%
  Точность train/test: 95.1% / 88.0%
  ✓ Нет переобучения (разрыв 7.1%)
Обучение завершено! 🚀
Модель сохранена в model.bson

Посмотрим на качество проведенного обучения:

In [ ]:
include("$(@__DIR__)/_scripts/analyze_training_log.jl")
gr()
df, classes, p = analyze_training_log("training_log.txt")
display(p)
No description has been provided for this image

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

Мы не стали продолжать обучение после достижения качества 100% на тесте, потому что исчез смысл сравнивать реализации между собой. Но нам определенно стоило бы породить больше объектов для датасета, поскольку, в среднем, к концу обучения модель достаточно точно определяла квадраты и круги, но из пяти предложенных треугольников в среднем "не замечала" один из них. Хотя те, которе она отмечала как треугольники действительно ими были (больше ошибок "ложного срабатывания" сеть демонстрировала для класса "круг").

Прогнозы от нейросети на Julia (Flux)

In [ ]:
include("$(@__DIR__)/_scripts/simple_mosaic.jl")
UNKNOWN_DIR = "$(@__DIR__)/неизвестно";
gr()
plot(create_simple_mosaic(UNKNOWN_DIR, imsize=64))
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
Out[0]:
No description has been provided for this image

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

Экспорт в C и тестирование

Теперь мы конвертируем предварительно обработанные изображения в бинарный формат, генерируем C-код нейросети, компилируем его в исполняемый файл и визуализируем предсказания, полученные от C-реализации. Мы заведомо предполагаем, что код будет работать на платформах, где нет библиотеки PNG. Поэтому переводим изображения в бинарный формат при помощи отдельного скрипта. В этих бинарных файлах лежат матрицы, в качестве элементов которых фигурирует каждый цветовой канал каждого пикселя, представленный одним числом UInt8.

In [ ]:
include("$(@__DIR__)/_scripts/convert_png_to_rgb8.jl")
convert_png_to_rgb8("$(@__DIR__)/неизвестно", "$(@__DIR__)/неизвестно_rgb8", 64)

Теперь, когда у нас готов датасет с бинарными изображениями, можно загрузить уже обученную модель и перевести ее в код на Си. Ключевое требование к успешному экспорту — полное согласование форматов данных (RGB8 для изображений, HWC порядок коэффициентов) и порядка обхода весов между Julia и C, что достигается явным контролем индексации и нормализации на всех этапах.

In [ ]:
include("$(@__DIR__)/_scripts/generate_cnn_code.jl")

BSON.@load "$(@__DIR__)/model.bson" model classes
model = Flux.testmode!(model)

# Генерируем библиотеку и main программу
generate_shared_lib(model, 64, length(classes))
generate_main_program(64, length(classes))
Generated neural_net.c and neural_net.h
Generated main.c

Саму нейросеть мы скомпилируем в библиотеку. Мы также сгенерировали программу main, которая подаёт в нейросеть изображения из папки "неизвестно_rgb8" и обрабатывает результаты классификации.

In [ ]:
;gcc -shared -fPIC neural_net.c -o libneuralnet.so -lm
In [ ]:
;gcc main.c -o classify_unknown -ldl -lm

Что любопытно, чтобы запустить эту нейросеть, нам не потребуются никакие библиотеки - ни Julia, ни C. Она выполняется на любой системе, где есть компилятор кода на Си.

In [ ]:
;./classify_unknown
File                 Prediction      Confidence
------------------------------------------------
circle_001.rgb       круг        0.998
circle_002.rgb       круг        0.999
circle_003.rgb       круг        0.992
circle_004.rgb       круг        1.000
circle_005.rgb       круг        0.876
circle_007.rgb       круг        0.885
circle_009.rgb       круг        0.929
circle_010.rgb       круг        0.991
circle_011.rgb       круг        0.985
circle_012.rgb       круг        0.990
circle_014.rgb       круг        0.994
circle_015.rgb       круг        0.999
circle_016.rgb       круг        0.966
circle_017.rgb       круг        0.896
circle_018.rgb       круг        0.847
circle_020.rgb       круг        0.921
circle_021.rgb       круг        0.981
circle_022.rgb       круг        0.867
circle_023.rgb       круг        0.994
circle_024.rgb       круг        0.977
circle_025.rgb       круг        1.000
circle_026.rgb       круг        0.998
circle_028.rgb       круг        0.983
circle_029.rgb       круг        0.997
circle_030.rgb       круг        0.976
square_001.rgb       квадрат  0.525
square_002.rgb       квадрат  0.504
square_003.rgb       квадрат  0.892
square_004.rgb       квадрат  0.854
square_005.rgb       квадрат  0.877
square_007.rgb       квадрат  0.729
square_008.rgb       круг        0.577
square_009.rgb       квадрат  0.786
square_010.rgb       квадрат  0.929
square_013.rgb       квадрат  0.801
square_014.rgb       квадрат  0.942
square_015.rgb       квадрат  0.952
square_016.rgb       квадрат  0.892
square_018.rgb       квадрат  0.927
square_019.rgb       квадрат  0.974
square_020.rgb       квадрат  0.887
square_021.rgb       квадрат  0.894
square_023.rgb       квадрат  0.786
square_024.rgb       квадрат  0.984
square_026.rgb       квадрат  0.725
square_027.rgb       квадрат  0.869
square_028.rgb       квадрат  0.918
square_029.rgb       квадрат  0.925
square_030.rgb       квадрат  0.898
triangle_001.rgb     треугольник 0.550
triangle_002.rgb     круг        0.692
triangle_003.rgb     круг        0.667
triangle_004.rgb     круг        0.554
triangle_006.rgb     круг        0.770
triangle_007.rgb     круг        0.811
triangle_008.rgb     круг        0.553
triangle_009.rgb     круг        0.897
triangle_010.rgb     треугольник 0.647
triangle_011.rgb     треугольник 0.544
triangle_012.rgb     круг        0.502
triangle_013.rgb     круг        0.931
triangle_014.rgb     треугольник 0.644
triangle_016.rgb     круг        0.800
triangle_017.rgb     круг        0.546
triangle_020.rgb     круг        0.897
triangle_021.rgb     круг        0.892
triangle_022.rgb     круг        0.928
triangle_023.rgb     круг        0.546
triangle_025.rgb     треугольник 0.571
triangle_026.rgb     круг        0.552
triangle_027.rgb     круг        0.506
triangle_028.rgb     круг        0.594
triangle_029.rgb     треугольник 0.684
triangle_030.rgb     круг        0.783
circle_027.rgb       круг        0.973
square_006.rgb       квадрат  0.882
square_011.rgb       квадрат  0.784
square_012.rgb       квадрат  0.970
square_017.rgb       квадрат  0.971
square_025.rgb       круг        0.577
triangle_019.rgb     круг        0.569
triangle_024.rgb     круг        0.979

При переносе модели в C пришлось решить несколько нетривиальных задач: ручная реализация свёрток и BatchNorm без сторонних библиотек, приведение всех операций к единому формату HWC, точное воспроизведение порядка обхода весов (особенно критичного для многоканальных слоёв), а также работа с бинарными файлами изображений из-за отсутствия библиотеки PNG в целевой среде — все эти трудности были успешно преодолены.

Прогнозы от нейросети на Си

In [ ]:
include("$(@__DIR__)/_scripts/create_mosaic_from_c_predictions.jl")
run(pipeline(`./classify_unknown`, stdout="pred.txt"))
UNKNOWN_DIR = "$(@__DIR__)/неизвестно";
gr()
mosaic_grouped = create_mosaic_from_c_predictions("неизвестно", "pred.txt", max_images=8)
Out[0]:
No description has been provided for this image

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

In [ ]:
include("$(@__DIR__)/_scripts/predict_to_csv.jl")
UNKNOWN_DIR = "$(@__DIR__)/неизвестно";
predict_to_csv(UNKNOWN_DIR, confidence_threshold=0.4, output_csv="$(@__DIR__)/predictions.csv")
run(pipeline(`./classify_unknown`, stdout="pred.txt"))
include("$(@__DIR__)/_scripts/compare_c_and_julia.jl")
df = compare_c_and_julia()
sort(df)
Обработано файлов: 82
  квадрат: 30
  круг: 27
  треугольник: 25

=== Сравнение C и Julia ===
Всего файлов: 82
Совпадающих предсказаний: 58
Точность: 70.73%

Статистика разницы в уверенности:
  Средняя разница: 0.1468
  Макс разница: 0.4452
  Мин разница: 0.0026
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
Out[0]:
82×12 DataFrame
57 rows omitted
RowFileC_PredictionC_ConfidenceBaseNameФайлJulia_PredictionJulia_ConfidenceВероятность_квадратВероятность_кругВероятность_треугольникMatchConfidence_Diff
StringStringFloat64StringString31String31Float64Float64Float64Float64BoolFloat64
1circle_001.rgbкруг0.998circle_001circle_001.pngкруг0.9119490.05539550.9119490.0326554true0.0860508
2circle_002.rgbкруг0.999circle_002circle_002.pngкруг0.9763530.008158840.9763530.0154881true0.0226469
3circle_003.rgbкруг0.992circle_003circle_003.pngкруг0.952730.02631530.952730.0209551true0.0392704
4circle_004.rgbкруг1.0circle_004circle_004.pngкруг0.9892990.005832470.9892990.00486871true0.0107013
5circle_005.rgbкруг0.876circle_005circle_005.pngкруг0.6770330.06953770.6770330.253429true0.198967
6circle_007.rgbкруг0.885circle_007circle_007.pngкруг0.8530360.1026440.8530360.0443203true0.0319639
7circle_009.rgbкруг0.929circle_009circle_009.pngкруг0.7493890.1471960.7493890.103415true0.179612
8circle_010.rgbкруг0.991circle_010circle_010.pngкруг0.9198560.01826790.9198560.0618764true0.0711443
9circle_011.rgbкруг0.985circle_011circle_011.pngкруг0.8211480.007267880.8211480.171584true0.163852
10circle_012.rgbкруг0.99circle_012circle_012.pngкруг0.6564440.3010540.6564440.0425026true0.333556
11circle_014.rgbкруг0.994circle_014circle_014.pngкруг0.9397310.01862430.9397310.0416447true0.0542691
12circle_015.rgbкруг0.999circle_015circle_015.pngкруг0.9706860.00608770.9706860.0232258true0.0283136
13circle_016.rgbкруг0.966circle_016circle_016.pngкруг0.8432020.03315850.8432020.12364true0.122798
⋮⋮⋮⋮⋮⋮⋮⋮⋮⋮⋮⋮⋮
71triangle_019.rgbкруг0.569triangle_019triangle_019.pngтреугольник0.466660.08558060.4477590.46666false0.10234
72triangle_020.rgbкруг0.897triangle_020triangle_020.pngтреугольник0.8207940.002196020.177010.820794false0.0762059
73triangle_021.rgbкруг0.892triangle_021triangle_021.pngтреугольник0.9412310.001349090.05741970.941231false0.0492312
74triangle_022.rgbкруг0.928triangle_022triangle_022.pngтреугольник0.9777810.000122910.0220960.977781false0.0497811
75triangle_023.rgbкруг0.546triangle_023triangle_023.pngтреугольник0.8183180.01850380.1631780.818318false0.272318
76triangle_024.rgbкруг0.979triangle_024triangle_024.pngкруг0.7807410.1150860.7807410.104173true0.198259
77triangle_025.rgbтреугольник0.571triangle_025triangle_025.pngтреугольник0.8574690.01879770.1237340.857469true0.286469
78triangle_026.rgbкруг0.552triangle_026triangle_026.pngтреугольник0.9535730.002183540.0442430.953573false0.401573
79triangle_027.rgbкруг0.506triangle_027triangle_027.pngтреугольник0.9088860.006617020.08449650.908886false0.402886
80triangle_028.rgbкруг0.594triangle_028triangle_028.pngтреугольник0.7929390.01448990.1925720.792939false0.198939
81triangle_029.rgbтреугольник0.684triangle_029triangle_029.pngтреугольник0.9475950.003543110.04886240.947595true0.263594
82triangle_030.rgbкруг0.783triangle_030triangle_030.pngтреугольник0.872280.003308110.1244120.87228false0.0892798

Заключение

Мы показали, как пройти полный цикл создания программы с нейросетью внутри: от создания датасета и обучения модели на Julia до экспорта в C и проверки работоспособности, что подтверждает принципиальную возможность использования сгенерированного кода далеко за пределами инженерной платформы Engee.