神经网络的编程基础(吴恩达课程笔记)
2.7 计算图
- 前向过程:计算网络的输出
- 反向过程:计算对应的梯度或导数
2.8 使用计算图求导数
当输入变化时,知道导数可以轻松计算出输出的变化。
反向传播:链式求导法则
d
J
d
a
=
d
J
d
v
⋅
d
v
d
a
\dfrac{dJ}{da}=\dfrac{dJ}{dv}·\dfrac{dv}{da}
dadJ=dvdJ⋅dadv
FinalOutputVar:简写为dJ,最终的输出变量
dvar指最终输出变量对中间变量var的导数。
d
J
d
v
a
r
→
d
v
a
r
(
p
y
t
h
o
n
)
\dfrac{dJ}{dvar} \to dvar (python)
dvardJ→dvar(python)
2.9逻辑回归中的梯度下降
z = w T x + b y ^ = a = σ ( z ) = s i g m o i d ( z ) = 1 1 + e − z L ( a , y ) = − ( y log ( a ) + ( 1 − y ) log ( 1 − a ) ) z = w^{T}x+b \\ \hat{y}=a=\sigma(z)=sigmoid(z)=\frac{1}{1+e^{-z}}\\ L(a,y)=-(y\log(a)+(1-y)\log(1-a)) z=wTx+by^=a=σ(z)=sigmoid(z)=1+e−z1L(a,y)=−(ylog(a)+(1−y)log(1−a))
a是logistic回归的输出,y是样本的标签值。
1. 计算导数(derivative computation)
- 1.反向传播第一步:计算L对a的导数,使用如上的(3)式。
d a = − y a + 1 − y 1 − a da=-\frac{y}{a}+\frac{1-y}{1-a} da=−ay+1−a1−y
- 2.反向传播第二步:计算a对z的导数,使用如上(2)式,并化成变量为a的式子。
d a d z = a ⋅ ( 1 − a ) d z = d L d z = d L d a ⋅ d a d z = a − y d a d z = e − z ( 1 + e − z ) 2 = e − z ⋅ a 2 = 1 − a a ⋅ a 2 = a ( 1 − a ) \dfrac{da}{dz}=a·(1-a)\\ dz=\dfrac{dL}{dz}=\dfrac{dL}{da}·\dfrac{da}{dz}=a-y\\ \dfrac{da}{dz}=\frac{e^{-z}}{(1+e^{-z})^{2}}={e^{-z}}·a^{2}=\frac{1-a}{a}·a^{2}=a(1-a) dzda=a⋅(1−a)dz=dzdL=dadL⋅dzda=a−ydzda=(1+e−z)2e−z=e−z⋅a2=a1−a⋅a2=a(1−a)
- 以两个参数为例:
z = w 1 x 1 + w 2 x 2 + b z=w_1x_1+w_2x_2+b z=w1x1+w2x2+b
- 3.反向传播第三步:计算z对w和b的导数dw1,dw2。
∂ L ∂ w 1 = d w 1 = x 1 ⋅ d z ∂ L ∂ w 2 = d w 2 = x 2 ⋅ d z ∂ L ∂ b = d b = d z \dfrac{\partial L}{\partial w_1}=dw_1=x_1·dz\\ \dfrac{\partial L}{\partial w_2}=dw_2=x_2·dz\\ \dfrac{\partial L}{\partial b}=db=dz ∂w1∂L=dw1=x1⋅dz∂w2∂L=dw2=x2⋅dz∂b∂L=db=dz
2.实现单个样本实例的梯度下降(logistic回归)(gradient descent)
- 更新w、b,以下即为单个样本实例的一次梯度更新步骤。
w 1 : = w 1 − α ⋅ d w 1 w 2 : = w 2 − α ⋅ d w 2 b : = b − α ⋅ d b w_1:=w_1-\alpha·dw_1\\ w_2:=w_2-\alpha·dw_2\\ b:=b-\alpha·db w1:=w1−α⋅dw1w2:=w2−α⋅dw2b:=b−α⋅db
2.10 m个样本的梯度下降(gradient descen on m examples)
成本函数的定义:
J
(
w
,
b
)
=
1
m
∑
0
<
i
≤
m
L
(
a
(
i
)
,
y
(
i
)
)
a
(
i
)
=
y
^
(
i
)
=
σ
(
z
(
i
)
)
=
σ
(
w
T
x
(
i
)
+
b
)
J(w,b)=\frac{1}{m}\sum_{0<i \le m}{L(a^{(i)},y^{(i)})}\\ a^{(i)}=\hat{y}^{(i)}=\sigma(z^{(i)})=\sigma(w^Tx^{(i)}+b)
J(w,b)=m10<i≤m∑L(a(i),y(i))a(i)=y^(i)=σ(z(i))=σ(wTx(i)+b)
成本函数实际上是1到m项损失函数和的平均。
∂
J
(
w
,
b
)
∂
w
1
=
1
m
∑
0
<
i
≤
m
∂
L
(
a
(
i
)
,
y
(
i
)
)
∂
w
1
\frac{\partial J(w,b)}{\partial w_1}=\frac{1}{m}\sum_{0<i \le m}{\dfrac{\partial L(a^{(i)},y^{(i)})}{\partial w_1}}
∂w1∂J(w,b)=m10<i≤m∑∂w1∂L(a(i),y(i))
代码流程:
J=0;dw1=0;dw2=0;db=0;
for i = 1 to m
z(i) = wx(i)+b;
a(i) = sigmoid(z(i));
J += -[y(i)log(a(i))+(1-y(i))log(1-a(i));
dz(i) = a(i)-y(i);
dw1 += x1(i)dz(i);
dw2 += x2(i)dz(i);
db += dz(i);
J/= m;
dw1/= m;
dw2/= m;
db/= m;
w=w-alpha*dw
b=b-alpha*db
遇到的问题:
需要两个for循环,一个遍历n个特征,另一个循环遍历m个样本。
解决方法:
向量化Vectorization,消除显性的for循环。
2.11 向量化(Vectorization)
什么是向量化
z = w T x + b w = ( . . . . . ) w ∈ R n x = ( . . . . . ) x ∈ R n x z=w^Tx+b\\ w=\begin{pmatrix}.\\ ...\\.\end{pmatrix}\ \ \ w\isin \reals^{n}\\x=\begin{pmatrix}.\\...\\. \end{pmatrix}\ \ \ \ x\isin \reals^{n_x} z=wTx+bw=⎝⎛.....⎠⎞ w∈Rnx=⎝⎛.....⎠⎞ x∈Rnx
-
非向量方法实现:
z=0; for i in range(n_x): z+=w[i]*x[i] z+=b
-
向量方法实现:
z=np.dot(w,x)+b # z=w_t*x+b,使用numpy库
#example import numpy as np a = np.array([1,2,3,4]) print(a) import time a = np.random.rand(1000000)#创建百万维度的数组 b = np.random.rand(1000000) ###### 向量化版本。 ##### tic = time.time() #记录当前时间 c = np.dot(a,b) toc = time.time() print(c) print("Vectorized version:" + str(1000*(toc-tic)) + "ms") #打印出使用向量化所花费的时间 ###### 非向量化版本 ###### c =0 tic = time.time() for i in range(1000000): c += a[i]*b[i] toc = time.time() print(c) print("For loop:” + str(1000*(toc-tic)) + “ms”)#打印for循环的版本的时间 #向量化快了接近300倍
- CPU&GPU均有并行化指令,即SIMD指令(Single Instruction Multiple Data),GPU比CPU更擅长并行化运算。
2.12 向量化的更多例子
经验法则:尽量避免for循环
1.向量相乘的计算:
U = A ⋅ v U=A·v U=A⋅v
u=np.dot(A,v)
2.向量指数运算:
v T = ( v 1 v 2 ⋅ ⋅ ⋅ v n ) u T = ( e ( v 1 ) e ( v 2 ) ⋅ ⋅ ⋅ e ( v 3 ) ) v^T=(v_1\ v_2\ ···\ v_n)\\ u^T=(e^{(v_1)}\ e^{(v_2)}\ ···\ e^{(v_3)}) vT=(v1 v2 ⋅⋅⋅ vn)uT=(e(v1) e(v2) ⋅⋅⋅ e(v3))
import numpy as np
u = np.exp(v)
np.log()
np.abs()
np.maximum()
v**2 #获取每个值的平方
1/v
3.逻辑回归的梯度下降:
J=0;dw1=0;dw2=0;db=0;##位置一
for i = 1 to m
z(i) = wx(i)+b;
a(i) = sigmoid(z(i));
J += -[y(i)log(a(i))+(1-y(i))log(1-a(i));
dz(i) = a(i)-y(i);
##位置二
dw1 += x1(i)dz(i); #n_x=2
dw2 += x2(i)dz(i);#去掉此处遍历n_x个特征w(n_X)的循环 for j in range(1,n_x):
db += dz(i);
J/= m;
dw1/= m;##位置三
dw2/= m;
db/= m;
w=w-alpha*dw
b=b-alpha*db
- 做法:不将dw_1,dw_2,…显式地初始化为0.而是将dw变成一个向量
dw = np.zeros((n_x,1))##位置一
dw+=x(i)dz(i)##位置二
dw/=m##位置三
2.13 向量化逻辑回归(Vectorization Logistic Regression)
逻辑回归的向量化
z ( i ) = w T x ( i ) + b y ^ ( i ) = a ( i ) = σ ( z ( i ) ) z^{(i)} = w^{T}x^{(i)}+b \\ \hat{y}^{(i)}=a^{(i)}=\sigma(z^{(i)}) z(i)=wTx(i)+by^(i)=a(i)=σ(z(i))
X = ( . . . x ( 1 ) x ( 2 ) . . . x ( m ) . . . ) x ( i ) ∈ R n x X=\begin{pmatrix} ...\\ x^{(1)} x^{(2)} ... x^{(m)}\\ ... \end{pmatrix}\ \ \ \ x^{(i)}\isin \reals^{n_x} X=⎝⎛...x(1)x(2)...x(m)...⎠⎞ x(i)∈Rnx
X是一个n_x*m的矩阵。
1.构建一个1*m的矩阵Z,计算得到Z。
Z = ( z ( 1 ) z ( 2 ) . . . z ( m ) ) = w T ⋅ X + ( b b . . . b ) 1 ∗ m = ( w T x ( 1 ) + b w T x ( 2 ) + b . . . w T x ( m ) + b ) 1 ∗ m Z=\begin{pmatrix}z^{(1)} z^{(2)} ... z^{(m)} \end{pmatrix}=w^T·X+\begin{pmatrix}b\ b\ ...\ b\ \end{pmatrix}_{1*m}=\begin{pmatrix} w^Tx^{(1)}+b\ \ w^Tx^{(2)}+b ... w^Tx^{(m)}+b \end{pmatrix}_{1*m} Z=(z(1)z(2)...z(m))=wT⋅X+(b b ... b )1∗m=(wTx(1)+b wTx(2)+b...wTx(m)+b)1∗m
Z=np.dot(w.T,x)+b
'''
此处的b为实数,在计算时,python会将b自动扩展成一个1*m的行向量。“broadcasting”
'''
2.构建一个1*m的矩阵A,计算得到A。
A = ( a ( 1 ) a ( 2 ) . . . a ( m ) ) = σ ( Z ) A=\begin{pmatrix} a^{(1)} a^{(2)} ... a^{(m)} \end{pmatrix} =\sigma(Z) A=(a(1)a(2)...a(m))=σ(Z)
将矩阵Z作为输入,通过python可以非常高效地输出A。
2.14向量化logistic回归的梯度计算
d z ( i ) = a ( i ) − y ( i ) d z = ( d z ( 1 ) d z ( 2 ) ⋅ ⋅ ⋅ d z ( m ) ) 1 ∗ m A = ( a ( 1 ) a ( 2 ) . . . a ( m ) ) Y = ( y ( 1 ) y ( 2 ) . . . y ( m ) ) dz^{(i)}=a^{(i)}-y^{(i)}\\ dz=(dz^{(1)}\ dz^{(2)}\ ···\ dz^{(m)})_{1*m}\\ A=\begin{pmatrix} a^{(1)} a^{(2)} ... a^{(m)} \end{pmatrix}\\ Y=\begin{pmatrix} y^{(1)} y^{(2)} ... y^{(m)} \end{pmatrix} dz(i)=a(i)−y(i)dz=(dz(1) dz(2) ⋅⋅⋅ dz(m))1∗mA=(a(1)a(2)...a(m))Y=(y(1)y(2)...y(m))
1.去掉计算dz的循环
- dz的循环:
###详细代码见2.12节3.逻辑回归的梯度下降
for i = 1 to m
z(i) = wx(i)+b;
a(i) = sigmoid(z(i));
dz(i) = a(i)-y(i);
- 去循环的方法:
d z = A − Y = ( a ( 1 ) − y ( 1 ) a ( 2 ) − y ( 2 ) ⋅ ⋅ ⋅ ) dz=A-Y=(a^{(1)}-y^{(1)}\ a^{(2)}-y^{(2)}\ ···\ ) dz=A−Y=(a(1)−y(1) a(2)−y(2) ⋅⋅⋅ )
2.去掉遍历训练集的循环
去掉计算dw,db的循环
- 与2.12去掉位置二的循环的分别:
- 2.12的循环是在对每个训练样本,遍历其所有特征值w1,w2,…,wn,n为每个样本特征的个数,即n_x,在2.12的例子中,特征个数为2。
- 2.14中的该循环是已经计算出每个样本的所有特征w1,w2,…,wn,对m个样本遍历,相加求每个特征在m个样本下的平均值。
2.1dw,db在训练集上的循环:
###
#此处的dw已经是向量了,即是经过2.12第三节位置二处理后的dw
dw=0,db = 0
for i in range(1,m):
dw+=x(i)dz(i)
db += dz(i);
dw/=m
db/= m;
#与2.12-3处的代码进行对照,与下面这部分相同。
for i = 1 to m:
###位置二
dw1 += x1(i)dz(i); #n_x=2
dw2 += x2(i)dz(i);
dwn += xn(i)dz(i);
db += dz(i);
dw1/= m;##位置三
dw2/= m;
dwn/= m;
db/= m;
2.2去循环的方法:
- db的去循环
d b = 1 m ∑ 0 < i ≤ m d z ( i ) db=\frac{1}{m}\sum_{0<i \le m}dz^{(i)} db=m10<i≤m∑dz(i)
db=1/m*np.sum(dz)
- dw的去循环
d w = 1 m X d z T = 1 m ( x ( 1 ) x ( 2 ) . . . x ( m ) ) n ∗ m ( d z ( 1 ) d z ( 2 ) . . . d z ( m ) ) m ∗ 1 = 1 m ( x ( 1 ) d z ( 1 ) + x ( 2 ) d z ( 2 ) + . . . + x ( n ) d z ( n ) ) n ∗ 1 注 : x ( i ) 为 n x 维 的 列 向 量 。 dw=\frac{1}{m}Xdz^T\\ =\frac{1}{m} \begin{pmatrix} x^{(1)}\ x^{(2)} \ ... x^{(m)}\\ \end{pmatrix}_{n*m} \begin{pmatrix} dz^{(1)}\\ dz^{(2)}\\ ...\\ dz^{(m)} \end{pmatrix}_{m*1}\\= \frac{1}{m} \begin{pmatrix} x^{(1)dz^{(1)}}+ x^{(2)dz^{(2)}}+ ...+ x^{(n)dz^{(n)}} \end{pmatrix}_{n*1}\\ 注:x^{(i)}为n_x维的列向量。 dw=m1XdzT=m1(x(1) x(2) ...x(m))n∗m⎝⎜⎜⎛dz(1)dz(2)...dz(m)⎠⎟⎟⎞m∗1=m1(x(1)dz(1)+x(2)dz(2)+...+x(n)dz(n))n∗1注:x(i)为nx维的列向量。
3.总结梳理
- 未向量化的过程
J=0;dw1=0;dw2=0;db=0;
for i = 1 to m
z(i) = wx(i)+b;##位置一
a(i) = sigmoid(z(i));
J += -[y(i)log(a(i))+(1-y(i))log(1-a(i));
dz(i) = a(i)-y(i);
##位置二
dw1 += x1(i)dz(i); #n_x=2
dw2 += x2(i)dz(i);#去掉此处遍历n_x个特征w(n_X)的循环 for j in range(1,n_x):
db += dz(i);
J/= m;
dw1/= m;##位置三
dw2/= m;
db/= m;
w=w-alpha*dw
b=b-alpha*db
- 向量化的步骤
1. 将位置二的循环向量化(去掉遍历每个特征的内循环)
dw+=x(i)dz(i)
2.将位置一的循环向量化(去掉计算z,a时遍历每个样本的循环)
Z=w_T*x+b=np.dot(w.T,x)+b #见2.13-1
A=sigma(z)#见2.13-2
dz=A-Y#见2.14-1
dw=1/m*X*dz_T#见2.14-2.2
db=1/m(np.sum(dz))#见2.14-2.2
#见2-10
w=w-alpha*dw
b=b-alpha*db
以上即实现了一次迭代的梯度下降。
2.15 python中的广播
如何能够不使用循环按列求和,再将每个元素处以该列的和计算出来呢?
import numpy as np
A = np.array([56.0,0.0,4.4,68.0],
[1.2,104.0,52.0,8.0],
[1.8,135.0,99.0,0.9])
print(A)
cal =A.sum(axis=0)#axis=0表示求和按列执行
print(cal)
percentage = 100*A/cal.reshape(1,4)
#不使用reshape结果也对,使用更严谨,确保是正确的矩阵形状,reshape()是O(1)运算
print(percentage)
关于axis:axis=0表示竖直相加,竖轴为0,水平轴为1。
- 对于m*n的矩阵,无论行或列相不相同,都会先复制到m行,再复制得到n列。
- 如果是3*2的矩阵与2*2的矩阵进行矩阵运算呢。