本文为 PyTorch 学习笔记,介绍了 PyTorch 项目的基本代码范式。

数据的导入

数据的导入需要用到两个 PyTorch 库,分别是 torch.utils.data.Datasettorch.utils.data.DataLoader

Dataset 类的使用

可以使用 Dataset 类处理自定义的数据集,示例如下:

class MyDataset(Dataset):  
  
    def __init__(self, root_dir, label_dir):  
        self.root_dir = root_dir  
        self.label_dir = label_dir  
  
        self.path = os.path.join(self.root_dir, self.label_dir)  
        self.image_name = os.listdir(self.path)  
  
  
    def __getitem__(self, idx):  
        img_name = self.image_name[idx]  
        img_path = os.path.join(self.path, img_name)  
          
        label = self.label_dir  
        img = Image.open(img_path)  
  
        return img, label  
  
  
    def __len__(self):  
  
        return len(self.image_name)

自定义自己的数据类需要继承 PyTorch 的 Dataset 类。

方法的重写

继承 Dataset 父类后,还需要根据数据集的具体结构和任务需求重写一些方法,其中必须重写的方法包括:__init____getitem____len__

构造方法的重写

重写构造方法的目的是定义其他方法所需的属性,例如上文示例中的根路径、标签路径等等。

getitem 方法的重写

除了对象 self 以外,__getitem__ 方法接受一个额外的变量 idx,表示数据集中一个数据的索引。
我们需要重写该方法,根据传入的索引返回将要输入模型的特征和标签。

len 方法的重写

重写该方法以定义数据集的长度,例如上文示例用图片数量定义数据集长度

使用已有数据集

这些提供的数据集使用方式很简单,只需要按照 PyTorch 文档中的使用方法填写参数,只需一行代码即可。
例如 CIFAR10 的使用方法如下:

test_set = torchvision.datasets.CIFAR10(
				"./dataset", 
				train=False,  
                transform=torchvision.transforms.ToTensor(), 
                download=True
)

数据的处理

处理图片

PyTorch 在 torchvision.transforms 中提供了许多处理图片的工具,例如常用的工具类 torchvision.transforms.ToTensor() 用于将 PIL 或 ndarray (使用 opencv) 格式的图片转换为 PyTorch 的 Tensor 类型,以便输入模型。
除此之外,transform 模块还提供了常见的图片处理工具,例如图片反转、切割等等。

DataLoader 类的使用

test_loader = DataLoader(dataset=test_set, batch_size=64, shuffle=True, 
						 num_workers=0, drop_last=False)

遍历数据集的方法:

for data in test_loader:  
    imgs, targets = data  

搭建模型

搭建模型有两种方式,分别是:

  • 从零搭建
  • 从已有的模型搭建

从零搭建模型

首先,定义一个继承 nn.module 的模型类,然后重写方法即可,示例如下:

class Model(nn.Module):  
    def __init__(self):  
        super(Model, self).__init__()  
        self.model = nn.Sequential(  
            nn.Conv2d(3, 32, 5, 1, 2),  
            nn.MaxPool2d(2),  
            nn.LeakyReLU(),  
            nn.Conv2d(32, 32, 5, 1, 2),  
            nn.MaxPool2d(2),  
            nn.LeakyReLU(),  
            nn.Conv2d(32, 64, 5, 1, 2),  
            nn.MaxPool2d(2),  
            nn.LeakyReLU(),  
            nn.Flatten(),  
            nn.Linear(64 * 4 * 4, 256),  
            nn.ReLU(),  
            nn.Linear(256, 64),  
            nn.ReLU(),  
            nn.Linear(64, 10),  
        )  
  
    def forward(self, x):  
        x = self.model(x)  
        return x

从已有的模型搭建模型

导入已有模型

获取已有的模型有两种方式,第一种方式是使用 PyTorch 提供的知名模型,例如 vgg16。

vgg16_pt = torchvision.models.vgg16(pretrained=True)

参数 pretrained 为 True 时会下载预训练后的模型参数,反之则使用未训练的随机模型参数

修改已有模型

