深度学习_05_2_TensorFlow2高阶_填充与复制&张量限幅&高阶操作

05_2_TensorFlow2高阶_填充与复制&张量限幅&高阶操作

填充与复制

Outline

  • pad 数据的填充
  • tile 数据的复制
  • broadcast_to

Pad(填充)

补充0,-1或者其他,默认填充0

举例子:shape为[3]

A表示左边填充1个单元,B表示右边填充2个单元。

=> [6]

例子:[2,2]

[ [0,1], [1,1] ] 上面补充0行,下面补充1行;左边补充1列,右边补充1列

在这里插入图片描述

具体的实例:

tf.pad(a,[[1,0],[0,0]])

在这里插入图片描述

在这里插入图片描述

Image padding

行和列,上下和左右padding2个单位

在这里插入图片描述

一开始 B略<A,我们为了使B=A,首先将A ->(padding) A’,A’会略>A

A’->B,可能等于A。选择合适的padding数量。

先使A放大一点点,A’通过convolution后,会得到B,B的size略<A’,这样的话A和B是有可能相等的。

这是image之间做convolution非常常见的操作。

在这里插入图片描述

tile(复制)

按不同维度复制会有不同的结果。就是绕着某个维度进行重复的复制的工作。

Inner dim first

tf.tile(a,[1,2])

1表示第1个维度不复制,2表示第2个维度复制1次。
1可以理解为保持不变,2表示当前的维度复制1次,新的Tensor是之前的2倍。

在这里插入图片描述

tile VS broadcast_to

最终的效果可能是一样的,但是从性能上来讲broadcast更强,它没有占用另外的内存,是运行时候完成的优化的操作。

并且broadcast有时不需要主动转换,有些操作会自动转换broadcast。

在这里插入图片描述

张量限幅

Outline

  • clip_by_value 通过具体数值裁减掉
  • relu 是通过clipping实现的
  • clip_by_norm 根据总体的范数来裁剪张量
  • gradient clipping 根据一群norm的总体的值来裁剪每一个张量的norm:gradient clipping非常常见

clip_by_value

根据值的裁剪,是通常意义上的限幅。

maximum(0,x),如果x<0, result=0;else x

minimum(0,x),如果x>0,result=0;else x

如果要将数据限制在某个区间内,比如2~8之间:min(8,max(x,2))

以下的方式更加简便:tf.clip_by_value(a,2,8)

给定最小值和最大值,底层还是通过maximum和minimum实现的,

上面代码的下限幅是2,上限幅是8。

在这里插入图片描述

relu

利用限幅实现rulu函数

tf.maximum(a,0) == tf.nn.relu(a)

在这里插入图片描述

clip_by_norm

根据范数来进行裁剪。

对一个Vector,它本身是有方向的,gradient代表函数的值增加的方向,负gradient代表函数的值减小的方向。如果我们在gradient clipping的时候,就是当Tensor的值变大变小的时候,如果我们是根据值来进行裁剪的话,这个Vector(x,y),如果x成分裁减了,y成分没有裁剪的话,就意味着改变了Vector的方向。对于gradient来说,我们是不希望改变gradient的方向的,因为gradient方向是我们已经计算出来的,如果做clipping的时候改变gradient的方向的话,这样是不利于找到最优解的,所以我们既需要把值给限制到比较小的范围之内,但是又不希望改变方向的话,就有等比例的放缩,首先把整体的模求出来,
( x , y ) ∣ ∣ ( x , y ) ∣ ∣ ( 二 范 数 ) \frac{(x,y)}{||(x,y)||(二范数)} (x,y)()(x,y),这样归一化到0~1的范围之内,然后再乘以一个值,比如希望整体的模最大值是15,那么 ( x , y ) ∣ ∣ ( x , y ) ∣ ∣ ∗ 15 \frac{(x,y)}{||(x,y)||} * 15 (x,y)(x,y)15,这样的范围就约束在15之内了,这样就实现了等比例的放缩。既没有改变Tensor的方向,然后又使Tensor整体的数值减少了,这就是clip_by_norm的功能。

tf.norm(a)默认为二范数, ∑ x 2 \sqrt{\sum x^2} x2

tf.clip_by_norm(a,15)对a根据norm进行放缩,a的新的二范数为15.000001

限幅就是限制新的norm,a->aa保持方向不变,只改变向量的模

在这里插入图片描述

Gradient clipping(梯度裁剪)

