카테고리 보관물: PyTorch

PyTorch 0.2.0 Release

파이토치PyTorch 0.2.0 버전이 드디어 릴리즈 되었습니다. 3개월이 넘게 걸렸지만 ICML 2017에 때를 맞추었네요.^^ 넘파이NumPy 스타일의 브로드캐스팅broadcasting, 분산처리, 강화된 배열 인덱싱, 2계 도함수 그래디언트second order gradient 등 많은 기능이 포함되었습니다. 추가된 기능에 대한 자세한 설명은 릴리즈 노트를 참고하세요. conda를 사용하면 비교적 편리하게 파이토치를 설치할 수 있습니다.

# Linux CUDA 7.5, macOS for Python 2.7/3.5/3.6
$ conda install pytorch torchvision -c soumith
# Linux CUDA 8.0
$ conda install pytorch torchvision cuda80 -c soumith

TF 성능 팁: Winograd 알고리즘 설정

텐서플로우tensorflow에서 훈련 성능을 높일 수 있는 위노그라드Winograd 콘볼루션 알고리즘이 디폴트로 활성화되어 있지 않습니다. ResNet을 비롯하여 요즘 규모의 콘볼루션 모델들에서 3×3 필터가 대세로 자리잡고 있습니다. 위노그라드 알고리즘은 3×3 행렬 계산의 연산 횟수를 줄여 줌으로써 모델 훈련 성능이 크게 증가됩니다. 이미 NVIDIA는 cuDNN5에서 부터 위노그라드 콘볼루션을 지원하고 있습니다. 일례로 Wide ResNet 에서 CIFAR10 데이터로 훈련시킬 때 K80 GPU에서 35%정도 빨라졌다고 합니다. 하지만 성능 개선의 정도는 GPU에 따라 다릅니다. 이 설정을 하려면 쉘이나 파이썬에서 아래와 같이 환경변수 TF_ENABLE_WINOGRAD_NONFUSED 를 지정해야 합니다.

os.environ['TF_ENABLE_WINOGRAD_NONFUSED'] = '1'
$export TF_ENABLE_WINOGRAD_NONFUSED=1

위노그라드 알고리즘의 설정이 파이토치PyTorch와의 성능 차이 요인 중 하나로 지적되면서 조만간 기본으로 사용하도록 설정될 것 같습니다. 일전에 소개해 드린 텐서플로우의 성능 테스트에서 사용한 코드도 위노그라드 설정을 지정하고 있습니다.

얼마전 NVIDIA가 GTC17에서 선보인 볼타Volta 아키텍처에서 16비트 부동소수점을 사용하여 성능을 크게 올렸다고 발표했습니다. 이와 동시에 페이스북이 지원하고 있는 Caffe2가 이미 16비트 부동소숫점을 지원한다고 발표했고 파이토치도 곧 기능을 추가할 거라고 언급했습니다. 아무래도 NVIDIA와 페이스북 간의 공조가 더 긴밀한 듯 합니다. 🙂

PyTorch v0.1.12

파이토치PyTorch의 0.1.12 버전이 릴리즈되었습니다. 이 버전은 0.1.x의 마지막 버전으로 알려져 있습니다. 넘파이NumPy 스타일을 따르도록 API 가 조금 변경되었고 CUDA의 희박행렬 연산 지원이 추가되었습니다. 그 외에 성능향상과 버그들이 많이 수정되었다고 합니다. 다음 버전은 0.2 버전으로 분산 처리와 그래디언트의 그래디언트를 계산하는 기능이 추가될 예정입니다.

때맞춰 텐서플로우 성능 테스트 자료가 공개되었습니다. 이는 얼마전 발표된 Caffe2성능 자료에 대한 반격으로 보입니다. NVidia DGX-1 에서 테스트한 결과를 보면 카페2와 텐서플로우가 앞서거니 뒤서거니 합니다. 하지만 여전히 초당 훈련 이미지 처리수가 중요한 성능지표인지에 대해서는 논란의 여지가 있습니다. 여기에 사용된 텐서플로우 벤치마크를 위한 코드는 상당히 최적화시킨 것 같습니다.

perf_summary_p100_single_server

18-caffe2-chart

파이토치를 설치하려면 리눅스와 맥OS에서 콘다 명령을 사용할 수 있습니다. 맥OS에서 CUDA 지원을 사용하려면 소스 컴파일을 해야합니다. 아직 윈도우즈 환경은 지원하지 않고 있습니다.

