Learning both Weights and Connections for Efficient Neural Networks 論文pytorch復現

Learning both Weights and Connections for Efficient Neural Networks 論文pytorch復現

https://img-blog.csdnimg.cn/20200611193652332.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0NDQ1Mzg4,size_16,color_FFFFFF,t_70
這是論文中主要的步驟,因此我們復現的時候也主要是利用這個思想。
代碼編寫需要兩個主要部分,首先原來神經網絡的訓練,然後就是神經網絡的裁剪。我這次實驗主要是使用論文中說的Lenet-300-100網絡來進行測試。

網絡的定義以及初次訓練

##############先導入需要的包###############################
import torch
import torch.nn as nn
from torchvision import datasets, transforms
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
import torch.nn.functional as F
##############導入手寫數字體數據###############################
batch_size  = 128
mnist_data = datasets.MNIST('./mnist_data', train=True, download=True,
                   transform=transforms.Compose([
                       transforms.ToTensor(),
                   ]))
dataloader = torch.utils.data.DataLoader(dataset=mnist_data,
                                         batch_size=batch_size,
                                         shuffle=True,num_workers = 20)
##############顯示手寫數字體數據###############################
for i in range(10):
    plt.figure()
    plt.imshow(next(iter(dataloader))[0][0][0],cmap="gray")
##############定義網絡Lenet-300-100網絡###############################
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(28*28, 300)
        self.fc2 = nn.Linear(300, 100)
        self.fc3 = nn.Linear(100, 10)
    def forward(self, x):
        x = x.view(-1,28*28)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        
        return F.log_softmax(x, dim=1)
def train(model, device, train_loader, optimizer, epoch, log_interval=100):
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        data, target = data.to(device), target.to(device)
        optimizer.zero_grad()
        output = model(data)
        
        loss = F.nll_loss(output, target)
       
        loss.backward()
        optimizer.step()
        if batch_idx % log_interval == 0:
            print("Train Epoch: {} [{}/{} ({:0f}%)]\tLoss: {:.6f}".format(
                epoch, batch_idx * len(data), len(train_loader.dataset), 
                100. * batch_idx / len(train_loader), loss.item()
            ))
def test(model, device, test_loader):
    model.eval()
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            data, target = data.to(device), target.to(device)
            output = model(data)
            test_loss += F.nll_loss(output, target, reduction='sum').item() # sum up batch loss
            pred = output.argmax(dim=1, keepdim=True) # get the index of the max log-probability
            correct += pred.eq(target.view_as(pred)).sum().item()
    test_loss /= len(test_loader.dataset)

    print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
        test_loss, correct, len(test_loader.dataset),
        100. * correct / len(test_loader.dataset)))
##############初次訓練###############################
lr = 0.01
momentum = 0.25
torch.manual_seed(53113)
batch_size = test_batch_size = 128
kwargs = {'num_workers': 40, 'pin_memory': True} if use_cuda else {}
train_loader = torch.utils.data.DataLoader(
    datasets.MNIST('./mnist_data', train=True, download=True,
                   transform=transforms.Compose([
                       transforms.ToTensor(),
                       transforms.Normalize((0.1307,), (0.3081,))
                   ])),
    batch_size=batch_size, shuffle=True, **kwargs)
test_loader = torch.utils.data.DataLoader(
    datasets.MNIST('./mnist_data', train=False, transform=transforms.Compose([
                       transforms.ToTensor(),
                       transforms.Normalize((0.1307,), (0.3081,))
                   ])),
    batch_size=test_batch_size, shuffle=True, **kwargs)

model = Net().to(device)
optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)
epochs = 20
for epoch in range(1, epochs + 1):
    train(model, device, train_loader, optimizer, epoch)
    test(model, device, test_loader)

訓練好網絡信息的顯示

