u++の備忘録

言語処理100本ノック 2020「53. 予測」

問題文

nlp100.github.io

問題の概要

学習を終えたモデルは、予測値が未知の特徴量(X_test)を与えて予測させることができます。

import pandas as pd
from sklearn.linear_model import LogisticRegression


X_train = pd.read_table('ch06/train.feature.txt', header=None)
y_train = pd.read_table('ch06/train.txt', header=None)[1]

clf = LogisticRegression(penalty='l2', solver='sag', random_state=0)
clf.fit(X_train, y_train)
y_train = clf.predict(X_train)

言語処理100本ノック 2020「52. 学習」

問題文

nlp100.github.io

問題の概要

用意した特徴量と予測の対象のペアから、機械学習アルゴリズムを用いて予測器を学習させましょう。

f:id:upura:20200726000350p:plain

import pandas as pd
import joblib
from sklearn.linear_model import LogisticRegression


X_train = pd.read_table('ch06/train.feature.txt', header=None)
y_train = pd.read_table('ch06/train.txt', header=None)[1]

clf = LogisticRegression(penalty='l2', solver='sag', random_state=0)
clf.fit(X_train, y_train)
joblib.dump(clf, 'ch06/model.joblib')

言語処理100本ノック 2020「51. 特徴量抽出」

問題文

nlp100.github.io

問題の概要

カテゴリ分類に有用そうな特徴量を抽出します。ここでは、問題文の指示通りの最低限の特徴量を作ります。sklearnに用意されている「CountVectorizer()」が利用可能です。

記事の見出しを単語列に変換したものが最低限のベースラインとなるであろう.

データセット内に「TMP」という一時的なカラムを作成し、X_train・X_valid・X_testを結合しておくことで、特徴量抽出の処理を一度で済ましています。

import joblib
import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer


X_train = pd.read_table('ch06/train.txt', header=None)
X_valid = pd.read_table('ch06/valid.txt', header=None)
X_test = pd.read_table('ch06/test.txt', header=None)
use_cols = ['TITLE', 'CATEGORY']
X_train.columns = use_cols
X_valid.columns = use_cols
X_test.columns = use_cols
X_train['TMP'] = 'train'
X_valid['TMP'] = 'valid'
X_test['TMP'] = 'test'

data = pd.concat([X_train, X_valid, X_test]).reset_index(drop=True)
vectorizer = CountVectorizer(token_pattern=u'(?u)\\b\\w+\\b')
bag = vectorizer.fit_transform(data['TITLE'])
data = pd.concat([data, pd.DataFrame(bag.toarray())], axis=1)

joblib.dump(vectorizer.vocabulary_, 'ch06/vocabulary_.joblib')

X_train = data.query('TMP=="train"').drop(use_cols + ['TMP'], axis=1)
X_valid = data.query('TMP=="valid"').drop(use_cols + ['TMP'], axis=1)
X_test = data.query('TMP=="test"').drop(use_cols + ['TMP'], axis=1)

X_train.to_csv('ch06/train.feature.txt', sep='\t', index=False, header=None)
X_valid.to_csv('ch06/valid.feature.txt', sep='\t', index=False, header=None)
X_test.to_csv('ch06/test.feature.txt', sep='\t', index=False, header=None)

言語処理100本ノック 2020「50. データの入手・整形」

問題文

nlp100.github.io

問題の概要

本章では、ニュース記事の見出しからカテゴリを分類する機械学習モデルを構築します。最初に指示に従ってデータセットを整形します。次の4段階で処理しました。

  1. ファイルのデータ形式の確認
  2. 情報源(publisher)が”Reuters”, “Huffington Post”, “Businessweek”, “Contactmusic.com”, “Daily Mail”の事例(記事)のみを抽出
  3. 抽出された事例をランダムに並び替え
  4. 抽出された事例の80%を学習データ,残りの10%ずつを検証データと評価データに分割し,それぞれtrain.txt,valid.txt,test.txtというファイル名で保存

1について、提供されているデータセットにはheaderがありません。そのため読み込み時には「header=None」を指定し、後に「pandas.DataFrame.columns」でカラム名を定義しています。

2については「pandas.Series.isin()」を利用しました。同時に「pandas.Series.sample(frac=1)」で値をランダムに並び替えています。「pandas.Series.sample()」はデータセットから指定した割合を抽出する処理で、抽出率を100%にすることでランダム並び替えと同等の処理になります。

後に機械学習アルゴリズムに投入するための前処理として、目的変数(予測の対象)のカラムの値は「map」を用いて数値に変換しています。

最後に4について「train_test_split()」を用いてデータセットを分割しました。「stratify」で目的変数の値を指定することで、分割後の目的変数の割合が等しくなるように設定しています。この分割方法は、機械学習アルゴリズムを検証する上で一般的です*1

import pandas as pd
from sklearn.model_selection import train_test_split


newsCorpora = pd.read_table('ch06/NewsAggregatorDataset/newsCorpora.csv', header=None)
newsCorpora.columns = ['ID', 'TITLE', 'URL', 'PUBLISHER', 'CATEGORY', 'STORY', 'HOSTNAME', 'TIMESTAMP']
newsCorpora = newsCorpora[newsCorpora['PUBLISHER'].isin(
    ['Reuters', 'Huffington Post', 'Businessweek', 'Contactmusic.com', 'Daily Mail'])].sample(frac=1, random_state=0)

X = newsCorpora[['TITLE', 'CATEGORY']].copy()
X['CATEGORY'] = X['CATEGORY'].map({'b': 0, 'e': 1, 't': 2, 'm': 3})
y = newsCorpora['CATEGORY']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=0)
X_valid, X_test, y_valid, y_test = train_test_split(X_test, y_test, test_size=0.5, stratify=y_test, random_state=0)

