損失関数の出力サンプル / Output sample of Loss function

SMEと交差エントロピーの具体的な出力を見て見ます。交差エントロピーに非対称性があること、複数次元の出力の出力の仕方がわかると思います。

import numpy as np

# 2乗和誤差
def mean_squared_error(y, t):
  # ニューラルネットワークの出力と教師データの各要素の差の2乗、の総和
  return 0.5 * np.sum((y-t)**2)

# 交差エントロピー誤差
def cross_entropy_error(y, t):
  delta = 1e-7 # マイナス無限大を発生させないように微小な値を追加する
  return -np.sum(t * np.log(y + delta))

以下を参照させていただきました: Python vs Ruby 『ゼロから作るDeep Learning』 4章 損失関数 (loss function) の実装 - Qiita

そもそもの違いを見ます。数字の差にあまり意味はありません。

t = 0.5 # 正解データ
y = 0.4 # 出力データ
print("MSE:" , mean_squared_error(y,t))
print("CEE:" , cross_entropy_error(y,t))
MSE: 0.0049999999999999975
CEE: 0.45814524093709313

MSEでは、位置による距離は変わりません。交差エントロピーは位置により出力が変化します。

t = 0.8 # 正解データ
y = 0.7 # 出力データ
print("MSE:" , mean_squared_error(y,t))
print("CEE:" , cross_entropy_error(y,t))

t = 0.6 # 正解データ
y = 0.5 # 出力データ
print("MSE:" , mean_squared_error(y,t))
print("CEE:" , cross_entropy_error(y,t))

t = 0.2 # 正解データ
y = 0.1 # 出力データ
print("MSE:" , mean_squared_error(y,t))
print("CEE:" , cross_entropy_error(y,t))
MSE: 0.005000000000000009
CEE: 0.2853398408652799
MSE: 0.0049999999999999975
CEE: 0.41588818833597924
MSE: 0.005000000000000001
CEE: 0.4605168185989092
t = 0.95 # 正解データ
y = 0.85 # 出力データ
print("MSE:" , mean_squared_error(y,t))
print("CEE:" , cross_entropy_error(y,t))

t = 0.15 # 正解データ
y = 0.05 # 出力データ
print("MSE:" , mean_squared_error(y,t))
print("CEE:" , cross_entropy_error(y,t))
MSE: 0.0049999999999999975
CEE: 0.15439287125818693
MSE: 0.004999999999999999
CEE: 0.4493595410333986

MSEは入れ替えても出力は変わりません。交差エントロピーは入れ替えると出力が変化します。

t = 0.5 # 正解データ
y = 0.4 # 出力データ
print("MSE:" , mean_squared_error(y,t))
print("MSE:" , mean_squared_error(t,y))

print("CEE:" , cross_entropy_error(y,t))
print("CEE:" , cross_entropy_error(t,y))
MSE: 0.0049999999999999975
MSE: 0.0049999999999999975
CEE: 0.45814524093709313
CEE: 0.27725879222398614

3次元における出力を見てみます。

t = [0.1, 0.3, 0.7] # 正解データ
y = [0.0, 0.2, 0.8] # トライデータ
print("MSE:" , mean_squared_error(np.array(y),np.array(t)))
print("CEE:" , cross_entropy_error(np.array(y),np.array(t)))
MSE: 0.015000000000000006
CEE: 2.250841187246052

損失関数の種類 / Types of Loss Function

機械学習における損失関数(Loss Function)の位置付けは、作成した学習ロジックに対する評価指標です。

 

正解データ z (zは実数の集合) に対し、入力データ x (xは実数の集合) を学習ロジックf に投入して得られたデータ y (yは実数の集合でzと同じ配列数) があった時、yがzに近ければ近いほど、 学習ロジック f は優秀であるといえます。

 

「近い」という尺度の概念は、1つの数直線上の評価です。つまり、ただの1次元の数字になるので、y = f(x) と z に対する損失関数 L (y, z) は 実数への写像になります。

 

