Tensorflow深度学习-第三天

为了区分需要计算梯度信息的张量与不需要计算梯度信息的张量,TensorFlow 增加了 一种专门的数据类型来支持梯度信息的记录:tf.Variable。tf.Variable 类型在普通的张量类 型基础上添加了 name,trainable 等属性来支持计算图的构建。由于梯度运算会消耗大量的 计算资源,而且会自动更新相关参数,对于不需要的优化的张量,如神经网络的输入𝑿, 不需要通过 tf.Variable 封装;相反,对于需要计算梯度并优化的张量,如神经网络层的𝑾 和𝒃,需要通过 tf.Variable 包裹以便 TensorFlow 跟踪相关梯度信息。

import tensorflow as tf
a = tf.constant([-1, 0, 1, 2]) # 创建 TF 张量 
aa = tf.Variable(a) # 转换为 Variable 类型 
aa.name, aa.trainable # Variable类型张量的属性
('Variable:0', True)

aa
<tf.Variable 'Variable:0' shape=(4,) dtype=int32, numpy=array([-1,  0,  1,  2], dtype=int32)>

a
<tf.Tensor: id=0, shape=(4,), dtype=int32, numpy=array([-1,  0,  1,  2], dtype=int32)>

其中张量的 name 和 trainable 属性是 Variable 特有的属性,name 属性用于命名计算图中的 变量,这套命名体系是 TensorFlow 内部维护的,一般不需要用户关注 name 属性;trainable 属性表征当前张量是否需要被优化,创建 Variable 对象时是默认启用优化标志,可以设置 trainable=False 来设置张量不需要优化。

 

正态分布(Normal Distribution,或 Gaussian Distribution)和均匀分布(Uniform Distribution)是最常见的分布之一,创建采样自这 2 种分布的张量非常有用,比如在卷积神 经网络中,卷积核张量𝑾初始化为正态分布有利于网络的训练;在对抗生成网络中,隐藏 变量𝒛一般采样自均匀分布。

通过 tf.random.normal(shape, mean=0.0, stddev=1.0)可以创建形状为 shape,均值为 mean,标准差为 stddev 的正态分布𝒩(mean, stddev2)。例如,创建均值为 0,标准差为 1 的正态分布:

tf.random.normal([2,2])
tf.random.normal([2,2], mean=1,stddev=2)

考虑自然语言处理(Natural Language Processing,简称 NLP)中句子的表示,如评价句 子的是否为正面情绪的情感分类任务网络,如图 4.3 所示。为了能够方便字符串被神经网 络处理,一般将单词通过嵌入层(Embedding Layer)编码为固定长度的向量,比如“a”编码 为某个长度 3 的向量,那么 2 个等长(单词数量为 5)的句子序列可以表示为 shape 为[2,5,3] 的 3 维张量,其中 2 表示句子个数,5 表示单词数量,3 表示单词向量的长度。

from tensorflow.keras import datasets, layers, optimizers, Sequential, metrics
# 创建32x32的彩色图片输入,个数为4
x = tf.random.normal([4,32,32,3])
# 创建卷积神经网络
layer = layers.Conv2D(16,kernel_size=3) 
out = layer(x) # 前向计算
out.shape # 输出大小Out[48]: TensorShape([4, 30, 30, 16])
#其中卷积核张量也是 4 维张量,可以通过 kernel 成员变量访问:
#layer.kernel.shape # 访问卷积核张量
TensorShape([4, 30, 30, 16])

layer.kernel.shape # 访问卷积核张量
TensorShape([3, 3, 3, 16])

索引与切片:

在 TensorFlow 中,支持基本的[𝑖][𝑗] ⋯标准索引方式,也支持通过逗号分隔索引号的索 引方式。考虑输入𝑿为 4 张32 × 32大小的彩色图片(为了方便演示,大部分张量都使用随机 分布模拟产生,后文同),shape 为[4,32,32,3],

 

通过start: end: step切片方式可以方便地提取一段数据,其中 start 为开始读取位置的索引,end 为结束读取位置的索引(不包含 end 位),step 为采样步长。

