[Ali DIEN] Keras версия анализа исходного кода сети Deep Interest evolution

искусственный интеллект глубокое обучение

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

DeepCTR: простой в использовании и расширяемый пакет алгоритмов прогнозирования CTR для глубокого обучения.

Чтение кода фреймворка Deepctr