统计学习方法 --- 感知机模型原理及c++实现
感知机学习旨在求出将训练数据集进行线性划分的分类超平面,为此,导入了基于误分类的损失函数,然后利用梯度下降法对损失函数进行极小化,从而求出感知机模型。感知机模型是神经网络和支持向量机的基础。下面分别从感知机学习的模型、策略和算法三个方面来介绍。
1. 感知机模型
感知机模型如下:
f(x)= sign(w*x+b)
其中,x为输入向量,sign为符号函数,括号里面大于等于0,则其值为1,括号里面小于0,则其值为-1。w为权值向量,b为偏置。求感知机模型即求模型参数w和b。感知机预测,即通过学习得到的感知机模型,对于新的输入实例给出其对应的输出类别1或者-1。2. 感知机策略
假设训练数据集是线性可分的,感知机学习的目标就是求得一个能够将训练数据集中正负实例完全分开的分类超平面,为了找到分类超平面,即确定感知机模型中的参数w和b,需要定义一个损失函数并通过将损失函数最小化来求w和b。
这里选择的损失函数是误分类点到分类超平面S的总距离。输入空间中任一点x0到超平面S的距离为:其中,||w||为w的L2范数。
其次,对于误分类点来说,当-yi (wxi + b)>0时,yi=-1,当-yi(wxi + b)<0时,yi=+1。所以对误分类点(xi, yi)满足:
-yi (wxi +b) > 0
所以误分类点(xi, yi)到分类超平面S的距离是:3. 感知机算法
感知机学习问题转化为求解损失函数式(1)的最优化问题,最优化的方法是随机梯度下降法。感知机学习算法是误分类驱动的,具体采用随机梯度下降法。首先,任意选取一个超平面w0,b0,然后用梯度下降法不断极小化目标函数式(1)。极小化的过程不是一次使M中所有误分类点的梯度下降,而是一次随机选取一个误分类点使其梯度下降。
损失函数L(w,b)的梯度是对w和b求偏导,即:
其中,(0<<=1)是学习率,即学习的步长。
随机梯度下降法:假如你站在曲面的一点,要以最快的速度到达最低点,当然会沿着坡度最大的方向往下走(梯度的反方向)
综上,感知机学习算法如下:
算法1 感知机学习算法的原始形式
原始形式C++实现的源代码
1 #include <iostream> 2 #include <vector> 3 #include <algorithm> 4 5 #define random(x) (rand()%(x)) 6 7 //向量的点积 8 double dot_product(std::vector<double>& a, std::vector<double>& b){ 9 if(a.size() != b.size()) return 0; 10 double res = 0; 11 for(int i = 0 ; i < a.size(); ++ i){ 12 res +=a[i]*b[i]; 13 } 14 return res; 15 } 16 17 //感知机模型类 18 class Preception{ 19 public: 20 Preception(int iters = 100,int learnRate = 1,double initw = 0, double initb = 0){ 21 iterators = iters; 22 w.push_back(initw); 23 b = initb; 24 step = learnRate; 25 } 26 27 ~Preception(){ 28 w.clear(); 29 b = 0; 30 } 31 32 //训练数据 33 //如果迭代次数完,还没有找到w和b, 则认为数据集不是线性可分的,返回false 34 //如果找到了w和b,则认为数据集是线性可分的,返回true 35 bool train(std::vector<std::vector<double> >& train_x,std::vector<int>& train_y){ 36 if(train_x.size() != train_y.size()) return false; 37 initWeight(train_x[0].size()); 38 39 for(int iter = 0 ; iter < iterators; ++ iter){ 40 bool flag = true; 41 for(int i = 0; i < train_x.size();){ 42 if( (dot_product(w,train_x[i]) + b)*(double)train_y[i] <= 0){ 43 update(train_x[i],train_y[i]); 44 flag = false; 45 }else{ 46 ++i; 47 } 48 } 49 if(flag) return true; 50 } 51 return false; 52 } 53 54 //批量预测数据 55 std::vector<int> predict(std::vector<std::vector<double> >& data_x){ 56 std::vector<int> ret; 57 for(int i = 0 ; i < data_x.size(); ++ i){ 58 ret.push_back(predict(data_x[i])); 59 } 60 return ret; 61 } 62 63 //预测x 64 int predict(std::vector<double>& x){ 65 return dot_product(x,w)+ b > 0 ? 1 : -1; 66 } 67 68 //打印感知机模型 69 void printPreceptronModel(){ 70 std::cout<<"原始形式感知机模型:f(x)=sign("; 71 for(int i = 0 ; i < w.size(); ++ i){ 72 if( i ) std::cout<<"+"; 73 if(w[i]!=1) std::cout<<w[i]; 74 std::cout<<"x"<<i+1; 75 } 76 if(b > 0) std::cout<<"+"; 77 std::cout<<b<<")"<<std::endl; 78 } 79 80 private: 81 //初始化向量w的维数 82 void initWeight(int size){ 83 for(int i = 1; i < size; ++ i){ 84 w.push_back(w[0]); 85 } 86 } 87 88 //更新w和b 89 void update(std::vector<double>& x, double y){ 90 for(int i = 0 ; i < w.size(); ++ i){ 91 w[i] += step*y*x[i]; 92 } 93 b += step*y; 94 95 // for(int i = 0 ; i < w.size(); ++ i) 96 // std::cout<<w[i]<<","; 97 // std::cout<<std::endl; 98 99 // std::cout<<b<<std::endl; 100 } 101 102 private: 103 int iterators; //迭代次数 104 105 //f(x) = sign(wx+b) 106 std::vector<double> w; //注意w是向量 107 double b; 108 109 double step; //学习速率 110 }; 111 112 int main(){ 113 std::vector<std::vector<double> >test_x(3); 114 test_x[0].push_back(3);test_x[0].push_back(3); 115 test_x[1].push_back(4);test_x[1].push_back(3); 116 test_x[2].push_back(1);test_x[2].push_back(1); 117 std::vector<int> test_y(3); 118 test_y[0] = 1; 119 test_y[1] = 1; 120 test_y[2] = -1; 121 122 Preception *model = new Preception(); 123 model->train(test_x,test_y); 124 model->printPreceptronModel(); 125 }
算法2 感知机学习算法的对偶形式
对偶形式的基本想法是,将w和b表示为实例xi和标记yi的线性组合形式,通过求解其系数而求得w和b。对误分类点(xi, yi)通过
所以,感知机学习算法的对偶形式如下:
对偶形式C++实现的源代码
1 #include <iostream> 2 #include <vector> 3 #include <algorithm> 4 5 #define random(x) (rand()%(x)) 6 7 //向量的点积 8 double dot_product(std::vector<double>& a, std::vector<double>& b){ 9 if(a.size() != b.size()) return 0; 10 double res = 0; 11 for(int i = 0 ; i < a.size(); ++ i){ 12 res +=a[i]*b[i]; 13 } 14 return res; 15 } 16 17 //感知机模型类 18 class Preception{ 19 public: 20 Preception(int iters = 100,int learnRate = 1,double initw = 0, double initb = 0){ 21 iterators = iters; 22 a.push_back(initw); 23 b = initb; 24 step = learnRate; 25 } 26 27 ~Preception(){ 28 a.clear(); 29 b = 0; 30 } 31 32 //训练数据 33 //如果迭代次数完,还没有找到a和b, 则认为数据集不是线性可分的,返回false 34 //如果找到了a和b,则认为数据集是线性可分的,返回true 35 bool train(std::vector<std::vector<double> >& train_x,std::vector<int>& train_y){ 36 if(train_x.size() != train_y.size()) return false; 37 initWeight(train_x.size()); 38 std::vector<std::vector<double> > gram = productGram(train_x); 39 for(int i = 0 ; i < a.size(); ++ i){ 40 int iter = 0; 41 while(iter < iterators){ 42 double sum = b; 43 for(int j = 0; j < a.size(); ++ j){ 44 sum += a[j]*train_y[j]*gram[j][i]; 45 } 46 sum *= train_y[i]; 47 if(sum <= 0) update(i,train_y[i]); 48 else break; 49 ++iter; 50 } 51 if(iter >= iterators) return false; 52 } 53 return true; 54 } 55 56 //批量预测数据 57 std::vector<int> predict(std::vector<std::vector<double> >& data_x){ 58 std::vector<int> ret; 59 for(int i = 0 ; i < data_x.size(); ++ i){ 60 ret.push_back(predict(data_x[i])); 61 } 62 return ret; 63 } 64 65 //预测x 66 int predict(std::vector<double>& x){ 67 return dot_product(x,a)+ b > 0 ? 1 : -1; 68 } 69 70 //打印感知机模型 71 void printPreceptronModel(){ 72 std::cout<<"原始形式感知机模型:f(x)=sign("; 73 for(int i = 0 ; i < a.size(); ++ i){ 74 if( i ) std::cout<<"+"; 75 if(a[i]!=1) std::cout<<a[i]; 76 std::cout<<"x"<<i+1; 77 } 78 if(b > 0) std::cout<<"+"; 79 std::cout<<b<<")"<<std::endl; 80 } 81 82 private: 83 //初始化向量a的维数 84 void initWeight(int size){ 85 for(int i = 1; i < size; ++ i){ 86 a.push_back(a[0]); 87 } 88 } 89 90 //生成Gram矩阵 91 std::vector<std::vector<double> > productGram(std::vector<std::vector<double> >& train_x){ 92 int n = train_x.size(); 93 std::vector<std::vector<double> > gram(n, std::vector<double>(n,0)); 94 for(int i = 0 ; i < n ; ++ i){ 95 for(int j = 0 ; j < n; ++ j){ 96 gram[i][j] = dot_product(train_x[i], train_x[j]); 97 } 98 } 99 return gram; 100 } 101 102 //更新w和b 103 void update(int index, double y){ 104 a[index] +=1; 105 b += step*y; 106 } 107 108 private: 109 int iterators; //迭代次数 110 111 std::vector<double> a; //注意w是向量 112 double b; 113 114 double step; //学习速率 115 }; 116 117 int main(){ 118 std::vector<std::vector<double> >test_x(3); 119 test_x[0].push_back(3);test_x[0].push_back(3); 120 test_x[1].push_back(4);test_x[1].push_back(3); 121 test_x[2].push_back(1);test_x[2].push_back(1); 122 std::vector<int> test_y(3); 123 test_y[0] = 1; 124 test_y[1] = 1; 125 test_y[2] = -1; 126 127 Preception *model = new Preception(); 128 model->train(test_x,test_y); 129 model->printPreceptronModel(); 130 }