---
# System prepended metadata

title: 3.6.4～3.6.8_周哲煒

---

<!--
{%hackmd dI-J6ApjSiWhbAt2wrqCDA %}
-->

# 3.6.4~3.6.8
p.3-92~3-106

## 3.6.4多分類交叉熵損失
- 樣本屬於每個分類的機率 : ($f_1^{(i)},f_2^{(i)},...,f_c^{(i)}$)  
- 樣本屬於目標分類$y^{(i)}$的機率 : $f_{y^{(i)}}^{(i)}$  
- m個樣本($x^i$,$y^i$)均以他們對應的目標分類出現的機率 : $\prod^m_{i=1}{f^{(i)}_{y^{(i)}}}$  
- 最佳參數: $W$ 使這m個樣本有最大的正確出現的機率  
:::warning
但${\prod}$容易使數值快速的趨近無限大或0  
-->改成求代價函數(上面機率的負對數的平均值)  
:::
- 代價函數 : $L(W)=-{\frac {1}{m}}{\sum^m_{i=1}{\log{(f^{(i)}_{y^{(i)}})}}}$  
- 其中"$-\log{(f^{(i)}_{y^{(i)}})}$"稱為交叉熵損失  
- 問題從求$\prod^m_{i=1}{f^{(i)}_{y^{(i)}}}$最大，變成求${-\log{(f^{(i)}_{y^{(i)}})}}$最小的問題  
---

<!-- 矩陣的打法
https://hackmd.io/@sysprog/B1RwlM85Z
-->

舉例:
2個樣本(m=2)，對應的機率矩陣F、目標向量y如下
$F=\left[
\begin{array}{ccc}
0.2&0.5&0.3\\
0.2&0.6&0.2\\
\end{array}
\right]$ , $y=\left[
\begin{array}{ccc}
2\\
1\\
\end{array}
\right]$
則$F_y=\left[
\begin{array}{c}
0.3\\
0.6\\
\end{array}
\right]$
因此其平均交叉商損失為:$L(W)=-\frac{1}{2}(\log(0.3)+\log(0.6))$

