Wide & Deep 학습 권장 시스템 팀

1. 클릭률 추정 소개

클릭률 추정은 어떤 문제를 해결합니까?

클릭률 추정은 각 광고 클릭에 대한 예측입니다. 클릭을 출력하거나 클릭하지 않을 수 있으며, 클릭 확률을 출력 할 수 있습니다. 후자는 pClick이라고도합니다.

 

클릭률 추정 모델은 무엇을해야합니까?

위의 클릭률 추정의 기본 개념을 통해 우리는 클릭률 추정의 문제가 실제로 두 가지 범주의 문제임을 알 수 있습니다. 머신 러닝에서는 로지스틱 회귀를 모델의 출력으로 사용할 수 있습니다. output은 확률값으로 머신 러닝에 의해 출력되는 확률값은 특정 사용자가 광고를 클릭 할 확률로 간주됩니다.

 

클릭률 추정과 추천 알고리즘의 차이점은 무엇입니까?

광고 클릭률 추정은 특정 광고에 대한 특정 사용자의 클릭률을 구한 후 광고의 입찰을 결합하여 순위를 매기는 것입니다. 대부분의 경우 추천 알고리즘은 최적의 추천 순서를 얻기 만하면됩니다. 즉, TopN 권장 문제입니다. 물론, 광고의 클릭률은 광고 추천 순위로도 사용할 수 있습니다.

 

2. FM은 향이 나지 않습니까?

전에 FM 모델을 배웠는데이 정도면 충분하지 않나요? 왜이 Wide & Deep을 조정해야하나요? 단점은 질의 항목 행렬이 희박하고 높은 순위 (예 : 사용자가 특별한 취미를 가지고 있거나 항목이 상대적으로 작음) 인 경우 저 차원 표현을 매우 효율적으로 학습하기 어렵다는 것입니다. 이 경우 대부분의 쿼리 항목은 관련이 없습니다. 그러나 조밀 한 임베딩은 거의 모든 쿼리 항목 예측 값이 0이 아니도록하여 권장 사항을 과도하게 일반화하고 관련성이 낮은 항목을 권장합니다. 반대로 단순한 선형 모델은 교차 제품 변환을 통해 이러한 예외 규칙 을 기억할 수 있습니다 . 교차 제품 변환이 의미하는 바는 나중에 언급됩니다.

 

3. Wide & Deep 모델의 "메모리"와 "일반화 능력"

추천 시스템에서 암기와 일반화는 매우 일반적인 두 가지 개념으로, 암기는 사용자와 제품 간의 대화 형 정보 매트릭스를 통해 학습 규칙을, 일반화는 일반화 규칙입니다. 앞서 소개 한 FM 알고리즘은 일반화의 좋은 예입니다 .vi는 각 사용자 기능 (임베딩)의 압축 표현을 저장하는 반면, 협업 필터링과 SVD는 둘 다 권장되는 대화 형 정보를 기반으로 비교적 짧은 행렬 V를 학습 할 수 있습니다. 사용자가 이전에 상호 작용 한 항목을 기억하여 추론 한 결과입니다. 물론 두 추천 결과 간에는 약간의 차이가 있습니다. Wide & Deep 모델은이 두 추천 결과를 병합하여 최종 추천을 만들고 이전보다 더 나은 결과를 얻을 수 있습니다. 권장 결과는 좋은 모델입니다.

즉, 암기는보다 보수적 인 경향이있어 사용자가 이전에 행동 한 적이있는 항목을 추천합니다. 반대로 일반화는 추천 시스템의 다양성을 증가시키는 경향이 있습니다. 암기는 달성하기 위해 선형 모델 만 사용하면되고, 일반화는 달성하기 위해 DNN을 사용해야합니다.

다음은 왼쪽의 넓은 부분 (간단한 선형 모델)과 오른쪽의 깊은 부분 (일반적인 DNN 모델)으로 구성된 wide & deep 모델의 구조 다이어그램입니다.

 

image-20200910214310877

 

