pytorch的应用

pytorch的应用

课前的话:

大多数机器学习工作流程包括处理数据、创建模型、优化模型参数以及保存训练过的模型。

以FashionMNIST数据集为例来训练一个神经网络,它可以预测输入图像是否属于以下类别之一:t恤/上衣、Trouser、套头衫、连衣裙、外套、凉鞋、衬衫、Sneaker、Bag或踝靴。

训练可以在云端进行,也可以在本地进行

1.1 快速开始

1.1.1 数据集

pytorch有初始数据集, torch.utils.data.DataLoader、torch.utils.data.Dataset。Dataset存放样本和他们相对应的标签,Dataloader在Dataset周围包装了一个可以迭代的对象。

导入的相关代码如下:

import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda, Compose
import matplotlib.pyplot as plt

pytorch提供了特定领域的库,比如说TorchText,TorchVision和TorchAudio,他们都包含数据集。

torchvision.datasets模块包含Dataset对象,包含真实视觉的数据例如CIFAR,COCO等数据(详见官方文档)以其中一个FashionMNIST数据集进行。每一个数据集有两个对象,transform和target_transform来更好鉴别样本和标签。

我们来用dataset里面的FashionMNIST来进行实验:

# Download training data from open datasets.
training_data = datasets.FashionMNIST(
    root="data",
    train=True,
    download=True,
    transform=ToTensor(),
)

# Download test data from open datasets.
test_data = datasets.FashionMNIST(
    root="data",
    train=False,
    download=True,
    transform=ToTensor(),
)

但是官网下载会很慢,甚至报错,所以就需要自行下载数据集进行,很多博客都有这样的数据集,下载以后更改root里面的路径就好了

我们将Dataset作为参数传递给DataLoader。这在我们的数据集上包装了一个迭代器,并支持自动批处理、采样、打乱和多进程数据加载。这里我们定义了批处理大小为64,也就是说,dataloader iterable中的每个元素将返回一批64个特性和标签。

加载数据的代码块如下

batch_size = 64

# Create data loaders.
train_dataloader = DataLoader(training_data, batch_size=batch_size)
test_dataloader = DataLoader(test_data, batch_size=batch_size)

for X, y in test_dataloader:
    print("Shape of X [N, C, H, W]: ", X.shape)
    print("Shape of y: ", y.shape, y.dtype)
    break

1.1.2 建立模型

为了建立一个神经网络模型, 我们建立了一个类里面包含了nn.Module。我们定义神经网络的每一个层数,并且在forward函数里面指定如何通过网络,如果GPU可以使用,我们就把它部署到GPU上面进行

代码块如下:

# Get cpu or gpu device for training.
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using {device} device")

# Define model
class NeuralNetwork(nn.Module):
    def __init__(self):
        super(NeuralNetwork, self).__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10)
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

model = NeuralNetwork().to(device)
print(model)

1.1.3 优化网络参数

为了更好训练网络,我们需要一个损失函数和一个优化值,代码块如下:

loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)

在单一的训练循环中,模型对训练数据集进行预测(分批反馈给它),并将预测误差反向传播以调整模型的参数。

训练数据的代码如下:

def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)
    model.train()
    for batch, (X, y) in enumerate(dataloader):
        X, y = X.to(device), y.to(device)

        # Compute prediction error
        pred = model(X)
        loss = loss_fn(pred, y)

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

        if batch % 100 == 0:
            loss, current = loss.item(), batch * len(X)
            print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")

为了确保他正在训练,我们也定义了训练集进行训练来检查。

def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    model.eval()
    test_loss, correct = 0, 0
    with torch.no_grad():
        for X, y in dataloader:
            X, y = X.to(device), y.to(device)
            pred = model(X)
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
    test_loss /= num_batches
    correct /= size
    print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

训练过程是在很多个迭代的过程里面进行的(epochs), 在每一次训练里面,明星学习参数来更好进行预测, 我们在每一次训练里面学习,并且打印出精确度和误差,我们可以通过代码来看一下每一个遍历情况下的精确度和误差:

epochs = 5
for t in range(epochs):
    print(f"Epoch {t+1}\n-------------------------------")
    train(train_dataloader, model, loss_fn, optimizer)
    test(test_dataloader, model, loss_fn)
print("Done!")

1.1.4 保存模型

保存模型的代码很简单,就这一个代码,然后保存到相应路径之下即可

torch.save(model.state_dict(), "model.pth")
print("Saved PyTorch Model State to model.pth")

1.1.5 加载模型

我们已经训练好一个模型了,接下来就是去加载它,加载模型的过程包括重新创建模型结构和将状态字典加载到模型中。

代码如下:

model = NeuralNetwork()
model.load_state_dict(torch.load("model.pth"))

这个模型可以用来预测:

classes = [
    "T-shirt/top",
    "Trouser",
    "Pullover",
    "Dress",
    "Coat",
    "Sandal",
    "Shirt",
    "Sneaker",
    "Bag",
    "Ankle boot",
]

model.eval()
x, y = test_data[0][0], test_data[0][1]
with torch.no_grad():
    pred = model(x)
    predicted, actual = classes[pred[0].argmax(0)], classes[y]
    print(f'Predicted: "{predicted}", Actual: "{actual}"')

1.2 张量(tensors)

张量是一种专门的数据结构,与数组和矩阵非常相似。在PyTorch中,我们使用张量来编码模型的输入和输出,以及模型的参数。

张量类似于numpy里面的ndarray,张量可以在GPU或其它硬件加速器上面运行,事实上,张量和NumPy数组通常可以共享相同的底层内存,从而消除了复制数据的需要(参见使用NumPy的桥接)。张量还针对自动微分进行了优化(我们将在稍后的Autograd部分中看到更多相关内容)。

首要操作代码:

import torch
import numpy as np

1.2.1 初始化张量

张量初始化有很多种方法,有很多样例:

直接赋值
data = [[1, 2],[3, 4]]
x_data = torch.tensor(data)
从numpy数组里面
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
从另外的张量里面

新张量保留了参数张量的属性(形状、数据类型),除非显式重写。

x_ones = torch.ones_like(x_data) # retains the properties of x_data
print(f"Ones Tensor: \n {x_ones} \n")

x_rand = torch.rand_like(x_data, dtype=torch.float) # overrides the datatype of x_data
print(f"Random Tensor: \n {x_rand} \n")
随机或者常量

Shape是张量维数的元组。在下面的函数中,它决定了输出张量的维数。

代码如下:

shape = (2,3,)
rand_tensor = torch.rand(shape)
ones_tensor = torch.ones(shape)
zeros_tensor = torch.zeros(shape)

print(f"Random Tensor: \n {rand_tensor} \n")
print(f"Ones Tensor: \n {ones_tensor} \n")
print(f"Zeros Tensor: \n {zeros_tensor}")

1.2.2 张量的属性

张量属性描述了它们的形状、数据类型和存储它们的设备。

代码如下:

tensor = torch.rand(3,4)

print(f"Shape of tensor: {tensor.shape}")
print(f"Datatype of tensor: {tensor.dtype}")
print(f"Device tensor is stored on: {tensor.device}")

1.2.3 张量的操作

由上面运行结果可以知道,但是tensor的操作是在CPU上面的,很多张量的操作可以完成很多数学运算。超过100个张量操作,包括算术,线性代数,矩阵操作(转置,索引,切片),采样和更多的全面描述这里。

但是这些张量计算可以在GPU上面跑,但是tensor默认在CPU上面跑,我们需要把它移动到GPU上面跑,方法见以下代码,规模大的tensor运算成本高昂,时间成本和空间成本,所以说需要在GPU上面跑,操作如下:

如果你的电脑上面有GPU,可以如下操作:

# We move our tensor to the GPU if available
if torch.cuda.is_available():
    tensor = tensor.to('cuda')

pytorch可以进行很多tensor运算,就像numpy一样,记住numpy的用法就和tensor差不多了。

操作方法见下:

tensor = torch.ones(4, 4)
print('First row: ', tensor[0])
print('First column: ', tensor[:, 0])
print('Last column:', tensor[..., -1])
tensor[:,1] = 0
print(tensor)

张量的连接,用torch.cat方法连接张量,将沿给定维数的张量序列串联起来。另一个操作是torch.stack方法,和torch.cat连接方法不一样。

见下:

t1 = torch.cat([tensor, tensor, tensor], dim=1)
print(t1)
代数运算
# This computes the matrix multiplication between two tensors. y1, y2, y3 will have the same value
y1 = tensor @ tensor.T
y2 = tensor.matmul(tensor.T)

