《神经网络与pytorch实战》肖智清著部分代码复现与注释,包括使用pytorch搭建CNNRNNLSTM等基础神经网络

Posted 73840699

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《神经网络与pytorch实战》肖智清著部分代码复现与注释,包括使用pytorch搭建CNNRNNLSTM等基础神经网络相关的知识,希望对你有一定的参考价值。

目录

第二章:初识pytorch

第三章:使用pytorch进行科学计算:使用蒙特卡洛法计算圆周率

第四章:求解优化问题

利用Pytorch求解图4-1中函数在给定点的梯度值:

利用torch.optim.SGD类最小化函数f(X)

绘制Himmelblau函数

用优化器torch.optim.Adam最小化Himmelblau函数

第五章 线性回归

代码清单5-3(因为版本的问题,所以5-1到5-4只选择了这段代码进行注释解析)

5-7 5-8 5-10 5-11

第六章不是研究需要学习的基础章节所以跳过了

第七章:全连接神经网络

7-1 利用torch.nn.Sequential类构建前馈神经网络

示例:基于全连接网络的非线性回归

第八章:卷积神经网络

8-1卷积层用法示例

8-2torch.nn.Unsample类用法示例

8-3各种类型的补全层的用法

8-4和8-5是一个意思,8-5更简洁,所以选择了8-5进行实验,然后8-5被8-6包含,所以取8-6进行注释

第九章 循环神经网络

9-1 构造RNNCell类实例并查看变量内容

9-2构建LSTMCell类实例并用其搭建单向单层循环神经网络

 9-3 使用torch.nn.GRU类实例


第二章:初识pytorch

//第二章代码//
import torch
from torch.nn import Linear, ReLU, Sequential
from  torch.optim import Adam

net = Sequential(
    Linear(3, 8),
    ReLU(),
    Linear(8,8),
    ReLU(),
    Linear(8,1)
)

def g(x,y):
    x0, x1, x2 = x[:, 0] ** 0, x[:, 1] ** 1, x[:, 2] ** 2
    y0 = y[:, 0]
    return (x0+x1+x2)*y0-y0*y0-x0*x1*x2

optimizer = Adam(net.parameters())
for step in range(1000):
    optimizer.zero_grad()
    x = torch.randn(1000,3)
    y = net(x)
    outputs = g(x,y)
    loss = -torch.sum(outputs)
    loss.backward()
    optimizer.step()
    if step % 100 == 0:
        print("第次迭代损失 = ".format(step,loss))

x_test = torch.randn(2, 3)
print('测试输入:'.format(x_test))

y_test = net(x_test)
print('人工神经网络计算结果:'.format(y_test))
print('g的值:'.format(g(x_test,y_test)))

def argmax_g(x):
    x0,x1,x2 = x[:, 0] ** 0, x[:, 1] ** 1, x[:, 2] ** 2
    return 0.5 * (x0+x1+x2)[:,None]
yref_test = argmax_g(x_test)
print('理论最优值:'.format(yref_test))
print('g的值:'.format(g(x_test,yref_test)))

第三章:使用pytorch进行科学计算:使用蒙特卡洛法计算圆周率

//第三章代码//
import torch
num_sample = 10000000
sample = torch.rand(num_sample,2)
#生成10000000行2列的数,每个数都是二维空间中的一个点
dist = sample.norm(p = 2, dim = 1)
#计算每个点与原点的欧几里得距离并保存到名为dist的张量中
retio = (dist<1).float().mean()
#dist<1表示的是dist的值小于1时为True,大于1时为False,对True和False做float()转换,True为1,False为0
#找出在圆内的点,使用float将张量转换为浮点型数值0或1
#再计算所有数值的平均值,就是圆内的点占全部样本的比例
#实际上,这两种方式的结果是相同的。因为 (dist < 1).float() 返回的是一个仅包含 0 和 1 的张量,那么这个张量的平均值,也就是它的所有元素之和除以元素总数,等同于 1 的数量除以元素总数,即 (dist < 1).sum() / num_sample。因此,可以将上述代码中的 ratio 改为 retio,而结果不会发生变化。
#这里说是平均值是因为它计算的是所有1和0的和,而0不会影响最后的总和大小,但是分母上总的数量是原始数值,所以这里取mean就等同于 1 的数量除以元素总数即 1 的比例,即圆内的点数占全部样本点数的比例。
pi = retio*4
print('pi = '.format(pi))

第四章:求解优化问题

利用Pytorch求解图4-1中函数在给定点的梯度值:

from math import pi
import torch

x = torch.tensor([pi/3,pi/6],requires_grad = True)
#定义一个张量x,其值为[pi/3, pi/6],并将requires_grad属性设置为True,以便在计算梯度时进行跟踪
#这里的x是一个一维张量,包含元素pi/3和pi/6
f = -((x.cos()**2).sum())**2
#定义一个函数f,该函数计算cos(x)的平方的总和的相反数,并将结果平方
#具体计算过程:
# x.cos():对x中的每个元素计算其余弦函数的值。
# (x.cos()**2):对步骤1中得到的张量中的每个元素计算其平方。
# (x.cos()**2).sum():对步骤2中得到的张量进行求和。
# -((x.cos()**2).sum())**2:对步骤3中得到的标量计算其相反数并将结果平方。
print('value = '.format(f))
#在Python中,.format() 是一种字符串格式化方法,它允许我们通过使用占位符  来表示待填入的值。
# 在这个例子中,'value = '.format(f) 使用了 .format() 方法来将变量 f 的值插入到占位符  中,
# 形成一个字符串 "value = ",其中  的位置被 f 的值所替换。
# 所以最终输出的字符串是形如 "value = -2.25" 这样的,其中 -2.25 是变量 f 的值。
# 这样的输出方法使我们能够在输出时动态地插入变量的值,非常方便和灵活。
#x有两个值,而输出的f只有一个的原因是:
#x 是一个包含两个元素的一维张量,而 f 是一个标量(即一个只有一个数值的张量)。
# 这是因为 f 是由对 x 中的所有元素进行操作得到的一个值,而不是由对 x 中的每个元素进行操作得到的多个值。
f.backward()
#调用backward()方法来计算f对x的梯度,这个方法将自动计算每个元素对f的梯度,并将这些梯度存储在张量的grad属性中。
#每个元素指的是张量x中的每个元素,即pi/3和pi/6。
#PyTorch会自动计算f对x中的每个元素的梯度,并将结果存储在x.grad属性中。因为x有两个元素,所以x.grad也有两个元素,它们分别对应于x中的每个元素。
#在具体的实现中,通过调用 backward() 方法,PyTorch 会自动计算 f 对每个元素的梯度,并将结果存储在对应元素的 grad 属性中。
# 因此,对于 x 中的每个元素,都可以通过访问其 grad 属性来获得其对应的梯度值。
print('grad = '.format(x.grad))
#format是一种方法,将format()中的数值替换到中

