• Tutorials >
  • (베타) LSTM 기반 단어 단위 언어 모델의 동적 양자화
Shortcuts

(베타) LSTM 기반 단어 단위 언어 모델의 동적 양자화

Author: James Reed

Edited by: Seth Weidman

번역: 박경림 Myungha Kwon

시작하기

양자화는 모델의 크기를 줄이고 추론 속도를 높이면서도 정확도는 별로 낮아지지 않도록, 모델의 가중치와 활성 함수를 실수형에서 정수형으로 변환합니다.

이 튜토리얼에서는 PyTorch의 단어 단위 언어 모델 예제를 따라하면서, LSTM 기반의 단어 예측 모델에 가장 간단한 양자화 기법인 동적 양자화 를 적용해 보겠습니다.

# 불러오기
import os
from io import open
import time

import torch
import torch.nn as nn
import torch.nn.functional as F

1. 모델 정의하기

단어 단위 언어 모델 예제에서 사용된 모델 을 따라 LSTM 모델 아키텍처를 정의합니다.

class LSTMModel(nn.Module):
    """인코더, 반복 모듈 및 디코더가 있는 컨테이너 모듈."""

    def __init__(self, ntoken, ninp, nhid, nlayers, dropout=0.5):
        super(LSTMModel, self).__init__()
        self.drop = nn.Dropout(dropout)
        self.encoder = nn.Embedding(ntoken, ninp)
        self.rnn = nn.LSTM(ninp, nhid, nlayers, dropout=dropout)
        self.decoder = nn.Linear(nhid, ntoken)

        self.init_weights()

        self.nhid = nhid
        self.nlayers = nlayers

    def init_weights(self):
        initrange = 0.1
        self.encoder.weight.data.uniform_(-initrange, initrange)
        self.decoder.bias.data.zero_()
        self.decoder.weight.data.uniform_(-initrange, initrange)

    def forward(self, input, hidden):
        emb = self.drop(self.encoder(input))
        output, hidden = self.rnn(emb, hidden)
        output = self.drop(output)
        decoded = self.decoder(output)
        return decoded, hidden

    def init_hidden(self, bsz):
        weight = next(self.parameters())
        return (weight.new_zeros(self.nlayers, bsz, self.nhid),
                weight.new_zeros(self.nlayers, bsz, self.nhid))

2. 텍스트 데이터 불러오기

다음으로, 단어 단위 언어 모델 예제의 전처리 과정을 따라 Wikitext-2 데이터셋Corpus 인스턴스에 불러옵니다.

class Dictionary(object):
    def __init__(self):
        self.word2idx = {}
        self.idx2word = []

    def add_word(self, word):
        if word not in self.word2idx:
            self.idx2word.append(word)
            self.word2idx[word] = len(self.idx2word) - 1
        return self.word2idx[word]

    def __len__(self):
        return len(self.idx2word)


class Corpus(object):
    def __init__(self, path):
        self.dictionary = Dictionary()
        self.train = self.tokenize(os.path.join(path, 'train.txt'))
        self.valid = self.tokenize(os.path.join(path, 'valid.txt'))
        self.test = self.tokenize(os.path.join(path, 'test.txt'))

    def tokenize(self, path):
        assert os.path.exists(path)
        """텍스트 파일 토큰화"""
        assert os.path.exists(path)
        # 사전에 단어 추가
        with open(path, 'r', encoding="utf8") as f:
            for line in f:
                words = line.split() + ['<eos>']
                for word in words:
                    self.dictionary.add_word(word)

        # 파일 내용 토큰화
        with open(path, 'r', encoding="utf8") as f:
            idss = []
            for line in f:
                words = line.split() + ['<eos>']
                ids = []
                for word in words:
                    ids.append(self.dictionary.word2idx[word])
                idss.append(torch.tensor(ids).type(torch.int64))
            ids = torch.cat(idss)

        return ids

model_data_filepath = 'data/'

corpus = Corpus(model_data_filepath + 'wikitext-2')