X_train.to_csv('ch06/train.txt', sep='\t', index=False, header=None)
X_valid.to_csv('ch06/valid.txt', sep='\t', index=False, header=None)
X_test.to_csv('ch06/test.txt', sep='\t', index=False, header=None)

「Sports Analyst Meetup #8」をオンラインで開催しました #spoana

「Sports Analyst Meetup #8」*1を、7月18日に開催しました。昨今の情勢を受け、7回目に引き続きのオンライン開催でした。

資料

spoana.connpass.com

togetter

togetter.com

発表内容

今回は10名の方にLTをしていただきました。いずれも素敵な内容で、多くの方が発表メモをブログに書いてくださっています。

yhiss.hatenablog.com

k-ptl.hatenablog.com

note.com

アーカイブ

オンライン開催の利点を活かして、発表者の許諾が得られた内容については、YouTubeアーカイブを掲載していく予定です。

www.youtube.com

また前回に引き続き、今回もログミーに取材して頂きました。前回の記事の第1弾は既に公開されていますので、ぜひご覧ください。

logmi.jp

おわりに

今回も多くの方にご参加いただき、誠にありがとうございました。今回も素敵な発表をしてくださった方々に、お礼申し上げます。

東西分割開催のJリーグ各チーム移動距離を可視化

新型コロナウイルス感染症の拡大防止のため第1節を終えた段階で中断していたJ1リーグは、7月4日に一斉再開しました。7月中は移動による感染リスクを避けるため近隣クラブが対戦する方式を採用しており、具体的には全18チームを東西に2分して各グループ内で対戦相手を決めています。

www.nikkei.com

次の記事で言及されている通り、序盤戦の移動距離が他チームに比べて少なく、スタートダッシュに有利な状況のチームが存在する可能性もあります。この記事では具体的な数字が出ていなかったので、本ブログで実際にデータに基づく可視化に取り組みます。

sportiva.shueisha.co.jp

結果

f:id:upura:20200711001731p:plain

縦軸の距離は、ヒュベニの公式に基づき座標から計算しました。名古屋グランパスなど複数のホームスタジアムを有している場合は、主に使われている方を採用しています。また本拠地と別の場所でキャンプを張っているなどの個別対応は考慮していません。

www.trail-note.net

当然ですが、札幌や九州のチームと戦うか否かの影響が大きくなりますね。

f:id:upura:20200711001745p:plain

おわりに

本記事では、東西分割開催のJリーグ各チーム移動距離を可視化しました。今後の課題としては、本来予定されていた2-7節のカードの移動距離と比較すると、差分が分かりやすくなりそうです。一連のコードは、下記で公開しています。

www.kaggle.com

ProbSpace「YouTube動画視聴回数予測」コンペ参加録

ProbSpaceで開催されていた「YouTube動画視聴回数予測」コンペに参加しました。Lain.さんとチームを組み、public 4位・private 6位でした。

f:id:upura:20200629002350p:plain

prob.space

コンペ概要

YouTube APIで取得できるメタデータを入力として、動画の視聴回数を予測するタスクでした。具体的には、下記のデータが利用できました。

項目名 説明
video_id 動画ごとに割り振られる一意なid
title 動画のタイトル
publishedAt 動画の投稿時間
channelId 動画を投稿したチャンネルのid
channelTitle チャンネルのタイトル
categoryId 動画カテゴリのid
collection_date データレコードの収集日
tags 動画に割り当てられたタグ`
likes 高評価の数
dislikes 低評価の数
comment_count コメント数
thumbnail_link 動画のサムネへのリンク
comments_disabled コメントが許可されない動画であるか? Trueの場合にはcomment_countは0となる
ratings_disabled 高評価と低評価が許可されない動画であるか? Trueの場合にはlikesとdislikesは0となる
description 動画の説明文

video_id, thumbnail_linkからはそれぞれ動画・サムネイル画像が取得でき、title, descriptionはテキストです。いわゆるマルチモーダルなデータが利用できるコンペでした。

取り組み

YouTubeという題材の身近さとマルチモーダルの技術的な面白さに興味をそそられ、終了10日前とギリギリでしたが参加を決めました。

その後にLain.さんとチームマージし、最終的にpublic 4位・private 6位となりました。ベストモデルは、私1モデルとLain.さん3モデルの合計4モデルの平均でした。

私の担当部分のソースコード一式はGitHubで公開しました。以下、概要を述べます。

github.com

特徴量

テーブル + テキストのtfidf*1&count*2 で特徴量を作りました。

テキストからはBERT*3、画像からはEfficientNet*4で特徴抽出しましたが、性能に寄与しませんでした。

モデル

画像を用いたEfficientNetのfine tuningも試しましたがCVが悪かったので、ニューラルネットワークは諦め勾配ブースティング系のモデル(LightGBM*5, CatBoost*6)を使いました。Pseudo Labeling でCV & public lbスコアが伸びました。

CV戦略

生の特徴量のみを用いたLightGBMモデルのfeature importanceで上位に来ていたratings_disabledを対象にしたStratifiedKFold(n_splits=5, shuffle=True, random_state=7)に変更して、CV & public lbスコアが伸びました。

おわりに

まずはチームを組んでくださったLain.さんにお礼申し上げます。

個人的な反省点は、マルチモーダルを扱うニューラルネットワークで全然lossが落ちなかったことです。結局は特徴抽出してLightGBMに突っ込む定番の解法になってしまいました。とはいえEfficientNetなどを実践で試せた経験を生かして、次回以降のコンペの糧としていきます。

せっかくのYouTubeコンペなので、この記事についての動画をYouTubeで公開しました。