0%

Keras玩耍迁移学习(VGG16)

某天(今天)下午闲的没事干,想起了当初学了一堆的深度学习的数学概念,还看了Keras的官方文档,既然会写(抄)代码和懂了理念,不如自己折腾点东西玩玩。

说干就干.png,本来是想做个OCR系统在写个GUI界面弄个小软件的,因为种种原因放弃(不喜欢那个数据集),猫狗大战的话数据量太大。挑来挑去选择了迁移学习,使用VGG16的结构去识别MNIST手写文字,怎么感觉有一个好的开始却选择了一个low的实现呢?其实也无所谓,重点是实现过程,有了这次过程实现以后的迁移学习就不是问题了。


如果对本文有疑问或者想找男朋友,可以联系我,点击此处有我联系方式

迁移学习

所谓迁移学习,是在数据样本较少或者没有GPU或者缺钱的情况下,直接使用别人训练好的参数权重而不是自己重新训练,毕竟自己穷的买不起GPU。当然,别人训练好的权重在自己的小数据集里面同样能取得很好的效果,因为前期的权重无非是提取些水平、数值、纹理特征,而这些基本特征与具体的数据集无关。

比如自己写了十层的网络,可以冻结前九层网络的参数使其不参与训练,在最后添加一层为自己的分类层,将数据带入前九层得到输出$y$,最后一层接收$y$为输入,只训练最后一层的权重预测输出,同时也能得到较好的结果。

同样得到下面的结论:

当数据越多,冻结的层数也越少(训练自己的特征),当有相当可观的数据时,应该从头开始自己训练。

下载数据集

MNIST数据集的下载和导入:

  • 下载地址
  • numpy导入:
    1
    2
    3
    4
    5
    6
    7
    path='examples/mnist.npz'
    f = np.load(path)

    x_train1, y_train = f['x_train'], f['y_train']
    x_test1, y_test = f['x_test'], ['y_test']

    f.close()

下载VGG16权重

来这里下载VGG16的权重

提示:

  • vgg16_weights_tf_dim_ordering_tf_kernels.h5 表示全部VGG16权重。
  • vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5 表示不要最后全连接层的权重。

要下载第二个,因为最后一层是自己要重新定义和训练的,所以不要VGG16原始的最后几层的权重。然而top一共是3层,权重参数的大小就从50MB突变到了500MB。可想最后3层得多少参数。

执行环境

  • python 3.5
  • keras 2.2.2
  • 代码执行的话,一段代码copy进一个jupyter lab的格子里执行就行。像下面那样,怎么总感觉在说废话。。。

数据预处理

如果用的是其他数据选择性忽略这里。

不幸的是VGG16能接受的图片大小至少是48,必须有RGB三通道,奈何MNIST数据集的大小是28,还没有RGB通道,只能使用numpy快速处理一下。

将图片填充为48 $\times$ 48和三个通道,三个通道就用自己本身复制三次代替,填充值为0。

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
import keras
import numpy as np

# 读取数据
path='examples/mnist.npz'
f = np.load(path)

x_train1, y_train = f['x_train'], f['y_train']
x_test1, y_test = f['x_test'], f['y_test']

f.close()

# 数据填充
'''
这段代码写的不是很好,pad三个维度,将shape[0]个数组填充,实在是不会只能用for循环
'''
x_train = np.ones((x_train1.shape[0], 48, 48))
x_test = np.ones((x_test1.shape[0], 48, 48))

for i in range (0, x_train.shape[0]):
x_train[i] = np.pad(x_train1[i], ((10,10),(10,10)), 'constant', constant_values = (0,0))

for i in range (0, x_test1.shape[0]):
x_test[i] = np.pad(x_test1[i], ((10,10),(10,10)), 'constant', constant_values = (0,0))

# 将单通道的图像填充为三通道
def gray2rgb(array):
number = array.shape[0]
height = array.shape[1]
width = array.shape[2]

array = np.repeat(array, 3)

array = array.reshape(number, height, width, 3)
return array

x_train = gray2rgb(x_train)
# y_train = gray2rgb(y_train)
x_test = gray2rgb(x_test)
# y_test = gray2rgb(y_test)

实际的训练数据有60000,我试了一下导入全部数据结果CPU和内存差点爆炸,因为我穷买不起GPU,所以只能用小部分数据做着玩玩,反正学的是思路,再说了迁移学习适合小数据,我只是选择了小部分数据。

1
2
3
4
5
6
# 100个训练数据
x_train = x_train[59900:]
y_train = y_train[59900:]
# 10个测试数据
x_test = x_test[9990:]
y_test = y_test[9990:]

VGG16迁移

1
2
3
4
5
6
7
8
9
10
11
height, width = x_train.shape[1], x_train.shape[2]
channels = x_train.shape[-1]

path='weights/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5'

from keras.applications import vgg16
base_model = vgg16.VGG16(weights = path,
include_top = False, ## 是否保留顶层的3个全连接网络
input_shape = (height, width, channels), ## 输入层的尺寸
)
base_model.summary()

我们发现迁移过来的VGG16是长这样的:最后一层的输出是(1, 1, 512)。那么添加的下一层的全连接层的输入维度就是512。

加入自定义层

将原始图片导入VGG16得到现在开始加入自己自定义的层,我添加了一个100个节点的全连接层和10个节点的全连接层。记得第一层的输入维度是512哦~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 输出VGG16的计算特征
train_feature = base_model.predict(x_train, batch_size=32)
print(train_feature.shape)

x_data = train_feature.copy()

from keras.models import Sequential
from keras.layers import Dense, Activation

model = Sequential()
# 添加第一层
model.add(Dense(100, activation='relu', input_dim=512))
# 添加第二层
model.add(Dense(10, activation='softmax'))
model.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])

模型训练

训练自己的自定义层,相信反向传播,相信优化器。

1
2
3
4
5
6
7
x_data = x_data.reshape(100, 512)

y_label = y_train.reshape(100, 1)
# Sequence模型接受类别的数据类型为one-hot或者词向量,这里只能使用one-hot。
y_one_hot_labels = keras.utils.to_categorical(y_label, num_classes=10)
# 开始训练
model.fit(x_data, y_one_hot_labels, epochs=10, batch_size=32)

训练误差

测试

训练完毕后就是在自己的测试数据上跑一跑,evaluate以下,同样用VGG16计算测试集的输出,将输出带入自定义网络进行预测,和标准数据对比。

1
2
3
4
5
6
7
8
test_feature = base_model.predict(x_test, batch_size=32)

print(test_feature.shape)

x_test_feature = test_feature.reshape(10, 512)
y_test_label = keras.utils.to_categorical(y_test, num_classes=10)

score = model.evaluate(x_test_feature, y_test_label, batch_size=32)

结语

实际上socre的得分不是很好,测试集上的准确率只有60%。这说明了什么呢?训练集的误差小,测试集的误差很大,归结下原因:

  • 对数据集的筛选,导致了训练集和测试集的来源不同,也就是测试集里面的数据网络没见过。
  • 个人认为不是网络的原因,因为训练集的误差很小。就算是过拟合也是由样本太少导致的。
感谢上学期间打赏我的朋友们。赛博乞讨:我,秦始皇,打钱。

欢迎订阅我的文章