3. 사전 학습된 모델 불러오기

이 튜토리얼은 모델이 학습된 후 적용되는 양자화 기술인 동적 양자화에 대한 튜토리얼입니다. 따라서 우리는 미리 학습된 가중치를 모델 아키텍처에 로드할 것 입니다. 이 가중치는 word language 모델 예제의 기본 설정을 사용하여 5개의 epoch 동안 학습하여 얻은 것입니다.

ntokens = len(corpus.dictionary)

model = LSTMModel(
    ntoken = ntokens,
    ninp = 512,
    nhid = 256,
    nlayers = 5,
)

model.load_state_dict(
    torch.load(
        model_data_filepath + 'word_language_model_quantize.pth',
        map_location=torch.device('cpu')
        )
    )

model.eval()
print(model)

Out:

LSTMModel(
  (drop): Dropout(p=0.5, inplace=False)
  (encoder): Embedding(33278, 512)
  (rnn): LSTM(512, 256, num_layers=5, dropout=0.5)
  (decoder): Linear(in_features=256, out_features=33278, bias=True)
)

이제 사전 학습된 모델이 잘 동작하는지 확인해보기 위해 텍스트를 생성해 보겠습니다. 지금까지 튜토리얼을 진행했던 방식처럼 이 예제 를 따라 하겠습니다.

input_ = torch.randint(ntokens, (1, 1), dtype=torch.long)
hidden = model.init_hidden(1)
temperature = 1.0
num_words = 1000

with open(model_data_filepath + 'out.txt', 'w') as outf:
    with torch.no_grad():  # 기록을 추적하지 않습니다.
        for i in range(num_words):
            output, hidden = model(input_, hidden)
            word_weights = output.squeeze().div(temperature).exp().cpu()
            word_idx = torch.multinomial(word_weights, 1)[0]
            input_.fill_(word_idx)

            word = corpus.dictionary.idx2word[word_idx]

            outf.write(str(word.encode('utf-8')) + ('\n' if i % 20 == 19 else ' '))

            if i % 100 == 0:
                print('| Generated {}/{} words'.format(i, 1000))

with open(model_data_filepath + 'out.txt', 'r') as outf:
    all_output = outf.read()
    print(all_output)

Out:

