用Pytorch训练神经网络


    本文目标是如何使用Pytorch以尽可能短的方式从图像中预测颜色、填充级别等连续属性。我们将学习加载现有网络,修改它以预测特定属性,并用不到40行代码(不包括空格)对其进行训练。
    Standart神经网络通常专注于分类问题,比如识别猫和狗。然而,这些网络可以很容易地进行修改,从图像中预测连续属性,如年龄、大小或价格。
    首先,让我们导入软件包并定义主要的训练参数:
    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/blob/main/Infer.py
    该脚本加载你之前训练和保存的网络,并使用它进行预测。
    这里的大部分代码与训练脚本相同,只有几处不同:
    Net.load_state_dict(torch.load(modelPath)) # Load trained model
    从modelPath中的文件加载我们之前训练和保存的网络
    #Net.eval()
    将网络从训练模式转换为评估模式。这主要意味着不会计算批次标准化统计数据。
    虽然使用它通常是一个好主意,但在我们的例子中,它实际上会降低准确性,因此我们将在没有它的情况下使用网络。
    with torch.no_grad():
    这意味着网络运行时没有收集梯度。梯度只与训练相关,收集梯度需要大量资源。
    感谢阅读!