0%

论文中的Python知识点

1. 元类

首先理解python中的类,用class修饰的都可以叫做类

1
2
3
4
5
6
7
class Class():
pass
c = Class()
Class.b = 2
print(c.b)

# out 2

我们平时用的类都是实例化以后的类,可以在任何时候动态的创建类,通常情况我们都是这样c=Class(),python解释器会将它认为是创建类,可是解释器本身是如何创建类的,答案是利用type

type平时我们可能认为是查看对象的类型,例如

1
2
3
4
5
6
print(type(c))
# out:
# <class '__main__.Class'>
print(type(Class))
# out:
# <class 'type'>

所以,Class的类型是type,我们可以用type直接生成一个类

type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

1
2
3
4
Class_type = type('Class_type', (), {'a': 1, 'b': 2})
c_t = Class_type()
print(c_t.a)
# out:1

元类:就是用来创建这些类(对象)的类,元类就是类的类

type就是所有类的元类,可以理解为所有的类都是由type创建的,我们也可以创建自己的元类,这个类需要继承在type

__metaclass__属性

Class中有__metaclass__这个属性吗?如果是,Python会在内存中通过__metaclass__创建一个名字为Test的类对象
如果Python没有找到__metaclass__,它会继续在Base(父类)中寻找__metaclass__属性,并尝试做和前面同样的操作。
如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。
如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象

那么__metaclass__是什么?

答:就是可以创建类的东西,类是由type创建的,所以__metaclass__内部一定要返回一个类,它可以是一个函数,也可以是一个类,而这个类就是我们自定义的元类,这个类必须继承自type

通常元类用来创建API是非常好的选择,使用元类的编写很复杂,但使用者可以非常简洁的调用API

abc.ABCMeta:

简单的说ABCMeta就是让你的类变成一个纯虚类,子类必须实现某个方法,这个方法在父类中用@abc.abstractmethod修饰

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import abc
class Base(object,metaclass=abc.ABCMeta):

@abc.abstractmethod
def func_a(self,data):
'''
:param data:
:return:
'''
@abc.abstractmethod
def func_b(self,data,out):
'''
:param data:
:param out:
:return:
'''
def func_d(self):
print('func_d in base')

你可以实现这两个虚方法,也可以不实现
这样在Base的子类中就必须实现func_a,func_b2个函数,否则就会报错

1
2
3
4
5
6
7
class Sub(Base):

def func_a(self,data):
print('over write func_a',data)

def func_b(self,data,out):
print('over write func_b')

如果还想调用虚类的方法用super

1
2
3
def func_b(self,data,out):
super(Sub,self).func_b(data,out)
print('over write func_b')

还有一种方法是,注册虚子类

1
2
3
4
5
6
7
class Register(object):
def func_c(self):
print('func_c in third class')

def func_a(self,data):
print('func_a in third class',data)
Base.register(Register)

这样调用issubclass(), issubinstance()进行判断时仍然返回真值

2. tensorflow中的“tf.name_scope()”有什么用?

2.1. tf.name_scope()命名空间的实际作用

(1)在某个tf.name_scope()指定的区域中定义的所有对象及各种操作,他们的“name”属性上会增加该命名区的区域名,用以区别对象属于哪个区域;

(2)将不同的对象及操作放在由tf.name_scope()指定的区域中,便于在tensorboard中展示清晰的逻辑关系图,这点在复杂关系图中特别重要。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import tensorflow as tf;  
tf.reset_default_graph()

# 无tf.name_scope()
a = tf.constant(1,name='my_a') #定义常量
b = tf.Variable(2,name='my_b') #定义变量
c = tf.add(a,b,name='my_add') #二者相加(操作)
print("a.name = "+a.name)
print("b.name = "+b.name)
print("c.name = "+c.name)

# 有tf.name_scope()
# with tf.name_scope('cgx_name_scope'): #定义一块名为cgx_name_scope的区域,并在其中工作
# a = tf.constant(1,name='my_a')
# b = tf.Variable(2,name='my_b')
# c = tf.add(a,b,name='my_add')
# print("a.name = "+a.name)
# print("b.name = "+b.name)
# print("c.name = "+c.name)

