ウェブサイト検索

機械学習のための微積分 (7 日間のミニコース)


機械学習のための微積分短期集中コース。
7 日間で機械学習の微積分テクニックに慣れましょう。

微積分は、多くの機械学習アルゴリズムの背後にある重要な数学手法です。アルゴリズムを使用するために必ずしもそれを知っている必要はありません。さらに深く掘り下げると、機械学習モデルの背後にある理論に関するあらゆる議論にそれが遍在していることがわかります。

実務家として、私たちは非常に難しい微積分の問題に遭遇することはほとんどないでしょう。それを行う必要がある場合は、解決策を支援する、または少なくとも解決策を検証するためのコンピューター代数システムなどのツールがあります。ただし、より重要なことは、微積分の背後にある考え方を理解し、微積分の用語を機械学習アルゴリズムでの使用に関連付けることです。

この短期集中コースでは、機械学習で使用される一般的な微積分のアイデアをいくつか学びます。 7 日間で Python の演習を行いながら学習します。

これは大きくて重要な投稿です。ブックマークしておくといいかもしれません。

始めましょう。

この短期集中コースは誰のためのものですか?

始める前に、ここが正しい場所であることを確認しましょう。

このコースは、応用機械学習の知識がある開発者を対象としています。おそらく、一般的なツールを使用して、予測モデリングの問題をエンドツーエンドで処理する方法、または少なくとも主要な手順のほとんどを処理する方法を知っているかもしれません。

このコースのレッスンでは、あなたについて次のようなことを前提としています。

  • あなたはプログラミングのための基本的な Python について理解しています。
  • 基本的な線形代数をいくつか知っているかもしれません。
  • 基本的な機械学習モデルをいくつか知っているかもしれません。

次のような必要はありません。

  • 数学の達人!
  • 機械学習の専門家です!

この短期集中コースでは、機械学習を少し知っている開発者から、機械学習アルゴリズムの微積分の概念について効果的に説明できる開発者までを導きます。

注: この短期集中コースは、SciPy や SymPy などのいくつかのライブラリがインストールされた Python 3.7 環境が動作していることを前提としています。環境に関するサポートが必要な場合は、次のステップバイステップのチュートリアルに従ってください。

  • Anaconda を使用した機械学習用に Python 環境をセットアップする方法

短期集中コースの概要

この短期集中コースは 7 つのレッスンに分かれています。

1 日に 1 つのレッスンを完了する (推奨) ことも、1 日ですべてのレッスンを完了する (ハードコア) こともできます。それは本当にあなたが使える時間とあなたの熱意のレベルによって異なります。

以下は、Python でのデータ準備を開始し、生産性を高めるための 7 つのレッスンのリストです。

  • レッスン 01: 微分積分
  • レッスン 02: 統合
  • レッスン 03: ベクトル関数の勾配
  • レッスン 04: ヤコビアン
  • レッスン 05: バックプロパゲーション
  • レッスン 06: 最適化
  • レッスン 07: サポート ベクター マシン

各レッスンには 5 分から最大 1 時間かかる場合があります。時間をかけて自分のペースでレッスンを完了してください。以下のコメントに質問したり、結果を投稿したりできます。

レッスンでは、あなたが外に出て、物事のやり方を見つけることが期待されるかもしれません。ヒントは提供しますが、各レッスンのポイントの 1 つは、Python のアルゴリズムと最高のツールに関するヘルプを探すにはどこに行けばよいのかを学習させることです。 (ヒント: すべての答えはこのブログにあります。 検索ボックスを使用してください。)

結果をコメントに投稿してください;応援するよ!

ちょっと待ってください。あきらめないで。

レッスン 01: 微分積分

このレッスンでは、微分積分または微分とは何かを学びます。

微分は、ある数学関数を別の数学関数に変換する操作であり、導関数と呼ばれます。導関数は、元の関数の傾き、つまり変化率を示します。

たとえば、関数 $f(x)=x^2$がある場合、その導関数は、$x$におけるこの関数の変化率を示す関数です。変化率は、少量の $\delta x$の場合、$$f'(x)=\frac{f(x+\delta x)-f(x)}{\delta x}$$として定義されます。

