ライナー君の最小二乗法:コスト関数の謎を解く

LinearAdventure

こんにちは、こんぶちゃです!

私はCourseraで提供されているStanford University & DeepLearning.AIによるMachine Learning コースを受講しました。そのコースで学んだ内容を基に、ストーリー仕立てで、より楽しく、かつ理解しやすい形で機械学習の基礎を解説していきます。

こんな人にオススメしたい
  • AIがどのように「考え」、動作するのかの裏側に興味がある方
  • 機械学習の基本的な概念やアルゴリズムをゼロから学びたい方
  • Pythonを使って、機械学習のモデルを自分で手を動かして実装してみたい方

この記事を通じて、機械学習の魅力的な世界への第一歩を踏み出しましょう。早速、始めていきましょう。

はじめに:ライナー君の使命

みなさん、こんにちは!今日は、ある特別なヒーロー、ライナー君を紹介します。ライナー君は、データの世界を旅する勇敢な冒険者です。彼の使命?それは、バラバラに散らばったデータ点たちの間に、最もフィットする直線を引くこと。この直線は、データの秘密を解き明かし、予測の鍵を握っています。でも、なぜ線形回帰なんていう技術が重要なのでしょうか?

線形回帰は、データポイント間の関係性を理解するための超パワフルなツールなんです。たとえば、家の大きさと価格の関係を予測したり、ある商品の広告費用とその売上を予測したりするのに使います。これができれば、未来を少しだけ予見することができるようになるんですよ!

ライナー君の最大の武器、それはコスト関数、またの名を平均二乗誤差関数です。この関数は、ライナー君が描く直線がデータ点にどれだけフィットしているかを数値で示します。数値が小さいほど、直線はデータにぴったりフィットしているというわけ。ライナー君の目標は、このコスト関数の値をできるだけ小さくすること。つまり、最もデータにフィットする直線を見つけ出す旅に出るわけです。

さあ、ライナー君と一緒にこの冒険に出発しましょう。彼がどのようにしてこのミッションを達成し、データの秘密を解き明かすのか、見ていきましょう!

コスト関数って何?

冒険が始まる前に、ライナー君が直面する最大の謎、コスト関数について探求しましょう。コスト関数とは、一体何者でしょうか?そして、なぜこれほどまでに重要なのでしょうか?

コスト関数を理解するには、まずライナー君が冒険の中で何を目指しているかを把握する必要があります。彼の目標は、データポイントたちと最も仲良くなれる直線を見つけること。でも、どの直線が「最も仲良し」なのかをどうやって判断すればいいのでしょう?

ここでコスト関数の出番です。コスト関数は、ライナー君が考える直線と、実際のデータポイントとの距離を計算します。この距離は、「誤差」とも呼ばれ、直線がデータポイントからどれだけ離れているかを示します。コスト関数は、すべてのデータポイントについてこの誤差を平均化し、一つの数値で表します。要するに、コスト関数の値が小さいほど、ライナー君の直線はデータポイントにとっての「親友」に近づくわけです。

数学的な見方

数学的には、コスト関数J(平均二乗誤差関数)は次のように表されます:

ここで、mはデータポイントの数、x(i)はi番目のデータポイントの特徴、y(i)はi番目のデータポイントの実際の値、fw,b(x(i))はライナー君の直線(予測値)です。ここで、fw,b(x(i))は、下記の式で表せられます。

この式は、ライナー君の直線がデータポイントからどれだけ離れているかを数値で示しています。

Pythonでの表現

Pythonを使って、コスト関数を実際に計算してみましょう。numpyというライブラリを使うと、これがとても簡単にできます。

import numpy as np

def compute_cost(x, y, w, b): 
    """
    線形回帰のコスト関数を計算します。
    
    引数:
      x (ndarray (m,)): データ、m 個の例
      y (ndarray (m,)): 目標値
      w,b (スカラー)    : モデルパラメータ
    
    戻り値:
        total_cost (float): x と y のデータポイントにフィットさせるために、
                             w,b をパラメータとして使用するコスト
    """
    # 訓練例の数
    m = x.shape[0] 
    
    cost_sum = 0 
    for i in range(m): 
        f_wb = w * x[i] + b   
        cost = (f_wb - y[i]) ** 2  
        cost_sum = cost_sum + cost  
    total_cost = (1 / (2 * m)) * cost_sum  

    return total_cost

この関数は、ライナー君の直線(f_wb)がデータポイント(Xy)にどれだけフィットしているかを計算します。Xは特徴の値を、yは各データポイントの実際の値を表し、f_wbは直線のパラメータです。

さあ、この謎を解き明かす鍵を手に入れたライナー君は、次のステップ、つまりコスト関数を最小化する方法を見つけ出すべく、冒険を続けます。彼の成功を祈りましょう!

Pythonでライナー君を動かす

