固有表現認識器にanaGo、フロントエンドにVue.js、バックエンドにFlaskを使ってリアルタイムに固有表現認識をしてみた。
SpatialDropoutは、画像認識の分野でTompsonらによって提案されたドロップアウト方法です。通常のドロップアウトが各要素を独立して落とすのに対して、SpatialDropoutはある領域全体をまるごと落とします。それにより、画像認識の分野で性能向上が報告されています。
本記事では、KerasにおけるSpatialDropoutの動作について理解を深めることを目的としています。KerasにはSpatialDropout1D、SpatialDropout2D、SpatialDropout3Dの3種類がありますが、本記事ではSpatialDropout1Dの動作を確認します。
はじめに、SpatialDropout1Dの入出力について確認しておきましょう。SpatialDropout1Dの入力は3次元のテンソル(samples, timesteps, channels)
です。出力は入力と同じ形のテンソルが出力されます。ただし、出力されたテンソルにはドロップアウトが適用されています。
SpatialDropout1Dの入力についてイメージを具体化するために、テキストについて考えます。テキストを3次元のテンソルで表すと(samples, sequence_length, embedding_dim)
として考えることができます。ここで、sequence_length
は文の長さ、embedding_dim
は分散表現の次元数を表しています。以下のようなイメージで表せます。
このようなテキストに対してSpatialDropout1Dを適用すると、分散表現のある次元全体に対してドロップアウトが適用されます。通常のドロップアウトが各要素を独立に落としていたのに対して、SpatialDropout1Dではある次元をまるごと落としています。これらの違いは以下で表せます。
実際に、コードを書いて動作を確認してみましょう。入力として(1, 7 ,5)のテンソルを用意し、DropoutとSpatialDropout1Dを適用して違いを確認してみましょう。
まずは、入力のテンソルを用意します。
>>> import numpy as np >>> import keras.backend as K >>> ary = np.arange(35).reshape((1, 7, 5)) >>> ary array([[[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19], [20, 21, 22, 23, 24], [25, 26, 27, 28, 29], [30, 31, 32, 33, 34]]]) >>> inputs = K.variable(ary)
入力を用意したら通常のDropoutを適用してみましょう。ここではKerasのDropout層の中で使われているkeras.backend.dropout
を使ってテンソルにDropoutを適用します。Dropout率は0.5に設定します。Dropout率はK.dropout
のlevel
パラメータで指定できます。
>>> K.eval(K.dropout(inputs, level=0.5)) array([[[ 0., 2., 0., 0., 0.], [10., 12., 14., 0., 18.], [ 0., 22., 0., 0., 28.], [ 0., 32., 0., 36., 0.], [ 0., 0., 44., 46., 48.], [ 0., 0., 54., 56., 58.], [60., 62., 64., 66., 68.]]], dtype=float32)
結果を見ると、各要素を独立に落としているらしいことが確認できます。
次に、SpatialDropout1Dを適用してみます。SpatialDropout1Dを適用するには、Dropoutの形を指定する必要があります。そのために使われるパラメータがnoise_shape
です。SpatialDropout1Dの場合、入力テンソルの形input_shape
に対して以下のようにnoise_shape
を指定します。
>>> input_shape=K.shape(inputs) >>> noise_shape=(input_shape[0], 1, input_shape[2]) >>> K.eval(K.dropout(inputs, 0.5, noise_shape)) array([[[ 0., 2., 4., 6., 0.], [ 0., 12., 14., 16., 0.], [ 0., 22., 24., 26., 0.], [ 0., 32., 34., 36., 0.], [ 0., 42., 44., 46., 0.], [ 0., 52., 54., 56., 0.], [ 0., 62., 64., 66., 0.]]], dtype=float32)
結果を見ると、ある領域をまるごと落としていることが確認できます。
ちなみに、分散表現の縦方向ではなく横方向全体にドロップアウトをかけるには、Dropout層に以下のようなnoise_shape
を渡せばOKです。
>>> noise_shape=(input_shape[0], input_shape[1], 1) >>> K.eval(K.dropout(inputs, 0.5, noise_shape)) array([[[ 0., 0., 0., 0., 0.], [10., 12., 14., 16., 18.], [ 0., 0., 0., 0., 0.], [30., 32., 34., 36., 38.], [40., 42., 44., 46., 48.], [ 0., 0., 0., 0., 0.], [60., 62., 64., 66., 68.]]], dtype=float32)
今日はマニアックな話。
Kerasを使っている人なら、to_categorical関数を使ったことがある人は多いのではないかと思う。to_cateogorical関数をいつ使うかというと、正解クラスをone-hotエンコーディングして出力に与えたいときに使うことが多い。Keras 2.2.0だと以下のように動作する。
>>> from keras.utils.np_utils import to_categorical >>> to_categorical([[1, 3]], num_classes=4) array([[[0., 1., 0., 0.], [0., 0., 0., 1.]]], dtype=float32) >>> to_categorical([[1, 3]], num_classes=4).shape (1, 2, 4)
ところが、系列長が1
の入力を渡すと面白い挙動を示す。
>>> to_categorical([[1]], num_classes=4) array([[0., 1., 0., 0.]], dtype=float32) >>> to_categorical([[1]], num_classes=4).shape (1, 4)
なんと、(1, 1, 4)にならない!
では、keras.backendのone_hotはどうなのかというと、想定通りの動きを示す。
>>> import keras.backend as K >>> K.one_hot([[1]], num_classes=4) <tf.Tensor 'one_hot:0' shape=(1, 1, 4) dtype=float32> >>> K.eval(K.one_hot([[1]], num_classes=4)) array([[[0., 1., 0., 0.]]], dtype=float32)
バグなのか?と思ってソースを見たところ、理由はわからないが意図的にやっていることは間違いない。2017/11/15日のcommitで仕様が変わっている。
以下のIssueでも議論している。
理由があろうがなかろうが使用者的には困ることがある。そういうときは以下のようにnumpyのexpand_dims
を使うととりあえず解決できる。
>>> y = to_categorical([[1]], num_classes=4) >>> y.shape (1, 4) >>> y = y if len(y.shape) == 3 else np.expand_dims(y, axis=0) >>> y.shape (1, 1, 4)