从头推导与实现 BP 网络

回归模型

目标

学习 \(y = 2x\)

模型

单隐层、单节点的 BP 神经网络

策略

Mean Square Error 均方误差

\[MSE = \frac{1}{2}(\hat{y} - y)^2
\]

模型的目标是 \(\min \frac{1}{2} (\hat{y} - y)^2\)

算法

朴素梯度下降。在每个 epoch 内,使模型对所有的训练数据都误差最小化。

网络结构

Forward Propagation Derivation

\[E = \frac{1}{2}(\hat{Y}-Y)^2 \\
\hat{Y} = \beta \\
\beta = W b \\
b = sigmoid(\alpha) \\
\alpha = V x
\]

Back Propagation Derivation

模型的可学习参数为 \(w,v\) ,更新的策略遵循感知机模型:

参数 w 的更新算法

\[w \leftarrow w + \Delta w \\
\Delta w = - \eta \frac{\partial E}{\partial w} \\
\frac{\partial E}{\partial w} = \frac{\partial E}{\partial \hat{Y}} \frac{\partial \hat{Y}}{\partial \beta} \frac{\partial \beta}{\partial w} \\
= (\hat{Y} - Y) \cdot 1 \cdot b
\]

参数 v 的更新算法

\[v \leftarrow v + \Delta v \\
\Delta v = -\eta \frac{\partial E}{\partial v} \\
\frac{\partial E}{\partial v} = \frac{\partial E}{\partial \hat{Y}} \frac{\partial \hat{Y}}{\partial \beta} \frac{\partial \beta}{\partial b}
\frac{\partial \beta}{\partial \alpha} \frac{\partial \alpha}{\partial v} \\
= (\hat{Y} - Y) \cdot 1 \cdot w \cdot \frac{\partial \beta}{\partial \alpha} \cdot x \\
\frac{\partial \beta}{\partial \alpha} = sigmoid(\alpha) [ 1 - sigmoid(\alpha) ] \\
sigmoid(\alpha) = \frac{1}{1+e^{-\alpha}}
\]

代码实现

C++ 实现

#include <iostream>
#include <cmath> using namespace std; class Network {
public :
Network(float eta) :eta(eta) {} float predict(int x) { // forward propagation
this->alpha = this->v * x;
this->b = this->sigmoid(alpha);
this->beta = this->w * this->b;
float prediction = this->beta;
return prediction;
} void step(int x, float prediction, float label) {
this->w = this->w
- this->eta
* (prediction - label)
* this->b;
this->alpha = this->v * x;
this->v = this->v
- this->eta
* (prediction - label)
* this->w
* this->sigmoid(this->alpha) * (1 - this->sigmoid(this->alpha))
* x;
}
private:
float sigmoid(float x) {return (float)1 / (1 + exp(-x));}
float v = 1, w = 1, alpha = 1, beta = 1, b = 1, prediction, eta;
}; int main() { // Going to learn the linear relationship y = 2*x
float loss, pred;
Network model(0.01);
cout << "x is " << 3 << " prediction is " << model.predict(3) << " label is " << 2*3 << endl;
for (int epoch = 0; epoch < 500; epoch++) {
loss = 0;
for (int i = 0; i < 10; i++) {
pred = model.predict(i);
loss += pow((pred - 2*i), 2) / 2;
model.step(i, pred, 2*i);
}
loss /= 10;
cout << "Epoch: " << epoch << " Loss:" << loss << endl;
}
cout << "x is " << 3 << " prediction is " << model.predict(3) << " label is " << 2*3 << endl;
return 0;
}

C++ 运行结果

初始网络权重,对数据 x=3, y=6的 预测结果为 \(\hat{y} = 0.952534\) 。

训练了 500 个 epoch 以后,平均损失下降至 7.82519,对数据 x=3, y=6的 预测结果为 \(\hat{y} = 11.242\) 。

PyTorch 实现

