DL| Pytorch

Pytorch(tensorflow 차이, 연산, 구성요소, 클래스, ray,…)

Pytorch VS TensorFlow

Pytorch: Dynamic Computation Graph(연산의 과정을 그래프로 표현)

Tensorflow: Define and Run(그래프를 먼저 정의 → 실행 시점에 데이터 feed)

image.png

Why Pytorch

→ Numpy + AutoGrad + Function

  • Define by Run의 장점
  • 즉시 확인 가능 → pythonic code
  • GPU support, Good API and Community
  • 사용하기 편한 장점이 큼
  • TF는 production과 scalability의 장점

  • Numpy 구조를 가지는 Tensor 객체로 array 표현
  • 자동미분을 지원하여 DL 연산을 지원
  • 다양한 형태의 DL을 지원하는 함수와 모델을 지원함

Pytorch Operations

  • Numpy + Autograd

Tensor

  • 다차원 Arrays를 표현하는 PyTorch 클래스
  • 사실상 Numpy의 ndarray와 동일(TensorFlow의 Tensor와도 동일)
  • Tensor를 생성하는 함수도 동일

Array to Tensor

# data to tensor
data = [[3, 5], [10, 5]]
x_data = torch.tensor(data)
# ndarray to tensor
nd_array_ex = np.array(data)
tensor_array = torch.from_numpy(nd_array_ex)

Tensor data types

기본적으로 tensor가 가질 수 있는 data 타입은 numpy와 동일

numpy likes operations

  • 기본적으로 Pytorch의 대부분의 사용법이 그대로 적용
  • pytorch의 tensor는 GPU에 올려서 사용가능

tensor handling

view, squeeze, unsqueeze 등으로 tensor 조정 가능

  • view: reshape과 동일하게 tensor의 shape을 변환
  • squeeze: 차원의 개수가 1인 차원을 삭제(압축)
  • unsqueeze: 차원의 개수가 1인 차원을 추가

view

tensor_ex = torch.rand(size=(2, 3, 2)) 
tensor_ex
# tensor([[[0.7466, 0.5440], 
# [0.7145, 0.2119], 
# [0.8279, 0.0697]],

# [[0.8323, 0.2671], 
# [0.2484, 0.8983], 
# [0.3228, 0.2254]]])

tensor_ex.view([-1, 6])
# tensor([[0.7466, 0.5440, 0.7145, 0.2119, 0.8279, 0.0697], 
# [0.8323, 0.2671, 0.2484, 0.8983, 0.3228, 0.2254]])

tensor_ex.reshape([-1, 6])
# tensor([[0.7466, 0.5440, 0.7145, 0.2119, 0.8279, 0.0697], 
# [0.8323, 0.2671, 0.2484, 0.8983, 0.3228, 0.2254]])

→ view와 reshape은 contiguity 보장의 차이

reshape은 a와 b의 메모리가 공유되지 않으므로 b는 변경되지 않음

a = torch.zeros(3, 2)
b = a.view(2, 3)
a.fill_(1)
'''
a
tensor([[1., 1.],
        [1., 1.],
        [1., 1.]])
b
tensor([[1., 1., 1.],
        [1., 1., 1.]])
'''
a = torch.zeros(3, 2)
b = a.t().reshape(6)
a.fill_(1)
'''
a
tensor([[1., 1.],
        [1., 1.],
        [1., 1.]])
b
tensor([0., 0., 0., 0., 0., 0.])
'''

squeeze, unsqueeze

  • squeeze: 차원이 1인 차원 제거
  • unssqueeze: squeeze 함수의 반대로 1인 1차원을 생성

image.png

tensor_ex = torch.rand(size=(2,1,2))
tensor_ex.squeeze()
# tensor([[0.8510, 0.8263],
# [0.7602, 0.1309]])

tensor_ex = torch.rand(size=(2,2))
tensor_ex.unsqueeze(0).shape
# torch.Size([1,2,2])

tensor_ex.unsqueeze(1).shape
# torch.Size([2,1,2])

tensor_ex.unsqueeze(2).shape
# torch.Size([2,2,1])

행렬곱셈 연산 mm vs matmul

n2 = np.arange(10).reshape(5, 2)
t2 = torch.FloatTensor(n2)

t1.mm(t2)
# tensor([[ 60., 70.],
# [160., 195.]])
t1.dot(t2)
# RuntimeError
t1.matmul(t2)
# tensor([[ 60., 70.],
# [160., 195.]])

