6
9

More than 3 years have passed since last update.

DeepLearning with Pythonを読んで得た学びを残しておく

Last updated at Posted at 2020-11-28

はじめに

Deep Learning with PythonのEnglish verを読んで学びがあった箇所を自分のために書き残します。

途中、Noteとある箇所は私のメモになります。

もし私以外の誰かの参考にもなれば嬉しいです。

文章のですます調や用語の統一ができていないところが多々ありますが、私のメモだと思ってご容赦ください。

次元の考え方

ベクトルも次元
軸の数(ランク)も次元

損失関数の使い分け

  • 2クラスの分類問題ではバイナリ・クロスエントロピー
  • 多クラスの分類問題ではカテゴリカル・クロスエントロピー
  • 回帰問題では平均二乗誤差
  • シーケンス学習問題ではコネクショニスト時間的分類(CTC)

確率を出力するモデルを扱う場合、通常はクロスエントロピーが最良の選択です。クロスエントロピーは,情報伝達理論の分野における量で,確率分布間の距離を測定するものです.

Kerasを使った機械学習の一連の流れ

1 トレーニングデータを定義する:入力テンソルと目標テンソル。
2 入力をターゲットにマッピングするレイヤーのネットワーク(またはモデル)を定義します。
3 損失関数、オプティマイザ、および監視するいくつかのメトリックを選択して学習プロセスを設定します。
4 モデルの fit() メソッドを呼び出して、学習データを反復します。

モデルを定義するには2つの方法があります:Sequentialクラスを使用する方法(最も一般的なネットワークアーキテクチャである線形スタックのレイヤーのみ)と、Func-tional APIを使用する方法(レイヤーの有向非周期グラフのため、完全にアービトラリーなアーキテクチャを構築することができます)です。

どちらの場合でもコンパイルステップ以降は同じです。

最適化手法

rmspropはオプティマイザとしてどのような問題でもフィットする良い選択肢。

Embedding

ラベルをone-hotでエンコーディングする場合はcategorical cross entropyでよいが、ndarrayとする場合はsparse_categorical_cross_entropyを使う。

ハイパーパラメータや活性化関数、損失関数に勘所ついて

  • 十分に大きな中間層を持つべき。最終出力が46次元だったら中間層の次元はそれより大きくすると表現力が増す。
  • 最終出力層に活性化関数を入れない場合、出力値の幅が制限されなくなる(Softmaxを入れると確率値として0-1に限定される)
  • 損失関数の平均2乗誤差と精度評価の平均絶対誤差(MAE)を区別すること。MAEは予測値と目標値の差の絶対値。
  • トレーニングデータが少ないときにはK-Fold交差検証を使うと良い。K-FoldはデータをK個に分割し、K-1個をトレーニングデータ、1個(パーティション)をバリデーションデータとして使う。最終的にはK個の検証スコアの平均をとる。通常、Kは4とか5個らしい。
  • 学習データが少ない場合、深刻なオーバーフィッティングを避けるために、隠れ層が少ない(通常は1つか2つだけ)小さなネットワークを使用することが好ましい。

Note:トレーニングデータとバリデーションデータ

トレーニングデータ・バリデーションデータ・テストデータの定義 https://datachemeng.com/trainingvalidationtestdatasets/

バリデーションデータは、回帰モデルやクラス分類モデルのハイパーパラメータを決めるためのデータです。たとえば部分的最小二乗回帰 (Partial Least Squares Regression, PLS) におけるハイパーパラメータは成分数です。成分数を 1, 2, 3, … と変えてモデルを構築し、それぞれのモデルでバリデーションデータの y の値を推定します。そして、yの実測値と推定値で計算された決定係数 r2 が最大となる成分数を選ぶわけです。

ハイパーパラメータを選んだ後は、トレーニングデータとバリデーションデータとを合わせてモデルを構築します。たとえばPLSのときは、選ばれた成分数で再び回帰モデルを構築するわけです。

ちなみに、バリデーションデータを用いずに、トレーニンデータのみからクロスバリデーションによりハイパーパラメータを選ぶ方法もあります。わたしは、ほとんどの場合において、クロスバリデーションでハイパーパラメータを選んでいます。

K-Foldは毎回、データをシャッフルして最終的なスコアは各実行で得られたスコアの平均を取る。

データを分割する際は、トレーニングデータとテストデータそれぞれがデータの代表性を持っていることが大事。
例えば1-9の数字画像を分類する際に、トレーニングデータは1-7のデータが、テストデータは8,9のデータがある場合はよい分け方ではない。逆に、時系列データのように並び順が重要な意味を持っている場合はデータをシャッフルしてはいけない。

前処理

  • ベクトル化

One-hotエンコーディングなど

  • 正規化

    • 均質で0~1の値を取るようにする
    • 平均0、標準偏差1になるようにする
    • x -= x.mean(axis=0)
    • x /= x.std(axis=0)

特徴量エンジニアリング

データやドメイン知識を生かして直に特徴量をハードコーディングすること。機械学習はとても重要だった。
もし使えるのであればリソースを削るためにも導入すべき。
オーバーフィッティングを防ぐためにネットワークを軽量化することも大切。ネットワークの軽量化とは、パラメータを削ること。パラメータが増えすぎると汎化能力が失われ、オーバーフィッティングしやすくなる。

正則化

複雑さに対するペナルティを与えることも重要。
L1正則化
L2正則化

※自分の経験上、L1を使うことはないと思われ。

