Pythonで学ぶ基礎線形代数(Part1)

#線形代数をpythonで実装していきます。

勉強したてなのでまだ理解ができていない部分もありますがやっていきたいと思います。

### まず簡単なベクトル・行列の理解

まず先に今回使うライブラリをインポートしていきます。

```python
import numpy as np
import scipy as sp
import sympy as sy
````
#### スカラーとベクトルについて

スカラーとは大きさのみを持ったもののことを指します。例えば普段何気なく使っている5や-10と言った実数がこれに該当します。

次にベクトルですがこちらは先程のスカラーの大きさに加えて向きを追加してものです。よくaやbと言った文字で表されます。

#### ノルムについて
ノルムとはベクトル（例えばa)の大きさのことを指し||a||と表すことができます。

#### 逆ベクトルとゼロベクトル、単位ベクトルについて
ベクトルは実数のようにある変数を用いて元の値をk倍することができます。例えばkという文字を使用して、k＝5と置きます。そして元のベクトルをaと置きます。この元のaベクトルをスカラー(k)倍することができます。これを利用して、k＝−1の時スカラー倍してあげると-aとなる。このベクトルを**aの逆ベクトル**と言います。

またk=0と置き、同様にスカラー倍してあげると0となり、これを**零ベクトル**と呼びます。

またある大きさのベクトルを正規化したのを**単位ベクトル**と呼びます。どういうことかとうと大きさを1に変換したベクトルのことを指します。単位ベクトルは次のように表現することができます。
$$e=\frac{1}{||a||}a$$

例えば大きさ4のベクトルがあったとします。上記の式に当てはめればちゃんと1になります。

では早速pythonでやっていきます。

```python
a = np.array([1, 2, 3])
k = [4, 1.5, -1, 0]
print(a * k[0]) #[4, 8, 12]
print(a * k[1]) #[1.5, 3, 4.5]
print(a * k[2]) #[-1, -2, -3]逆ベクトル
print(a * k[3]) # [0, 0, 0]零ベクトル

```
では次にノルムを実装します。今回はscipyからノルムをインポートしてきていますがnumpyにもあるのでそちらでも実装できます。

```python
from scipy.linalg import norm
a = np.array([3, 4])
n = linalg.norm(a)
print(n)## 5 
```
上記のプログラムで何が起こったかというと次の様なことが起きます。各値を二乗して平方根をとっています。これがノルムです。実際に機械学習などではニューラルネットワークなどのパラメータを調節するの使われます。(例えばL1ノルムやL2ノルムなど)

$$a = \sqrt{3^2 + 4^2} = \sqrt{25} = 5$$

では次に単位行列を実装してみます。先程さらっと確認しましたが、単位ベクトルとは元の向きを保ったベクトルを長さが１のベクトルに正規化するということです。先程の実装したaベクトルを元に実装してみます。

```python
a = np.array([3, 4])
n = norm(a)
b = a / n
print(n) # [0.6, 0.8]
```

### 内積
では次にベクトルの内積をとっていきます。**内積**とは二つのベクトル（a,b）の掛け算のことを指します。
ではa, bそれぞれのベクトルを次の様に定義してみます。ではpythonで実装してみます。numpyにあるdotを使用して求めることができます。

$$a = \array{[1, 2, 3]}$$$$b = \array{[4, 5, 6]}$$

```python
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
c = np.dot(a, b)
print(c)#32
```
このプログラムで何が起きているでしょう。次の様なことが起こっています。
$$c = 1 \times 4 + 2 \times 5 + 3 \times 6=32$$

各成分同士をかけて足しています。これが内積です。
$$a=\array[a_1, b_1, c_1]$$$$b=\array[a_2, b_2, c_3]$$$$c=a_1 \cdot a_2+b_1 \cdot b_2+c_1 \cdot c_2$$

またそれぞれのベクトルで生じた角度を実際に求めてみます。高校の教科書では次のような式で表されていました。では実際に求めてみます。

$$\cos\theta= \frac{a \cdot b}{||a||||b||}$$

```python
def get_cos(a, b):
    inner_product = np.dot(a, b)
    a_norm = norm(a)
    b_norm = norm(b)
    cos_theta = inner_product / (a_norm * b_norm)
    return cos_theta

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
get_cos(a,b)#0.6220084679281461

```
求めることはできましたが。小数でなんだかかわかりにくいですね。ここで登場するのが**SymPy**です。SymPyとはpythonで数式処理を行うライブラリです。そう言われてもピント来ないと思うので、実際に触れてみます。Sympyを使うと下のように綺麗な状態で返してくれます。多少numpyと書き方は異なりますが、大体は一緒なので使ってみてください。

```python
import sympy as np

a = sy.Matrix([[1, 2, 3]])
print(a.shape)
display(a)

#(1, 3)
#numpyだと次のように返ってくる：
array([[1, 2, 3]])
```
![Screen Shot 2020-12-23 at 18.55.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612696/30ae8be6-cf08-3f0c-de22-2f2f2f06db48.png)

少し逸れてしまいましがこちらを使用して実際に角度を求めてみましょう。

```python
def get_angle(a, b):
    a_v = sy.Matrix(a) 
    b_v = sy.Matrix(b)
    norm_a = a_v.norm(ord=2)
    norm_b = b_v.norm(ord=2)
    inner_product = a_v.dot(b_v)
    return inner_product / (norm_a * norm_b)
a = [5, 2, 5]
b = [4 ,1, 1]

