Обучение глубокой нейронной сети всегда является головной болью для тех пользователей/исследователей, у которых нет достаточно мощной машины с графическим процессором. С точки зрения использования Tensorflow, TFRecord — более правильный и элегантный способ подачи наших данных. Кроме того, TFRecord изначально поддерживается Tensorflow, что означает, что на платформе Google нам не нужно устанавливать дополнительные зависимости.

Данные TFRecord — это только представление ваших данных, как HDF5 или Pickles. Не бойтесь этого. Если вы используете Tensorflow, я полагаю, вам это понравится, так как есть целая куча API-интерфейсов TFRecordDataSet, которыми вы можете воспользоваться.

Вспомогательные функции

Во-первых, нам нужно сначала определить некоторые преобразователи, пока игнорируйте это, просто скопируйте и вставьте. Эти преобразователи используются для преобразования обычных списков в список функций TFRecords.

def _int64_feature(value):
  return tf.train.Feature(
            int64_list=tf.train.Int64List(value=value)
         )
def _floats_feature(value):
    return tf.train.Feature(
               float_list=tf.train.FloatList(value=value)
           )

def _bytes_feature(value):
  return tf.train.Feature(
              bytes_list=tf.train.BytesList(value=value)
         )

Чтобы прочитать изображения:

def load_image(path):
    img = cv2.imread(path)
    # cv2 load images as BGR, convert it to RGB
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = img.astype(np.float32)
    return img

Подумайте о своих данных

Набор данных TFRecords состоит из примеров TFRecord. Каждый пример представляет собой одни входные данные. Если вы хотите ввести 100 изображений в свою модель ML, у вас будет TFRecords размером 100, который содержит 100 примеров TFRecords, представляющих 100 изображений соответственно. В каждом примере TFRecord может быть 3 функции TFRecord для каналов R, G, B соответственно.

Прежде чем что-либо внедрять, нам нужно знать, что будет использоваться в качестве набора данных, другими словами, каковы будут функции. Например, в качестве изображений мы можем обнаружить, что каждый пиксель полезен. Предположим, что каждый пиксель содержит только r, g, b цветов, есть две стратегии его хранения.

A. для преобразования всего изображения в строку:

