web-dev-qa-db-ja.com

Tensorflow 2.0を使用したMNISTでのカスタムニューラルネットワークの実装?

*TensorFlow 2.0 beta* を使用して、MNISTデータセットに2つの非表示レイヤーを持つ基本的なニューラルネットワークのカスタム実装を書き込もうとしましたが、ここで何がうまくいかなかったのかわかりませんトレーニングロス精度はそれぞれ1.585で動かなくなっているようです。しかし、Kerasを使用してビルドすると、トレーニングの損失と精度が95%を超えて非常に低くなりました8-1エポックだけです。

私は自分の体重などを更新していないと思いますか?それで、私はbackprop関数で計算した私の新しい重みをそれぞれの重み/バイアス変数に割り当てる必要がありますか?

誰かがこれと私が以下に述べたこれらのいくつかのさらなる質問で私を助けてくれるなら本当に感謝しています。

その他の質問

1)このカスタム実装にDropoutおよびBatch Normalizationレイヤーを追加する方法は? (i.eトレーニング時間とテスト時間の両方で機能させる)

2)このコードでcallbacksを使用するにはどうすればよいですか?つまり(EarlyStoppingとModelCheckpointコールバックを利用する)

3)tensorflow 2.x @ tf.functionデコレータなどを使用するなど、このコードでさらに最適化できる何か他のコードがありますか?)

4)また、その分布をプロットおよびチェックするために取得した最終的な重みを抽出する必要があります。勾配の消失や爆発などの問題を調査するため。 (例:多分Tensorboard)

5)このコードに基づいてConvNets(つまり、Conv、MaxPoolなど)のような他のネットワークを簡単に実装できるように、このコードをより一般化された方法で記述できるようにしたいと思っています。

これが簡単な再現性のための私の完全なコードです

注:私はKerasのような高レベルのAPIを使用してモデルをはるかに簡単に構築できることを知っていますが、これは私の目標ではありません。理解してください。

import numpy as np
import os
import logging
logging.getLogger('tensorflow').setLevel(logging.ERROR)
import tensorflow as tf
import tensorflow_datasets as tfds

(x_train, y_train), (x_test, y_test) = tfds.load('mnist', split=['train', 'test'], 
                                                  batch_size=-1, as_supervised=True)

# reshaping
x_train = tf.reshape(x_train, shape=(x_train.shape[0], 784))
x_test  = tf.reshape(x_test, shape=(x_test.shape[0], 784))

ds_train = tf.data.Dataset.from_tensor_slices((x_train, y_train))
# rescaling
ds_train = ds_train.map(lambda x, y: (tf.cast(x, tf.float32)/255.0, y))

class Model(object):
    def __init__(self, hidden1_size, hidden2_size, device=None):
        # layer sizes along with input and output
        self.input_size, self.output_size, self.device = 784, 10, device
        self.hidden1_size, self.hidden2_size = hidden1_size, hidden2_size
        self.lr_rate = 1e-03

        # weights initializationg
        self.glorot_init = tf.initializers.glorot_uniform(seed=42)
        # weights b/w input to hidden1 --> 1
        self.w_h1 = tf.Variable(self.glorot_init((self.input_size, self.hidden1_size)))
        # weights b/w hidden1 to hidden2 ---> 2
        self.w_h2 = tf.Variable(self.glorot_init((self.hidden1_size, self.hidden2_size)))
        # weights b/w hidden2 to output ---> 3
        self.w_out = tf.Variable(self.glorot_init((self.hidden2_size, self.output_size)))

        # bias initialization
        self.b1 = tf.Variable(self.glorot_init((self.hidden1_size,)))
        self.b2 = tf.Variable(self.glorot_init((self.hidden2_size,)))
        self.b_out = tf.Variable(self.glorot_init((self.output_size,)))

        self.variables = [self.w_h1, self.b1, self.w_h2, self.b2, self.w_out, self.b_out]


    def feed_forward(self, x):
        if self.device is not None:
            with tf.device('gpu:0' if self.device=='gpu' else 'cpu'):
                # layer1
                self.layer1 = tf.nn.sigmoid(tf.add(tf.matmul(x, self.w_h1), self.b1))
                # layer2
                self.layer2 = tf.nn.sigmoid(tf.add(tf.matmul(self.layer1,
                                                             self.w_h2), self.b2))
                # output layer
                self.output = tf.nn.softmax(tf.add(tf.matmul(self.layer2,
                                                             self.w_out), self.b_out))
        return self.output

    def loss_fn(self, y_pred, y_true):
        self.loss = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y_true, 
                                                                  logits=y_pred)
        return tf.reduce_mean(self.loss)

    def acc_fn(self, y_pred, y_true):
        y_pred = tf.cast(tf.argmax(y_pred, axis=1), tf.int32)
        y_true = tf.cast(y_true, tf.int32)
        predictions = tf.cast(tf.equal(y_true, y_pred), tf.float32)
        return tf.reduce_mean(predictions)

    def backward_prop(self, batch_xs, batch_ys):
        optimizer = tf.keras.optimizers.Adam(learning_rate=self.lr_rate)
        with tf.GradientTape() as tape:
            predicted = self.feed_forward(batch_xs)
            step_loss = self.loss_fn(predicted, batch_ys)
        grads = tape.gradient(step_loss, self.variables)
        optimizer.apply_gradients(Zip(grads, self.variables))

