Pytorch อาร์กิวเมนต์ไล่ระดับคืออะไร


112

ฉันกำลังอ่านเอกสารของ PyTorch และพบตัวอย่างที่พวกเขาเขียน

gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)
print(x.grad)

โดยที่ x เป็นตัวแปรเริ่มต้นซึ่ง y ถูกสร้างขึ้น (เวกเตอร์ 3 ตัว) คำถามคืออาร์กิวเมนต์ 0.1, 1.0 และ 0.0001 ของเทนเซอร์ไล่ระดับสีคืออะไร? เอกสารประกอบยังไม่ชัดเจนในเรื่องนี้

คำตอบ:


15

รหัสเดิมที่ฉันไม่พบในเว็บไซต์ PyTorch อีกต่อไป

gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)
print(x.grad)

ปัญหาเกี่ยวกับโค้ดด้านบนไม่มีฟังก์ชันที่ใช้คำนวณการไล่ระดับสี ซึ่งหมายความว่าเราไม่ทราบว่ามีกี่พารามิเตอร์ (อาร์กิวเมนต์ที่ฟังก์ชันใช้) และมิติของพารามิเตอร์

เพื่อให้เข้าใจอย่างถ่องแท้ฉันได้สร้างตัวอย่างที่ใกล้เคียงกับต้นฉบับ:

ตัวอย่างที่ 1:

a = torch.tensor([1.0, 2.0, 3.0], requires_grad = True)
b = torch.tensor([3.0, 4.0, 5.0], requires_grad = True)
c = torch.tensor([6.0, 7.0, 8.0], requires_grad = True)

y=3*a + 2*b*b + torch.log(c)    
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients,retain_graph=True)    

print(a.grad) # tensor([3.0000e-01, 3.0000e+00, 3.0000e-04])
print(b.grad) # tensor([1.2000e+00, 1.6000e+01, 2.0000e-03])
print(c.grad) # tensor([1.6667e-02, 1.4286e-01, 1.2500e-05])

ฉันคิดว่าฟังก์ชันของเราคือy=3*a + 2*b*b + torch.log(c)และพารามิเตอร์คือเทนเซอร์ที่มีสามองค์ประกอบอยู่ภายใน

คุณสามารถคิดว่าgradients = torch.FloatTensor([0.1, 1.0, 0.0001])นี่คือตัวสะสม

ดังที่คุณอาจได้ยินการคำนวณระบบอัตโนมัติของ PyTorch นั้นเทียบเท่ากับผลิตภัณฑ์จาโคเบียน

จาโคเบียน

ในกรณีที่คุณมีฟังก์ชันเหมือนที่เราทำ:

y=3*a + 2*b*b + torch.log(c)

[3, 4*b, 1/c]จาโคเบียนจะเป็น อย่างไรก็ตามจาโคเบียนนี้ไม่ใช่วิธีที่ PyTorch ทำสิ่งต่างๆเพื่อคำนวณการไล่ระดับสี ณ จุดหนึ่ง

PyTorch ใช้การส่งต่อไปข้างหน้าและโหมดย้อนกลับความแตกต่างอัตโนมัติ (AD) ควบคู่กัน

ไม่มีคณิตศาสตร์เชิงสัญลักษณ์ที่เกี่ยวข้องและไม่มีความแตกต่างของตัวเลข

ความแตกต่างของตัวเลขที่จะคำนวณδy/δbสำหรับb=1และb=1+εที่εมีขนาดเล็ก

หากคุณไม่ใช้การไล่ระดับสีในy.backward():

ตัวอย่าง 2

a = torch.tensor(0.1, requires_grad = True)
b = torch.tensor(1.0, requires_grad = True)
c = torch.tensor(0.1, requires_grad = True)
y=3*a + 2*b*b + torch.log(c)

y.backward()

print(a.grad) # tensor(3.)
print(b.grad) # tensor(4.)
print(c.grad) # tensor(10.)

คุณจะง่ายได้รับผลที่จุดขึ้นอยู่กับวิธีการตั้งค่าของคุณa, b, cเทนเซอร์แรก

ระวังว่าคุณเริ่มต้นของคุณa, b, c:

ตัวอย่างที่ 3:

a = torch.empty(1, requires_grad = True, pin_memory=True)
b = torch.empty(1, requires_grad = True, pin_memory=True)
c = torch.empty(1, requires_grad = True, pin_memory=True)

