u++の備忘録

テーブルデータ向けのGAN(TGAN)で、titanicのデータを増やす

はじめに

ynktk さんのツイート*1を見て、テーブルデータ向けの GAN の存在を知りました。本記事では、TGAN を用いて titanic のデータを拡張してみます。

TGANとは

テーブルデータに対応した GAN (Generative Adversarial Network, 敵対的生成ネットワーク) *2 です。数値などの連続変数だけではなく、カテゴリ変数にも対応しています。

Titanic のデータを増やす

今回は、著名なデータセットである Titanic のデータを対象にTGANを試します。

データの読み込み

まずはデータを読み込みます。データは Kaggle からダウンロードしました*3。下図のようなデータが格納されています。

df = pd.read_csv('input/train.csv')
df.head()

f:id:upura:20190820092059p:plain

行数は900弱です。

df.shape
(891, 12)

欠損値の削除

TGANは、欠損値に対応していません。最初に各カラムの欠損値の数を確認しておきます。

df.isnull().sum()
PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age            177
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         2
dtype: int64

欠損値が大半を占める 'Cabin' は、この段階で削除します。合わせて、GANでの増幅に不適切な ['PassengerId', 'Name', 'Ticket'] も削除しておきます。

df.drop(['Cabin', 'PassengerId', 'Name', 'Ticket'], axis=1, inplace=True)

その他の欠損値を含む2カラムについて、'Age' は平均値を四捨五入して整数にした値、'Embarked' は最頻値である 'S' で埋めました。

df['Age'].fillna(round(df['Age'].mean(), 0), inplace=True)
df['Embarked'].fillna(df['Embarked'].value_counts().index[0], inplace=True)

カラム名の保持

現在 PyPI でインストールできる TGAN (ver 0.1.0) には、実行後に DataFrame のカラム名がインデックス番号に置換されてしまう不具合があります*4。そのため、実行後のために事前にカラム名を変数に入れて保持しておく必要があります。

df_columns = df.columns

連続変数の指定

TGAN の実行時には、連続変数のカラムのインデックス番号一覧をリスト型で渡します。今回は、次のように float 型のカラムを抽出しました。

continuous_columns = [df.columns.get_loc(c) for c in df.select_dtypes(include=['float']).columns]

TGAN の実行

いよいよ TGAN を実行します。

from tgan.model import TGANModel
tgan = TGANModel(continuous_columns, batch_size=50)
tgan.fit(df)

このときdocsには記載がありませんが、小さめのbatch_sizeを引数に指定しないと、tensorpack の assertion error *5で実行が止まってしまいます。

実行時間は、900弱のデータセットで15分程度でした。学習済のモデルは、次のように保存可能です。

model_path = 'output/models/mymodel.pkl'
tgan.save(model_path)

サンプルの抽出

学習済のモデルから、次のようにデータを生成できます。今回は、元のデータセットと同数を指定しました。

num_samples = len(df)
samples = tgan.sample(num_samples)

f:id:upura:20190819161132p:plain

'Age' が小数点以下になっているので丸める処理などは必要かもしれませんが、'Sex' などカテゴリ変数も含めてデータが生成できていると分かります。目的変数である 'Survived' も、問題なく増幅されていました。

samples['Survived'].value_counts()
0    540
1    310

おわりに

本記事では、TGAN を用いて titanic のデータを拡張してみました。Kaggle などの文脈で言うと、学習用データの水増しに利用できる可能性があります。ただし、ynktk さんとも議論した通り*6、GAN でまともなデータを作るにはそもそも十分量のデータセットが必要というジレンマがありそうです。

TGAN で増やしたデータで性能が向上するかはデータセットと課題設定次第ですが、機会があれば試してみても面白いなと思いました。

今回の実装は GitHub *7で公開しています。

*1:

*2:papers.nips.cc

*3:www.kaggle.com

*4:github.com

*5:github.com

*6:

*7:github.com

【ネタバレ有】『アルキメデスの大戦』とデータ分析の仕事

はじめに

OsciiArtさんの下記のツイートで興味を持ち、観に行きました。本記事では、ネタバレ要素を含みつつ、徒然と感想を書いていこうと思います。