张量的索引与切片方式多种多样,尤其是切片操作,初学者容易犯迷糊。但本质上切 片操作只有start: end: step这一种基本形式,通过这种基本形式有目的地省略掉默认参数, 从而衍生出多种简写方法,这也是很好理解的。它衍生的简写形式熟练后一看就能推测出 省略掉的信息,书写起来也更方便快捷。由于深度学习一般处理的维度数在四维以内,⋯ 操作符完全可以用:符号代替,因此理解了这些就会发现张量切片操作并不复杂。

x=tf.range(96)
<tf.Tensor: id=95, shape=(96,), dtype=int32, numpy=
array([ 0,  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, 27, 28, 29, 30, 31, 32, 33,
       34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
       51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67,
       68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,
       85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95], dtype=int32)>
x=tf.reshape(x,[2,4,4,3])
<tf.Tensor: id=97, shape=(2, 4, 4, 3), dtype=int32, numpy=
array([[[[ 0,  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],
         [27, 28, 29],
         [30, 31, 32],
         [33, 34, 35]],

        [[36, 37, 38],
         [39, 40, 41],
         [42, 43, 44],
         [45, 46, 47]]],


       [[[48, 49, 50],
         [51, 52, 53],
         [54, 55, 56],
         [57, 58, 59]],

        [[60, 61, 62],
         [63, 64, 65],
         [66, 67, 68],
         [69, 70, 71]],

        [[72, 73, 74],
         [75, 76, 77],
         [78, 79, 80],
         [81, 82, 83]],

        [[84, 85, 86],
         [87, 88, 89],
         [90, 91, 92],
         [93, 94, 95]]]], dtype=int32)>

数据在创建时按着初始的维度顺序写入,改变张量的视图仅仅是改变了张量的理解方 式,并不需要改变张量的存储顺序,这在一定程度上是从计算效率考虑的,大量数据的写 入操作会消耗较多的计算资源。由于存储时数据只有平坦结构,与数据的逻辑结构是分离 的,因此如果新的逻辑结构不需要改变数据的存储方式,就可以节省大量计算资源,这也 是改变视图操作的优势。改变视图操作在提供便捷性的同时,也会带来很多逻辑隐患,这 主要的原因是改变视图操作的默认前提是存储不需要改变,否则改变视图操作就是非法 的。

例如,张量𝑨按着初始视图[𝑏, h, , 𝑐]写入的内存布局,我们改变𝑨的理解方式,它可以 有如下多种合法的理解方式:

❑ [𝑏, h ∙ , 𝑐] 张量理解为𝑏张图片,h ∙ 个像素点,𝑐个通道
❑ [𝑏, h, ∙ 𝑐] 张量理解为𝑏张图片,h行,每行的特征长度为 ∙ 𝑐

 ❑ [𝑏, h ∙ ∙ 𝑐] 张量理解为𝑏张图片,每张图片的特征长度为h ∙ ∙ 𝑐 上述新视图的存储都不需要改变,因此是合法的。

从语法上来说,视图变换只需要满足新视图的元素总量与存储区域大小相等即可,即 新视图的元素数量等于

𝑏∙h∙w ∙𝑐

改变视图是神经网络中非常常见的操作,可以通过串联多个 reshape 操作来实现复杂 逻辑,但是在通过 reshape 改变视图时,必须始终记住张量的存储顺序,新视图的维度顺 序不能与存储顺序相悖,否则需要通过交换维度操作将存储顺序同步过来。举个例子,对 于 shape 为[4,32,32,3]的图片数据,通过 reshape 操作将 shape 调整为[4,1024,3],此时视图 的维度顺序为𝑏 − pixel − 𝑐,张量的存储顺序为[𝑏, h, , 𝑐]。可以将[4,1024,3]恢复为

 

[𝑏, h, w, 𝑐] = [4,32,32,3]时,新视图的维度顺序与存储顺序无冲突,可以恢复出无逻辑 问题的数据。

[𝑏, w, h, 𝑐] = [4,32,32,3]时,新视图的维度顺序与存储顺序冲突。

[h ∙w ∙ 𝑐, 𝑏] = [3072,4]时,新视图的维度顺序与存储顺序冲突。

通过上述的一系列连续变换视图操作时需要意识到,张量的存储顺序始终没有改变,数据 在内存中仍然是按着初始写入的顺序0,1,2, ⋯ ,95保存的。

