|
|
2.1. 계산 그래프 이해
---> 파이토치는 계산 그래프를 구성하고 이를 사용하여 그레이디언트 계산함.
3. 모델 파라미터를 저장하고 업데이트하기 위한 파이토치 텐서 객체
1 2 3 4 | a=torch.tensor(3.14,requires_grad=True) print(a) b=torch.tensor([1.0,2.0,3.0],requires_grad=True) print(b) | cs |
--> requires_grad 기본값은 False임. requires_grad() 메서드를 호출하여 True로 설정할 수 있음.
<글로럿 초기화로 텐서 만들기>
--> 더 실용적인 예
4. 자동 미분으로 그레이디언트 계산
--> 신경망을 최적화하려면 신경망의 가중치에 대한 비용의 그레이디언트를 계산해야함.
4.1. 훈련 가능한 변수에 대한 손실의 그레이디언트 계산
4.2. 자동 미분 이해하기
5. torch.nn 모듈을 사용하여 일반적인 아키텍처 구현하기
5.1. nn.Sequential 기반의 모델 구현하기
--> nn.Sequential에서 모델 안의 층은 순서대로 연결됨.
---> 첫번째 완전 연결 층의 출력이 첫번째 ReLU 층의 입력으로 사용됨.
---> 두번째 완전 연결 층의 출력은 두 번째 ReLU 층의 입력으로 사용됨.
5.2. 손실 함수 선택하기
--> 손실 함수 선택은 문제에 따라 다름. (ex 크로스 엔트로피 손실함수: 분류 작업에 사용)
--> 최적화 알고리즘 중에서는 SGD와 Adam이 가장 널리 사용되는 방법.
5.3. XOR 분류 문제
① 데이터셋 생성
- [-1,1) 사이의 균등 분포에서 뽑은 두개의 특성을 가진 200개의 훈련 샘플이 들어 있는 작은 데이터셋 생성
② 규칙에 따라 훈련샘플 i에 정답 레이블 부여
1 2 3 4 5 6 7 8 9 10 11 12 13 | torch.manual_seed(1) np.random.seed(1) x=np.random.uniform(low=-1,high=1,size=(200,2)) # 데이터 생성 y=np.ones(len(x)) # 레이블 생성 y[x[:,0]*x[:,1]<0]=0 n_train=100 # 훈련데이터와 검증 데이터 분리 x_train=torch.tensor(x[:n_train,:],dtype=torch.float32) y_train = torch.tensor(y[:n_train], dtype=torch.float32) x_valid = torch.tensor(x[n_train:, :], dtype=torch.float32) y_valid = torch.tensor(y[n_train:], dtype=torch.float32) | cs |
③ 모델 정의
- 층이 많을 수록 각 층에 뉴런 개수가 만흘수록 모델의 수용능력(모델이 얼마나 복잡한 함수를 근사할 수 있는지를 측정한 것)이 큼.
- 많은 파라미터를 가지고 있으면 모델이 복잡한 함수를 근사할 수 있지만 모델이 클수록 훈련하기 힘듦.
④ 손실함수와 옵티마이저 초기화
⑤ 훈련 데이터의 배치 크기가 2인 데이터 로더 생성
⑥ 훈련
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 | torch.manual_seed(1) num_epochs=200 def train(model,num_epochs,train_dl,x_valid,y_vaild): loss_hist_train=[0]*num_epochs accuracy_hist_train=[0]*num_epochs loss_hist_valid=[0]*num_epochs accuracy_hist_vaild=[0]*num_epochs for epoch in range(num_epochs): for x_batch,y_batch in train_dl: pred=model(x_batch)[:,0] loss=loss_fn(pred,y_batch) loss.backward() optimizer.step() optimizer.zero_grad() loss_hist_train[epoch]+=loss.item() is_correct=((pred>=0.5).float()==y_batch).float() accuracy_hist_train[epoch]+=is_correct.mean() loss_hist_train[epoch]/=n_train/batch_size accuracy_hist_train[epoch]/=n_train/batch_size pred=model(x_valid)[:,0] loss=loss_fn(pred,y_vaild) loss_hist_valid[epoch]=loss.item() is_correct=((pred>=0.5).float()==y_vaild).float() accuracy_hist_vaild[epoch]+=is_correct.mean() return loss_hist_train,loss_hist_valid,\ accuracy_hist_train,accuracy_hist_vaild history=train(model,num_epochs,train_dl,x_valid,y_valid) | cs |
⑦ 그래프
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | fig = plt.figure(figsize=(16, 4)) ax = fig.add_subplot(1, 2, 1) plt.plot(history[0], lw=4) plt.plot(history[1], lw=4) plt.legend(['Train loss', 'Validation loss'], fontsize=15) ax.set_xlabel('Epochs', size=15) ax = fig.add_subplot(1, 2, 2) plt.plot(history[2], lw=4) plt.plot(history[3], lw=4) plt.legend(['Train acc.', 'Validation acc.'], fontsize=15) ax.set_xlabel('Epochs', size=15) plt.show() | cs |
--> 결과에서 볼 수 있듯이 은닉층이 없는 간단한 모델은 선형결정 경계만 찾을 수 있음(XOR 문제 풀 수 없음)
=> 이로 인해 훈련 데이터셋과 검증 데이터셋의 손실이 매우 높고 분류 정확도는 매우 낮음.
<XOR 문제를 만족스럽게 해결하는 방법>
1. 은닉층을 추가하고 검증 데이터셋에서 만족스러운 결과가 나올 때까지 은닉 유닛 개수를 바꾸면서 비교해보는 것.
--> 은닉 유닛을 늘린다는 것은 층의 폭을 넓히는 것과 같음.
2. 더 많은 은닉층을 추가하여 모델의 깊이를 깊게 하는것.
---> 네트워크의 폭 대신 깊이를 깊게 하면 모델 수용 능력을 달성하는 데 필요한 파라미터 개수가 적다는 장점이 있지만 넓은 모델에 비해 깊은 모델은 그레이디언트가 폭주하거나 소멸될 수 있어 훈련하기 어렵다는 단점이 있음.
5.4. nn.Moudle로 유연성이 높은 모델 만들기
--> 복잡한 모델은 만드는 또 다른 방법은 nn.Moudle 클래스를 상속하는 것이다!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # nn.Moudle 클래스를 상속한 새로운 클래스 만들기 class MyMoudle(nn.Module): # 생성자 메서드 정의 def __init__(self): super().__init__() # 층층 l1=nn.Linear2,4) a1=nn.ReLU() l2=nn.Linear(4,4) a2=nn.ReLU() l3=nn.Linear(4,1) a3=nn.Sigmoid() l=[l1,a1,l2,a2,l3,a3] # 모든 층 nn.ModuleList 객체에 넣음 self.moudle_list=nn.ModuleList(l) # forward() 메서드를 사용하여 정방향 계산 정의 def forward(self,x): for f in self.moudle_list: x=f(x) return x | cs |
5.5. 파이토치에서 사용자 정의 층 만들기
- 파이토치에서 제공하지 않는 층을 새로 정의해야 하는 경우 nn.Module 클래스를 상속하여 새로운 클래스를 정의할 수 있음.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # nn.Module 클래스 상속 class NoisyLinear(nn.Module): # 생성자 # input_size: 변수를 만들고 초기화 할 수 있음, 변수 생성을 지연하고 다른 메서드에 변수 생성을 위임할 수 있음 def __init__(self,input_size,output_size, noise_stddev=0.1): super().__init__() w=torch.Tensor(input_size,output_size) self.w=nn.Parameter(w) b=torch.Tensor(output_size).fill_(0) self.b=nn.Parameter(b) self.noise_stddev=noise_stddev # training : 층이 훈련에 사용되는지 추론에 사용되는지 구분하는 매개변수 def forward(self,x,training=False): if training: noise=torch.normal(0.0,self.noise_stddev,x.shape) x_new=torch.add(x,noise) else: x_new=x return torch.add(torch.mm(x_new,self.w),self.b) | cs |
--> NoisyLinear 층을 다층 퍼셉트론의 첫번째 은닉층으로 사용함.
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | from torch.utils.data import DataLoader,TensorDataset from mlxtend.plotting import plot_decision_regions import torchvision.transforms as transforms import numpy as np import torch.nn as nn import matplotlib.pyplot as plt import torch np.random.seed(1) torch.manual_seed(1) x = np.random.uniform(low=-1, high=1, size=(200, 2)) y = np.ones(len(x)) y[x[:, 0] * x[:, 1]<0] = 0 n_train = 100 x_train = torch.tensor(x[:n_train, :], dtype=torch.float32) y_train = torch.tensor(y[:n_train], dtype=torch.float32) x_valid = torch.tensor(x[n_train:, :], dtype=torch.float32) y_valid = torch.tensor(y[n_train:], dtype=torch.float32) train_ds = TensorDataset(x_train, y_train) batch_size = 2 torch.manual_seed(1) train_dl = DataLoader(train_ds, batch_size, shuffle=True) # nn.Module 클래스 상속 class NoisyLinear(nn.Module): # 생성자 # input_size: 변수를 만들고 초기화 할 수 있음, 변수 생성을 지연하고 다른 메서드에 변수 생성을 위임할 수 있음 def __init__(self,input_size,output_size, noise_stddev=0.1): super().__init__() w=torch.Tensor(input_size,output_size) self.w=nn.Parameter(w) b=torch.Tensor(output_size).fill_(0) self.b=nn.Parameter(b) self.noise_stddev=noise_stddev # training : 층이 훈련에 사용되는지 추론에 사용되는지 구분하는 매개변수 def forward(self,x,training=False): if training: noise=torch.normal(0.0,self.noise_stddev,x.shape) x_new=torch.add(x,noise) else: x_new=x return torch.add(torch.mm(x_new,self.w),self.b) # MyNoisyModule 클래스 class MyNoisyModule(nn.Module): def __init__(self): super().__init__() self.l1 = NoisyLinear(2, 4, 0.07) # 첫번째 은닉층 NoisyLinear로 사용 self.a1 = nn.ReLU() self.l2 = nn.Linear(4, 4) self.a2 = nn.ReLU() self.l3 = nn.Linear(4, 1) self.a3 = nn.Sigmoid() def forward(self, x, training=False): x = self.l1(x, training) x = self.a1(x) x = self.l2(x) x = self.a2(x) x = self.l3(x) x = self.a3(x) return x def predict(self, x): x = torch.tensor(x, dtype=torch.float32) pred = self.forward(x)[:, 0] return (pred>=0.5).float() torch.manual_seed(1) model = MyNoisyModule() loss_fn = nn.BCELoss() optimizer = torch.optim.SGD(model.parameters(), lr=0.015) torch.manual_seed(1) num_epochs = 200 loss_hist_train = [0] * num_epochs accuracy_hist_train = [0] * num_epochs loss_hist_valid = [0] * num_epochs accuracy_hist_valid = [0] * num_epochs for epoch in range(num_epochs): for x_batch, y_batch in train_dl: pred = model(x_batch, True)[:, 0] # 훈련배치에 대한 예측 loss = loss_fn(pred, y_batch) loss.backward() optimizer.step() optimizer.zero_grad() loss_hist_train[epoch] += loss.item() is_correct = ((pred>=0.5).float() == y_batch).float() accuracy_hist_train[epoch] += is_correct.mean() loss_hist_train[epoch] /= n_train/batch_size accuracy_hist_train[epoch] /= n_train/batch_size pred = model(x_valid)[:, 0] loss = loss_fn(pred, y_valid) loss_hist_valid[epoch] = loss.item() is_correct = ((pred>=0.5).float() == y_valid).float() accuracy_hist_valid[epoch] += is_correct.mean() fig = plt.figure(figsize=(16, 4)) ax = fig.add_subplot(1, 3, 1) plt.plot(loss_hist_train, lw=4) plt.plot(loss_hist_valid, lw=4) plt.legend(['Train loss', 'Validation loss'], fontsize=15) ax.set_xlabel('Epochs', size=15) ax = fig.add_subplot(1, 3, 2) plt.plot(accuracy_hist_train, lw=4) plt.plot(accuracy_hist_valid, lw=4) plt.legend(['Train acc.', 'Validation acc.'], fontsize=15) ax.set_xlabel('Epochs', size=15) ax = fig.add_subplot(1, 3, 3) plot_decision_regions(X=x_valid.numpy(), y=y_valid.numpy().astype(np.int64), clf=model) ax.set_xlabel(r'$x_1$', size=15) ax.xaxis.set_label_coords(1, -0.025) ax.set_ylabel(r'$x_2$', size=15) ax.yaxis.set_label_coords(-0.025, 1) #plt.savefig('figures/13_06.png', dpi=300) plt.show() | cs |
8. 고수준 파이토치 API: 파이토치 라이트닝 소개
8.1. 파이토치 라이트닝 모델 준비하기
- LightningModule 사용 ==> pip install pytorch-lightning
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | import pytorch_lightning as pl import torch import torch.nn as nn from torchmetrics import __version__ as torchmetrics_version from pkg_resources import parse_version from torchmetrics import Accuracy class MultiLayerPerceptron(pl.LightningModule): # 생성자 def __init__(self, image_shape=(1, 28, 28), hidden_units=(32, 16)): super().__init__() # PL 속성: # 정확도 속성 추가 self.train_acc = Accuracy(task="multiclass", num_classes=10) self.valid_acc = Accuracy(task="multiclass", num_classes=10) self.test_acc = Accuracy(task="multiclass", num_classes=10) # 이전 코드와 비슷한 모델: input_size = image_shape[0] * image_shape[1] * image_shape[2] all_layers = [nn.Flatten()] for hidden_unit in hidden_units: layer = nn.Linear(input_size, hidden_unit) all_layers.append(layer) all_layers.append(nn.ReLU()) input_size = hidden_unit all_layers.append(nn.Linear(hidden_units[-1], 10)) self.model = nn.Sequential(*all_layers) # 입력데이터로 모델을 호출할 때 로짓을 반환하는 간단한 정방향 계산 구현 # 로짓: 네트워크에서 소프트맥스 층 전에 있는 마지막 완전 연결 층의 출력 def forward(self, x): x = self.model(x) return x # 이 로짓은 다음에 설명할 훈련, 검증 및 테스트 단계에 사용됨 def training_step(self, batch, batch_idx): x, y = batch logits = self(x) loss = nn.functional.cross_entropy(logits, y) preds = torch.argmax(logits, dim=1) self.train_acc.update(preds, y) self.log("train_loss", loss, prog_bar=True) return loss def on_train_epoch_end(self): self.log("train_acc", self.train_acc.compute()) self.train_acc.reset() def validation_step(self, batch, batch_idx): x, y = batch logits = self(x) loss = nn.functional.cross_entropy(logits, y) preds = torch.argmax(logits, dim=1) self.valid_acc.update(preds, y) self.log("valid_loss", loss, prog_bar=True) return loss def on_validation_epoch_end(self): self.log("valid_acc", self.valid_acc.compute(), prog_bar=True) self.valid_acc.reset() def test_step(self, batch, batch_idx): x, y = batch logits = self(x) loss = nn.functional.cross_entropy(logits, y) preds = torch.argmax(logits, dim=1) self.test_acc.update(preds, y) self.log("test_loss", loss, prog_bar=True) self.log("test_acc", self.test_acc.compute(), prog_bar=True) return loss def configure_optimizers(self): optimizer = torch.optim.Adam(self.parameters(), lr=0.001) return optimizer | cs |
8.2. 라이트닝을 위한 데이터 로더 준비하기
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | from torch.utils.data import DataLoader from torch.utils.data import random_split from torchvision.datasets import MNIST from torchvision import transforms class MnistDataModule(pl.LightningDataModule): def __init__(self, data_path='./'): super().__init__() self.data_path = data_path self.transform = transforms.Compose([transforms.ToTensor()]) # 데이터셋 다운로드와 같은 일반적인 단계 정의 def prepare_data(self): MNIST(root=self.data_path, download=True) # 훈련, 검증 및 테스트에 사용되는 데이터셋 정의 def setup(self, stage=None): # stage는 'fit', 'validate', 'test', 'predict' 중 하나입니다. mnist_all = MNIST( root=self.data_path, train=True, transform=self.transform, download=False ) self.train, self.val = random_split( mnist_all, [55000, 5000], generator=torch.Generator().manual_seed(1) ) self.test = MNIST( root=self.data_path, train=False, transform=self.transform, download=False ) def train_dataloader(self): return DataLoader(self.train, batch_size=64, num_workers=2) def val_dataloader(self): return DataLoader(self.val, batch_size=64, num_workers=2) def test_dataloader(self): return DataLoader(self.test, batch_size=64, num_workers=2) torch.manual_seed(1) mnist_dm = MnistDataModule() | cs |
8.3. 라이트닝 Trainer 클래스를 사용하여 모델 훈련하기
1 2 3 4 5 6 7 8 9 10 11 12 13 | from pytorch_lightning.callbacks import ModelCheckpoint mnistclassifier = MultiLayerPerceptron() callbacks = [ModelCheckpoint(save_top_k=1, mode='max', monitor="valid_acc")] # 가장 높은 성능의 모델 저장하기 if torch.cuda.is_available(): # GPU를 가지고 있다면 trainer = pl.Trainer(max_epochs=10, callbacks=callbacks, gpus=1) else: trainer = pl.Trainer(max_epochs=10, callbacks=callbacks) trainer.fit(model=mnistclassifier, datamodule=mnist_dm) | cs |
- 라이트닝은 Trainer 클래스를 구현하여 zero_grad(), backward(), optimizer.step() 호출과 같은 모든 중간 단계를 대신 처리함으로써 매우 편리하게 모델을 훈련할 수 있음
- 하나 이상의 GPU를 쉽게 지정할 수 있음.
8.4. 텐서보드로 모델 평가하기
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | import torch import pytorch_lightning as pl import torch.nn as nn from torchmetrics import Accuracy from torch.utils.data import DataLoader from torch.utils.data import random_split from torchvision.datasets import MNIST from torchvision import transforms from pytorch_lightning.callbacks import ModelCheckpoint class MultiLayerPerceptron(pl.LightningModule): def __init__(self, image_shape=(1, 28, 28), hidden_units=(31, 26)): super().__init__() self.train_acc = Accuracy(task="multiclass", num_classes=10) # 정확도 속성 -> 훈련 중에 정확도를 추적할 수 있음 self.valid_acc = Accuracy(task="multiclass", num_classes=10) # torchmetrics에 속함 self.test_acc = Accuracy(task="multiclass", num_classes=10) input_size = image_shape[0] * image_shape[1] * image_shape[2] all_layers = [nn.Flatten()] for hidden_unit in hidden_units: layer = nn.Linear(input_size, hidden_unit) all_layers.append(layer) all_layers.append(nn.ReLU()) input_size = hidden_unit all_layers.append(nn.Linear(hidden_units[-1], 10)) self.model = nn.Sequential(*all_layers) def forward(self, x): # 입력 데이터로 모델을 호출할 때 마지막 완전 연결층의 출력을 반환하는 계산 수행 x = self.model(x) return x def training_step(self, batch, batch_idx): x, y = batch logits = self(x) loss = nn.functional.cross_entropy(self(x), y) preds = torch.argmax(logits, dim=1) # 줄일 차원 1 self.train_acc.update(preds, y) self.log("train_loss", loss, prog_bar=True) return loss def on_train_epoch_end(self): self.log("train_acc", self.train_acc.compute()) self.train_acc.reset() def validation_step(self, batch, batch_idx): x, y = batch logits = self(x) loss = nn.functional.cross_entropy(self(x), y) preds = torch.argmax(logits, dim=1) self.valid_acc.update(preds, y) self.log("valid_loss", loss, prog_bar=True) return loss def on_validation_epoch_end(self): self.log("valid_acc", self.valid_acc.compute(), prog_bar=True) self.valid_acc.reset() def test_step(self, batch, batch_idx): x, y = batch logits = self(x) loss = nn.functional.cross_entropy(self(x), y) preds = torch.argmax(logits, dim=1) self.test_acc.update(preds, y) self.log("test_loss", loss, prog_bar=True) self.log("test_acc", self.test_acc.compute(), prog_bar=True) return loss def configure_optimizers(self): optimizer = torch.optim.Adam(self.parameters(), lr=0.001) return optimizer class MnistDataModule(pl.LightningDataModule): def __init__(self, data_path='./'): super().__init__() self.data_path = data_path self.transform = transforms.Compose([transforms.ToTensor()]) def prepare_data(self): MNIST(root=self.data_path, download=True) def setup(self, stage=None): mnist_all = MNIST( root=self.data_path, train=True, transform=self.transform, download=False ) self.train, self.val = random_split( mnist_all, [55000, 5000], generator=torch.Generator().manual_seed(1) ) self.test = MNIST( root=self.data_path, train=False, transform=self.transform, download=False ) def train_dataloader(self): return DataLoader(self.train, batch_size=64, num_workers=4) def val_dataloader(self): return DataLoader(self.val, batch_size=64, num_workers=4) def test_dataloader(self): return DataLoader(self.test, batch_size=64, num_workers=4) import torch.multiprocessing if __name__=='__main__': # 오류땜에 추가 import os log_dir = './lightning_logs' if os.path.exists(log_dir) and not os.path.isdir(log_dir): os.remove(log_dir) # 기존 파일 삭제 os.makedirs(log_dir, exist_ok=True) # 디렉토리 생성 torch.multiprocessing.freeze_support() torch.manual_seed(1) mnist_dm = MnistDataModule() mnistclassifier = MultiLayerPerceptron() callbacks = [ModelCheckpoint(save_top_k=1, mode='max', monitor="valid_acc")] if torch.cuda.is_available(): # if you have GPUs trainer = pl.Trainer(max_epochs=15, callbacks=callbacks, gpus=1) else: trainer = pl.Trainer(max_epochs=15, callbacks=callbacks) trainer.fit(model=mnistclassifier, datamodule=mnist_dm) | cs |
13장 정리문제
1) 훈련과 검증의 차이를 설명하라. 검증이 왜 필요한지 설명하고 훈련로스와 검증로스를 이용하여 과적합을 판단하는 방법을 설명하라.
- 훈련은 데이터를 이용해서 모델의 가중치와 파라미터를 최적화하는 과정이고 검증은 최적화된 파라미터를 사용해서 모델의 성능을 평가하는 과정이다.
- 검증 과정에서 모델이 새로운 데이터에 대해 얼마나 잘 일반화 할 수 있는지 판단할 수 있고 훈련 데이터에만 과도하게 적응하는 경우(과적합) 테스트 데이터에서 성능이 저하될 수 있는데 이러한 문제를 검증 과정에서 감지할 수 있기 때문에 필요하다.
- 훈련 로스와 검증로스가 함께 감소하면서 유사한 값을 유지하는 경우는 좋은학습이고, 훈련 로스는 계속 감소하지만 검증 로스가 어느 순간부터 증가하는 경우 과적합이다.
2) 신경망의 훈련예제에서 훈련/검증 손실과 정확도를 그래프로 그리는 방법을 설명하라.
- 매 에퍽마다 실시간으로 그래프를 업데이트하도록 할것 -> 훈련동안 시간의 흐름에 따라 loss의 변화를 확인가능하도록 할것
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | from torch.utils.data import DataLoader,TensorDataset from mlxtend.plotting import plot_decision_regions import torchvision.transforms as transforms import numpy as np import torch.nn as nn import matplotlib.pyplot as plt import torch np.random.seed(1) torch.manual_seed(1) x = np.random.uniform(low=-1, high=1, size=(200, 2)) y = np.ones(len(x)) y[x[:, 0] * x[:, 1]<0] = 0 n_train = 100 x_train = torch.tensor(x[:n_train, :], dtype=torch.float32) y_train = torch.tensor(y[:n_train], dtype=torch.float32) x_valid = torch.tensor(x[n_train:, :], dtype=torch.float32) y_valid = torch.tensor(y[n_train:], dtype=torch.float32) train_ds = TensorDataset(x_train, y_train) batch_size = 2 torch.manual_seed(1) train_dl = DataLoader(train_ds, batch_size, shuffle=True) # nn.Module 클래스 상속 class NoisyLinear(nn.Module): def __init__(self, input_size, output_size, noise_stddev=0.1): # 생성자에 새로운 매개변수 만들거나 초기화 가능 super().__init__() w = torch.Tensor(input_size, output_size) self.w = nn.Parameter(w) nn.init.xavier_uniform_(self.w) b = torch.Tensor(output_size).fill_(0) self.b = nn.Parameter(b) self.noise_stddev = noise_stddev def forward(self, x, training=False): # training=False -> 층이 훈련에 사용되는지 구분하는 역할 if training: noise = torch.normal(0.0, self.noise_stddev, x.shape) x_new = torch.add(x, noise) else: x_new = x return torch.add(torch.mm(x_new, self.w), self.b) class MyNoistModule(nn.Module): def __init__(self): super().__init__() self.l1 = NoisyLinear(2, 4, 0.07) self.a1 = nn.ReLU() self.l2 = nn.Linear(4, 4) self.a2 = nn.ReLU() self.l3 = nn.Linear(4, 1) self.a3 = nn.Sigmoid() def forward(self, x, training=False): x = self.l1(x, training) x = self.a1(x) x = self.l2(x) x = self.a2(x) x = self.l3(x) x = self.a3(x) return x def predict(self, x): x = torch.tensor(x, dtype=torch.float32) pred = self.forward(x)[:, 0] return (pred>=0.5).float() # 예측 클래스 0 or 1 반환 torch.manual_seed(1) model = MyNoistModule() loss_fn = nn.BCELoss() optimizer = torch.optim.SGD(model.parameters(), lr=0.015) torch.manual_seed(1) num_epochs=200 loss_hist_train = [0] * num_epochs accuracy_hist_train = [0] * num_epochs loss_hist_valid = [0] * num_epochs accuracy_hist_valid = [0] * num_epochs plt.ion() fig,(ax1,ax2)=plt.subplots(1,2,figsize=(16,4)) for epoch in range(num_epochs): for x_batch, y_batch in train_dl: pred = model(x_batch, True)[:, 0] loss = loss_fn(pred, y_batch) loss.backward() optimizer.step() optimizer.zero_grad() loss_hist_train[epoch] += loss.item() is_correct = ( (pred>=0.5).float() == y_batch ).float() accuracy_hist_train[epoch] += is_correct.mean() # 훈련 손실 및 정확도 평균 계산 loss_hist_train[epoch] /= n_train/batch_size accuracy_hist_train[epoch] /= n_train/batch_size # 검증 pred = model(x_valid)[:, 0] loss = loss_fn(pred, y_valid) loss_hist_valid[epoch] = loss.item() is_correct = ((pred>=0.5).float() == y_valid).float() accuracy_hist_valid[epoch] += is_correct.mean() # 실시간 그래프 업데이트 ax1.clear() ax1.plot(loss_hist_train, lw=2, label="Train Loss", color='blue') ax1.plot(loss_hist_valid, lw=2, label="Validation Loss", color='red') ax1.legend(fontsize=12) ax1.set_xlabel('Epochs') ax1.set_ylabel('Loss') ax1.set_title('Loss over epochs') ax2.clear() ax2.plot(accuracy_hist_train, lw=2, label="Train Accuracy", color='blue') ax2.plot(accuracy_hist_valid, lw=2, label="Validation Accuracy", color='red') ax2.legend(fontsize=12) ax2.set_xlabel('Epochs') ax2.set_ylabel('Accuracy') ax2.set_title('Accuracy over epochs') plt.pause(0.01) # 그래프 갱신 후 잠시 멈춤 plt.ioff() # 인터랙티브 모드 종료 plt.show() | cs |
3) 신경망의 훈련예제에서 훈련/검증 손실과 정확도를 텐서보드를 이용하여 그래프로 그려보라.
- 매 에퍽마다 실시간으로 그래프를 업데이트하도록 할것 -> 훈련동안 시간의 흐름에 따라 loss의 변화를 확인가능하도록 할것
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | import torch import pytorch_lightning as pl import torch.nn as nn from torchmetrics import Accuracy from torch.utils.data import DataLoader from torch.utils.data import random_split from torchvision.datasets import MNIST from torchvision import transforms from pytorch_lightning.callbacks import ModelCheckpoint class MultiLayerPerceptron(pl.LightningModule): def __init__(self, image_shape=(1, 28, 28), hidden_units=(31, 26)): super().__init__() self.train_acc = Accuracy(task="multiclass", num_classes=10) # 정확도 속성 -> 훈련 중에 정확도를 추적할 수 있음 self.valid_acc = Accuracy(task="multiclass", num_classes=10) # torchmetrics에 속함 self.test_acc = Accuracy(task="multiclass", num_classes=10) input_size = image_shape[0] * image_shape[1] * image_shape[2] all_layers = [nn.Flatten()] for hidden_unit in hidden_units: layer = nn.Linear(input_size, hidden_unit) all_layers.append(layer) all_layers.append(nn.ReLU()) input_size = hidden_unit all_layers.append(nn.Linear(hidden_units[-1], 10)) self.model = nn.Sequential(*all_layers) def forward(self, x): # 입력 데이터로 모델을 호출할 때 마지막 완전 연결층의 출력을 반환하는 계산 수행 x = self.model(x) return x def training_step(self, batch, batch_idx): x, y = batch logits = self(x) loss = nn.functional.cross_entropy(self(x), y) preds = torch.argmax(logits, dim=1) # 줄일 차원 1 self.train_acc.update(preds, y) self.log("train_loss", loss, prog_bar=True) return loss def on_train_epoch_end(self): self.log("train_acc", self.train_acc.compute()) self.train_acc.reset() def validation_step(self, batch, batch_idx): x, y = batch logits = self(x) loss = nn.functional.cross_entropy(self(x), y) preds = torch.argmax(logits, dim=1) self.valid_acc.update(preds, y) self.log("valid_loss", loss, prog_bar=True) return loss def on_validation_epoch_end(self): self.log("valid_acc", self.valid_acc.compute(), prog_bar=True) self.valid_acc.reset() def test_step(self, batch, batch_idx): x, y = batch logits = self(x) loss = nn.functional.cross_entropy(self(x), y) preds = torch.argmax(logits, dim=1) self.test_acc.update(preds, y) self.log("test_loss", loss, prog_bar=True) self.log("test_acc", self.test_acc.compute(), prog_bar=True) return loss def configure_optimizers(self): optimizer = torch.optim.Adam(self.parameters(), lr=0.001) return optimizer class MnistDataModule(pl.LightningDataModule): def __init__(self, data_path='./'): super().__init__() self.data_path = data_path self.transform = transforms.Compose([transforms.ToTensor()]) def prepare_data(self): MNIST(root=self.data_path, download=True) def setup(self, stage=None): mnist_all = MNIST( root=self.data_path, train=True, transform=self.transform, download=False ) self.train, self.val = random_split( mnist_all, [55000, 5000], generator=torch.Generator().manual_seed(1) ) self.test = MNIST( root=self.data_path, train=False, transform=self.transform, download=False ) def train_dataloader(self): return DataLoader(self.train, batch_size=64, num_workers=4) def val_dataloader(self): return DataLoader(self.val, batch_size=64, num_workers=4) def test_dataloader(self): return DataLoader(self.test, batch_size=64, num_workers=4) import torch.multiprocessing if __name__=='__main__': # 오류땜에 추가 import os log_dir = './lightning_logs' if os.path.exists(log_dir) and not os.path.isdir(log_dir): os.remove(log_dir) # 기존 파일 삭제 os.makedirs(log_dir, exist_ok=True) # 디렉토리 생성 torch.multiprocessing.freeze_support() torch.manual_seed(1) mnist_dm = MnistDataModule() mnistclassifier = MultiLayerPerceptron() callbacks = [ModelCheckpoint(save_top_k=1, mode='max', monitor="valid_acc")] if torch.cuda.is_available(): # if you have GPUs trainer = pl.Trainer(max_epochs=15, callbacks=callbacks, gpus=1) else: trainer = pl.Trainer(max_epochs=15, callbacks=callbacks) trainer.fit(model=mnistclassifier, datamodule=mnist_dm) | cs |
4) 신경망을 구현할때 사용하는 파이토치 클래스를 모두 설명하라.
- torch.nn.Module : 파이토치의 모든 신경망을 정의할 때 상속받는 기본 클래스
- torch.nn.Linear : 선형 변환을 수행하는 계층. 입력 크기와 출력 크기를 지정하여 완전 연결된 신경망을 구성할 때 사용
- torch.nn.ReLU : ReLU 활성화 함수. 음수 값은 0으로 처리하고 양수 값은 그대로 전달하는 비선형 함수
- torch.nn.Sigmoid : Sigmoid 활성화 함수. 출력값을 0과 1 사이로 압축하는 함수
- torch.nn.ModuleList : Module을 여러 개 리스트 형식으로 저장하는 클래스
- torch.nn.Sequential : 여러 Module드을 순차적으로 쌓을 때 사용하는 클래스
5) 신경망 모델의 입력텐서의 원소는 [0,1]사이로 정규화 하는 이유를 설명하라.
- 입력텐서의 원소를 [0,1]사이로 정규화 하는 이유는 입력 값의 범위가 너무 크거나 작으면 신경망의 역전파 과정에서 발생하는 그레이디언트가 너무 커지거나 작아져서 학습이 불안정해질 수 있기 때문이다.
6) 신경망 모델의 출력값의 범위에 대하여 설명하라.
- 신경망 모델의 출력값의 범위는 모델의 목적에 따라 달라진다. => 주로 문제의 종류와 활성화 함수에 의해 결정됨.
(1) 이진 분류
---> 출력값 : [0,1] 사이의 값 (시그모이드 활성화 함수가 출력값을 0과 1 사이로 변환하여 클래스 확률을 제공함)
(2) 다중 클래스 분류
---> 출력값 : [0,1] 사이의 값 (소프트맥스 활성화 함수가 각 클래스에 대한 확률을 계산하며 출력값의 합이 1이 되도록 반환함)
(3) 회귀
---> 출력값 : 실수(제한 없음) (선형 활성화 함수 사용. 출력값이 범위 제한 없이 자유롭게 변화할 수 있도록 함)
(4) 이미지 생성
---> 출력값 : 이미지 픽셀 값은 [0,1] 범위로 정규화 되거나 [-1,1] 범위로 정규화 되기도 함.