あらすじ

事前の触れ込み通り、まさに「データ分析」を題材にした映画で、主人公の天才数学者・櫂直が旧態依然の組織の中で奮闘します。

時は1933年、海軍では巨大戦艦の建造を巡った対立が発生していました。来たる戦争に向けて華美な巨大戦艦を建造したい「建造推進派」と、新世代の海戦を見据え機動力のある航空母艦を推す「建造反対派」です。

旧態依然な文化が根強く残る海軍の中では「建造推進派」が優勢でした。2週間後の最終決定の会議までに状況を覆したい山本五十六ら「建造反対派」は、巨大戦艦の建造費が過剰に小さく見積もられていると感じた点に目をつけました。

山本五十六は天才数学者の主人公に、巨大戦艦の建造費の正確な見積もりを依頼。しかし、主人公は戦艦のドメイン知識もなく、今回建造予定の巨大戦艦の詳細情報もありません。そのような中で付け焼き刃ながらドメイン知識を身につけ、地道な情報収集も重ねていきます。

「建造推進派」による数々の妨害にも屈せず、巨大戦艦の建造費の予測値を算出した主人公。予測値を突きつけられた「建造推進派」の反応、そして巨大戦艦の建造の顛末は・・・?

(続きは劇場で)

感想

「データ分析の能力を見込まれた主人公」=「データサイエンティスト」と見立てて、感情移入してしまいました。まずは「データ分析で課題が解決できそう」という目論見で呼び出されるも、使えるデータがほとんどない状況。ドメイン知識もない主人公が自ら様々な場所に足を運んでデータを収集し、少しずつ知見を得ていく姿は胸を打つものがあります*1

そして「建造推進派」による数々の妨害。主人公がデータを手に入れられないような政治的な根回しや、最終決定の会議の前倒しなど、フィクションでありながら妙なリアリティのある出来事*2が巻き起こります。「突然やってきた素人の主人公が成功したら困る」という理由で、「建造反対派」であるはずの設計士が主人公に非協力的な部分も、実に人間味にあふれて生々しかったです。

最後に、巨大戦艦の建造費の予測値を突きつける際の描写です。詳細部分は映画の根幹になってしまうので割愛しますが、如何に正しくても数式を押し付けるだけでは聴衆に響かない点や、数式を超越する物事の存在など、多くを考えさせられるクライマックスでした。

おわりに

本記事では、ネタバレ要素を含みつつ『アルキメデスの大戦』の感想を書きました。普段はこの類いの感想ブログはあまり書かないのですが、予想以上に感銘を受けたので筆を執ってしまいました。いつの間にか大人1枚1900円になった映画*3ですが、たまに観るのは悪くないなあと改めて感じました。

*1:自分もKaggleのコンペで自ら猫カフェに行ったことを思い出しました

*2:私はここまで酷い話は噂レベルでしか聞いたことないですが

*3:eiga.com

scikit-learn-contrib の Metric Learning を試す

Metric Learning について

Metric Learning は、データの教師情報を基にデータ間の距離や類似度などの Metric を学習する手法です。日本語で手軽に読める記事だと、*1, *2 などが詳しいです。

このたび、phalanx さんの tweet *3で、 Metric Learning の基礎的なアルゴリズムのいくつかが scikit-learn-contrib *4に搭載されていると知りました。

本記事では、scikit-learn-contrib の metric-learn パッケージを用いて、簡単にMetric Learning を試します。

インストール

README や PyPI *5 に記載のある通り、次の通りにインストールします。

pip install metric-learn

利用するデータセット

今回は、sklearn に含まれている load_digits データセットを利用します*6。64次元の特徴量・0-9の10種類のラベルを持つ手書き数字のデータセットです。

f:id:upura:20190818182045p:plain
画像は*7より引用。

可視化(Metric Learning 前)

特徴量の可視化に当たっては、T-SNE *8 を用いて2次元への削減を行います。

次のコードは、metric-learn の docs に掲載されていた内容*9を、凡例を出すように一部改変しています。

