PyTorch 入门基础实践

以下内容主要介绍 tensor 数据类型、torch 反向传播自动求导、使用 torch 实现线性回归、逻辑斯蒂回归、数据集的加载与构造、多分类 (手写数字)。

1 tensor 张量

  torch.tensor 是包含单个数据类型元素的多维矩阵,与 numpy 数组十分相似。tensor 有两种属性:类型和形状

  tensor 的数据类型:Torch 定义了 9 种 CPU 张量类型和 9 种 GPU 张量类型,torch.tensor 默认 torch.FloatTensor 类型。

  tensor 的常见形式:tensor 就是一个多维数据,例如,

  0 维张量,标量:

# Scalar
x = tensor(42.)
print(x)
print(x.item())
print(x.dim())
print(2*x)

  输出:

tensor(42.)
42.0
0
tensor(84.)

  1 维张量,矢量:

# Vector
v = tensor([1.5, -1.5, 3.0])
print(v)
print(v.dim())
print(v.size())

  输出:

tensor([ 1.5000, -1.5000,  3.0000])
1
torch.Size([3])

  2 维张量,矩阵:

# Matrix
M = tensor([[1., 2.], [3., 4.]])
print(M)
print(M.matmul(M))  # 矩阵乘法
print(M*M)  # 矩阵内积
print(tensor([1., 2.]).matmul(M))

  输出:

tensor([[1., 2.],
        [3., 4.]])
tensor([[ 7., 10.],
        [15., 22.]])
tensor([[ 1.,  4.],
        [ 9., 16.]])
tensor([ 7., 10.])

  tensor 的一些基本操作:

import torch

# 版本
print(torch.__version__)

# 创建矩阵
x = torch.empty(5, 3)
print(x)
x = torch.rand(5, 3)
print(x)
x = torch.zeros(5, 3, dtype=torch.long)
print(x)
x = torch.tensor([5.5, 3])
print(x)
x = x.new_ones(5, 3, dtype=torch.double)
print(x)
x = torch.randn_like(x, dtype=torch.float)  # 构建一个相同大小的随机矩阵
print(x)

# 展示矩阵大小
print(x.size())

# 基本计算方法
y = torch.rand(5, 3)
print(x + y)
print(torch.add(x, y))

# 索引
print(x[:, 1])
print(x[1, :])

# view 改变矩阵维度
x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8)
print(x.size(), y.size(), z.size())
print(x)
print(y)
print(z)

# 与 numpy 的相互转换
import numpy as np
a = torch.ones(5)
b = a.numpy()
print(b)
a = np.ones(5)
b = torch.from_numpy(a)
print(b)

 2 torch 自动求导

  这里拟合一个简单的线性方程 y'=w*x ,损失函数为 (y'-y)2

import torch

x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]

w = torch.Tensor([1.0])
w.requires_grad = True

def forward(x):
    return x * w

def loss(x, y):
    y_pred = forward(x)
    return (y_pred - y) ** 2

print("predict (before training)", 4, forward(4).item())

for epoch in range(100):
    for x, y in zip(x_data, y_data):
        l = loss(x, y)  # 构建计算图中直接使用张量
        l.backward()
        print('\tgrad:', x, y, w.grad.item())
        w.data = w.data - 0.01 * w.grad.data  # 取data计算不会生成张量图,权重更新时单纯的求数据不需要求梯度
        # eg:sum+=l  会不断构建张量图,循环次数过多会消耗内存,所以应该用 sum+=l.item()
        w.grad.data.zero_()  # 梯度会保留下来,如果不清零会不断累加,有些模型会需要这种累加功能,这里不需要就要清零
    print("progress:", epoch, l.item())

print("predict (before training)", 4, forward(4).item())

  对于需要计算梯度的参数 w,要将 w.requires_grad 置为 True;

  在计算前向传播和损失函数中,构造了一个计算图,通过直接调用 backward() ,w 的梯度值会被自动累加到 w.grad 中;

  注意,w.grad 会被保存下来,每次反向传播计算的梯度是累加进去的,所以往往需要手动清零;

  当 w 设置了 requires_grad 时,如果我们只是需要使用张量的值,而不是构造计算图计算梯度,应该使用 w.data,而不是 w ;

3 线性回归

  同样使用上面的数据,但这里的模型使用类来构造,优化器和损失函数直接调用 torch 已有的包,

import torch

x_data = torch.Tensor([[1.0], [2.0], [3.0]])
y_data = torch.Tensor([[2.0], [4.0], [6.0]])

