1. 赛题解析与背景
背景:近年来,mRNA疫苗在新冠预防领域取得巨大成功,推动了核酸类药物的研发。本次比赛的任务是利用机器学习和深度学习技术,预测化学修饰后的siRNA序列在RNA干扰(RNAi)机制下对靶基因的沉默效率。
数据:数据集包括公开文献中的siRNA修饰序列及实验条件,分为85%的训练集和15%的验集。复赛增加未公开专利数据以评估模型在新靶基因上的预测能力。
2. 必知概念入门
RNA干扰(RNAi):一种基因表达调控机制,通过siRNA等分子沉默特定基因。其在基因治疗和疾病治疗中具有广泛应用。
化学修饰siRNA:通过化学修饰增强siRNA的稳定性、靶向性和有效性,常见修饰包括磷酸酯骨架修饰、核苷酸修饰等。
深度学习与RNN:深度学习擅长处理复杂数据,RNN特别适合处理序列数据,能捕捉时间依赖关系。
词汇表与序列编码:将核酸序列转换为数值表示,以便输入模型。使用3-gram词汇表捕捉局部模式。
数据处理与特征选择:包括数据清洗、去除缺失值和异常值,以及选择最能代表数据特征的字段。
模型训练与评估:通过优化算法调整模型参数,常用评估指标包括均方误差(MSE)、平均绝对误差(MAE)、精确率(Precision)和召回率(Recall)。
PyTorch框架:一个开源的深度学习框架,支持灵活的动态计算图,广泛用于研究和生产环境。
3. 我的关于赛题的思考:生物与AI的奇妙碰撞
生物背景知识:细胞的静音模式
在生物界,有一种非常有趣的现象叫做RNA干扰(RNAi),就像按下了基因的“静音键”,让特定基因不再发声。小干扰RNA(siRNA)就是这个“静音键”的操作员。siRNA能锁定目标基因,把它们“闭嘴”的能力用在基因治疗上简直是一绝。2018年,全球首个siRNA药物Patisiran成功上市,这不仅是科研界的一大步,也是患者们的福音。
AI与制药:打破“双十定律”的魔法
药物研发界流传着一个不太友好的“双十定律”:从药物研发到上市,得花上十年时间和十亿美元。AI的出现给了这个古老定律一记漂亮的反击。比如,AI能在海量的化合物中找到潜在的药物分子,就像在草堆里找针一样,但AI的效率高得多。此外,AI还能优化临床试验设计,简直就是制药界的魔法师!看看英矽智能的例子,他们用AI设计的药物INS018_055,从发现到进入人体临床试验仅用了18个月,这速度,简直像是开启了“快进键”。
AI在生命科学中的超级应用
AI在生命科学中的表现简直就是超级英雄级别的。以下是几个AI的经典技能:
-
药物发现:传统药物研发就像是大海捞针,而AI的加入让这个过程像装上了金属探测器,不仅快还准。AI可以预测化合物的药效和毒性,把不靠谱的选项直接筛掉,省时又省钱。
-
基因组学:AI在基因组学中就像一台超级计算机,能够分析海量的基因数据,找出基因与疾病之间的关系。通过这种方式,个性化医疗已经不再是科幻小说里的情节。
-
医学影像分析:医生们靠AI帮忙看X光、CT、MRI就像多了一双火眼金睛。AI能自动检测早期疾病的征兆,比如癌症,减少医生的工作量,还能让诊断更准确。
-
蛋白质结构预测:蛋白质的结构决定了它的功能,而预测蛋白质结构一直是生物学中的一大难题。DeepMind的AlphaFold用AI突破了这个瓶颈,提供了精确的蛋白质结构预测,这对药物设计和疾病研究都有重大意义。
-
免疫疗法:AI还能在免疫疗法中发挥作用,特别是癌症治疗。通过分析肿瘤细胞和免疫细胞的互动,AI帮助优化治疗方案,提高了治疗效果,减少了副作用
AI和生物科技的结合正在重新定义我们对健康和疾病的理解。未来,随着技术的不断进步,我们可能会看到更多意想不到的突破。就像这次比赛一样,AI帮助我们更好地理解和设计siRNA,让精准医疗不再只是梦想,而是触手可及的现实。
4. baseline重要代码解读
class GenomicTokenizer:
def __init__(self, ngram=5, stride=2):
# 初始化分词器,设置n-gram长度和步幅
self.ngram = ngram
self.stride = stride
def tokenize(self, t):
# 将输入序列转换为大写
t = t.upper()
if self.ngram == 1:
# 如果n-gram长度为1,直接将序列转换为字符列表
toks = list(t)
else:
# 否则,按照步幅对序列进行n-gram分词
toks = [t[i:i+self.ngram] for i in range(0, len(t), self.stride) if len(t[i:i+self.ngram]) == self.ngram]
# 如果最后一个分词长度小于n-gram,移除最后一个分词
if len(toks[-1]) < self.ngram:
toks = toks[:-1]
# 返回分词结果
return toks
代码解读
- 类初始化:
__init__
方法接受两个参数ngram
和stride
,用于设置分词器的 n-gram 长度和步幅。 - 分词方法:
tokenize
方法将输入的序列转换为大写,并根据ngram
和stride
对序列进行分词。 n-gram
长度为 1 的处理:如果ngram
为 1,直接将序列转换为字符列表。n-gram
长度大于 1 的处理:按步幅进行分词,并确保每个分词的长度等于ngram
。- 最后一个分词的处理:如果最后一个分词长度小于
ngram
,将其移除。 - 返回分词结果:返回处理后的分词结果列表。
class GenomicVocab:
def __init__(self, itos):
# 初始化词汇表,itos是一个词汇表列表
self.itos = itos
# 创建从词汇到索引的映射
self.stoi = {v: k for k, v in enumerate(self.itos)}
@classmethod
def create(cls, tokens, max_vocab, min_freq):
# 创建词汇表类方法
# 统计每个token出现的频率
freq = Counter(tokens)
# 选择出现频率大于等于min_freq的token,并且最多保留max_vocab个token
itos = ['<pad>'] + [o for o, c in freq.most_common(max_vocab - 1) if c >= min_freq]
# 返回包含词汇表的类实例
return cls(itos)
代码解读
-
类初始化:
__init__
方法接受一个参数itos
,它是一个词汇表列表。itos
:从索引到词汇的映射。stoi
:从词汇到索引的映射,由itos
列表生成。
-
类方法
create
:创建词汇表的类方法,用于生成GenomicVocab
类的实例。- 参数:
tokens
:所有token的列表。max_vocab
:词汇表的最大容量。min_freq
:词汇在被包含到词汇表中的最低频率。
- 步骤:
- 统计
tokens
中每个token出现的频率。 - 按照频率从高到低排序,并选择出现频率大于等于
min_freq
的token,最多保留max_vocab
个。 - 在词汇表中添加一个特殊的
<pad>
token,用于填充序列。 - 返回包含生成的
itos
列表的GenomicVocab
实例。
- 统计
- 参数:
class SiRNAModel(nn.Module):
def __init__(self, vocab_size, embed_dim=200, hidden_dim=256, n_layers=3, dropout=0.5):
super(SiRNAModel, self).__init__()
# 初始化嵌入层
self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
# 初始化GRU层
self.gru = nn.GRU(embed_dim, hidden_dim, n_layers, bidirectional=True, batch_first=True, dropout=dropout)
# 初始化全连接层
self.fc = nn.Linear(hidden_dim * 4, 1) # hidden_dim * 4 因为GRU是双向的,有n_layers层
# 初始化Dropout层
self.dropout = nn.Dropout(dropout)
def forward(self, x):
# 将输入序列传入嵌入层
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()
代码解读
-
初始化方法
__init__
:vocab_size
:词汇表大小,用于嵌入层。embed_dim
:嵌入维度,嵌入层将词汇映射为embed_dim
维向量。hidden_dim
:隐藏层维度,GRU的隐藏状态维度。n_layers
:GRU的层数。dropout
:Dropout
层的丢弃率,用于防止过拟合。
该方法初始化了模型的各层,包括嵌入层
、GRU层
、全连接层
和Dropout层
。
-
前向传播方法
forward
:- 将输入序列传入嵌入层进行词汇嵌入。
- 对每个嵌入的序列进行
GRU
处理,提取最后一个隐藏状态并进行Dropout
处理。 - 将所有处理后的序列输出拼接起来,并传入全连接层。
- 返回经过全连接层后的结果。
def calculate_metrics(y_true, y_pred, threshold=30):
# 计算平均绝对误差
mae = np.mean(np.abs(y_true - y_pred))
# 将实际值和预测值转换为二进制分类(低于阈值为1,高于或等于阈值为0)
y_true_binary = (y_true < threshold).astype(int)
y_pred_binary = (y_pred < threshold).astype(int)
# 创建掩码,用于筛选预测值在0和阈值之间的样本
mask = (y_pred >= 0) & (y_pred <= threshold)
range_mae = mean_absolute_error(y_true[mask], y_pred[mask]) if mask.sum() > 0 else 100
# 计算精确度、召回率和F1得分
precision = precision_score(y_true_binary, y_pred_binary, average='binary')
recall = recall_score(y_true_binary, y_pred_binary, average='binary')
f1 = 2 * precision * recall / (precision + recall)
# 计算综合评分
score = (1 - mae / 100) * 0.5 + (1 - range_mae / 100) * f1 * 0.5
return score
代码解读
-
计算平均绝对误差 (MAE):
mae = np.mean(np.abs(y_true - y_pred))
:计算实际值和预测值之间的平均绝对误差
。 -
将实际值和预测值转换为二进制分类:
y_true_binary = (y_true < threshold).astype(int)
:如果实际值
小于阈值
,设为1
,否则设为0
。y_pred_binary = (y_pred < threshold).astype(int)
:如果预测值
小于阈值
,设为1
,否则设为0
。
-
创建掩码:
mask = (y_pred >= 0) & (y_pred <= threshold)
:筛选预测值
在0
和阈值
之间的样本。range_mae = mean_absolute_error(y_true[mask], y_pred[mask]) if mask.sum() > 0 else 100
:计算这些样本的平均绝对误差
,如果没有
符合条件的样本,设为100
。
-
计算精确度、召回率和F1得分:
precision = precision_score(y_true_binary, y_pred_binary, average='binary')
:计算精确度。recall = recall_score(y_true_binary, y_pred_binary, average='binary')
:计算召回率。f1 = 2 * precision * recall / (precision + recall)
:计算F1得分。
-
计算综合评分:
score = (1 - mae / 100) * 0.5 + (1 - range_mae / 100) * f1 * 0.5
:综合MAE、范围内的MAE和F1得分,计算最终评分。 -
返回评分:
return score
:返回综合评分。
5. 深度学习知识复习
见顶部资源。