Tensorflow2.X入門指南

概述

??本文是一篇較為基礎(chǔ)的深度學(xué)習(xí)框架Tensorflow2.x入門文章叉橱,在開始之前先安裝Tensorflow肮韧,當(dāng)然如果你已安裝区匠,可以直接跳過绣张。

!pip install tensorflow

??Tensorflow有CPU和GPU兩個(gè)版本背亥,GPU在深度學(xué)習(xí)中的重要性不言而喻睬愤,上面的命令默認(rèn)安裝CPU版本待榔。如準(zhǔn)備安裝GPU版本屁使,需先配置CUDA 和 cuDNN减细,具體內(nèi)容在此不做展開匆瓜,有興趣的讀者可以參考我的另一篇博文Ubuntu深度學(xué)習(xí)環(huán)境搭建。安裝完成后未蝌,使用下面命令檢查是否正確安裝Tensorflow并被配置CUDA 和 cuDNN環(huán)境驮吱。

import tensorflow as tf
tf.config.experimental.list_physical_devices('GPU')

??由于我的主機(jī)只有一張RTX2070,所以只輸出了一個(gè)GPU設(shè)備,并且設(shè)備編號(hào)為GPU:0

[PhysicalDevice(name='/physical_device:GPU:0', device_type='GPU')]

Part 1: TensorFlow 基礎(chǔ)

張量

??張量是對(duì)數(shù)組和矩陣的一種擴(kuò)展,張量的嚴(yán)格定義是利用線性映射來描述的,與矢量相類似萧吠,定義由若干坐標(biāo)系改變時(shí)滿足一定坐標(biāo)轉(zhuǎn)化關(guān)系的有序數(shù)組成的集合為張量左冬。 從幾何角度講, 它是一個(gè)真正的幾何量纸型,也就是說拇砰,它是一個(gè)不隨參照系的坐標(biāo)變換(其實(shí)就是基向量變化)而變化的東西。最后結(jié)果就是基向量與對(duì)應(yīng)基向量上的分量的組合(也就是張量)保持不變狰腌,比如一階張量(向量)T可表示為T = xi + yj除破。(i,j被稱為向量空間的基向量,基(basis)(也稱為基底)是描述琼腔、刻畫向量空間的基本工具瑰枫。向量空間的基是它的一個(gè)特殊的子集,基的元素稱為基向量丹莲。向量空間中任意一個(gè)元素躁垛,都可以唯一地表示成基向量的線性組合剖毯。如果基中元素個(gè)數(shù)有限,就稱向量空間為有限維向量空間教馆,將元素的個(gè)數(shù)稱作向量空間的維數(shù)逊谋。)由于基向量的特性,張量可以表示非常豐富的物理量土铺。

??簡(jiǎn)而言之張量是對(duì)數(shù)組和矩陣的一種擴(kuò)展胶滋,多維數(shù)組就是張量在編程中的具體實(shí)現(xiàn),所以在實(shí)際編程中等同于多維數(shù)組進(jìn)行理解即可悲敷。其中零階張量是標(biāo)量(常數(shù))究恤,一階張量是向量,二階張量是矩陣后德。

維基百科:張量

知乎:什么是張量部宿?


生成一個(gè)張量常數(shù)(constant tensor):

tf.constant(value, dtype=None, shape=None, name='Const', verify_shape=False)

value為常量值或者列表,dtype為類型瓢湃,shape為張量形狀理张,name為名稱、verify_shape為用于驗(yàn)證值的形狀绵患,默認(rèn)False雾叭。

需要注意的是,張量常數(shù)類似于C++的const常量落蝙,一旦定義就不能再改變?nèi)≈怠?/p>

x = tf.constant([[5, 2], [1, 3]])
print(x)
tf.Tensor(
[[5 2]
 [1 3]], shape=(2, 2), dtype=int32)

通過使用張量的.numpy()屬性取得張量的值

x.numpy()
array([[5, 2],
       [1, 3]], dtype=int32)

張量和Numpy的數(shù)組很相似织狐,都具有.dtype,.shape屬性

print('dtype:', x.dtype)
print('shape:', x.shape)
dtype: <dtype: 'int32'>
shape: (2, 2)

可以使用tf.ones and tf.zeros創(chuàng)建常量值張量 (類似于np.ones and np.zeros):

  • tf.zeros(shape, dtype=tf.float32, name=None)

    創(chuàng)建一個(gè)所有元素都設(shè)置為零的張量。shape為張量形狀筏勒,dtype為類型移迫,name為名稱。

  • tf.zeros_like(tensor, dtype=None, name=None, optimize=True)

    給定一個(gè)張量(tensor)管行,該操作返回與給定的張量相同類型和形狀的張量厨埋,該返回張量的所有元素會(huì)被設(shè)置為零〔⊥或者使用dtype指定返回張量的新類型揽咕。tensor為給定的張量悲酷,dtype為類型套菜,name為名稱,optimize為優(yōu)化項(xiàng)设易,如果為true逗柴,則嘗試靜態(tài)確定“張量”的形狀并將其編碼為常量。

  • tf.onestf.ones_like的用法和上面一致

print(tf.ones(shape=(2, 1)))
print(tf.zeros(shape=(2, 1)))
print(tf.zeros_like(x).shape)
tf.Tensor(
[[1.]
 [1.]], shape=(2, 1), dtype=float32)
tf.Tensor(
[[0.]
 [0.]], shape=(2, 1), dtype=float32)
(2, 2)
  • f.fill(dims, value, name=None)

    tf.zeros()tf.ones()是以0和1填充張量顿肺,而tf.fill()則可以指定要填充的值戏溺。參數(shù)dims表示輸出張量的形狀渣蜗。value為要填充的值。name為名稱旷祸。

tf.fill([3,3],10)
<tf.Tensor: shape=(3, 3), dtype=int32, numpy=
array([[10, 10, 10],
       [10, 10, 10],
       [10, 10, 10]], dtype=int32)>
  • tf.linspace(start, stop, num, name=None)

    產(chǎn)生一個(gè)等差數(shù)列一維向量耕拷,初始值是start、結(jié)束值是stop托享,個(gè)數(shù)是num骚烧。這個(gè)數(shù)列每次的增量是(stop - start)/(num-1)。
    在使用時(shí)發(fā)現(xiàn)闰围,如果start和stop為整數(shù)時(shí)赃绊,會(huì)報(bào)錯(cuò)Could not find valid device for node,暫時(shí)不知道原因。

tf.linspace(1.0,10.0,10)
<tf.Tensor: shape=(10,), dtype=float32, numpy=array([ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10.], dtype=float32)>
  • tf.range(start, limit, delta=1, dtype=None, name='range')

    產(chǎn)生一個(gè)等差數(shù)列的一維向量羡榴,初始值是start碧查,增量是delta,結(jié)束值小于limit校仑。start是初始值忠售,如果不指定,默認(rèn)是0肤视;delta是增量档痪,默認(rèn)是1。需要注意的是邢滑,生成的整數(shù)集合是一個(gè)左開右閉區(qū)間腐螟,即不包括右端點(diǎn),和Python的range函數(shù)相同困后。

print(tf.range(10))
print(tf.range(1,10))
tf.Tensor([0 1 2 3 4 5 6 7 8 9], shape=(10,), dtype=int32)
tf.Tensor([1 2 3 4 5 6 7 8 9], shape=(9,), dtype=int32)

隨機(jī)張量常數(shù)

  • tf.random.normal(shape, mean=0.0, stddev=1.0, dtype=tf.dtypes.float32, seed=None, name=None)

    隨機(jī)生成符合正態(tài)分布的張量常數(shù)

