Pythonで理解する微分の基本「変化率」の理解



こんにちは！今回は、統計学や機械学習を学び始めた方向けに、微分の基本概念である「変化率」をPythonコードを使って解説します。微分は、データの傾向を理解したり、機械学習モデルを最適化したりする上で非常に重要な概念です。

## 1. はじめに

微分は、ある点での関数の**瞬間**の変化率を表します。これは、統計学では回帰分析の傾きを求める際に、機械学習では勾配降下法などの最適化アルゴリズムで使われます。

今回は、不動産価格と駅からの距離の関係を例に取り、微分の概念を視覚的に理解していきましょう。

## 2. 環境設定

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

```python
import numpy as np
import matplotlib.pyplot as plt
import sympy as sp
```

## 3. 関数の定義

不動産価格と駅からの距離の関係を表す関数を定義します。ここでは、駅から離れるほど指数関数的に価格が下がると仮定します。

```python
def actual_land_price(distance):
    return 100000 * np.exp(-0.001 * distance)
```

## 4. 平均変化率と微分

まず、平均変化率を計算する関数を定義します。

```python
def average_rate_of_change(f, x1, x2):
    return (f(x2) - f(x1)) / (x2 - x1)
```

この関数は2点間の平均変化率を計算します。しかし、微分は**瞬間**の変化率です。微分は、この2点間の距離を限りなく0に近づけたときの極限として定義されます：

$f'(x) = \lim_{h \to 0} \frac{f(x+h) - f(x)}{h}$

これを数値的に近似するため、非常に小さなhを使用した関数を定義します：

```python
def numerical_derivative(f, x, h=1e-5):
    return (f(x + h) - f(x)) / h
```

## 5. 線形近似

線形近似は、ある点での関数の接線を使って関数を近似する方法です。接線の傾きは、その点での関数の微分係数に等しくなります。

```python
def linear_approximation(f, x0, x):
    y0 = f(x0)
    slope = numerical_derivative(f, x0)
    return y0 + slope * (x - x0)
```

この関数は、点(x0, y0)を通り、x0での微分係数を傾きとする直線の式を返します。

## 6. データの生成とグラフの描画

```python
distance = np.linspace(0, 1000, 100)
price_actual = actual_land_price(distance)

# 2つの異なる近似点での線形近似を計算
d1, d2 = 300, 700
price_approx1 = linear_approximation(actual_land_price, d1, distance)
price_approx2 = linear_approximation(actual_land_price, d2, distance)

plt.figure(figsize=(12, 6))
plt.plot(distance, price_actual, label='実際の価格傾向')
plt.plot(distance, price_approx1, label=f'線形近似 (x={d1}m)')
plt.plot(distance, price_approx2, label=f'線形近似 (x={d2}m)')
plt.scatter([d1, d2], [actual_land_price(d1), actual_land_price(d2)], color='red', s=50, zorder=5, label='近似点')
plt.title('駅からの距離と土地価格の関係')
plt.xlabel('駅からの距離 (m)')
plt.ylabel('土地価格 (円/m²)')
plt.legend()
plt.grid(True)
plt.show()
```

## 7. 結果の解釈

このグラフから、以下のことが分かります：

1. 実際の価格傾向（青線）は非線形で、駅に近いほど急激に価格が上がります。
2. 線形近似（オレンジ線とグリーン線）は、近似点の周辺では比較的良い近似になっていますが、その点から離れるほど誤差が大きくなります。
3. 異なる点での線形近似は異なる傾きを持ち、その点での関数の振る舞いを局所的に表現しています。

## 8. 誤差の計算と解釈

線形近似がどの程度正確かを確認するため、各近似点での誤差を計算してみましょう。

