文章目录

  • 前言
  • 神经网络公式推导
    • 参数定义
    • 前向传播(forward)
    • 反向传播(backward)
      • 隐藏层和输出层的权重更新
      • 输入层和隐藏层的权重更新
  • 代码实现
    • python手写实现
    • pytorch实现
  • 总结
  • 参考

前言

因为要课上讲这东西,因此总结总结,发个博客

神经网络公式推导

参数定义

                                     模型图

假设我们有这么一个神经网络,由输入层、一层隐藏层、输出层构成。
(这里为了方便,不考虑偏置bias)
输入特征为xn
输入层与隐藏层连接的权重为vij
隐藏层的输出(经过激活函数)为ym
隐藏层与输出层连接的权重为wjk
输出层的预测值(经过激活函数)为ol
隐藏层和输出层后面都接sigmoid激活函数。
Simoid激活函数如下:

前向传播(forward)

首先,我们可以试着表示一下y1
如模型图所示可以表示为:


那么我要表示yj呢?

其中j=1时,就是y1的表示,j=m时,就是ym的表示。

同理我们可以得到:

ok表示输出层第k个神经元的预测值,这就是我们需要的输出。
至此,正向传播完毕。

反向传播(backward)

光正向传播,我们只能得到模型的预测值,不能更新模型的参数,也就是说,正向传播的时候,模型是不会被更新的。

因为我们得到了模型输出的预测值,并且我们手上有对应的真实值,我们就能够将误差反向传播,更新模型参数。

具体操作怎么操作呢?

首先,我们需要定义误差,即预测值和真实值差了多少,以此来决定模型参数更新的方向和力度。

这里我们采用简单的差的平方的损失函数:

注意,这里只是更新输出层第k个神经元所反馈的误差。

隐藏层和输出层的权重更新

首先根据已知如下:

输出层预测值ok

激活函数Sigmoid


那我们可以试着展开一下Ek


因为我们现在需要更新的是wjk,因此展开到wjk我们就能有一个比较形象的认识了。

根据梯度下降法可得,我们现在只需要求出

即可通过

来更新我们隐藏层和输出层的权重了。
那么如何计算呢?
直接求导可能有点混乱,利用复合函数求导的方法,我们可以根据链式法则将表达式展开如下:

接下来我们分别求出

以及

就可以了。

我们先给出激活函数的导数推导过程:


就是使用复合函数除的求导法则进行求导。我们可以发现sigmoid函数求导之后还是挺好看的。

接下来就是计算两个导数即可。

首先:

一眼就能看出来了吧。

这个可能会有点困难,但是仔细看看,发现还是很简单的。
首先

然后我们知道 [f(g(x))]’ = g(x)’ * f(g(x))’
例如 y = log(x^2)
那么 y’ = (x^2)’ * [log(x^2)]’ = 2*x * 1 / x^2 = 2x / x^2
由于这里f(x)是Sigmoid激活函数
f(x)’ = (1-f(x)) * f(x) (上面已经推到过了)

那么这个结果计算起来就比较简单了。

既然如此,将结果拼起来就是我们要求的结果了:


其中:

全是已知的,不就可以更新参数了嘛

因此,加个学习率这层权重更新推导就大功告成了。

输入层和隐藏层的权重更新

如果上面的推导看懂了,下面的推导就非常简单了,无非就是多展开一级,多求一次导数而已。

首先(前面已经推到过了)

那么我们可以将误差再展开一级:

那么下面这个就非常值观了

同样的,我们也分别求出三次的导数,最后拼起来就行了。



至此分别求出来了,拼起来就是我们要的结果了:


通过观察,里面全是已知的变量
那么更新公式也就有了:

至此我们公式推导就完成了。

代码实现

首先需要数据集,这里使用手写数据集。
训练集 http://www.pjreddie.com/media/files/mnist_train.csv
测试集 http://www.pjreddie.com/media/files/mnist_test.csv

python手写实现

其中比较关键的就是那两个参数的更新公式。

隐藏层和输出层的权重更新:

输入层和隐藏层的权重更新

完整代码如下:

import numpy as np
import scipy.special
import matplotlib.pyplotclass Network:def __init__(self, input_size, hidden_size, output_size, learning_rate):self.input_size = input_sizeself.hidden_size = hidden_sizeself.output_size= output_sizeself.lr = learning_rate# 初始化参数# 输入层和隐藏层之间的参数self.Vij = np.random.normal(0.0, pow(self.hidden_size, -0.5), (self.hidden_size, self.input_size))# 隐藏层和输出层之间的参数self.wjk = np.random.normal(0.0, pow(self.output_size, -0.5), (self.output_size, self.hidden_size))# sigmoid激活函数self.activation_function = lambda x: 1 / (1 + np.exp(-x))def train(self, inputs_list, targets_list):# 数据inputs = np.array(inputs_list, ndmin=2).T# 标签targets = np.array(targets_list, ndmin=2).T# 隐藏层的输入hidden_inputs = np.dot(self.Vij, inputs)# 隐藏层的输出Yj = self.activation_function(hidden_inputs)# 输出层的输入final_inputs = np.dot(self.wjk, Yj)# 输出层的输出Ok = self.activation_function(final_inputs)# 输出层的误差->更新隐藏层和输出层之间的参数# targets:10x1  # Ok:10x1output_errors = targets - Ok # wjk: 10x128 # output_errors:10x1# Yj:128x1self.wjk += self.lr * np.dot((output_errors * Ok * (1 - Ok)), np.transpose(Yj))# 隐藏层的误差->输入层和隐藏层之间的参数# wjk: 10x128 # output_errors:10x1hidden_errors = np.dot(self.wjk.T, output_errors * (1 - Ok) * Ok) # wjk: 10x128 output_errors:10x1# Vij:128x784# hidden_errors: 128x1# Yj:128x1# inputs:784x1self.Vij += self.lr * np.dot((hidden_errors * Yj * (1 - Yj)), np.transpose(inputs)) # 简单计算均方误差errors = (np.power(output_errors, 2).sum() + np.power(hidden_errors, 2).sum())return errorsdef predict(self, inputs_list):inputs = np.array(inputs_list, ndmin=2).Thidden_inputs = np.dot(self.Vij, inputs)Yj = self.activation_function(hidden_inputs)final_inputs = np.dot(self.wjk, Yj)Ok = self.activation_function(final_inputs)return Okdef get_acc(self, data):sum = len(data)true_n = 0for d in data:all_values = d.split(',')inputs = (np.asfarray(all_values[1:])/255.0 * 0.99) + 0.01pred = np.argmax(self.predict(inputs))if int(pred) == int(all_values[0]):true_n += 1return true_n / suminput_size = 784
hidden_size = 128
output_size = 10
learning_rate = 0.001
epoch = 2model = Network(input_size=input_size, hidden_size=hidden_size, output_size=output_size, learning_rate=learning_rate)training_data_file = open("mnist_train.csv", "r")
training_data_list = training_data_file.readlines()
training_data_file.closetesting_data_file = open("mnist_test.csv", "r")
testing_data_list = testing_data_file.readlines()
testing_data_file.closefor i in range(epoch):errors = []for record in training_data_list:all_values = record.split(',')# 输入数据inputs = (np.asfarray(all_values[1:])/255.0 * 0.99) + 0.01# 标签数据targets = np.zeros(output_size) + 0.01targets[int(all_values[0])] = 0.99# 训练train_errors = model.train(inputs, targets)errors.append(train_errors)print("epoch", i)print("训练集平均损失为", np.mean(errors))
train_acc = model.get_acc(training_data_list)
test_acc = model.get_acc(testing_data_list)
print("训练集准确率", train_acc)
print("测试集准确率", test_acc)

输出:

pytorch实现