tf.random.normal(shape=(2, 2), mean=0., stddev=1.)
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[-1.0821191 ,  0.22778863],
       [ 1.0631582 ,  0.00430578]], dtype=float32)>
  • tf.random.uniform(shape, minval=0, maxval=None, dtype=tf.dtypes.float32, seed=None, name=None)

    隨機(jī)生成符合均勻分布的張量常數(shù)乐纸,minval要生成隨機(jī)數(shù)的下限,maxval要生成隨機(jī)數(shù)的上限

tf.random.uniform(shape=(2, 2), minval=0, maxval=10, dtype='int32')
<tf.Tensor: shape=(2, 2), dtype=int32, numpy=
array([[3, 9],
       [5, 1]], dtype=int32)>

變量

??變量是一種特殊的張量摇予,用于存儲(chǔ)動(dòng)態(tài)變化的張量(比如神經(jīng)網(wǎng)絡(luò)中的權(quán)重和偏置) 汽绢,Tensorlfow使用tf.Variable()創(chuàng)建變量,如果在tf.device作用域內(nèi)聲明侧戴,則變量將被存儲(chǔ)在該設(shè)備上宁昭;否則,變量將被存儲(chǔ)在與其dtype兼容的“最快”設(shè)備上(這意味著大多數(shù)變量將自動(dòng)放置在GPU上)酗宋。例如积仗,以下代碼片段創(chuàng)建一個(gè)名為的變量 v并將其放置在第二個(gè)GPU設(shè)備上:

with tf.device("/device:GPU:1"):
    v = tf.Variable(tf.zeros([10, 10]))
initial_value = tf.random.normal(shape=(2, 2))
a = tf.Variable(initial_value)
print(a)
with tf.device('/device:CPU:1'):
    b = tf.Variable(tf.zeros([10,10]))
<tf.Variable 'Variable:0' shape=(2, 2) dtype=float32, numpy=
array([[ 0.7042782 , -0.53633606],
       [ 1.292148  , -0.3515567 ]], dtype=float32)>
b
<tf.Variable 'Variable:0' shape=(10, 10) dtype=float32, numpy=
array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=float32)>

??使用 .assign(value), .assign_add(increment),.assign_sub(decrement)方法更新變量的值;

  • tensor.assign(values)

    使用values更新tensor的值

  • tensor.assign_add(increment)

    使用tensor+increment更新tensor的值

  • tensor.assign_sub(decrement)

    使用tensor-decrement更新tensor的值

new_value = tf.random.normal(shape=(2, 2))
a.assign(new_value)
for i in range(2):
  for j in range(2):
    assert a[i, j] == new_value[i, j]
added_value = tf.random.normal(shape=(2, 2))
a.assign_add(added_value)
for i in range(2):
  for j in range(2):
    assert a[i, j] == new_value[i, j] + added_value[i, j]

數(shù)學(xué)運(yùn)算

Tensorflow的張量支持常規(guī)的數(shù)學(xué)運(yùn)算,四則運(yùn)算蜕猫,開方寂曹,求冪等。

  • tf.multiply(tensor1,tensor2)

    計(jì)算兩個(gè)張量對(duì)應(yīng)元素各自相乘

  • tf.matmul(tensor1隆圆,tensor2)

    實(shí)現(xiàn)兩個(gè)張量之間的矩陣乘法

a = tf.random.normal(shape=(2, 2))
b = tf.random.normal(shape=(2, 2))

c = a + b
d = tf.square(c)
e = tf.exp(d)

使用GradientTape實(shí)現(xiàn)自動(dòng)微分

??Tensorflow使用tf.GradientTape()實(shí)現(xiàn)自動(dòng)微分:

  • tf.GradientTape(persistent=False,watch_accessed_variables=True)

    persistent: 布爾值漱挚,用來指定新創(chuàng)建的gradient tape是否是可持續(xù)性的。默認(rèn)是False渺氧,意味著只能調(diào)用gradient()函數(shù)一次,即只對(duì)導(dǎo)數(shù)旨涝。

    watch_accessed_variables: 布爾值,表明這個(gè)gradien tap是不是會(huì)自動(dòng)追蹤任何能被訓(xùn)練(trainable)的變量侣背。默認(rèn)是True颊糜。要是為False的話,意味著你需要手動(dòng)去指定你想追蹤的那些變量秃踩。

  • tape.watch(tensor)

    確保tensor被tape追蹤,以記錄在上下文環(huán)境中對(duì)該張量的操作

  • gradient(target,sources,output_gradients=None,unconnected_gradients=tf.UnconnectedGradients.NONE)

    根據(jù)tape上面的上下文來計(jì)算某個(gè)或者某些tensor的梯度

    target: 被微分的Tensor或者Tensor列表衬鱼,你可以理解為經(jīng)過某個(gè)函數(shù)之后的值

    sources: Tensors 或者Variables列表(當(dāng)然可以只有一個(gè)值),可以理解為多元函數(shù)的變量集合憔杨,如二元函數(shù)f(x,y)分別對(duì)x,y求偏導(dǎo),代碼如下:

    with tf.GradientTape(persistent=False,watch_accessed_variables=True) as tape:
        f_x,f_y = tape.gradient(f,[x,y])
    

    return:一個(gè)列表,存儲(chǔ)各個(gè)變量的梯度值鸟赫,和source中的變量列表一一對(duì)應(yīng),表明這個(gè)變量的梯度消别。

在上下文內(nèi)部的持久性磁帶上調(diào)用GradientTape.gradient()效率要比在上下文外部進(jìn)行調(diào)用的效率低得多(這會(huì)導(dǎo)致求導(dǎo)操作記錄在磁帶上抛蚤,從而導(dǎo)致CPU和內(nèi)存使用量增加)。如果您實(shí)際上要跟蹤導(dǎo)數(shù)以計(jì)算高階導(dǎo)數(shù)寻狂,則僅能在上下文內(nèi)調(diào)用GradientTape.gradient()岁经。

a = tf.random.normal(shape=(2, 2))
b = tf.random.normal(shape=(2, 2))

with tf.GradientTape(persistent=True,watch_accessed_variables=True) as tape:
    # 開始記錄對(duì)張量a的左右操作
    tape.watch(a)
    # 使用張量定義函數(shù)
    f = tf.sqrt(tf.square(a) + tf.square(b))  
    # 求f關(guān)于a,b的偏導(dǎo)數(shù)
    f_a = tape.gradient(f,a)
    f_a1 = tape.gradient(f_a,a)
print(f_a1)
WARNING:tensorflow:Calling GradientTape.gradient on a persistent tape inside its context is significantly less efficient than calling it outside the context (it causes the gradient ops to be recorded on the tape, leading to increased CPU and memory usage). Only call GradientTape.gradient inside the context if you actually want to trace the gradient in order to compute higher order derivatives.
tf.Tensor(
[[6.9585860e-02 3.9474440e-01]
 [1.1950731e-04 1.6646397e-01]], shape=(2, 2), dtype=float32)

一般會(huì)默認(rèn)對(duì)變量進(jìn)行追蹤,所以不需要顯式指定

a = tf.Variable(a)

with tf.GradientTape() as tape:
  c = tf.sqrt(tf.square(a) + tf.square(b))
  dc_da = tape.gradient(c, a)
  print(dc_da)
tf.Tensor(
[[ 0.7646002   0.48451895]
 [-0.30007327  0.5063112 ]], shape=(2, 2), dtype=float32)

也可以通過嵌套tf.GradientTape計(jì)算高階導(dǎo)數(shù)

