極限學習機(ELM)算法的matlab與C++實現
- 極限學習機的原理
極限學習機(Extreme learning machine,ELM)是單隱層神經網絡的算法,其最大特點就是能在保證學習精度的前提下比傳統的學習算法快。其結構如下圖所示:
對於一個單隱層神經網絡,假設有N個任意的樣本(Xi,ti),其中,
一個有L個隱層節點的單隱層神經網絡可以表示爲:
其中,h(x)爲激活函數,
爲輸入權重,βi爲輸出權重,bi是第個隱層單元的偏置。Wi·Wj表示Wi和Wj的內積。
單隱層神經網絡學習的目標是使得輸出的誤差最小,可以表示爲:
即存在βi,Wi和 bi使得
可以矩陣表示爲:
其中,是H隱層節點的輸出,β爲輸出權重,爲T期望輸出。
傳統的一些基於梯度下降法的算法,可以用來求解這樣的問題,但是基本的基於梯度的學習算法需要在迭代的過程中調整所有參數。而在ELM算法中, 一旦輸入權重Wi和隱層的偏置bi被隨機確定,隱層的輸出矩陣就被唯一確定。訓練單隱層神經網絡可以轉化爲求解一個線性系統
其中,H+是矩陣H的Moore-Penrose廣義逆。且可證明求得的解的範數是最小的並且唯一。
以一個簡單的二分類爲例,分別用matlab和c++實現。matlab代碼如下:
traindata=load('traindata.txt');
feature=traindata(:,1:2);%特徵
label=traindata(:,3);%標籤
X=feature;
[N,n]=size(X);
L=100;
m=2;%二分類
W=rand(n,L)*2-1;%權重-1到1
b_1=rand(1,L);
b=ones(N,1)*b_1;
H=1./(1+exp(-X*W+b));
temp_T=zeros(N,m);
for i=1:N
if(label(i)==1)
temp_T(i,1)=1;
temp_T(i,2)=0;
else
temp_T(i,1)=0;
temp_T(i,2)=1;
end
end
T=temp_T*2-1;
beta=pinv(H)*T;
x_1=X(:,1);
x_2=X(:,2);
hold on
for i=1:N
if(label(i)==1)
plot(x_1(i),x_2(i),'.g');
else
plot(x_1(i),x_2(i),'.r');
end
end
c++代碼如下,這裏的矩陣運算採用Eigen工具包,最難的地方就是廣義逆矩陣怎麼求,參照網上的資源,代碼如下:
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <Eigen/Dense>
#include <Eigen/SVD>
using namespace std;
using namespace Eigen;
template<typename _Matrix_Type_>
bool pseudoInverse(const _Matrix_Type_ &a, _Matrix_Type_ &result, double epsilon = std::numeric_limits<typename _Matrix_Type_::Scalar>::epsilon())
{
Eigen::JacobiSVD< _Matrix_Type_ > svd = a.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV);
if (a.rows() < a.cols())
{
typename _Matrix_Type_::Scalar tolerance = epsilon * std::max(a.cols(), a.rows()) * svd.singularValues().array().abs()(0);
result = svd.matrixV() * (svd.singularValues().array().abs() > tolerance).select(svd.singularValues().array().inverse(), 0).matrix().asDiagonal() * svd.matrixU().adjoint();
}
// return false;
else
{
typename _Matrix_Type_::Scalar tolerance = epsilon * std::max(a.cols(), a.rows()) * svd.singularValues().array().abs().maxCoeff();
// Eigen::JacobiSVD< _Matrix_Type_ > svd = a.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV);
// typename _Matrix_Type_::Scalar tolerance = epsilon * std::max(a.cols(), a.rows()) * svd.singularValues().array().abs().maxCoeff();
result = svd.matrixV() * ((svd.singularValues().array().abs() > tolerance).select(svd.singularValues().array().inverse(), 0)).matrix().asDiagonal() * svd.matrixU().adjoint();
}
return true;
}
int main()
{
ifstream trainfile;
trainfile.open("traindata.txt");
vector<vector<double>> traindata;
vector<double> rowdata;
double temp[3];
while (!trainfile.eof())
{
for (int i = 0; i < 3;i++)
{
trainfile >> temp[i];
rowdata.push_back(temp[i]);
}
traindata.push_back(rowdata);
rowdata.erase(rowdata.begin(), rowdata.end());
}
trainfile.close();
MatrixXd feature(traindata.size(), 2);
VectorXd label(traindata.size());
for (int i = 0; i < traindata.size(); i++)
{
for (int j = 0; j < 3; j++)
{
if (j < 2)
feature(i,j) = traindata[i][j];
else
label(i) = traindata[i][j];
}
}
int L = 50;//隱含層數
int m = 2;//二分類
int n = 2;//特徵數
int N = traindata.size();
MatrixXd W,b_1,b,R,Tem,H;
W = MatrixXd::Random(n, L);
b_1 = (MatrixXd::Random(1, L) + MatrixXd::Ones(1, L)) / 2;
b = MatrixXd::Ones(N, 1)*b_1;
R = -feature*W+b;
Tem = R.array().exp() + 1;
H = Tem.array().inverse();
MatrixXd temp_T,T;
temp_T = MatrixXd::Zero(N, m);
for (int i = 0; i < N;i++)
{
if (label(i)==1)
{
temp_T(i, 0) = 1;
temp_T(i, 1) = 0;
}
else
{
temp_T(i, 0) = 0;
temp_T(i, 1) = 1;
}
}
T = temp_T * 2 - MatrixXd::Ones(N, m);
MatrixXd result(L,N);
pseudoInverse(H, result);
MatrixXd beta = result*T;
MatrixXd output = H*beta;
for (int i = 0; i < N;i++)
cout << T(i,0) << " ";
cout << endl;
for (int i = 0; i < N; i++)
cout << output(i,0) << " ";
return 0;
}