(この記事は Elixir (その2)とPhoenix Advent Calendar 2016 7日目の記事です)
前回は、ごくごくカンタンな受け答えをする、AIとはとても呼べない何かを作りました
今回は、多少なりとも、言われたことを文章構成として解釈して、その内容に合わせた返事≒オウム返し、ができるようにしてみましょう
あと、言うことを変えるたびにイチイチrecompile()するのも面倒なので、PhoenixでWebアプリとして作っていきます
なお、本コラム中の「Elixirの書き方」については、あまり細かく説明をしていないので、「ここの書き方が分からない」とか「この処理が何をしているのかよく分からない」等あれば、コメントいただければ、回答します
Phoenixのインストール
ここはあまり大事なところでは無いので、やや説明を手抜きします
Elixir入門「第3回:Phoenix 1.3で高速webアプリ & REST APIアプリをサクッと書いてみる」のP6~10に、PhoenixのインストールとHTML編集について書いているので、こちらを実施して、Phoenixの使い方を押さえてください
なお、DockerでElixirを入れていない方は、P6先頭のDockerでのElixirイメージ起動は無視してください
Phoenixプロジェクト作成、GETパラメータの受け渡しと表示
Phoenixプロジェクトを作成し、起動します
# mix phx.new web_mini_ai --no-brunch --no-ecto
# cd web_mini_ai
# iex -S mix phx.server
ブラウザで以下URLにアクセスすると、上記資料のP7と同じ画面が表示されます
http://localhost:4000
次に、GETパラメータを受け渡しできるようにするために、index()の第2引数の「_params」を「params」に変更し、更にrenderの最後に、GETパラメータ群を引数として渡すための「, params: params」を追記します
defmodule WebMiniAi.PageController do
use WebMiniAi.Web, :controller
def index(conn, params) do
render conn, "index.html", params: params
end
end
ページ側も書き換えます
<p>あなた「<%= @params[ "message" ] %>」</p>
<p>貧弱AI「<%= MiniAi.listen( @params[ "message" ] ) %>」</p>
<form method="GET" action="/">
<input type="text" name="message" size="60" value="">
<input type="submit" value="話しかける">
</form>
ブラウザで以下画面が表示されます
テキストボックスに何かを入れ、「話しかける」ボタンを押すと、上部の「あなた」のところに入れたメッセージが表示されます
貧弱AIは、まだ喋れません
貧弱AIを引っ越しさせる
前回作った、mini_aiプロジェクト配下のlib/mini_ai.exモジュールを、今回プロジェクト配下のlibの下にコピーし、以下のように書き換えます(Webページで表示するので、不要なIO.putsを削除したもの)
defmodule MiniAi do
def listen( message ) do
case message do
"" -> ""
nil -> ""
_ ->
Mecab.parse( message )
|> get_words( [] )
|> reply
end
end
def reply( words ) do
case words |> Enum.any?( &( &1 == "AI" ) ) do
true -> "はい、そうです。「弱いAI」は思考できませんが、「強いAI」は人のように思考できるんですよ。"
false -> "ふーん( ´,_ゝ`)"
end
end
def get_words( [ %{ "surface_form" => word } | tail ], words ), do: get_words( tail, words ++ [ word ] )
def get_words( [], words ), do: words
end
ページ側も、貧弱AIが上記モジュールを使って喋るよう、書き換えます
<p>あなた「<%= @params[ "message" ] %>」</p>
<p>貧弱AI「<%= MiniAi.listen( @params[ "message" ] ) %>」</p>
…
改めてMeCabモジュールのインストール
今回プロジェクトには、まだMeCabモジュールを入れていないため、mix.exsにMeCabを追加します(cowboyのエントリーの後にカンマを入れ忘れないように注意)
defmodule WebMiniAi.Mixfile do
use Mix.Project
…
defp deps do
[{:phoenix, "~> 1.2.1"},
{:phoenix_pubsub, "~> 1.0"},
{:phoenix_html, "~> 2.6"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:gettext, "~> 0.11"},
{:cowboy, "~> 1.0"},
{ :mecab, "~> 1.0" },
]
end
…
iexを、Ctrl+Cを2回押して抜けた後、モジュールを取得します(要ネット接続)
iex>
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
(v)ersion (k)ill (D)b-tables (d)istribution
# mix deps.get
Windowsを使っているなら、MeCabモジュール内のUNIX依存コードを修正します
98: x when x == nil or x == false ->
99: """
100: cat <<EOS.907a600613b96a88c04a | mecab #{mecab_option}
101: #{str}
102: EOS.907a600613b96a88c04a
103: """
これを以下のように書き換えます
98: x when x == nil or x == false ->
99: "echo #{str} | mecab #{mecab_option}"
6/15追記:_build配下のフォルダを削除しなくても、「mix deps.compile」を使えば、リビルドできるようです
↓
_build/dev/lib/mecabフォルダがあれば削除し、リビルドとPhoenixサーバ起動を行います(warningが出ますが、ひとまず動くので気にせずに)
# iex -S mix phx.server
Eshell V8.0 (abort with ^G)
Compiling 2 files (.ex)
warning: the underscored variable "params" is used after being set. A leading underscore indicates
that the value of the variable should be ignored. If this is intended please rename the variable to
remove the underscore
web/controllers/page_controller.ex:5
話しかけてみましょう
何もロジックは進化していないけど、雰囲気、出てきたんじゃない?
「固有名詞」を捕まえてオウム返しさせる
今は「AI」という固定の単語にしか反応しないため、文中に「固有名詞」が出現した場合に、オウム返しさせるようなロジックに変更してみます
固有名詞は、MeCabの解析中、「part_of_speech_subcategory1」を見ると判定できるため、これを捕まえ、最後の固有名詞でオウム返しさせてみます
defmodule MiniAi do
def listen( message ) do
case message do
"" -> ""
nil -> ""
_ ->
Mecab.parse( message )
|> get_last_proper_noun( "" )
|> reply
end
end
def reply( words ) do
case words != "" do
true -> "#{words}が好きなんだねー"
false -> "ふーん( ´,_ゝ`)"
end
end
def get_words( [ %{ "surface_form" => word } | tail ], words ), do: get_words( tail, words ++ [ word ] )
def get_words( [], words ), do: words
def get_last_proper_noun( [ %{ "part_of_speech_subcategory1" => "固有名詞", "surface_form" => word } | tail ], last_word ), do: get_last_proper_noun( tail, word )
def get_last_proper_noun( [ %{ "part_of_speech_subcategory1" => _ } | tail ], last_word ), do: get_last_proper_noun( tail, last_word )
def get_last_proper_noun( [], last_word ), do: last_word
end
話しかけてみましょう
うぅ、なんか「ただの巨人好き」と勘違いされている...
という訳で、次回は、「進撃の巨人」をキチンと固有名詞として理解する現在っ子に進化させます
あと本当は、CaboChaとか使って、もうちょい頭の良い解析をしたいんだけど、どうもElixirのモジュールが世の中に無いようなので、次回、CaboCha Elixirモジュールも作ってみます