n_shape = x_train.shape[0]
epochs = 20
batch_size = 128

ds_train = ds_train.repeat().shuffle(n_shape).batch(batch_size).prefetch(batch_size)

neural_net = Model(512, 256, 'gpu')

for Epoch in range(epochs):
    no_steps = n_shape//batch_size
    avg_loss = 0.
    avg_acc = 0.
    for (batch_xs, batch_ys) in ds_train.take(no_steps):
        preds = neural_net.feed_forward(batch_xs)
        avg_loss += float(neural_net.loss_fn(preds, batch_ys)/no_steps) 
        avg_acc += float(neural_net.acc_fn(preds, batch_ys) /no_steps)
        neural_net.backward_prop(batch_xs, batch_ys)
    print(f'Epoch: {Epoch}, Training Loss: {avg_loss}, Training ACC: {avg_acc}')

# output for 10 epochs:
Epoch: 0, Training Loss: 1.7005115111824125, Training ACC: 0.7603832868262543
Epoch: 1, Training Loss: 1.6052448933478445, Training ACC: 0.8524806404020637
Epoch: 2, Training Loss: 1.5905528008006513, Training ACC: 0.8664196092868224
Epoch: 3, Training Loss: 1.584107405738905, Training ACC: 0.8727630912326276
Epoch: 4, Training Loss: 1.5792385798413306, Training ACC: 0.8773203844903037
Epoch: 5, Training Loss: 1.5759121985174716, Training ACC: 0.8804754322627559
Epoch: 6, Training Loss: 1.5739163148682564, Training ACC: 0.8826455712551251
Epoch: 7, Training Loss: 1.5722616605926305, Training ACC: 0.8840812018606812
Epoch: 8, Training Loss: 1.569699136307463, Training ACC: 0.8867688354803249
Epoch: 9, Training Loss: 1.5679460542742163, Training ACC: 0.8885049475356936
18
user_6396

私はあなたの複数の質問からどこから始めるべきか疑問に思いました、そして私はステートメントでそうすることに決めました:

あなたのコードは間違いなくそのように見えるべきではなく、現在のTensorflowのベストプラクティスに近いものではありません

申し訳ありませんが、それを段階的にデバッグすることは全員の時間の浪費であり、私たちのどちらにも利益をもたらすことはありません。

次に、3番目のポイントに移動します。

3)以下のコードには、tensorflow 2.x @ tf.functionデコレータなどを使用するなど、このコードでさらに最適化できるものはありますか?)

はい、_tensorflow2.0_機能を使用でき、それらから逃げているようです(_tf.function_デコレータは実際にはここでは役に立たないので、とりあえずそのままにしておきます)。

新しいガイドラインに従うことで、5番目のポイントに関する問題も緩和されます。

