다시 이음

딥러닝(6) - 자연어 처리(NLP, Natural Language Processing) 본문

AI 일별 공부 정리

딥러닝(6) - 자연어 처리(NLP, Natural Language Processing)

Taeho(Damon) 2021. 10. 25. 22:55

안녕하세요.

 

오늘은 자연어 처리에 대해서 알아보겠습니다.

 

 

자연어(Natural Language)

 

자연어란?

 

자연적으로 발생된 언어를 자연어라고 하는데 이 자연어를 컴퓨터를 통해 처리하는 것을 자연어 처리라고 합니다.

 

 

자연어 처리를 통해서 할 수 있는 것

 

자연어 이해(NLU, Natural Language Understanding)

 

-감정 분석(긍정적?부정적?), 자연어 추론(NLI, Natural Langauge Inference), 기계독해(MRC, Machine Reading Comprehension),질의 응답(QA),품사 태깅(POS tagging), 개체명 인식(Named Entity Recognition)

 

자연어 생성(NLG, Natural Language Generation)

 

기타(NLU&NLG)

 

-기계 번역(Machine Translation),요약(Summerization),추출 요약(Extractive summerization),생성 요약(Absractive summerization),챗봇(Chatbot),TTS(Text to Speech),STT(Speech to Text),Image Captioning

 

 

벡터화(Vectorize)

 

오늘 소개해드리는 것중에 가장 중요한 부분입니다.

 

벡터화란 앞서 말씀드린 자연어를 컴퓨터가 인식할 수 있도록 벡터 형태로 변환하는 작업입니다.

 

이 벡터화를 진행하기 전에 우리는 텍스트 전처리를 꼭! 해야합니다.

 

1) 텍스트 전처리(Text Preprocessing)

 

- 차원 줄이기

