使用MATLAB训练的神经网络如何导出到其他编程语言中?

15

我使用MATLAB神经网络工具箱进行了神经网络训练,特别是使用命令nprtool,该命令提供了一个简单的图形用户界面来使用工具箱功能,并导出包含生成的神经网络信息的net对象。

通过这种方式,我创建了一个可用作分类器的神经网络,并且表示它的图表如下:

代表神经网络的图表

有200个输入,在第一个隐藏层中有20个神经元,在最后一层中有2个神经元,它们提供二维输出。

我想要在其他编程语言(C#、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} 是一个20行200列的矩阵(20个神经元,200个权重),则前面的代码可以正常运行。
问题在于我发现size(net.IW{1})返回了意外的值:
>> size(net.IW{1})

    ans =

    20   199

我在使用一个有10000个输入的网络时遇到了同样的问题。在这种情况下,结果不是20x10000,而是类似于20x9384(我记不清确切的值了)。

那么问题来了:我怎样才能获得每个神经元的权重呢?在此之后,有人可以解释一下如何使用它们产生与MATLAB相同的输出吗?


有些权重可能已经被设置为0,因此从矩阵中剪枝掉了。希望如果我是正确的话,就会有某个设置可以避免这种行为。 - CTZStef
我看不到任何可以更改这种选项的设置... 你能帮我找到它们吗? - Vito Gentile
5个回答

14

我解决了上面描述的问题,我认为分享我所学到的东西是有用的。

前提条件

首先,我们需要一些定义。让我们考虑下面的图片,来自[1]:

神经网络的图示

在上图中,IW代表初始权重:它们代表第1层神经元的权重,每个神经元都与每个输入相连,如下图所示[1]:

所有神经元都与所有输入相连

所有其他权重称为层权重(在第一张图中为LW),它们也与上一层的每个输出相连。在我们的研究案例中,我们使用具有两层的网络,因此我们将使用一个LW数组来解决我们的问题。

问题的解决方案

在上述介绍之后,我们可以将问题分为两个步骤:

  • 强制使初始权重的数量与输入数组长度匹配
  • 使用这些权重在其他编程语言中实现和使用训练好的神经网络

A-强制使初始权重的数量与输入数组长度匹配

使用nprtool,我们可以训练我们的网络,并在过程结束时,我们还可以在工作区中导出有关整个训练过程的一些信息。特别地,我们需要导出:

  • 表示所创建的神经网络的MATLAB网络对象
  • 用于训练网络的输入数组
  • 用于训练网络的目标数组

此外,我们需要生成一个包含MATLAB用于创建神经网络的代码的M文件,因为我们需要修改它并更改一些训练选项。

下面的图像显示了如何执行这些操作:

nprtool导出数据和生成M代码的GUI

生成的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);

其中inputtarget是我们通过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数组以及被nprtool网络架构中使用的偏置b。在这种情况下,我们不关心偏差的作用;简单地说,我们需要使用它们是因为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] Howard Demuth, Mark Beale, Martin Hagan: Neural Network Toolbox 6 - User Guide, MATLAB

[2] Mathworks,tansig-双曲正切S形转移函数,MATLAB文档中心

附加说明

查看robott的答案Sangeun Chi的答案以获取更多细节。


1
谢谢您的回答。它对我很有帮助。但是我需要processFcns。我只在我的输入数据上使用了[inputs,PSi] = mapminmax(inputs);,并将其保存到minmaxParams.mat中save('minmaxParams','PSi')。然后在我的分类函数中,我添加了load minmaxParams.mat;input = mapminmax('apply',input, PSi); - Lukas Ignatavičius
1
我已经合并了所有的评论,纠正了一些打字错误,并给出了一个单隐藏层ANN的良好实现示例。它包括Sangeun Chi的改进和修正。 - fermat4214

3

这是对Vito Gentile答案的小改进。

如果您想使用预处理和后处理的'mapminmax'函数,请注意Matlab中的'mapminmax'是通过ROW而不是column进行归一化!

以下是您需要添加到上面的“classify”函数中以保持一致的前/后处理:

[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代码,但它可以很容易地被理解为伪代码。希望这对你有所帮助!


3
感谢VitoShadow和robott answers的帮助,我现在可以将Matlab神经网络的值导出到其他应用程序中。
非常感激他们的帮助,但我在他们的代码中发现了一些细小的错误,希望能够进行更正。
1)在VitoShadow的代码中,
Results = tansig(net.LW{2} * y1 + net.b{2});
-> Results = net.LW{2} * y1 + net.b{2};

在机器人预处理代码中,从变量net中提取xmax和xmin比计算它们更容易。
xmax = net.inputs{1}.processSettings{1}.xmax
xmin = net.inputs{1}.processSettings{1}.xmin

3) 在机器人后处理代码中,

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

0

我尝试使用OpenCV在C++中实现一个简单的2层神经网络,然后将权重导出到Android上,效果非常好。我编写了一个小脚本,生成了一个包含学习权重的头文件,并在以下代码片段中使用。

// 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}};

现在,谷歌发布了Tensorflow,这就变得过时了。

0
因此,解决方案变为(在纠正所有部分后)
这里我提供了Matlab的解决方案,但如果您有tanh()函数,您可以轻松将其转换为任何编程语言。它仅用于显示来自网络对象的字段和所需操作。
假设您有一个已训练好的ann(网络对象)要导出
假设训练ann的名称为trained_ann
以下是导出和测试的脚本。 测试脚本将原始网络结果与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

这个导出函数是为一个简单的情况设计的:只针对具有1个输入、1个输出和1个隐藏层的ANN。人们可以很容易地将其扩展到更多层次的ANN。 - fermat4214

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接