前置
比赛地址:Digit Recognizer
我的笔记本:Digit Recognizer Accuracy 99.3%
这个比赛是图像分类的入门赛,数据集为著名的MNIST手写数字集,我们可以使用机器学习算法(例如SVM、KNN等)进行数字分类,也可以使用深度学习方法。这里我们将使用Tensorflow2从零开始训练一个卷积神经网络。
- 首先进入比赛首页,点击
Rules
按钮,然后点击I Understand and Accept
按钮即可参加该比赛。只有加入比赛才可以查看和下载比赛数据集!
- 点击
Code
按钮,点击New Notebook
可以使用线上的notebook进行代码的运行。当然也可以点击Data
按钮将数据集下载到本地,然后进行操作。为了简单我们直接使用在线运行。(我认为你已经有了jupyter notebook的使用经验)
- 进入控制台后,因为我们并不需要下载网络资源,因此可以不用开启网络连接,由于我们需要训练一个神经网络,所以将硬件切换为GPU,这可以大大提高训练速度(kaggle每周提供30小时免费的GPU使用时间,以及20个小时免费的TPU使用时间)。然后你就可以像在本地运行jupyter notebook一样使用该控制台了。
数据准备
# 首先导入所需要的库
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
加载数据
在控制台右侧我们可以看到可以使用的数据集,也可以搜索其他数据集添加进来。可以点击复制数据路径,由于数据文件是csv格式,我们使用pandas进行读取。
data = pd.read_csv('../input/digit-recognizer/train.csv')
查看数据前五行
data.head()
label pixel0 pixel1 pixel2 pixel3 pixel4 pixel5 pixel6 pixel7 pixel8 ... pixel774 pixel775 pixel776 pixel777 pixel778 pixel779 pixel780 pixel781 pixel782 pixel783
0 1 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
2 1 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
3 4 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
查看数据维度
data.shape
(42000, 785)
数据处理
我们可以看到第一列标签为label,为要预测的数字,我们将这一列提取出来。
# 使用to_numpy转换为array,后续使用方便
labels = data.pop('label').to_numpy()
查看labels内容
labels
array([1, 0, 1, ..., 7, 6, 9])
再次查看数据维度
data.shape
(42000, 784)
可以看到第二个维度已经从785变为了784,而这一行为一个手写数字,为了使用卷积层,我们需要还原图片为三维,首先将784转换为28*28
data = data.to_numpy().reshape(-1,28,28)
此时data的维度变为 (42000, 28, 28),现在已经有二维图片的样子了,我们使用matplotlib可视化一些图片
plt.figure(figsize=(10,10))
for i in range(25):
plt.subplot(5,5,i+1)
plt.imshow(data[i])
plt.xticks([])
plt.yticks([])
plt.xlabel(labels[i])
现在我们可以很好的理解我们的数据集了。前面说过要想使用卷积层,图片需要三个维度,分别是宽、高、通道数,其中通道数为RGB颜色,由于我们的手写字体数据集为黑白的,所以我们的第三个维度为1,下面为数据集添加通道维度。
data = np.expand_dims(data,axis=-1)
labels = np.expand_dims(labels,axis=-1)
查看当前数据维度。
data.shape
(42000, 28, 28, 1)
labels.shape
(42000, 1)
现在数据集已经达到需求,但是图像的大小为28×28,我们接下来将建立类似LeNet-5(用于手写数字识别的著名网络)风格的神经网络。而LeNet-5需要的图像大小为32*32,因此我们需要将图像扩大,只需要使用0在四周填充两行或两列即可。
data = tf.pad(data,[[0,0],[2,2],[2,2],[0,0]])
让我们再次查看图像维度
data.shape
TensorShape([42000, 32, 32, 1])
数据划分
- 为了能够随时查看模型的进度,以及最后模型的评测,我们将数据集划分为训练集,验证集和测试集,比例为8:1:1 。使用
tf.split
可以实现需求
num = data.shape[0] // 10
train_data, val_data, test_data = tf.split(data,[num*8, num, num])
train_label, val_label, test_label = tf.split(labels,[num*8, num, num])
打印一下各个数据集的维度,确保正常工作
print('train:',train_data.shape,'-- label:',train_label.shape)
print('validition:',val_data.shape,'-- label:',val_label.shape)
print('test:',test_data.shape,'-- label:',test_label.shape)
train: (33600, 32, 32, 1) -- label: (33600, 1)
validition: (4200, 32, 32, 1) -- label: (4200, 1)
test: (4200, 32, 32, 1) -- label: (4200, 1)
-
创建Dataset对象,Dataset对象可以很方便的对数据进行处理和操作,当然你也可以直接使用数组类别进行训练,但是今后你一定会遇到数据集太大无法全部被内存加载的情况,那时你只能使用Dataset进行批量读取,因此这里我们熟悉一下。
from_tensor_slices
:从内存中加载数据
shuffle
:随机打乱数据
batch
:每一批读取的数据量
train_ds = tf.data.Dataset.from_tensor_slices((train_data,train_label)).shuffle(33600,seed=42).batch(128)
val_ds = tf.data.Dataset.from_tensor_slices((val_data,val_label)).shuffle(33600,seed=42).batch(128)
test_ds = tf.data.Dataset.from_tensor_slices((test_data,test_label)).shuffle(33600,seed=42).batch(128)
- 现在让我们展示Dataset中的数据。
使用take(num)
可以提取num个batch,前面我们设置的每个batch包含128个数据,因此这里image的维度为(128,32,32,1),label的维度为(128,1),take返回的是一个迭代器对象,不能直接使用,需要用for i,j in ***
的形式,或者使用image,label = next(iter(train_ds))
也可以提取一个batch的数据。
plt.figure(figsize=(10,10))
# 取出一个batch的数据
for image,label in train_ds.take(1):
for i in range(25):
plt.subplot(5,5,i+1)
plt.imshow(image[i])
plt.xticks([]) # 取消显示x,y轴刻度
plt.yticks([])
plt.xlabel(label[i][0].numpy())
- 使用图像进行训练神经网络时,最好将图像的像素缩放到0~1之间,前面的处理中,我们没有进行此操作,因为我们打算将此操作放在模型之中。这只需要在模型底层添加
Rescaling
层即可。
模型建立
# 模型的输入维度
image_shape = (32,32,1)
- 构建模型
# 使用蒙特卡罗Dropout来减少过拟合,以及改善输出概率
class MCDropout(layers.Dropout):
def call(self,inputs):
return super().call(inputs,training=True)
# 使用函数式方法构建 LetNet-5
inputs = layers.Input(shape=image_shape)
x = layers.experimental.preprocessing.Rescaling(1./255)(inputs) # normalized
x = layers.Conv2D(128,5,strides=1,activation='relu')(x)
x = layers.MaxPooling2D(2,strides=2)(x)
x = layers.Conv2D(128,5,strides=1,activation='relu')(x)
x = layers.MaxPooling2D(2,strides=2)(x)
x = layers.Conv2D(128,5,strides=1,activation='relu')(x)
x = layers.Flatten()(x)
x = layers.Dense(84,activation='relu')(x)
x = MCDropout(0.1)(x)
outputs = layers.Dense(10)(x)
model = keras.Model(inputs,outputs)
# 当然你可以使用 Sequential 来构建这个顺序的网络结构
'''
model = keras.Sequential([
layers.Input(shape=image_shape),
layers.experimental.preprocessing.Rescaling(1./255),
layers.Conv2D(128,5,strides=1,activation='relu'),
layers.MaxPooling2D(2,strides=2),
layers.Conv2D(128,5,strides=1,activation='relu'),
layers.MaxPooling2D(2,strides=2),
layers.Conv2D(128,5,strides=1,activation='relu'),
layers.Flatten(),
layers.Dense(84,activation='relu'),
MCDropout(0.1),
layers.Dense(10)
])
'''
- 配置模型属性
optimizer:我们使用adam
优化器,并设置学习率为0.001。
loss:因为我们并没有对标签进行独热编码,因此使用稀疏交叉熵损失,同样模型的顶层输出没有使用softmax
激活函数,因此这里需要设置参数from_logits=True
。
metrics:对于这个分类任务,我们关注的是准确率,因此评估指标只需要一个'accuracy'
即可,当然你可以用keras.metrics.Accuracy()
来替换这个'accuracy'
。
model.compile(
optimizer=keras.optimizers.Adam(lr=0.001),
loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy']
)
- 打印模型的结构
model.summary()
Model: "model"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) [(None, 32, 32, 1)] 0
_________________________________________________________________
rescaling (Rescaling) (None, 32, 32, 1) 0
_________________________________________________________________
conv2d (Conv2D) (None, 28, 28, 128) 3328
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 14, 14, 128) 0
_________________________________________________________________
conv2d_1 (Conv2D) (None, 10, 10, 128) 409728
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 5, 5, 128) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 1, 1, 128) 409728
_________________________________________________________________
flatten (Flatten) (None, 128) 0
_________________________________________________________________
dense (Dense) (None, 84) 10836
_________________________________________________________________
mc_dropout (MCDropout) (None, 84) 0
_________________________________________________________________
dense_1 (Dense) (None, 10) 850
=================================================================
Total params: 834,470
Trainable params: 834,470
Non-trainable params: 0
_________________________________________________________________
- 训练模型
# 使用提前终止,当验证集准确率10个轮次都没有改善,就结束训练
early_stopping = keras.callbacks.EarlyStopping(monitor='val_accuracy',mode='max',
patience=10,restore_best_weights=True)
# 使用学习率调度,当验证集准确率5个轮次没有改善时,将当前学习率乘以0.5,即降低一半
lr_scheduler = keras.callbacks.ReduceLROnPlateau(monitor='val_accuracy',mode='max',factor=0.5,patience=5)
# 使用fit进行训练,返回训练的信息,可以对准确率和损失进行可视化
history = model.fit(train_ds,batch_size=128,epochs=100,validation_data=val_ds,
callbacks=[early_stopping,lr_scheduler])
Epoch 1/100
263/263 [==============================] - 7s 12ms/step - loss: 0.5656 - accuracy: 0.8136 - val_loss: 0.0644 - val_accuracy: 0.9800
Epoch 2/100
263/263 [==============================] - 3s 9ms/step - loss: 0.0598 - accuracy: 0.9817 - val_loss: 0.0509 - val_accuracy: 0.9860
Epoch 3/100
263/263 [==============================] - 3s 9ms/step - loss: 0.0410 - accuracy: 0.9867 - val_loss: 0.0560 - val_accuracy: 0.9855
Epoch 4/100
263/263 [==============================] - 3s 9ms/step - loss: 0.0231 - accuracy: 0.9928 - val_loss: 0.0581 - val_accuracy: 0.9857
Epoch 5/100
263/263 [==============================] - 3s 10ms/step - loss: 0.0208 - accuracy: 0.9924 - val_loss: 0.0586 - val_accuracy: 0.9860
Epoch 6/100
263/263 [==============================] - 3s 9ms/step - loss: 0.0153 - accuracy: 0.9949 - val_loss: 0.0548 - val_accuracy: 0.9881
Epoch 7/100
263/263 [==============================] - 3s 9ms/step - loss: 0.0137 - accuracy: 0.9954 - val_loss: 0.0408 - val_accuracy: 0.9907
Epoch 8/100
263/263 [==============================] - 3s 9ms/step - loss: 0.0101 - accuracy: 0.9968 - val_loss: 0.0542 - val_accuracy: 0.9890
Epoch 9/100
263/263 [==============================] - 3s 9ms/step - loss: 0.0122 - accuracy: 0.9962 - val_loss: 0.0506 - val_accuracy: 0.9871
Epoch 10/100
263/263 [==============================] - 3s 9ms/step - loss: 0.0094 - accuracy: 0.9973 - val_loss: 0.0482 - val_accuracy: 0.9895
Epoch 11/100
263/263 [==============================] - 3s 9ms/step - loss: 0.0063 - accuracy: 0.9979 - val_loss: 0.0475 - val_accuracy: 0.9902
Epoch 12/100
263/263 [==============================] - 3s 10ms/step - loss: 0.0064 - accuracy: 0.9980 - val_loss: 0.0485 - val_accuracy: 0.9886
Epoch 13/100
263/263 [==============================] - 3s 9ms/step - loss: 0.0024 - accuracy: 0.9992 - val_loss: 0.0450 - val_accuracy: 0.9917
Epoch 14/100
263/263 [==============================] - 3s 9ms/step - loss: 5.4879e-04 - accuracy: 1.0000 - val_loss: 0.0524 - val_accuracy: 0.9921
Epoch 15/100
263/263 [==============================] - 3s 9ms/step - loss: 4.6251e-04 - accuracy: 0.9999 - val_loss: 0.0497 - val_accuracy: 0.9917
Epoch 16/100
263/263 [==============================] - 3s 10ms/step - loss: 1.7276e-04 - accuracy: 1.0000 - val_loss: 0.0554 - val_accuracy: 0.9931
Epoch 17/100
263/263 [==============================] - 3s 10ms/step - loss: 5.1048e-04 - accuracy: 0.9996 - val_loss: 0.0533 - val_accuracy: 0.9910
Epoch 18/100
263/263 [==============================] - 3s 9ms/step - loss: 6.1154e-04 - accuracy: 0.9999 - val_loss: 0.0605 - val_accuracy: 0.9917
Epoch 19/100
263/263 [==============================] - 3s 9ms/step - loss: 8.1936e-04 - accuracy: 0.9996 - val_loss: 0.0577 - val_accuracy: 0.9926
Epoch 20/100
263/263 [==============================] - 3s 10ms/step - loss: 0.0022 - accuracy: 0.9997 - val_loss: 0.0574 - val_accuracy: 0.9902
Epoch 21/100
263/263 [==============================] - 3s 9ms/step - loss: 0.0029 - accuracy: 0.9993 - val_loss: 0.0584 - val_accuracy: 0.9905
Epoch 22/100
263/263 [==============================] - 3s 9ms/step - loss: 0.0019 - accuracy: 0.9995 - val_loss: 0.0486 - val_accuracy: 0.9929
Epoch 23/100
263/263 [==============================] - 3s 9ms/step - loss: 2.7549e-04 - accuracy: 1.0000 - val_loss: 0.0555 - val_accuracy: 0.9929
Epoch 24/100
263/263 [==============================] - 3s 10ms/step - loss: 1.3819e-04 - accuracy: 1.0000 - val_loss: 0.0503 - val_accuracy: 0.9926
Epoch 25/100
263/263 [==============================] - 3s 9ms/step - loss: 1.6702e-04 - accuracy: 1.0000 - val_loss: 0.0569 - val_accuracy: 0.9931
Epoch 26/100
263/263 [==============================] - 3s 9ms/step - loss: 4.3005e-05 - accuracy: 1.0000 - val_loss: 0.0572 - val_accuracy: 0.9936
Epoch 27/100
263/263 [==============================] - 3s 9ms/step - loss: 3.5149e-05 - accuracy: 1.0000 - val_loss: 0.0599 - val_accuracy: 0.9938
Epoch 28/100
263/263 [==============================] - 3s 11ms/step - loss: 3.9171e-05 - accuracy: 1.0000 - val_loss: 0.0609 - val_accuracy: 0.9936
Epoch 29/100
263/263 [==============================] - 3s 9ms/step - loss: 2.2399e-05 - accuracy: 1.0000 - val_loss: 0.0570 - val_accuracy: 0.9929
Epoch 30/100
263/263 [==============================] - 2s 9ms/step - loss: 1.5650e-04 - accuracy: 0.9999 - val_loss: 0.0725 - val_accuracy: 0.9912
Epoch 31/100
263/263 [==============================] - 3s 9ms/step - loss: 3.7609e-04 - accuracy: 0.9999 - val_loss: 0.0703 - val_accuracy: 0.9900
Epoch 32/100
263/263 [==============================] - 3s 10ms/step - loss: 8.5924e-04 - accuracy: 0.9997 - val_loss: 0.0625 - val_accuracy: 0.9912
Epoch 33/100
263/263 [==============================] - 3s 9ms/step - loss: 5.6777e-04 - accuracy: 0.9998 - val_loss: 0.0579 - val_accuracy: 0.9919
Epoch 34/100
263/263 [==============================] - 3s 9ms/step - loss: 1.2358e-04 - accuracy: 1.0000 - val_loss: 0.0628 - val_accuracy: 0.9926
Epoch 35/100
263/263 [==============================] - 3s 9ms/step - loss: 4.4177e-05 - accuracy: 1.0000 - val_loss: 0.0608 - val_accuracy: 0.9929
Epoch 36/100
263/263 [==============================] - 3s 10ms/step - loss: 7.4256e-05 - accuracy: 1.0000 - val_loss: 0.0646 - val_accuracy: 0.9926
Epoch 37/100
263/263 [==============================] - 3s 9ms/step - loss: 2.6607e-05 - accuracy: 1.0000 - val_loss: 0.0626 - val_accuracy: 0.9919
- 绘画学习曲线
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
plt.figure(figsize=(8, 8))
plt.subplot(2, 1, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.ylabel('Accuracy')
plt.ylim([min(plt.ylim()),1])
plt.title('Training and Validation Accuracy')
plt.subplot(2, 1, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.ylabel('Cross Entropy')
plt.ylim([0,1.0])
plt.title('Training and Validation Loss')
plt.xlabel('epoch')
plt.show()
- 在测试集上评估模型
# verbose参数控制打印信息的详细度
model.evaluate(test_ds,verbose=2)
33/33 - 0s - loss: 0.0280 - accuracy: 0.9936
[0.02803613804280758, 0.993571400642395]
至此我们的模型在测试集上拿到了0.9935的准确率,是时候提交了。
提交结果
- 最后要提交预测的数据储存在test.csv里面,提交格式为sample_submission.csv
- 先使用pandas加载出来
test = pd.read_csv('../input/digit-recognizer/test.csv')
sample_submission = pd.read_csv('../input/digit-recognizer/sample_submission.csv')
- 让我们看看test.csv里面是什么
test.head()
pixel0 pixel1 pixel2 pixel3 pixel4 pixel5 pixel6 pixel7 pixel8 pixel9 ... pixel774 pixel775 pixel776 pixel777 pixel778 pixel779 pixel780 pixel781 pixel782 pixel783
0 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
5 rows × 784 columns
- 可以看到数据共有784列,缺少了标签列,不过这正是我们需要预测的
- 查看 sample_submission.csv
sample_submission.head()
ImageId Label
0 1 0
1 2 0
2 3 0
3 4 0
4 5 0
-
我们只需要将预测的结果放入label列就可以。
-
进行预测前,我们需要对数据进行曾经做过的相同处理。
test = test.to_numpy().reshape(-1,28,28)
test = np.expand_dims(test,axis=-1)
test = tf.pad(test,[[0,0],[2,2],[2,2],[0,0]])
test.shape
TensorShape([28000, 32, 32, 1])
- 使用predict预测结果
result = model.predict(test)
result.shape
(28000, 10)
- 最后的结果有10个类别,我们将值最大的索引提取出来,作为预测的类别,也就是预测的数字。
predict_label = np.argmax(result,axis=-1)
predict_label.shape
(28000,)
- 让我们展示一些预测的结果
plt.figure(figsize=(10,10))
for i in range(25):
plt.subplot(5,5,i+1)
plt.imshow(test[i,...,0])
plt.xticks([])
plt.yticks([])
plt.xlabel(predict_label[i])
- 看起来还不错,最后将 sample_submission.csv的Label列设置为我们的预测结果。
sample_submission['Label'] = predict_label
- 然后将我们的结果保存为一个新的csv文件,注意不用保存索引(设置
index=False
来取消)
sample_submission.to_csv('submission.csv', index=False)
后置
至此我们的notebook已经运行完毕,但是我们还需要最后一步将其保存。
- 点击右上角的
Save Version
- 点击
Advanced Setting
- 选择总是保存输出,然后点击
Save
- 点击
Save & Run All(Commit)
,选择Quick Save
,因为我们已经运行完毕,只需要保持现有样子即可,最后点击Save
,你的notebook就保存好了.5. 接下来点击右上角的头像,依次点击如下按钮。
6.你将跳转到刚刚保存的notebook页面
7.点击Share
按钮,可以设置是否公开这个代码。
8.点击右侧的Output
,可以看到刚刚你保存的 submission.csv 文件,点击Submit
按钮以提交测试。
9.提交完毕后,你就可以在 Leaderboard 排行榜看到你的排名了!
10.你已经学会如何使用kaggle了,去尝试探索更多比赛以及数据集。