Pythonと学ぶ線形代数1 ：行列の本当の基礎1


#数学が苦手な人に贈る「Pythonと学ぶ線形代数」
　自分自身、学習プロセスがめちゃくちゃな人間なので大学の講義ではかなり躓いた。正直な話、「教授の話をしっかりと聞く」「予習復習を行う」など、講義に対して前向きな姿勢であれば、留年や再履修をすることはないのだ。しかし、大学に入学した私は大学数学の重要性を甘く見ていた。そもそも、大学数学は人工知能やコンピュータグラフィックスの構成に依存する。全ての情報系学生が学習したほうがいいという訳ではないが、これらの分野を専攻したいのであればこのくらいは応用レベルまで勉強したほうがいい。中学生や、高校生など、これから情報系大学に進学を考えている方ならば、中学高校数学を中心に勉強するのがよい(特に高校数学)。大学で躓くことなく卒業できるはずだ。

#このシリーズの内容
まぁこのシリーズではバリバリPythonを使って計算させるわけだが、今回は基礎数学(線形・解析・統計・離散)の中から線形代数を学習し、その過程をここに記す。また学習した内容のロジックを説明し、Pythonで表現する。Pythonは[Pycharm](https://www.jetbrains.com/ja-jp/pycharm/ "Pycharm")を利用し結果を試す。[Pycharmのインストール方法は別のサイトを参照する](https://gammasoft.jp/python/pycharm-install-on-windows/ "Pycharmのインストール方法")。数値計算を効率化させるために、Numpyというライブラリを使用する。[インストール方法はここを参照する](https://gammasoft.jp/python/install-packages-with-pycharm/ "Pycharmのライブラリインストール方法")。また、このシリーズは**使える数学**を重視したいのでロジックの説明は行うが、細かい証明等は行わない。文章で伝わりにくい説明はほかのサイトを参照することがある。


#このシリーズで用意するもの
- 問題を解くためのノート
1. Pycharm
2. Numpyライブラリ



#1. 行列
　ここでは基本的な行列の性質を紹介するよ。**ここで使われる言葉や性質が応用的問題を解く上で頻出する**ので, つまらないかもしれないが覚えてほしい。
##1.1 行列とは
次のような形のものを一般的に**行列**と呼ぶ。(中の数字は気にしなくていい)

```math
\begin{pmatrix}
1 & 2 \\
3 & 4
\end{pmatrix}
,
\begin{pmatrix}
1 & 2 & 10\\
\sqrt5 & 4 & 20
\end{pmatrix}
,
\begin{pmatrix}
a & b & c\\
d & e & f\\
g & h & i
\end{pmatrix}
,
\begin{pmatrix}
1 & 0 & 0\\
0 & cosθ & -sinθ\\
0 & sinθ & cosθ
\end{pmatrix}
,
\begin{pmatrix}
x_1 & 1 \\
x_2 & 1\\
x_3 & 1\\
\vdots & \vdots\\
x_n & 1\\
\end{pmatrix}
,このように形は様々...
```
##1.2 行と列の名前と成分

```math
\begin{pmatrix}
a_{11} & a_{12} & \cdots & a_{1n}\\
a_{21} & a_{22} & \cdots & a_{2n}\\
\vdots & \vdots & \cdots & \vdots\\
a_{m1} & a_{m2} & \cdots & a_{mn}
\end{pmatrix}
```
数学が苦手な君！戻るボタンを押したらだめだよ。しっかり、この文章を読むのだ。
　$a_{11}$から$a_{1n}$までのように横に並んだ塊を **行** と呼び、$a_{11}$から$a_{m1}$までのように縦に並んだ塊を **列** と呼ぶ。また、$m$は行数を表し、$n$は列数を表す。
このように、行数$m$と列数$n$によって表せる行列を $\boldsymbol{m×n}$**行列** と呼び、行列の要素$(a_{ij})$を $(\boldsymbol{i,j})$**成分** とよぶ。そして、縦と横の長さが同じ場合は $\boldsymbol{n}$**次正方行列** と呼ぶ。ここまでの言葉はよく出てくるので覚えよう。

##1.3 行列の和　
行列同士を足し算する場合は以下のように行う。

```math
A=
\begin{pmatrix}
1 & 2 \\
3 & 4
\end{pmatrix}
,
B=
\begin{pmatrix}
5 & 6 \\
7 & 8
\end{pmatrix}
```
というような行列があるとき、二つの行列の和($A+B, A-B$)は

```math
A+B=
\begin{pmatrix}
1+5 & 2+6 \\
3+7 & 4+8
\end{pmatrix}
=
\begin{pmatrix}
6 & 8 \\
10 & 12
\end{pmatrix}
```

```math
A-B=
\begin{pmatrix}
1-5 & 2-6 \\
3-7 & 4-8
\end{pmatrix}
=
\begin{pmatrix}
-4 & -4 \\
-4 & -4
\end{pmatrix}
```
このようになる。この式は以下のように示せる。

```python:1.3_行列の和
#numpyライブラリを参照
import numpy as np

#A行列とB行列を宣言
A = np.array([[1,2],[3,4]])
B = np.array([[5,6],[7,8]])

#A行列とB行列を表示
print("A行列は\n",A)
print("B行列は\n",B)
#A+B,A-Bの結果を表示
print("A+Bは\n",A+B)
print("A-Bは\n",A-B)
```
このコードの結果は

```1.3_結果
A行列は
 [[1 2]
 [3 4]]
B行列は
 [[5 6]
 [7 8]]
A+Bは
 [[ 6  8]
 [10 12]]
A-Bは
 [[-4 -4]
 [-4 -4]]
```

しかし、**行と列の数が異なる場合はこのような操作を行えない**。例えば、

```math
C=
\begin{pmatrix}
2 & 1 &6\\
3 & 4 &5
\end{pmatrix}
とするとき, A+Cは行と列の数が違うため計算を行えない。
```
この計算を以下のように試してみる。

```python:1.3_異なる行列数の和
#numpyライブラリを参照
import numpy as np

#A行列とC行列を宣言
A = np.array([[1,2],[3,4]])
C = np.array([[2,1,6],[3,4,5]])

#A+B,A-Bの結果を表示
print("A+Cは\n",A+C)
```
$A+C$実行すると次のようなエラーが返ってくる。


```1.3_結果
Traceback (most recent call last):
  File "ファイルの場所", line 9, in <module>
    print("A+Cは\n",A+C)
ValueError: operands could not be broadcast together with shapes (2,2) (2,3) 
```


##1.4 スカラー
行列にある数 $k$**(スカラー)** を掛け合わせると、行列内の要素が $k$ 倍される。

```math
kA=k
\begin{pmatrix}
1 & 2\\
3 & 4
\end{pmatrix}
=
\begin{pmatrix}
1k & 2k\\
3k & 4k
\end{pmatrix}
```

もしこの行列Aを2倍したいときは

```math
2A=2
\begin{pmatrix}
1 & 2\\
3 & 4
\end{pmatrix}
=
\begin{pmatrix}
2 & 4\\
6 & 8
\end{pmatrix}
```

Pythonコード

```Python:1.4_スカラー倍
#numpyライブラリを参照
import numpy as np

#A行列を宣言
A = np.array([[1,2],[3,4]])

#2×Aの結果を表示
print("2×Aは\n",2*A)
```

結果

```1.4_結果
2×Aは
 [[2 4]
 [6 8]]

Process finished with exit code 0
```



##1.5 よく使われる既存の行列記号
- $O$ **行列**(ゼロ行列)：全ての要素がゼロの行列を言う。
- $I$ **行列**(単位行列)：行列要素が $i=j$ の場合に1が入る行列を単位行列と呼ぶ。


```math
O=
\begin{pmatrix}
0 & \cdots &0\\
\vdots & \ddots &\vdots\\
0 & \cdots &0\\
\end{pmatrix}
, I=
\begin{pmatrix}
1 & \cdots & 0\\
\vdots & 1 & \vdots\\
0 & \cdots & 1\\
\end{pmatrix}
```

コード

```python:1.5_ゼロ行列と単位行列
#numpyライブラリを参照
import numpy as np

#零行列を定義して出力する。()の数字は行と列の数
O = np.zeros((2, 2))
print("零行列=\n",O)

#単位行列を定義して出力する。()の数字は正方行列の次数
I = np.eye(2)
print("単位行列=\n",I)
```
結果

```1.5_結果
零行列=
 [[0. 0.]
 [0. 0.]]
単位行列=
 [[1. 0. 0. 0.]
 [0. 1. 0. 0.]
 [0. 0. 1. 0.]
 [0. 0. 0. 1.]]

Process finished with exit code 0
```

##1.6 行列の積
行列の積は少し複雑だが、覚えてしまえば簡単。これも例から先に見てもらう。

```math
A=
\begin{pmatrix}
a & b\\
c & d
\end{pmatrix}
,
B=
\begin{pmatrix}
e & f\\
g & h
\end{pmatrix}
とするとき,
A×B=
\begin{pmatrix}
a & b\\
c & d
\end{pmatrix}
×
\begin{pmatrix}
e & f\\
g & h
\end{pmatrix}
=
\begin{pmatrix}
ae+bg & af+bh\\
ce+dg & cf+dh
\end{pmatrix}
```

これは文で説明するのは難しいので(わかりにくくなってしまうので)、[Youtuberのヨビノリたくみさんの動画を参照する](https://www.youtube.com/watch?v=ltFl0FpLTzQ)。
分からない方は動画を見てから続きを見てほしい。それでは行列に数値を入れて計算してみよう。

```math
A=
\begin{pmatrix}
1 & 2\\
3 & 4
\end{pmatrix}
,
B=
\begin{pmatrix}
3 & 1\\
2 & 5
\end{pmatrix}
とするとき
```

```math
A×B=
\begin{pmatrix}
1 & 2\\
3 & 4
\end{pmatrix}
×
\begin{pmatrix}
3 & 1\\
2 & 5
\end{pmatrix}
=
\begin{pmatrix}
(1×3)+(2×2) & (1×1)+(2×5)\\
(3×3)+(4×2) & (3×1)+(4×5)
\end{pmatrix}
=
\begin{pmatrix}
7 & 11\\
17 & 23
\end{pmatrix}
```
このように解けた。実際にPythonで試す前に**関数dot()**を知る必要がある。

### ■行列の積を行う関数dot()
行列の積を計算するためにA×Bと入力すると同じ要素同士で掛け算をしてしまう。しかしその結果は行列の積ではない。以下のように**dot関数**を用いると正しく行列の積を求めることが出来る。

```python:1.6_行列の積1
import numpy as np

A = np.array([[1,2],[3,4]])
B = np.array([[3,1],[2,5]])

# A×Bの結果
ans = np.dot(A,B)

#A×Bの結果を表示
print("A×Bは\n",ans)
```
結果

```1.6_結果1
A×Bは
 [[ 7 11]
 [17 23]]

Process finished with exit code 0

```
確かに結果は同じになった。今回は2×2行列の計算を行ったが、3×3,4×4の行列も同様の操作を行う。また、行の要素数と列の要素数が異なる場合は積求めることはできない。実際に以下の宣言でどのような結果になるか試してみよう。

```math
A=
\begin{pmatrix}
1 & 2\\
3 & 4
\end{pmatrix}
,
B=
\begin{pmatrix}
1 & 2 & 3\\
3 & 4 & 5
\end{pmatrix}
の時、A×BとB×Aを求めよう
```

```python:1.6_行列の積2
import numpy as np

A = np.array([[1,2],[3,4]])
B = np.array([[1,2,3],[3,4,5]])

print("A×Bは\n",np.dot(A,B))
```

```1.6_結果2
A×Bは
 [[ 7 10 13]
 [15 22 29]]

Process finished with exit code 0
```
次にB×Aを試してみると、

```python:1.6_行列の積3
import numpy as np

A = np.array([[1,2],[3,4]])
B = np.array([[1,2,3],[3,4,5]])

print("A×Bは\n",np.dot(B,A))
```

```1.6_結果3
Traceback (most recent call last):
  File "ファイル名", line 6, in <module>
    print("A×Bは\n",np.dot(B,A))
  File "<__array_function__ internals>", line 5, in dot
ValueError: shapes (2,3) and (2,2) not aligned: 3 (dim 1) != 2 (dim 0)

Process finished with exit code 1
```
確かに、計算ができないというエラーが表示された。

##1.7 入れ替えの関係性
今まで私たちは数式を解くために、順番を入れ替えたり、並べ替えたりしてきた。しかし、線形代数の世界ではそれが**出来ない場合**もある。基本的に行列の積を求めることが多いので、ほとんどの場合入れ替えることはタブーーーーーー。
実際に行列を入れ替えて計算してみよう。以下は1.6と同じ宣言で和と積を計算した。


```python:1.7_行列の入れ替え1
import numpy as np

A = np.array([[1,2],[3,4]])
B = np.array([[3,1],[2,5]])

ans_sum1 = A+B
ans_sum2 = B+A
ans_mul1 = np.dot(A,B)
ans_mul2 = np.dot(B,A)

print("A+Bは\n",ans_sum1)
print("B+Aは\n",ans_sum2)
print("A×Bは\n",ans_mul1)
print("B×Aは\n",ans_mul2)
```
結果

```1.7_入れ替え結果1
A+Bは
 [[4 3]
 [5 9]]
B+Aは
 [[4 3]
 [5 9]]
A×Bは
 [[ 7 11]
 [17 23]]
B×Aは
 [[ 6 10]
 [17 24]]

Process finished with exit code 0
```
行列の和に関しては行列を入れ替えても結果が変わることはない。しかし、行列の積に関しては入れ替えることで結果が変わってしまう可能性があるので気を付けよう。

#### 追記：行列式の入れ替えが成り立つ場合
このように行列を入れ替えることを交換則と呼ぶが、交換則が成り立つ場合もある。

```math
A=
\begin{pmatrix}
1 & -1\\
-1 & 2
\end{pmatrix}
,
B=
\begin{pmatrix}
2 & -3\\
-3 & 5
\end{pmatrix}
の時、A×BとB×Aの結果はどうなるか
```

```python:1.6_行列の入れ替え2
import numpy as np

A = np.array([[1,-1],[-1,2]])
B = np.array([[2,-3],[-3,5]])

ans_mul1 = np.dot(A,B)
ans_mul2 = np.dot(B,A)

print("A×Bは\n",ans_mul1)
print("B×Aは\n",ans_mul2)
```

```1.6_入れ替え結果2
A×Bは
 [[ 5 -8]
 [-8 13]]
B×Aは
 [[ 5 -8]
 [-8 13]]

Process finished with exit code 0
```
確かに入れ替えた結果が同じにった。

#今日は終わり！
お疲れ様！　次回も行列の基礎についてもう少し記述する。