a = torch.rand(10) 
b = torch.rand(10) 
a.dot(b)

a = torch.rand(10) 
b = torch.rand(10)
a.mm(b)

→ mm과 matmul은 broadcasting 지원 차이

  • mm은 정확하게 matrix 곱의 사이즈가 맞아야 사용이 가능
  • matmul은 T1(10, 3, 4)와 T2(4)를 곱할 때

    맨 앞의 dim이 3개일 때는 첫 dim을 batch로 간주하고 T1(3, 4) tensor의 10개의 batch와 T2(4)랑 곱을 해주는 것임

tensor operations for ml/dl formula

nn.functional 모듈을 통해 다양한 수식 변환을 지원함

AutoGrad(pytorch의 자동 미분 엔진)

Pytorch Tutorial torch autograd에 대한 간단한 소개 • Autograd ; Automatic gradient calculating API

Pytorch에서 autograd 동작 이해

pytorch의 핵심은 자동 미분의 지원! → backword 함수를 사용

w = torch.tensor(2.0, requires_grad=True)
y = w**2
z = 10*y + 25
z.backward()
w.grad

image.png

a = torch.tensor([2., 3.], requires_grad=True)
b = torch.tensor([6., 4.], requires_grad=True)
Q = 3*a**3  b**2
external_grad = torch.tensor([1., 1.]) 
Q.backward(gradient=external_grad)
a.grad 
# a.grad
b.grad
# tensor([-12., -8.])

image.png image.png

Torch 구성요소

torch.nn.Module

  • 딥러닝을 구성하는 layer의 base class
  • input, output, forward, backward 정의
  • 학습의 대상이 되는 parameter(tensor) 정의

nn.Parameter

  • Tensor 객체의 상속 객체
  • nn.Module 내에 attribute가 될 때는 required_grad = True로 지정되어 학습 대상이 되는 Tensor
  • 우리가 직접 지정할 일은 잘 없고 대부분의 layer에는 weights 값들이 지정되어 있음

Backward

  • Layer에 있는 Parameter들의 미분을 수행
  • Forward의 결과값(model의 output=예측치)과 실제값간의 차이(loss)에 대해 미분을 수행
  • 해당 값으로 Parameter 업데이트

실제 backward는 Module 단계에서 직접 지정가능

  • Module에서 backward 와 optimizer 오버라이딩
  • 사용자가 직접 미분 수식을 써야하는 부담 → 쓸 일은 없으나 순서를 이해할 필요는 있음
class LR(nn.Moudule):
	def __init__(self, dim, lr = torch.scalar_tensor(0.01)):
		super(LR, self).__init__()
		# initialize parameters
		self.w = torch.zeros(dim, 1, dtype=torch.float).to(device)
		self.b = torch.scalar_tensor(0).to(device)
		self.grads = {'dw':torch.zeros(dim, 1, dtype=torch.float).to(device),
									'db':torch.scalar_tensor(0).to(device)}
		self.lr = lr.to(device)

def forward(self, x):
	# compute forward
	z = torch.mm(self.w.T, x)
	a = self.sigmoid(z)
	return z

def sigmoid(self, z):
	return 1 / (1 + torch.exp(-z))

def backward(self, x, yhat, y):
	## compute backward
	self.grads['dw'] = (1/x.shape[1]) * torch.mm(x, (yhat-y).T)
	self.grads['db'] = (1/x.shape[1]) * torch.sum(yhat-y)
	
def optimize(self):
	## optimization step
	self.w = self.w - self.lr * self.grads['dw']
	self.b = self.b - self.lr * self.grads['db']	

image.png image.png

Pytorch datasets & dataloaders

image.png

Dataset 클래스

  • 데이터 입력 형태를 정의하는 클래스
  • 데이터를 입력하는 방식의 표준화
  • Image, Text, Audio등에 따른 다른 입력 처리
import torch
from torch.utils.data import Dataset

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

Dataset 클래스 생성시 유의사항

  • 데이터 형태에 따라 각 함수를 다르게 정의
  • 모든 것을 데이터 생성 시점에 처리할 필요 없음: image의 Tensor 변화는 학습에 필요한 시점에 변환
  • 데이터 셋에 대한 표준화된 처리방법 제공 필요: 후속 연구자 또는 동료에게는 빛과 같은 존재
  • 최근에는 HuggingFace 등 표준화된 라이브러리 사용