with tf.GradientTape() as outer_tape:
  with tf.GradientTape() as tape:
    c = tf.sqrt(tf.square(a) + tf.square(b))
    dc_da = tape.gradient(c, a)
  dc = outer_tape.gradient(dc_da, a)
  print(dc)
tf.Tensor(
[[0.18161985 0.72908604]
 [0.88818395 0.3431898 ]], shape=(2, 2), dtype=float32)

示例一

??接下來基于一個(gè)構(gòu)造的線性可分的二分類數(shù)據(jù)集蛇券,使用Tensorflow訓(xùn)練單層前饋神經(jīng)網(wǎng)絡(luò)來作為這個(gè)數(shù)據(jù)集的二分類器缀壤。

class Perceptron(object):

    def __init__(self):

        self.input_dim = 2
        self.output_dim = 1
        self.learning_rate = 0.01
        self.w = tf.Variable(tf.random.uniform(shape=(self.input_dim, self.output_dim)))
        self.b = tf.Variable(tf.zeros(shape=(self.output_dim,)))

    def compute_predictions(self,features):
        return tf.matmul(features, self.w) + self.b

    def compute_loss(self,labels, predictions):
        return tf.reduce_mean(tf.square(labels - predictions))

    def train_on_batch(self,x, y):

        with tf.GradientTape(persistent=False,watch_accessed_variables=True) as tape:  
            predictions = self.compute_predictions(x)
            loss = self.compute_loss(y, predictions)
            dloss_dw, dloss_db = tape.gradient(loss, [self.w, self.b])
        self.w.assign_sub(self.learning_rate * dloss_dw)
        self.b.assign_sub(self.learning_rate * dloss_db)
        return loss

生成二分類數(shù)據(jù)集用于訓(xùn)練分類器

import numpy as np
import random
import matplotlib.pyplot as plt
%matplotlib inline

num_samples = 10000
# 負(fù)樣本
negative_samples = np.random.multivariate_normal(
    mean=[0, 3], cov=[[1, 0.5],[0.5, 1]], size=num_samples)
# 正樣本
positive_samples = np.random.multivariate_normal(
    mean=[3, 0], cov=[[1, 0.5],[0.5, 1]], size=num_samples)
# 沿著豎直方向?qū)?shù)組堆疊起來。將正樣本和負(fù)樣本進(jìn)行拼接得到數(shù)據(jù)集
features = np.vstack((negative_samples, positive_samples)).astype(np.float32)
# 生成正負(fù)樣本對(duì)應(yīng)的標(biāo)簽
labels = np.vstack((np.zeros((num_samples, 1), dtype='float32'),
                    np.ones((num_samples, 1), dtype='float32')))

plt.scatter(features[:, 0], features[:, 1], c=labels[:, 0])
png

接下來纠亚,通過將數(shù)據(jù)逐次迭代訓(xùn)練感知器

# 生成隨機(jī)數(shù)據(jù)序列
indices = np.random.permutation(len(features))
features = features[indices]
labels = labels[indices]
percep = Perceptron()
# 使用tf.data.Dataset對(duì)象來批量迭代數(shù)據(jù)進(jìn)行訓(xùn)練
dataset = tf.data.Dataset.from_tensor_slices((features, labels))
dataset = dataset.shuffle(buffer_size=1024).batch(256)

for epoch in range(10):
  for step, (x, y) in enumerate(dataset):
    loss = percep.train_on_batch(x, y)
  print('Epoch %d: last batch loss = %.4f' % (epoch, float(loss)))
Epoch 0: last batch loss = 0.0634
Epoch 1: last batch loss = 0.0325
Epoch 2: last batch loss = 0.0262
Epoch 3: last batch loss = 0.0275
Epoch 4: last batch loss = 0.0211
Epoch 5: last batch loss = 0.0216
Epoch 6: last batch loss = 0.0264
Epoch 7: last batch loss = 0.0172
Epoch 8: last batch loss = 0.0256
Epoch 9: last batch loss = 0.0250
predictions = percep.compute_predictions(features)
plt.scatter(features[:, 0], features[:, 1], c=predictions[:, 0] > 0.5)
png

使用tf.function為網(wǎng)絡(luò)訓(xùn)練加速

??Tensorflow 2.0默認(rèn)開啟eager模式塘慕,即動(dòng)態(tài)圖模式,可以立即得到運(yùn)行結(jié)果蒂胞,便于調(diào)試图呢。但是由于動(dòng)態(tài)圖無法進(jìn)行圖優(yōu)化,其計(jì)算速度比靜態(tài)圖慢,為此官方推出靜態(tài)圖轉(zhuǎn)換器tf.function可以顯著提升網(wǎng)絡(luò)的訓(xùn)練速度骗随。

import time

t_start = time.time()
for epoch in range(20):
  for step, (x, y) in enumerate(dataset):
    loss = percep.train_on_batch(x, y)
t_end = time.time() - t_start
print('訓(xùn)練耗時(shí): %.3f s' % (t_end / 20,))

訓(xùn)練耗時(shí): 0.126 s

通過在函數(shù)上添加tf.function裝飾器蛤织,將訓(xùn)練函數(shù)編譯成靜態(tài)圖形。

@tf.function
def train_on_batch(x, y):
  with tf.GradientTape() as tape:
    predictions = percep.compute_predictions(x)
    loss = percep.compute_loss(y, predictions)
    dloss_dw, dloss_db = tape.gradient(loss, [percep.w, percep.b])
  percep.w.assign_sub(percep.learning_rate * dloss_dw)
  percep.b.assign_sub(percep.learning_rate * dloss_db)
  return loss

轉(zhuǎn)為靜態(tài)圖之后的訓(xùn)練速度

t_start = time.time()
for epoch in range(20):
  for step, (x, y) in enumerate(dataset):
    loss = train_on_batch(x, y)
t_end = time.time() - t_start
print('訓(xùn)練耗時(shí): %.3f s' % (t_end / 20,))
訓(xùn)練耗時(shí): 0.069 s

??可以看到鸿染,對(duì)于這個(gè)簡(jiǎn)單的模型使用靜態(tài)圖轉(zhuǎn)換器后指蚜,訓(xùn)練時(shí)間減少了將近47%的時(shí)間,而對(duì)于更大牡昆、更復(fù)雜的模型靜態(tài)圖轉(zhuǎn)換器的效果越是顯著姚炕。動(dòng)態(tài)圖模式在調(diào)試和打印輸出是很方便的,但是在實(shí)際部署和訓(xùn)練時(shí)丢烘,靜態(tài)圖是更好的選擇柱宦。

Part 2: Keras API

??Keras是基于Python的深度學(xué)習(xí)庫,目前已被納入Tensorflow體系播瞳,成為Tensorflow的高級(jí)API掸刊,由于其易于擴(kuò)展的高度模塊化設(shè)計(jì),能夠以最快的速度實(shí)現(xiàn)idea赢乓,備受歡迎忧侧。

Layer

??Layer是Keras最基礎(chǔ)也是最重要的類,一個(gè)Layer封裝了一個(gè)狀態(tài)(權(quán)重)和一些計(jì)算(在call方法中定義),Keras基于Layer來搭建更復(fù)雜的模型牌芋,接下我們來實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Layer類蚓炬。

from tensorflow.keras.layers import Layer
import tensorflow as tf

class Linear(Layer):
  """
  構(gòu)建一個(gè)簡(jiǎn)易的Layer類,主要實(shí)現(xiàn)y = w.x + b的計(jì)算
  """
  def __init__(self, units=32, input_dim=32):
      super(Linear, self).__init__()
      w_init = tf.random_normal_initializer()
      self.w = tf.Variable(
          initial_value=w_init(shape=(input_dim, units), dtype='float32'),
          trainable=True)
      b_init = tf.zeros_initializer()
      self.b = tf.Variable(
          initial_value=b_init(shape=(units,), dtype='float32'),
          trainable=True)

  def call(self, inputs):
      return tf.matmul(inputs, self.w) + self.b