사실 wide & deep 모델 자체의 구조는 매우 간단하고, 머신 러닝 기반과 딥 러닝 기반이 적은 사람은 이해하기 쉽지만, 자신에 따라 Wide 부분에 어떤 기능을 배치할지 선택하는 방법은 무엇입니까? The Deep 부분은 모델의 다른 구조를 설계 할 때이 문서의 저자의 의도를 이해해야하므로이 모델을 잘 사용하기위한 전제이기도합니다.

Wide 부분이 모델의 "메모리 능력"을 향상시키는 데 도움이되고 Deep 부분이 모델의 "일반화 능력"을 향상시키는 데 도움이된다는 것을 어떻게 이해합니까?

  • 넓은 부분은 일반화 된 선형 모델입니다. 입력 기능은 주로 두 부분으로 구성됩니다. 하나는 기능의 원래 부분이고 다른 하나는 원래 기능의 교차 제품 변환입니다. 대화 형 기능은 다음과 같이 정의 할 수 있습니다.

    ϕk (x) = d∏i = 1xckii, cki∈ {0,1}

    독자들은이 공식이 무엇을 의미하는지 확인하기 위해 원본 논문을 찾을 수 있습니다. 일반적인 의미는 두 기능이 동시에 1이면 새 기능이 1이 될 수 있다는 것입니다. 그렇지 않으면 0입니다. 솔직히 말하면 기능 조합입니다. . 원본 논문의 예를 예로 들어 보겠습니다.

    AND (user_installed_app = QQ, pression_app = WeChat), 기능 user_installed_app = QQ 및 featurepression_app = WeChat이 모두 1로 설정되면 결합 된 기능 AND (user_installed_app = QQ, pression_app = WeChat)는 값 1을 사용하고 그렇지 않으면 Is 0.

    학습의 광범위한 부분에서 사용되는 최적화 프로그램은 L1 정규화 (정규화 된 리더 따르기)를 사용하는 FTRL 알고리즘이며 L1 FTLR은 모델의 희소 특성에 큰주의를 기울입니다. 즉, W & D 모델은 L1을 사용합니다. Wide 부분을 허용하는 FTRL 더 희박 해집니다. 즉, Wide 부분의 대부분의 매개 변수가 0이되어 모델 가중치와 특징 벡터의 차원이 크게 줄어 듭니다. 모델의 Wide 부분이 학습 된 후 남은 기능은 매우 중요하므로 모델의 "기억 능력"은 "직접", "폭력"및 "명백한"연관 규칙을 발견하는 능력으로 이해할 수 있습니다.  예를 들어 Google W & D는 광범위한 부분에서 이러한 규칙을 찾을 것으로 예상합니다. 사용자가 애플리케이션 A를 설치하고이 때 애플리케이션 B가 노출되면 사용자가 애플리케이션 B를 설치할 확률이 높습니다.

  • Deep 부분은 DNN 모델입니다. 입력 기능은 주로 숫자 기능 (DNN을 직접 입력 할 수 있음)이고 다른 하나는 카테고리 기능 (DNN에 입력하려면 Embedding을 통과해야 함), 그리고 Deep part의 수학 형식은 다음과 같습니다.

    a (l + 1) = f (Wla (l) + bl)

우리는 DNN 모델의 계층 수가 증가할수록 중간에있는 기능이 더욱 추상적이되어 모델의 일반화 능력이 향상된다는 것을 알고 있습니다.  Deep 부분의 DNN 모델의 경우 저자는 딥 러닝에 일반적으로 사용되는 최적화 도구 인 AdaGrad를 사용하여 모델이보다 정확한 솔루션을 얻도록합니다.

 

Wide 부분과 Deep 부분의 조합

W & D 모델은 합동 훈련을 위해 출력의 두 부분의 결과를 결합하고 깊고 넓은 부분의 출력을 로지스틱 회귀 모델과 재사용하여 최종 예측을 만들고 확률 값을 출력합니다. 합동 훈련의 수학적 형태는 다음과 같습니다.