計算程式如下
```python=
import numpy as np
def cross_entropy(F,y):
    m = len(F) #y.shape[0]
    log_Fy = -np.log(F[range(m),y]) #o.s. 什麼神奇的寫法
    return np.sum(log_Fy)/m

F = np.array([[0.2,0.5,0.3],[0.2,0.6,0.2]])
y = np.array([2,1])

print(cross_entropy(F,y))
```
若是用one-hot vector表示$y^{(i)}$，程式如下
```python=
import numpy as np
def cross_entropy_one_hot(F,y):
    m=len(F)
    return -np.sum(Y*np.log(F)) #-(1.0/m)*np.sum(np.multiply(y,np.log(F)))

F = np.array([[0.2,0.5,0.3],[0.2,0.6,0.2]])
y = np.array([[0,0,1],[0,1,0]])

print(cross_entropy_one_hot(F,y))
```
### one-hot encoding
![](https://hackmd.io/_uploads/B14kDukA3.png)

[可能有用的補充](https://axk51013.medium.com/%E4%B8%8D%E8%A6%81%E5%86%8D%E5%81%9Aone-hot-encoding-b5126d3f8a63)
## 3.6.5透過加權和計算交叉熵損失
- 一個樣本的加權和**z**的softmax函數的輸出就是機率$f$
```python=
#https://www.parasdahal.com/softmax-crossentropy
def softmax(Z):
    A = np.exp(Z-np.max(Z,axis=1,keepdims=True))
    return A/np.sum(A,axis=1,keepdims=True)

def softmax_cross_entropy(Z,y):
    m = len(Z)
    F = softmax(Z)
    log_Fy = -np.log(F[range(m),y])
    return np.sum(log_Fy)/m
```
舉例:
```python=+
Z = np.array([[2,25,13],[54,3,11]])
y = np.array([2,1])
print(softmax_cross_entropy(Z,y))
```
若目標向量是one-hot型式:
```python=
def softmax(Z):
    A = np.exp(Z-np.max(Z,axis=1,keepdims=True))
    return A/np.sum(A,axis=1,keepdims=True)

def softmax_cross_entropy_one_hot(Z,y):
    F = softmax(Z)
    loss = -np.sum(y*np.log(F),axis=1)
    return np.mean(loss)
```
舉例:
```python=+
Z = np.array([[2,25,13],[54,3,11]])
y = np.array([[0,0,1],[0,1,0]])
print(softmax_cross_entropy_one_hot(Z,y))
```

## 3.6.6 softmax回歸的梯度計算
- 目標: 求解使交叉熵損失$\mathcal L(W)$最小的$W$
- 方法 : 一樣是梯度下降法
- 需計算${\mathcal L(W)}$關於W的梯度(關於$W_{jk}$的偏導數)
### 1.交叉商損失關於加權和的梯度
推導:[p.3-97]()
程式:
```python=
def grad_softmax_crossentropy(Z,y):
    F = softmax(Z)
    I_i = np.zeros_like(Z)
    I_i[np.arrange(len(Z)),y] = 1
    return (F - I_i)/Z.shape[0]
def grad_softmax_cross_entropy(Z,y):
    m = len(Z)
    F = softmax(Z)
    F[range(m),y] -=1
    return F/m
```
舉例:
```python=+
Z = np.array([[2,25,13],[54,3,11]])
y = np.array([2,1])
print(grad_softmax_cross_entropy(Z,y))
```
數值梯度函數的程式(用以確認分析梯度是正確的)
```python=
def loss_f():
    return softmax_cross_entropy(Z,y)

import util 
Z = Z.astype(float)#注意:必須將整數陣列換成float型態
print("num_grad",util.numerical_gradient(loss_f,[Z]))
```

### 2.交叉熵損失關於權值參數的梯度
推導: [p.3-99]()
程式:
X表示   資料特徵矩陣
y表示   目標特徵值向量
reg表示 正則化參數
```python=
def gradient_softmax(W,X,y,reg):
    m = len(X)
    Z = np.dot(X,W)
    I_i = np.zeros_like(Z)
    I_i[np.arrange(len(Z)),y] = 1
    F = softmax(Z)
    #F = np.exp(Z)/np.exp(Z).sum(axis=1,keepdoms=True)
    grad = (1/m)*np.dot(X.T,F - I_i) #Z.shape[0]
    grad = grad +2*reg*W
    return grad

def loss_softmax(W,X,y,reg):
    m = len(X)
    Z = np.dot(X,W)
    Z_i_y_i = Z[np.arrange(len(Z)),y]
    negtive_log_prob = - Z_i_y_i + np.log(np.sum(np.exp(Z),axis=1))
    loss = np.mean(negtive_log_prob)+reg*np.sum(W*W)
    return loss
```
測試一下:
```python=+
X = np.array([[2,3],[4,5]])
y = np.array([2,1])
W = np.array([[0.1,0.2,0.3],[0.4,0.2,0.8]])

reg = 0.2

print(gradient_softmax(W,X,y,reg))
print(loss_softmax(W,X,y,reg))
```
若用one-hot表示 code在[p.3-102]()

## 3.6.7 softmax回歸的梯度下降法實現 p.3-103
```python=
def gradient_descent_softmax(x,X,y,reg=0.0,alpha=0.01,iterations=100,gamma=0.8,epsilon=1e-8):
    X = np.hstack((np.ones((X.shape[0],1),dtype=X.dtype),X)) #增加一列特徵 "1"
    v= np.zeros_like(w)
    #losses = []
    w_history=[]
    for i in range(0,iterations):
        gradient = gradient_softmax(w,X,y,reg)
        if np.max(np.abs(gradient))<epsilon:
            print("gradient is small enough!")
            print("iterated num is: ",i)
            break
        
        w = w - (alpha*gradient)
        #v = gamma*v+alpha*gradientz
        #w= w-v
        #losses.append(loss)
        w_history.append(w)
    return w_history
```

## 3.6.8 spiral 資料集的softmax回歸模型
對三分類資料及spiral訓練一個softmax回歸模型:
```python=
X_spiral,y_spiral = gen_spiral_dataset()
X = X_spiral
y = y_spiral
alpha = 1e-0
iteration = 200
reg = 1e-3

w = np.zeros([X.shape[1]+1,len(np.unique(y))])
w_history = gradient_descent_softmax(w,X,y,reg,alpha,iterations)
w = w_history[-1]
print("w: ",w)
loss_history = compute_loss_history(w_history,X,y,reg)
print(loss_history[:-1:len(loss_history)//10])
plt.plot(loss_history,color='r')
```
計算訓練模型在一批資料(X,y)上的預測準確性
```python=
def getAccuracy(w,X,y):
    X = np.hstack((np.ones((X.shape[0],1)dtype=X.dtype),X)) #增加一列特徵"1"
    probs = softmax(np.dot(X,w))
    predicts = np.argmax(probs,axis=1)
    accuracy = sum(predicts ==y)/(float(len(y)))
    return accuracy
```
使用
```python=+
getAccuracy(w,X_spiral,y_spiral)
```

繪製softmax模型的分類邊界
```python=
#plot the resulting classifier
h = 0.02
x_min, x_max = X[:,0].min()-1,X[:,0].max()+1
y_min, y_max = X[:,1].min()-1,X[:,1].max()+1
xx,yy = np.meshgrid(np.arrange(x_main,x_max,h), np.arrange(y_min,y_max,h))

z=np.dot(np.c_[np.ones(xx.size),xx.ravel(),yy.ravel()],w)
Z = np.argmax(Z,axis=1)
Z = Z.reshape(xx.shape)
fig = plt.figure()
plt.contourf(xx,yy,Z,camp=plt.cm.Spectral,alpha=0.3)
plt.scatter(X[:,0],X[:,1],c=y,s=40,cmap=plt.cm.Spectral)
plt.xlim(xx.min(),xx.max())
plt.ylim(yy.min(),yy.max())
#fig.savefig('spiral_linear.png')
```