【IT Text自然言語処理の基礎】第2章演習問題を解いてみた

# 本記事の概要
この記事では「IT Text 自然言語処理の基礎」（オーム社）の **第2章「自然言語処理のための機械学習の基礎」** に付いている演習問題を詳しく解説します。

書籍にも略解はありますが、解説が省略されていたりします。
略解でわからなくなったときは、この記事を参考にしてもらえればと思います！

# 問1
> パーセプトロンでは，式(2.9)に従って重みベクトルが更新される．負例の分類を間違えた場合に，式(2.9)による更新で同じ誤りが起こりにくくなることを確かめよ．
> $$\begin{align}
\hat{y} &= \boldsymbol{1}_{\boldsymbol{w}^\top\boldsymbol{x}\geq 0} \tag{2.8}\\\\
\boldsymbol{w} &\leftarrow \boldsymbol{w} + (y^{(i)} - \hat{y}^{(i)})\boldsymbol{x}^{(i)} \tag{2.9}
\end{align}$$

「負例の分類を間違えた」と言っているので、
$$y^{(i)}=0,\quad \hat{y}^{(i)}=1$$
です。したがって、現在の重み$\boldsymbol{w}$は更新式(2.9)により
$$\boldsymbol{w} + (0-1) \boldsymbol{x}^{(i)}=\boldsymbol{w}-\boldsymbol{x}^{(i)}$$
に更新されます。このとき、パーセプトロンの（定義関数$\boldsymbol{1}$に通す前の）出力は
$$(\boldsymbol{w}-\boldsymbol{x}^{(i)})^\top\boldsymbol{x}^{(i)}
=\boldsymbol{w}^\top\boldsymbol{x}^{(i)}-||\boldsymbol{x}^{(i)}||^2$$
となります。$||\boldsymbol{x}^{(i)}||^2\geq0$なので、更新前と同じか小さい値が出るはずです。
よってパーセプトロンの出力としては0が出やすくなり、同じ誤りが起こりにくくなることが示されました。

# 問2
> ロジスティック回帰モデルを確率的勾配降下法で学習するため，重みベクトル$\boldsymbol{w}$に関する更新式を式(2.38)に導出した．同様の方法で，バイアス$b$に関する更新式を導出せよ．

式(2.30)にあるように、ロジスティック回帰モデルのバイアス更新式は
$$
b^{(t+1)} = b^{(t)} + \eta_t \left.\frac{\partial\log l_{(\boldsymbol{x},y)}(\boldsymbol{w},b)}{\partial b}\right|\_{(\boldsymbol{w},b)=(\boldsymbol{w}^{(t)},b^{(t)})}\tag{2.30}
$$
です。ここで$l_{(\boldsymbol{x},y)}(\boldsymbol{w},b)$は尤度で、今回の場合
$$
l_{(\boldsymbol{x},y)}(\boldsymbol{w},b) = p^y(1-p)^{1-y}
$$
です（$p\equiv\sigma(\boldsymbol{w^\top x}+b)$とおいた）。自然対数を取った
$$
\log l_{(\boldsymbol{x},y)}(\boldsymbol{w},b) = y\log p + (1-y)\log(1-p)
$$
を$b$で偏微分します。合成関数の微分なのでChain-Ruleを使います。
$$
\begin{align}
\frac{\partial\log l_{(\boldsymbol{x},y)}(\boldsymbol{w},b)}{\partial b} &= y\frac{\partial\log p}{\partial p}\frac{\partial p}{\partial b} + (1-y)\frac{\partial\log(1-p)}{\partial p}\frac{\partial p}{\partial b}\\\\
&= y\frac{1}{p}\frac{\partial}{\partial b}\left(\frac{1}{1+\exp(-\boldsymbol{w^\top x}-b)}\right) - (1-y)\frac{1}{1-p}\frac{\partial}{\partial b}\left(\frac{1}{1+\exp(-\boldsymbol{w^\top x}-b)}\right)\\\\
&= \frac{y-p}{p(1-p)}\frac{\partial}{\partial b}\left(\frac{1}{1+\exp(-\boldsymbol{w^\top x}-b)}\right)\\\\
&= \frac{y-p}{p(1-p)}\frac{\exp(-\boldsymbol{w^\top x}-b)}{\set{1+\exp(-\boldsymbol{w^\top x}-b)}^2}\\\\
&= \frac{y-p}{p(1-p)}\frac{1+\exp(-\boldsymbol{w^\top x}-b)-1}{\set{1+\exp(-\boldsymbol{w^\top x}-b)}^2}\\\\
&= \frac{y-p}{p(1-p)}\left(\frac{1+\exp(-\boldsymbol{w^\top x}-b)}{\set{1+\exp(-\boldsymbol{w^\top x}-b)}^2} - \frac{1}{\set{1+\exp(-\boldsymbol{w^\top x}-b)}^2}\right)\\\\
&= \frac{y-p}{p(1-p)}\left(\frac{1}{1+\exp(-\boldsymbol{w^\top x}-b)} - \left(\frac{1}{1+\exp(-\boldsymbol{w^\top x}-b)}\right)^2\right)\\\\
&= \frac{y-p}{p(1-p)}(p - p^2)\\\\
&= y-p
\end{align}
$$
よって、式(2.30)に代入して
$$
b^{(t+1)} = b^{(t)} + \eta_t(y-p^{(t)})
$$


