めもめも

このブログに記載の内容は個人の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

Neural Networkを平易に解説してみる

Neural Networkとは

Neural Networkというのは、脳細胞を構成する「Neuron(ニューロン)」の活動を単純化したモデルです。これを使うと、人間の「記憶」という活動をシュミレーションして遊んでみることができます。

たとえば、次のような4つのニューロンからなる「脳みそ」をもった原始的な生物を考えてみます。

・それぞれのニューロンは、「+1」と「-1」のどちらかの状態をとります。何もないときは「-1」の状態です。

・それぞれのニューロンは、そのニューロンに固有の事象に反応して、「+1」になります。

  • Red Neuron : 赤色を見ると「+1」になるニューロン
  • Apple Neuron : リンゴを見ると「+1」になるニューロン
  • Yellow Neuron : 黄色を見ると「+1」になるニューロン
  • Banana Neuron : バナナを見ると「+1」になるニューロン

下図は、赤いリンゴを見て、「Red」と「Apple」のニューロンがピクピク反応している様子です。

ここで、それぞれのニューロンの間に電気信号をつたえる通信経路(シナプス)をつけてあげます。1つのシナプスは、一方向に信号を伝える機能があり、たとえば、「Red」→「Apple」というシナプスは、Red Neuronの状態(±1)をApple Neuronに伝えます。そして、それぞれのニューロンは、シナプスから伝達される信号にも反応します。複数のシナプスから信号を受け取った場合、その合計値が正であれば、そのニューロンは反応して「+1」になります。

たとえば、この生物に「赤いものを見たらリンゴと思え」「黄色いものを見たらバナナと思え」とコンコンと言い聞かせると、次のようなシナプスが形成されます。

では、このような教育を受けた生物の脳細胞(ニューロン)は、どのような活動をするでしょうか。そう、赤い色を見せると、勝手にリンゴのことを思い出すようになります。下図のように、赤い色に反応して、Red Neuronが反応すると、シナプスを通じて、Apple Neuronが自然に反応するようになります。同じように、黄色に反応して、自然にBanana Neuronも反応するようになります。

ここでは、この生物にコンコンと言い聞かせることで、シナプスを形成しましたが、進化の過程において、自発的に学習してシナプスを形成する能力を獲得したものとしてみます。つまり、「赤いリンゴ」を見て、「Red Neuron」と「Apple Neuron」が反応すると、この2つのニューロンには何か関係があると思って、自動的に、「Red」→「Apple」と「Apple」→「Red」という双方向のシナプスが形成されるのです。また、「赤いリンゴ」を何度も何度も見ていると、シナプスの信号伝達力はどんどん強くなります。n回見たあとは、「+1」の信号をn倍して、「+n」の信号として伝達するようになります。この場合は、双方向のシナプスが形成されるので、「赤」を見たら「リンゴ」を連想するだけではなく、「リンゴ」と言われると「赤」を思い浮かべるようになります。

ちょっと単純化しすぎな気もしますが、これは、人間が経験から学習する過程と似ています。ごくまれに「黄色いリンゴ」を見てしまって、あやまったシナプスができることもありますが、それ以上にたくさんの「赤いリンゴ」を見ていれば、そちらのシナプスの方が強く働くので、リンゴを見ても黄色を思い出すことはありません。ここでは、シナプスの伝達力は強くなるだけの想定ですが、たとえば、「赤いリンゴ」を見たら、逆に「Yellow」→「Apple」のシナプスを弱めるような処理を加えることもできるでしょう。

Hopfield Network

・・・というような話を整理して数式化すると、Hopfield Networkと呼ばれるNeural Networkができあがります。一般にn個のニューロンがあって、それぞれは「±1」の状態をとります(何もないときは「-1」)。各ニューロンの間には双方向のシナプスがありますが、最初、各シナプスの伝達力は「0」です。下図は、ニューロンが4個の場合の初期状態です。

この後、このニューロンに刺激をあたえて、シナプスの伝達力を鍛えていきます。たとえば、ニューロンが4個の例で、「ニューロン1」と「ニューロン2」を同時に刺激すると、つぎのような変化が起こります。

・同時に「+1」になっている「ニューロン1」と「ニューロン2」には関連があると思います。つまり、「1」→「2」と「2」→「1」のシナプスの伝達力が「1」増えます。
・おなじく同時に「-1」のままにとどまっている「ニューロン3」と「ニューロン4」にも関連があると思います。つまり、「3」→「4」と「4」→「3」のシナプスの伝達力が「1」増えます。
・「+1」と「-1」の逆向きの状態のニューロンについては、逆に関連がないものと思います。つまり、その他のシナプスの伝達力は「1」減ります。(伝達力は負の値もとれるものとします。)