P (Y = 1 | x) = δ (wTwide [x, ϕ (x)] + wTdeepa (lf) + b)

 

4. 운영 과정

  • 검색  : 기계 학습 모델 및 일부 인위적으로 정의 된 규칙을 사용하여 현재 쿼리와 가장 일치하는 작은 항목 집합을 반환합니다.이 집합은 최종 권장 사항 목록의 후보 집합입니다.

  • 순위

    • 다음과 같은 더 자세한 사용자 특성을 수집합니다.
      • 사용자 기능 (연령, 성별, 언어, 민족 등)
      • 상황 별 기능 (상황 별 기능 : 장비, 시간 등)
      • 노출 기능 (디스플레이 기능 : 앱 연령, 앱 이력 통계 정보 등)
    • 교육을 위해 기능을 Wide 및 Deep에 각각 전달 합니다 . 훈련 중에 최종 손실에 따라 기울기를 계산하고이를 Wide 및 Deep 부분으로 역전 파하고 고유 한 매개 변수를 개별적으로 훈련합니다 (넓은 구성 요소는 깊은 구성 요소의 단점을 채우기 만하면되므로 교차 제품이 더 적게 필요합니다. , 풀 사이즈 와이드 모델이 아님)
      • 훈련 방법은 미니 배치 확률 적 최적화를 사용하는 것입니다.
      • Wide 구성 요소는 FTRL (Follow-the-regularized-leader) + L1 정규화로 학습됩니다.
      • Deep 구성 요소는 AdaGrad를 사용하여 학습됩니다.
    • 훈련 후 TopN 추천

따라서 wide & deep 모델은 모델 구조상 매우 단순하지만, wide & deep 모델을 잘 사용하려면 비즈니스를 깊이 이해하고 넓은 부분에서 어떤 기능을 사용하는지, 어떤 기능을 딥에서 사용하는지 결정해야합니다. 부분 및 넓은 부분의 교차 기능 선택 방법

 

5. 코드 전투

실제 코드 전투는 크게 두 부분으로 나뉘는데, 첫 번째 부분은 tensorflow에 캡슐화 된 wide & deep 모델을 사용하는 것입니다.이 부분은 주로 모델 학습의 전체 구조에 익숙해지기위한 것입니다. 두 번째 부분은 tensorflow에서 keras를 사용하여 wide & deep을 구현하는 것으로, 주로 모델의 세부 사항을 가능한 한 많이보고 구현하는 것입니다.

Tensorflow 내장 WideDeepModel

Wide-Deep 모델은 Tensorflow 라이브러리에 이미 내장되어 있습니다. 특정 구현 프로세스를 이해하기 위해 소스 코드를 보려면 여기 를 참조 하십시오 1 . 설명은 Tensorflow 공식 웹 사이트의 샘플 코드 2참조하십시오 . 우리가 사용한 데이터 세트 다운로드 링크를 보려면 여기를 클릭하십시오 3 .

먼저 글로벌 구현을 살펴보십시오.

tf.keras.experimental.WideDeepModel(
    linear_model, dnn_model, activation=None, **kwargs
)

이 단계는 linear_model과 dnn_model이 함께 연결되어 있음을 쉽게 알 수 있으며, 이는 Wide-Deep FM의 마지막 단계에 해당합니다. 예를 들어 linear_model과 dnn_model을 가장 간단한 구현으로 만들 수 있습니다.

linear_model = LinearModel()
dnn_model = keras.Sequential([keras.layers.Dense(units=64),
                             keras.layers.Dense(units=1)])
combined_model = WideDeepModel(linear_model, dnn_model)
combined_model.compile(optimizer=['sgd', 'adam'], 'mse', ['mse'])
# define dnn_inputs and linear_inputs as separate numpy arrays or
# a single numpy array if dnn_inputs is same as linear_inputs.
combined_model.fit([linear_inputs, dnn_inputs], y, epochs)
# or define a single `tf.data.Dataset` that contains a single tensor or
# separate tensors for dnn_inputs and linear_inputs.
dataset = tf.data.Dataset.from_tensors(([linear_inputs, dnn_inputs], y))
combined_model.fit(dataset, epochs)

