TensorFlow2

业界主流开发框架

1. TensorFlow2 基础

1.1 tensor 介绍

TensorFlow 中,tensor 通常分为:常量 tensor 与变量 tensor:

  • 常量 tensor 定义后值和维度不可变,变量定义后值可变而维度不可变。

  • 在神经网络中,变量 tensor 一般可作为储存权重和其他信息的矩阵,是可训练的数据类型。而常量tensor 可作为储存超参数或其他结构信息的变量

1.2 创建常量 tensor

常量 tensor 的创建方式比较多,常见的有一下几种方式:

  • l tf.constant():创建常量 tensor;

  • l tf.zeros(), tf.zeros_like(), tf.ones(),tf.ones_like(): 创建全零或者全一的常量 tensor;

  • l tf.fill(): 创建自定义数值的 tensor;

  • l tf.random: 创建已知分布的 tensor;

  • l 从 numpy,list 对象创建,再利用 tf.convert_to_tensor 转换为类型。

步骤 1 tf.constant()

tf.constant(value, dtype=None, shape=None, name='Const', verify_shape=False)
  • l value:值;

  • l dtype:数据类型;

  • l shape:张量形状;

  • l name:常量名称;

  • l verify_shape:布尔值,用于验证值的形状,默认 False。verify_shape 为 True 的话表示检查 value 的形状与 shape 是否相符,如果不符会报错。

代码:

import tensorflow as tf

const_a = tf.constant([[1, 2, 3, 4]], shape=[2, 2], dtype=tf.float32)  # 创建 2x2 矩阵,值 1,2,3,4 
const_a

输出:
<tf.Tensor: id=2, shape=(2, 2), dtype=float32, numpy=
array([[1., 2.],
       [3., 4.]], dtype=float32)>

代码:

# 查看常见属性
print("常量const_a 的数值为:", const_a.numpy())
print("常量const_a 的数据类型为:", const_a.dtype)
print("常量const_a 的形状为:", const_a.shape)
print("常量const_a 将被产生的设备名称为:", const_a.device)

输出:
常量const_a 的数值为: [[1. 2.]
 [3. 4.]]
常量const_a 的数据类型为: <dtype: 'float32'>
常量const_a 的形状为: (2, 2)
常量const_a 将被产生的设备名称为: /job:localhost/replica:0/task:0/device:CPU:0 

步骤 2 tf.zeros(), tf.zeros_like(), tf.ones(),tf.ones_like()

因为 tf.ones(),tf.ones_like()与 tf.zeros(),tf.zeros_like()的用法相似,因此下面只演示前者的使用方法。

创建一个值为 0 的常量。

tf.zeros(shape, dtype=tf.float32, name=None):
  • l shape:张量形状;

  • l dtype:类型;

  • l name:名称。

代码:

zeros_b = tf.zeros(shape=[2, 3], dtype=tf.int32)  # 创建 2x3 矩阵,元素值均为 0

根据输入张量创建一个值为 0 的张量,形状和输入张量相同。

tf.zeros_like(input_tensor, dtype=None, name=None, optimize=True)
  • l input_tensor:张量;

  • l dtype:类型;

  • l name:名称;

  • l optimize:优化。

代码:

zeros_like_c = tf.zeros_like(const_a)
# 查看生成数据
zeros_like_c.numpy()

输出:
array([[0., 0.],
       [0., 0.]], dtype=float32)

步骤 3 tf.fill()

创建一个张量,用一个具体值充满张量。

tf.fill(dims, value, name=None)
  • l dims:张量形状,同上述 shape;
  • l vlaue:张量数值;
  • l name:名称。

代码:

fill_d = tf.fill([3, 3], 8)  # 2x3 矩阵,元素值均为为 8  
# 查看数据
fill_d.numpy()

输出:
array([[8, 8, 8],
       [8, 8, 8],
       [8, 8, 8]], dtype=int32)

步骤 4 tf.random

用于产生具体分布的张量。该模块中常用的方法包括:tf.random.uniform(),tf.random.normal()和 tf.random.shuffle()等。下面演示 tf.random.normal()的用法。

创建一个符合正态分布的张量。

tf.random.normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32,seed=None, name=None):
  • l shape:数据形状;
  • l mean:高斯分布均值;
  • l stddev:高斯分布标准差;
  • l dtype:数据类型;
  • l seed:随机种子
  • l name:名称。

代码:

random_e = tf.random.normal([5, 5], mean=0, stddev=1.0, seed=1)
# 查看创建数据
random_e.numpy()

输出:
array([[-0.8113182 ,  1.4845988 ,  0.06532937, -2.4427042 ,  0.0992484 ],
       [ 0.5912243 ,  0.59282297, -2.1229296 , -0.72289723, -0.05627038],
       [ 0.6435448 , -0.26432407,  1.8566332 ,  0.5678417 , -0.3828359 ],
       [-1.4853433 ,  1.2617711 , -0.02530608, -0.2646297 ,  1.5328138 ],
       [-1.7429771 , -0.43789294, -0.56601   ,  0.32066926,  1.132831  ]],
      dtype=float32)

步骤 5 从 numpy,list 对象创建,再利用 tf.convert_to_tensor 转换为类型。

将给定制转换为张量。可利用这个函数将 python 的数据类型转换成 TensorFlow 可用的 tensor 数据类型。

tf.convert_to_tensor(value,dtype=None,dtype_hint=None,name=None):
  • l value:需转换数值;
  • l dtype:张量数据类型;
  • l dtype_hint:返回张量的可选元素类型,当 dtype 为 None 时使用。在某些情况下,调用者在tf.convert_to_tensor 时可能没有考虑到 dtype,因此 dtype_hint 可以用作为首选项。

代码:

# 创建一个列表
list_f = [1, 2, 3, 4, 5, 6]
# 查看数据类型
type(list_f)

输出:
list

代码:

tensor_f = tf.convert_to_tensor(list_f, dtype=tf.float32)
tensor_f

输出:
<tf.Tensor: id=16, shape=(6,), dtype=float32, numpy=array([1., 2., 3., 4., 5., 6.], dtype=float32)>

1.3 创建变量 tensor

TensorFlow 中,变量通过 tf.Variable 类进行操作。tf.Variable 表示张量,其值可以通过在其上运行算术运算更改。可读取和修改变量值。

代码:

# 创建变量,只需提供初始值
var_1 = tf.Variable(tf.ones([2, 3]))
var_1

输出:
<tf.Variable 'Variable:0' shape=(2, 3) dtype=float32, numpy=
array([[1., 1., 1.],
       [1., 1., 1.]], dtype=float32)>

代码:

# 变量数值读取
print("变量 var_1 的数值:", var_1.read_value())
# 变量赋值
var_value_1 = [[1, 2, 3], [4, 5, 6]]
var_1.assign(var_value_1)
print("变量 var_1 赋值后的数值:", var_1.read_value())

输出:
变量 var_1 的数值: tf.Tensor(
[[1. 1. 1.]
 [1. 1. 1.]], shape=(2, 3), dtype=float32)
变量 var_1 赋值后的数值: tf.Tensor(
[[1. 2. 3.]
 [4. 5. 6.]], shape=(2, 3), dtype=float32)

代码:

# 变量加法
var_1.assign_add(tf.ones([2, 3]))
var_1

输出:
<tf.Variable 'Variable:0' shape=(2, 3) dtype=float32, numpy=
array([[2., 3., 4.],
       [5., 6., 7.]], dtype=float32)>

1.4 tensor 切片与索引

1.4.1 切片

切片的方式主要有:

  • l [start: end]:从 tensor 的开始位置到结束位置的数据切片;
  • l [start :end :step]或者[::step]:从 tensor 的开始位置到结束位置每隔 step 的数据切片;
  • l [::-1]:负数表示倒序切片;
  • l ‘…’:任意长。

代码:

# 创建一个 4 维tensor。tensor 包含 4 张图片,每张图片的大小为 100*100*3 
tensor_h = tf.random.normal([4, 100, 100, 3])
tensor_h

