Lesson 15

機械学習をしてみよう

Lesson 15 Chapter 1
線形回帰

この Lesson では、scikit-learn を使って実際に機械学習を行ってみます。 まずは機械学習の中では最も基本的といえる線形回帰を行ってみましょう。 scikit-learn では SGD(確率的勾配降下法、Lesson 4参照)を用いた回帰分析であるSGD回帰が実装されているので、それを使います。 また、今回は scikit-learn から提供されているカリフォルニアの住宅価格のデータセットを回帰分析の対象とします。 では、SGD線形回帰を行うプログラムを見ていきましょう。ステップごとに分けて説明していきます。

まずはいくつかの必要なライブラリをインポートしておきます。 使い方の分からないものが含まれているかもしれませんが、それはこれ以降の説明で明らかになるでしょう。

Python
import numpy as np 
import pandas as pd
from sklearn import model_selection, preprocessing, linear_model
from sklearn.datasets import fetch_california_housing

次にデータセットを用意します。

Python
california_housing = fetch_california_housing()
df_california_housing = pd.DataFrame(california_housing.data, columns=california_housing.feature_names)
df_california_housing["Price"] = california_housing.target
print(df_california_housing)

california_housing = fetch_california_housing()によってカリフォルニアの住宅価格のデータを取得します。 このデータは、california_housing.dataによって住宅の価格以外のデータを、 california_housing.targetによって住宅の価格のデータを取得できるようになっています。 今回の線形回帰においては、前者が独立変数、後者が従属変数ということになります。 なお独立変数として住宅価格以外のすべてのデータを使う必要はないのですが、今回はそのような特徴量の選別は行わないことにします。

続いてpd.DataFrameによってデータを Pandas の DataFrame に変換するのですが、 まずは独立変数の部分だけを DataFrame 化しています。 またcolumns=california_housing.feature_namesとすることでデータを元に各列ラベルをつけています。 そしてdf_california_housing["Price"] = california_housing.targetによって目的変数の列"Price"を加えます(DataFrame はこのようにして新たな列を追加することができます)。

こうしてできた DataFrame df_california_housingを見てみましょう。 それぞれのデータは独立変数として MedInc、HouseAge、AveRooms、AveBedrms、Population、AveOccup、Latitude、Longitude の8つを持つことがわかります。 またデータは全部で20640個存在することもわかります。

次に、この DataFrame から学習およびテストのためのデータを作っていきます。

Python
X = california_housing_df.iloc[:, 0:8]
print("独立変数")
print(X)
Y = california_housing_df["Price"].values
print("従属変数")
print(Y)

X = california_housing_df.iloc[:, 0:8]とすることで独立変数部分をXに格納します。 ここではilocという DataFrame の機能を使っており、Python のリストのスライス記法のようにして一部を取り出すことができます。 [:, 0:8]というのは、すべてのインデックスの0から7列目までを取り出すという意味になります。

また従属変数をY = california_housing_df["Price"].valuesのようにしてYに格納します。 "Prices"という列の名前は従属変数として扱う際には特に必要ないので、.valuesによって値のみを取り出しています。

XおよびYがちゃんと意図した通りのものになっているか確認しておきましょう。

次は、データの前処理として独立変数の標準化を行います。

標準化というのは、Lesson 10 でも紹介しましたが、各独立変数間の単位やスケールにおける違いによって学習に影響が出ることを防ぐためのものです。 具体的にはデータ$X$の標準化は、平均を$\mu$、標準偏差を$\sigma$として、次のような式で表されます。 \[ X_{標準化} = \dfrac{X - \mu}{\sigma} \]

この標準化をすべての独立変数に対して行うことで、各独立変数は平均が0に、標準偏差(および分散)が1に変換され、 どれが重要な特徴量でどれがあまり重要でない特徴量であるかが学習しやすくなります。

scikit-learn には標準化を行うための機能が備わっています。

Python
standard_scaler = preprocessing.StandardScaler()
standard_scaler.fit(X)
X = standard_scaler.transform(X)

