관리 메뉴

솜씨좋은장씨

[Keras]기사 제목을 가지고 긍정 / 부정 / 중립으로 분류하는 모델 만들어보기 본문

머신러닝 & 딥러닝/TensorFlow | Keras

[Keras]기사 제목을 가지고 긍정 / 부정 / 중립으로 분류하는 모델 만들어보기

사용자 솜씨좋은장씨 2019. 10. 7. 21:31

프로젝트를 진행하면서 네이버 기사 내용을 긍정/부정으로 분류해주는 기능을 넣자고 하여 구현해보았습니다.

모델을 만드는 것은 위키독스에서 제공하는 딥러닝을 이용한 자연어처리 입문에 나와있는 코드를 활용하였습니다.

 

위키독스

온라인 책을 제작 공유하는 플랫폼 서비스

wikidocs.net

 

0. 코드 관련

2020년 12월 22일 일부 코드 업데이트가 진행되었습니다.

아래의 코드의 대부분은 python과 keras를 공부한지 두달도 안된 상황에서 진행한 코드로 중간중간 설명이 이상하거나

제대로 동작하지 않는 부분이 있을 수도 있습니다. 양해부탁드립니다.

기사 제목 분류 관련 전체적인 코드 업데이트는 2021년에 진행할 예정입니다.

감사합니다.

솜장 드림.

 

1. 학습데이터, 테스트데이터 만들기

먼저 모델을 만들고 나서 학습을 시킬 데이터를 만들기 위해 네이버에서 몇몇의 기업을 선정하고

그 기업에 대한 기사의 제목을 크롤링하였습니다.

 

크롤링 후에 학습시 필요한 긍정, 부정, 중립을 나타내는 label이 있었어야했는데

손으로 일일이 부정, 긍정, 중립 이 세 가지로 label을 붙이려고 하다보니

눈도 아프고 비슷한 내용임에도 불구하고 앞에서는 긍정으로 했다가 뒤에서는 중립으로 표기하는 등의 문제가 있었습니다.

 

이를 컴퓨터가 긍정적인 단어, 부정적인 단어가 포함되어있는지 여부를 확인하여

자동으로 라벨을 붙여주면 편할 것 같아 그렇게 만들어 보았습니다.

 

먼저, 긍정적인 단어, 부정적인 단어가 포함된 txt파일을 각각 만들어주었습니다.

negative_words_self.txt
0.00MB
positive_words_self.txt
0.00MB

뉴스 기사를 보며 만든 긍정적인 단어, 부정적인 단어 모음입니다.

단어는 생각나는대로 계속 추가하고자합니다.

 

코드에서는 이 단어들을 파일에서 positive, negative라는 list로 받아와서

두 개의 list를 합쳐 posneg라는 list를 만들고 크롤링해오는 단어에서 posneg안에 있는 단어가 포함되어있으면 긍정, 부정 라벨을 붙여주고

포함되어있지 않으면 그냥 중립인 0의 상태로 그대로 두도록 만들어 보았습니다.

 

2020년 12월 22일 업데이트 전 코드 ( 업데이트 버전은 스크롤을 내려 나오는 코드를 참고해주세요! )

파일에서 단어를 불러와 posneg리스트를 만드는 코드

import codecs

positive = []
negative = []
posneg = []
    
pos = codecs.open("./positive_words_self.txt", 'rb', encoding='UTF-8')

while True:
    line = pos.readline()
    line = line.replace('\n', '')
    positive.append(line)
    posneg.append(line)

    if not line: break        
pos.close()



neg = codecs.open("./negative_words_self.txt", 'rb', encoding='UTF-8')

while True:
    line = neg.readline()
    line = line.replace('\n', '')
    negative.append(line)
    posneg.append(line)
    
    if not line: break
neg.close()

 

크롤링한 기사 제목과 기사 제목과 posneg를 활용하여 만든 긍정(1), 부정(-1), 중립(0)라벨 정보를 가지는 dataframe을 만드는 코드

(예시 : 네이버에서 버거킹으로 검색하여 나온 기사 4,000개 제목과 각각 제목의 긍정, 부정, 중립 라벨 생성)

import requests
from bs4 import BeautifulSoup
import re
import pandas as pd


label = [0] * 4000

my_title_dic = {"title":[], "label":label}

j = 0


for i in range(400):
    num = i * 10 + 1
    # bhc
#     url = "https://search.naver.com/search.naver?&where=news&query=bhc&sm=tab_pge&sort=0&photo=0&field=0&reporter_article=&pd=0&ds=&de=&docid=&nso=so:r,p:all,a:all&mynews=0&cluster_rank=23&start=" + str(num)
    # 아오리라멘
