前回の要約 
AIの教えに従い、「numpy.array()で行列を定義する」という主流があることは頭に刻むことにして、実際には使い勝手がよさそうなnumpy.mat()を利用していくことにする。AIがいうには、多次元化が容易なarray()の方が拡張性が高い上、これからの主流だ、というのだが、mat()で使えるA.Iなどといった省略形のコマンドは魅力である。しばらくはmat()も使えるということだから、こちらでコードを書いてみよう。
もちろん、この態度があとで「吠え面をかく」ことになることはわかっている。一番の典型例はRaspberry PI で一斉を風靡した(Python の)RPi-GPIOライブラリ であろう。昨年かそこらに突然消滅し、世界中のラズパイ初心プログラマ が途方に暮れた一件である。mat()もきっとそうなるのであろう。ということで、array()にいつでも切り替えられるよう、準備だけはしておく必要があるのは理解している。
行列はわかったので、次はベクトルだろう、そしてベクトルは(1xn)行列だから簡単だろう、と思っていたのだが、意外に難しくて足が止まったのである。ということで、本日はベクトルの定義についてまずはみてみよう。
ベクトルの定義 
AIに聞いてみたのだが、昨日は一読してもよくわからなかった。まずはAIの回答を引用しよう。
AIによる「一つ目のベクトルの表現」 
まずはAI「オススメ」のnumpy.array()を用いたものである。なんとなくわかったような気もするのだが、「1次元」と「2次元」の意味が今ひとつしっくりこない。
もう一つの表現であるmat()も、実は同じところに問題点がある。
「2つ目のベクトル表現」 
こちらは「必ず二次元」になるという。???
内積 とか計算したいのは山々だが、定義を深く理解しておかないとあとで困るから、ここで少し立ち止まることにする。
1次元と2次元の違いとは? 
どうみても、カッコの数だろう。1次元では[...]と表されているが、2次元では[ [ ] ... [ ] ]と表されている。カッコが二重なのだ。
リストの観点からすると、2次元はリストのリストになっているという意味だ。ということで、ベクトル成分の抜き出しを考えてみる。
もしリストと同じであるならば、上の例におけるv1は
v1[0 ]=1 , v1[1 ]=2 , v1[2 ]=3 
 
という具合に参照できるはずである。一方、v3(行ベクトル)については
v3[0 ][0 ]=1 , v3[0 ][1 ]=2 , v3[0 ][2 ] =3 
 
となっていると思うのだが....どうだろうか?
となれば、v2(列ベクトル)については
v2[0 ][0 ]=1 , v2[1 ][0 ]=2 , v2[2 ][0 ]=3 
 
となっているはずである。
たしかに、線形代数 の添字の使い方を思い出せば、$n$次元ベクトル空間の、$i$番目の基底ベクトルを$\vec{v}^{(i)}$と書いたとき、その$j$成分については行列的に$v_{ij}$と書けた記憶がある。すなわち、ベクトルを横に並べたものを行列とみなす考え方である。
$$ \left(\vec{v}^{(1)} \quad \vec{v}^{(2)} \quad\cdots\quad \vec{v}^{(i)} \quad \cdots \quad \vec{v}^{(n)}\right)
=\begin{pmatrix}v_{11} & v_{21} & \cdots & v_{i1} & \cdots & v_{n1}\\ v_{12} & v_{22} & \cdots & v_{i2} & \cdots & v_{n2}\\ \vdots & \vdots & \cdots & \vdots & \cdots & \vdots \\ v_{1j} & v_{2j} & \cdots & v_{ij} & \cdots & v_{nj}\\ \vdots & \vdots & \cdots & \vdots & \cdots & \vdots \end{pmatrix}$$
後ろの添字$j$が動くならば「列ベクトル(の成分)」、前の添字$i$が動くなら「行ベクトル(の成分)」と習った記憶がある。上に引用した、AIの説明によるpython におけるベクトル表現はこれによく似ている(...が、行と列の添字の位置が反転しているように見える)。
まずはリストで、この構造が利用されているか確認してみる。
v1 = [i for  i in  range (4 )]
v2 = [[j] for  j in  range (4 )]
v3 = [[i for  i in  range (4 )]]
print ("v1" )
for  i in  range (4 ):
    print (v1[i])
print (v1)
print ("v2" )
for  i in  range (4 ):
    print (v2[i][0 ])
print (v2)
print ("v3" )
for  i in  range (4 ):
    print (v3[0 ][i])
print (v3)
▷ 
v1
0 
1 
2 
3 
[0 , 1 , 2 , 3 ]
v2
0 
1 
2 
3 
[[0 ], [1 ], [2 ], [3 ]]
v3
0 
1 
2 
3 
[[0 , 1 , 2 , 3 ]]
 
