인공지능/RNN

pop corn DBset으로 Baseline, rnn, cnn, cnn-tf, word2vec, nltk 구성하기

쿠와와 2020. 12. 15. 22:58

이번 코드는 상당히 여러가지 요소들도 많고 기법들도 많이 들어가 있으니 주석을 잘 보시길 바랍니다. 

# Day_29_01_popcorn.py
import tensorflow as tf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import gensim
import nltk
from sklearn import model_selection, feature_extraction, linear_model
import re


# popcorn_models
# labeled = train set
# sample = 데이터를 만들어야하는 형태
# testData = test set (id와 리뷰만 있음)
# unlabeld = train set( 분류 안되있는거 사용안 할 것임)

# 팝콘에 5가지 정도의 모델을 만들어서 보여줄 것임
# RNN -> 최종적으로는 CNN으로 만들 것임


# preprocessing
def tokenizing_and_padding(x, vocab_size, seq_len):
    tokenizer = tf.keras.preprocessing.text.Tokenizer(num_words=vocab_size)
    tokenizer.fit_on_texts(x)       # x는 문자열이 들어있는 1차원 배열

    # print(tokenizer.num_words)          # 2000
    # print(len(tokenizer.index_word))    # 88582 -> 너무 많아서 one_hot 으로 만들기 힘들 것이다.
    # print(x[1])  # The Classic War ....

    x = tokenizer.texts_to_sequences(x)     # word를 숫자로 변환
    # sentense 분포 확인
    # 250는 80%, 500개는 90% 리뷰를 포함
    # freqs = sorted([len(t) for t in x])
    # plt.plot(freqs)
    # plt.show()

    x = tf.keras.preprocessing.sequence.pad_sequences(x, maxlen=seq_len)

    return x, tokenizer


def make_submission(ids, preds, file_path):
    f = open(file_path, 'w', encoding='utf-8')

    f.write('"id","sentiment"\n')
    for i, p in zip(ids, preds):
        f.write('"{}",{}\n'.format(i, p))

    f.close()


# 사용 모델: baseline, rnn, cnn, cnn-tf.data
def make_submission_for_deep(ids, x_test, model, tokenizer, seq_len, file_path):
    x_test = tokenizer.texts_to_sequences(x_test)
    x_test = tf.keras.preprocessing.sequence.pad_sequences(x_test, maxlen=seq_len)

    preds = model.predict(x_test)
    preds_arg = preds.reshape(-1)
    preds_bool = np.int32(preds_arg > 0.5)
    # print(preds_bool[:10])          # [1 0 1 1 1 1 1 0 0 0] sub mission 을 보면 ㄷ그렇게 나와있으니깐

    make_submission(ids, preds_bool, file_path)


def make_submission_for_word2vec(ids, x_test, model, word2vec, n_features, idx2word, file_path):
    x_test = [s.lower().split() for s in x_test]
    features = [make_features_for_word2vec(tokens, word2vec, n_features, idx2word) for tokens in x_test]
    x_test = np.vstack(features)

    preds = model.predict(x_test)
    make_submission(ids, preds, file_path)


# 사용 모델 : word2vec, word2vec_nltk
# tokens: [the, in, happy, in, disney]
def make_features_for_word2vec(tokens, word2vec, n_features, idx2word):
    # ex
    # 리뷰 : [the, in, happy, in]
    # the   : 1 0 0 0 0 0
    # in    : 0 0 1 0 0 0
    # happy : 0 0 0 0 1 0
    # the   : 0 0 1 0 0 0
    # +     --------------
    #       : 1 0 2 0 1 0  / 4 할 것임  -> 누적 결과 값이 다 다를 것임 + 형평성에 맞게 다 나눠줄 것임
    binds, n_words = np.zeros(n_features), 0    # 1차원에 6개의 0이 들어가있음

    # 못들어가는 경우도 있음
    for w in tokens:
        if w in idx2word:
            binds += word2vec.wv[w]
            n_words += 1

    # 리뷰 하나의 대해서 안에 있는 word의 갯수만큼 배열을 더해주고 나눔
    return binds if n_words == 0 else binds / n_words


