背景
Elixirの学習を兼ねて機械学習ライブラリの開発を検討中(Concurrent Programmingはモンテカルロ法やボルツマンマシンなどの実装で効果を発揮することを想定)。試しに形式ニューロン(単層パーセプトロン)による誤り訂正学習を実装した。非常にシンプルな例とするために、入力を2成分としたAND/OR演算回路の学習を行った。
neural_network.ex
defmodule NeuralNetwork do
@moduledoc """
形式ニューロンによる機械学習。論理回路(AND/OR)について、教師あり学習で誤り訂正学習を行う
# Configuration
シグモイド関数の閾値 = 1.0
修正係数 = 0.1
"""
@lambda 1.0 # シグモイド関数の閾値
@eta 0.1 # 修正係数
@doc """
形式ニューロン。ウェイト(3成分リスト)と入力(2成分リスト)を入れると値を返す。\n
活性化関数はシグモイド関数を利用
# Example
iex> w = [1,1,1]
iex> NeuralNetwork.of(w,[1,0]) # => 0.8807970779778823
"""
@spec of([number],[number]) :: number
def of(w,x) when length(w) == 3 and length(x) == 2 do
w
|> Enum.zip([1,x] |> List.flatten)
|> Enum.map(fn {a,b} -> a * b end)
|> Enum.sum
|> sigmoid
end
@doc """
誤り訂正学習。学習対象のウェイト(3成分リスト)と学習データ(入力(2成分リスト)と教師データ(数値)のタプル)を入力すると、ウェイトを修正して返す。
# Example
iex> w = [1,1,1]
iex> NeuralNetwork.error_correct(w,{[0,1],1})
[1.0119202922022117, 1.1, 1.0119202922022117]
"""
@spec error_correct(number,{[number],number}) :: [number]
def error_correct(w,{x,y}) do
w
|> Enum.zip([1,x] |> List.flatten)
|> Enum.map(fn {a,b} -> a + @eta * (y - of(w,x) * b) end)
end
@doc """
初期ウェイト(3成分リスト)と学習データを入力し、学習結果(3成分リスト)を返す。
# Example
iex> w = [1,1,1]
iex> or_data = [{[0,0],0},{[0,1],1},{[1,0],1},{[1,1],1}]
iex> NeuralNetwork.training(w,or_data)
[0.760358858313729, 0.920755241146367, 0.9197548982948772]
"""
@spec training([number],[{[number],number}]) :: [number]
def training(w,data), do: w |> _training(data)
defp _training(w,[]), do: w
defp _training(w,[h|t]), do: _training(error_correct(w,h),t)
@doc """
繰り返し学習の実行。
# Example
iex> w = [1,1,1]
iex> and_data = [{[0,0],0},{[0,1],0},{[1,0],0},{[1,1],1}]
iex> NeuralNetwork.exec(w,and_data,1000)
[-8.588678182412002, 5.616654849741987, 5.610602312105118]
"""
@spec exec([number],[{[number],number}],number) :: [number]
def exec(w,_,n) when n == 0, do: w
def exec(w,data,n) when n > 0, do: exec(training(w,data),data,n - 1)
def exec(_,_,_), do: false
defp sigmoid(x), do: 1 / (1 + :math.exp(-@lambda * x)) # シグモイド関数
end
実行結果
AND/OR回路の教師データで学習させる。4データだとあまり学習効果がないため、4パターンを1,000回(合計4,000回)学習させる。出力結果を用いて各パターンの出力を行うと、ほぼAND/OR回路の内容を学習できていることがわかる。
iex> c("neural_network.ex")
iex> alias NeuralNetwork, as: NN
iex> w = [1,1,1] # 初期パラメータ
# AND回路の学習と結果
iex> and_data = [{[0,0],0},{[0,1],0},{[1,0],0},{[1,1],1}]
iex> v = w |> NN.exec(and_data,1000)
iex> NN.of(v,[0,0]) # => 1.8616738545650644e-4
iex> NN.of(v,[0,1]) # => 0.048426217768489974
iex> NN.of(v,[1,0]) # => 0.0487058890113323
iex> NN.of(v,[1,1]) # => 0.9333035631517769
# OR回路の学習と結果
iex> or_data = [{[0,0],0},{[0,1],1},{[1,0],1},{[1,1],1}]
iex> v = w |> NN.exec(or_data,1000)
iex> NN.of(v,[0,0]) # => 0.01050281992405151
iex> NN.of(v,[0,1]) # => 1.0
iex> NN.of(v,[1,0]) # => 1.0
iex> NN.of(v,[1,1]) # => 1.0
課題
今回の例ではガード構文で成分数を制限しているが、この辺りを拡張することで一般化は可能。ただし、単層パーセプトロンは線形分離不可能性があるため、XORを実装するためには階層化する必要がある。この場合、学習には誤差逆伝播法(Back Propagation)を用いて計算する。
感想
関数型言語はロジックを簡潔に記載できるため非常に見通しが良くなる。ElixirはRubyライクな文法なのでRubyistの自分にはとても入りやすい。