【scikit-learn】機械学習チュートリアル-まとめ【Python】

本ページでは以下のページを要約するとともに、個人的な解説も記載しています。独特な解釈をしている部分があるので、誤りなどの指摘はtwitterまでお願いします。

参考ページ
はじめに — scikit-learn 1.1.2 ドキュメント
https://scikit-learn.org/stable/tutorial/basic/tutorial.html
閲覧日:2022年10月8日

https://www.yosoaidol.com/p/scikit-learnpython.html
よりも少しだけ高度な内容になっています。

こちらのページではscikit-learnの学習を進める上での用語と簡単な学習の例を紹介します。

問題設定

一般的に機械学習では、任意の数のデータから、未知のデータから得られる解答を予測しようとする試みです。1つ1つのデータが複数の値からなる場合、そのデータは複数の「特徴」があるという表現をします。
※「特徴」、「特徴量」などと表現されます。

問題はいくつかにカテゴリに分類することができます。

教師あり学習

既知のデータから、そのデータに付属するグループや値を予測します。
さらに教師あり学習は次の2つがあります。

分類

それぞれのデータは2種類以上のグループに所属しています。既にグループのラベル付けされたデータから、ラベルがついていないデータのグループを予測する学習のことを分類と呼びます。
分類問題の例としては、手書きの数字認識があります。この問題の目的は、入力された値(画像データ)を有限個のグループどどれかに割り当てることです。

回帰

予測される値が分類のように有限個のグループではなく、連続変数になる場合の問題を回帰と呼びます。回帰問題の例として、鮭の体長を年齢と体重の関数として予測するような問題があります。

教師なし学習

学習用のデータは、教師あり学習のデータと異なり答えとなる教師データを持ちません。
この問題の目標は3つあります。
・クラスタリング・・・大量のデータを複数のグループに分割する
・密度推定・・・データを生成する確率密度関数を推定
・次元削減・・・高次元からデータを射影し、次元数を減らす

データの分割

機械学習はデータを分析し学習、予測を行います。学習と予測に使うデータが同じだと、そのAIモデルを評価した際に高い値が出てしまい、未知のデータに本当に有効なものかはわかりません。
 そこで、データセットを学習用とテスト用に分割します。学習用のデータのことを本ブログでは「学習データ」とよび、評価用のデータのことを「テストデータ」と呼びます。
 学習データを使いAIモデルを作成し、テストデータを使ってAIモデルの評価を行います。

サンプルデータのロード

scikit-learnには手軽にAIモデルの作成の練習ができるように、分類用の「アヤメの品種」や「手書き文字」、回帰用の「糖尿病患者のデータ」などが用意されています。

今回はそのデータの呼び出し方やデータについて解説します。
公式ページではインタプリタを使用していますが、本ページではjupyter notebookでコードを動かしています。
次のコードで「アヤメの品種」と「手書き文字」のデータセットをロードします。

from  sklearn  import  datasets
#アヤメのデータセットを呼び出し
iris = datasets.load_iris()
#手書き文字のデータセットを呼び出し
digits = datasets.load_digits()

データセットは、全てのデータとデータに関するメタデータを保持するオブジェクトです。このデータの特徴は「.data」に格納されます。詳細は今後のブログでも解説予定ですが、公式はこちら