y3 = torch.rand_like(tensor)
torch.matmul(tensor, tensor.T, out=y3)


# This computes the element-wise product. z1, z2, z3 will have the same value
z1 = tensor * tensor
z2 = tensor.mul(tensor)

z3 = torch.rand_like(tensor)
torch.mul(tensor, tensor, out=z3)
单一元素张量

如果你有一个单元素张量,例如通过将一个张量的所有值聚合为一个值,你可以使用以下方法将它转换为Python数值:item()

agg = tensor.sum()
agg_item = agg.item()
print(agg_item, type(agg_item))

就地操作,当打就是把结果存储到操作数中的操作,他们结构就像这样:加了下划线_,x.copy_(y),x.t_(),可以改变x的值

print(tensor, "\n")
tensor.add_(5)
print(tensor)

但是用的不是很频繁,因为就地操作节省了一些内存,但是在计算导数时可能会出现问题,因为会立即丢失历史记录。

1.2.4 和numpy的关系桥梁

tensor在CPU上可以和numpy数组共享存储位置,并且改变一个就会改变另一个。

tensor到numpy的改变:

t = torch.ones(5)
print(f"t: {t}")
n = t.numpy()
print(f"n: {n}")

tensor改变了值,numpy数组值也改变了

t.add_(1)
print(f"t: {t}")
print(f"n: {n}")

numpy数组转tensor:

n = np.ones(5)
t = torch.from_numpy(n)

numpy数组值改变,tensor的值也会改变

np.add(n, 1, out=n)
print(f"t: {t}")
print(f"n: {n}")

1.3 数据集和数据下载器

处理数据样本的代码可能会变得混乱和难以维护;理想情况下,我们希望我们的数据集代码与我们的模型训练代码解耦,以获得更好的可读性和模块化。

pytorch提供了两个方法进行(在开篇讲过),这样的数据集允许你预加载数据和你自己的数据,Dataset存放样本和标签,Dataloader在Dataset周围包装了一个可以迭代的对象,意键更方便接触这些样本。

pytorch下有这些样本,他们有图片,文本和音频,可以帮我们训练样本来进行我们的研究。

我们看第一个样例,从TorchVision加载Fashion-MNIST数据集的示例。Fashion-MNIST是Zalando文章图片的数据集,包含60,000个训练示例和10,000个测试示例。每个示例包含一个28×28灰度图像和来自10个类之一的相关标签。

下面标签作用是:

root是数据集的路径,train的区分数据集还是验证集,是的话为true,否则为false,download:下载来自路径的数据(如果数据不在路径的话),transform:专门进行特征和标签转换。

标签和可视化:

可以进行index方法在tensor里面。

代码如下:

labels_map = {
    0: "T-Shirt",
    1: "Trouser",
    2: "Pullover",
    3: "Dress",
    4: "Coat",
    5: "Sandal",
    6: "Shirt",
    7: "Sneaker",
    8: "Bag",
    9: "Ankle Boot",
}
figure = plt.figure(figsize=(8, 8))
cols, rows = 3, 3
for i in range(1, cols * rows + 1):
    sample_idx = torch.randint(len(training_data), size=(1,)).item()
    img, label = training_data[sample_idx]
    figure.add_subplot(rows, cols, i)
    plt.title(labels_map[label])
    plt.axis("off")
    plt.imshow(img.squeeze(), cmap="gray")
plt.show()
在自己的文件里面建立自己的数据集

数据集里面必须包含下面三个函数:__init,____len,和getitem方法,数据集存放于img_dir,其他标签数据集存放在分开的文件夹annotations_file,样例代码如下:

import os
import pandas as pd
from torchvision.io import read_image

class CustomImageDataset(Dataset):
    def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):
        self.img_labels = pd.read_csv(annotations_file)
        self.img_dir = img_dir
        self.transform = transform
        self.target_transform = target_transform

    def __len__(self):
        return len(self.img_labels)

    def __getitem__(self, idx):
        img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
        image = read_image(img_path)
        label = self.img_labels.iloc[idx, 1]
        if self.transform:
            image = self.transform(image)
        if self.target_transform:
            label = self.target_transform(label)
        return image, label

__init__函数在实例化Dataset对象时运行一次。我们初始化包含图像、注释文件和这两种转换的目录(下一节将详细介绍)。

