基于推荐数据构建RNN模型

Reading time ~2 minutes

模型的构建

在构建模型之前,为了避免从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. 数据加载和清洗

在数据的加载和清洗阶段,主要包含以下三个部分:

  • 加载数据
  • 将数据切分成labelmessage两部分
  • 去除掉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

[2] SMS Spam Collection Dataset

[3] 优化方法与TensorFlow程序用例

腾讯广告MVKE实现多目标的用户画像建模

以House Price为例快速构建一个Base Line模型 Continue reading

召回部分代码重构

Published on September 29, 2022

Mind多兴趣召回

Published on September 26, 2022