これは、先に説明した「シナプスを弱める処理」を加えたモデルになっています。

このようにして、さまざまな組み合わせの同時刺激を与えていくことで、関連の強いニューロンと関連の弱いニューロンが学習されていきます。

一通り学習が終わったら、今度は、学習の成果を検証してみます。はじめに、ある1つ(もしくは複数)のニューロンを刺激して「+1」にしてやります。その後、それぞれのニューロンについて、順番に次のような処理(「記憶覚醒処理」と名づけておきます)を行います。

・他のニューロンからシナプスを通じて受け取る値(ニューロン値「±1」×「シナプスの伝達力」)の合計を計算する。
・計算結果が正(または0)であれば、そのニューロンは「+1」に変化する。負であれば、そのニューロンは「-1」に変化する。

すべてのニューロンについて、上記の「記憶覚醒処理」を行ったら、この状態を出発点として、再度、同じ「記憶覚醒処理」を繰り返します。これを何度も繰り返すと、それ以上変化しない安定状態になります。最後の安定状態は、この「脳みそ」が思い出した「記憶」を表すと考えられます。

先ほどの例に戻ると、うまく訓練された脳みそであれば、最初に「赤」のニューロンだけ刺激すると、最後は「赤」と「リンゴ」のニューロンが反応して、「リンゴは赤い」という記憶が蘇ります。一方、「黄色」のニューロンを刺激しても、「リンゴ」のニューロンは反応しません。つまり、この脳みそは、「リンゴ判定器」の能力を獲得したことになるわけです。

Hopfield Networkの実験君

そんなにうまくいくものか、実際にためしてみましょう。NumPy & Pandasで処理していきます。

はじめに、冒頭の例と同じ4つのニューロンを用意します。

names = ['Apple','Banana','Red','Yellow']
neurons = Series([-1]*len(names),
                 index=names,name='Activation')
In [83]: neurons
Out[83]: 
Apple    -1
Banana   -1
Red      -1
Yellow   -1
Name: Activation, dtype: int64

これらをつなぐシナプスは次のようになります。

links = DataFrame(np.zeros(shape=(len(neurons),len(neurons))),
                  index=names, columns=names)
links.index.name = 'input'
links.columns.name = 'output'
In [84]: links
Out[84]: 
output  Apple  Banana  Red  Yellow
input                             
Apple       0       0    0       0
Banana      0       0    0       0
Red         0       0    0       0
Yellow      0       0    0       0

これは、縦軸のニューロンから横軸のニューロンに信号を伝達するニューロンの「伝達力」のマトリックスです。対角成分は常に0なので無視してください。

まず、こいつに「赤いリンゴ」を見せて、シナプスを訓練します。

def learn_event(event, links):
    pairs = [(x,y) for x in links.index for y in links.columns if x!= y]
    for (x,y) in pairs:
        if (x in event) & (y in event):
            links.ix[x, y] += 1
        elif (x in event) & (not y in event):
            links.ix[x, y] -= 1
        elif (x not in event) & (y in event):
            links.ix[x, y] -= 1
        else:
            links.ix[x, y] += 1
In [85]: learn_event(['Red', 'Apple'], links)
In [86]: links
Out[86]: 
output  Apple  Banana  Red  Yellow
input                             
Apple       0      -1    1      -1
Banana     -1       0   -1       1
Red         1      -1    0      -1
Yellow     -1       1   -1       0

前述のルールにしたがって、各シナプスの伝達力が増減していることが分かります。訓練につかったのは「赤いリンゴ」ですが、暗黙に「黄色」と「バナナ」にも関連があることを学習しているのが、ちょっとおもしろいところです。一方で、「黄色」と「リンゴ」は関係ない(シナプスの伝達力がマイナス)という事実も認識しています。

続いて、「黄色いバナナ」を見せてみましょう。

In [87]: learn_event(['Yellow', 'Banana'], links)
In [88]: links
Out[88]: 
output  Apple  Banana  Red  Yellow
input                             
Apple       0      -2    2      -2
Banana     -2       0   -2       2
Red         2      -2    0      -2
Yellow     -2       2   -2       0

これで訓練が終わりましたので、今度は、その成果を試します。まず、「赤」のニューロンを刺激しておきます。

