やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

【レポート】第11回清流elixir勉強会を開催しました【Taskを使った並行処理に入門】

トピック

今回で第11回目の勉強会を開催致しました
elixir-sr.connpass.com

隔週にて勉強会を開催していたのですが、先月は仕事の都合で日程が合わずで3週間時間が空いてしまった
こういうのは一回サボる癖が付いてしまうと徐々に習慣化してしまうので気をつける様にしなければと反省
仕事って大変だ

最近は毎回参加して頂ける方、新規の参加者の方も増え、8人用のレンタルオフィスが手狭に感じるようになった
主催者側としてはより快適な場所を提供したいという気持ちもあるがこのローカル、アングラ感が気に入っていたりもする

さて今回はついにElixirの強力な並行処理について入門していくことになった

清流elixir-infomation
開催場所: 丸の内(愛知)
参加人数: 5 -> 6 update!
コミュニティ参加人数 : 13 -> 15 update!
20190908現在

第11回の勉強会の内容について

Elixirでの並行処理について話を始める前に、まず並行処理というものが何なのか、よく耳にする並列処理と何が違うのかという部分からスタートした

ざっくりと並行処理と並列処理について

技術が進化し、現代のCPUはマルチコアであることが一般的だ
ここでいうコアとはCPUの中に、CPUと同じ様な演算が行えるものが入っていると思えばいい(厳密にはCPUとイコールではない)
つまりはCPU in CPUという状態でたくさんの処理を行うことが可能になるというわけだ
よく聞くデュアルコアとは2つのコアを持つCPUで、クアッドコアというのも最近よく聞くようになった

そして我々がyoutubeを見たり、iTunesを使って音楽を聞いたりという操作をPCで行うわけだが、この際にそれぞれの仕事(task)はプロセスという単位で管理がされている
youtubeを見るためにブラウザを立ち上げることでブラウザを管理するプロセスが立ち上がる。iTunesも同様だ
このプロセスというものをどの様に扱う(動かす)かで並列処理なのか並行処理なのかが分かれる

並行処理について

1つのコア上で複数のプロセスを動作させることをいう。イメージとしては同じキッチンで切る、煮る、焼く、揚げるなどを1人が行なっていると思ってもらえれば分かりやすいのではないかと思う
あまりにも高速で動いているため、それぞれの動作が同時に行われているように見えるが、実際には高速で行う処理を切り替えている
この切り替えをコンテキストスイッチと呼ぶ(詳しくはggってね)

Elixirで並行処理を行うにはプロセス間でやり取りを行えば良い
よく取り上げられるElixirの強力な並行性というのは並行処理を指している場合が多いのではないだろうか
この部分は非常に混同しやすいので自身も気をつけている

  • process間通信(messageの送受信)
  • Task
  • Agent etc...

並列処理について

こちらは複数のコアでそれぞれ1つのプロセスを割り当てて処理することをいう。イメージとしては複数のキッチンそれぞれで1品の料理を調理すると思ってもらえれば分かりやすいのではないかと思う
性能という数の暴力とでもいえばかなり効率が良さそうだ

Elixirで並列処理を行うためには以下を使用するらしいが、私自身こちらにまだ触れていないため間違いがあれば指摘頂きたい

  • OTP: behaviourの1つ GenStage
  • 外部ライブラリ: Flow

Elixirでのプロセスの扱いについて

通常プロセスを生成する祭にはOSのAPIを使用して管理を行うが、Elixirの場合はErlang実行環境である、EVM(Erlang Virtual Machine)上で独自のプロセスを生成する(EVMで生成されるプロセスの実態はCの構造体)
このプロセスはそれぞれが個別にメモリ空間を確保しており、ヒープやスタックなどとCPUを使用する権限を持ち合わせている
そしてCPU使用の権限はプロセス毎に移り変わっていく

そうすることでCPUを独占してしまうような再帰処理を防ぐことが出来るし、メモリ空間をプロセス毎に持っているため、ガベージコレクタをプロセス単位で実行することが可能である

Taskについて

f:id:takamizawa46:20190908130053j:plain
Elixirには気軽にプロセスを生成して並行処理を行うためのTaskというモジュールが実装されている
つまりはこのTask自身もプロセスということになる(元のプロセスから生成される新たなプロセス)

最も気軽にTaskで並行処理をするための関数にTask.asyncというものがある
Task.asyncは非同期処理を行うため、実行結果を取得するためにawaitを行う
Task.async, Task.awaitの詳しい記述方法については公式ドキュメントを参照してもらうか、私が以前かいた記事を見て頂ければと思う

www.okb-shelf.work

まずはシンプルに受け取った数値に+2をする関数をTaskを使って並行処理で実行させてみる

# 無名関数の作成
iex(1)> func_ = fn num -> num + 2 end
#Function<7.91303403/1 in :erl_eval.expr/5>

# 作成した無名関数をasyncに渡す(fn -> end)の形でないとerrorになるため注意
iex(2)> task = Task.async(fn -> func_.(10) end)
%Task{
  owner: #PID<0.352.0>, # 生成元のプロセス認識子
  pid: #PID<0.2140.0>, # 生成されたのプロセス認識子
  ref: #Reference<0.3658657072.3843293185.212981>
}

# 実行結果を受け取る
iex(3)> Task.await(task)
12

# おまけ: 生成元のプロセス認識子
iex(4)> self()
#PID<0.352.0>

次に同じ処理を100個のプロセスを生成して実行させてみる
パイプ演算子を使って一気にawaitまでを実行する

iex(1)> res = Enum.map(0..100, fn num -> Task.async(fn -> num + 2 end) end) |> Enum.map(fn p -> Task.await(p) end)
[2, 3, 4, 5, 6, 7, 8, 9, 10,
 11, 12, 13, 14, 15, 16, 17,
 18, 19, 20, 21, 22, 23, 24,
 25, 26, 27, 28, 29, 30, 31,
 32, 33, 34, 35, 36, 37, 38,
 39, 40, 41, 42, 43, 44, 45,
 46, 47, 48, 49, 50, 51, ...]

