摘要: | 本文主要介绍了一个使用svm实现一个简单的数字手写识别的例子。首先讲到如何将svm应用到多类分类,又简单介绍了高斯核函数,之后又简单介绍了本例所使用的数据来源和格式,最后贴出来实现的代码。 |
前面两篇blog介绍了支持向量机SVM和SMO算法,这一篇就讲讲SVM的一个简单实际应用:使用svm实现一个简单的数字手写识别软件。首先要解决如何使用svm进行多类分类。
svm与多类分类1
svm是一个二类的分类器,即它只回答属于正类还是负类的问题。而现实中要解决的问题,往往是多类的问题,比如文本分类,比如数字识别。如何由两类分类器得到多类分类器, 一般有三种方法:
一对多(一对其余)
比如我们有5个类别,第一次就把类别1的样本定为正样本,其余2,3,4,5的样本合起来定为负样本,这样得到一个两类分类器。 通过类似的方法构造类别2、3、4、5的分类器,对于测试数据,每一个分类器都过一次,在那个分类器中,被判定为正,那么就认识它属于那个分类。 但有两个问题还没有解决:一是被多个分类器判定为正,二是被所有分类器判定为负。第一种情况,可以简单地选择第一个被判定为正的分类,对二种情况,可以视为分类失败了。另外一个问题是,“其余”的那一类样本数总是要数倍于正类(因为它是除正类以外其他类别的样本之和嘛),这就人为的造成了的“数据集偏斜”问题。
一对一
这种方法需要构造n*(n-1)/2个分类器。简单地可以理解为所有分类1v1进行pk,具体做法,还是每次选一个类的样本作正类样本,而负类样本则变成只选一个类,(例如构造一个1 pk 2的分类器,它的正样本为1的分类,负样本为2的分类)这就避免了偏斜。 因此过程就是算出这样一些分类器,第一个只回答“是第1类还是第2类”,第二个只回答“是第1类还是第3类”,第三个只回答“是第1类还是第4类”,如此下去,所以一共有5*(5-1)/2=10个分类器。
虽然分类器的数目多了,但是在训练阶段(也就是算出这些分类器的分类平面时)所用的总时间却比“一类对其余”方法少很多,在真正用来分类的时候,把一个测试数据扔给所有分类器,第一个分类器会投票说它是“1”或者“2”,第二个会说它是“1”或者“3”,让每一个都投上自己的一票,最后统计票数,如果类别“1”得票最多,就判这篇文章属于第1类。但这有个问题,分类器的数量是类别数量的平方,例如,类别数如果是1000,要调用的分类器数目会上升至约500,000个(类别数的平方量级)。
DAG SVM
这种方法是构造一个DAG SVM,(有向无环的svm)。 还是像一对一方法那样来训练,只是在对一篇文章进行分类之前,先按照下面图的样子来组织分类器这样在分类时,我们就可以先问分类器“1对5”(意思是它能够回答“是第1类还是第5类”),如果它回答5,我们就往左走,再问“2对5”这个分类器,如果它还说是“5”,我们就继续往左走,这样一直问下去,就可以得到分类结果。
好处在哪?我们其实只调用了4个分类器(如果类别数是k,则只调用k-1个),分类速度飞快,且没有分类重叠和不可分类现象!缺点在哪?假如最一开始的分类器回答错误(明明是类别1的文章,它说成了5),那么后面的分类器是无论如何也无法纠正它的错误的(因为后面的分类器压根没有出现“1”这个类别标签),其实对下面每一层的分类器都存在这种错误向下累积的现象。 不过不要被DAG方法的错误累积吓倒,错误累积在一对其余和一对一方法中也都存在,DAG方法好于它们的地方就在于,累积的上限,不管是大是小,总是有定论的,有理论证明。而一对其余和一对一方法中,尽管每一个两类分类器的泛化误差限是知道的,但是合起来做多类分类的时候,误差上界是多少,没人知道,这意味着准确率低到0也是有可能的,这多让人郁闷。
而且现在DAG方法根节点的选取(也就是如何选第一个参与分类的分类器),也有一些方法可以改善整体效果,我们总希望根节点少犯错误为好,因此参与第一次分类的两个类别,最好是差别特别特别大,大到以至于不太可能把他们分错;或者我们就总取在两类分类中正确率最高的那个分类器作根节点,或者我们让两类分类器在分类的时候,不光输出类别的标签,还输出一个类似“置信度”的东东,当它对自己的结果不太自信的时候,我们就不光按照它的输出走,把它旁边的那条路也走一走,等等。
高斯核函数
这个手写识别svm的核函数采用了高斯核函数。高斯核函数的公式如下
其中的径向基函数的宽度$\delta$对分类器的性能比较敏感,对取不同的$\delta$时,高斯核支持向量机的性能进行分析,若$\delta \to 0 $ ,则所有的训练样本点都是支持向量,且它们全部能被正确的分类,但容易出现“过学习”的现象,推广能力较差,对测试样本的错误识别率较高;若$\delta \to \infty$ ,高斯核支持向量机对所有样本一视同仁,推广能力或对测试样本的正确判别能力为零,即它把所有样本点判为同一类。 实际上,当$\delta$取比训练样本点之间的平均距离小得多时,就能达到$\delta \to 0 $的效果;当$\delta$取比训练样本点之间的平均距离大得多时,就能达到$\delta \to \infty$的效果。 在确定高斯径向基函数的宽度$\delta$时,最基本的方法是对$\delta$取不同的值,然后分别采用支持向量机方法进行训练,选择最小分类错误率的一组$\delta$参数。比较典型的方法有梯度下降法与交叉验证法。2
数据
我实现的这个数学手写识别的数据是“Machine Learning in Action”的第二章的数据,可以下载这本的source code获得。解压后的路径为machinelearninginaction/Ch02/digits.zip。
将digits.zip解压后就可以得到训练数据和测试数据。这些都是原始数据经过处理后的数据,是一个各个文件,一个文件是一个手写数据,文件名像这样”0_52.txt”,签名的0表示这个手写数据对应的数字,后面的数字52,是0这一类手写数据的文件编号。书写数据如下图所示,是一个32*32的黑白位图。
在处理手写数据时,例子中是将它们存放到一个32*32的矩阵中,也可以存放到一个有1024的一维数组。计算高斯核函数的${\left \| {x_i} - {x_j} \right \|} ^2 $如下,以一维数组为例:
实现
我实现了一个简单的基于svm的数字手写识别的python脚本,多类分类的模型采用的是一对多(一对其余)的方式,所以一共构造了10个分类器,分别对应0到9。算法采用的是前一篇blog讲的SMO,算法的实现也基本是一样的。下面是具体的代码,可以下载github上的代码和数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
|
训练数据一共是1934个,测试数据是946,一共是识别出来了914个,其中有911个识别正确。运行结果的截图如下:
在实现的过程中,第一个版本效率很低,主要是做了两个地方的改进,一是,缓存了核函数的运算结果,二是,计算预测值的与真实值之差$E_i$,改用增量变化的方式,即每次只计算出$new\_a_i$, $new\_a_j$对$E_i$的改变量。 同样一开始,还有识别率低的问题,主要通过配置精度,惩罚系数,$a_i$改变最小步长,这三个参数来提高识别率。但还是对高斯核函数的径向基函数的宽度$\delta$的调整最有效果,针对本例最优情况是当$\delta=10$左右。
不足
本文实现的例子,还有很多不足之处:
- 样本数据的偏移,训练时,正负样本的数量应该是相当的。本文中的例子,负样本是正样本的9倍。
- 高斯径向基函数的宽度,没有经过训练
- 选择第二个变量时,本文使用的是遍历方式,也可以改成寻找改变量最大的第二个参数,具体《统计学习方法》的P129
- 核函数计算时,效率比较低,主要影响测试分类时的速度,如果能用到位运算,应该效率会高很多。
其实上面的情况都还可以慢慢改进,最大的坑还是训练的时间太长了,我在虚拟机上装的ubuntu server运行的脚本,训练数据接近2000个,测试数据1000个,总共可能花了4个小时左右,时间太长。我之前的同事实现的ocr是居于online learing的,那么我们下一篇blog也来实现一个online learning的数字手写ocr,与今天这个对比一下。
-
主要参考了:SVM入门(十)将SVM用于多类分类。↩
-
张翔,肖小玲,徐光祐,一种确定高斯核模型参数的新方法,计算机工程,第33卷,第12期,2007年6月。↩