使用keras训练mnist数据集

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wphkadn/article/details/86772708

文章组织结构

本文使用不同的网络结构来训练mnist,从中了解一些问题

  • 使用keras 序贯模型实现简单的mnist识别,正确率约为90%
  • 更换loss函数,更换优化器来获得更好效果,正确率约为 97%
  • 使用卷积层来替换Dense层,正确率达到99%
  • 使用VGG16网络,正确率达到了97.4%
  • 经验教训

原始代码

(转载自 https://www.cnblogs.com/LHWorldBlog/p/8677131.html)

#=================== Test 1    Hello Keras for mnist==============================================================================
# 这是一个简单的全连接神经网络的例子。
from keras.models import Sequential  # 采用贯序模型
from keras.layers import Input, Dense, Dropout, Activation
from keras.models import Model
from keras.optimizers import SGD
from keras.datasets import mnist
import numpy as np
 
tBatchSize = 128
'''第一步:选择模型'''
model = Sequential() # 采用贯序模型
 
'''第二步:构建网络层'''
# Dense 这是第一个隐藏层,并附带定义了输入层,该隐含层有500个神经元。输入则是 784个节点
model.add(Dense(500,input_shape=(784,))) # 输入层,28*28=784 输入层将二维矩阵换成了一维向量输入
model.add(Activation('tanh')) # 激活函数是tanh 为双曲正切 
# tanh(x) = sinh(x)/cosh(x) = (e^x - e^(-x))/(e^x + e^(-x))
model.add(Dropout(0.5)) # 采用50%的dropout  随机取一半进行训练
 
#构建的第2个层作为隐藏层2, (如果加上输入层,实际上是第三层)
model.add(Dense(500)) # 隐藏层节点500个
model.add(Activation('tanh'))
model.add(Dropout(0.5))
 
model.add(Dense(500)) # 隐藏层3,节点500个
model.add(Activation('tanh'))
#model.add(Dropout(0.5))
 
#构建的第3个层作为输出层
model.add(Dense(10)) # 输出结果是10个类别,所以维度是10
# softmax介绍可以参考https://blog.csdn.net/haolexiao/article/details/72757796
model.add(Activation('softmax')) # 最后一层用softmax作为激活函数
 
'''第三步:网络优化和编译'''
#   lr:大于0的浮点数,学习率
#   momentum:大于0的浮点数,动量参数
#   decay:大于0的浮点数,每次更新后的学习率衰减值
#   nesterov:布尔值,确定是否使用Nesterov动量
sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True) # 优化函数,设定学习率(lr)等参数

model.compile(loss='categorical_crossentropy', optimizer=sgd) # 使用交叉熵作为loss函数  
 
'''第四步:训练'''
 
# 数据集获取 mnist 数据集的介绍可以参考 https://blog.csdn.net/simple_the_best/article/details/75267863
(X_train, y_train), (X_test, y_test) = mnist.load_data() # 使用Keras自带的mnist工具读取数据(第一次需要联网)
 
# 由于mist的输入数据维度是(num, 28, 28),这里需要把后面的维度直接拼起来变成784维
X_train = X_train.reshape(X_train.shape[0], X_train.shape[1] * X_train.shape[2])
X_test = X_test.reshape(X_test.shape[0], X_test.shape[1] * X_test.shape[2])
 
#这个能生成一个OneHot的10维向量,作为Y_train的一行,这样Y_train就有60000行OneHot作为输出
Y_train = (np.arange(10) == y_train[:, None]).astype(int)  # 整理输出
Y_test = (np.arange(10) == y_test[:, None]).astype(int)    #np.arange(5) = array([0,1,2,3,4])
 
'''
   .fit的一些参数
   batch_size:对总的样本数进行分组,每组包含的样本数量
   epochs :训练次数
   shuffle:是否把数据随机打乱之后再进行训练
   validation_split:拿出百分之多少用来做交叉验证
   verbose:屏显模式 0:不输出  1:输出进度  2:输出每次的训练结果
'''
model.fit(X_train, Y_train, batch_size=tBatchSize, epochs=50, 
               shuffle=True, verbose=2, validation_split=0.3)
#model.evaluate(X_test, Y_test, batch_size=200, verbose=0)
 
 
'''第五步:输出'''
print("test set")
# 误差评价 :按batch计算在batch用到的输入数据上模型的误差
scores = model.evaluate(X_test,Y_test, batch_size=tBatchSize, verbose=0)
print("")
print("The test loss is %f" % scores)
 
# 根据模型获取预测结果  为了节约计算内存,也是分组(batch)load到内存中的,
result = model.predict(X_test,batch_size=tBatchSize,verbose=1)
 
# 找到每行最大的序号
 #axis=1表示按行 取最大值   如果axis=0表示按列 取最大值 axis=None表示全部
result_max = np.argmax(result, axis = 1)
 # 这是结果的真实序号