5)このコードに基づいてConvNets(つまり、Conv、MaxPoolなど)のような他のネットワークを簡単に実装できるように、このコードをより一般化された方法で記述できるようにしたいと思っています。

それがそのために特別に設計されているので。少し紹介した後、いくつかの手順でそれらの概念を紹介します。

1.プログラムを論理的な部分に分割する

コードの読みやすさに関しては、Tensorflowは多くの害を及ぼしました。 _tf1.x_のすべてが通常1か所でクランチされ、グローバルの後に関数定義が続き、別のグローバルまたはデータの読み込みが続き、すべてが混乱していました。システムの設計がそれらのアクションを奨励したので、それは実際には開発者の責任ではありません。

現在、_tf2.0_では、pytorchchainer、およびその他のユーザーフレンドリーなフレームワークで確認できる構造と同様に、プログラマーが作業を分割することが推奨されています。

1.1データの読み込み

あなたは Tensorflow Datasets で良い方向に進んでいましたが、明確な理由もなく背を向けました。

これは、何が起こっているのかを解説したコードです:

_# You already have tf.data.Dataset objects after load
(x_train, y_train), (x_test, y_test) = tfds.load('mnist', split=['train', 'test'], 
                                                  batch_size=-1, as_supervised=True)

# But you are reshaping them in a strange manner...
x_train = tf.reshape(x_train, shape=(x_train.shape[0], 784))
x_test  = tf.reshape(x_test, shape=(x_test.shape[0], 784))

# And building from slices...
ds_train = tf.data.Dataset.from_tensor_slices((x_train, y_train))
# Unreadable rescaling (there are built-ins for that)
_

このアイデアを任意のデータセットに対して簡単に一般化し、これを別のモジュールに配置して、_datasets.py_と言います。

_import tensorflow as tf
import tensorflow_datasets as tfds


class ImageDatasetCreator:
    @classmethod
    # More portable and readable than dividing by 255
    def _convert_image_dtype(cls, dataset):
        return dataset.map(
            lambda image, label: (
                tf.image.convert_image_dtype(image, tf.float32),
                label,
            )
        )

    def __init__(self, name: str, batch: int, cache: bool = True, split=None):
        # Load dataset, every dataset has default train, test split
        dataset = tfds.load(name, as_supervised=True, split=split)
        # Convert to float range
        try:
            self.train = ImageDatasetCreator._convert_image_dtype(dataset["train"])
            self.test = ImageDatasetCreator._convert_image_dtype(dataset["test"])
        except KeyError as exception:
            raise ValueError(
                f"Dataset {name} does not have train and test, write your own custom dataset handler."
            ) from exception

        if cache:
            self.train = self.train.cache()  # speed things up considerably
            self.test = self.test.cache()

        self.batch: int = batch

    def get_train(self):
        return self.train.shuffle().batch(self.batch).repeat()

    def get_test(self):
        return self.test.batch(self.batch).repeat()
_

したがって、簡単なコマンドを使用してmnist以外のものをロードできます。

_from datasets import ImageDatasetCreator

if __name__ == "__main__":
    dataloader = ImageDatasetCreator("mnist", batch=64, cache = True)
    train, test = dataloader.get_train(), dataloader.get_test()
_

そして、今後データセットをロードするmnist以外の名前を使用することもできます。

ディープラーニングに関連するすべての1つのハンドオフスクリプトを作成するのはやめてください。あなたもプログラマーです

1.2モデルの作成

_tf2.0_なので、モデルの複雑さに応じて次の2つの推奨される方法があります。

  • _tensorflow.keras.models.Sequential_-この方法は @ Stewart_R によって示され、彼のポイントを繰り返す必要はありません。最も単純なモデルに使用されます(フィードフォワードでこれを使用する必要があります)。
  • _tensorflow.keras.Model_を継承し、カスタムモデルを作成します。これは、モジュール内に何らかのロジックがある場合、またはそれがより複雑な場合(ResNet、マルチパスネットワークなど)に使用する必要があります。全体として、より読みやすくカスタマイズ可能です。

