深度学习之pytorch常用函数及如何搭建图像分类模型

  • pytorch最基本的操作对象是Tensor(张量),它表示一个多维矩阵,类似于numpy的ndarrays,其可在GPU上使用可以加速计算,和numpy类型转换也非常方便

  • torch.nn为我们提供了成熟的层及一系列激活函数,可以像搭积木一样来非常快速的创建复杂的模型

  • 在pytorch中搭建模型的流程:定义类(该类要继承nn.Module),在类中实现两个方法:1.初始化函数:实现在搭建网络过程中所需要实现的网络层结构。2.在forward函数中定义正向传播的过程。

  • 创建一个层时,权重和偏置都会随机初始化

  • 经卷积后的矩阵尺寸大小计算公式为:N=(W-F+2P)/S+1

  • torch种Tensor的通道顺序:[batch,channel,height,width]

  • 复习一下CNN:1.卷积核的 层数 和 输入通道数 一致,卷积核的 个数 和 输出通道数 一致(卷积过程:每层卷积核分别和对应层的feature map进行卷积,同一通道相加)。2.池化层的目的:缩减参数以提高计算速度,提高feature map的鲁棒性(实验得知)

  • 最大池化下采样后高度和宽度就变成了原来的一半(池化层只会改变高和宽,不会改变feature map的深度)

  • 在模型测试的时候可以自定义一个随机的输入x作为输入图像,如:

> input = torch.rand([32,3,32,32]) //batch=32 channel=3````

> model = LeNet() //实例化模型

> output = model(input)

  • super函数是继承父类的某个函数(理解为先执行父类的某个函数,再执行下面的语句)

  • 定义卷积层的函数为nn.Conv2d,参数顺序为:深度、卷积核的个数、卷积核的大小,步距默认为1

  • 定义下采样层的函数为nn.MaxPool2d,参数顺序为:卷积核大小,步距,padding

  • 定义全连接层的函数为nn.Linear,全连接层的输入是一个一维的向量,因此需要将得到的特征矩阵给展平成一维向量

  • 最后一个全连接层的输出是需要根据自己的分类类别进行更改的

  • x = F.relu(self.conv1(x)) 的意思是输入经过第一个卷积层再经过relu激活函数

  • view函数起到的作用是reshape,view的参数的是改变后的shape,-1代表第一个维度(batch)是自动推理的,第二个参数就是展平

  • 一个最简单的模型LeNet网络的model.py文件代码如下:

class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv1 = nn.Conv2d(3, 16, 5)
        self.pool1 = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(16, 32, 5)
        self.pool2 = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(32*5*5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):#x代表输入的数据,就是一个tensor
        x = F.relu(self.conv1(x))    # input(3, 32, 32) output(16, 28, 28)  #将输入经过第一个卷积层再经过relu激活函数
        x = self.pool1(x)            # output(16, 14, 14)
        x = F.relu(self.conv2(x))    # output(32, 10, 10)
        x = self.pool2(x)            # output(32, 5, 5)
        x = x.view(-1, 32*5*5)       # output(32*5*5) view函数起到的作用是reshape,view的参数的是改变后的shape,-1代表第一个维度(batch)是自动推理的,第二个参数就是展平
        x = F.relu(self.fc1(x))      # output(120)
        x = F.relu(self.fc2(x))      # output(84)
        x = self.fc3(x)              # output(10) 没有接softmax的原因:理论上确实需要,但实际训练网络计算交叉熵时在其内部已经实现了一个更加高效的sofymax方法,所以不需要添加了(内置了)
        return x
  • torchvision是pytorch的一个图形库,它服务于PyTorch深度学习框架的,主要用来构建计算机视觉模型。torchvision.transforms主要是用于常见的一些图形变换。以下是torchvision的构成:

> 1.torchvision.datasets: 一些加载数据的函数及常用的数据集接口;

> 2.torchvision.models: 包含常用的模型结构(含预训练模型),例如AlexNet、VGG、ResNet等;

> 3.torchvision.transforms: 常用的图片变换,例如裁剪、旋转等;

> 4.torchvision.utils: 其他的一些有用的方法。

> 其中的torchvision.transforms.Compose()类。这个类的主要作用是串联多个图片变换的操作,Compose里面的参数实际上就是个列表,而这个列表里面的元素就是你想要执行的transform操作

> transforms.Normalize:对数据按通道进行标准化,即先减均值,再除以标准差

> transforms.ToTensor:将PIL Image或者 ndarray 转换为tensor,并且归一化至[0-1]。注意:归一化至[0-1]是直接除以255,若自己的ndarray数据尺度有变化,则需要自行修改。

> transforms.Resize:缩放

> transforms.RandomResizedCrop:将给定图像随机裁剪为不同的大小和宽高比,然后缩放所裁剪得到的图像为制定的大小

> transforms.Grayscale:转为灰度图

> transforms.RandomHorizontalFlip:以给定的概率随机水平旋转给定的PIL的图像,默认为0.5;