```python
def calculate_error(f, approx_f, x):
    return abs(f(x) - approx_f(x))

# 近似点での誤差
error_d1 = calculate_error(actual_land_price, lambda x: linear_approximation(actual_land_price, d1, x), d1)
error_d2 = calculate_error(actual_land_price, lambda x: linear_approximation(actual_land_price, d2, x), d2)

print(f"d1({d1}m)での誤差: {error_d1:.2f} 円/m²")
print(f"d2({d2}m)での誤差: {error_d2:.2f} 円/m²")

# 中間点での誤差
d_mid = (d1 + d2) / 2
error_mid1 = calculate_error(actual_land_price, lambda x: linear_approximation(actual_land_price, d1, x), d_mid)
error_mid2 = calculate_error(actual_land_price, lambda x: linear_approximation(actual_land_price, d2, x), d_mid)

print(f"中間点({d_mid}m)での誤差（d1からの近似）: {error_mid1:.2f} 円/m²")
print(f"中間点({d_mid}m)での誤差（d2からの近似）: {error_mid2:.2f} 円/m²")
```

近似点での誤差は0に近いですが、その点から離れるほど誤差が大きくなります。これは、非線形関数を線形で近似することの限界を示しています。誤差の大きさは曲線の曲率に関係しており、曲率が大きいほど線形近似の誤差も大きくなります。

## 9. 記号微分との比較

数値微分の結果を、真の微分（記号微分）と比較してみましょう。

```python
import sympy as sp

# 記号微分
x = sp.Symbol('x')
f = 100000 * sp.exp(-0.001 * x)
f_prime = sp.diff(f, x)

print("価格関数の微分:")
print(f_prime)

# d1, d2での微分係数（記号的に計算）
derivative_d1 = f_prime.subs(x, d1).evalf()
derivative_d2 = f_prime.subs(x, d2).evalf()

print(f"\nd1({d1}m)での微分係数（記号微分）: {derivative_d1:.2f}")
print(f"d2({d2}m)での微分係数（記号微分）: {derivative_d2:.2f}")

# 数値微分との比較
numerical_d1 = numerical_derivative(actual_land_price, d1)
numerical_d2 = numerical_derivative(actual_land_price, d2)

print(f"\nd1({d1}m)での微分係数（数値微分）: {numerical_d1:.2f}")
print(f"d2({d2}m)での微分係数（数値微分）: {numerical_d2:.2f}")
```

記号微分と数値微分の結果を比較することで、数値微分がどの程度正確に真の微分を近似できているかを確認できます。

## 10. 勾配降下法との関連

勾配降下法は、関数の最小値（または最大値）を見つけるための最適化アルゴリズムです。関数の微分（勾配）を使って、関数の値が減少する方向に少しずつパラメータを更新していきます。

簡単な例として、1次元の場合を考えてみましょう：

```python
def gradient_descent(f, df, start, learning_rate=0.01, n_iter=100):
    x = start
    for _ in range(n_iter):
        gradient = df(x)
        x = x - learning_rate * gradient
    return x

# 例：x^2 関数の最小値を見つける
def f(x):
    return x**2

def df(x):
    return 2*x

minimum = gradient_descent(f, df, start=10)
print(f"x^2 の最小値は x = {minimum:.4f} で発生")
```

この例では、x^2 関数の最小値を見つけています。微分係数（ここでは2x）を使って、関数の値が減少する方向にxを更新しています。

実際の機械学習では、より複雑な多変数関数を扱いますが、基本的な考え方は同じです。各パラメータについて偏微分を計算し、それに基づいてパラメータを更新します。

## 11. まとめ

今回のPythonコードを通じて、以下のことを学びました：

1. 微分の基本概念：平均変化率と瞬間変化率の違い
2. 数値微分と記号微分の実装と比較
3. 非線形関数の線形近似方法とその限界
4. 勾配降下法の基本的な考え方

これらの概念は、回帰分析や機械学習アルゴリズムの基礎となるものです。次のステップとして、多変数関数の偏微分や、より複雑な最適化問題への応用などを学んでいくと良いでしょう。

微分は難しく感じるかもしれませんが、このような基本的な概念をしっかり理解することが、複雑なアルゴリズムを理解する上での大きな助けになります。頑張って学習を続けてください！

引用元
妥協しないデータ分析のための 微積分+線形代数入門　定義と公式、その背景にある理由、考え方から使い方まで完全網羅！ – 2024/9/21
杉山 聡 (著)
https://amzn.asia/d/ihbBQCw