일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- machine learning
- MinHeap
- classification
- 딥러닝
- 강화학습
- 머신러닝
- canny edge detection
- sklearn
- Python
- image processing
- DP
- dynamic programming
- Mask Processing
- SIFT
- clustering
- AlexNet
- dfs
- 그래프 이론
- C++
- exists
- 백준
- opencv
- IN
- edge detection
- TD
- MySQL
- Reinforcement Learning
- 자료구조
- BFS
- 인공지능
- Today
- Total
JINWOOJUNG
[EECS 498] Assignment 1. PyTorch 101...(1) 본문
본 포스팅은 Michigan Univ.의 EECS 498 강의를 수강하면서 공부한 내용을 정리하는 포스팅입니다.
0. 개발 환경
OS : Ubuntu 20.04
GPU : GeForce RTX 3070
cuda version: 12.1
torch version : 2.3.0+cu121
1. Tensor Basics
def create_sample_tensor() -> Tensor:
x = torch.tensor([[0, 10],[100, 0],[0,0]])
return x
Tensor 객체는 torch.tensor를 통해 생성할 수 있다.
x = mytorch.create_sample_tensor()
print('Here is the sample tensor:')
print(x)
print(type(x))
print(x.dim())
print(x.shape)
# Here is the sample tensor:
# tensor([[ 0, 10],
# [100, 0],
# [ 0, 0]])
# <class 'torch.Tensor'>
# 2
# torch.Size([3, 2])
Tensor 객체는 tensor(Data) 형태로 표현되며, Data type은 torch.Tensor Class를 가진다.
Tensor 객체는 Rank, Shape 개념을 가진다.
- Rank
- 차원의 수
- Tensor.dim()을 통해 획득 가능
- Shape
- 크기
- Tensor.shape 를 통해 획득 가능
create_sample_tensor()를 통해 생성한 Tensor 객체는 2 Dimension을 가지며, 크기는 3x2 임을 확인할 수 있다.
def mutate_tensor(
x: Tensor, indices: List[Tuple[int, int]], values: List[float]
) -> Tensor:
# enumerate를 통해 index를 동시에 접근해서 변경할 Value를 가져옴
for idx, (i, j) in enumerate(indices):
x[i,j] = values[idx]
return x
Tensor 객체의 값을 변경하기 위해서는, 배열에 접근하는 것과 같이 Tensor 객체에 대하여 Index로 접근할 수 있다. mutate_tensor는 변경할 Tensor 객체의 원소의 Index가 담긴 indices List와 변경할 값을 가지는 values List를 받아와 해당 Tensor 객체의 값을 변경하는 함수이다.
# Mutate the tensor by setting a few elements
indices = [(0, 0), (1, 0), (1, 1)]
values = [4, 5, 6]
mytorch.mutate_tensor(x, indices, values)
print('\nAfter mutating:')
print(x)
# After mutating:
# tensor([[ 4, 10],
# [ 5, 6],
# [ 0, 0]])
Tensor 객체 x의 0,0 즉 1열 1행의 원소가 0에서 4로 바뀜을 확인할 수 있다.
def count_tensor_elements(x: Tensor) -> int:
num_elements = 1
# 각 차원의 크기를 누적 곱
for dim in x.shape:
num_elements *= dim
return num_elements
Tensor 객체의 원소 수를 계산하는 함수이다. 이는 Tensor.shape를 통해 각 차원의 크기를 구한 뒤, 누적 곱으로 쉽게 계산할 수 있다.
num = mytorch.count_tensor_elements(x)
print('\nNumber of elements in x: ', num)
# Number of elements in x: 6
Tensor 객체 x는 3x2의 크기를 가지므로, 원소의 총 개수는 6개가 된다.
2. Tensor constructors
PyTorch는 Tensor를 생성하는 몇가지 편리한 Method를 제공한다.
torch.zeros(*size, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor
a = torch.zeros(2, 3)
print('tensor of zeros:')
print(a)
# tensor of zeros:
# tensor([[0., 0., 0.],
# [0., 0., 0.]])
torch.zeros는 입력받은 shape를 가지고, 모든 원소가 0인 Tensor 객체를 생성한다.
torch.ones(*size, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor
b = torch.ones(1, 2)
print('\ntensor of ones:')
print(b)
# tensor of ones:
# tensor([[1., 1.]])
torch.ones는 입력받은 shape를 가지고, 모든 원소가 1인 Tensor 객체를 생성한다.
하나 주의할 점은, 위와 같이 생성된 Tensor 객체 b는 1차원이 아닌 2차원임을 주의하자.
torch.eye(n, m=None, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor
c = torch.eye(3)
print('\nidentity matrix:')
print(c)
# identity matrix:
# tensor([[1., 0., 0.],
# [0., 1., 0.],
# [0., 0., 1.]])
torch.eye는 nxn shape를 가지고, 주대각선상 원소가 1이고, 나머진 0인 Identity Matrix 형태의 Tensor 객체를 생성한다.
torch.rand(*size, *, generator=None, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False, pin_memory=False) → Tensor
d = torch.rand(4, 5)
print('\nrandom tensor:')
print(d)
# random tensor:
# tensor([[0.7247, 0.9109, 0.9399, 0.8177, 0.9258],
# [0.7217, 0.2551, 0.1919, 0.9802, 0.0872],
# [0.1974, 0.7558, 0.2224, 0.4291, 0.6515],
# [0.6502, 0.5123, 0.4780, 0.1624, 0.2765]])
torch.rand는 [0,1) 범위의 Random Value를 원소로 가지는 입력받은 shape의 Tensor 객체를 생성한다.
torch.full(size, fill_value, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor
def create_tensor_of_pi(M: int, N: int) -> Tensor:
# 입력받은 Value로 모두 채워진 MxN shape를 가지는 Tensor 객체 반환
x = torch.full((M,N), 3.14)
return x
x = mytorch.create_tensor_of_pi(4, 5)
# tensor([[3.1400, 3.1400, 3.1400, 3.1400, 3.1400],
# [3.1400, 3.1400, 3.1400, 3.1400, 3.1400],
# [3.1400, 3.1400, 3.1400, 3.1400, 3.1400],
# [3.1400, 3.1400, 3.1400, 3.1400, 3.1400]])
torch.full은 모든 원소를 fill_value로 가지고, 입력받은 shape의 Tensor 객체를 생성한다.
create_tensor_of_pi는 Tensor 객체의 Shape(M,N)을 입력받아서, 모든 원소를 3.14로 가지는 Tensor 객체를 반환하는 함수이다.
3. Datatypes
x0 = torch.tensor([1, 2]) # List of integers
x1 = torch.tensor([1., 2.]) # List of floats
x2 = torch.tensor([1., 2]) # Mixed list
print('dtype when torch chooses for us:')
print('List of integers:', x0.dtype)
print('List of floats:', x1.dtype)
print('Mixed list:', x2.dtype)
# dtype when torch chooses for us:
# List of integers: torch.int64
# List of floats: torch.float32
# Mixed list: torch.float32
Torch 객체의 Datatype은 torch.dtype으로 확인할 수 있다. 입력한 원소의 Datatype에 따라 Tensor 객체의 Datatype이 결정된다.
y0 = torch.tensor([1, 2], dtype=torch.float32) # 32-bit float
y1 = torch.tensor([1, 2], dtype=torch.int32) # 32-bit (signed) integer
y2 = torch.tensor([1, 2], dtype=torch.int64) # 64-bit (signed) integer
print('\ndtype when we force a datatype:')
print('32-bit float: ', y0.dtype)
print('32-bit integer: ', y1.dtype)
print('64-bit integer: ', y2.dtype)
# dtype when we force a datatype:
# 32-bit float: torch.float32
# 32-bit integer: torch.int32
# 64-bit integer: torch.int64
혹은, 생성 시 dtype으로 Datatype을 특정지을 수 있다.
x0 = torch.eye(3, dtype=torch.int64)
x1 = x0.float() # Cast to 32-bit float
x2 = x0.double() # Cast to 64-bit float
x3 = x0.to(torch.float32) # Alternate way to cast to 32-bit float
x4 = x0.to(torch.float64) # Alternate way to cast to 64-bit float
print('x0:', x0.dtype)
print('x1:', x1.dtype)
print('x2:', x2.dtype)
print('x3:', x3.dtype)
print('x4:', x4.dtype)
# x0: torch.int64
# x1: torch.float32
# x2: torch.float64
# x3: torch.float32
# x4: torch.float64
만약 기존에 존재하는 Tensor 객체의 Datatype을 변경하기 위해서는 torch.datatype 혹은 .datatype()으로 변경할 수 있다.
torch.zeros_like(input, *, dtype=None, layout=None, device=None, requires_grad=False, memory_format=torch.preserve_format) → Tensor
x0 = torch.eye(3, dtype=torch.float64) # Shape (3, 3), dtype torch.float64
x1 = torch.zeros_like(x0) # Shape (3, 3), dtype torch.float64
# x0 shape is torch.Size([3, 3]), dtype is torch.float64
# x1 shape is torch.Size([3, 3]), dtype is torch.float64
특정 Tensor 객체와 동일한 shape를 가지지만, 모든 원소가 0인 Tensor 객체를 생성하려면 torch.zeros_like를 사용한다.
Tensor.new_zeros(size, *, dtype=None, device=None, requires_grad=False, layout=torch.strided, pin_memory=False) → Tensor
x0 = torch.eye(3, dtype=torch.float64) # Shape (3, 3), dtype torch.float64
x2 = x0.new_zeros(4, 5) # Shape (4, 5), dtype torch.float64
# x0 shape is torch.Size([3, 3]), dtype is torch.float64
# x2 shape is torch.Size([4, 5]), dtype is torch.float64
tensor.new_zeros를 통해 입력한 shape를 가지고 모든 원소가 0인 Tensor 객체를 생성할 수 있다.
기존에 특정 shape를 가지고 모든 원소가 0인 Tensor 객체를 생성하는 Method인 torch.zeros를 배웠다. 그러면 이와 차이는 무엇일까?
x0 = torch.eye(3, dtype=torch.float64)
x1 = x0.cuda()
x2 = torch.zeros(4,5)
x3 = x1.new_zeros(4,5)
print("x0's device = ", x0.device)
print("x1's device = ", x1.device)
print("x2's device = ", x2.device)
print("x3's device = ", x3.device)
# x0's device = cpu
# x1's device = cuda:0
# x2's device = cpu
# x3's device = cuda:0
torch.new_zeros를 사용하면, 기존 torch 객체와 동일한 device를 갖게된다. 추후 병렬처리를 통한 빠른 연산을 위해 GPU로 옮겨서 동작시키게 될 것인데, 이때 유용하게 활용할 수 있다.
torch.arange(start=0, end, step=1, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor
def multiples_of_ten(start: int, stop: int) -> Tensor:
assert start <= stop
x = None
# start와 가장 가까운 10의 배수 계산
start_value = (start + 9) // 10 * 10
if start_value > stop:
x = torch.zeros((0,), dtype = torch.float64)
else:
x = torch.arange(start_value, stop + 1, 10, dtype = torch.float64)
return x
start = 5
stop = 25
x = mytorch.multiples_of_ten(start, stop)
print('Correct dtype: ', x.dtype == torch.float64)
print('Correct shape: ', x.shape == (2,))
print('Correct values: ', x.tolist() == [10, 20])
# Correct dtype: True
# Correct shape: True
# Correct values: True
mulstiples_of_tem은 입력받은 start~end 범위의 숫자 중 10의 배수인 값만 원소로 가지는 Tensor 객체를 생성하는 함수이다. 만약 없으면 shape가 (0,)인 빈 객체를 반환한다.
먼저 start와 가장 가까운 10의 배수를 계산한다. start에 9를 더하고 10으로 나눈 몫에 10을 곱하면 가장 가까운 10의 배수를 구할 수 있다. 만약 start_value가 stop보다 크면, start~end 범위에는 10의 배수가 없으므로 shape가 (0,)인 빈 객체를 반환한다. 아니면, torch.arrange를 이용해서 start_value부터 stop+1까지 10씩 증가하는 값들을 원소로 하는 Tensor 객체를 생성하여 반환한다.
4. Slice Indexing
Tensor 객체는 Index를 기반으로 Slicing 할 수 있다. 방법은 크게 가지로, start:stop / start:stop:step 이 있다. 이때, stop은 항상 포함되지 않음을 주의하자.
a = torch.tensor([0, 11, 22, 33, 44, 55, 66])
print(0, a) # torch([0, 11, 22, 33, 44, 55, 66])
print(1, a[2:5]) # torch([22, 33, 44])
print(2, a[2:]) # torch([22, 33, 44, 55, 66])
print(3, a[:5]) # torch([0, 11, 22, 33, 44])
print(4, a[:]) # torch([0, 11, 22, 33, 44, 55, 66])
print(5, a[1:5:2]) # torch([11, 33])
print(6, a[:-1]) # torch([0, 11, 22, 33, 44, 55])
print(7, a[-4::2]) # torch([33, 55])
start 혹은 stop이 비어있는 경우 끝까지라고 생각하면 된다. 또한 -Index의 경우 마지막 원소가 -1이다. step이 존재하면, step 단위로 증가한다. 주의할 점은 stop은 항상 포함되지 않는다.
a = torch.tensor([[1,2,3,4], [5,6,7,8], [9,10,11,12]])
print(a)
print('shape: ', a.shape) # shape: torch.Size([3, 4])
print(a[1, :]) # tensor([5, 6, 7, 8])
print(a[1]) # tensor([5, 6, 7, 8])
print('shape: ', a[1].shape) # shape: torch.Size([4])
print(a[:, 1]) # tensor([ 2, 6, 10])
print('shape: ', a[:, 1].shape) # shape: torch.Size([3])
print(a[:2, -3:])
print('shape: ', a[:2, -3:].shape)
# tensor([[2, 3, 4],
# [6, 7, 8]])
# shape: torch.Size([2, 3])
print(a[::2, 1:3])
print('shape: ', a[::2, 1:3].shape)
# tensor([[ 2, 3],
# [10, 11]])
# shape: torch.Size([2, 2])
다차원 Tesnor 객체에 대한 Index Slicing도 동일하다. 각각의 차원에 대해 Slice 할 수 있으며, " : "의 경우 즉, 전체에 대해서는 생략해도 동일한 결과가 나온다.
a[::2, 1:3]을 살펴보자면, 처음 행부터 2씩 증가시키고, [1,3)의 열만 가져오므로 2x2의 shape를 가짐을 예측할 수 있다.
a = torch.tensor([[1,2,3,4], [5,6,7,8], [9,10,11,12]])
print('Original tensor')
print(a)
row_r1 = a[1, :] # Rank 1 view of the second row of a
row_r2 = a[1:2, :] # Rank 2 view of the second row of a
print('\nTwo ways of accessing a single row:')
print(row_r1, row_r1.shape)
print(row_r2, row_r2.shape)
# We can make the same distinction when accessing columns:
col_r1 = a[:, 1]
col_r2 = a[:, 1:2]
print('\nTwo ways of accessing a single column:')
print(col_r1, col_r1.shape)
print(col_r2, col_r2.shape)
# tensor([[ 1, 2, 3, 4],
# [ 5, 6, 7, 8],
# [ 9, 10, 11, 12]])
# Two ways of accessing a single row:
# tensor([5, 6, 7, 8]) torch.Size([4])
# tensor([[5, 6, 7, 8]]) torch.Size([1, 4])
# Two ways of accessing a single column:
# tensor([ 2, 6, 10]) torch.Size([3])
# tensor([[ 2],
# [ 6],
# [10]]) torch.Size([3, 1])
조금 주의깊게 봐야하는 것은, Slicing 방법에 따라서 반환되는 객체의 Shape가 달라진다는 점이다. row_r1과 같이 Slicing 하면, Rank가 1인 Tensor가 반환된다. 하지만, row_r2와 같아 생성하게 되는 경우 Rank가 2가된다. 따라서 각 상황에 맞춰 적절히 연산을 수행해야 한다.
torch.clone(input, *, memory_format=torch.preserve_format) → Tensor
a = torch.tensor([[1, 2, 3, 4], [5, 6, 7, 8]])
b = a[0, 1:]
c = a[0, 1:].clone()
print('Before mutating:')
print(a)
print(b)
print(c)
# Before mutating:
# tensor([[1, 2, 3, 4],
# [5, 6, 7, 8]])
# tensor([2, 3, 4])
# tensor([2, 3, 4])
a[0, 1] = 20
b[1] = 30
c[2] = 40
print('\nAfter mutating:')
print(a)
print(b)
print(c)
# tensor([[ 1, 20, 30, 4],
# [ 5, 6, 7, 8]])
# tensor([20, 30, 4])
# tensor([ 2, 3, 40])
Slicing을 통해 반환되는 객체는 동일한 Tensor 객체를 가르키고 있다. 따라서 값을 변경하게 되면, Slicing 이전의 Tensor 객체에도 영향을 준다.
이를 방지하기 위해서는, Slicing 된 Tensor 객체에 대하여 torch.clone을 통해 복사본을 생성하면 영향을 주지 않는다.
def slice_indexing_practice(x: Tensor) -> Tuple[Tensor, Tensor, Tensor, Tensor]:
"""
Given a two-dimensional tensor x, extract and return several subtensors to
practice with slice indexing. Each tensor should be created using a single
slice indexing operation.
The input tensor should not be modified.
Args:
x: Tensor of shape (M, N) -- M rows, N columns with M >= 3 and N >= 5.
Returns:
A tuple of:
- last_row: Tensor of shape (N,) giving the last row of x. It should be
a one-dimensional tensor.
- third_col: Tensor of shape (M, 1) giving the third column of x. It
should be a two-dimensional tensor.
- first_two_rows_three_cols: Tensor of shape (2, 3) giving the data in
the first two rows and first three columns of x.
- even_rows_odd_cols: Two-dimensional tensor containing the elements in
the even-valued rows and odd-valued columns of x.
"""
assert x.shape[0] >= 3
assert x.shape[1] >= 5
last_row = x[-1,:]
third_col = x[:,2:3]
first_two_rows_three_cols = x[:2,:3]
even_rows_odd_cols = x[::2,1::2]
out = (
last_row,
third_col,
first_two_rows_three_cols,
even_rows_odd_cols,
)
return out
slice_indexing_practice는 각각의 조건에 맞는 Index Slicing 된 Tensor 객체를 반환하는 함수이다.
third_col의 경우 tow-dimensional tensor를 반환해야 하기에 x[:2]가 아닌 x[:,2:3]으로 구현하였다.
even_rows_odd_cols는 step을 2로 설정하고 시작 Index를 잘 설정 해 주면 쉽게 구현할 수 있다.
a = torch.zeros(2, 4, dtype=torch.int64)
a[:, :2] = 1
a[:, 2:] = torch.tensor([[2, 3], [4, 5]])
print(a)
# tensor([[1, 1, 2, 3],
# [1, 1, 4, 5]])
Slice Indexing 한 Tensor 객체에 대하여 값을 할당할 수 있다. 또한, 동일한 shape를 가지는 Tensor 객체를 할당할 수도 있다.
'딥러닝 > Michigan EECS 498' 카테고리의 다른 글
[EECS 498] Assignment 1. PyTorch 101...(3) (0) | 2024.12.22 |
---|---|
[EECS 498] Assignment 1. PyTorch 101...(2) (0) | 2024.12.22 |