```
答えは次のようになります。
$$\frac{\sqrt{3}}{2}$$

これを角度に直すと30°になります。


このコサインを求めることで**正射影と**言うものを求めることができます。
正射影とは次のように二つのベクトルが与えられた時、aに垂直に光が射した際に、bがaに落とす影のことを射します。
正射影は次の式で求めることができます。


$$\frac{a \cdot b}{||a||}=\frac{||a||||b||\cos\theta}{||a||}=||b||\cos\theta$$

### 行列 

次に行列についてみていきます。pythonを触れたことがある方はよく使っていると思います。次のようなのを行列と言います。1,2の組み合わせと3,4と組み合わせで構成されている部分が**行**と呼ばれる部分で一行目、二行目と呼びます。また1,3と2,4で構成される部分を**列**と呼びます。合わせて下記のような行列を２行２列の行列と表現します。そして下記の行列に含まれている数字や文字を成分と呼びます。例えば３と言う数字は２行目１列目に位置しています。その場合を**(2, 1)成分**と言います。他にも例をあげると(2,2)の成分は４、(1,1)の成分を１と捉えることができます。


```math
A = 
\left(
\begin{matrix}
1 & 2\\\
3 & 4
\end{matrix}
\right)
```

先程も少し紹介しましたが、pythonを使用して行列を生成してみます。sympyを使用することで綺麗な行列を生成することができます。（GoogleColaboratoryではsympyが表示されないっぽい？）

```python
a = np.array([[1, 2,
               3, 4]])
print(a.shape)#(2, 2):2行２列の行列という意味
print(a)

a = sy.Matrix([[1, 2,
                3, 4]])
```

### 零行列と単位行列

次に紹介するのが**零行列**と**単位行列**です。ベクトルの部分でもさらっと紹介しました。**零行列とは全ての成分が０の行列**のことを指します。**単位行列とは対角成分が全て１の行列**のことを指します。では早速pythonで実装していきます。

```python
O = np.zeros((2, 2))
print(O.shape)
print(O)


#(2, 2)
[[0. 0.]
 [0. 0.]]

E = np.eye(2, 2)
print(E.shape)
print(E)

#(2, 2)
[[1. 0.]
 [0. 1.]]

O = sy.zeros(2, 2)
print(O.shape)
O


E = sy.eye(2, 2)
print(E.shape)
E
```
```math
O = 
\left(
\begin{matrix}
0 & 0\\\
0 & 0
\end{matrix}
\right)



E = 
\left(
\begin{matrix}
1 & 0\\\
0 & 1
\end{matrix}
\right)

```
### 逆行列

次の紹介するのが**逆行列**です。AB＝BA＝Eを満たす行列をAの**逆行列**と言います。逆行列を次のように表します。$$A^{-1}$$$$AA^{-1}=A^{-1}A=E$$

しかし次のようにはならないです。
$$A^{-1}=\frac{1}{A}$$

A行列が次のように表される時逆行列は次のように表すことができます。
$$A = 
\left(
\begin{matrix}
a & b\\\
c & d
\end{matrix}
\right)$$
$$
A^{-1}= \frac{1}{ad-bc}
\left(
\begin{matrix}
d & -b\\\
-c & a
\end{matrix}
\right)
$$


$$ad-bcを\Deltaや|A|で表すこともできます。$$

もし**ad-bc=0の場合は逆行列は存在しません**。この逆行列を使用することで**連立方程式を簡単に解く**ことができます。（今回は扱いません）

### ケーリー・ハミルトンの定理

次に**ケーリー・ハミルトンの定理**を紹介します。次のように行列が与えられた時、ケーリー・ハミルトンの定理を次のようにに表すことができます。

$$A= \left(
\begin{matrix}
a & b\\\
c & d
\end{matrix}
\right)$$
$$A^2-(a+d)+(ad-bc)E=O$$

### 一次変換

次に扱うのが**一次変換**です。行列の掛け算により点や直線などを移動することができます。では典型的な点
の移動について紹介します。

点の移動には次の４種類のやり方があります。
・ x軸に関して対称移動させる
・ y軸に関して対称移動させる
・ 原点関して対称移動させる
・ y=xに関して対称移動させる


 x軸に関して対称移動させる
$$\left(
\begin{matrix}
1 & 0\\\
0 & -1
\end{matrix}
\right)$$

 y軸に関して対称移動させる
$$\left(
\begin{matrix}
-1 & 0\\\
0 & 1
\end{matrix}
\right)$$

 原点に関して対称移動させる
$$\left(
\begin{matrix}
-1 & 0\\\
0 & -1
\end{matrix}
\right)$$

 y=xに関して対称移動させる
$$\left(
\begin{matrix}
0 & 1\\\
1 & 0
\end{matrix}
\right)$$

上記の行列を元の行列にかけることで点移動が可能になります。今回は以上です。pythonで実装してみます。

```python
def x_axis(x):
    transformed = np.dot(x, np.array([[1, 0], [0, -1]]))
    return transformed
def y_axis(x):
    transformed = np.dot(x, np.array([[-1, 0]], [0, 1]))
    return transformed
def origin(x):
    transformed = np.dot(x, np.array([[-1, 0], [0, -1]]))
    return transformed
def y_x(x):
    transformed = np.dot(x, np.array([[0, 1], [1, 0]]))
    return transformed

x = np.array([2, 3])
x_axis(x)#array([ 2, -3])

a = np.array([2, -3])
y_x(a)#array([-3,  2])
```

できました。
次回も引き続き線形代数の基礎をやっていきます。

### 参考資料
・