conda install pytorch torchvision -c soumith

PyTorch v0.1.11 Release

파이토치 0.1.11 버전이 릴리즈 되었습니다. 텐서플로우와 케라스와 일관되게 하려고 Adamax 옵티마이저의 기본 파라미터 값이 변경되었습니다(lr=2e-3, eps=1e-8). 또 CuDNN v6 지원과 성능 향상 이외에도 많은 기능이 추가되고 버그가 수정되었습니다. 자세한 내용은 릴리즈 노트를 참고하세요.

아나콘다를 사용하면 맥과 리눅스에서 파이토치를 쉽게 설치할 수 있습니다. 맥OS에서 CUDA 지원을 사용하려면 소스 컴파일을 해야합니다. 아직 윈도우즈 환경은 지원하지 않고 있습니다.

conda install pytorch torchvision -c soumith

DiscoGAN [1703.05192]

SKT T-Brain 팀의 첫 페이퍼 “Learning to Discover Cross-Domain Relations with Generative Adversarial Networks”(DiscoGAN, 1703.05192) 공개되었습니다. DiscoGAN은 한 종류의 이미지에서 학습하여 비슷한 스타일의 다른 종류의 이미지를 생성해 줍니다. T-Brain 페이스북 페이지에서 친절하게 자세히 설명하고 있습니다. DiscoGAN은 파이토치(!)로 구현되어 있으며 깃허브에 공개되어 있습니다. 하지만 번개같은 속도로 데브시스타즈의 김태훈님이 더 깔금하게 파이토치 구현을 만들었습니다. 이외에도 두 개의 체이너 구현(1, 2)도 만들어졌습니다.

Visdom: Visualization for ML & DL

visidom2

페이스북에서 Torch, PyTorch, NumPy 를 위한 시각화 도구인 Visdom을 오픈소스로 공개했습니다. 자연스럽게 이는 텐서플로우의 텐서보드TensorBoard의 대항마일 것으로 여겨집니다. 비즈돔은 토치용 시각화 서버인 display에 영감을 받았다고 합니다. 비즈돔은 웹브라우저로 사용할 수 있으며 웹서버는 오래전 페이스북이 인수한 프렌드피드FriendFeed가 만든 토네이도Tornado를 사용하고 있고 실제적인 그래픽 처리는 plotly.js를 이용합니다. API 를 봐서는 계산 그래프를 시각화시키는 도구는 아닌 것으로 보입니다. 하지만 앞으로 어떤 기능이 추가될지 기대가 됩니다.

PyTorch v0.1.10 Release

파이토치 0.1.10 버전이 릴리즈 되었습니다. 이번 버전에서는 텐서의 인덱싱과 브로드캐스팅 기능이 많이 추가 되었고 가변길이variable-length RNNs, 희박 텐서sparse tensor도 추가 되었습니다. 또한 CPU 기반의 연산이 2~10배 가량 빨라져서 NumPy에 근접한 성능을 낸다고 합니다. 😀

0.1.10 버전 부터는 리눅스와 맥OS에서 콘다 명령을 사용해 pytorch, torchvision을 모두 설치할 수 있습니다. 맥OS에서 CUDA 지원을 사용하려면 소스 컴파일을 해야합니다. 아직 윈도우즈 환경은 지원하지 않고 있습니다.

conda install pytorch torchvision -c soumith

PyTorch 0.1.9 Release

파이토치(PyTorch) 0.1.9 버전이 릴리즈 되었습니다. 이 버전에서는 주로 버그가 수정되었으며 기능상에 큰 변화는 없습니다. 아나콘다를 사용하면 파이토치 바이너리를 편하게 설치할 수 있습니다.

### conda (linux, osx)
$ conda install pytorch torchvision -c soumith

### pip
# linux: python2.7 + cuda7.5
$ pip install https://s3.amazonaws.com/pytorch/whl/cu75/torch-0.1.9.post1-cp27-none-linux_x86_64.whl

# linux: python2.7 + cuda8.0
$ pip install https://s3.amazonaws.com/pytorch/whl/cu80/torch-0.1.9.post1-cp27-none-linux_x86_64.whl

# linux: python3.5 + cuda7.5
$ pip install https://s3.amazonaws.com/pytorch/whl/cu75/torch-0.1.9.post1-cp35-cp35m-linux_x86_64.whl

