[D&A 운영진 딥러닝 스터디] 4주차 1차시

권유진·2022년 1월 22일
0

D&A 운영진 스터디

목록 보기
7/17

자연어 처리(NLP; Natural Language Processing)

  • Text 데이터를 분석하고 모델링하는 분야
  • NLU + NLG
    • 자연어 이해(NLU; Natural Language Understanding): 자연어를 이해하는 영역
    • 자연어 생성(NLG; Natural Language Generation): 자연어를 생성하는 영역
  • NLP 과정
    1. Task 확인 후 데이터 수집
    2. Vectorization: 문자를 숫자로 바꾸는 과정
    3. Modeling
  • Downstream Task
    1. 감정 분석(Sentiment Analysis)
    • 문장에 대한 정보를 통해 특정 감정을 분류
    1. 요약(Summarization)
    • Extractive Summarization(text에서 중요한 부분 추출)과 Abstractive Summarization(text 이해 후 새로운 문장 생성)으로 분류
    1. 기계 번역(Translation)
    2. 질문 응답(Question Answering)
    • 주어진 문서를 이해하고 문서 속 정보에 대한 질문을 했을 때 답을 이끌어내는 task
    1. 등등

Tokenizaiton

  • 인간은 문장을 의미를 갖고 있는 부분으로 쪼개고 그 부분의 의미를 조합
  • Text Segmentation(문장을 의미를 갖고 있는 부분으로 쪼갬) & Representation(나누어진 부분을 숫자로 바꿈)
  • Tokenization: 문장을 의미 있는 부분으로 나누는 과정(Lexical Analysis)
    • Token: 문장을 구성하는 기본 단위

띄어쓰기를 통해 Tokenization

sentences = ['나는 책상 위에 사과를 먹었다.',
            '알고 보니 그 사과는 Jason 것이었다.',
            '그래서 Jason에게 사과를 했다.']

token2idx = {}
index = 0
for sentence in sentences:
    tokens = sentence.split(sentence)
    for token in tokens:
        if token2idx.get(token) == None:
            token2idx[token] = index
            index += 1
  • 여기서 token을 저장해 놓은 사전인 token2idx를 Vocabulary라고 한다.
  • 여기서의 index를 문자를 숫자로 바꾸는 데 사용
def indexed_sentence(sentence):
    return [token2idx[token] for token in sentence]

list(map(indexed_sentence, list(map(lambda x: x.split(), sentences))))

문제점

1. 없는 단어가 나오면 어떻게 하지?
2. 조사가 다르다면 다른 단어라고 해야 하나?
3. 동음이의어는 처리가 안되네?

Corpus&OOV(Out-of-Vocabulary)

  • OOV(Out-of-Vocabulary): 처음 본 단어(token)이 등장하는 현상
    • 이러한 경우 특수한 token인 <unk>를 만들어 <unk>로 변환
  • 사전을 풍부하게 만들면 OOV 해결
    • Corpus: token을 모으기 위해 모아 놓은 문장의 모음
    • Corpus를 잘 만드는 것도 모델 성능 향상의 큰 요인
    • 특정 영화나 뉴스 또는 위키피디아 기반의 다양한 Corpus 존재
  • Corpus가 커질수록 사전의 크기 역시 커져 모델 사이즈도 커진다.
    • 메모리에 부담을 주기 때문에 token을 효율적으로 만드는 것이 중요

띄어쓰기를 통한 Tokenization은 매우 비효율적이다.

  • 띄어쓰기가 잘 안되어 있을 수 있다.
  • 또한 한글의 경우에는 단어 단위로 띄어쓰기를 하지 않는다.
    • 음운, 음절, 형태소, 단어, 어절, 문장

Character based tokenization

  • 글자를 Token으로 사용
  • 사전의 크기가 줄어듦
  • OOV 해결
idx2char = {0:'<pad>', 1:'<unk>'}

srt_idx = len(idx2char)
for x in range(32, 127): # 영어 추가
    idx2char.update({srt_idx: chr(x)})
    srt_idx += 1
    
for x in range(int('0x3131',16), int('0x3163',16)+1): # 한국어 추가
    idx2char.update({srt_idx: chr(x)})
    srt_idx += 1
