일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- NLP
- 그룹바이
- 카이제곱분포
- 코딩테스트
- nlp논문
- Window Function
- 짝수
- airflow
- 논문리뷰
- torch
- 자연어 논문 리뷰
- update
- 서브쿼리
- MySQL
- leetcode
- SQL코테
- GRU
- 표준편차
- LSTM
- inner join
- HackerRank
- sigmoid
- Statistics
- SQL 날짜 데이터
- CASE
- 자연어 논문
- t분포
- sql
- 자연어처리
- 설명의무
- Today
- Total
HAZEL
[ NLP : CH4. 전처리 ] 전처리, 정제, 분절, 병렬 코퍼스 정렬,서브워드 분절(BPE알고리즘), SentencePiece,분절 복원 ( detokenization ), Torchtext 본문
[ NLP : CH4. 전처리 ] 전처리, 정제, 분절, 병렬 코퍼스 정렬,서브워드 분절(BPE알고리즘), SentencePiece,분절 복원 ( detokenization ), Torchtext
Rmsid01 2020. 12. 25. 23:384장. 전처리
4-1. 전처리
4.1.1. 코퍼스 ( 말뭉치 )
: 여러 단어들로 이루어진 문장
* 코퍼스의 종류
- 단일언어 코퍼스 : 한가지 언어로 구성된 코퍼스
- 이중 언어 코퍼스 : 두개의 언어로 구성된 코퍼스
- 다중 언어 코퍼스 : 두개 이상의 언어로 구성된 코퍼스
- 병령 코퍼스 : 언어간의 쌍으로 구성된 코퍼스 ( 영문과 한글이 함께 짝을 이루는 데이터 )
4.1.2. 전처리 과정
: 코퍼스 수집 -> 정제 -> 문장 단위 분절 -> 분절 -> 병렬 코퍼스 정렬 ( 생략 가능 ) -> 서브워드 분절
4-2. 정제
4.2.1. 전각 문자 제거
: 전각문자를 반각문자로 변환해주는 작업 필요
전각문자 : 특수문자 기호. 문자의 폭이 일반적인 영문자의 고정폭의 두배정도의 폭을 가지는 문자.
반각문자 : 우리가 상요하는 기호 . 전각문자의 폭의 절반을 폭으로 하는 문자
4.2.2. 대소문자 통일
: 같은 의미를 가지는 단어를 하나의 형태로 통일시켜 희소성을 줄이는 효과를 기대할 수 있다.
그러나, 딥러닝의 단어 임베딩을 통해 다양한 단어들을 비슷한 값의 벡터로 나타낼 수 있게 되어, 대소문자 통일과 같은 문제 해결의 필요성이 감소되었다.
4.2.3. 정규식 표현을 사용하여 정제
: ROW 데이터의 경우 , 데이터에 노이즈가 많기 때문에 정규표현식 ( import re ) 을 하여, 데이터를 깔끔하게 해준다.
4-3. 문장단위 분절
문장 단위로 분절하기 위해 '.'로 구분해서 나누어줄 수 는 있지만, 그러면 U.S. 와 같은 약자를 마주칠때, 출력값이 이상하게 될 수 있다. 따라서 1) 직접 분절하는 모듈을 만들기 2) NLTK의 자연어 처리 툴킷을 사용하는 것의 방법이 있다.
4.3.1. 문장 단위 분절
import sys, fileinput ,re
from nltk.tokenize import sent_tokenize
import nltk
nltk.download('punkt')
import sys, fileinput, re
from nltk.tokenize import sent_tokenize
if __name__ == "__main__":
for line in fileinput.input("input.txt",openhook=fileinput.hook_encoded("utf-8")):
if line.strip() != "": # 이 if문은 지금은 필요 x
line = re.sub(r'([a-z])\.([A-Z])', r'\1. \2', line.strip())
sentences = sent_tokenize(line.strip()) # 이 함수를 사용하면, 문장단위로 나누어주고 리스트 형식으로 만들어 줌
print(sentences, '\n')
for s in sentences:
if s != "":
# sys.stdout.write(s + "\n")
print(s) # 둘다 가능하다
fileinput.close() # fileinput을 열어두면, 다시 실행할 때 에러가 발생해서 닫아줌
위의 코드를 입력하면, 문장 단위로 분절되는 것을 알 수 있다.
아래가 위의 코드의 결과물이다. [ 글의 출처 : 카카오 라이언의 이야기.. ]
for line in fileinput.input("input.txt",openhook=fileinput.hook_encoded("utf-8")):
sentences = sent_tokenize(line.strip())
for i in sentences:
print(i)
이렇게만 사용해도, 위의 코드와 같은 결과를 얻어 낼 수는 있다.
4-4 분절 ( Tokenization )
: 띄어쓰기 정규화, 접사를 어근에서 분리하는 역할을 통해 희소성 문제를 해소한다.
* 많이 사용 되는 분절 프로그램
- 한국어 : Mecab / KoNLPy
- 일본어 : Mecab
- 중국어 : Stanford Parser / PKU Paser / Jieba
4.4.1. 한국어 분절 : konlpy.org/ko/latest/ 참고
- 기본적으로 어근과 접사를 분리하는 역할을 하며, 나아가 잘못된 띄어쓰기에 대해 올바르게 교정하는 역할을 함.
from konlpy.tag import Kkma
kkma = Kkma()
line = '둥둥섬의 왕위 계승자로 태어난 수사자 라이언. 무뚝뚝한 표정과는 다르게 배려심이 많고 따뜻한 리더십을 가지고 있습니다.'
tokenized = kkma.pos(line)
print(tokenized)
- KoNLPy 패키지에는 아래의 5가지가 있다.
-
- Hannanum Class
- Kkma Class
- Komoran Class
- Mecab Class
- Okt Class
4.4.2. 영어 분절
: 영어의 경우, 기본적으로 띄어쓰기가 잘 통일되어있어, 이부분에는 큰 이슈가 없다. 다만, 쉼표, 마침표, 인용부호 등을 띄어주어야한다.
4-5. 병렬 코퍼스 정렬
: “ 병렬 코퍼스는 여러 문장 단위로 정렬됨 ”
보통 크롤링을 해서 얻은 문서들은 문서와 문서와의 맵핑일 뿐 문장대 문장에 관한 정렬을 이루어지지 않음
따라서, 각 문장에 대해 정렬해줘야함 즉, 병렬 코퍼스 제작을 해줘야함.
4.5.1. 병렬 코퍼스 제작 프로세스
4.5.2. 사전 생성
: 기존에 사전이 없으면 사전을 만들어야 한다.
* 페이스북 MUSE : 이 만든 사전을 구축하는 방법과 코드를 제공함 .
- 병렬 코퍼스가 없는 상황에도 수행할 수 있기 때문에, '비지도 학습 '이라고 할 수 있음.
4.5.3. CTK를 활용한 정렬
: 이중언어 코퍼스의 문장 정렬을 수행하는 오픈소스
• CTK의 결과
1) 일대일 맵핑 2) 일대다 맵핑 3) 다대일 맵핑
4.6. 서브워드 분절
: BPE 알고리즘을 이용한 서브워드 단위 분절은 필수 전처리 방법이다.
* 서브워드 분절 : 단어는 의미를 가진 더 작은 서브워드들의 조합으로 이루어진다는 가정하에 적용되는 알고리즘
* 서브워드 분절로 얻을 수 있는 효과
1) 희소성 감소
2) UNK ( unkown ) 토큰에 대한 효율적인 대처 가능 => 자연어 처리 알고리즘의 결과물 품질 향상
: 자연어는 입력으로 받을 때, 단어들의 시퀀스로 받아드리는데 이때 UNK가 등장하면 모델의 확률이 크게 감소한다. 또한, 적절한 문장의 임베딩 또는 생성이 어렵다.
* OOV(Out-Of-Vocabulary) == UNK ( unkown Token )
4.6.1 BPE 알고리즘에 대해서 공부해보자!
BPE ( 바이트 페어 인코딩 : Byte Pair Encoding )
: 데이터 압축 알고리즘 -> 자연어처러리의 서브워드 분리 알고리즘으로 응용됨
1) BPE ( 바이트 페어 인코딩 : Byte Pair Encoding ) 의 작동 방법
- 연속적으로 가장 많이 등장하는 글자의 쌍을 찾아서 하나의 글자로 병합하는 방식을 수행
'가나나다다다다가나나다다'
# 가장 많이 등장하는 글자는 '다다' -> A로 치환
'가나나AA가나나A'
# 그 다음으로 많이 등장하는 글자 '가나나' -> B 치환
'BAABA'
# 이제 더이상 병합할 쌍이 없으므로 종료됨
2) 자연어 처리에서 BPE
- 빈도수에 기반하여 가장 많이 등장한 쌍을 병합하는 것
- 자연어 처리에서의 BPE는 단어를 분리한다는 의미임. 즉, 글자 단위에서 점차적으로 단어 집합을 만들어 내는 Bottom up 방식의 접근을 사용함
- https://arxiv.org/pdf/1508.07909.pdf : BPE 논문
- BPE의 특징은 알고리즘의 동작을 몇 회 반복(iteration)할 것인지를 사용자가 정한다는 점입니다. 여기서는 총 10회를 수행한다고 가정합니다. 다시 말해 가장 빈도수가 높은 유니그램의 쌍을 하나의 유니그램으로 통합하는 과정을 총 10회 반복합니다. 위의 딕셔너리에 따르면 빈도수가 현재 가장 높은 유니그램의 쌍은 (e, s)입니다.
import re, collections
from IPython.display import display, Markdown, Latex
num_merges = 4
dictionary = {'w a l k </w>' : 5,
'w a l k e d </w>' : 2,
'd r k i n g </w>':6,
'e a t i n g </w>':3
}
def get_stats(dictionary):
# 유니그램의 pair들의 빈도수를 카운트
pairs = collections.defaultdict(int)
for word, freq in dictionary.items():
symbols = word.split()
for i in range(len(symbols)-1):
pairs[symbols[i],symbols[i+1]] += freq
print('현재 pair들의 빈도수 :', dict(pairs))
return pairs
def merge_dictionary(pair, v_in):
v_out = {}
bigram = re.escape(' '.join(pair))
p = re.compile(r'(?<!\S)' + bigram + r'(?!\S)')
for word in v_in:
w_out = p.sub(''.join(pair), word)
v_out[w_out] = v_in[word]
return v_out
bpe_codes = {}
bpe_codes_reverse = {}
for i in range(num_merges):
display(Markdown("### Iteration {}".format(i + 1)))
pairs = get_stats(dictionary)
best = max(pairs, key=pairs.get)
dictionary = merge_dictionary(best, dictionary)
bpe_codes[best] = i
bpe_codes_reverse[best[0] + best[1]] = best
print("new merge: {}".format(best))
print("dictionary: {}".format(dictionary))
위 코드 결과 : KING 과 TING 이 생겼다.
이 결과에 의거해서, 새로운 단어가 나타났을 때, 분리하는 일에 참고할 수 있다.
3) BPE를 참고하여 만들어진 모델
- Wordpiece Model : 병합 되었을 때 코퍼스의 우도를 가장 높이는 쌍을 병합
- Unigram Language Model Tokenizer : 각각 서브워드들에 대해서 손실을 계산함. 즉, 서브워드가 단어 집합에서 제거 되었을 경우, 코퍼스 우도가 감소하는 정도를 말함.
4.6.2. SentencePiece
: BPE 알고리즘을 실무에서 사용하기 위한 패키지로 만든 것
1 ] 학습시키기
* 학습 파라미터는 아래 눌러서 확인
* input : 학습시킬 파일 (텍스트파일만)
* model_prefix : 만들어질 모델 이름 마음대로 커스텀 가능
* vocab_size : 단어 집합의 크기
* 커질수록 성능이 좋아지고 모델 파라미터수가 증가함--->시간이오래걸릴듯함
* model_type : 사용할 모델 (unigram(default), bpe, char, word)
* max_sentence_length: 문장의 최대 길이
* pad_id, pad_piece: pad token id, 값
* unk_id, unk_piece: unknown token id, 값
* bos_id, bos_piece: begin of sentence token id, 값
* eos_id, eos_piece: end of sequence token id, 값
* user_defined_symbols: 사용자 정의 토큰 내가 원하는 형태소만 가져오기
# template을 Train시켜준다.
spm.SentencePieceTrainer.Train(template)
학습이 완료되면, 두개의 파일이 생성된다.
1) subword_tokenizer_kor.vocab ( subword_tokenizer_kor 은 내가 이름을 지정해준것 )
: vocab 파일에서 학습된 서브워드들을 확인 할 수 있다.
# 학습이 완료되면, subword_tokenizer_kor.model 과 subword_tokenizer_kor.vocab 이 생성됨
# vocab 파일에서 학습된 서브워드들을 확인 할 수 있다.
with open('subword_tokenizer_kor.vocab', encoding = 'utf-8') as f :
vo = [ doc.strip().split('\t') for doc in f]
# v[0] : token name
# v[1] : token score
word2idx = { w[0] : i for i, w in enumerate(vo)}
2) subword_tokenizer_kor.model : 생성된 model파일을 로드해서 단어 시퀀스를 정수 시퀀스로 바꾸는 인코딩 작업이나 반대로 변환하는 디코딩 작업을 할 수 있음.
model_file = 'subword_tokenizer_kor.model'
model = spm.SentencePieceProcessor()
model.load(model_file)
* # 기능이 tensorflow 에도 있다. 위의 코드와 같은 기능을 하는 코드이다.
import tensorflow as tf
serialized_model_proto = tf.io.gfile.GFile(vocab_file, 'rb').read()
sp = spm.SentencePieceProcessor()
sp.load_from_serialized_proto(serialized_model_proto)
2 ] 테스트 하기
* 만들어진 모델의 함수는 아래 눌러서 확인
* GetPieceSize() : 단어 집합의 크기를 확인합니다. = vocab_size
* encode_as_pieces : 문장을 입력하면 서브 워드 시퀀스로 변환합니다.
* encode_as_ids : 문장을 입력하면 정수 시퀀스로 변환합니다.
* idToPiece : 정수로부터 맵핑되는 서브 워드로 변환합니다.
* PieceToId : 서브워드로부터 맵핑되는 정수로 변환합니다.
* DecodeIds : 정수 시퀀스로부터 문장으로 변환합니다.
* DecodePieces : 서브워드 시퀀스로부터 문장으로 변환합니다.
* encode : 문장으로부터 인자값에 따라서 정수 시퀀스 또는 서브워드 시퀀스로 변환 가능합니다.
tmp = '여행같은 음악 지친 하루 끝 힐링이 필요한 당신에게 추천하는 인디곡'
print('단어집합의 크기 : ' , model.GetPieceSize( )) # 학습시킬때 단어 량을 vocab_size = 32000 로 설정 해줘서 3200 이나옴
print("정수(1785)로부터 맵핑되는 서브워드 변환 : " , model.IdToPiece(1785))
print("서브워드(▁힐링이)로부터 맵핑되는 정수로 변환 : ", model.PieceToId("▁힐링이") , '\n')
# 서브워드 시퀀스로부터 문장을 반환
print(model.DecodeIds([360, 175, 18, 539, 252, 446, 1785, 814, 1122, 990, 3787]), '\n')
# 문장으로부터 인자값에 다라 정수 시퀀스 또는 서브워드 시퀀스로 변환
print(model.encode("힐링이 필요한 당신에게 추천하는 인디곡", out_type = str))
print(model.encode("힐링이 필요한 당신에게 추천하는 인디곡", out_type = int), '\n')
print(model.encode_as_pieces( tmp )) # 문장을 입력하면 서브 워드 시퀀스로 변환
print( model.encode_as_ids( tmp )) # 문장을 입력하면 정수 시퀀스로 변환
4.7. 분절 복원 ( detokenization )
: 전처리 과정에서 분절을 수행하고 다시 분절 복원 ( detokenization )을 하는 과정
import sys
if __name__ == "__main__":
for line in sys.stdin:
if line.strip() != "":
if '▁▁' in line:
line = line.strip().replace(' ', '').replace('▁▁', ' ').replace('▁', '').strip()
else:
line = line.strip().replace(' ', '').replace('▁', ' ').strip()
sys.stdout.write(line + '\n')
else:
sys.stdout.write('\n')
4.8. 토치 텍스트
: 자연어 처리 문제 또는 텍스트에 관한 머신러닝/딥러닝을 수행하는 데이터를 읽고 전처리하는 코드를 모아둔 라이브러리
4.8.1. 자연어처리 분야에서 주로 쓰이는 학습 데이터 형태
4.8.2. Torchtext
Field class : 우리가 읽고자 하는 텍스트 파일 내의 필드를 정의 ( 보통 tab 을 기준으로 함 )
Dataset class : 정의된 각 필드를 읽어들임
이터레이터 : 읽어들인 코퍼스는 미리 주어진 미니배치 크기에 따라서 나뉠 수 있게 함
패딩(PAD : Padding) : 미니배치 내에 문장의 길이가 다를 경우에는 필요에 따라 문장의 앞 뒤에 패딩을 삽입함
- BOS , EOS 와 함께 하나의 단어 또는 토큰과 같이 취급을 받음
=> 훈련코퍼스에 대해 어휘사전을 만들어 각 단어(토큰)을 숫자로 맵핑하는 작업을 수행
** 본 게시글은 자연어 책을 공부하면서 정리한 것
출처 : 김기현의 자연어 처리 딥러닝 캠프 _ 파이 토치 편
ratsgo.github.io/statistics/2017/09/22/information/
'DATA ANALYSIS > NLP' 카테고리의 다른 글
[ NLP : CH6. 단어 임베딩 ] 차원축소, 주성분 분석, 매니폴드 가설, word2vec, Glove (0) | 2021.01.14 |
---|---|
[ NLP : CH5. 유사성과 모호성 ] 특징 벡터, 벡터 유사도, 단어 중의성, 선택 선호도 (0) | 2021.01.04 |
[ NLP : CH5. 유사성과 모호성 ] 단어의 의미, 원핫 인코딩, 시소러스, 특징, TF-IDF (0) | 2020.12.27 |
[ NLP : CH3. 파이토치 ] 파이토치 기초 ,텐서 , 자동 미분 ( Autograd ), 피드포워드, nn.Module, 역전파 수행 (0) | 2020.12.18 |
[ NLP : CH2. 기초수학 ] 확률변수와 확률 분포/ 기댓값과 샘플링 / MLE / 정보이론 (0) | 2020.12.17 |