『Python数値計算ノート』ではアフィリエイトプログラムを利用して商品を紹介しています。

ベクトルの内積

【Pythonで学ぶ線形代数学講座(3)】ベクトルの内積
今回はベクトル同士の掛け算について学びます。ベクトルの掛け算には、内積と外積の2種類がありますが、今回は内積について考えます。内積の計算方法は特に難しくありませんが、「ベクトルの向きを揃えて掛ける」と考えるとイメージしやすいかと思います。機械学習の分野で頻繁に登場する概念なので、定義と実装方法についてマスターしておきましょう。外積については少し先の講座[09]で解説します

内積の定義と性質

ベクトルの 内積 (inner product) は要素同士の積の総和として定義されます。
 
たとえば、$\boldsymbol{v}=\begin{bmatrix}2\\3\end{bmatrix}$ と $\boldsymbol{w}=\begin{bmatrix}5\\1\end{bmatrix}$ の内積は
 \[\boldsymbol{v}\cdot\boldsymbol{w}=\begin{bmatrix}2\\3\end{bmatrix}\cdot\begin{bmatrix}5\\1\end{bmatrix}=2\times 5+3\times 1=13\tag{1}\]
のように計算できます。3 次元以上も同様で、一般に $n$ 次元ベクトル同士の内積は
 \[\begin{align*}\boldsymbol{v}\cdot\boldsymbol{w}=&\begin{bmatrix}v_1\\v_2\\\vdots\\v_n\end{bmatrix}\cdot\begin{bmatrix}w_1\\w_2\\\vdots\\w_n\end{bmatrix}\\[6pt]=&v_1\cdot w_1+v_2 \cdot w_2+\ \cdots\ +v_n\cdot w_n\end{align*}\tag{2}\]
と定義されます。ベクトル $\boldsymbol{v}$ の自身との内積、すなわち $\boldsymbol{v}\cdot\boldsymbol{v}$ は ベクトルの大きさ(ノルム)に等しくなっています:
 \[\boldsymbol{v}\cdot\boldsymbol{v}=v_1^2+v_2^2+\ \cdots\ +v_n^2=\parallel\boldsymbol{v}\parallel^2\tag{3}\]
すなわち、ベクトル $\boldsymbol{v}$ の大きさは内積を使って
 \[\parallel\boldsymbol{v}\parallel=\sqrt{\boldsymbol{v}\cdot\boldsymbol{v}}\tag{4}\]
のように表せます。

内積演算においては、交換法則、結合法則、分配法則が成り立ちます。
 \[\begin{align*}&\boldsymbol{v}\cdot\boldsymbol{w}=\boldsymbol{w}\cdot\boldsymbol{v}\tag{5}\\[6pt]&c(\boldsymbol{v}\cdot\boldsymbol{w})=(c\boldsymbol{v})\cdot\boldsymbol{w}\tag{6}\\[6pt]&(\boldsymbol{u}+\boldsymbol{v})\cdot\boldsymbol{w}=\boldsymbol{u}\cdot\boldsymbol{w}+\boldsymbol{v}\cdot\boldsymbol{w}\tag{7}\end{align*}\]
もう少し先の記事で行列について学ぶことになりますが、ベクトル $\boldsymbol{v},\ \boldsymbol{w}$ を $1$ 列の行列と考えて、内積を $\boldsymbol{v}^T\boldsymbol{w}$ と表記することもできます。$\boldsymbol{v}^T$ は $\boldsymbol{v}$ を転置して要素を横に並べた行ベクトルです。
 \[\boldsymbol{v}^T=\begin{bmatrix}v_1&v_2&…&v_n\end{bmatrix}\tag{8}\]
行列表記と整合性がとれるので、講座の後半では行ベクトルによる表記が増えると思います。行ベクトルを $\boldsymbol{v}=(v_1,\ v_2,\ …)$ と混同しないように注意してください。$()$ で括ってカンマで区切るベクトルは、普段使っている列ベクトルを便宜上横書きにしているだけです。