linear_layer = Linear(4, 2)
y = linear_layer(tf.ones((2, 2)))
assert y.shape == (2, 4)

Layer 類的weights負(fù)責(zé)追蹤記錄權(quán)重和偏置的變化

assert linear_layer.weights == [linear_layer.w, linear_layer.b]

先回顧下Linear在定義權(quán)重時(shí)是怎么進(jìn)行初始化的:

w_init = tf.random_normal_initializer()
self.w = tf.Variable(initial_value=w_init(shape=shape, dtype='float32'))

先創(chuàng)建了一個(gè)正態(tài)分布初始器躺屁,然后再用于初始化權(quán)重變量肯夏。

采用add_weight的方式來進(jìn)行初始化會(huì)更簡(jiǎn)潔一些:

self.w = self.add_weight(shape=shape, initializer='random_normal')

另外,在上面的寫法中犀暑,我們?cè)陬惖某跏己瘮?shù)__init__中創(chuàng)建并初始化權(quán)重和偏置驯击,為此我們需要傳入網(wǎng)絡(luò)的輸入和輸出維度。最好的做法是使用一個(gè)單獨(dú)的build方法來創(chuàng)建權(quán)重和偏置耐亏,只有在調(diào)用該方法時(shí)才創(chuàng)建并初始化權(quán)重和偏置徊都,并且無需傳入網(wǎng)絡(luò)的輸入維度。

class Linear(Layer):
  """y = w.x + b"""
  def __init__(self, units=32):
      super(Linear, self).__init__()
      self.units = units

  def build(self, input_shape):
      self.w = self.add_weight(shape=(input_shape[-1], self.units),
                               initializer='random_normal',
                               trainable=True)
      self.b = self.add_weight(shape=(self.units,),
                               initializer='random_normal',
                               trainable=True)

  def call(self, inputs):
      return tf.matmul(inputs, self.w) + self.b


# 初始化層時(shí)只需傳入網(wǎng)絡(luò)的輸出維度即可
linear_layer = Linear(4)

# This will also call `build(input_shape)` and create the weights.
y = linear_layer(tf.ones((2, 2)))
assert len(linear_layer.weights) == 2

Trainable

網(wǎng)絡(luò)的權(quán)重在被創(chuàng)建時(shí)可以指定Trainable為True广辰,即定義該變量為可訓(xùn)練的參數(shù)暇矫,而對(duì)于一些記錄計(jì)算步驟的'步驟'變量顯然是不可訓(xùn)練的,tf.GradientTape默認(rèn)追蹤可訓(xùn)練變量择吊。詳情如下袱耽,創(chuàng)建一個(gè)不能被訓(xùn)練的權(quán)重變量。

tf.reduce_sum(input_tensor, axis=None, keepdims=False, name=None)

根據(jù)軸(axis)計(jì)算張量指定維度的和干发,需要注意的是朱巨,其計(jì)算規(guī)則與numpy的sum正好相反。axis=0時(shí)枉长,按列求和冀续,axis=1時(shí),按行求和必峰,如下:

x = tf.constant([[1, 1, 1], [1, 1, 1]])
tf.reduce_sum(x)  # 6
tf.reduce_sum(x, axis=0)  # [2, 2, 2]
tf.reduce_sum(x, axis=1)  # [3, 3]
tf.reduce_sum(x, axis=1, keepdims=True)  # [[3], [3]]
tf.reduce_sum(x, [0, 1])  # 6
 from tensorflow.keras.layers import Layer

class ComputeSum(Layer):
  """Returns the sum of the inputs."""

  def __init__(self, input_dim):
      super(ComputeSum, self).__init__()
      # Create a non-trainable weight.
      self.total = tf.Variable(initial_value=tf.zeros((input_dim,)),
                               trainable=False)

  def call(self, inputs):
      self.total.assign_add(tf.reduce_sum(inputs, axis=0))
      return self.total  

my_sum = ComputeSum(2)
x = tf.ones((2, 2))

y = my_sum(x)
print(y.numpy())  # [2. 2.]

y = my_sum(x)
print(y.numpy())  # [4. 4.]

assert my_sum.weights == [my_sum.total]
assert my_sum.non_trainable_weights == [my_sum.total]
assert my_sum.trainable_weights == []
[2. 2.]
[4. 4.]

創(chuàng)建深度網(wǎng)絡(luò)

??在上文中洪唐,我們已經(jīng)實(shí)現(xiàn)了單層神經(jīng)網(wǎng)絡(luò),接下來通過遞歸嵌套單層網(wǎng)絡(luò)來創(chuàng)建更大的計(jì)算塊吼蚁,每一層將跟蹤其子層的權(quán)重(可訓(xùn)練和不可訓(xùn)練)凭需。接下來使用之前定義的Linear類和其build方法來遞歸創(chuàng)建更復(fù)雜问欠、更深的網(wǎng)絡(luò)。需要注意的是粒蜈,目前比較主流的觀點(diǎn)是顺献,更深的神經(jīng)網(wǎng)絡(luò)(即隱藏層更多)往往比更寬的神經(jīng)網(wǎng)絡(luò)有著更好的表征能力并且也更容易訓(xùn)練。

class MLP(Layer):
    """Simple stack of Linear layers."""

    def __init__(self):
        super(MLP, self).__init__()
        self.linear_1 = Linear(32)
        self.linear_2 = Linear(32)
        self.linear_3 = Linear(10)

    def call(self, inputs):
        x = self.linear_1(inputs)
        x = tf.nn.relu(x)
        x = self.linear_2(x)
        x = tf.nn.relu(x)
        return self.linear_3(x)

mlp = MLP()

# 初始化網(wǎng)絡(luò)
y = mlp(tf.ones(shape=(3, 32)))

# 權(quán)重將被遞歸追蹤
assert len(mlp.weights) == 6

Keras內(nèi)置層

Keras提供了廣泛的內(nèi)置層枯怖,不需要對(duì)模型進(jìn)行更細(xì)粒度的控制或者實(shí)現(xiàn)新穎的結(jié)構(gòu)注整,一般不需要自己進(jìn)行實(shí)現(xiàn)。keras已實(shí)現(xiàn)的層如下:

  • Convolution layers
  • Transposed convolutions
  • Separateable convolutions
  • Average and max pooling
  • Global average and max pooling
  • LSTM, GRU (with built-in cuDNN acceleration)
  • BatchNormalization
  • Dropout
  • Attention
  • ConvLSTM2D
  • etc.

更多詳細(xì)內(nèi)容請(qǐng)參考官方文檔
Keras內(nèi)建層

溫馨提示

??Keras的內(nèi)置層中有一些較為特殊度硝,比如BatchNormalization層和Dropout層肿轨,在訓(xùn)練和推理期間具有不同的行為。對(duì)于此類層蕊程,標(biāo)準(zhǔn)做法是在call方法中公開訓(xùn)練(布爾)參數(shù)椒袍。 通過在調(diào)用中公開此參數(shù),可以啟用內(nèi)置的訓(xùn)練和評(píng)估循環(huán)(例如藻茂,擬合)以在訓(xùn)練和推理中正確使用該圖層槐沼。


tf.keras.layers.Dropout(rate, noise_shape=None, seed=None, **kwargs)

