pytorch搭建卷积神经网络【第四课_自己搭建基于mnist训练网络】


前言

随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。


一、导入相关模块,使用torch.nn.functional

1、工程代码

#!/usr/bin/env python
# coding: utf-8

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms

2、torch.nn.functional和nn.model两者区别详见:

a、不同点

  • nn.Conv2是一个类,而F.conv2d是一个函数

  • 这两个功能并无区别,这两种实现方式同时存在的原因如下

  • 在建图过程中,往往有两种层,一种如全连接层 当中是有Variable ,另外一种是如Pooling Relu层,当中是没有Variable

  • 如果所有的层都用nn.funcitonal来定义,那么所有的Variable如weight bias等,都需要用户来手动定义,非常不方便

  • 如果所有的层都要nn来定义,那么简单的都需要来建类比较麻烦。

b、相同点

  • nn.Xxx和nn.functional.xxx的实际功能是相同的,即nn.Conv2d和nn.functional.conv2d都是进行卷积,nn.Dropout和nn.funtional.dropout都是进行dropout…
  • 运行效率也是近乎相同的。
  • nn.functional.xxx是函数接口,而nn.Xxx是nn.funcitonal.xxx的封装类,并且nn.xxx都继承于一个共同的祖先,nn.Module。这一点导致nn.Xxx除了具有nn.funcitonal.xxx功能之外,内部附带了nn.Module相关的属性和方法。如train eval load_state_dict

c、调用方式

  • nn.Xxx需要先实例化并传入参数,然后以函数调用的方式调用实例化的对象并传入数据。
  • nn.Xxx不需要自己定义和管理weight,而nn.funcitonal.xxx需要自定义weight,每次调用的时候需要手动传入weight。不利于代码复用。
  • 什么时候使用nn.functional.xxx,什么时候使用nn.Xxx
  • 依赖于解决问题的复杂度和个人的爱好。在nn.Xxx不能满足功能的时候,nn.funcitonal.xxx是更好的选择。

二、设置CPU生成随机数的种子,方便下次复现实验结果。

1、工程代码

# 设置CPU生成随机数的种子,方便下次复现实验结果。
torch.manual_seed(4242)

2、torch.manual_seed

a、相同随机种子

  • 由于每次初始化时,参数都是默认进行随机初始化的。然而每次运行py文件时,随机变量就会发生了变化。为了保证每次产生固定的随机数,可以通过torch.manual_seed()函数每次生成相同的随机数,这样有利于实验的比较和改进
torch.manual_seed(2)
x = torch.randn(2)
torch.manual_seed(2)
y = torch.randn(2)

#Out[16]: 
#x
#tensor([ 0.3923, -0.2236])
#y
#tensor([ 0.3923, -0.2236])

b、不同随机种子

  • 括号内的数字代表的是生成随机数的编号,编号相同则生成的随机数值相同
torch.manual_seed(2)
x = torch.randn(2)
torch.manual_seed(3)
y = torch.randn(2)

#Out[20]: 
#x
#tensor([ 0.3923, -0.2236])
#y
#tensor([0.8033, 0.1748])

三、数据集加载

1、工程代码

# In[9]:
train_loader = torch.utils.data.DataLoader(
    datasets.MNIST('../data/p1ch2/mnist', train=True, download=True,
                   transform=transforms.Compose([
                       transforms.ToTensor(),
                       transforms.Normalize((0.1307,), (0.3081,))
                   ])),
    batch_size=64, shuffle=True)

2、torch.utils.data.DataLoader函数参数解析

在这里插入图片描述

1.DataLoade参数1——dataset类
  • 训练模型一般都是先处理 数据的输入问题 和预处理问题。Pytorch提供了几个有用的工具:torch.utils.data.Dataset类 和 torch.utils.data.DataLoader类 。
  • 流程是先把原始数据转变成 torch.utils.data.Dataset类,随后再把得到的torch.utils.data.Dataset类当作一个参数传递给torch.utils.data.DataLoader类,得到一个数据加载器,这个数据加载器每次可以返回一个 Batch 的数据供模型训练使用。
  • 这一过程通常可以让我们把一张生图通过标准化、resize等操作转变成我们需要的 [B,C,H,W] 形状的 Tensor。
