下面的若干段代码是循循渐进的,而且保证了每段代码只对前一段代码进行最小程度的修改,方便连续阅读,这也是我学习的过程,有错误的地方希望不吝赐教,共同进步。
10个样本,每个样本特征数为1,需要训练的参数为一个特征权重和一个偏置,所以总共两个要训练的超参数。
采用批量梯度下降法,即使用全部样本的梯度更新权重和偏置。
"""
手工实现 y=wx+b 线性回归(梯度下降法)
"""
import torch
import numpy as np
def generate_dataset(true_w, true_b, num_examples, random_seed=10) :
"""
生成数据集
:param true_w: 规定的准确权重
:param true_b: 规定的准确偏置
:param num_examples: 样本个数
:param random_seed: 随机种子,保证每次执行都能生成相同的数据集
:return: 特征与标签(真实值)
"""
torch.manual_seed(random_seed) # 为CPU设置随机种子
np.random.seed(random_seed) # Numpy module
x = torch.randn(num_examples, dtype=torch.float32) # 随机生成若干个数据
y = true_w * x + true_b # 因变量的值
y += torch.normal(0, 0.01, size=y.size(), dtype=torch.float32) # 加入噪声
return x, y
def model(x, w, b) :
"""
模型,计算预测的值,即y_hat
:param x: 特征
:param w: 预测的权重
:param b: 预测的偏置
:return: 每个样本对应的预测值
"""
return w * x + b
def loss(y_hat, y) :
"""
损失函数,计算损失值,每个样本的预测值与真实值之间的误差的平方的均值
:param y_hat: 预测值
:param y: 真实值
:return: 损失值
"""
return ((y_hat - y) ** 2).mean()
def calculate_gradient(x, y, w, b) :
"""
计算梯度值,即loss对w的偏导和loss对b的偏导
:param x: 样本特征
:param y: 真实值
:param w: 预测的权重
:param b: 预测的偏置
:return: 返回loss对w的偏导和loss对b的偏导
"""
y_hat = model(x, w, b) # 计算预测值
dloss_dw = (2 * (y_hat - y) * x).mean() # loss对w的偏导
dloss_db = (2 * (y_hat - y) * 1).mean() # loss对b的偏导
return torch.tensor([dloss_dw, dloss_db], dtype=torch.float32) # 这里是将两个数合成一个tensor去操作
def tarin_model(num_epochs, learning_rate, params, x, y) :
"""
模型训练,梯度下降法更新 w 和 b
:param num_epochs: epoch次数
:param learning_rate: 学习率
:param params: w 和 b构成的一维tensor
:param x: 样本特征
:param y: 真实值
:return: 最终训练完毕的 w 和 b
"""
for epoch in range(1, num_epochs + 1) :
w, b = params
y_hat = model(x, w, b)
loss_value = loss(y_hat, y)
gradient = calculate_gradient(x, y, w, b)
params -= learning_rate * gradient # 更新 w 和 b
print("Epoch %d Loss %f" % (epoch, loss_value.item()))
return w, b
"""
生成数据集
"""
x, y = generate_dataset(true_w=2.5, true_b=4.2, num_examples=10)
# print(x, y)
"""
初始化 w 和 b
"""
w = 0
b = 0
"""
计算梯度
"""
grad_w, grad_b = calculate_gradient(x=x, y=y, w=w, b=b)
"""
反向传播,更新 w 和 b
"""
w, b = \
tarin_model(
num_epochs=100,
learning_rate=0.1,
params=torch.tensor([w, b], dtype=torch.float32),
x=x,
y=y
)
print("After Training w = %f b = %f" % (w, b))
使用批量梯度下降,即使用全部样本更新权重和偏置。
因为w
和b
是两个数,所以可以使用torch.tensor([w, b])
将两个数合并成一个tensor,但是如果w
和b
本身就是tensor,是不可以这样合并成更高维度的tensor的,需要使用torch.stack([w, b])
。
由于本数据集过于完美,所以收敛效果比较好,设置大的学习步长也没问题,但其他数据集就不一定了。
既然我们学习了backward()
,那尝试用backward()
实现一下。
首先,说明一下,下面代码中所用数据集为1000个样本,每个样本具有2个特征。为了计算方便,我将权重系数w和偏置b合在一起进行计算了,即代码中的w表示的就是w和b,所以代码中的w其实是三维的,同样的道理,样本本来是1000行2列,但由于w和b的合并,所以样本矩阵变为1000行3列了,新增的最后一列全1。
而且下面采用了小批量梯度下降法。
import torch
import numpy as np
def generate_dataset(true_w, num_examples, random_seed=10) :
"""
生成数据集
:param true_w: 规定的准确权重(含偏置)
:param num_examples: 样本个数
:param random_seed: 随机种子,保证每次执行都能生成相同的数据集
:return: 特征与标签(真实值)
"""
# ----- 保证每次的随机数都一样 -----
torch.manual_seed(random_seed) # 为CPU设置随机种子
np.random.seed(random_seed) # Numpy module
# ----- 生成特征信息 -----
x = torch.ones(size=(num_examples, 3), dtype=torch.float32) # 1000×3的全1张量 # 这里不用加requires_grad=True,因为我们最后是对w(含b)求导
x[:,:2] = torch.randn(size=(num_examples, 2), dtype=torch.float32) # 随机生成num_examples个数据,每个数据两个特征,即将x的前两列改为特征,最后一列保持为1
# ----- 生成标签信息 -----
y = torch.mm(x, true_w) # 进行矩阵乘法 x * w => 1000×3 * 3×1 = 1000×1 # y和x同理,无需加requires_grad=True
y += torch.normal(0, 0.01, size=y.size(), dtype=torch.float32) # 加入噪声
return x, y
def model(x, w) :
"""
模型,返回每个样本对应的预测值,即y_hat
:param x: 特征+一列1
:param w: 预测的权重(含偏置)
:return: 每个样本对应的预测值
"""
return torch.mm(x, w) # 矩阵乘法
def loss(y_hat, y) :
"""
损失函数,计算损失值,每个样本的预测值与真实值之间的误差的平方的均值
:param y_hat: 预测值
:param y: 真实值
:return: 损失值
"""
return ((y_hat - y) ** 2).mean()
# 与“纯手写线性回归”不同,这里不需要写计算梯度的函数了,因为backward自动计算
def train_model(num_epochs, learning_rate, batch_size, w, x, y) :
"""
训练模型,本质就是更新 w 。
每个epoch内进行多次更新,每次更新都使用一个batch,循环完一次全部的样本才算一个epoch结束。
:param num_epochs: epoch次数
:param learning_rate: 学习率
:param batch_size: 每个batch大小,即一个batch多少个样本
:param w: 训练的权重(含偏置)
:param x: 样本特征
:param y: 样本标签(真实值)
:return: 训练完成后的权重(含偏置)
"""
num_examples = len(x) # 样本数就是x的行数 # 对于二维矩阵而言,len返回的是行的数量
for epoch in range(1, num_epochs + 1) :
disorderly_idx = list(np.arange(num_examples)) # 生成0~num_examples-1的序列,即索引
np.random.shuffle(disorderly_idx) # 随机打乱索引,得到乱序索引
for i in range(0, num_examples, batch_size) : # 每次循环访问batch_size个索引,作为一组batch
batch_idx = disorderly_idx[i : min(i + batch_size, num_examples)] # 获取到这组batch的索引 # 之所以存在一个min,是因为防止出现最后一组不足一个batch的情况
batch_x = x.index_select(0, torch.LongTensor(batch_idx)) # 取出对应行,索引必须为Int64
batch_y = y.index_select(0, torch.LongTensor(batch_idx)) # 取出对应行,索引必须为Int64
# ----- 使用这组 batch 更新 w
loss_value = loss(model(batch_x, w), batch_y) # 有关小批量batch_x和batch_y的损失值
loss_value.backward() # 反向传播,计算梯度!
w_data = w.detach() # w是叶子节点,欲修改其值需要使用detach或data
w_data -= learning_rate * w.grad # 小批量梯度下降(由于计算损失值的时候已经取过均值了,所以这里就不再除以batch_size了)
w.grad.data.zero_() # 不要忘了梯度清零,而且要使用.data或.detach()
print("Epoch %d Loss %f" % (epoch, loss(model(x, w), y).item())) # 显示全部数据集用当前训练得到的w去预测,得到的损失值
return w
"""
生成数据集
注意:样本的特征是两维(样本数×2),所以权重矩阵w本应为两维(样本数×2),
但可以将偏置b放入权重矩阵w中,即w为三维(样本数×3),最终结果w的最后一维(列)就是b。
对应地,样本矩阵也要扩展一维至三维,即最后一维(列)为全1。
"""
num_examples = 1000
num_features = 2 # 样本特征数为2,但特征矩阵列数为3,同样的权重矩阵列数也为3,所以要求num_features始终比true_w中元素个数少1
true_w = torch.tensor([2.5, -1.5, 3.5], dtype=torch.float32).view(-1, 1) # 转换成1列
x, y = \
generate_dataset(
true_w=true_w,
num_examples=num_examples
)
"""
初始化 w(含偏置b)
"""
w = torch.ones(size=(num_features + 1, 1), dtype=torch.float32, requires_grad=True) # 注意+1(要求num_features始终比true_w中元素个数少1) # 不要忘记requires_grad=True
"""
训练模型
其中包含反向传播、计算梯度、更新 w 等过程
"""
w = train_model(num_epochs=10, learning_rate=0.01, batch_size=10, w=w, x=x, y=y)
print("True wieght true_w = ", true_w.storage().tolist()) # tensor.stroage()相当于将张量拉伸成一维的,之后使用tolist()转换为列表
print("After Training w = " , w.data.storage().tolist())
明确哪些变量是要进行设置requires_grad
,哪些不需要。
注意叶子节点更新值的时候要使用.data
或.detach()
。
学习如何实现小批量梯度下降。
从最开始”纯手工实现线性回归“中需要自己写计算梯度的过程,需要自己求出导函数;到”使用 backward() 实现线性回归“中调用自带的 backward() 函数实现计算梯度的过程。我们发现调用现有的函数多是一件美事啊!下面就尝试调用更多的现有函数来实现线性回归
(如果有些函数或者类之前的学习笔记没有讲过,会在使用时补充到用到该函数的博客中,但至于会不会添加到学习笔记三中,另说。也就是说知识点的补充可能会比较零散)。
创建一个继承自 Pytorch 中的 nn.Module 的类来实现神经网络,这样可以使用 Pytorch 提供的许多高级 API,而无需自己实现。
基于 nn.Module的类的最低要求是覆盖__init__()
方法和forward()
方法。
基本形式如下:
class myModel(nn.Module):
def __init__(self):
# 继承父类构造函数
super(myModel, self).__init__()
# 这里我们定义一些层次实例。
self.my_conv_layer = nn.Linear()
def forward(self, x):
# 这里我们调用在__init__中定义好的层次实例。
y_hat = my_conv_layer(x)
return y_hat
简单来说,在__int__
函数内我们定义实例,相当于构建出整个网络的架构;在forward
函数内只需要简单地调用定义好的实例就行,偶尔处理一下数据的维度等。
使用创建好的模型的方法:y_hat = myModel(x)
。
但是这个网络并不具有计算损失函数、进行更新等功能。因为这些功能都是网络结构之外的,我们只是定义了一个网络而已,也就是说,这个网络只有前向传播的能力,即进行预测。整体来看,和我们自定义的model
函数没什么区别,都是输入样本x
,预测值输出y_hat
。
不妨看个“高级”的网络模型:(千万不要把每一处都搞明白,这样浪费时间,看清楚结构即可)
class SimpleCNN(nn.Module):
def __init__(self):
#继承父类构造函数
super(SimpleCNN, self).__init__()
# 这里我们定义一些层次实例。
# 比如:
# 先卷积,再relu,再池化
self.myconv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=(3, 3), padding=1, stride=1, bias=True)
self.myrelu1 = nn.ReLU(inplace=True)
self.mymaxpooling1 = nn.MaxPool2d(kernel_size=(2, 2), stride=1)
# 先卷积,再relu,再池化
self.myconv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=(3, 3), padding=1, stride=1, bias=True)
self.myrelu2 = nn.ReLU(inplace=True)
self.mymaxpooling2 = nn.MaxPool2d(kernel_size=(2, 2), stride=1)
# 先卷积,再relu,再池化
self.myconv3 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=(3, 3), padding=1, stride=1, bias=True)
self.myrelu3 = nn.ReLU(inplace=True)
self.mymaxpooling3 = nn.MaxPool2d(kernel_size=(2, 2), stride=1)
# 开始全连接 2048 -> 512 -> 64 -> 10
self.myfullconnected1 = nn.Linear(in_features=2048, out_features=512)
self.myrelu4 = nn.ReLU(inplace=True)
self.myfullconnected2 = nn.Linear(in_features=512, out_features=64)
self.myrelu5 = nn.ReLU(inplace=True)
self.myfullconnected3 = nn.Linear(in_features=64, out_features=10)
def forward(self, x):
# 这里我们调用在__init__中定义好的层次实例。
conv1 = self.myconv1(x)
relu1 = self.myrelu1(conv1)
maxpooling1 = self.mymaxpooling1(relu1)
conv2 = self.myconv2(maxpooling1)
relu2 = self.myrelu2(conv2)
maxpooling2 = self.mymaxpooling2(relu2)
conv3 = self.myconv3(maxpooling2)
relu3 = self.myrelu3(conv3)
maxpooling3 = self.mymaxpooling3(relu3)
output = maxpooling3.view(maxpooling3.size(0), -1) # 不要忘记Linear函数的输入必须是二维的!
fullconnected1 = self.myfullconnected1(output)
relu4 = self.myrelu4(fullconnected1)
fullconnected2 = self.myfullconnected2(relu4)
relu5 = self.myrelu5(fullconnected2)
y_hat = self.myfullconnected3(relu5)
return y_hat
基础好的已经看出这是个手写数字识别的网络了(单看最后10个输出就知道了)。
这里只是让大家看看“学习笔记三”中的一些函数可以如此用,整体上把握网络结构即可,现在还不用能自己写出来。
我觉得学习pytorch,最重要的是模仿,先模仿着写,再明白为什么。这和我们入门人生中的第一门语言C语言是一样的道理,最开始大家都是先去试着写printf,但其实并不知道用法。
import torch
import numpy as np
import torch.nn as nn
"""
定义简单的线性神经网络。
输入样本具有二维特征,输出为一维预测标签,学习时含偏置。
"""
class MyLinearRegression(nn.Module) :
def __init__(self): # 注意别把init写成int
# 继承父类构造函数
super(MyLinearRegression, self).__init__()
# torch.nn.Linear(in_features, out_features, bias=True)
# 输入样本的特征数为2,输出特征数(标签维度)为1,加上偏置
self.linear = nn.Linear(in_features=2, out_features=1, bias=True)
def forward(self, x):
"""
前向传播。
对于该类来说就是进行线性计算。
:param x: 输入样本特征值
:return: 返回计算结果,输出特征值,即预测标签
"""
y_hat = self.linear(x)
return y_hat
"""
生成数据集,该函数与上面代码的不同在于不再将偏置b算入权重w中了,因为Linear函数我们设置了bais=True,会将bais当作一个单独的可学习参数进行训练
"""
def generate_dataset(true_w, num_examples, random_seed=10) :
"""
生成数据集
:param true_w: 规定的准确权重(含偏置)
:param num_examples: 样本个数
:param random_seed: 随机种子,保证每次执行都能生成相同的数据集
:return: 特征与标签(真实值)
"""
# ----- 保证每次的随机数都一样 -----
torch.manual_seed(random_seed) # 为CPU设置随机种子
np.random.seed(random_seed) # Numpy module
# ----- 生成特征信息 -----
x = torch.randn(size=(num_examples, 2), dtype=torch.float32) # 随机生成num_examples个数据,每个数据两个特征
# ----- 生成标签信息 -----
y = torch.mm(x, true_w) # 进行矩阵乘法 x * w
y += torch.normal(0, 0.01, size=y.size(), dtype=torch.float32) # 加入噪声
return x, y
if __name__ == "__main__" :
# 创建 MyLinearRegression() 的实例
model = MyLinearRegression()
print(model) # 显示模型层次
# ----- 规定一些参数的值 -----
num_examples = 1000 # 1000个样本
batch_size = 10
num_epochs = 10 # 迭代次数
learning_rate = 1e-2 # 学习率 0.01
mse_loss = torch.nn.MSELoss() # 损失函数,均方误差
true_w = torch.tensor([2.5, -1.5], dtype=torch.float32).view(-1, 1) # 转换成1列,为了方便进行矩阵乘法。其实放在generate_dataset函数内实现也可
true_b = torch.tensor([3.5], dtype=torch.float32)
# ----- -----
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate) # 优化函数
# 生成数据集
x, y = \
generate_dataset(
true_w=true_w,
num_examples=num_examples
)
# 迭代更新
for epoch in range(1, num_epochs+1) :
disorderly_idx = list(np.arange(num_examples)) # 生成0~num_examples-1的序列,即索引
np.random.shuffle(disorderly_idx) # 随机打乱索引,得到乱序索引
for i in range(0, num_examples, batch_size):
# 生成batch
batch_idx = disorderly_idx[i: min(i + batch_size, num_examples)] # 获取到这组batch的索引 # 之所以存在一个min,是因为防止出现最后一组不足一个batch的情况
batch_x = x.index_select(0, torch.LongTensor(batch_idx)) # 取出对应行,索引必须为Int64
batch_y = y.index_select(0, torch.LongTensor(batch_idx)) # 取出对应行,索引必须为Int64
# 预测
batch_y_hat = model(batch_x)
# 计算损失值
loss_value = mse_loss(batch_y_hat, batch_y)
# 反向传播
loss_value.backward()
# 一旦梯度被如backward()之类的函数计算好后,我们就可以调用这个函数更新参数
optimizer.step()
# 将可训练参数的梯度清零
optimizer.zero_grad()
print("Epoch %d Loss %f" % (epoch, mse_loss(model(x), y).item()))
torch.optim是一个实现了各种优化算法的库。所谓的优化算法就是更新可学习参数的方法,比如梯度下降等等。
只讲解SGD,Adam不大理解。
torch.optim.SGD(params, lr=
:随机梯度下降
参数的讲解(由于篇幅比较长,所以选择放在另一篇博客中)
实例化:optimizer = torch.optim.SGD(传入参数)
。
这是大多数optimizer所支持的简化版本。一旦梯度被如backward()之类的函数计算好后,我们就可以调用这个函数来自动使用我们定义好的optimizer优化算法来更新权重。
使用方法:optimizer.step()
将梯度清零。
至于与model.zero_grad()
的区别,网上几乎都是复制粘贴的,没什么参考价值,只找到了两篇相对有价值的讨论,但还是不理解。
Model.zero_grad() or optimizer.zero_grad()? - PyTorch Forums
PyTorch中的model.zero_grad() vs optimizer.zero_grad() - 简书
暂时这部分只能囫囵吞枣了。
optimizer = optim.SGD(model.parameters(), lr = 0.01, momentum=0.9) # 实例化
for epoch in range(1, num_epochs + 1) : # 迭代
optimizer.zero_grad() # 梯度清零
loss_value.backward() # 反向传播
optimizer.step() # 更新权重
torch.utils.data.Dataset:将tensor数据封装成数据集方便处理。生成的是索引型数据集。
torch.utils.data.DataLoader:生成迭代器,按自定义的方式读取数据集中的数据。生成的是迭代型数据集。
功能:Dataset 是抽象类,所有自定义的 Dataset 都需要继承该类,并且重写__getitem()__
方法和__len__()
方法 。__getitem()__
方法的作用是接收一个索引,返回索引对应的样本和标签,这是我们自己需要实现的逻辑。__len__()
方法是返回所有样本的数量。
import torch
import numpy as np
from torch.utils.data import DataLoader, Dataset
class MyDataSet(Dataset) :
# 重载了 __init__, __getitem__, __len__
# 将 Tensor 数据封装成 Tensor 数据集
# 通过索引可以获取某个样本信息,通过 len 可以获取样本个数
def __init__(self, features, target) :
self.x = features
self.y = target
def __getitem__(self, index) :
return self.x[index], self.y[index]
def __len__(self) :
return self.x.size(0)
# 生成数据
num_examples = 1000 # 样本数
num_features = 3 # 样本特征数
x = torch.randn(num_examples, num_features)
y = torch.randn(num_examples)
# 将数据封装成 Dataset
mydataset = MyDataSet(x, y)
# 索引获取样本信息
print(mydataset[0])
# len获取样本数量
print(len(mydataset))
"""
(tensor([-0.1909, 0.9481, 0.1173]), tensor(1.0922))
1000
"""
torch.utils.data.TensorDataset(*tensors)
:将张量封装成数据集。
描述一下就是,可以传入多个不同维度的张量,但是它们的第一维必须相同。这些张量将被封装成一个多个元组,元组个数为第一维大小。
举两个例子理解:
TensorDataSet与Dataset的关系:
你不感觉TensorDataSet更像是DataSet的一种特殊情况吗?也就是说我们可以通过继承DataSet类实现处理数据并封装成数据集形式,但TensorDataSet的输入参数只能是满足一定条件张量,直接将输入张量打包封装,可实现的功能更局限,而且无法自定义其他功能。
torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, batch_sampler=None, num_workers=0, collate_fn=None, pin_memory=False, drop_last=False, timeout=0, worker_init_fn=None, multiprocessing_context=None, generator=None, *, prefetch_factor=2, persistent_workers=False)
Epoch、Iteration 和 Batchsize
假设样本总数有 80,设置 Batchsize 为 8,则共有 80÷8 个 Iteration。这里 1 Epoch = 10 Iteration。
dataset:(数据类型 Dataset)
输入的数据类型,也是最重要的参数,它表示要加载数据的数据集对象。
batch_size:(数据类型 int)
批处理样本的大小,默认为1。
shuffle:(数据类型 bool)
在每轮迭代训练时是否将数据洗牌。默认设置为False。设置为True则是在每一轮中,输入数据的顺序将被打乱,这是为了使数据更有独立性,训练的时候一般都设置为True,若输入数据是有序的,就不要设置成True了。
collate_fn:(数据类型 callable可调用对象)
将一小段数据合并成数据列表,默认设置是False。如果设置成True,系统会在返回前会将张量数据(Tensors)复制到CUDA内存中。
sampler:(数据类型 Sampler)
采样,默认设置为None。根据定义的策略从数据集中采样输入。如果定义采样规则,则洗牌(shuffle)设置必须为False。
num_workers:(数据类型 Int)
子进程数量,默认是0。使用多少个子进程来加载数据。0 就是使用主进程来加载数据。注意:这个数字必须是大于等于0的,该值的设置应该量内存大小而为。
pin_memory:(数据类型 bool)
内存寄存,默认为False。在数据返回前,是否将数据复制到CUDA内存中。
drop_last:(数据类型 bool)
丢弃最后数据,默认为False。设置了 batch_size 的数目后,最后一批数据未必是设置的数目,有可能会小些。这时你是否需要丢弃这批数据。
timeout:(数据类型 numeric)
超时值,默认为0。是用来设置数据读取的超时时间,超过这个时间还没读取到数据的话就会报错。 所以,数值必须大于等于0。
"""
批训练,把数据变成一小批一小批数据进行训练。
DataLoader就是用来包装所使用的数据,每次抛出一批数据
"""
import torch
import torch.utils.data as Data
BATCH_SIZE = 5
x = torch.linspace(1, 10, 10)
y = torch.linspace(10, 1, 10)
# 把数据放在数据库中
torch_dataset = Data.TensorDataset(x, y)
loader = Data.DataLoader(
# 从数据库中每次抽出batch size个样本
dataset=torch_dataset,
batch_size=BATCH_SIZE,
shuffle=True,
num_workers=2,
)
def show_batch():
for epoch in range(3):
for step, (batch_x, batch_y) in enumerate(loader):
# training
print("step:{}, batch_x:{}, batch_y:{}".format(step, batch_x, batch_y))
if __name__ == '__main__':
"""
step:0, batch_x:tensor([ 5., 8., 7., 2., 10.]), batch_y:tensor([6., 3., 4., 9., 1.])
step:1, batch_x:tensor([1., 9., 4., 6., 3.]), batch_y:tensor([10., 2., 7., 5., 8.])
step:0, batch_x:tensor([3., 7., 9., 2., 5.]), batch_y:tensor([8., 4., 2., 9., 6.])
step:1, batch_x:tensor([ 4., 8., 10., 1., 6.]), batch_y:tensor([ 7., 3., 1., 10., 5.])
step:0, batch_x:tensor([ 3., 6., 9., 2., 10.]), batch_y:tensor([8., 5., 2., 9., 1.])
step:1, batch_x:tensor([8., 5., 7., 1., 4.]), batch_y:tensor([ 3., 6., 4., 10., 7.])
"""
你可以尝试着将 x x x改为更高维度的样本试试输出。
DataLodaer与DataSet类不同在于不能通过索引去访问每个样本信息,只能通过迭代的方式获取样本信息,更方便我们去遍历每一个batch。
讲了这么多终于可以写使用这些库来生成batch了!
import torch
import numpy as np
import torch.nn as nn
import torch.utils.data as Data
"""
定义简单的线性神经网络。
输入样本具有二维特征,输出为一维预测标签,学习时含偏置。
"""
class MyLinearRegression(nn.Module) :
def __init__(self):
super(MyLinearRegression, self).__init__()
self.linear = nn.Linear(in_features=2, out_features=1, bias=True)
def forward(self, x):
y_hat = self.linear(x)
return y_hat
"""
生成数据集,该函数与上面代码的不同在于不再将偏置b算入权重w中了,因为Linear函数我们设置了bais=True,会将bais当作一个单独的可学习参数进行训练
"""
def generate_dataset(true_w, num_examples, random_seed=10) :
torch.manual_seed(random_seed)
np.random.seed(random_seed)
x = torch.randn(size=(num_examples, 2), dtype=torch.float32)
y = torch.mm(x, true_w)
y += torch.normal(0, 0.01, size=y.size(), dtype=torch.float32)
return x, y
if __name__ == "__main__" :
# 创建 MyLinearRegression() 的实例
model = MyLinearRegression()
print(model) # 显示模型层次
# ----- 规定一些参数的值 -----
num_examples = 1000 # 1000个样本
batch_size = 10
num_epochs = 10 # 迭代次数
learning_rate = 1e-2 # 学习率 0.01
mse_loss = torch.nn.MSELoss() # 损失函数,均方误差
true_w = torch.tensor([2.5, -1.5], dtype=torch.float32).view(-1, 1) # 转换成1列,为了方便进行矩阵乘法。
true_b = torch.tensor([3.5], dtype=torch.float32)
# ----- -----
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate) # 优化函数
# 生成数据集
x, y = \
generate_dataset(
true_w=true_w,
num_examples=num_examples
)
# 封装成DataSet
dataset = Data.TensorDataset(x, y)
# 生成迭代器
loader = Data.DataLoader(
dataset=dataset,
batch_size=batch_size,
shuffle=True,
num_workers=2,
)
# 迭代更新
for epoch in range(1, num_epochs+1) :
for step, (batch_x, batch_y) in enumerate(loader): # 换成DataLoader的迭代方式
# 预测
batch_y_hat = model(batch_x)
# 计算损失值
loss_value = mse_loss(batch_y_hat, batch_y)
# 反向传播
loss_value.backward()
# 一旦梯度被如backward()之类的函数计算好后,我们就可以调用这个函数更新参数
optimizer.step()
# 将可训练参数的梯度清零
optimizer.zero_grad()
print("Epoch %d Step %d Loss %f" % (epoch, step+1, mse_loss(model(x), y).item()))
"""
输出太多不打印了,自己运行一下就知道了
"""
[1] 线性回归的从零开始实现 - Dive-into-DL-PyTorch
[2] PyTorch纯手工构建模型并训练 - CSDN博客
[3] pytorch 固定随机数种子 - CSDN博客
[4] 【小白学习PyTorch教程】四、基于nn.Module类实现线性回归模型 - CSDN博客
[5] 卷积神经网络中nn.Conv2d()和nn.MaxPool2d()以及卷积神经网络实现minist数据集分类 - 博客园
[6] PyTorch 学习笔记 2.1 DataLoader 与 DataSet - 知乎
[7] PyTorch中torch.utils.data.DataLoader加载数据 - CSDN博客
[8] Pytorch笔记05-自定义数据读取方式orch.utils.data.Dataset与Dataloader - 知乎
[9] Pytorch Sampler详解 - 博客园