# linux: python3.5 + cuda8.0
$ pip install https://s3.amazonaws.com/pytorch/whl/cu80/torch-0.1.9.post1-cp35-cp35m-linux_x86_64.whl

# linux: python3.6 + cuda7.5
$ pip install https://s3.amazonaws.com/pytorch/whl/cu75/torch-0.1.9.post1-cp36-cp36m-linux_x86_64.whl

# linux: python3.6 + cuda8.0
$ pip install https://s3.amazonaws.com/pytorch/whl/cu80/torch-0.1.9.post1-cp36-cp36m-linux_x86_64.whl

# osx: python2.7
$ pip install https://s3.amazonaws.com/pytorch/whl/torch-0.1.9.post1-cp27-none-macosx_10_7_x86_64.whl

# osx: python3.5
$ pip install https://s3.amazonaws.com/pytorch/whl/torch-0.1.9.post1-cp35-cp35m-macosx_10_6_x86_64.whl

# osx: python3.6
$ pip install https://s3.amazonaws.com/pytorch/whl/torch-0.1.9.post1-cp36-cp36m-macosx_10_6_x86_64.whl

### torchvision
$ pip install torchvision

GANs in 50 lines of pytorch

이안 굿펠로우(Ian Goodfellow)의 GANs(Generative Adversarial Networks) 페이퍼를 파이토치로 구현한 블로그가 있어서 재현해 보았습니다.(제목과는 달리 전체 코드는 100줄이 넘습니다) 이 블로그에 있는 파이토치 소스는 랜덤하게 발생시킨 균등 분포를 정규 분포 데이터로 만들어 주는 생성기(G, Generator)와 생성기가 만든 데이터와 진짜 정규 분포를 감별하는 분류기(D, Discriminator)를 학습시키는 것입니다.

분류기에는 감별하려는 분포의 데이터 한 벌이 모두 들어가서 참, 거짓의 이진 판별을 내 놓습니다. 생성기는 한번에 하나의 데이터 포인트가 들어가서 출력값도 하나입니다. 원하는 분포를 얻기 위해서 생성기에 필요한 만큼 데이터를 계속 주입하면 됩니다. 행렬로 생각하면 분류기에는 하나의 행이 들어가고, 생성기에는 배치 효과를 위해 하나의 열 벡터가 들어갑니다.

그런데 기대한 것처럼 정규분포와 비슷한 생성기를 학습시키기가 어려웠습니다. 하이퍼파라미터 세팅에 많이 민감하여 종종 에러가 폭주하기도 했습니다. 그리고 학습이 되어도 정규분포의 모양을 가진다기 보다 평균과 표준편차만 같은 분포를 만들어 내었습니다. 즉 평균과 표준편차를 학습한 것 같은 모습니다(물론 이 코드는 결과보다 예시를 위한 것이라고 합니다만..). 그래서 생성기의 출력을 한번에 전체 분포를 얻을 수 있도록 마지막 레이어의 노드 수를 늘렸습니다. 이렇게 했더니 손쉽게 정규 분포와 비슷한 결과물을 얻었습니다만 생성기의 목적을 제대로 수행한 것인지는 확신이 서지 않네요. 🙂

전체 노트북은 깃허브에 있습니다. 코드의 중요 부분은 아래와 같습니다.

학습시키고자 하는 정규 분포는 평균이 4, 표준 편차가 1.25 입니다. 분류기는 입력 유닛이 100개이고 히든 유닛의 수는 50, 출력 노드는 하나입니다. 생성기는 100개의 입출력 유닛을 가지고 히든 유닛 수는 50개 입니다. 이 예제의 분류기, 생성기는 모두 각각 두 개의 히든 레이어를 가지고 있는데 두 개의 히든 레이어의 노드 수는 동일합니다.

data_mean = 4
data_stddev = 1.25

d_input_size = 100
d_hidden_size = 50
d_output_size = 1

g_input_size = 100
g_hidden_size = 50
g_output_size = 100

분류기와 생성기의 클래스 정의입니다.

