1. 程式人生 > >【用Python學習Caffe】8. 網路結構的權重共享量化

【用Python學習Caffe】8. 網路結構的權重共享量化

8. 網路結構的權重共享量化

網路權重共享量化也是一類重要的網路壓縮方法,其本質在於先通過聚類方法得到該層權重的聚類中心,然後通過聚類中心值來表示原權重值。因此權重值並不是由32位的浮點數來表示,而是由其對應的聚類中心的序號表示,如果聚類級別為8位,此時權重值只需要用8位就能表示。

對於網路權重量化也有三個問題:

  1. 量化級別的確定,同修剪率一樣,可以通過試錯的試驗的方法來確定
  2. 量化後網路重新訓練問題
  3. 量化中心的初始選擇問題:聚類中心採用線性方法初始化,將初始點均勻分散,這種初始化方法不僅操作簡單,而且能夠將對網路影響較大但實際分佈較少的較大權重值也包含到初始中心點中,因此不容易造成較大權重的丟失。

8.1 Kmean聚類得到每層的聚類中心

對於Kmean聚類方法,這裡呼叫的是scipy庫的聚類函式

    # 獲得各層的量化碼錶
    def kmeans_net(net, layers, num_c=16, initials=None):
        # net: 網路
        # layers: 需要量化的層
        # num_c: 各層的量化級別
        # initials: 初始聚類中心
        codebook = {} # 量化碼錶
        if type(num_c) == type(1):
            num_c = [num_c] * len(layers)
        else:
            assert len(num_c) == len(layers)

        # 對各層進行聚類分析
        print "==============Perform K-means============="
        for idx, layer in enumerate(layers):
            print "Eval layer:", layer
            W = net.params[layer][0].data.flatten()
            W = W[np.where(W != 0)] # 篩選不為0的權重
            # 預設情況下,聚類中心為線性分佈中心
            if initials is None:  # Default: uniform sample
                min_W = np.min(W)
                max_W = np.max(W)
                initial_uni = np.linspace(min_W, max_W, num_c[idx] - 1)
                codebook[layer], _ = scv.kmeans(W, initial_uni)
            elif type(initials) == type(np.array([])):
                codebook[layer], _ = scv.kmeans(W, initials)
            elif initials == 'random':
                codebook[layer], _ = scv.kmeans(W, num_c[idx] - 1)
            else:
                raise Exception

            # 將0權重值附上
            codebook[layer] = np.append(0.0, codebook[layer])
            print "codebook size:", len(codebook[layer])

        return codebook

8.2 量化各層

通過各層聚類來進行各層權重的量化

    def quantize_net_with_dict(net, layers, codebook, use_stochastic=False, timing=False):
        start_time = time.time()
        codeDict = {} # 記錄各個量化中心所處的位置
        maskCode = {} # 各層量化結果
        for layer in layers:
            print "Quantize layer:", layer
            W = net.params[layer][0].data
            if use_stochastic:
                codes = stochasitc_quantize2(W.flatten(), codebook[layer])
            else:
                codes, _ = scv.vq(W.flatten(), codebook[layer])
            W_q = np.reshape(codebook[layer][codes], W.shape)
            net.params[layer][0].data[...] = W_q

            maskCode[layer] = np.reshape(codes, W.shape)
            codeBookSize = len(codebook[layer])
            a = maskCode[layer].flatten()
            b = xrange(len(a))

            codeDict[layer] = {}
            for i in xrange(len(a)):
                codeDict[layer].setdefault(a[i], []).append(b[i])

        if timing:
            print "Update codebook time:%f" % (time.time() - start_time)

        return codeDict, maskCode

