자연어처리

IMDB 영화 리뷰 데이타 베이스 Sentiment 분석

coding art 2022. 12. 19. 11:51
728x90

스탠포드 대학 서버에서 제공하는 IMDB 데이타 베이스를 사용하여 Sentiment 분석을 해 보기로 한다. Sentiment 라 함은 여론조사는 아니고 특정 사안에 대한 쏠림 경향 정도로 생각하면 될 듯하다.

 

첫 번째 시도는 Sevastian Raschka의 “Python Machine Learning” 8장의 오픈 소스를 사용해 보았으나 실제로 결과를 얻어낼 수 없었다. 이미 Sevastian Raschka의 친절한 가이드 내용에 따라 tfidf 계산 까지 성공적으로 마쳤으나 마지막 부분에 내용을 읽어 보면 코드 실행 시간이 데스크 톱 PC를 사용할 경우 40분이 걸린다는 언급이 있긴 하지만 뭔가 찾기 어려운 버그들이 내재되어 있는듯하여 또 다른 스텐포드 대학쪽에서 Github 에 올려놓은 오픈소스로 노선을 바꾸기로 한다.

 

이어서 기술되는 Sentiment 분석은 아래의 Github url 주소에서 다운 받아 활용할 수 있다. 아울러 Sentiment 분석에서 제공해야 할 IMDB 데이타 폴더들은 파이선 실행 코드와 같은 폴더에 위치하고 있어야 한다. 이미 설치되어 있는 상태하에서 아무런 수정 없이 작업이 가능하다.

https://github.com/dhavaltejlavwala/Sentimental-Analysis

 

IMDB를 다운로드 받아 압축을 풀어 배치한 폴더 구조이다.

train/test 폴더 밑에는 각각 IMDB의 feature vector들을 저장해 둔 labeledBow.feat 파일들이 들어 있다.

train/labeledBow.feat 파일을 열어보면 첫 문단의 시작과 끝에 해당하는 다음의 데이타를 발견 할 수 있을 것이다. 그 의미를 알아보자.

9 0:9 1:1 2:4 … 47304:1

9는 등급을 나타내며 0:9는 imdb.vocab 에 있는 토큰들 중 첫 번째로서 “the” 가 9회 빈도로 나타난다는 의미다. 1:1은 두 번째 토큰인 “and”가 1회 나타난다는 뜻이다. 47304:1은 47305번째 토큰인 “pettiness” 가 1회 출현한다는 뜻이다.

이런 구조의 데이타들을 읽어 들여 매트릭스를 구성하게 된다면 대부분의 요소들이 0 인 아주 성긴(sparse) 행렬이 될 수밖에 없으므로 load_svmlit_files 와 같은 특별한 명령을 사용하여 데이타를 읽어 들이자.

 

아울러 IMDB 데이타 베이스를 읽어 들이는 부분에서 아래와 같이 폴더 경로를 간단히 수정을 하도록 하자. 이 부분은 코드의 class 인 sentimental_analysis() 의 내부 함수인 method로서 def load_files(self, files): 에서 담당한다.

 

한편 이번에 발굴한 코드로 학습하여 테스트한 결과와 Sevastian Raschka의 텍스트 클리닝 및 토큰화를 시행한 코드의 학습 및 테스트 결과를 비교해 보면 놀랍게도 정밀도 차이가 1% 이내로 아주 작은 것으로 판명이 났다. 한편 이번에 발굴한 코드를 사용한 Logistic Regression의 테스트 정밀도가 88.3% 인 반면에 라이브러리 모듈 ‘re’와 토큰화 과정을 사용한 Sevastian Raschka의 테스트 결과가 89.2%에 불과해 그 차이가 1% 미만이라는 점이 확인되었다. Sevastian Raschka의 코드도 실행해 보았으나 뭔가 bug 문제로 인해 계산 결과를 얻지 못했으므로 이번에 발굴한 코드를 사용하여 영화 리뷰 데이타베이스인 IMDB 데이타를 대상으로 긍정 평가와 부정평가로 분류하기 위해서 직접 Logistic Regression 모델을 필두로 학습시켜 보도록 한다.

 

한편 class 인 sentimental_analysis()의 method 함수인 binerize()에서 읽은 데이타 들 중에 영화 리뷰 내용에 대한 긍정 평가 내지는 부정 평가 여부를 나타내는 영화 등급(rate) class의 라벨 값이 5를 중심으로 양분되어 있어 그 처리를 해 주어야 한다. 등급 6 이상이면 “1”, 등급 5이거나 그 이하이면 “0”으로 처리한다.

