Elixirで弱々しいAI#3「MeCab辞書差し替え |> CaboChaモジュールの作成」

（この記事は [Elixir (その2)とPhoenix Advent Calendar 2016](http://qiita.com/advent-calendar/2016/elixir2_and_phoenix) 8日目の記事です）

[前回](http://qiita.com/piacere/items/ea4ab52191a049898df1)は、PhoenixでWebアプリ化して、最後に出現した固有名詞をオウム返しする、多少は言われたことを掴む（文字通り「掴む」だけですが…）貧弱AIを作りましたが、「進撃の巨人」を知らないオールドタイプであることが判明しました :sweat_smile:

今回は、MeCabの辞書を差し替え、貧弱AIを現代っ子に進化させた後、文章の構成を解析するための「CaboCha」をElixirで使えるようにします

CaboChaのElixirモジュールは、世に無いようなので、今回作ってみます :rainbow:

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

# 新語辞書に差し替え

「mecab-ipadic-NEologd」という、MeCabの新語辞書をインストールすることで、現在っ子に進化します

Windowsを除いて、パッケージマネージャでインストール可能です :ok_hand:

- Docker(trenpixster/elixir)やUbuntu・・・[apt](http://kivantium.hateblo.jp/entry/2015/03/15/175612)
- CentOSなど・・・[yum](http://qiita.com/h1dia/items/26b8bca4bba4acc44a8e)
- Mac・・・[Homebrew](http://ichibariki.hatenablog.com/entry/2017/01/09/215028)
- Windows・・・[手動で設定](https://github.com/kazuki-aruga/mecab-ipadic-neologd-windows)　※UTF-8で行うため、以下2点の手順を変更する必要があります
 - 2の手順は、「 [Recompile SHIFT-JIS Dictionary]」では無く「 [Recompile UTF-8 Dictionary]」を実行
 - 4の手順の前に、mecab-dict-compile.cmdの「-t」の「shift-jis」を「UTF-8」に変更

早速、「神撃の巨人」をリトライしてみると．．．おー、通じるようになった :laughing: 

![1.png](https://qiita-image-store.s3.amazonaws.com/0/155423/71015f2c-5fa4-fad9-3844-e5b08912b6ee.png)

# CaboChaのインストール

CaboChaを下記の「ダウンロード」からインストールするか、yum等でインストールしてください

　CaboCha： https://taku910.github.io/cabocha/

なお、Windows版は、MeCab同様、「UTF-8」を選択してください

# CaboChaモジュールの作成

インストールが終わったら、CaboChaを呼び出すモジュールを追加します

```elixir:lib/cabocha.ex
defmodule Cabocha do
	def view( body ), do: tree( body )
	def tree( body ), do: if body == "" || body == nil, do: "", else: "echo #{body} | cabocha" |> execute
	def execute( command ) do
		command
		|> to_char_list
		|> :os.cmd
		|> to_string
	end
end
```

このモジュールを使って、CaboChaの解析結果をページに追加してみます

```erb: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>

<hr>
<h3>CaboCha解析結果</h3>
<p><pre><%= Cabocha.view( @params[ "message" ] ) %></pre></p>
```

話しかけると、CaboChaでどのように解析されているかが分かります

![2.png](https://qiita-image-store.s3.amazonaws.com/0/155423/e35ab6ca-376e-7d83-44d5-4dd40d3e4805.png)

# CaboCha出力をXMLに切り替え

このツリー構造は、見た目は良いですが、このままだとプログラム上からは扱いにくいので、CaboChaのコマンドラインオプション「-f3」を使って、XML出力に切り替えます

```elixir:lib/cabocha.ex
defmodule Cabocha do
	def view( body ), do: xml( body )
	def xml( body ),  do: if body == "" || body == nil, do: "", else: "echo #{body} | cabocha -f3" |> execute
　…
```

ブラウズをリロードすると、以下のようなXML表示に切り替わります

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/4708e5d8-ea19-c68d-aaa6-4eb49e85cdc8.png)

# CaboCha XMLをパターンマッチしやすい形に変換

XMLのままだと、Elixirで処理しにくいので、「xml_parser」モジュールを使って変換します

mix.exsにxml_parserモジュールのエントリーを追加します

```elixir: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" }, 
     { :xml_parser, "~> 0.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
```

xml_parser.parseを使って、XMLからパターンマッチしやすい形に変換します

```elixir:lib/cabocha.ex
defmodule Cabocha do
	def parse( body \\ "10年後、ブラックホールの謎を応用した、重力のプログラミングが可能となる" ) do
		case body == "" || body == nil do
			true  -> ""
			false ->
				body
				|> xml
				|> XmlParser.parse
		end
	end
　…
```

<strike>そのままPhoenixで表示できないので、</strike>コンソールで確認します
↑
5/8補足：inspect()使えばPhoenix上でも見れます

```
# iex -S mix phoenix.server
iex> Cabocha.parse
{:sentence, nil,
 [{:chunk,
   %{func: "3", head: "3", id: "0", link: "6", rel: "D", score: "-1.514907"},
   [{:tok, %{feature: "名詞,数,*,*,*,*,1,イチ,イチ", id: "0"}, "1"},
    {:tok, %{feature: "名詞,数,*,*,*,*,0,ゼロ,ゼロ", id: "1"}, "0"},
    {:tok,
     %{feature: "名詞,接尾,助数詞,*,*,*,年,ネン,ネン", id: "2"},
     "年"},
    {:tok, %{feature: "名詞,接尾,副詞可能,*,*,*,後,ゴ,ゴ", id: "3"},
     "後"},
    {:tok, %{feature: "記号,読点,*,*,*,*,、,、,、", id: "4"}, "、"}]},
  {:chunk,
   %{func: "6", head: "5", id: "1", link: "2", rel: "D", score: "1.513072"},
   [{:tok,
     %{feature: "名詞,固有名詞,一般,*,*,*,ブラックホール,ブラックホール,ブラックホール",
       id: "5"}, "ブラックホール"},
　…（略）…
    {:tok,
     %{feature: "動詞,自立,*,*,五段・ラ行,基本形,なる,ナル,ナル",
       id: "18"}, "なる"}]}]}
```

これで、文章の構成を解析できるようになったので、いよいよ[次回は、AIらしく、文脈から意味を読み取る](http://qiita.com/piacere/items/c947f1074edd1ab7241f)（といってもカンタンな解釈ですが）ようなロジックを作っていきます :rocket: