MLP:Multilayer perceptron,多层感知机的缩写

Embedding+MLP 模型的结构

Deep Crossing 从下到上可以分为 5 层,分别是 Feature 层、Embedding 层、Stacking 层、MLP 层和 Scoring 层。

我们先来看 Feature 层。Feature 层也叫做输入特征层,它处于 Deep Crossing 的最底部,作为整个模型的输入

Embedding 层就是为了把稀疏的 One-hot 向量转换成稠密的 Embedding 向量而设置的,我们需要注意的是,Embedding 层并不是全部连接起来的,而是每一个特征对应一个 Embedding 层,不同 Embedding 层之间互不干涉。

Embeding 层的结构就是 Word2vec 模型中从输入神经元到隐层神经元的部分(如图 2 红框内的部分)。参照下面的示意图,我们可以看到,这部分就是一个从输入层到隐层之间的全连接网络。

Stacking 层中文名是堆叠层,我们也经常叫它连接(Concatenate)层。它的作用比较简单,就是把不同的 Embedding 特征和数值型特征拼接在一起,形成新的包含全部特征的特征向量。

MLP 层的特点是全连接,就是不同层的神经元两两之间都有连接。就像图 3 中的两层神经网络一样,它们两两连接,只是连接的权重会在梯度反向传播的学习过程中发生改变。

最后是 Scoring 层,它也被称为输出层。虽然深度学习模型的结构可以非常复杂,但最终我们要预测的目标就是一个分类的概率。如果是点击率预估,就是一个二分类问题,那我们就可以采用逻辑回归作为输出层神经元,而如果是类似图像分类这样的多分类问题,我们往往在输出层采用 softmax 这样的多分类模型。


Embedding+MLP 的五层结构。它的结构重点用一句话总结就是,对于类别特征,先利用 Embedding 层进行特征稠密化,再利用 Stacking 层连接其他特征,输入 MLP 的多层结构,最后用 Scoring 层预估结果。


特征选择和模型设计

Embedding+MLP 模型的 TensorFlow 实现

我们先来看第一步,导入 TensorFlow 包。

import tensorflow as tf

TRAIN_DATA_URL = "file:///Users/zhewang/Workspace/SparrowRecSys/src/main/resources/webroot/sampledata/modelSamples.csv"

samples_file_path = tf.keras.utils.get_file("modelSamples.csv", TRAIN_DATA_URL)

第二步是载入训练数据

def get_dataset(file_path):

dataset = tf.data.experimental.make_csv_dataset(

file_path,

batch_size=12,

label_name='label',

na_value="?",

num_epochs=1,

ignore_errors=True)

return dataset

# sample dataset size 110830/12(batch_size) = 9235

raw_samples_data = get_dataset(samples_file_path)

test_dataset = raw_samples_data.take(1000)

train_dataset = raw_samples_data.skip(1000)

第三步是载入类别型特征

我们用到的类别型特征主要有这三类,分别是 genre、userId 和 movieId。在载入 genre 类特征时,我们采用了 tf.feature_column.categorical_column_with_vocabulary_list 方法把字符串型的特征转换成了 One-hot 特征。在这个转换过程中我们需要用到一个词表,你可以看到我在开头就定义好了包含所有 genre 类别的词表 genre_vocab。在转换 userId 和 movieId 特征时,我们又使用了 tf.feature_column.categorical_column_with_identity 方法把 ID 转换成 One-hot 特征,这个方法不用词表,它会直接把 ID 值对应的那个维度置为 1。比如,我们输入这个方法的 movieId 是 340,总的 movie 数量是 1001,使用这个方法,就会把这个 1001 维的 One-hot movieId 向量的第 340 维置为 1,剩余的维度都为 0。为了把稀疏的 One-hot 特征转换成稠密的 Embedding 向量,我们还需要在 One-hot 特征外包裹一层 Embedding 层,你可以看到 tf.feature_column.embedding_column(movie_col, 10) 方法完成了这样的操作,它在把 movie one-hot 向量映射到了一个 10 维的 Embedding 层上。

genre_vocab = ['Film-Noir', 'Action', 'Adventure', 'Horror', 'Romance', 'War', 'Comedy', 'Western', 'Documentary',

'Sci-Fi', 'Drama', 'Thriller',

'Crime', 'Fantasy', 'Animation', 'IMAX', 'Mystery', 'Children', 'Musical']

GENRE_FEATURES = {

'userGenre1': genre_vocab,

'userGenre2': genre_vocab,

'userGenre3': genre_vocab,

'userGenre4': genre_vocab,

'userGenre5': genre_vocab,

'movieGenre1': genre_vocab,

'movieGenre2': genre_vocab,

'movieGenre3': genre_vocab

}

