Можно ли расширить узлы модели замороженной нейронной сети по ширине?

#python #deep-learning #pytorch #torch

Вопрос:

Я хочу расширить число узлов модели замороженной нейронной сети по ширине в pytorch. Я хочу сделать что-то вроде того, что показано на рисунке ниже, где серые-это замороженные веса, а зеленые-это новые обучаемые веса.

введите описание изображения здесь .

У меня есть начальная модель, которая принимает 3 входа и возвращает один выход, эта модель также имеет два скрытых слоя с узлами h1=5 и h2=3 соответственно. Я создал модель в пирче и заморозил веса.

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

class Net(nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        self.fc1 = nn.Linear(3, 5)  
        self.fc2 = nn.Linear(5, 3)
        self.fc3 = nn.Linear(3, 1)
    def forward(self,x):
        x = self.fc1(x)
        x = self.fc2(x)
        x = self.fc3(x)
        return x       

print(Net())

model = Net()
X = torch.rand(5,3)
y = model(X)
print(y)

# Freeze layers
for param in model.parameters():
    param.requires_grad = False
 

Теперь я хочу расширить эту модель, добавив обучаемые узлы к h1=5 2, h2=3 1 и выходу= 1 1. Только вновь добавленные узлы должны быть обучаемыми, а все остальные веса должны быть заморожены, и эти замороженные веса должны иметь тот же вес, что и родительская модель. Можно ли это сделать в pytorch или в tensorflow?

Ответ №1:

Есть 2 вещи, которые нужно сделать

1. Разверните слои

Вам действительно следует использовать ModuleList или ModuleDict создавать слои, потому что это будет означать, что вы можете использовать цикл. Я знаю eval или setattr тоже работаю, но они, как правило, ломают что-то еще, поэтому я не хочу их использовать.

Я могу придумать 2 способа. Один из них заключается в прямой замене чем weight -то большим, а другой-в создании большего слоя и замене всего слоя.

 # Replace the weight with randomly generated tensor
fc1_newweight = torch.rand(7, 3)
fc1_newbias = torch.rand(7)
fc1_shape = model.fc1.weight.shape
fc1_newweight[:fc1_shape[0], :fc1_shape[1]] = model.fc1.weight.clone()
fc1_newbias[:fc1_shape[0]] = model.fc1.bias.clone()
model.fc1.weight = torch.nn.Parameter(fc1_newweight)
model.fc1.bias = torch.nn.Parameter(fc1_newbias)

# Replace the weight with the random generated weights from the new layer
fc2_shape = model.fc2.weight.shape
fc2 = nn.Linear(7, 4)
fc2_weight = fc2.state_dict()
fc2_weight['weight'][:fc2_shape[0], :fc2_shape[1]] = model.fc2.weight.clone()
fc2_weight['bias'][:fc2_shape[0]] = model.fc2.bias.clone()
fc2.load_state_dict(fc2_weight)
model.fc2.weight = torch.nn.Parameter(fc2_weight['weight'])
model.fc2.bias = torch.nn.Parameter(fc2_weight['bias'])

# Replace the whole layer
fc3_shape = model.fc3.weight.shape
fc3 = nn.Linear(4, 2)
fc3_weight = fc3.state_dict()
fc3_weight['weight'][:fc3_shape[0], :fc3_shape[1]] = model.fc3.weight.clone()
fc3_weight['bias'][:fc3_shape[0]] = model.fc3.bias.clone()
fc3.load_state_dict(fc3_weight)
model.fc3 = fc3
 

Я бы предпочел 2. или 3. вместо 1., потому что веса будут генерироваться с использованием nn.init.kaiming_uniform , а не просто однородных.

2. Выберите, что должно быть обучаемым

Это сложно, потому что вы не можете просто установить require_grad только некоторые элементы веса, потому что вы получите RuntimeError: you can only change requires_grad flags of leaf variables.

Но что-то вроде этого должно быть достаточно хорошей заменой. Опять же, использование ModuleList сделает код здесь тоже намного лучше.

 y = model(x)
loss = criterion(y, target)
loss.backward()

model.fc1.weight.grad[:fc1_shape[0], :fc1_shape[1]] = 0
model.fc1.bias.grad[:fc1_shape[0]] = 0
model.fc2.weight.grad[:fc2_shape[0], :fc2_shape[1]] = 0
model.fc2.bias.grad[:fc2_shape[0]] = 0
model.fc3.weight.grad[:fc3_shape[0], :fc3_shape[1]] = 0
model.fc3.bias.grad[:fc3_shape[0]] = 0

optimizer.step()