改变视图、增删维度都不会影响张量的存储。在实现算法逻辑时,在保持维度顺序不 变的条件下,仅仅改变张量的理解方式是不够的,有时需要直接调整的存储顺序,即交换 维度(Transpose)。通过交换维度操作,改变了张量的存储顺序,同时也改变了张量的视 图。

交换维度操作是非常常见的,比如在 TensorFlow 中,图片张量的默认存储格式是通道 后行格式:[𝑏, h, , 𝑐],但是部分库的图片格式是通道先行格式:[𝑏, 𝑐, h, ],因此需要完成 [𝑏, h, , 𝑐]到[𝑏, 𝑐, h, ]维度交换运算,此时若简单的使用改变视图函数 reshape,则新视图 的存储方式需要改变,因此使用改变视图函数是不合法的。我们以[𝑏, h, , 𝑐]转换到
[𝑏, 𝑐, h, ]为例,介绍如何使用 tf.transpose(x, perm)函数完成维度交换操作,其中参数 perm 表示新维度的顺序 List。考虑图片张量 shape 为[2,32,32,3],“图片数量、行、列、通道 数”的维度索引分别为 0、1、2、3,如果需要交换为[𝑏, 𝑐, h, ]格式,则新维度的排序为 “图片数量、通道数、行、列”,对应的索引号为[0,3,1,2],因此参数 perm 需设置为 [0,3,1,2],实现如下:

 