: 자연어 처리에서 우리가 분석할 문서의 단어의 종류의 개수가 차원의 개수가 됩니다. 차원의 개수가 많으면 차원의 저주라고 하는 딜레마에 빠지게 되는데요. 이 차원의 저주는 저의 블로그에서 따로 포스팅(https://thogood212.tistory.com/15)한 것이 있으니 참고하시면 좋겠습니다. 이러한 문제를 방지하기 위해 최대한 차원을 줄여주는 것이 좋습니다.

 

- 텍스트 형태 다듬기

: 대소문자 맞추기, 정규식을 통해 텍스트의 속성을 정해주기, 단어들을 토큰화 시키기

#1.데이터를 모두 소문자로 변환
df['brand'] = df['brand'].apply(lambda x: x.lower())
df['brand'].value_counts(

#2. 정규식을 통한 텍스트 형태 다듬는 함수 정의하기
import re
def tokenize(text):
    """text 문자열을 의미있는 단어 단위로 list에 저장합니다.
    Args:
        text (str): 토큰화 할 문자열
    Returns:
        list: 토큰이 저장된 리스트
    """
    # []: [] 사이 문자를 매치, ^: not
	regex = r"[^a-zA-Z0-9 ]" #영어알파벳과 숫자만 가능 #한글은 가-힣 포함
    # 정규식 적용
    tokens = re.sub(regex, subst, text)
    # 소문자로 치환
    tokens = tokens.lower().split()
    return tokens
    
3. spacy 라이브러리 사용하여 토큰화하기
import spacy
from spacy.tokenizer import Tokenizer

nlp = spacy.load("en_core_web_sm")

tokenizer = Tokenizer(nlp.vocab)

tokens = []

for doc in tokenizer.pipe(df['열이름']):
    doc_tokens = [re.sub(r"[^a-z0-9]", "", token.text.lower()) for token in doc]
    tokens.append(doc_tokens)

df['tokens'] = tokens
df['tokens'].head()

 

# 함수를 통해서 토큰화한 데이터를 분석해보기
from collections import Counter

def word_count(docs):
    """ 토큰화된 문서들을 입력받아 토큰을 카운트 하고 관련된 속성을 가진 데이터프레임을 리턴합니다.
    Args:
        docs (series or list): 토큰화된 문서가 들어있는 list
    Returns:
        list: Dataframe
    """
    # 전체 코퍼스에서 단어 빈도 카운트
    word_counts = Counter()

    # 단어가 존재하는 문서의 빈도 카운트, 단어가 한 번 이상 존재하면 +1
    word_in_docs = Counter()

    # 전체 문서의 갯수
    total_docs = len(docs)

    for doc in docs:
        word_counts.update(doc)
        word_in_docs.update(set(doc))

    temp = zip(word_counts.keys(), word_counts.values())

    wc = pd.DataFrame(temp, columns = ['word', 'count'])

    # 단어의 순위
    # method='first': 같은 값의 경우 먼저나온 요소를 우선
    wc['rank'] = wc['count'].rank(method='first', ascending=False)
    total = wc['count'].sum()

    # 코퍼스 내 단어의 비율
    wc['percent'] = wc['count'].apply(lambda x: x / total)

    wc = wc.sort_values(by='rank')

    # 누적 비율
    # cumsum() : cumulative sum
    wc['cul_percent'] = wc['percent'].cumsum()

    temp2 = zip(word_in_docs.keys(), word_in_docs.values())
    ac = pd.DataFrame(temp2, columns=['word', 'word_in_docs'])
    wc = ac.merge(wc, on='word')
    
    # 전체 문서 중 존재하는 비율
    wc['word_in_docs_percent'] = wc['word_in_docs'].apply(lambda x: x / total_docs)

    return wc.sort_values(by='rank')

 

- 불용어(Stop words) 처리하기

: 불용어란,  접속사, 관사, 부사, 대명사, 일반동사 등을 포함한 일반적으로 분석해도 의미가 없는 단어들의 모음입니다.

 

import spacy
from spacy.tokenizer import Tokenizer

nlp = spacy.load("en_core_web_sm")

tokenizer = Tokenizer(nlp.vocab)

# spacy라이브러리 내에 기본 저장되어있는 불용어 모음 확인해보기
print(nlp.Defaults.stop_words)

# 불용어 추가 혹은 제거 하여 사용하기
STOP_WORDS = nlp.Defaults.stop_words.union(['추가할 불용어'])

tokens = []

for doc in tokenizer.pipe(df['reviews.text']):
    doc_tokens = []
    for token in doc: 
        if token.text.lower() not in STOP_WORDS:
            doc_tokens.append(token.text.lower())
   
    tokens.append(doc_tokens)
    
df['tokens'] = tokens

 

- 통계적 트리밍(Trimming)

: 말 그대로 통계적으로 너무 많이 나타나는 단어 혹은 너무 없는 단어를 제외하는 방법입니다.

너무 많이 나타나는 단어는 있어도 유의미한 통찰력을 가져오지 못하며, 너무 없는 단어는 오타 혹은 의미없는 단어일 확률이 높기 때문입니다.

 

- 어간 추출(Stemming)과 표제어 추출(Lemmatization)

  • 어간추출은 말 그대로 어근이 되는 부분만 남기고 접두사,접미사를 제거합니다.
  • 단어의 앞뒤가 잘려 사전에 없는 단어가 나오기도 합니다.
  • 그러나, 알고리즘이 간단하여 속도가 빠릅니다.
from nltk.stem import PorterStemmer

ps = PorterStemmer()

tokens = []
for doc in df['tokens']:
    doc_tokens = []
    for token in doc:
        doc_tokens.append(ps.stem(token))
    tokens.append(doc_tokens)

df['stems'] = tokens
  • 표제어 추출은 단어들이 기본 사전형 단어 형태인 Lemma(표제어)로 변환됩니다.
  • 명사의 복수형은 단수형으로, 동사는 모두 타동사로 변환됩니다.
  • 그래서 체계적이나 속도가 느리다는 단점이 있습니다.
import spacy
from spacy.tokenizer import Tokenizer

nlp = spacy.load("en_core_web_sm")

def get_lemmas(text):

    lemmas = []
    
    doc = nlp(text)

    for token in doc: 
        if ((token.is_stop == False) and (token.is_punct == False)) and (token.pos_ != 'PRON'):
            lemmas.append(token.lemma_)
    
    return lemmas

 

 

2) 벡터화

 

우리는 벡터화 한 데이터를 문서-단어 행렬(Document-Term Matrix, DTM)이라는 형태로 만듭니다.

문서-단어 행렬이란 각 행에는 문서((Document)가, 각 열에는 단어(Term)가 있는 행렬입니다.

이 형태에 안에 들어갈 내용을 채우기 위한 방법으로 Bag-of-Words(TF, TF-IDF)가 있습니다.

 

 

등장 횟수 기반의 단어 표현(Count-based Representation)

 

-Bag-of-Words(BoW) : TF(Term Frequency)

: 문법이나 단어의 순서 등을 무시하고 단순히 단어들의 빈도만 고려하여 벡터화합니다.

import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.neighbors import NearestNeighbors
from sklearn.decomposition import PCA
import spacy

nlp = spacy.load("en_core_web_sm")

# CountVectorizer를 변수에 저장합니다.
# 특성이 많을 경우 제한하여 사용 가능합니다.
count_vect = CountVectorizer(stop_words='english', max_features=100)

# 학습시키기
dtm_count = count_vect.fit_transform(df['열이름'])

# 행에 숫자, 열에 단어들 배열하고 빈도수 체크하기
dtm_count = pd.DataFrame(dtm_count.todense(), columns=count_vect.get_feature_names())
dtm_count

 

- Bag-of-Words(BoW) : TF-IDF (Term Frequency - Inverse Document Frequency)

: 다른 문서에 등장하지 않는 단어, 즉 특정 문서에만 등장하는 단어에 가중치를 두는 방법입니다.

 

❄️ 지프의 법칙을 검색해보면 왜 빈도수가 높은 단어보다 적은 단어에 가중치를 두는지 이해가 쉽습니다.

 

 

 

from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer

# TF-IDF vectorizer.
# 아래와 같이 파라미터 값을 조정하여 사용할 수 있습니다.
tfidf = TfidfVectorizer(stop_words='english'
                        ,tokenizer=tokenize
                        ,ngram_range=(1,2) #토큰화할 때 연속된 단어 몇개까지 하나의 토큰으로 볼것인가
                        ,max_df=.7 # 0.7*100(%)이상의 문서에서 발견되는 단어는 제외
                        ,min_df=3 # 최소 3개의 문서에서 발견되어야 사용가능
                        ,max_features=15)

# Fit 후 dtm을 만듭니다.(문서, 단어마다 tf-idf 값을 계산합니다)
dtm_tfidf = tfidf.fit_transform(sentences_lst)

dtm_tfidf = pd.DataFrame(dtm_tfidf.todense(), columns=tfidf.get_feature_names())
dtm_tfidf

 

 

3) 유사도 분석

 

-NearestNeighbor (K-NN, K-최근접 이웃)

 

K-최근접 이웃법은 쿼리와 가장 가까운 상위 K개의 근접한 데이터를 찾아서 K개 데이터의 유사성을 기반으로 점을 추정하거나 분류하는 예측 분석에 사용됩니다

 

from sklearn.neighbors import NearestNeighbors

# dtm을 사용히 NN 모델을 학습시킵니다. (디폴트)최근접 5 이웃.
nn = NearestNeighbors(n_neighbors=5, algorithm='kd_tree')
nn.fit(df['열이름'])

#내가 찾고 싶은 기준값과 유사한 데이터 찾기(행번호가 출력된다.)
nn.kneighbors([df['열이름'].iloc[몇번째 행]])