人工知能(AI)を理解するための旅路


GoogleのDeep Mind社が開発した人工知能AlphaGoが囲碁のチャンピオンに勝ったというニュースは大きな注目を集めました。近年の急激なコンピュータ技術の発達により人口知能技術がロボットや自動車の安全運転、スマホによる自然言語や音声の認識、フィンテック、さらにはIoTのインフラ技術などに応用されるようになってきました。人工知能(AI)技術の基礎は深層学習(deep learning)と言われる分野での研究開発に多くを負っています。深層学習も機械学習(machine learning)の一つの分野であり、または大量のデータを用いてパラメータの学習をするという意味では、データサイエンスと呼ばれている分野における研究にも属します。

  このページでは深層学習及び機械学習の基礎を学びたいと思います。機械学習の分野の中でニューラルネットワークという概念を基にして画像認識や音声識別などの学習アルゴリズムを開発する領域がデープラーニングに対応すると考えます。まずは、ニューラルネットワークという概念について学習するところから始めます。著者も初学者なので少しづつ学習を深めていきたいと思います。機械学習及びディープラーニングの多くのフレームワークはPython及びC++で記述されています。ニューラルネットワークの基本的構造とその実装を理解するための言語としてはPythonが理解できれば十分でしょう。そういう意味では、この領域において必須のプログラミング言語はPythonです。したがって、読者の皆さんもPythonの基礎的な知識を習得していると想定します。Pythonの多様なモジュールを使用しますので、Anacondaのような一括インストーラーを用いてPython3がセットアップ済みであることを前提とします。他方、LinuxやC言語の知識は想定していません。

  ビッグデータとニューラルネットワークの大規模化に伴って、大量の学習演算をすることが必要となってきました。現実的なディープラーニングのソフトを実装して学習を行わせるためには通常のCPUを搭載したコンピュータでは心もとないのが現実です。ディープラーニングのフレームワークの多くはGPU(graphics Processing Unit)をサポートしています。複数のGPUや複数台のCPUマシンを用いた分散学習も始まっています。しかし、このページではCPUを搭載した普通のPCを使用することを前提にしています。OSはLinuxでも、MacOSでもWindowsでも、Python環境がインストールされていれば十分です。

  このページで利用されるPython3のスクリプトなどのコードはすべて、私のGitHubにありますので、そこからダウンロードして、利用してください。ipynbファイルを使用しますので、Jupyter Notebookが必要です。Anacondaをインストールした時は、自動的にインストールされています。Jupyter Notebookの簡単な使用法はJupyter Notebookのページを参考にしてください。

関連記事
Kerasを用いたディープラーニング
Scikit-Learnを用いたニューラルネットワークの実装
OpenCVで画像処理/Mac
Raspiに人工知能を組み込む
Ubuntu 16.04のインストール
ROS(Robot Operating System)のインストールと基本的使用法
Raspberry Pi Mouseロボットでの操作の実際
RaspberryPi 入門
PythonのTutorials/Jupyter Notebook
GitHub repositories

Last updated: 2018.6.10


**************************************************************
パーセプトロン
**************************************************************