所有的这些都是创造良好的环境,使得我们的gradient能很好的引导我们的网络走向比较优良的方向,也就是loss下降的方向。

  • 其中有两大障碍,Gradient Exploding(梯度爆炸)和vanishing(梯度消失)
    • Gradient Exploding就是gradient的值太大了,一次前进的步长太长了,这样会导致不停地来回震荡
    • Gradient Vanishing就是值太少了,长时间不动,loss一直不动
  • 为了使MNIST上模拟复现这些问题,在案例中set lr = 1
  • 有Gradient-w1,Gradient-w2,首先w1是一个方向,w2是一个方向,他们都是一个向量,方向是多维的,它不再是1维、2维的,有几百维几千维。我们可以通过clip_by_norm来使得w1的方向不变,w1的norm减少,但是w1和w2本身都是代表了整体的梯度下降的方向,也就是这个网络的下降方向不只有w1的参数决定,还有很多其他的参数w1,w2,w3。我们希望w1,w2,w3在做缩放的时候,里面的所有方向都不做改变
  • 可以使用**new_grads, total_norm = tf.clip_by_global_norm(grads,25)**
  • 实现对整体的所有的norm等比例缩放,而不会出现w1缩放10倍,w2缩放1倍,w3缩放0.1倍。 [ w 1 , w 2 , w 3 ] ∣ ∣ g _ w 1 ∣ ∣ + ∣ ∣ g _ w 2 ∣ ∣ \frac{[w1,w2,w3]}{||g\_w1||+||g\_w2||} g_w1+g_w2[w1,w2,w3]
  • 传入grads,[g_w1,g_w2, …]
  • 给定一个25,是指所有gradient整体的norm,所以比一般的clip_by_norm要稍微大一点, ∣ ∣ g _ w 1 ∣ ∣ + ∣ ∣ g _ w 2 ∣ ∣ ||g\_w1||+||g\_w2|| g_w1+g_w2
  • total_norm为之前没有做clipping时候的norm,可能有100
  • new_grads对每一个w1进行缩放, w 1 2 \frac{w1}{2} 2w1

具体的实例:

一般来说,范数在0~20之间是比较合适的。更新1次后,范数越来越大,意味着gradient exploding;如果范数接近0.0001的时候就是gradient vanish。可以看到这里已经出现了gradient exploding,这个时候就需要进行裁剪(clipping)。

在这里插入图片描述

因此我们需要对w1,w2,w3,b1,b2,b3的模做一个裁剪,裁剪的时候保持一个同比例,比如每个都裁剪4倍,利用clip_by_global_norm保持等比例的裁剪,只需要把所有的grads [g_w1,g_b1, …]保持整体norm是15。分别打印裁剪前和裁剪后的梯度值。

在这里插入图片描述

保证裁剪以后的范数是比较小的范围,这样更新是比较稳定的。

在这里插入图片描述

这个能不能收敛呢?看一个实例。

import  os
os.environ['TF_CPP_MIN_LOG_LEVEL']='2'

import  tensorflow as tf
from    tensorflow import keras
from    tensorflow.keras import datasets, layers, optimizers

print(tf.__version__)

(x, y), _ = datasets.mnist.load_data()
x = tf.convert_to_tensor(x, dtype=tf.float32) / 50.
y = tf.convert_to_tensor(y)
y = tf.one_hot(y, depth=10)
print('x:', x.shape, 'y:', y.shape)
train_db = tf.data.Dataset.from_tensor_slices((x,y)).batch(128).repeat(30)
x,y = next(iter(train_db))
print('sample:', x.shape, y.shape)
# print(x[0], y[0])

def main():
    # 784 => 512
    w1, b1 = tf.Variable(tf.random.truncated_normal([784, 512], stddev=0.1)), tf.Variable(tf.zeros([512]))
    # 512 => 256
    w2, b2 = tf.Variable(tf.random.truncated_normal([512, 256], stddev=0.1)), tf.Variable(tf.zeros([256]))
    # 256 => 10
    w3, b3 = tf.Variable(tf.random.truncated_normal([256, 10], stddev=0.1)), tf.Variable(tf.zeros([10]))

    optimizer = optimizers.SGD(lr=0.01)

    for step, (x,y) in enumerate(train_db):

        # [b, 28, 28] => [b, 784]
        x = tf.reshape(x, (-1, 784))

        with tf.GradientTape() as tape:

            # layer1.
            h1 = x @ w1 + b1
            h1 = tf.nn.relu(h1)
            # layer2
            h2 = h1 @ w2 + b2
            h2 = tf.nn.relu(h2)
            # output
            out = h2 @ w3 + b3
            # out = tf.nn.relu(out)

            # compute loss
            # [b, 10] - [b, 10]
            loss = tf.square(y-out)
            #下面两个合在一起和loss = tf.reduce_mean(loss)等价
            #下面第1个先计算每个batch上的均值,下面第2个计算每个instance上每一个点的均值
            # [b, 10] => [b]
            loss = tf.reduce_mean(loss, axis=1)
            # [b] => scalar
            loss = tf.reduce_mean(loss)

        # compute gradient
        grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3])

        print('==before==')
        for g in grads:
            print(tf.norm(g))
        grads,  _ = tf.clip_by_global_norm(grads, 15)
        print('==after==')
        for g in grads:
            print(tf.norm(g))
            
        # update w' = w - lr*grad
        optimizer.apply_gradients(zip(grads, [w1, b1, w2, b2, w3, b3]))

        if step % 100 == 0:
            print(step, 'loss:', float(loss))


