Pythonで統計・データ分析!〜基本統計量の活用と機械学習の基本:数学×Pythonプログラミング入門(4/5 ページ)
データ分析において最もよく使われる表形式のデータを取り扱う方法を見ていく。まず、pandasデータフレームの基本的な取り扱い方法を確認し、次に、各種の基本統計量を求める。また、基本統計量の可視化を行い、データの「見方」についても触れる。最後に、scikit-learnを使った回帰と分類の簡単な例を紹介する。
目標4: 機械学習(重回帰分析)を行う
いよいよ最後の目標です。重回帰分析により住宅価格の予測を行ってみましょう。重回帰分析の仕組みや計算方法については、[AI・機械学習の数学]の第7回で説明していますが、scikit-learnなどを使えば計算方法を知らなくても簡単にコードが書けます。実は、単回帰分析については、この連載の第5回で紹介しました。重回帰分析では説明変数が複数になるだけで、それ以外のコードは単回帰分析と同じです。そこで、「さわり」のレベルではありますが、もう少し機械学習の実践に踏み込んだものとしましょう。
重回帰分析では、まず、何を説明変数にするかが重要になってきます。住宅価格はおそらく、住宅そのものの要因と環境要因に大きく左右されるでしょうから、住宅そのものの要因として築年数と部屋数、環境要因として人口と世帯年収を説明変数としてみます。目的変数はもちろん住宅価格です。
重回帰分析だけでなく、機械学習では得られたパラメータの値が学習データに当てはまりすぎて、未知のデータに対する予測の精度が落ちる「過学習」が起こることがあります。そこで、学習データを訓練データとテストデータとにランダムに分け、得られた回帰式を基に、訓練データとテストデータのRMSEの値(平均二乗誤差MSEの√)を求めて評価を行ってみましょう。RMSEは予測値と実際の値の全体的な誤差の大きさを表すもので、以下の式で表されます。nがデータの件数、yîが予測値、yiが実際の値です。しかし、この式に従ってコードを書く必要はありません。sklearn.metricsモジュールのmean_squared_error関数に実際の値と予測値を指定し、引数squaredにFalseを指定するだけで求められます。
訓練データのRMSEが小さく、テストデータのRMSEが目立って大きい場合は過学習が起こっている可能性が大です*5。
学習データを訓練データとテストデータに分けるためのコードは、sklearn.model_selectionモジュールのtrain_test_split関数を使うと簡単に書けます。引数には、学習データの説明変数と目的変数を指定します。特に何も指定しなければ、訓練データの個数が75%、テストデータの個数が25%の割合になるように、ランダムにデータを分けてくれます。返される値は、訓練データの説明変数、テストデータの説明変数、訓練データの目的変数、テストデータの目的変数の4つです。
4. 重回帰分析を行うためのコードを書く
では、コードを書いてみましょう。ポイントは、
- 説明変数が複数あること
- 全てのデータを訓練データとテストデータに分けること
- RMSEの値を求めること
の3つです。複雑な繰り返し処理や条件分岐のない、単なる順次処理のコードなので、流れにそって見ていけば、意味は分かると思います。このコードについては動画でも解説しているので、1行ずつ追いかけていきたい方はぜひご視聴ください。
動画3 重回帰分析のコードの解説
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
# データを読み込み、説明変数と目的変数を用意する
df = pd.read_csv('./sample_data/california_housing_train.csv')
x = df[['housing_median_age', 'total_rooms', 'population', 'median_income']]
y = df['median_house_value']
# 訓練データとテストデータに分ける
x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=0)
# 重回帰分析を行う
model = LinearRegression()
model.fit(x_train, y_train)
# 予測値を求め、RMSEの値を表示する
y_train_pred = model.predict(x_train)
y_test_pred = model.predict(x_test)
print('訓練データのRMSE:', mean_squared_error(y_train, y_train_pred, squared=False))
print('テストデータのRMSE:', mean_squared_error(y_test, y_test_pred, squared=False))
# 出力例:
# 訓練データのRMSE: 80260.82977613159
# テストデータのRMSE: 80271.35915005974
train_test_split関数を使って、学習データを訓練データとテストデータにランダムに分ける。引数random_stateに値を指定すると、毎回同じ乱数が作成されるので、異なる方法を使った場合の結果を比較したいときに便利(値としては0や42がよく使われる)。LinearRegressionクラスのpredictメソッドに説明変数を指定すると予測値が求められる。mean_squared_error関数に実際の値と予測値を指定し、引数squaredにFalseを指定すればRMSEの値が求められる(指定しないとMSEの値が求められる)。
実行結果を見ると、RMSEは8万ドルぐらいとなっていることが分かります。また、訓練データの値とテストデータの値がそれほど離れていないので、過学習は起こっていないようです。しかし、もっと予測の精度を上げたい(RMSEを小さくしたい)ですね。そこで、散布図(図15)のところで触れたお話を思い出してみてください。住宅価格が500,000ドルより大きいのデータはひとまとめにされているようだということでした。
(df.median_house_value == df.median_house_value.max()).sum()
と入力して、最大値と同じ値のデータの個数を求めてみると、814件もあることが分かります*6。やはり、大きな値がひとまとめにされているようです。これではうまく予測ができないので、ひとまとめにされているデータは除外することにしましょう(実際の最大値は500,001なので、500,001未満のデータのみを使うことにします)。リスト23で、CSVファイルのデータをデータフレームに読み込んだコードの次の行に、以下のリスト24のコードを追加して、実行してみてください。
*6 住宅価格のそれぞれの値のデータ型は64ビット浮動小数点数です。ここではうまくいきますが、浮動小数点数同士を==演算子で比較すると、等しいと期待される場合でも、丸め誤差などにより等しくならないことがあるので注意が必要です。例えば0.1*3 == 0.3はFalseになります。
#
# これ以前はリスト23と同じ
#
df = pd.read_csv('./sample_data/california_housing_train.csv')
df = df.query('median_house_value < 500001') # この行を追加する
x = df[['housing_median_age', 'total_rooms', 'population', 'median_income']]
y = df['median_house_value']
#
# これ以降もリスト23と同じ
#
# 出力例:
# 訓練データのRMSE: 71415.98388655296
# テストデータのRMSE: 73493.16490078928
500,001以上のデータを除外するために、500,001未満のデータのみを取り出してデータフレームを作成しなおした。他のコードはリスト23と全く同じ。なお、このコードを実行すると元のデータフレームの内容が変わってしまうことに注意。元のデータフレームの内容をそのままにしておきたいのであれば、df1 = df.query('median_house_value < 500001')のように別の変数に代入するとよい。
RMSEが7万ドルを数千ドル超えた程度の値になり、当初の8万ドルと比べるとだいぶ小さくなりました。さらに、特徴量を追加したり、回帰分析のための他のモデル(リッジ回帰、ラッソ回帰、サポートベクター回帰など)を適用したり、パラメータを変えたり……とチューニングの余地はまだまだあります。また、部屋数と寝室数などのように、相関係数の大きな複数の説明変数を使うと予測の精度が落ちるといった問題(多重共線性)も考慮する必要があります。が、今回は機械学習の「さわり」ということなのでこれぐらいにしておきます(機械学習そのものよりもデータをどう取り扱うか、どう分析するかに重点を置いているので)。
この先に進みたい方は、「5分で分かる機械学習(ML)」や「僕たちのKaggle挑戦記」などを参考にしてください。用語については「AI・機械学習の用語辞典」が役に立ちます。
コラム ロジスティック回帰による分類を行う
機械学習のタスクとしては、回帰の他にも分類もあります。分類のためのモデルにもロジスティック回帰、決定木、サポートベクターマシンなどさまざまなものがありますが、ここでは、ロジスティック回帰の例を紹介しておきます。サンプルデータとしては、分類によく使われるアヤメの品種の例を使いましょう(あまりにも頻繁に使われるので、またそれか、と思われる方も多いかもしれませんね)。scikit-learnで提供されているサンプルデータを利用してみます。コードについては詳しく解説しなくても流れで理解できると思います。分類においても学習データを訓練データとテストデータに分けて評価を行いますが、ここではその処理は省略し、分類ができる最小限のコードを示すこととします。
from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
iris = datasets.load_iris() # サンプルデータの読み込み
x = iris.data # 特徴のデータ(がくの長さ、がくの幅、花びらの長さ、花びらの幅)
y = iris.target # 実際の品種(0: setosa、1: Versicolour、 2: Virginica)
# スケーリング
sc = StandardScaler() # 標準化を行う
sc.fit(x)
x_scale = sc.transform(x)
# モデルの適用
model = LogisticRegression()
model.fit(x_scale, y)
# 結果の表示
print(y) # 実際の品種(正解)
print(model.predict(x_scale)) # 予測結果
print(model.score(x_scale, y)) # 正解率
# 出力例:
# [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
# 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
# 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
# 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
# 2 2] # 実際の品種(正解)
# [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
# 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1
# 1 1 1 2 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
# 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2
# 2 2] # 予測結果
# 0.9733333333333334 # 正解率
ロジスティック回帰にはLogisticRegressionクラスを利用する。スケーリングとは説明変数の値を一定の範囲に収まるように変換すること。ここでは、平均を0に、分散を1に標準化した。標準化はStandardScalerクラスを使うと簡単にできる(この例ではスケーリングを行わなくても結果は得られるが、警告のメッセージが表示される)。結果は、150件中146件が正解(外れは4件)。
Copyright© Digital Advantage Corp. All Rights Reserved.