模型的构建
在构建模型之前,为了避免从0~1先在github找一个RNN的实现,具体可以看一下参考资料[1]中实现的垃圾邮件预测算法。 在垃圾邮件预测算法中,实现了一个简单的RNN模型,我们之后在这个模型的基础上做进一步的改进。
垃圾邮件预测算法
数据集
在处理正式了解代码之前,我们先来看一下数据情况。在垃圾邮件预测算法中使用的是UCI的SMS Spam Collection Dataset 数据集。这个数据集也能在Kaggle上找到,数据链接在参考资料中给出。
SMS垃圾邮件数据集是一个SMS垃圾邮件研究标注的SMS邮件集合。在这个数据集中一共包含了5574条垃圾邮件,这些垃圾邮件 被分为两种类型:ham(legitimate)和spam。
ham Go until jurong point, crazy.. Available only in bugis n great world la e buffet… Cine there got amore wat…
ham Ok lar… Joking wif u oni…
spam Free entry in 2 a wkly comp to win FA Cup final tkts 21st May 2005. Text FA to 87121 to receive entry question(std txt rate)T&C’s apply 08452810075over18’s
ham U dun say so early hor… U c already then say…
ham Nah I don’t think he goes to usf, he lives around here though
在数据集文件中,每一行包含一条邮件信息。每一行由两列组成,第一列包含邮件的标签,第二列包含邮件的原始文本信息。
数据处理
1. 数据加载和清洗
在数据的加载和清洗阶段,主要包含以下三个部分:
- 加载数据
- 将数据切分成
label
和message
两部分 - 去除掉
message
中的异常字符
text_data = []
with open(os.path.join(data_dir, data_file), 'r') as file_conn:
for row in file_conn:
text_data.append(row)
text_data = text_data[:-1]
text_data = [x.split('\t') for x in text_data if len(x) >= 1]
[text_data_target, text_data_train] = [list(x) for x in zip(*text_data)]
# Create a text cleaning function
def clean_text(text_string):
text_string = re.sub(r'([^\s\w]|_|[0-9])+', '', text_string)
text_string = " ".join(text_string.split())
text_string = text_string.lower()
return text_string
# Clean texts
text_data_train = [clean_text(x) for x in text_data_train]
2. 拆分训练集和测试集
在加载完成数据并去除特殊字符后,将文本数据处理成向量并且将转成正负样本。最后将得到的数据按照8:2拆分成训练 集和测试集。
# Change texts into numeric vectors
vocab_processor = tf.contrib.learn.preprocessing.VocabularyProcessor(max_sequence_length,
min_frequency=min_word_frequency)
text_processed = np.array(list(vocab_processor.fit_transform(text_data_train)))
# Shuffle and split data
text_processed = np.array(text_processed)
text_data_target = np.array([1 if x == 'ham' else 0 for x in text_data_target])
shuffled_ix = np.random.permutation(np.arange(len(text_data_target)))
x_shuffled = text_processed[shuffled_ix]
y_shuffled = text_data_target[shuffled_ix]
# Split train/test set
ix_cutoff = int(len(y_shuffled)*0.80)
x_train, x_test = x_shuffled[:ix_cutoff], x_shuffled[ix_cutoff:]
y_train, y_test = y_shuffled[:ix_cutoff], y_shuffled[ix_cutoff:]
vocab_size = len(vocab_processor.vocabulary_)
print("Vocabulary Size: {:d}".format(vocab_size))
print("80-20 Train Test split: {:d} -- {:d}".format(len(y_train), len(y_test)))
模型结构
在将数据集处理成向量类型并且拆分成训练数据和测试数据之后,构建一个RNN模型,其具体结构如下。
# Create placeholders
x_data = tf.placeholder(tf.int32, [None, max_sequence_length])
y_output = tf.placeholder(tf.int32, [None])
# Create embedding
embedding_mat = tf.Variable(tf.random_uniform([vocab_size, embedding_size], -1.0, 1.0))
embedding_output = tf.nn.embedding_lookup(embedding_mat, x_data)
# Define the RNN cell
# tensorflow change >= 1.0, rnn is put into tensorflow.contrib directory. Prior version not test.
if tf.__version__[0] >= '1':
cell = tf.contrib.rnn.BasicRNNCell(num_units=rnn_size)
else:
cell = tf.nn.rnn_cell.BasicRNNCell(num_units=rnn_size)
output, state = tf.nn.dynamic_rnn(cell, embedding_output, dtype=tf.float32)
output = tf.nn.dropout(output, dropout_keep_prob)
# Get output of RNN sequence
output = tf.transpose(output, [1, 0, 2])
last = tf.gather(output, int(output.get_shape()[0]) - 1)
weight = tf.Variable(tf.truncated_normal([rnn_size, 2], stddev=0.1))
bias = tf.Variable(tf.constant(0.1, shape=[2]))
logits_out = tf.matmul(last, weight) + bias
# Loss function
losses = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits_out, labels=y_output)
loss = tf.reduce_mean(losses)
accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(logits_out, 1), tf.cast(y_output, tf.int64)), tf.float32))
optimizer = tf.train.RMSPropOptimizer(learning_rate)
train_step = optimizer.minimize(loss)
init = tf.global_variables_initializer()
sess.run(init)
在上述代码中各个变量的含义:
- embedding_mat: 输入数据(vocabulary)的embedding矩阵,
- embedding_output: 神经网络第一层(embedding 层)的数据数据,对应为多个神经元的look up操作。
- cell: 定义了一个基本的RNN实现的实例。
- output: RNN在一个输入序列上输出的结果。
- state: RNN在一个输入序列上输出的state。
- weight: softmax层的权重
- bias: softmax层的bias
- logits_out: RNN输出对每一个类的概率。
- losses: 每一条输入数据的loss值
- loss: 整个batch的平均loss值
- accuracy: RNN模型预测结果和真实label相比的准确率。
- optimizer: RNN上定义的优化器。
除了有关变量的含义之外,上述代码中需要重点注意的几个点包括以下内容:
1. RNNCell是什么?
RNNCell是一个抽象类。在RNNCell的子类中实现了一些常见的RNN结构。在代码中实例化了个BasicRNNCell。 这个BasicRNNCell实现了一个基本的RNN结构。
在模型训练的过程中,将一个embedding序列输入到RNNCell当中去,就可以完成这个Sequence的训练。
RNNCell的实现可以是基本的RNN,也可以是一个LSTM结构,当然也可以是GRU,此外我们在开发的时候可以自定义一个 符合自己需求的RNNCell实现子类。
2. dynamic_rnn是什么?
调用dynamic_rnn表示在训练过程中对一个sequence做动态展开,也就是每一个sequence 不同的time stamp之间 是通过一个while循环在时间维度上动态完成。
与dynamic_rnn相对应的是静态时间展开,也就是static_rnn。使用静态时间展开,在模型训练之前会将RNN序列 的动态结构展开成图结构。使用static_rnn的时候,需要将sequence length保持定长。
两者对比的区别如下,前者为动态展开,后者为静态展开。
3. 为什么使用sparse_softmax_cross_entropy_with_logits
计算损失?
如果转成softmax_cross_entropy_with_logits
出现如下错误,
tensorflow.python.framework.errors_impl.InvalidArgumentError: logits and labels must be broadcastable: logits_size=[250,2] labels_size=[1,250]
在使用softmax_cross_entropy_with_logits
的时候计算出来的logit和label的维度不对等。因为在label中没有转成one hot的方式。
4. 为什么使用RMSPropOptimizer作为优化器?
因为RMSPropOptimizer对RNN效果很好。
模型训练
在模型训练的过程中对每一个epoch,首先对训练数据做一下shuffle。在对数据完成shuffle的基础上,分batch进行训练。 在训练阶段dropout的概率为0.5,测试阶段dropout的概率为1.0。
for epoch in range(epochs):
# Shuffle training data
shuffled_ix = np.random.permutation(np.arange(len(x_train)))
x_train = x_train[shuffled_ix]
y_train = y_train[shuffled_ix]
num_batches = int(len(x_train)/batch_size) + 1
# TO DO CALCULATE GENERATIONS ExACTLY
for i in range(num_batches):
# Select train data
min_ix = i * batch_size
max_ix = np.min([len(x_train), ((i+1) * batch_size)])
x_train_batch = x_train[min_ix:max_ix]
y_train_batch = y_train[min_ix:max_ix]
# Run train step
train_dict = {x_data: x_train_batch, y_output: y_train_batch, dropout_keep_prob:0.5}
sess.run(train_step, feed_dict=train_dict)
# Run loss and accuracy for training
temp_train_loss, temp_train_acc = sess.run([loss, accuracy], feed_dict=train_dict)
train_loss.append(temp_train_loss)
train_accuracy.append(temp_train_acc)
# Run Eval Step
test_dict = {x_data: x_test, y_output: y_test, dropout_keep_prob:1.0}
temp_test_loss, temp_test_acc = sess.run([loss, accuracy], feed_dict=test_dict)
test_loss.append(temp_test_loss)
test_accuracy.append(temp_test_acc)
print('Epoch: {}, Test Loss: {:.2}, Test Acc: {:.2}'.format(epoch+1, temp_test_loss, temp_test_acc))
推荐数据集上构建RNN模型
基于YouTube DNN对多分类问题处理的思想,这里我们套用RNN的模型结构,将原来垃圾邮件分类中的二分类问题,处理成为 一个多分类问题。由于分类数过多,为了避免softmax层过多的数据计算,这里我们依旧采用sampling-base approach方法 对负样本数据进行采样。
构建BaseLine模型
在最基本的模型结构中,不引入其他特征,只采用用户行为序列原始数据作为输入序列。
构建离线评估指标
加入其他特征
[1] Implementing RNN for Spam Prediction