支持向量机c++实现

参考李航《统计学习方法》,以及 JC Platt《Sequential Minimal Optimization: A Fast Algorithm for Training Support Vector Machines》实现了一个简易的支持向量机求解方法,采用数据集如链接所示,若发现有问题或不足之处,还忘各位不吝赐教

数据集: http://download.csdn.net/download/wz2671/10172405

main.cpp


  
  
  1. #include <Windows.h>
  2. #include "SVM.h"
  3. #include "mat rix.h"
  4. #include "mat.h"
  5. #include <iostream>
  6. #pragma comment(lib, "libmat.lib")
  7. #pragma comment(lib, "libmx.lib")
  8. using namespace std;
  9. const int fn = 13;
  10. const int sn1 = 59;
  11. const int sn2 = 71;
  12. const int sn3 = 48;
  13. const int sn = 178;
  14. int readData(double* &data, double* &label)
  15. {
  16. MATFile *pmatFile = NULL;
  17. mxArray *pdata = NULL;
  18. mxArray *plabel = NULL;
  19. int ndir; //矩阵数目
  20. //读取数据文件
  21. pmatFile = matOpen( "wine_data.mat", "r");
  22. if (pmatFile == NULL) return -1;
  23. /*获取.mat文件中矩阵的名称
  24. char **c = matGetDir(pmatFile, &ndir);
  25. if (c == NULL) return -2;
  26. */
  27. pdata = matGetVariable(pmatFile, "wine_data");
  28. data = ( double *)mxGetData(pdata);
  29. matClose(pmatFile);
  30. //读取类标
  31. pmatFile = matOpen( "wine_label.mat", "r");
  32. if (pmatFile == NULL) return -1;
  33. plabel = matGetVariable(pmatFile, "wine_label");
  34. label = ( double *)mxGetData(plabel);
  35. matClose(pmatFile);
  36. }
  37. int main()
  38. {
  39. doubl e *data ;
  40. double *label;
  41. readData(data, label);
  42. //需要注意从.mat文件中读取出的数据按列存储
  43. double *d;
  44. double *l;
  45. SVM svm;
  46. //第一组数据集与第二组数据集 预处理
  47. l = new double[sn1 + sn2];
  48. for( int i= 0; i<sn1+sn2; i++)
  49. {
  50. if ( fabs(label[i] - 2)< 1e-3) l[i] = -1;
  51. else l[i] = 1;
  52. }
  53. d = new double[(sn1 + sn2)*fn];
  54. for ( int i = 0; i < fn; i++)
  55. {
  56. for ( int j = 0; j < sn1+sn2; j++)
  57. {
  58. d[j*fn + i] = data[i*sn + j];
  59. }
  60. }
  61. /*
  62. for (int i = 0; i < sn1 + sn2; i++)
  63. {
  64. for (int j = 0; j < fn; j++)
  65. {
  66. cout << d[i*fn + j] << ' ';
  67. }
  68. cout << endl;
  69. }
  70. */
  71. svm.initialize(d, l, sn1+sn2, fn);
  72. svm.SMO();
  73. cout << "数据集1和数据集2";
  74. svm.show();
  75. delete l;
  76. delete d;
  77. //第二组数据集与第三组数据集
  78. l = new double[sn2 + sn3];
  79. for ( int i = sn1; i < sn1 + sn2 + sn3; i++)
  80. {
  81. if ( fabs(label[i] - 2) < 1e-3) l[i-sn1] = 1;
  82. else if ( fabs(label[i] - 3) < 1e-3) l[i-sn1] = -1;
  83. }
  84. d = new double[(sn2 + sn3)*fn];
  85. for ( int i = 0; i < fn; i++)
  86. {
  87. for ( int j = sn1; j < sn; j++)
  88. {
  89. d[(j - sn1)*fn + i] = data[i*sn + j];
  90. }
  91. }
  92. svm.initialize(d, l , sn2+sn3, fn);
  93. svm.SMO();
  94. cout << "\n数据集2和数据集3";
  95. svm.show();
  96. delete l;
  97. delete d;
  98. //第一组数据集和第三组数据集
  99. l = new double[sn1 + sn3];
  100. for ( int i = 0; i < sn1 + sn2 + sn3; i++)
  101. {
  102. if ( fabs(label[i] - 1) < 1e-3) l[i] = 1;
  103. else if ( fabs(label[i] - 3) < 1e-3) l[i - sn2] = -1;
  104. }
  105. d = new double[(sn1 + sn3)*fn];
  106. for ( int i = 0; i < fn; i++)
  107. {
  108. for ( int j = 0; j < sn1; j++)
  109. {
  110. d[j*fn + i] = data[i*sn + j];
  111. }
  112. for ( int j = sn1 + sn2; j < sn; j++)
  113. {
  114. d[(j - sn2)*fn + i] = data[i*sn + j];
  115. }
  116. }
  117. svm.initialize(d, l, sn1 + sn3, fn);
  118. svm.SMO();
  119. cout << "\n数据集1和数据集3";
  120. svm.show();
  121. delete l;
  122. delete d;
  123. getchar();
  124. return 0;
  125. }

