7rpn’s blog: うわああああな日常

好きなことをつらつらと。AIとかで面白いことをしたい。

UbuntuでMacのキーボードを使う

MacのJISキーボードをUbuntuで使いたい。

Macのキーボードの入力方法ってWindowsとかと違ってちょっと便利で,かなボタンで全角,英数ボタンで半角にすることができます。
これの何が便利かって,絶対に半角か全角にできる ってとこなんですよ。

Windowsは全角半角ボタンがひとつなので,いちいち入力前に確認しなきゃいけない。

半角で打ちたい -> あっ全角になってる -> 全半角ボタンを押す

みたいに。 このいちいち確認しないといけないってのがMacユーザーにはつらいんです。
Macなら,半角で打ちたい時は確認せずに半角押せば絶対半角で打てるのでそれがすごい便利。

この設定をUbuntuでも使いたい...と思ったのでメモ程度に残しておこうと思いました。

環境:

Ubuntu 16.04

まずApple wireless keyboardをUbuntuで認識させます。 Ubuntu側のbluetooth受信機はバッファローのアマゾンで1000円のやつ使いました。

sudo apt update
sudo apt upgrade

とりあえず,これ絶対必須!
やらないと認識してくれません。というかこれやれば他にbluetooth系のapt叩く必要ないっぽいです。

とりあえずシステムを最新の状態にして,GUIから設定すれば難なくキーボードが使えるようになります。

この後は sudo dpkg-reconfigure keyboard-configurationして

  • Macbook/Macbook Pro
  • 日本語
  • 日本語
  • キーボードの配置デフォルト
  • コンポーズキーなし
  • いいえ

Macのキーボードとして設定。

次,入力方法をibusに変更しないと細かい設定ができないので変えます。
設定から「言語サポート」を開いて,何かしらインストールしろって言われたらとりあえずインストール。
そしてキーボード入力に使うIMシステムをfcitxからibusに変更。

ここまで変更したらUnityの右上のキーボードアイコンをクリックして出てくる「設定」の「全体の設定」タブで拡張オプションを表示する。 そうすると細かい設定が出てくる!

「入力メソッドをオン」にかなボタン,オフに英数ボタンを割り当てて終わり。
普段のMacの快適さ。よいっすね。

MacBookAirのケース最強すぎ

90日間更新してないブログ広告が表示されるようになってしまったので,超どうでも良いことで更新します。笑

先日,腰ぐらいの高さからMacBook Air落としました。カバンの蓋が空いていたのに気づかなくてそこからするりと。
落ちた先はエスカレータの金属部分で,ゴンっていうクソみたいな音がしました。ケースは付けていたんですが結構高いところから落ちたので,(PCの)死を覚悟しましたね。

恐る恐るPCを見てみると…なんと無傷
サンワのAir用プロテクトケースとかいうの使ってたんですが,超オススメです。

さすがにこれはひとつの記事として短すぎるか…笑

…また広告が出たので追記。
IO-DATAのケース買ったんですがAir用なのにAirが入りませんでした。
どうなってんの,Amazonのレビューは見るべきだった

SwiftでSQLiteが使いたい?ならSwiftyDBが良いよ!

SwiftでSQLiteが使いたいと思ったわけです。
なのでとりあえず"Swift SQLite"みたいな感じで検索するじゃないですか。
一番上に出てきたSwiftDataとかいうのを使おうと思ってgithubを見ると,最終更新が2 years agoなわけです。あれ,やばくね?

もちろん使ってみてもSwift2に対応していないのかエラー出まくり
ほかのSQLiteのライブラリ試してみてもどれも一筋縄ではいかない。

だってRealmがあるもの

どうしてSwiftではSQLiteが打ち棄てられているのでしょうか。

理由は,Realmとかいうスーパーデータベースのせいなんですかね?
SQLiteの後継と言われてるらしくて,かなり盛り上がっています。書き込みも読み込みもSQLiteよりずっと速い。
基本的に今SwiftでDB扱うならまずRealmみたいな雰囲気なんでしょう。多分。

でも,Rubyを使う私たちはActiveRecordでさっと扱えるSQLiteが使いたいんですよ。
RubyでDBをさくっと作ってそれをシームレスにSwiftに渡してiOSアプリを作りたいんです。
Realmは現状JavaとSwift(あとcsv)にしか対応してないっぽいので,ここでRealmを扱うのは避けたい。

そんな風にActiveRecordぽくさっと使えて,今でもちゃんと更新されてるSwiftのSQLiteライブラリはないかな,と探し試すこと数時間。
ありました,その名もSwiftyDB! 日本語でこのライブラリを扱っている記事がなかったのでここに残しておきます。

環境

基本的な使い方

ざっくりとした使い方に触れます。より詳しく知りたい場合はSwiftyDBのサイトをどうぞ。

今回は,Dogという名のテーブルにname:string old:integerのデータを保存するdogtopia.sqliteを作りたいと思います。 そこからデータの保存と取り出しをやりたいと思います。

class Dog : Storable {
    var name: String?
    var old: Int?