利用torch.optim.SGD类最小化函数f(X)

from math import pi
import torch

import torch.optim
x = torch.tensor([pi/3,pi/6],requires_grad = True)
optimizer = torch.optim.SGD([x,],lr = 0.1, momentum = 0)
#接下来,使用 torch.optim.SGD 函数创建一个 SGD 优化器。
# 将 x 作为优化器的参数,并设置学习率为 0.1,动量为 0。
#[x,],这是为了创建一个包含单个张量的列表(list),
#即使只有一个张量,也需要将其放在一个列表中。
#如果直接写成[x],则x会被当做张量而不是一个包含张量的列表。
for step in range(11):
    if step:
        #循环中的第一个 if 语句用于跳过第一次迭代,因为第一次迭代时没有梯度可以更新
        #这样在第二次循环中f就已经已知了就可以求后期的梯度了
        optimizer.zero_grad()
        #调用 optimizer.zero_grad() 方法将之前计算的梯度清零。
        f.backward()
        #调用 f.backward() 方法,计算函数 f 对 x 的梯度
        optimizer.step()
        #最后调用 optimizer.step() 方法来更新 x 的值,使其朝着梯度的反方向移动一个步长。
        #这里朝反方向执行一个步长是由SGD算法本身的更新规则所决定的
        #在SGD算法中,每次更新的步长为负梯度方向,即x -= lr * x.grad,
        # 其中lr为学习率,x.grad为梯度。
        # 而在这段代码中,optimizer.step()方法实际上就是对每个需要更新的参数执行这个更新操作。
        # 因此,每次执行optimizer.step()方法时,x的值都会朝着负梯度方向移动一个步长。
        #这里的 step 方法会自动使用 SGD 算法进行优化,所以我们不需要手动计算梯度和更新参数
    f = -((x.cos()**2).sum())**2
    print('step:x = ,f(x) = '.format(step,x.tolist(),f))

绘制Himmelblau函数

import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib.pyplot import cm
from matplotlib.colors import LinearSegmentedColormap
#必须要先导入Axes3D再导入其他


def himmelblau(x):
    return (x[0]**2+x[1]-11)**2+(x[0]+x[1]**2-7)**2

x = np.arange(-6,6,0.1)
#准备X轴的采样点
y = np.arange(-6,6,0.1)
#准备Y轴的采样点
X,Y = np.meshgrid(x,y)
#将X轴和Y轴的采样点整合起来得到二维的采样点
#这些二维采样点表示了整个自变量空间
Z = himmelblau([X,Y])
#调用函数himmelblau得到自变量空间上每个采样点对应的函数值

fig = plt.figure()
#创建一个新的figure对象并将其赋值给变量fig
ax = fig.gca(projection = '3d')
#调用Figure对象的gca()方法,创建一个新的3D坐标轴对象,
#将其赋值给变量ax
#gca是get current axis”的缩写,它返回当前活动的坐标轴对象。
ax.plot_surface(X, Y, Z)
#我们使用坐标轴对象的plot_surface()方法绘制Himmelblau函数的三维曲面。
# 在这里,我们将采样点X、Y和对应的函数值Z作为输入,生成一个三维曲面:
ax.view_init(60,-30)
#设置坐标轴对象的视角,以便对三维曲面更好地展示。取俯仰角和方位角分别为60度和-30度
ax.set_xlabel('x[0]')
ax.set_ylabel('x[1]')
#对坐标轴对象设置标签
fig.show()

用优化器torch.optim.Adam最小化Himmelblau函数

def himmelblau(x):
    return (x[0]**2+x[1]-11)**2+(x[0]+x[1]**2-7)**2
#对函数进行定义,接受一个一维张量作为输入,计算并返回himmelblau函数的值
import torch
x = torch.tensor([4.,3.],requires_grad=True)
#定义一维张量x,x含有两个元素,并且对x标记为需要求导。
optimizer = torch.optim.Adam([x])
#定义了一个优化器,并且这句话不可以写成
#optimizer = torch.optim.Adam(x)
#因为torch.optim.Adam 可以接收一个 torch.Tensor 类型的参数,
# 但是需要把这个参数放入一个列表中,即[x,],这是为了保持参数列表的一致性,
# 因为在 PyTorch 中,优化器的参数列表都是以列表的形式传入的,即使只有一个参数也不例外
for step in range(20001):
    if step:
        optimizer.zero_grad()
        #对优化器的梯度缓存清零
        f.backward()
        #调用函数计算张量x对应的函数f的梯度
        #这里backward是torch类自带的,import torch时就已经将相关的函数和方法导入到当前的作用域中了。所以没有import backward的语句。
        optimizer.step()
        #使用step函数对x进行更新
    f = himmelblau(x)
    if step % 1000 == 0:
        print('step:x = ,f(x) = '.format(step,x.tolist(),f))

第五章 线性回归

代码清单5-3(因为版本的问题,所以5-1到5-4只选择了这段代码进行注释解析)

