자연어는 일상 생활에서 사용하는 언어를 뜻한다.
- 자연어 처리는 자연어의 의미를 분석 처리하는 일이다.
- 텍스트 분류, 감정 분석, 문서 요약, 번역, 질의 응답, 음성 인식, 챗봇과 같이 응용될 수 있다.
텍스트 처리
1. 대소문자 통합
- 대소문자를 통합하지 않는다면 컴퓨터는 같은 단어를 다르게 받아들인다.
- 파이썬의 내장 함수인 lower(), upper()를 통해 간단하게 통합이 가능하다.
word = 'AbCdEfGh'
lower = word.lower()
upper = word.upper()
print(lower, upper)
---------------------
abcdefh ABCDEFG
2. 정규 표현식
- 정규 표현식은 특정 문자들을 편리하게 지정하고 추가, 삭제가 가능하다.
- 데이터 전처리에서 정규 표현식을 많이 사용한다.
- 파이썬에서는 정규 표현식을 지원하는 re 패키지를 제공한다.
정규 표현식 문법 | 역슬래시를 이용한 문법 | ||
. | 앞의 문자 1개를 표현 | \\ | 역슬래시 자체를 의미 |
? | 문자 한개를 표현하나 존재할 수도, 존재하지 않을 수도 있음(0개 또는 1개) | \d | 모든 숫자를 의미, [0-9]와 동일 |
* | 앞의 문자가 0개 이상 | \D | 숫자를 제외한 모든 문자를 의미, [^0-9]와 동일 |
+ | 앞의 문자가 최소 1개 이상 | \s | 공백을 의미, [ \t\n\r\f\v]와 동일 |
^ | 뒤의 문자로 문자열이 시작 | \S | 공백을 제외한 모든 문자를 의미, [^ \t\n\r\f\v]와 동일 |
\$ | 앞의 문자로 문자열이 끝남 | \w | 문자와 숫자를 의미, [a-zA-Z0-9]와 동일 |
\{n\} | n번만큼 반복 | \W | 문자와 숫자를 제외한 다른 문자를 의미, [^a-zA-Z0-9]와 동일 |
\{n1, n2\} | n1 이상, n2 이하만큼 반복, n2를 지정하지 않으면 n1 이상만 반복 | ||
\[ abc \] | 안에 문자들 중 한 개의 문자와 매치, a-z처럼 범위도 지정 가능 | ||
\[ ^a \] | 해당 문자를 제외하고 매치 | ||
a|b | a 또는 b를 나타냄 |
Match
- 컴파일한 정규 표현식을 이용해 문자열이 정규 표현식과 맞는지 검사
import re
check = 'ab.'
print(re.match(check, 'abc'))
print(re.match(check, 'c'))
print(re.match(check, 'ab'))
-----------------------------
<re.Match object; span=(0, 3), match='abc'>
None
None
Compile
- Compile을 사용하면 여러 번 사용할 경우 일반 사용보다 더 빠른 속도를 보인다.
- Compile을 통해 정규 표현식을 사용하면 re가 아닌 컴파일한 객체 이름을 통해 사용해야 한다.
import tiem
normal_s_time = time.time()
check = 'ab.'
for i in range(1000):
re.match(check, 'abc')
print('일반 사용시 소요 시간:', time.time()-normal_s_time)
compile_s_time = time.time()
r = re.compile('ab.') # 컴파일한 객체
for i in range(1000):
r.match(check) # 컴파일한 객체 이름을 사용
print('컴파일 사용시 소요 시간:', time.time()-compile_s_time)
------------------------------------------------------------
일반 사용시 소요 시간: 0.0014789104461669922
컴파일 사용시 소요 시간: 0.0006053447723388672
Search
- Match와 다르게, search는 문자열의 전체를 검사한다.
check='ab?'
print(re.search('a',check))
print(re.match('kkkab',check))
print(re.search('kkkab',check))
print(re.match('ab',check))
--------------------------------
<re.Match object; span=(0, 1), match='a'>
None
None
<re.Match object; span=(0, 2), match='ab'>
Split
- 정규 표현식에 해당하는 문자열을 기준으로 문자열을 나눈다.
r = re.compile(' ')
print(r.split('abc abbc abcbab'))
r = re.compile('c')
print(r.split('abc abbc abcbab'))
r = re.compile('[1-9]')
print(r.split('s1abc 2v3s 4sss 5a'))
------------------------------------
['abc', 'abbc', 'abcbab']
['ab', ' abb', ' ab', 'bab']
['s', 'abc ', 'v', 's ', 'sss ', 'a']
Sub
- 정규 표현식과 일치하는 부분을 다른 문자열로 교체한다.
print(re.sub('[a-z]','1','abcdefg'))
print(re.sub('[^a-z]','1','abc defg'))
--------------------------------------
1111111
abc1defg
Findall
- 컴파일한 정규 표현식을 이용해 정규 표현식과 맞는 모든 문자열을 리스트로 반환한다.
print(re.findall('[\d]','1ab 2cd 3ef 4g')) # \d는 숫자
print(re.findall('[\W]','!abcd@@#')) # \W는 문자와 숫자를 제외한 다른 문자
------------------------------------------------------------------------
['1', '2', '3', '4']
['!', '@', '@', '#']
Finditer
- 컴파일한 정규 표현식을 이용해 정규 표현식과 맞는 모든 문자열을 iterator 객체로 반환한다.
- Iterator 객체를 이용하면 생성된 객체를 하나씩 자동으로 가져올 수 있어 처리가 간편하다.
iter1 = re.finditer('[\d]','1ab 2cd 3ef 4g')
print(iter1)
for i in iter1:
print(i)
iter2 = re.finditer('[\W]','!abcd@@#')
print(iter2)
for i in iter2:
print(i)
---------------------------------------------
<callable_iterator object at 0x7fc6a7716310>
<re.Match object; span=(0, 1), match='1'>
<re.Match object; span=(4, 5), match='2'>
<re.Match object; span=(8, 9), match='3'>
<re.Match object; span=(12, 13), match='4'>
<callable_iterator object at 0x7fc6a7816e80>
<re.Match object; span=(0, 1), match='!'>
<re.Match object; span=(5, 6), match='@'>
<re.Match object; span=(6, 7), match='@'>
<re.Match object; span=(7, 8), match='#'>
3. 영어 토큰화
특수 문자에 대한 처리
- 단어에 일반적으로 사용되는 알파벳, 숫자와는 다르게 특수 문자는 별도의 처리가 필요하다.
- 일괄적으로 단어의 특수 문자를 제거하는 방법도 있지만 특수 문자가 단어에 특별한 의미를 가질 때 이를 학습에 반영시키지 못할 수도 있다.
- 특수 문자를 전부 제거하기 보다는 데이터의 특성을 파악하고 처리를 하는 것이 중요하다.
단어에 대한 토큰 분리 방법
- 한 단어이지만 토큰으로 분리할 때 판단되는 문자들로 이루어진, we’re, United Kingdom 등의 단어는 어떻게 분리해야 할지 선택이 필요하다.
- we’re는 한 단어이나 분리해도 단어의 의미에 별 영향을 끼치진 않지만 United Kingdom은 두 단어가 모여 특정 의미를 가리켜 분리해서는 안된다.
- 사용자가 단어의 특성을 고려해 토큰을 분리하는 것이 학습에 유리하다.
단어 토큰화
- 파이썬 내장 함수인 split을 활용해 단어 토큰화
- 공백을 기준으로 단어를 분리한다.
sentence = 'Time is gold'
tokens = [x for x in sentence.split(' ')]
tokens
-----------------------------------------
['Time', 'is', 'gold']
- 토큰화는 nltk 패키지의 tokenize 모듈을 사용해 손쉽게 구현이 가능하다.
- 단어 토큰화는 word_tokenize() 함수를 사용해 구현이 가능하다.
from nltk.tokenize import word_tokenize
tokens = word_tokenize(sentence)
tokens
----------------------------------------
['Time', 'is', 'gold']
문장 토큰화
- 문장 토큰화는 줄바꿈 문자(\n)를 기준으로 문장을 분리한다.
sentences = 'The world is a beautiful book.\nBut of little use to him who cannot read it.'
print(sentences)
tokens = [x for x in sentences.split('\n')] #줄바꿈(\n)을 기준으로 문장을 분리
tokens
------------------------------------------------------------------------------------------
The world is a beautiful book.
But of little use to him who cannot read it.
['The world is a beautiful book.',
'But of little use to him who cannot read it.']
- 문장 토큰화는 sent_tokenize() 함수를 사용해 구현이 가능하다.
from nltk.tokenize import sent_tokenize
tokens=sent_tokenize(sentences)
tokens
----------------------------------------
['The world is a beautiful book.',
'But of little use to him who cannot read it.']
- 문장 토큰화에서는 온점(.)의 처리를 위해 이진 분류기를 사용할 수도 있다.
- 온점은 문장과 문장을 구분해줄 수도, 문장에 포함된 단어를 구성할 수도 있기 때문에 이를 이진 분류기로 분류해 더욱 좋은 토큰화를 구현할 수도 있다.
정규 표현식을 이용한 토큰화
- 토큰화 기능을 직접 구현할 수도 있지만 정규 표현식을 이용해 간단하게 구현할 수도 있다.
- nltk 패키지는 정규 표현식을 사용하는 토큰화 도구인 RegexpTokenizer를 제공한다.
from nltk.tokenize import RegexpTokenizer
sentence = 'Where there\'s a will, there\'s a way'
tokenizer1 = RegexpTokenizer('[\w]+')
tokens1 = tokenizer1.tokenize(sentence)
print(tokens1)
tokenizer2 = RegexpTokenizer('[\s]+',gaps=True)
tokens2 = tokenizer2.tokenize(sentence)
print(tokens2)
--------------------------------------------------
['Where', 'there', 's', 'a', 'will', 'there', 's', 'a', 'way']
['Where', 'there', 's', 'a', 'will', 'there', 's', 'a', 'way']
Keras를 이용한 토큰화
from keras.preprocessing.text import text_to_word_sequence
sentence = 'Where there\'s a will, there\'s a way'
text_to_word_sequence(sentence)
----------------------------------------------------------
['Where', 'there', 's', 'a', 'will', 'there', 's', 'a', 'way']
TextBlob을 이용한 토큰화
from textblob import TextBlob
sentence = 'Where there\'s a will, there\'s a way'
blob=TextBlob(sentence)
blob.words
--------------------------------------------------
['Where', 'there', 's', 'a', 'will', 'there', 's', 'a', 'way']
기타 tokenizer
WhiteSpace Tokenizer | 공백을 기준으로 토큰화 |
WordPunktTokenizer | 텍스트를 알파벳, 문자, 숫자, 알파벳 이외의 문자 리스트로 토큰화 |
MWETokenizer | MWE는 Multi-Word Expression의 약자로, 'republic of korea'와 같이 여러 단어로 이뤄진 특정 그룹을 한 개체로 취급한다. |
TweetTokenizer | 트위터에서 사용되는 문장의 토큰화를 위해서 만들어졌으며, 문장 속 감정 표현을 다룬다. |
4. 한국어 토큰화
- 한국어 자연어 처리 konlpy와 형태소 분석기 MeCab을 설치
!set -x \
&& pip install konlpy \
&& curl -s https://raw.githubusercontent.com/konlpy/konlpy/master/scripts/mecab.sh | bash -x
단어 토큰화
from konlpy.tag import Mecab
tagger = Mecab()
sentence = '언제나 현재에 집중할 수 있다면 행복할것이다.'
tagger.pos(sentence)
------------------------------------------------------
[('언제나', 'MAG'),
('현재', 'NNG'),
('에', 'JKB'),
('집중', 'NNG'),
('할', 'XSV+ETM'),
('수', 'NNB'),
('있', 'VV'),
('다면', 'EC'),
('행복', 'NNG'),
('할', 'XSV+ETM'),
('것', 'NNB'),
('이', 'VCP'),
('다', 'EF'),
('.', 'SF')]
- 토큰화만 실행할 때는 tagger.morphs() 함수를 이용한다.
tagger.morphs(sentence)
-----------------------
['언제나', '현재', '에', '집중', '할', '수', '있', '다면', '행복', '할', '것', '이', '다', '.']
- 형태소만 사용하고 싶을 때는 tagger.nouns() 함수를 이용해 조사, 접속사 등을 제거 가능하다.
tagger.nouns(sentence)
----------------------
['현재', '집중', '수', '행복', '것']
문장 토큰화
- 한국어 문장을 토큰화할 때는 kss(korean sentence splitter) 라이브러리를 이용한다.
import kss
text = '진짜? 내일 뭐하지. 이렇게 애매모호한 문장도? 밥은 먹었어? 나는...'
print(kss.split_sentences(text))
---------------------------------------------------------------------
['진짜? 내일 뭐하지.', '이렇게 애매모호한 문장도?', '밥은 먹었어?', '나는...']
5. n-gram 추출
- n-gram은 n개의 어절이나 음절을 연쇄적으로 분류해 그 빈도를 분석하는 것이다.
- n=1일 때는 unigram
- n=2일 때는 bigram
- n=3일 때는 trigram
Bigram
- nltk 라이브러리
from nltk import ngrams
sentence = 'There is no royal road to learning'
bigram = list(ngrams(sentence.split(),2))
print(bigram)
-----------------------------------------------
[('There', 'is'), ('is', 'no'), ('no', 'royal'), ('royal', 'road'), ('road', 'to'), ('to', 'learning')]
- TextBlob 라이브러리
from textblob import TextBlob
blob = TextBlob(sentence)
blob.ngrams(n = 2)
-----------------------------
[WordList(['There', 'is']),
WordList(['is', 'no']),
WordList(['no', 'royal']),
WordList(['royal', 'road']),
WordList(['road', 'to']),
WordList(['to', 'learning'])]
Trigram
- nltk 라이브러리
trigram = list(ngrams(sentence.split(),3))
print(trigram)
------------------------------------------
[('There', 'is', 'no'), ('is', 'no', 'royal'), ('no', 'royal', 'road'), ('royal', 'road', 'to'), ('road', 'to', 'learning')]
- TextBlob 라이브러리
blob.ngrams(n = 3)
------------------
[WordList(['There', 'is', 'no']),
WordList(['is', 'no', 'royal']),
WordList(['no', 'royal', 'road']),
WordList(['royal', 'road', 'to']),
WordList(['road', 'to', 'learning'])]
6. PoS(Parts of Speech) 태깅
- PoS는 품사를 의미하며, PoS 태깅은 문장 내에서 단어에 해당하는 각 품사를 태깅한다.
from nltk import word_tokenize
nltk.pos_tag(word_tokenize('A rolling stone gathers no moss'))
--------------------------------------------------------------
[('A', 'DT'),
('rolling', 'VBG'),
('stone', 'NN'),
('gathers', 'NNS'),
('no', 'DT'),
('moss', 'NN')]
Number | Tag | Description | 설명 |
1 | CC | Coordinating conjunction | |
2 | CD | Cardinal number | |
3 | DT | Determiner | 한정사 |
4 | EX | Existential there | |
5 | FW | Foreign word | 외래어 |
6 | IN | Preposition or subordinating conjunction | 전치사 또는 종속 접속사 |
7 | JJ | Adjective | 형용사 |
8 | JJR | Adjective, comparative | 헝용사, 비교급 |
9 | JJS | Adjective, superlative | 형용사, 최상급 |
10 | LS | List item marker | |
11 | MD | Modal | |
12 | NN | Noun, singular or mass | 명사, 단수형 |
13 | NNS | Noun, plural | 명사, 복수형 |
14 | NNP | Proper noun, singular | 고유명사, 단수형 |
15 | NNPS | Proper noun, plural | 고유명사, 복수형 |
16 | PDT | Predeterminer | 전치한정사 |
17 | POS | Possessive ending | 소유형용사 |
18 | PRP | Personal pronoun | 인칭 대명사 |
19 | PRP$ | Possessive pronoun | 소유 대명사 |
20 | RB | Adverb | 부사 |
21 | RBR | Adverb, comparative | 부사, 비교급 |
22 | RBS | Adverb, superlative | 부사, 최상급 |
23 | RP | Particle | |
24 | SYM | Symbol | 기호 |
25 | TO | to | |
26 | UH | Interjection | 감탄사 |
27 | VB | Verb, base form | 동사, 원형 |
28 | VBD | Verb, past tense | 동사, 과거형 |
29 | VBG | Verb, gerund or present participle | 동사, 현재분사 |
30 | VBN | Verb, past participle | 동사, 과거분사 |
31 | VBP | Verb, non-3rd person singular present | 동사, 비3인칭 단수 |
32 | VBZ | Verb, 3rd person singular present | 동사, 3인칭 단수 |
33 | WDT | Wh-determiner | |
34 | WP | Wh-pronoun | |
35 | WP$ | Possessive wh-pronoun | |
36 | WRB | Wh-adverb |
7. 불용어 제거
- 영어의 전치사, 한국어의 조사 등은 분석에 필요하지 않은 경우가 많다.
- 길이가 짧은 단어, 등장 빈도 수가 적은 단어들도 분석에 큰 영향을 주지 않는다.
- 일반적으로 사용되는 도구들은 해당 단어들을 제거해주지만 완벽하게 제거되지는 않는다.
- 사용자가 불용어 사전을 만들어 해당 단어들을 제거하는 것이 좋다.
- 도구들이 걸러주지 않는 전치사, 조사 등을 불용어 사전을 만들어 불필요한 단어들을 제거한다.
stop_words = 'on in the'
stop_words = stop_words.split(' ')
sentence = 'singer on the stage'
sentence = sentence.split(' ')
nouns=[]
for noun in sentence:
if noun not in stop_words:
nouns.append(noun)
nouns
----------------------------------
['singer', 'stage']
- nltk 패키지의 불용어 리스트
from nltk import word_tokenize
from nltk.corpus import stopwords
stop_words = stopwords.words('english')
s = 'If you do not walk today, you will have to run tomorrow'
words = word_tokenize(s)
no_stopwords=[]
for w in words:
if w not in stop_words:
no_stopwords.append(w)
print(no_stopwords)
------------------------------------------------------------
['If', 'walk', 'today', ',', 'run', 'tomorrow']
8. 철자 교정
- 사람이 적절한 추정을 통해 이해하는 데는 문제가 없지만, 컴퓨터는 철자가 틀린 단어를 그대로 받아들이기에 교정이 필요하다.
단어
from autocorrect import Speller
spell = Speller('en')
print(spell('peoplle'))
print(spell('peope'))
print(spell('peopae'))
-------------------------------
people
people
people
문장
s = word_tokenize('Earlly biird catchess the womm.')
print(s)
ss = ' '.join([spell(s) for s in s])
print(ss)
----------------------------------------------------
['Earlly', 'biird', 'catchess', 'the', 'womm', '.']
Early bird catches the worm .
9. 언어의 단수화 & 복수화
단수화
from textblob import TextBlob
words='apples bananas oranges'
tb=TextBlob(words)
print(tb.words.singularize())
------------------------------
['apple', 'banana', 'orange']
복수화
words='car train airplane'
tb=TextBlob(words)
print(tb.words.pluralize())
---------------------------
['cars', 'trains', 'airplanes']
10. 어간 추출
import nltk
stemmer = nltk.stem.PorterStemmer()
stemmer.stem('application')
stemmer.stem('beginning')
stemmer.stem('catches')
-----------------------------------
'applic'
'begin'
'catch'
11. 표제어 추출
from nltk.stem.wordnet import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()
lemmatizer.lemmatize('application')
lemmatizer.lemmatize('beginning')
lemmatizer.lemmatize('catches')
------------------------------------------------
'application'
'beginning'
'catch'
12. Bag of Words(BoW)
from sklearn.feature_extraction.text import CountVectorizer
corpus = ['Think like a man of action and act like man of thought']
vector = CountVectorizer()
bow = vector.fit_transform(corpus)
print(bow.toarray())
print(vector.vocabulary_)
-------------------------------------------------------------------
[[1 1 1 2 2 2 1 1]]
{'think': 6, 'like': 3, 'man': 4, 'of': 5, 'action': 1, 'and': 2, 'act': 0, 'thought': 7}
13. 문서 단어 행렬(DTM)
- Document-Term Matrix는 문서에 등장하는 여러 단어들의 빈도를 행렬로 표현한 형태이다.
- 각 문서에 대한 BoW를 하나의 행렬로 표현한 것이다.
from sklearn.feature_extraction.text import CountVectorizer
corpus = ['Think like a man of action and act like man of thought',
'Try not to become a man of succcess but rather try to become a man of vlaue',
'Give me liberty, of give me death']
vector = CountVectorizer(stop_words='english')
bow = vector.fit_transform(corpus)
print(bow.toarray())
print(vector.vocabulary_)
-----------------------------------------------------------------------------------------
[[1 1 0 0 2 2 0 1 1 0 0]
[0 0 0 0 0 2 1 0 0 2 1]
[0 0 1 1 0 0 0 0 0 0 0]]
{'think': 7, 'like': 4, 'man': 5, 'action': 1, 'act': 0, 'thought': 8, 'try': 9, 'succcess': 6, 'vlaue': 10, 'liberty': 3, 'death': 2}
14. TF-IDF 분석
- 어휘 빈도-문서 역빈도(Term Frequency-Inverse Document Frequency)는 단순히 빈도수가 높은 단어가 핵심어가 아닌, 특정 문서에서만 집중적으로 등장할 때 해당 단어가 문서의 주제를 잘 담고 있는 핵심어라고 가정하는 것이다.
- 특정 문서에서 특정 단어가 많이 등장하고 그 단어가 다른 문서에서 적게 등장할 때, 그 단어를 특정 문서의 핵심어로 간주한다.
- 어휘 빈도-문서 역빈도는 어휘 빈도와 역문서 빈도를 곱해 계산이 가능하다.
- 어휘 빈도는 특정 문서에서 특정 단어가 많이 등장하는 것을 의미한다.
$$ tf_{xy} $$
- 역문서 빈도는 다른 문서에서 등장하지 않는 단어 빈도를 의미한다.
$$ log(N/df_x) $$
- 어휘 빈도-문서 역빈도는 다음과 같이 표현한다.
$$ W_{x,y} = tf_{x,y} * log(N/df_x) $$
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer(stop_words='english').fit(corpus)
print(tfidf.transform(corpus).toarray())
print(tfidf.vocabulary_)
-----------------------------------------------------------
[[0.311383 0.311383 0. 0. 0.62276601 0.4736296
0. 0.311383 0.311383 0. 0. ]
[0. 0. 0. 0. 0. 0.52753275
0.34682109 0. 0. 0.69364217 0.34682109]
[0. 0. 0.70710678 0.70710678 0. 0.
0. 0. 0. 0. 0. ]]
{'think': 7, 'like': 4, 'man': 5, 'action': 1, 'act': 0, 'thought': 8, 'try': 9, 'succcess': 6, 'vlaue': 10, 'liberty': 3, 'death': 2}
'Study' 카테고리의 다른 글
[NLP] 6. Topic Modeling (0) | 2024.08.21 |
---|---|
[NLP] 5. Semantic Network Analysis (0) | 2024.08.20 |
[NLP] 4. Document Classification (0) | 2024.08.20 |
[NLP] 3. Cluster Analysis (0) | 2024.08.19 |
[NLP] 2. Keyword Analysis (1) | 2024.08.19 |