PyTorch
0. PyTorch 설치하기
PyTorch 설치 방법은 https://pytorch.org/get-started/locally/에서 안내받을 수 있다. 자신에게 맞는 조건들을 선택한 후 나타나는 명령어로 설치를 진행한다. GPU 없이 CPU만을 사용한다면 CUDA는 None으로 선택해도 된다.
1. 수를 표현하는 방식
Rank(차원) | 이름 | 예시 |
---|---|---|
0 | scalar | 1,2,3,4 |
1 | vector | [1,2,3,4] |
2 | matrix | [[1,2],[3,4]] |
3 | tensor | [[[1,2]]] |
cf) shape
- image
(4, 1, 5, 5) = (Sample, Channel, Width, Height)- 시계열
(4, 10, 16) = (Sample, Seq_len, Feature_Dim)
2. PyTorch에서의 Array
PyTorch에서 Array는 numpy에서 사용하는 것과 유사하게 사용할 수 있고 N차원 표현이 가능하다.
PyTorch에서는 FloatTensor와 LongTensor를 자주 사용하게 되며, 입력 X는 주로 FloatTensor로, 정답 Y는 주로 LongTensor로 표현한다.
t = torch.FloatTensor([0., 1., 2., 3., 4., 5., 6.])
print(type(t), t, t.dtype)
# <class 'torch.Tensor'> tensor([0., 1., 2., 3., 4., 5., 6.]) torch.float32
m = torch.LongTensor([1, 2, 3])
print(type(m), m, m.dtype)
# <class 'torch.Tensor'> tensor([1, 2, 3]) torch.int64
numpy와 유사한 활용법
.dim()
: 차원 확인.shape
및.size()
: shape 확인- 인덱싱, 슬라이싱, 브로드캐스팅
.mean()
: 평균 구하기. integer는 불가.sum()
: 합계.max()
: dimension이 지정되면 max와 argmax(인덱스)를 모두 반환.cat()
: Concatenation.stack()
: Stacking
numpy에서 Tensor로, Tensor에서 numpy로 변환할 수도 있다.
# numpy --> Tensor
k = np.array([0., 1., 2., 3., 4., 5., 6.])
t = torch.FloatTensor(k)
# Tensor --> numpy
t.numpy()
3. View
view는 numpy에서의 reshape과 유사하다. 다만, 실제 내부 동작방식에서는 차이가 있는데 view는 바뀐 것처럼 보여지게 하는 것이다.
t = np.array([[[0, 1, 2],
[3, 4, 5]],
[[6, 7, 8],
[9, 10, 11]]])
ft = torch.FloatTensor(t)
print(ft.shape) # torch.Size([2, 2, 3])
print(ft.view([-1, 3]).shape) # torch.Size([4, 3])
4. Squeeze & Unsqueeze
squeeze와 unsqueeze를 사용해 차원을 줄이고 늘릴 수 있다.
Squeeze
- (1, 2, 3) –> (2, 3)
- (2, 1, 3) –> (2, 3)
- (2, 3, 1) –> (2, 3)
- (3, 1) –> (3,)
ft = torch.FloatTensor([[0], [1], [2]])
print(ft.shape) # torch.Size([3, 1])
print(ft.squeeze().shape) # torch.Size([3])
Unsqueeze
.unsqueeze(0)
: (3,) –> (1, 3) / (2, 3) –> (1, 2, 3).unsqueeze(1)
: (3,) –> (3, 1) / (2, 3) –> (2, 1, 3).unsqueeze(-1)
: (3,) –> (3, 1) / (2, 3) –> (2, 3, 1)
ft = torch.Tensor([0, 1, 2])
print(ft.shape) # torch.Size([3])
print(ft.unsqueeze(0).shape) # torch.Size([1, 3])
5. 딥러닝 학습 (Linear Regression)
딥러닝에서 학습을 한다는 것은 X, Y가 주어졌을 때 Y = f(WX + b)
에서 W와 b를 스스로 찾아내는 것이다.
딥러닝에서의 실제 구현은 Y = W*X + b
가 아닌 Y = X*W + b
(*
는 Matrix Multiplication)로 되어 있으며, 두 가지 경우의 결과는 다르다.
Matrix Multiplication
Y = f(X.matmul(W) + b)
- f: 활성화함수
- X: 입력
- Y: 출력
- W: 가중치
- H(x): 주어진 x 값에 대해 예측을 어떻게 할 것인가
- cost(W, b): H(x) 가 y 를 얼마나 잘 예측했는가
- MSE (Mean Squared Error)
Regression에서는 MSE를, Classification에서는 Cross Entropy Loss를 사용한다.
이를 실제로 구현하면 다음과 같다.
hypothesis = x_train * W + b
cost = torch.mean((hypothesis - y_train) ** 2)
샘플 데이터로 학습시켜보자. 실행하면 W는 점차 1에 가까워지고 b는 점점 줄어들어 0에 가까워지는 것을 확인할 수 있다.
# 데이터 : 후에 (Dataset, DataLoader)를 이용해서 만들게 됨
x_train = torch.FloatTensor([[1], [2], [3]])
y_train = torch.FloatTensor([[1], [2], [3]])
# 모델 초기화
W = torch.zeros(1, requires_grad=True)
b = torch.zeros(1, requires_grad=True)
# optimizer 설정
optimizer = optim.SGD([W, b], lr=0.01) # backpropagation 과정 (내부에서 미분하는 과정)
nb_epochs = 1000 # Mini-batch
for epoch in range(nb_epochs + 1):
# H(x) 계산
hypothesis = x_train * W + b
# cost 계산
cost = torch.mean((hypothesis - y_train) ** 2)
# cost로 H(x) 개선
optimizer.zero_grad() # pytorch의 차이점: optimizer 내부 미분 값을 누적 저장하게 되어 있다. zero reset
cost.backward() # 미분 값을 계산
optimizer.step() # model parameter 업데이트: W(or b) = W(or b) -lr * grad
# 100번마다 로그 출력
if epoch % 100 == 0:
print('Epoch {:4d}/{} W: {:.3f}, b: {:.3f} Cost: {:.6f}'.format(
epoch, nb_epochs, W.item(), b.item(), cost.item()
))
nn.Module
을 상속받아 만든 Linear Regression 모델을 적용해보자.
class LinearRegressionModel(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(1, 1) # Linear 안에 weight matrix, bias 있음
def forward(self, x): # 필수 구현
return self.linear(x) # (3, 1) * (1, 1) ==> (3, 1) # Matrix Multiplication 연산
# 데이터 : 후에 (Dataset, DataLoader)를 이용해서 만들게 됨
x_train = torch.FloatTensor([[1], [2], [3]])
y_train = torch.FloatTensor([[1], [2], [3]])
# 모델 초기화
model = LinearRegressionModel()
# optimizer 설정
optimizer = optim.SGD(model.parameters(), lr=0.01)
nb_epochs = 1000
for epoch in range(nb_epochs + 1):
# H(x) 계산
prediction = model(x_train)
# cost 계산
cost = F.mse_loss(prediction, y_train)
# cost로 H(x) 개선
optimizer.zero_grad()
cost.backward()
optimizer.step()
# 100번마다 로그 출력
if epoch % 100 == 0:
params = list(model.parameters())
W = params[0].item()
b = params[1].item()
print('Epoch {:4d}/{} W: {:.3f}, b: {:.3f} Cost: {:.6f}'.format(
epoch, nb_epochs, W, b, cost.item()
))
다른 딥러닝 방법론을 사용할 수도 있으나, 이번 글에서는 간단하게만 언급한다.
- Logistic Classification
hypothesis = torch.sigmoid(x_train.matmul(W) + b)
cost = F.binary_cross_entropy(hypothesis, y_train)
- Softmax
hypothesis = F.softmax(x_train.matmul(W) + b, dim=1)
cost = F.cross_entropy(x_train.matmul(W) + b, y_train)
- 출력의 결과물로 확률을 반환하며, 이 확률값을 모두 다 더하면 1이 된다.
6. 하이퍼 파라미터 Optimization
전문가가 직접 입력해줘야 하는 파라미터를 ‘하이퍼 파라미터’라고 하며, 최적의 하이퍼 파라미터를 찾는 것을 ‘하이퍼 파라미터 Optimization’이라고 한다.
- 하이퍼 파라미터
- LR
- Layer 개수
- Layer 히든 유닛의 개수
- 최적 아키텍처
7. Overfitting
딥러닝은 모델링을 통해 수집한 과거데이터로 현재를 예측하는 과정이다. 이 때, 다음과 같은 가정을 하고 진행하게 된다.
- 과거 데이터는 현재 또는 미래 데이터를 대표할 가능성이 있다.
- 과거 데이터는 현재 또는 미래에 일어날 가능성이 높은 데이터일 수 있다.
하지만, 문제점이 존재한다.
- 과거 데이터를 수집할 때는 미래의 서비스에 입력될 데이터의 특성을 포함하고 있어야 한다.
- 과거 모든 데이터를 수집하더라도 미래의 데이터를 위해 완벽하게 준비해내는 것은 불가능하다.
- 학습 데이터가 적으면 예측 데이터의 분포를 잘 대표하지 못하는 문제가 있다.
너무 학습데이터에 한해 잘 학습하여 테스트 데이터에서는 좋은 성능을 내지 못하는 경우를 Overfitting이라고 하는데, 이를 방지하기 위한 방법은 크게 다음의 세 가지로 볼 수 있다.
- 더 많은 학습 데이터
- 더 적은 양의 feature (패턴인식의 차원의 저주 방지)
- Regularization
8. 순환신경망 (Recurrent Neural Network, RNN)
Basic
은닉층의 노드에서 활성화 함수를 통해 나온 결과값을 출력층 방향으로도 보내면서, 다시 은닉층 노드의 다음 계산의 입력으로 보내는 방식을 취한다.
t1: h -> e (==> t1_state)
t2: e -> l (==> t2_state)
t3: l -> l (==> t3_state)
t4: l -> o (==> t4_state)
import torch
import numpy as np
h = [1, 0, 0, 0]
e = [0, 1, 0, 0]
l = [0, 0, 1, 0]
o = [0, 0, 0, 1]
input_data_np = np.array([[h, e, l, l, o], [e, o, l, l, l], [l, l, e, e, l]], dtype=np.float32)
input_data_np의 shape = (3, 5, 4) = (Batch_Size (Sample), Seq_Len (T), Featrue_Dim)
- DNN 입력 데이터 Shape = (Batch_Size, Feature_Dim)
- CNN 입력 데이터 Shape = (Batch_Size, Channel(Color), Width, Height)
- RNN 입력 데이터 Shape = (Batch_Size, Seq_Len, Featrue_Dim)
Torch Tensor로 바꾸고, RNN을 선언한다.
input_size = 4
hidden_size = 2
input_data = torch.Tensor(input_data_np)
rnn = torch.nn.RNN(input_size, hidden_size)
결과를 확인해보자.
outputs, _status = rnn(input_data)
print(outputs)
print(_status) # 마지막 상태 정보만 기억
CharSeq
import torch
import torch.optim as optim
import numpy as np
sample = " if you want you"
char_set = list(set(sample))
char_dic = {c: i for i, c in enumerate(char_set)}
dic_size = len(char_dic) # out-layer의 인식해야하는 클래스(카테고리)의 개수
hidden_size = len(char_dic)
learning_rate = 0.1
sample_idx = [char_dic[c] for c in sample]
x_data = [sample_idx[:-1]]
x_one_hot = [np.eye(dic_size)[x] for x in x_data]
y_data = [sample_idx[1:]]
X = torch.FloatTensor(x_one_hot) # X = (Batch_Size, Seq_Len, Feature_Dim)
Y = torch.LongTensor(y_data) # Y = (BS, Seq_Len)
rnn = torch.nn.RNN(dic_size, hidden_size, batch_first=True)
criterion = torch.nn.CrossEntropyLoss()
optimizer = optim.Adam(rnn.parameters(), learning_rate)
for i in range(50):
optimizer.zero_grad()
outputs, _ = rnn(X)
# 상태정보를 이용하지 않을 때에는 _status 대신 _를 사용
# outputs.shape = (BS, Seq_Len, hidden_size(=10))
loss = criterion(outputs.view(-1, dic_size), Y.view(-1))
loss.backward()
optimizer.step()
result = outputs.data.numpy().argmax(axis=2)
result_str = ''.join([char_set[c] for c in np.squeeze(result)])
print(i, "loss: ", loss.item(), "prediction: ", result, "true Y: ", y_data, "prediction str: ", result_str)
- DNN
- outputs_shape = (BS, Output_Size)
- Y_shape = (BS, )
- RNN
- outputs_shape = (BS, Seq_Len, Output_Size (Hidden_Size)) –> (BS*Seq_Len, Output_Size (Hidden_Size)) (
.view()
에서 일어남) - Y_shape = (BS, Seq_Len) –> (BS*Seq_Len, )
- outputs_shape = (BS, Seq_Len, Output_Size (Hidden_Size)) –> (BS*Seq_Len, Output_Size (Hidden_Size)) (
9. Padding vs. Packing
PyTorch 라이브러리의 다음 함수들을 사용해 Padding과 Packing을 쉽게 수행할 수 있다.
pad_sequence
pack_sequence
pack_padded_sequence
pad_packed_sequence
import torch
import numpy as np
from torch.nn.utils.rnn import pad_sequence, pack_sequence, pack_padded_sequence, pad_packed_sequence
data = ['hello world',
'midnight',
'calculation',
'path',
'short circuit']
char_set = ['<pad>'] + list(set(char for seq in data for char in seq)) # Get all characters and include pad token
char2idx = {char: idx for idx, char in enumerate(char_set)} # Constuct character to index dictionary
print('char_set:', char_set)
# char_set: ['<pad>', 'i', 'l', 'w', 'a', 'h', 'p', 'd', ' ', 'o', 'e', 'r', 'n', 't', 'c', 'u', 'm', 'g', 's']
print('char_set length:', len(char_set))
# char_set length: 19
X = [torch.LongTensor([char2idx[char] for char in seq]) for seq in data]
lengths = [len(seq) for seq in X] # [11, 8, 11, 4, 13]
(1) (Zero) Padding
속도가 빠르며, 일반적으로 가장 많이 쓰이는 방법이며, PaddedSequence는 sequence중에서 가장 긴 sequence와 길이를 맞추어주기 위해 padding을 추가한 일반적인 Tensor를 말한다. padding_value=0.0
이 기본이지만, 원한다면 바꿀 수 있다.
padded_sequence = pad_sequence(X, batch_first=True)
(2) Packing
PyTorch에서는 PackedSequence를 통해 padding 없이도 정확히 필요한 부분까지 병렬 계산을 수행하게 만들 수 있다. 주의해야 할 점은, 주어지는 Tensor 리스트가 길이별 내림차순으로 정렬되어 있어야 한다는 것이다.
sorted_idx = sorted(range(len(lengths)), key=lengths.__getitem__, reverse=True)
sorted_X = [X[idx] for idx in sorted_idx]
packed_sequence = pack_sequence(sorted_X)
print(packed_sequence)
PackedSequence(data=tensor([ 8, 15, 14, 1, 12, 15, 10, 9, 2, 9, 17, 4, 4, 3, 7, 6, 4, 14,
5, 15, 7, 17, 13, 2, 11, 11, 4, 18, 14, 16, 9, 15, 2, 17, 7, 7,
6, 6, 2, 14, 4, 17, 13, 3, 5, 2, 7]), batch_sizes=tensor([5, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 1, 1]))
t1, t2, t3, t4에는 5개, t5, t6, t7, t8에는 4개, t9, t10, t11에는 3개, t12, t13에는 1개를 가져와서 처리한다.
Leave a comment