お布団宇宙ねこ

にゃーん

Haskell で『ゼロから作るDeep Learning』(2)

『ゼロから作るDeep Learning – Pythonで学ぶディープラーニングの理論と実装』 の読書メモです。

今回は 「3.6 手書き数字認識」の手前まで。

3章 ニューラルネットワーク

ニューラルネットワークとは

  • パーセプトロンでは人力で重みを決めていくのに対して、ニューラルネットワークは適切な重みを自動で学習できる
  • ニューラルネットワークは入力層、中間層(隠れ層)、出力層で構成される
  • 前章の ニューロンは受け取った信号の総和を計算し、それがある値を超えたときのみに 1 を出力する というような動作は関数で表すことができる。このような関数を「活性化関数」と呼ぶ
  • 出力層では、重み付き入力信号とバイアスの総和を計算し、それを活性化関数に渡した結果が出力される

活性化関数

  • 前章のパーセプトロンで使われていた、閾値を境にして出力が切り替わる関数を「ステップ関数(階段関数)」と呼ばれる
  • 活性化関数としてステップ関数以外の関数を使うことでパーセプトロンからニューラルネットワークの世界へ進むことができる
  • ニューラルネットワークでよく使われる活性化関数として「シグモイド関数」と呼ばれる関数がある
  • ステップ関数とシグモイド関数にはいくつか共通点がある
    • 入力信号の重要度に応じて、重要であれば大きな値を、逆に重要でなければ小さな値を出力する
    • 出力信号の値は 0 から 1 の間
    • 非線形関数である
      • 何か入力に対して、出力が入力の定数倍になるような関数を線形関数と呼ぶので、そうではない関数のこと
  • 最近は ReLU 関数というのも使われる
    • 入力が 0 を超えるとその入力をそのまま出力し、 0 以下なら 0 を出力する関数

出力層の設計

  • ニューラルネットワークの出力層の活性化関数は、問題の種類によって変更する必要がある
    • 回帰問題なら恒等関数、分類問題ならソフトマックス関数
  • ソフトマックス関数の出力の総和は 1 になる(もちろん各出力は 0 から 1.0 の実数の間に収まる)
    • この性質のおかげでこの出力を確率として解釈することができる
    • 確率として解釈できるから分類問題に適している
    • しかし、各要素の大小関係はこの関数を適用しても変わらないので、推論(分類)のフェーズでは省略されるのが一般的
  • 出力層のニューロンの数は解く問題に応じて決める必要がある
    • たとえば、手描きの画像が数字の 0 から 9 のどれか当てる問題なら、ニューロンの数を 10 個に設定する

実践編

行列の計算をやってみる

hmatrix ライブラリを使うことでベクトルや行列の計算ができるようになります。 以下は書籍で使われている PythonNumPy ライブラリとの比較です。

行列の生成

// Python
import numpy as np
A = np.array([1,2],[3,4])
B = np.array([5,6],[7,8])

// Haskell
// `R` は `Double` の型シノニムです。
import Numeric.LinearAlgebra
let a = (2><2) [1,2,3,4] :: Matrix R
let b = (2><2) [5,6,7,8] :: Matrix R

行列の次元数

// Python
A.shape

// Haskell
size a

行列の積

// Python
np.dot(A,B)

// Haskell
a <> b

例として、書籍 p.57 のニュートラルネットワークの内積を hmatrix を使って計算してみます。

*Main Numeric.LinearAlgebra> let x = (1><2) [1,2] :: Matrix R
*Main Numeric.LinearAlgebra> size x
(1,2)
*Main Numeric.LinearAlgebra> let w = (2><3) [1,3,5,2,4,6] :: Matrix R
*Main Numeric.LinearAlgebra> print w
(2><3)
 [ 1.0, 3.0, 5.0
 , 2.0, 4.0, 6.0 ]
*Main Numeric.LinearAlgebra> size w
(2,3)
*Main Numeric.LinearAlgebra> let y = x <> w
*Main Numeric.LinearAlgebra> print y
(1><3)
 [ 5.0, 11.0, 17.0 ]

