书接上回: 从零入门 AI for Science(AI+药物) #Datawhale AI 夏令营
Task2
前面了解了赛题,这个主要讲baseline代码,入门RNN和特征工程
解读官方baseline
set_random_seed
统一设置随机种子
def set_random_seed(seed):
"""
设置随机种子,确保结果可复现。
参数:
seed (int): 随机种子值。
返回:
无
"""
np.random.seed(seed) # 设置NumPy的随机种子
random.seed(seed) # 设置Python内置的随机数生成器的种子
torch.manual_seed(seed) # 设置PyTorch的随机种子
torch.cuda.manual_seed(seed) # 设置CUDA的随机种子
torch.cuda.manual_seed_all(seed) # 设置所有CUDA设备的随机种子
torch.backends.cudnn.deterministic = True # 确保每次卷积算法选择都是确定的
torch.backends.cudnn.benchmark = False # 关闭CuDNN自动优化功能,确保结果可复现
这里做了这些操作
- 设置NumPy的随机种子
- 设置Python内置的随机数生成器的种子
- 设置PyTorch的随机种子
- 设置CUDA的随机种子
- 设置所有CUDA设备的随机种子
- 确保每次卷积算法选择是确定的
- 关闭CuDNN自动优化功能
就是把每一个自动优化或随机种子的选项都关掉了,然后确保结果不会因为自动优化或随机数而改变,因而可以复现结果。
SiRNADataset
class SiRNADataset(Dataset):
def __init__(self, df, columns, vocab, tokenizer, max_len, is_test=False):
"""
初始化SiRNADataset类
参数:
df (DataFrame): 包含数据的数据框
columns (list): 包含序列的列名列表
vocab (Vocab): 词汇表
tokenizer (Tokenizer): 分词器
max_len (int): 最大序列长度
is_test (bool, optional): 是否是测试集,默认为False
"""
self.df = df
self.columns = columns
self.vocab = vocab
self.tokenizer = tokenizer
self.max_len = max_len
self.is_test = is_test
def __len__(self):
"""
返回数据集的长度
"""
return len(self.df)
def __getitem__(self, idx):
"""
获取数据集中的第idx个样本
参数:
idx (int): 样本索引
返回:
seqs (list): 编码后的序列列表
target (tensor): 目标值张量(仅在非测试集模式下)
"""
row = self.df.iloc[idx]
seqs = [self.tokenize_and_encode(row[col]) for col in self.columns]
if self.is_test:
return seqs
else:
target = torch.tensor(row['mRNA_remaining_pct'], dtype=torch.float)
return seqs, target
def tokenize_and_encode(self, seq):
"""
对序列进行分词和编码
参数:
seq (str): 待处理的序列
返回:
encoded_seq (tensor): 编码后的序列张量
"""
if ' ' in seq:
tokens = seq.split() # 如果序列中包含空格,则按空格分词
else:
tokens = self.tokenizer.tokenize(seq) # 否则使用分词器进行分词
encoded = [self.vocab.stoi.get(token, 0) for token in tokens] # 将分词后的每个词编码为对应的索引
padded = encoded + [0] * (self.max_len - len(encoded)) # 将序列补齐到最大长度
return torch.tensor(padded[:self.max_len], dtype=torch.long)
定义了一个SiRNADataset类来创建一个自定义的PyTorch数据集对象。
目的是将输入的数据框(df)中的序列数据分词、编码和填充,并返回编码后的序列和目标值。
SiRNADataset类的方法
初始化方法:
接受数据并处理成对象属性
接收了下面这些数据并保存为对象的属性:
1. 接收数据框(df)
2. 包含序列的列名(columns)
3. 词汇表(vocab)
4. 分词器(tokenizer)
5. 最大序列长度(max_len)
6. 否为测试集(is_test)
__len__方法
返回数据框中的样本数量。
__getitem__方法
根据给定的索引(idx),获取数据集中的第idx个样本。
首先根据索引获取数据框中的一行数据,然后对每一列的序列数据进行分词和编码。
- 如果是测试集模式(is_test为True),则返回编码后的序列。
- 如果不是测试集模式,则将目标值转换为张量,并返回编码后的序列和目标值。
tokenize_and_encode方法
接收一个序列(seq,这个就是我们要处理的序列)作为输入,根据序列是否包含空格,选择不同的方式分词。
这里有两种分词方法:
- 包含空格的序列,将其按空格进行分词; (这个就是对modified_siRNA_antisense_seq_list(modified_xxxx) 的数据,它本身已经根据空格分好了)
- 常规序列,使用指定的分词器进行分词。
然后,将分词后的token转换为词汇表中对应的索引,未知的token使用索引0(代表 < p a d > <pad> <pad>)。最后将编码后的序列填充到最大长度,返回张量格式的序列。
SiRNAModel 类
class SiRNAModel(nn.Module):
def __init__(self, vocab_size, embed_dim=200, hidden_dim=256, n_layers=3, dropout=0.5):
"""
初始化SiRNA模型
参数:
vocab_size (int): 词汇表大小
embed_dim (int): 嵌入维度 (默认值: 200)
hidden_dim (int): 隐藏层维度 (默认值: 256)
n_layers (int): GRU层的层数 (默认值: 3)
dropout (float): Dropout层的丢弃率 (默认值: 0.5)
"""
super(SiRNAModel, self).__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0) # 初始化嵌入层
self.gru = nn.GRU(embed_dim, hidden_dim, n_layers, bidirectional=True, batch_first=True, dropout=dropout) # 初始化GRU层
self.fc = nn.Linear(hidden_dim * 4, 1) # 初始化全连接层
self.dropout = nn.Dropout(dropout) # 初始化Dropout层
def forward(self, x):
"""
前向传播函数
参数:
x (List[Tensor]): 输入序列列表
返回:
Tensor: 模型的输出张量
"""
embedded = [self.embedding(seq) for seq in x] # 将输入序列传入嵌入层
outputs = []
for embed in embedded:
x, _ = self.gru(embed) # 传入GRU层
x = self.dropout(x[:, -1, :]) # 取最后一个隐藏状态,并进行dropout处理
outputs.append(x)
x = torch.cat(outputs, dim=1) # 将所有序列的输出拼接起来
x = self.fc(x) # 传入全连接层
return x.squeeze() # 返回结果
这个类继承自nn.Module类,用来处理RNA序列。
- 先将输入序列列表x传入嵌入层,
- 然后通过循环将每个序列的嵌入向量传入双向GRU层,取最后一个隐藏状态,并进行dropout处理。
- 最后,将所有序列的输出拼接起来,并传入一个全连接层,输出一个标量结果。
nn.Module类
nn.Module类是PyTorch中所有神经网络模型的基类,提供了一些基本的功能和方法,用于定义和管理神经网络模型的结构和参数。
nn.Module类的作用有:
定义模型的结构
通过__init__方法中定义各个层和模块,可以将不同的层组合在一起,构建出模型的结构。
前向传播函数
通过forward方法中定义前向传播的过程,可以将输入数据在模型中传递,计算输出结果。
参数管理
nn.Module类提供了一些方法,如parameters()
和named_parameters()
,可以自动追踪模型中所有的可学习参数,可以方便地进行参数的访问和管理。
parameters()
parameters()
方法返回一个迭代器,该迭代器会遍历模型中的所有可学习参数。
可学习参数是指那些需要在训练过程中进行优化调整的参数,例如神经网络中的权重和偏置项。
model = SiRNAModel(...)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
named_parameters()
named_parameters()
方法返回一个迭代器,该迭代器会遍历模型中的所有可学习参数,并为每个参数附上一个名称。
这个方法在调试和模型分析时常见,可以方便地查看每个参数的名称和对应的数值。也可以利用这个方法来选择性地冻结或更新某些参数。
for name, param in model.named_parameters():
if 'embedding' in name:
param.requires_grad = False # 冻结嵌入层的参数
模型保存和加载
nn.Module类提供了方法,如
state_dict()
和load_state_dict()
,可以方便地保存模型的状态和加载已保存的状态。
继承自nn.Module类的子类可以自由定义自己的网络结构,并且可以利用nn.Module提供的方法和功能来管理参数和实现前向传播过程。
还可以与优化器、损失函数、数据加载器等,进一步提升模型的训练和使用效果。
state_dict()
state_dict()
(状态字典)是一个Python字典对象,其中包含了模型的所有可学习参数的名称和对应的张量值。
state_dict()
方法返回模型的状态字典,可以将其保存到文件中,以便在之后的时间点恢复模型的状态。
model = SiRNAModel(...)
torch.save(model.state_dict(), 'model.pth')
load_state_dict()
用于加载之前保存的模型的状态字典。可以将保存的状态字典加载到同一类别的模型对象中,以便恢复模型的参数。
model = SiRNAModel(...)
model.load_state_dict(torch.load('model.pth'))
通过state_dict()
和load_state_dict()
方法,可以方便地保存和加载模型的参数状态,以便进行模型的训练和推理。这些方法在迁移学习、继续训练以及模型部署等场景中常见。
如何将序列转换成张量输入到模型里
关键代码是在
forward()
方法
方法:forward(self, x)
参数: x (List[Tensor]): 输入序列列表
发现输入的是这个x,x又是输入的序列
for inputs,target in train_loader:
print("len(inputs):\n {0}".format(len(inputs)))
print("inputs[0].shape:\n {0} ".format(inputs[0].shape))
print("intputs[0][0]:\n {0} ".format(inputs[0][0]))
print("traget.shape:\n {0}".format(target.shape))
break
在处理siRNA序列数据时,我们首先注意到输入数据inputs包含两个元素,每个元素的尺寸为64×25。
这里的64表示批量处理的大小,而25代表每个序列的长度。通过观察inputs[0][0],我们可以了解到siRNA的反义链序列(siRNA_antisense_seq)在经过向量化处理后的表现。在这里,序列的前7位是非零值,这些非零值代表了序列编码中每个字符的唯一标识符。
在这个模型的嵌入层初始化时我们做了这样一个操作,
其中
vocab_size表示词汇表的大小
embed_dim表示嵌入向量的维度
padding_idx=0表示对应的填充符号的索引。
为了使RNN模型能够有效处理这些数据,需要保证每个输入样本的长度一致,在创建模型时采取了填充(padding)策略(上方引用)。
如果某个序列编码后的长度小于最大长度,我们会在其后补零,以确保所有序列在输入到RNN模型时具有统一的长度。
这里把所有序列都被填充至25位,来满足模型的输入要求。
如何为siRNA序列分配唯一标识符
首先进行分词处理
对于未格式化的siRNA_antisense_seq等序列
使用GenomicTokenizer实现
siRNA_antisense_seq序列通过每3个核苷酸一组划分,使用GenomicTokenizer实现,其中ngram和stride均设为3。
例如序列"AGCCGAGAU",分词后得到[“AGC”, “CGA”, “GAU”]。
对于格式化的modified_siRNA_antisense_seq_list等序列
modified_siRNA_antisense_seq_list序列根据空格已分词。
基于数据集中所有token构建词汇表。
该词汇表映射token至唯一标识符,即索引。映射过程确保RNN模型接收数值形式输入,同时学习序列中不同token间关系。
使用GenomicVocab.create
方法基于tokens
创建基因词汇表。
创建基因词汇表代码
# 创建分词器
```python
tokenizer = GenomicTokenizer(ngram=3, stride=3)
# 创建词汇表
all_tokens = [] # 用于存储所有的tokens
for col in columns: # 遍历每一列
for seq in train_data[col]: # 遍历每个序列
if ' ' in seq: # 如果序列中包含空格,则说明是修改过的序列
all_tokens.extend(seq.split()) # 将序列按空格进行切分,并添加到all_tokens中
else: # 如果序列中不包含空格,则使用tokenizer对序列进行分词
all_tokens.extend(tokenizer.tokenize(seq)) # 将分词后的结果添加到all_tokens中
vocab = GenomicVocab.create(all_tokens, max_vocab=10000, min_freq=1) # 使用all_tokens创建基因词汇表,设定最大词汇量为10000,词频阈值为1
- 先创建一个
GenomicTokenizer
对象,用于对序列进行分词。 - 然后遍历数据集中的每个序列,如果序列中包含空格,则说明是修改过的序列,直接按空格切分并添加到
all_tokens
中; - 如果序列中不包含空格,则使用分词器
tokenizer
对序列进行分词,并将结果添加到all_tokens
中。 - 最后使用
GenomicVocab.create
方法基于all_tokens
创建基因词汇表,设定最大词汇量为10000,词频阈值为1。
来获得序列的最大长度
# 用于计算训练数据中每列数据最大长度
# 首先使用嵌套的生成器表达式,遍历训练数据中的每一列
# 在内部生成器中,首先检查当前列的每个样本,判断是否包含空格
# 如果包含空格,则使用split()方法将字符串拆分成单词,并返回拆分后的单词个数
# 如果不包含空格,则使用tokenizer.tokenize()将字符串拆分成单词,并返回拆分后的单词个数
# 通过max函数将每列中的最大长度取出,并使用嵌套的生成器表达式再次计算所有列中的最大长度
max_len = max(
max(
len(seq.split()) if ' ' in seq else len(tokenizer.tokenize(seq))
for seq in train_data[col]
)
for col in columns
)
SiRNADataset类
完成上面的操作之后,在loader获取样本的时候把token转为索引,即我们通过转换成SiRNADataset类的过程中,让数据转换成索引
class SiRNADataset(Dataset):
def __init__(self, df, columns, vocab, tokenizer, max_len, is_test=False):
"""
初始化SiRNADataset类
参数:
- df:包含数据的DataFrame
- columns:包含序列的列名
- vocab:词汇表
- tokenizer:分词器
- max_len:最大序列长度
- is_test:指示是否是测试集
"""
self.df = df # 数据框
self.columns = columns # 包含序列的列名
self.vocab = vocab # 词汇表
self.tokenizer = tokenizer # 分词器
self.max_len = max_len # 最大序列长度
self.is_test = is_test # 指示是否是测试集
def __len__(self):
"""
获取数据集的长度
返回值:
- 数据集的长度
"""
return len(self.df)
def __getitem__(self, idx):
"""
获取数据集中的第idx个样本
参数:
- idx:样本索引
返回值:
- 如果是测试集模式,返回编码后的序列
- 如果是训练集模式,返回编码后的序列和对应的目标值
"""
row = self.df.iloc[idx] # 获取第idx行数据
# 对每一列进行分词和编码
seqs = [self.tokenize_and_encode(row[col]) for col in self.columns]
if self.is_test:
# 仅返回编码后的序列(测试集模式)
return seqs
else:
# 获取目标值并转换为张量(训练集模式)
target = torch.tensor(row['mRNA_remaining_pct'], dtype=torch.float)
# 返回编码后的序列和目标值
return seqs, target
def tokenize_and_encode(self, seq):
"""
对序列进行分词和编码
参数:
- seq:输入的序列
返回值:
- 编码后的序列
"""
if ' ' in seq:
# 修改过的序列,按空格分词
tokens = seq.split()
else:
# 常规序列,使用分词器分词
tokens = self.tokenizer.tokenize(seq)
# 将token转换为索引,未知token使用0(<pad>)
encoded = [self.vocab.stoi.get(token, 0) for token in tokens]
# 将序列填充到最大长度
padded = encoded + [0] * (self.max_len - len(encoded))
# 返回张量格式的序列
return torch.tensor(padded[:self.max_len], dtype=torch.long)
这个类继承自PyTorch的Dataset类,用于加载数据并将其传递给模型进行训练或预测。
在__getitem__
方法中,根据索引idx获取对应的数据行。然后针对每个包含序列的列,调用tokenize_and_encode
方法对序列进行分词和编码。 如果是测试集模式,直接返回编码后的序列;如果是训练集模式,还需获取目标值并将其转换为张量。然后,tokenize_and_encode
方法用于对序列进行分词和编码。对于常规序列,使用传入的分词器对其进行分词;对于修改过的序列,直接按空格进行分词。然后将分词后的token转换为词汇表中的索引,未知token使用索引0表示。最后将序列填充到最大长度,并返回张量格式的序列。
Dataset类
它是一个数据集的抽象接口,可以根据需要自定义数据集的读取和处理方式。
在使用PyTorch进行训练和预测时,需要将数据加载到Dataset对象中,并通过DataLoader对象对数据进行批处理和数据加载。
通过继承Dataset类,我们可以自定义数据集的处理逻辑,包括数据读取、数据预处理、数据转换等。
我们需要实现__len__和__getitem__方法,分别用于获取数据集的长度和获取指定索引位置的样本。
可以自定义Dataset类来灵活地处理不同类型的数据集,并将其传递给模型进行训练或预测。
关于训练的模型前面在SiRNAModel 类
时讲过,就不再重述
我们首先进行索引嵌入处理,即将离散的符号(例如单词、字符或基因序列片段)转换成连续的向量形式。过程中涉及将高维的稀疏表示(如独热编码)转换为低维的密集向量,以使得语义相近的符号在向量空间中的相对位置更接近。
转换后,嵌入向量的维度将从BatchSize * Length扩展为BatchSize * Length * EmbeddingSize,其中EmbeddingSize,也就是嵌入维度embed_dim,被设定为200。
RNN(递归神经网络)知识点
一种专门用于处理序列数据的神经网络模型。
与传统的前馈神经网络不同,RNN具有反馈连接,可以将前面的输出作为后续输入的一部分,使其具有记忆性。
RNN的基本结构是一个单元(cell)或节点,其中包含一个输入层、一个隐藏层和一个输出层。隐藏层中的神经元通过时间反馈连接,使得信息可以在不同时间步之间传递和共享。这种结构使得RNN能够处理任意长度的序列数据,并且能够捕捉到序列中的上下文信息。
RNN的架构示意图
RNN,即循环神经网络(Recurrent Neural Network),是一种适合于序列数据的深度学习模型。它与传统的前馈神经网络(如多层感知机)不同,RNN 能够处理序列中的动态特征,即能够捕捉时间序列中的动态依赖关系。
RNN的数学表达可以简化为以下形式:
h
t
=
f
(
W
h
h
h
t
−
1
+
W
x
h
x
t
+
b
h
)
\\ h_t = f(W_{hh} h_{t-1} + W_{xh} x_t + b_h) \
ht=f(Whhht−1+Wxhxt+bh)
y
t
=
f
(
W
h
y
h
t
+
b
y
)
\ y_t = f(W_{hy} h_t + b_y) \
yt=f(Whyht+by)
其中,
h
t
h_t
ht是时间步
t
t
t的隐藏状态。
x
t
x_t
xt是时间步
t
t
t的输入向量。
W
h
h
W_{hh}
Whh和
W
x
h
W_{xh}
Wxh分别是从上一个时间步的隐藏状态到当前隐藏状态、从当前时间步的输入到当前隐藏状态的权重矩阵。
b
h
b_h
bh是隐藏层的偏置项。
W
h
y
W_{hy}
Why是从隐藏状态到输出的权重矩阵。
b
y
b_y
by是输出层的偏置项。
f
f
f是激活函数。
RNN的训练过程
在RNN中,每个时间步都有一个输入和一个输出。输入可以是任意维度的向量,而输出通常是一个固定大小的向量或者是一个标量。RNN通过学习一组可学习的权重参数来对输入序列进行处理,并输出相应的预测结果。
RNN的训练过程通常是使用反向传播算法来优化模型的权重参数。 由于反向传播算法的梯度消失问题,在处理长序列时RNN往往会出现难以学习到长期依赖关系的情况。为了解决这个问题,一种常用的改进版本是长短期记忆网络(LSTM)和门控循环单元(GRU),它们能够更有效地捕捉和利用序列中的长期依赖关系。
RNN 的特点
- 循环连接:RNN的每个神经元不仅与下一层的神经元相连,而且与同一层的下一个时间步的神经元相连,形成了一个循环结构。
- 时间步:RNN在序列的每个时间步上都会进行计算,每个时间步的输出不仅依赖于当前的输入,还依赖于前一个时间步的输出。
- 隐藏状态:RNN通过隐藏状态(hidden state)来传递之前时间步的信息。隐藏状态可以看作是网络对之前序列信息的总结。
- 参数共享:在RNN中,同一网络参数在每个时间步上都会被重复使用,这简化了模型结构,但同时也带来了一些挑战,如梯度消失或梯度爆炸问题。
- 长短期记忆(LSTM)和门控循环单元(GRU):这两种网络结构是对传统RNN的改进,它们通过引入门控机制来解决梯度消失问题,使得网络能够学习长期依赖关系。
- 应用领域:RNN广泛应用于自然语言处理(NLP)、语音识别、时间序列预测等领域,特别是在需要处理序列数据和捕捉时间依赖性的任务中。
- 训练挑战:RNN的训练可能面临梯度消失或梯度爆炸的问题,这使得训练过程可能不稳定。现代优化技术如梯度裁剪或使用更高级的优化器(如Adam)可以帮助缓解这些问题。
- 变长序列处理:RNN能够处理不同长度的序列,但需要通过填充(padding)或截断来保证输入序列具有相同的长度。
数据的特征工程 (EDA)
在官方baseline中,得分较低可能是由于数据特征简单、序列特征构造粗糙以及数据量不足等原因。为了解决序列特征问题,可以将其转化为表格问题并进行特征工程。
基本操作
缺失值处理
检查数据是否存在缺失值,并根据具体情况决定如何处理缺失值,如删除、填充等。
异常值处理
检测和处理数据中的异常值,包括通过可视化和统计学方法识别异常值,并根据业务逻辑进行处理。
处理类别型变量
统计唯一值
df.gene_target_symbol_name.nunique()
计算DataFrame(df)中某一列(gene_target_symbol_name)中唯一值(unique value)的数量(nunique)。也就是统计该列中有多少不重复的值。
nunique()
nunique()
函数是pandas库中的一个方法,用于计算一个序列(Series)或数据框(DataFrame)中唯一值的数量。
语法如下:
Series.nunique(dropna=True)
# 或
DataFrame.nunique(axis=0, dropna=True)
参数:
dropna
:是否排除缺失值,默认为True,即排除缺失值。axis
:对于数据框,可以指定按行(axis=0)或按列(axis=1)计算唯一值的数量。
data = pd.Series([1, 2, 3, 2, 1, 4, 5, 2, 3])
unique_count = data.nunique()
print(unique_count) # 输出:5
df = pd.DataFrame({'A': [1, 2, 3], 'B': [1, 1, 2]})
unique_count_col = df.nunique(axis=0)
print(unique_count_col)
# 输出:
# A 3
# B 2
# dtype: int64
统计每个值的频率分布
df.gene_target_symbol_name.value_counts()
这段代码是用来计算DataFrame(df)中某一列(gene_target_symbol_name)中每个唯一值(unique value)出现的次数(count)。它会返回一个Series对象,其中索引是唯一值,值是对应唯一值的出现次数。通过这个可以快速了解该列中每个值的频率分布。
value_counts()
value_counts()
函数是pandas库中的一个方法,用于计算一个序列(Series)中每个唯一值的数量。
语法如下:
Series.value_counts(normalize=False, sort=True, ascending=False, bins=None, dropna=True)
参数:
normalize
:是否返回相对频率,默认为False,即返回唯一值的数量。sort
:是否按值进行排序,默认为True,即按值进行排序。ascending
:是否按升序排列,默认为False,即按降序排列。bins
:指定柱状图的箱数。dropna
:是否排除缺失值,默认为True,即排除缺失值。
示例:
data = pd.Series([1, 2, 3, 2, 1, 4, 5, 2, 3])
value_count = data.value_counts()
print(value_count)
# 输出:
# 2 3
# 1 2
# 3 2
# 5 1
# 4 1
# dtype: int64
以上value_counts()
方法计算了序列data
中每个唯一值出现的次数,按降序排列输出。
one-hot特征的构造
# 如果有40个类别,那么会产生40列,如果第i行属于第j个类别,那么第j列第i行就是1,否则为0
df_gene_target_symbol_name = pd.get_dummies(df.gene_target_symbol_name)
df_gene_target_symbol_name.columns = [
f"feat_gene_target_symbol_name_{c}" for c in df_gene_target_symbol_name.columns
]
时间特征构造
有可能
没看出来,啃臭cv一份,很妙
在数据观察的时候发现,siRNA_duplex_id的编码方式很有意思,其格式为AD-1810676.1,我们猜测AD是某个类别,后面的.1是版本,当中的可能是按照一定顺序的序列号,因此可以构造如下特征
siRNA_duplex_id_values = df.siRNA_duplex_id.str.split("-|\.").str[1].astype("int")
这段代码是从siRNA_duplex_id列中提取出按照一定顺序的序列号作为新的特征siRNA_duplex_id_values。
siRNA_duplex_id的编码格式为"AD-1810676.1",其中"AD"表示某个类别,".1"表示版本号,而中间的数字则是按照顺序的序列号。(假定,大概率)
代码通过使用正则表达式分隔符"-“和”.",将siRNA_duplex_id拆分成多个部分,然后取第二部分(索引为1),并将其转换为整数类型。得到的siRNA_duplex_id_values列即为按照一定顺序的序列号特征。
上述对每一个siRNA_duplex_id的过程同下
(方便复制)
import re
import pandas as pd
import numpy as np
str = "AD-1810676.1"
# 使用正则表达式分割字符串
parts = re.split(r'[-.]', str)
# 将数字部分转换为NumPy数组,并转换为整数类型
numbers = np.array(parts[1], dtype=int)
print(numbers)
包含某些单词
对df中的cell_line_donor
列构造特征
# 对cell_line_donor列进行独热编码
df_cell_line_donor = pd.get_dummies(df.cell_line_donor)
# 为独热编码后的列名添加前缀
df_cell_line_donor.columns = [
f"feat_cell_line_donor_{c}" for c in df_cell_line_donor.columns
]
# 创建新的特征列feat_cell_line_donor_hepatocytes,值为cell_line_donor列是否包含"Hepatocytes"的布尔值转换为整数
df_cell_line_donor["feat_cell_line_donor_hepatocytes"] = (
(df.cell_line_donor.str.contains("Hepatocytes")).fillna(False).astype("int")
)
# 创建新的特征列feat_cell_line_donor_cells,值为cell_line_donor列是否包含"Cells"的布尔值转换为整数
df_cell_line_donor["feat_cell_line_donor_cells"] = (
df.cell_line_donor.str.contains("Cells").fillna(False).astype("int")
)
代码小结
- 使用
pd.get_dummies()
函数对cell_line_donor
列进行独热编码, 编码后的列会根据不同的取值创建新的列。 - 使用列表推导式为
df_cell_line_donor
的列名添加前缀 “feat_cell_line_donor_”。 - 创建新的特征列
feat_cell_line_donor_hepatocytes
,根据cell_line_donor
列是否包含 “Hepatocytes” ,将布尔值转换为整数(1 表示包含,0 表示不包含)。 - 创建新的特征列
feat_cell_line_donor_cells
,根据cell_line_donor
列是否包含 “Cells” 来确定的,将布尔值转换为整数(1 表示包含,0 表示不包含)。
将 cell_line_donor
列转换为独热编码,并创建两个新的特征列,用于表示是否包含特定的关键词。
对碱基的模式进行特征构造
根据上一个task中的rna知识提取
def siRNA_feat_builder(s: pd.Series, anti: bool = False):
"""
构建siRNA特征的函数
参数:
s: pd.Series -- 输入的siRNA序列
anti: bool -- 是否构建反义链特征,默认为False
返回:
pd.DataFrame -- 构建的siRNA特征DataFrame
"""
name = "anti" if anti else "sense" # 根据 anti 的值确定特征名称前缀
df = s.to_frame() # 将输入的 Series 对象转换为 DataFrame 对象
df[f"feat_siRNA_{name}_seq_len"] = s.str.len() # 计算序列长度,并将其作为特征添加到 DataFrame 中
# 遍历两个位置:第一个和最后一个
for pos in [0, -1]:
# 遍历碱基:A、U、G、C
for c in list("AUGC"):
# 判断序列的第一个或最后一个碱基是否与当前碱基相等,并将结果作为特征添加到 DataFrame 中
df[f"feat_siRNA_{name}_seq_{c}_{'front' if pos == 0 else 'back'}"] = (
s.str[pos] == c
)
# 判断序列是否以特定的模式开头和结尾,并将结果作为特征添加到 DataFrame 中
df[f"feat_siRNA_{name}_seq_pattern_1"] = s.str.startswith("AA") & s.str.endswith(
"UU"
)
df[f"feat_siRNA_{name}_seq_pattern_2"] = s.str.startswith("GA") & s.str.endswith(
"UU"
)
df[f"feat_siRNA_{name}_seq_pattern_3"] = s.str.startswith("CA") & s.str.endswith(
"UU"
)
df[f"feat_siRNA_{name}_seq_pattern_4"] = s.str.startswith("UA") & s.str.endswith(
"UU"
)
df[f"feat_siRNA_{name}_seq_pattern_5"] = s.str.startswith("UU") & s.str.endswith(
"AA"
)
df[f"feat_siRNA_{name}_seq_pattern_6"] = s.str.startswith("UU") & s.str.endswith(
"GA"
)
df[f"feat_siRNA_{name}_seq_pattern_7"] = s.str.startswith("UU") & s.str.endswith(
"CA"
)
df[f"feat_siRNA_{name}_seq_pattern_8"] = s.str.startswith("UU") & s.str.endswith(
"UA"
)
# 判断序列的第二位和倒数第二位是否为 A,并将结果作为特征添加到 DataFrame 中
df[f"feat_siRNA_{name}_seq_pattern_9"] = s.str[1] == "A"
df[f"feat_siRNA_{name}_seq_pattern_10"] = s.str[-2] == "A"
# 计算序列中的 GC 碱基占整体长度的比例,并将结果作为特征添加到 DataFrame 中
df[f"feat_siRNA_{name}_seq_pattern_GC_frac"] = (
s.str.count("G") + s.str.count("C")
) / s.str.len()
return df.iloc[:, 1:] # 返回除第一列外的所有列,即去掉序列本身的列
代码小结
- “feat_siRNA_{name}_seq_len”:siRNA序列的长度作为特征。
- siRNA序列的第一个和最后一个位置,在前端或后端:
- “feat_siRNA_{name}seq{c}_{‘front’ if pos == 0 else ‘back’}”:判断序列的第一个或最后一个碱基是否与’A’, ‘U’, ‘G’, 'C’相等。
- siRNA序列的起始和结束:
- “feat_siRNA_{name}seq_pattern_1",…,"feat_siRNA{name}_seq_pattern_8”:判断序列是否以特定的模式开头和结尾。
- siRNA序列的第二位和倒数第二位:
- “feat_siRNA_{name}_seq_pattern_9”:判断序列的第二位是否为’A’。
- “feat_siRNA_{name}_seq_pattern_10”:判断序列的倒数第二位是否为’A’。
- “feat_siRNA_{name}_seq_pattern_GC_frac”:计算序列中的GC碱基占整体长度的比例。
最后选择模型预测
这里是task2给出的lightgbm的代码来对特征值预测 引一份
train_data = lgb.Dataset(X_train, label=y_train)
test_data = lgb.Dataset(X_test, label=y_test, reference=train_data)
def print_validation_result(env):
result = env.evaluation_result_list[-1]
print(f"[{env.iteration}] {result[1]}'s {result[0]}: {result[2]}")
params = {
"boosting_type": "gbdt",
"objective": "regression",
"metric": "root_mean_squared_error",
"max_depth": 7,
"learning_rate": 0.02,
"verbose": 0,
}
gbm = lgb.train(
params,
train_data,
num_boost_round=15000,
valid_sets=[test_data],
callbacks=[print_validation_result],
)
tips : 可以多模型融合,k折 ,调超参等方法涨点