9
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ChatGPT-4 と始めるデータ分析入門!(回帰編)

Posted at

1. はじめに

 本記事は、ChatGPT を活用してみたい ChatGPT 初心者エンジニア向けに、活用例を紹介する「ChatGPTと始める」シリーズ第4弾です。今回はデータ分析(回帰)を行います!

(第1弾はGUI, 第2弾はCSV可視化、第3弾は機械学習アプリといずれもいいね数は100越えと大好評です。ありがとうございます。)

記事の背景

エンジニアであれば、試験データや業務データなど、データ分析に使用するデータの量には困らないと思いますが、いざそれを分析しようとすると、

  で、どうすればいいんだ...?

と、データ分析手法を色々と知っていたとしても、実際に手を動かし始めるまでに、大きなハードルがあることが多いのではないかと思います。

そこで、そのハードルをなるべく下げるために、まずは何から始めればいいか ChatGPT に相談するところからスタートし、コードの作成、分析まで、すべて手伝ってもらおうと思います!

ぜひ業務で使ってみて、ChatGPT を使えばここまで仕事が早く進むのかと、体感いただければと思います。

2. 実施内容を相談しよう!

2.1. 使用するデータセット

 今回はデータセットとして、アメリカ合衆国マサチューセッツ州ボストン市の住宅価格を予測する Boston Housing Dataset を使用します。

このデータセットには506の観測値があり、それぞれがボストン市内の異なる地域を表しています。各観測値は、以下の14の変数を含み、説明変数(1から13)を使用して目的変数(14:MEDV)を予測する回帰モデルを構築します。

  1. CRIM:人口1人当たりの犯罪発生率
  2. ZN:25,000平方フィート以上の住宅区画の割合
  3. INDUS:非小売業の土地面積の割合
  4. CHAS:チャールズ川沿いかどうか(1:沿い、0:沿っていない)
  5. NOX:窒素酸化物濃度(pphm単位)
  6. RM:住居あたりの平均部屋数
  7. AGE:1940年以前に建てられた家屋の割合
  8. DIS:ボストンの5つの雇用センターまでの重み付け距離
  9. RAD:環状高速道路へのアクセスしやすさの指数
  10. TAX:10,000ドルあたりの固定資産税率
  11. PTRATIO:生徒と教師の比率(生徒1人あたりの教師数)
  12. B:1000(Bk - 0.63)^2 ここで、Bkはアフリカ系アメリカ人居住者の割合
  13. LSTAT:低所得者の割合
  14. MEDV:所有者居住住宅の中央値の価格(単位:1000ドル)

(今回は、csvデータを kaggle からダウンロードして使用しました)

記事の内容は、このデータに限らず汎用的に使用できる方法です。

2.2. 本記事において ChatGPT に相談する際の注意点

 この記事では制約条件として、

  • ChatGPT にデータセットの中身が何であるか、何を分析したいのか、詳細を教えないこと

とします。理由としては、

  皆様、仕事でChatGPTの使用は許可されていますか?
  私は、もちろん許可されていません!
  (フットワークが軽い会社はいいですよね...)

そのため例えば「自動車の燃費が」とか「説明変数としては平均速度が」なんて業務情報を直接 ChatGPT にしゃべってしまうと、その瞬間に打ち首になる可能性があります。

そんなサラリーマンの不安に対応するために、ここではあえて、ChatGPT には具体的なデータセットの中身や数値は教えずに、それでも効率的であるということを紹介したいと思います!

2.3. 実施手順を聞いてみよう!

 まず実施手順を聞いてみます。
今回のように、比較的長い作業を行う場合は、先に ChatGPT と自分が考えているイメージが合っているか確認した方がよいです。

image01.png

プロンプトのポイント

  • ChatGPT に与える情報として、最低限必要なのはデータの数と、説明変数・目的変数のデータ数、データ型になります。
  • 業務上の制約がないときは、目的や背景、変数の概要やデータサンプルなど、自分がデータ分析を行うときに必要な情報を ChatGPT にも追加してあげてください。
  • 残りは、Pythonでコードを生成するときの依頼事項になります。このプロンプトの詳細は過去のシリーズ記事と同じです。

回答はこちらです。

image02.png

はい。ありがとうございます。いいですね!
(私はデータ分析の専門家ではないので、この時点で GPT 先生の方が圧倒的に詳しいと思います。)