8.3 重新訓練及聚類中心的更新

    @static_vars(step_cache={}, step_cache2={}, count=0)
    def update_codebook_net(net, codebook, codeDict, maskCode, args, update_layers=None, snapshot=None):

        start_time = time.time()
        extra_lr = args['lr'] # 基礎學習速率
        decay_rate = args['decay_rate'] # 衰減速率
        momentum = args['momentum'] # 遺忘因子
        update_method = args['update'] # 更新方法
        smooth_eps = 0

        normalize_flag = args['normalize_flag'] # 是否進行歸一化


        if update_method == 'rmsprop':
            extra_lr /= 100

        # 對碼錶與量化結果的初始化
        if update_codebook_net.count == 0:
            step_cache2 = update_codebook_net.step_cache2
            step_cache = update_codebook_net.step_cache
            if update_method == 'adadelta':
                for layer in update_layers:
                    step_cache2[layer] = {}
                    for code in xrange(1, len(codebook[layer])):
                        step_cache2[layer][code] = 0.0
                smooth_eps = 1e-8

            for layer in update_layers:
                step_cache[layer] = {}
                for code in xrange(1, len(codebook[layer])):
                    step_cache[layer][code] = 0.0

            update_codebook_net.count = 1

        else:
            # 讀入上次運算的結果
            step_cache2 = update_codebook_net.step_cache2
            step_cache = update_codebook_net.step_cache
            update_codebook_net.count += 1

        # 所有層名
        total_layers = net.params.keys()
        if update_layers is None: # 所有層都需要進行更新
            update_layers = total_layers

        # 權重碼錶的更新
        for layer in total_layers:
            if layer in update_layers:
                diff = net.params[layer][0].diff.flatten() # 誤差梯度
                codeBookSize = len(codebook[layer])
                dx = np.zeros((codeBookSize)) # 編碼表的誤差更新
                for code in xrange(1, codeBookSize):
                    indexes = codeDict[layer][code] # codeDict儲存屬於某編碼的權重的序號
                    #diff_ave = np.sum(diff[indexes]) / len(indexes)
                    diff_ave = np.sum(diff[indexes]) # 統計該編碼所有的誤差更新和

                    # 針對於不同方法進行更新
                    if update_method == 'sgd':
                        dx[code] = -extra_lr * diff_ave
                    elif update_method == 'momentum':
                        if code in step_cache[layer]:
                            dx[code] = momentum * step_cache[layer][code] - (1 - momentum) * extra_lr * diff_ave
                            step_cache[layer][code] = dx
                    elif update_method == 'rmsprop':
                        if code in step_cache[layer]:
                            step_cache[layer][code] = decay_rate * step_cache[layer][code] + (1.0 - decay_rate) * diff_ave ** 2
                            dx[code] = -(extra_lr * diff_ave) / np.sqrt(step_cache[layer][code] + 1e-6)
                    elif update_method == 'adadelta':
                        if code in step_cache[layer]:
                            step_cache[layer][code] = step_cache[layer][code] * decay_rate + (1.0 - decay_rate) * diff_ave ** 2
                            dx[code] = -np.sqrt((step_cache2[layer][code] + smooth_eps) / (step_cache[layer][code] + smooth_eps)) * diff_ave
                            step_cache2[layer][code] = step_cache2[layer][code] * decay_rate + (1.0 - decay_rate) * (dx[code] ** 2)

                # 是否需要進行歸一化更新引數
                if normalize_flag:
                    codebook[layer] += extra_lr * np.sqrt(np.mean(codebook[layer] ** 2)) / np.sqrt(np.mean(dx ** 2)) * dx
                else:
                    codebook[layer] += dx
            else:
                pass

            # maskCode儲存編碼結果
            W2 = codebook[layer][maskCode[layer]]
            net.params[layer][0].data[...] = W2 # 量化後權重值

        print "Update codebook time:%f" % (time.time() - start_time)

重新訓練時,其精度的變化圖,可以看到隨著迭代次數增加,其精度也逐漸提升

8.4 網路壓縮未來的方向