2. dataset类构建——直接用Pytorch的子模块 torchvision 准备好的数据

torchvision一般随着pytorch的安装也会安装到本地,直接导入就可以使用了。trochvision包含了 1.常用数据集;2.常用模型框架;3.数据转换方法。其中它提供的数据集就已经是一个Dataset类 了。torchvison.datasets就是专门提供各类常用数据集的模块。
以下是可供使用的数据集:

['CIFAR10', 'CIFAR100', 'Caltech101', 'Caltech256', 'CelebA']
['Cityscapes', 'CocoCaptions', 'CocoDetection', 'DatasetFolder', 'EMNIST']
['FakeData', 'FashionMNIST', 'Flickr30k', 'Flickr8k', 'HMDB51']
['ImageFolder', 'ImageNet', 'KMNIST', 'Kinetics400', 'LSUN']
['LSUNClass', 'MNIST', 'Omniglot', 'PhotoTour', 'Places365']
['QMNIST', 'SBDataset', 'SBU', 'SEMEION', 'STL10']
['SVHN', 'UCF101', 'USPS', 'VOCDetection', 'VOCSegmentation']
['VisionDataset']

下图是 MNIST类 的文档说明
在这里插入图片描述
以加载MNIST为例,运行以下代码:
from torchvision import datasets, transforms

# 导入训练集
trainDataset = datasets.MNIST(root=r'./data',
                              transform=transforms.ToTensor(),
                              train=True,
                              download=True)
# 导入测试集
testDataset = datasets.MNIST(root=r'data',
                             transform=transforms.ToTensor(),
                             train=False,
                             download=True)

看看我们得到了什么,本质上看,我们得到的 trainDataset 和 testDataset 都是 torch.utils.data.Dataset 的子类,它俩最重要的特性是有__getitem__和__len__方法,这意味着它俩可以用 value[index] 的方式访问内部元素(可以当作列表用)。

之所以提到这个是为 torch.utils.data.DataLoader 做准备。pytorch官方解释如下:
The most important argument of DataLoader constructor is dataset, which indicates a dataset object to load data from. PyTorch supports two different types of dataset:

  • map-style datasets
  • iterable-style datasets

我们得到的Dataset子类就是map-style datasets类型的。而iterable-style datasets类型最重要是包含了__iter__()方法,本质上是个迭代器,用next()访问内部元素的。

让我们实际输出这两个数据集一下看看我们得到了什么:

(1)

print("trainDataset 的类型:", type(trainDataset))
>>> trainDataset 的类型: <class 'torchvision.datasets.mnist.MNIST'>

(2)

print("trainDataset 的长度:", len(trainDataset))
>>> trainDataset 的长度: 60000

(3)

print("trainDataset[0] 的类型:", type(trainDataset[0]))
print("trainDataset[0] 的长度:", len(trainDataset[0]))
>>>  trainDataset[0] 的类型: <class 'tuple'>
     trainDataset[0] 的长度: 2

(4)

print("trainDataset[0][0] 的类型:", type(trainDataset[0][0]))
print("trainDataset[0][0] 的形状:", trainDataset[0][0].shape)
>>>  trainDataset[0][0] 的类型: <class 'torch.Tensor'>
     trainDataset[0][0] 的形状: torch.Size([1, 28, 28])

(5)

print("trainDataset[0][1] 的类型:", type(trainDataset[0][1]))
print("trainDataset[0][1] :", trainDataset[0][1])
>>>  trainDataset[0][1] 的类型: <class 'int'>
     trainDataset[0][1]5

总结一下就是我们的 trainDataset包含了60000个tuple,每个tuple第一项是一个形状为 [1,28,28] 的 Tensor,即样本值,第二项则是一个 int类型的标签值。

3. dataset类构建——自定义dataset类进行数据的读取以及初始化

前文我们是把 torchvision 准备好的MNIST数据集拿来用了,那如何用只有图片和标签的 row data 构建与前文类似的 Dataset 呢?

