AWS Lambda PyTorch RNN 모델 서빙하기
오늘 할 작업은 Lambda에 딥러닝 RNN 모델을 올려 서비스로 구현해보겠습니다.
RNN 모델이 하는 작업은 Character 글자 단위로 이름을 생성하는 작업입니다.
https://pytorch.org/tutorials/intermediate/char_rnn_generation_tutorial.html
튜토리얼 따라 쭉 진행하시면서 코드를 이해해주세요
데이터셋은 여기 있습니다. https://download.pytorch.org/tutorial/data.zip
이 모델을 이용하는 프로그램은 2가지의 입력을 받습니다.
language와 이름의 첫 시작할 단어 한 글자를 받습니다.
Lambda 생성
S3 생성 및 모델 파일 업로드
버킷을 하나 생성해주세요. Bucket name을 기억해주시고, lambda_function.py에서 수정해주세요.
두 개의 파일을 다운로드 받아주세요. rnn_model.pth 딥러닝 모델이 정의되어 있고, rnn_params.pkl은 모델의 파라미터가 저장되어 있습니다.
https://github.com/KimJeongChul/aws-lambda/blob/master/s3/lambda/classify-names-rnn/rnn_model.pth
https://github.com/KimJeongChul/aws-lambda/blob/master/s3/lambda/classify-names-rnn/rnn_params.pkl
자 이제 S3 Bucket에 업로드 해봅시다.
Torch 빌드
torch 빌드를 진행해봅시다.
CPU 버전으로 설치해야 합니다. CUDA 버전은 용량이 커서 라이브러리로 빌드 불가합니다.
$ virtualenv torch
$ source ~/torch/bin/activate
$ pip install http://download.pytorch.org/whl/cpu/torch-0.4.1-cp27-cp27mu-linux_x86_64.whl
$ pip install numpy
$ pip install pyyaml
$ ls -alh ~/torch/lib64/python2.7/dist-packages/
패키징할 라이브러리의 전체 크기를 살펴봅시다.
$ du -sh ~/torch/lib64/python2.7/dist-packages/*
385M 크기로 Lambda에 올라가기에는 용량이 너무 큽니다.
필요없는 파일들을 삭제 해봅시다.
$ cd ~/torch/lib64/python2.7/dist-packages/
find . -type d -name "tests" -exec rm -rf {} +
find -name "*.so" | xargs strip
find -name "*.so.*" | xargs strip
rm -r pip
rm -r pip-*
rm -r easy*
rm -r setup*
rm -r wheel
rm -r wheel-*
rm easy_install.py
find . -name \*.pyc -delete
파일 크기를 확인해봅시다.
$ du -sh ~/torch/lib64/python2.7/dist-packages/
$ du -sh ~/torch/lib64/python2.7/dist-packages/*
전체 불필요한 파일 크기를 줄이니 178M으로 줄었습니다.
이제 패키징을 진행합니다.
$ zip -r9 ~/torch.zip .
$ mkdir ClassifyNamesRNN
$ cp torch.zip ClassifyNamesRNN/ClassifyNamesRNN.zip
$ cd ClassifyNamesRNN
$ vi lambda_function.py
다음의 코드를 입력합니다.
코드를 변경하실 부분은 S3 Bucket Name이 되겠습니다.
import boto3
import os
import pickle
import numpy as np
import torch
import torch.nn as nn
from torch.autograd import Variable
BUCKET_NAME = "kmu-serverless-lambda-model"
s3_client = boto3.client('s3')
class RNN(nn.Module):
def __init__(self, input_size, hidden_size, output_size, n_categories):
super(RNN, self).__init__()
self.hidden_size = hidden_size
self.i2h = nn.Linear(n_categories + input_size + hidden_size, hidden_size)
self.i2o = nn.Linear(n_categories + input_size + hidden_size, output_size)
self.o2o = nn.Linear(hidden_size + output_size, output_size)
self.dropout = nn.Dropout(0.1)
self.softmax = nn.LogSoftmax(dim=1)
def forward(self, category, input, hidden):
input_combined = torch.cat((category, input, hidden), 1)
hidden = self.i2h(input_combined)
output = self.i2o(input_combined)
output_combined = torch.cat((hidden, output), 1)
output = self.o2o(output_combined)
output = self.dropout(output)
output = self.softmax(output)
return output, hidden
def initHidden(self):
return Variable(torch.zeros(1, self.hidden_size))
# Load preprocessing parameters
if not os.path.isfile('/tmp/rnn_params.pkl'):
s3_client.download_file(BUCKET_NAME, 'rnn_params.pkl', '/tmp/rnn_params.pkl')
with open('/tmp/rnn_params.pkl', 'rb') as pkl:
params = pickle.load(pkl)
all_categories = params['all_categories']
n_categories = params['n_categories']
all_letters = params['all_letters']
n_letters = params['n_letters']
# Check if models are available
# Download model from S3 if model is not already present
if not os.path.isfile('/tmp/rnn_model.pth'):
s3_client.download_file(BUCKET_NAME, 'rnn_model.pth', '/tmp/rnn_model.pth')
rnn = RNN(n_letters, 128, n_letters, n_categories=n_categories)
rnn.load_state_dict(torch.load("/tmp/rnn_model.pth"))
rnn.eval()
def inputTensor(line):
tensor = torch.zeros(len(line), 1, n_letters)
for li in range(len(line)):
letter = line[li]
tensor[li][0][all_letters.find(letter)] = 1
return tensor
def categoryTensor(category):
li = all_categories.index(category)
tensor = torch.zeros(1, n_categories)
tensor[0][li] = 1
return tensor
# Sample from a category and starting letter
def sample(category, start_letter='A'):
category_tensor = Variable(categoryTensor(category))
input = Variable(inputTensor(start_letter))
hidden = rnn.initHidden()
output_name = start_letter
max_length=20
for i in range(max_length):
output, hidden = rnn(category_tensor, input[0], hidden)
topv, topi = output.data.topk(1)
topi = topi[0][0]
if topi == n_letters - 1:
break
else:
letter = all_letters[topi]
output_name += letter
input = Variable(inputTensor(letter))
return output_name
# Get multiple samples from one category and multiple starting letters
def samples(category, start_letters='ABC'):
for start_letter in start_letters:
yield sample(category, start_letter)
"""
Language
- Italian, German, Portuguese, Chinese, Greek, Polish, French
- English, Spanish, Arabic, Crech, Russian, Irish, Dutch
- Scottish, Vietnamese, Korean, Japanese
"""
def lambda_handler(event, context):
language = event['language']
start_letters = event['start_letters']
output_names = list(samples(language, start_letters))
print(output_names)
return output_names
자 이제 lambda_function.py를 패키징합시다.
$ zip -g ClassifyNamesRNN.zip lambda_function.py
올리는 방식은 S3 Bucket에 업로드를 하고 Lambda에서 올릴 예정입니다.
$ aws s3 cp ClassifyNamesRNN.zip s3://[BUCKET-NAME]
다음의 명령어로 올려주세요. (aws configure) 설정해주셔야 합니다.
Lambda 코드 Update
다시 Lambda로 돌아가서 코드를 업데이트 합니다.
위에서 올린 패키징의 object url을 복사해서 S3 link URL에 붙여넣고 저장을 해주세요.
메모리와 timeout을 설정합니다.
테스트를 해볼까요?
“language” 와 “start_letters”를 입력해야 합니다.
입력 가능한 언어는 다음과 같습니다.
Language
- Italian, German, Portuguese, Chinese, Greek, Polish, French
- English, Spanish, Arabic, Czech, Russian, Irish, Dutch
- Scottish, Vietnamese, Korean, Japanese
테스트를 해봅시다.
잘 작동하네요.
고생하셨습니다.
'AWS' 카테고리의 다른 글
AWS Certified Cloud Practitioner 클라우드 자격증 (0) | 2020.12.13 |
---|---|
container보다 빠른 새로운 가상화 기술 Firecracker (0) | 2019.04.19 |
AWS Lambda Serverless MapReduce (0) | 2019.01.23 |
AWS EC2와 Lambda iPerf3 네트워크 성능 측정 (0) | 2019.01.09 |
AWS EC2 Ubuntu1804에 GitLab 서버 설치 (0) | 2019.01.07 |