線形回帰の世界へようこそ!ここでは、Pythonとnumpyという強力なツールを使って、ライナー君を動かし、データに最もフィットする直線を見つける方法を学びます。さあ、コーディングの冒険に出発しましょう!

環境準備

まず、必要なライブラリをインポートしましょう。numpyは数値計算を効率的に行うためのライブラリで、線形回帰モデルの実装には欠かせません。

import numpy as np

データセットの準備

実際にライナー君を動かす前に、彼が学習するためのデータセットを準備しましょう。ここでは、シンプルな例として、x(特徴量)とy(ターゲット)の関係が線形であるデータセットを使います。

# 特徴量(入力変数)
X = np.array([1, 2, 3, 4, 5])
# ターゲット(出力変数)
y = np.array([2, 4, 5, 4, 5])

モデルの定義

ライナー君のモデルを定義しましょう。線形回帰モデルは、入力変数とパラメータ(重みwとバイアスb)の線形結合で出力を予測します。

# 初期パラメータ(重みとバイアス)
w = np.random.randn()
b = np.random.randn()

ライナー君を動かす

ライナー君がデータにフィットする直線を見つけるためには、コスト関数を計算してみましょう。先ほど定義したcompute_cost関数を使用します。

cost = compute_cost(X, y, w, b)
print(f"初期コスト: {cost}")

学習ステップ

ライナー君が最適な直線を見つけるためには、コストを最小化するパラメータを見つける必要があります。これは後のセクション「コスト関数を最小化する」で詳しく説明しますが、基本的には勾配降下法を使ってパラメータを更新します。

コスト関数を最小化する

勾配降下法:ライナー君の秘密の技

ライナー君がコスト関数を最小化するための秘密の技、それが勾配降下法です。この方法では、コスト関数の勾配(つまり、傾き)を計算し、その勾配が示す方向に徐々に進むことで、最小値を探します。イメージとしては、山を下る最速のルートを見つけるようなものです。

勾配の計算

勾配降下法を実装するには、まず勾配を計算する必要があります。勾配は、重みwとバイアスbに関するコスト関数の偏微分係数であり、コスト関数に重みwとバイアスbで偏微分を行います。

勾配の計算:

def compute_gradient(x, y, w, b): 
    """
    線形回帰の勾配を計算します。
    引数:
      x (ndarray (m,)): データ、m 個の例
      y (ndarray (m,)): 目標値
      w, b (スカラー): モデルパラメータ
    戻り値:
      dj_dw (スカラー): パラメータ w に関するコストの勾配
      dj_db (スカラー): パラメータ b に関するコストの勾配
    """
    
    m = x.shape[0]  # 訓練例の数
    dj_dw = 0
    dj_db = 0
    
    for i in range(m): 
        f_wb = w * x[i] + b  # 予測値
        dj_dw += (f_wb - y[i]) * x[i]  # w に関するコストの勾配
        dj_db += f_wb - y[i]  # b に関するコストの勾配
    
    dj_dw /= m  # 平均を取る
    dj_db /= m  # 平均を取る
        
    return dj_dw, dj_db

パラメータの更新

次に、計算した勾配を使ってパラメータを更新します。学習率(alpha)を使って、どれだけのステップで勾配の反対方向に進むかを決めます。

w, bの更新:

ここで、コストJは下記で示される:

def gradient_descent(x, y, w_in, b_in, alpha, num_iters):
    """
    勾配降下法を用いてw, bを更新する。
    
    引数:
      x (ndarray (m,)): データ, m 個の例
      y (ndarray (m,)): 目標値
      w_in, b_in (scalar): モデルパラメータの初期値
      alpha (float): 学習率
      num_iters (int): 勾配降下法を実行する反復回数
      gradient_function: 勾配を計算する関数
      
    戻り値:
      w (scalar): 更新されたパラメータw
      b (scalar): 更新されたパラメータb
    """
    
    w = w_in
    b = b_in
    
    for i in range(num_iters):
        # 勾配を計算
        dj_dw, dj_db = compute_gradient(x, y, w, b)

        # パラメータを更新
        w -= alpha * dj_dw
        b -= alpha * dj_db
    
    return w, b

実際に試してみる

さあ、実際に勾配降下法を使って、ライナー君が最適な直線を見つけられるか試してみましょう。

import numpy as np

# 学習率と反復回数の設定
learning_rate = 0.01
iterations = 1000

# 初期パラメータを設定(thetaをwとbに分割して初期化)
w = np.random.randn()  # 重みの初期値
b = np.random.randn()  # バイアスの初期値

# 特徴量(入力変数)
X = np.array([1, 2, 3, 4, 5])
# ターゲット(出力変数)
y = np.array([2, 4, 5, 4, 5])

# 勾配降下法を適用
w, b = gradient_descent(X, y, w, b, learning_rate, iterations)

# 学習後のパラメータを表示
print(f"学習後の重みw: {w}, バイアスb: {b}")