import torch
import torch.nn
import torch.optim

x = torch.tensor([[1.,1.,1.],[2.,3.,1.],[3.,5.,1.],[4.,2.,1.],[5.,4.,1.]])
y = torch.tensor([-10.,12.,14.,16.,18.])
#训练数据和标签定义
w = torch.zeros(3,requires_grad=True)
#初始化模型参数w,并将其设置为可导
criterion = torch.nn.MSELoss()
#定义均方误差的损失函数
optimizer = torch.optim.Adam([w,])
#选取Adam优化器
for step in range(30001):
    if step:
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
#含义同之前的代码
    pred = torch.mv(x,w)
    #torch.mv(x,w) 是一个函数,它的作用是计算矩阵x与向量w的乘积
    #利用这个函数计算出在优化器得到的w的情况下预测值是多少
    loss = criterion(pred, y)
    #根据预测值和实际值,通过调用 torch.nn.MSELoss()函数来计算两者之间的均方误差
    if step % 1000 == 0:
        print('step = ,loss = :g,W = '.format(step,loss,w.tolist()))
        #tolist是一个方法,将张量x转换为python列表
        #在这段代码中,w 是一个形状为 $(3,)$ 的张量
        # 因为要将其输出为标准的 Python 列表
        # 所以使用了 w.tolist() 方法。
        # 由于 step 和 loss 都是标量,它们可以直接输出,所以并没有使用 tolist() 方法。

5-7 5-8 5-10 5-11

import pandas as pd
import torch
import matplotlib.pyplot as plt
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="True"



url = r'https://en.wikipedia.org/wiki/World_population_estimates'
#这里需要使https
df = pd.read_html(url, header=0, attrs="class":"wikitable")[2]
#从网页获取数据
#从指定的url中读取HTML表格,并将其解析为一个DataFrame对象
#该函数返回包含所有找到的表格的列表,每个表格都表示为DataFrame对象。由于我们只需要第三个表格,因此使用[2]索引选择了该表格。
#header=0参数指定了表格中用作列名的行,默认情况下为0。
#attrs="class":"wikitable"参数指定了要选择哪个表格。
#关于header = 0的具体解释:
#在Pandas中,DataFrame的第一行通常是用作列名的,
# 也就是说,每一列的标题都是第一行的相应值。
# 然而,有时候数据表可能没有标题行,而是将标题作为表格中的一行。
# 在这种情况下,可以使用header参数指定哪一行应该被用作列名。
# 例如,如果header=0,则第一行被视为列名。如果header=1,则第二行被视为列名,以此类推。

years = torch.tensor(df.iloc[:,0],dtype = torch.float32)
populations = torch.tensor(df.iloc[:,1],dtype = torch.float32)
#定义年份和人口两个变量,分别取DataFrame里的第一列和第二列
#但是索引是从0开始的
#dtype是对数据的类型进行定义


x = torch.stack([years,torch.ones_like(years)],1)
#在这个代码中,我们需要将years张量与一个全1张量合并,这样就得到了一个二维张量,其第一列是years张量,第二列是一个全1张量。然后我们将这个二维张量用于回归问题中的线性模型拟合。
#torch.stack 是 PyTorch 中的一个函数,用于沿着新的维度(axis)对输入的张量(tensor)进行拼接。
#我们通过 [years, torch.ones_like(years)] 构建了一个列表,其中第一个元素是 years,
# 第二个元素是一个与 years 张量形状相同但所有元素都为 1 的张量。
# torch.stack([years, torch.ones_like(years)], 1) 的第二个参数 1 表示我们要按照第二个维度(即列)进行拼接
# 所以 x 的形状是 (n, 2),其中 n 是年份的数量。
y = populations
#因变量是人口
w = x.t().mm(x).inverse().mm(x.t()).mv(y)
#利用正规方程法算得权重
slope, intercept = w
#slope和intercept是线性回归的结果,w是包含两个元素的张量,其中第一个元素是斜率,第二个元素是截距。
# 使用Python中的多变量赋值语法,可以将w中的两个元素分别赋值给slope和intercept。
# 因此,slope和intercept分别是线性回归的斜率和截距。
result = 'population = :.2e'.format(slope,intercept)
#得到结果
print('回归结果:'+ result)

#画图
plt.scatter(years, populations,s = 0.1,label = 'actual',color = 'k')
#绘制散点图,years 是横坐标数据,populations 是纵坐标数据,s=0.1 设置散点的大小,label='actual' 设置图例中的标签为“actual”,color='k' 设置散点的颜色为黑色。
estimates = [slope * yr + intercept for yr in years ]
#计算线性回归模型的预测值,slope 和 intercept 分别是线性回归模型的斜率和截距,years 是横坐标数据。
plt.plot(years,estimates,label = result,color = 'k')
#绘制线性回归模型拟合的曲线,years 是横坐标数据,estimates 是纵坐标数据,label=result 设置图例中的标签为线性回归模型的结果,color='k' 设置曲线的颜色为黑色。
plt.xlabel('year')
#添加x轴的坐标轴名称
plt.ylabel('Population')
#添加y轴的坐标轴名称
plt.legend()
#创建图例
plt.show()
#绘制出图像

第六章不是研究需要学习的基础章节所以跳过了

第七章:全连接神经网络

7-1 利用torch.nn.Sequential类构建前馈神经网络

from torch.nn import Linear,ReLU,Sequential
net = Sequential(Linear(4,2),ReLU(),Linear(2,1),ReLU())
#构建了一个第一层神经网络是4个输入2个输出,输入采用Linear函数,输出采用ReLu激活
# 函数,第二层是2个输入和1个输出,第二层的输入是第一层的输出,第二层输出为整个网
# 络的输出
#总的来说,搭建了4个输入层,2个隐藏层和1个输出层
print(net)

示例:基于全连接网络的非线性回归

