nanoseeingの技術系ブログ

機械学習・競プロなど。アウトプットが目的。

Google Colab上でKaggleのダウンロードからサブミッションをするまで

Kaggleの実行環境は何かと不便(週あたりの制限時間があるなど)。 そのため、Google Colab上でダウンロードからサブミッションまでを全てやってみる。


コンペは、タイタニックで試す。 www.kaggle.com

Google Driveに接続

入力データなどは、全てDrive上に保存しておくと便利。
まずは、Driveに接続する。

from google.colab import drive
drive.mount('/content/drive')

認証コードを聞かれるので、URL先からコピペすること。

なお、Driveのフォルダ構成は、以下を想定している。

(Drive直下)
MyDevelopment/
  |-Kaggle/
  |  |-kaggle.json
  |  |-titanic/
  |     |-input/
  |     |-output/
  |-my_modules/

kaggleライブラリを読み込む

Kaggle APIを使いたいため、kaggleライブラリをimportする。
単純にpipでインストールしてやればよいが、Google Colabが再起動されるたびにインストールし直さなければならず面倒。そのため、Drive上にモジュールを保存し、パスを教えてやることで解決する。

import sys
module_path = '/content/drive/MyDrive/MyDevelopment/my_modules/'

# kaggleライブラリをDrive上に保存していない場合は、pipから保存する。
# !pip install --target=$module_path kaggle

# パスを追加
sys.path.append(module_path)

jsonファイルの保存、読み込み

kaggle.jsonファイルは、Kaggleのプロフィールページから作成できる(Your Profile > Account > API > Create New API Token)

jsonファイルを作成できたらDrive上に保存する。

jsonファイルを読み込んで、Colab上の環境変数を設定してやることで、Kaggle APIが使えるようになる。

import json

json_file_path = '/content/drive/MyDrive/MyDevelopment/Kaggle/kaggle.json'
os.chmod(json_file_path, 777)

with open(json_file_path, 'r') as f:
    json_data = json.load(f) 
    os.environ['KAGGLE_USERNAME'] = json_data['username']
    os.environ['KAGGLE_KEY'] = json_data['key']

データのダウンロード

「kaggle competitions download -c (コンペ名)」で、データをダウンロードできる。コンペティションページのdataタブを開くと、コマンドをコピペできるので、それを使っても良い。

今回は、下記ページのコマンドをコピペして、タイタニックのデータをダウンロードしてみる。
https://www.kaggle.com/c/titanic/data

import kaggle

input_path = '/content/drive/MyDrive/MyDevelopment/Kaggle/titanic/input/'
os.chdir(input_path)

# データのダウンロード
! kaggle competitions download -c titanic

提出用ファイルを作成

適当な前処理と、ランダムフォレストで学習してみる。
提出用ファイルはoutputフォルダに出力した。

import os
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier

# ルートパスを設定して移動
root_path = '/content/drive/MyDrive/MyDevelopment/Kaggle/titanic/'
os.chdir(root_path)

# データ読み込み
train_path = './input/train.csv'
test_path = './input/test.csv'
gender_submission_path = './input/gender_submission.csv'

train = pd.read_csv(train_path)
test = pd.read_csv(test_path)
gender_submission = pd.read_csv(gender_submission_path)

# 前処理

data = pd.concat([train, test], sort=False)

data['Sex'].replace(['male', 'female'], [0, 1], inplace=True)
data['Embarked'].fillna(('S'), inplace=True)
data['Embarked'] = data['Embarked'].map({'S': 0, 'C': 1, 'Q': 2}).astype(int)
data['Fare'].fillna(np.mean(data['Fare']), inplace=True)
data['Age'].fillna(data['Age'].median(), inplace=True)

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

# 学習
train = data[:len(train)]
test = data[len(train):]

y_train = train['Survived']
X_train = train.drop('Survived', axis=1)
X_test = test.drop('Survived', axis=1)

clf = RandomForestClassifier(n_estimators=100, max_depth=2, random_state=0)
clf.fit(X_train, y_train)

# 予測
y_pred = clf.predict(X_test)
sub = pd.read_csv(gender_submission_path)
output_sub_path = './output/submission_randomforest.csv'
sub['Survived'] = list(map(int, y_pred))
sub.to_csv(output_sub_path, index=False)

サブミッション

「! kaggle competitions submit (コンペ名) -f (提出ファイル) -m "(コメント)"」でサブミッションできる。

