本次实践是基于李宏毅老师ML课程中“Hello world” of deep learning章节进行的实验探究。课程中李宏毅老师使用的是keras 2.0.1。可以参考中文文档自行安装。
参考链接:
Keras中文文档
李宏毅老师“Hello world” of deep learning PPT
一张图解释为什么要用Keras
1. Keras可以看做TensorFlow的接口,用Keras就相当于在用TensorFlow。
2. Keras集成了TensorFlow的许多复杂操作,使用起来更简洁。
3. 入门直接上手TensorFlow比较复杂。
数据准备
课程中获取数据的方法是从库中直接load_data
from keras.datasets import mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
但是由于网络限制(不知道是不是因为wall,试了几次都没成功),就直接去官网下了数据,参见mnist数据集下载地址。
该数据下载后得到的是idx格式数据,具体处理方法参考了这篇博客使用Python解析MNIST数据集(IDX文件格式),测试可用的源码如下(规则在注释里写得很详细),该文件保存为load_data.py,在后文中会直接调用。
# encoding: utf-8
"""
对MNIST手写数字数据文件转换为bmp图片文件格式。
数据集下载地址为http://yann.lecun.com/exdb/mnist。
相关格式转换见官网以及代码注释。
========================
关于IDX文件格式的解析规则:
========================
THE IDX FILE FORMAT
the IDX file format is a simple format for vectors and multidimensional matrices of various numerical types.
The basic format is
magic number
size in dimension 0
size in dimension 1
size in dimension 2
.....
size in dimension N
data
The magic number is an integer (MSB first). The first 2 bytes are always 0.
The third byte codes the type of the data:
0x08: unsigned byte
0x09: signed byte
0x0B: short (2 bytes)
0x0C: int (4 bytes)
0x0D: float (4 bytes)
0x0E: double (8 bytes)
The 4-th byte codes the number of dimensions of the vector/matrix: 1 for vectors, 2 for matrices....
The sizes in each dimension are 4-byte integers (MSB first, high endian, like in most non-Intel processors).
The data is stored like in a C array, i.e. the index in the last dimension changes the fastest.
"""
import numpy as np
import struct
import matplotlib.pyplot as plt
# 训练集文件
train_images_idx3_ubyte_file = './data/train-images-idx3-ubyte'
# 训练集标签文件
train_labels_idx1_ubyte_file = './data/train-labels-idx1-ubyte'
# 测试集文件
test_images_idx3_ubyte_file = './data/t10k-images-idx3-ubyte'
# 测试集标签文件
test_labels_idx1_ubyte_file = './data/t10k-labels-idx1-ubyte'
def decode_idx3_ubyte(idx3_ubyte_file):
"""
解析idx3文件的通用函数
:param idx3_ubyte_file: idx3文件路径
:return: 数据集
"""
# 读取二进制数据
bin_data = open(idx3_ubyte_file, 'rb').read()
# 解析文件头信息,依次为魔数、图片数量、每张图片高、每张图片宽
offset = 0
fmt_header = '>iiii'
magic_number, num_images, num_rows, num_cols = struct.unpack_from(fmt_header, bin_data, offset)
#print('魔数:%d, 图片数量: %d张, 图片大小: %d*%d' % (magic_number, num_images, num_rows, num_cols))
# 解析数据集
image_size = num_rows * num_cols
offset += struct.calcsize(fmt_header)
fmt_image = '>' + str(image_size) + 'B'
images = np.empty((num_images, num_rows, num_cols))
for i in range(num_images):
#if (i + 1) % 10000 == 0:
#print('已解析 %d' % (i + 1) + '张')
images[i] = np.array(struct.unpack_from(fmt_image, bin_data, offset)).reshape((num_rows, num_cols))
offset += struct.calcsize(fmt_image)
return images
def decode_idx1_ubyte(idx1_ubyte_file):
"""
解析idx1文件的通用函数
:param idx1_ubyte_file: idx1文件路径
:return: 数据集
"""
# 读取二进制数据
bin_data = open(idx1_ubyte_file, 'rb').read()
# 解析文件头信息,依次为魔数和标签数
offset = 0
fmt_header = '>ii'
magic_number, num_images = struct.unpack_from(fmt_header, bin_data, offset)
#print('魔数:%d, 图片数量: %d张' % (magic_number, num_images))
# 解析数据集
offset += struct.calcsize(fmt_header)
fmt_image = '>B'
labels = np.empty(num_images)
for i in range(num_images):
#if (i + 1) % 10000 == 0:
# print('已解析 %d' % (i + 1) + '张')
labels[i] = struct.unpack_from(fmt_image, bin_data, offset)[0]
offset += struct.calcsize(fmt_image)
return labels
def load_train_images(idx_ubyte_file=train_images_idx3_ubyte_file):
"""
TRAINING SET IMAGE FILE (train-images-idx3-ubyte):
[offset] [type] [value] [description]
0000 32 bit integer 0x00000803(2051) magic number
0004 32 bit integer 60000 number of images
0008 32 bit integer 28 number of rows
0012 32 bit integer 28 number of columns
0016 unsigned byte ?? pixel
0017 unsigned byte ?? pixel
........
xxxx unsigned byte ?? pixel
Pixels are organized row-wise. Pixel values are 0 to 255. 0 means background (white), 255 means foreground (black).
:param idx_ubyte_file: idx文件路径
:return: n*row*col维np.array对象,n为图片数量
"""
return decode_idx3_ubyte(idx_ubyte_file)
def load_train_labels(idx_ubyte_file=train_labels_idx1_ubyte_file):
"""
TRAINING SET LABEL FILE (train-labels-idx1-ubyte):
[offset] [type] [value] [description]
0000 32 bit integer 0x00000801(2049) magic number (MSB first)
0004 32 bit integer 60000 number of items
0008 unsigned byte ?? label
0009 unsigned byte ?? label
........
xxxx unsigned byte ?? label
The labels values are 0 to 9.
:param idx_ubyte_file: idx文件路径
:return: n*1维np.array对象,n为图片数量
"""
return decode_idx1_ubyte(idx_ubyte_file)
def load_test_images(idx_ubyte_file=test_images_idx3_ubyte_file):
"""
TEST SET IMAGE FILE (t10k-images-idx3-ubyte):
[offset] [type] [value] [description]
0000 32 bit integer 0x00000803(2051) magic number
0004 32 bit integer 10000 number of images
0008 32 bit integer 28 number of rows
0012 32 bit integer 28 number of columns
0016 unsigned byte ?? pixel
0017 unsigned byte ?? pixel
........
xxxx unsigned byte ?? pixel
Pixels are organized row-wise. Pixel values are 0 to 255. 0 means background (white), 255 means foreground (black).
:param idx_ubyte_file: idx文件路径
:return: n*row*col维np.array对象,n为图片数量
"""
return decode_idx3_ubyte(idx_ubyte_file)
def load_test_labels(idx_ubyte_file=test_labels_idx1_ubyte_file):
"""
TEST SET LABEL FILE (t10k-labels-idx1-ubyte):
[offset] [type] [value] [description]
0000 32 bit integer 0x00000801(2049) magic number (MSB first)
0004 32 bit integer 10000 number of items
0008 unsigned byte ?? label
0009 unsigned byte ?? label
........
xxxx unsigned byte ?? label
The labels values are 0 to 9.
:param idx_ubyte_file: idx文件路径
:return: n*1维np.array对象,n为图片数量
"""
return decode_idx1_ubyte(idx_ubyte_file)
def run():
train_images = load_train_images()
train_labels = load_train_labels()
test_images = load_test_images()
test_labels = load_test_labels()
# 查看前十个数据及其标签以读取是否正确
for i in range(10):
print(train_labels[i])
plt.imshow(train_images[i], cmap='gray')
plt.show()
print('done')
if __name__ == '__main__':
run()
数据预处理
导入相关包依赖及预处理函数
import numpy as np
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation
from keras.layers import Conv2D, MaxPooling2D, Flatten
from keras.optimizers import SGD, Adam
from keras.utils import np_utils
from load_data import *
#clean data
def load_dataset():
x_train, y_train = load_train_images(), load_train_labels()
x_test, y_test = load_test_images(), load_test_labels()
number = 60000
x_train, y_train = x_train[0:number], y_train[0:number]
x_train = x_train.reshape(number, 28*28)
x_test = x_test.reshape(x_test.shape[0], 28*28)
x_train, x_test = x_train.astype('float32'), x_test.astype('float32')
y_train, y_test = np_utils.to_categorical(y_train, 10), np_utils.to_categorical(y_test, 10)
x_train, x_test = x_train / 255, x_test / 255
return (x_train, y_train), (x_test, y_test)
到此,我们得到了训练和测试网络所需要的数据。
课程中搭建的网络及训练结果
(x_train, y_train), (x_test, y_test) = load_dataset()
model = Sequential()
#搭建三层网络
model.add(Dense(input_dim=28*28,units=633,activation='sigmoid'))
model.add(Dense(unit=633,activation='sigmoid'))
model.add(Dense(unit=10,activation='softmax'))
model.compile(loss='mse',optimizer=SGD(lr=0.1),metrics=['accuracy'])
model.fit(x_train,y_train,batch_size=100,epochs=20)
result = model.evaluate(x_test,y_test)
print('Test loss:', result[0])
print('Accuracy:', result[1])
李老师这个在课堂上两分钟构建出的三层网络达到的效果如下:
可以看到正确率是0.1135,效果并不太好。
对网络参数调整后的模型
改动地方主要为:
- 激励函数由sigmoid改为relu
- loss function由mse改为categorical_crossentropy
- 增加了Dropout,防止过拟合
改动后构建模型代码
(x_train, y_train), (x_test, y_test) = load_dataset()
model = Sequential()
model.add(Dense(input_dim=28*28,units=700,activation='relu'))
model.add(Dropout(0.2))
model.add(Dense(units=700,activation='relu'))
model.add(Dropout(0.2))
model.add(Dense(units=10,activation='softmax'))
model.compile(loss='categorical_crossentropy',optimizer=SGD(lr=0.1),metrics=['accuracy'])
model.fit(x_train,y_train,batch_size=100,epochs=20,validation_split=0.05)
result = model.evaluate(x_test,y_test)
print('Finish..')
print('Test loss:', result[0])
print('Accuracy:', result[1])
得到的结果
得到了比较好的测试结果。其中,最主要的还是激励函数影响(转自知乎Begin Again的回答)
1. 采用sigmoid等函数,算激活函数时(指数运算),计算量大,反向传播求误差梯度时,求导涉及除法,计算量相对大,而采用Relu激活函数,整个过程的计算量节省很多。
2. 对于深层网络,sigmoid函数反向传播时,很容易就会出现梯度消失的情况(在sigmoid接近饱和区时,变换太缓慢,导数趋于0,这种情况会造成信息丢失,从而无法完成深层网络的训练。
3. Relu会使一部分神经元的输出为0,这样就造成了网络的稀疏性,并且减少了参数的相互依存关系,缓解了过拟合问题的发生。
最后贴上完整代码
#encoding:utf-8
import numpy as np
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation
from keras.layers import Conv2D, MaxPooling2D, Flatten
from keras.optimizers import SGD, Adam
from keras.utils import np_utils
from load_data import *
#clean data
def load_dataset():
x_train, y_train = load_train_images(), load_train_labels()
x_test, y_test = load_test_images(), load_test_labels()
number = 60000
x_train, y_train = x_train[0:number], y_train[0:number]
x_train = x_train.reshape(number, 28*28)
x_test = x_test.reshape(x_test.shape[0], 28*28)
x_train, x_test = x_train.astype('float32'), x_test.astype('float32')
y_train, y_test = np_utils.to_categorical(y_train, 10), np_utils.to_categorical(y_test, 10)
x_train, x_test = x_train / 255, x_test / 255
return (x_train, y_train), (x_test, y_test)
def main():
(x_train, y_train), (x_test, y_test) = load_dataset()
#以下为网络搭建部分,不超过10行,简单易上手
model = Sequential()
model.add(Dense(input_dim=28*28,units=700,activation='relu'))
model.add(Dropout(0.2))
model.add(Dense(units=700,activation='relu'))
model.add(Dropout(0.2))
model.add(Dense(units=10,activation='softmax'))
model.compile(loss='categorical_crossentropy',optimizer=SGD(lr=0.1),metrics=['accuracy'])
model.fit(x_train,y_train,batch_size=100,epochs=20,validation_split=0.05)
result = model.evaluate(x_test,y_test)
#输出结果
print('Finish..')
print('Test loss:', result[0])
print('Accuracy:', result[1])
if __name__ == '__main__':
main()