2点間を測る指標です。片方(z)の方がより「正しく」て、検査対象(y)が正しいデータにどれだけ近いかを測るのが損失関数だと思えば、大まかな把握ができると思います。

 

ただし、いわゆる距離ではありません。zから見たyの損失とyから見たzの損失は必ずしも一致しないため、対象律が成立していません。距離函数 - Wikipedia

 

損失関数としては「クロスエントロピー (交差エントロピーとも言います、cross entoropy)」と「平均二乗誤差/MSE (Mean Squared Error)」がよく使用されています。

 

より直感的なのは、平均二乗誤差です。2つの実数 a ,b があった場合、比較に二乗平方根
 

{ \displaystyle \sqrt{(a-b)^2} }

がよく使われます。発想としては、これを複数の変数に対応して、平方根を外したものになります。平方根はとってもとらなくても順序は特に変わらないので、無駄な計算を省くために外しているものと考えると良いと思います。

こちらのサイトによくまとまっています。

www5e.biglobe.ne.jp

クロスエントロピーは、もう少し複雑で、2つデータを、それぞれのデータがもつ複雑さを表す指標(自己情報量と言います)で比較する手法です。

 

平均二乗誤差(MSE)は、回帰分析で使用されます。データ分類ではクロスエントロピーを使用します。

 

それぞれの具体的な出力をこちらの記事に書きました。

損失関数の出力サンプル / Output sample of Loss function - neuralnetな日記


 

ニューラル常微分方程式 / Neural Ordinary Differential Equations

昨年末にとても面白い論文を見つけましたので、合間の時間を見つけて読み進めています。

arxiv.org

この論文は、機械学習の分野でもっとも権威のある国際学会、NeurIPSにて2018年度に最優秀論文として評価されている論文です。
ミーハー心と興味本位で読み進めていましたが、読めば読むほど面白く読み応えのある論文です。

従来までのニューラルネットワークは、脳をモデルとして作られています。一見アナログ的ですが、数学的にはとてもデジタルな存在で、自然数個のノードを繋げてネットワークを構成しています。
近年のネットワークの層は年々ディープになり、2016年にMicrosoftから発表されたResNet(残差ネットワーク)は実に152層のノードから構成されています。

ResNetは勾配消失問題を解決し、より深い学習を可能にしましたが、一方で多くのメモリと多くの時間を要する存在になっていきました。
なにより、この152という数字には特別な意味はなく経験則的なアプローチから生み出された結果に過ぎません。
この曼荼羅のようなネットワークの状態をみて、どうにかならないものかと思った人も少なくないのではないかと思います。

ニューラル常微分方程式(Neural ODE)は、ここに対して非常に素晴らしいアプローチを提供しています。単純に数を増やすのではなく、デジタル的なネットワークの状態をアナログ化しているのです。イメージとしては今まで自然数で数えていたものを小数を含めて考えるアプローチに近いです。より正確にいうと離散化状態だったネットワークを連続的にします。

新しいネットワークの構造から比較すると、従来までのニューロンをベースに作られた概念の方が、むしろデジタルといいますか、カクカクとしたもののように見えてきます。そのぐらいネットワークの構造が滑らかに感じます。私の主観かもしれませんが、きっとそのように理解された方もいらっしゃると思います。

これによりニューラルネットワークの計算に常微分方程式(以下、ODE)ソルバーを導入することが可能になりました。最適化の手法に従来までのバックプロパゲーション(誤差逆伝搬法)ではなくアドジョイント法を用いているということも素晴らしいのですが、結果として解法ロジックを分離し、すでに研究しつくされた優秀なODEソルバーを採用すればよいという発想も素晴らしいと思います。

この構造変化の適応先はバラエティ豊かで、近年の画像処理の雄であるResNet、時系列処理の常識であるRNN(リカレントニューラルネットワーク)に加え、Glowで有名になった情報補完を可能にするNormalizing Flowへも適応できます。