여기서 첫 번째 단계는 keras.experimental에서 linear_model을 직접 호출하는 것이고, 두 번째 단계는 단순히 완전히 연결된 신경망을 구현하고, 세 번째 단계는 WideDeepModel을 사용하여 처음 두 단계에서 생성 된 두 모델을 결합하여 최종 결합 모델을 형성합니다. 그런 다음 정규 컴파일 및 맞춤이 제공됩니다.

또한 선형 모델과 DNN 모델은 공동 학습 전에 별도로 학습 할 수 있습니다.

linear_model = LinearModel()
linear_model.compile('adagrad', 'mse')
linear_model.fit(linear_inputs, y, epochs)
dnn_model = keras.Sequential([keras.layers.Dense(units=1)])
dnn_model.compile('rmsprop', 'mse')
dnn_model.fit(dnn_inputs, y, epochs)
combined_model = WideDeepModel(linear_model, dnn_model)
combined_model.compile(optimizer=['sgd', 'adam'], 'mse', ['mse'])
combined_model.fit([linear_inputs, dnn_inputs], y, epochs)

여기에서 코드의 처음 세 줄은 선형 모델을 훈련시키고, 중간 세 줄은 DNN 모델을 훈련시키고, 마지막 세 줄은 두 모델을 공동으로 훈련시킵니다. 위의 코드는 Tensorflow의 WideDeepModel에 대한 호출을 완료하며 각 함수에는 몇 가지가 있습니다. 여기에서 다른 매개 변수에 대해서는 자세히 설명하지 않습니다. 독자는 필요한 경우 tensorflow 공식 웹 사이트에서 쿼리 할 수 ​​있습니다. 또한이 부분의 소스 코드는 Tensorflow의 Github에 표시되며 링크는 여기 1 입니다.

 

Tensorflow는 넓고 깊은 모델을 구현합니다.

이 부분은 딥 피처와 와이드 피처의 선택, 피처의 교차와 같은 일련의 피처 작업은 물론 원래 피처를 변환합니다. 모델도 와이드 파트와 딥 파트로 나뉩니다. 위와 비교 -내장 된 tensorflow 모델을 사용하여 직접 언급하면 ​​더 자세하고 모델을 더 깊이 이해할 수 있습니다.

여기서는 간단한 구현을 위해 넓고 깊은 부분의 최적화는 동일한 최적화 프로그램을 사용하여 두 부분을 최적화합니다. 자세한 내용은 코드의 주석을 참조하십시오.

암호

import pandas as pd
import numpy as np
import warnings
import random, math, os
from tqdm import tqdm

from tensorflow.keras import *
from tensorflow.keras.layers import *
from tensorflow.keras.models import *
from tensorflow.keras.callbacks import *
import tensorflow.keras.backend as K
from tensorflow.keras.regularizers import l2, l1_l2

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder, MinMaxScaler, StandardScaler, LabelEncoder

# 读取数据,并将标签做简单的转换
def get_data():
    COLUMNS = ["age", "workclass", "fnlwgt", "education", "education_num",
               "marital_status", "occupation", "relationship", "race", "gender",
               "capital_gain", "capital_loss", "hours_per_week", "native_country",
               "income_bracket"]

    df_train = pd.read_csv("./data/adult_train.csv", names=COLUMNS)
    df_test = pd.read_csv("./data/adult_test.csv", names=COLUMNS)

    df_train['income_label'] = (df_train["income_bracket"].apply(lambda x: ">50K" in x)).astype(int)
    df_test['income_label'] = (df_test["income_bracket"].apply(lambda x: ">50K" in x)).astype(int)

    return df_train, df_test


