かえるのプログラミングブログ

プログラミングでつまずいたところとその解決策などを書いていきます。

kaggle の discussion の upvote downvote 予測をしてみた

おはようございます、かえるるるです。

【 こちらは 「kaggle Advent Calendar 2019」 の6日目の記事です https://qiita.com/advent-calendar/2019/kaggle

皆さま、楽しい kaggle life を送れておりますでしょうか。

kaggle には Competition tier の他にもコミュニティへの貢献度に応じて Notebook, Discussion, Dataset でもメダルを獲得し、その数に応じた tier を獲得することができる制度があり、コンペの精度を競う以外の楽しみ方もできます。

特に discussion はコンペのスコア向上に有益な情報が多く共有されています。

kaggle を始めてそろそろ 1年、もらうばかりではなく自分も何かしらの情報を公開して、コミュニティ貢献したいところです。

しかしながら、豆腐メンタルの私が不得意ながらひねり出した英語力で綴った内容のポストに downvote がついてしまった暁には、悲しみのあまり、夜しか眠れなくなってしまいかねません。

そこで、前置きが長くなってしまいましたが、kaggle の discussion の upvote downvote 予測をしてみたという内容です。 高い精度で予測できれば、ポストする前に自作の model に予測させて、downvote 判定がつきそうなら言い回しを変えるなどの対策が取れるようになり、高い心理的な安全性を得ることができます。

参考までに、こちらの記事に先立って、先日行われた分析コンペLT会 で同じデータセットを使用し、別のタスクを解いてみたのですが、text のみではうまく学習させることができませんでした。(accuracy 51% ほど)

単語自体の意味というよりは、usefil links や数式が多かったりなどの情報や、誰が投稿したかや、そもそもそのスレッドが盛り上がっているかなどの要素が影響しそうだという示唆が得られましたので、本ポストでは、そのような特徴量も使用してみて text のみの場合とどのように差がつくか、見ていこうと思います。

もくじ

  1. 分析コンペ LT 会での LT発表の紹介 -- play with kaggle discussion's text data

  2. 実験の結果

  3. 実験の概要

 ・使用したデータセットについて

 ・前処理

 ・行った特徴量エンジニアリングについて

 ・学習について

  ・BERT -- only text feature

  ・Logistic Regression -- text and meta feature

  1. 考察

  2. まとめ

分析コンペ LT 会での LT発表の紹介

こちらに貼っておりますのでご覧になって(可能であればツッコミを入れて)いただけると嬉しいです :)

www.slideshare.net

実験の結果

まず今回行った実験の結果を示します

\ BERT Logistic Regression
val accuracy 0.5044483985765125 0.6040925266903915
test accuracy 0.5081850533807829 0.599288256227758
特徴量 テキストのみ テキスト + 特徴量エンジニアリング

今回私が行った実験では、テキストのみを入力とした BERT に 10ポイントもの大差をつけて、特徴量エンジニアリングを行いテキストとテキスト以外の特徴量も入力に使用した Logistic Regression のモデルがより upvote と downvote を予測できるという結果になりました。

実験の概要

i ) 使用したデータ

Meta kaggle で提供されているデータを使用しました。 こちらは、kaggle 運営が毎日アップデートしているデータセット群で、我々 kaggler にとっては馴染みの深い、コンペのメタ情報や、discussion の内容、誰から誰への発言かなどのデータがあります。

今回使用する discussion に関連するデータは forum ~ .csv という名前で格納されています。 複数テーブルに分割されてあったので、私の公開カーネル で結合処理 (カラムの説明などないので key は当てずっぽうなので変なところがあるかもです。。) を施して kaggle_discussion_df.csv として使用しました。

以下のようなイメージです。

カラム名 概要 使えそう感(実験前)
CompetitionsTitle タイトル -
ForumTitle タイトル 有力な情報がありそうだったら閲覧が増えそう
ForumTopicTitle タイトル 有力な情報がありそうだったら閲覧が増えそう
PostUserId ポストした人 GM はいいこと言いそう
Message コメント 重要
TotalViews 閲覧数 見てる人が多いと。。
Score upvote と downvote の差 今回の目的変数の作成に使用
Medal メダル スコアを加工して目的変数を作成するのでリーク対策で削除
TotalMessages 全メッセージ数(コンペごと) 結合処理間違えたかも
TotalReplies ぶら下がっている投稿数 有力
CompetitionTypeId 不明, 画像コンペ, NLP, tabular と思ったら違った
OnlyAllowKernelSubmissions コードコンペか否か 興味本位で採用
EvaluationAlgorithmAbbreviation 評価指標 複雑なメトリックだと 有益な書き込み増えそう
TotalTeams 全参加チーム数 人が多いと書き込みも増える
TotalCompetitors 全参加者数 同上
TotalSubmissions 全サブ数 同上