メリットは以下のようなものがあります。
1) メモリが効率的
2) 最適な解法を選択可能
3) パラメータが効率的
4) スケーラブルで可逆的なNormalizing Flowを構築可能
5) 連続的な時系列モデルを実現可能

1のメモリ効率的はビジネス観点からみて有益です。ネットワークが複雑になる程、必要なメモリサイズが増えていかねばならず、高コストでした。また、現存するコンピュータの性能限界で計算できる範囲が限定されることもなくなるという意味でのメリットもあります。

2,3は最適な解法を選択可能,パラメータが効率的は機械学習システムを構築する上でのメリットです。特に2は目的に応じて最適なODEを選択できるようになるので、目的に応じて適切な機械学習手法が選択できるようになるはずです。

4,5は具体的な応用です。時系列モデルについては非常にわかりやすく、今までは自然数単位でしか解析できなかった情報が、連続化されることで任意の時間で取ることができるようになります。

以上がこの論文の概要になりますが、数学的なアプローチについても興味深いものが盛りだくさんですので、以後少しずつ書き進めていこうと思います。

TensorFlow まとめ 続き

前回の続きです。

tf.argmax

数学的なArgmaxは「その中で最大の値をとる集合」です。例えば、[1,3,2,1,3] という数列があった場合、[2番目の3,5番目の3]がArgmaxになります。tfのargmaxは少し違っていて、「最大の数字を取る値のインデックス番号」を返します。この例でいうと、2番目なので、1を返します。(インデックスなので0から始まります)

import tensorflow as tf
sess = tf.Session()

単純な数列の場合

x = tf.constant([1,2,3,2,3]) # 最大値を持つ値(ここでは3)の最初のインデックスを返すので、最初の3のインデックスである2を返す
xd = sess.run(tf.argmax(x))
print(xd)
2

行列の場合は、評価する方向を指定できます。

x = tf.constant([[1,2,3],[2,1,4]])
xd = sess.run(tf.argmax(x,axis=0)) # 行で評価する
print(xd)
xd = sess.run(tf.argmax(x,axis=1)) # 列で評価する
print(xd)
[1 0 1]
[2 2]

tf.cast

型変換をします。tf.cast(x, "float") は xをfloat型に変換します。

参考:

Tensorflow:cast

tf.reduce_mean

テンソルの次元間の要素の平均を計算します。

Tensorflow:reduce_mean

x = tf.constant([[1., 1.], [2., 2.]])
tf.reduce_mean(x)  # 1.5
tf.reduce_mean(x, 0)  # [1.5, 1.5]
tf.reduce_mean(x, 1)  # [1.,  2.]

TensorFlow まとめ

TensorFlowは、計算をあらかじめ記述し、それを後ほど明示的に実行するという流れをとります。「計算の記述」をOpノード、「明示的な実行」をセッションという概念で行います。

こちらの記事がよくまとまっています。
qiita.com
少し昔の記事なので、initialize_all_variablesはobsoletedなことに注意してください。(今はglobal_variables_initializerです)

以下、tfのライブラリの補足です。MNIST Basicに出てくる関数を取り扱います。

前提として、行列の概念を思い出す必要があります。
mathwords.net
をところどころで参考にしてください。

tf.constant

定数です。実際の定数を定義します。

xは1という数字です。 (数学的にはスカラーです)

import tensorflow as tf 
x = tf.constant(1)
print(sess.run(x))

1

yは[3,4]という2次元配列です。(数学的にはベクトルです。つまり1階のテンソル)

y = tf.constant([3,4]) # 2次元の配列
print(sess.run(y))

[3 4]

zは[[3,4],[5,6]]]という2次元配列です(数学的には以下の行列です。つまり2階のテンソル)
{
\left(
  \begin{array}{cc}
    3 & 4 \\
    5 & 6 \\
  \end{array}
\right)
}

