Kaggle入门赛手写数字识别—Tensorflow2

前置

比赛地址:Digit Recognizer
我的笔记本:Digit Recognizer Accuracy 99.3%

这个比赛是图像分类的入门赛,数据集为著名的MNIST手写数字集,我们可以使用机器学习算法(例如SVM、KNN等)进行数字分类,也可以使用深度学习方法。这里我们将使用Tensorflow2从零开始训练一个卷积神经网络。

  1. 首先进入比赛首页,点击 Rules按钮,然后点击 I Understand and Accept 按钮即可参加该比赛。只有加入比赛才可以查看和下载比赛数据集!
    在这里插入图片描述
  2. 点击 Code 按钮,点击New Notebook 可以使用线上的notebook进行代码的运行。当然也可以点击 Data 按钮将数据集下载到本地,然后进行操作。为了简单我们直接使用在线运行。(我认为你已经有了jupyter notebook的使用经验)
    在这里插入图片描述
  3. 进入控制台后,因为我们并不需要下载网络资源,因此可以不用开启网络连接,由于我们需要训练一个神经网络,所以将硬件切换为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已经运行完毕,但是我们还需要最后一步将其保存。
在这里插入图片描述

  1. 点击右上角的 Save Version
    在这里插入图片描述
  2. 点击Advanced Setting
    在这里插入图片描述
  3. 选择总是保存输出,然后点击Save
  4. 点击Save & Run All(Commit),选择Quick Save,因为我们已经运行完毕,只需要保持现有样子即可,最后点击Save,你的notebook就保存好了.在这里插入图片描述5. 接下来点击右上角的头像,依次点击如下按钮。在这里插入图片描述
    6.你将跳转到刚刚保存的notebook页面
    在这里插入图片描述
    7.点击Share按钮,可以设置是否公开这个代码。
    8.点击右侧的Output,可以看到刚刚你保存的 submission.csv 文件,点击 Submit 按钮以提交测试。

在这里插入图片描述
9.提交完毕后,你就可以在 Leaderboard 排行榜看到你的排名了!

在这里插入图片描述

10.你已经学会如何使用kaggle了,去尝试探索更多比赛以及数据集。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值