活性化関数のグラフを描画してみる

グラフ描画には今回 Chart というライブラリを使いました。実際にコードを書くときにはパッケージのドキュメントを見ても使い方がよくわからなかったので、こちらの Wiki を見ながらやりました。

そして、書いたコードから出力されたグラフが以下。

f:id:ku00:20170225112708p:plain

参考文献

書いたコード

GitHub - ku00/deep-learning-practice

Haskell で『ゼロから作るDeep Learning』(1)

『ゼロから作るDeep Learning – Pythonで学ぶディープラーニングの理論と実装』の読書メモです。

www.oreilly.co.jp

この書籍のサンプルプログラムは Python で書かれていますが、表題の通り自分は Haskell で書いていきます。

今回は、2章全部です。

2章 パーセプトロン

パーセプトロンとは

  • パーセプトロンとは、複数の信号を受け取ってひとつの信号を出力するアルゴリズムのこと
  • 信号を入出力するものを「ニューロン」や「ノード」と呼ぶ
  • 入力信号が送られるときには重みが乗算される
    • 入力(x) -> 重み(w) -> 出力(y)
  • ニューロンは受け取った信号の総和を計算し、それがある値を超えたときのみに 1 を出力する
    • そのある値を「閾値」と呼ぶ
    • 閾値を超えて 1 を出力することを「ニューロンが発火する」と表現することもある

論理回路パーセプトロンで表現する

  • 論理回路 AND・NAND・OR はパーセプトロンを使って表現することができる
  • 信号の総和の計算に「バイアス」を加えることで、ニューロンの発火のしやすさを調整できる
    • 重みはその信号の重要度を調整するために使う
    • 両者をまとめて「重み」と呼ぶこともある
  • 論理回路 XOR は AND などと同じような構造のパーセプトロンでは表現できない
    • 2次元グラフに描画したときに、出力する値の境界を1本の直線で分けることができない
  • XOR はパーセプトロンの層を重ねることで表現できる

参考文献

書いたコード

今回は、論理回路(AND・NAND・OR・XOR)を hmatrix というライブラリを使って実装しました。

deep-learning-practice/Gate.hs at master · ku00/deep-learning-practice · GitHub

2016年振り返り

2016年をざっくり振り返ります。

仕事

今年は個人的に下半期の密度が高かったように感じました。

下半期は おいちゃん(@inouetakuya) と二人三脚で頑張りつつ、11月頃には ペパカレ のメンター的な役割もこなしたりいろいろやっていました。

周りに支えてもらいながらガシガシ開発した結果、いい感じの Gem も作ることができました。

github.com

やってる様子は下記のインタビュー記事でも確認できますので是非。

employment.en-japan.com

プライベート

イベント

去年は3つほどしか行ってませんでしたが、今年は毎月何かしらのイベントに行っていました。

原因は μ's ファイナルライブによる μ's ロスだったのだと思います。 (9人全員のイベントに参加することができたのは本当に運が良かった...)

来年も9人全員のイベントに顔を出せるかは分かりませんが運とお金があるうちは追い続けていきたいですね。

以下参加したイベントです。

Haskell

上半期で すごいHaskellたのしく学ぼう! を読了して(遅い)、下半期は AOJ を Haskell で解くなどしてました。

Haskell と少しは仲良くなれた気がしますがまだ実用レベルには達してないので、来年はその辺りを視野に入れて勉強していきたいです。

AOJ で解いた問題のコード

github.com

AOJ を解いたときのブログ

ku00.hatenablog.com

その他

  • 数年ぶりにウォークマンを購入
  • スクフェスAC で人生初のアーケード体験
    • まだ12回くらいしかやってないけど EXTREME フルコンできそう
  • スクフェスで Rank 300
    • 始めたのがラブライブ!のアニメ1期の放送終了後くらいだったのでだいたい3年半くらいやってる
    • 動体視力はだいぶ鍛えられた