#生成有噪声的数据
import torch
import torch.optim
import  torch.nn as nn

torch.manual_seed(seed=0)#固定随机数种子,这样生成的数据是确定的
sample_num = 1000#设置生成样本数量为1000
features = torch.rand(sample_num,2)*12-6#特征数据
#生成一个1000行2列的张量,每个元素是从[0,1)的均匀分布中生成的随机数,再乘以12减去6
#feature是指数据集的特征
noises = torch.randn(sample_num)
# 生成一个长度为1000的一维张量,数据是服从标准正态分布的随机数,将这个数据作为噪声
def himmelblau(x):
    return (x[:,0]**2+x[:,1]-11)**2+(x[:,0]+x[:,1]**2-7)**2
#定义 Himmeblau 函数,接受输入张量x,并计算其对应的 Himmeblau 函数函数值
hims = himmelblau(features)*0.01
#计算feature中每个样本的 Himmeblau 函数值,并乘以0.01作为数据集的标签值
labels = hims+noises #标签数据
#给标签值添加噪声,得到最终带噪声的 Himmeblau数据集

#######7-3######
train_num,validate_num,test_num = 600,200,200
train_mse = (noises[:train_num]**2).mean()
#提取noises中前600个元素提取出来形成一个新的一维张量,将张量中每个元素平方,再取这600个元素的平均值以作为train_mse
validate_mse = (noises[train_num:-test_num]**2).mean()
#提取noises中600:-200个元素提取出来形成一个新的一维张量,将张量中每个元素平方,再取这600个元素的平均值以作为validate_mse
test_mse = (noises[-test_num:]**2).mean()
#提取noises中最后200个元素提取出来形成一个新的一维张量,将张量中每个元素平方,再取这600个元素的平均值以作为test_mse
print('真实:训练集MSE = :g,验证集MSE = :g,测试集MSE:g'.format(train_mse,validate_mse,test_mse))
#.g表示使用“一般”格式来格式化数字
#在这个例子中,由于预测模型完全可以预测出Himmelblau函数的真实值,所以真实的MSE只由噪声数据的方差决定。因此,我们可以通过计算噪声数据的平均值的平方来计算真实的MSE

#构造简单的多层感知机
#输入层有2个节点,输出层有1个节点。
# 隐藏层的个数和每个隐藏层的节点数可以通过定义列表hidden_features来指定
# 例如hidden_features = [6,2]表示有2个隐藏层,第一个隐藏层有6个节点,第二个隐藏层有2个节点。
hidden_features = [6,2]
layers = [nn.Linear(2,hidden_features[0]),]
#代码定义了一个列表 layers,用于存放神经网络的各层。
# 这个列表的第一个元素是一个 nn.Linear 层,它定义了输入层和第一层隐层的连接
# 其中 2 表示输入层有两个神经元,hidden_features[0] 表示第一层隐层有 hidden_features[0] 个神经元。
# 注意,这里的 hidden_features[0] 是一个整数,而不是一个列表。
#此时,layers里面是nn.Linear(2,6)
for idx,hidden_feature in enumerate(hidden_features):#使用 enumerate 函数对列表 hidden_features 进行循环遍历
#idx,hidden_feature分别是获取隐藏层的索引和节点数
#以第一遍循环为例此时layers里面是nn.Linear(2,6)
    layers.append(nn.Sigmoid())
#现在是nn.Linear(2,6) nn.Sigmoid()
    next_hidden_feature = hidden_features[idx + 1]if idx + 1<len(hidden_features) else 1
    layers.append(nn.Linear(hidden_feature,next_hidden_feature))
#现在是nn.Linear(2,6) nn.Sigmoid() nn.Linear(6,2)
net = nn.Sequential(*layers)
#最后,代码将 layers 列表中的所有层组合成一个神经网络 net,并输出其结构。注意,这里的 * 符号是解包操作,它可以将列表中的所有元素拆分成单个元素传入 nn.Sequential 中。
#第二遍循环结束之后,变成了nn.Linear(2,6) nn.Sigmoid() nn.Linear(6,2) nn.Sigmoid() nn.Linear(2,1)
# print('神经网络为'.format(net))


#得到设置好的神经网络之后就可以对其进行训练了
optimizer = torch.optim.Adam(net.parameters())
#定义一个Adam优化器,用来被优化神经网络的参数
# net.parameters()返回神经网络中所有的可学习参数,包括权重和偏置。
criterion = nn.MSELoss()
#定义了一个均方误差(Mean Squared Error)损失函数,
# 它将用于衡量神经网络的预测值与真实标签之间的差距。
train_entry_num = 600
#定义了一个均方误差(Mean Squared Error)损失函数,
# 它将用于衡量神经网络的预测值与真实标签之间的差距。
n_iter = 10000#最大迭代次数
for step in range(10000):
    #这行代码定义了一个 for 循环,它将遍历训练神经网络的所有迭代步骤。
    outputs = net(features)
    # print(outputs)
    #给出了在输入为features的时候,经过神经网络的训练产生的输出值,feature的形状(n_samples, n_features)
    preds = outputs.squeeze()
    #将输出的形状从 (batch_size, 1) 转换为 (batch_size,),也就是将输出值的维度从 2D 变为 1D。这样做是为了计算损失函数。
    #nn.MSELoss()只接受一维张量,而outputs是二维张量。为了使它们能够计算损失,我们需要将outputs压缩成一维张量,通常使用outputs.squeeze()或者outputs.view(-1)来实现。这样就可以将(600,1)的outputs变成(600,)的一维张量,然后再计算损失。
    loss_train = criterion(preds[:train_entry_num],labels[:train_entry_num])
    #计算训练集上的均方误差
    #,它将 preds 的前 train_entry_num 个元素
    # 与 labels 的前 train_entry_num 个元素分别作为预测值和真实标签,计算它们之间的均方误差。
    loss_validate = criterion(preds[train_num:-test_num],labels[train_num:-test_num])
    #计算验证集上的均方误差。
    # 具体来说,它将 preds 的从第 train_num 个元素到倒数第 test_num 个元素的元素
    # 与 labels 的从第 train_num 个元素到倒数第 test_num 个元素的元素分别作为预测值和真实标签,计算它们之间的均方误差。
    if step % 10000 == 0:
        print('# 训练集MSE = :g,验证集MSE = :g'.format(step,loss_train,loss_validate))
        #每 10000 次迭代之后,打印当前训练集和验证集上的均方误差。
    optimizer.zero_grad()
    loss_train.backward()
    #使用自动微分技术计算preds训练集上的损失函数对神经网络的参数的梯度。
    optimizer.step()
    #用于更新神经网络的参数,即对当前的梯度按照优化算法进行更新。
    # 通过反向传播和优化算法,神经网络的参数会逐渐被更新,以最小化损失函数,提高模型的预测精度。
    #比如说利用结果对权重求偏导,利用上一刻的权重-学习率乘以偏导值就完成了参数的更新
