CNN实现数字识别

最近实现了一个小小的神经网络预测数字类别。下面就来看看是怎么实现的吧。go…


导包

‘’’Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
import matplotlib.pyplot as plt

from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix #导入混淆矩阵,查看真实值被正确或者错误预测的个数

from keras.utils.np_utils import to_categorical #one-hot编码,构造稀疏矩阵,解决分类值表示问题对模型产生的负面影响
from keras.models import Sequential #导入顺序Sequential模型,后续可以直接在Sequential上构建神经网络
#Dense全连接层,Dropout正则化数据,Flatten压平数据连接全连接层,
#conv2D卷积层卷积计算,BatchNormalization数据规范化输出数据的均值接近0,标准差接近1
from keras.layers import Dense,Dropout,Flatten,Conv2D,MaxPool2D,BatchNormalization
from keras.optimizers import Adam #导入优化器Adam,基于一阶梯度的随机目标函数优化算法
from keras.preprocessing.image import ImageDataGenerator #图像增广技术扩充我们得数据
from keras.callbacks import LearningRateScheduler #动态修改学习率的回调函数

‘’’

读取数据并显示

‘’’Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#设置自己下载的数据的路径
train_file="./data/train.csv"
test_file="./data/test.csv"
output_file="./data/output/submission.csv"

#导入数据np.loadtxt
raw_data=np.loadtxt(train_file,skiprows=1,dtype='int',delimiter=',')
#划分数据集为训练集和验证集
x_train,x_val,y_train,y_val=train_test_split(raw_data[:,1:],raw_data[:,0],test_size=0.1)

fig,ax=plt.subplots(2,1,figsize=(12,6)) #nrows,ncols,figsize;fig表示整个图像,ax代表坐标轴和画的图
ax[0].plot(x_train[0])

#设置标题
ax[0].set_title('784*1 data')
#图形显示x_train数据-----#每个数据有784个值组成,cnn中将其视为28*28
ax[1].imshow(x_train[0].reshape(28,28),cmap='gray')
#设置标题
ax[1].set_title('28*28 data')

plt.show()

‘’’
loadtxt(fname,dtype=<class ‘float>,comments=’#’,delimiter=None,converters=None,skiprows=0,
usecols=None,unpack=False,ndmin=0)

fname:要读取的文件,文件名,或生成器
dtype:数据类型,默认float
comments:注释
delimiter:分隔符,默认空格
skiprows:跳过前几行读取,默认是0,必须是int整型
usecols:要读取哪些列,0是第一列,例如,usecols=(1,4,5)将提取第2,第5和第6列。默认读取所有列。
unpack:如果为True,将分列读取

数据重塑

‘’’Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#Rehape image in 3 dimensions(height=28px,width=28px,canal=1) 重塑3维图像
#784个输入,转成28*28+颜色通道
#-1表示自动填充行数
#注意keras的颜色通道表示在最后一个参数(与其他框架不同)
x_train=x_train.reshape(-1,28,28,1)
x_val=x_val.reshape(-1,28,28,1)

#原始数据上像素值为0到255,使用标准的权重初始化方法,0-1之间的数据应该使网络收敛更快
x_train=x_train.astype("float32")/255.
x_val=x_val.astype("float32")/255.
#标签以0到9之间的整数给出,我们需要将他们转换为独热编码。
y_train=to_categorical(y_train)
y_val=to_categorical(y_val)

print(y_train[0])

‘’’

搭建网络

‘’’Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#批量标准化是一种技术手段,可以加速训练速度。Dropout是一种正则化方法,其中
#该层随机地将每个训练样本的一部分权重替换为零。这迫使网络以分布式方式学习特征,
#而不是依赖于特定权重,因此改善了泛化。

#定义顺序模型Sequential,Sequential是多个网络层的线性堆叠
#通常是由以下几个层构成:数据输入层,卷积层,池化层,全连接层,数据输出层

