File size: 8,017 Bytes
6f1e670 | 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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | #!/public/home/scnb9biwet/.conda/envs/model_bio/bin/python
"""
Script to propose mutations using trained multievolve models.
Modified to load local models instead of using wandb.
"""
import os
os.environ["WANDB_MODE"] = "disabled"
os.environ["WANDB_SILENT"] = "true"
import wandb
import argparse
import pandas as pd
import numpy as np
from Bio import SeqIO
import matplotlib
matplotlib.use('Agg')
import glob
import re
import torch
from model.splitters import *
from model.featurizers import *
from model.predictors import *
from model.proposers import *
def parse_args():
"""Parse command line arguments."""
parser = argparse.ArgumentParser(description='Propose mutations using trained models')
parser.add_argument(
'--experiment-name',
required=True,
help='Name of experiment'
)
parser.add_argument(
'--protein-name',
required=True,
help='Name of protein'
)
parser.add_argument(
'--wt-files',
required=True,
help='Comma separated list of paths to the wildtype FASTA files'
)
parser.add_argument(
'--training-dataset',
required=True,
help='Path to training dataset CSV'
)
parser.add_argument(
'--mutation-pool',
required=True,
help='Path to mutation pool CSV'
)
parser.add_argument(
'--top-muts-per-load',
type=int,
default=3,
help='Number of top mutations to select per load (default: 3)'
)
parser.add_argument(
'--export-name',
required=True,
help='Name for export files'
)
args = parser.parse_args()
args.wt_files = [f.strip() for f in args.wt_files.split(',')]
return args
def main():
"""Main function."""
# Parse command line arguments
args = parse_args()
# Define variables from args
experiment_name = args.experiment_name
protein_name = args.protein_name
wt_files = args.wt_files
training_dataset_fname = args.training_dataset
mutation_pool_fname = args.mutation_pool
top_muts_per_load = args.top_muts_per_load
export_name = args.export_name
# Processed variables
mutation_pool = pd.read_csv(mutation_pool_fname, header=None).values.flatten().tolist()
wt_seq = "".join([str(SeqIO.read(wt_file, "fasta").seq.upper()) for wt_file in wt_files])
# 手动指定最佳超参数(来自 fcn_test_sweep.yaml 和训练设置)
bs = 32
lr = 0.0001
hidden = 100
layers = 1
print(bs, lr, hidden, layers)
# 配置模型
config = {
'layer_size': hidden,
'num_layers' : layers,
'learning_rate': lr,
'batch_size': bs,
'optimizer': 'adam',
'epochs': 300
}
# 初始化 splits(与训练时一致,5 折)
split = KFoldProteinSplitter(protein_name, training_dataset_fname, wt_files,
csv_has_header=True, use_cache=True, y_scaling=True, val_split=0.15)
splits = split.generate_splits(n_splits=5)
# 初始化 feature
feature = OneHotFeaturizer(protein=protein_name, use_cache=True)
# 加载已有模型
# 从分裂对象中获取 dataset_dir 和 dataset_name,动态构造模型目录
dataset_dir = splits[0].file_attrs['dataset_dir']
dataset_name = splits[0].file_attrs['dataset_name']
model_dir = os.path.join(dataset_dir, 'model_cache', dataset_name, 'objects')
#model_dir = os.path.join(splits[0].file_attrs['model_dir'], 'objects')
model_files = glob.glob(os.path.join(model_dir, 'split_by_kfold-*.pth'))
# 按 fold 编号排序
model_files.sort(key=lambda x: int(re.search(r'split_by_kfold-(\d+)_', os.path.basename(x)).group(1)))
print(f"Found {len(model_files)} model files in {model_dir}")
models = []
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
for i, split in enumerate(splits):
model = Fcn(split, feature, config=config, use_cache=True)
model.load_state_dict(torch.load(model_files[i], map_location=device, weights_only=True))
model.to(device)
model.eval()
models.append(model)
print(f"Loaded model from {model_files[i]}")
print("Proposing mutations...")
# 初始化 proposer 并评估提案
proposer = CombinatorialProposer(
start_seq=wt_seq,
models=models,
trust_radius=11,
num_seeds=-1, # evaluate all seeds
mutation_pool=mutation_pool)
proposer.propose(output_df=False)
proposer.evaluate_proposals()
proposer.save_proposals(f'{experiment_name}_proposals_all')
# 获取每个突变负荷的前 N 个变体
df = proposer.proposals
df_ls = []
for num_mut in range(3, 11, 1):
subset = df[df['num_muts'] == num_mut].copy()
subset.sort_values(by='average', ascending=False, inplace=True)
top_subset = subset.head(top_muts_per_load).copy()
df_ls.append(top_subset)
top_df = pd.concat(df_ls, ignore_index=True)
# 导出结果
print('Saving all proposals...')
top_df.to_csv(os.path.join(splits[0].file_attrs['dataset_dir'], 'proposers/results',
f'{experiment_name}_proposals_top_{top_muts_per_load}.csv'), index=False)
top_df[['Mut_string']].to_csv(os.path.join(splits[0].file_attrs['dataset_dir'], f'{export_name}.csv'),
index=False, header=None)
# 多链蛋白处理函数(原代码保留,未修改)
def reverse_multichain_mutations(mut_strings, chain_lengths):
cumulative_lengths = [sum(chain_lengths[:i]) for i in range(len(chain_lengths))]
mutation_map = {}
for mut_string in mut_strings:
mutations = mut_string.split('/')
chain_mutations = {i: [] for i in range(len(chain_lengths))}
for mut in mutations:
position = int(mut[1:-1])
wt_aa = mut[0]
mut_aa = mut[-1]
for chain_idx, start_pos in enumerate(cumulative_lengths):
if position <= cumulative_lengths[chain_idx + 1] if chain_idx + 1 < len(cumulative_lengths) else float('inf'):
chain_pos = position - start_pos
chain_mutations[chain_idx].append(f"{wt_aa}{chain_pos}{mut_aa}")
break
mutation_map[mut_string] = chain_mutations
return mutation_map
def mutation_map_to_df(mutation_map):
rows = []
for mut_string, chain_muts in mutation_map.items():
row = {'Mut_string': mut_string}
for chain_idx, mutations in chain_muts.items():
row[f'chain_{chain_idx + 1}'] = '/'.join(mutations) if mutations else ''
rows.append(row)
df = pd.DataFrame(rows)
chain_cols = [col for col in df.columns if col.startswith('chain_')]
df = df[['Mut_string'] + sorted(chain_cols)]
return df
if len(wt_files) > 1:
mutations = top_df['Mut_string'].values.tolist()
chain_lens = splits[0].wt_seq_lens
dict_mutations = reverse_multichain_mutations(mutations, chain_lens)
df_mutations = mutation_map_to_df(dict_mutations)
top_df = pd.merge(top_df, df_mutations, on='Mut_string', how='left')
top_df.to_csv(os.path.join(splits[0].file_attrs['dataset_dir'], 'proposers/results',
f'{experiment_name}_proposals_top_{top_muts_per_load}.csv'), index=False)
for col in df_mutations.columns[1:]:
mutations = set(df_mutations[col].tolist())
if '' in mutations:
mutations.remove('')
df_mutations_col = pd.DataFrame(mutations, columns=[col])
df_mutations_col.to_csv(os.path.join(splits[0].file_attrs['dataset_dir'],
f'{export_name}_{col}_mutants.csv'), index=False, header=None)
if __name__ == "__main__":
main()
|