最初に、パーセプトロンと呼ばれるモデルについて説明します。パーセプトロンとは最も単純な情報の処理過程のモデルで、複数の入力信号(情報)を受け取って、その情報を加工して出力を送り出すゲート・モデルです。このパーセプトロンというモデルはニューロンの情報処理過程を簡単化したモデルから出ています。
neuron.jpeg
ニューロンの構造とパーセプトロンの関係を見てみるとよく分かります。ニューロン樹状突起に入る信号がパーセプトロンの入力信号で、軸索(末端シナプス)が出力に対応しています。情報を処理する細胞体がパーセプトロンのノードの部分になります。 perceptron1.png
このグラフでは入力数が3になっていますが、実際には、入力の数も樹状突起の数だけありますので、制限はありません。出力信号の数もシナプスの数だけあるので、複数個あることが想定できます。ここでの最も簡単なパーセプトロンのモデルは入力信号が2種類で、出力信号が一つです。  入力信号が ( x 1 , x 2 ) であり、各入力信号に対する重みが ( w 1 , w 2 ) であるとして、出力信号をyとします。uが以下のように重みwをつけて荷重計算されると仮定します。
u = w 1 x 1 + w 2 x 2 このとき、 u θ のとき、出力 y=0 とし、 u > θ ならば出力 y=1
になるような処理がされるとします。これが単純な入力層と出力層の2層からなる2層型パーセプトロンと呼ばれるモデルです。ここで、 θ は閾値と呼ばれるパラメータです。入力信号の処理値がこの閾値を超えると、ゲートが発火するというものです。これを関数形で表現すると y = f ( u ) = { 1 if  u = w 1 x 1 + w 2 x 2 > θ 0 if  u = w 1 x 1 + w 2 x 2 θ となります。
 最もシンプルな2層型パーセプトロンとして、AND型、OR型、NAND型という3種類の処理ゲートが想定されます。AND型は以下の表のような入・出力関係になるケースです。これを真理値表と言います。

AND型ゲート
x_1x_2y
0 0 0
1 0 0
0 1 0
1 1 1

これは2入力がともに1の時にのみ1を出力して、それ以外の時はゼロと出力するゲートです。パラメータ値をどのように設定すればAND型ゲートが作成できるでしょうか。
( w 1 , w 2 , θ ) = (1.0, 1.0, 1.0) とするとき、AND型になります。
( w 1 , w 2 , θ ) = (0.5, 0.5, 0.5) とする場合にも、AND型ゲートが成り立ちます。
 次に、NAND型モデルをとりあげます。真理値表は以下の通りです。
NAND型ゲート
x_1x_2y
0 0 1
1 0 1
0 1 1
1 1 0

例えば、パラメータ値を ( w 1 , w 2 , θ ) = (-0.5, -0.5, -1.0) と設定すると、NAND型ゲートが作成できます。
 次に、OR型ゲートを考えましょう。これは2つの信号のうちどちらかが1であれば、出力を1とするモデルです。以下に真理値表を書きます。
OR型ゲート
x_1x_2y
0 0 0
1 0 1
0 1 1
1 1 1

このOR型ゲートを実現するパラメータ値はどうなるでしょうか。すぐ解ると思いますが、例えば、 ( w 1 , w 2 , θ ) = (1.0, 1.0, 0.5) のケースで考えてみてください。
 以上のことから、パーセプトロンという概念を使用すると、パラメータ値を色々と変えることによりAND型、NAND型、OR型ゲートをそれぞれ実現することがわかりました。そこで、これらのモデルをPythonのコードで実装することを考えましょう。以下にAND型ゲートを組み込むPythonスクリプトを示します。

# coding: utf-8
# AND_gate.py

import numpy as np

def AND(x1, x2):
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    theta = 0.5
    s = np.sum(w*x) - theta
    if s <= 0:
        return 0
    else:
        return 1

if __name__ == '__main__':
    for xs in [(0, 0), (1, 0), (0, 1), (1, 1)]:
        y = AND(xs[0], xs[1])
        print(str(xs) + " -> " + str(y))

新しくディレクトリ(例えば、perceptron)を作成して、このコードをAND_gate.pyという名前で保存してください。このファイルをPythonで開いて、実行してください。真理値表の通りの結果が表示されるはずです。
 パーセプトロンの各パラメータ値を変えると、AND、OR、NAND型になるので、Pythonのスクリプト上でパラメータ値を入力変数に設定しておけば汎用のゲート・モデルが記述できます。以下のスクリプトを見てください。

#coding: utf-8
# OR_gate.py
import numpy as np

def OR(x1, x2):
    x = np.array([x1, x2])
    w = np.array([1.0, 1.0])
    theta = 0.5
    s = np.sum(w*x) - theta
    if s <= 0:
        return 0
    else:
        return 1

if __name__ == '__main__':
    for xs in [(0, 0), (1, 0), (0, 1), (1, 1)]:
        y = OR(xs[0], xs[1])
        print(str(xs) + " -> " + str(y))
		
このスクリプトをOR_gate.pyという名前のファイルでディレクトリ(perceptron)に保存してください。更に、以下のスクリプトをNAND_gate.pyという名前でディレクトリ(perceptron)に保存してください。

#coding: utf-8
# NAND_gate.py

import numpy as np

def NAND(x1, x2):
    x = np.array([x1, x2])
    w = np.array([-0.5, -0.5])
    theta = -1.0
    s = np.sum(w*x) - theta
    if s <= 0:
        return 0
    else:
        return 1

if __name__ == '__main__':
    for xs in [(0, 0), (1, 0), (0, 1), (1, 1)]:
        y = NAND(xs[0], xs[1])
        print(str(xs) + " -> " + str(y))

これらのスクリプトを実行すると、各パーセプトロンの真理値を表示します。以上のパーセプトロンでは、出力値をゼロとする入力信号と1とする入力値の範囲が直線の上側か、下側かで識別できます。線形性を持つと言います。
OR.png

ORゲートで識別化できる領域


 次に、排他的論理和と呼ばれるゲートモデル、XORゲートを取り上げます。XORゲートは2入力信号のうちどちらか一つの信号のみが1であるときにのみ出力を1とし、それ以外の場合にはゼロを出力する。真理値表は以下のようになります。
XOR型ゲート
x_1x_2y
0 0 0
1 0 1
0 1 1
1 1 0

このXORゲートは単純なパーセプトロンでは実現できない。その理由はXORゲートが線形性を持たないからです。言い換えると、出力の値をゼロとする入力信号と1 とする入力信号の範囲が直線の上側か、下側かで差別化できないからです。実はXOR回路はNANDゲートとORゲートでを繋ぎ合わせることで合成できます。NANDゲートの出力とORゲートの出力を ( s 1 , s 2 )
とするとき、XORゲートの真理値を書き直すと以下のようになる。
XOR型ゲート
x_1x_2s_1s_2y
0 0 1 0 0
1 0 1 1 1
0 1 1 1 1
1 1 0 1 0

NANDゲートの出力の値とORゲートの出力の値を入力信号とするANDゲートの論理回路を通せば、XORの真理値を出力することができます。これをPythonスクリプトで実現する。

# coding: utf-8
# XOR_gate.py

from AND_gate import AND
from OR_gate import OR
from NAND_gate import NAND

def XOR(x1, x2):
    s1 = NAND(x1, x2)
    s2 = OR(x1, x2)
    y = AND(s1, s2)
    return y

if __name__ == '__main__':
    for xs in [(0, 0), (1, 0), (0, 1), (1, 1)]:
        y = XOR(xs[0], xs[1])
        print(str(xs) + " -> " + str(y))

このスクリプトを名前XOR_gate.pyとしてディレクトリ(perceptron)に保存してください。上記のPythonスクリプトはすべて同一のディレクトリ(名称は適当でいいです)に保存してください。読み込まれるPythonモジュールはNumPy、AND_gate.py、OR_gate.py、NAND_gate.pyです。
 このXORゲートは3層からなるパーセプトロンとなっています。第1層と第2層の間にNAND型論理回路とOR型論理回路が組み込まれ、それらの出力がAND型論理回路で処理されて第3層に出力されている。最も左側の層を入力層、最も右側を出力層と呼び、それらに挟まれた第2層目を中間(隠れ)層と呼びます。こうして、パーセプトロンを多層に組み合わせると、非線形の識別が可能となります。ニューラルネットワークはこれらの多様なパーセプトロンが多数の層になって形成されます。言い換えると、ニューラルネットワークは非線形性の識別能力を持つデータ処理回路となります。
XOR.jpeg XORゲートの模型図

**************************************************************
ニューラルネットワーク
**************************************************************


 ニューラルネットワークの話に入る前に、ここで、個別パーセプトロンの数式表現を整理します。 以下に一般的な入力シグナルを持つパーセプトロンの図が描かれています。通常、出力信号の数は一般的にm個あると想定されます。このグラフでは 出力シグナルは1つになっています。
perceptron2.jpeg

出力信号数が一つのケース

出力信号はstep function(ステップ関数)を通して出力されています。このステップ関数は活性化(activation)と呼ばれる役割をします。このステップ関数をパーセプトロンの説明では活性化関数(activation function)と呼びます。ステップ関数を含めて、活性化関数として用いられる関数型にはsoftmax関数、sigmoid関数と言われるものがります。(後で、説明します。)一般的に、活性化関数を

ϕ (x)
と表現します。ステップ関数の値が(0,1)のケースや(-1,1)のケースもあります。ステップ関数の値が(-1,1)のケースでは以下の通りに表記されます。

ϕ ( x ) = { 1 if  x > 1 - 1 if  x 1
 パーセプトロンへの入力信号の荷重和zは

z = w 1 x 1 + w 2 x 2 + w 0
と表現されます。パーセプトロンの出力yは活性化関数を通して、一般的な表現としては

y = ϕ ( z )
と表現します。通常、出力信号の数は一般的にm個あると想定されますが、簡単化のために、ここでは一つとしています。
 閾値に対応する部分はバイアスユニットとも言われます。バイアスユニットからの信号を通常の入力信号とは区別して b= w 0 と表記するときは、入力の荷重話は

z = w 1 x 1 + ... + w n x n + b という計算になります。
 別の表現方法も説明します。バイアスユニットからの信号も通常の入力と同列にと扱うために、

x 0 = 1
と置きます。入力信号の数が一般的にn個ある場合の入力信号ベクトルxを、バイアスユニットの分を加えて

x = ( x 0 , x 1 , ... , x n )
と表現します。各入力信号に対する重みwが

w = ( w 0 , w 1 , ... , w n )
であるとします。信号ベクトルと重みベクトルは、バイアスユニットの分だけ要素数が+1されます。入力信号の荷重値zは以下のように計算される。

z = w 0 x 0 + w 1 x 1 + ... + w n x n
以下、同様です。
 活性化関数として採用されるものはステップ関数だけではありません。これから取り上げるニューラルネットワークで重要な活性化関数の一つはシグモイド関数(sigmoid function)と呼ばれる関数です。この関数は、経済学など(広告やイノベーション伝搬)ではロジスティク曲線(logistic curve)と呼ばれています。シグモイド関数(ロジスティク曲線)は

ϕ ( x ) = 1 1 + e x p (-x)

と表現されます。ここで、exp(x)は指数関数です。シグモイド関数とステップ関数のグラフは下図のように比較できます。
sig_step.png

シグモイド関数(実線)とステップ関数(破線)のグラフ


シグモイド関数のグラフは連続的に変化してなめらかですが、ステップ関数のグラフは原点でジャンプします。また、シグモイド関数は入力の値が大きくなるに連れて、限りなく1の値に漸近します。両者の共通点は関数値がゼロと1の間に収まることです。
 さらに、活性化関数として用いられる関数形として、以下に定義されるLeLU(Rectified Linear Unit)関数が使用されます。

ϕ ( x ) = { x if  x > 0 0 if  x 0

 最後に、softmax関数を取り上げます。softmax関数は出力層からの出力信号が複数となるケースに採用される関数です。エントロピー関数を土台として、ロジスティック関数を一般化したものです。以下のように定義されます。出力層がn個の入力信号から構成され、i番目の入力信号をa_iとするとき、k番目の出力y_kは

yk= exp(ak) i=1n exp(ai)

に従って計算される。このsoftmax関数による処理は各出力に確率分布を与えるもので、分類問題に利用されます。Pythonに実装すると以下のようになります。

# coding: utf-8
# softmax.py

import numpy as np

def softmax(a):  
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y=exp_a/sum_exp_a
    return y

if __name__ == '__main__':
    a = np.array([1.0, 2.0, 3.0]) 
    y=softmax(a)
    print(y)
	
exp(a)が非常に大きな数値になることを避けるためのオーバーフローに対する処理をする必要もあります。詳しくは、斎藤康毅著『ゼロから作るDeep Learning』(オライリー・ジャパン社)などの参考書を参照してください。
   ここからは、多層パーセプトロン、つまり、ニューラルネットワークと呼ばれる人工知能で必須の道具立てを取り上げます。行列の積の計算を頻繁に取り扱いますので、Pythonスクリプトでどのように記述するのか説明します。Pythonシェルで以下を実行してください。

>>>import numpy as np
>>>A = np.array( [[1,2],[3,4]] )
>>>B = np.array( [[5,6,7],[8,9,10]])
>>>C = np.dot(A, B) 
>>>print(C)               
[[21, 24, 27],
       [47, 54, 61]]

dot(A,B)は線形代数での行列の積A・Bを計算します。AとBがベクトルの場合はベクトルの内積を計算します。(注意:MATLABなどでの命令文と異なります。)3層パーセプトロンの例を取り上げます。下図を見てください。
3layers.png

3層パーセプトロンのグラフ

入力層(第1層)から第2層(hidden layer)へは4つの信号が入ります。第3層の出力は一つになっていますが、一般的には複数個あります。
 いま、k番目の層のi番目の活性化ユニット(ノード)の出力信号を ai(k) と表記する。グラフの上端に位置するユニット x0a0(2) はバイアスユニットと呼ばれます。第1層の出力信号ベクトルは x =a1(1)am(1)=x1xm と表記する。k番目の層のi番目のユニットからk+1番目の層のj番目のユニットへの信号に対する重みを wji(k)  と表記する。従って、入力層から隠れ層への信号に対する重み係数は行列形式となるので、これを W(1) と表記します。同じように、隠れ層から出力層への信号に対する重み係数は行列形式となるので、これを W(2)  と表記します。隠れ層における各ユニットでの入力信号の荷重和は

z(2)=W(1)a(1)+b(1)
という行列計算式となります。第2層(隠れ層)の出力信号は活性化関数を用いて、

a(2)=ϕ(z(2))
と表現されます。多層ニューラルネットワークでは、各層について、同様な関係式が成立します。

z(3)=W(2)a(2)+b(2)
a(3)=ϕ(z(3))
 ここで、 b(k) は、k番目の層へのバイアスユニットからの信号です。
 以下に、3層ニューラルネットワークの実装の例を示します。第1層のニューロン数は3、第2、3層のニューロン数は2個となっています。第1層と第2層の活性化関数はsoftmax関数で、第3層出力層での活性化関数はsigmoid関数です。ベクトルb1、b2、b3はそれぞれ、第1、2、3層のニューロンへのバイアスユニットからの信号で、通常の入力信号と区別して表記してあります。

import numpy as np

def softmax(x):  
    exp_a = np.exp(x)
    sum_exp_a = np.sum(exp_a)
    y=exp_a/sum_exp_a
    return y
	
def sigmoid(x):
    return 1 / (1 + np.exp(-x)) 
	
def init_network():
	init_net = {}
	init_net['W1']=np.array([[0.1,0.3,0.5],[0.2, 0.4, 0.6]])
	init_net['W2']=np.array([[0.1, 0.4], [0.2, 0.5,], [0.3, 0.6]])
	init_net['W3']=np.array([[0.1,0.3], [0.2, 0.4]])

	init_net['b1']=np.array([0.1, 0.2, 0.3])
	init_net['b2']=np.array([0.1, 0.2])
	init_net['b3']=np.array([0.1, 0.2])
	return init_net

def ppn(init_net, x):
    w1, w2, w3 = init_net['W1'], init_net['W2'], init_net['W3']
    b1, b2, b3 = init_net['b1'], init_net['b2'], init_net['b3']

    z1 = np.dot(x, w1) + b1
    a1 = sigmoid(z1)
    z2 = np.dot(z1, w2) + b2
    a2 = sigmoid(z2)
    z3 = np.dot(z2, w3) + b3
    y = softmax(z3)
    return y
	
init_net = init_network()
x=np.array([1.0,1.0])
y=ppn(init_net, x)
print(y)   
このコードを実行すると、[0.35939292 0.64060708]と結果が表示されます。
 深層学習では、こうした多層ニューラルネットワークの理論上の関係を前提にして、各層の重み係数w1、w2、w3を推定することが課題となります。推定の誤差の大きさを測る関数を損失関数と言います。代表的な損失関数は二乗和誤差で、以下のように定義されます。

L(w)=k(yk-zk)2

ここで、 yk はサンプルkの正解、 zk はサンプルkから予測された出力を表す。 損失関数を重き係数wの関数とする理由は推定すべきパラメータがwだからです。この損失関数を最小にするwの値が推定された(学習された)モデルのパラメータになります。重み係数の推定方法のうちで最も単純な手法は勾配降下法(Gradient Desccent method)と呼ばれるアルゴリズムです。
 二乗和誤差の他に損失関数として用いられる代表的な関数の一つは交差エントロピー誤差(cross entropy error)です。以下のように定義されます。

L(w)=-kyklogzk

ここで、 yk はサンプルkの正解、 zk はサンプルkから予測された出力を表わします。
 ここで、最も単純なパーセプトロン・モデルを用いた重みパラメータの推定問題を見てみましょう。最小二乗法を用いて、パーセプトロンの重みwの値を推定します。この課題に応えるために用意されたデータの一つにiris(アヤメ)データセットがあります。これは3種類のアヤメについて、4つの特徴に関するデータ150個を収集したもです。アヤメの花びらについて知らない読者もいると思われますので、下にアヤメの写真を掲載します。
iris_three_species.jpg
このアヤメのデータセットは、UCI Machine Learning Repositoryから、Pythonのpandasライブラリーを使用して直接読み込むことができます。以下のように、Pythonシェルに打ち込むと

In [2]: import pandas as pd

In [3]: df = pd.read_csv('https://archive.ics.uci.edu/ml/'
   ...:         'machine-learning-databases/iris/iris.data', header=None)
   ...:         

In [4]: df.tail()
Out[4]: 
       0    1    2    3               4
145  6.7  3.0  5.2  2.3  Iris-virginica
146  6.3  2.5  5.0  1.9  Iris-virginica
147  6.5  3.0  5.2  2.0  Iris-virginica
148  6.2  3.4  5.4  2.3  Iris-virginica
149  5.9  3.0  5.1  1.8  Iris-virginica
と表示されます。この出力はデータの最後の5個のサンプルを表示しています。一般的に機械学習では、訓練データとテストデータの2つに分けて、学習を行います。まず。訓練データだけをつかって学習を行い、最適なパラメータwを見つけます。そして、テストデータを用いて、そのパラメータを組み込んだモデルの有効性を評価します。
 この学習において、訓練データから指定した枚数(ミニバッチと言います)を選び出し、このミニバッチごとに学習を行います。例えば、150枚のデータうち、10枚をランダムに選び学習を行います。これをミニバッチ学習と言います。通常は、このミニバッチ学習を何回も行います。150/10=15回のミニバッチ学習を行うことをepoch1回の学習と言います。
 最適なパラメータを求めるためには、モデルがどの程度データにフィットしているかを計測する必要があります。訓練用データとして、アヤメのsetosa種とversicolor種の2種類のデータをもちいます。
 Sebastian Raschka氏がミニバッチ学習用のモデルとコードを提供していますので、それを利用します。ソースは以下の、私のGitHubからダウンロードしてください。
 Jupyter Notebookを起動して、Iris_Perceptron.ipynbを開いてください。このnotebookを上から順番に実行すると、以下のような結果が得られます。訓練したモデルに基づいて、予測されたパラメータ値から計算したモデルの識別予測直線と散布図を描くと下のようになります。図中の直線がパーセプトロンで予測される識別境界です。四角の点がアヤメのsetosa種、×の点がversicolor種に対応します。
predict.png
 詳細な出力の手続きやPythonスクリプトについては、私のGitHubのリポジトリを参照してください。パーセプトロンのアルゴリズムの改良型であるADALINE(ADaptive LInear NEuron)を用いて重みの予測をすると、より優れた識別ができることが知られています。ここでは触れません。上図の識別直線が上方に少し移動することが確かめられます。ただし、3つの品種を線形直線で識別することは不可能です。これが単純パーセプトロン・モデルの限界です。
 次に、多層パーセプトロン・モデルで学習を行うことを考えましょう。多層ニューラルネットワークを用いて、重み係数の推定学習で用いられる有名なデータセットとして、MNIST(Mixed National Institute of Standards and Technology)のデータセットが有名です。これは500人の手書き数字のデータを収集したものです。MNISTデータセットはY. LeCunのWebサイトで公開されています。MNISTデータセットは
トレーニングデータセットの画像:train-images-idx3-ubyte.gz
トレーニングデータセットのラベル:train-labels-idx1-ubyte.gz
テストデータセットの画像:test-images-idx3-ubyte.gz
テストデータセットのラベル:test-labels-idx1-ubyte.gz
の4種類から構成されています。LeCunのWebサイト  で提供されている上記のファイルをダウンロードします。これらのファイルはダウンロードした後、gzipで解凍する必要があります。このデータセットをwotking directoryに配置して、以下のようなPythonコードを実行するとデータが読み込まれます。

#mnist.py
import os
import struct
import numpy as np
 
def load_mnist(path, kind='train'):
    """Load MNIST data from `path`"""
    labels_path = os.path.join('./', 
                               '%s-labels-idx1-ubyte' % kind)
    images_path = os.path.join('./', 
                               '%s-images-idx3-ubyte' % kind)
        
    with open(labels_path, 'rb') as lbpath:
        magic, n = struct.unpack('>II', 
                                 lbpath.read(8))
        labels = np.fromfile(lbpath, 
                             dtype=np.uint8)

    with open(images_path, 'rb') as imgpath:
        magic, num, rows, cols = struct.unpack(">IIII", 
                                               imgpath.read(16))
        images = np.fromfile(imgpath, 
                             dtype=np.uint8).reshape(len(labels), 784)
 
    return images, labels

ただし、このPythonスクリプトが保存されているフォルダーに、上記の解凍されたデータファイルを保存してください。この後、このmnist.pyを実行すると、mnistデータが読み込まれます。以下に読み込まれたデータの一部を掲載します。
number.png

mnist 手書き文字データ


 以上の作業を行うにために必要なスクリプト類は私のGitHubから入手できます。
 3層ニューラルネットワークの重み係数の推計アルゴリズムをPythonで実装したコードを見てみましょう。Jupyter Notebookを起動して、MNIST_MLP.ipynbを開いてください。推計と予測に使用されるモジュールはneuralnet.pyです。出力層に使用される活性化関数はシグモイド関数が採用されています。その理由は、モデルの誤差の大きさを示す損失関数として対数尤度関数(log-likelihood)が使用されているからです。対数尤度関数を用いた誤差損失関数は

J(w)=-i=1n[yilog(ϕ(zi(3))+(1-yi)log(1-ϕ(zi(3))]

と定義されます。ここで、yiはサンプルデータiの正しい数値、ϕ(zi(3)は活性化関数でシグモイド関数が採用されています。予測数値が正しい数値に近づくように、このロジステック回帰モデルで重み係数Wの学習が行われます。このプログラムでの学習方法は、深層学習で定番の誤差逆伝播法(back propagation)が実装されています。誤差逆伝播法の説明に関しては、深層学習の参考書、例えば、斎藤康毅著『ゼロから作るDeep Learning』(オライリー・ジャパン社)などの参考書を参照してください。
 実際の作業を行なってください。説明通りに、MNISTデータをこのnotebookと同じディレクトリに解凍して、MNIST_MLP.ipynbのセルを上から順番に実行してください。
 MNISTデータセットは60000個のトレーニングサンプルと10000個のテストサンプルから構成され、数値データ(0,1,2,3,4,5,6,7,8,9)の手書き画像は28x28=784バイトのサイズで保存されている。よって、入力層のユニット数は784となり、出力信号は10個のユニットからなります。nn = NeuralNetMLPクラスで、隠れ層のユニット数を50としてあります。epochs=800としてありますが、実行時間を気にしなければ、epochs=1000にしてもいいです。
 入力文nn.fitの実行により、正しい重み係数を推定するための学習が開始されます。バイアスユニット分を加えて、第1層の重み係数行列、W1は50x785行列、第2層のそれはW2は10x51行列となっています。各サンプルデータには正解(labels, target values)が付属しているので、これとモデルの予測値が乖離する間は重み係数値の修正を繰り返します。修正方法には勾配降下法(gradient descen method)が使用されます。このプログラムでは1000×6万回の繰り返し計算が行われます(epochs=1000の時)が800×6万回程度で収束します。CPUの速度に依存しますが、時間は20分から30分程度かかります。テストデータにおける識別の正答率は95.62%となっています。4.38%はニューラルネットワークによる識別に誤りが発生しています。この正答率を改善するためには、層の深さをもっと大きくする必要があります。
  類似のPythonコードは斎藤康毅著『ゼロから作るDeep Learning』のGitHub https://github.com/oreilly-japan/deep-learning-from-scratch に置かれているch05/train_neuralnet.pyです。第1層と隠れ層の間にsigmoid関数が挟まれているのはRaschka氏のプログラムと同じですが、相違する部分は、出力層の活性化関数としてsoftmax関数が採用されているところと、損失関数には交差エントロピー(cross entropy)が使用されている部分です。この交差エントロピー誤差を使用するときには、データ入力でone-hot-label=trueと設定する必要があります。
  いずれにしても、ニューラルネットワークの理論では、調整可能な重み係数が想定され、この重み係数をサンプルデータにフィットするように調整することを学習と呼んでいる。勾配降下法を用いた学習過程は以下の3ステップから構成されます。
(1)サンプルデータの中からランダムに一定数のデータを取り出す。
(2)各重み係数に関する損失関数の勾配を求める。
(3)各重み係数を勾配の反対方向に微小量だけ修正する。
を損失関数が最小値の近傍に達するまで繰り返す。
 何れにしても、ニューラルネットワークにおける学習は損失関数の最小値を実現するパラメータの値を推定することです。これは最適なパラメータ(重み係数)を見出す問題で、最適化問題の一つです。ニューラルネットワークの層が深くなるにつれて、パラメータは極めて多数にのぼり、複雑になるので、その最適化は難しい問題です。この最適化問題を解くアルゴリズムとして、様々な手法が提案されています。SGD(stochastic Gradient descent)やAdaGradと呼ばる手法がありますが、その説明は別のページにします。
 PythonライブラリのScikit-learnを活用すれば、多層パーセプトロンモデルを簡単な入力作業で便利に利用できます。scikit-Learnを使った簡単な機械学習
をみてください。

**************************************************************
畳み込みニューラルネットワーク(CNN)
**************************************************************


 deep learningはロボット操作、自動運転、遠隔医療技術、音声探索・案内など様々な分野に実用化され、活用されている。このフレームワークは,MicrosoftのCognitive Toolkit, GoogleのTensorFlow, IBM PowerAI Vision, Caffe2, Theano, Chainer などに代表されています。これらの応用技術の元になっている理論はCNN(Covolutional Neural Networks) と言われる枠組みの上で発展してきたものです。上記の実用技術における具体的な実装プログラムを説明することは私の能力を大きく超えます。とはいえ、「そうなっています」で終わりにするわけにもいかないので、CNNの大まかな説明は必要かもしれません。ニューラルネットワークのPythonライブラリの一つKerasを利用したCNNの実装については、Kerasを用いたディープラーニングの実例に詳しく説明されています。
 パーセプトロン概念を用いた多層パーセプトロンのニューラルネットワークでは、隣接する層のすべてのニューロンが互いに結合していました。これを全結合と呼び、denseなネットワーク(fully connected)となっています。このdenseなニューラルネットワークでは、層と層の間に活性化関数が入っていても、各層のニューロンは前の層のすべてのニューロンからの信号を受け取ります。
 これに対して、CNNはその名前の通り、全結合層が多層化されるのではなく、convolution layer層とpooling layer層(subsampling層ともいう)という新しい層が加わります。言い換えれば、convolution層とpooling層が交互に配置され、最終部分に全結合層が配置される多層ニューラルネットワークです。
 多層パーセプトロンから構成されたニューラルネットワークの問題点は、入力情報の空間的な情報が無視されてしまうことです。入力データが画像の時、画像情報は通常、縦・横・チャンネル(あるいは、チャンネル・縦・横の形式)からなる3次元情報で与えられます。例えば、MNISTデータのケースを見れば明らかな通り、入力画像の形状は(1,28,28)ですが、全結合層に入力する場合、28x28の1次元情報に変換しました。通常の画像データは3次元なので、この操作は画像の空間的な情報を失わせることになります。空間的に近い位置にあるピクセルは似たような性質を持つ、RGBに対応する各チャンネル間には類似の傾向があるという情報を失ってしまいます。
 CNNでは、こうした情報の損失を回避することができます。畳み込み層は形状を維持します。入力データが画像である場合、画像の3次元データを3次元データとして次の層に移します。CNNでは、畳み込み層の入出力データを特徴マップ(feature map)といい、入力データを入力特徴マップ、出力データを出力特徴マップと言います。
 ここで、畳込みについて説明します。畳み込みの計算は少々分かり難いです。畳み込みのカーネル(フィルター)行列Kが3✕3行列のケースが下に描かれています。

convolution_1.png
 カーネルの枠を一定間隔で入力データIの行列枠上をスライドさせます。各スライドごとに、カーネル行列の要素と入力データの対応する要素をそれぞれ乗算し、その総和をもとめて、この総和値を特徴マップ(feature maps)に移します。上の例では1x1+0x0+1x0+0x1+1x1+0x0+1x1+0x1+1x1=4と計算されています。入力データが7✕7行列であるときには、特徴マップのサイズは5✕5行列となります。次のケースは入力データが5✕5行列の場合です。カーネル行列は同じです。

convolution_2.gif
カーネル行列をスライドさせながら、各行列の各要素の乗算の総和を特徴マップに書き入れていきます。その結果、特徴マップは3✕3行列になっています。畳み込みの計算を数式で表現すると、特徴マップの(i,j)要素は


S(i,j)=I*K(i,j)=mnI(i+m,j+n)K(m,n)

となります。iはカーネル行列を横にスライドさせた時のi回目のスライド、jは縦にスライドさせた時の回数です。横に3回スライドして入力行列が覆い尽くされるときは、特徴マップの次数は3となります。
 max poolingとはフィルター行列で覆われた行列中の最大値をその行列の代表値にすることです。2x2フィルターによるmax poolingでは、入力データを2✕2行列ごとに覆い、2✕2行列の中の最大値を取り、その値を出力信号として送ります。この結果データサイズが縮小されます。下の図を見るとよく分かります。

maxpooling_1.png

  下で取り上げる代表的なCNNであるLeNetとAlexNetの構造を見ても理解できる通り、交互に並べられた畳込み層とプーリング層の後に、Classificationと呼ばれる多層パーセプトロンが配置されます。多層パーセプトロンは基本的にはすべてのユニットが結合されている構造を持つので、fully connected layersという言葉が使われています。畳込み層とプーリング層を重ねることによって入力データの各特徴がより明確に抽象化されます。こうして抽象化された各特徴を手がかりとして、最後の多層パーセプトロンの部分が入力データの分類を実行します。このことから、多層パーセプトロンの部分は分類器とも呼ばれる機能を持ちます。

 1998年にLeCunたちによって提案されたCNNが最初のモデルでLeNetと呼ばれています。その構造は下の図のとおりです。

lenet.png

CNNで有名なLeNetの例

 LeNetではcovolutional層とsubsampling層が交互に並んでいます。LeNetの図の例で、5✕5 covolutionという表示は、5行5列のフィルター行列(カーネルとも言う)で畳み込むことを意味しています。2✕2 subsamplingという表示は2✕2行列でデータサイズを縮小することを意味します。
 第1層の畳み込み層で入力データ(32x32サイズ)を5x5のフィルター行列で畳み込みを行い、28x28サイズの出力特徴マップとして出力しています。この特徴マップを2x2サイズのフィルター行列でサブサンプリング(プーリング)を行い、次の畳み込み層に14x14サイズで出力します。この14x14サイズの特徴マップを5x5サイズのフィルター行列で畳み込みを行い、10x10の特徴マップとして、プーリング層に出力します。2x2フィルター行列を持つプーリング層は、5x5サイズの特徴マップを出力して、全結合層(多層パーセプトロン)に入力信号として渡されます。全結合層の出力は、活性化関数を通して、10クラスの分類機にかけられます。
 
 その14年後、2012年にSimardたちが提案したモデルがAlexNet(筆頭著者の名から)と呼ばれるCNNです。covolutional層とpooling層が交互に並んでいます。

alexnet.png

CNNで有名なAlexNetの例

 このAlexNetの例では、5個のcovolutional層と3個のpooling層が存在します。"stride of 4"という表示は4列おきに、フィルター行列を入力データ上でスライドさせることを意味します。ネットワークの最終部分では、分類識別のために、3種類の全結合層が配置されて、最後の層からはsoftmax関数(活性化関数)を通して、1000クラスに対する確率分布の出力が出てきます。これは、ImageNetで採用されている画像の1000クラス分類に対応させるためです。

  CNNの基本構造を易しく分かりやすく説明することを試みました。少しは理解が深まったことと信じます。

**************************************************************
画像認識と一般物体検出
**************************************************************


CNNを基礎とする画像識別や音声認識のソフトウエアを活用する方法についての旅路に入りたいと思います。

 2012年開催された大規模画像認識のコンペILSVRC(ImageNet Large Scale Visual Recognition Challenge) でAlexNetが圧倒的な成績で優勝して以来、ディープラーニングの手法(CNN)が画像認識での主役に躍り出ました。 2014年に開催されたILSVRCでの優勝者はGoogLeNetでした。GoogLeNetは複雑に見える構成ですが、基本的には、AlexNetのようなCNNと本質的には同じです。ただ、GoogLeNetには、縦方向に深さがあるだけでなく、横方向にも深さがあるという点で異なっています。横方向の幅はinception構造と呼ばれています。
 このとき、VGGは2位の成績に終わりましたが、シンプルな構成なので応用面ではよく使用されます。ちなみに、VGGでは、3x3のフィルターによる畳み込み層を2回から4回連続して、プーリング層でサイズを半分にするという処理を繰り返します。重みのある層を16層にしたモデルをVGG16、19層にしたものをVGG19と呼んでいます。
  2015年のILSVRCでの優勝者はResNetで、Microsoftのチームによって開発されたニューラルネットワークでした。その特徴は、層を深くしすぎると過学習などの問題が起きるのを回避するために、スキップ構造(ショートカット)と呼ばる概念を導入した点です。ResNetは、VGGのネットワークをベースにして、スキップ構造を取り入れて、層を深くしています。具体的には、畳み込み層を2層おきにスキップしてつなぎ、層を150層以上まで深くしています。ILSVRCのコンペでは、誤認識率が3.5%という驚異的な成果を出しています。
 以上のVGG、GoogLeNet、ResNetがディープラーニングの代表的なモデルとなっています。最近では、これらのモデルを改善したモデルが登場しています。また、こうしたモデルを組み込んだディープラーニング向けのライブラリが充実してきました。

 代表的なPython APIは、Googleが公開したImage Recognition APIです。GoogleのImage Recognitionサイトです。TensorflowはOSに依存せず、インストールできます。Windows、Linux、Mac OSをサポートしています。

 Microsoftが公開しているcognitive-toolkitのサイトは、Microsoft/CNTKです。WindowsとLinuxをサポートしています。公式には、Mac OSをサポートしていません。

 また、ディープラーニングのpython APIの一つであるKerasを活用すれば、簡単な入力でCNNモデルを構築したり、それを学習させることも容易にできます。VGG16やGoogLeNetなどのCNNモデルがモジュールとして組み込まれているので、自分でモデルを一から構築する必要がありません。ImageNetのデータを用いて学習した学習済みモデルも用意されていますので、訓練用のデータも必要ありません。Kerasを使ってdeep-learningをぜひ読んでみてください。

 コンピュータビジョンの分野における物体検出(Object Detection)とは、ある画像の中から定められた物体の位置とカテゴリー(クラス)を検出することを指します。物体検出の問題は、性質のわからない、数が不明な物体をビデオ画像やカメラ画像から特定するという問題になります。だから、画像からの物体検出は、同時に2つの問題を解決する必要があります。画像の特定の領域が物体であるかどうかを判断し、どの物体であるかを調べる。後者の部分は画像分類にあたるので、上で取り扱ってきた画像識別のモデルを使えば解決できます。が、前者の部分と後者の部分を同時に解決するためには新しいモデルが必要です。
 Deep Learning(CNN)を応用した一般物体検出アルゴリズムで代表的なものとしては、3種類挙げられます。R-CNN (Regions with CNN features)の発展系Faster R-CNN、YOLO(You Only Look Once)、SSD: Single Shot MultiBox Detectorに大別できます。
 2017年1月、Facebookの研究チームであるFacebook AI Research(FAIR)が物体検出ライブラリであるDetectronをオープンソースとして公開しました。Facebookのrepositoryはここです。Detectronは下で取り上げるディープラーニング向けライブラリのCaffeを基礎としています。GPU+Linux+Python2を必要とします。Caffe系のライブラリはPython3では正常に機能しません。

   Keras+Tensorflowを用いた物体検出問題に対するPython実装については、Tenorflowのページを参照してください。

  自動車の自動運転を実現するためには、センサーから入手したデータから一般物体(人間、車、歩道、樹木など)の検出を可能とするソフトウエアが必要です。一般物体検出ソフトのフレームワークの一つにSegNetと呼ばれるCNNがあります。SegNetはケンブリッジ大学の研究グループが2015年に開発した畳み込みニューラルネットワークです。論文は( Vijay Badrinarayanan, Alex Kendall, Roberto Cipolla,「SegNet: A Deep Convolutional Encoder-Decoder Architecture for Image Segmentation」)、ホームページは
http://mi.eng.cam.ac.uk/projects/segnet/になります。

このホームページにwebdemoが掲載されていますので、自分の写真をアップして物体検出を実行してみると面白いです。自動車から撮影された動画実例と検出結果もアップされています。楽しんで見てください。
    SegNetは以下に示すような畳み込みニューラルネットワークの構造を持っています。  
segnet.png
左側の画像は自動車のカメラで撮影された画像で、これが入力画像としてニューラルネットワークでセグメンテーション処理されて出力された結果が右側の画像になります。
  SegNetの基礎になっているフレームワークはCaffeと呼ばれるCNNです。Caffeはカリフォルニア大学バークレー校の研究グループが開発したディープラーングの代表的なフレームワークになっています。ホームページは

http://caffe.berkeleyvision.org/にあります。

現在では、Berkeley Vision and Learning Center(BVLC)を中心として、コミュニティの研究者たちがGitHub上で開発/改良を重ねています。Caffeには「Model Zoo」という学習済みのモデルを配布するフレームワークがあります。Deep LearningではCNNのパラメータをゼロから学習することが難題となりますが、Model Zooを用いることでこの難題を避けることができます。学習済みのモデルを用いて実験をすることができそうですが、実際にスクリプトをダウンロードして自分のPC(ubuntu16.04)で実行すると、googleのprotobufに関するエラーが出てしまいます。C++でのmakeとPythonモジュールとの相性が悪くて実現できていません。GPUも搭載していませんので、スクリプトを実装して実験することは止めておきます。Python3では正常に作動しないようです。Python3を利用している場合には、以下の説明は無視してください。

  最近、FacebookがCaffeの軽量バージョンとしてCaffe2をオープンソース・ソフトウエア型式で公開しました。Caffe2のすべての機能を活用することはまだ出来ていませんが、初歩的な部分は利用可能です。手持ちの画像から物体を検出することを試みましょう。Caffe2のホームページは

https://caffe2.ai

です。このサイトに使用上のtutorialsがあります。ソースコードは以下のGitHubからダウンロードできます。

https://github.com/caffe2/caffe2


このGitHubのrepositoryを/home/ディレクトリにダウンロードして展開します。caffe2-masterというディレクトリが作成されますので、このディレクトリ名をcaffe2という名称に変更してください。このディレクトリの下に
caffe
caffe2
script
module
....
のようにいくつかの階層構造のディレクトリが作成されています。/caffe2/caffe2/python/tutorials/の中にテスト用のファイルが配置されています。テスト用には、学習済みのModel Zooのモデルを使用しますが、このtutorialsの中に入っていません。
Caffe及びCaffe2には、AlexNet、GoogleNet、VGGなど、多数の学習済み畳み込みニューラルネットワークのモデル(pretrained models)が収集されています。これらはModel Zooという名称で呼ばれているフォルダーに配置されています。Model ZooモデルはGitHubのrepository
https://github.com/caffe2/models/


にあります。これをダウンロードして、/home/ディレクトリに展開します。実は、この中で最初に使用するモデルはsqueezenetというものです。これだけをダウンロードするには

python -m caffe2.python.models.download --install squeezenet

と入力すると、suqueezenetフォルダーが/caffe2/caffe2/pyhon/models/ディレクトリにインストールされます。squeezenetは
/home/models/ squeezenet
/home/caffe2/caffe2/python/models/squeezenet
のどちらかに、あるいは、両方にあるはずです。以下のプログラムでは、squeezenetは/caffe2/caffe2/pyhon/models/にインストールされていることを前提しているので、確認しておいてください。
  Caffe2のsqueezenetを使ったテストをしてみましょう。Jupyter Notebookを開いて、[Python3],[File],[open]をクリックして、/caffe2/caffe2/python/tutorials/Model_Quickload.ipynbを開いてください。以下のようなセルが表示されます。

# load up the caffe2 workspace
from caffe2.python import workspace
# choose your model here (use the downloader first)
from caffe2.python.models import squeezenet as mynet
# helper image processing functions
import helpers

# load the pre-trained model
init_net = mynet.init_net
predict_net = mynet.predict_net
# you must name it something
predict_net.name = "squeezenet_predict"
workspace.RunNetOnce(init_net)
workspace.CreateNet(predict_net)
p = workspace.Predictor(init_net.SerializeToString(), predict_net.SerializeToString())

# use whatever image you want (urls work too)
# img = "https://upload.wikimedia.org/wikipedia/commons/a/ac/Pretzel.jpg"
img = "images/cat.jpg"
# img = "images/cowboy-hat.jpg"
# img = "images/cell-tower.jpg"
# img = "images/Ducreux.jpg"
# img = "images/pretzel.jpg"
# img = "images/orangutan.jpg"
# img = "images/aircraft-carrier.jpg"
#img = "images/flower.jpg"

# average mean to subtract from the image
mean = 128
# the size of images that the model was trained with
input_size = 227

# use the image helper to load the image and convert it to NCHW
img = helpers.loadToNCHW(img, mean, input_size)

# submit the image to net and get a tensor of results
results = p.run([img])   
response = helpers.parseResults(results)
# and lookup our result from the inference list
print (response)
このセルの入力画像は[ img = "images/cat.jpg" ]で指定されます。このセルを実行すると、

WARNING:root:This caffe2 python run does not have GPU support. Will run in CPU only mode.
WARNING:root:Debug message: No module named 'caffe2.python.caffe2_pybind11_state_gpu'

Raw top 3 results: [array([281.0, 0.5133159756660461], dtype=object), array([282.0, 0.3256784975528717], dtype=object), array([278.0, 0.07380081713199615], dtype=object)]
The image contains a tabby, tabby cat with a 51.3315975666 percent probability.

/Users/koichi/anaconda3/lib/python3.6/site-packages/skimage/transform/_warps.py:84: UserWarning: The default mode, 'constant', will be changed to 'reflect' in skimage 0.15.
  warn("The default mode, 'constant', will be changed to 'reflect' in "


という結果が表示されます。画像がtabby cat である確率は51.33%であると。warningが出てますが無視します。使用された画像は以下の猫の写真です。
cat.jpg
いかがですか。他の画像を使用するときは、#記号を外して、[ img = ...]で指定します。その場合は、画像のあるディレクトリを必ず指定する必要があります。ここでの例では、画像があるディレクトリは[ caffe2/caffe2/python/tutorials/images/ ]です。homeディレクトリに画像を置く場合は、[ img = "/home/username/" ]とする必要があります。試してみませんか。実は、このsqueezenetでは100種類の物体までしか区別できません。AlexNetを使うと、1000種類の物体の識別が可能です。

Keras+Tensorflowのページへ