__len__函数返回数据集中样本的数量。

__getitem__函数从给定索引idx的数据集中加载并返回一个样本。根据索引确定图像在磁盘上的位置,使用read_image将其转换为一个张量,并从self中的csv数据中检索相应的标签。调用它们的变换函数(如果适用),并以元组的形式返回张量图像和相应的标签。

转换数据集来用DataLoaders来进行训练

Dataset检索我们的数据集的特性,并每次标记一个样本。在训练模型时,我们通常希望以“小批量”方式传递样本,在每个epoch重新编排数据以减少模型的过拟合,并使用Python的多处理来加速数据检索。

DataLoader是一个可迭代对象,它在一个简单的API中为我们抽象了这种复杂性。

代码如下:

from torch.utils.data import DataLoader

train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
迭代数据加载器

我们已经将该数据集加载到DataLoader中,并可以根据需要迭代该数据集。下面的每次迭代返回一批train_features和train_labels(分别包含batch_size=64个feature和标签)。因为我们指定了shuffle=True,所以在遍历所有批之后,数据会被打乱

# Display image and label.
train_features, train_labels = next(iter(train_dataloader))
print(f"Feature batch shape: {train_features.size()}")
print(f"Labels batch shape: {train_labels.size()}")
img = train_features[0].squeeze()
label = train_labels[0]
plt.imshow(img, cmap="gray")
plt.show()
print(f"Label: {label}")

1.4 转换

数据并不总是以训练机器学习算法所需的最终处理形式出现。我们使用转换来对数据进行一些操作,使其适合于训练。

所有TorchVision数据集都有两个参数——transform用于修改特性,target_transform用于修改标签——它们接受包含转换逻辑的可调用对象。torchvision。Transforms模块提供了几种常用的开箱即用转换。

FashionMNIST特性采用PIL Image格式,标签为整数。在训练中,我们需要将特征作为归一化张量,而将标签作为单热点编码张量。为了进行这些变换,我们使用了ToTensor和Lambda。

方法见下面:

import torch
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda

ds = datasets.FashionMNIST(
    root="data",
    train=True,
    download=True,
    transform=ToTensor(),
    target_transform=Lambda(lambda y: torch.zeros(10, dtype=torch.float).scatter_(0, torch.tensor(y), value=1))
)
ToTensor()

ToTensor将PIL图像或NumPy ndarray转换为FloatTensor。在[0]范围内缩放图像的像素强度值。

Lambda Transforms

Lambda变换应用任何用户定义的Lambda函数。在这里,我们定义了一个函数,将整数转换为一个单热的编码张量。

它首先创建一个大小为10(数据集中标签的数量)的零张量,并调用scatter_,它在标签y给出的索引上赋值=1。

target_transform = Lambda(lambda y: torch.zeros(
    10, dtype=torch.float).scatter_(dim=0, index=torch.tensor(y), value=1))

1.5 建立神经网络模型

神经网络由对数据进行操作的层/模块组成。torch.nn命名空间提供了构建自己的神经网络所需的所有构建块。PyTorch中的每个模块都是nn.Module的子类。神经网络本身就是由其他模块(层)组成的模块。这种嵌套结构允许轻松地构建和管理复杂的体系结构。

建立方法如下:

import os
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

1.5.1 用什么样的设备进行训练

我们都希望用GPU来加速训练,所以我们就使用以下代码来教案是否有GPU:

device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f'Using {device} device')

1.5.2 定义一个神经网络

由于神经网络在类nn.Module之下,我们通过子类化nn来定义我们的神经网络。模块,并在__init__中初始化神经网络层。每一个神经网络。模块子类实现了forward方法中对输入数据的操作。

class NeuralNetwork(nn.Module):
    def __init__(self):
        super(NeuralNetwork, self).__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

然后定义模型,跑到GPU底下运行

model = NeuralNetwork().to(device)
print(model)

要使用模型,我们将输入数据传递给它。这将执行模型的正向操作,以及一些后台操作。不要直接调用model.forward()

在输入上调用模型将返回一个10维张量,其中包含每个类的原始预测值。我们通过将它传递给神经网络的一个实例来得到预测概率。用Softmax模块。

X = torch.rand(1, 28, 28, device=device)
logits = model(X)
pred_probab = nn.Softmax(dim=1)(logits)
y_pred = pred_probab.argmax(1)
print(f"Predicted class: {y_pred}")

