Я пытаюсь перенести пример Python DCGAN MNIST Code Lab (https://www.tensorflow.org/tutorials/generative/dcgan) на Tensorflow. js. Модель генератора должна быть способна создавать изображения рукописных цифр, аналогичные образцам данных MNIST.
Мой код выполняется без ошибок, но я столкнулся с двумя основными проблемами.
- Процесс обучения значительно медленнее, чем пример Python. Например, JS в браузере по сравнению с примером Python в Google Code Lab.
- Модель My Generator никогда не достигает точки, где она фактически генерирует рукописные числа.
Он учится до такой степени, что генерирует изображения в виде сетки, но, кажется, никогда не узнает многого из этого.
Я считаю, что модели являются Порт 1: 1. Вот мои модели.
// discriminator model
let dModel = tf.sequential();
const IMAGE_WIDTH = 28;
const IMAGE_HEIGHT = 28;
const IMAGE_CHANNELS = 1;
dModel.add(
tf.layers.conv2d({inputShape: [IMAGE_WIDTH, IMAGE_HEIGHT, IMAGE_CHANNELS], kernelSize: [5,5], filters: 64, strides: [2,2], activation: "relu",
kernelInitializer: "varianceScaling"
})
);
dModel.add(tf.layers.leakyReLU())
dModel.add(tf.layers.dropout(0.3))
dModel.add(
tf.layers.conv2d({kernelSize: [5,5], filters: 128, strides: [2,2],
activation: "relu", kernelInitializer: "varianceScaling"
})
);
dModel.add(tf.layers.leakyReLU())
dModel.add(tf.layers.dropout(0.3))
dModel.add(tf.layers.flatten());
const NUM_OUTPUT_CLASSES = 1;
dModel.add(tf.layers.dense({units: NUM_OUTPUT_CLASSES}))
// generator model
let gModel = tf.sequential();
gModel.add(tf.layers.dense({units: 7 * 7 * 256,inputShape: [100], useBias: false}));
gModel.add(tf.layers.batchNormalization());
gModel.add(tf.layers.leakyReLU());
gModel.add(tf.layers.reshape({ targetShape: [7, 7, 256] }));
gModel.add(tf.layers.conv2dTranspose({filters: 128, kernelSize: [5, 5], strides: [1, 1], useBias: false, padding: "same"}));
gModel.add(tf.layers.batchNormalization());
gModel.add(tf.layers.leakyReLU());
gModel.add(tf.layers.conv2dTranspose({filters: 64, kernelSize: [5, 5], strides: [2, 2], useBias: false,padding: "same" }));
gModel.add(tf.layers.batchNormalization());
gModel.add(tf.layers.leakyReLU());
gModel.add(tf.layers.conv2dTranspose({filters: 1,kernelSize: [5, 5], strides: [2, 2], useBias: false,padding: "same", activation: "tanh" }));
Функции потерь - это то, где я не могу найти JS, эквивалентный градиентной ленте, поэтому я разработал их немного по-другому.
Пример Python использует:
cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)
def discriminator_loss(real_output, fake_output):
real_loss = cross_entropy(tf.ones_like(real_output), real_output)
fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)
total_loss = real_loss + fake_loss
return total_loss
def generator_loss(fake_output):
return cross_entropy(tf.ones_like(fake_output), fake_output)
def train_step(images):
noise = tf.random.normal([BATCH_SIZE, noise_dim])
with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
generated_images = generator(noise, training=True)
real_output = discriminator(images, training=True)
fake_output = discriminator(generated_images, training=True)
gen_loss = generator_loss(fake_output)
disc_loss = discriminator_loss(real_output, fake_output)
gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)
gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)
generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))
discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))
Где, как я использовал optimizer.minimize. Я не уверен, что это может привести к переобучению дискриминатора и вызвать проблему.
Несмотря на то, что это похоже на повторяющиеся вызовы model.predict внутри функций потерь, я сделал это так, иначе я получил ошибку Please make sure the operations that use variables are inside the function f passed to minimize()
function trainStep() {
const noise = tf.randomNormal([BATCH_SIZE, 100])
const fakeLabels = tf.ones([BATCH_SIZE], 'int32')
const realLabels = tf.zeros([BATCH_SIZE], 'int32')
const dLossCalc = () => {
const fakeImages = gModel.predict(noise).add(1).div(2)
let realImages = data.nextTrainBatch(BATCH_SIZE).xs
realImages = realImages.reshape([BATCH_SIZE, IMAGE_WIDTH, IMAGE_HEIGHT, 1])
realImages = realImages.sub(127.5).div(127.5) //normalize to 1, -1
const fakeLogits = dModel.predict(fakeImages).reshape([BATCH_SIZE])
const realLogits = dModel.predict(realImages).reshape([BATCH_SIZE])
const fakeLoss = tf.losses.sigmoidCrossEntropy(fakeLabels.mul(0.98), fakeLogits)
const realLoss = tf.losses.sigmoidCrossEntropy(realLabels, realLogits)
const totalLoss = fakeLoss.add(realLoss)
console.log('Disc Loss ' + totalLoss.dataSync())
return totalLoss
}
const gLossCalc = () => {
const fakeImages = gModel.predict(noise).add(1).div(2)
const logits = dModel.predict(fakeImages).reshape([BATCH_SIZE])
const loss = tf.losses.sigmoidCrossEntropy(fakeLabels, logits)
console.log('Gen Loss ' + loss.dataSync())
return loss
}
dOptimizer.minimize(dLossCalc)
gOptimizer.minimize(gLossCalc)
}
На данный момент я потратил часы и был бы признателен за любую помощь.
Две основные вещи, для которых я не мог найти эквивалент, были градиент Градиенты Tape / Apply и функция потери tf.keras.losses.BinaryCrossentropy. Вместо этого я использую sigmoidCrossEntropy.
Вот полностью рабочий пример codepen, если кто-то хочет посмотреть: https://codepen.io/freeman-g/pen/KKpRyyX?editors=0010
В качестве примечания, я заметил, что applyGradients не задокументировано в документе Tensorflow. js API и открыло связанную проблему GitHub: https://github.com/tensorflow/tfjs/issues/2897