확률적 관점에서, 모델은 과거의 값들을 바탕으로 현재 값의 조건부 확률을 계산한다.
AR은 주어진 과거 값들에 조건을 둔 현재 값의 확률을 모델링하는 것이다.
모델은 과거의 데이터 포인트들을 바탕으로 현재 값의 조건부 확률을 계산하려고 시도한다.
AR의 특성: 연속 변수로의 확장이 가능하며 Gaussian Mixture Model을 사용할 수 있다.
AR은 여러 변수 간의 시간적인 의존성을 모델링하는데 효과적이다. 연속 변수로의 확장성이 좋다.
NADE는 데이터의 결합 확률 분포를 모델링하기 위한 신경망 기반의 접근법을 사용한다.
NADE는 데이터의 결합 확률 분포를 모델링하는 데 사용되는 신경망 기반의 방법론이다. 데이터의 각 차원에 대한 조건부 확률을 학습하여 전체 데이터의 확률 분포를 추정한다. 이러한 접근법은 복잡한 밀도 추정 문제에 효과적으로 사용될 수 있다.
VAE의 주요 목표는 잠재 공간에서의 샘플링을 통해 새로운 데이터를 생성하는 것이다.
VAE는 잠재 변수 모델로, 데이터를 압축된 표현(잠재 공간)으로 변환한 후 다시 원본과 유사한 출력을 생성한다. 이 잠재공간에서의 샘플링을 통해 새로운 데이터나 예측이 생성될 수 있게 되는 것이 VAE의 주요 특징 중 하나이다.
GAN은 Generator와 Discriminator라는 두 개의 네트워크로 구성된다.
GAN은 생성 네트워크(Generator)와 판별 네트워크(Discriminator)라는 두 개의 신경망으로 구성된다.
생성 네트워크는 데이터와 유사한 새로운 샘플을 생성하려고 시도한다.
판별 네트워크는 생성된 샘플이 실제 데이터인지 아닌지를 판별하려고 시도한다.
이 두 네트워크는 서로 경쟁적으로 학습되며, 이 경쟁 과정을 통해 GAN은 실제 데이터와 유사한 고품질의 샘플을 생성할 수 있게 된다.
Diffusion Models은 데이터의 원본을 노이즈로 변환하며, 이 과정을 역으로 수행하여 데이터를 생성한다.
cf. 이미지 분류 작업: 이미지 분류는 전체 이미지에 대한 한가지 레이블을 할당하는 반면, semantic segmentation은 각 픽셀에 레이블을 할당한다.
핵심 구성 요소 중 하나는 Attention Mehanism
이다. 이 메커니즘은 모델이 시퀀스의 각 부분에 얼마나 주의를 기울일지 결정하게 해준다.
Transformer에서 사용되는 Self-Attention은 각 입력 요소가 시퀀스 내의 다른 모든 요소와의 관계를 이해하는 데 도움을 줄 수 있다.
Transformer Model에서 사용되는 Positional Encoding
의 주요 목적
: 스퀀스의 각 위치에 고유한 정보를 제공하여 위치 정보를 포함시키는 것
Transformer 모델은 기본적으로 입력 시퀀스의 순서 정보를 고려하지 않는 구조이다.
Positional Encoding은 각 단어나 토큰의 위치 정보를 모델에 전달하기 위해 도입되었다.
이를 통해 Transformer는 시퀀스 내에서 단어의 위치를 이해하고, 이에 기반하여 더 정확한 컨텍스트 해석이 가능해진다.
transformer에서 self-attention mechanism의 주요 기능
: 입력 시퀀스의 각 위치에서 모든 위치로의 의존성을 계산한다.
시퀀스 내의 각 원소가 다른 모든 원소와의 관계를 모델링할 수 있게 해준다.
즉, 각 위치의 원소는 시퀀스 내의 모든 다른 위치로부터 정보를 얻을 수 있으며, 문장 내에서 단어 간의 관계나 문맥을 이해하는 데 매우 유용하다.
self-attention은 이러한 관계를 수치화하여 모델이 각 단어나 토큰에 주어진 정보를 최적으로 활용할 수 있도록한다.
Generalization gap 크다 = 과적합
모델이 훈련데이터에는 잘 맞지만, 새로운 데이터에 대해 잘 일반화되지 않아 과적합이 발생했다는 의미이다.
적절한 균형을 찾지 못하면 일반화 에러 증가
문제가 발생한다.
local minimum에서 벗어나기 위한 방법: 모멘텀
사용
모멘텀은 로컬미니멈에서 벗어나 전역미니멈을 찾기 위해 사용할 수 있는 최적화 기술이다.
계산하기 CNN모델에 3364개 filter, 모든 filter의 파라미터 수
: 각 필터에 대한 파라미터 수는 3364=576이다. bias 고려하면 576+1이다.
32개의 필터가 있으므로 577*32=18464이다.
여러 채널을 사용하는 이유
: 다양한 공간적 특징을 동시에 학습
여러 채널을 사용하면 다양한 공간적 특징을 동시에 학습하여 모델의 성능을 향상시킬 수 있다.
그래디언트를 직접 전달한다.
Skip connection은 그래디언트가 직접적으로 뒤로 전달될 수 있도록 하여 그래디언트 소실 문제를 완화한다.
같은 수용 영역을 가지면서 파라미터 수를 줄인다.
작은 conv filter를 여러번 사용하면, 큰 conv filter를 사용하는 것과 같은 수용 영역을 가질 수 있으면서도 더 적은 파라미터를 사용할 수 있다.
1x1, 3x3, 1x1 Conv를 순서대로 사용한다.
차원 축소와 확장을 위해서
torch.cuda.empty_cache()
: 사용되지 않는 GPU상의 캐시를 정리하고 가용 메모리를 확보하기 위해 사용되는 함수이다. 더 이상 필요하지 않은 GPU메모리를 반환하여 가용 메모리를 늘린다.
torch.no_grad()
: Inference시점에서 backward pass로 인해 쌓이는 메모리를 절약하기 위한 PyTorch 구문이다. Inference 시점에서는 모델을 업데이트할 필요가 없으므로 backward pass를 실행할 필요가 없다. torch.no_grad()
구문은 PyTorch에서 backward연산이 필요하지 않다고 지정하여 추론 시점의 메모리 사용량을 줄이는데 사용된다.
Training loop동안 tensor형식의 변수가 축적되면 GPU상의 메모리 사용량이 계속 증가할 수 있다.
total_loss=0
for i in range(10000):
optimizer.zeor_grad()
output = model(input)
loss = criterion(output)
loss.backward()
optimizer.step()
total_loss += loss
GPU 메모리를 효율적으로 관리하기 위한 코드 사용 방법
1. total_loss 변수에 loss.item()
사용하여 누적하기
loss.item()
메소드는 GPU상의 tensor 값을 CPU로 가져와 스칼라 값으로 반환한다. 변환된 값을 누적함으로써 GPU 메모리의 부담을 줄일 수 있다.
2. loss tensor를 사용한 후 del loss
명령어로 삭제하기
3. total_loss += loss.cpu().numpy()
사용하여 누적하기
4. loss 값을 저장하는 리스트 사용하고, loop가 끝나면 해당 리스트를 삭제하기
이 옵션들은 텐서의 축적을 방지하거나, GPU메모리 사용을 최적화하는 방법들로서, 훈련 도중 메모리 부족 문제를 줄이는 데 기여한다.
※torch.cuda.empty_cache()
함수는 사용되지 않는 GPU 메모리 캐시를 정리하는 데 유용하나, 매 iteration마다 호출하는 것은 훈련속도에 불필요한 부하를 주게 된다. 효율적인 학습을 위해 이 함수는 필요할 때에만 주기적으로 사용하는 것이 바람직하다.
딥러닝에서 주로 사용되는 데이터 유형: 텍스트, 이미지, 오디오 데이터 등
교차 엔트로피 손실: 이진 분류 문제에 주로 사용되는 손실 함수
실제 레이블과 모델의 예측 사이의 정보 손실을 측정한다.
확률적 경사 하강법(SGD): 모델의 가중치를 업데이트 하는데 사용되는 최적화 알고리즘
lr 높으면 발생하는 문제: 수렴하지 않음
복잡한 문제 해결 능력을 향상시키기 위해 다층 퍼셉트론(MLP)이 사용된다.
TensorBoard
: 학습중인 모델의 성능 지표나 중간 결과들을 시각적으로 확인하기 위한 도구로 사용된다.
wandb.log()
: 학습 도중의 metric 값을 로그로 기록하는 함수
wandb.log({'accuracy':train_acc,'loss':train_loss})
Wandb 로깅 코드
wandb.init(project="my-test-project", config=config)
wandb.config.batch_size = 128
for e in range(1, epochs+1):
wandb.log({'accuracy':train_acc,'loss':train_loss})
wandb.watch(model)
Wandb 프로젝트를 초기화하고 설정 값을 불러온다.
Wandb의 설정 값을 변경한다.
에포크 수만큼 반복한다.
학습 정확도와 손실 값을 Wandb에 로깅한다.
모델의 가중치 변화를 관찰하고 로깅한다.
다중 GPU 학습
1. 모델을 여러 GPU에 나눠서 학습
2. 데이터를 여러 GPU에 나눠서 학습
ModelParallel(모델 병렬화): 큰 모델을 여러 GPU에 나눠서 각 부분을 따로 학습한다.
모델이 크고 GPU 하나에 담기 어려울 때 유용하다.
ex) A부분은 GPU 1에서, B부분은 GPU 2에서 학습될 수 있다.
DataParallel(DistributedDataParallel, 데이터 병렬화): 모델을 여러 GPU에 복사하고, 각 GPU에 다른 데이터를 주어 동시에 학습한다. 여러 GPU에서 학습한 결과는 결합되어 모델을 업데이트한다.
DataParallel,DistributedDataParallel 차이
DistributedDataParallel(DDP)
:각 CPU마다 프로세스를 생성하여 개별 GPU에 할당한다.
각 GPU에서 독립적으로 모델 학습을 진행할 수 있도록 해준다.
더 효율적인 GPU 메모리 사용 및 더 나은 확장성을 제공한다.
병렬 처리를 효율적으로 수행한다.
DataParallel
:하나의 주 CPU 프로세스에서 여러 GPU로 모델과 데이터를 복사하여 병렬 처리를 수행한다.
GPU간의 통신 오버헤드와 GPU사용의 불균형 문제를 야기할 수 있다.
데이터 분산 불균형
: 한 GPU가 병목이 되면 다른 GPU들도 그 병목 때문에 영향을 받게 된다.
Grid Search
: 하이퍼 파라미터 튜닝 시 대표적인 방법 중 하나로 하이퍼 파라미터의 가능한 모든 조합을 시험하여 최적의 조합을 찾는 방법이다.
다른 기법에 비해 시간이 오래 걸릴 수 있지만 범위 내에서의 최적값을 찾을 수 있다.
nn.Parameter
객체는 nn.Module
내의 attribute로 설정되면 requires_grad_True
로 지정되어 학습 대상이 된다.
nn.Module
내에서 사용될 때 학습 대상이 되기 때문에 gradient계산이 가능하다.
nn.Parameter
는torch.Tensor
의 하위 클래스이므로torch.Tensor
를 상속받는 클래스이다.
backward()
메서드는 실제값과 forward의 결과값 간의 차이(loss)에 대해 미분을 수행한다.
backward()
메서드는 loss에 대한 gradient를 계산하는 데에 사용되며, 각 parameter에 대한 gradient를 계산한다.
nn.Module
에서 backward()
를 직접 오버라이딩할 수 있다.
in_feature
차원의 벡터를 입력으로 받아, out_features
차원으로 선형 변환(linear transform)후, bias를 더해주는 연산을 거치게 된다. 입력 값의 차원은 [B, in_features]로 구성되고, 출력값의 차원은 [B, out_features]로 구성된다.
class MyLinear(nn.Module):
def __init__(self, in_features, out_features, bias = True):
super().__init__()
self.in_features = in_features
self.out_features = out_features
self.weights = nn.Parameter(torch.randn(in_features,out_features))
self.bias = nn.Parameter(torch.randn(in_features,out_features))
def forward(self, x: Tensor):
return x @ self.weights + bias
학습 가능한 linear layer를 구현하기 위해서는 self.weights
, self.bias
가 nn.Parameter
를 필요로한다.
torch.randn
만으로는 requires_grad = False
이므로nn.Parameter
를 통해서 requires_grad = True
인 tensor 객체로 해준다.
선형 변환이라는 점에서 self.weights
와 x
만의 행렬곱 연산을 필요로 한다.
미니 배치를 고려하여 연산을 수행해야 하므로, torch.dot
이 아니여야 한다.
PyTorch의 utils.data모듈로부터 Dataset 클래스를 상속받아 CustomDataset을 구현할 때 필수적으로 오버라이딩 해야하는 매직 메서드
__init__
: 'Dataset' 객체가 생성될 때 한번만 실행된다. 주로 사용할 데이터 셋을 불러오고 필요한 변수를 선언한다.
__getitem__
: index값을 주었을 때, 반환되는 데이터의 형태(X,y)를 정의한다.
__len__
: 데이터의 전체 길이를 반환한다.
주어진 데이터셋을 미니 배치(mini batch)로 분할하고 미니 배치별로 학습을 할 수 있게 도와준다.
device
를 사용해 수동으로 프로그래머가 설정하여 GPU메모리로 전송한다.
데이터의 전처리는 Dataset
클래스에서 수행되거나 별도의 전처리 파이프라인을 통해 이루어진다.
파라미터 최적화를 자동으로 수행한다.
import torch
from torch.utils.data import Dataset, DataLoader
class CustomDataset(Dataset):
def __init__(self, text, labels):
self.labels = labels
self.data = text
def __len__(self):
return len(self.labels)
def __getitem__(self, idx):
label = self.labels[idx]
text = self.data[idx]
sample = {"Text":text, "Class":label}
return sample
text = ['Happy', 'Amazing', 'Sad', 'Unhappy', 'Glum']
labels = ['Positive', 'Positive', 'Negative', 'Negative', 'Neative']
MyDataset = CustomDataset(text, labels)
MyDataLoader = DataLoader(MyDataset, batch_size = 2, shuffle = False)
print(next(iter(MyDataLoader)))
DataLoader를 통해 반환되는 컨테이너 객체의 타입 & DataLoader의 셔플유무
CustomDataset의 __getitem__
에서 sample
에 딕셔너리 객체로 텍스트 데이터와 라벨 데이터를 반환하고 있다. 이에 따라 DataLoader를 통해 반환되는 컨테이너 객체의 타입은 딕셔너리이다.
__getitem__
에서 str 타입의 데이터가 숫자로 인코딩되지 않았다. 이에 따라 torch.tensor 객체로 데이터가 반환되지 않는다.
shuffle은 False로 전달됐기 때문에, 순서대로 데이터를 가져와 텍스트 데이터는 Happy, Amazing과 라벨 데이터는 Positive,Positive이다.
torch.save()
: 모델의 아키텍처와 가중치 저장
torch.save()
를 사용하여 모델의state_dict
만 저장하면 모델의 가중치만 저장된다. 그러나 모델 전체를 저장하면 아키텍처와 가중치 모두를 저장할 수 있다.
PyTorch에서 학습 중간 결과를 저장하고 불러오는 방법이다. checkpoint를 통해 불러온 후, 모델의 상태를 불러오는 코드
torch.save({
'epoch' : e,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'loss': epoch_loss,
}, PATH)
checkpoint = torch.load(PATH)
model.load_state_dict(checkpoint['model_state_dict'])
load_state_dict
메서드를 사용하며, 저장된 checkpoint에서 model_state_dict
키에 해당하는 값을 인자로 전달한다.
vgg = models.vgg16(pretrained=True).to(device)
class MyNewNet(nn.Module):
def __init__(self):
super(MyNewNet, self).__init__()
self.vgg19 = models.vgg19(pretrained=True)
self.linear_layers = nn.Linear(1000,1)
def forward(self,x):
x = self.vgg19(x)
return self.linear_layers(x)
for param in my_model.parameters():
param.requires_grad = False
for param in my_model.linear_layers.parameters():
param.requires_grad = True
param.requires_grad = False
: 모든 모델 파라미터의 학습을 비활성화
for param in my_model.linear_layers.parameters():
param.requires_grad = True
linear_layers
부분만 학습 가능한 상태로 변경
딥러닝 프레임워크를 사용하면 미분 연산을 자동으로 처리해주므로, 수작업으로 미분 식을 작성할 필요가 없다.
모델 구조 뿐만 아니라, 미분값 계산 및 학습 알고리즘 등 대부분 자동으로 처리한다.
이와 같은 맥락으로 optimizer와 loss function 모두 딥러닝 프레임워크에서 다양하게 지원하고 있다.
MXNet
, Caffe
, Keras
, TensorFlow
등이 있다.
Define and Run: 그래프를 먼저 정의하고 실행 시점에 데이터를 주입(feed)
- 실행 중에 그래프가 동적으로 바뀌지 않는 정적 그래프이다.
- TensorFlow 1.x버전 대가 채용하고 있는 방식이다.
Define by Run: 실행을 하면서 그래프를 생성- PyTorch에서는 동적으로 그래프를 생성하기에 필요에 따라 그래프를 변경할 수 있다.
flatten()
: 2차원의 tensor를 1차원 vector로 표현하기 위해 이용하는 메서드
1차원의 vector로 표현한 다음, indexing을 사용하여 원하는 요소들을 가져올 수 있다.
view
, reshape
함수는 모두 tensor의 차원 크기를 변경할 때 사용되는 함수다.
contiguity 차이
view
: tensor가 메모리에 연속적으로 존재할 때만 사용할 수 있다.
tensor가 contiguous하다는 것을 암시적으로 나타내므로, copy없이 더 빠른 연산이 가능하다는 측면에서 view를 권장한다.
reshape
: tensor가 메모리에 연속적으로 존재하지 않을 때, 새로 copy를 하여 차원의 크기를 변경한다. tensor가 메모리에 연속적으로 존재하는 경우에는 원래 tensor와 메모리를 공유한다.
is_contiguous
: tensor가 메모리에 연속적으로 존재하는지를 체크할 수 있는 함수
tensor의 contiguous가 깨지는 경우
t()
와 같은transpose
연산
permute()
와 같은 연산
torch.mm
: 2D 행렬에 대한 행렬 곱셈을 수행한다. Broadcasting은 지원하지 않는다.
torch.matmul
: 더 일반적인 행렬 곱셈을 수행한다. Broadcasting도 지원한다.
import torch
A = torch.tensor([[1,2], [3,4]])
B = torch.tensor([[1,1], [1,1]])
C = torch.tensor([[[1,1], [1,1],] [[1,1], [1,1]]])
#연산1
result1 = torch.mm(A,B)
#연산2
result2 = torch.matmul(A,C)
연산1: A,B는 둘다 2D행렬이므로, torch.mm(A,B)는 성공적으로 실행된다.
연산2: A는 (2,2)형태이고 C는 (2,2,2)형태이다. torch.matmul(A,C)은 broadcasting을 사용하여 성공적으로 실행된다.
쉬운 코드 재현이 어려울 수 있다.
실행 순서가 꼬일 가능성이 존재한다.
프로젝트의 여러 모듈(module)들이 분리되어 있어 코드를 재사용하기에 편리하다.
쉬운 코드의 재현이 가능하다.
Pytorch project template의 주 목적: 코드의 모듈화, 재사용성 향상
실행순서의 꼬임, 유지보수의 어려움, 배포의 복잡성 등은 해결하거나 줄일 수 있다.
프로젝트 템플릿의 목적은 코드의 모듈을 분리하여 관리하는 것이므로, 로깅, 지표, 유틸리티 등은 각자의 모듈 또는 파일에서 관리된다.
__getitem__
를 구현하면 클래스 인스턴스를 리스트나 딕셔너리처럼 사용할 수 있다.__getitem__
메서드는 보통 하나의 인자만 받는다.__getitem__
을 구현하면 for
loop나 in
연산자를 클래스 인스턴스에 사용할 수 있다.__getitem__
은 보통 "읽기 전용"연산으로 간주된다.__getitem__
에 전달받는 인덱스는 항상 정수일 필요 없고 딕셔너리처럼 문자열을 인덱스로 사용할 수 있다.자기자신을 호출하는 함수이다.
점화식과 같은 재귀적 수학 모형을 표현할 때 사용한다.
재귀 종료 조건이 존재하고 종료 조건까지 함수호출을 반복한다.
def factorial(n):
if n == 1:
return 1
else:
return n*factorial(n-1)
print(factorial(int(input("calculation: "))))
파이썬의 가장 큰 특징은 dynamic typing이다.
단점: 처음 함수를 사용하는 사람이 interface를 알기 어렵다.
def do_func(var_name: var_type) -> return_type:
pass
def type_hint_ex(name: str)->str:
return f"Hello, {name}"
Type hints의 장점
파이썬 함수에 대한 상세스펙을 사전에 작성해서 함수 사용자의 이행도를 높인다.
세개의 따옴표로 영역을 표시한다.
VSCode에서 확장프로그램을 설치하면 Ctrl
+shift
+P
로 기본적인 type을 사용할 수 있다.
좋은 프로그래머는 사람이 이해할 수 있는 코드를 짠다.
-마틴 파울러-
따라서 사람이 이해할 수 있는 규칙이 필요하고, 그 규칙을 코딩컨벤션이라고 한다.
-> 혼합하지 않고 일관성이 있게 쓴다.
나중에 넣은 데이터를 먼저 반환하도록 설계된 메모리 구조이다.
Data의 입력을 Push, 출력을 Pop이라고 한다.
리스트를 사용하여 스택 구조를 구현할 수 있다.
push는 append()
, pop은 pop()
을 사용한다.
pop()은 return이 없는 sort()와 다르게, return도 있고 원래 리스트도 변화시킨다.
먼저 넣은 데이터를 먼저 반환하도록 설계된 메모리 구조이다.
Data의 입력을 Put, 출력을 Get이라고 한다.
put은 append()
, get은 pop(0)
을 사용한다.
값의 변경이 불가능한 리스트이다.
장점: 바뀌면 안되는 데이터를 저장할 때 사용한다.
값을 순서없이 저장한다.
중복을 불허한다.
add()
: 추가
remove()
: 삭제
update()
: 한번에 많이추가
discard()
: 삭제
clear()
: 모든 원소 삭제
union()
: 합집합
intersection()
: 교집합
difference()
: 차집합
items()
: data가 key, value 쌍으로 출력됨
class Note
method write_content
,remove_all
variable content
class Note(object):
def __init__(self,content = None):
self.content = content
def write_content(self, content):
self.content = content
def remove_all(self):
self.content = ""
def __add__(self, other):
return self.content + other.content
def __str__(self):
return self.content
class NoteBook
method add_note
,remove_note
,get_number_of_pages
variable title
,page_number
,notes
class NoteBook(object):
def __init__(self, title):
self.title = title
self.page_number = 1
self.notes = {}
def add_note(self, note, page=0):
if self.page_number < 300:
if page == 0:
self.notes[self.page_number] = note
self.page_number += 1
else:
self.notes = {page : note}
self.page_number += 1
else:
print("page full")
def remove_note(self, page_number):
if page_number in self.notes.keys():
return self.notes.pop(page_number)
else:
print("page not found")
def get_number_of_pages(self):
return len(self.note.keys())