# encoding:utf8
# 极简的神经网络,单隐层、单节点、单输入、单输出 import torch as t
import torch.nn as nn
import torch.optim as optim class Model(nn.Module):
def __init__(self, in_dim, out_dim):
super(Model, self).__init__()
self.hidden_layer = nn.Linear(in_dim, out_dim) def forward(self, x):
out = self.hidden_layer(x)
out = t.sigmoid(out)
return out if __name__ == '__main__':
X, Y = [[i] for i in range(10)], [2*i for i in range(10)]
X, Y = t.Tensor(X), t.Tensor(Y)
model = Model(1, 1)
optimizer = optim.SGD(model.parameters(), lr=0.01)
criticism = nn.MSELoss(reduction='mean')
y_pred = model.forward(t.Tensor([[3]]))
print(y_pred.data)
for i in range(500):
optimizer.zero_grad()
y_pred = model.forward(X)
loss = criticism(y_pred, Y)
loss.backward()
optimizer.step()
print(loss.data)
y_pred = model.forward(t.Tensor([[3]]))
print(y_pred.data)

PyTorch 运行结果

初始网络权重,对数据 x=3, y=6的 预测结果为 $\hat{y} =0.5164 $ 。

训练了 500 个 epoch 以后,平均损失下降至 98.8590,对数据 x=3, y=6的 预测结果为 \(\hat{y} = 0.8651\) 。

结论

居然手工编程的实现其学习效果比 PyTorch 的实现更好,真是奇怪!但是我估计差距就产生于学习算法的不同,PyTorch采用的是 SGD。

分类模型

目标

目标未知,因为本实验的数据集是对 iris 取前两类样本,然后把四维特征降维成两维,得到本实验的数据集。

数据简介:

-1.653443 0.198723 1 0  # 前两列为特正,最后两列“1 0”表示第一类
1.373162 -0.194633 0 1 # "0 1",第二类

模型

单隐层双输入输入节点的分类 BP 网络

策略

在整个模型的优化过程中,使得在整个训练集上交叉熵最小:

\[\mathop{\arg\min}_{\theta} H(Y, \hat{Y})
\]

交叉熵:

\[\begin{align}
H(y, \hat y) & = -\sum_{i=1}^{2} y_i \log \hat{y}_i \\
& = - (y_1 \log \hat{y}_1 + y_2 \log \hat{y}_2)
\end{align}
\]

算法

梯度下降,也即在每个 epoch 内,使模型对所有的训练数据都误差最小。

网络结构

如图

Forward Propagation

公式推导如下

\[a_1 = w_{11}x_1 + w_{21}x_2 \\
a_2 = w_{12}x_1 + w_{22}x_2 \\
b_1 = sigmoid(a_1) \\
b_2 = sigmoid(a_2) \\
\hat{y_1} = \frac{\exp(b_1)}{\exp(b_1) + \exp(b_2)} \\
\hat{y_2} = \frac{\exp(b_2)}{\exp(b_1) + \exp(b_2)} \\
\]

\[\begin{align}
E^{(k)} & = H(y^{(k)}, \hat{y}^{(k)}) \\
& =- (y_1 \log\hat{y}_1 + y_2 \log\hat{y}_2)
\end{align}
\]

Back Propagation

\[\frac{\partial E}{\partial w_{11}} = (
\frac{\partial E}{\partial\hat{y}_1} \frac{\partial\hat{y}_1}{\partial b_1}
+ \frac{\partial E}{\partial\hat{y}_2} \frac{\partial\hat{y}_2}{\partial b_1})
\frac{\partial b_1}{\partial a_1} \frac{\partial a_1}{\partial w_{11}}
\]

其中,

\[\frac{\partial E}{\partial\hat{y}_1} = \frac{-y_1}{\hat{y}_1} \\
\frac{\partial E}{\partial\hat{y}_2} = \frac{-y_2}{\hat{y}_2} \\
\frac{\partial \hat{y}_1}{\partial b_1} = \hat{y}_1 (1- \hat{y}_1) \\
\frac{\partial \hat{y}_2}{\partial b_1} = - \hat{y}_1 \hat{y}_2 \\
\frac{\partial b1}{\partial a1} = sigmoid(a_1) [1 - sigmoid(a_1)] \\
\frac{\partial a_1}{\partial w_{11}} = x_1
\]

所以,

\[\frac{\partial E}{\partial w_{11}} = (\hat{y}_1 - y_1) sigmoid(a_1) [ 1 - sigmoid(a_1)] x_1
\]

