Keras で特定の軸方向に softmax したい

Keras で特定の軸方向に softmax したいときとかあると思います。

f:id:cookie-box:20170815221557p:plain:w720

Keras のドキュメントによるとどの軸に沿って正規化するかを指定する axis というのを渡せるようです。
Activations - Keras Documentation
ただ、渡し方がよくわかりません。以下の箇所に axis=2 と書いてもエラーになります。

from keras.models import Sequential
from keras.layers import Dense, Reshape, Activation

if __name__ == '__main__':
  model = Sequential()
  model.add(Dense(3*4*5, activation='relu', input_shape=(10,)))
  model.add(Reshape((3, 4, 5)))
  model.add(Activation('softmax', axis=2)) # ERROR
  model.compile(loss='mean_squared_error', optimizer='rmsprop')
  print(model.summary())

しょうがないので softmax をするだけの自作レイヤーをつくります。これはエラーになりません。

from keras.engine.topology import Layer
from keras.models import Sequential
from keras.layers import Dense, Reshape
from keras.activations import softmax

# いまやりたい softmax をするためだけのレイヤー
class MySoftmax(Layer):
  def __init__(self, **kwargs):
    super(MySoftmax, self).__init__(**kwargs)
  def call(self, x):
    return(softmax(x, axis=2))

if __name__ == '__main__':
  model = Sequential()
  model.add(Dense(3*4*5, activation='relu', input_shape=(10,)))
  model.add(Reshape((3, 4, 5)))
  model.add(MySoftmax())
  model.compile(loss='mean_squared_error', optimizer='rmsprop')
  print(model.summary())

本当に希望の軸方向に正規化されているか確認してみます。何でもよいので適当な入力を流し込めばよいです。

  x, y = [], []
  # 1だらけの適当なデータ
  for i in range(20):
    x.append(numpy.ones((10)))
    y.append(numpy.ones((3,4,5)))
  x = numpy.array(x)
  y = numpy.array(y)
  
  model.fit(x, y)
  y_test = model.predict(x)
  print(y_test[0])
[[[ 0.21292862  0.24584062  0.2997719   0.15851443  0.25      ]
  [ 0.30612475  0.24584062  0.22358862  0.32254457  0.25      ]
  [ 0.16930516  0.24584062  0.22358862  0.25293246  0.25      ]
  [ 0.31164148  0.26247811  0.25305092  0.26600859  0.25      ]]

 [[ 0.18996513  0.24999966  0.18364088  0.25715023  0.3020851 ]
  [ 0.23858465  0.25000107  0.27690381  0.21845251  0.27901751]
  [ 0.25863758  0.24999966  0.35581443  0.30594474  0.19078693]
  [ 0.31281266  0.24999966  0.18364088  0.21845251  0.22811046]]

 [[ 0.21095483  0.36942184  0.22160934  0.2253582   0.24394487]
  [ 0.26182058  0.22892946  0.22160934  0.32392535  0.24394487]
  [ 0.31626979  0.159567    0.335172    0.2253582   0.24394487]
  [ 0.21095483  0.24208161  0.22160934  0.2253582   0.26816541]]]

出力の shape が (3, 4, 5) で、axis=2 としたので、5つの要素の和を取る方向がこれで正規化されて…いません。
和が1になったのは4つの要素の和を取る方向でした。
axis=1, 2, 3 についてどの軸の方向が正規化されるのか調べると以下でした。5つの要素の和を取る方向を正規化したかったら axis=3 とすべきようです。

f:id:cookie-box:20170815225335p:plain:w750

axis=0, -1 のときは以下でした。axis=0 は 4x5 の面内の和が1になりました。axis=-1 はむしろ当初やりたかった softmax が達成されていました。-1 はデフォルトのはずで、じゃあわざわざカスタムレイヤつくる必要なかったのでしょうか。
f:id:cookie-box:20170815230442p:plain:w500

これでやりたい softmax ができたので、今度はこの softmax に合わせた cross entropy を用意しなくては、と思っているのですがもしかしてそれもわざわざやらなくてよいのでしょうか。