class LinearModel(torch.nn.Module):
    def __init__(self, input_dim, output_dim):
        super(LinearModel, self).__init__()  # 调用父类的构造函数
        self.linear = torch.nn.Linear(input_dim, output_dim)

    def forward(self, x):
        out = self.linear(x)
        return out
model = LinearModel(1, 1)

criterion = torch.nn.MSELoss(size_average=False)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)  # 确定需要优化的参数,学习率等

for epoch in range(10):
    y_pred = model(x_data)
    loss = criterion(y_pred, y_data)
    print(epoch, loss)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

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)

  torch.nn.Linear 是一个线性模型,其中有参数 w 和 b ;

  torch.nn.MSELoss 是均方损失函数,size_average 表示是否需要取均值 (即 /n);

  torch.optim.SGD 是 SGD 优化器,model.parameters() 可以确定模型中所有需要优化的参数;

  从上面可以看出,从模型的设计到训练一般分为四步:1.准备数据集;2.设计模型(class);3.构造损失函数和优化器;4.训练模型。

  其中训练模型一般包括:1.前向传播;2.计算损害函数;3.梯度清零(要在反向传播之前);4.反向传播;5.参数更新。

 4 逻辑斯蒂回归

  这是一个单特征二分类问题, y 取值为 0 或 1,

import torch
import torch.nn.functional as F

x_data = torch.tensor([[1.0], [2.0], [3.0]])
y_data = torch.tensor([[0.], [0.], [1.]])

class LogisticRegressionModel(torch.nn.Module): def __init__(self): super(LogisticRegressionModel, self).__init__() self.linear = torch.nn.Linear(1, 1) def forward(self, x): y_pred = F.sigmoid(self.linear(x)) return y_pred model = LogisticRegressionModel() criterion = torch.nn.BCELoss(size_average=False) # 交叉熵 optimizer = torch.optim.SGD(model.parameters(), lr=0.01) 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()

  注意,如果 y_data 中的数据没有小数点,被认为是 long 类型,使用交叉熵损失函数会被报错;

  torch.sigmoid() 、torch.nn.function.sigmoid() 是函数,在 forward 中使用,torch.nn.sigmoid 是一个类,可以当做神经网络中的一层 (没有参数),在 init 中使用。

  多特征二分类,下面的例子中数据 x 中的每个样本有 8 个特征,

import torch
import numpy as np

