카테고리 보관물: PyTorch

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의 텐서플로우 구현이 공개되었습니다.

PyTorch 0.1.8 Release

페이스북의 파이토치(PyTorch) 버전이 빠르게 올라가고 있습니다. 지난번 포스팅 이후 두번의 릴리즈가 더 있었습니다. 0.1.7 버전에는 텐서플로우에는 없는 LBFGS 알고리즘이 추가 되었고 0.1.8 버전에는 분산 처리를 위한 THD 라이브러리가 추가 되었습니다. 콘다를 사용하면 파이토치 바이너리를 편하게 설치할 수 있습니다.

### 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.8.post1-cp27-none-linux_x86_64.whl

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

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

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

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

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

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

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

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

### torchvision
$ pip install torchvision

PyTorch MNIST Example

파이토치(PyTorch)로 텐서플로우 튜토리얼에 있는 MNIST 예제를 재현해 보았습니다. 이 코드는 파이토치의 MNIST 예제를 참고했으며 주피터 노트북으로 작성되어 깃허브에 올려져 있습니다. 당연하지만 분류 결과는 텐서플로우로 만든 예제와 큰 차이가 없습니다.

파이토치는 버전 0.1.6을 사용하였습니다. 파이토치 설치는 이 포스트를 참고하세요. 파이토치는 코어 C 라이브러리를 토치(Torch)와 공유하고 있습니다. 그 외에는 모두 파이썬으로 구현되어 있으며 별도로 C API 가 없습니다. 오직 파이썬을 위해서 구현된 라이브러리이고 또 다이나믹 컴퓨테이션 그래프(Dynamic Computation Graph)를 지원하기 때문에 코드 모습이 일반 프로그래밍처럼 자연스러웠습니다.

파이토치의 예제와 유사하게 torch.nn.Module 클래스를 상속받아 모델 클래스를 만들었습니다.

class MnistModel(nn.Module):
    def __init__(self):
        super(MnistModel, self).__init__()
        # input is 28x28
        # padding=2 for same padding
        self.conv1 = nn.Conv2d(1, 32, 5, padding=2)
        # feature map size is 14*14 by pooling
        # padding=2 for same padding
        self.conv2 = nn.Conv2d(32, 64, 5, padding=2)
        # feature map size is 7*7 by pooling
        self.fc1 = nn.Linear(64*7*7, 1024)
        self.fc2 = nn.Linear(1024, 10)
        
    def forward(self, x):
        x = F.max_pool2d(F.relu(self.conv1(x)), 2)
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, 64*7*7)   # reshape Variable
        x = F.relu(self.fc1(x))
        x = F.dropout(x, training=self.training)
        x = self.fc2(x)
        return F.log_softmax(x)
    
model = MnistModel()

Module 를 상속받은 클래스는 뉴럴 네트워크의 정방향 계산을 수행하는 forward() 메소드를 구현해야만 합니다. 이 forward() 메소드는 model 오브젝트를 데이터와 함께 호출하면 자동으로 실행이됩니다.

풀링과 활성화 함수들은 torch.nn.functional 아래에 있고 함수처럼 불러서 사용할 수 있습니다. 이런 함수들은 Conv2d 클래스처럼 유사하게 forward(), backward() 메소드를 가진 클래스를 감싸고 있는 형태입니다. 최종 loss 에서 backward() 함수가 호출되면 거꾸로 짚어가면서 역방향 계산이 연쇄적으로 일어납니다. Conv2d 함수의 파라미터에서 볼 수 있듯이 패딩과 스트라이드를 명시적으로 지정해 주어야 합니다. 콘볼루션의 패딩 계산은 이 포스트를 참고하세요.

Linear 함수는 일반적인 완전연결(fully connected) 레이어를 의미하는 것으로 이것뿐만 아니라 많은 요소들이 토치의 네이밍을 따르고 있는 것 같습니다. 아마도 토치를 썼던 사람은 쉽게 사용할 수 있을 것 같습니다.

model.train()
train_loss = []
train_accu = []
i = 0
for epoch in range(15):
    for data, target in train_loader:
        data, target = Variable(data), Variable(target)
        optimizer.zero_grad()
        output = model(data)
        loss = F.nll_loss(output, target)
        loss.backward()    # calc gradients
        train_loss.append(loss.data[0])
        optimizer.step()   # update gradients
        prediction = output.data.max(1)[1]   # first column has actual prob.
        accuracy = prediction.eq(target.data).sum()/batch_size*100
        train_accu.append(accuracy)
        if i % 1000 == 0:
            print('Train Step: {}\tLoss: {:.3f}\tAccuracy: {:.3f}'.format(i, loss.data[0], accuracy))
        i += 1

모델을 학습시키는 부분도 직관적입니다. Negative Log Likelihood를 의미하는 nll_loss 함수에 출력과 타겟값을 넣고 loss.backward() 를 호출한 다음 optimizer.step() 으로 파라미터를 업데이트 합니다. 여기서 target 은 원-핫-인코딩이 아닌 타겟 레이블을 그대로 넣습니다. loss 와 accuracy 를 리스트에 저장하여 맷플롯립으로 그래프를 그렸습니다.