あなたのModelクラスは、そのようなものに似たものにしようとしましたが、再び南向きになりました。 backpropはモデル自体の一部ではなく、lossaccuracyでもありませんそれらを別のモジュールまたは関数に分離し、メンバーではありません!

つまり、2番目のアプローチを使用してネットワークをコーディングしましょう(簡潔にするために、このコードは_model.py_に配置する必要があります)。その前に、_tf.keras.Layers_から継承してYourDenseフィードフォワードレイヤーを最初からコーディングします(これは_layers.py_モジュールに入る可能性があります):

_import tensorflow as tf

class YourDense(tf.keras.layers.Layer):
    def __init__(self, units):
        # It's Python 3, you don't have to specify super parents explicitly
        super().__init__()
        self.units = units

    # Use build to create variables, as shape can be inferred from previous layers
    # If you were to create layers in __init__, one would have to provide input_shape
    # (same as it occurs in PyTorch for example)
    def build(self, input_shape):
        # You could use different initializers here as well
        self.kernel = self.add_weight(
            shape=(input_shape[-1], self.units),
            initializer="random_normal",
            trainable=True,
        )
        # You could define bias in __init__ as well as it's not input dependent
        self.bias = self.add_weight(shape=(self.units,), initializer="random_normal")
        # Oh, trainable=True is default

    def call(self, inputs):
        # Use overloaded operators instead of tf.add, better readability
        return tf.matmul(inputs, self.kernel) + self.bias
_

あなたについて

1)このカスタム実装にドロップアウトおよびバッチ正規化レイヤーを追加するにはどうすればよいですか? (つまり、トレーニング時間とテスト時間の両方で機能させる)

これらのレイヤーのカスタム実装を作成したいと思います。そうでない場合は、_from tensorflow.keras.layers import Dropout_をインポートして、 @ Leevo で指摘されているように、どこでも使用できます。以下のtrainおよびtestでの動作が異なる反転ドロップアウト:

_class CustomDropout(layers.Layer):
    def __init__(self, rate, **kwargs):
        super().__init__(**kwargs)
        self.rate = rate

    def call(self, inputs, training=None):
        if training:
            # You could simply create binary mask and multiply here
            return tf.nn.dropout(inputs, rate=self.rate)
        # You would need to multiply by dropout rate if you were to do that
        return self.rate * inputs
_

取られたレイヤー ここから およびショーケースの目的により適合するように変更されました。

これで、最終的にモデルを作成できます(単純なダブルフィードフォワード)。

_import tensorflow as tf

from layers import YourDense


class Model(tf.keras.Model):
    def __init__(self):
        super().__init__()
        # Use Sequential here for readability
        self.network = tf.keras.Sequential(
            [YourDense(100), tf.keras.layers.ReLU(), YourDense(10)]
        )

    def call(self, inputs):
        # You can use non-parametric layers inside call as well
        flattened = tf.keras.layers.Flatten()(inputs)
        return self.network(flattened)
_

多くの場合、一般的な実装では組み込みをできるだけ使用する必要があります。

この構造はかなり拡張可能であるため、このモジュールを介して実行する必要があるものは何でも、畳み込みネット、resnet、senetに一般化できます。あなたはそれについてもっと読むことができます ここ

それはあなたの5番目の点を満たしていると思います:

5)このコードに基づいてConvNets(つまり、Conv、MaxPoolなど)のような他のネットワークを簡単に実装できるように、このコードをより一般化された方法で記述できるようにしたいと思っています。

最後に、モデルのグラフを作成するためにmodel.build(shape)を使用する必要がある場合があります。

_model.build((None, 28, 28, 1))
_

これは、MNISTの_28x28x1_入力形状の場合です。ここで、Noneはバッチを表します。

1.3トレーニング

ここでも、トレーニングは次の2つの方法で行うことができます。

  • 標準Keras model.fit(dataset)-分類などの単純なタスクで役立ちます
  • _tf.GradientTape_-より複雑なトレーニングスキーム、最も顕著な例は Generative Adversarial Networks で、2つのモデルが直交を最適化しますminmaxゲームの目標

@ Leevo で再度指摘したように、2番目の方法を使用する場合、Kerasが提供するコールバックを単純に使用することはできないため、最初のオプションを使用することをお勧めしますいつでも可能なとき。