#####################獲取一張圖像信息#################################
image = next(iter(dataloader))[0][0][0]
output = model.forward(image)
pred = output.argmax(dim=1, keepdim=True)
print(pred)  #預測值
#####################顯示這張圖像#################################
plt.figure()
plt.imshow(image,cmap="gray")
plt.show()
#####################圖像預處理#################################
image = image.reshape(-1,28*28)
image = image.data.numpy()
image_count = image.copy()
#####################計算圖像非零點個數#################################
image_count[image_count != 0] = 1
count = np.sum(image_count)
print(count)
#####################轉化到numpy對數據進行處理#################################
fc1 = model.fc1.weight.data.cpu().numpy()
fc2 = model.fc2.weight.data.cpu().numpy()
fc3 = model.fc3.weight.data.cpu().numpy()
hidden1 = image.dot(fc1.T) 
hidden1 = np.maximum(0, hidden1)   #激活
hidden2 = hidden1.dot(fc2.T)
hidden2 = np.maximum(0, hidden2)
hidden3 = hidden2.dot(fc3.T)
#####################第一層神經元激活的個數#################################
hidden1_count = hidden1.copy()
hidden1_count[hidden1_count > 0] = 1
count = np.sum(hidden1_count)
print(count)
#####################第二層神經元激活的個數#################################
hidden2_count = hidden2.copy()
hidden2_count[hidden2_count > 0] = 1
count = np.sum(hidden2_count)
print(count)
#####################輸出預測結果#################################
out = np.exp(hidden3)
out = out / np.sum(out)
print(np.argmax(out))
#####################第一層神經元參數信息的圖像深度顯示,從這裏可以觀測出權重的變化#################################
fc1_plt =  np.abs(fc1)
print("min",np.min(fc1_plt),"max",np.max(fc1_plt))
plt.figure(figsize=(50,50))
im = plt.imshow(fc1_plt, vmin = np.min(fc1_plt), vmax =  np.max(fc1_plt) ,cmap = 'seismic')
plt.show()

剪枝操作

##########################函數定義###########################################
def expand_model(model, layers=torch.Tensor()):
    for layer in model.children():
         layers = torch.cat((layers.view(-1), layer.weight.view(-1)))  #將所有的參數拼接在一起
    return layers

def calculate_threshold(model, rate):   #求取所有參數的閾值所在的數值大小
    empty = torch.Tensor()
    if torch.cuda.is_available():
        empty = empty.cuda()
    pre_abs = expand_model(model, empty)    #獲取所有的參數爲一行
    weights = torch.abs(pre_abs)            #求絕對值

    return np.percentile(weights.detach().cpu().numpy(), rate)
def prune(model, threshold):
    model.fc1.weight.data = torch.mul(torch.gt(torch.abs(model.fc1.weight.data), threshold), model.fc1.weight.data)
    model.fc2.weight.data = torch.mul(torch.gt(torch.abs(model.fc2.weight.data), threshold), model.fc2.weight.data)
    model.fc3.weight.data = torch.mul(torch.gt(torch.abs(model.fc3.weight.data), threshold), model.fc3.weight.data)
def retrain(model, device, train_loader, test_loader, epochs, lr, momentum):
    optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)
    for epoch in range(1, epochs + 1):
        train(model, device, train_loader, optimizer, epoch)
        test(model, device, test_loader)
##########################按百分比計算剪枝的閾值###########################################
threshold = calculate_threshold(model, 96)
print(threshold)
##########################進行剪枝操作並計算測試正確率###########################################
prune(model, threshold)
test(model, device, test_loader)
##########################剪枝操作後進行再次訓練###########################################
retrain(model, device, train_loader ,test_loader, 20, 0.001, 0.25)

權重柱狀圖繪製

# 提取繪製的數據
def paraCount(layers, location, index):
    values = []
    for value in layers:
        if location[index] == 0:
            values.append(value)
        index += 1
    values = np.array(values)
    return  values
###########################繪製剪枝後的整體參數分佈############################
layers = expand_model(model)   
layers = layers.detach().cpu().numpy()
values = paraCount(layers, location, 0)
plt.figure(figsize=(20,5))
plt.hist(values, bins = 1000)
plt.show()
###########################繪製第一層網絡參數分佈############################
fc1_hist = fc1.reshape(-1)
fc1_hist = paraCount(fc1_hist, location, 0)
plt.figure(figsize=(20,5))
plt.hist(fc1_hist, bins = 300)
plt.show()
###########################繪製第二層網絡參數分佈############################
fc2_hist = fc2.reshape(-1)
fc2_hist = paraCount(fc2_hist, location, fc1.shape[0] * fc1.shape[1])
plt.figure(figsize=(20,5))
plt.hist(fc2_hist, bins = 100)
plt.show()
###########################繪製第三層網絡參數分佈############################
fc3_hist = fc3.reshape(-1)
fc3_hist = paraCount(fc3_hist,location, fc1.shape[0] * fc1.shape[1] + fc2.shape[0] * fc2.shape[1])
plt.figure(figsize=(20,5))
plt.hist(fc3_hist, bins = 100)
plt.show()

