からっぽのしょこ

読んだら書く!書いたら読む!同じ事は二度調べ(たく)ない

6.4:行列のベクトル積の性質の導出【『スタンフォード線形代数入門』のノート】

はじめに

 『スタンフォード ベクトル・行列からはじめる最適化数学』の学習ノートです。
 「数式の行間埋め」や「Pythonを使っての再現」によって理解を目指します。本と一緒に読んでください。

 この記事は6.4節「行列ベクトル積」の内容です。
 行列とベクトルの積の定義を確認して、性質を導出します。また、NumPyライブラリでの扱い方を確認します。

【前の内容】

www.anarchive-beta.com

【他の内容】

www.anarchive-beta.com

【今回の内容】

6.4.0 行列のベクトル積の性質の導出

 行列とベクトルの積の計算を数式とプログラム(NumPyライブラリ)で確認します。
 今回は、定義式を確認して、性質を導出します。「6.4:行列のベクトル積の計算例の導出【『スタンフォード線形代数入門』のノート】 - からっぽのしょこ」では、計算例を導出します。

 利用するライブラリを読み込みます。

# 利用ライブラリ
import numpy as np


行列のベクトル積の定義

 まずは、行列のベクトル積の定義式を確認します。

  m \times n 行列  \mathbf{A} n 次元ベクトル  \mathbf{x} の積は、次の式で定義されます。

 \displaystyle
\begin{aligned}
\mathbf{b}
   &= \mathbf{A} \mathbf{x}
\\
   &= \begin{bmatrix}
          a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\
          a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\
          \vdots  & \vdots  & \ddots & \vdots \\
          a_{m,1} & a_{m,2} & \cdots & a_{m,n}
      \end{bmatrix}
      \begin{bmatrix}
          x_1 \\ x_2 \\ \vdots \\ x_n
      \end{bmatrix}
\\
   &= \begin{bmatrix}
          a_{1,1} x_1 + a_{1,2} x_2 + \cdots + a_{1,n} x_n \\
          a_{2,1} x_1 + a_{2,2} x_2 + \cdots + a_{2,n} x_n \\
          \vdots \\
          a_{m,1} x_1 + a_{m,2} x_2 + \cdots + a_{m,n} x_n
      \end{bmatrix}
\\
   &= \begin{bmatrix}
          \sum_{j=1}^n a_{1,j} x_j \\
          \sum_{j=1}^n a_{2,j} x_j \\
          \vdots \\
          \sum_{j=1}^n a_{m,j} x_j
      \end{bmatrix}
\\
   &= \begin{bmatrix}
          b_1 \\ b_2 \\ \vdots \\ b_m
      \end{bmatrix}
\end{aligned}

 計算結果  \mathbf{b} は、「行列の行数」次元のベクトルになります。「行列の列数」と「ベクトルの次元数」が一致している必要があります。
  \mathbf{b} の各項は、 \mathbf{A} の対応する列ベクトル  \mathbf{a}_i \mathbf{x} の内積

 \displaystyle
b_i = \mathbf{a}_i^{\top} \mathbf{x}
    = \sum_{j=1}^n a_{i,j} x_j

になります。

 ベクトルの内積については「内積の性質と計算例」を参照してください。

 行列と縦ベクトルを指定して、行列とベクトルの積を計算します。

# 行列(2次元配列)を指定
A = np.array(
    [[0.0, 1.0, 2.0, 3.0], 
     [4.0, 5.0, 6.0, 7.0], 
     [8.0, 9.0, 10.0, 11.0]]
)
print(A)
print(A.shape)

# 縦ベクトル(1列の2次元配列)を指定
#x = np.array([[-1.0, 2.0, -3.0, 4.0]]).T
x = np.array([-1.0, 2.0, -3.0, 4.0])[:, np.newaxis]
print(x)
print(x.shape)

# 定義式により行列とベクトルの積を計算
b = np.array(
    [np.sum(A[i] * x.flatten()) for i in range(len(A))]
)[:, np.newaxis]
print(b)
print(b.shape)