DataLoader 클래스

  • Data의 Batch를 생성해주는 클래스
  • 학습직전(GPU feed전) 데이터의 변환을 책임
  • Tensor로 변환 + Batch 처리가 메인 업무
  • 병렬적인 데이터 전처리 코드의 고민 필요
# Dataset 생성
text = ['Happy', 'Amazing', 'Sad', 'Unhappy', 'Glum']
labels = ['Positive', 'Positive', 'Negative', 'Negative', 'Negative']
MyDataset = CustomDataset(text, labels)

MyDataLoader = DataLoader(MyDataset, batch_size=2, shuffle=True) 
next(iter(MyDataLoader))
# {‘Text’: ['Glum', ‘Sad'], 'Class': ['Negative', 'Negative’]}

# DataLoader Generator
MyDataLoader = DataLoader(MyDataset, batch_size = 2, shuffle = True)
for dataset in MyDataLoader:
 print(dataset)
# {‘Text’: ['Glum', 'Unhappy'], 'Class': ['Negative', 'Negative’]}
# {‘Text’: [‘Sad', ‘Amazing'], 'Class': ['Negative', ‘Positive’]}
# {‘Text’: [‘happy'], 'Class': [‘Positive']}

DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, 
	 batch_sampler=None, num_workers=0, collate_fn=None, 
	 pin_memory=False, drop_last=False, timeout=0, 
	 worker_init_fn=None, *, prefetch_factor=2, 
	 persistent_workers=False)

Model

model.save()

  • 학습의 결과를 저장하기 위한 함수
  • 모델 형태(architecture)와 파라메터를 저장
  • 모델 학습 중간 과정의 저장을 통해 최선의 결과모델을 선택
  • 만들어진 모델을 외부 연구자와 공유하여 학습 재연성 향상
# state_dict: 모델의 파라미터를 표시
print("Model's state_dict:")

for param_tensor in model.state_dict():
	print(param_tensor, "\t", model.state_dict()[param_tensor].size())

# 모델의 파라미터를 저장
torch.save(model.state_dict(),
 os.path.join(MODEL_PATH, "model.pt"))
# 같은 모델의 형태에서 파라미터에만 load
new_model = TheModelClass()
new_model.load_state_dict(torch.load(os.path.join(
	MODEL_PATH, "model.pt")))

# 모델의 architecture와 함께 저장
torch.save(model, os.path.join(MODEL_PATH, "model.pt"))
# 모델의 architecture와 함께 load
model = torch.load(os.path.join(MODEL_PATH, "model.pt"))

checkpoints

  • 학습의 중간결과를 저장하여 최선의 결과를 선택
  • earlystopping 기법 사용시 이전학습의 결과물을 저장
  • loss와 metric값을 지속적으로 확인 저장
  • 일반적으로 epoch,loss,metric을 함께 저장하여 확인
  • colab에서 지속적인 학습을 위해 필요

image.png

torch.save({
'epoch': e,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'loss': epoch_loss
},
f"saved/checkpoint_model_{e}_{epoch_loss/len(dataloader)}_{epoch_acc/len(dataloader)}.pt")

checkpoint = torch.load(PATH)
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
epoch = checkpoint['epoch']
loss = checkpoint['loss']

pretrained model Transfer learning

Transfer learning

  • 다른 데이터셋으로 만든 모델을 현재 데이터에 적용
  • 일반적으로 대용량 데이터셋으로 만들어진 모델의 성능↑
  • 현재의 DL에서는 가장 일반적인 학습 기법
  • backbone architecture가 잘 학습된 모델에서 일부분만 변경하여 학습을 수행함

Freezing

  • pretrained model을 활용시 모델의 일부분을 frozen 시킴
  • 보통 데이터의 특징을 추출하는 부분의 변수는 동결하고, 분류 파트에 해당하는 fully connected layer의 변수만 업데이트할 수 있음
# vgg16 모델을 vgg에 할당하기
vgg = models.vgg16(pretrained=True).to(device)

# 모델에 마지막 Linear Layer 추가하기
class MyNewNet(nn.Module): 
	def _init_(self):
		super(MyNewNet, self).__init_() 
		self.vgg19 = models.vgg19(pretrained=True) 
		self.linear_layers = nn.Linear(1000, 1)
		
	# Defining the forward pass 
	def forward(self, x):
		x = self.vgg19(x)
		return self.linear_layers(x)
		
