ดูบน TensorFlow.org | ทำงานใน Google Colab | ดูแหล่งที่มาบน GitHub | ดาวน์โหลดโน๊ตบุ๊ค |
การแยกความแตกต่างและการไล่ระดับสีอัตโนมัติ
การ แยกความแตกต่างอัตโนมัติ มีประโยชน์สำหรับการนำอัลกอริธึมการเรียนรู้ของเครื่องมาใช้ เช่น การขยาย พันธุ์ย้อนหลัง สำหรับเครือข่ายประสาทเทียมในการฝึกอบรม
ในคู่มือนี้ คุณจะสำรวจวิธีการคำนวณการไล่ระดับสีด้วย TensorFlow โดยเฉพาะอย่างยิ่งใน การดำเนินการอย่างกระตือรือร้น
ติดตั้ง
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
การคำนวณการไล่ระดับสี
เพื่อแยกความแตกต่างโดยอัตโนมัติ TensorFlow จำเป็นต้องจำการดำเนินการที่เกิดขึ้นในลำดับใดระหว่างการ ส่ง ต่อ จากนั้น ระหว่างการ ย้อนกลับ TensorFlow จะข้ามรายการการดำเนินการนี้ในลำดับย้อนกลับเพื่อคำนวณการไล่ระดับสี
เทปไล่โทนสี
TensorFlow จัดเตรียม tf.GradientTape
API สำหรับการแยกความแตกต่างโดยอัตโนมัติ นั่นคือ การคำนวณเกรเดียนต์ของการคำนวณที่เกี่ยวกับอินพุตบางตัว ปกติแล้ว tf.Variable
s TensorFlow "บันทึก" การดำเนินการที่เกี่ยวข้องซึ่งดำเนินการภายในบริบทของ tf.GradientTape
ลงบน "เทป" จากนั้น TensorFlow จะใช้เทปนั้นเพื่อคำนวณการไล่ระดับของการคำนวณที่ "บันทึกไว้" โดยใช้ โหมด Reverse mode differentiation
นี่เป็นตัวอย่างง่ายๆ:
x = tf.Variable(3.0)
with tf.GradientTape() as tape:
y = x**2
เมื่อคุณได้บันทึกการดำเนินการบางอย่างแล้ว ให้ใช้ GradientTape.gradient(target, sources)
เพื่อคำนวณการไล่ระดับของเป้าหมายบางส่วน (มักจะสูญเสีย) ที่สัมพันธ์กับแหล่งที่มาบางส่วน (มักจะเป็นตัวแปรของโมเดล):
# dy = 2x * dx
dy_dx = tape.gradient(y, x)
dy_dx.numpy()
6.0
ตัวอย่างข้างต้นใช้สเกลาร์ แต่ tf.GradientTape
ทำงานได้อย่างง่ายดายบนเทนเซอร์ใดๆ:
w = tf.Variable(tf.random.normal((3, 2)), name='w')
b = tf.Variable(tf.zeros(2, dtype=tf.float32), name='b')
x = [[1., 2., 3.]]
with tf.GradientTape(persistent=True) as tape:
y = x @ w + b
loss = tf.reduce_mean(y**2)
เพื่อให้ได้ระดับความ loss
ที่สัมพันธ์กับตัวแปรทั้งสอง คุณสามารถส่งผ่านทั้งสองแหล่งไปยังวิธีการ gradient
ได้ เทปมีความยืดหยุ่นเกี่ยวกับวิธีการส่งแหล่งที่มา และจะยอมรับการรวมรายการหรือพจนานุกรมที่ซ้อนกัน และส่งคืนโครงสร้างการไล่ระดับสีในลักษณะเดียวกัน (ดู tf.nest
)
[dl_dw, dl_db] = tape.gradient(loss, [w, b])
การไล่ระดับสีตามแหล่งที่มาแต่ละแหล่งจะมีรูปทรงของแหล่งกำเนิด:
print(w.shape)
print(dl_dw.shape)
(3, 2) (3, 2)
นี่คือการคำนวณการไล่ระดับสีอีกครั้ง คราวนี้ผ่านพจนานุกรมของตัวแปร:
my_vars = {
'w': w,
'b': b
}
grad = tape.gradient(loss, my_vars)
grad['b']
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([-1.6920902, -3.2363236], dtype=float32)>
การไล่สีตามโมเดล
เป็นเรื่องปกติที่จะรวบรวม tf.Variables
ลงใน tf.Module
หรือหนึ่งในคลาสย่อย ( layers.Layer
, keras.Model
) สำหรับ จุดตรวจสอบ และการ ส่งออก
ในกรณีส่วนใหญ่ คุณจะต้องคำนวณการไล่ระดับสีตามตัวแปรที่ฝึกได้ของแบบจำลอง เนื่องจากคลาสย่อยทั้งหมดของ tf.Module
รวมตัวแปรไว้ในคุณสมบัติ Module.trainable_variables
คุณจึงสามารถคำนวณการไล่ระดับสีเหล่านี้ได้ในโค้ดสองสามบรรทัด:
layer = tf.keras.layers.Dense(2, activation='relu')
x = tf.constant([[1., 2., 3.]])
with tf.GradientTape() as tape:
# Forward pass
y = layer(x)
loss = tf.reduce_mean(y**2)
# Calculate gradients with respect to every trainable variable
grad = tape.gradient(loss, layer.trainable_variables)
for var, g in zip(layer.trainable_variables, grad):
print(f'{var.name}, shape: {g.shape}')
dense/kernel:0, shape: (3, 2) dense/bias:0, shape: (2,)
ควบคุมสิ่งที่ดูเทป
พฤติกรรมเริ่มต้นคือการบันทึกการดำเนินการทั้งหมดหลังจากเข้าถึง tf.Variable
ที่ฝึกได้ เหตุผลคือ:
- เทปจำเป็นต้องรู้ว่าต้องบันทึกการดำเนินการใดในการส่งต่อเพื่อคำนวณการไล่ระดับสีในการส่งย้อนหลัง
- เทปมีการอ้างอิงถึงเอาต์พุตระดับกลาง ดังนั้นคุณจึงไม่ต้องการบันทึกการทำงานที่ไม่จำเป็น
- กรณีการใช้งานทั่วไปส่วนใหญ่เกี่ยวข้องกับการคำนวณระดับความสูญเสียที่สัมพันธ์กับตัวแปรที่ฝึกได้ของแบบจำลองทั้งหมด
ตัวอย่างเช่น สิ่งต่อไปนี้ล้มเหลวในการคำนวณการไล่ระดับสีเนื่องจาก tf.Tensor
ไม่ได้ "ดู" โดยค่าเริ่มต้น และ tf.Variable
ไม่สามารถฝึกได้:
# A trainable variable
x0 = tf.Variable(3.0, name='x0')
# Not trainable
x1 = tf.Variable(3.0, name='x1', trainable=False)
# Not a Variable: A variable + tensor returns a tensor.
x2 = tf.Variable(2.0, name='x2') + 1.0
# Not a variable
x3 = tf.constant(3.0, name='x3')
with tf.GradientTape() as tape:
y = (x0**2) + (x1**2) + (x2**2)
grad = tape.gradient(y, [x0, x1, x2, x3])
for g in grad:
print(g)
tf.Tensor(6.0, shape=(), dtype=float32) None None None
คุณสามารถระบุตัวแปรที่เทปกำลังรับชมได้โดยใช้เมธอด GradientTape.watched_variables
:
[var.name for var in tape.watched_variables()]
['x0:0']
tf.GradientTape
มีตะขอที่ให้ผู้ใช้ควบคุมสิ่งที่ดูอยู่หรือไม่ดู
ในการบันทึกการไล่ระดับสีเกี่ยวกับ tf.Tensor
คุณต้องเรียก GradientTape.watch(x)
:
x = tf.constant(3.0)
with tf.GradientTape() as tape:
tape.watch(x)
y = x**2
# dy = 2x * dx
dy_dx = tape.gradient(y, x)
print(dy_dx.numpy())
6.0
ในทางกลับกัน หากต้องการปิดใช้งานพฤติกรรมเริ่มต้นของการดู tf.Variables
ทั้งหมด ให้ตั้งค่า watch_accessed_variables=False
เมื่อสร้างเทปการไล่ระดับสี การคำนวณนี้ใช้ตัวแปรสองตัว แต่เชื่อมต่อการไล่ระดับสีสำหรับตัวแปรตัวใดตัวหนึ่งเท่านั้น:
x0 = tf.Variable(0.0)
x1 = tf.Variable(10.0)
with tf.GradientTape(watch_accessed_variables=False) as tape:
tape.watch(x1)
y0 = tf.math.sin(x0)
y1 = tf.nn.softplus(x1)
y = y0 + y1
ys = tf.reduce_sum(y)
เนื่องจากไม่ได้เรียกใช้ GradientTape.watch
บน x0
จึงไม่มีการไล่ระดับสีคำนวณด้วยความเคารพ:
# dys/dx1 = exp(x1) / (1 + exp(x1)) = sigmoid(x1)
grad = tape.gradient(ys, {'x0': x0, 'x1': x1})
print('dy/dx0:', grad['x0'])
print('dy/dx1:', grad['x1'].numpy())
dy/dx0: None dy/dx1: 0.9999546ตัวยึดตำแหน่ง22
ผลลัพธ์ระดับกลาง
คุณยังสามารถขอการไล่ระดับสีของผลลัพธ์ที่เกี่ยวข้องกับค่ากลางที่คำนวณภายในบริบท tf.GradientTape
x = tf.constant(3.0)
with tf.GradientTape() as tape:
tape.watch(x)
y = x * x
z = y * y
# Use the tape to compute the gradient of z with respect to the
# intermediate value y.
# dz_dy = 2 * y and y = x ** 2 = 9
print(tape.gradient(z, y).numpy())
18.0
โดยค่าเริ่มต้น ทรัพยากรที่ถือโดย GradientTape
จะถูกปล่อยทันทีที่มีการเรียกวิธีการ GradientTape.gradient
ในการคำนวณการไล่ระดับสีหลายรายการในการคำนวณเดียวกัน ให้สร้างเทปการไล่ระดับสีด้วย persistent=True
วิธีนี้อนุญาตให้เรียกวิธีการ gradient
ได้หลายครั้ง เนื่องจากทรัพยากรถูกปล่อยเมื่อวัตถุเทปถูกรวบรวมขยะ ตัวอย่างเช่น:
x = tf.constant([1, 3.0])
with tf.GradientTape(persistent=True) as tape:
tape.watch(x)
y = x * x
z = y * y
print(tape.gradient(z, x).numpy()) # [4.0, 108.0] (4 * x**3 at x = [1.0, 3.0])
print(tape.gradient(y, x).numpy()) # [2.0, 6.0] (2 * x at x = [1.0, 3.0])
[ 4. 108.] [2. 6.]
del tape # Drop the reference to the tape
หมายเหตุเกี่ยวกับประสิทธิภาพ
มีค่าใช้จ่ายเล็กน้อยที่เกี่ยวข้องกับการดำเนินการภายในบริบทเทปการไล่ระดับสี สำหรับการดำเนินการที่กระตือรือร้นที่สุด การดำเนินการนี้จะไม่มีค่าใช้จ่ายที่เห็นได้ชัดเจน แต่คุณควรใช้บริบทเทปรอบ ๆ พื้นที่เฉพาะที่จำเป็นเท่านั้น
เทปไล่โทนสีใช้หน่วยความจำเพื่อเก็บผลลัพธ์ขั้นกลาง รวมทั้งอินพุตและเอาต์พุต สำหรับใช้ระหว่างการส่งผ่านย้อนกลับ
เพื่อประสิทธิภาพ การดำเนินการบางอย่าง (เช่น
ReLU
) ไม่จำเป็นต้องเก็บผลลัพธ์ระดับกลางไว้ และจะถูกตัดออกในระหว่างการส่งต่อ อย่างไรก็ตาม หากคุณใช้persistent=True
บนเทปของคุณ ไม่มีอะไรถูกละทิ้ง และการใช้หน่วยความจำสูงสุดของคุณจะสูงขึ้น
การไล่สีเป้าหมายที่ไม่ใช่สเกลาร์
เกรเดียนต์เป็นพื้นฐานของการดำเนินการกับสเกลาร์
x = tf.Variable(2.0)
with tf.GradientTape(persistent=True) as tape:
y0 = x**2
y1 = 1 / x
print(tape.gradient(y0, x).numpy())
print(tape.gradient(y1, x).numpy())
4.0 -0.25
ดังนั้น หากคุณขอความลาดชันของเป้าหมายหลาย ๆ อัน ผลลัพธ์สำหรับแต่ละแหล่งคือ:
- การไล่ระดับของผลรวมของเป้าหมายหรือเทียบเท่า
- ผลรวมของการไล่ระดับสีของแต่ละเป้าหมาย
x = tf.Variable(2.0)
with tf.GradientTape() as tape:
y0 = x**2
y1 = 1 / x
print(tape.gradient({'y0': y0, 'y1': y1}, x).numpy())
3.75
ในทำนองเดียวกัน หากเป้าหมายไม่ใช่สเกลาร์ เกรเดียนท์ของผลรวมจะถูกคำนวณ:
x = tf.Variable(2.)
with tf.GradientTape() as tape:
y = x * [3., 4.]
print(tape.gradient(y, x).numpy())
7.0ตัวยึดตำแหน่ง33
ซึ่งทำให้ง่ายต่อการใช้การไล่ระดับของผลรวมของคอลเลกชันของการสูญเสีย หรือการไล่ระดับของผลรวมของการคำนวณการสูญเสียตามองค์ประกอบ
หากคุณต้องการการไล่ระดับสีแยกกันสำหรับแต่ละรายการ ให้อ้างอิงกับ จาโคเบียน
ในบางกรณี คุณสามารถข้ามภาษาจาโคเบียนได้ สำหรับการคำนวณตามองค์ประกอบ การไล่ระดับของผลรวมจะให้อนุพันธ์ของแต่ละองค์ประกอบเทียบกับองค์ประกอบอินพุต เนื่องจากแต่ละองค์ประกอบเป็นอิสระจากกัน:
x = tf.linspace(-10.0, 10.0, 200+1)
with tf.GradientTape() as tape:
tape.watch(x)
y = tf.nn.sigmoid(x)
dy_dx = tape.gradient(y, x)
plt.plot(x, y, label='y')
plt.plot(x, dy_dx, label='dy/dx')
plt.legend()
_ = plt.xlabel('x')
ควบคุมการไหล
เนื่องจากเทปเกรเดียนต์บันทึกการดำเนินการขณะที่ดำเนินการ โฟลว์การควบคุม Python จะได้รับการจัดการอย่างเป็นธรรมชาติ (เช่น คำสั่ง if
และ while
)
ที่นี่ใช้ตัวแปรที่แตกต่างกันในแต่ละสาขาของ if
การไล่ระดับสีเชื่อมต่อกับตัวแปรที่ใช้เท่านั้น:
x = tf.constant(1.0)
v0 = tf.Variable(2.0)
v1 = tf.Variable(2.0)
with tf.GradientTape(persistent=True) as tape:
tape.watch(x)
if x > 0.0:
result = v0
else:
result = v1**2
dv0, dv1 = tape.gradient(result, [v0, v1])
print(dv0)
print(dv1)
tf.Tensor(1.0, shape=(), dtype=float32) None
เพียงจำไว้ว่าคำสั่งควบคุมนั้นไม่สามารถสร้างความแตกต่างได้ ดังนั้นจึงไม่สามารถมองเห็นได้จากเครื่องมือเพิ่มประสิทธิภาพแบบไล่ระดับ
ขึ้นอยู่กับค่าของ x
ในตัวอย่างข้างต้น เทปบันทึก result = v0
หรือ result = v1**2
การไล่ระดับสีเทียบกับ x
จะเป็น None
เสมอ
dx = tape.gradient(result, x)
print(dx)
Noneตัวยึดตำแหน่ง39
รับการไล่ระดับของ None
เมื่อเป้าหมายไม่ได้เชื่อมต่อกับแหล่งที่มา คุณจะได้รับการไล่ระดับสี None
x = tf.Variable(2.)
y = tf.Variable(3.)
with tf.GradientTape() as tape:
z = y * y
print(tape.gradient(z, x))
Noneตัวยึดตำแหน่ง41
เห็นได้ชัดว่า z
ไม่ได้เชื่อมต่อกับ x
แต่มีหลายวิธีที่ไม่ชัดเจนที่สามารถตัดการไล่ระดับสีได้
1. แทนที่ตัวแปรด้วยเทนเซอร์
ในส่วน "การควบคุมสิ่งที่ดูเทป" คุณเห็นว่าเทปจะดูโดยอัตโนมัติ tf.Variable
แต่ไม่ใช่ tf.Tensor
ข้อผิดพลาดทั่วไปอย่างหนึ่งคือการแทนที่ tf.Variable
ด้วย tf.Tensor
โดยไม่ได้ตั้งใจ แทนที่จะใช้ Variable.assign
เพื่ออัปเดต tf.Variable
นี่คือตัวอย่าง:
x = tf.Variable(2.0)
for epoch in range(2):
with tf.GradientTape() as tape:
y = x+1
print(type(x).__name__, ":", tape.gradient(y, x))
x = x + 1 # This should be `x.assign_add(1)`
ResourceVariable : tf.Tensor(1.0, shape=(), dtype=float32) EagerTensor : Noneตัวยึดตำแหน่ง43
2. คำนวณนอก TensorFlow
เทปไม่สามารถบันทึกเส้นทางการไล่ระดับสีถ้าการคำนวณออกจาก TensorFlow ตัวอย่างเช่น:
x = tf.Variable([[1.0, 2.0],
[3.0, 4.0]], dtype=tf.float32)
with tf.GradientTape() as tape:
x2 = x**2
# This step is calculated with NumPy
y = np.mean(x2, axis=0)
# Like most ops, reduce_mean will cast the NumPy array to a constant tensor
# using `tf.convert_to_tensor`.
y = tf.reduce_mean(y, axis=0)
print(tape.gradient(y, x))
None
3. ทำการไล่ระดับด้วยจำนวนเต็มหรือสตริง
จำนวนเต็มและสตริงไม่สามารถแยกความแตกต่างได้ หากเส้นทางการคำนวณใช้ประเภทข้อมูลเหล่านี้จะไม่มีการไล่ระดับสี
ไม่มีใครคาดคิดว่าสตริงจะแยกความแตกต่างได้ แต่มันง่ายที่จะสร้างค่าคงที่หรือตัวแปร int
โดยไม่ได้ตั้งใจ ถ้าคุณไม่ระบุ dtype
x = tf.constant(10)
with tf.GradientTape() as g:
g.watch(x)
y = x * x
print(g.gradient(y, x))
WARNING:tensorflow:The dtype of the watched tensor must be floating (e.g. tf.float32), got tf.int32 WARNING:tensorflow:The dtype of the target tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32 WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32 None
TensorFlow ไม่ได้แยกระหว่างประเภทโดยอัตโนมัติ ดังนั้น ในทางปฏิบัติ คุณมักจะได้รับข้อผิดพลาดเกี่ยวกับประเภทแทนที่จะเป็นการไล่ระดับสีที่ขาดหายไป
4. ทำการไล่ระดับผ่านวัตถุที่เก็บสถานะ
สถานะหยุดการไล่ระดับสี เมื่อคุณอ่านจากออบเจ็กต์การเก็บสถานะ เทปสามารถสังเกตได้เฉพาะสถานะปัจจุบัน ไม่ใช่ประวัติที่นำไปสู่สถานะนั้น
tf.Tensor
ไม่เปลี่ยนรูป คุณไม่สามารถเปลี่ยนเทนเซอร์เมื่อสร้างแล้ว มี ค่า แต่ไม่มี สถานะ การดำเนินการทั้งหมดที่กล่าวถึงจนถึงขณะนี้ไม่มีสถานะเช่นกัน: ผลลัพธ์ของ tf.matmul
ขึ้นอยู่กับอินพุตเท่านั้น
tf.Variable
มีสถานะภายใน—ค่าของมัน เมื่อคุณใช้ตัวแปร สถานะจะถูกอ่าน เป็นเรื่องปกติในการคำนวณการไล่ระดับสีที่สัมพันธ์กับตัวแปร แต่สถานะของตัวแปรจะบล็อกการคำนวณการไล่ระดับสีไม่ให้ย้อนกลับไปไกลกว่านั้น ตัวอย่างเช่น:
x0 = tf.Variable(3.0)
x1 = tf.Variable(0.0)
with tf.GradientTape() as tape:
# Update x1 = x1 + x0.
x1.assign_add(x0)
# The tape starts recording from x1.
y = x1**2 # y = (x1 + x0)**2
# This doesn't work.
print(tape.gradient(y, x0)) #dy/dx0 = 2*(x1 + x0)
None
ในทำนองเดียวกัน tf.data.Dataset
iterators และ tf.queue
s เป็น stateful และจะหยุดการไล่ระดับทั้งหมดบนเทนเซอร์ที่ส่งผ่าน
ไม่มีการไล่ระดับสีที่ลงทะเบียน
tf.Operation
บางรายการได้รับ การลงทะเบียนว่าไม่แตกต่าง และจะส่งกลับ None
อื่นๆ ไม่มีการไล่ระดับสีที่ลงทะเบียน
หน้า tf.raw_ops
แสดงว่า ops ระดับต่ำใดที่มีการไล่ระดับสีที่ลงทะเบียนไว้
หากคุณพยายามใช้การไล่ระดับสีผ่าน float op ที่ไม่มีการไล่ระดับสีที่ลงทะเบียนไว้ เทปจะแสดงข้อผิดพลาดแทนที่จะส่งคืน None
อย่างเงียบ ๆ วิธีนี้จะทำให้คุณรู้ว่ามีบางอย่างผิดพลาด
ตัวอย่างเช่น ฟังก์ชัน tf.image.adjust_contrast
ล้อม raw_ops.AdjustContrastv2
ซึ่งอาจมีการไล่ระดับสีแต่ไม่มีการไล่ระดับสี:
image = tf.Variable([[[0.5, 0.0, 0.0]]])
delta = tf.Variable(0.1)
with tf.GradientTape() as tape:
new_image = tf.image.adjust_contrast(image, delta)
try:
print(tape.gradient(new_image, [image, delta]))
assert False # This should not happen.
except LookupError as e:
print(f'{type(e).__name__}: {e}')
LookupError: gradient registry has no entry for: AdjustContrastv2ตัวยึดตำแหน่ง51
หากคุณต้องการแยกความแตกต่างจาก op นี้ คุณจะต้องใช้การไล่ระดับสีและลงทะเบียน (โดยใช้ tf.RegisterGradient
) หรือใช้งานฟังก์ชันใหม่โดยใช้ ops อื่น
ศูนย์แทน None
ในบางกรณี จะเป็นการสะดวกที่จะได้รับ 0 แทนที่จะเป็น None
สำหรับการไล่ระดับสีที่ไม่เกี่ยวข้อง คุณสามารถตัดสินใจว่าจะส่งคืนอะไรเมื่อคุณมีการไล่ระดับสีที่ไม่ได้เชื่อมต่อโดยใช้อาร์กิวเมนต์ unconnected_gradients
:
x = tf.Variable([2., 2.])
y = tf.Variable(3.)
with tf.GradientTape() as tape:
z = y**2
print(tape.gradient(z, x, unconnected_gradients=tf.UnconnectedGradients.ZERO))
tf.Tensor([0. 0.], shape=(2,), dtype=float32)ตัวยึดตำแหน่ง53