次章から、実際に手を動かしていきます。

3. データの前処理をしよう!

この章は長いので、記事の概要を把握したい方は、とばして次章の 「4. 線形モデルで学習しよう」 に進んでください。

3.1. 欠損値と外れ値処理

 ここからは前章でChatGPTが提案してくれた実施手順の順番に ChatGPT に方法を聞きつつ、都度不明点について相談します。

image03.png

import pandas as pd

def load_data(file_name):
    """
    CSVファイルを読み込んでデータフレームに変換する関数。

    Args:
        file_name (str): CSVファイル名

    Returns:
        pd.DataFrame: CSVファイルを読み込んだデータフレーム
    """
    return pd.read_csv(file_name)

def main():
    file_name = "data.csv"
    data = load_data(file_name)
    print(data.head())  # データの先頭5行を表示

if __name__ == "__main__":
    main()

指示通りのコメントもあって、流用しやすく関数化された Python コードが出てきました。

実行すると、エラーも発生せず、以下のようにデータが読み取れているのが確認できます。

$ python data01.py
   Unnamed: 0     crim    zn  indus  chas    nox     rm   age     dis  rad  tax  ptratio   black  lstat  medv
0           1  0.00632  18.0   2.31     0  0.538  6.575  65.2  4.0900    1  296     15.3  396.90   4.98  24.0
1           2  0.02731   0.0   7.07     0  0.469  6.421  78.9  4.9671    2  242     17.8  396.90   9.14  21.6
2           3  0.02729   0.0   7.07     0  0.469  7.185  61.1  4.9671    2  242     17.8  392.83   4.03  34.7
3           4  0.03237   0.0   2.18     0  0.458  6.998  45.8  6.0622    3  222     18.7  394.63   2.94  33.4
4           5  0.06905   0.0   2.18     0  0.458  7.147  54.2  6.0622    3  222     18.7  396.90   5.33  36.2

続けて、欠損値、外れ値の確認に進みます。

image04.png

これ以降、長くなる話は以下のように折りたたんでいます。記事全体が長いので、気になる人だけ展開して見てください

:speech_balloon: ChatGPTが生成したコードやその解説
:beginner: 初心者向けの解説
:speech_balloon: 欠損値、外れ値確認用コード

import pandas as pd

def check_missing_values(data):
    """
    データフレーム内の欠損値の数を表示する関数。

    Args:
        data (pd.DataFrame): データフレーム
    """
    print("Missing values for each column:")
    print(data.isnull().sum())

def check_outliers(data):
    """
    データフレーム内の各変数の基本統計量を表示する関数。

    Args:
        data (pd.DataFrame): データフレーム
    """
    print("Basic statistics for each column:")
    print(data.describe())

def main():
    file_name = "data.csv"
    data = pd.read_csv(file_name)

    # カラム名を設定(適宜変更してください)
    data.columns = ["label1", "label2", "label3", ..., "label14"]

    # 欠損値の確認
    check_missing_values(data)

    # 外れ値の確認
    check_outliers(data)

if __name__ == "__main__":
    main()

これを実行すると、以下の結果が表示され、データに欠損がないことがわかります。

Missing values for each column:
Unnamed: 0    0
crim          0
zn            0
indus         0
chas          0
nox           0
rm            0
age           0
dis           0
rad           0
tax           0
ptratio       0
black         0
lstat         0
medv          0
dtype: int64

また、外れ値に関しても、勉強を兼ねて色々と聞いてみました。長くなるので折りたたみます。

:beginner: 外れ値ってどうやって調べればいい? (長いです)

さきほどのコードを実行すると、各変数の基本統計量(最小値、第1四分位数、中央値、平均、第3四分位数、最大値など)も表示されます。

Basic statistics for each column:
       Unnamed: 0        crim          zn       indus        chas         nox          rm         age         dis         rad         tax     ptratio       black       lstat        medv
