这篇笔记要记录的算法是 NB-SVM ,NB 是 Naive Bayes ,即把 NB 和 SVM 结合为一个算法来使用。
Kaggle 前面结束了一场 Toxic Comments Tagging(https://www.kaggle.com/c/jigsaw-toxic-comment-classification-challenge),因为我啥都不会嘛,所以只能等比赛结束跟着 Kernal 区大佬学学方法技巧。Fast.ai 的讲师 Jeremy 给出了一个很漂亮的 baseline(https://www.kaggle.com/jhoward/nb-svm-strong-linear-baseline),使用了这种 NB-SVM 的变体。基本没有改动的情况下得分 0.96+,作为 baseline 来说还凑合了。Fast.ai 的 Machine Learning 课程也在制作中, Jeremy 讲东西很有一套,上课形式和内容都很不错,真想学这类东西可以好好学学他们的公开课,比报那些不着四六的培训班靠谱多了。
NB-SVM 出自 2012 年斯坦福的一篇论文(https://nlp.stanford.edu/pubs/sidaw12_simple_sentiment.pdf),思想是把 NB 和 SVM 的长处综合一下,构造一个简单的线性模型。在几个情感分析的数据集上结合 bigram 得到的效果不错。
方法也很简单,就是用 NB 和 SVM 的思想来构造一个线性分类器:
具体可以拆解成四个部分:
1. 构造权重向量 r r :
其中 p=α+∑i:y(i)=1f(i) p = α + ∑ i : y ( i ) = 1 f ( i ) , q=α+∑i:y(i)=−1f(i) q = α + ∑ i : y ( i ) = − 1 f ( i ) , α α 是平滑系数, f(i)j f j ( i ) 是第 i i 个样本的特征向量的第 个特征出现的次数。这样求出的向量 r r 称为 log-count ratio,对数计数比?
简单来说就是求正负样本上所有特征出现的频率的比值的对数。这就是模型中的 NB 部分,只不过对二分类问题用正负样本特征的频率比直接替代了分别求概率然后比较的步骤。
2. 多项式 NB
在 MNB 中,, b=log(N+/N−) b = log ( N + / N − ) ,两个 N 分别是正样本和负样本的数量。作者补充说另一篇文献发现二值化的 f(k) f ( k ) 效果更好,所以 f(k) f ( k ) 可以做个二值化。
这一步可以看作上一步的后续优化。
3. SVM
把带 L2 约束的 SVM 的优化目标函数写出来:
经验证 L2-loss 比 L1-loss 表现好,而且稳定。实现时使用了 LibLinear 库。
4. SVM + NB features
怎么把 NB 和 SVM 搓到一起的呢?
目标函数还是 SVM 的 L2-loss,但 x(k) x ( k ) 这里变成了 r^∘f^(k) r ^ ∘ f ^ ( k ) 。这个圈表示对应位置元素相乘,即所谓的 element-wise product,实际上就是二值化后的输入又加入了 NB 分类权重。
作者说还可以在 MNB 和 SVM 间做个均衡:
w¯ w ¯ 是归一化后的 w w , 称为内插参数,可以理解为一种正则化:相信 NB 的判断,除非 SVM 置信度非常高。
我看到这还是觉得有点乱,为什么把权重归一化了?这个式子怎么就体现了平衡两种算法?主要是这个 w w 到底是哪个模型计算出的 ,没看明白。好在作者有 matlab 平台的开源代码,有兴趣的可以看看。我没啥兴趣,只介绍一下 Jeremy 怎么用的。
Jeremy 把 SVM 的部分替换成对率回归,把二值化的 bigram 特征换成了 tf-idf。全部过程参看https://www.kaggle.com/jhoward/nb-svm-strong-linear-baseline,我只说他 NB-SVM 的部分怎么做的。
变换出 tf-idf 矩阵后,首先是计算先验 p/q:
def pr(y_i, y):
p = x[y == y_i].sum(0)
return (p+1)/((y==y_i).sum()+1)
然后得到模型:
def get_mdl(y):
y = y.values
r = np.log(pr(1, y)/pr(0, y))
m = LogisticRegression(C=4, dual=True)
x_nb = x.multiply(r)
return m.fit(x_nb, y), r
计算类别所属的时候:
m, r = get_mdl(train_data)
preds = m.predict_proba(test_x.multiply(r))[:, 1]
注意上面代码不能直接运行,我做了一点改动以方便说明。
可以看到,Jeremy 版的 NB-LR 跟 NB-SVM 步骤基本一致:计算权重系数
r
r
<script type="math/tex" id="MathJax-Element-24">r</script>,按元素乘到特征向量上去得到新的输入,拟合出一个 LR 模型(使用了 scikit-learn)。Jeremy 表示 tf-idf 表现比二值化的 bigram 特征效果好得多。
有人提到按这个跑出来的结果是 0.977,跟我的不太一样,不知道为什么。
看起来 Jeremy 的改动挺随意的,但结果没什么大问题,还是对算法理解透彻啊。所以做算法还是要有好的基本功,这些东西都非常熟练、理解透了,才能做到这种随手写 baseline 的境界……
《机器学习技法》里讲过一个 probabilistic SVM,先用 SVM kernel 方法做一个变换,然后用 LR 做分类。思想跟这个比较相似。