SVM.h


  
  
  1. /*
  2. 用支持向量机求解二分类问题
  3. 分离超平面为:w'·x+b=0
  4. 分类决策函数:f(x)=sign(w'·x+b)
  5. */
  6. #include <iostream>
  7. using namespace std;
  8. class SVM
  9. {
  10. private:
  11. int sampleNum; //样本数
  12. int featureNum; //特征数
  13. double **data; //存放样本 行:样本, 列:特征
  14. double *label; //存放类标
  15. double *alpha;
  16. //double *w; 对于非线性问题,涉及kernel,不方便算
  17. double b;
  18. double *gx;
  19. double s_max(double, double);
  20. double s_min(double, double);
  21. int secondAlpha(int);
  22. void computeGx();
  23. double kernel(int, int);
  24. void update(int , int ,double, double);
  25. bool isConvergence();
  26. bool takeStep(int, int);
  27. public:
  28. ~SVM();
  29. //初始化数据
  30. void initialize(double *, double *, int, int);
  31. //序列最小最优算法
  32. void SMO();
  33. double objFun(int);
  34. void show();
  35. };

SVM.cpp


  
  
  1. #include "SVM.h"
  2. #include <math.h>
  3. using namespace std;
  4. #define eps 1e-2 //误差精度
  5. const int C = 100; //惩罚参数
  6. SVM::~SVM()
  7. {
  8. if (data) delete[]data;
  9. if (label) delete label;
  10. if (alpha) delete alpha;
  11. if (gx) delete gx;
  12. }
  13. //d中为样本,每个样本按行存储; l标签(1或-1); sn样本个数; fn特征个数
  14. void SVM::initialize( double *d, double *l, int sn, int fn)
  15. {
  16. this->sampleNum = sn;
  17. this->featureNum = fn;
  18. this->label = new double[sampleNum];
  19. this->data = new double*[sampleNum];
  20. for ( int i = 0; i < sampleNum; i++)
  21. {
  22. this->label[i] = l[i];
  23. }
  24. for ( int i = 0; i < sampleNum; i++)
  25. {
  26. this->data[i] = new double[featureNum];
  27. for ( int j = 0; j < featureNum; j++)
  28. {
  29. data[i][j] = d[i*featureNum + j];
  30. }
  31. }
  32. alpha = new double[sampleNum] { 0};
  33. gx = new double[sampleNum] { 0};
  34. }
  35. double SVM::s_max( double a, double b)
  36. {
  37. return a > b ? a : b;
  38. }
  39. double SVM::s_min( double a, double b)
  40. {
  41. return a < b ? a : b;
  42. }
  43. double SVM::objFun( int x)
  44. {
  45. int j = 0;
  46. //选择一个0 < alpha[j] < C
  47. for ( int i = 0; i < sampleNum; i++)
  48. {
  49. if (alpha[i]> 0 && alpha[i] < C)
  50. {
  51. j = i;
  52. break;
  53. }
  54. }
  55. //计算b
  56. double b = label[j];
  57. for ( int i = 0; i < sampleNum; i++)
  58. {
  59. b -= alpha[i] * label[i] * kernel(i, j);
  60. }
  61. //构造决策函数
  62. double objf = b;
  63. for ( int i = 0; i < sampleNum; i++)
  64. {
  65. objf += alpha[i] * label[i] * kernel(x, i);
  66. }
  67. return objf;
  68. }
  69. //判断有无收敛
  70. bool SVM::isConvergence()
  71. {
  72. //alpah[i] * y[i]求和等于0
  73. //0 <= alpha[i] <= C
  74. //y[i] * gx[i]满足一定条件
  75. double sum = 0;
  76. for ( int i = 0; i < sampleNum; i++)
  77. {
  78. if (alpha[i] < -eps || alpha[i] > C + eps) return false;
  79. else
  80. {
  81. // alpha[i] = 0
  82. if ( fabs(alpha[i]) < eps && label[i] * gx[i] < 1 - eps) return false;
  83. // 0 < alpha[i] < C
  84. if (alpha[i] > -eps && alpha[i] < C + eps && fabs(label[i] * gx[i] - 1)>eps) return false;
  85. // alpha[i] = C
  86. if ( fabs(alpha[i] - C) < eps && label[i] * gx[i] > 1 + eps) return false;
  87. }
  88. sum += alpha[i] * label[i];
  89. }
  90. if ( fabs(sum) > eps) return false;
  91. return true;
  92. }
  93. //假装是个核函数
  94. //两个向量做内积
  95. double SVM::kernel( int i, int j)
  96. {
  97. double res = 0;
  98. for ( int k = 0; k < featureNum; k++)
  99. {
  100. res += data[i][k] * data[j][k];
  101. }
  102. return res;
  103. }
  104. //计算g(xi),也就是对样本i的预测值
  105. void SVM::computeGx()
  106. {
  107. for ( int i = 0; i < sampleNum; i++)
  108. {
  109. gx[i] = 0;
  110. for( int j= 0; j < sampleNum; j++)
  111. {
  112. gx[i] += alpha[j] * label[j] * kernel(i, j);
  113. }
  114. gx[i] += b;
  115. }
  116. }
  117. //更新很多东西
  118. void SVM::update( int a1, int a2, double x1, double x2)
  119. {
  120. //更新阈值b
  121. double b1_new = -(gx[a1] - label[a1]) - label[a1] * kernel(a1, a1)*(alpha[a1] - x1)
  122. - label[a2] * kernel(a2, a1)*(alpha[a2] - x2) + b;
  123. double b2_new = -(gx[a2] - label[a2]) - label[a1] * kernel(a1, a2)*(alpha[a1] - x1)
  124. - label[a2] * kernel(a2, a2)*(alpha[a2] - x2) + b;
  125. if ( fabs(alpha[a1]) < eps || fabs(alpha[a1] - C) < eps || fabs(alpha[a2]) < eps || fabs(alpha[a2] - C) < eps)
  126. b = (b1_new + b2_new) / 2;
  127. else
  128. b = b1_new;
  129. /*
  130. int j = 0;
  131. //选择一个0 < alpha[j] < C
  132. for (int i = 0; i < sampleNum; i++)
  133. {
  134. if (alpha[i]>0 && alpha[i] < C)
  135. {
  136. j = i;
  137. break;
  138. }
  139. }
  140. //计算b
  141. double b = label[j];
  142. for (int i = 0; i < sampleNum; i++)
  143. {
  144. b -= alpha[i] * label[i] * kernel(i, j);
  145. }
  146. */
  147. //更新gx
  148. computeGx();
  149. }
  150. //选取第二个变量
  151. /*
  152. 先选择是对应E1-E2最大的
  153. 若没有,用启发式规则,选目标函数有足够下降的alpha2
  154. 还没有,选择新的alpha1
  155. */
  156. int SVM::secondAlpha( int a1)
  157. {
  158. //先计算出所有的E,也就是样本xi的预测值与真实输出之差Ei=g(xi)-yi
  159. //若E1为正,选最小的Ei作为E2,反正选最大
  160. bool pos = (gx[a1] - label[a1] > 0);
  161. double tmp = pos ? 100000000 : -100000000;
  162. double ei = 0; int a2 = -1;
  163. for ( int i = 0; i < sampleNum; i++)
  164. {
  165. ei = gx[i] - label[i];
  166. if (pos && ei < tmp || !pos && ei > tmp)
  167. {
  168. tmp = ei;
  169. a2 = i;
  170. }
  171. }
  172. //对于特殊情况,直接遍历间隔边界上的支持向量点,选择具有最大下降的值
  173. return a2;
  174. }
  175. //选定a1和a2,进行更新
  176. bool SVM::takeStep( int a1, int a2)
  177. {
  178. if (a1 < -eps) return false;
  179. double x1, x2; //old alpha
  180. x2 = alpha[a2];
  181. x1 = alpha[a1];
  182. //计算剪辑的边界
  183. double L, H;
  184. double s = label[a1] * label[a2]; //a1 与 a2同号或异号
  185. L = s < 0 ? s_max( 0, alpha[a2] - alpha[a1]) : s_max( 0, alpha[a2] + alpha[a1] - C);
  186. H = s < 0 ? s_min(C, C + alpha[a2] - alpha[a1]) : s_min(C, alpha[a2] + alpha[a1]);
  187. if (L >= H) return false;
  188. double eta = kernel(a1, a1) + kernel(a2, a2) - 2 * kernel(a1, a2);
  189. //更新alpah[a2]
  190. if (eta > 0)
  191. {
  192. alpha[a2] = x2 + label[a2] * (gx[a1] - label[a1] - gx[a2] + label[a2]) / eta;
  193. if (alpha[a2] < L) alpha[a2] = L;
  194. else if (alpha[a2] > H) alpha[a2] = H;
  195. }
  196. else //我也不知道为什么这么算,我抄的论文里的,意思是选到超平面距离近的边界
  197. {
  198. alpha[a2] = L;
  199. double Lobj = objFun(a2);
  200. alpha[a2] = H;
  201. double Hobj = objFun(a2);
  202. if (Lobj < Hobj - eps)
  203. alpha[a2] = L;
  204. else if (Lobj > Hobj + eps)
  205. alpha[a2] = H;
  206. else
  207. alpha[a2] = x2;
  208. }
  209. //下降太少,忽略不计
  210. if ( fabs(alpha[a2] - x2) < eps*(alpha[a2] + x2 + eps))
  211. {
  212. alpha[a2] = x2;
  213. return false;
  214. }
  215. //更新alpha[a1]
  216. alpha[a1] = x1 + s*(x2 - alpha[a2]);
  217. update(a1, a2, x1, x2);
  218. /*
  219. for (int ii = 0; ii < sampleNum; ii++)
  220. {
  221. cout << gx[ii] << endl;
  222. }
  223. */
  224. return true;
  225. }
  226. //由SVM分类决策的对偶最优化问题求解alpha
  227. /*
  228. 用序列最小最优化算法(SMO)求解alpha
  229. step1:选取一对需要更新的变量alpha[i]和alpha[j]
  230. step2:固定alpha[i]和alpha[j]以外的参数,求解对偶问题的最优化解获得更新后的alpha[i]和alpha[j]
  231. 参考:李航《统计学习方法》
  232. JC Platt《Sequential Minimal Optimization: A Fast Algorithm for Training Support Vector Machines》
  233. */
  234. void SVM::SMO()
  235. {
  236. //bool convergence = false; //判断有没有收敛
  237. int a1, a2;
  238. bool Changed = true; //有没有更新
  239. int numChanged = 0; //更新了多少次
  240. int *eligSample = new int[sampleNum]; // 记录访问过的样本
  241. int cnt = 0; //样本个数
  242. computeGx();
  243. do
  244. {
  245. numChanged = 0; cnt = 0;
  246. //选择第一个变量(最不满足KKT条件的样本点)
  247. //优先选 0 < alpha < C 的样本 , alpha会随着后面的迭代发生变化
  248. for ( int i = 0; i < sampleNum; i++)
  249. {
  250. //记录下不满足KKT条件的样本,做个缓存
  251. if (Changed)
  252. {
  253. cnt = 0;
  254. for ( int j = 0; j < sampleNum; j++)
  255. {
  256. if (alpha[j] > eps && alpha[j] < C - eps)
  257. {
  258. eligSample[cnt++] = j;
  259. }
  260. }
  261. Changed = false;
  262. }
  263. if (alpha[i] > eps && alpha[i] < C-eps)
  264. {
  265. a1 = i;
  266. //不满足KKT条件
  267. if ( fabs(label[i] * gx[i] - 1) > eps)
  268. {
  269. //选择第二个变量,优先选下降最多的
  270. a2 = secondAlpha(i);
  271. Changed = takeStep(a1, a2);
  272. numChanged += Changed;
  273. if(Changed) continue;
  274. else //目标函数没有下降
  275. {
  276. //先依次遍历间隔边界上的
  277. for( int j= 0; j<cnt;j++)
  278. {
  279. if (eligSample[j] == i) continue;
  280. a2 = eligSample[j];
  281. Changed = takeStep(a1, a2);
  282. numChanged += Changed;
  283. if (Changed) break;
  284. }
  285. if (Changed) continue;
  286. //再遍历整个数据集
  287. int k = 0;
  288. for ( int j = 0; j < sampleNum; j++)
  289. {
  290. //这是上面已经试过的间隔上的点
  291. if (eligSample[k] == j)
  292. {
  293. k++;
  294. continue;
  295. }
  296. a2 = j;
  297. Changed = takeStep(a1, a2);
  298. numChanged += Changed;
  299. if (Changed) break;
  300. }
  301. //找不到合适的alpha2, 换一个alpha1
  302. }
  303. }
  304. }
  305. }
  306. if(numChanged) //已经有改变了
  307. {
  308. Changed = false;
  309. continue;
  310. }
  311. //选其他不满足KKT条件的样本
  312. for ( int i = 0; i < sampleNum; i++)
  313. {
  314. a1 = i;
  315. if ( fabs(alpha[i]) < eps && label[i] * gx[i] < 1 ||
  316. fabs(alpha[i] - C) < eps && label[i] * gx[i] > 1)
  317. {
  318. //选择第二个变量,步骤同上
  319. a2 = secondAlpha(i);
  320. Changed = takeStep(a1, a2);
  321. numChanged += Changed;
  322. if (Changed) continue;
  323. else //目标函数没有下降
  324. {
  325. //先依次遍历间隔边界上的
  326. //间隔边界上的点已经记录在eligSample中了
  327. for( int j= 0; j<cnt; j++)
  328. {
  329. if (eligSample[j] == i) continue;
  330. a2 = eligSample[j];
  331. Changed = takeStep(a1, a2);
  332. numChanged += Changed;
  333. if (Changed) break;
  334. }
  335. if (Changed) continue;
  336. //再遍历整个数据集
  337. int k = 0;
  338. for ( int j = 0; j < sampleNum; j++)
  339. {
  340. if (j == eligSample[k])
  341. {
  342. k++;
  343. continue;
  344. }
  345. a2 = j;
  346. Changed = takeStep(a1, a2);
  347. numChanged += Changed;
  348. if (Changed) break;
  349. }
  350. //找不到合适的alpha2, 换一个alpha1
  351. }
  352. }
  353. }
  354. /*
  355. // if (!Changed)
  356. {
  357. cout<<"num"<<numChanged<<endl;
  358. show();
  359. }
  360. //《统计学习方法》里说的收敛条件是这个,但不管用
  361. //所以改用JC Platt论文伪代码所提方法(也不是完全一样)
  362. convergence = isConvergence();
  363. //show();
  364. cnt++;
  365. if (cnt == 10000)
  366. {
  367. cout << "num" << numChanged << endl;
  368. show();
  369. }
  370. */
  371. } while (numChanged);
  372. delete eligSample;
  373. }
  374. void SVM::show()
  375. {
  376. cout << "支持向量为:" << endl;
  377. for ( int i = 0; i < sampleNum; i++)
  378. {
  379. if(alpha[i]>eps)
  380. cout <<i<< " 对应的alpha为:"<<alpha[i]<< endl;
  381. }
  382. cout << endl;
  383. }

结果如下图所示:


猜你喜欢

转载自blog.csdn.net/monk1992/article/details/88553959