SGD(クラス分類)【Pythonとscikit-learnで機械学習:第1回】

本シリーズでは、Pythonを使用して機械学習を実装する方法を解説します。

また各アルゴリズムの数式だけでなく、その心、意図を解説していきたいと考えています。

第1回では

・PCにPythonの機械学習環境をそろえる方法

・クラス分類のSGDを実装する方法

・SGDのやりたいこと

について、解説します。

Pythonで機械学習環境を構築

まずはじめに手元のPCで機械学習をする環境を構築します。

今回はWindowsで説明します。

①「Anaconda」をPCにインストールします。

AnacondaはPythonと機械学習でよく使うライブラリを、まとめてインストールしてくれます。

●参考:Anacondaのインストール方法

②Anacondaで入っていない外部ライブラリをインストールします。

スタートアップからAnaconda Promptを開きます。

と打ちこみ、途中Y enterでインストールします。

mlxtendは機械学習結果をプロットするのに使用します。

③ Windowsのスタートメニューからjupyter notebookを選び、Launchを選択します。

jupyter notebookはpythonの実行環境のひとつです。

今回はこれを使用します。

●参考:Jupyter Notebookの基本的な使い方

以上で環境構築は終了です。

早速機械学習を実装します。

クラス分類:SGDの実装

概要

機械学習を分類すると

・クラス分類(classification)

・グループ分け(clustering)

・次元圧縮(dimensionality reduction)

・回帰(Regression)

に分類されます。

※推薦(Recommendation)を追加する場合もあります。

本日は機械学習のなかでも「クラス分類」というジャンルを行います。

クラス分類をするにあたり、SGD(stochastic gradient descent)というアルゴリズムを使用します。

本記事シリーズではscikit-learn(サイキット・ラーン)と呼ばれる機械学習ライブラリを利用します。

scikit-learnとは

scikit-learnではデータに対して、「どの機械学習アルゴリズムを使うべきか?」を示したマップを公開しており、今回使用するSGDは以下の場所にあります。

[scikit-learnのマップ]

START→データが50以上→カテゴリーデータ→ラベルありデータ→データ数10万以上→「SGD」

今回やりたいことは、「ワイン178本のデータを使用し、未知のワインの色とプロリン(アミノ酸の一種)の量の2変数から、その未知のワインが3つのブドウ品種のどれから作られたのかを識別する識別器を作成すること」です。

使用するデータを整理すると以下のようになります。

ラベル:0, 1, 2(ブドウの品種)

データ:178(ワインの数)

変数1:ワインの色

変数2:ワインに含まれるプロリンの量

これに対して、SGDというアルゴリズムを使用します。

SGDについては後ほど紹介します。

まずは実装例をご覧ください。

実装

解説1:ライブラリのインポート

ここでは今回使用するライブラリをインポートしています。

PCにインストールしてても、インポートしないと使えないので注意してください。

解説2:Wineのデータセットを読み込む

https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data

からWineのデータを読み込んでいます。

そのうち、品種(0列、1~3)と色(10列)とプロリンの量(13列)を使用します。

Wineデータの詳細はこちら

解説2までを実行すると、以下のようにデータが格納されていることが分かります。

解説3:このままでは分かりにくいので一度プロットしてデータがどうなっているのかを見てみます。

真ん中らへんはごちゃってしますが、概ね分かれているようです。

それではこれから3つのブドウ種類を識別するための、「線形な識別平面(今回は直線)」を求めます。

今回はクラスが3つあるので、マルチクラスの分類です。

そこで、“one versus all” (OVA) と呼ばれ、クラス1とその他、クラス2とその他、クラス3とその他を分ける線を求めて分離します。

解説 4:データの整形をします。

データを正規化しておきます。

解説5:機械学習で分類します。

今回は線形分離のSGDを用いたClassifierを作成します。

詳細はまたのちほど説明します。

解説6:実際の識別器がどの程度の性能を持つのかをK分割交差検証法で検討しています。

これはデータをK等分して、そのうち1固まりをテストデータにし、残りのK-1個を学習データにして正答率を検討する手法です。K回分の平均をとることで正答率と正答率の標準偏差を求めることが出来ます。

解説7:トレーニングデータとテストデータに分けて実行してみる

解説6のK分割法だけでは視覚的に見えず楽しくないので、一度実際にデータを分割して実行してみます。

上図には求めた識別平面と、テストデータが表示されています。

真ん中あたりでミスがありますが、概ね正解しています。

解説8:任意のデータに対して識別した結果を求めています。

ただし、正規化した後の値を入力しています。

解説9:求めた識別平面(今回は線)の式を求めています。

以上が実装の例です。

