李宏毅老師的作業四也同樣是不好對付的,這次我仍然用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的第一次踩坑,所以就沒有再重新花時間訓練該模型。
但網絡的構建是正確的,希望嘗試本文做法的讀者能夠順利完成作業!