pytorch-mnist-loss

loss

pytorch-mnist-accuracy

accuracy

모델을 훈련할 땐 model.train() , 테스트할 땐 model.eval() 함수를 호출하면 Module 클래스의 훈련 상태 여부를 바꾸게 되며 이는 dropout 같은 함수가 드롭아웃을 적용할지 안할지를 결정하게 만듭니다.

model.eval()
correct = 0
for data, target in test_loader:
    data, target = Variable(data, volatile=True), Variable(target)
    output = model(data)
    prediction = output.data.max(1)[1]
    correct += prediction.eq(target.data).sum()

print('Test set: Accuracy: {:.2f}%'.format(100. * correct / len(test_loader.dataset)))

최종결과는 99.29%를 얻었습니다. 전체 코드는 깃허브에서 확인할 수 있습니다.

PyTorch: New Cool DL Library

0120-sdt-github

(업데이트) 파이토치는 토치(Torch)의 코어 C 라이브러리를 공유하고 있으며 대략 2016년 5월 경에 시작된 것으로 추측됩니다. 텐서플로우도 다이나믹 그래프를 위한 시도를 하고 있으나(TensorFlow Fold) 아직 페이퍼외에 코드로 공개된 것은 없습니다.

널리 사용되는 딥러닝 라이브러리 중 하나는 페이스북에서 많이 사용하는 토치(torch)입니다. 하지만 루아 스크립트 인터페이스 때문에 처음 딥러닝을 시작하는 사람들에게 선택되기는 쉽지 않습니다. 그와중에 구글이 파이썬 인터페이스를 전면으로 내세운 텐서플로우로 딥러닝 프레임워크 시장의 선두로 올라섰습니다. 하지만 앞으로 이야기가 조금 달라질 수 있을것 같습니다. 바로 텐서플로우보다 더 파이써닉한 파이토치(PyTorch)가 공개되었기 때문입니다. 이름을 보아서는 토치에 파이썬 인터페이스를 추가한 것으로 오해할 수 있지만, 이 라이브러리는 넘파이(NumPy) 정도를 제외하고는 거의 의존성이 없으며 텐서 연산을 위한 C++ 코드를 제외하고는 거의 모두 파이썬 구현으로 만들어졌습니다. 이는 대부분의 코드가 C++ 로 구현되어 있고 인터페이스를 파이썬으로 가진 텐서플로우를 비롯한 다른 라이브러리들과는 큰 차이가 납니다. 웹사이트에서 주장하는 바 그대로 “Python First” 입니다.

파이토치의 주요 장점은 모든 텐서의 연산에 대해 자동으로 그래디언트를 계산해 주는 Autograd, NumPy/SciPy 등과의 뛰어난 호환성, 깔끔한 코드 등입니다. 하지만 가장 관심을 끄는 것 중 하나는 다이나믹하게 컴퓨테이션 그래프를 만들어 주는 기능입니다. 즉 텐서플로우를 비롯한 다른 라이브러리들처럼 그래프를 정의하고 실행하는 것이 아니고 코딩되는 대로 그래프가 점진적으로 만들어지는 것입니다. 이런 장점을 가진 라이브러리로는 일본에서 만든 체이너(Chainer)가 있고 최근 카네기멜론 대학의 Clab 에서 공개한 DyNet(말 그대로 다이나믹 넷이죠), 그리고 MXNet을 개발하는 DMLC 에서 최근에 공개한 MinPy 등이 있습니다. 실제로 파이토치의 일부 기능은 체이너의 포크에서 시작되었다고 합니다. 이 프로젝트는 페이스북의 서미스 친탈라(Soumith Chintala)와 아담 파스케(Adam Paszke)가 관리하고 있으며 혼돈될 수 있지만 휴 퍼킨스(Hugh Perkins)가 만든 토치 래퍼인 pytorch와는 다릅니다.

기존의 네트워크와 앞으로의 연구가 얼마나 파이토치로 포팅되어 나올지가 궁금합니다. 자세한 파이토치의 기능은 예제를 뜯어 보면서 정리해서 올리겠습니다. 파이토치는 아직 PyPI 에 올라가 있지는 않지만 비교적 쉽게 설치가 가능합니다. 다만 아직 윈도우 버전이 준비되어 있지 않고 맥에서는 GPU 지원이 되지 않습니다.

# 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.6.post22-cp27-cp27mu-linux_x86_64.whl 
$ pip install torchvision

# pip : linux + python2.7 + cuda8.0
$ pip install https://s3.amazonaws.com/pytorch/whl/cu80/torch-0.1.6.post22-cp27-cp27mu-linux_x86_64.whl 
$ pip install torchvision

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

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

# pip : osx + python2.7
$ pip install https://s3.amazonaws.com/pytorch/whl/torch-0.1.6.post22-cp27-cp27m-macosx_10_7_x86_64.whl 
$ pip install torchvision 

# pip : osx + python3.5
$ pip install https://s3.amazonaws.com/pytorch/whl/torch-0.1.6.post22-cp35-cp35m-macosx_10_6_x86_64.whl 
$ pip install torchvision