# 関数により行列とベクトルの積を計算
b = np.dot(A, x)
print(b)
print(b.shape)
[[ 0.  1.  2.  3.]
 [ 4.  5.  6.  7.]
 [ 8.  9. 10. 11.]]
(3, 4)
[[-1.]
 [ 2.]
 [-3.]
 [ 4.]]
(4, 1)
[[ 8.]
 [16.]
 [24.]]
(3, 1)
[[ 8.]
 [16.]
 [24.]]
(3, 1)

 縦ベクトル(1列(1軸の要素数が1)の2次元配列)は、1行(0軸の要素数が1)の2次元配列として値を指定して T メソッドで転置するか、1次元配列として値を指定して np.newaxis で軸を追加して作成できます。あるいは、A と同様にして値を指定します。
 np.flatten() で1次元配列に変換できます。

 リスト内包表記を使って、行列(2次元配列) A の行(0軸)を順番に取り出して、それぞれベクトル x との積和を計算します。
 行列とベクトルの積は np.dot() で計算できます。

 または、x を横ベクトル(1次元配列)として計算します。

# ベクトルを指定
x = np.array([-1.0, 2.0, -3.0, 4.0])
print(x)
print(x.shape)

# 定義式により行列とベクトルの積を計算
b = np.array(
    [np.sum(A[i] * x) for i in range(len(A))]
)
print(b)
print(b.shape)

# 関数により行列とベクトルの積を計算
b = np.dot(A, x)
print(b)
print(b.shape)
[-1.  2. -3.  4.]
(4,)
[ 8. 16. 24.]
(3,)
[ 8. 16. 24.]
(3,)

 x が横ベクトル(1次元配列)でも np.dot() で計算できます。

 どちらの計算方法でも、計算結果のベクトル bx と同じ形状になります。
 以降は、横ベクトルの場合を扱います。

行列のベクトル積の性質

 次は、行列とベクトルの積の性質を導出します。

結合法則

 「スカラー  \alpha」と「 m \times n 行列  \mathbf{A}」と「 n 次元ベクトル  \mathbf{u}」の積を考えます。

 \displaystyle
\begin{aligned}
(\alpha \mathbf{A}) \mathbf{u}
   &= \left(
          \alpha
          \begin{bmatrix}
              a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\
              a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\
              \vdots  & \vdots  & \ddots & \vdots \\
              a_{m,1} & a_{m,2} & \cdots & a_{m,n}
          \end{bmatrix}
      \right)
      \begin{bmatrix}
          u_1 \\ u_2 \\ \vdots \\ u_n
      \end{bmatrix}
\\
   &= \begin{bmatrix}
          \alpha a_{1,1} & \alpha a_{1,2} & \cdots & \alpha a_{1,n} \\
          \alpha a_{2,1} & \alpha a_{2,2} & \cdots & \alpha a_{2,n} \\
          \vdots & \vdots & \ddots & \vdots \\
          \alpha a_{m,1} & \alpha a_{m,2} & \cdots & \alpha a_{m,n}
      \end{bmatrix}
      \begin{bmatrix}
          u_1 \\ u_2 \\ \vdots \\ u_n
      \end{bmatrix}
\\
   &= \begin{bmatrix}
          \alpha a_{1,1} u_1 + \alpha a_{1,2} u_2 + \cdots + \alpha a_{1,n} u_n \\
          \alpha a_{2,1} u_1 + \alpha a_{2,2} u_2 + \cdots + \alpha a_{2,n} u_n \\
          \vdots \\
          \alpha a_{m,1} u_1 + \alpha a_{m,2} u_2 + \cdots + \alpha a_{m,n} u_n
      \end{bmatrix}
\\
   &= \begin{bmatrix}
          \alpha (a_{1,1} u_1 + a_{1,2} u_2 + \cdots + a_{1,n} u_n) \\
          \alpha (a_{2,1} u_1 + a_{2,2} u_2 + \cdots + a_{2,n} u_n) \\
          \vdots \\
          \alpha (a_{m,1} u_1 + a_{m,2} u_2 + \cdots + a_{m,n} u_n)
      \end{bmatrix}