xy = np.loadtxt('../dataset/diabetes.csv.gz', delimiter=',', dtype=np.float32)
x_data = torch.from_numpy(xy[:, :-1])
y_data = torch.from_numpy(xy[:, [-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()

    def forward(self, x):
        x = self.sigmoid(self.linear1(x))
        x = self.sigmoid(self.linear2(x))
        x = self.sigmoid(self.linear3(x))
        return x
model = Model()

criterion = torch.nn.BCELoss(size_average=True)
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)

for epoch in range(100):
    y_pred = model(x_data)
    loss = criterion(y_pred, y_data)
    print(epoch, loss.data)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

  diabetes.csv.gz 数据集可在本文末尾的参考链接中下载。

5 数据集

  Dataset 和 DataLoader 的使用,

from torch.utils.data import Dataset
from torch.utils.data import DataLoader

# 数据集不大,可以在init中全部加载进去,数据集过大,init就只做一个初始化
class DiabetesDataset(Dataset):
    def __init__(self):
        pass

    def __getitem__(self, item):  # 通过索引item把数据拿出来
        pass

    def __len__(self):  # 返回数据条数
        pass

dataset = DiabetesDataset()
train_loader = DataLoader(dataset=dataset,
                          batch_size=32,
                          shuffle=True,
                          num_workers=2)  # 需要几个并行进程读取数据

  num_workers 可能遇到的问题:直接使用 train_loader 会报错,windows 和 Linux 中多进程的实现是不一样的,即 spawn 和 fork。

  解决方法:将 loader 迭代的代码封装起来,例如封装到 if 语句或函数里,if __name__ == '__main__':

if __name__ == '__main__':
    for epoch in range(100):
        for i, data in enumerate(train_loader, 0):

  DataLoader 使用实例:

import torch
import numpy as np
from torch.utils.data import Dataset
from torch.utils.data import DataLoader

class DiabetesDataset(Dataset):
    def __init__(self, filepath):
        xy = np.loadtxt(filepath, delimiter=',', dtype=np.float32)
        print(xy.shape)
        self.len = xy.shape[0]
        self.x_data = torch.from_numpy(xy[:, :-1])
        self.y_data = torch.from_numpy(xy[:, [-1]])

    def __getitem__(self, item):
        return self.x_data[item], self.y_data[item]

    def __len__(self):
        return self.len

dataset = DiabetesDataset('../dataset/diabetes.csv.gz')
train_loader = DataLoader(dataset=dataset, batch_size=32, shuffle=True, num_workers=2)

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 x

model = Model()
criterion = torch.nn.BCELoss(size_average=True)
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)

if __name__ == '__main__':
    for epoch in range(10):
        for i, data in enumerate(train_loader, 0):
            # Prepare dataset
            inputs, labels = data
            # Forward
            y_pred = model(inputs)
            loss = criterion(y_pred, labels)
            print(epoch, i, loss.item())
            # Backward
            optimizer.zero_grad()
            loss.backward()
            # Updata
            optimizer.step()

  torchvision.datasets 中的数据集

   如何使用:

import torchvision
from torchvision import transforms
from torch.utils.data import DataLoader

train_dataset = torchvision.datasets.MNIST(root='../dataset/mnist',
                                           train=True,
                                           transform=transforms.ToTensor(),
                                           download=True)
test_dataset = torchvision.datasets.MNIST(root='../dataset/mnist',
                                          train=False,
                                          transform=transforms.ToTensor(),
                                          download=True)
train_loader = DataLoader(dataset=train_dataset,
                          batch_size=32,
                          shuffle=True)
test_loader = DataLoader(dataset=test_dataset,
                         batch_size=32,
                         shuffle=False)

  download=True 时,如果没有下载数据集,会自动下载。

 6 多分类问题

  第 4 节讨论了多特征二分类问题,本节讨论多分类问题,使用了 softmax 层、交叉熵损失函数。

  交叉熵损失函数:

import torch

y = torch.LongTensor([0])
z = torch.Tensor([[0.2, 0.1, -0.1]])
criterion = torch.nn.CrossEntropyLoss()
loss = criterion(z, y)
print(loss)

  交叉熵损失函数中的参数,标签 y 是一个长整型,上面的例子表示第 0 类,预测 z 是一个长度为 n 的向量 (一共 n 类),这一层 (即神经网络的最后一层) 不需要激活函数,直接代入到 softmax 中 (也不需要自己写,交叉熵损失函数会自己计算),通过这两个参数求出损失。

import torch

criterion = torch.nn.CrossEntropyLoss()
Y = torch.LongTensor([2, 0, 1])
Y_pred1 = torch.Tensor([[0.1, 0.2, 0.9],
                        [1.1, 0.1, 0.2],
                        [0.2, 2.1, 0.1]])
Y_pred2 = torch.Tensor([[0.8, 0.2, 0.3],
                        [0.2, 0.3, 0.5],
                        [0.2, 0.2, 0.5]])
l1 = criterion(Y_pred1, Y)
l2 = criterion(Y_pred2, Y)
print("Batch Loss1 = ", l1.data, "\nBatch Loss2 = ", l2.data)

  这个例子中计算了三个样本两种预测的交叉熵损失。

  手写数字识别:

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

batch_size = 64
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,))  # 两个参数分别表示 均值mean、标准差std
])  

train_dataset = datasets.MNIST(root='../dataset/mnist',
                               train=True,
                               transform=transform,
                               download=True)
test_dataset = datasets.MNIST(root='../dataset/mnist',
                              train=False,
                              transform=transform,
                              download=True)
train_loader = DataLoader(dataset=train_dataset,
                          batch_size=batch_size,
                          shuffle=True)
test_loader = DataLoader(dataset=test_dataset,
                         batch_size=batch_size,
                         shuffle=False)

class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.l1 = torch.nn.Linear(784, 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)
        x = 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)  # 最后一层接softmax层,不做激活

model = Net()
criterion = torch.nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.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()

        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.0

def test():
    correct = 0
    total = 0
    with torch.no_grad():  # 测试不需要计算梯度
        for data in test_loader:
            images, labels = data
            outputs = model(images)
            _, predicted = torch.max(outputs.data, dim=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(100):
        train(epoch)
        test()

  transform 参数将 PIL image 转换为了 Tensor,且将像素值标准化为均值 0.1307、方差 0.3081 的数据,同时,将 28*28 的数据转换为 1*28*28 的数据 (1 表示单通道)。

  PIL image:大小 28*28,像素值 0~255;

  PyTorch Tensor:大小 1*28*28,像素值 0~1 。

 

 

主要参考:《PyTorch深度学习实践》 (B站 BV1Y7411d7Ys)

课件下载:链接: https://pan.baidu.com/s/1Ku5c99yDHNFMt8EJAcF5LA 提取码: n4xh

posted @ 2020-10-08 15:52  sun-a  阅读(592)  评论(0编辑  收藏  举报