Экспорт нейронной сети, подготовленной с помощью MATLAB на других языках программирования

Я тренировал нейронную сеть с помощью набора инструментов Neural Network MATLAB и, в частности, с помощью команды nprtool, которая предоставляет простой графический интерфейс для использования функций панели инструментов и для экспорта объекта net, содержащего информацию о NN генерируется.

Таким образом, я создал рабочую нейронную сеть, которую я могу использовать в качестве классификатора, а диаграмма, представляющая ее, следующая:

Diagram representing the Neural Network

В первом скрытом слое имеется 200 входов, 20 нейронов и 2 нейрона в последнем слое, которые обеспечивают двумерный выход.

Что я хочу сделать, так это использовать сеть на каком-то другом языке программирования (С#, Java,...).

Чтобы решить эту проблему, я пытаюсь использовать следующий код в MATLAB:

y1 = tansig(net.IW{1} * input + net.b{1});
Results = tansig(net.LW{2} * y1 + net.b{2});

Предполагая, что input является монометрическим массивом из 200 элементов, предыдущий код работал бы, если net.IW{1} - матрица 20x200 (20 нейронов, 200 весов).

Проблема в том, что я заметил, что size(net.IW{1}) возвращает неожиданные значения:

>> size(net.IW{1})

    ans =

    20   199

У меня такая же проблема с сетью со 10000 вводами. В этом случае результат не был 20x10000, но что-то вроде 20x9384 (я не помню точное значение).

Итак, возникает вопрос: как я могу получить вес каждого нейрона? И после этого, может кто-нибудь объяснить мне, как я могу использовать их для получения того же выхода MATLAB?

Ответ 1

Я решил проблемы, описанные выше, и я думаю, что полезно поделиться тем, что я узнал.

Служебные

Прежде всего, нам нужны некоторые определения. Рассмотрим следующий образ, взятый из [1]:

A scheme of Neural Network

На приведенном выше рисунке IW обозначает начальные веса: они представляют собой веса нейронов на уровне 1, каждый из которых связан с каждым входом, так как следующее изображение показывает [1]:

All neurons are connected with all inputs

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

Решение проблемы

После вышеприведенного введения мы можем продолжить, разделив проблему в два этапа:

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

A - Заставить количество начальных весов соответствовать длине входного массива

Используя nprtool, мы можем обучить нашу сеть, и в конце процесса мы также можем экспортировать в рабочую область некоторую информацию обо всем процессе обучения. В частности, нам нужно экспортировать:

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

Кроме того, нам нужно сгенерировать M файл, содержащий код, используемый MATLAB для создания нейронной сети, потому что нам нужно изменить его и изменить некоторые варианты обучения.

На следующем рисунке показано, как выполнить следующие операции:

The nprtool GUI to export data and generate the M-code

Сгенерированный M-код будет похож на следующий:

function net = create_pr_net(inputs,targets)
%CREATE_PR_NET Creates and trains a pattern recognition neural network.
%
%  NET = CREATE_PR_NET(INPUTS,TARGETS) takes these arguments:
%    INPUTS - RxQ matrix of Q R-element input samples
%    TARGETS - SxQ matrix of Q S-element associated target samples, where
%      each column contains a single 1, with all other elements set to 0.
%  and returns these results:
%    NET - The trained neural network
%
%  For example, to solve the Iris dataset problem with this function:
%
%    load iris_dataset
%    net = create_pr_net(irisInputs,irisTargets);
%    irisOutputs = sim(net,irisInputs);
%
%  To reproduce the results you obtained in NPRTOOL:
%
%    net = create_pr_net(trainingSetInput,trainingSetOutput);

% Create Network
numHiddenNeurons = 20;  % Adjust as desired
net = newpr(inputs,targets,numHiddenNeurons);
net.divideParam.trainRatio = 75/100;  % Adjust as desired
net.divideParam.valRatio = 15/100;  % Adjust as desired
net.divideParam.testRatio = 10/100;  % Adjust as desired

% Train and Apply Network
[net,tr] = train(net,inputs,targets);
outputs = sim(net,inputs);

% Plot
plotperf(tr)
plotconfusion(targets,outputs)

Перед началом учебного процесса нам нужно удалить все функции предварительной обработки и постпроцессинга, которые MATLAB выполняет на входах и выходах. Это можно сделать, добавив следующие строки непосредственно перед строками % Train and Apply Network:

net.inputs{1}.processFcns = {};
net.outputs{2}.processFcns = {};

После этих изменений функции create_pr_net() просто мы можем использовать ее для создания нашей последней нейронной сети:

net = create_pr_net(input, target);

где input и target - значения, которые мы экспортировали через nprtool.

Таким образом, мы уверены, что количество весов равно длине входного массива. Кроме того, этот процесс полезен для упрощения переноса на другие языки программирования.

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

С этими изменениями мы можем определить такую ​​функцию:

function [ Results ] = classify( net, input )
    y1 = tansig(net.IW{1} * input + net.b{1});

    Results = tansig(net.LW{2} * y1 + net.b{2});
end

В этом коде мы используем описанные выше массивы IW и LW, а также предубеждения b, используемые в сетевой схеме с помощью nprtool. В этом контексте мы не заботимся о роли предубеждений; просто мы должны использовать их, потому что nprtool делает это.

Теперь мы можем использовать функцию classify(), определенную выше, или функцию sim() одинаково, получив те же результаты, что и в следующем примере:

>> sim(net, input(:, 1))

ans =

    0.9759
   -0.1867
   -0.1891

>> classify(net, input(:, 1))

ans =

   0.9759   
  -0.1867
  -0.1891

Очевидно, что функция classify() может быть интерпретирована как псевдокод, а затем реализована на всех языках программирования, в которой возможно определить функцию MATLAB tansig() [2] и основные операции между массивами.

Ссылки

[1] Говард Демут, Марк Бил, Мартин Хаган: Neural Network Toolbox 6 - Руководство пользователя, MATLAB

[2] Mathworks, tansig - Гиперболическая касательная сигмоидная передаточная функция, MATLAB Documentation center

Дополнительные примечания

Посмотрите на ответ robott и ответ Sangeun Chi для более подробной информации.

Ответ 2

Благодаря ответам VitoShadow и robott я могу экспортировать значения нейронной сети Matlab в другие приложения.

Я действительно ценю их, но я нашел некоторые тривиальные ошибки в своих кодах и хочу их исправить.

1) В кодах VitoShadow