def plot_tsne(X, y):
    plt.figure(figsize=(8, 6))
    
    # clean the figure
    plt.clf()

    tsne = TSNE()
    X_embedded = tsne.fit_transform(X)

    cmap = plt.get_cmap("tab10")
    for idx in range(10):
        plt.scatter(X_embedded[(y==idx), 0], X_embedded[(y==idx), 1], c=cmap(idx), label=idx)

    plt.legend()
    plt.xticks(())
    plt.yticks(())

    plt.show()

load_digits データセットをそのまま可視化したところ、下図のようになりました。大まかに分かれてはいますが、中央付近など少し煩雑になっていると分かります。

f:id:upura:20190818180214p:plain

可視化(Metric Learning 後)

次に、Metric Learning を実施します。

import metric_learn


# setting up LMNN
lmnn = metric_learn.LMNN(k=6, learn_rate=1e-6)

# fit the data!
lmnn.fit(X, y)

# transform our input space
X_lmnn = lmnn.transform(X)

いくつかのアルゴリズムが実装されていますが、ここでは Large Margin Nearest Neighbor (LMNN) を採用します。

Algorithms

  • Large Margin Nearest Neighbor (LMNN)
  • Information Theoretic Metric Learning (ITML)
  • Sparse Determinant Metric Learning (SDML)
  • Least Squares Metric Learning (LSML)
  • Neighborhood Components Analysis (NCA)
  • Local Fisher Discriminant Analysis (LFDA)
  • Relative Components Analysis (RCA)
  • Metric Learning for Kernel Regression (MLKR)
  • Mahalanobis Metric for Clustering (MMC)

READMEから引用。

Metric Learning 実施後の特徴量を可視化したところ、下図のようになりました。Metric Learning 実施前よりも、各クラスがハッキリと分かれているのが確認できます。

f:id:upura:20190818180230p:plain

今回は全データで学習し、全データに適用しています。Metric Learning はデータ間の距離や類似度などの Metric を学習しているので、学習に用いていないデータセットに適用することが可能です。

例えば Kaggle のような教師あり機械学習の文脈で利用する場合には、train データセットで Metric を学習し、test データセットにも適用することになるでしょう。分離に適した新しい特徴量空間を用いることで、より分類性能が高いモデルの構築が期待されます。

おわりに

本記事では、scikit-learn-contrib の metric-learn パッケージに搭載されている Metric Learning を試しました。なかなか使い所が難しい印象もある技術ではありますが、選択肢の一つとして持っておく価値は多分にあると感じています。

実装は notebook 形式で GitHub にて公開しています*10

【特徴量の追加編】機械学習を用いた大相撲千秋楽の勝敗予想

はじめに

前回は、「Sports Analyst Meetup #4」でのLTに向けて、ベンチマークとなる機械学習モデルを構築しました。新しい特徴量を追加することで、予測モデルの性能が向上することも確認しました。

upura.hatenablog.com

本記事では、新しい特徴量を加えて、ベンチマークのLightGBMモデルの改善に挑戦します。

「連勝・連敗」特徴量の追加

千秋楽を迎えるに当たって、力士が何連勝・何連敗しているかを示す特徴量です。例えば9日に勝利した後で10〜14日に5連敗している場合は 'wins_till_final_day' が 0 で 'loses_till_final_day' が5になります。直感的には、千秋楽の勝敗に影響を与えそうな特徴量です。

def calc_wins_till_final_day(row):
    cnt = 0
    for i in range(14):
        if row.iloc[14 - i] == 1:
            cnt += 1
        else:
            return cnt
    return cnt


def calc_loses_till_final_day(row):
    cnt = 0
    for i in range(14):
        if row.iloc[14 - i] == 0:
            cnt += 1
        else:
            return cnt
    return cnt


df['wins_till_final_day'] = df.apply(calc_wins_till_final_day, axis=1)
df['loses_till_final_day'] = df.apply(calc_loses_till_final_day, axis=1)

この2つの特徴量を前回の機械学習モデル(CVスコアはAUC: 0.554915)に加えたところ、CVスコアはAUC: 0.561075に向上しました。feature importanceで見ると上から3, 4番目に登場してきています。