# 保存graph用于tensorboard绘图
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
writer = tf.summary.FileWriter("./test",sess.graph)
print(sess.run(c))
writer.close()
1
2
3
4
5
6
7
8
9
10
# 输出结果
# 无tf.name_scope()
a.name = my_a:0
b.name = my_b:0
c.name = my_add:0

# 有tf.name_scope()
a.name = cgx_name_scope/my_a:0
b.name = cgx_name_scope/my_b:0
c.name = cgx_name_scope/my_add:0

从输出结果可以看出,在tf.name_scope()下的所有对象和操作,其name属性前都加了cgx_name_scope,用以表示这些内容全在其范围下。
下图展示了两种情况的tensorboard差异,差别一目了然。

img

2.2. name_scope()只决定“对象”属于哪个范围,并不会对“对象”的“作用域”产生任何影响。

tf.name_scope()只是规定了对象和操作属于哪个区域,但这并不意味着他们的作用域也只限于该区域(with的这种写法很容易让人产生这种误会),不要将其和“全局变量、局部变量”的概念搞混淆,两者完全不是一回事。在name_scope中定义的对象,从被定义的位置开始,直到后来某个地方对该对象重新定义,中间任何地方都可以使用该对象。本质上name_scope只对对象的name属性进行圈定,并不会对其作用域产生任何影响。这就好比甲、乙、丙、丁属于陈家,这里“陈家”就是一个name_scope划定的区域,虽然他们只属于陈家,但他们依然可以去全世界的任何地方,并不会只将他们限制在陈家范围。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
with tf.name_scope('cgx_1'):
a = tf.Variable(tf.constant(4), name='my_a')
print('case1: a.name = ' + a.name)
print("case2: a.name = " + a.name)

with tf.name_scope('cgx_2'):
print("case3: a.name = " + a.name)
a = tf.Variable(tf.constant(4), name='my_a')
print("case4: a.name = " + a.name)
print("case5: a.name = " + a.name)

# out:
'''
case1: a.name = cgx_1/my_a:0
case2: a.name = cgx_1/my_a:0
case3: a.name = cgx_1/my_a:0
case4: a.name = cgx_2/my_a:0
case5: a.name = cgx_2/my_a:0
'''

(1)程序首先指定了命名区域cgx_1,并在其中定义了变量a,紧接着case1直接在cgx_1中输出a.name = cgx_1/my_a:0,这很好理解,跟想象的一样;
(2)case2在cgx_1之外的公共区域也输出了相同的a.name,这就说明a的作用范围并没有被限制在cgx_1中
(3)接着程序又新指定了命名区域cgx_2,并在其中执行case3,输出a.name,结果还是和case1和case2完全相同,实际上还是最前面定义的那个a,这更进一步说明name_scope不会对对象的作用域产生影响
(4)★★接着在cgx_2中重新定义了变量“a”,紧接着就执行case4,输出a.name = cgx_2/my_a:0,可见此时的结果与前面三个case就不同了,说明这里新定义的a覆盖了前面的a,即使他们在两个完全独立的name_scope中
(5)case5输出的结果与case4结果相同,这已经无须解释了。

2.3 tf.name_scope(‘cgx_scope’)语句重复执行几次,就会生成几个独立的命名空间,尽管表面上看起来都是“cgx_scope”,实际上tensorflow在每一次执行相同语句都会在后面加上“_序数”,加以区别。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
with tf.name_scope('cgx_scope'):
a = tf.Variable(1, name='my_a')

with tf.name_scope('cgx_scope'):
b = tf.Variable(2, name='my_b')

c = tf.add(a, b, name='my_add')

print('a.name = ' + a.name)
print('b.name = ' + b.name)

# out:
'''
a.name = cgx_scope/my_a:0
b.name = cgx_scope_1/my_b:0
'''