# 問3
> 式(2.55)を導出せよ．
> $$\begin{align}
l_{(\boldsymbol{x},\boldsymbol{y})}(\boldsymbol{W},\boldsymbol{b}) = \prod_{k=1}^{K}p_k^{y_k}\tag{2.53}
\\\\
\frac{\partial\log l_{(\boldsymbol{x},\boldsymbol{y})}(\boldsymbol{W},\boldsymbol{b})}{\partial\boldsymbol{w}_j} &= (y_j - p_j)\boldsymbol{x} \tag{2.55}
\end{align}
$$
:::note warn
テキストの式(2.55)の左辺は
$$\frac{\partial l_{(\boldsymbol{x},\boldsymbol{y})}(\boldsymbol{W},\boldsymbol{b})}{\partial\boldsymbol{w}_j}$$

となっていますが、正しくは

$$\frac{\partial\log l_{(\boldsymbol{x},\boldsymbol{y})}(\boldsymbol{W},\boldsymbol{b})}{\partial\boldsymbol{w}_j}$$

です（第1版第4刷現在）。

※追記：出版社に問い合わせたところ、誤りであることが確認できました。次回重版（増刷）時に修正されるとのことです。
:::

ただの計算です。Chain-Ruleを使って頑張ります。
$$
\begin{align}
\frac{\partial\log l_{(\boldsymbol{x},\boldsymbol{y})}(\boldsymbol{W},\boldsymbol{b})}{\partial\boldsymbol{w}_j} &= \frac{\partial}{\partial\boldsymbol{w}_j}\log\prod _{k=1}^{K}p_k^{y_k}\\\\
&= \sum _{k=1}^{K}y_k \frac{\partial\log p_k}{\partial\boldsymbol{w}_j}\\\\
&= \sum _{k=1}^{K}y_k \frac{\partial\log p_k}{\partial p_k}\frac{\partial p_k}{\partial\boldsymbol{w}_j}\quad(\because\text{chain rule})\\\\
&= \sum _{k=1}^{K}y_k \frac{\partial\log p_k}{\partial p_k}\frac{\partial p_k}{\partial a_j}\frac{\partial a_j}{\partial\boldsymbol{w}_j}\quad(\because\text{chain rule})\tag{\*}
\end{align}
$$
和の中の偏微分を計算していきます。1つ目は簡単で、
$$
\frac{\partial\log p_k}{\partial p_k} = \frac{1}{p_k}\tag{a}
$$
です。2つ目は$p_k=\text{softmax}(a_1,\cdots,a_K)_k=\frac{\exp{a_k}}{\sum _{i=1}^{K}\exp(a_i)}$に注意すると、$k\ne j$ならば
$$
\begin{align}
\frac{\partial p_k}{\partial a_j} &= \frac{\partial}{\partial a_j}\left(\frac{\exp{a_k}}{\sum _{i=1}^{K}\exp(a_i)}\right)\\\\
&= - \frac{\exp{a_k}\exp{a_j}}{\left(\sum _{i=1}^{K}\exp(a_i)\right)^2}\\\\
&= - \frac{\exp{a_k}}{\sum _{i=1}^{K}\exp(a_i)}\cdot \frac{\exp{a_j}}{\sum _{i=1}^{K}\exp(a_i)}\\\\
&= -p_kp_j\tag{b1}
\end{align}
$$
一方、$k=j$ならば
$$
\begin{align}
\frac{\partial p_j}{\partial a_j} &= \frac{\partial}{\partial a_j}\left(\frac{\exp{a_j}}{\sum _{i=1}^{K}\exp(a_i)}\right)\\\\
&= \frac{\exp{a_j}\sum _{i=1}^{K}\exp(a_i)-(\exp{a_j})^2}{\left(\sum _{i=1}^{K}\exp(a_i)\right)^2}\\\\
&= \frac{\exp{a_j}}{\sum _{i=1}^{K}\exp(a_i)} - \left(\frac{\exp{a_j}}{\sum _{i=1}^{K}\exp(a_i)}\right)^2\\\\
&= p_j - p_j^2\tag{b2}
\end{align}
$$
となります。3つ目は$a_j=(\boldsymbol{Wx+b})_j=\boldsymbol{w}_j^\top\boldsymbol{x}+b_j$に注意すると、
$$
\frac{\partial a_j}{\partial\boldsymbol{w}_j} = \boldsymbol{x}\tag{c}
$$

