1. Classement de la taille
Le soi-disant élagage de modèle est en fait une technique de compression de modèle qui supprime les poids ou biais "inutiles" (poids/biais) du réseau de neurones. Quant à savoir quels paramètres sont "inutiles", c'est un domaine qui fait encore l'objet de recherches.
1.1, taille non structurée
Le Puning non structuré fait référence à l'élagage d'un seul élément d'un paramètre, tel qu'un seul poids dans une couche entièrement connectée, un seul élément de paramètre de noyau de convolution dans une couche convolutive ou la mise à l'échelle des flottants dans une couche personnalisée. Le fait est que les objets de poids élagués sont aléatoires et n'ont pas de structure spécifique, d'où le nom d'élagage non structuré .
1.2, taille structurée
Contrairement à l'élagage non structuré, l'élagage structuré élague toute la structure des paramètres. Par exemple, supprimer des pondérations pour des lignes ou des colonnes entières, ou supprimer des filtres entiers ( ) dans des couches convolutionnelles Filter
.
1.3, élagage local et global
L'élagage peut être effectué par couche (local) ou sur plusieurs/toutes les couches (global).
Deuxièmement, l'élagage de PyTorch
Les méthodes d'élagage par poids actuellement prises en charge par le framework PyTorch sont :
-
Aléatoire : Élaguez simplement les paramètres aléatoires.
-
Magnitude : Élaguez les paramètres avec le plus petit poids (par exemple leur norme L2)
Les deux méthodes ci-dessus sont simples à mettre en œuvre, faciles à calculer et peuvent être appliquées sans aucune donnée.
2.1, le principe de fonctionnement de la taille pytorch
La fonction d'élagage torch.nn.utils.prune
est implémentée dans la classe et le code se trouve dans le fichier torch/nn/utils/prune.py. La classe d'élagage principale est illustrée dans la figure ci-dessous. pytorch_pruning_api_file.png
Le principe de l'élagage repose sur la mise en place d'un masque (Mask) de tenseur (Tensor). Le masque est un tenseur booléen ayant la même forme que le tenseur. La valeur du masque est True, indiquant que le poids de la position correspondante doit être conservé, et la valeur du masque est False, indiquant que le poids de la la position correspondante peut être supprimée.
Pytorch <param>
copie les paramètres d'origine dans <param>_original
un paramètre nommé et crée un tampon pour stocker le masque d'élagage <param>_mask
. En même temps, il crée également une fonction de rappel forward_pre_hook au niveau du module (une fonction de rappel qui sera appelée avant que le modèle ne soit propagé en avant) pour appliquer le masque d'élagage aux poids d'origine.
La taille de pytorch api
et les tutoriels sont assez déroutants.Je vais personnellement faire le tableau suivant, dans l'espoir de résumer les api, les méthodes de taille et les classifications. pytorch_pruning_api
Le flux de travail de l'élagage du modèle dans pytorch est le suivant :
-
Sélectionnez la méthode d'élagage (ou sous-classe BasePruningMethod pour implémenter votre propre méthode d'élagage).
-
Spécifiez le module d'élagage et le nom du paramètre.
-
Définissez les paramètres de la méthode d'élagage, tels que le taux d'élagage.
2.2, taille locale
Il existe deux types d'élagage local dans le framework Pytorch : l'élagage non structuré et l'élagage structuré. Il convient de noter que l'élagage structuré ne prend en charge que le local mais pas le global.
2.2.1, taille locale non structurée
1. Le prototype de la fonction correspondant au Local Unstructured Pruning est le suivant :
def random_unstructured(module, name, amount)
1. Fonction : utilisée pour l'élagage non structuré des tenseurs des paramètres de poids . Cette méthode sélectionne au hasard certains poids ou connexions dans le tenseur pour l'élagage, et le taux d'élagage est spécifié par l'utilisateur. 2. Définition des paramètres de fonction :
-
module
(nn.Module) : la couche/le module réseau qui doit être élagué, comme nn.Conv2d() et nn.Linear(). -
name
(str) : Le nom du paramètre à élaguer, tel que "weight" ou "bias". -
amount
(entier ou flottant) : Spécifie la quantité à élaguer. S'il s'agit d'un décimal entre 0 et 1, il indique le taux d'élagage ; s'il s'agit d'un certificat, la quantité absolue du paramètre est directement coupée. Par exempleamount=0.2
, cela signifie que 20 % des éléments seront sélectionnés au hasard pour l'élagage.
3. Voici random_unstructured
un exemple d'utilisation de la fonction.
import torch
import torch.nn.utils.prune as prune
conv = torch.nn.Conv2d(1, 1, 4)
prune.random_unstructured(conv, name="weight", amount=0.5)
conv.weight
"""
tensor([[[[-0.1703, 0.0000, -0.0000, 0.0690],
[ 0.1411, 0.0000, -0.0000, -0.1031],
[-0.0527, 0.0000, 0.0640, 0.1666],
[ 0.0000, -0.0000, -0.0000, 0.2281]]]], grad_fn=<MulBackward0>)
"""
On peut voir que la moitié de la valeur de poids dans la couche de conversion de sortie est 0
.
2.2.2, taille structurée locale
L'élagage structuré local (Locall Structured Pruning) a deux fonctions, et les prototypes de fonction correspondants sont les suivants :
def random_structured(module, name, amount, dim)
def ln_structured(module, name, amount, n, dim, importance_scores=None)
1. Fonction fonction
Contrairement à l'élagage non structuré, qui supprime les poids de connexion, l'élagage structuré supprime les poids de canal entiers.
2. Définition des paramètres
Très similaire aux fonctions locales non structurées, la seule différence est que vous devez définir le paramètre dim (la fonction ln_structured a plus de n
paramètres).
n
Représente la norme de l'élagage dim
et représente la dimension de l'élagage.
Pour torche.nn.Linéaire :
-
dim = 0
: Supprimer un neurone. -
dim = 1
: Supprime toutes les connexions à une entrée.
Pour torche.nn.Conv2d :
-
dim = 0
(Canaux) : élagage des canaux / élagage des filtres -
dim = 1
(Neurons) : élagage du noyau du noyau à convolution bidimensionnelle, c'est-à-dire le noyau connecté au canal d'entrée
2.2.3, Exemple de code d'élagage structuré local
Avant d'écrire l'exemple de code, nous devons comprendre Conv2d
la relation entre les paramètres de la fonction, la forme du noyau de convolution, l'axe et le tenseur. Premièrement, le prototype de la fonction Conv2d est le suivant ;
class torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
Dans pytorch, le poids du noyau de convolution de la convolution conventionnelle shape
est ( C_out, C_in, kernel_height, kernel_width
), donc le poids de la couche de convolution dans le code shape
est [3, 2, 3, 3]
, et dim = 0 correspond à la forme [3, 2, 3, 3] 3
. Ici, nous assombrissons l'axe défini, puis l'axe correspondant au tenseur de poids changera après l'élagage naturel. faible
Après avoir compris les concepts clés précédents, les éléments suivants peuvent être utilisés dans la pratique, dim=0
comme indiqué ci-dessous.
conv = torch.nn.Conv2d(2, 3, 3)
norm1 = torch.norm(conv.weight, p=1, dim=[1,2,3])
print(norm1)
"""
tensor([1.9384, 2.3780, 1.8638], grad_fn=<NormBackward1>)
"""
prune.ln_structured(conv, name="weight", amount=1, n=2, dim=0)
print(conv.weight)
"""
tensor([[[[-0.0005, 0.1039, 0.0306],
[ 0.1233, 0.1517, 0.0628],
[ 0.1075, -0.0606, 0.1140]],
[[ 0.2263, -0.0199, 0.1275],
[-0.0455, -0.0639, -0.2153],
[ 0.1587, -0.1928, 0.1338]]],
[[[-0.2023, 0.0012, 0.1617],
[-0.1089, 0.2102, -0.2222],
[ 0.0645, -0.2333, -0.1211]],
[[ 0.2138, -0.0325, 0.0246],
[-0.0507, 0.1812, -0.2268],
[-0.1902, 0.0798, 0.0531]]],
[[[ 0.0000, -0.0000, -0.0000],
[ 0.0000, -0.0000, -0.0000],
[ 0.0000, -0.0000, 0.0000]],
[[ 0.0000, 0.0000, 0.0000],
[-0.0000, 0.0000, 0.0000],
[-0.0000, -0.0000, -0.0000]]]], grad_fn=<MulBackward0>)
"""
Il ressort clairement des résultats de l'exécution que le dernier tenseur de paramètre de canal des paramètres de couche convolutive a été supprimé (il s'agit d'un 0
tenseur).Voir la figure ci-dessous pour son explication.
dim_understand
dim = 1
Cas:
conv = torch.nn.Conv2d(2, 3, 3)
norm1 = torch.norm(conv.weight, p=1, dim=[0, 2,3])
print(norm1)
"""
tensor([3.1487, 3.9088], grad_fn=<NormBackward1>)
"""
prune.ln_structured(conv, name="weight", amount=1, n=2, dim=1)
print(conv.weight)
"""
tensor([[[[ 0.0000, -0.0000, -0.0000],
[-0.0000, 0.0000, 0.0000],
[-0.0000, 0.0000, -0.0000]],
[[-0.2140, 0.1038, 0.1660],
[ 0.1265, -0.1650, -0.2183],
[-0.0680, 0.2280, 0.2128]]],
[[[-0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, -0.0000],
[-0.0000, -0.0000, -0.0000]],
[[-0.2087, 0.1275, 0.0228],
[-0.1888, -0.1345, 0.1826],
[-0.2312, -0.1456, -0.1085]]],
[[[-0.0000, 0.0000, 0.0000],
[ 0.0000, -0.0000, 0.0000],
[ 0.0000, -0.0000, 0.0000]],
[[-0.0891, 0.0946, -0.1724],
[-0.2068, 0.0823, 0.0272],
[-0.2256, -0.1260, -0.0323]]]], grad_fn=<MulBackward0>)
"""
Évidemment, pour dim=1
la dimension de , la norme L2 du premier tenseur est plus petite, donc dans le tenseur de forme [2, 3, 3], le premier paramètre tenseur [3, 3] sera supprimé (c'est-à-dire que le tenseur est une matrice 0) .
2.3, élagage global non structuré
L'objet de l'élagage local mentionné ci-dessus est une couche réseau spécifique, tandis que l'élagage global consiste à traiter le modèle dans son ensemble pour supprimer les paramètres du rapport spécifié (nombre), et le résultat de l'élagage global entraînera le rapport creux de chaque couche du modèle doit être différente.
Le prototype de la fonction d'élagage non structurée globale est le suivant :
# v1.4.0 版本
def global_unstructured(parameters, pruning_method, **kwargs)
# v2.0.0-rc2版本
def global_unstructured(parameters, pruning_method, importance_scores=None, **kwargs):
1. Fonction fonction :
Sélectionnez au hasard une fraction de tous les paramètres à l'échelle mondiale (y compris les poids et les biais) pour l'élagage, quelle que soit la couche à laquelle ils appartiennent.
2. Définition des paramètres :
-
parameters
((Iterable of (module, name) tuples)) : taillez la liste des paramètres du modèle, et les éléments de la liste sont (module, name). -
pruning_method
(fonction): À l'heure actuelle, il semble que seul pruning_method=prune.L1Unstructured soit officiellement pris en charge, et il peut également s'agir d'une fonction de méthode d'élagage non structurée implémentée par soi-même. whaosoft aiot http://143ai.com -
importance_scores
: Indique le score d'importance de chaque paramètre, si Aucun, le score par défaut est utilisé. -
**kwargs
: Indique des paramètres supplémentaires passés à une méthode d'élagage spécifique. Par exemple,amount
spécifiez le nombre de branches à élaguer.
3. global_unstructured
L'exemple de code de la fonction est le suivant.
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
# 1 input image channel, 6 output channels, 3x3 square conv kernel
self.conv1 = nn.Conv2d(1, 6, 3)
self.conv2 = nn.Conv2d(6, 16, 3)
self.fc1 = nn.Linear(16 * 5 * 5, 120) # 5x5 image dimension
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
x = x.view(-1, int(x.nelement() / x.shape[0]))
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
model = LeNet().to(device=device)
model = LeNet()
parameters_to_prune = (
(model.conv1, 'weight'),
(model.conv2, 'weight'),
(model.fc1, 'weight'),
(model.fc2, 'weight'),
(model.fc3, 'weight'),
)
prune.global_unstructured(
parameters_to_prune,
pruning_method=prune.L1Unstructured,
amount=0.2,
)
# 计算卷积层和整个模型的稀疏度
# 其实调用的是 Tensor.numel 内内函数,返回输入张量中元素的总数
print(
"Sparsity in conv1.weight: {:.2f}%".format(
100. * float(torch.sum(model.conv1.weight == 0))
/ float(model.conv1.weight.nelement())
)
)
print(
"Global sparsity: {:.2f}%".format(
100. * float(
torch.sum(model.conv1.weight == 0)
+ torch.sum(model.conv2.weight == 0)
+ torch.sum(model.fc1.weight == 0)
+ torch.sum(model.fc2.weight == 0)
+ torch.sum(model.fc3.weight == 0)
)
/ float(
model.conv1.weight.nelement()
+ model.conv2.weight.nelement()
+ model.fc1.weight.nelement()
+ model.fc2.weight.nelement()
+ model.fc3.weight.nelement()
)
)
)
# 程序运行结果
"""
Sparsity in conv1.weight: 3.70%
Global sparsity: 20.00%
"""
Les résultats de l'exécution montrent que bien que la parcimonie globale (globale) du modèle soit 20%
, la parcimonie de chaque couche réseau n'est pas nécessairement de 20 %.
Trois, résumé
De plus, le framework pytorch fournit également quelques fonctions d'assistance :
-
torch.nn.utils.prune.is_pruned(module) : détermine si le module a été élagué.
-
torch.nn.utils.prune.remove(module, name) : utilisé pour supprimer l'opération d'élagage sur le paramètre spécifié dans le module spécifié , restaurant ainsi la forme et la valeur d'origine du paramètre.
Bien que PyTorch fournisse un élagage intégré API
, il prend également en charge certaines méthodes d'élagage non structurées et structurées, mais API
cela prête à confusion et la description du document correspondant n'est pas claire, donc je combinerai nni
les outils open source de Microsoft pour réaliser la fonction d'élagage du modèle plus tard.
Pour plus de pratique des méthodes de taille, veuillez vous référer à github
l'entrepôt : Modèle-Compression.