Results = tansig(net.LW{2} * y1 + net.b{2});
-> Results = net.LW{2} * y1 + net.b{2};

2) В кодах предварительной обработки robott, Было бы проще извлечь xmax и xmin из сетевой переменной, чем вычислять их.

xmax = net.inputs{1}.processSettings{1}.xmax
xmin = net.inputs{1}.processSettings{1}.xmin

3) В кодах постобработки robott

xmax = net.outputs{2}.processSettings{1}.xmax
xmin = net.outputs{2}.processSettings{1}.xmin

Results = (ymax-ymin)*(Results-xmin)/(xmax-xmin) + ymin;
-> Results = (Results-ymin)*(xmax-xmin)/(ymax-ymin) + xmin;

Вы можете вручную проверить и подтвердить значения следующим образом:

p2 = mapminmax('apply', net(:, 1), net.inputs{1}.processSettings{1})

- > предварительно обработанные данные

y1 = purelin ( net.LW{2} * tansig(net.iw{1}* p2 + net.b{1}) + net.b{2})

- > Обработанные данные нейронной сети

y2 = mapminmax( 'reverse' , y1, net.outputs{2}.processSettings{1})

- > постпроцессированные данные

Ссылка: http://www.mathworks.com/matlabcentral/answers/14517-processing-of-i-p-data

Ответ 3

Это небольшое улучшение в ответе великого Vito Gentile.