f:id:ku00:20161231195216p:plain

来年の抱負

Gem を作ったことで Ruby という言語に対する意識もだいぶ変わってきてより深く知ろうとする意欲が湧いたので、やっぱりアウトプットは大事だなと実感しつつこういった活動は継続していきたいと思いました。

Haskell は実用レベルで何か作って、最終的に仕事に組み込めるように頑張ります 💪

そんな感じで来年もバーンとやってこ。

「久保ユリカ クリスマスイベント2016」に行ってきた

久保ユリカ クリスマスイベント2016」の昼の部に行ってきました。久保ユリカさんのイベントは今回が初めてでした。会場はいつもの EX THEATER ROPPONGI 。

クリスマス仕様に装飾された部屋のセットと共に adidas のジャージ姿の久保さんが現れたときは「あ、これ自分の好きなグタグタやるゆるい感じのイベントだ...!」と確信したのでした。ジャージは黒か赤で迷ってそうですが、赤だと某ラジオ番組と勘違いする人がいるだろうからと黒にしたそうです。

一人でドレミの歌にのせて音程を合わせるゲームから始まり、一人チェキ撮影、絵描きコーナー、カラオケコーナー、一人黒ひげ危機一髪、など非常に久保さんらしい内容でした。今振り返るとあれはほぼ胃痛ラジオだったのでは...?

カラオケコーナーでは久保さんの歌いたい曲を1番だけ歌いまくるという主旨のものだったのですが、ファイナルライブぶりの「SUNNY DAY SONG」や「Snow halation」が聴けたので本当にありがとうございましたという感想しかなかったです。

最後のライブコーナーでは、 3rd シングルの「ありがとうの時間」と「Happy Cuty My Snow Man」の2曲を披露してくれました。久保さんソロの生歌を聴けたこともそうですし、シングルの中で一番好きな「ありがとうの時間」を生で聴けてよかったです。

ファーストアルバムの発売も決定したみたいですし、今後もいろいろな久保ユリカさんの歌声を聴けるのが楽しみです。

(そういえば絵描きコーナーで久保さんが描いていた三森すずこさんの似顔絵はいつ掲示されるんですかね?)


【久保ユリカ】「ありがとうの時間」MV short ver.


【久保ユリカ】3rdシングル c/w 「Happy Cuty My Snow Man」 試聴動画

Stack を使って Scotty 入門してみた

Haskell の Web フレームワークである Scotty を触ってみました。

Yesod で使えるなら Scotty でも同じようにできるだろうと思って今回も Stack を使ってみました。

Hello World 的なものを表示するまで

プロジェクトを新規作成してから setup までやっておきます。

stack new hello-scotty
cd hello-scotty
stack setup

次に、依存ライブラリとして Scotty を cabal ファイルに追記します。

executable hello-scotty-exe の段落に追記するのがポイントです。

( library ではありません)

hello-scotty.cabal

...

executable hello-scotty-exe
  hs-source-dirs:      app
  main-is:             Main.hs
  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
  build-depends:       base
                     , scotty
                     , hello-scotty
  default-language:    Haskell2010

...

そして stack build

$ stack build

ansi-terminal-0.6.2.3: configure
ansi-terminal-0.6.2.3: build
base-compat-0.9.1: configure
base-compat-0.9.1: build
auto-update-0.1.4: configure
auto-update-0.1.4: build
appar-0.1.4: configure
appar-0.1.4: build

...
Completed 67 action(s).

終わったら次は Scotty の README に載っているサンプルをそのままコピペしましょう。

Haskell のコードを試すときは src ディレクトリ以下にファイルを置いてそれを stack ghci で読み込むようにしてますが、今回はちゃんとしたアプリケーションなので app/Main.hs に書いていきます。

app/Main.hs