自己定义的dataset类需要继承: Dataset
需要实现必要的魔法方法:

  • __init__魔法方法里面进行读取数据文件
  • __getitem__魔法方法进行支持下标访问
  • __len__魔法方法返回自定义数据集的大小,方便后期遍历

注:自定义Dataset类只需要我们做到 1个父类继承,3个魔术方法。一般__init__负责加载全部原始数据,初始化之类的。__getitem__负责按索引取出某个数据,并对该数据做预处理。但是对于如何加载原始数据以及如何预处理数据完全是由自己定义的,包括我们用 dataset[index] 取出的数据的组织形式都是完全自行定义的。

本文下面的示例代码有两个关键的函数:load_data()、load_data_wrapper() 就体现出了这种自定义。原始数据是mnist.pkl.gz。load_data_wrapper() 通过一系列操作返回了三个列表,每个列表都是包含了数个元组,元组又是由样本值和标签值构成的。这意味着我把样本组织为了元组的形式,那么这个 自定义Dataset类 每次也是返回这样的元组供模型使用。所以我们只是受限于1个父类继承,3个魔术方法。其他部分完全可以有我们自己根据需要来定义。

示例如下:

class MyDataset(Dataset):

    def __init__(self, path, dataset_type="train", transform=None):
        self.path = path
        self.transform = transform
        self.dataset_type = dataset_type
        self.training_data, self.validation_data, self.test_data = self.load_data_wrapper()

    def __getitem__(self, index):
        if self.dataset_type == "test":
            img, target = self.test_data[index][0], self.test_data[index][1]
            if self.transform is not None:
                img = self.transform(img)
                target = self.transform(target)
        elif self.dataset_type == "valid":
            img, target = self.validation_data[index][0], self.validation_data[index][1]
            if self.transform is not None:
                img = self.transform(img)
                target = self.transform(target)
        else:
            img, target = self.training_data[index][0], self.training_data[index][1]
            if self.transform is not None:
                img = self.transform(img)
                target = self.transform(target)
        return img, target

    def __len__(self):
        if self.dataset_type == "test":
            return len(self.test_data)
        elif self.dataset_type == "valid":
            return len(self.validation_data)
        else:
            return len(self.training_data)

    def load_data(self):
        """Return the MNIST data as a tuple containing the training data,
        the validation data, and the test data.

        The ``training_data`` is returned as a tuple with two entries.
        The first entry contains the actual training images.  This is a
        numpy ndarray with 50,000 entries.  Each entry is, in turn, a
        numpy ndarray with 784 values, representing the 28 * 28 = 784
        pixels in a single MNIST image.

        The second entry in the ``training_data`` tuple is a numpy ndarray
        containing 50,000 entries.  Those entries are just the digit
        values (0...9) for the corresponding images contained in the first
        entry of the tuple.

        The ``validation_data`` and ``test_data`` are similar, except
        each contains only 10,000 images.

        This is a nice data format, but for use in neural networks it's
        helpful to modify the format of the ``training_data`` a little.
        That's done in the wrapper function ``load_data_wrapper()``, see
        below.
        """
        f = gzip.open(self.path, 'rb')
        training_data, validation_data, test_data = pickle.load(f, encoding='bytes')
        f.close()
        return training_data, validation_data, test_data

    def load_data_wrapper(self):
        """Return a tuple containing ``(training_data, validation_data,
        test_data)``. Based on ``load_data``, but the format is more
        convenient for use in our implementation of neural networks.

        In particular, ``training_data`` is a list containing 50,000
        2-tuples ``(x, y)``.  ``x`` is a 784-dimensional numpy.ndarray
        containing the input image.  ``y`` is a 10-dimensional
        numpy.ndarray representing the unit vector corresponding to the
        correct digit for ``x``.

        ``validation_data`` and ``test_data`` are lists containing 10,000
        2-tuples ``(x, y)``.  In each case, ``x`` is a 784-dimensional
        numpy.ndarry containing the input image, and ``y`` is the
        corresponding classification, i.e., the digit values (integers)
        corresponding to ``x``.

        Obviously, this means we're using slightly different formats for
        the training data and the validation / test data.  These formats
        turn out to be the most convenient for use in our neural network
        code."""
        tr_d, va_d, te_d = self.load_data()
        training_inputs = [np.reshape(x, (784, 1)) for x in tr_d[0]]
        training_results = [self.vectorized_result(y) for y in tr_d[1]]
        training_data = list(zip(training_inputs, training_results))
        validation_inputs = [np.reshape(x, (784, 1)) for x in va_d[0]]
        validation_data = list(zip(validation_inputs, va_d[1]))
        test_inputs = [np.reshape(x, (784, 1)) for x in te_d[0]]
        test_data = list(zip(test_inputs, te_d[1]))
        return training_data, validation_data, test_data

    @staticmethod
    def vectorized_result(j):
        """Return a 10-dimensional unit vector with a 1.0 in the jth
        position and zeroes elsewhere.  This is used to convert a digit
        (0...9) into a corresponding desired output from the neural
        network."""
        e = np.zeros((10, 1))
        e[j] = 1.0
        return e


