| import os |
| import sys |
| import copy |
| sys.path.append(os.path.dirname(__file__)) |
| sys.path.append(os.path.dirname(os.path.dirname(__file__))) |
|
|
| import json |
| import torch |
| import numpy as np |
| import pandas as pd |
| from torch import nn |
|
|
| from Bio import SeqIO |
| from Bio.SeqRecord import SeqRecord |
|
|
| from torch.utils.data import DataLoader, Dataset ,random_split,IterableDataset |
| from sklearn.model_selection import KFold,train_test_split |
| from bucket_sampler import Bucket_Sampler |
| from utils import Seq_one_hot |
|
|
| global script_dir |
| global data_dir |
|
|
| with open(os.path.join(os.path.dirname(os.path.dirname(__file__)),"machine_configure.json"),'r') as f: |
| config = json.load(f) |
|
|
| script_dir = config['script_dir'] |
| data_dir = config['data_dir'] |
|
|
| def one_hot(seq,complementary=False): |
| """ |
| one_hot encoding on sequence |
| complementary: encode nucleatide into complementary one |
| """ |
| |
| seq = list(seq.replace("U","T")) |
| seq_len = len(seq) |
| complementary = -1 if complementary else 1 |
| |
| keys = ['A', 'C', 'G', 'T'][::complementary] |
| oh_dict = {keys[i]:i for i in range(4)} |
| |
| oh_array = np.zeros((seq_len,4)) |
| for i,C in enumerate(seq): |
| try: |
| oh_array[i,oh_dict[C]]=1 |
| except: |
| continue |
| return oh_array |
|
|
| def read_bpseq(test_bpseq_path): |
| """ |
| read bpseq file, extract sequence and ptable |
| """ |
| with open(test_bpseq_path,'r') as f: |
| test_bpseq = f.readlines() |
| f.close() |
|
|
| for i in range(8): |
| if test_bpseq[i].startswith('1'): |
| start_index = i |
|
|
| bp_seq = test_bpseq[start_index:] |
|
|
| seq = ''.join([line.strip().split(" ")[1] for line in bp_seq]) |
| ptable = [int(line.strip().split(" ")[2]) for line in bp_seq] |
|
|
| assert len(ptable) == int(bp_seq[-1].split(" ")[0]) |
|
|
| return seq,ptable |
|
|
| def pad_zeros(X,pad_to): |
| """ |
| zero padding at the right end of the sequence |
| """ |
| if pad_to == 0: |
| |
| try: |
| seq_len = X.shape[0] |
| except: |
| seq_len = X[0].shape[0] |
| pad_to = 3*(seq_len//3 + 1) + 1 |
| |
| seq_len = X.shape[0] if isinstance(X, np.ndarray) else X[0].shape[0] |
| gap = pad_to - seq_len |
| |
| |
| pad_fn = nn.ZeroPad2d([0,0,gap,0]) |
| |
| if isinstance(X, list): |
| X_padded = [pad_fn(torch.tensor(X[0])), X[1]] |
| elif isinstance(X, np.ndarray): |
| X_padded = pad_fn(torch.tensor(X)) |
| return X_padded |
|
|
| def pack_seq(ds_zls:list,pad_to:int): |
| X_ts = [X for X,Y in ds_zls] |
| if pad_to == 0: |
| try: |
| max_len = np.max([X.shape[0] for X in X_ts]) |
| except: |
| max_len = np.max([X[0].shape[0] for X in X_ts]) |
| |
| pad_to = 3*(max_len//3 + 1) + 1 |
| |
| if isinstance(X_ts[0],np.ndarray): |
| X_packed = torch.stack([pad_zeros(X=x,pad_to=pad_to) for x in X_ts]) |
| else: |
| X_packed = [torch.stack([pad_zeros(X=x[0],pad_to=pad_to) for x in X_ts]), torch.tensor([x[1] for x in X_ts])] |
| Y_packed = torch.tensor(np.array([Y for X,Y in ds_zls])) |
| |
| return X_packed , Y_packed |
|
|
|
|
|
|
| class mask_reader(Dataset): |
| def __init__(self,npy_path): |
| """ |
| read the mask A549 sequence and with real sequence |
| """ |
| self.data_set = np.load(npy_path) |
| self.X = self.data_set[:,0] |
| self.Y = self.data_set[:,1] |
| |
| def __len__(self): |
| return self.X.shape[0] |
| |
| def __getitem__(self,index): |
| return (self.X[index,:,:],self.Y[index,:,:]) |
|
|
| class mix_dataset(Dataset): |
| def __init__(self): |
| |
| raise ValueError("the dataset is not defined") |
|
|
| class mask_dataset(Dataset): |
| def __init__(self): |
| raise ValueError("the dataset is not defined") |
|
|
| class ribo_dataset(Dataset): |
| |
| def __init__(self,DF, pad_to, trunc_len=50, seq_col='utr', |
| aux_columns='TE_count', other_input_columns=None): |
| """ |
| Dataset to trancate sequence and return in one-hot encoding way |
| `dataset(DF,pad_to,trunc_len=50,seq_col='utr')` |
| ...DF: the dataframe contain sequence and its meta-info |
| ...pad_to: final size of the output tensor |
| ...trunc_len: maximum sequence to retain. number of nt preceding AUG |
| ...seq_col : which col of the DF contain sequence to convert |
| """ |
| DF[seq_col] = DF[seq_col].astype(str) |
| self.df = DF |
| self.pad_to = pad_to |
| self.trunc_len = 0 if trunc_len is None else trunc_len |
| |
| |
| self.seqs = self.df.loc[:,seq_col].values |
| self.other_input_columns = other_input_columns |
| self.Y = self.df.loc[:,aux_columns].values |
| |
| def __len__(self): |
| return self.df.shape[0] |
| |
| def __getitem__(self,i): |
| seq = self.seqs[i] |
| x_padded = self.seq_chunk_N_oh(seq) |
| input = x_padded |
| if self.other_input_columns is not None: |
| input = [x_padded] |
| for col in self.other_input_columns: |
| input.append(self.df.loc[:,col].values[i]) |
| y = self.Y[i] |
| return input,y |
| |
| def seq_chunk_N_oh(self,seq): |
| """ |
| truncate the sequence and encode in one hot |
| """ |
| if (len(seq) > self.trunc_len)&(self.trunc_len >0): |
| seq = seq[-1* self.trunc_len:] |
| |
| X = one_hot(seq) |
| X = torch.tensor(X) |
|
|
| |
| |
| return X.float() |
| |
| |
| class MTL_dataset(Dataset): |
| def __init__(self, DF, pad_to=100, seq_col='utr', aux_columns=None, |
| other_input_columns=None, trunc_len=None): |
| """ |
| the dataset for Multi-task learning, will return sequence in one-hot encoded version, together with some auxilary task label |
| arguments: |
| ...csv_path: abs path of csv file, column `utr` should be in the csv |
| ...pad_to : maximum length of the sequences |
| ...columns : list contains what axuslary task label will be |
| """ |
| self.pad_to = pad_to |
| self.trunc_len = trunc_len |
| DF[seq_col] = DF[seq_col].astype(str) |
| self.df = DF |
| self.seqs = self.df[seq_col].values |
| self.columns = aux_columns |
| self.other_input_columns = other_input_columns |
| |
| assert trunc_len == None, "MTL dataset does not support argument trunc len" |
| |
| def __len__(self): |
| return self.df.shape[0] |
| |
| def __getitem__(self,index): |
| seq = self.seqs[index] |
| |
| X = one_hot(seq).astype(float) |
| |
| |
| if self.columns == None: |
| |
| item = X ,X |
| elif (type(self.columns) == list) & (len(self.columns)!=0): |
| |
| aux_labels = self.df.loc[:,self.columns].values[index] |
| |
| |
| item = X ,aux_labels |
|
|
| if self.other_input_columns is not None: |
| input = [] |
| for col in self.other_input_columns: |
| input.append(self.df.loc[:,col].values[index]) |
| item = (X, input),aux_labels |
|
|
| return item |
|
|
| class kmer_scan_dataset(Dataset): |
| def __init__(self, DF, seq_col, kmer_size, aux_columns=None, *args): |
| super().__init__() |
| self.df = DF |
| self.seqs = DF[seq_col].values |
| self.rls = DF[aux_columns].values |
| self.k = kmer_size |
| self.kmer_sets = self.create_kmer() |
| self.kmer_lookup = { kmer : i for i, kmer in enumerate(self.kmer_sets) } |
|
|
| def __len__(self): |
| return self.df.shape[0] |
|
|
| def __getitem__(self, index): |
| seq = self.seqs[index] |
| y = self.rls[index] |
|
|
| x = self.scan_kmer(seq) |
| return torch.tensor(x.astype(float)), torch.tensor(y) |
| |
| def scan_kmer(self, seq): |
|
|
| mat = np.zeros((len(self.kmer_lookup), len(seq)-self.k)) |
| for i in range(0, len(seq)-self.k): |
| seq_let = seq[i:i+self.k] |
| mat[self.kmer_lookup[seq_let], i] = 1 |
|
|
| return mat |
|
|
| |
| def create_kmer(self): |
| all_kmer = {0:['']} |
| k = 1 |
| while k <= self.k: |
| k_mer = [] |
| for source in all_kmer[k-1]: |
| k_mer += [source + base for base in ['A','C','G','T']] |
|
|
| assert len(k_mer) == 4**k, f"new kmers {len(k_mer)}, not equal to {4**k}" |
| all_kmer[k] = k_mer |
| k += 1 |
|
|
| all_kmer.pop(0) |
| return all_kmer[self.k] |
|
|
|
|
|
|
|
|
| class FASTA_dataset(MTL_dataset): |
| def __init__(self,DF, pad_to,trunc_len, seq_col, aux_columns, other_input_columns): |
| super().__init__(DF=DF, pad_to=pad_to, trunc_len=trunc_len, seq_col=seq_col, |
| aux_columns=aux_columns, other_input_columns=other_input_columns) |
|
|
| def get_splited_dataloader(dataset_func, df_ls, ratio:list, batch_size, shuffle, pad_to, seed=42,return_dataset=False): |
| """ |
| split the total dataset into train val test, and return in a DataLoader (train_loader,val_loader,test_loader) |
| dataset : the defined <UTR_dataset> |
| ratio : the ratio of train : val : test |
| batch_size : int |
| """ |
|
|
| |
| |
| set_ls = [dataset_func(df) for df in df_ls] |
| if return_dataset: |
| return set_ls |
| |
| if pad_to == 0 : |
| |
| |
| col_fn = lambda x: pack_seq(x,0) |
| sampler_ls = [{"batch_sampler":Bucket_Sampler(df,batch_size=batch_size,drop_last=True), |
| "num_workers":4, |
| "drop_last":False, |
| "collate_fn":col_fn} for df in df_ls] |
| |
| loader_ls = [DataLoader(subset,**kwargs) for subset,kwargs in zip(set_ls,sampler_ls)] |
| else: |
| col_fn = lambda x: pack_seq(x,pad_to) |
| loaderargs = {"batch_size": batch_size, |
| "generator":torch.Generator().manual_seed(42), |
| "drop_last":False , |
| "num_workers":4, |
| "shuffle":shuffle, |
| "collate_fn":col_fn} |
| |
| loader_ls = [DataLoader(subset,**loaderargs) for subset in set_ls] |
| |
| if len(loader_ls) == 2: |
| |
| loader_ls.append(None) |
| |
| return loader_ls |
|
|
| def split_DF(data_path,split_like,ratio, kfold_cv, kfold_index=None,seed=42): |
| |
| class _cf_data(object): |
| def __init__(self,data_path): |
| self.path = data_path |
| self.is_fasta = data_path.endswith('.fasta') |
| self.__read__() |
| |
| def __read__(self): |
| if self.is_fasta: |
| self.data = list(SeqIO.parse(self.path,'fasta')) |
| else: |
| self.data = pd.read_csv(os.path.join(data_dir,self.path),low_memory=False) |
| |
| def __len__(self): |
| return len(self.data) |
| |
| def _slice_(self, indices): |
| if isinstance(self.data, list) & self.is_fasta: |
| return np.array(self.data)[indices] |
| elif isinstance(self.data, pd.DataFrame): |
| return self.data.iloc[indices] |
| |
| |
| if kfold_cv == True: |
| full_df = pd.read_csv(os.path.join(data_dir,data_path),low_memory=False) |
| |
| df_ls = KFold_df_split(full_df,kfold_index) |
| |
| if kfold_cv == 'train_val': |
| train_val, test = [_cf_data(data_path).data for data_path in split_like] |
| |
| train, val = KFold_1_split(train_val,kfold_index) |
| df_ls = [train, val , test] |
| |
| elif type(split_like) == list: |
| df_ls = [_cf_data(data_path).data for data_path in split_like] |
|
|
| elif type(kfold_cv) == str: |
| full_df = pd.read_csv(os.path.join(data_dir,data_path),low_memory=False) |
| if kfold_cv in full_df.columns: |
| kfold_index = str(kfold_index) |
| test = full_df.query(f"`{kfold_cv}` == @kfold_index") |
| train_val = full_df.query(f"`{kfold_cv}` != @kfold_index") |
| train, val = train_test_split(train_val, test_size=0.05) |
| df_ls = [train, val, test] |
| |
| else: |
| full_df = _cf_data(data_path) |
| |
| total_len = len(full_df) |
| lengths = [int(total_len*sub_ratio) for sub_ratio in ratio[:-1]] |
| lengths.append(total_len-sum(lengths)) |
|
|
| set_ls = random_split(full_df,lengths,generator=torch.Generator().manual_seed(seed)) |
| df_ls = [full_df._slice_(subset.indices) for subset in set_ls] |
| |
| return df_ls |
| |
| def split_DF_call(data_path,split_like,ratio, kfold_cv, kfold_index=None,seed=42): |
| |
| class _cf_data(object): |
| def __init__(self,data_path): |
| self.path = data_path |
| self.is_fasta = data_path.endswith('.fasta') |
| self.__read__() |
| |
| def __read__(self): |
| if self.is_fasta: |
| self.data = list(SeqIO.parse(self.path,'fasta')) |
| else: |
| self.data = pd.read_csv(os.path.join(data_dir,self.path),low_memory=False) |
| |
| def __len__(self): |
| return len(self.data) |
| |
| def _slice_(self, indices): |
| if isinstance(self.data, list) & self.is_fasta: |
| return np.array(self.data)[indices] |
| elif isinstance(self.data, pd.DataFrame): |
| return self.data.iloc[indices] |
| |
| |
| if kfold_cv == True: |
| full_df = pd.read_csv(os.path.join(data_dir,data_path),low_memory=False) |
| |
| df_ls = KFold_df_split(full_df,kfold_index) |
| |
| if kfold_cv == 'train_val': |
| train_val, test = [_cf_data(data_path).data for data_path in split_like] |
| |
| train, val = KFold_1_split(train_val,kfold_index) |
| df_ls = [train, val , test] |
| |
| elif type(split_like) == list: |
| df_ls = [_cf_data(data_path).data for data_path in split_like] |
|
|
| elif type(kfold_cv) == str: |
| full_df = pd.read_csv(os.path.join(data_dir,data_path),low_memory=False) |
| if kfold_cv in full_df.columns: |
| kfold_index = str(kfold_index) |
| test = full_df.query(f"`{kfold_cv}` == @kfold_index") |
| train_val = full_df.query(f"`{kfold_cv}` != @kfold_index") |
| train, val = train_test_split(train_val, test_size=0.05) |
| df_ls = [train, val, test] |
| |
| else: |
| full_df = _cf_data(data_path) |
| |
| total_len = len(full_df) |
| lengths = [int(total_len*sub_ratio) for sub_ratio in ratio[:-1]] |
| lengths.append(total_len-sum(lengths)) |
|
|
| set_ls = random_split(full_df,lengths,generator=torch.Generator().manual_seed(seed)) |
| df_ls = [full_df._slice_(subset.indices) for subset in set_ls] |
| |
| return df_ls |
|
|
|
|
| def KFold_df_split(df,K,**kfoldargs): |
| """ |
| split the dataset DF in a ratio of 8:1:1 , train:val:test in the framework of K-fold CV |
| set random seed = 43 |
| arguments: |
| df : the `pd.DataFrame` object containing all data info |
| K : [0,4] , the index of subfold |
| """ |
| |
| |
| fold_index = list(KFold(10,shuffle=True,random_state=42).split(df)) |
| train_val_index, test_index = fold_index[K] |
| |
| |
| |
| train_val_df = df.iloc[train_val_index] |
| test_df = df.iloc[test_index] |
| |
| |
| train_df, val_df = train_test_split(train_val_df,test_size=0.05,random_state=42) |
| |
| return [train_df,val_df,test_df] |
| |
| def KFold_1_split(df,K,**kfoldargs): |
| """ |
| for dataset with standard test set, where only train_val can be splited |
| """ |
| fold_index = list(KFold(10,shuffle=True,random_state=42).split(df)) |
| train_index, val_index = fold_index[K] |
| |
| |
| |
| train = df.iloc[train_index] |
| val = df.iloc[val_index] |
|
|
| return train , val |
| |
| def get_dataloader(POPEN): |
| """ |
| wrapper |
| """ |
| |
| |
| |
| |
| |
| |
| |
| df_ls = split_DF(POPEN.csv_path,POPEN.split_like,POPEN.train_test_ratio,POPEN.kfold_cv,POPEN.kfold_index,seed=42) |
| |
|
|
| DS_Class = eval(POPEN.dataset+"_dataset") |
| |
| dataset_func = lambda x : DS_Class(x,pad_to=POPEN.pad_to,trunc_len=POPEN.trunc_len, |
| seq_col=POPEN.seq_col, aux_columns=POPEN.aux_task_columns, |
| other_input_columns=POPEN.other_input_columns) |
| |
| loader_ls = get_splited_dataloader(dataset_func, df_ls,ratio=POPEN.train_test_ratio, |
| batch_size=POPEN.batch_size, shuffle=POPEN.shuffle, |
| pad_to=POPEN.pad_to, seed=42) |
| |
| return loader_ls |