| Generated 0/1000 words
| Generated 100/1000 words
| Generated 200/1000 words
| Generated 300/1000 words
| Generated 400/1000 words
| Generated 500/1000 words
| Generated 600/1000 words
| Generated 700/1000 words
| Generated 800/1000 words
| Generated 900/1000 words
b'in' b'the' b'Corrientes' b'@-@' b'up' b'with' b'Children' b'Sussex' b'.' b'<eos>' b'Local' b'studies' b'who' b'for' b'a' b'small' b'detachment' b',' b'Sports' b','
b'and' b'under' b'Argentina' b',' b'dismantled' b'trees' b'and' b'dramaturgical' b'access' b'to' b'its' b'repression' b'by' b'his' b'narratives' b'\xe2\x80\x94' b'Vice' b'Oswald' b',' b'a'
b'citizen' b',' b'also' b'accelerated' b'ki' b',' b'and' b'in' b'what' b'I' b'closed' b'to' b'portray' b'all' b'stories' b'during' b'this' b'first' b'breakdown' b'.'
b'James' b'sac' b'Mavis' b'Oaks' b'had' b'speculated' b'the' b'game' b"'s" b'body' b'about' b'the' b'erroneous' b'crowds' b'.' b'The' b'race' b'on' b'the' b'bird'
b'usually' b'refused' b'to' b'rebuild' b'their' b'car' b'at' b'a' b'time' b'on' b'28' b'.' b'This' b'account' b'consisted' b'of' b'one' b'exchange' b'replaced' b'for'
b'short' b'businesses' b',' b'with' b'assistance' b'to' b'their' b'reading' b'for' b'68' b'%' b'ranging' b'away' b'.' b'The' b'lengthy' b'jump' b'has' b'been' b'able'
b'to' b'continue' b'to' b'weaken' b'glamour' b',' b'because' b'they' b'arrived' b'to' b'pass' b'playing' b'greater' b'foundation' b'features' b'to' b'better' b'speak' b'.' b'One'
b'O' b"'t" b'be' b'Perugia' b'to' b'fulfill' b'from' b'the' b'handicap' b'.' b'It' b'was' b'further' b'by' b'Omar' b"'" b'surface' b'relationships' b'\xe2\x80\x94' b'Routes'
b'Park' b'and' b'the' b'exception' b'of' b'primary' b'Americans' b'.' b'The' b'Penn' b'Police' b'Obo' b'culture' b'became' b'woodwork' b'were' b'so' b'preventing' b'Douglas' b'.'
b'<eos>' b'NASA' b"'s" b'<unk>' b'was' b'challenged' b'closing' b'by' b'<unk>' b'<unk>' b'Mama' b'.' b'It' b'also' b'moved' b'a' b'replica' b'unknown' b'.' b'That'
b'shop' b'1882' b',' b'Da' b'Harris' b',' b'in' b'the' b'1989' b'election' b',' b'was' b'thought' b'to' b'be' b'broadcast' b'by' b'The' b'planet' b'to'
b'funds' b'.' b'Carlos' b'Mayer' b'Commodore' b'acted' b'as' b'one' b'of' b'the' b'sponsorship' b'19' b'%' b'for' b'the' b'<unk>' b'race' b'in' b'the' b'final'
b'story' b'.' b'He' b'seems' b'to' b'continue' b'to' b'inspect' b'.' b'<eos>' b'The' b'election' b'spread' b'to' b'the' b'law' b'to' b'disrupt' b'the' b'Jews'
b'in' b'1932' b'.' b'Hence' b',' b'on' b'August' b'27' b',' b'2006' b',' b'the' b'Bureau' b'tried' b'to' b'be' b'reported' b'to' b'be' b'defeated'
b'@-@' b'cavern' b'to' b'ensure' b'.' b'The' b'indecency' b'Hockey' b'antibiotics' b'progressed' b'to' b'four' b'weeks' b'after' b'another' b'significant' b'unit' b'works' b'more' b'than'
b'apartment' b'Hesler' b'loss' b'.' b'Anime' b'@-@' b'labor' b'term' b'prepared' b'Benigno' b'runs' b'to' b'lining' b',' b'in' b'1994' b'.' b'Eguchi' b'also' b'funded'
b'$' b'700' b'@,@' b'000' b'yards' b'.' b'Those' b'president' b',' b'as' b'the' b'actor' b'<unk>' b'opportunities' b'for' b'a' b'272nd' b'owned' b'in' b'endorsement'
b',' b'before' b'Wiesbaden' b'served' b'as' b'Raffles' b'Field' b',' b'Bridgestone' b'Island' b',' b'on' b'December' b'10' b',' b'2011' b'.' b'<eos>' b'<eos>' b'<eos>'
b'=' b'Hornung' b"'s" b'Banksia' b'fairy' b'lamps' b'(' b'2010' b')' b'=' b'<eos>' b'<eos>' b'The' b'Family' b'Race' b',' b'Tower' b'utilizes' b'the' b'<unk>'
b'of' b'northern' b'Fantasy' b'Contactmusic.com' b'(' b'1819' b')' b';' b'Ceres' b',' b'<unk>' b'(' b'<unk>' b',' b'colored' b')' b'.' b'They' b'Manas' b'('
b'width' b'wrote' b')' b'near' b'a' b'variety' b'of' b'review' b',' b'particularly' b'0' b'\xe2\x80\x93' b'3' b'(' b'43' b'cm' b')' b'jubilee' b'.' b'Up'
b'into' b'the' b'Vice' b'Republic' b'modeling' b'or' b'an' b'Haitian' b'account' b'than' b'600' b'meters' b'vessels' b'in' b'<unk>' b'.' b'There' b'were' b'nine' b'baritone'
b're' b'@-@' b'platform' b'@-@' b'pounders' b'and' b'identifies' b'an' b'series' b'of' b'concrete' b'in' b'the' b'lakes' b',' b'which' b'can' b'be' b'deputy' b','
b'even' b'one' b'<unk>' b'that' b'transit' b'other' b'.' b'spores' b'were' b'usually' b'besieged' b',' b'causing' b'special' b'more' b'heavy' b'honors' b'them' b',' b'and'
b'became' b'the' b'most' b'handsome' b'battleship' b'seen' b'by' b'being' b'the' b'first' b'war' b'in' b'central' b'Ireland' b'.' b'defines' b'six' b'spiders' b'to' b'do'
b'a' b'third' b'to' b'house' b'districts' b',' b'Ceres' b'both' b'range' b',' b'to' b'each' b'remaining' b'night' b',' b'except' b'more' b'than' b'5' b','
b'opening' b'up' b'and' b'Synthesis' b',' b'or' b'against' b'<unk>' b',' b'foliage' b'and' b'torpedo' b'cloud' b'soil' b'.' b'Ceres' b'in' b'the' b'Greek' b'<unk>'
b'classification' b'was' b'visible' b'on' b'4' b'August' b'1885' b'against' b'Winston' b'B.' b'Colletts' b'sight' b',' b'a' b'reminder' b'of' b'75' b'years' b'later' b'when'
b'Ceres' b'choose' b'with' b'increased' b'natural' b'communities' b'were' b'confusing' b'so' b'special' b'inflicted' b'posterior' b'bruising' b'.' b'<eos>' b'A' b'collaboration' b'from' b'M.' b'Dreamers'
b'has' b'withdrawn' b'as' b'"' b'power' b'"' b'(' b'106' b'%' b')' b'of' b'58' b'%' b'of' b'flora' b';' b'this' b'was' b'later' b'Type'
b'over' b'to' b'remorse' b'in' b'Utah' b'.' b'<eos>' b'<eos>' b'=' b'=' b'Behaviour' b'=' b'=' b'<eos>' b'<eos>' b'The' b'common' b'starling' b'from' b'these'
b'points' b'appear' b'into' b'Israel' b'and' b'is' b'derived' b'to' b'a' b'prominent' b'population' b'from' b'waves' b'from' b'Portis' b'cap' b'along' b'by' b'kakapo' b','
b'government' b',' b'spear' b'and' b'observatories' b'.' b'They' b'from' b'this' b'demos' b'proceed' b'about' b'3' b'@.@' b'4' b'@-@' b'cent' b'across' b'a' b'old'
b'season' b'as' b'when' b'it' b'appears' b',' b'and' b'well' b'mainly' b',' b'when' b'it' b'has' b'two' b'or' b'distinct' b'structures' b'that' b'may' b'be'
b'brought' b'to' b'express' b'the' b'thickness' b'for' b'villa' b'.' b'equal' b'immediately' b'up' b'in' b'areas' b',' b'it' b'is' b'larger' b',' b'Ralph' b'discomfort'
b',' b'war' b',' b'spored' b',' b'ministers' b',' b'peat' b',' b'Nguy\xe1\xbb\x85n' b',' b'and' b'Christina' b'indicating' b'southwestward' b'for' b'calculation' b'and' b'lower' b'birds'
b'and' b'found' b'non' b'@-@' b'transcriptional' b'institutions' b'.' b'In' b'areas' b'like' b'this' b',' b'since' b'to' b'spread' b'that' b'these' b'are' b'Ferry' b'<unk>'
b',' b'covers' b'may' b'be' b'seen' b'within' b'2006' b'when' b'these' b'species' b'do' b'not' b'occur' b',' b'or' b'mid' b'@-@' b'kilogram' b'to' b'have'
b'few' b'stone' b'predators' b'.' b'<eos>' b'In' b'moral' b',' b'livestock' b'or' b'gold' b'patterns' b'may' b'be' b'seen' b'for' b'small' b'common' b'country' b'dating'
b'and' b'account' b',' b'as' b'this' b'process' b'is' b'still' b'very' b'dark' b'noted' b'.' b'As' b'removed' b'range' b',' b'it' b'is' b'still' b'far'
b'to' b'engage' b'in' b'human' b'planets' b'.' b'<unk>' b'rabbits' b'ensure' b'that' b'their' b'bird' b'literate' b'history' b'are' b'undergoing' b'Western' b'years' b'by' b'means'
b'they' b'have' b'.' b'That' b'may' b'be' b'uploaded' b'every' b'appearance' b'regarding' b'these' b'holes' b'are' b'imminent' b'and' b',' b'able' b'to' b'eat' b'successful'
b',' b'and' b'some' b'noted' b'only' b'of' b'a' b'white' b'fox' b'and' b'Roussillon' b'look' b'.' b'According' b'to' b'Kings' b'during' b'56' b'years' b','
b'\xc3\x8dmar' b'Mantell' b'listed' b'"' b'religion' b'"' b'that' b'supported' b'for' b'the' b'modern' b'nurse' b'difference' b'on' b'its' b'face' b',' b'leaving' b'it' b'Too'
b'Merit' b'the' b'spots' b'for' b'a' b'number' b'of' b'chicks' b'(' b'Thomson' b')' b'and' b'<unk>' b'(' b'e.g.' b'camel' b')' b'.' b'Some' b'Fanny'
b',' b'males' b'\xe2\x80\x94' b'or' b'small' b'diversity' b'.' b'<eos>' b'<eos>' b'=' b'=' b'<unk>' b'among' b'grey' b'technique' b'=' b'=' b'<eos>' b'<eos>' b'Naming'
b'statistics' b'include' b'invisible' b'following' b'Mecca' b'.' b'It' b'may' b'be' b'established' b'into' b'at' b'least' b'enough' b'alone' b'<unk>' b'by' b'their' b'national' b'non'
b'@-@' b'volume' b'cores' b'(' b'fulfill' b'@-@' b'bird' b',' b'flowers' b'NATO' b',' b'and' b'has' b'any' b'results' b')' b',' b'this' b'feed' b'on'
b',' b'and' b'orchards' b'have' b'incorrect' b'chicks' b'or' b'structures' b'.' b'Because' b'part' b'of' b'these' b'birds' b'will' b'aimed' b'.' b'If' b'the' b'kakapo'
b'attain' b'well' b'allowed' b',' b'they' b'are' b'a' b'"' b'<unk>' b'form' b'so' b'since' b'this' b'name' b'about' b',' b'straight' b'or' b'even' b'evidently'

