もっと長く勉強していたら、全体の点数はもっと良くなりますか?
この問いに答える一つの方法として、どれくらいの期間勉強して、どのような点数を取ったかというデータがあります。そして、そのデータにパターンがあるかどうか、そのパターンで、時間を増やすと、結果的に得点率も増えるのかどうかを試してみることができます。
例えば、1.5時間、87.5%というような時間スコアのデータセットがあったとします。1.61時間、2.32時間、78%や97%のスコアも含まれる。このように、任意の中間値(または任意の「粒度」)を持つことができるデータ型は、連続データとして知られています。
もう一つのシナリオは、A、B、Cのような数字ベースの成績ではなく、文字ベースの成績を含むアワースコアのデータセットがあるとします。成績は、A.23、A+++++++(そして無限大)、A * e^12を持つことができないので、分離できる明確な値です。このように、分割したり、より細かく定義することができない種類のデータ型を離散データという。
データのモダリティ(形態)、つまり勉強時間から何点を取るかを把握するために、回帰や分類を行うことになります。
です。
回帰は連続的なデータに対して行い、分類は離散的なデータに対して行います。回帰は、誰かの年齢を予測したり、物価の家を予測したり、変数の値を予測したりと、何でもありです。分類は、あるものがどのクラスに属するかを予測することです(例えば、腫瘍が良性か悪性か、など)。
注:住宅価格と癌の有無を予測することは小さな仕事ではなく、どちらも通常、非線形関係を含んでいる。線形関係は、すぐに分かるように、モデル化するのはかなり簡単です。
実世界の事例を中心とした実践的なプロジェクトで学びたい方は、「ハンズオン 住宅価格予測 – Pythonによる機械学習」と研究級の「ディープラーニングによる乳がん分類 – KerasとTensorflow」をご覧ください! 「ハンズオン 住宅価格予測 – Pythonによる機械学習」と「研究級の乳がん分類 – KerasとTensorflow」をご覧ください。
回帰も分類も – ラベル(対象変数の傘型)を予測するデータを使います。ラベルは分類タスクでは「B」(クラス)、回帰タスクでは「123」(数字)と何でもありです。私たちはラベルも提供するので、これらは教師あり学習アルゴリズムです。
この初心者向けガイドでは、PythonでScikit-Learnライブラリを使用して線形回帰を実行します。機械学習のパイプラインをエンドツーエンドで進めていきます。まず、学習するデータをロードし、それを可視化すると同時に、探索的データ解析を実行します。次に、データの前処理を行い、それに適合するモデルを(手袋のように)構築します。このモデルを評価し、良好であれば、新しい入力に基づく新しい値を予測するために使用します。
探索的データ解析
注:アワースコアのデータセットはこちらからダウンロードできます。
探索的データ解析から始めましょう。データを読み込んで特徴を可視化し、その関係を調べ、観察結果に基づいて仮説を立てるなど、まずはデータを知りたいと思うでしょう。データセットはCSV(カンマ区切り値)ファイルで、学習時間とその時間に基づいて得られたスコアが含まれています。Pandasを使って、データを DataFrame
にロードしてみます。
import pandas as pd
PandasとDataFrameに慣れていない方は、「PythonとPandasのガイド」をお読みください。DataFrame Tutorial with Examples” をご覧ください。
CSV ファイルを読み込んで、DataFrame
にパッケージしてみましょう。
# Substitute the path_to_file content by the path to your student_scores.csv file
path_to_file = 'home/projects/datasets/student_scores.csv'
df = pd.read_csv(path_to_file)
データを読み込んだら、head()
メソッドを使って最初の5つの値をざっと見てみましょう。
df.head()
この結果は
Hours Scores
0 2.5 21
1 5.1 47
2 3.2 27
3 8.5 75
4 3.5 30
また、shape
プロパティを使ってデータセットの形状をチェックすることもできます。
df.shape
データの形状を知ることは、データを分析したりモデルを構築したりする上で非常に重要です。
(25, 2)
25行2列、つまり、時間とスコアのペアを含む25個のエントリがあります。最初の質問は、より長く勉強した場合に、より高いスコアが得られるかどうかというものでした。要するに、時間とスコアの関係を求めているのです。では、これらの変数の間にはどのような関係があるのでしょうか?変数間の関係を調べるのに最適な方法は散布図です。X軸に時間、Y軸にスコアをプロットし、それぞれのペアに、その値に基づいてマーカーを配置します。
df.plot.scatter(x='Hours', y='Scores', title='Scatterplot of hours and scores percentages');
散布図が初めての方は、「Matplotlib散布図 – チュートリアルと例題」をお読みください! 散布図が初めての方は、「Matplotlib散布図 – チュートリアルと例題」をお読みください。
この結果、以下のようになります。
時間が長くなるにつれて、スコアも上がっていきます。かなり高い正の相関がありますね。点の作る線の形が直線に見えるので、Hours変数とScore変数の間に正の線形相関があると言います。どのように相関があるのでしょうか?corr()メソッドは、数値変数間の相関を計算し、
DataFrame` に表示します。
print(df.corr())
Hours Scores
Hours 1.000000 0.976191
Scores 0.976191 1.000000
この表では、HoursとHoursは 1.0
(100%) の相関があり、Scoreは当然Scoreに対して100%の相関があるのと同じです。どんな変数もそれ自身と1:1の対応関係を持つ! しかし、ScoreとHoursの相関は0.97
です。0.8`を超えると強い正の相関があるとみなされます。
線形変数間の相関について詳しく知りたい方、相関係数の違いについて知りたい方は、「Python with Numpyでピアソン相関係数を計算する」をご覧ください! 「Python with Numpy」は、Pythonで開発され、Pythonで開発されています。
線形相関
線形相関が高いということは、一般的に、ある特徴の値を、他の特徴から見分けることができることを意味します。計算しなくても、5時間勉強したら50%くらいになるとか。この関係は非常に強いので、このデータセットから、勉強時間に基づいてスコアを予測する、シンプルで正確な線形回帰アルゴリズムを構築することができます。
2つの変数の間に線形関係があるとき、我々は直線を見ます。3つ、4つ、5つ(またはそれ以上)の変数の間に線形関係があるとき、我々は平面の交点を見ることになる。どのような場合でも、このような性質は代数学で線形性と定義されています。
Pandasには統計的な要約のための素晴らしいヘルパーメソッドも同梱されており、データセットを describe()
してカラムの平均、最大、最小値などのアイデアを得ることができます。
print(df.describe())
Hours Scores
count 25.000000 25.000000
mean 5.012000 51.480000
std 2.525094 25.286887
min 1.100000 17.000000
25% 2.700000 30.000000
50% 4.800000 47.000000
75% 7.400000 75.000000
max 9.200000 95.000000
線形回帰理論
私たちの変数は、線形関係を表します。学習時間数から得点率を直感的に推測することができる.しかし、もっと正式な方法を定義できないだろうか。点間の線をたどり、与えられた「時間数」の値から垂直に線をたどれば「得点」の値を読み取ることができる。
任意の直線を記述する方程式は
y=a∗x+by=a∗x+bである。
y = a*x+b
この式で、yは得点率、xは学習時間です。bは線がY軸から始まる位置で、Y軸切片とも呼ばれ、a
は線がグラフの上側と下側のどちらに向かうか(線の角度)を定義するので、線の傾きと呼ばれます。
gt; 直線の傾きと切片を調整することで、直線を任意の方向に移動させることができます。このように–傾きと切片の値を把握することで、データに合うように直線を調整することができるのです
そうなんです。これが線形回帰の核心で、アルゴリズムは傾きと切片の値を求めるだけです。すでに持っているxとyの値を使い、aとbの値を変化させます。そうすることで、複数の直線をデータポイントにフィットさせ、すべてのデータポイントに近い直線、つまり最もフィットする直線を返します。その直線関係をモデル化することで、回帰アルゴリズムはモデルとも呼ばれます。このプロセスで、時間に基づいて割合を決定、または予測しようとすると、変数y
が変数x
の値に依存することを意味します。
注:統計学では、y
を従属変数、x
を独立変数と呼ぶのが通例である。コンピュータサイエンスでは、通常、「y」はターゲット、ラベル、「x」は特徴、または属性と呼ばれます。通常、予測したい変数とその値を求めるための変数があることを念頭に置いて、名前を入れ替えて使うことがわかります。また、統計学でもCSでも、小文字の代わりに大文字の「X」を使うのが慣例となっています。
PythonのScikit-learnによる線形回帰
PythonとScikit-Learnライブラリを使って線形回帰のアルゴリズムを実装してみましょう。まずは単純な線形回帰から始めて、新しいデータセットで重回帰まで拡張していきます。
データ前処理
前のセクションで、我々はすでにPandasをインポートし、ファイルを DataFrame
にロードし、線形関係の兆候があるかどうかを確認するためにグラフをプロットしています。ここで、データを2つの配列に分けることができます。1つは従属特徴、もう1つは独立、つまり対象特徴です。学習時間に応じて得点率を予測したいので、y
は「得点」列、X
は「時間」列となります。
ターゲットと特徴量を分離するために、データフレームのカラムの値を変数 y
と X
に帰属させることができます。
y = df['Scores'].values.reshape(-1, 1)
X = df['Hours'].values.reshape(-1, 1)
注意: df['Column_Name']
は、pandas の Series
を返します。いくつかのライブラリは、NumPyの配列と同じように Series
を扱うことができますが、すべてのライブラリがこのような認識を持っているわけではありません。場合によっては、データを記述している NumPy 配列を抽出したいと思うかもしれません。これは、 Series
の values
フィールドを使用することで簡単に行うことができます。
Scikit-Learnの線形回帰モデルは2Dの入力を想定しており、値を取り出すだけでは実際には1Dの配列を提供することになります。
print(df['Hours'].values) # [2.5 5.1 3.2 8.5 3.5 1.5 9.2 ... ]
print(df['Hours'].values.shape) # (25,)
これは、 LinearRegression()
クラス (これについては後で説明します) が、1つ以上の値を含む (しかし1つの値であることもある) エントリを想定しているため、2Dの入力を想定しているのです。どちらの場合でも、2次元配列である必要があり、各要素(時間)は実際には1要素の配列です。
print(X.shape) # (25, 1)
print(X) # [[2.5] [5.1] [3.2] ... ]
Xと
y` のデータを直接線形回帰モデルに与えることはできますが、すべてのデータを一度に使用した場合、その結果が良いものかどうか、どうやって知ることができるでしょうか?学習と同じように、データの一部を使ってモデルを学習し、別の一部を使ってモデルをテストするのです。
ということです。
経験則や分割の重要性、検証セット、ヘルパーメソッド「train_test_split()」についてもっと知りたい場合は、「Scikit-Learnのtrain_test_split() – Training, Testing and Validation Sets」の詳細ガイドを読んでください!>.
このメソッドには、 X
と y
の配列(DataFrame
でも動作し、1つの DataFrame
をトレーニングセットとテストセットに分割する)と test_size
を渡すことができます。test_size` は、データ全体に対するテスト用のデータの割合である。
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2)
このメソッドでは、定義した割合に従ってランダムにサンプルを取得しますが、サンプリングによって関係が完全に混同されないように、X-Y のペアを尊重します。一般的な訓練とテストの分割は、80/20や70/30です。
サンプリングプロセスは本質的にランダムなので,この方法を実行すると必ず異なる結果になります.同じ結果、つまり再現性のある結果を得るためには、人生の意味の値を持つSEED
という定数を定義すればよいのです(42)。
SEED = 42
注:seedは任意の整数で、ランダムサンプラーの種として使用されます。シードは通常ランダムであり、異なる結果を得ることができる。しかし、手動で設定しても、サンプラーは同じ結果を返します。人気小説「銀河ヒッチハイク・ガイド」シリーズにちなんで、42
をシードにするのが慣例となっている。
この SEED
を train_test_split
メソッドの random_state
パラメータに渡せばよいのです。
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = SEED)
X_train配列を表示すると、学習時間が表示され、
y_train` にはスコアパーセンテージが表示されます。
print(X_train) # [[2.7] [3.3] [5.1] [3.8] ... ]
print(y_train) # [[25] [42] [47] [35] ... ]
線形回帰モデルの学習
訓練セットとテストセットの準備ができました。Scikit-Learnには簡単にインポートして学習できるモデルがたくさんあり、LinearRegression
はそのうちの一つです。
from sklearn.linear_model import LinearRegression
regressor = LinearRegression()
ここで、データに対して直線をフィットさせる必要があります。そのためには、X_train
と y_train
のデータと共に .fit()
メソッドを使います。
regressor.fit(X_train, y_train)
エラーが出なければ、リグレッサは最適な直線を見つけたことになります。この直線は特徴量と切片/傾きで定義されます。実際、切片と傾きはそれぞれ regressor.intecept_
と regressor.coef_
属性を出力することで確認することができます。
print(regressor.intercept_)
2.82689235
傾き(x の係数でもある)を取得する場合。
print(regressor.coef_)
結果は以下のようになります。
[9.68207815]
これは文字通り、先ほどの式に当てはめることができる。
score=9.68207815∗hours+2.82689235score=9.68207815∗hours+2.82689235
score = 9.68207815*hours+2.82689235 となります。
これが推測と一致しているかどうか、実際に確認してみましょう。
hours=5score=9.68207815∗hours+2.82689235score=51.2672831hours=5score=9.68207815∗hours+2.82689235score=51.2672831
つまり、独立変数が1単位増加(または減少)したときに、従属変数がどうなるかを示すのが、傾きの値です。
予想すること
自分で計算するのを避けるために、値を計算する式を独自に書くことができる。
def calc(slope, intercept, hours):
return slope*hours+intercept
score = calc(regressor.coef_, regressor.intercept_, 9.5)
print(score) # [[94.80663482]]
しかし、我々のモデルを使って新しい値を予測するもっと便利な方法は、 predict()
関数を呼び出すことである。
# Passing 9.5 in double brackets to have a 2 dimensional array
score = regressor.predict([[9.5]])
print(score) # 94.80663482
結果は94.80663482
、つまり約95%です。これで、考えられるすべての時間について、得点の割合の推定値が得られました。しかし、その推定値は信頼できるのでしょうか?この疑問に対する答えが、そもそもデータをtrainとtestに分けた理由なのです。テストデータを使って予測を行い、その予測と実際の結果(グランド・トゥルース)を比較することができるようになりました。
テストデータに対して予測を行うには、 X_test
の値を predict()
メソッドに渡します。その結果を変数 y_pred
に代入することができます。
y_pred = regressor.predict(X_test)
変数 y_pred
には X_test
の入力値に対するすべての予測値が格納されます。これで、 X_test
の実際の出力値と予測値を、データフレーム構造で並べて比較できるようになりました。
df_preds = pd.DataFrame({'Actual': y_test.squeeze(), 'Predicted': y_pred.squeeze()})
print(df_preds
出力はこのようになります。
Actual Predicted
0 81 83.188141
1 30 27.032088
2 21 27.032088
3 76 69.633232
4 62 59.951153
我々のモデルはあまり正確ではないようですが、予測されたパーセンテージは実際のパーセンテージに近いです。実際のパフォーマンスを客観的に見るために、実際の値と予測値の差を数値化してみましょう。
モデルの評価
データを見て、線形関係を確認し、モデルを学習し、テストした後、いくつかの評価指標を用いて、そのモデルがどの程度予測できているかを理解することができます。回帰モデルでは、主に3つの評価指標を用います。
- 平均絶対誤差(MAE)。実際の値から予測値を引いて誤差を求め、その誤差の絶対値を合計して平均値を求める。この指標は、モデルの各予測値に対する全体的な誤差の概念を与え、小さいほど(0に近いほど)良い。
mae=(1n)n∑i=1|Actual-Predicted|mae=(1n)∑i=1n|Actual-Predicted| となる。
mae = (frac{1}{n})sum_{i=1}^{n}Lept|Actual – Predicted|Actual – Predicted|Lept
注: 方程式の中で y
と _177
という表記を目にすることがあるかもしれません。yは実際の値を、
ŷは予測された値を意味する。
-
- 平均二乗誤差(MSE): MAE指標と似ているが、誤差の絶対値を二乗する。また、MAEと同様、小さいほど、あるいは0に近いほど良い。MSE値は、大きな誤差をさらに大きくするように2乗される。注意しなければならないのは、通常、値の大きさと、データのスケールが同じでないという事実のために、解釈しにくい指標であるということである。
mse=D∑i=1(Actual-Predicted)2mse=∑i=1D(Actual-Predicted)2
mse = sum_{i=1}^{D}(Actual – Predicted)^2
- RMSE (Root Mean Squared Error)。3 RMSE(Root Mean Squared Error):MSEの最終値の平方根を求め、データの単位と同じにすることで、MSEの解釈の問題を解決しようとするものです。誤差を含むデータの実測値を表示する場合に、解釈しやすく、有効である。RMSEが4.35であれば、実際の値に4.35を足したか、実際の値を得るために4.35を必要としたために、モデルに誤差が生じたということになります。0に近ければ近いほどよい。
rmse=
⎷D∑i=1(Actual-Predicted)2rmse=∑i=1D(Actual-Predicted)2
rmse = sum_{i=1}^{D} (Actual – Predicted)^2} となります。
これら3つの測定基準のいずれかを使ってモデルを比較することができます(1つを選択する必要がある場合)。また、同じ回帰モデルを異なる引数値や異なるデータで比較し、その評価指標を検討することもできます。これはハイパーパラメータチューニングと呼ばれるもので、学習アルゴリズムに影響を与えるハイパーパラメータをチューニングして、その結果を観察するものです。
モデル間で選択する場合、通常、誤差が最小のものがより良い性能を発揮します。モデルを監視する場合、測定基準が悪くなったのであれば、以前のバージョンのモデルの方が優れているか、モデルの性能が悪くなるような重大な変化がデータにあったということです。
幸いなことに、メトリクスの計算を手動で行う必要はありません。Scikit-Learnのパッケージには、これらのメトリクスの値を求めるのに使える関数がすでに用意されています。それでは、テストデータを使って、これらのメトリクスの値を求めてみましょう。まず、MAEとMSEの誤差を計算するために必要なモジュールをインポートします。それぞれ、 mean_absolute_error
と mean_squared_error
と呼ばれます。
from sklearn.metrics import mean_absolute_error, mean_squared_error
ここで、y_test
(実測値)とy_pred
(予測値)をメソッドに渡すことで、MAEとMSEを計算することができます。RMSEは、MSEの平方根を取ることで計算できますので、NumPyのsqrt()
メソッドを使用します。
import numpy as np
メトリクスの計算には
mae = mean_absolute_error(y_test, y_pred)
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
また、メトリクスの結果は、f
という文字列を使い、カンマの後に:.2f
という2桁の精度で表示されます。
print(f'Mean absolute error: {mae:.2f}')
print(f'Mean squared error: {mse:.2f}')
print(f'Root mean squared error: {rmse:.2f}')
メトリクスの結果はこのようになります。
Mean absolute error: 3.92
Mean squared error: 18.94
Root mean squared error: 4.35
すべての誤差は低く、実際の値を最大で4.35%(低いか高いか)見逃しており、今あるデータを考えるとかなり小さい範囲です。
重回帰分析
ここまでは、1つの変数だけを使った線形回帰で値を予測しました。これは実生活でより一般的なシナリオであり、多くのことがある結果に影響を与えることができます。
例えば、アメリカの各州のガソリン消費量を予測する場合、ガソリン税という1つの変数だけを使って予測するのは限界があります。ガソリン税以外にも、ある地域の人々の一人当たりの所得、舗装された高速道路の延長、運転免許証を持っている人口の割合など、様々な要因がガソリン消費に関係しています。ある要因が他の要因よりも消費に影響を与える。ここで、相関係数が本当に役に立つのです。
このような場合、複数の変数を使うことが理にかなっていると、線形回帰は重回帰になります。
注:独立変数が1つの場合の線形回帰の別の命名法は、一変量線形回帰である。また、多くの独立変数による重回帰は、多変量線形回帰という。
通常、実世界のデータでは、より多くの変数があり、値の幅やばらつきが大きく、変数間の関係も複雑であるため、単回帰ではなく重回帰を行うことになります。
ということです。
つまり、日常的には、データに線形性があれば、重回帰を適用することが多いのです。
探索的データ解析
重回帰の実用的な感覚を得るために、ガス消費量の例で作業を続け、米国48州のガス消費量データを持つデータセットを使用してみましょう。
このデータセットには、米国48州のガス消費量のデータが含まれています。
注:ガス消費量のデータセットはKaggleでダウンロードできます。データセットの詳細については、こちらで確認できます。
線形回帰で行ったことに続き、重回帰を適用する前にデータも知っておきたいところです。まず、pandasの read_csv()
メソッドでデータをインポートします。
path_to_file = 'home/projects/datasets/petrol_consumption.csv'
df = pd.read_csv(path_to_file)
そして、df.head()
で最初の5行を見ることができます。
df.head()
この結果は
Petrol_tax Average_income Paved_Highways Population_Driver_licence(%) Petrol_Consumption
0 9.0 3571 1976 0.525 541
1 9.0 4092 1250 0.572 524
2 9.0 3865 1586 0.580 561
3 7.5 4870 2351 0.529 414
4 8.0 4399 431 0.544 410
Shape` を使って、このデータが何行何列であるかを見ることができます。
df.shape
これは次のように表示されます。
(48, 5)
このデータセットでは、48行と5列がある。データセットの大きさを分類する際にも、統計学とコンピュータサイエンスで違いがあります。
統計学では、30行以上または100行(または観測)以上のデータセットがすでに大きいと見なされます。一方、コンピュータサイエンスでは、データセットが「大きい」と見なされるには、通常少なくとも1000~3000行ある必要があります。また、「大きい」というのは非常に主観的なもので、3,000を大きいと考える人もいれば、3,000,000を大きいと考える人もいます。
私たちのデータセットの大きさについて、コンセンサスは得られていません。このまま探索を続け、この新しいデータの記述統計を見てみましょう。今回は、round()
メソッドで値を小数点以下2桁に切り上げ、T
プロパティで表を転置することで、統計量の比較を容易にすることにします。
print(df.describe().round(2).T)
表は行単位から列単位になりました。
count mean std min 25% 50% 75% max
Petrol_tax 48.0 7.67 0.95 5.00 7.00 7.50 8.12 10.00
Average_income 48.0 4241.83 573.62 3063.00 3739.00 4298.00 4578.75 5342.00
Paved_Highways 48.0 5565.42 3491.51 431.00 3110.25 4735.50 7156.00 17782.00
Population_Driver_licence(%) 48.0 0.57 0.06 0.45 0.53 0.56 0.60 0.72
Petrol_Consumption 48.0 576.77 111.89 344.00 509.50 568.50 632.75 968.00
注意:統計量間を比較したい場合は、転置された表がよく、変数間を比較したい場合は、元の表がよいです。
describeの表のminとmaxの列を見ると、我々のデータの最小値は0.45
で、最大値は17,782
であることがわかります。これは、データの範囲が17,781.55` (17,782 – 0.45 = 17,781.55) と非常に広いことを意味し、データのばらつきも大きいことを意味します。
また、平均値と標準偏差の列の値を比較すると、7.67
と 0.95
や 4241.83
と 573.62
など、平均値と標準偏差が実にかけ離れていることが分かります。これは我々のデータが平均から遠く離れ、分散していることを意味し、さらに変動性を高めています。
これは、0.45から17,782までフィットする直線を引くことが難しくなるため、統計学的に言えば、ばらつきを説明することが難しくなります。
いずれにせよ、データをプロットすることは常に重要です。形(関係)が異なるデータは、同じ記述統計量を持つことができます。では、このまま、ポイントをグラフで見てみましょう。
注)同じ記述統計量を持つ異なる形状のデータがある場合の問題は、「Anscombeの4重奏」と定義されています。その例はこちらで見ることができます。
また、異なる関係間で係数が同じになる例として、Pearson Correlation(線形相関をチェックするもの)があります。
このデータには明らかにパターンがありますね。しかし、それは非線形で、データには線形相関はありません。したがって、ピアソンの係数はほとんどの場合 0
となります。ランダムなノイズに対しても同様に 0
となる。
ピアソン係数についてもっと詳しく知りたい方は、「Python with Numpyでピアソン相関係数を計算する」を徹底的に読んでみてください! 「Python with Numpyでピアソン相関係数を計算する」を徹底的に読んでみてください! 「Python with Numpyでピアソン相関係数を計算する」を徹底的に読んでみてください。
gt;
単純回帰のシナリオでは、従属変数と独立変数の散布図を使って、点の形状が直線に近いかどうかを確認しました。現在のシナリオでは、4つの独立変数と1つの従属変数があります。すべての変数で散布図を作成するには、変数ごとに1次元が必要で、結果的に5次元のプロットになります。
すべての変数で5次元プロットを作成することはできますが、時間がかかり、少し読みにくいです。または、独立変数と従属変数の間に線形関係があるかどうかを見るために、それぞれについて1つの散布図を作成することができます。
オッカムのカミソリ(Occam’s razorとしても知られています)とPythonのPEP20 – “Simple is better than complex” – に従って、各変数のプロットでforループを作成しましょう。
注)オッカムのカミソリとは、複雑な理論や説明に対して、最も単純な理論や説明を優先するという哲学的・科学的な原則のことである。
今回は、Pandasがプロットする際に、Matplotlibの拡張機能であるSeabornを使用する予定です。
import seaborn as sns # Convention alias for Seaborn
variables = ['Petrol_tax', 'Average_income', 'Paved_Highways','Population_Driver_licence(%)']
for var in variables:
plt.figure() # Creating a rectangle (figure) for each plot
# Regression Plot also by default includes
# best-fitting regression line
# which can be turned off via `fit_reg=False`
sns.regplot(x=var, y='Petrol_Consumption', data=df).set(title=f'Regression plot of {var} and Petrol Consumption');
上のコードで、Seabornをインポートし、プロットしたい変数のリストを作成し、そのリストを通して各独立変数と従属変数をプロットしていることに注目してください。
私たちが使っているSeabornのプロットは `r
データの準備
単純な線形回帰で行ったように、データをロードして探索した後、それを特徴とターゲットに分割することができます。主な違いは、特徴量が1列から4列になったことです。
二重括弧 [[ ]]
を使ってデータフレームから選択することができます。
correlations = df.corr()
# annot=True displays the correlation values
sns.heatmap(correlations, annot=True).set(title='Heatmap of Consumption Data - Pearson Correlations');
Xと
y` のセットを設定した後、データをトレーニングセットとテストセットに分けることができます。同じ種を使い、データの20%を学習用に使います。
y = df['Petrol_Consumption']
X = df[['Average_income', 'Paved_Highways',
'Population_Driver_licence(%)', 'Petrol_tax']]
多変量解析モデルのトレーニング
データを分割した後、重回帰モデルを学習することができます。X` データはすでに1次元以上あるので、再形成する必要はないことに注意してください。
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=0.2,
random_state=SEED)
モデルを学習するために、前と同じコードを実行し、 LinearRegression
クラスの fit()
メソッドを使用します。
X.shape # (48, 4)
モデルをフィットして最適解を求めたら、切片も見てみましょう。
regressor = LinearRegression()
regressor.fit(X_train, y_train)
regressor.intercept_
そして、特徴量の係数
361.45087906668397
regressor.coef_
この4つの値は、各特徴の係数で、 X
データにあるのと同じ順番で並んでいます。データフレームの columns
属性を使用すると、各特徴量の名前のリストを表示することができます。
[-5.65355145e-02, -4.38217137e-03, 1.34686930e+03, -3.69937459e+01]
このコードは次のように出力されます。
feature_names = X.columns
このように素性と係数を一緒に見るのは少し難しいので、表形式で整理することができます。
そのためには、カラム名を feature_names
変数に代入し、係数を model_coefficients
変数に代入すればよいでしょう。その後、特徴をインデックスとして、係数をカラム値として coefficients_df
というデータフレームを作成することができる。
['Average_income', 'Paved_Highways', 'Population_Driver_licence(%)', 'Petrol_tax']
最終的な DataFrame
は以下のようなものになります。
feature_names = X.columns
model_coefficients = regressor.coef_
coefficients_df = pd.DataFrame(data = model_coefficients,
index = feature_names,
columns = ['Coefficient value'])
print(coefficients_df)
線形回帰モデルでは、1つの変数と1つの係数でしたが、重回帰モデルでは、4つの変数と4つの係数があります。これらの係数は何を意味するのでしょうか?線形回帰の係数の解釈と同じように、平均所得が1単位増加すると、ガス消費量が0.06ドル減少することを意味します。
同様に、舗装された高速道路が1単位増えると、ガス消費距離は0.004km減少し、運転免許を持つ人口比率が1単位増えると、ガス消費量は1兆3460億ガロン増加する。
そして最後に、ガソリン税が1単位増加すると、ガス消費量は36,993百万ガロン減少する。
係数データフレームを見ると、平均所得と舗装道路が0に近く、ガソリン消費量に最も影響を与えないことが分かります。一方、「Population_Driver_license(%)」と「Petrol_tax」は、それぞれ係数が1,346.86と-36.99で、予測対象への影響が最も大きくなっています。
つまり、ガソリン消費量は、意外にも(あるいは意外にも)運転免許保有者の人口比率とガソリン税額でほぼ説明できるのです。
この結果は、相関ヒートマップで見たこととどのような関係があるかがわかります。運転免許証のパーセンテージは最も相関が強く、ガソリン消費量の説明に役立つと予想され、ガソリン税は弱い負の相関を持っていましたが、同じく弱い負の相関を持っていた平均所得と比較すると、負の相関が最も-1に近く、結局はモデルを説明することになりました。
すべての値を重回帰式に追加すると、舗装道路と平均所得の傾きは0に近づき、運転免許証のパーセンテージと税金の所得は0から遠ざかる。
注:データサイエンスでは、仮説と不確実性を扱うことがほとんどです。100%の確実性はなく、常に誤差がある。もし、エラーが0だったり、スコアが100%だったりしたら、疑ってみてください。我々はデータのサンプルで1つのモデルをトレーニングしただけであり、最終的な結果が出たと考えるのは早計である。さらに進めるには、残差分析を行い、クロスバリデーションという手法で異なるサンプルでモデルを学習させることができます。また、より多くのデータとより多くの変数を取得して、結果を比較するためにモデルを探索し、プラグインすることができます。
これまでのところ、我々の分析は理にかなっているようです。今度は、現在のモデルに誤りがないかどうかを判断する番です。
多変量回帰モデルで予測する
我々のモデルが間違いを犯しているかどうかを理解するために、テストデータを使ってガス消費量を予測し、我々のモデルがどれだけうまく動作しているかを知るために我々のメトリクスを見ることができます。
単回帰モデルと同じように、テストデータを使って予測しましょう。
Coefficient value
Average_income -0.056536
Paved_Highways -0.004382
Population_Driver_licence(%) 1346.869298
Petrol_tax -36.993746
テストデータによる予測ができたので、それを DataFrame
フォーマットで整理して、 X_test
の実際の出力値と比較してみましょう。
y_pred = regressor.predict(X_test)
出力はこのようになります。
results = pd.DataFrame({'Actual': y_test, 'Predicted': y_pred})
print(results)
ここでは、各テストデータの行のインデックス、実際の値の列、予測値の列があります。実測値と予測値の差を見ると、631と607で24、587と674で-87と、両者の間に距離があるように見えますが、この距離は大きすぎるのでしょうか?
多変量解析モデルの評価
探索、トレーニング、そしてモデルの予測値を見た後、最後のステップは重回帰のパフォーマンスを評価することです。予測値が実際の値から離れすぎているかどうかを理解したいのです。これは、以前行ったのと同じ方法で、MAE、MSE、RMSE メトリクスを計算することによって行うことにします。
それでは、以下のコードを実行してみましょう。
Actual Predicted
27 631 606.692665
40 587 673.779442
26 577 584.991490
43 591 563.536910
24 460 519.058672
37 704 643.461003
12 525 572.897614
19 640 687.077036
4 410 547.609366
25 566 530.037630
メトリクスの出力は次のようになるはずです。
mae = mean_absolute_error(y_test, y_pred)
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
print(f'Mean absolute error: {mae:.2f}')
print(f'Mean squared error: {mse:.2f}')
print(f'Root mean squared error: {rmse:.2f}')
RMSEの値が63.90であることがわかります。これは、我々のモデルが実際の値から63.90を足したり引いたりすることによって予測を誤る可能性があることを意味します。この誤差は0に近い方がよく、63.90は大きな数字です。これは、我々のモデルがあまりうまく予測できていない可能性を示しています。
MAEも0から遠ざかっています。以前の単回帰が良い結果だったのと比較すると、その大きさに大きな違いがあることがわかります。
この指標は、MSE、RMSE、MAEといった個々のデータ値を考慮せず、より一般的な誤差のアプローチであるR2を使用します。
R2=1-∑(Actual-Predicted)2∑(Actual-Actual Mean)2R2=1-∑(Actual-Predicted)2∑(Actual-Actual Mean)2
R^2 = 1 – frac{sum(Actual – Predicted)^2}{sum(Actual – Actual Mean)^2} 望ましい。
R2 は、各予測値が実データとどの程度離れているか、近いかを示すものではなく、対象がどの程度モデルで捉えられているかを示すものです。
ということです。
言い換えれば、R2は、従属変数の分散のうち、どの程度がモデルによって説明されているかを定量的に示すものです。
>
R2という指標は0%から100%まで変化します。100%に近いほど良い。R2値がマイナスであれば、対象を全く説明していないことを意味します。
PythonでR2を計算することで、その仕組みをより深く理解することができます。
Mean absolute error: 53.47
Mean squared error: 4083.26
Root mean squared error: 63.90
actual_minus_predicted = sum((y_test - y_pred)**2)
actual_minus_actual_mean = sum((y_test - y_test.mean())**2)
r2 = 1 - actual_minus_predicted/actual_minus_actual_mean
print('R²:', r2)
R2 は Scikit-Learn の線形リグレッサークラスの score
メソッドにもデフォルトで実装されています。このように計算することができます。
R²: 0.39136640014305457
この結果は
regressor.score(X_test, y_test)
これまでのところ、現在のモデルはテストデータの39%しか説明していないようです。これは良い結果ではなく、テストデータの61%を説明できないままにしていることを意味します。
また、学習データに対して、我々のモデルがどの程度説明できるかを理解しましょう。
0.39136640014305457
どのように出力するのか。
regressor.score(X_train, y_train)
我々のモデルには問題があることがわかりました。訓練データの70%を説明していますが、テストデータでは39%しか説明していません。訓練データにはうまくフィットしているのに、テストデータにはフィットしていない – つまり、重回帰モデルがオーバーフィットしているのです。
この原因には多くの要因がありますが、そのうちのいくつかは以下の通りです。
-
- もっとデータが必要:1年分のデータ(しかも48行)しかないので、それほど多くはありませんが、複数年分のデータがあれば、予測結果の向上にかなり貢献できたはずです。
-
- オーバーフィッティングの克服:クロスバリデーションを用いて、データセットの異なるシャッフルサンプルにモデルを当てはめることで、オーバーフィッティングをなくすことができます。
-
- 仮定が成立しない: データが線形関係であると仮定したが、そうでないかもしれない。箱ひげ図によるデータの可視化、データ分布の把握、外れ値の処理、正規化などがその助けとなるかもしれません。
-
- 不完全な特徴:予測しようとする値と最も強い関係を持つ他の、あるいはより多くの特徴が必要かもしれない。
結論
本稿では、最も基本的な機械学習アルゴリズムの1つである線形回帰について研究した。機械学習ライブラリScikit-learnを用いて、単回帰と重回帰の両方を実装しました。