z = tf.constant([[3,4],[5,6]]) # 2x2行列
print(sess.run(z))

[[3 4]
[5 6]]

tf.placeholder

セッションの外側からデータを入れるために使います。プログラミング的には、関数の入力パラメーターが、セッションに対するプレースホルダーとなります。constantは実体を指定していますが、placeholderと次に出てくるvariableは、構造を指定しているのがポイントです。

1次元(=スカラー)
import tensorflow as tf 
sess = tf.Session()

x = tf.placeholder("float") # 特に指定しない場合は、1次元の数字、つまりスカラー
double = tf.add(x,x)
r = sess.run(double, feed_dict={x:2})
print(r)

4.0

2次元(=ベクトル、1階のテンソル)
x = tf.placeholder(tf.int32,[2]) # 2次元を指定
double = tf.add(x,x)
r = sess.run(double, feed_dict={x:[1,2]}) # 実体を入力
print(r)

[2 4]

2x2行列(=は、nxm行列、つまり2階のテンソル)

x = tf.placeholder("float",[2,2]) # 2x2行列を指定
double = tf.add(x,x)
r = sess.run(double, feed_dict={x:[[1,2],[3,4]]}) # 実体を入力
print(r)

[[2. 4.]
[6. 8.]]

tf.Variable

セッションの中で変数として扱われます。

import tensorflow as tf 
x = tf.Variable([2,3], name="counter") # 2x3行列
c = tf.constant([[1,2],[3,4],[5,6]])
x = c
init_op = tf.global_variables_initializer() 
sess = tf.Session()
sess.run(init_op)
r = sess.run(x)
print(r)

[[1 2]
[3 4]
[5 6]]

tf.matmul

数学の「内積」です。

x = tf.placeholder("float", [1, 2]) # 1x2行列 
y = tf.placeholder("float", [2, 1]) # 2x1行列 
z = tf.matmul(x,y) 
init = tf.initialize_all_variables() 
sess = tf.Session() sess.run(init) 
sess.run(z,feed_dict={x: [[3, 4]], y: [[5],[6]]}) # 内積だから 3x5+4x6 = 15+24 = 39
array([[39.]], dtype=float32)

tf.nn

Nerual Network系のライブラリです。

tf.nn.softmax

Softmax(シグモイド)関数です。確率分布、つまり総和が1になっています。

z = tf.placeholder("float", [1, 3]) # 1x3行列
s = tf.nn.softmax(z)
init = tf.initialize_all_variables()
sess = tf.Session()
sess.run(init)
pd1 = sess.run(s,feed_dict={z: [[1,2,3]]})
print("[1,2,3] -> " , pd1) # 和が1
pd2 = sess.run(s,feed_dict={z: [[1,2,2]]})
print("[1,2,2] -> " , pd2) # 和が1
[1,2,3] ->  [[0.09003057 0.24472848 0.66524094]]
[1,2,2] ->  [[0.15536241 0.42231882 0.42231882]]

MNISTの tf.nn.softmax(tf.matmul(x, W) + b) の理解のためにこちらを記載します。

# 2次元ベクトルへのSoftmax回帰
x = tf.placeholder("float", [1, 2]) # 1x2行列
y = tf.placeholder("float", [2, 2]) # 2x2行列
z = tf.matmul(x,y)
s = tf.nn.softmax(z)
init = tf.initialize_all_variables()
sess = tf.Session()
sess.run(init)
pz = sess.run(z,feed_dict={x: [[3, 4]], y: [[5,6],[7,8]]})
ps = sess.run(s,feed_dict={x: [[3, 4]], y: [[5,6],[7,8]]})
print("pz -> " , pz)
print("ps -> " , ps)
pz ->  [[43. 50.]]
ps ->  [[9.1105123e-04 9.9908900e-01]]

続きはこちらです。

LSTM / Long Short Term Memory / 長期短期記憶

LSTMについて。

 

