diff --git a/README.md b/README.md
index e69de29..a616462 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1,876 @@
+---
+layout: default
+title: 15. UNet
+subtitle: Deep Learning
+---
+-----
+
+[PINBlog Gitea Repository](https://gitea.pinblog.codes/CBNU/15_UNet)
+
+-----
+
+# AutoEncoder
+- 산업인공지능학과 대학원
+ 2022254026
+ 김홍열
+
+
+---
+
+
+# **의미론적 분할(Semantic Segmentation)이란?**
+
+![sementic-segmentation](./images/unet1.png)
+
+U-Net은 컴퓨터 비전 영역에서 풀려고 하는 문제(task) 중 의미론적 분할(Semantic Segmentation)을 수행할 수 있는 모델이다.
+
+의미론적 분할이란 이미지 내에서 픽셀 단위로 분류하는 것이다.
+
+즉, 각 픽셀별로 어떤 클래스에 속하는지를 예측하는 문제를 말한다.
+
+이미지 내에 객체 존재 여부를 예측하는 문제(이미지 분류; Image Classification)에 비해서 객체 경계 정보를 보존해야하고,
+
+전체적인 이미지의 문맥을 파악해야 하는 등 조금 더 높은 수준의 이미지 이해를 요구한다는 점에서 까다로운 문제에 속한다.
+
+
+
+# UNet이란?
+
+![unet](./images/unet2.png)
+
+U-Net은 이미지를 압축하는 수축 경로(contracting path)와 원본 이미지의 크기로 복원하는 확장경로(expansive path)로 구성된다.
+
+각 모듈을 인코더(Encoder), 디코더(Decoder)라고 부르고 모델의 구조가 U자 형태를 띄고 있다고 하여 U-Net으로 부른다.
+
+의미론적 분할을 수행하는 여러 모델들은 자율주행, 의생명공학 등 다양한 분야에 사용될 수 있다.
+
+U-Net은 MRI, CT 상에서 병변을 진단하거나 장기, 세포 조직 등을 분할하는 등 의료 영상(Biomedical) 분야에서 좋은 성능을 발휘하고 있고,
+
+U-Net 구조를 기반으로 한 모델들이 매년 다양한 문제를 더 잘 해결하는 모습을 보여주고 있다.
+
+paperwithcode에 따르면 U-Net이 해결하고 있는 문제의 10% 이상이 의료 분야와 관련되어 있고,
+
+의미론적 분할을 수행하는 모델 중 가장 많은 논문 수를 보유하고 있다.
+
+의미론적 분할은 기계가 수행하기 어려운 고난도의 문제임에도 불구하고 해당 영역의 최신 모델(SOTA; state of the art)들은 꽤 높은 수준에 이른 것으로 확인된다.
+
+paperwithcode에 따르면 의미론적 분할 영역에서 학습 및 테스트할 수 있는 대표적인 데이터셋인
+
+Cityscapes test/val와 PASCAL VOC 2012 test/val에 대해서 벤치마크에서 1위를 달성한 모델들이 각각 84.5/86.95%, 90.5/90.0%의 mIoU(mean Intersection of Union)를 달성하였다.
+
+
+
+# overlap-tile 이란?
+
+U-Net의 논문 제목 "U-Net: Convolutional Networks for Biomedical Image Segmentation"에서도 알 수 있듯이
+
+U-Net은 의료 영상 이미지 분할을 위해 고안된 모델이다.
+
+CT에서 혹의 위치를 찾거나(nodule detection) 배경으로부터 망막 혈관을 분할하는(retinal vessel segmentation) 등 병변을 진단하는데 도움이 줄 수 있다.
+
+일반적인 이미지와 다르게 의료공학 분야에서는 고해상도 이미지가 대부분이기 때문에 많은 연산량을 필요로 한다.
+
+고용량의 의료 이미지를 효율적으로 처리하기 위한 방안으로 overlap-tile 전략을 고안해냈다.
+
+
+![overlat-tile](./images/unet3.png)
+
+
+노란색 영역, 즉 타일을 예측하게 되면 다음 타일로 넘어가는데 필요한 영역이 이전에 예측을 위해 사용했던 영역과 겹치게(overlap) 된다.
+
+따라서 이 방법을 overlap-tile이라고 부른다.
+
+논문에서는 overlap-tile 전략은 "GPU 메모리가 한정되어 있을 때 큰 이미지들을 인공 신경망에 적용하는데 장점이 있다"고 말하고 있다.
+
+
+
+# 데이터 증폭이란?
+
+U-Net는 overlap-tile 이외에도 데이터 증폭이라는 방식을 사용하여 모델 학습을 수행합니다.
+
+의료 공학 분야에서는 훈련할 수 있는 이미지의 갯수가 적은 반면 조직의 변이나 변형이 매우 흔하기 때문에 확보한 데이터를 증폭하는 과정이 매우 중요하다고 합니다.
+
+데이터 증폭이란 확보한 이미지를 반전시키거나 회전, 뒤틀림, 이동시켜서 더 많은 양의 이미지를 확보하는 것을 의미합니다.
+
+U-Net 외에도 레이블링 비용 감소 등을 위해서 다른 모델에서도 데이터 증폭은 많이 사용되고 있습니다.
+
+
+
+# 손실함수 재정의
+
+![weight-mat-loss](./images/unet4.png)
+
+의료 이미지 분야에서 많은 세포들이 모여있는 경우, 즉 동일한 클래스가 인접해 있는 경우 분할하는데 많은 어려움을 겪는다.
+
+일반적인 세포와 배경을 구분하는 것은 쉽지만 위의 예시처럼 세포가 인접해있는 경우 각각의 인스턴스(instance)를 구분하는 것은 쉽지 않다.
+
+그래서 이 논문에서는 각 인스턴스의 경계와 경계 사이에 반드시 배경이 존재하도록 처리한다.
+
+즉 2개의 세포가 붙어있는 경우라도 둘 사이에 반드시 배경이 인식되어야하는 틈을 만들겠다는 의미이다.
+
+이를 고려하여 손실함수를 재정의 한다.
+
+weight map loss를 의미하는 항을 추가했는데 가장 가까운 세포의 경계까지의 거리와 두번째로 가까운 세포의 경계까지의 거리의 합이 최대가 되도록 하는 손실함수다.
+
+이렇게 하면 모델은 낮은 손실함수를 갖는 방향으로 학습하기 때문에 두 세포 사이의 간격을 넓히는 식, 즉 두 인스턴스 사이의 배경을 넓히는 방향으로 학습하게 된다.
+
+이렇게 하면 세포나 조직이 뭉쳐있는 경우에도 정확하게 인스턴스별로 분할이 가능하다.
+
+이런 의미에서 세포 객체들 사이에 존재하는 배경/틈에 높은 가중치가 부여된 것을 확인할 수 있다.
+
+
+
+# 참고자료
+
+ U-Net 논문, "U-Net: Convolutional Networks for Biomedical Image Segmentation(2015)" [Google Scholar]
+
+
+
+# 데이터셋
+
+![dataset](./images/unet5.png)
+
+ISBI 2012 EM Segmentation Challenge에 사용된 membrane 데이터셋
+
+왼쪽의 세포 이미지는 512x512(grayscale)이며, 오른쪽은 세포와 세포 사이의 벽(배경)을 분할한 모습이다.
+
+실제 레이블된 값은 세포는 255, 배경은 1로 지정되어 있다.
+
+```
+dataset/
+ train-volumne.tif # 훈련 이미지
+ train-labels.tif # 훈련 이미지의 분할 레이블
+ test-volumne.tif # 테스트 이미지
+```
+
+---
+
+### 예제 코드[¶]()
+
+
+Dataset
+
+
+```python
+
+## 라이브러리 불러오기
+import os
+import numpy as np
+from PIL import Image
+import matplotlib.pyplot as plt
+
+## 데이터 불러오기
+dir_data = './dataset'
+
+name_label = 'train-labels.tif'
+name_input = 'train-volume.tif'
+
+img_label = Image.open(os.path.join(dir_data, name_label))
+img_input = Image.open(os.path.join(dir_data, name_input))
+
+ny, nx = img_label.size
+nframe = img_label.n_frames
+
+## train/test/val 폴더 생성
+nframe_train = 24
+nframe_val = 3
+nframe_test = 3
+
+dir_save_train = os.path.join(dir_data, 'train')
+dir_save_val = os.path.join(dir_data, 'val')
+dir_save_test = os.path.join(dir_data, 'test')
+
+if not os.path.exists(dir_save_train):
+ os.makedirs(dir_save_train)
+
+if not os.path.exists(dir_save_val):
+ os.makedirs(dir_save_val)
+
+if not os.path.exists(dir_save_test):
+ os.makedirs(dir_save_test)
+
+## 전체 이미지 30개를 섞어줌
+id_frame = np.arange(nframe)
+np.random.shuffle(id_frame)
+
+## 선택된 train 이미지를 npy 파일로 저장
+offset_nframe = 0
+
+for i in range(nframe_train):
+ img_label.seek(id_frame[i + offset_nframe])
+ img_input.seek(id_frame[i + offset_nframe])
+
+ label_ = np.asarray(img_label)
+ input_ = np.asarray(img_input)
+
+ np.save(os.path.join(dir_save_train, 'label_%03d.npy' % i), label_)
+ np.save(os.path.join(dir_save_train, 'input_%03d.npy' % i), input_)
+
+## 선택된 val 이미지를 npy 파일로 저장
+offset_nframe = nframe_train
+
+for i in range(nframe_val):
+ img_label.seek(id_frame[i + offset_nframe])
+ img_input.seek(id_frame[i + offset_nframe])
+
+ label_ = np.asarray(img_label)
+ input_ = np.asarray(img_input)
+
+ np.save(os.path.join(dir_save_val, 'label_%03d.npy' % i), label_)
+ np.save(os.path.join(dir_save_val, 'input_%03d.npy' % i), input_)
+
+## 선택된 test 이미지를 npy 파일로 저장
+offset_nframe = nframe_train + nframe_val
+
+for i in range(nframe_test):
+ img_label.seek(id_frame[i + offset_nframe])
+ img_input.seek(id_frame[i + offset_nframe])
+
+ label_ = np.asarray(img_label)
+ input_ = np.asarray(img_input)
+
+ np.save(os.path.join(dir_save_test, 'label_%03d.npy' % i), label_)
+ np.save(os.path.join(dir_save_test, 'input_%03d.npy' % i), input_)
+
+## 이미지 시각화
+plt.subplot(122)
+plt.imshow(label_, cmap='gray')
+plt.title('label')
+
+plt.subplot(121)
+plt.imshow(input_, cmap='gray')
+plt.title('input')
+
+plt.show()
+
+
+```
+
+![output1](./images/output1.png)
+
+
+
+
+
+---
+
+### 예제 코드[¶]()
+
+
+UNet Network
+
+
+```python
+
+## 라이브러리 불러오기
+import os
+import numpy as np
+
+import torch
+import torch.nn as nn
+from torch.utils.data import DataLoader
+from torch.utils.tensorboard import SummaryWriter
+
+import matplotlib.pyplot as plt
+
+from torchvision import transforms, datasets
+
+## 네트워크 구축하기
+class UNet(nn.Module):
+ def __init__(self):
+ super(UNet, self).__init__()
+
+ # Convolution + BatchNormalization + Relu 정의하기
+ def CBR2d(in_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=True):
+ layers = []
+ layers += [nn.Conv2d(in_channels=in_channels, out_channels=out_channels,
+ kernel_size=kernel_size, stride=stride, padding=padding,
+ bias=bias)]
+ layers += [nn.BatchNorm2d(num_features=out_channels)]
+ layers += [nn.ReLU()]
+
+ cbr = nn.Sequential(*layers)
+
+ return cbr
+
+ # 수축 경로(Contracting path)
+ self.enc1_1 = CBR2d(in_channels=1, out_channels=64)
+ self.enc1_2 = CBR2d(in_channels=64, out_channels=64)
+
+ self.pool1 = nn.MaxPool2d(kernel_size=2)
+
+ self.enc2_1 = CBR2d(in_channels=64, out_channels=128)
+ self.enc2_2 = CBR2d(in_channels=128, out_channels=128)
+
+ self.pool2 = nn.MaxPool2d(kernel_size=2)
+
+ self.enc3_1 = CBR2d(in_channels=128, out_channels=256)
+ self.enc3_2 = CBR2d(in_channels=256, out_channels=256)
+
+ self.pool3 = nn.MaxPool2d(kernel_size=2)
+
+ self.enc4_1 = CBR2d(in_channels=256, out_channels=512)
+ self.enc4_2 = CBR2d(in_channels=512, out_channels=512)
+
+ self.pool4 = nn.MaxPool2d(kernel_size=2)
+
+ self.enc5_1 = CBR2d(in_channels=512, out_channels=1024)
+
+ # 확장 경로(Expansive path)
+ self.dec5_1 = CBR2d(in_channels=1024, out_channels=512)
+
+ self.unpool4 = nn.ConvTranspose2d(in_channels=512, out_channels=512,
+ kernel_size=2, stride=2, padding=0, bias=True)
+
+ self.dec4_2 = CBR2d(in_channels=2 * 512, out_channels=512)
+ self.dec4_1 = CBR2d(in_channels=512, out_channels=256)
+
+ self.unpool3 = nn.ConvTranspose2d(in_channels=256, out_channels=256,
+ kernel_size=2, stride=2, padding=0, bias=True)
+
+ self.dec3_2 = CBR2d(in_channels=2 * 256, out_channels=256)
+ self.dec3_1 = CBR2d(in_channels=256, out_channels=128)
+
+ self.unpool2 = nn.ConvTranspose2d(in_channels=128, out_channels=128,
+ kernel_size=2, stride=2, padding=0, bias=True)
+
+ self.dec2_2 = CBR2d(in_channels=2 * 128, out_channels=128)
+ self.dec2_1 = CBR2d(in_channels=128, out_channels=64)
+
+ self.unpool1 = nn.ConvTranspose2d(in_channels=64, out_channels=64,
+ kernel_size=2, stride=2, padding=0, bias=True)
+
+ self.dec1_2 = CBR2d(in_channels=2 * 64, out_channels=64)
+ self.dec1_1 = CBR2d(in_channels=64, out_channels=64)
+
+ self.fc = nn.Conv2d(in_channels=64, out_channels=1, kernel_size=1, stride=1, padding=0, bias=True)
+
+ # forward 함수 정의하기
+ def forward(self, x):
+ enc1_1 = self.enc1_1(x)
+ enc1_2 = self.enc1_2(enc1_1)
+ pool1 = self.pool1(enc1_2)
+
+ enc2_1 = self.enc2_1(pool1)
+ enc2_2 = self.enc2_2(enc2_1)
+ pool2 = self.pool2(enc2_2)
+
+ enc3_1 = self.enc3_1(pool2)
+ enc3_2 = self.enc3_2(enc3_1)
+ pool3 = self.pool3(enc3_2)
+
+ enc4_1 = self.enc4_1(pool3)
+ enc4_2 = self.enc4_2(enc4_1)
+ pool4 = self.pool4(enc4_2)
+
+ enc5_1 = self.enc5_1(pool4)
+
+ dec5_1 = self.dec5_1(enc5_1)
+
+ unpool4 = self.unpool4(dec5_1)
+ cat4 = torch.cat((unpool4, enc4_2), dim=1)
+ dec4_2 = self.dec4_2(cat4)
+ dec4_1 = self.dec4_1(dec4_2)
+
+ unpool3 = self.unpool3(dec4_1)
+ cat3 = torch.cat((unpool3, enc3_2), dim=1)
+ dec3_2 = self.dec3_2(cat3)
+ dec3_1 = self.dec3_1(dec3_2)
+
+ unpool2 = self.unpool2(dec3_1)
+ cat2 = torch.cat((unpool2, enc2_2), dim=1)
+ dec2_2 = self.dec2_2(cat2)
+ dec2_1 = self.dec2_1(dec2_2)
+
+ unpool1 = self.unpool1(dec2_1)
+ cat1 = torch.cat((unpool1, enc1_2), dim=1)
+ dec1_2 = self.dec1_2(cat1)
+ dec1_1 = self.dec1_1(dec1_2)
+
+ x = self.fc(dec1_1)
+
+ return x
+
+```
+
+
+
+
+
+Data Loader
+
+
+```python
+
+# 데이터 로더를 구현하기
+class Dataset(torch.utils.data.Dataset):
+ def __init__(self, data_dir, transform=None):
+ self.data_dir = data_dir
+ self.transform = transform
+
+ lst_data = os.listdir(self.data_dir)
+
+ lst_label = [f for f in lst_data if f.startswith('label')]
+ lst_input = [f for f in lst_data if f.startswith('input')]
+
+ lst_label.sort()
+ lst_input.sort()
+
+ self.lst_label = lst_label
+ self.lst_input = lst_input
+
+ def __len__(self):
+ return len(self.lst_label)
+
+ def __getitem__(self, index):
+ label = np.load(os.path.join(self.data_dir, self.lst_label[index]))
+ input = np.load(os.path.join(self.data_dir, self.lst_input[index]))
+
+ # 정규화
+ label = label/255.0
+ input = input/255.0
+
+ # 이미지와 레이블의 차원 = 2일 경우(채널이 없을 경우, 흑백 이미지), 새로운 채널(축) 생성
+ if label.ndim == 2:
+ label = label[:, :, np.newaxis]
+ if input.ndim == 2:
+ input = input[:, :, np.newaxis]
+
+ data = {'input': input, 'label': label}
+
+ # transform이 정의되어 있다면 transform을 거친 데이터를 불러옴
+ if self.transform:
+ data = self.transform(data)
+
+ return data
+
+
+```
+
+
+
+
+
+---
+
+### 예제 코드[¶]()
+
+
+Transform
+
+
+```python
+
+# 트렌스폼 구현하기
+class ToTensor(object):
+ def __call__(self, data):
+ label, input = data['label'], data['input']
+
+ label = label.transpose((2, 0, 1)).astype(np.float32)
+ input = input.transpose((2, 0, 1)).astype(np.float32)
+
+ data = {'label': torch.from_numpy(label), 'input': torch.from_numpy(input)}
+
+ return data
+
+class Normalization(object):
+ def __init__(self, mean=0.5, std=0.5):
+ self.mean = mean
+ self.std = std
+
+ def __call__(self, data):
+ label, input = data['label'], data['input']
+
+ input = (input - self.mean) / self.std
+
+ data = {'label': label, 'input': input}
+
+ return data
+
+class RandomFlip(object):
+ def __call__(self, data):
+ label, input = data['label'], data['input']
+
+ if np.random.rand() > 0.5:
+ label = np.fliplr(label)
+ input = np.fliplr(input)
+
+ if np.random.rand() > 0.5:
+ label = np.flipud(label)
+ input = np.flipud(input)
+
+ data = {'label': label, 'input': input}
+
+ return data
+
+
+```
+
+
+
+
+
+Model Load / Save
+
+
+```python
+
+## 네트워크 저장하기
+def save(ckpt_dir, net, optim, epoch):
+ if not os.path.exists(ckpt_dir):
+ os.makedirs(ckpt_dir)
+
+ torch.save({'net': net.state_dict(), 'optim': optim.state_dict()},
+ "%s/model_epoch%d.pth" % (ckpt_dir, epoch))
+
+## 네트워크 불러오기
+def load(ckpt_dir, net, optim):
+ if not os.path.exists(ckpt_dir):
+ epoch = 0
+ return net, optim, epoch
+
+ ckpt_lst = os.listdir(ckpt_dir)
+ ckpt_lst.sort(key=lambda f: int(''.join(filter(str.isdigit, f))))
+
+ dict_model = torch.load('%s/%s' % (ckpt_dir, ckpt_lst[-1]))
+
+ net.load_state_dict(dict_model['net'])
+ optim.load_state_dict(dict_model['optim'])
+ epoch = int(ckpt_lst[-1].split('epoch')[1].split('.pth')[0])
+
+ return net, optim, epoch
+
+
+```
+
+
+
+
+
+
+
+
+
+Train
+
+
+```python
+
+# 훈련 파라미터 설정하기
+lr = 1e-3
+batch_size = 4
+num_epoch = 20
+
+base_dir = './drive/MyDrive/DACrew/unet'
+data_dir = dir_data
+ckpt_dir = os.path.join(base_dir, "checkpoint")
+log_dir = os.path.join(base_dir, "log")
+
+
+# 훈련을 위한 Transform과 DataLoader
+transform = transforms.Compose([Normalization(mean=0.5, std=0.5), RandomFlip(), ToTensor()])
+
+dataset_train = Dataset(data_dir=os.path.join(data_dir, 'train'), transform=transform)
+loader_train = DataLoader(dataset_train, batch_size=batch_size, shuffle=True, num_workers=0)
+
+dataset_val = Dataset(data_dir=os.path.join(data_dir, 'val'), transform=transform)
+loader_val = DataLoader(dataset_val, batch_size=batch_size, shuffle=False, num_workers=0)
+
+# 네트워크 생성하기
+device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
+net = UNet().to(device)
+
+# 손실함수 정의하기
+fn_loss = nn.BCEWithLogitsLoss().to(device)
+
+# Optimizer 설정하기
+optim = torch.optim.Adam(net.parameters(), lr=lr)
+
+# 그밖에 부수적인 variables 설정하기
+num_data_train = len(dataset_train)
+num_data_val = len(dataset_val)
+
+num_batch_train = np.ceil(num_data_train / batch_size)
+num_batch_val = np.ceil(num_data_val / batch_size)
+
+# 그 밖에 부수적인 functions 설정하기
+fn_tonumpy = lambda x: x.to('cpu').detach().numpy().transpose(0, 2, 3, 1)
+fn_denorm = lambda x, mean, std: (x * std) + mean
+fn_class = lambda x: 1.0 * (x > 0.5)
+
+# Tensorboard 를 사용하기 위한 SummaryWriter 설정
+writer_train = SummaryWriter(log_dir=os.path.join(log_dir, 'train'))
+writer_val = SummaryWriter(log_dir=os.path.join(log_dir, 'val'))
+
+# 네트워크 학습시키기
+st_epoch = 0
+# 학습한 모델이 있을 경우 모델 로드하기
+net, optim, st_epoch = load(ckpt_dir=ckpt_dir, net=net, optim=optim)
+
+for epoch in range(st_epoch + 1, num_epoch + 1):
+ net.train()
+ loss_arr = []
+
+ for batch, data in enumerate(loader_train, 1):
+ # forward pass
+ label = data['label'].to(device)
+ input = data['input'].to(device)
+
+ output = net(input)
+
+ # backward pass
+ optim.zero_grad()
+
+ loss = fn_loss(output, label)
+ loss.backward()
+
+ optim.step()
+
+ # 손실함수 계산
+ loss_arr += [loss.item()]
+
+ print("TRAIN: EPOCH %04d / %04d | BATCH %04d / %04d | LOSS %.4f" %
+ (epoch, num_epoch, batch, num_batch_train, np.mean(loss_arr)))
+
+ # Tensorboard 저장하기
+ label = fn_tonumpy(label)
+ input = fn_tonumpy(fn_denorm(input, mean=0.5, std=0.5))
+ output = fn_tonumpy(fn_class(output))
+
+ writer_train.add_image('label', label, num_batch_train * (epoch - 1) + batch, dataformats='NHWC')
+ writer_train.add_image('input', input, num_batch_train * (epoch - 1) + batch, dataformats='NHWC')
+ writer_train.add_image('output', output, num_batch_train * (epoch - 1) + batch, dataformats='NHWC')
+
+ writer_train.add_scalar('loss', np.mean(loss_arr), epoch)
+
+ with torch.no_grad():
+ net.eval()
+ loss_arr = []
+
+ for batch, data in enumerate(loader_val, 1):
+ # forward pass
+ label = data['label'].to(device)
+ input = data['input'].to(device)
+
+ output = net(input)
+
+ # 손실함수 계산하기
+ loss = fn_loss(output, label)
+
+ loss_arr += [loss.item()]
+
+ print("VALID: EPOCH %04d / %04d | BATCH %04d / %04d | LOSS %.4f" %
+ (epoch, num_epoch, batch, num_batch_val, np.mean(loss_arr)))
+
+ # Tensorboard 저장하기
+ label = fn_tonumpy(label)
+ input = fn_tonumpy(fn_denorm(input, mean=0.5, std=0.5))
+ output = fn_tonumpy(fn_class(output))
+
+ writer_val.add_image('label', label, num_batch_val * (epoch - 1) + batch, dataformats='NHWC')
+ writer_val.add_image('input', input, num_batch_val * (epoch - 1) + batch, dataformats='NHWC')
+ writer_val.add_image('output', output, num_batch_val * (epoch - 1) + batch, dataformats='NHWC')
+
+ writer_val.add_scalar('loss', np.mean(loss_arr), epoch)
+
+ # epoch 50마다 모델 저장하기
+ if epoch % 50 == 0:
+ save(ckpt_dir=ckpt_dir, net=net, optim=optim, epoch=epoch)
+
+ writer_train.close()
+ writer_val.close()
+
+
+```
+
+
+
+
+
+Result
+
+
+```plaintext
+
+TRAIN: EPOCH 0001 / 0020 | BATCH 0001 / 0006 | LOSS 0.6337
+TRAIN: EPOCH 0001 / 0020 | BATCH 0002 / 0006 | LOSS 0.5923
+TRAIN: EPOCH 0001 / 0020 | BATCH 0003 / 0006 | LOSS 0.5694
+TRAIN: EPOCH 0001 / 0020 | BATCH 0004 / 0006 | LOSS 0.5456
+TRAIN: EPOCH 0001 / 0020 | BATCH 0005 / 0006 | LOSS 0.5214
+TRAIN: EPOCH 0001 / 0020 | BATCH 0006 / 0006 | LOSS 0.5036
+VALID: EPOCH 0001 / 0020 | BATCH 0001 / 0001 | LOSS 0.6128
+TRAIN: EPOCH 0002 / 0020 | BATCH 0001 / 0006 | LOSS 0.4027
+TRAIN: EPOCH 0002 / 0020 | BATCH 0002 / 0006 | LOSS 0.3897
+TRAIN: EPOCH 0002 / 0020 | BATCH 0003 / 0006 | LOSS 0.3905
+TRAIN: EPOCH 0002 / 0020 | BATCH 0004 / 0006 | LOSS 0.3856
+TRAIN: EPOCH 0002 / 0020 | BATCH 0005 / 0006 | LOSS 0.3807
+TRAIN: EPOCH 0002 / 0020 | BATCH 0006 / 0006 | LOSS 0.3745
+VALID: EPOCH 0002 / 0020 | BATCH 0001 / 0001 | LOSS 0.5134
+TRAIN: EPOCH 0003 / 0020 | BATCH 0001 / 0006 | LOSS 0.3422
+TRAIN: EPOCH 0003 / 0020 | BATCH 0002 / 0006 | LOSS 0.3350
+TRAIN: EPOCH 0003 / 0020 | BATCH 0003 / 0006 | LOSS 0.3372
+TRAIN: EPOCH 0003 / 0020 | BATCH 0004 / 0006 | LOSS 0.3337
+TRAIN: EPOCH 0003 / 0020 | BATCH 0005 / 0006 | LOSS 0.3293
+TRAIN: EPOCH 0003 / 0020 | BATCH 0006 / 0006 | LOSS 0.3286
+VALID: EPOCH 0003 / 0020 | BATCH 0001 / 0001 | LOSS 0.4308
+TRAIN: EPOCH 0004 / 0020 | BATCH 0001 / 0006 | LOSS 0.3094
+TRAIN: EPOCH 0004 / 0020 | BATCH 0002 / 0006 | LOSS 0.3079
+TRAIN: EPOCH 0004 / 0020 | BATCH 0003 / 0006 | LOSS 0.3090
+TRAIN: EPOCH 0004 / 0020 | BATCH 0004 / 0006 | LOSS 0.3078
+...
+TRAIN: EPOCH 0020 / 0020 | BATCH 0004 / 0006 | LOSS 0.2127
+TRAIN: EPOCH 0020 / 0020 | BATCH 0005 / 0006 | LOSS 0.2115
+TRAIN: EPOCH 0020 / 0020 | BATCH 0006 / 0006 | LOSS 0.2120
+VALID: EPOCH 0020 / 0020 | BATCH 0001 / 0001 | LOSS 0.2045
+
+
+```
+
+
+
+
+
+
+
+Test
+
+
+```python
+
+transform = transforms.Compose([Normalization(mean=0.5, std=0.5), ToTensor()])
+
+dataset_test = Dataset(data_dir=os.path.join(data_dir, 'test'), transform=transform)
+loader_test = DataLoader(dataset_test, batch_size=batch_size, shuffle=False, num_workers=0)
+
+# 그밖에 부수적인 variables 설정하기
+num_data_test = len(dataset_test)
+num_batch_test = np.ceil(num_data_test / batch_size)
+
+# 결과 디렉토리 생성하기
+result_dir = os.path.join(base_dir, 'result')
+if not os.path.exists(result_dir):
+ os.makedirs(os.path.join(result_dir, 'png'))
+ os.makedirs(os.path.join(result_dir, 'numpy'))
+
+
+net, optim, st_epoch = load(ckpt_dir=ckpt_dir, net=net, optim=optim)
+
+with torch.no_grad():
+ net.eval()
+ loss_arr = []
+
+ for batch, data in enumerate(loader_test, 1):
+ # forward pass
+ label = data['label'].to(device)
+ input = data['input'].to(device)
+
+ output = net(input)
+
+ # 손실함수 계산하기
+ loss = fn_loss(output, label)
+
+ loss_arr += [loss.item()]
+
+ print("TEST: BATCH %04d / %04d | LOSS %.4f" %
+ (batch, num_batch_test, np.mean(loss_arr)))
+
+ # Tensorboard 저장하기
+ label = fn_tonumpy(label)
+ input = fn_tonumpy(fn_denorm(input, mean=0.5, std=0.5))
+ output = fn_tonumpy(fn_class(output))
+
+ # 테스트 결과 저장하기
+ for j in range(label.shape[0]):
+ id = num_batch_test * (batch - 1) + j
+
+ plt.imsave(os.path.join(result_dir, 'png', 'label_%04d.png' % id), label[j].squeeze(), cmap='gray')
+ plt.imsave(os.path.join(result_dir, 'png', 'input_%04d.png' % id), input[j].squeeze(), cmap='gray')
+ plt.imsave(os.path.join(result_dir, 'png', 'output_%04d.png' % id), output[j].squeeze(), cmap='gray')
+
+ np.save(os.path.join(result_dir, 'numpy', 'label_%04d.npy' % id), label[j].squeeze())
+ np.save(os.path.join(result_dir, 'numpy', 'input_%04d.npy' % id), input[j].squeeze())
+ np.save(os.path.join(result_dir, 'numpy', 'output_%04d.npy' % id), output[j].squeeze())
+
+print("AVERAGE TEST: BATCH %04d / %04d | LOSS %.4f" %
+ (batch, num_batch_test, np.mean(loss_arr)))
+
+
+```
+
+
+
+
+
+
+
+Result
+
+
+```python
+
+##
+lst_data = os.listdir(os.path.join(result_dir, 'numpy'))
+
+lst_label = [f for f in lst_data if f.startswith('label')]
+lst_input = [f for f in lst_data if f.startswith('input')]
+lst_output = [f for f in lst_data if f.startswith('output')]
+
+lst_label.sort()
+lst_input.sort()
+lst_output.sort()
+
+##
+id = 0
+
+label = np.load(os.path.join(result_dir,"numpy", lst_label[id]))
+input = np.load(os.path.join(result_dir,"numpy", lst_input[id]))
+output = np.load(os.path.join(result_dir,"numpy", lst_output[id]))
+
+## 플롯 그리기
+plt.figure(figsize=(8,6))
+plt.subplot(131)
+plt.imshow(input, cmap='gray')
+plt.title('input')
+
+plt.subplot(132)
+plt.imshow(label, cmap='gray')
+plt.title('label')
+
+plt.subplot(133)
+plt.imshow(output, cmap='gray')
+plt.title('output')
+
+plt.show()
+
+
+```
+
+![output](./images/output5.png.jpg)
+
+
+
+
+
+
+# **AutoEncoder의 활용**
+
+* 차원 감소 ( Dimensionally Reduction)
+인코더는 입력을 선형 및 비선형의 데이터로 차원을 줄이기 위하여 히든레이어로 인코딩한다
+* 추천 엔진 (Recommendation Engines)
+* 이상탐지(Anommaly Detection)
+오토인코더는 훈련의 일부로 재구성 오류 (Reconstruction error) 를 최소화하려 한다. 따라서 재구성 손실의 크기를 통하여 이상치를 탐지할 수 있다
+* 이미지 잡음제거 (Denoising images)
+변질된 이미지가 원래 버전으로 복원이 가능하다
+* 이미지 인식 (Image Recognnition)
+겹겹이 쌓인 오토인코더는 이미지의 다른 특성을 학습하여 이미지 인식에 사용된다
+* 이미지 생성 (Image generation)
+오토인코더의 한 종류인 변형 오토인코더 (VAE, Variational Autoencoder)는 이미지 생성에 사용된다
+
+
+---
+
+### 참고[¶]()
+
+- AutoEncoder - Google
+- ChatGPT
+- [Blog](https://velog.io/@jochedda/%EB%94%A5%EB%9F%AC%EB%8B%9D-Autoencoder-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EC%A2%85%EB%A5%98)
diff --git a/unet.ipynb b/unet.ipynb
index e74d3f9..9276cc7 100644
--- a/unet.ipynb
+++ b/unet.ipynb
@@ -132,17 +132,14 @@
"\n",
"왼쪽의 세포 이미지는 512x512(grayscale)이며, 오른쪽은 세포와 세포 사이의 벽(배경)을 분할한 모습이다.\n",
"\n",
- "실제 레이블된 값은 세포는 255, 배경은 1로 지정되어 있다."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
+ "실제 레이블된 값은 세포는 255, 배경은 1로 지정되어 있다.\n",
+ "\n",
+ "```\n",
"dataset/\n",
" train-volumne.tif # 훈련 이미지\n",
" train-labels.tif # 훈련 이미지의 분할 레이블\n",
- " test-volumne.tif # 테스트 이미지"
+ " test-volumne.tif # 테스트 이미지\n",
+ "```"
]
},
{