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) 將賦值的參數,進行凍結不讓其進行訓練。