จาก ep ที่แล้ว ที่เราเรียนรู้ถึงคุณสมบัติพิเศษ ของ Tensor ที่จะมาช่วยในการคำนวนต่าง ๆ เมื่อเรามองเจาะลึกเข้าไปภายในของ Deep Neural Network เราจะพบว่าในขณะที่เราเทรน หรือขณะใช้งานโมเดลก็ตาม Mathematical Operations การดำเนินการทางคณิตศาสตร์ส่วนใหญ่ที่เกิดขึ้นก็คือ การคูณเมตริกซ์ โดยเฉพาะการคูณเมตริกซ์ (Matrix Multiplication) แบบ Dot Product
การคูณเมตริกซ์ที่รวดเร็วแม่นยำ มีผลต่อการทำงานของ Neural Network เป็นอย่างมาก

ดังนั้นใน ep นี้เราจะมาเรียนรู้การคูณเมตริกซ์ โดยใช้ คุณสมบัติพิเศษ ของ Tensor เช่น Element-wise, Broadcasting เพื่อให้เราออกแบบโมเดล และปรับปรุงอัลกอริทึม ให้เป็นแบบ Vectorization ให้โมเดลทำงานแบบขนาน บน GPU ได้ มีประสิทธิภาพดีขึ้น ตามที่เราต้องการ
เรามาเริ่มกันเลย
0. Magic¶
%load_ext autoreload
%autoreload 2
%matplotlib inline
1. Import¶
Import และประกาศฟังก์ชันที่จำเป็น
import operator
def test(a, b, cmp, cname=None):
if cname is None: cname=cmp.__name__
assert cmp(a, b), f"{cname}:\n{a}\n{b}"
def test_eq(a,b): test(a, b, operator.eq, '==')
def near(a,b): return torch.allclose(a, b, rtol=1e-3, atol=1e-5)
def test_near(a,b): test(a,b,near)
from pathlib import Path
from IPython.core.debugger import set_trace
from fastai import datasets
import pickle, gzip, math, torch, matplotlib as mpl
import matplotlib.pyplot as plt
from torch import tensor
mpl.rcParams['image.cmap'] = 'gray'
2. ดาวน์โหลด Data¶
ในเคสนี้เราจะใช้ MNIST Dataset แบบ Pickle คือข้อมูลแบบ NumPy Array
MNIST_URL='http://deeplearning.net/data/mnist/mnist.pkl'
path = datasets.download_data(MNIST_URL, ext='.gz')
path
with gzip.open(path, 'rb') as f:
((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding='latin-1')
3. ดู Data¶
x_train, y_train, x_valid, y_valid = map(tensor, (x_train, y_train, x_valid, y_valid))
n, c = x_train.shape
x_train.shape, x_train
y_train.shape, y_train
y_train.min(), y_train.max()
ตรวจเช็คความเรียบร้อยของข้อมูล
assert n == y_train.shape[0] == 50000
test_eq(c, 28*28)
test_eq(y_train.min(), 0)
test_eq(y_train.max(), 9)
img = x_train[0]
plt.imshow(img.view(28, 28))
weights = torch.randn(784, 10)
bias = torch.zeros(10)
4. เปรียบเทียบอัลกอริทึม การคูณเมตริกซ์¶
เราจะใช้ข้อมูลจาก MNIST มาใส่ Matrix m1 ไว้ 10 Row
m1 = x_valid[:10]
m2 = weights
m1.shape, m2.shape
4.1 ลูปซ้อนกัน 3 ชั้น¶
def matmul1(a, b):
# a row, a column
ar, ac = a.shape
# b row, b column
br, bc = b.shape
assert ac == br
c = torch.zeros(ar, bc)
for i in range(ar):
for j in range(bc):
for k in range(ac):
c[i, j] += a[i, k] * b[k, j]
return c
%timeit t1 = matmul1(m1, m2)
เทสความถูกต้อง ว่าผลลัพธ์ตรงกันไหม
test_near(m1.matmul(m2), matmul1(m1, m2))
4.2 ใช้ Element-wise¶
ใน Loop ในสุด แทนที่เราวนลูป บวกทีละตัว เราจะใช้คุณสมบัติ Element-wise ของ Tensor มาช่วยประมวลผลขนานกันไปทั้งหมดเลย พร้อมกันทีเดียว
def matmul2(a, b):
ar, ac = a.shape
br, bc = b.shape
assert ac == br
c = torch.zeros(ar, bc)
for i in range(ar):
for j in range(bc):
c[i, j] = (a[i, :] * b[:, j]).sum()
return c
%timeit t2 = matmul2(m1, m2)
เทสความถูกต้อง ว่าผลลัพธ์ตรงกันไหม
test_near(m1.matmul(m2), matmul2(m1, m2))
4.3 ใช้ Broadcasting¶
ใน Loop ต่อมา แทนที่เราวนลูป ทีละ Column เราจะใช้คุณสมบัติ Broadcasting ของ Tensor มาช่วยประมวลผลขนานกันไปทั้งหมดเลย พร้อมกันทีเดียว
def matmul3(a, b):
ar, ac = a.shape
br, bc = b.shape
assert ac == br
c = torch.zeros(ar, bc)
for i in range(ar):
c[i, :] = (a[i, :] * b[:, :].t()).sum(1)
return c
%timeit t3 = matmul3(m1, m2)
เทสความถูกต้อง ว่าผลลัพธ์ตรงกันไหม
test_near(m1.matmul(m2), matmul3(m1, m2))
4.4 ใช้ Einstein Summation¶
อันนี้ไม่เกี่ยวกับ Tensor แต่เป็น ฟังก์ชันพิเศษ ที่ประมวลผล Tensor ในมิติตาม Parameter ที่เรากำหนด สามารถใช้งานได้หลากหลาย เราจะอธิบายต่อไป
def matmul4(a, b):
return torch.einsum('ik,kj->ij', a, b)
%timeit t4 = matmul4(m1, m2)
เทสความถูกต้อง ว่าผลลัพธ์ตรงกันไหม
test_near(m1.matmul(m2), matmul4(m1, m2))
4.5 ใช้ PyTorch API¶
ใช้ฟังก์ชัน tensor.matmul
%timeit t5 = m1.matmul(m2)
ใช้เครื่องหมาย @ ในการ Matrix Multiplication
%timeit t6 = m1 @ m2
5. สรุป¶
ตารางเปรียบเทียบ อัลกอริทึมทั้งหมด เวลาที่ใช้ แล้วเร็วขึ้นกี่เปอร์เซ็น
ลำดับ | อัลกอริทึม | เวลา | ไมโครวินาที | เร็วขึ้น |
---|---|---|---|---|
1 | Loop 3 ชั้น | 3 วินาที | 3,000,000 | 0% |
2 | Loop 2 ชั้น + Element-wise | 5 มิลลิวินาที | 5,000 | 60,000% |
3 | Loop 1 ชั้น + Element-wise + Broadcasting | 900 ไมโครวินาที | 900 | 333,333.3% |
4 | Einstein Summation | 90 ไมโครวินาที | 90 | 3,333,333% |
5 | torch.matmul | 20 ไมโครวินาที | 20 | 15,000,000% |
6 | @ Operator | 10 ไมโครวินาที | 10 | 30,000,000% |
กระบวนการทั้งหมดดำเนินการอยู่บน Tensor เหมือนกัน แต่ต่างกันที่อัลกอริทึม ดังนั้นการออกแบบ เขียนโปรแกรมแบบ Vectorization จึงมีความสำคัญ มีผลต่อประสิทธิภาพการทำงานของระบบอย่างมาก
Credit¶