ワインの色とプロリンの量からで、85%くらいの正答率で元のブドウ種を推定できるようです。

それでは「結局、SGDって何をやっていたの?」を説明します。

SGDアルゴリズムの心

SGDはStochastic Gradient Descentの略称です。

日本語では確率的勾配降下法となります。

SGDでやっていることの意図を説明します。

※SGDの詳細と正確な情報は以下のリンクの通りです。

scikit-learnの解説1

scikit-learnの解説2

名前に確率的とか出てきて、なんか難しそうですが、なんのことはないです。

求めたいのは「識別平面の係数」です。

そこで適当な初期値の係数で識別平面を作ってみて、テストデータで識別してみます。

もちろん最初から完璧に識別はできていないです。

そこで、識別できていない具合を損失関数を導入し、計算してみます。

今回の実装の例では”hinge”というhinge関数を使用しました。

(”log”にすればlog関数で損失を計算します。これはロジスティック回帰をしていることと同じになります)

求めたいの識別平面の係数でしたが、そのためにはこの損失関数を最小化すれば良いことになりました。

今回はStochasticという名前がついており、確率的だそうです。

確率的とかなにやら難しそうですが、ただ全部のテストデータを一度に使わずに、データをひとつずつ使用するだけです。

テストデータひとつで識別平面の係数を変化させてしまうと、全部のデータを使用した場合に比べて少し変化の仕方が違うかもしれないですが、確率的にはほぼ合っているだろうという心です。

ではテストデータひとつを使ってどう変化させるのかです。

ここでGradient Descent(勾配変化法)の出番です。

ひとつのテストデータを使って損失関数を計算します。

そして、識別平面の各係数をそれぞれ少しずつ変化させたときに、最も損失関数が小さくなる方向を求めてあげます

(正確には損失関数の偏微分の値を解析的に求めてから、テストデータひとつを代入して方向を決定しています)。

あとは、その方向にちょびっとだけ、識別平面の各係数を変化させます。

これを繰り返すことで、損失関数が最小となる場所が求まります

(正確には極小)。

以上がSGDの心です。

とはいえ、いろいろ疑問が湧くかと思います。

疑問1:なんで損失関数とか使うの? 識別できた正答率で良いのでは?

理由は2つあります(正確には同じことですが)。

一つ目は、識別できた正答率では、完全に識別できた時点で識別平面が変化しなくなります。

つまり最後に正解になった一個は、ぎりぎりの場所にあることになります。

これだと、テストデータに対しては性能が悪そうな気がします。

二つ目は正答率は不連続で不便だからです。不連続なため、傾きを求めて、識別平面を連続的になめらかに変化させていって、ちょっとずつ良い係数を求めるということができません。

この2つの理由から正答率そのものでなく、損失関数を導入して、微分可能にし最高の識別平面を探します。

疑問2:損失関数には何を使えば良いの?

scikit-learnのdefaultではhinge関数になっています。

基本的にはhinge関数で良いですが、log関数にすれば識別確率を求めることができるようになります。

つまり、「とあるデータはクラス2と分類されましたがその確率は80%です」といったことが分かるようになります。

結局は識別平面からの距離を正規分布に基づく確率にしているだけですが、確率に基づく判断がしやすいので、識別結果を利用しやすいです。

またその他の関数もありますが、結局やりたい心は「識別平面が間違えていることに対して、距離に応じてどれくらいペナルティを与えるのか」を関数の形で変えていることになります。

ですが、ひどい外れ値がない場合、どの関数でもSGDの結果はあまり変わらない場合が多いです。

疑問3:SGDにデフォルトで設定されているl2って何?

l2は「L2正則化をかけます」という意味です。

学習データの数が、求めたいパラメータに対して少ない場合、少ない学習データに対して過度に適合してしまいます。

これを過学習と呼びます。

正則化はこの過学習を防ぐための方法です。

損失関数にpenalty項を追加します。

このpenalty項にはL2とL1があり、それぞれ求めるパラメータの「2乗和」と「絶対値の和」となっています。

L1の方がよりパラメータがスパースになる(たくさんのパラメータを求めているときに、0になるパラメータが多くなる)という特徴があります。

本記事のワインの例題では学習するパラメータが3つに対して、データ数が多いのであまり正則化は影響しませんが、うまく正則化を使うと高次元パラメータの推定に役立ちます。

以上、Pythonとscikit-learnで学ぶ機械学習入門|第1回:クラス分類 -SGD-でした。

次回は、Kernel approximationについて解説します。

【目次】Python scikit-learnの機械学習アルゴリズムチートシートを全実装・解説
scikit-learnのアルゴリズムチートマップで紹介されている手法を、全て実装・解説してみました。 ...