2장 텍스트 전처리
2.1. 텍스트 전처리 방식
- 여기서는 대부분의 자연어 처리에서 공통적으로 수행되는 전처리에 대해서 설명한다.
- 주어진 텍스트에서 노이즈와 같이 불필요한 부분을 제거하고, 문장을 표준 단어들로 분리한 후에 각 단어의 품사를 파악하는 것
- 왜 필요한가?
- 우리는 일반적으로 하나의 문장을 이해할 때는 사용된 단어들의 순차수열로 이해
- 각각의 단어를 이해하고 그 단어들의 순서에 따라 의미를 이해
- 컴퓨터에게 이해 시키려면 문장을 단어 단위로 나눈 후에 이 단어들의 리스트 형태로 변환 해주어야 한다.
- 이때 쓸모없다고 생각되는 문자를 없엔다.
- 예시 (별 헤는 밤)
- 계절이 지나가는 하늘에는 가을로 가득 차 있습니다.
- [’계절이’, ‘지나가는’, ‘하늘에는’, ‘가을로’, ‘가득’, ‘차’, ‘있습니다’]
- 마지막 ‘있습니다’는 ‘있다’에서 파생 단어여서 ‘있다’로 변환해서 쓸때 효율적일수 있다.
- 우리는 일반적으로 하나의 문장을 이해할 때는 사용된 단어들의 순차수열로 이해
- 전처리 단계
- 정제 : 분석에 불필요한 노이즈를 제러
- 사전에 있는 유의미한 단어라 하더라도 분석에 별 도움이 안되는 단어를 제거 (불용어 제거 라고 한다.)
- 불용어 리스트 : https://www.ranks.nl/stopwords/korean
- 사전에 있는 유의미한 단어라 하더라도 분석에 별 도움이 안되는 단어를 제거 (불용어 제거 라고 한다.)
- 토큰화
- 주어진 텍스트를 원하는 단위(토큰)로 나누는 작업
- 정규화
- 같은 의미를 가진 동일한 단어임에도 불구하고 다른 형태로 쓰여진 단어들을 통일시켜서 표준 단어로 만드는 작업
- 예시 goes는 3인칭에 쓰이는 동사이지만 go와 동일하게 판단하고 go로 변환 시킨다.
- 같은 의미를 가진 동일한 단어임에도 불구하고 다른 형태로 쓰여진 단어들을 통일시켜서 표준 단어로 만드는 작업
- 품사태깅
- 문법적인 기능에 따라 분류하는 것
- 정제 : 분석에 불필요한 노이즈를 제러
2.2. 토큰화
- NLTK는 교육용으로 개발된 자연어 처리 및 문서 분셕용 파이썬 패키지
- 자연어 처리를 지원하는 다양한 라이브러리와 말뭉치, 예제를 제공
# 필요한 nltk 라이브러리를 다운로드
import nltk
nltk.download('punkt')
nltk.download('webtext')
nltk.download('wordnet')
nltk.download('stopwords')
nltk.download('averaged_perceptron_tagger')
-
문장 토큰화
- 먼저 문장 토큰화를 해보자.
- nltk의 sent_tokenize를 사용한다.
- 각 문장을 문자열로 갖는 리스트를 출력한다.
para = "Hello everyone. It's good to see you. Let's start our text mining class!" from nltk.tokenize import sent_tokenize # 주어진 텍스트를 문장 단위로 토큰화. 주로 . ! ? 등을 이용 print(sent_tokenize(para)) # ['Hello everyone.', "It's good to see you.", "Let's start our text mining class!"]- 만약에 다른 언어에 대해 문장 토큰화를 하려면 아래와 같이 사전 학습된 모델을 지정후 실행해야한다.
- 안타깝게도 nltk에는 한글에 대해 사전학습된 모델이 없다. 다만 문장은 마침표나 ?, !같은 것을 기준으로 학습되어 있어서 영어 모델로도 어느정도 가능하다.
para_kor = "안녕하세요, 여러분. 만나서 반갑습니다. 이제 텍스트마이닝 클래스를 시작해봅시다!" print(sent_tokenize(para_kor)) # ['안녕하세요, 여러분.', '만나서 반갑습니다.', '이제 텍스트마이닝 클래스를 시작해봅시다!'] -
단어 토큰화
- NLTK에서는 word_tokenize로 단어 토큰화를 할 수 있다.
from nltk.tokenize import word_tokenize # 주어진 text를 word 단위로 tokenize함 print(word_tokenize(para)) # ['Hello', 'everyone', '.', 'It', "'s", 'good', 'to', 'see', 'you', '.', 'Let', "'s", 'start', 'our', 'text', 'mining', 'class', '!']- 아래는 WordPunctTokenizer를 사용한 방법이다.
from nltk.tokenize import WordPunctTokenizer print(WordPunctTokenizer().tokenize(para)) # ['Hello', 'everyone', '.', 'It', "'", 's', 'good', 'to', 'see', 'you', '.', 'Let', "'", 's', 'start', 'our', 'text', 'mining', 'class', '!']- 위 2개 차이점은 It 다음에 ‘s로 할 것이냐 ‘ , s로 나눌것이냐 차이이고 목적에 따라서 토크나이저를 잘 나눠 써야 한다.
- 한글은 NLTK에서 단어로는 잘 안되며, 대신에 KoNLPy를 이용해서 한글 전처리를 한다.
-
정규표현식을 이용한 토큰화
- 라이브러리는 편하지만 세밀하기 토큰화하기 어렵기에 정규표현식을 이용해서 다양한 조건에 따라 토큰화 할수 있다.
- 정규식을 익혀야 하는 문제가 있지만 요즘 chatGPT가 잘 만들어주니 잘 가져다 쓰자
- 여기서는 정규식 예제라서 설명은 건너뛴다.
-
노이즈와 불용어 제거
- 특수문자와 같은 불필요한 문자들 혹은 노이즈가 삭제를 시킨다. (요즘 특수문자도 해석을 위해 삭제 안하는 경우도 있긴 하다.)
- 토큰화 과정과 별도로 정규표현식을 이용한 치환을 통해 원하는 패턴의 노이즈를 제거할 수 있다.
- 불용어는 리스트를 보고 지우는 경우가 있다.
from nltk.corpus import stopwords # 일반적으로 분석대상이 아닌 단어들 english_stops = set(stopwords.words('english')) # 반복이 되지 않도록 set으로 변환 text1 = "Sorry, I couldn't go to movie yesterday." tokenizer = RegexpTokenizer("[\\\\w']+") tokens = tokenizer.tokenize(text1.lower()) # word_tokenize로 토큰화 # stopwords를 제외한 단어들만으로 list를 생성 result = [word for word in tokens if word not in english_stops] print(result) # ['sorry', 'go', 'movie', 'yesterday'] # can't를 추가하려면? tokenizer = RegexpTokenizer("[\\\\w]+") print(tokenizer.tokenize("Sorry, I can't go there.")) # ['Sorry', 'I', 'can', 't', 'go', 'there'] # 모두 소문자로 바꾸고 '를 포함해 세글자 이상의 단어들만 골라내려면? text1 = "Sorry, I can't go there." tokenizer = RegexpTokenizer("[\\w']{3,}") print(tokenizer.tokenize(text1.lower())) # ['sorry', "can't", 'there'] -
노이즈와 불용어 제거
- 앞에서 정규표현식을 이용한 토큰화 과정을 보면 특수문자와 같은 불필요한 문자들 혹은 노이즈를 삭제한다.
- 영어 같은 경우 보통 길이가 3미만인 단어들은 삭제를 한다.
- 또는 stopworkd라는 라이브러리를 사용해서 불용어 사전을 참조해 삭제한다. (직접 만들수도 있고 가져와서 쓰기도 한다.)
from nltk.corpus import stopwords # 일반적으로 분석대상이 아닌 단어들 english_stops = set(stopwords.words('english')) # 반복이 되지 않도록 set으로 변환 text1 = "Sorry, I couldn't go to movie yesterday." tokenizer = RegexpTokenizer("[\\\\w']+") tokens = tokenizer.tokenize(text1.lower()) # word_tokenize로 토큰화 # stopwords를 제외한 단어들만으로 list를 생성 result = [word for word in tokens if word not in english_stops] print(result) # ['sorry', 'go', 'movie', 'yesterday']- 불용어 사전에 무엇이 있는지 알고 싶으면 아래와 같이 볼수 있다.
print(english_stops) # {'his', 'any', 'was', 'it', 'being', "wasn't", 'its', 'down', 'has', 'again', 'itself', 'weren', 'did', "you'll", 'into', 'than', 'wouldn', 'these', 'he', 'o', 'so', "needn't", 'my', 'y', 'some', 'below', 'how', 'ourselves', 'hadn', 'too', 'which', 'all', 'me', "mustn't", 'the', 'out', 'your', 'on', 'don', 'her', 'does', "aren't", 'himself', 'from', 'further', 'here', 'mightn', "it's", "shouldn't", 'you', 'herself', 'ma', "you're", 'have', 'because', 'myself', "hasn't", 'there', 'him', 'against', 'them', 'can', 'as', 'those', 'isn', 'won', 'are', 'who', 'by', 've', 're', 'or', 'themselves', 'm', 'had', 'once', "she's", 'couldn', 'she', 'am', 'during', 'off', 'that', 'our', 'under', 'needn', 'i', 'then', "wouldn't", 'do', 'very', 'each', 'just', 'they', 'wasn', 'through', "that'll", 'ain', 'whom', "weren't", 'yourselves', 'didn', 'between', "shan't", "hadn't", 'before', 'but', "doesn't", "won't", 'shouldn', 's', 'been', 'if', 'hers', 'most', 'when', 'should', 'mustn', "you'd", 'not', 'this', 'theirs', 'own', 'until', 'will', 'what', 'more', 'hasn', 'doesn', 'a', 'now', 'an', 'where', 'for', 'their', 'yours', 't', 'same', "couldn't", 'above', "isn't", 'll', 'nor', 'were', "didn't", 'after', 'and', "don't", 'other', "mightn't", 'about', "should've", 'of', 'while', 'aren', 'doing', 'few', 'in', 'both', 'with', 'over', 'haven', 'to', "haven't", 'such', 'up', 'is', 'why', 'no', 'only', 'at', 'having', 'shan', 'be', 'ours', "you've", 'yourself', 'we', 'd'}
2.3. 정규화
-
이전에 얘기했다시피 goes와 같은 3인칭 동사이지만 go와 같은 취급을 위해 변환을 하는 작업
-
어간 추출
-
어형이 변형된 단어로부터 접사등을 제거하고 그 단어의 어간을 분리해 내는 작업
-
어형 : 단어의 형태를 의미
-
어간 : 어형변화에서 변화하지 않는 부분 or 용언의 바뀌지 않는 부분
-
용언 : 문장 안에서 서술하는 구실을 하는 동사와 형용사
-
예시

