极市导读
本文目标是如何使用Pytorch以尽可能短的方式从图像中预测颜色、填充级别等连续属性。我们将学习加载现有网络,修改它以预测特定属性,并用不到40行代码(不包括空格)对其进行训练。 >>加入极市CV技术交流群,走在计算机视觉的最前沿
神经网络通常专注于分类问题,比如识别猫和狗。然而,这些网络可以很容易地进行修改,从图像中预测连续属性,如年龄、大小或价格。
首先,让我们导入软件包并定义主要的训练参数:
import numpy as np
import torchvision.models.segmentation
import torch
import torchvision.transforms as tf
Learning_Rate=1e-5
width=height=900
batchSize=1
学习率:是训练过程中梯度下降的步长。
宽度和高度是用于训练的图像的尺寸。训练过程中的所有图像都将调整为该大小。
batchSize:是将用于每次训练迭代的图像数。
batchSize,width,height将与训练的内存需求成比例。根据硬件的不同,可能需要使用较小的批处理大小来避免内存不足问题。
请注意,由于我们只使用单一大小的图像进行训练,因此训练后的网络可能仅限于使用这种大小的图像。
接下来,让我们创建训练数据。我们想做一个简单的演示,所以我们将创建一个用白色填充到一定高度的图像。该网络的目标是预测被白色覆盖的图像的比例。这可以很容易地用于从真实图像预测更复杂的属性,如其他教程所示。
例如:
在上图中,我们希望网络预测为0.47,因为47%的图像填充为白色。在底图中,我们希望网络预测0.76,因为76%的图像填充为白色。
在实际环境中,你可能会从文件中加载数据。在这里,我们将动态创建它:
def ReadRandomImage():
FillLevel=np.random.random() # Set random fill level
Img=np.zeros([900,900,3],np.uint8) # Create black image
Img[0:int(FillLevel*900),:]=255 # Fill the image
transformImg=tf.Compose([tf.ToPILImage(),
tf.Resize((height,width)),tf.ToTensor(),tf.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))]) # Set image transformation
Img=transformImg(Img) # Transform to pytorch
return Img,FillLevel
在第一部分中,我们创建图像:
FillLevel=np.random.random() # Set random fill level
Img=np.zeros([900,900,3],np.uint8) # Create black image
Img[0:int(FillLevel*900),:]=255 # Fill the image
第一行选择0–1之间的随机数作为填充级别。
Img=np.zeros([900,900,3]) 创建一个大小为900X900的矩阵,填充零作为图像。这相当于一个高度和宽度为900的黑色图像。图像有3个对应于RGB的通道。
接下来,我们用白色填充图像的顶部,直到填充水平线。
Img[0:int(FillLevel*900),:]=255
现在我们创建了图像,我们对其进行处理并将其转换为Pytorch格式:
transformImg=tf.Compose([tf.ToPILImage(),
tf.Resize((height,width)),tf.ToTensor(),tf.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))]) # Set image transformation
这定义了一组将应用于图像的变换。这包括转换为PIL格式(转换的标准格式),以及调整大小和转换为PyTorch格式。
对于图像,我们还通过减去平均值并除以像素强度来标准化图像中像素的强度。
对于我们的简单图像,标准化和大小调整并不是真正需要的,但这些转换对于真实图像很重要。
接下来,我们将变换应用于图像:
Img=transformImg(Img)
对于训练,我们需要使用一批图像。这意味着在4D矩阵中,多个图像相互叠加。我们使用以下函数创建batch:
def LoadBatch(): # Load batch of images
images = torch.zeros([batchSize,3,height,width])
FillLevel = torch.zeros([batchSize])
for i in range(batchSize):
images[i],FillLevel[i]=ReadRandomImage()
return images,FillLevel
第一行创建一个空的4d矩阵,该矩阵将存储尺寸为[batchSize,Channel,height,width]的图像,其中Channel是图像的层数;这是RGB图像的3。下一行创建一个数组,其中存储填充级别。这将作为我们训练的标签。
下一部分使用前面定义的ReadRandomImage函数将图像集和填充级别加载到空矩阵:
for i in range(batchSize):
images[i],FillLevel[i]=ReadRandomImage()
现在我们可以加载数据了,是时候加载神经网络了:
device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')
Net = torchvision.models.resnet18(pretrained=True) # Load net
Net.fc = torch.nn.Linear(in_features=512, out_features=1, bias=True)
Net = Net.to(device)
optimizer = torch.optim.Adam(params=Net.parameters(),lr=Learning_Rate)
第一部分是确定计算机是否有GPU或CPU。如果有Cuda GPU,训练将在GPU上进行:
device = torch.device(‘cuda’) if torch.cuda.is_available() else torch.device(‘cpu’)
对于任何实际数据集,使用CPU进行训练都非常缓慢。接下来,我们加载用于图像分类的网络:
Net = torchvision.models.resnet18(pretrained=True)
_torchvision.models_包含许多有用的图像分类模型。Reseet18是一个轻量级的分类模型,适用于低资源训练或简单的数据集。对于更难的问题,最好使用resenet50(请注意,数字指的是网络中的层数)。
通过设置pretrained=True,我们在Imagenet数据集上加载带有预训练权重的网络。
在学习新问题时,最好从预训练的模型开始,因为它允许网络使用以前的经验并更快地收敛。
我们可以看到我们刚刚通过print(Net)查看网络的所有结构和所有层:
print(Net)
…
…
(avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
(fc): Linear(in_features=512, out_features=1000, bias=True)
这会按使用顺序打印层。
网络的最后一层是线性变换,输入512层,输出1000层。1000代表输出类的数量(这个网络是在图像网络上训练的,图像网络将图像分为1000个类中的一个)。
因为我们只想预测一个值,所以我们想用一个输出的新线性层来代替它:
Net.fc = torch.nn.Linear(in_features=512, out_features=1, bias=True)
公平地说,这部分是可选的,因为一个有1000个输出通道的网络只需忽略999个通道就可以预测一个值。但这样更优雅。
接下来,我们将网络加载到GPU或CPU设备中:
Net=Net.to(device)
最后,我们加载一个优化器:
optimizer=torch.optim.Adam(params=Net.parameters(),lr=Learning_Rate) # Create adam optimizer
优化器将在反向传播步骤中控制梯度速率。Adam是最快的优化器之一。
最后,我们通过加载数据开始训练,使用网络进行预测:
AverageLoss=np.zeros([50]) # Save average loss for display
for itr in range(2001): # Training loop
images,GTFillLevel=LoadBatch() # Load taining batch
images=torch.autograd.Variable(images,requires_grad=False).
to(device)
GTFillLevel = torch.autograd.Variable(GTFillLevel,
requires_grad=False).to(device)
PredLevel=Net(images) # make prediction
首先,我们希望保存训练期间的平均损失;我们创建一个数组来存储最后50步的损失。
AverageLoss=np.zeros([50])
这将使我们能够跟踪网络的学习情况。
我们将训练2000个步骤:
for itr in range(2000):
LoadBatch在前面定义,帮助加载一批图像及其填充级别。
torch.autograd.Variable_:将数据转换成网络可以使用的梯度变量。我们设置Requires_grad=False,因为我们只将梯度应用于网络的层。_to(device) 将张量复制到对应的设备(GPU/CPU)。
最后,我们将图像输入网络,得到预测结果。
PredLevel=Net(images)
一旦我们做出预测,我们可以将其与实际填充水平进行比较,并计算损失。损失是图像的预测和真实填充水平之间的绝对差(L1):
Loss=torch.abs(PredLevel-GTFillLevel).mean()
请注意,我们不是将损失应用于一张图像,而是应用于批次中的多张图像,因此我们需要将损失的平均值作为单个数字。
一旦我们计算了损失,我们就可以应用反向传播并改变权重。
Loss.backward() # Backpropogate loss
Optimizer.step() # Apply gradient descent change to wei
在训练期间,我们想看看我们的平均损失是否减少,看看网络是否真的学到了什么。
因此,我们将最后50个损失值存储在一个数组中,并显示每个步骤的平均值:
AverageLoss[itr%50]=Loss.data.cpu().numpy() # Save loss average
print(itr,") Loss=",Loss.data.cpu().numpy(),
'AverageLoss',AverageLoss.mean())
这涵盖了整个训练阶段,但我们还需要保存经过训练的模型。否则,一旦程序停止,它就会丢失。
保存很费时,所以我们希望大约每200步只做一次:
if itr % 200 == 0:
print(“Saving Model” +str(itr) + “.torch”)
torch.save(Net.state_dict(), str(itr) + “.torch”)
在运行这个脚本大约200步之后,网络应该会给出很好的结果。
总共40行代码,不包括空格。
完整代码可在此处找到:https://github.com/sagieppel/Train-neural-net-to-predict-continuous-property-from-an-image-in-40-lines-of-code-with-PyTorch
训练并保存网络后,可以加载网络进行预测:https://github.com/sagieppel/Train-neural-net-to-predict-continuous-property-from-an-image-in-40-lines-of-code-with-PyTorch/blob/main/Infer.py
该脚本加载你之前训练和保存的网络,并使用它进行预测。
这里的大部分代码与训练脚本相同,只有几处不同:
Net.load_state_dict(torch.load(modelPath)) # Load trained model
从modelPath中的文件加载我们之前训练和保存的网络
#Net.eval()
将网络从训练模式转换为评估模式。这主要意味着不会计算批次标准化统计数据。
虽然使用它通常是一个好主意,但在我们的例子中,它实际上会降低准确性,因此我们将在没有它的情况下使用网络。
with torch.no_grad():
这意味着网络运行时没有收集梯度。梯度只与训练相关,收集梯度需要大量资源。
感谢阅读!
公众号后台回复“CVPR2022”获取论文分类合集下载~
# CV技术社群邀请函 #
备注:姓名-学校/公司-研究方向-城市(如:小极-北大-目标检测-深圳)
即可申请加入极市目标检测/图像分割/工业检测/人脸/医学影像/3D/SLAM/自动驾驶/超分辨率/姿态估计/ReID/GAN/图像增强/OCR/视频理解等技术交流群
每月大咖直播分享、真实项目需求对接、求职内推、算法竞赛、干货资讯汇总、与 10000+来自港科大、北大、清华、中科院、CMU、腾讯、百度等名校名企视觉开发者互动交流~