理論的には、コールバックの関数をon_batch_begin()などの必要に応じて手動で呼び出すことができますが、面倒で、これがどのように機能するかわかりません。

最初のオプションに関しては、_tf.data.Dataset_オブジェクトを直接fitで使用できます。これは、別のモジュール(できれば_train.py_)内に表示されています。

_def train(
    model: tf.keras.Model,
    path: str,
    train: tf.data.Dataset,
    epochs: int,
    steps_per_Epoch: int,
    validation: tf.data.Dataset,
    steps_per_validation: int,
    stopping_epochs: int,
    optimizer=tf.optimizers.Adam(),
):
    model.compile(
        optimizer=optimizer,
        # I used logits as output from the last layer, hence this
        loss=tf.losses.SparseCategoricalCrossentropy(from_logits=True),
        metrics=[tf.metrics.SparseCategoricalAccuracy()],
    )

    model.fit(
        train,
        epochs=epochs,
        steps_per_Epoch=steps_per_Epoch,
        validation_data=validation,
        validation_steps=steps_per_validation,
        callbacks=[
            # Tensorboard logging
            tf.keras.callbacks.TensorBoard(
                pathlib.Path("logs")
                / pathlib.Path(datetime.datetime.now().strftime("%Y%m%d-%H%M%S")),
                histogram_freq=1,
            ),
            # Early stopping with best weights preserving
            tf.keras.callbacks.EarlyStopping(
                monitor="val_sparse_categorical_accuracy",
                patience=stopping_epochs,
                restore_best_weights=True,
            ),
        ],
    )
    model.save(path)
_

より複雑なアプローチは、PyTorchトレーニングループと非常によく似ており(ほとんどコピーアンドペースト)、それらに慣れていれば、それほど問題にはなりません。

_tf2.0_のドキュメント全体で例を見つけることができます。 ここ または ここ

2.その他

2.1未回答の質問

4)このコードでさらに最適化できる他のコードはありますか?つまり(tensorflow 2.x @ tf.functionデコレータなどを利用する)

上記はすでにモデルをグラフに変換しているので、この場合にそれを呼び出してもメリットはないと思います。そして、時期尚早な最適化はすべての悪の根源です。これを行う前にコードを測定することを忘れないでください。

データの適切なキャッシュ(#1.1の冒頭で説明)と適切なパイプラインを使用すれば、それらよりもはるかに多くを得ることができます。

5)また、トレーニング後にすべてのレイヤーのすべての最終的な重みを抽出して、プロットして分布を確認できるようにする方法も必要です。グラデーションの消失や爆発などの問題をチェックする。

上記の @ Leevo で指摘されているように、

_weights = model.get_weights()
_

あなたの重みを取得します。それらを_np.array_に変換し、seabornmatplotlib、analyze、checkまたはその他の必要なものを使用してプロットできます。

2.2まとめると

全体として、あなたの_main.py_(またはエントリポイントまたはそれに似たもの)は、これで構成されます(多かれ少なかれ)。

_from dataset import ImageDatasetCreator
from model import Model
from train import train

# You could use argparse for things like batch, epochs etc.
if __name__ == "__main__":
    dataloader = ImageDatasetCreator("mnist", batch=64, cache=True)
    train, test = dataloader.get_train(), dataloader.get_test()
    model = Model()
    model.build((None, 28, 28, 1))
    train(
        model, train, path epochs, test, len(train) // batch, len(test) // batch, ...
    )  # provide necessary arguments appropriately
    # Do whatever you want with those
    weights = model.get_weights()
_

ああ、上記の機能はコピー貼り付け用ではなく、ガイドラインのように扱う必要があることを覚えておいてください。質問があれば私を殴ってください。

3.コメントからの質問

3.1カスタムレイヤーと組み込みレイヤーを初期化する方法

3.1.1 TLDRについて

  • カスタムポアソン初期化関数、ただし3つの引数が必要
  • _tf.keras.initalization_ APIには2つの引数が必要です(最後の点 ドキュメントで を参照)。したがって、1つはPythonのlambdaで指定されます以前に作成したカスタムレイヤー内
  • レイヤーのオプションのバイアスが追加され、ブール値でオフにすることができます