count  506.000000  506.000000  506.000000  506.000000  506.000000  506.000000  506.000000  506.000000  506.000000  506.000000  506.000000  506.000000  506.000000  506.000000  506.000000
mean   253.500000    3.613524   11.363636   11.136779    0.069170    0.554695    6.284634   68.574901    3.795043    9.549407  408.237154   18.455534  356.674032   12.653063   22.532806
std    146.213884    8.601545   23.322453    6.860353    0.253994    0.115878    0.702617   28.148861    2.105710    8.707259  168.537116    2.164946   91.294864    7.141062    9.197104
min      1.000000    0.006320    0.000000    0.460000    0.000000    0.385000    3.561000    2.900000    1.129600    1.000000  187.000000   12.600000    0.320000    1.730000    5.000000
25%    127.250000    0.082045    0.000000    5.190000    0.000000    0.449000    5.885500   45.025000    2.100175    4.000000  279.000000   17.400000  375.377500    6.950000   17.025000
50%    253.500000    0.256510    0.000000    9.690000    0.000000    0.538000    6.208500   77.500000    3.207450    5.000000  330.000000   19.050000  391.440000   11.360000   21.200000
75%    379.750000    3.677083   12.500000   18.100000    0.000000    0.624000    6.623500   94.075000    5.188425   24.000000  666.000000   20.200000  396.225000   16.955000   25.000000
max    506.000000   88.976200  100.000000   27.740000    1.000000    0.871000    8.780000  100.000000   12.126500   24.000000  711.000000   22.000000  396.900000   37.970000   50.000000

偏っている変数とそうでない変数があることはなんとなくわかりますが、これをどう使えばよいのかよくわからなかったので聞いてみました。

image05.png
image06.png

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

def detect_outliers_iqr(data):
    """
    IQRを利用して外れ値を検出する関数。

    Args:
        data (pd.DataFrame): データフレーム

    Returns:
        pd.DataFrame: 外れ値を検出した結果のデータフレーム
    """
    outliers = pd.DataFrame(columns=data.columns)

    for column in data.columns:
        Q1 = data[column].quantile(0.25)
        Q3 = data[column].quantile(0.75)
        IQR = Q3 - Q1

        lower_bound = Q1 - 1.5 * IQR
        upper_bound = Q3 + 1.5 * IQR

        outliers_column = data[(data[column] < lower_bound) | (data[column] > upper_bound)]
        outliers = pd.concat([outliers, outliers_column])

    return outliers

def plot_boxplots(data):
    """
    データフレーム内の各変数の箱ひげ図を描画する関数。

    Args:
        data (pd.DataFrame): データフレーム
    """
    sns.set(style="whitegrid")
    plt.figure(figsize=(15, 10))
    sns.boxplot(data=data)
    plt.xticks(rotation=90)
    plt.show()

def main():
    file_name = "data.csv"
    data = pd.read_csv(file_name)

    # カラム名を設定(適宜変更してください)
    data.columns = ["label1", "label2", "label3", ..., "label14"]

    # IQRを利用した外れ値検出
    outliers = detect_outliers_iqr(data)
    print("Outliers detected using IQR:")
    print(outliers)

    # 箱ひげ図を描画
    plot_boxplots(data)

if __name__ == "__main__":
    main()

ChatGPTが出してくれたこのコードを実行すると,以下の箱ひげ図が表示されました。

箱ひげ図.png

箱ひげ図の見方は以下になります。

image07.png

データの分布に偏りがあるため、箱からはみ出たデータがたくさんあります。

今回使用するデータの性質上、偏りがあること自体は問題はないと思いますが、実際にどのようにデータが分布しているのか確認するためのヒストグラムもついでに出してもらいました(ラベルが上です)。

ヒストグラム.png

目的変数(左下)が滑らかな分布なので、外れ値は特に処理しなくてよいかなと判断しました。


結果的に、外れ値は処理しなくても問題なさそうだと考えたので、なにもしていません。

3.2. データの正規化/標準化

 つぎにデータの正規化/標準化に進みます。

image08.png

:speech_balloon: 正規化と標準化を行うコード
from sklearn.preprocessing import MinMaxScaler, StandardScaler

def normalize_data(data):
    """
    データを正規化する関数。

    Args:
        data (pd.DataFrame): データフレーム

    Returns:
        pd.DataFrame: 正規化されたデータフレーム
    """
    scaler = MinMaxScaler()
    normalized_data = scaler.fit_transform(data)
    return pd.DataFrame(normalized_data, columns=data.columns)

def standardize_data(data):
    """
    データを標準化する関数。

    Args:
        data (pd.DataFrame): データフレーム

    Returns:
        pd.DataFrame: 標準化されたデータフレーム
    """
    scaler = StandardScaler()
    standardized_data = scaler.fit_transform(data)
    return pd.DataFrame(standardized_data, columns=data.columns)

