【Pythonで学ぶ線形代数学講座(1)】ベクトルとスカラー
スカラー
ベクトルという新しい概念を導入するにあたって、ベクトルと区別するために普段使っている実数をスカラーとよぶことにします(場合によっては複素数もスカラーと考えることもできますが、当面はスカラーは実数であると考えて差支えありません)。下の図のような数直線を考えて、原点から任意のスカラーまで矢印を引いてみると、スカラーの符号によって矢印の方向は 2 種類あります。
スカラー同士の加算(足し算)は、この矢印をつなぎ合わせて求めることができます。たとえば、$-5$ に $+3$ を加える場合、下の図のように矢印をつないで $-2$ を得ることができます。
ベクトル
数直線上 (1次元) で定義されていた演算を $n$ 次元空間の演算に拡張するためにベクトル(vector)を導入します。2次元ベクトルはスカラーのペアとして定義されます:
\[\boldsymbol{v}=\begin{bmatrix}v_1\\v_2\end{bmatrix}\tag{1}\]
ベクトルは太字で $\boldsymbol{v}$ と書くか、あるいは文字の上に矢印を添えて $\vec{v}$ のように表します。ベクトルに含まれる個々のスカラー $v_1,\ v_2$ をベクトルの成分といいます。(1) を横書きで $(v_1,v_2)$ と書くこともあります(高校数学ではこの書き方が一般的です)。$xy$ 平面に図示するときは、任意に選んだ始点から $x$ 方向に $v_1$, $y$ 方向に $v_2$ だけ進んだ所を終点とし、始点と終点を結んだ矢印として表現します。
始点の選び方は自由なので、上の図に描かれている三本のベクトルはすべて同じベクトル $\boldsymbol{v}$ とみなします。特に始点を原点 $(0,0)$ にとるときは、ベクトルの成分は $(x,y)$ 座標に一致するので、これを 位置ベクトル (position vector) とよびます (下図)。
ベクトルの和と差
ベクトルの和は成分同士の足し算として定義されます。
\[\begin{bmatrix}v_1\\v_2\end{bmatrix}+\begin{bmatrix}w_1\\w_2\end{bmatrix}=\begin{bmatrix}v_1+w_1\\v_2+w_2\end{bmatrix}\tag{2}\]
たとえば、ベクトル $\begin{bmatrix}3\\2\end{bmatrix}$ と $\begin{bmatrix}-1\\3\end{bmatrix}$ を加えると
\[\begin{bmatrix}3\\2\end{bmatrix}+\begin{bmatrix}-1\\3\end{bmatrix}=\begin{bmatrix}2\\5\end{bmatrix}\tag{3}\]
となりますが、これは下図にあるように、2 つの矢印をつないで新しい矢印をつくることを意味しています。
和を定義したので、自動的に差をつくるルールも決まります。
式 (3) を少し変形すると
\[\begin{bmatrix}2\\5\end{bmatrix}-\begin{bmatrix}3\\2\end{bmatrix}=\begin{bmatrix}-1\\3\end{bmatrix}\tag{4}\]
となります。つまり、ベクトルの差は成分ごとの差をとってつくります:
\[\begin{bmatrix}v_1\\v_2\end{bmatrix}-\begin{bmatrix}w_1\\w_2\end{bmatrix}=\begin{bmatrix}v_1-w_1\\v_2-w_2\end{bmatrix}\tag{5}\]
上の図で見ると、幾何学的には引くほうのベクトルの終点から、引かれるベクトルの終点まで伸ばした矢印として表されます。
ベクトルとスカラーの演算
ベクトル $\boldsymbol{v}$ とスカラー $k$ の演算は積のみが定義されていて、$k\boldsymbol{v}$ はベクトル $\boldsymbol{v}$ のすべての成分を $k$ 倍することを意味します。
\[k\begin{bmatrix}v_1\\v_2\end{bmatrix}=\begin{bmatrix}kv_1\\kv_2\end{bmatrix}\tag{6}\]
幾何学的には矢印の長さを $k$ 倍にします(下図)。
負のスカラーをベクトルに掛けると向きを反転させます。
特に $-1$ を掛けたときは、長さは同じで反対方向を向くベクトルとなります。
n次元ベクトル
以上の議論は一般の $n$ 次元ベクトルにもそのまま拡張できます。
$n$ 次元ベクトルは $n$ 個のスカラーで構成されます:
\[\boldsymbol{v}=\begin{bmatrix}v_1\\v_2\\ \vdots \\v_n\end{bmatrix}\]
和と差、スカラー倍は
\[\begin{bmatrix}v_1\\v_2\\ \vdots \\v_n\end{bmatrix}\pm\begin{bmatrix}w_1\\w_2\\ \vdots \\w_n\end{bmatrix}=\begin{bmatrix}v_1\pm w_1\\v_2\pm w_2\\ \vdots \\v_n\pm w_n\end{bmatrix},\quad k\begin{bmatrix}v_1\\v_2\\ \vdots \\v_n\end{bmatrix}=\begin{bmatrix}kv_1\\kv_2\\ \vdots \\kv_n\end{bmatrix}\]
によって定義されます。
NumPyの配列演算と数学のベクトル演算の違い
SciPy および NumPy の公式ドキュメントでは、一次元配列(1-D array)のことをベクトルと表現しています。実際、一次元配列を受け取ってベクトル演算を実行するために、あらゆる種類の関数やメソッドが用意されています。しかし、演算子の使い方については、いくつかの点で注意が必要です。
ベクトルの加算・減算は、成分ごとの加算・減算として定義されています。
この操作は配列の演算規則と同じなので、そのまま適用できます。
# 1d_ndarray_calculation # In[1] import numpy as np # ベクトルを定義 v = np.array([10, 20, 30]) w = np.array([2, 4, 6]) print("v + w : {}".format(v + w)) print("w - v : {}".format(w - v)) # v + w : [12 24 36] # w - v : [8 16 24]
ベクトルの積は内積と外積の 2 種類が定義されています。ただし、NumPy の * 演算子による配列積は要素同士を掛け算する直積で、これは内積・外積のいずれとも異なる演算です(内積と外積はメソッドもしくは関数として用意されています)。
# In[2] # 配列積 print(v * w) # [ 20 80 180]
ベクトル同士の除算は定義されていませんが、/ 演算子で配列同士の除算を実行すると成分ごとに除算して返します。
# In[3] # 配列除算 print(v/w) # [5. 5. 5.]
ベクトル $\boldsymbol{v}$ とスカラー $k$ の積は、ベクトルを構成するすべての成分(要素)を $k$ 倍する操作として定義されています。この操作はスカラーと 1 次元配列でも同じです。
# In[4] # ベクトルを定義 v = np.array([1, 2, 3]) # ベクトルvにスカラー10を掛ける print(10 * v) # [10 20 30]
ベクトルとスカラーの間で加算・減算は定義されていませんが、配列 v にスカラー k を加えると、配列のすべての要素に k を加えます。
# In[5] # vにスカラー1を加える print(v + 1) # [2 3 4]
これは配列のブロードキャスト機能による演算結果です。ベクトルとスカラーの足し算を行なっているのではなく、すべての要素が 1 であるベクトルを作って、ベクトル同士の足し算を行なっています。
世界標準MIT教科書 ストラング:線形代数イントロダクション 新品価格 |
【線形代数Topic】テンソル演算
厳密には、ベクトルは一階のテンソルとして定義されます。テンソルとはスカラー、ベクトル、行列をすべて含む汎用概念です。NumPy の配列はテンソルに近い構造で設計されていて、関数やメソッドの中にはテンソル演算として定義されているものがいくつかあります。
おそらく無用な混乱を避けるためだと思われますが、公式ドキュメントではテンソルという用語はあまり使われていません。そのため、ベクトルの内積を計算すると説明されている関数に行列(二次元配列)を渡すと、内積とも行列積とも異なる不可解な演算結果を返したりするので、困惑することがしばしばあります。
テンソルは高度な概念ですが、使いこなせばとても効率的なコードを書くことができます。機械学習用ライブラリとして有名な TensorFlow も、その名が示すように Tensor(テンソル)演算をベースに設計されています。
コメント
いつも疑問に思っていたのですが、ベクトルの定義で、v = np.array([10, 20, 20])
となっていますが、これは列ベクトルなのか、あるいは行ベクトルなのか。
[10 20 30] を自分で列ベクトルに読み替えれば、計算はつじつまがあうように
なっているので今のところ納得はしています。
「NumPy」の記事のように 「1 次元配列の定義」となっているのが正解で、厳格に
2 次元配列としてnp.array([[10, 20, 30]]) や np.array([[10], [20], [30]])のように
行あるいは列ベクトルを定義してもよいが、[ ]を入力するのが面倒くさい。
配列に対して内積を計算する関数も用意したので、これでベクトル計算を代用したほうが
便利ということなのでしょうか。
一般に列ベクトルと行ベクトルを区別するのは、ベクトルを「行列」と同一視して演算を定義する場合です。たとえば、$\boldsymbol{v}$ を列ベクトルと考えるとき、よく知られているように、線形写像 $A\boldsymbol{v}$ が定義できます。しかし、$\boldsymbol{v}A$ は定義できません。逆に $\boldsymbol{v}$ を行ベクトルとみなすならば、$A\boldsymbol{v}$ は定義できませんが、$\boldsymbol{v}A$ を定義できます。
ですから、おっしゃるように、厳密にベクトルを行列の一部として捉えるのであれば、2 次元配列 np.array([[1, 2, 3]]) や np.array([[1], [2], [3]]) とすべきでしょう。しかし、NumPy の線形代数ライブラリは、関数に一次元配列が渡されたとき、演算の種類によって柔軟に処理してくれます。少し例を見てみましょう。次のような行列 $A$ と列ベクトル $v$ が与えられたとします。
\[A=\begin{bmatrix}0&1\\2&3\end{bmatrix}, \boldsymbol{v}=\begin{bmatrix}1\\2\end{bmatrix}\]
このとき、$A\boldsymbol{v}$ の演算結果は
\[A\boldsymbol{v}=\begin{bmatrix}2\\8\end{bmatrix}\]
のように列ベクトルとなります。この積の順序を交換したい場合は、$\boldsymbol{v}$ を転置して
\[\boldsymbol{v}^TA=\begin{bmatrix}1&2\end{bmatrix}\begin{bmatrix}0&1\\2&3\end{bmatrix}=\begin{bmatrix}4&7\end{bmatrix}\]
のように計算します。ところが、NumPy の線形代数ライブラリを使用する場合、こうしたことを特に意識する必要はありません。たとえば、v @ A を実行した場合でも、「定義されていない演算です!」みたいに融通の利かないエラーを返すのではなく、柔軟に $\boldsymbol{v}^TA$ を計算してくれます。
numpy.matmul() や numpy.dot() を使っても同様の結果が得られます。
公式ドキュメントの numpy.matmul() の項目には次ような説明に載っています。
要するに関数の内部処理でベクトルは自動的に行列に変換されて行列積を計算してくれます。本来であれば、その結果は行列ですが、ベクトルに再変換して戻してくれるということです。こうした設計はユーザーの利便性やコードの可読性を考えてのことだと思うので、Python で線形代数演算を実行するときは、ベクトルは基本的に 1 次元配列で渡すほうが良いと思います。
丁寧なご説明ありがとうございました。
今は数学(線形代数)のテキストにならってプログラムを組もうとしているので列ベクトルと行ベクトルを意識させられるのですが、実際にNumPyで計算させるのに慣れていけば、ベクトルを関数に渡して結果もベクトルで受け取るのが当たり前のようになるのでしょうね。