なぜそれほど役に立たないのですか? _tf2.0_で最終的にPythonの機能を使用できることを示すため、グラフの煩わしさはなく、_tf.cond_の代わりにifなど.

3.1.2 TLDRから実装まで

Keras初期化子は here とTensorflowのフレーバー here にあります。

特に_tf2.0_では、APIの不整合(クラスのような大文字、アンダースコアのような小文字)に注意してください。ただし、それは重要ではありません。

文字列を渡すことで(上記のYourDenseで行ったように)、またはオブジェクトの作成中にそれらを使用できます。

カスタムレイヤーでカスタム初期化を可能にするには、コンストラクターに引数を追加するだけです(_tf.keras.Model_クラスはまだPythonクラスであり、___init___も同じように使用する必要があります) Pythonのものとして)。

その前に、カスタム初期化を作成する方法を説明します。

_# Poisson custom initialization because why not.
def my_dumb_init(shape, lam, dtype=None):
    return tf.squeeze(tf.random.poisson(shape, lam, dtype=dtype))
_

署名は3つの引数を取りますが、_(shape, dtype)_のみを取る必要があります。それでも、次のような(拡張されたYourLinear)のように、独自のレイヤーを作成しながら、これを簡単に「修正」できます。

_import typing

import tensorflow as tf


class YourDense(tf.keras.layers.Layer):
    # It's still Python, use it as Python, that's the point of tf.2.0
    @classmethod
    def register_initialization(cls, initializer):
        # Set defaults if init not provided by user
        if initializer is None:
            # let's make the signature proper for init in tf.keras
            return lambda shape, dtype: my_dumb_init(shape, 1, dtype)
        return initializer

    def __init__(
        self,
        units: int,
        bias: bool = True,
        # can be string or callable, some typing info added as well...
        kernel_initializer: typing.Union[str, typing.Callable] = None,
        bias_initializer: typing.Union[str, typing.Callable] = None,
    ):
        super().__init__()
        self.units: int = units
        self.kernel_initializer = YourDense.register_initialization(kernel_initializer)
        if bias:
            self.bias_initializer = YourDense.register_initialization(bias_initializer)
        else:
            self.bias_initializer = None

    def build(self, input_shape):
        # Simply pass your init here
        self.kernel = self.add_weight(
            shape=(input_shape[-1], self.units),
            initializer=self.kernel_initializer,
            trainable=True,
        )
        if self.bias_initializer is not None:
            self.bias = self.add_weight(
                shape=(self.units,), initializer=self.bias_initializer
            )
        else:
            self.bias = None

    def call(self, inputs):
        weights = tf.matmul(inputs, self.kernel)
        if self.bias is not None:
            return weights + self.bias
_

_my_dumb_initialization_をデフォルトとして追加し(ユーザーが提供しない場合)、bias引数を使用してバイアスをオプションにしました。データに依存しない限り、ifを自由に使用できます。それが(または何らかの形で_tf.Tensor_に依存している)場合は、_@tf.function_デコレータを使用して、Pythonのフローをtensorflowの対応するものに変更する必要があります(例:ifから_tf.cond_)。

サインの詳細については こちら を参照してください。

上記のイニシャライザの変更をモデルに組み込む場合は、適切なオブジェクトを作成する必要があります。それだけです。

_... # Previous of code Model here
self.network = tf.keras.Sequential(
    [
        YourDense(100, bias=False, kernel_initializer="lecun_uniform"),
        tf.keras.layers.ReLU(),
        YourDense(10, bias_initializer=tf.initializers.Ones()),
    ]
)
... # and the same afterwards
_

組み込みの_tf.keras.layers.Dense_レイヤーを使用すると、同じことができます(引数名は異なりますが、考え方は変わりません)。

3.2 _tf.GradientTape_を使用した自動微分

3.2.1はじめに

_tf.GradientTape_のポイントは、ユーザーに別の変数に対する変数の通常のPython制御フローおよび勾配計算を許可することです。

