본문 바로가기
text/Python

네이버 댓글, 다음 댓글 이진분류 해보기

by hoonzii 2022. 1. 23.
반응형

예전에 친구와 얘기를 나누던 중, 다음 기사와 네이버 기사의 댓글 온도차(?) 가 크다는 걸 발견했었다.

해당 기사

 

문제가 됐던 기사 인데, 네이버와 다음의 댓글은 각각 이랬다.

https://news.naver.com/main/read.naver?m_view=1&mode=LSD&mid=sec&sid1=100&oid=032&aid=0003083419

 

문 대통령, 북한 풍산개 '곰이'가 낳은 강아지들 모습 공개

[경향신문] 문재인 대통령이 3일 사회관계망서비스(SNS)에 풍산개 ‘곰이’가 낳은 새끼 7마리의 모습을 공개했다. ‘곰이’는 2018년 남북정상회담 당시 김정은 북한 국무위원장이 문 대통령에게

news.naver.com

네이버 댓글 상태

 

https://news.v.daum.net/v/20210703121619046

 

문 대통령, 북한 풍산개 '곰이'가 낳은 강아지들 모습 공개

[경향신문] 문재인 대통령이 3일 사회관계망서비스(SNS)에 풍산개 ‘곰이’가 낳은 새끼 7마리의 모습을 공개했다. ‘곰이’는 2018년 남북정상회담 당시 김정은 북한 국무위원장이 문 대통령에게

news.v.daum.net

다음 댓글 상태

이 댓글의 온도차를 보면서 친구와 웃고 나서 한참 뒤, 나는 네이버와 다음 댓글 수집 방법에 대해 글을 썼었고,

그 뒤로 또 한참 뒤, 위와 같은 상황이 기억이 나서 실험을 하게 됐다.

궁금증은 네이버와 다음 댓글로 이진 분류를 하면 극명하게 갈릴 것인가?

컴퓨터로 이진분류하는 방법은 무수히 많겠지만 오늘은 머신러닝의 한 방법인 logistic regression 을 사용해보려고 한다.

 

데이터 모으기

데이터 준비는 이전 블로그 글을 참고!

다음 뉴스 댓글 가져오기

 

다음 뉴스 댓글 가져오기

예전에 네이버 댓글을 모았었는데~ 네이버 기사 댓글 가져오기 네이버 기사 댓글 가져오기 네이버 기사 댓글 가져오기 들어가기 전 네이버의 robots.txt 에 대해 먼저 숙지하자. 사용 언어 및 모듈

hoonzi-text.tistory.com

네이버 기사 댓글 가져오기

 

네이버 기사 댓글 가져오기

네이버 기사 댓글 가져오기 들어가기 전 네이버의 robots.txt 에 대해 먼저 숙지하자. 사용 언어 및 모듈     - python 3.7     - request = request 요청을 보내 html 값을 가져오기     ..

hoonzi-text.tistory.com

데이터 수집일은 이 글을 적은 시점과 다르다.

(2021-11-26 날자에 2021-11-25 날짜 기사 (네이버, 다음) 의 기사 댓글 을 수집하였음!)

 

daum 댓글의 경우, api 가 건네준 정보가 많기 때문에 한번 확인을 해야했다

(수집한지 오래되서 기억이 가물가물)

우리가 필요한건 “contents”(댓글) 이다.

네이버, 다음 댓글 간의 개수차이가 존재했는데, (네이버 약 13만개, 다음 약 20만개) 이때 다음 댓글을 약 7만개 정도 덜어내기 위해 기준이 필요했다.

잘보면 중간에  sympathyCount, antipathyCount 값이 존재한다. 공감, 비공감 수를 의미하고

나는 공감 많은 순으로 내림차순 정렬한 뒤, 네이버 댓글 수만큼 개수를 맞춰주려 한다.

daum = daum[daum['contents'] != ""] # 댓글이 공란으로 존재하는 경우가 있어 제거
# 공감순으로 내림차순 정렬
daum = daum.sort_values(by=['sympathyCount'], axis=0, ascending=False)
# 댓글 , 공감수, 비공감수 만 남기고 다른 column 제거
daum = daum[['contents','sympathyCount','antipathyCount']]

 