f:id:upura:20190808171307p:plain

testデータセットでの性能を見たところ、0.5を閾値にした場合の正答率と混同行列は次の通りに変化しました。

0.5476190476190477
→ 0.5561904761904762
array([[302, 218],
       [257, 273]])

→

array([[299, 221],
       [245, 285]])

tsfresh特徴量の追加

次いで、tsfreshという時系列特徴量を作成してくれるライブラリを利用します。勝敗の0, 1を時系列情報と見なし、特徴量を抽出する狙いがあります。

tsfresh.readthedocs.io

tsfreshの利用に当たって、dataframeを加工します。現在は1行ごとに列方向に勝敗を保持していますが、tsfreshでは縦方向にデータを保持する必要があります。

f:id:upura:20190803220932p:plain

df_list = []
for index, row in df.iterrows():
    _df = pd.DataFrame(row.iloc[1:15].T)
    _df.columns = ['results']
    _df['id'] = index
    df_list.append(_df)

df_tsf = pd.concat(df_list).reset_index(drop=True)
df_tsf['results'] = df_tsf['results'].astype(float)
df_tsf.head()

f:id:upura:20190810164537p:plain

あとは extract_features 関数を利用するだけで、800近くの特徴量を生成できます。今回は select_features 関数を利用し選定した9個の特徴量を利用しました。

from tsfresh import extract_features
extracted_features = extract_features(df_tsf, column_id='id')

f:id:upura:20190810164738p:plain

これらの特徴量を機械学習モデルに加えたところ、CVスコアはAUC: 0.5699616に向上しました。feature importanceで見ると、追加した特徴量が予測に寄与していると確認できます。しかし残念ながら、testデータセットでの性能は「連勝・連敗」特徴量を追加した時点を上回ることはありませんでした。

f:id:upura:20190810165506p:plain

おわりに

本記事では、ベンチマークとなる機械学習モデルに新しい特徴量を加えて、ベンチマークのLightGBMモデルの改善に挑戦しました。特徴量の追加を試した所感から言うと、LightGBMベースで特徴量を増やしていく方法には限界がありそうです。次回以降は、勝ち負けの「01111...」の流れをRNNに入れるなど、時系列的な特徴を加味した機械学習モデルを試してみたいと思います。

【書籍メモ】『データマイニングエンジニアの教科書』

データマイニングエンジニアの教科書』を読んだので、雑感を書きます。

www.c-r.com


書籍の概要

  • データマイニングエンジニアの教科書』
  • 著者:森下壮一郎・編著、水上ひろき/高野雅典/數見拓朗/和田計也・著
  • 出版社:シーアンドアール研究所 (2019/6/27)

以下は書籍紹介ページから引用。

本書は、プロとしてデータマイニングを行うための教養を身に付けるための1冊です。
データを分析するための知識を技術について、統計・エンジニアリング・ビジネス・倫理の基礎知識を解説しています。 データマイニングを始めようとしている人にオススメの1冊です。

目次は次の通りで、特にCHAPTER 11, 13など普通の技術書ではあまり扱わない話題を広範に掲載しています。

CHAPTER 01 データマイニングを始める前に
CHAPTER 02 統計学の基礎
CHAPTER 03 計算機上のデータ
CHAPTER 04 構造を持つデータ
CHAPTER 05 テーブル
CHAPTER 06 可視化
CHAPTER 07 パターンと距離
CHAPTER 08 多変量解析
CHAPTER 09 時系列解析
CHAPTER 10 計算量の見積もり
CHAPTER 11 エンジニア的財務会計
CHAPTER 12 指標を考える
CHAPTER 13 技術者倫理

所感

どの章も、端的に要点がまとまっています。全般に基礎的な内容を扱っている書籍なので、ある程度知っているトピックの場合は物足りない感は残ると思います。自分の場合は、サクサク読み流しながら、自分の知識の抜け漏れを確認するような使い方をしました。