#     url2 = "https://search.naver.com/search.naver?&where=news&query=%EC%95%84%EC%98%A4%EB%A6%AC%EB%9D%BC%EB%A9%98&sm=tab_pge&sort=0&photo=0&field=0&reporter_article=&pd=0&ds=&de=&docid=&nso=so:r,p:all,a:all&mynews=0&cluster_rank=34&start=" + str(num)
    
    # 버거킹
    url3 = "https://search.naver.com/search.naver?&where=news&query=%EB%B2%84%EA%B1%B0%ED%82%B9&sm=tab_pge&sort=0&photo=0&field=0&reporter_article=&pd=0&ds=&de=&docid=&nso=so:r,p:all,a:all&mynews=0&cluster_rank=23&start=" + str(num)
    
    req = requests.get(url3)
    
    soup = BeautifulSoup(req.text, 'lxml')
    
    titles = soup.select("a._sp_each_title")
    
    for title in titles:
        
        title_data = title.text
        title_data = re.sub('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…\"\“》]', '', title_data)
        my_title_dic['title'].append(title_data)
        
        
        for i in range(len(posneg)):
            posflag = False
            negflag = False
            if i < (len(positive)-1):
#                 print(title_data.find(posneg[i]))
                if title_data.find(posneg[i]) != -1:
                    posflag = True
                    print(i, "positive?","테스트 : ",title_data.find(posneg[i]),"비교단어 : ", posneg[i], "인덱스 : ", i, title_data)
                    break
            if i > (len(positive)-2):
                if title_data.find(posneg[i]) != -1:
                    negflag = True
                    print(i, "negative?","테스트 : ",title_data.find(posneg[i]),"비교단어 : ", posneg[i], "인덱스 : ", i, title_data)
                    break
        if posflag == True:
            label[j] = 1
#             print("positive", j)
        elif negflag == True:
            label[j] = -1
#             print("negative", j)
        elif negflag == False and posflag == False:
            label[j] = 0
#             print("objective", j)
        j = j + 1
my_title_dic['label'] = label
my_title_df = pd.DataFrame(my_title_dic)

이렇게 만든 데이터 프레임은

def dftoCsv(my_title_df, num):
    my_title_df.to_csv(('./title_datas'+ str(num) +'.csv'), sep=',', na_rep='NaN', encoding='utf-8')

다음 코드를 활용하여 csv파일로 저장하였습니다.

 

2020년 12월 22일 업데이트 버전 코드

크롤링 후 positive, negative 단어를 활용해 간단하게 라벨을 다는 부분만 업데이트 하였습니다.

with open("./negative_words_self.txt", encoding='utf-8') as neg:
  negative = neg.readlines()

negative = [neg.replace("\n", "") for neg in negative]

with open("./positive_words_self.txt", encoding='utf-8') as pos:
  positive = pos.readlines()

negative = [neg.replace("\n", "") for neg in negative]
positive = [pos.replace("\n", "") for pos in positive]

먼저 negative 단어 파일, positive 단어 파일을 with open으로 불러와서 각각 파일에 담겨있던 단어들을

리스트로 만들어줍니다.

 

크롤링을 진행하면서 바로 라벨을 붙이는 경우

import requests
from bs4 import BeautifulSoup
import re
import pandas as pd
from tqdm import tqdm

labels = []
titles = []

j = 0


for k in tqdm(range(400)):
    num = k * 10 + 1
    # 버거킹
    url = "https://search.naver.com/search.naver?&where=news&query=%EB%B2%84%EA%B1%B0%ED%82%B9&sm=tab_pge&sort=0&photo=0&field=0&reporter_article=&pd=0&ds=&de=&docid=&nso=so:r,p:all,a:all&mynews=0&cluster_rank=23&start=" + str(num)
    
    req = requests.get(url)
    
    soup = BeautifulSoup(req.text, 'lxml')
    
    titles = soup.select("a._sp_each_title")
    
    for title in titles:
        title_data = title.text
        clean_title = re.sub('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…\"\“》]', '', title_data) 
        negative_flag = False

        label = 0
        for i in range(len(negative)):
          if negative[i] in clean_title:
            label = -1
            negative_flag = True
            print("negative 비교단어 : ", negative[i], "clean_title : ", clean_title) 
            break
        if negative_flag == False:
          for i in range(len(positive)):
            if positive[i] in clean_title:
              label = 1
              print("positive 비교단어 : ", positive[i], "clean_title : ", clean_title)
              break
        titles.append(clean_title)
        labels.append(label)

my_title_df = pd.DataFrame({"title":titles, "label":labels})

그 다음 네이버에서 버거킹에 관한 뉴스 제목을 크롤링 해오고

각 기사에 대해서 아까 만들어둔 negative, positive 리스트를 활용하여 라벨을 붙여줍니다.

 

( 현재 이 방식은 정말정말 기초적인 방식으로 라벨링을 하는 것이기 때문에 실제 긍정 부정과는 다를 수 있습니다. )

크롤링 코드는 상황에 따라 작동하지 않을 수 있습니다.

 

제목만 있는 DataFrame을 가지고 라벨을 붙이는 경우

from tqdm import tqdm
import re

labels = []

title_data = list(my_title_df['title'])

for title in tqdm(title_data):
  clean_title = re.sub('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…\"\“》]', '', title) 
  negative_flag = False
  label = 0
  for i in range(len(negative)):
    if negative[i] in clean_title:
      label = -1
      negative_flag = True
      print("negative 비교단어 : ", negative[i], "clean_title : ", clean_title) 
      break
  if negative_flag == False:
    for i in range(len(positive)):
      if positive[i] in clean_title:
        label = 1
        print("positive 비교단어 : ", positive[i], "clean_title : ", clean_title)
        break

  labels.append(label)

