摘要: 本文介绍机器学习相关的学习库Scikit Learn,包含其安装及具体识别手写体数字案例,适合机器学习初学者入门Scikit Learn。
在我科研的时候,机器学习(ML)是计算机科学领域中最先吸引我的一门学科。虽然这个概念非常简单,但是它表现优异。以Google、AirBnb和Uber为代表的高科技公司,已经将机器学习应用于他们的产品中。当我第一次尝试在工作中应用机器学习时,Scikit Learn库是一个很好的起点。
针对于Python语言开发的Scikit Learn,允许开发者们轻易地将机器学习集成到自己的项目中。我希望通过一个简单的Scikit Learn应用来教会大家学习,如果你刚接触Python,不要感到害怕,本文会有详细的代码注释讲解。
安装
在进行应用展示之前,需要安装Scikit Learn。首先确保下载并安装Python(本文使用Python 3)。除此之外,确保通过pip语句安装NumPy 和 SciPy。
pip install numpy
pip install scipy
剩下的安装过程很简单,一条语句命令即可完成Scikit Learn的安装。
pip install scikit-learn
只需花费一小段时间即可安装成功,为了读取所举案例的CSV 文件,还需要安装Pandas,同样只需一条语句即可完成安装:
pip install pandas
此时,我们已经完成全部安装!
程序
Scikit Learn在其主页上提供了丰富的使用案例,当我第一次使用该软件包时,我发现这个主页是非常有用的。为了在这里展示Scikit Learn,我打算实现一个识别数字手写体的分类器,数据集来自于UCI数据集(由11000张图片组成)。这个数据集来自44个参与者,每个参与者需要手写250个数字,并且数据集中的每张图片(也被称作样本)对应于0-9之间的一个手写数字。
每个样本用一个保存0到100之间的特征向量表示,这些值表示样本中每个像素的强度。鉴于每个样本大小为500x500,这样会造成特征向量很长以至于难以处理。为了解决这个问题,图像被重新采样以减少像素的数目,采样后的特征向量长度为16。
数字0到9将是分类器在分类过程中要考虑的类别集合,分类器将从30名参与者(约7500张)抽取样本,以学习每个数字类别的样本。剩余的样本将被保留以测试训练好后的分类器。每个样本已经通过人为分类,这也意味着测试集中的每个样本有着正确的分类(标签)。这使得能够通过比较预测值与实际标签值来确定分类器的性能。
训练数据集和测试数据集均由UCI的CSV文件提供,通过Pandas将这些文件导入Python中,命令如下:
import pandas as pddef retrieveData():trainingData = pd.read_csv("training-data.csv").as_matrix()testData = pd.read_csv("test-data.csv").as_matrix()return trainingData, testData
使用read_csv读取每个文件,以生成Pandas 数据框架,并使用as_matrix将其转换为Numpy数组以便后续使用。这些文件的每一行都对应着一个数字样本——由长度为16的特征向量组成,后面跟着对应的类别标签。将特征向量与类别标签分离有利于后续Scikit Learn的使用。
def separateFeaturesAndCategories(trainingData, testData):trainingFeatures = trainingData[:, :-1]trainingCategories = trainingData[:, -1:]testFeatures = testData[:, :-1]testCategories = testData[:, -1:]return trainingFeatures, trainingCategories, testFeatures, testCategories
预处理
Scikit Learn提供的绝大多数分类器对特征缩放比较敏感,每个特征向量中的值是0到100之间,没有一致的均值或方差。将这些特征向量进行缩放以满足零均值和方差为1的条件,这有助于分类器在训练和分类过程中能够识别任何数字类别的样本。这种预处理操作是机器学习中一个可选步骤,但我强烈推荐使用这种操作,有助于提升分类器的性能。使用Scikit Learn的预处理数据包中的StandardScalar能够完成预处理操作,这样证明该操作实现起来非常简单。首先允许缩放器拟合训练数据以学习未缩放特征是什么样,缩放器能够将训练和测试数据集中的特征转换为零均值和方差为1的特征向量。
from sklearn.preprocessing import StandardScalerdef scaleData(trainingFeatures, testFeatures):scaler = StandardScaler()scaler.fit(trainingFeatures)scaledTrainingFeatures = scaler.transform(trainingFeatures)scaledTestFeatures = scaler.transform(testFeatures)return scaledTrainingFeatures, scaledTestFeatures
分类
Scikit Learn提供了一系列适合我们需求的分类器,选择其中的随机梯度下降分类器(SGD)作为此次举例的分类器,这是因为我过去经常使用该分类器。首先,我们需要将分类器拟合训练数据集(即训练分类器)。然后,我们准备设置分类器以预测未见过的测试样本的标签。使用Scikit Learn,所有的这些操作只需要通过几行代码即可实现。
from sklearn.linear_model.stochastic_gradient import SGDClassifierdef classifyTestSamples(trainingFeatures, trainingCategories, testFeatures):clf = SGDClassifier()clf.fit(trainingFeatures, trainingCategories)predictedCategories = clf.predict(testFeatures)return predictedCategories
结果
有了预测值之后,就可以与文件中提供的类别标签进行比较。那么这里会有几个问题,分类器效果怎样?我们如何衡量分类器的效果?给定一个特定的衡量标准,我们在哪里设置阈值来区分不好的结果?为了回答前两个问题,可以参考Scikit Learn的分类器度量包。我从中挑选出四个指标,分别是准确率、精度、召回率和F1分数。
- 准确率:正确分类样本所占的百分比
- 精度:类别x中正确分类的样本数目占类别x总数的百分比
- 召回率:类别x中正确分类的样本数目占其本身与其它不是x类别样本数之和的百分数
- F1分数:精度(P)和召回率(R)的加权平均值 ,在Scikit Learn中定义为2 * (P * R) / (P + R)
Scikit Learn的accuracy_score函数能够得出分类器的准确率,剩余的三个指标通过classification_report得到,最终打印出每个类别的准确率、精度、召回率和F1分数,并提供平均值。
from sklearn.metrics import accuracy_score, classification_reportdef gatherClassificationMetrics(testCategories, predictedCategories):accuracy = accuracy_score(testCategories, predictedCategories)metrics_report = classification_report(testCategories, predictedCategories)print("Accuracy rate: " + str(round(accuracy, 2)) + "\n")print(metrics_report)
分类器运行时其指标总会有小的变化,有些情况下会得到很高的测试准确率。虽然这些预测值与文件提供的标签值可能相一致,但也会出现分类器对其工作缺乏信心的情况。每次运行时,分类器可能会得出不同的预测结果,这可能归结为针对特定数字的样本数量不足或分类器遇到了与训练集中有显著区别的字迹。考虑到这些变化,下面是SGD分类器的一组实验结果。
Accuracy rate: 0.84precision recall f1-score support0 0.98 0.84 0.90 3631 0.58 0.84 0.69 3642 0.97 0.81 0.88 3643 0.98 0.90 0.94 3364 0.95 0.93 0.94 3645 0.62 0.94 0.75 3356 1.00 0.96 0.98 3367 0.88 0.84 0.86 3648 0.85 0.76 0.80 3369 0.93 0.58 0.72 336avg / total 0.87 0.84 0.85 3498
对于第一次尝试而言,84%的准确率已经相当不错了。这也提醒了我们之前提到的问题中的第三个问题——我们在哪里设置区分好结果和坏结果的阈值?这是一个棘手的问题,因为这完全取决于分类器要实现的目标,每个人考虑的好坏区分标准都不一样。我们是否可以改进分类器,使其始终能够比这里观察到的结果更好吗?
我们可以做得更好吗?
答案是肯定的,并有很多选择需要考虑。首先,本次举例使用了基本的预处理操作,更复杂的缩放方法可能会进一步降低分类器的敏感度以提升相关指标。其次,本次举例实现的是一个基本的SGD分类器,而且使用的是Scikit Learn提供的默认参数,没有进行适当的调整。因此,我们可以改变训练数据的迭代次数(被称作epoch),防止分类器在每次迭代时打乱训练数据,或者是多次运行分类器,启用它的热启动属性,以便分类器回忆之前做出的预测。
同样值得考虑的是,我们只实现了Scikit Learn中提供的其中一种分类器。虽然SGD分类器足够完成文中所举的例子,但是我们也可以考虑尝试使用一些其它的分类器,比如LinearSVC或Multinomial Naive Bayes等。机器学习的乐趣在于:有很多参数变量需要考虑,调整这些参数可能会改善或恶化整个模型吗的性能。为任何机器学习问题寻找最佳解决方案都是一项艰巨的任务,需要通过不断尝试。
结论
以上是所举例子的全部内容,文中只是介绍了一些基本知识,而Scikit Learn提供了更多丰富的内容等待着大家的探索,可以借助于其主页找到很多有用的文档。对于希望查看完整代码或自行尝试的读者,可以在本人的Github上找到相应的CSV文件和Python代码。
作者信息
Ross Rhodes,软件开发工程师,擅长Java、Python。
文章原标题《Machine Learning with Scikit Learn》,作者:Ross Rhodes,译者:海棠,审阅:
原文链接