深度學習原理與框架-Alexnet(遷移學習代碼) 1.sys.argv[1:](控制台輸入的參數獲取第二個參數開始) 2.tf.split(對數據進行切分操作) 3.tf.concat(對數據進行合並操作) 4.tf.variable_scope(指定w的使用范圍) 5.tf.get_variable(構造和獲得參數) 6.np.load(加載.npy文件)


1. sys.argv[1:]  # 在控制台進行參數的輸入時,只使用第二個參數以后的數據

參數說明:控制台的輸入:python test.py what, 使用sys.argv[1:],那么將獲得what這個數值

# test.py

import sys
print(sys.argv[1:])

2. tf.split(value=x, num_or_size_split=2, axis=3) # 對數據進行切分操作,比如原始維度為[1, 227, 227, 96], 切分后的維度為[2, 1, 227, 227, 48]

參數說明:value表示輸入數據,num_or_size_split切分的數據大小,axis對第幾個維度進行切分

3.tf.concat(values=x, axis=3) # 表示對數據進行合並操作,比如存在[1, 64, 64, 128], [1, 64, 64, 128] 合並后的大小為[1, 64, 64, 256]

參數說明:values表示輸入數據,axis表示合並的維度

4. tf.variable_scope(name, reuse=True) # 表示在name層名的范圍內,對參數進行復用操作

參數說明:name表示層的名字,reuse=True表示進行復用操作

5.tf.get_variable('w', shape=[5, 5, channel/group, num_filter], trainable) # 創建參數,如果存在就進行參數的復用

代碼說明:'w'表示參數名,shape表示參數的大小,trainable表示對參數是否進行凍結操作,即不參與參數的更新

6. np.load('a.npy', encoding='bytes').item()  # 進行.npy文件的讀取,npy文件是一種Python的獨有的讀取文件

參數說明:‘a.npy’表示文件名,encoding表示編碼方式,item()表示獲得文件的內容

a = {'a':1, 'b':2}
np.save('a.npy', a)
c = np.load('a.npy', encoding='bytes').item()
print(c)

 

1

                              Alex網絡結構圖

代碼說明:這里只進行圖片分類的測試,並不進行圖片的訓練,使用的方法是讀入.npy文件,獲得的數據是字典類型的參數數據,字典的鍵是’conv1‘,'fc6'每一層的名字,字典的值是一個大列表,列表中有兩個數據,一個是w的值,一個是b的值,對於w的值,其len(w.shape) > 1, 對於b的值,其len(b.shape) = 1 

需要做的是:構建Alexnet的網絡結構,讀入npy已經訓練好的參數,並進行訓練

數據說明:測試數據為3張圖片,使用cv2.resize將數據的維度變成(227, 227)

對代碼部分進行說明:這套代碼的特別點:主要是在卷積層的特殊之處

在第二層,第五層和第六層卷積,分別進行了分開卷積再合並的操作,

如果是分開卷積再合並的話,那么卷積核的維度為[Kheight, Kwidth, chanel/groups, num_filter], 因為數據的第三個維度為channel/groups

同時需要將w和x進行split切分,即各自的第四個維度平分成兩半,使用列表來存放結果,最后在使用tf.concat進行合並,再使用tf.nn.add_bias添加偏置項,使用激活函數進行激活操作

還有就是使用了with tf.variable_scope(name) as scope,  以及tf.get_variable('w', shape=[]), 在讀取的參數過程中,使用參數名‘fc6‘作為參數的范圍reuse=True,

tf.get_variable(獲得上面定義的參數), 使用.assign將載入的參數賦予’w‘, 演示代碼

a = {'a':1, 'b':2}
np.save('a.npy', a)
c = np.load('a.npy', encoding='bytes').item()
print(c)

with tf.variable_scope('a'):
    w = tf.get_variable('w', shape=[1], initializer=tf.constant_initializer(10))

with tf.variable_scope('b'):
    w = tf.get_variable('w', shape=[1], initializer=tf.constant_initializer(20))

