Pytorch обучение управлению параметрами нейронной сети

Вот запись метода управления параметрами нейронной сети pytorch (доступ к параметрам, инициализация параметров, привязка параметров) для моего удобства и друзья, которым это нужно Изучайте и консультируйтесь.

Оглавление

1. Доступ к параметрам

1.1 Доступ к указанным параметрам указанного слоя

1.2 Доступ ко всем параметрам определенного уровня или всей сети

1.3 Доступ к указанным параметрам вложенных блоков

2. Инициализация параметров

2.1 Встроенная инициализация

2.2 Пользовательская инициализация

3. Привязка параметров

4. Все тестовые коды


1. Доступ к параметрам

1.1 Доступ к указанным параметрам указанного слоя

Сначала создайте многослойный перцептрон.

import torch
from torch import nn

net = nn.Sequential(nn.Linear(2, 4), nn.ReLU(), nn.Linear(4, 1))
X = torch.rand(size=(2, 2))

При определении модели через nn.Sequential мы можем получить доступ к любому слою модели через индекс. Как будто модель представляет собой список и параметры каждого слоя находятся в его свойствах. Как показано ниже, мы можем проверить параметры любого полносвязного слоя.

# 1 查看网络第一层(即第一个全连接层)的参数
print(net[0].state_dict())
# 2 查看网络第三层(即第二个全连接层)偏置参数的类型
print(type(net[2].bias))
# 3 查看网络第三层(即第二个全连接层)偏置参数
print(net[2].bias)
# 4 查看网络第三层(即第二个全连接层)偏置参数的值
print(net[2].bias.data)
# 5 查看网络第一层(即第一个全连接层)权重参数
print(net[0].weight)
# 6 查看网络第二层
print(net[1])

Результаты приведены ниже:

# 1
OrderedDict([('weight', tensor([[ 0.0854,  0.1861],
        [ 0.5421,  0.2435],
        [ 0.5745,  0.2469],
        [ 0.4120, -0.4345]])), ('bias', tensor([ 0.3356,  0.4215,  0.2181, -0.2548]))])
# 2
<class 'torch.nn.parameter.Parameter'>
# 3
Parameter containing:
tensor([-0.1606], requires_grad=True)
# 4
tensor([-0.1606])
# 5
Parameter containing:
tensor([[-0.4710,  0.0820],
        [-0.5563,  0.0728],
        [ 0.1691,  0.2211],
        [ 0.4279, -0.5597]], requires_grad=True)
# 6
ReLU()

Как видно, каждый параметр представлен как экземпляр класса параметра.Чтобы выполнить любую операцию с параметром, сначала необходимо получить доступ к базовому значению. Количество сетевых слоев начинается с 0, то есть net[0] представляет первый уровень сети, а функция активации также является слоем в сети.

Смещение доступа использует базовый атрибут, а вес доступа использует атрибут веса. Параметры — это составные объекты, содержащие значения, градиенты и дополнительную информацию. Если вы хотите получить только значение параметра, добавьте атрибут данных после основы или веса. Помимо значений, у нас также есть доступ к градиентам каждого параметра.

print(net[2].weight.grad == None)
# 结果为 True
# 原因:由于还没有调用反向传播,所以参数的梯度处于初始状态

1.2 Доступ ко всем параметрам определенного уровня или всей сети

Когда нам нужно выполнить операцию со всеми параметрами, доступ к ним по одному может быть затруднительным, и в этот момент мы можем извлечь параметры каждого подблока, рекурсивно просматривая все дерево.

# 1 访问第一层的所有参数
print(*[(name, param.shape) for name, param in net[0].named_parameters()])
# 2 访问网络所有层的全部参数
print(*[(name, param.shape) for name, param in net.named_parameters()])

Результат следующий:

# 1
('weight', torch.Size([4, 2])) ('bias', torch.Size([4]))
# 2
('0.weight', torch.Size([4, 2])) ('0.bias', torch.Size([4])) ('2.weight', torch.Size([1, 4])) ('2.bias', torch.Size([1]))

Примечание. Функция активации не имеет параметров, поэтому все параметры печатной сети содержат только параметры двух полностью связанных слоев.

Кроме того, мы можем получить доступ к параметрам сети с помощью следующих методов.

