机器学习分类问题
一、简介
最常见的监督式学习任务包括回归问题(预测值)和分类问题(预测类),今天学习的内容就是监督学习中的分类问题,学习方法为通过经典的MNIST数据集分析来学习分类问题。
二、 数据集准备
网络上直接下载的数据需要进行处理以后需要进行一定的原数据需要进行处理(下面代码不需要看,与学习内容无关)需要数据集的评论区留言:
import numpy as np
import struct
import matplotlib.pyplot as plt
# 训练集文件
train_images_idx3_ubyte_file = './MNIST数据集/train-images-idx3-ubyte'
# 训练集标签文件
train_labels_idx1_ubyte_file = './MNIST数据集/train-labels-idx1-ubyte'
# 测试集文件
test_images_idx3_ubyte_file = './MNIST数据集/t10k-images-idx3-ubyte'
# 测试集标签文件
test_labels_idx1_ubyte_file = './MNIST数据集/t10k-labels-idx1-ubyte'
def decode_idx3_ubyte(idx3_ubyte_file):
# 读取二进制数据
bin_data = open(idx3_ubyte_file, 'rb').read()
# 解析文件头信息,依次为魔数、图片数量、每张图片高、每张图片宽
offset = 0
fmt_header = '>iiii'
magic_number, num_images, num_rows, num_cols = struct.unpack_from(fmt_header, bin_data, offset)
print('魔数:%d, 图片数量: %d张, 图片大小: %d*%d' % (magic_number, num_images, num_rows, num_cols))
# 解析数据集
image_size = num_rows * num_cols
offset += struct.calcsize(fmt_header)
fmt_image = '>' + str(image_size) + 'B'
images = np.empty((num_images, num_rows, num_cols))
for i in range(num_images):
if (i + 1) % 10000 == 0:
print('已解析 %d' % (i + 1) + '张')
images[i] = np.array(struct.unpack_from(fmt_image, bin_data, offset)).reshape((num_rows, num_cols))
offset += struct.calcsize(fmt_image)
return images
def decode_idx1_ubyte(idx1_ubyte_file):
# 读取二进制数据
bin_data = open(idx1_ubyte_file, 'rb').read()
# 解析文件头信息,依次为魔数和标签数
offset = 0
fmt_header = '>ii'
magic_number, num_images = struct.unpack_from(fmt_header, bin_data, offset)
print('魔数:%d, 图片数量: %d张' % (magic_number, num_images))
# 解析数据集
offset += struct.calcsize(fmt_header)
fmt_image = '>B'
labels = np.empty(num_images)
for i in range(num_images):
if (i + 1) % 10000 == 0:
print('已解析 %d' % (i + 1) + '张')
labels[i] = struct.unpack_from(fmt_image, bin_data, offset)[0]
offset += struct.calcsize(fmt_image)
return labels
def load_train_images(idx_ubyte_file=train_images_idx3_ubyte_file):
return decode_idx3_ubyte(idx_ubyte_file)
def load_train_labels(idx_ubyte_file=train_labels_idx1_ubyte_file):
return decode_idx1_ubyte(idx_ubyte_file)
def load_test_images(idx_ubyte_file=test_images_idx3_ubyte_file):
return decode_idx3_ubyte(idx_ubyte_file)
def load_test_labels(idx_ubyte_file=test_labels_idx1_ubyte_file):
return decode_idx1_ubyte(idx_ubyte_file)
X_train =np.reshape(np.asanyarray(load_train_images()),(-1,784)) #将3维数组降到2维
y_train =np.asanyarray(load_train_labels())
X_test =np.reshape(np.asanyarray(load_test_images()),(-1,784))
y_test=np.asanyarray(load_test_labels())
1. 数据介绍
MNIST数据集是一组由美国高中生和人口调查局员工手写的70000个数字的图片,每个图像都用其代表的数字标记。共有7万张图片,每张图片都有784个特征,每一个特征代表了一个像素的的强度,从0(白色)到255(黑色),下面具体展示了数据集中的一个数字,使用的是matplotlib的inshow函数:
import matplotlib
some_digit_image=train_images[36000] #转换为一个矩阵
plt.imshow(some_digit_image,cmap=matplotlib.cm.binary,interpolation="nearest")
plt.axis("off") #关闭坐标轴
plt.show()
运行结果为:
结果分析:选择train_images中第36000个元素,该元素为一个28*28的矩阵,每个矩阵元素都是0-255的数字表示颜色深度,选择相应位置设置合适的颜色深度就可以出现图中的效果。
API介绍:matplotlib下的imshow方法:imshow方法首先将二维数组的值标准化为0到1之间的值,然后根据指定的渐变色依次赋予每个单元格对应的颜色,就形成了热图。
2. 数据集划分
从官网下载完数据后会发现,MNIST官方已经将数据集分成了训练集(前6万张图片)和测试集(最后1万张图片)了。为了保证交叉验证时所有的折叠都差不多且有时候有些机器学习算法对训练实例的顺序敏感,如果连续输入许多相似的实例,可能会导致执行性能不佳,所以这里对数据集进行一个洗牌操作:
import numpy as np
shuffle_index=np.random.permutation(60000) #返回值是一个list,其值为0-60000的一个随机排列的序列
X_train,y_train=X_train[shuffle_index],y_train[shuffle_index]
三、训练一个二元分类器
由简入繁,首先我们这里先做一个简单的二分类分类器,使用 到的分类器是SGDClassifier(随机梯度下降分类器,这个分类器的优势是能够处理非常大型的数据集),解决的分类问题是“数字5的检测器”,即用来区分一个字符是5还是非5,具体的代码实现如下:
from sklearn.linear_model import SGDClassifier
y_train_5=(y_train==5)
y_test_5=(y_test==5) #这辆不操作将目标值的内容全变成了True or False (5和非5)
sgd_clf=SGDClassifier(random_state=42) #实例化一个分类器
sgd_clf.fit(X_train,y_train_5)#模型训练
sgd_clf.predict([X_train[36000]]) #判断训练集中的36000是否为5
运行结果:
结果分析:说明二分类模型生效,且判断的那个数据不是5(前面知道是9)
四、二分类器性能考核
评估分类器比评估回归器要困难的多,这里用到了基本上常用的几类性能考核的方法,分别有使用交叉验证测量精度、混淆矩阵、ROC曲线
1. 使用交叉验证测量精度
交叉验证将所有数据首先分为训练集和测试集合,再将训练集分为验证集和训练集,交叉验证的出现并不能提高模型的准确率,而是通过多次训练让我训练得出的模型的准确率更加准确可信。在《机器学习实战》这本书中在使用交叉验证之前还使用了分层抽样的思想来对模型进行评估,其基本想法是利用分层抽样的api将数据集分为3个部分,然后在每层抽取部分样本来进行模型训练,再计算准确率,也取得了较好的评估效果;下面使用交叉验证来评估上面的随机梯度下降分类器的效果:
from sklearn.model_selection import cross_val_score
cross_val_score(sgd_clf,X_train,y_train_5,cv=3,scoring="accuracy")
运行结果:
结果分析:可以发现所有的准确率都超过了95%
以上便是使用交叉验证得出的预测结果。但这时候我们需要思考一个问题,如果此时我们自己定义一个二分类器,这个分类器有一个特点,即将所有的图片都默认看作不是5,对该分类器进行评估,发现准确率也达到了90%(图片是5的很少),所以准确率通常无法成为分类器的首要性能指标,特别是我们在处理所谓的偏斜数据集的时候(即某些类比其他类更为频繁的出现——数据分布不均衡)
2.混淆矩阵
混淆矩阵是评估分类器最好的方法,总体思路是统计A类被统计为A类的次数(True Positive—TP)、A类被统计为B类的次数(False Negative—FN)、B类被统计为A类的次数(False Positive—FP)和B类统计为B类的结果(True Negative—TN),所以在计算混淆矩阵我们需要提前有一组预测才能与实际目标进行比较,才能计算TP、FN、FP和TN四个参数,下面是具体的代码处理:
from sklearn.model_selection import cross_val_predict #和cross_val_score()函数一样同样适用k-fold交叉验证,但返回的不是评估分数,而是每个折叠的预测
from sklearn.metrics import confusion_matrix #metrics里面封装的都是模型评估的api
y_train_pred=cross_val_predict(sgd_clf,X_train,y_train_5,cv=3)
confusion_matrix(y_train_5,y_train_pred)
运行结果:
结果分析:以上放回的便是混淆矩阵,对于混淆矩阵表的四个参数的数据
有了混淆矩阵的四个参数我们就可以展开下面的内容了,首先介绍精度:
在做一个单独的正类预测,并确保它是正确的,就可以得到完美精度100%,这样是毫无意义的,所以精度常常会和召回率使用,召回率表示预测样本中为正例的样本中真实为正例的比例
Scikit-Learn提供了许多种分类器指标的函数,精度和召回率也是其一:
from sklearn.metrics import precision_score ,recall_score
print("精度:\n",precision_score(y_train_5,y_train_pred))
print("召回率:\n",recall_score(y_train_5,y_train_pred))
运行结果:
结果分析:可以看出这里使用混淆矩阵评测出来的结果比前面使用交叉验证评测出来的结果是更加符合实际的(只有83%的时间是准确的,且只有65%的数字5被检测了出来)
现在我们可以很方便的将精度和召回率组合成一个单一的指标,称为F1分数,当你需要一个简单的方法来比较两种分类器是,这是一个很不错的指标,F1分数是精度和召回率的谐波平均值,正常的平均值平等的对待所有值,而谐波平均值会给予较低值更高的权值,因此,只有当召回率和精度够很高时,分类器才能得到较高的F1分数:
计算f1分数:
from sklearn.metrics import f1_score
f1_score(y_train_5,y_train_pred)
运行结果:
结果分析:以上便是f1的分数,但是f1分数对那些具有相近的精度和召回率的分类器更为有利。