\\
   &= \begin{bmatrix}
          \alpha \sum_{j=1}^n a_{1,j} u_j \\
          \alpha \sum_{j=1}^n a_{2,j} u_j \\
          \vdots \\
          \alpha \sum_{j=1}^n a_{m,j} u_j
      \end{bmatrix}
\end{aligned}

 スカラーとベクトルの積に分割します。

 \displaystyle
\begin{aligned}
(\alpha \mathbf{A}) \mathbf{u}
   &= \alpha
      \begin{bmatrix}
          \sum_{j=1}^n a_{1,j} u_j \\
          \sum_{j=1}^n a_{2,j} u_j \\
          \vdots \\
          \sum_{j=1}^n a_{m,j} u_j
      \end{bmatrix}
\\
   &= \alpha
      \begin{bmatrix}
          a_{1,1} u_1 + a_{1,2} u_2 + \cdots + a_{1,n} u_n \\
          a_{2,1} u_1 + a_{2,2} u_2 + \cdots + a_{2,n} u_n \\
          \vdots \\
          a_{m,1} u_1 + a_{m,2} u_2 + \cdots + a_{m,n} u_n
      \end{bmatrix}
\end{aligned}

 行列とベクトルの積に分割します。

 \displaystyle
\begin{aligned}
(\alpha \mathbf{A}) \mathbf{u}
   &= \alpha \left(
          \begin{bmatrix}
              a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\
              a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\
              \vdots  & \vdots  & \ddots & \vdots \\
              a_{m,1} & a_{m,2} & \cdots & a_{m,n}
          \end{bmatrix}
          \begin{bmatrix}
              u_1 \\ u_2 \\ \vdots \\ u_n
          \end{bmatrix}
      \right)
\\
   &= \alpha (\mathbf{A} \mathbf{u})
\end{aligned}

 行列とベクトルとスカラーの積は、結合法則が成り立ちます。

 行列とベクトルとスカラーの積の順番を入れ替えて計算します。

# ベクトルを指定
u = np.array([-1.0, 2.0, -3.0, 4.0])
print(u)
print(u.shape)

# スカラ
alpha = -1.5

# 行列とベクトルとスカラの積を計算
y = np.dot(alpha*A, u)
print(y)
print(y.shape)

# スカラと行列とベクトルの積を計算
z = alpha * np.dot(A, u)
print(z)
print(z.shape)

# 値を比較
print(y == z)
[-1.  2. -3.  4.]
(4,)
[-12. -24. -36.]
(3,)
[-12. -24. -36.]
(3,)
[ True  True  True]

 積の順番を入れ替えても計算結果が一致するのを確認できます。

分配法則

 「 m \times n 行列  \mathbf{A}」と「 n 次元ベクトル  \mathbf{u}, \mathbf{v} の和」の積を考えます。

 \displaystyle
\begin{aligned}
\mathbf{A} (\mathbf{u} + \mathbf{v})
   &= \begin{bmatrix}
          a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\
          a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\
          \vdots  & \vdots  & \ddots & \vdots \\
          a_{m,1} & a_{m,2} & \cdots & a_{m,n}
      \end{bmatrix} \left(
          \begin{bmatrix}
              u_1 \\ u_2 \\ \vdots \\ u_n
          \end{bmatrix}
          + \begin{bmatrix}
              v_1 \\ v_2 \\ \vdots \\ v_n
            \end{bmatrix}
      \right)
\\
   &= \begin{bmatrix}
          a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\
          a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\
          \vdots  & \vdots  & \ddots & \vdots \\
          a_{m,1} & a_{m,2} & \cdots & a_{m,n}
      \end{bmatrix}
      \begin{bmatrix}
          u_1 + v_1 \\
          u_2 + v_2 \\
          \vdots \\
          u_n + v_n
      \end{bmatrix}
\\
   &= \begin{bmatrix}
          a_{1,1} (u_1 + v_1) + a_{1,2} (u_2 + v_2) + \cdots + a_{1,n} (u_n + v_n) \\
          a_{2,1} (u_1 + v_1) + a_{2,2} (u_2 + v_2) + \cdots + a_{2,n} (u_n + v_n) \\
          \vdots \\
          a_{m,1} (u_1 + v_1) + a_{m,2} (u_2 + v_2) + \cdots + a_{m,n} (u_n + v_n)
      \end{bmatrix}