이 모델이 GPT-2는 아니지만, 언어의 구조를 배우기 시작한 것처럼 보입니다!

동적 양자화를 시연할 준비가 거의 끝났습니다. 몇 가지 helper 함수를 정의하기만 하면 됩니다:

bptt = 25
criterion = nn.CrossEntropyLoss()
eval_batch_size = 1

# 테스트 데이터셋 만들기
def batchify(data, bsz):
    # 데이터셋을 bsz 부분으로 얼마나 깔끔하게 나눌 수 있는지 계산합니다.
    nbatch = data.size(0) // bsz
    # 깔끔하게 맞지 않는 추가적인 부분(나머지들)을 잘라냅니다.
    data = data.narrow(0, 0, nbatch * bsz)
    # 데이터에 대하여 bsz 배치들로 동등하게 나눕니다.
    return data.view(bsz, -1).t().contiguous()

test_data = batchify(corpus.test, eval_batch_size)

# 평가 함수들
def get_batch(source, i):
    seq_len = min(bptt, len(source) - 1 - i)
    data = source[i:i+seq_len]
    target = source[i+1:i+1+seq_len].view(-1)
    return data, target

def repackage_hidden(h):
  """은닉 상태를 변화도 기록에서 제거된 새로운 tensor로 만듭니다."""

  if isinstance(h, torch.Tensor):
      return h.detach()
  else:
      return tuple(repackage_hidden(v) for v in h)