LSTMは、ニューラルネットワークのなかでもRNN(リカレントニューラルネットワーク)に分類されるものです。RNNは時系列データを学習するためのニューラルネットワークで、LSTMはRNNの中でも繰り返し改善が施されたもので、RNNの主流と言っても過言ではないでしょう。画像処理系のCNNとあわせて、昨今のニューラルネットワークの双璧をなしていると個人的には思っています。

 

主たる改善は、LSTM、Long Short Term Memory(長期短期記憶)という言葉が表すように、時系列データのなかから短期的な情報だけでなく、長期的な情報も取り込めるような設計が施されているところにあります。従来のRNNでは勾配消失問題が発生するため、長期的な情報をうまく取り込むことができませんでしたが、LSTMではこの問題が解決されています。

 

LSTMの歴史は(人口知能においては比較的)古く、1997年に論文が発表されています。

http://deeplearning.cs.cmu.edu/pdfs/Hochreiter97_lstm.pdf

その後、LSTMは複数回の改善(Forgate Gate、Peeple Connectioin、Full Gradient)がなされました。

このあたりは、非常に素晴らしい記事であるわかるLSTM ~ 最近の動向と共に - Qiita に詳しく記載されています。

 

LSTMにはLSTM Blockという概念が出てきます。前述までの改善は主にこのLSTM Blockに対してなされているものです。LSTM Blockは太陽系における惑星のようなもので、LSTMを理解するうえで非常に重要な概念ではありますが、これだけではLSTMを理解したというわけにはいきません。LSTMに対する理解の解像度を上げるのであれば、Blockで表示されるチャートで表現される意味を数式で理解することをお勧めします。また、実際にコードを書きたいのであれば、LSTMそのものの理解を実装ベースで深める必要があります。

 

前者についてはイメージはあるのですが、、少し書き出すのに時間がかかりそうです。後者については、TensorflowのLSTMのサンプルが有効だと思います。説明自体は

Recurrent Neural Networks

にあり、コードはGithubにあります。

github.com

特に以下のコードです。

tensorflow/ptb_word_lm.py at master · tensorflow/tensorflow · GitHub

 

2つの特徴的な関数がヒントになります。

BasicLSTMCell

https://www.tensorflow.org/versions/r0.9/api_docs/python/rnn_cell.html#BasicLSTMCell

MultiRNNCell

https://www.tensorflow.org/versions/r0.9/api_docs/python/rnn_cell.html#MultiRNNCell

 

関数を深掘りする前に、このチュートリアルが行っていることを説明しますと、特定の言語サンプルに対する、ある優秀な解析の再現を行っています。

 

特定の言語サンプルとは、PTB(Penn Tree Bank)のことです。以下のサイトで「Basic Examples」として提供されています。

rnnlm.org

該当する、simple-examples.tgzをダウンロードし、tar vxfzで展開すると、data/ ディレクトリの中に、ptb.test.txt (テストデータ) , ptb.train.txt (訓練データ) , ptb.valid.txt (開発データ、ハイパーパラメータの調整用) があります。ptb.test.txtの冒頭を見てみましょう。

no it was n't black monday    

but while the new york stock exchange did n't fall apart friday as the dow jones industrial average plunged N points most of it in the final hour it barely managed to stay this side of chaos ..

ブラックマンデーニューヨーク証券取引所と証券関連の言葉が綴られていますね。

PTBはコーパスというテキストを大量に集めてデータベース化した資料の1つで、データに構造を持たせたものになります。コンパクトにまとめられており、ベンチマークとしてよく利用されるそうです。やや話が逸れますが、日本語のコーパスとしては、小納言というサイトがあります。

KOTONOHA「現代日本語書き言葉均衡コーパス」 少納言

 

次に、ある優秀な解析の部分ですが、2014年に発表されたRNNの正則化に関するZarembaさんの論文に基づいています。

[1409.2329] Recurrent Neural Network Regularization 

 PDFはこちらです。