    required init() {}
}

おまじないとして,ActiveRecordのようにclassとしてtableの宣言が必要です。カラムの型を一つづつ明示する必要があり。 SwiftyDBでもclass名がそのままtable名になります。


let dogdb = SwiftyDB(databaseName: "dogtopia")

let dog1 = Dog()
dog1.name = "pochi"
dog1.old = 3
dogdb.addObject(dog1,update:true)

SwiftDB()でデータベースを使うことを宣言します。DBが存在していなければDocumentフォルダに勝手に作ってくれるのでとても便利。
DBファイルの名前は上記のコードの場合,"dogtopia.sqlite"になります。

Dog()で新しいデータを作って,プロパティとして情報を入れていきます。 addObjectするだけでデータの保存ができます。楽。


let filter = Filter.equal("old", value: 3)

let result = dogdb.dataForType(Dog.self,matchingFilter: filter)
if let data = result.value{
    data.forEach{
      print($0["name"])
      print($0["old"])
    }
}

dogdb.asyncDataForType(Dog.self,matchingFilter: filter){ (result) -> Void in
  // 処理
}

逆にデータを取り出す際はこんな感じ。Hash的に取り出せます。 dataForTypeでデータベースの中身を取り出して,あとはお好きなようにという感じ。ただOptionalかかってるのでそれは外してください。 filterもかなり楽にかけられます。

非同期処理でDBを取り出すメソッドasyncDataForTypeもあるので,やりたいことがサクッと済ませられる感じ。

ActiveRecordは保存するときにHashっぽくて取り出す時はプロパティですが,SwiftyDBは逆っぽいです。

RubyからSQLiteを渡して使う

自分はSwiftyDB.swiftを直接編集して使っています。

public initの部分で,

let path = NSBundle.mainBundle().pathForResource(databaseName, ofType: "sqlite")

みたいな感じでアプリのrootに保存したDBファイルを読み込みに行くように書き換えてます。Document内に最初からDB保存するとRejectされるっぽいので。

これでRubyで作ったDBをSwiftで扱うアプリが作り放題になりましたね。
ActiveRecordがRealmに対応するのが一番嬉しいんですが,正直当分はなさそうなので,それまではSwiftyDBでいこうと思います。

DeepLearningで自動作曲。GoogleのMagentaを自力で組んでみる。

最近,GoogleMagentaというプロジェクトが公開されました。 AIに自動作曲をさせようという試みで,Google謹製ライブラリのTensorflowを使います。

今回やり方だけを眺めて,同じやり方をchainerとかRubyとか使って組んで,自動作曲を行ってみました。

とりあえず作った曲

今回はBachのInvention4番の右手を学習させてみました。


作られた曲はそうですか...って感じ。130epochでこんなもの。


こっちは荒ぶってる。150epoch。

公式が上げてる自動作曲された曲もこんな感じなのでまぁ。

Magentaを使わない理由?

勉強会でMagentaやろうってなったけど,Bazelがまったく走らなかった。

途中でMagenta自体を自力で作った方が早いのでは?って話になり,chainerとか使って同じものを作ってみようって感じになりました。

まぁTensorflowなんとなく使いにくいですし。chainerのさらっと書ける感やばい。食わず嫌いかも?

というわけで実装

Magenta自体に三つ作曲方法が入っていますが,基本となるBasic RNNのやり方を真似てみました。

やることをざっくりと言うと,今の音を入力したら次の音は何か?を予測できるようなモデルを作ります。 リズムの情報が入ってくるのでそこが面倒。

手順

  • MIDIを最小イベントごとに区切る
  • 各Eventで音を鳴らすか,前回の状態を継続するか,音を止めるかのどれかを入力
  • そこから次Eventの状態を予測

入力は[音の高さ128次元]+[継続or止める]の130次元のone-hotベクトルになります。 一番細かい音符が64分だったなら曲全体を64分ごとに区切ってLSTMに突っ込む感じ。

一曲を[音の高さ128次元]+[継続or止める]のイベントベクトルの時系列データとみなして,前のベクトルを入力したら次のベクトルを予測して出力します。 これで曲を作らせる感じ。

コード

勉強会が一時間くらいでやろうって雰囲気だったので,結構美しくないコードを書いています。あまりお気になさらず。

まず,RubyMIDIを配列に書き換え。 MIDIファイルを読み込んで右手部分を配列にします。

require "midilib"

Encoding.default_external="utf-8"

seq = MIDI::Sequence.new()

File.open("ファイル名.mid", 'rb') do |file|
    seq.read(file)
end

delta = seq.note_to_delta('quarter')
midi = []
timesig = nil

seq.each_with_index do |track,i|
    print i,"\n"
    break if i == 2
    track.each do |event|
        if MIDI::TimeSig === event
            timesig = event.to_s.delete("time sig").split("/").map(&:to_f)
            print timesig,"\n"
        end
        if MIDI::NoteEvent === event and event.note
            if event.to_s =~/off/
                on_off = 0
            elsif event.to_s =~/on/
                on_off = 1
            end
            midi << [event.note,event.time_from_start,on_off]
        end
    end
