表格二分类AutoDL:神经网络架构搜索与预处理优化咨询
表格数据二分类自动深度学习项目开发记录
项目当前进展
我目前正在基于Python开发面向表格数据二分类任务的自动深度学习项目,已经实现了缺失值处理、变量编码等全流程预处理自动化,处理完成的数据可以直接输入神经网络训练,但暂时还没掌握针对特定任务自动搜索最优神经网络架构的实现方法。
项目初始功能验证使用的是公开的台湾信用卡客户违约数据集。
已实现的完整预处理代码
import pandas as pd pd.set_option('display.max_rows', None) import numpy as np from sklearn.utils import resample from sklearn.model_selection import train_test_split from sklearn.preprocessing import MinMaxScaler, LabelEncoder import category_encoders as ce df=pd.read_csv(".\Taiwan_Credit_Card_Clients\default_of_credit_card_clients.csv") #print(df.head()) #print(df.shape) def column_types(data): # 按数据类型划分DataFrame列 a=data.select_dtypes(include=['int64','float64']).columns numer_col=a.tolist() b=data.select_dtypes(include=['object','bool','category']).columns categ_col=b.tolist() c=data.select_dtypes(include=['datetime64','timedelta64']).columns date_col=c.tolist() output={} output["categorical_columns"]=categ_col output["numerical_columns"]=numer_col output["date_columns"]=date_col return output #print(column_types(df)) def columns_to_drop(data): s=column_types(data) # 确定需要丢弃的列 unique_val=data[s["numerical_columns"]].nunique() col_to_drop = unique_val.loc[unique_val.values==1].index.tolist() # 移除每条数据唯一的列(比如ID类字段) for col in data.columns: if df.shape[0]==df[col].nunique(): col_to_drop.append(col) if len(s["date_columns"])!=0: col_to_drop.append(s["date_columns"]) return col_to_drop #print(columns_to_drop(df)) # 丢弃无用列 def drop_un_columns(data): data = data.drop(columns_to_drop(data),axis=1) return data #a=drop_un_columns(df) #print(a.head()) #print(df.isna().sum()) # 缺失值填充 def handle_miss(data): s=column_types(data) if data.isnull().values.any()==True: # 数值列用均值填充 for col in s["numerical_columns"]: data[col].fillna(data[col].mean(),inplace=True) # 类别列用众数填充 for col in s["categorical_columns"]: data[col].fillna(data[col].mode()[0],inplace=True) return data #non_miss_df=handle_miss(df) #print(non_miss_df.head()) #print(non_miss_df.isna().sum()) # 数据不平衡处理(上采样) def handle_imb_under(data,target): target_vals=list(data[target].value_counts().to_dict().keys()) # 区分多数类和少数类 numb_1st_class=data[target].value_counts().to_dict()[target_vals[0]] numb_2nd_class=data[target].value_counts().to_dict()[target_vals[1]] # 类别样本数差超过20时触发不平衡处理 if abs(numb_1st_class-numb_2nd_class)>20: if numb_1st_class<=numb_2nd_class: df_majority = data[(data[target]==target_vals[0])] df_minority = data[(data[target]==target_vals[1])] else: df_majority = data[(data[target]==target_vals[1])] df_minority = data[(data[target]==target_vals[0])] # 少数类上采样 df_minority_upsampled = resample(df_minority, replace=True, # 有放回抽样 n_samples= len(df_majority), # 和多数类样本数对齐 random_state=42) # 固定随机种子保证可复现 # 合并上采样后的少数类和多数类 df_undersampled = pd.concat([df_minority_upsampled, df_majority]) ## 也可以用SMOTE方法: #X, y = SMOTE().fit_resample(list(x_y(data,target)[0], list(x_y(data,target)[1]))) #X_resampled, y_resampled = SMOTE().fit_resample(X, y) return df_undersampled b=handle_imb_under(df,'default payment next month') #print(b.shape) # 拆分特征和标签 def x_y(data,target): X = data.loc[:, data.columns!=target] y = data[[target]] return (X,y) a=x_y(b,'default payment next month') #print(a[0].shape) #print(a[1].shape) #print(a[1].value_counts()) # 拆分训练集和验证集 def train_val(x,y): X_train, X_val, y_train, y_val = train_test_split(x, y, test_size=0.2, random_state=42, shuffle=True) return(X_train, X_val, y_train, y_val) c=train_val(a[0],a[1]) #print(c[0].head()) #print(c[0].shape) #print(c[1].head()) #print(c[1].shape) #print(c[2].head()) #print(c[2].shape) #print(c[3].head()) #print(c[3].shape) ################## 对每列做MinMax归一化 # 对数值特征应用MinMaxScaler def minmaxscaler(xtrain,xval): xtrain.reset_index(drop=True,inplace=True) xval.reset_index(drop=True,inplace=True) cols=column_types(xtrain) num_cols=cols["numerical_columns"] cat_cols=cols["categorical_columns"] scaler = MinMaxScaler() X_train_cat=xtrain[cat_cols] X_train_num = scaler.fit_transform(xtrain[num_cols]) X_train_num=pd.DataFrame(X_train_num,columns=num_cols) X_train=pd.concat([X_train_num,X_train_cat],axis=1) X_val_num = scaler.transform(xval[num_cols]) X_val_cat=xval[cat_cols] X_val_num=pd.DataFrame(X_val_num,columns=num_cols) X_val=pd.concat([X_val_num,X_val_cat],axis=1) return X_train,X_val print(minmaxscaler(c[0],c[1])[0].head()) print(minmaxscaler(c[0],c[1])[1].head()) x_train_scaled=minmaxscaler(c[0],c[1])[0] x_val_scaled=minmaxscaler(c[0],c[1])[1] def encode(a): le = LabelEncoder() le.fit(a) le.transform(a) def lab_encode(ytrain,yval,target): ytrain_encoder=ytrain yval_encoder=yval if ytrain[target].dtype not in ['int64','float64']: le = LabelEncoder() le.fit(ytrain) ytrain_encoder=le.transform(ytrain) ytrain_encoder=pd.DataFrame(ytrain_encoder,columns=[target]) yval_encoder=le.transform(yval) yval_encoder=pd.DataFrame(yval_encoder,columns=[target]) else: print('////////////////') return ytrain_encoder,yval_encoder #print(lab_encode(c[2],c[3],'default payment next month')) def encode_cat(xtrain,xval): # 创建独热编码器对象 s=column_types(xtrain) OHE = ce.OneHotEncoder(cols=s["categorical_columns"],use_cat_names=True) # 对类别变量编码 xtrain_encoder = OHE.fit_transform(xtrain) xval_encoder=OHE.transform(xval) return xtrain_encoder, xval_encoder
现有预处理流程评估与优化建议
现有流程合理性
当前实现覆盖了表格建模最核心的预处理环节:自动识别列类型、丢弃无效列、缺失值填充、类别不平衡处理、训练验证集拆分、数值归一化、类别编码,基础逻辑通顺,处理后的数据确实可以直接输入全连接神经网络做训练,作为初始验证版本是合格的。
可优化的问题点
- 存在数据泄露风险:当前流程先做上采样、缺失值填充,再拆分训练集和验证集,会把验证集的信息泄露到训练过程中,导致验证集效果虚高。正确顺序应该是先拆分训练/验证/测试集,所有预处理逻辑(填充、归一化、编码、采样)都只在训练集上拟合,再应用到验证/测试集上。
- 列类型识别逻辑粗糙:只靠pandas默认dtype区分数值/类别列不准,很多表格里的数值列实际是离散类别(比如性别用0/1存储、教育水平用1-4编码),这些列如果被当成数值列做归一化会损失语义信息。需要加一层判断:比如数值列唯一值少于10个就归为类别列处理。
- 代码存在显性bug:
columns_to_drop函数里判断ID类列的时候用了全局变量df而不是传入的参数data,后续传入其他数据集会直接报错;handle_imb_under函数里如果类别样本差不满足触发阈值,df_undersampled变量没有定义,直接返回会抛出变量不存在的错误。 - 无效列处理逻辑不合理:日期列直接全部丢弃太粗暴,可以提取年、月、日、是否工作日、距离当前时间差等时间特征再使用;高缺失率(比如缺失超过70%)的列直接丢弃比强行填充效果更好。
- 缺失值处理策略单一:所有数值列直接用均值填充对偏态分布、有异常值的列效果很差,应该优先用中位数填充,或者增加缺失值标记列记录哪些值是填充的,给模型提供额外信息。
- 类别不平衡处理逻辑不严谨:函数名叫
handle_imb_under实际实现的是上采样,命名和逻辑不符;样本数差超过20就触发采样的阈值完全不合理,应该用不平衡比例(比如多数类样本量是少数类的2倍以上)判断;另外随机上采样容易过拟合,优先用SMOTE这类合成采样方法,或者不做采样直接在训练时用class weight调整损失权重。 - 编码逻辑有缺陷:标签编码函数
lab_encode里le.fit(ytrain)之后直接le.transform(yval),如果验证集出现训练集没有的标签类别会直接报错;所有类别列直接用独热编码,遇到高基数类别列(比如地址、职业这类有上百个取值的列)会导致维度爆炸,应该根据基数大小选择编码方式:低基数用独热,高基数用目标编码、序数编码。 - 归一化逻辑鲁棒性差:MinMaxScaler对异常值非常敏感,如果数值列有离群点会把大部分正常值压缩到很小的区间,应该加异常值截断逻辑,或者对偏态列先做对数变换再归一化,也可以换成对异常值更鲁棒的StandardScaler做效果对比。
- 流程复用性差:当前代码是脚本式写法,预处理步骤没有封装成可以持久化的流水线,每次处理新数据都要重新跑一遍逻辑,也没法保存编码器、归一化参数到生产环境部署。
表格数据神经网络架构自动搜索实现方向
针对表格二分类任务做神经网络架构搜索不用从零开始实现,可以从三个难度层级落地:
- 入门级:固定搜索空间用超参优化框架搜索:先把表格MLP的超参数定义成搜索空间:隐藏层数量(1-5层)、每层神经元数(32/64/128/256/512)、激活函数(ReLU/GELU/Swish)、Dropout比例(0-0.5)、学习率(1e-4到1e-2)、是否用BatchNorm/LayerNorm,直接用Optuna、Hyperopt这类超参优化框架遍历搜索,选验证集AUC最高的架构即可,实现成本最低,对表格任务效果足够好。
- 进阶级:复用成熟的表格自动ML方案:可以基于AutoGluon、AutoKeras、NNI这些自动机器学习框架内置的表格神经网络搜索模块,这些框架已经实现了针对表格数据优化的搜索空间(比如TabNet、FT-Transformer、MLP的混合架构搜索),只需要传入处理好的特征和标签就能自动搜索最优架构,不用自己写搜索逻辑。
- 定制级:实现轻量级可微架构搜索:如果要自己实现搜索逻辑,可以针对表格数据设计搜索空间:比如每层可选的操作(线性层+激活、线性层+激活+Dropout、残差连接、门控线性单元),把架构参数和网络权重一起用梯度下降训练,训练完成后剪枝保留权重最高的操作组成最终网络,适合做定制化优化。
内容的提问来源于stack exchange,提问作者Abdelkabir MENANI
相关产品推荐
相关产品推荐

