面白いデータを探して

適当に書く。間違えていたら教えてください。

プロ野球についての考察と分析① チームの実力と相性

私の趣味は野球観戦です。
私の贔屓のチームは特定の球団にめちゃくちゃ強いです。一方で、特定の球団にはボッコボコにされていたりします。
そんな球団の「実力」とか「相性」みたいなものを分析してみたいなーという話。
正直、ちゃんとデータを集めたわけでもないですし、仮定も雑ですがなんとなく納得できるような結果が出たので書いてみます。

続きを読む

ロジスティック回帰を数値微分で実装する

netres-bigdata.hatenablog.com
の続きみたいな感じ。

numpyと数値微分でロジスティック回帰を書いてみる。
ロジスティック回帰とか、微分した式めっちゃ簡単だから本当は数値微分をする必要はないけど。
コードもだいぶやっつけ。

コード

# -*- coding:utf-8 -*-

import numpy as np
import matplotlib.pyplot as plt

# h
h = 1e-7

def logits(x, w):
    _x = np.insert(x, x.shape[1], 1, axis=1)
    return 1 / (1 + np.exp(np.dot(_x, w.T)))

def loss(x, y, w, lam):
    l = np.clip(logits(x, w), h*1e+2, 1-(h*1e+2))
    return -np.sum(y*np.log(l)+(1-y)*np.log(1-l))/y.shape[0]+lam*np.sum(np.square(w))

def update_value(x, y, w, h, lam):
    _w = w + np.identity(w.shape[0])*h
    _w_pos = np.array([loss(x, y, __w, lam) for __w in _w])
    _w = w - np.identity(w.shape[0])*h
    _w_neg = np.array([loss(x, y, __w, lam) for __w in _w])
    return (_w_pos-_w_neg)/(2*h)

def pred(x, w):
    return logits(x, w)

# 学習させるデータ : OR
x = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 1, 1, 1])
w = np.array([0, 0, 0])

# 学習率と正規化率
eta = 0.5
lam = 1e-5

_loss = [loss(x, y, w, lam)]
for i in range(1000):
    _w = update_value(x, y, w, h, lam)
#    print("update value:", _w)
    w = w - eta*_w
    _loss.append(loss(x, y, w, lam))
plt.plot(list(range(len(_loss))), _loss)
plt.title("loss")
plt.xlabel("epoch")
plt.ylabel("loss")
plt.savefig("figure/loss.png")
print(pred(x, w))

結果

予測結果 : [ 0.04184762  0.98330325  0.98330325  0.99998741]

ちゃんと予測出来てるかな

f:id:netres:20190724201933p:plain
lossもちゃんと落ちてる。

数値微分について

数値微分について調べてみたのでメモ代わり。

はじめに

機械学習をしているとパラメータの更新に微分が現れる。

たとえば、SGD(Stochastic Gradient Descent : 確率的勾配降下法)では


 \displaystyle \theta \leftarrow \theta - \alpha \frac{\partial L}{\partial \theta}

となる。ただし、 Lは損失関数、 \thetaは更新したいパラメータ、 \alphaは学習率。

したがって、あるパラメータ \thetaの現在の値に対する偏微分を計算しなければならないが、コンピュータ上でこれをどのように実装したら良いかについて。

微分の定義について

ある関数 f(x)があって、 f(x)区間 (-\infty, \infty)微分可能であるとする。このとき、関数 f(x) x=aにおける導関数 f'(a)は以下で定義される。


 \displaystyle f'(a) = \lim_{h \rightarrow 0} \frac{f(a + h)-f(a)}{h}\ \ \cdots\cdots(1)

数値微分の気持ち

ここで、(1)の右辺に注目すると、この右辺では hを0に近づけている。なので、 hを0に近い値として右辺を計算すれば近似値が求まる。

たとえば、


 \displaystyle f'(a) \sim \frac{f(a + 0.00001) - f(a)}{0.00001}

一般には


 \displaystyle f'(a) \sim \frac{f(a + h) - f(a)}{h}

やってみる

 f(x)=\sin(x)

 f(x)=\sin (x)について試してみる。 x \in X = \{ -3.14, -3.13, \cdots\cdots, 3.13, 3.14 \} として計算。 hを0.1、0.01、0.001としたもの、そして正しい f'(x)の4つをグラフ上にプロットしてみる。そして、それぞれについて誤差がどのくらいになるか調べる。誤差は絶対誤差の平均を以下で計算した。


 \displaystyle \frac{1}{|X|}\sum_{x\in X}\left|f'(x) - \frac{f(x+h)-f(x)}{h}\right|

f:id:netres:20190724162601p:plain
重なってしまって見にくいが、 hを0.01、0.001としたものはほぼ正しい f'(x)と重なっており、おおよそ正しい値が計算できていることがわかる。

 h 誤差
0.1 0.0317892503788
0.01 0.00317963692601
0.001 0.000317964178614


 hの値を0.1倍するごとに、誤差も0.1倍されるような感じ。

 f(x)=\log (x)

 f(x)=\log (x)についても試してみる。 x \in X = \{ 0.01, 0.02, \cdots\cdots, 2.00 \} について同様にやってみる。

f:id:netres:20190724171535p:plain
 xが0に近いところで誤差が結構大きい。 \log(x)微分 \displaystyle \frac{1}{x}であるが、 xが0に近づくと発散するので、敏感になるのだろうと考えられる。

 h 誤差
0.1 1.03668486616
0.01 0.288800794761
0.001 0.0393169186566


上と同様の理由で、誤差も大きくなる。

両側近似の気持ち


 \displaystyle f'(a) = \lim_{h \rightarrow 0} \frac{f(a + h)-f(a)}{h}