\\
   &= \begin{bmatrix}
          \sum_{j=1}^n a_{1,j} (u_j + v_j) \\
          \sum_{j=1}^n a_{2,j} (u_j + v_j) \\
          \vdots \\
          \sum_{j=1}^n a_{m,j} (u_j + v_j)
      \end{bmatrix}
\end{aligned}

 スカラーの積は分配法則  a_{i,j} (u_j + v_j) = a_{i,j} u_j + a_{i,j} v_j が成り立つので、括弧を展開して、ベクトルの和に分割します。

 \displaystyle
\begin{aligned}
\mathbf{A} (\mathbf{u} + \mathbf{v})
   &= \begin{bmatrix}
          \sum_{j=1}^n (a_{1,j} u_j + a_{1,j} v_j) \\
          \sum_{j=1}^n (a_{2,j} u_j + a_{2,j} v_j) \\
          \vdots \\
          \sum_{j=1}^n (a_{m,j} u_j + a_{m,j} v_j)
      \end{bmatrix}
\\
   &= \begin{bmatrix}
          (a_{1,1} u_1 + a_{1,2} u_2 + \cdots + a_{1,n} u_n) + (a_{1,1} v_1 + a_{1,2} v_2 + \cdots + a_{1,n} v_n) \\
          (a_{2,1} u_1 + a_{2,2} u_2 + \cdots + a_{2,n} u_n) + (a_{2,1} v_1 + a_{2,2} v_2 + \cdots + a_{2,n} v_n) \\
          \vdots \\
          (a_{m,1} u_1 + a_{m,2} u_2 + \cdots + a_{m,n} u_n) + (a_{m,1} v_1 + a_{m,2} v_2 + \cdots + a_{m,n} v_n)
      \end{bmatrix}
\\
   &= \begin{bmatrix}
          a_{1,1} u_1 + a_{1,2} u_2 + \cdots + a_{1,n} u_n \\
          a_{2,1} u_1 + a_{2,2} u_2 + \cdots + a_{2,n} u_n \\
          \vdots \\
          a_{m,1} u_1 + a_{m,2} u_2 + \cdots + a_{m,n} u_n
      \end{bmatrix}
      + \begin{bmatrix}
          a_{1,1} v_1 + a_{1,2} v_2 + \cdots + a_{1,n} v_n \\
          a_{2,1} v_1 + a_{2,2} v_2 + \cdots + a_{2,n} v_n \\
          \vdots \\
          a_{m,1} v_1 + a_{m,2} v_2 + \cdots + a_{m,n} v_n
        \end{bmatrix}
\end{aligned}

 それぞれ行列とベクトルの積に分割します。

 \displaystyle
\begin{aligned}
\mathbf{A} (\mathbf{u} + \mathbf{v})
   &= \begin{bmatrix}
          a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\
          a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\
          \vdots  & \vdots  & \ddots & \vdots \\
          a_{m,1} & a_{m,2} & \cdots & a_{m,n}
      \end{bmatrix}
      \begin{bmatrix}
          u_1 \\ u_2 \\ \vdots \\ u_n
      \end{bmatrix}
      + \begin{bmatrix}
          a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\
          a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\
          \vdots  & \vdots  & \ddots & \vdots \\
          a_{m,1} & a_{m,2} & \cdots & a_{m,n}
        \end{bmatrix}
        \begin{bmatrix}
          v_1 \\ v_2 \\ \vdots \\ v_n
        \end{bmatrix}
\\
   &= \mathbf{A} \mathbf{u}
      + \mathbf{A} \mathbf{v}
\end{aligned}

 行列とベクトルの積は、分配法則が成り立ちます。

 「 m \times n 行列  \mathbf{A}, \mathbf{B} の和」と「 n 次元ベクトル  \mathbf{u}」の積を考えます。

 \displaystyle