y=3*a + 2*b*b + torch.log(c)

gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)

print(a.grad) # tensor([3.3003])
print(b.grad) # tensor([0.])
print(c.grad) # tensor([inf])

หากคุณใช้torch.empty()และไม่ใช้pin_memory=Trueคุณอาจได้ผลลัพธ์ที่แตกต่างกันในแต่ละครั้ง

นอกจากนี้การไล่ระดับสีของโน้ตก็เหมือนกับตัวสะสมดังนั้นจึงเป็นศูนย์เมื่อจำเป็น

ตัวอย่างที่ 4:

a = torch.tensor(1.0, requires_grad = True)
b = torch.tensor(1.0, requires_grad = True)
c = torch.tensor(1.0, requires_grad = True)
y=3*a + 2*b*b + torch.log(c)

y.backward(retain_graph=True)
y.backward()

print(a.grad) # tensor(6.)
print(b.grad) # tensor(8.)
print(c.grad) # tensor(2.)

เคล็ดลับสุดท้ายเกี่ยวกับเงื่อนไขการใช้ PyTorch:

PyTorch สร้างกราฟการคำนวณแบบไดนามิกเมื่อคำนวณการไล่ระดับสีในการส่งต่อ ดูเหมือนต้นไม้มาก

ดังนั้นคุณมักจะได้ยินใบของต้นไม้นี้tensors การป้อนข้อมูลและรากเป็นเมตริกซ์เอาท์พุท

การไล่ระดับสีที่มีการคำนวณโดยการติดตามกราฟจากรากใบและคูณลาดในทางที่ทุกคนใช้กฎลูกโซ่ การคูณนี้เกิดขึ้นในการส่งย้อนกลับ


ตอบโจทย์มาก! อย่างไรก็ตามฉันไม่คิดว่า Pytorch จะสร้างความแตกต่างเชิงตัวเลข ("สำหรับฟังก์ชันก่อนหน้านี้ PyTorch จะทำเช่นδy / δbสำหรับ b = 1 และ b = 1 + εโดยที่εมีขนาดเล็กดังนั้นจึงไม่มีอะไรที่เหมือนกับการคำนวณเชิงสัญลักษณ์ ") - ฉันเชื่อว่ามันสร้างความแตกต่างโดยอัตโนมัติ
max_max_mir

ใช่มันใช้ AD หรือการแยกความแตกต่างโดยอัตโนมัติในภายหลังฉันได้ตรวจสอบ AD เพิ่มเติมเช่นเดียวกับในPDFนี้อย่างไรก็ตามเมื่อฉันตั้งค่าคำตอบนี้ฉันไม่ค่อยได้รับแจ้ง
prosti

เช่นตัวอย่างที่ 2 ให้ RuntimeError: รูปร่างไม่ตรงกัน: grad_output [0] มีรูปร่างของคบเพลิงขนาด ([3]) และเอาต์พุต [0] มีรูปร่างของคบเพลิงขนาด ([])
Andreas K.

@AndreasK คุณพูดถูก PyTorch เปิดตัวเทนเซอร์ขนาดศูนย์เมื่อเร็ว ๆ นี้และสิ่งนี้มีผลกระทบต่อตัวอย่างก่อนหน้าของฉัน ลบออกเนื่องจากตัวอย่างเหล่านี้ไม่สำคัญ
prosti

100

คำอธิบาย

สำหรับโครงข่ายประสาทเทียมเรามักจะใช้lossเพื่อประเมินว่าเครือข่ายได้เรียนรู้การจัดประเภทอิมเมจอินพุต (หรืองานอื่น ๆ ) ได้ดีเพียงใด lossระยะมักจะเป็นค่าสเกลา ในการอัปเดตพารามิเตอร์ของเครือข่ายเราจำเป็นต้องคำนวณการไล่ระดับสีของlosswrt ไปยังพารามิเตอร์ซึ่งจริงๆแล้วleaf nodeในกราฟการคำนวณ (อย่างไรก็ตามพารามิเตอร์เหล่านี้ส่วนใหญ่เป็นน้ำหนักและอคติของเลเยอร์ต่างๆเช่น Convolution, Linear และ เป็นต้น).

ตามกฎลูกโซ่ในการคำนวณการไล่ระดับสีของlosswrt ไปยังโหนดลีฟเราสามารถคำนวณอนุพันธ์ของlosswrt ตัวแปรกลางบางตัวและการไล่ระดับสีของตัวแปรกลางที่เขียนถึงตัวแปร leaf ให้ทำผลิตภัณฑ์ดอทและสรุปสิ่งเหล่านี้ทั้งหมด