def main():
    file_name = "data.csv"
    data = pd.read_csv(file_name)

    # カラム名を設定(適宜変更してください)
    data.columns = ["label1", "label2", "label3", ..., "label14"]

    # データを正規化
    normalized_data = normalize_data(data)
    print("Normalized data:")
    print(normalized_data.head())

    # データを標準化
    standardized_data = standardize_data(data)
    print("Standardized data:")
    print(standardized_data.head())

if __name__ == "__main__":
    main()
:beginner: 正規化と標準化はどう違いますか?

image09.png

今回は、(最終的に全部試したのですが)標準化を中心に使っていきます。

3.3. 探索的データ解析

 ここから、目次の2項に進みますが、2項の探索的データ解析のうち、目的変数と説明変数の分布についてはすでに外れ値調査のところで確認したため(折りたたんだところで紹介しました)省略します。
説明変数と目的変数の相関の確認から調べます。

image11.png

:speech_balloon: 相関係数を調べるためのコード
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

def show_correlations(data, feature_columns, target_column):
    """
    相関係数を表示する関数。

    Args:
    - data: pandas DataFrame, データ
    - feature_columns: list of str, 説明変数の列名のリスト
    - target_column: str, 目的変数の列名
    """

    # 説明変数と目的変数を含むデータフレーム
    all_columns = feature_columns + [target_column]
    sub_data = data[all_columns]

    # 相関係数を計算
    correlations = sub_data.corr()

    # 相関係数を表示
    print("Correlations:")
    print(correlations)

def plot_scatter_matrix(data, feature_columns, target_column):
    """
    散布図行列をプロットする関数。

    Args:
    - data: pandas DataFrame, データ
    - feature_columns: list of str, 説明変数の列名のリスト
    - target_column: str, 目的変数の列名
    """

    # 説明変数と目的変数を含むデータフレーム
    all_columns = feature_columns + [target_column]
    sub_data = data[all_columns]

    # 散布図行列をプロット
    sns.pairplot(sub_data)
    plt.show()

def main():
    # CSVファイルからデータを読み込む(仮に"data.csv"とします)
    data = pd.read_csv("data.csv")

    # 説明変数の列名のリスト(仮に"feature1", "feature2", ...とします)
    feature_columns = ["feature1", "feature2", "feature3", "feature4"]

    # 目的変数の列名(仮に"target"とします)
    target_column = "target"

    # 相関係数を表示
    show_correlations(data, feature_columns, target_column)

    # 散布図行列をプロット
    plot_scatter_matrix(data, feature_columns, target_column)

if __name__ == "__main__":
    main()

これを実行すると、以下の Correlations と散布図が表示されます。

:beginner: Correlations の見方を説明してください!

image12.png

Correlations:
             crim        zn     indus      chas       nox        rm       age       dis       rad       tax   ptratio     black     lstat      medv
crim     1.000000 -0.200469  0.406583 -0.055892  0.420972 -0.219247  0.352734 -0.379670  0.625505  0.582764  0.289946 -0.385064  0.455621 -0.388305
zn      -0.200469  1.000000 -0.533828 -0.042697 -0.516604  0.311991 -0.569537  0.664408 -0.311948 -0.314563 -0.391679  0.175520 -0.412995  0.360445
indus    0.406583 -0.533828  1.000000  0.062938  0.763651 -0.391676  0.644779 -0.708027  0.595129  0.720760  0.383248 -0.356977  0.603800 -0.483725
chas    -0.055892 -0.042697  0.062938  1.000000  0.091203  0.091251  0.086518 -0.099176 -0.007368 -0.035587 -0.121515  0.048788 -0.053929  0.175260
nox      0.420972 -0.516604  0.763651  0.091203  1.000000 -0.302188  0.731470 -0.769230  0.611441  0.668023  0.188933 -0.380051  0.590879 -0.427321
rm      -0.219247  0.311991 -0.391676  0.091251 -0.302188  1.000000 -0.240265  0.205246 -0.209847 -0.292048 -0.355501  0.128069 -0.613808  0.695360
age      0.352734 -0.569537  0.644779  0.086518  0.731470 -0.240265  1.000000 -0.747881  0.456022  0.506456  0.261515 -0.273534  0.602339 -0.376955
dis     -0.379670  0.664408 -0.708027 -0.099176 -0.769230  0.205246 -0.747881  1.000000 -0.494588 -0.534432 -0.232471  0.291512 -0.496996  0.249929
rad      0.625505 -0.311948  0.595129 -0.007368  0.611441 -0.209847  0.456022 -0.494588  1.000000  0.910228  0.464741 -0.444413  0.488676 -0.381626
tax      0.582764 -0.314563  0.720760 -0.035587  0.668023 -0.292048  0.506456 -0.534432  0.910228  1.000000  0.460853 -0.441808  0.543993 -0.468536
ptratio  0.289946 -0.391679  0.383248 -0.121515  0.188933 -0.355501  0.261515 -0.232471  0.464741  0.460853  1.000000 -0.177383  0.374044 -0.507787
black   -0.385064  0.175520 -0.356977  0.048788 -0.380051  0.128069 -0.273534  0.291512 -0.444413 -0.441808 -0.177383  1.000000 -0.366087  0.333461
lstat    0.455621 -0.412995  0.603800 -0.053929  0.590879 -0.613808  0.602339 -0.496996  0.488676  0.543993  0.374044 -0.366087  1.000000 -0.737663
medv    -0.388305  0.360445 -0.483725  0.175260 -0.427321  0.695360 -0.376955  0.249929 -0.381626 -0.468536 -0.507787  0.333461 -0.737663  1.000000

