目录
0. 写作目的
好记性不如烂笔头。
1. 前言
对于较小的数据处理时,我们可以完全读入内存,但是针对较大的数据集,如果一次性读入内存,将会出现一下两点问题:
1) 内存是否够用。
一些较大的数据集,如ImageNet数据集,一次性完全读入内存,需要较大的内存,即使内存足够,我们也不会使用该方法,原因如下第二点。
2) 程序的交互性差
如果我们将较大的数据集一次性读入时,首先在程序运行之前,我们第一步就要读入数据,如果在读入数据后,后面的代码出现了bug,修改bug后,仍需要较大的时间读入,浪费时间。(虽然对于Jupyter notebook或类似的环境修改一下代码,可以避免重新读入,但是读入的过程的仍在浪费时间,而且即使数据全都读入内存,但在一段时间内我们使用的数据仍只是一部分,因此对于内存也造成了一种资源浪费。)
因此,一种较好的方式就是,即用即读。在深度框架中,不同的深度学习框架都有自己的序列化格式,如caffe的LMDB文件,Tensorflow的TFrecord文件等。
在之前使用Caffe的LMDB文件时,我就有一种想法,以图像分类为例,可不可以先保存图像的路径,以及对应的标签,然后在使用时,进行即用即读。本文首先进行了该种方法的尝试。
2. 即用即读的训练方式
2.1 数据的准备
首先下载MNIST的数据,如果下载的不是图像数据集,是train-images-idx3-ubyte.gz,在ubuntu下采用命令:gunzip train-images-idx3-ubyte.gz进行解压,然后通过以下代码[1](基于PyTorch的)来生成图像数据集。以下代码需要在数据目录下运行,运行结果将在数据集目录下,产生两个图像文件夹train和test,同时生成两个txt文件——train.txt 和test.txt,其中图像文件为所有图像,txt的内容是:图像的绝对路径以及对应的标签。
import os
from skimage import io
import torchvision.datasets.mnist as mnist
root=os.getcwd()
train_set = (
mnist.read_image_file(os.path.join(root, 'train-images-idx3-ubyte')),
mnist.read_label_file(os.path.join(root, 'train-labels-idx1-ubyte'))
)
test_set = (
mnist.read_image_file(os.path.join(root, 't10k-images-idx3-ubyte')),
mnist.read_label_file(os.path.join(root, 't10k-labels-idx1-ubyte'))
)
print("training set :",train_set[0].size())
print("test set :",test_set[0].size())
def convert_to_img(train=True):
if(train):
f=open(root+'/train.txt','w')
data_path=root+'/train/'
if(not os.path.exists(data_path)):
os.makedirs(data_path)
for i, (img,label) in enumerate(zip(train_set[0],train_set[1])):
img_path=data_path+str(i)+'.jpg'
io.imsave(img_path,img.numpy())
label = label.numpy()
f.write(img_path+' '+str(label)+'\n')
break
f.close()
else:
f = open(root + '/test.txt', 'w')
data_path = root + '/test/'
if (not os.path.exists(data_path)):
os.makedirs(data_path)
for i, (img,label) in enumerate(zip(test_set[0],test_set[1])):
img_path = data_path+ str(i) + '.jpg'
io.imsave(img_path, img.numpy())
label = label.numpy()
f.write(img_path + ' ' + str(label) + '\n')
f.close()
convert_to_img(True)
convert_to_img(False)
2.2 读取数据的类
此部分是在Reference的基础上进一步改进得到的,且发现了shuffle_batch的有趣现象,具体参考我的博客:。这里之介绍修改后的代码。(下面的代码所需要的数据集格式为:train图像文件夹下包含各子类文件夹,各子类文件夹下包含各自的图像。此处与上面代码得到的train文件夹不一样,读者可自行修改代码)
class LoadDatas(object):
'''
将所有的图像与label保存为文件,然后从文件中读入
'''
def __encode__(self, imageName):
'''
:param imageName: the name of category in real world
:return: the encode of label
'''
for ii in range(len(self.classes)):
if imageName == self.classes[ii]:
return ii
raise("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!label error!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
def __init__(self, dataFilePath, targetHeight, targetWidth, classNumber, flag):
'''
dataFilePath: is the data path. its like this: train/ 0 class File, 1 calss File
targetHeight: is the height of image you want
targetWidth: is the width of image you want
classNumber: is the number of calss
flag: is a sign to show 'train data' or 'test data'
self.data is a list of [image_file_path, class_label]
'''
if (flag == "train") or (flag == 'train'):
self.flag = 'train'
elif(flag == 'test' or (flag == "test")):
self.flag = 'test'
else:
raise("!!!!!!!!!!!!!!!!Data Type is Error!!!!!!!!!!!!!")
self.classes = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
self.data = []
self.targetHeight = targetHeight
self.targetWidth = targetWidth
self.classNumber = classNumber
self.length = 0
self.counter = 0
for className in os.listdir(dataFilePath):
for imageName in os.listdir( dataFilePath + '/' + str(className)):
tempImageName = dataFilePath + '/' + str(className) + '/' + imageName
self.data.append([tempImageName, self.__encode__(str(className))])
self.length += 1
if self.flag == 'train':
print("read Train Data is Done! train Data number is: {}".format(self.length))
elif self.flag == 'test':
print("read Test Data is Done! test Data number is: {}".format(self.length))
self.data = np.asarray(a=self.data)
def next_batch(self, batch_size=16):
'''
:param batch_size:
:return: images is a np.ndarray with shape = [None, targetHeight, targetWidth, 1/3]
labes: is a np.ndarray with shape = [None, ]
'''
if self.flag == 'train':
#np.random.shuffle(dataset)
images = []
labels = []
print(self.counter)
np.random.shuffle(self.data)
tempData = self.data[:batch_size]
for item in tempData:
img_raw = Image.open(item[0])
img_raw = img_raw.convert("RGB")
img_raw = img_raw.resize((self.targetHeight, self.targetWidth))
img_rawArr = np.array(img_raw)
img_rawArr = img_rawArr.astype(dtype=np.float32) * (1. / 255)
images.append(img_rawArr)
labels.append(self.__oneHotLabel__(item[1]))
return images, labels
elif self.flag == 'test':
images = []
labels = []
if( self.counter + batch_size > self.length ):
tempData = self.data[self.counter : self.length]
else:
tempData = self.data[self.counter: (self.counter + batch_size)]
for item in tempData:
img_raw = Image.open(item[0])
img_raw = img_raw.convert("RGB")
img_raw = img_raw.resize((self.targetHeight, self.targetWidth), Image.ANTIALIAS)
img_rawArr = np.array(img_raw)
img_rawArr = img_rawArr.astype(dtype=np.float32) * (1. / 255)
images.append(img_rawArr)
labels.append(self.__oneHotLabel__(item[1]))
return images, labels
def __oneHotLabel__(self, label):
label_oht = [0 for ii in range(self.classNumber)]
label_oht[int(label)] = 1
return label_oht
3. 进行训练(TensorFlow)
3.1 训练的网络
此处的myConv2d, myMaxPooling2D, myFc函数均为基于TensorFlow底层修改得到的,新手可以使用tf.contirb.slim来代替。注意:虽然MNIST数据集比较简单,但是网络对于数据集影响还是很大的,博主刚开始写的一个网络收敛速度较慢(网络有点儿深)。
def defNet(input_tensor, reuse=False):
conv1 = myConv2d(input_tensor, conv_size=3, output_channel=32, name='conv1', padding='SAME', act=tf.nn.relu,
reuse=reuse)
conv1_p1 = myMaxPooling2D(conv1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='VALID', name='conv1_p1',
reuse=reuse)
conv2 = myConv2d(conv1_p1, conv_size=3, output_channel=64, name='conv2', padding='SAME', act=tf.nn.relu,
reuse=reuse)
conv2_p2 = myMaxPooling2D(conv2, ksize=[1, 2, 2, 1], strides=[1,2,2,1], padding="VALID", name='conv2_p2', reuse=reuse)
conv3 = myConv2d(conv2_p2, conv_size=3, output_channel=128, name='conv3', padding='SAME', act=tf.nn.relu, reuse=reuse)
conv3_p3 = myMaxPooling2D(conv3, ksize=[1,2,2,1], strides=[1,2,2,1], padding='VALID', name='conv3_p3', reuse=reuse)
flatten_conv = tf.contrib.layers.flatten(conv3_p3)
print_Layer(flatten_conv)
fc4 = myFc(flatten_conv, output_channel=128, name='fc4')
logits = myFc(fc4, output_channel=10, name='logits', act=None)
return logits
3.2 训练的结果
1) 训练时,IDE端结果的输出
此处不必在意"0”表示什么意思。训练参数为10epoch,每个epoch3000次迭代,优化算法为:AdamOptimizer(0.01)。
2) Tensorboard 端的输出
查看网络结构:
查看训练时的loss以及Acc:
(此处对于loss感觉存在一些问题,训练集和测试集的Acc都很高,但是loss值不是很低,与PyTorch MNIST(epoch3: iter457: loss 0.032229 Acc: 0.990085)训练的结果相比高了不少,后续对此再进行探索)
问题为:在计算loss值时,参数选择有错误,tf.losses.softmax_cross_entropy函数输入为logist和y_true。
后续给出如何使用TFRecord格式来进行训练网络。
There may be some mistakes in this blog. So, any suggestions and comments are welcome!
[Reference]
[1] https://www.cnblogs.com/denny402/p/7520063.html
[2] https://blog.csdn.net/u014484783/article/details/79621811