# 特征处理分为wide部分的特征处理和deep部分的特征处理
def data_process(df_train, df_test):
    # 年龄特征离散化
    age_groups = [0, 25, 65, 90]
    age_labels = range(len(age_groups) - 1)
    df_train['age_group'] = pd.cut(df_train['age'], age_groups, labels=age_labels)
    df_test['age_group'] = pd.cut(df_test['age'], age_groups, labels=age_labels)

    # wide部分的原始特征及交叉特征
    wide_cols = ['workclass', 'education', 'marital_status', 'occupation', \
                 'relationship', 'race', 'gender', 'native_country', 'age_group']
    x_cols = (['education', 'occupation'], ['native_country', 'occupation'])

    # deep部分的特征分为两大类,一类是数值特征(可以直接输入到网络中进行训练),
    # 一类是类别特征(只能在embedding之后才能输入到模型中进行训练)
    embedding_cols = ['workclass', 'education', 'marital_status', 'occupation', \
                      'relationship', 'race', 'gender', 'native_country']
    cont_cols = ['age', 'capital_gain', 'capital_loss', 'hours_per_week']

    # 类别标签
    target = 'income_label'

    return df_train, df_test, wide_cols, x_cols, embedding_cols, cont_cols, target


def process_wide_feats(df_train, df_test, wide_cols, x_cols, target):
    # 合并训练和测试数据,后续一起编码
    df_train['IS_TRAIN'] = 1
    df_test['IS_TRAIN'] = 0
    df_wide = pd.concat([df_train, df_test])

    # 选出wide部分特征中的类别特征, 类别特征在DataFrame中是object类型
    categorical_columns = list(df_wide.select_dtypes(include=['object']).columns) 

    # 构造交叉特征
    crossed_columns_d = []
    for f1, f2 in x_cols:
        col_name = f1 + '_' + f2
        crossed_columns_d.append(col_name)
        df_wide[col_name] = df_wide[[f1, f2]].apply(lambda x: '-'.join(x), axis=1)

    # wide部分的所有特征
    wide_cols += crossed_columns_d
    df_wide = df_wide[wide_cols + [target] + ['IS_TRAIN']]

    # 将wide部分类别特征进行onehot编码
    dummy_cols = [c for c in wide_cols if c in categorical_columns + crossed_columns_d]
    df_wide = pd.get_dummies(df_wide, columns=[x for x in dummy_cols])

    # 将训练数据和测试数据分离
    train = df_wide[df_wide.IS_TRAIN == 1].drop('IS_TRAIN', axis=1)
    test = df_wide[df_wide.IS_TRAIN == 0].drop('IS_TRAIN', axis=1)

    cols = [c for c in train.columns if c != target]
    X_train = train[cols].values
    y_train = train[target].values.reshape(-1, 1)
    X_test = test[cols].values
    y_test = test[target].values.reshape(-1, 1)

    return X_train, y_train, X_test, y_test


def process_deep_feats(df_train, df_test, embedding_cols, cont_cols, target, emb_dim=8, emb_reg=1e-3):
    # 标记训练和测试集,方便特征处理完之后进行训练和测试集的分离
    df_train['IS_TRAIN'] = 1
    df_test['IS_TRAIN'] = 0
    df_deep = pd.concat([df_train, df_test])

    # 拼接数值特征和embedding特征
    deep_cols = embedding_cols + cont_cols
    df_deep = df_deep[deep_cols + [target,'IS_TRAIN']]

    # 数值类特征进行标准化
    scaler = StandardScaler()
    df_deep[cont_cols] = pd.DataFrame(scaler.fit_transform(df_train[cont_cols]), columns=cont_cols)

    # 类边特征编码
    unique_vals = dict()
    lbe = LabelEncoder()
    for feats in embedding_cols:
        df_deep[feats] = lbe.fit_transform(df_deep[feats])
        unique_vals[feats] = df_deep[feats].nunique()

    # 构造模型的输入层,和embedding层,虽然对于连续的特征没有embedding层,但是为了统一,将Reshape层
    # 当成是连续特征的embedding层
    inp_layer = []
    emb_layer = []
    for ec in embedding_cols:
        layer_name = ec + '_inp'
        inp = Input(shape=(1,), dtype='int64', name=layer_name)
        emb = Embedding(unique_vals[ec], emb_dim, input_length=1, embeddings_regularizer=l2(emb_reg))(inp)
        inp_layer.append(inp)
        emb_layer.append(inp)

    for cc in cont_cols:
        layer_name = cc + '_inp'
        inp = Input(shape=(1,), dtype='int64', name=layer_name)
        emb = Reshape((1, 1))(inp)
        inp_layer.append(inp)
        emb_layer.append(inp)

    # 训练和测试集分离
    train = df_deep[df_deep.IS_TRAIN == 1].drop('IS_TRAIN', axis=1)
    test = df_deep[df_deep.IS_TRAIN == 0].drop('IS_TRAIN', axis=1)

    # 提取训练和测试集中的特征
    X_train = [train[c] for c in deep_cols]
    y_train = np.array(train[target].values).reshape(-1, 1)
    X_test = [test[c] for c in deep_cols]
    y_test = np.array(test[target].values).reshape(-1, 1)

    return X_train, y_train, X_test, y_test, emb_layer, inp_layer


