一ヶ月前にGoogleがTensorFlowという機械学習ライブラリを公開しました。すでにQiitaにも幾つかの投稿がされています。さて、TensorFlowの公開でテンソルという単語を初めて聞いたという方がいると思います。そのような人を対象にテンソルの入門を書きたいと思います。
テンソルというのはベクトルや行列の仲間で、ベクトルが一次元、行列が二次元状に数値が並んでいるのを一般化して、$n$次元状に数値を並べたものです。なので、多次元配列を使えば、テンソルをプログラムの中で表現できます。
実際、TensorFlowでもnumpyのndarrayという多次元配列を利用しています。プログラムを書いたり利用したりする上で、上の説明で事足りることが多いでしょう。しかし、数学的に理解するという点からは満足できないでしょう。数学的に掘り下げてみましょう。
準備
まず、テンソルはベクトルや行列の延長上にあるので、大学一回生でならうであろう線形代数の話から始めます。
線形代数学は、ベクトルや行列、ベクトル空間について扱う分野です。基底、行列式、逆行列、固有値といった言葉が登場します。今回必要となるのは基底です。
基底
ベクトル空間とはベクトルの集まりでした。(正確には、ベクトル空間は、ベクトル空間の公理を満たす構造を持った集合で、その要素をベクトルという。)ベクトル空間は、普通、要素が多すぎて扱いづらいものに見えますが、 基底というものを使うことで、かなり扱いやすくすることができます。定義を書いておきましょう。
集合{$e_{\lambda}$}$_{\lambda\in\Lambda}$が体$K$上のベクトル空間 $V$ の基底とは、任意のベクトル$v\in V$が、
v=\sum_{\lambda\in\Lambda} a_\lambda e_\lambda,
ただし、$a_\lambda\in K$で、有限個の$\lambda$を除いて$a_\lambda=0$、となるような{$a_\lambda$}$_{\lambda\in\Lambda}$が、ただ一つ存在するときをいう。
任意のベクトルが基底の一次結合でただ一通りにかけるという言い方もできる。基底によってそのベクトル空間が支配されている状況だ。以下、話を簡単にするために有限次元の場合だけを考えることにしましょう。つまり、この基底という集合が有限集合の場合を考えることにします。基底を
\left\{e_i\right\}_{i=1,\dots,n}
として、基底の定義を書きなおしてみると、すべてのベクトル$v$が、$a_1,a_2,\dots ,a_n \in K$によって、
v=a_1 e_1 + a_2 e_2 + \dots + a_n e_n
と一意的にかける。
$a_1,a_2,\dots,a_n$。一次元状に並んだ値の列・・・。そう、配列が現れる。 **基底を介して、ベクトルが配列で表現されている。**抽象的なベクトル空間の議論が基底によって数値化される。(通常、数学では配列という言葉は使われず、縦ベクトルや横ベクトルと呼ばれる)
行列
基底を取ることでベクトルが配列になることを説明した。では、行列はどうだろうか。行列は、もう行と列があって、そこに数値が並んでいる。行列は何をどうすると数値化したら出てくるのか。
結論からいうと、線形写像を数値化すると行列(二次元配列)になります。線形写像の定義も見てみましょう。
$K$上のベクトル空間$V$から$K$上のベクトル空間$W$への写像$F$(写像は関数のようなもの)が線形写像とは次をみたすときをいう。
- $v_1,v_2\in V$に対して、$F(v_1+v_2) = F(v_1) + F(v_2)$。
- $a\in K$, $v\in V$に対して、$F(av) = aF(v)$。
さて、$V$と$W$の基底を使って数値化することを試みましょう。
$V$の基底を$e_1,e_2,\dots,e_m$、$W$の基底を$f_1,f_2,\dots,f_n$としよう。ベクトル$v\in V$は$v=a_1 e_1 + a_2 e_2 + \dots + a_m e_m$に対して、$F(v)$を調べる。
\begin{align}
F(v) &= F(\sum_{j=1}^m{a_j e_j})\\
&= \sum_{j=1}^m{a_jF(e_j)}
\end{align}
のようになる。$F(e_i)\in W$がわかれば、$F(v)$が計算できることがわかるでしょう。基底が線形代数学を支配しているのがことがここでも窺える。$F(e_j)\in W$を$W$の基底$f_1,f_2,\dots,f_n$で次のように書き直す。
$$F(e_j)=\sum_{i=1}^n A_{i,j}f_i.$$
このように$A_{i,j}$を使って置くと、$F(v)$は
$$F(v)=\sum_{i=1}^n{(\sum_{j=1}^m{A_{i,j}a_j})f_i}$$
と計算できる。
$V$と$W$の基底を使って、線形写像が
\{A_{i,j}\}_{i=1,\dots,n,j=1,\dots,m}
で数値化できた。添字が$i$と
$j$の2つあり、$i$行目の$j$列目が$A_{i,j}$の$n\times m$行列$A$で線形写像が表現できた。
余談
別の考え方もあります。$V$から$W$への線形写像の集合$\mathrm{Hom}(V,W)$もベクトル空間で、その基底を取って数値化するというものです。$V$と$W$の基底は上と同じように取るとする。線形写像$F_{i,j}$を
F_{i,j}(\sum_{k=1}^m{a_k e_k}) = a_j f_i
で定める。すると、
\{F_{i,j}\}_{i,j}
が$\mathrm{Hom}(V,W)$の基底になる。添字が2つありますが、無理に1つにすることなく、そのままにして、添字が2つあるから、数値化すると二次元配列で表示できる。
ここまで書かなかったことだが、線形代数の重要なポイントの1つに、基底を取り替えると表示がどのように変わるかというのがある。1つのベクトル空間に対して、基底の取り方は、普通、一通りではない。別の基底を選択すると、ベクトルの表示や行列表示も別のものになり、行列が真の姿を現すが、詳しくは、線形代数についての本格的な書物を参考にされたい。
まとめ
線形代数の抽象的に定義される概念も、基底を使うことで、数値化して扱いやすくなる。テンソルが多次元配列を使うのも同様の仕組みからくる。
テンソル
テンソルに移ろう。複数のベクトル空間から新しいベクトル空間を作るということを線形代数ではよく行う。その1つにテンソル積がある。テンソル積によってできるベクトル空間の要素をテンソルという。
まず$K$上のベクトル空間$V, W$のテンソル積$V\otimes W$からはじめよう。例のごとく、$V$の基底を$e_1,e_2,\dots,e_m$、$W$の基底を$f_1,f_2,\dots,f_n$とする。$V\otimes W$を
\left\{e_i\otimes f_j\right\}_{i=1,\dots,m,j=1,\dots,n}
の形式的な一次結合として表現されるものの集合とする。($e_i\otimes f_j$も単なる形式的な対だと考えてほしい。)
$V\otimes W$の要素は、
$$\sum_{i=1}^m\sum_{j=1}^n{a_{i,j}e_i\otimes f_j}$$
という形をしている。このような形式的な表現を集めてできたベクトル空間がテンソル積$V\otimes W$です。
では、$V\otimes W$の基底を考えよう。とはいえ、それはもう出てきている。
\left\{e_i\otimes f_j\right\}_{i=1,\dots,m,j=1,\dots,n}
が基底となっている。
\sum_{i=1}^m\sum_{j=1}^n{a_{i,j}e_i\otimes f_j}
中から、この基底で数値化して表示し数値の部分だけを拾ってくると、
\left\{a_{i,j}\right\}_{i,j}
のように、2つの添字がついた表示となる。
2つのベクトル空間をテンソル積でくっつけたが、もっと多くのベクトル空間のテンソル積も考えられる。$V_1\otimes V_2\otimes\dots\otimes V_n$を考えよう。
足し算や掛け算のように順にテンソル積を取っていく。まず、$V_1$と$V_2$のテンソル積を作り、それと$V_3$とのテンソル積を作り、・・・と$V_n$まで続ける。そうすれば、$V_1\otimes V_2\otimes\dots\otimes V_n$が出来上がる。
$T=V_1\otimes V_2\otimes\dots\otimes V_n$の基底を考えよう。まず$V_i$の基底を
\left\{e_{i,j}\right\}_{j=1,\dots,d_i}
としよう。さっきは2つだったものが、$n$個になったのだから、基底でも、2つのところを$n$個に変えればいい。つまり、
\left\{e_{1,j_1}\otimes e_{2,j_2}\otimes\dots \otimes e_{n,j_n}\right\}_{j_i=1,\dots,d_i}
が$T$の基底になる。この基底を使って$T$の要素を書くと、
$$\sum_{j_1=1,\dots,d_1}\sum_{j_2=1,\dots,d_2}\dots\sum_{j_n=1,\dots,d_n}a_{j_1,j_2,\dots, j_n}e_{1,j_1}\otimes e_{2,j_2}\otimes\dots \otimes e_{n,j_n}$$
となる。簡単にするために
e_{j_1,j_2 ,\dots, j_n} = e_{1,j_1}\otimes e_{2,j_2}\otimes\dots
\otimes e_{n,j_n}
と置くと、
$$\sum_{j_1=1,\dots,d_1}\sum_{j_2=1,\dots,d_2}\dots\sum_{j_n=1,\dots,d_n}a_{j_1,j_2,\dots, j_n}e_{j_1,j_2,\dots,j_n}$$
慣れていない人には、もう数学なんて関わらないでおこうと思ってしまいそうな式ですが、 添字が$n$個ある基底にそれぞれ係数をつけて足し合わせるという式だと理解しましょう。
最初にしたテンソルの説明を思い出しましょう。$n$次元状に数値を並べたものがテンソルだと説明しました。慣れた人なら、添字が$n$個あるからそれに合わせて並べるんだと理解できると思います。どのようにするかというと、$n$次元の点$(j_1,j_2,\dots,j_n)$に数値$a_{j_1,j_2,\dots,j_n}$が配置されていると思いましょう。($n$次元の点というのがイメージしづらいと思いますが、$n=1,2,3$の場合だけイメージして満足して、それ以上はこれの延長だと思いましょう。)
発展
より本格的なテンソルの議論が知りたいという人は、「線形代数学」(佐武一郎 著)のテンソルの章が充実していると思います。テンソル積の定義はここで書いたものとは別の方法でしています。ですが、概念として同じものとして使えます。
さらに進もうと思ったら、加群のテンソル積というものがあります。環論の本を参考にしてください。双線形写像に関する普遍性を使う抽象的な定義がされていると思います。
物理学でもテンソルが登場することがあります。アインシュタインの相対性理論でテンソルが使われることがありますが、このテンソルはテンソル場のことで、空間の各点にテンソルが乗っているようなものです。
プログラミングでテンソルが使われているのを体験したければ、TensorFlowのチュートリアルに取り組んでみてはどうでしょうか。"Deep MNIST for Experts"では4階のテンソル(上の説明なら$n=4$の場合)が使われています。
注意
幾つか数学では使われないような言葉を使った。配列や数値化という言葉は、数学では使われない言い方だが、プログラミングなら分かるという人を想定して、このような言葉を使った。