my_title_df['label'] = labels

 

학습데이터는 위의 코드를 통해 만들어진

버거킹기사 4,000개, 아오리라멘 기사 1,000개, 국대떡볶이기사 1,000개 데이터를 합쳐서 활용하였습니다.

train_dataset_1007.csv
0.44MB

테스트데이터는 맘스터치 1,500개의 기사데이터를 활용하였습니다.

test_dataset_1007.csv
0.11MB

 

 

2. 데이터 분석해보기

만들어진 csv파일을 google drive에 업로드하고 google colab에서 google drive를 마운트한 뒤 진행했습니다.

import pandas as pd

train_data = pd.read_csv("./train_dataset_1007.csv")
test_data = pd.read_csv("./test_dataset_1007.csv")

train_data와 test_data를 pandas의 read_csv를 활용하여 dataframe으로 불러옵니다.

 

그 다음 matplotlib을 활용하여 -1, 0, 1 라벨별로 각각 몇개의 데이터가 존재하는지 확인해봅니다.

%matplotlib inline
import matplotlib.pyplot as plt
train_data['label'].value_counts().plot(kind='bar')

 

test_data['label'].value_counts().plot(kind='bar')

숫자로도 확인해봅니다. 1이 긍정 -1이 부정 0이 중립입니다.

print(train_data.groupby('label').size().reset_index(name='count'))
print(test_data.groupby('label').size().reset_index(name='count'))

 

3. 모델을 만들기 위한 데이터 전처리 작업

먼저 각각의 제목을 토큰화 해주었습니다.

Okt형태소 분석기를 활용하였습니다.

stopwords = ['의', '가', '이', '은', '들', '는', '좀', '잘', '걍', '과', '도', '를', '으로', '자', '에', '와', '한', '하다']
import konlpy
from konlpy.tag import Okt
okt = Okt()
X_train = []
for sentence in train_data['title']:
  temp_X = []
  temp_X = okt.morphs(sentence, stem=True) # 토큰화
  temp_X = [word for word in temp_X if not word in stopwords] # 불용어 제거
  X_train.append(temp_X)
  
X_test = []
for sentence in test_data['title']:
  temp_X = []
  temp_X = okt.morphs(sentence, stem=True) # 토큰화
  temp_X = [word for word in temp_X if not word in stopwords] # 불용어 제거
  X_test.append(temp_X)

토큰화가 잘 되었는지 출력해보면 다음과 같습니다.

토큰화 한 단어를 컴퓨터가 인식할 수 있도록 정수인코딩을 해주었습니다.

from keras.preprocessing.text import Tokenizer
max_words = 35000
tokenizer = Tokenizer(num_words = max_words)
tokenizer.fit_on_texts(X_train)
X_train = tokenizer.texts_to_sequences(X_train)
X_test = tokenizer.texts_to_sequences(X_test)

데이터의 최대길이 평균길이 그리고 길이를 기준으로 데이터의 분포가 어떠한지 확인해 보았습니다.

print("제목의 최대 길이 : ", max(len(l) for l in X_train))
print("제목의 평균 길이 : ", sum(map(len, X_train))/ len(X_train))
plt.hist([len(s) for s in X_train], bins=50)
plt.xlabel('length of Data')
plt.ylabel('number of Data')
plt.show()

print("제목의 최대 길이 : ", max(len(l) for l in X_test))
print("제목의 평균 길이 : ", sum(map(len, X_test))/ len(X_test))
plt.hist([len(s) for s in X_test], bins=50)
plt.xlabel('length of Data')
plt.ylabel('number of Data')
plt.show()

다음으로는 y값으로 들어갈 label -1, 0, 1을 컴퓨터가 보고 알수 있도록 one-hot encoding을 해주었습니다.

import numpy as np

y_train = []
y_test = []



for i in range(len(train_data['label'])):
  if train_data['label'].iloc[i] == 1:
    y_train.append([0, 0, 1])
  elif train_data['label'].iloc[i] == 0:
    y_train.append([0, 1, 0])
  elif train_data['label'].iloc[i] == -1:
    y_train.append([1, 0, 0])

for i in range(len(test_data['label'])):
  if test_data['label'].iloc[i] == 1:
    y_test.append([0, 0, 1])
  elif test_data['label'].iloc[i] == 0:
    y_test.append([0, 1, 0])
  elif test_data['label'].iloc[i] == -1:
    y_test.append([1, 0, 0])

y_train = np.array(y_train)
y_test = np.array(y_test)

4. 모델 만들기

from keras.layers import Embedding, Dense, LSTM
from keras.models import Sequential
from keras.preprocessing.sequence import pad_sequences
max_len = 20 # 전체 데이터의 길이를 20로 맞춘다

X_train = pad_sequences(X_train, maxlen=max_len)
X_test = pad_sequences(X_test, maxlen=max_len)

먼저 필요한 것들을 import 해주고 pad_sequences를 활용하여 모든 데이터의 길이를 20으로 통일하였습니다.

model = Sequential()
model.add(Embedding(max_words, 100))
model.add(LSTM(128))
model.add(Dense(3, activation='softmax'))

model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
history = model.fit(X_train, y_train, epochs=10, batch_size=10, validation_split=0.1)