散布図.png

Correlations の結果を見ると、目的変数 medv に対して、rm と lstat が相関係数 0.7 程度で大きく、次に indus, tax, ptratio が 0.5 と中程度のようです。

ただし、相関係数 0.7 あった rm と lstat については、それぞれの説明変数間の相関係数も-0.6 と大きいです。
このようなときは、どうしたらよいのでしょうか?

:beginner: どの説明変数を使えばいい?

image13.png
image14.png

ChatGPT に相談してみた結果、説明変数として以下の順番で試してみようと思います。

  1. 相関関数が 0.7 程度の変数(rm と lstat)を使用する
  2. 相関関数が 0.5 程度の変数を追加する(indus, tax, ptratio)
  3. 全部の説明変数を使用する
  4. 場合によっては、PCAで次元削減する

4. 線形モデルで学習しよう!

4.1. 学習用データとテスト用データの分割

 学習を始める前に、交差検証用にデータセットを分割します。

image15.png

:speech_balloon: 学習・テスト用データ作成コードと解説

データを学習用データとテスト用データに分割する方法について説明します。Pythonでは、scikit-learnという機械学習ライブラリがあります。このライブラリには、データを分割するためのtrain_test_split関数が用意されています。

まず、scikit-learnをインストールしていない場合は、以下のコマンドでインストールしてください。

pip install scikit-learn

次に、以下のコードを使ってデータを学習用データとテスト用データに分割できます。

from sklearn.model_selection import train_test_split

def split_data(data, target, test_size=0.2, random_state=42):
    """
    データを学習用データとテスト用データに分割する関数。

    Args:
    data (DataFrame): 説明変数のデータ
    target (Series): 目的変数のデータ
    test_size (float): テスト用データの割合(デフォルトは0.2)
    random_state (int): 乱数生成のシード値(デフォルトは42)

    Returns:
    X_train, X_test, y_train, y_test: 分割後の学習用データとテスト用データ
    """

    X_train, X_test, y_train, y_test = train_test_split(
        data, target, test_size=test_size, random_state=random_state
    )

    return X_train, X_test, y_train, y_test

# main関数でデータ分割を実行
def main():
    # ここでデータを読み込んで、説明変数と目的変数に分割してください。
    # 例:
    # data = ...
    # target = ...

    X_train, X_test, y_train, y_test = split_data(data, target)

    print("X_train:")
    print(X_train.head())
    print("X_test:")
    print(X_test.head())
    print("y_train:")
    print(y_train.head())
    print("y_test:")
    print(y_test.head())

if __name__ == "__main__":
    main()

このコードは、説明変数のデータ(data)と目的変数のデータ(target)を受け取り、学習用データとテスト用データに分割します。test_sizeはテスト用データの割合を指定し、random_stateは乱数生成のシード値を指定します。データがランダムに分割されるため、random_stateを指定しておくことで、再現性を確保できます。


4.2. 線形モデル

 ここから線形モデルで学習を行います。

:beginner: 線形回帰モデルって?

image22.png

教えてもらった中から、まずは線形回帰と、リッジ回帰を試してみようと思います。