であったが、 hは正の方から0に近づけるのと、負の方から0に近づける場合が考えられる。上の例である

 \displaystyle f'(a) \sim \frac{f(a + 0.00001) - f(a)}{0.00001}

では正の方から近づけることを考えているが、微分可能であるということは負の方から0に近づけても近似値は取れるはずであるから、

 \displaystyle f'(a) \sim \frac{f(a - 0.00001) - f(a)}{-0.00001}

としても良いはずである。

なので、両方計算して、その平均を f'(a)としてみる。


 \begin{eqnarray*} f'(a) &\sim& \frac{1}{2} \left\{\frac{f(a + 0.00001) - f(a)}{0.00001} + \frac{f(a - 0.00001) - f(a)}{-0.00001}\right\} \\ &=& \frac{1}{2}\left\{\frac{f(a+0.00001)-f(a-0.00001)}{0.00001}\right\} \end{eqnarray*}

一般には


 \displaystyle f'(a)\sim \frac{f(a+h) - f(a-h)}{2h}

試してみる

 f(x) = \sin (x)についてどのくらい誤差が変化するか試してみる。設定は上と同じ。

 h 正の側から 負の側から 両側
0.1 0.0361962278046 0.0361962278046 0.000907100835736
0.01 0.00355426634458 0.00355426634458 9.07549967163e-06
0.001 0.000354746370367 0.000354746370367 9.07554592842e-08


両側から誤差を取るとだいぶ小さくなっていることがわかる。

まとめ

数値微分についていろいろ比較してみました。

Appendix : 両側微分すると誤差が小さくなることの証明

 f(x)が十分な回数だけ微分可能であるとすると、テイラー展開を用いると示せる。


 \displaystyle f(x+h) = f(x) + hf'(x) + \frac{h^2}{2}f''(x) + \frac{h^3}{3!}f'(x) + \cdots\cdots
 \displaystyle f(x-h) = f(x) - hf'(x) + \frac{h^2}{2}f''(x) - \frac{h^3}{3!}f'(x) + \cdots\cdots

正の側からの近似は
 \begin{eqnarray*} f'(a) &~& \frac{f(a + h) - f(a)}{h} \\ &=& f'(a) + \frac{h}{2}f''(a) + \frac{h^2}{3!}f'(a) + \cdots\cdots \\ &=& f'(a) + \mathcal{O}(h) \end{eqnarray*}

負の側からの近似は
 \begin{eqnarray*} f'(a) &~& \frac{f(a - h) - f(a)}{-h} \\ &=& f'(a) - \frac{h}{2}f''(a) + \frac{h^2}{3!}f'(a) - \cdots\cdots \\ &=& f'(a) + \mathcal{O}(h) \end{eqnarray*}

両側近似では
 \begin{eqnarray*} f'(a) &~& \frac{f(a + h) - f(a-h)}{2h} \\ &=& f'(a) + \frac{h}{3!}f'''(a) + \cdots\cdots \\ &=& f'(a) + \mathcal{O}(h^2) \end{eqnarray*}

以上より、両側近似が最も誤差のオーダーが低くなる。

オプティマイザーって重要なんだなって

研究の実験をしてて、オプティマイザーの重要性を感じる。

一番ひどいデータセット

SGD
auc 0.5731
F1 0.6568

Adam
auc 0.8959
F1 0.8816

コードを1行変えただけでこんなに差が出るもんかね。

pythonのlambdaの扱いについてまとめる

今だに挙動がよくわからなくなることがあるので、pythonのlambdaの扱いについてまとめて行く。

lambdaとは

関数を定義するために使う。
例えば

def inc(x):
    return x+1

print(inc(1))

# 2

というプログラムはlambdaを使うと

print((lambda x:x+1)(1))

# 2

と書くことができる。

inc = lambda x:x+1
print(inc(1))

# 2

と書くこともできる。

文法

lambdaの基本的な文法は下

lambda 引数 : 返り値

使えると何が嬉しい?

lambdaが使えると、ちょっとした関数を書きやすくなる。
mapとかsortとか、listなどの要素にちょっとした細工したいなーってときに、わざわざ名前付きの関数を1つ定義するのはめんどい。
なので、lambdaを使ってさらっと処理しましょう。

具体的に使用例

list内の要素を全て2倍する

a = list(range(10))

# 関数を定義して書く
def double(x):
    return x*2
b = list(map(double, a))

# list内包表記で書く
c = [x*2 for x in a]

# lambdaで書く
d = list(map(lambda x:x*2, a))

入れ子のlistの各要素をかけて返す

a = [[1, 2], [3, 4], [5, 6]]

# 関数を定義して各
def mul(x):
    return x[0]*x[1]
b = list(map(mul, a))

# リスト内包表記で書く
c = [x[0]*x[1] for x in a]

# lambda式で書く
d = list(map(lambda x:x[0]*x[1], a))

入れ子のデータを整列する

# aをそれぞれのリストの先頭の要素の順で整列したい。
a = [[2, 0], [3, 1], [1, 2]]

# sortedのkeyにlambda関数を指定
b = sorted(a, key=lambda x:x[0])

# 関数定義版
def func(x):
    return x[0]
c = sorted(a, key=func)

辞書データをvalueの順で整列する。

a = {"ken":1, "yuji":4, "taro":2, "takeshi":3}

# 整列
b = sorted(a, key=lambda x:a[x])

print("a:", a)
print("b:", b)

# a: {'ken': 1, 'yuji': 4, 'taro': 2, 'takeshi': 3}
# b: ['ken', 'taro', 'takeshi', 'yuji']

最後に

何か面白そうな使い方があったら教えてください。
見つけたら書きます。