下一个功能是用于创建嵌入层。因为“国家/地区”是稀疏的分类特征。此函数将提取要转换为嵌入的特征的字典,以及在此定义的该特征的唯一可能值的列表:
emb_layers= {'Country':list(X_train['Country'].unique())}
我们还稍后将对模型输入进行定义。对于Dimensions参数,我选择遵循默认特征规则。
defadd_emb(emb_layers={},model_inputs={}): emb_inputs= {} emb_features= [] forkey,valueinemb_layers.items(): emb_inputs[key] =model_inputs[key] catg_col=feature_column .categorical_column_with_vocabulary_list(key, value) emb_col=feature_column.embedding_column( catg_col,dimension=int(len(value)**0.25)) emb_features.append(emb_col) emb_layer=layers.DenseFeatures(emb_features) emb_outputs=emb_layer(emb_inputs) returnemb_outputs
在继续使用下一个函数之前,我们需要定义哪些特征需要从不同的特征模型中排除。从根本上讲,我们将要排除要预测的特征(数据泄漏)和用于嵌入的特征。这里还应该小心删除可直接用于计算输出特征的特征。例如,一个预测男女的模型,如果有类似Gender_Female之类的特征那么它基本上可以获得100%的准确率。 为了解决这个问题,我们将从相应的特征模型中排除其他性别,年龄和联系人等特征。
feature_layers= {col:[col,'Country'] forcolinmodel_cols} feature_layers['Gender_Female'] += ['Gender_Male', 'Gender_Transgender'] feature_layers['Gender_Male'] += ['Gender_Female', 'Gender_Transgender'] feature_layers['Gender_Transgender'] += ['Gender_Female', 'Gender_Male'] feature_layers['Age_0-9'] += ['Age_10-19','Age_20-24', 'Age_25-59','Age_60_'] feature_layers['Age_10-19'] += ['Age_0-9','Age_20-24', 'Age_25-59','Age_60_'] feature_layers['Age_20-24'] += ['Age_0-9','Age_10-19', 'Age_25-59','Age_60_'] feature_layers['Age_25-59'] += ['Age_0-9','Age_10-19', 'Age_20-24','Age_60_'] feature_layers['Age_60_'] += ['Age_0-9','Age_10-19', 'Age_20-24','Age_25-59'] feature_layers['Contact_Dont-Know'] += ['Contact_No','Contact_Yes'] feature_layers['Contact_No'] += ['Contact_Dont-Know','Contact_Yes'] feature_layers['Contact_Yes'] += ['Contact_Dont-Know','Contact_No']
我们还想为我们模型添加一个feature_layer:
feature_layers['Severity_Severe_aux'] = ['Country']
现在,我们拥有构建特征模型所需的所有东西。此函数将使用所有输入特征列表,上面定义的排除特征和嵌入字典,在add_model函数中描述的hidden_layer结构以及是否应使用批处理规范的配置等。
首先,该函数将使用TensorFlow方式的读取输入特征。使用这种方法的原因我们只需要定义一次,就可以在每个特征模型中一再地重复使用它们。接下来我们将确定是否定义了任何嵌入列,并创建一个嵌入层(可选)。对于每个特征模型,我们将创建DenseFeatures输入层(不包括上面定义的特征),并使用add_model函数创建一个单独的模型。在返回之前,我们检查循环是否在跳连接模型上运行。如果是这样,我们将附加输入要素,以便最终模型也可以使用原始要素进行训练。最后,此函数将返回模型输入的字典,每个要特征模型输出层的列表以及每个最终隐藏层(即新设计的特征)的列表。
deffeature_models( output_feature=None,all_features=[],feature_layers={}, emb_layers={},hidden_layers=[],batch_norm=False): model_inputs= {} forfeatureinall_features: iffeaturein [kfork,vinemb_layers.items()]: model_inputs[feature] =tf.keras.Input(shape=(1,), name=feature, dtype='string') else: model_inputs[feature] =tf.keras.Input(shape=(1,), name=feature) iflen(emb_layers) >0: emb_outputs=add_emb(emb_layers,model_inputs) output_layers= [] eng_layers= [] forkey,valueinfeature_layers.items(): feature_columns= [feature_column.numeric_column(f) forfinall_featuresiffnotinvalue] feature_layer=layers.DenseFeatures(feature_columns) feature_outputs=feature_layer({k:vfork,vinmodel_inputs.items() ifknotinvalue}) iflen(emb_layers) >0: feature_outputs=layers.concatenate([feature_outputs, emb_outputs]) last_layer, output_layer=add_model( feature_outputs=feature_outputs, hidden_layers=hidden_layers, batch_norm=batch_norm, model_name=key) output_layers.append(output_layer) eng_layers.append(last_layer) ifkey==output_feature+'_aux': eng_layers.append(feature_outputs) returnmodel_inputs, output_layers, eng_layers
如果使用嵌入层,则它将与这些模型的每个输入连接在一起。这意味着这些嵌入不仅可以训练最大化整体模型的精度,还可以训练每一个特征模型。这导致了非常健壮的嵌入。
在进入最终功能之前,让我们定义将要输入的每个参数。以上大多数参数已在上面进行了描述,或对于所有TensorFlow模型都是典型的。例如,patience 参数,当指定时间段内验证准确性没有提高时,可使用该参数停止训练模型。
params= {'all_features': list(X_train.columns), 'output_feature':y_train.columns[0], 'emb_layers':emb_layers, 'feature_layers':feature_layers, 'hidden_layers':[[256,0],[128,0.1],[64,0.2]], 'batch_norm': True, 'learning_rate':0.001, 'patience':3, 'epochs':20 }
对于最终模型,我们将从运行上一个函数开始以生成输入,输出和特征工程的特征。然后,我们将这些层/特征中的每一个串联起来,并将其输入到最终模型中。最后,我们构建,编译,训练和测试模型。
deffinal_model(params,test=True): print(params['batch_norm'],params['hidden_layers']) model_inputs, output_layers, eng_layers=feature_models( all_features=params['all_features'], feature_layers=params['feature_layers'], emb_layers=params['emb_layers'], hidden_layers=params['hidden_layers'], batch_norm=params['batch_norm'], output_feature=params['output_feature']) concat_layer=layers.concatenate(eng_layers) last_layer, output_layer=add_model( feature_outputs=concat_layer, hidden_layers=params['hidden_layers'], batch_norm=params['batch_norm'], model_name=params['output_feature']) output_layers.append(output_layer) model=tf.keras.Model( inputs=[model_inputs], outputs=output_layers) aux_loss_wgt=0.5/len(params['feature_layers']) loss_wgts= [aux_loss_wgtforiinrange(len(params['feature_layers'])) loss_wgts.append(0.5) model.compile(loss='binary_crossentropy', optimizer=tf.keras.optimizers.Adam( lr=params["learning_rate"]), loss_weights=loss_wgts, metrics=['accuracy']) es=tf.keras.callbacks.EarlyStopping( monitor='val_loss',mode='min',verbose=1, patience=params['patience'],restore_best_weights=True) history=model.fit( trainset,validation_data=valset, epochs=params['epochs'], verbose=0, callbacks=[es]) yhat=model.predict(testset) loss=log_loss( np.array(y_test[params['output_feature']]), yhat[-1])**.5print('Binary Crossentropy:',loss) iftest==True: sys.stdout.flush() return {'loss': loss, 'status': STATUS_OK} else: returnhistory, model
需要注意的是,函数的输入之一称为test。这个 输入可以在使用hyperopt求解最佳参数(test = True)或训练并返回最终模型(test = False)之间进行切换。编译模型时,也可能不调整loss_weights参数。因为我们有多个辅助输出,所以我们需要告诉TensorFlow在确定如何调整模型以提高准确性时给每个加权多少。我个人喜欢对辅助预测(总计)给予50%的权重,对目标预测给予50%的权重。有些人可能会觉得给辅助预测赋予任何权重是很奇怪的,因为它们在损失计算步骤中被丢弃了。问题是,如果我们不给它们赋予任何权重,模型多半会忽略它们,从而妨碍它学习有用的特征。
现在,我们只需要使用上面定义的参数来运行final_model:
history, model = final_model(params,test=False)
现在我们有了一个经过训练的模型,我们可以使用keras get_layer()函数选择性地提取要在其他模型中使用的新特性。如果这能引起足够的兴趣,我将把这一步留到以后的文章中讨论。
结果
“它最后战胜了XGBoost,这与传统观点不同,因为传统观点认为梯度增强模型更适合结构化数据集。”
你可以想象,这是一个计算上昂贵的模型训练。但是好消息是它通常会在比典型MLP少得多的试验中收敛到一个更准确的答案。如果你把不花几周时间在繁琐的工程特性上节省下来的时间也算进去,那就快得多了。此外,预测延迟足够小,可以使其成为一个生产模型(与典型的Kaggle 50+元模型相反)。如果你提取特征并用它们重新训练一个神经网络,那么它会变得更快。
问题仍然存在,它是准确的吗?在我应用这个模型的每一个案例中,它都是最准确的。它始终战胜了XGBoost,让我们看看它在这个问题上做得如何!
我测试了三种不同的模型:
- XGBoost
- 带有嵌入的标准MLP
- 上面训练的自动特征模型
对于自动特征模型,我使用hyperopt进行了20个试验,以试验不同的网络规模。对于这两个对比的模型,由于它们的训练时间较快,因此我进行了40次试验。结果如下:
正如预期的那样,我们的自动特征模型表现最好。需要记住的一件事是,这个简单的数据集没有足够的有用信息来允许任何模型比边际收益更好。当我处理数百个特征的海量数据集时,自动功能模型将领先XGBoost击败5–10%也是有可能的。