with tf.Session() as sess:
    for name in c:
        x = np.reshape(c[name], w.shape)
        with tf.variable_scope(name, reuse=True):
            w = tf.get_variable('w', trainable=False).assign(x)
            print(sess.run(w))

代碼:

第一步:使用argpase.ArgumentParser() 構建輸入參數,parser.add_argument添加參數,這里使用的參數是圖片的來源args.images, 圖片的地址args.path 

第二步:讀取圖片

            判斷圖片的來源方式:

                如果是folder, 使用lamda f: ’{}/{}‘.format(args.path, f) 構建文件的路徑, 使用os.listdir(args.path) 遍歷文件里的文件名,使用cv2.imread(withpath(f)) 讀取文件, 使用os.path.isfile(withpath(f)) 判斷是否是文件,使用dict將讀取的結果進行組合

               如果是url,即網絡上的圖片地址,構建讀取圖片的函數,使用urllib.request.urlopen()打開文件路徑,使用bytearray(resp.read()) 將讀取的圖片轉換為二進制格式,使用cv2.imdecode() 將圖片轉換為utf-8類型

第三步:如果讀到圖片,進行參數的設置,

             dropout: 用於進行tf.nn.dropout的keep_prob 

             skip: 用於在后續的參數加載中,用來去掉不需要進行加載的參數

             numClass: 分類的結果數

             x: tf.placeholder() 用於進行輸入數據的初始化

             imgMean: 圖片的均值,用於后續的圖片去均值

第四步:模型的實例化操作

            第一步:構建類AlexNet, 將傳入的參數進行self操作,參數有x, dropout, numClass, skip, pathmodel 

            第二步:使用self.buildModel, 調用函數進行model的構建

                           第一步:構建卷積網絡, 輸入的參數為x, kheight, kwidth, strideX, strideY, numfilter, name, padding, groups=1

                           第二步:構建池化層,輸入的參數為x, kheight, kwidth, strideX, strideY, name, padding

                           第三步:構建局部響應歸一化

                           第四步:第二層卷積層,groups=2,即進行分層卷積的操作, 池化層,局部響應歸一化

                           第五步:第三層卷積層, group=1,不進行分層卷積操作,池化層,局部響應歸一化

                           第六步:將歸一化后的結果,使用tf.reshape進行維度的變換

                           第七步:進行第一次全連接操作,輸入參數為x, inputS, outputS, relu_flag, name, 使用的也是with tf.variable_scope(name) as scope

                           第八步:進行dropout操作

                           第九步:全連接層,drought操作

                           第十層:全連接,使用self.fc8 表示輸出結果score,以便進行外部的調用

第五步:使用model.fc8獲得score得分值

第六步:使用tf.nn.softmax(score) 獲得概率值

第七步:使用with tf.Session() as sess來獲得sess

第八步:使用model.load_model(sess) 來加載參數,將獲得參數賦予給之前定義的參數

              第一步:在類中構造load_model(),傳入的參數是sess 

              第二步:使用np.load(path, encoding='bytes').item() 讀取npy文件, 讀入的數據是字典格式

              第三步:循環字典的keys,判斷key不在skip里面

              第四步:with tf.variable_scope(name, reuse=True), 即在這個范圍內獲得參數w,name等於’fc6‘等

              第五步:循環k, p in dict[name], 因為字典的鍵里面有兩組參數,一種是b,一種是w

              第六步:根據維度的大小來判斷是b,還是w,如果是w, 使用sess.run(tf.get_variable('w'., trainable=False) .assign(p))將w的數據變成p即字典的鍵,同理將b的值進行賦值操作

第九步:循環讀入的圖片字典,使用sess.run(softmax, feedict)獲得實際的得分scores

             第一步:對每一張圖片進行cv2.resize操作,同時減去ImageMean即均值

             第二步:使用np.argmax(sess.run(softmax, feed_dict={x:[resized]})) 獲得最大位置的索引值

             第三步:caffe_classes.class_names[mmax] 獲得最大索引值對應的類別名