Keras で顔写真から年齢が学習できるのか

深層学習で回帰をしてみたいときとかあると思います。そこで、顔写真からその人の年齢を回帰してみます。ただ、何のデータをつかえばいいかわからなかったので、Berryz工房のジャケ写からキャプチャした画像をつかうことにします。結論を先にいうとこの記事の学習は全然駄目です。全然駄目ですがデータが決定的に足りないのとプレ処理が雑なのでその辺を改善すれば何とかなるのかもしれません。何とかならないかもしれません。

データの準備

データはWebの画像検索から調達します。ベリメンの年齢は4学年にわたるので、だいたい3, 4年置きのシングルのジャケ写から顔写真をキャプチャすればだいたいの年齢がカバーできると思います。なので、適当に「あなたなしでは生きてゆけない」「告白の噴水広場」「本気ボンバー!!」「ロマンスを語って」を訓練データにチョイスします。テストデータは「ライバル」にします。適当です。テストデータ数をちょうど30にしたかったので、「ライバル」の梨沙子のデータだけ訓練データに含めます。なお、キャプチャはだいたい正方形になるように手動で切り取っています。訓練時に 128x128 にリサイズします。

訓練

面倒なので以下の記事のネットワーク構造を使いまわします。変更点は、今回は年齢そのものの出力を目指すので最終層を活性化しないというところです。損失関数と最適化アルゴリズムも適当に変更します。
 Keras で少ない画像データから学習したい - クッキーの日記

結果

何かを学習したような雰囲気はありつつも概して駄目でした。データが足りません。
全体的に幼い方に誤っているので最終層のバイアスを大きくするだけでもっと最小2乗誤差が小さくなりそうです(そうなっていないのは学習を早々に止めすぎました)。

f:id:cookie-box:20170814235401p:plain:w560

スクリプト

画像は最初に全て読み込まず、ミニバッチ学習のたびにミニバッチの分だけ読み込むという方法にしています(というか今回の目的がそのような処理の練習でした)。keras.preprocessing.image.ImageDataGenerator の flow_from_directory や flow を活用したかったのですが上手くできなかったので愚直な書き方になりました(flow_from_directory はクラス分類学習の際に cat や dog などというサブフォルダを切って猫画像データや犬画像データを配置しておけば勝手にミニバッチ毎の読み込みを正解データ生成含めてやってくれますが、今回はクラス分類ではなく回帰であり、個々の画像に固有の数値の正解データを生成したいため利用できませんでした)。

from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Dropout, Flatten, Dense
from keras import backend as K
import os
import numpy
import cv2
import pandas

# 顔画像ファイル名から年齢を抽出
def filename2age(filename):
  tempstr = filename.replace('.PNG', '')
  tempstrs = tempstr.split('_')
  return(int(tempstrs[2]))

# ファイル名のリストからデータをロード
def load_data(dirname, filenames, img_width, img_height):
  x = []
  y = []
  for filename in filenames:
    image = cv2.imread(dirname + filename)
    image = cv2.resize(image, (img_width, img_height))
    age = filename2age(filename)
    x.append(image)
    y.append(age)
  x = numpy.array(x)
  x = x.astype('float32') / 255.
  y = numpy.array(y)
  return(x, y)