\begin{aligned}
(\mathbf{A} + \mathbf{B}) \mathbf{u}
   &= \left(
          \begin{bmatrix}
              a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\
              a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\
              \vdots  & \vdots  & \ddots & \vdots \\
              a_{m,1} & a_{m,2} & \cdots & a_{m,n}
          \end{bmatrix}
          + \begin{bmatrix}
              b_{1,1} & b_{1,2} & \cdots & b_{1,n} \\
              b_{2,1} & b_{2,2} & \cdots & b_{2,n} \\
              \vdots  & \vdots  & \ddots & \vdots \\
              b_{m,1} & b_{m,2} & \cdots & b_{m,n}
            \end{bmatrix}
      \right)
      \begin{bmatrix}
          u_1 \\ u_2 \\ \vdots \\ u_n
      \end{bmatrix}
\\
   &= \begin{bmatrix}
          a_{1,1} + b_{1,1} & a_{1,2} + b_{1,2} & \cdots & a_{1,n} + b_{1,n} \\
          a_{2,1} + b_{2,1} & a_{2,2} + b_{2,2} & \cdots & a_{2,n} + b_{2,n} \\
          \vdots & \vdots & \ddots & \vdots \\
          a_{m,1} + b_{m,1} & a_{m,2} + b_{m,2} & \cdots & a_{m,n} + b_{m,n}
      \end{bmatrix}
      \begin{bmatrix}
          u_1 \\ u_2 \\ \vdots \\ u_n
      \end{bmatrix}
\\
   &= \begin{bmatrix}
          (a_{1,1} + b_{1,1}) u_1 + (a_{1,2} + b_{1,2}) u_2 + \cdots + (a_{1,n} + b_{1,n}) u_n \\
          (a_{2,1} + b_{2,1}) u_1 + (a_{2,2} + b_{2,2}) u_2 + \cdots + (a_{2,n} + b_{2,n}) u_n \\
          \vdots \\
          (a_{m,1} + b_{m,1}) u_1 + (a_{m,2} + b_{m,2}) u_2 + \cdots + (a_{m,n} + b_{m,n}) u_n
      \end{bmatrix}
\\
   &= \begin{bmatrix}
          \sum_{j=1}^n (a_{1,j} + b_{1,j}) u_j \\
          \sum_{j=1}^n (a_{2,j} + b_{2,j}) u_j \\
          \vdots \\
          \sum_{j=1}^n (a_{m,j} + b_{m,j}) u_j
        \end{bmatrix}
\end{aligned}

 スカラーの積は分配法則  (a_{i,j} + b_{i,j}) u_j = a_{i,j} u_j + b_{i,j} u_j が成り立つので、括弧を展開して、ベクトルの和に分割します。

 \displaystyle
\begin{aligned}
(\mathbf{A} + \mathbf{B}) \mathbf{u}
   &= \begin{bmatrix}
          \sum_{j=1}^n (a_{1,j} u_j + b_{1,j} u_j) \\
          \sum_{j=1}^n (a_{2,j} u_j + b_{2,j} u_j) \\
          \vdots \\
          \sum_{j=1}^n (a_{m,j} u_j + b_{m,j} u_j)
      \end{bmatrix}
\\
   &= \begin{bmatrix}
          (a_{1,1} u_1 + a_{1,2} u_2 + \cdots + a_{1,n} u_n) + (b_{1,1} u_1 + b_{1,2} u_2 + \cdots + b_{1,n} u_n) \\
          (a_{2,1} u_1 + a_{2,2} u_2 + \cdots + a_{2,n} u_n) + (b_{2,1} u_1 + b_{2,2} u_2 + \cdots + b_{2,n} u_n) \\
          \vdots \\
          (a_{m,1} u_1 + a_{m,2} u_2 + \cdots + a_{m,n} u_n) + (b_{m,1} u_1 + b_{m,2} u_2 + \cdots + b_{m,n} u_n)
      \end{bmatrix}
