본문 바로가기

NLP/ch1. 자연어처리 기초

NLP 기초

- 자연어는 일상생활에서 사용하는 언어

- 컴퓨터가 인간의 언어를 학습하는 방법에는 무엇이 있을까

텍스트분류, 감성분석, 문서요약, 번역, 질의 응답, 음성인식, 챗봇으로 응용이 될 수 있다. 

 

강의를 들은 사람들이라고 가정하고 시작하겠습니다.

 

 

split()의 기능중.. 생소했던 부분

s = 'No pain no gain'

s.split().index('gain')
>>> 3

s.split()[2][::-1]
>>> on

 

정규표현식

- 정규표현식은 특정문자들을 편리하게 지정하고 추가, 삭제가 가능합니다. 

- 전처리에서 아주 많이 사용됨.

- re패키치(regular expression)

 

문법

. 앞의 문자 1개를 표현
? 문자 한개를 표현하나 존재할 수도, 존재하지 않을 수도 있음(0개 또는 1개)
* 앞의 문자가 0개 이상
+ 앞의 문자가 최소 1개 이상
^ 뒤의 문자로 문자열이 시작
\$ 앞의 문자로 문자열이 끝남
\{n\} n번만큼 반복
\{n1, n2\} n1 이상, n2 이하만큼 반복, n2를 지정하지 않으면 n1 이상만 반복
\[ abc \] 안에 문자들 중 한 개의 문자와 매치, a-z처럼 범위도 지정 가능
\[ ^a \] 해당 문자를 제외하고 매치
a|b a 또는 b를 나타냄

자주 사용되는 역슬래시응 이용한 문자규칙

\\ 역슬래시 자체를 의미
\d 모든 숫자를 의미, [0-9]와 동일
\D 숫자를 제외한 모든 문자를 의미, [^0-9]와 동일
\s 공백을 의미, [ \t\n\r\f\v]와 동일
\S 공백을 제외한 모든 문자를 의미, [^ \t\n\r\f\v]와 동일
\w 문자와 숫자를 의미, [a-zA-Z0-9]와 동일
\W 문자와 숫자를 제외한 다른 문자를 의미, [^a-zA-Z0-9]와 동일

 

 

match

match(pattern, string) : 문자열의 시작부분부터 매칭이 되는지 검색

 

import re

check = 'ab.'

print(re.match(check, 'abc'))
>>> #ab다음에 하나만 올 수 있다. 매치됨

print(re.match(check, 'c')) 
>>> #매칭안됨
print(re.match(check, 'ab')) 
>>> #매칭안됨

compile사용시 주의 사항

 

★ r = re.compile('ab.')

r.match(check)

-> r을 지정해줘야 한다. 

많이 실수하고 놓치는 부분!!!

 

 

 

 

 

 

match와 search

어려웠던 점

match(pattern, string)   : 문자열의 시작부분부터 매칭이 되는지 검색

 

search(pattern, string) : 문자열 전체를  검사

###match와 search의 차이

##match
print(re.match('a', 'ab'))
>>> 매칭 성공

print(re.match('b', 'ab'))
>>> 매칭 실패
##search
print(re.search('a', 'ab'))
>>> 매칭 성공

print(re.search('b', 'ab'))
>>> 매칭 성공

 

match는  시작부분부터 pattern과 비교를 시작

search는 문장내에서 pattern과 맞는 부분을 다 찾아냄.

 

split

split사용시 생소한 예시

r = re.compile('[1-9]')
print(r.split('s1abc 2v3s 4sss 5a'))
>>> ['s', 'abc ', 'v', 's ', 'sss ', 'a']


##예제
r = re.compile('[ㄱ-ㅎ]')
print(r.split('123ㄱ456ㅈ45648ㄴ84ㅇ'))
>>> ?

 

sub

정규표현식과 일치하는 부분을 다른 문자열로 교체

 

토큰화(Tokenization)

문장을 최소 의미 단위로 잘라서 컴퓨터가 인식하도록 돕는 방법이다.

전처리의 필수적인 작업중 하나!!

 

조심해야함. 

고려해야 할 사항

1. 특수문자를 그냥 제외하면 안됨.

 그것만의 의미가 있을수도.. 

- 예) 마침표는 문장의 결계를 알 수 있게함.

- 예) m.p.h나 Ph.D나 AT&T 같은 경우 : 단어자체에 의미가 있음. 

 

2. 표준 토큰화 

Penn Treebank Tokenization의 규칙

1.) 하이푼으로 구성된 단어는 하나로 유지한다. (-)

2.) doesn't와 같이 아포스트로피로 '접어'가 함께하는 단어는 분리해준다. -> "does", "n't"