mytrainDataset = MyDataset(path=r'mnist.pkl.gz', transform=transforms.ToTensor())
print("trainDataset 的类型:", type(mytrainDataset))
print("trainDataset 的长度:", len(mytrainDataset))
print("trainDataset[0] 的类型:", type(mytrainDataset[0]))
print("trainDataset[0] 的长度:", len(mytrainDataset[0]))
print("trainDataset[0][0] 的类型:", type(mytrainDataset[0][0]))
print("trainDataset[0][0] 的形状:", mytrainDataset[0][0].shape)
print("trainDataset[0][1] 的类型:", type(mytrainDataset[0][1]))
print("trainDataset[0][1] :", mytrainDataset[0][1].shape)

输出为:

trainDataset 的类型: <class '__main__.MyDataset'>
trainDataset 的长度: 50000
trainDataset[0] 的类型: <class 'tuple'>
trainDataset[0] 的长度: 2
trainDataset[0][0] 的类型: <class 'torch.Tensor'>
trainDataset[0][0] 的形状: torch.Size([1, 784, 1])
trainDataset[0][1] 的类型: <class 'torch.Tensor'>
trainDataset[0][1] : torch.Size([1, 10, 1])
3. 总结

用原始数据都造出来的 Dataset子类 其实就是一个静态的数据池,这个数据池支持我们用 索引 得到某个数据,想要让这个数据池流动起来,源源不断地输出 Batch 还需要下一个工具 DataLoader类 。所以我们把创建的 Dataset子类 当参数传入 即将构建的DataLoader类才是使用Dataset子类最终目。


四、搭建网络

1、工程代码

# In[10]:
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
        self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
        self.conv2_drop = nn.Dropout2d()
        self.fc1 = nn.Linear(320, 50)
        self.fc2 = nn.Linear(50, 10)

    def forward(self, x):
        x = F.relu(F.max_pool2d(self.conv1(x), 2))
        x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
        x = x.view(-1, 320)
        x = F.relu(self.fc1(x))
        x = F.dropout(x, training=self.training)
        x = self.fc2(x)
        return F.log_softmax(x, dim=1)

2、nn.functional()和nn.Module案例详解

