使用PyTorch已有库ResNet搭建基本二分类模型,并在GPU上进行训练。

上一节中,我们介绍了Pytorch的基本知识,如数据格式,梯度,损失等内容。

在本节中,我们将介绍如何使用Pytorch来搭建一个经典的分类神经网络。

搭建一个神经网络并训练,大致有这么四个部分:

1、准备数据;

2、搭建模型;

3、评估函数;

4、优化网络权重。

先上一张模型结构图,基本包含了一个网络模型所包含的内容了。

pso训练神经网络python pytorch 训练神经网络_数据集

 接下来依次介绍。

一.数据准备

    这一部分内容在上一篇中已详细讲过,这里就不多赘述了。

二.搭建模型

 1、层的概念(神经网络的基本组建单元)

针对y=wx+b,搭建了一个简易的线性模型,如下:

from torch.nn import Linear
inp = Variable(torch.randn(1,10))
myLayer = Linear(in_features=10,out_features=5,bias=True)
myLayer(inp)

myLayer.weight  #通过权重来访问
Output :
Parameter containing:

-0.2386 0.0828 0.2904 0.3133 0.2037 0.1858 -0.2642 0.2862 0.2874 0.1141
0.0512 -0.2286 -0.1717 0.0554 0.1766 -0.0517 0.3112 0.0980 -0.2364 -0.0442
0.0776 -0.2169 0.0183 -0.0384 0.0606 0.2890 -0.0068 0.2344 0.2711 -0.3039
0.1055 0.0224 0.2044 0.0782 0.0790 0.2744 -0.1785 -0.1681 -0.0681 0.3141
0.2715 0.2606 -0.0362 0.0113 0.1299 -0.1112 -0.1652 0.2276 0.3082 -0.2745
[torch.FloatTensor of size 5x10]