> 关于:optimizer.zero_grad()---->将历史损失梯度清零 每计算一个batch就要调用一次optimizer.zero_grad(),如果不清除历史梯度,就会对计算的历史梯度进行累加通常,设置batchsize是根据硬件设备的条件,一般该值越大,训练的效果就会越好。但通常硬件设备由于内存等不足不可能用一个很大的batch去训练,就可以通过optimizer.zero_grad(),变相的实现一个很大的batch进行训练,也就是通过计算多个小的batch损失梯度,将其等效为一个大的batch的损失梯度进行反向传播

  • 通用的训练分类网络脚本 ,附带注释:

net = LeNet()
net.to(device)
loss_function = nn.CrossEntropyLoss()#定义损失函数,其内部已经内置了softmax
optimizer = optim.Adam(net.parameters(), lr=0.001)#定义优化器,参数一:所需要训练的参数(这里把网络中所有可训练的参数都进行训练);参数二:学习率
for epoch in range(5):
    running_loss = 0.0#用来累加在训练过程中的损失
    for step, data in enumerate(train_loader):#遍历训练集样本 enumerate() 函数返回数据和数据下标
        inputs, labels = data#将数据分离成输入的图像和其所对应的标签
        optimizer.zero_grad()
        outputs = net(inputs)#正向传播得到输出
        loss = loss_function(outputs, labels)#用定义的函数计算损失,参数为:网络预测的值,输入图片对应的真是标签
        loss.backward()#将loss进行反向传播
        optimizer.step()#进行参数的更新
        running_loss += loss.item()#每计算一个loss就将其追加到变量中
        if step % 500 == 499:#每隔五百步打印一次数据
            with torch.no_grad():#在接下来的计算过程中,都不会去计算每个节点的损失梯度,如果不用的话即使在测试集中也会计算损失梯度,会消耗更多的内存资源
                outputs = net(val_image)#验证集正向传播
                predict_y = torch.max(outputs, dim=1)[1]#寻找输出最大可能的标签类别 dim=1是在第一个维度寻找,因为第零个维度为batch
                #[1]代表只需要知道索引即可,不需要知道索引对应的最大值是多少
                #predict_y == val_label为将预测的标签类别和真实的标签类别进行比较,相同时返回true,不相同返回flase
                #sum()函数可以判断本次预测过程对了多少个样本
                #item()可以让tensor变量变成数值
                #最后再除以测试样本的数目,就得到了测试的准确率
                accuracy = (predict_y == val_label).sum().item() / val_label.size(0)
                print('[%d, %5d] train_loss: %.3f  test_accuracy: %.3f' %#打印一下训练过程中的信息:训练迭代轮数, 每一轮的多少步,五百步平均的训练损失(误差),测试样本的准确率
                      (epoch + 1, step + 1, running_loss / 500, accuracy))
                running_loss = 0.0
print('Finished Training')
save_path = './·····.pth'
torch.save(net.state_dict(), save_path)#将网络的所有参数进行保存
  • 通用的分类网络预测脚本,附带注释:

transform = transforms.Compose(
    [transforms.Resize((32, 32)),
     transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
net = LeNet()
net.load_state_dict(torch.load('Lenet.pth'))#载入保存的权重文件
im = Image.open('1.jpg')
im = transform(im)  # [C, H, W]                                                                                      
im = torch.unsqueeze(im, dim=0)  # [N, C, H, W] 转化成Tensor的格式,加一个维度
with torch.no_grad():#意思是不需要求损失梯度
    outputs = net(im)
    predict = torch.max(outputs, dim=1)[1].data.numpy()#找到输入图像对应的类别索引
print(classes[int(predict)])
  • nn.Sequential函数能够将一系列的层结构进行打包,组合成一个新的层结构,常用在模型初始化定义层结构中,相对于使用 self.conv1 = nn.Conv2d(3, 16, 5)语句会非常的高效方便。

  • nn.ReLU(inplace=True)语句中,inplace参数可以理解为是pytorch通过增加计算量,降低内存使用的一种方法,通过这个方法,可以在内存中载入更大的一个模型。

  • nn.Sequential结构示例:

class AlexNet(nn.Module):
    def __init__(self, num_classes=1000, init_weights=False):
        super(AlexNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 48, kernel_size=11, stride=4, padding=2),  # input[3, 224, 224]  output[48, 55, 55]
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),                  # output[48, 27, 27]
            nn.Conv2d(48, 128, kernel_size=5, padding=2),           # output[128, 27, 27]
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),                  # output[128, 13, 13]
            nn.Conv2d(128, 192, kernel_size=3, padding=1),          # output[192, 13, 13]
            nn.ReLU(inplace=True),
            nn.Conv2d(192, 192, kernel_size=3, padding=1),          # output[192, 13, 13]
            nn.ReLU(inplace=True),
            nn.Conv2d(192, 128, kernel_size=3, padding=1),          # output[128, 13, 13]
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),                  # output[128, 6, 6]
        )
        self.classifier = nn.Sequential(
            nn.Dropout(p=0.5),
            nn.Linear(128 * 6 * 6, 2048),
            nn.ReLU(inplace=True),
            nn.Dropout(p=0.5),
            nn.Linear(2048, 2048),
            nn.ReLU(inplace=True),
            nn.Linear(2048, num_classes),
        )
        if init_weights:
            self._initialize_weights()

    def forward(self, x):
        x = self.features(x)
        x = torch.flatten(x, start_dim=1)
        x = self.classifier(x)
        return x

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):#下面有介绍
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')#下面有介绍
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)
  • self.modules()函数会返回网络中所有的层结构, isinstance()函数可以判断数据的类型

  • nn.init.kaiming_normal_和 nn.init.normal_都是初始化变量函数,后者是正态分布的方法给参数进行赋值,其实这些语句并不需要,写出来只是为了让大家学习,在torch中,会自动对卷积和全连接层是自动使用凯明方法初始化权重。

  • 通过Dropout方法能够使全连接层的节点按一定比例进行失活,防止过拟合。其中的Dropout一般放在全连接层与全连接层之间,nn.Dropout(p=0.5)中的p代表随机失活的神经元的比例,默认是0.5。

  • nn.Linear(2048, 2048)定义全连接层的函数分别为输入神经元个数和输出神经元个数。

  • torch.flatten方法可以从自定义的维度开始展平变量

  • device = torch.device(“cuda:0” if torch.cuda.is_available() else “cpu”)语句: torch.device可以指定训练过程中所使用的设备,括号里面语法的意思是如果当前设备有可使用的GPU,那么就使用第一块GPU,如果没有则使用CPU。

  • 解释一下os.path.abspath(os.path.join(os.getcwd(), “… /…”)) :os.getcwd()是获取当前文件所在的目录,…/…为返回上上层目录,os.path.join是拼接两个路径

  • torchvision.datasets.ImageFolder是pytorch加载数据集的函数,其默认你的数据集已经按类分好了文件夹,同一个文件夹下是同一类的照片。第二个参数是数据预处理,常用的语句如下:

data_transform = {
    
    
    "train": transforms.Compose([transforms.RandomResizedCrop(224),
                                 transforms.RandomHorizontalFlip(),
                                 transforms.ToTensor(),
                                 transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),
    "val": transforms.Compose([transforms.Resize((224, 224)), 
                               transforms.ToTensor(),
                               transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}

data_root = os.path.abspath(os.path.join(os.getcwd(), "../.."))  
image_path = data_root + "/data_set/flower_data/"  
train_dataset = datasets.ImageFolder(root=image_path + "/train",
                                     transform=data_transform["train"])
train_num = len(train_dataset)#打印训练集有多少张图片
flower_list = train_dataset.class_to_idx#返回一个字典,及分类的名称和其对应的索引
  • cla_dict = dict((val, key) for key, val in flower_list.items()) :该语句可以将上面的字典反转,方便后续操作

  • json_str = json.dumps(cla_dict, indent=4) :该语句可将cla_dict字典编码成json格式

  • torch.utils.data.DataLoader 可以将刚加载好的数据集以及所设定的batchsize和shuffle就可以随机的从样本中获取一批一批的数据,其参数num_workers(采用的线程个数)在window下只能设置为0,在linux下可设置为非零值(开启的线程数),以便加快数据的生成速度,从而提高训练的速度。注:shuffle=True是先打乱所有数据,再取batch。

  • net.train()和net.eval()会管理dropout和BN层,在训练过程中调用net.train()就会启用,在验证中调用net.eval()就会关闭dropout和BN

  • 在一些Python的工程项目中,我们会看到函数参数中会有冒号,有的函数后面会跟着一个箭头,如下所示:def make_features(cfg: list):,值得注意的是,类型建议符并非强制规定和检查,也就是说即使传入的实际参数与建议参数不符,也不会报错,只是方便共同开发时程序员理解函数的输入与输出。

  • 可变参数传入,在列表或者元组前面加一个*可以把list或tuple的元素变成可变参数传入函数中,如这样:nn.Sequential(*layers),当然函数的定义肯定是如下形式:def 函数名(*a):。

  • 可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple,而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。和可变参数类似,也可以先组装出一个dict,也可以调用简化的写法:**extra。**extra表示把extra这个dict的所有key-value用关键字参数传入到函数的参数,函数参数将获得一个dict,如下所示:

net = vgg(model_name=model_name, num_classes=5, init_weights=True)
def vgg(model_name="vgg16", **kwargs):
	model = VGG(make_features(cfg), **kwargs)
	return model
  • torch.cat是将两个张量(tensor)按指定维度拼接在一起

  • nn.AdaptiveAvgPool2d是自适应平均池化函数,参数为输出特征矩阵的高和宽,无论输入特征矩阵的高和宽是什么样的大小,都能够指定的高和宽。

猜你喜欢

转载自blog.csdn.net/qq_42308217/article/details/113650317