Slackメッセージの感情分析

前の記事に引き続き、Slackメッセージの感情分析を行います。

メッセージの収集

まずはSlackメッセージの収集からです。

Slackチャネル上に飛び交うメッセージをログングするボットを作成し、対象チャネルにinviteしてメッセージを収集します。

ボットはhubotで作りました。 ロギングするスクリプトは以下で、これを scripts/ディレクトリに配置しておきます。

=== logger.coffee ===

LOG_DIR = 'log'

fs = require('fs')
util = require('util')
dateFormat = require('dateformat')

unless fs.existsSync(LOG_DIR)
  fs.mkdirSync(LOG_DIR)

module.exports = (robot) ->
  robot.hear /.*/, (res) ->
    msg = res.match
    today = dateFormat(new Date(), "yyyymmdd")
    logfile = util.format('%s/%s.txt', LOG_DIR, today)

    fs.appendFile logfile, msg + "\n", 'utf8', (err) ->
      if err
        console.log(err)

このボットはメッセージをひっそりせっせとログファイルに保存し続けます。:-)

データセットの作成

続いて、ここが最も肝心なデータセットの構築です。

入力メッセージと出力ラベルの組をどうするか?

例えば、次のようにメッセージとリアクションのやり取りがあったとします。

f:id:sanshonoki:20171007074317j:plain

ログファイルには次のように記録されます。

ありがとうございます:angel: :smile:
:+1:

コメントに対するリアクション③(:tada:)は残念ながら記録されません.. ><

また、このボットが複数チャネルでメッセージ収集している場合、

ありがとうございます:angel: :smile:
何か他の話題のメッセージ
:+1:

と①と④の間に他の話題のメッセージが混入してくる場合があります。

なので、①と④を入力メッセージと出力ラベルの組とすることは難しく、①と②を入力とラベルの組としました。

データ数を増やすという意味では (①, ③)や(①, ④)の組み合わせも含めたかったです...

基本的なアルゴリズム
  1. メッセージ部分とラベル部分に分ける

    リアクション文字は :pray::sob:のように :何とか: というようになっています。 正規表現 ':[a-z\+]{1}[\w_-]+:' でこのパターンを取り出します。

    'ありがとうございます'[:angel:, :smile:] に分割します。

    正規表現のテストは https://regex101.com/ が役に立ちました。 f:id:sanshonoki:20171008070639p:plain

  2. MeCabでメッセージ部分を単語に分割する

    'ありがとうございます'['ありがとう', 'ござい', 'ます'] と3つの単語に分割されます

  3. メッセージとラベルの組を作る

    • メッセージ部分: ['ありがとう', 'ござい', 'ます']
    • ラベル部分: [:angel:, :smile:]

    から

    • ['ありがとう', 'ござい', 'ます'] -> :angel:
    • ['ありがとう', 'ござい', 'ます'] -> :smile:

    という2つの組を作ります

ラベルのマージ

最初は メッセージ -> リアクション という予測ができると面白いなと考えていましたが ラベルの数のばらつきが大きいため難しそうでした。

【理由】ラベル数の分布に大きな偏りがあると最も数が多いラベルのみを出力するようにネットワークが学習されてしまいます

なので、ラベルをマージして positive か negative かの2種類になるようにしました。

変換テーブルの例
元ラベル 変換後
f:id:sanshonoki:20171007083023p:plain:w20:smile: positive
f:id:sanshonoki:20171007083804p:plain:w20:tada: positive
f:id:sanshonoki:20171007083835p:plain:w20:sob: negative
f:id:sanshonoki:20171007084420p:plain:w20:scream: negative
f:id:sanshonoki:20171007084454p:plain:w20:bow: (なし)

f:id:sanshonoki:20171007084454p:plain:w40 は "ごめんなさい"(negative)の意味で使われること以外にも "よろしくお願いします" の意味でも使われるので マッピングなし としました。(このようなリアクションは:bow:以外にもいくつかあります)

マージすることで入力とラベルが同じになった組はさらに1つにまとめます。

学習

サンプル数

46449メッセージから学習用と検証用のデータセットを構築しました。

学習データセット(90%) 検証用データセット(10%)
1048 117

それぞれ positive と negative が 半々ずつ含まれています。

positive、negativeに分類できるリアクション絵文字を含む文 となるとかなり少なくなってしまいました。。⊂(¯×¯٥)⊃

パラメータ

前回やったときとパラメータを少し変えましたがそれ以外は同じです。

  • seq_len: 30
  • hidden_unit: 300

結果

5〜10 epoch学習して 65〜70%の正解率となりました。(データ数が少ないせいかばらつき大きいです) 同じラベルを常に出力する あるいは完全にランダムに出力する と 約50% になるはずなので一応は感情を捉えていると思います。

あらためて入力データを眺めてみると、人間が見ても positive なのか negative なのか 分からない入力もたくさんありました..。

判断が難しいメッセージの例
入力メッセージ 正解ラベル
冬 よ 来い positive
経由 で negative
待機 positive
つくら ね ば negative

今回はデータ数が少なくて精度がイマイチというのもありますがSlackコミュニケーションの特性上、文脈が複数に分かれたメッセージとしてやり取りされるのでこのまま単純にデータ数を増やすだけでは精度は改善しないかもしれません。。

しかし、、データ集めるの難しいですね。。

判定ボット

せっかくなのでボットで判定できるようにしました ƪ(•◡•ƪ)"

f:id:sanshonoki:20171008063956j:plain