记一次猫狗分类线上比赛

前言

最近学了Google的深度学习库,TensorFlow,这个库是真的强大,当然还有更高的封装,keras之类的,但是既然一直用着TensorFlow,就想去找个好玩的东西试试,然后看到lintcode上有一个猫狗的分类比赛,就去试试吧。做了一个周末加几天,大概做出来了,因为做深度学习需要大量的计算资源,电脑是真的跑不起来(CPU版TensorFlow),这里是我的代码实现VGG16-tranfer,所以我用了Google的免费GPU资源进行计算Colaboratory,Colaboratory 是一个 Google 研究项目,旨在帮助传播机器学习培训和研究成果。它是一个 Jupyter 笔记本环境,不需要进行任何设置就可以使用,并且完全在云端运行。然后数据集和模型通过和Google Drive进行数据交互得到,别的不说,确实用GPU算的很快,无论是训练的时候还是验证的时候。

Colaboratory分配的计算资源

据说可以免费的使用Tesla k80 GPU加速,除了一些简单的debug在本地测试一下,我都想全部丢在上面跑了。。。

初步思路

之前只做过mnist数据集的训练(神经网络级别的hello world),mnist数据集的宽高是28x28x1,1代表了这个图是一通道的,所以计算量很小,我们要用什么网络实现呢,我一开始选的LetNet,并且重头开始训练(现在看来真是天真的不行)

LetNet网络图示

因为数据集是一个有20000张带label的图片,一旦输入数据处理的不对,对内存和显存都有着很大的压力,直接使用PIL把图像转成矩阵或者使用OpenCV(但是我觉得前期处理可以)转化应该是不行的了,然后接触到了TFRecord这种TensorFlow特有的数据集格式,我会另外写一篇来介绍Tfrecord,第一次转化的时候就被坑死了。。

一开始考虑到训练的难度,没有打算直接输入3通道图片进去训练,因为有着之前一个写好的一个LetNet,在单矩阵上做卷积,一开始的图片处理打算是全部压缩成灰度图,然后输入CNN里面进行训练,之后进行训练的时候(本机CPU训练),差点弄死我了,内存算上压缩的高达60GB+(Liunx会有swap区),我已经听到我的SSD在尖叫了。。。

TensorFlow压缩灰度图操作

1
2
3
4
5
# img_data读取的图片数据
# plt是import matplotlib.pyplot as plt
image_data = tf.image.rgb_to_grayscale(img_data)
plt.imshow(image_data.eval()[:, :, 0], cmap='gray')
plt.show()

初次实验改良搭的图,TensorBoard显示

在图上就可以看出,FC层非常硕大,这是因为全连接的神经网络参数众多,训练起来非常庞大,卷积神经网络CNN诞生的其中一个目标就是缩减参数,但是在一开始的时候我只搭了大概3层卷积层(后来使用VGG16甚至有着13层卷积层),然后在FC层诞生了一个几十万参数的矩阵进行乘法的操作。。。然后内存就爆了(那时候还在用CPU),通过性能可视化可以看到内存变化,tf.RunMetadata(),然后又加了两层卷积层,然后训练还是很难,然后就打算重构了。

这个代码可以在GithubLetNet_error找到

Tfrecord首坑

