자연어 감성 분석

TIL Day 35

By polaris0208

Sentiment Analysis

자연어 감성 분석
¶ Preprocessing
¶ Sentiment Analysis
¶ Add Result
¶ WordCloud

데이터 전처리

불러오기, 결측치 처리

import pandas as pd

df = pd.read_csv("/netflix_reviews.csv")

df['content'] = df['content'].fillna("so so")
df.duplicated(subset=['reviewId', 'userName', 'content', 'score']).sum() # 337
df = df.drop_duplicates(subset=['reviewId', 'userName', 'content', 'score'], keep = 'first')
# 첫번째만 남김

df.duplicated(subset=['reviewId', 'userName', 'content', 'score']).sum() # 0

학습에 필요한 데이터만 분리

df['content']
content = df['content']
socre = df['score']

데이터 개요 파악

  • RegexpTokenizer : 정규표현식 조건을 적용한 뒤 토큰화
  • Text : NLTK 모듈 클래스 선언, 통계 기능 사용 가능
  • 결과: 해석에 불필요한 단어 다수 포함
import nltk
from nltk import Text
from nltk.tokenize import RegexpTokenizer

retokenize = RegexpTokenizer(r"[\w]+") # 문자만

content_1st_lot = ' '.join(content) # 전체 문장을 하나로 통합
lot_1 = Text(retokenize.tokenize(content_1st_lot)) # 토큰화 일괄 적용
lot_1.plot(30) # 상위 30개의 단어 출력

불용어 설정

  • from nltk.corpus import stopwords : 불용어 사전
  • .union(list) : 불용어 사전에 사용자 정의 단어 추가
stop_words = set(stopwords.words('english'))
additional_stopwords = {'app', 'netflix', 'show', 'time', 'series', 'phone', 'movie', 'tv', 'would', 'watch'}
stop_words = stop_words.union(additional_stopwords)
stop_words.discard('not')
# not 이 빠지면 의미가 달리지는 경우가 있어 불용어에서 제외

전처리 함수 설정

표제어 추출

from nltk.stem import WordNetLemmatizer

  • 동사 원형 추출 : 동사의 형태 통일
  • 명사 원형 추출 : 명사의 형태 통일
from nltk.stem import WordNetLemmatizer 
nltk.download('wordnet')

lemmatizer = WordNetLemmatizer()

def lemma_v(sentence): # 동사
    return [lemmatizer.lemmatize(word, 'v') for word in sentence] 

def lemma_n(sentence): # 명사
    return [lemmatizer.lemmatize(word, 'n') for word in sentence]

불필요한 품사 제거

from nltk.tag import pos_tag : 태그 부착 후 태그를 기준으로 정리

  • 고유명사, 인칭 대명사, 관사 제거
from nltk.tokenize import word_tokenize
from nltk.tag import pos_tag

def TagFitter(sentense):
  tokens = pos_tag(word_tokenize(sentense)) # 토큰화
  tags = [t[0] for t in tokens if t[1] != "NNP" and t[1] != "NNPS" and t[1] != "PRP" and t[1] != "DT"]
  # 특정 품사에 해당하지 않는 단어추출
  return ' '.join(tags) # 문장으로 복원

단어 대체

  • 't 는 토큰화 하면 의미 없는t로 출력됨
  • not으로 대체
def t_replacer(sentense):
  tokens = word_tokenize(sentense)
  r = []
  for token in tokens:
    if token == 't': 
      r.append('not')
    else: 
      r.append(token)
  return ' '.join(r)

철자 검토 함수

  • from textblob import TextBlob : 철자 검토 기능
  • .correct()
from textblob import TextBlob
def WordCorrect(senntense):
  s = TextBlob(senntense)
  s = s.correct()
  s = ''.join(s)
  return s

종합하여 전처리 함수 작성

  • 주의점
    • 고유명사 태그는 대문자를 기준으로 하는 경우가 많기 떄문에 .lower() 전에 작성
    • 표제어 추출을 먼저 작성하여 원형만 사용 : 불용어 사전 최소화
    • 연산 효율화, 철자검사 및 불용어 처리를 위해 소문자화
    • 철자 검사는 처리 결과를 확인하고 사용할지 선택
def preprocessing(sentence):
  if isinstance(sentence, float): return '' # 실수형 데이터 제거, 문자형만
  cleaned = re.sub('[^a-zA-Z]', ' ', sentence) # 문자만
  cleaned = t_replacer(cleaned) # 't 를 not으로 대체
  cleaned = TagFitter(cleaned) # 태그를 기준으로 불필요한 품사 제ㄱ
  cleaned = cleaned.lower() # 소문자화
  cleaned = cleaned.strip() # 띄어쓰기 제외한 공백 제거
  cleaned = cleaned.split() # 문장 분할
  cleaned = lemma_v(cleaned) # 동사 원형화
  cleaned = lemma_n(cleaned) # 명사 원형화
  cleaned = [word for word in cleaned if word not in stop_words] 
  # 불용어 제거
  cleaned = ' '.join(cleaned) # 문장으로 복원
  # cleaned = WordCorrect(cleaned) # 철자 검사
  return cleaned

content[0:5].apply(preprocessing)
  # 
0                                             not open
1                                                 best
2    famous korean drama not dub sense pay subscrip...
3       superb please add comment section like youtube
4    reason not give four star opinion many foreign...
Name: content, dtype: object