x = tf.random.normal([2,32,32,3])
tf.transpose(x,perm=[0,3,1,2])
<tf.Tensor: id=105, shape=(2, 3, 32, 32), dtype=float32, numpy=
array([[[[-0.39762518,  1.4674902 ,  0.3429396 , ..., -0.18593988,
          -0.9753849 , -1.3450396 ],
         [ 0.9279257 , -1.9623818 ,  0.7048407 , ..., -0.8269033 ,
...

到现在为止,我们已经介绍了如何创建张量、对张量进行索引切片、维度变换和常见

的数学运算等操作。最后我们将利用已经学到的知识去完成三层神经网络的实现:

out = 𝑅𝑒𝐿𝑈{𝑅𝑒𝐿𝑈{𝑅𝑒𝐿𝑈[𝑿@𝑾1 + 𝒃1]@𝑾2 + 𝒃2}@𝑾 + 𝒃 }

我们采用的数据集是 MNIST 手写数字图片集,输入节点数为 784,第一层的输出节点数是256,第二层的输出节点数是 128,第三层的输出节点是 10,也就是当前样本属于 10 类别的概率。

合并与切割:

a = tf.random.normal([4,35,8]) # 模拟成绩册A 
b = tf.random.normal([6,35,8]) # 模拟成绩册B
c = tf.concat([a,b],axis=0) # 拼接合并成绩册

<tf.Tensor: id=134, shape=(10, 35, 8), dtype=float32, numpy=
array([[[ 0.06554277,  0.70229685,  0.05765281, ...,  0.16478623,
         -1.4000126 ,  1.8207668 ],
        [-2.2485626 , -0.27521434, -0.3096695 , ...,  1.1652282 ,
          0.5162945 ,  0.54561096],
        [ 1.0548744 ,  1.1472257 ,  0.24377841, ...,  0.7914138 ,
...

从语法上来说,拼接合并操作可以在任意的维度上进行,唯一的约束是非合并维度的 长度必须一致。

堆叠

a = tf.random.normal([4,35,8]) # 模拟成绩册A 
b = tf.random.normal([4,35,8]) # 模拟成绩册B
c = tf.stack([a,b],axis=0) # 拼接合并成绩册
<tf.Tensor: id=161, shape=(2, 4, 35, 8), dtype=float32, numpy=
...

 

合并操作的逆过程就是分割,将一个张量分拆为多个张量。继续考虑成绩册的例子, 我们得到整个学校的成绩册张量,shape 为[10,35,8],现在需要将数据在班级维度切割为 10 个张量,每个张量保存了对应班级的成绩册数据。

通过 tf.split(x, num_or_size_splits, axis)可以完成张量的分割操作,参数意义如下:

  • ❑  x参数:待分割张量。

  • ❑  num_or_size_splits参数:切割方案。当num_or_size_splits为单个数值时,如10,表 示等长切割为 10 份;当 num_or_size_splits 为 List 时,List 的每个元素表示每份的长 度,如[2,4,2,2]表示切割为 4 份,每份的长度依次是 2、4、2、2。

  • ❑  axis参数:指定分割的维度索引号。

x = tf.random.normal([10,35,8])
result = tf.split(x, num_or_size_splits=10, axis=0)
len(result)
10
<tf.Tensor: id=170, shape=(1, 35, 8), dtype=float32, numpy=
...


x = tf.random.normal([10,35,8])
result = tf.split(x, num_or_size_splits=[3,3,3,1], axis=0)
len(result)
4
result[1]
<tf.Tensor: id=189, shape=(3, 35, 8), dtype=float32, numpy=
...

等分切割的时候,num_or_size_splits必须可以背整除。

向量范数:

向量范数(Vector Norm)是表征向量“长度”的一种度量方法,它可以推广到张量上。

在神经网络中,常用来表示张量的权值大小,梯度大小等。常用的向量范数有:

 ❑ L1范数,定义为向量𝒙的所有元素绝对值之和

\left \| x \right \|_{1} = \sum _{I}\left | x_{i} \right |

\left \| x \right \|_{1} = \sum _{I}\left | x_{i} \right |

❑ L2范数,定义为向量𝒙的所有元素的平方和,再开根号

\left \| x \right \|_{2} = \sqrt{\sum _{i}\left | x_{i} \right |^{2}}

\left \| x \right \|_{2} = \sqrt{\sum _{i}\left | x_{i} \right |^{2}}

❑ ∞−范数,定义为向量𝒙的所有元素绝对值的最大值: 

\left \| x \right \|_{\infty} = max_{i}(\left | x_{i} \right |)

\left \| x \right \|_{\infty} = max_{i}(\left | x_{i} \right |)

对于矩阵和张量,同样可以利用向量范数的计算公式,等价于将矩阵和张量打平成向量后 计算。

x = tf.ones([2,2])
tf.norm(x,ord=1)
<tf.Tensor: id=224, shape=(), dtype=float32, numpy=4.0>
tf.norm(x,ord=2)
<tf.Tensor: id=229, shape=(), dtype=float32, numpy=2.0>
import numpy as np
tf.norm(x,ord=np.inf) # 计算∞范数
<tf.Tensor: id=233, shape=(), dtype=float32, numpy=1.0>

通过 tf.reduce_max、tf.reduce_min、tf.reduce_mean、tf.reduce_sum 函数可以求解张量在某个维度上的最大、最小、均值、和,也可以求全局最大、最小、均值、和信息。

 

fc = layers.Dense(512, activation=tf.nn.relu)

代码即可以创建一层全连接层 fc,并指定输出节点数为 512,输入的节点数 在fc(x)计算时自动获取,并创建内部权值张量𝑾和偏置张量𝒃。我们可以通过类内部的成 员名 kernel 和 bias 来获取权值张量𝑾和偏置张量𝒃对象:

fc.kernel

实际上,网络层除了保存了待优化张量列表 trainable_variables,还有部分层包含了不 参与梯度优化的张量,如后续介绍的 Batch Normalization 层,可以通过 non_trainable_variables 成员返回所有不需要优化的参数列表。

在设计全连接网络时,网络的结构配置等超参数可以按着经验法则自由设置,只需要 遵循少量的约束即可。例如,隐藏层 1 的输入节点数需和数据的实际特征长度匹配,每层 的输入层节点数与上一层输出节点数匹配,输出层的激活函数和节点数需要根据任务的具 体设定进行设计。总的来说,神经网络模型的结构设计自由度较大,如图 6.5 层中每层的 输出节点数不一定要设计为[256,128,64,10],可以自由搭配,如[256,256,64,10]或 [512,64,32,10]等都是可行的。至于与哪一组超参数是最优的,这需要很多的领域经验知识 和大量的实验尝试,或者可以通过 AutoML 技术搜索出较优设定。

 

总有事情,今天又双叒先到这里。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值