李宏毅老师的作业四也同样是不好对付的,这次我仍然用 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 的第一次踩坑,所以就没有再重新花时间训练该模型。
但网络的构建是正确的,希望尝试本文做法的读者能够顺利完成作业!
Comments