從上面可以看出來,在訓練中,各網路中的權重仍是32位的浮點數,而不是用8位來表示,而即使在實際執行中,也必須通過聚類中心表將量化後權重值轉換為32位的浮點數,因此並不能在減少網路的實際執行記憶體,只是減少網路的記憶體消耗。

要真正減少網路記憶體消耗,從而達到網路實際執行速度的提高,目前有兩類主流方法:

  1. 讓網路保證結構上的稀疏性,而不是隨機分佈的稀疏性。可以參考論文:
    Wen W, Wu C, Wang Y, et al. Learning Structured Sparsity in Deep Neural Networks[J]. 2016.

  2. 設計量化運算的網路,比如QNN及BNN等等。可以參考論文:Hubara I, Courbariaux M, Soudry D, et al. Quantized neural networks: Training neural networks with low precision weights and activations[J]. Journal of Machine Learning Research. 2016, 1: 1–29.

8.5 具體程式碼下載

相關推薦

Python學習Caffe8. 網路結構權重共享量化

8. 網路結構的權重共享量化 網路權重共享量化也是一類重要的網路壓縮方法,其本質在於先通過聚類方法得到該層權重的聚類中心,然後通過聚類中心值來表示原權重值。因此權重值並不是由32位的浮點數來表示,而是由其對應的聚類中心的序號表示,如果聚類級別為8位,此時權重值

Python學習Caffe4. 設計自己的網路結構