這個剪枝操作需要重複進行,這個全連接的神經網絡,我可以剪枝掉98%後,正確率還是會有90%,而且經過多次剪枝操作後,網絡大部分的參數接近與零,只有重要的參數纔會起作用。
jupyter下實現的代碼,需要的可以去下載: link.

實驗

下面就是基於以上的代碼,使用手寫數字體的數據集進行的實驗,我會記錄實驗過程中的數據變化過程。

初次訓練網絡

在這裏插入圖片描述

神經元激活

從上圖可以看出,經過20次的迭代,網絡的正確率已經到達了97%
對於數字4的圖像進行識別,圖像的非零點數是75個,第一層神經元激活個數169,第二層神經元激活個數70,識別結果是4正確

層數 個數
fc1 169
fc2 70
fc3 結果是4

在這裏插入圖片描述

第一層權重的深度分佈圖

下面是初次訓練第一層權重的深度分佈圖,從這裏可以明顯的看到了28的分割輪廓了,兩端的參數較小,不太重要。
在這裏插入圖片描述

權重柱狀圖

下圖是全部參數的柱狀圖、第一層參數的柱狀圖、第二層參數的柱狀圖、第三層參數的柱狀圖,可以看出參數都基本集中的0附近,成中心分佈的樣子。
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述

剪枝10%參數

計算出來的閾值是:0.0038155303103849293
在這裏插入圖片描述
剪枝後的正確率並沒有明顯的變化,仍舊是97%
然後進行再訓練
在這裏插入圖片描述
比初次訓練的正確個數還多了,說不定這個可以增加泛化能力。

神經元激活

層數 個數
fc1 167
fc2 71
fc3 結果是4

第一層權重的深度分佈圖

仔細對比可以發現,亮的地方變的更亮了一點。
在這裏插入圖片描述

權重柱狀圖

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

剪枝20%參數

計算出來的閾值是:0.007650174759328366
在這裏插入圖片描述
剪枝後的正確率並沒有明顯的變化,仍舊是97%
然後進行再訓練
在這裏插入圖片描述
比上訓練的正確個數還多了,又增加泛化能力。

神經元激活

層數 個數
fc1 172
fc2 73
fc3 結果是4

第一層權重的深度分佈圖

仔細對比可以發現,這次好像圖像暗的地方增多了
在這裏插入圖片描述

權重柱狀圖

在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述

剪枝30%參數

計算出來的閾值是:0.01150485947728157
在這裏插入圖片描述
剪枝後的正確率並沒有明顯的變化,仍舊是97%
然後進行再訓練
在這裏插入圖片描述
比上訓練的正確個數還多了,又增加泛化能力。

神經元激活

層數 個數
fc1 169
fc2 74
fc3 結果是4

第一層權重的深度分佈圖

仔細對比可以發現,這次好像圖像暗的地方更暗了
在這裏插入圖片描述

權重柱狀圖

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述

剪枝40%參數

計算出來的閾值是:0.01536105088889599
在這裏插入圖片描述
剪枝後的正確率並沒有明顯的變化,仍舊是97%
然後進行再訓練
在這裏插入圖片描述
比上訓練的正確個數還多了,又增加泛化能力。

神經元激活

層數 個數
fc1 167
fc2 73
fc3 結果是4

第一層權重的深度分佈圖

仔細對比可以發現,這次好像圖像暗的地方更暗了
在這裏插入圖片描述

權重柱狀圖

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述

剪枝50%參數

計算出來的閾值是:0.019335072487592697
在這裏插入圖片描述
剪枝後的正確率並沒有明顯的變化,仍舊是97%
然後進行再訓練
在這裏插入圖片描述
比上訓練的正確個數還多了,又增加泛化能力。

神經元激活

層數 個數
fc1 171
fc2 73
fc3 結果是4

第一層權重的深度分佈圖

仔細對比可以發現,這次好像圖像暗的地方更暗了
在這裏插入圖片描述

權重柱狀圖

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述

剪枝60%參數

計算出來的閾值是:0.023352954909205435
在這裏插入圖片描述
剪枝後的正確率並沒有明顯的變化,仍舊是97%
然後進行再訓練
在這裏插入圖片描述
比上訓練的正確個數還多了,又增加泛化能力。

