本文主要讲述如何通过 word2vec 和 CNN/RNN
对动作序列建模,在最近的一个比赛中验证了这个思路,的确有一定效果,在二分类的准确率上能达到0.87.本文主要介绍这个方法的具体步骤,并以比赛和代码为例进行说明。
这里提到的比赛是目前正在进行的精品旅行服务成单预测 ,
该比赛就是要根据用户的个人信息,行为信息和订单信息来预测用户的下一个订单是否是精品服务。本文提到的方法是仅利用用户的行为信息,主要的思路是:将每个动作通过
word2vec 转化为 embedding 表示,然后将动作序列转化为 embedding
序列并作为 CNN/RNN 的输入。 下面依次介绍通过 word2vec 获得动作
embedding,将 embedding
作为CNN的输入和将embedding作为RNN的输入这三部分内容。
word2vec 获取动作 embedding
word2vec
是一个很著名的无监督算法了,这个算法最初在NLP领域提出,可以通过词语间的关系构建词向量,进而通过词向量可获取词语的语义信息,如词语意思相近度等。而将
word2vec 应用到动作序列中,主要是受到了知乎上这个答案 的启发。因为
word2vec
能够挖掘序列中各个元素之间的关系信息,这里如果将每个动作看成是一个单词,然后通过
word2vec 得出每个动作的 embedding 表示,那么这些 embedding
之间会存在一定的关联程度,再将动作序列转为 embedding 序列,作为 CNN 或
RNN 的输入便可挖掘整个序列的信息。
这里训练动作 embedding 的方法跟训练 word embedding
的方法一致,将每个户的每个动作看做一个单词、动作序列看做一篇文章即可。训练时采用的是
gensim
, 训练的代码很简单,embedding 的维度设为 300,
filter_texts
中每一行是一各用户的行为序列,行为之间用空格隔开。
1 2 3 from gensim.models import word2vecvector_length = 300 model = word2vec.Word2Vec(filter_texts, size = vector_length, window=2 , workers=4 )
由于动作类型只有9种(1~9),也就是共有 9 个不同的单词,因此可将这 9
个动作的 embedding 存在一个 np.ndarray
中,然后作为后面
CNN/RNN 前的 embedding layer 的初始化权重。注意这里还添加了一个动作 0
,原因是 CNN
的输入要求长度一致,因此对于长度达不到要求长度的序列,需要在前面补
0(补其他的不是已知的动作也可以)。代码如下
1 2 3 4 import numpy as npembedding_matrix = np.zeros((10 , vector_length)) for i in range (1 , 10 ): embedding_matrix[i] = model.wv[str (i)]
CNN 对动作序列建模
CNN 采用的模型是经典的 TextCNN, 模型结构如下图所示
TextCNN
这里通过 Keras 实现,具体代码如下
首先需要处理序列,使得所有序列长度一致,这里选择的长度是
50,具体代码如下,代码中的 x_original
是一个
list[list[int]]
类型,表示所有用户的所有动作序列,对于长度比 max_len
长的,从后往前截取50个最近时间的动作,而短的则在前面补0.
1 2 3 4 5 from keras.preprocessing import sequencemax_len = 50 x_train = sequence.pad_sequences(x_original, maxlen=max_len) y_train = np.array(y_original) print (x_train.shape, y_train.shape)
然后通过前面得到的 embedding_matrix
初始化 embedding
层
1 2 3 4 5 6 7 8 9 10 from keras.models import Sequential, Modelfrom keras.layers import Dense, Dropout, Flatten, Input, MaxPooling1D, Convolution1D, Embedding, BatchNormalization, Activationfrom keras.layers.merge import Concatenatefrom keras import optimizersembedding_layer = Embedding(input_dim=embedding_matrix.shape[0 ], output_dim = embedding_dim, weights=[embedding_matrix], input_length=max_len, trainable=True )
然后建立模型并训练, 这里用了四种不同步长的卷积核,分别是
2、3、5、8,比起原始的 TextCNN,
用了两层的卷积层(在这个任务上经过测试比一层的要好),
后面的全连接层也拓展到了三层,具体代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 NUM_EPOCHS = 100 BATCH_SIZE = 64 DROP_PORB = (0.5 , 0.8 ) NUM_FILTERS = (64 , 32 ) FILTER_SIZES = (2 , 3 , 5 , 8 ) HIDDEN_DIMS = 1024 FEATURE_DIMS = 256 ACTIVE_FUNC = 'relu' sequence_input = Input(shape=(max_len, ), dtype='int32' ) embedded_seq = embedding_layer(sequence_input) conv_blocks = [] for size in FILTER_SIZES: conv = Convolution1D(filters=NUM_FILTERS[0 ], kernel_size=size, padding="valid" , activation=ACTIVE_FUNC, strides=1 )(embedded_seq) conv = Convolution1D(filters=NUM_FILTERS[1 ], kernel_size=2 , padding="valid" , activation=ACTIVE_FUNC, strides=1 )(conv) conv = Flatten()(conv) conv_blocks.append(conv) model_tmp = Concatenate()(conv_blocks) if len (conv_blocks) > 1 else conv_blocks[0 ] model_tmp = Dropout(DROP_PORB[1 ])(model_tmp) model_tmp = Dense(HIDDEN_DIMS, activation=ACTIVE_FUNC)(model_tmp) model_tmp = Dropout(DROP_PORB[0 ])(model_tmp) model_tmp = Dense(FEATURE_DIMS, activation=ACTIVE_FUNC)(model_tmp) model_tmp = Dropout(DROP_PORB[0 ])(model_tmp) model_output = Dense(1 , activation="sigmoid" )(model_tmp) model = Model(sequence_input, model_output) opti = optimizers.SGD(lr = 0.01 , momentum=0.8 , decay=0.0001 ) model.compile (loss='binary_crossentropy' , optimizer = opti, metrics=['binary_accuracy' ]) model.fit(x_tra, y_tra, batch_size = BATCH_SIZE, validation_data = (x_val, y_val))
由于最后要求的是 auc 指标,但是 Keras 中并没有提供,而 accuracy 与
auc 还是存在一定差距的,因此可以在每个epoch后通过 sklearn
计算auc,具体代码如下
1 2 3 4 5 6 from sklearn import metricsfor i in range (NUM_EPOCHS): model.fit(x_tra, y_tra, batch_size = BATCH_SIZE, validation_data = (x_val, y_val)) y_pred = model.predict(x_val) val_auc = metrics.roc_auc_score(y_val, y_pred) print ('val_auc:{0:5f}' .format (val_auc))
这种方法最终的准确率约为 0.86,auc 约为0.84
RNN 对动作序列建模
通过 RNN 进行建模与 CNN 类似,不同的是 RNN
可接受不同长度的输入,但是根据这里 的说明,对于输入也需要
padding 的操作,只是RNN 会将其自动忽略。
因此,数据的预处理和构建 embedding 层的代码与 CNN
中基本一致,这里只给出建立模型的代码,模型比较简单,首先是将输入通过
embedding 层的映射后,作为以 LSMT/GRU 为基础单元构建的 RNN 的输入,
最后通过 sigmoid 进行分类,具体代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 model = Sequential() model.add(embedding_layer) model.add(Bidirectional(LSTM(256 , dropout=0.2 , recurrent_dropout=0.2 ))) model.add(Dense(1 , activation='sigmoid' )) opti = optimizers.SGD(lr = 0.01 , momentum=0.8 , decay=0.0001 ) model.compile (loss='binary_crossentropy' , optimizer='adam' , metrics=['accuracy' ])
通过 RNN 得出的最终效果比 CNN 要好一点,准确率约为 0.87,auc
约为0.85。但是训练起来非常慢,且参数非常的
tricky,需要精调,这里我没有细调参数,模型也没有搞得很复杂,应该还有提升空间。
综上,本文提供了一种对动态序列建模的思路:将动作序列通过
word2vec,得到每个动作的 embedding 表示,然后将动作序列转化为 embedding
序列并作为 CNN/RNN
的输入。希望能够起到抛砖引玉的作用,如果您有更好的想法,欢迎交流。