4. 設計自己的網路結構 通過前文的例子,我們都知道了Caffe的網路都是一個prototxt的網路結構配置檔案定義的,該檔案可以用文字工具開啟,開啟後,我們可以看到如下結構: layer { name: "data" typ

Python學習Caffe5. 生成solver檔案

5. 生成solver檔案 網路訓練一般是通過solver來進行的。對於caffe來說,其是通過solver檔案來生成solver訓練器進行網路訓練及測試的,該solver檔案中包含了訓練及測試網路的配置檔案的地址,及相關訓練方法及一些訓練的超引數,該檔案一般

Vue.js學習筆記8:建立多個Vue例項物件,認識Vue中的元件

建立多個Vue例項物件 這裡在同一個js檔案中建立了兩個Vue例項物件,它們各自能完成前面學的那些功能,同時使用物件名稱也可以互相訪問,協同實現一些功能。 index.html <!DOCTYPE html> <html lang="en" xmlns:v-

SciKit-Learn學習筆記8:k-均值演算法做文字聚類,聚類演算法效能評估

學習《scikit-learn機器學習》時的一些實踐。 原理見K-means和K-means++的演算法原理及sklearn庫中引數解釋、選擇。 sklearn中的KMeans from sklearn.datasets import make_blobs from m

python學習筆記8

繼續今天的python學習 昨天我們說到了多執行緒共享資料(全域性變數),那麼今天我們就緊接著來說一下多執行緒不共享資料的使用方式 import threading import time def test1(): the_number = 0 for i

原創python學習筆記(8)--《笨辦法學python》關於list列表

一 列表,元組 和字典的概念 二 列表的各種方法 .append() .insert() .sort() .reverse() .index() .count() .remove() # -*- coding:utf-8 -*- #先看下list 再

python學習筆記12:matplotlib繪製3D函式影象

①用pyplot的figure()函式可以建立一個figure物件 ②以它為引數建立Axes3D物件,使之具有3D座標軸 ③pyplot的show()方法可以顯示所有figure物件 *顯示兩個3D座標軸 import matplotlib.pyplot as plt #

python學習筆記13:梯度下降法求解最優值問題

梯度是函式在某點沿每個座標的偏導數構成的向量,它反映了函式沿著哪個方向增加得最快。因此要求解一個二元函式的極小值,只要沿著梯度的反方向走,直到函式值的變化滿足精度即可。 這裡打表儲存了途徑的每個點,最後在圖上繪製出來以反映路徑。 *梯度下降的具體實現 impor

python學習筆記正則表示式從含中文的網頁中提取資料(含編碼轉換)

目標:用正則表示式從含中文的網頁中提取資料 1、獲得網頁全部資料 1.1思考過程 確定我們要操作的網頁:url = 'http://q.stock.sohu.com/cn/603077/cwzb.shtml' 開啟要操作的網頁:req = urllib2.open(url)

python實現《統計學習方法》之決策樹C4.5/ID3

宣告:本文根據李航博士的《統計學校方法》中的決策樹章節的原理:最大熵資訊增益、資訊增益比進行決策樹的實現。在視覺化方面主要參考的這篇博文。 決策樹演算法是一類在資料探勘中應用的特別多的符號學派分類器,並在整合學習中被大大采用。經典的c4.5和id3以及後來的c

python學習筆記6:Gauss-Legendre求積公式近似求積分值

高斯-勒讓德求積公式給出了一個定積分的近似求法: 不妙的是這種求法對上下限要求為1和-1,但是因為積分可以變限,所以求任意定積分只要做變換就好: 用高斯公式求積分的近似值,精確度是非常高的,一般用幾個點就可以得到很不錯的近似值。這裡用了三點高斯積分和五點高斯積分。

Python學習3月8python編程 從入門到實踐---》筆記(1)

store 執行 \n true r.js under 進行 一聲 tor 第十章:處理文件和異常 #學習處理文件,讓程序能夠快速地分析大量的數據#學習錯誤處理,避免程序在面對意外情形時崩潰#學習異常,是python創建的特殊對象,用於管理程序運行時出現#學習模塊json

Python學習筆記-APP圖標顯示未讀消息數目

運行 讀取 lin 簡單實現 ets fcm 筆記 python語言 lte 以小米手機系統為例,當安裝的某個APP有未讀消息時,就會在該APP圖標的右上角顯示未讀消息的數目。本文主要解說怎樣用Python語言實現圖標顯示未讀消息的數目。首先,還是要用到Python中P

python學習筆記6.抽象

位置參數 name 默認參數 [] 順序 fun append 聲明 value 【python學習筆記】6.抽象 創建函數: 使用def語句定義函數,不用聲明參數類型,和返回值類型 def function_name(param1, param2):

安全牛學習筆記python裝飾器

信息安全 python security+ 互聯網 python裝飾器 裝飾器本質上是一個函數,該函數用來處理其他函數,它可以讓其他函數在不需要修改代碼的前提 下增加額外的功能,裝飾器的返回值也是一個函數對象。它經常用於有切面需求的場景,比如:插 入日誌

安全牛學習筆記python實例

信息安全 python security+ 互聯網 閉包 什麽是閉包?簡單說,閉包就是根據不同的配置信息得到不同的結果 再來看看專業的解釋:閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量 的函數。這個被引用的自

安全牛學習筆記python使用入門

信息安全 security+ python 第一講 python使用入門1.python版本可以分為2.x和3.xPython3.x缺點不向下兼容2.使用python的優點:語法簡潔,可讀性高,開發效率高,無需編譯,移植性好等等3.使用python可以做的事情系統編程,用戶圖形接口,interne

視頻編解碼·學習筆記8. 熵編碼算法:基本算法列舉 & 指數哥倫布編碼

方法 mark enter 協議 int 十進制數 pan 進制數 tab 一、H.264中的熵編碼基本方法: 熵編碼具有消除數據之間統計冗余的功能,在編碼端作為最後一道工序,將語法元素寫入輸出碼流 熵解碼作為解碼過程的第一步,將碼流解析出語法元素供後續步驟重建圖像使用

Python學習筆記Coursera之PY4E學習筆記——File

color 學習筆記 函數 read mod rom stephen 內容 filename 1、打開文件 使用handle=open(filename,mode)打開文件。這一函數將會返回一個handle(應該翻譯為“柄”吧)用來操控文件,參數filename是一個字符串