ColaboratoryでKaggleを始める

[Google Colaboratory](https://colab.research.google.com/?hl=ja)で[Kaggle](https://www.kaggle.com/)をサクッと試したいときのメモ。Colaboratoryは無料でGPU/TPUが使えるのがうれしい点。

本記事の要点は以下。

1. ColaboratoryでKaggle APIを使えるように設定
2. 簡単な問題「Titanic」に挑戦
3. 予測モデルとしてNeural Networkを学習（機械学習フレームワークはTensorFlow）

# 必要なもの
* Googleアカウント
* Kaggleアカウント

# ノートブックの準備
[Colaboratory](https://colab.research.google.com/?hl=ja)にアクセスし、
ファイル > 新しいPython3ノートブック
で新しいノートブックを開く。

編集 > ノートブックの設定 > ハードウェアアクセラレータ > GPU > 保存
とすればGPUが使える。

# Kaggle APIの導入
Kaggle APIはコマンドラインからKaggleのデータをダウンロードしたり予測結果をアップロードできるツール。

## Kaggle APIをインストール
新しいセルを開いて下記コマンドを実行。

```
!pip install kaggle
```
これだけでは使えない。Kaggleアカウントと紐づける必要がある。

## Kaggleアカウントと紐づけ
[Kaggle](https://www.kaggle.com/)にアクセス。
My Profile > Edit Profile > Create New API Token
としてkaggle.jsonをダウンロード。
[Google drive](https://drive.google.com/drive/my-drive)を開いて適当な場所にkaggle.jsonをアップロード。

Colaboratoryから以下を実行。

```python
# download API key from google drive
## Original: https://colab.research.google.com/drive/1eufc8aNCdjHbrBhuy7M7X6BGyzAyRbrF#scrollTo=y5_288BYp6H1
## When you run for the first time, you will see a link to authenticate.

from googleapiclient.discovery import build
import io, os
from googleapiclient.http import MediaIoBaseDownload
from google.colab import auth

auth.authenticate_user()

drive_service = build('drive', 'v3')
results = drive_service.files().list(
        q="name = 'kaggle.json'", fields="files(id)").execute()
kaggle_api_key = results.get('files', [])

filename = "/root/.kaggle/kaggle.json"
os.makedirs(os.path.dirname(filename), exist_ok=True)

request = drive_service.files().get_media(fileId=kaggle_api_key[0]['id'])
fh = io.FileIO(filename, 'wb')
downloader = MediaIoBaseDownload(fh, request)
done = False
while done is False:
    status, done = downloader.next_chunk()
    print("Download %d%%." % int(status.progress() * 100))
os.chmod(filename, 600)
```
初回実行時に認証が必要になるので、表示されるURLから認証コードを入手しコピペする。
[オリジナル](https://colab.research.google.com/drive/1eufc8aNCdjHbrBhuy7M7X6BGyzAyRbrF#scrollTo=y5_288BYp6H1)は`filename = "/content/.kaggle/kaggle.json"`となっているが、/root/.kaggle/に置けというエラーメッセージが出たので`filename = "/root/.kaggle/kaggle.json"`に変更

```
Download 100%.
```
と表示されれば成功。

## 動作確認
現在オープンなCompetitionの一覧を表示。

```
!kaggle competitions list
```

# 簡単なコンペティションに参加
今回は[Titanic](https://www.kaggle.com/c/titanic)というコンペティションに参加。
Titanicは乗船者の年齢や性別といった属性情報から生存Yes/Noの二値を予測するタスク。データサイズも小さく、練習用として扱いやすいKaggleのHello world!的存在。

## データのダウンロード
```
!kaggle competitions download -c titanic
```
colaboratoryの左カラムの「ファイル」や`!ls`などで train.csv, test.csv, gender_submission.csv がダウンロードされていることを確認。
gender_submission.csvは提出する結果ファイルのサンプル。

## データの前処理
与えられたCSVファイルには数値以外の文字列や欠損が含まれているので、一定の基準で画一的なベクトル表現に変換する必要がある。データをどう加工するかは予測精度に大きく影響するが、今回は動作確認が目的なので以下のような大雑把なルールで変換する。

* 'Pclass', 'Sex', 'Fare','SibSp', 'Parch', 'Age', 'Embarked'の情報のみ使用
* 3種類の'Embarked'(出港地)は0,1,2に置き換え
* データの欠損は -1 に置き換え

また、学習状況のモニタリングのために学習データの一部を検証用（下記は10%）として使用。検証用データの精度は学習率、ネットワーク構造などのチューニングや過学習の判定に有用。

```python
import numpy as np
import pandas as pd
import tensorflow as tf
import time

def parse(df):
	# Nan を -1 に置換
	df["Age"] = df["Age"].fillna(-1)
	df["Embarked"] = df["Embarked"].fillna(-1)
	# 文字列を数値に変換
	df["Sex"][df["Sex"] == "male"] = 0
	df["Sex"][df["Sex"] == "female"] = 1
	df["Embarked"][df["Embarked"] == "S" ] = 0
	df["Embarked"][df["Embarked"] == "C" ] = 1
	df["Embarked"][df["Embarked"] == "Q"] = 2
	return df

def split_val(x,y,rate,seed=None):
	# x,y の rate[%] をランダム分割
	N = x.shape[0]
	val_num = int(N*rate)
	if seed is not None:
		np.random.seed(seed)
	perm = np.random.permutation(N)
	ti = perm[:-val_num]
	vi = perm[-val_num:]
	return x[ti],y[ti],x[vi],y[vi]

def load_data():
	train_csv = pd.read_csv('train.csv')
	train_csv = parse(train_csv)
	train_x = train_csv[['Pclass', 'Sex', 'Fare','SibSp', 'Parch', 'Age', 'Embarked']].values
	train_y = train_csv['Survived'].values
	return split_val(train_x,train_y,0.1)
```


## 学習

予測のアルゴリズムは決定木やSVMなど数多く存在するが、折角なのでTensorFlowを使ってNeural Networkを学習させる。
ネットワーク構造はu_dim次元の中間層をlayer_numだけ繰り返すシンプルな多層パーセプトロン。中間層の出力にReLUとBatchNormalizationを適用。
モデル定義

```python
def create_model(input_placeholder,u_dim,layer_num,y_dim,training):
	h = input_placeholder
	for i in range(layer_num):
		h = tf.layers.dense(inputs=h, units=u_dim,activation=tf.nn.relu)
		h = tf.layers.batch_normalization(h,training=training)  
	h = tf.layers.dense(inputs=h, units=y_dim)
	return h
```
学習。hyperparametersを調整していい感じのモデルを学習する。

```python
np.random.seed(0)
tf.reset_default_graph()
y_dim     = 2
# hyperparameters
u_dim     = 100
layer_num = 2
epoch     = 1000
log_freq  = 100
batchsize = 200
lr        = 0.001

# data load
train_x,train_y,val_x,val_y = load_data()
N  = train_y.shape[0]
Nv = val_y.shape[0]
print('train num: {}, val num: {}'.format(N,Nv))

# calculation graph
x = tf.placeholder(tf.float32, [None, train_x.shape[1]],"input")
y = tf.placeholder(tf.int32, [None])

with tf.variable_scope("model"):
  train_z = create_model(x,u_dim,layer_num,y_dim,training=True)
with tf.variable_scope("model", reuse=True):
  z       = create_model(x,u_dim,layer_num,y_dim,training=False)

cross_entropy = tf.losses.sparse_softmax_cross_entropy(labels=y, logits=train_z)

extra_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(extra_ops):
  train_step = tf.train.AdamOptimizer(lr).minimize(cross_entropy)

pred_y = tf.cast(tf.argmax(z, 1), tf.int32)
accuracy = tf.reduce_mean(tf.cast(tf.equal(pred_y, y), tf.float32))

# main loop
sess = tf.InteractiveSession()
tf.global_variables_initializer().run()
T = time.time() 
for ep in range(1,epoch+1) :
  perm=np.random.permutation(N)
  for i in range(0,N,batchsize):
    batch_xs=train_x[perm[i:i+batchsize]]
    batch_ys=train_y[perm[i:i+batchsize]]
    sess.run(train_step, feed_dict={x: batch_xs, y: batch_ys})
  # monitor
  if ep%log_freq ==0 :		
    train_loss, train_acc = sess.run([cross_entropy, accuracy], feed_dict={x: train_x, y: train_y})
    val_loss, val_acc     = sess.run([cross_entropy, accuracy], feed_dict={x: val_x, y: val_y})
    epochT = time.time()-T
    print('Epoch: %d, Time :%.4f (s), train_loss: %f,  train_acc: %f, val_loss: %f,  val_acc: %f' % (ep, epochT, train_loss, train_acc, val_loss, val_acc))
    T = time.time()
```

以下は学習結果例。

```
Epoch: 100, Time :1.7708 (s), train_loss: 0.391779,  train_acc: 0.822943, val_loss: 0.405918,  val_acc: 0.797753
Epoch: 200, Time :1.7101 (s), train_loss: 0.426542,  train_acc: 0.704489, val_loss: 0.488159,  val_acc: 0.629214
Epoch: 300, Time :1.6960 (s), train_loss: 0.370545,  train_acc: 0.839152, val_loss: 0.441939,  val_acc: 0.775281
Epoch: 400, Time :1.7387 (s), train_loss: 0.403303,  train_acc: 0.816708, val_loss: 0.467801,  val_acc: 0.786517
Epoch: 500, Time :1.7077 (s), train_loss: 0.399253,  train_acc: 0.846633, val_loss: 0.431753,  val_acc: 0.808989
Epoch: 600, Time :1.6997 (s), train_loss: 0.393861,  train_acc: 0.825436, val_loss: 0.436137,  val_acc: 0.808989
Epoch: 700, Time :1.7178 (s), train_loss: 0.364453,  train_acc: 0.834165, val_loss: 0.432697,  val_acc: 0.820225
Epoch: 800, Time :1.7211 (s), train_loss: 0.355730,  train_acc: 0.840399, val_loss: 0.461115,  val_acc: 0.797753
Epoch: 900, Time :1.6965 (s), train_loss: 0.387813,  train_acc: 0.815461, val_loss: 0.436706,  val_acc: 0.820225
Epoch: 1000, Time :1.7431 (s), train_loss: 0.413756,  train_acc: 0.749377, val_loss: 0.415829,  val_acc: 0.752809
```

ちなみに、上記はGPU実行時の処理時間。この程度の計算ではGPUによる高速化の恩恵は小さい。

## 予測
テストデータも学習データと同様に前処理して、学習した予測モデルに入力。

```python
# data
test_csv = pd.read_csv('test.csv')
test_csv = parse(test_csv)
test_x = test_csv[['Pclass', 'Sex', 'Fare','SibSp', 'Parch', 'Age', 'Embarked']].values
# predict
prediction = sess.run(pred_y, feed_dict={x: test_x})
# parse
PassengerId = np.array(test_csv["PassengerId"]).astype(int)
my_solution = pd.DataFrame(prediction, PassengerId, columns = ["Survived"])
# save
my_solution.to_csv("result.csv", index_label = ["PassengerId"])
```

result.csvが出力される。`!cat result.csv`などで内容を確認できる。

## 予測結果の提出

```
!kaggle competitions submit -c titanic -f result.csv -m 'first submit'
```
1日の提出回数に上限があるので注意。
## 結果の確認
[Kaggle](https://www.kaggle.com/)にアクセス。
My Profile > Competitions > Titanic ... > My submissions
から結果を確認。
76.5% など妥当な値が得られていればOK。

# 参考
以下の記事を参考にさせていただいた。
[Google Colaboratory で始める Kaggler 生活(データ入手と提出編)](https://qiita.com/fam_taro/items/eb9bae0b82248f9abd28)
[【Kaggle初心者入門編】タイタニック号で生き残るのは誰？](https://www.codexa.net/kaggle-titanic-beginner/)