def model_baseline(x, y, ids, x_test):
    vocab_size, seq_len = 2000, 200
    x, tokenizer = tokenizing_and_padding(x, vocab_size, seq_len)

    data = model_selection.train_test_split(x, y, train_size=0.8, shuffle=False)
    x_train, x_valid, y_train, y_valid = data

    # ------------------------------------------- #

    model = tf.keras.Sequential([
        tf.keras.layers.Input(shape=[seq_len]),
        tf.keras.layers.Embedding(vocab_size, 100),  # 입력(2차원), 출력(3차원) 피처 추출하는 것
        tf.keras.layers.LSTM(128, return_sequences=False),
        tf.keras.layers.Dense(1, activation='sigmoid')      # 감성분석 true or false -> 1
    ])

    model.compile(optimizer=tf.keras.optimizers.Adam(lr=0.01),
                  loss=tf.keras.losses.binary_crossentropy,  # 숫자 하나의 배열 사용하니깐
                  metrics=['acc'])
    # 훈련
    # step_per_epoch 한번 에 몇번 돌아가는지 알려줘야함
    model.fit(x_train, y_train, epochs=10, batch_size=128, verbose=2,
              validation_data=(x_valid, y_valid))
    # --------------------------------------- 3

    make_submission_for_deep(ids, x_test, model, tokenizer, seq_len, 'popcorn_models/baseline.csv')


# tf-idf : Term Frequency-Inverse Document Frequency
# 딥러닝 이전에 머신러닝에서 해볼 것임 과거의 방법들 (성능이 의외로 잘 나옴)
def model_tf_idf(x, y, ids, x_test):
    # 3가지 영역으로 나눌 수 있음
    # ------------------------------------------- #
    # x, y 데이터를 만드는 전처리 부분
    vocab_size, seq_len = 2000, 200
    x, tokenizer = tokenizing_and_padding(x, vocab_size, seq_len)
    print(x.shape)      # (1000, 200)
    # 싸이킷런에서 보통 문자열을 받기 때문에 다시 변환해줘야함 ㅂㄷㅂㄷ
    x = tokenizer.sequences_to_texts(x)     # 쓰기 싫지만 꼭 써야함 ( 숫자를 원래의 토큰으로 변환해줌 )

    # feature을 추출해주겠다는 것 ( 뽑하내는 방법 Text 중에 여러가지 있음 )
    tfidf = feature_extraction.text.TfidfVectorizer(
        min_df=0.0,             #  머신러닝이여서 별로 비중있게 다루지 않음
        analyzer='word',        # 단어 기반으로 만들겠다.
        sublinear_tf=True,      #
        ngram_range=(1, 3),     # 요 안에서 범위를 찾겠다. 동시에 발생? or not 판단
        max_features=5000       # 단어 하나를 숫자 몇개로 표현하겠는가??
    )
    x = tfidf.fit_transform(x)
    print(x.shape)  # (1000, 5000)

    data = model_selection.train_test_split(x, y, train_size=0.8, shuffle=False)
    x_train, x_valid, y_train, y_valid = data

    # ------------------------------------------- #

    # 딥러닝보다는 약함
    linear_regression = linear_model.LogisticRegression(class_weight='balanced')    # 사이킷 런에서 제공해주는 것
    linear_regression.fit(x_train, y_train)
    print('acc :', linear_regression.score(x_valid, y_valid))   # score로 결과를 알 수 있음

    # --------------------------------------- 3
    # 결과를 예측해서 submission 하는 과정

    # tf-ids 알고리즘을 적용한 모델에 대해 서브미션을 만들것임.
    # 예측할 때는 predict 함수를 사용할 것임.
    x_test = tokenizer.texts_to_sequences(x_test)
    x_test = tf.keras.preprocessing.sequence.pad_sequences(x_test, maxlen=seq_len)

    x_test = tokenizer.sequences_to_texts(x_test)
    x_test = tfidf.fit_transform(x_test)

    preds = linear_regression.predict(x_test)
    # print(preds)        # [1 1 1 ... 0 1 0]

    make_submission(ids, preds, 'popcorn_models/tfidf.csv')

    # make_submission_for_deep(ids, x_test, model, tokenizer, seq_len, 'popcorn_models/baseline.csv')


