これまでのあらすじ
第1話: つらそうな語句を含むツイートをした時に「にゃーん」と変換されるスクリプトができた
第2話: Mecab による形態素解析と日本語極性辞書を組み合せてツイートを解析。スコアが低くなった時に「わーい!」「すごーい!」と変換されるスクリプトができた
今回からは機械学習の力を借りてより実用的な社会性フィルターを目指します。
初めは前回のコメントでいただいたように Theano を用いて自前でこさえようと思ったのですが、調べてみるとすでにいくつかネガポジ判定の Web-API があることを知り、まずはそっちを試してみようということになりました。
1. indico
3行のソースコードを入れるだけで機械学習できると噂のindicoをNode.jsで使って機械学習入門してみる / Qiita という記事で知った Web-API サービス。Python だと本当に3行の実装で処理できるらしい。すごい。
しかも Sentiment Analysis の API 紹介の欄には
Languages: English (en), Chinese (zh), German (de), Spanish (es), French (fr), Italian (it), Japanese (ja), Russian (ru), Arabic (ar), Dutch (nl), Korean (ko), Portuguese (pt)
しれっと日本語対応を謳っております。こりゃ試してみるっきゃねえ!
さっそく Node.js から API を叩いてみます。結果は 0−1 の間で、1であればあるほどポジティブということになっています。
スクリプト
var indico = require('indico.io');
indico.apiKey = 'ここにAPIKeyを入れる';
module.exports = function (texts, callback) {
// single example
indico.sentimentHQ(texts)
.then(function (res) {
return callback(null, res);
})
.catch(function (err) {
console.log(err);
return callback(err, null);
});
}
const indico = require('./indico.js');
const text1 = 'ともだちが増えるよ!やったねたえちゃん!' // 多分 Positive
const text2 = '今日も1日仕事で疲れたけどビールが美味い' // Positive になってほしい
const text3 = 'どうしてあいつのために俺がこんなことやらされてるんだ、死んでくれ' // Negative
const text4 = 'I\'m so happy.' // Positive
const text5 = 'I\'m so sad.' // Negative
function getScore(text) {
indico(text, function (err, score) {
if (!err) {
console.log(text + ': ' + score);
}
});
}
getScore(text1);
getScore(text2);
getScore(text3);
getScore(text4);
getScore(text5);
結果
$ node app.js
I'm so happy.: 0.9948094487
I'm so sad.: 0.3294415176
どうしてあいつのために俺がこんなことやらされてるんだ、死んでくれ: 0.5633969307000001
今日も1日仕事で疲れたけどビールが美味い: 0.5633969307000001
ともだちが増えるよ!やったねたえちゃん!: 0.5633969307000001
……?なんかおかしいな……?
ちなみに数日前に叩いたときも
やたらと ”0.5633969307000001” というポイントが頻出しています。
どうやら indico の 感情分析 API は日本語対応を謳っておきながら日本語を入れるとおかしくなるっぽい。
これでは仕方ないので、一度英語に変換してから叩いてみます。
変換には Microsoft のものを使用しました。(初めて使った時の記事はこちら)
スクリプト・改
const request = require('request');
// アクセストークン取得
function getAccessToken(callback) {
let headers = {
'Content-Type': 'application/json',
'Accept': 'application/jwt',
'Ocp-Apim-Subscription-Key': 'ここにAPIKey'
};
let options = {
url: 'https://api.cognitive.microsoft.com/sts/v1.0/issueToken',
method: 'POST',
headers: headers,
json: true
};
request(options, function (err, res) {
if (err) {
console.log(err);
callback(err, null);
} else
callback(null, res.body);
});
}
// 翻訳 (日本語 -> 英語)
function translate(token, text, callback) {
let base_url = 'https://api.microsofttranslator.com/v2/http.svc/Translate',
appid = 'Bearer ' + token,
from = 'ja',
to = 'en';
let url = base_url + '?appid=' + appid +
'&text=' + text + '&from=' + from + '&to=' + to;
let headers = {
'Accept': 'application/xml'
};
let options = {
url: encodeURI(url),
method: 'get',
headers: headers,
json: true
};
request(options, function (err, res) {
if (err) {
console.log(err);
callback(err, null);
} else
callback(null, res.body.replace(/<("[^"]*"|'[^']*'|[^'">])*>/g, ''));
});
}
// 実行
module.exports = function (text, callback) {
getAccessToken(function (err, token) {
if (!err) {
// console.log(token);
translate(token, text, (err, translated) => {
if (err) {
console.log(err);
return callback(err, null);
} else
return callback(null, translated);
});
}
});
}
const indico = require('./indico.js');
const trans = require('./translate.js');
const text1 = 'ともだちが増えるよ!やったねたえちゃん!' // 多分 Positive
const text2 = '今日も1日仕事で疲れたけどビールが美味い' // Positive になってほしい
const text3 = 'どうしてあいつのために俺がこんなことやらされてるんだ、死んでくれ' // Negative
function getScore(text) {
trans(text, function (err, res) {
if (!err) {
indico(res, function (err, score) {
if (!err) {
console.log(text + ': ' + res + ': ' + score);
}
});
}
});
}
getScore(text1);
getScore(text2);
getScore(text3);
結果
$ node app.js
ともだちが増えるよ!やったねたえちゃん!: More my friend! Did it crunchy-CHAN!: 0.9197340012
今日も1日仕事で疲れたけどビールが美味い: Delicious beer today is tired at work one day but: 0.9261898994000001
どうしてあいつのために俺がこんなことやらされてるんだ、死んでくれ: Why I'm for him to die, and I've already been made: 0.6456769109
”crunchy-CHAN!” にツボったのは置いといて、うーむ。
あまり納得がいかない判定です。
英訳もある程度は仕方ないのでしょうが……。
indico 総評
API 自体は使えなくはないし、むしろ便利。
月5万回まで無料でコールできてネガポジ分析以外に種類も豊富。
ただ日本語が(対応を謳っているにもかかわらず)うまく扱えてないのと、ツイート的な文体だとどうしても精度が出ないのが悩みどころか。
2. 日本語極性判定API
続いては
日本語極性判定を作って公開した / Qiita
こちらの記事で知った API を使ってみます。結果はポジティブ・ネガティブ・ニュートラルそれぞれの割合が返ってきて、レスポンス(JSON)の predict
の中に最も値が高い分類とその割合が入っています。
スクリプト
const apiKey = 'ここにAPIKey';
const request = require('request');
function getScore(text, callback) {
let base_url = 'https://api.apitore.com/api/11/sentiment/predict';
let url = base_url + '?access_token=' + apiKey + '&text=' + text;
let headers = {
'Accept': 'application/json'
};
let options = {
url: encodeURI(url),
method: 'get',
headers: headers,
json: true
};
request(options, function (err, res) {
if (err) {
console.log(err);
callback(err, null);
} else {
let pn_result = res.body.predict.sentiment,
score = res.body.predict.score;
callback(null, [pn_result, score]);
}
});
}
module.exports = function (text, callback) {
getScore(text, function (err, res) {
if (err)
return callback(err, null);
else
return callback(null, res);
});
}
const apitore = require('./apitore.js');
const text1 = 'ともだちが増えるよ!やったねたえちゃん!' // 多分 Positive
const text2 = '今日も1日仕事で疲れたけどビールが美味い' // Positive になってほしい
const text3 = 'どうしてあいつのために俺がこんなことやらされてるんだ、死んでくれ' // Negative
function getScore(text) {
apitore(text, function callback(err, res) {
if (!err)
console.log(text + ': ' + res);
});
}
getScore(text1);
getScore(text2);
getScore(text3);
結果
$ node app.js
今日も1日仕事で疲れたけどビールが美味い: positive,0.535163402557373
ともだちが増えるよ!やったねたえちゃん!: positive,0.916395366191864
どうしてあいつのために俺がこんなことやらされてるんだ、死んでくれ: negative,0.5646057724952698
なんか意図した感じに出てる!
日本語極性API 総評
テストは3つのみでしたが、予想に非常に近い結果が得られました。
このAPIはデモサイトで修正データを送ることができて、ある程度集まったら再学習してくれるようなので、もしこの記事を読んでくださった方がいれば是非ご協力をお願いします。(ぼくも気付いた時にやってます)
まとめ
社会性フィルターを作り上げていくにあたり、”形態素解析→評価辞書とのマッチング” から ”機械学習” へ処理方法を変更しようということになりましたが、自分でモデルを構築せずとも十分有用な Web-API が利用できることがわかりました。
次回は実際にこのAPIを用いて更なる実装に取り組んでいきたいと思います。
つづく!