Pythonプログラマなのに、7年間機械学習が出来ないマンだったけど、Elixir歴６カ月で、AIと対戦できる４目並べをゼロから作れた話

# はじめに

Tensorflowのベータ版がリリースされて、7年。いまでは、機械学習を学ぶ学習コンテンツも豊富にあります。
Pythonでのプログラミングはしますが、機械学習については、学習コンテンツをちょっとやってみた程度でした。興味はあったんですが。

こんな私が、プログラミング言語、Elixirを学んだ事をきっかけにして、自分で、AIと対戦できる４目並べを作る事ができました。

https://twitter.com/GeekMasahiro/status/1612380843984719872

この経験を書いてみます。

「いいね」もらえると、励みになります。よかったら、いいねお願いします。


# Elixirとの出会い

2022年7月、Elixirをネットニュースで知りました。
[最も愛されてるWebフレームワークのトップPhoenix](https://survey.stackoverflow.co/2022/#most-loved-dreaded-and-wanted-webframe-love-dread)ってなに？Elixirって言語知らないんだけど。
これが始まりでした。
Elixirの[getting started](https://elixir-lang.org/getting-started/introduction.html)を読み進めて、言語として楽しい！と感じました（なにが楽しいのか、また別で書こうとおもうので、ここでは割愛します）。
日本語の[入門](https://elixirschool.com/ja/lessons/basics)もあります。

全体像がわかったので、次は、何か自分で作ってみよう。プログラミング言語を習得するには、何か作ってみるのが一番。
そこで、[Elixirで動くマインスイーパー](https://github.com/masahiro-999/phx-minesweeper)を作ってみました。このマインスイーパー、イベントハンドラーもElixirで書いてあって、Javascriptの記述なしなんです（PhoenixのLiveviewの機能）。

![minesweeper](https://user-images.githubusercontent.com/67159022/183947477-fffc4379-22d0-4848-8080-c01f7a838a21.png)

# ゼロから作るDeep Learing４

このころ、「ゼロから作るDeep Learning 4: 強化学習編」（以下、ゼロ作と記載します）という本に出合いました。強化学習は、詳しい事は知らなかったので、読んでみる事にしました。

https://www.oreilly.co.jp/books/9784873119755/

ゼロ作は、数式も出てきますが、基本的なことから書いてあり、繰り返し説明されていて、理解する事ができました。私にとっては、ちょうど良い内容でした。
Q学習の意味も知らなかったので、自分にはちょうどよい内容でした。

# ゼロから作るDeep Learing、Elixirで作ったら面白いんじゃない？

マインスイーパーもできて、次に何を作ろうか？
ゼロ作では簡単なプログラムから説明されていて、ちょうど良い課題です。
進めていけば最終的には強化学習のプログラムのElixir版ができる。
Elixir学習中の私には、一石二鳥の課題となりました。

Elixir版を作ると決めたことで、ゼロ作の[Pythonのコード](https://github.com/oreilly-japan/deep-learning-from-scratch-4)が、強化学習のアルゴリズムを正確に説明してくれる説明文に見えてきました。
実際、8章で大ハマりした時はPythonの実行結果といろいろ見比べる事ができてとても役立ちました。

あと、気分的な問題かもしれませんが、書籍にPythonのコードはあるので、Pythonで作ってても書き写してる感があります。Elixirでだったら、自分で作ってる感が持てます。このおかげで継続できました。

# Elixirの学習（１章～６章 ）

１～３章は、プログラミングコードは少しでてきますが、作ってみるまでもない感じ。４章以降でも類似したものがあるので作成は省略しました。読書のみ。

４章のGridWorldの問題を実際に説いてみるところからElixirで作ってみる事にしました。
説明文と数式を読んで、疲れてきたから、プログラム作ってみようという感じで、プログラムの作成が気分転換にもなりました。

pythonのdefaultdictが使われています。これを同実装するか？
Elixirにはclassがありません。クラスで書かれてるコードはElixirの場合どう書けばいいんだろう？
for/whileのようなloop文はどう書けばいいんだろう？

Classの取り扱いについては、別の記事に書きました。

https://qiita.com/GeekMasahiro/items/e1354b9920760c49e679

強化学習の問題というよりElixir初心者の問題が大きかったです。

比較的順調に進めたのは、テストコードを書きながら、確認して作ったからだと思います。
Elixirではテストのフレームワークが組み込まれていています。次のような感じです。

```Elixir
  test "next_state" do
    env = %{
      width: 4,
      height: 3,
      goal_state: {0, 3},
      wall_state: {1, 1},
      start_state: {2, 0},
      agent_state: {2, 0}
    }

    assert Gridworld.next_state(env, {0, 0}, :right) == {0, 1}
  end
```
このテストでは、Gridworldで、{0, 0}から右に移動したら、{0, 1}になることを確認しています。
テストでは動作を確認する事が目的ですが、文法のエラーなどもモジュール、関数を作成する都度確認する事ができました。

テストがパスできれば、最終的に結合した結果も結果もほぼ、すんなり書籍と同じ結果になりました。

やっぱり、実行してみて、書面と同じ様な結果が出た時には、嬉しくて、テンションも上がります。


# Elixirの本領発揮（７章）　

７章では、ニューラルネットワークが登場しました。

ゼロ作では、行列計算はnumpy、ニューラルネットの実装は、DeZeroが使われています。
Elixirは、実は、Nx/Axonなど機械学習のコードで必須になるライブラリーが揃っているという、好都合な言語だったんです。

||Python|Elixir|
|-------|------|------|
|行列計算|numpy|Nx|
|ニューラルネット|PyTorch<br>TensorFlow<br>DeZero|Axon|

[Nxのリリースが2021年2月](https://qiita.com/piacerex/items/db33fbe80751fdd30e48)。最近急激に開発が進んでいるホットな言語です。

ニューラルネットの学習の前に、一次方程式にフィッティングができるか試して見るところからやってみました。
このシンプルな学習ができて、Axonの基本的な使い方がわかりました。

https://qiita.com/GeekMasahiro/items/a527eb12304ed75cc71f

ゼロから作るDeep Learingは初編がニューラルネット自体の作成です。Qiitaにも、初編をElixirで作るの記事があります。ニューラルネットの部分の理解を深めたい場合は、参考になりるとおもいます。

https://qiita.com/the_haigo/items/1a2f0b371a3644960251
https://qiita.com/Yoosuke/items/8599ed0a976de1371ead


# Elixirのコミュニティに参加

Elixirを勉強し始めたときから、Twitterでは、できるようになったことをつぶやいていました。
Elixirのコミュニティに参加したり、slackのチャネルに参加してみました。

丁度、[Livebook](https://livebook.dev/)(Pythonのjupyterのようなもの）で機械学習に関するLTの機会があったので、試していた事をLTで話する事ができました。

https://qiita.com/GeekMasahiro/items/d956a7a880a937370a56

このような発表の機会も、自分のわかってることを整理する機会にもなって理解が深まりました。
また、他の方の記事なども役に立ちました。

余談ですが、Elixirの日本語の情報は少ないんですが、なぜかQiitaで検索するとよくみつかるんです。

なぜかは、12月にわかりました。
Advent Calenderのランキングがプログラミング言語でトップ。数年前からの蓄積で、AxonやNxの記事もあって参考になりました。国内のコミュニティが活発ですね。私も、Axonの件など、いくつか記事を書いてみました。

https://qiita.com/advent-calendar/2022/ranking/feedbacks/categories/programming_languages

# Open AI GymをElixirから使うのに一苦労（８章）

7章をクリアして、いよいよ、Deep Learingを使ったQ学習、DQN（Deep Q Network）の実装です。

7章まではGridworldという単純なゲームでしたが、8章では、Cart Poleと呼ばれる、台車の上に立ててある棒を倒れないように台車を動かすゲームです。
強化学習を行うには環境（台車を動かしてみる環境）がないと実行ができません。
Cart Poleは、Pythonのコードで書かれていて、なんとかこれを動かす必要があります。

Pythonほど環境が整っていないというのが、Elixirで機械学習を行う場合の課題かと思いました。

しかし、なんと、GymをElixirから使える[gyx](https://github.com/doctorcorral/gyx)がありました。同じことをしている人が、世界にはいるんだ。心強くなりました。

ただこのライブラリー、開発が止まっていて、現行のgymと互換性がなく動作しませんでした。
Pythonとのインターフェース部分を、ちょちょっと直して、Cart Poleで動くようにできました。
ElixirでOpenAI Gym使いたい方、使ってください

https://github.com/masahiro-999/gyx

詳しくは別記事にしようとおもいます。

# DQNの実装で大ハマり

gyxもできて、後は、DQNの実装です。Elixirのプログラムにも慣れてきたので、Pythonのコードを見ながら、実装できました。
ところが、書籍にある実行結果のように安定した学習結果が得られません。全く学習できないわけでもない不安定な感じでした。

DQNのプログラムの動作は初体験なので、どうやって調べていいやらわかりません。
Python版のプログラムを動かして見て比較するなど、様々な事をやってみました。
一か月くらい停滞することになりました。

結局、動かなかった原因は、tensorの次元が間違っている箇所でした。

```diff_elixir
   end
 
   def update(state, action, reword, next_state, done, agent) do
-    ReplayBuffer.add({state, [action], [reword], next_state, done},agent.replay_buffer)
+    ReplayBuffer.add({state, [action], [reword], next_state, [done]},agent.replay_buffer)
 
     cond do
       ReplayBuffer.get_len(agent.replay_buffer) < agent.batch_size -> nil
```

分ってしまえば、これだけの問題でした。

ゼロ作の説明や、式を何度も見直したので、おかげで、用語が身に付きました。

これで、DQNの実装が完成しました。私にとっては、大きな一歩になりました。

# 4目並べに挑戦

強化学習は、状態や、行動、報酬を何にするかを変えれば学習する対象を変えられます。同じ強化学習のプログラムで、５目並べとかオセロもできそうです。
ゼロ作の９，10章が残っているんですが、脱線して、手始めに、3目並べの強化学習してみました。
意外とあっさり学習する事ができました。
次に、５ｘ５マスの4目並べをやってみました。層構成を調整することで強化学習できました。

|対象|状態|行動|報酬|学習完了までのエピソード数|
|---------|----------------------------------|----------------------|----|----|
|Cart Pole|台車の位置、速度、ポールの角度、角速度|右に移動、左に移動の選択|傾き、位置が基準内の時1point|300|
|3目並べ|碁石の配置|石置く座標|自分が3つ並べたら1point、敵が3つ並べたら-1point|10000|
|4目並べ|碁石の配置|石置く座標|自分が4つ並べたら1point、敵が4つ並べたら-1point|200000|

# 対戦ゲームに仕上げる

マインスイーパーを作っていたので、４目並べをブラウザで動作させるのは簡単でした。

PhoenixのLiveviewを使った、イベントハンドラーは、こんな感じです。
碁石を置いた時の処理で、x, yにマスの座標がはいっています

```Elixir
  def handle_event("clicked", %{"x" => x, "y" => y}, socket) do
    x = String.to_integer(x)
    y = String.to_integer(y)
    socket = socket
     |> turn_human({y, x})
     |> turn_ai()
    {:noreply, socket}
  end
```

turn_human({y, x})は、人間側の処理で、クリックした座標に石を置きます。
turn_ai()がAIの手番の処理です。Nx/Axonで書いたQ関数の値を調べて、その場所に石を置きます。
Elixirで書けるので、AI側の処理も、直接書けるんです。

Frontendをvue.jsやreact.jsで作った場合は、サーバを作ってAPIを呼び出してみたな処理が必要で、こんなに簡単にはいかなかったと思います。

# Elixirの強み

2022年12月に、Elixirのコミュニティで話題になったのが[bumblebee](https://news.livebook.dev/announcing-bumblebee-gpt2-stable-diffusion-and-more-in-elixir-3Op73O)です。
stable-diffusion等の学習済みのモデルをElixirから簡単に利用できようにするものです。説明動画でもPhoenixを使って、Webアプリに簡単に組み込む様子が説明されています。
機械学習とWebプログラム両方ができるElixirの強みを生かしたものだと思いました。

今までは、機械学習といえば、Pythonの一択でしたが、bumblebeeや、4目並べの場合のように、WebUIに組み込むような部分では、Elixirも選択肢に入ってくるのではないかと思いました。

# まとめ

- Elixirに出会ってなかったら、ゼロ作は読書どまりだった。
- ゼロ作を参考にしながら、DQNの強化学習のプログラムをElixirでゼロから作る経験ができた。
- ElixirからOpenAI Gym使えるようにした([gyx](https://github.com/masahiro-999/gyx))。
- 大ハマりはしがた、デバックの体験を通して理解が深まった。
- 4目並べの強化学習に成功し、SPAのWebアプリに組み込めた。Elixirの強みを実感できた。

Elixirを学んで、機械学習に挑戦という、遠回りをしてみました。私にとっては、いい経験になりました。
ゼロから作ってみようと思う方、Elixirの学習から初めてみてはいかがでしょうか。
OpenAI Gymを呼び出す部分等、強化学習の本題でもない部分は、私が修正したものを使ってもらえば、強化学習部分に集中して学習できると思います。Elixirのプログラムの詳細は、ネタバレにならないように、伏せておきます。

質問、ご意見などあればコメントください。意見交換も歓迎です。