Machine Learning Crash Courseの内容を整理する

# 前置き

googleの [Machine Learning Crash Course](https://developers.google.com/machine-learning/crash-course/) を受講しながら、気になったことをつらつらとメモしていきます。
一通り書き終わったころには全く整理になっていないメモ書きが出来ていそうですが、おいおい整理します。

なお、筆者は機械学習もtensorflowもpythonも始めてです。

# 機械学習

## まず用語

機械学習にはこんな用語が出てくる。
各用語には、「届いたメールがスパムメールかそうでないか」を学習する際の例をつける。

* Labels: 予測したい対象そのもののこと。
    * 例： メールがスパムであるかどうか
* Features: 予測するために入力する変数のこと。
    * 例： メール本文・送信アドレス・送信時刻　など
* Examples: 準備したデータそのもののこと。exampleにはさらに2種類ある。
    * labeled examples (ラベル付きデータ): featureとlabelが含まれているデータ。学習の教師データとして使うことができる。
        * 例： 過去に届いたスパムメール / 過去に届いたスパムでないメール
    * unlabeled examples (ラベルなしデータ): featureしかないデータ。unlabeled examplesに対して正しいラベルを予測するのが目的。
        * 例：　新しく届いたメール
* [Models](#models): featureとlabelとの関係を定義したもの。

### Models

* label: y
* feature: x

簡単化のためにfeatureが1種類であるとして、
上のように定義すると、モデルは次のように表される。

```
y = wx + b
```

このうち、wとbがモデルに相当する。
また、wとbのことを下のように呼ぶ。

* w: weight
* b: bias

実際には、featureが複数あったりするので、下のようになる。

```
y = w1*x1 + w2*x2 + ... + b
```

また、Modelには2つの状態がある。

* Training: モデルがまだ学習中の状態であること。
* Inference (推論): モデルの学習が完了し、unlabeled examplesに適用できる段階のこと。この状態のモデルを使ってラベルを予測する。

Modelの種類にも大きく分けて2つある。

* regression model (回帰モデル): 連続数を予測するモデル
    * 例：カリフォルニアの家の価格は？ / ユーザが広告をクリックする確率は？
* classification model (分類モデル): 離散的な値を予測するモデル
    * examplesをいくつかの種類に分類するイメージ
    * 例：届いたメールはスパムかそうでないか？ / この画像に写っているのは犬か猫か？

このコースでは、Regression modelの中でも単純なLinear regression model (線形回帰モデル) からスタートしています。

## Linear regression model

このモデルのイメージは、「2次元グラフにプロットしたexamplesを近似するような直線を求める」(featureが一つの場合)
[こちらの図1, 2](https://developers.google.com/machine-learning/crash-course/descending-into-ml/linear-regression)がイメージしやすいです。

ここで出てくる用語は以下。

* loss (誤差): モデルが予測したlabelと、実際のexamplesのlabelとの差。これを最小化するのが目標。
    * 指標にはmean squared error (平均二乗誤差)などが使われる。
* gradient descent mechanism (勾配降下法): lossを最小化するために使われる手法。詳しくは下で。
* learning rate (学習率): gradient descent において、1ステップあたりにモデルのパラメータを変化させる割合。大きすぎても小さすぎてもダメ。


## Gradient descent

linear regression modelで解こうとする問題では、[こちらの図2](https://developers.google.com/machine-learning/crash-course/reducing-loss/gradient-descent)のような関係がある。
すなわち、featureを横軸に、lossを縦軸に取ったグラフを書くと、「凸型」のグラフになる。

これを全て計算するのはコストが大きすぎるので、よく知られた方法としてgradient descent (勾配降下法)というものがある。

1. 適当な初期値を与えてモデルを計算し、lossを求める。
2. lossの勾配を計算し、lossが下がる方向へモデルを更新する。
3. 更新したモデルを使ってlossを求める。
4. 2, 3 を繰り返し、lossが最小値付近に収束するまで続ける。

## Stochastic Gradient Descent

上記2. のステップで勾配を計算するときに、学習データ全体を使って計算すると非効率になる。
そこで、Stochastic Gradient Descent (SGD: 確率的勾配降下法)や、Mini-batch SGDという手法が取られる。

勾配を計算するときに使うデータ数のことを**batch size**という。
通常のgradient descentでは全ての学習データを使って勾配を計算するので、``batch_size = (学習データ数)``となる。
これが、SGD や Mini-batch SGD の場合以下のようになる。

```python
# SGD
batch_size = 1

# Mini-batch SGD
batch_size = 10 # to 1000
```

データ数が少ないほど勾配の計算コストは下がるが、計算結果が"noisy"になってしまうという特徴がある。

## 機械学習のワナ

代表的なワナとしてoverfitting (過学習) がある。
これを避けるためにいくつか心がけるべきことがある。

* モデルはシンプルである方が良い。
* 学習用データセットとテスト用データセットは分けなくてはならない。学習にテスト用データセットを使ってはならない。
    * 同じテスト用データセットを使い続けると「摩耗」する。新しいデータセットを入手出来ると最高。
    * そのため、データセットは3つに分けるのがベター。
        * 学習用: モデルを学習するために使う
        * 検証用: モデルの学習度合いを評価するのに使う (学習毎に評価する)
        * テスト用: 最終的に作成したモデルが使用するに値するかテストするためだけに使う

## データの加工

機械学習において重要なのは、学習するためのコードよりも、**学習に使うデータ**である。
学習効果を高めるために、生のデータセットをいくらか「加工」してやることが効果的。

* 数値で入ってくるデータはそのまま使うことができる。そのとき、整数値は小数値として取り込む。
* 文字列で入ってくるデータは加工が必要。
    * まず vocabulary (語彙)を定義してやる。
        * vocabulary: データセットの中に、どのような語が存在するか
    * one-hot encodingを行う。
        * 1/0 (もしくはtrue/false) のベクトルに変換する。
        * vocabulary の中の対応する語の位置だけ1、残りは全て0のベクトルとする。
* カテゴリ値 (列挙値) の場合も加工が必要。
    * 登場する可能性のあるカテゴリを vocabulary とし、one-hot encodingしてやる。

「良いデータ」にするためには次のようなことに気をつける。

* データセット中に最低でも5回以上登場するような値をfeatureとすること。
    * たとえばデータに対してユニークに付けられたIDからは、なんの特徴も得られない。
* 値が表す意味を明確にする。
    * 例えば人の年齢を表すのであれば、``age: 27``などがよい。
    * ``age: 851472000`` (unix timeで表記)などは避けるべき。
    * そうすることで、``age: 277``のような明らかな異常値があることが分かるようになる。
* 実データと「マジックナンバー」を混ぜない
    * マジックナンバー: データが存在しないときなどに使う特殊な値 (例: データがないときは-1 とか)
    * このようなデータを扱うときは、2つのfeatureに分ける
        * 実データ
        * データが定義されているかどうかを表すフラグ
* 時間によって変化しない(変化しにくい)値を使う。
    * たとえば、街の名前は変化しない(しにくい)が、あるシステムで付けた街のIDは変化しやすい(他のシステムから収集したデータでは別のIDかもしれない)。

これらのことを気をつけた上で、さらにデータを整形すると効果が上がる場合がある。

* 取りうる値が一定の範囲に収まるようにスケールしなおす
    * featureによって取りうる値の範囲が極端に違う場合により効果を発揮する。
* 極端な異常値を処理する
    * ごく一部のデータだけが異常に大きな/小さな値を持っているような場合、clippingしてやると良い場合がある。
        * clipping: 一定よりも大きな/小さな値を全て範囲の最大値/最小値に丸めてしまう
        * この場合は、データを捨てる訳ではない。
* 値の分布が正規化されていないときは、Bucketize(Binning)も一つの方法
    * Bucketize: 取りうる値の範囲を複数の「バケツ」に分割し、別々のfeatureとしてしまう。
* 正しくないデータを修正する。
    * 正しくないデータの例
        * 値が抜けている
        * データが重複している
        * ラベルが間違っている
        * featureの値が間違っている
    * 正しくないデータはデータセットから外したりして修正する。
* データについて知る。
    * どんなデータを扱っているのか知っておくこと。
    * 期待しているものとデータが合っているか確かめること。(あるいはなぜ期待から外れているか説明できること)
    * 学習データを別の方法であらかじめチェックすること。(pandasやmatplotlibなどで可視化してみるとか)

## 線形でない問題を解きたい

扱うデータが線形問題として解けない場合どうするか。

* Bucketized (Binning) features: featureをBucketizeして新たなfeatureを作ってみる。
* feature cross: feature同士を掛け算し、新たなfeatureを作ってみる。
    * 特に、one-hot encodingしたベクトル同士をcrossした場合、matrix (行列)を得ることができる。
    * feature crossを多用しすぎると、複雑なモデルになってoverfitting (過学習) する。

## モデルの評価

[Linear Regression Model](#linear-regression-model)のところで、「lossを最小化するのが目標」と書いた。
しかし、学習データに対してlossを最小化すると過学習の状態に陥ってしまう。
そこで、作成したモデルを評価する指標に次のものを使う。

```math
minimize(Loss(Data|Model) + \lambda\, complexity(Model))
```

つまり、次のことが言いたい。

* lossが小さいほど良い
* モデルが複雑でないほど良い

モデルの複雑さを定量的に表すために、**L<sub>2</sub> regularization** (L<sub>2</sub> 正則化)を用いることが出来る。
L<sub>2</sub> regularizationは、以下の式で表される。

```math
||w||^2_2 = w^2_1 + w^2_2 + ... + w^2_n
```

要するに、作成したモデルの全てのfeatureにかかるweightを二乗和したものである。
そのため、L<sub>2</sub> regularizationを行うと次のようなことが起こる。

* weightの値を0に近づけようとする
* weightの平均も0に近づけようとする
* weightをガウス分布に近づけようとする

モデルを評価する指標として示した式にある$\lambda$は、**regularization rate** (正則化率)と呼ばれる。
$\lambda$が大きければ大きいほどL<sub>2</sub> regularizationの効果を大きくしようとする。
なので、$\lambda$の大小によって次のようなことが言える。

* $\lambda$が大きすぎるときには、モデルを単純にしようとし過ぎる。そのため、**学習不足で充分な予測ができない**リスクがある。
* $\lambda$が小さすぎるときには、誤差を最小にしようとしすぎる。そのため、**過学習が起こる**リスクがある。

よって、$\lambda$も適切な大きさを設定してやることが大切。
また、$\lambda$の大きさを変えた場合、L<sub>2</sub> regularizationによって生じるweightの変化も起こる。
そのため、learning rate (学習率)とregularization rate (正則化率)は**同時に調整しない**ようにすべき。

## Logistic Regression

「どの程度確からしいか」という確率をもとめる場合、(例: このメールがスパムであるのは何%の確率か)Logistic Regression (ロジスティック回帰) が使える。
Linear Regressionの場合はloss function (損失関数: 誤差を求めるために使う関数) として二乗誤差を用いた。
Logistic Regressionの場合は[Log Loss](https://developers.google.com/machine-learning/crash-course/logistic-regression/model-training)を使う。

Logistic Regressionでは正則化が非常に重要になる。
ほとんどの場合、次のような正則化のうちどれかを行う。

* L<sub>2</sub> regularization
* Early stopping (学習ステップを制限したり、learning rateを小さくしたりして学習を止める)
* L<sub>1</sub> regularization (この時点ではまだ出てきてない)

## Classification

Logistic Regressionでは「確率」を得ることができる。
例えば、「このメールがスパムである確率は99.95%である」のような。
これを使って分類しようとしたとき、「このメールがスパムである確率は60%である」と言われたら、これはどちらに分類したら良いか。
それを決めるために**classification threshold**を設定してやる必要がある。
この設定は問題依存なので、自分でチューニングする必要がある。

Classification Modelはどうやって評価するのか？

* Accuracy (正確度): 全ての予測に対してどの程度正しい分類をしたか
    * False Positive (偽陽性) やFalse Negative (偽陰性) な結果を全てひっくるめてAccuracyとするので、数値は高くなりがち
    * Accuracy単体で評価せず、PrecisionやRecallも合わせて評価する必要がある
* Precision (精度): 全ての「真」だと予測したものに対してどの程度が正しい分類であったか
* Recall: 全ての「真」だったものに対してどの程度正しく「真」と分類したか

PrecisionやRecallはClassification Thresholdを調整することで向上が見込めるが、
それだけでは最終的にトレードオフの関係になってしまう。

## L<sub>1</sub> Regularization

特徴

* 各フィーチャのweightの絶対値に比例したペナルティを課す
    * L<sub>2</sub> Regularizationはweightの2乗に比例したペナルティを課す
* weightが0のフィーチャの個数を多くしようと働く(フィーチャの係数を0にする方向に正則化が働く)
    * L<sub>2</sub> Regularizationはweightが0に近づくように(0にはならない事が多い)働く
* モデルが使用するメモリサイズが小さくなる(フィーチャのweightが0 → そのフィーチャの項を削除することができるため)

## Neural Networks

非線形な問題を解くことができる手法。
入力と出力の間に"hidden layer" (隠れ層) を追加して非線形なモデルを計算する。
hidden layer のノードの出力は、Activation Functionと呼ばれる非線形な関数を通すことで非線形性が生まれる。

Activation Functionの種類には以下のようなものが使われる。

* ReLU (Rectified Linear Unit): $F(x) = max(0, x)$
    * 負の出力を0でクリップしただけの単純な関数
    * 計算が単純な分、計算コストが低い
* Sigmoid: $F(x) = \frac{1}{1+e^{-x}}$
* tanh: $F(x) = \tanh x$

## Backpropagation algorithm

Neural networksのトレーニングに使われるアルゴリズム。
簡単にいえば、

* weightが大きくなると誤差が大きくなる場合は、weightを小さくしてみる。その逆もしかり。
* 各ニューロン間のweightごとに誤差の勾配を計算していく。その際、outputに近いほうから順に戻る方向へ計算していく。
* どの程度weightを修正するかというのは、learning rateで調整する。

## Neural Networksの学習

* Neural Networksでもデータの加工が効果を発揮する。平均0の正規分布に近い形に整形してやると効果が出やすい
    * logスケールする
    * 値をクリッピングする
    * z-scoreでスケールする　など

## Multi-Class Neural Networks

* One vs. All: 一つの入力から、取りうる全ての出力に対して、binary classificationを行う。
    * 例: この写真に写っているのは？ → りんご: 92%, くま: 2%, キャンディー: 23%, 犬: 0.3%, 卵: 18%, ...
    * 分類するクラスが増えるほど、急激に計算コストは増加する。
* Softmax: One vs. All と似ているが、全ての出力を足し合わせると1.0 (100%) になるような制約を付ける
    * かならずどれか一つのクラスに分類される問題に適している。
    * 逆に言えば、複数のクラスに該当するものはうまく分類できない。
        * 例: この写真に写っているのは？ → りんごと犬が写っている → うまく分類できない

## Embedding

たとえば文章中に出てくる単語をone-hot encodingして入力ベクトルを作成する場合、たいていは入力ベクトルが巨大でかつ疎になってしまう。
Neural Networksの入力が巨大な数になると、いくつか問題が出てくる。

* 必要なデータ量: モデルのweightを効率的に計算するために、より大量のデータ量が必要になる。
* 必要な計算量: モデルを学習するためにより多くのweightを計算しなければならず、計算量が爆発的に増える。

そこで使われるのがEmbeddingという手法。
例えば、文章に対してEmbeddingを行うときにはprincipal component analysis (PCA: 主成分分析)などが用いられる。
主成分分析のためのアルゴリズムとしては、Googleのword2vecが紹介されている。
意味的な距離というのをdistributional hypothesis (分布仮説)に基づいて表現しているらしい。


# 機械学習で出てくるアルゴリズム

* Gradient Descent Algorithm
    * iterativeなアルゴリズムなので、学習データ数に対してよくスケールする
    * さらに計算コストを抑えたものとして、SGDやMini-batch SGDが使われる
* Follow the Regularized Leader (FTRL) Optimization Algorithm
    * sparseなfeatureが多いときに特に有用
    * オンライン学習 (随時学習データが増えることがある) にも適用可能
* Adagrad optimizer
    * leaning rateを学習途中で適応的に変化させていく
    * 凸型問題には非常に良くフィットするが、凸型でない問題には常に理想的であるとは言えない。
* Adam optimizer
    * 非凸型問題にフィットするアルゴリズム

# 後書き

* 2018/05/10: 現在、受講途中…。進み具合に合わせて加筆・修正していきます。
* 2018/05/14: 加筆修正。一通りの説明を読み終えました。もう少し整理します。
* 2018/05/15: tensorflowに関する部分を抜き出した別記事へのリンクを追加。