数学基礎

本講義は、深層学習(主にニューラルネットワーク)について解説します。ここでは、ニューラルネットワークの理解に必要な数学の基本知識をおさらいします。
速いアキレスが遅い亀を追いかける。亀が少し先からスタートした場合、アキレスがその地点に到達する間に、亀は少しだけ前に進んでしまう。
アキレスが新しい位置に到達するたびに、亀もわずかに進むため、アキレスは無限に追いつけないように見える。
空を飛ぶ矢を、ある瞬間に観察すると、その矢は特定の位置に「静止」している。すべての瞬間で矢が静止しているなら、「動き」は存在しないのではないか?
パラドックスの原因は 0÷0:
ΔtΔx=Δtx(t+Δt)−x(t) 極限(limit)の概念を導入することで、これらのパラドックスを解決できる。極限は、ある変数が特定の値に近づくときの関数の挙動を理解するための数学的手法である。
微分は「極限的に短い時間における変化率」を定義することにより、時間間隔がゼロに近づいても変化率(=速度)は有限の値として存在することを整合的に表す(極限で無限を有限として扱える)。
1.1微分の概念¶
微分とは、結論から言うと、変数の微小な変化に対応する、関数の変化量を求めることです。
微分を用いると接線の傾きを計算することができます。このことから、微分が関数の最小化問題に有用なツールであることがわかります。
a=(x+h)−xf(x+h)−f(x) 次にhをh→0のように小さくしていけば、直線の開始点と終了点の2点が1点に収束し、1点での接線として考えることができます。このように、平均変化率の極限値が存在するならば、微分可能であると言います。この式をfの導関数 (derivative)と呼び、f′(x)と書きます。関数f(x)の微分係数f′(a)はその曲線の点(a,f(a))(接点)における接線の傾きです。
f′(x)=h→0limhf(x+h)−f(x) 
# 微分の近似計算の精度と計算の安定性のバランスをとるために、中心差分法といった関数の前後の点を使用して計算する方法を用いる
def numerical_diff(f,x):
h = 1e-4 # 微小な変化量: 0.0001
nd = (f(x+h) - f(x-h))/(2 * h)
return nd
def function_1(x):
return x**3 - 3*x**2 + x
print(numerical_diff(function_1, 4)) # 25.0 に近い値が期待される
PyTorchでは、微分を計算するために自動微分機能を利用することができます。
import torch
# 入力値をテンソルとして作成し、勾配計算を有効にする
x = torch.tensor(4.0, requires_grad=True)
# 関数の出力を計算
y = function_1(x)
# 勾配を計算
y.backward()
# 勾配を表示
print(x.grad.item())
1.3微分の公式¶
覚えておくと便利な微分の公式がありますので,以下に幾つか紹介していきます。
(c)′(x)′(cf(x))′(xn)′(f(x)+g(x))′(f(x)g(x))′(f(g(x)))′=0=1=cf′(x)=nxn−1=f′(x)+g′(x)=f′(x)g(x)+f(x)g′(x)=dudf(u)dxdu=f′(g(x))⋅g′(x) 1.4合成関数の微分¶
y=f(x)と z=g(y)の合成関数とは、fを適用したあとにgを適用する関数、すなわち z=g(f(x))のことを指します。
合成関数の導関数がそれぞれの導関数の積で与えられる性質は連鎖律(chain rule)と言います。
dxdf(g(x))=dudf(u)dxdu
関数
y=sin(3x2+4x)
の微分を求めます。
ここで、
f(u)=sin(u) g(x)=3x2+4x とし、y は f と g の合成関数、すなわち y=f(g(x)) であると考えます。
連鎖律を用いると:
dxdy=dudf⋅dxdu f′(u)=cos(u)
g′(x)=6x+4
これらを連鎖律の式に代入すると:
dxdy=cos(3x2+4x)⋅(6x+4) したがって、
y′=cos(3x2+4x)⋅(6x+4) が得られます。
このように、連鎖律を使用することで、合成関数の微分を計算することができます。
1.5偏微分¶
機械学習において、多くの場合、複数の入力変数 x1,x2,…,xnを用いてyを予測する多変数関数が扱われます。
偏微分とは、n変数関数のある一つの変数以外のn−1個の変数の値を固定し、残りの1つの変数について関数を微分することです。
例えば、ある入力 xnにのみ注目する偏微分は以下のように表します。
∂xn∂f(x1,x2,…,xn) 微分を意味する記号が、dから∂に変わっています。こうすると、∂xn∂は xn以外を定数と考え、 xnにのみ着目して微分を行うという意味となります。
∂x1∂(3x1+4x2)=∂x1∂(3x1)+∂x1∂(4x2)=3×∂x1∂(x1)+4×∂x1∂x2=3×1+4×0=3
import numpy as np
import matplotlib.pyplot as plt
from sympy import symbols, diff
#plt.style.use('seaborn-poster')
x, y = symbols('x y')
# 関数の定義
f = x**2 + y**2
# 偏微分の計算
partial_x = diff(f, x)
partial_y = diff(f, y)
print("Partial Derivative with respect to x:", partial_x)
print("Partial Derivative with respect to y:", partial_y)
# 可視化のためのデータ生成
X, Y = np.meshgrid(np.linspace(-5, 5, 100), np.linspace(-5, 5, 100))
Z = X**2 + Y**2
Zx = 2*X
Zy = 2*Y
fig = plt.figure(figsize=(20, 6))
# 関数の可視化
ax1 = fig.add_subplot(1, 3, 1, projection='3d')
ax1.plot_surface(X, Y, Z, cmap='jet')
ax1.set_title("Function: $f(x, y) = x^2 + y^2$",size=20)
ax1.set_xlabel('$x$', labelpad=15)
ax1.set_ylabel('$y$', labelpad=15)
ax1.set_zlabel('$f(x, y)$', labelpad=15)
# xに関する偏微分
ax2 = fig.add_subplot(1, 3, 2, projection='3d')
ax2.plot_surface(X, Y, Zx, cmap='jet')
ax2.set_title(r"Partial Derivative: $\frac{\partial}{\partial x}$",size=20)
ax2.set_xlabel('$x$', labelpad=15)
ax2.set_ylabel('$y$', labelpad=15)
ax2.set_zlabel(r'$\frac{\partial f}{\partial x}$', labelpad=15)
# yに関する偏微分
ax3 = fig.add_subplot(1, 3, 3, projection='3d')
ax3.plot_surface(X, Y, Zy, cmap='jet')
ax3.set_title(r"Partial Derivative: $\frac{\partial}{\partial y}$",size=20)
ax3.set_xlabel('$x$', labelpad=15)
ax3.set_ylabel('$y$', labelpad=15)
ax3.set_zlabel(r'$\frac{\partial f}{\partial y}$', labelpad=15)
plt.tight_layout()
plt.show()
2.1ベクトル¶
2.1.1ベクトルとは¶
ベクトル(vector)とは、大きさと向きを持つ量です。ベクトルは、数が一列に並んだ集まりとして表現できます。例えば、
x=⎣⎡x1x2x3⎦⎤, y=⎣⎡y1y2⋮yN⎦⎤ 上の例のように、その要素を縦方向に並べたものは列ベクトルと呼びます。一方、
z=[z1z2z3] のように、要素を横方向に並べたものは行ベクトルと呼びます。
一般的には、ベクトルを数式で書く際には, Wのように太字の記号で表現するか、Wのようにベクトルの上に矢印を付けてベクトルを示すことが多いです。
2.1.2ベクトルの基本演算¶
加算(足し算)及び減算(引き算)は同じサイズのベクトル同士の間だけで成立します。
⎣⎡123⎦⎤+⎣⎡456⎦⎤=⎣⎡1+42+53+6⎦⎤=⎣⎡579⎦⎤ スカラ倍とはベクトルにスカラを掛ける演算です。
10⎣⎡123⎦⎤=⎣⎡10∗110∗210∗3⎦⎤=⎣⎡102030⎦⎤ 複数のベクトル a1,a2,…,an に対して、
c1a1+c2a2+⋯+cnan の形で作られるベクトルを線形結合といいます。
それらのベクトルをスカラーで伸ばしたり、足したりして作れるすべてのベクトルの集合をそれらのベクトルが張る空間(span)と呼びます

