인공지능/RNN

자연어 처리 - 영화 리뷰 분석해서 그래프로 그려보기

쿠와와 2020. 11. 25. 18:04

주석이 부족한 부분이 있으면 말해주세요.

import csv
import re
import nltk
import collections
import matplotlib.pyplot as plt
# https://github.com/e9t/nsmc

# x, y 데이터를 반환하는 함수를 만드세요

# 김윤 박사 cnn sentence 검색하면 깃헙 나옴
# 해당 사이트에서 clean_str 함수를 찾아서 우리 코드에 적용하세요

# 모든 문서의 토큰 갯수를 그래프로 그려보세요

def clean_str(string, TREC=False):
    """
    Tokenization/string cleaning for all datasets except for SST.
    Every dataset is lower cased except for TREC
    """
    string = re.sub(r"[^가-힣A-Za-z0-9(),!?\'\`]", " ", string)
    string = re.sub(r"\'s", " \'s", string)
    string = re.sub(r"\'ve", " \'ve", string)
    string = re.sub(r"n\'t", " n\'t", string)
    string = re.sub(r"\'re", " \'re", string)
    string = re.sub(r"\'d", " \'d", string)
    string = re.sub(r"\'ll", " \'ll", string)
    string = re.sub(r",", " , ", string)
    string = re.sub(r"!", " ! ", string)
    string = re.sub(r"\(", " \( ", string)
    string = re.sub(r"\)", " \) ", string)
    string = re.sub(r"\?", " \? ", string)
    string = re.sub(r"\s{2,}", " ", string)
    return string.strip() if TREC else string.strip().lower()


def get_data(file_path):
    # 8544678	뭐야 이 평점들은.... 나쁘진 않지만 10점 짜리는 더더욱 아니잖	0
    txt = open('data/ratings_' + file_path + '.txt', 'r', encoding='utf-8')
    txt.readline()
    # for t in text:
    #     print(t.strip().split('\t'))    # strip -> 개행문자 없앰, '\t' -> tab 없앰
    x, y = [], []
    for _, doc, label in csv.reader(txt, delimiter='\t'):   # 개행문자 기준으로 알아서 한줄 씩
        x.append(clean_str(doc).split())
        y.append(label)

    txt.close()

    # print(*x[:3], sep='\n')
    # return x, y   # 이대로 하면 메모리 터짐
    return x[:1000], y[:1000]


x_train, y_train = get_data('train')
x_test, y_test = get_data('test')

# train 데이터로 학습하고 test 데이터에 대해 정확도를 구하세요


# 1. 먼저 voc -> train data
def make_vocab(documents, vocab_size=2000):        # 사실 2천개는 너무 적음 만개는 해야함
    # words = nltk.corpus.movie_reviews.words()
    all_words = collections.Counter([w for doc in documents for w in doc])
    most_2000 = all_words.most_common(vocab_size)   # 최빈 값을 찾아줌

    return [w for w, _ in most_2000]    # 최빈 값은 사용하지 않을 것임


# 2. make feature 해주기
def make_feature(doc_words, vocab):  # filename = 리뷰룰 앍어오는 파일 이름
    # doc_words = nltk.corpus.movie_reviews.words(filename)
    feature, uniques = {}, set(doc_words)       # uniques <- 중복 x 검색 빠르게 할려고

    for v in vocab:
        feature['has_{}'.format(v)] = (v in uniques)  # uniques 안의 v를 찾아 true false 넣어줌
        # feature 수 2천개
    return feature


# 3. data 만들기
def make_feature_data(documents, lables, vocab):
    # return [(make_feature(name), lables) for name, gender in documents]    # 제일 어려운 부분
    # return [(make_feature(documents[i], vocab), lables[i]) for i in range(len(lables))]
    return [(make_feature(doc, vocab), label) for doc, label in zip(documents, lables)]


def show_freq_dist(documents):
    # freq = collections.Counter([t for token in documents for t in token])
    heights = sorted([len(doc) for doc in documents])
    # print(freq)

    plt.plot(range(len(heights)), heights)
    plt.show()


# 모든 문서의 토큰 길이를 25개로 맞춰 주세요
# 긴 것은 삭제 부족한 부분은 공백 토큰으로 채웁니다
def make_same_length(documents, max_len):
    new_docs = []
    for doc in documents:
        doc_len = len(doc)
        if doc_len < max_len:
            new_docs.append(doc + [''] * (max_len - doc_len))
        else:
            new_docs.append(doc[:max_len])

        assert (len(new_docs[-1]) == max_len)       # bool 값만 전달 가능 False -> 비정상 종료
    return new_docs

# print(x_train)
# show_freq_dist(x_train)     # 토큰 40개 정도면 대부분의 리뷰를 포함
# exit(-1)

vocab = make_vocab(x_train, vocab_size=2000)

x_train = make_same_length(x_train, 25)
x_test = make_same_length(x_test, 25)
#
# # 올바른 make_feature_data
train_set = make_feature_data(x_train, y_train, vocab)
test_set = make_feature_data(x_test, y_test, vocab)

clf = nltk.NaiveBayesClassifier.train(train_set)    # 엄청 빠름 왜? 정확도가 떨어지니깐
print('acc :', nltk.classify.accuracy(clf, test_set))   # 결과 정답빈도를 알려줌