# 마지막 레이어 제외하고 frozen
for param in my_model.parameters(): 
	param.requires_grad = False
for param in my_model.linear_layers.parameters(): 
	param.requires_grad = True

Monitoringtools for PyTorch

tensorboard, weight&biases

Multi-GPU

Model parallel

다중 GPU에 학습 분산하기

모델 나누는 것은 alexnet 때부터 사용

모델의 병목, 파이프라인의 어려움 등으로 인해 모델 병렬화는 고난이도 과제

image.png

class ModelParallelResNet50(ResNet):
	def __init__(self, **args, **kwargs):
		super(ModelParallelResNet50, self).__init__(
			Bottleneck, [3, 4, 6, 3], num_classes = num_classes, *args, **kwargs)
			
			self.seq1 = nn.Sequential(
				self.conv1, self.bn1, self.relu, self.maxpool, self.layer1, self.layer2
				).to('cuda:0') # 첫번째 모델을 cuda 0에 할당
			
			self.seq2 = nn.Sequential(
				self.layer3, self.layer4, self.avgpool,
				).to('cuda:1') # 두번째 모델은 cuda 1에 할당
				
			self.fc.to('cuda:1')
	
	def forward(self, x):
		x = self.seq2(self.seq1(x).to('cuda:1')) # 두 모델을 연결
		return self.fc(x.view(x.size(0), -1))

Data parallel

데이터를 나눠 GPU에 할당 후 결과의 평균을 취하는 방법

minibatch 수식과 유사, 한번에 여러 GPU에서 수행

Training Neural Nets on Larger Batches: Practical Tips for 1-GPU, Multi-GPU & Distributed setups

  • Pytorch에서는 아래 두 가지 방식을 제공
    • DataParallel: 단순히 데이터를 분배한 후 평균을 취함 → GPU 사용 불균형 문제 발생, Batch 사이즈 감소(한 GPU가 병목), GIL

        parallel_model = torch.nn.DataParallel(model)
              
        predictions = parallel_model(inputs)
        loss = loss_function(predictions, lablels)
        loss.mean().backward()
        optimizer.step()
        predictions = parallel_model(inputs)
      
    • DistributedDataParallel: 각 CPU마다 process 생성하여 개별 GPU에 할당 → 기본적으로 DataParallel로 하나 개별적으로 연산의 평균을 냄

        train_sampler = torch.utils.data.distributed.DistiributedSampler(train_data)
        shuffle = False
        pin_memory = True
              
        trainloader = torch.utils.DataLoader(train_data, batch_size=20, shuffle=True,
        pin_memory=pin_memory, num_workers=3, shuffle=shuffle, sampler = train_sampler)
      

      image.png

        def main():
        	n_gpus = torch.cuda.device_count()
        	torch.multiprocessing.spawn(main_worker, nprocs=n_gpus, args=(n_gpus, ))
              
        def main_worker(gpu, n_gpus):
        	image_size = 224
        	batch_size = 512
        	num_worker = 8
        	epochs = ...
              	
        	batch_size = int(batch_size / n_gpus)
        	num_worker = int(num_worker / n_gpus)
              	
        	# 멀티프로세싱 통신 규약 정의
        	torch.distributed.init_process_group(
        		backend='nccl’, init_method='tcp://127.0.0.1:2568, world_size=n_gpus, rank=gpu)
              	
        	# Distributed DataParallel 정의
        	model = MODEL
        	torch.cuda.set_device(gpu) 
        	model = model.cuda(gpu)
        	model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[gpu])
              
        # Python의 멀티프로세싱 코드
        from multiprocessing import Pool
        	def f(x):
        		return x*x
              		
        	if __name__ == '__main ':
        		with Pool(5) as p:
        		print(p.map(f, [1, 2, 3]))
      

Hyperparameter Tuning

grid vs random, 최근에는 베이지안 기법들이 주도

Ray

  • multi-node multi processing 지원 모듈
  • ML/DL의 병렬 처리를 위해 개발된 모듈
  • 기본적으로 현재 분산병렬 ML/DL 모듈의 표준
  • Hyperparameter Search를 위한 다양한 모듈 제공