通常、上記を制限の形式で定義します。つまり、

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

つまり、$\delta x$は可能な限りゼロに近づく必要があります。

導関数を見つけやすくするための微分規則がいくつかあります。上記の例に適合するルールの 1 つは、$\frac{d}{dx} x^n=nx^{n-1}$です。したがって、$f(x)=x^2$の場合、導関数 $f'(x)=2x$が得られます。

これが事実であることは、変化率に従って計算された関数 $f'(x)$を微分規則に従って計算された関数と一緒にプロットすることで確認できます。以下では Python で NumPy と matplotlib を使用します。

import numpy as np
import matplotlib.pyplot as plt

# Define function f(x)
def f(x):
    return x**2

# compute f(x) = x^2 for x=-10 to x=10
x = np.linspace(-10,10,500)
y = f(x)
# Plot f(x) on left half of the figure
fig = plt.figure(figsize=(12,5))
ax = fig.add_subplot(121)
ax.plot(x, y)
ax.set_title("y=f(x)")

# f'(x) using the rate of change
delta_x = 0.0001
y1 = (f(x+delta_x) - f(x))/delta_x
# f'(x) using the rule
y2 = 2 * x
# Plot f'(x) on right half of the figure
ax = fig.add_subplot(122)
ax.plot(x, y1, c="r", alpha=0.5, label="rate")
ax.plot(x, y2, c="b", alpha=0.5, label="rule")
ax.set_title("y=f'(x)")
ax.legend()

plt.show()

上のプロットでは、変化率を使用して求められた微分関数と微分規則を使用して求められた導関数が完全に一致していることがわかります。

あなたのタスク

他の関数も同様に微分することができます。たとえば、$f(x)=x^3 – 2x^2 + 1$となります。微分規則を使用してこの関数の導関数を求め、その結果を限界率を使用して求めた結果と比較します。上のプロットで結果を確認してください。正しく実行している場合は、次のグラフが表示されるはずです。

次のレッスンでは、統合が微分の逆であることを発見します。

レッスン 02: 統合

このレッスンでは、統合が微分の逆であることを学びます。

関数 $f(x)=2x$を考え、各ステップごとに $\delta x$の間隔で (例: $\delta x=0.1$)、たとえば $x=-10$から$x=10$として:

$$
f(-10)、f(-9.9)、f(-9.8)、\cdots、f(9.8)、f(9.9)、f(10)
$$

明らかに、より小さいステップ $\delta x$がある場合、上記にはより多くの項が含まれます。

上記のそれぞれにステップ サイズを掛けて合計すると、次のようになります。

$$
f(-10)\times 0.1 + f(-9.9)\times 0.1 + \cdots + f(9.8)\times 0.1 + f(9.9)\times 0.1
$$

この合計は $f(x)$の積分と呼ばれます。本質的に、この合計は $f(x)$の $x=-10$から $x=10$までの曲線の下の面積です。微積分の定理によれば、曲線の下の面積を関数として置くと、その導関数は $f(x)$になります。したがって、積分は微分の逆操作として見ることができます。

レッスン 01 で見たように、$f(x)=x^2$の微分は $f'(x)=2x$です。つまり、$f(x)=2x$の場合、$\int f(x) dx=x^2$と書くことができ、あるいは $f(x)=x$の逆導関数は $x^2$であると言えます。 Python で面積を直接計算することでこれを確認できます。

import numpy as np
import matplotlib.pyplot as plt

def f(x):
    return 2*x

# Set up x from -10 to 10 with small steps
delta_x = 0.1
x = np.arange(-10, 10, delta_x)
# Find f(x) * delta_x
fx = f(x) * delta_x
# Compute the running sum
y = fx.cumsum()
# Plot
plt.plot(x, y)
plt.show()

このプロットは、レッスン 01 の $f(x)$と同じ形状をしています。実際、すべての関数は、同じ導関数を持つ定数 (例: $f(x)$と $f(x)+5$) によって異なります。したがって、計算された逆微分のプロットは、元のプロットを垂直方向にシフトしたものになります。

あなたのタスク