gradientข้อโต้แย้งของVariable's backward()วิธีการที่ใช้ในการคำนวณผลรวมถ่วงน้ำหนักขององค์ประกอบของตัวแปรแต่ละ WRT ตัวแปรใบ น้ำหนักเหล่านี้เป็นเพียงอนุพันธ์ของlosswrt สุดท้ายแต่ละองค์ประกอบของตัวแปรกลาง

ตัวอย่างที่เป็นรูปธรรม

ลองมาเป็นตัวอย่างที่เป็นรูปธรรมและเข้าใจง่าย

from torch.autograd import Variable
import torch
x = Variable(torch.FloatTensor([[1, 2, 3, 4]]), requires_grad=True)
z = 2*x
loss = z.sum(dim=1)

# do backward for first element of z
z.backward(torch.FloatTensor([[1, 0, 0, 0]]), retain_graph=True)
print(x.grad.data)
x.grad.data.zero_() #remove gradient in x.grad, or it will be accumulated

# do backward for second element of z
z.backward(torch.FloatTensor([[0, 1, 0, 0]]), retain_graph=True)
print(x.grad.data)
x.grad.data.zero_()

# do backward for all elements of z, with weight equal to the derivative of
# loss w.r.t z_1, z_2, z_3 and z_4
z.backward(torch.FloatTensor([[1, 1, 1, 1]]), retain_graph=True)
print(x.grad.data)
x.grad.data.zero_()

# or we can directly backprop using loss
loss.backward() # equivalent to loss.backward(torch.FloatTensor([1.0]))
print(x.grad.data)    

ในตัวอย่างข้างต้นผลลัพธ์ของอันดับแรกprintคือ

2 0 0 0
[torch.FloatTensor ขนาด 1x4]

ซึ่งเป็นอนุพันธ์ของ z_1 wrt ถึง x

ผลลัพธ์ของวินาทีprintคือ:

0 2 0 0
[torch.FloatTensor ขนาด 1x4]

ซึ่งเป็นอนุพันธ์ของ z_2 wrt ถึง x

ตอนนี้ถ้าใช้น้ำหนักของ [1, 1, 1, 1] ในการคำนวณอนุพันธ์ของ Z WRT เป็น x 1*dz_1/dx + 1*dz_2/dx + 1*dz_3/dx + 1*dz_4/dxผลคือ จึงไม่น่าแปลกใจที่ผลลัพธ์ของ 3rd printคือ:

2 2 2 2
[torch.FloatTensor ขนาด 1x4]

ควรสังเกตว่าเวกเตอร์น้ำหนัก [1, 1, 1, 1] เป็นอนุพันธ์ของlosswrt ถึง z_1, z_2, z_3 และ z_4 อนุพันธ์ของlosswrt to xคำนวณได้ดังนี้

d(loss)/dx = d(loss)/dz_1 * dz_1/dx + d(loss)/dz_2 * dz_2/dx + d(loss)/dz_3 * dz_3/dx + d(loss)/dz_4 * dz_4/dx

ดังนั้นผลลัพธ์ของ 4 printจึงเหมือนกับครั้งที่ 3 print:

2 2 2 2
[torch.FloatTensor ขนาด 1x4]


1
แค่สงสัยทำไมเราถึงคำนวณx.grad.dataสำหรับการไล่ระดับสีสำหรับการสูญเสียหรือ z
Priyank Pathak

7
บางทีฉันอาจพลาดบางอย่างไป แต่ฉันรู้สึกว่าเอกสารอย่างเป็นทางการสามารถอธิบายgradientข้อโต้แย้งได้ดีขึ้น ขอบคุณสำหรับคำตอบ.
ตัวเอก

3
@jdhao "มันควรจะตั้งข้อสังเกตว่าน้ำหนักเวกเตอร์[1, 1, 1, 1]เป็นสิ่งที่มาของlossWRT ไปz_1, z_2, z_3และz_4." ฉันคิดว่าคำพูดนี้เป็นกุญแจสำคัญในคำตอบจริงๆ เมื่อดูรหัสของ OP เครื่องหมายคำถามใหญ่ ๆ คือตัวเลข (มายากล) ตามอำเภอใจเหล่านี้สำหรับการไล่ระดับสีมาจากไหน ในตัวอย่างที่เป็นรูปธรรมของคุณฉันคิดว่ามันจะมีประโยชน์มากที่จะชี้ให้เห็นความสัมพันธ์ระหว่างเช่น[1, 0, 0 0]เทนเซอร์และlossฟังก์ชันทันทีเพื่อให้เราเห็นว่าค่าต่างๆนั้นไม่ได้เป็นไปตามอำเภอใจในตัวอย่างนี้
a_guest

