[파이토치] Unsupervised Learning
오토인코더(Autoencoder)
오토인코더는 비지도방식으로 훈련된 인공신경망으로, 먼저 데이터에 인코딩 된 표현을 학습한 다음, 학습된 인코딩 표현에서 입력데이터를 가능한 가깝게 생성하는 것을 목표로 한다. 즉, 입력을 저차원 잠재 공간으로 인코딩한 후 디코딩하여 복원하는 네트워크이다.

다음은 오토 인코더의 구현 코드 입니다. Autoencoder의 클래스의 self.encoder는 인코더 모델을 나타내고, self.decoder는 디코더 모델을 나타냅니다.
class Flatten(torch.nn.Module): # 4D -> 2D로 계산하기
def forward(self, x):
batch_size = x.shape[0]
return x.view(batch_size, -1) # (배치 수, 채널 수, 이미지 너비, 이미지 높이) -> (배치 수, 채널 수*이미지 너비*이미지 높이)
class Deflatten(nn.Module): # 2D -> 4D로 계산하기
def __init__(self, k):
super(Deflatten, self).__init__()
self.k = k
def forward(self, x):
s = x.size()
# 벡터 사이즈 = 채널 수*이미지 너비*이미지 높이
# 벡터 사이즈 = 채널 수*이미지 사이즈**2
# 이미지 사이즈 = (벡터 사이즈//채널 수)**.5
feature_size = int((s[1]//self.k)**.5)
return x.view(s[0],self.k,feature_size,feature_size) # (배치 수, 채널 수*이미지 너비*이미지 높이) -> (배치 수, 채널 수, 이미지 너비, 이미지 높이)
class Autoencoder(nn.Module):
def __init__(self):
super(Autoencoder, self).__init__()
k = 16
self.encoder = nn.Sequential(
nn.Conv2d(1, k, 3, stride=2),
nn.ReLU(),
nn.Conv2d(k, 2*k, 3, stride=2),
nn.ReLU(),
nn.Conv2d(2*k, 4*k, 3, stride=1),
nn.ReLU(),
Flatten(),
nn.Linear(1024, 10),
nn.ReLU()
)
# ConvTranspose2d
# 입력 성분(Conv의 결과)을 출력 성분(Conv의 입력)으로 미분하여 그 값을 입력 벡터와 곱해 출력 벡터를 산출한다.
# 출력 된 벡터는 행렬 형태로 변환한다.
self.decoder = nn.Sequential(
nn.Linear(10, 1024),
nn.ReLU(),
Deflatten(4*k),
nn.ConvTranspose2d(4*k, 2*k, 3, stride=1), # (입력 채널 수, 출력 채널 수, 필터 크기, stride)
nn.ReLU(),
nn.ConvTranspose2d(2*k, k, 3, stride=2),
nn.ReLU(),
nn.ConvTranspose2d(k, 1, 3, stride=2,output_padding=1),
nn.Sigmoid()
)
def forward(self, x):
encoded = self.encoder(x)
decoded = self.decoder(encoded)
return decoded
적대적 생성 신경망(GAN)
적대적 생성 신경망은 오토인코더와는 다른 방법으로 잠재 공간을 학습합니다. GAN을 직관적으로 이해하는 방법은 가짜 피카소 그림을 만드는 위조범을 생각하는 것입니다. 처음에 위조범은 형편없이 위조합니다. 진짜 피카소 그림과 위조품을 섞어서 그림 판매상에게 보여 줍니다. 판매상은 각 그림이 진짜인지 평가하고 어떤 것이 피카소 그림 같은지 위조범에게 피드백을 줍니다. 시간이 지남에 따라, 위조범은 피카소의 스타일을 모방하는 데 점점 더 능숙해질 것이고, 그림 판매상은 위조품을 구분하는데 더 전문가가 될 것입니다. 결국 진짜와 분류하기 어려운 훌륭한 피카소 위조품을 만들게 될것입니다. GAN에는 이와 같이 2개의 네트워크(generator network와 discriminator network)가 존재하고, 생성자 네트워크는 판별자 네트워크를 속이도록 훈련합니다. GAN은 최적화와 최솟값이 고정되지 않은 시스템으로, 최적화 과정이 최솟값을 찾는 것이 아니라 두힘 간의 평혐점을 찾는 다이나믹 시스템입니다. 이러한 이유로 GAN은 훈련하기 위해서는 모델 구조와 훈련 파라미터를 주의 깊게 많이 조정해야 합니다.

다음은 적대적 생성 신경망의 구현 코드 입니다. 해당 모델은 Vanila GAN으로 구현을 하였지만, Linear layer를 Convolutional Layer로 변환할 경우 DC-GAN으로 변형하여 적용 가능합니다. 해당 모델을 보면 ReLU함수를 사용하지 않고, LeakyReLU로 변경하여 선언하였는데, 이는 음수값은 미분했을경우 전부 0으로 나오기 때문에 dying-ReLU 현상이 발생할 수 있기 때문이다. LeakyReLU(0.2)의 경우 y=0.2x의 활성화 함수를 음수 범위에서 취한다는 뜻이다.
class Generator(nn.Module):
def __init__(self):
super(Generator, self).__init__()
self.n_features = 128
self.n_out = 784
self.linear = nn.Sequential(
nn.Linear(self.n_features, 256),
nn.LeakyReLU(0.2),
nn.Linear(256, 512),
nn.LeakyReLU(0.2),
nn.Linear(512, 1024),
nn.LeakyReLU(0.2),
nn.Linear(1024, self.n_out),
nn.Tanh()
)
def forward(self, x):
x = self.linear(x)
x = x.view(-1, 1, 28, 28)
return x
class Discriminator(nn.Module):
def __init__(self):
super(Discriminator, self).__init__()
self.n_in = 784
self.n_out = 1
self.linear = nn.Sequential(
nn.Linear(self.n_in, 1024),
nn.LeakyReLU(0.2),
nn.Dropout(0.3),
nn.Linear(1024, 512),
nn.LeakyReLU(0.2),
nn.Dropout(0.3),
nn.Linear(512, 256),
nn.LeakyReLU(0.2),
nn.Dropout(0.3),
nn.Linear(256, self.n_out),
nn.Sigmoid()
)
def forward(self, x):
x = x.view(-1, 784)
x = self.linear(x)
return x
적대적 생성 신경망에서는 두가지 손실함수를 정의 함과 동시에 최종적으로는 binary cross entropy를 사용한다. 학습 전략을 구현한 코드는 다음과 같다.
# 손실함수 및 최적화 방법 정의
generator = Generator().to(device)
discriminator = Discriminator().to(device)
g_optim = optim.Adam(generator.parameters(), lr=2e-4)
d_optim = optim.Adam(discriminator.parameters(), lr=2e-4)
g_losses = []
d_losses = []
images = []
criterion = nn.BCELoss()
def noise(n, n_features=128):
return Variable(torch.randn(n, n_features)).to(device)
def label_ones(size):
data = Variable(torch.ones(size, 1))
return data.to(device)
def label_zeros(size):
data = Variable(torch.zeros(size, 1))
return data.to(device)
# 학습 전략 정의
def train_discriminator(optimizer, real_data, fake_data):
n = real_data.size(0)
optimizer.zero_grad()
prediction_real = discriminator(real_data)
d_loss = criterion(prediction_real, label_ones(n))
prediction_fake = discriminator(fake_data)
g_loss = criterion(prediction_fake, label_zeros(n))
loss = d_loss + g_loss
loss.backward()
optimizer.step()
return loss.item()
def train_generator(optimizer, fake_data):
n = fake_data.size(0)
optimizer.zero_grad()
prediction = discriminator(fake_data)
# loss가 작다는 것은 discriminator가 구별을 잘못한다는 뜻
loss = criterion(prediction, label_ones(n))
loss.backward()
optimizer.step()
return loss.item()