φυβλαςのβλογ
บล็อกของ phyblas



pytorch เบื้องต้นบทที่ ๒: เทนเซอร์
เขียนเมื่อ 2018/09/08 09:53
แก้ไขล่าสุด 2022/07/09 15:00
>> ต่อจาก บทที่ ๑



การสร้างเทนเซอร์

ตัวแปรหลักที่ต้องใช้ในการคำนวณภายใน 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()




จากตัวอย่างที่ยกมาจะเห็นได้ว่าเทนเซอร์ถูกใช้ในลักษณะที่คล้ายกับอาเรย์มาก

แต่ว่าคุณสมบัติที่พิเศษกว่าอาเรย์ ซึ่งจะทำให้เราจำเป็นต้องใช้มันจริงๆก็คือเรื่องความสามารถในการคำนวณอนุพันธ์ ซึ่งจะพูดถึงในบทต่อไป


>> อ่านต่อ บทที่ ๓


-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> ปัญญาประดิษฐ์ >> โครงข่ายประสาทเทียม
-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python >> pytorch

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

ไทย

日本語

中文