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 ライブラリを使うことでベクトルや行列の計算ができるようになります。 以下は書籍で使われている Python の NumPy ライブラリとの比較です。
行列の生成
// 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 を見ながらやりました。
そして、書いたコードから出力されたグラフが以下。
参考文献
書いたコード
Haskell で『ゼロから作るDeep Learning』(1)
『ゼロから作るDeep Learning – Pythonで学ぶディープラーニングの理論と実装』の読書メモです。
この書籍のサンプルプログラムは Python で書かれていますが、表題の通り自分は Haskell で書いていきます。
今回は、2章全部です。
2章 パーセプトロン
パーセプトロンとは
- パーセプトロンとは、複数の信号を受け取ってひとつの信号を出力するアルゴリズムのこと
- 信号を入出力するものを「ニューロン」や「ノード」と呼ぶ
- 入力信号が送られるときには重みが乗算される
入力(x) -> 重み(w) -> 出力(y)
- ニューロンは受け取った信号の総和を計算し、それがある値を超えたときのみに 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 も作ることができました。
やってる様子は下記のインタビュー記事でも確認できますので是非。
プライベート
イベント
去年は3つほどしか行ってませんでしたが、今年は毎月何かしらのイベントに行っていました。
原因は μ's ファイナルライブによる μ's ロスだったのだと思います。 (9人全員のイベントに参加することができたのは本当に運が良かった...)
来年も9人全員のイベントに顔を出せるかは分かりませんが運とお金があるうちは追い続けていきたいですね。
以下参加したイベントです。
- [4/1,2] ラブライブ!μ's Final LoveLive! 〜μ'sic Forever♪♪♪♪♪♪♪♪♪〜
- 1日目 ライブビューイング
- 2日目 東京ドーム
- [4/10] 飯田里穂 tour of KISS3 (1部)
- [5/1,2] Pile Birthday Party !!! 2016 (LIVE)
- [5/8] 「南條愛乃・エオルゼアより愛をこめて」第2回オフ会「またお会いしたわね。」
- [6/4] 新田恵海 LIVE 2016 EAST EMUSIC 〜つなぐメロディー〜
- [6/26] 内田彩 AYAUCHIDA LIVE TOUR 2016
- [7/24] 内田彩 内田彩 30th ANNIVERSARY EVENT 〜MISOJI〜
- [7/31] ラブライブ!サンシャイン!! Aqours 夏休み課外活動 〜みんなでいっしょに夏まつり〜 in 沼津
- [8/13] 内田彩 AYA UCHIDA Complete LIVE 〜COLORS〜
- [9/19] 南條愛乃 LIVE TOUR 2016 "N" supported by dアニメストア
- [10/10] 第2回 南條愛乃オフィシャルFCイベント ごきんじょるの町内会2016 〜町をあげてのハロウィンパーティー〜
- [10/15] みらくる青空ナイトvol.9 〜瞬間、歌、重ねて〜 ライブ Colorful Voice Party (夜の部)
- [10/28] MIMORI SUZUKO GRAND REVUE LIVE TOUR 2016
- [11/13] 楠田亜衣奈 1st Live Blu-ray 「Eternal Precious Wave」 リリース記念LIVE
- [11/19] fripSide fripSide concert tour 2016-2017 -infinite synthesis 3- supported by animelomix
- [12/25] 久保ユリカ クリスマスイベント 2016 (昼の部)
Haskell
上半期で すごいHaskellたのしく学ぼう! を読了して(遅い)、下半期は AOJ を Haskell で解くなどしてました。
Haskell と少しは仲良くなれた気がしますがまだ実用レベルには達してないので、来年はその辺りを視野に入れて勉強していきたいです。
AOJ で解いた問題のコード
AOJ を解いたときのブログ
その他
- 数年ぶりにウォークマンを購入
- 購入したのは SONY NW-A36HN
- 前に使ってたのはたしか iPod nano 第3世代...
- スクフェスAC で人生初のアーケード体験
- まだ12回くらいしかやってないけど EXTREME フルコンできそう
- スクフェスで Rank 300
- 始めたのがラブライブ!のアニメ1期の放送終了後くらいだったのでだいたい3年半くらいやってる
- 動体視力はだいぶ鍛えられた
来年の抱負
Gem を作ったことで Ruby という言語に対する意識もだいぶ変わってきてより深く知ろうとする意欲が湧いたので、やっぱりアウトプットは大事だなと実感しつつこういった活動は継続していきたいと思いました。
Haskell は実用レベルで何か作って、最終的に仕事に組み込めるように頑張ります 💪
そんな感じで来年もバーンとやってこ。
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)
見れました。
実はテンプレートがあった
Yesod のときは「テンプレート」というものを使って簡単にセットアップできたのですが、調べたら Scotty のものもあったという...。
(Yesod のは 公式のクイックスタート にも載っています )
使い方は簡単で、 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 ...
生成されたファイルも最低限という印象。
まとめ
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 のリストを走査して各アルファベットの数を出力する
アルファベットの数を保持するのに今回は 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 が、時計回りに連続した文字を取ってきて作れるかどうかというところが肝なので解き方を考えるのに結構苦労しました。
解き方は次のような流れでやりました。
- P の先頭が、 S のどの位置に現れるのかを調べる
- 1で調べた位置をもとに、そこから P が作れるか調べる
- 作れたならそこで終わり、そうでなければ次の位置に進む
(流れだけ書いてみると意外と単純だった...?)
ポイントとしては、「リング状」の文字列から探すというのをどう実現するかというところ。今回は 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 編はこの辺りで一旦終わりにします。それでは。
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 モジュールにあるためそれを使いました。
どうして転置行列を使うことで行列の積が計算できるのかは別途証明する必要がありそうです...。(その辺りの文献が見当たらなかったので)
参考文献
『Effective Ruby』を読んだ
- 作者: Peter J. Jones,arton,長尾高弘
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/09
- メディア: 大型本
- この商品を含むブログ (13件) を見る
なぜ読んだか
Ruby は一応書けるけれど、実践的なコードを書くことになったときに発生する問題を解決するためのベストプラクティスや、より深いイディオムについてまだまだ足りない部分があると感じていて、Ruby の中級者向け以上の書籍を読みたかったので本書を読みました。
感想
各章の項目にその項目で言いたい結論が書かれているので、読み進める前の頭の整理にもなるし、何よりすべて読み終えた後で参照したくなったときに目次を見ればどの項目を読めばいいのか分かるのが良いと思いました。
例えば
第3章 コレクション
のような。
普段 Ruby を書く上で意識していなかった継承階層の組み立て方や super の括弧有り無しでの挙動の違いから、構造化データの表現には Struct を使うのがよいとか reduce でコレクションの畳み込みを使おうなど、なるほどと思えるテクニックもあり面白く読み進めることができました。
ただ、メタプログラミングの章については、この本の性質上メタプログラミング全体について書いてあるわけではなかったので、理解が追いつかない部分がありいまいち自分の中で消化できていない感じがしました。 『メタプログラミング Ruby』 を読んでから出直したいと思います。
あと、この書籍とはあまり関係がないのですが、本書の「第1章 Ruby に身体を慣らす」で言及しているように、 Ruby では定数がミュータブルであることだったりオブジェクトが nil になる可能性を常に考慮しないといけないことは、 Haskell を学習した後では煩わしさをよく感じるようになりました(自分の中に Static おじさんの存在を感じる...)。そんなこともありつつ、特異な Ruby の思想を取り上げている第1章やテストの章はわかるわかる〜と思いながら読んでました。
まとめ
本書を読んだことで Ruby の様々なテクニックを知ることができて Ruby と少し仲良くなれた気がしました。 何か問題にぶち当たったときや、別の書籍を読み終わった後に改めて読み返したいと思います。