【Python】２次元行列の各要素にベクトルをかけて３次元の行列を作る

<script type="text/javascript" async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML">
</script>
<script type="text/x-mathjax-config">
 MathJax.Hub.Config({
 tex2jax: {
 inlineMath: [['$', '$'] ],
 displayMath: [ ['$$','$$'], ["\\[","\\]"] ]
 }
 });
</script>


# 2本目です．

今回は友人の課題を解決した時の内容を紹介します．

## 課題1
$f(a,\vec{x})=ag(\vec{x})$

$\vec{x}=
\begin{bmatrix}
x_{1} \\ x_{2}\\
\vdots \\ x_{l}
\end{bmatrix}
\quad
g(\vec{x})=
\begin{bmatrix}
g(x_{1}) \\ g(x_{2}) \\
\vdots \\ g(x_{l})
\end{bmatrix}
\quad
ag(\vec{x})=
\begin{bmatrix}
ag(x_{1}) \\ ag(x_{2}) \\
\vdots \\ ag(x_{l})
\end{bmatrix}
$


以上のスカラーとベクトルの演算$f(a,\vec{x})$をPythonで実装します．

今回は例として，

$a=2
\quad
\vec{x}=
\begin{bmatrix}
1 \\ 2 \\ 3
\end{bmatrix}
\quad
f(a,\vec{x})=
\begin{bmatrix}
2 \\ 4 \\ 6
\end{bmatrix}
$

について考えます．


```python
import numpy as np

a = 2
x = np.array([1, 2, 3])
print('a = {}  x = {}'.format(a, x))
```

    a = 2  x = [1 2 3]
    


```python
f = a * x
print('f(a, x) = {}'.format(f))
```

    f(a, x) = [2 4 6]
    

出来ました．

列と行が逆なのは見逃してください．

でも，ここまでは簡単ですね．

## 課題2
「実は$a$は$m \times n$個あって，それぞれに$\vec{x}をかけたい$」

$A=
\begin{bmatrix}
a_{11} a_{12} \dots a_{1n} \\ 
\vdots \quad \quad \vdots \\
a_{m1} \dots \dots a_{mn}
\end{bmatrix}
$

$h(A,\vec{x})=
\begin{bmatrix}


なるほどなるほど．

上記の行列$A$の全ての要素とベクトルをかけたらいいと．

for文を使ったら簡単そうですね．


```python
A = np.array([[1, 2], [3, 4]])
print('A={}'.format(A))
```

    A=[[1 2]
     [3 4]]
    

行列のままではfor文で回せないので，`reshape`を使ってベクトルに変換します．


```python
Avec = A.reshape(4)
print(Avec)
```

    [1 2 3 4]
    


```python
B = np.array([], dtype=int)
np.set_printoptions(precision=0)
for a in Avec:
    B = np.append(B, [a * x])
B = B.reshape(2, 2, 3)
print('B = \n {}'.format(B))
```

    B = 
     [[[ 1  2  3]
      [ 2  4  6]]
    
     [[ 3  6  9]
      [ 4  8 12]]]
    

出来ました．

## 課題3
「for文を使うと計算時間が長くなってしまうから並列に計算したい」

なるほど．

目的とする形は同じですが，各要素単位に計算するのではなく行列演算のように一気に計算したいと．

ただこれは簡単にはいきません．

例えば，単純に以下のようにすると...



```python
print(A * x) 
```


    ---------------------------------------------------------------------------

    ValueError                                Traceback (most recent call last)

    <ipython-input-32-3cea4e3e8850> in <module>
    ----> 1 print(A * x)
    

    ValueError: operands could not be broadcast together with shapes (2,2) (3,) 


上手くいきません．

`*`は行列の定数倍か同じ大きさの行列を要素ごとにかける場合にしか使えません．

`A`と`x`はそもそも次元の大きさが違うのでそらできませんよね．

### 解決法
ではここからは解決法を紹介します．

まずは，次元数を揃えます．

現在`A`は二次元，`x`は一次元です．

目的の`B`は三次元なので，両者を三次元に揃えます．

np配列の次元数を変えるには`np.newaxis`を使います．


```python
A3 = A[:, :, np.newaxis]
x3 = x[np.newaxis, np.newaxis, :]
print('A3 = \n{} \n\nx3 = {}'.format(A3, x3))
```

    A3 = 
    [[[1]
      [2]]
    
     [[3]
      [4]]] 
    
    x3 = [[[1 2 3]]]
    

参照:[NumPy配列ndarrayに次元を追加するnp.newaxis, np.expand_dims()](https://note.nkmk.me/python-numpy-newaxis/)

出来ました．

ここで，もう一度先程できなかった演算をしてみましょう．


```python
B = A3 * x3
print(B)
```

    [[[ 1  2  3]
      [ 2  4  6]]
    
     [[ 3  6  9]
      [ 4  8 12]]]
    

### 出来ました!!
numpyにはブロードキャストという機能があり，次元数さえ同じであれば配列の大きさが違っても自動で演算してくれるようです．

今回の例では，$2 \times 2 \times 1$の`A3`と$1 \times 1 \times 3$の`x3`の要素ごとの掛け算が自動化されています．

小さいほうの配列が大きいほうに合わされるようなので，実際には...


```python
A3 = np.array([[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]]])
x3 = np.array([[[1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3]]])
print('A3 =\n{}\n\nx3 = \n{}\n\nB = \n{}'.format(A3, x3, A3 * x3))
```

    A3 =
    [[[1 1 1]
      [2 2 2]]
    
     [[3 3 3]
      [4 4 4]]]
    
    x3 = 
    [[[1 2 3]
      [1 2 3]]
    
     [[1 2 3]
      [1 2 3]]]
    
    B = 
    [[[ 1  2  3]
      [ 2  4  6]]
    
     [[ 3  6  9]
      [ 4  8 12]]]
    

恐らくこのように変換されたうえで演算が行われているのでしょう．

## 終わりに
今回の方法自体は調べても出てこなかったので，そこそこ有意義な記事を書けたのかもしれません．

でも，まだあまり余裕がなくて記事として面白くないです．

面白い記事が書けるようになりたいですね．