CV&NLP基础8之TensorFlow进阶(下)

实战Fashion-Mnsit

代码与上一篇文章基本相同,只是最下面改动成 每轮大循环都计算一下准确率,可以看出随着模型的训练、参数的修改,准确率不断波动提升

代码及运行截图

导入各种包

  1. layers里面包含了各种”层“,目前只用到Dense层(线性层)
  2. optimizers(优化器)里面包含了各种梯度下降方法,来优化模型的参数,目前用过“SGD”、“Adam”
  3. datasets包含了一些内置数据集,目前用过“Mnsit”、“Fashion-Mnsit”
  4. Sequential顺序模型是多个网络层的线性堆叠。将想要搭建的模型按顺序排放。与“layers”是包含关系
import os # “os”是系统包
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' # TensorFlow的最小打印日志的等级为2。
# TensorFlow集成了深度学习环境,每次运行都会显示一些电脑环境信息,设置了这个值,就可以隐藏一些不紧要的输出,更简洁。
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, optimizers, datasets, Sequential
from __future__ import division

运行截图:
在这里插入图片描述

获取数据

(x, y), (x_val, y_val) = datasets.fashion_mnist.load_data() 
# (x,y)是训练集;(x_val, y_val)是验证集。
print("训练集: ", x.shape, y.shape)
print(type(x),'/',type(y))
print(x.dtype, ' / ', y.dtype)
print('-----------------------------')
print("验证集: ", x_val.shape, y_val.shape)
print(type(x_val),'/',type(y_val))
print(x_val.dtype, ' / ', y_val.dtype)

运行截图:
在这里插入图片描述

处理数据

“除以255”的原因是“normalization method 归一化法”,对于计算机而言,更适合处理-1~1之间的数字

# 预处理函数,处理训练集、验证集时都用这个函数
def preprocessing(x_data, y_data): 
    x_data = tf.cast(x_data, dtype = tf.float32)/255 #不要忘记图片输入数据除以255
    y_data = tf.cast(y_data, dtype = tf.int32)
    #y_data = tf.one_hot(y_data, depth=10) #将标量y_data转换成独热编码y_data
    
    print(f'类型为: {type(x_data)} ; {type(y_data)}')
    print(f"各自里面元素类型为: {x_data.dtype} ; {y_data.dtype}")
    print('数据格式为: ', x_data.shape, y_data.shape)
    # print(tf.reduce_max(x_data), tf.reduce_min(x_data))
    print('--------------------------------------')
    
    return x_data, y_data
# 数据预处理,切片。600组,每组100对。切片、预处理顺序无所谓。一般是先切片。
# 下面两行是把数据切成“散片”,还没有batch
db_train = tf.data.Dataset.from_tensor_slices((x, y))
db_test = tf.data.Dataset.from_tensor_slices((x_val, y_val))

# 重点: 将 切成 散片的数据“反射”到与preprocessing()函数。
# 输出完毕后,再以100张图片为一组
print("训练集: ")
db_train = db_train.map(preprocessing).batch(100) # 每组100张图片,共 600 组
print("验证集: ")
db_test = db_test.map(preprocessing).batch(100)# 每组100张图片,共 100 组

运行截图:
在这里插入图片描述


# 看一下是否每一组是100张图片

# iter()将db_train变成可迭代对象,上一篇文章里面的train_dataset也可以这样操作
# sample是元组,保存了100张图片的输入、标签。里面有2个元素,某个100张图片的每张图片输入x、标签y
sample = next(iter(db_train)) 

 # 直接用sample.shape错误。sample[0]是100个图片的输入x,sample[1]是100个标签y
print(sample[0].shape, sample[1].shape)
print('-----------------------')
print(sample[0])
print('-----------------------')
print(sample[1])

运行截图:
在这里插入图片描述
在这里插入图片描述

搭建模型

