char4-TensorFlow基础入门
TensorFlow
是一个面向深度学习算法的科学计算库,内部数据保存在张量Tensor对象中,所有的运算操作都是基于张量进行的
数据类型
数值类型
数值类型的张量是TF
的主要数据载体,包含:
- 标量
Scalar
,单个的实数,维度是0,形状shape
是[]
- 向量
Vector
,n
个实数的有序集合,通过中括号包裹,例如[1,2,4,5,3]
,维数是1,长度不定,shape
为n - 矩阵
Matrix
,m行n列实数的有序集合,shape为[m,n] - 张量是所有维度数(dim>2)的数组的统称,每个维度也称之为轴
Axis
。通常将标量、向量、矩阵也统称为张量;张量的维度和形状自行判断
标量
创建标量的关键字是constant,必须通过TF规定的方式去创建张量
代码语言:javascript复制import tensorflow as tf
a = 2 # python形式
b = tf.constant(2.0) # 这才是TF形式
c = tf.constant([1,2.0,3.7])
tf.is_true(b) # True
返回值中几个具体信息:
- id:内部索引对象的编号
- shape:张量的形状
- dtype:张量的数值精度
向量
向量的定义必须通过List
类型转递给tf.constant
函数
a = tf.constant([1.0]) # 即使是一个元素也是如此
b = tf.constant([1.0, 2.4, 4.5])
矩阵
代码语言:javascript复制a = tf.constant([[1,2],
[3,4]]) # 2维
b = tf.constant([[[1,2], [3,4]],
[[5,6], [7,8]]]) # 3维
字符串类型
字符串类型Strings类型的数据
代码语言:javascript复制a = tf.constant("hello tensorflow")
tf.strings模块中提供了常见的工具函数:
- join
- length
- split
布尔类型
TF中支持布尔类型的张量
代码语言:javascript复制a = tf.constant([True, False])
# tf中布尔类型和Python的中布尔类型是不等同的
b = tf.constant(True)
b == True # 结果是False
数值精度
精度设置和获取
TF支持不同类型的精度,Bit位数越长,精度越高,同时占用的内存空间越大。
- tf.int16/32/64
- tf.float16/32/64;
tf.float64
就是tf.double
需要注意的点:
- 高精度转低精度可能会报错
- 对于浮点数,高精度的张量可以表示更精准的数据
- 实际中,一般使用
tf.int32
和tf.float32
import numpy as np
import tensorflow as tf
# 创建张量的时候指定精度
tf.constant(12345678, dtype=tf.int32)
tf.constant(np.pi, dtype=tf.float64)
通过张量的dtype
属性可以获取张量的精度
类型转换
通过tf.cast
函数进行转换,需要注意的地方:
- 保证转换操作的合法性,比如高精度转低精度,可能发生溢出现象
- 布尔型和整形之间可以转换
- False默认是0,True表示1;其他非0数字默认是1
a = tf.constant([True, False])
tf.cast(a, tf.bool) # 1,0
待优化张量
有些张量是需要计算梯度,因此产生了需要计算待优化的张量,专门用来支持梯度信息的记录,使用的函数是tf.Variable
。
tf.Variable
类型在普通的张量类型基础上添加了name 、trainable等属性来支持计算的构建。
梯度的计算会消耗大量的资源,且会自动更新相关参数。
创建张量
从Numpy、List对象创建
numpy中的array数组和Python中的list都可以直接用来创建张量,通过tf.convert_to_tensor
import tensorflow as tf
import numpy as np
tf.convert_to_tensor([1,2,3])
tf.convert_to_tensor(np.array([[1,2,3],[4,5,6]])
numpy中默认使用的是64-bit精度,转到TF中使用的是tf.float64
创建全0、全1张量
几个函数记住即可,like
只是创建形状相同的张量:
- tf.ones()/tf.ones_like()
- tf.zeros()/tf.zeros_like()
tf.ones([2,3])
a = tf.zeros([2,4])
b = tf.ones_like(a) # 形状相同
自定义数值张量
在创建张量的时候,可以指定初始值:tf.fill(shape, vlaue)
代码语言:javascript复制tf.fill([2,3], -1) # 形状为2*3,值全部是-1
创建已知分布的张量
正态分布和均匀分布是最常见的。
- 正态分布:卷积神经网络中卷积核张量W,
tf.random.normal(shape, mean=0.0, stddev=1.0)
- 均匀分布:对抗网络中的隐藏层z一般采样自均匀分布,
tf.random.uniform(shape, minval=0,maxval=None,dtype=float32)
注意:如果均匀分布中采样的是整数类型,必须指定maxval和数据类型
创建序列
创建序列类型的张量是通过函数tf.range()
,标准的格式为:
tf.range(start,end,delta=1) # 含头不含尾,delta为步长
张量的应用
标量
标量的应用主要是误差值的表示、各种测量指标的表示,入精确度、精度、召回率等
代码语言:javascript复制out = tf.random.uniform(4,10) # 随机模拟网络输出
y = tf.constant([2,3,4,5]) # 随机构造样本真实输出标签
y = tf.one_hot(y, depth=10) # 转成热编码
loss = tf.keras.losses.mse(y, out) # 计算MSE
loss = tf.reduce_mean(loss) # 平均MSE
print(mse)
向量
在全连接层和卷积神经网络中,偏置b就是向量b=[b_!,b_2]^T
通过高层结口Dense()方式创建地网络层,张量W和b存储在类的内部,由类自动创建。
- 通过全连接层的bias成员查看偏置b
- 类的偏置bias初始值全部是0
fc = layers.Dense(3) # 创建一层Wx b,输出节点为3
fc.build(input_shape=(2,4))
fc.bias # 查看偏置
矩阵
矩阵也是非常常见的张量类型,比如全连接层的批量输入X=[b,d_{in}],其中b表示的是输入样本的个数,即batch size,d_{in}表示的是输入特征的长度。
代码语言:javascript复制w = tf.constant([3,4]) # 定义两个张量
b = tf.constant([3])
o = x@w b # 执行X@W b
- X@W b叫做线性层,也称之为全连接层,通过Dense类直接实现。
- 通过全连接层的kernel属性查看权重矩阵W
fc = layers.Dense(3) # 定义全连接层的输出节点为3
fc.build(input_shape=(2,4)) # 定义全连接层的输入节点为4
fc.kernel # 查看权重矩阵
3维张量
三维的张量一个典型应用是表示序列信号,它的格式是? = [?, ???????? ???, ??????? ???]
- ?表示序列信号的数量
- ???????? ???表示时间维度上的采样点数
- ??????? ???表示每个点的特征长度
4维张量
4维张量在卷积神经网络中应用的非常广泛,它用于保存特征图Feature maps
数据, 格式一般定义为[b,h,w,c]
- b表示输入的数量
- h/w表示特征图的高宽
- c表示特征图的通道数量
代码语言:javascript复制对于含有 RGB 3 个通道的彩色图片,每张图片包含了 h 行 w 列像素点,每个点需要 3 个数 值表示 RGB 通道的颜色强度,因此一张图片可以表示为[h, w, 3]
# 创建32x32的彩色图片输入,个数为4
x = tf.random.normal([4,32,32,3]) # 创建卷积神经网络
layer = layers.Conv2D(16,kernel_size=3) out = layer(x) # 前向计算
out.shape # 输出大小
# 卷积核张量也是4维张量,通过kernel属性来查看
layer.kernel.shape
索引和切片
索引
- 从0开始
- 两种方式
[i][j][k]...
[i,j,k,…]
x = tf.random.normal([4, 32, 32, 3])
x[0]
x[0][1][2]
切片
通过?????: ???: ????
切片方式提取数据
- 含头不含尾
- step步长,可以为负数
关于冒号和三个点的使用:都是表示某个维度上的所有数据
代码语言:javascript复制x = tf.random.normal([4, 32, 32, 3])
x[1:3]
x[0,::]
x[0, 0:28, 2:28:2, :]
x[::-2]
x[0:2,...,1:]
维度变换
线性层的批量形式
假设:
- X 包含了 2 个样本,每个样本的特征长度为 4,X 的 shape 为[2,4]
- 线性层的输出为3个节点,其shape为[4,3]
- 偏置b的shape为[3]
那么不同shape的张量之间如何进行相加?此时,维度变换可以解决
改变视图reshape
张量存储
- 张量的存储体现张量在内存上保存为一块连续的存储区域
- 张量的存储需要人为跟踪
- shape中相对靠左的维度称之为大维度;相对靠右的维度称之为小维度
张量视图
语法格式为tf.reshape(x, new_shape)
- 改变张量的视图始终不改变张量的存储顺序
- 视图变换需要满足新视图的元素总量与内存区域大小相等即可
- 为了能够正确恢复出数据,必须保证张量的存储顺序与新视图的维度顺序一致
- 在实现reshape操作的时候,需要记住张量的存储顺序
- 参数-1表示长度的自定推导
x = tf.random.normal([4, 32, 32, 3])
tf.reshape(x, [2,-1])
tf.reshape(x,[2,4,12])
tf.reshape(x,[2,-1,3])
增删维度
增加维度
- 增加一个长度为1的维度相当于是给原数据的维度增加一个新维度,可以理解成改变视图的一种特殊方式
- 数据的存储方式不变,通过函数
tf.expand_dims(x,axis)
来实现 axis
为正,表示在当前维度之前插入一个新维度;axis
为负数,在当前维度之后插入一个新维度
x = tf.random.uniform([28,28],maxval=10,dtype=tf.int32)
x = tf.expand_dims(x,axis=2)
删除维度
- 增加维度的逆操作,只能删除长度为1的维度
- 不改变张量的存储方式
- 通过
tf.squeeze(x, axis)
来实现 - axis表示删除维度的索引号;如果不指定,默认删除全部长度为1的维度
x = tf.random.uniform([1,28,28,1],maxval=10,dtype=tf.int32)
x = tf.squeeze(x, axis=2)
tf.squeeze(x)
维度交换
- 改变张量的存储,后续的所有操作都是基于新的存储顺序
- 改变张量的视图
- 通过
tf.transpose(x, perm)
来实现;其中perm
表示新维度的顺序list
x = tf.random.normal([2,32,32,3])
tf.transpose(x,perm=[0,3,1,2])
数据复制
通过函数tf.tile(x, multiples)
来实现,关于参数multiples
:
- 1表示不复制
- 2表示长度为2倍,即复制1份
- 3表示长度为3倍,即复制2份;类推
复制操作会创建一个新的张量来保存复制后的张量,涉及到大量的IO操作,运算代价大
代码语言:javascript复制b = tf.constant([1,2])
b = tf.expand_dims(b, axis=0) # 插入新维度
b = tf.tile(b, multiples=[2,1]) # axis=0上复制1份
x = tf.range(4)
x = tf.reshape(x, [2,2])
x = tf.tile(x, multiples=[1,2]) # 列上复制
x = tf.tile(x,multiples=[2,1]) # 行上复制
广播机制Broadcasting
通过函数tf.broadcast_to(x, new_shape)
实现
特点
- 自动扩展,一种轻量级张量复制;在逻辑上扩展张量数据的形状
- 对于大部 分场景,Broadcasting 机制都能通过优化手段避免实际复制数据而完成逻辑运算
- 通过优化手段避免实际复制数据而完成逻辑运算,较少计算开销
- 广播机制不会立即复制数据,逻辑上改变张量的形状
x = tf.random.normal([2,4])
w = tf.random.normal([4,3])
b = tf.random.normal([3])
y = x@w b # 等价于y = x@w tf.broadcast_to(b,[2,3]) 实现自动广播
核心思想
广播机制的核心思想是普适性,同一份数据能够适合于不同的位置
- 长度为1,默认数据适合当前维度的其他位置
- 长度不是1,增加维度后才会才适合
有些运算可以在处理不同 shape 的张量时,会隐式地调用广播机制
数学运算
方法 | 作用 |
---|---|
// | 整除 |
% | 余除 |
tf.power(x,a),$x**a$ | 乘方 |
tf.square(x) | 平方 |
tf.sqrt(x) | 平方根 |
tf.power(a,x),$a**x$ | 指数运算 |
tf.exp(x) | 自然指数 |
tf.math.log(x) | 自然对数 |
$log_ax=frac{log_ex}{log_ea}$ | 其他底数的对数 |
矩阵相乘
两种方式实现:
- @
- tf.matmul(a,b)函数
实战-前向传播
采用的手写数字图片集数据:
- 输入节点数是784,第一层节点数是256,第二次层是128,第三层是10
- 3层神经网络的实现
# 计算每个线性函数的张量参数
w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev=0.1))
b1 = tf.Variable(tf.zeros([256]))
w2 = tf.Variable(tf.random.truncated_normal([256, 128], stddev=0.1))
b2 = tf.Variable(tf.zeros([128]))
w3 = tf.Variable(tf.random.truncated_normal([128, 10], stddev=0.1))
b3 = tf.Variable(tf.zeros([10]))
# 将输入数据进行维度变化
# [b, 28, 28] => [b, 28*28]
x = tf.reshape(x, [-1, 28*28])
# 非线性函数的计算
h1 = x@w1 tf.broadcast_to(b1, [x.shape[0], 256])
h1 = tf.nn.relu(h1) # 得到输出函数
# 完成剩下两个非线性函数
# [b, 256] => [b, 128]
h2 = h1@w2 b2
h2 = tf.nn.relu(h2)
# [b, 128] => [b, 10]
out = h2@w3 b3
# 计算均方差MSE
# mse = mean(sum(y-out)^2)
# [b, 10]
loss = tf.square(y_onehot - out)
# mean: scalar
loss = tf.reduce_mean(loss)
# 计算梯度
grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3]
# w1 = w1 - lr * w1_grad
w1.assign_sub(lr * grads[0])
b1.assign_sub(lr * grads[1])
w2.assign_sub(lr * grads[2])
b2.assign_sub(lr * grads[3])
w3.assign_sub(lr * grads[4])
b3.assign_sub(lr * grads[5])
tape.gradient()
求出网络参数的梯度信息assign_sub()
:实现参数的自我更新