PyTorch Basics

Tensors (เทนเซอร์)

Tensors เป็นโครงสร้างข้อมูลพื้นฐานและเป็นหัวใจสำคัญของ PyTorch โดยมีลักษณะคล้ายกับอาร์เรย์ (arrays) หรือเมทริกซ์ (matrices) มาก เราใช้เทนเซอร์เพื่อเข้ารหัสอินพุตและเอาต์พุตของโมเดล รวมถึงจัดเก็บพารามิเตอร์ของโมเดลด้วย

import torch
import numpy as np

การสร้าง Tensor (Initializing a Tensor)

เทนเซอร์สามารถสร้างขึ้นได้หลายวิธี ดังนี้:

1. สร้างจากข้อมูลโดยตรง (Directly from data)

เทนเซอร์สามารถสร้างขึ้นจากโครงสร้างข้อมูลแบบ List ของ Python ได้โดยตรง โดยชนิดข้อมูล (Data type) จะถูกประเมินโดยอัตโนมัติ

data = [[1, 2],[3, 4]]
x_data = torch.tensor(data)
print(x_data)
Output:
tensor([[1, 2],
        [3, 4]])
ผลลัพธ์: Matrix (Shape: [2, 2])

2. สร้างจากเทนเซอร์อื่น และด้วยค่าสุ่ม

เทนเซอร์ใหม่จะคงคุณสมบัติ (shape) ของเทนเซอร์เดิมเอาไว้ หรือเราสามารถใช้ shape ที่เป็น tuple กำหนดมิติได้

shape = (2,3,)
rand_tensor = torch.rand(shape)
ones_tensor = torch.ones(shape)
zeros_tensor = torch.zeros(shape)

print(f"Random Tensor: \n {rand_tensor} \n")
print(f"Ones Tensor: \n {ones_tensor} \n")
print(f"Zeros Tensor: \n {zeros_tensor}")
Output:
Random Tensor: 
 tensor([[0.8823, 0.9150, 0.3829],
        [0.9593, 0.3904, 0.6009]]) 

Ones Tensor: 
 tensor([[1., 1., 1.],
        [1., 1., 1.]]) 

Zeros Tensor: 
 tensor([[0., 0., 0.],
        [0., 0., 0.]])
ผลลัพธ์: rand_tensor (Shape: [2, 3])

คุณสมบัติของ Tensor (Attributes of a Tensor)

คุณสมบัติของเทนเซอร์จะบอกรูปร่าง (shape), ชนิดข้อมูล (datatype), และอุปกรณ์ (device) ที่เทนเซอร์นั้นถูกจัดเก็บอยู่

tensor = torch.rand(3,4)

print(f"Shape of tensor: {tensor.shape}")
print(f"Datatype of tensor: {tensor.dtype}")
print(f"Device tensor is stored on: {tensor.device}")
Output:
Shape of tensor: torch.Size([3, 4])
Datatype of tensor: torch.float32
Device tensor is stored on: cpu

การดำเนินการบน Tensor (Operations)

PyTorch มีฟังก์ชันการดำเนินการทางคณิตศาสตร์ พีชคณิตเชิงเส้น ฯลฯ มากกว่า 100 แบบ ซึ่งสามารถคำนวณบน GPU ได้เพื่อความเร็วที่มากขึ้น

การรวมเทนเซอร์ (Joining tensors)

คุณสามารถใช้ torch.cat เพื่อนำเทนเซอร์มาต่อกันตามมิติที่กำหนด (dimension) คล้ายกับการต่อเลโก้

