我们在之前的文章已经介绍了机器学习的一些基础概念,当拿到一个数据之后如何处理、如何评估一个模型、以及如何对模型调参等。接下来,我们正式开始学习如何实现机器学习的一些算法。 回归和分类是机器学习的两大最基本的问题,对于分类算法的详细理论部分。 本文主要从python代码的角度来实现分类算法。
# 导入相关库
import sklearn
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
下面我们以mnist
数据集为例进行演示,这是一组由美国人口普查局的高中生和雇员手写的70000个数字图像。每个图像都用数字表示。也是分类问题非常经典的一个数据集
# 导入mnist数据集
from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784', version=1, as_frame=False)
mnist.keys()
dict_keys(['data', 'target', 'frame', 'categories', 'feature_names', 'target_names', 'DESCR', 'details', 'url'])
其中data
是我们输入的特征,target
是0-9
的数字
X, y = mnist["data"], mnist["target"]
X.shape,y.shape
((70000, 784), (70000,))
可以看出一共有70000图像,其中X一共有784个特征,这是因为图像是28×28
的,每个特征是0-255之间的。下面我们通过imshow()
函数将其进行还原
%matplotlib inline
import matplotlib as mpl
digit = X[0]
digit_image = digit.reshape((28, 28))#还原成28×28
plt.imshow(digit_image, cmap=mpl.cm.binary)
plt.axis("off")
plt.savefig("some_digit_plot")
plt.show()
从我们人类角度来看,我们很容易辨别它是5,我们要做的是,当给机器一张图片时,它能辨别出正确的数字吗?我们来看看y的值
y[0]
'5'
我们要实现的就是,给我们一张图片,不难发现这是一个多分类任务,下面我们正式进入模型建立,首先将数据集划分为训练集和测试集,这里简单的将前60000
个划分为训练集,后10000
个为测试集,具体代码如下
y = y.astype(np.uint8)#将y转换成整数
X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]
在实现多分类任务之前,我们先从一个简单的问题考虑,现在假设我只想知道给我一张图片,它是否是7(我最喜欢的数字)。这个时候就是一个简单的二分类问题,首先我们要将我们的目标变量进行转变,具体代码如下
y_train_7 = (y_train == 7)
y_test_7 = (y_test == 7)
现在,我们选择一个分类器并对其进行训练。我们先使用SGD(随机梯度下降)分类器
from sklearn.linear_model import SGDClassifier
sgd_clf = SGDClassifier(max_iter=1000, tol=1e-3, random_state=123)#设置random_state为了结果的重复性
sgd_clf.fit(X_train, y_train_7)
SGDClassifier(random_state=123)
训练好模型之后我们可以进行预测,以第一张图片为例,我们预测一下它是否是7(很显然我们知道不是)
sgd_clf.predict(X[0].reshape((1,-1)))
array([False])
我们根据分类评估指标来看看SGD
分类器效果
from sklearn.model_selection import cross_val_score
cross_val_score(sgd_clf, X_train, y_train_7, cv=3, scoring="accuracy")
array([0.97565, 0.97655, 0.963 ])
y_train_pred = sgd_clf.predict(X_train)
from sklearn.metrics import confusion_matrix
confusion_matrix(y_train_7, y_train_pred)
array([[53304, 431],
[ 550, 5715]], dtype=int64)
from sklearn.metrics import precision_score, recall_score
print('precision:',precision_score(y_train_7, y_train_pred))
print('recall:',recall_score(y_train_7,y_train_pred))
precision: 0.929873088187439
recall: 0.9122106943335994
from sklearn.metrics import roc_curve
fpr, tpr, thresholds = roc_curve(y_train_7, y_scores)
plt.plot(fpr, tpr, linewidth=2)
plt.plot([0, 1], [0, 1], 'k--')
plt.axis([0, 1, 0, 1])
plt.xlabel('False Positive Rate (Fall-Out)', fontsize=16)
plt.ylabel('True Positive Rate (Recall)', fontsize=16)
plt.grid(True)
在上部分文章中,我们介绍了如何对mnist
数据集建立一个二分类模型,我们当时解决的问题是给我一张图片,判断是否是数字7,但是我们不仅仅对数字7感兴趣,我们希望给我一张任意的图片,计算机能告诉我这张图片是数字几。这是一个多分类问题。一些算法(如SGD
分类器、 随机森林分类器和朴素贝叶斯分类器)能处理多个类。其他(如logistic回归
)是严格的二元分类器。但是我们可以通过一些策略来实现使用二分类器进行多分类
OvR
:一种方法是对于0-9
十个类别,我们对每个类建立一个二分类器。判断是否属于该类,具体实现方法是,给我一张图片,分别使用这十个分类器预测属于该类的概率。选择概率最大的那一类作为预测结果OvO
:另一种方法是对于0-9
十个类别,每一次选两个类别进行比较,比较属于哪一类的概率更大。对于minist
数据集,则必须在所有45个分类器进行比较,看看哪个类赢的最多。OvO
的主要优点是,每个分类器只需要在训练集的一部分进行训练,即选择需要区分的两个类的数据集。然而,对于大多数二进制分类算法,OvR
是首选。当我们使用二分类器来处理多分类任务时,sklearn
会自动选择OvO
或者OvR
来处理。例如我们以支持向量机(SVM
)为例
# 导入数据集
from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784', version=1, as_frame=False)
import numpy as np
X, y = mnist["data"], mnist["target"]
y = y.astype(np.uint)#更改y数据类型为整数
# 将数据划分为测试集和训练集
X_train,X_test,y_train,y_test = X[:6000],X[6000:],y[:6000],y[6000:]
from sklearn.svm import SVC
svm_clf = SVC(gamma="auto", random_state=123)
svm_clf.fit(X_train, y_train) # y_train
svm_clf.predict([X[0]])
array([5], dtype=uint32)
还记得,我们在分类算法上介绍的,第一张图片是数字5,预测正确. 其实SVC
默认是采用了OvR
策略,我们通过decision_function
可以看到每一个样本有10个scores
some_digit_scores = svm_clf.decision_function([X[0]])
some_digit_scores
array([[ 1.8249344 , 8.01830986, 0.81268669, 4.8465137 , 5.87200033,
9.29462954, 3.8465137 , 6.94086295, -0.21310287, 2.83645231]])
可以看出,最大的是5
np.argmax(some_digit_scores)
5
# 查看一共有几类
svm_clf.classes_
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint32)
注意:训练分类器时,它会将目标类列表按值排序存储在其classes_
属性中。在这种情况下,classes_
数组中每个类的索引都可以方便地匹配类本身。在本例中,索引5处的类恰好是类5
下面我们使用随机森林
模型看看结果
from sklearn.ensemble import RandomForestClassifier
rf_clf = RandomForestClassifier(random_state=123)
rf_clf.fit(X_train, y_train) # y_train
rf_clf.predict([X[0]])
array([5], dtype=uint32)
首先看看混淆矩阵。需要使用Cross_val_predict
函数进行预测,然后调用confusion_matrix()
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import cross_val_predict
首先这里我将X进行标准化处理
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train.astype(np.float64))
y_train_pred = cross_val_predict(svm_clf, X_train_scaled, y_train, cv=3)
conf_mx = confusion_matrix(y_train, y_train_pred)
conf_mx
array([[576, 0, 4, 2, 3, 2, 2, 0, 3, 0],
[ 0, 649, 9, 1, 3, 1, 0, 3, 4, 1],
[ 4, 5, 531, 7, 8, 2, 3, 9, 11, 1],
[ 0, 5, 28, 542, 2, 14, 1, 9, 5, 2],
[ 0, 2, 14, 0, 578, 1, 2, 6, 0, 20],
[ 3, 4, 9, 16, 7, 450, 10, 7, 3, 5],
[ 3, 2, 23, 0, 2, 7, 567, 2, 2, 0],
[ 2, 8, 14, 0, 7, 0, 0, 593, 0, 27],
[ 4, 7, 15, 8, 2, 15, 6, 2, 488, 4],
[ 4, 2, 9, 7, 13, 2, 0, 25, 3, 536]], dtype=int64)
这是有很多类。使用Matplotlib
的matshow()
函数查看混淆矩阵的图像表示通常更方便:
plt.matshow(conf_mx, cmap=plt.cm.gray)
plt.show()
这个混淆矩阵看起来不错,因为大多数图像都在主对角线上,这意味着它们被正确分类。5比其他数字略暗,这可能意味着数据集中5的图像较少,或者分类器在5上的性能不如其他数字。现在我们来比较错误率。
row_sums = conf_mx.sum(axis=1, keepdims=True)#计算数量
norm_conf_mx = conf_mx / row_sums#计算错误率的混淆矩阵
np.fill_diagonal(norm_conf_mx, 0)
plt.matshow(norm_conf_mx, cmap=plt.cm.gray)
plt.show()
注意,行代表正确的类,列代表预测的列,可以看出2这个数字这一列很亮,说明有很多其他类被误判为2,但是2这一行却又错判为其他类。通过分析混淆矩阵可以让我们深入了解改进分类器的方法。本例中可以先优化数字2,来减少其他数字对2的错判。例如,您可以尝试为看起来像(但不是)的数字收集更多的训练数据,以便分类器可以学习将它们与真实的2区分开来。或者你可以设计一些新的特性来帮助分类器,例如,编写一个算法来计算每个数字圆圈的数量(例如,8有两个,6有一个,5没有)。或者,你可以对图像进行预处理(例如,使用Scikit Image
、Pillow
或OpenCV
),以使某些图案(例如闭合环)更加突出。
分析单个错误也是一种很好的方法,可以了解分类器正在做什么,以及它失败的原因,但这更困难、更耗时。例如,让我们绘制数字5和3
def plot_digits(instances, images_per_row=10, **options):
size = 28
images_per_row = min(len(instances), images_per_row)#每一行的数字
n_rows = (len(instances) - 1) // images_per_row + 1
n_empty = n_rows * images_per_row - len(instances)
padded_instances = np.concatenate([instances, np.zeros((n_empty, size * size))], axis=0)
image_grid = padded_instances.reshape((n_rows, images_per_row, size, size))
big_image = image_grid.transpose(0, 2, 1, 3).reshape(n_rows * size,
images_per_row * size)
plt.imshow(big_image, cmap = mpl.cm.binary, **options)
plt.axis("off")
cl_a, cl_b = 3,5
X_aa = X_train[(y_train == cl_a) & (y_train_pred == cl_a)]
X_ab = X_train[(y_train == cl_a) & (y_train_pred == cl_b)]
X_ba = X_train[(y_train == cl_b) & (y_train_pred == cl_a)]
X_bb = X_train[(y_train == cl_b) & (y_train_pred == cl_b)]
plt.figure(figsize=(8,8))
plt.subplot(221); plot_digits(X_aa[:25], images_per_row=5)#每一行五个数字
plt.subplot(222); plot_digits(X_ab[:25], images_per_row=5)
plt.subplot(223); plot_digits(X_ba[:25], images_per_row=5)
plt.subplot(224); plot_digits(X_bb[:25], images_per_row=5)
plt.show()
上面一行第二张图是错把3误判为5,第二行第一幅图是错把5判为3的情况
到目前为止,每个分类器都是分给一个类,在某些情况下,我们可能希望一个分类器输出多个类,例如一个人脸识别器;如果它能识别一个图片多个人,那么这就是一个多标签分类器。下面我们照样以mnist
数据集为例, 假设此时我们的目标一个是大于7的数,另一个是偶数。下面使用KNN算法为例
from sklearn.neighbors import KNeighborsClassifier
y_train_large = (y_train >= 7)
y_train_odd = (y_train % 2 == 0)
y_multilabel = np.c_[y_train_large, y_train_odd]
knn_clf = KNeighborsClassifier()
knn_clf.fit(X_train, y_multilabel)
KNeighborsClassifier()
knn_clf.predict([X[0]])
array([[False, False]])
我们知道第一个数是5,它小于7并且不是偶数,因此两个返回值都是False
小编是一名Python开发工程师,自己整理了一套 【最新的Python系统学习教程】,包括从基础的python脚本到web开发、爬虫、数据分析、数据可视化、机器学习等。
保存图片微信扫描下方CSDN官方认证二维码免费领取【保证100%免费
】
如果你是准备学习Python或者正在学习,下面这些你应该能用得上:
Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
工欲善其事必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。
我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
用通俗易懂的漫画,来教你学习Python,让你更容易记住,并且不会枯燥乏味。
这份完整版的Python全套学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费
】
CSDN大礼包:《Python入门资料&实战源码&安装工具】免费领取(安全链接,放心点击)