test_max = np.argmax(Y_test, axis = 1)

 
result_bool = np.equal(result_max, test_max) # 预测结果和真实结果一致的为真(按元素比较)
true_num = np.sum(result_bool) #正确结果的数量
print("The accuracy of the model is %f" % (true_num/len(result_bool))) # 验证结果的准确率
 

该代码正确率约为90%.因为存在dropout所以不太稳定.

一点小改进

因为上面的代码使用的loss函数为 tanh,自然想到要替换为relu, 优化方法为sgd,自然想到要替换为adam
因为 relu的函数结果为 0-1,那么原来的图片就应该转换为 0-1范围,这样能更接近于 relu的结果.
于是加入
X_train = X_train / 256.0
如果没有进行这个处理, 效果会没有那么好, loss偏高
但是使用归一化就会使得结果崩溃,不能用X_train = (X_train - 127) /127, 猜测是因为 经过归一化的图片有负值,而relu函数将这些负值置为0, 虽然 卷积核能使 原来是负的像素值变为正的,但因为 mnist大部分是黑色,少部分白色,所以导致失灵,因而要尽量确保 处理后的图像的值仍然为正值. 另外分类的结果都是正的,这也侧面要求处理后的图像的值是正的.

改进loss函数 ,以及输入图像和优化器的代码,结果为 97%左右
from keras.models import Sequential  # 采用贯序模型
from keras.layers import Input, Dense, Dropout, Activation
from keras.models import Model
from keras.optimizers import SGD
from keras.datasets import mnist
import numpy as np
 
tBatchSize = 128
'''第一步:选择模型'''
model = Sequential() # 采用贯序模型
 
'''第二步:构建网络层'''
model.add(Dense(500,input_shape=(784,))) # 输入层,28*28=784 输入层将二维矩阵换成了一维向量输入
model.add(Activation('relu')) # 激活函数是tanh 为双曲正切
model.add(Dropout(0.5)) # 采用50%的dropout  随机取一半进行训练

model.add(Activation('relu'))
model.add(Dropout(0.5))
 
model.add(Activation('relu'))
#model.add(Dropout(0.5))
 
model.add(Dense(10)) # 输出结果是10个类别,所以维度是10
model.add(Activation('softmax')) # 最后一层用softmax作为激活函数
 
'''第三步:网络优化和编译'''
model.compile(loss='categorical_crossentropy', optimizer='adam') # 使用交叉熵作为loss函数    
 
'''第四步:训练'''
 
# 数据集获取 mnist 数据集的介绍可以参考 https://blog.csdn.net/simple_the_best/article/details/75267863
(X_train, y_train), (X_test, y_test) = mnist.load_data() # 使用Keras自带的mnist工具读取数据(第一次需要联网)
 
# 由于mist的输入数据维度是(num, 28, 28),这里需要把后面的维度直接拼起来变成784维
X_train = X_train.reshape(X_train.shape[0], X_train.shape[1] * X_train.shape[2])
X_test = X_test.reshape(X_test.shape[0], X_test.shape[1] * X_test.shape[2])

#这个能生成一个OneHot的10维向量,作为Y_train的一行,这样Y_train就有60000行OneHot作为输出
Y_train = (np.arange(10) == y_train[:, None]).astype(int)  # 整理输出
Y_test = (np.arange(10) == y_test[:, None]).astype(int)    #np.arange(5) = array([0,1,2,3,4])
 
 #非常重要!!!
X_train = X_train/256.0
X_test = X_test / 256.0

model.fit(X_train, Y_train, batch_size=tBatchSize, epochs=50, 
              shuffle=True, verbose=2, validation_split=0.3)
 

加入卷积层

因为是图片,加入卷积是很自然的想法,只是这里需要修改一下输入数据的shape,同时加入Flatten

#!!非常重要
X_train = X_train/256.0
X_test = X_test / 256.0

以下是代码,正确率为 99%,虽然训练速度变慢了一点,但准确率得到了提升

from keras.models import Sequential  # 采用贯序模型
from keras.layers import Input, Dense, Dropout, Activation
from keras.models import Model
from keras.optimizers import SGD
from keras.datasets import mnist
from keras.layers import Conv2D, MaxPooling2D, Flatten
import numpy as np
 
tBatchSize = 128
'''第一步:选择模型'''
model = Sequential() # 采用贯序模型
 
'''第二步:构建网络层'''