精読したのは、CHAPTER 11「エンジニア的財務会計」から。昔に簿記3級を学んだ経験があるので、思い出しながらエンジニア向け書籍に会計の話題がある理由を噛み締めていました。

CHAPTER 12「指標を考える」は、個人的に一番好きな内容でした。「会社のビジネスを紐解いて指標に落とし込む」という自分が業務において常々意識している話が明文化されていて、統計や分析の手法だけでなく視野の広い話が掲載されている点で、本書への好感度が更に高まりました。

CHAPTER 13「技術者倫理」は、期せずしてタイムリーな話題でした。当たり前の内容が多かったですが、データを扱う人間として一読して改めて思いを馳せる良い機会となりました。

おわりに

本記事では、『データマイニングエンジニアの教科書』を紹介しました。単なる統計や分析の手法だけでなく、会計・指標・倫理などの話題も広範に扱っています。各トピックは基礎的な内容が掲載されている印象なので、前から目を通して気になった部分を重点的に学ぶような使い方もできそうです。網羅的に知識の確認ができる、良い本でした。

【ベンチマーク編】機械学習を用いた大相撲千秋楽の勝敗予想

はじめに

前回は、「Sports Analyst Meetup #4」でのLTに向けて、「Sumo Reference」から収集したデータを用いて特徴量作成のための簡単な可視化を実施しました。

upura.hatenablog.com

本記事では、ベンチマークとなる機械学習モデルを構築します。ベンチマークに新しい特徴量を加えることで、性能の変化も確認します。

github.com

ベンチマークの構築

特徴量

特徴量としては、前回の記事で取得した「1〜14日目の勝敗」と、可視化を通じて予測に寄与しそうだと確認した「14日目終了時点での勝数」を用います。

  • day_1, day_2, ... , day_14: N日目の勝敗(1 or 0)
  • wins_at_day_14: 14日目終了時点での勝数

目的変数

  • final_day: 15日目の勝敗(1 or 0)

機械学習モデル

Kaggleでテーブル形式データを扱う上での定番になっている「LightGBM」を用います。

Validation の構築

今回2000〜2019年のデータを取得していますが、test検証用に2015年以降のデータは学習に用いないことにします。

split_datetime = 201501
train = df[df['datetime']<split_datetime]
test = df[~(df['datetime']<split_datetime)]

その上で、Validationは下図のように時系列で切りました。具体的には、validationに用いる28場所分のデータを、1場所ずつズラしていくことで複数の {train, valid} のペアを作成しています。それぞれでvalidationのデータセットに対する性能を今回はAUCで測り、それらの平均をCVスコアとして採用しています。ペアの数をいくつにするかは計算量との兼ね合いですが、現時点ではさほど計算量が多くないので30組を作成しました。

f:id:upura:20190804130114p:plain

評価性能

CVスコアはAUC: 0.52895で、二値分類なのでランダムよりは僅かに良い程度の性能になりました。 "gain" で測定した feature importanceは次の通りです。「wins_at_day_14」が圧倒的に高い値で、その他は相対的に0に近いです。(強いて挙げれば「day_1」が高く、定性的には「初日の結果が場所全体の調子に影響を与え得る」くらいの解釈ができるかもしれません)

f:id:upura:20190804170551p:plain

testデータセットでの性能を見たところ、AUCはCVとほぼ同様の0.54になりました。(こんなにcurve感のないAUCも珍しいですね)

f:id:upura:20190804171048p:plain

0.5を閾値にした場合、正答率と混同行列は次の通りでした。

from sklearn.metrics import accuracy_score
accuracy_score(y_test, y_sub)
0.5304761904761904
from sklearn.metrics import confusion_matrix
confusion_matrix(y_test, y_sub)
array([[333, 187],
       [306, 224]])

新しい特徴量の追加

追加する特徴量

追加する特徴量は「千秋楽の対戦相手の14日終了時点での勝数」です。理由は単純で、本人の「14日終了時点での勝数」が効くなら、同様に対戦相手のものも効くだろうという発想です。

評価性能(新しい特徴量の追加)

CVスコアは0.554915で、特徴量追加前よりも微量ですが向上しました。feature importanceを確認すると、追加した特徴量が上位に来ていると分かります。