image16.png

ChatGPTが出してくれたこれまでのコードをくっつけるように要求されたので完成版を貼っておきます。

import numpy as np
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
import pandas as pd
from sklearn.metrics import r2_score
from sklearn.preprocessing import MinMaxScaler, StandardScaler

def split_data(data, target, test_size=0.2, random_state=42):
    """
    データを学習用データとテスト用データに分割する関数。

    Args:
    data (DataFrame): 説明変数のデータ
    target (Series): 目的変数のデータ
    test_size (float): テスト用データの割合(デフォルトは0.2)
    random_state (int): 乱数生成のシード値(デフォルトは42)

    Returns:
    X_train, X_test, y_train, y_test: 分割後の学習用データとテスト用データ
    """

    X_train, X_test, y_train, y_test = train_test_split(
        data, target, test_size=test_size, random_state=random_state
    )

    return X_train, X_test, y_train, y_test

def train_model(X_train, y_train, X_test, y_test, model_type='linear_regression', alpha=1.0):
    """
    線形回帰またはリッジ回帰のモデルを学習し、テストデータに対する予測精度を評価する。

    Parameters
    ----------
    X_train: DataFrame
        学習データの説明変数
    y_train: DataFrame
        学習データの目的変数
    X_test: DataFrame
        テストデータの説明変数
    y_test: DataFrame
        テストデータの目的変数
    model_type: str, optional (default='linear_regression')
        使用するモデルのタイプ。'linear_regression' または 'ridge_regression'
    alpha: float, optional (default=1.0)
        リッジ回帰の正則化パラメータ(リッジ回帰を使用する場合のみ)

    Returns
    -------
    trained_model: LinearRegression or Ridge
        学習済みのモデル
    rmse: float
        テストデータに対する平均二乗誤差の平方根(RMSE)
    """
    if model_type == 'linear_regression':
        model = LinearRegression()
    elif model_type == 'ridge_regression':
        model = Ridge(alpha=alpha)
    else:
        raise ValueError("Invalid model_type")

    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)

    rmse = mean_squared_error(y_test, y_pred, squared=False)
    r2 = r2_score(y_test, y_pred)

    return model, rmse, r2

def standardize_data(data):
    """
    データを標準化する関数。

    Args:
        data (pd.DataFrame): データフレーム

    Returns:
        pd.DataFrame: 標準化されたデータフレーム
    """
    scaler = StandardScaler()
    standardized_data = scaler.fit_transform(data)
    return pd.DataFrame(standardized_data, columns=data.columns)

def main():
    # CSVファイルからデータを読み込む(仮に"data.csv"とします)
    import_df= pd.read_csv("data.csv")

    # データを標準化する
    df = standardize_data(import_df)

    # 説明変数を絞る場合
    feature_columns = ['rm','lstat']
    # feature_columns = ['rm','lstat','indus','tax','ptratio']

    # 全部の説明変数を使う場合
    #feature_columns = df.columns.to_list()
    #feature_columns.pop(0)
    #feature_columns.remove("medv")

    # 目的変数の設定
    data =  df.loc[:, feature_columns]
    target = df["medv"]

    # データの分割
    X_train, X_test, y_train, y_test = split_data(data, target)

    # 学習の実行
    lr_model, lr_rmse, lr_r2 = train_model(X_train, y_train, X_test, y_test, model_type='linear_regression')
    print("Linear Regression RMSE:", lr_rmse)
    print("Linear Regression R^2:", lr_r2)

    ridge_model, ridge_rmse, ridge_r2 = train_model(X_train, y_train, X_test, y_test, model_type='ridge_regression', alpha=1.0)
    print("Ridge Regression RMSE:", ridge_rmse)
    print("Ridge Regression R^2:", ridge_r2)

if __name__ == '__main__':
    main()

これを実行すると以下の結果が表示されました。

Linear Regression RMSE: 0.6083546779594221
Linear Regression R^2: 0.573957741502586
Ridge Regression RMSE: 0.608195135682935
Ridge Regression R^2: 0.5741811731334953
:beginner: RMSEとR2スコアとは?

image17.png

上記の結果は、説明変数として相関が最も高い2個(rm, lstat)を使ったものになりますが、これを 3.3節で計画したように、説明変数を目的変数の相関の大きい順から 2個→5個→すべて のようにパターンを変えて実行すると、R2スコア は0.57→0.62→0.67 のように増加しました(R2スコアは1に近いほどよいです)。