In [94]: neurons['Red'] = 1
In [95]: neurons
Out[95]: 
Apple    -1
Banana   -1
Red       1
Yellow   -1
Name: Activation, dtype: int64

この状態から、前述の「記憶覚醒処理」を実行します。今の場合は、1回の処理で記憶は安定化します。

def run_hopfield(neurons, links):
    for index, value in links.iterrows():
        a = (value * neurons).sum()
        if a >= 0:
            neurons[index] = 1
        else:
            neurons[index] = -1
In [96]: run_hopfield(neurons, links)
In [97]: neurons
Out[97]: 
Apple     1
Banana   -1
Red       1
Yellow   -1
Name: Activation, dtype: int64

予想通り、「赤」と「リンゴ」のニューロンが反応して、「リンゴは赤い」という事実を思い出しています。

ここで、ちょっといたずらをして、誤った知識を植えつけてみます。普段はあまり目にしない、「黄色いリンゴ」をあえて見せてみましょう。

In [100]: learn_event(['Yellow', 'Apple'], links)
In [101]: links
Out[101]: 
output  Apple  Banana  Red  Yellow
input                             
Apple       0      -3    1      -1
Banana     -3       0   -1       1
Red         1      -1    0      -3
Yellow     -1       1   -3       0

しかしながら! シナプスの状態をよく見ると、「黄色」と「リンゴ」の繋がりは「-1」という負の値になっています。事前の2回の学習で、「黄色」と「リンゴ」は関係ないという事実を植え付けられているので、1回ぐらい変なことを教えられても簡単には騙されないのです。

再度、記憶を確認すると、ちゃんと、「赤いものはリンゴ」と覚えているようです。

In [104]: neurons['Red'] = 1; neurons['Apple'] = -1
In [105]: neurons
Out[105]: 
Apple    -1
Banana   -1
Red       1
Yellow   -1
Name: Activation, dtype: int64
In [106]: run_hopfield(neurons, links)
In [107]: neurons
Out[107]: 
Apple     1
Banana   -1
Red       1
Yellow   -1
Name: Activation, dtype: int64

このようにたくさんの事実を学ぶことで、変な情報にまどわされなくなるのは、Neural Networkの特徴の1つと言えるでしょう。

Hopfieldネットワークで文字認識

実験君がうまくいったので、これに気をよくして、今度は、文字認識に挑戦します。5 x 5 のドット絵文字を記憶するために、各ドットに対応する25個のニューロンとその間をつなぐシナプスを用意します。

まず、ドット絵からニューロンを定義する関数と、ニューロンを絵で表示する関数を用意します。

def create_neurons(pict):
    names = [(x,y) for x in range(5) for y in range(5)]
    neurons = Series([-1]*len(names), index=names, name='Activation')
    for y, line in enumerate(pict):
        for x in range(len(line)):
            if line[x] != ' ':
                neurons[(x,y)] = 1
            else:
                neurons[(x,y)] = -1
    return neurons
def show(neurons):
    for y in range(5):
        for x in range(5):
            if neurons[(x, y)] == 1:
                print '#',
            else:
                print ' ',
        print

これを使って、「D」「J」「C」「M」の4つの文字を表す(それぞれの文字を見た時の状態を表す)ニューロンを用意します。

d = create_neurons(['#### ',' #  #',' #  #',' #  #',' ### '])
j = create_neurons(['#####','   # ','   # ','#  # ','###  '])
c = create_neurons([' ####','#    ','#    ','#    ',' ####'])
m = create_neurons(['#   #','## ##','# # #','#   #','#   #'])
In [221]: show(d)
# # # #  
  #     #
  #     #
  #     #
  # # #  
In [222]: show(j)
# # # # #
      #  
      #  
#     #  
# # #    
In [223]: show(c)
  # # # #
#        
#        
#        
  # # # #
In [224]: show(m)
#       #
# #   # #
#   #   #
#       #
#       #

これらの実際の定義は、次のように座標値をラベルとするニューロンとそれぞれの状態(±1)の集合です。

In [226]: d
Out[226]: 
(0, 0)    1
(0, 1)   -1
(0, 2)   -1
(0, 3)   -1
(0, 4)   -1
(1, 0)    1
(1, 1)    1
(1, 2)    1
(1, 3)    1
(1, 4)    1
...(以下省略)...

25個のドットに対応する、25個のニューロンがあるので、これらを結ぶシナプス群は、25 x 25の行列になります。