class Discriminator(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(Discriminator, self).__init__()
        self.map1 = nn.Linear(input_size, hidden_size)
        self.map2 = nn.Linear(hidden_size, hidden_size)
        self.map3 = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        x = F.relu(self.map1(x))
        x = F.relu(self.map2(x))
        return F.sigmoid(self.map3(x))

class Generator(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(Generator, self).__init__()
        self.map1 = nn.Linear(input_size, hidden_size)
        self.map2 = nn.Linear(hidden_size, hidden_size)
        self.map3 = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        x = F.relu(self.map1(x))
        x = F.relu(self.map2(x))
        return self.map3(x)

렐루(ReLU)를 사용해서 레이어를 감싸고 분류기의 출력은 시그모이드로, 생성기의 출력은 마지막 레이어의 값을 그대로 뽑았습니다. 다음은 분류기와 생성기가 서로 학습하는 과정입니다.

for epoch in range(num_epochs):
    # 분류기와 생성기의 학습 횟수가 서로 다를 수 있습니다.
    for d_index in range(d_steps):
        D.zero_grad()  # 그래디언트를 비웁니다.
        real_data = Variable(real_sampler(d_input_size)) # 정규분포
        real_decision = D(real_data) # 정규분포 판별
        real_error = loss(real_decision, Variable(torch.ones(1))) # 무조건 참이어야 합니다
        real_error.backward() # 역전파

        fake_input = Variable(fake_sampler(g_input_size)) # 균등분포
        fake_data = G(fake_input) # 생성기가 정규분포로 변조시킵니다
        fake_decision = D(fake_data) # 가짜인지 구분해야 합니다
        fake_error = loss(fake_decision, Variable(torch.zeros(1))) # 무조건 거짓이어야 합니다
        fake_error.backward()  # 역전파
        d_optimizer.step()     # 분류기 파라미터 업데이트

    # 생성기 학습 차례
    for g_index in range(g_steps):
        G.zero_grad()  # 그래디언트를 비웁니다.
        fake_input = Variable(fake_sampler(g_input_size)) # 균등분포
        fake_data = G(fake_input) # 생성기가 정규분포로 변조합니다
        fake_decision = D(fake_data) # 가짜인지 구분합니다.
        fake_error = loss(fake_decision, Variable(torch.ones(1))) # 생성기 입장에서는 분류기가 참으로 속아야 합니다.
        fake_error.backward()  # 역전파
        g_optimizer.step()     # 생성기 파라미터 업데이트

이렇게 학습시키게 되면 초기에는 평균과 표준편차가 잠시 널뛰지만 반복이 어느 시점을 넘어서면 거의 변동이 없는 상태가 됩니다. 아래 그림에서 파란색이 평균, 붉은 색이 표준편차 입니다.

gan-pytorch-1

에러는 총 세가지로 분류기가 학습할 때 계산하는 정답 분포에 대한 에러(파란)와 가짜 분포(녹색)에 대한 에러, 그리고 생성기가 학습할 때 계산하는 가짜 분포에 대한 에러(적색) 입니다. 아래 그래프에서 볼 수 있듯이 세 에러가 거의 변동이 없이 일치하고 있습니다.

gan-pytorch-2

학습이 끝난 후 만들어진 생성기로 랜덤한 균등 분포를 입력해서 정규 분포로 바꾸어 보았습니다. 눈으로 볼 때에도 구분할 수 없을 만큼 비슷했습니다. 깃허브에 있는 노트북 코드원본 코드와는 조금 다르며 코드를 간소화하기 위해 람다 함수 등을 수정했습니다.

Wasserstein GAN [1701.07875]

wgan

아마 지난 주에 가장 많이 회자된 페이퍼 중 하나가 페이스북 AI 팀에서 내놓은 Wasserstein GAN(WGAN)일 것 같습니다. 이 페이퍼에 대한 레딧의 포스팅 열기를 봐서는 GAN에 관심있다면 꼭 살펴봐야할 것 같습니다. 주요한 요점은 GAN 의 Jensen-Shannon 발산을 Wasserstein 거리로 바꿈으로 해서 조금 더 구현이 간소화되었다고 합니다. 페이퍼의 그림을 보면 왼쪽 WGAN으로 만든 샘플은 배치 정규화 등을 하지 않아도 좋은 품질의 이미지를 만들어 내었습니다. WGAN의 구현은 파이토치케라스 버전으로 공개되었습니다. 파이토치 버전은 페이스북의 팀에서 직접 만든 것으로 앞으로 계속 파이토치 코드로 페이퍼를 낼지 기대가 됩니다.

(업데이트) 이 페이퍼를 요약한 드롭박스 문서와 쿠오라에서 이안 굿펠로우의 답변도 함께 참고하세요.

(2017.02.14) WGAN의 텐서플로우 구현이 공개되었습니다.