end
bar = delta  * 4 * (timesig[0]/timesig[1])
output = midi.map do |event|
    bar_num = event[1] / bar.to_i
    step = event[1] % bar.to_i
    [event[0],event[-1],bar_num,step/bar]
end
# => note_num, on_off, 小節

min_num = (音符の最小の長さ) #本当はベタ打ちすべきでは無い
midi.map!{|m| m[1] = m[1]/min_num;m}
hash = midi.group_by{|g| g[1]}.map do |key,value|
    if value.size > 1
        value = value.select{|s| s.last == 1}
    end
    [key,*value]
end.to_h
list = (0..midi.last[1]).to_a.map{|m| hash[m]}
list.map! do |event|
    if event
        if event.last == 0
            ret = 129
        elsif event.last == 1
            ret = event.first
        else
            raise
        end
    else
        ret = 128
    end
    ret
end
print list #=> MIDIの配列

Rubyで実行した配列をPythonに放り込んで学習。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import numpy as np
import chainer
from chainer import cuda, Function, gradient_check, report, training, utils, Variable
from chainer import datasets, iterators, optimizers, serializers
from chainer import Link, Chain, ChainList
import chainer.functions as F
import chainer.links as L
from chainer.training import extensions

def each_cons(x, size):
    return [x[i:i+size] for i in range(len(x)-size+1)]

class RNN(Chain):
    def __init__(self):
        super(RNN, self).__init__(
            embed=L.EmbedID(130, 100),
            mid=L.LSTM(100, 50),  # the first LSTM layer
            out=L.Linear(50, 130),  # the feed-forward output layer
        )

    def reset_state(self):
        self.mid.reset_state()

    def __call__(self, input_vector):
        # Given the current word ID, predict the next word.
        x = self.embed(input_vector)
        h = self.mid(x)
        y = self.out(h)
        return y

rnn = RNN()
model = L.Classifier(rnn)
optimizer = optimizers.SGD()
optimizer.setup(model)


midi = (MIDIの配列を入れる)


loss = 0
count = 0
seqlen = len(midi[1:])
for x in range(100):
    print x
    rnn.reset_state()
    for cur_event,next_event in each_cons(midi,2):
        cur_event, next_event = np.asarray([cur_event]).astype(np.int32), np.asarray([next_event]).astype(np.int32)
        loss += model(cur_event, next_event)
        count += 1
        if count % 30 == 0 or count == seqlen:
            #print loss.data
            model.zerograds()
            loss.backward()
            loss.unchain_backward()
            optimizer.update()


model.predictor.reset_state()
list = []
prev_event = chainer.Variable(np.array([64], np.int32))
prob = F.softmax(model.predictor(prev_event))
list.append(64)

for i in range(200):
    prob = F.softmax(model.predictor(prev_event))
    probability = prob.data[0].astype(np.float64)
    probability /= np.sum(probability)
    index = np.random.choice(range(len(probability)), p=probability)

    list.append(index)

    prev_event = chainer.Variable(np.array([index], dtype=np.int32))

print list #=>自動作曲の出力

出力結果をRubyに渡してMIDIを作成。

require 'midilib'
include MIDI
Encoding.default_external="utf-8"

seq = Sequence.new()

m_track = Track.new(seq)
seq.tracks << m_track
m_track.events << Tempo.new(Tempo.bpm_to_mpq 120 )
m_track.events << MetaEvent.new(META_SEQ_NAME, 'wave')

track = Track.new(seq)
seq.tracks << track
seq.name = 'Input Track'
track_instrument = GM_PATCH_NAMES[0]

track.events << ProgramChange.new(0, 1, 0)
delta = seq.note_to_delta('quarter')

list = (さっきの出力)

track.events = list.map do |m|
    if m < 128
        on = NoteOn.new(0,m,127,0)
        off = NoteOff.new(0,m,127,delta/2)
    else
        on = NoteOn.new(0,0,0,0)
        off = NoteOff.new(0,0,0,delta/2)
    end
    [on,off]
end.flatten

File.open('output.midi', 'wb'){|f| seq.write(f)}

という流れ。全部Pythonに統一した方が良いんだろうけどRubyから離れられない人。

これからやってみたいこと

  • lookbackとattentionも実装したい
  • たくさんのデータを詰め込んでみたいよね
  • あとはコード進行データとかと同期させたり,和音に対応させたり
  • 即興のお供作りたい

必要だと思う

  • 転調しても曲になるから全調に転調して突っ込むべきな気がする

落ちたら死ぬ!!の看板がある国道157号線を自転車で走った。

少し前ですが,国道157号を通ってきました。 福井県行きたいなーと思って,名古屋から福井へ。
噂の酷道を体験してみたかったので。

しかし残念なお知らせ。 落ちたら死ぬ!!の看板はなくなっていました。有名になりすぎたんですかね。

f:id:s7rpn:20160629185256j:plain:w400
ミラーの脇にあるはずなのに...
引用: 酷道157号8年ぶりに開通!