\\
   &= \begin{bmatrix}
          a_{1,1} u_1 + a_{1,2} u_2 + \cdots + a_{1,n} u_n \\
          a_{2,1} u_1 + a_{2,2} u_2 + \cdots + a_{2,n} u_n \\
          \vdots \\
          a_{m,1} u_1 + a_{m,2} u_2 + \cdots + a_{m,n} u_n
      \end{bmatrix}
      + \begin{bmatrix}
          b_{1,1} u_1 + b_{1,2} u_2 + \cdots + b_{1,n} u_n \\
          b_{2,1} u_1 + b_{2,2} u_2 + \cdots + b_{2,n} u_n \\
          \vdots \\
          b_{m,1} u_1 + b_{m,2} u_2 + \cdots + b_{m,n} u_n
        \end{bmatrix}
\end{aligned}

 それぞれ行列とベクトルの積に分割します。

 \displaystyle
\begin{aligned}
(\mathbf{A} + \mathbf{B}) \mathbf{u}
   &= \begin{bmatrix}
          a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\
          a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\
          \vdots  & \vdots  & \ddots & \vdots \\
          a_{m,1} & a_{m,2} & \cdots & a_{m,n}
      \end{bmatrix}
      \begin{bmatrix}
          u_1 \\ u_2 \\ \vdots \\ u_n
      \end{bmatrix}
      + \begin{bmatrix}
          b_{1,1} & b_{1,2} & \cdots & b_{1,n} \\
          b_{2,1} & b_{2,2} & \cdots & b_{2,n} \\
          \vdots  & \vdots  & \ddots & \vdots \\
          b_{m,1} & b_{m,2} & \cdots & b_{m,n}
        \end{bmatrix}
        \begin{bmatrix}
          u_1 \\ u_2 \\ \vdots \\ u_n
      \end{bmatrix}
\\
   &= \mathbf{A} \mathbf{u}
      + \mathbf{B} \mathbf{u}
\end{aligned}

 行列とベクトルの積は、分配法則が成り立ちます。

 行列とベクトルの和と積の順番を入れ替えて計算します。

# ベクトルを指定
v = np.array([-1.0, 0.0, 1.0, 2.0])
print(v)
print(v.shape)

# 行列とベクトルの和との積を計算
y = np.dot(A, u+v)
print(y)
print(y.shape)

# 行列とベクトルの積和を計算
z = np.dot(A, u) + np.dot(A, v)
print(z)
print(z.shape)

# 値を比較
print(y == z)
[-1.  0.  1.  2.]
(4,)
[16. 32. 48.]
(3,)
[16. 32. 48.]
(3,)
[ True  True  True]

 和と積の順番を入れ替えても計算結果が一致するのを確認できます。

 行列とベクトルの和と積の順番を入れ替えて計算します。

# 行列を指定
B = np.array(
    [[-3.0, -2.5, -2.0, -1.5], 
     [-1.0, -0.5, 0.0, 0.5], 
     [1.0, 1.5, 2.0, 2.5]]
)
print(B)
print(B.shape)

# 行列の和とベクトルとの積を計算
y = np.dot(A+B, u)
print(y)
print(y.shape)

# 行列とベクトルの積和を計算
z = np.dot(A, u) + np.dot(B, u)
print(z)
print(z.shape)

# 値を比較
print(y == z)
[[-3.  -2.5 -2.  -1.5]
 [-1.  -0.5  0.   0.5]
 [ 1.   1.5  2.   2.5]]
(3, 4)
[ 6. 18. 30.]
(3,)
[ 6. 18. 30.]
(3,)
[ True  True  True]

 和と積の順番を入れ替えても計算結果が一致するのを確認できます。

交換法則

 行列とベクトルの積は、列数と次元数が一致している必要があるので、交換法則は成り立ちません。

 この記事では、行列とベクトルの積の性質を確認しました。次の記事では、計算例を確認します。

参考書籍

  • Stephen Boyd・Lieven Vandenberghe(著),玉木 徹(訳)『スタンフォード ベクトル・行列からはじめる最適化数学』講談社サイエンティク,2021年.

おわりに

 どうでもいい話ですが、6章の記事は書く順番がかなり前後しまして、最後に書いたのがこの記事です。というわけで、私的には6章完了です!

【次の内容】

www.anarchive-beta.com