Если вы хотите использовать функции предварительной обработки и постобработки "mapminmax" , вам следует обратить внимание, потому что "mapminmax" в Matlab нормализуется по ROW, а не по столбцу!

Это то, что вам нужно добавить к верхней функции "классифицировать", чтобы сохранить согласованную предварительную/пост-обработку:

[m n] = size(input);
ymax = 1;
ymin = -1;
for i=1:m
   xmax = max(input(i,:));
   xmin = min(input(i,:));
   for j=1:n
     input(i,j) = (ymax-ymin)*(input(i,j)-xmin)/(xmax-xmin) + ymin;
   end
end

И это в конце функции:

ymax = 1;
ymin = 0;
xmax = 1;
xmin = -1;
Results = (ymax-ymin)*(Results-xmin)/(xmax-xmin) + ymin;

Это код Matlab, но его можно легко прочитать как псевдокод. Надеюсь, это будет полезно!

Ответ 4

Я попытался реализовать простой двухслойный NN на С++ с использованием OpenCV, а затем экспортировал весы на Android, который работал хорошо. Я написал небольшую script, которая генерирует файл заголовка с узнанными весами, и это используется в следующем коде, сжатом.

// Map Minimum and Maximum Input Processing Function
Mat mapminmax_apply(Mat x, Mat settings_gain, Mat settings_xoffset, double settings_ymin){

    Mat y;

    subtract(x, settings_xoffset, y);
    multiply(y, settings_gain, y);
    add(y, settings_ymin, y);

    return y;


    /* MATLAB CODE
     y = x - settings_xoffset;
     y = y .* settings_gain;
     y = y + settings_ymin;
     */
}




// Sigmoid Symmetric Transfer Function
Mat transig_apply(Mat n){
    Mat tempexp;
    exp(-2*n, tempexp);
    Mat transig_apply_result = 2 /(1 + tempexp) - 1;
    return transig_apply_result;
}


// Map Minimum and Maximum Output Reverse-Processing Function
Mat mapminmax_reverse(Mat y, Mat settings_gain, Mat settings_xoffset, double settings_ymin){

    Mat x;

    subtract(y, settings_ymin, x);
    divide(x, settings_gain, x);
    add(x, settings_xoffset, x);

    return x;


/* MATLAB CODE
function x = mapminmax_reverse(y,settings_gain,settings_xoffset,settings_ymin)
x = y - settings_ymin;
x = x ./ settings_gain;
x = x + settings_xoffset;
end
*/

}


Mat getNNParameter (Mat x1)
{

    // convert double array to MAT

    // input 1
    Mat x1_step1_xoffsetM = Mat(1, 48, CV_64FC1, x1_step1_xoffset).t();
    Mat x1_step1_gainM = Mat(1, 48, CV_64FC1, x1_step1_gain).t();
    double x1_step1_ymin = -1;

    // Layer 1
    Mat b1M = Mat(1, 25, CV_64FC1, b1).t();
    Mat IW1_1M = Mat(48, 25, CV_64FC1, IW1_1).t();

    // Layer 2
    Mat b2M = Mat(1, 48, CV_64FC1, b2).t();
    Mat LW2_1M = Mat(25, 48, CV_64FC1, LW2_1).t();

    // input 1
    Mat y1_step1_gainM = Mat(1, 48, CV_64FC1, y1_step1_gain).t();
    Mat y1_step1_xoffsetM = Mat(1, 48, CV_64FC1, y1_step1_xoffset).t();
    double y1_step1_ymin = -1;



    // ===== SIMULATION ========


    // Input 1
    Mat xp1 = mapminmax_apply(x1, x1_step1_gainM, x1_step1_xoffsetM, x1_step1_ymin);

    Mat  temp = b1M + IW1_1M*xp1;

    // Layer 1
    Mat a1M = transig_apply(temp);

    // Layer 2
    Mat a2M = b2M + LW2_1M*a1M;

    // Output 1
    Mat y1M = mapminmax_reverse(a2M, y1_step1_gainM, y1_step1_xoffsetM, y1_step1_ymin);

    return y1M;
}

