深度学习本科课程 实验1 Pytorch基本操作

一、Pytorch基本操作考察

1.1 任务内容

  1. 使用 初始化一个 × 的矩阵 和一个 × 的矩阵 ,对两矩阵进行减法操作(要求实现三种不同的形式),给出结果并分析三种方式的不同(如果出现报错,分析报错的原因),同时需要指出在计算过程中发生了什么
  2. ① 利用 创建两个大小分别 × 和 × 的随机数矩阵 和 ,要求服从均值为0,标准差0.01为的正态分布;② 对第二步得到的矩阵 进行形状变换得到 的转置 ^ ;③ 对上述得到的矩阵 和矩阵$ ^ $求矩阵相乘
  3. 给定公式 y 3 = y 1 + y 2 = x 2 + x 3 y_3=y_1+y_2=x^2 + x^3 y3=y1+y2=x2+x3,且 x = 1 x=1 x=1。利用学习所得到的Tensor的相关知识,求 y 3 y_3 y3 x x x的梯度,即 d y 3 d x \dfrac{dy_3}{dx} dxdy3。要求在计算过程中,在计算 x 3 x^3 x3时中断梯度的追踪,观察结果并进行原因分析(可略)

1.2 任务思路及代码

步骤1: 矩阵生成与三种矩阵减法 ……

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 - BC = torch.sub(A, B)原理基本一致, 即对两个尺寸相同的矩阵(如果尺寸不同, torch将尝试广播机制), 进行每个元素对应的减法

第三种方法A.sub_(B)被称作原地操作, 前两种方法创建了新的Tensor对象来保存结果, 而原地操作将计算结果保留在了原对象上

比较而言, 非原地操作产生了额外的内存开销, 而原地操作直接修改了原对象, 后者的缺点在于丢失了原始数据

步骤2: 矩阵转置与乘法

# 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

步骤3: 梯度计算

# 正常的梯度计算
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

二、实现 logistic 回归

2.1 任务内容

  1. 要求动手从0实现 logistic 回归(只借助Tensor和Numpy相关的库)在人工构造的数据集上进行训练和测试,并从loss以及训练集上的准确率等多个角度对结果进行分析
    (可借助nn.BCELoss或nn.BCEWithLogitsLoss作为损失函数,从零实现二元交叉熵为选作)
  2. 利用 torch.nn 实现 logistic 回归在人工构造的数据集上进行训练和测试,并对结果进行分析,并从loss以及训练集上的准确率等多个角度对结果进行分析
    ……

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%, 说明模型训练结果良好

步骤2

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 MSE0.039
M A E ≈ 0.006 MAE \approx 0.006 MAE0.006

从两个误差值来看, 预测误差非常小, 模型预测效果良好, 正确率高

三、实现 softmax 回归

3.1 任务内容

  1. 要求动手从0实现 softmax 回归(只借助Tensor和Numpy相关的库)在Fashion-MNIST数据集上进行训练和测试,并从loss、训练集以及测试集上的准确率等多个角度对结果进行分析(要求从零实现交叉熵损失函数)

利用torch.nn实现 softmax 回归在Fashion-MNIST数据集上进行训练和测试,并从loss,训练集以及测试集上的准确率等多个角度对结果进行分析

3.2 任务思路及代码

步骤1

引入库

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)

创建Softmax回归模型

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("训练完毕!")

步骤2

测试模型

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))

3.3 实验结果分析

经过500轮迭代, 损失度为0.37, 其值较小, 说明预测值和样本值基本符合而略有偏差, 训练过程中损失度没有明显的下降, 可能是训练轮次不足导致的

训练集大小为60000, 测试集大小为10000, 本次训练耗时2个小时

如果对模型进行更高轮次的训练, 时间开销可能会过大

检验准确度为0.8439, 说明模型对输入数据分类正确度较高, 预测效果良好

你可能感兴趣的:(深度学习,本科课程,深度学习,人工智能)