def model_word2vec(x, y, ids, x_test):
    # 3가지 영역으로 나눌 수 있음
    # ------------------------------------------- #
    # x, y 데이터를 만드는 전처리 부분
    x = [s.lower().split() for s in x]

    # gensim에 들어가면 우리가 썼던 거 있음 그걸 이용하면 됨
    n_features = 100
    word2vec = gensim.models.Word2Vec(
        x, workers=4,       # 얼마나 빨리 처리할 것인지
        size=n_features,    # feature size
        min_count=40, window=10, sample=0.001
    )

    idx2word = set(word2vec.wv.index2word)  # 계산이 너무 길어지기 때문에 만들어 줘야함
    # make_features_for_word2vec 함수 안을 잘 보자
    features = [make_features_for_word2vec(tokens, word2vec, n_features, idx2word) for tokens in x]

    # x = np.reshape(features, newshape=[-1, n_features])  # (1000, 100)
    # x = np.vstack(features)         # (1000, 100)
    print(x.shape)

    data = model_selection.train_test_split(x, y, train_size=0.8, shuffle=False)
    x_train, x_valid, y_train, y_valid = data

    # ------------------------------------------- #

    # 딥러닝보다는 약함
    linear_regression = linear_model.LogisticRegression(class_weight='balanced')  # 사이킷 런에서 제공해주는 것
    linear_regression.fit(x_train, y_train)
    print('acc :', linear_regression.score(x_valid, y_valid))  # score로 결과를 알 수 있음

    # --------------------------------------- 3
    # 결과를 예측해서 submission 하는 과정

    make_submission_for_word2vec(
        ids, x_test, linear_regression, word2vec, n_features, idx2word,
        'popcorn_models/word2vec.csv'
    )


def model_word2vec_nltk(x, y, ids, x_test):
    # x = [s.lower().split() for s in x]

    # 위에 코드보다 조금 더 잘 변환한 것
    tokenizer = nltk.RegexpTokenizer(r'\w+')    # 단어만을 필터링 하겠다. 한글자 이상
    sent_tokens = [tokenizer.tokenize(s.lower()) for s in x]
    # print(sent_tokens[0])       # ['with', 'all', 'this', 'stuff', ...

    # 어간 추출 해줌
    st = nltk.stem.PorterStemmer()

    sent_tokens = [[st.stem(w) for w in s] for s in sent_tokens]
    # print(sent_tokens[:3])

    stop_words = nltk.corpus.stopwords.words('english')
    # 불용어를 제외한 sentence
    sent_stops = [[w for w in s if w not in stop_words] for s in sent_tokens]
    x = sent_stops

    # ------------------------------------------------------- #
    # 아래 코드는 앞에 나온 함수와 완벽하게 동일함
    n_features = 100
    word2vec = gensim.models.Word2Vec(x, workers=4, size=n_features, min_count=40, window=10, sample=0.001)

    idx2word = set(word2vec.wv.index2word)
    features = [make_features_for_word2vec(tokens, word2vec, n_features, idx2word) for tokens in x]

    x = np.vstack(features)         # (1000, 100)

    data = model_selection.train_test_split(x, y, train_size=0.8, shuffle=False)
    x_train, x_valid, y_train, y_valid = data

    # ------------------------------------------- #

    # 딥러닝보다는 약함
    linear_regression = linear_model.LogisticRegression(class_weight='balanced')  # 사이킷 런에서 제공해주는 것
    linear_regression.fit(x_train, y_train)
    print('acc :', linear_regression.score(x_valid, y_valid))  # score로 결과를 알 수 있음

    # --------------------------------------- 3
    # 결과를 예측해서 submission 하는 과정

    make_submission_for_word2vec(
        ids, x_test, linear_regression, word2vec, n_features, idx2word,
        'popcorn_models/word2vec_nltk.csv'
    )


# baseline 모델 확장해서 만들었음
def model_rnn(x, y, ids, x_test):
    vocab_size, seq_len = 2000, 200
    x, tokenizer = tokenizing_and_padding(x, vocab_size, seq_len)

    data = model_selection.train_test_split(x, y, train_size=0.8, shuffle=False)
    x_train, x_valid, y_train, y_valid = data

    # ------------------------------------- #

    model = tf.keras.Sequential()
    model.add(tf.keras.layers.Input(shape=[seq_len]))
    model.add(tf.keras.layers.Embedding(vocab_size, 100))
    # model.add(tf.keras.layers.LSTM(64, return_sequences=True))    # layer 추가
    # model.add(tf.keras.layers.LSTM(64, return_sequences=False))
    cells = [tf.keras.layers.LSTMCell(64) for _ in range(2)]    # 위의 코드를 대신 할 수 있음 여러개 만들 수 있음
    multi = tf.keras.layers.StackedRNNCells(cells)              # 2개를 cell 에 추가해서 추가해준다.
    model.add(tf.keras.layers.RNN(multi))                       # layer 추가
    model.add(tf.keras.layers.Dense(64, activation='relu'))         # dense 추가
    model.add(tf.keras.layers.Dense(1, activation='sigmoid'))

    model.compile(optimizer=tf.keras.optimizers.Adam(lr=0.01),
                  loss=tf.keras.losses.binary_crossentropy,
                  metrics=['acc'])

    model.fit(x_train, y_train, epochs=10, batch_size=128, verbose=1,
              validation_data=(x_valid, y_valid))

    # -----------------------------------------#

    make_submission_for_deep(ids, x_test, model, tokenizer, seq_len, 'popcorn_models/rnn.csv')


