机器学习笔记

知错能改演算法-感知机

PLA算法
要实现上面的,得做一些准备工作。首先矩阵运算少不了吧。
Eigen is a C++ template library for linear algebra: matrices, vectors, numerical solvers, and related algorithms
说人话就是:Eigen是一个用于线性代数的C++模板库:矩阵、向量、数值求解器和相关算法
首先去官网,由于笔者用的是win10,所以选择zip格式的。
Eigen官网

下载完成后解压到自定义文件夹中,建议放到C盘,避免误删除
接下来就是在Code Blocks里面配置,也很简单,如下图
code blocks配置
在setting-compiler-Search directories点击Add添加Eigen所在目录即可。
试运行一下:

#include <iostream>
#include <Eigen/Dense>

using Eigen::MatrixXd;
using namespace std;

int main()
{
  MatrixXd m(2,2);
  m(0,0) = 3;
  m(1,0) = 2.5;
  m(0,1) = -1;
  m(1,1) = m(1,0) + m(0,1);
  cout << m << endl;
}

输出正常,代表配置完成

官方文档有这么一段解释:

意思是啥呢,简单应用使用MatrixXd就够了,MatrixXd代表的是任意大小的矩阵,其中的每一个元素都是double类型;头文件Eigen/Dense包含了MatrixXd类型的所有成员函数,这些类和成员函数都是Eigen命名空间内
所以不管程序咋写,前面最省心的是写上这几行:

#include <iostream>
#include <Eigen/Dense>
 
using namespace Eigen;
using namespace std;

输出矩阵和向量也很简单,直接cout就行,不用每行每列。
下图是另一段包含矩阵和向量运算的代码,以及运算结果

第二个示例首先声明一个3×3矩阵m,该矩阵m使用Random()方法初始化,内容是介于-1和1之间的随机值。下一行应用线性映射,使值介于10和110之间。函数调用MatrixXd::Constant(3,3,1.2)返回一个3×3矩阵表达式,其所有系数均等于1.2。其余为标准算术。

main的下一行引入了一种新类型:VectorXd。这表示任意大小的(列)向量。这里,向量v被创建为包含3个未初始化的系数。最后一行使用所谓的逗号初始值设定项(是不是感觉很牛逼)。
官方后面还给了一个例子:

#include <iostream>
#include <Eigen/Dense>
 
using Eigen::Matrix3d;
using Eigen::Vector3d;
 
int main()
{
  Matrix3d m = Matrix3d::Random();
  m = (m + Matrix3d::Constant(1.2)) * 50;
  std::cout << "m =" << std::endl << m << std::endl;
  Vector3d v(1,2,3);
  
  std::cout << "m * v =" << std::endl << m * v << std::endl;
}

也没啥,就是引用了固定大小的Matrix3d和Vector3d,说是建议矩阵大小小于等于4*4时用固定大小的比较好,效率高。

官方的这一小段话告诉我们动态大小的矩阵是怎么来的。
后面还有不少介绍,沾个网址方便下次查询:Eigen文档
再回到文章最前面那张图

w是m1维的向量,每一维都代表特征值的权重;
X是m
n的矩阵,每一列代表一条数据,一共有n列,也就是n条数据;Xn代表第n条数据。
不管怎么组织数据,都要保证维数匹配,不然无法运算。
下面再看看sign是啥,该怎么实现:

也就是一个符号函数:
当x>0,sign(x)=1;
当x=0,sign(x)=0;
当x<0, sign(x)=-1;
再想想还需要什么,要从文件中读取数据到Matrix和Vector中,为了直观显示,还要将这些数据显示在平面坐标中。

先实现从文件读取数据到Matrix中

方法1:C++ >>和<<读写文本文件

fstream 或者 ifstream 类负责实现对文件的读取,它们内部都对 >> 输出流运算符做了重载;同样,fstream 和 ofstream 类负责实现对文件的写入,它们的内部也都对 << 输出流运算符做了重载。

所以,当 fstream 或者 ifstream 类对象打开文件(通常以 ios::in 作为打开模式)之后,就可以直接借助 >> 输入流运算符,读取文件中存储的字符(或字符串);当 fstream 或者 ofstream 类对象打开文件(通常以 ios::out 作为打开模式)后,可以直接借助 << 输出流运算符向文件中写入字符(或字符串)。

