본문 바로가기
Study

[NLP] 1. National Language Processing

by Hwanin99 2024. 8. 19.

 

자연어는 일상 생활에서 사용하는 언어를 뜻한다.

  • 자연어 처리는 자연어의 의미를 분석 처리하는 일이다.
    • 텍스트 분류, 감정 분석, 문서 요약, 번역, 질의 응답, 음성 인식, 챗봇과 같이 응용될 수 있다.

텍스트 처리

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