「あるベクトル集合のすべての線形結合を集めた集合」は、ベクトル空間になる。
ベクトル空間 V において、
その空間を張る最小限のベクトルの組(基底)が存在します。
このとき、
dim(V)=基底を構成するベクトルの本数 を次元(dimension)と呼びます。
内積 (ドット積) とは、同じサイズの2つのベクトルは、それぞれのベクトルの同じ位置に対応する要素同士を掛け、それらを足し合わせる計算です。xとyの内積はx⋅yで表されます。
[123]⋅⎣⎡456⎦⎤=1×4+2×5+3×6=32 ドット積は、2つのベクトルの長さと角度に関係しています:
a⋅b=∣a∣∣b∣cosθ ここで:

ベクトル
a=(a1,a2),b=(b1,b2) を考え、原点 O からそれぞれの終点を A,B とします。
なす角を θ とします(図のように)。
三角形 OAB に余弦定理を使うと,
AB2=OA2+OB2−2OAOBcosθ すなわちベクトルの長さで表すと,
∥a−b∥2=∥a∥2+∥b∥2−2∥a∥∥b∥cosθ ∥a−b∥2=(a1−b1)2+(a2−b2)2 展開して整理すると:
∥a−b∥2=(a12+a22)+(b12+b22)−2(a1b1+a2b2) すなわち,
∥a−b∥2=∥a∥2+∥b∥2−2(a1b1+a2b2) 右辺の形が同じなので,
2(a1b1+a2b2)=2∥a∥∥b∥cosθ したがって,
cosθ=∥a∥∥b∥a1b1+a2b2 2.2行列¶
2.2.1行列とは¶
行列 (matrix) は同じサイズのベクトルを複数個並べたものです。例えば、
X=⎣⎡x11x21x31x12x22x32⎦⎤ Xは「 3 行 2 列の行列」になります。
2.2.2行列積¶
行列の乗算には、行列積、外積、要素積(アダマール積)など複数の方法があります。 ここではそのうち、機械学習の多くの問題で登場します行列積について説明します。
行列Aと行列Bの行列積はABと書き 、Aの各行とBの各列の内積を並べたものとして定義されます。
例えば、行列Aの1行目の行ベクトルと、行列Bの1列目の列ベクトルの内積の結果は、AとBの行列積の結果を表す行列Cの1行1列目に対応します。