단어 토큰화

- "I am student" -> "I", "am", "student" :

>>> nltk패키지의 'punkt'다운로드 -> word_tokenize 

import nltk
nltk.download('punkt')

from nltk.tokenize import word_tokenize
sentence = "Time is gold"
tokens = word_tokenize(sentence)
tokens
>>> ['Time', 'is', 'gold']

 

문장 토큰화 

-  문장 토큰화는 주로 줄바꿈 문자('\n')을 기준으로 문장을 분리

sent_tokenize(sentences)

sentences = 'The world is a beautiful book.\n But of little use to him who cannot read it'

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']

 

토크나이저 

  • WhiteSpaceTokenizer: 공백을 기준으로 토큰화
  • WordPunktTokenizer: 텍스트를 알파벳 문자, 숫자, ㅇ라파벳 이외의 문자 리스트로 토큰화
  • MWETokenizer: MWE는 Multi-Word Expression의 약자로 'republic of korea'와 같이 여러 단어로 이뤄진 특정 그룹을 한 개체로 취급
  • TweetTokenizer: 트위터에서 사용되는 문장의 토큰화를 위해서 만들어졌으며, 문장 속 감성의 표현과 감정을 다룸

n-gram 추출

n-gram은 word2vec에서 예측모델에 많이 쓰이는 개념입니다. 

훗날 window라는 개념으로 쓰입니다. 

from nltk import ngrams

sentence = 'There is no rayal road to learning'
bigram = list(ngrams(sentence.split(), 2))
print(bigram)

>>>[('There', 'is'), ('is', 'no'), ('no', 'rayal'), ('rayal', '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', 'rayal']),
>> WordList(['rayal', 'road']),
>> WordList(['road', 'to']),
>> WordList(['to', 'learning'])]

 

 

 

POS태킹(품사태킹) 

불용어제거 - 영어의 전치사(on, in), 한국어의 조사(을, 를) 등은 분석에 필요하지 않은단어들을 빼주기

import nltk
nltk.download('punkt')

from nltk import word_tokenize
nltk.download('averaged_perceptron_tagger')
nltk.pos_tag(word_tokenize("A rolling stone gathers no moss"))

>> [('A', 'DT'),
>> ('rolling', 'VBG'),
>> ('stone', 'NN'),
>> ('gathers', 'NNS'),
>> ('no', 'DT'),
>> ('moss', 'NN')]

 

철자교정(Speller)

  • 텍스트에 오탈자가 존재하는 경우가 있음
  • 예를 들어, 단어 'apple'을 'aplpe'과 같이 철자 순서가 바뀌거나 spple 같이 철자가 틀릴 수 있음
  • 사람이 적절한 추정을 통해 이해하는데는 문제가 없지만, 컴퓨터는 이러한 단어를 그대로 받아들여 처리가 필요
  • 철자 교정 알고리즘은 이미 개발되어 워드 프로세서나 다양한 서비스에서 많이 적용됨 
spell = Speller('en')

print(spell('people'))
print(spell('peope'))
print(spell('peopae'))

 

 

어간추출(Stemming) 추출 / 표제어(Lemmatization) 추출

어간 : application -> applic 같이  단어의 의미를 담고 있는 단어의 핵심 부분.

표제오 : 삼인칭 단수 동사에는 -s/-es가 붙는다. 이럴때, 동사원형으로 바꿔준다. 

 

 

단어중의성(Lexical Ambiguity)

import nltk
from nltk.wsd import lesk

s = "I saw bats."

print(word_tokenize(s))
>> ['I', 'saw', 'bats', '.']

print(lesk(word_tokenize(s), 'saw'))
>> Synset('saw.v.01')

print(lesk(word_tokenize(s), 'bats'))
>> Synset('squash_racket.n.01')
##나는 야구방망이를 보았다.

 

 

Bag Of Words(BOW)

-  단어집합

from sklearn.feature_extraction.text import CountVectorizer

corpus = ["Think like a man of actioin and act like man of thought."]

vector = CountVectorizer()
bow = vector.fit_transform(corpus)

print(bow.toarray()) #인덱스에서 차지하고 있는 부분
>>> [[1 1 1 2 2 2 1 1]]

print(vector.vocabulary_) #인덱스
>>> {'think': 6, 'like': 3, 'man': 4, 'of': 5, 'actioin': 1, 'and': 2, 'act': 0, 'thought': 7}

 

문서 단어 행렬(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 success but rather try to become a man of value.",
          "Give me liberty, of give me death"]

vector = CountVectorizer(stop_words='english')
bow = vector.fit_transform(corpus)

