この記事はQiita 数学 Advent Calendar 2015の3日目の記事です。(後付け)
さいきんTensorFlowが流行っているので、Rのtensorパッケージの内容を唐突に説明します。
tensorのことを何も知らない人にも、なるべく優しくします。
将来、相対論とか真面目にやりたい人には、ちょっといい感じかもしれません。
ライブラリ
次のライブラリを使用します。
install.packages("tensor")
library(tensor)
NULLを指定したtensorと、テンソル積の定義みたいなもの
ベクトルとベクトルとテンソルと
縮約しない、「本当のテンソル積」を得るには、次のようにします。
> A <- c(1,2,3)
> B <- c(4,5,6)
> tensor(A,B,NULL,NULL)
[,1] [,2] [,3]
[1,] 4 5 6
[2,] 8 10 12
[3,] 12 15 18
いわゆる普通の行列の添字のルールで$C=(C_{i,j})$とすると、$A$の添え字が$i$に、$B$の添え字が$j$に、それぞれ対応します。
ここで本当のテンソル積と言っているのは、要するに$A$を$A=(a_1,a_2,a_3)$を適当な基底$<u_1,u_2,u_3>$に対する行列表示として$a_1u_1 + a_2u_2 + a_3u_3$のことと思い、$B=(b_1,b_2,b_3)$を同様に適当な基底$<v_1,v_2,v_3>$に対する行列表示のことで$b_1v_1 + b_2v_2 + b_3v_3$と同一視することにして、$<u_1,u_2,u_3>\otimes <v_1,v_2,v_3>$の基底として$<u_i\otimes v_j>$を取った時の行列表示、ということです。
こういう書き方をすると非常に仰々しい感じもします。
素朴な考え方としては、$a,b$を係数、$u,v$をベクトルとして、$(au)\otimes(bv)=a(u\otimes(bv))=b((au)\otimes v)=ab(u\otimes v)$を満たして、いわゆる分配法則を満たすような「積」みたいなものを考えたとき、その中で最も「普遍的な」対象を考えると、(その存在や定義は決して自明ではないものの)きちんと存在することが証明できて、そのようなもののことをテンソル積と言います。
「普遍的な」と言っているのは、Universal propertyという数学的に定義される意味での普遍性を意味しますが、ここでは詳細は割愛します。
$\otimes$を、本当に掛け算の記号みたいなものと思って、上で書いた等式を用いて計算すると、$(a_1u_1 + a_2u_2 + a_3u_3)\otimes (b_1v_1 + b_2v_2 + b_3v_3) = \sum a_ib_j u_i\otimes v_j$という形に変換できます。
このテンソル積はベクトル空間でもあって、これを書き表すために基底を選ぶことを考えると、上で述べた$<u_i\otimes v_j>$が基底として機能するので、この基底を使ってテンソル積を「計算」した結果が上の行列になる、ということになります。
ベクトルを1階の配列と思い、複数階のテンソルを取るときの基底とか計算について考えてみる
ベクトルは1階の配列、つまり添え字の数が一つの配列と思うことができます。このとき、添え字のとりうる値が次元に対応します。
例えば、3次元のベクトルa=(a[1],a[2],a[3])というものを考えると、aの添え字の個数は1つで、添え字は1,2,3の3つの値を取り得るので3次元、というような捉え方です。
複数の添え字を持つ配列、a[i,j]は、添え字の個数が2つになるという意味で、2階の配列という言い方ができます。このとき、例えば2×2型の配列を考えると、(a[1,1],a[1,2],a[2,1],a[2,2])という4つの値を持つことになり、この2階の配列は次元で言えば4次元になります。
一般に、ベクトル空間は必ずしも「階」という構造を持たないですが、この「階」という構造と、上のテンソル積の考え方を両立した方が色々と物事を考えやすい場合があります。
具体的には、テンソル積は「階の足し算」みたいに思う、ということです。
例えば、$C=(c_{ij})$を2階の配列、$D=(d_k)$を1階の配列とします。
上の計算と同じようなルールで$C\otimes D = \sum_{i,j,k} c_{ij} d_k(u_{ij}\otimes v_k)$という計算をすると、$c_{ij}d_k$という数たちを$i,j,k$の組み合わせ分だけ作り出す作業とある意味同じであることが分かります。
そこで、一般に複数階、すなわち複数の添え字の組み合わせによって表されているような配列に対して、$m$階の配列$A=(a_{i_1,...,i_m})$と$n$階の配列$B=(b_{j_1,...,j_n})$から$m+n$階の配列$(a_{i_1,...,i_m}b_{j_1,...,j_n})$を作り出す作業を定式化できます。
このような計算を行うのが、tensor(A,B,NULL,NULL)
です。
クロネッカー積との関係
Rには、よく知られた演算であるクロネッカー積というものが定義されています。
> A %x% t(B)
[,1] [,2] [,3]
[1,] 4 5 6
[2,] 8 10 12
[3,] 12 15 18
上のテンソル積の計算結果と、全く同じですね。
クロネッカー積というのは、実はテンソル積の様々な実現方法のひとつなのですが、この積は特によい性質を持っています。
それは、(AB)%x%(CD) = (A%x%C)(B%x%D)
という性質です。普通の行列の積をあえて%*%
と書くことにすれば(これはRの普通の記法ですが)、(A%*%B)%x%(C%*%D) = (A%x%C)%*%(B%x%D)
という、%*%
と%x%
を交換できるような、そういう性質です。
クロネッカー積は、行列=2階の配列から行列=2階の配列への積なので、tensor()
のように階数が増えたりしません。(上の例の場合は、Aやt(B)は1x3や3x1の潰れた形の行列とみなしています。)
ここではこんなしょぼい例でしか試していないですが、もう少し一般の行列とかでも試してみてください。
tensorの縮約と、行列の積と
何も考えずに、次のような計算をしてみます。
> A <- matrix(1:4,2,2)
> B <- matrix(4:1,2,2)
> A %*% B
[,1] [,2]
[1,] 13 5
[2,] 20 8
> tensor(A,B,2,1)
[,1] [,2]
[1,] 13 5
[2,] 20 8
NULL以外の数、2と1を指定すると、これは普通に行列の積と同じになりました。
NULLを指定したtensorであれば4階になるはずが、2階の形になっている、というところが重要なポイントです。
実は、この計算は、「Aの2つ目の添え字と、Bの1つ目の添え字を縮約する」ということを意味しています。
縮約というのは、ざっくりと「添え字が同じもの同士の積を取り、足し合わせる」ということを意味します。
いわゆる内積や、1行n列×n行1列の行列の積、などと同じ計算です。
数式を使って書けば、今の場合だと$i,l$ごとに$\sum_{j=k} a_{ij} b_{kl}$を計算する、ということになります。これによって$j,k$は縮んで消えるので、縮約という言葉のイメージに即していますね。そして、$A$の添え字の2つ目と$B$の添え字の1つ目を縮約して消すこの計算は、まさしく行列の積の計算の通りです。
tensor()の場合、この2つ目と1つ目に限らず、例えば2つ目と2つ目とか、添え字に対する次元が一致している限りは自由に指定することができます。また、tensor(A,B,c(1,3),c(2,4))のように、Aの1つ目とBの3つ目、Aの2つ目とBの4つ目、みたいな縮約のペアの指定もできます。
tensorしてから縮約するのと、縮約してからtensorするのと、添え字の順番を除いて同一になることを確認して、本稿を終わります。
> A <- matrix(sample(100,4),nrow = 2, ncol = 2)
> B <- matrix(sample(100,4),nrow = 2, ncol = 2)
> P <- matrix(sample(100,4),nrow = 2, ncol = 2)
> Q <- matrix(sample(100,4),nrow = 2, ncol = 2)
> X <- tensor(
+ tensor(A, B, NULL, NULL),
+ tensor(P, Q, NULL, NULL),
+ c(2,4), c(1,3)
+ )
> Y <- tensor(
+ tensor(A, P, 2, 1),
+ tensor(B, Q, 2, 1),
+ NULL, NULL
+ )
> Z <- aperm(Y,c(1,3,2,4))
> X - Z
, , 1, 1
[,1] [,2]
[1,] 0 0
[2,] 0 0
, , 2, 1
[,1] [,2]
[1,] 0 0
[2,] 0 0
, , 1, 2
[,1] [,2]
[1,] 0 0
[2,] 0 0
, , 2, 2
[,1] [,2]
[1,] 0 0
[2,] 0 0
Xがtensorしてから縮約、Yが縮約(行列の積)してからtensorのパターンです。ZはYの添え字を入れ替えて、X-Zは0ばかりできれいになりました。
ちゃんちゃん。