リストの表式で一括表示されると(まだ慣れていないので)よくわからないが、個々の成分の参照の仕方を見れば、なんとなく理解できる。とはいうものの、通常の線形代数 の表記$v_{ij}$と比べると、python での添字の順番はv[j][i]となっているように見える。つまりiとjの順番が「ひっくり返って」いる ように見える。
これはC言語 Python なのに何故C言語 ?と思うかもしれないが、たいていのPython はC言語 で記述されている、いわゆる「CPython」と呼ばれる言語だからだ(私の使っているPython は確実にCPythonである)。もちろん、Python の配列のメモリ中への格納方法はC言語 より複雑である。しかし、もともとがC言語 なので、Python をデザインした人たちの頭の中にはC言語 の「掟」が叩き込まれているはずなのである。
C言語 で2次元配列(行列に似ている変数)はA[i][j]と書き、Python のリストと同じで形式である。メモリ中で連続しているのはj、つまり2つ目の変数の方である。ちなみに、FORTRAN では2次元配列はA(i,j)と書き、かなり「行列」と近い表記であるが、こちらは、iの方が連続メモリに対応している(つまりC言語 とひっくり返し)。C言語 はポインタによって配列を記述するので、A[i][j]は*A[i]とほぼ同じなのである。だからjの方が連続メモリ領域に対応する。
さて、Python のリストに話を戻そう。リスト形式の一つ[ [1,2,3....] ]という形式は、1,2,3...が連続メモリに対応しているはずである(なぜなら同じカッコに含まれているから)。ということは、[1,2,3...]の中の添字はC言語 風に解釈するなら「後ろの添字」つまりjの方である。一方で、外側のカッコは、一次元のリストを一つだけ含んでいるので、あたかも連続メモリ領域が一つある、と解釈できるはずである。したがって、リスト[ [ 1,2,3....] ]は[i=0][j=1,2,3...]と解釈できるのではないだろうか。実際にこの解釈に従って、各成分を参照できることは上のサンプルプログラムで確認がとれた。
この考え方を拡大すれば、[[1],[2],[3],....,]という表記がどういうことになっているかも理解することができる。外側のカッコが複数の「内カッコ」(とても短いやつ)を抱えている、という表現である。言うなれば、連続メモリ領域が1つ、2つ、3つ...という具合にパッチワークのように貼り付けられているという感じだろう。したがって、[1]と表された短いリストは、成分が一つ(C言語 風にいえば、0番目の要素)で、かつ「一つ目の”ポインタ”」ということになる。となれば、その成分は[1][0]と表せるだろう。次のリスト[2]は[2][0]と解釈できるだろう。つまり[[1],[2],[3]...]というリスト構造は、[i=1,2,3...][j=0]と解釈できるというわけである。
通常の線形代数 の教科書では$v_{ij}$という順番になっているが、Python では、これをv[j][i]と書くのであろう。したがって、jを止めてiを走らせれば「行ベクトル」になり、iを止めてjを走らせれば「列ベクトル」になるのだ。やっとわかった!
しかし、これはあくまで「リスト」を用いた理解であり、実際のベクトルがどういう形で実装されているかは、Python のmat()およびarray()をみてみないとわからない。果たして、リストと同じような方式で、ベクトル成分を参照できるだろうか?
まずは”AIオススメ”のnumpy.array()でベクトル化してみる。上のコードの先頭ににimport numpyを書き足し上で、下のコードを付け足し、実行した。
vp1 = numpy.array(v1)
print (vp1)
vp2 = numpy.array(v2)
print (vp2)
vp3= numpy.array(v3)
print (vp3)
▷
[0  1  2  3 ] ... vp1
[[0 ]    ... vp2
 [1 ]
 [2 ]
 [3 ]]
[[0  1  2  3 ]] ... vp3
 
「おーっ!」ってなもんである。1次元のリストをベクトルにしたもの(vp1)はカッコが一重である。2次元表現(vp2 , vp3)はカッコが二重になっている。なるほど、これが「多次元配列」というやつなんだろう。さらに、2次元表現では行ベクトル(vp2 )と列ベクトル(vp3)がキチンと区別されて表記されている。
もう一つ気がついたのは、ベクトル表記はリスト表記によく似ているものの、リストのように,(comma)で区切られておらず、空白で区切られている。awk 風にいうなら、FS=","がリストであり、FS=" "がベクトルである!
次はmat()でベクトルを作ってみる。
vp1 = numpy.mat(v1)
print (vp1)
vp2 = numpy.mat(v2)
print (vp2)
vp3 = numpy.mat(v3)
print (vp3)
▷
[[0  1  2  3 ]]  ... vp1
[[0 ]   ... vp2
 [1 ]
 [2 ]
 [3 ]]