每次訓(xùn)練時(shí)隨機(jī)忽略一部分神經(jīng)元,這些神經(jīng)元被靜默了捌治。換句話講岗钩,這些神經(jīng)元在正向傳播時(shí)對(duì)下游的啟動(dòng)影響被忽略,反向傳播時(shí)也不會(huì)更新權(quán)重肖油,使得網(wǎng)絡(luò)對(duì)某個(gè)神經(jīng)元的權(quán)重變化更不敏感兼吓,增加泛化能力,減少過擬合,其中rate為被靜默的比例森枪。更詳細(xì)內(nèi)容請(qǐng)參考Srivastava等大牛在2014年的論文《Dropout: A Simple Way to Prevent Neural Networks from Overfitting》


tf.keras.layers.BatchNormalization(
    axis=-1,
    momentum=0.99,
    epsilon=0.001,
    center=True,
    scale=True,
    beta_initializer='zeros',
    gamma_initializer='ones',
    moving_mean_initializer='zeros',
    moving_variance_initializer='ones',
    beta_regularizer=None,
    gamma_regularizer=None,
    beta_constraint=None,
    gamma_constraint=None,
    renorm=False,
    renorm_clipping=None,
    renorm_momentum=0.99,
    fused=None,
    trainable=True,
    virtual_batch_size=None,
    adjustment=None,
    name=None,
    **kwargs,
)

批量標(biāo)準(zhǔn)化層 (Ioffe and Szegedy, 2014)
在每一個(gè)批次的數(shù)據(jù)中標(biāo)準(zhǔn)化前一層的激活項(xiàng)视搏,即,應(yīng)用一個(gè)維持激活項(xiàng)平均值接近0县袱,標(biāo)準(zhǔn)差接近1的轉(zhuǎn)換浑娜。

  • Paramas:

    axis: 整數(shù),需要標(biāo)準(zhǔn)化的軸 (通常是特征軸)式散。 例如筋遭,在 data_format="channels_first"的 Conv2D 層之后, 在 BatchNormalization中設(shè)置 axis=1暴拄。

    momentum: 移動(dòng)均值和移動(dòng)方差的動(dòng)量漓滔。

    epsilon: 增加到方差的小的浮點(diǎn)數(shù),以避免除以零乖篷。

    center: 如果為 True响驴,把 beta 的偏移量加到標(biāo)準(zhǔn)化的張量上。 如果為 False撕蔼, beta 被忽略豁鲤。

    scale: 如果為 True秽誊,乘以 gamma。 如果為 False琳骡,gamma 不使用锅论。 當(dāng)下一層為線性層(或者例如 nn.relu), 這可以被禁用日熬,因?yàn)榭s放將由下一層完成。

    beta_initializer: beta 權(quán)重的初始化方法肾胯。

    gamma_initializer: gamma 權(quán)重的初始化方法竖席。

    moving_mean_initializer: 移動(dòng)均值的初始化方法。

    moving_variance_initializer: 移動(dòng)方差的初始化方法敬肚。

    beta_regularizer: 可選的 beta 權(quán)重的正則化方法毕荐。

    gamma_regularizer: 可選的 gamma 權(quán)重的正則化方法。

    beta_constraint: 可選的 beta 權(quán)重的約束方法艳馒。

    gamma_constraint: 可選的 gamma 權(quán)重的約束方法憎亚。

批標(biāo)準(zhǔn)化技術(shù)能顯著提升神經(jīng)網(wǎng)絡(luò)的訓(xùn)練速度和泛化能力,是神經(jīng)網(wǎng)絡(luò)中比較重要的優(yōu)化技術(shù)之一弄慰。想要進(jìn)一步了解其原理的讀者第美,請(qǐng)參考原論文
Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift

from tensorflow.keras.layers import Layer

class Dropout(Layer):
  
  def __init__(self, rate):
    super(Dropout, self).__init__()
    self.rate = rate

  def call(self, inputs, training=None):
    if training:
      return tf.nn.dropout(inputs, rate=self.rate)
    return inputs

class MLPWithDropout(Layer):

  def __init__(self):
      super(MLPWithDropout, self).__init__()
      self.linear_1 = Linear(32)
      self.dropout = Dropout(0.5)
      self.linear_3 = Linear(10)

  def call(self, inputs, training=None):
      x = self.linear_1(inputs)
      x = tf.nn.relu(x)
      x = self.dropout(x, training=training)
      return self.linear_3(x)
    
mlp = MLPWithDropout()
y_train = mlp(tf.ones((2, 2)), training=True)
y_test = mlp(tf.ones((2, 2)), training=False)

函數(shù)式API

??要構(gòu)建深度學(xué)習(xí)模型,不必一直使用面向?qū)ο蟮木幊搪剿>W(wǎng)絡(luò)層也可以使用Tensorflow的函數(shù)式API按功能進(jìn)行組合什往,如下所示:

# 使用Input描述神經(jīng)網(wǎng)絡(luò)輸入的形狀和類型
inputs = tf.keras.Input(shape=(16,))


x = Linear(32)(inputs) 
x = Dropout(0.5)(x) 
outputs = Linear(10)(x)

# 通過指定輸入和輸出來定義功能性的"模型"。 
# 模型本身就是一個(gè)網(wǎng)絡(luò)層慌闭。
model = tf.keras.Model(inputs, outputs)

# 在調(diào)用任何數(shù)據(jù)之前别威,功能模型已經(jīng)具有權(quán)重。這是因?yàn)樵贗nput中預(yù)先定義了其輸入形狀驴剔。
assert len(model.weights) == 4


y = model(tf.ones((2, 16)))
assert y.shape == (2, 10)

??Keras功能性API是一種創(chuàng)建模型的方法省古,該模型比tf.keras.SequentialAPI更靈活。功能性API可以處理具有非線性拓?fù)涞哪P蜕ナВ哂泄蚕韺拥哪P鸵约熬哂卸鄠€(gè)輸入或輸出的模型豺妓。深度學(xué)習(xí)模型通常是層的有向無環(huán)圖(DAG)的主要思想,因此,功能性API是一種構(gòu)建層圖的方法布讹。

??對(duì)于具有單個(gè)輸入和單個(gè)輸出的簡(jiǎn)單圖層堆疊的模型科侈,可以直接使用Sequential類將圖層列表轉(zhuǎn)換為Model。

from tensorflow.keras import Sequential

model = Sequential([Linear(32), Dropout(0.5), Linear(10)])

y = model(tf.ones((2, 16)))
assert y.shape == (2, 10)

損失函數(shù)

Keras實(shí)現(xiàn)了目前大部分主流的損失函數(shù)炒事,例如BinaryCrossentropy臀栈,CategoricalCrossentropyKLDivergence等挠乳。
需要特別注意的是訓(xùn)練多分類神經(jīng)網(wǎng)絡(luò)時(shí)一般使用交叉熵?fù)p失函數(shù)权薯,需要注意的好是:

  • 對(duì)于二分類神經(jīng)網(wǎng)絡(luò)姑躲,一般使用BinaryCrossentropy交叉熵函數(shù)作為損失函數(shù):
tf.keras.losses.BinaryCrossentropy(
    from_logits=False, label_smoothing=0, reduction=losses_utils.ReductionV2.AUTO,
    name='binary_crossentropy'
)
  • 對(duì)于標(biāo)簽為整數(shù)形式的多分類神經(jīng)網(wǎng)絡(luò),一般使用SparseCategoricalCrossentropy交叉熵函數(shù)作為損失函數(shù):
