• 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'20th' b'action' b'to' b'make' b'the' b'domain' b':' b'the' b'<unk>' b'call' b'with' b'the' b'Israel' b'World' b'War' b',' b'Crash' b'Mosley' b',' b'and'
b'out' b'of' b'Ferryland' b'available' b"'" b'qualifies' b'Desert' b'concierge' b',' b'10' b'in' b'1901' b'.' b'<eos>' b'<eos>' b'=' b'=' b'Treatment' b'=' b'='
b'<eos>' b'<eos>' b'<eos>' b'=' b'=' b'Description' b'=' b'=' b'<eos>' b'<eos>' b'The' b'most' b'common' b'difference' b'from' b'Ceres' b'featuring' b'Warriors' b'is' b'during'
b'18' b'games' b'(' b'4' b'%' b'of' b'1' b'@.@' b'3' b'in' b')' b',' b'and' b'arrested' b'its' b'old' b'overall' b'source' b'in' b'less'
b'than' b'one' b'roots' b'.' b'They' b'are' b'mostly' b'a' b'suborder' b'of' b'pre' b'@-@' b'firing' b',' b'and' b'they' b'will' b'be' b'unable' b'to'
b'indicate' b'that' b'a' b'barrier' b'defined' b'by' b'referenda' b'.' b'CSIRO' b'also' b'<unk>' b'Vasco' b'@-@' b'<unk>' b',' b'exposed' b'q' b'(' b'al' b'Ni\xc3\xb1o'
b')' b',' b'<unk>' b'Cheboygan' b'Fantasy' b'(' b'<unk>' b'Imperial' b'Male' b'parrot' b')' b'along' b'numerous' b'people' b',' b'with' b'stripping' b'over' b'6' b'knots'
b'(' b'4' b'@.@' b'0' b'in' b')' b'.' b'During' b'his' b'second' b'season' b'1012' b',' b'Oscar' b'II' b'does' b'not' b'throw' b'with' b'their'
b'landslide' b'treatment' b'and' b'are' b'gifted' b'Husband' b',' b'which' b'they' b'are' b'likely' b'to' b'be' b'starling' b'Ones' b'of' b'emergency' b'trees' b'.' b'As'
b'of' b'other' b',' b'they' b'they' b'do' b'not' b'commending' b'event' b',' b'leaving' b'that' b'they' b'attract' b'gameplay' b',' b'with' b'options' b'and' b'culture'
b'and' b'download' b'"' b'slowed' b'"' b'or' b'Society' b'shops' b'.' b'He' b'controlled' b'onto' b'the' b'flooding' b'Kund' b'and' b'sometimes' b'struggled' b'that' b'they'
b'did' b'not' b'escape' b'until' b'he' b'was' b'open' b'.' b'There' b'have' b'substantially' b'wines' b'that' b'were' b'essential' b'from' b'the' b'ACC' b'Faye' b'.'
b'However' b',' b'when' b'she' b"'s" b'citizenship' b'have' b'criteria' b'of' b'passages' b'with' b'only' b'8' b'years' b':' b'cut' b'on' b'how' b'skirt' b'is'
b'not' b'the' b'neck' b'being' b'being' b'killed' b'and' b'carried' b'its' b'own' b'orbits' b',' b'this' b'has' b'the' b'same' b'half' b'active' b'able' b'.'
b'<unk>' b',' b'dominant' b'for' b'a' b'election' b'he' b'is' b'not' b'reworked' b',' b'so' b'only' b'great' b'but' b'a' b'<unk>' b'fan' b'or' b'fraud'
b'spread' b'before' b'female' b'more' b';' b'her' b'eggs' b'have' b'been' b'found' b'to' b'grow' b'for' b'day' b'.' b'<eos>' b'<eos>' b'=' b'=' b'Ecology'
b'=' b'=' b'<eos>' b'<eos>' b'A' b'survey' b'of' b'Venus' b'coincided' b'during' b'Dillon' b':' b'"' b'there' b'is' b'first' b'that' b'difference' b'on' b'disappear'
b'"' b'across' b'Wanyan' b'Young' b'High' b"'in" b'Bang' b'Bang' b'Stay' b'for' b'a' b'X' b'eclipse' b'in' b'that' b'season' b';' b'sculptor' b'S.' b'portraiture'
b'knew' b'that' b'there' b'would' b'be' b'found' b'to' b'be' b'so' b'"' b'or' b'indeed' b'well' b'for' b'that' b'year' b',' b'though' b'visually' b'to'
b'be' b'close' b'"' b'.' b'This' b'bird' b'had' b'the' b'Brent' b'Ingres' b'illustrated' b'as' b'Alfred' b'Picard' b',' b'whose' b'wife' b'who' b'enrolled' b'on'
b'several' b'books' b'.' b'The' b'tracks' b'now' b'took' b'place' b'were' b'killed' b'during' b'political' b'submarine' b'of' b'a' b'study' b'and' b'their' b'<unk>' b'.'
b'<eos>' b'<eos>' b'=' b'=' b'Demographics' b'=' b'=' b'<eos>' b'<eos>' b'Director' b'I' b'had' b'written' b'for' b'39' b'%' b',' b'formerly' b'described' b'by'
b'Bath' b'programs' b'.' b'<eos>' b'Ceres' b':' b'The' b'Seer' b'(' b'1997' b')' b',' b'the' b'FISA' b'Race' b',' b'assisted' b'by' b'an' b'former'
b'stone' b'architect' b'on' b'August' b'2' b',' b'2000' b',' b'was' b'a' b'object' b'in' b'Shadows' b'attached' b'to' b'tropical' b'Yorkshire' b'at' b'a' b'third'
b'seven' b'years' b'to' b'find' b'Mars' b'.' b'There' b'is' b'approached' b'winds' b'of' b'six' b'different' b'examination' b'believed' b'that' b'they' b'do' b'cross' b'.'
b'people' b'marks' b'an' b'attempt' b'to' b'oncoming' b'Anti' b'@-@' b'channel' b'pieces' b'of' b'at' b'least' b'3' b'@,@' b'000' b'.' b'The' b'game' b'are'
b'one' b'of' b'the' b'best' b'considered' b'devoted' b'on' b'Piloted' b'to' b'Rome' b',' b'although' b'the' b'8E' b'donated' b'goalball' b'television' b'.' b'"' b'Chess'
b'"' b',' b'each' b'of' b'which' b'wanted' b'other' b'as' b'he' b'remained' b'a' b'vacation' b',' b'as' b'well' b'as' b'"' b'they' b'represent' b'one'
b'hand' b',' b'a' b'Dateline' b'that' b'led' b'way' b'to' b'<unk>' b'us' b',' b'in' b'which' b'he' b'recorded' b'it' b'rather' b'than' b'more' b'than'
b'characteristics' b'when' b'they' b"'ll" b'happens' b'outside' b'@-@' b'overall' b'players' b',' b'a' b'other' b'freedom' b'of' b'Scottish' b'names' b'aboard' b'during' b'that' b'country'
b'.' b'"' b'Rolling' b'temperatures' b'are' b'introduced' b'to' b'Darwin' b',' b'along' b'for' b'approximately' b'5' b'months' b'after' b'he' b'enjoyed' b'it' b'and' b'unable'
b'to' b'kill' b'water' b',' b'Gentil' b'has' b'van' b'McGregor' b'.' b'Now' b'down' b',' b'they' b'lie' b'in' b'determining' b'any' b'punishment' b',' b'it'
b'<unk>' b'extensive' b'stories' b',' b'and' b'surrounded' b'similar' b'with' b'courage' b'being' b'also' b'showing' b'handled' b'for' b'a' b'powerful' b'Cope' b'.' b'<eos>' b'After'
b'this' b'first' b'meeting' b',' b'Veerashaiva' b'broadcasts' b'Walpole' b"'s" b'body' b'is' b'extremely' b'small' b'.' b'It' b'also' b'unfavorable' b'evidence' b'Erlewine' b'are' b'like'
b'an' b'fifties' b',' b'exercise' b'.' b'Both' b'observations' b'level' b'behavioral' b'mistakes' b'and' b'patterns' b'.' b'By' b'the' b'creation' b'of' b'his' b'forests' b','
b'producers' b'tries' b'for' b'his' b'car' b'cultures' b'for' b'these' b'years' b'and' b'culminating' b'his' b'zinc' b'<unk>' b'.' b'Critics' b'may' b'run' b'from' b'.'
b'As' b'1955' b',' b'themselves' b'exists' b'in' b'the' b'nature' b'of' b'complex' b'spelling' b',' b'nine' b'or' b'over' b'150' b'to' b'having' b'nine' b'maternal'
b'behaviour' b'led' b'on' b'Ismailia' b'.' b'The' b'male' b'takes' b'a' b'12' b'@-@' b'minute' b'deal' b'in' b'breaking' b'it' b'to' b'mRNA' b'when' b'the'
b'"' b'bearing' b'Ahead' b'carriage' b'"' b'would' b'be' b'apparent' b'.' b'As' b'these' b'other' b'Colin' b'cardinals' b'admits' b'the' b'name' b'Banks' b'treated' b'in'
b'turn' b'to' b'Tel' b'Saban' b',' b'"' b'Why' b',' b'Grossman' b'described' b'<unk>' b'a' b'difficult' b'bird' b'to' b'know' b'"' b',' b'"' b'The'
b'Father' b'of' b'definite' b'Agricultural' b'rule' b'"' b',' b'and' b'"' b'Social' b'Fate' b'tolerance' b'"' b'.' b'Traditionally' b',' b'there' b'is' b'little' b'universal'
b'evidence' b'of' b'treatment' b'development' b',' b'being' b'even' b'connected' b'to' b'speak' b'.' b'Wisteria' b'Romanesque' b'brazen' b'see' b'eggs' b'protested' b'to' b'be' b'used'
b'to' b'discuss' b'foraging' b'with' b'the' b'indigenous' b'crime' b'being' b'understood' b'away' b'again' b'.' b'They' b'likely' b'easier' b'to' b'maintain' b'defenders' b'that' b'they'
b'have' b'claimed' b'that' b'that' b'"' b'ease' b']' b'his' b'record' b'is' b'death' b'off' b'[' b'Jeremy' b']' b'it' b'is' b'travelling' b'.' b'In'
b'the' b'seventeenth' b'century' b',' b'in' b'1863' b',' b'they' b'are' b'false' b'by' b'the' b'Mid' b'1950' b'<unk>' b"'" b'"' b'.' b'sweeping' b'for'
b'Palmyrene' b'associates' b'in' b'Japan' b'and' b'by' b'August' b'21' b',' b'the' b'Vulnerable' b'ostrich' b'these' b'birds' b'for' b'discuss' b'zoologist' b'la' b'sodium' b','
b'thought' b':' b'"' b'"' b'I' b'are' b'sawgrass' b'upon' b'good' b'during' b'they' b'have' b'choice' b'if' b'they' b'seem' b'it' b'has' b'been' b'chromosomes'
b'connected' b'.' b'"' b'After' b'hungry' b'<unk>' b',' b'color' b',' b'<unk>' b',' b'is' b'boycotted' b'from' b'depends' b'on' b'a' b'planet' b'diagram' b','
b'but' b'judge' b'rectify' b'it' b'to' b'Waddon' b',' b'but' b'aspired' b'that' b'his' b'forecastle' b'display' b'is' b'driven' b'to' b'present' b'aggressive' b'mind' b'easy'
b'.' b'He' b'recorded' b'such' b'at' b'a' b'Reagan' b'union' b',' b'Pryce' b'often' b'asserted' b'by' b'an' b'benefit' b',' b'a' b'planet' b'that' b'thick'

이 모델이 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.739034

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

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

마치며

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

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

Total running time of the script: ( 6 minutes 9.279 seconds)

Gallery generated by Sphinx-Gallery


이 튜토리얼이 어떠셨나요?

© Copyright 2022, PyTorch & 파이토치 한국 사용자 모임(PyTorch Korea User Group).

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

PyTorchKorea @ GitHub

파이토치 한국 사용자 모임을 GitHub에서 만나보세요.

GitHub로 이동

한국어 튜토리얼

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

튜토리얼로 이동

커뮤니티

다른 사용자들과 의견을 나누고, 도와주세요!

커뮤니티로 이동