nn.functional是一个很常用的模块,nn中的大多数layer在functional中都有一个与之对应的函数。nn.functional中的函数与nn.Module()的区别是:

  • nn.Module实现的层(layer)是一个特殊的类,都是由class Layer(nn.Module)定义,会自动提取可学习的参数
  • nn.functional中的函数更像是纯函数,由def functional(input定义

如:

# -*- coding: utf-8 -*-
#@Time    :2019/7/4 22:15
#@Author  :XiaoMa
import torch as t
import torch.nn as nn
from torch.autograd import Variable as V
 
input=V(t.randn(2,3))
model=nn.Linear(3,4)
output1=model(input)    #得到输出方式1
 
output2=nn.functional.linear(input,model.weight,model.bias) #得到输出方式2
 
print(output1==output2)
 
b=nn.functional.relu(input)
b2=nn.ReLU(input)
 
print(b==b2)
 
 
输出结果:
tensor([[1, 1, 1, 1],
        [1, 1, 1, 1]], dtype=torch.uint8)
注意——可学习参数:

如果模型有可学习的参数时,最好使用nn.Module;否则既可以使用nn.functional也可以使用nn.Module,二者在性能上没有太大差异,具体的使用方式取决于个人喜好。由于激活函数(ReLu、sigmoid、Tanh)、池化(MaxPool)等层没有可学习的参数,可以使用对应的functional函数,而卷积、全连接等有可学习参数的网络建议使用nn.Module。

注意——不可学习:

虽然dropout没有可学习参数,但建议还是使用nn.Dropout而不是nn.functional.dropout,因为dropout在训练和测试两个阶段的行为有所差别,使用nn.Module对象能够通过model.eval操作加以区分。
示例代码:

from torch.nn import functional as F
class Net(nn.Module):
    def __init(self):
    nn.Module(self).__init__()
    self.conv1=nn.Conv2d(3,6,5)
    slf.conv2=nn.Conv2d(6,16,5)
    self.fc1=nn.Linear(16*5*5,120)    #16--上一层输出为16通道,两个5为上一层的卷积核的宽和高
                                        #所以这一层的输入大小为:16*5*5
    self.fc2=nn.Linear(120,84)
    self.fc3=nn.Linear(84,10)
 
    def forward(self,x):    #对父类方法的重载
        x=F.pool(F.relu(self.conv1(x)),2)
        x=F.pool(F.relu(self.conv2(x)),2)
        x=x.view(-1,16*5*5)
        x=x.relu(self.fc1(x))
        x=x.relu(self.fc2(x))
        x=self.fc3(x)
 
        return x

代码说明:

在代码中,不具备可学习参数的层(激活层、池化层),将它们用函数代替,这样可以不用放置在构造函数__init__中。有可学习的模块,也可以用functional代替,只不过实现起来比较繁琐,需要手动定义参数parameter,如前面实现自定义的全连接层,就可以将weight和bias两个参数单独拿出来,在构造函数中初始化为parameter。

如这种:

class MyLinear(nn.Module):
    def __init__(self):
    super(MyLinear,self).__init__():
    self.weight=nn.Parameter(t.randn(3,4))
    self.bias=nn.Parameter(t.zeros(3))
    
    def forward(self):
        return F.linear(input,weight,bias)

五、实例化网络优化器设置

1、工程代码

# In[11]:
model = Net()

# In[12]:
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)

2、优化器详解

  • Optimizer

当数据、模型和损失函数确定,任务的数学模型就已经确定,接着就要选择一个合适
的优化器(Optimizer)对该模型进行优化。
本节将介绍 PyTorch 中优化器的方法(函数)和使用方法,下一小节将介绍 PyTorch 中
十种不同的优化器。

  • 优化器基类 Optimizer

PyTorch 中所有的优化器(如:optim.Adadelta、optim.SGD、optim.RMSprop 等)均是
Optimizer 的子类,Optimizer 中定义了一些常用的方法,有 zero_grad()、
step(closure)、state_dict()(遍历参数组)、load_state_dict(state_dict)和
add_param_group(param_group)

  • state_dict()(遍历参数组)
    pytorch 中的 state_dict 是一个简单的python的字典对象,将每一层与它的对应参数建立映射关系.(如model的每一层的weights及偏置等等)(注意,只有那些参数可以训练的layer才会被保存到模型的state_dict中,如卷积层,线性层等等)优化器对象Optimizer也有一个state_dict,它包含了优化器的状态以及被使用的超参数(如lr, momentum,weight_decay等)
import torch.nn as nn
import torch.nn.functional as F


# ----------------------------------- state_dict
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 1, 3)
        self.pool = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(1 * 3 * 3, 2)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = x.view(-1, 1 * 3 * 3)
        x = F.relu(self.fc1(x))
        return x

net = Net()

# 获取网络当前参数
net_state_dict = net.state_dict()

print('net_state_dict类型:', type(net_state_dict))
print('net_state_dict管理的参数: ', net_state_dict.keys())
for key, value in net_state_dict.items():
    print('参数名: ', key, '\t大小: ',  value.shape)
  • 参数组(param_groups)的概念