here からの例ですが、個別の部分に分割されています。

_def f(x, y):
  output = 1.0
  for i in range(y):
    if i > 1 and i < 5:
      output = tf.multiply(output, x)
  return output
_

通常python forおよびifフロー制御ステートメントを含む関数

_def grad(x, y):
  with tf.GradientTape() as t:
    t.watch(x)
    out = f(x, y)
  return t.gradient(out, x)
_

グラディエントテープを使用すると、Tensorsのすべての操作(およびそれらの中間状態も)を記録し、それを逆方向に「再生」できます(追跡規則を使用して自動逆方向微分を実行します)。

tf.GradientTape()コンテキストマネージャ内のすべてのTensorは自動的に記録されます。一部のTensorがスコープ外の場合は、上記のようにwatch()メソッドを使用します。

最後に、outputに対するxの勾配(入力が返されます)。

3.2.2ディープラーニングとの関係

上記で説明したのはbackpropagationアルゴリズムです。勾配w.r.t(に関して)出力は、ネットワーク内の各ノードに対して(またはすべての層に対して)計算されます。これらの勾配は、さまざまなオプティマイザによって修正を行うために使用され、繰り返されます。

続けて、_tf.keras.Model_、オプティマイザインスタンス、_tf.data.Dataset_、および損失関数がすでに設定されていると仮定します。

トレーニングを実行するTrainerクラスを定義できます。 疑問がある場合は、コード内のコメントを読んでください

_class Trainer:
    def __init__(self, model, optimizer, loss_function):
        self.model = model
        self.loss_function = loss_function
        self.optimizer = optimizer
        # You could pass custom metrics in constructor
        # and adjust train_step and test_step accordingly
        self.train_loss = tf.keras.metrics.Mean(name="train_loss")
        self.test_loss = tf.keras.metrics.Mean(name="train_loss")

    def train_step(self, x, y):
        # Setup tape
        with tf.GradientTape() as tape:
            # Get current predictions of network
            y_pred = self.model(x)
            # Calculate loss generated by predictions
            loss = self.loss_function(y, y_pred)
        # Get gradients of loss w.r.t. EVERY trainable variable (iterable returned)
        gradients = tape.gradient(loss, self.model.trainable_variables)
        # Change trainable variable values according to gradient by applying optimizer policy
        self.optimizer.apply_gradients(Zip(gradients, self.model.trainable_variables))
        # Record loss of current step
        self.train_loss(loss)

    def train(self, dataset):
        # For N epochs iterate over dataset and perform train steps each time
        for x, y in dataset:
            self.train_step(x, y)

    def test_step(self, x, y):
        # Record test loss separately
        self.test_loss(self.loss_function(y, self.model(x)))

    def test(self, dataset):
        # Iterate over whole dataset
        for x, y in dataset:
            self.test_step(x, y)

    def __str__(self):
        # You need Python 3.7 with f-string support
        # Just return metrics
        return f"Loss: {self.train_loss.result()}, Test Loss: {self.test_loss.result()}"
_

これで、このクラスをコードで実際に次のように使用できます。

_EPOCHS = 5

# model, optimizer, loss defined beforehand
trainer = Trainer(model, optimizer, loss)
for _ in range(EPOCHS):
    trainer.train(train_dataset) # Same for training and test datasets
    trainer.test(test_dataset)
    print(f"Epoch {Epoch}: {trainer})")
_

印刷では、各エポックのトレーニングとテストの損失がわかります。トレーニングとテストを好きなように組み合わせることができます(たとえば、トレーニングに5エポックと1つのテスト)。さまざまな指標を追加できます。

非OOP指向のアプローチが必要な場合は こちら を参照してください(IMOは読みにくいですが、それぞれ独自のものです)。

20
Szymon Maszke

また、コードで改善できることがあれば、私にも知らせてください。

このようなもののための高レベルAPIを採用します。ほんの数行のコードでそれを行うことができ、デバッグ、読み取り、および推論がはるかに簡単です。

(x_train, y_train), (x_test, y_test) = tfds.load('mnist', split=['train', 'test'], 
                                                  batch_size=-1, as_supervised=True)

