ゼッタノート

ゼッタが勉強したことをまとめたノート代わりのブログ。たまに日記になるかも。

勉強したこと:Linear SVC編

勉強したこと記事、記念すべき第1回はLinear SVCについてです。scikit-learnのチートシートで、データ数10万未満のクラス分類をやろうとしたとき一番最初にぶち当たるやつですね。今回はそのLinear SVCについて勉強したことについて、ここにまとめます。

今回は以下の記事を軸に勉強していきました。
Linear SVC(クラス分類 )(SVM Classification)【Pythonとscikit-learnで機械学習:第3回】

※注意!この記事は機械学習入門者が独自に調べ独自に解釈した内容により構成されているため、誤った記述が含まれている可能性があります。

本題に入る前に

このブログの方針について書いておきます(挨拶記事に書いておくべきだった...)。 想定している読者は私と同レベルの機械学習入門者です。記事の書き方として、関連した内容ごとに章を分けて書くのではなく、自分が調べた順に書いていこうと思っています。なので、話があっちこっちに飛んで、既に記事内容について知っている方にとっては読みづらいものになるかもしれません。ですが、私と同じ機械学習入門者にとってはつまずくであろうところを順に解消していけるため、一緒に学んでいける記事になるのではないかと思っています。

それでは本題に入っていきましょう。

Linear SVCって?

そもそもLinear SVCって何者なんでしょう?一言でいえばあるデータ群をグループ分けできるような境界を求めるアルゴリズムです。もう少し詳しく言うと、マージンができるだけ大きくなるようにパラメータを調整し、境界面を確定するアルゴリズム、と言えるでしょうか。これについては後で述べます。参考サイトではワインの色と成分から、使用されているブドウの品種を推定しようとしています。

コードを書いてみる

参考サイトのコードを見てもライブラリに関する知識がほとんどなかったのでちんぷんかんぷんでした。以下調べたこと。

pd.read_csv

まずpandasに含まれるread_csvpythonにもcsvファイルを読み込む機能があるのに、なんでpandasにも含まれているんだろうと思いました。そのあとに続くコードを見てもらえれば分かると思いますが、pd.read_csvcsvを読み込むことで、DataFrame型としてファイルを読み込むことができるんですね。それによってDataFrameの特徴を活かしたデータ利用ができて扱いやすいと。とても単純なことでした。

plt.scatter()

散布図を描けるメソッド、というのは知っていたのですが、色を指定する部分でDataFrame型を渡しています。自分はてっきり"red"とか"blue"とかで指定するものだと思っていたので困惑しました。引数として渡されているzはブドウの種類(数字で1~3)が格納されています。このDataFrameを渡してあげると、メソッド側で色を三色用意してそれぞれに割り当ててくれるようです。とても便利ですね。
ちなみに参考サイトのコードではzの各要素から1を引いていますが、引かなくても配色が変わらなかったところを見るに数字自体に特に意味はなさそうです。

preprocessing.StandardScaler()

データを標準化してくれるクラスのようです。ですが、そもそも標準化って何なんでしょう。そしてそれって何のためにするんでしょう。調べました。

標準化って?

以下のサイトを参考にしました。
統計における標準化の意味と目的-具体例で学ぶ数学

このサイトによると、あるデータXに対して

\require{color}\textcolor{red}{Y = \frac{X-\mu}{\sigma}}

の操作を行い、変換後のデータの平均と分散がそれぞれ0, 1となるような操作を標準化という、と書いてあります。具体例と、統計における標準化の目的についてはリンク先をご覧ください。

ここまでで統計におけるありがたみはわかりましたが、機械学習におけるありがたみについては未だにピンと来ませんでした。もう少し調べているとこんな記事が。
Feature Scalingはなぜ必要?
上記の記事に以下のような記述がありました。

Δw_xxの大きさに、Δw_yyの大きさにそれぞれ依存していることがわかります。 もし、xのスケールが大きく、反対にyのスケールが小さい場合は、 w_yの更新幅は小さく、学習がw_xに比べて遅くなってしまいます。その結果、パラメータ間で更新幅に偏りが生じます。

今回のコードのように、色と成分、そして予想するものはブドウの品種と、異なる要素間で学習を行う場合は標準化を行うことでスケールを揃え、学習速度を同じにする必要がある、ということのようです。例えば今回のようにスケーリングを行わずに2つの要素について学習を進めると、一方の学習は十分済んだのにもう一方の要素について学習が不十分、という事態が発生し適切に予測できなかったり、その不十分な分を補うためにさらに学習を行わせると、今度は学習進度の高かった要素で過学習が発生してしまう恐れがある、ということでしょうか。その点、スケーリングを行えば効率も良くなるし、一方のパラメータのために過学習が発生してしまうということもなく、精度を高めることができる、と。

