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) 将赋值的参数,进行冻结不让其进行训练。