numpy.dot()

numpy.dot(a, b) は行列積を計算する関数です。a, b に 1 次元配列(ベクトル)をわたせば内積を返します (1 列の行列同士の積はベクトルの内積と同じです)。

# numpy_inner_product_1

# In[1]

import numpy as np

# ベクトルv=[3 2 6]
v = np.array([3, 2, 6])

# ベクトルw=[4 7 2]
w = np.array([4, 7, 2])

# vとwの内積を計算
ip = np.dot(v, w)

print(ip)
# 38

ndarray.dot()

配列 (ndarrayオブジェクト) には内積を計算する dot() メソッドがあります。

# In[2]

# ベクトルv=[3 2 6]
v = np.array([3, 2, 6])

# ベクトルw=[4 7 2]
w = np.array([4, 7, 2])

# vとwの内積を計算
ip = v.dot(w)

print(ip)
# 38

numpy.vdot()

numpy.vdot(a, b) にベクトル(1 次元配列)を渡して内積を計算できます。

# numpy_inner_product_2

# In[1]

import numpy as np

# ベクトルv=[7+2i 3+i]
v = np.array([7 + 2j, 3 + 1j])

# ベクトルw=[4+5i 8]
w = np.array([4 + 5j, 8])

# vとwの内積を計算
ip = np.dot(v, w)

print(ip)
# 42+51j

numpy.vdot() の引数 a, b に 2 次元以上の配列を渡すと、1 次元配列に平坦化してから内積を計算します(テンソル複内積)。

# In[2]

# 行列a
a = np.array([[1, 4],
              [3, 5]])

# 行列b
b = np.array([[3, 7],
              [4, 9]])

# 1*3+4*7+3*4+5*9
ip = np.vdot(a, b)

print(ip)
# 88

numpy.inner()

numpy.inner(a, b) にベクトル (1 次元配列) を渡して内積を計算することができます。

# numpy_inner_product_3

# In[1]

import numpy as np

# ベクトルv=[2 5 0]
v = np.array([2, 5, 0])

# ベクトルw=[3 1 4]
w = np.array([3, 1, 4])

# vとwの内積を計算
ip = np.inner(v, w)

print(ip)
# 11

引数 a, b に行列を渡すと、a と b.T の行列積を返します (b.T は b の転置行列)。すなわち、np.dot(a, b.T) と同じ結果を返します。

内積の幾何学的意味

ベクトル $\boldsymbol{v}$ と $\boldsymbol{w}$ のなす角を $\theta$ とすると、二つのベクトルの内積は
 \[\boldsymbol{v}\cdot\boldsymbol{w}=\parallel\boldsymbol{v}\parallel\parallel\boldsymbol{w}\parallel\cos\theta\tag{10}\]
と表されます。この定義は (1) と同値であり(余弦定理を使って証明できます)、幾何学的には下図のように $\boldsymbol{v}$ の射影と $\boldsymbol{w}$ の長さの積を意味します。
 
ベクトルの内積 (dot product)
2 つのベクトルのなす角度が直角であるとき、射影がなくなるので内積は $0$ になることがわかります(下図)。
 
直交するベクトルの内積はゼロ
たとえば、$\begin{bmatrix}1\\0\end{bmatrix}$ と $\begin{bmatrix}0\\1\end{bmatrix}$ は互いに直交するので内積は $0$ です。
 
また、式 (10) より、ベクトル $\boldsymbol{v}$ と $\boldsymbol{w}$ の間の角度の余弦 (cosine) は
 \[\cos\theta=\frac{\boldsymbol{v}\cdot\boldsymbol{w}}{\parallel\boldsymbol{w}\parallel\parallel\boldsymbol{v}\parallel}\tag{11}\]
となるので、$\cos$ の逆関数 $\mathrm{Arccos}$ を使うと、
 \[\theta=\mathrm{Arccos}\left(\frac{\boldsymbol{v}\cdot\boldsymbol{w}}{\parallel\boldsymbol{v}\parallel\parallel\boldsymbol{w}\parallel}\right)\tag{12}\]