类似的,可得

\[\frac{\partial E}{\partial w_{21}} = (\hat{y}_1 - y_1) sigmoid(a_1) [ 1 - sigmoid(a_1)] x_2 \\
\frac{\partial E}{\partial w_{12}} = (\hat{y}_2 - y_2) sigmoid(a_2) [ 1 - sigmoid(a_2)] x_1 \\
\frac{\partial E}{\partial w_{22}} = (\hat{y}_2 - y_2) sigmoid(a_2) [ 1 - sigmoid(a_2)] x_2
\]

代码实现

Python 3 实现

# encoding:utf8

from math import exp, log
import numpy as np def load_data(fname):
X, Y = list(), list()
with open(fname, encoding='utf8') as f:
for line in f:
line = line.strip().split()
X.append(line[:2])
Y.append(line[2:])
return X, Y class Network:
eta = 0.5
w = [[0.5, 0.5], [0.5, 0.5]]
b = [0.5, 0.5]
a = [0.5, 0.5]
pred = [0.5, 0.5] def __sigmoid(self, x):
return 1 / (1 + exp(-x)) def forward(self, x):
self.a[0] = self.w[0][0] * x[0] + self.w[1][0] * x[1]
self.a[1] = self.w[0][1] * x[0] + self.w[1][1] * x[1]
self.b[0] = self.__sigmoid(self.a[0])
self.b[1] = self.__sigmoid(self.a[1])
self.pred[0] = self.__sigmoid(self.b[0]) / (self.__sigmoid(self.b[0]) + self.__sigmoid(self.b[1]))
self.pred[1] = self.__sigmoid(self.b[1]) / (self.__sigmoid(self.b[0]) + self.__sigmoid(self.b[1]))
return self.pred def step(self, x, label):
g = (self.pred[0] - label[0]) * self.__sigmoid(self.a[0]) * (1-self.__sigmoid(self.a[0])) * x[0]
self.w[0][0] = self.w[0][0] - self.eta * g
g = (self.pred[0] - label[0]) * self.__sigmoid(self.a[0]) * (1 - self.__sigmoid(self.a[0])) * x[1]
self.w[1][0] = self.w[1][0] - self.eta * g
g = (self.pred[1] - label[1]) * self.__sigmoid(self.a[1]) * (1 - self.__sigmoid(self.a[1])) * x[0]
self.w[0][1] = self.w[0][1] - self.eta * g
g = (self.pred[1] - label[1]) * self.__sigmoid(self.a[1]) * (1 - self.__sigmoid(self.a[1])) * x[1]
self.w[1][1] = self.w[1][1] - self.eta * g if __name__ == '__main__':
X, Y = load_data('iris.txt')
X, Y = np.array(X).astype(float), np.array(Y).astype(float) model = Network()
pred = model.forward(X[0])
print("Label: %d %d, Pred: %f %f" % (Y[0][0], Y[0][1], pred[0], pred[1])) epoch = 100
loss = 0
for i in range(epoch):
loss = 0
for j in range(len(X)):
pred = model.forward(X[j])
loss = loss - Y[j][0] * log(pred[0]) - Y[j][1] * log(pred[1])
model.step(X[j], Y[j])
print("Loss: %f" % (loss)) pred = model.forward(X[0])
print("Label: %d %d, Pred: %f %f" % (Y[0][0], Y[0][1], pred[0], pred[1]))

网络在训练之前,预测为:

Label: 1 0, Pred: 0.500000 0.500000
Loss: 55.430875

学习率 0.5, 训练 100 个 epoch 以后:

Label: 1 0, Pred: 0.593839 0.406161
Loss: 52.136626

结论

训练后损失减小,模型预测的趋势朝着更贴近标签的方向前进,本次实验成功。

只不过模型的参数较少,所以学习能力有限。

Reference

Derivative of Softmax Loss Function