f:id:s7rpn:20160629185353p:plain:w400
ない!

ここにあったことを知らなかったんですが,偶然写真を撮ってました。iPhoneのLivePhotoが初めて役に立ちました。
この看板を見ることをかなり楽しみにしてたので残念でした。

見落としたかもしれないですが一つも見つけられなかったので,撤去される流れなんですかね。

看板が撤去されたと言っても,いまでも落ちたら死ぬと思います。昔よりは安全にはなったと思いますが...
携帯も圏外だったので,落ちたらもう這い上がって来れないのでは?って感じの道でした。

※9/3追記-----
写真よりも右側に看板は現存しているそうです。
せっかくあそこまで行ったので一目見たかった...
-----追記ここまで

f:id:s7rpn:20160629191645j:plain:w400

あと落ちたら死ぬ!の看板はありませんでしたが,道路を突っ切る川は健在でした。
本当に川が道路の中を流れてて笑った

f:id:s7rpn:20160629191020p:plain:w400

この他にもたくさんの洗い越しがありました。
通るときちょっと楽しかったです。ぬれたけど。

福井側がまともに舗装されてなくてつらかったです。まさに酷道
ロードだったらパンクするでしょこれ

f:id:s7rpn:20160629191908j:plain:w400

県境越えて下りで40km近く速度出してる時にここに突っ込んだので吹っ飛ぶかと思いました。笑

あとかなり岐阜側ですが,とてもやばい橋がありました。 すごく渡りたい。さすがにやめましたけど。

f:id:s7rpn:20160629195036j:plain:w600

157号線も大抵川沿いを通ってるんですけど,本当に川が綺麗でした。 かなり下流だけどこの綺麗さ。

f:id:s7rpn:20160629200337j:plain:w400

上流のほうで一回川まで降りてみれば良かったかなーと思いますね。

てか今日一日で3800m以上登ったらしいです。
自転車で富士山登れる。

f:id:s7rpn:20160629195350p:plain:w200

全体として温見峠越えが本当にきつかったです。こんな暑い時期に行かなくても。
自販機が思ったより早い地点でなくなってしまったので水不足に悩まされました。まぁ157号線は8年ずっと封鎖されてたので近くにあるわけがないんですけどね。

途中に登山口があったのですが,山登ってる人に「自転車でここまで来るとか笑」みたいに言われました。 山はちょっと登ってすぐ諦めました。

やっぱり登った分だけ下れるってのが良いですね。下りは車のスピードで走れるのでとても楽しかったです。
国道157号線,ちょっとスリリングで楽しかったです。

Googleが出した囲碁ソフト「AlphaGo」の論文を翻訳して解説してみる。

就活も無事終わったので,一番やりたかったAlphaGoの論文を翻訳しました。
ご存知の通り,長らく世界最強だった囲碁棋士イ・セドル九段を破ったGoogle囲碁プログラムです。

論文の内容に触れつつ何となく解説入れていきたいと思います。なるべく囲碁やDeepLearningを知らない初心者の人とかでも仕組みを理解できるように分かりやすく書いていければいいなと思います。

原題は"Mastering the game of Go with deep neural networks and tree search"。

とりあえず最初の要約の訳から。
謎の単語とかは後から説明入れるので,さらっと流し読みしていただければ。

囲碁はこれまでAIにとってとても難しいゲームだとみなされてきた。それは探索範囲がとても広いことと,盤面の評価が難しいため。
この論文では,コンピュータを用いた囲碁の新しいアプローチを紹介する。 このアプローチは,盤面の評価のためのvalue networkと,手を選ぶためのpolicy networkを用いる。
これらDeep Neural Networkは,人間の打ち手を用いた教師付き学習と,自分同士の対戦による強化学習をうまく組み合わせたもの。 このneural networkは,先読みの探索を行わずとも,最先端の他のモンテカルロ木探索を用いた囲碁ソフト(これらは自分自身と戦うことで何千もの先読みを行う)に匹敵した能力を持つ。
同様に,モンテカルロ木探索とvalueおよびpolicy netwrokを組み合わせた新しい探索方法を提案する。 このアルゴリズムを使うことによって,AlphaGoはほかの囲碁のプログラムに99.8%勝利できるようになった。そしてヨーロッパチャンピオンに5戦5勝をおさめた。 19路の囲碁においてコンピュータプログラムが人間のプロを負かすという,これまで何十年もかかると言われて来た,偉業を達成できたのはこれが初めて。

要約でいい感じに論文がまとまっていますね。
ここで重要なのは,先読み無しで最先端の囲碁ソフトに匹敵って部分なのではないでしょうか。

従来の囲碁プログラムは,相手が自分自身だったらどう打つかを考えて次の打ち手を決めます。
すなわち,今の盤面からどう打つかを決める際には,この手を打ったら相手は(自分のプログラムで考えれば)こう打ち返して,それに自分が打ち返して...という手筋を先読みして何パターンも考えるわけですね。そのうえで一番勝てそうだと判断した手を選ぶわけです。ここは人間によく似ている部分だと思います。