$f(x)=3x^2-4x$を考え、この関数の逆導関数を見つけてプロットします。また、上記の Python コードをこの関数に置き換えてみてください。両方を一緒にプロットすると、次のようになります。

以下のコメント欄に答えを投稿してください。あなたが何を思いつくかぜひ見てみたいです。

これら 2 つのレッスンは、1 つの変数を持つ関数に関するものです。次のレッスンでは、複数の変数を持つ関数に微分を適用する方法を学習します。

レッスン 03: ベクトル関数の勾配

このレッスンでは、多変量関数の勾配の概念を学びます。

1 つの変数ではなく 2 つ以上の変数の関数がある場合、微分は自然に各変数に関する関数の微分に拡張されます。たとえば、関数 $f(x,y)=x^2 + y^3$がある場合、各変数の微分を次のように書くことができます。

$$
\begin{aligned}
\frac{\partial f}{\partial x} &= 2x \\
\frac{\partial f}{\partial y} &= 3y^2
\end{整列}
$$

ここでは、偏導関数の表記法を導入しました。これは、他の変数が定数であると仮定しながら、1 つの変数で関数を微分することを意味します。したがって、上記では $\frac{\partial f}{\partial x}$を計算するときに、関数 $f(x,y)$の $y^3$部分を無視しました。

2 つの変数を持つ関数は、平面上の曲面として視覚化できます。上記の関数 $f(x,y)$は、matplotlib を使用して視覚化できます。

import numpy as np
import matplotlib.pyplot as plt

# Define the range for x and y
x = np.linspace(-10,10,1000)
xv, yv = np.meshgrid(x, x, indexing='ij')

# Compute f(x,y) = x^2 + y^3
zv = xv**2 + yv**3

# Plot the surface
fig = plt.figure(figsize=(6,6))
ax = fig.add_subplot(projection='3d')
ax.plot_surface(xv, yv, zv, cmap="viridis")
plt.show()

この関数の勾配は次のように表されます。

$$\nabla f(x,y)=\Big(\frac{\partial f}{\partial x},\; \frac{\partial f}{\partial y}\Big)=(2x,\; 3y^2)$$

したがって、各座標 $ (x,y)$では、勾配 $\nabla f(x,y)$はベクトルになります。このベクトルは次の 2 つのことを伝えます。

  • ベクトルの方向は、関数 $f(x,y)$が最も速く増加する場所を指します。
  • ベクトルのサイズは、関数 $f(x,y)$のこの方向の変化率です。

グラデーションを視覚化する 1 つの方法は、 グラデーションをベクトル フィールドとして考えることです。

import numpy as np
import matplotlib.pyplot as plt

# Define the range for x and y
x = np.linspace(-10,10,20)
xv, yv = np.meshgrid(x, x, indexing='ij')

# Compute the gradient of f(x,y)
fx = 2*xv
fy = 2*yv

# Convert the vector (fx,fy) into size and direction
size = np.sqrt(fx**2 + fy**2)
dir_x = fx/size
dir_y = fy/size

# Plot the surface
plt.figure(figsize=(6,6))
plt.quiver(xv, yv, dir_x, dir_y, size, cmap="viridis")
plt.show()

matplotlib の viridis カラー マップでは、大きい値が黄色で、小さい値が紫で表示されます。したがって、上のプロットでは、中央よりも端の方が勾配が「急」であることがわかります。

座標 (2,3) を考慮すると、次を使用して $f(x,y)$が最も速く増加する方向を確認できます。

import numpy as np

def f(x, y):
    return x**2 + y**3

# 0 to 360 degrees at 0.1-degree steps
angles = np.arange(0, 360, 0.1)

# coordinate to check
x, y = 2, 3
# step size for differentiation
step = 0.0001

# To keep the size and direction of maximum rate of change
maxdf, maxangle = -np.inf, 0
for angle in angles:
    # convert degree to radian
    rad = angle * np.pi / 180
    # delta x and delta y for a fixed step size
    dx, dy = np.sin(rad)*step, np.cos(rad)*step
    # rate of change at a small step
    df = (f(x+dx, y+dy) - f(x,y))/step
    # keep the maximum rate of change
    if df > maxdf:
        maxdf, maxangle = df, angle

