Node.js 用モジュールmecab-asyncを利用することで手軽に形態素解析を行うことが出来ます。
解析結果から品詞の抽出をすることで、Botのオウム返しが自然になるのではないかと思い試してみました。
環境
- Mac OSX10.11.5
- Node.js v6.2.0
- mecab 0.996
- mecab-ipadic-neologd 2.0
チャットのフレームワーク
express
とsocket.io
を利用してまずはシンプルに投稿をオウム返しさせてみます。
{
"dependencies": {
"socket.io": "^1.4.6",
"express": "^4.13.4"
}
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');
});
<!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を使わせて頂きました。
「こんにちは、サミュエルLジャクソンです。」を形態素解析するとこの様な結果が得られます。
$ 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
を利用して感動詞と名詞を抽出してみます。
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);
}
});
});
実行してみます
$ node app.js
感動詞 : こんにちは
名詞 : サミュエル・L・ジャクソン
今回は辞書を生成するのに適しているということで、map
クラスを利用してみました。これを使うとキーを利用して任意のデータを取得することが可能になります。
解析結果(ここではmorphs
)の配列の中から、キーlexical
を指定する事で指定した品詞の形態素を抽出する事が出来ました。
さて、これを利用してチャット投稿を形態素解析してみる事にします。
品詞抽出してBotに返信させてみる
今回は「感動詞」と「固有名詞」、「自立形容詞」を抽出してBotのオウム返しに変化をつけてみたいと思います。
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
に格納しています。
$ node app.js
listening on *:3000
[ 'こんにちは' ]
[ '妖怪ウォッチ' ]
[ '妖怪ウォッチ', 'アイカツ!' ]
[ 'ジバニャン' ]
[ 'かわいいよ' ]
[ '欲しい' ]
[ 'うん' ]
[ 'うん', 'さようなら' ]
[ 'さよなら' ]
今回はarray
に格納した配列をランダムで返信させてみました。
「感動詞」(例:こんにちは)を含む際は感動詞を返信。(例:「ポンディ こんにちは」→「こんにちは!」)
「固有名詞」(例:妖怪ウォッチ・アイカツ!)を含む際は固有名詞に「っていいよね。」をつけて返信。(例:「私ね 妖怪ウォッチとアイカツが好きなの」→「アイカツ!っていいよね。」)
「自立形容詞」(例:欲しい)を含む際は自立形容詞に「って楽しそうだね」をつけて返信。(例:「ぬいぐるみが欲しいの」→「欲しいって楽しそうだね。」)
単純なオウム返しと比較すると、少し自然な相槌になっている様な気がします。
まとめ
今回は子供がゆるキャラのぬいぐるみに話しかける様な場面を思いながら返信を書いてみました。簡単な雑談ならこれ位シンプルでも成り立つ様な気もします。(すぐ飽きちゃうだろうけど)
品詞抽出の条件や返答を工夫すればもう少し雑談Botらしくなるかもしれませんね。では。