import pandas as pd
import numpy as np
import torch as th
import torch.nn as nn
import torch.utils.data.dataloader as dataloader
from torch.utils.data import TensorDataset
from tqdm import tqdm
from sklearn.metrics import accuracy_scoredef get_dataloader(batch_size, file_name):filedata = pd.read_csv(file_name, header=None)label = filedata.values[:, 0]data = filedata.values[:, 1:]data = th.from_numpy(data).to(th.float32)label = th.from_numpy(label).to(th.long)  # 标签这里用不到,但是不影响吧dataset = TensorDataset(data, label)data_loader = dataloader.DataLoader(dataset=dataset, shuffle=True, batch_size=batch_size)  return data_loaderbatch_size = 256
input_size = 784
hidden_size = 128
output_size = 10
learning_rate = 0.001
epoch = 2
test_loader = get_dataloader(batch_size=batch_size, file_name = "mnist_test.csv")
train_loader = get_dataloader(batch_size=batch_size, file_name = "mnist_train.csv")class network(nn.Module):def __init__(self, input_size, hidden_size, output_size):super().__init__()self.input_size = input_sizeself.hidden_size = hidden_sizeself.output_size = output_sizeself.w1 = nn.Linear(input_size, hidden_size, bias=False)self.w2 = nn.Linear(hidden_size, output_size, bias=False)self.sigmoid = nn.Sigmoid()def forward(self, x):i2h = self.w1(x)i2h = self.sigmoid(i2h)h2o = self.w2(i2h)h2o = self.sigmoid(h2o)return h2odef evaluate_model(model, iterator, criterion):all_pred = []all_y = []losses = []for i, batch in tqdm(enumerate(iterator)):if th.cuda.is_available():input = batch[0].cuda()label = batch[1].type(th.cuda.LongTensor)else:input = batch[0]label = batch[1]y_pred = model(input)loss = criterion(y_pred, label)losses.append(loss.cpu().detach().numpy())predicted = th.max(y_pred.cpu().data, 1)[1]all_pred.extend(predicted.numpy())all_y.extend(label.cpu().detach().numpy())score = accuracy_score(all_y, np.array(all_pred).flatten())return score, np.mean(losses)model = network(input_size=input_size, hidden_size=hidden_size, output_size=output_size)optimizer = th.optim.Adam(model.parameters(), lr=learning_rate) # Adam优化器
loss_func = nn.CrossEntropyLoss() # 损失函数train_scores = []
test_scores = []
train_losses = []
test_losses = []
for epoch in range(epoch):model.train() # 模型训练for step, (x, label) in enumerate(train_loader):pred = model(x)loss = loss_func(pred, label)      # 损失函数optimizer.zero_grad()               # 清空梯度loss.backward()                     # 反向传播optimizer.step()                    # 优化器model.eval() # 固定参数train_score, train_loss = evaluate_model(model, train_loader, loss_func)test_score, test_loss = evaluate_model(model, test_loader, loss_func)train_losses.append(train_loss)test_losses.append(test_loss)train_scores.append(train_score)test_scores.append(test_score)print('#' * 20)print('train_acc:{:.4f}'.format(train_score))print('test_acc:{:.4f}'.format(test_score))import matplotlib.pyplot as plt
# 训练完画图
x = [i for i in range(len(train_scores))]
fig = plt.figure()
plt.plot(x, train_scores, color ="r", label="train_score")
plt.plot(x, test_scores, color="g", label="test_score")
plt.legend()
plt.show()# 训练完画图
x = [i for i in range(len(train_scores))]
fig = plt.figure()
plt.plot(x, train_losses, color ="r", label="train_loss")
plt.plot(x, test_losses, color="g", label="test_loss")
plt.legend()
plt.show()

输出



总结

感觉从推导到代码实现也是一个反复的过程,从推导发现代码写错了,写不出代码了就要去看看推导的过程,这个过程让我对反向传播有了较全面的理解。

我们发现,手写代码运行时间要一分多钟而pytorch其实只要10s不到,毕竟框架,底层优化很多,用起来肯定用框架。

以及二者准确率有一些差距,可能是因为pytorch里使用了交叉熵损失函数,比较适合分类任务;手写的并没有分batch,而是所有数据直接更新参数,但是pytorch里分了batch,分batch能够使得模型训练速度加快(并行允许),也使得模型参数更新的比较平稳。

代码+数据集+PPT

参考

神经网络反向传播算法及代码实现