# Report the result
dx, dy = np.sin(maxangle*np.pi/180), np.cos(maxangle*np.pi/180)
gradx, grady = dx*maxdf, dy*maxdf
print(f"Max rate of change at {maxangle} degrees")
print(f"Gradient vector at ({x},{y}) is ({dx*maxdf},{dy*maxdf})")

その出力は次のとおりです。

Max rate of change at 8.4 degrees
Gradient vector at (2,3) is (3.987419245872443,27.002750276227097)

式による勾配ベクトルは (4,27) であり、上記の数値結果はこれに十分近いものです。

あなたのタスク

関数 $f(x,y)=x^2+y^2$を考えてみましょう。(1,1) における勾配ベクトルは何でしょうか?偏微分から答えが得られた場合、上記の Python コードを変更して、さまざまな方向の変化率をチェックすることでそれを確認できますか?

以下のコメント欄に答えを投稿してください。あなたが何を思いつくかぜひ見てみたいです。

次のレッスンでは、ベクトル入力を受け取りベクトル出力を生成する関数の微分を学習します。

レッスン 04: ヤコビアン

このレッスンでは、ヤコビ行列について学習します。

関数 $f(x,y)=(p(x,y), q(x,y))=(2xy, x^2y)$は、2 つの入力と 2 つの出力を持つ関数です。場合によっては、この関数を呼び出してベクトル引数を受け取り、ベクトル値を返します。この関数を微分するのがヤコビアンと呼ばれる行列です。上記の関数のヤコビアンは次のとおりです。

$$
\mathbf{J} =
\begin{bmatrix}
\frac{\partial p}{\partial x} & \frac{\partial p}{\partial y} \\
\frac{\partial q}{\partial x} & \frac{\partial q}{\partial y}
\end{bmatrix}
=
\begin{bmatrix}
2y & 2x \\
2xy & x^2
\end{bmatrix}
$$

ヤコビ行列では、各行は出力ベクトルの各要素の偏微分を持ち、各列は入力ベクトルの各要素に関する偏微分を持ちます。

ヤコビアンの使用法については後で説明します。ヤコビアン行列を求めるには多くの偏微分が必要となるため、コンピューターに計算をチェックさせられれば素晴らしいでしょう。 Python では、SymPy を使用して上記の結果を検証できます。

from sympy.abc import x, y
from sympy import Matrix, pprint

f = Matrix([2*x*y, x**2*y])
variables = Matrix([x,y])
pprint(f.jacobian(variables))

その出力は次のとおりです。

⎡ 2⋅y   2⋅x⎤
⎢          ⎥
⎢        2 ⎥
⎣2⋅x⋅y  x  ⎦

SymPy にシンボル xy を定義してから、ベクトル関数 f を定義するように依頼しました。その後、jacobian() 関数を呼び出すことでヤコビアンを見つけることができます。

あなたのタスク

関数を考えてみましょう
$$
f(x,y)=\begin{bmatrix}
\frac{1}{1+e^{-(px+qy)}} &\frac{1}{1+e^{-(rx+sy)}} & \frac{1}{1+e^{-(tx+uy)}}
\end{bmatrix}
$$

ここで、$p,q,r,s,t,u$は定数です。 $f(x,y)$のヤコビアン行列とは何ですか? SymPyで確認してもらえますか?

次のレッスンでは、ニューラル ネットワークのバックプロパゲーション アルゴリズムにおけるヤコビ行列の応用を学びます。

レッスン 05: バックプロパゲーション

このレッスンでは、バックプロパゲーション アルゴリズムがヤコビ行列をどのように使用するかを説明します。

1 つの隠れ層を持つニューラル ネットワークを考慮すると、それを関数として表すことができます。

$$
y=g\Big(\sum_{k=1}^M u_k f_k\big(\sum_{i=1}^N w_{ik}x_i\big)\Big)
$$

ニューラル ネットワークへの入力はベクトル $\mathbf{x}=(x_1, x_2, \cdots, x_N)$であり、各 $x_i$に重み $w_{ik}$が乗算されて隠れ層に入力されます。隠れ層のニューロン $k$の出力は重み $u_k$で乗算され、出力層に供給されます。隠れ層と出力層の活性化関数はそれぞれ $f$と $g$です。