for x in range(int('0xAC00',16), int('0xD7A3',16)+1):
    idx2char.update({srt_idx: chr(x)})
    srt_idx += 1
    
char2idx = {v:k for k,v in idx2char.items()}
list(map(lambda x: [char2idx.get(c,0) for c in x], sentences))

문제점

1. 표현법에 대한 학습이 어려움(글자 하나는 의미 보유 x)

n-gram Tokenization

  • 글자의 특정 연속성이 의미를 가진 단어라는 것을 학습
  • 그것으로 문장이나 문단과 같은 더 긴 형태의 글 이해
  • 여러 개의 연속된 윈도우를 단위로 살펴보기 위해 n-gram 방법 도입
    • n=1: uni-gram(Character based tokenization)
    • n=2: bi-gram
    • n=3: tri-gram
    • n \ge 4: n-gram
  • 각 글자를 기준으로 윈도우가 이동하면서 token으로 잡아냄
  • 띄어쓰기 단위로도 n-gram 사용 가능

문제점

1. 쓸모 없는 조합이 너무 많이 생성됨
2. 사전이 과하게 커진다.

BPE(Byte Pair Encoding)

  • n-gram의 이점을 챙기면서 그중 의미가 있는 것들만 token으로 사용하는 방법
  • 반복적으로 나오는 데이터의 연속된 패턴을 치환하는 방식을 사용해 데이터를 효율적으로 저장
word = 'abbcabcab'
word = word.replace('ab','X')
word = word.replace('cX','Y')
  1. 단어 횟수를 기록한 사전을 만듦
  2. 각 단어에 대해 연속된 2개의 글자와 숫자를 세어 가장 많이 나오는 글자 2개의 조합을 찾는다.(Character bi-gram)
  3. 두 글자를 합쳐 기존의 사전의 단어를 수정
  4. 미리 정해 놓은 횟수만큼 2~3번 과정 반복
import re, collections

def get_stats(vocab): # 2번 과정 함수
    pairs = collections.defaultdict(int) # dictation 생성
    for word, freq in vocab.items():
        symbols = word.split() # 띄어쓰기 기준으로 나눔
        for i in range(len(symbols)-1):
            pairs[symbols[i],symbols[i+1]] += freq # 가장 많이 나오는 글자 조합 찾기
    return pairs
def merge_vocab(pair, v_in): # 3번 과정 함수
    v_out = {}
    bigram = re.escape(' '.join(pair)) # 결합
    p = re.compile(r'(?<!\\S)' + bigram + r'(?!\\S)')
    for word in v_in: # v_in에 있는 쌍 변환
        w_out = p.sub(''.join(pair), word)
        v_out[w_out] = v_in[word]
    return v_out

vocab = {'l o w </w>':4,  'l o w e r </w>':2,
        'n e w e s t </w>':6, 'w i d e s t </w>':3}
num_merges = 10
for i in range(num_merges):
    pairs = get_stats(vocab)
    best = max(pairs, key=pairs.get)
    vocab = merge_vocab(best, vocab)
    print(f'Step {i+1}')
    print(best)
    print(vocab)
    print('\\n')
  • 자주 등장하는 글자의 연속인 subwords 찾을 수 있었음
  • 잡을 수 없던 token 잡아냄(동음이의어, 조사 포함한 단어 등)
  • OOV는 여전히 존재하지만 Corpus 크기 키우면 극복 가능
  • 적절한 Iteration 지정하는 것이 중요(무조건 많이 반복한다고 좋은 Vocabulary가 생성되는 것이 아님)

Pre-Trained Tokenizer

  1. Sentencepiece
!pip install sentencepiece

import sentencepiece as spm

s = spm.SentencePieceProcessor(model_file='spm.model')
for n in range(5):
    s.encode('New York', out_type=str, enable_sampling=True, alpha=0.1, nbest=-1)
  1. BERT Tokenizer
!pip install transformers

from transformers import BertTokenizer

tokenizer = BertTokenizer.from_pretrained('bert-base-uncased') # bert-base-uncased 모델에서 사용한 tokenizer 사용
tokenizer.tokenize('My dog is cute. He likes playing')

tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-uncased') # bert-base-multilingual-uncased모델에서 사용한 tokenizer(다양한 언어 학습)
tokenizer.tokenize('My dog is cute. He likes playing')
tokenizer.tokenize('나는 책상 위에 사과를 먹었다. 알고 보니 그 사과는 Jason 것이었다. 그래서 Jason에게 사과를 했다.')

Representation

  • 문장을 어떻게 잘 나눌 지: Tokenization
  • token을 유한한 개수를 가진 변수로 생각하고 범주형 자료로 표현할 시 사칙연산을 수행했을 때 숫자가 표현하고 있는 본연의 의미 변화가 달라짐

One-Hot Encoding

  1. Corpus를 모두 Tokenization해 Vocabulary 만들고 각 token마다 index 정함
  2. 단어 사전의 크기를 V = len(token vocabulary)라 하고 V 길이의 모두 0값을 가진 0벡터 생성
  3. 각 token은 해당하는 index의 값만 1의 값을 가진 벡터로 표현

python list 활용

token2idx = {}
index = 0
for sentence in sentences:
    tokens = sentence.split(sentence)
    for token in tokens:
        if token2idx.get(token) == None:
            token2idx[token] = index
            index += 1

v = len(token2idx)
token2vec = [([0 if i != idx else 1 for i in range(v)], idx, token) for token, idx in token2idx.items()]

numpy 활용

import numpy as np

for sentence in sentences:
    onehot_s = []
    tokens = sentence.split()
    for token in tokens:
        if token2idx.get(token) != None:
            vector = np.zeros((1,v))
            vector[:, token2idx[token]] = 1
            onehot_s.append(vector)

문제점

1. Sparse하다. (대부분 0이기 때문에 비효율적이다.) - 파이썬의 sparse 라이브러리 활용해 계산, 메모리 효율화 가능

Frequency-based Method

  • 문장에 있는 token의 등장 횟수를 세어 표현하는 방식
  • token의 횟수에만 집중하기 때문에 주머니 같은 곳에 token을 모아 놓고 단어를 뽑아 문장을 표현한다고 해서 Bag of Word(BoW)라고 표현
  • token을 원-핫 인코딩한 결과를 문장마다 합하면 쉽게 나타낼 수 있음
  • 단어 빈도(Term-Frequency, TF)를 이용한 방식
    • 모든 token에 대해 BoW 형태로 표현해 만든 TDM(Term-Document Matrix)를 모델링에 사용
      • a, an, the, of와 같은 특정 token은 문장이나 문단과 상관없이 많이 등장
      • 등장빈도가 적은 token에 악영향
    • TF-IDF(Term Frequency - Inverse Document Frequency) 사용
      idf(t,S)=log(S{sS:ts}+ϵ)t:token,s:sentence,S:sentencesettfidf=tf(t,s)idf(t,s)idf(t,S) = \log(\cfrac{|S|}{|\{s \in S:t \in s \} + \epsilon|})\\ t: token, s:sentence, S: sentence\,set\\ tf-idf = tf(t,s) * idf(t,s)
      • idf: 해당 token이 등장하는 문서 수의 비율의 역수 (많은 문서에 포함되었을수록 0에 가까워짐)

문제점

1. 순서가 무시됨

Dense Representation

  • 원-핫 인코딩의 sparse한 단점 극복

Word2Vector

출처: https://wikidocs.net/22660
  • token의 의미는 주변 token의 정보로 표현된다.
  • 비슷한 token은 비슷한 위치의 벡터로 표현
  • 관계를 연산으로 설명할 수 있는 벡터 표현이 가능
  • 학습 방법: CBOW, Skip-Gram
    • 기준 token의 양 옆 token을 포함한 윈도우가 이동하면서 윈도우 속 token과 기준 token의 관계를 학습시키는 과정을 진행
      • 기준 token을 target, 주변 token을 context라고 표현
    • CBOW: Context Token의 벡터로 변환해 더한 후 Target Token 맞춤
    • Skip-Gram: Target Token을 벡터로 변환한 후 Context Token을 맞추는 것