# メイン処理
if __name__ == '__main__':
  img_width, img_height = 128, 128
  if K.image_data_format() == 'channels_first':
    input_shape = (3, img_width, img_height)
  else:
    input_shape = (img_width, img_height, 3)
  
  # モデル構築
  model = Sequential()
  model.add(Conv2D(32, (3, 3), activation='relu', input_shape=input_shape))
  model.add(MaxPooling2D(pool_size=(2, 2)))
  model.add(Conv2D(32, (3, 3), activation='relu'))
  model.add(MaxPooling2D(pool_size=(2, 2)))
  model.add(Conv2D(64, (3, 3), activation='relu'))
  model.add(MaxPooling2D(pool_size=(2, 2)))
  model.add(Flatten())
  model.add(Dense(64, activation='relu'))
  model.add(Dense(1))
  model.compile(loss='mean_squared_error', optimizer='rmsprop')
  
  # 学習
  filenames = numpy.array(os.listdir('data/')) # 30 files
  batch_size = 10
  n_batch_loop = 3 # 30 / 10
  epochs = 50
  for e in range(epochs):
    print('Epoch', e)
    indices = numpy.repeat(range(n_batch_loop), batch_size)
    numpy.random.shuffle(indices)
    print(indices)
    for i_batch_loop in range(n_batch_loop):
      filenames_temp = filenames[indices == i_batch_loop]
      x, y = load_data('data/', filenames_temp, img_width, img_height)
      model.fit(x, y, epochs=1, batch_size=batch_size)
  
  # 学習結果の確認 (訓練データ)
  x, y = load_data('data/', filenames, img_width, img_height)
  result = pandas.DataFrame(filenames, columns = ['filename'])
  result['actual'] = y
  result = pandas.concat([result,
    pandas.DataFrame(model.predict(x), columns = ['predict'])], axis=1)
  result.to_csv('train.csv', index=False, encoding='utf-8')
  
  # 学習結果の確認 (テストデータ)
  filenames = numpy.array(os.listdir('test_data/'))
  x, y = load_data('test_data/', filenames, img_width, img_height)
  result = pandas.DataFrame(filenames, columns = ['filename'])
  result['actual'] = y
  result = pandas.concat([result,
    pandas.DataFrame(model.predict(x), columns = ['predict'])], axis=1)
  result.to_csv('test.csv', index=False, encoding='utf-8')

Keras で少ない画像データから学習したい

深層学習で画像分類をしてみたいときとかあると思います。でも画像を用意するのが面倒です。すると Keras ブログに「少ないデータから強力な画像分類」とあります。GitHubスクリプトもあります。これを使ってみます。
Building powerful image classification models using very little data
fchollet/classifier_from_little_data_script_1.py - GitHub

データの準備

Google 検索で出てきた順にカレーとラーメンの画像を保存しました。でも時間がなかったので訓練用とテスト用に各クラス10枚ずつしか保存しませんでした。Keras ブログに書いてあるように、「少ないデータ」とは少ないといえども "just a few hundred or thousand pictures" ということなので、真面目にやる場合はきちんと用意してください。これらの画像は Keras ブログの指示通り、data/train/curry/ のようにフォルダを切った下に置きます。

訓練

このカレーとラーメンの画像分類を、畳み込み層とプーリング層を積み上げたニューラルネットワークで訓練するわけですが、少ないデータを最大限に活用して学ぶために、画像を常にランダムに少しの回転や平行移動、シア変形(平行四辺形のように歪める変形)、拡大縮小、左右反転などをさせながらモデルに入力します(どれだけの変形まで許容するかは自分で指定します)。こうすることによって過学習が抑制されモデルの汎化性能が上がるそうです。

結果

訓練データが20枚というやる気のなさでもテストデータの9割を正解してくれました(下表)。何回か訓練を試行するとテスト正解率が100%になることも多かったです。

f:id:cookie-box:20170810000634p:plain:w480
なお「カレー」と誤判断されたラーメンは以下のサイトの「天日地鶏」というお店のもので、ニューラルネットはこのチャーシューがカレールーに、麺とネギがライスに見えたのかもしれません(?)。
キングオブ静岡ラーメン~人気ランキングTOP10|静岡新聞SBS-アットエス
「ラーメン」と誤判断されたカレーは以下でした。何がラーメン要素だったのかよくわかりません。
​新潟発。衝撃200円カレーが東京に初進出 - エキサイトニュース(1/2)
逆に以下のあまりカレーに見えない MOKUBAZA のチーズキーマカレーはカレーに判定されました。ラーメンにも見えませんが。
【保存版】2015年のカレーまとめ / もっとも印象に残ったお店20選 | ロケットニュース24