f:id:upura:20190804173559p:plain

testデータセットでのAUCも0.57に上昇しました。

f:id:upura:20190804172605p:plain

0.5を閾値にした場合の正答率と混同行列は次の通りです。

0.5476190476190477
array([[302, 218],
       [257, 273]])

おわりに

本記事では、「Sports Analyst Meetup #4」でのLTに向けてベンチマークとなる機械学習モデルを構築しました。新しい特徴量を追加することで、予測モデルの性能が向上することも確認しました。

次回以降、次の2つの方向性で取り組んでいく予定です。

  1. 新しい特徴量を加えて、ベンチマークのLightGBMモデルを改善する
  2. 時系列性を扱える機械学習モデルを利用する別のアプローチを試す

【可視化編】機械学習を用いた大相撲千秋楽の勝敗予想

はじめに

前回は、「Sports Analyst Meetup #4」でのLTに向けて、大相撲のデータを収録している「Sumo Reference」を紹介しました。

upura.hatenablog.com

本記事では、「Sumo Reference」から収集したデータを用いて、特徴量の作成に向けた簡単な可視化を実施します。

データの収集

データは「Sumo Reference」から取得し、次の形式に加工しました。

df.head(3)

f:id:upura:20190803220932p:plain

各行に、特定の場所の特定の力士の情報を格納しています。

「day_1」〜「day15」は、1〜15日目の勝敗です。NaNは、欠場を意味します。千秋楽の勝敗予想に当たって必要になる情報として「千秋楽の対戦相手」を「final_opponent」として取得しました。「datetime」は「yyyymm」形式で開催場所を示しています。

データは2000年1月場所から、最新の2019年7月場所までを取得しました。

len(df)
4340

仮説

いきなり思考停止で特徴量を作って機械学習モデルにデータを突っ込んでも良いですが、まずはこの分析を始めるに当たっての仮説に立ち戻ります。

千秋楽の勝敗は、その場所の調子(14日目までの結果)までに左右される部分があるのでは?

具体例を考えると、次の通りです。

  1. 7勝7敗で千秋楽を迎えた力士は、何としてでも勝ち越しをしたい(「ここ一番」の本気を出す)
  2. 13勝1敗のように大勝している場合は、勝率が高くなりそう
  3. 千秋楽を前にN連勝している場合は、その流れで千秋楽も勝利しそう

今回の分析ではこのような仮説に基づき、1〜14日目の勝敗データから、千秋楽(15日目)の勝敗を予測するモデルの構築を目指します。

可視化

ここでは、上述した仮説1, 2に対応する特徴量「14日目までの勝数」を考えます。勝数が決まれば敗数は14からの差分として自明に定まるので、カテゴリとしては「勝数」と「勝敗」に違いはありません。

まずは「14日目までの勝数」の分布を見てみます。

ここでの可視化に当たって、1日でも欠場のある力士は削除しているので、0勝はかなり少なくなっています。

f:id:upura:20190729121703p:plain

次いで「14日目までの勝数」別の「千秋楽の勝率」です。

f:id:upura:20190729121736p:plain

個人的に着目したのは次の2点です。それぞれ、先に述べた仮説の1, 2に対応します。

  • 7勝7敗の場合の勝率が(6勝8敗や8勝6敗の場合に比べて)高い
  • 14日目までに12〜14勝している場合は勝率が0.5よりも高い

※ 前者について、こういう数字が出ると「八百長」のような話が出がちですが、今回は話題として取り上げません。ここに因果があるかを議論するには扱っている情報量が少なすぎるためです。

可視化を通じて、事前に持っていた仮説がある程度正しそうだと分かりました。つまり、今回作成した「14日目の勝数」という特徴量は、千秋楽の勝敗を予想するモデルに寄与する可能性がありそうです。

おわりに

本記事では「Sports Analyst Meetup #4」でのLTに向けて、「Sumo Reference」から収集したデータを用いて特徴量作成のための簡単な可視化を実施しました。次回は、ベンチマークとなる機械学習モデルを構築する予定です。