# conv1d 사용
def model_cnn(x, y, ids, x_test):
    vocab_size, seq_len = 2000, 200
    x, tokenizer = tokenizing_and_padding(x, vocab_size, seq_len)

    data = model_selection.train_test_split(x, y, train_size=0.8, shuffle=False)
    x_train, x_valid, y_train, y_valid = data

    # ------------------------------------- #

    input = tf.keras.layers.Input(shape=[seq_len])

    embed = tf.keras.layers.Embedding(vocab_size, 100)(input)
    embed = tf.keras.layers.Dropout(0.5)(embed)             # 전체에 대해서 1/2

    # 필터크기가 달라짐에 따라 삐저나가는 정도가 달라짐
    conv1 = tf.keras.layers.Conv1D(128, 3, activation='relu')(embed)
    conv1 = tf.keras.layers.GlobalAvgPool1D()(conv1)        # 3차원 -> 2차원

    conv2 = tf.keras.layers.Conv1D(128, 4, activation='relu')(embed)
    conv2 = tf.keras.layers.GlobalAvgPool1D()(conv2)         # 3차원 -> 2차원

    conv3 = tf.keras.layers.Conv1D(128, 5, activation='relu')(embed)
    conv3 = tf.keras.layers.GlobalAvgPool1D()(conv3)         # 3차원 -> 2차원

    concat = tf.keras.layers.concatenate([conv1, conv2, conv3])     # 354

    full1 = tf.keras.layers.Dense(256, activation='relu')(concat)
    full1 = tf.keras.layers.Dropout(0.5)(full1)

    full2 = tf.keras.layers.Dense(1, activation='sigmoid')(full1)        # 0과 1 사이의 값으로 변환해줌

    model = tf.keras.Model(input, full2)
    model.summary()

    model.compile(optimizer=tf.keras.optimizers.Adam(lr=0.01),
                  loss=tf.keras.losses.binary_crossentropy,
                  metrics=['acc'])

    model.fit(x_train, y_train, epochs=10, batch_size=128, verbose=1,
              validation_data=(x_valid, y_valid))

    # -----------------------------------------#

    make_submission_for_deep(ids, x_test, model, tokenizer, seq_len, 'popcorn_models/rnn.csv')


# ---------------------------------------------------------------------- #
popcorn = pd.read_csv("popcorn/labeledTrainData.tsv",
                      delimiter='\t',         # tab 없애줘야함
                      index_col=0,)
# print(popcorn)

x = popcorn.review.values
y = popcorn.sentiment.values.reshape(-1, 1)     # 2차원으로 바꿔줄 것임
# print(x.dtype, y.dtype)     # object int64

n_samples = 1000
# 껏다 켰다 해줘야함
# x, y = x[:n_samples], y[:n_samples]     # 1000개만 쓰겠다.

test_set = pd.read_csv("popcorn/testData.tsv",
                       delimiter='\t',         # tab 없애줘야함
                       index_col=0,)
ids = test_set.index.values                     # numpy로 바꿔줬음
x_test = test_set.review.values


# model_baseline(x, y, ids, x_test)
# model_tf_idf(x, y, ids, x_test)
# model_word2vec(x, y, ids, x_test)
# model_word2vec_nltk(x, y, ids, x_test)
# model_rnn(x, y, ids, x_test)
model_cnn(x, y, ids, x_test)

# baseline: 0.8634
# tf-idf  : 0.8742
# word2vec: 0.8244
# nltk    : 0.8562
# rnn     : 0.8794
# cnn     : 0.8662