有时候,下载的模型无法满足我们的任务需求,例如处理分类问题时,模型的输出层的神经元个数与我们需要区分的类别数量不同。这个时候,我们就需要修改模型。

修改模型所用到的函数或方法主要有以下几个:

# 1. 增加:add_module 方法,可以增加单层也可以增加 Sequential
vgg16.add_module("mod", nn.Sequential(OrderedDict([  # 可以不给每层取名
    ("linear", nn.Linear(1000, 256)),  
    ("relu", nn.ReLU()),  
    ("softmax", nn.Softmax(10))  
])))

vgg16.add_module(nn.Linear(64, 10))


# 2. 修改:直接使用 [idx] 和 `.`
print(vgg16)  # 先print查看模型结构

vgg16.classifier[6] = nn.Linear(64, 10)
vgg16.classifier[6] = nn.Sequential(
									nn.Linear(64, 10),
									nn.Linear(10, 5)
)


# 3. 删除:使用 del 即可
del vgg16.classifier

训练模型

下面提供一个训练示例:

# * 3. Create the model object and Setting hyperparameters  
  
# create model object  
model = Model()  
if torch.cuda.is_available():  
    model = model.cuda()  
    
# define the loss function  
loss_fn = nn.CrossEntropyLoss()  
if torch.cuda.is_available():  
    loss_fn = loss_fn.cuda()  
    
# define the optimizer  
learning_rate = 0.01  
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)  
# optimizer = torch.optim.Adam(model.parameters())  
  
# Setting  
total_train_step = 0  
total_test_step = 0  
epoch = 30  
  
# set tensorboard  
writer = SummaryWriter("./logs_model")
# * 4. Training the model
for i in range(epoch):  
    print(f"------ Epoch {i} begin. ------")  
  
    start_time = time.time()  
    # train  
    model.train()  # for some certain layer(such as dropout), See PyTorch.org.  
    for data in train_dataloader:  
        imgs, targets = data  
        # move to gpu  
        if torch.cuda.is_available():  
            imgs, targets = imgs.cuda(), targets.cuda()  
  
        output = model(imgs)  
  
        loss = loss_fn(output, targets)  
  
        # optimize  
        optimizer.zero_grad()  
        loss.backward()  
        optimizer.step()  
  
        total_train_step = total_train_step + 1  
        if total_train_step % 100 == 0:  
            print(f"Train step: {total_train_step} / Loss: {loss}")  
            end_time = time.time()  
            print(f"tic {end_time-start_time}")  
            writer.add_scalar("train_loss(step)", loss, total_train_step)  
  
    # test  
    model.eval()  # for some certain layer  
    total_test_loss = 0  
    total_accuracy = 0  
    with torch.no_grad():  
        for data in test_dataloader:  
            imgs, targets = data  
            # move to gpu  
            if torch.cuda.is_available():  
                imgs, targets = imgs.cuda(), targets.cuda()  
            output = model(imgs)  
  
            # compute accuracy  
            accuracy = (output.argmax(1) == targets).sum()  
            total_accuracy += accuracy  
  
            loss = loss_fn(output, targets)  
            total_test_loss += loss  
  
    # Show and log test info  
    print(ColorText.info(f"\nTotal loss in test set: {total_test_loss}"))  
    print(  
        ColorText.info(f"Total accuracy in test set: {total_accuracy / test_set_size}")  
    )  
    writer.add_scalar("test_loss(epoch)", total_test_loss, total_test_step)  
    writer.add_scalar(  
        "test_accuracy(epoch)", total_accuracy / test_set_size, total_test_step  
    )  
    total_test_step += 1  
  
    # Save Model  
    torch.save(model, f"model_{i}.pt")  
    print(ColorText.info(f"..::Model {i} Saved.."))  
  
  
writer.close()

附录

Tensorboard 的使用

示例:
首先在脚本内记录数据到指定路径

from torch.urils.tensorboard import SummaryWriter

# writer 对象
writer = SummaryWriter("./logs")  # log_dir

# 记录数据
writer.add_scalar("loss curve", y_value, x_value)

# 
writer.close()

然后,命令行用 Tensorboard 打开日志路径

tensorboard --log_dir ./logs