[ML Practice] CycleGAN

김현우
None
Published in
13 min readNov 22, 2020

휴먼스케이프 Software engineer Covy입니다.

이전 포스트에서는 CycleGAN에 대한 논문리뷰를 진행했습니다. 본 포스트에서는 CycleGAN에서 제시한 네트워크와 학습 기법을 활용해 실습을 구현해보는 시간을 가지려고 합니다. 이전 포스트가 궁금하신 분들은 이곳을 참고하시면 좋습니다. 실습을 진행할 환경은 pyTorch 1.6.0입니다.

Objective

본 포스트에서는 PyTorch를 이용해 CycleGAN 논문에 적혀있는 네트워크 구조와 학습 기법을 활용해서 실습을 구현하는 것을 목적으로 합니다. 더불어 cpu를 이용한 저번 실습과 다르게 google colaboratory 환경에서의 실습을 구현하는 방법에 대해서 소개드리려고 합니다. 본 포스트에서 설정한 목표는 말과 얼룩말의 1:1 변환 관계를 학습하는 것이며, 이를 위한 dataset인 말과 얼룩말의 이미지들은 이곳에서 얻을 수 있었습니다.

Custom Dataset

CycleGAN에서는 이전 학습과는 달리 학습에 필요한 dataset의 종류가 두 가지입니다. Dataset X에서 dataset Y로의 학습 뿐만 아니라 dataset Y에서 dataset X로의 학습도 필요하기 때문입니다. 본 포스트에서 사용한 예시로 들자면, 얼룩말 dataset과 말 dataset 두 가지 종류가 필요한 것입니다. 이 때문에 dataLoader를 사용해서 image dataset을 불러올 때, 두 가지 dataset 모두를 가져올 수 있도록 custom dataset을 설계해야 합니다.

제가 사용한 방식은 위와 같습니다.
ToTensor 함수의 경우, 3차원 데이터의 저장 형태가 pyTorch tensor와 matplotlib image가 서로 다르기 때문에 matplotlib로 불러온 이미지를 pyTorch tensor에 맞도록 변형시켜 주고, 연산 수를 줄이기 위해 data를 float32 형태로 지정해주는 역할을 합니다.
CustomDataset의 경우, torch.utils.data.Dataset을 상속받아 정의해 줄 수 있는데, 가장 기본적으로 위와 같이 init, len, getitem을 정의해야 합니다. init에는 데이터 전처리, len에는 데이터의 개수 반환, getitem에는 i 번째 데이터 반환을 위한 로직이 들어가게 됩니다. 저는 X, Y 데이터를 모두 반환하기 위해서 dictionary 형태를 사용했습니다.
부가적으로 설명 드릴 것은 흑백 데이터에 대한 핸들링을 위해 dimension을 확인하여 새로운 dimension를 추가하는 로직을 더했고, datatype이 unit8 형태인 경우에 255.0으로 나누어주어 input 범위를 0에서 1사이로 맞추어주는 작업을 진행했습니다.

Layer & Model

다음으로 진행한 작업이 layer와 이를 이용한 model을 설계하는 작업입니다. 이는 이전 pix2pix 때와 크게 다르지 않으나 CycleGAN에서 특별히 다르게 적용되는 부분을 포함한 layer에 대해서 짚고 넘어가려고 합니다.

위는 논문에 나와 있는 residual block에 대한 구현을 진행한 것입니다. 가장 먼저 특징적으로 살펴보실 것은 논문에서 padding이 reflection padding의 형태로 구현되어있다는 점입니다. 이 때 convolution layer의 parameter로 padding_mode를 사용해서 ‘reflect’를 설정해주시면 됩니다.
더불어 제가 처음에 크게 실수를 했던 부분이 residual block에 대한 생각 없이 forward propagation 부분을 적은 것인데, 처음에 forward 함수에 대한 반환을 self.residual(x)로만 진행 했었습니다. 하지만, 제가 이전에 작성한 ResNet 논문리뷰에서도 언급했듯이, residual block은 깊은 네트워크 구조에서도 gradient descent가 잘 반영되도록 linear한 term인 x를 더해주는 형태를 가지고 있습니다. 때문에, 위와 같이 x + self.residual(x) 의 형태로 설계를 진행해주어야 합니다.

위에서 설명드린 layer말고도, 논문에서 사용하는 layer를 일반화해서 구현해주시면 됩니다. pix2pix 실습에서 사용한 방법을 비슷하게 따라가시면 되니, 이 부분에 대한 설명을 생략하도록 하겠습니다.

