Datawhale AI夏令营——第三期siRNA药物预测(二)

本期带来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: object

seq
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]: 19

len(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

接下来是尝试对模型做修改。

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值