# 访问第网络第三层的偏置参数的值
print(net.state_dict()['2.bias'].data)

Результат следующий:

tensor([-0.1089])

 Если вы не используете nn.Sequential для определения модели, а сами определяете класс для реализации сети, то нельзя использовать индекс для доступа к параметрам указанного слоя . Как показано ниже:

class mlp(nn.Module):
    def __init__(self, input_size, output_size):
        super().__init__()
        self.linear1 = nn.Linear(input_size, 4)  # 全连接层
        self.relu = nn.ReLU()
        self.linear2 = nn.Linear(4, output_size)  # 全连接层

    def forward(self, x):
        x = self.linear1(x)
        return self.relu(self.linear2(x))

mlp_net= mlp(2, 1)
X = torch.rand(size=(2, 2))
# 如果使用索引访问会报错
print(mlp_net[0].state_dict())

В это время будут выведены следующие результаты:

Traceback (most recent call last):
  File "E:/SoftwareLearning/Python/Code/d2l-Train/ParameterManagement.py", line 46, in <module>
    print(mlp_net[0].state_dict())
TypeError: 'mlp' object is not subscriptable

Однако доступ к нему можно получить через:

# linear2为定义网络模型时自己起的某一全连接层的名称
print(mlp_net.state_dict()['linear2.bias'].data)

Вывод следующий:

tensor([0.3709])

1.3 Доступ к указанным параметрам вложенных блоков

Начните с построения сети вложенных блоков.

def block1():
    return nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
                         nn.Linear(8, 4), nn.ReLU())


def block2():
    net = nn.Sequential()
    for i in range(4):
        # 在这里嵌套
        net.add_module(f'block {i}', block1())
    return net

# 实例化
rgnet = nn.Sequential(block2(), nn.Linear(4, 1))
# 查看网络结构
print(rgnet)

Вывод следующий:

Sequential(
  (0): Sequential(
    (block 0): Sequential(
      (0): Linear(in_features=4, out_features=8, bias=True)
      (1): ReLU()
      (2): Linear(in_features=8, out_features=4, bias=True)
      (3): ReLU()
    )
    (block 1): Sequential(
      (0): Linear(in_features=4, out_features=8, bias=True)
      (1): ReLU()
      (2): Linear(in_features=8, out_features=4, bias=True)
      (3): ReLU()
    )
    (block 2): Sequential(
      (0): Linear(in_features=4, out_features=8, bias=True)
      (1): ReLU()
      (2): Linear(in_features=8, out_features=4, bias=True)
      (3): ReLU()
    )
    (block 3): Sequential(
      (0): Linear(in_features=4, out_features=8, bias=True)
      (1): ReLU()
      (2): Linear(in_features=8, out_features=4, bias=True)
      (3): ReLU()
    )
  )
  (1): Linear(in_features=4, out_features=1, bias=True)
)

Поскольку они иерархически вложены, мы также можем получить к ним доступ так же, как при индексировании через вложенные списки. Затем мы получаем доступ к весовым членам первого уровня второго подблока в первом основном блоке.

print(rgnet[0][1][0].weight.data)

Вывод следующий:

tensor([[ 0.3175,  0.0233,  0.3233, -0.0627],
        [-0.0835, -0.3371, -0.4527,  0.0141],
        [ 0.1070,  0.3952,  0.4051,  0.3921],
        [ 0.1958, -0.3643,  0.4481, -0.3448],
        [ 0.0446, -0.0256,  0.1490,  0.4568],
        [-0.1352, -0.2099, -0.1225, -0.0413],
        [ 0.3027,  0.2114, -0.4063, -0.0288],
        [-0.4594,  0.0076, -0.2671,  0.2669]])

2. Инициализация параметров

Инициализация очень важна для нейронных сетей. Хорошая инициализация может помочь модели быстро сходиться и имеет решающее значение для поддержания числовой стабильности сети. По умолчанию PyTorch инициализирует матрицы весов и смещений равномерно на основе диапазона, рассчитанного на основе входных и выходных измерений. Модуль nn.init PyTorch предоставляет множество предустановленных методов инициализации.

2.1 Встроенная инициализация

Сначала вызовите встроенный инициализатор, чтобы инициализировать параметры сети. Следующий код инициализирует все весовые параметры гауссовскими случайными величинами со стандартным отклонением 0,01 и устанавливает параметр смещения равным 0.