(1)指定了“cgx_scope”命名区域,并在其中定义变量a;
(2)又指定了相同名称的“cgx_scope”命名区域,并在其中定义变量b;
(3)输出a.name = cgx_scope/my_a:0和b.name = cgx_scope_1/my_b:0,可见b.name已经自动加了“_1”,这是tensorflow的特点,自动检测是否重复,有重复就自动增加数字作为标记

3. tf.shape()

1
2
3
4
5
tf.shape(
input,
name=None,
out_type=tf.int32
)
  • 将矩阵的维度输出为一个维度矩阵
1
2
3
4
5
6
7
8
9
10
11
12
import tensorflow as tf
import numpy as np

A = np.array([[[1, 1, 1], [2, 2, 2]],
[[3, 3, 3], [4, 4, 4]],
[[5, 5, 5], [6, 6, 6]]])

t = tf.shape(A)
with tf.Session() as sess:
print(sess.run(t))

# out: [3 2 3]

参数

  • input:张量或稀疏张量
  • name:op 的名字,用于tensorboard中
  • out_type:默认为tf.int32

返回值

  • 返回out_type类型的张量

4. tf.reshape()

1
tf.reshape(tensor,shape,name=None)

参数

  • tensor:输入张量

  • shape:列表形式,可以存在-1

    -1 代表的含义是不用我们自己指定这一维的大小,函数会自动计算,但列表中只能存在一个-1

  • name:命名

输出

将tensor变换为参数shape的形式

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
a = np.array([0, 1, 2, 3, 4, 5, 6, 7])
print('a = ', a)

b = a.reshape((2, 4))
print('b = ', b)

c = a.reshape((2, 2, 2))
print('c = ', c)

# out:
'''
a = [0 1 2 3 4 5 6 7]
b = [[0 1 2 3]
[4 5 6 7]]
c = [[[0 1]
[2 3]]

[[4 5]
[6 7]]]

'''

5. tf.control_dependencies()

在有些机器学习程序中我们想要指定某些操作执行的依赖关系,这时我们可以使用tf.control_dependencies()来实现。

control_dependencies(control_inputs)返回一个控制依赖的上下文管理器,使用with关键字可以让在这个上下文环境中的操作都在control_inputs 执行。

1
2
3
4
with g.control_dependencies([a, b, c]):
# `d` and `e` will only run after `a`, `b`, and `c` have executed.
d = ...
e = ...

可以嵌套control_dependencies 使用

1
2
3
4
with g.control_dependencies([a, b]):
# Ops constructed here run after `a` and `b`.
with g.control_dependencies([c, d]):
# Ops constructed here run after `a`, `b`, `c`, and `d`.

可以传入None 来消除依赖:

1
2
3
4
5
6
7
with g.control_dependencies([a, b]):
# Ops constructed here run after `a` and `b`.
with g.control_dependencies(None):
# Ops constructed here run normally, not waiting for either `a` or `b`.
with g.control_dependencies([c, d]):
# Ops constructed here run after `c` and `d`, also not waiting
# for either `a` or `b`.

注意
控制依赖只对那些在上下文环境中建立的操作有效,仅仅在context中使用一个操作或张量是没用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# WRONG
def my_func(pred, tensor):
t = tf.matmul(tensor, tensor)
with tf.control_dependencies([pred]):
# The matmul op is created outside the context, so no control
# dependency will be added.
return t

# RIGHT
def my_func(pred, tensor):
with tf.control_dependencies([pred]):
# The matmul op is created in the context, so a control dependency
# will be added.
return tf.matmul(tensor, tensor)

例子:
在训练模型时我们每步训练可能要执行两种操作,op a, b 这时我们就可以使用如下代码:

1
2
3
4
5
6
with tf.control_dependencies([a, b]):
c= tf.no_op(name='train')#tf.no_op;什么也不做
sess.run(c)
'''
c= tf.no_op提供一个什么都不做的节点,该节点属于整个执行的流程图,但是操作a和操作b不是流程图中的一部分,但是为了确保操作a和操作b在某一个环节(此时可能是个未知环节)之前执行,所以提供一个什么都不做的环节c(前面称为节点),确保操作a和操作b在c之前能够完成。而环节c可以插入流程图中。在整个流程图运行起来时,当运行到c时,就确保a,b操作先执行。 我只是根据官方文档以及常用用法猜测,不一定对。
'''