tensor = torch.ones(4, 4)
tensor[:,1] = 0 # กำหนดให้คอลัมน์ที่ 1 (เริ่มนับจาก 0) มีค่าเป็น 0
# นำ tensor ขนาด 4x4 มาต่อกัน 3 อันในแกนที่ 1 (แนวนอน)
t1 = torch.cat([tensor, tensor, tensor], dim=1) 
print(t1)
Output:
tensor([[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
        [1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
        [1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
        [1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.]])
ผลลัพธ์: t1 (Shape: [4, 12]) นำ 4x4 มาต่อกัน 3 ชุด

การดำเนินการทางคณิตศาสตร์ (Arithmetic operations)

ตัวอย่างการคูณแบบสมาชิกต่อสมาชิก (Element-wise product)

# z1, z2, z3 จะได้ผลลัพธ์เหมือนกัน
z1 = tensor * tensor
z2 = tensor.mul(tensor)

print(z1)
Output:
tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]])
ผลลัพธ์: Element-wise (Shape: [4, 4])

การทำ Indexing และ Slicing

คุณสามารถดึงข้อมูลแบบเดียวกับการใช้ NumPy (Standard numpy-like indexing and slicing) ได้เลย

tensor = torch.ones(4, 4)
print(f"First row: {tensor[0]}")
print(f"First column: {tensor[:, 0]}")
print(f"Last column: {tensor[..., -1]}")
tensor[:,1] = 0
print(tensor)
Output:
First row: tensor([1., 1., 1., 1.])
First column: tensor([1., 1., 1., 1.])
Last column: tensor([1., 1., 1., 1.])
tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]])
ผลลัพธ์หลัง Slicing: (Shape: [4, 4])

เทนเซอร์แบบสมาชิกเดียว (Single-element tensors)

หากคุณใช้ฟังก์ชันรวมค่าเทนเซอร์จนเหลือเพียงค่าเดียว (เช่น การหาผลรวม) คุณสามารถแปลงกลับเป็นตัวเลข Python ธรรมดาได้โดยใช้ item()

agg = tensor.sum()
agg_item = agg.item()
print(agg_item, type(agg_item))
Output:
12.0 <class 'float'>
ผลลัพธ์: Scalar Value (Shape: [1])

การดำเนินการแบบ In-place

การดำเนินการใดๆ ที่บันทึกผลลัพธ์ทับตัวแปรเดิม (In-place) จะมีเครื่องหมาย _ ต่อท้าย เช่น x.copy_(y) หรือ x.t_() ฟังก์ชันเหล่านี้จะเปลี่ยนค่า x ทันที

print(f"{tensor} \n")
tensor.add_(5)
print(f"{tensor}")
Output:
tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]]) 

tensor([[6., 5., 6., 6.],
        [6., 5., 6., 6.],
        [6., 5., 6., 6.],
        [6., 5., 6., 6.]])

การเชื่อมต่อกับ NumPy (Bridge with NumPy)

เทนเซอร์ที่อยู่บน CPU และอาร์เรย์ของ NumPy สามารถ แชร์ตำแหน่งหน่วยความจำเดียวกันได้ หมายความว่าการเปลี่ยนค่าในฝั่งใดฝั่งหนึ่งจะส่งผลให้อีกฝั่งเปลี่ยนตามไปด้วย

1. แปลง Tensor เป็น NumPy array

t = torch.ones(5)
n = t.numpy()

# การเปลี่ยนแปลงที่ tensor `t` จะส่งผลไปถึงอาร์เรย์ `n` ด้วย
t.add_(1)
print(f"t: {t}") # tensor([2., 2., 2., 2., 2.])
print(f"n: {n}") # [2. 2. 2. 2. 2.]
Output:
t: tensor([2., 2., 2., 2., 2.])
n: [2. 2. 2. 2. 2.]
ผลลัพธ์: 1D Array Shared Memory (Shape: [5])

2. แปลง NumPy array เป็น Tensor

n = np.ones(5)
t = torch.from_numpy(n)

# การเปลี่ยนแปลงที่ NumPy array `n` จะส่งผลไปถึง tensor `t` ด้วย
np.add(n, 1, out=n)
print(f"t: {t}")
print(f"n: {n}")
Output:
t: tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
n: [2. 2. 2. 2. 2.]