李宏毅作業4-語句情感判斷

李宏毅老師的作業四也同樣是不好對付的,這次我仍然用TensorFlow實現一遍,記錄踩坑過程。

迫於心疼我的筆電,這次作業在Kaggle編寫程式、而本次作業的數據集,Kaggle上也有現成的。

附上課程作業4的Kaggle地址:點擊前往

Introduction for HomeWork

給的數據文件夾總共有三個檔案
在這裏插入圖片描述

  • training_label.txt:有 label 的 training data(句子配上 0-negative or 1-postive,+++$+++ 是分隔符)

  • e.g., 1 +++$+++ are wtf … awww thanks !

  • training_nolabel.txt:沒有 label 的 training data(只有句子),用做 semi-supervised learning

  • e.g: hates being this burnt !! ouch

  • testing_data.txt:你要判斷 testing data 裏的句子是 0 or 1

    id,text

    0,my dog ate our dinner . no , seriously … he ate it .

    1,omg last day sooon n of primary noooooo x im gona be swimming out of school wif the amount of tears am gona cry

    2,stupid boys … they ’ re so … stupid !

Load Data and Word2Vector

拷貝ExampleCode的函數式:

def load_training_data(path='data/training_label.txt'):
    if 'training_label' in path:
        with open(path, 'r') as f:
            lines = f.readlines()
            lines = [line.strip('\n').split(' ') for line in lines]
        x = [line[2:] for line in lines]
        y = [line[0] for line in lines]
        return x, y
    else:
        with open(path, 'r') as f:
            lines = f.readlines()
            x = [line.strip('\n').split(' ') for line in lines]
        return x
def load_testing_data(path='data/testing_data'):
    with open(path, 'r') as f:
        lines = f.readlines()
        X = ["".join(line.strip('\n').split(",")[1:]).strip() for line in lines[1:]]
        X = [sen.split(' ') for sen in X]
    return X
print("loading training data ...")
train_x, train_y = load_training_data('/kaggle/input/ml2020spring-hw4/training_label.txt')
train_x_no_label = load_training_data('/kaggle/input/ml2020spring-hw4/training_nolabel.txt')
print("loading testing data ...")
test_x = load_testing_data('/kaggle/input/ml2020spring-hw4/testing_data.txt')
print("loading data end")

讀出完成數據之後,Print數據的一項、查看讀取出來的數據格式:
在這裏插入圖片描述
讀取數據的函數將一個sentence被轉化成了一個wordlist,但我們不能直接將wordlist輸入我們的RNN,我們需要將每一個word轉化成向量。

但爲了將其轉化成向量,我們需要一個word_vector_dict,這個字典記錄了每一個word和vector的映射關係,但我們並沒有這個字典,看題目的要求,我們需要手動訓練得到這個字典,想必training_nolabel.txt這個文件的意義就是如此了。

對於用什麼模型去訓練出這樣一個字典,在課程作業介紹視頻裏推薦我們使用gensim,在查閱了在Google上找的文檔後:點我前往,我實現的代碼如下:

from gensim.models import word2vec
def train_word2vec(x):
    model = word2vec.Word2Vec(x, size=250, window=5, min_count=5, workers=12, iter=10, sg=1)
    return model
print("trainning model ...")
# model = train_word2vec(train_x + test_x + train_x_no_label)
model = train_word2vec(train_x + test_x)
print("trainning model end ...")

因爲用的kaggle,有GPU了就要好好利用,爲了確保字典足夠大,我一開始把三個數據集的wordlist全都丟進去了。然而運行過程中,GPU的使用率爲0%,我意識到該模型的訓練並沒有使用到GPU、我的內心是崩潰的,於是爲了取巧(反正我又不要給老師打分),索性就把train_x_no_label捨棄掉了,這樣可以大大縮減我們的訓練時間,並且得到對於train和test來說質量都很好的字典,順帶一提訓練的過程大概需要十分鐘。爲了避免每次都要訓練該模型,我們可以將模型保存下來。

print("saving model ...")
model.save('./w2v_all.model'))
print("save model end")

當我們需要模型的時候,進行導入:

from gensim.models import Word2Vec
embedding = Word2Vec.load('./w2v_all.model')
embedding_dim = embedding.vector_size
print(embedding,embedding_dim)

但是我發現,這個embedding模型有個卵用,每個詞train出來的向量實在是太複雜了,也許是現在我還不理解gensim的強大,但好在他將字符按照出現的頻次排序了,我用很笨的方法自己生成了字典。