#include <iostream>
#include <fstream>
using namespace std;
int main()
{
    int x,sum=0;
    ifstream srcFile("in.txt", ios::in); //以文本模式打开in.txt备读
    if (!srcFile) { //打开失败
        cout << "error opening source file." << endl;
        return 0;
    }
    ofstream destFile("out.txt", ios::out); //以文本模式打开out.txt备写
    if (!destFile) {
        srcFile.close(); //程序结束前不能忘记关闭以前打开过的文件
        cout << "error opening destination file." << endl;
        return 0;
    }
    //可以像用cin那样用ifstream对象
    while (srcFile >> x) {
        sum += x;
        //可以像 cout 那样使用 ofstream 对象
        destFile << x << " ";
    }
    cout << "sum:" << sum << endl;
    destFile.close();
    srcFile.close();
    return 0;
}

上述这一小段是从一个博客C++文件怎么进行读取和写入操作中截取的,C++还有其他读写方法,不过既然这个这么直观,就它了。
下面结合Eigen写了一小段代码,虽然功能单一了 些,不过也勉强实现了从文件读到Matrix以及从Matrix写入文件。

#include <iostream>
#include<fstream>// Save to local file.
#include <Eigen/Dense>

using namespace std;
using namespace Eigen;

void save_file(MatrixXd mat, string filename)
{
	ofstream outfile(filename, ios::trunc);
	outfile << mat;
	outfile.close();
}

int readfile(string infile,MatrixXd& mat,int row,int column)
{
    //假设已经知道文件的数据格式了
    double x;
    ifstream srcFile(infile, ios::in); //以文本模式打开infile备读
    if (!srcFile) { //打开失败
        cout << "error opening source file." << endl;
        return 0;
    }
    for(int i=0;i<row;i++){
        for(int j=0;j<column;j++){
            srcFile >> x;
            mat(i,j) = x;
        }
    }
    srcFile.close();
    return 0;
}
int main()
{
    MatrixXd mat(24, 3);
    readfile("data.txt",mat,24,3);
    cout<<mat<<endl;
    return 0;
}

正常情况下,一个数据集中,前 n-1列都是特征值x,最后一列是标签y,所以需要对Matrix进行预处理,把特征值和标签分开。

VectorXd v =mat.col(2);//把矩阵的第二列赋值给向量v

取出来就没事了吗?非也,像笔者使用的数据集,标签是0和1,就得转化为-1和1

for(int i=0;i<DataSize;i++) v(i) = 2* v(i) - 1;

那要删除最后一列就更简单了

mat.conservativeResize(24,2);//使用resize会出现一些未初始化的值,还是conservativeResize靠谱

另外为了统一操作,需要给特征值加一列X0,也就是b这个角色,在此也可以直接把标签所在的第二列更改为全1.

mat.col(2) = MatrixXd::Constant(100,1,1);

到此为止,准备工作算告一段落,PS:可视化的事挪到下一篇章,打算采用code blocks和QT来实现
下面是完整代码,测试案例一共100组,最终识别出96组,96%的成功率,第一次跑可以了。测试样本的地址为:数据集,提取码为:watb

#include <iostream>
#include<fstream>// Save to local file.
#include <Eigen/Dense>
#include <vector>
#include <ctime>

using namespace std;
using namespace Eigen;

void save_file(MatrixXd mat, string filename)
{
	ofstream outfile(filename, ios::trunc);
	outfile << mat;
	outfile.close();
}

int readfile(string infile,MatrixXd& mat,int row,int column)
{
    //假设已经知道文件的数据格式了
    double x;
    ifstream srcFile(infile, ios::in); //以文本模式打开infile备读
    if (!srcFile) { //打开失败
        cout << "error opening source file." << endl;
        return 0;
    }
    for(int i=0;i<row;i++){
        for(int j=0;j<column;j++){
            srcFile >> x;
            mat(i,j) = x;
        }
    }
    srcFile.close();
    return 0;
}

int computeDiff(VectorXd& l,VectorXd& y){
    int sum = 0;
    for(int i=0;i<100;i++){
        if(l(i)*y(i)<=0) sum++;
    }
    return sum;
}

int getRand(int mi, int ma) {
    return ( rand() % (ma - mi + 1) ) + mi ;
}