在这样简单的要求下,可以将上面代码替换为:

1
2
c= tf.group([a, b])
sess.run(c)

set_shape()与reshape()

set_shape() 方法更新张量对象的静态形状,通常用于在无法直接推断时提供其他形状信息。它不会改变张量的动态形状

reshape()操作创建一个具有不同动态形状的新张量

tf.image.resize_images()

改变图片尺寸的大小

1
2
3
4
5
6
7
8
9
10
 img_resized = tf.image.resize_images(image_data, [300, 300], method=0)
# 第一个参数为袁术图像的大小
# 第二三个分别为调整后图像的大小
# method参数给出了调整图像大小的方向
'''
method = 0, 双线性插值法
method = 1, 最近邻居法
method = 2, 双三次插值法
method = 3, 面积插值法
'''

xreadline() 与 readlines()

xreadlines返回的是一个生成器类型

readlines()返回的是一个列表

但是使用时是相同的

os.path.join()

连接两个或更多的路径名组件

  • 如果各组件名首字母不包含‘/’,则函数会自动加上
  • 如果有一个组件是一个绝对路径,则在它之前的所有组件均会被舍弃
  • 如果最后䘝组件为空,则生成的路径以一个‘/’分隔符结尾

tf.train.slice_input_producer()

是一个tensor生成器,作用是按照设定,每次从一个tensor列表中按顺序或者随机抽取出一个tensor放入文件名队列。

1
slice_input_producer(tensor_list, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None)

tf.read_file() & tf.image.decode_jpeg()处理图片

1
2
file_contents = tf.read_file(filename)
image = tf.image.decode_png(file_contents) # 解码png格式

os.path.splitext()

os.path.splitext(“文件路径”)分离文件名与扩展名;默认返回(frame,fextension)元组,可做分片操作

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
import os
path_01='D:/User/wgy/workplace/data/notMNIST_large.tar.gar'
path_02='D:/User/wgy/workplace/data/notMNIST_large'
root_01=os.path.splitext(path_01)
root_02=os.path.splitext(path_02)
print(root_01)
print(root_02)

# out:
'''
('D:/User/wgy/workplace/data/notMNIST_large.tar', '.gar')
('D:/User/wgy/workplace/data/notMNIST_large', '')
'''

tf.concat()

tf.concat([tensor1, tensor2, tensor3, ...], axis)

1
2
3
4
5
6
t1 = [[1, 2, 3], [4, 5, 6]]  
t2 = [[7, 8, 9], [10, 11, 12]]
tf.concat([t1, t2], 0) # [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]
tf.concat([t1, t2], 1) # [[1, 2, 3, 7, 8, 9], [4, 5, 6, 10, 11, 12]]
# axis = 0, 代表在第0个维度拼接
# axis = 1, 代表在第1个维度拼接

对于一个二维矩阵,第0个维度代表最外层方括号所框下的子集,第一个维度代表内部方括号所框下的子集。维度越高,括号越小

对于[ [ ], [ ]]和[[ ], [ ]],低维拼接等于拿掉最外面括号,高维拼接是拿掉里面的括号(保证其他维度不变)。

注意:tf.concat()拼接的张量只会改变一个维度,其他维度是保存不变的。

比如两个shape为[2,3]的矩阵拼接,要么通过axis=0变成[4,3],要么通过axis=1变成[2,6]。改变的维度索引对应axis的值。

tf.contrib.layers.batch_norm()