긍정, 부정, 중립 3가지로 분류해야하니 LSTM, softmax, categorical_crossentropy를 사용하였습니다.

batch_size는 10 6,000개의 훈련데이터 중 10퍼센트인 600개는 validation_data로 활용하기위해 validation_split을 0.1을 부여하였습니다.

optimizer는 rmsprop을 사용하여 위와 같이 모델을 만들고 학습을 시켜보았습니다.

맘스터치관련 기사 제목 1,000개로 구성되어있는 테스트 데이터셋으로 평가해보니 94.27퍼센트가 나왔습니다.

 

생각보다 너무 잘나와서 조금 이상하지만 optimizer만 adam으로 바꿔 한번 더 해보았습니다.

 

이번엔 96.07%라는 결과가 나왔습니다.

 

predict = model.predict(X_test)
import numpy as np
predict_labels = np.argmax(predict, axis=1)
original_labels = np.argmax(y_test, axis=1)
for i in range(30):
  print("기사제목 : ", test_data['title'].iloc[i], "/\t 원래 라벨 : ", original_labels[i], "/\t예측한 라벨 : ", predict_labels[i])

numpy와 predict를 활용하여 원래 라벨과 예측한 라벨을 비교해보았습니다.

 

생각보다 예측을 잘 하는 것 같습니다.

 

5. 느낀점 및 앞으로의 계획

저번 영화 평점 예측에 이어 이번엔 기사 제목이 긍정인지, 부정인지, 중립인지 분류해보았습니다.

생각보다 분류를 잘하는 것에 신기했지만 한편으로는 중립 데이터가 많아 잘 되는 것 같은 느낌도 들었습니다.

 

더 공부해서 영화평점예측과 추석에 하다가 말았던 추석 귀성/귀경 소요시간 예측도 더 발전시켜 보고 싶습니다~

