import torch
X = torch.tensor([111,222,333])
Y = torch.tensor([[9999], [999]])
print(X)
print(Y)
# 进行减法运算
temp1 = torch.tensor([10,10,10])
temp2 = torch.tensor([[10],[10]])
# 第一种方法
C1 = X - temp1
C2 = Y - temp2
print("-----1-----")
print(C1)
print(C2)
# 第二种方法
C1 = torch.sub(X, temp1)
C2 = torch.sub(Y, temp2)
print("-----2-----")
print(C1)
print(C2)
# 第三种方法
X.sub_(temp1)
Y.sub_(temp2)
print("-----3-----")
print(X)
print(Y)
实验结果分析
三种方法中, C = A - B
与C = torch.sub(A, B)
原理基本一致, 即对两个尺寸相同的矩阵(如果尺寸不同, torch将尝试广播机制), 进行每个元素对应的减法
第三种方法A.sub_(B)
被称作原地操作, 前两种方法创建了新的Tensor对象来保存结果, 而原地操作将计算结果保留在了原对象上
比较而言, 非原地操作产生了额外的内存开销, 而原地操作直接修改了原对象, 后者的缺点在于丢失了原始数据
# step1: 服从正态分布的随机数矩阵生成
P = 0.01 * torch.randn(3,2)
Q = 0.01 * torch.randn(4,2)
print("Matrix P :")
print(P)
print("Matrix Q :")
print(Q)
print("-------")
print()
# step2: 矩阵的转置
Q_T = Q.t()
print("Q矩阵转置前:")
print(Q)
print("Q矩阵转置后:")
print(Q_T)
print("-------")
print()
# step3: 矩阵的乘法
result = torch.mm(P, Q_T)
print("乘法结果:")
print(result)
实验结果分析
注: 矩阵的乘法除了调用.mm()
方式外, 还可以使用torch.matmul()
以及C = A @ B
# 正常的梯度计算
x = torch.tensor(1.0, requires_grad = True)
y1 = x**2
y2 = x**3
y3 = y1 + y2
y3.backward()
gradient = x.grad
print("梯度为", gradient)
# 在计算x**3时中断梯度跟踪
x = torch.tensor(1.0, requires_grad = True)
y1 = x**2
with torch.no_grad():
y2 = x**3
y3 = y1 + y2
y3.backward()
gradient = x.grad
print("梯度为", gradient)
实验结果分析
由本实验提供的公式来看, 显然x为自变量, 计算梯度时应该对x进行跟踪
在定义时需要在x=1.0的基础上提交requires_grad = True
按顺序输入公式, 最后调用backward()函数即可计算梯度值
若要实现终端梯度跟踪, 则使用with torch.no_grad()
语句
在本实验中, 计算$ x^3 时中断跟踪 , 意味着放弃考虑 时中断跟踪, 意味着放弃考虑 时中断跟踪,意味着放弃考虑 x^3 $项对函数梯度的贡献
因此, 原本梯度值为2+3=5, 现在计算为2 = 2
import numpy as np
from IPython import display
from matplotlib import pyplot as plt
import random
np.random.seed(0)
num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2
features = torch.tensor(np.random.normal(0, 1, (num_examples, num_inputs)), dtype=torch.float)
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float)
def use_svg_display():
# 用矢量图显示
display.set_matplotlib_formats('svg')
def set_figsize(figsize=(3.5, 2.5)):
use_svg_display()
# 设置图的尺寸
plt.rcParams['figure.figsize'] = figsize
set_figsize()
plt.scatter(features[:, 1].numpy(), labels.numpy(), 1)
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
random.shuffle(indices)
# 样本的读取顺序是随机的
for i in range(0, num_examples, batch_size):
j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)])
# 最后一次可能不足一个batch
yield features.index_select(0, j), labels.index_select(0, j)
def linreg(X, w, b):
return torch.mm(X, w) + b
def squared_loss(y_hat, y):
return (y_hat - y.view(y_hat.size())) ** 2 / 2
def sgd(params, lr, batch_size):
for param in params:
param.data -= lr * param.grad / batch_size
# 权重初始化
w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)), dtype=torch.float32)
b = torch.zeros(1, dtype=torch.float32)
# 迭代求参数
w.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
# 模型训练
lr = 0.03
num_epochs = 3
batch_size = 10
net = linreg
loss = squared_loss
for epoch in range(num_epochs): # 训练模型一共需要num_epochs个迭代周期
# 在每一个迭代周期中,会使用训练数据集中所有样本一次
for X, y in data_iter(batch_size, features, labels):
# x和y分别是小批量样本的特征和标签
l = loss(net(X, w, b), y).sum() # l是有关小批量X和y的损失
l.backward() # 小批量的损失对模型参数求梯度
sgd([w, b], lr, batch_size) # 使用小批量随机梯度下降迭代模型参数
w.grad.data.zero_() # 梯度清零
b.grad.data.zero_()
train_l = loss(net(features, w, b), labels)
print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))
print(true_w, '\n', w)
print(true_b, '\n', b)
实验结果分析
经过3轮的迭代后, 模型损失度<0.01%, 说明模型预测值与实际值相差非常小
从模型训练得到的参数来看, w值和b值的差距<0.03%, 说明模型训练结果良好
lr = 0.03
import torch.utils.data as Data
batch_size = 10
# 将训练数据的特征和标签组合
dataset = Data.TensorDataset(features, labels)
# 把 dataset 放入 DataLoader
data_iter2 = Data.DataLoader(
dataset=dataset, # torch TensorDataset format
batch_size=batch_size, # mini batch size
shuffle=True, # 是否打乱数据 (训练集一般需要进行打乱)
num_workers=0, # 多线程来读数据,注意在Windows下需要设置为0
)
from torch import nn
class LinearNet(nn.Module):
def __init__(self, n_feature):
super(LinearNet, self).__init__()
self.linear = nn.Linear(n_feature, 1)
# forward 定义前向传播
def forward(self, x):
y = self.linear(x)
return y
net = LinearNet(num_inputs)
# 模型参数初始化
from torch.nn import init
init.normal_(net.linear.weight, mean=0, std=0.01)
init.constant_(net.linear.bias, val=0) #也可以直接修改bias的data:net[0].bias.data.fill_(0)
# 选择损失函数
loss = nn.MSELoss()
# 选择优化算法(小批量随机梯度下降算法)
import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr=0.03) #梯度下降的学习率指定为0.03
# 可以为不同的子网络设置不同学习率
# optimizer =optim.SGD([
# # 如果不指定学习率,则用默认的最外层学习率
# {'params': net.subnet1.parameters()}, # lr=0.03
# {'params': net.subnet2.parameters(), 'lr': 0.01}
# ], lr=0.03)
num_epochs = 3
for epoch in range(1, num_epochs + 1):
for X, y in data_iter2:
output = net(X)
l = loss(output, y.view(-1, 1))
optimizer.zero_grad() # 梯度清零,等价于net.zero_grad()
l.backward()
optimizer.step()
print('epoch %d, loss: %f' % (epoch, l.item()))
# 定义准确率计算函数
def accuracy(y_hat, y):
return (y_hat.argmax(dim=1) == y).float().mean().item()
def evaluate_accuracy(data_iter, net):
acc_sum, n = 0.0, 0
for X, y in data_iter:
acc_sum = acc_sum + (net(X).argmax(dim=1) == y).float().sum().item()
n += y.shape[0]
return acc_sum / n
# 定义均方误差计算函数
def calMSE(y_hat, y):
return ((y_hat - y)**2).float().mean().item()
def evaluate_MSE(data_iter, net):
MSE_sum, n = 0.0, 0
for X, y in data_iter:
MSE_sum = calMSE(net(X).argmax(dim=1), y)
n += y.shape[0]
return MSE_sum / n
# 定义平均绝对误差函数
def calMAE(y_hat, y):
return torch.abs(y_hat - y).float().mean().item()
def evaluate_MAE(data_iter, net):
MAE_sum, n = 0.0, 0
for X, y in data_iter:
MAE_sum = calMAE(net(X).argmax(dim=1), y)
n += y.shape[0]
return MAE_sum / n
print("本模型的准确率:")
print(evaluate_accuracy(data_iter2, net))
print("本模型的MSE:")
print(evaluate_MSE(data_iter2, net))
print("本模型的MAE:")
print(evaluate_MAE(data_iter2, net))
2.3 实验结果分析
经过3轮迭代, 模型损失度loss<0.01%, 说明模型预测值与样本实际值相差非常小
然而, 准确度计算结果为0, 说明预测值与实际值完全不相等, 这主要是因为本任务是回归问题而不是分类问题
$ y$ 与 $ \hat{y} $ 的值域是一个连续的区间, 而不是离散的类型, 准确率计算函数可能不适用
如下例所示, 一次抽样中每个对应的元素相差基本处于$ [-0.001, 0.001] $, 却被判定为预测错误, 显然不合理
for X, y in data_iter2:
print(X, y)
print(net(X))
print(net(X).argmax(dim=1) == y)
print((net(X).argmax(dim=1) == y).float().mean().item())
break
因此, 需要用其他特征来评价线性回归模型, 如均方误差和均方误差
M S E ≈ 0.039 MSE \approx 0.039 MSE≈0.039
M A E ≈ 0.006 MAE \approx 0.006 MAE≈0.006
从两个误差值来看, 预测误差非常小, 模型预测效果良好, 正确率高
利用torch.nn实现 softmax 回归在Fashion-MNIST数据集上进行训练和测试,并从loss,训练集以及测试集上的准确率等多个角度对结果进行分析
import torchvision
import torchvision.transforms as transforms
mnist_train = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST', train=True, download=True, transform=transforms.ToTensor())
mnist_test = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST', train=False, download=True, transform=transforms.ToTensor())
batch_size = 256
num_workers = 0
train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=num_workers)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=num_workers)
class SoftmaxRegression(nn.Module):
def __init__(self, input_size, num_classes):
super(SoftmaxRegression, self).__init__()
self.linear = nn.Linear(input_size, num_classes)
def forward(self, x):
return self.linear(x)
[num_features, num_classes] = [784, 10]
model = SoftmaxRegression(num_features, num_classes)
model = model.to('cuda')
def cross_entropy_loss(y_hat, y):
# 计算Softmax
exp_y_hat = torch.exp(y_hat)
softmax = exp_y_hat / exp_y_hat.sum(dim=1, keepdim=True)
# 选择目标类别的概率
batch_size = y.size(0)
predicted_probs = softmax[torch.arange(batch_size), y]
# 计算交叉熵损失
loss = -torch.log(predicted_probs)
return loss.mean()
criterion = cross_entropy_loss
optimizer = optim.SGD(model.parameters(), lr=0.1)
num_epochs = 500
for epoch in range(num_epochs):
for X, y in train_iter:
# 前向传播
X = X.view(X.size(0), -1)
X = X.to('cuda')
y = y.to('cuda')
outputs = model(X)
loss = criterion(outputs, y)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch + 1) % 10 == 0:
print("当前轮数: ", epoch+1, "Loss: ", loss.item())
print("训练完毕!")
def eval_Accuracy(data_iter, model):
acc_sum, n = 0.0, 0
for X, y in data_iter:
X = X.view(X.size(0), -1)
# X.to('cuda')
# y.to('cuda')
acc_sum = acc_sum + (model(X).argmax(dim=1) == y).float().sum().item()
n += y.shape[0]
return acc_sum / n
model.to('cpu')
print(eval_Accuracy(test_iter, model))
经过500轮迭代, 损失度为0.37, 其值较小, 说明预测值和样本值基本符合而略有偏差, 训练过程中损失度没有明显的下降, 可能是训练轮次不足导致的
训练集大小为60000, 测试集大小为10000, 本次训练耗时2个小时
如果对模型进行更高轮次的训练, 时间开销可能会过大
检验准确度为0.8439, 说明模型对输入数据分类正确度较高, 预测效果良好