위는 layer들을 사용해 구현한 model입니다. 논문에 적혀있는대로 순차적으로 layer를 선언하여 forward propagation에서 사용했습니다. 여기서 한 가지 pix2pix와 다른 점이 있다면 pix2pix에서는 마지막 layer에 activation function을 포함하지 않고 forward propagation에서 설정해주었는데 여기서는 마지막 layer에 activation function을 포함한 형태로 설계를 진행했습니다.
(p.s 같은 residual block인 layer가 많은데 루프로 묶어서 배열에 넣고, layer에서 진행했던 것 처럼 nn.Sequential()로 private 변수에 할당할 수도 있습니다. 저는 여러가지 시행착오를 거치느라 rough하게 짜두었습니다.)

Training

다음으로 training을 위한 과정을 소개해 드리려고 합니다.

가장 먼저 진행한 것은 네트워크를 정의하고 초기화하는 부분입니다. 이 부분은 기존의 pix2pix 구현과 다른 부분이 없고 구현한 model을 가지고 정의하는 부분이기 때문에 크게 다르게 소개해드리지 않도록 하겠습니다.

다음으로 cost function과 optimizer 설정 부분에 대해서 설명드리도록 하겠습니다. Cost function은 논문에서 주어진 바와 같게 크게 3가지로 나누어 집니다. 가장 먼저 일반적인 ganLoss(생성한 이미지를 discriminator가 얼마나 잘 구별하는가)를 들 수 있고, 다음으로 cycle consistency loss(정변환과 역변환을 거쳐서 원래 이미지와 얼마나 다른가)를 들 수 있고, 마지막으로 논문에서는 특정 부분에서만 언급된(없어도 크게 문제는 없지만 있으면 도움이 되기 때문) identitiy loss(변환된 이미지를 정변환하거나, 변환되기 전 이미지를 역변환했을 때, 기존 이미지와 얼마나 다른가)를 들 수 있습니다.

다음으로 optimizer는 위와 같이 Adam optimizer를 사용한다고 되어 있는 내용을 바탕으로 구현했습니다. optimG같은 경우에는 netG와 netF를 itertools.chain()으로 묶어서 parameter list를 합쳐서 정의했습니다. 사실 이 부분은 밑의 discriminator처럼 따로 정의해도 상관은 없으나, 두 가지 방법을 해보기 위해서 저는 위와 같이 설계해 보았습니다. 이와 같이 하실 경우 loss를 나중에 정의할 때도 G와 F에서 발생한 loss를 합쳐서 정의해주어야 합니다.

위는 본격적인 training 부분입니다. 코드 앞부분의 이중 루프의 형태는 일반적인 dataLoader를 이용한 training이기 때문에 설명을 생략하도록 하겠습니다.

먼저 주석으로 forward path라고 적혀진 부분을 살펴봅시다. 이 부분에서는 새로운 데이터를 dataLoader에서 꺼낸 후에 현재 학습된 network의 상태를 바탕으로 network의 forward path를 지난 결과들을 저장하게 됩니다. 저는 X를 말의 image, Y를 얼룩말의 image로 생각하여 구현했고, fakeY의 경우 generator가 거짓으로 생성한 얼룩말 이미지로 해석해주시면 되고, reconX의 경우 generator가 거짓으로 생성한 얼룩말 이미지를 다시 역 generator에 통과시켜 거짓으로 생성한 말 이미지라고 해석해주시면 되겠습니다.

다음으로 backward generator path를 살펴봅시다. 이 부분에서는 generator가 loss에 기여하는 부분들을 모아서 back propagation을 진행하는 것으로 보시면 됩니다. 먼저 X 에서 Y로의 변환에서 나타나는 ganLoss, 그리고 Y에서 X로의 변환에서 나타나는 ganLoss에서 generator가 기여하는 loss term이 존재합니다. “얼마나 generator가 생성한 이미지를 진실이라고 잘 속이는지" 에 대한 항목입니다. 이에 따라 ganLoss에 비교 tensor로 ones_like를 사용하여 진실이라고 속인것과 비교한 loss를 더해줍니다. 나머지는 L1 loss로 target image와 얼마나 비슷한지의 척도이기 때문에 cycle consistency loss의 경우 한 cycle을 돌아 왔을 때 얼마나 비슷한 이미지가 형성되는지, 그리고 identity loss의 경우 변환 전 이미지를 역변환 시켰을 때 얼마나 처음 이미지와 비슷한 이미지가 형성되는지를 보는 term을 추가하여 loss를 계산하게 됩니다. 물론 이 모든 과정은 X 에서 Y 뿐만 아니라 Y에서 X의 term도 고려해 주어야 합니다.
이후 이전의 변화량을 0으로 초기화 시킨 후에 back propagation을 통해서 각 network의 parameter에 grad term을 갱신시킨 후, optimizer.step()을 이용해 갱신된 grad와 learning rate만큼을 변화시킵니다.

