DeepLearning三步骤
-
定义一组函数(就是函数集)其实就是神经网络(neural network)
-
判断函数的好坏(loss function)从function set中选取一个最小的Loss function
-
选择一个最好的函数
一个neural network其实就是一个function,经过一层一层的计算最后得到最后的结果
不同的情况采用不同的神经网络框架
-
真实财产(例如房地产和在线广告):标准的神经网络
-
图像领域:CNN
-
序列数据(语音和文字):RNN或者RNNs
-
更复杂的应用(无人驾驶等):复杂或者是混合的神经网络架构
神经网络简述
常见的三种神经网络:标准神经网络,CNN(卷积神经网络),RNN(循环神经网络)
CNN适用于解决图像问题,RNN适用于解决一维序列数据问题。
结构化数据:数据的数据库,每个特征都有清晰的定义;
非结构化数据:例如音频,原始音频以及图像等。
二元分类
每张彩色图片对应着红绿蓝三种颜色通道,那么如果需要保存一张图片的话,就需要存储三个矩阵,分别对应三个颜色。
-
如果图片大小为64x64像素,那么就有三个64x64的矩阵,分别对应图片中红、绿、蓝三种像素的强度值
-
为了把这些像素值放到一个特征向量中,需要把这些像素值提取出来,放入一个特征向量 x 。
首先把所有的像素都取出来,如果图片的大小为64x64像素,那么向量 x 的总维度,将是64乘以64乘以3,这是三个像素矩阵中像素的总量。在这个例子中它为12,288。现在我们用 nx=12,288 ,来表示输入特征向量的维度。有时候为了简洁,会直接用小写的 n 来表示输入特征向量 x 的维度。(n表示的是向量X的长度而不是特征)
符号定义
在这个系列课程里使用到的一些定义如下:
-
x :表示一个 nx 维数据,为输入数据,维度为 (nx, 1) ;
-
y :表示输出结果,取值为 (0, 1) ;
-
(x(i), y(i)) :表示第 i 组数据,可能是训练数据,也可能是测试数据,此处默认为训练数据;
-
X = [x(1), x(2), ..., x(m)] :表示所有的训练数据集的输入值,放在一个 nx × m 的矩阵中,其中 m 表示样本数目;
-
Y = [y(1), y(2), ..., y(m)] :对应表示所有训练数据集的输出值,维度为 1 × m 。
-
一对(x, y) 来表示一个单独的样本, x 代表 nx 维的特征向量, y 表示标签(输出结果)只能为0或1。
定义训练集由 m 个训练样本组成时,有:
-
(x(1),y(1)) 表示第一个样本的输入和输出
-
(x(m),y(m)) 表示最后一个样本
-
有时为了强调这是训练样本的个数,会写作 Mtrain
-
当涉及到测试集的时候,我们会使用 Mtest 来表示测试集的样本数
X.shape=(nx,m) #说明x这是一个nx*m维的矩阵 Y.shape=(1,m) #说明y是一个1*m维的矩阵
Logistic Regression
Logistic Regression其实就是给定一个x,输出的是y^,代表着当x满足条件时y等于1(例如图片的话,y=1则代表着是这个类型的图片)的概率为多大,y^其实就是概率值。
那么其实就是要得到y^的值为多大。如果采用y^=wT*x+b(其中w是一个参数向量)输出的结果可能是个负数或者是大于1的,这显然在概率中不成立。所以采用σ函数作用于wT*x+b上,则y^=σ(wT*x+b),其中z=wT*x+b。
σ(z)就可以用下面这张图片来表示:g(z)就表示σ(z)
图像如下图所示:
因为要保证y^是个非负数,所以采用上图的函数来计算y^。
所以当使用logistic的时候那么需要做的就是学习调整w和b,使得y^变成一个好的估计
y^=σ(wT*x+b)(其中z=wT*x+b)的另一种表示:
当X0=1的时候,X是一个nx+1维的向量。
y^=σ(θT*X),θ=[θ0,θ1,......θnx]T,其中θ0就是等于b,θ1,......θnx就是等于w。
Logistic Regerssion cost function
损失函数是定义在单个训练样本上的,也就是就算一个样本的误差,用L表示
代价函数是定义在整个训练集上面的,也就是所有样本的误差的总和的平均,也就是损失函数的总和的平均
为了训练回归模型的参数w和b,则需要定义一个cost function,cost function可以用来衡量w和b的效果的。
要确定参数w和b,那么就需要给定训练数据来对模型进行训练,使得y^≈y(y是数据中给定的)
Loss(error) Function可以用来查明你的y^和y到底有多接近。Loss Function可以定义为:L(y^,y)= 1/2(y^-y)2
但是在logistic regression中一般不这样定义,因为这样会变成一个非凸函数,非凸函数就会得到许多个局部最优解,根据梯度下降法,找不到全局的最优解。(左边这个就是一个非凸函数)
在Logistic Regerssion采用这样的Loss Function:L(y^,y)=-( y logy^+ (1-y) log(1-y^) )
在Logistic Regression采用下面这样的cost function,用 J 表示:J(w,b)=-1/m Σ(i=1到m)[ y(i) logy^(i)+ (1-y(i)) log(1-y^(i)]
cost function其实就是loss function的平均求和
要找到w和b使得 J 尽可能的小
Gradient Desecnt
用gradient descent来训练或学习训练集上的参数w和b(找到使得 J 最小的w和b)
梯度下降是从初始点开始,朝着最陡的下坡方向走一步,直到走到最低点。
梯度下降法就是一直在重复下面的这个过程:
Repeat{ w:=w-α*dJ(W)/dw(可以直接写成w:=w-dw) }
对上面过程的解释:α是代表学习率,α控制每一次迭代或者梯度下降法中的步长。dJ(W)/dw就是导数,是对参数w的更新就是w的变化量
b和w通过下面的方式进行更新:
w:=w-α*dJ(w,b)/dw
b:=b-α*dJ(w,b)/db
导数是等于▲y/▲x(y的变化值处于x的变化值)
导数
导数会用到前向传播以及反向传播中
下图就是链式求导法则:
v是由a,b,c三个参数构成的,J是由v构成的
如果要求出J对于a的导数,那么就只需要知道J先对v求导然后v再对a求导,得到最终的J对a的导数。整理如下:
dJ/da=dJ/dv*dv/da
在python中:使用d FinalOutputVar/d var就是最后输出的参数对于其中一个参数求导数
在本题的例子中d FinalOutputVar/d var得到的结果就是d J/d a
前向传播
前向传播就是通过输入的参数如何得到最后的代价函数J
z(1) = wT*x+b,z(2) = wT*x+b,......
a(1)=σ(z(1)),a(2)=σ(z(2)),.......
反向传播
反向传播就是通过J如何计算得到最开始的参数值
向量化
如果在遍历数据的时候采用for循环的话会非常的复杂而且计算量会很大,所以可采取向量化的方式来摆脱for循环。在使用向量化之后或许一个for循环都不需要使用。
当使用非向量化的操作的时候,如果要计算z=wT*x+b的话需要用到for循环,像下面这样:
for i in range(n-x): z += w[i]*x[i] z += b
但是在向量化的操作的时候,如果要计算z的话,那么只需要使用numpy的包就可以了,像下面这样:
z = np.dot(w^T^,x)+b #这行命令就可以计算z=w^T^*x的结果
向量化logistic regression
下图显示的是使用for循环来实现的logistic regression求导
上图的错误了,dz是等于a(i)-y(i),其中a(i)表示的是预测值, y(i)表示的是真实值,在这里,loga的导数等于1/a,因为在交叉熵中loga就等于lna
dz(i)表示的是代价函数J对于z(i)的导数,就是d J/d z(i),其中y^(i)其实就是等于a(i)
其他d也都是一样的,都是dJ对于d 某个字母的导数
在dw1+=x1(i)*dz(i)以及dw2+=x2(i)*dz(i)
就可以用下面这行命令进行代替
dw = np.zero(n-x,1) # 其中的n-x代表的是矩阵的高度 dw += x[i]*dz[i]
使用上述的命令可以成功的消去一些for循环。
向量化前向传播
z(1) = wT*x+b,z(2) = wT*x+b,......
a(1)=σ(z(1)),a(2)=σ(z(2)),.......
使用向量化的话就是
X = [x1,x2,......xm]
令Z = [Z1,Z2,......Zm]
b=[b,b,b,...,b]
Z = wT*X+b
那么就可以使用下面的命令来对上面的过程进行简化
z = np.dot(w^T^,X)+b
向量化反向传播
根据上面的图片可以知道
dz(1)=a(1)-y1),dz(2)=a(2)-y(2),......,dz(m)=a(m)-y(m)
使用向量化的话就是
令dz=[dz1,dz2,......,dzm]
A = [a1,a2,......,am]
Y = [y1,y2,......,ym]
dz = A-Y
向量化regression
db = 1/m ∑ dz(i)
db = 1/m np.sum(dz)
dw = 1/m * X * dzT
最后面的w和b的变化其实就是梯度下降的一次迭代。
numpy
numpy一些函数
np.dot(a,b) # 会计算a*b的值,就是计算a[i]*b[i]的值 np.random.randn(1000) # 会创建一个1000维度的数组 np.exp(v)# 会让向量中的每个元素i变成e^i^ np.log(v) np.abs(v) np.maximum(v) # 和上面的exp一样,会对v中的每个向量执行操作 np.zeros(3,3) # 创建一个3*3的矩阵的全零矩阵
numpy向量说明
-
尽量不要用这种秩为1的矩阵
例如:A= [1,2,3,4,5]这样的1*5的矩阵,可以用A.shape查看矩阵的形状,得到的结果是(5,)的就最好不要用
# 不要用下面这种形式来定义矩阵 np.random.randn(5) #这会得到一个1*5的矩阵 # 最好用这种形式来定义 np.random.randn(5,1) #这会得到一个5*1的矩阵
如果不确定得到的矩阵是什么样的可以使用assert函数
assert(a.shape == (5,1))
广播
广播其实就是numpy如何处理一些特殊的矩阵,例如一些不对称的矩阵的加减乘除等等,下面有几个例子
1.下面是一个例子用来计算每个物质的含量占总含量的百分之多少:
要在python中写入矩阵可以按照下面的定义来定义矩阵
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]])
cal = A.sum(axis = 0) # 代表着对矩阵A中的竖轴的数值进行相加 percentage = 100*A/(cal.reshape(1,4)) #相当于是一个3*4的矩阵除以1*4的矩阵 reshape函数的意思是将结果变成一个1*4的矩阵
2.一个向量加上一个常数(这个就相当于logistic regression中加上b原理一样)
A = np.array([56.0,0.0,4.4,68.0]) A += 100 # 那么python会将100扩展成为1*3的矩阵,并将A矩阵中的每个数字加上100得到一个最终的矩阵
3.一个2x3的就矩阵加上一个1x3的矩阵
python会将后面那个1x3的矩阵复制变成一个2个1x3的矩阵,其中2个1x3的矩阵数值是一样的
jupyter
1.先在pycharm命令行中添加清华大学的软件源
- 这个是仅使用一次,即只在这次下载软件使用清华的源
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple some-package
# 其中some-package是你要下载的包的名字
- 如果要在以后的下载中默认清华的源,那么可以使用下面的命令
python -m pip install --upgrade pip
# 如果pip升级的过程使用默认源比较慢,可以用python -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --upgrade pip来升级pip,并继续执行下面的命令即可
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
2.使用pip命令下载jupyter
pip install jupyter
# 注意:如果有开VPN的话,需要先关闭否则会导致连接不上清华的源
3.再使用命令启动jupyter即可
jupyter notebook
神经网络
神经网络表示
[x1,x2,x3]表示的是输入层,中间很多节点的表示隐藏层(中间训练的这些东西的数值我们是不知道的),最后只有一个节点的是输出层(输出预测的y值)
使用符号来标记上面的这些不同的层
a[0]表示输入的特征向量,也就是a[0]=X
a[1]表示隐藏层,其中隐藏层的第一个节点是a1[1],第二个节点是a2[2],......,以此类推。
隐藏层有两个参数:W和b,在上图中用W[1]和b[1]来表示隐藏层的参数,W[1]是一个4x3的矩阵,其中4是说明有四个节点,3是说明有三个输入的特征值(x1,x2,x3),b是一个4x1的矩阵
a[2]表示输出层。输出层也有两个参数分别是W[2]和b[2],在上图中W[2]是一个1x4的矩阵,b[2]是一个1x1的矩阵。因为输出层只有一个节点,有四个输入。
上图的神经网络架构被称为两层神经网络结构,因为输入层不算一层,只有一层隐藏层以及一层输出层。
单样本神经网络输出
在神经网络中,使用这样的定义才计算z[1]等参数。
使用Z[1]来表示[Z1[1],......,Z4^[1]]的集合。分别用下面的符号表示不同的式子。
如下图所示:展示了如何计算得到最终的输出的结果。Z[1]得到的结果是第一层隐藏层四个节点计算得到的结果。Z[2]表示的是第二层单个的输出节点的东西,将Z[2]进行计算可以得到最终的a[2],这就是最终的y^。
双层神经网络矩阵维度总结
在一个两层神经网络中,通常会有以下参数和数据:
输入数据 ( X ) 的维度: ( (n_x, m) ),其中 ( n_x ) 是输入特征的数量,( m ) 是样本的数量。
第一层的权重 ( W^{[1]} ) 的维度: ( (n_h, n_x) ),其中 ( n_h ) 是第一层隐藏层的神经元数量,( n_x ) 是输入特征的数量。
第一层的偏置 ( b^{[1]} ) 的维度: ( (n_h, 1) )。
第二层的权重 ( W^{[2]} ) 的维度: ( (n_y, n_h) ),其中 ( n_y ) 是输出层的神经元数量,( n_h ) 是第一层隐藏层的神经元数量。
第二层的偏置 ( b^{[2]} ) 的维度: ( (n_y, 1) )。
第一层的加权输入 ( Z^{[1]} ) 的维度: ( (n_h, m) )。
第一层的激活值 ( A^{[1]} ) 的维度: ( (n_h, m) )。
第二层的加权输入 ( Z^{[2]} ) 的维度: ( (n_y, m) )。
第二层的激活值 ( A^{[2]} ) 的维度: ( (n_y, m) )。
这些维度会根据具体的神经网络架构和数据集情况而变化。
多样本向量化
上面显示的是单个样本在神经网络中如何输出。下面将说明多个样本在神经网络中如何表示以及说明。
这里的多个样本是用X(1),X(2),......,X(m)来表示的,对应的估计值是用a[2](1)来表示的,其中[2]表示的是第2层,(1)表示的是第1个样本
下图通过一个for循环的方式来遍历所有的输入数据。
对于数据集中的每一个样本(i从1到m),执行以下步骤。(这里的W[1],W[2],b[1]和b[2]都是模型初始化的参数,会在模型的学习过程中修改的)
z[1](i) = W[1]x(i) + b[1]:计算第一层的z值,通过将权重矩阵W[1]与输入向量x(i) 相乘,然后加上偏置向量b[1]得到z值。
a[1][i]=σ(z[1](i)):对z[1](i)应用激活函数σ,得到第一层的激活值a[1][i]。
z[2](i) = W[2]a[1](i) + b[2]:使用第一层的激活值a[1](i) ,再次计算预激活值,这次是为了第二层,同样地加上第二层的偏置向量b[2]。
a[2](i) = σ(z[2](i)):将激活函数σ应用于z[2](i),得到最终的输出a[2](i),这是对第
i
个样本的预测或处理结果。其实就是先计算第一层的第一个数据再计算第二个一直计算到第m个数据,然后再计算第二层的数据,最后得到最终的输出值。
可以将上面的for循环使用下列向量化的方式进行代替。
将输入的x[i]写成列向量的形式变成一个大的矩阵X
Z[1]和A[1]也是一样的,当然还有Z[2]和A[2]也是一样的。
激活函数
隐藏层和输出层里面的激活函数都是可以选择的(前面所提到的σ就是一个激活函数)
在隐藏层部分,激活函数已经不采用σ函数了,采用tanh(不是tan函数)函数比σ函数好。这是因为tanh函数的输出范围是[-1, 1],使得其均值接近于 0,这有助于加速收敛速度。
在输出层部分,如果进行的是二元分类的任务,那么输出层一般采用σ函数,因为σ函数的取值范围是[0,1],而tanh函数的取值范围是[-1,1]
采用这种形式来表明当前这个激活函数应用于哪一层:假设g为激活函数,则g[1]和g[2]表示第一层和第二层的激活函数。
上述两个激活函数都有一个问题,那就是当z很大的时候,函数的斜率会变得非常的小趋近于0(梯度消失问题),这会影响梯度下降算法。所以可以采用ReLU函数来作为隐藏层的激活函数。
ReLU函数的表达式是:g( z )=max(0 , z),函数的图像如下图所示:
有以下四种隐藏函数:除非在做二元分类的任务,否则一般不采用σ激活函数,tanh函数也比较少用,目前采用最多的是ReLU激活函数以及leak ReLU激活函数。
leak ReLU激活函数的函数表达式是:g(z)=max(0.01*z , z)其中0.01是可选的,也可以将0.01看成一个调整的参数但是一般不去调整这个数值。
激活函数的导数:
当g是σ函数的时候,即g(z) = 1/1+
,令a = g(z)
dg(z) / dz = a*(1-z)
当g是tanh函数的时候,即g(z) =
,令a = g(z)
dg(z) / dz = 1-a2
当g是ReLU函数的时候,即g(z) = max(0 , z)
dg(z) / dz = 0(当z<0的时候) 或 =1(当z>=0的时候)
当g是laeky ReLU函数的时候,即g(z) = max(0.01*z , z)
dg(z) / dz = 0.01(当z<0的时候) 或 =1(当z>=0的时候)
神经网络的梯度下降法
可以借助这张图对下面这些推导过程的理解
下图是一个正向传播以及反向传播的过程示意图:
前向传播(Forward Propagation):
-
z[1] = W[1]X + b[1]: 这一步计算第一层的加权输入,其中W[1]是权重矩阵,X是输入矩阵,b[1]是偏置向量。
-
A[1] = g[1](z[1]): 表示第一层的激活函数g[1](通常是sigmoid或ReLU函数)到加权输入(z[1]上,以获得第一层的激活输出A[1]。
-
z[2]= W[2]A[1] + b[2]: 类似地,第二层的加权输入z[2]是第一层的激活输出A[1] 与第二层的权重W[2]的乘积,再加上偏置b[2]。
-
A[2] = g[2](z[2]) = σ(z[2]): g[2]表示第二层的激活函数,第二层的激活输出A[2],其中
σ
表示sigmoid激活函数(σ是假设第二层采用的是σ函数)。
反向传播(Back Propagation):
-
dZ[2] = A[2] - Y: 计算输出层误差,即预测结果A[2]与真实值
Y
之间的差。 -
dW[2] = 1/m * dZ[2]A[1]T: 计算对权重W[2]的梯度,A[1]T是A[1]的转置,m是样本数量。
-
db[2] = 1/m * np.sum(dZ[2], axis=1, keepdims=True): 计算对偏置b[2]的梯度,通过对dZ[2]求和(
np.sum
表示NumPy的求和函数,axis=1
表示沿着行的方向,keepdims=True保持原有的维度,防止python自己生成类似于 (n,) 这种维度的矩阵)。 -
dZ[1] = W[2]T * dZ[2] * g[1]'(z[1]): 计算第一层的误差dZ[1],其中W[2]T是W[2]的转置,g[1]'是第一层激活函数的导数。
-
dW[1] = 1/m * dZ[1]XT: 计算对权重W[1]的梯度。
-
db[1] = 1/m * np.sum(dZ[1], axis=1, keepdims=True): 计算对偏置b[1] 的梯度。
这个过程通过计算损失函数关于每个参数的梯度,然后使用这些梯度通过梯度下降算法更新参数,从而减少预测误差,优化网络性能。反向传播是实现这一目的的关键步骤,通过它网络能够学习从输入数据到输出结果的映射。
随机初始化
训练神经网络的时候需要随机初始化权重
在初始化参数的时候不可以将参数初始化为全0矩阵,因为如果将W和b矩阵全部都初始化为全0矩阵,就会导致任意的x输入都会得到一样的输出(因为Z = WT * X + b)。所以只能将b设置为全0矩阵,不能将W设置为全0矩阵。
根据上面这个简单的神经网络来随机初始化参数:
w[1] = np.random.randn(2,2)*0.01 b[1] = np.zero(2,1)
对于上面的代码为什么要*0.01的解释:
因为如果初始化的W权重太大,那么得到的Z就会落到一个比较大的位置,根据σ和tanh函数的图像可知,当Z较大的时候,函数的斜率就会变得很小趋近于0。