tf.keras.losses.SparseCategoricalCrossentropy(
    from_logits=False, reduction=losses_utils.ReductionV2.AUTO,
    name='sparse_categorical_crossentropy'
)
  • 對(duì)于標(biāo)簽為為One-Hot形式的多分類神經(jīng)網(wǎng)絡(luò)盟蚣,一般使用CategoricalCrossentropy交叉熵函數(shù)作為損失函數(shù):
tf.keras.losses.CategoricalCrossentropy(
    from_logits=False, label_smoothing=0, reduction=losses_utils.ReductionV2.AUTO,
    name='categorical_crossentropy'
)

Note:三個(gè)函數(shù)都有一個(gè)共同的參數(shù)from_logits黍析,該參數(shù)表示是否將網(wǎng)絡(luò)輸出層中使用Logits函數(shù)或Softmax函數(shù)(多維形式的Logits)激活的輸出映射為真實(shí)值,默認(rèn)為False,將輸出值映射真實(shí)值再帶入損失函數(shù)計(jì)算屎开。

使用方法如下:

bce = tf.keras.losses.BinaryCrossentropy()
y_true = [0., 0., 1., 1.]  # Targets
y_pred = [1., 1., 1., 0.]  # Predictions
loss = bce(y_true, y_pred)
print('Loss:', loss.numpy())
Loss: 11.522857

需要注意的是阐枣,模型的損失是無狀態(tài)的:__call__的輸出僅是輸入的函數(shù)。

評(píng)價(jià)函數(shù)

Keras還實(shí)現(xiàn)了評(píng)價(jià)函數(shù)奄抽,例如BinaryAccuracy蔼两,AUCFalsePositives等逞度。評(píng)價(jià)函數(shù)用于評(píng)估當(dāng)前訓(xùn)練模型的性能额划。當(dāng)模型編譯后(compile),評(píng)價(jià)函數(shù)應(yīng)該作為metrics的參數(shù)來輸入档泽。與損失函數(shù)不同俊戳,評(píng)價(jià)函數(shù)是有狀態(tài)的」菽洌可以使用update_state方法更新其狀態(tài)抑胎,并使用result查詢標(biāo)量度量結(jié)果:

m = tf.keras.metrics.AUC()
m.update_state([0, 1, 1, 1], [0, 1, 0, 0])
print('第一步更新的結(jié)果:',m.result().numpy())

m.update_state([1, 1, 1, 1], [0, 1, 1, 0])
print('第二步更新的結(jié)果:', m.result().numpy())
第一步更新的結(jié)果: 0.6666667
第二步更新的結(jié)果: 0.71428573

可以使用etric.reset_states清除評(píng)價(jià)函數(shù)內(nèi)部狀態(tài)。

當(dāng)然渐北,如果有必要圆恤,也可以通過將Metric類子類化來輕松推出自己的指標(biāo):

  • __init__中創(chuàng)建狀態(tài)變量
  • update_state中更新給定y_truey_pred的變量 返回結(jié)果中的度量結(jié)果
  • 清除reset_states中的狀態(tài)

通過下面的例子來演示如何實(shí)現(xiàn)自己的評(píng)價(jià)類:

class BinaryTruePositives(tf.keras.metrics.Metric):

  def __init__(self, name='binary_true_positives', **kwargs):
    super(BinaryTruePositives, self).__init__(name=name, **kwargs)
    self.true_positives = self.add_weight(name='tp', initializer='zeros')

  def update_state(self, y_true, y_pred, sample_weight=None):
    # 將張量轉(zhuǎn)為布爾類型
    y_true = tf.cast(y_true, tf.bool)
    y_pred = tf.cast(y_pred, tf.bool)

    values = tf.logical_and(tf.equal(y_true, True), tf.equal(y_pred, True))
    values = tf.cast(values, self.dtype)
    if sample_weight is not None:
      sample_weight = tf.cast(sample_weight, self.dtype)
      values = tf.multiply(values, sample_weight)
    self.true_positives.assign_add(tf.reduce_sum(values))

  def result(self):
    return self.true_positives

  def reset_states(self):
    self.true_positive.assign(0)

m = BinaryTruePositives()
m.update_state([0, 1, 1, 1], [0, 1, 0, 0])
print('第一步更新的結(jié)果:', m.result().numpy())

m.update_state([1, 1, 1, 1], [0, 1, 1, 0])
print('第二步更新的結(jié)果:', m.result().numpy())
第一步更新的結(jié)果: 1.0
第二步更新的結(jié)果: 3.0

優(yōu)化器

??深度學(xué)習(xí)問題可以歸結(jié)為一個(gè)最優(yōu)化問題:最優(yōu)化最小目標(biāo)函數(shù),通過使用各種優(yōu)化器來更新和計(jì)算影響神經(jīng)網(wǎng)絡(luò)中的參數(shù)腔稀,使目標(biāo)函數(shù)逼近或達(dá)到最優(yōu)值盆昙,從而最小化目標(biāo)函數(shù)。常見的優(yōu)化器有Adadelta焊虏,Adagrad淡喜,RMSProp,Adam等诵闭,關(guān)于優(yōu)化器的原理由此篇幅有限在此不做展開炼团,有興趣的讀者,可以參考下面這篇博客疏尿。

An overview of gradient descent optimization algorithms

MNIST

??MNIST是機(jī)器學(xué)習(xí)領(lǐng)域中的一個(gè)經(jīng)典問題瘟芝。該問題解決的是把28x28像素的灰度手寫數(shù)字圖片識(shí)別為相應(yīng)的數(shù)字,其中數(shù)字的范圍從0到9褥琐。

??接下來锌俱,我們通過經(jīng)典的深度學(xué)習(xí)示例MNIST手寫數(shù)字識(shí)別學(xué)習(xí)如何使用各種優(yōu)化器。

??首先使用keras的dataset加載Mnist數(shù)據(jù)集,該數(shù)據(jù)集有兩大部分組成敌呈,一部分是70000個(gè)28x28像素的灰度圖片樣本(下圖是其中一個(gè)樣本1的手寫灰度圖)贸宏,另一部分則是這些圖片樣本對(duì)應(yīng)的數(shù)字標(biāo)簽:

Mnist

??接下來需要使用Mnist數(shù)據(jù)集訓(xùn)練一個(gè)神經(jīng)網(wǎng)絡(luò)造寝,來識(shí)別手寫數(shù)字。

  • 接下來先加載Mnist數(shù)據(jù)集吭练,Keras提供的API默認(rèn)將該數(shù)據(jù)劃分為訓(xùn)練集和驗(yàn)證集诫龙,其樣本數(shù)目分別為60000,10000鲫咽,為了更好的對(duì)模型進(jìn)行評(píng)估签赃,我們將數(shù)據(jù)集重新進(jìn)行劃分,從60000個(gè)樣本的訓(xùn)練集中再分出10000個(gè)樣本作為測(cè)試集來評(píng)估模型的最終表現(xiàn)分尸。
(x_train,y_train),(x_validation,y_validation) = tf.keras.datasets.mnist.load_data()
x_train,y_train,x_test,y_test = x_train[:50000,:,:],y_train[:50000],x_train[50000:,:,:],y_train[50000:]
import matplotlib.pyplot as plt
print(y_train[1])
_ = plt.imshow(x_train[1,:,:])
0
png
from tensorflow.keras import layers


