Help us understand the problem. What is going on with this article?

Elixirで弱々しいAI#1「MeCabで文章パース」

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

AIには、「強いAI」と「弱いAI」という分類がありますが、Elixirと、MeCabやWikipedia API等を使って、「弱いAI」の中でも、更に貧弱ゥな「弱々しいAI」(笑)を何回かに分けて作ってみます

ザックリとした仕様としては、こんな感じですかね :speech_balloon:

  • 対話して、言われた文脈を何となく解釈して、それとない返事を返す
  • 聞いた言葉から受けた印象から、感情のようなものが揺らぎ、返事が変化する
  • 足りない知識は、Wikipediaに取得しに行き、当たり障り無い感じで引用する

なんとも頭の悪いAIになりそうな気配しかしませんが、アレコレしているうちに魂みたいなものが宿るか、哲学的ゾンビができあがるかも知れない

ひとまず今回は、MeCabで日本語の文章をパースして、ワンパターンな意味解釈をするところまで作ります :white_check_mark:

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

ElixirとMeCabのインストール

まず、Elixirをインストールします
下記からダウンロードしてインストールするか、yumやapt、Homebrew等でインストールしてください

 Elixirダウンロード: http://elixir-lang.org/install.html

Dockerを使うのであれば、Dockerをあらかじめインストールした上で、Elixirイメージを持ってきます

 Dockerダウンロード: https://www.docker.com/get-docker
  ※Windows版やMac版は、Stableが調子悪い(docker pullが失敗する)ため、そのときはEdge版で治ることが多い

> docker pull trenpixster/elixir
> docker run -it trenpixster/elixir /bin/bash
# uname -a
Linux 8abfc1bd7754 4.9.12-moby #1 SMP Tue Feb 28 12:11:36 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

次に、MeCabを下記からダウンロードしてインストールするか、yum等でインストールしてください

 MeCabダウンロード: http://taku910.github.io/mecab/#download

ちなみに、Windows版MeCabは、「C:\Program Files (x86)\MeCab\」以外の場所にインストールすると、次回以降で大変な思いをすることになるので、インストール先は変えないほうが無難です

あと、辞書の文字コードは、「UTF-8」を選択してください

Elixirプロジェクト作成、MeCabモジュール導入/修正

Elixirプロジェクトを作成します

mix new mini_ai
cd mini_ai

MeCabをElixirで利用する準備として、mix.exsにMeCabを追加します

mix.exs
defmodule MiniAi.Mixfile do
    use Mix.Project
    
    defp deps do
        [
            { :mecab, "~> 1.0" }, 
        ]
    end
    

MeCabモジュールを取得します(要ネット接続)

mix deps.get

WindowsネイティブのElixirを利用する場合、そのままのコンソールでは、Elixirが扱うUTF-8を表示できないため、事前に切り替えておきます

chcp 65001

