• 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'of' b'his' b'expressway' b'.' b'This' b'is' b'now' b'small' b',' b'while' b'creating' b'that' b'Home' b'Love' b'Armies' b'walked' b'of' b'engineers' b',' b'it'
b'declined' b'a' b'large' b'destination' b'for' b'his' b'film' b"'" b'a' b'boat' b'at' b'night' b'.' b'<eos>' b'In' b'his' b'1902' b'view' b'of' b'a'
b'symbol' b'of' b'evidence' b',' b'the' b'official' b'American' b'monarch' b'had' b'been' b'affected' b'in' b'DD' b'both' b'leaves' b'the' b'artist' b'he' b'represented' b'the'
b'collection' b',' b'suggesting' b'that' b'they' b'were' b'in' b'midpoint' b'.' b'<eos>' b'Kaboom' b'in' b'the' b'number' b'of' b'unique' b'dissolve' b'3D' b'bodies' b'from'
b'over' b'one' b'@-@' b'planet' b'are' b'<unk>' b',' b'which' b'was' b'introduced' b'in' b'parts' b'of' b'Baltimore' b'and' b'framed' b'a' b'Garuda' b',' b'a'
b'specific' b'accidental' b'city' b',' b'with' b'passes' b'as' b'Payette' b'calospora' b'and' b'into' b'the' b'base' b'of' b'shout' b'.' b'The' b'presence' b'of' b'these'
b'have' b'been' b'designated' b'as' b'counting' b'.' b'In' b'his' b'1966' b'well' b'eSports' b'territories' b',' b'spring' b'1948' b'was' b'developed' b'by' b'<unk>' b'<unk>'
b':' b'one' b'of' b'the' b'Jackman' b'that' b'was' b'a' b'mysterious' b',' b'ambitious' b'movie' b',' b'and' b'has' b'necessary' b'and' b'from' b'the' b'also'
b'ruins' b'Evolution' b'.' b'All' b',' b'brick' b'interpretations' b'of' b'eight' b'substances' b'(' b'sometimes' b'ecological' b'cross' b'content' b')' b',' b'been' b'introduced' b'in'
b'these' b'regions' b'having' b'removed' b'from' b'1' b'@,@' b'500' b'audiences' b'.' b'<eos>' b'In' b'announcement' b'of' b'first' b'point' b'the' b'election' b'led' b'to'
b'1' b'@,@' b'420' b'books' b'from' b'the' b'country' b'opened' b'during' b'the' b'speech' b'.' b'<eos>' b'<eos>' b'=' b'=' b'Impact' b'=' b'=' b'<eos>'
b'<eos>' b'1973' b',' b'a' b'phenomenon' b'for' b'@-@' b'Sherborne' b'reached' b'danger' b'his' b'dish' b'meet' b'of' b'Wales' b'.' b'Like' b'the' b"'" b'<unk>'
b"'" b',' b'and' b'protagonist' b'range' b',' b'depending' b'in' b'the' b'stage' b',' b'could' b'not' b'Taiwan' b'in' b'series' b'without' b'birds' b'.' b'They'
b'commercially' b'clad' b'in' b'total' b'of' b'the' b'century' b'at' b'a' b'standstill' b'over' b'events' b'on' b'a' b'must' b'social' b'open' b'forms' b'of' b'the'
b'4' b'\xe2\x80\x93' b'4' b'doesn' b'Bang' b'(' b'Falco' b'+' b'Kapoor' b')' b'.' b'In' b'part' b',' b'the' b'kakapo' b'find' b'Arikamedu' b'proper' b'@-@'
b'northeast' b',' b'which' b'will' b'be' b'called' b'off' b'.' b'In' b'the' b'summer' b'of' b'2010' b',' b'rainfall' b'of' b'local' b'Rico' b'and' b'romp'
b'was' b'<unk>' b'.' b'<unk>' b'and' b'increased' b'outlets' b'were' b'introduced' b'for' b'a' b'special' b'behest' b'.' b'The' b'body' b'varies' b'work' b'that' b'control'
b'of' b'Mfume' b'moves' b'before' b'other' b'or' b'small' b'scholars' b',' b'on' b'what' b'their' b'parts' b'of' b'the' b'latter' b',' b'or' b'bearing' b'much'
b'information' b'.' b'This' b'is' b'known' b'for' b'predators' b'of' b'a' b'lazy' b'by' b'receiving' b'Ry\xc5\xabjinmura' b'through' b'9' b'or' b'1' b'p.m.' b'indicated' b'('
b'2' b'@.@' b'4' b'to' b'8' b'@.@' b'5' b'km2' b')' b'.' b'Because' b'Ireland' b'displays' b'26th' b'Tribune' b',' b'efficiently' b'led' b'a' b'maximum'
b'@-@' b'level' b'loop' b'of' b'kilometers' b':' b'This' b'species' b'from' b'rebuilding' b'around' b'1' b'@.@' b'5' b'\xe2\x80\x93' b'8' b'.' b'The' b'eye' b'holds'
b'display' b'under' b'five' b'ordinary' b'accidents' b'and' b'a' b'newly' b'temporary' b'giant' b'team' b',' b'then' b'high' b'for' b'this' b'species' b'.' b'This' b'asteroid'
b'fires' b'toward' b'a' b'light' b'road' b'in' b'all' b'island' b'charts' b'and' b'sing' b'cattle' b'during' b'London' b'.' b'A' b'large' b'tale' b'thick' b'25'
b'in' b'dams' b'and' b'silicon' b'stands' b'with' b'a' b'long' b'Bhakti' b'and' b'<unk>' b'documentary' b'feeding' b'seemingly' b'down' b'until' b'scoring' b'.' b'Other' b'female'
b'males' b'sets' b'when' b'it' b'may' b'be' b'transported' b'to' b'ribosomes' b',' b'so' b'their' b'boxes' b'are' b'the' b'common' b'definition' b'defined' b'by' b'several'
b'artillery' b'nests' b',' b'before' b'that' b'practice' b'there' b'are' b'no' b'active' b'damage' b'and' b'risk' b'\xe2\x80\x93' b'<unk>' b'just' b'that' b'are' b'known' b'.'
b'To' b'wings' b'Synopsis' b',' b'a' b'nearby' b'privacy' b'is' b'facing' b',' b'armoured' b'elements' b'and' b'their' b'body' b'to' b'his' b'side' b'and' b'risk'
b'of' b'Ireland' b'.' b'"' b'Ruler' b'art' b'control' b'is' b'murdered' b'by' b'another' b'pattern' b'and' b'its' b'affection' b'and' b'who' b'might' b'let' b'absolutely'
b'<unk>' b'"' b'.' b'The' b'development' b'of' b'this' b'population' b'is' b'clinched' b'as' b'she' b'says' b'complemented' b'<unk>' b';' b'they' b'find' b'.' b'<eos>'
b'When' b'though' b'several' b'forms' b'are' b'found' b',' b'there' b'is' b'10' b'observers' b'of' b'common' b'birds' b'though' b'ordination' b'have' b'hampered' b'males' b'that'
b'occurred' b'use' b'.' b'After' b'not' b',' b'there' b'became' b'their' b'evidence' b',' b'the' b'character' b'is' b'anyway' b'.' b'In' b'doing' b'church' b'deposits'
b',' b'house' b'feathers' b',' b'encountering' b'in' b'their' b'forest' b'and' b'her' b'desired' b'orange' b',' b'Burns' b'or' b'calls' b'in' b'a' b'circular' b'career'
b'.' b'A' b'variant' b'of' b'1' b'@.@' b'5' b'years' b'reproductive' b'before' b'three' b'years' b',' b'one' b'another' b'head' b'females' b',' b'1' b'@.@'
b'5' b'in' b'(' b'coniferous' b')' b'wide' b'membrane' b',' b'and' b'feathers' b'may' b'be' b'revealed' b'or' b'notably' b'or' b'rounded' b'up' b'to' b'30'
b':' b'00' b'(' b'13' b'@.@' b'8' b'\xce\xbcm' b')' b'.' b'About' b'16' b'%' b'than' b'<unk>' b'.' b'The' b'female' b'is' b'later' b'calculated'
b',' b'and' b'with' b'up' b'1' b'%' b'of' b'Mars' b'(' b'including' b'less' b'points' b')' b',' b'food' b'residents' b'or' b'long' b'parrots' b'.'
b'The' b'kakapo' b'likely' b'are' b'rapid' b';' b'd4' b'is' b'fed' b'for' b'42' b'kilometres' b'per' b'hour' b'(' b'41' b'@.@' b'5' b'inches' b')'
b'and' b'two' b'flexible' b'schools' b',' b'from' b'once' b'of' b'2' b'communities' b'.' b'M.' b'koreanus' b'impacts' b'for' b'1692' b'scriptures' b',' b'which' b'has'
b'been' b'stem' b'into' b'occasionally' b'<unk>' b'.' b'Additionally' b',' b'mature' b'areas' b'(' b'mostly' b'undertaken' b'only' b'to' b'be' b'classified' b'as' b'with' b'100'
b'neon' b'egg' b')' b',' b'allows' b'ara' b'isolated' b'in' b'urine' b'.' b'Europium' b'often' b'serve' b'a' b'breeding' b'sense' b',' b'at' b'[' b'of'
b'ends' b'silver' b'for' b'1' b',' b'to' b'6' b':' b'130' b',' b'2' b'@.@' b'4' b'(' b'II' b')' b',' b'<unk>' b'(' b'95'
b'\xe2\x80\x93' b'18' b')' b',' b'is' b'separated' b'with' b'critics' b',' b'although' b'is' b'mainly' b'to' b'defend' b'five' b'or' b'encourage' b'more' b'years' b'seen'
b'to' b'mean' b'other' b'birds' b',' b'explicitly' b'if' b'a' b'pair' b'indicates' b'the' b'remains' b'of' b'magic' b'.' b'Common' b'bodies' b'would' b'produced' b'well'
b'taking' b'giving' b'of' b'Milligan' b',' b'some' b'countries' b'are' b'typically' b'<unk>' b'(' b'numbers' b'such' b'as' b'some' b'lit' b'eucalypt' b'objects' b'such' b'as'
b'185' b'axis' b'as' b'they' b'are' b'omnivorous' b';' b'roughly' b'56' b'%' b')' b'.' b'<eos>' b'The' b'supervising' b'starling' b'of' b'420' b'\xe2\x80\x93' b'95'
b'centimetres' b'(' b'now' b'in' b'males' b')' b',' b'which' b'makes' b'unlike' b'primitive' b'starlings' b',' b'was' b'particularly' b'infected' b'to' b'viability' b',' b'and'
b'is' b'almost' b'more' b'widely' b',' b'because' b'they' b'may' b'be' b'abundant' b',' b'with' b'their' b'entire' b'debt' b'cycle' b'even' b'threats' b'of' b'deep'
b'gigalitres' b'<unk>' b'.' b'They' b'steadily' b'in' b'renegade' b'seed' b'areas' b',' b'and' b'while' b'some' b'male' b'advances' b',' b'E' b'carcasses' b'and' b'trees'
b'(' b'also' b'most' b'solar' b'common' b'weaker' b'larger' b'<unk>' b'are' b'our' b'inedible' b',' b'inside' b'foraging' b',' b'chemical' b'common' b'or' b'stumps' b')'
b'on' b'each' b'other' b'that' b'can' b'have' b'been' b'respect' b'.' b'Above' b'Phlebopus' b'are' b'reddish' b'micrometres' b'.' b'Generally' b',' b'Unfortunately' b'on' b'the'

이 모델이 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.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): 174.6
loss: 5.168
elapsed time (seconds): 121.0

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

마치며

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

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

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

Gallery generated by Sphinx-Gallery

공식 문서 (영어)

PyTorch 공식 문서입니다.

공식 문서로 이동

한국어 튜토리얼

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

튜토리얼로 이동

커뮤니티

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

커뮤니티로 이동