(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
# 由于只是使用前饋神經(jīng)網(wǎng)絡(luò)進(jìn)行訓(xùn)練锦聊,將28*28的圖片展開成784的行或列向量,并進(jìn)行標(biāo)準(zhǔn)化
x_train = x_train[:].reshape(60000, 784).astype('float32') / 255
# 使用tf.data.Dataset API構(gòu)建輸入管道
dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
# shuffle創(chuàng)建了一個(gè)固定大小的緩存區(qū)寓落,每次從數(shù)據(jù)集中隨機(jī)抽取固定數(shù)目的樣本存入緩存區(qū)
# batch 每次從緩存區(qū)中無放回的取出指定大小的數(shù)據(jù)組成一個(gè)batch括丁,直到取出全部元素 
dataset = dataset.shuffle(buffer_size=1024).batch(64)

model = tf.keras.Sequential([
  layers.Dense(256, activation=tf.nn.relu),
  layers.Dense(256, activation=tf.nn.relu),
  layers.Dense(10)
])

# 使用交叉熵?fù)p失函數(shù)來對(duì)網(wǎng)絡(luò)進(jìn)行評(píng)估
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
# 計(jì)算訓(xùn)練過程中的識(shí)別準(zhǔn)確率
accuracy = tf.keras.metrics.SparseCategoricalAccuracy()
# 使用Adam優(yōu)化器
optimizer = tf.keras.optimizers.Adam()

for step, (x, y) in enumerate(dataset):
  
  with tf.GradientTape() as tape:

    # 計(jì)算前向過程
    logits = model(x)

    # 計(jì)算當(dāng)前Batch的損失函數(shù)
    loss_value = loss(y, logits)
     
  # 使用損失函數(shù)計(jì)算所有可訓(xùn)練參數(shù)的梯度
  gradients = tape.gradient(loss_value, model.trainable_weights)
  
  # 使用計(jì)算的梯度更新所有可訓(xùn)練參數(shù)
  optimizer.apply_gradients(zip(gradients, model.trainable_weights))

  # 更新當(dāng)前的預(yù)測(cè)精度
  accuracy.update_state(y, logits)
  
  # 訓(xùn)練過程
  if step % 100 == 0:
    print('Epochs:', step)
    print('Loss from last step: %.3f' % loss_value)
    print('Total running accuracy so far: %.3f' % accuracy.result())
Step: 0
Loss from last step: 2.408
Total running accuracy so far: 0.094
Step: 100
Loss from last step: 0.426
Total running accuracy so far: 0.842
Step: 200
Loss from last step: 0.511
Total running accuracy so far: 0.880
Step: 300
Loss from last step: 0.084
Total running accuracy so far: 0.898
Step: 400
Loss from last step: 0.321
Total running accuracy so far: 0.910
Step: 500
Loss from last step: 0.114
Total running accuracy so far: 0.917
Step: 600
Loss from last step: 0.152
Total running accuracy so far: 0.924
Step: 700
Loss from last step: 0.175
Total running accuracy so far: 0.928
Step: 800
Loss from last step: 0.153
Total running accuracy so far: 0.931
Step: 900
Loss from last step: 0.060
Total running accuracy so far: 0.935

使用 SparseCategoricalAccuracy 指標(biāo)函數(shù)計(jì)算每一個(gè)Batch測(cè)試數(shù)據(jù)的準(zhǔn)確率荞下。

x_test = x_test[:].reshape(10000, 784).astype('float32') / 255
test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test))
test_dataset = test_dataset.batch(128)

accuracy.reset_states()  # This clears the internal state of the metric

for step, (x, y) in enumerate(test_dataset):
  logits = model(x)
  accuracy.update_state(y, logits)

print('Final test accuracy: %.3f' % accuracy.result())
Final test accuracy: 0.964

add_loss

??有時(shí)侯伶选,需要在前向計(jì)算期間動(dòng)態(tài)計(jì)算損失值(尤其是正則化損失)。 Keras通過add_loss方法隨時(shí)計(jì)算損失值尖昏,并持續(xù)追蹤損失值仰税。 下面是一個(gè)基于L2范數(shù)的正則化損失示例:

from tensorflow.keras.layers import Layer

class ActivityRegularization(Layer):
  """Layer that creates an activity sparsity regularization loss."""
  
  def __init__(self, rate=1e-2):
    super(ActivityRegularization, self).__init__()
    self.rate = rate
  
  def call(self, inputs):
    # We use `add_loss` to create a regularization loss
    # that depends on the inputs.
    self.add_loss(self.rate * tf.reduce_sum(tf.square(inputs)))
    return inputs

可以通過任何圖層或模型的.losses屬性查看通過add_loss添加的損失值:

from tensorflow.keras import layers

class SparseMLP(Layer):

  def __init__(self, output_dim):
      super(SparseMLP, self).__init__()
      self.dense_1 = layers.Dense(32, activation=tf.nn.relu)
      self.regularization = ActivityRegularization(1e-2)
      self.dense_2 = layers.Dense(output_dim)

  def call(self, inputs):
      x = self.dense_1(inputs)
      x = self.regularization(x)
      return self.dense_2(x)
    

mlp = SparseMLP(1)
y = mlp(tf.ones((10, 10)))

print(mlp.losses)
# Losses correspond to the *last* forward pass.
mlp = SparseMLP(1)
mlp(tf.ones((10, 10)))
assert len(mlp.losses) == 1
mlp(tf.ones((10, 10)))
assert len(mlp.losses) == 1  # No accumulation.

# Let's demonstrate how to use these losses in a training loop.

# Prepare a dataset.
(x_train, y_train), _ = tf.keras.datasets.mnist.load_data()
dataset = tf.data.Dataset.from_tensor_slices(
    (x_train.reshape(60000, 784).astype('float32') / 255, y_train))
dataset = dataset.shuffle(buffer_size=1024).batch(64)

# A new MLP.
mlp = SparseMLP(10)

# Loss and optimizer.
loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
optimizer = tf.keras.optimizers.SGD(learning_rate=0.1)

for step, (x, y) in enumerate(dataset):
  with tf.GradientTape() as tape:
    # Forward pass.
    logits = mlp(x)

    # External loss value for this batch.
    loss = loss_fn(y, logits)
    
    # Add the losses created during the forward pass.
    loss += sum(mlp.losses)
     
    # Get gradients of loss wrt the weights.
    gradients = tape.gradient(loss, mlp.trainable_weights)
  
  # Update the weights of our linear layer.
  optimizer.apply_gradients(zip(gradients, mlp.trainable_weights))
  
  # Logging.
  if step % 100 == 0:
    print('Loss at step %d: %.3f' % (step, loss))

Sequential

??誠然關(guān)注底層設(shè)計(jì)能帶來更細(xì)粒度的控制,但是對(duì)于簡(jiǎn)單的模型抽诉,如果采用上面的方式來實(shí)現(xiàn)陨簇,顯然是沒有必要的。 Keras提供了相關(guān)的高級(jí)API來搭建神經(jīng)網(wǎng)絡(luò)迹淌,如Model類的子類河绽、函數(shù)式API或順序模型Sequential。 下面使用Keras的Sequential搭建一個(gè)MNIST分類網(wǎng)絡(luò):

# Prepare a dataset.
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train = x_train.reshape(60000, 784).astype('float32') / 255
dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
dataset = dataset.shuffle(buffer_size=1024).batch(64)

# Instantiate a simple classification model
model = tf.keras.Sequential([
  layers.Dense(256, activation=tf.nn.relu),
  layers.Dense(256, activation=tf.nn.relu),
  layers.Dense(10)
])

# Instantiate a logistic loss function that expects integer targets.
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)

# Instantiate an accuracy metric.
accuracy = tf.keras.metrics.SparseCategoricalAccuracy()

# Instantiate an optimizer.
optimizer = tf.keras.optimizers.Adam()

定義模型結(jié)構(gòu)后唉窃,配置模型訓(xùn)練的優(yōu)化器耙饰,損失函數(shù)和指標(biāo)函數(shù)

model.compile(optimizer=optimizer, loss=loss, metrics=[accuracy])