しかし,この先読み無しAlphaGoは違います。今の盤面の状態を見て,そこからここに打つと勝ちやすいだろうという感覚だけで打っても他の何千パターンもの先読みをしている囲碁プログラムに匹敵してしまいます。

そのAlphaGoに先読みを組み込んだら...どのような恐ろしい強さを誇るのでしょうか...?

ご存知の通り,この先読みを組み込んだAlphaGoは,囲碁で世界最強と謳われたイ・セドル九段を打ち負かしました。

というわけで,今回この記事では先読み無しAlphaGoがどのようにしてこの強さを誇るようになったかと,そのAlphaGoに先読みを組み込むことで最強の囲碁プログラムAlphaGoが作られた手法の二つに分けて紹介したいと思います。

こちらの記事ではかなり噛み砕いて分かりやすいように再構築しています。
逐語訳も作ったんですが思った以上に読みにくいので,気が向いたら綺麗に清書して投稿します。

とりあえず導入と背景

囲碁はこれまでAIにとってとてつもなく難しいゲームで,人間を越えるにはあと数十年かかるのではと言われていました。
その理由として,幅と深さが膨大なことです。 ほかのボードゲームと比べて,一手ごとに打てる手のパターン(すなわち幅)が多く,またゲームが終わるまでの手数(すなわち深さ)が多いわけです。 なので,チェスのように探索がすぐ終わる,というわけにはいかないんですね。

一般的にチェスは幅35,深さ80程度なので,全探索しても{35^{80}}通り(1247桁)調べるだけで済みます。しかし囲碁は幅250,深さ150ほどであり,3607桁と計算量がとても膨大になります。一桁増えれば総当たりの計算量は10倍になるわけで,これがコンピュータ囲碁がこれまで人間に全く歯が立たなかった理由の一つです。

この現状で囲碁プログラムが人間に勝てるようになるためには,計算量を削減するしかないわけですね。
第一に深さを削る。第二に幅を削る。このようにして計算量を削減します。

深さを削る場合,重要になるのは評価関数value functionです。 盤面の状態sから,勝っているか負けているかを表す評価関数{v^{*}(s)}の精度が非常に求められます。
なぜなら,理想的にはゲームの最後まで先読みするのが最も精度が高いですが,計算量を考えると現実的にはある程度のところで打ち切る必要があります。その打ち切った盤面での優勢劣勢を精度良く評価することが手の良さに直結するので,評価関数が非常に効いてきます。 この手法を用いることで,チェスやチェッカー,オセロにおいて人間を越えるパフォーマンスを出しています。

幅を削る場合,次にどの手を打つと勝ちやすいか?を表すpolicyが重要になってきます。 悪手を削り,良い手のみを深い手まで探索していくことで,幅の計算量をより減らすことが可能になります。 例えば,Monte Carlo rollouts searchはpolicyによって良い手のみを選んで終局まで探索することによって次の手を選びます。バックギャモンスクラブル(なにそれ?)で人間を越えているそうです。

その手法を発展させたのがモンテカルロ木探索,Monte Carlo tree search(MCTS)です。policyによって効率的な手を複数選び,計算を進めることで探索範囲を増やします。計算量を増やすことでより精度良く手を選ぶことができ,AlphaGo以前ではMCTSを用いたものが最強の囲碁プログラムでした(人の手のビッグデータを用いてpolicyを作ったもの)。 しかし,囲碁においてはMCTSを用いても強いアマチュアレベルまでしか到達できませんでした。

では,人類を越えたAlphaGoはどのように計算量を削減したのでしょうか。 AlphaGoでは幅と深さの計算量削減を,DeepLearningに委ねます。 深さを削るために勝敗評価を行うvalue network,幅を削るために良い手を選ぶpolicy networkを用意します。これらは多層のニューラルネットワークで構成されています。 そこにMCTSを組み合わせるという手法です。

policy networkのみで前述の先読み無しAlphaGoが構成され,そこにvalue networkとMCTSを組み合わせて先読みありのAlphaGoが作られるという感じです。 これらがどういう風に組まれているのかを,追って見ていきます。

DeepLearningについてですが,これは画像認識などでとても高い精度を誇っていて,2015年には物体の分類において人間の認識精度を越えています。 Googleの天才がたは,盤面を19×19の画像と見なして,盤面を畳み込みニューラルネットに読み込ませるという手法をとりました。
つまりAlphaGoは囲碁の盤面を画像認識に突っ込んで,勝ちやすいか勝ちにくいか判定させようっていうぶっ飛んだ手法をとってます。
とりあえずは,ニューラルネットワークは画像(=盤面)を入力したら確率などの値が出てくる関数のようなものと考えてください。

このくらいがざっとした導入ですかね。

先読み無しAlphaGoの作成

先読み無しAlphaGoですが,主に作るための作業が二段階に分かれています。

教師付き学習フェーズでは,実際の囲碁のプロが打った手のデータベースを用いて,policy networkを学習させます。
その後強化学習フェーズにおいては,自分自身と戦わせて,より強くpolicyを進化させていくフェーズになります。