'''
Conv2D:
#filters:整数,输出空间的维度(卷积中滤波器的输出数量)滤波器做卷积计算用的,卷积计算的过程也正是图形匹配的过程
#kernel_size:一个整数,或着单个整数表示的元组或列表,指明1D卷积窗口的长度(即卷积滤波器窗口大小)
#activation:激活函数,relu激活函数是指让负数输出为0,正数原样输出
#传递一个input_shape的关键字参数给第一层,input_shape是一个tuple类型的数据,
#其中也可以填入None,如果填入None则表示此位置可能是任何正整数
#数据的batch大小不应包含在其中
#(28,28,1)表示28*28的矩阵输入,1为颜色通道,灰色图则为1,rgb图则为3

BatchNormlization:
规范化,该层在每一个批次上将前一层的激活值重新规范化,即使得其输出数据的均值接近0,标准差接近1
作用:
(1)加速收敛
(2)控制过拟合,可以少用或不用Dropout和正则
(3)降低网络对初始权重的敏感
(4)允许使用较大的学习率

Maxpooling图层:
只查看4个相邻像素并选取最大值,这将图像的大小减少了一半
strides设置查看窗口的大小,即在相邻的几个像素中做处理
'''
model=Sequential()

model.add(Conv2D(filters=16,kernel_size=(3,3),activation='relu',input_shape=(28,28,1)))
model.add(BatchNormalization())

model.add(Conv2D(filters=16,kernel_size=(3,3),activation='relu'))
model.add(BatchNormalization())
model.add(MaxPool2D(strides=(2,2)))
model.add(Dropout(0.25))

model.add(Conv2D(filters=32,kernel_size=(3,3),activation='relu'))
model.add(BatchNormalization())

model.add(Conv2D(filters=32,kernel_size=(3,3),activation='relu'))
model.add(BatchNormalization())
model.add(MaxPool2D(strides=(2,2)))
model.add(Dropout(0.25))

'''
Flatten层用来将输入“压平”,即把多维的输入一维化,常用在从卷积层(convolution)到全连接层(Dense)的过渡.也就是说,Convolution卷积层之后无法直接连接Dense全连接层,需要把卷积层的数据压平
'''