myLayer.bias  #通过偏置来访问
Output :
Parameter containing:
-0.2646
-0.2232
0.2444
0.2177
0.0897
[torch.FloatTensor of size 5

在不同的框架里,线性层有着不一样的名字,如:Dense或fully connected layers.

一般情况下,为了解决实际问题,需要同时搭建多个线性层,就象下面这样:

myLayer1 = Linear(10,5)
myLayer2 = Linear(5,2)
myLayer2(myLayer1(inp))

其中,每层的参数都是不一样:

Layers Weight1
Layer1 3.0
Layer2 2.0

不过,只是简单的堆叠线性层并不能有效帮助网络学习到更多新的东西。这里举一个例子,如:

Y=2(3X1)-2LinearLayers

Y=6(X1)-1 LinearLayers

为了解决上面这个问题,就需要引入非线性函数的概念,也就是激活函数。

以下是一些常用的激活函数:

Sigmoid :f(x)=1 /(1+e^(-x))
Tanh:f(x)=(e^x-e^(-x)/(e^x+e^(-x)))
ReLU:f(x)=max(0,x)
Leaky ReLU:f(x)=x  x>=0  ;f(x)=ax  x<0 {a<0,1}

sample_data = Variable(torch.Tensor([[1,2,-1,-1]]))
myRelu = ReLU()
myRelu(sample_data)
Output:
Variable containing:
1 2 0 0
[torch.FloatTensor of size 1x4]

2、搭建一个深度学习算法

以下是基于nn.Module搭建的简易模型类

class MyFirstNetwork(nn.Module):
    def __init__(self,input_size,hidden_size,output_size):
        super(MyFirstNetwork,self).__init__()
        self.layer1 = nn.Linear(input_size,hidden_size)
        self.layer2 = nn.Linear(hidden_size,output_size)
    def __forward__(self,input):
        out = self.layer1(input)
        out = nn.ReLU(out)
        out = self.layer2(out)
        return out

三.损失函数

机器学习主要解决的问题有:二分类,多分类。,回归

1、回归:使用线性网络的最近一层的其中一个输出值。

 一般情况下,回归的损失函数使用MSE(Mean Square Error)来描述

loss = nn.MSELoss()
input = Variable(torch.randn(3, 5), requires_grad=True)
target = Variable(torch.randn(3, 5))
output = loss(input, target)
output.backward()

2、分类:使用sigmoid激活函数(近0或1)作为最终输出值。也即2分类问题。

对于分类,使用交叉熵损失函数,算法基本实现如下:

def cross_entropy(true_label, prediction):
    if true_label == 1:
        return -log(prediction)
    else:
        return -log(1 - prediction)

3、多分类:使用softmax层作为最终输出。

loss = nn.CrossEntropyLoss()
input = Variable(torch.randn(3, 5), requires_grad=True)
target = Variable(torch.LongTensor(3).random_(5))
output = loss(input, target)
output.backward()

常用损失函数:

loss(input, target)=|input-target|
    loss = torch.nn.L1Loss(reduce=True,size_average=False)
MSE Loss:回归时使用 
    loss = torch.nn.MSELoss(reduce=False,size_average=False)
BCELoss
loss = torch.nn.BCELoss(reduce=False,size_average=False)
CrossEntropy loss:多分类时使用 
    loss = torch.nn.CrossEntropyLoss(reduce=False,size_average=False,weight=None)
NLL Loss:非平衡数据集分类时使用   Log likelihood loss损失 ,用于训练一个n类分类器
    m=torch.nn.LogSoftmax()
    loss = torch.nn.NLLLoss()
    input=Variable(torch.randn(3,5),requires_grad=True)
    target=Variable(torch.LongTensor([1,0,4]))
    output=loss(m(input),target)
NLL Loss2d:像素级分类时使用(语义分割)
    m=nn.Conv2d(16,32,(3,3)).float()
    loss = nn.NLLLoss2d()
    input = Variable(torch.randn(3,16,10,10)
    target = Variable(torch.LongTensor(3,8,8).random_(0,4))
    output = loss(m(input),target)
MultiMarginLoss :适用于多分类模型
    x = Variable(torch.randn(3,10))
    y = Variable(torch.LongTensor(3).random_(10))
    loss = torch.nn.MultiMarginLoss()

四.优化器

常用优化器如下:

ADADELTA
Adagrad
Adam
SparseAdam
Adamax
ASGD
LBFGS
RMSProp
Rprop
SGD

举例如下:
optimizer = optim.SGD(model.parameters(), lr = 0.01)

优化器的使用方法如下:

for input, target in dataset:
optimizer.zero_grad()
output = model(input)
loss = loss_fn(output, target)
loss.backward()
optimizer.step()

五、搭建一个图像分类的深度学习模型

解决任何问题前很重要的一步是获取数据,Kaggle上提供了大量竞赛用数据用于图像识别。

1、这里制作了一套简单的数据集:dogsandcats,格式如下:

dogsandcats/
  train/
    dog/
      dog.183.jpg
      dog.184.jpg
    cat/
      cat.17.jpg
      cat.2.jpg
   valid/
     dog/
       dog.192.jpg
       dog.132.jpg
     cat/
       cat.132.jpg
       cat.23.jpg

2、读取数据集的脚本文件:

path = './dogsandcats/'
#Read all the files inside our folder.
files = glob(os.path.join(path,'*/*.jpg'))  #获取文件夹中文件
print(f'Total no of images {len(files)}')
no_of_images = len(files)
#Create a shuffled index which can be used to create a validation data set
shuffle = np.random.permutation(no_of_images)  #打乱数据顺序
#Create a validation directory for holding validation images.
os.mkdir(os.path.join(path,'valid'))
#Create directories with label names
for t in ['train','valid']:   #新建两个文件夹,分别存放两个不同的类别
    for folder in ['dog/','cat/']:
        os.mkdir(os.path.join(path,t,folder))
#Copy a small subset of images into the validation folder.
for i in shuffle[:2000]:
    folder = files[i].split('/')[-1].split('.')[0]
    image = files[i].split('/')[-1]
    os.rename(files[i],os.path.join(path,'valid',folder,image))
#Copy a small subset of images into the training folder.
for i in shuffle[2000:]:
    folder = files[i].split('/')[-1].split('.')[0]
    image = files[i].split('/')[-1]
    os.rename(files[i],os.path.join(path,'train',folder,image))

3、加载数据集进Tensor

在Pytorch中,torchvision.datasets提供了一个接口ImageFolder可用来加载图片。

基本功能三步曲:裁剪成统一尺寸,标准化数据集,转换成Tensor。

simple_transform=transforms.Compose([transforms.Scale((224,224)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],[0.229,0.224,0.225])
train = ImageFolder('dogsandcats/train/',simple_transform)
valid = ImageFolder('dogsandcats/valid/',simple_transform)

train.class_to_idx - {'cat': 0, 'dog': 1}
train.classes - ['cat', 'dog']

对Tensor中图片进行可视化显示,是调试中不可缺少的一环。

def imshow(inp):
"""Imshow for Tensor."""
    inp = inp.numpy().transpose((1, 2, 0))
    mean = np.array([0.485, 0.456, 0.406])
    std = np.array([0.229, 0.224, 0.225])
    inp = std * inp + mean
    inp = np.clip(inp, 0, 1)
    plt.imshow(inp)

imshow(train[50][0])  #可以用来显示图片内容

4、批量加载Tensor

使用GPU训练深度学习模型时,为了提高芯片的利用率,一般一次性会导入多张图片。

train_data_gen = torch.utils.data.DataLoader(train,batch_size=64,num_workers=3)
valid_data_gen = torch.utils.data.DataLoader(valid,batch_size=64,num_workers=3)  #num_workers 表示并行处理数

5、搭建一个网络模型

在处理真实问题时,通常情况下,我们很少搭建全新的模型。目前已有很多成熟的方案。如ResNet,在目前的Pytorch中已经完全实现,只能简单调用就可以了。

model_ft = models.resnet18(pretrained=True)
num_ftrs = model_ft.fc.in_features
model_ft.fc = nn.Linear(num_ftrs, 2)
if is_cuda:
    model_ft = model_ft.cuda()

ResNet在PyTorch实现:

ResNet(
    (conv1):Conv2d(3,64,kernel_size=(7,7),stride=(2,2),padding=(3,3),bias=False)
    (bn1):BatchNorm2d(64,eps=1e-5,momentum=0.1,affine=True)
    (relu):ReLU(inplace)
    (maxpool):MaxPool2d(size=(3,3),stride-(2,2),padding=(1,1),dilation=(1,1))
    (layer1):Sequential(
       (0):BasicBlock(
           (conv1):Conv2d(64,64,kernel_size=(3,3),stride=(1,1),padding=(1,1),bias=False)
           (bn1):BatchNorm2d(64,eps=1e-5,momentum=0.1,affine=True)
           (relu):ReLU(inplace)
           (conv2):Conv2d(64,64,kernel_size=(3,3),stride=(1,1),padding=(1,1),bias=False)
           (bn2):BatchNorm2d(64,eps=1e-5,momentum=0.1,affine=True)
       (1):BasicBlock(
           (conv1):Conv2d(64,64,kernel_size=(3,3),stride=(1,1),padding=(1,1),bias=False)
           (bn1):BatchNorm2d(64,eps=1e-5,momentum=0.1,affine=True)
           (relu):ReLU(inplace)
           (conv2):Conv2d(64,64,kernel_size=(3,3),stride=(1,1),padding=(1,1),bias=False)
           (bn2):BatchNorm2d(64,eps=1e-5,momentum=0.1,affine=True)
 (layer2):Sequential(
       (0):BasicBlock(
           (conv1):Conv2d(64,128,kernel_size=(3,3),stride=(2,2),padding=(1,1),bias=False)
           (bn1):BatchNorm2d(128,eps=1e-5,momentum=0.1,affine=True)

ResNet是基于数据集ImageNet来预测1000个分类的网络。所以,我们不能直接使用,为了能够用来处理我们的数据集,需要在最后一层把1000个分类改成这里的2类,

model_ft.fc = nn.Linear(num_ftrs, 2)

如果当前有可用的GPU,当前已成功安装了CUDA,使用前需要在代码里指定,否则默认会使用CPU。

if is_cuda:
     model_ft = model_ft.cuda()

6、训练模型

# Loss and Optimizer
learning_rate = 0.001
criterion = nn.CrossEntropyLoss()
optimizer_ft = optim.SGD(model_ft.parameters(), lr=0.001, momentum=0.9)
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7,
gamma=0.1)

下面是完整的训练模型接口,加载一个模型并运行加个周期来提高我们算法。

def train_model(model, criterion, optimizer, scheduler, num_epochs=25):
    since = time.time()
    best_model_wts = model.state_dict()
    best_acc = 0.0
    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch, num_epochs - 1))
        print('-' * 10)
        # Each epoch has a training and validation phase
        for phase in ['train', 'valid']:
            if phase == 'train':
                scheduler.step()
                model.train(True) # Set model to training mode
            else:
                model.train(False) # Set model to evaluate mode
            running_loss = 0.0
            running_corrects = 0
            # Iterate over data.
            for data in dataloaders[phase]:
                # get the inputs
                inputs, labels = data
                # wrap them in Variable
                if is_cuda:
                    inputs = Variable(inputs.cuda())
                    labels = Variable(labels.cuda())
                else:
                    inputs, labels = Variable(inputs), Variable(labels)
                # zero the parameter gradients
                optimizer.zero_grad()
                # forward
                outputs = model(inputs)
                _, preds = torch.max(outputs.data, 1)
                loss = criterion(outputs, labels)
                # backward + optimize only if in training phase
                if phase == 'train':
                    loss.backward()
                    optimizer.step()
                # statistics
                running_loss += loss.data[0]
                running_corrects += torch.sum(preds == labels.data)
           epoch_loss = running_loss / dataset_sizes[phase]
           epoch_acc = running_corrects / dataset_sizes[phase]
           print('{} Loss: {:.4f} Acc: {:.4f}'.format(
           phase, epoch_loss, epoch_acc))
           # deep copy the model
           if phase == 'valid' and epoch_acc > best_acc:
               best_acc = epoch_acc
               best_model_wts = model.state_dict()
        print()
    time_elapsed = time.time() - since
    print('Training complete in {:.0f}m {:.0f}s'.format(
    time_elapsed // 60, time_elapsed % 60))
    print('Best val Acc: {:4f}'.format(best_acc))
    # load best model weights
    model.load_state_dict(best_model_wts)
    return model

一段时间后,训练LOG显示如下:

Epoch 18/24
----------
train Loss: 0.0044 Acc: 0.9877
valid Loss: 0.0059 Acc: 0.8740
Epoch 19/24
----------
train Loss: 0.0043 Acc: 0.9914
valid Loss: 0.0059 Acc: 0.8725
Epoch 20/24
----------
train Loss: 0.0041 Acc: 0.9932
valid Loss: 0.0060 Acc: 0.8725
Epoch 21/24
----------
train Loss: 0.0041 Acc: 0.9937
valid Loss: 0.0060 Acc: 0.8725
Epoch 22/24
----------
train Loss: 0.0041 Acc: 0.9938
valid Loss: 0.0060 Acc: 0.8725
Epoch 23/24
----------
train Loss: 0.0041 Acc: 0.9938
valid Loss: 0.0060 Acc: 0.8725
Epoch 24/24
----------
train Loss: 0.0040 Acc: 0.9939
valid Loss: 0.0060 Acc: 0.8725
Training complete in 27m 8s
Best val Acc: 0.874000

这是在Titan X GPU上训练了30分钟后的结果。后续会使用不同的技术可以更快地训练模型。