1
@smwikipedia นั้นไม่เป็นความจริง ถ้าเราขยายก็จะกลายเป็นloss = z.sum(dim=1) loss = z_1 + z_2 + z_3 + z_4ถ้าคุณรู้ว่าแคลคูลัสง่ายคุณจะรู้ว่าที่มาของlossWRT จะเป็นz_1, z_2, z_3, z_4 [1, 1, 1, 1]
jdhao

1
ผมรักคุณ. ไขข้อสงสัย!
Black Jack 21

45

โดยทั่วไปกราฟการคำนวณของคุณจะมีเอาต์พุตสเกลาร์หนึ่งlossรายการ จากนั้นคุณสามารถคำนวณการไล่ระดับสีของlossWRT น้ำหนัก ( w) loss.backward()โดย ที่เริ่มต้นของการโต้แย้งคือbackward()1.0

ถ้าผลลัพธ์ของคุณมีค่าหลายค่า (เช่นloss=[loss1, loss2, loss3]) คุณสามารถคำนวณการไล่ระดับสีของการสูญเสียน้ำหนัก WRT loss.backward(torch.FloatTensor([1.0, 1.0, 1.0]))โดย

นอกจากนี้หากคุณต้องการเพิ่มน้ำหนักหรือ importances loss.backward(torch.FloatTensor([-0.1, 1.0, 0.0001]))การสูญเสียที่แตกต่างกันคุณสามารถใช้

ซึ่งหมายความว่าจะคำนวณ-0.1*d(loss1)/dw, d(loss2)/dw, 0.0001*d(loss3)/dwพร้อมกัน


1
"หากคุณต้องการเพิ่มน้ำหนักหรือการนำเข้าให้กับการสูญเสียที่แตกต่างกันคุณสามารถใช้ loss.backward (torch.FloatTensor ([- 0.1, 1.0, 0.0001]))" -> นี่เป็นความจริง แต่ค่อนข้างทำให้เข้าใจผิดเพราะสาเหตุหลักที่เราผ่านgrad_tensorsคือการไม่ชั่งน้ำหนักให้แตกต่างกัน แต่การไล่ระดับจะเขียนแต่ละองค์ประกอบของเทนเซอร์ที่เกี่ยวข้อง
Aerin

27

ในที่นี้ผลลัพธ์ของ forward () คือ y คือ aa 3-vector

ค่าสามค่าคือการไล่ระดับสีที่เอาต์พุตของเครือข่าย โดยปกติจะตั้งค่าเป็น 1.0 หาก y เป็นผลลัพธ์สุดท้าย แต่สามารถมีค่าอื่น ๆ ได้เช่นกันโดยเฉพาะอย่างยิ่งถ้า y เป็นส่วนหนึ่งของเครือข่ายที่ใหญ่กว่า

สำหรับเช่น ถ้า x เป็นอินพุต y = [y1, y2, y3] คือเอาต์พุตระดับกลางที่ใช้ในการคำนวณผลลัพธ์สุดท้าย z

จากนั้น

dz/dx = dz/dy1 * dy1/dx + dz/dy2 * dy2/dx + dz/dy3 * dy3/dx

ที่นี่สามค่าที่จะย้อนกลับคือ

[dz/dy1, dz/dy2, dz/dy3]

แล้วย้อนกลับ () คำนวณ dz / dx


5
ขอบคุณสำหรับคำตอบ แต่สิ่งนี้มีประโยชน์อย่างไรในทางปฏิบัติ ฉันหมายถึงว่าเราต้องการ [dz / dy1, dz / dy2, dz / dy3] ที่ใดนอกเหนือจากการเข้ารหัส backprop
hi15

ถูกต้องหรือไม่ที่จะบอกว่าอาร์กิวเมนต์เกรเดียนต์ที่ให้มาเป็นเกรเดียนต์ที่คำนวณในส่วนหลังของเครือข่าย
Khanetor
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.