从头推导与实现 BP 网络的更多相关文章

  1. 我对BP网络的简单的理解

    最近在学习tf的神经网络算法,十多年没有学习过数学了,本来高中数学的基础,已经彻底还给数学老师了.所以我把各种函数.公式和推导当做黑盒子来用,理解他们能做到什么效果,至于他们是如何做到的,暂时不去深究 ...

  2. 关于BP网络的一些总结

    背景 前段时间,用过一些模型如vgg,lexnet,用于做监督学习训练,顺带深入的学习了一下相关模型的结构&原理,对于它的反向传播算法记忆比较深刻, 就自己的理解来描述一下BP网络. 关于BP ...

  3. 基于Levenberg-Marquardt训练算法的BP网络Python实现

    经过一个多月的努力,终于完成了BP网络,参考的资料为: 1.Training feed-forward networks with the Marquardt algorithm 2.The Leve ...

  4. 基于Opencv自带BP网络的车标简易识别

    代码地址如下:http://www.demodashi.com/demo/12966.html 记得把这几点描述好咯:代码实现过程 + 项目文件结构截图 + 演示效果 1.准备工作 1.1 训练集和测 ...

  5. BP网络中的反向传播

    本文的主要参考:How the backpropagation algorithm works 下面是BP网络的参数结构示意图 首先定义第l层网络第j个神经元的输出(activation) 为了表示简 ...

  6. BP网络简单实现

    目录 BP算法的简单实现 Linear 全连接层 ReLu MSELoss 交叉熵损失函数 BP算法的简单实现 """ BPnet 简易实现 约定输入数据维度为(N, i ...

  7. Matlab实现BP网络识别字母

    训练样本空间   每个样本使用5×5的二值矩阵表征一个字母.一共10个字母类型,分别是N,I,L,H,T,C,E,F,Z,V.每个字母9个样本.共90个. N1=[1,0,0,0,1; 1,0,0,0 ...

  8. MATLAB——BP网络的设计

  9. bp网络全解读

    https://blog.csdn.net/weixin_40432828/article/details/82192709

随机推荐

  1. 手写AVL 树(下)

    上一篇 手写AVL树上实现了AVL树的插入和查询 上代码: 头文件:AVL.h #include <iostream> template<typename T1,typename T ...

  2. 与前端(使用vue框架)对接的问题

    1.跨域问题 跨域问题是: 浏览器的同源安全策略 没错,就是这家伙干的,浏览器只允许请求当前域的资源,而对其他域的资源表示不信任.那怎么才算跨域呢? 请求协议http,https的不同 域domain ...

  3. js 中数组的遍历

    var x = ['a','b','c'] x.forEach(function(val,k){ console.log(val + '--' +k); }) a--0 b-- 1 c-- 2 var ...

  4. Mac 下安装Fiddler抓包工具

    需求 我们都知道在Mac电脑下面有一个非常好的抓包工具:Charles.但是这个只能抓代理的数据包.但是有时候想要调试本地网卡的数据库 Charles 就没办法了.就想到了在windows下面的一个F ...

  5. 转载--python模块

    模块,用一砣代码实现了某个功能的代码集合. 类似于函数式编程和面向过程编程,函数式编程则完成一个功能,其他代码用来调用即可,提供了代码的重用性和代码间的耦合.而对于一个复杂的功能来,可能需要多个函数才 ...

  6. 【UNIX环境高级编程】文件I/O

    [UNIX环境高级编程]文件I/O大多数文件I/O只需要5个函数: open.read.write.lseek以及close 不带缓冲的I/O: 每个read和write都调用内核中的一个系统调用 1 ...

  7. B-Tree与B+Tree简明扼要的区别

    原文:https://blog.csdn.net/zhuanzhe117/article/details/78039692 看了很多讲B树和B+树的文章,大多都是围绕各自的特性讲的,第一,树中每个结点 ...

  8. jedis & common pool

    http://mvnrepository.com/artifact/redis.clients/jedis http://mvnrepository.com/artifact/org.apache.c ...

  9. 【记录tomcat报错解决办法】tomcat请求组件没有找到的问题

    报错原因: An incompatible version 1.1.14 of APR based Apache Tomcat Native library is installed, while T ...

  10. 学习MySQL过程中的随笔一

    第一天: 关于安装出现了很多问题,各种不懂的bug,没得法只能在网上查找解决方法,终于!!! 登录成功了,一下午的时间 附上参考资料:https://blog.csdn.net/weibo_boer/ ...