# 将所有权重参数初始化为标准差为0.01的高斯随机变量, 且将偏置参数设置为0
def init_normal(m):
    if type(m) == nn.Linear:
        # 将权重参数初始化为标准差为0.01的高斯随机变量
        nn.init.normal_(m.weight, mean=0, std=0.01)
        # 将偏置参数初始化为0
        nn.init.zeros_(m.bias)

net.apply(init_normal)
print(net[0].weight.data[0], '\n', net[0].bias.data[0])

Вывод следующий:

tensor([-0.0142, -0.0054]) 
tensor(0.)

Мы также можем инициализировать все параметры заданной константой, например 1.

# 将所有参数初始化为给定的常数
def init_constant(m):
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight, 1)
        nn.init.zeros_(m.bias)

net.apply(init_constant)
print(net[0].weight.data[0], net[0].bias.data[0])

Вывод следующий:

tensor([1., 1.]) 
tensor(0.)

Мы также можем применять разные методы инициализации к разным слоям. Например, ниже мы используем метод инициализации Ксавьера для инициализации первого слоя нейронной сети, а затем инициализируем третий уровень нейронной сети постоянным значением 42.

def xavier(m):
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight)


def init_42(m):
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight, 42)


# 使用Xavier初始化方法初始化第一个神经网络层, 然后将第三个神经网络层初始化为常量值42
net[0].apply(xavier)
net[2].apply(init_42)
print(net[0].weight.data[0])
print(net[2].weight.data)

Вывод следующий:

tensor([ 0.9265, -0.1521])
tensor([[42., 42., 42., 42.]])

2.2 Пользовательская инициализация

Иногда платформа глубокого обучения не предоставляет нужный нам метод инициализации.В этом случае нам необходимо настроить метод инициализации для инициализации параметров.

# 自定义初始化
def my_init(m):
    if type(m) == nn.Linear:
        print("Init", *[(name, param.shape)
                        for name, param in m.named_parameters()][0])
        nn.init.uniform_(m.weight, -10, 10)  # 均匀分布
        # 如果绝对值大于5则参数不变,如果绝对值小于5则将参数置0
        m.weight.data *= m.weight.data.abs() >= 5


net.apply(my_init)
print(net[0].weight[:2])

Вывод следующий:

Init weight torch.Size([4, 2])
Init weight torch.Size([1, 4])
tensor([[ 6.6987, -5.3545],
        [ 6.6684, -6.3039]], grad_fn=<SliceBackward0>)

Вы также можете напрямую задать параметры указанного слоя по мере необходимости.

net[0].weight.data[:] += 1
net[0].weight.data[0, 0] = 42
print(net[0].weight.data[0])

Вывод следующий:

Init weight torch.Size([4, 2])
Init weight torch.Size([1, 4])
tensor([42.0000,  8.5777])

3. Привязка параметров

Иногда нам нужно разделить параметры между несколькими слоями. В этом случае мы можем определить полностью связанный слой, а затем использовать его параметры для установки параметров другого слоя для достижения совместного использования параметров.

shared = nn.Linear(8, 8)
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
                    shared, nn.ReLU(),
                    shared, nn.ReLU(),
                    nn.Linear(8, 1))

print(net(X))
# 检查参数是否相同
print(net[2].weight.data[0] == net[4].weight.data[0])
net[2].weight.data[0, 0] = 100
# 确保它们实际上是同一个对象,而不只是有相同的值
print(net[2].weight.data[0] == net[4].weight.data[0])

Вывод следующий:

tensor([[0.4362],
        [0.4562]], grad_fn=<AddmmBackward0>)
tensor([True, True, True, True])
tensor([True, True, True, True])

Этот пример показывает, что параметры третьего и пятого слоев нейронной сети связаны. Они не только равны по значению, но и представлены одним и тем же тензором. Следовательно, если мы изменим один из параметров, другой параметр тоже изменится. Вы можете подумать: что происходит с градиентом, когда параметры связаны? Ответ заключается в том, что, поскольку параметры модели содержат градиенты, градиенты второго скрытого слоя (т. е. третьего слоя нейронной сети) и третьего скрытого слоя (т. е. пятого слоя нейронной сети) суммируются во время обратного распространения ошибки.