네이버 댓글 개수만큼 다음 댓글 개수를 맞춰주기 위해

daum = daum[:len(naver)]

다음은 두 datafram을 하나의 데이터로 병합하는 작업이다. 댓글과 사이트별로 라벨링을 해준다.

네이버의 경우 1, 다음의 경우 0으로 라벨링해 차후 logistic regression 할때 분류 기준으로 삼는다.

new_comment_list = []
for i, row in tqdm(naver.iterrows(), total=len(naver)):
    temp_dict = {}
    temp_dict['comment'] = row['content']
    temp_dict['label'] = 1 #naver의 경우, labeling을 1로
    new_comment_list.append(temp_dict)

for i, row in tqdm(daum.iterrows(), total=len(daum)):
    temp_dict = {}
    temp_dict['comment'] = row['contents']
    temp_dict['label'] = 0 #daum의 경우, labeling을 0으로 
    new_comment_list.append(temp_dict)

comment_df = pd.DataFrame(new_comment_list)

병합완료

 

임베딩하기

기본적인 데이터는 준비되었으니, 해당 데이터를 컴퓨터가 알아듣게 숫자 데이터로 바꿔줄 필요가 있다.

내 데스트탑으로는 머신러닝을 돌릴 수 없으니, 구글 Colab을 이용한다.

 

데이터를 불러오기

import pandas as pd
from tqdm.notebook import tqdm
from gensim.models import Word2Vec
import numpy as np

from google.colab import drive
drive.mount('/content/drive') # 구글 드라이브 연결
comment_df = pd.read_csv('경로')

문장→토큰→임베딩 벡터 로 바꾸는데, 토큰의 단위는 음절, 어절, 형태소, 다른 규칙 등 성능이 잘나올 것 같은걸로 진행하면 된다. 내 경우에는 음절 단위로 토큰을 생성했다.

(그전에 다음, 네이버 댓글 dataFrame을 row 단위로 섞어준다.)

comment_df = comment_df.sample(frac=1)  # row 전체 shuffle
comment_df = comment_df[['comment', 'label']]
comment_df.index = range(len(comment_df))

 

train / test set으로 나눈뒤, train set으로 word vector를 구성한다. word vector 의 경우, word2vec 이용!

from sklearn.model_selection import train_test_split
train, test = train_test_split(comment_df, test_size=0.2) # 8:2 로 나누기!

train_token = []
for i, row in tqdm(train.iterrows(), total=len(train)):
    comment = row['comment']
    token_list = comment.split()
    syllable_list = []
    for token in token_list:
        syllable_list.extend([t for t in token]) # 음절 단위로 나눈걸로 문장 구성
    train_token.append(syllable_list)

#벡터 크기는 300, 단어 양옆으로 최대 5개씩, 1개 나온 것도 무시하지 않고 보기, 모델 구성시 thread 4개사용
model = Word2Vec(sentences=train_token, size=300, window=5, min_count=1, workers=4)
model.save("~경로/word2vec_comment_20220122.model")

train set으로 구성한 모델을 이용해 train / test 댓글 → train / test vector로 구성한다.

# comment -> syllable -> vector
def comment2vec(model = None, comment_list = None):
    if model == None:
        return []
    else:
        if comment_list == None:
            return []
        else:
			# train 에 등장하지 않았던 음절이 존재할 수 있기 때문에 모르는 음절은 [0,0,0...]
            unk_vec = np.zeros(model.trainables.layer1_size, dtype="float32")
            comment_vec_list = []
            for comment in tqdm(comment_list, total=len(comment_list)):
                token_list = comment.split()
                comment_vec = []
                for token in token_list:
                    syllable_list = [t for t in token]
                    for syllable in syllable_list:
                        vec = []
                        if syllable in model.wv:
                            vec = model.wv[syllable]
                        else:
                            vec = unk_vec
                        comment_vec.append(vec)
                comment_vec = np.average(comment_vec, axis=0)
                comment_vec_list.append(comment_vec)

            return comment_vec_list

벡터 구성

train_vec = comment2vec(model= model, comment_list = [comment for comment in train['comment']])
test_vec = comment2vec(model = model, comment_list = [comment for comment in test['comment']])

train_label = [ [label] for label in train['label']]
test_label = [ [label] for label in test['label']]

 

 