実際に試してみる。

! kaggle competitions submit titanic -f $output_sub_path -m "test submission"


下記サブミッションページから結果を確認。
https://www.kaggle.com/c/titanic/submissions

f:id:nanoseeing:20210303143101p:plain
サブミッション結果

うまくいった。

みなさんも良いKaggleライフを。

統計検定2級 合格体験記

統計検定2級に合格したので、これから合格を目指す方の参考になるように、勉強法などをメモしておきます。

目安

勉強時間:〜50時間程度(高校数学は理解している前提)
費用:1万円(CBT方式受験料7000円、公式問題集2000円、電卓なければ1000円)

おすすめ勉強方法

Youtubeの動画で統計知識をざっくり掴む

www.youtube.com

大学の数学・物理などをわかりやすく教えてくれる、通称「ヨビノリ」さんの授業動画。 「推定」や「検定」といった概念をざっくり掴むのにとても良い。たまに出てくるボケはスルーで(笑)

目安勉強時間:3時間程度

「統計WEB」で試験範囲を一通りインプットする

bellcurve.jp


解説はわかりやすい上に、練習問題もついている。 全部の章を読み切り、練習問題を1〜2周するだけで、合格できるだけの知識は身につく。

少しもったいないのは、公式が突然出てきて、厳密な数学的説明が省かれているところ。 ただし、数学的背景は重要ではない場合が多いので、 割り切って、公式を道具として使えるようにする練習をまずは優先しよう。

目安勉強時間:20時間程度

「高校数学の美しい物語」で数学的理解を深める

mathtrain.jp

「統計WEB」で拾いきれなかった数学的理解を深める。 期待値・分散に関する公式や、さまざまな分布に関する数学的背景について、よくまとまっている。 特に、この辺りの記事が非常に参考になる。

期待値・分散に関しては、定義から理解して、 典型的な性質を理解しておかないと解けない問題も出題されるので、 よく勉強しておこう。

目安勉強時間:数時間

過去問を解く

www.amazon.co.jp

年度別に数冊出版されているが、最新の過去問だけでOK。公式解説はあまり評判がよくないので、

【統計検定2級 解説記事一覧】 | ブログ | 統計WEB
こちらの解説も併用すると良い。


目安勉強時間:20時間程度

ロジスティック回帰【機械学習アウトプット第5回】

ロジスティック回帰とは

回帰と名前がついているが、実際には(2値)分類問題。 どのクラスに所属するかの確率を計算することで分類する。

単回帰・重回帰分析と基本構造は同じで、

 n次元の説明変数: \boldsymbol{x} = [x_0= 1, x_1, x_2, ... x_n]
目的変数: \boldsymbol{y} = [y_1, y_2, ... y_n] (y_i = [0, 1])
パラメータ: \boldsymbol{w} = [w_0, w_1, w_2, ... w_n]
予測値:\boldsymbol{\hat y} = f(\boldsymbol{w^{T}x}) (0 ≦ \hat {y}_{i} ≦ 1)


を用いて、 \boldsymbol{y} \boldsymbol{\hat y}の誤差を 最小化するような\boldsymbol{w}を求めることが目的である。 なお、 x_0=1としたのはバイアスを表している。

ただし、予測値\boldsymbol{\hat y}を0から1の確率値に収めるために、関数fを噛ませている点に注意。

そして、このfは、

 f(x) = \dfrac{1}{1 + e^{-x}}


で表される、シグモイド関数と呼ばれるやつである。

なぜシグモイド関数が用いられるか

「オッズ」という概念がある。競馬とかでオッズ○倍とかいう時のオッズである。 オッズは単に勝敗の比率のことで、勝利確率を pとすると、

 \dfrac{p}{1-p} (p:1-p)


で表される。

これにlogをとった、

 logit = log \dfrac{p}{1-p}


を考える(ちなみにlogをとる理由は、勝ちと負けの尺度を揃えるためなどの理屈がある)。

すると、入力pの範囲は 0 ≦ p ≦ 1、出力logitの範囲は実数全体であるから、 logitの逆関数を考えると、実数全体を確率値0から1に収めることができ、都合が良い。

その逆関数シグモイド関数

 f(x) = \dfrac{1}{1 + e^{-x}}


である。

パラメータの推定方法

単回帰・重回帰分析のときは、損失関数として最小二乗誤差を用い、 損失を最小にすることを目的としてパラメータを推定した。