[[0  1  2  3 ]]  ... vp3
 
array()と同じ結果と、array()とは異なる結果が混じっている。vp2 とvp3は同じだが、vp1がvp3と同じになっている点がarray()と異なる。なるほど、これがAIの言っていた「混ぜると面倒」というやつなのであろう。直感的には、1次元リストで「ベクトル」を作りがちだ(つまり初心はそうやるだろう)。ところが、プログラミングに慣れてきたころ、途中でmat()からarray()に変えたりして、両者が混在するようなコードになると、この辺りの不一致で不具合が生じそうである。
違いはわかったが、1次元表現と2次元表現の長所短所はいったいなんなのであろうか?AIに聞いても、この辺りの微妙な内容についてはハッタリをかまされる可能性もあるから、聞かずにおこうと思う。使っているうちに「あっ、わかった」という瞬間がくるだろうから、それまでノンビリ構えることにする。
ヒルベルト 空間なら内積 が定義されていなくてはならない!ということで、量子力学 を念頭におくなら、Python の線形代数 で内積 をどう表すのか、ここで覚えておく必要があろう。
出力するのはarray()もmat()も以下の演算である。
print (vp3*vp2)    .... (1 )
print (vp3@vp2)  .... (2 )
print (numpy.dot(vp3,vp2))  ....(3 )
print (numpy.multiply(vp3,vp2))   ....(4 )
果たして、内積 を与えるのはどれか?
まずはarray()の結果を見てみる。
[[0 0 0 0]
 [0 1 2 3]
 [0 2 4 6]
 [0 3 6 9]] ....(1)
[[14]] ....(2)
[[14]] ....(3)
[[0 0 0 0]
 [0 1 2 3]
 [0 2 4 6]
 [0 3 6 9]] ....(4) 
内積 になったのは(2)と(3)の演算であった。残りは「アマダール積?」のような、テンソル 積のような演算である。
次にmat()でやってみる。
[[14]] ....(1)
[[14]] ....(2)
[[14]] ....(3)
[[0 0 0 0]
 [0 1 2 3]
 [0 2 4 6]
 [0 3 6 9]] ....(4) 
(1)、(2)、(3)が内積 となった!これは要注意である。
どちらの場合も、内積 となった場合でも、単なる整数ではなく[ [14] ]という2次元表現になっているのが特徴的である。これは[0][0]という意味であるから、「スカラー 」だ。
ああ、ここでやっとわかった。ベクトルの2次元表現は、デカルト テンソル によく似た感じだ(とはいえ、[0][0]がスカラー というのは球テンソル みたいだが)。もしかして、A[i][j][k]みたいな構造を許すのであろうか?
やってみよう。[j][k]までは列ベクトルの複数集合である。それをさらに複数集めれば良い。まずはリストで設計する。
TL1 = [[1 ,2 ,3 ],[4 ,5 ,6 ],[7 ,8 ,9 ]]
TL2 = [[-1 ,2 ,-3 ],[4 ,-5 ,6 ],[-7 ,8 ,-9 ]]
TL3 = [TL1,TL2]
print (TL3)
▷
[[[1 , 2 , 3 ], [4 , 5 , 6 ], [7 , 8 , 9 ]], [[-1 , 2 , -3 ], [4 , -5 , 6 ], [-7 , 8 , -9 ]]]
 
おー、できた!array()でテンソル に変換してみよう。
[[[ 1  2  3]
  [ 4  5  6]
  [ 7  8  9]]
 [[-1  2 -3]
  [ 4 -5  6]
  [-7  8 -9]]] 
ほおー。3x3 の行列による、2次元の「列ベクトル」か?
下の行列の「ど真ん中」にある「-5」を抜き出してみよう。
TT[1][1][1]だろうか?
(まず、「列ベクトル」の下の成分なので[1][..][..]。CやPython は添字は0から始まるので、
「2つ目」は「1つ目」と読み替える。次に、3x3 行列の真ん中なので、(2,2)つまり(1,1)位置の対角要素なので、
[..][..]の部分が[1][1]となる。まとめて、[1][1][1]というわけだ。)
print (TT[1 ][1 ][1 ])
▷ -5 
おー!出たよ。じゃあ、その隣の6は[1][1][2]か?
print (TT[1 ][1 ][2 ])
▷ 6 
I know Kung-Fu.