例えば、次のコードでは「手書き文字」のデータセットの特長量が表示されます。
※手書き文字データは「8×8」の画像データを1次元配列で表現しています。
digits.data
‘’’
array([[ 0., 0., 5., …, 0., 0., 0.],
[ 0., 0., 0., …, 10., 0., 0.],
[ 0., 0., 0., …, 16., 9., 0.],

そしてこちらで教師データを確認することができます。
どの画像がどの数字を表しているかを配列で格納されています。

digits.target
#array([0, 1, 2, ..., 8, 9, 8])

データ配列の形状は常に正しく表現されている訳ではありません。例えば先ほどの手書き文字のデータであれば、本来2次元で表されるべき画像データが、1次元の配列で表現されていました。
正しく表示させたい場合は、以下のコードで出力できます。

digits.images[0]
'''
array([[ 0., 0., 5., 13., 9., 1., 0., 0.],
[ 0., 0., 13., 15., 10., 15., 5., 0.],
[ 0., 3., 15., 2., 0., 11., 8., 0.],
[ 0., 4., 12., 0., 0., 8., 8., 0.],
[ 0., 5., 8., 0., 0., 9., 8., 0.],
[ 0., 4., 11., 0., 1., 12., 7., 0.],
[ 0., 2., 14., 5., 10., 12., 0., 0.],
[ 0., 0., 6., 13., 10., 0., 0., 0.]])
'''

詳細はこちらで解説していますが、今後本ブログでも記事にする予定です。

外部データの読み込みenter link description hereも可能です。こちらも今後記事にする予定です。

学習と予測

「手書き文字」のデータセットの場合のタスクは、与えられた画像(実際は8×8のそれぞれの画素の白黒の情報量を1次元配列で表している)から、それを表す数字を予測することです。
0から9のそれぞれのサンプルが学習データとして与えられ、AIモデルを作成し未知のデータを予測できるようにします。

scikit-learn ではfit(X, y)で学習し、predict(T)で予測を行います。

今回はサポートベクターマシンというアルゴリズムsklearn.svm.SVCを使用し、分類を行います。

分類用のサポートベクターマシンは以下のコードで呼び出します。

from  sklearn  import  svm
clf = svm.SVC(gamma=0.001, C=100.)

この例ではgammmaCを手動で設定します。細かい説明は省きますが、gammaは決定境界の複雑さに影響します。大きいほど複雑な境界になります。Cは誤分類を許容するパラメータです。

今回は前回ロードしたdigits.dataを利用してAIモデルを作成してみましょう
再掲

from  sklearn  import  datasets
digits.data
'''
array([[ 0., 0., 5., ..., 0., 0., 0.],
[ 0., 0., 0., ..., 10., 0., 0.],
[ 0., 0., 0., ..., 16., 9., 0.],
...
[ 0., 0., 1., ..., 6., 0., 0.],
[ 0., 0., 2., ..., 12., 0., 0.],
[ 0., 0., 10., ..., 12., 1., 0.]])
'''

fitメソッドを使用することで学習をすることができます。今回は、最後のデータ以外を使って学習し、最後のデータで予測を行います。
[:-1]で配列の最初から一番最後の一つ手前までのデータを取得しています。

clf.fit(digits.data[:-1], digits.target[:-1])
#SVC(C=100.0, gamma=0.001)

以下のコードで最後のデータが何の数値を表すのかを予測した結果を出力します。

clf.predict(digits.data[-1:])
#array([8])

「8」と予測されました。
実際の画像データは以下になります。

参考
以下のコードで画像に戻すことができます。

import  numpy  as  np
import  matplotlib.pyplot  as  plt
img = np.reshape(digits.data[-1:], (8,8))
plt.imshow(img, cmap=plt.cm.gray_r, interpolation='nearest')
plt.axis('off')
plt.show()

あなたはこの予測結果が正しいと判断しますか?結局元の画像の解像度が低いため、うまく予測できているかどうかはわかりません。

本データセットのもう少し詳しい説明はこちら。Recognizing hand-written digits

ルール

scikit-learnのestimatorはルールに従って設計されています。詳細なルールについてはこちらで定義されています。今後ブログでも扱う予定です。

特に指定がない限り、float64にキャストされます。
以下の例では、10行2000列で生成したnumpy配列を明示的にfloat32に指定しました。
その後、その配列をRBFSamplerと呼ばれる次元圧縮を行うアルゴリズムを使用し、10行100列のnumpy配列に変換しています。変換後の型を確認するとfloat64にキャストされています。

import numpy as np
from sklearn import kernel_approximation

rng = np.random.RandomState(0)
#10行,2000列のランダムな数値を生成
X = rng.rand(10, 2000)
#float32に型を設定
X = np.array(X, dtype='float32')
X.dtype
#dtype('float32')

#RBFSamplerは次元拡張、圧縮用のアルゴリズム
#ここでは10行100列の配列に次元圧縮をおこなっている
transformer = kernel_approximation.RBFSampler()
X_new = transformer.fit_transform(X)
X_new.dtype
print(X_new.shape)
#dtype('float64')

分類での予測値は、教師データとして使用した型がそのまま出力されます。
以下の例では、教師データとして整数値を入力した場合は整数値が、文字型として入力した場合は文字型として出力されています。
回帰の予測値はfloat64として出力されます。

from  sklearn  import  datasets
from  sklearn.svm  import  SVC

#分類用のアヤメのデータセットをロード
iris = datasets.load_iris()
#サポートベクターマシンの分類用アルゴリズムを使用して学習
#教師データとしては、品種を整数値(0,1,2,…)で表したものを使用
clf = SVC()
clf.fit(iris.data, iris.target)
#SVC()

list(clf.predict(iris.data[:3]))
#[0, 0, 0] 整数値で出力

#教師データとしては、品種を文字('setosa'など)で表したものを使用
clf.fit(iris.data, iris.target_names[iris.target])
#SVC()

list(clf.predict(iris.data[:3]))
#['setosa', 'setosa', 'setosa'] 文字型で出力

パラメータの再調整

学習した後でもパラメータの調整を行うことができます。
以前学習したものに上書きされるので、過去のものは残りません。
以下の例では、一度サポートベクターマシンのカーネル関数と呼ばれるハイパーパラメータをlinearにして学習したのち、rbfに変更し再学習をおこなっています。clf.set_params(kernel='rbf').fit(X, y)
の部分で再学習をおこなっています。この時、linearで学習したAIモデルに上書きされます。

import numpy as np
from sklearn.datasets import load_iris
from sklearn.svm import SVC
X, y = load_iris(return_X_y=True)

clf = SVC()
#サポートベクターマシンのカーネル関数(次元拡張のやり方を決める関数)を'linear'に設定
clf.set_params(kernel='linear').fit(X, y)
#SVC(kernel='linear')
clf.predict(X[:5])
#array([0, 0, 0, 0, 0])

#カーネル関数(次元拡張のやり方を決める関数)を'rbf'にして再学習
clf.set_params(kernel='rbf').fit(X, y)
#SVC()
clf.predict(X[:5])
#array([0, 0, 0, 0, 0])

マルチクラスとマルチラベルの学習

サポートベクターマシンなど分類で使用するアルゴリズムの中で、2クラス分類用のアルゴリズムがあります。
2クラス分類用のアルゴリズムを多クラス分類で用いるためには、考え方が3種類あります。詳細はこちら
・one-vs-the-rest(one-vs-all)
・one-vs-one
・error correcting output codes
scikit-learnでは、全ての分類用モデルに多クラス分類ができるよう設計されていますが、多クラス分類を行うとき指定することによって2クラス分類をどのように使って、多クラス分類を行なうのかを選択することができます。

以下のコードではOneVsRestClassfierという手法を使用して、2クラス分類モデルを多クラス分類に適応させています。

from sklearn.svm import SVC
from sklearn.multiclass import OneVsRestClassifier
from sklearn.preprocessing import LabelBinarizer

X = [[1, 2], [2, 4], [4, 5], [3, 2], [3, 1]]
y = [0, 0, 1, 1, 2]
#2クラス分類をOneVsRestClassfierという手法を使って多クラス分類をできるようにしている。
classif = OneVsRestClassifier(estimator=SVC(random_state=0))
classif.fit(X, y).predict(X)
#array([0, 0, 1, 1, 2])

また、以下のようなコードにすることで、予測値をワンホットエンコーディング化することができます。
しかし、4つ目と5つ目の結果をみてください。全ての予測ラベルが「0」になっています。これはどのラベルにも当てはまらなかったことを意味しています。出力方法を変えることで、このような違いも出てくることに注意してください。

y = LabelBinarizer().fit_transform(y)
classif.fit(X, y).predict(X)
#array([[1, 0, 0],
#       [1, 0, 0],
#       [0, 1, 0],
#       [0, 0, 0],
#       [0, 0, 0]])

このような教師データをワンホットエンコーディングすることで、複数のラベルを持つマルチラベルの予測を行うことができます。
教師データが「0~4」の5つのデータが存在するので、5列の配列に変換されます。
最終的な予測値のとして、それぞれのデータがどのラベルに属するのかを出力します。

from sklearn.preprocessing import MultiLabelBinarizer
y = [[0, 1], [0, 2], [1, 3], [0, 2, 3], [2, 4]]
y = MultiLabelBinarizer().fit_transform(y)
classif.fit(X, y).predict(X)
#array([[1, 1, 0, 0, 0],
#       [1, 0, 1, 0, 0],
#       [0, 1, 0, 1, 0],
#       [1, 0, 1, 0, 0],
#       [1, 0, 1, 0, 0]])