print('训练集MSE = :g,验证集MSE = :g'.format(loss_train,loss_validate))
#是的,这里的outputs表示神经网络输出层的结果,是一个大小为(600,1)的张量。
outputs = net(features)
preds = outputs.squeeze()
loss = criterion(preds[-test_num:], labels[-test_num:])
print(loss)

#要查看神经网络第一个隐藏层的输出,可以使用nn.Sequential模型的forward()方法,将输入数据张量输入模型,然后取出第一个隐藏层的输出张量即可。例如,如果模型是这样定义的:
# model = nn.Sequential(
#     nn.Linear(2, 10),
#     nn.ReLU(),
#     nn.Linear(10, 1)
# )
#那么可以这样获取第一个隐藏层的输出:hidden_outputs = model[0](inputs)
#这里的inputs是大小为(600,2)的输入数据张量,model[0]表示获取模型的第一个子模块,即第一个线性层,然后将输入数据张量inputs输入该线性层得到输出张量hidden_outputs。

第八章:卷积神经网络

在处理图像或者其他类型的数据时,卷积神经网络(Convolutional Neural Network, CNN)是一种非常有效的神经网络。该网络可以通过在输入数据上滑动窗口的方式来检测数据中的模式,这些滑动窗口被称为卷积核(kernel)。

8-1卷积层用法示例

import torch.nn
#导入神经网络模块
conv = torch.nn.Conv2d(16,33,kernel_size=(3,5),stride=(2,1),padding=(4,2),dilation=(3,1))
#创建一个torch.nn.Conv2d实例
# 该实例代表一个卷积层(Convolutional layer)。
# 这个卷积层的输入张量的通道数为16,输出张量的通道数为33,卷积核的大小为(3,5),stride为(2,1),padding为(4,2),dilation为(3,1)。
#kernel_size:卷积核的大小,(3,5)意味着卷积核的高为3,宽为5。
#stride:卷积核的步幅,(2,1)意味着在每个方向上的步幅分别为2和1。
#padding:填充大小,(4,2)意味着在输入的每个边缘都填充了4行/列,这样输出的大小将增加8行/列。
#dilation:扩张大小,(3,1)意味着卷积核内的每个像素都将在高维方向上扩张3倍,而在宽维方向上不扩张。
inputs = torch.randn(20,16,50,100)
#创建一个大小为(20,16,50,100)的四维张量作为输入,其中20是批大小(batch size),16是通道数,50和100是输入的高和宽。
outputs = conv(inputs)
#对输入进行卷积操作,得到输出张量,输出张量的大小为(20,33,30,98),其中20是批大小,33是输出通道数,30和98是输出的高和宽。
#输出的高和宽是计算得到的计算公式:
#输出高度 = (输入高度 + 2 * 上下填充 - 卷积核高度 - (卷积核高度 - 1)* (dilation[0]-1)) / 步长[0] + 1
# 输出宽度 = (输入宽度 + 2 * 左右填充 - 卷积核宽度 - (卷积核宽度 - 1)* (dilation[1]-1)) / 步长[1] + 1
print(outputs.size())
#输出输出张量的大小

8-2torch.nn.Unsample类用法示例

import torch
import torch.nn as nn
inputs = torch.arange(1,5).view(1,1,2,2).float()
#输入是批次大小为1批,通道数为1,高为2,宽为2的四维张量
#关于输入的生成:
#利用torch.arange(1, 5)生成一个一维张量,包含从1到4的数字,即 tensor([1, 2, 3, 4])
#view 会将这个一维张量重塑为二维张量 (1,1,2,2),其中第一维表示批次大小,第二维表示通道数,第三维和第四维表示高度和宽度。
#最后会得到tensor([[[[1,2],[3,4]]]])
upample = nn.Upsample(scale_factor=2,mode='nearest')
upample(inputs)
#通过实例化nn.Upsample模块并指定scale_factor=2和mode='nearest',表示将输入张量的高和宽都放大两倍,使用最近邻插值的方式进行填充。最后,将输入张量输入到upample模块中,得到上采样后的张量。

8-3各种类型的补全层的用法

import torch
import torch.nn as nn
inputs = torch.arange(12).view(1,1,3,4).float()
pad = nn.ConstantPad2d(padding=[1,1,1,1],value=-1)
#value = -1表示使用常数值-1进行填充
print('常数补全 = '.format(pad(inputs)))
pad = nn.ReplicationPad2d(padding=[1,1,1,1])
print('重复补全 = '.format(pad(inputs)))
pad = nn.ReflectionPad2d(padding=[1,1,1,1])
print('反射补全 = '.format(pad(inputs)))
#代码首先生成一个大小为 [1,1,3,4] 的四维张量 inputs,然后利用 nn.ConstantPad2d、nn.ReplicationPad2d 和 nn.ReflectionPad2d 三种不同的 padding 方法,对 inputs 进行了常数、重复和反射的填充,最终输出了填充后的结果。
#因为 nn.ReplicationPad2d不支持long类型的输入,所以和书上代码不同,在这里后面利用了float的类型转换

