감성분석을 모델링을 진행하기에 앞서 긍정, 부정이 라벨링된 Train Set을 만들기로 했다.
감성어 사전을 바탕으로 형태소 분석을 진행한 뒤, 문장 내 형태소가 긍정어 사전에 있으면 점수에 +1을, 부정어 사전에 있으면 -1을 했고, 각 문장별로 점수가 0보다 작으면 부정(-1), 0보다 크면 긍정(1), 0이면 중립/unknown(0)으로 라벨링했다. 이번 단계의 작업을 통해 얻고자 하는 결과물은, 커뮤니티 게시글, 댓글을 문장 단위로 나누어 긍/부정 라벨링을 진행한 데이터셋이다.
# 사용한 라이브러리
- KoNLPy
- Pandas, Numpy
- Matplotlib.pyplot, Seaborn
Jupyter Notebook 및 윈도우 환경에서는 KoNLPy를 사용하기에 제약이 많다. 따라서 Google Colabaratory를 이용해 KoNLPy를 설치하여 사용했다. 다음의 코드를 실행하면 된다.
! pip3 install konlpy
! wget https://bitbucket.org/eunjeon/mecab-ko/downloads/mecab-0.996-ko-0.9.2.tar.gz
! tar xvfz mecab-0.996-ko-0.9.2.tar.gz > /dev/null 2>&1
! ./configure > /dev/null 2>&1
! make > /dev/null 2>&1
! make check > /dev/null 2>&1
! make install > /dev/null 2>&1
! ldconfig > /dev/null 2>&1
! wget https://bitbucket.org/eunjeon/mecab-ko-dic/downloads/mecab-ko-dic-2.1.1-20180720.tar.gz
! tar xvfz mecab-ko-dic-2.1.1-20180720.tar.gz > /dev/null 2>&1
! ./configure > /dev/null 2>&1
! make > /dev/null 2>&1
! make install > /dev/null 2>&1
! apt-get update > /dev/null 2>&1
! apt-get upgrade > /dev/null 2>&1
! apt install curl > /dev/null 2>&1
! apt install git > /dev/null 2>&1
! bash <(curl -s https://raw.githubusercontent.com/konlpy/konlpy/master/scripts/mecab.sh) > /dev/null 2>&1
다만, 이 역시 Colab에 임시로 KoNLPy를 설치하여 사용하는 것이다. 매 번 새로 노트를 열어 작업할 때마다 다시 설치해줘야 한다. 따라서 이후 본격적인 모델링 및 사용자 사전 추가 등의 작업을 진행하기 위해서는 AWS를 활용해 개발환경을 구축해야 할 듯하다.
# 데이터 전처리
라벨링에 앞서 전처리로 다음과 같은 작업을 진행했다.
첫째, ㄱ-ㅎ, ㅏ-ㅣ 등 하나의 자음, 모음들을 제거했다. 이 과정에서 제거한 문자는 'ㅋㅋㅋ', 'ㅎㅎㅎ', 'ㄲㄲㄲ', 'ㄷㄷㄷ', 'ㅎㄷㄷㄷㄷ', 'ㅜㅠㅜ' 등과 같이 댓글에서 자주 찾아볼 수 있는 문자열이다. 웃음, 슬픔, 놀람 등을 표현하지만, 어휘집합의 크기가 커지고, 긍정, 부정 의미를 파악하기 어렵기 때문에 노이즈로 작용할 것이라 판단했다.
둘째, 특수문자와 구둣점을 제거했다. 이모티콘에 대해 고민했지만, 이모티콘을 글로 바꾸는 기준이 명확하지 않고, 어휘집합의 크기가 커질 것을 예상해 제거하기로 했다.
셋째, (있다면) 답글 표시, 댓글 수정 표시를 제거했다.
정규표현식으로 쉽게 처리할 수 있는 두 번째 단계까지는 함수를 만들어 진행했다. 그러나 커뮤니티별로 댓글을 다는 방식이나 답글, 댓글 수정 표시 유무가 다르기 때문에 데이터를 뜯어 보며 최대한 정제하고자 노력했다.
이를 통해 라벨링을 진행할 80000건 이상의 문장을 얻었다.
# 형태소 분석
Mecab 형태소 분석기를 이용해 각 문장을 형태소 단위로 분석하고, 품사를 태깅했다. 형태소 분석기에서 품사를 태깅하고, 그 결과를 가나다 순으로 정렬하는 class를 구현했다.
# load data
community_raw = pd.read_csv(f"{my_path}/data/community_text.csv")
pos_raw = pd.read_excel(f"{project_path}/02.긍 부정 사전/pos_02.xlsx")
neg_raw = pd.read_excel(f"{project_path}/02.긍 부정 사전/neg_02.xlsx")
# copy data
community_df = community_raw.copy()
pos_df = pos_raw.copy()
neg_df = neg_raw.copy()
# 긍정 형태소, 부정 형태소 리스트
pos_subset = pos_df[['단어', '품사']]
pos_morphs = [f"{x[0]}/{x[1]}" for x in pos_subset.to_numpy()]
neg_subset = neg_df[['단어', '품사']]
neg_morphs = [f"{x[0]}/{x[1]}" for x in neg_subset.to_numpy()]
# 형태소 분석기
class Tokenizer:
def __init__(self, tagger):
self.tagger = tagger
def pos_tag(self, sentence): # call : 부르면 실행
pos = self.tagger.pos(sentence)
pos = sorted([f'{word}/{tag}' for word, tag in pos])
return pos
mecab = Tokenizer(Mecab())
# 형태소 분석
for i in range(len(community_df)):
community_df["tagged_str"][i] = mecab.pos_tag(community_df["text"][i])
그 결과를 dataframe으로 만들고, csv 파일로 저장했다.
# 문장 라벨링
각 문장에 대해 각 문장의 형태소 별로 점수를 매기고, 점수 범위에 따라 긍정, 부정, 중립으로 나누는 함수를 만들었다.
def get_score_morphs(df, tag_colname, pos, neg):
df["score_morph"] = 0
df["label_morph"] = 0
for i in range(len(df)):
sentence = df[tag_colname][i]
val = 0
for morph in sentence:
if morph in pos:
val += 1
elif morph in neg:
val -= 1
# print(f"문장 : {sentence}")
# print(f"점수 : {val}")
df["score"][i] = val
if val > 0 :
df["score_label"][i] = "긍정"
elif val < 0 :
df["score_label"][i] = "부정"
else:
df["score_label"][i] = "중립"
return df
문장의 긍/부정 점수와 긍/부정 라벨링이 완료된 dataframe을 csv 파일로 저장했다.
아래 데이터셋에서 score_morph는 긍/부정 점수를, label_morph는 긍정/부정/중립 여부를, label은 긍정을 1, 부정을 -1, 중립을 0으로 바꿔 정수로 라벨링한 컬럼이다.
# 데이터 확인
긍정, 부정 라벨별로 각 데이터의 분포를 확인한 결과는 다음과 같다.
첫째로 확인되는 특징은 중립, 즉, 긍/부정을 파악할 수 없는 문장의 비중이 상당히 높다는 것이다. 추정되는 이유는 두 가지이다.
우선, 감성어 사전에 수록한 어휘의 수가 전체 데이터의 수에 비해 적다. 감성어 사전에 수록된 어휘의 총 개수는 6천 개도 되지 않는 반면, 전체 문장 데이터를 형태소로 분석했을 때 unique 값은 20만 개가 넘는다. 여기에 더해 어휘와 품사가 정확히 일치해야 점수를 변화시키는 방식으로 라벨링했기 때문에, 전체 데이터셋에서 점수 산정에 활용되는 형태소의 비중이 적었을 가능성이 높다.
다음으로, 라벨링 과정이 단순했다. 점수 산정에 활용되는 형태소의 수가 적다는 점을 차치하더라도, 특정 형태소가 더 강한 긍정 혹은 부정의 감정을 나타낸다면 가중치를 주는 등 다른 방식으로 점수를 산정할 수 있었다. 그러나 시간적인 한계로 인해 어떤 단어에 가중치를 주어야 하는지, 점수를 산정하는 과정에서 공식을 어떻게 사용해야 할지 고민할 수 없었다.
둘째로 확인되는 특징은, 부정으로 라벨링된 문장이 긍정으로 라벨링된 문장의 두 배 이상이라는 것이다. 원인은 명확하다. 부정어 사전에 수록된 형태소의 개수가 긍정어 사전에 수록된 형태소의 개수보다 많다. 그것도 2배 이상. 애초에 감성어 사전을 고민한 지점이었는데, 실제로 라벨링된 결과도 부정이 훨씬 많았다.
나이브하게 라벨의 비중을 살펴봄으로써 확인할 수 있는 것은 라벨의 분포가 상당히 불균형하다는 것이다.
0으로 라벨링된 데이터의 경우, 실제로 긍/부정을 파악할 수 없는 데이터가 있을 것이기에 어떤 데이터들이 0에 분포하고 있는지 확인해야 할 것이다. -1로 라벨링된 데이터가 많다는 지점에 대해서는, 고민이 필요하다. 민감할 수 있지만, 인터넷 글이나 댓글에는 비속어, 정치색이 다른 상대 유저와의 논쟁 등으로 인해 부정적인 데이터 셋이 많을 수도 있다.
imdb 데이터셋이나 네이버 영화 리뷰 데이터셋처럼 train set을 균등하게 구성할 수 있다면 좋을 것이다. 그러나 평점이라는 객관적인 "수치"가 존재하는 영화 리뷰와 달리, 게시물이나 댓글에는 긍정, 부정을 판단할 수 있는 수치적인 기준이 존재하지 않는다. 실제로 Kaggle의 트위터 데이터셋만 보더라도, negative로 라벨링된 데이터가 positive로 라벨링된 데이터에 비해 2배 이상 많다.
귀찮더라도 눈으로 데이터를 확인해야 한다. 라벨링된 결과가 사람의 판단과 얼추 일치하는지, 아니면 터무니 없이 다른지 직접 뜯어 볼 필요가 있다. 판단이 일치하지 않는 부분에 대해, 형태소 분석을 진행하고 그 결과를 뜯어 보며 왜 이러한 결과가 나왔는지 살펴야 한다. 그 이후는, 결국 라벨링의 문제이다. 감성어 사전을 더 정밀하고 엄밀하게 구축해야 한다. 한편으로 라벨링 방식을 정교화하거나, Word2Vec 등 단어 임베딩을 적용할 수도 있을 것이라 보인다.
# 라벨링 결과
감성어 사전의 버전을 업데이트하며 라벨링 결과도 조금씩 달라졌다. 3차례에 걸친 라벨링 결과, 데이터셋 극성 분포는 다음과 같이 변화했다.
ver1 | ver2 | ver3 | |
라벨링 결과 | |||
예 |
주52시간 시행일이 가까워질 수록 우려됩니다. 다른 방안을 갖춰야 합니다. → 긍정 |
주52시간 시행일이 가까워질 수록 우려됩니다. 다른 방안을 갖춰야 합니다. → 중립 | 주52시간 시행일이 가까워질 수록 우려됩니다. 다른 방안을 갖춰야 합니다. → 부정 |
'AI > 정책 댓글 반응 NLP' 카테고리의 다른 글
[4] 포털 댓글 감성 분석_1. 순환신경망_2. 모델링 및 예측 (0) | 2020.04.17 |
---|---|
[4] 포털 댓글 감성 분석_1. 순환신경망_1. 사전 작업 (0) | 2020.04.17 |
[2] 감성어 사전 구축 (35) | 2020.04.10 |
[1] 네이버 뉴스 댓글 크롤러_ver2 (3) | 2020.04.06 |
[1] 커뮤니티 게시물 크롤러_2. 루리웹닷컴 (0) | 2020.04.06 |