다음으로 backward discriminator path를 살펴봅시다. 이 부분에서는 discriminator가 loss에 기여하는 부분들을 모아서 back propagation을 진행하는 것으로 보시면 됩니다. 여기서는 ganLoss term만 고려해주면 되는데 이는 “얼마나 generator가 생성한 이미지를 가짜라고 잘 구별해내는지"와 “얼마나 real image를 진짜라고 잘 구별해내는지"에 대한 항목입니다. 이에 따라 ganLoss에 비교 tensor로 각각 zeros_like과 ones_like를 넣어준 것을 볼 수 있습니다. 마찬가지 방법을 활용해서 전의 변화량을 0으로 초기화 시킨 후에 back propagation을 통해서 각 network의 parameter에 grad term을 갱신시킨 후, optimizer.step()을 이용해 갱신된 grad와 learning rate만큼을 변화시키면 학습을 진행할 수 있습니다.

이후, pix2pix 실습에서 사용한 방법과 같이 학습 이미지를 중간에 저장해주었습니다.

Using Google Colaboratory

학습을 진행할 때, 이번에는 google colaboratory를 이용하여 진행해보았습니다. 그 진행 방법에 대해서 간단히 소개드리려고 합니다.

먼저 google drive에 들어가시게 되면 좌측 상단에 새로 만들기가 보이실 텐데, 이를 클릭하시면 폴더 업로드가 나타납니다.

위 상태에서 폴더 업로드를 클릭해서 작업한 프로젝트를 올리시면 작업한 환경을 열 수 있습니다.

다음으로 프로젝트의 빈 부분에서 우클릭 > 더보기 > Google Colaboratory를 통해서 빈 google colaboratory 파일을 선택할 수 있습니다.

위는 google colaboratory가 열린 모습입니다. 이후, 좌측 상단에 수정 버튼을 눌러서 하드웨어 가속기를 GPU로 변경하는 작업을 진행해야 합니다.

수정 버튼을 누르시면, 노트 설정이 뜨는데 이를 클릭해서 하드웨어 가속기를 변경하실 수 있습니다.

마지막으로, 좌측의 파일 모양을 클릭해서 파일을 연 이후에 구글 드라이브 모양이 켜져있는 폴더 모양을 클릭하시게 되면 위와 같은 글귀가 뜨는데 GOOGLE 드라이브에 연결을 클릭하시게 되면 아래와 같이 마운트가 가능하게 됩니다.

이제 드라이브에서 아까 올렸던 파일의 경로를 찾아 작업을 진행하시면 됩니다.

이 때 주의하실 점이, google colaboratory는 사용자들 중에서 작업이 진행되는 사용자 위주로 런타임을 제공하기 때문에, 일정 시간 google colaboratory 내에 변화가 없다면 런타임이 종료되어 버립니다.

이를 해결하기 위해서 개발자 도구에서 console에 다음과 같은 명령어를 입력해주시면 일정 간격으로 세션을 생성하면서 런타임이 종료되는 것을 방지할 수 있습니다.

function ClickConnect(){
console.log("Working");
document.querySelector("colab-toolbar-button").click()
}setInterval(ClickConnect, 1800000)

Result

다음은 제가 30 epoch 까지 실행한 결과입니다.

먼저 30epoch 까지 실행해서 변환이 완전하지 않았다는 점을 말씀드립니다. Google Colaboratory를 사용하니 12시간 이후에 확정적으로 런타임이 종료되어 버려서 학습을 진행할 수 없는 단점이 있어 학습을 길게 진행하지 못했습니다. 그럼에도 어느정도 변환이 성공적으로 일어난 점을 확인할 수 있었습니다.

Conclusion

이것으로 간단하게 CycleGAN 논문에서 제시한 network를 가지고 training을 진행하고 결과를 확인해보는 시간을 가졌습니다. 저번에 cpu를 가지고 pix2pix를 training을 할 때는 일주일 넘게 걸리더라고 학습을 오래 진행시킬 수 있었는데, google colaboratory를 사용하니 학습이 빠르지만 오랜 시간 동안 지속할 수 없어서 아쉬웠습니다. 다음에 학습을 진행할 때는 google cloud platform을 이용해서 학습을 진행시키는 방안에 대해서도 고려해보아야 할 것 같습니다.

Get to know us better!
Join our official channels below.

Telegram(EN) : t.me/Humanscape
KakaoTalk(KR) : open.kakao.com/o/gqbUQEM
Website : humanscape.io
Medium : medium.com/humanscape-ico
Facebook : www.facebook.com/humanscape
Twitter : twitter.com/Humanscape_io
Reddit : https://www.reddit.com/r/Humanscape_official
Bitcointalk announcement : https://bit.ly/2rVsP4T
Email : support@humanscape.io

--

--