认识 Optimizer 的方法之前,需要了解一个概念,叫做参数组(param_groups)。在
finetune,某层定制学习率,某层学习率置零操作中,都会设计参数组的概念,因此首先
了解参数组的概念非常有必要。
optimizer 对参数的管理是基于组的概念,可以为每一组参数配置特定的
lr,momentum,weight_decay 等等。
参数组在 optimizer 中表现为一个 list(self.param_groups),其中每个元素是
dict,表示一个参数及其相应配置,在 dict 中包含’params’、‘weight_decay’、‘lr’ 、
'momentum’等字段。

案例源码:
github:https://github.com/2012Netsky/pytorch_cnn/blob/main/optimizer_1.ipynb
在这里插入图片描述
在这里插入图片描述


六、训练流程

1、工程代码

# In[ ]:
for epoch in range(10):
    for batch_idx, (data, target) in enumerate(train_loader):
        optimizer.zero_grad()
        output = model(data)
        loss = F.nll_loss(output, target)
        loss.backward()
        optimizer.step()
    print('Current loss', float(loss))

2、解释

  • zero_grad() 功能:将梯度清零。由于 PyTorch 不会自动清零梯度,所以在每一次更新前会进行此操作。
# coding: utf-8

import torch
import torch.optim as optim

# ----------------------------------- zero_grad

w1 = torch.randn(2, 2)
w1.requires_grad = True

w2 = torch.randn(2, 2)
w2.requires_grad = True

optimizer = optim.SGD([w1, w2], lr=0.001, momentum=0.9)

optimizer.param_groups[0]['params'][0].grad = torch.randn(2, 2)

print('参数w1的梯度:')
print(optimizer.param_groups[0]['params'][0].grad, '\n')  # 参数组,第一个参数(w1)的梯度

optimizer.zero_grad()
print('执行zero_grad()之后,参数w1的梯度:')
print(optimizer.param_groups[0]['params'][0].grad)  # 参数组,第一个参数(w1)的梯度

  • state_dict() 功能:获取模型当前的参数,以一个有序字典形式返回。这个有序字典中,key 是各层参数名,value 就是参数。
# coding: utf-8

import torch.nn as nn
import torch.nn.functional as F


# ----------------------------------- state_dict
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 1, 3)
        self.pool = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(1 * 3 * 3, 2)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = x.view(-1, 1 * 3 * 3)
        x = F.relu(self.fc1(x))
        return x


net = Net()

# 获取网络当前参数
net_state_dict = net.state_dict()

print('net_state_dict类型:', type(net_state_dict))
print('net_state_dict管理的参数: ', net_state_dict.keys())
for key, value in net_state_dict.items():
    print('参数名: ', key, '\t大小: ',  value.shape)

  • load_state_dict(state_dict) 功能:将 state_dict 中的参数加载到当前网络,常用于 finetune。
# coding: utf-8

import torch
import torch.nn as nn
import torch.nn.functional as F


