前言
之前使用tensorflow
和keras
的时候,都各自有一套数据读取方法,但是遇到一个问题就是,在训练的时候,GPU
的利用率忽高忽低,极大可能是由于训练过程中读取每个batch
数据造成的,所以又看了tensorflow
官方的加载数据方法。主要是利用了tf.data.Dataset
这里面的一系列操作。
国际惯例,参考博客:
tensorflow官方加载数据集方法
官方文档对应的代码images.ipynb
官方文档对应的代码tf_records.ipynb
Tensorflow中创建自己的TFRecord格式数据集
TensorFlow全新的数据读取方式:Dataset API入门教程
以tf.data优化训练数据 Google开发者大会2018
Tensorflow数据预处理之tf.data.TFRecordDataset—TFRecords详解\TFRecords图像预处理
buffer_size的含义——Dataset.map , Dataset.prefetch and Dataset.shuffle
tensorflow 数据读取总结—(直接供给数据(feeding) 从文件中以管线形式读取数据 预加载数据)
复习
先复习一下之前博客中tensorflow
和keras
加载数据的方法
之前采用的tensorflow
加载数据方法
详细查看之前的这篇博客:
IMG_HEIGHT = 28 # 高
IMG_WIDTH = 28 # 宽
CHANNELS = 3 # 通道数
def read_images(dataset_path, batch_size):imagepaths, labels = list(), list()data = open(dataset_path, 'r').read().splitlines()for d in data:imagepaths.append(d.split(' ')[0])labels.append(int(d.split(' ')[1])) # 转换为张量imagepaths = tf.convert_to_tensor(imagepaths, dtype=tf.string)labels = tf.convert_to_tensor(labels, dtype=tf.int32)# 建立TF队列,打乱数据image, label = tf.train.slice_input_producer([imagepaths, labels],shuffle=True)# 读取数据image = tf.read_file(image)image = tf.image.decode_jpeg(image, channels=CHANNELS)# 将图像resize成规定大小image = tf.image.resize_images(image, [IMG_HEIGHT, IMG_WIDTH])# 手动归一化image = image * 1.0/127.5 - 1.0# 创建batchinputX, inputY = tf.train.batch([image, label], batch_size=batch_size,capacity=batch_size * 8,num_threads=4)return inputX, inputY
主要使用tf.train
中的一系列操作
keras
中自带的数据加载方法
直接看官方文档即可,我比较喜欢用下面这一系列方法从文件夹中读取数据:
train_datagen = ImageDataGenerator(rescale=1./255,shear_range=0.2,zoom_range=0.2,horizontal_flip=True)
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory('data/train',target_size=(150, 150),batch_size=32,class_mode='binary')validation_generator = test_datagen.flow_from_directory('data/validation',target_size=(150, 150),batch_size=32,class_mode='binary')
只要使用flow_from_directory
自动从文件夹中读取数据。
利用tf.data
直接读取数据
数据集准备
下列所有实验的数据都基于tensorflow
提供的flower_photos
数据集,才220M左右,下载地址戳这里。
我也上传到网盘了:
链接:https://pan.baidu.com/s/13esPlx-fkKlXaegJNROPyw
提取码:nv64
解压后,得到五个文件夹,每个文件夹一类花朵。
代码
首先引入必要的包:
import os
import tensorflow as tf
import pathlib
import random
import numpy as np
读取图片数据
-
首先找到所有图片和对应的路径:
data_root = pathlib.Path('./dataset/flower_photos/') all_image_paths = list(data_root.glob('*/*')) all_image_paths = [str(path) for path in all_image_paths] random.shuffle(all_image_paths) image_count = len(all_image_paths) print('total image num:',image_count)#total image num: 3670
-
数据预处理:
class Process_img:def __init__(self,img_size):self.img_size = img_sizedef load_and_preprocess_image(self,img_path):image = tf.read_file(img_path)image = tf.image.decode_jpeg(image,channels=3)#进行各种图像处理:裁剪、缩放、旋转、亮度调整等image=tf.image.resize_images(image,self.img_size) #此处严格按照API文档调用,tensorflow 版本不同使用的方法不同image /= 255.0return image
-
数据预处理必须使用如下流程,先
from_tensor_slice
转换成Dataset
格式,然后使用map
将数据丢到预处理函数中:path_ds = tf.data.Dataset.from_tensor_slices(all_image_paths)a=Process_img(img_size=[192,192]) image_ds = path_ds.map(a.load_and_preprocess_image,num_parallel_calls=tf.contrib.data.AUTOTUNE)
这里有一个小技巧就是:本人不清楚
map
函数如何接受预处理函数所需传递的方法,有一个方法就是将预处理参数,比如image_size
也丢到Dataset
里面去,但是有点麻烦,这样做from_tensor_slice
里面的参数有点长,还不如初始化一个对象,存储预处理所需参数了,清晰易懂。
读取标签数据
-
先获取标签,因为路径中文件名的上级文件夹就是标签,所以可以:
label_names = sorted(item.name for item in data_root.glob('*/') if item.is_dir()) print('label names:',label_names) #label names: ['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']
-
再将标签转换为
int
型索引:#将标签转换为索引值 label_to_index = dict((name,index) for index,name in enumerate(label_names)) print('label corresponding index:',label_to_index)
-
然后获取到所有图像对应的标签:
all_image_labels = [label_to_index[pathlib.Path(path).parent.name] for path in all_image_paths]
验证一下看看:
print('first 10 sample path and labels:') for i in range(0,10):print('{0}\t {1}'.format(all_image_paths[i],all_image_labels[i])) ''' first 10 sample path and labels: dataset\flower_photos\roses\5799616059_0ffda02e54.jpg 2 dataset\flower_photos\roses\22385375599_1faf334f5d_n.jpg 2 dataset\flower_photos\sunflowers\6627521877_6e43fb3c49_m.jpg 3 dataset\flower_photos\dandelion\3465599902_14729e2b1b_n.jpg 1 dataset\flower_photos\roses\4267024012_295e7141a3_n.jpg 2 dataset\flower_photos\dandelion\23414449869_ee849a80d4.jpg 1 dataset\flower_photos\tulips\4418204816_018375acd0_m.jpg 4 dataset\flower_photos\dandelion\9517326597_5d116a0166.jpg 1 dataset\flower_photos\dandelion\7197581386_8a51f1bb12_n.jpg 1 dataset\flower_photos\dandelion\425800274_27dba84fac_n.jpg 1 '''
-
同样将标签也转换成
Dataset
格式:label_ds = tf.data.Dataset.from_tensor_slices(tf.cast(all_image_labels,tf.int64))
组合数据
因为后续需要打乱和分批,所以需要将图像与标签对应打包对应好,后面一起变换
img_label_ds = tf.data.Dataset.zip((image_ds,label_ds))
打乱、重复数据、分批,详细解释可以查看这里,我们只看如何使用:
batch_size = 32
ds = ds.cache()
ds=img_label_ds.shuffle(buffer_size=image_count)
ds=ds.repeat()
ds=ds.batch(batch_size)
ds = ds.prefetch(buffer_size=tf.contrib.data.AUTOTUNE)
【注】不加cache
也行,但是官方文档说加了能提高数据喂进内存的性能。prefetch
在官方文档中说的是在训练时将数据喂到batch
里面。
训练
使用tf.keras
里面的mobileNetV2
模型微调
预处理
因为mobileNetV2
要求输入数据范围在(−1,−1)(-1,-1)(−1,−1),所以我们还要做一次预处理:
# 把数据由(0,1)转换为(-1,1)
def change_range(image,label):return 2*image-1,label
keras_ds=ds.map(change_range)
【注】可以发现,数据变成Dataset
格式以后,各种预处理都得用map
映射到处理函数。
载入模型并训练
去掉mobileNet
的尾巴,是否使用imagenet
的权重与训练,取决于weights
是否None
mobile_net = tf.keras.applications.MobileNetV2(input_shape=(192,192,3),include_top=False,weights=None)
接个全连接做分类:
model = tf.keras.Sequential([mobile_net,tf.keras.layers.GlobalAveragePooling2D(),tf.keras.layers.Dense(5,activation='softmax')
])
看看网络结构
model.summary()
'''
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
mobilenetv2_1.00_192 (Model) (None, 6, 6, 1280) 2257984
_________________________________________________________________
global_average_pooling2d (Gl (None, 1280) 0
_________________________________________________________________
dense (Dense) (None, 5) 6405
=================================================================
Total params: 2,264,389
Trainable params: 2,230,277
Non-trainable params: 34,112
_________________________________________________________________
'''
编译模型:
model.compile(optimizer=tf.train.AdamOptimizer(),loss=tf.keras.losses.sparse_categorical_crossentropy,metrics=["accuracy"])
开始训练
steps_per_epoch = int(np.ceil(len(all_image_paths)/batch_size))
model.fit(keras_ds,epochs=1000,steps_per_epoch=steps_per_epoch)
'''
Epoch 1/1000
115/115 [==============================] - 172s 1s/step - loss: 1.3721 - acc: 0.4427
Epoch 2/1000
115/115 [==============================] - 155s 1s/step - loss: 1.1061 - acc: 0.5582
Epoch 3/1000
115/115 [==============================] - 150s 1s/step - loss: 0.9562 - acc: 0.6190
Epoch 4/1000
115/115 [==============================] - 148s 1s/step - loss: 0.8750 - acc: 0.6617
Epoch 5/1000
115/115 [==============================] - 223s 2s/step - loss: 0.8136 - acc: 0.6927
Epoch 6/1000
115/115 [==============================] - 148s 1s/step - loss: 0.7368 - acc: 0.7201
Epoch 7/1000
115/115 [==============================] - 148s 1s/step - loss: 0.6718 - acc: 0.7582
Epoch 8/1000
115/115 [==============================] - 148s 1s/step - loss: 0.6206 - acc: 0.7682
Epoch 9/1000
115/115 [==============================] - 148s 1s/step - loss: 0.5699 - acc: 0.7905
Epoch 10/1000
115/115 [==============================] - 147s 1s/step - loss: 0.5368 - acc: 0.8041
Epoch 11/1000
115/115 [==============================] - 147s 1s/step - loss: 0.4938 - acc: 0.8190
Epoch 12/1000
115/115 [==============================] - 148s 1s/step - loss: 0.4456 - acc: 0.8372
Epoch 13/1000
115/115 [==============================] - 147s 1s/step - loss: 0.4257 - acc: 0.8429
Epoch 14/1000
115/115 [==============================] - 149s 1s/step - loss: 0.3856 - acc: 0.8573
.......
'''
利用tf.data
转成tfrecord
再载入
比较喜欢的方法就是跟caffe
一样,先做数据集,训练的时候读取,tensorflow
中建议的存储格式就是tfrecord
制作数据集
导入对应包:
import os
import tensorflow as tf
import pathlib
import random
import numpy as np
获取图像数据路径:
data_root = pathlib.Path('./dataset/flower_photos/')
all_image_paths = list(data_root.glob('*/*'))
all_image_paths = [str(path) for path in all_image_paths]
random.shuffle(all_image_paths)
image_count = len(all_image_paths)
print('total image num:',image_count)
获取图像对应标签:
label_names = sorted(item.name for item in data_root.glob('*/') if item.is_dir())
label_to_index = dict((name,index) for index,name in enumerate(label_names))
all_image_labels = [label_to_index[pathlib.Path(path).parent.name] for path in all_image_paths]
把图像与标签打包:
image_labels = zip(all_image_paths,all_image_labels)
按照tensorflow
的方法将图像和标签做成tfrecord
格式数据集:
def _bytes_feature(value):"""Returns a bytes_list from a string / byte."""return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))def _float_feature(value):"""Returns a float_list from a float / double."""return tf.train.Feature(float_list=tf.train.FloatList(value=[value]))def _int64_feature(value):"""Returns an int64_list from a bool / enum / int / uint."""return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))
def image_example(image_string, label):feature = {'label': _int64_feature(label),'image_raw': _bytes_feature(image_string)}return tf.train.Example(features=tf.train.Features(feature=feature))
with tf.python_io.TFRecordWriter('images.tfrecords') as writer:for filename, label in image_labels:image_string = open(filename, 'rb').read()tf_example = image_example(image_string, label)writer.write(tf_example.SerializeToString())
上面需要注意的就是image_example
里面的feature
里面存的内容,你可以自己定义一些其它的,比如图像宽高之类的,后续读取的时候可以通过键值获取对应值,这里只存了必须的图像字节和标签。其余的函数干啥的,别问,用之就对了。
读取数据
读取必要包:
import os
import tensorflow as tf
import pathlib
import random
import numpy as np
读取tfrecord
对应的数据:
raw_image_dataset = tf.data.TFRecordDataset('images.tfrecords')# Create a dictionary describing the features.
image_feature_description = {'label': tf.FixedLenFeature([], tf.int64),'image_raw': tf.FixedLenFeature([], tf.string),
}def _parse_image_function(example_proto):# Parse the input tf.Example proto using the dictionary above.example = tf.parse_single_example(example_proto, image_feature_description)image = tf.image.resize_images(tf.image.decode_jpeg(example['image_raw'],channels=3),[192,192])image/=255.0label = example['label']return image,labelparsed_image_dataset = raw_image_dataset.map(_parse_image_function)
parsed_image_dataset
流程基本就是使用tf.data.TFRecordDataset
载入tfrecord
数据,然后取对应存储的信息,如图像与标签。还可以来一波预处理,当然还是利用map
将数据丢到预处理函数中。
接着就是打乱、分批、重复
train_data = parsed_image_dataset.shuffle(buffer_size=100)
train_data = train_data.batch(8)
train_data = train_data.repeat()
train_data = train_data.prefetch(buffer_size=tf.contrib.data.AUTOTUNE)
print(train_data)
关于buffer_size
的说明,戳这里
训练
跟前面没啥区别:
mobile_net = tf.keras.applications.MobileNetV2(input_shape=(192,192,3),include_top=False,weights=None)model = tf.keras.Sequential([mobile_net,tf.keras.layers.GlobalAveragePooling2D(),tf.keras.layers.Dense(5,activation='softmax')
])model.compile(optimizer=tf.train.AdamOptimizer(),loss=tf.keras.losses.sparse_categorical_crossentropy,metrics=["accuracy"])model.fit(train_data,epochs=1000,steps_per_epoch=1000)
'''
Epoch 1/1000
1000/1000 [==============================] - 356s 356ms/step - loss: 1.2820 - acc: 0.4885
Epoch 2/1000
1000/1000 [==============================] - 358s 358ms/step - loss: 1.0426 - acc: 0.5928
Epoch 3/1000
1000/1000 [==============================] - 355s 355ms/step - loss: 0.9630 - acc: 0.6330
Epoch 4/1000
1000/1000 [==============================] - 354s 354ms/step - loss: 0.9110 - acc: 0.6524
Epoch 5/1000
1000/1000 [==============================] - 354s 354ms/step - loss: 0.8589 - acc: 0.6771
Epoch 6/1000
1000/1000 [==============================] - 355s 355ms/step - loss: 0.7635 - acc: 0.7152
Epoch 7/1000
1000/1000 [==============================] - 355s 355ms/step - loss: 0.6983 - acc: 0.7406
Epoch 8/1000
1000/1000 [==============================] - 354s 354ms/step - loss: 0.6482 - acc: 0.7632
Epoch 9/1000
1000/1000 [==============================] - 354s 354ms/step - loss: 0.5834 - acc: 0.7769
Epoch 10/1000
1000/1000 [==============================] - 357s 357ms/step - loss: 0.5439 - acc: 0.7995
'''
训练结果和上面直接从文件夹读取的结果差不多,说明流程没问题。
有个坑
读取tfrecord
数据集的这句话:
raw_image_dataset = tf.data.TFRecordDataset('images.tfrecords')
丫的竟然不核对这个tfrecords
文件是否存在,或者是否为空数据,不信你随便改个名,这句话还能运行,真的是醉了。程序model.fit
会直接进入死机状态,你也不知道它是在读数据,还是崩了。
所以我们在进行下列一顿操作以后:
raw_image_dataset = tf.data.TFRecordDataset('images11.tfrecords')# Create a dictionary describing the features.
image_feature_description = {'label': tf.FixedLenFeature([], tf.int64),'image_raw': tf.FixedLenFeature([], tf.string),
}def _parse_image_function(example_proto):# Parse the input tf.Example proto using the dictionary above.example = tf.parse_single_example(example_proto, image_feature_description)image = tf.image.resize_images(tf.image.decode_jpeg(example['image_raw'],channels=3),[192,192])image/=255.0label = example['label']return image,labelparsed_image_dataset = raw_image_dataset.map(_parse_image_function)
parsed_image_datasettrain_data = parsed_image_dataset.shuffle(buffer_size=100)
train_data = train_data.batch(8)
train_data = train_data.repeat()
train_data = train_data.prefetch(buffer_size=tf.contrib.data.AUTOTUNE)
print(train_data)
必须得验证一下这个train_data
里面是不是有数据,图片与标签是否对应。
验证方法,是迭代输出
iterator = train_data.make_one_shot_iterator()
one_element = iterator.get_next()
with tf.Session() as sess:try:while True:a=sess.run(one_element)print(a[0].shape)#(8, 192, 192, 3)print(a[1].shape)#(8,)breakexcept tf.errors.OutOfRangeError:print('end!')
我们把数据保存在a
里面,同时从a
的shape
可以看出来,存了图片和标签,而且存储的是一个batch_size
大小的数据。接下来显示一下:
label_name=['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']
print((a[0][0]).shape)
show_idx = 0
plt.imshow(a[0][show_idx])
plt.title(label_name[a[1][show_idx]])
有图片输出就说明没问题了。
后记
可以发现这一系列的数据读取操作是可以封装在一起的,这里先将实验验证用的ipynb
放出来:
- 直接使用
tf.data
遍历文件夹训练:
链接:https://pan.baidu.com/s/1YSWLVfmfU2brnLI0uRyljg
提取码:n2ht - 制作
tfrecord
数据集:
链接:https://pan.baidu.com/s/1HGH66klAl5zECEznRPhV7g
提取码:w6ss - 读取
tfrecord
数据集并训练:
链接:https://pan.baidu.com/s/1Jyyu2u96xLkomJhKT-AgIA
提取码:mnkj
为了方便后续使用,直接写一个现成的Python脚本,以后直接传入路径,输出可以直接训练的数据参数。
-
直接使用
tf.data
遍历文件夹训练:
链接:https://pan.baidu.com/s/1yb0EoBXzhyQEA-BO3i1Fcg
提取码:2706 -
制作
tfrecord
数据集:链接:https://pan.baidu.com/s/1Jw2LDKGeTrMKaItDHqe3dA
提取码:znfy -
读取
tfrecord
数据集训练:链接:https://pan.baidu.com/s/1rRKx9tP8jrAZhzXjIZNNTQ
提取码:sa2t