del something (#814)
This commit is contained in:
Родитель
7fa3e1eb81
Коммит
a6d46f34f9
|
@ -3,203 +3,16 @@
|
|||
|
||||
## 5.1 正规方程解法
|
||||
|
||||
英文名是 Normal Equations。
|
||||
|
||||
对于线性回归问题,除了前面提到的最小二乘法可以解决一元线性回归的问题外,也可以解决多元线性回归问题。
|
||||
|
||||
对于多元线性回归,可以用正规方程来解决,也就是得到一个数学上的解析解。它可以解决下面这个公式描述的问题:
|
||||
|
||||
$$y=a_0+a_1x_1+a_2x_2+\dots+a_kx_k \tag{1}$$
|
||||
以下为本小节目录,详情请参阅《智能之门》正版图书,高等教育出版社。
|
||||
|
||||
### 5.1.1 简单的推导方法
|
||||
|
||||
在做函数拟合(回归)时,我们假设函数 $H$ 为:
|
||||
|
||||
$$H(w,b) = b + x_1 w_1+x_2 w_2+ \dots +x_n w_n \tag{2}$$
|
||||
|
||||
令 $b=w_0$,则:
|
||||
|
||||
$$H(W) = w_0 + x_1 \cdot w_1 + x_2 \cdot w_2 + \dots + x_n \cdot w_n\tag{3}$$
|
||||
|
||||
公式3中的 $x$ 是一个样本的 $n$ 个特征值,如果我们把 $m$ 个样本一起计算,将会得到下面这个矩阵:
|
||||
|
||||
$$H(W) = X \cdot W \tag{4}$$
|
||||
|
||||
公式5中的 $X$ 和 $W$ 的矩阵形状如下:
|
||||
|
||||
$$
|
||||
X =
|
||||
\begin{pmatrix}
|
||||
1 & x_{1,1} & x_{1,2} & \dots & x_{1,n} \\\\
|
||||
1 & x_{2,1} & x_{2,2} & \dots & x_{2,n} \\\\
|
||||
\vdots & \vdots & \vdots & \ddots & \vdots \\\\
|
||||
1 & x_{m,1} & x_{m,2} & \dots & x_{m,n}
|
||||
\end{pmatrix} \tag{5}
|
||||
$$
|
||||
|
||||
$$
|
||||
W= \begin{pmatrix}
|
||||
w_0 \\\\
|
||||
w_1 \\\\
|
||||
\vdots \\\\
|
||||
w_n
|
||||
\end{pmatrix} \tag{6}
|
||||
$$
|
||||
|
||||
然后我们期望假设函数的输出与真实值一致,则有:
|
||||
|
||||
$$H(W) = X \cdot W = Y \tag{7}$$
|
||||
|
||||
其中,Y的形状如下:
|
||||
|
||||
$$
|
||||
Y= \begin{pmatrix}
|
||||
y_1 \\\\
|
||||
y_2 \\\\
|
||||
\vdots \\\\
|
||||
y_m
|
||||
\end{pmatrix} \tag{8}
|
||||
$$
|
||||
|
||||
|
||||
直观上看,$W = Y/X$,但是这里三个值都是矩阵,而矩阵没有除法,所以需要得到 $X$ 的逆矩阵,用 $Y$ 乘以 $X$ 的逆矩阵即可。但是又会遇到一个问题,只有方阵才有逆矩阵,而 $X$ 不一定是方阵,所以要先把左侧变成方阵,就可能会有逆矩阵存在了。所以,先把等式两边同时乘以 $X$ 的转置矩阵,以便得到 $X$ 的方阵:
|
||||
|
||||
$$X^{\top} X W = X^{\top} Y \tag{9}$$
|
||||
|
||||
其中,$X^{\top}$ 是 $X$ 的转置矩阵,$X^{\top}X$ 一定是个方阵,并且假设其存在逆矩阵,把它移到等式右侧来:
|
||||
|
||||
$$W = (X^{\top} X)^{-1}{X^{\top} Y} \tag{10}$$
|
||||
|
||||
至此可以求出 $W$ 的正规方程。
|
||||
|
||||
### 5.1.2 复杂的推导方法
|
||||
|
||||
我们仍然使用均方差损失函数(略去了系数$\frac{1}{2m}$):
|
||||
|
||||
$$J(w,b) = \sum_{i=1}^m (z_i - y_i)^2 \tag{11}$$
|
||||
|
||||
把 $b$ 看作是一个恒等于 $1$ 的feature,并把 $Z=XW$ 计算公式带入,并变成矩阵形式:
|
||||
|
||||
$$J(W) = \sum_{i=1}^m \left(\sum_{j=0}^nx_{ij}w_j -y_i\right)^2=(XW - Y)^{\top} \cdot (XW - Y) \tag{12}$$
|
||||
|
||||
对 $W$ 求导,令导数为 $0$,可得到 $W$ 的最小值解:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
\frac{\partial J(W)}{\partial W} &= \frac{\partial}{\partial W}[(XW - Y)^{\top} \cdot (XW - Y)] \\\\
|
||||
&=\frac{\partial}{\partial W}[(W^{\top}X^{\top} - Y^{\top}) \cdot (XW - Y)] \\\\
|
||||
&=\frac{\partial}{\partial W}[(W^{\top}X^{\top}XW -W^{\top}X^{\top}Y - Y^{\top}XW + Y^{\top}Y)]
|
||||
\end{aligned}
|
||||
\tag{13}
|
||||
$$
|
||||
|
||||
求导后(请参考矩阵/向量求导公式):
|
||||
|
||||
第一项的结果是:$2X^{\top}XW$(分母布局,denominator layout)
|
||||
|
||||
第二项的结果是:$X^{\top}Y$(分母布局方式,denominator layout)
|
||||
|
||||
第三项的结果是:$X^{\top}Y$(分子布局方式,numerator layout,需要转置$Y^{\top}X$)
|
||||
|
||||
第四项的结果是:$0$
|
||||
|
||||
再令导数为 $0$:
|
||||
|
||||
$$
|
||||
\frac{\partial J}{\partial W}=2X^{\top}XW - 2X^{\top}Y=0 \tag{14}
|
||||
$$
|
||||
|
||||
$$
|
||||
X^{\top}XW = X^{\top}Y \tag{15}
|
||||
$$
|
||||
|
||||
$$
|
||||
W=(X^{\top}X)^{-1}X^{\top}Y \tag{16}
|
||||
$$
|
||||
|
||||
结论和公式10一样。
|
||||
|
||||
以上推导的基本公式可以参考第0章的公式60-69。
|
||||
|
||||
逆矩阵 $(X^{\top}X)^{-1}$ 可能不存在的原因是:
|
||||
|
||||
1. 特征值冗余,比如 $x_2=x^2_1$,即正方形的边长与面积的关系,不能作为两个特征同时存在;
|
||||
2. 特征数量过多,比如特征数 $n$ 比样本数 $m$ 还要大。
|
||||
|
||||
以上两点在我们这个具体的例子中都不存在。
|
||||
|
||||
### 5.1.3 代码实现
|
||||
|
||||
我们把表5-1的样本数据带入方程内。根据公式(5),我们应该建立如下的 $X,Y$ 矩阵:
|
||||
|
||||
$$
|
||||
X = \begin{pmatrix}
|
||||
1 & 10.06 & 60 \\\\
|
||||
1 & 15.47 & 74 \\\\
|
||||
1 & 18.66 & 46 \\\\
|
||||
1 & 5.20 & 77 \\\\
|
||||
\vdots & \vdots & \vdots \\\\
|
||||
\end{pmatrix} \tag{17}
|
||||
$$
|
||||
|
||||
$$
|
||||
Y= \begin{pmatrix}
|
||||
302.86 \\\\
|
||||
393.04 \\\\
|
||||
270.67 \\\\
|
||||
450.59 \\\\
|
||||
\vdots \\\\
|
||||
\end{pmatrix} \tag{18}
|
||||
$$
|
||||
|
||||
根据公式(10):
|
||||
|
||||
$$W = (X^{\top} X)^{-1}{X^{\top} Y} \tag{19}$$
|
||||
|
||||
1. $X$ 是 $1000\times 3$ 的矩阵,$X$ 的转置是 $3\times 1000$,$X^{\top}X$ 生成 $3\times 3$ 的矩阵;
|
||||
2. $(X^{\top}X)^{-1}$ 也是 $3\times 3$ 的矩阵;
|
||||
3. 再乘以 $X^{\top}$,即 $3\times 3$ 的矩阵乘以 $3\times 1000$ 的矩阵,变成 $3\times 1000$ 的矩阵;
|
||||
4. 再乘以 $Y$,$Y$是 $1000\times 1$ 的矩阵,所以最后变成 $3\times 1$ 的矩阵,成为 $W$ 的解,其中包括一个偏移值 $b$ 和两个权重值 $w$,3个值在一个向量里。
|
||||
|
||||
```Python
|
||||
if __name__ == '__main__':
|
||||
reader = SimpleDataReader()
|
||||
reader.ReadData()
|
||||
X,Y = reader.GetWholeTrainSamples()
|
||||
num_example = X.shape[0]
|
||||
one = np.ones((num_example,1))
|
||||
x = np.column_stack((one, (X[0:num_example,:])))
|
||||
a = np.dot(x.T, x)
|
||||
# need to convert to matrix, because np.linalg.inv only works on matrix instead of array
|
||||
b = np.asmatrix(a)
|
||||
c = np.linalg.inv(b)
|
||||
d = np.dot(c, x.T)
|
||||
e = np.dot(d, Y)
|
||||
#print(e)
|
||||
b=e[0,0]
|
||||
w1=e[1,0]
|
||||
w2=e[2,0]
|
||||
print("w1=", w1)
|
||||
print("w2=", w2)
|
||||
print("b=", b)
|
||||
# inference
|
||||
z = w1 * 15 + w2 * 93 + b
|
||||
print("z=",z)
|
||||
```
|
||||
|
||||
### 5.1.4 运行结果
|
||||
|
||||
```
|
||||
w1= -2.0184092853092226
|
||||
w2= 5.055333475112755
|
||||
b= 46.235258613837644
|
||||
z= 486.1051325196855
|
||||
```
|
||||
|
||||
我们得到了两个权重值和一个偏移值,然后得到房价预测值 $z=486$ 万元。
|
||||
|
||||
至此,我们得到了解析解。我们可以用这个做为标准答案,去验证我们的神经网络的训练结果。
|
||||
|
||||
### 代码位置
|
||||
|
||||
ch05, Level1
|
||||
|
|
|
@ -3,415 +3,20 @@
|
|||
|
||||
## 5.2 神经网络解法
|
||||
|
||||
与单特征值的线性回归问题类似,多变量(多特征值)的线性回归可以被看做是一种高维空间的线性拟合。以具有两个特征的情况为例,这种线性拟合不再是用直线去拟合点,而是用平面去拟合点。
|
||||
以下为本小节目录,详情请参阅《智能之门》正版图书,高等教育出版社出版。
|
||||
|
||||
### 5.2.1 定义神经网络结构
|
||||
|
||||
我们定义一个如图5-1所示的一层的神经网络,输入层为2或者更多,反正大于2了就没区别。这个一层的神经网络的特点是:
|
||||
|
||||
1. 没有中间层,只有输入项和输出层(输入项不算做一层);
|
||||
2. 输出层只有一个神经元;
|
||||
3. 神经元有一个线性输出,不经过激活函数处理,即在下图中,经过 $\Sigma$ 求和得到 $Z$ 值之后,直接把 $Z$ 值输出。
|
||||
|
||||
<img src="./img/5/setup.png" ch="500" />
|
||||
|
||||
图5-1 多入单出的单层神经元结构
|
||||
|
||||
与上一章的神经元相比,这次仅仅是多了一个输入,但却是质的变化,即,一个神经元可以同时接收多个输入,这是神经网络能够处理复杂逻辑的根本。
|
||||
|
||||
#### 输入层
|
||||
|
||||
单独看第一个样本是这样的:
|
||||
|
||||
$$
|
||||
x_1 =
|
||||
\begin{pmatrix}
|
||||
x_{11} & x_{12}
|
||||
\end{pmatrix} =
|
||||
\begin{pmatrix}
|
||||
10.06 & 60
|
||||
\end{pmatrix}
|
||||
$$
|
||||
|
||||
$$
|
||||
y_1 = \begin{pmatrix} 302.86 \end{pmatrix}
|
||||
$$
|
||||
|
||||
一共有1000个样本,每个样本2个特征值,X就是一个$1000 \times 2$的矩阵:
|
||||
|
||||
$$
|
||||
X =
|
||||
\begin{pmatrix}
|
||||
x_1 \\\\ x_2 \\\\ \vdots \\\\ x_{1000}
|
||||
\end{pmatrix} =
|
||||
\begin{pmatrix}
|
||||
x_{1,1} & x_{1,2} \\\\
|
||||
x_{2,1} & x_{2,2} \\\\
|
||||
\vdots & \vdots \\\\
|
||||
x_{1000,1} & x_{1000,2}
|
||||
\end{pmatrix}
|
||||
$$
|
||||
|
||||
$$
|
||||
Y =
|
||||
\begin{pmatrix}
|
||||
y_1 \\\\ y_2 \\\\ \vdots \\\\ y_{1000}
|
||||
\end{pmatrix}=
|
||||
\begin{pmatrix}
|
||||
302.86 \\\\ 393.04 \\\\ \vdots \\\\ 450.59
|
||||
\end{pmatrix}
|
||||
$$
|
||||
|
||||
|
||||
$x_1$ 表示第一个样本,$x_{1,1}$ 表示第一个样本的一个特征值,$y_1$ 是第一个样本的标签值。
|
||||
|
||||
#### 权重 $W$ 和 $B$
|
||||
|
||||
由于输入层是两个特征,输出层是一个变量,所以 $W$ 的形状是 $2\times 1$,而 $B$ 的形状是 $1\times 1$。
|
||||
|
||||
$$
|
||||
W=
|
||||
\begin{pmatrix}
|
||||
w_1 \\\\ w_2
|
||||
\end{pmatrix}
|
||||
$$
|
||||
|
||||
$$B=(b)$$
|
||||
|
||||
$B$ 是个单值,因为输出层只有一个神经元,所以只有一个bias,每个神经元对应一个bias,如果有多个神经元,它们都会有各自的b值。
|
||||
|
||||
#### 输出层
|
||||
|
||||
由于我们只想完成一个回归(拟合)任务,所以输出层只有一个神经元。由于是线性的,所以没有用激活函数。
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
Z&=
|
||||
\begin{pmatrix}
|
||||
x_{11} & x_{12}
|
||||
\end{pmatrix}
|
||||
\begin{pmatrix}
|
||||
w_1 \\\\ w_2
|
||||
\end{pmatrix}
|
||||
+(b) \\\\
|
||||
&=x_{11}w_1+x_{12}w_2+b
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
写成矩阵形式:
|
||||
|
||||
$$Z = X\cdot W + B$$
|
||||
|
||||
#### 损失函数
|
||||
|
||||
因为是线性回归问题,所以损失函数使用均方差函数。
|
||||
|
||||
$$loss_i(W,B) = \frac{1}{2} (z_i-y_i)^2 \tag{1}$$
|
||||
|
||||
其中,$z_i$ 是样本预测值,$y_i$ 是样本的标签值。
|
||||
|
||||
### 5.2.2 反向传播
|
||||
|
||||
#### 单样本多特征计算
|
||||
|
||||
与上一章不同,本章中的前向计算是多特征值的公式:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
z_i &= x_{i1} \cdot w_1 + x_{i2} \cdot w_2 + b \\\\
|
||||
&=\begin{pmatrix}
|
||||
x_{i1} & x_{i2}
|
||||
\end{pmatrix}
|
||||
\begin{pmatrix}
|
||||
w_1 \\\\
|
||||
w_2
|
||||
\end{pmatrix}+b
|
||||
\end{aligned} \tag{2}
|
||||
$$
|
||||
|
||||
因为 $x$ 有两个特征值,对应的 $W$ 也有两个权重值。$x_{i1}$ 表示第 $i$ 个样本的第 $1$ 个特征值,所以无论是 $x$ 还是 $W$ 都是一个向量或者矩阵了,那么我们在反向传播方法中的梯度计算公式还有效吗?答案是肯定的,我们来一起做个简单推导。
|
||||
|
||||
由于 $W$ 被分成了 $w_1$ 和 $w_2$ 两部分,根据公式1和公式2,我们单独对它们求导:
|
||||
|
||||
$$
|
||||
\frac{\partial loss_i}{\partial w_1}=\frac{\partial loss_i}{\partial z_i}\frac{\partial z_i}{\partial w_1}=(z_i-y_i) \cdot x_{i1} \tag{3}
|
||||
$$
|
||||
$$
|
||||
\frac{\partial loss_i}{\partial w_2}=\frac{\partial loss_i}{\partial z_i}\frac{\partial z_i}{\partial w_2}=(z_i-y_i) \cdot x_{i2} \tag{4}
|
||||
$$
|
||||
|
||||
求损失函数对 $W$ 矩阵的偏导是无法直接求的,所以要变成求各个 $W$ 的分量的偏导。由于 $W$ 的形状是:
|
||||
|
||||
$$
|
||||
W=
|
||||
\begin{pmatrix}
|
||||
w_1 \\\\ w_2
|
||||
\end{pmatrix}
|
||||
$$
|
||||
|
||||
所以求 $loss_i$ 对 $W$ 的偏导,应该这样写:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
\frac{\partial loss_i}{\partial W}&=
|
||||
\begin{pmatrix}
|
||||
\frac{\partial loss_i}{\partial w_1} \\\\
|
||||
\frac{\partial loss_i}{\partial w_2}
|
||||
\end{pmatrix}
|
||||
=\begin{pmatrix}
|
||||
(z_i-y_i) \cdot x_{i1} \\\\
|
||||
(z_i-y_i) \cdot x_{i2}
|
||||
\end{pmatrix} \\\\
|
||||
&=\begin{pmatrix}
|
||||
x_{i1} \\\\
|
||||
x_{i2}
|
||||
\end{pmatrix}
|
||||
(z_i-y_i)
|
||||
=\begin{pmatrix}
|
||||
x_{i1} & x_{i2}
|
||||
\end{pmatrix}^{\top}(z_i-y_i) \\\\
|
||||
&=x_i^{\top}(z_i-y_i)
|
||||
\end{aligned} \tag{5}
|
||||
$$
|
||||
|
||||
$$
|
||||
\frac{\partial loss_i}{\partial B}=z_i-y_i \tag{6}
|
||||
$$
|
||||
|
||||
#### 多样本多特征计算
|
||||
|
||||
当进行多样本计算时,我们用 $m=3$ 个样本做一个实例化推导:
|
||||
|
||||
$$
|
||||
z_1 = x_{11}w_1+x_{12}w_2+b
|
||||
$$
|
||||
|
||||
$$
|
||||
z_2= x_{21}w_1+x_{22}w_2+b
|
||||
$$
|
||||
|
||||
$$
|
||||
z_3 = x_{31}w_1+x_{32}w_2+b
|
||||
$$
|
||||
|
||||
$$
|
||||
J(W,B) = \frac{1}{2 \times 3}[(z_1-y_1)^2+(z_2-y_2)^2+(z_3-y_3)^2]
|
||||
$$
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
\frac{\partial J}{\partial W}&=
|
||||
\begin{pmatrix}
|
||||
\frac{\partial J}{\partial w_1} \\\\
|
||||
\frac{\partial J}{\partial w_2}
|
||||
\end{pmatrix}
|
||||
=\begin{pmatrix}
|
||||
\frac{\partial J}{\partial z_1}\frac{\partial z_1}{\partial w_1}+\frac{\partial J}{\partial z_2}\frac{\partial z_2}{\partial w_1}+\frac{\partial J}{\partial z_3}\frac{\partial z_3}{\partial w_1} \\\\
|
||||
\frac{\partial J}{\partial z_1}\frac{\partial z_1}{\partial w_2}+\frac{\partial J}{\partial z_2}\frac{\partial z_2}{\partial w_2}+\frac{\partial J}{\partial z_3}\frac{\partial z_3}{\partial w_2}
|
||||
\end{pmatrix}
|
||||
\\\\
|
||||
&=\begin{pmatrix}
|
||||
\frac{1}{3}(z_1-y_1)x_{11}+\frac{1}{3}(z_2-y_2)x_{21}+\frac{1}{3}(z_3-y_3)x_{31} \\\\
|
||||
\frac{1}{3}(z_1-y_1)x_{12}+\frac{1}{3}(z_2-y_2)x_{22}+\frac{1}{3}(z_3-y_3)x_{32}
|
||||
\end{pmatrix}
|
||||
\\\\
|
||||
&=\frac{1}{3}
|
||||
\begin{pmatrix}
|
||||
x_{11} & x_{21} & x_{31} \\\\
|
||||
x_{12} & x_{22} & x_{32}
|
||||
\end{pmatrix}
|
||||
\begin{pmatrix}
|
||||
z_1-y_1 \\\\
|
||||
z_2-y_2 \\\\
|
||||
z_3-y_3
|
||||
\end{pmatrix}
|
||||
\\\\
|
||||
&=\frac{1}{3}
|
||||
\begin{pmatrix}
|
||||
x_{11} & x_{12} \\\\
|
||||
x_{21} & x_{22} \\\\
|
||||
x_{31} & x_{32}
|
||||
\end{pmatrix}^{\top}
|
||||
\begin{pmatrix}
|
||||
z_1-y_1 \\\\
|
||||
z_2-y_2 \\\\
|
||||
z_3-y_3
|
||||
\end{pmatrix}
|
||||
\\\\
|
||||
&=\frac{1}{m}X^{\top}(Z-Y)
|
||||
\end{aligned}
|
||||
\tag{7}
|
||||
$$
|
||||
注:3泛化为m。
|
||||
$$
|
||||
\frac{\partial J}{\partial B}=\frac{1}{m}(Z-Y) \tag{8}
|
||||
$$
|
||||
|
||||
### 5.2.3 代码实现
|
||||
|
||||
公式6和第4.4节中的公式5一样,所以我们依然采用第四章中已经写好的`HelperClass`目录中的那些类,来表示我们的神经网络。虽然此次神经元多了一个输入,但是不用改代码就可以适应这种变化,因为在前向计算代码中,使用的是矩阵乘的方式,可以自动适应`x`的多个列的输入,只要对应的`w`的矩阵形状是正确的即可。
|
||||
|
||||
但是在初始化时,我们必须手动指定`x`和`W`的形状,如下面的代码所示:
|
||||
|
||||
```Python
|
||||
if __name__ == '__main__':
|
||||
# net
|
||||
params = HyperParameters(2, 1, eta=0.1, max_epoch=100, batch_size=1, eps = 1e-5)
|
||||
net = NeuralNet(params)
|
||||
net.train(reader)
|
||||
# inference
|
||||
x1 = 15
|
||||
x2 = 93
|
||||
x = np.array([x1,x2]).reshape(1,2)
|
||||
print(net.inference(x))
|
||||
```
|
||||
|
||||
在参数中,指定了学习率`0.1`,最大循环次数`100`轮,批大小`1`个样本,以及停止条件损失函数值`1e-5`。
|
||||
|
||||
在神经网络初始化时,指定了`input_size=2`,且`output_size=1`,即一个神经元可以接收两个输入,最后是一个输出。
|
||||
|
||||
最后的`inference`部分,是把两个条件(15公里,93平方米)代入,查看输出结果。
|
||||
|
||||
在下面的神经网络的初始化代码中,`W`的初始化是根据`input_size`和`output_size`的值进行的。
|
||||
|
||||
```Python
|
||||
class NeuralNet(object):
|
||||
def __init__(self, params):
|
||||
self.params = params
|
||||
self.W = np.zeros((self.params.input_size, self.params.output_size))
|
||||
self.B = np.zeros((1, self.params.output_size))
|
||||
```
|
||||
|
||||
#### 正向计算的代码
|
||||
|
||||
```Python
|
||||
class NeuralNet(object):
|
||||
def __forwardBatch(self, batch_x):
|
||||
Z = np.dot(batch_x, self.W) + self.B
|
||||
return Z
|
||||
```
|
||||
|
||||
#### 误差反向传播的代码
|
||||
|
||||
```Python
|
||||
class NeuralNet(object):
|
||||
def __backwardBatch(self, batch_x, batch_y, batch_z):
|
||||
m = batch_x.shape[0]
|
||||
dZ = batch_z - batch_y
|
||||
dB = dZ.sum(axis=0, keepdims=True)/m
|
||||
dW = np.dot(batch_x.T, dZ)/m
|
||||
return dW, dB
|
||||
```
|
||||
|
||||
### 5.2.4 运行结果
|
||||
|
||||
在Visual Studio 2017中,可以使用Ctrl+F5运行Level2的代码,但是,会遇到一个令人沮丧的打印输出:
|
||||
|
||||
```
|
||||
epoch=0
|
||||
NeuralNet.py:32: RuntimeWarning: invalid value encountered in subtract
|
||||
self.W = self.W - self.params.eta * dW
|
||||
0 500 nan
|
||||
epoch=1
|
||||
1 500 nan
|
||||
epoch=2
|
||||
2 500 nan
|
||||
epoch=3
|
||||
3 500 nan
|
||||
......
|
||||
```
|
||||
|
||||
减法怎么会出问题?什么是`nan`?
|
||||
|
||||
`nan`的意思是数值异常,导致计算溢出了,出现了没有意义的数值。现在是每500个迭代监控一次,我们把监控频率调小一些,再试试看:
|
||||
|
||||
```
|
||||
epoch=0
|
||||
0 10 6.838664338516814e+66
|
||||
0 20 2.665505502247752e+123
|
||||
0 30 1.4244204612680962e+179
|
||||
0 40 1.393993758296751e+237
|
||||
0 50 2.997958629609441e+290
|
||||
NeuralNet.py:76: RuntimeWarning: overflow encountered in square
|
||||
LOSS = (Z - Y)**2
|
||||
0 60 inf
|
||||
...
|
||||
0 110 inf
|
||||
NeuralNet.py:32: RuntimeWarning: invalid value encountered in subtract
|
||||
self.W = self.W - self.params.eta * dW
|
||||
0 120 nan
|
||||
0 130 nan
|
||||
```
|
||||
|
||||
前10次迭代,损失函数值已经达到了`6.83e+66`,而且越往后运行值越大,最后终于溢出了。下面的损失函数历史记录也表明了这一过程。
|
||||
|
||||
<img src="./img/5/wrong_loss.png" ch="500" />
|
||||
|
||||
图5-2 训练过程中损失函数值的变化
|
||||
|
||||
### 5.2.5 寻找失败的原因
|
||||
|
||||
我们可以在`NeuralNet.py`文件中,在图5-3代码行上设置断点,跟踪一下训练过程,以便找到问题所在。
|
||||
|
||||
<img src="./img/5/debug.png" />
|
||||
|
||||
图5-3 在VisualStudio中Debug
|
||||
|
||||
在VS2017中用F5运行debug模式,看第50行的结果:
|
||||
|
||||
```
|
||||
batch_x
|
||||
array([[ 4.96071728, 41. ]])
|
||||
batch_y
|
||||
array([[244.07856544]])
|
||||
```
|
||||
|
||||
返回的样本数据是正常的。再看下一行:
|
||||
|
||||
```
|
||||
batch_z
|
||||
array([[0.]])
|
||||
```
|
||||
|
||||
第一次运行前向计算,由于`W`和`B`初始值都是`0`,所以`z`也是`0`,这是正常的。再看下一行:
|
||||
|
||||
```
|
||||
dW
|
||||
array([[ -1210.80475712],
|
||||
[-10007.22118309]])
|
||||
dB
|
||||
array([[-244.07856544]])
|
||||
```
|
||||
|
||||
`dW`和`dB`的值都非常大,这是因为图5-4所示这行代码。
|
||||
|
||||
<img src="./img/5/backward.png" />
|
||||
|
||||
图5-4 有问题的代码行
|
||||
|
||||
`batch_z`是`0`,`batch_y`是`244.078`,二者相减,是`-244.078`,因此`dB`就是`-244.078`,`dW`因为矩阵乘了`batch_x`,值就更大了。
|
||||
|
||||
再看`W`和`B`的更新值,一样很大:
|
||||
|
||||
```
|
||||
self.W
|
||||
array([[ 121.08047571],
|
||||
[1000.72211831]])
|
||||
self.B
|
||||
array([[24.40785654]])
|
||||
```
|
||||
|
||||
如果`W`和`B`的值很大,那么再下一轮进行前向计算时,会得到更糟糕的结果:
|
||||
|
||||
```
|
||||
batch_z
|
||||
array([[82459.53752331]])
|
||||
```
|
||||
|
||||
果不其然,这次的`z`值飙升到了8万多,如此下去,几轮以后数值溢出是显而易见的事情了。
|
||||
|
||||
那么我们到底遇到了什么情况?
|
||||
|
||||
### 代码位置
|
||||
|
||||
ch05, Level2
|
||||
|
|
|
@ -3,240 +3,24 @@
|
|||
|
||||
## 5.3 样本特征数据标准化
|
||||
|
||||
数据标准化(Normalization),又可以叫做数据归一化。
|
||||
以下为本小节目录,详情请参阅《智能之门》正版图书,高等教育出版社出版。
|
||||
|
||||
### 5.3.1 发现问题的根源
|
||||
|
||||
仔细分析一下屏幕打印信息,前两次迭代的损失值已经是天文数字了,后面的W和B的值也在不断变大,说明网络发散了。难道我们遇到了传说中的梯度爆炸!数值太大,导致计算溢出了。第一次遇到这个情况,但相信不会是最后一次,因为这种情况在神经网络中太常见了。
|
||||
|
||||
回想一个问题:为什么在第4章中,我们没有遇到这种情况?把第4章的数据样本拿来看一看,如表5-4所示。
|
||||
|
||||
表5-4 第4章中的样本数据
|
||||
|
||||
|样本序号|服务器数量(千台)X|空调功率(千瓦)Y|
|
||||
|---|---|---|
|
||||
|1|0.928|4.824|
|
||||
|2|0.469|2.950|
|
||||
|3|0.855|4.643|
|
||||
|...|...|...|
|
||||
|
||||
所有的X值(服务器数量除以1000后的值)都是在 $[0,1]$ 之间的,而本章中的房价数据有两个特征值,一个是公里数,一个是平米数,全都不在 $[0,1]$ 之间,且取值范围不相同。我们不妨把本次样本数据也做一下这样的处理,亦即“标准化”。
|
||||
|
||||
其实,数据标准化是深度学习的必要步骤之一,已经是大师们的必杀技能,也因此它很少被各种博客/文章所提及,以至于初学者们经常被坑。
|
||||
|
||||
根据5.0.1中对数据的初步统计,我们是不是也可以把公里数都除以100,而平米数都除以1000呢?这样可能也会得到 $[0,1]$ 之间的数字?公里数的取值范围是 $[2,22]$,除以100后变成了 $[0.02,0.22]$。平米数的取值范围是 $[40,120]$,除以1000后变成了 $[0.04,0.12]$。
|
||||
|
||||
对本例来说这样做肯定是可以正常工作的,但是下面我们要介绍一种更科学合理的做法。
|
||||
|
||||
### 5.3.2 为什么要做标准化
|
||||
|
||||
理论层面上,神经网络是以样本在事件中的统计分布概率为基础进行训练和预测的,所以它对样本数据的要求比较苛刻。具体说明如下:
|
||||
|
||||
1. 样本的各个特征的取值要符合概率分布,即 $[0,1]$。
|
||||
2. 样本的度量单位要相同。我们并没有办法去比较1米和1公斤的区别,但是,如果我们知道了1米在整个样本中的大小比例,以及1公斤在整个样本中的大小比例,比如一个处于0.2的比例位置,另一个处于0.3的比例位置,就可以说这个样本的1米比1公斤要小。
|
||||
3. 神经网络假设所有的输入输出数据都是标准差为1,均值为0,包括权重值的初始化,激活函数的选择,以及优化算法的设计。
|
||||
|
||||
4. 数值问题
|
||||
|
||||
标准化可以避免一些不必要的数值问题。因为激活函数sigmoid/tanh的非线性区间大约在 $[-1.7,1.7]$。意味着要使神经元有效,线性计算输出的值的数量级应该在1(1.7所在的数量级)左右。这时如果输入较大,就意味着权值必须较小,一个较大,一个较小,两者相乘,就引起数值问题了。
|
||||
|
||||
5. 梯度更新
|
||||
|
||||
若果输出层的数量级很大,会引起损失函数的数量级很大,这样做反向传播时的梯度也就很大,这时会给梯度的更新带来数值问题。
|
||||
|
||||
6. 学习率
|
||||
|
||||
如果梯度非常大,学习率就必须非常小,因此,学习率(学习率初始值)的选择需要参考输入的范围,不如直接将数据标准化,这样学习率就不必再根据数据范围作调整。对 $w_1$ 适合的学习率,可能相对于 $w_2$ 来说会太小,若果使用适合 $w_1$ 的学习率,会导致在 $w_2$ 方向上步进非常慢,从而消耗非常多的时间;而使用适合 $w_2$ 的学习率,对 $w_1$ 来说又太大,搜索不到适合 $w_1$ 的解。
|
||||
|
||||
### 5.3.3 从损失函数等高线图分析标准化的必要性
|
||||
|
||||
在房价数据中,地理位置的取值范围是 $[2,20]$,而房屋面积的取值范围为 $[40,120]$,二者相差太远,放在一起计算会怎么样?
|
||||
|
||||
根据公式$z = x_1 w_1+x_2 w_2 + b$,神经网络想学习 $w_1$ 和 $w_2$,但是数值范围问题导致神经网络来说很难“理解”。图5-5展示了标准化前后的情况损失函数值的等高图,意思是地理位置和房屋面积取不同的值时,作为组合来计算损失函数值时,形成的类似地图的等高图,见图5-5,左侧为标准化前,右侧为标准化后。
|
||||
|
||||
<img src="./img/5/normalize.jpg" />
|
||||
|
||||
图5-5 标准化前后的损失函数等高线图的对比
|
||||
|
||||
房屋面积的取值范围是 $[40,120]$,而地理位置的取值范围是 $[2,20]$,二者会形成一个很扁的椭圆,如左侧。这样在寻找最优解的时候,过程会非常曲折。运气不好的话,根本就没法训练。
|
||||
|
||||
### 5.3.4 标准化的常用方法
|
||||
|
||||
- Min-Max标准化(离差标准化),将数据映射到 $[0,1]$ 区间
|
||||
|
||||
$$x_{new}=\frac{x-x_{min}}{x_{max} - x_{min}} \tag{1}$$
|
||||
|
||||
- 平均值标准化,将数据映射到[-1,1]区间
|
||||
|
||||
$$x_{new} = \frac{x - \bar{x}}{x_{max} - x_{min}} \tag{2}$$
|
||||
|
||||
- 对数转换
|
||||
$$x_{new}=\ln(x_i) \tag{3}$$
|
||||
|
||||
- 反正切转换
|
||||
$$x_{new}=\frac{2}{\pi}\arctan(x_i) \tag{4}$$
|
||||
|
||||
- Z-Score法
|
||||
|
||||
把每个特征值中的所有数据,变成平均值为0,标准差为1的数据,最后为正态分布。Z-Score规范化(标准差标准化 / 零均值标准化,其中std是标准差):
|
||||
|
||||
$$x_{new} = \frac{x_i - \bar{x}}{std} \tag{5}$$
|
||||
|
||||
- 中心化,平均值为0,无标准差要求
|
||||
|
||||
$$x_{new} = x_i - \bar{x} \tag{6}$$
|
||||
|
||||
- 比例法,要求数据全是正值
|
||||
|
||||
$$
|
||||
x_{new} = \frac{x_k}{\sum_{i=1}^m{x_i}} \tag{7}
|
||||
$$
|
||||
|
||||
|
||||
### 5.3.5 如何做数据标准化
|
||||
|
||||
我们再看看样本的数据,表5-5。
|
||||
|
||||
表5-5 房价原始样本数据抽样
|
||||
|
||||
|样本序号|地理位置|居住面积|价格(万元)|
|
||||
|---|---|---|---|
|
||||
|1|10.06|60|302.86|
|
||||
|2|15.47|74|393.04|
|
||||
|3|18.66|46|270.67|
|
||||
|4|5.20|77|450.59|
|
||||
|...|...|...|...|
|
||||
|
||||
按照标准化的定义,我们只要把地理位置列和居住面积列分别做标准化就达到要求了,结果如表5-6。
|
||||
|
||||
表5-6 标准化后的样本数据
|
||||
|
||||
|样本序号|地理位置|居住面积|价格(万元)|
|
||||
|---|---|---|---|
|
||||
|1|0.4033|0.2531|302.86|
|
||||
|2|0.6744|0.4303|393.04|
|
||||
|3|0.8341|0.0759|270.67|
|
||||
|4|0.1592|0.4683|450.59|
|
||||
|...|...|...|...|
|
||||
|
||||
注意:
|
||||
|
||||
1. 我们并没有标准化样本的标签Y数据,所以最后一行的价格还是保持不变
|
||||
2. 我们是对两列特征值分别做标准化处理的
|
||||
|
||||
### 5.3.6 代码实现
|
||||
|
||||
在`HelperClass`目录的`SimpleDataReader.py`文件中,给该类增加一个方法:
|
||||
|
||||
```Python
|
||||
def NormalizeX(self):
|
||||
......
|
||||
```
|
||||
|
||||
返回值`X_new`是标准化后的样本,和原始数据的形状一样。
|
||||
|
||||
再把主程序修改一下,在`ReadData()`方法后,紧接着调用`NormalizeX()`方法:
|
||||
|
||||
```Python
|
||||
if __name__ == '__main__':
|
||||
# data
|
||||
reader = SimpleDataReader()
|
||||
reader.ReadData()
|
||||
reader.NormalizeX()
|
||||
......
|
||||
```
|
||||
|
||||
### 5.3.7 运行结果
|
||||
|
||||
运行上述代码,看打印结果:
|
||||
|
||||
```
|
||||
epoch=9
|
||||
9 0 391.75978721600353
|
||||
9 100 387.79811202735783
|
||||
......
|
||||
9 800 380.78054509441193
|
||||
9 900 575.5617634691969
|
||||
W= [[-41.71417524]
|
||||
[395.84701164]]
|
||||
B= [[242.15205099]]
|
||||
z= [[37366.53336103]]
|
||||
```
|
||||
|
||||
虽然损失函数值没有像我们想象的那样趋近于`0`,但是却稳定在了`400`左右震荡,这也算是收敛!看一下损失函数图像。
|
||||
|
||||
<img src="./img/5/correct_loss.png" ch="500" />
|
||||
|
||||
图5-6 训练过程中损失函数值的变化
|
||||
|
||||
再看看W和B的输出值和z的预测值:
|
||||
|
||||
```
|
||||
w1 = -41.71417524
|
||||
w2 = 395.84701164
|
||||
b = 242.15205099
|
||||
z = 37366.53336103
|
||||
```
|
||||
|
||||
回忆一下正规方程的输出值:
|
||||
|
||||
```
|
||||
w1= -2.0184092853092226
|
||||
w2= 5.055333475112755
|
||||
b= 46.235258613837644
|
||||
z= 486.1051325196855
|
||||
```
|
||||
|
||||
正规方程预测房价结果:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
Z&= -2.018 \times 15 + 5.055 \times 93 + 46.235 \\\\
|
||||
&= 486.105(万元)
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
神经网络预测房价结果:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
Z&= -14.714 \times 15 + 395.847 \times 93 + 242.152 \\\\
|
||||
&= 37366(万元)
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
好吧,我们遇到了天价房!这是怎么回事儿?难道和我们做数据标准化有关系?
|
||||
|
||||
### 5.3.8 工作原理
|
||||
|
||||
在5.0.1中,我们想象神经网络会寻找一个平面,来拟合这些空间中的样本点,是不是这样呢?我们通过下面的函数来实现这个可视化:
|
||||
|
||||
```Python
|
||||
def ShowResult(net, reader):
|
||||
......
|
||||
```
|
||||
|
||||
前半部分代码先是把所有的点显示在三维空间中,我们曾经描述它们像一块厚厚的草坪。后半部分的代码在 $[0,1]$ 空间内形成了一个50乘50的网格,亦即有2500个点,这些点都是有横纵坐标的。然后把这些点送入神经网络中做预测,得到了2500个Z值,相当于第三维的坐标值。最后把这2500个三维空间的点,以网格状显示在空间中,就形成了表5-7的可视化的结果。
|
||||
|
||||
细心的读者可能会问两个问题:
|
||||
|
||||
1. 为什么要在[0,1]空间中形成50乘50的网格呢?
|
||||
2. 50这个数字从哪里来的?
|
||||
|
||||
`NumPy`库的`np.linspace(0,1)`的含义,就是在 $[0,1]$ 空间中生成50个等距的点,第三个参数不指定时,缺省是50。因为我们前面对样本数据做过标准化,统一到了 $[0,1]$ 空间中,这就方便了我们对问题的分析,不用考虑每个特征值的实际范围是多大了。
|
||||
|
||||
表5-7 三维空间线性拟合的可视化
|
||||
|
||||
|正向|侧向|
|
||||
|---|---|
|
||||
|<img src="./img/5/level3_result_1.png"/>|<img src="./img/5/level3_result_2.png"/>|
|
||||
|
||||
从表5-7中的正向图可以看到,真的形成了一个平面;从侧向图可以看到,这个平面也确实穿过了那些样本点,并且把它们分成了上下两个部分。只不过由于训练精度的问题,没有做到平分,而是斜着穿过了点的区域,就好像第4.3节中的那个精度不够的线性回归的结果。
|
||||
|
||||
所以,这就印证了我们在4.3.5中的关于高维空间拟合的说法:在二维平面上是一条拟合直线,在三维空间上是一个拟合平面。每个样本点到这个平面上都有一个无形的作用力(不妨想象为引力),当作用力达到平衡时,也就是平面拟合的最佳位置。
|
||||
|
||||
这下子我们可以大致放心了,神经网络的训练结果并没有错,一定是别的地方出了什么问题。在下一节中我们来一起看看问题出在哪里!
|
||||
|
||||
### 代码位置
|
||||
|
||||
ch05, Level3
|
||||
|
|
|
@ -3,85 +3,15 @@
|
|||
|
||||
## 5.5 正确的推理预测方法
|
||||
|
||||
以下为本小节目录,详情请参阅《智能之门》正版图书,高等教育出版社出版。
|
||||
|
||||
### 5.5.1 预测数据的标准化
|
||||
|
||||
在上一节中,我们在用训练出来的模型预测房屋价格之前,还需要先还原 $W$ 和 $B$ 的值,这看上去比较麻烦,下面我们来介绍一种正确的推理方法。
|
||||
|
||||
既然我们在训练时可以把样本数据标准化,那么在预测时,把预测数据也做相同方式的标准化,不是就可以和训练数据一样进行预测了吗?且慢!这里有一个问题,训练时的样本数据是批量的,至少是成百成千的数量级。但是预测时,一般只有一个或几个数据,如何做标准化?
|
||||
|
||||
我们在针对训练数据做标准化时,得到的最重要的数据是训练数据的最小值和最大值,我们只需要把这两个值记录下来,在预测时使用它们对预测数据做标准化,这就相当于把预测数据“混入”训练数据。前提是预测数据的特征值不能超出训练数据的特征值范围,否则有可能影响准确程度。
|
||||
|
||||
### 5.5.2 代码实现
|
||||
|
||||
基于这种想法,我们先给`SimpleDataReader`类增加一个方法`NormalizePredicateData()`,如下述代码:
|
||||
|
||||
```Python
|
||||
class SimpleDataReader(object):
|
||||
# normalize data by self range and min_value
|
||||
def NormalizePredicateData(self, X_raw):
|
||||
X_new = np.zeros(X_raw.shape)
|
||||
n = X_raw.shape[1]
|
||||
for i in range(n):
|
||||
col_i = X_raw[:,i]
|
||||
X_new[:,i] = (col_i - self.X_norm[i,0]) / self.X_norm[i,1]
|
||||
return X_new
|
||||
```
|
||||
|
||||
`X_norm`数组中的数据,是在训练时从样本数据中得到的最大值最小值,比如表5-11所示的样例。
|
||||
|
||||
表5-11 各个特征值的特征保存
|
||||
|
||||
||最小值|数值范围(最大值减最小值)|
|
||||
|---|---|---|
|
||||
|特征值1|2.02|21.96-2.02=19.94|
|
||||
|特征值2|40|119-40=79|
|
||||
|
||||
所以,最后`X_new`就是按照训练样本的规格标准化好的预测标准化数据,然后我们把这个预测标准化数据放入网络中进行预测:
|
||||
|
||||
```Python
|
||||
import numpy as np
|
||||
from HelperClass.NeuralNet import *
|
||||
|
||||
if __name__ == '__main__':
|
||||
# data
|
||||
reader = SimpleDataReader()
|
||||
reader.ReadData()
|
||||
reader.NormalizeX()
|
||||
# net
|
||||
params = HyperParameters(eta=0.01, max_epoch=100, batch_size=10, eps = 1e-5)
|
||||
net = NeuralNet(params, 2, 1)
|
||||
net.train(reader, checkpoint=0.1)
|
||||
# inference
|
||||
x1 = 15
|
||||
x2 = 93
|
||||
x = np.array([x1,x2]).reshape(1,2)
|
||||
x_new = reader.NormalizePredicateData(x)
|
||||
z = net.inference(x_new)
|
||||
print("Z=", z)
|
||||
```
|
||||
### 5.5.3 运行结果
|
||||
|
||||
```
|
||||
......
|
||||
199 99 380.5942402877278
|
||||
[[-40.23494571] [399.40443921]] [[244.388824]]
|
||||
W= [[-40.23494571]
|
||||
[399.40443921]]
|
||||
B= [[244.388824]]
|
||||
Z= [[486.16645199]]
|
||||
```
|
||||
比较一下正规方程的结果:
|
||||
```
|
||||
z= 486.1051325196855
|
||||
```
|
||||
二者非常接近,可以说这种方法的确很方便,把预测数据看作训练数据的一个记录,先做标准化,再做预测,这样就不需要把权重矩阵还原了。
|
||||
|
||||
看上去我们已经完美地解决了这个问题,但是且慢,仔细看看`loss`值,还有`W`和`B`的值,都是几十几百的数量级,这和神经网络的概率计算的优点并不吻合,实际上它们的值都应该在 $[0,1]$ 之间的。
|
||||
|
||||
大数量级的数据有另外一个问题,就是它的波动有可能很大。目前我们还没有使用激活函数,一旦网络复杂了,开始使用激活函数时,像486.166这种数据,一旦经过激活函数就会发生梯度饱和的现象,输出值总为1,这样对于后面的网络就没什么意义了,因为输入值都是1。
|
||||
|
||||
好吧,看起来问题解决得并不完美,我们看看还能有什么更好的解决方案!
|
||||
|
||||
### 代码位置
|
||||
|
||||
ch05, Level5
|
||||
|
|
|
@ -3,166 +3,14 @@
|
|||
|
||||
## 5.6 对标签值标准化
|
||||
|
||||
以下为本小节目录,详情请参阅《智能之门》正版图书,高等教育出版社出版。
|
||||
|
||||
### 5.6.1 发现问题
|
||||
|
||||
这一节里我们重点解决在训练过程中的数值的数量级的问题。
|
||||
|
||||
我们既然已经对样本数据特征值做了标准化,那么如此大数值的损失函数值是怎么来的呢?看一看损失函数定义:
|
||||
|
||||
$$
|
||||
J(w,b)=\frac{1}{2m} \sum_{i=1}^m (z_i-y_i)^2 \tag{1}
|
||||
$$
|
||||
|
||||
其中,$z_i$ 是预测值,$y_i$ 是标签值。初始状态时,$W$ 和 $B$ 都是 $0$,所以,经过前向计算函数 $Z=X \cdot W+B$ 的结果是 $0$,但是 $Y$ 值很大,处于 $[181.38, 674.37]$ 之间,再经过平方计算后,一下子就成为至少5位数的数值了。
|
||||
|
||||
再看反向传播时的过程:
|
||||
|
||||
```Python
|
||||
def __backwardBatch(self, batch_x, batch_y, batch_z):
|
||||
m = batch_x.shape[0]
|
||||
dZ = batch_z - batch_y
|
||||
dB = dZ.sum(axis=0, keepdims=True)/m
|
||||
dW = np.dot(batch_x.T, dZ)/m
|
||||
return dW, dB
|
||||
```
|
||||
第二行代码求得的`dZ`,与房价是同一数量级的,这样经过反向传播后,`dW`和`dB`的值也会很大,导致整个反向传播链的数值都很大。我们可以debug一下,得到第一反向传播时的数值是:
|
||||
```
|
||||
dW
|
||||
array([[-142.59982906],
|
||||
[-283.62409678]])
|
||||
dB
|
||||
array([[-443.04543906]])
|
||||
```
|
||||
上述数值又可能在读者的机器上是不一样的,因为样本做了shuffle,但是不影响我们对问题的分析。
|
||||
|
||||
这么大的数值,需要把学习率设置得很小,比如`0.001`,才可以落到 $[0,1]$ 区间,但是损失函数值还是不能变得很小。
|
||||
|
||||
如果我们像对特征值做标准化一样,把标签值也标准化到 $[0,1]$ 之间,是不是有帮助呢?
|
||||
|
||||
### 5.6.2 代码实现
|
||||
|
||||
参照X的标准化方法,对Y的标准化公式如下:
|
||||
|
||||
$$y_{new} = \frac{y-y_{min}}{y_{max}-y_{min}} \tag{2}$$
|
||||
|
||||
在`SimpleDataReader`类中增加新方法如下:
|
||||
|
||||
```Python
|
||||
class SimpleDataReader(object):
|
||||
def NormalizeY(self):
|
||||
self.Y_norm = np.zeros((1,2))
|
||||
max_value = np.max(self.YRaw)
|
||||
min_value = np.min(self.YRaw)
|
||||
# min value
|
||||
self.Y_norm[0, 0] = min_value
|
||||
# range value
|
||||
self.Y_norm[0, 1] = max_value - min_value
|
||||
y_new = (self.YRaw - min_value) / self.Y_norm[0, 1]
|
||||
self.YTrain = y_new
|
||||
```
|
||||
|
||||
原始数据中,$Y$ 的数值范围是:
|
||||
|
||||
- 最大值:674.37
|
||||
- 最小值:181.38
|
||||
- 平均值:420.64
|
||||
|
||||
标准化后,$Y$ 的数值范围是:
|
||||
- 最大值:1.0
|
||||
- 最小值:0.0
|
||||
- 平均值:0.485
|
||||
|
||||
注意,我们同样记住了`Y_norm`的值便于以后使用。
|
||||
|
||||
修改主程序代码,增加对 $Y$ 标准化的方法调用`NormalizeY()`:
|
||||
|
||||
```Python
|
||||
# main
|
||||
if __name__ == '__main__':
|
||||
# data
|
||||
reader = SimpleDataReader()
|
||||
reader.ReadData()
|
||||
reader.NormalizeX()
|
||||
reader.NormalizeY()
|
||||
......
|
||||
```
|
||||
|
||||
### 5.6.3 运行结果
|
||||
|
||||
运行上述代码得到的结果其实并不令人满意:
|
||||
|
||||
```
|
||||
......
|
||||
199 99 0.0015663978030319194
|
||||
[[-0.08194777] [ 0.80973365]] [[0.12714971]]
|
||||
W= [[-0.08194777]
|
||||
[ 0.80973365]]
|
||||
B= [[0.12714971]]
|
||||
z= [[0.61707273]]
|
||||
```
|
||||
|
||||
虽然W和B的值都已经处于 $[-1,1]$ 之间了,但是 $z$ 的值也在 $[0,1]$ 之间,一套房子不可能卖0.61万元!
|
||||
|
||||
聪明的读者可能会想到:既然对标签值做了标准化,那么我们在得到预测结果后,需要对这个结果应该做反标准化。
|
||||
|
||||
根据公式2,反标准化的公式应该是:
|
||||
|
||||
$$y = y_{new}(y_{max}-y_{min})+y_{min} \tag{3}$$
|
||||
|
||||
代码如下:
|
||||
|
||||
```Python
|
||||
if __name__ == '__main__':
|
||||
# data
|
||||
reader = SimpleDataReader()
|
||||
reader.ReadData()
|
||||
reader.NormalizeX()
|
||||
reader.NormalizeY()
|
||||
# net
|
||||
params = HyperParameters(eta=0.01, max_epoch=200, batch_size=10, eps=1e-5)
|
||||
net = NeuralNet(params, 2, 1)
|
||||
net.train(reader, checkpoint=0.1)
|
||||
# inference
|
||||
x1 = 15
|
||||
x2 = 93
|
||||
x = np.array([x1,x2]).reshape(1,2)
|
||||
x_new = reader.NormalizePredicateData(x)
|
||||
z = net.inference(x_new)
|
||||
print("z=", z)
|
||||
Z_real = z * reader.Y_norm[0,1] + reader.Y_norm[0,0]
|
||||
print("Z_real=", Z_real)
|
||||
```
|
||||
|
||||
倒数第二行代码,就是公式3。运行结果如下:
|
||||
|
||||
```
|
||||
W= [[-0.08149004]
|
||||
[ 0.81022449]]
|
||||
B= [[0.12801985]]
|
||||
z= [[0.61856996]]
|
||||
Z_real= [[486.33591769]]
|
||||
```
|
||||
|
||||
看`Z_real`的值,完全满足要求!
|
||||
|
||||
总结一下从本章中学到的正确的方法:
|
||||
|
||||
1. $X$ 必须标准化,否则无法训练;
|
||||
2. $Y$ 值不在 $[0,1]$ 之间时,要做标准化,好处是迭代次数少;
|
||||
3. 如果 $Y$ 做了标准化,对得出来的预测结果做关于 $Y$ 的反标准化
|
||||
|
||||
至此,我们完美地解决了北京通州地区的房价预测问题!
|
||||
|
||||
### 5.6.4 总结
|
||||
|
||||
归纳总结一下前面遇到的困难及解决办法:
|
||||
|
||||
1. 样本不做标准化的话,网络发散,训练无法进行;
|
||||
2. 训练样本标准化后,网络训练可以得到结果,但是预测结果有问题;
|
||||
3. 还原参数值后,预测结果正确,但是此还原方法并不能普遍适用;
|
||||
4. 标准化测试样本,而不需要还原参数值,可以保证普遍适用;
|
||||
5. 标准化标签值,可以使得网络训练收敛快,但是在预测时需要把结果反标准化,以便得到真实值。
|
||||
|
||||
### 代码位置
|
||||
|
||||
ch05, Level6
|
||||
|
|
|
@ -3,439 +3,16 @@
|
|||
|
||||
## 7.1 多分类函数
|
||||
|
||||
此函数对线性多分类和非线性多分类都适用。
|
||||
|
||||
先回忆一下二分类问题,在线性计算后,使用了Logistic函数计算样本的概率值,从而把样本分成了正负两类。那么对于多分类问题,应该使用什么方法来计算样本属于各个类别的概率值呢?又是如何作用到反向传播过程中的呢?我们这一节主要研究这个问题。
|
||||
以下为本小节目录,详情请参阅《智能之门》正版图书,高等教育出版社出版。
|
||||
|
||||
### 7.1.1 多分类函数定义 - Softmax
|
||||
|
||||
#### 如何得到多分类问题的分类结果概率值?
|
||||
|
||||
Logistic函数可以得到诸如0.8、0.3这样的二分类概率值,前者接近1,后者接近0。那么多分类问题如何得到类似的概率值呢?
|
||||
|
||||
我们依然假设对于一个样本的分类值是用这个线性公式得到的:
|
||||
|
||||
$$
|
||||
z = x \cdot w + b
|
||||
$$
|
||||
|
||||
但是,我们要求 $z$ 不是一个标量,而是一个向量。如果是三分类问题,我们就要求 $z$ 是一个三维的向量,向量中的每个单元的元素值代表该样本分别属于三个分类的值,这样不就可以了吗?
|
||||
|
||||
具体的说,假设$x$是一个 (1x2) 的向量,把w设计成一个(2x3)的向量,b设计成(1x3)的向量,则z就是一个(1x3)的向量。我们假设z的计算结果是$[3,1,-3]$,这三个值分别代表了样本x在三个分类中的数值,下面我们把它转换成概率值。
|
||||
|
||||
有的读者可能会有疑问:我们不能训练神经网络让它的z值直接变成概率形式吗?答案是否定的,因为z值是经过线性计算得到的,线性计算能力有限,无法有效地直接变成概率值。
|
||||
|
||||
#### 取max值
|
||||
|
||||
z值是 $[3,1,-3]$,如果取max操作会变成 $[1,0,0]$,这符合我们的分类需要,即三者相加为1,并且认为该样本属于第一类。但是有两个不足:
|
||||
|
||||
1. 分类结果是 $[1,0,0]$,只保留的非0即1的信息,没有各元素之间相差多少的信息,可以理解是“Hard Max”;
|
||||
2. max操作本身不可导,无法用在反向传播中。
|
||||
|
||||
#### 引入Softmax
|
||||
|
||||
Softmax加了个"soft"来模拟max的行为,但同时又保留了相对大小的信息。
|
||||
|
||||
$$
|
||||
a_j = \frac{e^{z_j}}{\sum\limits_{i=1}^m e^{z_i}}=\frac{e^{z_j}}{e^{z_1}+e^{z_2}+\dots+e^{z_m}}
|
||||
$$
|
||||
|
||||
上式中:
|
||||
|
||||
- $z_j$ 是对第 $j$ 项的分类原始值,即矩阵运算的结果
|
||||
- $z_i$ 是参与分类计算的每个类别的原始值
|
||||
- $m$ 是总分类数
|
||||
- $a_j$ 是对第 $j$ 项的计算结果
|
||||
|
||||
假设 $j=1,m=3$,上式为:
|
||||
|
||||
$$a_1=\frac{e^{z_1}}{e^{z_1}+e^{z_2}+e^{z_3}}$$
|
||||
|
||||
用图7-5来形象地说明这个过程。
|
||||
|
||||
<img src="./img/7/softmax.png" />
|
||||
|
||||
图7-5 Softmax工作过程
|
||||
|
||||
当输入的数据$[z_1,z_2,z_3]$是$[3,1,-3]$时,按照图示过程进行计算,可以得出输出的概率分布是$[0.879,0.119,0.002]$。
|
||||
|
||||
对比MAX运算和Softmax的不同,如表7-2所示。
|
||||
|
||||
表7-2 MAX运算和Softmax的不同
|
||||
|
||||
|输入原始值|MAX计算|Softmax计算|
|
||||
|:---:|:---:|:---:|
|
||||
|$[3, 1, -3]$|$[1, 0, 0]$|$[0.879, 0.119, 0.002]$|
|
||||
|
||||
也就是说,在(至少)有三个类别时,通过使用Softmax公式计算它们的输出,比较相对大小后,得出该样本属于第一类,因为第一类的值为0.879,在三者中最大。注意这是对一个样本的计算得出的数值,而不是三个样本,亦即Softmax给出了某个样本分别属于三个类别的概率。
|
||||
|
||||
它有两个特点:
|
||||
|
||||
1. 三个类别的概率相加为1
|
||||
2. 每个类别的概率都大于0
|
||||
|
||||
#### Softmax的反向传播工作原理
|
||||
|
||||
我们仍假设网络输出的预测数据是 $z=[3,1,-3]$,而标签值是 $y=[1,0,0]$。在做反向传播时,根据前面的经验,我们会用 $z-y$,得到:
|
||||
|
||||
$$z-y=[2,1,-3]$$
|
||||
|
||||
这个信息很奇怪:
|
||||
|
||||
- 第一项是2,我们已经预测准确了此样本属于第一类,但是反向误差的值是2,即惩罚值是2
|
||||
- 第二项是1,惩罚值是1,预测对了,仍有惩罚值
|
||||
- 第三项是-3,惩罚值是-3,意为着奖励值是3,明明预测错误了却给了奖励
|
||||
|
||||
所以,如果不使用Softmax这种机制,会存在有个问题:
|
||||
|
||||
- z值和y值之间,即预测值和标签值之间不可比,比如 $z_0=3$ 与 $y_0=1$ 是不可比的
|
||||
- z值中的三个元素之间虽然可比,但只能比大小,不能比差值,比如 $z_0>z_1>z_2$,但3和1相差2,1和-3相差4,这些差值是无意义的
|
||||
|
||||
在使用Softmax之后,我们得到的值是 $a=[0.879,0.119,0.002]$,用 $a-y$:
|
||||
|
||||
$$a-y=[-0.121, 0.119, 0.002]$$
|
||||
|
||||
再来分析这个信息:
|
||||
|
||||
- 第一项-0.121是奖励给该类别0.121,因为它做对了,但是可以让这个概率值更大,最好是1
|
||||
- 第二项0.119是惩罚,因为它试图给第二类0.119的概率,所以需要这个概率值更小,最好是0
|
||||
- 第三项0.002是惩罚,因为它试图给第三类0.002的概率,所以需要这个概率值更小,最好是0
|
||||
|
||||
这个信息是完全正确的,可以用于反向传播。Softmax先做了归一化,把输出值归一到[0,1]之间,这样就可以与标签值的0或1去比较,并且知道惩罚或奖励的幅度。
|
||||
|
||||
从继承关系的角度来说,Softmax函数可以视作Logistic函数扩展,比如一个二分类问题:
|
||||
|
||||
$$
|
||||
a_1 = \frac{e^{z_1}}{e^{z_1} + e^{z_2}} = \frac{1}{1 + e^{z_2 - z_1}}
|
||||
$$
|
||||
|
||||
是不是和Logistic函数形式非常像?其实Logistic函数也是给出了当前样本的一个概率值,只不过是依靠偏近0或偏近1来判断属于正类还是负类。
|
||||
|
||||
### 7.1.2 正向传播
|
||||
|
||||
#### 矩阵运算
|
||||
|
||||
$$
|
||||
z=x \cdot w + b \tag{1}
|
||||
$$
|
||||
|
||||
#### 分类计算
|
||||
|
||||
$$
|
||||
a_j = \frac{e^{z_j}}{\sum\limits_{i=1}^m e^{z_i}}=\frac{e^{z_j}}{e^{z_1}+e^{z_2}+\dots+e^{z_m}} \tag{2}
|
||||
$$
|
||||
|
||||
#### 损失函数计算
|
||||
|
||||
计算单样本时,m是分类数:
|
||||
$$
|
||||
loss(w,b)=-\sum_{i=1}^m y_i \ln a_i \tag{3}
|
||||
$$
|
||||
|
||||
计算多样本时,m是分类数,n是样本数:
|
||||
$$J(w,b) =- \sum_{j=1}^n \sum_{i=1}^m y_{ji} \log a_{ji} \tag{4}$$
|
||||
|
||||
如图7-6示意。
|
||||
|
||||
<img src="./img/7/Loss-A-Z.jpg" ch="500" />
|
||||
|
||||
图7-6 Softmax在神经网络结构中的示意图
|
||||
|
||||
### 7.1.3 反向传播
|
||||
|
||||
#### 实例化推导
|
||||
|
||||
我们先用实例化的方式来做反向传播公式的推导,然后再扩展到一般性上。假设有三个类别,则:
|
||||
|
||||
$$
|
||||
z_1 = x \cdot w+ b_1 \tag{5}
|
||||
$$
|
||||
$$
|
||||
z_2 = x \cdot w + b_2 \tag{6}
|
||||
$$
|
||||
$$
|
||||
z_3 = x \cdot w + b_3 \tag{7}
|
||||
$$
|
||||
$$
|
||||
a_1=\frac{e^{z_1}}{\sum_i e^{z_i}}=\frac{e^{z_1}}{e^{z_1}+e^{z_2}+e^{z_3}} \tag{8}
|
||||
$$
|
||||
$$
|
||||
a_2=\frac{e^{z_2}}{\sum_i e^{z_i}}=\frac{e^{z_2}}{e^{z_1}+e^{z_2}+e^{z_3}} \tag{9}
|
||||
$$
|
||||
$$
|
||||
a_3=\frac{e^{z_3}}{\sum_i e^{z_i}}=\frac{e^{z_3}}{e^{z_1}+e^{z_2}+e^{z_3}} \tag{10}
|
||||
$$
|
||||
|
||||
为了方便书写,我们令:
|
||||
|
||||
$$
|
||||
E ={e^{z_1}+e^{z_2}+e^{z_3}}
|
||||
$$
|
||||
|
||||
$$
|
||||
loss(w,b)=-(y_1 \ln a_1 + y_2 \ln a_2 + y_3 \ln a_3) \tag{11}
|
||||
$$
|
||||
|
||||
$$
|
||||
\frac{\partial{loss}}{\partial{z_1}}= \frac{\partial{loss}}{\partial{a_1}}\frac{\partial{a_1}}{\partial{z_1}} + \frac{\partial{loss}}{\partial{a_2}}\frac{\partial{a_2}}{\partial{z_1}} + \frac{\partial{loss}}{\partial{a_3}}\frac{\partial{a_3}}{\partial{z_1}} \tag{12}
|
||||
$$
|
||||
|
||||
依次求解公式12中的各项:
|
||||
|
||||
$$
|
||||
\frac{\partial loss}{\partial a_1}=- \frac{y_1}{a_1} \tag{13}
|
||||
$$
|
||||
|
||||
$$
|
||||
\frac{\partial loss}{\partial a_2}=- \frac{y_2}{a_2} \tag{14}
|
||||
$$
|
||||
|
||||
$$
|
||||
\frac{\partial loss}{\partial a_3}=- \frac{y_3}{a_3}
|
||||
\tag{15}
|
||||
$$
|
||||
|
||||
$$
|
||||
\frac{\partial a_1}{\partial z_1}=(\frac{\partial e^{z_1}}{\partial z_1} E -\frac{\partial E}{\partial z_1}e^{z_1})/E^2 =\frac{e^{z_1}E - e^{z_1}e^{z_1}}{E^2}=a_1(1-a_1)
|
||||
\tag{16}
|
||||
$$
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
\frac{\partial a_2}{\partial z_1}&=(\frac{\partial e^{z_2}}{\partial z_1} E -\frac{\partial E}{\partial z_1}e^{z_2})/E^2 =\frac{0 - e^{z_1}e^{z_2}}{E^2}=-a_1 a_2
|
||||
\end{aligned}
|
||||
\tag{17}
|
||||
$$
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
\frac{\partial a_3}{\partial z_1}&=(\frac{\partial e^{z_3}}{\partial z_1} E -\frac{\partial E}{\partial z_1}e^{z_3})/E^2 =\frac{0 - e^{z_1}e^{z_3}}{E^2}=-a_1 a_3
|
||||
\end{aligned}
|
||||
\tag{18}
|
||||
$$
|
||||
|
||||
把公式13~18组合到12中:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
\frac{\partial loss}{\partial z_1}&=-\frac{y_1}{a_1}a_1(1-a_1)+\frac{y_2}{a_2}a_1a_2+\frac{y_3}{a_3}a_1a_3 \\\\
|
||||
&=-y_1+y_1a_1+y_2a_1+y_3a_1 \\\\
|
||||
&=-y_1+a_1(y_1+y_2+y_3) \\\\
|
||||
&=a_1-y_1
|
||||
\end{aligned}
|
||||
\tag{19}
|
||||
$$
|
||||
|
||||
不失一般性,由公式19可得:
|
||||
$$
|
||||
\frac{\partial loss}{\partial z_i}=a_i-y_i \tag{20}
|
||||
$$
|
||||
|
||||
#### 一般性推导
|
||||
|
||||
1. Softmax函数自身的求导
|
||||
|
||||
由于Softmax涉及到求和,所以有两种情况:
|
||||
|
||||
- 求输出项 $a_1$ 对输入项 $z_1$ 的导数,此时:$j=1, i=1, i=j$,可以扩展到 $i,j$ 为任意相等值
|
||||
- 求输出项 $a_2$ 或 $a_3$ 对输入项 $z_1$ 的导数,此时:$j$ 为 $2$ 或 $3$, $i=1,i \neq j$,可以扩展到 $i,j$ 为任意不等值
|
||||
|
||||
Softmax函数的分子:因为是计算 $a_j$,所以分子是 $e^{z_j}$。
|
||||
|
||||
Softmax函数的分母:
|
||||
$$
|
||||
\sum\limits_{i=1}^m e^{z_i} = e^{z_1} + \dots + e^{z_j} + \dots +e^{z_m} \Rightarrow E
|
||||
$$
|
||||
|
||||
- $i=j$时(比如输出分类值 $a_1$ 对 $z_1$ 的求导),求 $a_j$ 对 $z_i$ 的导数,此时分子上的 $e^{z_j}$ 要参与求导。参考基本数学导数公式33:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
\frac{\partial{a_j}}{\partial{z_i}} &= \frac{\partial{}}{\partial{z_i}}(e^{z_j}/E) \\\\
|
||||
&= \frac{\partial{}}{\partial{z_j}}(e^{z_j}/E) \quad (因为z_i==z_i)\\\\
|
||||
&=\frac{e^{z_j}E-e^{z_j}e^{z_j}}{E^2}
|
||||
=\frac{e^{z_j}}{E} - \frac{(e^{z_j})^2}{E^2} \\\\
|
||||
&= a_j-a^2_j=a_j(1-a_j) \\\\
|
||||
\end{aligned}
|
||||
\tag{21}
|
||||
$$
|
||||
|
||||
- $i \neq j$时(比如输出分类值 $a_1$ 对 $z_2$ 的求导,$j=1,i=2$),$a_j$对$z_i$的导数,分子上的 $z_j$ 与 $i$ 没有关系,求导为0,分母的求和项中$e^{z_i}$要参与求导。同样是公式33,因为分子$e^{z_j}$对$e^{z_i}$求导的结果是0:
|
||||
|
||||
$$
|
||||
\frac{\partial{a_j}}{\partial{z_i}}=\frac{-(E)'e^{z_j}}{E^2}
|
||||
$$
|
||||
求和公式对$e^{z_i}$的导数$(E)'$,除了$e^{z_i}$项外,其它都是0:
|
||||
$$
|
||||
(E)' = (e^{z_1} + \dots + e^{z_i} + \dots +e^{z_m})'=e^{z_i}
|
||||
$$
|
||||
所以:
|
||||
$$
|
||||
\begin{aligned}
|
||||
\frac{\partial{a_j}}{\partial{z_i}}&=\frac{-(E)'e^{z_j}}{(E)^2}=-\frac{e^{z_j}e^{z_i}}{{(E)^2}} =-\frac{e^{z_i}}{{E}}\frac{e^{z_j}}{{E}}=-a_{i}a_{j}
|
||||
\end{aligned}
|
||||
\tag{22}
|
||||
$$
|
||||
|
||||
2. 结合损失函数的整体反向传播公式
|
||||
|
||||
看上图,我们要求Loss值对Z1的偏导数。和以前的Logistic函数不同,那个函数是一个 $z$ 对应一个 $a$,所以反向关系也是一对一。而在这里,$a_1$ 的计算是有 $z_1,z_2,z_3$ 参与的,$a_2$ 的计算也是有 $z_1,z_2,z_3$ 参与的,即所有 $a$ 的计算都与前一层的 $z$ 有关,所以考虑反向时也会比较复杂。
|
||||
|
||||
先从Loss的公式看,$loss=-(y_1lna_1+y_2lna_2+y_3lna_3)$,$a_1$ 肯定与 $z_1$ 有关,那么 $a_2,a_3$ 是否与 $z_1$ 有关呢?
|
||||
|
||||
再从Softmax函数的形式来看:
|
||||
|
||||
无论是 $a_1,a_2,a_3$,都是与 $z_1$ 相关的,而不是一对一的关系,所以,想求Loss对Z1的偏导,必须把Loss->A1->Z1, Loss->A2->Z1,Loss->A3->Z1,这三条路的结果加起来。于是有了如下公式:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
\frac{\partial{loss}}{\partial{z_i}} &= \frac{\partial{loss}}{\partial{a_1}}\frac{\partial{a_1}}{\partial{z_i}} + \frac{\partial{loss}}{\partial{a_2}}\frac{\partial{a_2}}{\partial{z_i}} + \frac{\partial{loss}}{\partial{a_3}}\frac{\partial{a_3}}{\partial{z_i}} \\\\
|
||||
&=\sum_j \frac{\partial{loss}}{\partial{a_j}}\frac{\partial{a_j}}{\partial{z_i}}
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
当上式中$i=1,j=3$,就完全符合我们的假设了,而且不失普遍性。
|
||||
|
||||
前面说过了,因为Softmax涉及到各项求和,A的分类结果和Y的标签值分类是否一致,所以需要分情况讨论:
|
||||
|
||||
$$
|
||||
\frac{\partial{a_j}}{\partial{z_i}} = \begin{cases} a_j(1-a_j), & i = j \\\\ -a_ia_j, & i \neq j \end{cases}
|
||||
$$
|
||||
|
||||
因此,$\frac{\partial{loss}}{\partial{z_i}}$应该是 $i=j$ 和 $i \neq j$两种情况的和:
|
||||
|
||||
- $i = j$ 时,loss通过 $a_1$ 对 $z_1$ 求导(或者是通过 $a_2$ 对 $z_2$ 求导):
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
\frac{\partial{loss}}{\partial{z_i}} &= \frac{\partial{loss}}{\partial{a_j}}\frac{\partial{a_j}}{\partial{z_i}}=-\frac{y_j}{a_j}a_j(1-a_j) \\\\
|
||||
&=y_j(a_j-1)=y_i(a_i-1)
|
||||
\end{aligned}
|
||||
\tag{23}
|
||||
$$
|
||||
|
||||
- $i \neq j$,loss通过 $a_2+a_3$ 对 $z_1$ 求导:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
\frac{\partial{loss}}{\partial{z_i}} &= \frac{\partial{loss}}{\partial{a_j}}\frac{\partial{a_j}}{\partial{z_i}}=\sum_j^m(-\frac{y_j}{a_j})(-a_ja_i) \\\\
|
||||
&=\sum_j^m(y_ja_i)=a_i\sum_{j \neq i}{y_j}
|
||||
\end{aligned}
|
||||
\tag{24}
|
||||
$$
|
||||
|
||||
把两种情况加起来:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
\frac{\partial{loss}}{\partial{z_i}} &= y_i(a_i-1)+a_i\sum_{j \neq i}y_j \\\\
|
||||
&=-y_i+a_iy_i+a_i\sum_{j \neq i}y_j \\\\
|
||||
&=-y_i+a_i(y_i+\sum_{j \neq i}y_j) \\\\
|
||||
&=-y_i + a_i*1 \\\\
|
||||
&=a_i-y_i
|
||||
\end{aligned}
|
||||
\tag{25}
|
||||
$$
|
||||
|
||||
因为$y_j$取值$[1,0,0]$或者$[0,1,0]$或者$[0,0,1]$,这三者加起来,就是$[1,1,1]$,在矩阵乘法运算里乘以$[1,1,1]$相当于什么都不做,就等于原值。
|
||||
|
||||
我们惊奇地发现,最后的反向计算过程就是:$a_i-y_i$,假设当前样本的$a_i=[0.879, 0.119, 0.002]$,而$y_i=[0, 1, 0]$,则:
|
||||
$$a_i - y_i = [0.879, 0.119, 0.002]-[0,1,0]=[0.879,-0.881,0.002]$$
|
||||
|
||||
其含义是,样本预测第一类,但实际是第二类,所以给第一类0.879的惩罚值,给第二类0.881的奖励,给第三类0.002的惩罚,并反向传播给神经网络。
|
||||
|
||||
后面对 $z=wx+b$ 的求导,与二分类一样,不再赘述。
|
||||
|
||||
### 7.1.4 代码实现
|
||||
|
||||
第一种,直截了当按照公式写:
|
||||
```Python
|
||||
def Softmax1(x):
|
||||
e_x = np.exp(x)
|
||||
v = np.exp(x) / np.sum(e_x)
|
||||
return v
|
||||
```
|
||||
这个可能会发生的问题是,当x很大时,`np.exp(x)`很容易溢出,因为是指数运算。所以,有了下面这种改进的代码:
|
||||
|
||||
```Python
|
||||
def Softmax2(Z):
|
||||
shift_Z = Z - np.max(Z)
|
||||
exp_Z = np.exp(shift_Z)
|
||||
A = exp_Z / np.sum(exp_Z)
|
||||
return A
|
||||
```
|
||||
测试一下:
|
||||
```Python
|
||||
Z = np.array([3,0,-3])
|
||||
print(Softmax1(Z))
|
||||
print(Softmax2(Z))
|
||||
```
|
||||
两个实现方式的结果一致:
|
||||
```
|
||||
[0.95033021 0.04731416 0.00235563]
|
||||
[0.95033021 0.04731416 0.00235563]
|
||||
```
|
||||
|
||||
为什么一样呢?从代码上看差好多啊!我们来证明一下:
|
||||
|
||||
假设有3个值a,b,c,并且a在三个数中最大,则b所占的Softmax比重应该这样写:
|
||||
|
||||
$$P(b)=\frac{e^b}{e^a+e^b+e^c}$$
|
||||
|
||||
如果减去最大值变成了a-a,b-a,c-a,则b'所占的Softmax比重应该这样写:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
P(b') &= \frac{e^{b-a}}{e^{a-a}+e^{b-a}+e^{c-a}} \\
|
||||
&=\frac{e^b/e^a}{e^a/e^a+e^b/e^a+e^c/e^a} \\
|
||||
&= \frac{e^b}{e^a+e^b+e^c}
|
||||
\end{aligned}
|
||||
$$
|
||||
所以:
|
||||
$$
|
||||
P(b) == P(b')
|
||||
$$
|
||||
|
||||
`Softmax2`的写法对一个一维的向量或者数组是没问题的,如果遇到Z是个$M \times N$维(M,N>1)的矩阵的话,就有问题了,因为`np.sum(exp_Z)`这个函数,会把$M\times N$矩阵里的所有元素加在一起,得到一个标量值,而不是相关列元素加在一起。
|
||||
|
||||
所以应该这么写:
|
||||
|
||||
```Python
|
||||
class Softmax(object):
|
||||
def forward(self, z):
|
||||
shift_z = z - np.max(z, axis=1, keepdims=True)
|
||||
exp_z = np.exp(shift_z)
|
||||
a = exp_z / np.sum(exp_z, axis=1, keepdims=True)
|
||||
return a
|
||||
|
||||
```
|
||||
|
||||
`axis=1`这个参数非常重要,因为如果输入Z是单样本的预测值话,如果是分三类,则应该是个 $3\times 1$ 的数组,如果:
|
||||
|
||||
- $z = [3,1,-3]$
|
||||
- $a = [0.879,0.119,0.002]$
|
||||
|
||||
但是,如果是批量训练,假设每次用两个样本,则:
|
||||
```
|
||||
if __name__ == '__main__':
|
||||
z = np.array([[3,1,-3],[1,-3,3]]).reshape(2,3)
|
||||
a = Softmax().forward(z)
|
||||
print(a)
|
||||
```
|
||||
结果:
|
||||
```
|
||||
[[0.87887824 0.11894324 0.00217852]
|
||||
[0.11894324 0.00217852 0.87887824]]
|
||||
```
|
||||
其中,a是包含两个样本的softmax结果,每个数组里面的三个数字相加为1。
|
||||
|
||||
如果`s = np.sum(exp_z)`,不指定`axis=1`参数,则:
|
||||
```
|
||||
[[0.43943912 0.05947162 0.00108926]
|
||||
[0.05947162 0.00108926 0.43943912]]
|
||||
```
|
||||
A虽然仍然包含两个样本,但是变成了两个样本所有的6个元素相加为1,这不是softmax的本意,softmax只计算一个样本(一行)中的数据。
|
||||
|
||||
### 思考与练习
|
||||
|
||||
1. 有没有可能一个样本的三分类值中,有两个或多个分类值相同呢,比如$[0.3,0.3,0.4]$?
|
||||
|
|
|
@ -3,250 +3,15 @@
|
|||
|
||||
## 7.2 线性多分类的神经网络实现
|
||||
|
||||
以下为本小节目录,详情请参阅《智能之门》正版图书,高等教育出版社出版。
|
||||
|
||||
### 7.2.1 定义神经网络结构
|
||||
|
||||
从图7-1来看,似乎在三个颜色区间之间有两个比较明显的分界线,而且是直线,即线性可分的。我们如何通过神经网络精确地找到这两条分界线呢?
|
||||
|
||||
- 从视觉上判断是线性可分的,所以我们使用单层神经网络即可
|
||||
- 输入特征有两个,分别为经度、纬度
|
||||
- 最后输出的是三个分类,分别是魏蜀吴,所以输出层有三个神经元
|
||||
|
||||
如果有三个以上的分类同时存在,我们需要对每一类别分配一个神经元,这个神经元的作用是根据前端输入的各种数据,先做线性处理($Z=WX+B$),然后做一次非线性处理,计算每个样本在每个类别中的预测概率,再和标签中的类别比较,看看预测是否准确,如果准确,则奖励这个预测,给与正反馈;如果不准确,则惩罚这个预测,给与负反馈。两类反馈都反向传播到神经网络系统中去调整参数。
|
||||
|
||||
这个网络只有输入层和输出层,由于输入层不算在内,所以是一层网络,如图7-7所示。
|
||||
|
||||
<img src="./img/7/MultipleClassifierNN.png" ch="500" />
|
||||
|
||||
图7-7 多入多出单层神经网络
|
||||
|
||||
与前面的单层网络不同的是,图7-7最右侧的输出层还多出来一个Softmax分类函数,这是多分类任务中的标准配置,可以看作是输出层的激活函数,并不单独成为一层,与二分类中的Logistic函数一样。
|
||||
|
||||
#### 输入层
|
||||
|
||||
输入经度 $x_1$ 和纬度 $x_2$ 两个特征:
|
||||
|
||||
$$
|
||||
x=\begin{pmatrix}
|
||||
x_1 & x_2
|
||||
\end{pmatrix}
|
||||
$$
|
||||
|
||||
#### 权重矩阵
|
||||
|
||||
$W$权重矩阵的尺寸,可以从前往后看,比如:输入层是2个特征,输出层是3个神经元,则$W$的尺寸就是 $2\times 3$。
|
||||
|
||||
$$
|
||||
W=\begin{pmatrix}
|
||||
w_{11} & w_{12} & w_{13}\\\\
|
||||
w_{21} & w_{22} & w_{23}
|
||||
\end{pmatrix}
|
||||
$$
|
||||
|
||||
$B$的尺寸是1x3,列数永远和神经元的数量一样,行数永远是1。
|
||||
|
||||
$$
|
||||
B=\begin{pmatrix}
|
||||
b_1 & b_2 & b_3
|
||||
\end{pmatrix}
|
||||
$$
|
||||
|
||||
#### 输出层
|
||||
|
||||
输出层三个神经元,再加上一个Softmax计算,最后有$A1,A2,A3$三个输出,写作:
|
||||
|
||||
$$
|
||||
Z = \begin{pmatrix}z_1 & z_2 & z_3 \end{pmatrix}
|
||||
$$
|
||||
$$
|
||||
A = \begin{pmatrix}a_1 & a_2 & a_3 \end{pmatrix}
|
||||
$$
|
||||
|
||||
其中,$Z=X \cdot W+B,A = Softmax(Z)$
|
||||
|
||||
### 7.2.2 样本数据
|
||||
|
||||
使用`SimpleDataReader`类读取数据后,观察一下数据的基本属性:
|
||||
|
||||
```Python
|
||||
reader.XRaw.shape
|
||||
(140, 2)
|
||||
reader.XRaw.min()
|
||||
0.058152279749505986
|
||||
reader.XRaw.max()
|
||||
9.925126526921046
|
||||
|
||||
reader.YRaw.shape
|
||||
(140, 1)
|
||||
reader.YRaw.min()
|
||||
1.0
|
||||
reader.YRaw.max()
|
||||
3.0
|
||||
```
|
||||
|
||||
- 训练数据X,140个记录,两个特征,最小值0.058,最大值9.925
|
||||
- 标签数据Y,140个记录,一个分类值,取值范围是$\\{1,2,3\\}$
|
||||
|
||||
#### 样本标签数据
|
||||
|
||||
一般来说,在标记样本时,我们会用$1,2,3$这样的标记来指明是哪一类。所以样本数据是这个样子的:
|
||||
$$
|
||||
Y =
|
||||
\begin{pmatrix}
|
||||
y_1 \\\\ y_2 \\\\ \vdots \\\\ y_{140}
|
||||
\end{pmatrix}=
|
||||
\begin{pmatrix}3 \\\\ 2 \\\\ \vdots \\\\ 1\end{pmatrix}
|
||||
$$
|
||||
|
||||
在有Softmax的多分类计算时,我们用下面这种等价的方式,俗称OneHot。
|
||||
$$
|
||||
Y =
|
||||
\begin{pmatrix}
|
||||
y_1 \\\\ y_2 \\\\ \vdots \\\\ y_{140}
|
||||
\end{pmatrix}=
|
||||
\begin{pmatrix}
|
||||
0 & 0 & 1 \\\\
|
||||
0 & 1 & 0 \\\\
|
||||
... & ... & ... \\\\
|
||||
1 & 0 & 0
|
||||
\end{pmatrix}
|
||||
$$
|
||||
|
||||
OneHot的意思,在这一列数据中,只有一个1,其它都是0。1所在的列数就是这个样本的分类类别。标签数据对应到每个样本数据上,列对齐,只有$(1,0,0),(0,1,0),(0,0,1)$三种可能,分别表示第一类、第二类和第三类。
|
||||
|
||||
在`SimpleDataReader`中实现`ToOneHot()`方法,把原始标签转变成One-Hot编码:
|
||||
|
||||
```Python
|
||||
class SimpleDataReader(object):
|
||||
def ToOneHot(self, num_category, base=0):
|
||||
......
|
||||
```
|
||||
|
||||
### 7.2.3 代码实现
|
||||
|
||||
绝大部分代码可以从上一章的`HelperClass`中拷贝出来,但是需要我们为了本章的特殊需要稍加修改。
|
||||
|
||||
#### 添加分类函数
|
||||
|
||||
在`Activators.py`中,增加Softmax的实现,并添加单元测试。
|
||||
|
||||
```Python
|
||||
class Softmax(object):
|
||||
def forward(self, z):
|
||||
......
|
||||
```
|
||||
|
||||
#### 前向计算
|
||||
|
||||
前向计算需要增加分类函数调用:
|
||||
|
||||
```Python
|
||||
class NeuralNet(object):
|
||||
def forwardBatch(self, batch_x):
|
||||
Z = np.dot(batch_x, self.W) + self.B
|
||||
if self.params.net_type == NetType.BinaryClassifier:
|
||||
A = Logistic().forward(Z)
|
||||
return A
|
||||
elif self.params.net_type == NetType.MultipleClassifier:
|
||||
A = Softmax().forward(Z)
|
||||
return A
|
||||
else:
|
||||
return Z
|
||||
```
|
||||
|
||||
#### 反向传播
|
||||
|
||||
在多分类函数一节详细介绍了反向传播的推导过程,推导的结果很令人惊喜,就是一个简单的减法,与前面学习的拟合、二分类的算法结果都一样。
|
||||
|
||||
```Python
|
||||
class NeuralNet(object):
|
||||
def backwardBatch(self, batch_x, batch_y, batch_a):
|
||||
......
|
||||
```
|
||||
|
||||
#### 计算损失函数值
|
||||
|
||||
损失函数不再是均方差和二分类交叉熵了,而是交叉熵函数对于多分类的形式,并且添加条件分支来判断只在网络类型为多分类时调用此损失函数。
|
||||
|
||||
```Python
|
||||
class LossFunction(object):
|
||||
# fcFunc: feed forward calculation
|
||||
def CheckLoss(self, A, Y):
|
||||
m = Y.shape[0]
|
||||
if self.net_type == NetType.Fitting:
|
||||
loss = self.MSE(A, Y, m)
|
||||
elif self.net_type == NetType.BinaryClassifier:
|
||||
loss = self.CE2(A, Y, m)
|
||||
elif self.net_type == NetType.MultipleClassifier:
|
||||
loss = self.CE3(A, Y, m)
|
||||
#end if
|
||||
return loss
|
||||
# end def
|
||||
|
||||
# for multiple classifier
|
||||
def CE3(self, A, Y, count):
|
||||
......
|
||||
```
|
||||
|
||||
#### 推理函数
|
||||
|
||||
```Python
|
||||
def inference(net, reader):
|
||||
xt_raw = np.array([5,1,7,6,5,6,2,7]).reshape(4,2)
|
||||
xt = reader.NormalizePredicateData(xt_raw)
|
||||
output = net.inference(xt)
|
||||
r = np.argmax(output, axis=1)+1
|
||||
print("output=", output)
|
||||
print("r=", r)
|
||||
```
|
||||
注意在推理之前,先做了归一化,因为原始数据是在[0,10]范围的。
|
||||
|
||||
函数`np.argmax`的作用是比较`output`里面的几个数据的值,返回最大的那个数据的行数或者列数,0-based。比如output=(1.02,-3,2.2)时,会返回2,因为2.2最大,所以我们再加1,把返回值变成1,2,3的其中一个。
|
||||
|
||||
`np.argmax`函数的参数`axis=1`,是因为有4个样本参与预测,所以需要在第二维上区分开来,分别计算每个样本的argmax值。
|
||||
|
||||
#### 主程序
|
||||
|
||||
```Python
|
||||
if __name__ == '__main__':
|
||||
num_category = 3
|
||||
......
|
||||
num_input = 2
|
||||
params = HyperParameters(num_input, num_category, eta=0.1, max_epoch=100, batch_size=10, eps=1e-3, net_type=NetType.MultipleClassifier)
|
||||
......
|
||||
```
|
||||
|
||||
### 7.2.4 运行结果
|
||||
|
||||
#### 损失函数历史记录
|
||||
|
||||
从图7-8所示的趋势上来看,损失函数值还有进一步下降的可能,以提高模型精度。有兴趣的读者可以多训练几轮,看看效果。
|
||||
|
||||
<img src="./img/7/loss.png" ch="500" />
|
||||
|
||||
图7-8 训练过程中损失函数值的变化
|
||||
|
||||
下面是打印输出的最后几行:
|
||||
|
||||
```
|
||||
......
|
||||
epoch=99
|
||||
99 13 0.25497053433985734
|
||||
W= [[-1.43234109 -3.57409342 5.00643451]
|
||||
[ 4.47791288 -2.88936887 -1.58854401]]
|
||||
B= [[-1.81896724 3.66606162 -1.84709438]]
|
||||
output= [[0.01801124 0.73435241 0.24763634]
|
||||
[0.24709055 0.15438074 0.59852871]
|
||||
[0.38304995 0.37347646 0.24347359]
|
||||
[0.51360269 0.46266935 0.02372795]]
|
||||
r= [2 3 1 1]
|
||||
```
|
||||
注意,r是分类预测结果,对于每个测试样本的结果,是按行看的,即第一行是第一个测试样本的分类结果。
|
||||
|
||||
1. 经纬度相对值为 $(5,1)$ 时,概率0.734最大,属于2,蜀国
|
||||
2. 经纬度相对值为 $(7,6)$ 时,概率0.598最大,属于3,吴国
|
||||
3. 经纬度相对值为 $(5,6)$ 时,概率0.383最大,属于1,魏国
|
||||
4. 经纬度相对值为 $(2,7)$ 时,概率0.513最大,属于1,魏国
|
||||
|
||||
### 代码位置
|
||||
|
||||
ch07, Level1
|
||||
|
|
|
@ -3,216 +3,15 @@
|
|||
|
||||
## 7.3 线性多分类原理
|
||||
|
||||
此原理对线性多分类和非线性多分类都适用。
|
||||
以下为本小节目录,详情请参阅《智能之门》正版图书,高等教育出版社出版。
|
||||
|
||||
### 7.3.1 多分类过程
|
||||
|
||||
我们在此以具有两个特征值的三分类举例。可以扩展到更多的分类或任意特征值,比如在ImageNet的图像分类任务中,最后一层全连接层输出给分类器的特征值有成千上万个,分类有1000个。
|
||||
|
||||
1. 线性计算
|
||||
|
||||
$$z_1 = x_1 w_{11} + x_2 w_{21} + b_1 \tag{1}$$
|
||||
$$z_2 = x_1 w_{12} + x_2 w_{22} + b_2 \tag{2}$$
|
||||
$$z_3 = x_1 w_{13} + x_2 w_{23} + b_3 \tag{3}$$
|
||||
|
||||
2. 分类计算
|
||||
|
||||
$$
|
||||
a_1=\frac{e^{z_1}}{\sum_i e^{z_i}}=\frac{e^{z_1}}{e^{z_1}+e^{z_2}+e^{z_3}} \tag{4}
|
||||
$$
|
||||
$$
|
||||
a_2=\frac{e^{z_2}}{\sum_i e^{z_i}}=\frac{e^{z_2}}{e^{z_1}+e^{z_2}+e^{z_3}} \tag{5}
|
||||
$$
|
||||
$$
|
||||
a_3=\frac{e^{z_3}}{\sum_i e^{z_i}}=\frac{e^{z_3}}{e^{z_1}+e^{z_2}+e^{z_3}} \tag{6}
|
||||
$$
|
||||
|
||||
3. 损失函数计算
|
||||
|
||||
单样本时,$n$表示类别数,$j$表示类别序号:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
loss(w,b)&=-(y_1 \ln a_1 + y_2 \ln a_2 + y_3 \ln a_3) \\\\
|
||||
&=-\sum_{j=1}^{n} y_j \ln a_j
|
||||
\end{aligned}
|
||||
\tag{7}
|
||||
$$
|
||||
|
||||
批量样本时,$m$ 表示样本数,$i$ 表示样本序号:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
J(w,b) &=- \sum_{i=1}^m (y_{i1} \ln a_{i1} + y_{i2} \ln a_{i2} + y_{i3} \ln a_{i3}) \\\\
|
||||
&=- \sum_{i=1}^m \sum_{j=1}^n y_{ij} \ln a_{ij}
|
||||
\end{aligned}
|
||||
\tag{8}
|
||||
$$
|
||||
|
||||
损失函数计算在交叉熵函数一节有详细介绍。
|
||||
|
||||
### 7.3.2 数值计算举例
|
||||
|
||||
假设对预测一个样本的计算得到的 $z$ 值为:
|
||||
|
||||
$$z=[z_1,z_2,z_3]=[3,1,-3]$$
|
||||
|
||||
则按公式4、5、6进行计算,可以得出Softmax的概率分布是:
|
||||
|
||||
$$a=[a_1,a_2,a_3]=[0.879,0.119,0.002]$$
|
||||
|
||||
#### 如果标签值表明此样本为第一类
|
||||
|
||||
即:
|
||||
|
||||
$$y=[1,0,0]$$
|
||||
|
||||
则损失函数为:
|
||||
|
||||
$$
|
||||
loss_1=-(1 \times \ln 0.879 + 0 \times \ln 0.119 + 0 \times \ln 0.002)=0.123
|
||||
$$
|
||||
|
||||
反向传播误差矩阵为:
|
||||
|
||||
$$a-y=[-0.121,0.119,0.002]$$
|
||||
|
||||
因为$a_1=0.879$,为三者最大,分类正确,所以$a-y$的三个值都不大。
|
||||
|
||||
#### 如果标签值表明此样本为第二类
|
||||
|
||||
即:
|
||||
|
||||
$$y=[0,1,0]$$
|
||||
|
||||
则损失函数为:
|
||||
|
||||
$$
|
||||
loss_2=-(0 \times \ln 0.879 + 1 \times \ln 0.119 + 0 \times \ln 0.002)=2.128
|
||||
$$
|
||||
|
||||
可以看到由于分类错误,$loss_2$的值比$loss_1$的值大很多。
|
||||
|
||||
反向传播误差矩阵为:
|
||||
|
||||
$$a-y=[0.879,-0.881,0.002]$$
|
||||
|
||||
本来是第二类,误判为第一类,所以前两个元素的值很大,反向传播的力度就大。
|
||||
|
||||
### 7.3.3 多分类的几何原理
|
||||
|
||||
在前面的二分类原理中,很容易理解为我们用一条直线分开两个部分。对于多分类问题,是否可以沿用二分类原理中的几何解释呢?答案是肯定的,只不过需要单独判定每一个类别。
|
||||
|
||||
假设一共有三类样本,蓝色为1,红色为2,绿色为3,那么Softmax的形式应该是:
|
||||
|
||||
$$
|
||||
a_j = \frac{e^{z_j}}{\sum\limits_{i=1}^3 e^{z_i}}=\frac{e^{z_j}}{e^{z_1}+e^{z_2}+^{z_3}}
|
||||
$$
|
||||
|
||||
#### 当样本属于第一类时
|
||||
|
||||
把蓝色点与其它颜色的点分开。
|
||||
|
||||
如果判定一个点属于第一类,则 $a_1$ 概率值一定会比 $a_2,a_3$ 大,表示为公式:
|
||||
|
||||
$$a_1 > a_2 且 a_1 > a_3 \tag{9}$$
|
||||
|
||||
由于Softmax的特殊形式,分母都一样,所以只比较分子就行了。而分子是一个自然指数,输出值域大于零且单调递增,所以只比较指数就可以了,因此,公式9等同于下式:
|
||||
|
||||
$$z_1 > z_2, z_1 > z_3 \tag{10}$$
|
||||
|
||||
把公式1、2、3引入到10:
|
||||
|
||||
$$x_1 w_{11} + x_2 w_{21} + b_1 > x_1 w_{12} + x_2 w_{22} + b_2 \tag{11}$$
|
||||
$$x_1 w_{11} + x_2 w_{21} + b_1 > x_1 w_{13} + x_2 w_{23} + b_3 \tag{12}$$
|
||||
|
||||
变形:
|
||||
|
||||
$$(w_{21} - w_{22})x_2 > (w_{12} - w_{11})x_1 + (b_2 - b_1) \tag{13}$$
|
||||
|
||||
$$(w_{21} - w_{23})x_2 > (w_{13} - w_{11})x_1 + (b_3 - b_1) \tag{14}$$
|
||||
|
||||
我们先假设:
|
||||
|
||||
$$w_{21} > w_{22}, w_{21}> w_{23} \tag{15}$$
|
||||
|
||||
所以公式13、14左侧的系数都大于零,两边同时除以系数:
|
||||
|
||||
$$x_2 > \frac{w_{12} - w_{11}}{w_{21} - w_{22}}x_1 + \frac{b_2 - b_1}{w_{21} - w_{22}} \tag{16}$$
|
||||
|
||||
$$x_2 > \frac{w_{13} - w_{11}}{w_{21} - w_{23}} x_1 + \frac{b_3 - b_1}{w_{21} - w_{23}} \tag{17}$$
|
||||
|
||||
简化,即把公式16、17的分数形式的因子分别简化成一个变量$W$和$B$,并令$y = x_2,x = x_1$,利于理解:
|
||||
|
||||
$$y > W_{12} \cdot x + B_{12} \tag{18}$$
|
||||
|
||||
$$y > W_{13} \cdot x + B_{13} \tag{19}$$
|
||||
|
||||
此时y代表了第一类的蓝色点。
|
||||
|
||||
<img src="./img/7/z1.png" ch="500" />
|
||||
|
||||
图7-9 如何把蓝色样本与其它两色的样本分开
|
||||
|
||||
借用二分类中的概念,公式18的几何含义是:有一条直线可以分开第一类(蓝色点)和第二类(红色点),使得所有蓝色点都在直线的上方,所有的红色点都在直线的下方。于是我们可以画出图7-9中的那条绿色直线。
|
||||
|
||||
而公式19的几何含义是:有一条直线可以分开第一类(蓝色点)和第三类(绿色点),使得所有蓝色点都在直线的上方,所有的绿色点都在直线的下方。于是我们可以画出图7-9中的那条红色直线。
|
||||
|
||||
也就是说在图中画两条直线,所有蓝点都同时在红线和绿线这两条直线的上方。
|
||||
|
||||
#### 当样本属于第二类时
|
||||
|
||||
即如何把红色点与其它两色点分开。
|
||||
|
||||
$$z_2 > z_1,z_2 > z_3 \tag{20}$$
|
||||
|
||||
同理可得
|
||||
|
||||
$$y < W_{12} \cdot x + B_{12} \tag{21}$$
|
||||
|
||||
$$y > W_{23} \cdot x + B_{23} \tag{22}$$
|
||||
|
||||
<img src="./img/7/z2.png" ch="500" />
|
||||
|
||||
图7-10 如何把红色样本与其它两色的样本分开
|
||||
|
||||
|
||||
此时$y$代表了第二类的红色点。
|
||||
|
||||
公式21和公式18几何含义相同,不等号相反,代表了图7-10中绿色直线的分割作用,即红色点在绿色直线下方。
|
||||
|
||||
公式22的几何含义是,有一条蓝色直线可以分开第二类(红色点)和第三类(绿色点),使得所有红色点都在直线的上方,所有的绿色点都在直线的下方。
|
||||
|
||||
#### 当样本属于第三类时
|
||||
|
||||
即如何把绿色点与其它两色点分开。
|
||||
|
||||
$$z_3 > z_1,z_3 > z_2 \tag{22}$$
|
||||
|
||||
最后可得:
|
||||
|
||||
$$y < W_{13} \cdot x + B_{13} \tag{23}$$
|
||||
|
||||
$$y < W_{23} \cdot x + B_{23} \tag{24}$$
|
||||
|
||||
此时$y$代表了第三类的绿色点。
|
||||
|
||||
<img src="./img/7/z3.png" ch="500" />
|
||||
|
||||
图7-11 如何把绿色样本与其它两色的样本分开
|
||||
|
||||
公式23与公式19不等号相反,几何含义相同,代表了图7-11中红色直线的分割作用,绿色点在红色直线下方。
|
||||
|
||||
公式24与公式22不等号相反,几何含义相同,代表了图7-11中蓝色直线的分割作用,绿色点在蓝色直线下方。
|
||||
|
||||
#### 综合效果
|
||||
|
||||
把三张图综合在一起,应该是图7-12的样子。
|
||||
|
||||
<img src="./img/7/z123.png" ch="500" />
|
||||
|
||||
图7-12 三条线分开三种样本
|
||||
|
||||
|
||||
### 思考与练习
|
||||
|
||||
|
|
|
@ -3,125 +3,14 @@
|
|||
|
||||
## 7.4 多分类结果可视化
|
||||
|
||||
神经网络到底是一对一方式,还是一对多方式呢?从Softmax公式,好像是一对多方式,因为只取一个最大值,那么理想中的一对多方式应该是图7-13所示的样子。
|
||||
|
||||
<img src="./img/7/OneVsOthers.png" ch="500" />
|
||||
|
||||
图7-13 理想中的一对多方式的分割线
|
||||
|
||||
实际上是什么样子的,我们来看下面的具体分析。
|
||||
以下为本小节目录,详情请参阅《智能之门》正版图书,高等教育出版社出版。
|
||||
|
||||
### 7.4.1 显示原始数据图
|
||||
|
||||
与二分类时同样的问题,如何直观地理解多分类的结果?三分类要复杂一些,我们先把原始数据显示出来。
|
||||
|
||||
```Python
|
||||
def ShowData(X,Y):
|
||||
for i in range(X.shape[0]):
|
||||
if Y[i,0] == 1:
|
||||
plt.plot(X[i,0], X[i,1], '.', c='r')
|
||||
elif Y[i,0] == 2:
|
||||
plt.plot(X[i,0], X[i,1], 'x', c='g')
|
||||
elif Y[i,0] == 3:
|
||||
plt.plot(X[i,0], X[i,1], '^', c='b')
|
||||
# end if
|
||||
# end for
|
||||
plt.show()
|
||||
```
|
||||
|
||||
会画出图7-1来。
|
||||
|
||||
### 7.4.2 显示分类结果分割线图
|
||||
|
||||
下面的数据是神经网络训练出的权重和偏移值的结果:
|
||||
|
||||
```
|
||||
......
|
||||
epoch=98
|
||||
98 1385 0.25640040547970516
|
||||
epoch=99
|
||||
99 1399 0.2549651316913006
|
||||
W= [[-1.43299777 -3.57488388 5.00788165]
|
||||
[ 4.47527075 -2.88799216 -1.58727859]]
|
||||
B= [[-1.821679 3.66752583 -1.84584683]]
|
||||
......
|
||||
```
|
||||
|
||||
其实在7.2节中讲解多分类原理的时候,我们已经解释了其几何理解,那些公式的推导就可以用于指导我们画出多分类的分割线来。先把几个有用的结论拿过来。
|
||||
|
||||
从7.2节中的公式16,把不等号变成等号,即$z_1=z_2$,则代表了那条绿色的分割线,用于分割第一类和第二类的:
|
||||
|
||||
$$x_2 = \frac{w_{12} - w_{11}}{w_{21} - w_{22}}x_1 + \frac{b_2 - b_1}{w_{21} - w_{22}} \tag{1}$$
|
||||
|
||||
$$即:y = W_{12} \cdot x + B_{12}$$
|
||||
|
||||
由于`Python`数组是从0开始的,所以公式1中的所有下标都减去1,写成代码:
|
||||
|
||||
```Python
|
||||
b12 = (net.B[0,1] - net.B[0,0])/(net.W[1,0] - net.W[1,1])
|
||||
w12 = (net.W[0,1] - net.W[0,0])/(net.W[1,0] - net.W[1,1])
|
||||
```
|
||||
|
||||
从7.2节中的公式17,把不等号变成等号,即$z_1=z_3$,则代表了那条红色的分割线,用于分割第一类和第三类的:
|
||||
|
||||
$$x_2 = \frac{w_{13} - w_{11}}{w_{21} - w_{23}} x_1 + \frac{b_3 - b_1}{w_{21} - w_{23}} \tag{2}$$
|
||||
$$即:y = W_{13} \cdot x + B_{13}$$
|
||||
|
||||
写成代码:
|
||||
|
||||
```Python
|
||||
b13 = (net.B[0,0] - net.B[0,2])/(net.W[1,2] - net.W[1,0])
|
||||
w13 = (net.W[0,0] - net.W[0,2])/(net.W[1,2] - net.W[1,0])
|
||||
```
|
||||
|
||||
从7.2节中的公式24,把不等号变成等号,即$z_2=z_3$,则代表了那条蓝色的分割线,用于分割第二类和第三类的:
|
||||
|
||||
$$x_2 = \frac{w_{13} - w_{12}}{w_{22} - w_{23}} x_1 + \frac{b_3 - b_2}{w_{22} - w_{23}} \tag{3}$$
|
||||
$$即:y = W_{23} \cdot x + B_{23}$$
|
||||
|
||||
写成代码:
|
||||
|
||||
```Python
|
||||
b23 = (net.B[0,2] - net.B[0,1])/(net.W[1,1] - net.W[1,2])
|
||||
w23 = (net.W[0,2] - net.W[0,1])/(net.W[1,1] - net.W[1,2])
|
||||
```
|
||||
|
||||
完整代码请看ch07 Level2的python文件。
|
||||
|
||||
改一下主函数,增加对以上两个函数`ShowData()`和`ShowResult()`的调用,最后可以看到图7-14所示的分类结果图,注意,这个结果图和我们在7.2中分析的一样,只是蓝线斜率不同。
|
||||
|
||||
<img src="./img/7/result.png" ch="500" />
|
||||
|
||||
图7-14 神经网络绘出的分类结果图
|
||||
|
||||
图7-14中的四个三角形的大点是需要我们预测的四个坐标值,其中三个点的分类都比较明确,只有那个蓝色的点看不清在边界那一侧,可以通过在实际的运行结果图上放大局部来观察。
|
||||
|
||||
### 7.4.3 理解神经网络的分类方式
|
||||
|
||||
做为实际结果,图7-14与我们猜想的图7-13完全不同:
|
||||
|
||||
- 蓝色线是2|3的边界,不考虑第1类
|
||||
- 绿色线是1|2的边界,不考虑第3类
|
||||
- 红色线是1|3的边界,不考虑第2类
|
||||
|
||||
我们只看蓝色的第1类,当要区分1|2和1|3时,神经网络实际是用了两条直线(绿色和红色)同时作为边界。那么它是一对一方式还是一对多方式呢?
|
||||
|
||||
图7-14的分割线是我们令$z_1=z_2, z_2=z_3, z_3=z_1$三个等式得到的,但实际上神经网络的工作方式不是这样的,它不会单独比较两类,而是会同时比较三类,这个从Softmax会同时输出三个概率值就可以理解。比如,当我们想得到第一类的分割线时,需要同时满足两个条件:
|
||||
|
||||
$$z_1=z_2,且:z_1=z_3 \tag{4}$$
|
||||
|
||||
即,同时,找到第一类和第三类的边界。
|
||||
|
||||
这就意味着公式4其实是一个线性分段函数,而不是两条直线,即图7-15中红色射线和绿色射线所组成的函数。
|
||||
|
||||
<img src="./img/7/multiple_result_true.png" ch="500" />
|
||||
|
||||
图7-15 分段线性的分割作用
|
||||
|
||||
同理,用于分开红色点和其它两类的分割线是蓝色射线和绿色射线,用于分开绿色点和其它两类的分割线是红色射线和蓝色射线。
|
||||
|
||||
训练一对多分类器时,是把蓝色样本当作一类,把红色和绿色样本混在一起当作另外一类。训练一对一分类器时,是把绿色样本扔掉,只考虑蓝色样本和红色样本。而我们在此并没有这样做,三类样本是同时参与训练的。所以我们只能说神经网络从结果上看,是一种一对多的方式,至于它的实质,我们在后面的非线性分类时再进一步探讨。
|
||||
|
||||
### 代码位置
|
||||
|
||||
ch07, Level2
|
||||
|
|
Загрузка…
Ссылка в новой задаче