机器学习之神经网络的公式推导与python代码(手写+pytorch)实现相关推荐

  1. 一百多行 Python 代码手写蕃茄钟

    对现实不满,充满无力感,可是作为一个码农,大时代里的一个小人物,并不能改变什么,只能在程序代码里的世界里找点乐趣吧, 东坡先生如是说: 莫听穿林打叶声,何妨吟啸且徐行.竹杖芒鞋轻胜马,谁怕?一蓑烟雨任 ...

  2. python重点知识归纳_一文了解机器学习知识点及其算法(附python代码)

    一文了解机器学习知识点及其算法(附python代码) 来源:数据城堡 时间:2016-09-09 14:05:50 作者: 机器学习发展到现在,已经形成较为完善的知识体系,同时大量的数据科学家的研究成 ...

  3. Python代码如何写的更优雅

    首先最重要的一点, 忘掉其他语言里的写法, 尝试使用Python风格进行code, 熟练之后,你会觉得她真的很美! 1. 多个值进行初始化 # > yes s1,s2,s3 = [],[],0 ...

  4. linux 让代码美观,为什么 Python 代码要写得美观而明确 | Linux 中国

    原标题:为什么 Python 代码要写得美观而明确 | Linux 中国 欢迎阅读"Python 光明节(Pythonukkah)"系列文章,这个系列文章将会讨论<Pytho ...

  5. 为什么 Python 代码要写得美观而明确

    为什么 Python 代码要写得美观而明确 美观胜于丑陋 明确胜于隐晦 欢迎阅读 Python 代码之美观 首先来看<Python 之禅>里的前两个原则:美观与明确. 早在 1999 年, ...

  6. 用Python代码自己写Python代码,竟如此简单

    用Python代码自己写Python代码,竟如此简单 Python作为一门功能强大且使用灵活的编程语言,可以应用于各种领域,具有"无所不能"的特质. Python甚至可以代替人,自 ...

  7. 【卷积神经网络CNN 实战案例 GoogleNet 实现手写数字识别 源码详解 深度学习 Pytorch笔记 B站刘二大人 (9.5/10)】

    卷积神经网络CNN 实战案例 GoogleNet 实现手写数字识别 源码详解 深度学习 Pytorch笔记 B站刘二大人 (9.5/10) 在上一章已经完成了卷积神经网络的结构分析,并通过各个模块理解 ...

  8. 【Tensorflow学习三】神经网络搭建八股“六步法”编写手写数字识别训练模型

    神经网络搭建八股"六步法"编写手写数字识别训练模型 Sequential用法 model.compile(optimizer=优化器,loss=损失函数,metrics=[&quo ...

  9. 关于代码手写UI,xib和StoryBoard

    代码手写UI 这种方法经常被学院派的极客或者依赖多人合作的大型项目大规模使用.Geek们喜欢用代码构建UI,是因为代码是键盘敲出来的,这样可以做到不开IB,手不离开键盘就完成工作,可以专注于编码环境, ...

最新文章

  1. NPM 3 Beta为Windows用户带来利好消息
  2. 江苏自学考试计算机网络专业,速看,江苏自考本科计算机网络专业介绍
  3. argparse模块_Argparse:一个具体案例教会你python命令行参数解析
  4. 云服务器一直显示启动中,云服务器一直启动代码
  5. TypeScript_学习笔记
  6. 解决 Python 连不上pip库的问题(使用国内镜像地址)
  7. EOS技术研究:合约与数据库交互
  8. 波音可以自己做安全认证,错误在于故意掩盖问题
  9. npm 编译慢_如何有效提升快应用(Webpack)编译速度
  10. 红黑所-1996-2011年中国黑客大事记
  11. 为什么很多人交易十几年都做不到稳定盈利呢?
  12. 无root权限怎么完全卸载系统自带软件(捆绑软件)
  13. 微信小程序记录与项目实践
  14. 安卓10侧边返回_安卓10.0内测版现新操作手势:取消返回键、全靠Home胶囊完成...
  15. mysql jail_2.1.5 jail在生产环境下的注意事项
  16. Python爬虫:逆向分析酷我音乐请求参数(支持SQ超品音质)
  17. 8051单片机指令系统有哪几种寻址方式?
  18. 众昂矿业:萤石资源列入战略性矿产目录
  19. 电话一点通:基础电话用语(2)
  20. e-table 合并行 合并列

热门文章

  1. #编译原理# 文法和内容(二)
  2. 为什么使用mybatis
  3. 解决toastr的escapeHtml属性失效问题
  4. 【Django】Django路由urls详解
  5. seurat的 addmodule循环画图 等同于基因的叠加图 识别细胞类型 空转与单细胞得到的marker基因联合
  6. 肯德基、必胜客母公司披露数据泄露事件,300家快餐厅被迫关闭
  7. python爬虫_requests获取小黄人表情保存到文件夹
  8. 屏蔽机房设计方案知识
  9. 基于Spring包扫描工具和MybatisPlus逆向工程组件的数据表自动同步机制
  10. Tcp的三次握手和四次挥手过程