本期带来baseline详解。因为ipynb格式没法更好的debug,所以我把文件拆分重构成多个模块,优化了模型结构,弄成自己习惯的格式然后放到本地跑。花了点时间。
一、代码结构图
接下来,逐步debug去观察程序以及数据是如何去处理运行的。
二、初始化过程
Data
1
DataSet
DataLoader.py
def get_train_dataloader(model_config: dict):
# 获取数据路径——'data_dir': 'Data/'
data_dir = model_config['data_dir']
# 获取数据集文件名——'train_dataset_name': 'train_data'
dataset_name = model_config['train_dataset_name']
# 组装路径——Data/train_data.csv
path = data_dir + dataset_name + '.csv'
# 4.读取数据
train_data = pd.read_csv(path)
# 指定需要处理的列
columns = ['siRNA_antisense_seq', 'modified_siRNA_antisense_seq_list']
# 5.删除数据框中包含缺失值的行,inplace=True表示操作会直接在原始的train_data数据框上进行。
train_data.dropna(subset=columns + ['mRNA_remaining_pct'], inplace=True)
# 6.将数据分为训练集和验证集,test_size=0.1表示测试集将占原始数据集的10%
train_data, val_data = train_test_split(train_data, test_size=0.1, random_state=42)
# 7.传入待处理列与训练集,返回词汇表,分词器,最大序列长度。
vocab, tokenizer, max_len = get_params(columns, train_data)
# 15.创建训练、验证数据集;创建数据加载器,随机打乱顺序
train_set = SIRNADataset(train_data, columns, vocab, tokenizer, max_len)
val_set = SIRNADataset(val_data, columns, vocab, tokenizer, max_len)
train_loader = DataLoader(train_set, batch_size=model_config['bs'], shuffle=True)
val_loader = DataLoader(val_set, batch_size=model_config['bs'])
return train_loader, val_loader, len(vocab.itos)
def get_test_dataloader(model_config: dict):
data_dir = model_config['data_dir']
dataset_name = model_config['test_dataset_name']
path = data_dir + dataset_name + '.csv'
test_data = pd.read_csv(path)
columns = ['siRNA_antisense_seq', 'modified_siRNA_antisense_seq_list']
test_data.dropna(subset=columns, inplace=True)
vocab, tokenizer, max_len = get_params(columns, test_data)
test_set = SIRNADataset(test_data, columns, vocab, tokenizer, max_len, is_test=True)
test_loader = DataLoader(test_set, batch_size=model_config['bs'], shuffle=False)
return test_data, test_loader, len(vocab.itos)
def get_params(columns, data):
# 8.创建分词器
tokenizer = GenomicTokenizer(ngram=3, stride=1)
# 创建词汇表
all_tokens = []
# 10.解释循环
for col in columns:
for seq in data[col]:
if ' ' in seq:
# 10.3.修改过的序列
all_tokens.extend(seq.split())
else:
# 10.1.未经修改过的列
all_tokens.extend(tokenizer.tokenize(seq))
# 11.使用all_tokens中的词来创建一个词汇表
# 词汇表中最多包含10000个单词。
# 只有出现频率至少为1的单词才会被包含在词汇表中。
vocab = GenomicVocab.create(all_tokens, max_vocab=10000, min_freq=1)
# 14.计算序列最大长度
max_len = max(max(len(seq.split()) if ' ' in seq else len(tokenizer.tokenize(seq)) for seq in data[col]) for col in columns)
return vocab, tokenizer, max_len
4.读取数据,观察数据形状。
train_data.shape
Out[2]: (25782, 19)
5.删除后再观察数据大小。(好吧,这步多余了)
train_data.shape
Out[4]: (25782, 19)
6.划分训练集与验证集。
train_data.shape
Out[5]: (23203, 19)
val_data.shape
Out[6]: (2579, 19)
7.执行get_params()方法
8.初始化GenomicTokenizer类,打开Model/Genomic.py进入GenomicTokenizer的构造函数以ngram=3和stride=1作为参数传入,返回得到的结果并赋值给tokenizer(分词器)。
10.解释循环。
columns
Out[7]: ['siRNA_antisense_seq', 'modified_siRNA_antisense_seq_list']
col
Out[8]: 'siRNA_antisense_seq'
data[col].shape
Out[9]: (23203,)
data[col].shape
Out[10]: (23203,)
data[col]
Out[11]:
15055 AAAGGAGCUUAAUUGUGAACG
17579 UAAGACUAGAAACAGUAAGGCGG
4057 UACGGAAGAUUGUUUUCCCUUUG
7109 AGUAAUAGCAGCAAGAAUCACUC
3111 UACCUUAGAAGGGUAGCCCUGCA
...
21575 AUUGUACACGUCAAAGGUGAAUC
5390 AUAGAUGUUCAUGGAGCCUGAAG
860 UAGAAAUUCAACAGACAUUUACA
15795 UCGAUGGAUGAGGUGUAAUCAGU
23654 AAACUUGUGGUCUUCAUAAUUGA
Name: siRNA_antisense_seq, Length: 23203, dtype: objectseq
Out[12]: 'AAAGGAGCUUAAUUGUGAACG'
10.1.进入Model/Genomic.py的GenomicTokenizer类的tokenize方法。把未修改的序列按次序传入tokenize,返回的结果加到all_tokens中。返回的结果如下,随后进行循环,把‘siRNA_antisense_seq’扩展到all_tokens中
all_tokens
Out[2]:
['AAA', 'AAG', 'AGG', 'GGA', 'GAG', 'AGC', 'GCU', 'CUU', 'UUA', 'UAA', 'AAU', 'AUU', 'UUG',‘UGU', 'GUG', 'UGA', 'GAA', 'AAC', 'ACG']
len(all_tokens)
Out[3]: 19len(all_tokens)
Out[4]: 486639
10.3.对于修改过的序列,则使用空格分隔。这里只展示一个循环
seq
Out[25]: 'a dA a g dG a (G2p) c u u a a u Uf g Uf g a a c g'
all_tokens[-21:]
Out[26]:
['a','dA', 'a', 'g', 'dG', 'a', '(G2p)', 'c', 'u', 'u', 'a', 'a', 'u', 'Uf', 'g', 'Uf', 'g', 'a', 'a', 'c', 'g']len(all_tokens)
Out[5]: 1028024
11.进入Model/Genomic.py的GenomicVocab类的create方法,得到词汇表实例,请注意,此处的vocab是一个实例化过的类。itos是词汇表列表,而stoi是词汇到索引的映射。进行查看。
len(vocab.itos)
Out[9]: 92
len(vocab.stoi)
Out[10]: 92
vocab.itos[0:5]
Out[11]: ['<pad>', 'a', 'u', 'g', 'c']
vocab.stoi.keys()
Out[12]: dict_keys(['<pad>', 'a', 'u', 'g', 'c', 'Uf', 'Af', 'Cf', 'Gf', 'AAA', 'CUU', 'AAG', 'GAA', 'UUG', 'AGA', 'UUU', 'UGA', 'UCA', 'UUC', 'UCU', 'AAU', 'ACU', 'dA', 'AUU', 'UGU', 'ACA', 'CUG', 'CAU', 'AGU', 'UCC', 'AUG', 'CAG', 'UGG', 'VP', 'CCA', 'AGG', 'CAA', 'AAC', 'AUC', 'UAA', 'CUC', 'CAC', 'UAG', 'dT', 'AUA', 'AGC', 'GAG', 'UGC', 'CCU', 'GCU', 'UUA', 'dG', 'GUU', 'GAU', 'GUG', 'dC', 'GGA', 'GUA', 'GCA', 'GUC', 'GGU', 'UAC', 'GAC', 'UAU', 'ACC', 'GGC', 'GCC', 'ACG', 'CUA', 'GGG', 'CCC', 'CGA', 'UCG', '(Tgn)', 'CGU', 'CGG', '(Agn)', 'GCG', 'CCG', 'CGC', '(G2p)', '(C2p)', '(Cgn)', '(Ggn)', '(A2p)', 's', '(U2p)', 'C', 'U', 'A', 't', 'G'])
vocab.stoi.values()
Out[13]: dict_values([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91])
14.解释代码——max_len = max(max(len(seq.split()) if ' ' in seq else len(tokenizer.tokenize(seq)) for seq in data[col]) for col in columns)
外层循环——for col in columns
内层循环——for seq in data[col]
条件检查——if ' ' in seq:
计算长度,取最大值——max(len(seq.split())
条件检查——else
使用
tokenizer
进行分词,计算分词后的单词数量,取最大值取最大值赋值给max_len。
max_len
Out[24]: 25返回vocab, tokenizer, max_len到第7步。
15.创建训练集验证集,进入DataSet/SIRNADataset.py,初始化构造函数。没啥好说的。返回到第3步。
SIRNADataset.py
Model
Genomic.py
class GenomicTokenizer:
def __init__(self, ngram=5, stride=2):
# 9.ngram=3,stride=3
self.ngram = ngram
self.stride = stride
# 10.1.进入该方法
def tokenize(self, t):
# 将输入序列转换为大写
t = t.upper()
if self.ngram == 1:
# 如果n-gram长度为1,直接将序列转换为字符列表
toks = list(t)
else:
# 10.2.按照步幅对序列进行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
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):
# 创建词汇表类方法
# 12.统计每个token出现的频率
freq = Counter(tokens)
# 13.选择出现频率大于等于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)
9.初始化成功,返回。
10.2. 拆解这行代码。返回toks。
toks = [t[i: i +self.ngram] for i in range(0, len(t), self.stride) if len(t[i: i +self.ngram]) == self.ngram]
t
Out[13]: 'AAAGGAGCUUAAUUGUGAACG'
len(t)
Out[14]: 21
循环遍历——for i in range(0, len(t), self.stride)
起始值是0(
i = 0
),每次增加1(i += 1
),直到i
超过字符串t
的长度减去1(i > len(t) - 1
),终止条件是i + 1 <= len(t)
条件检查——if len(t[i: i +self.ngram]) == self.ngram
每次迭代中,代码检查子字符串
t[i: i + 3]
的长度是否等于3。如果长度不等于3,循环继续,不执行后续的列表推导式。
列表推导式——t[i: i +self.ngram]
如果子字符串的长度是3,代码执行列表推导式,将该子字符串添加到
toks
列表中。列表推导式中的表达式
t[i: i + 3]
是子字符串的生成器,它生成从索引i
开始,长度为3的子字符串。
toks
Out[15]:['AAA', 'AAG', 'AGG', 'GGA', 'GAG', 'AGC', 'GCU', 'CUU', 'UUA', 'UAA', 'AAU', 'AUU', 'UUG',‘UGU', 'GUG', 'UGA', 'GAA', 'AAC', 'ACG']
12.查看freq
len(freq)
Out[1]: 91
freq.most_common(10)
Out[2]:
[('a', 121746), ('u', 121585), ('g', 87493), ('c', 84167), ('Uf', 23958), ('Af', 23341), ('Cf', 15736), ('Gf', 15041), ('AAA', 14125), ('CUU', 13051)]
13.语法理解——itos = ['<pad>'] + [o for o, c in freq.most_common(max_vocab - 1) if c >= min_freq]
创建一个词汇表列表
itos
添加一个特殊的
<pad>
标记到列表的开头。遍历
freq.most_common(max_vocab - 1)
返回的列表并创建一个新的列表,包含那些出现次数至少为min_freq
的单词。将这两个列表合并,
<pad>
标记总是在列表的最前面,而其他单词按照出现次数从高到低排序。
查看结果
itos[0:11]
Out[8]: ['<pad>', 'a', 'u', 'g', 'c', 'Uf', 'Af', 'Cf', 'Gf', 'AAA', 'CUU']
Loss.py
class LossFunction(nn.Module):
def __init__(self):
super(LossFunction, self).__init__()
pass
Model.py
class SiRNAModel(nn.Module):
def __init__(self, model_config, vocab_size):
super(SiRNAModel, self).__init__()
self.embed_dim = model_config['embed_dim']
self.hidden_dim = model_config['hidden_dim']
self.n_layers = model_config['n_layers']
self.drop = model_config["dropout"]
# 初始化嵌入层
self.embedding = nn.Embedding(vocab_size, self.embed_dim, padding_idx=0)
# 初始化GRU层
self.gru = nn.GRU(self.embed_dim, self.hidden_dim, self.n_layers, bidirectional=True, batch_first=True, dropout=self.drop)
# 初始化全连接层
self.fc = nn.Linear(self.hidden_dim * 4, 1) # hidden_dim * 4 因为GRU是双向的,有n_layers层
# 初始化Dropout层
self.dropout = nn.Dropout(self.drop)
pass
17.贴baseline模型图。都是pytorch库的一些简单的模型。此处构造函数进行初始化而已。返回第16步。
Score.py
class ScoreFunction(nn.Module):
def __init__(self):
super(ScoreFunction, self).__init__()
pass
Trainer.py
class Trainer(object):
def __init__(self, model_config: dict):
# 从model_config获取初始化过程中必要的元素
self.device = model_config['device']
self.lr = model_config['lr']
self.output_dir = model_config['output_dir']
self.sche_gamma = model_config['sche_gamma']
self.result = model_config['result']
# 3.获取训练数据和验证数据
self.train_loader, self.val_loader, self.big1 = get_train_dataloader(model_config)
# 获取测试数据和big
self.test_data, self.test_loader, self.big = get_test_dataloader(model_config)
# 16.定义训练模型并进行初始化,传入必要参数
self.model = SiRNAModel(model_config, self.big1).to(self.device)
# 定义测试模型并进行初始化,传入必要参数
self.model1 = SiRNAModel(model_config, self.big).to(self.device)
# 18.定义损失函数并进行初始化
self.loss_func = LossFunction().to(self.device)
# 19.定义评估分数并进行初始化
self.score_func = ScoreFunction().to(self.device)
pass
3.进入DataSet/DataLoader.py中的get_train_dataloader()函数。
16.将model_config和big(即len(vocab.itos))作为参数传入SiRNAModel模型进行初始化。进入Model.py。观察模型。
18.进入损失函数看看。打开Loss.py。只是初始化而已。
19.看看评估分数。打开Score.py。只是初始化而已。
20.至此,模型的初始化过程就完全结束了,接下来第三部分分享模型的训练和验证过程。
output
1
result
1
main.py
model_config = {
'sche_gamma': 0.99,
'bs': 64,
'epochs': 15,
'lr': 0.001,
'seed': 42,
'output_dir': 'output/models',
'device': 'cuda:0',
'train_dataset_name': 'train_data',
'test_dataset_name': 'sample_submission',
'data_dir': 'Data/',
'embed_dim': 128,
'hidden_dim': 256,
'n_layers': 3,
'dropout': 0.5,
'result': 'result'
}
if __name__ == "__main__":
pass
# 2.初始化训练器入口
trainer = Trainer(model_config=model_config)
1.首先定义模型参数model_config,model_config是一个元组,存放模型训练需要用到的参数。
2.执行trainer = Trainer(model_config=model_config),初始化训练器,将model_config元组作为参数传递进去。进入Trainer.py的构造函数[def __init__(self, model_config: dict):]中。
utils.py
1
三、训练过程
Data
DataSet
DataLoader.py
SIRNADataset.py
def __getitem__(self, idx):
# 3.获取数据集中的第idx个样本
row = self.df.iloc[idx] # 获取第idx行数据
# 4.对每一列进行分词和编码
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)
# 8.返回编码后的序列和目标值
return seqs, target
def tokenize_and_encode(self, seq):
if ' ' in seq: # 修改过的序列
tokens = seq.split() # 按空格分词
else:
# 5.常规序列
tokens = self.tokenizer.tokenize(seq) # 使用分词器分词
# 6.将token转换为索引,未知token使用0(<pad>)
encoded = [self.vocab.stoi.get(token, 0) for token in tokens]
# 7.将序列填充到最大长度
padded = encoded + [0] * (self.max_len - len(encoded))
# 返回张量格式的序列
return torch.tensor(padded[:self.max_len], dtype=torch.long)
3.self.df.iloc[idx]是什么?
self.df.shape——整张表的大小
Out[5]: (23203, 19)row = self.df.iloc[idx]——此处idx=2263
row
Out[11]:
id 58832
publication_id WO2b901a9fb5
gene_target_symbol_name ABLIM3
gene_target_ncbi_id NM_001301015.3
gene_target_species Homo sapiens
siRNA_duplex_id AD-1808098.1
siRNA_sense_seq GAUGGUGUUCCAUACUGUGAA
siRNA_antisense_seq UUCACAGUAUGGAACACCAUCCU
cell_line_donor Hepa1-6 Cells
siRNA_concentration 1.0
concentration_unit nM
Transfection_method Lipofectamine
Duration_after_transfection_h 24.0
modified_siRNA_sense_seq gaugg(Uhd)GfuUfCfCfauacugugaa
modified_siRNA_antisense_seq VPuUfcacAfgUfAfuggaAfcAfccauccu
modified_siRNA_sense_seq_list g a u g g (Uhd) Gf u Uf Cf Cf a u a c u g u g a a
modified_siRNA_antisense_seq_list VP u Uf c a c Af g Uf Af u g g a Af c Af c c a...
gene_target_seq ACACCCCCTTTGGGCTCCGGAGCCGCTGGGCAGGAATAGTCCGGGG...
mRNA_remaining_pct 19.558
Name: 25695, dtype: object
4.seqs = [self.tokenize_and_encode(row[col]) for col in self.columns]
for col in self.columns——siRNA_antisense_seq、modified_siRNA_antisense_seq_list
1、row[siRNA_antisense_seq]——UUCACAGUAUGGAACACCAUCCU
进入self.tokenize_and_encode,以UUCACAGUAUGGAACACCAUCCU为参数传入。
seqs[0].shape
Out[6]: torch.Size([25])
seqs[0]
Out[7]:
tensor([18, 17, 41, 25, 31, 28, 57, 63, 30, 32, 56, 12, 37, 25, 41, 64, 34, 27, 38, 29, 48, 0, 0, 0, 0])
seqs[1].shape
Out[8]: torch.Size([25])
seqs[1]
Out[9]:
tensor([33, 2, 5, 4, 1, 4, 6, 3, 5, 6, 2, 3, 3, 1, 6, 4, 6, 4, 4, 1, 2, 4, 4, 2, 0])
5.常规序列
tokens = self.tokenizer.tokenize(UUCACAGUAUGGAACACCAUCCU) 步长为3,步幅为1
tokens
Out[16]:
['UUC', 'UCA', 'CAC', 'ACA', 'CAG', 'AGU', 'GUA', 'UAU', 'AUG', 'UGG', 'GGA', 'GAA', 'AAC', 'ACA', 'CAC', 'ACC', 'CCA', 'CAU', 'AUC', 'UCC', 'CCU']
6.将token转换为索引,未知token使用0。
我们已知有vocab(词汇表)
self.vocab.stoi
Out[18]:{'<pad>': 0, 'a': 1, 'u': 2, 'g': 3, 'c': 4, 'Uf': 5, 'Af': 6, 'Cf': 7, 'Gf': 8, 'AAA': 9, 'CUU': 10, 'AAG': 11, 'GAA': 12, 'UUG': 13, 'AGA': 14, 'UUU': 15, 'UGA': 16, 'UCA': 17, 'UUC': 18, 'UCU': 19, 'AAU': 20, 'ACU': 21, 'dA': 22, 'AUU': 23, 'UGU': 24, 'ACA': 25, 'CUG': 26, 'CAU': 27, 'AGU': 28, 'UCC': 29, 'AUG': 30, 'CAG': 31, 'UGG': 32, 'VP': 33, 'CCA': 34, 'AGG': 35, 'CAA': 36, 'AAC': 37, 'AUC': 38, 'UAA': 39, 'CUC': 40, 'CAC': 41, 'UAG': 42, 'dT': 43, 'AUA': 44, 'AGC': 45, 'GAG': 46, 'UGC': 47, 'CCU': 48, 'GCU': 49, 'UUA': 50, 'dG': 51, 'GUU': 52, 'GAU': 53, 'GUG': 54, 'dC': 55, 'GGA': 56, 'GUA': 57, 'GCA': 58, 'GUC': 59, 'GGU': 60, 'UAC': 61, 'GAC': 62, 'UAU': 63, 'ACC': 64, 'GGC': 65, 'GCC': 66, 'ACG': 67, 'CUA': 68, 'GGG': 69, 'CCC': 70, 'CGA': 71, 'UCG': 72, '(Tgn)': 73, 'CGU': 74, 'CGG': 75, '(Agn)': 76, 'GCG': 77, 'CCG': 78, 'CGC': 79, '(G2p)': 80, '(C2p)': 81, '(Cgn)': 82, '(Ggn)': 83, '(A2p)': 84, 's': 85, '(U2p)': 86, 'C': 87, 'U': 88, 'A': 89, 't': 90, 'G': 91}
self.vocab.itos
Out[19]:
['<pad>',
'a', 'u', 'g', 'c', 'Uf', 'Af', 'Cf', 'Gf', 'AAA', 'CUU', 'AAG', 'GAA', 'UUG', 'AGA', 'UUU', 'UGA', 'UCA','UUC', 'UCU', 'AAU', 'ACU', 'dA', 'AUU', 'UGU','ACA', 'CUG', 'CAU', 'AGU', 'UCC', 'AUG', 'CAG', 'UGG', 'VP', 'CCA', 'AGG', 'CAA', 'AAC', 'AUC', 'UAA', 'CUC', 'CAC', 'UAG', 'dT', 'AUA', 'AGC',
'GAG', 'UGC', 'CCU', 'GCU', 'UUA', 'dG','GUU', 'GAU', 'GUG', 'dC', 'GGA', 'GUA', 'GCA', 'GUC', 'GGU', 'UAC', 'GAC', 'UAU', 'ACC', 'GGC', 'GCC',
'ACG', 'CUA', 'GGG', 'CCC', 'CGA', 'UCG', '(Tgn)', 'CGU', 'CGG', '(Agn)', 'GCG', 'CCG', 'CGC', '(G2p)', '(C2p)', '(Cgn)', '(Ggn)', '(A2p)', 's', '(U2p)',
'C', 'U', 'A', 't', 'G']
for token in tokens
token='UUC'](举一个为例)
self.vocab.stoi.get('UUC', 0)
Out[21]: 18继续循环,把结果赋值给encoded,最终结果:
encoded
Out[22]:
[18, 17, 41, 25, 31, 28, 57, 63, 30, 32, 56, 12, 37, 25, 41, 64, 34, 27, 38, 29, 48]
7.填充序列到最大长度。返回到第4步。
self.max_len
Out[23]: 25
len(encoded)
Out[24]: 21运行后
padded
Out[25]:
[18, 17, 41, 25, 31, 28, 57, 63, 30, 32, 56, 12, 37, 25, 41, 64, 34, 27, 38, 29, 48, 0, 0, 0, 0]
8.看seqs和target的值,target是预测值,也就是y。 依次遍历得到seqs集和targets集,返回第2步。
seqs
Out[13]:
[tensor([18, 17, 41, 25, 31, 28, 57, 63, 30, 32, 56, 12, 37, 25, 41, 64, 34, 27, 38, 29, 48, 0, 0, 0, 0]),
tensor([33, 2, 5, 4, 1, 4, 6, 3, 5, 6, 2, 3, 3, 1, 6, 4, 6, 4, 4, 1, 2, 4, 4, 2, 0])]
target
Out[14]: tensor(19.5580)
Model
Genomic.py
Loss.py
class LossFunction(nn.Module):
def __init__(self):
super(LossFunction, self).__init__()
# 简单的均方误差
def forward(self, output, target):
criterion = nn.MSELoss()
loss = criterion(output,target)
17.查看损失
return loss
17.观察损失并返回到第16步。
loss
Out[37]: tensor(4023.7244, device='cuda:0', grad_fn=<MseLossBackward0>)
Model.py
class SiRNAModel(nn.Module):
pass
def forward(self, x):
# 10.将输入序列传入嵌入层
embedded = [self.embedding(seq) for seq in x]
outputs = []
# 对每个嵌入的序列进行处理
for embed in embedded:
# 11.将embedding后的向量传入gru层
x, _ = self.gru(embed)
# 12.取最后一个隐藏状态,并进行dropout处理
x = self.dropout(x[:, -1, :])
# 将隐藏状态添加到输出列表 outputs
outputs.append(x)
# 13.将所有序列的输出拼接起来
x = torch.cat(outputs, dim=1)
# 14.传入全连接层
x = self.fc(x)
# 移除张量 x=[64,1] 中的任何大小为1的维度,使其更紧凑和高效,返回
return x.squeeze()
10.seq中有两组张量,用for循环遍历出来之后,使用self.embedding(seq)将每组输入的张量(离散的特征)转换为连续的稠密向量。
embedded[0].shape
Out[24]: torch.Size([64, 25, 128])
embedded[1].shape
Out[25]: torch.Size([64, 25, 128])64是每个样本,25是统一长度,embedding_dim是128,也就是嵌入维度是128
11.GRU层返回两个值:第一个值是序列的输出,第二个值是下一个时间步的隐藏状态和细胞状态。此处不重要。
x.shape
Out[28]: torch.Size([64, 25, 512])
_.shape
Out[29]: torch.Size([6, 64, 256])
12.取每个序列的最后一个隐藏状态,可以看作是序列在最后一个时间步的完整“记忆”,这可以作为序列的最终表示。
x.shape
Out[30]: torch.Size([64, 512])
13.沿着dim=1维度拼接起来,我们已知x.shape=[64, 512],拼接后的大小如下:
x.shape
Out[32]: torch.Size([64, 1024])
14.观察全连接层的维度。
self.fc
Out[34]: Linear(in_features=1024, out_features=1, bias=True)执行后
x.shape
Out[33]: torch.Size([64, 1])
15.返回到第9步中,把x赋值给output。
Score.py
class ScoreFunction(nn.Module):
def __init__(self):
super(ScoreFunction, self).__init__()
def forward(self, 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
返回值是一个分数就对了,不纠结这个,没时间了。
Trainer.py
class Trainer(object):
pass
def training(self, epochs):
# 将模型移动到指定设备
# 创建Adam优化器,使用给定的学习率和权重衰减参数来更新模型参数。
optimizer = optim.Adam(self.model.parameters(), lr=self.lr, weight_decay=1e-5)
# 创建指数学习率调度器,根据给定的衰减率来调整优化器的lr。
scheduler = optim.lr_scheduler.ExponentialLR(optimizer, gamma=self.sche_gamma)
# 初始化分数负无穷大
best_score = -float('inf')
# 初始化最佳模型
best_model = None
print("Training Start.")
for epoch in range(epochs):
# 设置模型为训练模式
self.model.train()
# 初始化训练损失
train_loss = 0
# 2.进入循环
for step, (x_inputs, targets) in enumerate(tqdm(self.train_loader, desc=f'Epoch {epoch + 1}/{epochs}')):
# 将输入移动到设备
x_input = [x.to(self.device) for x in x_inputs]
# 将目标值移动到设备
target = targets.to(self.device)
# 清空梯度
optimizer.zero_grad()
# 9.前向传播
output = self.model(x_input)
# 16.计算损失(均方误差)
loss = self.loss_func(output, target)
# 反向传播
loss.backward()
# 更新参数
optimizer.step()
# 调用指数学习率调度器
scheduler.step()
# 累加训练损失
train_loss += loss.item()
self.model.eval()
val_loss = 0 # 初始化验证损失
val_preds = []
val_targets = []
# 19.计算预测损失,不需要用到梯度下降算法,所以关掉梯度计算。
with torch.no_grad():
for step, (x_inputs, targets) in enumerate(self.val_loader):
x_input = [x.to(self.device) for x in x_inputs]
target = targets.to(self.device) # 将目标值移动到设备
output = self.model(x_input) # 前向传播
loss = self.loss_func(output, target)
val_loss += loss.item()
val_preds.extend(output.cpu().numpy()) # 收集预测值
val_targets.extend(targets.cpu().numpy()) # 收集目标值
# 20.看看损失是多少
train_loss /= len(self.train_loader) # 计算平均训练损失
val_loss /= len(self.val_loader) # 计算平均验证损失
val_preds = np.array(val_preds)
val_targets = np.array(val_targets)
# 21.看看评分机制
score = self.score_func(val_targets, val_preds)
print(f'Epoch {epoch + 1}/{epochs}')
print(f'Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}')
print(f'Learning Rate: {optimizer.param_groups[0]["lr"]:.6f}')
print(f'Validation Score: {score:.4f}')
if score > best_score:
best_score = score # 更新最佳得分
best_model = self.model.state_dict().copy() # 更新最佳模型
torch.save(self.model.state_dict(), os.path.join(self.output_dir, "best.pt".format(epoch))) # 保存最佳模型
print(f'New best model found with score: {best_score:.4f}')
# 不返回了,这里改成保存到模型,以便测试的时候直接加载
return best_model # 返回最佳模型
pass
2.对于循环代码:for step, (x_inputs, targets) in enumerate(tqdm(self.train_loader, desc=f'Epoch {epoch + 1}/{epochs}')):,首先是先加载训练数据集self.train_loader,需要到SIRNADataset类中访问魔术方法__getitem__( 当使用PyTorch的数据加载器时,__getitem__
方法会被自动调用,用于获取数据集中的样本。这个方法接收一个索引(通常是整数),并返回一个样本。)。我们过去SIRNADataset中访问。
遍历得到x_inputs和targets。我们知道x_input列表里有两个变量,对应columns的两个属性。
x_inputs[0].shape
Out[20]: torch.Size([64, 25])
x_inputs[1].shape
Out[21]: torch.Size([64, 25])targets.shape
Out[22]: torch.Size([64])
9.进行前向传播,进入SiRNAModel类的forward函数中。观察运行后的output和target的维度。
output.shape
Out[35]: torch.Size([64])
target.shape
Out[36]: torch.Size([64])
16.进入Loss.py的LossFunction的forward函数中。得到结果如下:
loss
Out[38]: tensor(4023.7244, device='cuda:0', grad_fn=<MseLossBackward0>)
18.执行optimizer.step()更新了参数之后,返回到第2步,遍历这个批次的所有数据,由于数据有23203条,我们的batchsize是64,所以这个循环要执行363次。
19.把所有数据过了一轮之后,开始计算预测损失。其实with torch.no_grad():后面的循环内容和训练循环大差不差的。
20.观察结果。
len(self.train_loader)
Out[49]: 363
len(self.val_loader)
Out[50]: 41
train_loss
Out[51]: 3.3415977961432506(平均后的)
val_loss
Out[52]: 1251.1617788919589(平均后的)
val_preds.shape
Out[54]: (2579,)
val_targets.shape
Out[55]: (2579,)
21.进入评分机制Score.py中。
22.接下来的步骤,其实都很简单了。
output
result
main.py
# 1.将epochs作为参数传入训练器中,开始训练,然后返回最佳模型
best_model = trainer.training(model_config['epochs'])
1.将epochs作为参数传入训练器中,开始训练,然后返回最佳模型。进入Trainer类的training方法。
utils.py
接下来是尝试对模型做修改。