• 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"'s" b'all' b'@-@' b'time' b'wives' b'known' b'as' b'"' b'and' b'so' b'mention' b'spurs' b'.' b'"' b'L\xc3\xbctzow' b'writes' b'Midge' b"'s" b'main' b'motion'
b'to' b'Little' b'cities' b',' b'"' b'one' b'of' b'a' b'precedent' b'is' b'unclear' b'to' b'need' b'to' b'visit' b'a' b'battalion' b'and' b'dancers' b'and'
b'most' b'type' b'of' b'cheek' b'.' b'"' b'<unk>' b'team' b'commented' b'that' b'"' b'a' b'book' b'is' b'going' b'to' b'win' b'account' b'\xe2\x80\x98' b'honor'
b'of' b'forelimbs' b'&' b'<unk>' b'bowling' b'for' b'going' b',' b'Heatseekers' b',' b'and' b'his' b'fishing' b'<unk>' b'in' b'his' b'efforts' b'is' b'starring' b'about'
b'that' b'there' b'has' b'received' b'so' b'.' b'"' b'Fleets' b'felt' b'to' b'be' b'controlling' b'up' b'to' b'this' b'college' b'script' b"'s" b'"' b'total'
b'Lycoming' b'"' b',' b'The' b'translation' b'of' b'Arabia' b'in' b'force' b'The' b'87th' b'show' b',' b'Lewis' b'"' b'the' b'Pitt' b'"' b',' b'his'
b'son' b'(' b'Ottomans' b')' b',' b'a' b'many' b'documentary' b'promising' b',' b'poet' b'Bond' b',' b'coordinated' b'Iraqi' b'television' b'Fantasy' b'VanDerWerff' b'.' b'The'
b'Screen' b'consultancy' b'Evening' b'Post' b'<unk>' b'and' b'core' b'Segers\xc3\xa4ll' b'<unk>' b'the' b'yellow' b'domestic' b'effect' b'In' b'his' b'1960s' b'<eos>' b'In' b'1900' b'Hornung'
b'and' b'Lester' b'Roberts' b'allowed' b'a' b'three' b'@-@' b'act' b'<unk>' b'to' b'retain' b'European' b'PSP' b'programs' b'before' b'Bartlett' b'and' b'his' b'daughter' b'run'
b'<unk>' b'sighting' b'for' b'the' b'book' b'.' b'Known' b'after' b'his' b'death' b'on' b'sequel' b'with' b'Lemon' b'Nelson' b'Shakespeare' b',' b'Alfred' b'<unk>' b','
b'John' b'Price' b',' b'The' b'Jeremy' b'Field' b',' b'and' b'Milton' b'was' b'Veblen' b'.' b'The' b'game' b'was' b'originally' b'declared' b'in' b'November' b'2012'
b',' b'challenged' b'criticism' b'to' b'attribute' b'[' b'of' b'the' b'farewell' b'with' b'John' b'<unk>' b'and' b'his' b'discovery' b'.' b'Eva' b'State' b'was' b'a'
b'distant' b'manufacturer' b',' b'describing' b'in' b'assistance' b'.' b'<eos>' b'<eos>' b'=' b'=' b'=' b'Coaching' b'and' b'fires' b'=' b'=' b'=' b'<eos>' b'<eos>'
b'Fez' b'controlled' b'football' b'\xe2\x80\x93' b'71' b'in' b'2007' b'in' b'reserve' b',' b'to' b'add' b'himself' b'<unk>' b'its' b'20' b'increases' b'later' b',' b'with'
b'40' b'earned' b'1' b'\xc2\xb0' b'miles' b'(' b'72' b'km' b'/' b'h' b')' b'.' b'Despite' b'winds' b'in' b'the' b'year' b'and' b'<unk>' b','
b'it' b'was' b'increasingly' b'misunderstood' b'into' b'by' b'Eric' b'Nights' b'.' b'Those' b'who' b'would' b'be' b'Supervisor' b'to' b'be' b'just' b'positive' b',' b'as'
b'soon' b'force' b'or' b'agricultural' b'pressure' b'as' b'they' b'are' b'.' b'<eos>' b'red' b'technique' b'was' b'attributed' b'to' b'turn' b'reconcile' b'(' b'56' b'@.@'
b'0' b'in' b')' b'at' b'Battle' b"'" b'The' b'Soul' b'of' b'Natural' b'Cross' b'(' b'6' b'point' b')' b',' b'which' b'pass' b'magnitude' b'with'
b'the' b'two' b'habitation' b'of' b'refuse' b'.' b'Three' b'dark' b'individuals' b'were' b'fired' b'around' b'an' b'one' b'of' b'1' b'@,@' b'000' b'levels' b'plan'
b',' b'with' b'a' b'practice' b'pass' b'across' b'creating' b'the' b'2009' b'race' b'and' b'adolescence' b',' b'and' b'it' b'Mountain' b'is' b'strongly' b'three' b'for'
b'first' b'fish' b'.' b'<eos>' b'North' b'Korean' b'language' b'concluded' b'containing' b'Mesolithic' b'251' b'obstacles' b'in' b'the' b'Spanish' b'period' b'.' b'Lyrically' b',' b'as'
b'they' b'were' b'never' b'the' b'<unk>' b',' b'yellow' b'theologians' b',' b'and' b'recently' b'early' b'from' b'August' b'life' b'.' b'"' b'It' b"'s" b'the'
b'largest' b'interests' b'to' b'have' b'its' b'distinctive' b'Boo' b'"' b'.' b'Today' b'is' b'Jellicoe' b"'s" b'first' b'drive' b'to' b'promote' b'transit' b'in' b'flying'
b'horses' b'in' b'1978' b'.' b'Then' b'in' b'the' b'media' b',' b'several' b'tracks' b'were' b'illuminated' b'throughout' b'the' b'summer' b'of' b'2012' b',' b'begin'
b'across' b'a' b'absence' b'and' b'declined' b'in' b'Normally' b'achieve' b'questions' b'and' b'rock' b',' b'to' b'provide' b'such' b'Google' b'calls' b',' b'to' b'help'
b'a' b'compromise' b'to' b'throw' b'besides' b',' b'revenues' b',' b'or' b'Chesham' b'.' b'The' b'first' b'further' b'approval' b'date' b'is' b'based' b'in' b'giant'
b'domestic' b',' b'shooting' b',' b'the' b'stable' b'subject' b"'s" b'fuel' b',' b'symbolically' b',' b'with' b'New' b'York' b',' b'Northern' b'Australia' b',' b'and'
b'R\xc3\xa9union' b'.' b'Some' b'New' b'@-@' b'Korean' b'high' b'sand' b'will' b'have' b'been' b'a' b'official' b'staple' b',' b'with' b'many' b'<unk>' b',' b'dark'
b'or' b'control' b',' b'or' b',' b'while' b'at' b'behest' b'.' b'<eos>' b'3' b':' b'cheats' b'plantations' b'and' b'yellow' b'performances' b'has' b'been' b'widely'
b'carried' b'to' b'circumstances' b'(' b'nearing' b'valued' b'tests' b')' b'.' b'Montgomery' b'associating' b'woods' b'by' b'hearing' b'rendering' b'such' b'the' b'tropical' b'Key' b'demand'
b'as' b'"' b'A' b'tiger' b'"' b'.' b'Flooding' b'belongs' b'to' b'2012' b'in' b'many' b'manner' b',' b'New' b'York' b',' b'reddish' b',' b'and'
b'carvings' b',' b'mixed' b',' b'open' b',' b'and' b'even' b'nesting' b',' b'drunkards' b',' b'and' b'high' b',' b'who' b'had' b'taxonomic' b'fist' b'.'
b'<eos>' b'The' b'typical' b'exact' b'election' b'in' b'Ireland' b'are' b'known' b',' b'which' b'no' b'less' b'than' b'one' b'hundred' b'and' b'western' b'thirteen' b'have'
b'urinary' b'spores' b',' b'and' b'comes' b'on' b'to' b'be' b'discarded' b'.' b'safe' b',' b'Hairan' b'Paul' b'rufipes' b'pointed' b'on' b'to' b'impose' b'other'
b'photographs' b'.' b'Condoms' b'is' b'divided' b'from' b'higher' b'conditions' b'conducted' b'by' b'Welles' b'.' b'<eos>' b'<unk>' b'diving' b'sales' b'in' b'Allosaurus' b'of' b'Prey'
b'memorials' b',' b'is' b'an' b'white' b'<unk>' b'Laz' b',' b'a' b'variety' b'of' b'other' b'coral' b'interpretations' b'houses' b'in' b'the' b'common' b'starling' b','
b'but' b'in' b'mottled' b'of' b'<unk>' b'flow' b',' b'Ireland' b'was' b'redesignated' b'as' b'1913' b'was' b'established' b'by' b'items' b'of' b'entertainment' b',' b'complimenting'
b',' b'concrete' b'<unk>' b',' b'yellow' b'auxiliary' b'and' b'stylized' b'Harbor' b'.' b'The' b'second' b'version' b'(' b'Forms' b',' b'searching' b'to' b'Australia' b')'
b'are' b'that' b'of' b'them' b',' b'before' b'some' b'of' b'those' b'households' b'for' b'Ireland' b'and' b'<unk>' b'red' b'species' b'are' b'the' b'largest' b'little'
b'.' b'In' b'the' b'1995' b'eye' b',' b'the' b'kakapo' b'is' b'currently' b'spreading' b'by' b'light' b'days' b',' b'and' b'distributed' b'not' b'open' b'difficulty'
b'it' b'in' b'agricultural' b'toxic' b',' b'such' b'as' b'Mai' b'<unk>' b',' b'meaning' b'an' b'female' b'tree' b'.' b'The' b'counteract' b'window' b'of' b'black'
b'presence' b'and' b'extrasolar' b'manufacturing' b'species' b'have' b'prank' b';' b'they' b'have' b'their' b'own' b'cause' b'sent' b'by' b'slower' b'administratively' b'trees' b',' b'or'
b'one' b'of' b'her' b'cores' b'through' b'world' b',' b'most' b'obvious' b'or' b'Commonwealth' b',' b'even' b'hard' b'.' b'<eos>' b'Northern' b'Ireland' b'in' b'Europe'
b'stretches' b'to' b'bachelor' b'the' b'River' b'Theoretical' b'-' b'1907' b',' b'with' b'the' b'foreign' b'purposes' b'of' b'toss' b',' b'DAGs' b',' b'CRE' b','
b'and' b'pure' b'human' b'plaques' b',' b'often' b'by' b'Human' b'Burnside' b"'s" b'diet' b'against' b'which' b'assembly' b'them' b',' b'brought' b'promotion' b'.' b'<eos>'
b'As' b'1971' b',' b'males' b'do' b'not' b'meet' b'that' b'when' b'gradient' b'between' b'plants' b',' b'and' b'most' b'birds' b'may' b'be' b'long' b'('
b'late' b'48' b'Ireland' b'repelled' b')' b'.' b'When' b'they' b'will' b'be' b'unlikely' b'due' b'to' b'<unk>' b'or' b'New' b'Zealand' b',' b'<unk>' b','
b'they' b'surprised' b'in' b'Free' b'.' b'It' b'is' b'often' b'barriers' b'to' b'ignore' b'such' b'countries' b'.' b'diagram' b'passed' b'me' b'for' b'several' b'sight'
b',' b'which' b'are' b'more' b'controversial' b',' b'it' b'is' b'active' b'funny' b'.' b'After' b'their' b'numbers' b',' b'they' b'have' b'worked' b'their' b'<unk>'
b'or' b'there' b'is' b'no' b'such' b'@-@' b'profile' b'bowls' b',' b'with' b'creating' b'their' b'properties' b'of' b'endanger' b'Socorro' b'.' b'These' b'starlings' b'have'

이 모델이 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].reshape(-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.944608
Size (MB): 79.738778

두 번째로, 평가 손실값은 같으나 추론(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): 241.1
loss: 5.168
elapsed time (seconds): 166.4

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

마치며

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

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

Total running time of the script: ( 8 minutes 4.154 seconds)

Gallery generated by Sphinx-Gallery

공식 문서 (영어)

PyTorch 공식 문서입니다.

공식 문서로 이동

한국어 튜토리얼

한국어로 번역 중인 PyTorch 튜토리얼입니다.

튜토리얼로 이동

커뮤니티

다른 사용자들과 의견을 나눠보세요!

커뮤니티로 이동