内積が定義される条件はベクトルのサイズが等しいということでしたが、ここでもそれが成り立つために、Aの行のサイズと B の列のサイズが一致する必要があります。

2つのベクトル a∈Rm,b∈Rn について:
abT=⎣⎡a1b1a2b1⋮amb1a1b2a2b2⋮amb2……⋱…a1bna2bn⋮ambn⎦⎤ となり、行列(m×n)を生成します。
a=⎣⎡123⎦⎤,b=[45] abT=⎣⎡1⋅42⋅43⋅41⋅52⋅53⋅5⎦⎤=⎣⎡481251015⎦⎤ 同じサイズの2つの行列 A,B に対して,
A∘B=[AijBij] すなわち、同じ位置の要素同士を掛けるだけの積です。
A=[1324],B=[5768] A∘B=[1⋅53⋅72⋅64⋅8]=[5211232] 2.2.3転置¶
転置(transpose)とは、m 行 n 列の行列 A に対して、 A の (i,j) 要素と (j,i) 要素を入れ替えて、n 行 m 列の行列に変換する操作です。転置は行列の右肩にTと書くことで表します。
A=⎣⎡123456⎦⎤, AT=[142536] 転置について、以下の定理を覚えておきましょう。
(1) (AT)T=A(2) (AB)T=BTAT(3) (ABC)T=CTBTAT 2.2.4ベクトルによる微分と勾配¶
深層学習は、本質的に「多数のパラメータによる線形結合」と「非線形変換」による成り立つモデルであり、以下のような計算が行われます
yi=wi1x1+wi2x2+⋯+winxn+bi ただ、深層学習モデルには、このような計算が多く含まれています
⎩⎨⎧y1=w11x1+w12x2+⋯+w1nxn+b1y2=w21x1+w22x2+⋯+w2nxn+b2⋮ym=wm1x1+wm2x2+⋯+wmnxn+bm ここで、線形代数を使うことでまとめると非常に簡潔になります
y=Wx+b 線形結合とは、スカラー倍したベクトル同士を足し合わせることです。
例えば、
bbTx=[34], x=[x1x2]=[34][x1x2]=3x1+4x2 のようにxの要素であるx1およびx2に関して一次式となっています。
bTxをベクトルxで微分したものを、
∂x∂(bTx) と表します。
「ベクトルで微分」とは、ベクトルのそれぞれの要素で対象を微分し、その結果を要素に対応する位置に並べてベクトルを作ることです。つまり、
∂x∂(bTx)=∂x∂(3x1+4x2)=[∂x1∂(3x1+4x2)∂x2∂(3x1+4x2)]=[34]
転置はベクトルに対しても定義できます。転置を用いると、2 つの列ベクトルx,yの内積x⋅yは、行列積を用いてxTyと書けます。
x と y がともに n 次元のベクトルであるとき:
x=⎣⎡x1x2⋮xn⎦⎤,y=⎣⎡y1y2⋮yn⎦⎤ 列ベクトル x の転置は行ベクトルになります:
xT=[x1,x2,…,xn] したがって、行列の掛け算として:
xTy=[x1,x2,…,xn]⎣⎡y1y2⋮yn⎦⎤=x1y1+x2y2+⋯+xnyn すなわち、内積は転置を用いた行列積として表すことができる
xTy=x⋅y 入力ベクトルの要素毎に出力に対する偏微分を計算し、それらを並べてベクトルにしたものが 勾配 (gradient) と言います。
つまり、多変数関数における傾きのようなもので、1変数関数のようにスカラーではなく、勾配で最も急な変化の方向とその大きさを示しています。勾配ベクトルは以下のように定義されます:
∇f=(∂x∂f,∂y∂f,…) ここで、関数f(x,y)=x2+y2の値等高線とその勾配を同時にプロットします。赤い矢印が勾配ベクトルを示しており、各点で関数の最も急な上昇方向を指し示しています。
# 関数とその勾配
def function(x, y):
return x**2 + y**2
def gradient(x, y):
dfdx = 2*x
dfdy = 2*y
return dfdx, dfdy
# 座標の生成
x = np.linspace(-5, 5, 20)
y = np.linspace(-5, 5, 20)
X, Y = np.meshgrid(x, y)
Z = function(X, Y)
U, V = gradient(X, Y)
# 可視化
plt.figure(figsize=(10, 8))
plt.contour(X, Y, Z, levels=50, cmap='jet') # 値を等高線で表示
plt.quiver(X, Y, U, V, angles='xy', scale_units='xy', scale=10, color='red', width=0.005) # 勾配ベクトルを矢印で表示
plt.axhline(0, color='black', linewidth=0.5)
plt.axvline(0, color='black', linewidth=0.5)
plt.title('Gradient of $f(x, y) = x^2 + y^2$')
plt.xlabel('$x$')
plt.ylabel('$y$')
plt.grid(True)
plt.show()