Node.jsとMeCabで品詞抽出してBotに返信させてみる。

Node.js 用モジュール[mecab-async](https://www.npmjs.com/package/mecab-async)を利用することで手軽に形態素解析を行うことが出来ます。

解析結果から品詞の抽出をすることで、Botのオウム返しが自然になるのではないかと思い試してみました。

## 環境

- Mac OSX10.11.5
- Node.js v6.2.0
- mecab 0.996
- mecab-ipadic-neologd　2.0

## チャットのフレームワーク

`express`と`socket.io`を利用してまずはシンプルに投稿をオウム返しさせてみます。

![pondy 2016-07-17 14-15-16.png](https://qiita-image-store.s3.amazonaws.com/0/47128/90d421fc-b83a-27ab-e1d2-b4d9e95dc070.png)


```json:package.json
{
  "dependencies": {
    "socket.io": "^1.4.6",
    "express": "^4.13.4"
  }

```
```js:app.js
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);

app.get('/', function(req, res){
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', function(socket){
   socket.on('chat message', function(msg){

  　var your_reply = 'あなた > ' + msg;
  　io.emit('chat message', your_reply);
    var bot_reply = 'ポンディ > ' +  msg + 'だボット';
    io.emit('chat message', bot_reply);

});
});

http.listen(process.env.PORT || 3000, function(){
  console.log('listening on *:3000');
});

```
```html:index.html
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>pondy</title>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      body { font: 13px Helvetica, Arial; }
      #title { background: #fff; padding: 10px; position: fixed; top: 0; width: 100%; }
      form { background: #ddd; padding: 20px 10px 10px; position: fixed; bottom: 0; width: 100%; }
      #messages { list-style-type: none; margin-top: 6em;}
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(odd) { background: #eee; }
      #footer {margin-top:20px;}
    </style>
  </head>
  <body>
    <h1 id="title">pondy</h1>
    <ul id="messages"></ul>
    <form action="">

      <div class="form-group">
      <input id="m" autocomplete="off"　type="text" class="form-control" placeholder="メッセージを入力">
      </div>
      <button type="submit" value="talk"  class="btn btn-default">送信</button>
      <br>
      <p id="footer" class="small">© 2016 PonDad All Right Reserved.</p>
    </form>

    <script src="https://cdn.socket.io/socket.io-1.2.0.js"></script>
    <script src="http://code.jquery.com/jquery-1.11.1.js"></script>
    <script>
      var socket = io();
      $('form').submit(function(){
        socket.emit('chat message', $('#m').val());
        $('#m').val('');
        return false;
      });
      socket.on('chat message', function(msg){
        $('#messages').append($('<li>').text(msg));
      });
    </script>

  </body>
</html>

```

## mecab-async で品詞を抽出する

MeCab辞書は[mecab-ipadic-NEologd](https://github.com/neologd/mecab-ipadic-neologd)を使わせて頂きました。

「こんにちは、サミュエルLジャクソンです。」を形態素解析するとこの様な結果が得られます。

```bash:terminal
$ node app.js
[ { kanji: 'こんにちは',
    lexical: '感動詞',
    compound: '*',
    compound2: '*',
    compound3: '*',
    conjugation: '*',
    inflection: '*',
    original: 'こんにちは',
    reading: 'コンニチハ',
    pronunciation: 'コンニチワ' },
  { kanji: '、',
    lexical: '記号',
    compound: '読点',
    compound2: '*',
    compound3: '*',
    conjugation: '*',
    inflection: '*',
    original: '、',
    reading: '、',
    pronunciation: '、' },
  { kanji: 'サミュエルLジャクソン',
    lexical: '名詞',
    compound: '固有名詞',
    compound2: '一般',
    compound3: '*',
    conjugation: '*',
    inflection: '*',
    original: 'サミュエル・L・ジャクソン',
    reading: 'サミュエルエルジャクソン',
    pronunciation: 'サミュエルエルジャクソン' },
  { kanji: 'です',
    lexical: '助動詞',
    compound: '*',
    compound2: '*',
    compound3: '*',
    conjugation: '特殊・デス',
    inflection: '基本形',
    original: 'です',
    reading: 'デス',
    pronunciation: 'デス' },
  { kanji: '。',
    lexical: '記号',
    compound: '句点',
    compound2: '*',
    compound3: '*',
    conjugation: '*',
    inflection: '*',
    original: '。',
    reading: '。',
    pronunciation: '。' } ]
```

`mecab-async`を利用して感動詞と名詞を抽出してみます。

```js:app.js
var MeCab = new require('mecab-async')
var mecab = new MeCab();
    MeCab.command = "mecab -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd"
    var text = 'こんにちは、サミュエルLジャクソンです。'
    //注：パースコマンドを利用する時 "MeCab.~"と大文字にしないと動かないみたいです
    MeCab.parseFormat(text, function(err, morphs) {
        if (err) throw err;
        morphs.map(function(morph) {
        if (morph.lexical === '感動詞') {
          console.log(morph.lexical + ' : ' + morph.original);
        }
        if (morph.lexical === '名詞') {
          console.log(morph.lexical + ' : ' +morph.original);
        }
    });
    });

```

実行してみます

```bash:terminal
$ node app.js
感動詞 : こんにちは
名詞 : サミュエル・L・ジャクソン
```

今回は辞書を生成するのに適しているということで、`map`クラスを利用してみました。これを使うとキーを利用して任意のデータを取得することが可能になります。

解析結果（ここでは`morphs`）の配列の中から、キー`lexical`を指定する事で指定した品詞の形態素を抽出する事が出来ました。

さて、これを利用してチャット投稿を形態素解析してみる事にします。

## 品詞抽出してBotに返信させてみる

今回は「感動詞」と「固有名詞」、「自立形容詞」を抽出してBotのオウム返しに変化をつけてみたいと思います。

![pondy 2016-07-17 14-11-14.png](https://qiita-image-store.s3.amazonaws.com/0/47128/26d01c99-b1fe-23a7-670a-a054d676af38.png)

```js:app.js
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var MeCab = new require('mecab-async')
var mecab = new MeCab();
var array = [];
var reply = [];

app.get('/', function(req, res){
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', function(socket){
   socket.on('chat message', function(msg){

  　var your_reply = 'あなた > ' + msg;
  　io.emit('chat message', your_reply);

  　MeCab.command = "mecab -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd"
    MeCab.parseFormat(msg, function(err, morphs) {
        if (err) throw err;
        morphs.map(function(morph) {
        if (morph.lexical === '感動詞') {
          array.push(morph.original);
          reply = '!'
          console.log(array);
        }
        if (morph.compound === '固有名詞') {
          array.push(morph.original);
          reply = 'っていいよね。'
          console.log(array);
        }
        if (morph.lexical === '形容詞' | morph.compound === '自立') {
          array.push(morph.original);
          console.log(array);
          reply = 'って楽しそうだね。'
        }
    });
    if(array.length <= 0){
      bot_reply = 'ポンディ > そうなんだ。' ;
    }else{
      bot_reply = 'ポンディ > ' +  array[Math.floor(Math.random() * array.length)] + reply;
    }
    io.emit('chat message', bot_reply);
    array = [];
    reply = [];
    });
});
});

http.listen(process.env.PORT || 3000, function(){
  console.log('listening on *:3000');
});

```

シンプルチャットの`app.js`を置き換えます。ターミナルで確認すると投稿から指定した品詞（ここでは「感動詞」・「固有名詞」・「自立形容詞」）を抽出しているのが分かります。

複数同じ品詞が含まれることもあるので、ここでは抽出した形態素を`push`を利用して変数`array`に格納しています。

```bash:terminal
$ node app.js
listening on *:3000
[ 'こんにちは' ]
[ '妖怪ウォッチ' ]
[ '妖怪ウォッチ', 'アイカツ!' ]
[ 'ジバニャン' ]
[ 'かわいいよ' ]
[ '欲しい' ]
[ 'うん' ]
[ 'うん', 'さようなら' ]
[ 'さよなら' ]

```

今回は`array`に格納した配列をランダムで返信させてみました。

「感動詞」（例：こんにちは）を含む際は感動詞を返信。(例：「ポンディ　こんにちは」→「こんにちは！」)

「固有名詞」（例：妖怪ウォッチ・アイカツ！）を含む際は固有名詞に「っていいよね。」をつけて返信。（例：「私ね　妖怪ウォッチとアイカツが好きなの」→「アイカツ!っていいよね。」）

「自立形容詞」（例：欲しい）を含む際は自立形容詞に「って楽しそうだね」をつけて返信。（例：「ぬいぐるみが欲しいの」→「欲しいって楽しそうだね。」）

単純なオウム返しと比較すると、少し自然な相槌になっている様な気がします。

## まとめ

今回は子供がゆるキャラのぬいぐるみに話しかける様な場面を思いながら返信を書いてみました。簡単な雑談ならこれ位シンプルでも成り立つ様な気もします。（すぐ飽きちゃうだろうけど）

品詞抽出の条件や返答を工夫すればもう少し雑談Botらしくなるかもしれませんね。では。

## 参考にさせていただきました

[Node.jsでmecab-ipadic-NEologdを使う - knjcode blog](https://blog.knjcode.com/neologd-on-nodejs/)

[Botと対話する - SlideShare](http://www.slideshare.net/hoto17296/bot-47071519)