int main(){
	int DataSize,weightSize;//DataSize代表数据集大小,weightSize代表特征值数量,也代表权值数量
	DataSize = 100;weightSize = 3;
    MatrixXd mat(DataSize, weightSize);
    readfile("DataSet.txt",mat,DataSize,weightSize);
    VectorXd v = mat.col(weightSize-1);//label在最后一列
    for(int i=0;i<DataSize;i++) v(i) = 2* v(i) - 1;//数据集本来的标签是0和1,要转化为-1和1
    mat.col(weightSize-1) = MatrixXd::Constant(DataSize,1,1);
    VectorXd w(weightSize);//初始化权值
    for(int i=0;i<weightSize;i++) w(i) = 0;
    double pace = 0.001;//步长设定为0.001
    //至此完成了特征值以及标签的提取以及权重的初始化
    int t = 0;//t代表次数
    int N = 3000;
    srand(time(0));
    while(t++<N){
        int i = getRand(0,DataSize-1);//随便找一个点
        double l = w.dot(mat.row(i));//点乘
        if(l*v(i)>0) continue;//这个点是正确的
        //按照公式w(t+1) = w(t) + yn(t)*Xt;
        //w(0) += pace*v(i)*mat(i,0);w(1) += pace*v(i)*mat(i,1);w(2) += pace*v(i)*mat(i,2);
        w += pace*v(i)*mat.row(i);
    }
    VectorXd  y = mat*w;
    int sum = computeDiff(v,y);
    cout<<"time:"<<t<<endl;
    cout<<"W:"<<endl;
    cout<<w<<endl;
    cout<<"accuracy:"<<(DataSize-sum)*100.0/DataSize<<"%"<<endl;
    return 0;
}

上面关于sum是否需要计算的表述,其实是有问题的,如果确定数据是线性可分的,当然不需要跑sum = computeDiff(v,y),但是如果不确定是否线性可分,或者说数据集噪声点比较大,只能找到一条不错的犯错误最少的线,则sum必须要计算,而且还要保存sum最小时候的weight。

int main()
{
	int DataSize,weightSize;//DataSize代表数据集大小,weightSize代表特征值数量,也代表权值数量
	DataSize = 100;weightSize = 3;
    MatrixXd mat(DataSize, weightSize);
    readfile("DataSet.txt",mat,DataSize,weightSize);
    VectorXd v = mat.col(weightSize-1);//label在最后一列
    for(int i=0;i<DataSize;i++) v(i) = 2* v(i) - 1;//转化label为-1和1
    mat.col(weightSize-1) = MatrixXd::Constant(DataSize,1,1);
    VectorXd w(weightSize);//初始化权值
    VectorXd bestw(weightSize);//最佳权值
    int minLoss = DataSize;
    for(int i=0;i<weightSize;i++) w(i) = 0;
    double pace = 0.001;//步长设定为0.001
    //至此完成了特征值以及标签的提取以及权重的初始化
    int t = 0,sum = 0,mintime = 0;//t代表次数
    int i = 0;
    srand(time(0));
    int N = 3000;
    while(t++<N){
        VectorXd y = mat*w;//因为每次要计算整体错误率,所以要计算这个
        sum = computeDiff(v,y);
        if(sum<minLoss){
        	minLoss = sum;
        	bestw = w;
        	mintime = t;
        }
        i = getRand(0,DataSize-1);//随机找一个点
        if(y(i)*v(i)>0) continue;//这个点是正确的
        //按照公式w(t+1) = w(t) + yn(t)*Xt;
        //w(0) += pace*v(i)*mat(i,0);w(1) += pace*v(i)*mat(i,1);w(2) += pace*v(i)*mat(i,2);
        w += pace*v(i)*mat.row(i);
    }
    cout<<"time:"<<mintime<<endl;
    cout<<"bestW:"<<endl;
    cout<<bestw<<endl;
    cout<<"accuracy:"<<(DataSize-minLoss)*100.0/DataSize<<"%"<<endl;
    return 0;
}

跑出下面的结果:

其实PLA还没有完,上面实现还有不少不足,比如跑的次数N取多少,步长pace选多少,没有一个确定的标准。
在考虑这个之前,先温习一下高中知识。
点到直线的距离
2条直线的夹角
2条直线的夹角,不就是2个向量的夹角么。
PLA Fact
两个向量越接近,这两个向量的内积越大。
PLA Fact
额,数学不行,上面那个推导还是看不明白,还有下面的,先搁置,后面再分析。

这个博客有不错的证明,码了,有余力时再看。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TsubasaAngel

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值