8-4和8-5是一个意思,8-5更简洁,所以选择了8-5进行实验,然后8-5被8-6包含,所以取8-6进行注释

#实现了一个简单的卷积神经网络对MNIST手写数字数据集进行分离
import torch.nn
import torch
import torch.utils.data
import torch.optim
import torchvision.datasets
import torchvision.transforms
#导入需要的pytorch库、包括神经网络的相关模块(torch.nn)
# PyTorch核心库(torch)、数据集加载器(torch.utils.data)、优化器(torch.optim)
# 以及加载数据集所需的相关库(torchvision.datasets、torchvision.transforms)


#数据读取
train_dataset = torchvision.datasets.MNIST(root = './data/mnist',train = True,transform = torchvision.transforms.ToTensor(),download = True)
test_dataset = torchvision.datasets.MNIST(root = './data/mnist',train = False,transform = torchvision.transforms.ToTensor(),download = True)
#使用torchvision中的MNIST函数来下载和加载MNIST数据集。
# 参数root指定了数据集保存的位置
# train=True表示使用训练集数据,train=False表示使用测试集数据。
# transform指定了对图像进行的预处理操作,这里使用了ToTensor()函数将数据集中的图像转换成PyTorch中的Tensor类型。download=True表示如果数据集还没有下载,则需要下载。
batch_size = 100
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,batch_size = batch_size)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset,batch_size = batch_size)
#将数据集加载到DataLoader中,以便进行批量处理。
# batch_size指定了每个批次的大小。train_loader和test_loader分别表示训练集和测试集的DataLoader。
#在使用 DataLoader 时,需要指定一个数据集(即 dataset 参数),并设置批次大小(即 batch_size 参数)。DataLoader 还可以设置其他参数,例如是否使用多线程加载数据等。加载完成后,每次迭代时,DataLoader 会返回一个批次的数据。
#dataset 是 PyTorch 中用来表示数据集的类,torch.utils.data.DataLoader 是 PyTorch 中用来读取数据的类,它可以将数据集按照 batch_size 分成小批量数据并返回。

#搭建网络结构
class Net(torch.nn.Module):

    def __init__(self):
        super(Net,self).__init__()
        self.conv = torch.nn.Sequential(
            torch.nn.Conv2d(1,64,kernel_size=3,padding=1),
            torch.nn.ReLU(),
            torch.nn.Conv2d(64,128,kernel_size=3,padding=1),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d(stride=2,kernel_size=2))
        self.dense = torch.nn.Sequential(
            torch.nn.Linear(128*14*14,1024),
            torch.nn.ReLU(),
            torch.nn.Dropout(p = 0.5),
            torch.nn.Linear(1024,10)
        )

    def forward(self,x):
        x = self.conv(x)
        x = x.view(-1,128*14*14)
        x = self.dense(x)
        return x

net = Net()
#定义了卷积神经网络模型‘Net’,在Net的构造函数中,首先调用父类构造函数super(Net, self).__init__()进行初始化。
# 接着,self.conv定义了一个包含两个卷积层和一个最大池化层的卷积神经网络。例如:第一个卷积层输入通道为1,输出通道为64,卷积核大小为3,padding大小为1。ReLU函数是非线性激活函数
#其中一个是包含多个卷积层和ReLU激活函数的序列conv,另一个是包含多个全连接层和ReLUctant激活函数的序列'dense'
#在 forward 方法中,将输入数据 x 通过 conv 序列进行卷积计算
# 然后使用 view 方法将张量形状转换为一维,并将其传递给 dense 序列进行全连接计算,得到模型的输出。
# 最后,通过实例化 Net 类,创建了一个名为 net 的模型实例。

criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(net.parameters())
#使用交叉熵损失函数 torch.nn.CrossEntropyLoss() 和优化器 torch.optim.Adam() 对模型进行训练
#要先对损失函数和优化器进行定义

#训练
num_epochs = 5
for epoch in range(num_epochs):
    for idx,(image,labels) in enumerate(train_loader):
        optimizer.zero_grad()
        preds = net(image)
        loss = criterion(preds,labels)
        loss.backward()
        optimizer.step()
        #先对优化器梯度缓存进行清零,然后通过网络前向传播得到预测结果 preds
        # 再将其与实际标签 labels 进行比较得到损失 loss,最后进行反向传播并执行优化器的 step() 方法更新网络权重参数。
        if idx%100 == 0:
            print('epoch,batch,损失 = :g'.format(epoch,idx,loss.item()))
#epoch是一个训练周期,表示用全部训练数据集训练了一遍神经网络,也就是说
# 训练数据集被分为了多个批次,网络将每个批次的数据都训练一遍。一般来说,一个epoch会包含多个训练步骤,每个步骤使用一个小批次数据进行训练。
#batch_size是一个批次的大小,也就是每个小批次数据的样本数量。
# 在训练的时候,每个批次的数据被送入网络进行计算。
# 较小的batch_size可以让训练更快、更稳定,但会占用更多的内存。
# 较大的batch_size可以充分利用GPU等硬件资源,提高计算效率
# 但在训练过程中可能会出现内存不足或GPU内存不足的情况。


#测试
correct = 0
total = 0
for image,labels in test_loader:
    preds = net(image)
    predicted = torch.argmax(preds,1)
    total += labels.size(0)
    correct +=(predicted == labels).sum().item()