今回のデータとモデルでは、説明変数は使えるだけ使った方が、よい結果になるようです。

一番よい結果になった、説明変数としてすべての変数を使った場合について、ChatGPT に評価をお願いしてみました。

image18.png

ChatGPT のコメント通りアルファを調整したり、PCA (次元削減) を行ってみたりしたのですが、大きく変化がなかったため、別のモデルを適用したいと思います。

5. 機械学習モデルで学習しよう!

 他に回帰分析としてどのようなモデルが使えるか聞いてみます。

image19.png

(記事を書くのに)どれが良さそうかと考えて... 線形回帰と書いているもの以外を全部実装してもらうことにしました。

image20.png

ここで、これまで出力されたコードに、テストデータの評価指標(R2スコアなど)しかないことに気づいたので、併せて学習用データの評価指標も表示するよう依頼しています。

:speech_balloon: 機械学習回帰モデルを加えたコード
import numpy as np
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
import pandas as pd
from sklearn.metrics import r2_score
from sklearn.preprocessing import MinMaxScaler, StandardScaler
from sklearn.neighbors import KNeighborsRegressor
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.svm import SVR


def split_data(data, target, test_size=0.2, random_state=42):
    """
    データを学習用データとテスト用データに分割する関数。

    Args:
    data (DataFrame): 説明変数のデータ
    target (Series): 目的変数のデータ
    test_size (float): テスト用データの割合(デフォルトは0.2)
    random_state (int): 乱数生成のシード値(デフォルトは42)

    Returns:
    X_train, X_test, y_train, y_test: 分割後の学習用データとテスト用データ
    """

    X_train, X_test, y_train, y_test = train_test_split(
        data, target, test_size=test_size, random_state=random_state
    )

    return X_train, X_test, y_train, y_test

def train_model(X_train, y_train, X_test, y_test, model_type='linear_regression', alpha=1.0, k=5):
    """
    線形回帰またはリッジ回帰のモデルを学習し、テストデータに対する予測精度を評価する。

    Parameters
    ----------
    X_train: DataFrame
        学習データの説明変数
    y_train: DataFrame
        学習データの目的変数
    X_test: DataFrame
        テストデータの説明変数
    y_test: DataFrame
        テストデータの目的変数
    model_type: str, optional (default='linear_regression')
        使用するモデルのタイプ。'linear_regression' または 'ridge_regression'
    alpha: float, optional (default=1.0)
        リッジ回帰の正則化パラメータ(リッジ回帰を使用する場合のみ)

    Returns
    -------
    trained_model: LinearRegression or Ridge
        学習済みのモデル
    rmse: float
        テストデータに対する平均二乗誤差の平方根(RMSE)
    """
    if model_type == 'linear_regression':
        model = LinearRegression()
    elif model_type == 'ridge_regression':
        model = Ridge(alpha=alpha)
    elif model_type == 'knn_regression':
        model = KNeighborsRegressor(n_neighbors=k)
    elif model_type == 'decision_tree_regression':
        model = DecisionTreeRegressor()
    elif model_type == 'random_forest_regression':
        model = RandomForestRegressor()
    elif model_type == 'gradient_boosting_regression':
        model = GradientBoostingRegressor()
    elif model_type == 'svr':
        model = SVR()
    else:
        raise ValueError("Invalid model_type")

    model.fit(X_train, y_train)
    y_pred_test = model.predict(X_test)
    y_pred_train = model.predict(X_train)

    rmse_test = mean_squared_error(y_test, y_pred_test, squared=False)
    r2_test = r2_score(y_test, y_pred_test)
    
    rmse_train = mean_squared_error(y_train, y_pred_train, squared=False)
    r2_train = r2_score(y_train, y_pred_train)

    return model, rmse_test, r2_test, rmse_train, r2_train

def standardize_data(data):
    """
    データを標準化する関数。

    Args:
        data (pd.DataFrame): データフレーム

    Returns:
        pd.DataFrame: 標準化されたデータフレーム
    """
    scaler = StandardScaler()
    standardized_data = scaler.fit_transform(data)
    return pd.DataFrame(standardized_data, columns=data.columns)