43 Comments
  • 이전 댓글 더보기
  • 프로필사진 dlwnghk7@naver.com 2020.01.19 11:00 헉 소중한 답변 정말 너무나 감사합니다. 이 파트를 꼭 해결하고 싶어서요 ㅠㅠ

    (우선 맨 아래에 Google Colab 주소를 적어두었습니다)

    우선 긍정/ 부정 정의된 단어를 정의하는 방법은 똑같이 사용되었습니다.

    import codecs

    positive = []
    negative = []
    posneg = []

    pos = codecs.open("./positive_words_self.txt", 'rb', encoding='UTF-8')

    while True:
    line = pos.readline()
    line = line.replace('\n', '')
    positive.append(line)
    posneg.append(line)

    if not line: break
    pos.close()


    neg = codecs.open("./negative_words_self.txt", 'rb', encoding='UTF-8')

    while True:
    line = neg.readline()
    line = line.replace('\n', '')
    negative.append(line)
    posneg.append(line)

    if not line: break
    neg.close()
    그리고 뉴스 column index (0부터 시작)와 기사의 제목만 있는 1904개 항목 csv 파일을 다음과 같이 읽었습니다.

    import re
    import pandas as pd

    my_title_dic = pd.read_csv('NEWS2.csv', index_col=0)

    그다음 다음의 코드를 취했습니다. url 동작을 할필요없으니 request와 bs4를 제거했습니다.

    label = [0] * 200
    my_title_dic = {"title":[], "label":label}

    j = 0


    for title in titles:
    title_data = title.text
    title_data = re.sub('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…\"\“》]', '', title_data)
    my_title_dic['title'].append(title_data)

    for i in range(len(posneg)):
    posflag = False
    negflag = False
    if i < (len(positive)-1):
    # print(title_data.find(posneg[i]))
    if title_data.find(posneg[i]) != -1:
    posflag = True
    print(i, "positive?","테스트 : ",title_data.find(posneg[i]),"비교단어 : ", posneg[i], "인덱스 : ", i, title_data)
    break
    if i > (len(positive)-2):
    if title_data.find(posneg[i]) != -1:
    negflag = True
    print(i, "negative?","테스트 : ",title_data.find(posneg[i]),"비교단어 : ", posneg[i], "인덱스 : ", i, title_data)
    break
    if posflag == True:
    label[j] = 1
    # print("positive", j)
    elif negflag == True:
    label[j] = -1
    # print("negative", j)
    elif negflag == False and posflag == False:
    label[j] = 0
    # print("objective", j)
    j = j + 1

    my_title_dic['label'] = label
    my_title_df = pd.DataFrame(my_title_dic)


    하지만 다음과 같은 오류가 발생하였습니다.
    ValueError: arrays must all be same length

    https://colab.research.google.com/drive/1EapJ_skQjBZPtD--BtK8Q6CWM8CQ-lNY

    이곳에 시도한 코드가 있는데 혹시 소중한 도움 부탁드려도 될까요? ㅠㅠ
    답변해주셔서 너무나 놀랐고 감사합니다 !
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.01.19 14:59 신고 안녕하세요!
    코드와 함께 상세한 질문 감사드립니다.
    지금 발생하는 오류는 dictionary를 가지고 데이터 프레임을 만들때 title과 label이라는 키에 들어있는 두 데이터의 길이가 달랐을때 발생하는 오류입니다.

    알려주신 Google Colab 링크로 들어가서 코드를 보니

    해당코드 속 NEWS2.csv 데이터가 1,904개의 기사 제목 데이터가 들어있는 것으로 보입니다.

    먼저 pandas의 read_csv를 활용하여
    my_title_df = pd.read_csv('NEWS2.csv')

    my_title_df라는 변수에 dataframe형식으로 데이터를 불러옵니다.

    그 뒤에 my_title_df.columns = ['title']
    코드를 통해서 column명을 title로 설정해줍니다.

    그러면 list(my_title_df['title']) 이 코드를 활용하여 my_title_df 데이터프레임에서 제목만 리스트로 뽑아내어서 활용할 수 있습니다.

    제 코드는 실시간으로 기사 제목을 크롤링해와서 감성사전을 통한 라벨링 이후 dictionary를 만들고 그 dictionary를 가지고 DataFrame으로 만들어 사용하는 코드였고

    질문자님은 이미 기사 제목이 담겨있는 csv파일을 불러와 dataframe 형식으로 불러와서 사용하는 것이니 더 간단하게 해결할 수 있습니다.

    먼저 titles = list(my_title_df['title])
    코드를 통해서 제목만 list로 뽑아옵니다.

    그 다음 감성사전을 통해서 라벨링을 한 데이터를 담을 빈 list를 하나 만들어줍니다.

    label = []

    이제 for 반복문과 감성사전을 통하여 라벨링을 해주면됩니다.

    # label 정보를 담을 빈 label 리스트 생성

    label = []
    for i in range(titles):
    # re.sub을 통해서 기사 제목에서 특수문자 제거
    clean_title = re.sub('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…\"\“》]', '', titles[i])

    for j in range(len(posneg)):
    posflag = False
    negflag = False
    if j < (len(positive)-1):
    if clean_title.find(posneg[j]) != -1:
    posflag = True
    print(j, "positive?","테스트 : ",clean_title.find(posneg[j]),"비교단어 : ", posneg[j], "인덱스 : ", j, clean_title)
    break
    if j > (len(positive)-2):
    if clean_title.find(posneg[j]) != -1:
    negflag = True
    print(j, "negative?","테스트 : ",clean_title.find(posneg[j]),"비교단어 : ", posneg[j], "인덱스 : ", j, clean_title)
    break
    if posflag == True:
    label.append(1)
    elif negflag == True:
    label.append(-1)
    elif negflag == False and posflag == False:
    label.append(0)

    # 데이터를 넣어준 label 리스트를 my_title_df에 추가
    my_title_df['label'] = label
    my_title_df

    해당 코드 colab 공유링크는
    https://colab.research.google.com/drive/1vFIcCXo3nC_V4y-3VxjSrSGEoLDz92ta
    입니다.

    해보시고 또 안되는 부분이 있으실때 알려주시면 답변 드리겠습니다!
    읽어주셔서 감사합니다.
  • 프로필사진 dlwnghk7@naver.com 2020.01.19 17:20 덕분에 잘해결되었습니다 ! 너무나 감사합니다 흑흑 ㅠㅠㅠ 각 문장에 감성점수를 부여하는것까지 다 완료가 되었습니다..
    혹시 감성분석 점수 결과를 높이려면 단어를 늘리는 방법 밖에 없겠죠?? 뉴스 기사 제목을 학습 데이터로 만들고 건수를 늘려서 word2vec이나 Bi-LSTM으로 활용하려고 하거든요.
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.01.19 17:59 신고 도움이 되셨다니 다행입니다!
    긍정부정이 이미 라벨링되어있는 데이터를 가지고 학습을 시킨다면 ( 예를 들어 박은정님의 네이버 영화리뷰 데이터 ) 긍정부정 단어사전 없이 그 데이터를 가지고 LSTM이라던지 다른 모델을 사용하여 학습을 시키면 되지만

    제가 학습 데이터로 사용한 데이터는 네이버에서 크롤링을 통해 얻어 아직 라벨링이 되어있지 않은 데이터를 사용했기에 사람이 라벨링하기에는 한계가 있는 것 같아 단어 사전을 활용해서 라벨링을 실시하고 학습데이터를 만들었던 것입니다!

    단어사전을 만들면서 느꼈던 것은 기사라고 해도 정치나 음식과같은 주제별로 긍정부정을 나타내는 단어가 다른것 같았습니다.

    현재 방식은 부정인 단어가 있는지 없는지 앞에서 부터 확인해보며 글정적인 단어가 존재하면 긍정 으로 분류하고 반복문을 종료하고 부정인 단어가 존재하면 부정으로 분류하는 방식이라

    라벨링 후 결과를 보면 BBQ 1위(위생불량 결과) 와 같은 부정적인 기사제목에서 1위라는 단어가 부정적인 단어보다 앞쪽에 포함되어있어 긍정으로 라벨링되는 경우가 있었습니다.

    이 방법은 조금 시간이 걸리더라도 앞에서부터 끝까지 확인하며 긍정단어 부정단어 개수를 카운트하고 점수를 부여한후 라벨링을 하면 조금 라벨링이 잘 된 나은 학습데이터를 뽑아낼 수 있을 것이라고 생각합니다.

    그리고 긍정인 기사임에도 단어사전에 존재하지 않은 단어 사용으로 인하여 중립으로 라벨링 되어있는 기사도 있어 단어사전의 추가 필요성을 느꼈었습니다.

    조금 더 나은 학습데이터 구축을 위해서는 감성사전 단어 추가도 필요할 것으로 생각합니다~

    금융문자 분석 대회를 도전해보면서 느꼈던 것은 좀 더 성능이 좋은 모델을 만드려면

    여러 데이터 정제방법

    여러 임베딩방법을 고민해보고 도전해보는 것도 좋을 것 같습니다!
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.01.19 17:59 신고 도움이 되셨다니 다행입니다!
    긍정부정이 이미 라벨링되어있는 데이터를 가지고 학습을 시킨다면 ( 예를 들어 박은정님의 네이버 영화리뷰 데이터 ) 긍정부정 단어사전 없이 그 데이터를 가지고 LSTM이라던지 다른 모델을 사용하여 학습을 시키면 되지만

    제가 학습 데이터로 사용한 데이터는 네이버에서 크롤링을 통해 얻어 아직 라벨링이 되어있지 않은 데이터를 사용했기에 사람이 라벨링하기에는 한계가 있는 것 같아 단어 사전을 활용해서 라벨링을 실시하고 학습데이터를 만들었던 것입니다!

    단어사전을 만들면서 느꼈던 것은 기사라고 해도 정치나 음식과같은 주제별로 긍정부정을 나타내는 단어가 다른것 같았습니다.

    현재 방식은 부정인 단어가 있는지 없는지 앞에서 부터 확인해보며 글정적인 단어가 존재하면 긍정 으로 분류하고 반복문을 종료하고 부정인 단어가 존재하면 부정으로 분류하는 방식이라

    라벨링 후 결과를 보면 BBQ 1위(위생불량 결과) 와 같은 부정적인 기사제목에서 1위라는 단어가 부정적인 단어보다 앞쪽에 포함되어있어 긍정으로 라벨링되는 경우가 있었습니다.

    이 방법은 조금 시간이 걸리더라도 앞에서부터 끝까지 확인하며 긍정단어 부정단어 개수를 카운트하고 점수를 부여한후 라벨링을 하면 조금 라벨링이 잘 된 나은 학습데이터를 뽑아낼 수 있을 것이라고 생각합니다.

    그리고 긍정인 기사임에도 단어사전에 존재하지 않은 단어 사용으로 인하여 중립으로 라벨링 되어있는 기사도 있어 단어사전의 추가 필요성을 느꼈었습니다.

    조금 더 나은 학습데이터 구축을 위해서는 감성사전 단어 추가도 필요할 것으로 생각합니다~

    금융문자 분석 대회를 도전해보면서 느꼈던 것은 좀 더 성능이 좋은 모델을 만드려면

    여러 데이터 정제방법

    여러 임베딩방법을 고민해보고 도전해보는 것도 좋을 것 같습니다!
  • 프로필사진 syh 2020.02.19 20:37 좋은글 잘보고갑니다. 혹시 감성사전의 출처를 알 수 있을까요? 저는 크롤링한 경제 데이터에 대하여 라벨링을 해보고자 하는데 괜찮을까해서요
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.02.19 20:44 신고 읽어주셔서 감사합니다. 감성사전은 기사를 하나하나 읽어보면서 긍정적인 기사에는 어떤 단어들이 있는지 부정적인 기사에는 어떤 단어들이 있는지 파악해보며 직접 만들었었습니다.
  • 프로필사진 dtd 2020.03.08 01:37 잘 읽었습니다
    제가 아직 머신러닝을 잘 모르는데 이 정도 구현은 머신러닝 입문자에게 얼마정도 시간이 필요할까요?
    그리고 이렇게 텍스트로부터 유익한 결론을 이끌어내는 일을 적용시킬만한 다른 주제나 아니면 응용하여 할 만한 일이 어떤게 있을까요??
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.03.08 15:45 신고 안녕하세요! 답변이 늦어 죄송합니다.
    저도 아직 이 머신러닝 딥러닝 그리고 자연어처리에 대해서 공부한지 약 8개월 정도 밖에 되지 않았습니다. 그래서 아직 부족한 점이 정말 많습니다 ㅠㅠㅠ 이 글은 한 2달정도 됐을때 위키북스의 코드를 참고하여 만들고 썼던 글입니다. 댓글 달아주신 분도 지속적으로 관심을 가지고 구글링을 통하면 금방 구현하실 수 있을겁니다!

    텍스트로부터 유익한 결론을 이끌어낼만한 다른 주제는 리뷰 기반 추천시스템 등이 있을 것 같습니다!

    부족하지만 글 읽고 댓글 남겨주셔서 감사합니다!
  • 프로필사진 dtd 2020.03.08 17:45 댓글 빨리 달아주셔서 감사합니다!
    리뷰기반 추천시스템이라고 하셨는데 추천 관련된 프로그램 구현은 저도 잘 몰라서 그러는데 많이 어렵나요?
    추천 알고리즘들이 있는거 같긴한데 이 중에서 가장 적합한 걸 찾아서 쓰는게 핵심인가요?

  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.03.08 17:56 신고 저도 아직 제대로 공부해보지는 못해서 자세히는 알려드리지는 못하지만 제가 공부하는 방법은 처음부터 이론적인 내용만 공부하다 보면 재미없으니 일단 기존에 존재하는 여러 알고리즘들을 하나씩 다 적용해보면서 어떻게 동작하는지 이해하고 그것들 중에서 내가 가지고 있는 데이터에 적용하면 성능이 좋게 나올 만한 것을 가져다가 사용합니다! 그럼 공부하는기간동안 여러 결과도 볼수 있고 금방 지루해하지 않는 것 같아 좋은 것 같습니다.
  • 프로필사진 dtd 2020.03.08 20:09 감사합니다!
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.03.08 20:10 신고 화이팅입니다! 즐거운 공부하시기 바랍니다~~
  • 프로필사진 BlogIcon 전댕댕 2020.04.08 16:48 신고 맘씨좋은장씨
  • 프로필사진 택린이 2020.08.03 14:04 코드 공개 정말 감사합니다!

    따라하는 과정에서 문제가 발생했는데요 ㅜㅜ

    제가 원하는 키워드가 포함된 기사 제목을 크롤링 하면 10개까지만 가져와지고 그 밑은 다 NaN으로 표시가 되더라구요.

    그래서 솜장님께서 하신 버거킹 키워드로 해봤는데 이것도 마찬가지로 10개까지만 크롤링되고 나머지는 NaN으로 표시가 되더군요..
    혹시 이에 대해서 답변을 주실수 있는지 궁금합니다!
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.08.03 23:02 신고 확인해보고 말씀드리겠습니다~
  • 프로필사진 택린이 2020.08.04 11:41 네 감사합니닷!
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.08.08 22:31 신고 안녕하세요. 답변이 늦었습니다! 제가 테스트 해본결과 저는 제대로 동작하는 것 같은데 어떤 부분에서 어떻게 동작이 제대로 안되는지 알려주시면 답변 드리겠습니다!

    댓글로 남기기 어려우시면 somjang@kakao.com으로 보내주세요!
  • 프로필사진 시작 2020.08.15 23:48 좋은 글 정말 감사합니다.
    정말 많은 도움이 되었습니다.

    저는 문자열이 아닌 실제 시계열 데이터를 이용해서 바이너리 문제를 해결하려 하는데
    150개의 시계열 데이터가 총 8000여개 정도 있습니다. 데이터로 치면 (8000,150)
    각각에 라벨링도 다 되어있구요. 데이터로 치면 (8000,)

    저는 벡터길이도 150으로 다 맞춰져있고, 다 숫자이기 때문에
    벡터길이 맞추는 작업과 임베딩 작업을 할 필요가 없다고 생각해서

    데이터를 (8000,150,1)의 3차원으로 Reshape하고,
    model.add(LSTM(25, dropout=0.2, input_shape=(150,1)))
    model.add(Dense(1, activation='sigmoid'))
    optimizer = optimizers.SGD(lr=0.1, decay=1e-3, momentum=0.9, nesterov=True)

    history = model.fit(X_train, y_train,
    batch_size=32,
    epochs=50,
    verbose=1,
    validation_data=(X_train, y_train))
    이렇게 모델을 학습하는데 학습,검증 데이터 모두 acc와 loss가 변하지않는 현상이 발생합니다
    이런 경우 어떻게 해야하는지 궁금하네요
    답변해주시면 감사하겠습니다
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.08.27 10:29 신고 시계열 데이터를 바이너리 문제를 해보지는 않아서 해당 방법에 대해서는 조금 찾아보고 고민해봐야할 것 같습니다. 혹시 해결 하셨다면 저도 해결 방법을 들어보고 싶습니다!
  • 프로필사진 2020.08.20 22:59 비밀댓글입니다
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.08.27 10:33 신고 현재 올라와 있는 코드에서는 앞에서 부터 탐색하면서 긍정인 단어가 한번이라도 나오면 긍정으로 분류하기 때문에 긍정인 단어가 2개 이상일 경우는 고려하지 않습니다.

    이를 보완할 방법으로 생각해본 것은 전체 단어에서 긍정인 단어 개수를 카운팅하고 부정인 단어를 카운팅해서 긍정-부정 점수를 조금 세분화하는 방법을 고민해봤는데 해당 방법은 아직 구현해보지는 않았습니다.

    그리고 원래 라벨에 숫자 2가 들어가있는것은 np.argmax를 거치고나니 -1, 0, 1로 되어있던 라벨이 0, 1, 2로 변경되어 그런 것 같습니다. 자연어처리 공부를 하던 초기에 만들었던 코드라 조금 부족한 점이 많은것 같습니다.

    읽어주셔서 감사합니다!
  • 프로필사진 유튜브 2020.08.28 02:31 기사 제목과 posneg를 활용하여 만든 긍정(1), 부정(-1), 중립(0)라벨 정보를 만드는 과정에서
    if i < (len(positive)-1):
    ...
    f i > (len(positive)-2):
    posneg의 인덱스에 조건문을 하신 이유를 알 수 있을까요?

    기사의 라벨 정보를 만드는 과정 코드를 보니 먼저 찾게 되는 단어가 긍정인지 부정인지에 따라 기사의 라벨링이 긍정/부정 으로 나뉘게 되는게 맞나요?
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.08.28 05:21 신고 맞습니다. 현재 블로그에 포스팅 되어있는 방법은 말씀하신대로 앞에서부터 먼저 찾게되는 단어에 따라 긍정인지 부정인지 나뉘어지게됩니다. 그래서 생각했던 방법이 각각의 긍정주정 단어 개수를 카운팅하여 긍부정 척도를 계산하여 라벨링하는 방법을 구현은 해보지않고 생각만 해봤습니다.

    아마 제 글에서 데이터가 불균형했던 부분도 이러한 문제때문이지 않을까 생각해봅니다.
  • 프로필사진 유튜브 2020.08.31 23:13 정말 감사합니다. 감정분석에 대해서 정말 많은 도움이 되었습니다.
    추가적으로 기사 제목과 posneg를 활용하여 만든 긍정(1), 부정(-1), 중립(0)라벨 정보를 만드는 과정에서
    if i < (len(positive)-1):
    ...
    f i > (len(positive)-2):
    posneg의 인덱스에 if문을 하신 이유를 알 수 있을까요?
  • 프로필사진 BlogIcon M/D 2020.10.05 12:39 신고 y값에 원핫인코딩을 하셨는데 x값을 보고 y값을 예측하는건데 원핫인코딩이 의미가있는건가요??
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.10.05 16:25 신고 현재 코드에서 보면 loss 를 categorical_crossentropy를 사용하고 있습니다. 이를 사용하기 위해서는 원핫인코딩된 데이터를 넣어주어야합니다.

    원핫인코딩을 하지 않고 사용하시기 위해서는 sparse_categorical_crossentropy를 사용하시면 될 것 같습니다. 혹시라도 틀린 내용이나 잘못된 정보일 경우 다시 말씀드리겠습니다~
  • 프로필사진 mest 2020.10.06 05:36 안녕하세요 . 따라하면서 정말 많이 배웟습니다. 한가지 궁금한 점이 있는데 만약 긍정 부정 중립 이외에 추가적인 카테고리를 한다면 단순히 one hot encoding에서 0 0 0 1 이런식으로 단순하게 처리해도 문제 없을까요??
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.10.06 13:33 신고 네 해당 방법으로 처리해도 될 것 같고 직접 0 0 0 1 설정해주지 않고 keras.utils에 있는 to_categorical 함수를 활용하셔도 될 것 같습니다.
  • 프로필사진 BlogIcon 야밥2 2020.11.15 16:53 신고 안녕하세요!! 코드 정말 감사합니다!! 그런데 제가 코드를 보고 따라하는 과정중에서 반복문에서 계속 'DataFrame' object cannot be interpreted as an integer이런 오류가 뜹니다..ㅠㅠ 해결 방법이 있을까요??
    혹시 몰라서 코드 주소 올려드립니당..ㅠㅠ
    https://bit.ly/2UqndOu
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.11.17 00:12 신고 안녕하세요! 댓글을 이제봤습니다. ㅠㅠ 확인해보고 답변 드리겠습니다~
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.11.17 09:28 신고 어느 코드 부분에서 어떤 오류가 나는지
    somjang@kakao.com으로 보내주시면 확인해보고 답변드리겠습니다~ 감사합니다~
  • 프로필사진 BlogIcon 야밥2 2020.11.17 12:53 신고 친절한 답변 감사합니다. 오류 내용 메일로 보내드렸습니다~
  • 프로필사진 BlogIcon 야밥2 2020.11.17 21:54 신고 안녕하세요. 혹시 이러한 이유가 뜨는 이유를 알 수 있을까요??ㅠㅠ
    ValueError: Input 0 of layer sequential_3 is incompatible with the layer: expected axis -1 of input shape to have value 10000 but received input with shape [10, 70]
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.11.17 21:54 신고 이 내용도 메일로 보내주시면 내일 답변 드리겠습니다!
  • 프로필사진 BlogIcon 야밥2 2020.11.17 21:56 신고 넵 정말 감사합니다!! 답변 기다리고 있겠습니다!!
  • 프로필사진 구글링 2020.12.10 15:59 이쪽 공부하는 학생인데 덕분에 많은 도움되었습니다 감사합니다 좋은일만 있으시길ㅎㅎㅎ
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.12.16 23:13 신고 감사합니다~
  • 프로필사진 파이썬지겹다 2021.01.21 16:03 test랑 train 모두 같은 데이터가 포함되어 있는 것으로 쓰신건가요 ? ...
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2021.01.21 18:14 신고 안녕하세요 질문주셔서 감사합니다.

    저 당시에 train data에서 model.fit을 할 당시에 validation split =0.1 설정을 활용하여 train data의 10%만 validation data로 활용했습니다.
    test는 test 대로 train과 겹치지 않게 활용하였습니다.

    혹시 답변이 되었을까요!
  • 프로필사진 파이썬지겹다 2021.01.22 09:55 아~ train과 test 데이터가 애초부터 다르군요~! 빠르게 코드만 훑다보니 , 하나의 데이터 내에서 train/test를 나누신줄 알았네요 ㅎㅎ 넵 ! 답변 감사드립니다 ㅎㅎ
댓글쓰기 폼