我目前正在学习Andrej Karpathy的神经网络黑客指南。在《第2章:机器学习,二元分类》中,他给出了一个(非常基础的)SVM的例子。以下是Karpathy的代码:
var a = 1, b = -2, c = -1; // 初始参数for(var iter = 0; iter < 400; iter++) { // 随机选择一个数据点 var i = Math.floor(Math.random() * data.length); var x = data[i][0]; var y = data[i][1]; var label = labels[i];// 计算拉力 var score = a*x + b*y + c; var pull = 0.0; if(label === 1 && score < 1) pull = 1; if(label === -1 && score > -1) pull = -1;// 计算梯度并更新参数 var step_size = 0.01; a += step_size * (x * pull - a); // -a 是来自正则化 b += step_size * (y * pull - b); // -b 是来自正则化 c += step_size * (1 * pull);}
以下是我用Python编写的版本:
import numpyimport randomX = numpy.array([[1.2, 0.7], [-0.3, 0.5], [-3, -1], [0.1, 1.0], [3.0, 1.1], [2.1, -3]])labels = [1, -1, 1, -1, -1, 1]a = 1b = -2c = -1l = len(X)-1steps = 400for n in range(0, steps): i = random.randint(0, l) x = X[i][0] y = X[i][1] label = labels[i] if n == 0: for j in range(0, l+1): x = X[j][0] y = X[j][1] label = labels[j] score = a*x + b*y + c print x,",",y,"-->", label, "vs.", score score = a*x + b*y + c pull = 0.0 if label == 1 and score < 1: pull = 1 if label == -1 and score > -1: pull = -1 step_size = 0.01 a += step_size * (x * pull - a) b += step_size * (y * pull - b) c += step_size * (1 * pull) if n == steps-1: print "" for j in range(0, l+1): x = X[j][0] y = X[j][1] label = labels[j] score = a*x + b*y + c print x,",",y,"-->", label, "vs.", score
问题是,即使经过建议的400次迭代,对于某些向量,参数仍然无法产生正确的标签。
以下是400次迭代后的输出:
1.2 , 0.7 --> 1 vs. -0.939483353298-0.3 , 0.5 --> -1 vs. -0.589208602761-3.0 , -1.0 --> 1 vs. 0.6519534487050.1 , 1.0 --> -1 vs. -0.9218825861413.0 , 1.1 --> -1 vs. -1.445520773312.1 , -3.0 --> 1 vs. 0.896623596303
“–>”之后的第一个值是正确的标签,第二个值是得分,即学习到的标签。
除了第一个向量之外,所有向量/学习到的标签都是正确的(在符号正确的意义上)。
我不确定这是什么原因:是我代码中出了错吗?我检查了几次,但没有发现任何问题。还是我忘记了Python特定的某些东西?或者,最后,是否有与机器学习相关的理由导致在这种情况下无法学习到正确的标签。不过我对此表示怀疑,否则Karpathy得到正确结果就说不通了。
任何评论或帮助我解决这个问题都非常感激。
回答:
我认为我找到了问题所在:
(A) 你的数据集没有线性分割。
(B) Karpathy的“蒙特卡洛”梯度下降法在这种数据集上表现不佳。
(C) 你和Karpathy使用了不同的数据。
DATA SETSlabel Karpathy's yours 1 [1.2, 0.7] [1.2, 0.7] -1 [-0.3, -0.5] [-0.3, 0.5] 1 [3.0, 0.1] [-3, -1] -1 [-0.1, -1.0] [0.1, 1.0] -1 [-1.0, 1.1] [3.0, 1.1] 1 [2.1, -3] [2.1, -3]
你提供的数据集几乎有一条切割线(超平面),大致在 y = 1/3x + 1/2 附近,但最接近这条线的三个点一直在争论分割线的位置。结果证明,最佳的分割线明显不同,使得 [1.2, 0.7] 严重偏离了这条线,但对此感到非常不满。
原始数据有一条整洁的切割线,大致在 y = -3x + 1 附近,这个算法大致用(四舍五入后的)0.6x – 0.1y – 0.5 来近似它。
再次强调,这个算法寻找的是最小成本的拟合,而不是“纯”SVM中最宽的分离通道。即使确实存在一条整洁的切割线,这个算法也不会收敛到它;相反,它会以一种粗糙的方式到达一个可接受的解决方案的附近区域。
它选择一个随机点。如果该点被坚定地分类——得分符号正确且幅度大于1——则不会发生任何事情。然而,如果该点位于线的错误一侧,或者甚至离线太近以至于不舒服,那么它会拉动参数以获得更有利的处理。
除非通道宽度达到2个单位,否则在争议区域内的点将继续轮流将其推来推去。没有收敛标准…实际上,在某一点之后也没有收敛保证。
仔细查看Karpathy的代码:主算法对得分<1或>-1(取决于训练类别)的数据点进行更改。然而,评估算法如果结果的符号正确就宣告胜利。这是合理的,但它与训练函数并不完全一致。在我的试验中,第一个点总是有一个幅度<0.07的得分,但实际值在0的两侧摇摆不定。其他的点都远离0,但只有两个点通过了1。有四个点在争论线应该在哪里。
这是否为你解答了疑惑?