打开深度学习的锁
导言
本篇知识背景来源于吴恩达教授的DeepLearning课程作业–第三节,有兴趣的同学可以自行搜索。
博客所用到的数据集和测试代码已经公开:GitHub
这篇博客本质内容吴恩达教授的DeepLearning的课程作业第三节,是题解。
有了上次的学习作为引导,这次的练习简单了很多。
这次要做的任务,是构建一个只有一个隐藏层的神经网络。
本次学习用到的数据集-那朵花,是使用随机数生成的。
虽然是随机,不过在生成的时候使用了随机数种子,保证了数据的一致,所以在每一个模块的测试练习的时候,如果你的输出和我的不一样,那就说明代码中存在一些问题。
它们必须一样
好了,开始吧
PS:神经网络的训练过程
放此图在这里为了快速认识,也为了需要的时候快速查找。初次看的时候看不懂没关系,可以先看下面的内容。
一、数据集和包的说明
1.1准备文件
本次练习用给到的文件是两个.py文件,别无其他。分别是planar_utils.py
和testCases.py
。如果一些原因没办法访问Github,可以直接复制代码,如下:
planar_utils.py
主要作用就是生成一些随机数,以此来充当数据集。
虽然是随机,不过在生成的时候使用了随机数种子,保证了数据的一致,所以在每一个模块的测试练习的时候,如果你的输出和我的不一样,那就说明代码中存在一些问题。
planar_utils.py:
import matplotlib.pyplot as plt
import numpy as np
import sklearn
import sklearn.datasets
import sklearn.linear_model
def plot_decision_boundary(model, X, y):
"""
绘制模型的决策边界。
参数:
- model: 用于预测数据集输出的函数。
- X: 输入数据。
- y: 实际标签。
"""
# 设置最小和最大值并给予一些填充
x_min, x_max = X[0, :].min() - 1, X[0, :].max() + 1
y_min, y_max = X[1, :].min() - 1, X[1, :].max() + 1
h = 0.01
# 生成距离为h的点阵
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
# 预测整个网格的函数值
Z = model(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
# 绘制轮廓和训练样本
plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
plt.ylabel('x2')
plt.xlabel('x1')
plt.scatter(X[0, :], X[1, :], c=y, cmap=plt.cm.Spectral)
def sigmoid(x):
"""
计算x的sigmoid。
参数:
- x: 任意大小的标量或numpy数组。
返回:
s: sigmoid(x)
"""
s = 1/(1+np.exp(-x))
return s
def load_planar_dataset():
"""
加载2D平面数据集到X和Y。
返回:
- X: 表示数据点的numpy数组。
- Y: 表示标签的numpy数组。
"""
np.random.seed(1)
m = 400 # 样本数量
N = int(m/2) # 每个类别的点数
D = 2 # 维度
X = np.zeros((m,D)) # 每行为一个单独的例子的数据矩阵
Y = np.zeros((m,1), dtype='uint8') # 标签向量 (0为红色, 1为蓝色)
a = 4 # 花的最大半径
for j in range(2):
ix = range(N*j,N*(j+1))
t = np.linspace(j*3.12,(j+1)*3.12,N) + np.random.randn(N)*0.2 # 角度
r = a*np.sin(4*t) + np.random.randn(N)*0.2 # 半径
X[ix] = np.c_[r*np.sin(t), r*np.cos(t)]
Y[ix] = j
X = X.T
Y = Y.T
return X, Y
def load_extra_datasets():
"""
加载额外的数据集。
返回:
- 不同的合成数据集作为元组 (数据, 标签)。
"""
N = 200
noisy_circles = sklearn.datasets.make_circles(n_samples=N, factor=.5, noise=.3)
noisy_moons = sklearn.datasets.make_moons(n_samples=N, noise=.2)
blobs = sklearn.datasets.make_blobs(n_samples=N, random_state=5, n_features=2, centers=6)
gaussian_quantiles = sklearn.datasets.make_gaussian_quantiles(mean=None, cov=0.5, n_samples=N, n_features=2, n_classes=2, shuffle=True, random_state=None)
no_structure = np.random.rand(N, 2), np.random.rand(N, 2)
return noisy_circles, noisy_moons, blobs, gaussian_quantiles, no_structure
testCases.py
的主要作用是提供了一些用于测试单元的工具,在需要的时候直接调用就可。
testCases.py:
import numpy as np
def layer_sizes_test_case():
np.random.seed(1)
X_assess = np.random.randn(5, 3)
Y_assess = np.random.randn(2, 3)
return X_assess, Y_assess
def initialize_parameters_test_case():
n_x, n_h, n_y = 2, 4, 1
return n_x, n_h, n_y
def forward_propagation_test_case():
np.random.seed(1)
X_assess = np.random.randn(2, 3)
parameters = {'W1': np.array([[-0.00416758, -0.00056267],
[-0.02136196, 0.01640271],
[-0.01793436, -0.00841747],
[ 0.00502881, -0.01245288]]),
'W2': np.array([[-0.01057952, -0.00909008, 0.00551454, 0.02292208]]),
'b1': np.array([[ 0.],
[ 0.],
[ 0.],
[ 0.]]),
'b2': np.array([[ 0.]])}
return X_assess, parameters
def compute_cost_test_case():
np.random.seed(1)
Y_assess = np.random.randn(1, 3)
parameters = {'W1': np.array([[-0.00416758, -0.00056267],
[-0.02136196, 0.01640271],
[-0.01793436, -0.00841747],
[ 0.00502881, -0.01245288]]),
'W2': np.array([[-0.01057952, -0.00909008, 0.00551454, 0.02292208]]),
'b1': np.array([[ 0.],
[ 0.],
[ 0.],
[ 0.]]),
'b2': np.array([[ 0.]])}
a2 = (np.array([[ 0.5002307 , 0.49985831, 0.50023963]]))
return a2, Y_assess, parameters
def backward_propagation_test_case():
np.random.seed(1)
X_assess = np.random.randn(2, 3)
Y_assess = np.random.randn(1, 3)
parameters = {'W1': np.array([[-0.00416758, -0.00056267],
[-0.02136196, 0.01640271],
[-0.01793436, -0.00841747],
[ 0.00502881, -0.01245288]]),
'W2': np.array([[-0.01057952, -0.00909008, 0.00551454, 0.02292208]]),
'b1': np.array([[ 0.],
[ 0.],
[ 0.],
[ 0.]]),
'b2': np.array([[ 0.]])}
cache = {'A1': np.array([[-0.00616578, 0.0020626 , 0.00349619],
[-0.05225116, 0.02725659, -0.02646251],
[-0.02009721, 0.0036869 , 0.02883756],
[ 0.02152675, -0.01385234, 0.02599885]]),
'A2': np.array([[ 0.5002307 , 0.49985831, 0.50023963]]),
'Z1': np.array([[-0.00616586, 0.0020626 , 0.0034962 ],
[-0.05229879, 0.02726335, -0.02646869],
[-0.02009991, 0.00368692, 0.02884556],
[ 0.02153007, -0.01385322, 0.02600471]]),
'Z2': np.array([[ 0.00092281, -0.00056678, 0.00095853]])}
return parameters, cache, X_assess, Y_assess
def update_parameters_test_case():
parameters = {'W1': np.array([[-0.00615039, 0.0169021 ],
[-0.02311792, 0.03137121],
[-0.0169217 , -0.01752545],
[ 0.00935436, -0.05018221]]),
'W2': np.array([[-0.0104319 , -0.04019007, 0.01607211, 0.04440255]]),
'b1': np.array([[ -8.97523455e-07],
[ 8.15562092e-06],
[ 6.04810633e-07],
[ -2.54560700e-06]]),
'b2': np.array([[ 9.14954378e-05]])}
grads = {'dW1': np.array([[ 0.00023322, -0.00205423],
[ 0.00082222, -0.00700776],
[-0.00031831, 0.0028636 ],
[-0.00092857, 0.00809933]]),
'dW2': np.array([[ -1.75740039e-05, 3.70231337e-03, -1.25683095e-03,
-2.55715317e-03]]),
'db1': np.array([[ 1.05570087e-07],
[ -3.81814487e-06],
[ -1.90155145e-07],
[ 5.46467802e-07]]),
'db2': np.array([[ -1.08923140e-05]])}
return parameters, grads
def nn_model_test_case():
np.random.seed(1)
X_assess = np.random.randn(2, 3)
Y_assess = np.random.randn(1, 3)
return X_assess, Y_assess
def predict_test_case():
np.random.seed(1)
X_assess = np.random.randn(2, 3)
parameters = {'W1': np.array([[-0.00615039, 0.0169021 ],
[-0.02311792, 0.03137121],
[-0.0169217 , -0.01752545],
[ 0.00935436, -0.05018221]]),
'W2': np.array([[-0.0104319 , -0.04019007, 0.01607211, 0.04440255]]),
'b1': np.array([[ -8.97523455e-07],
[ 8.15562092e-06],
[ 6.04810633e-07],
[ -2.54560700e-06]]),
'b2': np.array([[ 9.14954378e-05]])}
return parameters, X_assess
1.2 需要导入的包
与上一篇相比,除了Numpy和 Matplotlib以外,多了一个sklearn,#scikit-learn
,提供了许多常见的机器学习算法的简单和高效的工具,如分类、回归、聚类和降维等。它还包括了用于模型选择和评估的工具,如交叉验证和各种性能指标。
import numpy as np #Python科学计算的一些包
import matplotlib.pyplot as plt #画图
from Week_3_DataSet.testCases import * #测试示例
from Week_3_DataSet.planar_utils import * #一些功能
#导数数据挖掘和数据分析的一些包
import sklearn
import sklearn.datasets #它提供了一系列用于测试和学习的数据集。这个模块包含了多种类型的数据集
import sklearn.linear_model #它包含了许多线性模型,这些模型可以用于各种任务,如回归、分类和异常检测。
二、构建神经网络的架构
首先,我们要引入一个新的中间层,隐藏层。
让我们先不考虑损失的计算,也就是现忽略标签
Y
Y
Y数据集合,单单来讨论
X
X
X和
Y
^
\hat Y
Y^的关系
那么我们现在知道的神经网络结构这样的:
总的来说,就是将特征输入到线性回归+激活函数函数中,得到预测,然后计算损失,然后调整模型参数,然后重复这一过程。
现在,我们添加一个东西,顺便完善一下这个结构
现在,在中间添加了一个层,名字叫隐藏层。
从目前来看,这个层中的元素个数有四个,分别从 a [ 1 ] 1 a[1]^1 a[1]1 到 a [ 1 ] 4 a[1]^4 a[1]4,而这四个节点呢,每一个其实都是一个线性回归+激活函数(线性回归+激活函数也就是逻辑回归)
我们可以看到,输入层中的每一个元素都于这四个元素进行了全链接,如果换个角度来看的话,就是:
在隐藏层中,输入的样本被每一个逻辑回归单元进行了一次预测
然后,我们将隐藏层中得到的预测再次进行拟合,也就是传入到输出层,得到一个最终的 y ^ \hat y y^
所以,通俗的理解来说:添加了隐藏层就是为了对样本进行二次预测处理。
但是, 我可以这么说,但你不能这样想!这个话本是并不是很正确。但是我希望这句话可以让你有一个大概的感觉。
如果你有了,一定要忘记这句话。
那么,隐藏层到底是用来做什么的呢?
在深度学习中,神经网络的隐藏层的作用可以从以下几个方面来理解:
1.特征抽取: 隐藏层可以自动从数据中提取更高级和更复杂的特征。在卷积神经网络(CNN)中,例如,前几层可能会捕获边缘和纹理,而更深的层则可能会捕获更复杂的图案和结构。
2.非线性映射: 通过在隐藏层使用非线性激活函数,神经网络能够学习并表示非线性函数。这使得网络能够逼近几乎任何复杂的函数。
3.抽象表示: 在很多任务中,隐藏层提供了一种抽象表示的能力,这意味着每一层都在将数据转化为一种更加抽象和通用的表示。例如,在自然语言处理任务中,深层网络的早期层可能关注于单词和短语,而后续的层则可能捕获句子或段落的更高级含义。
4.计算层: 隐藏层提供了更多的计算层,允许网络学习更多的参数和更复杂的结构。这使得网络能够逼近更为复杂的函数。
如果搞不懂这些意义暂时也没关系。只需要知道,我们在编写神经网络对应的函数的时候,我们需要指定输入层、隐藏层、输出层
#1.定义神经网络的结构
def layer_sizes(X,Y):#传入数据集和标签
# X:样本集合 维度(数据的特征数量,数据量) 每一个数据有两个特征,即:每一个点有X和Y两个坐标值
# Y:标签的集合(标签数量,数据量) 每一个数据有一个标签,即:0/1 == 红/蓝色
Input_layer=X.shape[0]#输入层的神经元数量,它等于数据的特征数量,每个数据点有两个特征(x ,y )坐标
Hidden_layer=4 #隐藏层单元的数量,设置4
Output_layer=Y.shape[0]#,表示输出层只有一个神经元来预测一个标签(0或1)。
return (Input_layer,Hidden_layer,Output_layer)#得到神经网络
三、初始化函数
现在,我们可以来构建初始化函数了,也就是对
W
W
W和
b
b
b赋值了。这里有两个关键点:
1:初始化最好用随机
2:需要构建的
W
W
W和
b
b
b的个数。
第一个我在第一章的时候解释过,随机的
W
W
W比全0的要好。
但是第二个,为什么要构建
W
W
W和
b
b
b的个数呢?因为这个时候的
W
W
W和
b
b
b的不止一个。
我们再看一下这张图:
在神经网络中,每一层对应需要 W W W和 b b b都不一样。
当前有两个层:输入层和隐藏层会用到 W W W和 b b b,所以就定义两组。
#2.初始化函数
def init_parameters(Input_layer,Hidden_layer,Output_layer):
np.random.seed(2)
#由于只有一层隐藏层,所以可以显式的写出初始化W1和W2
W1=np.random.randn(Hidden_layer,Input_layer)*0.01 #W的维度(当前层的单元数,上一层的单元数)
b1=np.zeros(shape=(Hidden_layer,1)) #b的维度(当前层的单元数,1)
W2=np.random.randn(Output_layer,Hidden_layer)*0.01
b2=np.zeros(shape=(Output_layer,1))
#断言测试个格式
assert(W1.shape==(Hidden_layer,Input_layer))
assert(b1.shape==( Hidden_layer , 1))
assert(W2.shape==(Output_layer,Hidden_layer))
assert(b2.shape==(Output_layer,1))
parameters ={
"W1":W1,
"b1":b1,
"W2":W2,
"b2":b2
}
return parameters
四、激活函数
激活函数是做什么的呢?
用一句话来说就是:
转化线性函数的为非线性函数,使得模型具有更多的功能。
转化线性函数的为非线性函数,使得模型具有更多的功能。
转化线性函数的为非线性函数,使得模型具有更多的功能。
对了,还有一句话,别问为什么,记住就行————
除了用于二分类任务最后的输出,否则不要用 s i g m o i d 函数 除了用于二分类任务最后的输出,否则不要用sigmoid函数 除了用于二分类任务最后的输出,否则不要用sigmoid函数
不用 s i g m o i d sigmoid sigmoid用什么呢?
让我来介绍另外一种激活函数: t a n h tanh tanh函数
4.1 tanh(双曲正切函数)函数
关于tanh函数,它的图像是这样的:上趋近于1,下趋近于-1
由于
t
a
n
h
tanh
tanh函数的输出值位于-1到1之间。这意味着它会将输入值归一化到这个范围内,所以如果我们想控制和规范神经元的输出值,我们可以选择这个函数。
在这道题中,为了练习。对于第一个激活函数,我选择使用了 t a n h tanh tanh函数,而之后涉及到最终输出的激活函数,还是需要 s i g m o i d sigmoid sigmoid函数
所以我们需要定义两个激活函数
#3.激活函数
#Numpy里面有thah函数,但是为了练习还是自己写一个吧
def tanh(z): #传入一个线性的函数
A=(np.exp(z)-np.exp(-z))/(np.exp(z)+np.exp(-z))
return A #A是非线性输出
#因为是二分类任务,所以对于最后的结果输出一定要用sigmoid函数
def sigmoid(z):
A=1/(1+np.exp(-z))
return A
五,前向传播
相较于上一篇文章提到的公式。有隐藏层的神经网络的公式由很大的改动:
假设:
N
x
Nx
Nx =(
N
[
0
]
N^{\left[0\right]}
N[0],
N
[
1
]
N^{\left[1\right]}
N[1],
N
[
2
]
N^{\left[2\right]}
N[2])
N
[
0
]
N^{\left[0\right]}
N[0]:输入特征的个数;
N
[
1
]
N^{\left[1\right]}
N[1]:隐藏单元的个数;
N
[
2
]
N^{\left[2\right]}
N[2]:输出特征的个数;
此外,给定单层神经网络的逻辑回归方程:
隐藏层:
Z
[
1
]
=
W
[
1
]
X
+
b
[
1
]
Z^{\left[1\right]}=W^{\left[1\right]}X+b^{\left[1\right]}
Z[1]=W[1]X+b[1]
A
[
1
]
=
g
[
1
]
(
Z
[
1
]
)
A^{\left[1\right]}=g^{\left[1\right]}(Z^{\left[1\right]})
A[1]=g[1](Z[1])
// 其中 Z [ 1 ] Z^{\left[1\right]} Z[1]是输入的线性函数的输出, A [ 1 ] A^{\left[1\right]} A[1]是 Z [ 1 ] Z^{\left[1\right]} Z[1]进过 非线性函数变换 以后的 输出
W
[
1
]
W^{\left[1\right]}
W[1]的维度为(
N
[
1
]
N^{\left[1\right]}
N[1],
N
[
0
]
N^{\left[0\right]}
N[0])
b
[
1
]
b^{\left[1\right]}
b[1]的维度为(
N
[
1
]
N^{\left[1\right]}
N[1],
1
1
1)
输出层:
Z
[
2
]
=
W
[
2
]
A
[
1
]
+
b
[
2
]
Z^{\left[2\right]}=W^{\left[2\right]}A^{\left[1\right]}+b^{\left[2\right]}
Z[2]=W[2]A[1]+b[2]
A
[
2
]
=
g
[
2
]
(
Z
[
2
]
)
A^{\left[2\right]}=g^{\left[2\right]}(Z^{\left[2\right]})
A[2]=g[2](Z[2])
W
[
2
]
W^{\left[2\right]}
W[2]的维度为(
N
[
2
]
N^{\left[2\right]}
N[2],
N
[
1
]
N^{\left[1\right]}
N[1])
b
[
2
]
b^{\left[2\right]}
b[2]的维度为(
N
[
2
]
N^{\left[2\right]}
N[2],
1
1
1)
// 其中 Z [ 2 ] Z^{\left[2\right]} Z[2]是 非线性输出 A [ 1 ] A^{\left[1\right]} A[1] 添加上当前的 权重 W [ 1 ] W^{\left[1\right]} W[1]和偏差 b [ 1 ] b^{\left[1\right]} b[1] 所构成的线性输出。
A [ 2 ] A^{\left[2\right]} A[2]是 Z [ 2 ] Z^{\left[2\right]} Z[2]经过 非线性函数变换 以后的 最终输出,也就是预测结果 y ^ \hat y y^
知道公式以后,代码很好写:
#4.构建前向传播(不用Y)
def propagate(X,parameters):
W1=parameters["W1"]
b1=parameters["b1"]
W2=parameters["W2"]
b2=parameters["b2"]
Z1=np.dot(W1,X)+b1
#使用tanh函数聚合值
A1=tanh(Z1)
#输出层计算
Z2=np.dot(W2,A1)+b2
#因为是二分类任务,所以对于最后的结果输出一定要用sigmoid函数
A2=sigmoid(Z2) #A2就是最后的输出
cache={
"Z1":Z1,
"A1":A1,
"Z2":Z2,
"A2":A2
}
return (A2,cache)
# #测试forward_propagation
# print("=========================测试forward_propagation=========================")
# X_assess, parameters = forward_propagation_test_case()
# A2, cache = propagate(X_assess, parameters)
# print(np.mean(cache["Z1"]), np.mean(cache["A1"]), np.mean(cache["Z2"]), np.mean(cache["A2"]))
测试结果:
六、损失函数
根据任务的不同,所用到的损失函数也不同。
关于当前这道题,我们处理的内容还是二分类问题。所以损失函数没有太大的变化
对于单个样本:
二元交叉熵损失函数(Binary Cross-Entropy Loss,也称为log loss)
L o s s = L ( y , y ^ ) = − [ y log ( y ^ ) + ( 1 − y ) log ( 1 − y ^ ) ] Loss=L(y, \hat{y}) = -[y \log(\hat{y}) + (1 - y) \log(1 - \hat{y})] Loss=L(y,y^)=−[ylog(y^)+(1−y)log(1−y^)]
对于神经网络损失的计算:
损失函数(Cost Function / Loss Function)
J ( w [ 1 ] , b [ 1 ] , w [ 2 ] , b [ 2 ] ) = 1 m ∑ i = 1 m L ( y ( i ) , y ^ ( i ) ) J(w^{\left[1\right]}, b^{\left[1\right]},w^{\left[2\right]}, b^{\left[2\right]}) = \frac{1}{m} \sum_{i=1}^{m} \text{L}(y^{(i)}, \hat{y}^{(i)}) J(w[1],b[1],w[2],b[2])=m1i=1∑mL(y(i),y^(i))
或者也可以使用 A [ 2 ] A^{\left[2\right]} A[2] 代替 y ^ ( i ) \hat{y}^(i) y^(i)
J
(
w
[
1
]
,
b
[
1
]
,
w
[
2
]
,
b
[
2
]
)
=
1
m
∑
i
=
1
m
L
(
y
(
i
)
,
A
[
2
]
(
i
)
)
J(w^{\left[1\right]}, b^{\left[1\right]},w^{\left[2\right]}, b^{\left[2\right]}) = \frac{1}{m} \sum_{i=1}^{m} \text{L}(y^{(i)}, A^{\left[2](i\right)})
J(w[1],b[1],w[2],b[2])=m1i=1∑mL(y(i),A[2](i))
在深度学习中,我们需要找合适的
w
w
w和
b
b
b,尽可能的缩小
J
(
w
,
b
)
J(w,b)
J(w,b)
代码:
#5.损失函数构建
# 首先,A2是输出的结果,也就是预测,也就是Yhat,
# 其次,我们需要真实的标签Y
def Cost_Func(A2,Y):
m=Y.shape[1] #总数据数
sum_cost=-(
np.multiply(np.log(A2),Y)
+
np.multiply(np.log(1-A2),(1-Y))
)
cost=np.sum(sum_cost)/m
#当前通过矩阵计算的得到的应该还是一个矩阵,这个不行,我们需要一个数
cost=float(np.squeeze(cost))
return cost
# #测试compute_cost
# print("=========================测试compute_cost=========================")
# A2 , Y_assess , parameters = compute_cost_test_case()
# print("cost = " + str(Cost_Func(A2,Y_assess)))
测试结果:
七、后向传播
所谓后向传播,就是计算 W W W和 b b b需要改变多少,只是计算,不改变。改变是梯度下降的事情。
如果想玩后向传播就必须学会导数!
可是我不会导数呀?不能玩吗?
…也能, 这样吧,我给你导完以后的公式,你直接用,别问为什么就行。
后向传播公式(其中的Y是(1,m)的标签集):
d
Z
[
2
]
=
A
[
2
]
−
Y
dZ^{\left[2\right]}=A^{\left[2\right]}-Y
dZ[2]=A[2]−Y
d
W
[
2
]
=
1
m
d
Z
[
2
]
A
[
1
]
T
dW^{\left[2\right]}=\frac{1}{m}dZ^{\left[2\right]}A^{\left[1\right]T}
dW[2]=m1dZ[2]A[1]T
d
b
[
2
]
=
1
m
n
p
.
s
u
m
(
d
z
[
2
]
,
a
x
i
s
=
1
,
k
e
e
p
d
i
m
s
=
T
r
u
e
)
db^{\left[2\right]}=\frac{1}{m}np.sum(dz^{\left[2\right]}, axis=1, keepdims=True)
db[2]=m1np.sum(dz[2],axis=1,keepdims=True)
d
Z
[
1
]
=
W
[
2
]
T
d
Z
[
2
]
∗
g
[
Z
]
′
dZ^{\left[1\right]}=W^{\left[2\right]T}dZ^{\left[2\right]}*g^{\left[Z\right]'}
dZ[1]=W[2]TdZ[2]∗g[Z]′
d
W
[
1
]
=
1
m
d
Z
[
1
]
X
T
dW^{\left[1\right]}=\frac{1}{m}dZ^{\left[1\right]}X^T
dW[1]=m1dZ[1]XT
d
b
[
1
]
=
1
m
n
p
.
s
u
m
(
d
z
[
1
]
,
a
x
i
s
=
1
,
k
e
e
p
d
i
m
s
=
T
r
u
e
)
db^{\left[1\right]}=\frac{1}{m}np.sum(dz^{\left[1\right]}, axis=1, keepdims=True)
db[1]=m1np.sum(dz[1],axis=1,keepdims=True)
7.1 激活函数的导数
激活函数的导数,用符号表示: g [ Z ] ′ g^{\left[Z\right]'} g[Z]′
7.1.1 Sigmoid 函数
Sigmoid 函数:
σ
(
z
)
=
1
1
+
e
−
z
\sigma(z) = \frac{1}{1 + e^{-z}}
σ(z)=1+e−z1
导数:
σ
′
(
z
)
=
σ
(
z
)
⋅
(
1
−
σ
(
z
)
)
\sigma'(z) = \sigma(z) \cdot (1 - \sigma(z))
σ′(z)=σ(z)⋅(1−σ(z))
7.1.2 Tanh 函数
Tanh 函数:
tanh
(
z
)
=
e
z
−
e
−
z
e
z
+
e
−
z
\tanh(z) = \frac{e^{z} - e^{-z}}{e^{z} + e^{-z}}
tanh(z)=ez+e−zez−e−z
导数:
tanh
′
(
z
)
=
1
−
tanh
2
(
z
)
\tanh'(z) = 1 - \tanh^2(z)
tanh′(z)=1−tanh2(z)
7.1.3 ReLU 函数 (Rectified Linear Unit)
ReLU 函数:
R
e
L
U
(
z
)
=
max
(
0
,
z
)
ReLU(z) = \max(0, z)
ReLU(z)=max(0,z)
导数:
R
e
L
U
′
(
z
)
=
{
1
if
z
>
0
0
if
z
≤
0
ReLU'(z) = \begin{cases} 1 & \text{if } z > 0 \\ 0 & \text{if } z \leq 0 \end{cases}
ReLU′(z)={10if z>0if z≤0
7.1.4 Leaky ReLU 函数
Leaky ReLU 函数导数:
LeakyReLU
′
(
z
)
=
{
1
if
z
>
0
α
if
z
≤
0
\text{LeakyReLU}'(z) = \begin{cases} 1 & \text{if } z > 0 \\ \alpha & \text{if } z \leq 0 \end{cases}
LeakyReLU′(z)={1αif z>0if z≤0
Leaky ReLU 函数导数:
LeakyReLU
′
(
z
)
=
{
1
if
z
>
0
α
if
z
≤
0
\text{LeakyReLU}'(z) = \begin{cases} 1 & \text{if } z > 0 \\ \alpha & \text{if } z \leq 0 \end{cases}
LeakyReLU′(z)={1αif z>0if z≤0
7.2 后向传播的代码
#后向传播
#首先,我们需要损失函数想相较于最后一层输出的导数
#我们还需要使用链式法则计算每一层的W和b的梯度
def back_propagtion(parameters,cache,X,Y):
m=Y.shape[1] #总数据数
W1=parameters["W1"]
W2=parameters["W2"]
A1=cache["A1"]
A2=cache["A2"]
#就算损失函数较于最后一层输出的导数dz
#带入公式就好
dZ2=A2-Y
dW2=(1/m) *(np.dot(dZ2,A1.T))
db2=(1/m) * np.sum(dZ2,axis=1,keepdims=True)
#使用公式 g`(z)=1-a^2
dZ1=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)
back_con={
"dW1":dW1,
"dW2":dW2,
"db2":db2,
"db1":db1
}
return back_con
# #测试backward_propagation
# print("=========================测试backward_propagation=========================")
# parameters, cache, X_assess, Y_assess = backward_propagation_test_case()
# grads = back_propagtion(parameters, cache, X_assess, Y_assess)
# print ("dW1 = "+ str(grads["dW1"]))
# print ("db1 = "+ str(grads["db1"]))
# print ("dW2 = "+ str(grads["dW2"]))
# print ("db2 = "+ str(grads["db2"]))
测试结果:
补充说明:
axis=1: 这个参数指定了求和应该沿着哪个轴进行。axis=1 意味着对每一行的元素进行求和。
keepdims=True: 这个参数确保结果保持原始数组的维度。这意味着即使求和是沿着某个轴进行的,输出的数组仍将保持与原始数组相同的维数。
八、梯度下降
更新 W W W和 b b b,其中 α \alpha \frac{}{} α是学习率
公式:
w
=
w
−
α
∂
J
∂
w
w = w - \alpha \frac{\partial J}{\partial w}
w=w−α∂w∂J
b
=
b
−
α
∂
J
∂
b
b = b - \alpha \frac{\partial J}{\partial b}
b=b−α∂b∂J
代码:
#梯度下降,也就是更新参数
def update_parameter(parameters,back_con,learning_rate=1.2):
#原数据
W1,W2=parameters["W1"],parameters["W2"]
b1,b2=parameters["b1"],parameters["b2"]
#它们的导数
dW1,dW2,db1,db2=back_con["dW1"],back_con["dW2"],back_con["db1"],back_con["db2"]
W1=W1-learning_rate*dW1
b1=b1-learning_rate*db1
W2=W2-learning_rate*dW2
b2=b2-learning_rate*db2
update ={
"W1":W1,
"b1":b1,
"W2":W2,
"b2":b2
}
return update
# #测试update_parameters
# print("=========================测试update_parameters=========================")
# parameters, grads = update_parameters_test_case()
# parameters = update_parameter(parameters, grads)
# print("W1 = " + str(parameters["W1"]))
# print("b1 = " + str(parameters["b1"]))
# print("W2 = " + str(parameters["W2"]))
# print("b2 = " + str(parameters["b2"]))
测试结果:
九、构建预测
这一步很关键,我们要使用 X X X来生成预测,与它们的标签 Y Y Y来进行拟合。
上一篇中没有把它们单独拿出来,看起来有点乱有点难理解,这一次单独提出来。
#构建预测
def predict(parameters,X):
#parameters - 包含参数的字典类型的变量。
#X - 输入数据(n_x,m)
A2,cache=propagate(X,parameters)
predic=np.round(A2)
return predic
# #测试predict
# print("=========================测试predict=========================")
# parameters, X_assess = predict_test_case()
# predictions = predict(parameters, X_assess)
# print("预测的平均值 = " + str(np.mean(predictions)))
测试结果:
十、聚合和主函数
好了,所有的功能都写完了。接下来写一个聚合它们的函数就可以了。
def model(X,Y,num_iterations,print_cost=False):
np.random.seed(3)
#开始初始化
Input_layer=layer_sizes(X,Y)[0] #导入输入层、隐藏层和输出层
Hidden_layer=layer_sizes(X,Y)[1]
Output_layer=layer_sizes(X,Y)[2]
parameters=init_parameters(Input_layer,Hidden_layer,Output_layer)
W1=parameters["W1"]
W2=parameters["W2"]
b1=parameters["b1"]
b2=parameters["b2"]
#初始化完成,开始神经网络
for i in range(num_iterations):
#前向传播,得到最后一个非线性输出结果和其它结果
A2,cache=propagate(X,parameters)
#计算损失
cost=Cost_Func(A2,Y)
#后向传播
back_con=back_propagtion(parameters,cache,X,Y)
#梯度下降/更新parameters
parameters=update_parameter(parameters,back_con)
#1000次打印
if print_cost:
if i%1000 == 0:
print("第 ",i," 次循环,成本为:"+str(cost))
#循环结束,返回最后的值
return parameters
主函数:
def main():
X,Y=load_planar_dataset()
parameters=model(X,Y,num_iterations=10000,print_cost=True)
#draw graphe
plot_decision_boundary(lambda x: predict(parameters, x.T), X, Y)
plt.title("Decision Boundary for hidden layer size " + str(4))
plt.show()
predictions = predict(parameters, X)
完整代码:
import numpy as np #Python科学计算的一些包
import matplotlib.pyplot as plt #画图
from Week_3_DataSet.testCases import * #测试示例
from Week_3_DataSet.planar_utils import * #一些功能
#scikit-learn提供了许多常见的机器学习算法的简单和高效的工具,如分类、回归、聚类和降维等。它还包括了用于模型选择和评估的工具,如交叉验证和各种性能指标。
#导数数据挖掘和数据分析的一些包
import sklearn
import sklearn.datasets #它提供了一系列用于测试和学习的数据集。这个模块包含了多种类型的数据集
import sklearn.linear_model #它包含了许多线性模型,这些模型可以用于各种任务,如回归、分类和异常检测。
#1.定义神经网络的结构
def layer_sizes(X,Y):
# X:样本集合 维度(数据的特征数量,数据量) 每一个数据有两个特征,即:每一个点有X和Y两个坐标值
# Y:标签的集合(标签数量,数据量) 每一个数据有一个标签,即:0/1 == 红/蓝色
Input_layer=X.shape[0]#输入层的神经元数量,它等于数据的特征数量,每个数据点有两个特征(x ,y )坐标
Hidden_layer=4 #隐藏层单元的数量,设置4
Output_layer=Y.shape[0]#,表示输出层只有一个神经元来预测一个标签(0或1)。
return (Input_layer,Hidden_layer,Output_layer)
#2.初始化函数
def init_parameters(Input_layer,Hidden_layer,Output_layer):
np.random.seed(2)
#由于只有一层隐藏层,所以可以显式的写出初始化W1和W2
W1=np.random.randn(Hidden_layer,Input_layer)*0.01 #W的维度(当前层的单元数,上一层的单元数)
b1=np.zeros(shape=(Hidden_layer,1)) #b的维度(当前层的单元数,1)
W2=np.random.randn(Output_layer,Hidden_layer)*0.01
b2=np.zeros(shape=(Output_layer,1))
#断言测试个格式
assert(W1.shape==(Hidden_layer,Input_layer))
assert(b1.shape==( Hidden_layer , 1))
assert(W2.shape==(Output_layer,Hidden_layer))
assert(b2.shape==(Output_layer,1))
parameters ={
"W1":W1,
"b1":b1,
"W2":W2,
"b2":b2
}
return parameters
#3.激活函数
#Numpy里面有thah函数,但是为了练习还是自己写一个吧
def tanh(z): #传入一个线性的函数
A=(np.exp(z)-np.exp(-z))/(np.exp(z)+np.exp(-z))
return A #A是非线性输出
#因为是二分类任务,所以对于最后的结果输出一定要用sigmoid函数
def sigmoid(z):
A=1/(1+np.exp(-z))
return A
#4.构建前向传播(不用Y)
def propagate(X,parameters):
W1=parameters["W1"]
b1=parameters["b1"]
W2=parameters["W2"]
b2=parameters["b2"]
Z1=np.dot(W1,X)+b1
A1=tanh(Z1)
#输出层计算
Z2=np.dot(W2,A1)+b2
#因为是二分类任务,所以对于最后的结果输出一定要用sigmoid函数
A2=sigmoid(Z2) #A2就是最后的输出
cache={
"Z1":Z1,
"A1":A1,
"Z2":Z2,
"A2":A2
} #为什么要存呢?
return (A2,cache)
# #测试forward_propagation
# print("=========================测试forward_propagation=========================")
# X_assess, parameters = forward_propagation_test_case()
# A2, cache = propagate(X_assess, parameters)
# print(np.mean(cache["Z1"]), np.mean(cache["A1"]), np.mean(cache["Z2"]), np.mean(cache["A2"]))
#5.损失函数构建
# 首先,A2是输出的结果,也就是预测,也就是Yhat,
# 其次,我们需要真实的标签Y
def Cost_Func(A2,Y):
m=Y.shape[1] #总数据数
sum_cost=-(
np.multiply(np.log(A2),Y)
+
np.multiply(np.log(1-A2),(1-Y))
)
cost=np.sum(sum_cost)/m
#当前通过矩阵计算的得到的应该还是一个矩阵,这个不行,我们需要一个数
cost=float(np.squeeze(cost))
return cost
# #测试compute_cost
# print("=========================测试compute_cost=========================")
# A2 , Y_assess , parameters = compute_cost_test_case()
# print("cost = " + str(Cost_Func(A2,Y_assess)))
#后向传播
#首先,我们需要损失函数想相较于最后一层输出的导数
#我们还需要使用链式法则计算每一层的W和b的梯度
def back_propagtion(parameters,cache,X,Y):
m=Y.shape[1] #总数据数
W1=parameters["W1"]
W2=parameters["W2"]
A1=cache["A1"]
A2=cache["A2"]
#就算损失函数较于最后一层输出的导数dz
#带入公式就好
dZ2=A2-Y
dW2=(1/m) *(np.dot(dZ2,A1.T))
db2=(1/m) * np.sum(dZ2,axis=1,keepdims=True)
#使用公式 g`(z)=1-a^2
dZ1=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)
back_con={
"dW1":dW1,
"dW2":dW2,
"db2":db2,
"db1":db1
}
return back_con
# #测试backward_propagation
# print("=========================测试backward_propagation=========================")
# parameters, cache, X_assess, Y_assess = backward_propagation_test_case()
# grads = back_propagtion(parameters, cache, X_assess, Y_assess)
# print ("dW1 = "+ str(grads["dW1"]))
# print ("db1 = "+ str(grads["db1"]))
# print ("dW2 = "+ str(grads["dW2"]))
# print ("db2 = "+ str(grads["db2"]))
#梯度下降,也就是更新参数
def update_parameter(parameters,back_con,learning_rate=1.2):
#原数据
W1,W2=parameters["W1"],parameters["W2"]
b1,b2=parameters["b1"],parameters["b2"]
#它们的导数
dW1,dW2,db1,db2=back_con["dW1"],back_con["dW2"],back_con["db1"],back_con["db2"]
W1=W1-learning_rate*dW1
b1=b1-learning_rate*db1
W2=W2-learning_rate*dW2
b2=b2-learning_rate*db2
update ={
"W1":W1,
"b1":b1,
"W2":W2,
"b2":b2
}
return update
# #测试update_parameters
# print("=========================测试update_parameters=========================")
# parameters, grads = update_parameters_test_case()
# parameters = update_parameter(parameters, grads)
# print("W1 = " + str(parameters["W1"]))
# print("b1 = " + str(parameters["b1"]))
# print("W2 = " + str(parameters["W2"]))
# print("b2 = " + str(parameters["b2"]))
def model(X,Y,num_iterations,print_cost=False):
np.random.seed(3)
#开始初始化
Input_layer=layer_sizes(X,Y)[0] #导入输入层、隐藏层和输出层
Hidden_layer=layer_sizes(X,Y)[1]
Output_layer=layer_sizes(X,Y)[2]
parameters=init_parameters(Input_layer,Hidden_layer,Output_layer)
W1=parameters["W1"]
W2=parameters["W2"]
b1=parameters["b1"]
b2=parameters["b2"]
#初始化完成,开始神经网络
for i in range(num_iterations):
#前向传播,得到最后一个非线性输出结果和其它结果
A2,cache=propagate(X,parameters)
#计算损失
cost=Cost_Func(A2,Y)
#后向传播
back_con=back_propagtion(parameters,cache,X,Y)
#梯度下降/更新parameters
parameters=update_parameter(parameters,back_con)
#1000次打印
if print_cost:
if i%1000 == 0:
print("第 ",i," 次循环,成本为:"+str(cost))
#循环结束,返回最后的值
return parameters
#构建预测
def predict(parameters,X):
#parameters - 包含参数的字典类型的变量。
#X - 输入数据(n_x,m)
A2,cache=propagate(X,parameters)
predic=np.round(A2)
return predic
# #测试predict
# print("=========================测试predict=========================")
# parameters, X_assess = predict_test_case()
# predictions = predict(parameters, X_assess)
# print("预测的平均值 = " + str(np.mean(predictions)))
def main():
X,Y=load_planar_dataset()
parameters=model(X,Y,num_iterations=10000,print_cost=True)
#draw graphe
plot_decision_boundary(lambda x: predict(parameters, x.T), X, Y)
plt.title("Decision Boundary for hidden layer size " + str(4))
plt.show()
predictions = predict(parameters, X)
# Y=Y*0 改变颜色玩一下
# plt.scatter(X[0, :], X[1, :], c=np.squeeze(Y), s=40, cmap=plt.cm.Spectral) #绘制散点图
# plt.show()
# print(X.shape)#(2,400) 点的横坐标和纵坐标
# print(Y.shape)#(1,400) 标签,红0蓝1
# #原样测试:
# plt.scatter(X[0, :], X[1, :], c=np.squeeze(Y), s=40, cmap=plt.cm.Spectral) #绘制散点图
# plt.show()
# #测试:使用自带的函数逻辑回归的线性分割
# clf=sklearn.linear_model.LogisticRegressionCV()
# clf.fit(X.T,Y.T)
# plot_decision_boundary(lambda x: clf.predict(x), X, Y) #绘制决策边界
# plt.title("Logistic Regression") #图标题、
# plt.show()
# LR_predictions = clf.predict(X.T) #预测结果
# print(Y.shape)
# print(LR_predictions.shape)
# print ("逻辑回归的准确性: %d " % float((np.dot(Y, LR_predictions) +
# np.dot(1 - Y,1 - LR_predictions)) / float(Y.size) * 100) +
# "% " + "(正确标记的数据点所占的百分比)")
if __name__ =="__main__":
main()
输出结果:
总结
这个人太懒了,什么都没有写~~
疑缺待补