names = [(x,y) for x in range(5) for y in range(5)]
links = DataFrame(np.zeros(shape=(25, 25)), index=names, columns=names)
links.index.name = 'input'
links.columns.name = 'output'

このシナプス群に、先ほどの4文字を学習させます。

def learn_character(neurons, links):
    pairs = [(f,t) for f in links.index for t in links.columns if f != t]
    for (f,t) in pairs:
        if neurons[f] == neurons[t]:
            links.ix[f, t] += 1
        else:
            links.ix[f, t] -= 1
In [246]: learn_character(d, links)
In [247]: learn_character(j, links)
In [248]: learn_character(c, links)
In [249]: learn_character(m, links)

さて・・・・。このNeural Networkは、本当に文字を記憶したのでしょうか・・・・。

次のように、ちょっと変形した「D」のニューロンを用意します。

In [250]: input = create_neurons(['#### ',' #  #',' #  #','##  #',' ### '])
In [251]: show(input)
# # # #  
  #     #
  #     #
# #     #
  # # #  

ここから、「記憶覚醒処理」を実施してみます。この場合、実は、1回の処理で安定化します。

In [252]: run_hopfield(input, links)

結果は・・・・。ドキドキ。

In [253]: show(input)
# # # #  
  #     #
  #     #
  #     #
  # # #  

すばらしい。ちゃんと、最初に記憶した「D」の文字を思い出しています。1ドットのエラーをちゃんと修正しています。

では、2ドット以上のエラーはどうでしょうか。こんな感じの文字から出発します。

In [373]: input = create_neurons([' ## #','# #  ','#    ','#   #',' ##  '])
In [374]: show(input)
  # #   #
#   #    
#        
#       #
  # #    

そうとうに崩れた文字ですが、この場合もなんと一発の処理で記憶が回復します。

In [375]: run_hopfield(input,links)
In [376]: show(input)
  # # # #
#        
#        
#        
  # # # #

次のように2回の「記憶覚醒処理」で記憶が安定化する例もあります。

In [382]: input = create_neurons([' ## #','# ## ','#    ','#   #','###  '])
In [383]: show(input)
  # #   #
#   # #  
#        
#       #
# # #    
In [384]: run_hopfield(input,links)
In [385]: show(input)
# # # # #
#     #  
#     #  
#     #  
# # #    
In [386]: run_hopfield(input,links)
In [387]: show(input)
# # # # #
      #  
      #  
#     #  
# # #    

このようにNeural Networkを利用して、「正しい文字」を事前に学習させておくと、特別なロジックを組み込まなくても、自然に「手書き文字認識装置」が実現できてしまいます。ちょっと驚きですね。もちろん、あまりに崩れたパターンからは、正しい記憶に戻らないこともありますが、この例の場合は、最初に記憶した4文字に加えて、必ず、いくつかの特定パターンの記憶にたどり着くことが分かっています。

Boltzmann Machineについて

ところで、前回に紹介したIsing Modelを思い出すと、今回説明したNeural Networkと似ている気がしませんか? それぞれの原子のスピンは、Neuronと同じ「±1」の値をとります。また、ある原子のスピンは、その周りの原子の影響を受け取って、新しい方向に変化します。違う所は、次のあたりでしょうか。

・Ising Modelの場合は、周りの4つの原子の影響しか受けませんでしたが、Hopfield Networkでは、その他のすべてのNeuronからの影響をうけます。
・Ising Modelの場合は、周りの原子からの影響は共通の定数「J」に決まっていましたが、Hopfield Networkでは、事前の学習によって、シナプス伝達力が変化していました。

これは、言い換えると、Hopfiled Networkのシナプス伝達力を特別な値に設定することで、Ising Modelが得られることを意味しています。

ただし、状態を変化させる手続きは、少し異なります。今回紹介したHopfield Networkの場合は、「(Neuronの値×シナプスの伝達力)の合計値の正負」で変化を決定しましたが、Ising Modelの場合はスピンが「上を向く確率」の公式が決まっていて、それに基いて確率的に変化を行いました。Hopfield Networkに対して、Ising Modelのように状態変化のルールに確率を取り入れたモデルは、「Boltzmann Machine」としても知られています。

参考文献

前回も紹介しましたが、NumPy & Pandasの使い方は、こちらの書籍が参考になります。個人的にはRよりも気に入ってます。

Python for Data Analysis

Python for Data Analysis

今回紹介した、文字認識装置は、こちらの書籍からの引用です。

Information Theory, Inference and Learning Algorithms

Information Theory, Inference and Learning Algorithms