tf.contrib.layers.conv2d()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
tf.contrib.layers.conv2d(
inputs, # [batch_size] + input_spatial_shape + [in_channels]如果data_format不以“NC”(默认值)[batch_size, in_channels] + input_spatial_shape开头,或者 data_format以“NC”开头,则为形状等级N + 2的张量。
num_outputs, # 整数,输出过滤器的数量。
kernel_size, # N个正整数的序列,指定过滤器的空间维度。可以是单个整数,以指定所有空间维度的相同值。
stride=1,
padding='SAME',
data_format=None,
rate=1,
activation_fn=tf.nn.relu,
normalizer_fn=None, # 使用标准化功能代替biases。如果 normalizer_fn提供biases_initializer, biases_regularizer则忽略并且biases不创建也不添加。没有规范化器功能,默认设置为“无”
normalizer_params=None, # 规范化函数参数。
weights_initializer=initializers.xavier_initializer(), # 权重的初始化程序。
weights_regularizer=None, # 可选的权重正则化器。
biases_initializer=tf.zeros_initializer(), # 偏移量的初始化程序。如果没有跳过偏移量。
biases_regularizer=None, # 偏移量的可选正则化器。
reuse=None, # 是否应重用图层及其变量。必须给出能够重用层范围的能力
variables_collections=None, # 所有变量的集合的可选列表或包含每个变量的不同集合列表的字典。
outputs_collections=None, # 用于添加输出的集合。
trainable=True, # 如果True还将变量添加到图表集合中 GraphKeys.TRAINABLE_VARIABLES(请参阅tf.Variable)。
scope=None # 可选范围variable_scope。
)

tf.contrib.layers.variance_scaling_initializer()

1
2
3
4
5
6
7
variance_scaling_initializer(
factor=2.0,
mode='FAN_IN',
uniform=False,
seed=None,
dtype=tf.float32
)

方差缩放初始化

这种初始化方法比常规高斯分布初始化、阶段高斯分布初始化及Xavier初始化的泛华/缩放性能更好。粗略地说,方差缩放初始化根据每一层输入或输出的数量(在 TensorFlow 中默认为输入的数量)来调整初始随机权重的方差,从而帮助信号在不需要其他技巧(如梯度裁剪或批归一化)的情况下在网络中更深入地传播。

tf.constant_initializer()

初始化为常数,这个非常有用,通常偏置项就是用它初始化的。

由它衍生出的两个初始化方法:

  • tf.zeros_initializer(), 也可以简写为tf.Zeros()
  • tf.ones_initializer(), 也可以简写为tf.Ones()

tf.contrib.layers.convolution()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def convolution(inputs,
num_outputs,
kernel_size,
stride=1,
padding='SAME',
data_format=None,
rate=1,
activation_fn=nn.relu,
normalizer_fn=None,
normalizer_params=None,
weights_initializer=initializers.xavier_initializer(),
weights_regularizer=None,
biases_initializer=init_ops.zeros_initializer(),
biases_regularizer=None,
reuse=None,
variables_collections=None,
outputs_collections=None,
trainable=True,
scope=None):

x.get_shape().as_list()

x.get_shape(),只有tensor才可以使用这种方法,返回的是一个元组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
a_array = np.array([[1, 2, 3], [4, 5, 6]])
b_list = [[1, 2, 3], [3, 4, 5]]
c_tensor = tf.constant([[1, 2, 3], [4, 5, 6]])

print(c_tensor.get_shape())
print(c_tensor.get_shape().as_list())

with tf.Session() as sess:
print(sess.run(tf.shape(a_array)))
print(sess.run(tf.shape(b_list)))
print(sess.run(tf.shape(c_tensor)))

# out:
'''
(2, 3)
[2, 3]
[2 3]
[2 3]
[2 3]
'''

只能用于tensor来返回shape,但是是一个元组,需要通过as_list()的操作转换成list.

tf.image.rgb_to_grayscale()

1
2
3
4
5
tf.image.rgb_to_grayscale(
images, # 要转换的RGB张量,最后一个维度的大小必须为3,并且应该包含RGB值
name=None # 操作的名称(可选)
)
# 返回:该函数返回转换后的灰度图像

将一个或多个图像从RGB转化为灰度

输出与images具有相同DType和等级的张量,最后一个维度大小为1,包含像素的灰度值。