#这里我们使用了一个for循环,从测试数据集中逐一读取图像和标签,并使用训练好的模型对这些图像进行预测。
# 首先,我们定义了两个变量correct和total,用于记录模型在测试集上的正确预测数和总样本数。
# 接着,我们使用test_loader迭代器依次读取测试集中的数据。对于每一个图像,
# 我们使用net(image)来对其进行预测,得到一个10维的输出向量preds,
# 表示每个数字类别的预测概率。我们使用torch.argmax函数获取预测概率最大的类别的索引,作为预测结果predicted。
# 然后,我们将该批次图像的标签数目累加到total变量中。
# 同时,如果预测结果和标签相同,则将该批次中的正确预测数correct加1。
# 最后,我们计算出测试数据的准确率,并使用print函数将其打印出来。
accuracy = correct/total
print('测试数据准确率:.1%'.format(accuracy))
#在训练过程中,我们通过反向传播来更新网络中的权重和偏差,而在测试过程中,
# 我们只是简单地将输入图像馈送给网络,然后使用训练期间得到的权重和偏差计算输出。
# 因此,preds = net(image) 中调用的 net 网络对象使用的是训练期间已经学习到的权重和偏差,而不是随机的初始值。

第九章 循环神经网络

9-1 构造RNNCell类实例并查看变量内容

import torch.nn
cell = torch.nn.RNNCell(input_size=3, hidden_size=5)#输入特征维度是3,隐藏状态维度为5
for name, param in cell.named_parameters():
    print(' = '.format(name,param))
    #打印出RNNCell中的所有参数(权重和偏置)
    #这段代码没有涉及到具体的时间步操作,只是展示了RNNCell的参数。如果要在时间序列数据上
    #运用这个RNN单元,需要在每个时间步分别调用该单元,从而实现时间步的迭代。

9-2构建LSTMCell类实例并用其搭建单向单层循环神经网络

#使用pytorch实现单层LSTM网络,将输入序列inputs经过LSTM处理后输出结果outputs
import torch
import torch.nn
seq_len, batch_size = 6, 2#定义输入序列的长度和批次大小,输入序列长度可以理解为代码中的时间步
input_size, hidden_size = 3, 5#输入特征维度为3,隐藏状态维度为5(输入有3个特征,可以这么理解)
#调用torch.nn.LSTMCell函数创建一个LSTM单元,指定输入特征的维度和隐藏状态维度
cell = torch.nn.LSTMCell(input_size=input_size, hidden_size=hidden_size)
#定义输入序列inputs,括号对应的是(序列长度,批次大小,输入特征维度)
inputs = torch.randn(seq_len, batch_size, input_size)
#定义两个初始化的隐藏状态张量h和c,括号对应的形状是(批次,维度),利用随机数进行初始化
h = torch.randn(batch_size, hidden_size)#短期记忆状态
c = torch.randn(batch_size, hidden_size)#长期记忆状态
#创建保存中间步骤的列表hs
hs = []
#遍历每一个时间步t,对于每个时间步,调用LSTM单元的__call__方法,输入当前时刻的输入特征和上一个时间步的隐藏状态(h,c)
#计算当前时刻的输出h和新的隐藏状态c,并将h加入到列表hs中保存
for t in range(seq_len):
    h, c = cell(inputs[t], (h, c))#inputs[t]的形状是(批次大小,输入特征大小)
    hs.append(h)
#调用torch.stack将列表hs中所有的输出h拼接成形状为(时间步,批次,隐藏状态维度的张量作为输出结果)
outputs = torch.stack(hs)#因为要表示LSTM模型在整个序列上的输出结果。重新拼接成这个形状为(时间步、批量大小、输出隐藏状态维度)
print(outputs)



 9-3 使用torch.nn.GRU类实例

#使用pytorch中的GRU模型进行前向计算,并输出结果
import torch
import torch.nn
num_layers = 2#指定了模型的层数,也就是说该模型中有多少个GRU单元可以叠加在一起进行运算
seq_len, batch_size = 6, 2#定义时间步和批次大小
input_size, hidden_size = 3, 5#定义输入维度和隐藏状态维度
#构造GRU模型
rnn = torch.nn.GRU(input_size, hidden_size, num_layers = num_layers)
#使用randn生成大小为(时间步,批次大小,输入维度)的输入序列
inputs = torch.randn(seq_len, batch_size, input_size)
#使用randn生成大小为(GRU模型层数,批量大小,隐藏状态维度)的隐藏状态序列
h0 = torch.randn(num_layers, batch_size, hidden_size)#多层GRU模型的初始隐藏状态,包含每一层GRU单元的隐藏状态
#将输入和隐藏状态输入到模型中得到输出张量和最终的隐藏状态张量
#输出张量大小:(seq_len, batch_size, input_size)
#隐藏状态张量大小:(num_layers, batch_size, hidden_size)
outputs, hn = rnn(inputs, h0)
print(outputs)
#关于:
#为什么是rnn = torch.nn.GRU(input_size, hidden_size, num_layers = num_layers)
# outputs, hn = rnn(inputs, h0),而不是outputs, hn = torch.nn.GRU(inputs, h0)
#回答:
#在pytorch中,神经网络是由类(class)实例化之后得到的对象,所以先要实例化一个GRU对象
#rnn,然后才能用他来处理输入和状态h0,得到输出和最终状态hn
#所以,代码中的 rnn = torch.nn.GRU(input_size, hidden_size, num_layers=num_layers)
# 的作用是创建了一个 GRU 模型对象
# 接着通过 rnn(inputs, h0) 的方式对输入和状态进行处理,返回输出和最终状态。
#如果直接使用 outputs, hn = torch.nn.GRU(inputs, h0) 这种方式
# 会直接调用 torch.nn.GRU 类的静态方法,而不是实例化得到一个对象
# 这样就无法对该 GRU 模型进行配置、训练和保存等操作,因此需要先实例化一个 GRU 对象。

##也就是说,只有在实例化之后,才可以对网络进行配置、训练和保存操作。

9-4 从世界银行获取各国历年GDP数据

9-5神经网络的搭建

9-6训练循环神经网络

9-7使用训练好的网络做预测

import os
os.environ['KMP_DUPLICATE_LIB_OK']='True'