http://arxiv.org/pdf/1409.2329v5.pdf

 

LSTMでは、過学習を防止するために通常のドロップアウトがうまく機能しないことに対し、ドロップアウトの適応方法を紹介するものです。言語モデリングの応用事例として、上記のPTB(simple-examples)に適応した際の結果が紹介されている他、スピーチの認識、機械翻訳、画像キャプションの生成などへの適応が紹介されています。 

 

BasicLSTMCellについて

 

前置きが長くなりましたが、TensorflowのLSTMに関する関数の1つであるBasicLSTMCellで用いられているLSTMはこの論文に基づいて実装されています。Memory Cellの構造は以下のとおりです。

f:id:neuralnet:20160818220030p:plain

(転載については、Zarembaさんに許可を頂いております)

 

他、97年の初版LSTMに基づいたLSTMCellという関数もあります。

 

(続く)

LSTM/RNNとCNNの組み合わせ

複数言語の同時解釈への応用の観点から、以前からLSTM(もしくは単にRNN)とCNNの組み合わせについて興味がありましたので、調べました。3つほどそれらしい論文があったのでメモを取ります。

 

1. 画像認識と画像抽出のためのLong-term Reccurent Convolution Networks

[1411.4389] Long-term Recurrent Convolutional Networks for Visual Recognition and Description

PDFはこちら: https://arxiv.org/pdf/1411.4389.pdf

動画が対象になっています。動画は、基本的には画像なので、CNNによる処理が有効です。また、連続的なものですから、LSTMが関わってきます。基本的なアプローチはCNNをかけた後、LSTMで処理するモデルです。以下の3つのモデルが紹介されていました。

1)連続的な入力に対して、単一の意味を出力するモデル(動画から「走高跳び」と出力する)

2)単一の入力に対して、連続的な出力をするモデル(静止画から「人」「が」「走っている」と出力する)

3)連続的な入力に対して、連続的な出力をするモデル(動画から「人」「が」「高く」「飛んだ」と出力する)

3はCNNではなくCRFを使っていました。


2. 複数ラベルによる画像分類のための統合フレームワーク

[1604.04573] CNN-RNN: A Unified Framework for Multi-label Image Classification

PDF: https://arxiv.org/pdf/1604.04573.pdf

単一の画像に複数のラベル付けをするためのフレームワークとして、CNNにLSTMの概念を追加しているようですが、すみません、なぜLSTMを追加することで複数のラベルを扱えるようになっているかはあまりわかっていないです。

 

3. CONVOLUTIONAL, LONG SHORT-TERM MEMORY, FULLY CONNECTED DEEP NEURAL NETWORKS

CONVOLUTIONAL, LONG SHORT-TERM MEMORY, FULLY CONNECTED DEEP NEURAL NETWORKS - Patent application

PDF: 

http://static.googleusercontent.com/media/research.google.com/ja//pubs/archive/43455.pdf

タイトルの方は特許です。PDFは論文です。

複数のバラエティーがあるスピーチに対して認識をさせるためのものとして、CNN、LSTM、DNNの組み合わせを紹介しています。CLDNNという呼称を付けており、基本的な構造はConvolutional Layers => LSTM Layers => 全接続 Layersとなっています。英語のスピーチを学習データとして、CNN+LSTMのケース、LSTM+DNNのケースといった風に分析をかけています。

 

多言語、例えば、英語、ドイツ語、フランス語、日本語、中国語、韓国語などを同時に読み込ませ、そこから同時翻訳や文章予測に向けた解釈をするアルゴリズムができれば面白いなというのが、今回の調査のモチベーションでした。残念ながら該当する方法論に対する研究は見つけられませでしたが、近しいアプローチをしている方はそれなりにいるようです。いずれも「2016年」に何らかの動きがあるあたり、まさに今動いているテクノロジーという感じで興味深かったです。

 

自分自身でもロジックを組んでみようと思います。