print(bow.toarray())
>>>[[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]]

print(vector.vocabulary_)
>>> {'think': 7, 'like': 4, 'man': 5, 'action': 1, 'act': 0, 'thought': 8, 'try': 9, 'success': 6, 'value': 10, 'liberty': 3, 'death': 2}

 

어휘 빈도-문서 역빈도(TF-IDF) 분석

Term Frequency - Inverse Document Frequency

- 특정문서에 집중적으로 등장할 때, 해단 단어가 문서의 주제를 잘 담고 있는  핵심어라고 가정

-> 단순히 빈도수가 높은 단어가 핵심어가 아님!!

 

주로 

1. 문서의 유사도를 구하는 작업

2. 검색 시스템에서 검색 결과의 중요도를 정하는 작업

3. 문서 내에서 특정 단어의 중요도를 구하는 작업

 

-  특정 문서에서만!! 등장하는 단어들을 해당 문서의 핵심어로 간주!!

계산 : 어휘빈도(TF) X 역문서 빈도(IDF)

       -> 어휘 빈도 :  특정 문서(x)에서 특정 단어(y)의 등장 횟수

$$tf_{x,y}$$ 

       -> 역문서 빈도 : 다른문서에 등장하지 않는 단어 빈도

$$ log(N/df_{x}) $$ 

                => 특정 문서(x)에 특정 단어가 등장한 문서의 수, Not 단어의 수 

 $$df_{y}$$ 

                => 위에것에 반비례하고, 전체 문서의 수를 곱하고, log를 취함. 

 

     

$$ W_{x,y} = tf_{x,y} * log(N/df_x) $$

https://wikidocs.net/31698

 

04-04 TF-IDF(Term Frequency-Inverse Document Frequency)

이번에는 DTM 내에 있는 각 단어에 대한 중요도를 계산할 수 있는 TF-IDF 가중치에 대해서 알아보겠습니다. TF-IDF를 사용하면, 기존의 DTM을 사용하는 것보다 보다 많…

wikidocs.net

 

 

예를들어 생각해보자

 

여러문서에 한가지 단어의 TF-IDF분석

문서A,B,C가 있다. '박지훈'이란 단어가 각각의 문서에 2번, 7번, 0번 등장했다고 가정하자. 

- 어휘빈도(TF) 

    $$tf_{A,박지훈} = 2 $$

    $$tf_{B,박지훈} = 7 $$

    $$tf_{C,박지훈} = 0 $$

 

- $$df_{박지훈} = 2 $$

# B,C문서 총 2문서에만 '박지훈'이 등장하기 때문

 

- 역문서빈도(IDF)

$$ log(N/df_{x}) = log(frac_{3}{2}) = $$

 

$$TF-IDF(A, '박지훈') = 2 \times \log{\frac{3}{2}}$$

$$TF-IDF(B, '박지훈') = 7 \times \log{\frac{3}{2}}$$

$$TF-IDF(C, '박지훈') = 0 \times \log{\frac{3}{2}}$$

 

문서도 여러개, 단어도 여러개

from sklearn.feature_extraction.text import TfidfVectorizer

corpus = ["Think like a man of action and act like man of thought.", #문서1
          "Try not to become a man of success but rather try to become a man of value.", #문서2
          "Give me liberty, of give me death"] #문서3
tfidf = TfidfVectorizer(stop_words='english').fit(corpus) 
print(tfidf.vocabulary_)
>>> {'think': 7, 'like': 4, 'man': 5, 'action': 1, 'act': 0, 'thought': 8, 'try': 9, 'success': 6, 'value': 10, 'liberty': 3, 'death': 2}
#BoW생성

print(pd.DataFrame(tfidf.transform(corpus).toarray()))
         0         1         2         3         4         5         6   
0  0.311383  0.311383  0.000000  0.000000  0.622766  0.473630  0.000000   
1  0.000000  0.000000  0.000000  0.000000  0.000000  0.527533  0.346821   
2  0.000000  0.000000  0.707107  0.707107  0.000000  0.000000  0.000000   

         7         8         9         10  
0  0.311383  0.311383  0.000000  0.000000  
1  0.000000  0.000000  0.693642  0.346821  
2  0.000000  0.000000  0.000000  0.000000

 

또한 다음과 같이 문장간의 유사도 또한 구할 수 있다. 

from sklearn.metrics.pairwise import linear_kernel
tfidf_matrix = tfidf.fit_transform(corpus)
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)
cosine_sim

>>>array([[1.        , 0.24985513, 0.        ],
>>>       [0.24985513, 1.        , 0.        ],
>>>       [0.        , 0.        , 1.        ]])