Как сформировать TFRecordDataset для соответствия Model API? - PullRequest
1 голос
/ 14 марта 2020

Я строю модель на основе этого кода для подавления шума . Моя проблема с ванильной реализацией состоит в том, что она загружает все данные одновременно, что не самая лучшая идея, когда обучающие данные становятся действительно большими; мой входной файл, обозначенный в связанном коде как training.h5, занимает более 30 ГБ.

Я решил вместо этого go с tf.data интерфейсом, который должен позволять мне работать с большими наборами данных; Моя проблема здесь заключается в том, что я не знаю, как правильно сформировать TFRecordDataset, чтобы он соответствовал требованиям API модели.

Если вы установите model.fit(x_train, [y_train, vad_train], для этого по существу потребуется следующее:

  • x_train, форма [nb_sequences, window, 42]
  • y_train, форма [nb_sequences, window, 22]
  • vad_train, форма [nb_sequences, window, 1]

window один типично исправления (в коде: 2000), поэтому единственная переменная nb_sequences зависит от размера вашего набора данных. Однако с tf.data мы не предоставляем x и y, а только x (см. Документы API модели ).

Сохранение tfrecord в файл

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

writer = tf.io.TFRecordWriter(path='example.tfrecord')
for record in data:
    feature = {}
    feature['X'] = tf.train.Feature(float_list=tf.train.FloatList(value=record[:42]))
    feature['y'] = tf.train.Feature(float_list=tf.train.FloatList(value=record[42:64]))
    feature['vad'] = tf.train.Feature(float_list=tf.train.FloatList(value=[record[64]]))
    example = tf.train.Example(features=tf.train.Features(feature=feature))
    serialized = example.SerializeToString()
    writer.write(serialized)
writer.close()

data - это наши тренировочные данные с формой [10000, 65]. Мой example.tfrecord доступен здесь . Это 3 МБ, в действительности это будет 30 ГБ +.

Вы можете заметить, что в связанном коде массив numpy имеет форму [x, 87], а мой - [x, 65]. Это нормально - остаток нигде не используется.

Загрузка набора данных с помощью tf.data.TFRecordDataset

Я хотел бы использовать tf.data для загрузки «по запросу» данных с некоторой предварительной выборкой Нет необходимости хранить все это в памяти. Моя попытка:

import datetime
import numpy as np
import h5py
import tensorflow as tf
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import GRU
from tensorflow.keras import regularizers
from tensorflow.keras.constraints import Constraint
from tensorflow.keras.callbacks import ModelCheckpoint
from tensorflow.keras import backend as K
from tensorflow.keras.layers import concatenate

def load_dataset(path):
    def _parse_function(example_proto):
        keys_to_features = {
            'X': tf.io.FixedLenFeature([42], tf.float32),
            'y': tf.io.FixedLenFeature([22], tf.float32),
            'vad': tf.io.FixedLenFeature([1], tf.float32)
        }
        features = tf.io.parse_single_example(example_proto, keys_to_features)
        return (features['X'], (features['y'], features['vad']))

    dataset = tf.data.TFRecordDataset(path).map(_parse_function)
    return dataset


def my_crossentropy(y_true, y_pred):
    return K.mean(2 * K.abs(y_true - 0.5) * K.binary_crossentropy(y_pred, y_true), axis=-1)


def mymask(y_true):
    return K.minimum(y_true + 1., 1.)


def msse(y_true, y_pred):
    return K.mean(mymask(y_true) * K.square(K.sqrt(y_pred) - K.sqrt(y_true)), axis=-1)


def mycost(y_true, y_pred):
    return K.mean(mymask(y_true) * (10 * K.square(K.square(K.sqrt(y_pred) - K.sqrt(y_true))) + K.square(
        K.sqrt(y_pred) - K.sqrt(y_true)) + 0.01 * K.binary_crossentropy(y_pred, y_true)), axis=-1)


def my_accuracy(y_true, y_pred):
    return K.mean(2 * K.abs(y_true - 0.5) * K.equal(y_true, K.round(y_pred)), axis=-1)


class WeightClip(Constraint):
    '''Clips the weights incident to each hidden unit to be inside a range
    '''

    def __init__(self, c=2.0):
        self.c = c

    def __call__(self, p):
        return K.clip(p, -self.c, self.c)

    def get_config(self):
        return {'name': self.__class__.__name__,
                'c': self.c}

def build_model():
    reg = 0.000001
    constraint = WeightClip(0.499)
    main_input = Input(shape=(None, 42), name='main_input')
    tmp = Dense(24, activation='tanh', name='input_dense', kernel_constraint=constraint, bias_constraint=constraint)(
        main_input)
    vad_gru = GRU(24, activation='tanh', recurrent_activation='sigmoid', return_sequences=True, name='vad_gru',
                  kernel_regularizer=regularizers.l2(reg), recurrent_regularizer=regularizers.l2(reg),
                  kernel_constraint=constraint, recurrent_constraint=constraint, bias_constraint=constraint)(tmp)
    vad_output = Dense(1, activation='sigmoid', name='vad_output', kernel_constraint=constraint,
                       bias_constraint=constraint)(vad_gru)
    noise_input = concatenate([tmp, vad_gru, main_input])
    noise_gru = GRU(48, activation='relu', recurrent_activation='sigmoid', return_sequences=True, name='noise_gru',
                    kernel_regularizer=regularizers.l2(reg), recurrent_regularizer=regularizers.l2(reg),
                    kernel_constraint=constraint, recurrent_constraint=constraint, bias_constraint=constraint)(noise_input)
    denoise_input = concatenate([vad_gru, noise_gru, main_input])

    denoise_gru = GRU(96, activation='tanh', recurrent_activation='sigmoid', return_sequences=True, name='denoise_gru',
                      kernel_regularizer=regularizers.l2(reg), recurrent_regularizer=regularizers.l2(reg),
                      kernel_constraint=constraint, recurrent_constraint=constraint, bias_constraint=constraint)(
        denoise_input)

    denoise_output = Dense(22, activation='sigmoid', name='denoise_output', kernel_constraint=constraint,
                           bias_constraint=constraint)(denoise_gru)

    model = Model(inputs=main_input, outputs=[denoise_output, vad_output])

    model.compile(loss=[mycost, my_crossentropy],
                  metrics=[msse],
                  optimizer='adam', loss_weights=[10, 0.5])
    return model

model = build_model()
dataset = load_dataset('example.tfrecord')

Мой набор данных теперь имеет следующую форму:

<MapDataset shapes: ((42,), ((22,), (1,))), types: (tf.float32, (tf.float32, tf.float32))>

, что, как я думал, - это то, что ожидает Model API (спойлер: это не так).

model.fit(dataset.batch(10))

выдает следующую ошибку:

ValueError: Error when checking input: expected main_input to have 3 dimensions, but got array with shape (None, 42)

Имеет смысл, у меня нет window здесь. В то же время кажется, что Model(inputs=main_input, outputs=[denoise_output, vad_output]).

не получает правильную форму, как изменить load_dataset так, чтобы он соответствовал ожидаемому API модели для tf.data?

1 Ответ

1 голос
/ 14 марта 2020

Учитывая, что ваша модель имеет 1 вход и 2 выхода, ваш tf.data.Dataset должен иметь две записи:
1) Массив ввода формы (window, 42)
2) Кортеж из двух массивов формы (window, 22) и (window, 1)