def main():
    # CSVファイルからデータを読み込む(仮に"data.csv"とします)
    import_df= pd.read_csv("data.csv")

    # データを標準化する
    df = standardize_data(import_df)

    # feature_columns = ['rm','lstat']
    # feature_columns = ['rm','lstat','indus','tax','ptratio']

    feature_columns = df.columns.to_list()
    feature_columns.pop(0)
    feature_columns.remove("medv")

    data =  df.loc[:, feature_columns]
    target = df["medv"]

    X_train, X_test, y_train, y_test = split_data(data, target)

    # 以下のコードで各モデルを学習・評価します。
    # 必要に応じて、alphaやkなどのハイパーパラメータを調整してください。

    for model_type in ['linear_regression', 'ridge_regression', 'knn_regression', 'decision_tree_regression', 'random_forest_regression', 'gradient_boosting_regression', 'svr']:
        model, rmse_test, r2_test, rmse_train, r2_train = train_model(X_train, y_train, X_test, y_test, model_type=model_type)
        print(f"{model_type} Test RMSE: {rmse_test}")
        print(f"{model_type} Test R^2: {r2_test}")
        print(f"{model_type} Train RMSE: {rmse_train}")
        print(f"{model_type} Train R^2: {r2_train}")
        print("----")

if __name__ == '__main__':
    main()

これを実行すると以下の結果が表示されます。

linear_regression Test RMSE: 0.5364166292540398
linear_regression Test R^2: 0.6687594935356322
linear_regression Train RMSE: 0.5063155571752505
linear_regression Train R^2: 0.7508856358979672
----
ridge_regression Test RMSE: 0.5366751346847491
ridge_regression Test R^2: 0.6684401592810275
ridge_regression Train RMSE: 0.5063313819719012
ridge_regression Train R^2: 0.7508700636102706
----
knn_regression Test RMSE: 0.4946699331742575
knn_regression Test R^2: 0.7183109257429845
knn_regression Train RMSE: 0.3934579403570154
knn_regression Train R^2: 0.8495636267791526
----
decision_tree_regression Test RMSE: 0.3827256591991545
decision_tree_regression Test R^2: 0.8313780396754408
decision_tree_regression Train RMSE: 6.628279193576708e-17
decision_tree_regression Train R^2: 1.0
----
random_forest_regression Test RMSE: 0.3316634536781134
random_forest_regression Test R^2: 0.8733706982687588
random_forest_regression Train RMSE: 0.15477221689931248
random_forest_regression Train R^2: 0.9767221838953121
----
gradient_boosting_regression Test RMSE: 0.2760124393513248
gradient_boosting_regression Test R^2: 0.9123006680633332
gradient_boosting_regression Train RMSE: 0.14335420352795925
gradient_boosting_regression Train R^2: 0.9800300447996301
----
svr Test RMSE: 0.39400542527434923
svr Test R^2: 0.8212922535756355
svr Train RMSE: 0.3303824300017988
svr Train R^2: 0.8939305999039542
----

線形モデルでは、R2スコアは最大 0.67 だったのに比べて、新しく追加したモデルではすべて 0.7 以上と、スコアがかなりよくなったように見えます。

ChatGPT に解説を依頼しました。

image21.png

パラメータは一切調整していないので他の手法がよくなる可能性はありますが、今回のデータでは、線形回帰よりもランダムフォレスト回帰や勾配ブースティング回帰などの非線形回帰かつアンサンブルを行っている手法の方が良さそうです。

:beginner: ランダムフォレスト回帰モデルの中はどうなっている?

image22.png
image23.png

送られてきたコードを実行し、ランダムフォレスト回帰の決定木のうちはじめの3つを出してみました。

ランダムフォレスト.png

こんな感じです。複雑ですね。


記事を書くために、追加でやってみるかと適当に試してみただけなのに、この短時間でR2スコアが 0.7 程度から 0.9 程度まで上がるのかと驚きました(過学習の懸念があるため、仕事の場合はもう少し掘り下げる必要があるとは思います)。

今回のこの記事を書くためのChatGPTと相談作業全体でかかった時間は4時間程度で、色々と余計なことを聞いた分を含めても非常に効率的ではないかなと思います!

6. まとめ

 今回は、ChatGPTとBoston Housingデータセットを使って回帰分析を行ってみました。
皆様ぜひとも自分のデータで、お手元で色々と聞いてみながら遊んでみてください!

参考になれば嬉しいです。

モチベーションupのために、ぜひとも、参考になればいいねをお願いします!

9
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?