这段时间在参加比赛,发现有一些比赛上公开的代码,其中的数据预处理步骤值得我们参考。
平常我们见到的都是数据预处理,现在我们来讲一下特征工程跟数据预处理的区别。
-
数据预处理是指对原始数据进行清洗、转换、缩放等操作,以便为后续的建模或分析任务做准备。这包括处理缺失值、异常值、重复值,以及对数据进行归一化、标准化等操作,使数据适合模型处理。
-
特征工程则更侧重于从原始数据中提取、构建或转换特征,以提高模型的性能。这包括特征选择、特征抽取、特征转换等过程。在特征工程中,可以创建新的特征、组合现有特征、进行降维等操作,以便使模型更好地捕捉数据中的模式和关系
训练集
测试集
注:这个代码也可以用在自己的工程项目中,还是比较不错的!也是目前在Kaggle社区里公开代码分数比较高的一个单模型。
在这个代码里,我们将训练使用所有的数据可用的深度学习模型!
预处理: 我把所有训练集的和测试集的smiles编码并保存在这里,这可能需要1个小时的 TPU。
训练与推理: 我使用了一个简单的1dcnn 模型对20个epochs进行训练。
如何改进:
尝试不同的体系结构: 我能够得到0.604的 LB 分数,只需对这个体系结构进行一些小的更改。
尝试另一个模型,如Transformer,或 LSTM。
为更多的epochs而训练。
添加更多特性,比如 bb2或 bb3的热编码。
当然还有 GBDT 模型。
1、导入相应的包
!pip install fastparquet -q
fastparquet
用于读写Parquet格式的数据文件。
2、导入库
import gc
import os
import pickle
import random
import joblib
import numpy as np
import pandas as pd
from tqdm import tqdm
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import average_precision_score as APS
这段代码导入了必要的Python库,包括用于数据处理和建模的常见库,例如pandas、numpy、scikit-learn等。它还使用了tqdm
来显示进度条。
3、定义配置参数
class CFG:PREPROCESS = FalseEPOCHS = 100BATCH_SIZE = 4096LR = 1e-3WD = 0.05NBR_FOLDS = 15SELECTED_FOLDS = [0]SEED = 2024
这里定义了一个名为CFG
的类,用于存储一些全局配置参数。这些参数包括是否进行数据预处理、训练时的迭代次数、批量大小、学习率、权重衰减等。NBR_FOLDS
表示交叉验证的折数,SELECTED_FOLDS
表示选择参与训练的折数,SEED
是随机种子。这里我们表示只选择第一份数据作为验证集,其余14份数据作为训练集。
4、设置随机数种子
import tensorflow as tf
def set_seeds(seed):os.environ['PYTHONHASHSEED'] = str(seed)random.seed(seed)tf.random.set_seed(seed)np.random.seed(seed)set_seeds(seed=CFG.SEED)
set_seeds()
函数用于设置随机种子,确保实验的可重复性。它设置了Python、NumPy、random和TensorFlow的随机种子。
注:str
是用于字符串操作的内置数据类型,而os.environ
是用于管理操作系统环境变量的模块对象。在这段代码中,str(seed)
用于将整数转换为字符串,os.environ['PYTHONHASHSEED']
用于设置Python的哈希种子。
5、分布式策略
import tensorflow as tf# Detect hardware, return appropriate distribution strategy
try:tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect(tpu="local") # "local" for 1VM TPUstrategy = tf.distribute.TPUStrategy(tpu)print("Running on TPU")print("REPLICAS: ", strategy.num_replicas_in_sync)
except tf.errors.NotFoundError:print("Not on TPU")
这段代码尝试连接到TPU(如果可用),并创建了一个分布式策略strategy
,以便在TPU上进行训练。如果没有TPU可用,则打印"Not on TPU"。strategy.num_replicas_in_sync
用于获取分布式策略中的副本数。
tf.distribute.cluster_resolver.TPUClusterResolver
是 TensorFlow 中用于连接 TPU(Tensor Processing Unit,张量处理单元)集群的类。下面是对其功能的解释:
-
连接到TPU集群:
TPUClusterResolver
的主要作用是连接到 TPU 集群,以便在该集群上执行 TensorFlow 计算任务。 -
**指定TPU名称:**你可以通过
TPUClusterResolver
类来指定要连接的 TPU 的名称,可以是本地的 TPU(local),也可以是远程的 TPU。例如,在上述代码中,使用了tpu="local"
来指定连接到本地的 TPU。 -
**解析TPU集群地址:**除了连接到指定的 TPU 外,
TPUClusterResolver
还可以解析 TPU 集群的地址,以便连接到指定地址的 TPU 集群。这对于连接到远程的 TPU 集群非常有用。 -
**获取TPU实例信息:**一旦连接到了 TPU 集群,
TPUClusterResolver
还可以提供关于 TPU 实例的信息,例如 TPU 的数量、类型等。
在上述代码中,TPUClusterResolver
被用于连接到本地的 TPU(tpu="local"
)。如果连接成功,将返回一个 TPUClusterResolver
对象,否则会抛出异常。连接成功后,可以使用该对象来获取有关 TPU 集群的信息,并将其用于 TensorFlow 的分布式计算任务。我这里用的是云服务器上的TPU。
tf.distribute.TPUStrategy
是 TensorFlow 中专门用于在 TPU 上进行分布式训练的策略。类似地,TensorFlow 也提供了用于在 CPU 和 GPU 上进行分布式训练的相应策略。
-
**
tf.distribute.MirroredStrategy
:**用于在多个 GPU 上进行分布式训练。每个 GPU 上的模型副本会在各自的 GPU 上运行,同时使用 AllReduce 算法来进行梯度聚合和参数同步。 -
**
tf.distribute.MultiWorkerMirroredStrategy
:**用于在多个工作节点(通常是多台机器)的多个 GPU 上进行分布式训练。它在MirroredStrategy
的基础上增加了多个工作节点之间的通信和同步。 -
**
tf.distribute.experimental.MultiDeviceMirroredStrategy
:**用于在多个 GPU 或 CPU 上进行分布式训练。与MirroredStrategy
类似,但允许跨设备进行梯度和参数同步。 -
**
tf.distribute.experimental.CentralStorageStrategy
:**用于在多个工作节点的多个 GPU 或 CPU 上进行分布式训练。与MirroredStrategy
类似,但使用中心化参数服务器来进行梯度聚合和参数同步。
这些策略都允许在不同的硬件设备上进行分布式训练,包括 CPU、GPU 和 TPU。选择合适的策略取决于你的硬件环境、任务需求和性能要求。例如,如果你拥有多个 GPU,并且想要在这些 GPU 上进行训练,可以选择 MirroredStrategy
;如果你想要在多台机器上进行训练,则可以选择 MultiWorkerMirroredStrategy
。
6、数据预处理
if CFG.PREPROCESS:enc = {'l': 1, 'y': 2, '@': 3, '3': 4, 'H': 5, 'S': 6, 'F': 7, 'C': 8, 'r': 9, 's': 10, '/': 11, 'c': 12, 'o': 13,'+': 14, 'I': 15, '5': 16, '(': 17, '2': 18, ')': 19, '9': 20, 'i': 21, '#': 22, '6': 23, '8': 24, '4': 25, '=': 26,'1': 27, 'O': 28, '[': 29, 'D': 30, 'B': 31, ']': 32, 'N': 33, '7': 34, 'n': 35, '-': 36}train_raw = pd.read_parquet('/kaggle/input/leash-BELKA/train.parquet')smiles = train_raw[train_raw['protein_name']=='BRD4']['molecule_smiles'].valuesassert (smiles!=train_raw[train_raw['protein_name']=='HSA']['molecule_smiles'].values).sum() == 0assert (smiles!=train_raw[train_raw['protein_name']=='sEH']['molecule_smiles'].values).sum() == 0def encode_smile(smile):tmp = [enc[i] for i in smile]tmp = tmp + [0]*(142-len(tmp))return np.array(tmp).astype(np.uint8)smiles_enc = joblib.Parallel(n_jobs=96)(joblib.delayed(encode_smile)(smile) for smile in tqdm(smiles))smiles_enc = np.stack(smiles_enc)train = pd.DataFrame(smiles_enc, columns = [f'enc{i}' for i in range(142)])train['bind1'] = train_raw[train_raw['protein_name']=='BRD4']['binds'].valuestrain['bind2'] = train_raw[train_raw['protein_name']=='HSA']['binds'].valuestrain['bind3'] = train_raw[train_raw['protein_name']=='sEH']['binds'].valuestrain.to_parquet('train_enc.parquet')test_raw = pd.read_parquet('/kaggle/input/leash-BELKA/test.parquet')smiles = test_raw['molecule_smiles'].valuessmiles_enc = joblib.Parallel(n_jobs=96)(joblib.delayed(encode_smile)(smile) for smile in tqdm(smiles))smiles_enc = np.stack(smiles_enc)test = pd.DataFrame(smiles_enc, columns = [f'enc{i}' for i in range(142)])test.to_parquet('test_enc.parquet')else:train = pd.read_parquet('/kaggle/input/belka-enc-dataset/train_enc.parquet')test = pd.read_parquet('/kaggle/input/belka-enc-dataset/test_enc.parquet')
如果CFG.PREPROCESS
为True,则执行数据预处理。否则,直接从parquet文件中读取处理好的训练和测试数据。
enc = {...}
:这里定义了一个字典 enc
,将 SMILES 字符串中的字符映射为整数编码。这个编码过程是为了将 SMILES 字符串转换为数字序列。
train_raw[train_raw['protein_name']=='BRD4']
这部分代码实际上是在利用 Pandas DataFrame 的索引功能。让我们解释一下它是如何工作的:
-
train_raw['protein_name']=='BRD4'
:这部分代码首先选择了train_raw
DataFrame 中的protein_name
列,并将其与字符串'BRD4'
进行比较。这将返回一个布尔数组,其中对应的位置为 True 表示该位置对应的蛋白质名称为 'BRD4',否则为 False。例如,如果train_raw['protein_name']
的值分别是['BRD4', 'HSA', 'BRD4', 'sEH', 'HSA']
,那么上述代码返回的布尔数组就是[True, False, True, False, False]
。 -
train_raw[train_raw['protein_name']=='BRD4']
:这部分代码将上一步得到的布尔数组作为索引,从train_raw
DataFrame 中选择出满足条件的行。具体来说,它保留了布尔数组中对应位置为 True 的行,而过滤掉了对应位置为 False 的行。这样,就实现了根据条件筛选数据的目的。
综上所述,train_raw[train_raw['protein_name']=='BRD4']
的含义是:从 train_raw
DataFrame 中选择出满足条件(蛋白质名称为 'BRD4')的所有行。
np.stack()
函数的作用是沿着新的轴堆叠数组序列。当你有一系列形状相同的数组时,你可以使用 np.stack()
将它们沿着新的轴堆叠起来,形成一个新的数组。
7、定义神经网络模型
def my_model():with strategy.scope():INP_LEN = 142NUM_FILTERS = 32hidden_dim = 128inputs = tf.keras.layers.Input(shape=(INP_LEN,), dtype='int32')x = tf.keras.layers.Embedding(input_dim=36, output_dim=hidden_dim, input_length=INP_LEN, mask_zero = True)(inputs)x = tf.keras.layers.Conv1D(filters=NUM_FILTERS, kernel_size=3, activation='relu', padding='valid', strides=1)(x)x = tf.keras.layers.Conv1D(filters=NUM_FILTERS*2, kernel_size=3, activation='relu', padding='valid', strides=1)(x)x = tf.keras.layers.Conv1D(filters=NUM_FILTERS*3, kernel_size=3, activation='relu', padding='valid', strides=1)(x)x = tf.keras.layers.GlobalMaxPooling1D()(x)x = tf.keras.layers.Dense(1024, activation='relu')(x)x = tf.keras.layers.Dropout(0.1)(x)x = tf.keras.layers.Dense(1024, activation='relu')(x)x = tf.keras.layers.Dropout(0.1)(x)x = tf.keras.layers.Dense(512, activation='relu')(x)x = tf.keras.layers.Dropout(0.1)(x)outputs = tf.keras.layers.Dense(3, activation='sigmoid')(x)model = tf.keras.models.Model(inputs = inputs, outputs = outputs)optimizer = tf.keras.optimizers.Adam(learning_rate=CFG.LR, weight_decay = CFG.WD)loss = 'binary_crossentropy'weighted_metrics = [tf.keras.metrics.AUC(curve='PR', name = 'avg_precision')]model.compile(loss=loss,optimizer=optimizer,weighted_metrics=weighted_metrics,)return model
模型详解
with strategy.scope():
TensorFlow 中的一种分布式策略,strategy.scope()
用于在分布式环境中构建模型。
INP_LEN = 142NUM_FILTERS = 32hidden_dim = 128
定义了一些常量和超参数,如输入序列长度 INP_LEN
、卷积层滤波器数量 NUM_FILTERS
和嵌入层的隐藏维度 hidden_dim
。
inputs = tf.keras.layers.Input(shape=(INP_LEN,), dtype='int32')
定义了模型的输入层,指定了输入序列的形状和数据类型。
x = tf.keras.layers.Embedding(input_dim=36, output_dim=hidden_dim, input_length=INP_LEN, mask_zero=True)(inputs)
这是一个嵌入层,用于将输入的离散特征(SMILES 编码)映射到稠密的低维空间。
x = tf.keras.layers.Conv1D(filters=NUM_FILTERS, kernel_size=3, activation='relu', padding='valid', strides=1)(x)x = tf.keras.layers.Conv1D(filters=NUM_FILTERS*2, kernel_size=3, activation='relu', padding='valid', strides=1)(x)x = tf.keras.layers.Conv1D(filters=NUM_FILTERS*3, kernel_size=3, activation='relu', padding='valid', strides=1)(x)
一系列卷积层,用于从输入序列中提取特征。
x = tf.keras.layers.GlobalMaxPooling1D()(x)
全局最大池化层,用于从卷积层输出的特征图中提取最显著的特征。
x = tf.keras.layers.Dense(1024, activation='relu')(x)x = tf.keras.layers.Dropout(0.1)(x)x = tf.keras.layers.Dense(1024, activation='relu')(x)x = tf.keras.layers.Dropout(0.1)(x)x = tf.keras.layers.Dense(512, activation='relu')(x)x = tf.keras.layers.Dropout(0.1)(x)
一系列全连接层,用于学习特征之间的非线性关系。
outputs = tf.keras.layers.Dense(3, activation='sigmoid')(x)
模型的输出层,输出预测的概率。
model = tf.keras.models.Model(inputs=inputs, outputs=outputs)
将输入和输出连接成模型。
optimizer = tf.keras.optimizers.Adam(learning_rate=CFG.LR, weight_decay=CFG.WD)
定义优化器,这里使用 Adam 优化器。
loss = 'binary_crossentropy'
定义损失函数,这里使用二元交叉熵损失函数。
weighted_metrics = [tf.keras.metrics.AUC(curve='PR', name='avg_precision')]
定义评估指标,这里使用了平均精度 (Average Precision)。
model.compile(loss=loss, optimizer=optimizer, weighted_metrics=weighted_metrics)
编译模型,将优化器、损失函数和评估指标配置到模型中。
return model
返回构建好的模型对象。
8、交叉验证循环
FEATURES = [f'enc{i}' for i in range(142)]
TARGETS = ['bind1', 'bind2', 'bind3']
skf = StratifiedKFold(n_splits = CFG.NBR_FOLDS, shuffle = True, random_state = 42)
#定义了特征列的名称和目标列的名称,以及使用Stratified K-Fold进行交叉验证的划分。
all_preds = []
for fold,(train_idx, valid_idx) in enumerate(skf.split(train, train[TARGETS].sum(1))):if fold not in CFG.SELECTED_FOLDS:continue;X_train = train.loc[train_idx, FEATURES]y_train = train.loc[train_idx, TARGETS]X_val = train.loc[valid_idx, FEATURES]y_val = train.loc[valid_idx, TARGETS]es = tf.keras.callbacks.EarlyStopping(patience=5, monitor="val_loss", mode='min', verbose=1)checkpoint = tf.keras.callbacks.ModelCheckpoint(monitor='val_loss', filepath=f"model-{fold}.h5",save_best_only=True, save_weights_only=True,mode='min')reduce_lr_loss = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.05, patience=5, verbose=1)model = my_model()history = model.fit(X_train, y_train,validation_data=(X_val, y_val),epochs=CFG.EPOCHS,callbacks=[checkpoint, reduce_lr_loss, es],batch_size=CFG.BATCH_SIZE,verbose=1,)model.load_weights(f"model-{fold}.h5")oof = model.predict(X_val, batch_size = 2*CFG.BATCH_SIZE)print('fold :', fold, 'CV score =', APS(y_val, oof, average = 'micro'))preds = model.predict(test, batch_size = 2*CFG.BATCH_SIZE)all_preds.append(preds)preds = np.mean(all_preds, 0)
这是交叉验证的主要循环。在每个折中,根据SELECTED_FOLDS
选择指定的折数进行训练和预测。在每个折数中,首先选择对应的训练和验证数据,然后建立模型并进行训练。训练完成后,使用模型对测试集进行预测,并将预测结果存储在all_preds
列表中。
代码详解
定义特征和目标列:
FEATURES = [f'enc{i}' for i in range(142)]
TARGETS = ['bind1', 'bind2', 'bind3']
这里,FEATURES
是一个包含特征列名称的列表,TARGETS
是一个包含目标列名称的列表。
Stratified K-Fold 交叉验证:
skf = StratifiedKFold(n_splits=CFG.NBR_FOLDS, shuffle=True, random_state=42)
这段代码初始化了一个 Stratified K-Fold 对象,使用了由 CFG.NBR_FOLDS
指定的折数。Stratified K-Fold 保持每个折中类别的分布,以确保每个折都代表整体数据集。
模型训练:
all_preds = []
for fold, (train_idx, valid_idx) in enumerate(skf.split(train, train[TARGETS].sum(1))):if fold not in CFG.SELECTED_FOLDS:continueX_train = train.loc[train_idx, FEATURES]y_train = train.loc[train_idx, TARGETS]X_val = train.loc[valid_idx, FEATURES]y_val = train.loc[valid_idx, TARGETS]
这个循环遍历了 Stratified K-Fold 对象生成的每个折。它将数据分割成当前折的训练集和验证集。
回调函数:
es = tf.keras.callbacks.EarlyStopping(patience=5, monitor="val_loss", mode='min', verbose=1)
checkpoint = tf.keras.callbacks.ModelCheckpoint(monitor='val_loss', filepath=f"model-{fold}.h5",save_best_only=True, save_weights_only=True,mode='min')
reduce_lr_loss = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.05, patience=5, verbose=1)
这些是在模型训练过程中使用的回调函数。EarlyStopping
在监控的指标停止改善时停止训练。ModelCheckpoint
如果是到目前为止最佳的模型,则在每个周期后保存模型。ReduceLROnPlateau
在监控的指标停止改善时降低学习率。
模型训练(继续):
model = my_model()
history = model.fit(X_train, y_train,validation_data=(X_val, y_val),epochs=CFG.EPOCHS,callbacks=[checkpoint, reduce_lr_loss, es],batch_size=CFG.BATCH_SIZE,verbose=1,
)
这段代码使用训练数据(X_train
和 y_train
)训练模型。它在验证数据上(X_val
和 y_val
)验证模型的性能。回调函数用于监控训练过程。
加载最佳模型:
model.load_weights(f"model-{fold}.h5")
训练后,从 ModelCheckpoint
保存的文件中加载最佳模型的权重。
预测和评估:
oof = model.predict(X_val, batch_size=2*CFG.BATCH_SIZE)
print('fold :', fold, 'CV score =', APS(y_val, oof, average='micro'))
对验证集(X_val
)进行预测。使用自定义函数 APS
(此处未显示)评估模型的性能。
最终预测:
preds = np.mean(all_preds, 0)
在所有折训练和验证完成后,对预测进行平均以获得测试数据的最终预测。这些预测存储在 preds
变量中。
9、生成预测结果
tst = pd.read_parquet('/kaggle/input/leash-BELKA/test.parquet')
tst['binds'] = 0
tst.loc[tst['protein_name']=='BRD4', 'binds'] = preds[(tst['protein_name']=='BRD4').values, 0]
tst.loc[tst['protein_name']=='HSA', 'binds'] = preds[(tst['protein_name']=='HSA').values, 1]
tst.loc[tst['protein_name']=='sEH', 'binds'] = preds[(tst['protein_name']=='sEH').values, 2]
tst[['id', 'binds']].to_csv('submission.csv', index = False)
原文地址:BELKA 1DCNN Starter with all data (kaggle.com)