输出:
<tf.Tensor: id=40, shape=(4, 100, 100, 3), dtype=float32, numpy=
array([[[[-1.2748867 ,  0.61186457, -1.3725697 ],
         [-0.32428452,  0.2527103 ,  0.69562674],
         [ 0.55394286,  1.0515776 ,  1.6650721 ],
         ...,
         [ 0.5942018 , -0.3625194 ,  1.3041826 ],
         [ 0.81127596,  3.019384  ,  0.22381118],
         [ 0.9802666 ,  0.52870286,  1.8634393 ]],

        [[ 0.05032753, -0.45593497,  1.006573  ],
         [-0.97207814,  0.65104896, -0.5570135 ],
         [-0.90569866,  0.1540982 , -0.04971581],
         ...,
       
# 取出第一张图片
tensor_h[0, :, :, :]

输出:
<tf.Tensor: id=44, shape=(100, 100, 3), dtype=float32, numpy=
array([[[-1.2748867 ,  0.61186457, -1.3725697 ],
        [-0.32428452,  0.2527103 ,  0.69562674],
        [ 0.55394286,  1.0515776 ,  1.6650721 ],
        ...,
        [ 0.5942018 , -0.3625194 ,  1.3041826 ],
        [ 0.81127596,  3.019384  ,  0.22381118],
        [ 0.9802666 ,  0.52870286,  1.8634393 ]],

# 每两张图片取出一张的切片
tensor_h[::2, ...]

输出:
<tf.Tensor: id=48, shape=(2, 100, 100, 3), dtype=float32, numpy=
array([[[[-1.2748867 ,  0.61186457, -1.3725697 ],
         [-0.32428452,  0.2527103 ,  0.69562674],
         [ 0.55394286,  1.0515776 ,  1.6650721 ],
         ...,
         [ 0.5942018 , -0.3625194 ,  1.3041826 ],
         [ 0.81127596,  3.019384  ,  0.22381118],
         [ 0.9802666 ,  0.52870286,  1.8634393 ]],

#倒序切片
tensor_h[::-1]

输出:
<tf.Tensor: id=52, shape=(4, 100, 100, 3), dtype=float32, numpy=
array([[[[ 0.5161808 ,  0.23140958, -0.43209437],
         [ 0.00776087,  1.3823358 ,  0.6929437 ],
         [ 1.5621277 , -0.04508307, -0.1968164 ],
         ...,
         [-1.2630497 , -1.0238761 , -1.1867431 ],
         [-0.24941015, -0.11402028,  0.27717665],
         [ 0.8502298 , -0.30275303, -0.85351145]],

1.4.2 索引

索引的基本格式:a[d1] [d2] [d3]

代码:

# 取出第一张图片第二个通道中在[20,40]位置的像素点 
tensor_h[0][19][39][1]

输出:
<tf.Tensor: id=68, shape=(), dtype=float32, numpy=-1.614223>

在某一维度进行索引。

tf.gather(params, indices,axis=None)
  • l params:输入张量;
  • l indices:取出数据的索引;
  • l axis:所取数据所在维度。

代码:

# 取出tensor_h([4,100,100,3])中,第 1,2,4 张图像。 
indices = [0, 1, 3]
tf.gather(tensor_h, axis=0, indices=indices)

输出:
<tf.Tensor: id=83, shape=(3, 100, 100, 3), dtype=float32, numpy=
array([[[[-0.01252888, -0.46393958,  0.60526013],
         [-0.01322627, -0.24589129, -1.7498221 ],
         [ 0.15702602, -0.84582156, -0.01484271],
         ...,
         [-1.4868114 ,  2.6454847 ,  0.84849346],
         [-0.15565126,  0.5482018 , -0.96524566],
         [ 1.1514304 , -0.07815795,  1.7596667 ]],

tf.gather_nd 允许在多维上进行索引:

tf.gather_nd(params,indices)
  • l params:输入张量;
  • l indices:取出数据的索引,一般为多维列表。

代码:

# 取出tensot_h([4,100,100,3])中,第一张图像第一个维度中[1,1]的像素点;第二张图片第一像素点中[2,2]的像素点 
indices = [[0, 1, 1, 0], [1, 2, 2, 0]]
tf.gather_nd(tensor_h, indices=indices)

输出:
<tf.Tensor: id=85, shape=(2,), dtype=float32, numpy=array([-0.38638964, -1.1200645 ], dtype=float32)>

1.5 张量的维度变化

1.5.1 维度查看

代码:

const_d_1 = tf.constant([[1, 2, 3, 4]], shape=[2, 2], dtype=tf.float32)  # 查看维度常用的三种方式
print(const_d_1.shape)
print(const_d_1.get_shape())
print(tf.shape(const_d_1))  # 输出为张量,其数值表示的是所查看张量维度大小

输出:
(2, 2)
(2, 2)
tf.Tensor([2 2], shape=(2,), dtype=int32)

可以看出.shape 和.get_shape()都是返回 TensorShape 类型对象,而 tf.shape(x)返回的是 Tensor 类型对象。

1.5.2 维度重组

tf.reshape(tensor,shape,name=None)
  • l tensor:输入张量;
  • l shape:重组后张量的维度。

代码:

reshape_1 = tf.constant([[1, 2, 3], [4, 5, 6]])
print(reshape_1)
tf.reshape(reshape_1, (3, 2))

输出:
tf.Tensor(
[[1 2 3]
 [4 5 6]], shape=(2, 3), dtype=int32)
<tf.Tensor: id=92, shape=(3, 2), dtype=int32, numpy=
array([[1, 2],
       [3, 4],
       [5, 6]], dtype=int32)>

1.5.3 维度增加

tf.expand_dims(input,axis,name=None)
  • l input:输入张量;
  • l axis:在第 axis 维度后增加一个维度。在输入 D 尺寸的情况下,轴必须在[-(D + 1),D](含)范围内。负数代表倒序。

代码:

# 生成一个大小为 100*100*3 的张量来表示一张尺寸为 100*100 的三通道彩色图片
expand_sample_1 = tf.random.normal([100, 100, 3], seed=1)
print("原始数据尺寸:", expand_sample_1.shape)
print("在第一个维度前增加一个维度(axis=0):", tf.expand_dims(expand_sample_1, axis=0).shape)
print("在第二个维度前增加一个维度(axis=1):", tf.expand_dims(expand_sample_1, axis=1).shape)
print("在最后一个维度后增加一个维度(axis=-1):", tf.expand_dims(expand_sample_1, axis=-1).shape)

输出:
原始数据尺寸: (100, 100, 3)
在第一个维度前增加一个维度(axis=0)(1, 100, 100, 3)
在第二个维度前增加一个维度(axis=1)(100, 1, 100, 3)
在最后一个维度后增加一个维度(axis=-1)(100, 100, 3, 1)

1.5.4 维度减少

tf.squeeze(input,axis=None,name=None)
  • l input:输入张量;
  • l axis:axis=1,表示要删掉的为 1 的维度。

代码:

# 生成一个大小为 100*100*3 的张量来表示一张尺寸为 100*100 的三通道彩色图片
squeeze_sample_1 = tf.random.normal([1, 100, 100, 3])
print("原始数据尺寸:", squeeze_sample_1.shape)
squeezed_sample_1 = tf.squeeze(expand_sample_1)
print("维度压缩后的数据尺寸:", squeezed_sample_1.shape)

输出:
原始数据尺寸: (1, 100, 100, 3)
维度压缩后的数据尺寸: (100, 100, 3)

1.5.5 转置

tf.transpose(a,perm=None,conjugate=False,name='transpose')
  • l a:输入张量;
  • l perm:张量的尺寸排列;一般用于高维数组的转置。
  • l conjugate:表示复数转置;
  • name:名称。
# 低维的转置问题比较简单,输入需转置张量调用 tf.transpose 
trans_sample_1 = tf.constant([1, 2, 3, 4, 5, 6], shape=[2, 3])
print("原始数据尺寸:", trans_sample_1.shape)
transposed_sample_1 = tf.transpose(trans_sample_1)
print("转置后数据尺寸:", transposed_sample_1.shape)

输出:
原始数据尺寸: (2, 3)
转置后数据尺寸: (3, 2)

代码:

'''
高维数据转置需要用到 perm 参数,perm 代表输入张量的维度排列。
对于一个三维张量来说,其原始的维度排列为[0, 1, 2](perm)分别代表高维数据的长宽高。通过改变perm
中数值的排列,可以对数据的对应维度进行转置
'''
# 生成一个大小为$*100*200*3 的张量来表示 4 张尺寸为 100*200 的三通道彩色图片
trans_sample_2 = tf.random.normal([4, 100, 200, 3])
print("原始数据尺寸:", trans_sample_2.shape)
# 对 4 张图像的长宽进行对调。原始 perm 为[0,1,2,3],现变为[0,2,1,3]
transposed_sample_2 = tf.transpose(trans_sample_2, [0, 2, 1, 3])
print("转置后数据尺寸:", transposed_sample_2.shape)

输出:
原始数据尺寸: (4, 100, 200, 3)
转置后数据尺寸: (4, 200, 100, 3)

1.5.6 广播(broadcast_to)

利用把 broadcast_to 可以将小维度推广到大维度。

tf.broadcast_to(input,shape,name=None)
  • l input:输入张量;
  • l shape:输出张量的尺寸。
broadcast_sample_1 = tf.constant([1, 2, 3, 4, 5, 6])
print("原始数据:", broadcast_sample_1.numpy())
broadcasted_sample_1 = tf.broadcast_to(broadcast_sample_1, shape=[4, 6])
print("广播后数据:", broadcasted_sample_1.numpy())

输出:
原始数据: [1 2 3 4 5 6]
广播后数据: [[1 2 3 4 5 6]
 [1 2 3 4 5 6]
 [1 2 3 4 5 6]
 [1 2 3 4 5 6]]

代码:

# 运算时,当两个数组的形状不同时,与 numpyy 一样,TensorFlow 将自动触发广播机制。 
a = tf.constant([[0, 0, 0],
                 [10, 10, 10],
                 [20, 20, 20],
                 [30, 30, 30]])
b = tf.constant([1, 2, 3])
print(a + b)

输出:
tf.Tensor(
[[ 1  2  3]
 [11 12 13]
 [21 22 23]
 [31 32 33]], shape=(4, 3), dtype=int32)

1.6 张量的算术运算

1.6.1 算术运算符

算术运算主要包括了:加(tf.add)、减(tf.subtract)、乘(tf.multiply)、除(tf.divide)、取对数

(tf.math.log)和指数(tf.pow)等。 因为调用比较简单,下面只演示一个加法例子。

a = tf.constant([[3, 5], [4, 8]])
b = tf.constant([[1, 6], [2, 9]])
print(tf.add(a, b))

输出:
tf.Tensor(
[[ 4 11]
 [ 6 17]], shape=(2, 2), dtype=int32)

1.6.2 矩阵乘法运算

矩阵乘法运算的实现通过调用 tf.matmul。

代码:

tf.matmul(a, b)

输出:
<tf.Tensor: id=134, shape=(2, 2), dtype=int32, numpy=
array([[13, 63],
       [20, 96]], dtype=int32)>

1.6.3 张量的数据统计

张量的数据统计主要包括:

  • l tf.reduce_min/max/mean():求解最小值最大值和均值函数;
  • l tf.argmax()/tf.argmin():求最大最小值位置;
  • l tf.equal():逐个元素判断两个张量是否相等;
  • l tf.unique():除去张量中的重复元素。
  • l tf.nn.in_top_k(prediction, target, K):用于计算预测值和真是值是否相等,返回一个 bool 类型的张量。

下面演示 tf.argmax()的用法:返回最大值所在的下标

  • l tf.argmax(input,axis):
  • l input:输入张量;
  • l axis:按照 axis 维度,输出最大值。

代码:

argmax_sample_1 = tf.constant([[1, 3, 2], [2, 5, 8], [7, 5, 9]])
print("输入张量:", argmax_sample_1.numpy())
max_sample_1 = tf.argmax(argmax_sample_1, axis=0)
max_sample_2 = tf.argmax(argmax_sample_1, axis=1)
print("按列寻找最大值的位置:", max_sample_1.numpy())
print("按行寻找最大值的位置:", max_sample_2.numpy())

输出:
输入张量: [[1 3 2]
 [2 5 8]
 [7 5 9]]
按列寻找最大值的位置: [2 1 2]
按行寻找最大值的位置: [1 2 2]

1.7 基于维度的算术操作

TensorFlow 中,tf.reduce_*一系列操作等都造成张量维度的减少。这一系列操作都可以对一个张量在维度上的元素进行操作,如按行求平均,求取张量中所有元素的乘积等。

常用的包括:tf.reduce_sum(加法)、tf.reduce_prod(乘法)、tf.reduce_min(最小)、 tf.reduce_max(最大)、tf.reduce_mean(均值)、tf.reduce_all(逻辑和)、tf.reduce_any

(逻辑或)和 tf.reduce_logsumexp(log(sum(exp)))操作)等。

这些操作的使用方法都相似,下面只演示 tf.reduce_sum 的操作案例。计算一个张量的各个维度上元素的总和

tf.reduce_sum(input_tensor, axis=None, keepdims=False,name=None)
  • l input_tensor:输入张量;
  • l axis:指定需要计算的轴,如果不指定,则计算所有元素的均值;
  • l keepdims:是否降维度,设置为 True,输出的结果保持输入 tensor 的形状,设置为 False,输出结果会降低维度;
  • l name:操作名称。
reduce_sample_1 = tf.constant([1, 2, 3, 4, 5, 6], shape=[2, 3])
print("原始数据", reduce_sample_1.numpy())
print("计算张量中所有元素的和(axis=None):", tf.reduce_sum(reduce_sample_1, axis=None).numpy())
print("按列计算,分别计算各列的和(axis=0):", tf.reduce_sum(reduce_sample_1, axis=0).numpy())
print("按行计算,分别计算各列的和(axis=1):", tf.reduce_sum(reduce_sample_1, axis=1).numpy())

输出:
原始数据 [[1 2 3]
 [4 5 6]]
计算张量中所有元素的和(axis=None): 21
按列计算,分别计算各列的和(axis=0): [5 7 9]
按行计算,分别计算各列的和(axis=1): [ 6 15]

1.8 张量的拼接与分割

1.8.1 张量的拼接

TensorFlow 中,张量拼接的操作主要包括:

  • l tf.contact():将向量按指定维连起来,其余维度不变。
  • l tf.stack() :将一组 R 维张量变为 R+1 维张量,拼接前后维度变化。
tf.concat(values, axis, name='concat')
  • l values:输入张量;
  • l axis:指定拼接维度;
  • l name:操作名称。
concat_sample_1 = tf.random.normal([4, 100, 100, 3])
concat_sample_2 = tf.random.normal([40, 100, 100, 3])
print("原始数据的尺寸分别为:", concat_sample_1.shape, concat_sample_2.shape)
concated_sample_1 = tf.concat([concat_sample_1, concat_sample_2], axis=0)
print("拼接后数据的尺寸:", concated_sample_1.shape)

输出:
原始数据的尺寸分别为: (4, 100, 100, 3) (40, 100, 100, 3)
拼接后数据的尺寸: (44, 100, 100, 3)

在原来矩阵基础上增加了一个维度,也是同样的道理,axis 决定维度增加的位置。

tf.stack(values, axis=0, name='stack')
  • l values:输入张量;一组相同形状和数据类型的张量。
  • l axis:指定拼接维度;
  • l name:操作名称。
stack_sample_1 = tf.random.normal([100, 100, 3])
stack_sample_2 = tf.random.normal([100, 100, 3])
print("原始数据的尺寸分别为:", stack_sample_1.shape, stack_sample_2.shape)
# 拼接后维度增加。axis=0,则在第一个维度前增加维度。
stacked_sample_1 = tf.stack([stack_sample_1, stack_sample_2], axis=0)
print("拼接后数据的尺寸:", stacked_sample_1.shape)

输出:
原始数据的尺寸分别为: (100, 100, 3) (100, 100, 3)
拼接后数据的尺寸: (2, 100, 100, 3)

1.8.2 张量的分割

TensorFlow 中,张量分割的操作主要包括:

  • l tf.unstack():将张量按照特定维度分解。
  • l tf.split():将张量按照特定维度划分为指定的分数。

与 tf.unstack()相比,tf.split()更佳灵活。

tf.unstack(value,num=None,axis=0,name='unstack')
  • l value:输入张量;
  • l num:表示输出含有 num 个元素的列表,num 必须和指定维度内元素的个数相等。通常可以忽略不写这个参数。
  • l axis:指明根据数据的哪个维度进行分割;
  • l name:操作名称。
# 按照第一个维度对数据进行分解,分解后的数据以列表形式输出。
tf.unstack(stacked_sample_1, axis=0)

输出:
[<tf.Tensor: id=176, shape=(100, 100, 3), dtype=float32, numpy=
 array([[[-0.6100131 ,  1.1552308 ,  1.9557871 ],
         [ 0.7781739 , -0.97797275,  1.1395042 ],
         [ 1.487099  , -2.3994513 ,  0.6791377 ],
         ...,
         [ 0.00845545, -0.7138963 , -0.40552953],
         [-0.38446733,  0.01316335, -0.6392299 ],
         [-0.523189  , -1.0822618 , -0.7537402 ]],
tf.split(value, num_or_size_splits, axis=0)
  • l value:输入张量;
  • l num_or_size_splits:准备切成几份
  • l axis:指明根据数据的哪个维度进行分割。
  1. 如果 num_or_size_splits 传入的是一个整数,那直接在 axis=D 这个维度上把张量平均切分成几个小张量。

  2. 如果 num_or_size_splits 传入的是一个向量,则在 axis=D 这个维度上把张量按照向量的元素值切分成几个小张量。

import numpy as np

split_sample_1 = tf.random.normal([10, 100, 100, 3])
print("原始数据的尺寸为:", split_sample_1.shape)
splited_sample_1 = tf.split(split_sample_1, num_or_size_splits=5, axis=0)
print("当m_or_size_splits=10,分割后数据的尺寸为:", np.shape(splited_sample_1))
splited_sample_2 = tf.split(split_sample_1, num_or_size_splits=[3, 5, 2], axis=0)
print("当num_or_size_splits=[3,5,2],分割后数据的尺寸分别为:",
      np.shape(splited_sample_2[0]), np.shape(splited_sample_2[1]), np.shape(splited_sample_2[2]))
      
*输出:
原始数据的尺寸为: (10, 100, 100, 3)
当 m_or_size_splits=10,分割后数据的尺寸为: (5, 2, 100, 100, 3)
当 num_or_size_splits=[3,5,2],分割后数据的尺寸分别为: (3, 100, 100, 3) (5, 100, 100, 3) (2, 100, 100, 3)

1.9 张量排序

TensorFlow 中,张量排序的操作主要包括:

  • l tf.sort():按照升序或者降序对张量进行排序,返回排序后的张量。
  • l tf.argsort():按照升序或者降序对张量进行排序,但返回的是索引。
  • l tf.nn.top_k():返回前 k 个最大值。 tf.sort/argsort(input, direction, axis):
  • l input:输入张量;
  • l direction:排列顺序,可为 DESCENDING 降序或者 ASCENDING(升序)。默认为 ASCENDING(升序);
  • l axis:按照 axis 维度进行排序。默认 axis=-1 最后一个维度。

代码:

sort_sample_1 = tf.random.shuffle(tf.range(10))
print("输入张量:", sort_sample_1.numpy())
sorted_sample_1 = tf.sort(sort_sample_1, direction="ASCENDING")
print("生序排列后的张量:", sorted_sample_1.numpy())
sorted_sample_2 = tf.argsort(sort_sample_1, direction="ASCENDING")
print("生序排列后,元素的索引:", sorted_sample_2.numpy())

输出:
输入张量: [4 3 5 0 6 2 8 1 7 9]
生序排列后的张量: [0 1 2 3 4 5 6 7 8 9]
生序排列后,元素的索引: [3 7 5 1 0 2 4 8 6 9]
tf.nn.top_k(input,K,sorted=TRUE):
  • l input:输入张量;

  • l K:需要输出的前 k 个值及其索引。

  • l sorted: sorted=TRUE 表示升序排列;sorted=FALSE 表示降序排列。

返回两个张量:

  • l values:也就是每一行的最大的 k 个数字
  • l indices:这里的下标是在输入的张量的最后一个维度的下标
values, index = tf.nn.top_k(sort_sample_1, 5)
print("输入张量:", sort_sample_1.numpy())
print("升序排列后的前 5 个数值:", values.numpy())
print("升序排列后的前 5 个数值的索引:", index.numpy())

输出:
输入张量: [4 3 5 0 6 2 8 1 7 9]
升序排列后的前 5 个数值: [9 8 7 6 5]
升序排列后的前 5 个数值的索引: [9 6 8 4 2]

1.10 TensorFlow2 Eager Execution 模式

Eager Execution 介绍:

TensorFlow 的 Eager Execution 模式是一种命令式编程(imperative programming),这和原生 Python 是一致的,当你执行某个操作时,可以立即返回结果的。

Graph 模式介绍:

TensorFlow1.0 一直是采用 Graph 模式,即先构建一个计算图,然后需要开启 Session,喂进实际的数据才真正执行得到结果。

Eager Execution 模式下,我们可以更容易 debug 代码,但是代码的执行效率更低。

下面我们在 Eager Execution 和 Graph 模式下,用 TensorFlow 实现简单的乘法,来对比两个模式的区别。

x = tf.ones((2, 2), dtype=tf.dtypes.float32)
y = tf.constant([[1, 2],
                 [3, 4]], dtype=tf.dtypes.float32)
z = tf.matmul(x, y)
print(z)

输出:
tf.Tensor(
[[4. 6.]
 [4. 6.]], shape=(2, 2), dtype=float32)
# 在 TensorFlow 2 版本中使用 1.X 版本的语法;可以使用 2.0 中的 v1 兼容包来沿用 1.x 代码,并在代码中关闭 eager运算。
import tensorflow.compat.v1 as tf

tf.disable_eager_execution()
# 创建graph,定义计算图
a = tf.ones((2, 2), dtype=tf.dtypes.float32)
b = tf.constant([[1, 2],
                 [3, 4]], dtype=tf.dtypes.float32)
c = tf.matmul(a, b)
# 开启绘画,进行运算后,才能取出数据。
with tf.Session() as sess:
    print(sess.run(c))
    
输出:
[[4. 6.]
 [4. 6.]]

首先重启一下 kernel,使得 TensorFlow 恢复到 2.0 版本并打开 eager execution 模式。 Eager Execution 模式的另一个优点是可以使用 Python 原生功能,比如下面的条件判断:

import tensorflow as tf
import numpy as np

thre_1 = tf.random.uniform([], 0, 1)
x = tf.reshape(tf.range(0, 4), [2, 2])
print(thre_1)
if thre_1.numpy() > 0.5:
    y = tf.matmul(x, x)
else:
    y = tf.add(x, x)

输出:
tf.Tensor(0.8043928, shape=(), dtype=float32)

这种动态控制流主要得益于 eager 执行得到 Tensor 可以取出 numpy 值,这避免了使用 Graph 模式下的 tf.cond 和 tf.while 等算子。

1.11 TensorFlow2 AutoGraph

当使用 tf.function 装饰器注释函数时,可以像调用任何其他函数一样调用它。它将被编译成图,这意味着可以获得更高效地在在 GPU 或 TPU 上运行。此时函数变成了一个 TensorFlow 中的 operation。我们可以直接调用函数,输出返回值,但是函数内部是在 graph 模式下执行的,无法直接查看中间变量数值

@tf.function
def simple_nn_layer(w, x, b):
    print(b)

    return tf.nn.relu(tf.matmul(w, x) + b)


w = tf.random.uniform((3, 3))
x = tf.random.uniform((3, 3))
b = tf.constant(0.5, dtype='float32')

simple_nn_layer(w, x, b)


输出:
Tensor("b:0", shape=(), dtype=float32)
<tf.Tensor: id=40, shape=(3, 3), dtype=float32, numpy=
array([[0.5868423, 0.6898268, 0.8843413],
       [0.8371662, 0.847257 , 1.1481965],
       [1.017222 , 1.1640502, 1.46533  ]], dtype=float32)>

通过输出结果可知,无法直接查看函数内部 b 的数值,而返回值可以通过.numpy()查看。通过相同的操作(执行一层 lstm 计算),比较 graph 和 eager execution 模式的性能。

# timeit 测量小段代码的执行时间 
import timeit

# 创建一个卷积层。
CNN_cell = tf.keras.layers.Conv2D(filters=100, kernel_size=2, strides=(1, 1))


# 利用@tf.function,将操作转化为 graph。 @tf.function
def CNN_fn(image):
    return CNN_cell(image)


image = tf.zeros([100, 200, 200, 3])

# 比较两者的执行时间CNN_cell(image) CNN_fn(image)
# 调用timeit.timeit,测量代码执行 10 次的时间
print("eager execution 模式下做一层 CNN 卷积层运算的时间:", timeit.timeit(lambda: CNN_cell(image), number=10))
print("graph 模式下做一层 CNN 卷积层运算的时间:", timeit.timeit(lambda: CNN_fn(image), number=10))


输出:
eager execution 模式下做一层 CNN 卷积层运算的时间: 53.275061096
graph 模式下做一层 CNN 卷积层运算的时间: 47.896033531

通过比较,我们可以发现 graph 模式下代码执行效率要高出许多。因此我们以后,可以多尝试用@tf.function 功能,提高代码运行效率。

2. TensorFlow 2 常用模块介绍

本节将为大家介绍 TensorFlow 2 常用模块,主要包括:

  • l tf.data:实现对数据集的操作;

    包括读取从内存中直接读取数据集、读取 CSV 文件、读取 tfrecord 文件和数据增强等。

  • l tf.image:实现对图像处理的操作;

    包括图像亮度变换、饱和度变换、图像尺寸变换、图像旋转和边缘检测等操作。

  • l tf.gfile:实现对文件的操作;

    包括对文件的读写操作、文件重命名和文件夹操作等。

  • l tf.keras:用于构建和训练深度学习模型的高阶 API;

  • l tf.distributions 等等。

2.1 模型构建

2.1.1 模型堆叠(tf.keras.Sequential)

最常见的模型构建方法是层的堆叠,我们通常会使用 tf.keras.Sequential。

代码:

import tensorflow.keras.layers as layers
import tensorflow as tf

model = tf.keras.Sequential()
model.add(layers.Dense(32, activation='relu'))
model.add(layers.Dense(32, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))

2.1.2 函数式模型构建

函数式模型主要利用 tf.keras.Input 和 tf.keras.Model 构建,比 tf.keras.Sequential 模型要复杂,但是效果很好,可以同时/分阶段输入变量,分阶段输出数据; 你的模型需要多于一个的输出,那么需要选择函数式模型。

模型堆叠(.Sequential)vs 函数式模型(Model):

tf.keras.Sequential 模型是层的简单堆叠,无法表示任意模型。使用 Keras 的函数式模型可以构建复杂的模型拓扑,例如:

  • l 多输入模型;
  • l 多输出模型;
  • l 具有共享层的模型;
  • l 具有非序列数据流的模型(例如,残差连接)。
# 以上一层的输出作为下一层的输入
x = tf.keras.Input(shape=(32,))
h1 = layers.Dense(32, activation='relu')(x)
h2 = layers.Dense(32, activation='relu')(h1)
y = layers.Dense(10, activation='softmax')(h2)
model_sample_2 = tf.keras.models.Model(x, y)

# 打印模型信息
model_sample_2.summary()

输出:
Model: "model"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         [(None, 32)]              0         
_________________________________________________________________
dense_3 (Dense)              (None, 32)                1056      
_________________________________________________________________
dense_4 (Dense)              (None, 32)                1056      
_________________________________________________________________
dense_5 (Dense)              (None, 10)                330       
=================================================================
Total params: 2,442
Trainable params: 2,442
Non-trainable params: 0
_________________________________________________________________

2.1.3 网络层构建(tf.keras.layers)

tf.keras.layers 模块的主要作用为配置神经网络层。其中常用的类包括:

  • l tf.keras.layers.Dense:构建全连接层;
  • l tf.keras.layers.Conv2D:构建 2 维卷积层;
  • l tf.keras.layers.MaxPooling2D/AveragePooling2D:构建最大/平均池化层;
  • l tf.keras.layers.RNN:构建循环神经网络层;
  • l tf.keras.layers.LSTM/tf.keras.layers.LSTMCell:构建 LSTM 网络层/LSTM unit;
  • l tf.keras.layers.GRU/tf.keras.layers.GRUCell:构建 GRU unit/GRU 网络层;
  • l tf.keras.layers.Embedding 嵌入层将正整数(下标)转换为具有固定大小的向量,如 [[4],[20]]->[[0.25,0.1],[0.6,-0.2]]。Embedding 层只能作为模型的第一层;
  • l tf.keras.layers.Dropout:构建 dropout 层等。

下面主要讲解 tf.keras.layers.Dense、 tf.keras.layers.Conv2D、 tf.keras.layers.MaxPooling2D/AveragePooling2D 和 tf.keras.layers.LSTM/tf.keras.layers.LSTMCell。

tf.keras.layers 中主要的网络配置参数如下:

  • l activation:设置层的激活函数。默认情况下,系统不会应用任何激活函数。
  • l kernel_initializer 和 bias_initializer:创建层权重(核和偏置)的初始化方案。默认为 “Glorot uniform” 初始化器。
  • l kernel_regularizer 和 bias_regularizer:应用层权重(核和偏置)的正则化方案,例如 L1 或 L2 正则化。默认情况下,系统不会应用正则化函数。
2.3.1.1.1 tf.keras.layers.Dense

tf.keras.layers.Dense 可配置的参数,主要有:

  • l units: 神经元个数;
  • l activation: 激活函数;
  • l use_bias: 是否使用偏置项。默认为使用;
  • l kernel_initializer: 创建层权重核的初始化方案;
  • l bias_initializer: 创建层权重偏置的初始化方案;
  • l kernel_regularizer: 应用层权重核的正则化方案;
  • l bias_regularizer: 应用层权重偏置的正则化方案;
  • l activity_regularizer:施加在输出上的正则项,为 Regularizer 对象;
  • l kernel_constraint: 施加在权重上的约束项;
  • l bias_constraint: 施加在权重上的约束项。
# 创建包含 32 个神经元的全连接层,其中的激活函数设置为 sigmoid。
# activation 参数可以是函数名称字符串,如'sigmoid';也可以是函数对象,如 tf.sigmoid。 
layers.Dense(32, activation='sigmoid')
layers.Dense(32, activation=tf.sigmoid)

# 设置kernel_initializer 参数
layers.Dense(32, kernel_initializer=tf.keras.initializers.he_normal)
# 设置kernel_regularizer 为L2 正则
layers.Dense(32, kernel_regularizer=tf.keras.regularizers.l2(0.01))

输出:
<tensorflow.python.keras.layers.core.Dense at 0x7fd1299c51d0>
2.3.1.1.1 tf.keras.layers.Conv2D

tf.keras.layers.Conv2D 可配置的参数,主要有:

  • l filters:卷积核的数目(即输出的维度);

  • l kernel_size:卷积核的宽度和长度;

  • l strides:卷积的步长。

  • l padding:补 0 策略。

    padding=“valid”代表只进行有效的卷积,即对边界数据不处理。padding=“same”代表保留边界处的卷积结果,通常会导致输出 shape 与输入 shape 相同;

  • l activation:激活函数;

  • l data_format:数据格式,为“channels_first”或“channels_last”之一。以 128x128 的 RGB 图像为例,“channels_first”应将数据组织为(3,128,128),而“channels_last”应将数据组织为(128,128,3)。该参数的默认值是~/.keras/keras.json 中设置的值,若从未设置过,则为 “channels_last”。

  • l 其他参数还包括:use_bias;kernel_initializer;bias_initializer;kernel_regularizer; bias_regularizer;activity_regularizer;kernel_constraints;bias_constraints。

layers.Conv2D(64, [1, 1], 2, padding='same', activation="relu")

输出:
<tensorflow.python.keras.layers.convolutional.Conv2D at 0x7fd12991b410>
2.3.1.1.1 tf.keras.layers.MaxPooling2D/AveragePooling2D

tf.keras.layers.MaxPooling2D/AveragePooling2D 可配置的参数,主要有:

  • l pool_size:池化 kernel 的大小。如取矩阵(2,2)将使图片在两个维度上均变为原长的一半。为整数意为各个维度值都为该数字。
  • l strides:步长值。
  • l 其他参数还包括:padding;data_format。
layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 1))

输出:
<tensorflow.python.keras.layers.pooling.MaxPooling2D at 0x7fd1299d7ed0>
2.3.1.1.1 tf.keras.layers.LSTM/tf.keras.layers.LSTMCell

tf.keras.layers.LSTM/tf.keras.layers.LSTMCell 可配置的参数,主要有:

  • l units:输出维度;
  • l input_shape (timestep, input_dim),timestep 可以设置为 None,input_dim 为输入数据维度;
  • l activation:激活函数;
  • l recurrent_activation: 为循环步施加的激活函数;
  • l return_sequences:=True 时,返回全部序列;=False 时,返回输出序列中的最后一个 cell 的输出;
  • l return_state: 布尔值。除了输出之外是否返回最后一个状态;
  • l dropout:0~1 之间的浮点数,控制输入线性变换的神经元断开比例;
  • l recurrent_dropout:0~1 之间的浮点数,控制循环状态的线性变换的神经元断开比例。
import numpy as np

inputs = tf.keras.Input(shape=(3, 1))
lstm = layers.LSTM(1, return_sequences=True)(inputs)
model_lstm_1 = tf.keras.models.Model(inputs=inputs, outputs=lstm)

inputs = tf.keras.Input(shape=(3, 1))
lstm = layers.LSTM(1, return_sequences=False)(inputs)
model_lstm_2 = tf.keras.models.Model(inputs=inputs, outputs=lstm)

# t1, t2, t3 序列
data = [[[0.1],
         [0.2],
         [0.3]]]
print(data)
print("当 return_sequences=True 时的输出", model_lstm_1.predict(data))
print("当 return_sequences=False 时的输出", model_lstm_2.predict(data))

**输出:
[[[0.1], [0.2], [0.3]]]
当 return_sequences=True 时的输出 [[[-0.0106758 ] [-0.02711176]
[-0.04583194]]]
当 return_sequences=False 时的输出 [[0.05914127]]

LSTMcell 是 LSTM 层的实现单元。

  • l LSTM 是一个 LSTM 网络层
  • l LSTMCell 是一个单步的计算单元,即一个 LSTM UNIT。
# LSTM
tf.keras.layers.LSTM(16, return_sequences=True)

# LSTMCell
x = tf.keras.Input((None, 3))
y = layers.RNN(layers.LSTMCell(16))(x)
model_lstm_3 = tf.keras.Model(x, y)

2.2 训练与评估

2.2.1 模型编译,确定训练流程。

构建好模型后,通过调用 compile 配置该模型的学习流程:

  • l compile( optimizer=‘rmsprop’, loss=None, metrics=None, loss_weights=None):
  • l optimizer:优化器;
  • l loss:损失函数,对于二分类任务就是交叉熵,回归任务就是 mse 之类的;
  • l metrics:在训练和测试期间的模型评估标准。比如 metrics = [‘accuracy’]。 指定不同的评估标准,需要传递一个字典,如 metrics = {‘output_a’:‘accuracy’}。
  • l loss_weights: 如果的模型有多个任务输出,在优化全局 loss 的时候,需要给每个输出指定相应的权重。
model = tf.keras.Sequential()
model.add(layers.Dense(10, activation='softmax'))
# 确定优化器(optimizer)、损失函数(loss)、模型评估方法(metrics)
model.compile(optimizer=tf.keras.optimizers.Adam(0.001),
              loss=tf.keras.losses.categorical_crossentropy, metrics=[tf.keras.metrics.categorical_accuracy])

2.2.2 模型训练

fit(x=None, y=None, batch_size=None, epochs=1, verbose=1, callbacks=None, validation_split=0.0, validation_data=None, shuffle=True, class_weight=None, sample_weight=None, initial_epoch=0, steps_per_epoch=None, validation_steps=None):
  • l x: 输入训练数据;
  • l y: 目标(标签)数据;
  • l batch_size: 每次梯度更新的样本数。如果未指定,默认为 32;
  • l epochs:训练模型迭代轮次;
  • l verbose:0, 1 或 2。日志显示模式。 0 = 不显示, 1 = 进度条, 2 = 每轮显示一行;
  • l callbacks:在训练时使用的回调函数;
  • l validation_split:验证集与训练数据的比例;
  • l validation_data:验证集;这个参数会覆盖 validation_split;
  • l shuffle: 是否在每轮迭代之前混洗数据。当 steps_per_epoch 非 None 时,这个参数无效;
  • l initial_epoch: 开始训练的轮次,常用于恢复之前的训练权重;
  • l steps_per_epoch:steps_per_epoch = 数据集大小/batch_size;
  • l validation_steps:只有在指定了 steps_per_epoch 时才有用。停止前要验证的总步数(批次样本)。
import numpy as np

train_x = np.random.random((1000, 36))
train_y = np.random.random((1000, 10))

val_x = np.random.random((200, 36))
val_y = np.random.random((200, 10))

model.fit(train_x, train_y, epochs=10, batch_size=100, validation_data=(val_x, val_y))

输出:
Train on 1000 samples, validate on 200 samples
Epoch 1/10
1000/1000 [==============================] - 1s 969us/sample - loss: 12.6233 - categorical_accuracy: 0.1030 - val_loss: 12.8527 - val_categorical_accuracy: 0.1050
Epoch 2/10
1000/1000 [==============================] - 0s 108us/sample - loss: 12.6198 - categorical_accuracy: 0.1030 - val_loss: 12.8505 - val_categorical_accuracy: 0.1050
Epoch 3/10
1000/1000 [==============================] - 0s 91us/sample - loss: 12.6178 - categorical_accuracy: 0.1030 - val_loss: 12.8481 - val_categorical_accuracy: 0.1050
Epoch 4/10
1000/1000 [==============================] - 0s 85us/sample - loss: 12.6146 - categorical_accuracy: 0.1030 - val_loss: 12.8449 - val_categorical_accuracy: 0.1050
Epoch 5/10
1000/1000 [==============================] - 0s 87us/sample - loss: 12.6109 - categorical_accuracy: 0.1030 - val_loss: 12.8407 - val_categorical_accuracy: 0.1050
Epoch 6/10
1000/1000 [==============================] - 0s 376us/sample - loss: 12.6105 - categorical_accuracy: 0.1030 - val_loss: 12.8426 - val_categorical_accuracy: 0.1050
Epoch 7/10
1000/1000 [==============================] - 0s 93us/sample - loss: 12.6102 - categorical_accuracy: 0.1030 - val_loss: 12.8418 - val_categorical_accuracy: 0.1050
Epoch 8/10
1000/1000 [==============================] - 0s 81us/sample - loss: 12.6101 - categorical_accuracy: 0.1030 - val_loss: 12.8417 - val_categorical_accuracy: 0.1050
Epoch 9/10
1000/1000 [==============================] - 0s 84us/sample - loss: 12.6093 - categorical_accuracy: 0.1030 - val_loss: 12.8402 - val_categorical_accuracy: 0.1050
Epoch 10/10
1000/1000 [==============================] - 0s 87us/sample - loss: 12.6074 - categorical_accuracy: 0.1030 - val_loss: 12.8387 - val_categorical_accuracy: 0.1050
<tensorflow.python.keras.callbacks.History at 0x7ff6013f2750>

对于大型数据集可以使用 tf.data 构建训练输入。

dataset = tf.data.Dataset.from_tensor_slices((train_x, train_y))
dataset = dataset.batch(32)
dataset = dataset.repeat()
val_dataset = tf.data.Dataset.from_tensor_slices((val_x, val_y))
val_dataset = val_dataset.batch(32)
val_dataset = val_dataset.repeat()

model.fit(dataset, epochs=10, steps_per_epoch=30, validation_data=val_dataset, validation_steps=3)

输出:
Train for 30 steps, validate for 3 steps
Epoch 1/10
30/30 [==============================] - 1s 20ms/step - loss: 12.5710 - categorical_accuracy: 0.1031 - val_loss: 12.6103 - val_categorical_accuracy: 0.0938
Epoch 2/10
30/30 [==============================] - 0s 4ms/step - loss: 12.6300 - categorical_accuracy: 0.1026 - val_loss: 12.6063 - val_categorical_accuracy: 0.0938
Epoch 3/10
30/30 [==============================] - 0s 3ms/step - loss: 12.5923 - categorical_accuracy: 0.0972 - val_loss: 12.5991 - val_categorical_accuracy: 0.0938
Epoch 4/10
30/30 [==============================] - 0s 3ms/step - loss: 12.5959 - categorical_accuracy: 0.1015 - val_loss: 12.5924 - val_categorical_accuracy: 0.0938
Epoch 5/10
30/30 [==============================] - 0s 3ms/step - loss: 12.5987 - categorical_accuracy: 0.1015 - val_loss: 12.5859 - val_categorical_accuracy: 0.0938
Epoch 6/10
30/30 [==============================] - 0s 4ms/step - loss: 12.5951 - categorical_accuracy: 0.1015 - val_loss: 12.5804 - val_categorical_accuracy: 0.0938
Epoch 7/10
30/30 [==============================] - 0s 3ms/step - loss: 12.5632 - categorical_accuracy: 0.1015 - val_loss: 12.5750 - val_categorical_accuracy: 0.0938
Epoch 8/10
30/30 [==============================] - 0s 4ms/step - loss: 12.5913 - categorical_accuracy: 0.1036 - val_loss: 12.5709 - val_categorical_accuracy: 0.0938
Epoch 9/10
30/30 [==============================] - 0s 4ms/step - loss: 12.5877 - categorical_accuracy: 0.1047 - val_loss: 12.5667 - val_categorical_accuracy: 0.0938
Epoch 10/10
30/30 [==============================] - 0s 3ms/step - loss: 12.6003 - categorical_accuracy: 0.1036 - val_loss: 12.5641 - val_categorical_accuracy: 0.0938
<tensorflow.python.keras.callbacks.History at 0x7ff60196a490>

2.2.3 回调函数

回调函数是传递给模型以自定义和扩展其在训练期间的行为的对象。我们可以编写自己的自定义回调,或使用tf.keras.callbacks 中的内置函数,常用内置回调函数如下:

  • tf.keras.callbacks.ModelCheckpoint:定期保存模型。
  • tf.keras.callbacks.LearningRateScheduler:动态更改学习率。
  • tf.keras.callbacks.EarlyStopping:提前终止。
  • tf.keras.callbacks.TensorBoard:使用 TensorBoard。
# 超参数设置
Epochs = 10


# 定义一个学习率动态设置函数
def lr_Scheduler(epoch):
    if epoch > 0.9 * Epochs:
        lr = 0.0001
    elif epoch > 0.5 * Epochs:
        lr = 0.001
    elif epoch > 0.25 * Epochs:
        lr = 0.01
    else:
        lr = 0.1

    print(lr)
    return lr


callbacks = [
    # 早 停 :
    tf.keras.callbacks.EarlyStopping(
        # 不再提升的关注指标
        monitor='val_loss',
        # 不再提升的阈值
        min_delta=1e-2,
        # 不再提升的轮次
        patience=2),

    # 定 期 保 存 模 型 :
    tf.keras.callbacks.ModelCheckpoint(
        # 模型路径
        filepath='testmodel_{epoch}.h5',
        # 是否保存最佳模型
        save_best_only=True,
        # 监控指标
        monitor='val_loss'),

    # 动态更改学习率
    tf.keras.callbacks.LearningRateScheduler(lr_Scheduler),

    # 使用 TensorBoard
    tf.keras.callbacks.TensorBoard(log_dir='./logs')]
model.fit(train_x, train_y, batch_size=16, epochs=Epochs,
          callbacks=callbacks, validation_data = (val_x, val_y))

输出:
Train on 1000 samples, validate on 200 samples
0.1
Epoch 1/10
1000/1000 [==============================] - 0s 334us/sample - loss: 12.6352 - categorical_accuracy: 0.1080 - val_loss: 12.1066 - val_categorical_accuracy: 0.1150
0.1
Epoch 2/10
1000/1000 [==============================] - 0s 234us/sample - loss: 12.6656 - categorical_accuracy: 0.0820 - val_loss: 12.2066 - val_categorical_accuracy: 0.0950
0.1
Epoch 3/10
1000/1000 [==============================] - 0s 215us/sample - loss: 12.5260 - categorical_accuracy: 0.0920 - val_loss: 12.1053 - val_categorical_accuracy: 0.1100
<tensorflow.python.keras.callbacks.History at 0x7f7fe15e2690>

2.2.4 评估与预测

评估和预测函数:tf.keras.Model.evaluate 和 tf.keras.Model.predict 方法。

# 模型评估
test_x = np.random.random((1000, 36))
test_y = np.random.random((1000, 10))
model.evaluate(test_x, test_y, batch_size=32)

输出:
1000/1000 [==============================] - 0s 45us/sample - loss: 12.2881 -
categorical_accuracy: 0.0770
[12.288104843139648, 0.077]


# 模型预测
pre_x = np.random.random((10, 36))
result = model.predict(test_x, )
print(result)

输出:
[[0.0460458  0.08133552 0.27336136 ... 0.05372529 0.15784709 0.06589693]
 [0.07072002 0.07391042 0.2307739  ... 0.06366149 0.08080809 0.12174617]
 [0.07114598 0.07659262 0.31846598 ... 0.03336082 0.05275346 0.05002784]
 ...
 [0.03928034 0.07010722 0.46011814 ... 0.03338903 0.04595451 0.07160679]
 [0.04240561 0.04399079 0.4240673  ... 0.04155495 0.0740051  0.06719721]
 [0.05728028 0.03738481 0.3684643  ... 0.05326979 0.09295851 0.09666349]]

2.3 模型保存与恢复

2.3.1 保存和恢复整个模型

代码:

import numpy as np  # 模型保存

model.save('model/the_save_model.h5')
# 导入模型
new_model = tf.keras.models.load_model('model/the_save_model.h5')
new_prediction = new_model.predict(test_x)
# np.testing.assert_allclose: 判断两个对象的近似程度是否超出了指定的容差限。若是,则抛出异常。:
# atol:指定的容差限
np.testing.assert_allclose(result, new_prediction, atol=1e-6)  # 预测结果一样

模型保存后可以在对应的文件夹中找到对应的权重文件。

2.3.2 只保存和加载网络权重

若权重名后有.h5 或.keras 后缀,则保存为 HDF5 格式文件,否则默认为 TensorFlow Checkpoint格式文件。

代码:

model.save_weights('model/model_weights')
model.save_weights('model/model_weights.h5')
# 权重加载
model.load_weights('model/model_weights')
model.load_weights('model/model_weights.h5')

3. 利用 TensorFlow 进行手写数字识别

手写数字识别是常见的图像识别任务,计算机通过手写体图片来识别图片中的字,与印刷字体不同的是,不同人的手写体风格迥异,大小不一,造成了计算机对手写识别任务的困难,此项目通过应用深度学习和 tensorflow 工具对 MNIST 手写数据集进行训练并建模。

  • 读取 mnist 手写数字数据集;
  • 利用简单数学模型入门 tensorflow;
  • 高级 API 实现 softmax 回归;
  • 构建多层卷积网络 CNN;
  • 高级 API 实现卷积网络 CNN;
  • 预测结果可视化;

3.1 数据集介绍

1). MNIST 数据集来自美国国家标准与技术研究所(National Institute of Standards and Technology ,简称 NIST);

2). 该数据集由来自 250 个不同人手写的数字构成,其中 50%是高中学生,50%来自人口普查局的工组人员;

3). 数据集可在 http://yann.lecun.com/exdb/mnist/ 获取, 它包含了四个部分:

- Training set images: train-images-idx3-ubyte.gz (9.9 MB, 解压后 47 MB, 包含 60,000 个样本)

- Training set labels: train-labels-idx1-ubyte.gz (29 KB, 解压后 60 KB, 包含 60,000 个标签)

- Test set images: t10k-images-idx3-ubyte.gz (1.6 MB, 解压后 7.8 MB, 包含 10,000个样本)

- Test set labels: t10k-labels-idx1-ubyte.gz (5KB, 解压后 10 KB, 包含 10,000 个标签) 4). mnist 是一个入门级的计算机视觉数据集,它包含各种手写数字图片:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oj84g3QQ-1594520426345)(images/image-20200707142939032.png)]

它也包含每一张图片对应的标签,告诉我们这个是数字几,比如说,上面这四张图片的标签分别是5,0,4,1。

3.1.1 mnist 数据集读取

从 tensorflow 直接读取数据集,联网下载解压;

代码:

import os
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, optimizers, datasets
from matplotlib import pyplot as plt
import numpy as np

# 省略掉一些无关信息的打印
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
# 防止libiomp5.dylib 报错
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"

(x_train_raw, y_train_raw), (x_test_raw, y_test_raw) = datasets.mnist.load_data()

print(y_train_raw[0])
print(x_train_raw.shape, y_train_raw.shape)
print(x_test_raw.shape, y_test_raw.shape)

# 将分类标签变为 onehot 编码
num_classes = 10
y_train = keras.utils.to_categorical(y_train_raw, num_classes)
y_test = keras.utils.to_categorical(y_test_raw, num_classes)
print(y_train[0])

输出:
5
(60000, 28, 28) (60000,)
(10000, 28, 28) (10000,)
[0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]

在 mnist 数据集中,images 是一个形状为[60000,28,28]的张量,第一个维度数字用来索引图片,第二、三个维度数字用来索引每张图片中的像素点。在此张量里的每一个元素,都表示某张图片里的某个像素的强度值,介于 0,255 之间。

标签数据是"one-hot vectors",一个 one-hot 向量除了某一位数字是 1 之外,其余各维度数字都是 0,如标签 1 可以表示为([0,1,0,0,0,0,0,0,0,0,0]),因此, labels 是一个 [60000, 10] 的数字矩阵。

3.2 数据集预处理及可视化

3.2.1 数据可视化

绘制前 9 张图片

plt.figure()
for i in range(9):
    plt.subplot(3, 3, i + 1)
    plt.imshow(x_train_raw[i])
    # plt.ylabel(y[i].numpy()) 
    plt.axis('off')
plt.show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yrz6Fq5r-1594520426347)(images/number.png)]

数据处理,因为我们构建的是全连接网络所以输出应该是向量的形式,而非现在图像的矩阵形式。因此 我们需要把图像整理成向量。

# 将 28*28 的图像展开成 784*1 的向量
x_train = x_train_raw.reshape(60000, 784)
x_test = x_test_raw.reshape(10000, 784)

现在像素点的动态范围为 0 到 255。处理图形像素值时,我们通常会把图像像素点归一化到 0 到 1 的范围内。

# 将图像像素值归一化
x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255

3.3 DNN 网络构建

3.3.1 DNN 构建网络

# 创建模型。模型包括 3 个全连接层和两个 RELU 激活函数
model = keras.Sequential([
    layers.Dense(512, activation='relu', input_dim=784),
    layers.Dense(256, activation='relu'),
    layers.Dense(124, activation='relu'),
    layers.Dense(num_classes, activation='softmax')])

model.summary()

输出:
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 512)               401920    
_________________________________________________________________
dense_1 (Dense)              (None, 256)               131328    
_________________________________________________________________
dense_2 (Dense)              (None, 124)               31868     
_________________________________________________________________
dense_3 (Dense)              (None, 10)                1250      
=================================================================
Total params: 566,366
Trainable params: 566,366
Non-trainable params: 0

其中 layer.Dense()表示全连接层,activation 参数表示使用的激活函数。

3.3.2 编译 DNN 模型

Optimizer = optimizers.Adam(0.001)
model.compile(loss=keras.losses.categorical_crossentropy,
              optimizer=Optimizer, metrics=['accuracy'])

以上定义了模型的损失函数为“交叉熵”,优化算法为“Adam”梯度下降方法。

3.3.3 DNN 模型训练

# 使用 fit 方法使模型对训练数据拟合
model.fit(x_train, y_train,
          batch_size=128, epochs=10, verbose=1)
          
输出:
Train on 60000 samples
Epoch 1/10
60000/60000 [==============================] - 8s 132us/sample - loss: 0.2395 - accuracy: 0.9289
Epoch 2/10
60000/60000 [==============================] - 9s 155us/sample - loss: 0.0855 - accuracy: 0.9736
Epoch 3/10
60000/60000 [==============================] - 9s 149us/sample - loss: 0.0564 - accuracy: 0.9820
Epoch 4/10
60000/60000 [==============================] - 8s 129us/sample - loss: 0.0402 - accuracy: 0.9872
Epoch 5/10
60000/60000 [==============================] - 9s 142us/sample - loss: 0.0324 - accuracy: 0.9888
Epoch 6/10
60000/60000 [==============================] - 8s 138us/sample - loss: 0.0263 - accuracy: 0.9911
Epoch 7/10
60000/60000 [==============================] - 7s 120us/sample - loss: 0.0175 - accuracy: 0.9944
Epoch 8/10
60000/60000 [==============================] - 7s 121us/sample - loss: 0.0183 - accuracy: 0.9942
Epoch 9/10
60000/60000 [==============================] - 9s 153us/sample - loss: 0.0174 - accuracy: 0.9942
Epoch 10/10
60000/60000 [==============================] - 7s 120us/sample - loss: 0.0161 - accuracy: 0.9944

其中 epoch 表示批次,表示将全量的数据迭代 10 次。

3.3.4 DNN 模型评估

score = model.evaluate(x_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])

输出:
Test loss: 0.08009832046656702
Test accuracy: 0.9811

经过评估,模型准确率为 0.87,迭代了 10 次训练。

3.3.5 保存模型

model.save('model/final_DNN_model.h5')

3.4 构建 CNN 网络

之前用传统方法构建 CNN 网络,可以更清楚的了解内部的网络结构,但是代码量比较多,所以我们尝试用高级 API 构建网络,以简化构建网络的过程。

3.4.1 CNN 构建网络

代码:

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, optimizers, datasets
import numpy as np
import os

# 省略掉一些无关信息的打印
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
# 防止libiomp5.dylib 报错
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"

(x_train_raw, y_train_raw), (x_test_raw, y_test_raw) = datasets.mnist.load_data()

# 将分类标签变为 onehot 编码
num_classes = 10
y_train = keras.utils.to_categorical(y_train_raw, num_classes)
y_test = keras.utils.to_categorical(y_test_raw, num_classes)
print(y_train[0])

# 将 28*28 的图像展开成 784*1 的向量
x_train = x_train_raw.reshape(60000, 784)
x_test = x_test_raw.reshape(10000, 784)

# 将图像像素值归一化
x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255

model = keras.Sequential()  # 创建网络序列 ## 添加第一层卷积层和池化层
model.add(keras.layers.Conv2D(filters=32, kernel_size=5, strides=(1, 1),
                              padding='same', activation=tf.nn.relu, input_shape=(28, 28, 1)))
model.add(keras.layers.MaxPool2D(pool_size=(2, 2), strides=(2, 2), padding='valid'))
## 添加第二层卷积层和池化层
model.add(keras.layers.Conv2D(filters=64, kernel_size=3, strides=(1, 1), padding='same', activation=tf.nn.relu))
model.add(keras.layers.MaxPool2D(pool_size=(2, 2), strides=(2, 2), padding='valid'))
# 添加dropout 层 以减少过拟合
model.add(keras.layers.Dropout(0.25))
model.add(keras.layers.Flatten())
# 添加两层全连接层
model.add(keras.layers.Dense(units=128, activation=tf.nn.relu))
model.add(keras.layers.Dropout(0.5))
model.add(keras.layers.Dense(units=10, activation=tf.nn.softmax))

以上网络中,我们利用 keras.layers 添加了两个卷积池化层,之后又添加了 dropout 层,防止过拟合,最后添加了两层全连接层。

3.4.2 CNN 网络编译和训练

# 将数据扩充维度,以适应 CNN 模型
X_train = x_train.reshape(60000, 28, 28, 1)
X_test = x_test.reshape(10000, 28, 28, 1)
model.compile(optimizer=tf.optimizers.Adam(), loss="categorical_crossentropy", metrics=['accuracy'])
model.fit(x=X_train, y=y_train, epochs=5, batch_size=128)

输出:
[0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
Epoch 1/5
469/469 [==============================] - 49s 105ms/step - loss: 0.2675 - accuracy: 0.9175
Epoch 2/5
469/469 [==============================] - 57s 121ms/step - loss: 0.0878 - accuracy: 0.9736
Epoch 3/5
469/469 [==============================] - 55s 117ms/step - loss: 0.0686 - accuracy: 0.9796
Epoch 4/5
469/469 [==============================] - 47s 101ms/step - loss: 0.0552 - accuracy: 0.9839
Epoch 5/5
469/469 [==============================] - 45s 96ms/step - loss: 0.0474 - accuracy: 0.9854

在训练时,网络训练数据只迭代了 5 次,可以再增加网络迭代次数,自行尝试看效果如何。

3.4.3 CNN 模型验证

test_loss, test_acc = model.evaluate(x=X_test, y= datasets.mnist.test.labels)
print("Test Accuracy %.2f" % test_acc)

输出:
313/313 [==============================] - 2s 6ms/step - loss: 0.0235 - accuracy: 0.9925
Test Accuracy 0.99

最终结果也达到了 99%的准确率。

3.4.4 CNN 模型保存

model.save('model/final_DNN_model.h5')

3.5 预测结果可视化

3.5.1 加载 CNN 保存模型

from tensorflow.keras.models import load_model

new_model = load_model('model/final_CNN_model.h5')
new_model.summary()

输出:
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 28, 28, 32)        832       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 14, 14, 32)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 14, 14, 64)        18496     
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 7, 7, 64)          0         
_________________________________________________________________
dropout (Dropout)            (None, 7, 7, 64)          0         
_________________________________________________________________
flatten (Flatten)            (None, 3136)              0         
_________________________________________________________________
dense (Dense)                (None, 128)               401536    
_________________________________________________________________
dropout_1 (Dropout)          (None, 128)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 10)                1290      
=================================================================
Total params: 422,154
Trainable params: 422,154
Non-trainable params: 0
_________________________________________________________________

用将预测结果可视化

# 测试集输出结果可视化
import matplotlib.pyplot as plt
% matplotlib inline


def res_Visual(n):
    final_opt_a = new_model.predict_classes(X_test[0:n])  # 通过模型预测测试集
    fig, ax = plt.subplots(nrows=int(n / 5), ncols=5)
    ax = ax.flatten()
    print('前{}张图片预测结果为:'.format(n))
    for i in range(n):
        print(final_opt_a[i], end=',')
        if int((i + 1) % 5) == 0:
            print('\t')
        # 图片可视化展示
        img = X_test[i].reshape((28, 28))  # 读取每行数据,格式为 Ndarry
        plt.axis("off")
        ax[i].imshow(img, cmap='Greys', interpolation='nearest')  # 可视化
        ax[i].axis("off")
    print('测试集前{}张图片为:'.format(n))


res_Visual(20)

输出:
前20张图片预测结果为:
7,2,1,0,4,	
1,4,9,5,9,	
0,6,9,0,1,	
5,9,7,8,4,	
测试集前20张图片为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qtaSs7IX-1594520426349)(images/image-20200707183818758.png)]

你可能感兴趣的:(笔记,tensorflow,机器学习,深度学习)