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