简单的神经网络示意图如下:
其中的每个节点都做着和logistic回归类似的工作,只不过仅输出层计算成本
每一层其实就是把前一层输入的特征向量进行预测,并把这些每个节点的预测值作为新的特征向量来传给下一层。简单来说就是把原本可以”具体”反应数据的最原始的特征向量抽象化,而每个节点都有自己的参数
最后通过不断迭代,得到满意的参数
首先对于单个样本,在隐藏层:
当隐藏层节点数很多时,这样就显得不高效了,因此要将这几步计算向量化,即:
可以帮助记忆的特点是:
1)参数矩阵在整个过程中,维度是不会改变的
2)这些参数(w、b)矩阵,各层算出来的关键数值矩阵(z、a),他们的行数都是当前层的节点数
每一层都有自己输出的”特征向量“,或者说“激活向量”,用于激活下一层的运作。比如这个例子中,隐藏层对单个样本输出的是一个(4,1)向量,4代表隐藏层节点数,1代表一个样本,对于它的下一层——输出层,这个(4,1)向量就是输出层用于学习参数的”学习资料“。
下文中的特征向量和激活向量可能会交替使用,但是只要知道都是表示当前层的输出产物就行了
所以总体是这样的(单个样本):
那么对于整个样本,就和我们在logistic回归得到的一样:把代表每个样本的特征向量堆叠成列。所以我们要做的就是把a[0]变成A[0]:
这样其实后面的关键数值矩阵的维度也会有相应的变化,这些矩阵的列也从1变成了m——样本数!
至此,我们完成了整个样本的正向传播
def forward_propagation(X , parameters):
"""
参数:
X - 维度为(n_x,m)的输入数据。
parameters - 初始化函数(initialize_parameters)的输出
返回:
A2 - 使用sigmoid()函数计算的第二次激活后的数值
cache - 包含“Z1”,“A1”,“Z2”和“A2”的字典类型变量
"""
W1 = parameters["W1"]
b1 = parameters["b1"]
W2 = parameters["W2"]
b2 = parameters["b2"]
#开始传播
Z1 = np.dot(W1,X) + b1 # Z1的维度是 (n_h,n_x)
A1 = np.tanh(Z1)
Z2 = np.dot(W2,A1) + b2 # Z2的维度是 (n_y,n_h)
A2 = sigmoid(Z2)
assert(A2.shape == (n_y,X.shape[1]))
#创建字典存储正向传播过程中产生的预测值
cache = {
"Z1":Z1,
"A1":A1,
"Z2":Z2,
"A2":A2
}
return A2,cache
这个例子中,我们的损失函数还是
成本也只是各样本损失值求和后取平均而已
#构建计算成本的函数
def compute_cost(A2,Y,parameters):
"""
参数:
A2 - 使用sigmoid()函数计算的第二次激活后的数值
Y - "True"标签向量,维度为(1,数量)
parameters - 一个包含W1,b1,W2和b2的字典类型的变量
返回:
成本
"""
m = Y.shape[1]
W1 = parameters["W1"]
W2 = parameters["W2"]
#计算成本
cost = (-1/m)*np.sum(Y * np.log(A2) + (1 - Y) * (np.log(1-A2))) # 两个矩阵用 '*' 代表着对应位置的元素相乘
cost = float(np.squeeze(cost))
assert(isinstance(cost,float))
return cost
这里Y、A2的维度是(1,m)每一列都是样本的损失值
我们还是从单个样本讲起:
其实就是链式求导法则的运用,只不过运算是在矩阵上运行的。不过我们可以总结出几点:
1)链式求导可以化简约掉的就大胆地约,比如这里求dz[2]的过程
2)求导公式化出来后,如果是两个不同维度矩阵相乘,那么可以根据答案矩阵的维度(dw[2]的维度又等于w[2]的维度)来进行等式右边矩阵的相乘位置或者转置的变换,比如这里求dw[2]的过程,就进行了转置
3)公式化出来后如果是两个维度相同的矩阵相乘,那么就是两矩阵相同位置的元素相乘。用的是 matrix1 * matrix2 或者numpy.multiply(matrix1,matrix2)
下面来看整个训练集的反向传播
其实变化的只有:
1)a[2]变A[2],这一步其实在正向传播已经完成
2) 参数dw变dW,前面再除以m,因为得出dW的运算式变了但是他的维度却没变,事实上也可以看出dW中的每个参数都是各个样本对其影响之和
3)而db因为它的得出过程,就只是dz,没有另一个矩阵来相乘从而保持db的维度不变但是又包含各样本的影响。而就像我们前面所说那样,参数矩阵的维度是不能变的,因此我们需要手动将各列的样本对b的影响求和,从而把db维度保持和b一致。用到的是numpy.sum(matrix1,axis=1,keepdims = True) —— axis=1代表压缩列,即变成(n,1);axis=0代表压缩0;keepdims = True保证了得出的运算结果是矩阵而不是列表或者其他结构
所以整个反向传播有6个关键公式
def backward_propagation(parameters,cache,X,Y):
"""
使用上述说明搭建反向传播函数。
参数:
parameters - 包含我们的参数的一个字典类型的变量。
cache - 包含“Z1”,“A1”,“Z2”和“A2”的字典类型的变量。
X - 输入数据,维度为(2,数量)
Y - “True”标签,维度为(1,数量)
返回:
grads - 包含W和b的导数一个字典类型的变量。
"""
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,A1.T) #要除以m的原因是,这里参数矩阵中的每一个参数都是m个样本对这个参数的影响之和
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
这个没什么好说的了,就是用梯度下降法来更新。注意函数参数的选择,下面直接贴出代码
#更新参数,使用(dw1,db1,dw2,db2) 来更新 (w1,b1,w2,b2)
def update_parameters(parameters,grads,learning_rate = 1.2):
W1,W2 = parameters["W1"],parameters["W2"]
b1,b2 = parameters["b1"],parameters["b2"]
dW1,dW2 = grads["dW1"],grads["dW2"]
db1,db2 = grads["db1"],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
至此,学习过程的每个步骤就完成了,下面就是把这些步骤整合起来,通过循环来进行优化了
#整合
def nn_model(X,Y,n_h,num_iterations,print_cost = False):
"""
参数:
X - 数据集,维度为(2,示例数)
Y - 标签,维度为(1,示例数)
n_h - 隐藏层的数量
num_iterations - 梯度下降循环中的迭代次数
print_cost - 如果为True,则每1000次迭代打印一次成本数值
返回:
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(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,learning_rate = 0.5)
if print_cost:
if i%1000 == 0:
print("第",i,"次循环,成本为:",cost)
return parameters
学习完后干什么?当然就是做题检验了!这里我们就只需要进行正向传播,返回得到的最终预测值
#预测
def predict(parameters,X):
A2, cache = forward_propagation(X,parameters)
predictions = np.round(A2)
return predictions
1)传入训练集
2)传入测试集进行预测
3)对比预测值和label(答案),打印正确率
4) 绘制边界——用图像展示分类成果 (具体的代码还没弄懂,待续)
#正式运行
parameters = nn_model(X,Y,n_h = 4,num_iterations=10000,print_cost = True)
#绘制边界
plot_decision_boundary(lambda x: predict(parameters,x.T),X,Y)
plt.title("Decision Boundary for hidden layer size is four")
predictions = predict(parameters,X)
print ('准确率: %d' % float((np.dot(Y, predictions.T) + np.dot(1 - Y, 1 - predictions.T)) / float(Y.size) * 100) + '%')
个人未明白记录(2021.8.16)
1.绘制边界
2.正确率的输出方法
3.该作业的具体要求(到底是怎么完成红蓝点的分类)
https://blog.csdn.net/u013733326/article/details/79702148 (吴恩达深度学习week3编程作业)