考えてみると

$$z_k=f_k\big(\sum_{i=1}^N w_{ik}x_i\big)$$

それから

$$
\frac{\partial y}{\partial x_i}=\sum_{k=1}^M \frac{\partial y}{\partial z_k}\frac{\partial z_k}{\partial x_i}
$$

層全体を一度に考えると、 $\mathbf{z}=(z_1, z_2, \cdots, z_M)$となり、

$$
\frac{\partial y}{\partial \mathbf{x}}=\mathbf{W}^\top\frac{\partial y}{\partial \mathbf{z}}
$$

ここで、$\mathbf{W}$は $M\times N$ヤコビ行列で、行 $k$と列 $i$の要素は $\frac{\partial z_k}{\partial x_i}$です。

これは、ニューラル ネットワークのトレーニングにおけるバックプロパゲーション アルゴリズムの仕組みです。複数の隠れ層を持つネットワークの場合、各層のヤコビ行列を計算する必要があります。

あなたのタスク

以下のコードは、自分で試すことができるニューラル ネットワーク モデルを実装しています。これには、2 つの隠れ層と、2 次元の点を 2 つのクラスに分離するための分類ネットワークがあります。関数 backward() を調べて、どれがヤコビアン行列であるかを特定してください。

このコードを試す場合、クラス mlp は変更しないでください。ただし、モデルの作成方法に関するパラメーターは変更できます。

from sklearn.datasets import make_circles
from sklearn.metrics import accuracy_score
import numpy as np
np.random.seed(0)

# Find a small float to avoid division by zero
epsilon = np.finfo(float).eps

# Sigmoid function and its differentiation
def sigmoid(z):
    return 1/(1+np.exp(-z.clip(-500, 500)))
def dsigmoid(z):
    s = sigmoid(z)
    return 2 * s * (1-s)

# ReLU function and its differentiation
def relu(z):
    return np.maximum(0, z)
def drelu(z):
    return (z > 0).astype(float)

# Loss function L(y, yhat) and its differentiation
def cross_entropy(y, yhat):
    """Binary cross entropy function
        L = - y log yhat - (1-y) log (1-yhat)

    Args:
        y, yhat (np.array): nx1 matrices which n are the number of data instances
    Returns:
        average cross entropy value of shape 1x1, averaging over the n instances
    """
    return ( -(y.T @ np.log(yhat.clip(epsilon)) +
               (1-y.T) @ np.log((1-yhat).clip(epsilon))
              ) / y.shape[1] )

def d_cross_entropy(y, yhat):
    """ dL/dyhat """
    return ( - np.divide(y, yhat.clip(epsilon))
             + np.divide(1-y, (1-yhat).clip(epsilon)) )

