AI => Tensorflow2.0語法 - 張量&基本函數(一)

前言

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方法。意味大多數實例對象能夠看成函數來直接調用python

行列軸

以列表爲例(抽象舉例,摞起來的麪包片。。。。)segmentfault

[               # 最外層,無心義不用記 
    [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):session

  1. 0軸一般表明,樣本(上下壓扁)
  2. 1軸一般表明,屬性(左右壓扁)

常須要用 axis參數 的相關聚合函數:app

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([(偏導,自變量)])
相關文章
相關標籤/搜索