model.add(Conv2D(32,(3,3), activation='relu', input_shape=(28,28,1)))
model.add(Conv2D(32,(3,3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(500)) # 输入层,28*28=784 输入层将二维矩阵换成了一维向量输入
model.add(Activation('relu')) # 激活函数是tanh 为双曲正切 
model.add(Dropout(0.5)) # 采用50%的dropout  随机取一半进行训练
 
#构建的第2个层作为隐藏层2, (如果加上输入层,实际上是第三层)
model.add(Dense(500)) # 隐藏层节点500个
model.add(Activation('relu'))
model.add(Dropout(0.5))
 
model.add(Dense(500)) # 隐藏层3,节点500个
model.add(Activation('relu'))
#model.add(Dropout(0.5))
 
#构建的第3个层作为输出层
model.add(Dense(10)) # 输出结果是10个类别,所以维度是10
# softmax介绍可以参考https://blog.csdn.net/haolexiao/article/details/72757796
model.add(Activation('softmax')) # 最后一层用softmax作为激活函数

model.compile(loss='categorical_crossentropy', optimizer='adam') # 使用交叉熵作为loss函数    

'''第四步:训练'''
(X_train, y_train), (X_test, y_test) = mnist.load_data() # 使用Keras自带的mnist工具读取数据(第一次需要联网)
 
# 由于mist的输入数据维度是(num, 28, 28),这里需要把后面的维度直接拼起来变成784维
X_train = X_train.reshape(X_train.shape[0], X_train.shape[1] , X_train.shape[2],1)
X_test = X_test.reshape(X_test.shape[0], X_test.shape[1] , X_test.shape[2],1)

#这个能生成一个OneHot的10维向量,作为Y_train的一行,这样Y_train就有60000行OneHot作为输出
Y_train = (np.arange(10) == y_train[:, None]).astype(int)  # 整理输出
Y_test = (np.arange(10) == y_test[:, None]).astype(int)    #np.arange(5) = array([0,1,2,3,4])
#!!非常重要
X_train = X_train/256.0
X_test = X_test / 256.0

model.fit(X_train, Y_train, batch_size=tBatchSize, epochs=50, 
              shuffle=True, verbose=2, validation_split=0.3)

使用VGG16

使用VGG16的话,需要将图像变为3通道的,同时VGG16要求输入图像大小最少为48483,因此需要调整图像,
同样需要使用

X_train = X_train/256.0
X_test = X_test / 256.0

否则会出现不收敛的情况.
VGG16的好处在于参数来自于imagenet,卷积核的参数设置为不变的,这样能利用前人训练好的结果,其loss更低,准确率更高
以下为代码

from keras.models import Sequential  # 采用贯序模型
from keras.layers import Input, Dense, Dropout, Activation, Flatten
from keras.models import Model
from keras.optimizers import SGD
from keras.datasets import mnist
from keras.applications.vgg16 import VGG16
import numpy as np
import cv2

tBatchSize = 64
'''第一步:选择模型'''   #VGG16要求图片大小最少48 
model_vgg = VGG16(include_top=False, input_shape=(48,48,3))
for layer in model_vgg.layers:
    layer.trainable = False

model = Flatten(name='flatten')(model_vgg.output)
model = Dense(500, activation='relu', name='fc1')(model)
model = Dense(500, activation='relu', name='fc2')(model)
model = Dropout(0.5)(model)
model = Dense(10, activation='softmax')(model)
model = Model(inputs=model_vgg.input, 
                        outputs = model, name = 'vgg16')
 
model.compile(loss='categorical_crossentropy', optimizer='adam') # 使用交叉熵作为loss函数   

(X_train, y_train), (X_test, y_test) = mnist.load_data() # 使用Keras自带的mnist工具读取数据(第一次需要联网)
 
# 由于mist的输入数据维度是(num, 28, 28),vgg16 需要三维图像,因为扩充一下mnist的最后一维

X_train = [cv2.cvtColor(cv2.resize(i, (48, 48)), cv2.COLOR_GRAY2RGB) for i in X_train]
X_test = [cv2.cvtColor(cv2.resize(i, (48, 48)), cv2.COLOR_GRAY2RGB) for i in X_test]

X_train = np.array(X_train)
X_test = np.array(X_test)

#这个能生成一个OneHot的10维向量,作为Y_train的一行,这样Y_train就有60000行OneHot作为输出
Y_train = (np.arange(10) == y_train[:, None]).astype(int)  # 整理输出
Y_test = (np.arange(10) == y_test[:, None]).astype(int)    #np.arange(5) = array([0,1,2,3,4])
 
 # 非常重要! 虽然不至于会导致 不收敛,但是加入以下代码, 收敛速度会得到明显提升
X_train = X_train/256.0
X_test = X_test / 256.0

model.fit(X_train, Y_train, batch_size=tBatchSize, epochs=10, shuffle=True, validation_split=0.3)

经验与教训

直接将最初的 loss函数从tanh换为relu,发现loss难以收敛
将图片预处理为 x= (x-127)/127, loss函数从tanh换为relu,发现loss难以收敛
可以认为 relu函数的应用是有一定的范围的,如果要应用relu, 首先要进行一定的归一化.
在mnist的例子中,出现负值不行,但其他地方或许是可以的.

展开阅读全文

没有更多推荐了,返回首页