model = keras.Sequential([
    layers.Dense(512, activation='relu', name='layer1'),
    layers.Dense(256, activation='relu', name='layer2'),
    layers.Dense(128, activation='relu', name='layer3'),
    layers.Dense(64, activation='relu', name='layer4'),
    layers.Dense(10, activation='relu', name='layer5'),
])
# 这里优化器不用SGD,换个用Adam,不用管具体区别,只要牢记优化器里的所有优化函数的核心就是“梯度下降”
optimizer = optimizers.Adam(learning_rate=0.0001)
# 下面两行代码只是用来看看这个框架都有些什么参数
model.build(input_shape=(None, 784)) # None意思是没有输入图片, 仅是测试而已
model.summary() # 用来看框架内部结构的函数

# 下面输出解读:
# 第一列Layer层:这里是Dense函数产生的层
# 第二列就是每道工序的“输出维度”
# 第三列的参数就是 w、b的个数,以第一层工序举例: 第一层的输入是[None ,784],
# 那么第一层的“W参数”就是[784, 512],“b参数”就是[512],所以784*512+512=401920个参数

运行截图:
在这里插入图片描述

使用“训练数据集”训练模型过程及每轮计算准确率

# 大循环,整个数据集(这里是6万张图片)循环的次数
for epoch in range(20): 
    print(f"当前是 第 {epoch} 次 大循环")
    
    # 小循环,一次循环处理一个batch(这里是100张图片),共600次小循环
    for step, (x, y) in enumerate(db_train):
        
        # 打平操作
        # x:[b, 28, 28] => [b, 784]
        # y:[b, 1] => [b, 10]
        x = tf.reshape(x, (-1, 28*28))
        
        #将y转换成独热编码,可以放在预处理函数里,也可以放在这里
        y = tf.one_hot(y, depth=10)
        
        # 构建梯度环境
        with tf.GradientTape() as tape:
            
            # 用单词“logits”代表多分类问题的预测输出,也可以跟上一篇文章一样用“out”
            # x的维度,注意要与模型搭建时给的输入维度一致
            logits = model(x)
            
            # 计算loss。下面loss_mse、loss_ce是两种计算损失值的方法
            # 均方差
            # tf.losses.MSE(y, logits)相当于tf.square(logits-y)
            loss_mse = tf.reduce_mean(tf.losses.MSE(y, logits))
            
            # 多分类常用的一种loss函数
            # categorical: 分类;crossentropy: 交叉熵
            loss_ce = tf.losses.categorical_crossentropy(y, logits, from_logits=True)
            loss_ce = tf.reduce_mean(loss_ce) # 计算平均交叉熵损失
            
        # 注意缩进
        # 计算梯度,“model.trainable_variables”里面是各个w、b参数,更新参数w、b后也放入其中 
        # grads 里面是 w、b的梯度
        grads = tape.gradient(loss_ce, model.trainable_variables)
        
        # 更新梯度 w' = w - learning rate * grad w      b' = b - learning rate * grad b
        optimizer.apply_gradients(zip(grads, model.trainable_variables))
        
        if step % 100 == 0:
            print('epoch: {}, step: {}, loss: {}'.format(epoch, step, float(loss_ce)))
    
    # 算每轮准确率
    total_correct = 0
    total_number = 0
    for (xval, yval) in db_test:
        xval = tf.reshape(xval, (-1, 784))
        logits_test = model(xval)
        probability = tf.nn.softmax(logits_test, axis=1)
        predict = tf.argmax(probability, axis=1)
        predict = tf.cast(predict, dtype=tf.int32)
        
        bool_correct = tf.equal(predict, yval)
        correct = tf.cast(bool_correct, dtype=tf.int32)
        correct = tf.reduce_sum(correct)
        total_correct += int(correct)
        total_number += yval.shape[0]
    print(f"正确个数为: {total_correct}; 总图片数是: {total_number}")
    accuracy = total_correct / total_number
    print(f"准确率为: {accuracy}")
    print('---------------------------')  

部分运行截图:
在这里插入图片描述
在这里插入图片描述



在这里插入图片描述

创建张量(tensor)