です。よって、(a)～(c)を(*)へ代入して
$$
\begin{align}
\frac{\partial\log l_{(\boldsymbol{x},\boldsymbol{y})}(\boldsymbol{W},\boldsymbol{b})}{\partial\boldsymbol{w}_j} &= -\sum _{k\ne j}\frac{y_k}{p_k}p_kp_j\boldsymbol{x} + \frac{y_j}{p_j}(p_j-p_j^2)\boldsymbol{x}\\\\
&= -\sum _{k\ne j}y_kp_j\boldsymbol{x} + y_j\boldsymbol{x}-y_jp_j\boldsymbol{x}\\\\
&= -\sum _{k=1}^{K}y_kp_j\boldsymbol{x} + y_j\boldsymbol{x}\\\\
&= (y_j - p_j) \boldsymbol{x} \quad(\because \sum _{k=1}^{K}y_k=1)
\end{align}
$$
最後は$y$がOne-Hotベクトルであることをもちいています。


# 問4
> 2値分類タスクの評価指標としては，本章で説明した適合率や再現率以外にもさまざまな指標が存在する．代表的な指標を挙げ，表2.3の例を用いてその評価指標の値を計算せよ．

テキストの略解では「誤り率」「特異度」が挙げられています。他にも「偽陽性率」「偽陰性率」などで評価する場合もあるでしょう。

偽陰性率は
$$\frac{\text{FN}}{\text{TP}+\text{FN}}$$

偽陽性率は
$$\frac{\text{FP}}{\text{FP}+\text{TN}}$$

です。表2.3の例では、偽陰性率=15/(20+15)≈0.429、偽陽性率=5/(5+60)≈0.0769となります。

# 問5
> 排他的論理和(XOR)の入出力を再現できる2層のニューラルネットワークの構成例を示せ．

排他的論理和（XOR）の入出力の関係は以下の表のとおりです。

| $x_1$ | $x_2$ | $y$ |
|:-:|:-:|:-:|
| 0 | 0 | 0 |
| 1 | 0 | 1 |
| 0 | 1 | 1 |
| 1 | 1 | 0 |

この入出力を再現できる2層のニューラルネットワークは無限に存在するのですが、その一例が略解にあります。

ここでは別のアプローチとして、PyTorchを使って最適化を行って構成してみます。

```python:train.py
import torch
from torch import nn
import random
import numpy as np
import os

def set_seed(seed):
    """乱数シードを固定する関数"""
    random.seed(seed)
    np.random.seed(seed)
    os.environ["PYTHONHASHSEED"] = str(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False

class XOR(nn.Module):
    def __init__(self, hidden_size):
        super().__init__()
        self.fc1 = nn.Linear(2, hidden_size)
        self.fc2 = nn.Linear(hidden_size, 1)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        out = self.sigmoid(self.fc1(x))
        out = self.sigmoid(self.fc2(out))
        return out

set_seed(1234)
model = XOR(hidden_size=2)
loss_fn = nn.BCELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.1)

X = torch.tensor(
    [[0.,0.], 
     [1.,0.],
     [0.,1.], 
     [1.,1.]])
y = torch.tensor([0.,1.,1.,0.]).reshape(-1,1)

for i in range(100):
    optimizer.zero_grad()
    output = model(X)
    loss = loss_fn(output, y)
    loss.backward()
    optimizer.step()
```

これを実行すると、XORを再現する2層のニューラルネットワークが学習されます。
モデルの出力を見てみましょう。

```python
print(model(X))
# tensor([[0.0189],
#         [0.9753],
#         [0.9746],
#         [0.0374]], grad_fn=<SigmoidBackward0>)
```
無事に再現出来ているようです。次に、パラメータを見てみましょう。

```python
print(model.state_dict())
# OrderedDict([('fc1.weight',
#               tensor([[-7.5294, -7.2618],
#                       [-5.3188, -5.3107]])),
#              ('fc1.bias', tensor([3.1400, 7.7734])),
#              ('fc2.weight', tensor([[-8.7285,  8.1103]])),
#              ('fc2.bias', tensor([-3.6877]))])
```
数式で表すと、次のようになります。
$$
\begin{align}
\boldsymbol{h} &= 
\sigma\left(
\begin{pmatrix}
-7.5294 & -7.2618\\\\
-5.3188 & -5.3107
\end{pmatrix}
\boldsymbol{x} + 
\begin{pmatrix}
3.1400\\\\
7.7734
\end{pmatrix}
\right)\\\\
y &= 
\sigma(
\begin{pmatrix}
-8.7285 & 8.1103
\end{pmatrix}
\boldsymbol{h} + -3.6877)
\end{align}
$$


# 問6
> 図2.10の計算グラフにおいて，入力が$(x,y,z)=(3,-1,-2)$の場合の関数の勾配を計算グラフを利用して求めよ．

計算グラフを用いるやり方はテキストの略解を参照すればわかるでしょう。計算グラフの計算結果が正しいかだけ、確認しておきましょう。

関数$f(x,y,z)=(x+y)z$の勾配は
$$
\left(\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}, \frac{\partial f}{\partial z}\right)^\top = (z, z, x+y)^\top
$$
と書けるので、これに$(x,y,z)=(3,-1,-2)$を代入すれば
$$(-2,-2,2)^\top$$