这次做的是数字识别器,借鉴了大佬的文章Kaggle竞赛实战系列(一):手写数字识别器(Digit Recognizer)得分99.53%、99.91%和100%_手写数字识别实现 digist-CSDN博客
加入了自己对大佬里面步骤的解释和理解
题目呢就是做一个能识别手写数字的识别器,我想到的一些算法包括用神经网络来完成自主识别,用分类算法,比如svm,knn,决策树来做,我这里是跟着大佬用神经网络来试着做的。
首先导入库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import seaborn as sns
%matplotlib inline
np.random.seed(2)
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
import itertools
# 转换为独热编码
from tensorflow.keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPool2D, BatchNormalization
from keras.optimizers import RMSprop
from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import ReduceLROnPlateau
#设置显示样式的参数
# http://seaborn.pydata.org/generated/seaborn.set.html
sns.set(style='white', context='notebook', palette='deep')
库前面是基础的有pandas,numpy,可视化的plt,sns,还有基本的分类train_test_split
还有用来评估的混淆矩阵,接下来就是特征编码的一些函数
像to_categorical 是kares中的独热编码函数,还有就是神经网络的一系列层
-
Sequential
: 这是 Keras 中的一个模型类,用于创建一个线性堆叠的神经网络。你可以通过简单地将层添加到序列中来构建模型。 -
Dense
: 这是全连接层,用于构建神经网络中的全连接层,即每个神经元都与上一层的所有神经元相连。 -
Dropout
: Dropout 层用于在训练过程中随机丢弃一定比例的神经元,以防止模型过拟合。 -
Flatten
: 这个层用于将多维输入一维化,通常在卷积层和全连接层之间使用。 -
Conv2D
: 这是二维卷积层,用于处理图像数据,可以提取图像的特征。 MaxPool2D
: 最大池化层,用于减少数据的空间大小,从而减少参数数量和计算量,同时保持重要的特征BatchNormalization
: 批量归一化层,用于调整网络中间层的输出,目的是提高训练速度、稳定性和性能-
RMSprop
: 这是一种优化算法,用于更新网络权重,它通过调整学习率来加速收敛。 -
ImageDataGenerator
: 这是一个用于图像数据增强的工具,可以实时地对图像数据进行变换,从而增加模型的泛化能力。 -
ReduceLROnPlateau
: 这是一个回调函数,用于在训练过程中减少学习率,当验证集的性能不再提升时,它会降低学习率。
数据准备
train = pd.read_csv("train.csv")
test= pd.read_csv("test.csv")
数据清洗,可视化
# 'label'
Y_train = train["label"]
print(Y_train.shape)
# 删除 'label' 列
X_train = train.drop(labels = ["label"],axis = 1)
print(X_train.shape)
# 释放一些空间
del train
# 使用条形图显示每个分类数据集合中的观测值
g = sns.countplot(Y_train)
# 对训练集中的元素计数
Y_train.value_counts()
数据检查
X_train.isnull().any().describe()
test.isnull().any().describe()
归一化
#对数据进行归一化,到[0,1]范围内,减小光照的影响,并可加速cnn收敛速度
X_train = X_train/255.0
test = test/255.0
reshape
重塑为一个新的三维形状,重塑的目的是为了将图像数据转换成适合输入到卷积神经网络(CNN)的形状
X_train = X_train.values.reshape(-1,28,28,1)
test = test.values.reshape(-1,28,28,1)
X_train.shape[0]
37800
g = plt.imshow(X_train[0][:,:,0])
g = plt.imshow(X_train[200][:,:,0])
卷积神经网络 CNN
使用卷积神经网络(CNN)进行数字识别相比于传统的分类算法,如KNN、SVM、决策树等,具有以下优势:
-
特征提取能力:CNN通过卷积层自动学习图像的特征,无需手动设计特征提取器,这使得模型能够捕捉到数字图像中的复杂特征,如形状、边缘和纹理等。
-
参数共享:CNN中的卷积层通过参数共享减少了模型的复杂度,这意味着网络中的每个卷积核在整个输入图像上滑动时使用相同的权重,减少了需要训练的参数数量,提高了计算效率。
-
平移不变性:CNN能够学习到具有平移不变性的特征,即使数字在图像中的位置发生变化,模型仍然能够准确识别。
-
多层次特征学习:CNN通过多层卷积和池化操作构建了从低级到高级的特征表示,这使得模型能够理解图像的局部细节和全局结构。
-
泛化能力:CNN在训练后通常具有良好的泛化能力,这意味着它们能够在未见过的数据上表现良好,尤其是在大规模数据集上训练时。
-
数据增强:CNN可以通过数据增强技术(如旋转、缩放、裁剪等)来提高模型的鲁棒性和性能,而不需要额外的标记数据。
-
适用于大规模数据集:CNN可以处理大规模的图像数据集,并且随着数据量的增加,模型的性能通常会提高。
-
端到端学习:CNN可以实现端到端的学习,即直接从原始图像到最终的分类结果,无需额外的预处理或特征提取步骤。
model = Sequential()
model.add(Conv2D(filters = 32,kernel_size = (5,5),padding = 'Same',
activation = 'relu',input_shape = (28,28,1)))
model.add(BatchNormalization())
model.add(Conv2D(filters = 32,kernel_size = (5,5),padding = 'Same',
activation = 'relu'))
model.add(BatchNormalization())
model.add(MaxPool2D(pool_size = (2,2)))
model.add(Flatten())
model.add(Dense(256,activation = 'relu'))
model.add(Dropout(0.5))
model.add(Dense(10,activation = 'softmax'))
model.summary()
-
model = Sequential()
:创建一个序贯模型,这是一个线性堆叠的层的模型。 -
model.add(Conv2D(...))
:添加第一个卷积层,它有32个过滤器(或卷积核),每个大小为5x5。padding='Same'
表示输出的宽度和高度与输入相同,activation='relu'
表示使用ReLU激活函数。input_shape=(28,28,1)
指定了输入图像的尺寸和通道数(对于灰度图像,通道数为1)。 -
model.add(BatchNormalization())
:添加批量归一化层,用于调整神经网络中间层的输出,目的是提高训练速度、稳定性和性能。 -
model.add(Conv2D(...))
:添加第二个卷积层,与第一个卷积层配置相同。 -
model.add(BatchNormalization())
:再次添加批量归一化层。 -
model.add(MaxPool2D(pool_size = (2,2)))
:添加最大池化层,池化窗口大小为2x2。这会减小特征图的空间维度,同时保留最重要的特征。 -
model.add(Flatten())
:添加一个展平层,将多维的输出展平成一维,以便可以传递给全连接层。 -
model.add(Dense(256,activation = 'relu'))
:添加一个全连接层,有256个神经元,使用ReLU激活函数。 -
model.add(Dropout(0.5))
:添加一个 Dropout 层,丢弃率为0.5,用于减少过拟合。 -
model.add(Dense(10,activation = 'softmax'))
:添加最后一个全连接层,有10个神经元(对应10个类别),使用softmax激活函数,输出每个类别的概率。 -
model.summary()
:打印模型的摘要,显示模型的层、每层的参数数量等信息。
关于优化器adam和sgd
Adam(Adaptive Moment Estimation)和SGD(Stochastic Gradient Descent,随机梯度下降)是两种常用的优化算法,用于训练神经网络和其他机器学习模型。它们在目标相同——最小化损失函数——的情况下,有着不同的更新规则和特性。以下是它们的主要区别:
-
动量(Momentum):
- SGD:在最基本的形式中,SGD没有使用动量。但在实践中,通常会加入动量(SGD with momentum)来加速学习过程并减少震荡。
- Adam:结合了动量的概念,通过计算梯度的一阶矩估计(均值)和二阶矩估计(未中心的方差),从而调整每个参数的学习率。
-
自适应学习率:
- SGD:所有参数共享相同的学习率(除非手动调整)。
- Adam:为每个参数自适应地调整学习率,这通常导致更好的训练动态和更快的收敛。
-
稳定性:
- SGD:可能会在最小值附近产生较大震荡,特别是在加入动量之后。
- Adam:通常在训练过程中提供更平滑的收敛路径,因为它考虑了梯度的历史信息。
-
参数更新规则:
- SGD:参数更新是基于当前梯度和学习率。
- Adam:参数更新是基于梯度的一阶和二阶矩估计,这使得它在不同的参数上可以有不同的学习率。
-
超参数:
- SGD:主要的超参数是学习率和动量。
- Adam:除了学习率,还有两个超参数:β1(一阶矩估计的指数衰减率)和β2(二阶矩估计的指数衰减率),以及一个很小的数值ϵ,用于防止在计算中除以零。
-
收敛速度:
- SGD:收敛速度可能较慢,尤其是在没有适当调整超参数的情况下。
- Adam:通常收敛速度更快,因为它利用了梯度的历史信息来加速学习。
-
适用场景:
- SGD:适合大规模数据集和简单模型,因为它简单且计算效率高。
- Adam:适合复杂的模型和较小的数据集,因为它能够更有效地处理参数的自适应学习率。
-
内存需求:
- SGD:内存需求较低,因为它不需要存储梯度的历史信息。
- Adam:内存需求较高,因为它需要存储梯度的一阶和二阶矩估计。
# 用adam优化器和交叉熵损失进行编译
#model.compile(optimizer="adam", loss="categorical_crossentropy", metrics=["accuracy"])
# 用sgd优化器
model.compile(optimizer="sgd", loss="categorical_crossentropy", metrics=["accuracy"])
# 定义优化器
# optimizer = RMSprop(lr=0.001, rho=0.9, epsilon=1e-08, decay=0.0)
# 编译模型
# model.compile(optimizer = optimizer , loss = "categorical_crossentropy", metrics=["accuracy"])
设置学习率,这边用学习率衰减,目的是在训练的不同阶段减小学习步长,从而提高模型的收敛速度和精度。随着训练的进行,逐渐减小学习率可以帮助模型更细致地逼近最优解,避免在训练后期出现较大震荡。
# 设置一个学习率衰减
learning_rate_reduction = ReduceLROnPlateau(monitor='val_acc',
patience=3,
verbose=1,
factor=0.5,
min_lr=0.00001)
# 训练轮数,暂时设置为30,可以自己尝试调整
epochs = 30
# 批大小
batch_size = 86
数据增强,数据增强能防止过拟合和提高模型泛化能力
# 增加数据以防止过拟合
datagen = ImageDataGenerator(
featurewise_center=False, # 在数据集上将输入平均值设置为0
samplewise_center=False, # 将每个样本的平均值设置为0
featurewise_std_normalization=False, # 将输入除以数据集的std
samplewise_std_normalization=False, # 将每个输入除以它的std
zca_whitening=False, # 使用ZCA白化
rotation_range=10, # 在范围内随机旋转图像(0到180度)
zoom_range = 0.1, # 随机缩放图像
width_shift_range=0.1, # 水平随机移动图像(总宽度的一部分)
height_shift_range=0.1, # 垂直随机移动图像(总高度的一部分)
horizontal_flip=False, # 随机翻转图像
vertical_flip=False) # 随机翻转图像
datagen.fit(X_train)
featurewise_center
:设置为False
表示不从每个特征(即图像通道)中减去平均值。samplewise_center
:设置为False
表示不从每个样本中减去平均值。featurewise_std_normalization
:设置为False
表示不将每个特征除以其标准差进行标准化。samplewise_std_normalization
:设置为False
表示不将每个样本除以其标准差进行标准化。zca_whitening
:设置为False
表示不应用 ZCA 白化。ZCA 白化是一种数据预处理技术,用于减少数据中的冗余信息。rotation_range
:设置为10
表示随机旋转图像的角度范围(+/- 10度)。zoom_range
:设置为0.1
表示随机缩放图像的范围(原图像大小的10%)。width_shift_range
:设置为0.1
表示水平方向上随机平移图像的范围(总宽度的10%)。height_shift_range
:设置为0.1
表示垂直方向上随机平移图像的范围(总高度的10%)。horizontal_flip
:设置为False
表示不随机水平翻转图像。vertical_flip
:设置为False
表示不随机垂直翻转图像。
当你将
ImageDataGenerator
中的featurewise_center
、samplewise_center
、featurewise_std_normalization
和samplewise_std_normalization
设置为True
时,你会启用以下数据预处理步骤:
featurewise_center=True
:
- 会对整个数据集中的每个特征(即图像的每个通道,对于RGB图像来说是3个通道)进行中心化,即减去该特征的平均值。这样做的目的是使得数据在每个通道上的平均值为0。
samplewise_center=True
:
- 会对每个样本(即每张图像)进行中心化,即从每个像素值中减去该图像的平均值。
featurewise_std_normalization=True
:
- 会对整个数据集中的每个特征进行标准化,即每个特征的数值都会除以其标准差。这有助于使得每个通道的数值范围大致相同,有助于模型的训练。
samplewise_std_normalization=True
:
- 会对每个样本进行标准化,即每个像素值都会除以该图像的标准差。
这些预处理步骤通常用于提高模型的训练效率和性能,因为它们可以减少模型训练过程中的内部协变量偏移(internal covariate shift)。内部协变量偏移是指在训练过程中,输入数据分布的变化,这可能会减慢梯度下降算法的收敛速度。
然而,需要注意的是,这些标准化步骤可能会降低数据增强的效果,因为它们会改变图像的原始分布。例如,如果你对图像进行了缩放或旋转,然后进行标准化,那么图像的像素值分布将会改变,这可能不是你想要的效果。
拟合数据
# 拟合模型
history = model.fit_generator(datagen.flow(X_train,Y_train, batch_size=batch_size),
epochs = epochs, validation_data = (X_val,Y_val),
verbose = 2, steps_per_epoch=X_train.shape[0] // batch_size,
callbacks=[learning_rate_reduction])
评估模型
#训练和验证曲线
# 绘制训练和验证的损失和精度曲线
fig, ax = plt.subplots(2,1)
ax[0].plot(history.history['loss'], color='b', label="Training loss")
ax[0].plot(history.history['val_loss'], color='r', label="validation loss",axes =ax[0])
legend = ax[0].legend(loc='best', shadow=True)
ax[1].plot(history.history['accuracy'], color='b', label="Training accuracy")
ax[1].plot(history.history['val_accuracy'], color='r',label="Validation accuracy")
legend = ax[1].legend(loc='best', shadow=True)
混淆矩阵
混淆矩阵可以非常有助于了解模型缺点,所以绘制了验证结果的混淆矩阵。
# 看混淆矩阵
def plot_confusion_matrix(cm, classes,
normalize=False,
title='Confusion matrix',
cmap=plt.cm.Blues):
"""
此函数打印并绘制混淆矩阵
可以通过设置 “normalize=true” 应用归一化
"""
plt.imshow(cm, interpolation='nearest', cmap=cmap)
plt.title(title)
plt.colorbar()
tick_marks = np.arange(len(classes))
plt.xticks(tick_marks, classes, rotation=45)
plt.yticks(tick_marks, classes)
if normalize:
cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
thresh = cm.max() / 2.
for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
plt.text(j, i, cm[i, j],
horizontalalignment="center",
color="white" if cm[i, j] > thresh else "black")
plt.tight_layout()
plt.ylabel('True label')
plt.xlabel('Predicted label')
# 从验证数据集中预测值
Y_pred = model.predict(X_val)
# 将预测类转换为一个独热向量
Y_pred_classes = np.argmax(Y_pred,axis = 1)
# 将验证观测转换为一个独热向量
Y_true = np.argmax(Y_val,axis = 1)
# 计算混淆矩阵
confusion_mtx = confusion_matrix(Y_true, Y_pred_classes)
# 绘制混淆矩阵
plot_confusion_matrix(confusion_mtx, classes = range(10))
这里可以看到,CNN
在所有数字上都表现得非常好,考虑到验证集的大小(4200张图像),可以说错误是非常少的了。然而,也有一些麻烦,比如真实为4的数有好多被误分类为9。
# 显示一些错误结果
# 错误是预测标签和真实标签之间的区别
errors = (Y_pred_classes - Y_true != 0)
Y_pred_classes_errors = Y_pred_classes[errors]
Y_pred_errors = Y_pred[errors]
Y_true_errors = Y_true[errors]
X_val_errors = X_val[errors]
def display_errors(errors_index,img_errors,pred_errors, obs_errors):
"""
此函数显示6个图像及其预测和实际标签
"""
n = 0
nrows = 2
ncols = 3
fig, ax = plt.subplots(nrows,ncols,sharex=True,sharey=True)
for row in range(nrows):
for col in range(ncols):
error = errors_index[n]
ax[row,col].imshow((img_errors[error]).reshape((28,28)))
ax[row,col].set_title("Predicted label :{}\nTrue label :{}".format(pred_errors[error],obs_errors[error]))
n += 1
# 错误预测数的概率
Y_pred_errors_prob = np.max(Y_pred_errors,axis = 1)
# 误差集中真值的预测概率
true_prob_errors = np.diagonal(np.take(Y_pred_errors, Y_true_errors, axis=1))
# 预测标签概率与真实标签概率之差
delta_pred_true_errors = Y_pred_errors_prob - true_prob_errors
# 对预测标签概率与真实标签概率之差的列表进行排序
sorted_dela_errors = np.argsort(delta_pred_true_errors)
# Top 6错误
most_important_errors = sorted_dela_errors[-6:]
# 展示Top 6错误
display_errors(most_important_errors, X_val_errors, Y_pred_classes_errors, Y_true_errors)
预测和提交
# 预测结果
results = model.predict(test)
# 选择最大概率的整数
results = np.argmax(results,axis = 1)
results = pd.Series(results,name="Label")
submission = pd.concat([pd.Series(range(1,28001),name = "ImageId"),results],axis = 1)
# 转换成CSV格式,不保留索引
submission.to_csv("cnn_mnist_datagen.csv",index=False)