x_train = tf.cast(tf.reshape(x_train, shape=(x_train.shape[0], 784)), tf.float32)
x_test  = tf.cast(tf.reshape(x_test, shape=(x_test.shape[0], 784)), tf.float32)

model = tf.keras.models.Sequential([
  tf.keras.layers.Dense(512, activation='sigmoid'),
  tf.keras.layers.Dense(256, activation='sigmoid'),
  tf.keras.layers.Dense(10, activation='softmax')
])
model.fit(x_train, y_train, epochs=5)
model.evaluate(x_test, y_test)
5
Stewart_R

Tensorflow 2.0ベータ版を使用して、MNISTデータセットに2つの非表示レイヤーを持つ基本的なニューラルネットワークのカスタム実装を書き込もうとしましたが、ここで何が問題だったかわかりませんが、トレーニングの損失と精度はそれぞれ1.5と85前後で止まっているようです。

トレーニング部分はどこですか? TF 2.0のトレーニングは、Kerasの構文またはtf.GradientTape()を使用したEager executionのいずれかをモデル化します。コードをconvレイヤーと高密度レイヤーで貼り付けることはできますか?また、どのようにトレーニングしたのですか?


その他の質問:

1)このカスタム実装にドロップアウトレイヤーを追加するにはどうすればよいですか?つまり(トレーニング時間とテスト時間の両方で機能させる)

Dropout()レイヤーを追加するには:

from tensorflow.keras.layers import Dropout

そして、あなたはそれをSequential()モデルに挿入します:

Dropout(dprob)     # where dprob = dropout probability

2)このコードにバッチ正規化を追加するにはどうすればよいですか?

以前と同じで、

from tensorflow.keras.layers import BatchNormalization

ここでを選択して、モデルにbatchnormを入れるかどうかは、あなた次第です。経験則はありません。実験することをお勧めします。 MLでは、それは常に試行錯誤のプロセスです。


3)このコードでコールバックを使用するにはどうすればよいですか?つまり(EarlyStoppingとModelCheckpointコールバックを利用する)

Kerasの構文を使用してトレーニングしている場合は、単純にそれを使用できます。使い方はこちら 非常に徹底したチュートリアル をご覧ください。わずか数行のコードが必要です。 Eager executionでモデルを実行している場合は、独自のコードを使用して、これらの手法を自分で実装する必要があります。より複雑ですが、実装の自由度も高くなります。


4)このコードでさらに最適化できる他のコードはありますか?つまり(tensorflow 2.x @ tf.functionデコレータなどを利用する)

場合によります。 Keras構文を使用している場合、それを追加する必要はないと思います。 Eager実行でモデルをトレーニングしている場合は、いくつかの関数で@tf.functionデコレーターを使用して少し高速化することをお勧めします。 this Notebook でデコレータを使用する方法に関する実用的なTF 2.0の例を見ることができます。

これ以外に、重みの初期化、L1-L2損失などの正規化手法を試すことをお勧めします。


5)また、トレーニング後にすべてのレイヤーのすべての最終的な重みを抽出して、プロットして分布を確認できるようにする方法も必要です。グラデーションの消失や爆発などの問題をチェックする。

モデルがトレーニングされると、次のようにしてその重みを抽出できます。

weights = model.get_weights()

または:

weights = model.trainable_weights

トレーニング可能なものだけを保持したい場合。


6)このコードに基づいて畳み込みネットワーク(Conv、MaxPoolなど)のような他のネットワークを簡単に実装できるように、このコードをより一般化された方法で記述できるようにしたいと思っています。

すべてのコードを関数にパックしてから、 このNotebook の終わりに、私はこのようなことをしました(これはフィードフォワードNNのためのものです。これははるかに単純ですが、それは始まりであり、必要に応じてコードを変更できます)。

---

[〜#〜]更新[〜#〜]

私の CNN分類器のTensorFlow 2.0実装 を確認してください。これは有用なヒントとなる場合があります。Fashion MNISTデータセットでトレーニングされるため、タスクに非常に似ています。

3
Leevo