具体的にpolicy networkをどう組んで,どうトレーニングさせたか見ていきましょう。

教師付き学習フェーズ(SL policyの作成)

論文のSupervised learning of policy networksの部分に相当します。

先行研究においての最強の囲碁プログラムは,上級者の手をpolicy networkに学習させることで手筋を予測させていました。 そこで,まずは先行研究と同じように教師付き学習policy network {p_σ(a|s)},以後はSL policyと呼ぶ,に上級者の手を学習させます。

f:id:s7rpn:20160610190516p:plain:h200

具体的には,KGS囲碁サーバーにある16万局,3000万の盤面(の反転,回転を加えて3000万×8)を用いて,13層のpolicy networkを学習させます。
囲碁データの盤面{s}をpolicyに入力したら,次に打たれる手{a}の確率が最も高くなるような確率分布{p_σ(a|s)}を出力するように確率的勾配降下法を用いて学習させます。
結果,次の手の予測が57.0%の精度で予測できるようになりました。 従来の最高の予測精度が44.4%であり,これだけでも大きな進歩です。

同様に,高速policy network{p_π(a|s)}も作成しました。これは予測精度は24.2%と低いですが,手を選ぶのに2μsしか消費しません。{p_σ(a|s)}は3ms消費するので,1000倍以上の速さを誇ります。
この速さを生かして終局まで探索することが可能であり,後に先読みを組み込む際に精度上昇に貢献します。

強化学習フェーズ(RL policyの作成)

論文のReinforcement learning of policy networksの部分に相当。

ここでは,policy network同士を戦わせることでより良い手を打つ強化学習policy network {p_ρ(a|s)},以後RL policyと呼ぶ,を作ることを目的とします。

最初はSL policyの重み{σ}を用います。
現在のRL policyとこれまでのpolicy(SL policyから現在のRL policyまで)からランダムに選ばれたpolicy同士を戦わせることで,現在のRL policyを進化させます。 ランダムに選ぶ理由として,現時点最強のみと戦わせていた場合に極所解に陥る可能性があるので,より多様性のあるの打ち方を体験できるように弱いpolicyから強いpolicyまでを使っています。

policyの進化のさせ方として,前述の通り,現時点では先読み無しのpolicyを扱っているので,ある手を打った場合の終局時の盤面が即座に計算で決定されます。
その手が勝っている場合は+1,負けている場合は-1として,各手ごとに勝ち負けを判断して,networkの勾配を更新します。

この強化学習フェーズを終えることで,先読み無しAlphaGoの完成です。50個のGPUを使って一日で128万局の対戦を行わせたそうです。完全に人間の能力を越えていますね...

最終的に,RL policyはSL policyに80%の確率で勝つようになりました。
また,オープンソースで最も強い囲碁ソフトPachi(こいつはもちろん先読みする囲碁ソフト)に,85%の確率で勝利できるようになりました。

そして,次は先読みをAlphaGoに組み込む時です。

AlphaGoに先読みを組み込む

これまでの先読み無しAlphaGoの時点で,強い手を選ぶことが可能なpolicyを作ることができました。 なので,幅についての計算量は心配する必要がないと思います。
ここで問題となるのは深さの計算量を削ることです。そのためには正確な盤面の勝敗の評価関数value functionが必要でした。

今回,AlphaGoに先読みを組み込む準備として,まずは盤面評価のためのvalue networkを作ります。
その後,それらpolicy,valueMCTSに上手く組み込むことでAlphaGoを完成させます。

value networkの作成

論文のReinforcement learning of value networksの部分。

深さの計算量を削るための評価関数を作ります。

value network {v_θ(s)}は構造はpolicy networkとほぼ同じ13層のニューラルネットですが,最後の出力が異なり,勝敗を出力するように組まれています。 つまり,盤面{s}を入力して勝敗{z}(-1~+1)を出力します。

f:id:s7rpn:20160610190525p:plain:h200

その盤面の勝敗に関しては,両方が最善手を打つpolicy同士で戦わせた際の勝敗を{z}とします。現時点で最強のpolicyはRL policyなので,これを用いて勝敗を求めます。
value networkを作るにあたって,RL policyで戦わせた3000万局から独立した盤面のstate{s}とそれの勝敗{z}を教師データとして学習させました。
従来手法では人間が打った囲碁データからvalue functionを作ったものもあるそうですが,KGSの学習データ16万局ではGoogleの求める精度には至らなかったようです。(過学習ぎみになる) そのため,わざわざpolicy同士を戦わせてデータを新しく作ることになりました。

f:id:s7rpn:20160610190849p:plain:h180

グラフをご覧の通り,value networkによる盤面評価は,実際に現時点最強のRL policy同士で戦わせた盤面評価に迫る性能を発揮しています。
にもかかわらず,RL policyと比較してなんと計算時間が15000倍少なく済みます。
これがいわゆる”人間の勘とか呼ばれているもの”のような気もします。

先読みを組み込み,AlphaGoを完成させる