model.add(Flatten())
model.add(Dense(512,activation='relu'))#Dense(512)表示output的shape为(*,512)
model.add(Dropout(0.25))
model.add(Dense(1024,activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(10,activation='softmax')) #Dense(10)表示output的shape为(*,10),也就是0-9的10个标签

‘’’

模型训练(含数据增强)

‘’’Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
'''
zoom_range参数:可以调整图片在长或宽的方向进行放大,这个参数可以是一个数或者一个list.
当给出一个数时,图片同时在长宽两个方向上进行同等程度的放缩;
当给出的是一个list时,则代表[width_zoom_range,height_zoom_range],即分别对 长宽进行不同程度的放缩。而参数大于0小于1时,执行的是放大操作,当参数大于1时,执行 的是缩小操作.
width_shift_range:水平位置平移
height_shift_range:竖直位置平移
其参数可以是[0,1]的浮点数,也可以是大于1,但最好不要大于1,
超出原图范围的区域大多效果不好,其最大平移距离为图片长或宽的尺寸乘以参数
同样平移距离并不固定为最大平移距离,平移距离在[0,最大平移距离]区间内
rotation_range:
用户指定旋转角度范围,其参数只需指定一个整数即可,可在[0,指定角度]范围内进行随机角度旋转。
'''

#数据扩充技术扩充数据集
datagen=ImageDataGenerator(zoom_range=0.1,
height_shift_range=0.1,
width_shift_range=0.1,
rotation_range=10)

#在开始训练之前,需要先编译模型,选择损失函数和优化器,度量标准(仅用于评估)
model.compile(loss='categorical_crossentropy',optimizer=Adam(lr=1e-4),metrics=["accuracy"])
#以较小的学习率训练一次以确保收敛,然后加快速度,只是为了将每个时期的学习率降低10%
annealer=LearningRateScheduler(lambda x:1e-3 * 0.9 ** x)

#在训练期间使用非常小的验证集来节省内核中的时间
hist=model.fit_generator(datagen.flow(x_train,y_train,batch_size=16),
steps_per_epoch=500,
epochs=50,#Increase this when not on kaggle kernel
verbose=2, #1 for ETA,0 for silent
validation_data=(x_val[:400,:],y_val[:400,:]), #For speed
callbacks=[annealer] )

#训练集的正确率和误差:acc,loss
#验证集正确率和误差:val_acc,val_loss
'''

API:model.compile(optimizer='',loss='',metrics='')
优化器optimizer:该参数可以指定为已预定义的优化器名,如rmsprop,adagrad,
或者一个optimizer类的对象
损失函数loss:模型试图最小化的目标函数,可为预定义的损失函数名,如:
categorical_crossentropy,mse.也可以为一个损失函数
指标列表metrics:对于分类问题,我们一般将该列表设置为metrics=['accuracy'].
指标可以是一个预定义指标的名字,也可以是一个用户定制的函数,
指标函数应该返回单个张量,或一个完成metric_name->metric_value
映射的字典
'''


'''
fit_generator:模型的训练
datagen.flow:加载训练数据
batch_size=None:每经过多少个sample更新一次权重,default 32
epochs=1:训练的轮数
verbose=1:0不在标准输出流输出日志信息,1为输出进度条记录,
2为每个epoch输出一行记录
validation_data=None:验证集
steps_per_epoch=None:将一个epoch分为多少个steps,也就是划分一个batch_size多大,
比如steps_per_epoch=10,则就是讲训练集分为10份,不能和
batch_size共同使用
validation_steps=None:当steps_per_epoch被启用时才有用,验证集的batch_size
callbacks=None:list,list中的元素为keras.callbacks.Callback对象,在训练过程
中会调用list中的回调函数
'''

‘’’

模型评估

‘’’Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#评估
#我们在训练期间仅使用了验证集的子集来节省时间,现在我们检查整个验证集的性能
final_loss,final_acc=model.evaluate(x_val,y_val,verbose=0)

'''
evaluate:模型评估,返回损失函数和指定的精确指标
evaluate(self,x,y,batch_size=32,verbose=1,sample_weight=None)
x_val:数据
y_val:标签
batch_size:整数,指定进行梯度下降时每个batch包含的样本数。训练时一个batch
的样本会被计算一次梯度下降,使目标函数优化一步
#verbose:日志显示,0为不在标准输出流输出日志信息,1为输出进度条记录,
2为每个epoch输出一行记录
#sample_weight:权值的numpy array,用于在训练时调整损失函数(仅用于训练)
可以传递一个1D的与样本等长的向量用于对样本进行1对1的加权,
或者在面对时序数据时,传递一个的形式为(samples,sequence_length)
的矩阵来为每个时间步上的样本赋不同的权。
'''

print("Final loss:{0:.4f},final accuracy:{1:.4f}".format(final_loss,final_acc))

'''
hist.history包含val_loss,val_acc,loss,acc
#训练集的正确率和误差:acc,loss
#验证集正确率和误差:val_acc,val_loss
'''

plt.plot(hist.history['loss'],color='b')
plt.plot(hist.history['val_loss'],color='r')
plt.show()
plt.plot(hist.history['acc'],color='b')
plt.plot(hist.history['val_acc'],color='r')
plt.show()

‘’’

模型预测

‘’’Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
'''
predict(x,batch_size=None,verbose=0,steps=None,callbacks=None)
x:输入数据,作为Numpy数组(如果模型有多个输入,则为Numpy数组列表)
batch_size:整数,指定进行梯度下降时每个batch包含的样本数,训练时一个batch
的样本会被计算一次梯度下降,是目标函数优化一步
verbose:日志显示,0为不在标准输出流输出日志信息,1为输出进度条记录,
2为每个epoch输出一行记录
step:在宣布预测轮次结束之前的步骤总数(样本批次)
callbacks=None,list,list中的元素为keras.callbacks.Callback对象,
在训练过程中会调用list中的回调函数
'''

y_hat=model.predict(x_val)


#numpy.argmax(a,axis=None,out=None) 返回沿轴axis最大值的索引
y_pred=np.argmax(y_hat,axis=1) #axis=1表示横轴,axis=0表示纵轴
y_true=np.argmax(y_val,axis=1) #返回最大值的索引


#confusion_matrix(混淆矩阵):查看真实值被正确或者错误预测的个数,
#对角线表示0到9被正确预测的个数
cm=confusion_matrix(y_true,y_pred)
print(cm)

#可以调整相当多的参数(层数,过滤器数量,Dropout参数,学习速率,增强设置)

#发生过拟合现象时:在训练集上获得完美的结果,在验证集上结果很差。
#可以尝试增加Dropout参数,增加扩充,或者提前停止训练
#如果你想要提高准确度,可以尝试添加另外两个图层,或者增加过滤器的数量。

‘’’

保存结果

‘’’Python

1
2
3
4
5
6
7
8
9
10
11
12
#将结果写入文件
mnist_testset=np.loadtxt(test_file,skiprows=1,dtype='int',delimiter=',')
x_test=mnist_testset.astype("float32")
x_test=x_test.reshape(-1,28,28,1)/255.
y_hat=model.predict(x_test,batch_size=64)

y_pred=np.argmax(y_hat,axis=1)

with open(output_file,'w') as f:
f.write('ImageId,Label\n')
for i in range(len(y_pred)):
f.write("".join([str(i+1),',',str(y_pred[i]),'\n']))

‘’’


实验结果

​ Epoch 1/50

  • 176s - loss: 0.2878 - acc: 0.9149 - val_loss: 0.0615 - val_acc: 0.9875
    Epoch 2/50
  • 177s - loss: 0.1885 - acc: 0.9473 - val_loss: 0.1000 - val_acc: 0.9800
    Epoch 3/50
  • 176s - loss: 0.1515 - acc: 0.9583 - val_loss: 0.0388 - val_acc: 0.9875
    Epoch 4/50
  • 188s - loss: 0.1333 - acc: 0.9637 - val_loss: 0.0239 - val_acc: 0.9925
    Epoch 5/50
  • 174s - loss: 0.1149 - acc: 0.9689 - val_loss: 0.0104 - val_acc: 0.9950
    Epoch 6/50
  • 177s - loss: 0.1031 - acc: 0.9706 - val_loss: 0.0245 - val_acc: 0.9925
    Epoch 7/50
  • 184s - loss: 0.0913 - acc: 0.9748 - val_loss: 0.0169 - val_acc: 0.9950
    Epoch 8/50
  • 185s - loss: 0.0845 - acc: 0.9761 - val_loss: 0.0198 - val_acc: 0.9900
    Epoch 9/50
  • 201s - loss: 0.0785 - acc: 0.9775 - val_loss: 0.0103 - val_acc: 0.9975
    Epoch 10/50
  • 195s - loss: 0.0692 - acc: 0.9806 - val_loss: 0.0043 - val_acc: 1.0000
    Epoch 11/50
  • 189s - loss: 0.0687 - acc: 0.9803 - val_loss: 0.0285 - val_acc: 0.9925
    Epoch 12/50
  • 181s - loss: 0.0660 - acc: 0.9817 - val_loss: 0.0194 - val_acc: 0.9900
    Epoch 13/50
  • 197s - loss: 0.0564 - acc: 0.9839 - val_loss: 0.0119 - val_acc: 0.9950
    Epoch 14/50
  • 200s - loss: 0.0599 - acc: 0.9836 - val_loss: 0.0141 - val_acc: 0.9975
    Epoch 15/50
  • 201s - loss: 0.0567 - acc: 0.9840 - val_loss: 0.0111 - val_acc: 0.9975
    Epoch 16/50
  • 191s - loss: 0.0499 - acc: 0.9857 - val_loss: 0.0061 - val_acc: 0.9975
    Epoch 17/50
  • 189s - loss: 0.0530 - acc: 0.9848 - val_loss: 0.0129 - val_acc: 0.9925
    Epoch 18/50
  • 194s - loss: 0.0468 - acc: 0.9858 - val_loss: 0.0085 - val_acc: 0.9950
    Epoch 19/50
  • 199s - loss: 0.0486 - acc: 0.9865 - val_loss: 0.0065 - val_acc: 0.9975
    Epoch 20/50
  • 192s - loss: 0.0442 - acc: 0.9866 - val_loss: 0.0071 - val_acc: 0.9975
    Epoch 21/50
  • 190s - loss: 0.0434 - acc: 0.9883 - val_loss: 0.0038 - val_acc: 1.0000
    Epoch 22/50
  • 173s - loss: 0.0426 - acc: 0.9876 - val_loss: 0.0088 - val_acc: 0.9950
    Epoch 23/50
  • 191s - loss: 0.0419 - acc: 0.9879 - val_loss: 0.0094 - val_acc: 0.9950
    Epoch 24/50
  • 189s - loss: 0.0424 - acc: 0.9877 - val_loss: 0.0060 - val_acc: 0.9975
    Epoch 25/50
  • 191s - loss: 0.0384 - acc: 0.9886 - val_loss: 0.0060 - val_acc: 0.9975
    Epoch 26/50
  • 182s - loss: 0.0395 - acc: 0.9890 - val_loss: 0.0055 - val_acc: 0.9975
    Epoch 27/50
  • 173s - loss: 0.0385 - acc: 0.9889 - val_loss: 0.0072 - val_acc: 0.9950
    Epoch 28/50
  • 172s - loss: 0.0348 - acc: 0.9892 - val_loss: 0.0057 - val_acc: 0.9950
    Epoch 29/50
  • 171s - loss: 0.0371 - acc: 0.9891 - val_loss: 0.0047 - val_acc: 0.9975
    Epoch 30/50
  • 171s - loss: 0.0372 - acc: 0.9897 - val_loss: 0.0059 - val_acc: 0.9950
    Epoch 31/50
  • 171s - loss: 0.0340 - acc: 0.9898 - val_loss: 0.0092 - val_acc: 0.9950
    Epoch 32/50
  • 173s - loss: 0.0374 - acc: 0.9892 - val_loss: 0.0071 - val_acc: 0.9975
    Epoch 33/50
  • 196s - loss: 0.0352 - acc: 0.9894 - val_loss: 0.0063 - val_acc: 0.9950
    Epoch 34/50
  • 209s - loss: 0.0347 - acc: 0.9895 - val_loss: 0.0035 - val_acc: 0.9975
    Epoch 35/50
  • 192s - loss: 0.0363 - acc: 0.9900 - val_loss: 0.0040 - val_acc: 0.9975
    Epoch 36/50
  • 190s - loss: 0.0352 - acc: 0.9899 - val_loss: 0.0043 - val_acc: 0.9975
    Epoch 37/50
  • 203s - loss: 0.0363 - acc: 0.9896 - val_loss: 0.0051 - val_acc: 0.9950
    Epoch 38/50
  • 199s - loss: 0.0345 - acc: 0.9900 - val_loss: 0.0043 - val_acc: 0.9975
    Epoch 39/50
  • 190s - loss: 0.0346 - acc: 0.9897 - val_loss: 0.0044 - val_acc: 0.9975
    Epoch 40/50
  • 187s - loss: 0.0351 - acc: 0.9899 - val_loss: 0.0034 - val_acc: 0.9975
    Epoch 41/50
  • 181s - loss: 0.0325 - acc: 0.9906 - val_loss: 0.0043 - val_acc: 0.9975
    Epoch 42/50
  • 184s - loss: 0.0340 - acc: 0.9905 - val_loss: 0.0049 - val_acc: 0.9950
    Epoch 43/50
  • 173s - loss: 0.0342 - acc: 0.9897 - val_loss: 0.0053 - val_acc: 0.9975
    Epoch 44/50
  • 190s - loss: 0.0339 - acc: 0.9898 - val_loss: 0.0059 - val_acc: 0.9950
    Epoch 45/50
  • 206s - loss: 0.0370 - acc: 0.9900 - val_loss: 0.0048 - val_acc: 0.9975
    Epoch 46/50
  • 204s - loss: 0.0332 - acc: 0.9905 - val_loss: 0.0047 - val_acc: 0.9950
    Epoch 47/50
  • 212s - loss: 0.0345 - acc: 0.9903 - val_loss: 0.0048 - val_acc: 0.9975
    Epoch 48/50
  • 198s - loss: 0.0305 - acc: 0.9910 - val_loss: 0.0053 - val_acc: 0.9975
    Epoch 49/50
  • 174s - loss: 0.0330 - acc: 0.9905 - val_loss: 0.0050 - val_acc: 0.9975
    Epoch 50/50
  • 179s - loss: 0.0323 - acc: 0.9905 - val_loss: 0.0049 - val_acc: 0.9950
    Final loss:0.0209,final accuracy:0.9950