1. Computer Vision
计算机视觉,顾名思义:就是计算机的眼睛,凡是可以想到用机器看的方式来完成一些工作,都可以考虑计算机视觉的方法。像相机用来拍照片,照片分类;汽车的智能识别;人脸识别......
1.1 计算机视觉常用的一些库
torchvision
, torchvision.models
, torchvision.datasets
, torchvision.transforms
, torch.utils.data.Dataloader
等等,想一想这些库你在实战中大概都会用到里面的哪些方法......
1.2 加载数据集
python
# Setup training data
train_data = datasets.FashionMNIST(
root="data", # where to download data to?
train=True, # get training data
download=True, # download data if it doesn't exist on disk
transform=ToTensor(), # images come as PIL format, we want to turn into Torch tensors
target_transform=None # you can transform labels as well
)
# Setup testing data
test_data = datasets.FashionMNIST(
root="data",
train=False, # get test data
download=True,
transform=ToTensor()
)
首先我们用到了torchvision.datasets
里面的FashionMNIST数据集,一个图像-类别数据集,当然后续我们可以自定义这个数据集。
这里面的一些参数如:root下载数据到哪个文件夹下,train是下载训练集还是测试集,是否下载到本地,transform将输入的数据转换成对应的形式(这里由于我们读进来的是图片,我们需要把其转换为张量,用到了torchvision.transforms.ToTensor
),target_transform将表奇案进行一个处理(归一化、one-hot编码等等);当然,参数不止这些,用到其他参数可通过查文档的方式探索。这里得到的train_data
并不是我们想到那种直接就是(datas, labels)这样的两元组,实际上这个train_data是一个数据管理员,我们可以通过datas, labels = train_data.data, train_data.classes
来得到对应的数据和标签。
我们通过这样得到的图片的data的tensor形式,是pytorch默认的NCHW即Channel在前面,而pytorch更推荐 NHWC这种方式,一方面计算速度更快,另一方面在plt画图也采用的是NHWC的方式。
在进行数据加载的时候可以多通过可视化的方法来观察数据,可视化便于让你搞清楚数据究竟是什么样的形式,以及后来我们怎么将这个数据送入模型。
这里涉及到一个画多个子图的不同方式,一种是设定fig,然后在这个fig的基础上添加不同的子图;另一种是直接全局画子图,plt.subplot这是比较依赖全局的fig的,如果在画图时创建了多个fig,这就需要考虑可能出现问题了。推荐用前一种。
1.3 准备DataLoader
顾名思义就是将数据切分成不同的batchsize,然后将数据加载好。切分成batchsize后,处理数据会更加快速,同时梯度下降的次数会更多,这样提升模型能力的机会会更大。
python
from torch.utils.data import DataLoader
# Setup the batch size hyperparameter
BATCH_SIZE = 32
# Turn datasets into iterables (batches)
train_dataloader = DataLoader(train_data, # dataset to turn into iterable
batch_size=BATCH_SIZE, # how many samples per batch?
shuffle=True # shuffle data every epoch?
)
这里用到torch.utils.data.DataLoader
方法。
1.4 准备模型
1.4.1 简单的线性模型
python
from torch import nn
class FashionMNISTModelV0(nn.Module):
def __init__(self, input_shape: int, hidden_units: int, output_shape: int):
super().__init__()
self.layer_stack = nn.Sequential(
nn.Flatten(), # neural networks like their inputs in vector form
nn.Linear(in_features=input_shape, out_features=hidden_units), # in_features = number of features in a data sample (784 pixels)
nn.Linear(in_features=hidden_units, out_features=output_shape)
)
def forward(self, x):
return self.layer_stack(x)
这里只说明一点:nn.Flatten()将二维的图片数据拉伸为一维的数据,即:(height, width) => (height*width, )
。由于线性层只是一个二维的乘积,所以需要(channel, height*width)而不是(channel, height, width)
1.4.2 加入非线性层
python
# Create a model with non-linear and linear layers
class FashionMNISTModelV1(nn.Module):
def __init__(self, input_shape: int, hidden_units: int, output_shape: int):
super().__init__()
self.layer_stack = nn.Sequential(
nn.Flatten(), # flatten inputs into single vector
nn.Linear(in_features=input_shape, out_features=hidden_units),
nn.ReLU(),
nn.Linear(in_features=hidden_units, out_features=output_shape),
nn.ReLU()
)
def forward(self, x: torch.Tensor):
return self.layer_stack(x)
1.4.3 使用卷积神经网络
python
# Create a convolutional neural network
class FashionMNISTModelV2(nn.Module):
"""
Model architecture copying TinyVGG from:
https://poloclub.github.io/cnn-explainer/
"""
def __init__(self, input_shape: int, hidden_units: int, output_shape: int):
super().__init__()
self.block_1 = nn.Sequential(
nn.Conv2d(in_channels=input_shape,
out_channels=hidden_units,
kernel_size=3, # how big is the square that's going over the image?
stride=1, # default
padding=1),# options = "valid" (no padding) or "same" (output has same shape as input) or int for specific number
nn.ReLU(),
nn.Conv2d(in_channels=hidden_units,
out_channels=hidden_units,
kernel_size=3,
stride=1,
padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2,
stride=2) # default stride value is same as kernel_size
)
self.block_2 = nn.Sequential(
nn.Conv2d(hidden_units, hidden_units, 3, padding=1),
nn.ReLU(),
nn.Conv2d(hidden_units, hidden_units, 3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.classifier = nn.Sequential(
nn.Flatten(),
# Where did this in_features shape come from?
# It's because each layer of our network compresses and changes the shape of our input data.
nn.Linear(in_features=hidden_units*7*7,
out_features=output_shape)
)
def forward(self, x: torch.Tensor):
x = self.block_1(x)
# print(x.shape)
x = self.block_2(x)
# print(x.shape)
x = self.classifier(x)
# print(x.shape)
return x
卷积神经网络这块就不详细展开了,这里直接用的tinyVGG的网络结构及参数,不过应该大概知道这个卷积层发生了什么,池化层发生了什么......至于原理后期会再补充
1.5 训练模型、测试模型、获取推理结果
可以看到我们在实际中会对比多个模型的表现效果,那反复的写训练过程的代码会显得非常冗余且费时,所以此时我们可以考虑将训练过程、测试过程以及最终得到的结果过程提取成一个函数,以实现复用。我们只需改变传入模型的参数即可。
python
def train_step(model: torch.nn.Module,
data_loader: torch.utils.data.DataLoader,
loss_fn: torch.nn.Module,
optimizer: torch.optim.Optimizer,
accuracy_fn,
device: torch.device = device):
train_loss, train_acc = 0, 0
model.to(device)
for batch, (X, y) in enumerate(data_loader):
# Send data to GPU
X, y = X.to(device), y.to(device)
# 1. Forward pass
y_pred = model(X)
# 2. Calculate loss
loss = loss_fn(y_pred, y)
train_loss += loss
train_acc += accuracy_fn(y, y_pred.argmax(dim=1)) # Go from logits -> pred labels
# 3. Optimizer zero grad
optimizer.zero_grad()
# 4. Loss backward
loss.backward()
# 5. Optimizer step
optimizer.step()
# Calculate loss and accuracy per epoch and print out what's happening
train_loss /= len(data_loader)
train_acc /= len(data_loader)
print(f"Train loss: {train_loss:.5f} | Train accuracy: {train_acc * 100:.2f}%")
python
def test_step(data_loader: torch.utils.data.DataLoader,
model: torch.nn.Module,
loss_fn: torch.nn.Module,
accuracy_fn,
device: torch.device = device):
test_loss, test_acc = 0, 0
model.to(device)
model.eval() # put model in eval mode
# Turn on inference context manager
with torch.inference_mode():
for X, y in data_loader:
# Send data to GPU
X, y = X.to(device), y.to(device)
# 1. Forward pass
test_pred = model(X)
# 2. Calculate loss and accuracy
test_loss += loss_fn(test_pred, y)
test_acc += accuracy_fn(y, test_pred.argmax(dim=1) # Go from logits -> pred labels
)
# Adjust metrics and print out
test_loss /= len(data_loader)
test_acc /= len(data_loader)
print(f"Test loss: {test_loss:.5f} | Test accuracy: {test_acc * 100:.2f}%\n")
python
def eval_model(model: torch.nn.Module,
data_loader: torch.utils.data.DataLoader,
loss_fn: torch.nn.Module,
accuracy_fn,
device: torch.device=device):
"""Returns a dictionary containing the results of model predicting on data_loader.
Args:
model (torch.nn.Module): A PyTorch model capable of making predictions on data_loader.
data_loader (torch.utils.data.DataLoader): The target dataset to predict on.
loss_fn (torch.nn.Module): The loss function of model.
accuracy_fn: An accuracy function to compare the models predictions to the truth labels.
Returns:
(dict): Results of model making predictions on data_loader.
"""
loss, acc = 0, 0
model.eval()
with torch.inference_mode():
for X, y in data_loader:
X, y = X.to(device), y.to(device)
# Make predictions with the model
y_pred = model(X)
# Accumulate the loss and accuracy values per batch
loss += loss_fn(y_pred, y)
acc += acc_fn(y, y_pred.argmax(dim=1)) # For accuracy, need the prediction labels (logits -> pred_prob -> pred_labels)
# Scale loss and acc to find the average loss/acc per batch
loss /= len(data_loader)
acc /= len(data_loader)
return {"model_name": model.__class__.__name__, # only works when model was created with a class
"model_loss": loss.item(),
"model_acc": acc.item() * 100}
1.6 对最终结果的分析
根据我们训练好的模型,我们在推理阶段进行分析,这个过程可以包含很多内容,可以是简单的直接看准确率,也可以通过可视化,随机选取几个样本看看预测结果是什么样得,也可以通过混淆矩阵具体看看哪几种分类最相似等等。
2. 定制数据集
整个数据集就是为我们要完成的任务服务的,我们需要从给定的数据集中提取出对应的特征,并将这部分特征应用到下游的各种任务中去。那定制数据集一方面我们可以根据我们的需要,把所谓的数据封装成pytorch框架下使用的数据形式,另一方面我们也在了解pytroch框架底层是如何封装数据的。
一些封装好的:TorchVision, TorchText, TorchAudio, TorchRec
等等
在实际进行实验时,我们也是一点点搭起来的,为了确保实验的正确性,一开始我们只是用少量的数据以确保训练时间不会过长同时训练方向是正确的。此时,我们需要定制属于我们的数据集。
在定义我们数据集时首先要做到的就是Become one with data(Prepare Data)
,我们需要完全了解我们的数据是什么样的;数据存储结构是什么?共有多少张图片?训练的有多少?测试的有多少?具体某张图片是什么样的?进行一些可视化等等。
在使用plt.imshow()进行可视化的时候,一般接受的是numpy()数组,但是我们有时候传入tensor也可以,是因为pytorch会自动转换为numpy,保险起见,还是显式转换一下。
然后我们可以进行一些数据的转换或者说是数据增强。利用torchvision.transform这个库中的transformer.Compose([..., ..., ...])进行一系列数据操作,可以包括图片尺寸的改变、图片的反转、将图片数据转成tensor等等。
这些数据都处理好后,我们就可以将数据转换成ImageFolder类型兼容pytorch,等待后续训练的时候的调用。
python
from torchvision import datasets
train_data = datasets.ImageFolder(root=train_dir, # target folder of images
transform=data_transform, # transforms to perform on data (images)
target_transform=None) # transforms to perform on labels (if necessary)
test_data = datasets.ImageFolder(root=test_dir,
transform=data_transform)
接下来我们就可以像之前读取datasets.FashionMNIST()那样读取我们的数据了。但是注意:使用ImageFoder的前提是数据的文件格式必须按照标准化的分类形式排列,即:每个图片的父文件夹是类别,类别的父文件夹是训练或测试。
然后就是将data转换成DataLoader分成不同的batchsize,同时也可以添加num_workers.
最后我们可以在训练好的模型上进行一个别的图片的推理,比如从网上找一张有关食物的图片,但是此时这张图片不一定和我们原模型的输入一致,所以我们需要调整该输入,使其保持和原模型输入一致从而完成推理。这其中包括调整数据类型 、调整数据shape 、调整数据所在装置(cpu or gpu) 等等
3. 模块化
Going to Modular 是指将所运行的notebook中的一个个cell变成python的脚本script.py,便于使用一行的命令完成运行python train.py --model .......
将notebook中的cell转换成对应的script.py可使用%%writefile file/script.py
,当然也可以直接用IDE写脚本。