このコードを実行することで、ライナー君はデータに最もフィットする直線のパラメータを見つけ出します。学習率や繰り返し回数を変えて、結果がどう変わるか試してみるのも面白いでしょう。

実践:あなたもライナー君になれる

手始めに

まずは、簡単なデータセットを用意しましょう。numpyを使って、線形に近いデータを生成します。このデータセットが、あなたの冒険の地図となります。

import numpy as np
import matplotlib.pyplot as plt

# データセットの生成
X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)

# データのプロット
plt.scatter(X, y)
plt.xlabel("X")
plt.ylabel("y")
plt.title("Generated Data")
plt.show()

ライナー君の初歩

線形回帰モデルを訓練してみましょう。まずは、numpyを使って直接計算する方法から始めます。

import numpy as np

# 正規方程式を用いて w, b を算出
X_b = np.c_[np.ones((100, 1)), X] # 全てのサンプルにx0 = 1を追加(バイアス項を追加)
"""
X_b は設計行列で、各サンプルの特徴量の前に 1 を追加しています。これにより、バイアス b がモデルの計算に含まれるようになります。
この設計行列 X_b を使用して、正規方程式を適用し、最適なパラメーターを直接計算します。

正規方程式は以下の式で表されます:
theta_best = (X_b.T X_b)^(-1) X_b.T y

ここで、
- X_b.T は X_b の転置行列、
- (X_b.T X_b)^(-1) は X_b.T と X_b の積の逆行列、
- X_b.T y は X_b の転置行列と目標ベクトル y の積です。

この式を通じて、データセットに最もよくフィットする線形モデルのパラメータ(theta_best)を計算します。
"""
theta_best = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y)

# theta_bestからbとwを抽出
b = theta_best[0]  # バイアスbはtheta_bestの最初の要素
w = theta_best[1:]  # 重みwはtheta_bestの残りの要素

# 最適なパラメータを表示
print(f"最適なバイアスb: {b}")
print(f"最適な重みw: {w}")

自分でコードを書いてみよう

今度は、前のセクションで学んだ勾配降下法を自分で実装してみましょう。ステップバイステップでコードを書き、データにフィットする直線を見つけます。

# 勾配降下法の関数をここに記述(前述のコードを参照)
# compute_gradientとgradient_descentの定義

# 初期パラメータ、学習率、繰り返し回数を設定
learning_rate = 0.01
iterations = 10000
w_init = np.random.randn()  # 重みの初期値
b_init = np.random.randn()  # バイアスの初期値

# 勾配降下法でパラメータを更新
w, b = gradient_descent(X, y, w_init, b_init, learning_rate, iterations)

# 最適なパラメータを表示
print(f"最適なバイアスb: {b}")
print(f"最適な重みw: {w}")

Numpy(正規方程式)を用いて算出した w, b:

最適なバイアスb: [4.00945053] 最適な重みw: [[3.07275574]]

勾配効果法を用いて算出した w, b:

最適なバイアスb: [4.00945049] 最適な重みw: [3.07275578]

勾配効果法においても、正しく算出されていることがわかります。

チャレンジ

さあ、ここからが本当の冒険です。異なるデータセットを用いて、ライナー君の役割を再び担ってみてください。データを変更し、どのような結果が得られるか観察しましょう。

  • 新しいデータセットを生成してみましょう。
  • 学習率や繰り返し回数を変えて、結果がどう変わるか試してみてください。

まとめと次の冒険

旅の振り返り

今回の冒険では、ライナー君と共に、線形回帰とコスト関数の謎を解き明かしました。Pythonを駆使して、データに最もフィットする直線を見つける方法を学び、そのプロセスを実際にコードで体験しました。勾配降下法を使って、コスト関数を最小化する方法もマスターしました。これらの知識は、データ分析の基本であり、あなたの分析スキルを次のレベルに引き上げるための大切な一歩です。

次の冒険に向けて

データ分析の世界は広大で、探索すべき地域がまだまだたくさんあります。線形回帰をマスターした今、私たちはさらに複雑なデータパターンを理解するための方法、重回帰分析へと進みます。重回帰分析では、複数の説明変数を用いて、より精度の高い予測モデルを構築します。これにより、現実世界のより複雑な問題に対応することが可能になります。

  • 重回帰分析の冒険:次回は、複数の入力特徴から出力を予測する重回帰分析の技術に焦点を当てます。この新しいツールを使って、さらに実践的な問題解決能力を養いましょう。

終わりに

データ分析は、常に新しい発見があるエキサイティングな旅です。今回の旅で学んだことを基に、さらに多くの知識と技術を積み重ねていきましょう。そして、何よりも大切なのは、好奇心を持ち続け、新しいことに挑戦する勇気を持って、データの世界をさらに深く探索していくことです。次回の重回帰分析の冒険でも、あなたの学びの旅がさらに豊かなものとなることを願っています。では、次の冒険でまたお会いしましょう!

コメント

タイトルとURLをコピーしました