def get_feature(image, label):
    return {
        ‘label’: _int64_feature(label),
        ‘image’: _bytes_feature(
                 # Note the square brackets here, to be a 1D list
                    [tf.compat.as_bytes(image.tostring())]
                 )

Хотя это определенно сработает. Но если вы посмотрите на него поближе, вы обнаружите, что размер файла TFRecord чрезвычайно велик по сравнению с обычным файлом JPEG. Поэтому я рекомендую кодировать наше изображение в формате JPEG. Трюк, как показано ниже:

def get_feature(sess, image, label): 
# pass a simple tf.Session() will work
    return {
        ‘label’: _int64_feature(label),
        ‘image’: _bytes_feature(
                 # Note the square brackets here, to be a 1D list
                 [tf.image.encode_jpeg(image, quality=100).eval()]
                 )

B. для преобразования всего изображения в виде массивов.

Примечательно, что функции TFRecords могут хранить только одномерные векторы в виде функций. Следовательно, мы не можем поместить все изображение в виде трехмерного массива, мы можем разделить его, как показано ниже:

def get_feature(image, label):
    return { # split to channels just for clearer representation, you may also encode it as image.reshape(-1)
        'r': _floats_feature(image[:, :, 0].reshape(-1)),
        'g': _floats_feature(image[:, :, 1].reshape(-1)),
        'b': _floats_feature(image[:, :, 2].reshape(-1)),
        'label': _int64_feature([label])
    }

Я лично предпочитаю последний, так как он более четко показывает, что было сохранено. Обратите внимание, что мы должны использовать изображения одинаковой ширины и высоты.

Создать TFRecords

Создать TFRecords так же просто, как показано ниже:

paths = ['path/to/image1', 'path/to/image2', ...]
label = [ 2, 0, ...]
output_file_path = 'file_name.tfrecords'
with tf.python_io.TFRecordWriter(output_file_path) as writer:
        for index in range(paths):
            img = load_image(paths[index])
            example = tf.train.Example(
              features=tf.train.Features(
                  feature = get_feature(
                            img,
                            label[index])
            ))
            writer.write(example.SerializeToString())
            print('\r{:.1%}'.format((index+1)/len(paths)), end='')

Кроме того, на этом этапе вы можете разделить набор данных для обучения и тестирования. Это для создания нескольких файлов для разделения обучения/тестирования. Затем вы можете загрузить набор данных в хранилище Google в ожидании обучения.

Чтение TFRecords

При чтении из TFRecords мы можем использовать два типа функций: tf.FixedLenFeature и tf.VarLenFeature. Если вы знаете длину своей функции, вы можете использовать первую. Если длина отличается друг от друга, вы можете использовать последний. Что ж, мы лично рекомендуем FixedLenFeature, так как наши операции должны знать наши данные в деталях, таких как сверточные слои, слои пула и т. д.

A. Из значений, закодированных строкой

def get_tfrecords_features():
    return {
        'height': tf.FixedLenFeature([1], tf.int64),
        'width': tf.FixedLenFeature([1], tf.int64),
        'image_raw': tf.FixedLenFeature([1], tf.string),
        'label': tf.FixedLenFeature([5], tf.int64)
        }

Расшифровка по:

def feature_retrieval(serialized_example):
    
    features = tf.parse_single_example(
                  serialized_example,
                  features=get_tfrecords_features()
               )
# Decoding ...
    _height = tf.cast(features['height'], tf.int64)[0]
    _width = tf.cast(features['width'], tf.int64)[0]
    _image_raw = tf.cast(features['image_raw'], tf.string)[0]
    
    # Image processing
    image_shape = tf.stack([_height, _width, 3])
    # has to be uint8 type
    image_raw = tf.decode_raw(_image_raw, tf.uint8)
    image = tf.reshape(image_raw, image_shape)
# Important, since we do not know the image shape information, it is all encoded in Tensorflow. Hence, it can hardly pass the shape check in your Tensorflow Neural Network.
    resized_image = tf.image.resize_image_with_crop_or_pad(
        image=image,
        target_height=IMAGE_HEIGHT,
        target_width=IMAGE_WIDTH
    )
    resized_image = tf.cast(resized_image, tf.float32)
    # Label processing
    label = tf.cast(features['label'], tf.int64)
    
    return resized_image, label

Точно так же, если вы используете байты, закодированные в формате JPEG, вместо этого замените одну простую строку:

image_raw = tf.decode_raw(_image_raw, tf.uint8)

to

image_raw = tf.image.decode_jpeg(_image_raw)

B. Из значений, закодированных массивом

Если мы используем изображения размером 255x255, нам нужно определить нашу функцию как:

def get_tfrecords_features():
    return {
        'r': tf.FixedLenFeature([255*255], tf.float32),
        'g': tf.FixedLenFeature([255*255], tf.float32),
        'b': tf.FixedLenFeature([255*255], tf.float32),
        'label': tf.FixedLenFeature([1], tf.int64)
        }

Чтобы получить реальные данные из примера, выполните следующие действия:

def feature_retrieval(serialized_example, width, height):

    features = tf.parse_single_example(
                  serialized_example,
                  features=get_tfrecords_features()
               )

    _r = tf.cast(features['r'], tf.float32)
    _g = tf.cast(features['g'], tf.float32)
    _b = tf.cast(features['b'], tf.float32)
    _label = tf.cast(features['label'], tf.int64)

    data = tf.transpose(
            tf.stack([
                tf.reshape(_r, (height, width)), 
                tf.reshape(_g, (height, width)), 
                tf.reshape(_b, (height, width))
            ])
        )
    label = tf.transpose(_label)
    return data, label

Чтобы вывести значения в консоль

Следовательно, вы можете прочитать его, как показано ниже:

def load_tfrecords(tfrecords_filepath):
    items = []
    labels = []
    print("Loading %s" % tfrecords_filepath)
    with tf.Session() as sess:
        for serialized_example in tf.python_io.tf_record_iterator(tfrecords_filepath):
            data, label = feature_retrieval(serialized_example)
            items.append(data)
            labels.append(label)
    print("Finished Loading %s" % tfrecords_filepath)
    return (tf.stack(items), tf.stack(labels))

Использовать TFRecords

Для подачи наших данных в этом руководстве представлены два метода.

А. Использование обработчиков

Мы можем создать дескриптор строки вместо того, чтобы сразу передавать данные. В этом случае нам не нужно указывать, что такое входной тензор, а значит, нам не нужно перестраивать модель, чтобы изменить вход.

handle_pl = tf.placeholder(tf.string, shape=[])

Далее мы извлечем итератор обучения и тестирования. Между тем, мы определяем BATCH_SIZE как 24.

def tfrecords_to_dataset(handle):
    training_files = [ PATH_TO_EACH_TFRECORD ]
    testing_files = [ PATH_TO_EACH_TFRECORD ]
    BATCH_SIZE = 24
    # Training data
    train_dataset = tf.data.TFRecordDataset(training_files)
    train_dataset = train_dataset.map(feature_retrieval)
    train_dataset = train_dataset.apply(
               tf.contrib.data.batch_and_drop_remainder(BATCH_SIZE))
    # Testing data
    test_dataset = tf.data.TFRecordDataset(testing_files)
    test_dataset = test_dataset.map(feature_retrieval)
    test_dataset = test_dataset.apply(
              tf.contrib.data.batch_and_drop_remainder(BATCH_SIZE))
    # Initializable iterator
    iterator = tf.data.Iterator.from_string_handle(
            handle,
            train_dataset.output_types,
            train_dataset.output_shapes)
    
    next_elem = iterator.get_next()

    train_init_iter = train_dataset.make_initializable_iterator()
    test_init_iter = test_dataset.make_initializable_iterator()

    return next_elem, train_init_iter, test_init_iter

Затем вы можете проверить это:

handle_pl = tf.placeholder(tf.string, shape=[])
next_value, train_init_iter, test_init_iter = tfrecords_to_dataset(handle_pl)
image_pl, labels_pl = next_value
// Get your model here
loss = YOUR_MODEL(image_pl, labels_pl)
  
# Training
with tf.Session() as sess:
    training_handle = sess.run(train_init_iter.string_handle())
    sess.run(train_init_iter.initializer)
    sess.run(loss, feed_dict={'handle_pl' = training_handle})
    
with tf.Session() as sess:
    testing_handle = sess.run(test_init_iter.string_handle())
    sess.run(test_init_iter.initializer)
    sess.run(loss, feed_dict={'handle_pl' = testing_handle})

Как получить следующую порцию данных? Для этого мы должны убедиться, что запускаем дескриптор перед следующим кормлением.

sess.run(ITERATOR.string_handle())

B. Используйте конвейер ввода

Использование обработчиков может быть не таким интуитивно понятным в начале, поэтому использование преимуществ tf.train может быть лучшим вариантом. Ключевым моментом здесь является использование tf.train.shuffle_batch для создания пакетов.

def prepare_data(filename_queue):
    
    IMAGE_HEIGHT = 3000
    IMAGE_WIDTH = 3000
    
    reader = tf.TFRecordReader()
    _, serialized_example = reader.read(filename_queue)
    
    image, label = feature_retrieval(serialized_example)
    
    images, labels = tf.train.shuffle_batch(
        [image, label],
        batch_size=2,
        capacity=30,
        num_threads=2,
        min_after_dequeue=10)
    return images, labels

Чтобы начать обучение:

def train():
    
    filename_queue = tf.train.string_input_producer(
            ['./tfrecords/training.tfrecord'], 
            num_epochs=5
        )
    images_batch, labels_batch = prepare_data(filename_queue)
    # feed to your model directly
    model = YOUR_MODEL(images_batch, labels_batch)
    
    sess, ops = get_trainer(image_pl, label_pl)
    # Init
    init_op = tf.group(
            tf.global_variables_initializer(),
            # if we have 'num_epochs' in filename_queue
            tf.local_variables_initializer()
        )
    sess.run(init_op)
    # Start training
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(sess=sess, coord=coord)
    
    start_training(sess, data_batch, ...)
    coord.request_stop()
    coord.join(threads)

Ключевым моментом обучения является использование цикла while для использования последнего фрагмента данных, триггером конца является OutOfRangeError:

def start_training():
    while not coord.should_stop():
        try:
            _ = sess.run([ops['train_op']])
        except tf.errors.OutOfRangeError:
           print('finished')

Недостатком этого метода является то, что вы не можете отслеживать процесс эпохи. Следовательно, вы можете попробовать установить num_epoches = 1, чтобы инициализировать его для каждой эпохи, чтобы упростить подсчет.

Из облака Google

В этой реализации вам не нужно делать больше работы, чтобы заставить его работать в Google Cloud. Единственный совет для этого — использовать библиотеку file_io, а не Python open() напрямую, поскольку open работает только при использовании локального пути. Когда вы загружаете его в хранилище Google, URL-адрес начинается с gs://.

from tensorflow.python.lib.io import file_io
file_io.FileIO(google_cloud_filename, mode='r')

Это в основном все, что вам нужно сделать для подготовки и использования данных.