직접 TfidfTransformer() 작업을 하게 되면 학습 데이타와 테스트 데이타의 경우 89527이라는 수가 출력되는데 토큰 수가 아닌가 한다. 영화 리뷰별로 계산된 Tfidf 벡터와 영화 등급으로부터 처리된 타겟 값을 사용하여 학습을 하도록 한다.

Binary Classification 작업이긴 하지만 영화 리뷰별 샘플 수가 대단히 많고 동시에 리뷰별 토큰 수가 sparse 한 특성을 감안한다고 해도 제법 커서 학습과정에서 웨이트 매트릭스 계산과정에 컴퓨팅 부담이 상당할 것으로 예상했으나 1.0 초만에 계산이 완료 되었으며 테스트 리뷰들의 정밀도도 88%에 달했다.

 

LogisticRegression 외에도 세부 파라메터가 없는 경우와 있는 경우의 Linear SVM Classifier 가 적용되었으나 결과 정밀도는 LogisticRegression과 비슷한 수준을 보였다. 참고로 SVM Classifier는 비선형 Classification 문제와 MNIST CNN 머신러닝에서도 뛰어난 정밀도를 보여주는 것으로 알려져 있다.

 
 

DecisionTree들에 대한 앙상블 평균값을 계산하는 Random Forests 기법 적용 결과도 기껏 82% 정도의 정밀도를 보여준다.

 

#IMDBX_01.py

# Import all libraries for machine learning
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.svm import LinearSVC, SVC
#from sklearn.utils import shuffle
from sklearn.preprocessing import Binarizer
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_svmlight_files
from sklearn.model_selection import cross_val_predict
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.ensemble import RandomForestClassifier
from time import time

class sentimental_analysis:
    
    def load_files(self, files):
        return load_svmlight_files(files, n_features=None, dtype=None)

    #Calculating Tf-Idf for training and testing
    def tfidf(self, training_data, testing_data):
        tf_transformer = TfidfTransformer()

        print("Training_data TF-IDF")
        # It computes the TF for each review, the IDF using each review, 
        # and finally the TFIDF for each review                                                                                                                                                                                                          
        training_data_tfidf = tf_transformer.fit_transform(training_data)
        print(training_data_tfidf.shape)
        print(training_data_tfidf[0,0])
        print(training_data_tfidf[24999,89526])

        print("Testing_data TF-IDF")
        #transform on the testing data which computes the TF for each review, 
        #then the TF-IDF for each review using the IDF from the training data 
        testing_data_tfidf = tf_transformer.transform(testing_data)
        print(testing_data_tfidf.shape)

        return [training_data_tfidf,testing_data_tfidf]

    #Binerize target data
    #Converting target into binary
    def binerize (self, raw_target):    
        binerize_target = []
        for i in range(len(raw_target)):
            if raw_target[i] > 5:
                binerize_target.append(1) # Positive
            else:
                binerize_target.append(0) # Negative
        return binerize_target

    #Train and test Logistic Regression Classifier
    def lrc(self, training_data, raw_training_target, testing_data, raw_testing_target):
        print("Binerizing target ...")
        training_target = self.binerize(raw_training_target)
        testing_target = self.binerize(raw_testing_target)
        start = time()
        logreg = LogisticRegression()
        print("Training ...")
        logreg.fit(training_data, training_target)
        print("Training Done")
        print("Testing ...")
        logreg_accuracy = logreg.score(testing_data, testing_target) * 100
        end = time()
        return [logreg, round(logreg_accuracy,2), str(round((end-start), 2))]
    
    #Train and test Linear SVM Classifier with and without parameter 
    def lSVC(self, training_data, raw_training_target, testing_data, raw_testing_target, parameter=False):
        print("Binerizing target ...")
        training_target = self.binerize(raw_training_target)
        testing_target = self.binerize(raw_testing_target)
        start = time()
        if parameter == True:        
            result_lSVC= self.lSVC_para(training_data, training_target, testing_data, testing_target)
            end = time()
            return [result_lSVC[0], round(result_lSVC[1],2), result_lSVC[2], str(round((end-start), 2))]
        else:
            clf_linear = LinearSVC()
            print("Training ...")
            clf_linear.fit(training_data, training_target)
            print("Training Done")
            print("Testing ...")
            result_lSVC = clf_linear.score(testing_data, testing_target)*100    
            end = time()
            return [clf_linear, round(result_lSVC,2), str(round((end-start), 2))]
    
    #Calculating best parameter for LinearSVC Classifier
    def lSVC_para(self, training_data, training_target, testing_data, testing_target):
        print("Calculating best parameter for LinearSVC Classifier ...")
        clist = 2**np.array(range(-2, 10), dtype='float')
        cvscores = []
        for c in clist:
            print(c)
            clf= LinearSVC(C=c)
            scores = cross_val_score(clf, training_data, training_target, cv=3)
            print("score", scores)
            cvscores.append(scores.mean()*100)
            bestscore, bestC = max([(val, clist[idx]) for (idx, val) in enumerate(cvscores)])
        print('Best CV accuracy =', round(bestscore,2), '% achieved at C =', bestC)

        #Retrain on whole trainning set using best C value obtained from Cross validation
        print("Retrain on whole trainning set using best C value obtained from Cross validation")
        clf = LinearSVC(C=bestC)
        clf.fit(training_data, training_target)
        accu = clf.score(testing_data, testing_target)*100
        return [clf, accu, bestC]

    #Train and test Random Forest Classifier
    def random_forest(self, training_data, raw_training_target, testing_data, raw_testing_target):
        print("Binerizing target ...")
        training_target = self.binerize(raw_training_target)
        testing_target = self.binerize(raw_testing_target)
        start = time()
        print("Training ...")
        clf_forest = RandomForestClassifier(n_estimators = 100, min_samples_leaf=5, max_features='auto', max_depth=16)
        clf_forest.fit(training_data, training_target)
        print("Training Done")
        print("Testing ...")
        clf_forest_accuracy = clf_forest.score(testing_data, testing_target)*100
        end = time()
        return [clf_forest, round(clf_forest_accuracy,2),str(round((end-start), 2))]

    #Train and test Kernel SVM Classifier
    def kernel_SVM(self, training_data, raw_training_target, testing_data, raw_testing_target):
        print("Binerizing target ...")
        training_target = self.binerize(raw_training_target)
        testing_target = self.binerize(raw_testing_target)
        start = time()
        clf_kernel = SVC()
        print("Training ...")
        clf_kernel.fit(training_data, training_target)
        print("Training Done")
        print("Testing ...")
        end = time()
        clf_kernel_accuracy = clf_kernel.score(testing_data, testing_target)*100
        end = time() 
        return [clf_kernel, round(clf_kernel_accuracy,2),str(round((end-start), 2))]
    
    #Prediction from Random Forest 
    def prediction(self, obj_clf,fileName,labels):
        pre = obj_clf.predict(testing_data)
        print("Done")
        prediction_result = []
        for i in range(len(pre)):
            if pre[i] == 0:
                prediction_result.append(str(i) + ", negative") 
            else:
                prediction_result.append(str(i) + ", positive") 
        self.save_csv(prediction_result, fileName, labels)
        
    #Storing prediction in CSV file
    def save_csv(self, prediction_result, fileName, labels):
        print("Creating CSV file")
        #Open File
        output_file = open(fileName+".csv",'w')
        output_file.write(','.join(labels)+"\n")
        #Write data to file
        for r in prediction_result:
            output_file.write(r + "\n")
        output_file.close()
        print("File saved!")