在打包train数据集的时候,写错了几行代码,导致跑了一个晚自习,后来发现一台卡死的电脑和一个高达80G的tfrecord文件
(´-﹏-`;)

本来正常操作应该是这样的(伪代码)

1
2
3
4
5
6
7
def _bytes_feature(value):
return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))

for name in names:
image_raw_data = tf.gfile.FastGFile('../train/'+name, 'rb').read()
example = tf.train.Example(features=tf.train.Features(feature={
'image': _bytes_feature(image_raw_data),}))

但是我读取之后使用了jpeg解码。。。把解码后的数据读进去了,然后几何倍数般的文件大小增长。

1
2
img_data = tf.image.decode_jpeg(image_raw_data)
img_data = tf.image.convert_image_dtype(img_data, dtype=tf.float32)

VGG16 迁移学习

最后一直在思考如何使用高正确率的网络(之前的网络能跑是能跑,但是正确率有点低,训练集都这样了,拿到测试集不完蛋了),后来想到了之前的CTPN提取feature map时候使用的是VGG16,然后就去了解了VGG16,顺便了解了迁移学习,经过一段时间的了解,明白了迁移学习在TensorFlow里的主要实现机制,在TensorFlow里进行梯度下降之类的优化操作(反向传播BP)是使用优化器(optimizer),而这个优化器优化的都是TensorFlow里的变量tf.Variable,我们把需要优化的参数使用变量,固定的参数使用常量之类的就好了,这样就很简单了。

经过很多层卷积层和池化层之后,参数到FC分类层的时候确实变得很少了,而且迁移学习仅仅是训练后面几个层而已,一开始是仅仅训练最后的FC层,在数据集上获得了大概70的正确率。

大概图(VGG主体)(tensorboard):

理论图:

之后为了提高正确率,采用了数据提升的手段,这样我们的模型将看不到任何两张完全相同的图片,这有利于我们抑制过拟合,使得模型的泛化能力更好。(来自keras文档),在这里我使用了图片翻转、调整图片色彩(亮度、对比度之类的)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 随机调整图像的色彩
def distort_color(image, color_ordering=0):
if color_ordering == 0:
image = tf.image.random_brightness(image, max_delta=32. / 255.)
image = tf.image.random_saturation(image, lower=0.5, upper=1.5)
image = tf.image.random_hue(image, max_delta=0.2)
image = tf.image.random_contrast(image, lower=0.5, upper=1.5)
else:
image = tf.image.random_saturation(image, lower=0.5, upper=1.5)
image = tf.image.random_brightness(image, max_delta=32. / 255.)
image = tf.image.random_contrast(image, lower=0.5, upper=1.5)
image = tf.image.random_hue(image, max_delta=0.2)
return tf.clip_by_value(image, 0.0, 1.0)
image = tf.image.random_flip_left_right(image)
image = distort_color(image, np.random.randint(2))

L2正则化、dropout这些实现就不赘述了。

验证test

我们训练网络之后,选择一个正确率较高的模型固化成ckpt格式,然后在转化成pb格式的模型以供使用,pb模型提供输入就可以得到输出了,比ckpt好用,在验证的时候也有诸多问题,比如机器配置不够。。。到最后测试集都是分开两次进行验证的,一次性读入进行验证对内存显存的占用的太可怕了,到了最后也只能转为TFrecord文件来读取,而且还分了两次,因为一次性无法全部验证,就在遍历TFrecord文件上跳了坑。。。

TFrecord验证集神坑

在使用测试集的tfrecord的时候,读取数据使用了队列+tf.train.batch,就跟训练的时候一样。我想如果我一次batch读取50个,遍历一次应该用100次就能遍历完了(测试集5000张图),但是。。这个读取方法会有重复数据读取,而不会抛出tf.errors.OutOfRangeError错误,在后来使用pandas对结果排序的时候,发现在100次里面是随机读取的数据,读取的数据重复了3次,只能去找不重复的遍历方法,后来在一篇文章发现了将TFrecord的数据转成迭代器使用的方法数据集的使用方法,主要实现:

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
def parse(record):
features = tf.parse_single_example(
record,
features={
'image': tf.FixedLenFeature([], tf.string),
'label': tf.FixedLenFeature([], tf.int64),
'height': tf.FixedLenFeature([], tf.int64),
'width': tf.FixedLenFeature([], tf.int64),
'channels': tf.FixedLenFeature([], tf.int64),
}
)
image, label = features['image'], features['label']
height = tf.cast(features['height'], tf.int32)
width = tf.cast(features['width'], tf.int32)
# 图片解码
decoded_image = tf.image.decode_jpeg(image)
# 图片转换类型
decoded_image = tf.image.convert_image_dtype(decoded_image, dtype=tf.float32)

image = tf.reshape(decoded_image, [height, width, 3])
image = tf.image.resize_images(image, [224, 224], method=np.random.randint(0, 3))
return image, label

dataset = tf.data.TFRecordDataset(file)

datatset = dataset.map(parse) # parse函数主要是来处理数据集

test_dataset = datatset.batch(batch_size=50)

test_iterator = test_dataset.make_initializable_iterator()
# 读取数据,可用于进一步计算
test_image_batch, test_label_batch = test_iterator.get_next()

with tf.Session() as sess:
sess.run(test_iterator.initializer)
image_batch, label_batch = sess.run([test_image_batch, test_label_batch])

这样就可以遍历整个TFrecord了,这个东西真的坑死我了。

后记

这次比赛是真的认识了很多东西,主要都是TensorFlow的实现,然后就是完全熟练的使用Colaboratory,这个东西确实对于没有计算资源的学习者有很大的帮助啊。深深认识到没有计算资源基本玩不了深度学习。。。。

文章目录
  1. 1. 前言
  2. 2. 初步思路
    1. 2.1. Tfrecord首坑
  3. 3. VGG16 迁移学习
  4. 4. 验证test
    1. 4.1. TFrecord验证集神坑
  5. 5. 后记
|