data_dir = os.path.abspath("./data")
load_data(data_dir)
# config에 search space 지정, 학습 스케줄링 알고리즘 지정
config = {
	"l1": tune.sample_from(lambda _: 2 ** np.random.randint(2, 9)),
	"l2": tune.sample_from(lambda _: 2 ** np.random.randint(2, 9)),
	"lr": tune.loguniform(1e-4, 1e-1),
	"batch_size": tune.choice([2, 4, 8, 16])
}

scheduler = ASHAScheduler(
	metric="loss", mode="min", max_t=max_num_epochs, grace_period=1, reduction_factor=2)

# 결과 출력 양식 지정
reporter = CLIReporter(
	metric_columns=["loss", "accuracy", "training_iteration"])

# 병렬 처리 양식으로 학습 시행	
result = tune.run(
	partial(train_cifar, data_dir=data_dir),
	resources_per_trial={"cpu": 2, "gpu": gpus_per_trial},
	config=config, num_samples=num_samples,
	scheduler=scheduler,
	progress_reporter=reporter)

PyTorch Troubleshooting

OOM(Out Of Memory)

  • 왜 발생했는지 어려움, 어디서인지 어려움, Error backtracking이 이상한데로 감, 메모리 이전 상황의 파악 어려움

GPUUtil 사용하기

  • nvidia-smi처럼 GPU 상태를 보여줌
  • colab은 환경에서 GPU 상태 보여주기 편함
  • iter마다 메모리가 늘어나는지 확인
!pip install GPUtil
import GPUtil
GPUtil.showUtilization()

torch.cuda.empty_cache()써보기

  • 사용되지않은 GPU상 cache를 정리
  • 가용 메모리를 확보
  • del과는 구분이 필요
  • reset 대신 쓰기 좋은 함수
import torch
from GPUtil import showUtilization as gpu_usage

print("Initial GPU Usage")
gpu_usage()

tensorList = []
for x in range(10):
	tensorList.append(torch.randn(10000000,10).cuda())
print("GPU Usage after allcoating a bunch of Tensors")
gpu_usage()

del tensorList
print("GPU Usage after deleting the Tensors")
gpu_usage()

print("GPU Usage after emptying the cache")
torch.cuda.empty_cache()
gpu_usage()

trainingloop에 tensor로 축적되는 변수는 확인할것

  • tensor로 처리된 변수는 GPU 상에 메모리 사용
  • 해당 변수 loop 안에 연산에 있을 때 GPU에 computational graph를 생성(메모리 잠식)
total_loss = 0
for i in range(10000):
	optimizer.zero_grad()
	output = model(input)
	loss = criterion(output)
	loss.backward()
	optimizer.step()
	total_loss += loss

1-d tensor의 경우 python 기본객체로 변환하여 처리할 것

image.png

total_loss = 0
for x in range(10):
	# assume loss is computed
	iter_loss = torch.randn(3,4).mean()
	iter_loss.requires_grad = True
	total_loss += iter_loss

del 명령어를 적절히 사용하기

  • 필요가 없어진 변수는 적절한 삭제가 필요
  • python의 메모리 배치 특성상 loop이 끝나도 메모리를 차지함

가능한 batch 사이즈 실험

  • 학습시 OOM이 발생했다면 batch 사이즈를 1로 해서 실험해보기

torch.no_grad() 사용하기

  • inference 시점에서는 torch.no_grad() 구문을 사용
  • backward pass로 인해 쌓이는 메모리에서 자유로움
with torch.no_grad():
	for data, target in test_loader:
		output = network(data)
		test_loss += F.nll_loss(output, target, size_average=False).item()
		pred = output.data.max(1, keepdim=True)[1]
		correct += pred.eq(target.data.view_as(pred)).sum()

예상치 못한 에러 메시지

  • OOM 말고도 유사한 에러들이 발생
  • CUDNN_STATUS_NOT_INIT 이나 device-side-assert 등
  • 해당 에러도 cuda와 관련하여 OOM의 일종으로 생각될 수 있으며, 적절한 코드 처리의 필요함

그외…

  • colab에서 너무 큰사이즈는 실행하지말 것 (linear,CNN,LSTM)
  • CNN의 대부분의 에러는 크기가 안맞아서 생기는경우 (torchsummary등으로 사이즈를 맞출것)
  • tensor의 float precision을 16bit로 줄일수도있음

© 2024. All rights reserved.

Powered by Hydejack v9.2.1