# おまけ
iex(2)> Enum.sum(res)
5252

iex(3)> accurate = Enum.sum(2..102)
5252

iex(4)> Enum.sum(res) == accurate
true

これで基礎はバッチリ抑えた。次にAPIを並行処理でcallしてみる

mixプロジェクトの立ち上げと関数の用意

callするAPIはおなじみのジブリAPI
httpクライアントの外部ライブラリであるpoisonを使用したいためmixのプロジェクトを立ち上げる

$ mix new ghibli_app
$ cd ghibli_app

次に必要な外部ライブラリをmix.exsのdepsに記述する

defp deps do
    [
        {:httpoison, "~> 1.5"}
    ]
  end

もし使用しているElixirのversionが1.3もしくはそれ以下の場合にはmix.exsのapplicationに以下を追加

def application do
  [applications: [:httpoison]]
end

準備が整ったので、外部ファイルをダウンロードする

$ mix deps.get
Resolving Hex dependencies...
Dependency resolution completed:
New:
  certifi 2.5.1
  hackney 1.15.1
  httpoison 1.5.1
  idna 6.0.0
  metrics 1.0.1
  mimerl 1.2.0
  parse_trans 3.3.0
  ssl_verify_fun 1.1.4
  unicode_util_compat 0.4.1
* Getting httpoison (Hex package)
:
:
* Getting parse_trans (Hex package)

無事にダウンロードされたようだ。この時点でmix deps.compileというコマンドを使用して自身でcompileを行なってもいいが、次回のiexプロンプトの立ち上げ時に自動でcompileが実行されるため、行わなくても良い

一度、iexプロンプトを起動して./lib/ghibli_app.ex に標準で記述されているhello関数を実行してみる

