最近看完了 Willi Richert的《机器学习系统设计》。书虽然有点薄但也比较全,内容感觉有点偏文本处理,里面介绍了一些文本处理的方法和工具。综合起来看作为机器学习入门还是挺不错的,这里就简单记一下我做的笔记,方便回顾。书中的代码可以通过它说到的网站下载,这里是第4部分笔记。

第五章 KNN和逻辑回归

这一章给出的想法非常有意思,要做一个问答网站检测答案质量的分类器,它能够持续的判断用户的答案是否有劣质答案的迹象,从而引导用户提高答案的质量。

数据集

然而我并没有下载下来stackoverflow的数据,在书中给出的链接已经失效,书中给出的代码里面并没有提供下载数据的脚本,但我从网上找到了stackoverflow的数据的一个http://blog.stackoverflow.com/tags/cc-wiki-dump/ 这个网站里有关于stackoverflow exchange的一些内容,需要翻墙才能下载。

KNN方法

KNN的方法不是一种基于模型的方法,所以几乎可以学习任何数据。

特征抽取:由于分类器接受的必须是数值形式,所以必须对原始数据进行处理,这里文中的方法是检测一个答案的HTML链接数,数值越高越好。可以手写检测,也可以用工具,如BeautifulSoap解析HTML。除了链接数,代码的行数也是一个较好的选择,他标志着作者花费的时间可能会比较多。统计一下删除代码后的词语的数量也可以作为一个不错的特征。我们还统计了句子的平均词语个数,平均字符个数,大写形式的词语个数以及感叹号的个数。把这7个特征都交给KNN来进行分类,代码的形式类似下面这样:

1
2
knn = neighbors.KNeighborsClassifier()
knn.fit(X,Y) ## X是特征数组,包含了所有的特征;Y是标签

经过测试发现,KNN的效果并不好,正确率在50%左右,而且KNN的方法带来的明显的问题,一是导致了较低的实时分类性能,二是随着时间的增加,进入系统的帖子会越来越多。KNN方法必须在系统中存储所有的帖子,而我们得到的帖子越多,预测性能会越慢。这跟基于模型的方法是不同的,在那里我们会从数据中得到一个模型。我们怎么提升机器学习的效果呢?

调整性能的方法

提升效果,基本上有如下的选择:

  1. 增加更多的数据 也许我们并没有为学习算法提供足够的数据,因此增加更多的训练数据即可

  2. 考虑模型复杂度 也许模型还不够复杂,或者夜景太复杂了。在这种情况下,对于KNN,我们可以降低K值,使得较少的近邻被考虑进去,从而更好地预测不平滑的数据。我么也可以提高K值,来得到相反的效果。

  3. 修改特征空间 也许我们的特征集合不好。例如,我们可以改变当前特征的范围,或者设计新的特征。又或者,如果有特征和另外一些特征是别名关系,我们还可以删除一些特征。

  4. 改变模型 也许KNN并不适合我们的问题。无论我们让模型变得有多复杂,无论特征空间会变得多复杂,它永远也无法得到良好的预测效果。

机器学习中最容易出现的两个问题就是1.高偏差; 2.高方差

高偏差: 测试和训练误差很接近,但都处于难以接受的较大的数值上。 (欠拟合) 此时模型偏离数据太远了;在这种情况下,加入更多的训练数据明显不会有什么帮助,同样,删减特征也没有帮助,模型可能过于简单。往往参数较少的神经网络很容易欠拟合出现高偏差的问题。
解决高偏差的方法:
1.增加更多的特征 2. 让模型更为复杂或者尝试其他模型

高方差: 不同的数据测出来的误差很大,表现为训练误差同测试误差差异大。 (过拟合) 此时模型对于训练数据表现的太好,意味着我们的模型对于训练数据来说太复杂
解决高方差的方法:
在这种情况下,我们只能尝试获得更多数据或者降低模型复杂度。对于KNN来说,增大K使得更多的近邻被考虑进行,或者删掉一些特征。

knn的不足

(1)当不同的类包含的样本数量差异很大时,即有的类包含的样本比其他类样本多很多,有时候输入一个和小容量类同类型的新样本,该样本的k个邻居中大容量类的样本占大多数,从而导致误分类。这种情况可以采用加权的方法,按照样本容量对不同的类分配合适的权值。

(2)分类时需要先计算待分类样本和全体已知样本的距离,计算量大。针对这种情况可以对已知样本点进行剪辑,去除对分类作用不大的样本。

逻辑回归

我们假设一个特征有0.9的概率属于类别1,也就是说,$P(y=1)=0.9$,那么让步比就是$P(y=1)/P(y=0)=0.9/0.1=9$。也就是说,这个特征映射到类别1的机会是9:1.如果$P(y)=0.5$,那么这个样本属于1的机会将是1:1。让步比的范围是(0,+\infty),对他取对数就可以映射到整个实数域。这样的好处就是更高的概率对应于更高的让步比对数。

对于线性等式:

$$y_i=c_0+c_1x_i$$

我们可以用对数的让步比来替换:

$$\log(\dfrac{p_i}{1-p_i})=c_0+c_1x_i$$

我们可以从等式中求解出$p_i$,如下所示:

$$p_i=\dfrac{1}{1+e^{c_0+c_1x_i}}$$

通过找到合适的系数,我们便可以求出对于所有的数据对$(x_i,p_i)$中能给出的最小的误差,这个可以使用scikit-learn来实现。

1
2
3
4
5
6
7
8
>>> from sklearn.linear_model import LogisticRegression
>>> clf = LogisticRegression
>>> clf.fit(X,y)
>>> print np.exp(clf.intercept_), np.exp(clf.coef_.ravel())
>>> def lr_model(clf, X):
return 1 / (1 + np.exp(-(clf.intercept_ + clf.coef_*X)))
>>> print "P(x=-1)=%.2f\tP(x=7)=%.2f"%(lr_model(clf,-1)),lr_model(clf,7)
P(x=-1)=0.05 P(x=7)=0.85

准确率和召回率

准确率表示尽量少的不要分出错误的答案,召回率则表示尽量把所有的正确答案都分出来。

$$准确率=\dfrac{TP}{TP+FP}$$

$$召回率=\dfrac{TP}{TP+FN}$$

sklearn中已经有封装好的准确率和召回率的计算函数:

1
2
>>> from sklearn.metrics import precision_recall_curve
>>> precision, recall, thresholds = precision_recall_curve(y_test, clf.predict(X_test))

sklearn 中还能以报告的形式输出准确率和召回率的函数

1
2
3
>>> from sklearn.metrics import classification_report
>>> print(classification_report(y_test, clf.predict_proba[:,1] > 0.63,
targe_name=['not accepted','accepted']))