class mlp:
    '''Multilayer perceptron using numpy
    '''
    def __init__(self, layersizes, activations, derivatives, lossderiv):
        """remember config, then initialize array to hold NN parameters
        without init"""
        # hold NN config
        self.layersizes = tuple(layersizes)
        self.activations = tuple(activations)
        self.derivatives = tuple(derivatives)
        self.lossderiv = lossderiv
        # parameters, each is a 2D numpy array
        L = len(self.layersizes)
        self.z = [None] * L
        self.W = [None] * L
        self.b = [None] * L
        self.a = [None] * L
        self.dz = [None] * L
        self.dW = [None] * L
        self.db = [None] * L
        self.da = [None] * L

    def initialize(self, seed=42):
        """initialize the value of weight matrices and bias vectors with small
        random numbers."""
        np.random.seed(seed)
        sigma = 0.1
        for l, (n_in, n_out) in enumerate(zip(self.layersizes, self.layersizes[1:]), 1):
            self.W[l] = np.random.randn(n_in, n_out) * sigma
            self.b[l] = np.random.randn(1, n_out) * sigma

    def forward(self, x):
        """Feed forward using existing `W` and `b`, and overwrite the result
        variables `a` and `z`

        Args:
            x (numpy.ndarray): Input data to feed forward
        """
        self.a[0] = x
        for l, func in enumerate(self.activations, 1):
            # z = W a + b, with `a` as output from previous layer
            # `W` is of size rxs and `a` the size sxn with n the number of data
            # instances, `z` the size rxn, `b` is rx1 and broadcast to each
            # column of `z`
            self.z[l] = (self.a[l-1] @ self.W[l]) + self.b[l]
            # a = g(z), with `a` as output of this layer, of size rxn
            self.a[l] = func(self.z[l])
        return self.a[-1]

    def backward(self, y, yhat):
        """back propagation using NN output yhat and the reference output y,
        generates dW, dz, db, da
        """
        # first `da`, at the output
        self.da[-1] = self.lossderiv(y, yhat)
        for l, func in reversed(list(enumerate(self.derivatives, 1))):
            # compute the differentials at this layer
            self.dz[l] = self.da[l] * func(self.z[l])
            self.dW[l] = self.a[l-1].T @ self.dz[l]
            self.db[l] = np.mean(self.dz[l], axis=0, keepdims=True)
            self.da[l-1] = self.dz[l] @ self.W[l].T

    def update(self, eta):
        """Updates W and b

        Args:
            eta (float): Learning rate
        """
        for l in range(1, len(self.W)):
            self.W[l] -= eta * self.dW[l]
            self.b[l] -= eta * self.db[l]

# Make data: Two circles on x-y plane as a classification problem
X, y = make_circles(n_samples=1000, factor=0.5, noise=0.1)
y = y.reshape(-1,1) # our model expects a 2D array of (n_sample, n_dim)

# Build a model
model = mlp(layersizes=[2, 4, 3, 1],
            activations=[relu, relu, sigmoid],
            derivatives=[drelu, drelu, dsigmoid],
            lossderiv=d_cross_entropy)
model.initialize()
yhat = model.forward(X)
loss = cross_entropy(y, yhat)
score = accuracy_score(y, (yhat > 0.5))
print(f"Before training - loss value {loss} accuracy {score}")

# train for each epoch
n_epochs = 150
learning_rate = 0.005
for n in range(n_epochs):
    model.forward(X)
    yhat = model.a[-1]
    model.backward(y, yhat)
    model.update(learning_rate)
    loss = cross_entropy(y, yhat)
    score = accuracy_score(y, (yhat > 0.5))
    print(f"Iteration {n} - loss value {loss} accuracy {score}")

次のレッスンでは、微分を使用して関数の最適な値を見つける方法を学びます。

レッスン 06: 最適化

このレッスンでは、微分の重要な使い方を学びます。

関数の微分は変化率であるため、微分を利用して関数の最適点を見つけることができます。

関数が最大値に達した場合、関数は低い点から最大値まで移動すると予想され、さらに移動すると、関数はさらに低い点に落ちます。したがって、最大値の時点では、関数の変化率はゼロになります。最小値の場合はその逆です。

例として、$f(x)=x^3-2x^2+1$を考えてみましょう。 $x=0$および $x=4/3$での導関数は $f'(x)=3x^2-4x$および $f'(x)=0$です。したがって、$x$のこれらの位置は、$f(x)$が最大値または最小値になる場所です。 $f(x)$をプロットすることで視覚的に確認できます (レッスン 01 のプロットを参照)。

あなたのタスク

関数 $f(x)=\log x$を考え、その導関数を求めます。 $f'(x)=0$のとき$x$の値はどうなるでしょうか?対数関数の最大値または最小値について何がわかりますか? $\log x$の関数をプロットして、答えを視覚的に確認してください。

次のレッスンでは、サポート ベクターを見つける際のこの手法の応用を学びます。

レッスン 07: サポート ベクター マシン

このレッスンでは、サポート ベクター マシンを最適化問題に変換する方法を学びます。

2 次元平面では、任意の直線は次の方程式で表すことができます。

$$ax+by+c=0$$

$xy$座標系で。座標幾何学の研究結果によると、任意の点 $ (x_0,y_0)$について、線分 $ax+by+c=0$までの距離は次のようになります。

$$
\frac{\vert ax_0+by_0+c \vert}{\sqrt{a^2+b^2}}
$$