まずstandard_scaler = preprocessing.StandardScaler()によってStandardScaler(scikit-learn で標準化を行うためのクラス)のインスタンスを生成します。 そしてstandard_scaler.fit(X)とすることで、 standard_scalerが独立変数Xを標準化できるようにします(具体的には、Xの平均値や標準偏差を取得しています)。 最後にX = standard_scaler.transform(X)とすることで、標準化を行ったあとの独立変数をXに格納し直しています (同じ変数に入れ直す必要はないですが、標準化前のXはもう使わないのでこのようにしています)。

次に、データを学習のためのデータとテストのためのデータに分割します。

X_train, X_test, Y_train, Y_test = model_selection.train_test_split(X, Y, test_size=0.2, random_state=0)

scikit-learn には学習データとテストデータへの分割のための機能もあり、 上記のようにするだけで学習データ(X_trainY_train)およびテストデータ(X_testY_test)が得られます。 またこのときオプションで分割の方法をカスタマイズでき、今回はtest_size=0.2によってテストデータの割合を2割に(つまり学習データの割合は8割)、 random_state=0によって分割の結果を固定しています(学習の再現性を確保するためです)。

ではいよいよ回帰分析を実行してみましょう。

Python
sgd_regressor = linear_model.SGDRegressor(max_iter=1000)
sgd_regressor.fit(X_train, Y_train)

scikit-learn を用いると、このようなわずかな記述でSGD回帰を実行できます。 まずsgd_regressor = linear_model.SGDRegressor(max_iter=1000)により回帰分析のためのインスタンスを生成します。 max_iter=1000によって学習データを最大で何周分使うかを設定しています。 そしてsgd_regressor.fit(X_train, Y_train)とすることで、 先ほど用意したX_trainY_trainに対してSGD回帰を実行します。

ではSGD回帰を行った結果、回帰係数がどのようになったかを確認してみます。

Python
print("回帰係数")
print(sgd_regressor.intercept_) 
print(sgd_regressor.coef_)

sgd_regressor.intercept_によってバイアス(Lesson 3 での$\beta_0$)を取得でき、 sgd_regressor.coef_によって各独立変数に掛かる重み(Lesson 3 での$\beta_1$、$\beta_2、\cdots$の部分)を取得できます。

それではテストデータを用いて回帰分析の結果を評価してみましょう。

Python
Y_pred = sgd_regressor.predict(X_test)
MSE = np.mean((Y_pred - Y_test) ** 2)
print("平均二乗誤差")
print(MSE)

まずY_pred = sgd_regressor.predict(X_test)によって、 回帰モデルがX_testに対してどのように従属変数(住宅価格)を予測するかを取得します。 それとY_testを用いて、平均二乗誤差を計算しています。

以上のようにして、scikit-learn を使って線形回帰を行うことができました。 scikit-learn による線形回帰では、Ridge回帰やLasso回帰といった手法を行うこともできるので、ぜひ試してみてその結果を評価し、比較すると良いでしょう。

Lesson 15 Chapter 2
ロジスティック回帰

線形回帰の次はロジスティック回帰を行ってみます。 scikit-learn を使うとロジスティック回帰も簡単に試すことができます。 今回は scikit-learn から提供されているアヤメのデータセットを使います。 これはアヤメの花弁の長さなどのデータを独立変数、品種名を従属変数とするようなデータセットです。

まずは必要なライブラリをインポートします。

Python
import numpy as np 
import pandas as pd 
from sklearn import model_selection, preprocessing, linear_model
from sklearn.datasets import load_iris
from sklearn.metrics import confusion_matrix, accuracy_score, precision_score, recall_score, f1_score

続いてアヤメのデータセットを用意し、カリフォルニアの住宅価格のときと同様にして DataFrame 化します。

Python
iris = load_iris()
iris_df = pd.DataFrame(iris.data, columns=iris.feature_names)
iris_df["species"] = iris.target
print(iris_df)