によって角度を計算できます。4 次元以上のベクトルのなす角度も(想像するのは難しいですが)、(12) で定義できます。Python で角度を求める関数を実装してみましょう。

# python_vector_angle

# In[1]

import numpy as np

# 2つのベクトルのなす角度を求める関数
def v_angle(v1, v2, deg = False):

    # v1とv2の大きさ(ノルム)を計算
    v1_n = np.linalg.norm(v1)
    v2_n = np.linalg.norm(v2)

    # v1とv2の内積を計算
    v1_dot_v2 = np.dot(v1, v2)

    # 角度の余弦を計算
    cos_theta = v1_dot_v2 / (v1_n * v2_n)

    # 逆三角関数を使って角度を計算
    theta = np.arccos(cos_theta)

    # 引数degがTrueならば、角度を度数単位(degree)に変換
    if deg == True:
        theta = np.degrees(theta)

    return theta

deg は結果を度数法単位 (deg) とラジアン (rad) のどちらで返すか決定するオプション引数です。デフォルトでは False に設定されているので、この引数を省略するとラジアンで返してきます。
 
v_angle() を使って、$\begin{bmatrix}1\\0\end{bmatrix}$ と $\begin{bmatrix}1\\\sqrt{3}\end{bmatrix}$ の間の角度を求めてみましょう。
 
deg は True に設定して結果を度数単位で返すようにします。

# In[2]

# ベクトルを定義
v = np.array([1, 0])
w = np.array([1, np.sqrt(3)])

# vとwの間の角度を計算
theta = v_angle(v, w, deg = True)

print("θ = {:.3f}".format(theta))
# θ = 60.000

コメント

  1. HNaito より:

    下記は誤植と思われますので、ご確認ください。
    式(11)の上の文章で、式(6)より → 式(10)より
    式(12)の下の文章で、(2)で定義 → (12)で定義

  2. あとりえこばと より:

    【AI連載小説】科学とコードの交差点(62)
    「Pythonで内積を計算しよう」
     
    開誠がホワイトボードにベクトル $\mathbf{v}$ とベクトル $\mathbf{w}$ を書きながら、内積の定義について説明しています。

    開誠:こちらがベクトル $\mathbf{v} = [v_1, v_2, \ldots, v_n]$ とベクトル $\mathbf{w} = [w_1, w_2, \ldots, w_n]$ だ。ベクトルの内積 $\mathbf{v} \cdot \mathbf{w}$ は $v_1 \cdot w_1 + v_2 \cdot w_2 + \ldots + v_n \cdot w_n$ で表されるよね。

    美純は興味津々でホワイトボードを見つめ、明信はノートパソコンを操作しながら関数を実装しようとしています。
     
    明信:では、これを関数にしてみよう。

    def calculate_dot_product(vector1, vector2):
        # 各要素同士を掛けて足し合わせる
        dot_product = sum([v1 * v2 for v1, v2 in zip(vector1, vector2)])
        
        return dot_product

    美純はホワイトボードの数式を見ながら頷き、開誠は手元のノートでメモをとりながら次のステップに進みます。
     
    開誠:これで $\mathbf{v} \cdot \mathbf{w}$ を計算する関数ができたよ。例えば、$\mathbf{v} = [1, 2, 3]$ と $\mathbf{w} = [4, 5, 6]$ だったらどうなるか試してみよう。
     
    明信がコードを実行し、結果が表示されます。

    # ベクトルを定義
    vector_v = [1, 2, 3]
    vector_w = [4, 5, 6]
    
    # 内積を計算
    result = calculate_dot_product(vector_v, vector_w)
    
    print(f"The dot product of {vector_v} and {vector_w} is {result}")
    
    # 実行結果:
    # The dot product of [1 2 3] and [4 5 6] is 32

    開誠:これでベクトルの内積を計算する関数が使えるようになったね。