пример для смещения в заголовке может быть следующим:

static double b2[1][48] = {
        {-0.19879, 0.78254, -0.87674, -0.5827, -0.017464, 0.13143, -0.74361, 0.4645, 0.25262, 0.54249, -0.22292, -0.35605, -0.42747, 0.044744, -0.14827, -0.27354, 0.77793, -0.4511, 0.059346, 0.29589, -0.65137, -0.51788, 0.38366, -0.030243, -0.57632, 0.76785, -0.36374, 0.19446, 0.10383, -0.57989, -0.82931, 0.15301, -0.89212, -0.17296, -0.16356, 0.18946, -1.0032, 0.48846, -0.78148, 0.66608, 0.14946, 0.1972, -0.93501, 0.42523, -0.37773, -0.068266, -0.27003, 0.1196}};

Теперь, когда Google опубликовал Tensorflow, это стало устаревшим.

Ответ 5

Следовательно, решение становится (после исправления всех частей)

Здесь я даю решение в Matlab, но если у вас есть функция tanh(), вы можете легко преобразовать его в любой язык программирования. Это просто отображение полей из сетевого объекта и необходимых операций.

  • Предположим, у вас есть подготовленный журнал (сетевой объект), который вы хотите экспортировать
  • Предположим, что имя обученного журнала - training_ann

Вот пример script для экспорта и тестирования. Тестирование script сравнивает исходный результат сети с результатом my_ann_evaluation()

% Export IT
exported_ann_structure = my_ann_exporter(trained_ann);

% Run and Compare 
% Works only for single INPUT vector
% Please extend it to MATRIX version by yourself
input = [12 3 5 100];
res1 = trained_ann(input')';
res2 = my_ann_evaluation(exported_ann_structure, input')';

где вам понадобятся следующие две функции

Первый my_ann_exporter:

function [ my_ann_structure ] = my_ann_exporter(trained_netw)
% Just for extracting as Structure object
my_ann_structure.input_ymax = trained_netw.inputs{1}.processSettings{1}.ymax;
my_ann_structure.input_ymin = trained_netw.inputs{1}.processSettings{1}.ymin;
my_ann_structure.input_xmax = trained_netw.inputs{1}.processSettings{1}.xmax;
my_ann_structure.input_xmin = trained_netw.inputs{1}.processSettings{1}.xmin;

my_ann_structure.IW = trained_netw.IW{1};
my_ann_structure.b1 = trained_netw.b{1};
my_ann_structure.LW = trained_netw.LW{2};
my_ann_structure.b2 = trained_netw.b{2};

my_ann_structure.output_ymax = trained_netw.outputs{2}.processSettings{1}.ymax;
my_ann_structure.output_ymin = trained_netw.outputs{2}.processSettings{1}.ymin;
my_ann_structure.output_xmax = trained_netw.outputs{2}.processSettings{1}.xmax;
my_ann_structure.output_xmin = trained_netw.outputs{2}.processSettings{1}.xmin;
end

Вторая my_ann_evaluation:

function [ res ] = my_ann_evaluation(my_ann_structure, input)
% Works with only single INPUT vector
% Matrix version can be implemented

ymax = my_ann_structure.input_ymax;
ymin = my_ann_structure.input_ymin;
xmax = my_ann_structure.input_xmax;
xmin = my_ann_structure.input_xmin;
input_preprocessed = (ymax-ymin) * (input-xmin) ./ (xmax-xmin) + ymin;

% Pass it through the ANN matrix multiplication
y1 = tanh(my_ann_structure.IW * input_preprocessed + my_ann_structure.b1);

y2 = my_ann_structure.LW * y1 + my_ann_structure.b2;

ymax = my_ann_structure.output_ymax;
ymin = my_ann_structure.output_ymin;
xmax = my_ann_structure.output_xmax;
xmin = my_ann_structure.output_xmin;
res = (y2-ymin) .* (xmax-xmin) /(ymax-ymin) + xmin;
end