«Это 27-й день моего участия в ноябрьском испытании обновлений. Подробную информацию об этом событии см.:Вызов последнего обновления 2021 г.".
1. Формат входных данных TFRecord
1.1 Введение в формат TFRecord
Данные в файле TFRecord хранятся в формате tf.train.Example Protocol Buffer. Следующий код дает определение:
message Example{
Features features = 1;
}
message Features{
map<String, Features> feature = 1;
}
message Feature{
oneof kind{
BytesList bytes_list = 1;
Floatlist float_list = 2;
Int64List int64_list = 3;
}
}
Как видно из приведенного выше кода, структура данных tf.train.Example относительно лаконична. tf.train.Example содержит словарь от имен атрибутов до значений. Имя атрибута представляет собой строку, а значение атрибута может быть строкой, списком действительных чисел или списком целых чисел.
1.2 Пример программы TFRecord
# 先定义一个FileName
filename = "......"
# 创建一个writer来写TFRecord文件
writer = tf.python_io.TFRecordWriter(filename)
for index in range(num_examples):
# 将图像矩阵转化成一个字符串
image_raw = images[index].toString
# 将一个样例转化成Example Protocol Buffer。并将所有信息写入这个数据结构
example = tf.train.Example(features=tf.train.Features(feature={
'pixels':_int64_feature(pixels),
'label':_int64_feature(np.argmax(labels[index])),
'image_raw':_bytes_feature(image_raw)}))
# 将一个Example写入TFRecord文件
writer.write(example.SerializeToString)
writer.close()
2. Обработка данных изображения
2.1 Обработка кодирования изображения
Изображение в цветовом режиме RGB можно рассматривать как трехмерную матрицу, и каждое число в матрице представляет собой яркость разных цветов в разных местах изображения. Однако при сохранении речь идет не о записи чисел на матрицу, а о записи результата после сжатия и кодирования, в это время требуются функции кодирования и декодирования. Ниже приведен пример кода TensorFlow:
# matplotlib.pyplot是一个python的画图工具
import matplotlib.pyplot as plt
import tensorflow as tf
# 读取图像的原始数据
image_raw_data = tf.gfile.FastGFile("文件路径名", r).read()
with tf.Session() as sess:
# 对图像进行jepg格式解码从而得到图相对应的三维矩阵。TensorFlow还提供了
# tf.image_decode_png函数对png格式的图像进行解码。解码之后的结果为一个张量
# 在使用它的取值之前需要明确调用运行的过程
image_data = tf.image.deode_jepg(image_raw_data)
# 接下来就用pyplot可视化工具展示图像
# 将表示一张图像的三维矩阵重新按照jepg格式编码并存入文件中。打开这张图像
# 可以得到和原始图像一样的数据
encoded_image = tf.image.encode_jpeg(img_data)
with tf.gfile.GFile("/path/to/output", "wb") as f:
f.write(encoded_image.eval())
2.2 Изменение размера изображения
Размер получаемых изображений разный, но количество узлов, вводимых в нейронную сеть, фиксировано, поэтому размер необходимо унифицировать перед вводом.
Есть два способа настроить размер изображения: первый — использовать алгоритм, позволяющий максимально сохранить в новом изображении всю информацию исходного изображения. TensorFlow предоставляет четыре разных метода и заключает их в функцию tf.image.resize_images. Следующий код является примером программы:
# 首先将图片数据转化为实数类型。这一步将0~255的像素值转化为0.0-1.0范围内的实数。
# 大多数图像处理API支持整书和实数类型的输入。如果输入是整数类型,这些API会在内部将输入转化为实数处理
# 再输出转化为整数。如果有多个处理步骤,在整数和实数之间的反复转化将导致精度损失,因此推荐在图像处理前将其
# 转化为实数类型。下面的样例将略去这一步骤,假设img_data是经过类型转化的图像
img_data = tf.image.convert_image_dtype(img_data, dtype=tf.float32)
# 通过tf.image.resize_images函数调整图像的大小。这个函数的第一个参数为原始图像
# 第二和第三个参数为调整后图像的大小,method参数给出了调整图像大小的算法
# 注意,如果输入数据是unit8格式,那么输出将是0~255之内的实数,不方便后续处理,故建议转化为实数类型
resized = tf.image.resize_images(img_data, [300, 300], method=0)
В следующей таблице показан алгоритм изменения размера изображения, соответствующий значению параметра method функции tf.image.resize_images:
Значение метода | Алгоритм изменения размера изображения |
---|---|
0 | Билинейная интерполяция |
1 | Интерполяция ближайшего соседа |
2 | Бикубическая интерполяция |
3 | Интерполяция площади |
Помимо полного сохранения всей информации об изображении, TensorFlow также предоставляет API для обрезки или заливки изображения. Вот пример кода:
# 通过tf.image.resize_image_with_crop_or_pad函数调整图像的大小。这个函数的
# 第一个参数为原始图像,后面两个参数是调整后的目标图像大小。如果原始图像的尺寸大于
# 目标图像,这个函数会自动在视奏填充全0背景。因为原始图像大小为1797x2673
# 所以第一个命令会自动裁剪,第二个命令会自动填充
croped = tf.image.resize_image_with_crop_or_pad(image_data, 1000, 1000)
padded = tf.image_resize_image_with_crop_or_pad(image_data), 3000, 3000)
TensorFlow также поддерживает изменение размера изображений путем масштабирования.Ниже приведен пример кода:
# 通过tf.image.central_crop函数可以按比例裁剪图像。该函数的第一个参数为原始图像,第二个为调整比例。这个比例需要是一个(0,1]的实数
central_cropped = tf.image.central_crop(img_data, 0.5)
TensorFlow также предоставляет функцию tf.image.crop_to_bounding_box и функцию tf.image.pad_to_bounding_box для обрезки или заполнения заданной области изображения. Однако обе функции требуют заданного размера для удовлетворения определенных требований. В противном случае будет сообщено об ошибке. Например, при использовании функции кадрирования tf требует, чтобы предоставленный размер изображения был больше целевого размера.
2.3 Переворот изображения
TensorFlow предоставляет некоторые функции для поддержки перелистывания изображений. Следующий код реализует перелистывание вверх и вниз, перелистывание влево и вправо, перелистывание по диагонали и перелистывание случайным образом:
# 上下翻转
flipped = tf.image.flip_up_down(img_data)
# 左右翻转
flipped = tf.image.flip_left_right(img_data)
# 对角线翻转
transposed = tf.image.transpose_image(img_data)
# 随机翻转训练图像
# 以%50概率上下翻转
flipped = tf.image_random_flip_up_down(img_data)
# 以%50概率左右翻转
flipped = tf.image_random_flip_left_right(img_data)
2.4 Настройка цвета изображения
Отрегулируйте яркость, контрастность, насыщенность и оттенок. При обучении нейронной сети эти свойства обучающих изображений могут быть случайным образом скорректированы, чтобы на обученную модель как можно меньше влияли посторонние факторы. tf предоставляет соответствующие API, следующий код показывает, как изменить яркость:
# 将图像的亮度-0.5
adjusted = tf.image.adjust_brightness(img_data, -0.5)
# 色彩调整的API可能导致像素的实数值超出0.0-1.0的范围,因此在输出最终图像前需要
# 将其值截断在0.0-1.0范围区间内,否则不仅图像无法正常可视化,以此为输入的神经网络
# 的训练质量也可能受到影响
# 如果对图像进行多项处理操作,那么这一截断过程应当在所有处理完成后进行。举个例子,
# 假如对图像以此提高亮度和减少对比度,那么第二个操作可能将第一个操作生成的部分
# 过亮的像素拉回到不超过1.0的范围内,因此在第一个操作后不应该立即截断
adjusted = tf.clip_by_value(adjusted, 0.0, 1.0)
adjusted = tf.image.adjust_brightness(img_data, 0.5)
# 在[-max_delta, max_delta]的范围随机调整图像的亮度
adjusted = tf.image.random_brightness(image, max_delta)
Следующий код показывает, как настроить контрастность изображения:
# 将图像的对比度减少到0.5倍
adjusted = tf.image.adjust_contrast(img_data, 0.5)
# 将图像的对比度增加5倍
adjusted = tf.image.adjust_contrast(img_data, 5)
# 在[lower, upper]的范围内随机调整图的对比度
adjusted = tf.image.random_contrast(image, lower, upper)
Следующий код показывает, как настроить оттенок изображения:
# 下面四条命令分别将色相增加0.1, 0.3, 0.6, 0.9
adjusted = tf.image.adjust_hue(img_data, 0.1)
adjusted = tf.image.adjust_hue(img_data, 0.3)
adjusted = tf.image.adjust_hue(img_data, 0.6)
adjusted = tf.image.adjust_hue(img_data, 0.9)
# 在[-max_delta, max_delta]的范围内随即调整图像的色相
# max_delta的取值在[0, 0.5]之间
adjusted = tf.image.random_hue(image, maxdelta)
Следующий код показывает, как настроить насыщенность изображения:
# 将图像的饱和度-5
adjusted = tf.image.adjust_saturation(img_data, -5)
#在[lower, upper]内随机调整饱和度
adjusted = f.image.random_saturation(image, lower, upper)
TensorFlow также предоставляет API для завершения операции нормализации изображения, то есть средняя яркость становится равной 0, а дисперсия становится равной 1:
# 将代表一张图像的三维矩阵中的数字均值变成0,方差变成1
adjusted = tf.image.per_image_standardization(img_data)
2.5 Обработка полей выноски
Добавьте выноски к частям изображения, которые нужно выделить:
# 将图像缩小一些,这样可视化能让标注框更加清楚
img_data = tf.image.resize_images(img_data, [180, 267], method=1)
# tf.image.draw_bounding_boxes函数要求图像矩阵中的数字为实数,所以需要先将
# 图像矩阵转化为实数类型。tf.image.draw_bounding_boxes函数图像的输入是一个batch的数据
# 也就是多张图像组成的四维矩阵,所以需要将解码后的图像矩阵加一维。
batched = tf.expend_dims(tf.image.convert_image_dtype(img_data, tf.float32), 0)
# 给出每一张图像的所有标注框,一个标注框有4个数字,分别代表[ymin, xmin, ymax, xmax]
# 这里给出的数字都是图像的相对位置。比如在180x267图像中,
# [0.35, 0.47, 0.5, 0.56]代表了从(63, 125)到(90, 150)的图像
boxes = tf.constant([[[0.05, 0.05, 0.9, 0.7], [0.35, 0.47, 0.5, 0.56]]])
result = tf.imagedraw_bounding_boxes(batched, boxes)
Случайная обрезка информативной части изображения также является способом повысить надежность модели. Это позволяет обученной модели быть независимой от размера распознаваемого объекта. В следующем коде показано, как использовать функцию tf.image.sample_distorted_bounding_box для завершения процесса случайного перехвата изображений:
boxes = tf.constant([[[0.05, 0.05, 0.9, 0.7], [0.35, 0.47, 0.5, o.56]]])
# 可以通过提供标注框的方式来告诉随机截取图像的算法哪些部分是“有信息量”的
# min_object_covered = 0.4表示截取部分至少包含某个标注框%40的内容。
begin, size, bbox_for_draw = tf.image.sample_distorted_bounding_box(
tf.shape(img_data), bounding_boxes = boxes,
min_object_covered = 0.4)
# 通过标注框可视化截取得到的图像
batched = tf.expend_dims(
tf.image.convert_image_dtype(img_data, tf.float32), 0)
image_with_box = tf.image.draw_boudning_boxes(batched, bbox_for_draw)
# 截取随机出来的图像,因为算法带有随机成分,所以每次结果都会有所不同
distorted_image = tf.slice(img_data, bigin, size)
2.6 Резюме предварительной обработки изображения
- Учитывая изображение, отрегулируйте цвет изображения, отрегулируйте яркость, контрастность, насыщенность и настройте последовательность;
- Если поле аннотации не предусмотрено, все изображение считается частью, требующей внимания;
- Преобразование типа тензора изображения;
- Произвольно перехватывать изображения, чтобы уменьшить влияние размера объекта, требующего внимания, на алгоритм распознавания изображений;
- Подгонять случайно перехваченное изображение под размер входного слоя нейронной сети, причем алгоритм подгонки размера случайный;
- Произвольно перевернуть изображение влево и вправо;
- Корректирует цвета изображения в случайном порядке.
3. Фреймворк многопоточной обработки входных данных
3.1 Очереди и многопоточность
В TensorFlow очереди, как и переменные, являются узлами с отслеживанием состояния на вычислительном графе. Другие вычислительные узлы могут изменять свое состояние. Для переменных вы можете изменить значение переменной с помощью операций присваивания. Для очередей операции по изменению состояния очереди в основном включают Enqueue, EnqueueMany и Dequeue. Следующий код показывает, как работать с очередью:
import tensorflow as tf
# 创建一个先进先出的队列,指定队列最多保存两个元素,并指定类型为整型
q = tf.FIFOQueue(2, "int32")
# 使用enqueue_many函数来初始化队列中的元素。类似变量的初始化,使用队列前必须得初始化
init = q.enqueue_many(([0, 10], ))
# 使用dequeue将第一个元素出队列
x = q.dequeue()
# 后面的就浅显易懂了
y = x + 1
q_inc = q.enqueue(y)
with tf.Session() as sess:
init.run()
for _ in range(5):
v, _ = sess.run([x, q_inc])
print(v)
TensorFlow предоставляет два вида очередей, FIFOQueue и RandomShuffleQueue, последняя будет перемешивать элементы очереди, и каждый раз операция извлечения из очереди будет получать случайно выбранный из всех элементов текущей очереди. В TensorFlow очереди — это не только структура данных, но и важный механизм асинхронного вычисления значений тензора. Например, несколько потоков могут одновременно записывать элементы в очередь или одновременно читать элементы из очереди.
TensorFlow предоставляет два класса, tf.Coordinator и tf.QueueRunner, для выполнения функции многопоточной совместной работы. t.Coordinator в основном используется для одновременной остановки нескольких потоков и предоставляет три функции should_stop, requets_stp и join. Перед запуском потока необходимо объявить класс tf.Coordinator и передать этот класс в каждый созданный поток. Запущенный поток должен всегда запрашивать функцию should_stop, предоставленную в классе tf.Coordinator.Когда возвращаемое значение этой функции истинно, текущий поток также должен выйти. Каждый запущенный поток может уведомить другие потоки о выходе, вызвав функцию request_stop. Когда поток вызывает функцию request_stop, возвращаемое значение функции should_stop устанавливается равным true, чтобы другие потоки могли быть завершены. Ниже приведен пример кода:
import tensorflow as tf
import numpy as np
import threading
import time
# 线程中运行的程序,这个程序每隔1秒判断是否需要停止并打印自己的ID
def MyLoop(coord, worker_id):
# 使用tf.Coordinator类提供的协同工具判断当前线程是否需要停止
while not coord.should_stop():
# 随机停止所有的线程
if np.random.rand() < 0.1:
print("Stoping from id:%d\n" % worker_id)
# 调用crod.request_stop()函数来通知其他线程停止
coord.request_stop(0)
else:
# 打印当前线程的id
print("Working on id:%d\n" % worker_id)
# 暂停1秒
time.sleep(1)
# 声明一个tf.train.Coordinator类来协同多个线程
coord = tf.rain.Coordinator()
# 声明创建5个线程
threads = [
threading.Thread(target=MyLoop, args=(coord, i, )) for i in range(5)]
# 启动所有线程
for t in threads:t.start()
# 等待所有线程退出
coord.join(threads)
tf.QueueRunner в основном используется для запуска нескольких потоков для работы с одной и той же очередью, и этими потоками можно управлять единообразно через tf.Coordinator.
Следующий код — это tf.QueueRunner и tf.Coordinator для управления многопоточными операциями очереди:
import tensorflow as tf
# 声明一个先进先出的队列,队列中最多100个元素,类型为实数
queue = tf.FIFOQueue(100, "float")
# 定义队列的入队操作
enqueue_op = queue.enqueue([tf.random_normal([1])])
# 使用tf.train.QueueRunner来创建多个线程娙队列的入队操作
# tf.train.QueueRunner的第一个参数给出了被操作的队列,[enqueue_op] * 5
# 表示了需要启动5个线程,每个线程中运行的是enqueue_op操作
qr = tf.train.QueueRunner(queue, [enqueue_op] * 5)
# 将定义过的QueueRunner加入TensorFlow计算图上指定的集合
# tf.train.add_queue_runner函数没有指定集合
# qr加入默认的tf.GraphKeys.QUEUE_RUNNERS集合
tf.train.add_queue_runner(qr)
# 定义出队操作
out_tensor = queue.dequeue()
with tf.Session() as sess:
# 使用tf.train.Coordinator来协同启动线程
coord = tf.train.Coordinator()
'''
使用tf.train.QueueRunner时,需要明确调用tf.train.start_queue_runners来启动所有的线程
否则因为没有线程运行入队操作,当调用出队操作时,程序会一直等待入队操作被运行。
tf.train.start_queue_runners函数会默认启动tf.GraphKeys.QUEUE_RUNNERS集合中所有
的QueueRunner。因为这个函数只支持启动指定集合中的QueueRunner,所以一般来说
tf.train.add_queue_runner函数和tf.train.start_queue_runners函数会指定同一个集合
'''
threads = tf.train.start_queue_runners(sess=sess, coord=coord)
# 获取队列中的取值
for _ in range(3): print(sess.run(out_tensor)[0])
# 使用tf.train.Coordinator来停止所有的线程
coord.request_stop()
coord.join(threads)
3.2 Очередь входных файлов
- TensorFlow предоставляет функцию tf.train.match_filenames_once для получения всех файлов, соответствующих регулярному выражению, а полученным списком файлов можно эффективно управлять с помощью функции tf.train.string_input_producer;
- Функция tf.train.string_input_producer создаст входную очередь, используя список файлов, предоставленный во время инициализации, а исходными элементами входной очереди являются все файлы в списке файлов;
- Установив параметр shuffle, функция tf.train.strin_input_producer поддерживает случайное перемешивание порядка, в котором файлы удаляются из очереди в списке файлов. Когда параметр shuffle имеет значение True, файлы будут перемешиваться перед добавлением в очередь, поэтому порядок удаления из очереди также является случайным.Процесс случайного перемешивания порядка файлов и добавления их во входную очередь будет выполняться в отдельном потоке. , что не повлияет на скорость получения файлов;
- Когда все файлы во входной очереди будут обработаны, он повторно поставит в очередь все файлы из списка файлов, предоставленного во время инициализации.
Функция tf.train.string_input_producer может установить параметр num_epochs, чтобы ограничить максимальное количество эпох для загрузки исходного списка файлов.В следующем коде показано, как создать образец:
import tensorflow as tf
# 创建TFRecord文件的帮助函数
def _int64_feature(value):
return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))
# 模拟海量数据情况下将数据写入不同的文件。num_shards定义了总共写入多少个文件。
# instances_per_shard定义了每个文件中有多少数据
num_shards = 2
instance_per_shard = 2
for i in range(num_shards):
'''
将数据分为多少个文件时,可以将不同文件以类似0000n-of-0000m的后缀区分。其中m表示
了数据总共被存在了多少个文件中,n表示当前文件的编号。式样的方式既方便了通过正
则表达式获取文件列表,又在文件名中加入了更多的信息
'''
filename = ('/path/to/data.tfrecords-%.5d-of-%.5d' % (i, num_shards))
writer = tf.python_io.TFRecordWriter(filename)
# 将数据封装成Example结构并写入TFRecord文件
for j in range(instance_per_shard):
example = tf.train.Example(features=tf.train.Features(features={
'i': _int64_feature(i),
'j': _int64_feature(j)
}))
writer.write(example.SerializeToString())
writer.close()
После запуска вышеуказанной программы в целевом файле будут созданы два файла, в каждом из которых хранятся образцы.После создания образцов данных следующий код показывает функцию tf.train.match_filenames_once и функцию tf.train.string_input_producer. использовать:
import tensorflow as tf
# 使用tf.train.match_filenames_once函数获取文件列表
files = tf.train.match_filenames_once("/path/to/data.tfrecords-*")
# 通过tf.train.string_input_producer函数创建输入队列,输入队列中的文件列表为
# tf.train.match_filenames_once函数获取的文件列表这里将shuffle参数设置为False
# 来避免随机打乱文件顺序。但一般在解决真实问题时,会将shuffle参数设置为True
filename_queue = tf.train.string_input_producer(files, shuffle=False)
# 解析一个样本
reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue)
features = tf.parse_single_example(
serialized_example,
features={
'i': tf.FixedLenFeature([], tf.int64),
'j': tf.FixedLenFeature([], tf.int64)
}
)
with tf.Session() as sess:
# 初始化一些变量
tf.local_variables_initializer().run()
print(sess.run(files))
# 声明tf.train.Coordinator类来协同不同线程,并启动线程
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess, coord=coord)
# 多次执行获取数据的操作
for i in range(6):
print(sess.run([features['i'], features['j']]))
coord.request_stop()
coord.join(threads)
3.3 Комбинированные тренировочные данные
Организуйте отдельные данные в пакеты. TensorFlow предоставляет функции tf.train.batch и f.train.shuffle_batch для организации отдельных образцов в пакетную форму. Обе функции будут генерировать очередь. Операция входа в очередь — это метод создания одного образца, в то время как каждый раз, когда вы выходите из очередь, вы получаете партию образцов. Единственная разница между ними заключается в том, будут ли они перетасовывать порядок. Ниже приведен код отображения:
# 这里example结构中i表示一个样例的特征向量
# 比如一张图像的矩阵,而j表示该样例对应的标签
example, label = features['i'], features['j']
# 一个batch中样例的个数
batch_size = 3
# 组合样例的队列中最多可以存储的样例个数。这个队列如果太大,那么需要占用很多内存资源
# 如果太小,那么出队操作可能会因为没有数据而被阻碍,从而导致训练效率降低,一般来说,
# 这个队列的大小会和每一个batch的大小相关,下面给出了设置队列大小的一种方式
capacity = 1000 + 3 * batch_size
# 使用tf.train.batch来组合样例。[example, label]给出了需要组合的元素
# 一般example和label分别代表训练样本和这个样本对应的正确标签。batch_size参数给出了
# 每个batch中样例的个数。capacity给出了队列的最大容量。当队列长度等于容量时,
# TensorFlow将自动重新启动入队操作
example_batch, label_batch = tf.train.batch(
[example, label], batch_size=batch_size, capacity=capacity
)
with tf.Session() as sess:
tf.initialize_all_variables().run()
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess, coord=coord)
# 获取并打印组合之后的样例。在真实问题中,这个输出一般会作为神经网络的输入
for i in range(2):
cur_example_batch, cur_label_batch = sess.run(
[example_batch, label_batch]
)
print(cur_example_batch, cur_label_batch)
coord.request_stop()
coord.join(threads)
Следующий код показывает, как использовать функцию tf.train.shuffle_batch:
example, label = features['i'], features['j']
'''
使用tf.train.shuffle_batch函数来组合样例。tf.train.shuffle_batch函数
的参数大部分都和tf.train.batch函数相似,但是min_after_dequeue参数限制了出队时队列中元素的
最少个数。当队列中元素个数太少时,随机打乱样例顺序的作用就不大了。所以
tf.train.shuffle_batch函数提供了限制出队时最少元素的个数来保证随机打乱顺序的作用,当出队函数被调用
但是队列元素个数不够时,出队操作将等待更多的元素入队才会完成,
如果min_after_dequeue参数被设定,capacity也应该相应调整来满足性能需求
'''
example_batch, label_batch = tf.train.shuffle_batch(
[example, label], batch_size=batch_size,
capacity=capacity, min_after_dequeue=30
)
- Установив параметр num_threads в функции tf.train.shuffle_batch, вы можете указать несколько потоков для одновременного выполнения операции постановки в очередь. Когда параметр num_threads больше 1, несколько потоков будут одновременно читать разные примеры в файле и выполнять их предварительную обработку;
- Для функции tf.train.shuffle_batch разные потоки будут читать один и тот же файл. Если сэмплы в файле похожи, это может повлиять на обучающий эффект нейронной сети, поэтому при использовании функции tf.train.shuffle_batch необходимо максимально случайным образом перемешать сэмплы в одном и том же файле FRecord. При использовании функции tf.train.shuffle_batch_join разные потоки будут читать разные файлы.Если количество потоков, читающих данные, больше, чем общее количество файлов, несколько потоков могут читать одинаковые части данных в одном и том же файле. Кроме того, несколько потоков, читающих несколько файлов, могут вызвать слишком много адресов жесткого диска, что сделает чтение менее эффективным.
3.4 Структура обработки входных данных
import tensorflow as tf
import temp
# 创建文件列表,并通过文件列表创建输入文件队列。在调用输入数据处理流程前,需要统一
# 所有原始数据的格式并将它们存储到TFRecord文件中。下面给出的文件列表应该包含所有
# 提供训练数据的TFRecord
files = tf.train.match_filenames_once("/path/to/file_pattern-*")
filename_queue = tf.train.string_input_producer(files, shuffle=False)
# 解析数据,这里假设Image中存的是图像的原始数据,label为该样例对应的标签
# height、width和channels给出了图片的维度
reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue)
features = tf.parse_single_example(
serialized_example,
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, width = features['height'], features['width']
channels = features['channels']
# 从原始图像数据解析出像素矩阵,并根据图像尺寸还原图像
decoded_image = tf.decode_raw(image, tf.unit8)
decoded_image.set_shape([height, width, channels])
image_size = 299
distorted_image = temp.preprocess_for_train(decoded_image, image_size, image_size, None)
# 将处理后的图像和标签数据通过tf.train.shuffle_batch整理成神经网络训练时需要的batch
min_after_dequeue = 10000
batch_size = 100
capacity = min_after_dequeue + 3 * batch_size
image_batch, label_batch = tf.train.shuffle_batch(
[distorted_image, label], batch_size=batch_size, capacity=capacity,
min_after_dequeue=min_after_dequeue
)
# 定义神经网络的结构及优化过程。image_batch可以作为输入提供给神经网络的输入层。
# label_batch则提供了输入batch中样例的正确答案
learning_rate = 0.01
logit = inference(image_batch)
loss = calc_loss(logit, label_batch)
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss)
# 声明会话并运行神经网络的优化过程
with tf.Session() as sess:
# 神经网络训练准备工作,包括变量初始化,线程启动
sess.run((tf.global_variables_initializer(), tf.local_variables_initializer()))
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess, coord=coord)
# 神经网络训练过程
TRAINING_ROUNDS = 5000
for i in range(TRAINING_ROUNDS):
sess.run(train_step)
# 停止所有线程
coord.request_stop()
coord.join(threads)
Схема обработки входных данных:
- Первый шаг — получить список файлов, в которых хранятся обучающие данные;
- Второй шаг — выборочно перетасовать порядок файлов в списке файлов с помощью функции tf.train.string_input_producer;
- Третий шаг — получение данных изображения и их предварительная обработка, которая будет выполняться в нескольких потоках параллельно с помощью механизма, предоставляемого tf.train.shuffle_batch;
- На четвертом этапе функция tf.train.shuffle_batch организует обработанные одиночные входные выборки в пакеты и предоставляет их входному слою нейронной сети.
4. Набор данных (DataSet)
4.1 Основное использование наборов данных
В структуре набора данных каждый набор данных представляет собой источник данных: данные могут поступать из тензора, файла формата TFRecord или серии сегментированных файлов и т. д. Поскольку обучающие данные обычно не все записываются в память, для чтения данных из набора данных требуется итератор для последовательного чтения. Подобно очереди, набор данных также является узлом вычислительного графа.
Существуют следующие шаги для чтения данных из набора данных:
- Определить метод построения набора данных;
- определить траверс;
- Используйте метод get_next() для чтения тензоров данных из обходчика в качестве входных данных для других частей графа вычислений.
В реальных проектах обучающие данные обычно сохраняются в файлах на жестком диске. В настоящее время вы можете использовать TextLineDataset для более удобного чтения данных:
import tensorflow as tf
# 从文本文件创建数据集,假定每行文字是一个训练例子。这里可以提供多个文件
input_files = ["/path/to/input_file1", "/path/to/input_file2"]
dataset = tf.data.TextLineDataset(input_files)
# 定义迭代器用于遍历数据集
iterator = dataset.make_one_shot_iterator()
# 这里用get_next()返回一个字符串类型的张量,代表文件的一行
x = iterator.get_next()
with tf.Session() as sess:
for i in range(3):
print(sess.run(x))
В задачах, связанных с изображениями, входные данные обычно хранятся в формате данных TFRecoed, а TFRecordDataset может использоваться для чтения данных. В отличие от текстовых файлов, каждый TFRecord имеет свой собственный формат функций, поэтому при чтении TFRecord вам необходимо предоставить функцию анализа для анализа прочитанного формата данных TFRecoed:
import tensorflow as tf
# 解析一个TFRecord的方法,record时从文件列表中读取的一个样例
def parser(record):
features = tf.parse_single_example(
record,
features={
'feat1': tf.FixedLenFeature([], tf.int64),
'feat2': tf.FixedLenFeature([], tf.int64),
}
)
return features['feat1'], features['feat2']
# 从TFRecord文件创建数据集
input_files = ["/path/to/input_file1", "/path/to/input_file2"]
dataset = tf.data.TFRecordDataset(input_files)
# map函数表示对数据集中的每一条数据进行调用相应方法。使用TFRecordDataset读出来的是二进制
# 数据,这里需要通过map()来调用parser()对二进制数据进行解析。类似的,map()函数也可以用来
# 完成其他数据与处理工作
dataset = dataset.map(parser)
# 定义迭代器
iterator = dataset.make_one_shot_iterator()
# feat1, feat2是parser()返回的一维int64型张量,可以作为输入用于进一步计算
feat1, feat2 = iterator.get_next()
with tf.Session as sess:
for i in range(10):
f1, f2 = sess.run(feat1, feat2)
В приведенном выше примере используется простейший one_shot_iterator для перебора набора данных. При использовании one_shot_iterator должны быть определены все параметры набора данных, поэтому one_shot_iterator не требует специального процесса инициализации. Если вам нужно использовать заполнитель для инициализации набора данных, вам нужно использовать initializable_iterator. Вот пример:
import tensorflow as tf
# 解析一个TFRecord的方法,record时从文件列表中读取的一个样例
def parser(record):
features = tf.parse_single_example(
record,
features={
'feat1': tf.FixedLenFeature([], tf.int64),
'feat2': tf.FixedLenFeature([], tf.int64),
}
)
return features['feat1'], features['feat2']
# 从TFRecord文件创建数据集,具体文件路径是一个placeholder,稍后再提供具体路径
input_files = tf.placeholder(tf.string)
dataset = tf.data.TFRecordDataset(input_files)
dataset = dataset.map(parser)
# 定义迭代器
iterator = dataset.make_initializable_iterator()
# feat1, feat2是parser()返回的一维int64型张量,可以作为输入用于进一步计算
feat1, feat2 = iterator.get_next()
with tf.Session as sess:
sess.run(iterator.initializer, feed_dict={input_files:
["/path/to/iput_file1", ["/path/to/input_file2"]]})
while True:
try:
sess.run([feat1, feat2])
except tf.errors.OutOfRangeError:
break
В дополнение к двум вышеупомянутым итераторам Google также предоставляет еще два гибких итератора, reinitializable_iterator и feedable_iterator.Первый может инициализироваться несколько раз для обхода разных источников данных, а второй может динамически указывать, какой итератор запускать, с помощью feed_dict.
4.2 Высокоуровневые операции над наборами данных
dataset = dataset.map(parser)
Карта — один из наиболее распространенных методов работы с наборами данных, здесь метод карты (парсер) означает вызов метода парсера, указанного в параметрах, для каждого фрагмента данных в наборе данных. После обработки каждого фрагмента данных карта упаковывает обработанные данные в новый набор данных и возвращает его. Функция карты очень гибкая. Как показано ниже, в структуре набора данных вы можете использовать карту для вызова метода preprocess_for_train для каждого фрагмента данных:
dataset = dataset.map(lambda x :preprocess_for_train(x, image_size, image_size, None))
В приведенном выше коде роль лямбда-выражения заключается в преобразовании исходной функции с 4 параметрами в функцию только с 1 параметром.
dataset = dataset.shuffle(buffer_size) # 随机打乱顺序
dataset = dataset.batch(batch_size) # 将数据组合成batch
Параметр buffer_size метода shuffle эквивалентен параметру min_after_dequence tf.train.shuffle_batch. Алгоритм тасования внутренне использует буфер для хранения фрагментов данных buffer_size, и каждый раз, когда считывается новый фрагмент данных, фрагмент данных случайным образом выбирается из этого буфера для вывода.
Повтор — еще один часто используемый метод операции, который копирует данные в наборе данных несколько раз, каждый из которых называется эпохой.
dataset = dataset.repeat(N) # 将数据集重复N份
В дополнение к этим методам набор данных также предоставляет множество других операций, например, concatentate() последовательно объединяет два набора данных, take(N) считывает первые N элементов из набора данных, skip(N) в наборе данных Пропускает первый N элементов данных, лоскут_карта по очереди считывает данные из нескольких наборов данных.