いつもやっているpythonの初期環境設定
いつも忘れるので書く。以下は全てコマンド。
git clone git://github.com/yyuu/pyenv.git ~/.pyenv
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(pyenv init -)"' >> ~/.bashrc
pyenv install --list
pyenvでインストール可能なパッケージがここで出てきたと思うので、anaconda3の中から新しめのやつをインストール
pyenv install anaconda3-4.4.0
pyenv global anaconda3-4.4.0
conda create -n py27 python=2.7 anaconda
pyenv global anaconda3-4.4.0 anaconda3-4.4.0/envs/py27
以上で、python2とpython3の環境が整った。
あとはpipでtensorflowやchainerなどを追加でインストールする。
バージョンの切り替えは
pyenv versions
で環境を確認し、
pyenv global anaconda3-4.4.0/envs/py27
などして切り替えよう
「このディレクトリではpython2を使いたい」
というときは
pyenv local anaconda3-4.4.0/envs/py27
とても便利だ……
仮想通貨ブーム
ちょっと東京に行っていた時(2017/8月)に、仮想通貨についていろいろと聞く機会があった。これはと思いビットコインを5000円分購入(少ない)。それがあれよあれよという間に9000円に増えた。
イケル! そう確信した俺はBTCに30万円追加で投入。これを書いている現在、37万円にまで増やすことが出来た。投資ではこんなこと考えちゃいけないのだが、初めから30万円入れていれば今頃60万円になってたし、買ったり売ったりうろちょろしなければもっと行っていたと考えると凄い。もしあの頃に僕の総資産を全ツッパしてレバレッジをかけていれば家が二軒建ってたゾ
と、そんなような話を後輩にしていたら、三人の後輩が仮想通貨に参入してきた。参入するなら言ってくれれば紹介したのに。と思ったがまあよい。それが今年10月初めのことである。
参入した後輩はT, O, Kの三人。OとKは早々に損失を出した(OはLiskを買い損切り、Kはレバレッジ取引で損切りを繰り返した)。生き残ったのはTのみ。しかしTはレバレッジ取引でガッツリ稼いだ模様で、彼は11月の初め頃、「学生の間に3億円目指します」と嘯いていた。3億というのは、一人の男性の平均生涯賃金と同額である。つまりは働かなくても良くなるまで稼ぐということに違いない。いや、適当に3億と言っただけの可能性もかなりある。3億と言ってみたかっただけかもしれない。
そして11月10日。大変なことが起きた。ビットコインまさかの大暴落。85万だったビットコインはわずか一日の間に72万へと墜落した。そのときロングポジションに居たTは多額の損害を出しながらも損切り。以降、ロング、ショートをうろうろした結果大幅なマイナスとなってしまったのである。そのとき彼は、
「うんちぶりぶり(損失が利益を上回る音)」
という悲しいツイートをしていた。
ということで、今日は僕が使っている取引所を紹介しておく。周りに流されない自信のあるものは挑戦してみると良い。1000円位からでも全然はじめられるので、いいおもちゃにもなるぞ。
その1000円が3億になることを願う。
https://www.binance.com/?ref=15838875
DCGANで人間の顔を生成
chainer-DQNでオリジナルゲームはまた今度です.
いや,プログラム自体はもう出来ているんですが,いろいろと説明が大変なので
説明が大変じゃないこちらを先に更新します.
本題
何番煎じかわかりませんが,DCGANで人間の顔画像を生成してみます.
DCGANを知らない人のためにざっくりとした説明をします.
DCGANは,画像などを生成するように学習するニューラルネットワーク技術の一種です.
作る人(Generator)と,作られた画像か本物の画像かを見分ける人(Discriminator)に分かれて学習していきます.
Generatorは,Discriminatorが見分けられないように,本物っぽい画像を作っていきます.
一般人が理解すべきことはこれだけです.
だれでも簡単にできるので,人間の顔だけでなく,いろいろな画像に対して行ってみてはどうでしょう.
既出のものたちを紹介します.
ポケモンなんかはポケモンのようなポケモンじゃないような変な生き物が出来上がってておもしろいですね
顔イラスト http://qiita.com/mattya/items/e5bfe5e04b9d2f0bbd47
イラスト http://qiita.com/rezoolab/items/5cc96b6d31153e0c86bc
間取り画像 http://nextdeveloper.hatenablog.com/entry/2016/02/26/211332
アイドル顔画像 http://memo.sugyan.com/entry/20160516/1463359395
ポケモン http://bohemia.hatenablog.com/entry/2016/08/13/132314
アルファベット http://qiita.com/hitokun-s/items/38c0bdc3245a45fd8c29
家紋 http://qiita.com/yu4u/items/47053a1f3f20e9561823
さて,僕はPFNのmattyaさんが作成したchainer-DCGANをそのまま使わせていただきました.
qiita.com
用意した画像はとりあえず1000枚.OpenCVのfacedetectを利用して収集しました.
もっと用意してあるのでそれで実験したらまたブログを更新します.
画像収集に協力してくれた後輩のMくん,ありがとう!(これが一番たいへん)
まず面白いところから.はじめに出来た画像を貼ってみます.
怖い!
例えるなら,呪われたトイレに浮かび上がる呪いのシミといった感じです.
次に生成された画像はこちら
もう顔っぽくなってきた.まだちょっと怖いけど,目,鼻,口,髪があり,輪郭はちゃんとしています.
顔の感じはもう完全につかんできていますね
次
なんか色が怖い.プレステゲームの「サイレン」
に出てきそうな感じです.
次は学習がかなり進んだあとの画像
かなり良い! もうほとんど顔やね.
次は学習が進みすぎたっぽい画像
顔画像1000枚という少なさが原因なのか,単純に過学習なのか,(たぶん両方で,後者がより影響大)似たような画像で埋め尽くされています.
しかもここまで学習が進んでも,人間が見れば「生成された画像っぽい」と分かってしまいますね.
しかしまあ,1000枚程度の画像でここまでできるなら上出来といったところでしょうか.
プログラムの解説や,もっとデータ数を増やした実験はまた次の記事に.
DQN-Chainerちょっとだけ中身見た その2
ここから下の内容は僕のメモみたいなものです.最期まで読んでも得られるものは少ないです.
最終目標は報酬をいろいろと弄ることですが,ちょっとハードルが高くて断念しています.
RL_glueやALEのソースコードも載せていますが,ライセンスも確認しましたので大丈夫なはずです.
万が一間違ってたら教えてください.
dqn_classに実装されているメソッドは以下の6つ.
def agent_init(self, taskSpec):
def agent_start(self, observation):
def agent_step(self, reward, observation):
def agent_end(self, reward):
def agent_cleanup(self):
def agent_message(self, inMessage):
それぞれの役割は以下
agent_init……必要なものの定義.self.DQN = DQN_class()で,同一ファイル内のchainerで学習するためのクラスDQN_classを指定している.
agent_start……observation(多分ゲームからもらう情報)を受け取り,4*84*84のstateをself.stateに入れる.
action, Q_now = self.DQN.e_greedy(state_, self.epsilon)で,行動と現在のQ値を取ってくる.
return returnActionで,self.DQN.e_greedy(state_, self.epsilon)で決定したactionを返す
agent_step……agent_startと同じく,4*84*84のstateをself.stateに入れる.
DQNの学習はLearningとEvaluationに分かれていて,Learningのとき,Random行動を入れることがある.
action, Q_now = self.DQN.e_greedy(state_, self.epsilon)で,agent_startと同様に行動と現在のQ値を取ってくる.
self.DQN.stockExperienceとself.DQN.experienceReplayで,状態,行動,報酬,次状態などを保存,保存したものから学習(ネットワークの更新)を行う.ココが核.
Learningならtime+=1する
agent_end……多分,Episodeの最期に実行される.agent_stepから,Episodeの最期では必要のない処理を抜いたものっぽい.
agent_cleanup……ここはパスしている.
agent_message……いろんなメッセージを書く.experiment_ale.pyを実行したターミナルに表示される.
おそらくこれらのメソッドはrl_gllueで環境と実験とRLを繋いで学習を行う上で定義しなければならないものなのでしょう.
あとは,
if __name__ == "__main__": AgentLoader.loadAgent(dqn_agent())
でdqn_agent()を渡してやります.
AgentLoader.loadAgentとはどんなやつなのでしょう?
from rlglue.agent import AgentLoader as AgentLoader
ここを見る限り,rlglueの機能のようです.
使い方はexperiment_ale.pyを見れば分かるでしょうか.ちょっと見てみます.
print "\n\nDQN-ALE Experiment starting up!" RLGlue.RL_init() while learningEpisode < max_learningEpisode: # Evaluate model every 10 episodes if np.mod(whichEpisode, 10) == 0: print "Freeze learning for Evaluation" RLGlue.RL_agent_message("freeze learning") runEpisode(is_learning_episode=False) else: print "DQN is Learning" RLGlue.RL_agent_message("unfreeze learning") runEpisode(is_learning_episode=True)
ここは繰り返し処理の部分です.runEpisodeメソッドが主機能でしょう.
同じくexperiment_ale.pyの
def runEpisode(is_learning_episode): global whichEpisode, learningEpisode RLGlue.RL_episode(0) totalSteps = RLGlue.RL_num_steps() totalReward = RLGlue.RL_return() whichEpisode += 1 if is_learning_episode: learningEpisode += 1 print "Episode " + str(learningEpisode) + "\t " + str(totalSteps) + " steps \t" + str(totalReward) + " total reward\t " else: print "Evaluation ::\t " + str(totalSteps) + " steps \t" + str(totalReward) + " total reward\t "
ん? totalReward = RLGlue.RL_return()ってことは,rewardはどこか別から受け取っているようです.てっきりexperiment_ale.pyで定義しているのだと思っていました.
dqn_agent_nature.pyでもどこかから受け取っています.いったいどこで定義してるのでしょう?
aleから受け取った勝敗数とかが怪しいと思うのですが,とりあえずRL_glueのpython_codecを見に行ってみましょう.
報酬の合計を取ってくるRL_return()を見てみます.
def RL_return(): reward = 0.0 doCallWithNoParams(Network.kRLReturn) doStandardRecv(Network.kRLReturn) reward = network.getDouble() return reward
reward = network.getDouble()だそうです.ではnetwork.getDouble()とは?
26 import rlglue.network.Network as Network ... 63 network = Network.Network()
networkというのはrlglue.network.NetworkのNetwork()というやつらしいです.
network.pyを見に行きます.そこにgetDouble()があるはずです.
84 kDoubleSize = 8 ... 93 self.recvBuffer = StringIO.StringIO('') ... 145 def getDouble(self): 146 s = self.recvBuffer.read(kDoubleSize) 147 return struct.unpack("!d",s)[0]
うーん...つまり,getDouble()ではバッファーから読み取った文字sを数値化して返すということでしょうか?
じゃあバッファーというのは?
一つずつ追いかけていきましょう.
experiment_ale.pyにおいてまずはじめに実行されるRL_init()を見てみます.
101 def RL_init(): 102 forceConnection() 103 doCallWithNoParams(Network.kRLInit) 104 doStandardRecv(Network.kRLInit) 105 #Brian Tanner added 106 taskSpecResponse = network.getString() 107 return taskSpecResponse
104 doStandardRecv(Network.kRLInit)に注目します.
71 def doStandardRecv(state): 72 network.clearRecvBuffer() 73 recvSize = network.recv(8) - 8 74 75 glueState = network.getInt() 76 dataSize = network.getInt() 77 remaining = dataSize - recvSize 78 79 if remaining < 0: 80 remaining = 0 81 82 remainingReceived = network.recv(remaining) 83 84 # Already read the header, so discard it 85 network.getInt() 86 network.getInt() 87 88 if (glueState != state): 89 sys.stderr.write("Not synched with server. glueState = " + str(glueState) + " but s hould be " + str(state) + "\n") 90 sys.exit(1)
82 remainingReceived = network.recv(remaining)に注目します.network.recv()を見てみます.
119 def recv(self,size): 120 s = '' 121 while len(s) < size: 122 s += self.sock.recv(size - len(s)) 123 self.recvBuffer.write(s) 124 self.recvBuffer.seek(0) 125 return len(s)
122 s += self.sock.recv(size - len(s))に注目します.sockという名前的に,ソケットでしょうか.
僕はBSDソケットのことをよく知らないのですが,やはりaleから受け取っていると見てよさそうです.
28 import socket
とありました.ググります.
17.2. socket — 低レベルネットワークインターフェース — Python 2.7.x ドキュメント
このモジュールは、PythonでBSD ソケット(socket) インターフェースを利用するために使用します。最近のUnixシステム、Windows, Max OS X, BeOS, OS/2など、多くのプラットフォームで利用可能です。
やっぱりBSDソケットでした.RL_glueを動かした時の動きが見たこと無いなあと思ってたらこういうことだったんですね.勉強になりました.
64 network.connect(host,port)
もう一度experiment_ale.pyにおいてまずはじめに実行されるRL_init()を見てみます.
101 def RL_init(): 102 forceConnection() 103 doCallWithNoParams(Network.kRLInit) 104 doStandardRecv(Network.kRLInit) 105 #Brian Tanner added 106 taskSpecResponse = network.getString() 107 return taskSpecResponse
ここのforceConnection()でBSDソケット通信の初期化をしてると思います.
37 def forceConnection(): 38 global network 39 if network == None: 40 41 theSVNVersion=get_svn_codec_version() 42 theCodecVersion=get_codec_version() 43 44 host = Network.kLocalHost 45 port = Network.kDefaultPort 46 47 hostString = os.getenv("RLGLUE_HOST") 48 portString = os.getenv("RLGLUE_PORT") 49 50 if (hostString != None): 51 host = hostString 52 53 try: 54 port = int(portString) 55 except TypeError: 56 port = Network.kDefaultPort 57 58 print "RL-Glue Python Experiment Codec Version: "+theCodecVersion+" (Build "+theSVN Version+")" 59 print "\tConnecting to " + host + " on port " + str(port) + "..." 60 sys.stdout.flush() 61 62 63 network = Network.Network() 64 network.connect(host,port) 65 network.clearSendBuffer() 66 network.putInt(Network.kExperimentConnection) 67 network.putInt(0) 68 network.send()
64 network.connect(host,port)
ここだなあ
101 def connect(self, host=kLocalHost, port=kDefaultPort, retryTimeout=kRetryTimeout): 102 while self.sock == None: 103 try: 104 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 105 self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 106 self.sock.connect((host, port)) 107 except socket.error, msg: 108 self.sock = None 109 time.sleep(retryTimeout) 110 else: 111 break
ここでconnectの初期化を行ったみたいです.
いまだrewardへはたどり着けていません.おもったより長い道のりです.
ともあれ,network.recv()の
119 def recv(self,size): 120 s = '' 121 while len(s) < size: 122 s += self.sock.recv(size - len(s)) 123 self.recvBuffer.write(s) 124 self.recvBuffer.seek(0) 125 return len(s)
では,ソケットから受け取った文字列をwriteしていることがわかります.size-len(s)はなんなんでしょう?
sizeはどこかから受け取っていますが,len(s)はイミフです.上でs=''とやっているのだから,len(s)は必ず0になるんじゃないんでしょうか?
まあそれは置いといて,sock.recv(size - len(s))がrewardにつながるのかな? sizeの値によりそうですが.
recvを呼んでいるのが,RLglue.pyの
82 remainingReceived = network.recv(remaining)
でした.
remainingとは?
同じくRLglue.pyのdoStandardRecv(state):から
73 recvSize = network.recv(8) - 8 74 75 glueState = network.getInt() 76 dataSize = network.getInt() 77 remaining = dataSize - recvSize
remainingはdataSize - recvSizeです.
recvSizeはnetwork.recv(8) - 8です.
つまり,len(s)から8を引くことになります.
len(s)はself.sock.recv(8)を文字列化したもの文字数です.
sock.recv()とnetwork.recv()は似てますが全く別物です.
socketのrecvメソッドはこちら
len(s)-8というのはつまり,文字数から8を引くということです.だめだ.わけわかんなくなってきた.
dataSizeはnetwork.getInt()で取ってきます.
その直前のglueStateもnetwork.getInt()で取ってきています.同じものでしょうか? 見てみます.
83 kIntSize = 4 ... 141 def getInt(self): 142 s = self.recvBuffer.read(kIntSize) 143 return struct.unpack("!i",s)[0]
dataSizeは4のようです.
要するにremainingはsocket(8)-8-4でしょうか.
ギブアップです.
報酬の付近をいろいろと弄ってみたかった(たとえば勝ち負けではなくラリーが続くように学習するなど)のですが,ちょっと調べることが多くなりそうです.知ってる方がいたらぜひコメントください.お願いします.
まあぶっちゃけゲームをプレイさせるのはそんなに重要な事ではないので,今回は潔く諦めることにしましょう.
DQN-Chainerを動かした&ちょっとだけ中身見た
ラボのサーバーでDQN-Chainerを動かしました.
CUIだと動かないかもと思ったけど動いてます.
途中失敗したっぽかったけどなんとか動いてる.
DQN-chainerの記事はこちら.
基本参考にしたインストール手順
hirotaka-hachiya.hatenablog.com
後輩が「CUIだと動かないっぽいっすよ」と言っていたので恐る恐るやってたらちゃんと動きました.動いてる様子はこちら.
きもいけどなんとか動いてる.
全然学習できてない段階で動かしてるので右側のDQNちゃんが弱すぎですね.
lab_server@user:~/RL/Arcade-Learning-Environment-0.5.1$ make -j 4 Linking C executable doc/examples/RLGlueAgent Linking C executable doc/examples/RLGlueExperiment /usr/bin/ld: cannot find -lrlexperiment collect2: error: ld returned 1 exit status /usr/bin/ld: cannot find -lrlagent collect2: error: ld returned 1 exit status make[2]: *** [doc/examples/RLGlueExperiment] Error 1 make[1]: *** [CMakeFiles/RLGlueExperiment.dir/all] Error 2 make[1]: *** Waiting for unfinished jobs.... make[2]: *** [doc/examples/RLGlueAgent] Error 1 make[1]: *** [CMakeFiles/RLGlueAgent.dir/all] Error 2 [ 33%] Built target ale-bin [ 66%] Built target ale-c-lib make: *** [all] Error 2
こんなエラーが出て,ALEのインストールの66%の段階で失敗したからもうダメかと思ったけど,ディレクトリにaleっていう実行ファイルがあったからダメ元で動かしたら動きました.
僕の環境だとだいたい15分でEpisode20まで動きました.先駆者によると,Episode700くらいでHandMadeAgentより強くなります.現実的な範囲ですね.
先駆者の動画はこちら
www.youtube.com
簡単に中身を見たのでforwardのあたりだけ解説します.間違ってたらコメントくれるとありがたいです.
まず,DQN-Chainer/dqn_agent_nature.pyから.
ここがネットワーク部分
self.model = FunctionSet( l1=F.Convolution2D(4, 32, ksize=8, stride=4, nobias=False, wscale=np.sqrt(2)), l2=F.Convolution2D(32, 64, ksize=4, stride=2, nobias=False, wscale=np.sqrt(2)), l3=F.Convolution2D(64, 64, ksize=3, stride=1, nobias=False, wscale=np.sqrt(2)), l4=F.Linear(3136, 512, wscale=np.sqrt(2)), q_value=F.Linear(512, self.num_of_actions, initialW=np.zeros((self.num_of_actions, 512), dtype=np.float32))
覚え書きとしてl4の入力が何故3136になるのか書いておきます.
はじめ,AtariのPongというゲームは210*160の画像サイズで入力されますが,諸事情によりそれを84*84にします.
84*84の画像は,l1の畳み込み層によって20*20の画像になります.しかしそんなことはどこにも書いてありません.自分で計算します.
出力画像の幅は,こういう計算式で導き出されます.
ネットワーク構造的には3つの畳み込み層の後,全結合層があり,最後にQ値を計算する全結合層が一つあるという感じです.
DQNは,強化学習であるところのQ学習におけるQ値の更新を,
本来なら価値関数近似を用いて行う部分にニューラルネットワークを用いていると,
なにやらかっこいいことを言っていますが,要するにQ値をCNNで決めてるってだけの話ですね.構造はとっても単純です.
l1のstrideが4なのはおそらくプレイヤーの横幅かボールのどちらかが4ピクセルだからじゃないでしょうか(適当).
strideの設定とか,ksizeの設定とかはなぜこうなっているのかわからないです.理論にもとづいているのか,いろいろ試した結果なのか.
ここがOptimizerの設定です.
print "Initizlizing Optimizer" self.optimizer = optimizers.RMSpropGraves(lr=0.00025, alpha=0.95, momentum=0.95, eps=0.0001) self.optimizer.setup(self.model.collect_parameters())
RMSpropGravesを使っています.CNNの学習で,最高の性能を示すと言われているやつです.
lr, alpha, momentum, epsの値は,このPongというタスク(ゲーム)に最適化された値だと見ていいと思います.
ここが,実際にネットワークを使って学習する部分です.
def forward(self, state, action, Reward, state_dash, episode_end): num_of_batch = state.shape[0] s = Variable(state) s_dash = Variable(state_dash) Q = self.Q_func(s) # Get Q-value
ちょっと古いchainerのバージョンで開発されたっぽい書き方ですが,新しめのchainerでも動きます.(僕の環境だと1.7.0)
state(ゲームの状態)を受け取り,chainerで扱えるようにVariable型にキャストします.
state_dashは次の状態で,これも誰かから受け取っています.
Q_fanc(s)でQ値を計算します.
以下,Q_fancメソッド.
def Q_func(self, state): h1 = F.relu(self.model.l1(state / 255.0)) # scale inputs in [0.0 1.0] h2 = F.relu(self.model.l2(h1)) h3 = F.relu(self.model.l3(h2)) h4 = F.relu(self.model.l4(h3)) Q = self.model.q_value(h4) return Q
もちろんstateは画像ですので,配列内のすべての値を255で割ってます.
forwardに戻ります.
# Generate Target Signals tmp = self.Q_func_target(s_dash) # Q(s',*) tmp = list(map(np.max, tmp.data.get())) # max_a Q(s',a) max_Q_dash = np.asanyarray(tmp, dtype=np.float32) target = np.asanyarray(Q.data.get(), dtype=np.float32)
ここで次状態のQ値の最大値(つまりは期待値)を取り出してますね.
このtargetというのを教師データにするようです.状態をいい方向へ持っていくという目的に沿っていると考えられます.
対戦ゲームを極めた人ならわかると思いますが,プレイヤーは有利状況を作り出し,不利状況をなるべく避けるものです.
ちなみにQ_func_targetはこの部分
def Q_func_target(self, state): h1 = F.relu(self.model_target.l1(state / 255.0)) # scale inputs in [0.0 1.0] h2 = F.relu(self.model_target.l2(h1)) h3 = F.relu(self.model_target.l3(h2)) h4 = F.relu(self.model_target.l4(h3)) Q = self.model_target.q_value(h4) return Q
です.Q_fancと同じです.
model_target.l1~model_target.q_valueは,
def target_model_update(self): self.model_target = copy.deepcopy(self.model)
と,self.modelをまるごとコピーしたもののようです.
for i in xrange(num_of_batch): if not episode_end[i][0]: tmp_ = np.sign(Reward[i]) + self.gamma * max_Q_dash[i] else: tmp_ = np.sign(Reward[i]) action_index = self.action_to_index(action[i]) target[i, action_index] = tmp_
ここで行動とそれに対する報酬(または報酬の期待値)をtargetに入れてるようです.
np.sign(x)は御存知の通り,x<0のとき-1, x=0のとき0, x>0のとき1を返します.
報酬を0~1までにクリッピングしているようです.
それだと報酬の重み付けができないじゃないか! とも思うんですが,これがデフォのようですね.
こっちのほうが学習が進みやすいそうなので,まあ良しとしましょう.
DeepLearningで学習が全然進まないのは悪夢なので,とりあえずこっち優先というのも頷けます.
# TD-error clipping td = Variable(cuda.to_gpu(target)) - Q # TD error td_tmp = td.data + 1000.0 * (abs(td.data) <= 1) # Avoid zero division td_clip = td * (abs(td.data) <= 1) + td/abs(td_tmp) * (abs(td.data) > 1) zero_val = Variable(cuda.to_gpu(np.zeros((self.replay_size, self.num_of_actions), dtype=np.float32))) loss = F.mean_squared_error(td_clip, zero_val) return loss, Q
ここが1番むずいところかもしれません.
ここにTD誤差について詳しく書いてあります.
qiita.com
ぶっちゃけよくわかりません.まあここは改造することもなさそうなので一旦無視します.
2016/09/26 こっち読むと分かりやすかったです
TD誤差学習
forwardだけで結構長くなってしまいましたね.
ここからオリジナルの何かを作るときは,RL_glueの使い方をしっかり勉強する必要がありそうです.
次回はdqn_agentクラスを見つつ,RL_glueの使い方を書いていくことにしましょう.
はじめました
機械学習、主にDeep Learning関連について書いていきます。
たまに小咄や、日常の出来事なども書いていく予定です。
これだけだとさすがに味気ないので、さっそく小咄をひとつ。
「コリーちゃんがいる」
まずはじめに言っておくのが、これからお話することはすべて本当にあったことであるということです。僕の人生において、一番恐怖を感じた体験と聞かれれば間違いなくこれであると断言します。
中学生の職場体験というのは今もあるのでしょうか? 僕の中学では、3日ほど地元の職場で働くというイベントがありました。
そのイベントは、ある程度希望したところに行けるというシステムで、僕は子供が好きだったので幼稚園を希望しました。
子供というのは凄い人達で、僕がどう振る舞おうか考えているうちにあっという間に仲良くなってしまいました。
驚いたことは他にもあります。給食の量の少なさと、子どもたちの食べる遅さにも相当びっくりしました。おにぎり一個に20分かけて食べる感じです。僕も給食を貰ったんですが、あんまり暇だったので給食を二人分食べてしまいました。
問題はここからです。園庭で遊ぶ時間になった時、僕は鬼ごっこで最初の鬼になりました。追いかけると子供が蜘蛛の子を散らしたように逃げます。地形を良く知ったすばしっこい子どもたちを捕まえるのは至難の業で、一人も捕まえられずに5分ほど経ちました。
砂場の真ん中で、僕に背を向けてピクリとも動かない園児がいたのです。
これはチャンスだと思い、そっと忍び寄ってその子にタッチしました。
驚く子供の顔が見たかったのですが、反応がありません。よく見ると、両手で目を塞いでいました。泣いているというわけでもなく、ただ目を塞いでいたのです。
「どうしたの?」
そう優しく聞くと、
「コリーちゃんがいるよ」
というのです。
「コリーちゃんって何?」
「コリーちゃんだよ」
意味不明です。
ダメだこりゃ。他の子をタッチしよう。そう思い、周囲を見渡すと、園庭にいた園児が全員目を塞いでいたのです。
異常な光景でした。あれだけ騒がしかった園庭がウソのように静まり返り、示し合わせたかのように皆、一つの方向を向いて目を塞いでいるというのは。
あまりの驚きにその場を動けなかった僕なのでしたが、先生が、
「コリーちゃんはもう行ったよ!」
と大声で言いました。
すると、「コリーちゃん行っちゃった?」「ほんとだ、コリーちゃんもういないよ」とパラパラと聞こえ、また元のように騒がしい園庭に戻りました。
「なんだかよくわからないんだけど、一日に一回か二回あるんだよ。『コリーちゃんはもう行った』と誰かが言わないと、ずっとあのままなんだ」と、先生。
妙な遊びがあるもんだと感心して、その時はそれで終わりました。
翌日。同じことが起きました。
「コリーちゃんが来たよ」
「コリーちゃん、今日は二人だよ」
「違うよ、三人いるよ」
「いっぱいいるよ」
今日はそういう遊びか。僕も乗ってみることにしました。
「本当だ、いっぱいいるね」
「お兄ちゃん、大人なのにわかるの?」
目を塞いだまま子供が聞いてきました。
「もちろん。ばっち見えてるよ」
そう言った瞬間、園庭が一気にざわつきました。
「お兄ちゃんがコリーちゃん見ちゃった」
口々にそう言っていました。
なにかまずいことをしたらしい。そう感じ取った僕は
「うそうそ、見てないよ」
ととっさにごまかしたのですが、もう駄目でした。
「コリーちゃんはもう行ったよ」
子供の誰かが言いました。そして目を塞いでいた手をどけた子どもたちは一斉に僕を見てきたのです。ただ見るだけではなく、残念なものを見るような目で。僕はありえないほどの恐怖を感じました。
そして、子どもたちは僕に一切かまってくれなくなったのです。
あれだけ人気者だった僕は、一瞬で孤立しました。
もちろん先生に報告しました。どうも子どもたちの例の遊びのルールを破って、嫌われてしまったみたいです、と。
すると先生は、「大丈夫。僕も経験あるけど、明日には元通りだよ」と。
じゃあ、今日は我慢するしか無いのか。僕は園庭を離れ、教室の端っこで座りました。
「僕、コリーちゃん信じてないよ」
そう言ったのは、いつの間にか隣にいた男の子でした。
「ねえ、コリーちゃんって何なの?」
「わかんない。けど、見ちゃだめなんだって」
「見たらどうなるの?」
「死ぬ」
その子の目は真剣でした。たかが子供の言うこと。なのに僕はどうしようもないくらいに恐怖を感じました。本当に死ぬかもしれない。そう思ってしまうほどに。
僕が何も言えないでいると、男の子は飽きちゃったのか、どこかへ行ってしまいました。
コリーちゃんという謎の存在。見たら死ぬ。そんな強烈な設定を、誰が考えたのだろう。いつからあるものなんだろう。いろんな疑問が頭に浮かびました。気持ちが落ち着いた頃に、僕は先生に聞きに行くことにしました。
「先生、コリーちゃんって、いつからあるんですか?」
「うーん、僕が来た時にはすでにあったなあ。もう6年も前の話だけど」
そんなに昔からあるなんて。こうなると、設定を考えた子もすでに卒園してしまっています。
「気にしないほうが良いよ。慣れれば大したことないし」
そうですね。と、振り返って園庭に向かおうとした時、
「あんまり詮索しないほうが良い。コリーちゃんは探られるのが嫌いだから」
先生が言いました。え、と思って先生を見ると、普通の顔をしています。冗談を言ったようではありません。むしろ、不自然なくらいに無表情でした。
「コリーちゃんはもう行ったよ」
園庭の子供の誰かの声が聞こえました。
今まさに、コリーちゃんは来ていた。
「そういうことだからね」
先生はにこりと僕に笑いかけて来ました。
さっきの無表情が無かったかのように。
僕が帰る頃には、強めの雨が降っていました。
職場体験というのは普段体験できないことを経験し、学ぶ場です。しかし、これほどまでに奇妙な体験をしているのは僕だけだろうと思いました。
そもそも幼稚園を選んだのは、子供が好きだからという理由を最初に挙げましたが、実はもう一つ理由がありました。
雨の降る日に、公園で泣いていた僕に、若い女の人が声をかけてくれたことがありました。ちょうど、職場体験からの帰り道に降っていた雨のような日です。僕はそのときの優しさに憧れていたのです。だから、子供には優しくというのが僕のポリシーであり、優しくすることで喜びを感じるようになったのです。
僕は、そのお姉さんに声をかけてもらったときのことを思い出していました。
そして、ひとつ疑問に思いました。あの日、何故僕は泣いていたのか。
それを考えた時、答えは案外とすぐに出ました。
『コリーちゃんがいるよ!』
その時に思い浮かんだ映像です。僕はコリーちゃんが怖かった。だから泣いていたんです。
コリーちゃんは僕が幼稚園児のころからありました。それをずっと忘れていたんです。そして、何を隠そう、コリーちゃんを考えついたのは僕なのです。ただし、そのときとは設定が大分ちがいました。
コリーちゃんは外国人の女の子です。とてもきれいな髪をした小さな女の子で、名前は僕が適当につけたものです。近所のスーパーマーケットに行った時に見かけた女の子に勝手に名前をつけて、「コリーちゃんを見た」と僕が言ったんです。
金色の髪の女の子なんて居るはずがないと、そんなのは嘘だと決めつけられました。すごく悔しくなりました。悔し紛れに、「コリーちゃんがいるよ!」と大声で叫んだんです。とっさについた嘘に、「見たら死ぬ」という設定を付け加えました。
あのときのコリーちゃんが、まだ残っているなんて驚きです。そういえば、子供というのはどこから教わったのか、鬼ごっこ、氷鬼、ドロケイなどの遊びを知っています。それというのは実は、世代から世代へと受け継がれていくのでしょう。
それにしても、設定を考えた自分が、「コリーちゃんを一度見たから死んじゃう」なんて思い込みをしていたなんておかしな話ではあります。しかし幼稚園児なら、自分で吐いた嘘を本当だと思い込むということも珍しくありません。
家につく頃には、僕はもう全く怖さを感じなくなっていました。
翌日、職場体験の最終日。
子どもたちも初日のように仲良く振る舞ってくれたし、またコリーちゃんは来たけど、もう僕は何もしなかったので何も問題は起きませんでした。謎が解けた今、わざわざ遊びを乱すようなことはするべきではないと思ったからです。
そして何事も無くその日は終わり、とうとう子供たちともお別れのときがやってきました。
泣いている子供も居ました。つられて僕も泣きそうになりました。
幼稚園の玄関から出る時、僕は先生に、コリーちゃんの正体を教えました。「コリーちゃんは実は、僕が作った遊びだったんです。昨日、ようやく思い出しました」
すると先生は、「へえ、そうなのか」と、案外そっけなく返事をしましたが、何かを考え込んでいる様子でした。それはあまり気に留めず、僕は園児の皆に見送られながら学校に向けて歩き始めました。
さよーならーー。と大きい声が聞こえます。僕も振り返って手を振り返しました。すると、泣きそうな顔で一人の女の子が走り寄って来ました。
「お兄ちゃん、死なないで」
言うやいなや、その子は泣き出しました。これは困ったぞと思い、僕はその子に真実を教えることにしました。
「お兄ちゃんは死なないよ。だって、コリーちゃんなんて本当は居ないんだ。コリーちゃんっていうのは僕がついた嘘なんだよ」
そう言うと、その子は
「違うよ。だって、私がコリーちゃんだもん」
はっとしてその子の顔を見ると、不気味な笑みを浮かべて充血した目を僕に向けたまま、右手に持っていた黒い何かを振りかざして僕の顔面を
というところで目が覚めました。
今までの話はすべて、本当に僕が見た夢の話です。
おしまい。