如果我们将一个可训练参数与不可训练参数结合起来,那么原始可训练参数是不是可训练?

Posted

技术标签:

【中文标题】如果我们将一个可训练参数与不可训练参数结合起来,那么原始可训练参数是不是可训练?【英文标题】:If we combine one trainable parameters with a non-trainable parameter, is the original trainable param trainable?如果我们将一个可训练参数与不可训练参数结合起来,那么原始可训练参数是否可训练? 【发布时间】:2018-10-13 03:37:33 【问题描述】:

我有两个网络,我只使用 pytorch 操作以某种奇特的方式组合它们的参数。我将结果存储在第三个网络中,其参数设置为non-trainable。然后我继续并通过这个新网络传递数据。新网络只是一个占位符:

placeholder_net.W = Op( not_trainable_net.W, trainable_net.W )

然后我传递数据:

output = placeholder_net(input)

我担心由于占位符网络的参数设置为non-trainable,它实际上不会训练它应该训练的变量。这会发生吗?或者将可训练参数与不可训练参数结合起来(然后将其设置在不可训练参数的位置)会产生什么结果?


目前的解决方案:

del net3.conv0.weight
net3.conv0.weight = net.conv0.weight + net2.conv0.weight

import torch
from torch import nn
import torch.optim as optim

import torchvision
import torchvision.transforms as transforms

from collections import OrderedDict

import copy

def dont_train(net):
    '''
    set training parameters to false.
    '''
    for param in net.parameters():
        param.requires_grad = False
    return net