def wide_deep(df_train, df_test, wide_cols, x_cols, embedding_cols, cont_cols):
    # wide部分特征处理
    X_train_wide, y_train_wide, X_test_wide, y_test_wide = \
        process_wide_feats(df_train, df_test, wide_cols, x_cols, target)

    # deep部分特征处理
    X_train_deep, y_train_deep, X_test_deep, y_test_deep, deep_inp_embed, deep_inp_layer = \
        process_deep_feats(df_train, df_test, embedding_cols,cont_cols, target)

    # wide特征与deep特征拼接
    X_tr_wd = [X_train_wide] + X_train_deep
    Y_tr_wd = y_train_deep  # wide部分和deep部分的label是一样的
    X_te_wd = [X_test_wide] + X_test_deep
    Y_te_wd = y_test_deep  # wide部分和deep部分的label是一样的

    # wide部分的输入
    w = Input(shape=(X_train_wide.shape[1],), dtype='float32', name='wide')

    # deep部分的NN结构
    d = concatenate(deep_inp_embed)
    d = Flatten()(d)
    d = Dense(50, activation='relu', kernel_regularizer=l1_l2(l1=0.01, l2=0.01))(d)
    d = Dropout(0.5)(d)
    d = Dense(20, activation='relu', name='deep')(d)
    d = Dropout(0.5)(d)

    # 将wide部分与deep部分的输入进行拼接, 然后输入一个线性层
    wd_inp = concatenate([w, d])
    wd_out = Dense(Y_tr_wd.shape[1], activation='sigmoid', name='wide_deep')(wd_inp) 
    
    # 构建模型,这里需要注意模型的输入部分是由wide和deep部分组成的
    wide_deep = Model(inputs=[w] + deep_inp_layer, outputs=wd_out)
    wide_deep.compile(optimizer='Adam', loss='binary_crossentropy', metrics=['AUC'])

    # 设置模型学习率,不设置学习率keras默认的学习率是0.01
    wide_deep.optimizer.lr = 0.001

    # 模型训练
    wide_deep.fit(X_tr_wd, Y_tr_wd, epochs=5, batch_size=128)

    # 模型预测及验证
    results = wide_deep.evaluate(X_te_wd, Y_te_wd)

    print("\n", results)


if __name__ == '__main__':
    # 读取数据
    df_train, df_test = get_data()

    # 特征处理
    df_train, df_test, wide_cols, x_cols, embedding_cols, cont_cols, target = data_process(df_train, df_test)

    # 模型训练
    wide_deep(df_train, df_test, wide_cols, x_cols, embedding_cols, cont_cols)

 

6. 딥 러닝 추천 시스템 개발

서론에서 언급했듯이 Wide & Deep 모델은 딥 러닝 개발에 매우 ​​중요한 역할을하며, 아래 그림에서 후속 모델 개발에 미치는 영향을 확인할 수 있습니다.

 

image-20200923143559968

 

추천

출처blog.csdn.net/yichao_ding/article/details/109317828