[PyTorch] 자연어 처리와 토큰화
Posted: Updated:
AI 스터디를 하며 ‘파이토치 트랜스포머를 활용한 자연어 처리와 컴퓨터비전 심층학습’ 교재를 정리한 글입니다.
자연어 처리 (Natural Language Processing, NLP)
자연어는 사람들이 언어 활동을 위해 자연히 만들어진 언어를 의미
자연어 처리는 컴퓨터가 인간의 언어를 이해하고 해석, 생성하기 위한 기술
- 모호성(Ambiguity): 인간의 언어에서 단어와 구가 사용되는 맥락에 따라 여러 의미를 갖는 것
- 가변성(Variability): 사투리(Dialects), 강세(Accent), 신조어(Coined word), 작문 스타일로 인해 가변적
- 구조(Structure): 문장, 구를 이해할 때 구문(Syntactic)을 파악하여 의미(Semantic)를 해석
토큰화 (Tokenization)
말뭉치(Corpus): 자연어 모델 훈련, 평가에 사용되는 대규모 텍스트 데이터
토큰(Token): 개별 단어, 문장 구호와 같은 텍스트로 말뭉치보다 작은 단위
코퍼스를 토큰으로 나누는 과정을 토큰화라고 함
개별 토큰은 의미가 없어 자연어 모델로 각 토큰의 의미를 조합해 결과를 도출해야 함
토크나이저(Tokenizer)
토큰화를 위해 토크나이저 사용
- 공백 분할: 텍스트를 공백 단위로 분리
- 정규표현식 적용: 정규표현식으로 특정 패턴을 식별해 분할
- 어휘 사전(Vocabulary) 적용: 사전에 정의된 단어 집합을 토큰으로 사용
- 머신러닝 활용: 데이터셋 기반 토큰화 방법 학습한 머신러닝 적용
단어 및 글자 토큰화
입력된 텍스트 데이터를 단어(Word), 글자(Character) 단위로 나누는 기법
review = " 현실과 구분 불가능한 cg. 시각적 즐거음은 최고! 더불어 ost는 더더욱 최고!!"
단어 토큰화(Word Tokenization)
텍스트를 단어 단위로 분리하는 작업
띄어쓰기, 문장 부호, 대소문자 등 특정 구분자를 사용한 토큰화
품사 태깅, 개체명 인식, 기계 번역 등 작업에서 널리 사용되는 일반적인 방식
tokenized = review.split() # 공백 기준으로 분리
print(tokenized)
출력
['현실과', '구분', '불가능한', 'cg.', '시각적', '즐거음은', '최고!', '더불어', 'ost는', '더더욱', '최고!!']
글자 토큰화(Character Tokenization)
띄어쓰기뿐만 아니라 글자 단위로 문장을 나누는 방식
비교적 작은 단어 사전 구축 가능
언어 모델링과 같은 시퀀스 예측 작업에 활용
tokenized = list(review)
print(tokenized)
출력
[' ', '현', '실', '과', ' ', '구', '분', ' ', '불', '가', '능', '한', ' ', 'c', 'g', '.', ' ', '시', '각', '적', ' ', '즐', '거', '음', '은', ' ', '최', '고', '!', ' ', '더', '불', '어', ' ', 'o', 's', 't', '는', ' ', '더', '더', '욱', ' ', '최', '고', '!', '!']
자모(jamo) 라이브러리
한글 문자 및 자모 작업을 위한 한글 음절 분해 및 합성 라이브러리
텍스트를 자소 단위로 분해해 토큰화 진행
단어 단위로 토큰화하는 것보다 적은 크기의 단어 사전 구축 가능
pip install jamo
from jamo import h2j, j2hcj
decomposed = j2hcj(h2j(review))
tokenized = list(decomposed)
print(tokenized)
출력
[' ', 'ㅎ', 'ㅕ', 'ㄴ', 'ㅅ', 'ㅣ', 'ㄹ', 'ㄱ', 'ㅘ', ' ', 'ㄱ', 'ㅜ', 'ㅂ', 'ㅜ', 'ㄴ', ' ', 'ㅂ', 'ㅜ', 'ㄹ', 'ㄱ', 'ㅏ', 'ㄴ', 'ㅡ', 'ㅇ', 'ㅎ', 'ㅏ', 'ㄴ', ' ', 'c', 'g', '.', ' ', 'ㅅ', 'ㅣ', 'ㄱ', 'ㅏ', 'ㄱ', 'ㅈ', 'ㅓ', 'ㄱ', ' ', 'ㅈ', 'ㅡ', 'ㄹ', 'ㄱ', 'ㅓ', 'ㅇ', 'ㅡ', 'ㅁ', 'ㅇ', 'ㅡ', 'ㄴ', ' ', 'ㅊ', 'ㅚ', 'ㄱ', 'ㅗ', '!', ' ', 'ㄷ', 'ㅓ', 'ㅂ', 'ㅜ', 'ㄹ', 'ㅇ', 'ㅓ', ' ', 'o', 's', 't', 'ㄴ', 'ㅡ', 'ㄴ', ' ', 'ㄷ', 'ㅓ', 'ㄷ', 'ㅓ', 'ㅇ', 'ㅜ', 'ㄱ', ' ', 'ㅊ', 'ㅚ', 'ㄱ', 'ㅗ', '!', '!']
형태소 토큰화(Morpheme Tokenization)
언어의 문법과 구조를 고려해 단어를 분리하고 의미 있는 단어로 분류하는 작업
한국어와 같은 교착어(Agglutinative Language)에서 중요
형태소(Morpheme): 실제로 의미를 가진 최소의 단위
자립 형태소(Free Morpheme): 스스로 의미를 가지는 명사, 동사, 형용사 등의 단어를 이루는 기본 단위
의존 형태소(Bound Morpheme): 스스로 의미를 갖지 못하고 다른 형태소와 조합되어 사용되는 조사, 어미, 접두사, 접미사 등
형태소 어휘 사전(Morpheme Vocabulary)
각 단어의 형태소 정보를 포함하는 어휘 사전
일반적으로 형태소의 품사와 품사의 뜻 정보도 함께 제공
품사 태깅(POS Tagging): 텍스트 데이터를 형태소 분석해 각 형태소에 해당하는 품사(Part of Speech)를 태깅하는 작업
KoLNPy
한국어 자연어 처리를 위해 개발된 라이브러리
명사 추출, 형태소 분석, 품사 태깅 등 기능 제공
텍스트 데이터 전처리, 분석을 위한 다양한 도구와 함수 제공
텍스트 마이닝, 감성 분석, 토픽 모델링 등 NLP 작업에 사용
자바 기반으로 JDK 설치가 필요함
Okt(Open Korean Text), 꼬꼬마(kkma), 코모란(Komoran), 한나눔(Hannanum), 메캅(Mecab) 등 지원
pip install konlpy
from konlpy.tag import Okt
from konlpy.tag import Kkma
sentence = "무엇이든 상상할 수 있는 사람은 무엇이든 만들어 낼 수 있다."
okt = Okt() # SNS 텍스트 데이터 기반
nouns = okt.nouns(sentence) # 명사 추출
phrases = okt.phrases(sentence) # 구 추출
morphs = okt.morphs(sentence) # 형태소 추출
pos = okt.pos(sentence) # 품사 태깅
print("명사 추출 :", nouns)
print("구 추출 :", phrases)
print("형태소 추출 :", morphs)
print("품사 태깅 :", pos)
kkma = Kkma() # 국립국어원 세종 말뭉치 기반
nouns = kkma.nouns(sentence) # 명사 추출
sentences = kkma.sentences(sentence) # 구 추출
morphs = kkma.morphs(sentence) # 형태소 추출
pos = kkma.pos(sentence) # 품사 태깅
print("명사 추출 :", nouns)
print("문장 추출 :", sentences)
print("형태소 추출 :", morphs)
print("품사 태깅 :", pos)
출력
명사 추출 : ['무엇', '상상', '수', '사람', '무엇', '낼', '수']
구 추출 : ['무엇', '상상', '상상할 수', '상상할 수 있는 사람', '사람']
형태소 추출 : ['무엇', '이든', '상상', '할', '수', '있는', '사람', '은', '무엇', '이든', '만들어', '낼', '수', '있다', '.']
품사 태깅 : [('무엇', 'Noun'), ('이든', 'Josa'), ('상상', 'Noun'), ('할', 'Verb'), ('수', 'Noun'), ('있는', 'Adjective'), ('사람', 'Noun'), ('은', 'Josa'), ('무엇', 'Noun'), ('이든', 'Josa'), ('만들어', 'Verb'), ('낼', 'Noun'), ('수', 'Noun'), ('있다', 'Adjective'), ('.', 'Punctuation')]
명사 추출 : ['무엇', '상상', '수', '사람', '무엇']
문장 추출 : ['무엇이든 상상할 수 있는 사람은 무엇이든 만들어 낼 수 있다.']
형태소 추출 : ['무엇', '이', '든', '상상', '하', 'ㄹ', '수', '있', '는', '사람', '은', '무엇', '이', '든', '만들', '어', '내', 'ㄹ', '수', '있', '다', '.']
품사 태깅 : [('무엇', 'NNG'), ('이', 'VCP'), ('든', 'ECE'), ('상상', 'NNG'), ('하', 'XSV'), ('ㄹ', 'ETD'), ('수', 'NNB'), ('있', 'VV'), ('는', 'ETD'), ('사람', 'NNG'), ('은', 'JX'), ('무엇', 'NP'), ('이', 'VCP'), ('든', 'ECE'), ('만들', 'VV'), ('어', 'ECD'), ('내', 'VXV'), ('ㄹ', 'ETD'), ('수', 'NNB'), ('있', 'VV'), ('다', 'EFN'), ('.', 'SF')]
NLTK(Natural Language Toolkit)
영어 외에도 네덜란드어, 프랑스어, 독일어 등 다양한 언어를 위한 데이터와 모델 제공
토큰화, 형태소 분석, 구문 분석, 개체명 인식, 감성 분석 등 기능 제공
pip install nltk
import nltk
from nltk import tokenize
from nltk import tag
# Treebank
nltk.download("punkt") # 통계 기반 모델
nltk.download("averaged_perceptron_tagger") # 퍼셉트론 기반
nltk.download('punkt_tab')
sentence = "Those who can imagine anything, can create the impossible."
word_tokens = tokenize.word_tokenize(sentence) # 공백 기준으로 단어 분리, 구두점 등 처리
sent_tokens = tokenize.sent_tokenize(sentence) # 문장을 구두점 기준으로 분리
pos = tag.pos_tag(word_tokens) # 영문 품사 태깅: Averaged Perceptron Tagger 모델로 진행
print(word_tokens)
print(sent_tokens)
print(pos)
출력
True
['Those', 'who', 'can', 'imagine', 'anything', ',', 'can', 'create', 'the', 'impossible', '.']
['Those who can imagine anything, can create the impossible.']
[('Those', 'DT'), ('who', 'WP'), ('can', 'MD'), ('imagine', 'VB'), ('anything', 'NN'), (',', ','), ('can', 'MD'), ('create', 'VB'), ('the', 'DT'), ('impossible', 'JJ'), ('.', '.')]
spaCy
사이썬(Cython) 기반 오픈 소스 라이브러리
빠른 속도와 높은 정확도를 목표로 함
NLTK보다 크고 복잡하며 많은 리소스가 필요함
GPU 가속, 영어, 한국어, 프랑스어, 일본어 등 24개 이상 언어로 사전 학습된 모델 제공
pip install spacy
python -m spacy download en_core_web_sm
import spacy
nlp = spacy.load("en_core_web_sm") # 모델 불러오기
sentence = "Those who can imagine anything, can create the impossible."
doc = nlp(sentence) # doc 객체에 저장
for token in doc:
print(f"[{token.pos_:5} - {token.tag_:3}] : {token.text}")
출력
[PRON - DT ] : Those
[PRON - WP ] : who
[AUX - MD ] : can
[VERB - VB ] : imagine
[PRON - NN ] : anything
[PUNCT - , ] : ,
[AUX - MD ] : can
[VERB - VB ] : create
[DET - DT ] : the
[ADJ - JJ ] : impossible
[PUNCT - . ] : .
하위 단어 토큰화(Subword Tokenization)
현대 자연어 처리에서 신조어, 오탈자, 축약어들을 고려하기 위한 방법
하나의 단어를 빈번하게 사용되는 하위 단어(Subword)의 조합으로 나눔
바이트 페어 인코딩(Byte Pair Encoding, BPE)
다이그램 코딩(Digam Coding)이라고도 하는 하위 단어 토큰화의 한 종류
텍스트 데이터에서 가장 빈번하게 등장하는 글자 쌍의 조합을 찾아 부호화하는 압축 알고리즘
사전 구축 방법: 빈도 사전 내 모든 단어를 글자 단위로 나누고, 가장 많이 등장한 글자 쌍을 병합하고 어휘 사전에 추가하는 과정 반복
센텐스피스(Sentencepiece)와 코포라(Korpora)
센텐스피스: 구글에서 개발한 오픈소스 하위 단어 토크나이저 라이브러리
코포라: 국립국어원, AI Hub의 코퍼스를 쉽게 사용할 수 있도록 해주는 오픈소스 라이브러리
pip install sentencepiece Korpora
from Korpora import Korpora # 코포라
corpus = Korpora.load("korean_petitions") # 청와대 청원 코퍼스 다운로드
dataset = corpus.train
petition = dataset[0]
print("청원 시작일 :", petition.begin)
print("청원 종료일 :", petition.end)
print("청원 동의 수 :", petition.num_agree)
print("청원 범주 :", petition.category)
print("청원 제목 :", petition.title)
print("청원 본문 :", petition.text[:30])
petitions = corpus.get_all_texts() # 본문 데이터셋 한번에 불러오기
with open("datasets/corpus.txt", "w", encoding="utf-8") as f: # 텍스트 파일로 저장
for petition in petitions:
f.write(petition + "\n")
from sentencepiece import SentencePieceTrainer # 센텐스피스 사용
# 토크나이저 모델 학습
SentencePieceTrainer.Train( # model 파일(토크나이저 모델), vocab 파일(어휘 사전) 생성
"--input=datasets/corpus.txt\
--model_prefix=models/petition_bpe\
--vocab_size=8000 model_type=bpe"
)
출력
Korpora 는 다른 분들이 연구 목적으로 공유해주신 말뭉치들을
손쉽게 다운로드, 사용할 수 있는 기능만을 제공합니다.
말뭉치들을 공유해 주신 분들에게 감사드리며, 각 말뭉치 별 설명과 라이센스를 공유 드립니다.
해당 말뭉치에 대해 자세히 알고 싶으신 분은 아래의 description 을 참고,
해당 말뭉치를 연구/상용의 목적으로 이용하실 때에는 아래의 라이센스를 참고해 주시기 바랍니다.
# Description
Author : Hyunjoong Kim lovit@github
Repository : https://github.com/lovit/petitions_archive
References :
청와대 국민청원 게시판의 데이터를 월별로 수집한 것입니다.
청원은 게시판에 글을 올린 뒤, 한달 간 청원이 진행됩니다.
수집되는 데이터는 청원종료가 된 이후의 데이터이며, 청원 내 댓글은 수집되지 않습니다.
단 청원의 동의 개수는 수집됩니다.
자세한 내용은 위의 repository를 참고하세요.
# License
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
Details in https://creativecommons.org/publicdomain/zero/1.0/
청원 시작일 : 2017-08-25
청원 종료일 : 2017-09-24
청원 동의 수 : 88
청원 범주 : 육아/교육
청원 제목 : 학교는 인력센터, 취업센터가 아닙니다. 정말 간곡히 부탁드립니다.
청원 본문 : 안녕하세요. 현재 사대, 교대 등 교원양성학교들의 예비
센텐스피스 라이브러리 하이퍼 파라미터
매개변수 | 의미 |
---|---|
input | 말뭉치 텍스트 파일의 경로 |
model_prefix | 모델 파일 이름 |
vocab_size | 어휘 사전 크기 |
character_coverage | 말뭉치 내에 존재하는 글자 중 토크나이저가 다룰 수 있는 글자의 비율 |
model_type | 토크나이저 알고리즘 - unigram - bpe - char - word |
max_sentence_length | 최대 문장 길이 |
unk_id | 어휘 사전에 없는 OOV를 의미하는 unk 토큰의 id (기본값: 0) |
bos_id | 문장이 시작되는 지점을 의미하는 bos 토큰의 id (기본값: 1) |
eos_id | 문장이 끝나는 지점을 의미하는 eos 토큰의 id (기본값: 2) |
바이트 페어 인코딩 토큰화
from sentencepiece import SentencePieceProcessor
tokenizer = SentencePieceProcessor()
tokenizer.load("models/petition_bpe.model") # 모델 불러오기
sentence = "안녕하세요, 토크나이저가 잘 학습되었군요!"
sentences = ["이렇게 입력값을 리스트로 받아서", "쉽게 토크나이저를 사용할 수 있답니다"]
# 문장 토큰화
tokenized_sentence = tokenizer.encode_as_pieces(sentence)
tokenized_sentences = tokenizer.encode_as_pieces(sentences)
print("단일 문장 토큰화 :", tokenized_sentence)
print("여러 문장 토큰화 :", tokenized_sentences)
# 토큰을 정수로 인코딩 (토큰에 매핑된 ID)
encoded_sentence = tokenizer.encode_as_ids(sentence)
encoded_sentences = tokenizer.encode_as_ids(sentences)
print("단일 문장 정수 인코딩 :", encoded_sentence)
print("여러 문장 정수 인코딩 :", encoded_sentences)
# 정수 인코딩으로 문자열 데이터로 변환
decode_ids = tokenizer.decode_ids(encoded_sentences)
decode_pieces = tokenizer.decode_pieces(encoded_sentences)
print("정수 인코딩에서 문장 변환 :", decode_ids)
print("하위 단어 토큰에서 문장 변환 :", decode_pieces)
출력
True
단일 문장 토큰화 : ['▁안녕하세요', ',', '▁토', '크', '나', '이', '저', '가', '▁잘', '▁학', '습', '되었', '군요', '!']
여러 문장 토큰화 : [['▁이렇게', '▁입', '력', '값을', '▁리', '스트', '로', '▁받아서'], ['▁쉽게', '▁토', '크', '나', '이', '저', '를', '▁사용할', '▁수', '▁있', '답니다']]
단일 문장 정수 인코딩 : [667, 6553, 994, 6880, 6544, 6513, 6590, 6523, 161, 110, 6554, 872, 787, 6648]
여러 문장 정수 인코딩 : [[372, 182, 6677, 4433, 1772, 1613, 6527, 4162], [1681, 994, 6880, 6544, 6513, 6590, 6536, 5852, 19, 5, 2639]]
정수 인코딩에서 문장 변환 : ['이렇게 입력값을 리스트로 받아서', '쉽게 토크나이저를 사용할 수 있답니다']
하위 단어 토큰에서 문장 변환 : ['이렇게 입력값을 리스트로 받아서', '쉽게 토크나이저를 사용할 수 있답니다']
어휘 사전 불러오기
from sentencepiece import SentencePieceProcessor
tokenizer = SentencePieceProcessor()
tokenizer.load("models/petition_bpe.model")
vocab = {idx: tokenizer.id_to_piece(idx) for idx in range(tokenizer.get_piece_size())}
# get_piece_size: 센텐스피스 모델에서 생성된 하위 단어 개수
# id_to_piece: 정수를 하위 단어로 변환
print(list(vocab.items())[:5]) # 토큰 출력
print("vocab size :", len(vocab)) # 어휘 사전 크기(설정값에 따라 달라질 수 있음)
# <unk>: unknown의 약자, OOV (어휘 사전에 없음)
# <s>, </s>: 문장의 시작 지점과 종료 지점
출력
True
[(0, '<unk>'), (1, '<s>'), (2, '</s>'), (3, '니다'), (4, '▁이')]
vocab size : 8000
워드피스(Wordpiece)
확률 기반으로 글자 쌍 병합
새 하위 단어 생성 시 이전 하위 단어와 함께 나타날 확률을 계산해 가장 높은 확률을 가진 하위 단어 선택
$f$: 빈도(frequency)
사전 구축 방법: 빈도 사전 내 모든 단어를 글자 단위로 나눈 후 글자 쌍 병합 점수 수식을 활용해 계산, 점수가 가장 높은 쌍을 병합하고 어휘 사전에 추가
토크나이저스
허깅 페이스에서 제공하는 라이브러리로 워드피스 API 사용 가능
정규화(Normalization), 사전 토큰화(Pre-tokenization) 제공
정규화: 텍스트 표준화를 위한 과정으로 불필요한 공백 제거, 대소문자 변환, 유니코드 정규화, 구두점 처리, 특수문자 처리 등
사전 토큰화: 토큰화하기 전 공백 또는 구두점 기준으로 입력 문자를 작은 단위로 나누는 기능
pip install tokenizers
from tokenizers import Tokenizer
from tokenizers.models import WordPiece
from tokenizers.normalizers import Sequence, NFD, Lowercase
from tokenizers.pre_tokenizers import Whitespace
tokenizer = Tokenizer(WordPiece()) # 워드피스 모델 불러오기
tokenizer.normalizer = Sequence([NFD(), Lowercase()]) # 정규화 방식: sequence 형식으로 NFD 유니코드 정규화, 소문자 변환(Lowercase) 전달
tokenizer.pre_tokenizer = Whitespace() # 사전 토큰화 방식: 공백과 구두점 기준
tokenizer.train(["datasets/corpus.txt"])
tokenizer.save("models/petition_wordpiece.json")
워드피스 토큰화
from tokenizers import Tokenizer
from tokenizers.decoders import WordPiece as WordPieceDecoder
tokenizer = Tokenizer.from_file("models/petition_wordpiece.json") # 모델 결과 불러오기
tokenizer.decoder = WordPieceDecoder() # 디코더 설정
sentence = "안녕하세요, 토크나이저가 잘 학습되었군요!"
sentences = ["이렇게 입력값을 리스트로 받아서", "쉽게 토크나이저를 사용할 수 있답니다"]
encoded_sentence = tokenizer.encode(sentence) # 문장 토큰화
encoded_sentences = tokenizer.encode_batch(sentences) # 여러 문장 한 번에 토큰화
print("인코더 형식 :", type(encoded_sentence))
print("단일 문장 토큰화 :", encoded_sentence.tokens)
print("여러 문장 토큰화 :", [enc.tokens for enc in encoded_sentences])
print("단일 문장 정수 인코딩 :", encoded_sentence.ids)
print("여러 문장 정수 인코딩 :", [enc.ids for enc in encoded_sentences])
print("정수 인코딩에서 문장 변환 :", tokenizer.decode(encoded_sentence.ids))
출력
인코더 형식 : <class 'tokenizers.Encoding'>
단일 문장 토큰화 : ['안녕하세요', ',', '토', '##크', '##나이', '##저', '##가', '잘', '학습', '##되었', '##군요', '!']
여러 문장 토큰화 : [['이렇게', '입력', '##값을', '리스트', '##로', '받아서'], ['쉽게', '토', '##크', '##나이', '##저', '##를', '사용할', '수', '있다', '##ᆸ니다']]
단일 문장 정수 인코딩 : [8760, 11, 8693, 8415, 16269, 7536, 7488, 7842, 15016, 8670, 8734, 0]
여러 문장 정수 인코딩 : [[8187, 19643, 13834, 28119, 7495, 12607], [9739, 8693, 8415, 16269, 7536, 7510, 14129, 7562, 8157, 7489]]
정수 인코딩에서 문장 변환 : 안녕하세요, 토크나이저가 잘 학습되었군요!
댓글남기기