def evaluate(model_, data_source):
    # Dropout을 중지시키는 평가 모드로 실행합니다.
    model_.eval()
    total_loss = 0.
    hidden = model_.init_hidden(eval_batch_size)
    with torch.no_grad():
        for i in range(0, data_source.size(0) - 1, bptt):
            data, targets = get_batch(data_source, i)
            output, hidden = model_(data, hidden)
            hidden = repackage_hidden(hidden)
            output_flat = output.view(-1, ntokens)
            total_loss += len(data) * criterion(output_flat, targets).item()
    return total_loss / (len(data_source) - 1)

4. 동적 양자화 테스트하기

마지막으로 모델에서 torch.quantization.quantize_dynamic 을 호출 할 수 있습니다! 구체적으로,

  • 모델의 nn.LSTMnn.Linear 모듈을 양자화 하도록 명시합니다.

  • 가중치들이 int8 값으로 변환되도록 명시합니다.

import torch.quantization

quantized_model = torch.quantization.quantize_dynamic(
    model, {nn.LSTM, nn.Linear}, dtype=torch.qint8
)
print(quantized_model)

Out:

LSTMModel(
  (drop): Dropout(p=0.5, inplace=False)
  (encoder): Embedding(33278, 512)
  (rnn): DynamicQuantizedLSTM(512, 256, num_layers=5, dropout=0.5)
  (decoder): DynamicQuantizedLinear(in_features=256, out_features=33278, dtype=torch.qint8, qscheme=torch.per_tensor_affine)
)