sc.fit()

コードの話に戻ります。StandardScalerクラスにfitメソッドがあるのですが、これがまた何なのかわからなかった。特に値が返ってくるわけでもなく実行してそのままのコード。StandardScalerについてのリファレンスを見てもらえれば分かりますが、この後実際に標準化を行う際に用いるデータの平均値と分散を求めるメソッドのようです。計算した値はどこに行ってるのか疑問に思って実際にコードを見てみると、単純にアトリビュートとして保存されているようでした。python(というかプログラミングそのもの)に慣れていないのがばれてしまう...

svm.LinearSVC(loss, C, ...)

ようやくLinearSVCという文字列が出てきました。このクラスで実際に学習を行うようですが、引数がたくさんあります。今回はlossとCの2つに絞って調べました。

損失関数

lossとは損失関数のことでした。以下のサイトが大変参考になりました。
機械学習で抑えておくべき損失関数(分類編)
以下自分なりのまとめ。

損失関数とはその名の通り、予測結果の損失を表す関数です。損失、というぐらいですからこの値は小さいほうが良くて、LinearSVCでは各データにおいて損失関数の値を計算し、その総和がより小さくなる方向に学習を進めています。また、正解ラベルt (1 or -1)を用意することで目的関数のftfについて以下の法則が出来ます。

正解tが 1 であり、出力fが正→tfが正
正解tが 1 であり、出力fが負→tfが負
正解tが -1 であり、出力fが正→tfが負
正解tが -1 であり、出力fが負→tfが正

このように、fが正解をはじきだせば正に、間違いをはじき出せば負の値が出力されることになります。ここで自分が嵌ってしまったことですが、目的関数が直線とした場合、y = wx + bと表すこともできますが、fとはwx+b-y = 0yを移行したものということです。この後、ヒンジ損失は正解からの距離も反映した損失関数である、ということに触れますが、このことを把握しておかないと理解ができないので、一応...

今回のサンプルコードでは損失関数の引数に "hinge" を渡しています。これはヒンジ損失というもので式で表すと以下のようになります。

L(t, f(w,x)) = max(1 - tf(w, x), 0)

先ほど述べた通り、tfは正解ならば正となるので、境界面から遠く離れた、ばっちり正解しているデータについては損失0に、境界面近傍のデータは小さい損失を、境界面を大きくまたいだ大不正解なデータについては大きな損失を与える関数となっています。つまり、正解・不正解だけを判定するのではなく、どの程度で正解・不正解なのかをも反映した損失関数となっています。

正則化係数

Cとは正則化係数のことでした。以下のサイトが大変参考になりました。
線形SVM - Qiita
以下自分なりのまとめ。

分類できるような境界を引く、といってもその引き方(具体的には式)には数えられないほどのパターンがあります。ある直線を境界線として引くことができたとして、その傾きをほんの少しだけ動かしたものも、境界線として機能することでしょう。ではそこからまた少し動かしたものは?切片を少しずらしたものは?このままでは終わりが見えないので何が「良い境界線」なのかを定義してあげる必要があります。それは各クラスのデータ集合が境界線からできるだけ離れているもの、ということです。各クラスのデータ集合と境界線からの距離、と言ってもデータ集合のどこからの距離なのかを決めてあげる必要があります。そこで特徴ベクトルというものを定義します。これはデータ集合の内、一番境界線と近いデータのことです。そして特徴ベクトルと境界線の距離のことをマージンと呼びます。
押さえておきたい用語について説明できたところでハードマージンSVM、ソフトマージンSVMについて触れます。
ハードマージンSVM
ハードマージンSVMとは、一切の妥協を許さないSVMです。一切の妥協を許さない、というのはどういうことかというと、ある境界によってデータが完全に分離可能である、という仮定の下で行うSVMによる分類をハードマージンと呼びます。 マージンについて”ハード”ということで、あるデータが境界線を越えることはないよね、そういう仮定で分類するからね、ということです。

ソフトマージンSVM
しかし、現実問題として、ハードマージンSVMのようにそこまで綺麗に分類できるデータは稀でしょう。そこで、ソフトマージンSVMというものがあります。これは、一定の妥協を許します。例えば境界面ギリギリの分類だったり、中には境界面を越えて存在するデータもあるでしょうが、それらについては一定の損失を与えるにとどめて妥協します。このような緩和処理を施す考え方をソフトマージンと呼びます。