# L2正則化
model = models.Sequential()
model.add(layer.Dense(16, kernel_regularizer=regularizers.l2(0.001), activation='relu', input_shape=(10000,))
model.add(layer.Dense(16, kernel_regularizer=regularizers.l2(0.001), activation='relu')
model.add(layer.Dense(1, activation='sigmoid')

regularizers.l2(0.001)は、レイヤーの重み行列の各係数がネットワークの総損失に0.001 * weight_coefficient_valueを加えることを意味します。このペナルティは学習時にのみ加算されるので、このネットワークの損失はテスト時よりも学習時の方がはるかに大きくなることに注意してください。

以下のような正則化も可能。
regularizers.l1(0.001) #L1
regularizers.l1_l2(l1=0.001, l2=0.001) #L1&L2

ドロップアウト

  • ゼロにする重みの割合は通常、0.2~0.5で設定される

学習時には,重み行列の値のうちの何分の一かの値をランダムに0にします.

  layer_output *= np.random.randint(0, high=2, size=layer_output.shape) #50%のパラメータが0になる

このとき、注意しなければいけないのは、トレーニング時に50%脱落させるのであれば、テスト時は、どのユニットもドロップアウトされず、代わりに出力値がドロップアウト率と同じ比率でスケールダウンされます。これは、訓練時に比べてたくさんのユニットがアクティブであることに対してバランスをとるためです。

  layer_output *= 0.5   #テスト時

Note:Kerasによる処理履歴の表示

Kerasで学習のHistoryを確認する方法は以下が参考になった。
Kerasでmodel学習のhistory結果をグラブ表示する方法 - Qiita https://qiita.com/rokusyou/items/16c2154096b8fb06cfe7

Kerasでは、ドロップアウトレイヤーを介してネットワークにドロップアウトを導入することができ、その直前のレイヤーの出力に適用されます。

model.add(layer.Dropout(0.5))

以下はニューラルネットワークの過学習を防ぐための最も一般的な方法です。

  • より多くのトレーニングデータを使う
  • ネットワークの容量を減らす
  • 重みの正規化を追加する
  • ドロップアウトを追加する

モデルの成功の尺度

すべてのクラスが等しく可能性が高いバランスの取れた分類問題では,精度と受信機動作特性曲線下面積(ROC AUC)が一般的な測定基準である.クラスが不均衡な問題では,precisionとrecallを使用することができる.

ランク付け問題やマルチラベル分類では、平均平均精度を使用することができる。また、成功を測定するための独自のカスタム・メトリックを定義する必要があることも珍しくありません。機械学習の成功指標の多様性と、それらが異なる問題領域にどのように関連しているかを知るには、Kaggle のデータサイエンスコンテスト(https://kaggle.com)を参照すると便利です。

評価方法の決定

データが豊富な場合→ホールドアウト
K-Fold CV→ データが少なくホールドアウトの結果が信用できない場合
K-Fold Iteration Validation → めっちゃ精度の高いモデルを少ないデータで作りたい時

優れたモデルの開発

  • 最後の層の活性化 - これは、ネットワークのアウトプットに有用な制約を確立します。例えば、IMDB分類の例では最後の層でシグモイドを使用し、回帰の例では最後の層の活性化を使用しませんでした。

  • 損失関数-これは、解決しようとしている問題のタイプと一致しなければなりません。例えば、IMDBの例ではbinary_crossentropyを使用し、回帰ではmseを使用しています。

    • 使い分けは以下のよう感じ。
    • バイナリ分類:シグモイド、バイナリクロスエントロピー マルチクラス、シングルラベル分類:ソフトマックス、カテゴリカルクロスエントロピー マルチクラス、マルチラベル分類:シグモイド、バイナリクロスエントロピー 任意の値への回帰:MSE 0と1の間の値への回帰:シグモイド、mse または binary_crossentropy
  • 最適化の設定-どのようなオプティマイザを使用しますか?その学習率は?ほとんどの場合、rmspropとそのデフォルトの学習率を使うのが安全です。

CNN

  • 全結合層は大域的なパターンを学習するのに対し、畳込み層は局所的な特徴を学習する。
  • 画像認識におけるフィルタのチャネルはそれぞれ特徴を示す。フィルタは入力データの特定の側面を符号化することができる。フィルタのサイズは3✕3または5✕5だが前者が主流。
  • 畳み込みやマックスプーリングによりダウンサンプリングすることは、 ・処理する特徴マップのパラメータ数を減らす ・空間フィルタ操作を誘導するため

Note:バッチサイズとは

ディープラーニングでは、損失関数を最小化して最適なパラメータ(重み、バイアス)を見つけるために勾配降下法と呼ばれる手法が使われます。
多くの場合、ミニバッチ勾配降下法というバッチ勾配降下法と確率的勾配降下法の間を取った手法が使われ、データセットを幾つかのサブセットに分ける必要があります(学習データとテストデータに分けるのとは別の話しです)。

そして、この幾つかに分けたぞれぞれのサブセットに含まれるデータの数をバッチサイズと呼びます。
例えば、1,000件のデータセットを200件ずつのサブセットに分ける場合、バッチサイズは200となります。
また、バッチサイズのことをミニバッチサイズと呼ぶこともあります。

バッチサイズは機械学習の分野の慣習1として2のn乗の値が使われることが多く、32, 64, 128, 256, 512, 1024, 2048辺りがよく使われる数値だと思います。
データセットの件数が数百件程度であれば32, 64をまずは試してみて、数万件程度であれば1024, 2048をまずは試して見るのが良いのではないでしょうか。
そして、学習がうまくいっておらず、他に調整するパラメータがなくなった時にバッチサイズを大きくしたり小さくしたりするのが良いと思います。

イテレーション数とは?
イテレーション数はデータセットに含まれるデータが少なくとも1回は学習に用いられるのに必要な学習回数であり、バッチサイズが決まれば自動的に決まる数値です。
先程の1,000件のデータセットを200件ずつのサブセットに分ける場合では、イテレーション数は5 (=1,000/200)となります。
つまり、学習を5回繰り返す(イテレートする)ということです。

エポック数とは?
データセットをバッチサイズに従ってN個のサブセットに分ける。
各サブセットを学習に回す。つまり、N回学習を繰り返す。
1と2の手順により、データセットに含まれるデータは少なくとも1回は学習に用いられることになります。
そして、この1と2の手順を1回実行することを1エポックと呼びます。
つまり、エポック数とは1と2の手順を何回実行するのかということです。

普通、1エポックで十分に学習されることはなく、数エポックから数十エポック回すことが多いです。
では、何回エポックを回せば十分なのでしょうか?

答えとしては、損失関数(コスト関数)の値がほぼ収束するまでです。
そして、データの性質や件数によって収束するまでに掛かる回数は異なるので、実際に幾つかの数値で試してみて損失関数の推移を見るしかありません。
また、ほぼ収束と書いたのは、完全に収束まで実行すると過学習になる恐れがあるためです。

Note:Imagedatageneratorとは

データの前処理を行うのに便利なモジュール
学習データの生成にも使える

基本的な使い方

import keras.preprocessing.image as Image
datagen = Image.ImageDataGenerator(
            featurewise_center = False,
            samplewise_center = False,
            featurewise_std_normalization = False,
            samplewise_std_normalization = False,
            zca_whitening = False,
            rotation_range = 30,
            width_shift_range = 0.3,
            height_shift_range = 0.3,
            horizontal_flip = True,
            vertical_flip = False,
            validation_split = 0.1
        )

# データを読み込む
train_generator = datagen.flow_from_directory(
            train_dir,
            target_size=(224,224),
            batch_size=batch_size,
            class_mode='categorical',
            shuffle=True,
            subset = "training" 
        )

val_generator = datagen.flow_from_directory(
            train_dir,
            target_size=(224,224),
            batch_size=batch_size,
            class_mode='categorical',
            shuffle=True,
            subset = "validation"
        )

ここで、ポイントなのはvalidation_splitです。この値を決めておくと、読み込んだデータを自動でvalidationデータとtrainデータに分けてくれます。この場合は1割がvalidationデータとなります。
注:標準化などの基本統計量が必要になる前処理をしたい場合はfitする必要があります詳しくは調べてみてください

ここで出てきた'flow_from_directory'がデータのあるディレクトリからデータを自動で読み取ってジェネレータを作ってくれます。
引数はそれぞれ、

train_dir:データのあるディレクトリのpath
target-size:画像データのサイズ
batch-size:一度に読み込むデータの数
class_mode:正解ラベルの形(複数のクラスであれば'categorical'、2クラスであれば'binary'など
suffle:データをシャッフルするかどうか(真偽値)
subset:はじめに書いたvalidationかtrainのデータかを指定する。
ここで意地悪なのが、kerasの日本語公式ドキュメントにはsubsetの説明が無い、、(英語の方には確かあったけど)
train_dirの中をラベルごとに分けてフォルダを作っておけば、自動でデータにlabelづけしてくれます。便利ですね。

ジェネレータを使った学習

self._target.fit_generator(
            train_generator,
            steps_per_epoch=5,
            epochs=epochs,
            validation_data=val_generator,
            validation_steps=1,
            callbacks=[
                TensorBoard(log_dir=self.log_dir),
                ModelCheckpoint(os.path.join(self.log_dir, self.model_file_name), save_best_only=True)
            ],
            verbose=2,
            workers=1
        )

ここは、主な引数の説明をします。

train_generator:先ほどの学習データのジェネレータ
steps_per_epoch,epochs:steps_per_epoch * epochs=全データ数となるようにします。
validation_data:先ほどのバリデーションデータのジェネレータ
validation_steps:ジェレータから使用するサンプルのバッチ数
こんな感じになります

step_per_epoch引数の役割です: ジェネレータからstep_per_epochバッチを描画した後
steps_per_epoch 勾配降下ステップ-フィッティング処理が次のエポックまで行くかどうか。この場合、バッチは20サンプルなので、目標の2,000サンプルが表示されるまで100バッチかかります。

fit_generator を使 う 際には、 fit メ ソ ッ ド と 同様に、validation_data 引数を渡す こ と がで き ます。こ の引数はデー タ 生成器であ る こ と がで き る こ と に注意 し て く だ さ いが、 こ の引数は Numpy 配列の タ ッ プルであ る こ と も で き ます。validation_dataとしてジェネレータを渡すと、このジェネレータは無限に検証データのバッチを生成することが期待されます。

ちょっと注意なのが、普通のfitだと、ここでvalidationデータに分けられるのですが、fit_generatorはここでvalidationデータに分けることはできないので、あらかじめ二つのジェネレータを用意する必要あります。

これで一通りメモリに乗り切らないようなデータの学習もできるようになったのでは無いかと思います

テストデータでもジェネレータを使いたい
もちろんテストデータにもジェネレータは使えます

predict_generator
evaluate_generator
あたりが使えるともいます。基本的な機能はジェネレータを使わないものと同じになると思います。

参考:画像の前処理 - Keras Documentation https://keras.io/ja/preprocessing/image/

過学習対策

学習において多くの種類のデータをダブりなく用いることが重要。目標は、トレーニング時にモデルが全く同じ画像を2度見ないようにすることです。これにより、モデルがデータのより多くの側面にさらされ、より良い一般化が可能になります。

Kerasでは、ImageDataGeneratorインスタンスによって読み込まれた画像に対して実行されるランダムな変換を設定することで、これを行うことができます。

datagen = ImageDataGenerator(
    rotation_range=40,
    width_shift_range=0.2,
    height_shift_range=0.2,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True,
    fill_mode='nearest')

これらは利用可能なオプションのほんの一部です (詳細は Keras のドキュメントを参照してください)。

このコードを早速見ていきましょう。

  • rotation_range は度数 (0-180) の値で、画像を回転させる範囲を指定します。
  • width_shift と height_shift は、画像を縦または横にランダムに翻訳するための範囲 (総幅または高さの何分の一か) です。
  • shear_rangeは、ランダムに剪断変換を適用するためのものです。
  • zoom_rangeは写真の内側をランダムにズームするためのものです。
  • horizontal_flipは、水平非対称性を前提としない場合(例えば、実世界の絵など)に、ランダムに画像の半分を水平方向に反転させるためのものです。
  • fill_mode は新たに作成されたピクセルを塗りつぶすために使われるストラテジーで、 回転や幅/高さのシフトの後に現れることがあります。

訓練済みモデルを使う

訓練されたConvネットは凍結し(パラメータをつかいまわし)、最終の全結合層を取り替えることによって新しいタスクに対応できる。これは、Convネット(畳み込み層)で学習されたものは一般的なものである可能性が高いから。密結合層はオブジェクトのいち関係を保持しないため、それが重要なタスクでは意味をなさない。

from keras.applications import VGG16
conv_base = VGG16(weights='imagenet'.
include_top=Fals,

input_shape=(150, 150, 3))

コンストラクタには3つの引数を渡します。

​ weightsはモデルを初期化するためのウェイトチェックポイントを指定します。

  • include_top は,密に接続された分類器をネットワークの先頭に含めるかどうかを指定します.デフォルトでは,この密に接続された分類器は,ImageNetの1,000クラスに対応します.あなた自身の密接続分類器(猫と犬の2つのクラスのみ)を使うつもりなので,それを含める必要はありません.
  • input_shape はネットワークに供給する画像テンソルの形状です。この引数は純粋に任意です: これを渡さなければ、ネットワークはどのようなサイズの入力でも処理することができます。

この時点では、2つの方法で進めることができます。

  • データセット上で畳み込みベースを実行し、その出力をディスク上のNumpyアレイに記録し、このデータを入力として使用して、この本のパート1で見たようなスタンドアロンの密に接続された分類器を作成します。この方法は、入力画像ごとに1回だけ畳み込みベースを実行するだけでよいので、高速かつ安価に実行することができます。しかし、同じ理由で、この手法ではデータ拡張を使用することはできません。
  • モデル(conv_base)を拡張し、その上に密なレイヤーを追加して、入力データ上で全体を最後まで実行します。これにより、モデルが見るたびにすべての入力画像が畳み込みベースを通過するので、データの増大を利用することができます。しかし、同じ理由で、この手法は最初の手法よりもはるかに高価です。

先に紹介したImageDataGeneratorのインスタンスを実行して、画像をラベルと同様にNumpy配列として抽出することから始めます。これらの画像から conv_base モデルの predict メソッドを呼び出して特徴を抽出します。

from keras.preprocessing.image import ImageDataGenerator

base_dir = '/Users/fchollet/Downloads/cats_and_dogs_small' 
train_dir = os.path.join(base_dir, 'train') 
validation_dir = os.path.join(base_dir, 'validation') 
test_dir = os.path.join(base_dir, 'test')

datagen = ImageDataGenerator(rescale=1./255)
batch_size = 20

def extract_features(directory, sample_count):
    features = np.zeros(shape=(sample_count, 4, 4, 512)
     labels = np.zeros(shape=(sample_count) 
     generator = datagen.flow_from_directory(directory, target_size=(150, 150), batch_size=batch_size, class_mode='binary')
    i = 0

    for inputs_batch, labels_batch in generator:
         features_batch = conv_base.predict(input_batch)
        features[i * batch_size : (i + 1) * batch_size] = features_batch
        labels[i * batch_size : (i + 1) * batch_size] = labels_batch 
         i += 1
         if i * batch_size >= sample_count:
            break
    return features, labels

train_features, train_labels = extract_features(train_dir, 2000)
validation_features, validation_labels = extract_features(validation_dir, 1000)
test_features, test_labels = extract_features(test_dir, 1000)

既存の訓練済みモデルを使って最後の全結合層だけ付け替える場合、事前に訓練済みモデルのパラメータを凍結させることが重要。これを行わないと訓練中にパラメータが変更されてしまう。全結合層のパラメータはランダム尼初期化されているため、逆伝播により大きな値がでんぱし、せっかく訓練したパラメータが破壊されてしまう。

フリーズさせるためにはtrainable属性をFalseにする。
conv_base.trainable = False

追加した全結合層とベースモデルの最終層のパラメータを訓練する。,再利用されるモデルのより抽象的な表現をわずかに調整して,目下の問題に対してより再現性の高いものにするために,微調整と呼ばれます.

この場合でも、全結合層はトレーニングしていない状態で大きな値を逆伝播するため、事前にトレーニングしておく必要がある。

  1. 既に訓練されたベースネットワークの上にカスタムネットワークを追加します。
  2. ベースネットワークをフリーズさせます。
  3. 追加した部分を鍛える。
  4. ベースネットワークのいくつかのレイヤーの凍結を解除します。
  5. これらのレイヤーと追加した部分の両方を共同でトレーニングします。

以下、頭に入れておくべき点として記載されていました。

  • 畳み込みベースの初期のレイヤーは、より一般的で再利用可能な特徴をエンコードし、上位のレイヤーはより専門的な特徴をエンコードします。なぜなら、これらは新しい問題に再利用する必要のあるものだからです。下位のレイヤーを微調整すると、リターンが急速に減少します。

  • 学習するパラメータが多ければ多いほど、オーバーフィットのリスクが高くなります。畳み込みベースには1,500万個のパラメータがあるので、小さなデータセットでそれを訓練しようとするのは危険です。

conv_base.trainable = True # ベースをトレーニング可能とするか固定するか

set_trainable = False
for layer in conv_base.layers:
    if layer.name == 'block5_conv1':
        set_trainable = True
    if set_trainable:
        layer.trainable = True
    else:
        layer.trainable = False        
  • RMSPropのオプティマイザーを使って、非常に低い学習率でこれを行います。低い学習率を使う理由は、微調整する3つの層の表現に加える変更の大きさを制限したいからです。大きすぎる更新は、これらの表現に悪影響を与える可能性があります。

RNN

KerasのOne-hotエンコーディングユーティリティ

from keras.preprocessing.text import Tokenizer

samples = ['The cat sat on the mat.', 'The dog ate my homework.']

文字のエンベッティング

# 最もよく使われる1000語のみ考慮する,
tokenizer = Tokenizer(num_words=1000)

# This builds the word index
tokenizer.fit_on_texts(samples)

# 文字をインデックスのリストに変換
sequences = tokenizer.texts_to_sequences(samples)

# これでOne-hot表現ができた。他の方法もある。
one_hot_results = tokenizer.texts_to_matrix(samples, mode='binary')

# これがワードインデックスからもとに戻す方法です。
word_index = tokenizer.word_index
print('Found %s unique tokens.' % len(word_index))

ワンホット・エンコーディングの変形として、いわゆるワンホット・ハッシュ・トリックがあります。各単語に明示的にインデックスを割り当て、そのインデックスの参照を辞書に保持する代わりに、 単語を固定サイズのベクトルにハッシュ化することができます。これは通常、非常に軽量なハッシュ関数を使って行われます。この方法の主な利点は、明示的な単語インデックスを維持する必要がないので、メモリを節約でき、データのオンラインエンコーディングが可能になります(利用可能なデータをすべて確認する前に、すぐにトークンベクトルを生成できます)。このアプローチの欠点は、ハッシュの衝突の影響を受けやすいということです。ハッシュ空間の次元数が、ハッシュされるユニークなトークンの総数よりもはるかに大きくなると、ハッシュ衝突の可能性は低くなります。

ハッシュトリックのサンプル

samples = ['The cat sat on the mat.', 'The dog ate my homework.']

# 単語を0-1000までのサイズのハッシュに変換します。

# もし1000語近く、もしくは1000語を越える単語をハッシュ化する場合はコリジョンが起きやすくなります。

dimensionality = 1000
max_length = 10

results = np.zeros((len(samples), max_length, dimensionality))
for i, sample in enumerate(samples):
    for j, word in list(enumerate(sample.split()))[:max_length]:

        # Hash the word into a "random" integer index

        # that is between 0 and 1000
        index = abs(hash(word)) % dimensionality
        results[i, j, index] = 1.

単語埋め込み[ワードエンベッティング](P184)

ベクトルと単語を関連付けるもう一つの一般的で強力な方法は、単語エンベッディングとも呼ばれる高密度の単語ベクトルを使用することです。ワンホット・エンコーディングで得られるベクトルはバイナリで、疎で(ほとんどがゼロでできています)、非常に高次元(語彙の単語数と同じ次元)であるのに対し、ワード・エンベッディングは低次元の浮動小数点ベクトルです(つまり、疎なベクトルではなく、密なベクトルです);図6.2を参照してください。図6.2を参照してください。ワンホットエンコーディングで得られるワードベクトルとは異なり、ワードエンベッディングはデータから学習されます。非常に大きな語彙を扱う場合、256次元、512次元、1,024次元の単語エンベッディングを見ることがよくあります。一方、ワン・ホット・エンコーディングの単語は、一般的に20,000次元以上のベクトルになります(この場合、20,000トークンのボキャブラリーをキャプチャします)。つまり、ワードエンベッディングは、はるかに少ない次元に多くの情報を詰め込んでいます。

単語のエンベッディングを取得するには、2つの方法があります。
-気になるメインタスク(文書分類や感情予測など)と共同で単語の埋め込みを学習します。このセットアップでは、まずランダムな単語ベクトルから始めて、ニューラルネットワークの重みを学習するのと同じ方法で単語ベクトルを学習します。
-解こうとしているタスクとは異なる機械学習タスクを使用して事前に計算された単語エンベッディングをモデルにロードします。これらは、事前学習された単語の埋め込みと呼ばれています。

from keras.layers import Embedding

# とり得るトークン数(ここでは1000。1+ワードインデックスの最大値)と埋め込みの次元数(ここでは64)が引数となる
embedding_layer = Embedding(1000, 64)

エンベッディング・レイヤーは、整数インデックス (特定の単語を表す) を密なベクトルにマップする辞書として最もよく理解されています。これは、入力として整数を取り、内部辞書でこれらの整数を検索し、関連するベクトルを返します。これは効率的に辞書検索を行います。
埋め込みレイヤは、入力として形状(samples, sequence_length)の整数の 2 次元テンソルを取り、各エントリは整数のシーケンスとなります。これは、可変長のシーケンスを埋め込むことができます。例えば、先ほどの例では、形状 (32, 10) のバッチ (長さ 32 のシーケンスのバッチ) をエンベッディング レイヤーに入力していましたが、エンベッディング レイヤーでは、その長さを変更することができます。

しかし,バッチ内のすべてのシーケンスは同じ長さでなければならない(1つのテンソルにまとめる必要があるため)ので,他より短いシーケンスは0で埋め,長いシーケンスは切り捨てます.
このレイヤは,形状(サンプル,シーケンス長,埋め込み次元)の3次元浮動小数点テンソルを返す.このような3次元テンソルは,その後,RNN層や1次元畳み込み層で処理することができます(どちらも次のセクションで紹介します).
トレーニング中、これらの単語ベクトルはバックプロパゲーションによって徐々に調整され、下流のモデルが利用できるように空間が構造化されます。完全に訓練されると、埋め込み空間は多くの構造を示すようになり、モデルを訓練している特定の確率に特化した一種の構造になります。

Note:Embedding Layerが何をしているか

tensorflowのEmbedding レイヤーは何をするか? - Qiita https://qiita.com/9ryuuuuu/items/e4ee171079ffa4b87424

以下、RNNで登場する各引数の意味を残しておく。

vocab_size:入力文書の総単語数
output_dim:分散ベクトルの次元数(1単語を何次元に変換するか)
input_length:文書の次元数

Note:GlobalAveragePooling1Dとはなにか

Embedding Layerで得られた値を次元毎に平均をとって圧縮する。これにより各文書の特徴を小さいなデータで表現することができる。この情報圧縮により単語の前後情報は失われる。

事前学習済みの特徴ベクトルを用いる場合

事前学習済みの単語の特徴ベクトルを用いる場合、モデルを構築するなかでEmbedding Layerは学習させないようにフリーズさせる。

model.layer[0].set_weights([embedding_matrix])
model.layer[0].trainable = False

多くの学習データが利用できる場合には事前学習されたものよりよい成果が出る場合がある。

Kerasでは単純なRNNはSimpleRNNクラスで定義されている。

from keras.models import Sequential
from keras.layers import Embedding, SimpleRNN
model = Sequential()
model.add(Embedding(10000, 32))
model.add(SimpleRNN(32))
model.summary()

ここで入力が(timesteps, input_features)となっているが、実際はバッチ処理するため、(batch_size, timesteps, input_features)となる。

SimpleRNNは2つの異なるモードで実行可能。各タイムステップの連続出力の完全なシーケンス(3次元テンソルの形状(batch_size, timesteps, output_features))、または各入力シーケンスの最後の出力(2次元テンソルの形状(batch_size, output_features))のいずれか

後者のパターンでは以下のようにreturn_sequencesをTrueにする。

model = Sequential()
model.add(Embedding(10000, 32))
model.add(SimpleRNN(32, retrn_sequences=True)
model.summary()

ネットワークの表現力を高めるために、いくつかのRNNレイヤーを次々に積み重ねることが有用な場合があります。このような設定では、すべての中間層が完全なシーケンスの出力を返すようにしなければなりません。

Dropoutを入れる場所

リカレント層のまえにDropoutを入れると学習を妨げる。GRUやLSTMのような層のリカレントゲートによって形成される表現を規則化するために、時間的に一定のドロップアウトマスクを層の内側のリカレント活性化に適用すべきである(リカレントドロップアウトマスク)。recurrent_dropout を追加し、リカレント・ユニットのドロップアウト率を指定します。

model = Sequential() model.add(layer.GRU(32, dropout=0.2recurrent_dropout=0.2, input_shape=(None, float_data.shape[-1])) 
model.add(layer.Dense(1))
model.compile(optimizer=RMSprop(), loss='mae') 
history = model.fit_generator(train_gen, steps_per_epoch=500, epochs=40, validation_data=val_gen, validation_steps=val_steps)

再帰層の積み重ね(P217)

オーバーフィットが主な障害になるまでは、ネットワークの容量を増やすのが一般的には良い考えです(以下のように仮定します)。

ネットワークの容量を増やすには、通常、レイヤー内のユニット数を増やしたり、レイヤーを追加したりします。リカレント・レイヤースタッキングは、より強力なリカレント・ネットワークを構築するための古典的な方法です。例えば、現在のGoogle翻訳アルゴリズムでは、7つの大きなLSTMレイヤがスタックされています。
Kerasでリカレントレイヤーを重ねるには、すべての中間レイヤーを
は、最後のタイムステップでの出力ではなく、出力の完全なシーケンス(3Dテンソル)を返すべきです。これは return_sequences=True を指定することで行われます。

model = Sequential() model.add(layer.GRU(32, dropout=0.1recurrent_dropout=0.5return_sequences=True, input_shape=(None, float_data.shape[-1])) 
model.add(layer.GRU(64, activation='relu', dropout=0.1, recurrent_dropout=0.5))
model.add(layer.Dense(1))
model.compile(optimizer=RMSprop(), loss='mae') 
history = model.fit_generator(train_gen, steps_per_epoch=500, epochs=40, validation_data=val_gen, validation_steps=val_steps)

双方向RNN(P219)

双方向性RNNは、特定のタスクにおいて通常のRNNよりも高い性能を提供できる一般的なRNNの変種です。自然言語処理で頻繁に使用されており、自然言語処理のためのディープラーニングのスイスアーミーナイフと呼ぶこともできます。

双方向 RNN は RNN の次数感応性を利用しています:これは,おなじみの GRU や LSTM 層のような 2 つの通常の RNN を使用しており,それぞれが入力シーケンスを一方の方向(時系列と反時系列)に処理し,それらの表現をマージします.シーケンスを双方向に処理することで、双方向RNNは単方向RNNでは見落とされる可能性のあるパターンをキャッチすることができます。
驚くべきことに、このセクションのRNN層がシーケンスを時系列順に処理した(古いタイムステップが先)という事実は、恣意的な決定だったかもしれません。少なくとも,これまでのところ,この決定に疑問を抱くことはありませんでした.例えば、入力シーケンスを逆時系列で処理した場合(新しいタイムステップが先)、RNNは十分な性能を発揮できたでしょうか?これを実際に試してみて、何が起こるか見てみましょう。必要なのは、入力シーケンスを時間次元に沿って戻すデータ生成器の変形を書くだけです(最後の行を yield samples[:, ::-1, :], targets に置き換えてください)。

逆順の GRU は、常識的なベースラインでさえも強く劣っており、この場合、アプローチの成功には時系列処理が重要であることを示しています。これは完全に理にかなっています:基礎となる GRU レイヤーは通常、遠い過去よりも最近の過去を記憶するのが得意であり、当然ながら、最近の気象データポイントは古いデータポイントよりも問題の予測性が高いのです(これがコモンセンスのベースラインをかなり強力にしている理由です)。したがって、年代順のレイヤーは、逆順のレイヤーよりも優れていることになります。重要なことは、これは自然言語を含む他の多くの問題には当てはまらないということです:直観的には、文を理解する上での単語の重要性は、通常、文の中での位置に依存しません。

時系列順のLSTMとほぼ同じ性能を得ることができます。驚くべきことに、このようなテキストデータセットでは、逆順処理は時系列処理と同じように動作し、言語を理解する上で語順は重要であるが、どの語順を使用するかは重要ではないという仮説を確認しました。重要なことは、逆順処理で訓練されたRNNは、元のシーケンスで訓練されたものとは異なる表現を学習するということで、現実世界で時間が逆順に流れていた場合、異なるメンタルモデルを持つことになります。機械学習では、異なる表現でありながら有用な表現は常に活用する価値があり、異なるほど良いものです。これがアンサンブルの背後にある直感であり、第7章で探求する概念です。
双方向性 RNN は,このアイデアを利用して時系列順 RNN の性能を向上させます.双方向性 RNN は入力シーケンスを双方向に見て(図 6.25 参照),より豊かな表現を得て,時系列順バージョンだけでは見逃していた可能性のあるパターンを捕捉します.

Kerasで双方向性RNNのインスタンスを作成するには、双方向性レイヤを使用します。双方向性は、このリカレントレイヤーの2番目の別のインスタンスを作成し、一方のインスタンスを入力シーケンスを時系列で処理し、他方のインスタンスを入力シーケンスを逆順で処理するために使用します。IMDBの感情分析タスクで試してみましょう。

model = Sequential()
model.add(layer.Embedding(max_features, 32))
model.add(layer.Bidirectional(layer.LSTM(32)))
model.add(layer.Dense(1, activation='sigmoid')

model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc']) history = model.fit(x_train, y_train.
epochs=10, batch_size=128, validation_split=0.2)

双方向性レイヤーはクロノロジカルLSTMの2倍のパラメータを持っているので、当然のことですが、より早くオーバーフィットしてしまうようです。正則化を行うことで、双方向性アプローチはこのタスクで強力なパフォーマンスを発揮するでしょう。

CNNのRNNへの適用(P225)

1次元CNNは、特定のシーケンス処理問題において、通常はかなり安い計算コストでRNNと競合することができます。最近では、一般的に拡張カーネルを用いた1次元CNNが、音声生成や機械翻訳の分野で大きな成功を収めています。これらの特定の成功に加えて、テキストの分類や時系列予測などの単純なタスクでは、小さな1次元CNNがRNNの高速な代替手段となることが知られています。

これまで紹介した畳み込みレイヤーは、画像テンソルから2次元パッチを抽出し、すべてのパッチに同一の変換を適用する2次元畳み込みでした。同様に、シーケンスから局所的な1次元パッチ(サブシーケンス)を抽出する1次元畳み込みを使用することもできます

このような 1 次元畳み込み層は、シーケンス内の局所的なパターンを認識することができる。すべてのパッチで同じ入力変換が行われるため、文中のある位置で学習したパターンを後から別の位置で認識することができ、1次元畳み込み層は(時間的な翻訳に対して)翻訳不変性を持つようになる。例えば、サイズ5の畳み込み窓を用いて文字列を処理している1D convnetは、長さ5以下の単語や単語断片を学習することができなければならず、次のように認識することができるはずです。入力シーケンスのどのような文脈においても,これらの単語を学習することができます.このようにして、文字レベルの1D convnetは、単語の形態学を学習することができます。

Kerasでは、Conv2Dと同様のインターフェースを持つConv1Dレイヤを介して1次元のConvnetを使用します。これは、形状(サンプル、時間、特徴量)を持つ3次元テンソルを入力として受け取り、同様の形状の3次元テンソルを返します。畳み込みウィンドウは、入力テンソルの時間軸:軸1上の1Dウィンドウです。
単純な2層の1D convnetを構築し、IMDBの感情分類タスクに適用してみましょう。注意点として、これはデータを取得し、前処理を行うためのコードです。

from keras.datasets import imdb
from keras.preprocessing import sequence
max_features = 10000
max_len = 500
print('データの読み込み中...')
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features) print(len(x_train), 'train sequences')
print(len(x_test), 'テストシーケンス')
print('パッドシーケンス(サンプル×時間)')
x_train = sequence.pad_sequences(x_train, maxlen=max_len) 
x_test = sequence.pad_sequences(x_test, maxlen=max_len) 
print('x_train shape:', x_train.shape)
print('x_test shape:', x_test.shape)

1D CNNは、第 5 章で使用した 2D CNNと同じ方法で構造化されています。1D CNNは、Conv1D と MaxPooling1D のスタックで構成されており、最後はグローバルプーリングレイヤまたはFlattenレイヤで終わります。
しかし、1D 畳み込みでは、1D 畳み込みでより大きな畳み込みウィンドウを使用することができます。2D 畳み込みレイヤーでは、3 × 3 の畳み込みウィンドウには 3 × 3 = 9 個の特徴ベクトルが含まれますが、1D 畳み込みレイヤーでは、サイズ 3 の畳み込みウィンドウには 3 個の特徴ベクトルしか含まれません。このように、サイズ7または9の1D畳み込みウィンドウを簡単に購入することができます。

from keras.models import Sequential from keras import layers
from keras.optimizers import RMSprop

model = Sequential()
model.add(layer.Embedding(max_features, 128, input_length=max_len)
model.add(layer.Conv1D(32, 7, activation='relu') 
model.add(layer.MaxPooling1D(5)) 
model.add(layer.Conv1D(32, 7, activation='relu') 
model.add(layer.GlobalMaxPooling1D()) 
model.add(layer.Dense(1))
model.summary()
model.compile(optimizer=RMSprop(lr=1e-4).
loss='binary_crossentropy', metrics=['acc'])
history = model.fit(x_train, y_train.
epochs=10, batch_size=128, validation_split=0.2)

1次元CNNは入力パッチを独立して処理するため、RNNとは異なり、タイムステップの順序(局所的なスケール、畳み込み窓のサイズを超えて)に敏感ではありません。これはコンベネットが入力時系列のどこにでもパターンを探しており、見たパターンの時間的な位置(初めに向かって、終わりに向かって、など)を知らないからです。

Note:1次元畳み込みについて

1次元畳み込みについては以下がわかりやすかった。
時系列予測を一次元畳み込みを使って解く with Keras - Qiita https://qiita.com/niisan-tokyo/items/a94dbd3134219f19cab1

畳み込みLstm https://www.slideshare.net/tak9029/lstm-69749573

Note:ヒープホールとは

ひさびさにLSTMみたらCECとかピープホールってなんだっけ?となったので以下を参考にしました。

(機械学習基礎)数式なし! LSTM・GRU超入門 | AGIRobots https://agirobots.com/lstmgruentrance-noformula/

Keras Functional API

シーケンシャルモデルは、ネットワークが正確に1つの入力と正確に1つの出力を持ち、線形の層のスタックで構成されていることを前提としています。
これは一般的に検証されている仮定です。この設定は非常に一般的なので、このページでは、これまでシーケンシャル・モデル・クラスのみを使用して多くのトピックと実用的なアプリケーションをカバーしてきました。しかし、この仮定のセットは、多くのケースで柔軟性に欠けます。ネットワークによっては、いくつかの独立した入力を必要とするものもあれば、複数の入力を必要とするものもあります。

ネットワークの中には、複数の出力を必要とするものもあり、レイヤー間での分岐がレイヤーの線形スタックではなく、レイヤーのグラフのように見えるものもあります。

例えば、マルチモーダル入力を必要とするタスクもある。異なる入力ソースからのデータをマージし、異なる種類のニューラル層を使用して各タイプのデータを処理する。ディープラーニング・モデルが、次の入力を使用して、中古の衣類の市場価格を予測しようとしていることを想像してみてください:ユーザーが提供するメタデータ(アイテムのブランドや年齢など)、ユーザーが提供するテキストの説明、アイテムの写真。メタデータだけが利用可能であれば、それをワンホットエンコードして、密に接続されたネットワークを使用して価格を予測することができます。テキストの説明だけが利用可能な場合は、RNNや1D convnetを使用することができます。画像だけがあれば、2D convnetを使うことができます。しかし、どのようにして3つを同時に使うことができるのでしょうか?

ナイーブなアプローチは、3つの別々のモデルを訓練し、それらの予測値を加重平均することです。しかし、これはモデルによって抽出された情報が冗長になる可能性があるため、最適ではないかもしれません。より良い方法は、利用可能なすべての入力モダリティを同時に見ることができるモデルを使用して、データのより精度の高いモデルを共同学習することです。

同様に、いくつかのタスクでは、入力データの複数のターゲット属性を予測する必要があります。小説や短編小説のテキストが与えられた場合、ジャンル(ロマンスやスリラーなど)で自動的に分類しつつ、それが書かれたおおよその日付を予測したい場合があります。もちろん、2つの別々のモデルをトレーニングすることもできます:1つはジャンル、もう1つは日付です。しかし、これらの属性は統計的に独立していないので、ジャンルと日付の両方を同時に共同で予測するように学習することで、より良いモデルを構築することができます。そのような共同モデルは、2つの出力、つまりヘッドを持つことになります(図7.3を参照)。ジャンルと日付の間には相関関係があるため、小説の日付を知ることは、モデルが小説のジャンル空間の豊かで正確な表現を学習するのに役立ち、その逆もまた然りです。

さらに、最近開発されたニューラル・アーキテクチャの多くは、有向非周期グラフとして構成されたネットワークという非線形ネットワーク・トポロジーを必要とします。例えば、GoogleのSzegedyらによって開発されたネットワークのInceptionファミリー1は、Inceptionモジュールに依存しており、入力は複数の並列畳み込み分岐によって処理され、その出力はn個が1つのテンソルにマージされたものです(図7.4参照)。また、最近では、モデルに残差接続を追加する傾向がありますが、これはResNetネットワークのファミリー(マイクロソフトのHeらによって開発されました)から始まりました。残差接続とは、過去の出力テンソルを後の出力テンソルに追加することで、過去の表現を下流のデータフローに再挿入することです(図7.5参照)。このようなグラフ状のネットワークの例は他にもたくさんある。

Multi-input models(P238)

2つの入力を持つモデルは、
keras.layers.add
keras.layers.concatenate
等によって記述される。

参考文書と質問を別々のインプットとし、答えを導く例が書かれている。

text_input = Input(shape=(None,), dtype='int32', name=text')

embedded_text = layers.Embedding(
64, text_vocabulary_size)(text_input) encoded_text = layers.LSTM(32)(embedded_text)
question_input = Input(shape=(None,).
dtype=int32', name='question')

embedded_question = layers.Embedding(
32, question_vocabulary_size)(question_input)

encoded_question = layers.LSTM(16)(embedded_question)

concatenated = layers.concatenate([encoded_text, encoded_question], axis=-1)
answer = layers.Dense(answer_vocabulary_size, activation=softmax')(concatenateed)

2入力モデルをどのようにして学習するのでしょうか?APIには2つの方法があります: 入力としてNumpy配列のリストをモデルに与えるか、入力名をNumpy配列にマップする辞書をモデルに与えるかです。当然のことながら、後者のオプションは入力に名前を与えた場合にのみ利用可能です。

text = np.random.randint(1, text_vocabulary_size.
size=(num_samples, max_length))

question = np.random.randint(1, question_vocabulary_size, size=(num_samples, max_length)) answers = np.random.randint(0, 1, size=(num_samples, answer_vocabulary_size)

①Numpy配列で与える方法

model.fit([text, question], answers, epochs=10, batch_size=128) 

②辞書形式で与える方法

model.fit({'text': text, 'question': question}, answers, epochs=10, batch_size=128)

Multi-output models

機能APIを使用して、複数の出力(または複数のヘッド)を持つモデルを構築することができます。簡単な例としては、データの異なる特性を同時に予測しようとするネットワークがあります。例えば、単一の匿名の人物からの一連のソーシャルメディアの投稿を入力として受け取り、年齢、性別、所得レベルなどのその人物の属性を予測しようとするネットワークがあります。

from keras import layers
from keras import Input
from keras.models import Model

vocabulary_size = 50000
num_income_groups = 10
posts_input = Input(shape=(None,), dtype='int32', name='posts') 
embedded_posts = layers.Embedding(256, vocabulary_size)(posts_input) 
x = layers.Conv1D(128, 5, activation='relu')(embedded_posts)
x = layers.MaxPooling1D(5)(x)
x = layers.Conv1D(256, 5, activation='relu')(x) 
x = layers.Conv1D(256, 5, activation='relu')(x) 
x = layers.MaxPooling1D(5)(x)
x = layers.Conv1D(256, 5, activation='relu')(x) 
x = layers.Conv1D(256, 5, activation='relu')(x) 
x = layers.GlobalMaxPooling1D()(x)

x = layers.Dense(128, activation='relu')(x)
age_prediction = layers.Dense(1, name='age')(x) 
income_prediction = layers.Dense(num_income_groups, activation=softmax', name=income)(x)

# 出力レイヤーには名前が付けられていることに注意してください。
gender_prediction = layers.Dense(1, activation='sigmoid', name='gender')(x)
model = Model(posts_input, [age_prediction, income_prediction, gender_prediction])

重要なことは、このようなモデルを訓練するには、ネットワークの異なるヘッドに対して異なる損失関数を指定する能力が必要です:例えば、年齢予測はスカラー回帰タスクですが、性別予測はバイナリ分類タスクであり、異なる訓練手順を必要とします。しかし、勾配降下法ではスカラーを最小化する必要があるので、モデルを訓練するためにはこれらの損失を1つの値にまとめなければなりません。異なる損失を組み合わせる最も簡単な方法は、それらをすべて合計することです。Kerasでは、コンパイル時に損失のリストまたは辞書を使用して、異なる出力に対して異なるオブジェクトを指定することができます; 結果として得られた損失値はグローバル損失に合計され、訓練中に最小化されます。

model.compile(optimizer='rmsprop',loss=['mse', 'ategorical_crossentropy', 'binary_crossentropy'])

model.compile(optimizer='rmsprop', loss={'age': 'mse'.
'income': 'categorical_crossentropy', 'gender': 'binary_crossentropy'})

損失の寄与が非常に不均衡になると、モデル表現が他のタスクを犠牲にして、個々の損失が最も大きいタスクに優先的に最適化されてしまうことに注意してください。これを修正するには、最終的な損失への寄与度において、損失値に異なるレベルの重要度を割り当てることができます。これは特に、損失の値が異なる尺度を使用している場合に便利です。例えば、年齢回帰タスクに使用される平均二乗誤差(MSE)損失は通常3-5前後の値を取りますが、性別分類タスクに使用されるクロスエントロピー損失は0.1と低い値になります。このような状況では、異なる損失の寄与のバランスをとるために、クロス・エントロピー損失に10の重みを割り当て、MSE損失に0.25の重みを割り当てることができます。

model.compile(optimizer='rmsprop', loss=['mse', 'categorical_crossentropy', 'binary_crossentropy'], loss_weights=[0.25, 1., 10.])
model.compile(optimizer='rmsprop',loss={'age': 'mse', 'income': 'categorical_crossentropy', 'gender': 'binary_crossentropy'}, loss_weights={'age': 0.25, 'income': 1., 'gender': 10.})

マルチ入力モデルの場合と同様に、Numpyのデータを配列のリストまたは配列の辞書を使ってモデルに渡して学習させることができます。

レイヤーインスタンスの再利用

機能APIのもう一つの重要な機能は、レイヤーインスタンスを何度も再利用できることです。レイヤーインスタンスを2回呼び出すと、呼び出すたびに新しいレイヤーをインスタンス化するのではなく、呼び出すたびに同じウェイトを再利用します。これにより、共有ブランチ、つまり、同じ知識を共有し、同じ操作を行う複数のブランチを持つモデルを構築することができます。つまり、同じ表現を共有し、異なる入力セットに対して同時にその表現を学習します。
例えば、2つの文の間の意味的類似性を評価しようとするモデルを考えてみましょう。モデルは2つの入力(比較する2つの文)を持ち、0と1の間のスコアを出力します。このようなモデルは、対話システムにおける自然言語の重複排除など、多くのアプリケーションで有用である。
この設定では、2つの入力文は交換可能である。なぜなら、意味的な類似度は対称的な関係であるからである:AとBの類似度はBとAの類似度と同じである。

入力された各文を処理する。むしろ、1つのLSTM層で両方を処理したいと考えています。このLS TM層の表現(その重み)は、両方の入力に基づいて同時に学習されます。これがシャムLSTMモデルまたは共有LSTMと呼ばれるものです。
このようなモデルをKerasの機能APIでレイヤー共有(レイヤー再利用)を利用して実装する方法を紹介します。

from keras import layers
from keras import Input
from keras.models import Model 

# LSTMレイヤーを一度だけインスタンス化します。
lstm = layers.LSTM(32)

# モデルの左枝の構築: 入力は,サイズ128のベクトルの可変長シーケンスである.
left_input = Input(shape=(None, 128) 
left_output = lstm(left_input)
right_input = Input(shape=(None, 128) 
right_output = lstm(right_input)

# モデルの正しい分岐を構築する: 既存のレイヤーインスタンスを呼び出すときに、その重みを再利用します。
merged = layers.concatenate([left_output, right_output], axis=-1) 
predictions = layers.Dense(1, activation='sigmoid')(merged)
model = Model([left_input, right_input], predictions) 
model.fit([left_data, right_data], targets)

重要なことは、機能APIでは、モデルはレイヤーを使用するのと同じように使用することができます。これはシーケンシャルクラスとモデルクラスの両方に当てはまります。これは、入力テンソルでモデルを呼び出し、出力テンソルを取得できることを意味します。
y = model(x)
モデルが複数の入力テンソルと複数の出力テンソルを持つ場合は、テンソルのリストで呼び出されなければなりません。
y1, y2 = model([x1, x2])
モデルのインスタンスを呼び出すと、モデルの重みを再利用します。レイヤーインスタンスでもモデルインスタンスでも、インスタンスを呼び出すと、常に学習したインスタンスの既存の表現が再利用されます。
モデルインスタンスを再利用することで構築できるものの簡単な実用的な例としては、入力としてデュアルカメラを使用するビジョンモデルがあります。このようなモデルは奥行きを知覚することができ、多くのアプリケーションで有用です。視覚情報を抽出するのに、2つの独立したモデルは必要ありません。

このような低レベル処理は、2つの入力をマージする前に、左カメラと右カメラから特徴を抽出することで、2つの入力を共有することができます。このような低レベルの処理は、2つの入力間で共有することができます。つまり、同じ重みを使用するレイヤーを介して行われ、同じ表現を共有することができます。以下は、Kerasでシャムビジョンモデル(共有畳み込みベース)を実装する方法です。

from keras import layers
from keras import applications
from keras import Input

# ベースとなる画像処理モデルは、Xceptionネットワーク(畳み込みベースのみ)です。
xception_base = applications.Xception(weights=None, include_top=False)

left_input = Input(shape=(250, 250, 3))
right_input = Input(shape=(250, 250, 3))
left_features = xception_base(left_input) 
right_input = xception_base(right_input)
merged_features = layers.concatenate( [left_features, right_input], axis=-1)

KerasコールバックとTensorBoardを使用したディープラーニングモデルの検査とモニタリング(P249)

モデルを訓練するとき、最初から予測できないことがたくさんあります。特に、最適な価数損失に到達するために何エポックが必要なのかはわかりません。これまでの例では、オーバーフィッティングを開始するのに十分なエポック数のトレーニングを行い、最初のランを使用してトレーニングに必要な適切なエポック数を把握し、最終的にこの最適な数を使用してゼロから新しいトレーニングランを開始するという戦略を採用しています。もちろん、このアプローチは無駄が多いです。
これに対処するためのより良い方法としては、値の低下が改善されないと判断したら、トレーニングを中止することです。これはKerasのコールバックを使用することで実現できます。コールバックとは、オブジェクト(特定のメソッドを実装したクラスインスタンス)のことで、フィットの呼び出しでモデルに渡され、トレーニング中のさまざまなポイントでモデルから呼び出されます。コールバックは、モデルの状態とその性能に関する利用可能なすべてのデータにアクセスすることができ、トレーニングを中断したり、モデルを保存したり、別のウェイトセットをロードしたり、モデルの状態を変更したりといったアクションを取ることができます。
ここでは、コールバックを使用する方法の例をいくつか紹介します。

  • モデルのチェックポイント - 訓練中のさまざまなポイントでモデルの現在の重みを保存する.
  • 早期停止-検証損失が改善されなくなった場合にトレーニングを中断する(もちろん、トレーニング中に得られた最良のモデルを保存する)。
  • 学習中に特定のパラメータの値を動的に調整する -オプティマイザの学習率など
  • トレーニング中のトレーニングと検証のメトリクスをログに記録したり、モデルが更新されたときにモデルが学習した表現を視覚化したりすることができます-お馴染みのKerasのプログレスバーはコールバックです! keras.callbacksモジュールには、多くの組み込みコールバックが含まれています(これは完全なリストではありません)。
keras.callbacks.ModelCheckpoint 
keras.callbacks.EarlyStopping
keras.callbacks.LearningRateScheduler 
keras.callbacks.ReduceLROnPlateau keras.callbacks.CSVLogger

モデルチェックポイントとEarly Stoppingコールバック

EarlyStoppingコールバックを使用して、監視対象のメトリックが一定のエポック数の間改善しなくなったら、トレーニングを中断することができます。例えば、このコールバックを使用すると、オーバーフィッティングが始まったらすぐにトレーニングを中断することができるので、より少ないエポック数でモデルを再トレーニングする必要がなくなります。このコールバックは、通常、ModelCheckpointと組み合わせて使用され、トレーニング中にモデルを継続的に保存することができます(オプションで、現在のベストモデルのみを保存することもできます:エポック終了時にベストパフォーマンスを達成したモデルのバージョン)。

callback_list = [
keras.callbacks.EarlyStopping( monitor='acc', patience=1.),
keras.callbacks.ModelCheckpoint( filepath='my_model.h5', monitor='val_loss',save_best_only=True.)
]

1つ以上のエポック(つまり、2つのエポック)で精度の向上が止まった場合、トレーニングを中断します。

import keras

callbacks_list = [keras.callbacks.EarlyStopping(monitor='acc', patience=1,),
    keras.callbacksModelCheckpoint(filepath='my_model.h5', monitor='val_loss', save_best_only=True,)]

model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
model.fit(x, y, epochs=10batch_size=32callbacks=callbacks_list, validation_data=(x_val, y_val)

このコールバックを使って、検証損失の改善が止まったときに学習率を下げることができます。損失プラトーが発生した場合に学習率を下げたり増やしたりすることは、学習中に局所的なミニマムから抜け出すために有効な戦略です。次の試験では、ReduceLROnPlateauコールバックを使用しています。

callbacks_list = [ keras.callbacks.ReduceLROnPlateau(
monitor='val_loss', 
factor=0.1, # Callbackがトリガーされたとき、学習率を10で割る
patience=10, # バリデーションロスが10エポックの間改善しなかった場合にトリガーされる
)
]

model.fit(x, y, epochs=10batch_size=32callbacks=callbacks_list, validation_data=(x_val, y_val)

自分でコールバックを書く(P251)

トレーニング中に、組み込みのコールバックでカバーされていない特定のアクションを行う必要がある場合は、独自のコールバックを記述することができます。コールバックは、クラス keras.callbacks.Callback.Callback のサブクラスとして実装します。そして、以下の透過的な名前のメソッドを実装することができます。

on_epoch_begin:各epochの開始時に呼ばれる
on_epoch_end:同じく終了時に呼ばれる

on_batch_begin:各バッチの処理前に呼ばれる
on_batch_end:処理後に呼ばれる

on_train_begin:トレーニングの開始時に呼ばれる
on_train_end:トレーニングの終了時に呼ばれる

これらのメソッドはすべて logs 引数を指定して呼び出されます。これは、前のバッチ、エポック、トレーニング実行に関する情報を含む辞書です。さらに、コールバックは以下の属性にアクセスできます。

  • self.model- コールバックが呼び出されるモデルインスタンス
  • self.validation_data-検証データとして適合させるために渡された値。 これは、エポックごとにモデルの各レイヤーの活性化をディスクに保存する(Numpy配列として)カスタムコールバックの簡単な例です(バリデーションセットの最初のサンプルで計算されます)。
import keras 
import numpy as np

class ActivationLogger(keras.callbacks.Callback):
    def set_model(self, model): 
        self.model = model
        layer_outputs = [layer.output for layer in model.layer] 
        self.activations_model = keras.models.Modelmodel.input, layer_outputs)

    def on_epoch_end(self, epoch, logs=None): 
        if self.validation_data is None.
            raise RuntimeError('Required validation_data.')

validation_sample = self.validation_data[0][0:1]
activations = self.activations_model.predict(validation_sample) f = open('activa    tions_at_epoch_' + str(epoch) + '.npz', 'w') np.savez(f, activations)
f.close()

TensorBoardについて(p252)

# histgoram_freq=1は1epochごとにアクティベーションヒストグラムを記録する
# embeddings_freq=1は1epochごとにembedding dataを記録する

callbacks = [
    keras.callbacks.TensorBoard( log_dir='my_log_dir', histogram_freq=1, embeddings_freq=1, embeddings_freq=1,
    )]

history = model.fit(x_train, y_train, epochs=20batch_size=128, validation_split=0.2, callbacks=callbacks)

コマンドラインから TensorBoard サーバを起動し、コールバックが現在書き込んでいるログを読み込むように指示します。Tensorboard ユーティリティは、TensorFlow をインストールした時点で自動的にマシンにインストールされているはずです(例えば pip 経由で)。
$ tensorboard --logdir=my_log_dir

それから http://localhost:6006 にアクセスして、モデルのトレーニングを見ることができます。トレーニングと検証メトリクスのライブグラフに加えて、ヒストグラムタブにアクセスして、レイヤーによって取られた活性化値のヒストグラムの可視化を見つけることができます。

エンベッディングタブでは、初期エンベッディングレイヤーで学習した、入力語彙の10,000語のエンベッディング位置や空間関係を調べることができます。埋め込み空間は128次元であるため、TensorBoardは、主成分分析(PCA)またはt-Distributed stochastic neighbor embedding(t-SNE)のいずれかを選択した次元削減アルゴリズムを使用して、自動的に2Dまたは3Dに再利用します。図7.12では、点群の中に、肯定的な意味合いを持つ単語と否定的な意味合いを持つ単語の2つのクラスターがはっきりと見えます。このように視覚化されていることから、特定の目的を持って共同で訓練された埋め込みは、基礎となるタスクに完全に特化したモデルになることが一目瞭然であることがわかります。

グラフタブは、Kerasモデルの基礎となる低レベルのTensorFlow操作のグラフをインタラクティブに可視化します(図7.13参照)。ご覧のように、予想以上に多くのことが行われています。先ほど構築したモデルは、Keras で定義されたときにはシンプルに見えるかもしれません(基本的なレイヤの小さなスタック)が、その下ではかなり複雑なグラフ構造を構築する必要があります。その多くは勾配降下プロセスに関連しています。見えるものと操作しているものとの間にあるこの複雑さの違いが、生のTensorFlowを使ってゼロからすべてを定義するのではなく、モデルを構築する方法としてKerasを使用する主な動機となっています。Kerasを使うと、ワークフローが劇的にシンプルになります。

Kerasには、TensorFlow操作のグラフではなく、レイヤーのグラフとしてモデルをプロットするための別のすっきりした方法もあります。これを使うには、Pythonのpydotとpydot-ngライブラリとgraphvizライブラリがインストールされている必要があります。

from keras.utils import plot_model plot_model(model, to_file='model.png')

また、レイヤのグラフに形状情報を表示するオプションもあります。この例では plot_model と show_shapes オプションを使ってモデルのトポロジーを視覚化しています。

from keras.utils import plot_model
plot_model(model, show_shapes=True, to_file='model.png')

バッチ正規化

正規化とは、機械学習モデルが見た異なるサンプルを互いにより類似したものにすることを目的とした手法であり、これによりモデルが新しいデータを学習して一般化するのに役立つ。データの正規化の最も一般的な形式は、本書ですでに何度か見てきたものである:データから平均値を引いて0を中心とし、データをその標準偏差で割って単位標準偏差を与える。実際には、これはデータが正規分布(またはガウス分布)を持つと仮定し、この分布が中心に置かれ、単位分散にスケーリングされていることを確認します。

normalized_data = (data - np.mean(data, axis=...))/ np.std(data, axis=....)

これまでの例では、モデルに投入する前にデータを正規化していました。しかし、データの正規化は、ネットワークによって操作されるすべての変換の後に懸念されるべきです:たとえDenseまたはConv2Dネットワークに入るデータが平均値が0で単位分散が0であったとしても、出てくるデータがそうであることを先験的に期待する理由はありません。
バッチ正規化は、IoffeとSzegedyによって2015年に導入されたレイヤ(KerasのBatchNormalization)の一種です。これは、学習中に見られるデータのバッチごとの平均と分散の指数移動平均を内部的に維持することで動作します。バッチ正規化の主な効果は、残差接続のような勾配のプロパゲーションを支援することで、より深いネットワークを可能にすることです。非常に深いネットワークの中には、複数のバッチ正規化層を含んでいないと学習できないものもあります。例えば、BatchNormalizationは、ResNet50、Inception V3、Xceptionなど、Kerasに同梱されている先進的なCNN・アーキテクチャの多くで頻繁に使用されています。

バッチ正規化層は、典型的には、畳み込み層または密に接続された層の後に使用される。

conv_model.add(layer.Conv2D(32, 3, activation='relu')
conv_model.add(layer.BatchNormalization())
dense_model.add(layer.Dense(32, activation='relu') )
dense_model.add(layer.BatchNormalization())

バッチ正規化レイヤは、正規化されるべき特徴軸を指定する軸引数を取ります。この引数のデフォルトは、入力テンソルの最後の軸である-1です。これは、Dense層、Conv1D層、RNN層、Conv2D層をdata_formatを"channels_last"に設定して使用する場合の正しい値です。しかし、データフォーマットが"channels_first"に設定されているConv2Dレイヤーのニッチな使用例では、特徴軸は軸1です; したがって、BatchNormalizationの軸引数は1に設定する必要があります。

ハイパーパラメータの最適化(P264)

現時点では、ランダム探索が最良の方法。これはPythonのハイパーパラメータ最適化用ライブラリで、内部的にパルゼン推定子のツリーを使用して、うまく機能しそうなハイパーパラメータのセットを予測します。Hyperas (https://github.com/maxpumperla/hyperas)と呼ばれる別のライブラリは、Kerasモデルで使用するためにHyperoptを統合しています。

モデルアンサンブル

分類器の集合の予測をプールする(分類器をアンサンブルする)最も簡単な方法は、推論時にその予測を平均化することです。
4つの異なるモデルを使用して、初期予測を計算します。

preds_a = model_a.predict(x_val)
preds_b = model_b.predict(x_val)
preds_c = model_c.predict(x_val)
preds_d = model_d.predict(x_val)

final_preds = 0.25 * (preds_a + preds_b + preds_c + preds_d)

これは,分類器が多かれ少なかれ等しく良い場合にのみ機能します.それらのうちの1つが他の分類器よりも有意に悪い場合,最終的な予測は,そのグループの中で最も良い分類器ほど良くないかもしれません.
典型的には、より良い分類器にはより高い重みが与えられ、より悪い分類器にはより低い重みが与えられます。アンサンブルするための良い重みのセットを検索するには、ランダム検索やNelder-Meadのような単純な最適化アルゴリズムを使用することができます。

preds_a = model_a.predict(x_val)
preds_b = model_b.predict(x_val)
preds_c = model_c.predict(x_val)
preds_d = model_d.predict(x_val)

これらの重り(0.5, 0.25, 0.1, 0.15)は経験的に学習することを前提としている。

final_preds = 0.5 * preds_a + 0.25 * preds_b + 0.1 * preds_c + 0.15 * preds_d

このため、可能な限り異なるモデルでありながら、可能な限り優れたモデルをアンサンブルする必要があります。これは通常、非常に異なるアーキテクチャを使用したり、異なるブランドの機械学習アプローチを使用したりすることを意味します。同じネットワークを、異なるランダムな初期化から独立して数回学習したものをアンサンブルすることは、あまりやる価値がありません。もしモデル間の違いがランダム初期化と学習データにさらされた順番だけならば、アンサンブルは多様性が低く、単一のモデルよりもわずかな改善しか得られないでしょう。
私が実際にうまくいくことを発見したことの一つは、木をベースにした手法(ランドームフォレストや勾配ブースト木など)とディープニューラルネットワークのアンサンブルを使うことです。2014年には、パートナーのAndrei Kolevと私は、様々な木のモデルとディープニューラルネットワークのアンサンブルを使って、Kaggle(www.kaggle.com/c/higgs-boson)上のヒッグス粒子崩壊検出チャレンジで4位を獲得しました。驚くべきことに、アンサンブルの中のモデルの一つは、他のモデルとは異なる方法で作られており(それは正則化された貪欲な森である)、他のモデルよりも著しく悪いスコアを持っていました。当然のことながら、それはアンサンブルの中で小さなウェイトを割り当てられました。それは、他のモデルとは非常に異なっていて、他のモデルがアクセスできない情報を提供していたからです。これがまさにアンサンブルのポイントです。最良のモデルがどれだけ優れているかということではなく、候補モデルのセットの多様性が重要なのです。
近年、実務上非常に成功している基本アンサンブルのスタイルの一つに、深層学習と浅層学習を融合させたワイドでディープなカテゴリのモデルがある。このようなモデルは、大規模な線形モデルとディープニューラルネットワークを共同で学習することで構成されている。多様なモデル群を共同で学習することも、モデルアンサンブルを実現するためのもう一つの選択肢である。

テキスト生成(P273)

サンプリングプロセスにおける確率性の量を制御するために、サンプリングに使用される確率分布のエントロピーを特徴づける温度付きsoftmaxと呼ばれるパラメータを導入します:これは、次の文字の選択がどれだけ驚くべきものか、あるいは予測可能かを特徴づけます。ある温度値が与えられると、新しい確率分布が、元の確率分布(モデルのsoftmax出力)から、以下の方法で重み付けを変更して計算されます。

import numpy as np
def reweight_distribution(original_distribution, temperature=0.5): 

    # original_distributionは合計で1になる確率値の1次元Numpy配列。
    distribution = np.log(original_distribution) / temperature 
    distribution = np.exp(distribution)

    # 元の分布の再加重したものを返す。この和は1ではない可能性があるので、総和で割って新しい分布を得る。
    return distribution / np.sum(distribution)

温度が高くなると、より高いエントロピーのサンプリング分布が得られ、より驚くべき、構造化されていない生成データが生成されるのに対し、温度が低くなると、ランダム性が低くなり、より予測可能な生成データが得られる。

Note:エントロピーとはなんぞや、、、

機械学習のための数学 https://hinaser.github.io/Machine-Learning/math-for-ml.html

平均情報量とも言う。
自己情報量ではある単一の事象xにおける情報量しかわからなかったが、エントロピーの概念を用いれば、 標本空間全体の情報量を定量することができる。
起こりうる事象の数を固定すると、分布関数の選び方によってエントロピーの大小が変わる。 a個の事象を考えるとき、それぞれの事象の確率が等しく1/aである一様分布はこの事象のエントロピーを最大にする分布である。 これは事象の不確定さが最大ということを意味する。 不確定さが小さくなるほどエントロピーも小さくなる。

要するに、不確実性を測る指標、とでもいうべきか。

温度付きのソフトマックスは、温度パラメータ T を大きくすると出力の分布がよりソフトになります。これにより、知識の蒸留のソフトターゲットで効率良く情報を伝達できるようになります。

なお、温度付きソフトマックスを使うとソフトターゲットの勾配の強さが 1T2 となることから、ハードターゲットを加える場合はソフトターゲットの損失に T2 を乗じる必要があります。

path = keras.utils.get_file( 'nietzsche.txt',
origin='https://s3.amazonaws.com/text-datasets/nietzsche.txt')
text = open(path).read().lower()
print('Corpus length:', len(text))

次に、部分的に重なっている長さmaxlenのシーケンスを抽出し、ワンショットエンコードして、3D Numpyの形状(sequence, maxlen, unique_characters)の配列xに格納します。同時に,抽出された各シーケンスの後に来るワンショットエンコードされた文字などの相関対象を含む配列 y を用意します

maxlen = 60
step = 3 
sentences = [] 
next_chars = []


# 60文字のシーケンスを抽出します。
# 3文字ごとに新しいシーケンスをサンプルします。
# 抽出されたシーケンスを保持します。
# ターゲットを保持します

for i in range(0, len(text) - maxlen, step): 
    sentences.append(text[i: i + maxlen]) 
    next_chars.append(text[i + maxlen])

print('シーケンスの数:', len(sentences)
chars = sorted(list(set(text))) 
print('一意の文字:'len(chars)

char_indices = dict((char, chars.index(char)) for char in chars)
print('ベクトル化...')
x = np.zeros((len(sentences), maxlen, len(chars)), dtype=np.bool) 
y = np.zeros((len(sentences), len(chars)), dtype=np.bool)
for i, sentence in enumerate(sentences): 
    for t, char in enumerate(sentence).
        x[i, t, char_indices[char] = 1 
    y[i, char_indices[next_chars[i]] = 1

Note:隠れ層の状態の保持/非保持について

Kerasの隠れ状態を持つ、持たないの実装差異や入力やバッチの値の設定については以下の記事がわかりやすかった。

KerasのステートフルRNNで学習を高速化する - Helve’s Python memo https://helve-python.hatenablog.jp/entry/keras-stateful-rnn

トレーニングされたモデルとテキストスニペットを与えることで、次の単語を繰り返し生成することができる。

  1. これまでに利用可能な生成されたテキストを与えられた次の文字の確率分布をモデルから描画します。
  2. 分布を一定の温度になるようにリウェイトします。
  3. リウェイトされた分布に従って、次の文字をランダムにサンプリングします。
  4. 利用可能なテキストの最後に新しい文字を追加します。

def sample(preds, temperature=1.0).
preds = np.asarray(preds).astype('float64')
preds = np.log(preds) / temperature
exp_preds = np.exp(preds)
preds = exp_preds / np.sum(exp_preds)
probas = np.random.multinomial(1, preds, 1)
return np.argmax(probas)

最後に、次のループは訓練とテキスト生成を繰り返します。エポックごとに異なる温度の範囲を使用してテキストの生成を開始します。これにより、モデルが収束し始めると生成されたテキストがどのように進化するか、またサンプリング戦略における温度の影響を見ることができます。

import random
import sys

for epoch in range(1, 60): 
    print('epoch', epoch)
    model.fit(x, y, batch_size=128, epochs=1)
    start_index = random.randint(0, len(text) - maxlen - 1) 
    generated_text = text[start_index: start_index + maxlen] 
    print('---シードで生成: "' + generated_text + '"')

    for temperature in [0.2, 0.5, 1.0, 1.2]: 
        print(' ------temperature:', temperature) 
        sys.stdout.write(generated_text)

        for i in range(400):
            sampled = np.zeros((1, maxlen, len(chars)))) 
            for t, char in enumerate(generated_text):
                sampled[0, t, char_indices[char]] = 1.

        preds = model.predict(sampled, verbose=0)[0] 
        next_index = sample(preds, temperature) 
        next_char = chars[next_index]

        generated_text += next_char 
        generated_text = generated_text[1:]

        sys.stdout.write(next_char)

ご覧のように、温度が低いと、非常に反復的で予測可能なテキストになりますが、局所的な構造は非常にリアルです。温度が高くなると、ジェネレートされたテキストは、より面白く、驚き、創造的になり、時には全く新しい単語を発明して、多少はもっともらしく聞こえることもあります(eterned や troveration など)。温度が高くなると、局所的な構造が崩れ始め、ほとんどの単語が半ランダムな文字列のように見えてきます。この特定の設定では、間違いなく0.5がテキスト生成に最も適した温度です。常に複数のサンプリング戦略を試してみてください。学習した構造とランダム性のバランスが、生成を面白くするのです。

Note:バッチサイズの決め方

【Deep Learning】 Batch sizeをどうやって決めるかについてまとめる - St_Hakky’s blog https://www.st-hakky-blog.com/entry/2017/11/16/161805

変分オートエンコーダーを持ちた画像生成(P300)

VAEは、入力画像を潜在空間の固定コードに圧縮するのではなく、画像を統計分布のパラメータ(平均値と分散値)に変換します。基本的に、これは入力画像が統計的なプロセスによって生成されたものであると仮定していることを意味し、エンコードやデコードの際にはこのプロセスのランダム性を考慮に入れる必要があることを意味します。次に VAE は平均と分散パラメータを使用して分布の1つの要素をランダムにサンプリングし、その要素を元の入力にデコードします。このプロセスの確率性はロバスト性を向上させ、潜在空間があらゆる場所で意味のある表現を符号化することを強制します。

技術的な用語では、ここではVAEがどのように機能するかを説明します。

  1. エンコーダモジュールは、入力サンプル input_img を表現の潜在空間内の 2 つのパラメータ z_mean と z_log_variance に変換します。
  2. 入力画像を生成すると仮定される潜在正規分布から, z = z_mean + exp(z_log_variance) * epsilon を用いて,点 z をランダムにサンプリングします.
  3. デコーダモジュールは、潜在空間のこの点を元の入力画像にマップする。

イプシロンはランダムなので、このプロセスでは、input_imgをエンコードした潜在位置(z-mean)に近いすべての点が、input_imgと類似したものにデコードされることを保証し、潜在空間を連続的に意味のあるものにすることを強制します。潜在空間内の2つの近い点は、非常に類似した画像にデコードされます。連続性は、潜在空間の低次元性と組み合わされ、潜在空間のあらゆる方向が、データの平均的な変動軸を符号化するように強制されます。
VAEのパラメータは2つの損失関数を介して学習されます:デコードされたサンプルが初期入力と一致するように強制的に再構成する再構成損失と、形の良い潜在空間を学習し、訓練データへのオーバーフィットを減らすのに役立つ正則化損失です。KerasによるVAEの実装を簡単に見てみましょう。模式的には次のようになります。

# 入力を平均と分散のパラメータにエンコードします.
z_mean, z_log_variance = encoder(input_img) 

# 小さなランダムなイプシロンを使って潜伏点を描画します。
z = z_mean + exp(z_log_variance) * ε 

# 入力画像をその再構成にマップするオートエンコーダーモデルを開始する。
reconstructed_img = decoder(z)
model = Model(input_img, reconstructed_img)


以下は入力xを潜在空間の2つのベクトルz_meanz_log_varにマップする一連のコードです
import keras
from keras import layers
from keras import backend as K 
from keras.models import Model 
import numpy as np

img_shape = (28, 28, 1)
batch_size = 16
latent_dim = 2 # 潜在空間の次元性:2次元平面

input_img = keras.Input(shape=img_shape)
x = layers.Conv2D(32, 3.
padding=同じ', activation='relu')(input_img) x = layers.Conv2D(64, 3, padding='same', activation='relu', strides=(2, 2))(x)
x = layers.Conv2D(64, 3, padding=same', activation='relu')(x) 
x = layers.Conv2D(64, 3, padding=same', activation='relu')(x) 
shape_before_flattening = K.int_shape(x)

x = layers.Flatten()(x)
x = layers.Dense(32, activation='relu')(x)

z_mean = layers.Dense(latent_dim)(x) 
z_log_var = layers.Dense(latent_dim)(x)

次に、潜在空間点zを生成するために、input_imgを生成したと仮定した統計処理のパラメータであるz_meanとz_log_varを使用するコードです。ここでは、任意のコード(Kerasのバックエンドプリミティブの上に構築されたもの)をLambdaレイヤーにラップします。Kerasでは、すべてのものがレイヤーになる必要があるので、ビルドインレイヤーの一部ではないコードはLambda(またはカスタムレイヤー)でラップする必要があります。

def sampling(args):
    z_mean, z_log_var = args

    # εは標準正規分布からランダムにサンプリングした行列
    epsilon = K.random_normal(shape=(K.shape(z_mean)[0], latent_dim), mean=0., stddev=1.) 
    return z_mean + K.exp(z_log_var) * epsilon

z = layers.Lambda(sampling)([z_mean, z_log_var])

以下のリストは,デコーダの実装を示しています.ベクトル z を画像の寸法に再構築し、いくつかの畳み込みレイヤを使用して、元の input_img と同じ寸法を持つ最終的な画像出力を取得します。

# 入力 z 入力をアップサンプリングします。
decoder_input = layers.Input(K.int_shape(z)[1:])
x = layers.Dense(np.prod(shape_before_flattening[1:]), activation='relu')(decoder_input) 

# エンコーダモデルの最後の平坦化レイヤーの直前のフィーチャーマップと同じ形状のフィーチャーマップに z をリシェイプします。
x = layers.Reshape(shape_before_flattening[1:])(x)

# Conv2DTransposeレイヤとConv2Dレイヤを使用して,元の画像入力と同じサイズの特徴マップにzをデコードします.
x = layers.Conv2DTranspose(32, 3, padding='same', activation='relu', strides=(2, 2))(x)
x = layers.Conv2D(1, 3, padding=same', activation='sigmoid')(x)

# デコーダモデルをインスタンス化し、"decoder_input"を変換します。  
decoder = Model(decoder_input, x) 

# z に適用して、復号化された z を復元します。
z_decoded = decoder(z)

VAEのデュアルロスは、フォームロス(input, target)のサンプルワイズ関数の従来の期待値には合いません。そこで、内蔵の add_loss レイヤーメソッドを使って内部的に任意の損失を作成するカスタムレイヤーを書くことで損失を設定します。

class CustomVariationalLayer(keras.layer.Layer):
    def vae_loss(self, x, z_decoded): 
        x = K.flatten(x):
        z_decoded = K.flatten(z_decoded)
        xent_loss = keras.metrics.binary_crossentropy(x, z_decoded) 
        kl_loss = -5e-4 * K.mean(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var), axis=-1) 
        return K.mean(xent_loss + kl_loss)

    def call(self, inputs): 
        x = inputs[0]
        z_decoded = inputs[1]
        loss = self.vae_loss(x, z_decoded) 
        self.add_loss(loss, inputs=inputs) return x # この出力は使いませんが、レイヤーは何かを返さなければなりません。呼び出しメソッドを書いてカスタムレイヤーを実装します。


# 入力と復号化された出力上のカスタムレイヤを呼び出して最終モデルを出力する。
 y = CustomVariationalLayer()([input_img, z_decoded])

最後に、モデルをインスタンス化して訓練する準備ができました。損失はカスタムレイヤーで処理されるので、コンパイル時に外部損失を指定しない(loss=None)ことで、トレーニング中にターゲットデータを渡さないことを意味します(ご覧のように、フィット時にモデルにx_trainを渡すだけです)。

from keras.datasets import mnist

vae = Model(input_img, y) 
vae.compile(optimizer='rmsprop', loss=None) 
vae.summary()

(x_train, _), (x_test, y_test) = mnist.load_data()

x_train = x_train.astype('float32') / 255. 
x_train = x_train.reshape(x_train.shape + (1,)) 
x_test = x_test.astype('float32') / 255.
x_test = x_test.reshape(x_test.shape + (1,)

vae.fit(x=x_train, y=None, shuffle=Trueepochs=10batch_size=batch_size, validation_data=(x_test, None)

GAN(生成的敵対的ネットワーク)入門 P305

GAN(Generative adversarial networks)は、画像の潜在空間を学習するためのVAEの代替手段である。生成された画像を統計的に実物とほとんど区別がつかないように強制的に生成することで、かなり現実的な合成画像を生成することができる。

GANを直感的に理解する方法は、贋作のピカソの絵を作ろうとしている贋作者を想像することです。最初は、偽造者はかなり下手な作業をしています。偽造品と本物のピカソの絵を混ぜて、美術商に見せます。美術商は、それぞれの絵の真贋判定を行い、ピカソをピカソのように見せるためにはどうすればいいのかについて、贋作者にフィードバックを与えます。贋作者はアトリエに戻り、新しい偽物を準備します。時代が進むにつれ、贋作者はピカソのスタイルを真似ることができるようになり、画商は偽物を見抜くことができるようになっていきます。そして最終的に、彼らは素晴らしいピカソの偽物を手にすることになります。
これがGANとは何かというと、偽造者ネットワークとエキスパートネットワークであり、それぞれが他のものよりも優れたものになるように訓練されています。このように、GANは二つの部分で構成されています

  • ジェネレータネットワーク - ランダムなベクトル(潜在空間内のランダムな点)を入力とし、それを合成画像にデコードする。
  • 識別ネットワーク(または敵対者)-画像(本物または合成)を入力として受け取り,画像が訓練セットから来たものか,生成ネットワークによって生成されたものかを予測します.

ジェネレータ・ネットワークは、識別ネットワークを欺くことができるように訓練されているので、訓練が進むにつれて、よりリアルな画像を生成するように進化していきます。一方、識別器は、生成器の能力が徐々に向上していくのに合わせて常に適応し、生成された画像に高いリアリズムのバーを設定します。訓練が終われば、ジェネレーターは入力空間内の任意の点を信憑性のある画像に変えることができる。VAEとは異なり、この潜在空間は意味のある構造を明示的に保証するものが少なく、特に連続的ではありません。

GANでは、勾配降下の坂を下る一歩一歩が全体の景色を少しずつ変えていきます。それは、最適化プロセスが最小値ではなく、2つの力の間の均衡を求めている動的なシステムなのです。このような理由から、GANはトレーニングが難しいことで知られています。GANを動作させるには、モデルアーキテクチャとトレーニングパラメータを注意深く調整する必要があります。

6
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
9