# ----------------------------------- load_state_dict

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 1, 3)
        self.pool = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(1 * 3 * 3, 2)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = x.view(-1, 1 * 3 * 3)
        x = F.relu(self.fc1(x))
        return x

    def zero_param(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                torch.nn.init.constant_(m.weight.data, 0)
                if m.bias is not None:
                    m.bias.data.zero_()
            elif isinstance(m, nn.Linear):
                torch.nn.init.constant_(m.weight.data, 0)
                m.bias.data.zero_()
net = Net()

# 保存,并加载模型参数(仅保存模型参数)
torch.save(net.state_dict(), 'net_params.pkl')   # 假设训练好了一个模型net
pretrained_dict = torch.load('net_params.pkl')

# 将net的参数全部置0,方便对比
net.zero_param()
net_state_dict = net.state_dict()
print('conv1层的权值为:\n', net_state_dict['conv1.weight'], '\n')

# 通过load_state_dict 加载参数
net.load_state_dict(pretrained_dict)
print('加载之后,conv1层的权值变为:\n', net_state_dict['conv1.weight'])

  • add_param_group() 功能:给 optimizer 管理的参数组中增加一组参数,可为该组参数定制 lr, momentum,weight_decay 等,在 finetune 中常用。
# coding: utf-8

import torch
import torch.optim as optim

# ----------------------------------- add_param_group

w1 = torch.randn(2, 2)
w1.requires_grad = True

w2 = torch.randn(2, 2)
w2.requires_grad = True

w3 = torch.randn(2, 2)
w3.requires_grad = True

# 一个参数组
optimizer_1 = optim.SGD([w1, w2], lr=0.1)
print('当前参数组个数: ', len(optimizer_1.param_groups))
print(optimizer_1.param_groups, '\n')

# 增加一个参数组
print('增加一组参数 w3\n')
optimizer_1.add_param_group({'params': w3, 'lr': 0.001, 'momentum': 0.8})

print('当前参数组个数: ', len(optimizer_1.param_groups))
print(optimizer_1.param_groups, '\n')

print('可以看到,参数组是一个list,一个元素是一个dict,每个dict中都有lr, momentum等参数,这些都是可单独管理,单独设定,十分灵活!')

  • step(closure) 功能:执行一步权值更新, 其中可传入参数 closure(一个闭包)。如,当采用
    LBFGS优化方法时,需要多次计算,因此需要传入一个闭包去允许它们重新计算 loss
for input, target in dataset: 
	def closure(): 
		optimizer.zero_grad() 
		output = model(input) 
		loss = loss_fn(output, target) 
		loss.backward() 
		return loss 
	optimizer.step(closure)

a、损失函数

  • nn.NLLLoss

github:https://github.com/2012Netsky/pytorch_cnn/blob/main/nn.NLLLoss.ipynb
在这里插入图片描述

# coding: utf-8

import torch
import torch.nn as nn
import numpy as np

# ----------------------------------- log likelihood loss

# 各类别权重
weight = torch.from_numpy(np.array([0.6, 0.2, 0.2])).float()

# 生成网络输出 以及 目标输出
output = torch.from_numpy(np.array([[0.7, 0.2, 0.1], [0.4, 1.2, 0.4]])).float()  
output.requires_grad = True
target = torch.from_numpy(np.array([0, 0])).type(torch.LongTensor)


loss_f = nn.NLLLoss(weight=weight, size_average=True, reduce=False)
loss = loss_f(output, target)

print('\nloss: \n', loss)
print('\n第一个样本是0类,loss = -(0.6*0.7)={}'.format(loss[0]))
print('\n第二个样本是0类,loss = -(0.6*0.4)={}'.format(loss[1]))


# coding: utf-8

import torch
import torch.nn as nn
import numpy as np
import math

# ----------------------------------- CrossEntropy loss: base

loss_f = nn.CrossEntropyLoss(weight=None, size_average=True, reduce=False)
# 生成网络输出 以及 目标输出
output = torch.ones(2, 3, requires_grad=True) * 0.5      # 假设一个三分类任务,batchsize=2,假设每个神经元输出都为0.5
target = torch.from_numpy(np.array([0, 1])).type(torch.LongTensor)

loss = loss_f(output, target)

print('--------------------------------------------------- CrossEntropy loss: base')
print('loss: ', loss)
print('由于reduce=False,所以可以看到每一个样本的loss,输出为[1.0986, 1.0986]')


# 熟悉计算公式,手动计算第一个样本
output = output[0].detach().numpy()
output_1 = output[0]              # 第一个样本的输出值
target_1 = target[0].numpy()

# 第一项
x_class = output[target_1]
# 第二项
exp = math.e
sigma_exp_x = pow(exp, output[0]) + pow(exp, output[1]) + pow(exp, output[2])
log_sigma_exp_x = math.log(sigma_exp_x)
# 两项相加
loss_1 = -x_class + log_sigma_exp_x
print('---------------------------------------------------  手动计算')
print('第一个样本的loss:', loss_1)


# ----------------------------------- CrossEntropy loss: weight

weight = torch.from_numpy(np.array([0.6, 0.2, 0.2])).float()
loss_f = nn.CrossEntropyLoss(weight=weight, size_average=True, reduce=False)
output = torch.ones(2, 3, requires_grad=True) * 0.5  # 假设一个三分类任务,batchsize为2个,假设每个神经元输出都为0.5
target = torch.from_numpy(np.array([0, 1])).type(torch.LongTensor)
loss = loss_f(output, target)
print('\n\n--------------------------------------------------- CrossEntropy loss: weight')
print('loss: ', loss)  #
print('原始loss值为1.0986, 第一个样本是第0类,weight=0.6,所以输出为1.0986*0.6 =', 1.0986*0.6)

# ----------------------------------- CrossEntropy loss: ignore_index

loss_f_1 = nn.CrossEntropyLoss(weight=None, size_average=False, reduce=False, ignore_index=1)
loss_f_2 = nn.CrossEntropyLoss(weight=None, size_average=False, reduce=False, ignore_index=2)

output = torch.ones(3, 3, requires_grad=True) * 0.5  # 假设一个三分类任务,batchsize为2个,假设每个神经元输出都为0.5
target = torch.from_numpy(np.array([0, 1, 2])).type(torch.LongTensor)

loss_1 = loss_f_1(output, target)
loss_2 = loss_f_2(output, target)

print('\n\n--------------------------------------------------- CrossEntropy loss: ignore_index')
print('ignore_index = 1: ', loss_1)     # 类别为1的样本的loss为0
print('ignore_index = 2: ', loss_2)     # 类别为2的样本的loss为0


七、权重从模型保存到本地

1、工程代码

# In[8]:
torch.save(model.state_dict(), '../data/p1ch2/mnist/mnist.pth')

2、案例


# coding: utf-8

import torch.nn as nn
import torch.nn.functional as F


# ----------------------------------- state_dict
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 1, 3)
        self.pool = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(1 * 3 * 3, 2)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = x.view(-1, 1 * 3 * 3)
        x = F.relu(self.fc1(x))
        return x


