はじめに
プログラミング自体は文系、理系、年齢関わらず勉強すればある程度ものになります。プログラミングがある程度できるようになるとTensorflow,PyTorchやscikit-learn等のライブラリで簡単にできる機械学習やデータサイエンスに興味を持つの必然! これからさらになぜ上手くいくのか・いかないのかの議論をしたい、社内・外に発表したい、理論的な所を理解したい、先端研究を取り入れたい、応用したい等々と次々に実現したい事が増えるのもまた必然でしょう。このときに初めて数学的なバックグラウンドの有無という大きな壁が立ちはだかります。しかし、数学は手段であって目的ではないので自習に使える時間をあまり割きたくないですよね。また、そもそも何から手を付けたら良いかわからないって人もいるかと思います。そんな人に向けた記事です。本記事の目標は式の意図する事はわからんが、仕組みはわかるという状態に短期間で持っていく事とします。本来、数学を修めるとは筆を持って数式と数式の行間を埋める、演習問題を解くなどの様々なステップからなる作業に耐えたもの者のみが辿り着ける境地です。筆を持たない分、プログラミングを通して仕組みの習得に全力を尽くしてください。ここで取り扱う内容は皆様が興味を持つ分野の数式に登場する記号の(所感)9割程度はカバーできるはずです。読者が増えるとモチベーションがあがるのでこの記事を面白い・わかりやすいと思ったらお勧めしてくれると私が喜びます。ちょくちょく更新するので興味があればストック等お願いします。
Discordサーバー(質問やリクエストなどどうぞ!!):(https://discord.gg/E8SjUfJarS)
#目次
1. 数学記号
2. ベクトル
3. 行列
4. 微分
5. 積分
6. 集合
#1. 数学記号
##1.1 総和
\sum_{i=1}^{N},\quad\sum_{i=-10}^{10},\quad\sum_{i=1}^{200}
上記のような記号は総和やsumと呼ばれるものです。下付き文字(左から$i=1,i=-10,i=1$)から上付き文字(左から$N,10,200$)まで$i$を変えて$\sum$の右側のものを加算し続けるという意味であり、同じ処理を繰り返すFor文やWhile文で記述できそうなイメージがわくかと思います。上付き文字は省略される事があり、これは上付き文字が明らかなケース、不明なケース、総和のみ明示的に示したいケース、取り扱うのがインデックスの変化でないケースなど著者によって変わるので注意深くその文献を読む必要があります。$\sum_{i=1}^{10}$だけが記述されているときは暗に$1$を$i=1$から$i=10$まで$1$を加算し続ける事を意味しており、数式で書くと下記です。
\sum_{i=1}^{10}=\sum_{i=1}^{10}1=1+1+\dots +1=10
プログラムにすると下記です。
ans = 0
for i in range(1,11):
ans += 1
このような処理ではあまりこの$\sum$記号の旨みが見えてきませんが、加算する対象を$a_{i}$とするとわかりやすいかと思います。
\sum_{i=1}^{N}a_{i}=a_{1}+a_{2}+\dots +a_{N}
ここで、$a$の添え字の$i$はインデックスになります。$a_{1},\dots,a_{N}$の変数にはどんな数字でも入れられるため、$a_{1}=1,a_{2}=2,\dots,a_{N}=N$として書いてみると以下になります。
N = 10
a = [i for i in range(1,N+1)] # a[0]=a_1, a[1]=a_2, .., a[N-1]=a_N
# 総和
# sum(a)と等価な処理ですがfor文で書きます
ans = 0
for i in a:
ans += i
この$\sum$は集合やベクトル、行列などと親和性が高く以下のようなに使われることもあります。
\sum_{r\in R},\quad\sum_{i \in \{1,2,3\}},\quad\sum_{\vec{a}\in V},\quad\sum_{i<10}
ここら辺の話は後ほど扱います。
ここで区切りかと思うかもしれませんがもう少しだけ話を続けます。$\sum$は$1$つだけしか使えないのか?という疑問が湧くかもしれませんが、何個でも使えます。つまり、以下のような表現も可能です。
\sum_{i=1}^{N}\sum_{j=1}^{M}a_{ij},\quad\sum_{i=1}^{N}\sum_{j=1}^{M}\sum_{k=1}^{L}a_{ijk},\quad\dots
$a_{ij},a_{ijk}$に難しい印象を持つかもしれませんが、以下のコードを見てもらえばわかるようにただの多次元配列内の要素のインデックスです。簡単のため、$\sum_{i=1}^{N}\sum_{j=1}^{M}a_{ij}$を書いてみます。
a = [[1,2],[3,4]] # a_{11}=a[0][0],a_{12}=a[0][1],a_{21}=a[1][0],a_{22}=a[1][1]
ans = 0
for i in range(2):
for j in range(2):
ans += a[i][j]
##1.2 総乗
\prod_{i=1}^{N},\quad\prod_{i=-10}^{10},\quad\prod_{i=1},\quad\prod_{1<i<10}
総乗という概念は先ほどの総和の乗算verです。文献によっては総積とも呼ばれます。先ほどと同様に単に$\prod$のみが書かれていた場合は$1$が省略されており、以下を意味します。
\prod_{i=1}^{5}=\prod_{i=1}^{5}1=1\times 1\times 1\times 1\times 1=1
残りの話は総和を乗算にすれば同様なのでいくつかサンプルコードを載せておきます。
\prod_{i=1}^{3}a_{i}=a_{1}a_{2}a_{3}=1\times 2\times 3=6
a = [1,2,3] # a[0]=a_1,a[1]=a_2,a[2]=a_3
ans = 1 # ここを0にしたらどんな配列をもってきても0になってしまうので初期値は1
for i in a:
ans *= i
\begin{eqnarray}
\prod_{i=1}^{2}\prod_{j=1}^{2}a_{ij}&=&\prod_{i=1}^{2}a_{i1}a_{i2}\\
&=&a_{11}a_{12}a_{21}a_{22}
\end{eqnarray}
a = [[1,2],[3,4]]
ans = 1
for i in range(2):
for j in range(2):
ans *= a[i][j]
##1.3. Let's try!
1章の総括として以下の数式を解いてみましょう。
【問題1.1】以下を求めよ。
\sum_{i=1}^{3}\sum_{j=1}^{3}x_{ij}a_{ij}
ただし、
\begin{eqnarray}
\left(
\begin{array}{ccc}
x_{11} & x_{12} & x_{13}\\
x_{21} & x_{22} & x_{23}\\
x_{31} & x_{32} & x_{33}
\end{array}\right)=\left(
\begin{array}{ccc}
0 & 0 & 1\\
1 & 0 & 0\\
0 & 1 & 0
\end{array}\right)\\
\left(
\begin{array}{ccc}
a_{11} & a_{12} & a_{13}\\
a_{21} & a_{22} & a_{23}\\
a_{31} & a_{32} & a_{33}
\end{array}\right)=\left(
\begin{array}{ccc}
0 & 20 & 30\\
20 & 0 & 10\\
30 & 10 & 0
\end{array}\right)\\
\end{eqnarray}
とします。
ヒント
$(\cdot)$の表現は初めてみたと思いますが前述したように多重配列ですので以下のように表されます。
x = [[0,0,1],[1,0,0],[0,1,0]]
a = [[0,20,30],[20,0,10],[30,10,0]]
解答・解説
x = [[0,0,1],[1,0,0],[0,1,0]]
a = [[0,20,30],[20,0,10],[30,10,0]]
ans = 0
for i in range(3):
for j in range(3):
ans += x[i][j]*a[i][j]
この仕組みをみてみると、$x_{ij}\neq 0$出ないところの$a_{ij}$が加算される事に気付きます。この形式は巡回セールスマン問題におけるコスト関数やグラフ理論でよく登場する隣接行列とエッジ情報の積など様々なところでみかける式です。
【問題1.2】
以下を求めよ。
\sum_{i=1}^{2}\prod_{j=1}^{3}a_{ij}
ただし、
\begin{eqnarray}
\left(
\begin{array}{ccc}
a_{11} & a_{12} & a_{13}\\
a_{21} & a_{22} & a_{23}\\
a_{31} & a_{32} & a_{33}
\end{array}\right)=\left(
\begin{array}{ccc}
1 & 2 & 3\\
4 & 5 & 6\\
7 & 8 & 9
\end{array}\right)\\
\end{eqnarray}
とします。
解答・解説
\begin{eqnarray}
\sum_{i=1}^{2}\prod_{j=1}^{3}a_{ij} &=& \sum_{i=1}^{2}a_{i1}a_{i2}a_{i3}\\
&=& a_{11}a_{12}a_{13} + a_{21}a_{22}a_{23}
\end{eqnarray}
または、
\begin{eqnarray}
\sum_{i=1}^{2}\prod_{j=1}^{3}a_{ij} &=& \prod_{j=1}^{3}a_{1j}+\prod_{j=1}^{3}a_{2j}\\
&=& a_{11}a_{12}a_{13} + a_{21}a_{22}a_{23}
\end{eqnarray}
を計算すればよいです。
a = [[1,2,3],[4,5,6],[7,8,9]]
ans = 0
for i in range(2):
b = 1 # 初期値
for j in range(3):
b *= a[i][j]
ans += b
#2. ベクトル
##2.1. ベクトルとは
ベクトルとは順序を持った数字の配列です。これを理解するために何かしらのステータスをリストでまとめるという事を考えてみましょう。Aさんの年齢は25歳、通勤時間は30分、財布の中身1万円、Bさんの年齢は40歳、通勤時間は90分、財布の中身500円としたとき、リストでまとめると以下になります。
a = [25,30,10000] # Aさん
b = [40,90,500] # Bさん
このとき、配列の0番目が年齢、配列の1番目が通勤時間、配列の2番目が財布の中身を表しています。つまり、$a=[30,10000,25]$とミスタイプしてまった場合、30歳、通勤時間10000分、財布の中身が25円を表していることになります。ベクトルとはまさにこのような配列の配置場所それぞれに意味をもった数字の列となります。このベクトルは高校の教科書ではアルファベット小文字の上に矢印を付けた$\vec{a}$と記述されますが、専門書や論文ではボールド体で$\boldsymbol{a}$で記述されます。
\boldsymbol{a}=\left(\begin{array}{c}
25\\
30\\
10000
\end{array}\right), \boldsymbol{b}=\left(\begin{array}{c}
40\\
90\\
500
\end{array}\right)
ベクトルを構成する$25,30,10000,\dots$などの数値はスカラーと呼ばれ、今回の例ではint型で構成されていますが、float型、complex型で構成されていてもベクトルです。注意して欲しいのが、ベクトルを構成する数値のみをスカラーと呼ぶのではなく、構造を持たないint/float/complex型の数値をスカラーと呼びます。なので、500円の500という数字も25歳の25という数字はスカラーです。また、ベクトルを構成する数値が$N$個あればそれは$N$次元ベクトルと呼ばれますが、Pythonでは$N$個の要素を持った1次元配列の事ですので次元という言葉の使い方が異なる点は注意です。
行列の説明の章でもベクトルが登場しますのでもう少し発展させます。上記のように数字を縦に並べて、かっこで囲んだものをベクトルと呼びましたが列ベクトルとも呼ばれます。列があるので当然ながら行ベクトルもあります。上記の例なら以下のように書きます。
\boldsymbol{a}=\left(\begin{array}{ccc}
25 & 30 & 10000
\end{array}\right), \boldsymbol{b}=\left(\begin{array}{ccc}
40 & 90 & 500
\end{array}\right)
上記のプログラムではまさにこの行ベクトル表記で記述していたといえます。Pythonはこの行ベクトルと非常に相性がよいため、列ベクトルではなく行ベクトルで記述されている事に注意です。文献によってどちらを基準にするのかまちまちですので確認しで下さい。簡単なチェック方法は3章でふれます。あえて"基準にする"という言葉を使いましたが、これは1つの式中に列ベクトルと行ベクトルが混在する事があるからです。列ベクトルverの$\boldsymbol{a},\boldsymbol{b}$を基準にしたとき、行ベクトルを記述で書きたいときはそれと区別するために$\boldsymbol{a}^T,\boldsymbol{b}^T$または$\boldsymbol{a}',\boldsymbol{b}'$と書きます。後ほど触れますがこの列から行への変換を転置と呼びます。
##2.2. ベクトルの加法・減法
スカラーの加法・減法はおつりの計算などで日常的にやっているかと思います。そのベクトルverです。前述したように年齢や通勤時間などのスカラーを1つの構造にまとめたものがベクトルなので、その年齢を表す要素同士、通勤時間を表す要素同士が意味を持ちます。これを踏まえるとベクトルの加法・減法は以下のように記述されます。
\boldsymbol{a}+\boldsymbol{b}=\left(\begin{array}{c}
a_{1}+b_{1}\\
a_{2}+b_{2}\\
\vdots \\
a_{N}+b_{N}
\end{array}\right)\\
\boldsymbol{a}-\boldsymbol{b}=\left(\begin{array}{c}
a_{1}-b_{1}\\
a_{2}-b_{2}\\
\vdots \\
a_{N}-b_{N}
\end{array}\right)
ここで重要な事は$N$次元ベクトル同士の加算・減法は必ず$N$次元ベクトルになるという点です。つまり、加算・減法後も順序が保存されているという事になります。
2.1.の$\boldsymbol{a},\boldsymbol{b}$を利用して加算・減法の計算コードにしてみると以下のようになります。
a = [25,30,10000] # Aさん
b = [40,90,500] # Bさん
ans1 = [] # 加算
ans2 = [] # 減法
for i in range(3):
ans1.append(a[i] + b[i])
ans2.append(a[i] - b[i])
Numpyを使うと以下で計算できます。
import numpy as np
a = np.array([25,30,10000]) # Aさん
b = np.array([40,90,500]) # Bさん
ans1 = a+b # 加算
ans2 = a-b # 減法
##2.3. ベクトルとスカラーの積
スカラーの四則演算とは加算・減法・乗算・除算の4つですが、ベクトルはスカラーのときと比べて少し特殊なので順をおって説明します。まずはスカラーとベクトルの乗算・除算です。スカラーを$c$、$N$次元ベクトルを$\boldsymbol{a}$とおけば、
c\boldsymbol{a}=c\left(\begin{array}{c}
a_{1}\\
a_{2}\\
\vdots \\
a_{N}
\end{array}\right)=\left(\begin{array}{c}
ca_{1}\\
ca_{2}\\
\vdots \\
ca_{N}
\end{array}\right)
となります。つまり、スカラーとベクトルの積はベクトルの要素が全てスカラー倍になります。
c = 10 # スカラー
a = [25,30,10000] # Aさん
ans = []
for i in range(3):
ans.append(c*a[i])
これが定義できる事で各要素の平均値等の量を計算する事ができます。
c = 2 # 人数
a = [25,30,10000] # Aさん
b = [40,90,500] # Bさん
ans = [] # 加算
for i,j in zip(a,b):
ans.append(1/c*(i+j))
この結果を眺めると、$ans=[32.5,60,5250]$であり、年齢、通勤時間、財布の中身の平均値が計算されている事がわかります。スカラーとベクトルの積とここでは呼んでいますが、一般にベクトルの定数倍と呼ばれますので注意です。なお、Numpyを使って書くと以下です。
c = 2 # 人数
a = np.array([25,30,10000]) # Aさん
b = np.array([40,90,500]) # Bさん
ans = (1/c)*(a+b)
##2.4. ベクトルとベクトルの積
ベクトルとベクトルの積は色々あるのですが頻出の3つを紹介します。未だに色々な積が考案さていますので他の積に興味がある人は探してみてください。
###2.4.1 アダマール積
アダマール積とは同じ属性の要素同士の積になります。おそらくこれはベクトルを初めて勉強した人が素直に思いつく積ではないでしょうか。$N$次元ベクトル$\boldsymbol{a},\boldsymbol{b}$とおいたとき、アダマール積は以下のように定義されます。
\boldsymbol{a}\circ\boldsymbol{b}=\left(\begin{array}{c}
a_{1}b_{1}\\
a_{2}b_{2}\\
\vdots \\
a_{N}b_{N}
\end{array}\right)
ここで、アダマール積を$\circ$で表現していますが文献によっては異なる記号が使われますので注意が必要です。
a = [25,30,10000]
b = [40,90,500]
ans = []
for i,j in zip(a,b):
ans.append(i*j)
###2.4.2. 内積
これは少し特殊です。今までの計算ではベクトル同士の加法・減法・アダマール積・定数倍のどの演算であっても$N$次元ベクトルは$N$次元のままでした。しかし、内積はベクトルからスカラーに変換されます。
\boldsymbol{a}\cdot\boldsymbol{b}=a_{1}b_{1}+a_{2}b_{2}+\dots +a_{N}b_{N}=\sum_{i=1}^{N}a_{i}b_{i}
この計算は1章でみたものですね。また、内積を$<\boldsymbol{a},\boldsymbol{b}>$と表現する事があります。
a = [25,30,10000]
b = [40,90,500]
ans = 0
for i,j in zip(a,b):
ans += i*j
###2.4.3. 外積
これは厄介で物理では頻出なのですが、外積(クロス積)を定義できない次元というのがあるため私が知る限りでは機械学習等で扱われません。今は知識だけに留めておくと良いでしょう。ここでは$3$次元ベクトル同士の外積を取り扱います。
\boldsymbol{a}\times\boldsymbol{b}=\left(\begin{array}{c}
a_{2}b_{3}-a_{3}b_{2}\\
a_{3}b_{1}-a_{1}b_{3}\\
a_{1}b_{2}-a_{2}b_{1}
\end{array}\right)\neq \boldsymbol{b}\times\boldsymbol{a}
どうしてこうなった?という疑問を当然持たれることだと思いますが、後述する行列式等を理解した方がわかりやすいので割愛します。
a = [25,30,10000]
b = [40,90,500]
ans = [a[2]*b[3]-a[3]*b[2],a[3]*b[1]-a[1]*b[3],a[1]*b[2]-a[2]*b[1]]
##2.5. L1ノルム、L2ノルム
損失関数のペナルティ項としてよく使われるやつです。ベクトルというものは大きさがあり、その大きさの事をノルムといいます。L1ノルムの時はベクトルを$||\cdot|| _ {1}$の$\cdot$のところに入れて書き、L2ノルムの時はベクトルを$||\cdot|| _ {2}$の$\cdot$のところに入れて書きます。
【L1ノルム】
||\boldsymbol{a}||_{1}=|a_{1}|+|a_{2}|+\dots +|a_{N}|=\sum_{i=1}^{N}|a_{i}|
ここで、$|\cdot|$は$\cdot$の絶対値を取る事を意味します。
a = [25,-30,100]
ans = 0
for i in a:
ans += abs(i)
【L2ノルム】
||\boldsymbol{a}||_{2}=\sqrt{a_{1}^2+a_{2}^2+\dots +a_{N}^2}=\sqrt{\sum_{i=1}^{N}a_{i}^2}
a = [25,-30,100]
ans = 0
for i in a:
ans += i**2
ans = (ans)**(0.5)
他にもL$\infty$ノルムなど色々あります。
##2.6. Let's try!
【問題2.1】以下を計算せよ。
\boldsymbol{c}\cdot(\boldsymbol{a}\times\boldsymbol{b})
ただし、
\boldsymbol{a}=\left(\begin{array}{c}
2\\
0\\
0
\end{array}\right),\quad\boldsymbol{b}=\left(\begin{array}{c}
0\\
1\\
0
\end{array}\right),\quad\boldsymbol{c}=\left(\begin{array}{c}
0\\
0\\
1
\end{array}\right).
ヒント
$\boldsymbol{a}\times\boldsymbol{b}=\boldsymbol{d}$とおいて、$\boldsymbol{c}\cdot\boldsymbol{d}$の計算を考えましょう。
解答・解説
式を解くと以下になります。
\begin{eqnarray}
\boldsymbol{c}\cdot(\boldsymbol{a}\times\boldsymbol{b})&=&\boldsymbol{c}\cdot\left(\begin{array}{c}
a_{2}b_{3}-a_{3}b_{2}\\
a_{3}b_{1}-a_{1}b_{3}\\
a_{1}b_{2}-a_{2}b_{1}
\end{array}\right)\\
&=& c_{1}(a_{2}b_{3}-a_{3}b_{2})+c_{2}(a_{3}b_{1}-a_{1}b_{3})+c_{3}(a_{1}b_{2}-a_{2}b_{1})\\
&=& 2
\end{eqnarray}
コードで計算するなら以下になります。
a = [2,0,0]
b = [0,1,0]
c = [0,0,1]
d = [a[2]*b[3]-a[3]*b[2],a[3]*b[1]-a[1]*b[3],a[1]*b[2]-a[2]*b[1]]
ans = 0
for i,j in zip(c,d):
ans += i*j
これはスカラー三重積と呼ばれるもので、3つのベクトルがはる平行六面体の体積を計算します。今回の例では縦2,横1,幅1の直方体の体積の計算をしていたことになります。
#3. 行列
##3.1. 行列とは
ベクトルの同様に配置に意味を持つ2次元配列の事を行列と呼びます。例えば、以下のような表を考えてみましょう。
Aさん | Bさん | |
---|---|---|
年齢 | 25 | 35 |
通勤時間(分) | 30 | 90 |
このような表を作成した時、Aさんの年齢は25歳、通勤時間は30分、Bさんの年齢は35歳、通勤時間は90分である事は経験上わかります。行列とはこの構造をそのまま保持したものであり、この表を$T$とおけば以下のように記述されます。
T = \left(\begin{array}{cc}
25 & 35\\
30 & 90
\end{array}\right)
また、Aさんのステータスを表すベクトルを$\boldsymbol{a}$、Bさんのステータスを表すベクトルを$\boldsymbol{b}$とおいたとき以下のように記述できる事は2章で取り扱いました。
\boldsymbol{a}=\left(\begin{array}{c}
25\\
30
\end{array}\right), \boldsymbol{b}=\left(\begin{array}{c}
35\\
90
\end{array}\right)
これと行列$T$を見比べてると、
T = \left(\begin{array}{cc}
25 & 35\\
30 & 90
\end{array}\right) = (\boldsymbol{a}\quad\boldsymbol{b})
と書けることに気付けるかと思います。ここで気付いてほしいのが、ベクトルは複数のスカラーから構成されており、行列は複数のベクトルから構成されているという点です。当然ながら、行列はベクトルから構成されているのでスカラーから構成されているとも言えます。しかし、その逆はないのです。この関係に気付けると一般に理解の難しいテンソルは複数の行列から構成されているものである事が推測でき、親近感がわくかと思います。この行列をさらに他の観点から眺めてみましょう。年齢、通期時間を表す行ベクトル$\boldsymbol{c},\boldsymbol{d}$とおくと、
\boldsymbol{c}=\left(\begin{array}{c}
25 \\
35
\end{array}\right), \boldsymbol{d}=\left(\begin{array}{c}
30\\
90
\end{array}\right)
と記述できます。このとき行列$T$は
T = \left(\begin{array}{cc}
25 & 35\\
30 & 90
\end{array}\right) = \left(\begin{array}{c}
\boldsymbol{c}'\\
\boldsymbol{d}'
\end{array}\right)
と書く事も出来る訳ですね。このように考えると行列は2通りの書き方がある事に気付けるかと思います。
T = [[25,35],
[30,90]]
c = [25,35] # 年齢
d = [30,90] # 通勤時間
T = [c,d]
この例では、列数が2、行数が2であるため、$2\times 2$行列と呼ばれます。このように行列を書いてみると、$N$次元の列ベクトル(縦長のベクトル)は(あくまでも配列の形に注目したらという意味で)$N\times 1$行列といえます。$N$次元の行ベクトル(横長のベクトル)は同様に考えると$1\times N$行列と言えます。行列はなれないと少し難しいかもしれませんが、表を$(\cdot)$で数値だけまとめたものが行列という認識で大丈夫です。
この節はもう少しだけ続きます。表$T$を以下のように書き直してみましょう。
年齢 | 通勤時間(分) | |
---|---|---|
Aさん | 25 | 30 |
Bさん | 35 | 90 |
これはエクセルでも頻出の操作の1つですよね。このように行と列を入れ替える操作と転置と呼びます。もとの表$T$に対して、転置を施す事を$T^T$または$T'$と書き、
T' = \left(\begin{array}{cc}
25 & 30\\
35 & 90
\end{array}\right)
と表します。この変更によって、A,Bさんのステータスを表すベクトルが縦から横に変更されている事に気付けるかと思います。つまり、転置によってベクトルが列から行に変更する転置ととったと言い換える事が出来、以下のように記述される事を意味します。
T' = \left(\begin{array}{cc}
25 & 30\\
35 & 90
\end{array}\right) = \left(\begin{array}{c}
\boldsymbol{a}'\\
\boldsymbol{b}'
\end{array}\right)
転置のイメージは湧いたと思います。もう少し詳しく書いてみます。$M\times N$行列を$A$とし、以下のように記述されるとします。
A = \left(\begin{array}{cccc}
a_{11} & a_{12} & \cdots & a_{1N}\\
a_{21} & a_{22} & \cdots & a_{2N}\\
\vdots & \vdots & \ddots & \vdots\\
a_{M1} & a_{M2} & \cdots & a_{MN}
\end{array}\right)
この転置をとるというのは、以下のように書かれる事です。要素のインデックスに注目して下さい。
A' = \left(\begin{array}{cccc}
a_{11} & a_{21} & \cdots & a_{M1}\\
a_{12} & a_{22} & \cdots & a_{M2}\\
\vdots & \vdots & \ddots & \vdots\\
a_{1N} & a_{2N} & \cdots & a_{MN}
\end{array}\right)
つまり、$A'$の$i$行$j$列目の数字と$A$の$j$行$i$列目が等しい事が見てとれるかと思います。例えば、$A$の$2$行$1$列目の数字と$A'$の$1$行$2$列目の数字が同じ$a_{21}$です。これでもわからりずらいと思う人もいるかと思うのでベクトルで以下のベクトルを用意してみましょう。
\boldsymbol{a}_{i}=\left(\begin{array}{c}
a_{1i}\\
a_{2i}\\
\vdots \\
a_{Mi}
\end{array}\right)
このように書いてみると行列$A$は
A = \left(\begin{array}{cccc}
a_{11} & a_{12} & \cdots & a_{1N}\\
a_{21} & a_{22} & \cdots & a_{2N}\\
\vdots & \vdots & \ddots & \vdots\\
a_{M1} & a_{M2} & \cdots & a_{MN}
\end{array}\right) = (\boldsymbol{a}_{1}\quad\boldsymbol{a}_{2}\quad\dots\quad\boldsymbol{a}_{N})
と書けることに気付きます。この転置をとるというのはこの行列を構成するベクトルを転置する事と同義ですので、
A' = \left(\begin{array}{c}
\boldsymbol{a}_{1}'\\
\boldsymbol{a}_{2}'\\
\vdots \\
\boldsymbol{a}_{N}'
\end{array}\right)= \left(\begin{array}{cccc}
a_{11} & a_{21} & \cdots & a_{M1}\\
a_{12} & a_{22} & \cdots & a_{M2}\\
\vdots & \vdots & \ddots & \vdots\\
a_{1N} & a_{2N} & \cdots & a_{MN}
\end{array}\right)
と考えても良いわけです。このようにするとわかりやすいかと思いますが、$M\times N$行列の転置をとると$N\times M$行列になります。さっそく、この転置をプログラムで確認してみましょう。変換前の行列の行数と列数を調査後、転置前の$i$行$j$列目の数字が転置後の$j$行$i$列目の数字に対応する事を利用します。
A = [[11,12],[21,22],[31,33]] # みやすいように要素はインデックスにしてます
m = len(A) # 行数
n = len(A[0]) # 列数
At = []
for i in range(n):
vec = []
for j in range(m):
vec.append(A[j][i])
At.append(vec)
Numpyを使うと一発で変換してくれます。
np.array(A).T
##3.2. 行列の加算・減法
前述したように行列は複数のベクトルから構成されているといえます。つまり、ベクトルで学んだルールと整合性がとれるようルールで演算できる必要があります。これを踏まえると$N\times M$行列$A,B$の間の加算・減法は以下のようになります。
\begin{eqnarray}
A+B &=& \left(\begin{array}{cccc}
a_{11} & a_{12} & \cdots & a_{1M}\\
a_{21} & a_{22} & \cdots & a_{2M}\\
\vdots & \cdots & \ddots & \vdots\\
a_{N1} & a_{N2} & \cdots & a_{NM}
\end{array}\right) + \left(\begin{array}{cccc}
b_{11} & b_{12} & \cdots & b_{1M}\\
b_{21} & b_{22} & \cdots & b_{2M}\\
\vdots & \cdots & \ddots & \vdots\\
b_{N1} & b_{N2} & \cdots & b_{NM}
\end{array}\right)\\
&=& \left(\begin{array}{cccc}
a_{11}+b_{11} & a_{12}+b_{12} & \cdots & a_{1M}+b_{1M}\\
a_{21}+b_{21} & a_{22}+b_{22} & \cdots & a_{2M}+b_{2M}\\
\vdots & \cdots & \ddots & \vdots\\
a_{N1}+b_{N1} & a_{N2}+b_{N2} & \cdots & a_{NM}+b_{NM}
\end{array}\right)\\
A-B &=& \left(\begin{array}{cccc}
a_{11}-b_{11} & a_{12}-b_{12} & \cdots & a_{1M}-b_{1M}\\
a_{21}-b_{21} & a_{22}-b_{22} & \cdots & a_{2M}-b_{2M}\\
\vdots & \cdots & \ddots & \vdots\\
a_{N1}-b_{N1} & a_{N2}-b_{N2} & \cdots & a_{NM}-b_{NM}
\end{array}\right)
\end{eqnarray}
行列$A,B$が以下のように与えられているとき、加法・減法を計算してみましょう。
A = \left(\begin{array}{cc}
25 & 30\\
30 & 90
\end{array}\right),\quad B = \left(\begin{array}{cc}
40 & 10\\
20 & 15
\end{array}\right)
一旦ベクトルを作成してから、再構成する感じで作成すると以下です。
A = [[25,30],
[30,90]]
B = [[40,10],
[20,15]]
# 加法
ans1 = []
for i in range(2):
vec = []
for j in range(2):
vec.append(A[i][j] + B[i][j])
ans1.append(vec)
# 減法
ans2 = []
for i in range(2):
vec = []
for j in range(2):
vec.append(A[i][j] - B[i][j])
ans2.append(vec)
##3.3. スカラーと行列の積
これまた行列はベクトルで構成されていると言えるのでベクトルと同じ性質を持つ必要があります。つまり、スカラーと行列の積は行列の全ての要素にスカラーを乗じる事を意味します。これより、スカラー$c$と$M\times N$行列$A$の積は以下のように書けます。
\begin{eqnarray}
cA &=& c\left(\begin{array}{cccc}
a_{11} & a_{12} & \cdots & a_{1M}\\
a_{21} & a_{22} & \cdots & a_{2M}\\
\vdots & \vdots & \ddots & \vdots\\
a_{N1} & a_{N2} & \cdots & a_{NM}
\end{array}\right)\\
&=& \left(\begin{array}{cccc}
ca_{11} & ca_{12} & \cdots & ca_{1M}\\
ca_{21} & ca_{22} & \cdots & ca_{2M}\\
\vdots & \vdots & \ddots & \vdots\\
ca_{N1} & ca_{N2} & \cdots & ca_{NM}
\end{array}\right)
\end{eqnarray}
コードは以下です。
c = 2
A = [[25,30],
[30,90]]
# 加法
ans = []
for i in range(2):
vec = []
for j in range(2):
vec.append(c*A[i][j])
ans.append(vec)
##3.4. ベクトルと行列の積
今まで特に言及せず、暗にリスト型を使って示していましたがベクトルの加法・減法・内積・外積・アダマール積、行列の加法・減法は同じサイズ(行数と列数の事をまとめてサイズと呼んでいます)間でしか演算できません。積に関しては少し異なっていて、同じサイズ間のみという制限はありませんが積を定義できる条件があります。それは行列$A$のサイズを$M\times N$、行ベクトル$\boldsymbol{x}$のサイズを$O\times 1$とおいたとき、$N=O$のときのみ積を考える事ができます。この対応を理解するために以下の行ベクトルを定義します。前述したように列ベクトルを基本としているため、転置$'$の記号がベクトルについてるので注意です。
\boldsymbol{b}_{i}'=\left(\begin{array}{cccc}
a_{i1} & a_{i2} & \cdots & a_{iN}
\end{array}\right)
このベクトルは$1\times N$である事を頭の隅に置いておいてください。このように置いたとき、行列$A$は以下のような置き換えができます。
\begin{eqnarray}
A &=& \left(\begin{array}{cccc}
a_{11} & a_{12} & \cdots & a_{1N}\\
a_{21} & a_{22} & \cdots & a_{2N}\\
\vdots & \vdots & \ddots & \vdots\\
a_{M1} & a_{M2} & \cdots & a_{MN}
\end{array}\right)\\
&=& \left(\begin{array}{c}
\boldsymbol{b}_{1}'\\
\boldsymbol{b}_{2}'\\
\vdots \\
\boldsymbol{b}_{M}'\\
\end{array}\right)
\end{eqnarray}
これを利用すると、
\begin{eqnarray}
A\boldsymbol{x} &=& \left(\begin{array}{cccc}
a_{11} & a_{12} & \cdots & a_{1N}\\
a_{21} & a_{22} & \cdots & a_{2N}\\
\vdots & \vdots & \ddots & \vdots\\
a_{M1} & a_{M2} & \cdots & a_{MN}
\end{array}\right)\left(\begin{array}{c}
x_{1}\\
x_{2}\\
\vdots \\
x_{N}
\end{array}\right)\\
&=& \left(\begin{array}{c}
\boldsymbol{b}_{1}'\\
\boldsymbol{b}_{2}'\\
\vdots \\
\boldsymbol{b}_{M}'\\
\end{array}\right)\left(\begin{array}{c}
x_{1}\\
x_{2}\\
\vdots \\
x_{N}
\end{array}\right)\\
&=& \left(\begin{array}{c}
\boldsymbol{b}_{1}\cdot\boldsymbol{x}\\
\boldsymbol{b}_{2}\cdot\boldsymbol{x}\\
\vdots \\
\boldsymbol{b}_{M}\cdot\boldsymbol{x}\\
\end{array}\right)\\
&=& \left(\begin{array}{c}
\sum_{i=1}^{N}a_{1i}x_{i}\\
\sum_{i=1}^{N}a_{2i}x_{i}\\
\vdots \\
\sum_{i=1}^{N}a_{Mi}x_{i}\\
\end{array}\right)
\end{eqnarray}
というのが行列とベクトルの積になります。$\boldsymbol{b}_{i}$と$\boldsymbol{x}$はどちらも$N$次元ベクトルであるため、内積が定義できます。この内積が絡んでくるがゆえに$N=O$という制限がかかるという事ですね。じゃあ、$N\neq O$のときどうなるのか?って話になりますがこのときは$A\boldsymbol{x}$の計算はできません。また、注目したいのが$M\times N$行列と$N\times 1$のベクトルの積は内積がスカラーになる事に留意すると$M\times 1$になっている点です。この計算によって得られるベクトルまたは行列のサイズにはルールがあります。積の順番に行列およびベクトルのサイズを掛け算のように書いてみると$M\times N\times N\times1$であり、この内側の$N\times N$が内積計算によってつぶれるので左端と右端の数字だけが残って$M\times 1$となると考えれば良いです。同じような要領で考えると$5\times 3$行列と$3\times 1$ベクトルの積は$5\times 1$になります。このテクニックに加え、加算・減算等が同サイズでしか行えないというルールを理解しておけば、サイズが書いてない事が多々ある重み行列やバイアスのサイズを自力で導くこともできます。本記事を読む間だけでもこのチェックをして自身をアップデートさせていきましょう。
本記事では大は小をかねるというようなスタイルで話を発展させている訳ですが、上記の話題があって初めてベクトルの内積のサイズどうなってるのか?という疑問を抱けるかと思います。$N\times 1$ベクトルと$N\times 1$なら$N=1$じゃなきゃ整合とれなくないか?という指摘ができていれば理解度が高いといえます。実は$1\times N$と$N\times 1$から$1\times 1$、すなわちスカラーを算出するというのが内積なのです。つまり、$N\times 1$ベクトル$\boldsymbol{a},\boldsymbol{b}$の内積を考えるという事は暗に片方のベクトルを転置させていたという事になります。数式で書くと以下の関係がなりたっていたという事ですね。
\boldsymbol{a}\cdot \boldsymbol{b} = \boldsymbol{a}'\boldsymbol{b}
ここでは列ベクトルを基準としていますが、行ベクトルを基準としている場合、つまり$\boldsymbol{a},\boldsymbol{b}$が$1\times N$行列であるときは
\boldsymbol{a}\cdot \boldsymbol{b} = \boldsymbol{a}\boldsymbol{b}'
と転置がつく方が異なりますので注意が必要です。$A\boldsymbol{x}$を考えてきたわけですが、$M=O$であれば$\boldsymbol{x}'A$も計算可能な事がわかりますね。最後にコードを確認して理解を深めましょう。行列やベクトルのサイズを色々変えてみて理解を深めてください。
# ●行列
A = [[1,2,3],
[4,5,6]]
m = len(A) # 行数
n = len(A[0]) # 列数
# ●列ベクトル
# x = [7,8,9]を列ベクトルだと思い込んでもらえば
# 良いのですが混乱させそうなので下記で列ベクトルを表現
x = [[7],
[8],
[9]]
o = len(x) # 行数
p = len(x[0]) # 列数
# ●計算
ans = []
# Axを考える上でn=oである必要がるのでチェック
if n==o:
for i in range(m):
inPro = 0 # 内積
for j in range(n):
inPro += A[i][j]*x[j][0]
ans.append([inPro])
print(ans)
else:
print("Check the size")
【トレーニング】
ディープラーニングおなじみの$\boldsymbol{x}'W+\boldsymbol{b}$はベクトルと行列の積を使っています。このときの$\boldsymbol{x}$は行ベクトルを基準にした表記でしょうか、列ベクトルを基準にしたものでしょうか。
解答
列ベクトルをベースに考えてみます。$W$のサイズを$M\times N$、$\boldsymbol{x}$のサイズを$O\times 1$、$\boldsymbol{b}$のサイズを$L\times 1$とおきます。後でサイズはそろえれば良いので今は適当に決めています。このとき、$\boldsymbol{x}'W$は$1\times O\times M\times N$ですので$O=M$とすれば、$\boldsymbol{x}'W$は$1\times N$になります。ベクトルの加算・減法は同サイズ間でしか行えない事を思い出すと$1\times N$と$L\times 1$の加算が許容となるのは$N=L=1$のときになります。つまり、$\boldsymbol{x}$は$M\times 1$、$W$は$M\times 1$、$\boldsymbol{b}$はスカラーである必要がありますね。つまり、ベクトルして考えていた$\boldsymbol{b}$はスカラーじゃないと成り立たないよという意味になります。意図した処理であれば問題ありませんが、大抵出力をベクトルで取りたい訳ですから、行ベクトルをベースにしていると考えるのが妥当でしょう。
##3.5. 行列と行列の積
###3.5.1. アダマール積
2章でも登場した積ですね。アダマール積とは要素同士の積になりますので$N\times M$行列$A,B$のアダマール積は以下になります。
A\circ B = \left(\begin{array}{cccc}
a_{11}b_{11} & a_{12}b_{12} & \cdots & a_{1M}b_{1M}\\
a_{21}b_{21} & a_{22}b_{22} & \cdots & a_{2M}b_{2M}\\
\vdots & \vdots & \ddots & \vdots\\
a_{N1}b_{N1} & a_{N2}b_{N2} & \cdots & a_{NM}b_{NM}
\end{array}\right)
コードは下記です。
A = [[1,2,3],
[4,5,6]]
B = [[7,8,9],
[10,11,12]]
m = len(A) # 行数
n = len(A[0]) # 列数
ans = []
for i in range(m): #行
vec = []
for j in range(n): #列
vec.append(A[i][j]*B[i][j])
ans.append(vec)
###3.5.2. 行列積
$M\times N$行列$A$と$R\times L$行列$B$の行列積を考えます。この行列積というのは
3.4. ベクトルと行列の積で学んだものを素直に発展させたものと言え、$N=R$のときのみ$A$と$B$の行列積$AB$を考える事ができ、$L=M$のときのみ$BA$を考える事ができます。ここでは$N=R$を考え、行列積$AB$を取り扱います。そのために、以下のベクトルを定義します。
\begin{eqnarray}
\boldsymbol{a}_{i}'=\left(\begin{array}{cccc}
a_{i1} & a_{i2} & \cdots & a_{iN}
\end{array}\right)\\
\boldsymbol{b}_{j} = \left(\begin{array}{c}
b_{1j}\\
b_{2j}\\
\vdots \\
b_{Nj}
\end{array}\right)
\end{eqnarray}
このように書いたとき、$A,B$は以下のように書き換えられます。
\begin{eqnarray}
A &=& \left(\begin{array}{cccc}
a_{11} & a_{12} & \cdots & a_{1N}\\
a_{21} & a_{22} & \cdots & a_{2N}\\
\vdots & \vdots & \ddots & \vdots\\
a_{M1} & a_{M2} & \cdots & a_{MN}
\end{array}\right)\\
&=& \left(\begin{array}{c}
\boldsymbol{a}_{1}'\\
\boldsymbol{a}_{2}'\\
\vdots \\
\boldsymbol{a}_{M}'\\
\end{array}\right)\\
B &=& \left(\begin{array}{cccc}
b_{11} & b_{12} & \cdots & b_{1L}\\
b_{21} & b_{22} & \cdots & b_{2L}\\
\vdots & \vdots & \ddots & \vdots\\
b_{N1} & b_{N2} & \cdots & b_{NL}
\end{array}\right) = \left(\begin{array}{c}
\boldsymbol{b}_{1} & \boldsymbol{b}_{2} & \cdots & \boldsymbol{b}_{L}
\end{array}\right)
\end{eqnarray}
これらを利用すると$AB$は以下から求まります。
\begin{eqnarray}
AB &=& \left(\begin{array}{c}
\boldsymbol{a}_{1}'\\
\boldsymbol{a}_{2}'\\
\vdots \\
\boldsymbol{a}_{M}'
\end{array}\right)\left(\begin{array}{c}
\boldsymbol{b}_{1} & \boldsymbol{b}_{2} & \cdots & \boldsymbol{b}_{L}
\end{array}\right)\\
&=& \left(\begin{array}{cccc}
\boldsymbol{a}_{1}'\boldsymbol{b}_{1} & \boldsymbol{a}_{1}'\boldsymbol{b}_{2} & \cdots & \boldsymbol{a}_{1}'\boldsymbol{b}_{L}\\
\boldsymbol{a}_{2}'\boldsymbol{b}_{1} & \boldsymbol{a}_{2}'\boldsymbol{b}_{2} & \cdots & \boldsymbol{a}_{2}'\boldsymbol{b}_{L}\\
\vdots & \vdots & \ddots & \vdots\\
\boldsymbol{a}_{M}'\boldsymbol{b}_{1} & \boldsymbol{a}_{M}'\boldsymbol{b}_{2} & \cdots & \boldsymbol{a}_{M}'\boldsymbol{b}_{L}
\end{array}\right)
\end{eqnarray}
となります。ここで、$AB$の$i$行$j$列目の成分は
\boldsymbol{a}_{i}'\boldsymbol{b}_{j} = \sum_{k=1}^{N} a_{ik}b_{kj}
です。ではさっそくコード中身を確認していきましょう。
# 最終的にはn×lになり、また内積はn次元ベクトル同士になる事に留意
A = [[1,2,3],
[4,5,6]]
B = [[7,8],
[9,10],
[11,12]]
M = len(A) # Aの行数
N = len(A[0]) # Aの列数
L = len(B[0]) # Bの列数
ans = []
for i in range(M):
vec = []
for j in range(L):
inPro = 0
for k in range(N): # 内積計算
inPro += A[i][k]*B[k][j]
vec.append(inPro)
ans.append(vec)
Numpy verは下記です。
np.dot(A,B)
###3.5.3. クロネッカー積
だいぶ高級な数理統計の専門書、画像処理、画像認識関連のディープラーニングとかでちょくちょく顔をみせるのがこのクロネッカー積です。この積は行列数を考えずに実行できます。数式を確認します。$M\times N$行列$A$と$R\times L$行列$B$のクロネッカー積は以下です。
\begin{eqnarray}
A\otimes B &=& \left(\begin{array}{cccc}
a_{11} & a_{12} & \cdots & a_{1N}\\
a_{21} & a_{22} & \cdots & a_{2N}\\
\vdots & \vdots & \ddots & \vdots\\
a_{M1} & a_{M2} & \cdots & a_{MN}
\end{array}\right)\otimes\left(\begin{array}{cccc}
b_{11} & b_{12} & \cdots & b_{1L}\\
b_{21} & b_{22} & \cdots & b_{2L}\\
\vdots & \vdots & \ddots & \vdots\\
b_{R1} & b_{R2} & \cdots & b_{RL}
\end{array}\right)\\
&=& \left(\begin{array}{cccc}
a_{11}B & a_{12}B & \cdots & a_{1N}B\\
a_{21}B & a_{22}B & \cdots & a_{2N}B\\
\vdots & \vdots & \ddots & \vdots\\
a_{M1}B & a_{M2}B & \cdots & a_{MN}B
\end{array}\right)\\
&=& \left(\begin{array}{ccccc}
a_{11}b_{11} & a_{11}b_{12} & \cdots & a_{11}b_{1L} & \cdots\\
a_{11}b_{21} & a_{11}b_{22} & \cdots & a_{11}b_{2L} & \cdots\\
\vdots & \vdots & \ddots & \vdots & \vdots\\
a_{11}b_{R1} & a_{11}b_{R2} & \cdots & a_{11}b_{RL} & \cdots\\
\vdots & \vdots & \vdots & \vdots & \vdots\\
\end{array}\right)
\end{eqnarray}
この積は$\otimes$の左側の要素すべて右側の行列をかけるという処理になりますので、この積によって求まる行列は$MR \times NL$になります。この定義を利用するとスカラー$c$と行列$A$のクロネッカー積はある積と等価であることがわかります。少し考えてみてください。
答え
c\otimes A = cA
A = np.array([[1,2,3],
[4,5,6]])
B = np.array([[7,8],
[9,10],
[11,12]])
np.kron(A,B)
##3.6. 行列式
\det{A}, |A|
上記のように書いたとき、行列$A$の行列式を計算する事を意味します。こいつが何者なのかについて小難しい話が必要なので割愛します。この行列式は行列$A$が正方行列のときのみ計算できます。正方行列とは縦と横のサイズが同じ$N\times N$行列の事です。式で書くと
\det{A} = \sum_{\sigma\in S_N}(sgn\sigma)\prod_{i=1}^{N}a_{i,\sigma (i)}
です。ここで、$S(N)$は$N$次置換群(対称群)、$sgn$は置換群$S_{N}$に対する符号を与える関数(符号関数)です。これをみた皆様の気持ちはわかります、今まで取り扱ってきたものに比べてだいぶ難しそうな式ですよね。これをプログラムで組んでみるとそこまで難しいものではないので1つ1つ理解していきましょう。2次元、3次元だとたすき掛けやサラスの公式という方法で簡単に計算できるのため、大学受験で扱われていたこともあるそうなのですが$4$次元以上だと導入なしで取り扱われる事はまずありません。$4$次以上に対しては余因子展開と呼ばれるテクニックをつかって、$3$次におとしてサラスの公式というのが鉄板ですが余因子展開に付随する概念をある程度理解する必要があるため、上記の公式1つで理解させます(単純に行列を何度も書くのが面倒というものあります。この公式だと簡単に求まるので採用しています)。
###3.6.1. N次置換群
数学で対象となるものは想像以上に多いものです。数学で登場する置換とはものを並び替える操作の事を指します。たとえば以下の操作も置換になります。
a = ['a','b','c']
a[1],a[0]=a[0],a[1]
上の例だとリストaのインデックス番号0と1の数字を入れ替えています。これを数式で記述するなら下記になります。
\left(\begin{array}{ccc}
a & b & c\\
b & a & c
\end{array}\right)
まずこれは行列と同じ形をしているのですが行列ではありません。この記法では上段$abc$、下段$bac$で示す事が異なっており、上段を下段に並び替えるという事を表しています。
a = [1,2,3]
a[1],a[0]=a[0],a[1]
のときは、
\left(\begin{array}{ccc}
1 & 2 & 3\\
2 & 1 & 3
\end{array}\right)
と記述されます。数字でもアルファベットでもひらがなでもこの記法が使えます。この置換という操作を使って考えたいのが、$N$個の数値列・文字列の並び替えは何通りあるでしょうか。順列を思い出してみると$N!$通りですよね。この全通りの並び替えを要素に持つのが$N$次置換群です。具体例を確認しましょう。3次置換群の時は
\begin{eqnarray}
S_3 &=& \left\{
\left(\begin{array}{ccc}
1 & 2 & 3\\
1 & 2 & 3
\end{array}\right),
\left(\begin{array}{ccc}
1 & 2 & 3\\
2 & 1 & 3
\end{array}\right),
\left(\begin{array}{ccc}
1 & 2 & 3\\
1 & 3 & 2
\end{array}\right),
\left(\begin{array}{ccc}
1 & 2 & 3\\
2 & 3 & 1
\end{array}\right),
\left(\begin{array}{ccc}
1 & 2 & 3\\
2 & 3 & 1
\end{array}\right),
\left(\begin{array}{ccc}
1 & 2 & 3\\
3 & 1 & 2
\end{array}\right),
\left(\begin{array}{ccc}
1 & 2 & 3\\
3 & 2 & 1
\end{array}\right)
\right\}\\
&=& \{\sigma_1,\sigma_2,\sigma_3,\sigma_4,\sigma_5,\sigma_6\}
\end{eqnarray}
この集合の要素$\sigma$を使って、$\sigma (1)$と記述すれば、上段のインデックス$1$の下の数字を意味します。例えば、$\sigma_2$のとき、$\sigma_2(1)=2,\sigma_2(2)=1,\sigma_2(3)=3$です。これで行列中の$a_{i,\sigma (i)}$が何を示すのか分かったのではないでしょうか。$N=3$のときで確認してみましょう。
$\prod_{i=1}^{3}a_{i,\sigma(i)}$において$\sigma_{2}$のとき、
\begin{eqnarray}
\prod_{i=1}^{3}a_{i,\sigma(i)} &=& a_{1\sigma_2 (1)}a_{2\sigma_2 (2)}a_{3\sigma_2 (3)}\\
&=& a_{12}a_{21}a_{33}
\end{eqnarray}
という事ですね。$N$次対称群$S_N$の下段の数字を出力するプログラムを作ってみましょう。
import itertools
def S(n):
a = [i for i in range(1,n+1)]
S = list(itertools.permutations(a))
return S
###3.6.2. 置換の符号
行列式中に登場した$sgn$について説明します。これは置換に対して符号を与える関数で、
sgn(\sigma)=\left\{
\begin{array}{ll}
1 & (\sigma が偶置換)\\
-1 & (\sigma が奇置換)
\end{array}
\right.
を意味します。元の数値列$1,2,\dots, N$は$\sigma$によって$\sigma (1),\sigma (2),\dots , \sigma (N)$と並び替えられます。このとき、$\sigma (1),\sigma (2),\dots , \sigma (N)$から$1,2,\dots, N$に戻すときに何回数字を入れ替えるのか?という観点で置換を評価してみます。3.6.1.の$\sigma_{1},\dots,\sigma_{6}$を例に考えてみましょう。$\sigma_{1}$のときは何もしなくて良いので$0$回ですね。$\sigma_{2}$のときは$2$と$1$を入れ替えれば$123$ですね。つまり、$1$回です。$\sigma_{3}$のときは$2$と$3$を入れ替えるので$1$回、$\sigma_{4}$のときは$1$と$2$、$2$と$3$の計$2$回です。同様に考えていくと$\sigma_{5}$は$2$回、$\sigma_{6}$は1回です。このように見てみると偶数回でもとに戻るものと、奇数回の置換でもとに戻る2タイプがある事がわかります。前者は偶置換、後者は奇置換と呼ばれます。今回の例では$\sigma_1,\sigma_4,\sigma_5$が偶置換、$\sigma_2,\sigma_3,\sigma_6$が奇置換になります。つまり、前者の場合は$1$、後者の場合は$-1$を与える関数が$sgn$となりますプログラムで書くと下記です。
def sgn(li):
"""
li--下段の数字の並びのリスト
"""
cnt = 0
# バブルソート
for i in range(len(li)):
for j in range(len(li)-1-i):
if li[j] > li[j+1]:
li[j],li[j+1] = li[j+1],li[j]
cnt += 1
# 偶奇の判定
ans = 1 if cnt%2==0 else -1
return ans,cnt
S = S(3) # 3次対称群
for s in S:
print(s,sgn(list(s)))
上記でバブルソートを採用しているのは隣接した数字の入れ替えのみを愚直に行うプログラムだからです。人間が判断して置換するよりも多くの置換が行われることがありますが、最小回数でもとの数字に並びに戻しても、そうでなくとも偶奇が入れ替わる事がないという性質を活用しています。この性質も上記の関数の出力cntをチェックして確認してみて下さい。
###3.6.3. 再び行列式に戻る
3.6.1、3.6.2を通して行列式の計算方法がわかってもらえたかと思います。それではさっそく行列式を計算するプログラムを作っていきましょう。Pythonの仕様に合わせて先ほど作成したプログラムを改変しています。初めて作りましたが割と簡単に行列式を計算できるようですね。Numpyを使えばnp.linalg.det(np.array(mtx))で計算できます。
import itertools
def S(n):
a = [i for i in range(n)]
S = list(itertools.permutations(a))
return S
def sgn(li):
cnt = 0
# バブルソート
for i in range(len(li)):
for j in range(len(li)-1-i):
if li[j] > li[j+1]:
li[j],li[j+1] = li[j+1],li[j]
cnt += 1
# 偶奇の判定
ans = 1 if cnt%2==0 else -1
return ans
mtx = [[1,2,3],
[4,5,6],
[7,8,9]]
n = len(mtx) # 次元
ans = 0
for s in S(n):
p = 1
for i in range(n):
p *= mtx[i][s[i]]
ans += sgn(list(s))*p
print(ans)
計算できたとしてどんな量を表しているのかよくわからないかと思いますがかなり頻出な量ですのでしっかり覚えておきましょう。ここでもう1つ覚えておいて欲しいのが行列$A$の行列式が$\det{A}\neq 0$のとき、$A$は正則行列と呼ばれます。
##3.7. 逆行列
逆行列を説明する前に単位行列について説明します。単位行列とは対角線上の成分が全て$1$で、それ以外が$0$となる行列です。行列のサイズ$N\times N$行列のとき、$N$次の単位行列と呼び、$I_{N}$と書くことにします。下記に単位行列を列挙しておきます。
I_2 = \left(\begin{array}{cc}
1 & 0\\
0 & 1
\end{array}\right),
I_3 = \left(\begin{array}{ccc}
1 & 0 & 0\\
0 & 1 & 0\\
0 & 0 & 1
\end{array}\right),\dots
本題に戻りますが、$N\times N$行列$A$が正則行列(正方行列かつ$\det{A}\neq 0$)のとき、
AB = BA = I_{N}
を満たすような$B$を逆行列と呼びます。こういった$B$を$A^{-1}$と書きます。この$A^{-1}$を求める方法は色々ありますが上記がわかっていればそれ以上の理解は必要ないのでNumpyで記述する方法を紹介します。
A = np.array([[1,2,3],
[0,5,6],
[7,8,9]])
Ai = np.linalg.inv(A)
print(np.dot(A,Ai)) # AA^{-1}
print(np.dot(Ai,A)) # A^{-1}A
非対角成分に計算誤差がありますが、だいぶ小さいため、ほぼ$0$と言えるような値がでてくるかと思います。
上の説明で逆行列が良くわからないという人のためにどんな事を計算させているのか書いておきます。簡単のため、$2\times 2$行列で説明します。このとき、
\begin{eqnarray}
AB &=& \left(\begin{array}{cc}
a & b\\
c & d
\end{array}\right)\left(\begin{array}{cc}
e & f\\
g & h
\end{array}\right) = \left(\begin{array}{cc}
ae+bg & af+bh\\
ce+dg & cf+dh
\end{array}\right)\\
&=& \left(\begin{array}{cc}
1 & 0\\
0 & 1
\end{array}\right)\\
BA &=& \left(\begin{array}{cc}
e & f\\
g & h
\end{array}\right)\left(\begin{array}{cc}
a & b\\
c & d
\end{array}\right) = \left(\begin{array}{cc}
ea+fc & eb+fd\\
ga+hc & gb+hd
\end{array}\right)\\
&=& \left(\begin{array}{cc}
1 & 0\\
0 & 1
\end{array}\right)
\end{eqnarray}
という関係を持ちます。上記より、$B$が$A$の逆行列となるためには
ae+bg = 1,af+bh=0,ce+dg=0,cf+dh=1,ea+fc=1,eb+fd=0,ga+hc=0,gb+hd=1
を満たすような$e,f,g,h$を要素に持つ必要があります。これをnp.linalg.inv(A)
が求めてくれます。似たような話ですが$B=A^{\frac{1}{2}}$というのが良く機械学習で登場しますがこれは$BB=A$となるような$B$を意味します。Qiita内の記事では行列の$n$乗の取り扱いを誤っているものが多く存在するため、要注意です。A**(0.5)
というような処理はA
の要素を0.5乗するだけなので$A^{1/2}$が想定している処理とは異なり、A
が特殊な行列でない限り誤りです。
##3.8. トレース
これは正方行列でのみ定義されるものです。トレースとは正方行列における対角成分の総和です。つまり、
tr A = tr \left(\begin{array}{cccc}
a_{11} & a_{12} & \cdots & a_{1N}\\
a_{21} & a_{22} & \cdots & a_{2N}\\
\vdots & \vdots & \ddots & \vdots\\
a_{N1} & a_{N2} & \cdots & a_{NN}
\end{array}\right) = \sum_{i=1}^{N} a_{ii}
です。対角成分の和を取る事から対角和と呼ばれることもあります。早速コードにしてみましょう。
A = [[1,2,3],
[0,5,6],
[7,8,9]]
n = len(A)
ans = 0
for i in range(n):
ans += A[i][i]
##3.9. フロベニウスノルム
2章で取り扱ったL1ノルム、L2ノルムはベクトルの大きさを表していましたが、ベクトルの拡張が行列だから当然大きさというのがあると考えていたと思います。ここで紹介するフロベニウスノルムはL2ノルムの拡張という見方ができ、
||A||_{F} = \sqrt{\sum_{i=1}^{M}\sum_{j=1}^{N} a_{ij}^2}
と記述されます。プログラムで書くと以下です。
A = [[1,2,3],
[4,5,6]]
m = len(A) # 行
n = len(A[0]) # 列
ans = 0
for i in range(m):
for j in range(n):
ans += A[i][j]**2
##3.10. Let's try!
【問題3.1】$K\times L$行列を$A$、$L\times M$行列を$B$,$M\times N$行列を$C$とおく。このとき、行列$A,B,C$の間に下記の関係を持つ。左辺と右辺を別々に実行し、右辺と左辺が等価であることを確認せよ。
\begin{eqnarray}
(A')'=A,\quad (B)'=B,\quad (C')'=C\tag{1}\\
(AB)'=B'A',\quad (BC)'=C'B'\tag{2}\\
(ABC)'=C'B'A'\tag{3}
\end{eqnarray}
解答
# 行列A,B,C
A = np.array([[1,2],
[3,4],
[5,6]]) # 3×2行列
B = np.array([[7,8],
[9,10]]) # 2×2行列
C = np.array([[11,12,13,14],
[15,16,17,18]]) # 2×4行列
# (1)
print("(1)(A')'=A'",(A.T.T == A).all())
print("(2)(B')'=B'",(B.T.T == B).all())
print("(3)(B')'=B'",(B.T.T == B).all())
# (2)
print("(2)(AB)'=B'A'",(np.dot(A,B).T == np.dot(B.T,A.T)).all())
print("(2)(BC)'=C'B'",(np.dot(B,C).T == np.dot(C.T,B.T)).all())
# (3) np.dotをまとめる順序に結果が依存するかどうかも確認しておく
print("(3)(ABC)'=C'B'A'",(np.dot(A,np.dot(B,C)).T == np.dot(np.dot(C.T,B.T),A.T)).all())
print("(3)(ABC)'=C'B'A'",(np.dot(A,np.dot(B,C)).T == np.dot(C.T,np.dot(B.T,A.T))).all())
print("(3)(ABC)'=C'B'A'",(np.dot(np.dot(A,B),C).T == np.dot(np.dot(C.T,B.T),A.T)).all())
print("(3)(ABC)'=C'B'A'",(np.dot(np.dot(A,B),C).T == np.dot(C.T,np.dot(B.T,A.T))).all())
#4. 関数
f(x),\quad f(x,y),\quad f(\boldsymbol{x}), \quad f(A)
ここでは上記のような$f(\cdot)$が持つ意味を紹介します。このような表現は頻出なのですが馴染みがない人もいるかと思うので説明をします。これは後に続く微分や積分でも登場しますので未習の人はマスターしましょう。$f(\cdot)$の$f$は関数の英訳functionの(おそらく)頭文字をとったものでかっこ内の変数に依存する関数を表します。$f(x)$と書いた場合、$x$に依存する関数という事を意味し、$f(x,y)$と記述すれば$x,y$に依存する関数であると言えます。$f$の形は何でも良く、
f(x)=x^2 + 1
と書いてもOKです。コードでも確認してみましょう。
def f(x):
return x**2 + 1
x = 5
f(x)
これがわかると機械学習やディープラーニングによる推定についても理解を深める事ができます。関数$f$が何なのかわからないとします。しかし、変数$x$を代入するとその出力を知る事ができるという状態を想定します。このとき、私たちが関数$f$を知るための方法として、とりあえず変数xを色々変えてみるという事のみでしょうか。
x = [i for i in range(0,100)]
y = [f(i) for i in x]
データのある$x$に対する$y=f(x)$を求める事に相当し、このデータをもとに我々から見えない関数$f$を求めるという事を目指すのがざっくりといえば推定になります。関数は写像の導入になるのでもう少し話を続けます。$N$次元ベクトル$\boldsymbol{x}$を代入すると、そのL2ノルムを計算する関数を考えてみます。
f(\boldsymbol{x})=\sqrt{\boldsymbol{x}\cdot\boldsymbol{x}}
コードは以下です。
def f(x):
ans = 0
for i in x:
ans += i**2
return (ans)**(0.5)
x = [2,50,-30]
y = [f(i) for i in x]
このとき、$x$に所属する要素を$f$によって別の値$f(x)$に変換しているというように考えてみて下さい。簡単にいえばこういった操作を写像と言います。さらに言えば、この例における関数$f$の役割はベクトルからスカラーに変換する写像($f:\mathbb{R}^{N} \to\mathbb{R}$みたいに書かれます)といえます。詳しくは後ほどふれるかと思います。
#5. 微分
##5.1. 1変数の微分
微分を簡単に言えば割り算です。スカラー$x$に依存した関数$f(x)$を考えたとき、
\frac{f(x+h)-f(x)}{h}
という量を考えてみましょう。差分商と呼ばれる量です。$f(x+h)$という書き方に困惑するかもしれないのでプログラムにして確認します。下記では$f(x)=x^2+1$を採用しており、この例では$f(x+h)=(x+h)^2+1$を意味します。
def f(x):
return x**2 + 1
h = 1 # 数字は何でも良い
x = 5
f(x+h)
つまり、$f(x)$の$x$を$h$だけ増やしたものを$f(x)$に代入する事を意味します。このとき、$f(x+h)-f(x)$は$x$を$h$だけ増加(減少)させたときの関数$f$の変化量と言えます(増加・減少については8.1を参考にしてください)。これより、$f(x+h)-f(x)/h$は変化率を表していると言えます。プログラムにしてみましょう。
def f(x):
return x**2 + 1
h = 1
x = 5
(f(x+h)-f(x))/h
この$h$をめちゃくちゃ小さくとったものが微分です。コンピュータで計算する上では$h=0.0001$とすれば良いです。なぜ?という疑問は当然湧くかと思いますが、ここは割り切りましょう。微分の数値的な解法は下記です。
\frac{df(x)}{dx}=\frac{f(x+0.0001)-f(x)}{0.0001}\tag{5.1}
数値的という表現は初見だと思うので少し説明しておくと、数値的とは解析的という言葉の対であり、数式の変形から解くときに解析的に解くといい、数値の演算から解くときに数値的に解くといいます。謎の$d$が出てきましたが、$d$の後に続く関数の差分を取る事を意味していると解釈しましょう。つまり、
df(x)=f(x+0.0001)-f(x),\quad dx=(x+0.0001)-x =0.0001
という感じになっているとように考えましょう。つまり、$df(x)$と$dx$の割り算が微分といえます。$\frac{df(x)}{dx}$の他にも$f(x)'$、$\dot{f(x)}$と記述されたりします。これをプログラムにすると以下です。
def diff(f,x):
h = 0.0001
return (f(x+h)-f(x))/h
x = 5
diff(f,x)
いくつかの関数の微分を眺めてみましょう。ビジュアルを確認した方が理解がはかどると思うのでmatplotlibを使って図示していきましょう。
f_{1}(x) = x
import matplotlib.pyplot as plt
def diff(f,x):
h = 0.0001
return (f(x+h)-f(x))/h
def f1(x):
return x
x = [0.0001*i for i in range(1,100000)]
y = [f1(i) for i in x]
dfdx = [diff(f1,i) for i in x]
plt.plot(x,y,'r')
plt.plot(x,dfdx,'b')
plt.show()
ここでは$x$は$0.0001$から$9.9999$まで変化させています。このプログラムを実行すると変数dfdxは常に1を出力し続ける事に気付くかと思います。この微分を解析的に解くと、
\frac{df_{1}(x)}{dx} = \frac{dx}{dx} = 1
です。$x$がどんな値をとってもこの関数の微分は$1$という事ですね(前述したようにただの割り算ですので$dx$とう数字を$dx$で割ったから$1$と求まりました)。この$1$が何を意味してるのか?という話ですが、図示したときの関数の傾きはいくつですか? そう、$1$ですね。微分とは関数の傾きを表していると言えます。
f_{2}(x) = x^2 + x + 1
def f2(x):
return x**2+x+1
x = [0.0001*i for i in range(1,100000)]
y = [f2(i) for i in x]
dfdx = [diff(f2,i) for i in x]
plt.plot(x,y,'r')
plt.plot(x,dfdx,'b')
plt.show()
f_{3}(x) = e^x = exp(x)
この$e$はネイピア数と呼ばれるもので、$2.7182\dots$です。$exp(x)$と書いたとき、$e$の$x$乗を意味します。知らないという人は結構いるかと思いますが、シグモイド関数やソフトマックス関数と呼ばれる機械学習において頻出の関数でも使われている数字です。科学の話になりますが自然現象を説明する式には必ずといっていいほど登場する不思議な数字です(いつかこういった話ができたらと思います)。
import math
def f3(x):
return math.e**x
x = [0.0001*i for i in range(1,100000)]
y = [f3(i) for i in x]
dfdx = [diff(f3,i) for i in x]
plt.plot(x,y,'r')
plt.plot(x,dfdx,'b')
plt.show()
関数$f(x)$を適当に決めて色々プロットしてみて下さい。例えば、$f(x)=x^n$の$n$を色々変えてみて計算してみると面白いです。何かルールがありそうですよね。このルールに関しては色々な人がネットの記事にまとめていますので気になったら、『微分 公式』等で検索してみて下さい。
もう少し理解を深めていきましょう。式(5.1)を変形すると、
f(x+0.0001)=f(x)+0.0001\frac{df(x)}{dx}
です。つまり、$f(x)$にその微分と変化量の積を加算したもので$f(x+0.0001)$を決める事ができるという訳ですね。このように眺めてみると微分が関数の傾きと呼ばれる所以がわかるかと思います。ちなみにこれに似た式をどこかで見たことありませんか? 式の細かい所は異なりますがニューラルネットワークとかで登場するパラメータ更新方法の1つである勾配法の更新式ですよね。これと見比べてみて下さい、パラメータ更新をどのように行っているのか理解が深まると思います。
*この節の話で取り扱った微分は前述したように数値的に微分を実行する方法であり、数値微分と呼ばれるものです。数値微分が目指すのは解析的に解いた微分(本物)に近づくような微分を実行できる事であり、解析的な微分値と数値的な微分値の差を減らすことにあります。本節では前方差分の微分($f(x+h)-f(x)/h$)を取り扱ったものですが、他にも後方差分の微分($f(x)-f(x-h)/h$)、中心差分の微分($f(x+h)-f(x-h)/2h$)などがあります。これらのうちどれが本物に近いのかというような議論は『数値微分における3つの差分とその誤差について』が詳しいです。本記事では簡単のため、前方差分の微分を使います。
**微分の仕組みはわかったからこれでもう微分に関しては勉強しなくても良いのか?というのは本記事の読者の皆様が考えるところではないでしょうか? 私個人の見解としては微分そのものをツール以上のものとして使わないのであればScipyやSimpyで解析的な解を計算できてしまうのでこの節以上に勉強する必要はないと思います(ScipyやSimpyによる微分の代数計算は3.2. Sympy : Python での代数計算が詳しい)。ただし、ディープラーニングや機械学習の流行のその先を見据えているでしょうか?という疑問を投げかけておきます。$2004$年に報告されたとある論文を契機にGAFAの投資先がある分野に集中しています。この分野の成果としては推定の1部の分野ではディープラーニングを超え、また、ディープラーニングに補助的にある操作をするだけで劇的に性能が変わるというものです(2010年あたりからこういった報告が増えてきました)。この分野を理解する手助けになるのものの1つが微分です。
##5.2. 多変数の微分
4章でみたように関数$f$の引数はいくつでもとって良いため、$f(x,y,z,\dots)$みたいな関数も当然存在します。このような関数の微分をどう取り扱うのか?というのがこの節での話になります。簡単のために2変数に依存する$f(x,y)$から考えていきます。5.1.を素直に発展させるだけなので基準点$f(x,y)$から$f(x+h,y)$、$f(x,y+h)$と変化させたときの差分商というのを取り扱います。つまり、変化をさせない方は固定して取り扱います。こういった意味では5.1で取り扱った微分とは異なるので、これを偏微分と呼び、$d$の代わりに$\partial$(デル、パーシャル、ラウンドと呼んだりします)という記号で表現します。数式で書き表すと下記です。
\frac{\partial f(x,y)}{\partial x} = \frac{f(x+0.0001,y)-f(x,y)}{0.0001},\quad \frac{\partial f(x,y)}{\partial y} = \frac{f(x,y+0.0001)-f(x,y)}{0.0001}
左は$x$に関する偏微分、右は$y$に関する偏微分を表します。プログラムにすると下記です。本例では$f(x,y)=x^2 + 2y^2$を取り扱います。ただし、前述したように片方の変数を固定値として扱う必要があるため、本例では$f(x,y=1)$、$f(x=1,y)$における$x$の偏微分、$y$の偏微分を取り扱います。
def fx(x):
y = 1
return x**2+2*y**2
def fy(y):
x = 1
return x**2+2*y**2
def diff(f,p):
h = 0.0001
return (f(p+h)-f(p))/h
x = [0.0001*i for i in range(1,100000)]
y = [0.0001*i for i in range(1,100000)]
# xの偏微分
delxf = [diff(fx,i) for i in x]
# yの偏微分
delyf = [diff(fy,i) for i in y]
複数の変数から構成されていたとしても、変化を考えない変数は固定値として考えれば良いので5.1で作成したdiff関数がそのまま活用できます。関数の引数が$n$個になっても話は同じで$f(x_1,x_2,\dots,x_n)$の$x_1$に関する偏微分、$x_2$に関する偏微分、...は下記です。
\begin{eqnarray}
\frac{\partial f(x_1,x_2,\dots,x_n)}{\partial x_1} &=& \frac{f(x_1+0.0001,x_2,\dots,x_n)-f(x_1,x_2,\dots,x_n)}{0.0001}\\
\frac{\partial f(x_1,x_2,\dots,x_n)}{\partial x_2} &=& \frac{f(x_1,x_2+0.0001,\dots,x_n)-f(x_1,x_2,\dots,x_n)}{0.0001}\\
\vdots\\
\frac{\partial f(x_1,x_2,\dots,x_n)}{\partial x_n} &=& \frac{f(x_1,x_2,\dots,x_n+0.0001)-f(x_1,x_2,\dots,x_n)}{0.0001}
\end{eqnarray}
##5.3. ベクトルの微分
##5.4. 行列の微分
#6. 積分
#6.1. 1変数の積分
今、変数$x$に依存する関数$f(x)$が与えられているとします。このとき、$x$軸と$f(x)$に囲まれた領域を計算したいとします。$x$は制限がない限りどんな値でもとれて面倒なので、$x=a$から$x=b$までという制限をかけてみましょう。これを図示するプログラムは下記です。この4つの青線で囲まれた領域の面積を計算したい訳です。
import matplotlib.pyplot as plt
def f(x):
return x**2
x = [i for i in range(0,100)]
a = 20
b = 80
y = [f(i) for i in x]
plt.plot(x,y,'b')
plt.axvline(x=a,c='b') # x=a
plt.axvline(x=b,c='b') # x=b
plt.axhline(y=0,c='b') # x軸
plt.show()
この領域の面積が積分と呼ばれる量です。数式で書くと下記です。
I = \int_{a}^{b}f(x)dx\tag{6.1}
今回のプログラムの例だと
I = \int_{a}^{b}f(x)dx = \int_{20}^{80}x^2 dx
と記述します。これは微分でも出てきたように解析的な解と数値的な解が存在し、上式を解析的に解くと
I = \int_{20}^{80}x^2 dx = 168000
と求まります。どうやって解くんだよ?という話になりますが、それは『積分 公式』等で検索をかけて調べてみて下さい。ここでは数値的な手法によってこの積分というものがどんな計算をしているのかを確認します。気付きを得るために先ほどのプログラムに下記を追加してプロットしてみて下さい。
dy = [(f(b)-f(a))*(i-a)/(b-a)+f(a) for i in x]
plt.plot(x,dy,'r')
新たに追加した赤線と$x$軸、$x=a$、$x=b$は何に見えますか? そう、台形です。台形の面積は(上底+下底)×高さ÷2だったことを思い出すと、この台形の面積$S$は
S = \frac{(f(a)+f(b))\times (b-a)}{2} = 204000
(f(b)+f(a))*(b-a)/2
と求まります。つまり、求めたい面積を$36000$ほど過大評価している事がわかります。その原因は$f(x)$と新たに追加した赤線で囲まれた領域なのは明らかですよね。この過剰な領域を減らすために$a<c<b$となるような$c$を一旦中継してあげれば良さそうという方策が立ちます。言い換えると、求めたい$I$を台形2つの足し合わせと考えてみるという事です。
x = [i for i in range(0,100)]
a = 20
b = 80
c = 50
y = [f(i) for i in x]
dy = [(f(b)-f(a))*(i-a)/(b-a)+f(a) for i in x] # (b,f(b))と(a,f(a))を結ぶ線
dy1 = [(f(c)-f(a))*(i-a)/(c-a)+f(a) for i in x] # (c,f(c))と(a,f(a))を結ぶ線
dy2 = [(f(b)-f(c))*(i-c)/(b-c)+f(c) for i in x] # (c,f(c))と(b,f(b))を結ぶ線
plt.plot(x,y,'b--')
plt.plot(x,dy,'r--')
plt.plot(x,dy1,'r')
plt.plot(x,dy2,'r')
plt.axvline(x=a,c='b') # x=a
plt.axvline(x=b,c='b') # x=b
plt.axvline(x=c,c='b') # x=c
plt.axhline(y=0,c='b') # x軸
plt.show()
上記を実行してみると先ほどよりも過剰に計算する領域が減ったと思いませんか? 実際に計算してみましょう。左側の台形の面積を$S_{1}$、右側の台形の面積を$S_{2}$とおけば、
S_{1} = \frac{(f(a)+f(c))\times (c-a)}{2} = 43500,\quad S_{2} = \frac{(f(c)+f(b))\times (b-c)}{2} = 133500
と求まるので、$S=S_{1}+S_{2}=177000$となり、$I-S=9000$とだいぶ近づくことがわかります。この方策は良さそうですね。この議論を進めていくと、中継地点を増やせば増やすほど$I$に近づきそうという結論に至ります。つまり、$a<c_{1}<c_{2}<\dots <c_{n-1} <b$と細かく中継地点を用意し、その台形を計算してやれば良いのではという発想になります。この中継地点をどうやって分割してやるのか?という点まで考えると考える余地はあるのですが、これはさらに進んだ学問で取り扱われるので簡単に$b-a$を$n$分割する事を考えます。ここで$a=c_{0},b=c_{n}$と定義してやると、$i$番目の中継地点(区切り)$c_{i}$は
c_{i} = c_{0} + i\frac{c_{n}-c_{0}}{n}
と求まる事がわかります。これを利用してやると式(6.1)の右辺は下記のように書き換えられます。
\begin{eqnarray}
\int_{a}^{b}f(x)dx &\approx & \sum_{i=1}^{n}\frac{(f(c_{i-1})+f(c_{i}))(c_{i}-c_{i-1})}{2}\\
&=& \sum_{i=1}^{n}\frac{(f(c_{i-1})+f(c_{i}))(c_{n}-c_{0})}{2n}\\
&=& \sum_{i=1}^{n}\frac{(f(c_{i-1})+f(c_{i}))(b-a)}{2n}\\
&=& \frac{b-a}{2n}\sum_{i=1}^{n}(f(c_{i-1})+f(c_{i}))
\end{eqnarray}
つまり、1章の総和に戻ってくる訳ですね。ここで$\approx$というのを使いましたが雑に言えば大体イコールなときに使うイコールです。積分をこのように取り扱う方法は台形公式と呼ばれるもので、取り扱う関数によっては良い精度で数値的に積分できるという事が知られています。何でもかんでも積分できるわけではないといったアドバンスな話があったりしますがこのような話題は割愛します。早速、台形公式を利用した数値積分を実装しましょう。ここではNumpyのlinspaceという便利な関数を用いて$n$等分しています。
import numpy as np
def f(x):
return x**2
def integral(f,a,b,n):
"""
f--関数
a--下限
b--上限
n--分割数
"""
c = np.linspace(a,b,n)
ans = 0
for i in range(n-1):
ans += f(c[i])+f(c[i+1])
return (b-a)*ans/(2*n)
a = 20
b = 80
n = 100
integral(f,a,b,n)
$n$をどれくらいにすれば良いのか? これは気になるところですよね。数値シミュレーションしてみましょう。
def Mysimulation(f,a,b,I):
"""
f--関数
a--下限
b--上限
I--解析解
"""
N = [i for i in range(20,10000,10)]
y = [integral(f,a,b,n)-I for n in N] # Iと数値積分の差分
plt.scatter(N,y)
I = 168000
a = 20
b = 80
Mysimulation(f,a,b,I)
*積分の勉強をする意義について軽く触れておきます。恐らくこの記事の読者はデータサイエンス等にも興味があると思うのでいずれ統計学の勉強を開始するかと思います。この基礎となるのが確率論と呼ばれる分野です。この確率論を学んだ人間は例外なく確率は面積であると言います。面積とは本節で取り扱ったように積分によって算出する事ができます。つまり、確率論の基礎に積分があります。
#7. 集合
##7.1. 集合とタプル
ここで取り扱う集合とはもの集まりです。ものとは何でも良く、アルファベット、数字、平仮名などから集合そのものまで基本に何でもありです。ただし、多重集合と呼ばれるような特殊な集合を考えない限り、ものの重複はなしです。また、このものの事を要素または元と呼び、本記事では統一して要素と呼ぶ事にします。ある集合$A$が$a_{1},a_{2},\dots,a_{N}$という$N$個の要素からなるとき、以下のように記述されます。
A = \{a_{1},a_{2},\dots,a_{N}\}
ポイントは${\cdot}$で要素を囲んでいる点と、要素の並びに意味がないことです。つまり、
A = \{a_{N},a_{2},\dots,a_{1}\}
と並びを変えても問題ないという事です。ここはベクトルとは異なる点ですよね。要素に順序を持たせたい場合は
A = (a_{1},a_{2},\dots,a_{N})
と書き、これをNタプルと呼びます。この順序という点に注目するとベクトルを思い出しませんか? ベクトルのより一般的な概念がタプルです。詳しくは群環体論を勉強する必要があるのですが要素に集合とか含めてよいのがタプルと考えておけば当分問題ありません。Pythonではこの集合を扱うためにset
型というのがあります。下記は$1,2,3,4,5$を要素に持つ集合$A$をPythonで記述したものです。
A = {1,2,3,4,5}
前述したように通常の集合は要素の重複を許さないため、
A = {1,1,2}
print(A) # {1,2}
ユニークなものしか残らないようになっています。上記をタプルverで出力してみましょう。
A = (1,1,2)
print(A) # (1,1,2)
次は重複したものが残っていますね。これは前述したように順序そのものが重要な意味を持つためですね。もう少しset
型で集合について確認していきましょう。集合の要素が集合あっても良いという話をしましたが下記みたいのもありです。
A = {1,2,3}
B = {5,6}
C = A.union(B)
print(C) # {1,2,3,4,5}
集合$C$をみると{{1,2},{3,4,5}}
ではなく、{1,2,3,4,5}
と出力さている点に注目してみましょう。ただ単に集合2つを$C$が保持しているというよりも$C$の中に集合$A,B$の領域があるというイメージが湧きませんか? このように$A$に属する要素すべてが$C$に属している状態をAはCの部分集合であるといいます。また、同様にBはCの部分集合であるという事もいえます。これを$A\subset C$または$C\supset A$、$B\subset C$または$C\supset B$と書きます。この例では明らかな上下関係を付けて書きましたが、$A$と$C$が全く同じ集合においても部分集合というものが考えられるため、イコールという意味込み($A=B$)で$A\subseteq B$または$B\supseteq A$と、さっきの記号の下に下線を入れた記号を使う事があります。また、$a\in A$と記述したとき、これは集合$A$を構成する$a$を示します。$A={1,2,3}$のとき、$a\in A$が表す$a$とは$1,2,3$です。
##7.2.集合の記法
集合の要素が数個であるとき、上記のような書き方で特に不便はないが1以上3未満の実数の集合を考えたいときどうすれば良いのだろうか? 要素を1つ1つ書くというのは実数の定義上不可能なわけです。こんな時に役立つのが
\{x|1\leq x<3\}
という表記の仕方です。${変数 \mid 条件}$という構造になっています。他にも${条件1 \mid 条件2}$というような構造になる事をもあります。覚えておきましょう。
##7.3. 集合の演算
ベクトルや行列のときもそうでしたが何かしらの構造物を考えると次に考えたいのはその構造物同士の演算です。ここでは重要な4つの演算について記述します。
###7.3.1. 和集合
私たちが想像する足し算っぽいのがこれです。集合$A,B$の和集合は
A\cup B = \{x\mid x\in A または x\in B\}
です。簡単に言えば、2つの集合(領域)内にある要素を全て含む集合です。いくつか例をみていきましょう。
A = {1,2}
B = {5,6}
print(A.union(B))
\begin{eqnarray}
A\cup B &=& \{x\mid x\in Aまたはx\in B\}\\
&=& \{x\mid x\in \{1,2\}または x\in \{5,6\}\}\\
&=& \{1,2,5,6\}
\end{eqnarray}
A = {1,2,3,4}
B = {3,4,5,6}
print(A.union(B))
\begin{eqnarray}
A\cup B &=& \{x\mid x\in Aまたはx\in B\}\\
&=& \{x\mid x\in \{1,2,3,4\}または x\in \{3,4,5,6\}\}\\
&=& \{1,2,3,4,5,6\}
\end{eqnarray}
###7.3.2. 積集合
集合の共通項を抜き出す演算です。集合$A,B$の積集合は
A\cap B = \{x\mid x\in A かつ x\in B\} = \{x\mid x\in A,x\in B\}
と記述されます。集合$A$と$B$に共通項がないときには要素を1つも含まない集合を表す空集合$\emptyset$を使って、
A\cap B = \emptyset
と記述します。このとき、集合$A$と$B$は互いに素であるといいます。いくつか例を確認していきましょう。
A = {1,2,3}
B = {4,5}
print(A.intersection(B))
\begin{eqnarray}
A\cap B &=& \{x\mid x\in A,x\in B\}\\
&=& \{x\mid x\in \{1,2,3\},x\in \{4,5\}\}\\
&=& \emptyset
\end{eqnarray}
A = {1,2,3}
B = {3,4,5}
print(A.intersection(B))
\begin{eqnarray}
A\cap B &=& \{x\mid x\in A,x\in B\}\\
&=& \{x\mid x\in \{1,2,3\},x\in \{3,4,5\}\}\\
&=& \{3\}
\end{eqnarray}
###7.3.3. 差集合
簡単に言えば集合同士の引き算です。集合$A$と$B$の差集合は
A\setminus B = \{x\mid x\in A, x\notin B\}
です。この意味は$A$には含まれていて、$B$には含まれないような$x$の集合を意味します。つまり、$A$から$B$の要素を排すというのが差集合になります。他にも$A-B$と書くことがあります。いくつか例を確認していきましょう。
A = {1,2,3}
B = {4,5}
print(A.difference(B))
\begin{eqnarray}
A\setminus B &=& \{x\mid x\in A,x\notin B\}\\
&=& \{x\mid x\in \{1,2,3\},x\notin \{4,5\}\}\\
&=& \{1,2,3\}
\end{eqnarray}
A = {1,2,3}
B = {2,3}
print(A.difference(B))
\begin{eqnarray}
A\setminus B &=& \{x\mid x\in A,x\notin B\}\\
&=& \{x\mid x\in \{1,2,3\},x\notin \{2,3\}\}\\
&=& \{1\}
\end{eqnarray}
集合$A$同士の差集合$A\setminus A$はどうなるでしょうか。試してみて下さい。
###7.3.4. 補集合
ある集合以外というのが補集合です。集合$A,B$があり、$A\subset B$に対して、
A^c = B\setminus A
を$A$の$B$に関する補集合といいます。$B$から$A$を取り除いた$B$というのが$A$の補集合という訳ですね。いくつか例を確認していきましょう。
A = {1,2,3}
B = {1,2,3,4,5}
print(B.difference(A))
\begin{eqnarray}
A^c &=& B\setminus A\\
&=& \{x\mid x\in B, x\notin A\}\\
&=& \{x\mid x\in \{1,2,3,4,5\},x\notin \{1,2,3\}\}\\
&=& \{4,5\}
\end{eqnarray}
A = {3}
B = {1,2,3,4,5}
print(B.difference(A))
\begin{eqnarray}
A^c &=& B\setminus A\\
&=& \{x\mid x\in B, x\notin A\}\\
&=& \{x\mid x\in \{1,2,3,4,5\},x\notin \{3\}\}\\
&=& \{1,2,4,5\}
\end{eqnarray}
###7.3.5. 直積集合
各集合から1つずつ元を取り出してタプルにするというのがこの直積集合です。集合$A,B$の直積集合は
A\times B = \{(x,y)\mid x\in A, y\in B\}
と書くことができます。復習になりますが、タプルとは順序を持った構造物なので$(x,y)\neq (y,x)$となります。いくつか例を確認していきましょう。
def dctPro(set1,set2):
"""
set1,set2 -- 集合
"""
dctPro = set()
for i in set1:
for j in set2:
dctPro.add(tuple([i,j]))
return dctPro
A = {'♡','♧','♠','♦'}
B = {i for i in range(1,14)}
dctPro(A,B)
\begin{eqnarray}
A\times B &=& \{(a,b)\mid a\in A,b\in B\}\\
&=& \{(a,b)\mid a\in\{♡,♧,♠,♦\},b\in\{x\in \mathbb{Z}|1\leq x\leq 13\}\\
&=& \{(♡,1),( ♡,2),\dots,( ♦,13)\}
\end{eqnarray}
A = {1,2,3}
B = {4,5,6}
dctPro(A,B)
\begin{eqnarray}
A\times B &=& \{(a,b)\mid a\in A,b\in B\}\\
&=& \{(a,b)\mid a\in\{1,2,3\},b\in\{4,5,6\}\\
&=& \{(1,4),( 1,5),\dots,( 3,6)\}
\end{eqnarray}
##7.4. 覚えておきたい集合
実数とか自然数全体とかを表す特別な集合があります。この集合に属するなら何でも良いって時に使います。
記号 | 意味 |
---|---|
$\mathbb{R}$ | 実数全体の集合($-1,-1.1,1\dots$) |
$\mathbb{N}$ | 自然数全体($0,1,2,3\dots$.$0$を含まない場合もある) |
$\mathbb{Z}$ | 整数全体($-2,-1,0,1,2,3\dots$) |
他にもあるのですが大体この$3$つを覚えておけば困らないかと思います。$\mathbb{R}^{N\times M}$と記述されているときは、実数を要素に持つ$N\times M$の行列の集合を意味する事があります。 |
##7.5. 指示関数
特徴量選択とかの論文などでは頻出の表現ですかね。ある行列$A$とその部分集合$B$に対して、$A$の要素$x$が$B$に属すとき$1$、そうでなければ$0$を出力する関数です。そのような関数を$f_B$とおいたとき、
f_B(x)=\left\{
\begin{array}{ll}
1 & (x\in A)\\
0 & (x\notin A)
\end{array}
\right.
と記述されます。意味はわかったけどなんでこんなものが重要なのか?という疑問が湧くかと思いますが
\sum_{x\in A} f_{B}(x)
というようなものを考えてみると集合$B$の要素数のカウントに使えますし、$x$を数値に限れば
\frac{\sum_{x\in A} xf_{B}(x)}{ \sum_{x\in A} f_{B}(x)}
は平均値を表したりする訳ですね。ちなみに、集合の要素の個数は$|A|,card(A)$などと記述します。覚えておきましょう。それでは早速実装していきましょう。
def f(sSet,x):
"""
sSet -- 部分集合
x -- 含まれているか判定したい要素
"""
if x in sSet:
ans = 1
else:
ans = 0
return ans
A = {i for i in range(0,30)} # A = {x∈Z|0<=x<30}
B = {i for i in range(15,20)} # B = {x∈Z|15<=x<20}
for i in A:
print(i,f(B,i))
##7.5. 同じ演算を複数回
1章ではインデックスを付けた変数$a_1,a_2,\dots,a_N$を全て加算したい、乗算したいときには$\sum,\prod$という記号を使って表しました。集合の演算においてもインデックスで区別した集合$A_1,A_2,\dots,A_N$の和集合、積集合などを考えたい場面は多々あります。適切なタイトルが思いつかなかったのですがこの節ではこの話をしていきます。
##7.5.1. 和集合
$N$個の集合$A_1,A_2,\dots,A_N$に対するこれらの和集合は
A_{1}\cup A_{2}\cup \dots \cup A_{N} = \bigcup_{i=1}^{N} A_{i}
と記述されます。どんな計算になるのかよくわからないとは思いますが、コードを見て理解を深めましょう。
A1 = {i for i in range(0,10)}
A2 = {i for i in range(5,15)}
A3 = {i for i in range(10,20)}
A = [A1,A2,A3] # まとめただけ
ans = set()
for i in A:
ans |= i # 和集合
コードを実行するとわかるかと思いますが、愚直に和集合を作っていくという事ですね。
###7.5.2. 積集合
$N$個の集合$A_1,A_2,\dots,A_N$に対するこれらの和集合は
A_{1}\cap A_{2}\cap \dots \cap A_{N} = \bigcap_{i=1}^{N} A_{i}
と記述されます。コードを見て理解を深めましょう。
A1 = {i for i in range(0,10)}
A2 = {i for i in range(5,15)}
A3 = {i for i in range(5,20)}
A = [A1,A2,A3] # まとめただけ
ans = A[0] # 初期値を空集合にしてしまうとどんなに頑張っても出力結果は空集合
for i in A:
ans &= i # 積集合
上記では暗に以下の事実を活用しています。1つ目は
A\cap A = A
です。$A$と$A$自身は同じものなので共通項は$A$の要素全てですよね。つまり、$A$と$A$の積集合は$A$となります。2つ目は
A\cap \emptyset = \emptyset
です。空の集合と空でない集合の共通項はどこですか?といえばない(あるともいえるが…)ので空集合となります。つまり、初期値を空集合にしてしまうと出力結果は空集合にしかならないという事ですね。
##7.5.3. 直積集合
$N$個の集合$A_1,A_2,\dots,A_N$に対するこれらの直積集合は
\begin{eqnarray}
\prod_{i=1}^{N} A_{i} &=& A_{1}\times A_{2}\times \dots \times A_{N} \\
&=& \{(a_{1},a_{2},\dots,a_{N})\mid a_{1}\in A_{1},a_{2}\in A_{2},\dots,a_{N}\in A_{N}\}
\end{eqnarray}
と記述されます。和集合、積集合の流れを汲めば$(a_{1},(a_{2},a_{3}))$みたいなのを考えるのかと思いますが裏切られましたね。
A1 = {'♡','♧','♠','♦'}
A2 = {i for i in range(1,14)}
A3 = {'r','b'}
ans = set()
for i in A1:
for j in A2:
for k in A3:
ans.add(tuple([i,j,k]))
##7.5.4. デカルト冪(べき)
内容としては直積集合と同じなのですが、特別な奴がこれです。ここで考えるのは集合$A$に対し、それ自身の直積として得られる集合です。自分自身を$N$回乗じる事を$N-$乗デカルト冪と呼び、
\begin{eqnarray}
A^{N} &=& \prod_{i=1}^{N}A\\
&=& A\times A\times \dots \times A\\
&=& \{(a_1,a_2,\dots,a_N)\mid a_{i}\in A,\forall i=1,2,\dots, N\}
\end{eqnarray}
で与えられます。$\mathbb{R}^{N}$というった表現はこんな感じで$\mathbb{R}\times \mathbb{R} \times \dots \times \mathbb{R}$と$\mathbb{R}$から$N$個の数字を抜き出してタプルにするという処理をかましているという事になります(少し正しさは欠いているのですが認識としてこれで良いです)。これを利用すると$\boldsymbol{x}\in \mathbb{R}^2$って記述されているとき、$\mathbb{R}$から要素を2つ抜き出して構成した2次元ベクトルを表現できるわけです。ディープ系の論文では重みやバイアスに対してこのデカルト冪を利用して実数で構成されている事を表現したりします。
A = {'♡','♧','♠','♦'}
ans = set() # A^3
for i in A:
for j in A:
for k in A:
ans.add(tuple([i,j,k]))
#8. 小話
ここら辺の話は地味に役立つ(かもしれない)小話です。
##8.1. 四則演算
x+h
上の式皆さんはどんな印象を受けましたか? おそらく、$x$と$h$の足し算だと考えたかと思います。これ自体は間違いではないのですが、引き算になる事もあるというのがわかったでしょうか。$h<0$の場合を考えてみましょう。$h$の絶対値$|h|$を$h'$とおくと上式は
x+h=x-|h|=x-h'
となり、引き算といえる事がわかります。$h$の符号に言及がない時はこういった可能性があるので要注意です。これらの踏まえて以下の式について考えてみましょう。
c\times x
この計算に割り算が含まれる可能性がある事を見抜けましたか? $0<c<1$という範囲に注目した時、$c$は小数です。例えば、$c=0.1$のとき、
c\times x = 0.1\times x = \frac{x}{10}
という割り算と考える事ができます。$c$次第では掛け算にも割り算にもなるという事ですね。
#9. 目標達成度チェック
ここには色々な式を列挙します。これらをコードに変換できるようになっていれば目標達成です。この記事を読む前までの数学がわからずに苦しんでいた状態から書いてあることはわかるけど内容がわからないという状態にステップアップできてたら間違いなくレベルアップできてます。
【問9.1】パラメータ$r>0$、2次元ベクトル$\boldsymbol{c},\boldsymbol{p}$が
||\boldsymbol{p}-\boldsymbol{c}||_{2}=r
という関係を持つとします。これはとある図形を表す式です。ここで、簡単のため、
\boldsymbol{c} = \left(\begin{array}{c}
0\\
0
\end{array}\right)
とし、また、$r=1$とします。すなわち、
||\boldsymbol{p}||_{2}=1
と記述できる場合を考えます。これを満たす$\boldsymbol{p}$を複数求め、これを図示し、この式が示す図形を答えよ。また、
||\boldsymbol{p}||_{1}=1
も同様にこれを満たす$\boldsymbol{p}$を複数求め、これを図示し、この式が示す図形を答えよ。
ヒント
まずは$\boldsymbol{p}=(x\quad y)'$とおいて考えていきましょう。これより、
|x|+|y|=1(L1),\quad x^2+y^2=1(L2)
と記述できます。なお、L2に関してはさらに両辺を2乗しています。8章で少し触れたように$x,y$は正、負のどちらでも取りうるという事に注意します。この関係を満たすような$x,y$を決めてやれば良いのですが、もう少し式を眺めてみると$x=1000$のような大きな値はとらず$0$から$1$程度しかとらなさそうだなと読み取れればOKです。
#10. 書籍の紹介
私が学部生の頃に読んだ・読まされた和書をあげておきます。本記事を読んでもう少し先の事を学びたいと思った方は参考にしてください。
-『理工系のための微分積分学入門』: 学部1年のときに指定された教科書。定理などの証明は基本的に省略されているため、そこが気になる人にはあまりお勧めできない。短期間でとにかく微分積分の手計算を仕上げたい、知識を網羅しておきたいという人にはお勧めできる。ページ数は200ページ程度。問題演習込みで40時間で読了したという記録がある。
-『演習と応用 ベクトル解析』:学部1年のとき特に難しいと感じていた分野。本記事の読者がベクトル解析を使う事はないと思うが一応書いておきます。演習がメインなので抽象的な概念を具体例で学べるというが良い。微分積分学をある程度修めていれば、パラ読み可能な本。心配なところ・読んで理解できないところだけ演習するという使い方でOK。線形独立、線形従属などの概念だけでもおさえておきたい。パラ読み+演習で10時間程度。
-『演習と応用 線形代数』:学部1年生のときに自習で使った本。上記と同じサイエンス社の本で演習で慣れる系の本で問題が豊富かつ答えが親切なのが良い。今回取り扱わなかった固有値方程式、ユニタリ行列、2次形式などの話題も取り扱っている。ここら辺の話をカバーしておくと主成分分析、特異値分解などの次元削除がどういう仕組みになっているのかが理解できるので興味があれば勉強すると良いかもしれません。パラ読み+演習で15時間程度。
#その他
今回リクエスト頂いた確率・統計、グラフ理論などは別記事で取り扱う予定です。