続々・node.js で社会性フィルター

# これまでのあらすじ
[第1話](http://qiita.com/albno273/items/2e17fd2d89ceb58d9578): つらそうな語句を含むツイートをした時に「にゃーん」と変換されるスクリプトができた
[第2話](http://qiita.com/albno273/items/bcaf836a213b0f8d3add): Mecab による形態素解析と日本語極性辞書を組み合せてツイートを解析。スコアが低くなった時に「わーい！」「すごーい！」と変換されるスクリプトができた

今回からは機械学習の力を借りてより実用的な社会性フィルターを目指します。
初めは前回のコメントでいただいたように Theano を用いて自前でこさえようと思ったのですが、調べてみるとすでにいくつかネガポジ判定の Web-API があることを知り、まずはそっちを試してみようということになりました。

# 1. [indico](https://indico.io/)
[3行のソースコードを入れるだけで機械学習できると噂のindicoをNode.jsで使って機械学習入門してみる / Qiita](http://qiita.com/n0bisuke/items/868b43411078bc2018fb) という記事で知った 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)

<b>しれっと日本語対応を謳っております。</b>こりゃ試してみるっきゃねえ！
さっそく Node.js から API を叩いてみます。結果は 0−1 の間で、1であればあるほどポジティブということになっています。

## スクリプト
```js:indico.js
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);
        });
}
```

```js:app.js
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
```

……？なんかおかしいな……？
ちなみに数日前に叩いたときも

![2017-02-18](https://qiita-image-store.s3.amazonaws.com/0/163084/c6fd4894-39de-3b9c-5118-69f60b75f262.png)

やたらと ”0.5633969307000001” というポイントが頻出しています。
どうやら indico の 感情分析 API は日本語対応を謳っておきながら日本語を入れるとおかしくなるっぽい。

これでは仕方ないので、一度英語に変換してから叩いてみます。
変換には Microsoft のものを使用しました。(初めて使った時の記事は[こちら](http://qiita.com/albno273/items/c160d435ee3a2cc3fe5f))

## スクリプト・改
```js:translate.js
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);
            });
        }
    });
}
```

```js:app.js(改)
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](https://apitore.com/store/apis/details?id=11)
続いては
[日本語極性判定を作って公開した / Qiita](http://qiita.com/keigohtr/items/e85b413283b87ac02f4b)
こちらの記事で知った API を使ってみます。結果はポジティブ・ネガティブ・ニュートラルそれぞれの割合が返ってきて、レスポンス(JSON)の ``predict`` の中に最も値が高い分類とその割合が入っています。

## スクリプト
```js:apitore.js
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);
    });
}
```

```js:app.js
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は[デモサイト](https://mashup.apitore.com/social-sentiment/index.html)で修正データを送ることができて、ある程度集まったら再学習してくれるようなので、もしこの記事を読んでくださった方がいれば是非ご協力をお願いします。(ぼくも気付いた時にやってます)

# まとめ
社会性フィルターを作り上げていくにあたり、”形態素解析→評価辞書とのマッチング” から ”機械学習” へ処理方法を変更しようということになりましたが、自分でモデルを構築せずとも十分有用な Web-API が利用できることがわかりました。
次回は実際にこのAPIを用いて更なる実装に取り組んでいきたいと思います。

つづく！

# 参考にさせていただいた記事
- [Microsoft Translator Text APIをNode.jsから使ってみる / Qiita](http://qiita.com/n0bisuke/items/350752978b16f4429b31)
- [3行のソースコードを入れるだけで機械学習できると噂のindicoをNode.jsで使って機械学習入門してみる / Qiita](http://qiita.com/n0bisuke/items/868b43411078bc2018fb)
- [日本語極性判定を作って公開した / Qiita](http://qiita.com/keigohtr/items/e85b413283b87ac02f4b)