i i ) 目的変数とデータセット情報

・データ総数は 208569 件

df = df[df['Score']!=0].reset_index(drop=True)
df['up_or_down'] = np.where(df['Score'] > 0, 1, 0)

down = df[df['up_or_down']==0]
up = df[df['up_or_down']==1].sample(3500, random_state=SEED)

train = pd.concat([down, up], sort=True)

・score 0 が全データの 22% ほどあったので削除し、スコアがついたものだけを使用 ・そのうちマイナスのスコアがついたものが 3500件しかなかったため、正例はランダムダウンサンプルし、 合計 7025 件のデータで検証. ・train : val : test = 4496 : 1124 : 1405 で分割. (stratify=y)

i i i ) 前処理

・'< p >', '< / p >' などのタグ

・' & lt ;', '& gt ;' などの特殊文字

・'span', 'style', 'href' など正規表現芸の力不足ゆえ残ってしまったものたち

・"i'd": 'i would' の変換

・url の削除

を行いました。

i v ) 特徴量エンジニアリング

def make_features(df):
    df = df.fillna(" ")
    # 文字数や単語数 (情報量が多い) 方が upvote 多い?
    df['num_chars'] = df["Message"].apply(len)
    df['num_unique_words'] = df["Message"].apply(lambda comment: len(set(w for w in comment.split())))
    df['num_words'] = df["Message"].apply(lambda x: len(x.split()))
    # CV スコアや LB スコアを載せてるものは upvote が多い?
    df['num_num'] = df["Message"].progress_apply(lambda x: sum(x.count(w) for w in '1234567890'))
    for keyword in ['lb', 'cv', 'cross validation', 'fold']:
        df[keyword + '_num_words'] = df["Message"].progress_apply(lambda x: x.count(keyword))

    # useful links のような url を多く載せてるものも upvote が多い?
    df['num_urls'] = df["Message"].progress_apply(lambda x: get_url_num(x))
    return df

思いついた特徴量をえいやで実装しました。

また、Logistic Regression にテキスト特徴を投入する際、kaggle の NLP コンペのスターターコードによく見られるような、 Tfidf --> SVD で 200次元に圧縮したものを使用しました。

v ) 学習について

・BERT --> BERT learge, 最大系列長 220, BCEWithLogitsLoss

・Logistic Regression --> sklearn.linear_model のものでハイパーパラメータは公開カーネルから拝借してきました

考察

分析コンペLT 会でも考察したように単語の意味のみでは discussion に upvote がつくか downvote がつくかを判別するのが困難であるらしい。

一方で url を含むかどうかやテキストの長さ、また私がコンペに参加する中でキーワードっぽいと決め打った単語の含有数といった簡単な特徴を加えるだけでシンプルな LR が BERT よりも良い精度で予測できるようになりました。

以下は LR の coef です。

こちらのブログ によると、

ただ、プラスであればその特徴量は1の方向に、反対にマイナスなら0の方向に働く。

という事なので、私のお手製の温かみのある特徴量たちは、モデルが 0 (downvote) を予測するのに役に立ったようです。

f:id:kaeru_nantoka_py:20191206042801p:plain

まとめ

・discussion を見ていてもなぜ downvote がついているのだろうと思うような投稿もあるように、単語の持つ意味のみで vote の種類を予測するのは sota sota の実を食べた sota 人間の BERT でも困難なようです。

・一方で我々の日頃の感覚を反映した特徴量エンジニアリングで生み出した特徴量たちは見事にいい感じに決定境界を引くのに役立ってくれました。

・とはいえ精度 60 % では自信を持って upvote がつきそうな投稿に絞ってポストするといったことはできなさそうです。無念。

以上です、ありがとうございました🐸

[追記] ポスト公開後、Twitter などを通して、 「それ Leak してませんか??」のお声をいくつかいただきました。 具体的には、TotalViews, TotalMessages, TotalReplies などは、投稿をするタイミングでは得られない情報であり、今回の目的である 「投稿する時点で予測器にかけてポストするか否か判断する」には適さない処理でした。 今後ともどうぞよろしくお願いいたします。