categorical_columns = []

for feature, vocab in GENRE_FEATURES.items():

cat_col = tf.feature_column.categorical_column_with_vocabulary_list(

key=feature, vocabulary_list=vocab)

emb_col = tf.feature_column.embedding_column(cat_col, 10)

categorical_columns.append(emb_col)

movie_col = tf.feature_column.categorical_column_with_identity(key='movieId', num_buckets=1001)

movie_emb_col = tf.feature_column.embedding_column(movie_col, 10)

categorical_columns.append(movie_emb_col)

user_col = tf.feature_column.categorical_column_with_identity(key='userId', num_buckets=30001)

user_emb_col = tf.feature_column.embedding_column(user_col, 10)

categorical_columns.append(user_emb_c

第四步是数值型特征的处理

这一步非常简单,我们直接把特征值输入到 MLP 内,然后把特征逐个声明为 tf.feature_column.numeric_column 就可以了,不需要经过其他的特殊处理。

numerical_columns = [tf.feature_column.numeric_column('releaseYear'),

tf.feature_column.numeric_column('movieRatingCount'),

tf.feature_column.numeric_column('movieAvgRating'),

tf.feature_column.numeric_column('movieRatingStddev'),

tf.feature_column.numeric_column('userRatingCount'),

tf.feature_column.numeric_column('userAvgRating'),

tf.feature_column.numeric_column('userRatingStddev')]

第五步是定义模型结构

我们直接利用 DenseFeatures 把类别型 Embedding 特征和数值型特征连接在一起形成稠密特征向量,

preprocessing_layer = tf.keras.layers.DenseFeatures(numerical_columns + categorical_columns)

model = tf.keras.Sequential([

preprocessing_layer,

tf.keras.layers.Dense(128, activation='relu'),

tf.keras.layers.Dense(128, activation='relu'),

tf.keras.layers.Dense(1, activation='sigmoid'),

])

第六步是定义模型训练相关的参数

在这一步中,我们需要设置模型的损失函数,梯度反向传播的优化方法,以及模型评估所用的指标。关于损失函数,我们使用的是二分类问题最常用的二分类交叉熵,优化方法使用的是深度学习中很流行的 adam,最后是评估指标,使用了准确度 accuracy 作为模型评估的指标。

model.compile(

loss='binary_crossentropy',

optimizer='adam',

metrics=['accuracy'])

第七步是模型的训练和评估。

TensorFlow 模型的训练过程和 Spark MLlib 一样,都是调用 fit 函数,然后使用 evaluate 函数在测试集上进行评估。不过,这里我们要注意一个参数 epochs,它代表了模型训练的轮数,一轮代表着使用所有训练数据训练一遍,epochs=10 代表着训练 10 遍。

model.fit(train_dataset, epochs=10)

test_loss, test_accuracy = model.evaluate(test_dataset)

print('\n\nTest Loss {}, Test Accuracy {}'.format(test_loss, test_accuracy)

Epoch 1/10

8236/8236 [==============================] - 20s 2ms/step - loss: 2.7379 - accuracy: 0.5815

Epoch 2/10

8236/8236 [==============================] - 21s 3ms/step - loss: 0.6397 - accuracy: 0.6659

Epoch 3/10

8236/8236 [==============================] - 21s 3ms/step - loss: 0.5550 - accuracy: 0.7179

Epoch 4/10

8236/8236 [==============================] - 21s 2ms/step - loss: 0.5209 - accuracy: 0.7431

Epoch 5/10

8236/8236 [==============================] - 21s 2ms/step - loss: 0.5010 - accuracy: 0.7564

Epoch 6/10

8236/8236 [==============================] - 20s 2ms/step - loss: 0.4866 - accuracy: 0.7641

Epoch 7/10

8236/8236 [==============================] - 20s 2ms/step - loss: 0.4770 - accuracy: 0.7702

Epoch 8/10

8236/8236 [==============================] - 21s 2ms/step - loss: 0.4688 - accuracy: 0.7745

Epoch 9/10

8236/8236 [==============================] - 20s 2ms/step - loss: 0.4633 - accuracy: 0.7779

Epoch 10/10

8236/8236 [==============================] - 20s 2ms/step - loss: 0.4580 - accuracy: 0.7800

1000/1000 [==============================] - 1s 1ms/step - loss: 0.5037 - accuracy: 0.7473

Test Loss 0.5036991238594055, Test Accuracy 0.747250020503997