在 TensorFlow 中,可以通过多种方式创建张量,如从 Python 列表对象创建,从 Numpy 数组创建,或者创建采样自某种已知分布的张量等。

从数组、列表对象创建

Numpy Array 数组和 Python List 列表是 Python 程序中间非常重要的数据载体容器,很多数据都是通过 Python 语言将数据加载至 Array 或者 List 容器,再转换到 Tensor 类型,通过 TensorFlow 运算处理后导出到 Array 或者 List 容器,方便其他模块调用

通过 tf.convert_to_tensor 函数可以创建新 Tensor,并将保存在 Python List 对象或者 Numpy Array 对象中的数据导入到新 Tensor 中,例如:
在这里插入图片描述
需要注意的是,Numpy 浮点数数组默认使用 64 位精度保存数据,转换到 Tensor 类型时精度为 tf.float64,可以在需要的时候将其转换为 tf.float32 类型。
实际上,tf.constant()和 tf.convert_to_tensor()都能够自动的把 Numpy 数组或者 Python 列表数据类型转化为 Tensor 类型,这两个 API 命名来自 TensorFlow 1.x 的命名习惯,在 TensorFlow 2 中函数的名字并不是很贴切,使用其一即可。

创建全0或全1的张量

将张量创建为全 0 或者全 1 数据是非常常见的张量初始化手段。考虑线性变换𝒚 = 𝑾𝒙 + 𝒃,将权值矩阵𝑾初始化为全 1 矩阵,偏置 b 初始化为全 0 向量,此时线性变化层输出𝒚 = 𝒙,因此是一种比较好的层初始化状态。通过 tf.zeros()和 tf.ones()即可创建任意形状,且内容全 0 或全 1 的张量。

例如,创建为 0 和为 1 的标量:

In [24]: tf.zeros([]),tf.ones([]) # 创建全0,全1的标量
Out[24]:
(<tf.Tensor: id=90, shape=(), dtype=float32, numpy=0.0>,
<tf.Tensor: id=91, shape=(), dtype=float32, numpy=1.0>)

创建全 0 和全 1 的向量,实现如下:

In [25]: tf.zeros([1]),tf.ones([1]) # 创建全0,全1的向量 
Out[25]:
(<tf.Tensor: id=96, shape=(1,), dtype=float32, numpy=array([0.],
dtype=float32)>,
 <tf.Tensor: id=99, shape=(1,), dtype=float32, numpy=array([1.],
dtype=float32)>)

创建全 0 的矩阵,例如:

In [26]:tf.zeros([2,2]) # 创建全0矩阵,指定shape为2行2列 
Out[26]:
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[0., 0.],
       [0., 0.]], dtype=float32)>

创建全 1 的矩阵,例如:

In [27]:tf.ones([3,2]) # 创建全1矩阵,指定shape为3行2列
Out[27]:
<tf.Tensor: shape=(3, 2), dtype=float32, numpy=
array([[1., 1.],
       [1., 1.],
       [1., 1.]], dtype=float32)>

通过 tf.zeros_like, tf.ones_like 可以方便地新建与某个张量 shape 一致,且内容为全 0 或 全 1 的张量。例如,创建与张量𝑨形状一样的全 0 张量:

在这里插入图片描述


创建与张量𝑨形状一样的全 1 张量:
在这里插入图片描述

张量索引

高维索引示例:

在这里插入图片描述

start:end

在这里插入图片描述

start : end : step、 : : step

在这里插入图片描述

: : -1

解释 a[ : : -2] : 先把数组倒过来,再从左至右依据步长每两步取值
在这里插入图片描述

创建已知分布的张量

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

务必注意下面两个函数第一个参数都是“shape(维度)”

正态分布(random.normal)

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

例如,创建均值为 0,标准差为 1 的正态分布:

In [33]:tf.random.normal([2,2]) # 创建标准正态分布的张量 
Out[33]:
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[-1.2156643 , -1.6855826 ],
       [ 0.68651384, -0.60394895]], dtype=float32)>

务必注意[2, 2]指的是shape

例如,创建均值为 1,标准差为 2 的正态分布:

In [34]:tf.random.normal([2,2], mean=1,stddev=2) # 创建正态分布的张量
Out[34]:
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[-3.175376  ,  2.0691204 ],
       [-2.5508618 , -0.33188474]], dtype=float32)>

均匀分布(random.uniform)

通过 tf.random.uniform(shape, minval=0, maxval=None, dtype=tf.float32)可以创建采样自 [minval, maxval)区间的均匀分布的张量。

例如创建采样自区间[0,1),shape 为[2,2]的矩阵:

In [35]:tf.random.uniform([2,2]) # 创建采样自[0,1)均匀分布的矩阵 
Out[35]:
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[0.42767704, 0.37408912],
       [0.27021766, 0.2663933 ]], dtype=float32)>

例如,创建采样自区间[0,10),shape 为[2,2]的矩阵:

In [36]: tf.random.uniform([2,2],maxval=10) # 创建采样自[0,10)均匀分布的矩阵 
Out[36]:
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[4.2767706, 3.7408912],
       [2.7021766, 2.663933 ]], dtype=float32)>

如果需要均匀采样整形类型的数据,必须指定采样区间的最大值 maxval 参数,同时指定数据类型为 tf.int*型:

In [37]: # 创建采样自[0, 100) 均匀分布的整型矩阵
tf.random.uniform([2,2],maxval=100,dtype=tf.int32)
Out [37]:
<tf.Tensor: shape=(2, 2), dtype=int32, numpy=
array([[31, 20],
       [ 3, 66]], dtype=int32)>

改变视图(reshape)

在这里插入图片描述

增、删维度

增加维度(expand_dims)

增加一个长度为 1 的维度相当于给原有的数据添加一个新维度,维度长度为 1,故数据并不需要改变,仅仅是改变数据的理解方式,因此它其实可以理解为改变视图的一种特殊方式。

考虑一个具体例子,一张28 × 28大小的灰度图片的数据保存为 shape 为[28,28]的张量,在末尾给张量增加一新维度,定义为通道数维度,此时张量的 shape 变为[28,28,1], 实现如下:

In [72]: # 产生矩阵
x = tf.random.uniform([28,28],maxval=10,dtype=tf.int32)
Out[72]:
<tf.Tensor: shape=(28, 28), dtype=int32, numpy=
array([[7, 3, 2, 1, 6, 5, 9, 0, 9, 9, 8, 6, 7, 5, 6, 4, 9, 7, 5, 1, 4, 4,
        3, 7, 1, 9, 2, 8],
       [7, 6, 0, 2, 9, 1, 0, 7, 2, 3, 0, 0, 0, 7, 4, 7, 9, 0, 0, 5, 4, 6,
        8, 6, 9, 2, 7, 0],
       [9, 0, 1, 6, 8, 2, 2, 0, 9, 2, 1, 1, 3, 6, 1, 4, 3, 2, 0, 4, 0, 2,
        0, 9, 4, 9, 0, 6],.............

通过 tf.expand_dims(x, axis)可在指定的 axis 轴可以插入一个新的维度:

In [73]: x = tf.expand_dims(x,axis=2) # axis=2 表示宽维度后面的一个维度 
Out[73]:
<tf.Tensor: shape=(28, 28, 1), dtype=int32, numpy=
array([[[7],
        [3],
        [2],
        [1],
        [6],
        [5],
        [9],
        [0],
        [9],
        [9],
        [8],................

可以看到,插入一个新维度后,数据的存储顺序并没有改变,依然按着7,3,2,1,6,5, ⋯的顺序保存,仅仅是在插入一个新的维度后,改变了数据的视图。

同样的方法,我们可以在最前面插入一个新的维度,并命名为图片数量维度,长度为 1,此时张量的 shape 变为[1,28,28,1],实现如下:

In [74]: x = tf.expand_dims(x,axis=0) # 高维度之前插入新维度
Out[74]:
<tf.Tensor: shape=(1, 28, 28, 1), dtype=int32, numpy=
array([[[[7],
         [3],
         [2],
         [1],
         [6],
         [5],
         [9],
         [0],
         [9],
         [9],
         [8],
         [6],..................

需要注意的是,tf.expand_dims 的 axis 为正时,表示在当前维度之前插入一个新维度; 为时,表示当前维度之后插入一个新的维度。以[b, h, w, c]张量为例,不同 axis 参数的实际插入位置如下图所示:
在这里插入图片描述

根据上面所述,用下面的例子复习一下概念:
在这里插入图片描述

删除维度(squeeze)

删除维度是增加维度的逆操作,与增加维度一样,删除维度只能删除长度为 1 的维度,也不会改变张量的存储。

继续考虑增加维度后 shape 为[1,28,28,1]的例子,如果希望将图片数量维度删除,可以通过 tf.squeeze(x, axis)函数,axis 参数为待删除的维度的索引号
例如,图片数量的维度轴 axis=0:

In [75]: x = tf.squeeze(x, axis=0) # 删除图片数量维度
Out[75]:
<tf.Tensor: shape=(28, 28, 1), dtype=int32, numpy=
array([[[7],
        [3],
        [2],
        [1],
        [6],
        [5],
        [9],
        [0],
        [9],
        [9],
        [8],
        [6],..........

继续删除通道数维度,由于已经删除了图片数量维度,此时的 x 的 shape 为[28,28,1],因此删除通道数维度时指定 axis=2,实现如下:

In [76]: x = tf.squeeze(x, axis=2) # 删除图片通道数维度 
Out[76]:
<tf.Tensor: shape=(28, 28), dtype=int32, numpy=
array([[7, 3, 2, 1, 6, 5, 9, 0, 9, 9, 8, 6, 7, 5, 6, 4, 9, 7, 5, 1, 4, 4,
        3, 7, 1, 9, 2, 8],
       [7, 6, 0, 2, 9, 1, 0, 7, 2, 3, 0, 0, 0, 7, 4, 7, 9, 0, 0, 5, 4, 6,
        8, 6, 9, 2, 7, 0],
       [9, 0, 1, 6, 8, 2, 2, 0, 9, 2, 1, 1, 3, 6, 1, 4, 3, 2, 0, 4, 0, 2,
        0, 9, 4, 9, 0, 6],..................

如果不指定维度参数 axis,即 tf.squeeze(x),那么它会默认删除所有长度为 1 的维度,例如(观察输出里面的shape):
在这里插入图片描述
在这里插入图片描述

交换维度—tf.transpose(x, perm)

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

交换维度操作是非常常见的,比如在 TensorFlow 中,图片张量的默认存储格式是通道后行格式: [b, h, w, c],但是部分库的图片格式是通道先行格式: [b, c, h, w],因此需要完成 [b, h, w, c]到[b, c, h, w]维度交换运算,此时若简单的使用改变视图函数 reshape,则新视图的存储方式需要改变,因此使用改变视图函数是不合法的。

我们以[b, h, w, c]转换到[b, c, h, w]为例,介绍如何使用 tf.transpose(x, perm)函数完成维度交换操作,其中参数 perm 表示新维度的顺序 List。考虑图片张量 shape 为[2,32,32,3],

“图片数量、行、列、通道 数”的维度索引分别为 0、1、2、3,如果需要交换为[b, c, h, w]格式,则新维度的排序为 “图片数量、通道数、行、列”,对应的索引号为[0,3,1,2],因此参数 perm 需设置为 [0,3,1,2],实现如下:

注意参数perm里面放的是索引

在这里插入图片描述

如果希望将[b, h, w, c]交换为[b, w, h, c],即将高、宽维度互换,则新维度索引为 [0,2,1,3],实现如下:
在这里插入图片描述

需要注意的是,通过 tf.transpose 完成维度交换后,张量的存储顺序已经改变,视图也随之改变,后续的所有操作必须基于新的存续顺序和视图进行。相对于改变视图操作,维度交换操作的计算代价更高。

根据上面概念,复习思考下面例子:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值