第十步:進行作圖操作

              第一步:定義文字的類型,cv2.FONT_HERSHEY_SIMPLEX

              第二步:使用cv2.putText() 進行文字的添加

              第三步:使用cv2.imshow() 進行畫圖,使用cv2.waitkey(0)

代碼:testalex.py

import argparse
import cv2
import numpy as np
import tensorflow as tf
import sys
import os
import alexnet
import caffe_classes
import urllib.request


# 第一步:使用argparse構建輸入參數
parser = argparse.ArgumentParser(description='classify some picture')
# 是夠是本地路徑
parser.add_argument('images', choices=['folder', 'url'], default='folder')
# 添加路徑
parser.add_argument('path', help='the image path')
# 用於獲得除了testAlex.py的其他參數
args = parser.parse_args(sys.argv[1:])

# 第二步:根據路徑讀圖片數據
# 本地路徑
if args.images == 'folder':
    # 使用lambda做路徑的組合函數
    withPath = lambda f:'{}/{}'.format(args.path, f)
    # 使用os.listdir()遍歷文件路徑,獲得圖片名,判斷組合路徑下的文件是夠是文件,如果是,就使用cv2.imread讀取組合路徑下的圖片
    testImg = dict((f, cv2.imread(withPath(f))) for f in os.listdir(args.path) if os.path.isfile(withPath(f)))
# 網上圖片的路徑
elif args.images == 'url':
    # 根據url定義讀取的文件
    def url2img(url):
        """url to image"""
        # 打開url
        resp = urllib.request.urlopen(url)
        # 將讀取的文件轉換為二進制類型
        image = np.asarray(bytearray(resp.read()))
        # 對圖片進行解碼操作,轉換為utf-8
        image = cv2.imdecode(image, cv2.IMREAD_COLOR)
        return image
    # 將讀取的圖片轉換為字典的格式
    testImg = {args.path : url2img(args.path)}

# 第三步:如果存在testImg,進行參數設置
if testImg.values():
    # 用於dropout參數層
    dropout = 1
    # 用於判斷哪些參數不需要進行加載
    skip = []
    # 分類的類別數
    numClass = 1000
    # 使用tf.placeholder進行x的輸入參數初始化
    x = tf.placeholder(tf.float32, [1, 227, 227, 3])
    # 圖片的imgMean,原始圖片訓練的時候也是使用這個值的
    imgMean = np.array([104, 117, 124], dtype=np.float32)
    # 第四步:對模型進行實例化操作
    model = alexnet.AlexNet(x, dropout, numClass, skip)
    # 第五步:使用model.fc8獲得模型的score得分
    score = model.fc8
    # 第六步:使用tf.nn.softmax獲得模型的概率值
    prob = tf.nn.softmax(score)
    # 第七步:使用with tf.Session() as sess構造執行函數
    with tf.Session() as sess:
        # 第八步:使用model中的load_model函數加載模型的參數,並將參數賦值給定義好的參數
        model.load_model(sess)
        # 第九步:對讀入的圖片進行預測類別的操作
        for image in testImg.values():
            # 進行圖像的維度變換,變為227,227,同時去除均值
            resized = cv2.resize(image.astype(np.float32), (227, 227)) - imgMean
            # sess.run獲得prob值,再使用np.argmax獲得最大值的索引值
            mmax = np.argmax(sess.run(prob, feed_dict={x:[resized]}))
            # 獲得索引值對應的標簽名
            res = caffe_classes.class_names[mmax]
            # 第十步:進行作圖操作
            # 字體
            font = cv2.FONT_HERSHEY_SIMPLEX
            # 將文字添加到圖片中
            cv2.putText(image, res, (int(image.shape[0]/3), int(image.shape[1]/3)), font, 1, (0, 0, 255), 3)
            # 圖片的展示
            cv2.imshow('image', image)
            # 按任意鍵退出
            cv2.waitKey(0)

alexnet.py

import tensorflow as tf
import numpy as np