Searching with policy and value networksの部分。

さて,この記事も大詰めを迎えてきました。

とても計算が速く精度も高いvalue networkと,先読みせずともとても強いpolicy networkが揃ったので,後は先読みのためにこれらを上手くMCTSに組み込みます。手順が複雑。

f:id:s7rpn:20160610190508p:plain

まず,探索を一番上(root)から始めて,各々の状態(node)において次の手{a_t}を求めます。
その{t}番目のnodeでの次の手{a_t}

{ \displaystyle a_t =\underset{a}{argmax}(Q(s_t,a)+u(s_t,a))}

すなわち,{Q(s_t,a)+u(s_t,a)}を最も最大化する{a}が選ばれます。 ここで,

{\displaystyle u(s_t,a)∝\frac{P(s,a)}{1 + N(s,a)}}となります。

この{u(s_t,a)}{t}番目のnodeにおける次の手の確率分布{P(s,a)}と比例します。ただしその端点を訪ねた回数{N(s,a)}で割っているので,同じ場所ばかりを探索させないようにする効果を持ちます。1を足してるのは0除算を避けるためですよね。
以上を規定の数{L}番目のnodeまで探索を行い,端点に辿りついた際,SL policyを用いてその手までの各nodeでの確率分布{P}を求めます。
そして,この時点での勝敗の評価を,二通りの方法で行います。

  • value networkに盤面を評価させる
  • 高速policy{p_π}を用いて,終局まで対戦させ,勝敗を評価する

これら二通りの方法を足しあわせて,その時点での盤面の勝敗の評価{V(s_L)}

{V(s_L) = (1 − λ)v_θ(s_L) + λz_L}

として求めます。λは定数。

探索が最後まで終わったら,端点の{Q(s_t,a)}{N(s_t,a)}が更新されます。 これらは

{\displaystyle N(s,a)=\sum^{n}_{i=1}1(s,a,i)}

{\displaystyle Q(s,a)=\frac{1}{N(s,a)}\sum^{n}_{i=1}1(s,a,i)V(s_L^{i})}

から求められます。

式だけ見るとうわって感じですが,単純に{N(s,a)}は打てる手のうちの訪ねた手の数を数えているだけです。
{Q(s,a)}は訪ねた手での勝敗の評価を訪ねた手の数で割っているので,訪ねた手の勝率を返しています。
より勝ちやすい手を多く探索して,負けやすい手に目を向けない式の形になっていますね。
ある程度のステップで探索を止めますが,探索が終わった時点で,より多く訪ねた手を次の手として採用します。

興味深いのは,最終的なAlphaGoではpolicy部分にRL policyではなくSL policyを用いた方が強いことです。 RL policyは一つの強い手に集まりますが,多くの手筋を持ったSL policyのほうが見通しの能力が高いのでは?とのこと。
逆にvalue functionに関してはRL policyを用いて作成したvalue networkのほうがSL policyより強い手を打てるそうです。実際の囲碁でも強い人と戦うと盤面評価の能力は上がるけれども,よりたくさんの人と戦わないと想定外の手に対応できないような気もするので,感覚的にもなるほどと思いました。

あと,{λ}に関しては最適は0.5だそうです。高速policyとvalue networkを半々で評価すると一番強くなるっていうのも興味深いですね。
人間に例えるなら頭の中でざっくりと深い手まで先読みしつつ,同時に勘に頼って次の手を決めるって感じなので,かなり人っぽいと自分は思いますがどうでしょうか。

ハード的な面に関しては,AlphaGoは,value networkを求めるGPUと手の探索をするCPUを並列して動かすことで,より速度を上げているそうです。 最終的に,AlphaGoは48個のCPUと8個のGPUで使えるように組まれました。 同時に,より強い分散処理型として,1202個のCPUと176個のGPUを用いたものも作られました。これがイ・セドル九段を破ったAlphaGoです。

感想とか

やっぱり語りたいのはイ・セドル九段 vs AlphaGoの四局目78手ですよね。イ・セドル九段が打った神の一手とも言えるあの手です。 正直あの手は思い浮かびませんでした。でも後から見返すとものすごい妙手ですよね。
実際AlphaGoもあの手を考慮に入れていなかったんだと思います。長考した末あの手を打ったセドルが格好良すぎる。
この一局もリアルタイムで見ましたが,ものすごく興奮しました。人間の思考力がGoogleの本気のプログラムをまだ上回れるんだ,とそう思いました。

と同時に,ボードゲームでは人間が機械に全く歯が立たなくなる日も近いんだろうな,とも感じます。
何がすごいってvalue networkの計算速度の速さですよね。15000倍。これが人間の勘とか感覚に相当する物なら,勘を要求するような職人芸などはいずれ機械に取って代わられることになるんですかね。
ただ,囲碁に関して言えばそれをするために(自分同士で戦った譜ですが)大量のデータが必要だったので,人間の勘を完全に機械が越えるのもまだ時間はかかりそうだな,とも思います。
ただDeepLearningは学習時間と計算能力をつぎ込めば底なしに思えてしまうレベルで最適化が進んでいくので,最適化しやすい分野はどんどん取って代わられてしまうんでしょうね。
未来はどうなってしまうんでしょうか。ちょっと怖い反面,わくわくしています。