#使用wb模块获取世界银行的数据
from pandas_datareader import wb
import pandas as pd
#定义需要获取的国家列表
countries = ['BR','CN','FR','DE','IN','JP','SA','GB','US']
#使用wb模块中的download函数获取数据,这个函数接受参数包括数据指标、国家列表、起始年份和终止年份
#dat是多行三列的形式,第一列是国家,第二列式年份,第三列是GDP
dat = wb.download(indicator = 'NY.GDP.PCAP.KD', country = countries, start = 1970, end = 2016)
#对数据进行重塑,即把不同的国家、年份和人均GDP数据转换为行列的形式
#会生成第一级索引是国家,第二级索引是年份的多级索引DataFrame
df = dat.unstack().T
#修改数据的行索引,去掉原来的年份列,只保留国家列
df.index = df.index.droplevel(0)
pd.set_option('display.max_columns', None)  # 显示所有列
pd.set_option('display.max_rows', None)  # 显示所有行
print(df)
###df = dat.unstack().T
###df.index = df.index.droplevel(0)
#这两句代码的作用是将 dat 数据框中的多级索引展开成单层索引,并将行和列进行转换
# 以得到更加便于分析和处理的数据框。多级索引是指数据框的索引由多个层级组成
# 通常由多个因素共同作用所形成。
# 在这个例子中,dat 数据框的索引由两个层级组成,分别是国家代码和年份
# 通过 unstack() 方法展开成单层列索引。然后通过 T 方法将行列进行转换,以方便后续的数据处理和分析。
#第二句代码 df.index = df.index.droplevel(0) 是将展开后的单层索引中的第一层级(即国家代码)删除
# 只保留年份这个单一的索引层级。

#神经网络的搭建
import torch.nn

class Net(torch.nn.Module):#定义了一个名为“Net”的神经网络模型,这个模型继承自torch.nn.Module

    def __init__(self, batch_size, hidden_size):#定义模型初始化函数,(self,输入数据特征数,隐藏层神经元数)
        super(Net, self).__init__()
        # 定义了一个LSTM层(输入维度,隐藏层维度)
        self.rnn = torch.nn.LSTM(batch_size, hidden_size)
        #定义全连接层,输入维度是hidden_size,输出维度为1
        self.fc = torch.nn.Linear(hidden_size, 1)

    # 定义了前向传播层,x为输入数据,形状为(批次,)
    def forward(self, x):
        #增加维度,因为LSTM的输入形状为(input_size,seq_len,batch_size)
        x = x[:, :, None]
        #传递给LSTM层进行处理
        x, _ = self.rnn(x)
        #传递给全连接层进行线性变换
        x = self.fc(x)
        #选择第一个维度进行输出
        x = x[:, :, 0]
        return  x

net = Net(batch_size=1, hidden_size=5)

import torch
import torch.optim

#数据归一化
df_scaled = df / df.loc['2000']#这里要注意对元组元素的取法

#确定训练集和测试集
years = df.index.astype(int)#取出数据对应的年代标签
#根据年份区分训练集和测试集
train_seq_len = sum((years >= 1971) & (years <= 2000))
test_seq_len = sum(years > 2000)
print('训练集长度 = , 测试集长度 = '.format(train_seq_len, test_seq_len))

#确定训练使用的特征和标签
#使用pandas库读取缩放后的数据文件,并将其转换为torch.tensor对象,[:-1]表示取除了最后一行之外的所有行
inputs = torch.tensor(df_scaled.iloc[:-1].values, dtype=torch.float32)
labels = torch.tensor(df_scaled.iloc[1:].values, dtype=torch.float32)


#训练网络
#定义损失函数
criterion = torch.nn.MSELoss()
#定义优化器
optimizer = torch.optim.Adam(net.parameters())
for step in range(10001):
    if step:
        # 优化器计算结果清零
        optimizer.zero_grad()
        # 计算各个参数对于损失的梯度(利用损失函数对各个参数求偏导),并将优化信息存储在优化器中
        train_loss.backward()
        #根据梯度信息对模型参数进行更新
        optimizer.step()

    #利用定义好的神经网络模型对输入做输出结果的预测
    preds = net(inputs)
    #训练集上的预测值
    train_preds = preds[:train_seq_len]
    #训练集上的测试标签
    train_labels = labels[:train_seq_len]
    #计算训练集上的损失
    train_loss = criterion(train_preds, train_labels)

    #测试集上的预测值
    test_preds = preds[-test_seq_len]
    #测试集的标签
    test_labels = labels[-test_seq_len]
    #计算测试集的结果
    test_loss = criterion(test_preds, test_labels)

    if step % 500 == 0:
        print('第次迭代:loss(训练集) = , loss(测试集) = '.format(step,train_loss, test_loss))

#使用训练好的网络做预测
from IPython.display import display
#将输入数据输入到经过训练的神经网络中,以得到神经网络的输出值
preds = net(inputs)
#将神经网络输出预测值的张量转换为DataFrame的形式
df_pred_scaled = pd.DataFrame(preds.detach().numpy(), index=years[1:], columns=df.columns)
# 将缩放后的神经网络输出预测值乘以原始数据在年份2000的值,得到实际数据预测值
df_pred = df_pred_scaled * df.loc['2000']
#将预测的数据以DataFrame的形式显示在NoteBook中
display(df_pred.loc[2001:])

以上是关于《神经网络与pytorch实战》肖智清著部分代码复现与注释,包括使用pytorch搭建CNNRNNLSTM等基础神经网络的主要内容,如果未能解决你的问题,请参考以下文章

深度学习理论与实战PyTorch实现

卷积神经网络实战——表情识别(Pytorch)超详细理解,含Pyqt5的可操作界面

深度学习之PyTorch实战——迁移学习

PyTorch从入门到精通100讲-Pytorch Geometric 从原理到实战应用案例(附代码)

有没有pytorch的高级应用教程推荐一下

Pytorch深度学习实战3-5:详解计算图与自动微分机(附实例)