ในการติดตั้ง pytorch ทำตามคำแนะนำในเว็บไซต์ทางการ:
pip install torch torchvision
เรามุ่งหวังที่จะขยายซีรีส์นี้ค่อยๆโดยการเพิ่มบทความใหม่และทำให้เนื้อหาได้รับการปรับปรุงให้ทันสมัยด้วยการเผยแพร่ล่าสุดของ Pytorch API หากคุณมีคำแนะนำเกี่ยวกับวิธีการปรับปรุงซีรีส์นี้หรือค้นหาคำอธิบายที่คลุมเครืออย่าลังเลที่จะสร้างปัญหาส่งแพตช์หรือเข้าถึงอีเมล
Pytorch เป็นหนึ่งในห้องสมุดที่ได้รับความนิยมมากที่สุดสำหรับการคำนวณเชิงตัวเลขและปัจจุบันเป็นหนึ่งในห้องสมุดที่ใช้กันอย่างแพร่หลายสำหรับการวิจัยการเรียนรู้ของเครื่อง ในหลาย ๆ ด้าน Pytorch นั้นคล้ายคลึงกับ Numpy ด้วยประโยชน์เพิ่มเติมที่ Pytorch ช่วยให้คุณทำการคำนวณของคุณใน CPU, GPU และ TPU โดยไม่ต้องเปลี่ยนเนื้อหาใด ๆ กับรหัสของคุณ Pytorch ยังทำให้ง่ายต่อการกระจายการคำนวณของคุณในอุปกรณ์หรือเครื่องจักรหลายเครื่อง หนึ่งในคุณสมบัติที่สำคัญที่สุดของ Pytorch คือความแตกต่างโดยอัตโนมัติ ช่วยให้การคำนวณการไล่ระดับสีของฟังก์ชั่นของคุณวิเคราะห์อย่างมีประสิทธิภาพซึ่งเป็นสิ่งสำคัญสำหรับรูปแบบการเรียนรู้ของเครื่องฝึกโดยใช้วิธีการไล่ระดับสี เป้าหมายของเราที่นี่คือการแนะนำ Pytorch อย่างอ่อนโยนและหารือเกี่ยวกับแนวปฏิบัติที่ดีที่สุดสำหรับการใช้ Pytorch
สิ่งแรกที่เรียนรู้เกี่ยวกับ Pytorch คือแนวคิดของเทนเซอร์ เทนเซอร์เป็นเพียงอาร์เรย์หลายมิติ Pytorch Tensor คล้ายกับอาร์เรย์ numpy กับบางส่วน วิเศษ ฟังก์ชั่นเพิ่มเติม
เทนเซอร์สามารถเก็บค่าสเกลาร์:
import torch
a = torch . tensor ( 3 )
print ( a ) # tensor(3)หรืออาร์เรย์:
b = torch . tensor ([ 1 , 2 ])
print ( b ) # tensor([1, 2])เมทริกซ์:
c = torch . zeros ([ 2 , 2 ])
print ( c ) # tensor([[0., 0.], [0., 0.]])หรือเทนเซอร์มิติใด ๆ :
d = torch . rand ([ 2 , 2 , 2 ])เทนเซอร์สามารถใช้ในการดำเนินการพีชคณิตได้อย่างมีประสิทธิภาพ หนึ่งในการดำเนินการที่ใช้กันมากที่สุดในแอพพลิเคชั่นการเรียนรู้ของเครื่องคือการคูณเมทริกซ์ สมมติว่าคุณต้องการคูณเมทริกซ์แบบสุ่มสองขนาด 3x5 และ 5x4 ซึ่งสามารถทำได้ด้วยการดำเนินการคูณเมทริกซ์ (@):
import torch
x = torch . randn ([ 3 , 5 ])
y = torch . randn ([ 5 , 4 ])
z = x @ y
print ( z )ในทำนองเดียวกันในการเพิ่มเวกเตอร์สองตัวคุณสามารถทำได้:
z = x + yในการแปลงเทนเซอร์เป็นอาร์เรย์ numpy คุณสามารถเรียกวิธี NumPy () ของ Tensor:
print ( z . numpy ())และคุณสามารถแปลงอาร์เรย์ numpy เป็นเทนเซอร์ได้โดย:
x = torch . tensor ( np . random . normal ([ 3 , 5 ]))ข้อได้เปรียบที่สำคัญที่สุดของ pytorch มากกว่า NumPy คือฟังก์ชั่นการสร้างความแตกต่างอัตโนมัติซึ่งมีประโยชน์มากในการเพิ่มประสิทธิภาพแอปพลิเคชันเช่นการเพิ่มประสิทธิภาพพารามิเตอร์ของเครือข่ายประสาท ลองเข้าใจด้วยตัวอย่าง
สมมติว่าคุณมีฟังก์ชั่นคอมโพสิตซึ่งเป็นห่วงโซ่ของสองฟังก์ชั่น: g(u(x)) ในการคำนวณอนุพันธ์ของ g ด้วยความเคารพต่อ x เราสามารถใช้กฎโซ่ซึ่งระบุว่า: dg/dx = dg/du * du/dx Pytorch สามารถวิเคราะห์อนุพันธ์สำหรับเราได้
ในการคำนวณอนุพันธ์ใน Pytorch ก่อนเราจะสร้างเทนเซอร์และตั้งค่า requires_grad เป็นจริง เราสามารถใช้การดำเนินการของเทนเซอร์เพื่อกำหนดฟังก์ชั่นของเรา เราถือว่า u เป็นฟังก์ชันกำลังสองและ g เป็นฟังก์ชันเชิงเส้นง่าย ๆ :
x = torch . tensor ( 1.0 , requires_grad = True )
def u ( x ):
return x * x
def g ( u ):
return - u ในกรณีนี้ฟังก์ชั่นคอมโพสิตของเราคือ g(u(x)) = -x*x ดังนั้นอนุพันธ์ของมันเกี่ยวกับ x คือ -2x ที่จุด x=1 นี่เท่ากับ -2
มาตรวจสอบสิ่งนี้กันเถอะ สามารถทำได้โดยใช้ฟังก์ชั่นบัณฑิตใน pytorch:
dgdx = torch . autograd . grad ( g ( u ( x )), x )[ 0 ]
print ( dgdx ) # tensor(-2.) เพื่อให้เข้าใจว่าการสร้างความแตกต่างอัตโนมัติที่ทรงพลังสามารถดูตัวอย่างอื่นได้อย่างไร สมมติว่าเรามีตัวอย่างจากเส้นโค้ง (พูด f(x) = 5x^2 + 3 ) และเราต้องการประเมิน f(x) ตามตัวอย่างเหล่านี้ เรากำหนดฟังก์ชันพารามิเตอร์ g(x, w) = w0 x^2 + w1 x + w2 ซึ่งเป็นฟังก์ชันของอินพุต x และพารามิเตอร์แฝง w เป้าหมายของเราคือการค้นหาพารามิเตอร์แฝงเช่น g(x, w) ≈ f(x) สิ่งนี้สามารถทำได้โดยการลดฟังก์ชั่นการสูญเสียต่อไปนี้: L(w) = Σ (f(x) - g(x, w))^2 แม้ว่าจะมีวิธีการแก้ปัญหาแบบปิดสำหรับปัญหาง่าย ๆ นี้ แต่เราเลือกที่จะใช้วิธีการทั่วไปที่สามารถนำไปใช้กับฟังก์ชั่นที่แตกต่างกันโดยพลการและใช้การไล่ระดับสีแบบสุ่ม เราเพียงแค่คำนวณการไล่ระดับสีเฉลี่ยของ L(w) ด้วยความเคารพต่อ w ผ่านชุดตัวอย่างและย้ายไปในทิศทางตรงกันข้าม
นี่คือวิธีที่สามารถทำได้ใน Pytorch:
import numpy as np
import torch
# Assuming we know that the desired function is a polynomial of 2nd degree, we
# allocate a vector of size 3 to hold the coefficients and initialize it with
# random noise.
w = torch . tensor ( torch . randn ([ 3 , 1 ]), requires_grad = True )
# We use the Adam optimizer with learning rate set to 0.1 to minimize the loss.
opt = torch . optim . Adam ([ w ], 0.1 )
def model ( x ):
# We define yhat to be our estimate of y.
f = torch . stack ([ x * x , x , torch . ones_like ( x )], 1 )
yhat = torch . squeeze ( f @ w , 1 )
return yhat
def compute_loss ( y , yhat ):
# The loss is defined to be the mean squared error distance between our
# estimate of y and its true value.
loss = torch . nn . functional . mse_loss ( yhat , y )
return loss
def generate_data ():
# Generate some training data based on the true function
x = torch . rand ( 100 ) * 20 - 10
y = 5 * x * x + 3
return x , y
def train_step ():
x , y = generate_data ()
yhat = model ( x )
loss = compute_loss ( y , yhat )
opt . zero_grad ()
loss . backward ()
opt . step ()
for _ in range ( 1000 ):
train_step ()
print ( w . detach (). numpy ())ด้วยการเรียกใช้รหัสชิ้นนี้คุณควรเห็นผลลัพธ์ใกล้เคียงกับสิ่งนี้:
[ 4.9924135 , 0.00040895029 , 3.4504161 ]ซึ่งเป็นการประมาณที่ค่อนข้างใกล้เคียงกับพารามิเตอร์ของเรา
นี่เป็นเพียงเคล็ดลับของภูเขาน้ำแข็งสำหรับสิ่งที่ Pytorch สามารถทำได้ ปัญหามากมายเช่นการเพิ่มประสิทธิภาพเครือข่ายประสาทขนาดใหญ่ที่มีพารามิเตอร์นับล้านสามารถนำไปใช้ได้อย่างมีประสิทธิภาพใน pytorch ในรหัสเพียงไม่กี่บรรทัด Pytorch ดูแลการปรับขนาดข้ามอุปกรณ์หลายตัวและเธรดและรองรับแพลตฟอร์มที่หลากหลาย
ในตัวอย่างก่อนหน้านี้เราใช้เทนเซอร์กระดูกเปลือยและการทำงานของเทนเซอร์เพื่อสร้างแบบจำลองของเรา เพื่อให้รหัสของคุณมีการจัดระเบียบมากขึ้นขอแนะนำให้ใช้โมดูลของ Pytorch โมดูลเป็นเพียงคอนเทนเนอร์สำหรับพารามิเตอร์ของคุณและห่อหุ้มการทำงานของโมเดล ตัวอย่างเช่นบอกว่าคุณต้องการแสดงโมเดลเชิงเส้น y = ax + b รุ่นนี้สามารถแสดงด้วยรหัสต่อไปนี้:
import torch
class Net ( torch . nn . Module ):
def __init__ ( self ):
super (). __init__ ()
self . a = torch . nn . Parameter ( torch . rand ( 1 ))
self . b = torch . nn . Parameter ( torch . rand ( 1 ))
def forward ( self , x ):
yhat = self . a * x + self . b
return yhatหากต้องการใช้โมเดลนี้ในทางปฏิบัติคุณจะยกตัวอย่างโมดูลและเรียกมันว่าเหมือนฟังก์ชั่น:
x = torch . arange ( 100 , dtype = torch . float32 )
net = Net ()
y = net ( x ) พารามิเตอร์เป็นเทนเซอร์ที่มีการตั้งค่า requires_grad เป็น TRUE สะดวกในการใช้พารามิเตอร์เพราะคุณสามารถดึงข้อมูลทั้งหมดด้วยวิธี parameters() :
for p in net . parameters ():
print ( p ) ตอนนี้สมมติว่าคุณมีฟังก์ชั่นที่ไม่รู้จัก y = 5x + 3 + some noise และคุณต้องการเพิ่มประสิทธิภาพพารามิเตอร์ของโมเดลของคุณให้พอดีกับฟังก์ชั่นนี้ คุณสามารถเริ่มต้นด้วยการสุ่มตัวอย่างบางจุดจากฟังก์ชั่นของคุณ:
x = torch . arange ( 100 , dtype = torch . float32 ) / 100
y = 5 * x + 3 + torch . rand ( 100 ) * 0.3คล้ายกับตัวอย่างก่อนหน้านี้คุณสามารถกำหนดฟังก์ชั่นการสูญเสียและเพิ่มประสิทธิภาพพารามิเตอร์ของโมเดลของคุณดังนี้:
criterion = torch . nn . MSELoss ()
optimizer = torch . optim . SGD ( net . parameters (), lr = 0.01 )
for i in range ( 10000 ):
net . zero_grad ()
yhat = net ( x )
loss = criterion ( yhat , y )
loss . backward ()
optimizer . step ()
print ( net . a , net . b ) # Should be close to 5 and 3 Pytorch มาพร้อมกับโมดูลที่กำหนดไว้ล่วงหน้าจำนวนมาก หนึ่งโมดูลดังกล่าวคือ torch.nn.Linear ซึ่งเป็นรูปแบบทั่วไปของฟังก์ชันเชิงเส้นมากกว่าที่เรากำหนดไว้ข้างต้น เราสามารถเขียนโมดูลของเราใหม่ได้โดยใช้ torch.nn.Linear เช่นนี้:
class Net ( torch . nn . Module ):
def __init__ ( self ):
super (). __init__ ()
self . linear = torch . nn . Linear ( 1 , 1 )
def forward ( self , x ):
yhat = self . linear ( x . unsqueeze ( 1 )). squeeze ( 1 )
return yhat โปรดทราบว่าเราใช้การบีบและไม่คับหนานตั้งแต่ torch.nn.Linear ทำงานกับชุดของเวกเตอร์เมื่อเทียบกับสเกลาร์
โดยค่าเริ่มต้นพารามิเตอร์การโทร () บนโมดูลจะส่งคืนพารามิเตอร์ของ submodules ทั้งหมด:
net = Net ()
for p in net . parameters ():
print ( p ) มีโมดูลที่กำหนดไว้ล่วงหน้าบางอย่างที่ทำหน้าที่เป็นคอนเทนเนอร์สำหรับโมดูลอื่น ๆ โมดูลคอนเทนเนอร์ที่ใช้กันมากที่สุดคือ torch.nn.Sequential ตามชื่อของมันหมายถึงมันใช้ในการซ้อนโมดูลหลายโมดูล (หรือเลเยอร์) ด้านบนของกันและกัน ตัวอย่างเช่นการซ้อนสองชั้นเชิงเส้นที่มีความไม่เชิงเส้น ReLU ระหว่างคุณสามารถทำได้:
model = torch . nn . Sequential (
torch . nn . Linear ( 64 , 32 ),
torch . nn . ReLU (),
torch . nn . Linear ( 32 , 10 ),
) Pytorch รองรับการดำเนินการ Broadcasting Elementwise โดยปกติเมื่อคุณต้องการดำเนินการเช่นการเพิ่มและการคูณคุณต้องตรวจสอบให้แน่ใจว่ารูปร่างของตัวถูกดำเนินการจับคู่เช่นคุณไม่สามารถเพิ่มเทนเซอร์ของรูปร่าง [3, 2] ให้กับรูปร่างของรูปร่าง [3, 4] แต่มีกรณีพิเศษและนั่นคือเมื่อคุณมีมิติที่แปลกประหลาด Pytorch กระเบื้องโดยปริยายเทนเซอร์ข้ามมิติเอกพจน์เพื่อให้ตรงกับรูปร่างของตัวถูกดำเนินการอื่น ๆ ดังนั้นจึงเป็นเรื่องถูกต้องที่จะเพิ่มเทนเซอร์ของรูปร่าง [3, 2] ลงในเทนเซอร์ของรูปร่าง [3, 1]
import torch
a = torch . tensor ([[ 1. , 2. ], [ 3. , 4. ]])
b = torch . tensor ([[ 1. ], [ 2. ]])
# c = a + b.repeat([1, 2])
c = a + b
print ( c )การออกอากาศช่วยให้เราสามารถทำปูกระเบื้องโดยปริยายซึ่งทำให้รหัสสั้นลงและมีประสิทธิภาพมากขึ้นเนื่องจากเราไม่จำเป็นต้องจัดเก็บผลลัพธ์ของการทำงานของการปูกระเบื้อง สถานที่ที่เรียบร้อยหนึ่งที่สามารถใช้ได้คือการรวมคุณสมบัติของความยาวที่แตกต่างกัน เพื่อที่จะต่อคุณสมบัติของความยาวที่แตกต่างกันเรามักจะกระเบื้องเทนเซอร์อินพุตให้สอดคล้องกับผลลัพธ์และใช้ความไม่เชิงเส้นบางอย่าง นี่เป็นรูปแบบทั่วไปในสถาปัตยกรรมเครือข่ายประสาทที่หลากหลาย:
a = torch . rand ([ 5 , 3 , 5 ])
b = torch . rand ([ 5 , 1 , 6 ])
linear = torch . nn . Linear ( 11 , 10 )
# concat a and b and apply nonlinearity
tiled_b = b . repeat ([ 1 , 3 , 1 ])
c = torch . cat ([ a , tiled_b ], 2 )
d = torch . nn . functional . relu ( linear ( c ))
print ( d . shape ) # torch.Size([5, 3, 10]) แต่สิ่งนี้สามารถทำได้อย่างมีประสิทธิภาพมากขึ้นด้วยการออกอากาศ เราใช้ความจริงที่ว่า f(m(x + y)) เท่ากับ f(mx + my) ดังนั้นเราสามารถดำเนินการเชิงเส้นแยกต่างหากและใช้การกระจายเสียงเพื่อทำการต่อโดยนัย:
a = torch . rand ([ 5 , 3 , 5 ])
b = torch . rand ([ 5 , 1 , 6 ])
linear1 = torch . nn . Linear ( 5 , 10 )
linear2 = torch . nn . Linear ( 6 , 10 )
pa = linear1 ( a )
pb = linear2 ( b )
d = torch . nn . functional . relu ( pa + pb )
print ( d . shape ) # torch.Size([5, 3, 10])ในความเป็นจริงรหัสชิ้นนี้ค่อนข้างทั่วไปและสามารถนำไปใช้กับเทนเซอร์ของรูปร่างโดยพลการตราบใดที่การแพร่ภาพระหว่างเทนเซอร์เป็นไปได้:
class Merge ( torch . nn . Module ):
def __init__ ( self , in_features1 , in_features2 , out_features , activation = None ):
super (). __init__ ()
self . linear1 = torch . nn . Linear ( in_features1 , out_features )
self . linear2 = torch . nn . Linear ( in_features2 , out_features )
self . activation = activation
def forward ( self , a , b ):
pa = self . linear1 ( a )
pb = self . linear2 ( b )
c = pa + pb
if self . activation is not None :
c = self . activation ( c )
return cจนถึงตอนนี้เราได้พูดคุยเกี่ยวกับส่วนที่ดีของการออกอากาศ แต่ส่วนที่น่าเกลียดที่คุณถามคืออะไร? สมมติฐานโดยนัยมักจะทำให้การดีบักทำยากขึ้น พิจารณาตัวอย่างต่อไปนี้:
a = torch . tensor ([[ 1. ], [ 2. ]])
b = torch . tensor ([ 1. , 2. ])
c = torch . sum ( a + b )
print ( c ) คุณคิดว่าคุณค่าของ c จะเป็นอย่างไรหลังจากการประเมินผล? ถ้าคุณเดา 6 มันผิด มันจะเป็น 12 นี่เป็นเพราะเมื่ออันดับสองเทนเซอร์ไม่ตรงกัน Pytorch จะขยายมิติแรกของเทนเซอร์โดยอัตโนมัติด้วยอันดับที่ต่ำกว่าก่อนการดำเนินการองค์ประกอบดังนั้นผลลัพธ์ของการเพิ่มจะเป็น [[2, 3], [3, 4]] และการลดพารามิเตอร์ทั้งหมดจะให้ 12
วิธีที่จะหลีกเลี่ยงปัญหานี้คือการชัดเจนที่สุดเท่าที่จะทำได้ หากเราระบุมิติที่เราต้องการลดลงการจับข้อผิดพลาดนี้จะง่ายกว่ามาก:
a = torch . tensor ([[ 1. ], [ 2. ]])
b = torch . tensor ([ 1. , 2. ])
c = torch . sum ( a + b , 0 )
print ( c ) ที่นี่คุณค่าของ c จะเป็น [5, 7] และเราจะเดาได้ทันทีตามรูปร่างของผลลัพธ์ที่มีบางอย่างผิดปกติ กฎทั่วไปของหัวแม่มือคือการระบุขนาดในการลดการดำเนินการและเมื่อใช้ torch.squeeze
เช่นเดียวกับ numpy, pytorch overloads ผู้ให้บริการ Python จำนวนหนึ่งเพื่อให้รหัส pytorch สั้นลงและอ่านได้มากขึ้น
OP แบบแยกเป็นหนึ่งในตัวดำเนินการที่มากเกินไปที่สามารถทำให้การจัดทำดัชนีเทนเซอร์ง่ายมาก:
z = x [ begin : end ] # z = torch.narrow(0, begin, end-begin)ระวังให้มากเมื่อใช้ OP นี้ OP แบบแยกเช่นเดียวกับ OP อื่น ๆ มีค่าใช้จ่ายอยู่บ้าง เพราะมันเป็น OP ที่พบบ่อยและดูไร้เดียงสามันอาจใช้มากเกินไปซึ่งอาจนำไปสู่ความไร้ประสิทธิภาพ เพื่อให้เข้าใจว่า OP นี้ไม่มีประสิทธิภาพอย่างไรลองดูตัวอย่าง เราต้องการลดระดับของเมทริกซ์ด้วยตนเองด้วยตนเอง:
import torch
import time
x = torch . rand ([ 500 , 10 ])
z = torch . zeros ([ 10 ])
start = time . time ()
for i in range ( 500 ):
z += x [ i ]
print ( "Took %f seconds." % ( time . time () - start )) สิ่งนี้ทำงานได้ค่อนข้างช้าและเหตุผลก็คือเรากำลังเรียก Slice OP 500 ครั้งซึ่งเพิ่มค่าใช้จ่ายจำนวนมาก ตัวเลือกที่ดีกว่าคือการใช้ torch.unbind OP เพื่อแบ่งเมทริกซ์ลงในรายการเวกเตอร์ทั้งหมดในครั้งเดียว:
z = torch . zeros ([ 10 ])
for x_i in torch . unbind ( x ):
z += x_iนี่คืออย่างมีนัยสำคัญ (~ 30% ในเครื่องของฉัน) เร็วขึ้น
แน่นอนวิธีที่ถูกต้องในการลดความเรียบง่ายนี้คือการใช้ torch.sum op กับสิ่งนี้ในหนึ่ง op:
z = torch . sum ( x , dim = 0 )ซึ่งเร็วมาก (~ 100x เร็วขึ้นบนเครื่องของฉัน)
Pytorch ยังโอเวอร์โหลดช่วงของตัวดำเนินการเลขคณิตและตรรกะ:
z = - x # z = torch.neg(x)
z = x + y # z = torch.add(x, y)
z = x - y
z = x * y # z = torch.mul(x, y)
z = x / y # z = torch.div(x, y)
z = x // y
z = x % y
z = x ** y # z = torch.pow(x, y)
z = x @ y # z = torch.matmul(x, y)
z = x > y
z = x >= y
z = x < y
z = x <= y
z = abs ( x ) # z = torch.abs(x)
z = x & y
z = x | y
z = x ^ y # z = torch.logical_xor(x, y)
z = ~ x # z = torch.logical_not(x)
z = x == y # z = torch.eq(x, y)
z = x != y # z = torch.ne(x, y) นอกจากนี้คุณยังสามารถใช้ OPS เหล่านี้ได้ ตัวอย่างเช่น x += y และ x **= 2 ก็ใช้ได้เช่นกัน
โปรดทราบว่า Python ไม่อนุญาตให้มีการโอเวอร์โหลด and or not คำหลัก
Pytorch ได้รับการปรับให้เหมาะสมเพื่อดำเนินการกับเทนเซอร์ขนาดใหญ่ การดำเนินการหลายอย่างในเทนเซอร์ขนาดเล็กนั้นค่อนข้างไม่มีประสิทธิภาพใน Pytorch ดังนั้นเมื่อใดก็ตามที่เป็นไปได้คุณควรเขียนการคำนวณของคุณในรูปแบบแบทช์เพื่อลดค่าใช้จ่ายและปรับปรุงประสิทธิภาพ หากไม่มีวิธีที่คุณสามารถแบทช์การดำเนินงานของคุณด้วยตนเองการใช้ TorchScript อาจปรับปรุงประสิทธิภาพของรหัสของคุณ Torchscript เป็นเพียงส่วนย่อยของฟังก์ชั่น Python ที่ได้รับการยอมรับจาก Pytorch Pytorch สามารถเพิ่มประสิทธิภาพรหัส Torchscript ของคุณโดยอัตโนมัติโดยใช้คอมไพเลอร์ในเวลา (JIT) โดยอัตโนมัติและลดค่าใช้จ่ายบางส่วน
ลองดูตัวอย่าง การดำเนินการที่พบบ่อยมากในแอปพลิเคชัน ML คือ "รวบรวมแบทช์" การดำเนินการนี้สามารถเขียนเป็น output[i] = input[i, index[i]] สิ่งนี้สามารถนำไปใช้ใน Pytorch ได้ดังนี้:
import torch
def batch_gather ( tensor , indices ):
output = []
for i in range ( tensor . size ( 0 )):
output += [ tensor [ i ][ indices [ i ]]]
return torch . stack ( output ) ในการใช้ฟังก์ชั่นเดียวกันโดยใช้ Torchscript เพียงใช้ torch.jit.script Decorator:
@ torch . jit . script
def batch_gather_jit ( tensor , indices ):
output = []
for i in range ( tensor . size ( 0 )):
output += [ tensor [ i ][ indices [ i ]]]
return torch . stack ( output )ในการทดสอบของฉันจะเร็วขึ้นประมาณ 10%
แต่ไม่มีอะไรดีไปกว่าการแบตช์การดำเนินงานของคุณด้วยตนเอง การนำเวกเตอร์ในการทดสอบของฉันเร็วกว่า 100 เท่า:
def batch_gather_vec ( tensor , indices ):
shape = list ( tensor . shape )
flat_first = torch . reshape (
tensor , [ shape [ 0 ] * shape [ 1 ]] + shape [ 2 :])
offset = torch . reshape (
torch . arange ( shape [ 0 ]). cuda () * shape [ 1 ],
[ shape [ 0 ]] + [ 1 ] * ( len ( indices . shape ) - 1 ))
output = flat_first [ indices + offset ]
return output ในบทเรียนสุดท้ายเราได้พูดคุยเกี่ยวกับการเขียนรหัส pytorch ที่มีประสิทธิภาพ แต่เพื่อให้รหัสของคุณทำงานด้วยประสิทธิภาพสูงสุดคุณต้องโหลดข้อมูลของคุณอย่างมีประสิทธิภาพลงในหน่วยความจำของอุปกรณ์ของคุณ โชคดีที่ Pytorch เสนอเครื่องมือในการทำให้การโหลดข้อมูลง่ายขึ้น มันเรียกว่า DataLoader DataLoader ใช้คนงานหลายคนในการโหลดข้อมูลจาก Dataset และเลือกใช้ Sampler เพื่อสุ่มตัวอย่างรายการข้อมูลและสร้างแบทช์
หากคุณสามารถเข้าถึงข้อมูลของคุณแบบสุ่มโดยใช้ DataLoader นั้นง่ายมาก: คุณเพียงแค่ต้องใช้คลาสชุด Dataset ที่ใช้ __getitem__ (เพื่ออ่านแต่ละรายการข้อมูล) และ __len__ (เพื่อส่งคืนจำนวนรายการในชุดข้อมูล) ตัวอย่างเช่นนี่คือวิธีการโหลดภาพจากไดเรกทอรีที่กำหนด:
import glob
import os
import random
import cv2
import torch
class ImageDirectoryDataset ( torch . utils . data . Dataset ):
def __init__ ( path , pattern ):
self . paths = list ( glob . glob ( os . path . join ( path , pattern )))
def __len__ ( self ):
return len ( self . paths )
def __item__ ( self ):
path = random . choice ( paths )
return cv2 . imread ( path , 1 )ในการโหลดภาพ JPEG ทั้งหมดจากไดเรกทอรีที่กำหนดคุณสามารถทำได้ดังต่อไปนี้:
dataloader = torch . utils . data . DataLoader ( ImageDirectoryDataset ( "/data/imagenet/*.jpg" ), num_workers = 8 )
for data in dataloader :
# do something with dataที่นี่เราใช้คนงาน 8 คนเพื่ออ่านข้อมูลของเราจากดิสก์อย่างพร้อมกัน คุณสามารถปรับจำนวนคนงานบนเครื่องเพื่อให้ได้ผลลัพธ์ที่ดีที่สุด
การใช้ DataLoader เพื่ออ่านข้อมูลด้วยการเข้าถึงแบบสุ่มอาจใช้ได้ถ้าคุณมีที่เก็บข้อมูลที่รวดเร็วหรือหากรายการข้อมูลของคุณมีขนาดใหญ่ แต่ลองนึกภาพว่ามีระบบไฟล์เครือข่ายที่มีการเชื่อมต่อช้า การขอไฟล์แต่ละไฟล์ด้วยวิธีนี้อาจช้ามากและอาจกลายเป็นคอขวดของไปป์ไลน์การฝึกอบรมของคุณ
วิธีที่ดีกว่าคือการจัดเก็บข้อมูลของคุณในรูปแบบไฟล์ที่ต่อเนื่องกันซึ่งสามารถอ่านได้ตามลำดับ ตัวอย่างเช่นหากคุณมีคอลเลกชันภาพขนาดใหญ่คุณสามารถใช้ TAR เพื่อสร้างไฟล์เก็บถาวรและแยกไฟล์จากไฟล์เก็บถาวรตามลำดับใน Python ในการทำเช่นนี้คุณสามารถใช้ IterableDataset ของ Pytorch ในการสร้างคลาส IterableDataset คุณจะต้องใช้เมธอด __iter__ ซึ่งจะอ่านและให้รายการข้อมูลจากชุดข้อมูลตามลำดับ
การใช้งานที่ไร้เดียงสาจะชอบสิ่งนี้:
import tarfile
import torch
def tar_image_iterator ( path ):
tar = tarfile . open ( self . path , "r" )
for tar_info in tar :
file = tar . extractfile ( tar_info )
content = file . read ()
yield cv2 . imdecode ( content , 1 )
file . close ()
tar . members = []
tar . close ()
class TarImageDataset ( torch . utils . data . IterableDataset ):
def __init__ ( self , path ):
super (). __init__ ()
self . path = path
def __iter__ ( self ):
yield from tar_image_iterator ( self . path )แต่มีปัญหาสำคัญกับการดำเนินการนี้ หากคุณพยายามใช้ Dataloader เพื่ออ่านจากชุดข้อมูลนี้กับคนงานมากกว่าหนึ่งคนที่คุณสังเกตเห็นภาพที่ซ้ำกันจำนวนมาก:
dataloader = torch . utils . data . DataLoader ( TarImageDataset ( "/data/imagenet.tar" ), num_workers = 8 )
for data in dataloader :
# data contains duplicated items ปัญหาคือคนงานแต่ละคนสร้างอินสแตนซ์แยกต่างหากของชุดข้อมูลและแต่ละคนจะเริ่มต้นจากจุดเริ่มต้นของชุดข้อมูล วิธีหนึ่งในการหลีกเลี่ยงสิ่งนี้คือแทนที่จะมีไฟล์ TAR หนึ่งไฟล์แบ่งข้อมูลของคุณออกเป็น num_workers แยกไฟล์ tar และโหลดแต่ละไฟล์ด้วยคนงานแยกต่างหาก:
class TarImageDataset ( torch . utils . data . IterableDataset ):
def __init__ ( self , paths ):
super (). __init__ ()
self . paths = paths
def __iter__ ( self ):
worker_info = torch . utils . data . get_worker_info ()
# For simplicity we assume num_workers is equal to number of tar files
if worker_info is None or worker_info . num_workers != len ( self . paths ):
raise ValueError ( "Number of workers doesn't match number of files." )
yield from tar_image_iterator ( self . paths [ worker_info . worker_id ])นี่คือวิธีที่คลาสข้อมูลของเราสามารถใช้:
dataloader = torch . utils . data . DataLoader (
TarImageDataset ([ "/data/imagenet_part1.tar" , "/data/imagenet_part2.tar" ]), num_workers = 2 )
for data in dataloader :
# do something with dataเราได้พูดคุยเกี่ยวกับกลยุทธ์ง่ายๆเพื่อหลีกเลี่ยงปัญหารายการที่ซ้ำกัน แพ็คเกจ TFRECORD ใช้กลยุทธ์ที่ซับซ้อนกว่าเล็กน้อยเพื่อให้ข้อมูลของคุณได้ทันที
เมื่อใช้ไลบรารีการคำนวณเชิงตัวเลขใด ๆ เช่น numpy หรือ pytorch สิ่งสำคัญคือต้องทราบว่าการเขียนรหัสที่ถูกต้องทางคณิตศาสตร์ไม่จำเป็นต้องนำไปสู่ผลลัพธ์ที่ถูกต้อง คุณต้องตรวจสอบให้แน่ใจว่าการคำนวณมีความเสถียร
เริ่มต้นด้วยตัวอย่างง่ายๆ ในทางคณิตศาสตร์มันง่ายที่จะเห็นว่า x * y / y = x สำหรับค่าที่ไม่ใช่ศูนย์ใด ๆ ของ x แต่มาดูกันว่าเป็นจริงในทางปฏิบัติเสมอ:
import numpy as np
x = np . float32 ( 1 )
y = np . float32 ( 1e-50 ) # y would be stored as zero
z = x * y / y
print ( z ) # prints nan เหตุผลสำหรับผลลัพธ์ที่ไม่ถูกต้องคือ y นั้นเล็กเกินไปสำหรับประเภท Float32 ปัญหาที่คล้ายกันเกิดขึ้นเมื่อ y มีขนาดใหญ่เกินไป:
y = np . float32 ( 1e39 ) # y would be stored as inf
z = x * y / y
print ( z ) # prints nanค่าบวกที่เล็กที่สุดที่ Float32 ประเภทสามารถแสดงได้คือ 1.4013E-45 และสิ่งใดก็ตามที่อยู่ด้านล่างซึ่งจะถูกเก็บไว้เป็นศูนย์ นอกจากนี้หมายเลขใด ๆ ที่เกิน 3.40282E+38 จะถูกเก็บไว้เป็น INF
print ( np . nextafter ( np . float32 ( 0 ), np . float32 ( 1 ))) # prints 1.4013e-45
print ( np . finfo ( np . float32 ). max ) # print 3.40282e+38เพื่อให้แน่ใจว่าการคำนวณของคุณมีความเสถียรคุณต้องการหลีกเลี่ยงค่าที่มีค่าสัมบูรณ์ขนาดเล็กหรือใหญ่มาก สิ่งนี้อาจฟังดูชัดเจนมาก แต่ปัญหาประเภทนี้อาจกลายเป็นเรื่องยากมากที่จะแก้ไขข้อบกพร่องโดยเฉพาะอย่างยิ่งเมื่อทำการไล่ระดับสีใน Pytorch นี่เป็นเพราะคุณไม่เพียง แต่ต้องตรวจสอบให้แน่ใจว่าค่าทั้งหมดในบัตรผ่านไปข้างหน้าอยู่ในช่วงข้อมูลที่ถูกต้องของประเภทข้อมูลของคุณ แต่คุณต้องตรวจสอบให้แน่ใจว่าเหมือนกันสำหรับการผ่านไปข้างหลัง (ระหว่างการคำนวณการไล่ระดับสี)
ลองดูตัวอย่างจริง เราต้องการคำนวณ softmax ผ่านเวกเตอร์ของ logits การใช้งานที่ไร้เดียงสาจะมีลักษณะเช่นนี้:
import torch
def unstable_softmax ( logits ):
exp = torch . exp ( logits )
return exp / torch . sum ( exp )
print ( unstable_softmax ( torch . tensor ([ 1000. , 0. ])). numpy ()) # prints [ nan, 0.] โปรดทราบว่าการคำนวณแบบเอ็กซ์โปเนนเชียลของ logits สำหรับจำนวนที่ค่อนข้างเล็กผลลัพธ์ไปยังผลลัพธ์ขนาดมหึมาที่อยู่ในช่วง Float32 logit ที่ถูกต้องมากที่สุดสำหรับการใช้งาน Softmax ที่ไร้เดียงสาของเราคือ ln(3.40282e+38) = 88.7 อะไรก็ตามที่นอกเหนือจากนั้นนำไปสู่ผลลัพธ์ของ NAN
แต่เราจะทำให้สิ่งนี้มีเสถียรภาพมากขึ้นได้อย่างไร? การแก้ปัญหาค่อนข้างง่าย เป็นเรื่องง่ายที่จะเห็นว่า exp(x - c) Σ exp(x - c) = exp(x) / Σ exp(x) ดังนั้นเราสามารถลบค่าคงที่ใด ๆ จาก logits และผลลัพธ์จะยังคงเหมือนเดิม เราเลือกค่าคงที่นี้เพื่อให้สูงสุดของ logits วิธีนี้โดเมนของฟังก์ชันเอ็กซ์โปเนนเชียลจะถูก จำกัด ไว้ที่ [-inf, 0] และดังนั้นช่วงของมันจะเป็น [0.0, 1.0] ซึ่งเป็นที่ต้องการ:
import torch
def softmax ( logits ):
exp = torch . exp ( logits - torch . reduce_max ( logits ))
return exp / torch . sum ( exp )
print ( softmax ( torch . tensor ([ 1000. , 0. ])). numpy ()) # prints [ 1., 0.] ลองดูกรณีที่ซับซ้อนกว่านี้ พิจารณาว่าเรามีปัญหาการจำแนกประเภท เราใช้ฟังก์ชั่น SoftMax เพื่อสร้างความน่าจะเป็นจากบันทึกของเรา จากนั้นเรากำหนดฟังก์ชั่นการสูญเสียของเราให้เป็นเอนโทรปีข้ามระหว่างการทำนายและฉลากของเรา โปรดจำไว้ว่า cross entropy สำหรับการแจกแจงแบบจัดหมวดหมู่สามารถกำหนดได้ง่ายๆเป็น xe(p, q) = -Σ p_i log(q_i) ดังนั้นการใช้งานข้ามเอนโทรปีที่ไร้เดียงสาจะมีลักษณะเช่นนี้:
def unstable_softmax_cross_entropy ( labels , logits ):
logits = torch . log ( softmax ( logits ))
return - torch . sum ( labels * logits )
labels = torch . tensor ([ 0.5 , 0.5 ])
logits = torch . tensor ([ 1000. , 0. ])
xe = unstable_softmax_cross_entropy ( labels , logits )
print ( xe . numpy ()) # prints infโปรดทราบว่าในการใช้งานนี้เป็นเอาท์พุท SoftMax เข้าใกล้ศูนย์เอาท์พุทของบันทึกจะเข้าใกล้อินฟินิตี้ซึ่งทำให้เกิดความไม่แน่นอนในการคำนวณของเรา เราสามารถเขียนสิ่งนี้ใหม่ได้โดยการขยาย Softmax และทำสิ่งที่ง่ายขึ้น:
def softmax_cross_entropy ( labels , logits , dim = - 1 ):
scaled_logits = logits - torch . max ( logits )
normalized_logits = scaled_logits - torch . logsumexp ( scaled_logits , dim )
return - torch . sum ( labels * normalized_logits )
labels = torch . tensor ([ 0.5 , 0.5 ])
logits = torch . tensor ([ 1000. , 0. ])
xe = softmax_cross_entropy ( labels , logits )
print ( xe . numpy ()) # prints 500.0นอกจากนี้เรายังสามารถตรวจสอบได้ว่าการไล่ระดับสีนั้นถูกคำนวณอย่างถูกต้อง:
logits . requires_grad_ ( True )
xe = softmax_cross_entropy ( labels , logits )
g = torch . autograd . grad ( xe , logits )[ 0 ]
print ( g . numpy ()) # prints [0.5, -0.5]ให้ฉันเตือนอีกครั้งว่าต้องใช้ความระมัดระวังเป็นพิเศษเมื่อทำการไล่ระดับสีเพื่อให้แน่ใจว่าช่วงของฟังก์ชั่นของคุณเช่นเดียวกับการไล่ระดับสีสำหรับแต่ละชั้นอยู่ในช่วงที่ถูกต้อง ฟังก์ชั่นแบบเอ็กซ์โปเนนเชียลและลอการิทึมเมื่อใช้อย่างไร้เดียงสาเป็นปัญหาโดยเฉพาะอย่างยิ่งเพราะพวกเขาสามารถแมปจำนวนน้อยกับคนจำนวนมากและอีกทางหนึ่ง
โดยค่าเริ่มต้นเทนเซอร์และพารามิเตอร์โมเดลใน Pytorch จะถูกเก็บไว้ในความแม่นยำของจุดลอยตัว 32 บิต การฝึกอบรมเครือข่ายประสาทโดยใช้ลอย 32 บิตมักจะมีเสถียรภาพและไม่ก่อให้เกิดปัญหาเชิงตัวเลขที่สำคัญอย่างไรก็ตามเครือข่ายประสาทได้รับการแสดงให้ทำงานได้ค่อนข้างดีใน 16 บิตและแม้กระทั่งความแม่นยำที่ต่ำกว่า การคำนวณในช่วงเวลาที่ต่ำกว่าสามารถเร็วขึ้นอย่างมีนัยสำคัญใน GPU ที่ทันสมัย นอกจากนี้ยังมีประโยชน์เป็นพิเศษในการใช้หน่วยความจำที่น้อยกว่าที่เปิดใช้งานการฝึกอบรมรุ่นใหญ่และ/หรือมีขนาดแบทช์ขนาดใหญ่ซึ่งสามารถเพิ่มประสิทธิภาพเพิ่มเติมได้ ปัญหาคือการฝึกอบรมใน 16 บิตมักจะไม่มั่นคงมากเพราะความแม่นยำมักจะไม่เพียงพอที่จะดำเนินการบางอย่างเช่นการสะสม
เพื่อช่วยให้ปัญหานี้ Pytorch สนับสนุนการฝึกอบรมในความแม่นยำผสม โดยการฝึกอบรมความแม่นยำแบบผสมสั้น ๆ นั้นทำได้โดยการดำเนินการที่มีราคาแพง (เช่น Convolutions และ Matrix Multications) ใน 16 บิตโดยการคัดเลือกอินพุตในขณะที่ดำเนินการอื่น ๆ ที่มีความละเอียดอ่อนเช่นการสะสมใน 32 บิต วิธีนี้เราได้รับประโยชน์ทั้งหมดจากการคำนวณ 16 บิตโดยไม่มีข้อเสีย ต่อไปเราจะพูดคุยเกี่ยวกับการใช้ AutoCast และ GradScaler เพื่อทำการฝึกอบรมแบบผสมผสานอัตโนมัติ
autocast ช่วยปรับปรุงประสิทธิภาพการทำงานของรันไทม์โดยการหล่อข้อมูลเป็น 16 บิตโดยอัตโนมัติสำหรับการคำนวณบางอย่าง เพื่อทำความเข้าใจว่ามันทำงานอย่างไรลองดูตัวอย่าง:
import torch
x = torch . rand ([ 32 , 32 ]). cuda ()
y = torch . rand ([ 32 , 32 ]). cuda ()
with torch . cuda . amp . autocast ():
a = x + y
b = x @ y
print ( a . dtype ) # prints torch.float32
print ( b . dtype ) # prints torch.float16 หมายเหตุทั้ง x และ y เป็นเทนเซอร์ 32 บิต แต่ autocast ทำการคูณเมทริกซ์ใน 16 บิตในขณะที่ยังคงดำเนินการเพิ่มเติมใน 32 บิต จะเกิดอะไรขึ้นถ้าหนึ่งในตัวถูกดำเนินการอยู่ใน 16 บิต?
import torch
x = torch . rand ([ 32 , 32 ]). cuda ()
y = torch . rand ([ 32 , 32 ]). cuda (). half ()
with torch . cuda . amp . autocast ():
a = x + y
b = x @ y
print ( a . dtype ) # prints torch.float32
print ( b . dtype ) # prints torch.float16 autocast อีกครั้งและเปิดตัวดำเนินการ 32 บิตเป็น 16 บิตเพื่อทำการคูณเมทริกซ์ แต่ไม่ได้เปลี่ยนการดำเนินการเพิ่มเติม โดยค่าเริ่มต้นการเพิ่มเทนเซอร์สองตัวใน Pytorch ส่งผลให้มีความแม่นยำสูงกว่า
ในทางปฏิบัติคุณสามารถไว้วางใจ autocast ในการคัดเลือกนักแสดงที่ถูกต้องเพื่อปรับปรุงประสิทธิภาพของรันไทม์ สิ่งสำคัญคือการคำนวณการคำนวณผ่านไปข้างหน้าทั้งหมดของคุณภายใต้บริบท autocast :
model = ...
loss_fn = ...
with torch . cuda . amp . autocast ():
outputs = model ( inputs )
loss = loss_fn ( outputs , targets )นี่อาจเป็นสิ่งที่คุณต้องการหากคุณมีปัญหาการเพิ่มประสิทธิภาพที่ค่อนข้างเสถียรและหากคุณใช้อัตราการเรียนรู้ที่ค่อนข้างต่ำ การเพิ่มรหัสพิเศษหนึ่งบรรทัดนี้สามารถลดการฝึกอบรมของคุณได้มากถึงครึ่งหนึ่งในฮาร์ดแวร์ที่ทันสมัย
ดังที่เราได้กล่าวถึงในช่วงเริ่มต้นของส่วนนี้ความแม่นยำ 16 บิตอาจไม่เพียงพอสำหรับการคำนวณบางอย่าง กรณีหนึ่งที่น่าสนใจคือแสดงถึงค่าการไล่ระดับสีซึ่งส่วนใหญ่มักเป็นค่าเล็ก ๆ การเป็นตัวแทนของพวกเขาด้วยการลอยตัว 16 บิตมักจะนำไปสู่การบัฟเฟอร์อันเดอร์โฟล (เช่นพวกเขาจะเป็นตัวแทนเป็นศูนย์) สิ่งนี้ทำให้การฝึกอบรมเครือข่ายประสาทไม่แน่นอน GradScalar ได้รับการออกแบบมาเพื่อแก้ไขปัญหานี้ มันใช้เป็นอินพุตค่าการสูญเสียของคุณและทวีคูณด้วยสเกลาร์ขนาดใหญ่ค่าการไล่ระดับสีที่พองตัวและทำให้พวกเขาแสดงให้เห็นถึงความแม่นยำ 16 บิต จากนั้นจะปรับขนาดลงในระหว่างการอัปเดตการไล่ระดับสีเพื่อให้แน่ใจว่าพารามิเตอร์ได้รับการปรับปรุงอย่างถูกต้อง นี่คือสิ่งที่ GradScalar ทำ แต่ภายใต้ฮูด GradScalar นั้นฉลาดกว่านั้นเล็กน้อย การเพิ่มความลาดชันอาจส่งผลให้เกิดการล้นซึ่งไม่ดีพอ ๆ กัน ดังนั้น GradScalar จึงตรวจสอบค่าการไล่ระดับสีและหากตรวจพบล้นมันก็ข้ามการอัพเดทให้ปรับสเกลาร์ลงตามตารางเวลาที่กำหนดค่าได้ (กำหนดการเริ่มต้นมักจะใช้งานได้ แต่คุณอาจต้องปรับมันสำหรับกรณีการใช้งานของคุณ)
การใช้ GradScalar นั้นง่ายมากในทางปฏิบัติ:
scaler = torch . cuda . amp . GradScaler ()
loss = ...
optimizer = ... # an instance torch.optim.Optimizer
scaler . scale ( loss ). backward ()
scaler . step ( optimizer )
scaler . update () โปรดทราบว่าก่อนอื่นเราจะสร้างอินสแตนซ์ของ GradScalar ในการฝึกอบรมลูปเราเรียก GradScalar.scale เพื่อปรับขนาดการสูญเสียก่อนที่จะโทรไปข้างหลังเพื่อสร้างการไล่ระดับสีที่สูงเกินจริงเราจะใช้ GradScalar.step ซึ่ง (อาจ) อัปเดตพารามิเตอร์แบบจำลอง จากนั้นเราเรียก GradScalar.update ซึ่งดำเนินการอัปเดตสเกลาร์หากจำเป็น นั่นคือทั้งหมด!
ต่อไปนี้เป็นรหัสตัวอย่างที่แสดงกรณีการฝึกอบรมที่มีความแม่นยำผสมกับปัญหาสังเคราะห์ของการเรียนรู้ที่จะสร้างกระดานหมากรุกจากพิกัดภาพ คุณสามารถวางลงบน Google colab ตั้งแบ็กเอนด์เป็น GPU และเปรียบเทียบประสิทธิภาพเดียวและความแม่นยำผสม โปรดทราบว่านี่เป็นตัวอย่างของเล่นขนาดเล็กในทางปฏิบัติกับเครือข่ายขนาดใหญ่ที่คุณอาจเห็นการเพิ่มประสิทธิภาพที่ใหญ่ขึ้นโดยใช้ความแม่นยำแบบผสม
import torch
import matplotlib . pyplot as plt
import time
def grid ( width , height ):
hrange = torch . arange ( width ). unsqueeze ( 0 ). repeat ([ height , 1 ]). div ( width )
vrange = torch . arange ( height ). unsqueeze ( 1 ). repeat ([ 1 , width ]). div ( height )
output = torch . stack ([ hrange , vrange ], 0 )
return output
def checker ( width , height , freq ):
hrange = torch . arange ( width ). reshape ([ 1 , width ]). mul ( freq / width / 2.0 ). fmod ( 1.0 ). gt ( 0.5 )
vrange = torch . arange ( height ). reshape ([ height , 1 ]). mul ( freq / height / 2.0 ). fmod ( 1.0 ). gt ( 0.5 )
output = hrange . logical_xor ( vrange ). float ()
return output
# Note the inputs are grid coordinates and the target is a checkerboard
inputs = grid ( 512 , 512 ). unsqueeze ( 0 ). cuda ()
targets = checker ( 512 , 512 , 8 ). unsqueeze ( 0 ). unsqueeze ( 1 ). cuda () class Net ( torch . jit . ScriptModule ):
def __init__ ( self ):
super (). __init__ ()
self . net = torch . nn . Sequential (
torch . nn . Conv2d ( 2 , 256 , 1 ),
torch . nn . BatchNorm2d ( 256 ),
torch . nn . ReLU (),
torch . nn . Conv2d ( 256 , 256 , 1 ),
torch . nn . BatchNorm2d ( 256 ),
torch . nn . ReLU (),
torch . nn . Conv2d ( 256 , 256 , 1 ),
torch . nn . BatchNorm2d ( 256 ),
torch . nn . ReLU (),
torch . nn . Conv2d ( 256 , 1 , 1 ))
@ torch . jit . script_method
def forward ( self , x ):
return self . net ( x ) net = Net (). cuda ()
loss_fn = torch . nn . MSELoss ()
opt = torch . optim . Adam ( net . parameters (), 0.001 )
start_time = time . time ()
for i in range ( 500 ):
opt . zero_grad ()
outputs = net ( inputs )
loss = loss_fn ( outputs , targets )
loss . backward ()
opt . step ()
print ( loss )
print ( time . time () - start_time )
plt . subplot ( 1 , 2 , 1 ); plt . imshow ( outputs . squeeze (). detach (). cpu ());
plt . subplot ( 1 , 2 , 2 ); plt . imshow ( targets . squeeze (). cpu ()); plt . show () net = Net (). cuda ()
loss_fn = torch . nn . MSELoss ()
opt = torch . optim . Adam ( net . parameters (), 0.001 )
scaler = torch . cuda . amp . GradScaler ()
start_time = time . time ()
for i in range ( 500 ):
opt . zero_grad ()
with torch . cuda . amp . autocast ():
outputs = net ( inputs )
loss = loss_fn ( outputs , targets )
scaler . scale ( loss ). backward ()
scaler . step ( opt )
scaler . update ()
print ( loss )
print ( time . time () - start_time )
plt . subplot ( 1 , 2 , 1 ); plt . imshow ( outputs . squeeze (). detach (). cpu (). float ());
plt . subplot ( 1 , 2 , 2 ); plt . imshow ( targets . squeeze (). cpu (). float ()); plt . show ()