1. 元类
首先理解python中的类,用class修饰的都可以叫做类
1 | class Class(): |
我们平时用的类都是实例化以后的类,可以在任何时候动态的创建类,通常情况我们都是这样c=Class(),python解释器会将它认为是创建类,可是解释器本身是如何创建类的,答案是利用type
type平时我们可能认为是查看对象的类型,例如
1 | print(type(c)) |
所以,Class的类型是type,我们可以用type直接生成一个类
type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
1 | Class_type = type('Class_type', (), {'a': 1, 'b': 2}) |
元类:就是用来创建这些类(对象)的类,元类就是类的类
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 | import abc |
你可以实现这两个虚方法,也可以不实现
这样在Base的子类中就必须实现func_a,func_b2个函数,否则就会报错
1 | class Sub(Base): |
如果还想调用虚类的方法用super
1 | def func_b(self,data,out): |
还有一种方法是,注册虚子类
1 | class Register(object): |
这样调用issubclass(), issubinstance()进行判断时仍然返回真值
2. tensorflow中的“tf.name_scope()”有什么用?
2.1. tf.name_scope()命名空间的实际作用
(1)在某个tf.name_scope()指定的区域中定义的所有对象及各种操作,他们的“name”属性上会增加该命名区的区域名,用以区别对象属于哪个区域;
(2)将不同的对象及操作放在由tf.name_scope()指定的区域中,便于在tensorboard中展示清晰的逻辑关系图,这点在复杂关系图中特别重要。
例如:
1 | import tensorflow as tf; |
1 | # 输出结果 |
从输出结果可以看出,在tf.name_scope()下的所有对象和操作,其name属性前都加了cgx_name_scope,用以表示这些内容全在其范围下。
下图展示了两种情况的tensorboard差异,差别一目了然。
2.2. name_scope()只决定“对象”属于哪个范围,并不会对“对象”的“作用域”产生任何影响。
tf.name_scope()只是规定了对象和操作属于哪个区域,但这并不意味着他们的作用域也只限于该区域(with的这种写法很容易让人产生这种误会),不要将其和“全局变量、局部变量”的概念搞混淆,两者完全不是一回事。在name_scope中定义的对象,从被定义的位置开始,直到后来某个地方对该对象重新定义,中间任何地方都可以使用该对象。本质上name_scope只对对象的name属性进行圈定,并不会对其作用域产生任何影响。这就好比甲、乙、丙、丁属于陈家,这里“陈家”就是一个name_scope划定的区域,虽然他们只属于陈家,但他们依然可以去全世界的任何地方,并不会只将他们限制在陈家范围。
例如:
1 | with tf.name_scope('cgx_1'): |
(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 | with tf.name_scope('cgx_scope'): |
(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 | tf.shape( |
- 将矩阵的维度输出为一个维度矩阵
1 | import tensorflow as tf |
参数
- 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 | a = np.array([0, 1, 2, 3, 4, 5, 6, 7]) |
5. tf.control_dependencies()
在有些机器学习程序中我们想要指定某些操作执行的依赖关系,这时我们可以使用tf.control_dependencies()来实现。
control_dependencies(control_inputs)返回一个控制依赖的上下文管理器,使用with关键字可以让在这个上下文环境中的操作都在control_inputs 执行。
1 | with g.control_dependencies([a, b, c]): |
可以嵌套control_dependencies
使用
1 | with g.control_dependencies([a, b]): |
可以传入None
来消除依赖:
1 | with g.control_dependencies([a, b]): |
注意:
控制依赖只对那些在上下文环境中建立的操作有效,仅仅在context中使用一个操作或张量是没用的
1 | # WRONG |
例子:
在训练模型时我们每步训练可能要执行两种操作,op a, b
这时我们就可以使用如下代码:
1 | with tf.control_dependencies([a, b]): |
在这样简单的要求下,可以将上面代码替换为:
1 | c= tf.group([a, b]) |
set_shape()与reshape()
set_shape() 方法更新张量对象的静态形状,通常用于在无法直接推断时提供其他形状信息。它不会改变张量的动态形状
reshape()操作创建一个具有不同动态形状的新张量
tf.image.resize_images()
改变图片尺寸的大小
1 | img_resized = tf.image.resize_images(image_data, [300, 300], method=0) |
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 | file_contents = tf.read_file(filename) |
os.path.splitext()
os.path.splitext(“文件路径”)
分离文件名与扩展名;默认返回(frame,fextension)
元组,可做分片操作
例子:
1 | import os |
tf.concat()
tf.concat([tensor1, tensor2, tensor3, ...], axis)
1 | t1 = [[1, 2, 3], [4, 5, 6]] |
对于一个二维矩阵,第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 | tf.contrib.layers.conv2d( |
tf.contrib.layers.variance_scaling_initializer()
1 | variance_scaling_initializer( |
方差缩放初始化
这种初始化方法比常规高斯分布初始化、阶段高斯分布初始化及Xavier初始化的泛华/缩放性能更好。粗略地说,方差缩放初始化根据每一层输入或输出的数量(在 TensorFlow 中默认为输入的数量)来调整初始随机权重的方差,从而帮助信号在不需要其他技巧(如梯度裁剪或批归一化)的情况下在网络中更深入地传播。
tf.constant_initializer()
初始化为常数,这个非常有用,通常偏置项就是用它初始化的。
由它衍生出的两个初始化方法:
- tf.zeros_initializer(), 也可以简写为tf.Zeros()
- tf.ones_initializer(), 也可以简写为tf.Ones()
tf.contrib.layers.convolution()
1 | def convolution(inputs, |
x.get_shape().as_list()
x.get_shape(),只有tensor才可以使用这种方法,返回的是一个元组
1 | a_array = np.array([[1, 2, 3], [4, 5, 6]]) |
只能用于tensor来返回shape,但是是一个元组,需要通过as_list()的操作转换成list.
tf.image.rgb_to_grayscale()
1 | tf.image.rgb_to_grayscale( |
将一个或多个图像从RGB转化为灰度
输出与images具有相同DType和等级的张量,最后一个维度大小为1,包含像素的灰度值。