# 卷積層,輸入參數x, 卷積核的寬和長,步長的大小,卷積的數目,輸出層的名字,是否補零,以及分層的數目
def convLayer(x, Kheight, Kwidth, strideX, strideY, numfilter, name, padding='SAME', groups=1):
    # 獲得圖片的通道數
    channels = int(x.get_shape()[-1])
    # 使用lambda構造卷積的函數
    conv = lambda a, b:tf.nn.conv2d(a, b, strides=[1, strideY, strideX, 1], padding=padding)
    # 在名字為name的環境下,進行卷積操作
    with tf.variable_scope(name) as scope:
        # 使用tf.get_variable初始化w
        w = tf.get_variable('w', shape=[Kheight, Kwidth, channels/groups, numfilter])
        # 初始化b
        b = tf.get_variable('b', shape=[numfilter])
        # 根據group的大小,如果group等於2,那么第三個維度就切分成兩個數據
        # X的切分
        Xnew = tf.split(value=x, num_or_size_splits=groups, axis=3)
        # w的切分
        Wnew = tf.split(value=w, num_or_size_splits=groups, axis=3)
        # 對於各自的X,w進行分別的卷積操作
        featureMap = [conv(t1, t2) for t1, t2 in zip(Xnew, Wnew)]
        # 將各自卷積后的結果進行拼接,方便后續的池化和歸一化操作
        mergeMap = tf.concat(featureMap, axis=3)
        # 添加偏置項
        out = tf.nn.bias_add(mergeMap, b)
        # 添加激活層函數, 名字為scope.name
        out = tf.nn.relu(out, name=scope.name)

        return out

# 最大值池化,輸入參數為x,卷積的寬和長,卷積的步長,結果的名字,以及補零
def MaxPool(x, Kheight, Kwidth, strideX, strideY, name, padding='SAME'):
    # tf.nn.max_pool的參數,輸入x, ksize表示卷積大小,strides表示步長,name表示返回值的名字,padding補零的方式
    return tf.nn.max_pool(x, ksize=[1, Kheight, Kwidth, 1], strides=[1, strideY, strideX, 1], name=name, padding=padding)

# 局部響應歸一化
def LRN(x, R, alpha, beta, name, bias=1.0):
    # 在不同的filter上進行各個像素的歸一化操作
    return tf.nn.lrn(x, depth_radius=R, alpha=alpha, beta=beta, name=name, bias=bias)

# 全連接操作,輸入x, inputS表示輸入層w的大小,outputS表示輸出層w的大小,relu_flag表示是否卷積,name表示名字
def fcLayer(x, inputS, outputS, relu_flag, name):
   # 在name的范圍下進行操作
    with tf.variable_scope(name) as scope:
        # 設置w的維度
        w = tf.get_variable('w', shape=[inputS, outputS])
        # 設置b的維度
        b = tf.get_variable('b', shape=[outputS])
        # 進行點乘在進行加偏置項操作
        out = tf.nn.xw_plus_b(x, w, b, name=scope.name)
        # 如果進行relu
        if relu_flag:
            # 使用tf.nn.relu進行relu操作
            return tf.nn.relu(out)
        else:
            return out
# 進行dropout,輸入x,保留的大小,keep_prob,name表示名字
def dropout(x, keep_prob, name):

    return tf.nn.dropout(x, keep_prob=keep_prob, name=name)

