Keras-LeNet5-图片分类
1、原始数据集
下载mnist.pkl.gz数据集,得到raw_train, raw_validation, raw_test。
raw_train是一个三维列表,第1个分量是(50000,784)的列表,表示50000个样本,每个样本是(28,28)图片拉直成的向量。第2个分量是50000大小的一维列表,记录每个样本的数字。
raw_validation是一个三维列表,第1个分量是(10000,784)的列表,表示10000个样本,每个样本是(28,28)图片拉直成的向量。第2个分量是10000大小的一维列表,记录每个样本的数字。
raw_test是一个三维列表,第1个分量是(10000,784)的列表,表示10000个样本,每个样本是(28,28)图片拉直成的向量。第2个分量是10000大小的一维列表,记录每个样本的数字。
对原始数据集进行加工,制作训练数据集,得到train_x、train_y、validation_x、validation_y,、test_x、test_y。train数据集都是三维数组,每个样本是(28,28)大小的数组。test数据集都是二维数组,每个样本是10维的向量。
train_x : 50000, 28, 28
train_y : 50000, 10
validation_x : 10000, 28, 28
validation_y : 10000, 10
test_x : 10000, 28, 28
test_y : 10000, 10
2、LeNet5网络细节
LeNet5网络结构如下:
第1步、初始输入图片大小:(28,28),灰度图,只含有单通道。
第2步、卷积层,利用6个(5,5)的卷积核,stride=1,映射得到6张(24,24)的feature map。
第3步、最大池化层,取池化核(2,2),stride=2,映射得到6张(12,12)的feature map,然后利用sigmoid激活函数进行加工。
第4步、卷积层。将6张(12,12)的feature map,映射成16张(8,8)的feature map。具体映射细节如下:
将6张(12,12)的feature map分别标记为A0-A5,将16张(8,8)的feature map分别标记为B0-B15。举个特例,如何由A0-A5映射到B0呢?
由上面表格可以看到详细的连接关系。利用3个(5,5)的卷积核,分别对A0、A1、A2进行卷积再加上偏置,得到3个(8,8)的矩阵,进行相加,得到B0的feature map。
如何由A0-A5映射到B15呢?利用6个(5,5)的卷积核,分别对A0-A5进行卷积再加上偏置,得到6个(8,8)的矩阵,进行相加,得到B15的feature map。
第5步、最大池化层,取池化核(2,2),stride=2,映射得到16张(4,4)的feature map,然后利用sigmoid激活函数进行加工。
第6步、卷积拉直层,取120个(16,4,4)的卷积核,全连接。由于上一层的feature map尺寸(4,4),用(4,4)卷积后恰好得到了一个数字。这就完成了由CNN特征图到BP神经元的映射。然后利用sigmoid激活函数进行加工。
第7步、全连接层,取84个神经元,取tanh激活函数。
第8步:全连接层,取10个神经元,取tanh激活函数。
3、对模型结果的深入思考
自己训练了10000次,大概花费30h,模型分类准确率98.47%,这个分类问题已经算解决的比较好了。
误差损失函数图像如下:
思考1:为什么网络一开始训练的准确率都在0.1040附近?
因为网络刚开始是随机初始化,相当于随机乱猜。这是10分类问题,数学意义上每次猜对的概率p=0.1,由大数定律可知,样本准确率就应该在0.1附近。
思考2:卷积层到底提取到了什么特征?
这是原始输入图像:
利用已经训练好的模型,对原始(28,28)灰度图做第一次卷积,得到6个(24,24)的feature map。由图中可以看出,前期的卷积层提取得到的图片特征较为直观、具体,能够很清晰地被我们所理解。
对6个(24,24)的feature map做第一次池化,得到6个(12,12)的feature map。
对6个(12,12)的feature map做第二次卷积,得到16个(8,8)的feature map。可以看出,后期的卷积层提取得到的图片特征较为抽象。我们虽然无法直观上去理解这种特征提取背后的idea,但这种由机器自动提取的特征,往往能取得比较好的分类效果。
这是图片最后映射成的向量,可以看出,映射结果与实际y向量非常接近了。
思考3:对于超大型数据集,无法加载进内存进行训练,如何分批量加载数据训练?
对于大型图片数据集,如果一次性加载进内存,利用fit函数进行网络训练,消耗空间太大,很可能出现程序异常终止、电脑卡死的情况。
CNN背后的优化原理是,每次计算一个batch的梯度以代替所有样本梯度,从而进行目标函数优化。所以我们可以利用生成器函数yield,每次只加载进一个batch的图片进入内存,这也大大加快了网络训练速度。
思考4:网络训练调节技巧。在每一大轮的训练过程中,应该怎样调节学习率?
对网络进行训练时,如果直接设置一个model.fit_generator函数,我们无法知道网络多少轮训练好了、多少轮已经过拟合、什么时候应该手动调节学习率。无法真正做到实时调控,让模型朝着我们想要的方向优化。这时可以这样做:
假设我们想训练10000个epoch,我们把每1000个epoch记为一个大轮。我们每次只用model.fit_generator函数训练epoch=1000,然后保存模型,输出loss函数时序图。然后我们再加载已经保存好的模型,在此基础上继续进行训练,调整优化参数。
如果上一轮loss函数是不断降低的,我们这一轮可以增大学习率。如果上一轮loss函数已经趋于停滞,这时应该分情况处理。如果此时测试集精度已经达到0.95,说明模型现在真的在全局最优附近,这时应该减小学习率,细化,一点一点的继续优化。如果测试集精度在0.6附近就停滞了,说明模型此时陷入了一个局部最优解,应该增大学习率,从局部最优跳出来。
4、具体代码
数据集加工
import pickle
import gzip
import numpy as np
import cv2
def number_vector(j):
vector = np.zeros(10)
vector[j] = 1.0
return vector
def vector_image(v):
img = np.zeros((28, 28))
for i in range(28):
img[i, :] = v[28*i:(28*i+28)]
return img
# raw_train : [50000*784, 50000]
# raw_validation : [10000*784, 10000]
# raw_test : [10000*784, 10000]
# train_x : 50000, 28, 28
# train_y : 50000, 10
# validation_x : 10000, 28, 28
# validation_y : 10000, 10
# test_x : 10000, 28, 28
# test_y : 10000, 10
def make_dataset():
f = gzip.open('/home/archer/CODE/PF/data/mnist.pkl.gz', 'rb')
(raw_train, raw_validation, raw_test) = pickle.load(f, encoding='bytes')
f.close()
train_x = np.zeros((50000, 28, 28))
train_y = np.zeros((50000, 10))
for i in range(50000):
train_x[i, :, :] = vector_image(raw_train[0][i])
train_y[i, :] = number_vector(raw_train[1][i])
validation_x = np.zeros((10000, 28, 28))
validation_y = np.zeros((10000, 10))
test_x = np.zeros((10000, 28, 28))
test_y = np.zeros((10000, 10))
for i in range(10000):
validation_x[i, :, :] = vector_image(raw_validation[0][i])
validation_y[i, :] = number_vector(raw_validation[1][i])
test_x[i, :, :] = vector_image(raw_test[0][i])
test_y[i, :] = number_vector(raw_test[1][i])
# display 0-9 picture
# display_img = test_x[0]
# print('number - vector', test_y[0])
# cv2.namedWindow("Image")
# cv2.imshow("Image", display_img)
# cv2.waitKey(0)
return train_x, train_y, validation_x, validation_y, test_x, test_y
LeNet5网络搭建
import numpy as np
from keras.models import Sequential, Model
from keras.layers import Dense, Activation, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras.models import load_model
import matplotlib.pyplot as plt
# LeNet network
# input : 28 * 28
# conv1 : 6 * 24 *24
# pool1 : 6 * 12 * 12
# activation : 6 * 12 * 12
# conv2 : 16 * 8 * 8
# pool2 : 16 * 4 * 4
# activation : 16 * 4 * 4
# conv3 : 120
# activation : 120
# dens 84
# activation : 84
# dense : 10
# activation : 10
def create_network():
np.random.seed(1)
model = Sequential()
model.add(Conv2D(6, (5, 5), input_shape=(28, 28, 1), strides=1))
model.add(MaxPooling2D(pool_size=(2, 2), strides=2))
model.add(Activation('sigmoid'))
model.add(Conv2D(16, (5, 5), strides=1))
model.add(MaxPooling2D(pool_size=(2, 2), strides=2))
model.add(Activation('sigmoid'))
model.add(Conv2D(120, (4, 4), strides=1))
model.add(Flatten())
model.add(Activation('tanh'))
model.add(Dense(84))
model.add(Activation('tanh'))
model.add(Dense(10))
model.add(Activation('softmax'))
model.summary()
return model
# batch generator: reduce the consumption of computer memory
def generator(train_x, train_y, batch_size):
while 1:
row = np.random.randint(0, len(train_x), size=batch_size)
x = train_x[row]
y = train_y[row]
yield x, y
# create model and train and save
def train_network(train_x, train_y, test_x, test_y, epoch, batch_size):
train_x = train_x[:, :, :, np.newaxis]
test_x = test_x[:, :, :, np.newaxis]
model = create_network()
model.compile(loss='categorical_crossentropy', optimizer='adadelta', metrics=['accuracy'])
model.fit_generator(generator(train_x, train_y, batch_size), epochs=epoch,
steps_per_epoch=len(train_x) // batch_size)
score = model.evaluate(test_x, test_y, verbose=0)
print('first_model test accuracy:', score[1])
model.save('first_model.h5')
# Load the partially trained model and continue training and save
def load_network_then_train(train_x, train_y, test_x, test_y, epoch, batch_size, input_name, output_name):
train_x = train_x[:, :, :, np.newaxis]
test_x = test_x[:, :, :, np.newaxis]
model = load_model(input_name)
history = model.fit_generator(generator(train_x, train_y, batch_size),
epochs=epoch, steps_per_epoch=len(train_x) // batch_size)
score = model.evaluate(test_x, test_y, verbose=0)
print(output_name, 'test accuracy:', score[1])
model.save(output_name)
show_plot(history)
# plot the loss and the accuracy
def show_plot(history):
# list all data in history
print(history.history.keys())
plt.plot(history.history['loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.savefig('loss1.jpg')
plt.show()
plt.plot(history.history['accuracy'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.savefig('accuracy1.jpg')
plt.show()
# Show network intermediate results - feature map
def show_feature_map(test_x, input_name):
model = load_model(input_name)
model.summary()
test_x = test_x[:, :, :, np.newaxis]
conv1_model = Model(inputs=model.input, outputs=model.layers[0].output)
conv1_output = conv1_model.predict(test_x)
feature_img = conv1_output[0, :, :, 0]
plt.imshow(feature_img, cmap='gray')
plt.show()
主函数调用
import getdata as gt
import network as nt
if __name__ == "__main__":
train_x, train_y, validation_x, validation_y, test_x, test_y = gt.make_dataset()
nt.train_network(train_x, train_y, test_x, test_y, epoch=10, batch_size=128)
nt.load_network_then_train(train_x, train_y, test_x, test_y, epoch=10, batch_size=128, input_name='first_model.h5', output_name='second_model.h5')
nt.show_feature_map(test_x, input_name='second_model.h5')
5、项目链接
如果代码跑不通,可以去下载我的项目链接:https://blog.csdn.net/Twilight737