単純パーセプトロンを実装してみた

年が明けていますが、PureScript Advent Calendar 2017 - Qiitaの20日目の記事にしてしまいます。

Python機械学習プログラミングを2章まで読んだ。

読んだ内容を体にいれるために、適当にPureScriptで単純パーセプトロンを実装してみる。

module Main where

import Prelude

import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Console (CONSOLE, log)
import Control.Monad.State (StateT, execStateT, get, modify, put)
import Control.Safely (replicateM_)
import Data.Array (snoc, zipWith)
import Data.Foldable (sum)
import Data.Traversable (for)
import Data.Tuple (Tuple(..))


-- 学習によって更新されていく値
type State =
  { weights :: Array Number
  , bias :: Number
  , errors :: Array Number
  }



main :: forall e. Eff (console :: CONSOLE | e) Unit
main = do
  -- 学習率は0.01とし、エポック数は10としてみる
  state <- flip execStateT initialState $ fit 0.01 10
  log $ "expected: 1.0, gotten: " <> (show $ predict $ netInput [1.0, 1.0] state)
  log $ "expected: -1.0, gotten: " <> (show $ predict $ netInput [0.0, 1.0] state)
  log $ "expected: -1.0, gotten: " <> (show $ predict $ netInput [1.0, 0.0] state)
  log $ "expected: -1.0, gotten: " <> (show $ predict $ netInput [0.0, 0.0] state)
  log $ "weights: " <> show state.weights
  log $ "bias: " <> show state.bias
  log $ "errors: " <> show state.errors


-- 学習
fit :: forall e. Number -> Int -> StateT State (Eff e) Unit
fit eta iter =
  replicateM_ iter do
    errors <- for samples \(Tuple xs y) -> do
      st <- get
      let d = eta * (y - predict (netInput xs st))
      put $ st
        { weights = zipWith (+) st.weights $ map ((*) d) xs
        , bias = st.bias + d
        }
      pure d
    -- イテレーションごとに雑に誤差をいれておく
    modify \s -> s { errors = snoc s.errors $ sum errors }


-- ステップ関数
predict :: Number -> Number
predict z =
  if z >= 0.0
    then 1.0
    else -1.0


-- 総入力
netInput :: Array Number -> State -> Number
netInput xs { weights, bias } =
  bias + (sum $ zipWith (*) xs weights)


-- 初期状態
initialState :: State
initialState =
  { weights: [ 0.0, 0.0 ]
  , bias: 0.0
  , errors: []
  }


-- サンプル
-- 特徴量と期待する出力
samples :: Array (Tuple (Array Number) Number)
samples =
  [ Tuple [ 0.0, 0.0 ] (-1.0)
  , Tuple [ 0.0, 1.0 ] (-1.0)
  , Tuple [ 1.0, 0.0 ] (-1.0)
  , Tuple [ 1.0, 1.0 ] 1.0
  ]

今回はAND演算を学習させてみた。

実行してみる。

expected: 1.0, gotten: 1.0
expected: -1.0, gotten: -1.0
expected: -1.0, gotten: -1.0
expected: -1.0, gotten: -1.0
weights: [0.04,0.02]
bias: -0.06
errors: [0.0,-0.02,-0.02,0.0,-0.02,0.0,0.0,0.0,0.0,0.0]

6回目から誤差が出なくなっているっぽい。

ADALINEも実装しようと思ったが、飽きたので一旦終了。