4. Все тестовые коды

Все тестовые коды следующие.

import torch
from torch import nn


class mlp(nn.Module):
    def __init__(self, input_size, output_size):
        super().__init__()
        self.linear1 = nn.Linear(input_size, 4)  # 全连接层
        self.relu = nn.ReLU()
        self.linear2 = nn.Linear(4, output_size)  # 全连接层

    def forward(self, x):
        x = self.linear1(x)
        return self.relu(self.linear2(x))


def block1():
    return nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
                         nn.Linear(8, 4), nn.ReLU())


def block2():
    net = nn.Sequential()
    for i in range(4):
        # 在这里嵌套
        net.add_module(f'block {i}', block1())
    return net


net = nn.Sequential(nn.Linear(2, 4), nn.ReLU(), nn.Linear(4, 1))
X = torch.rand(size=(2, 2))
print(net(X))
# 当通过Sequential类定义模型时,可以通过索引来访问模型的任意层。这就像模型是一个列表一样,每层的参数都在其属性中。
print(net[0].state_dict())
print(type(net[2].bias))
print(net[2].bias)
print(net[2].bias.data)
print(net[0].weight)
print(net[1])
print(net[2].weight.grad == None)

# 一次性访问所有参数
print(*[(name, param.shape) for name, param in net[0].named_parameters()])
print(*[(name, param.shape) for name, param in net.named_parameters()])
# 访问网络参数的另一种方式
print(net.state_dict()['2.bias'].data)

# 不使用nn.Sequential定义模型
mlp_net = mlp(2, 1)
X = torch.rand(size=(2, 2))
print(mlp_net(X))
# 不能使用索引访问某一层的参数,会报错
# print(mlp_net[0].state_dict())
print(mlp_net.state_dict()['linear2.bias'].data)

# 从嵌套块收集参数
rgnet = nn.Sequential(block2(), nn.Linear(4, 1))
print(rgnet)
# 访问第一个主要的块中、第二个子块的第一层的权重
print(rgnet[0][1][0].weight.data)


# 参数初始化
# 将所有权重参数初始化为标准差为0.01的高斯随机变量, 且将偏置参数设置为0
def init_normal(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, mean=0, std=0.01)
        nn.init.zeros_(m.bias)


# 将所有参数初始化为给定的常数
def init_constant(m):
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight, 1)
        nn.init.zeros_(m.bias)


net.apply(init_normal)
print(net[0].weight.data[0], '\n', net[0].bias.data[0])

net.apply(init_constant)
print(net[0].weight.data[0], '\n', net[0].bias.data[0])


def xavier(m):
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight)


def init_42(m):
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight, 42)


# 使用Xavier初始化方法初始化第一个神经网络层, 然后将第三个神经网络层初始化为常量值42
net[0].apply(xavier)
net[2].apply(init_42)
print(net[0].weight.data[0])
print(net[2].weight.data)


# 自定义初始化
def my_init(m):
    if type(m) == nn.Linear:
        print("Init", *[(name, param.shape)
                        for name, param in m.named_parameters()][0])
        nn.init.uniform_(m.weight, -10, 10)
        m.weight.data *= m.weight.data.abs() >= 5


net.apply(my_init)
print(net[0].weight[:2])

# 也可以直接设置参数
net[0].weight.data[:] += 1
net[0].weight.data[0, 0] = 42
print(net[0].weight.data[0])

# 参数绑定
# 有时我们希望在多个层间共享参数:我们可以定义一个稠密层,然后使用它的参数来设置另一个层的参数
# 我们需要给共享层一个名称,以便可以引用它的参数
shared = nn.Linear(4, 4)
net = nn.Sequential(nn.Linear(2, 4), nn.ReLU(),
                    shared, nn.ReLU(),
                    shared, nn.ReLU(),
                    nn.Linear(4, 1))
print(net(X))
# 检查参数是否相同
print(net[2].weight.data[0] == net[4].weight.data[0])
net[2].weight.data[0, 0] = 100
# 确保它们实际上是同一个对象,而不只是有相同的值
print(net[2].weight.data[0] == net[4].weight.data[0])

おすすめ

転載: blog.csdn.net/ting_qifengl/article/details/124959266