また、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:          """

これを以下のように書き換えます

deps/mecab/lib/mecab.ex ※修正後
 98:        x when x == nil or x == false ->
 99:          "echo #{str} | mecab #{mecab_option}"

なお、deps.getで取得したモジュールは、上記のようなコード書き換えを行っても自動的にはリビルドされないため、もし_build/dev/lib/mecabフォルダが作られている場合は削除しておきます(後述の手順でリビルドが走ります)

MeCabでのパースを実装

lib/配下にmini_ai.exというファイルができているので、以下の内容に書き換えます

lib/mini_ai.ex
defmodule MiniAi do
    def listen( message \\ "あなたは弱々しいAIですか?" ) do
        Mecab.parse( message )
    end
end

コンソールからの日本語入力に不自由しそうなので、今回はコード内に日本語文章を入れることにしました
(どこかの回で自由入力可能な状態にもっていきます)

これで準備は整いましたので、iex(Elixirのインタラクティブシェル)を起動し、ビルドします

iex -S mix
Eshell V8.0  (abort with ^G)
==> mecab
Compiling 1 file (.ex)
Generated mecab app
==> mini_ai
Compiling 1 file (.ex)
Generated mini_ai app
Interactive Elixir (1.4.2) - press Ctrl+C to exit (type h() ENTER for help)
iex>

iexから実装したコードを呼び出すと、MeCabでパースされ、文章が「単語毎のMapのリスト」として分解されます

iex> MiniAi.listen()
[%{"conjugation" => "", "conjugation_form" => "", "lexical_form" => "あなた",
   "part_of_speech" => "名詞", "part_of_speech_subcategory1" => "代名詞",
   "part_of_speech_subcategory2" => "一般",
   "part_of_speech_subcategory3" => "", "pronunciation" => "アナタ\r",
   "surface_form" => "あなた", "yomi" => "アナタ"},
 %{"conjugation" => "", "conjugation_form" => "", "lexical_form" => "は",
   "part_of_speech" => "助詞", "part_of_speech_subcategory1" => "係助詞",
   "part_of_speech_subcategory2" => "", "part_of_speech_subcategory3" => "",
   "pronunciation" => "ワ\r", "surface_form" => "は", "yomi" => "ハ"},
 %{"conjugation" => "", "conjugation_form" => "", "lexical_form" => "AI",
   "part_of_speech" => "名詞",
   "part_of_speech_subcategory1" => "固有名詞",
   "part_of_speech_subcategory2" => "一般",
   "part_of_speech_subcategory3" => "", "pronunciation" => "アイ\r",
   "surface_form" => "AI", "yomi" => "アイ"},
 %{"conjugation" => "基本形", "conjugation_form" => "特殊・デス",
   "lexical_form" => "です", "part_of_speech" => "助動詞",
   "part_of_speech_subcategory1" => "", "part_of_speech_subcategory2" => "",
   "part_of_speech_subcategory3" => "", "pronunciation" => "デス\r",
   "surface_form" => "です", "yomi" => "デス"},
 %{"conjugation" => "", "conjugation_form" => "", "lexical_form" => "か",
   "part_of_speech" => "助詞",
   "part_of_speech_subcategory1" => "副助詞/並立助詞/終助詞",
   "part_of_speech_subcategory2" => "", "part_of_speech_subcategory3" => "",
   "pronunciation" => "カ\r", "surface_form" => "か", "yomi" => "カ"},
 %{"conjugation" => "", "conjugation_form" => "", "lexical_form" => "?",
   "part_of_speech" => "記号", "part_of_speech_subcategory1" => "一般",
   "part_of_speech_subcategory2" => "", "part_of_speech_subcategory3" => "",
   "pronunciation" => "?\r", "surface_form" => "?", "yomi" => "?"},
 %{"conjugation" => "", "conjugation_form" => "", "lexical_form" => "",
   "part_of_speech" => "", "part_of_speech_subcategory1" => "",
   "part_of_speech_subcategory2" => "", "part_of_speech_subcategory3

ここから、各単語を拾うには、以下のようなコードで、「単語毎のMapのリスト」のうち、単語だけを抽出し続け、それをリストに追加していきます

lib/mini_ai.ex
defmodule MiniAi do
    def listen( message \\ "あなたはAIですか?" ) do
        Mecab.parse( message )
        |> get_words( [] )
    end
    def get_words( [ %{ "surface_form" => word } | tail ], words ), do: get_words( tail, words ++ [ word ] )
    def get_words( [], words ), do: words
end

コード修正したら、iexの中でrecompile()とすると、リビルドされます

iex> recompile()
iex> MiniAi.listen()
["あなた", "は", "AI", "です", "か", "?", ""]

意味解釈(ごくカンタンなもの)

キチンとした意味解釈は、次回行うとして、今回は、「AI」という単語が出現したらAIのウンチクを語り、「AI」が無ければそっけない返答をする...そんなウザいAIに仕上げて、今回は完了としましょう :disappointed:

lib/mini_ai.ex
defmodule MiniAi do
    def listen( message \\ "あなたはAIですか?" ) do
        IO.puts( "あなた「#{message}」" )
        answer = Mecab.parse( message )
        |> get_words( [] )
        |> reply
        IO.puts( "貧弱AI「#{answer}」" )
    end
    def get_words( [ %{ "surface_form" => word } | tail ], words ), do: get_words( tail, words ++ [ word ] )
    def get_words( [], words ), do: words
    def reply( words ) do
        case words |> Enum.any?( &( &1 == "AI" ) ) do
            true  -> "はい、そうです。「弱いAI」は思考できませんが、「強いAI」は人のように思考できるんですよ。"
            false -> "ふーん( ´,_ゝ`)"
        end
    end
end

リビルドして、実行すると、ウンチクを語ります

iex> recompile()
iex> MiniAi.listen
あなた「あなたはAIですか?」
貧弱AI「はい、そうです。「弱いAI」は思考できませんが、「強いAI」は人のように思考できるんですよ。」
:ok

文章から「AI」が無くなると、まるで興味無い返答になります :cold_sweat:

iex> recompile()
iex> MiniAi.listen
あなた「あなたはマシーンですか?」
貧弱AI「ふーん( ´,_ゝ`)
:ok

次回は、もう少し、頭良い感じにしましょう :hearts:


p.s.

:stars::stars: 6/8(木)開催の《fukuoka.ex #1》発足MeetUp、満員御礼です。ご来訪を楽しみにお待ちしております
(※立ち見席とか検討するので、ダメ元でよろしければ、引き続きお申込みどうぞ)
image.png


Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away