-
영어와 우리말은 원리와 구조가 달라서 어간 추출이 다르다.
-
영어의 경우 명사가 복수형으로 기술된 것을 단수형으로 바꾸는 작업도 어간 추출에 포함
-
포터 스테머(Porter Stemmer)
- 마틴 포터가 작성한 스테밍 알고리즘
- 단어가 변형되는 규칙을 이용해 원형을 찾는거라서 사전에 있는 단어로 변환 되는것은 아니다.
from nltk.stem import PorterStemmer stemmer = PorterStemmer() print(stemmer.stem('cooking'), stemmer.stem('cookery'),stemmer.stem('cookbooks')) # cook cookeri cookbook -
랭카스트 스테머(Lancaster Stemmer)
from nltk.stem import LancasterStemmer stemmer = LancasterStemmer() print(stemmer.stem('cooking'), stemmer.stem('cookery'),stemmer.stem('cookbooks')) # cook cookery cookbook- 두 알고리즘 차이는 cookery에서 볼수 있다.
-
-
-
표제어 추출
- 주어진 단어를 기본형으로 변환
- 의미적 관점에서 단어의 기본형을 찾는 것
from nltk.stem import WordNetLemmatizer lemmatizer = WordNetLemmatizer() print(lemmatizer.lemmatize('cooking')) print(lemmatizer.lemmatize('cooking', pos='v')) # 품사를 지정 print(lemmatizer.lemmatize('cookery')) print(lemmatizer.lemmatize('cookbooks')) # cooking # cook # cookery # cookbook- cooking에 대한 기본형이 동일한데 사전에 요리라는 뜻으로 cooking이라는 명사가 존재하기 때문이고 pos에 동사라고 지정하면 cook이라고 반환한다.
2.4. 품사 태깅
-
토큰화와 정규화 과정을 거쳐서 나온 각 결과를 보통은 형태소라고 한다.
-
형태소는 의미를 가진 가장 작은 말의 단위
- 예시 ) 책가방 ⇒ 책 + 가방 ≠>책 + 가 + 방
-
품사의 이해
- 주어진 텍스트를 분리할때 낱말까지 하는것이 좋은지 아니면 형태소까지 하는 것이 좋은지는 분석하고자 하는 내용과 성능에 달려 있다.
- 아래는 우리말의 주요 품사와 설명이다.
| 품사 | 설명 |
|---|---|
| 명사 | 이름을 나타내는 낱말 |
| 대명사 | 이름을 대신해 가리키는 낱말 |
| 수사 | 수량이나 순서를 가리키는 낱말 |
| 조사 | 도와주는 낱말 |
| 동사 | 움직임을 나타내는 낱말 |
| 형용사 | 상태나 성질을 나타내는 낱말 |
| 관형사 | 체언을 꾸며 주는 낱말 |
| 부사 | 주로 용언을 꾸며주는 낱말 |
| 감탄사 | 놀람, 느낌, 부름, 대답을 나타내는 낱말 |
- 용언 : 동사와 형용사를 함께 부르는 말
- 체언 : 명사, 대명사, 수사를 묶어서 부르는 말
- 조사 : 관계언
- 감탄사 : 독립언
- 공용 품사 태그
- 품사 태그는 언어나 학자에 따라 다르게 정의함
- 다양한 언어에서 공통되는 품사 태그를 나타낸 것
| 태그 | 뜻 | 예 |
|---|---|---|
| ADJ | adjective | new, good, high, special, big, local |
| ADP | adposition | on, of, at, with, by, into, under |
| ADV | adverb | really, already, still, early, now |
| CONJ | conjunction | and, or, but, if, while, although |
| DET | determiner, article | the, a, some, most, every, no, which |
| NOUN | noun | year, home, costs, time, Africa |
| NUM | numberal | twenty-four, fourth, 1991, 14:24 |
| PRT | particle | at, on, out, over, per, that, up, with |
| PRON | pronoun | he, their, her, its, my, I, us |
| VERB | verb | is, say, told, given, playing, would |
| . | punctuation marks | .,;! |
| X | other | ersatz, esprit, dunno, gr8, univeristy |
-
펜 트리뱅크 태그 집합
- 공용 품사 태그 집합에 비해 훨씬 세분화된 품사 분류
- https://www.ling.upenn.edu/courses/Fall_2003/ling001/penn_treebank_pos.html
-
NLTK를 활용한 품사 태깅
- 영어는 NLTK가 잘 해준다.
- NLTK는 펜 트리뱅크 태그 집합을 사용한다. 의미를 모르면 코드 마지막 처럼 쓸수 있다.
import nltk from nltk.tokenize import word_tokenize nltk.download('tagsets') tokens = word_tokenize("Hello everyone. It's good to see you. Let's start our text min-ing class!") print(nltk.pos_tag(tokens)) # [('Hello', 'NNP'), ('everyone', 'NN'), ('.', '.'), ('It', 'PRP'), ("'s", 'VBZ'), ('good', 'JJ'), ('to', 'TO'), ('see', 'VB'), ('you', 'PRP'), ('.', '.'), ('Let', 'VB'), ("'s", 'POS'), ('start', 'VB'), ('our', 'PRP$'), ('text', 'JJ'), ('min-ing', 'JJ'), ('class', 'NN'), ('!', '.')] nltk.help.upenn_tagset('NNP') """ NNP: noun, proper, singular Motown Venneboerger Czestochwa Ranzer Conchita Trumplane Christos Oceanside Escobar Kreisler Sawyer Cougar Yvette Ervin ODI Darryl CTCA Shannon A.K.C. Meltex Liverpool ... """- 원하는 품사만 추출
my_tag_set = ['NN', 'VB', 'JJ'] my_words = [word for word, tag in nltk.pos_tag(tokens) if tag in my_tag_set] print(my_words) # ['everyone', 'good', 'see', 'Let', 'start', 'text', 'mining', 'class']- 단어에 품사 정보를 추가해 구분
- 동음이의어를 처리하거나 품사를 이용해 단어를 더 정확하게 구분하는 방법으로 문장의 의미를 정확히 파악하려고 할때 많이 쓰인다.
words_with_tag = ['/'.join(item) for item in nltk.pos_tag(tokens)] print(words_with_tag) # ['Hello/NNP', 'everyone/NN', './.', 'It/PRP', "'s/VBZ", 'good/JJ', 'to/TO', 'see/VB', 'you/PRP', './.', 'Let/VB', "'s/POS", 'start/VB', 'our/PRP$', 'text/JJ', 'min-ing/JJ', 'class/NN', '!/.'] -
한글 형태소 분석과 품사 태깅
- NLTK로는 제대로 안되므로 KoNLPy를 사용한다.
- KoNLPy는 5종의 형태소 분석기를 제공하며 홈페이지에서 성능 비교를 확인할 수 있다.
- morphs(phrase) : 주어진 텍스트를 형태소 단위로 분리
- nouns(phrase) : 주어진 텍스트를 형태소 단위로 분리해서 명사만 반환
- pos(phrase) : 주어진 텍스트를 형태소 단위로 분리하고 각 형태소에 부착해 반환
from konlpy.tag import Okt t = Okt() sentence = '''절망의 반대가 희망은 아니다. 어두운 밤하늘에 별이 빛나듯 희망은 절망 속에 싹트는 거지 만약에 우리가 희망함이 적다면 그 누가 세상을 비출어줄까. 정희성, 희망 공부''' print('형태소:', t.morphs(sentence)) print() print('명사:', t.nouns(sentence)) print() print('품사 태깅 결과:', t.pos(sentence)) """ 형태소: ['절망', '의', '반대', '가', '희망', '은', '아니다', '.', '어', '두운', '밤하늘', '에', '별', '이', '빛나듯', '\\n', '희망', '은', '절망', '속', '에', '싹트는', '거지', '\\n', '만약', '에', '우리', '가', '희망', '함', '이', '적다면', '\\n', '그', '누가', '세상', '을', '비출어줄까', '.', '정희성', ',', '희망', '공부'] 명사: ['절망', '반대', '희망', '어', '두운', '밤하늘', '별', '희망', '절망', '속', '거지', '만약', '우리', '희망', '함', '그', '누가', '세상', '정희성', '희망', '공부'] 품사 태깅 결과: [('절망', 'Noun'), ('의', 'Josa'), ('반대', 'Noun'), ('가', 'Josa'), ('희망', 'Noun'), ('은', 'Josa'), ('아니다', 'Adjective'), ('.', 'Punctuation'), ('어', 'Noun'), ('두운', 'Noun'), ('밤하늘', 'Noun'), ('에', 'Josa'), ('별', 'Noun'), ('이', 'Josa'), ('빛나듯', 'Verb'), ('\\n', 'Foreign'), ('희망', 'Noun'), ('은', 'Josa'), ('절망', 'Noun'), ('속', 'Noun'), ('에', 'Josa'), ('싹트는', 'Verb'), ('거지', 'Noun'), ('\\n', 'Foreign'), ('만약', 'Noun'), ('에', 'Josa'), ('우리', 'Noun'), ('가', 'Josa'), ('희망', 'Noun'), ('함', 'Noun'), ('이', 'Josa'), ('적다면', 'Verb'), ('\\n', 'Foreign'), ('그', 'Noun'), ('누가', 'Noun'), ('세상', 'Noun'), ('을', 'Josa'), ('비출어줄까', 'Verb'), ('.', 'Punctuation'), ('정희성', 'Noun'), (',', 'Punctuation'), ('희망', 'Noun'), ('공부', 'Noun')] """