ロジスティック回帰の場合には、最尤推定を用いてパラメータを推定する。

導出は省くが、最尤推定に基づいて計算した、対数尤度関数は下記で表される。

 logL(\boldsymbol{w}) =  \sum_{i=1}^{n} {y}_{i}log(\hat y_i) + (1-y_i)log(1 - \hat y_i)


この対数尤度を最大化するようなパラメータ \boldsymbol{w}を求めるのが、最終的な目標である。

ただし、解析的に解くことは不可能なので、ニュートン法などの近似的に解を求めるアルゴリズムが必要。

なお余談だが、

 \sum_{i=1}^{n} {y}_{i}log(\hat y)


この式は交差(クロス)エントロピーなどと呼ばれ、 情報理論でいう、平均情報量などの概念と結びついているところが興味深い。

実装

次回で。

ラッソ回帰(L1ノルム)による正則化【機械学習アウトプット第4回】

復習

重回帰分析において、損失関数は下記で表された。

 L=(\boldsymbol{y}-\boldsymbol{Xw})^{T}(\boldsymbol{y}-\boldsymbol{Xw})


この損失関数にパラメータ \boldsymbol{w}が小さくなるように罰則項を与えることが、「正則化」の一種であった。

また、 L_{p} ノルムとは、

 ‖\boldsymbol{x}‖_{p} = \sqrt[p]{\sum_{i=1}^{n} {|x_{i}|}^{p}}


で表され、

損失関数に、L2ノルム(いわゆるユークリッド距離) ‖\boldsymbol{w}‖_{2} を罰則項として与えた回帰問題をリッジ回帰といった。

ラッソ回帰とは

L1ノルムは、下記で表される。

 ‖\boldsymbol{w}‖_{1} = \sum_{i=1}^{n} {|w_{i}|}


ゆえに、このL1ノルムを罰則項として与えた損失関数は、

 L=(\boldsymbol{y}-\boldsymbol{Xw})^{T}(\boldsymbol{y}-\boldsymbol{Xw}) + \alpha\sum_{i=1}^{n} {|w_{i}|}


となり、この損失関数を最小化する回帰問題がラッソ回帰である。

以下、Lを最小化する問題を解くわけだが、罰則項に絶対値が含まれるため、解析的に偏微分することが難しい。 よって、「座標降下法」などのアルゴリズムを用いて最適なパラメータを求める必要がある。

…が、面倒なのでsklearnの実装だけやってみる。

自作データのプロット

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline

np.random.seed(seed=32)
plot_num = 1000

x1 = np.random.rand(plot_num) * 10 - 5 
x2 = np.random.rand(plot_num) * 10 - 5 
y = 3 * x1 - 2 * x2 + 7
y += 5 * np.random.randn(plot_num)

x1 = x1.reshape(-1, 1)
x2 = x2.reshape(-1, 1)
y = y.reshape(-1, 1)
fig = plt.figure(figsize=(6, 6))
ax = Axes3D(fig)
ax.plot(x1.ravel(), x2.ravel(), y.ravel(), marker=".", linestyle='None')
plt.show()

f:id:nanoseeing:20210205172006p:plain

sklearnで予測

import sklearn.linear_model as lm

X = np.concatenate([x1, x2], axis=1)

lasso = lm.Lasso(alpha=1.0)
lasso.fit(X, y)
print(lasso.intercept_)
print(lasso.coef_)
>>
[6.88137878]
[ 2.90263201 -1.97552597]

予測値を可視化

x1_p = np.linspace(-5,5,10).reshape(-1,1)
x2_p = np.linspace(-5,5,10).reshape(-1,1)
y_pred = lasso.predict(np.concatenate([x1_p, x2_p], axis=1)).ravel()
xx1, xx2 = np.meshgrid(x1_p.ravel(), x2_p.ravel())
zz, _ = np.meshgrid(y_pred, y_pred)

fig = plt.figure(figsize=(6, 6))
ax = Axes3D(fig)
ax.plot(x1.ravel(), x2.ravel(), y.ravel(), marker=".", linestyle='None')
ax.plot_surface(xx1, xx2, zz, alpha = 0.3)
plt.show()

f:id:nanoseeing:20210205171955p:plain

以上。3次元平面を書くのが難しかった。

参考サイト

行列の積の種類(行列の積・アダマール積・フロベニウス積)とnumpyでの書き方

単なる行列の積のことを「内積」とか書いてる記事をみつけて混乱したので、まとめてみた。