さて、では正則化係数とはいったい何なのか、それはソフトマージンにおいてどの程度間違いを許容するかを表す係数のことです。ということは今回行っているのはソフトマージンによる分類だったんですね。このCを大きくしていくとハードマージンに近づいていきます。

K分割交差検証って?

サンプルコードを打っているだけなのに知らないことだらけで疲れてきましたね。ですが後2項目で終わりなので、頑張っていきましょう。
サンプルコードではcross_validationからcross_val_scoreを呼び出していますが、これはどうやら古いやり方のようで、今はmodel_selectionから呼び出すようです。参考にしたのは以下の記事
scikit-learn を用いた交差検証(Cross-validation)とハイパーパラメータのチューニング(grid search)
以下自分なりのまとめ。

K分割交差検証について触れる前にトレーニングデータとテストデータについて触れておきます。トレーニングデータとは機械学習を行う上で学習素材とするデータ群のことです。テストデータとは、学習した結果が正しいかどうかを確かめるためのデータ群のことです。例えば、今回のワインのデータをトレーニングデータとテストデータに分け、トレーニングデータを用いて学習を行います。そのあと、テストデータからワインの色と成分を取得し、その出力結果が正しいかどうかを比較検証します。

そこでK分割交差検証とは、データをK分割し、そのうちの一つをテストデータに、残りのK-1個をトレーニングデータとして学習を行い検証する、ということをK回繰り返します。最後にK回分の平均を取ることで、正解率と、その標準偏差を求めます。

っていうのをmodel_selection.cross_val_score()で行っています。引数のcvで何分割するか、というのを指定します。

model_selection.train_test_split()

こちらも検証です。ただK分割交差検証と違い単純にトレーニングデータとテストデータに分けます。第1引数に複数の配列形式のデータを指定できます。返り値の順番は1番目の配列のトレーニングデータ、テストデータ、2番目の配列のトレーニングデータ、テストデータ...となるようです。引数のtest_sizeでデータの内何割をテストデータとするかを指定します。
さて、サンプルコード通りに分割してそれぞれの中身を見てみるとブドウの品種を格納しているzの値が不思議なことになっています。最初はどうなっているのか分かりませんでしたが、どうやらこのメソッドは分割する際データのシャッフルを行うようです。なぜこんなことをするのか?以下のサイトが参考になりました
【Deep Learning with Python】機械学習の基礎
以下自分なりのまとめ。

レーニングデータとテストデータに分ける際なぜシャッフルするのか?それは、レーニングデータとテストデータともに、それらのデータが正しくデータセットを代表するものでなければならないからです。例えば今回のようにブドウの品種を推定する場合、元のデータはブドウの品種ごとにソートされています。これを上から取ってトレーニングデータに、残りをテストデータとして分割してしまうと、トレーニングデータには品種3のデータが含まれず正しく分類できないだけでなく、トレーニングデータに含まれていなかったブドウの品種3に対してテストを行うことになってしまい、学習としてもテストとしても全く機能しません。それを避けるためにデータをシャッフルしたうえでトレーニングデータとテストデータに分けます。
一方でシャッフルするべきでない場合もあります。それは天気推定のような時系列データを扱う場合です。このようなデータをシャッフルして分割してしまうと、未来を知っている状態で未来を予測することになります。これでは実用上まったく意味をなさないので、時系列データを扱う場合はシャッフルをするべきではありません。

という感じでしょうか。今更ですが、モジュール、とても便利だ...

まとめ

Linear SVCとは分類問題を解く機械学習アルゴリズムである。このようなアルゴリズムは損失関数の総和が最小になる方向に学習することでその目的を達成しようとする。また、精度を高めるために、使用するデータは事前に標準化をしておく必要がある。学習結果の評価方法には様々な方法があるが、scikit-learnにはそれらが簡単に行えるクラスやメソッドが含まれているので、それらを活用するとよい。




と、いうことで今回の勉強したこと:Linear SVC編はこれにて終わりとなります。機械学習について本格的に学ぶのはこれが初めてだったので知らないことがたくさんあり、勉強量も記事内容も膨大になってしまいました。勉強するのも、この記事を書くのも疲れました...
以降もこのようなスタイルで書いていくことになると思います。また冒頭にあります通り間違えたことを書いている可能性が高いですので、もし気づいたことがありましたらご指摘いただけると大変嬉しいです。

それでは今回はここまで。読んでいただきありがとうございました。