神經元激活

層數 個數
fc1 176
fc2 74
fc3 結果是4

第一層權重的深度分佈圖

仔細對比可以發現,這次好像圖像暗的地方更暗了
在這裏插入圖片描述

權重柱狀圖

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述

剪枝70%參數

計算出來的閾值是:0.02758437413722277
在這裏插入圖片描述
剪枝後的正確率並沒有明顯的變化,仍舊是97%
然後進行再訓練
在這裏插入圖片描述
比上訓練的正確個數還多了,又增加泛化能力。

神經元激活

層數 個數
fc1 174
fc2 73
fc3 結果是4

第一層權重的深度分佈圖

仔細對比可以發現,這次好像圖像暗的地方更暗了
在這裏插入圖片描述

權重柱狀圖

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述

剪枝80%參數

計算出來的閾值是:0.03225095123052597
在這裏插入圖片描述
剪枝後的正確率並沒有明顯的變化,仍舊是97%
然後進行再訓練
在這裏插入圖片描述
和上次訓練的正確個數差不多,網絡性能沒有增加了

神經元激活

層數 個數
fc1 181
fc2 77
fc3 結果是4

第一層權重的深度分佈圖

仔細對比可以發現,這次好像圖像暗的地方更暗了
在這裏插入圖片描述

權重柱狀圖

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述

剪枝90%參數

計算出來的閾值是:0.039930999279022224
在這裏插入圖片描述
剪枝後的正確率發生了變化,下降到了93%
然後進行再訓練
在這裏插入圖片描述
和上次訓練的正確個數差不多,網絡性能沒有增加了

神經元激活

層數 個數
fc1 184
fc2 78
fc3 結果是4

第一層權重的深度分佈圖

仔細對比可以發現,這次好像圖像暗的地方更暗了
在這裏插入圖片描述

權重柱狀圖

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述

剪枝95%參數

計算出來的閾值是:0.05171532221138477
在這裏插入圖片描述
剪枝後的正確率發生了變化,下降到了85%
然後進行再訓練
在這裏插入圖片描述
和上次訓練的正確個數差不多,網絡性能沒有增加了

神經元激活

層數 個數
fc1 189
fc2 84
fc3 結果是4

第一層權重的深度分佈圖

仔細對比可以發現,這次好像圖像暗的地方更暗了
在這裏插入圖片描述

權重柱狀圖

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述

剪枝98%參數

計算出來的閾值是:0.07121909260749816
在這裏插入圖片描述
剪枝後的正確率降到了很低,下降到了60%
然後進行再訓練
在這裏插入圖片描述
和上次訓練的正確個數差不多,網絡性還是能夠恢復的

神經元激活

層數 個數
fc1 174
fc2 76
fc3 結果是4

第一層權重的深度分佈圖

仔細對比可以發現,這次好像圖像暗的地方更暗了
在這裏插入圖片描述

權重柱狀圖

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述

剪枝99%參數

計算出來的閾值是:0.091620362251997
在這裏插入圖片描述
剪枝後的正確率降到了很低,下降到了46%
然後進行再訓練
在這裏插入圖片描述
和上次訓練的正確個數下降一個百分點,網絡性還是能夠恢復的,但是恢復的性能略有下降

神經元激活

層數 個數
fc1 169
fc2 74
fc3 結果是4

第一層權重的深度分佈圖

仔細對比可以發現,這次好像圖像暗的地方更暗了
在這裏插入圖片描述

權重柱狀圖

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述

剪枝99%參數

計算出來的閾值是:0.04240277148829452
在這裏插入圖片描述
剪枝後的正確率降到了很低,下降到了50%
然後進行再訓練
在這裏插入圖片描述
和上次訓練的正確個數下降一個百分點,網絡性還是能夠恢復的,但是恢復的性能略有下降

神經元激活

層數 個數
fc1 167
fc2 75
fc3 結果是4

第一層權重的深度分佈圖

仔細對比可以發現,這次好像圖像暗的地方更暗了
在這裏插入圖片描述

權重柱狀圖

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述

剪枝99%參數

計算出來的閾值是:0.04132488820748184
在這裏插入圖片描述
剪枝後的正確率降到了很低,下降到了50%
然後進行再訓練
在這裏插入圖片描述
和上次訓練的正確個數下降一個百分點,網絡性還是能夠恢復的,但是恢復的性能略有下降