#Store path in array for training and testing files

#Object for sentiment_analysis
sa = sentimental_analysis()

files = './ac1Imdb/train/labeledBow.feat','./ac1Imdb/test/labeledBow.feat'

#Load data for training_data, training_target and testing_data, testing_target 
print("Loading Files ...")
training_data, raw_training_target, testing_data, raw_testing_target = sa.load_files(files)
print("Done")

#Count tf-idf for training and testing data
tfidf_data = sa.tfidf(training_data, testing_data)

training_data = tfidf_data[0]
testing_data = tfidf_data[1]

print("Logistic Regression Classifier")
result = sa.lrc(training_data, raw_training_target, testing_data, raw_testing_target)
obj_lrc = result[0]
print("Accuracy = ", result[1], "% Time = ", result[2],"seconds")

print("Linear SVM Classifier ")
result = sa.lSVC(training_data, raw_training_target, testing_data, raw_testing_target)
obj_lSCV = result[0]
print("Accuracy = ", result[1], "% Time = ", result[2],"seconds")

print("Linear SVM Classifier With Parameter Selection")
result = sa.lSVC(training_data, raw_training_target, testing_data, raw_testing_target, True)
obj_lSVC_para = result[0]
print("Accuracy = ", result[1], "% at Best C = ", result[2],"Time = ", result[3],"seconds")

print("Random Forest Classifier")
result = sa.random_forest(training_data, raw_training_target, testing_data, raw_testing_target)
obj_random_forest = result[0]
print("Accuracy = ", result[1], "% Time = ", result[2],"seconds")

#print("Kernel SVM Classifier")
#result = sa.random_forest(training_data, raw_training_target, testing_data, raw_testing_target)
#obj_kernel_SVM = result[0]
#print("Accuracy = ", result[1], "% Time = ", result[2],"seconds")

print("Prediction for new dataset from classifier...")
#You can pass any classifier's object for prediction data and file name
labels = ["review","rating"]
sa.prediction(obj_random_forest, "random", labels)