スクリプト

ほぼ GitHubスクリプトの通りですが、サンプル数を合わせてあるのと、最後に個別データの確率を csv 出力する部分を追加してあります。

from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras import backend as K
import pandas

if __name__ == '__main__':
  img_width, img_height = 150, 150 # 訓練時の画像サイズ
  
  train_data_dir = 'data/train'
  validation_data_dir = 'data/validation'
  nb_train_samples = 20
  nb_validation_samples = 20
  epochs = 50
  batch_size = 5
  
  if K.image_data_format() == 'channels_first':
    input_shape = (3, img_width, img_height)
  else:
    input_shape = (img_width, img_height, 3)
  
  # モデル構築
  model = Sequential()
  model.add(Conv2D(32, (3, 3), activation='relu', input_shape=input_shape))
  model.add(MaxPooling2D(pool_size=(2, 2)))
  model.add(Conv2D(32, (3, 3), activation='relu'))
  model.add(MaxPooling2D(pool_size=(2, 2)))
  model.add(Conv2D(64, (3, 3), activation='relu'))
  model.add(MaxPooling2D(pool_size=(2, 2)))
  model.add(Flatten())
  model.add(Dense(64, activation='relu'))
  model.add(Dropout(0.5))
  model.add(Dense(1, activation='sigmoid'))
  
  model.compile(loss='binary_crossentropy', optimizer='rmsprop', metrics=['accuracy'])

  # 訓練データのプレ処理の設定: RGB値のスケーリングに加え、シア変形や拡大縮小によって訓練増強
  train_datagen = ImageDataGenerator(rescale=1. / 255, shear_range=0.2, zoom_range=0.2, horizontal_flip=True)
  # テストデータのプレ処理の設定: こちらはRGB値のスケーリングのみ
  test_datagen = ImageDataGenerator(rescale=1. / 255)
  
  # 訓練データをディレクトリ内のデータから随時作成するジェネレータ
  train_generator = train_datagen.flow_from_directory(train_data_dir,
    target_size=(img_width, img_height), batch_size=batch_size, class_mode='binary')
  # テストデータ作成をディレクトリ内のデータから随時作成するジェネレータ
  validation_generator = test_datagen.flow_from_directory(validation_data_dir,
    target_size=(img_width, img_height), batch_size=batch_size, class_mode='binary')
  
  # 訓練
  model.fit_generator(train_generator, steps_per_epoch=nb_train_samples//batch_size,
    epochs=epochs, validation_data=validation_generator, validation_steps=nb_validation_samples//batch_size)
  # 訓練済重みの保存
  model.save_weights('weight.h5')
  
  # 具体的に個々のデータに対してどのような確率分布になったかの出力
  train_generator = test_datagen.flow_from_directory(train_data_dir,
    target_size=(img_width, img_height), batch_size=1, class_mode='binary', shuffle=False)
  validation_generator = test_datagen.flow_from_directory(validation_data_dir,
    target_size=(img_width, img_height), batch_size=1, class_mode='binary', shuffle=False)
  
  predict = model.predict_generator(train_generator, steps=nb_train_samples)
  prob = pandas.DataFrame(predict, columns = ['curry'])
  prob['ramen'] = 1.0 - prob['curry']
  prob['predict'] = prob.idxmax(axis=1)
  prob.to_csv('train.csv', index=False, encoding='utf-8')
  
  predict = model.predict_generator(validation_generator, steps=nb_validation_samples)
  prob = pandas.DataFrame(predict, columns = ['curry'])
  prob['ramen'] = 1.0 - prob['curry']
  prob['predict'] = prob.idxmax(axis=1)
  prob.to_csv('test.csv', index=False, encoding='utf-8')

雑記: 最近のふぁぼより

後で読もうとふぁぼって結局読まないこととかあると思います。なので最近どんなことをふぁぼったか復習してみたんですが結局まだ読んでいません。

「A Distributional Perspective on Reinforcement Learning

 [1707.06887] A Distributional Perspective on Reinforcement Learning

強化学習において Bellman 最適方程式による行動価値の更新のとき、期待値に更新するのではなく分布のまま扱おうという話のようです。期待値で更新する方法でもリスク回避的な学習にはできるけどって言っていると思います。行動価値を分布として保持するのはイメージが湧くような気がしないでもないですがそれでどう方策を決めるのかはまだ全然読んでいないのでわかりません。確率的に行動するんだろうと思います。

「Reducing Dimensionality from Dimensionality Reduction Techniques」

 Reducing Dimensionality from Dimensionality Reduction Techniques

次元削減手法(PCA、t-SNE、自己符号化器)に関する解説のようです。

「深層学習とベイズ統計」

 深層学習とベイズ統計

@yutakashino 様のスライドです。決定論的モデルであるニューラルネットを確率論的モデルに modify したいため、ニューラルネットの各重みパラメータ  \theta を「ある分布からの実現値」として取り扱おうという話だと思います。
_人人人人人人人人人人人人人人人人_
> 学習の収束のふるまいがキモい < (スライド13頁)
  ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄

「Deep Reinforcement Learning, Decision Making, and Control」

 https://sites.google.com/view/icml17deeprl

深層強化学習のチュートリアルのようです。ICML2017 で講演があったのでしょうか。

「物理のいらない量子アニーリング入門」

 物理のいらない量子アニーリング入門 - Platinum Data Blog by BrainPad

量子アニーリング入門ということです。github でコードが公開されているということです。
GitHub - ohtaman/anneal

「Fundamentals of Nonparametric Bayesian Inference (Cambridge Series in Statistical and Probabilistic Mathematics)」

Fundamentals of Nonparametric Bayesian Inference (Cambridge Series in Statistical and Probabilistic Mathematics)Fundamentals of Nonparametric Bayesian Inference (Cambridge Series in Statistical and Probabilistic Mathematics)
Subhashis Ghosal Aad van der Vaart

Cambridge University Press 2017-06-26
売り上げランキング : 2101

Amazonで詳しく見る
by G-Tools

比較的最近出た本らしいです。670ページらしいです。購入したいですが置く場所がありません。

「On the State of the Art of Evaluation in Neural Language Models」

 [1707.05589] On the State of the Art of Evaluation in Neural Language Models

自然言語処理には結局 LSTM がよいという話のようです。なんかもう state-of-the-art がタイトルになってしまいました。

「~簡単!炎上のさせ方講座~」

@jagarikin 様のツイートより。炎上のさせ方が簡単にわかります。
ただ臨場感あふれる炎上にするにはコツが要るらしく、適当な実装では上手くいきませんでした(下図)。なんか地面が全体的にのっぺり燃えているようになってしまい、点々と炎を吹いているようにならなかったです。炎上のさせ方は奥が深いようです。また頑張ります。

f:id:cookie-box:20170808113251g:plain

あと PyTorch とか最近話題ですね。

岩波データサイエンス Vol.6: ノート1

6月下旬に以下の本が出ましたので、この本を読んで時系列解析の復習ポイントだと思ったことと、本の記述に対して自分で考えたことを書きます。誤っている可能性があります。
岩波データサイエンス Vol.6岩波データサイエンス Vol.6
岩波データサイエンス刊行委員会

岩波書店 2017-06-23
売り上げランキング : 944

Amazonで詳しく見る
by G-Tools

  • そもそもなぜ時系列解析には状態空間モデルなのかというと、時系列を予測したい場合は、本質的に、「観測できない真のダイナミクス」と「ノイズ」を分離する必要があるからだよという話。ノイズまで学習してしまったら予測にとっては害だから(「しばしば悲惨な結果をもたらす」5ページ)。
  • 直接観測できない真の姿を状態という。状態を観測するときにノイズが加わるし(観測ノイズ)、その状態が時間発展するときにもノイズが加わる(システムノイズ)(7ページ)。
    • 線形・ガウス型状態空間モデルとは発生する観測ノイズ  v_n とシステムノイズ w_n が平均ゼロのガウシアン(分散は時変でよい)、一期前の状態を現在の状態にうつす写像 F_n が線形(時変でよい)、システムノイズが状態に加わるときの変換  G_n も線形(時変でよい)、観測も線形 H_n(時変でよい;観測ノイズは平均ゼロのガウシアンのまま観測に加わるとする)の状態空間モデルを特にそういう(7~8ページ)。
       x_n = F_n(x_{n-1})+G_n(v_n)
      y_n = H_n(x_n) + w_n
      この式をみるとなんでシステムモデルの方のノイズだけ  G_n が付いてるんだと思うけど、平均ゼロのガウシアンを G_n で線形変換するというのは平均ゼロとは限らないガウシアンにするということで、例えば海の上で漂流していて陸地までの距離を知りたいとき(下図;2015-12-29の日記からリサイクル)沖の方に流される風が吹いていたら「大気によるノイズ」はゼロでないドリフト成分をもつだろう。「それって状態の時間発展 F_n の方に入らないの?」って思うかもしれないけど、ケースバイケースだと思う。状態空間モデルを描くとき現実のシステムを思い浮かべて描くわけで、「うーんこの要素はシステムの範疇」だったら  F_n に含めるし、システム外と考えるのだったら  G_n に含めると思う。他方観測ノイズはドリフトしないのは「観測ノイズがドリフトすんな」という要請だと思う。仮に何らかのドリフト要素があるなら  H_n に含めましょうということなのだと思います。
      f:id:cookie-box:20151229173717p:plain:w400
  • 「パラメータ推定に使われる尤度も、予測値だけでは計算できない(11ページ)」
    • 観測データ  y_0 を説明するモデルを  p(y | \theta) と考えているとする。パラメータ  \theta の尤度関数は  f(\theta) = p(y_0 | \theta) となる。もし  p(x | \theta)デルタ関数だったら(分布を考えず点のみ予測するのだったら)  p(y_0 | \hat{\theta})=1 を与える  \hat{\theta} を除いて尤度がゼロになってしまいパラメータが尤もらしいかどうかの比較ができない。尤度関数が微分できないので最尤法もしづらい。たぶん。なお、時系列の場合の尤度関数は  f(\theta) = p(y_{0:n} | \theta) = \prod_{i=0}^{n} p(y_{i} | \theta)
  • 季節調整法で、トレンド成分、季節成分に加えて定常AR成分を導入すると予測がよくなることがある(20ページ)。
    • 定常過程とは: 弱定常過程 - クッキーの日記
      • 時系列から任意の10時点の値を取ってきたときにそれらがしたがう同時確率分布と、さらにそれぞれの30ステップ後の10時点の値を取ってきたときにそれらがしたがう同時確率分布が同じ。時系列をプロットしていったとき、プロットはずっと水平な帯の中におさまる。
    • 「定常モデルと非定常モデルでは長期予測においてまったく働きが異なる(20ページ)」: ここが何を言いたいのかと思ったんだけど、次の文章と合わせると、定常モデルは「繰り返されるパターンが何か」を表現しさえすればよいけど、非定常モデルは「繰り返されるパターン」「あと時系列全体の成長トレンド」を同時に表現することになるので働きが異なるということなんだろうと思います。
    • ここでいう「トレンドが波打っている」というのは、季節調整で排除できない、「商品が売れた月の翌月は一時的に売れ行きが鈍る」のような短期変動なのだろうと思います。
  • 拡張カルマンフィルタ(言葉と参考文献のみ;20ページ):
  • 混合ガウス分布近似で「正規分布の項数が時間の進行とともに爆発的に増大する(21ページ)」:
    • 何が爆発するのかわからないんですが、毎ステップか数ステップ毎に何か情報量基準を最小化するように混合正規分布をその個数も含めてフィッティングし直すということなんでしょうか。
  • R の dlm はつかったことがあります。