行列の積

単に、行列の「積」というと、下記のこと。

 \boldsymbol{A} : n * m 行列,  \boldsymbol{B} : m * p 行列
 \boldsymbol{(AB)_{ij}} = \sum_{k=1}^{m} {a}_{ik}{b}_{kj}


これが、一般的に思いつく「積」のはずで、間違っても内積だの外積だのとは言わないはず。左行列の行ベクトルと、右行列の列ベクトルの「内積」を並べた行列とは言える。

numpyだとこう書く。

import numpy as np
A = np.array([[1,2,3],[4,5,6]])
B = np.array([[7,8],[9,10],[11,12]])
AB = np.dot(A, B)
print(AB)
>>
[[ 58  64]
 [139 154]]
AB = A @ B
print(AB)
>>
[[ 58  64]
 [139 154]]

アダマール

アダマール積は、下記で表される。

\boldsymbol{A} : n * m 行列,  \boldsymbol{B} : n * m 行列
\boldsymbol{(A∘B)_{ij}} = {a}_{ij}{b}_{ij}


要するに、「同じ位置の要素同士の積」をとっただけ。当然、同じサイズの行列同士でないと計算できない。

numpyだとこう書く。

A = np.array([[1,2,3],[4,5,6]])
B = np.array([[7,8,9],[10,11,12]])
AB_hadamard = A * B # numpy.matrixでは通常の積になるので注意。
print(AB_hadamard)
>>
[[ 7 16 27]
 [40 55 72]]
AB_hadamard = np.multiply(A, B)
print(AB_hadamard)
>>
[[ 7 16 27]
 [40 55 72]]

フロベニウス内積

フロベニウス内積は、下記で表される。

 \boldsymbol{A} : n * m 行列,  \boldsymbol{B} : n * m 行列
 \boldsymbol{A:B} = \sum_{i,j} {a}_{ij}{b}_{ij}


総和をとっているので、計算結果は「スカラー」になることに注意。「同じ位置の要素同士の積の合計」と覚える。フロベニウス内積はベクトルの「内積」と計算の見た目が同じ。

numpyだとこう書く。

A = np.array([[1,2,3],[4,5,6]])
B = np.array([[7,8,9],[10,11,12]])
AB_frobenius = np.sum(A*B) # アダマール積を計算してからsumをとる
print(AB_frobenius)
>>
217

以上。積ひとつとっても色々概念があって難しい…

参考資料

リッジ回帰(L2ノルム)による正則化【機械学習アウトプット第3回】

正則化とは

前提として、重回帰分析がある。
重回帰分析については第2回記事を参照。

nanoseeing.hatenablog.com

重回帰分析における二乗和誤差による損失関数は、 下記で表せる。

 L=(\boldsymbol{y}-\boldsymbol{Xw})^{T}(\boldsymbol{y}-\boldsymbol{Xw})

この損失関数に、ある罰則項を与え、求めるべきパラメーター\boldsymbol{w}の値を小さくすることが、 正則化の目的である。

正則化を行うことで、モデルの複雑化を防ぎ、過学習を抑えることが期待される。

リッジ回帰(L2ノルム)とは

そもそもL_{p}ノルムとは、下記のこと。

‖\boldsymbol{x}‖_{p} = \sqrt[p]{\sum_{i=1}^{n} {x_{i}}^{p}}

p=2のときL2ノルムと呼ばれ、これはいわゆるユークリッド距離のことである。

このL2ノルムを罰則項として付与した損失関数、

 L=(\boldsymbol{y}-\boldsymbol{Xw})^{T}(\boldsymbol{y}-\boldsymbol{Xw}) + \alpha‖\boldsymbol{w}‖_{2}

を考える。 ここで、\alpha正則化の強さをコントロールするパラメータで、任意に決定してよい。

損失関数を偏微分してイコール0となる方程式を解くと、

\boldsymbol{w}=(\boldsymbol{X^{T}}\boldsymbol{X}+\alpha\boldsymbol{I})^{-1}\boldsymbol{X^{T}}\boldsymbol{y}

が求まる。

データ作成

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline

np.random.seed(seed=32)
plot_num = 50

x1 = np.random.rand(plot_num) * 10 - 5 
x2 = np.random.rand(plot_num) * 10 - 5 
y = 3 * x1 - 2 * x2 + 7
y += 5 * np.random.randn(plot_num)