あと,思ったより実装できそうなシンプルな構成だったので,暇がある時に実装できると良いなーとも思います。GPU絶対足りないですけどね...

この記事結構ざっと書いたので,質問とかあればなんなりと書き込んでください。
もちろん中上級者のかたがたの「これ違うんじゃね?」突っ込みもお待ちしています。実際訳してて不安な場所も多々あったので。時間見てブラッシュアップしていく予定。

最後に,この記事を通して何か興味深い知見を得ていただけたのならば幸いです。ここまで読んでいただきありがとうございました。

関連: 7rpn.hatenablog.com

7rpn.hatenablog.com

就活はクソ

就活ちょっとひどくないですかね?とくに2017卒の今年。
結構早いうちに内定先決まったので不満はないのですが,言いたいことはたくさんあるのでつらつら述べていきたいと思います。

短期決戦がひどい

短期決戦てどう考えても失敗じゃないですか?割を食った学生は多いと思います。
今年は3月広報解禁,6月面接開始と経団連が決めていました。まともに就活できる期間がたった3ヶ月しかない。

自分は就活始めたのが3月中盤だったんですよね。めぼしい会社プレエントリーしてさあES書くぞ!と思って採用ページ見たら並ぶ一次募集締め切りましたの文字。
ひどい会社だとプレエントリーすらさせてもらえませんでした。結構志望度高かったのに。

こんなペースだったら企業をじっくり選別なんてしている暇がないですよね。 だから何となくで志望業界を決めて,何となく選考を進めていくことになります。
途中で「あれ,自分ってこの業界向いてないんじゃ?」と思ってもすでに変更不可能。ひどすぎる。

自分も最初SIer志望だったんですが,一歩向いてないなと感じるのが遅かったら致命傷でした。本当に危なかったと思います。
巡り合わせが悪ければNTTデータさんに推薦出してたかもしれない。元第一志望だったので。
結局データさんにはESすら出しませんでした。

情報系ならSIerだろとかいうなんとなくな思考は危険なわけですね。上流は人のマネジメントばかりなので。
そんな感じで,今年の就活生は企業とのミスマッチがめっちゃ起こって離職率高かったりするんじゃないですかね。この誰も得してない感じ。

自分は早いうちに志望職種切り替えました。現時点で精密機器メーカー二社を貰えてそこの片方に決めたんですが,正直ミスマッチも少し怖いです。短期決戦なので全然企業研究してる暇なかったし。福利厚生とかもっとちゃんと調べれば良かった...
まぁ他にここより行きたいって会社が出てきてないのが幸いですかね。

学生と企業の騙しあいなのがひどい

これ。学生も企業も嘘つきすぎじゃないですかね。笑

自分はまったく嘘偽り無く自己PRとかを書いたのですが,盛ってる人とかやばいですね。 失敗経験を成功経験に変えてES書いている人とかもいましたし。
実際面接のプロの人事のかたでも,一時間程度の面接でどこまで見抜けるかは定かじゃないと思います。大半は嘘に気づいていると信じたいですが。

この正直者が馬鹿をみる仕組みってどうなんでしょう,とは思いますね。
自分も選考で「御社の親会社受けてます」とか言ったことを後悔したりしました。これはやっぱり嘘ついても第一志望って言うべきだったんでしょうね。通ってたんで良かったですけど。

あと,企業からのメールで「選考ではありませんのでお気軽にお越し下さい」って書いてあるのにガチ面接始まるやつ。あれなんなん。
最初は自分も気づいてなかったです。すごく面接っぽい相談会だなーとか普通に思っていました笑
あとからみんしゅう見て知ったんですけど,あれで落ちてたらショックだったんじゃないですかね。相談会と思って文字通り対策してないんですから。
結局通ってたので幸いでしたけどね。落ちてたら泣くに泣けない。

やっぱり,相談会って書いてあるけど面接だよと事前に告知してくれる企業が良いと思います。
自分はそういう会社に行きます。

最後は運なのがひどい

正直面接官との相性じゃないですか?就活。
運ゲーで今後数十年働くかもしれない会社決めるって怖いなぁって思います。
もうちょっといい方法ないんですかね。ないから毎年こんな感じなんでしょうけれど。

これやっておけば良かったと思うこと

最後にこうしていればもう少し楽だったなと思うことをいくつか。

去年の就活の流れを確認するべきだったなと思います。2chの過去スレやみんしゅうみたいなのを見て。 うまくいく方法の大枠を掴んで保険かけつつ就活するのが一番楽だと思うので。
推薦の扱いとかもかなり謎でした。理系だとうまく使わないとかなりつらいのでやっぱり情報は大事ですね。

これを見ているみなさんは早く就活に向けて動いて,余裕で最大手決めまくってくれることを祈っています。祈っちゃダメか。笑