1.5.3 模型的层数

input_image = torch.rand(3,28,28)
print(input_image.size())
nn.Flatten

我们初始化nn.Flatten层,将每个2D 28x28图像转换为一个784像素值的连续数组(保持小批量维度(在dim=0))。

flatten = nn.Flatten()
flat_image = flatten(input_image)
print(flat_image.size())
nn.Linear

线性层是一个模块,它使用其存储的权重和偏差对输入进行线性变换。

layer1 = nn.Linear(in_features=28*28, out_features=20)
hidden1 = layer1(flat_image)
print(hidden1.size())
nn.ReLU

非线性激活是在模型的输入和输出之间创建复杂映射的原因。它们在线性变换后被用于引入非线性,帮助神经网络学习各种各样的现象。

在这个模型中,我们使用nn.ReLU在我们的线性层之间,但还有其他激活可以在您的模型中引入非线性。

print(f"Before ReLU: {hidden1}\n\n")
hidden1 = nn.ReLU()(hidden1)
print(f"After ReLU: {hidden1}")
nn.Sequential

nn.Sequential是模块的有序容器。数据按照定义的相同顺序通过所有模块。您可以使用顺序容器来构建一个像seq_modules这样的快速网络。

seq_modules = nn.Sequential(
    flatten,
    layer1,
    nn.ReLU(),
    nn.Linear(20, 10)
)
input_image = torch.rand(3,28,28)
logits = seq_modules(input_image)
nn.Softmax

神经网络的最后一个线性层返回logits ([-infty, infty]中的原始值),它被传递给nn.Softmax模块。对数被缩放到代表模型对每个类别的预测概率的值[0,1]。Dim参数指示值之和为1的维度。

softmax = nn.Softmax(dim=1)
pred_probab = softmax(logits)

1.5.4 模型的参数

神经网络中的许多层都是参数化的,即在训练过程中优化了相关的权重和偏差。子类化神经网络。模块自动跟踪模型对象中定义的所有字段,并使用模型的parameters()或named_parameters()方法访问所有参数。

print("Model structure: ", model, "\n\n")

for name, param in model.named_parameters():
    print(f"Layer: {name} | Size: {param.size()} | Values : {param[:2]} \n")

1.6 参数的优化

现在我们已经有了一个模型和数据,现在是时候通过优化数据上的参数来训练、验证和测试我们的模型了。训练模型是一个迭代的过程;在每次迭代(称为epoch)中,模型对输出进行猜测,计算其猜测中的误差(损失),收集误差对其参数的导数(如我们在前一节中看到的),并使用梯度下降优化这些参数。(可以看吴恩达视频加以理解)

代码前面就有过了

对以下东西有一个定义:

1.6.1 超参数

超参数是可调节的参数,让您控制模型优化过程。不同的超参数值会影响模型的训练和收敛速度

我们定义了以下训练超参数:

epochs——遍历数据集的次数

batch_size-参数更新之前通过网络传播的数据样本的数量

learning_rate-在每批/历期更新多少模型参数。值越小,学习速度越慢,而值越大,训练过程中行为不可预测。

给个样例:

learning_rate = 1e-3
batch_size = 64
epochs = 5

1.6.2 每一层循环优化

一旦我们设置了超参数,我们就可以用一个优化循环来训练和优化我们的模型。优化循环的每次迭代被称为一个epoch。

每个epoch由两个主要部分组成:

Train Loop—迭代训练数据集并尝试收敛到最优参数。

The Validation/Test Loop——遍历测试数据集,检查模型性能是否提高。

1.6.3 损失函数

常见的损失函数包括nn.MSELoss(均方误差)用于回归任务,nn.NLLLoss(负对数似然值)用于分类。 nn.CrossEntropyLoss 包含 nn.LogSoftmax 和 nn.NLLLoss.

# Initialize the loss function
loss_fn = nn.CrossEntropyLoss()

1.6.4 优化

优化是在每个训练步骤中调整模型参数以减少模型误差的过程。优化算法定义了如何执行这个过程(在本例中,我们使用随机梯度下降)。所有优化逻辑都封装在优化器对象中。方法吴恩达讲过

optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

应用看前面

1.7 模型的保存

前面就介绍过了,这里就不赘述了

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值