要实现上面的,得做一些准备工作。首先矩阵运算少不了吧。
Eigen is a C++ template library for linear algebra: matrices, vectors, numerical solvers, and related algorithms
说人话就是:Eigen是一个用于线性代数的C++模板库:矩阵、向量、数值求解器和相关算法
首先去官网,由于笔者用的是win10,所以选择zip格式的。
下载完成后解压到自定义文件夹中,建议放到C盘,避免误删除
接下来就是在Code Blocks里面配置,也很简单,如下图
在setting-compiler-Search directories点击Add添加Eigen所在目录即可。
试运行一下:
#include#include 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#include 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#include 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是mn的矩阵,每一列代表一条数据,一共有n列,也就是n条数据;Xn代表第n条数据。
不管怎么组织数据,都要保证维数匹配,不然无法运算。
下面再看看sign是啥,该怎么实现:
也就是一个符号函数:
当x>0,sign(x)=1;
当x=0,sign(x)=0;
当x<0, sign(x)=-1;
再想想还需要什么,要从文件中读取数据到Matrix和Vector中,为了直观显示,还要将这些数据显示在平面坐标中。
方法1:C++ >>和<<读写文本文件
fstream 或者 ifstream 类负责实现对文件的读取,它们内部都对 >> 输出流运算符做了重载;同样,fstream 和 ofstream 类负责实现对文件的写入,它们的内部也都对 << 输出流运算符做了重载。
所以,当 fstream 或者 ifstream 类对象打开文件(通常以 ios::in 作为打开模式)之后,就可以直接借助 >> 输入流运算符,读取文件中存储的字符(或字符串);当 fstream 或者 ofstream 类对象打开文件(通常以 ios::out 作为打开模式)后,可以直接借助 << 输出流运算符向文件中写入字符(或字符串)。
#include#include 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#include // Save to local file. #include 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 for(int j=0;j
srcFile >> x; mat(i,j) = x; } } srcFile.close(); return 0; } int main() { MatrixXd mat(24, 3); readfile("data.txt",mat,24,3); cout< 正常情况下,一个数据集中,前 n-1列都是特征值x,最后一列是标签y,所以需要对Matrix进行预处理,把特征值和标签分开。
VectorXd v =mat.col(2);//把矩阵的第二列赋值给向量v取出来就没事了吗?非也,像笔者使用的数据集,标签是0和1,就得转化为-1和1
for(int i=0;i那要删除最后一列就更简单了
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#include // Save to local file. #include #include #include 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 for(int j=0;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 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:"< 上面关于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;iVectorXd y = mat*w;//因为每次要计算整体错误率,所以要计算这个 sum = computeDiff(v,y); if(sum 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:"< 跑出下面的结果:
其实PLA还没有完,上面实现还有不少不足,比如跑的次数N取多少,步长pace选多少,没有一个确定的标准。
在考虑这个之前,先温习一下高中知识。
2条直线的夹角,不就是2个向量的夹角么。
两个向量越接近,这两个向量的内积越大。
额,数学不行,上面那个推导还是看不明白,还有下面的,先搁置,后面再分析。
这个博客有不错的证明,码了,有余力时再看。