神經元激活

層數 個數
fc1 167
fc2 77
fc3 結果是4

第一層權重的深度分佈圖

仔細對比可以發現,這次好像圖像暗的地方更暗了
在這裏插入圖片描述

權重柱狀圖

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述

剪枝99%參數

計算出來的閾值是:0.04077488094611925
在這裏插入圖片描述
剪枝後的正確率降到了很低,下降到了50%
然後進行再訓練
在這裏插入圖片描述
和上次訓練的正確個數下降一個百分點,網絡性還是能夠恢復的,但是恢復的性能略有下降

神經元激活

層數 個數
fc1 167
fc2 77
fc3 結果是4

第一層權重的深度分佈圖

仔細對比可以發現,這次好像圖像暗的地方更暗了
在這裏插入圖片描述

權重柱狀圖

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述

剪枝99%參數

經過非常多次剪枝99%然後參數再訓練,整個網絡的性能還是會慢慢提升的
計算出來的閾值是:0.025321735092738397
在這裏插入圖片描述
剪枝後的正確率降到了很低,下降到了65%,但是比剛開始還是有明顯的提高
然後進行再訓練
在這裏插入圖片描述
和上次訓練的正確個數下降一個百分點,網絡性還是能夠恢復的,還是快恢復到了巔峯時期
然後剪枝80%
計算出來的閾值是:0.0012542949290946129
在這裏插入圖片描述

層數 個數
fc1 124
fc2 69
fc3 結果是4

然後剪枝90%
計算出來的閾值是:0.0027993932599201803
在這裏插入圖片描述

層數 個數
fc1 101
fc2 66
fc3 結果是4

然後剪枝95%
計算出來的閾值是:0.005025399965234099
在這裏插入圖片描述

層數 個數
fc1 78
fc2 64
fc3 結果是4

然後剪枝99%
計算出來的閾值是:0.02462294178083847
在這裏插入圖片描述

層數 個數
fc1 25
fc2 56
fc3 結果是1

通過上面的一系列數據,其實可以發現,網絡中90的參數可以刪掉,幾乎不會影響準確性。95%的刪掉,準確性還是可以接受。但是相對於參數的減少,在實際運算過程中,激活神經元的減少數量其實沒有那麼明顯,當刪除95% 的參數時,纔會減少一半左右的神經元,當刪除99%的參數時,神經元能夠大幅度減少,但是可以發現現在的預測準確性已經無法接受。
同時,也可以發現,當前可以刪除的神經元是在第一層全連接層,而第二層全連接層明顯變化沒有第一層劇烈。
思考:
神經網絡的結構是每遞進一層深度,所獲得的信息就會更加抽象,說明在深層處抽象的特徵多餘的較少。

第一層權重的深度分佈圖

仔細對比可以發現,圖片裏面亮起來的點已經非常少了,基本上就是亮的和不亮的了。
在這裏插入圖片描述

權重柱狀圖

99%裁剪的情況下,一共使用了2662參數。
在這裏插入圖片描述
第一層一共使用了379個參數,此時和剛開始2828300=235200,減少了620倍,在淺層的多餘參數這麼多的嘛。而且,也可以看到,參數大量集中到了0.2左右的中心分佈,偏離了原來以0爲中心分佈的情況。

在這裏插入圖片描述
第二層一共使用了1654個參數,此時和剛開始100*300=30000,減少了18倍。參數幾乎還是以零爲中心在分佈。
在這裏插入圖片描述

第三層一共使用了629個參數,此時和剛開始100*10=1000,減少了1.59倍。可見參數其實減少的非常少。
在這裏插入圖片描述

思考與提問

從上面的分析,可以發現,越深層的參數和神經元越重要,從參數的大小和被裁剪的多少都可以明顯的看出。這裏我們是按照所有層的參數放在一起按百分比進行裁剪,如果是按照每一層自己的百分比進行裁剪,是不是整個訓練精度就會下降很多,明顯不如這一個?下面可以做一個實驗測試一下。
還有就是,現在我們第一層的神經元是300個,底層每次要激活170個左右,大概是在50%的激活率,如果我們把第一層的神經元個數增加到600個,那麼每次激活的個數還是50%左右還是170嗎?,如果我們進行同樣的剪枝操作之後激活的個數或者百分比有什麼變化?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章