$ iex -S mix
Erlang/OTP 21 [erts-10.2.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

===> Compiling parse_trans
===> Compiling mimerl
:
:
Generated test app

自動でコンパイルが実行される
さっそくhello関数を呼び出してみよう

iex(1)> GhibliApp.hello
:world

上手く動作されることが確認できたので./lib/ghibli_app.exにAPIをcallする関数と並行処理を行う関数を記述していく
今回使用するAPIのエンドポイントは以下だ。GETメゾットで脳死で使用できるものを選択した

https://ghibliapi.herokuapp.com/films

defmodule GhibliApp do
 @moduledoc """
 Documentation for GhibliApp.
 """
 @doc """
 Hello world.
 ## Examples
     iex> GhibliApp.hello()
     :world
 """

 # APIをcallする関数
 def call_ghibli_api do
   HTTPoison.get!("https://ghibliapi.herokuapp.com/films")
 end

 # 受け取った指定数のプロセスを実行してAPIをcallする
 def parallel_exec(total_process) do
   Enum.map(1..total_process, fn _num ->
     Task.async(fn -> call_ghibli_api() end)
   end)
   |> Enum.map(fn p -> Task.await(p) end)
   |> Enum.map(fn data -> data.status_code end) # 戻り値の構造体からstatus_codeを抽出(データ量が多く可読性のため追加)
 end
end

ファイルに編集を行なったので立ち上がっているiexプロンプトに再度compileをするようにコマンドを記述する

iex(2)> recompile
Compiling 1 file (.ex)
:ok

続いて、メインの処理(parallel_exec)を実行するが、大量のプロセスを生成して並行でcallするとサーバーに迷惑となるので常識ある数にする
今回は適当にジョジョのアニメ5部完結記念に4を選択した

iex(3)> GhibliApp.parallel_exec(4)
[200, 200, 200, 200]

いいね
非同期処理でDBにinsertしたり、複数ファイルをstreamで読み込んで編集したりと使い方を考えるだけでワクワクする

次回の勉強会について

特に希望がなければOTPのbehaviorの1つであるGenServerを使って、stateを持つサーバーを実装してみようと思う
皆さん、お気づきかと思うが勉強会のテーマにしているものはこのブログで自身が学んだものをアウトプットするような形で選択している

開催日は9月20日(金) 19:30 ~ 21:30を予定している

参考文献

【失敗しないプレゼン資料】プレゼン資料のアンチパターンとプレゼンに対する考え方について

なんだこの記事

f:id:takamizawa46:20190831143333p:plain
自分は大学生時代に研究室で先生から資料の作り方の極意たるものを叩き込まれた
専門分野の知識よりも、資料の作り方を意識することに大半の時間を費やしたと思う
(卒論のプレゼン資料に枚数制限(上限8枚, 他研究室は平均20枚ぐらい)とフォントサイズ制限があったりなど)

当初はなぜ自分の資料が良くないものなのかということを客観的に判断することが出来なかったが
今になって先生が仰っていたことが「こういうことだったのか」と完全に理解し始めた

こういう経緯があってLT・プレゼン資料に関しては、パッと見れば、ある程度ツッコめる人間になった

そして最近、LT・プレゼンをやらせてもらったり登壇させて頂く機会が多く、1週間まるまる資料を作るなんてことがよくあった
(markdownpowerpointだけを利用する1週間と成り果てる...トホホ...)

その中で元々、社内で用意されているプレゼン・LT資料を改めて意識して閲覧したのだが「何だこの資料は...」と感じる点がいくつもあった
自身の経験を元に良くない資料のパターンと資料作成の基本的な考え方についてまとめようと思い立ったわけなのです

良くない資料の典型例

以降、「コンテンツ」というのはあるページ内でのスライドの内容のことを指している(eg: 1ページ目の内容)

タイトル詐欺

ページのコンテンツとタイトルがまったく一致しないことが良くある
ページタイトルで「〇〇について」と記述しているのにコンテンツを見てみると
全く〇〇について触れておらず、単純に情報量過多となっていて収集がつかなくなっている事が多い

大半の原因はタイトルを先に決めてから、内容を作り込むため、当初の予定とコンテンツの内容に整合性がなくなってくるためだ
コンテンツを作り終えた時に「ページタイトルと内容がマッチしているか」と振り返る癖をつけないといけない

フォント問題

小さい。14px以下の文字なんて後ろの席からは見えないし、意識して読まない
補足で必要な情報があれば別途に資料を用意して会場で配布すれば良い

フォントは最低でも20px以上を意識した方が良い
あとは良く分からないフリーのフォントを使いたがる人が多いが、自己満足なのでやめたほうが良い
搭載されているデフォルトのフォントで十分

見た目やデザインを気にする前に、読んでもらえない資料になっていないかを確認するべきだ
美しさを気にするのは最後に余裕がある時だけにしたい

読むだけの資料

LT・プレゼンをする意味のない資料が非常に多い
「え、そこに全部書いてあるじゃん」と思われていることに気づいた方が良い

ただ読むだけの資料なら配布して終わりで良い。自分の好きな時間に好きなペースで読める
LT・プレゼンをすることでその場でしか触れられない付加価値を資料に付け足せないのなら
コンテンツに含めすぎている情報量を見直して、当日これは話そうかなとざっくりと計画を練る

不必要に情報を詰め込まないことは非常に重要で情報の取捨選択について考える癖がつく

導線が見えない

コンテンツに統一感がなく、パッと見た時にどの順番で話せばいいのか分からないような構図の資料が多い
これはすなわち、資料を見る側も同じ思いをするということになる

  • 矢印を使って、流れを見える化しておく
  • 説明したいと思っていることは下線や色文字にしておく
  • 上から下に情報が流れるようにしておく

など思いつく対策法はいくらでもある
当日の自分が「どこからだっけ」と困らない様にしておく

カラフルすぎる

デザイナーが作成した資料に多い
中身の情報量がカッスカッスなのに対して、やたらと凝っていたりする

全体の文字色のベースがパステルカラーであったりと見た目は映えるが、LT・プレゼンは
スクリーンに映し出す場合が多いため、薄い文字色は見えなくなる

色文字を使う時にはそれぞれの色に役割を持たせると良い

例:

  • 赤色 -> 説明すべき用語
  • 青色 -> 自分の意見や考察
  • 緑色 -> リスナーへの投げかけ

など、色に役割を持たせておくと自身の資料を作る際にも楽だし、早く作れる様になる
黄色は見づらいのでおすすめしない

意味のない画像やアニメーション

若い方や学生に多い。資料をインパクトあるものにしようとして一生懸命アニメーションやおもしろ画像が練りこまれている
画像は単純に情報量が多くなるだけなのでほどほどに。リスナー視点で本当に必要なのかを考えるべきだ

アニメーションを作り込みすぎると当日の自分が困る
全過程を記憶しているなら構わないが、当日リラックスしてLT・プレゼンをしたいので当日に自分が緊張状態かつ
頭真っ白になった状態で把握できないようなアニメーションは作るべきではない

質問時間の時にもスライドを戻るのにアニメーションが作り込まれていると面倒だ

良い資料を作るために心がけていること

ここまでよくある資料の典型例を見てみた
このような例にならないように普段、資料作成の際に留意している点をいくつか紹介する
上記の典型例がなぜダメなのかということがこの留意点から伝わればと思う

当日に記憶喪失になっても良いように

一番、大切にしていることはこれだ
「こいつバカか?」と思うだろうが、その通りだ

この言葉の意味することは、当日に緊張してガチガチになってしまう状態であっても
どの順にどの情報をどの層に説明するかということを資料をパッとLT・プレゼン時に見ただけで思い出せるよう・分かるようににしておくということにある

そうすることで極論、プレゼンの練習が不要になるし、頭に何も詰め込まなくて良い

この考え方をベースに資料を作るとすれば

  • タイトル詐欺
  • 導線が見えない
  • カラフルすぎる
  • 意味のない画像やアニメーション

といったことは自然としなくなるだろう

話を聞いてもらえないことが最悪

LT・プレゼンにおいて最も最悪なケースというのは何だろうか
もちろん「話を聞いてもらえない」ことだ

自己満足の資料を作って話しても聞いてもらえなければ意味がない
いかにユーザー視点で情報が明確な資料を用意するかということでユーザーの興味を惹きつけることが出来る
ただ、ジャパネットの高田さんのような話術のスペシャリストもいるので例外もある

ただ、僕の様な一般人が興味を持ってもらうために用意できるシンプルなことは「資料を明確化する」こと
そうすると

  • フォント問題
  • 読むだけの資料
  • 導線が見えない
  • カラフルすぎる

こういうことはするべきではないだろう

まとめと後語り

今回はあまりにも身近にある資料の内容がひどすぎたので自身の考え方をまとめてみた
最もベースにある考え方は2つで

  • 当日、記憶喪失になっても良い様に作っておく
  • 話を聞いてもらえないことが最悪と知る

ということ
そうすれば、自然とアンチパターンを避けていくことが出来る

ただ自分の作った資料には愛着が湧いてしまうので批判的に見ることは難しい
この点に関しては訓練が必要で、何度もリスナー視点で、何も知らない人の視点で資料を見返していると
「おや?」と思う様になってくるだろう

資料は作りゃあいいってもんじゃないということを留意したい

大学生の時に教えて頂いた事がこんなに役に立つとは思わなかった
改めてお礼をさせて頂きたいと思います

【OTP入門】ElixirとOTPを使ってスタックサーバーを実装するまで

そもそもOTPとは

一言で言えば、Erlangで用意された便利なライブラリなどの集合体で便利ツールをまとめたものという認識をしている
OTPとはopen telecom platoformの略で当初は堅牢性が重要な電話交換機を開発するために使用されていた
今になってはElixirでもOTPを使用して気軽に様々なアプリを作成することが可能

OTPには behaviour(ビヘイビア: 振る舞い、動作)というルール(規約)、役割のようなものが数多く用意されている
ドラクエでいう職業のようなもので、それぞれのビヘイビアによって実行する内容や役割が異なる

今回、扱うのは「GenServer」と言われるビヘイビアでサーバーを実装するためのものだ
このGenServerを使用することでstateを保持するサーバーを簡単に実装することが可能
関数型言語でどのように値を保持しているかという内容を補足的に解説しておくと、以前から扱っているアキュムレーターがまさにそれである
内部的には再帰関数によってアキュムレーターによる値の保存を行なっている
しかし、それを毎度毎度、記述するのはどうなのよということでGenServerの出番というわけだ
アキュムレーターについて解説した記事もあるので宜しければ、ご覧ください

www.okb-shelf.work

また合わせて「Supervisor」と言われるプロセスの死活管理のためのビヘイビアについても軽く扱う
戦士(GenServer)と僧侶(Supervisor)の二人組のパーティを作っていくと思ってもらえれば良い
戦士(GenServer)が死亡した際に、僧侶(Supervisor)が面倒をみてくれる
Supervisorは優秀で彼が使う蘇生呪文は「ザオリク」であり、100%プロセスを復活させる(例え話ね)
そのためOTPを使ったアプリケーションは高い保守性と堅牢性とトラッキング(ログ、行動管理など)を実現することが出来る

GenServerについて

先ほども説明した通り、用意されたビヘイビアの1つで値を保持するサーバーを実装するために今回は用いる
GenServerにはクライアントのリクエストを捌くための2つの関数が用意されている

  • handle_call -> 同期関数で戻り値を指定可能
  • handle_cast -> 非同期関数で戻り値を指定不可

自身では戻り値があるやつとないやつ程度のレベルで理解している
このhandle_callとhandle_castがクライアントからのリクエストを受け付ける関数で第1引数のアトムでパターンマッチをして対象の関数を呼び分ける
第2引数はpid(Process Identifier(プロセス認識子))が渡り、第3引数にはサーバーで保持しているstateが渡る
今回は簡略化のためmoduleでwrapしていないが、実際にはそうではない。詳しくはまた後ほど記述するだろう

handle_call

# 戻り値は{:reply, クラインとに返したい値, サーバーのstateとして更新する値}を記述する
# stateを1に
def handle_call(:one, _form, _state), do: {:reply, 1, 1}

# stateを2に
def handle_call(:two, _form, _state), do: {:reply, 2, 2}

# stateを3に
def handle_call(:three, _form, _state), do: {:reply, 3, 3}

handle_cast

# 戻り値は{:noreply, サーバーのstateとして更新する値}を記述する
# stateを0にしてリセット
def handle_cast(:reset, _state), do: {:noreply, 0)

こんな感じでcallもcastも同名の関数でパターンマッチを使用して処理を分岐させてるんだなーという理解をしてもらえれば100点だ
他にも

  • init()
  • handle_info()
  • terminate()
  • code_change()
  • form_status()

という関数も用意されているがそれぞれについて詳しくは触れない

いまさらながらスタックについて

有名なデータ構造の1つでいわゆる「後入れ先出し」という動きをとる
スタックにはpush(追加)とpop(取得&除去)という2つの動き方がある
リストを使って説明をすると以下のようになる
なお、先頭要素は1で末尾要素は5なので1.皿の上に2.皿が。2.皿の上に3.皿が....というイメージをしてみてほしい

# 最初のデータ
init_stack_values = [1,2,3,4,5]

# 6をpush
stack_values = [1,2,3,4,5,6]

# 99をpush
stack_values = [1,2,3,4,5,6,99]

# pop(末尾の値を取得して取り除く)
popped_value = 99
stack_values = [1,2,3,4,5,6]

# pop
popped_value = 6
stack_values = [1,2,3,4,5]

今回はこのpushとpopに加えて、現在のstackの状態を知らせるinfoという関数も実装する

プロジェクトの作成

一通り説明したところでさっそくスタックサーバーを作成する
まずはプロジェクトを新規作成する。その際に「--sup」というオプションを付与しておく
これは後にsupervisorを実装する際に必要となるので指定しておく

$ mix new --sup stack

コマンドの実行後、stackというディレクトリが生成され、中には様々なファイルが生成される
./ilb/stack にserver.exを新規作成して以下の記述をする

.lib/stack/server.ex

defmodule Stack.Server do
  # GenServerを使うということを宣言(必須)
  use GenServer

  # GenServerの立ち上げ用の関数
  def start_link(init_lst) do
    # arguments: 1 -> 対象のモジュール, 2: -> stateの初期値, name: -> プロセスに名前付け
    # __MODULE__ -> Stack.Serverが参照される
    GenServer.start_link(__MODULE__, init_lst, name: __MODULE__)
  end

  # 以下、handole_castとhandle_callでpopとpushとinfoを実装
  def handle_call(:pop, _form, state) do
    [head | tail] = Enum.reverse(state)
    {:reply, head, Enum.reverse(tail)}
  end

  # 値を渡したい時は第1引数をタプルにする
  # 第1引数: {:push(パターンマッチ用), 渡された値}
  def handle_cast({:push, value}, state) do
    {:noreply, state ++ [value]}
  end

  def handle_call(:info, _form, state) do
    {:reply, state, state}
  end
end

さっそく動作を確認してみる
GenServerのプロセスを立ち上げて、スタックの動きを確認する

$ iex -S mix

iexのプロンプトが立ち上がる

iex> Stack.Server.start_link([1,2,3])
{:ok, #PID<0.148.0>}

iex> GenServer.call(Stack.Server, :pop)
3

iex> GenServer.cast(Stack.Server, {:push, 99})
:ok

iex> GenServer.call(Stack.Server, :info)
[1, 2, 99]

どうやら一連の動きは問題なく稼働しているようだ
しかしながら、例外を無理矢理、発生されると当然ながらエラーになる(stackを空にしてpop)

iex> GenServer.call(Stack.Server, :pop)
99

iex> GenServer.call(Stack.Server, :pop)
2

iex> GenServer.call(Stack.Server, :pop)
1

iex> GenServer.call(Stack.Server, :info)
[]

iex> GenServer.call(Stack.Server, :pop)
22:11:06.670 [error] GenServer Stack.Server terminating
** (MatchError) no match of right hand side value: []
:

一度、プロセスがcrashするともうGenServerの関数を呼び出しても反応がなくなる

iex> GenServer.call(Stack.Server, :info)
** (exit) exited in: GenServer.call(Stack.Server, :info, 5000)
:

再度、GenServer(プロセス)を立ち上げてやれば問題はない

iex> Stack.Server.start_link([11,22,33])
{:ok, #PID<0.163.0>}

iex> GenServer.call(Stack.Server, :info)
[11, 22, 33]

しかしながら面倒だ
Elixir、Erlangでは死んだプロセスは破棄して新たにプロセスを立ち上げてやれば良いという方針で
こんな時にSupervisorが役に立つ。死んだプロセスを破棄して新たなプロセスを立ち上げてくれる

supervisorを設定する

今回はシンプルなsupervisorを設定する
iexプロンプトの立ち上げ時にGenServer(プロセス)の立ち上げとプロセスがcrashした際に新規にプロセスを立ち上げるようにしてもらう
--supをプロジェクト生成時に渡しているため、以下のファイルが生成されているはずだ

./lib/stack/application.ex

defmodule Stack.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application

  def start(_type, _args) do
    # List all child processes to be supervised
    children = [
      # Starts a worker by calling: Stack.Worker.start_link(arg)
      # {Stack.Worker, arg}
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: Stack.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

初期の状態は上記のようになっているので、これを以下のように変更する

defmodule Stack.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application

  def start(_type, _args) do
    # モジュールのimport
    import Supervisor.Spec, warn: false
    # List all child processes to be supervised
    children = [
      # 起動したいGenServer(モジュール)を指定し、第2引数にstateの初期値をリストで渡す
      worker(Stack.Server, [[1,2,3]])
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: Stack.Supervisor]
    Supervisor.start_link(children, opts)
  end

プロンプトを一度、終了して再度立ち上げる

$ iex -S mix

すでにGenServer(プロセス)が立ち上がっているのが分かる

iex> GenServer.call(Stack.Server, :info)
[1, 2, 3]

そしてわざとcrashさせてみる。先ほどは再度、GenServer(プロセス)を立ち上げたが今回はどうか

iex> GenServer.call(Stack.Server, :pop)
3
iex> GenServer.call(Stack.Server, :pop)
2
iex> GenServer.call(Stack.Server, :pop)
1
iex> GenServer.call(Stack.Server, :pop)
22:31:05.161 [error] GenServer Stack.Server terminating
** (MatchError) no match of right hand side value: []
:

iex> GenServer.call(Stack.Server, :info)
[1, 2, 3]

supervisorによってGenServer(プロセス)が新たに作成されているようだ
初期値は先程、applicationの初期値として指定したものが与えられている
crash前の値を保持できないの?と当然思うが、別のプロセスを用意してそちらへ保管させることで可能である
そのやり方についてはプログラミングElixirにも記述があるが別の記事にまとようと思う

例外処理と関数の整理

最後にGenServerの利便性を上げるためにちょっとした修正をする
毎回、GenServer.call(Stack.Server, :info)とやるのは面倒だし、よりシンプルにスタックを利用したいため、関数を以下の様に整備した
またスタックが空になった時に、errorになってしまう問題を解決するために再帰関数でお馴染みのパターンマッチを用意する

defmodule Stack.Server do
  use GenServer
  def start_link(init_lst) do
    GenServer.start_link(__MODULE__, init_lst, name: __MODULE__)
  end

  # pop関数を実装 -> GenServer.callを呼び出す
  def pop(), do: GenServer.call(__MODULE__, :pop)

  # push関数を実装 -> 受け取った値をGenServer.castに渡して呼び出す
  def push(val), do: GenServer.cast(__MODULE__, {:push, val})

  # info関数を実装
  def info(), do: GenServer.call(__MODULE__, :info)

  # stackが空になった場合に例外処理が発動する様に関数を追加
  def handle_call(:pop, _form, []), do: {:reply, [], []}
  def handle_call(:pop, _form, state) do
    [head | tail] = Enum.reverse(state)
    {:reply, head, Enum.reverse(tail)}
  end

  def handle_cast({:push, value}, state) do
    {:noreply, state ++ [value]}
  end

  def handle_call(:info, _form, state) do
    {:reply, state, state}
  end
end

こうしてやることでよりシンプルで利便性よくGenServerを利用することが可能だ

iex> Stack.Server.pop()
3
iex> Stack.Server.pop()
2
iex> Stack.Server.push(99)
:ok
iex> Stack.Server.info()
[1, 99]
iex> Stack.Server.push(999)
:ok
iex> Stack.Server.info()
[1, 99, 999]
iex> Stack.Server.pop()
[]

呼び出しがシンプルになり、例外のerrorが無事に発生しなくなった
いいね

感想というか語らせて

以前からOTPというものの存在を理解はしていたが、どちらかというとElixirのsyntaxや仕様ばかりに着目していた
今回OTPを自身でまとめてみたのには理由がある
知人の方からElixirでリモートワークを募集しているページを見せて頂いた
求人の要項には

  • OTPへの理解がある方
  • phoenixへの理解がある方
  • 新しいものへの挑戦心

という項目が多かった。3つ目はばっちりだとしても上記の2つはよく分かっていない
特にOTPはそうで、どこで使うだとか、全くイメージできていないレベル感で「こりゃあかんな」と思った
今まで趣味レベルでのElixirへのアプローチをとってきたが少し、仕事を意識してしまったというのが今回の記事が生まれた経緯となる
というか絶対に落ちないサーバーってめちゃくちゃかっこいいやん(joe曰く99.9999%落ちない)
なんでスルーしてたんだろうと反省

ようやく入門できたかなと自己満足して終了

参考文献

プログラミングElixir
What is OTP?
Elixir初心者がOTPって結局なんなのか調べてみた
[翻訳] ElixirにおけるOTPの紹介
スタック

みんなのAIキャリカレッジin関西にて頂いた質問17個への回答まとめ

みんなのAIキャリアカレッジin関西

edgeai.connpass.com

先日こちらのイベントのパネルディスカッションなるものに登壇させて頂きまして
参加者の方から頂いた質問にお答えしました
自分自身は業務として機械学習に取り組む人間の1人として回答をさせて頂きました

お恥ずかしながら「回答がためになりました」と好評を頂きましたので、せっかくなのでテキストにまとめておこうかと思います
当日はお話できなかった情報も一部付与しています
まだまだ先駆者が少なく確定したジョブモデルが少ないので、自分自身、日々試行錯誤しながら手を進めています

同じように悩んでいる方の助けになればと思います
司会を勤められた岸さんはとても気さくな方でした。当日はありがとうございました^^

人生3度目の関西、新幹線でしたが無事に辿り着くことが出来た
f:id:takamizawa46:20190811001830j:plain:w500

質問と回答

自身は知名度のある大学院を出ているわけではなく博士をもっているわけでもない
どちらかといえばビジネスサイドにおける機械学習の適応、問題への落とし込みの方法という視点から回答をしている
研究での機械学習とビジネスでの機械学習では毛色がかなり異なると考えている

特にビジネスにおける機械学習の立ち振る舞いはまだ先駆者が少なく十分な情報とモデルがない
技術の情報は論文や記事を通じてキャッチできるが、機械学習案件の回し方や要件定義の仕方というものはまだまだ情報が少ない

テーマ1. 必要なスキルと学習法

プログラム未経験者ですが、AIやデータサイエンス業界へ興味を持ち、年初から独学で半年ほどですがPythonを勉強しています。はじめはどのようなスキルを磨くと業界への転職に有利なのかご教示願えますでしょうか

とりあえず手を動かせばいいのではないかと考えます
kaggleをやるのもよし、公開のデータセットを使用して画像分類をするもよし
やり方はいくらでもあるでしょう
私はkaggleのタイタニックチュートリアルから始めました

また実務ではPythonのみで何とかなるいう場面は少なく

などなど広い知識が要求される場合があります

実務経験がない場合に、実力を測る指標としてkaggleの成績が利用されるケースがあると思いますが、kaggleでどの程度の成績を残せばアピールになりますか

企業のレベル感によります。メダルを保持していればいるほど評価されるようなところもあるかと考えます
実務では課題に対してどのようにトライをするかという点が重視されるので成績よりも「なぜその施策をしたのか」ということに企業は興味をもつかと
その部分の説明ができれば十分なアピールになるのではないでしょうか

数学的知識や、論文読解力の高度な知識はどの程度要求されますか。

あればあるほど良いですが、必ずしも必要になるとは考えていません
分からない部分があれば適宜、インプットすれば良いので全てを熟知する必要はありません
ただ、論文を調査する際に英語がスラスラ読めると楽ですし、取得可能な情報量が増えます
論文英語は分野の専門用語が多いので、留学経験者であってもスラスラ読めない場合が多いです

論文の情報はどこでチェックするのが良いですか。

テーマ2. AIとSIの違い

このテーマは現在多くの機械学習をテーマに取り組み企業が悩んでいる部分でもある
模索しつつ、最も適切な手段を選択しているつもりだが中々、クライアントに理解してもらえないことが多い
クライアントは「AIなら何でも出来るんでしょ? この前ニュースでやってたぞ」という姿勢でやってくる

AIの開発と通常のシステム開発の違いを教えてください。

AI開発は受注したというものの、最終的な納品物を定義することが難しいです
例えば、精度95%(accuracy)と言っても達成できる保証はなく納品物として定義するとひどい目をみることがあります
SIと大きく異なるのはこの「納品物の定義が難しく、ある意味あいまいである」ということだと考えています

スケジュールを切って、この日まで確実なモデルが手に入ることはほとんどないということです
精度といっても全てのデータに対する精度ではないので。あくまでもテストデータや軽い運用で得た結果なので
時間とともに精度も変わるでしょうし、中々、難しいところではあります

システムエンジニアからAI技術者へ転身するには、具体的にどのような学習が有効でしょうか。

システムエンジニアとしての経験があるのであればコードはすらすらかけると思うので何か機械学習の問題に取り組むのが良いのではないでしょうか
そして、AI開発とSI開発が異なることを知ること、理解することです

同じように考えると痛い目にあいますし、部下に痛い目をあわさせてしまいます
上司がSI出身やセールスでAIの領域の理解が進まなくて案件や自社プロジェクトが停滞している問題は頻繁に発生しています

一般的なSierであまり馴染みがないPoCという工程(考え方)について教えて欲しい。

先ほども回答した通り、そもそもAI開発というものは最終的な納品物を定義することが難しいです
そのため、そもそもこの問題って機械学習で解決できるものなのかを調査するような、砕けた言い方をすれば「お試し」が必要になります
機械学習で解けない問題を受注して、最終的な納品物が達成できないというリストを回避するためにもミニマムでスタートして
実現可能なのかをPoC(proof of concept)で確認をするという流れになるのです

なぜPoCが必要なのかということをクライアントに伝えることは非常に重要でセールスにも絶対に理解してもらう必要があります
基本的にクライアントは現状ではAIは何でもやってくれるというレベル感なので説明が重要です

しかし、その一方でいつまでもPoCばかりで本開発に乗り出せないPoC地獄という問題も待ち構えている...辛い...

テーマ3. 具体的な仕事

現在、案件で最も多いデータの種類は何か(画像、時系列など)

ここ数ヶ月は時系列データ(言語データ)が特に多いかな
正直なところ、画像の分類はクラウドや手軽なサービスが多く、誰にでもトライできるレベルになっています

その点、言語データは扱いが難しく、何よりも課題の落とし込みが難しいですが
世界的な技術のレベルが上がってきたため、ようやくビジネスにも展開できるようになったという印象です

今まで一番テンションの上がったプロジェクトは何ですか。

言語生成モデルの作成(雑談bot)

1日の仕事の流れを教えて頂きたいです。

日によって様々ですが、リストだしをするとこんな感じに

  • セールスとの打ち合わせ
  • セールスに同行してクライアントと打ち合わせ
  • データの処理やらクレンジング
  • ラベル付け(アノテーション)
  • モデル構築
  • 報告レポートの作成
  • API作成
  • cloud環境の作成
  • 最新動向の調査

など、今思いつくだけでもこれだけあります
特に重要なのはクライアントの課題を見つけて機械学習に解ける形に落とし込むこと
社内のメンバーに期待するよりも私は自分でクライアントの元へ行ってヒアリングをした方が早いと考えています

ユーザーへどのようなアウトプットをされていますか。

このブログを読んでいただきありがとうございます

大小さまざまだと思いますが、一般的に一案件あたり、どれくらいの人数/工期なのでしょうか。

自身が所属するAIチームでは1案件に対してそれぞれが手を動かすため、はっきりした人数は出せないが2ヶ月でPoCを実施することが多いです
(クライアントの金額感がちょうど2ヶ月程度なため)

いわゆる機械学習エンジニアがヒアリングから課題設定、工数出し、レポート作成までほとんど全てを行なっています

テーマ4. 企業の特性

AI領域でいち早く経験を積むなら、現状は、データを持つ会社に行くのがいいか、AI技術をもつ会社に行くのがよいのか。

これは非常に難しい問題で、どちらにもメリットがありデメリットがあります
データを持つ会社で働くということは自社のプロダクトとして稼働できるため、無理難題を押し付けられても
ある程度ストップをかけることが出来るし、失敗しても社内だけの影響に終わりますが
受注では失敗は基本的にクライアント側から許されることはなく、リスクを常に抱えている

しかし、その一方でAI技術をもつベンチャーに行けば、クライアントへのヒアリングから提案、APIの作成までと
幅広い領域でAI開発に携わることが出来る。データを持つ大企業では割り振りされる作業はごく一部だろう

よく聞く黒い話はこんな感じ

  • データを持つ会社(大企業)でデータサイエンティストとして公募しておいてアノテーションを永遠とやらせる(スクールからの紹介が多いそうなので要注意)
  • AI技術をもつ会社(ベンチャー)では任されるタスクの範囲が広く、手に負えない

どちらを選ぶかはあなた次第です

AIの分野で今最も熱いベンチャー企業はどこですか。人工知能で最も注目している会社を教えてください。

日本で言えばやはりPFN。強靭、無敵、最強!!
普段、会社名を意識して情報収拾しないなので正直なところ会社名には疎いです

御社のデータサイエンティストの年収はいくらぐらいですか。

私の口からは大きな声で言えないが、普通の新卒より少し多いくらい(学部卒: 新卒23歳)

テーマ5. 将来像

これから先が楽しみな分野ですが、10年後にはこの技術はどうなっていると考えていますか。

いわゆる人工知能というものへの参入の障壁は下がり続け、現在の車やコンピューター同様に誰しも気軽に使えるものになっているでしょう
開発の難易度も同時に下がる。しかし、開発の難易度が下がるということはイコール、個人によって便利なものが生まれるとは考えていないです

人工知能というのは課題解決のための手段の1つであり、何に使うかが非常に重要なため、結局はアイディアは人間が捻出するので
人間の仕事を全て置き換えるような状態にはなっていないんじゃないかと勝手に想像してます

AI・人工知能に関する開発で、現在はまだ難しいがいつか解決したい社会の課題や、実現したい(創りたい)ことはありますか。

ひしひしと仕事をする中で感じさせられていることは「なぜそうなったのか」というプロセスが確認できません
いわゆるブラックボックスとして認知されている現在のディープラーニングでは実運用には困るということです
なぜそうなったのかが分からないということはすなわち、説明が出来ないということになります

「なぜ私が人工知能によってガンと診断されたのか教えてください」と問いかけても
「そういうものですので」という返答が返ってくるわけです

現在も研究は広く行われているが、イメージするには十分でしょう
いかにして人工知能(ディープラーニング)の決定プロセスを確認するか(xai -> 説明可能なAI)という分野の今後の発展に期待しています

また個人的には人工知能の技術を農業に落とし込みたい
日本は食料自給率が大変低く、多くを海外からの輸入で賄っています
その一方で、世界で最も食料を廃棄する国でもあるわけです

この矛盾はなんなのか。どうにかして解決したいと密かに思っています

感想

普段は前にたって偉そうにお話をすることはないので大変、緊張しました
正直なところ、研究という立場で開発をしていないので技術力に関してはへっぽこだと思う
しかし、どのようにビジネスとして運用するかという経験は多少なりとも積んできたつもりだ

何が問題になるのか、何が出来るのかは理解しているし、説明することも出来る
今後、技術がより一般的になる一方で課題を技術で解けるように落とし込むことは以前変わらず難しいだろう

これは私の生存戦略の1つで換えの効かないスキルとしてより高めていこうと感じた

小難しい話ばかりしてきたが、最後は教えていただいた「だるま」にて打ち上げ。本場はやはりソースの2度付けが禁止でした

f:id:takamizawa46:20190811001736j:plain

【空いた時間を有効活用】暇な人・挫折した人に見て欲しいTEDの素晴らしい公演7選

TEDとは何か

www.ted.com

世界中で講演会を開催している非営利団体であり、毎年世界各地で数多くの公演を開いている
公演の内容は非常に幅広く、経済や政治にはじまり技術や人生観など様々で非常に多様性がある
また、その公演の多くはTED公式サイト、youtubeなどを通して広く配信されており、気軽に公演を視聴することができる
海外の団体であるが、すでに日本人でも多くの方がTEDの公演に出演しており、非常に素晴らしい公演を披露している

私はTEDが好きで、落ち込んだ時ややる気が出ない時、作業用動画としてそこそこの数の講演を動画で見た
今回は過去のTEDの公演の中からyoutubeで視聴可能な日本人の公演もしくは日本語字幕があるものを10個を選別した
それぞれの動画に私がどんな時に見たものかという説明もつけた

  • 暇でやることがない
  • やる気ができない
  • 挫折してしまった
  • いいアイディアが思いつかない

など様々なコンセプトで動画を選別したので、ぜひ視聴してみてほしい
講演会のタイトルや公演内で使用されている英語を私の独断で翻訳して解釈している部分があり
正しい翻訳でない場合があるのでご了承お願いします

字幕はyoutubeの字幕のアイコンをクリックすることでon/offを切り替えることが可能です

ちなみに補足だがTEDにはTEDxという世界各地で行われている講演会がある
TEDxTOKYOのようにxの後には講演会を開催した場所が記述される
近年、日本各所でTEDxが開催されている

夢を叶えるために大切なこと

www.youtube.com

「思うは招く」
私がTEDにハマるきっかけとなった公演
北海道の民間企業でロケットの開発・研究を行う植松努さんの公演で、夢をあきらめないことの大切さを教えてくれる
幼少の頃より「ロケット開発を、おまえなんかに絶対できるわけない」
バカにされ続けてきた植松さんがどのように考え、どのようにして夢を実現したのかが語られている

笑いあり涙あり。「どうせむり」という言葉がいかに人の熱意を奪うのか。ぜひ一度視聴してみてほしい

1つのことを続けることができない(熱中できない)

www.youtube.com

この公演で語られている内容はまさに私のことだと感じた
昔から1つのことに熱中することが出来ず、すぐに違うことに興味が湧いてしまい、そちらに気持ちが流れていく
1つのことを極めることが出来ず、「自分はダメなやつだな...」と昔から劣等感を感じていたが、この公演を見て考え方が変わった

1つのことに熱中できず、様々なことに興味がもてる人を「マルチ・ポテンシャライト」と呼び
これは一種の才能であると語っている
さまざまな分野の知識がかけ合わさり、あなたにしか考え出せないイノベーションが生まれる。素晴らしい才能だということを教えてくれた

いいアイディアが思いつかない

www.youtube.com

今では懐かしさも感じるが平成の時代に「無限プチプチ」というおもちゃが大流行した
今でいう手持ち扇風機のような存在で、学生時代にはどのクラスにも1人は持っている人が居た
そんな「無限プチプチ」を開発した高橋さんが普段どのようにして独創的なアイディアを生み出しているかという方法が語られている

「アイディアは掛け算」である。A x Bの組み合わせで無限にアイディアを生み出すことが出来る。Bはしりとりでワードを繋いでいく
りんご x B で何かを考えてみた

  • りんご x スマホ
  • りんご x ホットケーキ
  • りんご x 切り方
  • りんご x たこ焼き
  • りんご x 幾何学

一見くだらないかもしれないが、この中からとんでもないアイディアが生まれることがあるというわけだ

何かを成し遂げるための時間の使い方

www.youtube.com

  • 叶えたい夢がある
  • 成し遂げたいことがある
  • お金持ちになりたい

など人には様々な目標がある
しかし、多くの人は「時間がない」と口にして実行に移ることはない
本当にそうなのかということを実数値で算出して時間の大切さを教えてくれる
100歳まで生きるとして誰しも平等に与えられている自由な時間は14年と4ヶ月

「この時間をどのように生きるのか」ということを教えてくれる
自分のためではなく誰かのために命を使う、つまりは「あなたの使命」をどのように考えるのか

今ある時間を大切に使わねばと考えさせられる公演
大学生の頃に視聴して貴重な時間をなんてくだらないことに費やしているんだろうと踏み出す勇気をもらった

考えの違う人を理解する方法

www.youtube.com

そりの合わない上司や同僚で悩んでいた時にたまたま見た公演
アンさんがアメリカから23歳の時に日本に越してきて日本の生活の中で見つけた「世界観」にまつわる話
なぜ日本人が「いただきます」という言葉を食事の前に言うのか。それは「Let's eat」という意味ではないということ
命を捧げてくれた動物・植物への感謝、運んでくれた人、全ての携わってくれた人への感謝が込められているということ

国や人はそれぞれの世界観をもっており、大事にしているものが違う

上司、友人、同僚、恋人、家族...それぞれが大事にしている「世界観」は異なる
人の大切にしている世界観に興味を持つことで自分と自分の将来を変えることが出来るということを教えてくれる
博多弁でユーモアに溢れる非常に素晴らしい公演

何でも良いからやってみて続けてみること

www.youtube.com

「何かしたいけど何をしていいのか分からない」

そんな時に荒川さんは兄から「口だけやん」と叱責され、突如「ゴミ拾い」を始めることを決意した
「ゴミ拾いなら出来るやろ」というマインドで毎日ゴミ拾いを始めた
ゴミ拾いを続ける中で非常にたくさんの嫌がらせを受けつつも、続けていくうちに仲間が増え、取材され...テレビにラジオに...

全国各地からゴミ拾いをする仲間が集まってきた

どんなことでも初めて続けてみることが大切、何かを成し遂げれば自信を身につけることが出来る
ゴミ拾いから始まった人生が大きく変わっていく話。どんなことでもバカにされても否定されてもやってみることの重要さを教えてくれる

誰しも最初から出来たわけではない。どんな人でもはじまりは小さなことだと気づかされる

数学の面白さ・不思議さ

www.youtube.com

私は昔から計算間違えが多く、算数や数学という科目でいつも高い点数が取れずいつのまにか苦手意識をもっていた
考え方や論理には興味がもてるものの、好きかと言われれば嫌いだったと思う

そんな中で、この公演で紹介される「ガウス曲線」とゴルドンボードの実験に鳥肌がだった
ランダムという現象がもたらす結果の分布はガウス曲線の形になっている

この現象が数学で証明されること
初期のgoogle検索の被リンク優位がゴルドンボードの実験と同じ現象を使って作られているということに何とも言えない神秘さを感じさせられる

数学は決して得意とは言えない私だが、興味を持つべき対象であるということを教えてくれた素晴らしい公演