作成した DataFrame を見ると、独立変数は4つあり、またデータの数は150個であることがわかります。 また従属変数である"species"を見ると0、1、2のいずれかの値がそれぞれ割り当てられています。 これらはそれぞれアヤメのある品種に対応しています(順に Setosa、Versicolour、Virginica)。 今回行うロジスティック回帰は主に2値分類のタスクを解くのに用いるので、 "species"が0または1であるようなデータのみを使って0と1の2値分類を行うことにします。

Python
iris_df = iris_df[iris_df["species"] 

上記のように、簡単に"species"が0または1であるようなデータのみを取り出すことができます。

続いてこのデータセットから学習データおよびテストデータを作っていきますが、今回は独立変数のうちの一部だけを用いてみましょう。 例えば"sepal width (cm)"という独立変数のみを使う場合には次のようにします。

Python
X = df.iloc[:, 1:2]
print("独立変数")
print(X)
Y = df["species"]
print("従属変数")  
print(Y)

このような独立変数の選別は、明らかに不要な独立変数をあらかじめ削除することで、学習の際に本当に重要な特徴量を抽出しやすいようにしたい、というときに使います。 なお今回のデータセットはどの独立変数が不要なのかを事前に判断できるものではないので、独立変数の一部を選ぶときにどうすればよいかを知るための参考にとどめておいてください。

次にデータの標準化を行います(上の例のように独立変数が1つだけであれば必要ありません)。コードはChapter1 と同様です。 また学習データとテストデータへの分割も先ほどと同様に行います。

Python
standard_scaler = preprocessing.StandardScaler()
standard_scaler.fit(X)
X = standard_scaler.transform(X)

X_train, X_test, Y_train, Y_test = model_selection.train_test_split(X, Y, test_size = 0.2, random_state = 0)

では準備ができたのでロジスティック回帰を行ってみましょう。 scikit-learn では次のようにしてロジスティック回帰を実行できます。 また回帰係数(logistic_regression.intercept_logistic_regression.coef_)を出力し、 テストデータX_testに対する回帰モデルの予測を取得(logistic_regression.predict)しています。

Python
logistic_regression = linear_model.LogisticRegression(max_iter=1000)
logistic_regression.fit(X_train, Y_train)

print("回帰係数")
print(logistic_regression.intercept_) 
print(logistic_regression.coef_)

Y_pred = logistic_regression.predict(X_test)

このロジスティック回帰モデルの評価として、今回は Lesson 3 で学んだ混同行列などの指標を使ってみます。 scikit-learn では、次のようにしてテストデータとモデルの予測データから混同行列、適合率、再現率、F1値などを自動で計算できます。

print("混同行列:", confusion_matrix(y_true=Y_test, y_pred=Y_pred))
print("適合率:", precision_score(y_true=Y_test, y_pred=Y_pred))
print("再現率", recall_score(y_true=Y_test, y_pred=Y_pred))
print("F1値:", f1_score(y_true=Y_test, y_pred=Y_pred))

scikit-learn を用いてロジスティック回帰を行うやり方を学びました。 興味があれば、学習に用いる独立変数の種類や数を変えてみて回帰を行い、混同行列などを求め、それらを比較してみましょう。

Lesson 15 Chapter 3
k-meansクラスタリング

scikit-learn を用いた機械学習の実践例として、最後に k-means によるクラスタリングを行ってみましょう。 データセットとしては先ほども用いたアヤメのデータセットを用いますが、 今回は3つの品種のデータを使って k-means によるクラスター数3のクラスタリングを行います。

ではコードを見ていきましょう。まずは必要なライブラリのインポートです。

Python
import numpy as np 
import pandas as pd 
from sklearn import preprocessing
from sklearn.datasets import load_iris
from sklearn.cluster import KMeans

                    

続いてデータセットをロードし、学習のためのデータを用意して、標準化を行います。

Python
iris = load_iris()
iris_df = pd.DataFrame(iris.data, columns=iris.feature_names)
iris_df["species"] = iris.target
print(iris_df)
                      
X = iris_df.iloc[:, 0:3:2]
print("学習データ")
print(X)

standard_scaler = preprocessing.StandardScaler()
standard_scaler.fit(X)
X = standard_scaler.transform(X)

今回は学習データとして"sepal length (cm)"および"petal length (cm)"の列を用いることにします。 そのためにiris_df.iloc[:, 0:3:2]としています。 なお k-means は教師なし学習なので、正解ラベルのデータ(ロジスティック回帰などのコードにおけるY)は学習には必要ありません。

それでは k-means を実行します。

Python
kmeans = KMeans(n_init=10, n_clusters=3)
kmeans.fit(X)
Y_pred = kmeans.predict(X)
print(Y_pred)

まずkmeans = KMeans(n_init=10, n_clusters=3)によってKMeansのインスタンスを生成します。 このときクラスターの数をn_clusters=3として指定します。 またn_init=10というのは、初期の重心の位置をランダムに変えながら何回 k-means を行うかを指定しています(繰り返した中で良い結果が得られたものが採用されます)。 そしてkmeans.fit(X)によって学習データから k-means による学習を行い、 Y_pred = kmeans.predict(X)によってその結果を得ます。 Y_predを見ると、それぞれのデータに 0、1、2 のいずれかのラベルが割り当てられていることがわかります。

ではクラスタリングの結果を可視化してみましょう。 まずは正解データに基づいて、"sepal length (cm)"を横軸、"petal length (cm)"を縦軸にとったグラフ上に"species"ごとに色分けしてプロットします。

Python
species = [iris_df[iris_df["species"] == i] for i in range(3)]
colors = ['r', 'g', 'b']
                      
for i in range(3):
    plt.scatter(species[i]["sepal length (cm)"], species[i]["petal length (cm)"], c=colors[i])
                      
plt.xlabel("sepal length (cm)")
plt.ylabel("petal length (cm)")
plt.title("iris dataset")
plt.show()

上記のようにplt.scatterを使うことで点をプロットでき、また色を指定できます。 実行結果は以下のようになります。

L15C3_kmeans1.png 正解データの図示(Matplotlib により作成)

続いて k-means の結果を図示します。 グラフ化しやすくするために先に予測の結果を DataFrame に"species_pred"として追加しています。

Python
iris_df["species_pred"] = Y_pred
species_pred = [iris_df[iris_df["species_pred"] == i] for i in range(3)]
colors_pred = ['c', 'm', 'y']

for i in range(3):
    plt.scatter(species_pred[i]["sepal length (cm)"], species_pred[i]["petal length (cm)"], c=colors_pred[i])

plt.xlabel("sepal length (cm)")
plt.ylabel("petal length (cm)")
plt.title("k-means predict")
plt.show()

結果の例は次のようになります。

L15C3_kmeans2.png k-means の結果(Matplotlib により作成)

それぞれの図を比較すると、k-means の結果正解データに近いクラスタリングができていることがわかります。

scikit-learn によって k-means を使ったクラスタリングを行う方法を学びました。 なお、クラスター数やクラスタリングの結果を評価する方法としてエルボー法やシルエット分析があるので、興味のある人は調べてみると良いでしょう。 これらについても scikit-learn を使えば簡単に実行できます。

k-means の結果について

先ほど、k-means を実行した結果データに 0、1、2 のいずれかが割り当てられていましたが、これらの数字は元々の正解データの数字(品種)と対応しているわけではありません (そもそも正解データを学習データに使っていないのでこれは当然のことです)。 Lesson 3 でも述べたように、クラスタリングの結果をどう解釈するかはそれを行った人次第です。このことを忘れないようにしましょう。

クラスタリングの可視化

今回は学習データとして2つの値("sepal length (cm)"と"petal length (cm)")を用いたので、クラスタリングの結果を可視化するのは簡単でした。 しかしもし4つの値すべてを用いる場合は、4次元のグラフとなりそのまま図示することは難しいです。 そこで登場するのが Lesson 3 で学んだ次元削減です。 scikit-learn には主成分分析(PCA)などの次元削減の手法が実装されており、簡単に試すことができます。