一、深度学习概论
1.1 神经网络
激活函数ReLU(基本修正单元Rectified linear unit),一开始是零,后面沿直线上升。
一个神经网络中每一个单元都可能是ReLU或者其他非线性单元。
1.2 监督学习
(1)深度学习的应用领域
在监督学习中,先输入X,然后学习到一个函数,映射输出到Y。
目前深度学习在线上广告投放、图像处理、语音识别、机器翻译、无人驾驶等领域已经发挥了重要的作用。但每一项应用都需要我们合理选择x和y,才能解决特定问题。
(2)一些常用框架
价格预测,通用标准的神经网络框(Standard NN)
图像处理,卷积神经网络CNN(Convolutional NN)
序列处理,如音频,循环神经网络RNN(Recurrent NN)
语言处理,使用更复杂的RNNs
无人驾驶中使用图像和雷达,会使用更复杂的混合的神经网络结构
(3)结构化数据与非结构化数据
结构化数据,是数据的数据库,例如价格预测时会有标准的数据库,广告投放时会有用户信息,广告信息等数据库。
非结构化数据指语音、图像、视频、文本等,相比较结构化数据,更难以让计算机理解。
1.3 关于深度学习
深度学习近年强势发展的主要因素。
以前的算法,比如传统机器学习向量机等,随着数据量的增大,算法的表现基本没有提高。早期数据很少,随着数据的增多,算法有了一些提高,但后期人们进入数据化时代,每个人每天都在产生大量数据,数据量越来越大,但以前的算法却并没有得到更好的结果。
这时如果使用数据训练一个小型的神经网络,算法表现会比以前好很多,如果训练一个更复杂的大型神经网络,那么得到的结果会更好。虽然数据量增大会使训练时间增高,但如今大型神经网络已经帮著人类取得了很多很多成果。
另一方面,以前的SVM向量机的训练好坏更却决于认为手动设计,二现在神经网络更加通用。
其次是计算能力,CPU和GPU运算能力的提升也是深度学习发展的基础。
算法方面的创新也极大地促进了深度学习,许多算法的提出都是为了改善神经网络的性能,增加计算速度。
二、神经网络编程基础
2.1 符号定义
对于想要遍历数据集的情况,尽量不要使用for循环。
例如识别一张猫的图片是否是猫。
首先我们获取一张图片,其由3个矩阵(RGB)组成,我们可以将每一个矩阵所有像素值提取出来列成一个特征向量。这样输入的矩阵维度就是3xheightxwidth。
输入就是特征向量,输出就是标签0/1代表有猫还是没猫。
用数字符号表示为如下:
输入与输出:$(x,y)$
数据集:$m:{(x^{(1)},y^{(1)}),(x^{(2)},y^{(2)}),…,(x^{(m)},y^{(m)})}$
通常会将输入数据$x^{(n)}$以列向量的形式构建为一个输入矩阵
$X=[x^{(1)},x^{(2)},…,x^{(m)}]$
输入矩阵$X\in{R^{n_x\times{m}}}$,其行列数为$X.shape=(nx,m)$。
同样输出数据$y^{(n)}$也会构建为一个输出矩阵
$Y=[y^{(1)},y^{(2)},…,y^{(m)}]$
输入矩阵$Y\in{R^{1\times{m}}}$,其行列数为$Y.shape=(1,m)$。
2.2 二元分类问题——logistic回归方法
(1)二元分类问题
对于一个神经网络的问题,比如图像目标识别,输出的结果$y\in(0,1)$,即图片要么是目标,要么不是。
我们给定了图片输入$x$特征向量,期望得到一个预测值$\hat{y}$,判断图片是不是我们想要的目标,预测的过程相当于求一个概率:
$\hat{y}=P(y=1|x)$
(2)logistic方法
由于二元分类问题,预测结果应为[0,1],因此线性方式一般不使用,因为线性表达,Y可以无穷大也。常用的方法是在线性的基础上再使用sigmoid函数。
$\textcolor{red}{\hat{y}=\sigma(w^Tx+b)}$
其中$w^T\in{R^{n_x}}$是$x$的系数向量,$b\in{R}$是常数,是一个拦截器,$\sigma(z)=\frac{1}{1+e^{-z}}$是sigmoid函数。利用$\sigma(z)$就可以将直线输出转换为[0,1]输出。
(3)损失函数Loss
损失函数Loss是单个样本的预测值与实际值之差
为了训练$w$和$b$,我们需要定义一个损失函数,用来描述$\hat{y}$与$y$的接近程度。再logistic方法中我们使用下面的函数作为损失函数Loss。
$\textcolor{red}{L({\hat{y},y})=-(y\log\hat{y}+(1-y)\log(1-\hat{y}))}$
对于损失函数,我们希望它越小越好。
当$y=1$时,$L(\hat{y},y)=-\log\hat{y}$,可以看出来$\hat{y}$越大($\hat{y}\rightarrow1$),Loss越小。
同理$y=0$时,$L(\hat{y},y)=-\log{(1-\hat{y})}$,可以看出来$\hat{y}$越小($\hat{y}\rightarrow0$),Loss越小。
(再其他方法中可能会用方差作为Loss函数,这里不用是因为方差得到的结果是凹凸的,也就是会有多个局部最优解,不便于后续梯度下降求全局最优解,而上面的Loss函数则解决了这个问题)
(4)成本函数
成本函数是全体样本的预测值与实际值之差
$\textcolor{red}{J(w,b)=\frac{1}{m}\sum^m_{i=1}{L(\hat{y}^{(i)},y^{(i)})}=-\frac{1}{m}(y\log\hat{y}+(1-y)\log(1-\hat{y}))}$
2.3 梯度下降法
成本函数衡量了训练集的预测效果,我们想要的时找到合适的 $w,b$ 使得成本函数 $J(w,b)$ 尽可能小,这就用到了梯度下降法。
由于成本函数 $J(w,b)$ 是凸函数,因此函数形状是如下图这样的,这也是我们为什么定义损失函数那样形式的原因。
我们要做的是初始化一个 $w,b$ 的值,然后让其向梯度下降的方向走,直到找到梯度最低的点。
以 $w$ 为例,我们将重复以下过程:$w=w-\alpha\frac{dJ(w)}{dw}$,来更新 $w$ ,其中 $\alpha$ 是学习率参数,可以看出如果斜率越大,那么每次迭代w变化也越大。
实际中我们会使用下面的方程进行梯度下降求解 $w,b$
$w=w-\alpha\frac{\partial{J(w,b)}}{\partial{w}}$
$b=b-\alpha\frac{\partial{J(w,b)}}{\partial{b}}$
2.4 计算图
计算图 是从左到右的计算,来计算成本函数 $J$。
对于一个流程图,可以很容易的看出 $J$ 的导数。反向传播 ,就是从最终输出反推 $J$ 对各个中间变量以及输入的导数。
在程序里,通常约定编程时使用dvar
代表最终输出变量对于变量var
的导数
代码大致过程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 J = 0 dw1 = 0 dw2 = 0 db = 0 for i = 1 to m zi = w1 * x1 + w2 * x2 +b ai = 1 /(1 +exp(-zi)) J += -[yi * log(ai) + (1 - yi) * log(1 -ai)] dzi = ai -yi dw1 += x1 * dzi dw2 += x2 * dzi db += dzi J/=m dw1/=m dw2/=m db/=m w1=w1-a*dw1 w2=w2-a*dw2 b=b-a*db
2.5 向量化
(1)什么是向量化
由于输入数据$x$和系数$w$都是列向量,对于这两个向量相乘,如果使用非向量化的方法即for循环实现,运算速度非常慢,而如果使用向量化的方法,例如在numpy中使用z=np.dot(w,x)
实现两个向量的点乘,运算速度会快的多。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import timea = np.random.rand(1000000 ) b = np.random.rand(1000000 ) tic = time.time() c = np.dot(a,b) toc = time.time() print ("Vectorized:" + str (1000 *(toc-tic))+"ms" )a = np.random.rand(1000000 ) b = np.random.rand(1000000 ) tic = time.time() for i in range (1000000 ): c += a[i]*b[i] toc = time.time() print ("For Loop:" + str (1000 *(toc-tic))+"ms" )
对于这个小算法区分还不明显,但如果你训练一个神经网络使用非向量化方法需要300小时,向量化方法只需要1个小时,那样差距就非常明显了。
(2)前向传播的向量化
前向传播的目的是计算$z^{(i)}$和$a^{(i)}$
$z^{(i)}=w^Tx^{(i)}+b$
$a^{(i)}=\sigma(z^{(i)})$
可以通过以下方式实现向量化:
$Z=[z^{(1)}\ z^{(2)}\ \cdots\ z^{(m)}]=w^T\cdot{X}+[b\ b\ \cdots\ b]=[w^Tx^{(1)}+b\ w^Tx^{(2)}+b\ \cdots\ w^Tx^{(m)}+b]$
其中$X$是输入矩阵,每列是每一个样本的所有特征,共有m列即共m个样本;$w^T$是参数$w^{(i)}\rightarrow w^{(nx)}$组成的行向量。
以python
程序表示上述过程:
(3)梯度计算的向量化
梯度计算就是计算$dz^{(i)}$、$dw^{(i)}$和$db^{(i)}$
$dz^{(i)}=a^{(i)}-y^{(i)}$
$dw^{(i)}=x^{(i)}dz^{(i)}$
$db^{(i)}=dz^{(i)}$
可以通过以下方式实现向量化:
将$dz^{(i)}$, $dw^{(i)}$和$db^{(i)}$向量化:
$dw=[dw^{(1)}\ dw^{(2)}\ \cdots\ dw^{(m)}]$
$db=[db^{(1)}\ db^{(2)}\ \cdots\ db^{(m)}]$
$dz=[dz^{(1)}\ dz^{(2)}\ \cdots\ dz^{(m)}]$
则$db=\frac{1}{m}\sum^{m}_{i=1}dz^{(i)}$, $dw=\frac{1}{m}Xdz^T$
以python
表达
1 2 3 4 5 6 7 Z=np.dot(w.T,x)+b A=sigmod(Z) dZ=A-Y db=np.sum (dZ)/m dw=np.dot(X,dZ^T)/m w=w-a1*dw b=b-a2*db
(4)总结
为了加快代码计算速度,我们使用了向量化的手段,即尽量使用numpy计算数据集而不是用for循环。
输入矩阵 $X.shape = (nx,m)$,$m$个样本,每个样本$nx$个特征
$X=[x^{(1)}\ x^{(2)}\ \cdots\ x^{(m)}]$,其中 $x^{(i)}=[x^{(i)}_1\ x^{(i)}2\ \cdots\ x^{(i)} {nx}]^T$
参数矩阵 $W.shape=(nx,1)$,每个特征对应一个参数$w$
$W=[w_1\ w_2\ \cdots\ w_{nx}]^T$
参数 $B.shape=(1,m)$ ,每个特征对应一个参数$b$
$B=[b\ b\ \cdots\ b]$
线性值 $Z.shape=(1,m)$,相当于简化版 $\hat y$ ,每个样本都会计算得到一个计算值
$Z=W^TX+b=[W^Tx^{(1)}+b\ \ \ \ W^Tx^{(2)}+b\ \ \cdots\ \ W^Tx^{(m)}+b]$
预测值 $A.shape=(1,m)$,每个样本一个预测值
$A=\hat Y=\sigma{(Z)}$
损失函数 $L.shape=(1,m)$,每个样本的预测值与标定值距离
$L=-Y\log{A}-(1-Y)\log{(1-A)}$
成本函数 $J.spape=(1,1)$,所有损失函数平均值
$J=\frac{1}{m}L.sum()$
导数 $dZ.shape=(1,m)$
$dZ=\frac{dJ}{dZ}=A-Y$
导数 $dW.shape=(nx,1)$
$dW=\frac{1}{m}X\cdot dZ=[\frac{1}{m}dw^{(1)}\ \frac{1}{m}dw^{(2)}\ \cdots\ \frac{1}{m}dw^{(nx)}]^T$
导数 $dB.shape=(1,1)$
$db=\frac{1}{m}dZ.sum()$
2.6 关于numpy与常用函数
最好不要使用秩为1的数组a=np.random.randn(5)
,而要使用$(m,1)$或$(1,n)$的矩阵。也就是直接定义成行列向量a=np.random.randn(5,1)
,或者使用reshape
改变形状。
如果不确定某个向量的维度,可以使用assert声明来判断。
(1)sigmod函数
1 2 3 4 5 6 7 8 9 10 11 12 def sigmoid (x ): """ 参数: x -- 任意形状的numpy数组 返回值: s -- sigmoid(x) """ s=1 /(1 +np.exp(-x)) return s
(2)梯度计算
1 2 3 4 5 6 7 8 9 10 11 12 13 def sigmoid_derivative (x ): """ 参数: x -- numpy数组 返回值: ds -- 计算的梯度值 """ s = 1 /(1 +np.exp(-x)) ds = s*(1 -s) return ds
**(3)输入图片变形为[width x height x 3, 1]
1 2 3 4 5 6 7 8 9 10 11 12 def image2vector (image ): """ 参数: image -- 特定形状的numpy数组 shape(length, height, depth) 返回值: v -- 向量形式的数组 shape(length*height*depth, 1) """ v = image.reshape(image.shape[0 ]*image.shape[1 ]*image.shape[2 ],1 ) return v
(4)归一化处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def normalizeRows (x ): """ 参数: x -- 一个二维numpy数组 shape(n, m) 返回值: x -- 按行归一化后的numpy矩阵 """ x_norm = np.linalg.norm(x,ord =2 ,axis=1 ,keepdims=True ) x=x/x_norm return x
参数
说明
计算方法
ord=默认
二范数:$l_2$
$\sqrt{x_1^2+x_2^2+\dots+x_n^2}$
ord=2
二范数:$l_2$
同上
ord=1
一范数:$l_1$
$\lvert x_1\rvert+\lvert x_2\rvert+\dots+\lvert x_n\rvert$
ord=np.inf
无穷范数:$l_{\infty}$
$MAX\lvert x_i\rvert$
axis=1
按行向量处理
axis=0
按列向量处理
axis=None
矩阵范数
(5)softmax函数
softmax函数是一个规范化函数,用于算法需要分类两个或多个类的情况。
$$softmax(x) = softmax\begin{bmatrix}
x_{11} & x_{12} & x_{13} & \dots & x_{1n} \
x_{21} & x_{22} & x_{23} & \dots & x_{2n} \
\vdots & \vdots & \vdots & \ddots & \vdots \
x_{m1} & x_{m2} & x_{m3} & \dots & x_{mn}
\end{bmatrix} = \begin{bmatrix}
\frac{e^{x_{11}}}{\sum_{j}e^{x_{1j}}} & \frac{e^{x_{12}}}{\sum_{j}e^{x_{1j}}} & \frac{e^{x_{13}}}{\sum_{j}e^{x_{1j}}} & \dots & \frac{e^{x_{1n}}}{\sum_{j}e^{x_{1j}}} \
\frac{e^{x_{21}}}{\sum_{j}e^{x_{2j}}} & \frac{e^{x_{22}}}{\sum_{j}e^{x_{2j}}} & \frac{e^{x_{23}}}{\sum_{j}e^{x_{2j}}} & \dots & \frac{e^{x_{2n}}}{\sum_{j}e^{x_{2j}}} \
\vdots & \vdots & \vdots & \ddots & \vdots \
\frac{e^{x_{m1}}}{\sum_{j}e^{x_{mj}}} & \frac{e^{x_{m2}}}{\sum_{j}e^{x_{mj}}} & \frac{e^{x_{m3}}}{\sum_{j}e^{x_{mj}}} & \dots & \frac{e^{x_{mn}}}{\sum_{j}e^{x_{mj}}}
\end{bmatrix} = \begin{pmatrix}
softmax\text{(first row of x)} \
softmax\text{(second row of x)} \
… \
softmax\text{(last row of x)} \
\end{pmatrix} $$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def softmax (x ): """ 参数: x -- 一个二维numpy数组 shape(n, m) 返回值: x -- softmax后的numpy矩阵 """ x_exp = np.exp(x) x_sum = np.sum (x_exp, axis = 1 , keepdims = True ) s = x_exp / x_sum return s
(6)Loss函数
L1 Loss函数,用于评价模型的表现,loss越大,说明预测值和真实值的偏差预约。
$$\begin{align*} & L_1(\hat{y}, y) = \sum_{i=0}^m|y^{(i)} - \hat{y}^{(i)}| \end{align*}\tag{6}$$
1 2 3 4 5 6 7 8 9 10 11 12 13 def L1 (yhat, y ): """ 参数: yhat -- 长度m的向量(预测值) y -- 长度m的向量(真实值) 返回值: loss -- 上面定义的L1 Loss值 """ loss = np.sum (np.abs (y-yhat)) return loss
L2 Loss函数。
$$\begin{align*} & L_2(\hat{y},y) = \sum_{i=0}^m(y^{(i)} - \hat{y}^{(i)})^2 \end{align*}\tag{7}$$
1 2 3 4 5 6 7 8 9 10 11 12 13 def L2 (yhat, y ): """ 参数: yhat -- 长度m的向量(预测值) y -- 长度m的向量(真实值) 返回值: loss -- 上面定义的L2 Loss值 """ loss = np.sum (np.dot(y-yhat,y-yhat)) return loss
2.7 总结
(1)数学过程
二分神经网络目的是给定一个输入 $x$(可以是图片等),输出预测值 $\hat y$,并尽可能让预测值 $\hat y$ 接近实际值 $y$,即:
$\hat{y}=P(y=1|x)$
而输出预测值 $\hat y$ 与输入 $x$ 之间的函数关系可以表示为(在线性表达的基础上加上sigmod函数,保证输出位于[0,1]区间)
$$\hat y=\sigma{(wx+b)}$$
其中定义 $z=wx+b$,$\sigma(z)=\frac{1}{1+e^{-z}}$
这其中我们需要做的就是找到最合适的 $w$ 和 $b$ ,使输出 $\hat y$ 尽可能接近 $y$。
因此为了衡量 $\hat y$ 与 $y$ 的接近程度,我们定义了一个损失函数Loss,只要让损失函数足够小,就能保证 $\hat y$ 与 $y$ 足够接近。
$$L(\hat y,y)=-y\log{\hat y}-(1-y)\log{(1-\hat y)}$$
当 $y=1$ 时, $L(\hat y,y)=-\log{\hat y}$,我们想要 $L(\hat y,y)$ 足够小,就要让 $\hat y$ 足够大即 $\hat y\rightarrow y=1$(由于时二分问题,$y\in [0,1]$)
当 $y=0$ 时, $L(\hat y,y)=-\log{(1-\hat y)}$,我们想要 $L(\hat y,y)$ 足够小,就要让 $\hat y$ 足够小即 $\hat y\rightarrow y=0$。
以上就说明了为什么这个损失函数能够描述 $\hat y$ 与 $y$ 的接近程度。
而上面只是一个样本 $x$ 的损失函数,而对于一个问题一般会输入大量的样本,假设有 $m$ 个,对于每一个样本都需要按照上述方法计算一个损失函数,而所有损失函数的平均值就是我们的输入的整体成本函数。
$$J(w,b)=\frac{1}{m}\sum^{n_x}_{i=1}L(\hat y^{(i)},y^{(i)})$$
因此训练的目的就变成了找到让 $J(w,b)$ 的极小值时的 $w$ 和 $b$。
因此每次训练迭代,都需要对成本函数求导 $\frac{\partial J}{\partial w}$ 和 $\frac{\partial J}{\partial b}$,然后根据求导结果更新 $w=w-\alpha \frac{\partial J}{\partial w}$ 和 $b=b-\frac{\partial J}{\partial w}$,直到找到 $J_{min}$ 对应的 $w$ 和 $b$,此时训练就完成了。
(2)代码过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 import numpy as npimport matplotlib.pyplot as pltimport h5py import scipy from PIL import Image from scipy import ndimage def load_dataset (): train_dataset = h5py.File('datasets/train_catvnoncat.h5' , "r" ) train_set_x_orig = np.array(train_dataset["train_set_x" ][:]) train_set_y_orig = np.array(train_dataset["train_set_y" ][:]) test_dataset = h5py.File('datasets/test_catvnoncat.h5' , "r" ) test_set_x_orig = np.array(test_dataset["test_set_x" ][:]) test_set_y_orig = np.array(test_dataset["test_set_y" ][:]) classes = np.array(test_dataset["list_classes" ][:]) train_set_y_orig = train_set_y_orig.reshape((1 , train_set_y_orig.shape[0 ])) test_set_y_orig = test_set_y_orig.reshape((1 , test_set_y_orig.shape[0 ])) return train_set_x_orig, train_set_y_orig, test_set_x_orig, test_set_y_orig, classes def sigmoid (x ): """ 参数: x -- numpy数组 返回值: s -- 计算的sigmoid值,sigmoid(z)=1/(1+e^(-z)) """ s = 1 /(1 +np.exp(-x)) return s def initialize_with_zeros (dim ): """ 参数: dim -- w向量的长度 返回值: w -- 初始化的向量, w.shape() -> (dim, 1) b -- 初始化的标量, 偏置值b """ w = np.zeros([dim,1 ]) b = 0 assert (w.shape == (dim, 1 )) assert (isinstance (b, float ) or isinstance (b, int )) return w, b def propagate (w, b, X, Y ): """ 参数: w -- weights权重, numpy数组 w.shape -> (num_px * num_px * 3, 1) b -- bias偏置, 标量 X -- 数据 X.shape -> (num_px * num_px * 3, number of examples) Y -- 标签向量 (0非猫; 1猫) Y.shape -> (1, number of examples) 返回值: cost -- 成本函数(逻辑回归的负对数) dw -- 损失函数对w的梯度,与w维度相同 db -- 损失函数对b的梯度,与b维度相同 """ m = X.shape[1 ] A = sigmoid(np.dot(w.T,X) + b) cost = - 1 /m * np.sum (Y*np.log(A) + (1 -Y)*np.log(1 -A)) dw = 1 /m * np.dot(X, (A-Y).T) db = 1 /m * np.sum (A-Y) assert (dw.shape == w.shape) assert (db.dtype == float ) cost = np.squeeze(cost) assert (cost.shape == ()) grads = {"dw" : dw, "db" : db} return grads, cost def optimize (w, b, X, Y, num_iterations, learning_rate, print_cost = False ): """ 参数: w -- weights权重, numpy数组 w.shape -> (num_px * num_px * 3, 1) b -- bias偏置, 标量 X -- 数据 X.shape -> (num_px * num_px * 3, number of examples) Y -- 标签向量 (0非猫; 1猫) Y.shape -> (1, number of examples) num_iterations -- 优化循环的迭代次数 learning_rate -- 学习率 print_cost -- True则每100次打印1次成本函数 返回值: params -- 包括 权重w和偏置b 的字典 grads -- 包括 成本函数对w和b梯度 的字典 costs -- 优化过程中所有的成本函数列表 """ costs = [] for i in range (num_iterations): grads, cost = propagate(w, b, X, Y) dw = grads["dw" ] db = grads["db" ] w = w - learning_rate * dw b = b - learning_rate * db if i % 100 == 0 : costs.append(cost) if print_cost and i % 100 == 0 : print ("Cost after iteration %i: %f" %(i, cost)) params = {"w" : w, "b" : b} grads = {"dw" : dw, "db" : db} return params, grads, costs def predict (w, b, X ): ''' 参数: w -- weights权重, numpy数组 w.shape -> (num_px * num_px * 3, 1) b -- bias偏置, 标量 X -- 数据 X.shape -> (num_px * num_px * 3, number of examples) 返回值: Y_prediction -- numpy向量包含对所有X样本的预测值 (0/1) ''' m = X.shape[1 ] Y_prediction = np.zeros((1 ,m)) w = w.reshape(X.shape[0 ], 1 ) A = sigmoid(np.dot(w.T, X) + b) for i in range (A.shape[1 ]): if A[0 ,i] < 0.5 : Y_prediction[0 ,i] = 0 else : Y_prediction[0 ,i] = 1 assert (Y_prediction.shape == (1 , m)) return Y_prediction def model (X_train, Y_train, X_test, Y_test, num_iterations = 2000 , learning_rate = 0.5 , print_cost = False ): """ 参数: X_train -- 训练数据 (num_px * num_px * 3, m_train) Y_train -- 训练数据标签 (1, m_train) X_test -- 测试数据 (num_px * num_px * 3, m_test) Y_test -- 测试数据标签 (1, m_test) num_iterations -- 迭代次数 learning_rate -- 学习率 print_cost -- 是否打印成本函数 返回值: d -- 包含模型信息的字典 """ w, b = initialize_with_zeros(X_train.shape[0 ]) params, grads, costs = optimize(w, b, X_train, Y_train, num_iterations, learning_rate, print_cost) w, b = params["w" ], params["b" ] Y_prediction_train = predict(w, b, X_train); Y_prediction_test = predict(w, b, X_test) print ("train accuracy: {} %" .format (100 - np.mean(np.abs (Y_prediction_train - Y_train)) * 100 )) print ("test accuracy: {} %" .format (100 - np.mean(np.abs (Y_prediction_test - Y_test)) * 100 )) d = {"costs" : costs, "Y_prediction_test" : Y_prediction_test, "Y_prediction_train" : Y_prediction_train, "w" : w, "b" : b, "learning_rate" : learning_rate, "num_iterations" : num_iterations} return d train_set_x_orig, train_set_y, test_set_x_orig, test_set_y, classes = load_dataset() m_train = train_set_x_orig.shape[0 ] m_test = test_set_x_orig.shape[0 ] num_px = train_set_x_orig.shape[1 ] train_set_x_flatten = train_set_x_orig.reshape(train_set_x_orig.shape[0 ],-1 ).T test_set_x_flatten = test_set_x_orig.reshape(test_set_x_orig.shape[0 ],-1 ).T train_set_x = train_set_x_flatten/255 test_set_x = test_set_x_flatten/255 d = model(train_set_x, train_set_y, test_set_x, test_set_y, num_iterations = 20000 , learning_rate = 0.005 , print_cost = True ) index = 1 plt.imshow(test_set_x[:,index].reshape((num_px, num_px, 3 ))) print ("y = " + str (test_set_y[0 ,index]) + ", you predicted that it is a \"" + classes[int (d["Y_prediction_test" ][0 ,index])].decode("utf-8" ) + "\" picture." ) costs = np.squeeze(d['costs' ]) plt.plot(costs) plt.ylabel('cost' ) plt.xlabel('iterations (per hundreds)' ) plt.title("Learning rate =" + str (d["learning_rate" ])) plt.show()
三、神经网络
3.1 神经网络的表示
一般的神经网络包括:输入层、隐藏层、输出层、输出值。
隐藏层:在训练集中,这些节点的数值我们不知道,我们能看到输入值、输出值,但是中间值我们在训练是看不到的,所以叫隐藏层。
上图就是标准的二层神经网络(输入层不算是标准的层),或者称为单隐层神经网络。
上面的网络中,输入层也可以用$a^{[0]}$表示,$a^{[0]}=X$,中间层可以用$a^{[1]}$表示,$a^{[1]}=[a^{[1]}_1 a^{[1]}_2 \cdots a^{[1]}_n]$,输出层可以用$a^{[2]}$表示。我们用方括号表示神经网络的层数。其中隐藏层和输出层有着各自的参数,分别记作$w^{[1]}, b^{[1]}$和$w{[2]}, b{[2]}$。
3.2 神经网络的计算
神经网络的计算过程,就是上一节中的回归计算的多次叠加。下图为上一节所说的逻辑回归的流程。
对应于二层神经网络中是如下图的部分(隐藏层与输入层之间),每一个隐层单元的计算过程都是同样的。
输出层与隐藏层之间的计算,也和隐藏层与输入层类似。只不过将隐藏层计算的$a^{[1]}$看作输入来进行回归运算。
用数学公式表示二层神经网络的计算过程:
$$z^{[1]}=W^{[1]}x+b^{[1]}$$
$$a^{[1]}=\sigma (z^{[1]})$$
$$z^{[2]}=W^{[2]}a^{[1]}+b^{[2]}$$
$$a^{[2]}=\sigma (z^{[2]})$$
3.3 激活函数
(1)$\sigma$函数
前面我们提到激活函数,都是使用的$\sigma$函数[0, 1],但实际中几乎不使用该函数。
(2)tanh函数
更一般的情况下,我们会使用其他非线性函数例如$tanh = \frac{e^z-e^{-z}}{e^z+e^{-z}}$函数[-1, 1]。事实证明,对于隐藏层的激活函数,如果使用tanh效果几乎总比$\sigma$好。对于输出层,由于$\hat y \in [0, 1]$,因此使用$\sigma$函数效果更好。
(3)ReLU函数
但是无论是$\sigma$还是tanh,我们都可以看出在z非常大或非常小时,激活函数的斜率都很小,这会严重影响梯度下降找到最优解的速度。因此人们提出了线性修正单元ReLU。
还有一种带泄露的线性修正单元 Leaky ReLU,在z<0时a不为0。
(4)为什么需要非线性激活函数
如果不使用机器学习,那么之前所说的二层神经网络的前向计算过程:
$$z^{[1]}=W^{[1]}x+b^{[1]}$$
$$a^{[1]}=\sigma (z^{[1]})$$
$$z^{[2]}=W^{[2]}a^{[1]}+b^{[2]}$$
$$a^{[2]}=\sigma (z^{[2]})$$
令$a^{[1]}=z^{[1]}$,那么计算过程就可以写成下面的形式:
$$z^{[2]}=W^{[2]}z^{[1]}+b^{[2]}=W^{[2]}(W^{[1]}x+b^{[1]})+b^{[2]}=W’x+b’$$
化简出来$z^{[2]}$与x之间可以线性表示,那么也就是说无论网络有多少层,都可以将输出和输入用一个线性函数表示,那么神经网络的作用就没法体现了。因此激活函数是十分必要的。
从另一个角度来说,神经网络模拟的就是神经元的信号传递过程,ReLU等激活函数,模拟的就是神经元的放电阈值,当电刺激达到一定阈值才会向下一神经元发送信号,如果激活函数达到阈值才会输出,否则就是0,也是为了去掉某些不重要的样本特征,防止出现过拟合等现象。
(5)激活函数的导数
如果是sigmod函数,即$g(z)=\frac{1}{1+e^{-z}}$,则容易计算得到$\frac{d}{dz}g(z)=\frac{1}{1+e^{-z}}] (1-\frac{1}{1+e^{-z}})=g(z)(1-g(z))$。
z->+∞时,g(z)->1,g’(z)->0;
z->-∞时,g(z)->0,g’(z)->0
z=0是,g(z)=0.5,g’(z)=0.25
如果是tanh函数,即$g(z)=\frac{e^z-e^{-z}}{e^z+e^{-z}}$,则容易计算得到$\frac{d}{dz}g(z)=1-(tanh(z))^2$。
z->+∞时,g(z)->1,g’(z)->0;
z->-∞时,g(z)->-1,g’(z)->0
z=0是,g(z)=0,g’(z)=1
如果是ReLU函数,即$g(z)=max(0,z)$,则容易计算得到
$g’(z)=0, if z<0$
$g’(z)=1, if z>=0$
(其中z=0时可以在程序中设置导数为1,避免不可导问题)
3.4 神经网络的梯度下降
对于一个神经网络,我们有以下参数:$w^{[1]}, b^{[1]}, w^{[2]}, b^{[2]}$
我们计算的损失函数:
$J(w^{[1]}, b^{[1]}, w^{[2]}, b^{[2]})=\frac{1}{m} \sum^n_{i=1}l(\hat y, y)$
梯度下降过程重复以下步骤:
计算预测值$\hat y$
计算导数:$d(w^{[1]})=\frac{\partial J}{\partial w^{[1]}}$,$d(b^{[1]})=\frac{\partial J}{\partial b^{[1]}}$
更新参数:$w^{[1]}=w^{[1]}-\alpha \frac{\partial J}{\partial w^{[1]}}$,$b^{[1]}=b^{[1]}-\alpha \frac{\partial J}{\partial b^{[1]}}$
向量化之后反向传播的计算方法如下:
$$dZ^{[2]}=A^{[2]}-Y$$
$$dW^{[2]}=\frac{1}{m}dZ^{[2]}A^{[1]T}$$
$$db^{[2]}=dZ^{[2]}$$
$$dZ^{[1]}=W^{[2]T}dZ^{[2]}*g^{[1]'}(Z^{[1]})$$
$$dW^{[1]}=\frac{1}{m}dZ^{[1]}X^T$$
$$db^{[1]}=dZ^{[1]}$$
3.5 参数初始化
对于神经网络的各层权重参数,我们都需要进行初始化,但是如果只是初始化为0,那么神经网络将完全无效,因此我们需要进行随机初始化。
可以令
W[1]=np.random.randn((2,2))*0.01
b[1]=np.zero((2,1))
W[1]=np.random.randn((1,2))*0.01
b[1]=0
乘0.01是因为我们比较喜欢让权重尽可能小,这样z也将会比较小,更容易落在激活函数斜率大的区域,让神经网络回归的更快。
3.6 代码实现
(1)安装包
在实现神经网络过程中需要导入一些必要的包
numpy:基础的科学计算包
sklearn:提供了数据挖掘和数据分析的简单有效的工具
matplotlib:画图工具
(2)神经网络
建立神经网络的方法
定义神经网络结构(输入单元数,隐藏单元数等)
初始化模型的参数
循环:
实现前向传播
计算损失函数
后向传播获得梯度
梯度下降更新参数
定义神经网络结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def layer_sizes (X, Y ): """ 参数: X -- 输入数据集,shape(input size, number of examples) Y -- 标签,shape(output size, number of examples) 返回值: n_x -- 输入层的大小 n_h -- 隐藏层的大小 n_y -- 输出层的大小 """ n_x = X.shape[0 ] n_h = 4 n_y = Y.shape[0 ] return (n_x, n_h, n_y)
初始化模型参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 def initialize_parameters (n_x, n_h, n_y ): """ 参数: n_x -- 输入层的大小 n_h -- 隐藏层的大小 n_y -- 输出层的大小 返回值: params -- python字典包含以下参数: W1 -- 权重矩阵,shape (n_h, n_x) b1 -- 偏置向量,shape (n_h, 1) W2 -- 权重矩阵,shape (n_y, n_h) b2 -- 偏置向量,shape (n_y, 1) """ np.random.seed(2 ) W1 = np.random.randn(n_h, n_x) b1 = np.zeros((n_h, 1 )) W2 = np.random.randn(n_y, n_h) b2 = np.zeros((n_y, 1 )) assert (W1.shape == (n_h, n_x)) assert (b1.shape == (n_h, 1 )) assert (W2.shape == (n_y, n_h)) assert (b2.shape == (n_y, 1 )) parameters = {"W1" : W1, "b1" : b1, "W2" : W2, "b2" : b2} return parameters
前向传播计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 def forward_propagation (X, parameters ): """ 参数: X -- 输入数据,size (n_x, m) parameters -- 包含所有参数的字典 (上面初始化函数的输出) 返回值: A2 -- 第二次激活后的输出 cache -- 包含 "Z1", "A1", "Z2", "A2" 的字典 """ W1 = parameters["W1" ] b1 = parameters["b1" ] W2 = parameters["W2" ] b2 = parameters["b2" ] Z1 = np.dot(W1, X) + b1 A1 = np.tanh(Z1) Z2 = np.dot(W2, A1) + b2 A2 = np.tanh(Z2) assert (A2.shape == (1 , X.shape[1 ])) cache = {"Z1" : Z1, "A1" : A1, "Z2" : Z2, "A2" : A2} return A2, cache
计算损失函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def compute_cost (A2, Y, parameters ): """ 参数: A2 -- 第二次激活函数的输出,shape (1, number of examples) Y -- 真值向量,shape (1, number of examples) parameters -- 包含所有参数的字典 返回值: cost -- 损失函数 """ m = Y.shape[1 ] logprobs = np.multiply(np.log(A2), Y) + np.multiply((1 - Y), np.log(1 - A2)) cost = - np.sum (logprobs) / m cost = np.squeeze(cost) assert (isinstance (cost, float )) return cost
反向传播
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 def backward_propagation (parameters, cache, X, Y ): """ 参数: parameters -- 包含所有参数的字典 cache -- 包含 "Z1", "A1", "Z2", "A2" 的字典 X -- 输入数据,shape (2, number of examples) Y -- 真值向量,shape (1, number of examples) 返回值: grads -- 包含所有参数的梯度值的字典 """ m = X.shape[1 ] W1 = parameters["W1" ] W2 = parameters["W2" ] A1 = cache["A1" ] A2 = cache["A2" ] dZ2 = A2 - Y dW2 = 1 /m * np.dot(dZ2, A2.T) db2 = 1 /m * np.sum (dZ2, axis = 1 , keepdims = True ) dZ1 = np.multiply(np.dot(W2.T, dZ2), (1 - np.power(A1, 2 ))) dW1 = 1 /m * np.dot(dZ1, X.T) db1 = 1 /m * np.sum (dZ1, axis = 1 , keepdims = True ) grads = {"dW1" : dW1, "db1" : db1, "dW2" : dW2, "db2" : db2} return grads
更新参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 def update_parameters (parameters, grads, learning_rate = 1.2 ): """ 参数: parameters -- 包含所有参数的字典 grads -- 包含所有梯度的字典 返回值: parameters -- 包含所有更新后的参数的字典 """ W1 = parameters["W1" ] b1 = parameters["b1" ] W2 = parameters["W2" ] b2 = parameters["b2" ] dW1 = grads["dW1" ] db1 = grads["db1" ] dW2 = grads["dW2" ] db2 = grads["db2" ] W1 = W1 - learning_rate * dW1 b1 = b1 - learning_rate * db1 W2 = W2 - learning_rate * dW2 b2 = b2 - learning_rate * db2 parameters = {"W1" : W1, "b1" : b1, "W2" : W2, "b2" : b2} return parameters
函数整合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 def nn_model (X, Y, n_h, num_iterations = 10000 , print_cost=False ): """ 参数: X -- 数据集,shape (2, number of examples) Y -- 标签,shape (1, number of examples) n_h -- 隐藏层的大小 num_iterations -- 梯度下降循环的迭代次数 print_cost -- True则每1000次迭代打印cost 返回值: parameters -- 模型学习到的参数,可以被用来预测 """ np.random.seed(3 ) n_x = layer_sizes(X, Y)[0 ] n_y = layer_sizes(X, Y)[2 ] parameters = initialize_parameters(n_x, n_h, n_y) W1 = parameters["W1" ] b1 = parameters["b1" ] W2 = parameters["W2" ] b2 = parameters["b2" ] for i in range (0 , num_iterations): A2, cache = forward_propagation(X, parameters) cost = compute_cost(A2, Y, parameters) grads = backward_propagation(parameters, cache, X, Y) parameters = update_parameters(parameters, grads) if print_cost and i % 1000 == 0 : print ("Cost after iteration %i: %f" %(i, cost)) return parameters
预测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def predict (parameters, X ): """ 参数: parameters -- 包含训练好的参数的字典 X -- 输入数据,size (n_x, m) 返回值: predictions -- 模型的预测结果向量 """ A2, cache = forward_propagation(X, parameters) predictions = np.round (A2) return predictions
四、深层神经网络
4.1 前向传播
(1)数学计算
$$z^{[1]}=W^{[1]}x+b^{[1]}$$
$$a^{[1]}=g(z^{[1]})$$
$$z^{[2]}=W^{[2]}a^{[1]}+b^{[2]}$$
$$a^{[2]}=g(z^{[2]})$$
$$\cdots$$
$$z^{[l]}=W^{[l]}a^{[l-1]}+b^{[l]}$$
$$a^{[l]}=g(z^{[l]})$$
(2)向量化
$$Z^{[l]}=W^{[l]}A^{[l-1]}+b^{[l]}$$
$$A^{[l]}=g(Z^{[l]})$$
4.2 核对矩阵维数
(1)推导过程
对于计算过程:
$$Z^{[l]}=W^{[l]}A^{[l-1]}+b^{[l]}$$
由于$Z^{[l]}$的行数等于每一层神经元单元数$n^{l}$,列数等于样本数$m$,因此其中的$Z^{[l]}$的维数为$(n^{[l]}, m)$。
$A^{[l-1]}$是由$Z^{[l-1]}$经过激活函数计算而得,因此维数与$Z^{[l-1]}$相同为$(n^{[l-1]},1)$。
根据矩阵计算的规则,可以知道$W^{[l]}$的维数为$(n^{[l]}, n^{[l-1]})$,$b$的维数应该与$Z^{[l]}$相同$(n^{[l]}, m)$,但是由于python的广播规则,因此程序中$b$的维数通常是$(n^{[l]}, 1)$
(2)维数总结
$$W^{[l]}:(n^{[l]}, n^{[l-1]})$$
$$b^{[l]}:(n^{[l]}, 1)$$
$$dW^{[l]}:(n^{[l]}, n^{[l-1]})$$
$$db^{[l]}:(n^{[l]}, 1)$$
4.3 参数和超参数
深度学习中我们用到的参数有:
权值:$W^{[l]}$
偏置:$b^{[b]}$
超参数包括:
学习率:$\alpha$
迭代次数
隐藏层层数:L
隐藏层单元数:$n^{[l]}$
激活函数的选择
所有的超参数都可以一定程度上决定最终得到的参数W和b。我们可以通过设定不同的超参数通过观察成本函数变化来判断当前超参数是否是最合适的,通常情况下超参数需要不断尝试检验才能找到对于当前问题最好的一个值。
4.4 关键步骤编程实现
(1)初始化参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def initialize_parameters_deep (layer_dims ): """ 参数: layer_dims -- 包含每一层维度的列表 返回值: parameters -- 包含 "W1", "b1", ..., "WL", "bL" 的字典 Wl -- 权值矩阵,shape (layer_dims[l], layer_dims[l-1]) bl -- 偏置向量,shape (layer_dims[l], 1) """ np.random.seed(3 ) parameters = {} L = len (layer_dims) for l in range (1 , L): parameters["W" + str (l)] = np.random.randn(layer_dims[l], layer_dims[l - 1 ]) * 0.01 parameters["b" + str (l)] = np.zeros((layer_dims[l], 1 )) assert (parameters['W' + str (l)].shape == (layer_dims[l], layer_dims[l-1 ])) assert (parameters['b' + str (l)].shape == (layer_dims[l], 1 )) return parameters
(2)前向传播线性激活模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 def linear_activation_forward (A_prev, W, b, activation ): """ 参数: A_prev -- 上一层激活后的结果 (或输入数据),shape(上一层单元数, 样本数) W -- 权值矩阵,shape (当前层单元数, 上一层单元数) b -- 偏置向量,shape (当前层单元数, 1) activation -- 本层使用的激活函数, 字符串格式: "sigmoid" or "relu" 返回值: A -- 激活函数后的输出 cache -- 包含 "linear_cache" and "activation_cache" 的字典,便于反向传播的计算 """ if activation == "sigmoid" : Z = np.dot(W, A_prev) + b A, activation_cache = sigmoid(Z) elif activation == "relu" : Z = np.dot(W, A_prev) + b A, activation_cache = relu(Z) assert (A.shape == (W.shape[0 ], A_prev.shape[1 ])) cache = (linear_cache, activation_cache) return A, cache
(3)L层神经网络
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 def L_model_forward (X, parameters ): """ 参数: X -- 输入数据,shape (input size, number of examples) parameters -- 初始化参数,initialize_parameters_deep()的输出 返回值: AL -- 激活函数的输出 caches -- 包括 每层linear_relu_forward()的cache (一共L-1个, 索引从0到L-2) linear_sigmoid_forward()的cache (只有一个, 索引为L-1) """ caches = [] A = X L = len (parameters) // 2 for l in range (1 , L): A_prev = A A, cache = linear_activation_forward(A_prev, parameters["W" + str (l)], parameters["b" + str (l)], "relu" ) caches.append(cache) AL, cache = linear_activation_forward(A, parameters["W" + str (L)], parameters["b" + str (L)], "sigmoid" ) caches.append(cache) assert (AL.shape == (1 ,X.shape[1 ])) return AL, caches
(4)成本函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def compute_cost (AL, Y ): """ 参数: AL -- 与输入的标签匹配的预测值向量, shape(1, number of examples) Y -- 真值标签向量 (例如: 不是猫为0,猫为1), shape (1, number of examples) 返回值: cost -- 成本函数 """ m = Y.shape[1 ] cost = -1 / m * np.sum (Y * np.log(AL) + (1 -Y) * np.log(1 -AL),axis=1 ,keepdims=True ) cost = np.squeeze(cost) assert (cost.shape == ()) return cost
(5)线性部分反向传播
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 def linear_backward (dZ, cache ): """ 参数: dZ -- 第L层的线性输出的梯度 cache -- (A_prev, W, b)组成的元组,来自当前层的前向传播 返回值: dA_prev -- 上一层激活函数后的梯度 dW -- W的梯度 db -- b的梯度 """ A_prev, W, b = cache m = A_prev.shape[1 ] dW = 1 /m * np.dot(dZ, A_prev.T) db = 1 /m * np.sum (dZ,axis=1 , keepdims=True ) dA_prev = np.dot(W.T, dZ) assert (dA_prev.shape == A_prev.shape) assert (dW.shape == W.shape) assert (db.shape == b.shape) return dA_prev, dW, db
(6)线性激活部分反向传播
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def linear_activation_backward (dA, cache, activation ): """ 参数: dA -- 当前层激活后的梯度 cache -- (linear_cache, activation_cache)组成的元组,为了反向传播计算更快 activation -- 当前层所用的激活函数, 以字符串格式存储: "sigmoid" or "relu" 返回值: dA_prev -- 上一层激活后的梯度 dW -- W的梯度 db -- b的梯度 """ linear_cache, activation_cache = cache if activation == "relu" : dZ = relu_backward(dA, activation_cache) dA_prev, dW, db = linear_backward(dZ, linear_cache) elif activation == "sigmoid" : dZ = sigmoid_backward(dA, activation_cache) dA_prev, dW, db = linear_backward(dZ, linear_cache) return dA_prev, dW, db
(7)线性模型反向传播
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 def L_model_backward (AL, Y, caches ): """ 参数: AL -- 概率向量, 前向传播的输出(L_model_forward()) Y -- 数据集真值向量 caches -- 列表包括 linear_activation_forward() 函数的cache (是caches[l], for l in range(L-1) i.e l = 0...L-2) linear_activation_forward() 函数的cache (是caches[L-1]) 返回值: grads -- 梯度的字典 grads["dA" + str(l)] = ... grads["dW" + str(l)] = ... grads["db" + str(l)] = ... """ grads = {} L = len (caches) m = AL.shape[1 ] Y = Y.reshape(AL.shape) dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL)) current_cache = caches[L-1 ] grads["dA" + str (L)], grads["dW" + str (L)], grads["db" + str (L)] = linear_activation_backward(dAL, current_cache, activation = "sigmoid" ) for l in reversed (range (L - 1 )): current_cache = caches[l] dA_prev_temp, dW_temp, db_temp = linear_activation_backward(grads["dA" + str (l+2 )], current_cache, activation = "relu" ) grads["dA" + str (l + 1 )] = dA_prev_temp grads["dW" + str (l + 1 )] = dW_temp grads["db" + str (l + 1 )] = db_temp return grads
(8)参数更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def update_parameters (parameters, grads, learning_rate ): """ 参数: parameters -- 包含所有参数的python字典 grads -- 包含所有梯度的字典, L_model_backward 函数的输出 learning_rate -- 学习率 返回值: parameters -- 包含所有更新后参数的字典 parameters["W" + str(l)] = parameters["W" + str(l)] - learning_rate * grads["dW" + str(l + 1)] parameters["b" + str(l)] = parameters["b" + str(l)] - learning_rate * grads["db" + str(l + 1)] """ L = len (parameters) // 2 for l in range (L): parameters["W" + str (l + 1 )] -= learning_rate * grads["dW" + str (l + 1 )] parameters["b" + str (l + 1 )] -= learning_rate * grads["db" + str (l + 1 )] return parameters