zoukankan      html  css  js  c++  java
  • Tensorflow2.0语法

     

    转自 https://segmentfault.com/a/1190000020413887

    前言

    TF2.0 是之前学习的内容,当时是写在了私有的YNote中,重写于SF。
    TF2.0-GPU 安装教程传送门:https://segmentfault.com/a/11...
    之前接触过 TF1, 手动session机制,看着很是头疼。 TF2.0不需要做这些
    TF2.0 理解起来更容易(逐渐 Pythonic and Numpic)
    TF2.0 后端采用keras接口 (构建网络层),更方便。
    TF2.0 的keras接口定义的模型层,都实现了 call方法。意味大多数实例对象可以当作函数来直接调用

    行列轴

    以列表为例(抽象举例,摞起来的面包片。。。。)

    [               # 最外层,无意义不用记 
        [1,2,3],    # 面包片1  (第一个样本)
        [4,5,6],    # 面包片2  (第二个样本)
    ]
    
    1. 每个 次内层列表 代表一个样本, 比如 [1,2,3] 整体代表 第一个样本
    2. 最内层元素代表属性值。 eg: 1,2,3 单个拿出来都是属性值。
    3. 例子: 元素5 单独拿出来,它就被看做 "第二个样本的,属性值5" (当然横纵索引依然都是从0取的)

    以刚才的数据为例:

    t = tf.constant(
        [
            [1., 2., 3.], 
            [4., 5., 6.]
        ]
    )
    
    print(tf.reduce_sum(t, axis=0))  # 求和操作,上下压扁, 聚合样本
    >> tf.Tensor([5. 7. 9.], shape=(3,), dtype=float32) 
    
    print(tf.reduce_sum(t, axis=1))  # 求和操作,左右压扁, 聚合属性
    >> tf.Tensor([ 6. 15.], shape=(2,), dtype=float32)   

    注:Numpy轴也是这样的,我最初用x,y轴方式 抽象 去记忆, 基本上是记不住的。。太多概念混淆。
    但如果你记不住,你每次使用各种操作和聚合API时,都会自己在心理重新花大量时间理一遍。浪费时间。

    所以你一定要练习理解,要做到:“瞄一眼,就能知道这种维度的数据的意义,以及轴操作的意义”

    我自己的记忆方式(axis=0, axis=1):

    1. 0轴通常代表,样本(上下压扁)
    2. 1轴通常代表,属性(左右压扁)

    常需要用 axis参数 的相关聚合函数:

    tf.reduce_sum()  # 求和
    tf.reduce_mean() # 平均值
    tf.reduce_max()  # 最大值
    tf.reduce_min()  # 最小值
    tf.square()      # 平方
    tf.concat()      # 拼接
    
    注: 如果 axis参数 "不传", 那么"所有维度"都会被操作。
    

    常用导入

    # 基本会用到的
    import numpy as np
    import tensorflow as tf
    from tensorflow import keras
    
    # 可选导入
    import os, sys, pickle
    
    import scipy
    import pandas as pd
    import matplotlib.pyplot as plt
    
    from sklearn.preprocessing import StandardScaler      # 标准化
    from sklearn.model_selection import train_test_split  # 训测分离
    

    张量&操作符

    常量(普通的张量)

    定义:

    c = tf.constant( [[1., 2., 3.], [4., 5., 6.]] )    # 数字后面加个点代表 转float32 类型
    print(c)  
    >> tf.Tensor([[1. 2. 3.] [4. 5. 6.]], shape=(2, 3), dtype=float32)

    六则运算(加减乘除,矩阵乘, 矩阵转置)

    先说矩阵乘法(大学都学过的,运算过程不说了):

    语法格式:  a @ b
    条件要求:  a的列数 === b的行数     (必须相等)
    eg:       (5行2列 @ 2行10列 = 5行10列)
    
    特例: (第0维度,必须相等)
        t1 = tf.ones([2, 20, 30])
        t2 = tf.ones([2, 30, 50])
        print( (t1@t2).shape )
        >> (2, 20, 50)   # 第0维没变, 后2维照常按照矩阵乘法运算

    矩阵转置:

    tf.transpose(t)
    # 不仅可以普通转置,还可以交换维度
    t2 = tf.transpose(t,[1,0])    # 行变列,列变行。 和基本的转置差不多(逆序索引,轴变逆序)
    
    # 或假如以 (2,100,200,3)形状 为例
    t = tf.ones([2, 100, 200, 3])                 
    print(tf.transpose(t, [1, 3, 0, 2]).shape) # 轴交换位置
    >> (100, 3, 2, 200)
    # 原1轴 -> 放在现在0轴
    # 原3轴 -> 放在现在1轴
    # 原0轴 -> 放在现在2轴
    # 原2轴 -> 放在现在3轴

    加减乘除都具有"广播机制" :
    形象(广播机制解释)解释:

    我尝试用白话解释下:
        1. 我形状和你不一样, 但我和你运算的时候,我会尽力扩张成 你的形状  来和你运算。
        2. 扩张后如果出现空缺, 那么把自己复制一份,填补上 (如果补不全,就说明不能运算)
        3. 小形状 服从 大形状 (我比你瘦,我动就行。 你不用动。。。)
        eg:
            t = tf.constant(
                [
                    [1, 2, 3],
                    [4, 5, 6],
                ]
            )
            t + [1,2,1] 
        过程分析:
            [1,2,1] 显然是 小形状, 它会自动尝试变化成大形状 ->
                第一步变形(最外层大框架满足, 里面还有空缺):
                    [
                        [1,2,1],        
                    ]
                第二步变形 (把自己复制,然后填补空缺):
                    [
                        [1,2,1],
                        [1,2,1],          # 这就是复制的自己
                    ]
                第三步运算(逐位相加)
                    [             +     [              =  [
                        [1,2,3],            [1,2,1],          [2,4,4],
                        [4,5,6],            [1,2,1],          [5,7,7],
                    ]                   ]                 [

    抽象(广播机制)演示:

    假如 t1 的 shape为 [5,200,100,50]
    假如 t2 的 shape为 [5,200]
    
    注意:我以下的数据演示,全是表示 Tensor的形状,形状,形状!
        [5,200,1,50]     # 很明显,开始这2行数据 维度没匹配, 形状也没对齐
               [5,1]
    ------------------------            
        [5,200,1,50]    
              [5,50]     # 这行对齐补50
    ------------------------            
        [5,200,5,50]     # 这行对齐补5
              [5,50]
    ------------------------            
        [5,200,5,50]           
        [1, 1, 5,50]     # 这行扩张了2个, 默认填1
    ------------------------            
        [5,200,5,50]           
        [1,200, 5,50]    # 这行对齐补200
    ------------------------            
        [5,200,5,50]           
        [5,200,5,50]     # 这行对齐补5
    
    注意:
        1. 每个维度形状:二者必须有一个是1, 才能对齐。 (不然ERROR,下例ERROR->)
            [5,200,1,50]
                  [5,20]    # 同理开始向右对齐,但是  50与20都不是1,所以都不能对齐,所以ERROR
        2. 若维度缺失:
            依然是全部贴右对齐
            然后先从右面开始,补每个维度的形状 
            然后扩展维度,并默认设形状为1
            然后补扩展后维度的形状(因为默认设为1了,所以是一定可以补齐的)

    当然上面说的都是运算时的自动广播机制
    你也可以手动广播:

    t1 = tf.ones([2, 20, 1])                # 原始形状  【2,20,1】
    print(tf.broadcast_to(t1, [5,2,20,30]).shape) # 目标形状【5,2,20,30】
    
    [5,2,20,30]
      [2,20, 1]
    -----------
    [5,2,20,30]
      [2,20,30]
    -----------
    [5,2,20,30] 
    [1,2,20,30]
    -----------
    [5,2,20,30]
    [5,2,20,30]
    
    注:因为是手动广播,所以只能 原始形状 自己向 目标形状 ”补充维度,或者补充形状“
        而目标形状是一点也不能动的。

    扩充维度(f.expand_dims)+ 复制(tile) 代替 => 广播(tf.broadcasting)
    同样是上面的例子,我想把形状 [2,20,1] ,变成 [5,2,20,30]

    t1 = tf.ones([2, 20, 1])
    a = tf.expand_dims(t1,axis=0)  # 0轴索引处插入一个轴, 结果[1,2,20,1]
    print(tf.tile(a,[5,1,1,30]).shape)    # 结果 [5, 2, 20, 30]
    流程:
        [5,2,20,30]
          [2,20,1]
        -----------
        [5,2,20,30]    # tf.expand_dims(t1,axis=0)
        [1,2,20,1]     # 0号索引插入一个新轴(增维)
        -----------
        [5,2,20,30]    # tf.tile(5,1,1,30)   (形状对齐,tile每个参数代表对应轴的形状扩充几倍)
        [5,2,30,30]         1*5 2*1 20*1 1*30

    tile 与 broadcasting的区别:

    1. tile是物理复制,物理空间增加
    2. 而broadcasting是虚拟复制,(为了计算,隐式实现的复制,并没有物理空间增加)
    3. tile可以对任意(整数倍复制n*m, mn同为整数)
    4. 而broadcasting(原始数据形状只能存在1的情况下才能扩张。 1*n , n为整数)

    压缩维度(tf.squeeze):
    就是把每个维度为1的维度都删除掉 (就像数学 a * 1 = a)

    print(tf.squeeze(tf.ones([2,1,3,1])).shape)
    >>> (2, 3)
    

    当然你也可以指定维度压缩(默认不指定,所有维度为1的全部压缩):

    print(tf.squeeze(tf.ones([2,1,3,1]), axis=-1).shape)
    >>> (2, 1, 3)

    索引&切片

    灵魂说明:无论索引还是切片, (行列 是使用 逗号 分隔的), 并且无论行列,索引都是从0开始的。
    索引:取一个值

    print(t[1,2])  # 逗号前面代表行的索引, 逗号后面是列的索引
    >> tf.Tensor(6.0, shape=(), dtype=float32)

    切片:取子结构 (有两种方式)
    方式1(冒号切片):

    print(t[:, 1:])  # 逗号前面是行。只写: 代表取所有行。逗号后面是列。 1: 代表第二列到最后
    >> tf.Tensor([[2. 3.] [5. 6.]], shape=(2, 2), dtype=float32)
    

    方式2(省略号切片): (我相信不了解Numpy的人都没听说过 python的 Ellipsis , 就是省略号类)
    先自己去运行玩玩这行代码:

    print(... is Ellipsis)
    >>> True
    

    回到正题:(省略号 ... 切片,是针对多维度的, 如果是二维直接用:即可)

    (我们以三维为例,这个就不适合称作行列了)
    # shape 是 (2, 2, 2)
    t = tf.constant(
        [   # 一维
            [   # 二维
                [1, 2], # 三维
                [3, 4],
            ],
            [
                [5, 6],
                [7, 8],
            ],
        ]
    )
    伪码:t[1维切片, 二维切片, 三维切片]
    代码:t[:, :, 0:1]     # 1维不动, 2维不动, 3维 取一条数据
    结果: shape为 (2,2,1)
        [   # 一维
            [   # 二维
                [1], # 三维
                [3],
            ],
            [
                [5],
                [7],
            ],
        ]

    看不明白就多看几遍。
    发现没,即使我不对 1维,和 2维切片,我也被迫要写 2个: 来占位
    那假如有100个维度,我只想对最后一个维度切片。 前99个都不用动, 那难道我要写 99个 : 占位??
    不,如下代码即可解决:

    print(t[..., 0:1])       # 这就是 ... 的作用 (注意,只在 numpy 和 tensorflow中有用)

    tensor 转 numpy 类型

    t.numpy()    # tensor 转为 numpy 类型
    

    变量

    定义:

    v = tf.Variable(   # 注意: V是大写
        [
            [1, 2, 3], 
            [4, 5, 6]
        ]
    )

    变量赋值(具有自身赋值的性质):

    注意: 变量一旦被定义,形状就定下来了。 赋值(只能赋给同形状的值)
    
    v.assign(
        [
            [1,1,1], 
            [1,1,1],
        ]
    )
    print(v)
    >> <tf.Variable 'Variable:0' shape=(2, 3) dtype=int32, numpy=array([[1, 1, 1],[1, 1, 1]])>

    变量取值(相当于转换为Tensor):

    特别: 变量本身就是 Variable类型, 取值取出得是 Tensor (包括切片取值,索引取值等)
    print( v.value() )
    >> tf.Tensor([[1 2 3] [4 5 6]], shape=(2, 3), dtype=int32)

    变量 索引&切片 赋值:

    常量:是不可变的。所以只有取值,没有赋值。
    变量:取值、赋值都可以
    v.assign(xx)  类似于 python的 v=xx
    
    v[0, 1].assign(100)          # 索引赋值,  v.assign 等价于
    v[0, :].assign([10, 20, 30]) # 注意,切片赋值传递的需要是容器类型
    
    特别注意: 前面说过,变量 结构形状 是 不可变的,赋值的赋给的是数据。
               但是你赋值的时候要时刻注意,不能改变变量原有形状
    拿切片赋值为例:
        你切多少个,你就得赋多少个。 并且赋的值结构要一致。
        举个栗子: 你从正方体里面挖出来一个小正方体。那么你必须填补一块一模一样形状的小正方体)
        
    还有两种扩展API:
        v.assign_add()  # 类似python 的 +=
        v.assign_sub()  # 类似python 的 -=

    变量 索引&切片 取值

    同 常量切片取值(略)

    Variable 转 Numpy

    print(v.numpy())

    不规则张量(RaggedTensor)

    定义:

    rag_tensor = tf.ragged.constant(
        [ 
            [1,2], 
            [2,3,4,5],
        ]
    )    # 允许每个维度的数据长度参差不齐

    拼接:假如需要"拼接 不规则张量" (可使用 tf.concat(axis=) )

    0轴:竖着拼接(样本竖着摞起来)可随意拼接。 拼接后的依然是"不规则张量"
    1轴:横着拼接(属性水平拼起来)这时候需要你样本个数必须相等, 否则对不上,报错
    总结: 样本竖着随便拼, 属性横着(必须样本个数相等) 才能拼

    RaggedTensor 普通 Tensor:

    说明:普通Tensor是必须要求, 长度对齐的。入 对不齐的 末尾补0
    tensor = rag_tensor.to_tensor()

    稀疏张量 (Sparse Tensor)

    特点(可理解为 记录索引):

    1. 只记录非0的坐标位置, indices参数:每个 子列表 表示 一个坐标
    2. 虽然只记录坐标,但是转为普通Tensor后,只有坐标位置 有值, 其他位置的值全是0
    3. 填充范围,取决于 dense_shape的设定

    定义:

    s = tf.SparseTensor(
        indices=[[0, 1], [1, 0], [2, 3]],  # 注意,这个索引设置需要是(从左到右,从上到下)的顺序设置
        values=[1, 2, 3],   # 将上面3个坐标值分别设值为 1,2,3
        dense_shape=[3, 4]  # Tensor总范围
    )
    print(s)
    >> SparseTensor(indices=tf.Tensor([[0 1], [1 0],[2 3]], shape=(3, 2), dtype=int64)。。。

    转为普通 Tensor (转为普通Tensor后,看见的才是存储真正的值)

    tensor = tf.sparse.to_dense(s) 
    print(tensor)
    >> tf.Tensor([ [0 1 0 0],[2 0 0 0],[0 0 0 3] ], shape=(3, 4), dtype=int32)

    如果上面使用 to_dense() 可能会遇到错误:

    error: is out of range
    
    这个错误的原因是创建 tf.SparseTensor(indices=) ,前面也说了indices,要按(从左到右,从上到下)顺序写
    当然你也可以用排序API,先排序,然后再转:
    eg:
        _ = tf.sparse.reorder(s)        # 先将索引排序
        tensor = tf.sparse.to_dense(_)  # 再转

    tf.function

    这个API作为一个装饰器使用, 用来将 Python 语法转换 尽可能有效的转换为 TF语法、图结构

    import tensorflow as tf
    import numpy as np
    
    @tf.function
    def f():
        a = np.array([1, 2, 3])
        b = np.array([4, 5, 6])
        return a + b
    print( f() )
    >>> tf.Tensor([5 7 9], shape=(3,), dtype=int32)
    你应该发现了一个特点,我们定义的 f()函数内部,一个tf语法都没写。 只装饰了一行 @tf.function
    而调用结果返回值居然是个 tensor 。
    这就是 @tf.function 装饰器的作用了!

    当然函数里面,也可以写 tf的操作,也是没问题的。
    但注意一点, 函数里面不允许定义 变量, 需要定义的变量 应 拿到函数外面定义

    a = tf.Variable([1,2,3])  # 如需tensor变量,应该放在外面
    
    @tf.function
    def f():
        # a = tf.Variable([1,2,3])  # 这里面不允许定义变量!
        pass

    合并相加(tf.concat)

    我的理解就是(基本数学的 合并同类项)

    # 合并同类项的原则就是有1项不同,其他项完全相同。
    # 前提条件:(最多,有一个维度的形状不相等。   注意是最多)
    t1 = tf.ones([2,5,6])
    t2 = tf.ones([6,5,6])
    print( tf.concat([t1,t2],axis=0).shape )   # axis=0,传了0轴,那么其他轴捏死不变。只合并0轴
    >> (8,5,8) 
    

    堆叠降维(tf.stack)

    我的理解就是(小学算术的,进位,(进位就是扩充一个维度表示个数))

    # 前提条件:所有维度形状必须全部相等。
    tf1 = tf.ones([2,3,4])
    tf2 = tf.ones([2,3,4])
    tf3 = tf.ones([2,3,4])
    print(tf.stack([tf1,tf2,tf3], axis=0).shape)
    # 你可以想象有3组 [2,3,4],然后3组作为一个新维度,插入到 axis对应的索引处。
    >> (3, 2, 3, 4)        # 对比理解,如果这是tf.concat(), 那么结果就是 (6,3,4)

    拆分降维(tf.unstack)

    和tf.stack正好是互逆过程,指定axis维度是几,它就会拆分成几个数据,同时降维。

    a = tf.ones([3, 2, 3, 4])
    for x in tf.unstack(a, axis=0):
        print(x.shape)
    
    结果如下(分成了3个 [2,3,4])
    >>> (2, 3, 4)
    >>> (2, 3, 4)
    >>> (2, 3, 4)

    拆分不降维(tf.split)

    语法:

    和tf.unstack的区别就是,tf.unstack是均分降维, tf.stack是怎么分都不会降维,且能指定分隔份数

    a = tf.ones([2,4,35,8])
    for x in tf.split(a, axis=3,num_or_size_splits=[2,2,4]):
        print(x.shape)
        
    结果: 
        >> (2, 4, 35, 2)  # 最后一维2
        >> (2, 4, 35, 2)  # 最后一维2
        >> (2, 4, 35, 4)  # 最后一维4 

    使用场景:

    假如我们想切分数据集为(train-test-valid) 3分比例为 6:2:2
    方法1:(scikit-learn 连续分切2次)

    x_train, x_test, y_train, y_test = train_test_split(x,y,test_size=0.2)
    x_train, x_valid, y_train, y_valid = train_test_split(x_train, y_train,test_size=0.2)
    # 源码中显示  test_size如果不传。默认为0.25。
    # 思路,因为 scikit-learn 只能切出2个结果: 所以我们需要切2次:
        # 第一次 从完整训练集 切出来 (剩余训练集, 测试集)
        # 第二次 从剩余数据集 切出来 (剩余训练集2, 验证集)

    方法2: (tf.split)

    x = tf.ones([1000, 5000])
    y = tf.ones([1000, 1])
    
    x_train, x_test, x_valid = tf.split(
        x, 
        num_or_size_splits=[600,200,200],        # 切3份
        axis=0
    )
    y_train, y_test, y_valid = tf.split(
        y,
        num_or_size_splits=[600,200,200],       #  同样切3份
        axis=0
    )
    print(x_train.shape, y_train.shape)
    print(x_test.shape, y_test.shape)
    print(x_valid.shape, y_valid.shape)
    
    结果
    >>>  (600, 5000) (600, 1)
    >>>  (200, 5000) (200, 1)
    >>>  (200, 5000) (200, 1)

    高级索引(tf.gather)

    numpy这种索引叫做 fancy indexing(如果我没记错的话)

    data = tf.constant([6,7,8])        # 当作真实数据
    index = tf.constant([2, 1, 0])     # 当作索引
    print(tf.gather(data, index))    
    >> tf.Tensor([8 7 6], shape=(3,), dtype=int32)

    排序(tf.sort)

    data = tf.constant([6, 7, 8])
    print(tf.sort(data, direction='DESCENDING')) # 'ASCENDING'
    # 默认是ASCENDING升序
    
    tf.argsort()    # 同上, 只不过返回的是排序后的,对应数据的index

    Top-K(tf.math.top_k)

    查找出最大的n个(比先排序然后切片的性能要好)

    a = tf.math.top_k([6,7,8],2)   # 找出最大的两个,返回是个对象
    print(a.indices)    # 取出最大的两个 索引 ()
    print(a.values)     # 取出最大的两个 值
    >> tf.Tensor([2 1], shape=(2,), dtype=int32)
    >> tf.Tensor([8 7], shape=(2,), dtype=int32)

    tf.GradientTape (自定义求导)

    求偏导

    v1, v2 = tf.Variable(1.), tf.Variable(2.) # 变量 会 被自动侦测更新的
    c1, c2 = tf.constant(1.), tf.constant(2.) # 常量 不会 自动侦测更新
    
    y = lambda x1,x2: x1**2 + x2**2
    
    with tf.GradientTape(persistent=True) as tape:
        """默认这个 tape使用一次就会被删除, persistent=True 代表永久存在,但后续需要手动释放"""
        # 因为常量不会被自动侦测,所以我们需要手动调用 watch() 侦测
        tape.watch(c1) # 如果是变量,就不用watch这两步了
        tape.watch(c2)
        f = y(c1,c2)   # 调用函数,返回结果
    c1_, c2_ = tape.gradient(f, [c1,c2])  
        # 参数2:传递几个自变量,就会返回几个 偏导结果
        # c1_ 为 c1的偏导
        # c2_ 为 c2的偏导
    del tape  # 手动释放 tape

    求二阶偏导(gradient嵌套)

    v1, v2 = tf.Variable(1.), tf.Variable(2.) # 我们使用变量
    
    y = lambda x1,x2: x1**2 + x2**2
    with tf.GradientTape(persistent=True) as tape2:
        with tf.GradientTape(persistent=True) as tape1:
            f = y(v1,v2)
        once_grads = tape1.gradient(f, [v1, v2])       # 一阶偏导        
    # 此列表推导式表示:拿着一阶偏导,来继续求二阶偏导(注意,用tape2)
    twice_grads = [tape2.gradient(once_grad, [v1,v2]) for once_grad in once_grads] # 二阶偏导
    
    print(twice_grads)
    del tape1    # 释放
    del tape2    # 释放

    说明

    求导数(一个自变量):tape1.gradient(f, v1)       # gradient传 1个自变量
    求偏导(多个自变量):tape1.gradient(f, [v1,v2])  # gradient传 1个列表, 列表内填所有自变量

    SGD(随机梯度下降)

    方式1:手撕(不使用优化器)

    v1, v2 = tf.Variable(1.), tf.Variable(2.)  # 我们使用变量
    y = lambda x1, x2: x1 ** 2 + x2 ** 2       # 二元二次方程
    
    learning_rate = 0.1                        # 学习率
    for _ in range(30):                        # 迭代次数
        with tf.GradientTape() as tape:        # 求导作用域
            f = y(v1,v2)
        d1, d2 = tape.gradient(f, [v1,v2])     # 求导, d1为 v1的偏导,  d2为v2的偏导
        v1.assign_sub(learning_rate * d1)
        v2.assign_sub(learning_rate * d2)
    print(v1)
    print(v2)
    
    实现流程总结:
        1. 偏导 自变量v1,v2求出来的。    (d1, d2 = tape.gradient(f, [v1,v2]))
        2. 自变量v1,v2的衰减 是关联 偏导的( 衰减值 = 学习率*偏导)
        3. 我们把前2步套了一个大循环(并设定迭代次数),  1-2-1-2-1-2-1-2-1-2 步骤往复执行

    方式2:借用 Tensorflow 优化器(optimizer) 实现梯度下降

    v1, v2 = tf.Variable(1.), tf.Variable(2.)  # 我们使用变量
    y = lambda x1, x2: x1 ** 2 + x2 ** 2       # 二元二次函数 ,  通常这个函数我们用作计算loss
    
    learning_rate = 0.1                        # 学习率
    optimizer = keras.optimizers.SGD(learning_rate=learning_rate)    # 初始化优化器
    for _ in range(30):                        # 迭代次数
        with tf.GradientTape() as tape:
            f = y(v1,v2)
        d1, d2 = tape.gradient(f, [v1,v2])     # d1为 v1的偏导,  d2为v2的偏导
    
        optimizer.apply_gradients(             # 注意这里不一样了,我们之前手动衰减
            [                                  # 而现在这些事情, optimizer.SGD帮我们做了
                (d1, v1),                      # 我们只需把偏导值,和自变量按这种格式传给它即可
                (d2, v2),
            ]
        )                                      
        # 通常这种格式,我们用 zip() 实现
        # eg:
            # model = keras.models.Sequential([......])
            # .......
            # grads = tape.gradient(f, [v1,v2])
            # optimizer.apply_gradients(
            #     zip(grads, model.trainable_variables)
            # )
    print(v1)
    print(v2)
    
    实现流程总结:
    1. 偏导 是自变量v1,v2求出来的    (d1, d2 = tape.gradient(f, [v1,v2])) # 此步骤不变
    2. 把偏导 和 自变量 传给optimizer.apply_gradients()  optimizer.SGD() 自动帮我们衰减。
    3. 我们还是把前2步套了一个大循环(并设定迭代次数),  1-2-1-2-1-2-1-2-1-2 步骤往复执行。
    
    注: 假如你用adam等之类的其他优化器,那么可能有更复杂的公式,如果我们手撕,肯能有些费劲。
         这时候我们最好使用 optimizer.Adam ...等各种 成品,优化器。通用步骤如下
         1. 先实例化出一个优化器对象
         2. 实例化对象.apply_gradients([(偏导,自变量)])
  • 相关阅读:
    c#对XML读取
    WPF--TypeConverter使用
    WPF---对于没有Command属性的添加以下代码可以达到有Command效果
    自定义事件、属性、方法
    读取Excel文件
    ClickOnce安装部署,手动。
    Logger 日志记录
    Maven
    等待与通知范式
    线程状态及基本方法
  • 原文地址:https://www.cnblogs.com/whw1314/p/12121874.html
Copyright © 2011-2022 走看看