V:VocabularySize(thenumberoftokens)D:EmbeddingDimensionWVD:Weightmatrix(inputhidden)WDV:Weightmatrix(hiddenoutput)V:\, Vocabulary\, Size(the\,number\,of\,tokens)\\ D:\, Embedding\,Dimension\\ W_{V*D}:\, Weight\,matrix(input-hidden)\\ W'_{D*V}:\, Weight\,matrix(hidden-output)
  • 한 문장을 Tokenization한 결과는 Token의 One-Hot Vector의 나열로 표현
    x1,x2,x3,...,xT(xi:ithtokensonehotencodingvector)x_1, x_2, x_3, ..., x_T(x_i:\,i_{th}\,token's\,one-hot\,encoding\,vector)
  • 그리고 각 token을 기준으로 오른쪽으로 이동하면서 미리 정해 놓은 윈도우 크기에 맞춰 기준 token의 양옆으로 윈도우 안에 있는 token을 선택
    targettoken:xicontexttokens:xi1,xi+1target\,token:\,x_i\\ context\,tokens:\,x_{i-1},\,x_{i+1}
  • x_i는 vocabulary에서 token의 해당 index 부분만 1, 나머지는 모두 0인 벡터이기 때문에, W와 곱하면 W행렬의 해당 index 부분의 행만 남음
    • 이 값을 'Word Embedding Vector'라고 함.
  • Skip-Gram은 Context Token을 모두 예측하기 때문에 학습 횟수가 많아져 학습 속도가 느리지만 평균을 내는 부분이 없어 드물게 등장하는 단어들에 대해서는 좀 더 좋은 성능 보유
  • Skip-Gram의 단점을 보완하는 네거티브 샘플링 도입

Glove

  • 기존 Representation 기법에서 사용한 문서 내 모든 단어의 통계 정보와 Word2Vec의 Local Context Window 정보를 동시에 사용하는 모델링

FastText

  • Word2Vec을 개선
  • 1개의 word에 대한 vector로 n-gram character에 대한 vector 평균을 사용
    • 글자 단위 n-gram을 이용한 준단어 토큰화를 이용
    • n=3일 경우 where는 <wh, whe, her, ere, re>로 분해
    • 단어 임베딩 = 준단어 토큰의 임베딩의 합
  • 장점: 신조어 접근 가능

BERT

  • 문맥에 따라 단어의 Embedding Vector가 바뀔 수 있는 Contextual Embedding이라는 이름으로 기존 Word Representation 영역의 또 다른 큰 변화를 가져옴

CharSeq 문제

# 다음 character을 예측하는 모델 구축
# charseq 문제
sample = ' if you want you'

# make dictionary
char_set = list(set(sample))
char_dic = {c:i for i,c in enumerate(char_set)}

# hyperparameters
input_size = len(char_set)
dic_size = len(char_dic)
hidden_size = len(char_dic)
learning_rate = 0.1

# data setting
sample_idx = [char_dic[c] for c in sample] # index 가져옴
x_data = [sample_idx[:-1]] # 마지막 character 제외 후 가져옴
x_one_hot = [np.eye(dic_size)[x] for x in x_data] # np.eye는 Identity matrix임-여기서 원하는 vector 가져옴
y_data = [sample_idx[1:]]

# transform as torch tensor variable
X = torch.FloatTensor(x_one_hot)
Y = torch.LongTensor(y_data)

# declare RNN
rnn = torch.nn.RNN(input_size, hidden_size, batch_first=True)

# loss&optimizer setting
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(rnn.parameters(), learning_rate)

# start training
for i in range(100):
    optimizer.zero_grad()
    outputs, _status = rnn(X)
    loss = criterion(outputs.view(-1, input_size), Y.view(-1))
    loss.backward()
    optimizer.step()
    result = outputs.data.numpy().argmax(axis=2)
    result_str = ''.join([char_set[c] for c in np.squeeze(result)])
    print(F'{i}, loss: {loss.item()}, prediction: {result}, true Y: {y_data}, prediction str: {result_str}')

참고
파이썬 딥러닝 파이토치 (이경택, 방성수, 안상준)
모두를 위한 딥러닝 시즌 2 Lab 11-2

profile
데이터사이언스를 공부하는 권유진입니다.

0개의 댓글