免费网站建设软件有哪些,wix做的网站能扒下来,枣庄网站开发公司,免费注册论坛✨ ✨我们抬头便看到星光#xff0c;星星却穿越了万年. ✨ ✨
#x1f3af;作者主页#xff1a;追光者♂ #x1f338;个人简介#xff1a;在读计算机专业硕士研究生、CSDN-人工智能领域新星创作者#x1f3c6;、2022年度博客之星人工智能领域TOP4#x1f31f;、阿里云…✨ ✨我们抬头便看到星光星星却穿越了万年. ✨ ✨
作者主页追光者♂ 个人简介在读计算机专业硕士研究生、CSDN-人工智能领域新星创作者、2022年度博客之星人工智能领域TOP4、阿里云开发者社区专家博主 2023励志在10月底成为CSDN Blog Experts 【无限进步一起追光】
欢迎点赞 收藏⭐ 留言 本篇是关于深度学习框架——PyTorch 的基础练习面向 初学者/复习者/遗忘者。一定要多次回顾请相信这是神经网络/深度学习 触类旁通的基础
万丈高楼平地起地基不牢可不行~ 模型简单但极其重要请重视哦
参考视频学习内容在文末统一指出。
阅读目录一、基础 模型算法 回顾1.1 线性模型1.1.1 基础练习 y^ωx\widehat y\omega xyωx1.1.2 练习 y^ωxb\widehat y\omega xbyωxb1.1.3 其它在深度学习训练中横轴往往是Epoch1.2 Gradient Descent梯度下降1.2.1 梯度下降法 的由来问题背景1.2.2 何为梯度1.2.3 梯度下降1.2.4 梯度下降的公式 如何得来的理解1.2.5 随机梯度下降Stochastic gradient descentSGDMini-batch1.2.6 梯度下降练习方法一推荐 完全 自己 多手写几遍1.2.7 方法二与法一类似不过这里 纵轴 是 权值的收敛变化收敛变化收敛变化1.2.8 随机梯度下降练习一可视化1.2.9 随机梯度下降练习二可视化1.3 反向传播Back Propagation1.3.1 从 神经网络视角下 来看1.3.2 “计算图” 中的 神经网络由 单纯添加层数的“无效复杂” 到 添加 非线性激活函数(如sigmoid/ReLU/等)后的“有效复杂”1.3.3 反向传播的过程前馈计算反向传播1.3.4 附PyTorch中的 前馈与反馈Tensor1.3.5 PyTorch反向传播练习1y^ωx\widehat y\omega xyωx1.3.6 PyTorch反向传播练习2y^ω1x2ω2xb\widehat y\omega_1 x^2 \omega_2x byω1x2ω2xb1.3.7 补充——针对1.3.5可视化看权重收敛情况。y^ωx\widehat y\omega xyωx1.3.8 补充——针对1.3.6可视化看参数w1、w2、b收敛情况 y^ω1x2ω2xb\widehat y\omega_1 x^2 \omega_2x byω1x2ω2xb1.4 线性回归——使用PyTorch实现 y^ωxb\widehat y \omega x byωxb1.4.1 Pytorch深度学习的一般流程1.4.2 线性单元----Linear Unit1.4.3 练习训练周期前馈、反馈、权重更新1.4.4 练习2——测试一下其它优化器效果如何1.5 逻辑回归 (LogisticRegression)——基于PyTorch实现1.5.1 问题背景——二分类问题1.5.2 常做练习用的数据集介绍MNIST 和 CIFAR-101.5.3 二分类 与 sigmoid1.5.4 模型的变化由线性回归模型----二分类模型增添sigmoid函数损失函数的变化由线性回归损失函数(MSE 均方误差损失函数) ----二分类损失函数BCELoss (mini-batch 小批量交叉熵损失函数cross entropy)1.5.5 code练习1.6 处理多维特征的输入1.6.1 问题背景含 多维特征 的数据集1.6.2 多维度特征的处理Mini-batch多层神经网络1.6.3 练习1.6.4 可视化使用 Adam优化器/SGD优化器 试试看1.6.5 附可尝试 不同的激活函数1.7 加载数据集 Dataset and DataLoader1.7.1 Dataset类、DataLoader类Epoch、Mini-batch、Iteration1.7.2 DataLoader类 核心参数解释1.7.3 如何在代码层面实现 Dataset和DataLoader1.7.4 其它说明torchvision内置数据集1.7.5 练习1【建议取num_workers0若2速度反而慢了】1.7.6 练习2划分训练集、测试集封装训练过程、测试过程1.7.3 练习3使用GPU训练1.8 多分类问题——交叉熵损失函数CrossEntropyLossSoftmax分类器One-hot针对MNIST数据集1.8.1 针对多分类问题输出的概率 应满足“分布”的要求1.8.2 Softmax 计算公式1.8.3 通过softmax得到概率分布后损失函数Loss如何做1.8.4 交叉熵损失CrossEntropyLoss Softmax 原Loss(即 −YlogY^-Y log\widehat Y−YlogY)1.8.5 有了上面的分析这里如何处理MNIST数据集呢1.8.6 模型1.8.7 练习1未使用GPU默认CPU1.8.8 练习2使用GPU1.9 卷积神经网络CNN基础篇1.9.1 基础概念卷积、卷积运算过程、单通道卷积多通道卷积、卷积层 权重www的四个维度1.9.2 CNN计算过程示例1.9.3 改进——padding一般 外围补01.9.4 改进——步长 stride1.9.5 改进——下采样(又叫“池化”)一般采用 Max Pooling Layer1.9.6 针对MNIST数据集所采用的模型1.9.7 如何在GPU上来跑怎么用显卡来计算1.9.8 练习11.9.9 练习2使用GPU来跑1.10 卷积神经网络CNN高级篇1.10.1 背景引出更复杂的网络结构非串行的1.10.2 非串行网络结构 之 GoogLeNet1.10.3 GoogLeNetInception ModuleAverage Pooling1×11\times11×1卷积1.10.4 GoogLeNet 练习1.10.5 GoogLeNet 练习GPU版----------------------------------分割线----------------------------------1.10.6 ResNet残差网络1.10.7 Residual Net“和xxx做加法”1.10.8 Residual Net 练习——accuracy99%1.10.9 Residual Net 练习——GPU版1.10.10 附其它Residual块 设计——可参阅Paper1.11 循环神经网络RNN基础篇1.11.1 问题背景RNN专门用来处理序列数据1.11.2 RNN的运算1.11.3 构造RNN1.12 循环神经网络RNN高级篇二、其他——PyTorch深度学习库的特点2.1 Torch简介2.2 PyTorch安装2.3 特点——GPU加速2.4 特点——自动求导2.5 特点——封装的API三、其他——神经网络基础3.1 基本原理3.2 正向传播与反向传播3.2.1 正向传播3.2.2 反向传播四、传统机器学习策略五、学习系统的发展5.1 基于规则的系统5.2 经典机器学习方法5.3 表示学习方法5.3.1 维度诅咒5.3.2 解决方法六、人工智能补充知识6.1 需要会的必备知识6.2 人工智能——问题分类6.2.1 推理6.2.2 预测6.3 人工智能——算法分类6.3.1 传统算法与智能算法七、Reference一、基础 模型算法 回顾
1.1 线性模型 对于一个算法模型。在深度学习中简要的处理方式是 准备数据集Datasets— Model(选择模型) — Training (模型训练) — 推理(进行推理预测)。 至于优化等可以理解为后续的补充。 监督学习数据集需要 交付给算法模型 进行训练利用所训练的模型在获得 新的数据时 可以得到相应的输出。 线性模型的基本模型如下其中的ω\omegaω和bbb是模型中的参数训练模型的过程即为确定模型中参数的过程
y^ωxb\widehat y\omega xbyωxb 在本模型中设置成 y^ωx\widehat y\omega xyωx对于不同的 ω\omegaω 有不同的线性模型及图像与之对应。 模型的训练过程 在模型训练中 会先随机取得一个值继而 计算其和标准量之间的 偏移量从而判断 当前模型 是否符合预期。 记实际值为y(x)y(x)y(x)模型对应的 预测值为y^(x)\widehat y(x)y(x)则其中的 偏移量为∣y^(x)−y(x)∣\left|\widehat y(x)-y(x)\right|∣y(x)−y(x)∣以此来代表 模型估计值 对原值的误差。 通常该公式定义为Training Loss (Error)
loss(y^−y)2(ωx−y)2loss (\widehat y - y)^2 (\omega x - y)^2loss(y−y)2(ωx−y)2 本例中原题目中的几种ω\omegaω所对应的Loss如下 几个图所示: 其中的每行 为 www不同时 的 单个样本的损失最后一行为 平均损失。 对于单个样本loss 可用于 指代样本误差。对于所有样本可同理用Mean Square Error (MSE)来指代 整体样本的平均平方误差均方差cost MSE均方误差(Mean Square Error)。即 每个样本对应的损失值 (平方) 求和再除以样本总个数。
cost1N∑n1N(y^n−yn)2cost \frac{1}{N} \displaystyle\sum_{n1}^{N}(\widehat y_n-y_n)^2costN1n1∑N(yn−yn)2
ω3\omega3ω3: ω4\omega4ω4: ω2\omega2ω2: 可以看到 平均损失值为0 由cost的计算公式可知当平均损失为0时模型最佳但由于 仅当数据无噪声 且 模型完美贴合数据 的情况下才会出现这种情况因此 模型训练的目的 应当是 误差(损失)尽可能小而非找到 误差为0的情况。
不同ω\omegaω 得到的 MSE 1.1.1 基础练习 y^ωx\widehat y\omega xyωx
根据上面的分析code如下注释我已经写的比较清楚啦。
# 昵 称:XieXu
# 时 间: 2023/2/12/0012 21:10# 导入必要的工具包
import numpy as np
import matplotlib.pyplot as plt# 自定义 简单数据集。x与y 一一对应 训练集
x_data [1.0, 2.0, 3.0]
y_data [2.0, 4.0, 6.0]# 模型(前馈)
def forward(x):return x * w# 损失函数
def loss(x, y):y_pred forward(x) # 即 y_hat。相当于 预测的值return (y_pred - y) * (y_pred - y) # 平方均方误差。简单形式的均方误差。这个计算的 为单个样本的误差# 【穷举法】
# 如下两个列表 保存 权重 及其对应的损失值
w_list []
mse_list []# [0.0,4.1),间隔为0.1。即 0.0, 0.1, 0.2, 0.3 ... 4.0 起始值(含)停止值(不含)步长
for w in np.arange(0.0, 4.1, 0.1): # 外层循环 控制权重 2023.2.13 08:20print(w, w)l_sum 0# zip 将x_data 和 y_data 打包为一个tuple元组方便同时遍历。for x_val, y_val in zip(x_data, y_data): # 内层循环控制进行 权重 调用forward函数 对应的预测以及 调用上面定义的loss函数 进行损失值计算y_pred_val forward(x_val) # 计算 每个样本的 预测值loss_val loss(x_val, y_val) # 计算 每个样本的 损失值l_sum loss_val # 将 所有样本的 损失求和这里没做均值print(\t, x_val, y_val, y_pred_val, loss_val) # 打印出 每一个样本xx对应的y预测值y_hat损失值print(MSE, l_sum / 3) # 这里 除以样本总数进行均值即 均方误差w_list.append(w) # 将每次 用完的 权重添加到列表中用以 下面画图 的横坐标~mse_list.append(l_sum / 3) # 除以样本总数。 每个权重 对应的 所有样本的平均误差(均方误差) MSE也叫均值吧。下面绘图的纵坐标# 绘制图形
plt.plot(w_list, mse_list) # 横坐标、纵坐标 的取值
plt.ylabel(Loss) # 纵坐标y的标签
plt.xlabel(w) # 横坐标w的标签
plt.show()
可以得到结果红色为我做的标注~ 从下面 控制台打印的日志中我们 可以 很容易看出来上图 的由来
开始时随着W增加平均损失MSE逐渐减小 到W为2.0时MSE达到最小 即0 后面 W继续增加MSE又变大了
1.1.2 练习 y^ωxb\widehat y\omega xbyωxb 同样地基于上面的例子练习 实现线性模型y^ωxb\widehat y\omega xbyωxb并输出loss的3D图像。 不同之处在于定义的模型与上面相比加了个 偏置项B。
# 昵 称:XieXu
# 时 间: 2023/2/13/0012 12:26# 练习题
# 作业题目实现线性模型ywxb并画出loss的3D图像。
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D# 以下两行代码 解决坐标轴不能显示中文问题 否则会报类似错误FigureCanvasAgg.draw(self) 图上的 中文无法正常显示
from pylab import *mpl.rcParams[font.sans-serif] [SimHei]# 输入数据,设函数为y3x2
x_data [1.0, 2.0, 3.0]
y_data [5.0, 8.0, 11.0]# 定义模型
def forward(x):return x * w b# 定义损失函数
def loss(x, y):y_pred forward(x)return (y_pred - y) * (y_pred - y)mse_list [] # 保存mse均方误差
W np.arange(0.0, 4.1, 0.1)
B np.arange(0.0, 4.1, 0.1)
[w, b] np.meshgrid(W, B) # 通过 两个坐标轴上的点 在平面上画网格l_sum 0
for x_val, y_val in zip(x_data, y_data):y_pred_val forward(x_val)print(y_pred_val)loss_val loss(x_val, y_val)l_sum loss_valfig plt.figure()
ax Axes3D(fig)
ax.plot_surface(w, b, l_sum / 3)
# 以下三行 设置坐标的文字说明
ax.set_xlabel(权重W)
ax.set_ylabel(偏置项 B)
ax.set_zlabel(损失值)
plt.show() # 画图
可以得到如下图形这就是 ( y^ωxb\widehat y\omega xbyωxb ) 损失值的3D图形。 其中控制台输出的日志中含有警告 定位到源代码中
ax Axes3D(fig)翻译一下上面的警告大意即 由于版本不同可暂时忽略。 1.1.3 其它在深度学习训练中横轴往往是Epoch 在深度学习 训练 的可视化图形中一般 横轴是Epoch即训练轮数往往在训练集上表现是随着训练轮数增多损失越来越低而在测试集(老师读 开发集)上的效果是损失 在刚开始会下降而后 到某个点又会逐渐上升。而我们的目标就是想找到损失最低的那个点从而进一步 对超参数进行处理等其它操作。 深度学习中可能需要考虑更多问题比如 可视化问题 / 训练的时间问题(可能需要几天甚至连续几周都是有可能的) / 断点问题(比如训练7天会结束但是第6天程序崩溃了这期间产生的数据 结果 怎么办) 老师提到了 Pytorch可视化工具——Visdom。目前我没有用到…稍微大型的项目可能会用到吧。可以在百度搜索Visdom并查看它的相关信息以及其GitHub官网。
1.2 Gradient Descent梯度下降
1.2.1 梯度下降法 的由来问题背景 在上面 线性模型的方法中所使用的思想是基于穷举即提前已经设定好 参数的准确值 在某个区间内并以某个步长进行穷举如np.arange(0.0,4.1,0.1)。 但是这种思想 在多维的情况下即多个参数的时候会引起维度诅咒的现象在一个N维曲面中找一个最低点容易 使得原问题变得不可解。那么 既然有这样的问题就需要对算法 进行改进。
那么使用 分治法 如何 即大化小小化无先对整体 进行分割采样在相对最低点进行进一步采样直到其步长与误差符合条件。 但是分治法有两个缺点 容易只找到局部最优解而不易找到一个全局最优解。 如果需要分得更加细致则计算量仍然巨大。 同时由于以上问题的存在引起了参数优化的问题即求解使loss最小时的参数的值。 简言之即求得 ω\omegaω使得loss值最小。
ω∗argminωcost(ω)\omega ^* \mathop{\arg\min}_\omega cost(\omega)ω∗argminωcost(ω) 如何优化求得符合条件的ω\omegaω呢
1.2.2 何为梯度 梯度即 导数变化最大的值其方向为导数变化最大的方向。 这里可以使用高等数学中关于 一个点处 导数的定义
∂f∂xlim△x→∞f(x△x)−f(x)△x\frac{\partial f}{\partial x}\lim_{\triangle x\rightarrow\infty}\frac{f(x\triangle x)-f(x)}{\triangle x}∂x∂f△x→∞lim△xf(x△x)−f(x) 这里仅简单理解并非严谨数学推导对于△x\triangle x△x若增函数梯度为上升方向若减函数梯度为下降方向。由数学知识 知道需要取梯度下降的方向 即梯度的反方向作为变化方向才能尽可能地求得极小值(最小值)。 下面的图将便于理解
1.2.3 梯度下降 在深度学习中所说的凸函数是与高等数学定义中的 凸函数完全反过来的知道这一点就好。如下图所示在深度学习中将其看做一个凸函数。
当前WWW的取值点 与 全局最小值点: 那么取值点 需要 向下更新所取的梯度即为∂cost∂ω\frac{\partial cost}{\partial \omega}∂ω∂cost更新的公式如下其中α\alphaα为 学习率即 所下降的步长不宜取太大。
ωω−α∂cost∂ω\omega \omega - \alpha \frac{\partial cost}{\partial \omega}ωω−α∂ω∂cost WWW按梯度下降方向进行更新 局限性
[1] 梯度下降算法 容易进入 局部最优解非凸函数但是实际问题中的局部最优点较少或 已经基本可以当成全局最优点。 [2] 梯度下降算法容易陷入鞍点(鞍点处梯度值为0)总之不是取得 损失最小值的点简单理解为 局部最优在优化问题中鞍点 是一种特殊的局部最优解是一个难以优化的点因为优化算法 可能 很难从鞍点 附近找到 全局最优解。陷入到鞍点导致无法继续 进行迭代 1.2.4 梯度下降的公式 如何得来的理解 通过线性模型我们知道 均方误差的公式即其中 (y^ωx\widehat y\omega xyωx) cost1N∑n1N(y^n−yn)2cost \frac{1}{N} \displaystyle\sum_{n1}^{N}(\widehat y_n-y_n)^2costN1n1∑N(yn−yn)2 进一步地可以对 ω\omegaω求偏导一步一步来 ∂cost(w)∂ω∂∂w1N∑n1N(y^n−yn)2\frac{\partial cost(w)}{\partial \omega} \frac{\partial}{\partial w} \frac{1}{N} \displaystyle\sum_{n1}^{N}(\widehat y_n-y_n)^2∂ω∂cost(w)∂w∂N1n1∑N(yn−yn)2
∂cost(w)∂ω∂∂w1N∑n1N(ωxn−yn)2\frac{\partial cost(w)}{\partial \omega} \frac{\partial}{\partial w} \frac{1}{N} \displaystyle\sum_{n1}^{N}(\omega x_n-y_n)^2∂ω∂cost(w)∂w∂N1n1∑N(ωxn−yn)2 1N\frac{1}{N}N1是常数对求ω\omegaω的导数没有影响可以提到前面来
∂cost(w)∂ω1N∑n1N∂∂w(ωxn−yn)2\frac{\partial cost(w)}{\partial \omega} \frac{1}{N} \displaystyle\sum_{n1}^{N}\frac{\partial}{\partial w}(\omega x_n-y_n)^2∂ω∂cost(w)N1n1∑N∂w∂(ωxn−yn)2 根据数学知识由于是对ω\omegaω求导那么 可以 把(ωxn−yn)(\omega x_n-y_n)(ωxn−yn)看做一个整体【复合函数求导】对含有ω\omegaω的 (外部)整体 求导乘以内部 对ω\omegaω求导故而得
∂cost(w)∂ω1N∑n1N2(xnω−yn)∂(xnω−yn)∂w\frac{\partial cost(w)}{\partial \omega} \frac{1}{N} \displaystyle\sum_{n1}^{N}2(x_n \omega-y_n)\frac{\partial(x_n \omega - y_n)}{\partial w}∂ω∂cost(w)N1n1∑N2(xnω−yn)∂w∂(xnω−yn) 而(ωxn−yn)(\omega x_n-y_n)(ωxn−yn) 对ω\omegaω求导的结果 显然为xnx_nxn调整一下顺序就得到对ω\omegaω的最终求导结果
∂cost(w)∂ω1N∑n1N2xn(xnω−yn)\frac{\partial cost(w)}{\partial \omega} \frac{1}{N} \displaystyle\sum_{n1}^{N}2 x_n(x_n \omega - y_n)∂ω∂cost(w)N1n1∑N2xn(xnω−yn) 因此梯度下降的更新公式为
ωω−α1N∑n1N2xn(xnω−yn)\omega \omega - \alpha \frac{1}{N} \displaystyle\sum_{n1}^{N}2 x_n(x_n \omega - y_n)ωω−αN1n1∑N2xn(xnω−yn) 下图即上述公式的推导过程 梯度的求解公式应用到code中的示例 1.2.5 随机梯度下降Stochastic gradient descentSGDMini-batch 平时用的比较多的是 随机梯度下降SGD。 SGD采用单个训练样本的损失来近似平均损失故 SGD 用单个训练数据即可对模型参数进行一次更新大大加快了训练速度。 随机梯度下降 每次 只需要计算 一个样本 关于WWW的导数 随机梯度下降SGD 与 标准梯度下降的区别 标准梯度下降 在权值更新前 汇总所有样例得到的标准梯度随机梯度下降 则是通过考察每次训练实例来更新。 标准梯度下降的是使用准确的梯度理直气壮地走随机梯度下降使用的是近似的梯度小心翼翼地走。 标准梯度下降的步长 比 随机梯度下降 的大。 当有多个局部极小值时随机梯度 反而更可能 避免 进入局部极小值中。 同时为了降低随机梯度的方差使迭代算法更加稳定在真实操作中会同时处理若干训练数据该方法叫做小批量随机梯度下降法(Mini_Batch Gradient Densent)。这才是真正地运用了 随机梯度下降(SGD)目前在实际应用中我们所说的梯度下降batch都是指的 Mini-batch SGD小批量 随机梯度下降。 这在神经网络中尤其明显使用及其广泛既 保证 性能又保证时间复杂度不是特别高
小结 普通梯度下降算法利用数据整体不容易避免鞍点算法性能欠佳但算法效率高。随机梯度下降需要利用每个的单个数据虽然算法性能良好但计算过程 环环相扣 无法将样本抽离开 并行运算因此算法效率低时间复杂度高。 综上所述可采取一种折中的方法即批量梯度下降方法。 将若干个样本分为一组记录一组的梯度 用以代替随机梯度下降中的单个样本。 该方法最为常用也是默认接口。一般mini-batch可以在2的幂次中挑选最优取值。例如16、32、64、128、256等。
1.2.6 梯度下降练习方法一推荐 完全 自己 多手写几遍
给定一个数据集x_data、y_data。寻找ywx模型的w最优解。 code练习如下注释中我已经介绍的比较详细啦我想这可以帮助绝大多数朋友理解。
# 昵 称:XieXu
# 时 间: 2023/2/13/0013 13:26# 梯度下降算法# 注在 深度学习算法 中并没有过多的局部最优点。
# 即 一般 通过梯度下降 就可以求得 最优点import numpy as np
import matplotlib.pyplot as plt# 训练数据x与y一一对应
x_data [1.0, 2.0, 3.0]
y_data [2.0, 4.0, 6.0]epoch_list [] # # 2023.2.13 13:58 保存训练轮数
cost_list [] # 2023.2.13 13:58 每一轮对应的损失值w 1.0 # 初始化我们指定一个W取值# 前馈计算定义模型
def forward(x):return x * w# 求MSE 定义cost函数计算均方误差
def cost(xs, ys):cost 0for x, y in zip(xs, ys):y_pred forward(x)cost (y_pred - y) ** 2return cost / len(xs) # 计算 均方误差损失# 求梯度求W的偏导数
def gradient(xs, ys):grad 0for x, y in zip(xs, ys):temp forward(x)grad 2 * x * (temp - y) # 由导数公式得return grad / len(xs)print(Predict(before training), 4, forward(4)) # 这是根据 我们 给定的那个W计算ywx得到的值forward(4) 即4# 开始训练
for epoch in range(1, 101, 1):cost_val cost(x_data, y_data) # 计算均方误差损失grad_val gradient(x_data, y_data) # 求梯度W的偏导数w - 0.01 * grad_val # 梯度下降更新梯度Wprint(Epoch: , epoch, w , w, loss , cost_val) # w就会用在下一轮的训练中epoch_list.append(epoch) # 保存轮数1~100做绘图的横坐标cost_list.append(cost(x_data, y_data)) # 每轮对应的 损失值print(Predict(after training), 4, forward(4)) # 根据100轮 之后的权重W计算x取4时预测的y值。实际应该是无限接近2即2# 绘图
plt.plot(epoch_list, cost_list) # 横坐标 和 纵坐标 分别是 轮数 和 对应该轮的 损失值
plt.title(train loss)
plt.xlabel(Epoch)
plt.ylabel(Cost)
plt.show()得到结果可以看到在20轮左右损失值就已经很接近0了。 控制台的结果为了便于展示这里我也特意截取到了20轮的训练结果。 1.2.7 方法二与法一类似不过这里 纵轴 是 权值的收敛变化收敛变化收敛变化 第二种方式与第一种类似只不过这里纵坐标取的是 权值另外直接把梯度下降 放到Epoch训练里面了。代码如下
# 方法二
import numpy as np
import matplotlib.pyplot as pltx_data [1.0, 2.0, 3.0]
y_data [2.0, 4.0, 6.0]scope_list [] # 轮数
w_list [] # 权值Ww 60 # 我们给W一个初始值# 学习率
k 0.01 # 定义学习率# 开始训练
for i in range(1,201,1):# 计算costloss的和loss_sum 0# 求梯度for x_val, y_val in zip(x_data, y_data):loss_sum 2 * x_val * (w * x_val - y_val)cost loss_sum / 3# 计算本轮ww w - k * costprint(Epoch:,i,W:,w)scope_list.append(i)w_list.append(w)# 按说纵坐标不应该取权值的既然取了那么可以从图形中看出来大概 50左右权值就收敛了
plt.plot(scope_list, w_list) # 横坐标 取值 依然是 轮数纵坐标取值是按照W来取的值
plt.xlabel(scope)
plt.ylabel(W)
plt.show()
结果如下所示 我们也可以看一下Console控制台输出的内容可以看到50多轮依然还在收敛100轮左右就已经收敛的比较好了~ 最终权值WWW应为2 1.2.8 随机梯度下降练习一可视化 这里即 普通的 随机梯度下降每轮训练中每次计算的 关于WWW的 偏导数是仅仅 只计算 一个样本的而非 所有样本的 关于WWW的偏导数 求和 再取均值。 即 注这里随机梯度主要是指每次拿一个训练数据来训练然后更新梯度参数。
# 昵 称:XieXu
# 时 间: 2023/2/13/0013 15:11
# 随机梯度下降# 方法一
# 注这里随机梯度主要是指每次拿一个训练数据来训练然后更新梯度参数。
# 本算法中 梯度总共更新100(epoch)x3 300次。梯度下降算法中 梯度总共更新100(epoch)次。
import matplotlib.pyplot as pltx_data [1.0, 2.0, 3.0]
y_data [2.0, 4.0, 6.0]w 1.0def forward(x):return x * w# calculate loss function损失函数--均方误差
def loss(x, y):y_pred forward(x)return (y_pred - y) ** 2 # 仅 计算一个样本 的损失函数# define the gradient function sgd定义随机梯度下降 函数
def gradient(x, y):return 2 * x * (x * w - y) # 注这里仅计算 “一个样本” 关于W 的 偏导数epoch_list [] # 训练轮数100轮 2023.2.13 19:24
loss_list []
print(predict (before training), 4, forward(4))
for epoch in range(1, 101, 1):for x, y in zip(x_data, y_data):grad gradient(x, y) # 这里每轮训练中三个 样本中 都分别计算了 梯度for...zip循环。 应该是没有用到“随机”w w - 0.01 * grad # update weight by every grad of sample of training set。根据梯度更新W。。。print(\tgrad:, x, y, grad) # 打印每轮训练中每个样本的x、y、根据其计算的梯度。 2023.2.13 19:36l loss(x, y) # 根据梯度得到的 更新后的W,进一步 计算损失函数(均方误差) 2023.2.13 19:36print(progress:, epoch, w, w, loss, l) # 打印该轮中通过每一个样本 得到的 W 以及 损失函数值2023.2.13 19:37epoch_list.append(epoch)loss_list.append(l)print(predict (after training), 4, forward(4))
plt.plot(epoch_list, loss_list)
plt.title(train loss)
plt.ylabel(loss)
plt.xlabel(epoch)
plt.show()以此类推 得到的损失函数值关于 训练轮数的图像如下
1.2.9 随机梯度下降练习二可视化 上面的随机梯度下降算法中貌似仅仅 是只计算了 每一个样本的梯度好像没有体现“随机”。 这里再换个类似的算法基本一样但用到了random随机。 该方法与目录1.2.6类似。
# 方法二
import random
import numpy as np
import matplotlib.pyplot as pltx_data [1.0, 2.0, 3.0]
y_data [2.0, 4.0, 6.0]scope_list [] # 训练轮数
w_list [] # 权值w 60# 学习率
k 0.01for i in range(1, 201, 1):# 计算cost(即随机一个loss当cost用)rand random.randint(0, 2) # 取值为[0,2]即 随机生成0~2内的 某个 整数包含0和2。三个样本随机取一个cost 2 * x_data[rand] * (w * x_data[rand] - y_data[rand]) # 从而计算得到 关于W的偏导数# 计算本轮ww w - k * cost # 更新Wprint(Epoch, i, W, w) # 本轮更新后的W 2023.2.13 20:30scope_list.append(i)w_list.append(w)plt.plot(scope_list, w_list) # 横坐标为轮数纵坐标为权值。
plt.xlabel(scope)
plt.ylabel(W)
plt.show()如下图所示可以看出75轮左右权值就已经逐渐收敛了~
1.3 反向传播Back Propagation
1.3.1 从 神经网络视角下 来看 Back Propagation算法 是神经网络中很重要的一个算法。可以在图上 进行梯度的传播。这里用到的核心知识我认为依然是计算图。 在线性模型y^ωx\widehat y \omega xyωx中若以神经网络的视角代入来看则xxx为输入层即input层ω\omegaω为权重y^\widehat yy为输出层。 在神经网络中通常将ω\omegaω以及∗*∗计算操作的部分 合并 看做一个神经元层。而神经网络的训练过程即为更新ω\omegaω的过程注意其更新的情况依赖于∂loss∂ω\frac{\partial loss}{\partial \omega}∂ω∂loss而并非∂y^∂ω\frac{\partial \widehat y}{\partial \omega}∂ω∂y.
神经网络 视角下的 线性模型 示意图 可是对于复杂模型而言 求解过程就复杂很多。在图示的神经网络中每个结点为一个神经元结点之间的连线为权重。这里各个符合 及其 表示的含义 如下表格所示关键词为 层、权重权值、输入层、输出层、隐含层。
符号表示的含义xix_ixi输入层的第i个节点hijh_{ij}hij第i层隐含层的 第j个节点oio_ioi输出层的第i个节点ωx1mn\omega_{x1}^{mn}ωx1mn输入层的第m个结点 与 隐含层的第n个结点之间的权重ωijmn\omega_{ij}^{mn}ωijmn隐含层第i层的 第m个节点 与 第j层的第n个节点 之间的权重ωkomn\omega_{ko}^{mn}ωkomn隐含层最后一层(第k层) 的第m个节点 与 输出层第n个节点 之间的权重如下图所示输入层 与隐含层第一层 之间就有5∗6305*6305∗630个权重隐含层的第一层与第二层之间又有6∗7426*7426∗742个权重以此类推上图中共有304249423019330424942301933042494230193个权重需要计算传统的 写解析式的方式 是难以完成 (无法完成)的。
含有四个隐含层 的神经网络模型
1.3.2 “计算图” 中的 神经网络由 单纯添加层数的“无效复杂” 到 添加 非线性激活函数(如sigmoid/ReLU/等)后的“有效复杂” 在下图右侧的计算图中绿色的模块为计算模块可以在计算过程中求导。MM为矩阵乘法(Matrix Multiplication)ADD表示加法运算。简单来说 即 权重WWW与输入层XXX 做矩阵乘法运算得到的结果 再与 偏置项bbb进行 向量加法运算。因此 bbb通常是向量的形式一列 / 一行。 上图左侧公式中可以化简得到如下公式
y^W2(W1Xb1)b2W2W1X(W2b1b2)WXb\widehat y W_2(W_1Xb_1)b_2W_2W_1X(W_2b_1b_2)WXbyW2(W1Xb1)b2W2W1X(W2b1b2)WXb 也就是说在这个结构下 单纯地增加层数并不能增加神经网络的复杂程度因为最后都可以化简为一个单一的神经网络。 下图即神经网络的“无效复杂” 如何改进呢让模型变得真正复杂 方法是在每层网络结构中增加一个 非线性的变换函数激活函数
增加激活函数后的神经网络 如 添加sigmoid激活函数(或其它非线性激活函数 如ReLU激活函数)注这里下边的 本小节code练习中并未用到sigmoid因为模型简单并不需要搭建神经网络. 在 目录1.5的逻辑回归(以二分类为例)中将会正式介绍sigmoid激活函数【添加激活函数后 才是真正的神经网络】 有同学说加了激活函数后神经网络才有了 能够逼近 任意函数形式的能力~
1.3.3 反向传播的过程前馈计算反向传播
前馈计算 在某一神经元处输入的xxx与ω\omegaω经过函数f(x,ω)f(x,\omega)f(x,ω)的计算,可以获得输出值zzz并继续向前以得到损失值loss。 在向前计算的过程中在f(x,ω)f(x,\omega)f(x,ω)的计算模块中 会计算导数∂z∂x\frac{\partial z}{\partial x}∂x∂z以及∂z∂ω\frac{\partial z}{\partial \omega}∂ω∂z,并将其保存下来在pytorch中这样的值保存在变量xxx以及ω\omegaω中。 前馈计算过程沿着箭头方向先去计算最终的Loss。这一过程中计算fff时就能顺便把∂z∂x\frac{\partial z}{\partial x}∂x∂z 和 ∂z∂ω\frac{\partial z}{\partial \omega}∂ω∂z给求出来为反向传播时的计算 做准备。注为何也要求出 ∂z∂x\frac{\partial z}{\partial x}∂x∂z这是因为 除输入层外xxx 也很有可能是 中间结果如 xxx是上一层的输出此时 就对ZZZ有需求了 反向传播 根据链式求导法则求得loss以后前面的神经元会将∂loss∂z\frac{\partial loss}{\partial z}∂z∂loss的值反向传播给原先的神经元在计算单元f(x,ω)f(x,\omega)f(x,ω)中将得到的∂loss∂z\frac{\partial loss}{\partial z}∂z∂loss与之前存储的导数∂z∂ω\frac{\partial z}{\partial \omega}∂ω∂z、∂z∂x\frac{\partial z}{\partial x}∂x∂z分别 相乘即可得到损失值对于 权重以及输入层的导数即∂loss∂x\frac{\partial loss}{\partial x}∂x∂loss,以及∂loss∂ω\frac{\partial loss}{\partial \omega}∂ω∂loss.基于该梯度才进行权重的调整。 反向传播过程 举个实际的例子如下图假设fx⋅ωfx·\omegafx⋅ω且x2,ω3x2,\omega3x2,ω3已经求得 损失L 对 输出Z的导数值为5那么计算过程即如下所示 这样我们就得到了 相应的导数(损失LLL关于权重WWW的导数)就可以去做 权重WWW的更新了 再举个例子 练习题1这里若x2,w1,y4x2,w1,y4x2,w1,y4那么前馈计算 与 反向传播的计算如下我想我写的比较清楚叭建议大家也手推一遍~ 练习题2当为 y^ωxb\widehat y\omega xbyωxb且w1,x1,b2,y2w1,x1,b2,y2w1,x1,b2,y2时 这里我建议大家 自己手推一遍哈 可能有同学会说反向传播 计算Loss 对每个 ω\omegaω的梯度然后更新ω\omegaω 来进行 “梯度下降”降低Loss“前馈计算loss反馈计算权值”
1.3.4 附PyTorch中的 前馈与反馈Tensor 通过pytorch进行 深度学习 基础模型的构建最主要的是构建计算图。
“张量” Tensor中可以存标量、向量、矩阵亦有高维度的Tensor。Tensor是一个类。 Tensor中的两个重要成员 是 data 和 grad。data用于保存 权重本身的值ω\omegaωgrad用于保存 损失函数对权重的导数∂loss∂ω\frac{\partial loss}{\partial \omega}∂ω∂lossgrad本身也是个张量。对张量进行的计算操作都是建立计算图的过程。 关于Tensor的小练习未来我会抽时间记录。 张量 Tensor
1.3.5 PyTorch反向传播练习1y^ωx\widehat y\omega xyωx
先算损失然后反向传播有了梯度就可以 用梯度下降 进行(权重) 更新 该小节的代码注释我依然写的是比较详细的另外建议结合以下几张图会理解的更深刻一些~ 阅读code时建议看下注释你会理解的 ♪(^ ∇ ^*)~
# 昵 称:XieXu
# 时 间: 2023/2/14/0014 17:47
import torchx_data [1.0, 2.0, 3.0]
y_data [2.0, 4.0, 6.0]w torch.tensor([1.0]) # 设置tensor中的data(数据。。)即 w的初值为1.0
w.requires_grad True # 指定w是需要计算梯度的因为 默认不指定的话是不会计算w的梯度的2023.2.14 19:13def forward(x):# 这里 x与w要进行 【数乘】 运算而w是一个Tensor# 因此x要进行 自动类型转换把它转换成一个Tensorreturn x * w # 注意 w是一个Tensor# x和w进行 乘法运算得到相应地输出构建了【计算图】# w是需要计算梯度的因此 x*w 运算得到的结果 也需要计算梯度# 每调用一次loss函数就动态地【构建了】一次 【计算图】
def loss(x, y):y_pred forward(x) # 得到 y_hatreturn (y_pred - y) ** 2 # 计算损失print(predict (before training), 4, forward(4).item())for epoch in range(1, 101, 1):for x, y in zip(x_data, y_data): # (每次都 随机梯度下降SGD~) 把x_data 和 y_data zip成 一个数据~# 前馈的过程你只需要计算loss ## 即 创建【新的】计算图 2023.2.14 20:49l loss(x, y) # loss是一个“张量”tensor主要是在建立计算图 forward, compute the loss# 调用张量l的 成员函数backward(), 它就会自动地把 计算链路上刚刚 画的【计算图】上 所有的需要梯度的地方把梯度 都求出来# 求完之后把梯度 都存到变量里边如 这里是把梯度 都存到w里面。# 存到w里面之后实际上 计算图 就被释放了。“只要一做backward这个计算图就被释放了这个计算图就没有了”# 那么下一次再进行loss计算的时候会创建一个 新的计算图# ### 进行反馈计算此时才开始求梯度此后 之前的计算图进行释放 2023.2.14 20:48l.backward() # backward,compute grad for Tensor whose requires_grad set to Trueprint(\tgrad:, x, y, w.grad.item()) # .item() 是 把梯度的数值 直接拿出来变成Python中的标量【###即 grad.item()取grad中的值变成标量】# 注grad也是一个tensor。# # # #必须要 取到grad的data即 (w.grad).data。 如果是 w.data - 0.01 * w.grad那这就是 在建立计算图而不是更新权重。# 取张量的data来计算好处 是 它是不会建立计算图的# 构建计算图的时候 使用张量就可但是权重更新的时候要使用data### 即 单纯的数值计算要利用data而不能用张量否则会在内部创建新的计算图w.data w.data - 0.01 * w.grad.data # 权重更新时注意“grad也是一个tensor” 。。。这里做的就是对 权重w 纯数值的更新(修改)# 有同学说 w.data相当于 requires_gradFalse的w但是与requires_gradTrue的w共享数据这是为了节省空间# # # # 以下code意为把权重里面 梯度的数据 全都清零# 因为上面把w即权重更新完之后L关于w的 梯度(导数)还在。如果不清零那么下一次运算会把之前L关于w的导数 加到一起..# 即如果想要w的梯度(导数)清零必须显示地 写明 清零的语句~w.grad.data.zero_() # after update, remember set the grad to zeroprint(progress:, epoch, l.item()) # 取出loss使用l.item不要直接使用ll是tensor会构建计算图print(predict (after training), 4, forward(4).item())附便于理解~2023.2.17 23:31 1.3.6 PyTorch反向传播练习2y^ω1x2ω2xb\widehat y\omega_1 x^2 \omega_2x byω1x2ω2xb 题目如下需要求解三个梯度(导数) 推导过程我没有平板公式就不好直接写了找了一个例子如下推导过程 应该是没问题的。 # 昵 称:XieXu
# 时 间: 2023/2/14/0014 21:56# yw1x²w2xbimport numpy as np
import matplotlib.pyplot as plt
import torchx_data [1.0, 2.0, 3.0]
y_data [2.0, 4.0, 6.0]# 声明w1、w2、b为Tensor并赋予初值 且指定 都需要计算梯度
w1 torch.Tensor([1.0]) # 初始权值
w1.requires_grad True # 计算梯度默认是不计算的,因此这里需要手动指定为True即 要计算梯度。 如下亦同理
w2 torch.Tensor([1.0])
w2.requires_grad True
b torch.Tensor([1.0])
b.requires_grad Truedef forward(x):return w1 * (x ** 2) w2 * x b # 注w1 w2 b 三者 都是Tensordef loss(x, y): # 构建计算图y_pred forward(x)return (y_pred - y) ** 2# print(Predict (befortraining), 4, forward(4)) # 不加.item()的话那么结果 就成Tensor啦 取值需要通过 .item() 2023.2.14 22:18
print(Predict (befortraining), 4, forward(4).item()) # 未预测之前 通过我们上面给出的默认的w1,w2和b 计算的结果164121# 注学习率为0.01时5000轮 结果为8.03 或 10000轮 结果为8.002 效果 都会比较好一些 2023.2.14 22:13
# 训练100次后可以看到当x4时y8.544与正确值8相差比较大。 原因是α0.01取值过小可以取 α0.02那么100轮后结果 8.06for epoch in range(1, 101, 1):# l loss(1, 2) # 为了在for循环之前定义l,以便之后的输出无实际意义for x, y in zip(x_data, y_data):l loss(x, y) # 前馈过程计算损失l.backward() # 反馈过程反向传播求解梯度。w1 w2 b的梯度都会计算出来print(\tgrad:, x, y, w1.grad.item(), w2.grad.item(), b.grad.item()) # 注意是通过.item() 即 将梯度 转换为标量 打印出来 值# 更新w1 w2 b注意 取值要从data取w1.data w1.data - 0.01 * w1.grad.data # 注意 这里的grad是一个tensor所以要取它的data下亦 同理w2.data w2.data - 0.01 * w2.grad.datab.data b.data - 0.01 * b.grad.dataw1.grad.data.zero_() # 释放之前计算的梯度。下亦同理w2.grad.data.zero_()b.grad.data.zero_()print(Epoch:, epoch, l.item()) # 取出本轮的损失值注意 也是通过.item()来取 2023.2.15 07:55# print(Epoch:, epoch, l) # tensor([0.0063], grad_fnPowBackward0),这样是张量通过item才能取到其 值print(Predict(after training), 4, forward(4).item())注意这是当Epoch为100轮学习率为0.01时的结果当x为4时最后y取值为8.5与正确值8 误差还是比较大的 那么把学习率都设置为0.02试试即 w1.data w1.data - 0.02 * w1.grad.data w2.data w2.data - 0.02 * w2.grad.datab.data b.data - 0.02 * b.grad.data结果是8.06已经比较接近8了 学习率仍然设置为0.01轮数设置为5000轮8.03结果更接近8了更精准了 学习率还是不变为0.01训练10000轮结果是8.002更接近8了。 这可能是由于模型 比之前设置的 要复杂一些因此在超参数的调整上如学习率不能太小这里0.01就显得有些小了可以试试0.02等训练轮数不能太少等等这些因素都有关系。当然使用的数据 显然是一次函数的数据要拟合二次函数模型非线性 模型确实要费些力气。【多调试一下参数如尝试增大学习率 / 增加训练轮数 等】
附便于理解~ 2023.2.17 23:28 1.3.7 补充——针对1.3.5可视化看权重收敛情况。y^ωx\widehat y\omega xyωx 与目录1.3.5 类似这里拟合的模型依然是线性模型 y^ωx\widehat y\omega xyωx直接把 求损失函数 写在 训练过程里了同时为了看出权重www的收敛情况给它设定初值66大一些。
# 昵 称:XieXu
# 时 间: 2023/2/15/0015 8:26# 也是拟合yw*x
# 【反向传播】构造BP网络--使用反向传播拟合线性模型
# y wx模型
# 可视化看训练多少轮权重收敛情况import torch
import matplotlib.pyplot as pltx_data [1.0, 2.0, 3.0]
y_data [2.0, 4.0, 6.0]w_data []
epoch_data []
epoch 1# 创建Tensor节点
w torch.Tensor([66.0])
# 设置计算梯度
w.requires_grad Truefor epoch in range(1, 101, 1):k 1# 每次都能拿到一个梯度直接全部都用for x, y in zip(x_data, y_data):# 构建图求均方误差~l (w * x - y) ** 2# 反馈更新grad梯度值l.backward()# 根据梯度下降法公式更新权重w.data w.data - 0.01 * w.grad.data# print(epoch:, epoch)print(k, 号样本, 梯度值w.grad.item, w.grad.item())print(权重 W:, w.data.item()) # 注意需要通过 .item()取值哈 2023.2.15 08:42w_data.append(w.data.item()) # 这里也可以w_data.append(w.data),plt也能识别出来Tensor里面的数值epoch_data.append(epoch)# epoch 1w.grad.data.zero_()k 1print(以上即, epoch, 轮.\n)print(epoch_data)
print(len(epoch_data):, len(epoch_data)) # 显然为300。因为100轮每轮 都是 这三个样本 做计算print(w_data)
print(len(w_data):,len(w_data)) # 显然也为300。plt.plot(epoch_data, w_data)
plt.xlabel(Epoch)
plt.ylabel(W)
plt.show() 结果如下可以看到权重的收敛情况 我们知道 训练集的w2那么 结果越接近2拟合效果越好可以看出拟合地还是很好的
1.3.8 补充——针对1.3.6可视化看参数w1、w2、b收敛情况 y^ω1x2ω2xb\widehat y\omega_1 x^2 \omega_2x byω1x2ω2xb 只不过这里的数据是更符合该 非线性模型的比起 目录1.3.6中的要更完美。训练集w1 2, w2 3, b 4。
# 昵 称:XieXu
# 时 间: 2023/2/15/0015 9:01# 反向传播梯度下降
# 拟合y w1x^2 w2x b(训练集w1 2, w2 3, b 4)# y w1x^2 w2x b
# 正确值w1 2, w2 3, b 4import torch
import matplotlib.pyplot as plt
import matplotlibmatplotlib.rc(font, familyMicrosoft YaHei) # 配合显示坐标纵轴的 汉字说明x_data [0.0, 1.0, 2.0, 3.0]
y_data [4.0, 9.0, 18.0, 31.0]w1_data []
w2_data []
b_data []epoch_data []
epoch 1# 创建Tensor节点
w1 torch.Tensor([10.0])
w2 torch.Tensor([10.0])
b torch.Tensor([10.0])
# 设置计算梯度
w1.requires_grad True
w2.requires_grad True
b.requires_grad True# 可以试着调整如训练1000轮、2000轮、3000轮、5000轮等
for epoch in range(1, 501, 1):k 1# 每次都能拿到一个梯度直接全部都用for x, y in zip(x_data, y_data):# 构建图l ((w1 * (x ** 2) w2 * x b) - y) ** 2 # 其实就是利用反向传播来求这个式子在各个权重方向的偏导# 反馈更新grad梯度值l.backward()# 根据梯度下降法公式更新权重w1.data w1.data - 0.01 * w1.grad.data # 也可以尝试 调整学习率w2.data w2.data - 0.01 * w2.grad.datab.data b.data - 0.01 * b.grad.dataprint(由,k, 号样本)# print(epoch:, epoch)print(W1:, w1.data.item()) # 注意 通过.item()取出 权重 即以标量的形式取出 2023.2.15 10:02print(W2:, w2.data.item())print(b:, b.data.item(), \n)w1_data.append(w1.data.item()) # 这里也可以w_data.append(w.data),plt也能识别出来Tensor里面的数值w2_data.append(w2.data.item())b_data.append(b.data.item())epoch_data.append(epoch)# epoch 1w1.grad.data.zero_() # 将权重的 梯度 清零每次只用当前的 梯度来进行 权重的更新 2023.2.15 10:11w2.grad.data.zero_()b.grad.data.zero_()k 1print(以上即 第, epoch, 轮\n)plt.plot(epoch_data, w1_data, g, labelW1)
# plt.legend([W1])plt.plot(epoch_data, w2_data, r, labelW2)
# plt.legend([W2])plt.plot(epoch_data, b_data, labelb)plt.legend([W1, W2, b])plt.xlabel(Epoch)
plt.ylabel(三个参数的收敛情况)plt.show()来看训练500轮的结果 可以看出w1、w2、b已经接近正确的值了(由上图 它们的收敛 也可以看出来) 训练1000轮 是的w1、w2、b的值已经更接近2、3、4了 训练5000轮 拟合的还是非常好的各位也可以尝试调整学习率等 来看效果如何。 1.4 线性回归——使用PyTorch实现 y^ωxb\widehat y \omega x byωxb
1.4.1 Pytorch深度学习的一般流程 前馈(算损失)、反馈(算梯度)、更新(使用 (随机) 梯度下降算法 更新权重) 前馈计算损失反馈计算梯度
用PyTorch进行深度学习模型构建的一般过程
[1] 准备数据集Prepare dataset[2] 设计用于计算最终结果的模型Design model[3] 构造损失函数及优化器Construct loss and optimizer[4] 设计循环周期Training cycle——前馈、反馈、更新
在pytorch中若使用mini-batch的 (小批量随机梯度下降) 风格一次性求出一个批量的y^\widehat yy则需要xxx以及y^\widehat yy作为矩阵参与计算【注只要知道了 输入xxx 和 输出y^\widehat yy 各自的维度那么www和bbb的维度就能推出来】此时利用 numpy的广播机制可以将原标量参数ω\omegaω 扩写为 同维度的矩阵[w][w][w],参与运算而不改变其Tensor的性质。
广播机制/扩充维度以便可以实现矩阵相加~ www和bbb 都会 自动扩充例如 下所示
1.4.2 线性单元----Linear Unit
在下述 线性模型的计算图中红框区域为线性单元其中的ω\omegaω以及bbb是需要反复训练确定的在设计时需要事先设计出二者的维度。
而由于公式y^ωxb\widehat y \omega x byωxb因此只要确定了y^\widehat yy以及xxx的维度就可以确定上述两个量的维度大小
例如已知xxx 和 zzz 或(yyy)的维度即可求www和bbb的维度.
线性模型 “计算图” 由上述理论由于前边的计算过程都是针对矩阵的因此最后的losslossloss也是矩阵但由于 要进行 反向传播 调整参数因此losslossloss应当是个标量因此要对矩阵[loss][loss][loss]内的每个量求和 并求 均值(MSE)。
loss1NΣ[loss1⋮lossn]loss \frac{1}{N}\Sigma \begin{bmatrix} {loss_1}\\ {\vdots}\\ {loss_n}\\ \end{bmatrix}lossN1Σloss1⋮lossn
1.4.3 练习训练周期前馈、反馈、权重更新
使用PyTorch实现线性回归 准备数据集 用类封装设计一个模型。目的是 为了前向传播forward即计算y^\widehat yy (预测值) 使用PyTorch的API 来定义 loss 和 optimizer。其中计算loss是为了计算损失 从而反向传播 求解梯度optimizer是为了根据得到的梯度 更新权重。 训练过程 forwardbackwardupdate 前馈 反馈 更新
首先是几张图便于理解 无论哪种目的都是把维度拼出来 torch.nn.Linear 中的 部分参数 说明 此外为了便于理解下图中 forward()的参数这里我已经做了说明。可回顾再往下的 Python基础语法知识~
注意Python中一个类实例要变成一个可调用对象只需要实现一个特殊方法__ call __()Module实现了函数 __ call __()call()里面有一条语句是要调用forward()。因此新写的类中需要重写forward()覆盖掉父类中的forward()。
call()函数的作用是可以直接在对象后面加()例如实例化的model对象 model LinearModel()和实例化的linear对象 self.linear torch.nn.Linear(1, 1)y_pred self.linear(x)。这在下面的最终代码中 都将会看到
self.linear(x)也由于函数call的实现 将会调用torch.nn.Linear类中的forward至此完成封装也就是说forward最终是在torch.nn.Linear类中实现的具体怎么实现可以不用关心大概就是完成y wx b操作。
其实也可以从源码中可见一斑 Python基础语法知识回顾函数中 多个参数的传递*args**kwargs当调用函数时可以传入 数目 正好的值
def func(a, b, c, x, y):passfunc(1, 2, 3, x6, y7)也可以传入 多个数量的值这样即可实现
def func(*args, x, y): # 定义 *args那么用户就可以传入多个参数print(args)func(1, 2, 3, 9, x6, y7) # 用户传入 多个参数 1 2 3 9然后再是x、y的匹配输出元组的形式进而可以取元素进行遍历 上述内容中x6y7 也可 在函数中其他 形式来表示
def func(*args, **kwargs): # 定义 *args那么用户就可以传入多个参数,以元组的形式print(args)print(kwargs) # 两个star那么 就会把参数变成 字典的形式func(1, 2, 3, 9, x6, y7) # 用户传入 多个参数 1 2 3 9然后再是x、y的匹配输出 以上操作是函数里面参数传递 常用到方式~
那么
class Foobar:def __init__(self):pass# 让对象可调用def __call__(self, *args, **kwargs):print(Hello str(args[0]))foobar Foobar()
foobar(1, 2, 3)构造 损失函数和优化器 我想我已经写的很细了… : 老师讲到backward之前要把梯度清零这是框架里面的需求老师特意强调的在之前 自己 手动实现的 梯度下降----反向传播 目录 1.3内的code是 在 反向传播之后 才把梯度清零的…en…总之这里记得 在框架里面的话backward 和 梯度下降 更新权重之前先梯度清零 基础上面所有这些分析本次关于 【线性回归——PyTorch实现】的练习code如下注释我写的也比较细
# 昵 称:XieXu
# 时 间: 2023/2/15/0015 19:10
# 2023.2.16 08:53
import torch# 1、准备数据集
# 数据作为矩阵参与Tensor计算
# x,y都是矩阵3行1列即共三个数据每个数据 只有一个特征
x_data torch.Tensor([[1.0], [2.0], [3.0]]) # 注意 x 和 y的值 都必须是“矩阵”
y_data torch.Tensor([[2.0], [4.0], [6.0]])# 2、使用类来 设计模型2023.2.16 08:41
我们的模型类应该继承于 nn.Module它是所有神经网络模块的基类。
必须实现成员方法 __init__() 和 forward()
nn.linear类包含两个成员Tensorsweight和bias。
nn.linear类已经实现了神奇的方法__call__()这使得该类的实例可以像函数一样被调用。
通常情况下forward()将被调用。# 将我们的模型 定义为 类 2023.2.15 20:18
# LinearModel类 继承于 Module类 (Module这个父类 中有许多方法是未来 模型训练过程中 要用到的)
class LinearModel(torch.nn.Module):# 定义“构造函数”# 即初始化 对象的时候 默认调用的函数def __init__(self):# 调用父类的init。【这就像是模板一样写上就行必须写】super(LinearModel, self).__init__() # 调用父类的initial# Linear 是一个 类torch.nn.Linear() 加括号 就是在“构建对象” 2023.2.15 20:28# Linear()这个 对象 包含 权重 weight(w)以及 偏置bias(b) 两个Tensor。。因此可以直接用linear 来完成 权重乘以输入 加上 偏置 的计算# (1,1)是指输入x和输出y的特征维度这里数据集中的x和y的特征 都是1维的 2023.2.16 08:44# 该线性程 需要学习的参数 是w和b获取w和b的方式分别是 linear.weight 和 linear.biasself.linear torch.nn.Linear(1, 1)# 注Linear 也是继承 自Module的,所以可以自动地进行 反向传播 ~ 2023.2.15 20:31# self.linear 是个对象这个对象的类型 是torch下 nn这个模块 中的 Linear这个类 ~ 2023.2.15 20:33# nn为缩写即 Neural Network 神经网络。(神经网络中的一个组件:Linear 它能够完成 权重和x相乘 算出中间值如Z Z再加上偏移量B 作为输出)# 前馈函数forward对父类函数 overwrite重写、覆盖2023.2.16 08:46# 就得叫做 forward一定要叫 这个名这是必须要定义的 2023.2.15 20:19# 前馈的过程中 所要实现的计算~def forward(self, x):# 调用linear中的call()以利用父类forward()计算wxby_pred self.linear(x)return y_pred# 之所以 这里没有 反馈函数backward是因为 由Module构建的对象 会自动根据“计算图”生成model LinearModel() # 实例化模型# 3、定义loss和优化器
# 构造的criterion对象所接受的参数为y,y
# criterion torch.nn.MSELoss(size_averageFalse) # UserWarning: size_average and reduce args will be deprecated, please use reductionsum instead. warnings.warn(warning.format(ret))
criterion torch.nn.MSELoss(reductionsum) # 由上面警告改为这样 即可~ 2023.2.15 23:13# model.parameters()用于检查模型中所能进行优化的张量。即 model.parameters() 自动完成参数的初始化操作
# learningrate(lr)表学习率可以统一 也可以不统一
optimizer torch.optim.SGD(model.parameters(), lr0.01)# 4、训练周期前馈、反馈、更新
for epoch in range(1000):# 前馈计算y_predy_pred model(x_data)# 前馈计算损失lossloss criterion(y_pred, y_data)# 打印调用loss时会自动调用内部__str__()函数避免产生计算图# print(epoch, loss)print(epoch, loss.item()) # 还是得以item来访问输出loss的 取值 2023.2.16 09:01# 梯度清零optimizer.zero_grad() # 也有朋友把这称作“初始化梯度”...# 梯度反向传播计算图清除loss.backward() # 自动反向传播计算梯度值# 根据传播的梯度以及学习率更新参数optimizer.step() # 更新参数w和b 2023.2.16 08:51# Output 输出最终的w和b
print(w , model.linear.weight.item())
print(b , model.linear.bias.item())# TestModel 测试部分
x_test torch.Tensor([[4.0]])
y_test model(x_test)print(y_pred , y_test.data)输出 直接输出y_pred的值
print(y_pred , y_test.item())附在翻看评论时发现的便于理解吧~ 2023.2.17 23:24 2023.2.17 23:34.
1.4.4 练习2——测试一下其它优化器效果如何
另外可尝试 不同的优化器 在如上 的线性模型中的效果 所用的code依然是基于上面小目录1.4.3只是稍微改动一下顺便加个可视化。这里由于在调用LBFGS优化器 有些问题因此这里就不测试了…故LBFGS相关注释可不用打开
# 昵 称:XieXu
# 时 间: 2023/2/16/0016 9:57# 验证不同优化器的效果
import torch
import matplotlib.pyplot as plt
import matplotlibmatplotlib.rc(font, familyMicrosoft YaHei) # 坐标系中汉字说明x_data torch.Tensor([[1.0], [2.0], [3.0]])
y_data torch.Tensor([[2.0], [4.0], [6.0]])epoch_list []
Loss_list []class LinearModel(torch.nn.Module):def __init__(self): # 构造函数super(LinearModel, self).__init__()self.linear torch.nn.Linear(1, 1) # 构造对象并说明输入输出的维数第三个参数默认为true表示用到bdef forward(self, x):y_pred self.linear(x) # 可调用对象计算ywxbreturn y_predmodel LinearModel() # 实例化模型criterion torch.nn.MSELoss(reductionsum)
# model.parameters()会扫描module中的所有成员如果成员中有相应权重那么都会将结果加到要训练的参数集合上optimizer torch.optim.SGD(model.parameters(), lr0.01) # lr为学习率
# optimizer torch.optim.Adagrad(model.parameters(), lr0.01)
# optimizer torch.optim.Adam(model.parameters(), lr0.01)
# optimizer torch.optim.Adamax(model.parameters(), lr0.01)
# optimizer torch.optim.ASGD(model.parameters(), lr0.01)
# optimizer torch.optim.LBFGS(model.parameters(), lr0.01) # TypeError: step() missing 1 required positional argument: closure
# optimizer torch.optim.RMSprop(model.parameters(), lr0.01)
# optimizer torch.optim.Rprop(model.parameters(), lr0.01)# def closure(): # 这里写的有问题所以 暂时不测试 LBFGS优化器了
# y_pred model(x_data)
# loss criterion(y_pred, y_data)
# loss.backward()
# return loss
# optimizer.step(closureclosure)for epoch in range(1000):y_pred model(x_data)loss criterion(y_pred, y_data)print(epoch, loss.item())optimizer.zero_grad()loss.backward()optimizer.step() # 使用LBFGS优化器时会报错TypeError: step() missing 1 required positional argument: closure# optimizer.step(closureclosure) # LBFGS优化器时报错暂时不测试该优化器了...# optimizer.zero_grad() # 梯度清零放到这里也行。。2023.2.16 16:08epoch_list.append(epoch)Loss_list.append(loss.item())print(w, model.linear.weight.item())
print(b, model.linear.bias.item())x_test torch.Tensor([[4.0]])
y_test model(x_test)
print(y_pred , y_test.data)
# print(y_pred , y_test.item())plt.plot(epoch_list,Loss_list)plt.xlabel(Epoch)
plt.ylabel(Loss)plt.title(所用优化器SGD)
# plt.title(所用优化器Adagrad)
# plt.title(所用优化器Adam)
# plt.title(所用优化器Adamax)
# plt.title(所用优化器ASGD)
# plt.title(所用优化器LBFGS) # TypeError: step() missing 1 required positional argument: closure...LBFGS优化器时报错暂时不测试该优化器了..
# plt.title(所用优化器RMSprop)
# plt.title(所用优化器Rprop)plt.show()为了便于查看我将轮数设置为100轮上面的1000后面可自行修改~
此外为了关于观察我将所有图形都分别截取然后放到如下一张图里面了。大家在实验时可以 自行打开注释分别测试几个优化器即可~
可以看出Adagrad优化器的损失是比较大的其它几个优化器也各有千秋有损失收敛比较快的也由曲线形式收敛的~本例中老师最初选取的是SGD–小批量随机梯度下降 优化器。 训练1000轮效果是比较好的近乎准确.
附torch.nn.Linear的pytorch官方文档可以看看官方的描述进去后 甚至可以按照torch的版本 来选文档来看。
1.5 逻辑回归 (LogisticRegression)——基于PyTorch实现
1.5.1 问题背景——二分类问题
注截止到上面的所有实例使用的 都是 学习时间----对应得分的例子即 学习1小时 得2分学习2小时 得4分学习三小时 得6分…这是问题背景。
本目录记录逻辑回归众所周知它是解决分类问题的虽然名字里边带回归…常用的损失函数 是交叉熵损失函数~
针对上面学习时间----得分的示例这里 的问题背景是学习时间----是否及格即学习1小时----不及格(记为类别0)学习2小时----不及格(类别0)学习三小时----及格(记为类别1)判断学习4小时是否及格呢
显然这是 二分类问题。
1.5.2 常做练习用的数据集介绍MNIST 和 CIFAR-10
[1] 手写数字数据集
即 MNIST数据集。
该数据集的基础内容是
训练集 Training_set 含有 60000 个样本测试集 Test_set 含有 10000 个样本类别共10个类别即 0~9 十种数字。
[2] CIFAR-10数据集(十种 事物/物体 的数据集)
基础内容是
训练集 Training_set: 共 50000 个样本测试集 Test_set: 共 10000个样本物品个数 Classes: 10 种类别分别是飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船、卡车 这十种物品/事物。
1.5.3 二分类 与 sigmoid
我们知道二分类要么可以标记为0要么标记为1非此即彼。我们得到的结果 是 输入数据 所属的类别。这与之前的回归问题(结果的取值是连续的) 有所不同分类问题中得到的结果 一般是 不连续的即离散的。
在二分类问题中我们怎样衡量 输入数据 运算过后 所属的类别
一般是通过概率来表示这也是容易理解的有输入数据经过运算我们得到 它对应于 这些类别的各个概率值哪个概率值大就认定为 它是 属于 哪个类别。
所有类别的概率之和 1 在二分类中那么就有
P(y^1)P(y^0)1P(\widehat y1)P(\widehat y0) 1P(y1)P(y0)1
由于非此即彼那么 只需要计算一种类别的结果就自然而然知道 属于另一种类别的概率了。这里一般计算P(y^1)P(\widehat y1)P(y1)。
紧接着考虑到的问题是概率的取值 一定是0到1的 即y^∈[0,1]\widehat y \in [0,1]y∈[0,1]因此就需要对之前模型 y^ωxb\widehat y \omega xbyωxb 得到的结果进行一个映射把它的值 映射到 0到1之间这里用到的映射函数是sigmoid函数(也常称为 逻辑函数)也是极其常见的一个函数。该函数的特性如下
σ(x)11e−x\sigma(x) \frac{1}{1e^{-x}}σ(x)1e−x1
由下面其图像可知
函数值在0到1之间变化明显曲线陡 函数值激增导数大在趋近于0和1处函数逐渐平滑导数小函数为饱和函数x超过某值时导数变得越小sigmoid曲线逐渐平缓大致是这种意思【满足limx→∞f′(x)→0\lim \limits_{x\rightarrow\infty}f(x)\;\rightarrow0x→∞limf′(x)→0 的函数为右饱和函数满足limx→−∞f′(x)→0\lim \limits_{x\rightarrow-\infty}f(x)\;\rightarrow0x→−∞limf′(x)→0的函数为左饱和函数。同时满足二者的为饱和函数】sigmoid函数 为单调增函数由其公式 和 数学知识 容易知道x取0时 sigmoid(0)0.5x取正无穷时 sigmoid(正无穷) 无限趋近1x取负无穷时 sigmoid(负无穷) 无限趋近0。
这个函数 原名为 logistics函数属于sigmod类函数由于其特性优异代码中的sigmod函数就指的是该函数这也是约定俗成的其函数图像为
sigmoid函数图像 另外其导函数大致如此有些像 正态分布的曲线… 注此外还要其它形式的sigmoid函数 1.5.4 模型的变化由线性回归模型----二分类模型增添sigmoid函数损失函数的变化由线性回归损失函数(MSE 均方误差损失函数) ----二分类损失函数BCELoss (mini-batch 小批量交叉熵损失函数cross entropy)
经过上面的分析容易知道sigmoid函数的输入值 即 参数 为y^\widehat yy。(输出值 套上了 一个sigmoid函数仅此而已其它都是差不多的。再就是数据中 输出值 为0或1(分类结果) )
原来的线性回归模型 变为了 二分类模型 损失函数Loss的变化
之前我们是计算两个标量 数值间的差距也就是数轴上的距离。
现在为了计算(表示) 两个概率之间的差异可以用 “分布”这个词来形容一下需要利用到交叉熵的理论。 【二分类 交叉熵损失函数】
看上图中带logloglog的那个公式若y0y0y0则取括号中 加号后边的那个式子即loss−log(1−y^)loss-log(1-\widehat y)loss−log(1−y)老师讲到这是在计算分布的差异如概率论与数理统计中 KL散度(这个知识我了解不够需要补)、cross-entropy 即交叉熵损失函数。继续看公式我们希望losslossloss尽可能小而公式中带了负号那么 即 希望 log(1−y^)log(1-\widehat y)log(1−y)尽可能大而logloglog函数是单调增函数则希望1−y^1-\widehat y1−y尽可能大即 希望y^\widehat yy尽可能小即 y^\widehat yy越 无限趋近0 越好。反之输出的 y1y1y1时同样的道理为了losslossloss尽可能小我们希望y^\widehat yy尽可能接近1。
例如下边有两个分布可用如下绿框中的形式 来表示这两个分布之间 差异性的大小我们希望 这个值 越大越好 目标是在训练的时候让分布尽可能地去接近 真实类别。本例中使用的是 小批量 的二分类损失函数(BCE Loss)可以看到类别为1时y^\widehat yy值越接近1BCE损失函数越小反之越大同理类别为0时y^\widehat yy值越接近0BCE损失函数越小反之BCE损失函数越大。 注BCELoss 是CrossEntropyLoss的一个特例只用于二分类问题而CrossEntropyLoss可以用于二分类也可以用于多分类。如果是二分类问题建议用BCELoss 再次 附关于 torch.nn.BCELoss官方文档
1.5.5 code练习
问题背景即上面提到的。
# 昵 称:XieXu
# 时 间: 2023/2/16/0016 17:51
import torch.nn.functional as F
import torch# 【1】Prepare data
x_data torch.Tensor([[1.0], [2.0], [3.0]])
y_data torch.Tensor([[0.0], [0.0], [1.0]]) # 这里与之前不同分别是0/1两种类别不合格/合格 2023.2.16 17:53# 【2】Design model
# 改用LogisticRegressionModel 也是同样继承于Module
class LogisticRegressionModel(torch.nn.Module):def __init__(self):super(LogisticRegressionModel, self).__init__()self.linear torch.nn.Linear(1, 1)def forward(self, x):# 对原先的linear结果进行sigmod激活将y_hat值 映射到0~1之间# y_pred F.sigmoid(self.linear(x)) # UserWarning: nn.functional.sigmoid is deprecated. Use torch.sigmoid instead.y_pred torch.sigmoid(self.linear(x)) # 2023.2.16 20:05return y_pred# 实例化 2023.2.16 17:53
model LogisticRegressionModel()# 【3】Select criterion and optimizer (using PyTorch API)
# 构造的criterion对象所接受的参数为y_hat,y,这里的y_hat指的是归一化之后的(初始计算的y_hat 被映射到0~1之后的值)
# UserWarning: size_average and reduce args will be deprecated, please use reductionsum instead.
# 这个警告~ 和之前线性模型也遇到的一样
# criterion torch.nn.BCELoss(size_averageFalse) # 计算损失
criterion torch.nn.BCELoss(reductionsum) # 计算损失 【默认对一个batch里面的数据做二元交叉熵并且求平均。】optimizer torch.optim.SGD(model.parameters(), lr0.01) # 梯度下降更新权重# 【4】Training cycle 10000轮时效果近乎100%
for epoch in range(1000):y_pred model(x_data) # 输入参数得到 被映射到 0~1 之间的 (新的)y_hat值loss criterion(y_pred, y_data) # 利用BCELoss损失函数 计算损失值print(epoch, loss.item())optimizer.zero_grad() # 梯度清零。2023.2.16loss.backward() # 反向传播求解梯度optimizer.step() # 梯度下降 更新权重print(w , model.linear.weight.item()) # 注意通过item()来访问 取值 2023.2.16 17:54
print(b , model.linear.bias.item())x_test torch.Tensor([[4.0]]) # 测试。看 当x4时
y_test model(x_test) # 即问题背景下的 学习时间为4小时 时合格/不合格 两种类别print(y_pred , y_test.data) # 得到最终的概率值... 概率大指的是取值为1即合格。反之概率值小意为不合格。
# print(y_pred , y_test.item()) # 和上一行哪个 都可~
# print(type(y_test)) # class torch.Tensor若训练10000轮可以看到结果如下这说明输入学习时间为4小时 时结果为合格的概率近乎100%即确定合格。若不想看到tensor只想看到y_pred的数值可以 直接把最后输出y_pred的语句改为 print(y_pred , y_test.item()) 即可因为y_pred的类型也是一个Tensor 1.6 处理多维特征的输入
1.6.1 问题背景含 多维特征 的数据集
在上面的介绍中x大都只有一个特征即只有一个维度那么 当x的维度是多维时即 输入有多个特征时如何处理
之前的数据集 本次使用的 含多维特征的数据集——糖尿病数据集sklearn中 其实也有类似的糖尿病数据集每个样本/记录sample/record有8个维度的信息feature并以此进行二分类。Y表示一年后 糖尿病 病情是否加重。 1.6.2 多维度特征的处理Mini-batch多层神经网络
在之前单个特征维度 的 逻辑回归模型为 y^(i)σ(x(i)ωb)\widehat y^{(i)} \sigma(x^{(i)} \omegab) y(i)σ(x(i)ωb)
其中的x(i)x^{(i)}x(i)表示第i个样本的维度对于多维度输入要变为8个维度的输入因此模型应当变为
y^(i)σ(∑n18xn(i)ωnb)\widehat y^{(i)} \sigma(\sum _{n1}^8 x^{(i)}_n \omega _nb)y(i)σ(n1∑8xn(i)ωnb)
其中的xn(i)x^{(i)}_nxn(i)表示第i个样本的第n个维度。由于在实际代码运算中是以矩阵进行计算的因此
∑n18xn(i)ωn[x1(i)⋯x8(i)][w1⋮w8]\sum _{n1}^8 x^{(i)}_n \omega _n \begin{bmatrix} {x_1^{(i)}}{\cdots}{x_8^{(i)}} \end{bmatrix} \begin{bmatrix} {w_1}\\ {\vdots}\\ {w_8} \end{bmatrix}n1∑8xn(i)ωn[x1(i)⋯x8(i)]w1⋮w8
回到原式故可以表示成
y^(i)σ([x1(i)⋯x8(i)][w1⋮w8]b)σ(z(i))\widehat y^{(i)} \sigma( \begin{bmatrix} {x_1^{(i)}}{\cdots}{x_8^{(i)}} \end{bmatrix} \begin{bmatrix} {w_1}\\ {\vdots}\\ {w_8} \end{bmatrix}b)\\ \sigma(z^{(i)})y(i)σ([x1(i)⋯x8(i)]w1⋮w8b)σ(z(i))
转换成矩阵运算就可以更好地利用CPU/GPU的特性来提高运行速度和效率等
Mini-batch
基于上面对数据的处理我们就可以对小批量数据也 合并 成 矩阵运算
[y^(1)⋮y^(N)][σ(z(1))⋮σ(z(N))]σ([z(1)⋮z(N)])\begin{bmatrix} {\widehat y^{(1)}}\\ {\vdots}\\ {\widehat y^{(N)}} \end{bmatrix} \begin{bmatrix} {\sigma(z^{(1)})}\\ {\vdots}\\ {\sigma(z^{(N)})} \end{bmatrix} \sigma( \begin{bmatrix} {z^{(1)}}\\ {\vdots}\\ {z^{(N)}} \end{bmatrix})y(1)⋮y(N)σ(z(1))⋮σ(z(N))σ(z(1)⋮z(N))
其中 z(i)[x1(i)⋯x8(i)][w1⋮w8]bz^{(i)} \begin{bmatrix} {x_1^{(i)}}{\cdots}{x_8^{(i)}} \end{bmatrix} \begin{bmatrix} {w_1}\\ {\vdots}\\ {w_8} \end{bmatrix}b\\ z(i)[x1(i)⋯x8(i)]w1⋮w8b
从而有 [z(1)⋮z(N)][x1(1)⋯x8(1)⋮⋮⋮x1(N)⋯x8(N)][w1⋮w8][b⋮b]\begin{bmatrix} {z^{(1)}}\\ {\vdots}\\ {z^{(N)}} \end{bmatrix} \begin{bmatrix} {x_1^{(1)}} {\cdots} {x_8^{(1)}}\\ {\vdots} {\vdots} {\vdots}\\ {x_1^{(N)}} {\cdots} {x_8^{(N)}}\\ \end{bmatrix} \begin{bmatrix} {w_1}\\ {\vdots}\\ {w_8} \end{bmatrix} \begin{bmatrix} {b}\\ {\vdots}\\ {b} \end{bmatrix}z(1)⋮z(N)x1(1)⋮x1(N)⋯⋮⋯x8(1)⋮x8(N)w1⋮w8b⋮b
整体上将之前的标量运算转换为矩阵运算以方便进行并行计算提高算法效率。下图指明了矩阵的维度NX8N X 8NX8矩阵中每一行是 各个样本的第1到第8个特征8X18 X 18X1矩阵中8行每行一个权重按照矩阵乘法这两个矩阵分别相乘即 每个样本的每个特征 与 对应每个特征的权重相乘每个样本 都有8个特征特征与权重 乘完再 各自 加上 偏置bbb。 因此对于模型的修改即输入维度改为8输出维度仍为1即可。 XNX8N X 8NX8 维 YNX1N X 1NX1 即可~ 另外偏置bbb 可通过“广播机制”自动扩充。 故只需要将Linear()中的参数改成下面代码即可完成 从8维输入到1位输出的过程。
self.linear torch.nn.Linear(8,1)多层神经网络
老师讲到什么是矩阵矩阵是一种空间变换的函数。
例将xxx由NNN维空间 映射到 MMM维空间去: 例把任意 8维空间的向量映射到二维空间上这里是 线性映射 神经网络本质上 是寻找一种 非线性的空间变换函数。
经常要做的空间变换不一定是线性的而是非常复杂的非线性的我们用多个线性变换层通过找到最优的权重把它们组合起来来模拟出 非线性变换。
那么如果不用(8,2)而用(8,6)就可以把输入xxx 从8维空间 降到 6维空间后面 可以再从 6维 降到 2维2维 降到 1维。把维度 分布地进行降低。
由此可以增加网络层数增加网络复杂度。此外对网络结构 先增后减即先扩充维度 再降低维度也是OK的。这里Linear 能做到空间维度的变换。
分布降低由于它是纯线性的所以在把它们 接起来之前要记得σ\sigmaσ因为Linear是不做非线性的通过σ\sigmaσ 将其转换为 非线性。因此这几次的变换每一次的 空间压缩 都引入 一个非线性这样我们只要调整 每一步线性变换我们就可以用 这样的方式 来 去拟合 我们真正想要的 空间变换。【老师原话】
在神经网络里面σ\sigmaσ叫做激活函数通过引入σ\sigmaσ激活函数给线性变换 增加 非线性(因子)。使得可以去拟合 相应地 非线性变换。【这就是神经网络 设计 所采用的方式】
变换的维度和层数决定了网络的复杂程度。但是具体取什么值比较好这个是 超参数搜索的问题。一般来说,隐层越多即中间执行的步骤越多中间的神经元越多那么 非线性变换的学习能力就越强但是 但是 但是学习能力越强并不是越好这样会把输入样本里面 噪声的规律也学到。。噪声是我们不想要的且这个噪声 和 真实的应用场景里面的噪声 不一定是一样的。我们要学的 是 数据真值本身的规律。 学习能力太强并不好。即学习能力 必须要有 泛化能力 才是最好的。【老师讲到要练习 读文档的能力具有基本的架构的理解能力(CPU架构/GPU/操作系统/主机架构/内存管理/编译原理架构等) 】 1.6.3 练习
基于上述分析。
本次【问题背景糖尿病–病情是否加重】练习用到的模型 大致思路同之前 具体地针对构建模型的代码 附若想查看 神经网络 某些层的参数 构造 损失函数和优化器输出仍是概率即y^1\widehat y1y1的概率故依然用BCE损失 训练周期中这里没有用到mini-batch而是把所有数据都放进来了。至于minibatch后面 将会在pytorch所提供的工具类 DataLoader 记录 关于下述Model类Python代码中的 一些关键词构造函数、super函数、父类、 子类、方法(函数)重写/覆盖
# 昵 称:XieXu
# 时 间: 2023/2/17/0017 9:15
# 处理多维特征的输入import torch
import numpy as np# 读取文件一般GPU只支持32位浮点数
xy np.loadtxt(diabetes.csv, delimiter,, dtypenp.float32) # 也可以读取 .gz 压缩文件# 确保x_data 和 y_data 在计算过程中 都得是矩阵不能是一个向量 2023.2.17 11:15
# torch.from_numpy 会创建Tensor# x_data torch.from_numpy(xy[:-1, :-1]) #-1行-1列不取
x_data torch.from_numpy(xy[:, :-1]) # 第一个:是指 读取所有行 第二个:是指从第一列开始最后一列不要。即 取前8列# y_data torch.from_numpy(xy[:-1, [-1]]) #单取-1列作为矩阵
y_data torch.from_numpy(xy[:, [-1]]) # [-1] 最后得到的是个矩阵 即 取最后一列
查看数据集的 分割
print(x_data.item()) # ValueError: only one element tensors can be converted to Python scalars
print(x_data)
print(x_data.shape) # torch.Size([759, 8]),只取前8列即作为输入x 8个特征
print(y_data) # 数据集中的最后一列以矩阵的形式~
print(y_data.shape) # torch.Size([759, 1])
# 取-1行的测试集部分
test_data torch.from_numpy(xy[[-1], :-1]) # 即最后一行的 x 输入数据
pred_test torch.from_numpy(xy[[-1], [-1]]) # 最后一行 的 最后一列即1类别class Model(torch.nn.Module):def __init__(self):super(Model, self).__init__()self.linear1 torch.nn.Linear(8, 6)self.linear2 torch.nn.Linear(6, 4)self.linear3 torch.nn.Linear(4, 1)self.sigmoid torch.nn.Sigmoid() # 这里 torch.nn.Sigmoid() 将其看做是网络的一层而不是普通的函数使用# self.ReLU torch.nn.ReLU()def forward(self, x):x self.sigmoid(self.linear1(x))x self.sigmoid(self.linear2(x))x self.sigmoid(self.linear3(x))# x self.ReLU(self.linear1(x))# x self.ReLU(self.linear2(x))# x self.sigmoid(self.linear3(x))return x# 实例化 2023.2.17 09:17
model Model()# 二分类 交叉熵损失函数
# criterion torch.nn.BCELoss(size_averageTrue) # UserWarning: size_average and reduce args will be deprecated, please use reductionmean instead.
criterion torch.nn.BCELoss(reductionmean) # 2023.2.17 11:09optimizer torch.optim.SGD(model.parameters(), lr0.1)# 1000轮数时 test_pred 为0.65左右
# 10000轮时test_pred 为0.94左右。迭代次数增多训练效果更好了~
for epoch in range(10000):# Forward 这里并非mini-batch的设计(把所有数据都扔进来了)只是mini-batch的风格y_pred model(x_data)loss criterion(y_pred, y_data)print(epoch, loss.item())# Backwardoptimizer.zero_grad()loss.backward() # 反向传播# Updateoptimizer.step() # 更新 权重print(test_pred , model(test_data).item())
print(infact_pred , pred_test.item())
训练10000轮可以看到与真实值1.0已经比较接近了 在使用其他激活函数时记得forward()方法中的最后部分 要写sigmoid哈否则就 容易会 嗯…用了ReLU激活函数要比原来 概率还要高呢 1.6.4 可视化使用 Adam优化器/SGD优化器 试试看
这里我只训练了30万轮。是需要一些时间的。使用的是默认的CPU没用GPU运行。
# 昵 称:XieXu
# 时 间: 2023/2/17/0017 13:40
import numpy as np
import torch
import matplotlib.pyplot as plt# prepare dataset
xy np.loadtxt(diabetes.csv, delimiter,, dtypenp.float32)x_data torch.from_numpy(xy[:, :-1])
y_data torch.from_numpy(xy[:, [-1]])# design model using class
class Model(torch.nn.Module):def __init__(self):super(Model, self).__init__()self.linear1 torch.nn.Linear(8, 6) # 输入数据x的特征是8维x有8个特征,输出为6维self.linear2 torch.nn.Linear(6, 4)self.linear3 torch.nn.Linear(4, 1)self.sigmoid torch.nn.Sigmoid() # 将其看作是网络的一层而不是简单的函数使用def forward(self, x): # 构建一个计算图x self.sigmoid(self.linear1(x))x self.sigmoid(self.linear2(x)) # 将上面一行的输出作为输入x self.sigmoid(self.linear3(x))return xmodel Model() # 实例化# construct loss and optimizer
# model.parameters()会扫描module中的所有成员如果成员中有相应权重那么都会将结果加到要训练的参数集合上
criterion torch.nn.BCELoss(reductionmean)
# optimizer torch.optim.SGD(model.parameters(), lr0.1) # 30w次效果acc0.84 loss0.37
optimizer torch.optim.Adam(model.parameters(), lr0.1) # 20w次效果acc0.85 2023.2.17 14:33epoch_list []
loss_list []# training cycle forward, backward, update
for epoch in range(1,300001,1):y_pred model(x_data)loss criterion(y_pred, y_data)# print(epoch, loss.item())epoch_list.append(epoch)# loss_list.append(loss)loss_list.append(loss.item())optimizer.zero_grad()loss.backward()optimizer.step()# 输出准确率acc为评价指标if epoch % 10000 0:# 预测的概率大于0.5的话这里就认为 预测类别属于1否则就属于0二分类~# torch.where(conditionab)其中,输入参数condition条件限制如果满足条件则选择a否则选择b作为输出。y_pred_label torch.where(y_pred 0.5, torch.tensor([1.0]), torch.tensor([0.0]))# 计算正确的数量acc torch.eq(y_pred_label, y_data).sum().item() / y_data.size(0) # 即 预测值 要么1正确/要么0不正确 二分类2023.2.17 16:35print(epoch , epoch, loss , loss.item(), acc , acc)# 可视化
plt.plot(epoch_list, loss_list)
plt.ylabel(Loss)
plt.xlabel(Epoch)
# plt.title(Loss with epoch (SGD))
plt.title(Loss with epoch (Adam))
plt.show()至于
训练30w次使用SGD(随机梯度下降)优化器的效果acc0.84 loss0.37 训练30w次使用Adam优化器的效果acc0.83 loss1.38。反而不如SGD了。这有可能过拟合了。 我又测试了下Adam优化器训练了1w轮acc为0.87要比上边30w轮效果还好… 同样是1w轮SGD优化器效果 acc0.76这时候不如Adam优化器效果好。 1.6.5 附可尝试 不同的激活函数 点击 这里 可以跳转进入查看不同的激活函数图像 可选择不同的激活函数 可以看到激活函数的图像(蓝色) 以及 其导函数的图像(浅黄色)ReLU激活函数 Tanh激活函数 另外PyTorch中提供了 大量的激活函数PyTorch激活函数 官方文档
修改如下地方可测试不同的激活单元(函数) 对神经网络性能的影响如下边使用的是ReLU激活函数【注意若要求概率的话最后一层要使用sigmoid激活函数】 其实已经在上面目录1.6.3使用过 ReLU激活函数了。关于其它激活函数读者可自行替换 尝试实验 即可。【要记得forward的最后部分 依然使用sigmoid哈否则 可能会出现概率值为0的情况概率 为0~1】
不同的激活函数(降低维度用/映射用/)、不同的优化器(梯度下降or求偏导用)、不同的学习率、不同的训练轮数等。有很多值得调试的超参数哈哈哈
1.7 加载数据集 Dataset and DataLoader
1.7.1 Dataset类、DataLoader类Epoch、Mini-batch、Iteration
DataSet 和 DataLoader是用于加载数据的工具类。 DataSet 用于构造数据集数据集 应该支持索引/下标操作DataSet 是一个抽象类只能通过继承实现 DataLoader是用于拿出Mini-Batch即一组数据用于训练来快速使用。
在进行梯度下降的时候我们传入数据时可以选择随机梯度下降每次只用一个样本这样会得到比较好的随机性也会帮助我们跨越 将来在优化过程中 所遇到的“鞍点”。训练出的模型 性能会比较好。但是会导致在优化的时候 用的时间过长。每次一个样本这无法利用CPU或GPU的并行能力所以训练的时间 是会非常长的
// 若一次性 把所有数据都 执行梯度下降即Batch它的优势是 可以最大化利用 向量计算的优势来提升计算的速度。虽然计算速度非常快但是在求得的模型性能上可能不是那么好。
因此在深度学习中会使用Mini-batch的方法来均衡 在性能 和 训练时间上的平衡需求。
EpochBatch-SizeIteration
在外层循环中每一层是一个epoch训练周期在内层循环中每一次是一个Mini-BatchBatch的迭代. epoch、batch-size、Iteration的概念亦如下图所解释 Epoch所有的样本都进行了一次 前馈计算和反向传播即为一次epoch Batch-Size每次训练的时候所使用的样本数量 Iterationsbatch分的次数。
1.7.2 DataLoader类 核心参数解释
batch_size每次训练的时候所使用的样本数量一般指定2的幂次数shuffle(可理解为“洗牌”用于打乱顺序提高训练的随机性)这样每一次生成的batch_size中的数据集都是具有随机性的
通过获得DataSet(数据集)的索引(即 可用下标访问) 以及 数据集大小(len)来自动地生成小批量训练集。
具体地DataLoader先对数据集进行洗牌【打乱】再将数据集按照Batch_Size的长度划分为小的Batch【分组】并按照Iterations进行加载以方便 通过循环对每个Batch进行操作。 1.7.3 如何在代码层面实现 Dataset和DataLoader
这里老师讲到在构造数据集时在__init()方法里两种对数据加载到内存中的处理方式如下
[1] 加载所有数据到dataset每次使用时 读索引这适用于数据量小的情况。如本例中糖尿病的数据集就是一个关系表很小直接从内存中读取就好。但是如果是 图像数据集几十G那么 在initial里把数据都读进来这显然是不可能的通常把它们打包到一个文件里面[2] 只对dataset 进行初始化仅存 文件名 到列表每次使用时 再通过索引到内存中去读取。
num_workers 指的是 对于Mini-batch内的数据集读的时候是否要用 多线程。即到底 用几个并行的进程 去读取数据。并行 可以 提高 读取的效率在windows下若遇到num_workers参数设置会报错的用if__main__包起来就OK了亦或是将num_workers设置为0 使用Loader时可能会遇到问题这是由于torch0.4的版本所致 只需要将迭代的语句 封装到 if语句里面或者函数里面就可以解决该报错 进一步数据集的类 是如何实现的 training cycle的变化变成了二重循环外层即Epoch
Dataset每次拿过来的 都是X的第i行和 Y的第i行DataLoader每次根据Mini-batch的数量会把X和Y 各自变为矩阵且会自动地转换成Tensor因此这里直接data即可不用再 自己手动转换为Tensor了。
简言之与之前的区别就是把data中的数据 解成 XXX和YYY。 当然也有另外一种写法可以不写 inputs,labels data
即直接 写在上面这种形式for i,(inputs,labels) enumerate(train_loader,0)本意都是一样的。
整体步骤 1.7.4 其它说明torchvision内置数据集
torchvision中内置了很多数据集可以直接去使用这些数据集都是派生自Dataset因此有__getitem__和__len__等方法的实现当然 这些数据集 也可以用 DataLoader来进行加载。并且可以使用多进程 来进行加速。 如之前提到过的手写数字数据集MNIST入门图像识别常会用到的要使用该数据集只需要从torchvision将datasets导入进来即可。datasets中 有一个MNIST类可用该类 来构造一个MNIST实例。
参数路径、训练集/测试集、图像要做什么样的transform(变换 )【即 PIL Image 类型 —Tensor张量 。图像中的像素值 为0~255将其缩放到0 ~ 1或者-1 ~ 1等区间的操作】、download若没有该数据集是否联网下载
在训练数据集中通常把shuffle设置为true。在测试的时候因为对模型是没有改变的所以把shuffle设置为False就好了不做shuffle每次 输出测试样本的 数据 及对它的预测 的时候可以保证顺序是一样的这对我们观察结果很有帮助~ 注关于手写数字的识别这个将会在下面的多分类中实现。 注关于“泰坦尼克号”的作业这个后续会补充说明
1.7.5 练习1【建议取num_workers0若2速度反而慢了】
建议这里取 num_workers0若2虽然也能Run可速度却反而慢了所以这里不要使用CPU多线程。下面有我测试的例子。
# 昵 称:XieXu
# 时 间: 2023/2/17/0017 18:16
import torch
import numpy as npfrom torch.utils.data import Dataset # DataSet是抽象类无法实例化from torch.utils.data import DataLoader # DataLoader可实例化class DiabetesDataset(Dataset):def __init__(self, filepath):xy np.loadtxt(filepath, delimiter,, dtypenp.float32)self.len xy.shape[0] # 获得数据集长度self.x_data torch.from_numpy(xy[:, :-1]) # 前8列X输入(特征)self.y_data torch.from_numpy(xy[:, [-1]]) # 最后一列Y结果类别# 获得索引方法def __getitem__(self, index):return self.x_data[index], self.y_data[index]# 获得数据集长度def __len__(self):return self.lendataset DiabetesDataset(diabetes.csv)
# num_workers表示多线程的读取。下面可通过train_loader获得(X,Y)元组其中会自动把它们转为矩阵形式(根据batch_size数量)也是Tensor
train_loader DataLoader(datasetdataset, batch_size32, shuffleTrue, num_workers2)
# 建议num_workers0这样反而会秒得结果。很快。不要取num_workers22023.2.18 09:21class Model(torch.nn.Module):def __init__(self):super(Model, self).__init__()self.linear1 torch.nn.Linear(8, 6)self.linear2 torch.nn.Linear(6, 4)self.linear3 torch.nn.Linear(4, 1)self.sigmoid torch.nn.Sigmoid()def forward(self, x):x self.sigmoid(self.linear1(x))x self.sigmoid(self.linear2(x))x self.sigmoid(self.linear3(x)) # 这里需要是sigmoid哦2023.2.18 08:32return xmodel Model()# criterion torch.nn.BCELoss(size_averageTrue) # UserWarning: size_average and reduce args will be deprecated, please use reductionmean instead.
criterion torch.nn.BCELoss(reductionmean) # 2023.2.17 18:18optimizer torch.optim.SGD(model.parameters(), lr0.1)# 100轮loss0.715 每次不一样啦~
# 1000轮loss0.38
# 500轮loss0.49
# 1000轮lr0.8loss0.35
if __name__ __main__:for epoch in range(100):for i, data in enumerate(train_loader, 0): # enumerate:可获得当前迭代的次数# 准备数据dataloader会将按batch_size返回的数据整合成矩阵加载inputs, labels data# 前馈求y_hat即 概率 2023.2.18 08:39y_pred model(inputs)loss criterion(y_pred, labels)print(epoch, i, loss.item())# 反向传播反馈optimizer.zero_grad()loss.backward()# 更新optimizer.step()初次训练
可以看到一轮训练24次是什么意思呢这是由于是执行了24次 batch_size为32的数据即【一个epoch就是过一遍所有数据这个24就是取了24次batchsize为32的数据也就是说数据一共有32 * 24 768条或者少于768、大于 736 32 * 23。我们知道该糖尿病数据集中的样本数量为759。736 759 768759/3223.71875故最少 只能执行24次batch_size32 的数据才OK】以下图中的0 0 为例这一竖列 0 代表 第0轮然后后面紧跟着的0表示训练第0个batch_size即取出32个样本训练(这应该是之前已经通过 DataLoader 划分好的) 结果。经过测试num_workers2 时我觉得时间还行用的不是特别多大概需要2、3分钟吧。 这里再测试一下num_workers0即不使用CPU多线程
好吧结果是秒得结果这比起上面要快的多【看到有朋友说不要在DataLoader中使用多个worker把num_workers设为0】 同样地也是取num_workers0既然训练很快那1000轮试试loss0.38效果好了一些。 至于进一步优化en…大家可以再试试 调整 lr学习率、optimizer优化器、激活函数 等等其它参数。
如调整lr0.8还是1000轮loss0.35效果又好了一点点。 补充说明
1.7.6 练习2划分训练集、测试集封装训练过程、测试过程
与练习1类似学习时建议先以1为准。
这里不同之处 在于 对数据集进行了划分并将训练 和 测试 都做了封装另外训练50000轮训练一直在进行并每当训练过了2000轮时 便 执行 一次测试。
# 昵 称:XieXu
# 时 间: 2023/2/18/0018 9:32# 1、将原始数据集分为 训练集和测试集
# 2、对训练集进行 批量梯度下降
# 3、评估测试集的准确率import torch
import numpy as np
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
from sklearn.model_selection import train_test_split# 读取原始数据并划分训练集和测试集
raw_data np.loadtxt(diabetes.csv, delimiter,, dtypenp.float32)
X raw_data[:, :-1]
y raw_data[:, [-1]]
Xtrain, Xtest, Ytrain, Ytest train_test_split(X, y, test_size0.3)
Xtest torch.from_numpy(Xtest)
Ytest torch.from_numpy(Ytest)# 将训练数据集进行批量处理
# prepare datasetclass DiabetesDataset(Dataset):def __init__(self, data, label):self.len data.shape[0] # shape(多少行多少列)self.x_data torch.from_numpy(data)self.y_data torch.from_numpy(label)def __getitem__(self, index):return self.x_data[index], self.y_data[index]def __len__(self):return self.lentrain_dataset DiabetesDataset(Xtrain, Ytrain)
train_loader DataLoader(datasettrain_dataset, batch_size32, shuffleTrue, num_workers0) # num_workers 多线程# design model using classclass Model(torch.nn.Module):def __init__(self):super(Model, self).__init__()self.linear1 torch.nn.Linear(8, 6)self.linear2 torch.nn.Linear(6, 4)self.linear3 torch.nn.Linear(4, 2)self.linear4 torch.nn.Linear(2, 1)self.sigmoid torch.nn.Sigmoid()def forward(self, x):x self.sigmoid(self.linear1(x))x self.sigmoid(self.linear2(x))x self.sigmoid(self.linear3(x))x self.sigmoid(self.linear4(x))return xmodel Model()# construct loss and optimizer
criterion torch.nn.BCELoss(reductionmean)
optimizer torch.optim.SGD(model.parameters(), lr0.01)# training cycle forward, backward, updatedef train(epoch):train_loss 0.0count 0for i, data in enumerate(train_loader, 0):inputs, labels datay_pred model(inputs)loss criterion(y_pred, labels)optimizer.zero_grad()loss.backward()optimizer.step()train_loss loss.item()count iif epoch % 2000 1999:print(train loss:, train_loss / count, end,)def test(): with torch.no_grad(): # test是不需要计算梯度的 2023.2.18 11:49。那么下面的执行 就不再计算梯度y_pred model(Xtest)y_pred_label torch.where(y_pred 0.5, torch.tensor([1.0]), torch.tensor([0.0]))acc torch.eq(y_pred_label, Ytest).sum().item() / Ytest.size(0)print(test acc:, acc)if __name__ __main__:for epoch in range(50000):train(epoch)if epoch % 2000 1999:test()故一共执行了25次测试这需要一些时间大概几分钟 至于优化大家可以去调试参数同练习(1)。这里就不再冗余了。后面有时间的话继续补充~
1.7.3 练习3使用GPU训练
基于练习2在拆分数据集为训练集和测试集的基础上划分为 比例 0.8的训练集0.2的测试集。此外使用GPU训练。
# 昵 称:XieXu
# 时 间: 2023/2/18/0018 10:18
import numpy as np
import torch
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
from sklearn.model_selection import train_test_split
import pdb# 定义一个cpu
device torch.device(cuda:0 if torch.cuda.is_available() else cpu)# 读取原始数据集并且划分为训练集和测试集
xy np.loadtxt(diabetes.csv, delimiter,, dtypenp.float32)
x xy[:, :-1]
y xy[:, [-1]]
x_train, x_test, y_train, y_test train_test_split(x, y, test_size0.3)
x_test torch.from_numpy(x_test).to(device)
y_test torch.from_numpy(y_test).to(device)# prepare dataset
class DiabetesDataset(Dataset):def __init__(self, data, labels):# xy np.loadtxt(filepath, delimiter ,, dtype np.float32)self.len data.shape[0] # shape(行列)self.x_data torch.from_numpy(data)self.y_data torch.from_numpy(labels)def __getitem__(self, index):return self.x_data[index], self.y_data[index]def __len__(self):return self.lentrain_dataset DiabetesDataset(x_train, y_train)
train_loader DataLoader(datasettrain_dataset, batch_size32, shuffleTrue, num_workers0) # num_workers 多线程# design model using class
class Model(torch.nn.Module):def __init__(self):super(Model, self).__init__()self.linear1 torch.nn.Linear(8, 6)self.linear2 torch.nn.Linear(6, 4)self.linear3 torch.nn.Linear(4, 1)self.sigmoid torch.nn.Sigmoid()def forward(self, x):x self.sigmoid(self.linear1(x))x self.sigmoid(self.linear2(x))x self.sigmoid(self.linear3(x))return xmodel Model()
model.to(device)# construct loss and optimizer
criterion torch.nn.BCELoss(reductionmean)
optimizer torch.optim.SGD(model.parameters(), lr0.01)# optimizer torch.optim.Adam(model.parameters(),
# lr0.001,
# betas(0.9, 0.999),
# eps1e-08,
# weight_decay0,
# amsgradFalse)# training cycle forward, backward, update
def train(epoch):train_loss 0.0count 0.0for i, data in enumerate(train_loader, 0): # start 0train_loader 是先shuffle后mini_batch# inputs, labels datainputs, labels data[0].to(device), data[1].to(device) # 使用gpu训练y_pred model(inputs)loss criterion(y_pred, labels)# print(epoch, i, loss.item())optimizer.zero_grad()loss.backward()optimizer.step()train_loss loss.item()count iif epoch % 1000 999:# pdb.set_trace()print(epoch:, epoch 1, train loss:, train_loss / count, end,)def test():# 在with torch.no_grad()下对变量的操作均不会让求梯度为真with torch.no_grad():y_pred model(x_test)y_pred_label torch.where(y_pred 0.5, torch.tensor([1.0]).to(device), torch.tensor([0.0]).to(device))acc torch.eq(y_pred_label, y_test).sum().item() / y_test.size(0)print(test acc:, acc)if __name__ __main__:for epoch in range(50000):train(epoch)if epoch % 1000 999: # 每训练 1000轮就执 行一次测试test()在训练过程中可以 通过nvidia-smi查看一下确实在使用GPU训练 也可以这样看看 训练结果如下这需要一些时间由于每1000轮训练就会执行一次测试共训练50000轮则会测试50次所需的时间会比练习2要长。建议大家调试时可以减少训练轮数或者增加测试 所需的 训练间隔轮数等。以我目前的PC大概Run30分钟。 程序运行结束也就不再占用GPU了 1.8 多分类问题——交叉熵损失函数CrossEntropyLossSoftmax分类器One-hot针对MNIST数据集
1.8.1 针对多分类问题输出的概率 应满足“分布”的要求
回顾:
在此之前我们针对糖尿病数据集搭建了一个二分类网络。y^\widehat {y}y输出的是 当y1时的概率值。因为只有两个分类所以只需要 输出一个概率就行另外一个概率用1减去 当前求得的y1y1y1时的概率就好。
之前的网络模型 而面对 像手写数字识别(MNIST数据集)这样的 多分类问题(0~9共10个数字即10分类)我们在解决时会用到softmax分类器。
本小节要解决的问题是
[1] 用softmax分类器来解决多分类问题[2] 在PyTorch中如何去实现
针对多分类问题输出的概率 应满足“分布”的要求:
一个思路是是采用之前的【二分类】来解决。即 将每一个分类作为二分类来判断。
例如 当输出为1时对其他的 非1输出 都规定为0以此来判断属于哪个类别。 但是这种情况下类别之间 所存在的互相抑制的关系没有办法体现当一个类别出现的概率较高时其他类别出现的概率仍然有可能很高。
也就是说当计算输出为1的概率之后再计算输出为2的概率时并不是在输出为 非1的条件下进行的即 计算输出为1的概率并没有影响再计算输出为其它的概率即 所有输出的概率之和实际上是大于1的。【有朋友说每一个都看成二分类是在单独计算每一种类别的概率 各个类别判断之间 没有太多联系】
这里我们希望输出的概率满足 “分布”的性质要求。(通过Softmax就可完成)
第一y^1\widehat y_{1}y1到y^10\widehat y_{10}y10的输出每一个 都得大于或等于0; 即 P(yi)≥0P(yi) \geq 0P(yi)≥0第二y^1\widehat y_{1}y1到y^10\widehat y_{10}y10 这些概率求和等于1。即 ∑i09P(yi)1\sum_{i0}^{9} P(yi) 1∑i09P(yi)1
这样才满足离散分布的要求。才真正算出了 样本属于 各个分类的 概率分布。
对于分类问题输出的 是个分布二分类里 之所以 输出一个就行是因为 只需要 y1y1y1的概率就行了y0y0y0的概率可以用1减 1.8.2 Softmax 计算公式
----Softmax 其公式 就是用来将多个Linear之后得到的值 (多个类别 对应的值是过程值不是最终值)进行映射只是映射为[0,1]的值且这些值 加和1。----简言之通过Softmax 得到多分类问题 中 概率分布 ZiZ_iZi表示最后一层线性层的输出(各个类别 经过多层Linear 以及Sigmoid之后的过程值)
softmax函数为
P(yi)ezi∑j0K−1ezi,i∈{0,⋯,K−1}P(yi)\frac{e^{z_i}}{\sum_{j0}^{K-1}e^{z_i}}, i \in \{0,{\cdots},K-1\}P(yi)∑j0K−1eziezi,i∈{0,⋯,K−1}
分子是 以e为底的指数函数取值是大于0的。也就是说将每个类别 经过这些Linear层最终得到的值当然这里边是有正有负的通过上面的公式映射为一个0~1的数(所属各个类别(分类)的概率)而它们加起来 和为1。刚刚说过了又啰嗦一下啦 ~
例如就像下面这样 若有3个分类(注最后一层不使用sigmoid了而是 直接输入到softmax函数中。因此softmax 的输入可能有负值但没关系直接执行即可) 这样才是最终的 y^\widehat yy。
小结
softmax的 输入参数 不需要 再做非线性变换即softmax之前不再需要激活函数(relu)。 softmax两个作用
1、如果在进行softmax前的input有负数通过指数变换得到正数2、使所有类别的概率求和为1归一化/映射为0~1的值。
对于多分类问题输出Softmax会先对所有输出进行指数运算以满足1要求(得到正数)再对结果进行归一化处理以满足2要求。
1.8.3 通过softmax得到概率分布后损失函数Loss如何做
这里先不做过多解释直接给出损失函数
Loss(Y^,Y)−YlogY^Loss(\widehat Y,Y) -Ylog \widehat YLoss(Y,Y)−YlogY
由公式 结合 下图知对通过softmax得到的概率分布再对这些概率取对数然后与编码进行运算即可。显然只有一项取标签为1的其它都是与标签0相乘。 例如 模拟实现如下可以看到 loss输出如 注释。给出了通过softmax计算得到概率分布 以及 如何计算损失的过程。【实际处理的时候0项会直接忽略掉。如若类别为1那么会直接找到类别为1的索引取对数再进行Loss计算即可】
# 昵 称:XieXu
# 时 间: 2023/2/18/0018 21:58
# 测试实现import numpy as npy np.array([1, 0, 0])
z np.array([0.2, 0.1, -0.1])y_pred np.exp(z) / np.exp(z).sum() # 即 softmax的过程# print(y_pred) # 即概率分布 [0.37797814 0.34200877 0.28001309]loss (-y * np.log(y_pred)).sum() # 即 损失函数print(loss) # 0.9729189131256584闲话由于是多分类用Softmax替换之前普通的Sigmoid激活函数
1.8.4 交叉熵损失CrossEntropyLoss Softmax 原Loss(即 −YlogY^-Y log\widehat Y−YlogY)
值得注意的是上述代码 封装在 CrossEntropyLoss()函数中即可直接调用
因此在使用交叉熵损失的时候注意 神经网络的最后一层 是不要做激活的因为 将其变为概率分布的激活(Softmax) 是包含在 CrossEntropyLoss 中的!(Linear最后一层的结果 直接传入 CrossEntropyLoss 内做激活啦得到概率分布再进行log然后求损失…)
故最后一层不用做非线性变换直接交给交叉熵损失就行啦! import torchy torch.LongTensor([0]) # 老师解释这里选的是第0个分类。我测试过这里和我们设定的值有关可以填0/1/2一共就三类填3及以上就会报错
z torch.Tensor([[0.2, 0.1, -0.1]])
criterion torch.nn.CrossEntropyLoss()
loss criterion(z, y)
print(loss) # tensor(0.9729)这里再看一个具体的例子结合我写的注释理解 即可~
# 2023.2.18 23:25
import torch
criterion torch.nn.CrossEntropyLoss()
Y torch.LongTensor([2, 0, 1]) # 有3个样本分别属于 第2类 第0类 第1类# 下面有两组不同的预测
Y_pred1 torch.Tensor([[0.1, 0.2, 0.9], # 0.9最大经过softmax之后也会比较大 符合第2类
[1.1, 0.1, 0.2], # 1.1最大同理符合第0类
[0.2, 2.1, 0.1]]) # 2.1最大同理符合 第1类
Y_pred2 torch.Tensor([[0.8, 0.2, 0.3], # 第0类 这与实际的Y 不符
[0.2, 0.3, 0.5], # 第2类 这也与实际的Y不符
[0.2, 0.2, 0.5]]) # 第2类 这也与实际的Y不符
l1 criterion(Y_pred1, Y)
l2 criterion(Y_pred2, Y)
print(Batch Loss1 , l1.data, \nBatch Loss2, l2.data)输出如下:
Batch Loss1 tensor(0.4966) # 可以从上边的样本看出loss是比较小的因为样本的取值大小 是和 上面Y的类别 符合的
Batch Loss2 tensor(1.2389) # 而这里损失就大一些。结合上边第二个预测确实和实际的Y不符有差别所以损失大~附交叉熵损失 和 NLLLoss之间有什么差别可参阅文档如下
https://pytorch.org/docs/stable/nn.html#crossentropylosshttps://pytorch.org/docs/stable/nn.html#nllloss
取决于需求有时候会用NLLLoss有时候会直接用交叉熵CrossEntropyLoss。 1.8.5 有了上面的分析这里如何处理MNIST数据集呢
上面几个小节 记录过的神经网络中输入XXX都是向量。这里的数据集是图像数据。下面是MNIST数据集中的一张手写数字的图像。【如下左图颜色越深就表明数值是0越亮的地方就表明数值越大。这在右侧图中可以看出来】 每个数字都是一个28∗2878428*2878428∗28784大小的灰度图784个像素每个像素的取值是0~255可将灰度图中的每个像素值映射到(0,1)(0,1)(0,1)区间内0即0255即1就是上面的右图 28∗2828*2828∗28的矩阵。数值大的 用深颜色数值小一些的用浅颜色。
导入一些工具包 维度 由28∗2828*2828∗28 转换为1∗28∗281*28*281∗28∗28形式的张量(Tensor)即由单通道变为多通道将(0,255) 转换为 (0,1)浮点数的形式即0 1 分布 以供神经网络进行训练。【0 1 分布的数据给神经网络训练是最好的】
并且定义好transform变换以后直接 将其放到数据集里面读取第i个数据样本的时候拿到的数据会直接 经transform处理 即通道放前边为了 更高效地 进行图像处理、卷积运算而做的转换~ 如 四六级成绩的计算假设希望将成绩映射为0,1的正态分布。【0,1分布指的是 均值为0标准差为1的正态分布。N(0,1)是标准正态分布】 附百度知道——N(0,1)分布是什么分布
1.8.6 模型
激活层改用ReLU激活函数 最后的输出层不做激活因为是由交叉熵损失去计算softmax。 此外还要通过view 转换为矩阵。
具体地如下图下图中NNN可理解为batch_size 交叉熵损失 梯度下降中使用“冲量”momentum来优化训练过程。大概作用冲破局部极小值给数据一个惯性值可以从局部极值走出来尽可能找到全局最优解 封装训练过程 每300次迭代输出一次running_loss。 test里不需要进行反向传播不需要计算梯度 只需要算正向的算一下分类算对了多少~ 具体如下 1.8.7 练习1未使用GPU默认CPU
# 昵 称:XieXu
# 时 间: 2023/2/18/0018 11:58
# 针对手写数字数据集MNISTimport torch
# 组建DataLoader
from torchvision import transforms # 针对 图像 进行处理 2023.2.18 23:57
from torchvision import datasets
from torch.utils.data import DataLoader
# 激活函数和优化器
import torch.nn.functional as F # For using function relu() 2023.2.18 23:58
import torch.optim as optim# DatasetDataloader必备
batch_size 64
# pillowPIL读的原图像格式为W*H*C原值较大
# 转为格式为C*W*H值为0-1的Tensor
transform transforms.Compose([# 变为格式为C*W*H的Tensortransforms.ToTensor(),# 第一个是均值第二个是标准差变值为0-1transforms.Normalize((0.1307,), (0.3081,))
])train_dataset datasets.MNIST(root../dataset/mnist/,trainTrue,downloadTrue,transformtransform)train_loader DataLoader(train_dataset, shuffleTrue, batch_sizebatch_size)test_dataset datasets.MNIST(root../dataset/mnist/,trainFalse,downloadTrue,transformtransform)
test_loader DataLoader(test_dataset, shuffleFalse, batch_sizebatch_size)class Net(torch.nn.Module):def __init__(self):super(Net, self).__init__()# 线性层1input784维 output512维self.l1 torch.nn.Linear(784, 512)# 线性层2input512维 output256维self.l2 torch.nn.Linear(512, 256)# 线性层3input256维 output128维self.l3 torch.nn.Linear(256, 128)# 线性层4input128维 output64维self.l4 torch.nn.Linear(128, 64)# 线性层5input64维 output10维self.l5 torch.nn.Linear(64, 10)def forward(self, x):# 改变张量形状view\reshape# view 只能用于内存中连续存储的Tensortranspose\permute之后的不能用# 变为二阶张量矩阵-1用于计算填充batch_sizex x.view(-1, 784)# relu 激活函数x F.relu(self.l1(x))x F.relu(self.l2(x))x F.relu(self.l3(x))x F.relu(self.l4(x))# 注第五层 不可以 再进行relu激活 2023.2.18 19:33。交叉熵损失 CrossEntropyLoss 已经包含激活啦# 第五层 不用再 显式地 用激活函数 处理 映射了~# 交叉熵损失 CrossEntropyLoss 已经包含激活 对五层的值 激活啦softmax得概率分布呗之后再求损失也是封装到CrossEntropyLoss内了# 即 第五层(最后一层) 的结果直接 送到 CrossEntropyLoss 求损失函数即可 2023.2.18 22:46return self.l5(x) # 因此直接将 第五层得到的结果 送入CrossEntropyLoss即可后续再其内 会激活(Softmax)滴 2023.2.18 22:49model Net()# LossOptimizer
# 交叉熵损失
criterion torch.nn.CrossEntropyLoss()
# 随机梯度下降momentum表冲量在更新时一定程度上保留原方向
optimizer optim.SGD(model.parameters(), lr0.01, momentum0.5)def train(epoch):running_loss 0.0# 提取数据for batch_idx, data in enumerate(train_loader, 0):inputs, target data# 优化器清零optimizer.zero_grad()# 前馈反馈更新outputs model(inputs)loss criterion(outputs, target)loss.backward()optimizer.step()# 累计lossrunning_loss loss.item()if batch_idx % 300 299:print([%d, %5d] loss: %.3f % (epoch 1, batch_idx 1, running_loss / 300))running_loss 0.0def test():correct 0total 0# 避免计算梯度with torch.no_grad():for data in test_loader:images, labels dataoutputs model(images)# 取每一行dim1表第一个维度最大值max的下标(predicted)及最大值(_)_, predicted torch.max(outputs.data, dim1)# 加上这一个批量的总数batch_sizelabel的形式为[N,1]total labels.size(0)correct (predicted labels).sum().item()print(Accuracy on test set: %d %% % (100 * correct / total))if __name__ __main__:for epoch in range(10):train(epoch)test()初次Run时会下载MNIST数据集也可以自己事先准备好 看一下执行的结果最后正确率 到了97%.
到了97%很难再上升了。这是由于 全连接的神经网络 对于图像处理分析对局部信息的利用 做的并不是非常好。所有的元素都做了全连接即 图像中的一个元素 与其它任何一个位置的元素 都要产生联系…权重不够多在处理图像的时候我们更关注 高抽象级别的特征而这里我们用的是 非常原始的特征。因此 用某些特征提取做分类训练可能效果会更好一些或者自动进行特征提取如CNN CPU/内存使用情况 1.8.8 练习2使用GPU
与练习1类似仅仅添加/修改了使用GPU的代码。
# 昵 称:XieXu
# 时 间: 2023/2/18/0018 21:15
# 使用GPU加速# 尚未执行 手写字符识别pytorch实现
import torch
from torchvision import transforms, datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F
from tqdm import tqdm# 定义一个cpu
device torch.device(cuda:0 if torch.cuda.is_available() else cpu)# prepare dataset
batch_size 64
# 图像预处理
transform transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.1307,), (0.3081,))]) # 归一化两个值是均值和方差train_dataset datasets.MNIST(root../dataset/mnist/, trainTrue, downloadTrue, transformtransform)
train_loader DataLoader(train_dataset, shuffleTrue, batch_sizebatch_size)
test_dataset datasets.MNIST(root../dataset/mnist/, trainFalse, downloadTrue, transformtransform)
test_loader DataLoader(test_dataset, shuffleFalse, batch_sizebatch_size)# design model using class
class Net(torch.nn.Module):def __init__(self):super(Net, self).__init__()self.l1 torch.nn.Linear(28 * 28, 512)self.l2 torch.nn.Linear(512, 256)self.l3 torch.nn.Linear(256, 128)self.l4 torch.nn.Linear(128, 64)self.l5 torch.nn.Linear(64, 10)def forward(self, x):x x.view(-1, 784) # 将N*1*28*28的图片转换成N*1*784-1代表N的值即自动获取mini_batchx F.relu(self.l1(x))x F.relu(self.l2(x))x F.relu(self.l3(x))x F.relu(self.l4(x))return self.l5(x) # 最后一层不做激活,直接接到softmaxmodel Net()
model.to(device)# construct loss and optimizer
criterion torch.nn.CrossEntropyLoss()
optimizer torch.optim.SGD(model.parameters(), lr0.01, momentum0.5)# training cycle forward, backward, update
def train(epoch):running_loss 0.0for batch_idx, data in enumerate(tqdm(train_loader), 0): # tqdm是一个快速的易扩展的进度条提示模块 2023.2.19 01:50inputs, labels data[0].to(device), data[1].to(device) # 使用gpu训练optimizer.zero_grad()# forward backward optimize# 获得模型预测结果(64, 10)outputs model(inputs)# 交叉熵代价函数outputs(64,10),target64loss criterion(outputs, labels)loss.backward()optimizer.step()running_loss loss.item()if batch_idx % 300 299: # 输出每次的平均lossprint(\n [%d, %5d] loss: %.3f % (epoch 1, batch_idx 1, running_loss / 300))running_loss 0.0def test():correct 0total 0with torch.no_grad():for data in test_loader:images, labels data[0].to(device), data[1].to(device)outputs model(images)_, predicted torch.max(outputs.data, dim1) # 并不关心最大值是多少用下划线来存需要在意的是index# dim1表示输出所在行的最大值若改写成dim0则输出所在列的最大值。total labels.size(0) # N*1correct (predicted labels).sum().item() # 张量之间的比较运算print(accuracy on test set: %d %% % (100 * correct / total))if __name__ __main__:for epoch in range(10):train(epoch)test()nvidia-smi查看运行过程中是否使用了GPU CPU和内存情况比起使用CPU来运行这样好多了CPU占用很少了~ 另一种查看GPU的使用 结果大概需要1、2分钟吧 正确率依然是97%. 1.9 卷积神经网络CNN基础篇
1.9.1 基础概念卷积、卷积运算过程、单通道卷积多通道卷积、卷积层 权重www的四个维度
CNN比起一维信息的Linear线性层可以保留图像的空间特征。
卷积神经网络即 Convolutional Neural Network。
在上一目录介绍了全连接神经网络。所谓全连接即网络中使用的全都是线性层Linear。如果网络是由全连接层串行起来就称之为全连接网络。
每一个输入节点都要参与到下一层的计算上。这样也叫全连接Fully Connected。作为下一层而言每一个输入值 和 每一个输出值之间都存在权重。 在全连接层中实际上是把原先空间状态上的信息转换为了一维的信息使得 原有的 空间相对位置 所蕴含的信息丢失。而卷积神经网络中可以保留图像的空间特征(信息)。
卷积实际上是把 原始图像 仍然按照 空间的结构 来进行保存数据。
特征提取阶段直接对图像做 卷积运算提取特征转换为向量经过全连接神经网络再做分类 卷积
1×28×28 指的是C(channle)×W(width)×H(Hight)C(channle) \times W(width) \times H(Hight)C(channle)×W(width)×H(Hight)
即通道数 ×\times× 图像宽度 ×\times× 图像高度通道可以理解为层数通过同样大小的多层图像堆叠才形成了最原始的图。
我们拿到的图像要分成红、绿、蓝三个通道即Channel。
如下图所示对一个3×W×H3 \times W \times H3×W×H的原始图像卷积的处理是每次对其中一个Patch进行处理也就是从原数图像的左上角开始依次抽取一个3×W′×H′3 \times W \times H3×W′×H′的图像对其进行卷积输出一个C′×W′′×H′′C \times W \times HC′×W′′×H′′的子图。 可以抽象的理解成原先的图是一个立方体性质的卷积是将立方体的长宽高按照新的比例进行重新分割而成的 卷积运算过程
单通道卷积(1×W×H1 \times W \times H1×W×H)即 对于规格为1×W×H1 \times W \times H1×W×H的原图利用一个规格为1×W′×H′1 \times W \times H1×W′×H′的卷积核进行卷积处理的数乘操作。
从原始数据的左上角开始 依次选取与核的规格相同(1×W′×H′1 \times W \times H1×W′×H′)的输入数据进行数乘操作并将求得的数值作为一个Output值进行填充。 …依次类推可得到Output 多通道卷积(N×W×HN \times W \times HN×W×H)通常是许多个通道。以三通道为例红、绿、蓝 三个通道分别和不同的卷积核做卷积每个通道 都配一个核~所以通道数和卷积核的数目 是相同的先是得到如下结果 算出来的三个矩阵再做加法 此过程就称为 卷积运算 把它们挪到一块儿进一步将平面的图像转为立体的角度观察 亦即 因此同样的道理若输入是NNN个通道卷积核的数目也是nnn个即n×3×3n \times 3 \times 3n×3×3的张量输出的通道数为111。 若想要 输出的 channelmchannelmchannelm类比上边只需要 让输入 的channel 再与 卷积核 运算这样就又可以得到 一个输出channel啦如下图举例 故准备mmm 批(组) 卷积核这样的卷积核就得到输出的channel数为mmm啦就可以得到mmm个输出通道。 进一步地把得到的这些通道 拼接(Cat)起来按照得到通道的顺序依次拼接起来就好 上图中
每个卷积核通道数量 要求和 输入数量是一样的这种卷积核数量有多少个是和 输出通道数 是一样的 至于卷积核大小是3x3的、5x5、还是7x7的等那是自己定义的这和图像大小是没关系的。
“卷积核就是这么大的”在对每一个图像块 做运算时用的都是相同的卷积核即“共享权重”的机制。
进一步地可将m个卷积核 拼成一个4维的张量 因此构建卷积层其权重 要包括四个维度输出维度、输入维度、卷积核宽度、卷积核高度。
小结
1.9.2 CNN计算过程示例
卷积计算示例
# 昵 称:XieXu
# 时 间: 2023/2/19/0019 15:14
# 卷积计算 示例import torch# 即 n,m
in_channels, out_channels 5, 10 # 定义 input channel输入通道数、output channel 输出通道数 2023.2.19 15:15width, height 100, 100 # 图像大小kernel_size 3 # 卷积核大小默认转为3*3最好用奇数正方形。PyTorch中 卷积运算用偶数也可 2023.2.19 15:32# 在PyTorch中所有的输入数据必须是 小批量数据 2023.2.19 15:17
# 因此对于C*W*H的三个维度图像在代码中实际上是一个Bbatch*C*W*H的四个维度的图像
batch_size 1# 生成一个四维的随机数满足正态分布采样的 随机数 2023.2.19 15:19
input torch.randn(batch_size, # batch 小批量in_channels, # nwidth, # wheight) # h# Conv2d必须 设定输入输出的通道数以及卷积核尺寸
conv_layer torch.nn.Conv2d(in_channels, # 输入通道的数量 nout_channels, # 输出通道的数量 mkernel_sizekernel_size) # 卷积核大小(尺寸) 如3*3。也可以自己指定元组 如(5,5)等output conv_layer(input)print(input.shape)
print(output.shape)
print(conv_layer.weight.shape)1.9.3 改进——padding一般 外围补0
开始时输入5x5维度经过3x3的卷积核我们能得到输出的维度 是3x3。 如果还是想要得到5x5的维度就需要在原始的输入外围进行填充。
即若对于一个大小为N×NN \times NN×N的原图经过大小为M×MM \times MM×M的卷积核卷积后仍然想要得到一个大小为N×NN \times NN×N的图像则需要对原图进行Padding即外围填充。
具体而言对于一个5×55 \times 55×5的原图若想使用一个3×33 \times 33×3的卷积核进行卷积并获得一个同样5×55 \times 55×5的图像则需要进行Padding1即填充一圈通常外围填充0。 填充一圈0并计算的结果 若卷积核大小为5x5的那就得再填充一圈才能保持计算之后输出的维度和输入的维度同~
一般情况下
卷积核3x3——padding1圈3/21整除哈卷积核5x5——padding2圈5/22整除哈是一个这种规律这样算即可
对于上图的实现
# 昵 称:XieXu
# 时 间: 2023/2/19/0019 16:12
# padding练习import torchinput [3, 4, 6, 5, 7,2, 4, 6, 8, 2,1, 6, 7, 8, 4,9, 7, 4, 6, 2,3, 7, 5, 4, 1]# 将输入变为B*C*W*H
input torch.Tensor(input).view(1, 1, 5, 5) # 1 1 5 5 ,分别是batchChannelWidthHeight 2023.2.19 16:14# 偏置量bias置为false。输入 和 输出 都只有1个通道~
conv_layer torch.nn.Conv2d(1, 1, kernel_size3, padding1, biasFalse) # bias 会在卷积后 给 每个通道 加偏置量但这里不需要加偏置# 将卷积核变为CI*CO*W*H
kernel torch.Tensor([1, 2, 3, 4, 5, 6, 7, 8, 9]).view(1, 1, 3, 3) # 1 1 3 3 分别是输出通道数、输入通道数、宽度、高度# 将做出来的 卷积核张量赋值 给 卷积运算中的 权重参与卷积计算
conv_layer.weight.data kernel.dataoutput conv_layer(input)print(output)
# print(output.data) # 亦可结果如下与上图中是一样的。 卷积本质上依然是 线性计算.
1.9.4 改进——步长 stride
【可以有效降低feature map 的宽度和高度】
初始 步长为2则 往下走也会跳一格 这样5x5 的 就 变为 2x2的了 练习
依然是针对上面1.9.3的代码不添加padding添加stride2即
conv_layer torch.nn.Conv2d(1, 1, kernel_size3, stride2, biasFalse) # stride步长练习结果 1.9.5 改进——下采样(又叫“池化”)一般采用 Max Pooling Layer
下采样用的比较多的一般可采用“最大池化”Max Pooling Layer。
最大池化层是没有权重的。
如使用 2x2的 Max Pooling 注意 它默认 stride2。 做 Max Pooling 时只是对于通道做 最大池化通道之间不会去找最大值。 故 通道数量不变。但如若使用 2x2 的 MaxPooling图像大小会变为原来一半~
练习
# 昵 称:XieXu
# 时 间: 2023/2/19/0019 17:16# 最大池化 MaxPooling 练习import torchinput [3, 4, 6, 5,2, 4, 6, 8,1, 6, 7, 8,9, 7, 4, 6]input torch.Tensor(input).view(1, 1, 4, 4)# kernel_size2 则 MaxPooling中的Stride也会被 默认设置为2
maxpooling_layer torch.nn.MaxPool2d(kernel_size2)output maxpooling_layer(input)print(output)1.9.6 针对MNIST数据集所采用的模型
整体上采用的CNN模型如下 卷积本质上 也是线性运算不加激活函数的话多层卷积 相当于 多层线性运算没有意义。即 加入激活函数 是为了引入非线性(因素)让网络更复杂处理提取的特征更多
下图简言之卷积以后先ReLU再做 最大池化再卷积ReLU激活最大池化。view改变形状再接 全连接层 降到10维之后就和数字图像的输出对应上了然后传到 交叉熵损失函数获得损失进行训练。
下图右边的代码里是先做的池化MaxPooling而后ReLU和左图中相反了这应该没关系在结果上区别不大下边我也测试过了。有同学说先池化后ReLU计算量小一些还有同学说确实区别不大只要激活函数在两次卷积之间就好了 模型就是上面指出的。
下面考虑将模型在GPU上来跑。
1.9.7 如何在GPU上来跑怎么用显卡来计算
【把运算迁移到GPU上】
首先是把模型迁移到GPU上
定义device 模型迁移到GPU即 将整个模型的参数、缓存以及所有的模块 都放到cuda里面转成cuda Tensor。 把用来计算的张量 也迁移到GPU
即 输入 和 对应输出注意 迁移的device要和模型的device 要在同一块显卡上 测试里面也添加
1.9.8 练习1
未使用GPU
基于前几个目录这里注释就不写太多了可以自己看着理解一下不明白的再往上翻…
# 昵 称:XieXu
# 时 间: 2023/2/19/0019 17:37
import torch
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F
import torch.optim as optim# prepare datasetbatch_size 64
transform transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])train_dataset datasets.MNIST(root../dataset/mnist/, trainTrue, downloadTrue, transformtransform)
train_loader DataLoader(train_dataset, shuffleTrue, batch_sizebatch_size)
test_dataset datasets.MNIST(root../dataset/mnist/, trainFalse, downloadTrue, transformtransform)
test_loader DataLoader(test_dataset, shuffleFalse, batch_sizebatch_size)# design model using classclass Net(torch.nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 torch.nn.Conv2d(1, 10, kernel_size5)self.conv2 torch.nn.Conv2d(10, 20, kernel_size5)self.pooling torch.nn.MaxPool2d(2)self.fc torch.nn.Linear(320, 10)def forward(self, x):# flatten data from (n,1,28,28) to (n, 784)batch_size x.size(0) # 张量.size把维度拿出来 2023.2.19 19:19x F.relu(self.pooling(self.conv1(x)))x F.relu(self.pooling(self.conv2(x)))x x.view(batch_size, -1) # -1 此处自动算出的是320# view()函数用来转换size大小x x.view(batch_size, -1)中batchsize指转换后有几行# 而-1根据原Tensor数据和batchsize自动分配列数~ 2023.2.19 19:27x self.fc(x)return xmodel Net()# construct loss and optimizer
criterion torch.nn.CrossEntropyLoss()
optimizer optim.SGD(model.parameters(), lr0.01, momentum0.5)# training cycle forward, backward, updatedef train(epoch):running_loss 0.0for batch_idx, data in enumerate(train_loader, 0):inputs, target dataoptimizer.zero_grad()outputs model(inputs)loss criterion(outputs, target)loss.backward()optimizer.step()running_loss loss.item()if batch_idx % 300 299:print([%d, %5d] loss: %.3f % (epoch 1, batch_idx 1, running_loss / 300))running_loss 0.0def test():correct 0total 0with torch.no_grad():for data in test_loader:images, labels dataoutputs model(images)_, predicted torch.max(outputs.data, dim1)total labels.size(0)correct (predicted labels).sum().item()print(accuracy on test set: %d %% % (100 * correct / total))if __name__ __main__:for epoch in range(10):train(epoch)test()
# accuracy on test set: 98 % 结果如下正确率 98%.在目录 1.8中使用全连接Linear来跑的时候准确率是97%看来这样提升了一丢丢 企业级理解如下【老师讲到不要去从 正确率的角度说要从 错误率的角度说错误率 由原来的3% 降到了 2%这是降低一个百分点嘛不是足足降低了1/3错误率降低成 原来的2/3有了30%多的改进】够 “吹” 的啦
上述代码中forward()里先池化后ReLU。
这里再记录一下 先ReLU激活 后池化的效果即 def forward(self, x):batch_size x.size(0)x self.pooling(F.relu(self.conv1(x))) # 先ReLU 后池化也行~x self.pooling(F.relu(self.conv2(x)))x x.view(batch_size, -1)x self.fc(x)return x嗯…先ReLU激活后池化效果和之前差不多也是98%所以都行的~ 1.9.9 练习2使用GPU来跑
基于目录1.9.7中如何添加GPU来跑只是对1.9.8的代码 稍微添加即可 device torch.device(cuda if torch.cuda.is_available() else cpu)
model.to(device)inputs, target inputs.to(device), target.to(device) # 添加该行 2023.2.19 20:16images, labels images.to(device), labels.to(device) # 添加改行
完整code如下含绘图
# 昵 称:XieXu
# 时 间: 2023/2/19/0019 19:38
# 在GPU上来跑import torch
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F
import torch.optim as optim
import matplotlib.pyplot as plt# prepare datasetbatch_size 64
transform transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])train_dataset datasets.MNIST(root../dataset/mnist/, trainTrue, downloadTrue, transformtransform)
train_loader DataLoader(train_dataset, shuffleTrue, batch_sizebatch_size)
test_dataset datasets.MNIST(root../dataset/mnist/, trainFalse, downloadTrue, transformtransform)
test_loader DataLoader(test_dataset, shuffleFalse, batch_sizebatch_size)# design model using classclass Net(torch.nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 torch.nn.Conv2d(1, 10, kernel_size5)self.conv2 torch.nn.Conv2d(10, 20, kernel_size5)self.pooling torch.nn.MaxPool2d(2)self.fc torch.nn.Linear(320, 10)def forward(self, x):# flatten data from (n,1,28,28) to (n, 784)batch_size x.size(0)x F.relu(self.pooling(self.conv1(x)))x F.relu(self.pooling(self.conv2(x)))x x.view(batch_size, -1) # -1 此处自动算出的是320# print(x.shape,x.shape)x self.fc(x)return xmodel Net()
device torch.device(cuda if torch.cuda.is_available() else cpu) # 添加改行
model.to(device) # 添加改行# construct loss and optimizer
criterion torch.nn.CrossEntropyLoss()
optimizer optim.SGD(model.parameters(), lr0.01, momentum0.5)# training cycle forward, backward, updatedef train(epoch):running_loss 0.0for batch_idx, data in enumerate(train_loader, 0):inputs, target datainputs, target inputs.to(device), target.to(device) # 添加该行 2023.2.19 20:16optimizer.zero_grad()outputs model(inputs)loss criterion(outputs, target)loss.backward()optimizer.step()running_loss loss.item()if batch_idx % 300 299:print([%d, %5d] loss: %.3f % (epoch 1, batch_idx 1, running_loss / 300))running_loss 0.0def test():correct 0total 0with torch.no_grad():for data in test_loader:images, labels dataimages, labels images.to(device), labels.to(device) # 添加改行outputs model(images)_, predicted torch.max(outputs.data, dim1)total labels.size(0)correct (predicted labels).sum().item()print(accuracy on test set: %d %% % (100 * correct / total))return correct / total # 返回测试集的 正确率 2023.2.19 20:20if __name__ __main__:epoch_list [] # 画图用横轴坐标 2023.2.19 20:19acc_list [] # 纵轴坐标for epoch in range(10):train(epoch)acc test() # 得到该轮 的正确率~epoch_list.append(epoch) # 横轴acc_list.append(acc) # 纵轴plt.plot(epoch_list, acc_list)plt.ylabel(accuracy)plt.xlabel(epoch)plt.title(accuracy with epoch)plt.show()查看GPU使用情况 来看结果 是的结果 accuracy也是98%. 1.10 卷积神经网络CNN高级篇
1.10.1 背景引出更复杂的网络结构非串行的
在上面曾介绍过的 卷积神经网络CNN、多层感知机 全连接网络它们在网络架构上是串行的结构即当前层的输出 是 下一层的输入。
但是在神经网络中有很多更为复杂的结构在网络结构中会有分支甚至说 某层的输出 可能还会 回过头来 继续作为输入。 回顾上一节2个卷积层2个池化层后接 全连接层然后得到输出~ 事实上上述网络结构是非常近似于 LeNet5网络结构的。
下面介绍复杂一些的网络结构不是串行的网络结构。
1.10.2 非串行网络结构 之 GoogLeNet
GoogLeNet网络结构包括卷积Convolution池化Pooling、全连接Softmax以及连接Other四个部分。 看着是有些复杂代码会定义很多…
那么根据编程经验减少代码的冗余第一要减少代码中的重复重复多了有可能出错
减少代码冗余在 过程式编程泛型里如C语言中 的“函数” 可以作为减少代码冗余的工具在面向对象里面我们可以通过构造 类 来减少代码冗余。
因此观察上图中的模型看有没有相似或相同的结构。
显然是有的就像下面标注的 “蓝 蓝 红四个蓝”好多都是这种样式的 因此把它们封装起来就OK啦
即 把这种块 封装成一个类然后再把 这些块 串起来这样就能极大减少代码里 重复的工作。
1.10.3 GoogLeNetInception ModuleAverage Pooling1×11\times11×1卷积
在GoogLeNet中把上述画起来的 那种块 叫做 Inception。
有一部很著名的电影——《盗梦空间》其英文名字即 Inception
注Inception Module 有很多可以构造的方式下面只是其中一种。以它为例来介绍。 构造成上述样式的原因是在构造神经网络时有些超参数是比较难选的如卷积核Kernel大小用3x3的还是用5x5的还是其它的即 选择哪一个卷积核效果更好比较困难。
GoogLeNet的出发点是不知道哪个卷积核比较好用那么就在一个块里把这几种卷积都用一下然后把它们的结果放到一起若3x3的卷积核好用那么 它的权重就会比较大。而其它路线下权重相对来讲就会变小。【即GoogLeNet提供了几种候选的 CNN的配置通过训练在以上几条路线中 自动地找到最优的组合】
Concatenate 用来将张量拼接起来例如下面 有两个张量沿着通道可将二者拼接起来这就叫做Concatenate拼接 从四条路径出来的是四个张量卷积操作过后需要再把它们拼接回一个张量。
要注意在上述四个路径四种方法中最终的输出图必须仍然保持相同的W图像宽度以及H图像高度否则无法再次进行拼接 传输到下一层模块中。可以这样理解沿着通道方向宽高平面接在一起所以要求宽高一致
我们知道在上一目录中进行最大池化时会导致图像变为原来一半因此要想保证图像大小不变需要人为地指定 stride和padding值。在均值池化 Average Pooling这里也是如此通过人为指定stride和padding值来保证输入和输出的图像大小(W、H)是一致的
此外1x1卷积的个数取决于 输入张量的通道个数。
1x1卷积举例显然卷积过后依然保持3×33\times 33×3大小。多个通道那么针对每一个通道 都要配一个卷积核。 做完卷积之后同时也是要求和的同样地不管输入通道多少个最后 加和过后 也都是 变成 1×W×H1 \times W \times H1×W×H的 feature map。若 三个挪起来的卷积核有mmm个那么输出就可以得到mmm个 1×W×H1 \times W \times H1×W×H feature maps。 因此1×11\times11×1的卷积 可以跨越不同通道相同位置 元素的值。 即 将它们的信息融合到一起了。【信息融合】。
为什么要使用1×11\times11×1卷积
主要作用1×11\times11×1卷积可以改变通道的数量。从而极大降低运算量。
下边举例来说明
输入张量有192个通道图像大小宽高 为28×2828\times 2828×28用5×55\times55×5的卷积核 做卷积运算padding2 保证卷积过后 图像宽高不变
那么卷积运算进行的 浮点数运算量次数亦如下所示。5x5卷积对每一个元素都进行运算故乘以28228^2282每次卷积实际上 是对所有通道来做的因此再乘以192这样的运算共做了32次才能得到输出通道故再乘以32。。可以看到计算量(计算次数)超过 1亿两千万次…【注深度学习中 问题之一 就是运算量太大因此要想办法降低 运算量】 附对于上面的计算大家的问题 [1] 不乘32的话得到的就是一组卷积核卷积之后的结果是单通道需要32组卷积核才能出32通道 (多通道 输出)。 [2] 一个卷积核生成一个特征图右边共有32张特征图 组成所以在此基础上 再乘32。 [3] 因为你要生成32通道的输出嘛就需要32组卷积核 和输入做卷积所以要乘上32。 因此用1x1卷积来改变通道数量进而降低运算量看下图最终得到的结果和上面是一样的但运算量变成了之前的1/10计算量小了很多【这意味着在训练的时候用这种方式来减少运算的数量如之前训练需要10个小时但现在只需要1个小时那我们在训练的时候 就可以训练更多的轮数 或者尝试更多的权重换句话说省money~】有时候也把用了1x1的神经网络 叫做 Network in Network——神经网络中的神经网络。 附对于上图的计算大家的问题 [1] 这里的卷积计算其实不只是针对 1x1的卷积对所有的卷积都是一样的。卷积核的个数决定了输出的通道数或者 反过来也一样我们自己决定 想要 多少输出通道这就决定了卷积核的个数。输入的通道数决定了卷积核的层数。 [2] 变成16是因为 选择用了16个1x1的kernel想变成其它个数也可以。 Inception的实现
为了观察把模型调整一下方向 卷积计算完毕后把这四个块儿 拼一起即 沿着通道的维度把它们拼到一起因为张量的维度是batchchannelwidthheight即b,c,w,g0 1 2 3故沿着维度为1 拼起来之前dim-1就是沿最后一个元素方向 注意初始的通道数这里没有写死而是初始化的时候在构造函数里作为参数传进去目的是将来在实例化的时候 可以指明输入通道数是多少。更多内容请看下图的标注 有同学说 [1] inception输出的channel一律为88不改变图像的wh只有卷积/池化改变wh所以最后88x4x41408 附论文地址
1.10.4 GoogLeNet 练习
网络结构如上目录。
# 昵 称:XieXu
# 时 间: 2023/2/20/0020 12:30
# GoogLeNet练习
import torch
import torch.nn as nn
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F
import torch.optim as optim# prepare datasetbatch_size 64
transform transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))]) # 归一化,均值和方差train_dataset datasets.MNIST(root../dataset/mnist/, trainTrue, downloadTrue, transformtransform)
train_loader DataLoader(train_dataset, shuffleTrue, batch_sizebatch_size)
test_dataset datasets.MNIST(root../dataset/mnist/, trainFalse, downloadTrue, transformtransform)
test_loader DataLoader(test_dataset, shuffleFalse, batch_sizebatch_size)# design model using class
class InceptionA(nn.Module):def __init__(self, in_channels):super(InceptionA, self).__init__()self.branch1x1 nn.Conv2d(in_channels, 16, kernel_size1)self.branch5x5_1 nn.Conv2d(in_channels, 16, kernel_size1)self.branch5x5_2 nn.Conv2d(16, 24, kernel_size5, padding2)self.branch3x3_1 nn.Conv2d(in_channels, 16, kernel_size1)self.branch3x3_2 nn.Conv2d(16, 24, kernel_size3, padding1)self.branch3x3_3 nn.Conv2d(24, 24, kernel_size3, padding1)self.branch_pool nn.Conv2d(in_channels, 24, kernel_size1)def forward(self, x):branch1x1 self.branch1x1(x)branch5x5 self.branch5x5_1(x)branch5x5 self.branch5x5_2(branch5x5)branch3x3 self.branch3x3_1(x)branch3x3 self.branch3x3_2(branch3x3)branch3x3 self.branch3x3_3(branch3x3)branch_pool F.avg_pool2d(x, kernel_size3, stride1, padding1)branch_pool self.branch_pool(branch_pool)outputs [branch1x1, branch5x5, branch3x3, branch_pool]return torch.cat(outputs, dim1) # b,c,w,h 故c对应的是dim1class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 nn.Conv2d(1, 10, kernel_size5)self.conv2 nn.Conv2d(88, 20, kernel_size5) # 88 24x3 16self.incep1 InceptionA(in_channels10) # 与conv1 中的10对应self.incep2 InceptionA(in_channels20) # 与conv2 中的20对应self.mp nn.MaxPool2d(2)self.fc nn.Linear(1408, 10)def forward(self, x):in_size x.size(0)x F.relu(self.mp(self.conv1(x)))x self.incep1(x)x F.relu(self.mp(self.conv2(x)))x self.incep2(x)x x.view(in_size, -1)x self.fc(x)return xmodel Net()# construct loss and optimizer
criterion torch.nn.CrossEntropyLoss()
optimizer optim.SGD(model.parameters(), lr0.01, momentum0.5)# training cycle forward, backward, updatedef train(epoch):running_loss 0.0for batch_idx, data in enumerate(train_loader, 0):inputs, target dataoptimizer.zero_grad()outputs model(inputs)loss criterion(outputs, target)loss.backward()optimizer.step()running_loss loss.item()if batch_idx % 300 299:print([%d, %5d] loss: %.3f % (epoch 1, batch_idx 1, running_loss / 300))running_loss 0.0def test():correct 0total 0with torch.no_grad():for data in test_loader:images, labels dataoutputs model(images)_, predicted torch.max(outputs.data, dim1)total labels.size(0)correct (predicted labels).sum().item()print(accuracy on test set: %d %% % (100 * correct / total))if __name__ __main__:for epoch in range(10):train(epoch)test()我的PC内存不是很大所以占用会比较多ps散热风扇呼呼转ing… 结果accuracy依然是98% 1.10.5 GoogLeNet 练习GPU版
与上一部分几乎一样只是把model以及运算的部分迁移到GPU来计算
# 昵 称:XieXu
# 时 间: 2023/2/20/0020 12:54
# GoogLeNet GPU版本
# 网络结构等 都不用动只是把模型 以及 数据运算 迁移到GPU上
import torch
import torch.nn as nn
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F
import torch.optim as optim# prepare datasetbatch_size 64
transform transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))]) # 归一化,均值和方差train_dataset datasets.MNIST(root../dataset/mnist/, trainTrue, downloadTrue, transformtransform)
train_loader DataLoader(train_dataset, shuffleTrue, batch_sizebatch_size)
test_dataset datasets.MNIST(root../dataset/mnist/, trainFalse, downloadTrue, transformtransform)
test_loader DataLoader(test_dataset, shuffleFalse, batch_sizebatch_size)# design model using class
class InceptionA(nn.Module):def __init__(self, in_channels):super(InceptionA, self).__init__()self.branch1x1 nn.Conv2d(in_channels, 16, kernel_size1)self.branch5x5_1 nn.Conv2d(in_channels, 16, kernel_size1)self.branch5x5_2 nn.Conv2d(16, 24, kernel_size5, padding2)self.branch3x3_1 nn.Conv2d(in_channels, 16, kernel_size1)self.branch3x3_2 nn.Conv2d(16, 24, kernel_size3, padding1)self.branch3x3_3 nn.Conv2d(24, 24, kernel_size3, padding1)self.branch_pool nn.Conv2d(in_channels, 24, kernel_size1)def forward(self, x):branch1x1 self.branch1x1(x)branch5x5 self.branch5x5_1(x)branch5x5 self.branch5x5_2(branch5x5)branch3x3 self.branch3x3_1(x)branch3x3 self.branch3x3_2(branch3x3)branch3x3 self.branch3x3_3(branch3x3)branch_pool F.avg_pool2d(x, kernel_size3, stride1, padding1)branch_pool self.branch_pool(branch_pool)outputs [branch1x1, branch5x5, branch3x3, branch_pool]return torch.cat(outputs, dim1) # b,c,w,h c对应的是dim1class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 nn.Conv2d(1, 10, kernel_size5)self.conv2 nn.Conv2d(88, 20, kernel_size5) # 88 24x3 16self.incep1 InceptionA(in_channels10) # 与conv1 中的10对应self.incep2 InceptionA(in_channels20) # 与conv2 中的20对应self.mp nn.MaxPool2d(2)self.fc nn.Linear(1408, 10)def forward(self, x):in_size x.size(0)x F.relu(self.mp(self.conv1(x))) # 卷积和池化的先后关系不影响x self.incep1(x)x F.relu(self.mp(self.conv2(x)))x self.incep2(x)x x.view(in_size, -1)x self.fc(x)return xmodel Net()
device torch.device(cuda:0 if torch.cuda.is_available() else cpu) # 定义device使用GPU
model.to(device) # 将整个模型运算迁移到GPU# construct loss and optimizer
criterion torch.nn.CrossEntropyLoss()
optimizer optim.SGD(model.parameters(), lr0.01, momentum0.5)# training cycle forward, backward, updatedef train(epoch):running_loss 0.0for batch_idx, data in enumerate(train_loader, 0):inputs, target datainputs, target inputs.to(device), target.to(device) # 张量迁移到GPU 2023.2.20 13:09optimizer.zero_grad()outputs model(inputs)loss criterion(outputs, target)loss.backward()optimizer.step()running_loss loss.item()if batch_idx % 300 299:print([%d, %5d] loss: %.3f % (epoch 1, batch_idx 1, running_loss / 300))running_loss 0.0def test():correct 0total 0with torch.no_grad():for data in test_loader:images, labels datalabels, images labels.to(device), images.to(device) # 迁移到GPUoutputs model(images)_, predicted torch.max(outputs.data, dim1)total labels.size(0)correct (predicted labels).sum().item()print(accuracy on test set: %d %% % (100 * correct / total))if __name__ __main__:for epoch in range(10):train(epoch)test()查看GPU调用 结果accuracy98%依然不变。 GooLeNet 更强调把网络做得更深从而使网络变得更为复杂。
----------------------------------分割线----------------------------------
1.10.6 ResNet残差网络
ResNet的提出者来自我国学者何凯明等人。
论文地址Deep Residual Learning for Image Recognition
一般来讲并不是训练的轮数越多越好这期间是有可能出现“过拟合”的从而错过正确率比较高的点。
如下图若一直堆叠3x3的卷积看性能会不会变好。实验发现就CIFAR数据集来讲20层的卷积其性能要比56层的卷积性能更好。 出现 训练轮数多但效果没有提升反而下降的问题这可能是由于“梯度消失”了。
由反向传播我们知道许多梯度相乘倘若梯度1的话那 这么多梯度会越乘越小逐渐趋近0。由于权重的训练是ww−α×梯度ww-\alpha\times梯度ww−α×梯度那这样对于权重www来讲就几乎没有改变训练很多次的效果就微乎其微了。
如何解决这个问题
1.10.7 Residual Net“和xxx做加法”
相比于 在传统神经网络中先进行权重计算如卷积Softmax等再经过激活函数如relu等最终得到输出。
Residual Net 的思想引入了跳链接就是 让输入在N一般N2N2N2层连接后并入第N层的输出
H(x)F(x)xH(x) F(x) xH(x)F(x)x
然后 再进行ReLU激活得到输出。 在这样的结构中以上图为例如果要进行H(x)H(x)H(x)对xxx的求导则会有
∂H(x)∂x∂F(x)∂x1\frac{\partial H(x)}{\partial x} \frac{\partial F(x)}{\partial x} 1∂x∂H(x)∂x∂F(x)1
也就是说即便 存在梯度消失现象即 存在某一层网络中的∂F(x)∂x→0\frac{\partial F(x)}{\partial x} \to 0∂x∂F(x)→0由于上述公式会使得在反向传播过程中传播的梯度保持在1左右即∂H(x)∂x→1\frac{\partial H(x)}{\partial x} \to 1∂x∂H(x)→1。那么 离输入较近的层也可以得到充分的训练。 下图中有同学说池化是为了减小输入层的尺寸使得和输出的尺寸一样才可以跳连接相加。 Residual Block记得要保证输入张量的维度和输出张量的维度一样。 Residual Block的实现记得先算xyxyxy而后再激活~ 从而就可以把网络写出来了 1.10.8 Residual Net 练习——accuracy99%
网络模型及设计如上述介绍。
# 昵 称:XieXu
# 时 间: 2023/2/20/0020 15:00
# ResNet,即 Residual Net。残差网络import torch
import torch.nn as nn
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F
import torch.optim as optim# prepare datasetbatch_size 64
transform transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))]) # 归一化,均值和方差train_dataset datasets.MNIST(root../dataset/mnist/, trainTrue, downloadTrue, transformtransform)
train_loader DataLoader(train_dataset, shuffleTrue, batch_sizebatch_size)
test_dataset datasets.MNIST(root../dataset/mnist/, trainFalse, downloadTrue, transformtransform)
test_loader DataLoader(test_dataset, shuffleFalse, batch_sizebatch_size)# design model using class
class ResidualBlock(nn.Module):def __init__(self, channels):super(ResidualBlock, self).__init__()self.channels channelsself.conv1 nn.Conv2d(channels, channels, kernel_size3, padding1)self.conv2 nn.Conv2d(channels, channels, kernel_size3, padding1)def forward(self, x):y F.relu(self.conv1(x))y self.conv2(y)return F.relu(x y)class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 nn.Conv2d(1, 16, kernel_size5)self.conv2 nn.Conv2d(16, 32, kernel_size5) # 88 24x3 16# self.mp nn.MaxPool2d(2) # 写这儿也行写下边也可。。2023.2.20 15:06self.rblock1 ResidualBlock(16)self.rblock2 ResidualBlock(32)self.mp nn.MaxPool2d(2)self.fc nn.Linear(512, 10) # 暂时不知道1408咋能自动出来的 2023.2.20 15:01def forward(self, x):in_size x.size(0)x self.mp(F.relu(self.conv1(x)))x self.rblock1(x)x self.mp(F.relu(self.conv2(x)))x self.rblock2(x)x x.view(in_size, -1)x self.fc(x)return xmodel Net()# construct loss and optimizer
criterion torch.nn.CrossEntropyLoss()
optimizer optim.SGD(model.parameters(), lr0.01, momentum0.5)# training cycle forward, backward, updatedef train(epoch):running_loss 0.0for batch_idx, data in enumerate(train_loader, 0):inputs, target dataoptimizer.zero_grad()outputs model(inputs)loss criterion(outputs, target)loss.backward()optimizer.step()running_loss loss.item()if batch_idx % 300 299:print([%d, %5d] loss: %.3f % (epoch 1, batch_idx 1, running_loss / 300))running_loss 0.0def test():correct 0total 0with torch.no_grad():for data in test_loader:images, labels dataoutputs model(images)_, predicted torch.max(outputs.data, dim1)total labels.size(0)correct (predicted labels).sum().item()print(accuracy on test set: %d %% % (100 * correct / total))if __name__ __main__:for epoch in range(10):train(epoch)test()测试结果如下测试集的正确率accuracy99% 1.10.9 Residual Net 练习——GPU版
同样地也是将模型及运算迁移到GPU上即可与上面微乎其微。
# 昵 称:XieXu
# 时 间: 2023/2/20/0020 15:13
# Residual NetResNetGPU版import torch
import torch.nn as nn
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F
import torch.optim as optim# prepare datasetbatch_size 64
transform transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))]) # 归一化,均值和方差train_dataset datasets.MNIST(root../dataset/mnist/, trainTrue, downloadTrue, transformtransform)
train_loader DataLoader(train_dataset, shuffleTrue, batch_sizebatch_size)
test_dataset datasets.MNIST(root../dataset/mnist/, trainFalse, downloadTrue, transformtransform)
test_loader DataLoader(test_dataset, shuffleFalse, batch_sizebatch_size)# design model using class
class ResidualBlock(nn.Module):def __init__(self, channels):super(ResidualBlock, self).__init__()self.channels channelsself.conv1 nn.Conv2d(channels, channels, kernel_size3, padding1)self.conv2 nn.Conv2d(channels, channels, kernel_size3, padding1)def forward(self, x):y F.relu(self.conv1(x))y self.conv2(y)return F.relu(x y) # 先做yx再做reluclass Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 nn.Conv2d(1, 16, kernel_size5)self.conv2 nn.Conv2d(16, 32, kernel_size5)self.rblock1 ResidualBlock(16)self.rblock2 ResidualBlock(32)self.mp nn.MaxPool2d(2)self.fc nn.Linear(512, 10)def forward(self, x):in_size x.size(0)x self.mp(F.relu(self.conv1(x)))x self.rblock1(x)x self.mp(F.relu(self.conv2(x)))x self.rblock2(x)x x.view(in_size, -1)x self.fc(x)return xmodel Net()
device torch.device(cuda:0 if torch.cuda.is_available() else cpu) # 定义device使用GPU 2023.2.20 15:14
model.to(device) # 将整个网络模型 迁移到GPU 2023.2.20 15:14# construct loss and optimizer
criterion torch.nn.CrossEntropyLoss()
optimizer optim.SGD(model.parameters(), lr0.01, momentum0.5)# training cycle forward, backward, updatedef train(epoch):running_loss 0.0for batch_idx, data in enumerate(train_loader, 0):inputs, target datainputs, target inputs.to(device), target.to(device) # 使用GPUoptimizer.zero_grad()outputs model(inputs)loss criterion(outputs, target)loss.backward()optimizer.step()running_loss loss.item()if batch_idx % 300 299:print([%d, %5d] loss: %.3f % (epoch 1, batch_idx 1, running_loss / 300))running_loss 0.0def test():correct 0total 0with torch.no_grad():for data in test_loader:images, labels dataimages, labels images.to(device), labels.to(device) # 使用GPUoutputs model(images)_, predicted torch.max(outputs.data, dim1)total labels.size(0)correct (predicted labels).sum().item()print(accuracy on test set: %d %% % (100 * correct / total))if __name__ __main__:for epoch in range(10):train(epoch)test()经过GPU加速运行这样测试后accuracy反而是98%了… 于是再次测试了 99%。看来有一点点玄学炼丹? bushi 不知道大家测试的结果如何可以在评论区分享一下哈 1.10.10 附其它Residual块 设计——可参阅Paper
除上面介绍的一种Residual设计之外还有很多设计形式。 许多的跳连接… 附老师的建议。别骂了 别骂了bushi 1.11 循环神经网络RNN基础篇
1.11.1 问题背景RNN专门用来处理序列数据
RNN也是神经网络的一种。
结构上看似复杂实际很简单说白了就是对线性层的复用。
在使用全连接网络的时候也把这种网络叫做稠密网络Dense亦或Deep即 深层次。多个线性层对于输入的数据在空间上进行变换
x1、x2...x8x1、x2...x8x1、x2...x8 等为数据样本的不同特征这也是我们都了解的。 全连接网络权重实际上是最多的。若 CNN中 输入通道是Input128个输出通道是Output64个卷积核采用5x5的那么计算的参数为25×21320480025\times 2^{13}20480025×213204800超过20万参数的计算量。 卷积层的输入和输出只和通道以及卷积核的大小有关。
而全连接层和变换之后的数据大小有关。比如对于图像三阶的张量 经过一系列卷积运算过后剩余4096个元素而4096 在实际中很少 直接把它降维成10维或1维。这中间可能要用 如1024来过渡 4096∗102441943044096*102441943044096∗10244194304。计算量很大。所以卷积层的运算虽然有些复杂但是其权重并不多。而全连接层 权重太多啦
在使用神经网络如CNN的时候要明确 全连接层占的权重的比例在网络中全部的参数里面“是占大头的” 全连接层的权重是最多的。
所以计算、存储、推理的时候遇到的瓶颈 很大程度上来自于全连接层。
为什么卷积神经网络CNN的权重少呢——“权重共享”。对于图像中的一个块并不是说每一个像素 和 下一层的 feature map 中的每一个值 之间要建立连接而是说 这一块在卷积的时候整个图像上用的卷积核是共享的。所以CNN的权重数量就少
在解决序列问题的时候也会遇到这种问题如处理的序列是视频视频中每一帧是一张图像把一组图像变成一个集合再去处理那么在稠密层 用全连接网络处理时那么遇到的权重数量----天文数字。
RNN 专门用来处理带有序列模式的数据其中也要使用 共享的概念。即权重共享来减少需要训练的权重的数量。
对于输入数据如x1、x2、x3x1、x2、x3x1、x2、x3不仅把它们看成一个序列在使用的时候思路是既要考虑x1x1x1和x2x2x2的连接关系还要考虑它们有先后的时间序列的关系。如前序和后序如x2x2x2的数值 会依赖于x1x1x1x3x3x3的数据会依赖于x2x2x2。感觉解释的不是很清楚可以翻看我之前的博客
具有典型的序列数据天气 / 股市 / 金融数据 / 自然语言等
如 对于自然语言我 / 爱 / 北京长城。这几个词显然是具有序列关系的有先后顺序。若打乱顺序如 长城爱北京我这显然不符合我们的认知。自然语言是依赖于词的顺序的。
1.11.2 RNN的运算
什么是RNN Cell
xtx_txt 表示时刻ttt时的输入数据当然 xtx_txt 具有维度。hth_tht 表示时刻ttt时得到的输出隐含层。即xtx_txt经过RNN Cell 转换后得到的数据另外维度的向量。RNN Cell本质 就是一个线性层。可以把数据从某一个维度映射到另一个维度。该线性层和一般的线性层的区别是这个线性层是共享的。
一般多数都将RNN Cell画成这样 这里我们将其展开更便于观察如下图x1x1x1x2x2x2x3x3x3x4x4x4是输入序列假设以天气数据为例它们分别表示每一天里天气的几个特征。将输入送到RNN Cell(事实上就是Linear)里即做线性变换然后得到输出hih_ihi我们将hih_ihi如h1h_1h1、h2h_2h2等叫做hidden隐藏 / 隐层。 序列之间每一项都和前一项存在关系因此当前项如x2x_2x2在通过RNN Cell计算出的h2h_2h2里面不仅要包含x2x_2x2的信息还要包含x1x_1x1的信息想办法 把x1x_1x1向量的值通过某种运算和x2x_2x2进行融合(注求和/求乘积等都叫融合)。
进一步讲即x1x_1x1在经过 RNN Cell处理输出得到h1h_1h1注意 这里不仅仅是输出得到h1h_1h1顺便还把h1h_1h1的值送入下一个x2x_2x2的运算过程中。所以说上面的红色箭头就是指的h1h_1h1、h2h_2h2等。此外对于x1x_1x1来讲也需要一个输入的h0h_0h0。如果有先验知识就把先验知识作为h0h_0h0送个RNN。如通过图像生成文本那我们就可以 先用CNNFC 来生成h0h_0h0再把h0h_0h0作为RNN的cell 送进去这样就把卷积神经网络CNN和循环神经网络RNN接上了完成图像到文本的转换。若没有h0h_0h0就把它设成和h1h_1h1一样即可即 若h1h_1h1 有五个维度那就把h0h_0h0设为五个维度的0送入RNN即可
RNN的计算
input_size输入维度hidden_size隐层维度
循环神经网络RNN中激活函数使用的是tanh因为其取值范围是 -1到1 简言之输入乘以一个维度上一个隐层乘以一个维度进行转换之后相加…事实上图中的线性层是可以拼起来的即 在构建权重的时候构建RNN Cell把 x 和 h拼起来拼出来的维度 即 (hidden_size input_size) x1 的向量 总之看上去是两个线性层的运算本质上就是一个线性层的运算。
把RNN Cell以此类循环的形式 将序列送进去依次算出隐层的过程此即循环神经网络。
1.11.3 构造RNN 提示了写不开了因此在另一篇里面继续记录!
1.12 循环神经网络RNN高级篇
真的写不开了…字数太多啦在另一篇发布。
二、其他——PyTorch深度学习库的特点
2.1 Torch简介 Facebook公司由torch重新做的版本目前不再基于torch而是基于caffee框架所写。
与tensorflow相比 pytorch更加灵活tensorflow文档完善但更加大而麻烦。 pytorch使用 动态计算图每一步代码 完成一次变量的新增或改变。 tensorflow使用 静态计算图先根据代码建立好计算图再往里面填变量进行计算。好处在于 可以一次成图坏处在于 其内部的命名体系以及时序控制逻辑不好控制难以在中间进行介入。 PStensorflow2.x 使用 动态图优先 的原则创建计算图。
2.2 PyTorch安装 可以在官网下载部署即 去PyTorch官网上找pytorch对应版本的conda或者pip命令大致如下图 注若初次下载没有配置镜像的话上述指令可能会下载较慢可以换许多镜像源还不行的话单击下载pytorch选择合适版本的pytorch进行下载到本地再安装下载的时候主要包括torch、torchvision、torchaudio这三个包。
安装之后可以cmd打开终端验证一下 2.3 特点——GPU加速
测试code
# 昵 称:XieXu
# 时 间: 2023/2/13/0013 10:17# 特点1GPU加速
import torch
import time# 建立两个随机矩阵
a torch.randn(10000, 1000)
b torch.randn(1000, 2000)# cpu 模式下的矩阵乘法计算时间
t0 time.time()
c torch.matmul(a, b)
t1 time.time()
print(a.device, t1 - t0, c.norm(2))# a,b移交到cudaGPU上
device torch.device(cuda)
a a.to(device)
b b.to(device)
# 第一遍有cuda初始化的时间
t0 time.time()
c torch.matmul(a, b)
t1 time.time()
print(a.device, t1 - t0, c.norm(2))t0 time.time()
c torch.matmul(a, b)
t1 time.time()
print(a.device, t1 - t0, c.norm(2))结果如下 再执行一次
2.4 特点——自动求导 以ya2xbxcya²xbxcya2xbxc为例求解当x1x1x1时y对于a,b,c三个数的偏导数即∂y∂a\frac{\partial y}{\partial a}∂a∂y ∂y∂b\frac{\partial y}{\partial b}∂b∂y ∂y∂c\frac{\partial y}{\partial c}∂c∂y 显然他们分别等于2a,1,1利用pytorch实现如下
# 昵 称:XieXu
# 时 间: 2023/2/13/0013 10:40# 自动求导
import torch
from torch import autograd# 建立张量 x a b c 此时都为常数
# requires_grad表后续计算中需要该参数梯度 相当于在计算图中开放其修改权限来求解记录
x torch.tensor(1.)
a torch.tensor(1., requires_gradTrue)
b torch.tensor(2., requires_gradTrue)
c torch.tensor(3., requires_gradTrue)y a ** 2 * x b * x c
# 在常数下的梯度导数 全为0
print(before: , a.grad, b.grad, c.grad)# 按照y式的关系对a,b,c求导
grads autograd.grad(y, [a, b, c])print(after: , grads[0], grads[1], grads[2])
结果
2.5 特点——封装的API 包括对张量Tensor的运算以及神经网络的各类接口。 Tensor的运算我会继续记录。
三、其他——神经网络基础
3.1 基本原理 模仿(人类)神经元细胞来进行的设计在整体结构上模仿神经进行分层设计属于仿生学的算法。 3.2 正向传播与反向传播 正向传播本质上是按照 输入层到输出层 的顺序求解并保存网络中的中间变量本身。 反向传播本质上是按照输出层到输入层的顺序求解并保存网络中的中间变量以及其他参数的导数梯度。 注两者核心都是计算图。 下图中左图每一步是需要 正向传播 过程计算的 中间变量及结果右图是需要进行 反向传播 过程进行计算的 导数。
反向传播求导示例图
3.2.1 正向传播 上图中的实际计算过程为 e(ab)∗(b1)e(ab)*(b1) e(ab)∗(b1) 每一步 都只能进行原子计算每个原子计算构成一个圈继而形成整个计算图。 在计算图中先进行正向计算cabcabcab, db1db1db1再计算ec∗dec*dec∗d求解得到eee的值以后即完成了正向计算的过程。
3.2.2 反向传播
反向传播 具体求导 示例图 在前馈计算过程中可以求解得 ∂c∂a∂c∂b∂d∂b∂d∂1\frac{\partial c}{\partial a} \frac{\partial c}{\partial b} \frac{\partial d}{\partial b} \frac{\partial d}{\partial 1} ∂a∂c∂b∂c∂b∂d∂1∂d 等一系列梯度这其中所有的梯度信息在正向计算过程中进行保存并在之后依照计算图中的链接根据链式法则反方向求导计算∂e∂a\frac{\partial e}{\partial a}∂a∂e以及∂e∂b\frac{\partial e}{\partial b}∂b∂e需要优化的核心梯度。
四、传统机器学习策略 五、学习系统的发展
5.1 基于规则的系统 即在输入之后进行 手工的设计算法 以达到相应目的 的输出。 基于规则的系统 eg. 基于原函数求解的法则设计原函数求解的程序。 但其中所利用到的规则是用来建立知识库的并不是真正的智能。
5.2 经典机器学习方法 在输入之后进行手工的特征提取并对所提取的特征进行适配以此来得到相应的输出。即 先将具体信息 变成张量向量再实现对张量的映射来得到输出。 经典机器学习方法
5.3 表示学习方法
5.3.1 维度诅咒 即在整个input里面对feature的需求越多则对整个样本数量的需求就越大。
补充 依据大数定律采样越多与数据真实情况越接近。假定对于每个feature需要采集十个样本才能保证与真实情况足够得接近。 对于一组数据如果只有一个feature需要10个采样点。有两个feature 则需要10 x 2个采样点有n个feature,则需要10n个采样点。从而引起对原始数据量的过度需求即维度诅咒。
5.3.2 解决方法 进行一个线性或者非线性的映射来让原始维度降低。经过降维后的特征有可能会 丢失关键信息因此综合上述问题需要进行表示学习的过程即在降维的同时 而又保持住高维空间的度量信息。
第一代表示学习 在提取特征时利用特殊的算法进行而非人工进行。 提取特征的部分以及最终学习的部分深色框需要分开训练。先提取特征再学习算法。 第一代表示学习 第二代深度学习 利用原始特征 来进行特征提取在原始特征的基础上再进行提取。 所有训练过程是统一的。 深度学习
六、人工智能补充知识
6.1 需要会的必备知识
Python编程语言有基础即可线性代数矩阵运算概率论与数理统计统计分析
6.2 人工智能——问题分类 简言之所要实现的人工智能 具体上可分为推理与预测两大部分总的来说即是 利用计算机来代替人脑进行此类工作。
6.2.1 推理 即 根据已经有的信息 来进行 推理 并作出决策的过程。 如 通过外界信息或自身实际情况来判断自己能够吃什么。 吃什么 机器学习——吃什么 6.2.2 预测 根据具体的实体将其与抽象概念结合起来 如通过认识到猫这一动物实体来与汉字“猫”建立联系本质上是一个预测过程赋予真实存在的事物一个并不真实存在的符号。 识别猫 机器学习——识别猫
6.3 人工智能——算法分类 主要分为 监督学习(算法) 和 非监督学习 (算法)。
6.3.1 传统算法与智能算法
传统算法 如 穷举法、贪心法、分治法、动态规划等基于这样的思想利用数据结构等人工设计算法。
智能算法 基于数据集进行算法的生成而非人工设计。主要是统计相关。 深度学习实际上是 基于机器学习中的神经网络 而衍生出来的一种手段是机器学习的一种分支。 人工智能领域示意图AI知识库- 机器学习逻辑回归- 表示学习:自动编码器 - 深度学习多层感知机等神经网络…
七、Reference 本文部分参考自Bilibili视频学习资料《PyTorch深度学习实践》完结合集老师讲的很棒哦建议多刷几遍 热门专栏推荐
PythonAI专栏【Python从入门到人工智能】前端专栏【前端之梦~代码之美(H5CSS3JS.】论文阅读项目专栏【小小的项目 (实战案例)】C语言/C专栏【C语言、C基础代码~】问题解决专栏【工具、技巧、解决办法】 加入Community 一起追光追光者♂社区
持续创作优质好文ing…✍✍✍
记得一键三连哦
文章转载自: http://www.morning.pqsys.cn.gov.cn.pqsys.cn http://www.morning.bqwnp.cn.gov.cn.bqwnp.cn http://www.morning.jjhrj.cn.gov.cn.jjhrj.cn http://www.morning.plznfnh.cn.gov.cn.plznfnh.cn http://www.morning.dpqqg.cn.gov.cn.dpqqg.cn http://www.morning.gnkbf.cn.gov.cn.gnkbf.cn http://www.morning.jfjfk.cn.gov.cn.jfjfk.cn http://www.morning.ntzfj.cn.gov.cn.ntzfj.cn http://www.morning.yqqxj1.cn.gov.cn.yqqxj1.cn http://www.morning.btqrz.cn.gov.cn.btqrz.cn http://www.morning.nthyjf.com.gov.cn.nthyjf.com http://www.morning.rkgyx.cn.gov.cn.rkgyx.cn http://www.morning.jpqmq.cn.gov.cn.jpqmq.cn http://www.morning.jqkjr.cn.gov.cn.jqkjr.cn http://www.morning.sqfnx.cn.gov.cn.sqfnx.cn http://www.morning.tmsxn.cn.gov.cn.tmsxn.cn http://www.morning.gjqgz.cn.gov.cn.gjqgz.cn http://www.morning.ldpjm.cn.gov.cn.ldpjm.cn http://www.morning.rrpsw.cn.gov.cn.rrpsw.cn http://www.morning.dnpft.cn.gov.cn.dnpft.cn http://www.morning.qfrsm.cn.gov.cn.qfrsm.cn http://www.morning.tqgx.cn.gov.cn.tqgx.cn http://www.morning.rnqnp.cn.gov.cn.rnqnp.cn http://www.morning.tfwsk.cn.gov.cn.tfwsk.cn http://www.morning.rqmr.cn.gov.cn.rqmr.cn http://www.morning.tqhpt.cn.gov.cn.tqhpt.cn http://www.morning.rfwrn.cn.gov.cn.rfwrn.cn http://www.morning.nnhfz.cn.gov.cn.nnhfz.cn http://www.morning.gxhqt.cn.gov.cn.gxhqt.cn http://www.morning.ykwbx.cn.gov.cn.ykwbx.cn http://www.morning.ljhnn.cn.gov.cn.ljhnn.cn http://www.morning.hgtr.cn.gov.cn.hgtr.cn http://www.morning.bmncq.cn.gov.cn.bmncq.cn http://www.morning.xhwty.cn.gov.cn.xhwty.cn http://www.morning.flxqm.cn.gov.cn.flxqm.cn http://www.morning.rwlns.cn.gov.cn.rwlns.cn http://www.morning.inheatherskitchen.com.gov.cn.inheatherskitchen.com http://www.morning.lqjlg.cn.gov.cn.lqjlg.cn http://www.morning.fnkcg.cn.gov.cn.fnkcg.cn http://www.morning.brmbm.cn.gov.cn.brmbm.cn http://www.morning.xqknl.cn.gov.cn.xqknl.cn http://www.morning.mzhgf.cn.gov.cn.mzhgf.cn http://www.morning.yqgbw.cn.gov.cn.yqgbw.cn http://www.morning.smpb.cn.gov.cn.smpb.cn http://www.morning.hhzdj.cn.gov.cn.hhzdj.cn http://www.morning.mlfgx.cn.gov.cn.mlfgx.cn http://www.morning.zsyrk.cn.gov.cn.zsyrk.cn http://www.morning.zybdj.cn.gov.cn.zybdj.cn http://www.morning.mgskc.cn.gov.cn.mgskc.cn http://www.morning.txnqh.cn.gov.cn.txnqh.cn http://www.morning.eshixi.com.gov.cn.eshixi.com http://www.morning.dfygx.cn.gov.cn.dfygx.cn http://www.morning.okiner.com.gov.cn.okiner.com http://www.morning.sskhm.cn.gov.cn.sskhm.cn http://www.morning.czwed.com.gov.cn.czwed.com http://www.morning.srjbs.cn.gov.cn.srjbs.cn http://www.morning.fbdtd.cn.gov.cn.fbdtd.cn http://www.morning.mhbcy.cn.gov.cn.mhbcy.cn http://www.morning.wbns.cn.gov.cn.wbns.cn http://www.morning.dytqf.cn.gov.cn.dytqf.cn http://www.morning.nkcfh.cn.gov.cn.nkcfh.cn http://www.morning.xxwhz.cn.gov.cn.xxwhz.cn http://www.morning.rkzk.cn.gov.cn.rkzk.cn http://www.morning.cfocyfa.cn.gov.cn.cfocyfa.cn http://www.morning.ymwny.cn.gov.cn.ymwny.cn http://www.morning.gxfpk.cn.gov.cn.gxfpk.cn http://www.morning.piekr.com.gov.cn.piekr.com http://www.morning.qwqzk.cn.gov.cn.qwqzk.cn http://www.morning.nsjpz.cn.gov.cn.nsjpz.cn http://www.morning.qnpyz.cn.gov.cn.qnpyz.cn http://www.morning.mkxxk.cn.gov.cn.mkxxk.cn http://www.morning.bpwz.cn.gov.cn.bpwz.cn http://www.morning.xhhzn.cn.gov.cn.xhhzn.cn http://www.morning.pwfwk.cn.gov.cn.pwfwk.cn http://www.morning.snygg.cn.gov.cn.snygg.cn http://www.morning.nwmwp.cn.gov.cn.nwmwp.cn http://www.morning.yntsr.cn.gov.cn.yntsr.cn http://www.morning.fhrgk.cn.gov.cn.fhrgk.cn http://www.morning.nndbz.cn.gov.cn.nndbz.cn http://www.morning.fhtmp.cn.gov.cn.fhtmp.cn