РЕДАКТИРОВАТЬ: обновленный ответ - вы уже возвращаете кортеж из двух элементов

Я только что заметил, что в вашем наборе данных есть эти две записи (аналогичные описанным выше), и единственное, что отличается это форма.
Единственные операции, которые вам нужно выполнить, - это выполнить пакетную обработку данных дважды:
Первая - восстановить параметр окна. Второе - передать партию модели.

window_size = 1
batch_size = 10
dataset = load_dataset('example.tfrecord')
model.fit(dataset.batch(window_size).batch(batch_size)

И это должно сработать.

Ниже приведен старый ответ, где я ошибочно принял форму вашего набора данных:

Старый ответ, где я предположил, что вы возвращаете кортеж из трех элементов:

Предполагая, что вы начинаете из трехэлементного кортежа форм (42,), (22,) и (1,) это может быть достигнуто в одних и тех же пакетных операциях, обогащенных функцией custom_reshape для возврата двухэлементного кортежа:

window_size = 1
batch_size = 10
dataset = load_dataset('example.tfrecord')
dataset = dataset.batch(window_size).batch(batch_size)

# Change output format
def custom_reshape(x, y, vad):
    return x, (y, vad)

dataset = dataset.map(custom_reshape)

Короче говоря, учитывая эту форму набора данных, вы можете просто позвонить: model.fit(dataset.batch(window_size).batch(10).map(custom_reshape)
, и это тоже должно сработать.

Удачи. И еще раз извините за суету.

...