index_from = 3
word_index = {}
index_word = []
# generate word2index index2word and embedding_martix
for i, word in enumerate(embedding.wv.vocab):
    print('get words #{}'.format(i+1+index_from), end='\r')
    word_index[word] = len(word_index)+index_from+1

word_index['<PAD>'] = 0
word_index['<START>'] = 1
word_index['<UNK>'] = 2
word_index['<END>'] = 3
index_word = dict(
    [(value, key) for key, value in word_index.items()]
)

執行以上函數式、即可生成word_index、index_word這兩個映射關係,順便寫了個函數驗證:
在這裏插入圖片描述
完美!

然後我們需要將原來的wordlist轉換成vectorlist。

def transform_wordlist_ids(wordlist, word_index_dict):
    word_ids = []
    for word in wordlist:
        word_ids.append(word_index_dict.get(word, 2))
    return word_ids

def transform_sentense_list_id_list(sentence_list, word_index_dict):
    id_list = []
    for wordlist in sentence_list:
        id_list.append(transform_wordlist_ids(wordlist, word_index_dict))
    return id_list

train_data = transform_sentense_list_id_list(train_x, word_index)
train_labels = train_y
test_data = transform_sentense_list_id_list(test_x, word_index)

此時,train_data的每個單元就變成了整型數組了。

最後,我們要對其train的每一組向量,爲他們指定最大長度,使用keras內置的函數能很輕鬆的實現。

max_length = 50 # Max sentence length
train_data = keras.preprocessing.sequence.pad_sequences(train_data, 
                                                        maxlen=max_length, 
                                                        value=word_index['<PAD>'],
                                                        padding='post')
test_data = keras.preprocessing.sequence.pad_sequences(test_data, 
                                                        maxlen=max_length, 
                                                        value=word_index['<PAD>'],
                                                        padding='post')
print(len(train_data),len(train_labels))
print(len(test_data))
print(train_data[9])

在這裏插入圖片描述
那數據的讀取和向量化就到這裏,接下來我們開始訓練模型了。

FirstModel - Embedding

首先用最簡單的模型構建、Embedding+AveragePooling就完事了

batch_size = 128
model = keras.models.Sequential()
# define an input matrix is vocab_size * embedding_dim
# output Matrix Shape will be (batch_size, input_length, output_dim)
model.add(keras.layers.Embedding(input_dim=vocab_size, output_dim=embedding_dim,
                                input_length=max_length))
model.add(keras.layers.GlobalAveragePooling1D())
model.add(keras.layers.Dense(64, activation='relu'))
model.add(keras.layers.Dense(1, activation='sigmoid'))

model.summary()

model.compile(optimizer='adam',
             loss='binary_crossentropy',
              metrics=['accuracy']
             )

SecondModel - RNN

在Keras裏有簡單實現RNN的方法,在Embedding後增加一層SimpleRNN的layer。

batch_size = 128
model = keras.models.Sequential()
model.add(keras.layers.Embedding(input_dim=vocab_size, output_dim=embedding_dim,
                                input_length=max_length))

model.add(keras.layers.Bidirectional(keras.layers.SimpleRNN(units=64, return_sequences=False)))
model.add(keras.layers.Dense(64, activation='relu'))
model.add(keras.layers.Dense(1, activation='sigmoid'))

model.summary()

model.compile(optimizer='adam',
             loss='binary_crossentropy',
              metrics=['accuracy']
             )

但是僅增加一層SimpleRNN的效果不見好,因爲RNN需要雙向訓練,在keras裏也有簡單實現的方法,如上文。

LastModel - LSTM

而對於LSTM、實現的代碼與RNN幾乎相同。

batch_size = 128
model = keras.models.Sequential()
model.add(keras.layers.Embedding(input_dim=vocab_size, output_dim=embedding_dim,
                                input_length=max_length))

model.add(keras.layers.Bidirectional(keras.layers.LSTM(units=64, return_sequences=False)))
model.add(keras.layers.Dense(64, activation='relu'))
model.add(keras.layers.Dense(1, activation='sigmoid'))

model.summary()

model.compile(optimizer='adam',
             loss='binary_crossentropy',
              metrics=['accuracy']
             )

Summary

可能讀者會發現,文中沒有給出訓練結果。其實是因爲寫到一半睡覺去了,醒來發現文中訓練詞向量的文件不見了,這是Kaggle的第一次踩坑,所以就沒有再重新花時間訓練該模型。

但網絡的構建是正確的,希望嘗試本文做法的讀者能夠順利完成作業!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章