net = Net()

# 获取网络当前参数
net_state_dict = net.state_dict()

print('net_state_dict类型:', type(net_state_dict))
print('net_state_dict管理的参数: ', net_state_dict.keys())
for key, value in net_state_dict.items():
    print('参数名: ', key, '\t大小: ',  value.shape)


八、权重从本地保存到模型

1、工程代码

# In[9]:
pretrained_model = Net()
pretrained_model.load_state_dict(torch.load('../data/p1ch2/mnist/mnist.pth'))

2、案例




总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

使用pytorch搭建卷积神经网络识别手写数字的命令如下: ```python import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms # 定义数据预处理 transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) # 加载数据集 train_set = datasets.MNIST('./data', train=True, download=True, transform=transform) test_set = datasets.MNIST('./data', train=False, download=True, transform=transform) # 定义模型 class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 32, kernel_size=3) self.conv2 = nn.Conv2d(32, 64, kernel_size=3) self.fc1 = nn.Linear(64 * 5 * 5, 128) self.fc2 = nn.Linear(128, 10) def forward(self, x): x = nn.functional.relu(self.conv1(x)) x = nn.functional.relu(self.conv2(x)) x = nn.functional.max_pool2d(x, 2) x = x.view(-1, 64 * 5 * 5) x = nn.functional.relu(self.fc1(x)) x = self.fc2(x) return nn.functional.log_softmax(x, dim=1) # 训练模型 model = Net() optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5) loss_fn = nn.NLLLoss() def train(epoch): model.train() for batch_idx, (data, target) in enumerate(train_set): optimizer.zero_grad() output = model(data.unsqueeze(0)) loss = loss_fn(output, torch.tensor([target])) loss.backward() optimizer.step() if batch_idx % 1000 == 0: print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format( epoch, batch_idx * len(data), len(train_set.dataset), 100. * batch_idx / len(train_set), loss.item())) for epoch in range(1, 11): train(epoch) # 测试模型 model.eval() test_loss = 0 correct = 0 with torch.no_grad(): for data, target in test_set: output = model(data.unsqueeze(0)) test_loss += loss_fn(output, torch.tensor([target]), reduction='sum').item() pred = output.argmax(dim=1, keepdim=True) correct += pred.eq(torch.tensor([target]).view_as(pred)).sum().item() test_loss /= len(test_set.dataset) print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format( test_loss, correct, len(test_set.dataset), 100. * correct / len(test_set.dataset))) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

【网络星空】

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值