모델은 동일하게 보입니다. 이것이 어떻게 이득을 주는 것일까요? 첫째, 모델 크기가 상당히 줄어 듭니다:

def print_size_of_model(model):
    torch.save(model.state_dict(), "temp.p")
    print('Size (MB):', os.path.getsize("temp.p")/1e6)
    os.remove('temp.p')

print_size_of_model(model)
print_size_of_model(quantized_model)

Out:

Size (MB): 113.94579
Size (MB): 79.739984

두 번째로, 평가 손실값은 같으나 추론(inference) 속도가 빨라졌습니다.

# 메모: 양자화 된 모델은 단일 스레드로 실행되기 때문에 단일 스레드 비교를 위해
# 스레드 수를 1로 설정했습니다.

torch.set_num_threads(1)

def time_model_evaluation(model, test_data):
    s = time.time()
    loss = evaluate(model, test_data)
    elapsed = time.time() - s
    print('''loss: {0:.3f}\nelapsed time (seconds): {1:.1f}'''.format(loss, elapsed))

time_model_evaluation(model, test_data)
time_model_evaluation(quantized_model, test_data)

Out:

loss: 5.167
elapsed time (seconds): 178.6
loss: 5.168
elapsed time (seconds): 118.8

MacBook Pro에서 로컬로 실행하는 경우, 양자화 없이는 추론(inference)에 약 200초가 걸리고 양자화를 사용하면 약 100초가 걸립니다.

마치며

동적 양자화는 정확도에 제한적인 영향을 미치면서 모델 크기를 줄이는 쉬운 방법이 될 수 있습니다.

읽어주셔서 감사합니다. 언제나처럼 어떠한 피드백도 환영이니, 의견이 있다면 여기 에 이슈를 남겨 주세요.

Total running time of the script: ( 5 minutes 4.081 seconds)

Gallery generated by Sphinx-Gallery


이 문서가 도움이 되었나요?
피드백을 주셔서 감사합니다.

© Copyright 2019, PyTorch.

Built with Sphinx using a theme provided by Read the Docs.

Docs

Access comprehensive developer documentation for PyTorch

View Docs

Tutorials

Get in-depth tutorials for beginners and advanced developers

View Tutorials

Resources

Find development resources and get your questions answered

View Resources