LoginSignup
22
6

More than 5 years have passed since last update.

Elixirで弱々しいAI#2「構文解釈してオウム返し(ついでにPhoenixでWebアプリ化)」

Last updated at Posted at 2017-05-05

(この記事は Elixir (その2)とPhoenix Advent Calendar 2016 7日目の記事です)

前回は、ごくごくカンタンな受け答えをする、AIとはとても呼べない何かを作りました :sweat:

今回は、多少なりとも、言われたことを文章構成として解釈して、その内容に合わせた返事≒オウム返し、ができるようにしてみましょう

あと、言うことを変えるたびにイチイチrecompile()するのも面倒なので、PhoenixでWebアプリとして作っていきます

なお、本コラム中の「Elixirの書き方」については、あまり細かく説明をしていないので、「ここの書き方が分からない」とか「この処理が何をしているのかよく分からない」等あれば、コメントいただければ、回答します :headphones:

Phoenixのインストール

ここはあまり大事なところでは無いので、やや説明を手抜きします :checkered_flag:

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」を追記します

lib/web_mini_ai_web/controllers/page_controller.ex
defmodule WebMiniAi.PageController do
  use WebMiniAi.Web, :controller

  def index(conn, params) do
    render conn, "index.html", params: params
  end
end

ページ側も書き換えます

lib/web_mini_ai_web/templates/page/index.html.eex
<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>

ブラウザで以下画面が表示されます

1.png

テキストボックスに何かを入れ、「話しかける」ボタンを押すと、上部の「あなた」のところに入れたメッセージが表示されます

貧弱AIは、まだ喋れません :baby_tone1:

貧弱AIを引っ越しさせる

前回作った、mini_aiプロジェクト配下のlib/mini_ai.exモジュールを、今回プロジェクト配下のlibの下にコピーし、以下のように書き換えます(Webページで表示するので、不要なIO.putsを削除したもの)

lib/mini_ai.ex
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が上記モジュールを使って喋るよう、書き換えます

lib/web_mini_ai_web/templates/page/index.html.eex
<p>あなた「<%= @params[ "message" ] %></p>
<p>貧弱AI「<%= MiniAi.listen( @params[ "message" ] ) %></p>

改めてMeCabモジュールのインストール

今回プロジェクトには、まだMeCabモジュールを入れていないため、mix.exsにMeCabを追加します(cowboyのエントリーの後にカンマを入れ忘れないように注意)

mix.exs
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依存コードを修正します

deps/mecab/lib/mecab.ex
 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

話しかけてみましょう

2.png

何もロジックは進化していないけど、雰囲気、出てきたんじゃない? :sweat_smile:

3.png

「固有名詞」を捕まえてオウム返しさせる

今は「AI」という固定の単語にしか反応しないため、文中に「固有名詞」が出現した場合に、オウム返しさせるようなロジックに変更してみます

固有名詞は、MeCabの解析中、「part_of_speech_subcategory1」を見ると判定できるため、これを捕まえ、最後の固有名詞でオウム返しさせてみます

lib/mini_ai.ex
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

話しかけてみましょう

4.png

うぅ、なんか「ただの巨人好き」と勘違いされている...

という訳で、次回は、「進撃の巨人」をキチンと固有名詞として理解する現在っ子に進化させます

あと本当は、CaboChaとか使って、もうちょい頭の良い解析をしたいんだけど、どうもElixirのモジュールが世の中に無いようなので、次回、CaboCha Elixirモジュールも作ってみます :hearts:

22
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
6