開始訓(xùn)練網(wǎng)絡(luò)

model.fit(dataset, epochs=3)

Note: 當(dāng)使用fit時(shí),默認(rèn)情況下使用靜態(tài)圖執(zhí)行纹份,因此無需在模型或圖層中添加任何tf.function裝飾器苟跪。

x_test = x_test[:].reshape(10000, 784).astype('float32') / 255
test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test))
test_dataset = test_dataset.batch(128)

loss, acc = model.evaluate(test_dataset)
print('loss: %.3f - acc: %.3f' % (loss, acc))

可以在訓(xùn)練期間監(jiān)視某些驗(yàn)證數(shù)據(jù)上的損失和指標(biāo)。 另外蔓涧,Keras還支持直接在Numpy數(shù)組上調(diào)用fit件已,因此不需要數(shù)據(jù)集轉(zhuǎn)換:

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train = x_train.reshape(60000, 784).astype('float32') / 255

num_val_samples = 10000
x_val = x_train[-num_val_samples:]
y_val = y_train[-num_val_samples:]
x_train = x_train[:-num_val_samples]
y_train = y_train[:-num_val_samples]

# Instantiate a simple classification model
model = tf.keras.Sequential([
  layers.Dense(256, activation=tf.nn.relu),
  layers.Dense(256, activation=tf.nn.relu),
  layers.Dense(10)
])

# Instantiate a logistic loss function that expects integer targets.
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)

# Instantiate an accuracy metric.
accuracy = tf.keras.metrics.SparseCategoricalAccuracy()

# Instantiate an optimizer.
optimizer = tf.keras.optimizers.Adam()

model.compile(optimizer=optimizer,
              loss=loss,
              metrics=[accuracy])
model.fit(x_train, y_train,
          validation_data=(x_val, y_val),
          epochs=3,
          batch_size=64)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
Epoch 1/3
782/782 [==============================] - 4s 5ms/step - loss: 0.2434 - sparse_categorical_accuracy: 0.9291 - val_loss: 0.1209 - val_sparse_categorical_accuracy: 0.9632
Epoch 2/3
782/782 [==============================] - 3s 4ms/step - loss: 0.0947 - sparse_categorical_accuracy: 0.9704 - val_loss: 0.0841 - val_sparse_categorical_accuracy: 0.9732
Epoch 3/3
782/782 [==============================] - 3s 4ms/step - loss: 0.0609 - sparse_categorical_accuracy: 0.9806 - val_loss: 0.0982 - val_sparse_categorical_accuracy: 0.9715

Callbacks

??fit的簡(jiǎn)潔功能之一(內(nèi)置了對(duì)樣本加權(quán)和類別加權(quán)的支持)是可以使用callbacks.輕松地自定義訓(xùn)練和評(píng)估期間發(fā)生的情況。 回調(diào)是在訓(xùn)練過程中(例如元暴,每個(gè)batch或epoch結(jié)束時(shí))在不同時(shí)間點(diǎn)調(diào)用的對(duì)象篷扩,并執(zhí)行一些操作,例如保存模型茉盏,加載檢查點(diǎn)瞻惋,停止培訓(xùn)等回調(diào)函數(shù)厦滤,例如ModelCheckpoint可以在訓(xùn)練期間的每個(gè)紀(jì)元之后保存模型,或者EarlyStopping可以在驗(yàn)證指標(biāo)開始停止時(shí)中斷訓(xùn)練歼狼。
還可以輕松編寫自己的回調(diào).

# Instantiate a simple classification model
model = tf.keras.Sequential([
  layers.Dense(256, activation=tf.nn.relu),
  layers.Dense(256, activation=tf.nn.relu),
  layers.Dense(10)
])

# Instantiate a logistic loss function that expects integer targets.
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)

# Instantiate an accuracy metric.
accuracy = tf.keras.metrics.SparseCategoricalAccuracy()

# Instantiate an optimizer.
optimizer = tf.keras.optimizers.Adam()

model.compile(optimizer=optimizer,
              loss=loss,
              metrics=[accuracy])

# Instantiate some callbacks
callbacks = [tf.keras.callbacks.EarlyStopping(),
             tf.keras.callbacks.ModelCheckpoint(filepath='my_model.keras',
                                                save_best_only=True)]

model.fit(x_train, y_train,
          validation_data=(x_val, y_val),
          epochs=30,
          batch_size=64,
          callbacks=callbacks)
Epoch 1/30
782/782 [==============================] - 4s 4ms/step - loss: 0.2391 - sparse_categorical_accuracy: 0.9284 - val_loss: 0.1330 - val_sparse_categorical_accuracy: 0.9600
Epoch 2/30
782/782 [==============================] - 3s 4ms/step - loss: 0.0951 - sparse_categorical_accuracy: 0.9708 - val_loss: 0.0795 - val_sparse_categorical_accuracy: 0.9753
Epoch 3/30
782/782 [==============================] - 3s 4ms/step - loss: 0.0627 - sparse_categorical_accuracy: 0.9804 - val_loss: 0.0864 - val_sparse_categorical_accuracy: 0.9751

HiPlot

??調(diào)參是深度學(xué)習(xí)模型訓(xùn)練的一個(gè)難點(diǎn)掏导,最后給大家推一個(gè)模型調(diào)參使用的可視化工具。

使用KerasTuner和Hiplot進(jìn)行神經(jīng)網(wǎng)絡(luò)超參數(shù)調(diào)整

import hiplot as hip
data = [{'dropout':0.1, 'lr': 0.001, 'loss': 10.0, 'optimizer': 'SGD'},
        {'dropout':0.15, 'lr': 0.01, 'loss': 3.5, 'optimizer': 'Adam'},
        {'dropout':0.3, 'lr': 0.1, 'loss': 4.5, 'optimizer': 'Adam'}]
hip.Experiment.from_iterable(data).display(store_state_key="cell1")
image
image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末羽峰,一起剝皮案震驚了整個(gè)濱河市趟咆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌梅屉,老刑警劉巖值纱,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異坯汤,居然都是意外死亡虐唠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門惰聂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疆偿,“玉大人,你說我怎么就攤上這事搓幌「斯剩” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵溉愁,是天一觀的道長(zhǎng)处铛。 經(jīng)常有香客問我,道長(zhǎng)拐揭,這世上最難降的妖魔是什么撤蟆? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮堂污,結(jié)果婚禮上家肯,老公的妹妹穿的比我還像新娘。我一直安慰自己敷鸦,他們只是感情好息楔,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著扒披,像睡著了一般值依。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碟案,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天愿险,我揣著相機(jī)與錄音,去河邊找鬼。 笑死辆亏,一個(gè)胖子當(dāng)著我的面吹牛风秤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扮叨,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼乏屯,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼疫铜!你這毒婦竟也來了拓萌?” 一聲冷哼從身側(cè)響起辙浑,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎衷蜓,沒想到半個(gè)月后累提,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡磁浇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年斋陪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片置吓。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡无虚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出交洗,到底是詐尸還是另有隱情骑科,我是刑警寧澤橡淑,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布构拳,位于F島的核電站,受9級(jí)特大地震影響梁棠,放射性物質(zhì)發(fā)生泄漏置森。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一符糊、第九天 我趴在偏房一處隱蔽的房頂上張望凫海。 院中可真熱鬧,春花似錦男娄、人聲如沸行贪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽建瘫。三九已至,卻和暖如春尸折,著一層夾襖步出監(jiān)牢的瞬間啰脚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來泰國打工实夹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留橄浓,地道東北人粒梦。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像荸实,于是被迫代替她去往敵國和親匀们。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351