φυβλαςのβλογ
บล็อกของ 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)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文