$xy$平面の点 (0,0)、(1,2)、(2,1) について考えます。最初の点と後の 2 つの点は異なるクラスに属します。これら 2 つのクラスを最もよく分ける境界線は何でしょうか?これはサポート ベクター マシン分類子の基礎です。この場合、サポート ベクターは最大分離線です。

そのような行を見つけるために、次のものを探します。

$$
\begin{整列}
\text{最小化} && a^2 + b^2 \\
\text{対象} && -1(0a+0b+c) &\ge 1 \\
&& +1(1a+2b+c) &\ge 1 \\
&& +1(2a+1b+c) &\ge 1
\end {整列}
$$

目標 $a^2+b^2$は、各データ点からラインまでの距離が最大になるように最小化されます。条件 $-1(0a+0b+c)\ge 1$は、点 (0,0) がクラス $-1$であることを意味します。他の 2 点についても同様に、クラス $+1$です。直線は、これら 2 つのクラスを平面の異なる側に配置する必要があります。

これは制約付き最適化問題であり、これを解決する方法はラグランジュ乗数アプローチを使用することです。ラグランジュ乗数アプローチを使用する最初のステップは、次のラグランジュ関数の偏微分を求めることです。

$$
L=a^2+b^2 + \lambda_1(-c-1) + \lambda_2 (a+2b+c-1) + \lambda_3 (2a+b+c-1)
$$

偏微分をゼロに設定して、$a$、$b$、$c$を解きます。ここで説明するには長すぎますが、SciPy を使用して上記の解を数値的に見つけることができます。

import numpy as np
from scipy.optimize import minimize

def objective(w):
    return w[0]**2 + w[1]**2

def constraint1(w):
    "Inequality for point (0,0)"
    return -1*w[2] - 1

def constraint2(w):
    "Inequality for point (1,2)"
    return w[0] + 2*w[1] + w[2] - 1

def constraint3(w):
    "Inequality for point (2,1)"
    return 2*w[0] + w[1] + w[2] - 1

# initial guess
w0 = np.array([1, 1, 1])

# optimize
bounds = ((-10,10), (-10,10), (-10,10))
constraints = [
    {"type":"ineq", "fun":constraint1},
    {"type":"ineq", "fun":constraint2},
    {"type":"ineq", "fun":constraint3},
]
solution = minimize(objective, w0, method="SLSQP", bounds=bounds, constraints=constraints)
w = solution.x
print("Objective:", objective(w))
print("Solution:", w)

次のように出力されます:

Objective: 0.8888888888888942
Solution: [ 0.66666667  0.66666667 -1.        ]

上記は、これら 3 つの点を分ける線が $0.67x + 0.67y – 1=0$であることを意味します。 $N$データポイントを指定した場合、$N$制約が定義されることになることに注意してください。

あなたのタスク

点 (-1,-1) と (-3,-1) を (0,0) とともに第 1 クラスとし、点 (3,3) を点 (1,2) とともに第 2 クラスとみなします。 ) と (2,1)。この 6 点の問題で、上記のプログラムを修正して 2 つのクラスを区切る線を見つけることができますか?解決策が上記と同じであっても驚かないでください。それには理由があります。わかりますか?

以下のコメント欄に答えを投稿してください。あなたが何を思いつくかぜひ見てみたいです。

これが最後のレッスンでした。

最後です!
(どこまで来たか)

成功しましたね。よくやった!

少し時間を取って、これまでの道のりを振り返ってください。

あなたは次のことを発見しました:

  • 微分とは何か、そしてそれが関数にとって何を意味するのか
  • 統合とは何ですか
  • 微分をベクトル引数の関数に拡張する方法
  • ベクトル値関数で微分を行う方法
  • ニューラル ネットワークのバックプロパゲーション アルゴリズムにおけるヤコビアンの役割
  • 微分を使用して関数の最適点を見つける方法
  • サポート ベクター マシンは制約付き最適化問題であり、解決するには微分が必要です。

まとめ

ミニコースはどうでしたか?
この短期集中コースは楽しかったですか?

何か質問はありますか?何か引っかかる点はありましたか
教えてください。以下にコメントを残してください。

関連記事