{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty

import Data.Monoid (mconcat)

main = scotty 3000 $ do
    get "/:word" $ do
        beam <- param "word"
        html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"]

あとはサーバを起動するだけです。 stack exec で実行します。デフォルトだと [プロジェクト名]-exe が実行ファイル名になります。

$ stack exec hello-scotty-exe

Setting phasers to stun... (port 3000) (ctrl-c to quit)

見れました。

f:id:ku00:20170109113731p:plain

実はテンプレートがあった

Yesod のときは「テンプレート」というものを使って簡単にセットアップできたのですが、調べたら Scotty のものもあったという...。

(Yesod のは 公式のクイックスタート にも載っています )

github.com

使い方は簡単で、 stack new でプロジェクト名の後に使いたいテンプレート名を入れるだけです。

$ stack new template-hello-scotty scotty-hello-world

Downloading template "scotty-hello-world" to create project "template-hello-scotty" in template-hello-scotty/ ...
Looking for .cabal or package.yaml files to use to init the project.
Using cabal packages:
- template-hello-scotty/template-hello-scotty.cabal
...

生成されたファイルも最低限という印象。

f:id:ku00:20170109113735p:plain

まとめ

Stack を使うと一通りのことをやってくれるので本当に便利ですね。また、 Yesod と比べると Scotty は依存ライブラリがそこまで多くないのでセットアップに時間がかからないです。

さて、次からは本格的なの作っていくぞ。

参考文献

HaskellでAOJの問題を解いた(ITP1_8)

AOJ の Introduction to Programming を解いたときのメモです。

前回: HaskellでAOJの問題を解いた(ITP1_7)

今回解いた問題

8_A Toggling Cases

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_8_A

import qualified Data.Char as Char (isUpper, isLower, toUpper, toLower)

main = putStrLn . toggleCases =<< getLine

toggleCases :: String -> String
toggleCases [] = []
toggleCases (x:xs)
    | Char.isLower x = Char.toUpper x : toggleCases xs
    | Char.isUpper x = Char.toLower x : toggleCases xs
    | otherwise      = x : toggleCases xs

与えられた文字列の小文字と大文字を入れ替えるだけの問題です。

文字判定と変換はすべて Data.Char モジュールがやってくれます。今回入力を別の型に変換する必要がなかったので =<< を使って toggleCases 関数に渡すようにしました。 main がワンライナーで書けるとスマートに解けた気がして嬉しいですね。

8_B Sum of Numbers

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_8_B

import Control.Applicative ((<$>))
import qualified Control.Monad as Monad (when)
import qualified Data.Char as Char (digitToInt)

main = do
    s <- getLine
    Monad.when (read s /= 0) $ do
        print $ sum $ Char.digitToInt <$> s
        main

与えられた数の各桁の和を計算する問題です(H本でも見たことある気がする)。

入力された文字列のすべての文字に digitToInt で Int 型に変換してそれを sum で集計するだけです(特に難しいことはなかった...)。

8_C Counting Characters

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_8_C

import Control.Applicative ((<$>))
import qualified Control.Monad as Monad (forM_)
import qualified Data.Char as Char (toLower)
import qualified Data.List as List (sort, group)
import qualified Data.Map as Map (Map, fromList, lookup)

type AlphabetCountMap = Map.Map Char Int

main = do
    s <- getContents
    let acl = alphabetCounts $ groupAlphabet s
    Monad.forM_ ['a'..'z'] $ \c -> do
        putStrLn $ case Map.lookup c acl of
            Nothing -> c : " : 0"
            Just n  -> c : " : " ++ show n

groupAlphabet :: String -> [String]
groupAlphabet s = List.group . List.sort $ filter (\x -> x `elem` ['a'..'z']) $ Char.toLower <$> s

alphabetCounts :: [String] -> AlphabetCountMap
alphabetCounts cs = Map.fromList $ [(head c, length c) | c <- cs]

与えられた文字列に含まれる各アルファベットの数をカウントして出力する問題です。

やりたいことは単純そうなのですが実装をどうしようか結構悩んで、愚直にやるなら文字列を一から走査して各アルファベットの数を State モナドで保持しておく...みたいなことを考えましたが、やりたいことの割に複雑な処理になりそうだったので止めました(主にモナド変換子あたり)。

最終的に次のような実装に落ち着きました。

  1. 入力された文字列を、同じ文字同士が隣り合うように並べ替えて、グルーピングする
  2. 1 のリストを、アルファベットとその数のタプルでリスト化する
  3. 2 のリストを走査して各アルファベットの数を出力する

アルファベットの数を保持するのに今回は Data.Map を組み込んでみました。今までは型シノニムを定義するだけで特に走査関数を用意するようなことをしてなかったのですが、 Data.Map の lookup 関数便利ですね。速度のことは特に考えてないです。

8_D Ring

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_8_D

import qualified Data.List as List (elemIndices)

main = do
    s <- getLine
    p <- getLine
    putStrLn $ if elemFromRing (List.elemIndices (head p) s) s p then "Yes" else "No"

elemFromRing :: [Int] -> String -> String -> Bool
elemFromRing [] _  _ = False
elemFromRing (x:xs) s p
    | pickFromRing [x..(x + length p - 1)] s == p = True
    | otherwise                                   = elemFromRing xs s p

pickFromRing :: [Int] -> String -> String
pickFromRing [] _ = []
pickFromRing (n:ns) xs = xs !! (n `mod` length xs) : pickFromRing ns xs

リング状の文字列(以下 S )を受け取って、そこから任意の文字列(以下 P )が作れるかどうかを判定する問題です。 P が、時計回りに連続した文字を取ってきて作れるかどうかというところが肝なので解き方を考えるのに結構苦労しました。

解き方は次のような流れでやりました。

  1. P の先頭が、 S のどの位置に現れるのかを調べる
  2. 1で調べた位置をもとに、そこから P が作れるか調べる
  3. 作れたならそこで終わり、そうでなければ次の位置に進む

(流れだけ書いてみると意外と単純だった...?)

ポイントとしては、「リング状」の文字列から探すというのをどう実現するかというところ。今回は pickFromRing 関数で、P の先頭が出現する位置から P の長さをリストで引数として与えていますが ( [x..(x + length p - 1)] の部分)、このリストの各要素(位置)をもとに S から文字を取ってくるときに、各要素に対して n `mod` ( S の長さ) で余数を計算してその数で文字を取り出すようにしています。こうすることで下の例のように、 S の長さを超えた位置でも、余数を使うことで位置を折り返して取得することができます。

*Main> let s="vanceknowledgetoad"
*Main> length s
18
*Main> pickFromRing [16..22] s
"advance"

まとめ

Haskell で AOJ を解くのもだいぶ慣れてきて少しは Haskell と仲良くなれた感じがしてきたので、そろそろ次のステップに進もうと思います。具体的には JSON Web API でも作ってみようかなあと。なので AOJ 編はこの辺りで一旦終わりにします。それでは。

github.com

HaskellでAOJの問題を解いた(ITP1_7)

AOJ の Introduction to Programming を解いたときのメモです。

前回: HaskellでAOJの問題を解いた(ITP1_6)

今回解いた問題

7_A Grading

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_7_A

import Control.Applicative ((<$>))
import qualified Control.Monad as Monad (when)

main = loopGrading

loopGrading :: IO ()
loopGrading = do
    result <- map (read :: String -> Int) . words <$> getLine
    Monad.when (any (/= (-1)) result) $ do
        putStrLn $ grading result
        loopGrading

grading :: [Int] -> String
grading [m,f,r]
    | m == (-1) || f == (-1) = "F"
    | total >= 80            = "A"
    | total >= 65            = "B"
    | total >= 50 || r >= 50 = "C"
    | total >= 30            = "D"
    | otherwise              = "F"
    where total = m + f

試験の点数によってAやBなどの成績をつけるありふれた問題です。ガードと where 節を使うと綺麗に書ける気がします。

7_B How many ways?

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_7_B

import Control.Applicative ((<$>))
import qualified Control.Monad as Monad (when)

main = do
    nx <- getLine' $ map read . words
    Monad.when (any (/= 0) nx) $ do
        print $ length $ combination nx
        main

getLine' :: (String -> a) -> IO a
getLine' f = f <$> getLine

combination :: [Int] -> [[Int]]
combination [n,x] = [[a,b,c] | a <- [1..n], b <- [a..n], c <- [b..n],
                                a + b + c == x, a /= b, a/= c, b /= c]

組み合わせの問題もリスト内包表記で表現することができます。重複無しにするためには b と c の始めの要素を1つ前の変数の要素から始めることと、それぞれの変数の要素が一致しないように述語を入れることで実現できます。

また今回は getLine で入力を受け取る部分を String -> a の関数を受け取る関数にしてみました。こう書くことで型推論によって (read :: String -> Int) みたいなことをいちいち書かなくても済む...ような気がします(型推論がどこまで面倒を見てくれるのかよくわかっていない)。

7_C Spreadsheet

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_7_C

import Control.Applicative ((<$>))
import qualified Control.Monad as Monad (forM, replicateM)

type Row = [Int]
type Spreadsheet = [Row]

main = do
    [r,c] <- getLine'
    xs <- Monad.replicateM r $ getLine'
    mapM_ (putStrLn . printFormat) =<< newSpreadsheet (c+1) xs

getLine' :: IO [Int]
getLine' = map read . words <$> getLine

printFormat :: Row -> String
printFormat xs = unwords $ show <$> xs

newSpreadsheet :: Int -> Spreadsheet -> IO Spreadsheet
newSpreadsheet n xs = do
    let xs' = zipWith (++) xs $ rowSums xs
    return $ xs' ++ columnSums n xs'

rowSums :: Spreadsheet -> Spreadsheet
rowSums = map $ (:[]) . sum

columnSums :: (Monad m) =>Int -> Spreadsheet -> m Row
columnSums n xs = Monad.forM [0..(n-1)] $ \i -> return $ sum $ map (!! i) xs

入力として与えられるエクセル表のような数列に、各行の最後の列としてその行の合計値を、各行の最後の行としてその列の合計値を追加した表を出力するという問題でした。

rowSums で各行の行の合計値を計算した後に columnSums で各列の列の合計値を計算するようにしています。本当はモナド必要ないのに繰り返しを forM を使って計算させるの違和感しかないので、何か別の良い方法ないですかね...。畳み込みは今回だと与えられたリストに対して処理を行うわけではないため適切ではないと思い使いませんでした。

7_D Matrix Multiplication

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_7_D

import Control.Applicative ((<$>), (<*>))
import qualified Control.Monad as Monad (replicateM)
import qualified Data.List as List (transpose)
import qualified Data.List.Split as Split (chunksOf)

type Row = [Int]
type Matrix = [Row]

main = do
    [n,m,l] <- getLine'
    a <- Monad.replicateM n getLine'
    b <- Monad.replicateM m getLine'
    let result = toMatrix l $ dotProduct <$> a <*> List.transpose b
    mapM_ (putStrLn . printFormat) result

getLine' :: IO [Int]
getLine' = map (read :: String -> Int) . words <$> getLine

printFormat :: Row -> String
printFormat xs = unwords $ show <$> xs

toMatrix :: Int -> [Int] -> Matrix
toMatrix n xs = Split.chunksOf n xs

dotProduct :: Row -> Row -> Int
dotProduct xs ys = sum $ zipWith (*) xs ys

6_D Matrix Vector Multiplication では行列と列ベクトルの積を計算する問題でしたが、今回は行列同士の積を計算する問題です。行列Aと行列Bの積は、行列Aの行と、行列Bの転置行列の列の内積で計算することができます。行列の転置には transpose という便利な関数が Data.List モジュールにあるためそれを使いました。

どうして転置行列を使うことで行列の積が計算できるのかは別途証明する必要がありそうです...。(その辺りの文献が見当たらなかったので)

参考文献

github.com