우리가 다룰 Dataset은 CelebA (Celebrity Attributes) Dataset이다. 이 데이터 셋은 10,000명 이상의 유명인사 얼굴 이미지 200,000장 이상을 가지고 있다.
CV작업을 위한 연구 및 실험에 자주 사용된다. 데이터 셋의 사용은 연구목적으로 제한되며, 상업적 사용은 불가하다.
다양한 포즈와 배경이 포함된 얼굴 이미지가 많아, 성능을 평가하기에 적합하다.
이렇게 생겼다. 데이터 불러오는 코드는 생략하겠다.
image.shape은 (218,178,3)이다. 각각 h = 218px, w = 178px, RGB를 담기 위한 3개의 layer이다.
# dataset class
class CelebADataset(Dataset):
def __init__(self, file):
self.file_object = h5py.File(file, 'r')
self.dataset = self.file_object['img_align_celeba']
pass
def __len__(self):
return len(self.dataset)
def __getitem__(self, index):
if (index >= len(self.dataset)):
raise IndexError()
img = numpy.array(self.dataset[str(index)+'.jpg'])
return torch.cuda.FloatTensor(img) / 255.0
def plot_image(self, index):
plt.imshow(numpy.array(self.dataset[str(index)+'.jpg']), interpolation='nearest')
pass
pass
CelebA 데이터셋을 불러오고 처리하기 위한 CelebADataset 클래스를 정의한다. 이 클래스는 PyTorch의 Dataset 추상 클래스를 상속받아 구현되며, 이미지 데이터를 불러오고 전처리하는 기능을 제공한다. 아래는 클래스의 주요 메서드들에 대한 설명이다:
다 비슷비슷해서 이것만 보자.
self.model = nn.Sequential(
View(218*178*3),
nn.Linear(3*218*178, 100),
nn.LeakyReLU(),
nn.LayerNorm(100),
nn.Linear(100, 1),
nn.Sigmoid()
100개의 노드를 연결하는 Linear Layer 하나 있다.
LeakyReLu ActivFunc사용
정규화
마지막 sigmoid.
View(2181783)은 218 x 178 x 3 텐서를 116,412의 1차원 tensor로 바꾸고 FClayer로 드간다.
나머지는 전 과정과 똑같다.
# discriminator class
class Discriminator(nn.Module):
def __init__(self):
# initialise parent pytorch class
super().__init__()
# define neural network layers
self.model = nn.Sequential(
View(218*178*3),
nn.Linear(3*218*178, 100),
nn.LeakyReLU(),
nn.LayerNorm(100),
nn.Linear(100, 1),
nn.Sigmoid()
)
# create loss function
self.loss_function = nn.BCELoss()
# create optimiser, simple stochastic gradient descent
self.optimiser = torch.optim.Adam(self.parameters(), lr=0.0001)
# counter and accumulator for progress
self.counter = 0;
self.progress = []
pass
def forward(self, inputs):
# simply run model
return self.model(inputs)
def train(self, inputs, targets):
# calculate the output of the network
outputs = self.forward(inputs)
# calculate loss
loss = self.loss_function(outputs, targets)
# increase counter and accumulate error every 10
self.counter += 1;
if (self.counter % 10 == 0):
self.progress.append(loss.item())
pass
if (self.counter % 1000 == 0):
print("counter = ", self.counter)
pass
# zero gradients, perform a backward pass, update weights
self.optimiser.zero_grad()
loss.backward()
self.optimiser.step()
pass
def plot_progress(self):
df = pandas.DataFrame(self.progress, columns=['loss'])
df.plot(ylim=(0), figsize=(16,8), alpha=0.1, marker='.', grid=True, yticks=(0, 0.25, 0.5, 1.0, 5.0))
pass
pass
D와 반대임.
이하 생략.
# generator class
class Generator(nn.Module):
def __init__(self):
# initialise parent pytorch class
super().__init__()
# define neural network layers
self.model = nn.Sequential(
nn.Linear(100, 3*10*10),
nn.LeakyReLU(),
nn.LayerNorm(3*10*10),
nn.Linear(3*10*10, 3*218*178),
nn.Sigmoid(),
View((218,178,3))
)
# create optimiser, simple stochastic gradient descent
self.optimiser = torch.optim.Adam(self.parameters(), lr=0.0001)
# counter and accumulator for progress
self.counter = 0;
self.progress = []
pass
def forward(self, inputs):
# simply run model
return self.model(inputs)
def train(self, D, inputs, targets):
# calculate the output of the network
g_output = self.forward(inputs)
# pass onto Discriminator
d_output = D.forward(g_output)
# calculate error
loss = D.loss_function(d_output, targets)
# increase counter and accumulate error every 10
self.counter += 1;
if (self.counter % 10 == 0):
self.progress.append(loss.item())
pass
# zero gradients, perform a backward pass, update weights
self.optimiser.zero_grad()
loss.backward()
self.optimiser.step()
pass
def plot_progress(self):
df = pandas.DataFrame(self.progress, columns=['loss'])
df.plot(ylim=(0), figsize=(16,8), alpha=0.1, marker='.', grid=True, yticks=(0, 0.25, 0.5, 1.0, 5.0))
pass
pass
마지막 view()는, 1차원을 3차원으로 바꿈
%%time
# create Discriminator and Generator
D = Discriminator()
D.to(device)
G = Generator()
G.to(device)
epochs = 1
for epoch in range(epochs):
print ("epoch = ", epoch + 1)
# train Discriminator and Generator
for image_data_tensor in celeba_dataset:
# train discriminator on true
D.train(image_data_tensor, torch.cuda.FloatTensor([1.0]))
# train discriminator on false
# use detach() so gradients in G are not calculated
D.train(G.forward(generate_random_seed(100)).detach(), torch.cuda.FloatTensor([0.0]))
# train generator
G.train(D, generate_random_seed(100), torch.cuda.FloatTensor([1.0]))
pass
pass
판별자는 진짜 이미지(image_data_tensor)에 대해 "진짜"라고 판단하도록 학습된다(torch.cuda.FloatTensor([1.0])는 진짜 레이블을 의미한다).
생성자가 생성한 가짜 이미지에 대해 판별자를 학습시킨다. .detach() 메서드는 생성자의 가중치에 대한 그래디언트 계산이 이루어지지 않도록 한다(즉, 판별자 학습 시에만 가중치가 업데이트된다).
torch.cuda.FloatTensor([0.0])는 가짜 레이블을 의미한다.
생성자는 판별자를 속이도록 학습된다. 생성자는 판별자가 생성자의 출력을 "진짜"로 분류하도록 유도하는 방향으로 가중치를 업데이트한다.
G.train() 메서드는 생성된 이미지를 판별자에게 제공하고, 판별자의 반응을 기반으로 생성자의 손실을 계산하여 업데이트한다. "진짜" 레이블(torch.cuda.FloatTensor([1.0]))을 사용한다.
깔꼼하다. 적당한 값으로 수렴하고 있다.
ㄱㅊㄱㅊ 0.69 = ln(2)에 수렴하는 아주 좋은 현상
결과물!
good
에폭 좀 더 돌리면 더 깔끔해질듯
RGB의 shape은 (H,W,3)이다.
HDF는 계층적 구조라 효율적이다.
GAN은 훈련데이터를 기억하지 않는다. 확률분포를 파악하고 이를 재현한 데이터를 생성하기 위해 노력한다.