if __name__ == '__main__':
    main()

高阶操作技巧

Outline

  • where 根据坐标有目的性的选择
  • scatter_nd 根据坐标有目的性的更新
  • meshgrid 生成坐标系

Where(tensor)

Tensor是boolean的类型,where对这个Tensor操作时会返回一系列的坐标,就是当前True所在的坐标。比如对于一个shape为[3,3]的Tensor,一共有3个True元素,因此第一个维度会返回3;每个坐标是2维的Tensor。->[3,2],3行2列的Tensor,这个是shape。实际会返回:

[[0,0]
[1,1]
[2,2]]

返回三个这样的坐标,是一个[3,2]的Tensor。拿到后可以和之前的tf.gather_nd一起,收集到对应元素为True的位置的值。

在这里插入图片描述

a是3行3列的随机分布,与0比较得到mask,返回一个类型为Boolean型的Tensor,shape不变。

通过使用tf.boolean_mask(a,mask)取得元素位置上为True的对应元素。

tf.where(mask)查询到所有元素为True的值的坐标。

结合tf.gather_nd(a,indices),取回的都是True上的值。

where返回的Tensor的shape为[N,n维],N为True的数量,n为原来的Tensor是几维。

在这里插入图片描述

如果where接收到3个参数tf.where(cond,A,B),它会根据中间的True or False,从A、B中间有目的性的选择值。True的元素会从A上面选,False的元素会从B上面选。

在这里插入图片描述

scatter_nd

根据坐标来有目的性的更新,首先给出原始状态(底板),底板是根据shape给定的,比如[2,2] [8],给出全是0的底板,给出具体的数和坐标,将该坐标下更新这个值到底板上去。

在这里插入图片描述

首先看一个1维的例子。

它可以实现两个功能:

  1. 更新:对指定位置清零,再把新的值A’‘加到A-A’
  2. 对指定位置的值的相加/相减

在这里插入图片描述

在看2维的例子:

白色表示0,将updatas的值更新到shape中,获得output。

在这里插入图片描述

实现方法:

在这里插入图片描述

meshgrid

如果要画一个3D的图片,先要生成3D的坐标轴,下图最下的面为坐标轴,根据点来存元素的值,叫做z。这样可以得到每一个点上的函数的值,就能把函数很好的画出来。

在这里插入图片描述

Points

x:-2~2
y:-2~2

再给出采样的间隔,在-2~2之间采样5个点,这样x上有5个点,y上有5个点,一共生成25个点。[25,2],一共有25个点,每个点的坐标用两个数字来表示,就会生成这样的Tensor。

在这里插入图片描述

怎么得到这样坐标的Tensor?

Numpy

两层嵌套,-2~2间隔5个点,生成点保存到list去,在转换为np的array。[25,2]

但是这个方法是没有GPU加速的;而且是numpy实现的,无法和TensorFlow深度结合在一起。

在这里插入图片描述

GPU acceleration

目的:给出x: [-2~2], y: [-2,2],生成Points: [N,2]

实现:

y = tf.linspace(-2.,2,5)
x = tf.linspace(-2.,2,5)
points_x,points_y = tf.meshgrid(x,y)

在这里插入图片描述

point_x和point_ya拼在一起组合成[5,5,2]的Tensor,前面的一部分存储在points_x,后面的一部分存储在points_y,其实是把一个坐标拆分开来,保存在两个Tensor中去。前半部分是x的坐标,后半部分是y的坐标

在这里插入图片描述

目前是[5,5,2],还原成[25,2]的格式:利用tf.stack,再利用reshape。

axis=2返回的结果为[5,5,2],因为函数使得points_x的x和points_y的y维度合并。

在这里插入图片描述

画出函数的曲面 z = s i n ( x ) + s i n ( y ) z = sin(x) + sin(y) z=sin(x)+sin(y)

在这里插入图片描述

实现:

import  os
os.environ['TF_CPP_MIN_LOG_LEVEL']='2'

import  tensorflow as tf
from matplotlib import pyplot as plt

def func(x):
    '''

    :param x: [b,2]
    :return:
    '''
    z = tf.math.sin(x[...,0]) + tf.math.sin(x[...,1])
    return z

x = tf.linspace(0.,2*3.14,500)
y = tf.linspace(0.,2*3.14,500)
#[500,500]
point_x,point_y = tf.meshgrid(x,y)
#[500,500,2]
points = tf.stack([point_x,point_y], axis=2)
# points = tf.reshape(points,[-1,2])
print('points:',points.shape)
z = func(points)
print('z:',z.shape)

plt.figure('plot 2d func value')
plt.imshow(z,origin='lower',interpolation='none')
plt.colorbar()

plt.figure('plot 2d func contour')
plt.contour(point_x,point_y,z)
plt.colorbar()
plt.show()
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值