분류하기

이번에는 TensorFlow 가 아니라 PyTorch를 이용했고, 코드는 여기를 참고했다.

https://wikidocs.net/57805

 

01. 로지스틱 회귀(Logistic Regression)

일상 속 풀고자하는 많은 문제 중에서는 두 개의 선택지 중에서 정답을 고르는 문제가 많습니다. 예를 들어 시험을 봤는데 이 시험 점수가 합격인지 불합격인지가 궁금할 수도 있 ...

wikidocs.net

필요한 모듈 임포트

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

벡터를 torch Tensor 로 변경

torch.manual_seed(1)

train_vec_list = np.array(train_vec)
x_train = torch.FloatTensor(train_vec_list)

train_label = np.array(train_label)
y_train = torch.FloatTensor(train_label)

test_vec_list = np.array(test_vec)
x_test = torch.FloatTensor(test_vec_list)

test_label = np.array(test_label)
y_test = torch.FloatTensor(test_label)

기본적인 logistic regression 학습

# 모델 초기화
W = torch.zeros((model.trainables.layer1_size, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
# optimizer 설정
optimizer = optim.SGD([W, b], lr=1)

nb_epochs = 10000
for epoch in range(nb_epochs + 1):

    # Cost 계산
    hypothesis = torch.sigmoid(x_train.matmul(W) + b)
    cost =  - ((y_train * torch.log(hypothesis) + (1 - y_train) * torch.log(1 - hypothesis)).mean())

    # cost로 H(x) 개선
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()

    # 10번마다 로그 출력
    if epoch % 1000 == 0:
        print('Epoch {:4d}/{} Cost: {:.6f}'.format(
            epoch, nb_epochs, cost.item()
        ))

 

결과를 살펴보자

hypothesis = torch.sigmoid(x_train.matmul(W) + b)
prediction = hypothesis >= torch.FloatTensor([0.5])

true_count = 0
false_count = 0
for label, pred in zip(train_label, prediction):
    if (label == 0 and pred.item() == False) or (label == 1 and pred.item() == True):
        true_count += 1
    else:
        false_count += 1

약 60%...? 이정도면 거의 못한다고 봐야한다!

 

test set 결과

 

역시 60%... 랜덤으로 고르는 결과를 50%라고 보니, 랜덤으로 고른것과 다를 바 없는 것이다.

그래도 마지막으로 내가 쓴 댓글이 다음 댓글인지, 네이버 댓글인지 구별해보자.

def commentClassification(txt):
    txt_vec = comment2vec(model, [txt]) # 문장 -> 토큰 -> 벡터로 변경해주는 함수
    input = torch.FloatTensor(txt_vec[0]) # 해당 벡터를 텐서로 변경

    hypothesis = torch.sigmoid(input.matmul(W) + b) # 학습한 W,b로 결과 반환
    prediction = hypothesis >= torch.FloatTensor([0.5])
    if prediction.item() == False:
        print("daum comment")
    else:
        print("naver comment")

 

흠... 처음가정과 다른 결과가 나왔다.

 

문제는 이럴 것 같다. 토큰 단위를 단순히 음절 단위로 나눈것 (보통 형태소, 어절 단위로 진행)

예를 들어 [ 문, 재, 인] 과 [ 문, 재, 앙] 과 같은 단어가 음절로 나눈걸 word2vec에 돌렸을 경우,

‘인’ 단어와 ‘앙’ 단어는 벡터 공간상 비슷한 곳에 맵핑 될 가능성이 높다. (주변 단어들이 비슷하므로)

나는 인, 앙의 미묘한 차이를 통해 네이버/ 다음 댓글을 구별하고 싶었으니

음절단위로 진행하는 것 잘못된 선택이였다는 것이다!

 

 

아무튼 이렇게 첫번째 댓글 구별을 진행했고, 이후 방법을 달리해 진행할 요소는

  1. 토큰 단위를 다르게 하기 (어절, 형태소, sentencepiece 등)
  2. 임베딩 방법을 다르게 하기 (pretrain 방법이 아니라 nn.Module 임베딩을 해보기?)
  3. 분류 모델 다르게 하기(svm 등?)

하지만 언제 할지는 미지수~

 

이번에도 재밌었다!

반응형

댓글