>> ต่อจาก
บทที่ ๑ การสร้างเทนเซอร์ ตัวแปรหลักที่ต้องใช้ในการคำนวณภายใน pytorch ทั้งหมดคือตัวแปรชนิดที่เรียกว่าเทนเซอร์ (Tensor) ซึ่งเทียบเท่าได้กับอาเรย์ภายใน numpy แต่มีความสามารถหลายอย่างเพิ่มเติมเข้ามา
การสร้างเทนเซอร์ทำได้หลายวิธี แต่โดยพื้นฐานที่สุดก็คือใช้คำสั่ง torch.Tensor ตามด้วยลิสต์ของค่าตัวเลขที่ต้องการสร้างเป็นอาเรย์
import torch
print(torch.Tensor([1,2])) # ได้ tensor([1., 2.])
คำสั่ง torch.Tensor นั้นจะสร้างเทนเซอร์ของตัวเลขที่เป็น float32 ซึ่งเป็นชนิดมาตรฐาน
แต่นอกจากนี้เทนเซอร์ใน pytorch มีชนิดตัวแปรอยู่หลายแบบ ถ้าจะสร้างชนิดไหนก็ให้ใส่ชื่อชนิดนำหน้า เช่น DoubleTensor จะเป็น float64 หรือ LongTensor ก็จะเป็น int64
ที่ใช้เป็นหลักคือถ้าเป็นเลขทศนิยมจะใช้ FloatTensor และถ้าเป็นเลขจำนวนเต็มจะใช้ LongTensor
ถ้าเป็น numpy เลขทศนิยมโดยทั่วไปจะถูกใช้เป็น float64 เป็นหลัก ซึ่งกินหน่วยความจำมาก แต่ในการคำนวณภายในโครงข่ายประสาทเทียมเราไม่จำเป็นต้องใช้เลขที่มีความแม่นยำสูงขนาดนั้น pytorch จึงใช้ float32 เป็นหลัก
การดูว่าเป็นเทนเซอร์ชนิดไหนให้ดูที่แอตทริบิวต์ .dtype ได้ในลักษณะเดียวกันกับอาเรย์ของ numpy
ตัวอย่างเทนเซอร์ชนิดต่างๆ
print(torch.Tensor([1]).dtype) # ได้ torch.float32
print(torch.FloatTensor([1]).dtype) # ได้ torch.float32
print(torch.DoubleTensor([1]).dtype) # ได้ torch.float64
print(torch.IntTensor([1]).dtype) # ได้ torch.int32
print(torch.LongTensor([1]).dtype) # ได้ torch.int64
print(torch.ByteTensor([1]).dtype) # ได้ torch.uint8
print(torch.BoolTensor([1]).dtype) # ได้ torch.bool
FloatTensor หรือ Tensor ก็คือ float32 เหมือนกัน
สรุปชนิดของเทนเซอร์
ชนิดข้อมูล |
dtype |
ชื่อเทนเซอร์ |
32-bit float |
torch.float32 หรือ torch.float |
torch.FloatTensor |
64-bit float |
torch.float64 หรือ torch.double |
torch.DoubleTensor |
16-bit float |
torch.float16 หรือ torch.half |
torch.HalfTensor |
8-bit int (unsigned) |
torch.uint8 |
torch.ByteTensor |
8-bit int (signed) |
torch.int8 |
torch.CharTensor |
16-bit int (signed) |
torch.int16 หรือ torch.short |
torch.ShortTensor |
32-bit int (signed) |
torch.int32 หรือ torch.int |
torch.IntTensor |
64-bit int (signed) |
torch.int64 หรือ torch.long |
torch.LongTensor |
Boolean |
torch.bool |
torch.BoolTensor |
ถ้าจะเปลี่ยนชนิดของตัวแปรในเทนเซอร์ก็ทำได้โดยใช้คำสั่ง .type() ซึ่งจะคล้ายกับ .astype ใน numpy เพียงแต่ถ้าไม่ใส่ชนิดที่ต้องการแปลงก็จะกลายเป็นให้คืนค่าชนิดของตัวแปรแทน
print(torch.LongTensor([1]).type(torch.float64)) # ได้ tensor([1.], dtype=torch.float64)
print(torch.IntTensor([1]).type()) # ได้ 'torch.IntTensor'
หรือง่ายกว่านั้นคือใช้เมธอดที่ชื่อเป็นตัวแปรนั้นได้เลย
t = torch.tensor([1.1])
print(t.int()) # ได้ tensor([1], dtype=torch.int32)
print(t.long()) # ได้ tensor([1])
print(t.double()) # ได้ tensor([1.1000], dtype=torch.float64)
คำสั่งสร้างเลข 0 หรือ 1 ล้วนแบบ numpy ก็ใช้สร้างได้
print(torch.ones(4)) # ได้ tensor([1., 1., 1., 1.])
print(torch.zeros([1,3])) # ได้ tensor([[0., 0., 0.]])
t = torch.Tensor([1,1])
print(torch.zeros_like(t)) # ได้ tensor([0., 0.])
print(torch.ones_like(t)) # ได้ tensor([1., 1.])
และถ้าเวลาที่ใช้คำสั่งสร้างเทนเซอร์ไม่ได้ใส่ข้อมูลเป็นลิสต์แต่กลับใส่เลขจำนวนเต็มจะได้เป็นเทนเซอร์เปล่าขนาดเท่านั้นแทน
print(torch.Tensor(5)) # ได้ tensor([8.4490e-39, 1.0010e-38, 9.6429e-39, 8.4490e-39, 9.5510e-39])
เทนเซอร์มีเมธอด .fill_ ซึ่งเอาไว้เติมค่าทุกตัวในเทนเซอร์นั้นด้วยค่าที่กำหนด สามารถใช้วิธีนี้สร้างอาเรย์ที่มีค่าตามที่ต้องการได้
print(torch.Tensor(4).fill_(7)) # ได้ tensor([7., 7., 7., 7.])
หรือจะใช้ .full() ก็ได้
print(torch.full([1,4],6)) # ได้ tensor([[6., 6., 6., 6.]])
คำสั่งสร้างค่าเรียงตามลำดับมีเหมือนกับ numpy
print(torch.arange(1,8,2)) # ได้ tensor([1, 3, 5, 7])
print(torch.linspace(0,1,5)) # ได้ tensor([0.0000, 0.2500, 0.5000, 0.7500, 1.0000])
print(torch.logspace(0,1,5)) # ได้ tensor([ 1.0000, 1.7783, 3.1623, 5.6234, 10.0000])
การสร้างค่าเลขสุ่ม pytorch มีคำสั่งที่ใช้สร้างค่าแบบสุ่มคล้ายกับ numpy
print(torch.rand(4)) # ได้ tensor([[0.6853, 0.4649, 0.4405, 0.5287]])
print(torch.randn(1,5)) # ได้ tensor([[-2.2284, -0.1523, -0.4851, -1.9513, 1.0127]])
print(torch.randint(1,4,[9])) # ได้ tensor([1., 2., 3., 2., 2., 3., 3., 2., 3.])
print(torch.randperm(9)) # ได้ tensor([8, 7, 2, 0, 1, 3, 6, 5, 4])
กรณีที่ต้องการให้ผลเหมือนเดิมตลอดให้ใช้ torch.manual_seed() คำสั่งนี้คล้ายกับ np.random.seed() ใน numpy แต่ว่าต้องใช้แยกกัน การ seed ใน numpy จะไม่มีผลกับการสุ่มใน pytorch
for i in range(2):
for s in range(2):
torch.manual_seed(s)
print(torch.randn(3+i))
ได้
tensor([ 1.5410, -0.2934, -2.1788])
tensor([0.6614, 0.2669, 0.0617])
tensor([ 1.5410, -0.2934, -2.1788, 0.5684])
tensor([0.6614, 0.2669, 0.0617, 0.6213])
การแปลงไปมาระหว่างเทนเซอร์และอาเรย์ของ numpy ถ้ามีอาเรย์ของ numpy อยู่แล้วต้องการสร้างเทนเซอร์ที่เป็นข้อมูลชนิดเดียวกันให้ใช้คำสั่ง .from_numpy() เพียงแต่ว่าถ้าใช้คำสั่งนี้เทนเซอร์ที่ได้จะใช้ข้อมูลร่วมกับอาเรย์ หากค่าถูกเปลี่ยนแปลงก็จะถูกเปลี่ยนไปด้วย
a = np.array([1.])
t = torch.from_numpy(a)
print(t) # ได้ tensor([1.], dtype=torch.float64)
print(a) # ได้ [ 1.]
t += 1
print(t) # ได้ tensor([2.], dtype=torch.float64)
print(a) # ได้ [ 2.]
หากต้องการให้คัดลอกข้อมูล ไม่ต้องการให้เปลี่ยนค่าไปด้วยให้ใช้พวก torch.Tensor, torch.IntTensor เพียงแต่ชนิดข้อมูลจะไม่ได้เป็นไปตามอาเรย์นั้น ต้องเลือกชนิดข้อมูลให้ตรงกับที่ต้องการด้วย
หากจะเปลี่ยนจากเทนเซอร์เป็นอาเรย์ก็อาจใช้คำสั่ง .numpy() ที่ตัวเทนเซอร์นั้น อาเรย์ที่จะได้ก็จะยังใช้ข้อมูลเดียวกับเทนเซอร์นั้นอยู่ ถ้าเปลี่ยนค่าก็จะเปลี่ยนไปด้วยกัน
t = torch.Tensor([2])
b = t.numpy()
print(b) # ได้ [ 2.]
b += 1
print(b) # ได้ [ 3.]
print(t) # ได้ tensor([3.], dtype=torch.float64)
หากไม่ต้องการให้เปลี่ยนค่าไปด้วย ให้ใช้ np.array()
t = torch.Tensor([1,3])
a = np.array(t.data)
a *= 2
print(a) # ได้ [ 2. 6.]
print(w) # ได้ tensor([1., 3.])
การคำนวณของเทนเซอร์ เทนเซอร์สามารถทำการคำนวณได้เช่นเดียวกับอาเรย์ สามารถบวกลบคูณหารกับเทนเซอร์ด้วยกันเองหรือกับเลขตัวเดียวก็ได้ หรือจะทำอะไรกับอาเรย์ก็ได้ และเทนเซอร์คนละชนิดกันก็ทำอะไรกันได้ โดยจะมีการแปลงชนิดให้อัตโนมัติ
print(torch.LongTensor([1,2])+1) # ได้ tensor([2, 3])
print(torch.Tensor([3,5])*torch.Tensor([4,2])) # ได้ tensor([12., 10.])
print(torch.Tensor([2,2])-np.array([1,2])) # tensor([1., 0.], dtype=torch.float64)
print(torch.LongTensor([1,5])*torch.IntTensor([4,7])) # tensor([ 4, 35])
เวลานำเทนเซอร์มาเข้าตัวดำเนินการเปรียบเทียบจะได้ตัวแปรชนิด bool ซึ่งจะแสดงเป็น True หรือ False
print(torch.Tensor([1,2,3])>=2) # tensor([False, True, True])
คำสั่งอย่างคูณเมทริกซ์ก็มี แต่ว่าไม่ใช่ .dot() เหมือนอย่างใน numpy แต่คำสั่งชื่อ .matmul()
t1 = torch.Tensor([1,2])
t2 = torch.Tensor([[1,2],[3,4]])
print(torch.matmul(t1,t2)) # ได้ tensor([ 7., 10.])
# หรือ t1.matmul(t2)
print(torch.matmul(t2,t1)) # ได้ tensor([ 5., 11.])
พวกคำสั่งหาค่าเฉลี่ย, ผลรวม, ส่วนเบี่ยงเบนมาตรฐาน, ฯลฯ ก็ใช้ได้เหมือนกัน
t = torch.Tensor([2,3,5])
print(t.mean()) # ได้ tensor(3.3333)
# หรือ torch.mean(t)
print(t.sum()) # ได้ tensor(10.)
print(t.cumsum(0)) # ได้ tensor([ 2., 5., 10.])
print(t.std()) # ได้ tensor(1.5275)
print(t.var()) # ได้ tensor(2.3333)
print(t.max()) # ได้ tensor(5.)
print(t.argmax()) # ได้ tensor(2)
print(t.prod()) # ได้ tensor(30.)
max(),min() ถ้าระบุแกนไปด้วยจะไม่แค่คืนค่าสูงสุดแต่ยังคืนตำแหน่งที่สูงสุดมาด้วย
print(torch.Tensor([1,7,3,2]).max(0))
ได้
torch.return_types.max(
values=tensor(7.),
indices=tensor(1))
print(torch.Tensor([[6,2],
[1,8]]).min(1))
ได้
torch.return_types.min(
values=tensor([2., 1.]),
indices=tensor([1, 0]))
พวกฟังก์ชันคำนวณต่างๆก็ใช้ได้เช่นกัน แถมยังใช้ในรูปเมธอดได้เลยในขณะที่ numpy ทำแบบนี้ไม่ได้
t = torch.Tensor([-3,4])
print(t.sqrt()) # ได้ tensor([ nan, 2.0000])
print(t.abs()) # ได้ tensor([3., 4.])
print(t.log()) # ได้ tensor([ nan, 1.3863])
print(t.log10()) # ได้ tensor([ nan, 0.6021])
print(t.exp()) # ได้ tensor([ 0.0498, 54.5982])
print(t.sin()) # ได้ tensor([-0.1411, -0.7568])
print(t.cos()) # ได้ tensor([-0.9900, -0.6536])
print(t.tan()) # ได้ tensor([0.1425, 1.1578])
print(t.atan()) # ได้ tensor([-1.2490, 1.3258])
print(t.tanh()) # ได้ tensor([-0.9951, 0.9993])
เพียงแต่ว่าคำสั่งเหล่านี้ส่วนใหญ่มีแค่ใน FloatTensor เท่านั้น ถ้าใช้กับ IntTensor จะ error ดังนั้นต้องแปลงชนิดก่อน
torch.LongTensor([2,5]).mean() # RuntimeError
torch.IntTensor([2,5]).std() # RuntimeError
คำสั่งที่ใช้ใน IntTensor ได้มีเช่น sum(), cumsum(), prod(), max()
แล้วคำสั่งพวกนี้ถ้าหากเติม _ ไปด้านหลังจะกลายเป็นการแทนที่ค่าลงไปในอาเรย์นั้นเลย
t = torch.Tensor([4,9])
t.sqrt_()
print(t) # ได้ tensor([2., 3.])
การเปลี่ยนรูปร่างเทนเซอร์ รูปร่างของเทนเซอร์สามารถหาได้โดยดูที่แอตทริบิวต์ .shape หรือใช้เมธอด .size()
t = torch.Tensor([[-1,-2,3],[2,1,-4]])
print(t.shape) # ได้ torch.Size([2, 3])
print(t.size()) # ได้ torch.Size([2, 3])
เวลาจะเปลี่ยนรูปร่างของเทนเซอร์จะใช้เมธอด .view() หรือ .reshape() ก็ได้
print(t.view(1,6)) # ได้ tensor([[-1., -2., 3., 2., 1., -4.]])
print(t.reshape(1,6)) # ได้ tensor([[-1., -2., 3., 2., 1., -4.]])
ถ้าจะยุบค่าทั้งหมดให้เหลืออยู่ในมิติเดียวอาจใช้ .flatten() หรือ .view(-1)
print(t.flatten()) # ได้ tensor([-1., -2., 3., 2., 1., -4.])
print(t.view(-1)) # ได้ tensor([-1., -2., 3., 2., 1., -4.])
ส่วนคำสั่ง .squeeze() จะกำจัดมิติที่มีค่าเดียวทิ้ง
t = torch.zeros([2,1,4])
print(t.squeeze().shape) # ได้ torch.Size([2, 4])
ส่วนเรื่องการสับเปลี่ยนมิติ เทนเซอร์ไม่มีการใช้ .T แบบในอาเรย์ แต่มีเมธอด .transpose() เพียงแต่จะต่างจาก .transpose() ของ numpy ตรงที่สลับได้ทีละ ๒ แกน
t = torch.zeros([2,3,4])
print(t.transpose(1,2).shape) # ได้ torch.Size([2, 4, 3])
การเพิ่มมิติโดยใช้ None ทำได้เช่นเดียวกัน
t = torch.zeros(3)
print(t[:,None].shape) # ได้ torch.Size([3, 1])
t = torch.zeros([2,3,4])
print(t[None,None,None,:,None,None].shape) # ได้ torch.Size([1, 1, 1, 2, 1, 1, 3, 4])
print(t[...,None,None].shape) # ได้ torch.Size([2, 3, 4, 1, 1])
การเชื่อมรวมเทนเซอร์ คำสั่งสำหรับนำเทนเซอร์มาต่อกันในมิติใหม่คือ stack() เหมือนใน numpy ส่วนคำสั่งสำหรับต่อกันในมิติเดิมคือ cat()
t1 = torch.Tensor([1,2,3])
t2 = torch.Tensor([4,0,5])
print(torch.stack([t1,t2]))
print(torch.stack([t1,t2],1))
print(torch.cat([t1,t2]))
ได้
tensor([[1., 2., 3.],
[4., 0., 5.]])
tensor([[1., 4.],
[2., 0.],
[3., 5.]])
tensor([1., 2., 3., 4., 0., 5.])
t1 = torch.Tensor([[1,2],
[7,3]])
t2 = torch.Tensor([[4,0],
[5,6]])
print(torch.stack([t1,t2]))
print(torch.stack([t1,t2],1))
print(torch.stack([t1,t2],2))
print(torch.cat([t1,t2]))
print(torch.cat([t1,t2],1))
ได้
tensor([[[1., 2.],
[7., 3.]],
[[4., 0.],
[5., 6.]]])
tensor([[[1., 2.],
[4., 0.]],
[[7., 3.],
[5., 6.]]])
tensor([[[1., 4.],
[2., 0.]],
[[7., 5.],
[3., 6.]]])
tensor([[1., 2.],
[7., 3.],
[4., 0.],
[5., 6.]])
tensor([[1., 2., 4., 0.],
[7., 3., 5., 6.]])
ใช้กับ matplotlib เทนเซอร์สามารถใช้กับบางคำสั่งของ matplotlib ได้โดยตรง เช่น scatter กับ imshow แต่บางคำสั่งเช่น plot จะใช้ไม่ได้ ต้องแปลงเป็นอาเรย์ก่อน
ลองนำเทนเซอร์มาใช้ใน imshow กับ scatter
import torch
import matplotlib.pyplot as plt
plt.imshow(torch.randn([20,20])+torch.linspace(0,5,400).view(20,20),cmap='coolwarm')
x,y,z = torch.rand([3,150])
plt.scatter(x*19,y*19,200,c=z,marker='*',cmap='rainbow',edgecolor='k')
plt.show()
จากตัวอย่างที่ยกมาจะเห็นได้ว่าเทนเซอร์ถูกใช้ในลักษณะที่คล้ายกับอาเรย์มาก
แต่ว่าคุณสมบัติที่พิเศษกว่าอาเรย์ ซึ่งจะทำให้เราจำเป็นต้องใช้มันจริงๆก็คือเรื่องความสามารถในการคำนวณอนุพันธ์ ซึ่งจะพูดถึงในบทต่อไป
>> อ่านต่อ
บทที่ ๓