class AlexNet(object):

    def __init__(self, x, keep_prob, numClass, skip, pathmodel='bvlc_alexnet.npy'):
        # 將輸入的參數進行self操作
        self.x = x
        self.keep_prob = keep_prob
        self.numClass = numClass
        self.skip = skip
        # 參數的路徑
        self.pathmodel = pathmodel
        # 進行模型框架的構建
        self.bulidModel()

    def bulidModel(self):
        # 進行第一層的卷積操作,卷積核的大小為11*11,步長為4*4, num_filter=96,name='conv1', padding='VALID'
        conv1 = convLayer(self.x, 11, 11, 4, 4, 96, 'conv1', 'VALID')
        # 進行第一層的最大值池化操作
        pool1 = MaxPool(conv1, 3, 3, 2, 2, 'pool1', 'VALID')
        # 進行局部響應歸一化操作
        lrn1 = LRN(pool1, 2, 2e-5, 0.75, 'norm1')
        # 進行第二次的卷積操作,卷積核大小5*5,步長1,num_filter=256, 名字'conv2',進行分層卷積操作
        conv2 = convLayer(lrn1, 5, 5, 1, 1, 256, 'conv2', groups=2)
        # 進行第二層的最大值池化操作
        pool2 = MaxPool(conv2, 3, 3, 2, 2, 'pool2', 'VALID')
        # 進行局部響應歸一化操作
        lrn2 = LRN(pool2, 2, 2e-5, 0.75, 'norm2')
        # 進行第三層的卷積操作,卷積核的大小為3*3, 步長為1*1,num_filter=384,name='conv3'
        conv3 = convLayer(lrn2, 3, 3, 1, 1, 384, 'conv3')
        # 進行第四層卷積操作,卷積核的大小為3*3,1*1,num_filter=384,進行分層操作
        conv4 = convLayer(conv3, 3, 3, 1, 1, 384, 'conv4', groups=2)
        # 進行第五層卷積操作,卷積核大小為3*3,步長為1*1,num_filter=256, name='conv5'進行分層操作
        conv5 = convLayer(conv4, 3, 3, 1, 1, 256, 'conv5', groups=2)
        # 進行第五層的池化操作
        pool5 = MaxPool(conv5, 3, 3, 2, 2, 'pool5', 'VALID')
        # 對於池化后的數據,進行維度的變換,將4維數據,轉換為二維數據,以便進行后續的全連接操作
        fcIn = tf.reshape(pool5, [-1, 6*6*256])
        # 進行全連接操作,輸入參數,輸入層w的維度,輸出層w的維度,是夠進行relu操作,name='fc6'
        fc6 = fcLayer(fcIn, 6*6*256, 4096, True, 'fc6')
        # 進行dropout操作
        dropout1 = dropout(fc6, self.keep_prob, 'dropout1')
        # 進行第七層的全連接操作,輸入,4096表示輸入層w的維度,4096輸出層w的維度
        fc7 = fcLayer(dropout1, 4096, 4096, True, 'fc7')
        # 進行dropout操作
        dropout2 = dropout(fc7, self.keep_prob, 'dropout2')
        # 最后一層進行類別預測得分的層,使用self.fc8為了score可以被外部調用
        self.fc8 = fcLayer(dropout2, 4096, self.numClass, True, 'fc8')

    # 將加載的參數根據層的名字,賦值給w和b參數
    def load_model(self, sess):
        # 進行npy文件的讀取,使用.item()獲得實際的值,這里的表示方式是列表,鍵為層數名,值為w和b
        dict_W = np.load(self.pathmodel, encoding='bytes').item()
        # 循環層數名
        for name in dict_W:
            # 如果層數名不在去除的名單里,這個可以用來進行參數的初始化,一部分參數不進行初始化,比如最后一層的finetune操作
            if name not in self.skip:
                # 在當前層的名字下,對參數進行復用
                with tf.variable_scope(name, reuse=True):
                    # 循環鍵中的參數值
                    for p in dict_W[name]:
                        # 如果參數的維度大於1,即為w
                        if len(p.shape) > 1:
                            # 使用tf.get_variable()獲得值,使用trainable對參數實行凍結,.assign將讀取的參數賦值給w
                            sess.run(tf.get_variable('w', trainable=False).assign(p))
                        else:
                            # 使用tf.get_variable獲得參數,使用trainable對參數進行凍結,assign將讀入數據賦值給b
                            sess.run(tf.get_variable('b', trainable=False).assign(p))

收獲:可以使用skip來篩選出需要下載的參數,這樣自己可以構建最后一層的參數,或者最后幾層的參數進行訓練。

同樣的,使用tf.get_variable('w', trainable=False) 將賦值的參數,進行凍結不讓其進行訓練。

 

             

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM