0x00 сводка
DIEN — это аббревиатура Ali Deep Interest Evolution Network.
Ранее мы интерпретировали исходный код DIEN, который основан наGitHub.com/Mouna99/Вниз…реализация в.
Позже, продолжая изучать DSIN, я обнаружил, что в коде DSINGitHub.com/Шен Вейхен…
Так что читайте и систематизируйте, поэтому есть эта статья.
0x01 фон
1.1 Эволюция кода
Всем известно, что эволюция модели Али: DIN, DIEN, DSIN, ...
Тогда есть три версии кода.
Автор первой версии прямо заявил, что эффективность нехорошая, и рекомендуется вторая версияGitHub.com/Mouna99/Вниз…. Эта версия кода представляет собой чистый тензорный код, надежный и легко читаемый.
Третья версия основана на Keras и deepctr.По сравнению с предыдущей версией, она модернизирована от партизанской до регулярной армии, с различными высокоуровневыми и подпрограммами.
1.2 Deepctr
Причина, по которой она стала регулярной армией, заключается в том, что deepctr играет очень важную роль, о чем будет рассказано ниже.
DeepCtr — это простая структура модели CTR, которая объединяет все популярные модели глубокого обучения и подходит для людей, изучающих модели рекомендательных систем.
Этот проект в основном реализует некоторые текущие алгоритмы прогнозирования CTR, основанные на глубоком обучении, такие как PNN, WDL, DeepFM, MLR, DeepCross, AFM, NFM, DIN, DIEN, xDeepFM, AutoInt и т. д., и обеспечивает согласованный внешний вызов интерфейса.
1.2.1 Единая перспектива
Появление Deepctr не только снижает сложность начала работы с моделью прогнозирования рейтинга кликов по рекламе и облегчает сравнение моделей, но также дает нам возможность научиться строить модель из этих превосходных исходных кодов.
Дизайн DeepCTR предназначен в основном для тех студентов, которые интересуются алгоритмами глубокого обучения и прогнозирования CTR, поэтому они могут использовать этот пакет:
- Единый вид моделей
- Быстро проводить простые сравнительные эксперименты
- Быстро создавайте новые модели, используя существующие компоненты
1.2.2 Модульность
DeepCTR абстрагирует и обобщает структуру существующей модели прогнозирования CTR, основанной на глубоком обучении, и принимает идею модульности в процессе проектирования.Каждый модуль сам по себе имеет высокую степень повторного использования, и каждый модуль независим друг от друга. Модель прогнозирования CTR на основе глубокого обучения можно разделить на следующие четыре модуля в соответствии с функциями внутренних компонентов модели:
- входной модуль
- Встроенный модуль
- Модуль извлечения признаков
- модуль вывода предсказания
Все модели построены в строгом соответствии с модулями 4. Ввод, встраивание и вывод в основном общие.Разница между каждой моделью в основном заключается в части извлечения признаков.
1.2.3 Преимущества фреймворка
- Общая структура понятна и гибка. Линейный возвращает логит, уровень FM возвращает логит, а глубокий содержит результаты промежуточного уровня. Упакуйте последний слой глубокого в каждую модель, определите, нужны ли линейный, fm и глубокий, и, наконец, получите доступ к полностью подключенному слой.
- В основном используемые модули и архитектуры: keras' Concatenate (список в тензор), Dense (последний полносвязный слой и плотный), Embedding (разреженный, плотный, последовательность), Input (разреженный, плотный, последовательность) и обычные операции: оптимизатор, регуляризатор
- Мультиплексирован и перегружен слой Layer, переписаны build, call, calculate_output_shape, calculate_mask, get_config
Давайте начнем внимательно изучать последнюю версию DIEN.
0x2 тестовые данные
DIEN использует данные Tianchi, а ридми предлагает скачать:
1. Download Dataset [Ad Display/Click Data on Taobao.com](https://tianchi.aliyun.com/dataset/dataDetail?dataId=56)
2. Extract the files into the ``raw_data`` directory
Ali_Display_Ad_Click — это набор данных Taobao для оценки кликабельности медийной рекламы, предоставленный Alibaba.
2.1 Введение в набор данных
имя данных | инструкция | Атрибуты |
---|---|---|
raw_sample | оригинальный образец скелета | Идентификатор пользователя, идентификатор рекламы, время, бит ресурса, клик или нет |
ad_feature | Основная информация об объявлении | Идентификатор рекламы, идентификатор рекламной программы, идентификатор категории, идентификатор бренда |
user_profile | Основная информация о пользователе | Идентификатор пользователя, возрастная группа, пол и т. д. |
raw_behavior_log | Журнал поведения пользователей | Идентификатор пользователя, тип поведения, время, идентификатор категории продукта, идентификатор бренда |
2.2 Скелет исходного образца raw_sample
Журналы отображения/кликов рекламы (26 миллионов записей) 1,14 миллиона пользователей за 8 дней были случайным образом отобраны с веб-сайта Taobao для формирования исходного образца. Описание полей следующее:
- (1) user_id: десенсибилизированный идентификатор пользователя;
- (2) adgroup_id: идентификатор десенсибилизированного рекламного блока;
- (3) time_stamp: временная метка;
- (4) pid: ресурсный бит;
- (5) noclk: 1 означает отсутствие щелчка, 0 означает щелчок;
- (6) clk: 0 означает отсутствие щелчка, 1 означает щелчок;
Мы используем первые 7 дней в качестве обучающих выборок (20170506-20170512) и 8-й день в качестве тестовых выборок (20170513).
2.3 Таблица основной информации о рекламе ad_feature
Этот набор данных охватывает основную информацию обо всех рекламных объявлениях в raw_sample. Описание полей следующее:
- (1) adgroup_id: идентификатор десенсибилизированной рекламы;
- (2) cate_id: идентификатор категории десенсибилизированного товара;
- (3) кампания_id: идентификатор десенсибилизированного рекламного плана;
- (4) customer_id: идентификатор десенсибилизированного рекламодателя;
- (5) бренд: идентификатор бренда десенсибилизации;
- (6) цена: цена ребенка
Один из рекламных идентификаторов соответствует продукту (ребенок), ребенок относится к категории, а ребенок относится к бренду.
2.4 Таблица основной информации пользователя user_profile
Этот набор данных охватывает основную информацию обо всех пользователях в raw_sample. Описание полей следующее:
- (1) userid: идентификатор пользователя для десенсибилизации;
- (2) cms_segid: идентификатор микрогруппы;
- (3) cms_group_id: cms_group_id;
- (4) final_gender_code: пол 1: мужской, 2: женский;
- (5) age_level: возрастной уровень;
- (6) pvalue_level: уровень потребления, 1: низкий уровень, 2: средний уровень, 3: высокий уровень;
- (7) shopping_level: глубина покупок, 1: поверхностные пользователи, 2: умеренные пользователи, 3: активные пользователи.
- (8) профессия: студент колледжа или нет, 1: да, 0: нет
- (9) new_user_class_level: уровень города
2.5 Журнал поведения пользователя behavior_log
Этот набор данных охватывает покупательское поведение всех пользователей в raw_sample за 22 дня (всего 700 миллионов записей). Описание полей следующее:
- (1) пользователь: десенсибилизированный идентификатор пользователя;
- (2) time_stamp: временная метка;
- (3) btag: тип поведения, включая следующие четыре:
тип | инструкция |
---|---|
ipv | просматривать |
cart | добавить в корзину |
fav | как |
buy | Купить |
- (4) cate: категория десенсибилизированных товаров;
- (5) бренд: фирменное слово для десенсибилизации;
Здесь, с user + time_stamp в качестве ключа, будет много повторяющихся записей; это потому, что наши разные типы данных о поведении записываются разными отделами, и когда они упакованы вместе, на самом деле будет небольшое отклонение (то есть, два Один и тот же time_stamp на самом деле два раза с меньшей разницей).
2.6 Типичные сценарии научных исследований
В соответствии с историческим покупательским поведением пользователя он прогнозирует вероятность клика пользователя при согласии на показ рекламы.
исходный уровеньППК: 0,622
0x03 Структура каталогов
Структура каталога кода следующая: первые пять — обработка данных, модели ниже — модели, а train_xxx — обучающий код.
.
├── 0_gen_sampled_data.py
├── 1_gen_sessions.py
├── 2_gen_dien_input.py
├── 2_gen_din_input.py
├── 2_gen_dsin_input.py
├── config.py
├── config.pyc
├── models
│ ├── __init__.py
│ ├── dien.py
│ ├── din.py
│ └── dsin.py
├── train_dien.py
├── train_din.py
└── train_dsin.py
структура данных 0x04
Далее мы анализируем часть структуры данных.
4.1 Генерация демонстрационных данных
Роль 0_gen_sampled_data.py заключается в создании выборочных данных:
- Основная логика следующая:
- Извлекать образцы от пользователей по соотношению;
- Извлеките данные, соответствующие пользователю выборки, из исходного скелета выборки raw_sample;
- Дедупликация пользовательских данных;
- Извлечение данных, соответствующих выбранным пользователям, из поведенческих данных;
- Дополнение -1 для отсутствующих данных объявления ['бренд'];
- Используйте LabelEncoder для жесткого кодирования функций и нумерации текстовых функций:
- Дедуплицируйте ad['cate_id'] и log['cate'], а затем закодируйте;
- Дедуплицируйте объявление['бренд'] и журнал['бренд'], а затем закодируйте;
- log удаляет столбец btag;
- log удаляет недопустимые столбцы временных меток;
- Затем сохраните его как файл;
4.2 Входные данные, необходимые для создания DIEN
2_gen_dien_input.py используется для генерации входных данных, требуемых DIEN.Основная логика:
Получите файлы, связанные с сеансом пользователя, в выборке данных (эта часть на самом деле не нужна для DIEN).
FILE_NUM = len(
list(
filter(lambda x: x.startswith('user_hist_session_' + str(FRAC) + '_din_'), os.listdir('../sampled_data/'))))
Пройдите файл и поместите данные в user_hist_session_
for i in range(FILE_NUM):
user_hist_session_ = pd.read_pickle(
'../sampled_data/user_hist_session_' + str(FRAC) + '_din_' + str(i) + '.pkl')
user_hist_session.update(user_hist_session_)
del user_hist_session_
Используйте gen_sess_feature_dien для генерации данных сеанса, а также отдельно генерируйте словари и отображайте их с помощью индикатора выполнения.
Создайте dict, каждое значение представляет собой список действий пользователя (cate_id, brand, time_stamp):
sess_input_dict = {'cate_id': [], 'brand': []}
neg_sess_input_dict = {'cate_id': [], 'brand': []}
sess_input_length = []
for row in tqdm(sample_sub[['user', 'time_stamp']].iterrows()):
a, b, n_a, n_b, c = gen_sess_feature_dien(row)
sess_input_dict['cate_id'].append(a)
sess_input_dict['brand'].append(b)
neg_sess_input_dict['cate_id'].append(n_a)
neg_sess_input_dict['brand'].append(n_b)
sess_input_length.append(c)
Функция gen_sess_feature_dien получает данные сеанса.
- Просматривайте исторические сеансы текущего пользователя от конца к началу и удаляйте все сеансы меньше временной метки в каждом сеансе.
- пройти через
for e in cur_sess[max(0, i + 1 - sess_max_len):i + 1]]
вынуть последние логи sess_max_len в сессии, - Используйте функцию выборки для создания отрицательных данных выборки,
- Наконец, получите два данных «cate_id», «brand».
def gen_sess_feature_dien(row):
sess_max_len = DIN_SESS_MAX_LEN
sess_input_dict = {'cate_id': [0], 'brand': [0]}
neg_sess_input_dict = {'cate_id': [0], 'brand': [0]}
sess_input_length = 0
user, time_stamp = row[1]['user'], row[1]['time_stamp']
if user not in user_hist_session or len(user_hist_session[user]) == 0:
sess_input_dict['cate_id'] = [0]
sess_input_dict['brand'] = [0]
neg_sess_input_dict['cate_id'] = [0]
neg_sess_input_dict['brand'] = [0]
sess_input_length = 0
else:
cur_sess = user_hist_session[user][0]
for i in reversed(range(len(cur_sess))):
if cur_sess[i][2] < time_stamp:
sess_input_dict['cate_id'] = [e[0]
for e in cur_sess[max(0, i + 1 - sess_max_len):i + 1]]
sess_input_dict['brand'] = [e[1]
for e in cur_sess[max(0, i + 1 - sess_max_len):i + 1]]
neg_sess_input_dict = {'cate_id': [], 'brand': []}
for c in sess_input_dict['cate_id']:
neg_cate, neg_brand = sample(c)
neg_sess_input_dict['cate_id'].append(neg_cate)
neg_sess_input_dict['brand'].append(neg_brand)
sess_input_length = len(sess_input_dict['brand'])
break
return sess_input_dict['cate_id'], sess_input_dict['brand'], neg_sess_input_dict['cate_id'], neg_sess_input_dict[
'brand'], sess_input_length
Образец генерирует данные отрицательной выборки, которые должны случайным образом генерировать индекс, а затем повторно генерировать индекс, если соответствующая категория равна cate_id. Получаются выборочные данные.
def sample(cate_id):
global ad
while True:
i = np.random.randint(0, ad.shape[0])
sample_cate = ad.iloc[i]['cate_id']
if sample_cate != cate_id:
break
return sample_cate, ad.iloc[i]['brand']
Заполните недостающие значения для пользователя с -1, переименуйте new_user_class_level, переименуйте пользователя в 'userid';
user = user.fillna(-1)
user.rename(
columns={'new_user_class_level ': 'new_user_class_level'}, inplace=True)
sample_sub.rename(columns={'user': 'userid'}, inplace=True)
Подключиться к sample_sub, user, и подключиться к данным, ad.
data = pd.merge(sample_sub, user, how='left', on='userid', )
data = pd.merge(data, ad, how='left', on='adgroup_id')
Жестко закодируйте sparse_features для нумерации текстовых функций.
Стандартное масштабирование для плотности_функций.
sparse_features = ['userid', 'adgroup_id', 'pid', 'cms_segid', 'cms_group_id', 'final_gender_code', 'age_level',
'pvalue_level', 'shopping_level', 'occupation', 'new_user_class_level', 'campaign_id',
'customer']
dense_features = ['price']
for feat in tqdm(sparse_features):
lbe = LabelEncoder() # or Hash
data[feat] = lbe.fit_transform(data[feat])
mms = StandardScaler()
data[dense_features] = mms.fit_transform(data[dense_features])
Создайте SingleFeat для sparse_features и плотно_features соответственно, который представляет собой namedtuple, встроенный в deepCtr.
sparse_feature_list = [SingleFeat(feat, data[feat].nunique(
) + 1) for feat in sparse_features + ['cate_id', 'brand']]
dense_feature_list = [SingleFeat(feat, 1) for feat in dense_features]
sess_feature = ['cate_id', 'brand']
Для функций в sparse_feature получите значение из sess_input_dict и neg_sess_input_dict для построения последовательности, эти два являются данными сеанса, то есть данными поведения.
sess_input = [pad_sequences(
sess_input_dict[feat], maxlen=DIN_SESS_MAX_LEN, padding='post') for feat in sess_feature]
neg_sess_input = [pad_sequences(neg_sess_input_dict[feat], maxlen=DIN_SESS_MAX_LEN, padding='post') for feat in
sess_feature]
Для функций в sparse_feature_list и плотном_feature_list пройдите соответствующее значение в данных и создайте model_input.
Встроить sess_input, neg_sess_input и [np.array(sess_input_length)] в sess_lists;
Добавьте sess_lists в model_input;
model_input = [data[feat.name].values for feat in sparse_feature_list] + \
[data[feat.name].values for feat in dense_feature_list]
sess_lists = sess_input + neg_sess_input + [np.array(sess_input_length)]
model_input += sess_lists
Следующим шагом является сохранение данных в файл.
pd.to_pickle(model_input, '../model_input/dien_input_' +
str(FRAC) + '_' + str(DIN_SESS_MAX_LEN) + '.pkl')
pd.to_pickle(data['clk'].values, '../model_input/dien_label_' +
str(FRAC) + '_' + str(DIN_SESS_MAX_LEN) + '.pkl')
try:
pd.to_pickle({'sparse': sparse_feature_list, 'dense': dense_feature_list},
'../model_input/dien_fd_' + str(FRAC) + '_' + str(DIN_SESS_MAX_LEN) + '.pkl', )
except:
pd.to_pickle({'sparse': sparse_feature_list, 'dense': dense_feature_list},
'../model_input/dien_fd_' + str(FRAC) + '_' + str(DIN_SESS_MAX_LEN) + '.pkl', )
0x05 модель ДИЕН
В частности, модель можно разделить на две части.
- train_dien.py отвечает за построение модели и связанных с ней частей.
- Dien.py — конкретная реализация модели;
5.1 train_dien.py
Сначала прочтите данные.
fd = pd.read_pickle('../model_input/dien_fd_' +
str(FRAC) + '_' + str(SESS_MAX_LEN) + '.pkl')
model_input = pd.read_pickle(
'../model_input/dien_input_' + str(FRAC) + '_' + str(SESS_MAX_LEN) + '.pkl')
label = pd.read_pickle('../model_input/dien_label_' +
str(FRAC) + '_' + str(SESS_MAX_LEN) + '.pkl')
sample_sub = pd.read_pickle(
'../sampled_data/raw_sample_' + str(FRAC) + '.pkl')
Создавайте этикетки.
sample_sub['idx'] = list(range(sample_sub.shape[0]))
train_idx = sample_sub.loc[sample_sub.time_stamp < 1494633600, 'idx'].values
test_idx = sample_sub.loc[sample_sub.time_stamp >= 1494633600, 'idx'].values
train_input = [i[train_idx] for i in model_input]
test_input = [i[test_idx] for i in model_input]
train_label = label[train_idx]
test_label = label[test_idx]
sess_len_max = SESS_MAX_LEN
BATCH_SIZE = 4096
sess_feature = ['cate_id', 'brand']
TEST_BATCH_SIZE = 2 ** 14
Генерируется модель keras, поэтому вы можете подогнать ее и спрогнозировать позже.
model = DIEN(fd, sess_feature, 4, sess_len_max, "AUGRU", att_hidden_units=(64, 16),
att_activation='sigmoid', use_negsampling=DIEN_NEG_SAMPLING)
Основные подпрограммы keras: компилировать, подгонять, предсказывать.
model.compile('adagrad', 'binary_crossentropy',
metrics=['binary_crossentropy', ])
if DIEN_NEG_SAMPLING:
hist_ = model.fit(train_input, train_label, batch_size=BATCH_SIZE,
epochs=1, initial_epoch=0, verbose=1, )
pred_ans = model.predict(test_input, TEST_BATCH_SIZE)
else:
hist_ = model.fit(train_input[:-3] + train_input[-1:], train_label, batch_size=BATCH_SIZE, epochs=1,
initial_epoch=0, verbose=1, )
pred_ans = model.predict(
test_input[:-3] + test_input[-1:], TEST_BATCH_SIZE)
5.2 dien.py
Вот код модели, ядро этой статьи. Поскольку общая идея DIEN остается неизменной, мы ориентируемся на вторую версию (GitHub.com/Mouna99/Вниз…) разница.
5.2.1 Версия 2
Вспомним основной код второй версии для сравнения.
class Model_DIN_V2_Gru_Vec_attGru(Model):
def __init__(self, n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE, use_negsampling=False):
super(Model_DIN_V2_Gru_Vec_attGru, self).__init__(n_uid, n_mid, n_cat,
EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE,
use_negsampling)
# RNN layer(-s)
with tf.name_scope('rnn_1'):
rnn_outputs, _ = dynamic_rnn(GRUCell(HIDDEN_SIZE), inputs=self.item_his_eb,
sequence_length=self.seq_len_ph, dtype=tf.float32,
scope="gru1")
tf.summary.histogram('GRU_outputs', rnn_outputs)
# Attention layer
with tf.name_scope('Attention_layer_1'):
att_outputs, alphas = din_fcn_attention(self.item_eb, rnn_outputs, ATTENTION_SIZE, self.mask,
softmax_stag=1, stag='1_1', mode='LIST', return_alphas=True)
tf.summary.histogram('alpha_outputs', alphas)
with tf.name_scope('rnn_2'):
rnn_outputs2, final_state2 = dynamic_rnn(VecAttGRUCell(HIDDEN_SIZE), inputs=rnn_outputs,
att_scores = tf.expand_dims(alphas, -1),
sequence_length=self.seq_len_ph, dtype=tf.float32,
scope="gru2")
tf.summary.histogram('GRU2_Final_State', final_state2)
inp = tf.concat([self.uid_batch_embedded, self.item_eb, self.item_his_eb_sum, self.item_eb * self.item_his_eb_sum, final_state2], 1)
self.build_fcn_net(inp, use_dice=True)
Тогда взгляните на последнюю третью версию.
5.2.2 Входные параметры
Входные параметры DIEN следующие, и их значение можно приблизительно узнать из комментариев.
def DIEN(feature_dim_dict, seq_feature_list, embedding_size=8, hist_len_max=16,
gru_type="GRU", use_negsampling=False, alpha=1.0, use_bn=False, dnn_hidden_units=(200, 80),
dnn_activation='relu',
att_hidden_units=(64, 16), att_activation="dice", att_weight_normalization=True,
l2_reg_dnn=0, l2_reg_embedding=1e-6, dnn_dropout=0, init_std=0.0001, seed=1024, task='binary'):
"""Instantiates the Deep Interest Evolution Network architecture.
:param feature_dim_dict: dict,to indicate sparse field (**now only support sparse feature**)like {'sparse':{'field_1':4,'field_2':3,'field_3':2},'dense':[]}
:param seq_feature_list: list,to indicate sequence sparse field (**now only support sparse feature**),must be a subset of ``feature_dim_dict["sparse"]``
:param embedding_size: positive integer,sparse feature embedding_size.
:param hist_len_max: positive int, to indicate the max length of seq input
:param gru_type: str,can be GRU AIGRU AUGRU AGRU
:param use_negsampling: bool, whether or not use negtive sampling
:param alpha: float ,weight of auxiliary_loss
:param use_bn: bool. Whether use BatchNormalization before activation or not in deep net
:param dnn_hidden_units: list,list of positive integer or empty list, the layer number and units in each layer of DNN
:param dnn_activation: Activation function to use in DNN
:param att_hidden_units: list,list of positive integer , the layer number and units in each layer of attention net
:param att_activation: Activation function to use in attention net
:param att_weight_normalization: bool.Whether normalize the attention score of local activation unit.
:param l2_reg_dnn: float. L2 regularizer strength applied to DNN
:param l2_reg_embedding: float. L2 regularizer strength applied to embedding vector
:param dnn_dropout: float in [0,1), the probability we will drop out a given DNN coordinate.
:param init_std: float,to use as the initialize std of embedding vector
:param seed: integer ,to use as random seed.
:param task: str, ``"binary"`` for binary logloss or ``"regression"`` for regression loss
:return: A Keras model instance.
"""
5.2.3 Построение векторов
Здесь строятся два вида векторов, а именно вектор плотности (Dense Vector) и разреженный вектор (Spasre Vector). Вектор плотности будет хранить все значения, включая нулевые значения, в то время как разреженный вектор хранит положение и значение индекса и не хранит нулевые значения.Только когда объем данных относительно велик, разреженный вектор может отражать свои преимущества и значение .
Функция get_input предназначена для извлечения вектора из входного словаря.
def get_input(feature_dim_dict, seq_feature_list, seq_max_len):
sparse_input, dense_input = create_singlefeat_inputdict(feature_dim_dict)
user_behavior_input = OrderedDict()
for i, feat in enumerate(seq_feature_list):
user_behavior_input[feat] = Input(shape=(seq_max_len,), name='seq_' + str(i) + '-' + feat)
user_behavior_length = Input(shape=(1,), name='seq_length')
return sparse_input, dense_input, user_behavior_input, user_behavior_length
Перейдите feature_dim_dict, чтобы создать словарь функций, каждый элемент имеет имя: Embedding. Его функция состоит в том, чтобы получить вложение, соответствующее конкретной входной переменной в sparse_input, из sparse_embedding_dict.
sparse_embedding_dict = {feat.name: Embedding(feat.dimension, embedding_size,
embeddings_initializer=RandomNormal(
mean=0.0, stddev=init_std, seed=seed),
embeddings_regularizer=l2(
l2_reg_embedding),
name='sparse_emb_' + str(i) + '-' + feat.name) for i, feat in
enumerate(feature_dim_dict["sparse"])}
Получите var внедрения, где каждый embedding_dict[feat] является матрицей.
query_emb_list = get_embedding_vec_list(sparse_embedding_dict, sparse_input, feature_dim_dict["sparse"],
return_feat_list=seq_feature_list)
сшить их вместе.
query_emb = concat_fun(query_emb_list)
keys_emb = concat_fun(keys_emb_list)
deep_input_emb = concat_fun(deep_input_emb_list)
5.2.4 Уровень эволюции интересов
Давайте начнем называть слой эволюции интереса поколения.
hist, aux_loss_1 = interest_evolution(keys_emb, query_emb, user_behavior_length, gru_type=gru_type,
use_neg=use_negsampling, neg_concat_behavior=neg_concat_behavior,
embedding_size=embedding_size, att_hidden_size=att_hidden_units,
att_activation=att_activation,
att_weight_normalization=att_weight_normalization, )
в:
- DynamicGRU эквивалентен второй версии dynamic_rnn, которая является первым уровнем «rnn_1»;
- вспомогательный_лосс почти такой же, как и во второй версии;
- вспомогательная_сеть отличается только на последнем шаге y_hat = tf.nn.sigmoid(dnn3);
Конкретный код выглядит следующим образом:
def interest_evolution(concat_behavior, deep_input_item, user_behavior_length, gru_type="GRU", use_neg=False,
neg_concat_behavior=None, embedding_size=8, att_hidden_size=(64, 16), att_activation='sigmoid',
att_weight_normalization=False, ):
aux_loss_1 = None
rnn_outputs = DynamicGRU(embedding_size * 2, return_sequence=True,
name="gru1")([concat_behavior, user_behavior_length])
if gru_type == "AUGRU" and use_neg:
aux_loss_1 = auxiliary_loss(rnn_outputs[:, :-1, :], concat_behavior[:, 1:, :],
neg_concat_behavior[:, 1:, :],
tf.subtract(user_behavior_length, 1), stag="gru") # [:, 1:]
if gru_type == "GRU":
rnn_outputs2 = DynamicGRU(embedding_size * 2, return_sequence=True,
name="gru2")([rnn_outputs, user_behavior_length])
hist = AttentionSequencePoolingLayer(att_hidden_units=att_hidden_size, att_activation=att_activation,
weight_normalization=att_weight_normalization, return_score=False)([
deep_input_item, rnn_outputs2, user_behavior_length])
else: # AIGRU AGRU AUGRU
scores = AttentionSequencePoolingLayer(att_hidden_units=att_hidden_size, att_activation=att_activation,
weight_normalization=att_weight_normalization, return_score=True)([
deep_input_item, rnn_outputs, user_behavior_length])
if gru_type == "AIGRU":
hist = multiply([rnn_outputs, Permute([2, 1])(scores)])
final_state2 = DynamicGRU(embedding_size * 2, gru_type="GRU", return_sequence=False, name='gru2')(
[hist, user_behavior_length])
else: # AGRU AUGRU
final_state2 = DynamicGRU(embedding_size * 2, gru_type=gru_type, return_sequence=False,
name='gru2')([rnn_outputs, user_behavior_length, Permute([2, 1])(scores)])
hist = final_state2
return hist, aux_loss_1
5.2.4.1 DynamicGRU 1
DynamicGRU эквивалентен второй версии dynamic_rnn, которая является первым уровнем «rnn_1».
Этот уровень соответствует желтой части схемы архитектуры, а именно уровню извлечения интересов, а основным компонентом является GRU.
Основная функция заключается в извлечении последовательности интересов пользователя на основе последовательности поведения путем имитации процесса миграции интересов пользователя. То есть элемент, встраивающий историю поведения пользователя, вводится в динамический rnn (первый уровень GRU).
rnn_outputs = DynamicGRU(embedding_size * 2, return_sequence=True,
name="gru1")([concat_behavior, user_behavior_length])
5.2.4.2 auxiliary_loss
Расчет вспомогательных потерь фактически представляет собой двухклассовую модель, соответствующую статье:
вспомогательный_лосс почти такой же, как и во второй версии.
def auxiliary_loss(h_states, click_seq, noclick_seq, mask, stag=None):
#:param h_states:
#:param click_seq:
#:param noclick_seq: #[B,T-1,E]
#:param mask:#[B,1]
#:param stag:
#:return:
hist_len, _ = click_seq.get_shape().as_list()[1:]
mask = tf.sequence_mask(mask, hist_len)
mask = mask[:, 0, :]
mask = tf.cast(mask, tf.float32)
# 倒数第一维度concat,其余不变
click_input_ = tf.concat([h_states, click_seq], -1)
# 倒数第一维度concat,其余不变
noclick_input_ = tf.concat([h_states, noclick_seq], -1)
# 获取正样本最后一个y_hat
click_prop_ = auxiliary_net(click_input_, stag=stag)[:, :, 0]
# 获取负样本最后一个y_hat
noclick_prop_ = auxiliary_net(noclick_input_, stag=stag)[
:, :, 0] # [B,T-1]
# 对数损失,并且mask出真实历史行为
click_loss_ = - tf.reshape(tf.log(click_prop_),
[-1, tf.shape(click_seq)[1]]) * mask
noclick_loss_ = - \
tf.reshape(tf.log(1.0 - noclick_prop_),
[-1, tf.shape(noclick_seq)[1]]) * mask
loss_ = tf.reduce_mean(click_loss_ + noclick_loss_)
return loss_
5.2.4.3 auxiliary_net
вспомогательная_сеть отличается только на последнем шаге y_hat = tf.nn.sigmoid(dnn3) .
def auxiliary_net(in_, stag='auxiliary_net'):
bn1 = tf.layers.batch_normalization(
inputs=in_, name='bn1' + stag, reuse=tf.AUTO_REUSE)
dnn1 = tf.layers.dense(bn1, 100, activation=None,
name='f1' + stag, reuse=tf.AUTO_REUSE)
dnn1 = tf.nn.sigmoid(dnn1)
dnn2 = tf.layers.dense(dnn1, 50, activation=None,
name='f2' + stag, reuse=tf.AUTO_REUSE)
dnn2 = tf.nn.sigmoid(dnn2)
dnn3 = tf.layers.dense(dnn2, 1, activation=None,
name='f3' + stag, reuse=tf.AUTO_REUSE)
y_hat = tf.nn.sigmoid(dnn3)
return y_hat
5.2.4.4 AttentionSequencePoolingLayer
Эта часть дополняется deepctr, который соответствует второй версии din_fcn_attention.Для din_fcn_attention обратитесь к предыдущей статье.[Интерпретация диссертации] Общая структура кода Ali DIEN.
В DIEN роль слоя «Attention_layer_1» заключается в следующем: путем добавления механизма «Внимание» на основе уровня извлечения интереса моделировать процесс развития интереса, связанного с текущей целевой рекламой, и моделировать процесс развития интереса, связанный с целевой элемент. Выходные данные первого уровня подаются в ГРУ второго уровня, а оценка внимания (рассчитанная на основе выходного вектора первого уровня и материала-кандидата) используется для управления воротами обновления ГРУ второго уровня. .
class AttentionSequencePoolingLayer(Layer):
"""The Attentional sequence pooling operation used in DIN.
Input shape
- A list of three tensor: [query,keys,keys_length]
- query is a 3D tensor with shape: ``(batch_size, 1, embedding_size)``
- keys is a 3D tensor with shape: ``(batch_size, T, embedding_size)``
- keys_length is a 2D tensor with shape: ``(batch_size, 1)``
Output shape
- 3D tensor with shape: ``(batch_size, 1, embedding_size)``.
Arguments
- **att_hidden_units**:list of positive integer, the attention net layer number and units in each layer.
- **att_activation**: Activation function to use in attention net.
- **weight_normalization**: bool.Whether normalize the attention score of local activation unit.
- **supports_masking**:If True,the input need to support masking.
References
- [Zhou G, Zhu X, Song C, et al. Deep interest network for click-through rate prediction[C]//Proceedings of the 24th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining. ACM, 2018: 1059-1068.](https://arxiv.org/pdf/1706.06978.pdf)
"""
def __init__(self, att_hidden_units=(80, 40), att_activation='sigmoid', weight_normalization=False,
return_score=False,
supports_masking=False, **kwargs):
self.att_hidden_units = att_hidden_units
self.att_activation = att_activation
self.weight_normalization = weight_normalization
self.return_score = return_score
super(AttentionSequencePoolingLayer, self).__init__(**kwargs)
self.supports_masking = supports_masking
def build(self, input_shape):
if not self.supports_masking:
if not isinstance(input_shape, list) or len(input_shape) != 3:
raise ValueError('...)
if len(input_shape[0]) != 3 or len(input_shape[1]) != 3 or len(input_shape[2]) != 2:
raise ValueError(...)
if input_shape[0][-1] != input_shape[1][-1] or input_shape[0][1] != 1 or input_shape[2][1] != 1:
raise ValueError(...)
else:
pass
self.local_att = LocalActivationUnit(
self.att_hidden_units, self.att_activation, l2_reg=0, dropout_rate=0, use_bn=False, seed=1024, )
super(AttentionSequencePoolingLayer, self).build(
input_shape) # Be sure to call this somewhere!
def call(self, inputs, mask=None, training=None, **kwargs):
if self.supports_masking:
if mask is None:
raise ValueError(...)
queries, keys = inputs
key_masks = tf.expand_dims(mask[-1], axis=1)
else:
queries, keys, keys_length = inputs
hist_len = keys.get_shape()[1]
key_masks = tf.sequence_mask(keys_length, hist_len)
attention_score = self.local_att([queries, keys], training=training)
outputs = tf.transpose(attention_score, (0, 2, 1))
if self.weight_normalization:
paddings = tf.ones_like(outputs) * (-2 ** 32 + 1)
else:
paddings = tf.zeros_like(outputs)
outputs = tf.where(key_masks, outputs, paddings)
if self.weight_normalization:
outputs = tf.nn.softmax(outputs)
if not self.return_score:
outputs = tf.matmul(outputs, keys)
outputs._uses_learning_phase = attention_score._uses_learning_phase
return outputs
5.2.4.5 DynamicGRU 2
Оценка предыдущего внимания вводится как часть этого GRU.
if gru_type == "AIGRU":
hist = multiply([rnn_outputs, Permute([2, 1])(scores)])
final_state2 = DynamicGRU(embedding_size * 2, gru_type="GRU", return_sequence=False, name='gru2')(
[hist, user_behavior_length])
else: # AGRU AUGRU
final_state2 = DynamicGRU(embedding_size * 2, gru_type=gru_type, return_sequence=False,
name='gru2')([rnn_outputs, user_behavior_length, Permute([2, 1])(scores)])
hist = final_state2
Эта часть выполняется deepctr, который соответствует второй версии GRU, и переносит сюда VecAttGRUCell и так далее.
class DynamicGRU(Layer):
def __init__(self, num_units=None, gru_type='GRU', return_sequence=True, **kwargs):
self.num_units = num_units
self.return_sequence = return_sequence
self.gru_type = gru_type
super(DynamicGRU, self).__init__(**kwargs)
def build(self, input_shape):
# Create a trainable weight variable for this layer.
input_seq_shape = input_shape[0]
if self.num_units is None:
self.num_units = input_seq_shape.as_list()[-1]
if self.gru_type == "AGRU":
self.gru_cell = QAAttGRUCell(self.num_units)
elif self.gru_type == "AUGRU":
self.gru_cell = VecAttGRUCell(self.num_units)
else:
self.gru_cell = tf.nn.rnn_cell.GRUCell(self.num_units)
# Be sure to call this somewhere!
super(DynamicGRU, self).build(input_shape)
def call(self, input_list):
"""
:param concated_embeds_value: None * field_size * embedding_size
:return: None*1
"""
if self.gru_type == "GRU" or self.gru_type == "AIGRU":
rnn_input, sequence_length = input_list
att_score = None
else:
rnn_input, sequence_length, att_score = input_list
rnn_output, hidden_state = dynamic_rnn(self.gru_cell, inputs=rnn_input, att_scores=att_score,sequence_length=tf.squeeze(sequence_length,), dtype=tf.float32, scope=self.name)
if self.return_sequence:
return rnn_output
else:
return tf.expand_dims(hidden_state, axis=1)
5.2.5 Полносвязный уровень DNN
Теперь, когда у нас есть конкатенированный вектор плотного представления, следующим шагом будет использование полносвязного слоя для автоматического изучения комбинации нелинейных отношений между объектами.
Так через многослойную нейронную сеть получается окончательное оценочное значение ctr, и эта часть представляет собой вызов функции.
В соответствии с бумагой:
код показывает, как показано ниже:
deep_input_emb = Concatenate()([deep_input_emb, hist])
deep_input_emb = tf.keras.layers.Flatten()(deep_input_emb)
if len(dense_input) > 0:
deep_input_emb = Concatenate()(
[deep_input_emb] + list(dense_input.values()))
output = DNN(dnn_hidden_units, dnn_activation, l2_reg_dnn,
dnn_dropout, use_bn, seed)(deep_input_emb)
final_logit = Dense(1, use_bias=False)(output)
output = PredictionLayer(task)(final_logit)
model_input_list = get_inputs_list(
[sparse_input, dense_input, user_behavior_input])
if use_negsampling:
model_input_list += list(neg_user_behavior_input.values())
model_input_list += [user_behavior_length]
model = tf.keras.models.Model(inputs=model_input_list, outputs=output)
if use_negsampling:
model.add_loss(alpha * aux_loss_1)
tf.keras.backend.get_session().run(tf.global_variables_initializer())
return model
На этом анализ версии Keras в основном завершен.
0xEE Личная информация
★★★★★★Думая о жизни и технологиях★★★★★★
Публичный аккаунт WeChat: мысли Росси
Если вы хотите получать своевременные новости о статьях, написанных отдельными лицами, или хотите видеть технические материалы, рекомендованные отдельными лицами, обратите внимание.
ссылка 0xFF
2019-11-06 Прогноз CTR объявления: простое введение в библиотеку DeepCTR