def get_cifar10():
    transform = transforms.Compose(
        [transforms.ToTensor(),
         transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
    trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
    trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,shuffle=True, num_workers=2)
    classes = ('plane', 'car', 'bird', 'cat','deer', 'dog', 'frog', 'horse', 'ship', 'truck')
    return trainloader,classes



def combine_nets(net_train, net_no_train, net_place_holder):
        '''
            Combine nets in a way train net is trainable
        '''
        params_train = net_train.named_parameters()
        dict_params_place_holder = dict(net_place_holder.named_parameters())
        dict_params_no_train = dict(net_no_train.named_parameters())
        for name, param_train in params_train:
            if name in dict_params_place_holder:
                layer_name, param_name = name.split('.')
                param_no_train = dict_params_no_train[name]
                ## get place holder layer
                layer_place_holder = getattr(net_place_holder, layer_name)
                delattr(layer_place_holder, param_name)
                ## get new param
                W_new = param_train + param_no_train  # notice addition is just chosen for the sake of an example
                ## store param in placehoder net
                setattr(layer_place_holder, param_name, W_new)
        return net_place_holder

def combining_nets_lead_to_error():
    '''
    Intention is to only train the net with trainable params.
    Placeholder rnet is a dummy net, it doesn't actually do anything except hold the combination of params and its the
    net that does the forward pass on the data.
    '''
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    ''' create three musketeers '''
    net_train = nn.Sequential(OrderedDict([
          ('conv1', nn.Conv2d(1,20,5)),
          ('relu1', nn.ReLU()),
          ('conv2', nn.Conv2d(20,64,5)),
          ('relu2', nn.ReLU())
        ])).to(device)
    net_no_train = copy.deepcopy(net_train).to(device)
    net_place_holder = copy.deepcopy(net_train).to(device)
    ''' prepare train, hyperparams '''
    trainloader,classes = get_cifar10()
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(net_train.parameters(), lr=0.001, momentum=0.9)
    ''' train '''
    net_train.train()
    net_no_train.eval()
    net_place_holder.eval()
    for epoch in range(2):  # loop over the dataset multiple times
        running_loss = 0.0
        for i, (inputs, labels) in enumerate(trainloader, 0):
            optimizer.zero_grad() # zero the parameter gradients
            inputs, labels = inputs.to(device), labels.to(device)
            # combine nets
            net_place_holder = combine_nets(net_train,net_no_train,net_place_holder)
            #
            outputs = net_place_holder(inputs)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
            # print statistics
            running_loss += loss.item()
            if i % 2000 == 1999:  # print every 2000 mini-batches
                print('[%d, %5d] loss: %.3f' %
                      (epoch + 1, i + 1, running_loss / 2000))
                running_loss = 0.0
    ''' DONE '''
    print('Done \a')

if __name__ == '__main__':
    combining_nets_lead_to_error()

【问题讨论】:

相关:discuss.pytorch.org/t/… 相关:discuss.pytorch.org/t/… 也许有用,删除属性...discuss.pytorch.org/t/… 【参考方案1】:

我不确定这是不是你想知道的。

但是当我理解你的正确时 - 你想知道 non-trainabletrainable 变量的操作结果是否仍然 trainable ?

如果是这样,确实是这样,这里有一个例子:

>>> trainable = torch.ones(1, requires_grad=True)
>>> non_trainable = torch.ones(1, requires_grad=False)
>>> result = trainable + non_trainable
>>> result.requires_grad
True

也许您还会发现 torch.set_grad_enabled 很有用,这里提供了一些示例(0.4.0 版本的 PyTorch 迁移指南):

https://pytorch.org/2018/04/22/0_4_0-migration-guide.html

【讨论】:

我想知道的是,如果我用优化器类更新结果(通过交叉熵传递它之后),如果只有可训练的参数会改变。我想我可以尝试一下,然后在 optimizer.step() 之后检查可训练事物的值是否发生了变化 所以你想先通过可训练的网络输入数据,然后再通过不可训练的网络,你的问题是第一个网络是否会被正确训练? 没有。我想以任何我想要的方式将两个网络与 Op 结合起来(比如说求和,因为它是您建议的示例),然后我想通过这个新网络传递数据,但只针对可训练网络进行训练(WLOG)。从语义上讲,新网络只是一个占位符,它没有真正的意义,希望作为一种通过组合网络传递数据的方式(也许作为一种为可训练网络获取梯度的方式)。 其实我觉得这应该不是问题,但是我没试过。如果您对此表示怀疑,您可以尝试一个类似结构的简单虚拟示例 - 祝您好运!【参考方案2】:

首先,不要对任何网络使用 eval() 模式。将 requires_grad 标志设置为 false 以使参数对 only 第二个网络不可训练并训练占位符网络。

如果这不起作用,您可以尝试以下我喜欢的方法。

您可以使用单个网络并在非线性之前的每个可训练层之后使用一个不可训练层作为并行连接,而不是使用多个网络。

例如看这张图片:

将 requires_grad 标志设置为 false 以使参数不可训练。不要使用eval() 并训练网络。

在非线性之前组合层的输出很重要。初始化并行层的参数,并选择后操作,使其得到与参数组合时相同的结果。

【讨论】:

为什么在我的情况下对任何网络错误/不正确使用 eval?只是好奇。 dropout 或 batchNormalization 等模块需要在训练和评估期间表现不同。冻结层/网络并获得您想要的行为是不够的。这就像我们在非训练期间需要为特定模块使用的附加模式。 我只尝试了您的第一个选项,因为第二个选项对我来说更难做,因为我使用的网络已经过训练。所以我有两个“相互微调”的网络。我不知道它是否不起作用是因为代码有错误,还是因为我使用的方法可能不起作用。也许一个好主意是让我的新数据集大小为 1,看看即使使用这种有趣的训练方法,它是否至少可以记住一个数据点。 好的,我绝对认为第一个建议(至少在我的代码上)不起作用,因为我刚刚做了 W+0 并在 1 个示例上训练了 W(0 是不可训练的参数) cifar 10 和网络无法学习该 1 示例。肯定有什么问题…… 请注意,只有当我使用combine_nets 代码时,它才不起作用。当我直接在网上训练时,它当然会按预期记住单个示例。【参考方案3】:

下面的原始答案,我在这里解决您上传的添加代码。

在您的 combine_nets 函数中,您不必要地尝试删除和设置属性,而您可以像这样简单地复制所需的值:

def combine_nets(net_train, net_no_train, net_place_holder):
    '''
        Combine nets in a way train net is trainable
    '''
    params_train = net_no_train.named_parameters()
    dict_params_place_holder = dict(net_place_holder.named_parameters())
    dict_params_no_train = dict(net_train.named_parameters())
    for name, param_train in params_train:
        if name in dict_params_place_holder:
            param_no_train = dict_params_no_train[name]
            W_new = param_train + param_no_train 
            dict_params_no_train[name].data.copy_(W_new.data)
    return net_place_holder

由于您提供的代码中存在其他错误,如果不进行进一步更改,我无法使其运行,因此我在下面附上了我之前提供给您的代码的更新版本:

import torch
from torch import nn
from torch.autograd import Variable
import torch.optim as optim


# toy feed-forward net
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()

        self.fc1 = nn.Linear(10, 5)
        self.fc2 = nn.Linear(5, 5)
        self.fc3 = nn.Linear(5, 1)

    def forward(self, x):
        x = self.fc1(x)
        x = self.fc2(x)
        x = self.fc3(x)
        return x

def combine_nets(net_train, net_no_train, net_place_holder):
    '''
        Combine nets in a way train net is trainable
    '''
    params_train = net_no_train.named_parameters()
    dict_params_place_holder = dict(net_place_holder.named_parameters())
    dict_params_no_train = dict(net_train.named_parameters())
    for name, param_train in params_train:
        if name in dict_params_place_holder:
            param_no_train = dict_params_no_train[name]
            W_new = param_train + param_no_train
            dict_params_no_train[name].data.copy_(W_new.data)
return net_place_holder


# define random data
random_input1 = Variable(torch.randn(10,))
random_target1 = Variable(torch.randn(1,))
random_input2 = Variable(torch.rand(10,))
random_target2 = Variable(torch.rand(1,))
random_input3 = Variable(torch.randn(10,))
random_target3 = Variable(torch.randn(1,))

# define net
net1 = Net()
net_place_holder = Net()
net2 = Net()

# train the net1
criterion = nn.MSELoss()
optimizer = optim.SGD(net1.parameters(), lr=0.1)
for i in range(100):
    net1.zero_grad()
    output = net1(random_input1)
    loss = criterion(output, random_target1)
    loss.backward()
    optimizer.step()

# train the net2
criterion = nn.MSELoss()
optimizer = optim.SGD(net2.parameters(), lr=0.1)
for i in range(100):
    net2.zero_grad()
    output = net2(random_input2)
    loss = criterion(output, random_target2)
    loss.backward()
    optimizer.step()

# train the net2
criterion = nn.MSELoss()
optimizer = optim.SGD(net_place_holder.parameters(), lr=0.1)
for i in range(100):
    net_place_holder.zero_grad()
    output = net_place_holder(random_input3)
    loss = criterion(output, random_target3)
    loss.backward()
    optimizer.step()

print('#'*50)
print('Weights before combining')
print('')
print('net1 fc2 weight after train:')
print(net1.fc3.weight)
print('net2 fc2 weight after train:')
print(net2.fc3.weight)

combine_nets(net1, net2, net_place_holder)

print('#'*50)
print('')
print('Weights after combining')
print('net1 fc2 weight after train:')
print(net1.fc3.weight)
print('net2 fc2 weight after train:')
print(net2.fc3.weight)


# train the net
criterion = nn.MSELoss()
optimizer1 = optim.SGD(net1.parameters(), lr=0.1)
for i in range(100):
    net1.zero_grad()
    net2.zero_grad()
    output1 = net1(random_input3)
    output2 = net2(random_input3)
    loss1 = criterion(output1, random_target3)
    loss2 = criterion(output2, random_target3)
    loss = loss1 + loss2
    loss.backward()
    optimizer1.step()

print('#'*50)
print('Weights after further training')
print('')
print('net1 fc2 weight after freeze:')
print(net1.fc3.weight)
print('net2 fc2 weight after freeze:')
print(net2.fc3.weight)

【讨论】:

所以我们不需要删除像conv0.weight这样的属性?这就是我在 pytorch 论坛上提出的建议,但对我来说似乎很奇怪......我想我只需要确保在计算树中插入正确的变量,以便正确完成向后计算,对吗?我想到的是拥有 3 个网络,其中 2 个是我正在组合的真实网络,第 3 个只是作为占位符。占位符设置为eval(),未经训练的网络也设置为eval。但另一个没有设置为评估。然后占位符网络将保存正确完成前向计算的组合。 你的代码的问题是你错过了我的代码不起作用的关键部分,即组合网络。如果您尝试将一个网络的参数组合到另一个网络的参数......由于某种原因它不起作用。 我粘贴了我的代码,如果你想查看它,它会结合参数......虽然代码不起作用......它说 parsms 没有将“requires_grad”标志设置为真。 @CharlieParker,为了清楚起见,你想拥有三个网络 N1、N2、N3 并将它们组合成一个网络 N4,对于每一层 i:L1 * L3 + L2 * L3?并且您希望反向传播只影响 N1 的变量? 不完全是,但这应该足以解决我的问题。它更像是我结合了 2 个参数并希望通过 net1 进行反向传播

以上是关于如果我们将一个可训练参数与不可训练参数结合起来,那么原始可训练参数是不是可训练?的主要内容,如果未能解决你的问题,请参考以下文章

TensorFlow 存储与读取

神经网络训练的时候什么时候更新参数?

不可训练参数的定义是啥?

Pytorch模型保存与加载,并在加载的模型基础上继续训练

预训练中Word2vec,ELMO,GPT与BERT对比

机器学习 - 支持向量机