fig = plt.figure(figsize=(6, 6))
ax = Axes3D(fig)
ax.plot(x1, x2, y, marker="o", linestyle='None')
plt.show()

f:id:nanoseeing:20210202165048p:plain

データは記事第2回の重回帰分析のときから変えていない。 切片7、回帰係数[3, -2]が予想される

自作実装

x0 = np.ones(plot_num).reshape(plot_num,1)
x1 = x1.reshape(plot_num,1)
x2 = x2.reshape(plot_num,1)
y = y.reshape(plot_num,1)

alpha = 5

X = np.concatenate([x0, x1, x2], 1)
I = np.eye(X.shape[1])
W = np.linalg.inv(X.T @ X + alpha * I) @ X.T @ y
print(W[1:])
print(W[0])
>>
[[ 2.8152892 ]
 [-2.36886758]]
[6.5056526]

sklearnで実装

from sklearn.linear_model import Ridge

X = np.concatenate([x1, x2], 1)
clf = Ridge(alpha=5)
clf.fit(X, y)
print(clf.coef_)
print(clf.intercept_)
>>
[[ 2.79545591 -2.3170463 ]]
[7.18487782]

自作実装と違う値が求まってしまった。 何が間違っているのかわからないが、理論はあっているはず。

参考サイト

重回帰分析を実装する【機械学習アウトプット第2回】

重回帰分析とは

教師データとして、m次元の説明変数xと、 それに対応する目的変数yが与えられたとき、

\hat{y}={w}_{0}{x}_{0} + {w}_{1}{x}_{1} + ... + {w}_{n}{x}_{m}

と近似できるパラメータ、

\boldsymbol{w}=[{w}_{0},{w}_{1},{w}_{2},...,{w}_{m}]

を求める問題。

自作データをプロットしてみる

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline

np.random.seed(seed=32)
plot_num = 50

x1 = np.random.rand(plot_num) * 10 - 5 
x2 = np.random.rand(plot_num) * 10 - 5 
y = 3 * x1 - 2 * x2 + 7
y += 5 * np.random.randn(plot_num)
fig = plt.figure(figsize=(6, 6))
ax = Axes3D(fig)
ax.plot(x1, x2, y, marker="o", linestyle='None')
plt.show()

f:id:nanoseeing:20210202165048p:plain

目的変数y=3{x}_{1} - 2{x}_{2} + 7とし、教師データの作成時には 正規分布に従う誤差を加えた。

求めたい正解のパラメータは、

\boldsymbol{w}=[7,3,-2]

である。

自力で数式から実装

m次元の教師データ(\boldsymbol{x},y)の組がn組与えられたとする。

このとき、

 \boldsymbol{X} =
\begin{pmatrix}
x_{10}&x_{11}&x_{12}&...&x_{1m}\\
x_{20}&x_{21}&x_{22}&...&x_{2m}\\
...\\
x_{n0}&x_{n1}&x_{n2}&...&x_{nm}\\

\end{pmatrix}


とおくと、求めたいパラメータ \boldsymbol{w}は、

\boldsymbol{w}=(\boldsymbol{X^{T}}\boldsymbol{X})^{-1}\boldsymbol{X^{T}}\boldsymbol{y}

で求まる。

これは、二乗和誤差を行列のノルム形式で表して、偏微分して、ごちゃごちゃ変形した結果である。 具体的な計算過程は参考サイトに任せる。

実際に実装するとこうなる。

x0 = np.ones(plot_num).reshape(plot_num,1)
x1 = x1.reshape(plot_num,1)
x2 = x2.reshape(plot_num,1)
y = y.reshape(plot_num,1)
X = np.concatenate([x0, x1, x2], 1)
W = np.dot(np.dot(np.linalg.inv(np.dot(X.transpose(), X)), X.transpose()), y)
print(W)
>>
[[ 7.15745485]
 [ 2.83263504]
 [-2.35856492]]

正解のパラメータ \boldsymbol{w}=[7,3,-2]に近いことが確認できる。

sklearnで実装

sklearnのLinearRegression()を使えば一発。

from sklearn import linear_model

X = np.concatenate([x1, x2], 1)
clf = linear_model.LinearRegression()
clf.fit(X, y)
print(clf.coef_)
print(clf.intercept_)
>>[[ 2.83263504 -2.35856492]]
[7.15745485]

切片の値w_{0}は「clf.intercept_」に、それ以外のパラメータは「clf.coef_」に入っている。 自力実装と同じ値が出力された。

参考サイト