전처리 결과 확인

  • not은 추후에 제거
  • 혼동을 피하기 위해 데이터 명명에 주의
content_2nd_lot = content.apply(preprocessing)
df['content_c'] = content.apply(preprocessing)
content_2nd_lot = ' '.join(content_2nd_lot)

lot_2 = Text(word_tokenize(content_2nd_lot))
lot_2.plot(30)

¶ Top

감성분석

Sentiment

  • TextBlob, .sentiment
  • 감성분석의 긍정/부정 수치를 출력하는 함수 작성
  • -1.0 ~ 1.0
def sentiment(sentense):
 senti = TextBlob(sentense).sentiment
 polar = senti.polarity
 return polar

test_lot = content[0:10].apply(preprocessing)
test1 = test_lot.apply(sentiment)
test1.head(10)
#
0    0.000000
1    1.000000
2    0.350000
3    1.000000
4    0.036667
5    0.000000
6    0.214286
7    0.700000
8    0.000000
9    0.000000
Name: content, dtype: float64

NaiveBayes

  • NaiveBayes 모델 적용
  • 학습에 시간이 걸림
from textblob.sentiments import NaiveBayesAnalyzer
def NB_cl(sentense):
  senti = TextBlob(sentense, analyzer = NaiveBayesAnalyzer())
  cl = senti.sentiment.classification
  return cl

test2 = test_lot.apply(NB_cl)
test2.head(10)

감성분석 결과 데이터 추가

df['sentiment'] = (content.apply(preprocessing)).apply(sentiment)
df['sentiment']
#
0         0.000000
1         1.000000
2         0.350000
3         1.000000
4         0.036667
            ...   
116926    0.350000
116927    0.450000
116928    0.050000
116929   -0.500000
116930    0.500000
Name: sentiment, Length: 116594, dtype: float64

결과에 라벨 부여

df['sentiment_label'] = df['sentiment'].apply(lambda x: 'positive' if x > 0.1 else ('negative' if x < -0.1 else 'neutral'))
df[['content_c', 'score', 'sentiment_label']]
content_c score sentiment_label
0 not open 1 neutral
1 best 5 positive
2 famous korean drama not dub sense pay subscrip... 2 positive
3 superb please add comment section like youtube 5 positive
4 reason not give four star opinion many foreign... 1 neutral
... ... ... ...
116926 really like many kdramas 5 positive
116927 love always enjoy use 5 positive
116928 sound quality slow 1 neutral
116929 rate expensive bcos see sunday charge hole month 1 negative
116930 awesome english 4 positive

116594 rows × 3 columns

WordCloud

  • 리뷰 감성별 주요 단어 추출

데이터 전처리

감성별로 데이터 분리

pos = df[df['sentiment_label'] == 'positive']
neu = df[df['sentiment_label'] == 'neutral']
neg = df[df['sentiment_label'] == 'negative']

positive = pos['content_c']
neutral = neu['content_c']
negative = neg['content_c']

Not 제거

  • 워드클라우드에 not이 포함되지 않게 제거
rm_not = ['not']

def remove_not(sentense):
  cleaned = sentense.split()
  cleaned = [word for word in cleaned if word not in rm_not]
  cleaned = ' '.join(cleaned)
  return cleaned

positive = positive.apply(remove_not)
neutral = neutral.apply(remove_not)
negative = negative.apply(remove_not)

Gensim - LDA 모델 적용

Latent Dirichlet Allocation
잠재 디리클레 할당: 문서집합에서 토픽을 찾아내는 프로세스

LDA 모델 작성

from gensim.models import LdaModel
def LDA_model(serise):
  preprocessed = serise.apply(simple_preprocess) # 전처리
  dictionary = corpora.Dictionary(preprocessed) # 코퍼스 생성
  bow = [dictionary.doc2bow(doc) for doc in preprocessed] # Bag of Word ; 단어집 생성
  return LdaModel(bow, num_topics=1, id2word=dictionary, passes=10) # passes : 반복 횟수

pos_model = LDA_model(positive)
neu_model = LDA_model(neutral)
neg_model = LDA_model(negative)

토픽 추출

.show_topic(idx)

pos_model.show_topic(0)
# 단어, 비율
[('good', 0.036281966),
 ('love', 0.03461763),
 ('great', 0.018250687),
 ('like', 0.01683506),
 ('best', 0.015260779),
 ('get', 0.011255411),
 ('really', 0.011232897),
 ('use', 0.01018283),
 ('new', 0.008160302),
 ('work', 0.0079550715)]

WordCloud 생성

  • 단어별 빈도수를 계산하여 출력
for idx in range(pos_model.num_topics):
    word_freq = dict(pos_model.show_topic(idx, topn=200))  
    # 인덱스 를 사용하여 단어와 빈도수 가져오기
    # topn : 가져올 단어 수
    
    # 워드클라우드 생성
    wordcloud = WordCloud(width=400, height=200, background_color='white').generate_from_frequencies(word_freq) # 빈도수

    # 워드클라우드 시각화
    plt.figure(figsize=(10, 5))
    plt.imshow(wordcloud, interpolation='bilinear') # 보간법 = 선형
    plt.axis('off')  # 축 제거
    plt.title('Positive Reviews Word Cloud')
    plt.show()

¶ TOP

Tags: TIL NLP