philm 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
philm/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.0.1"
@@ -0,0 +1,7 @@
1
+ data:
2
+ test_X: data/Phage_test.tsv
3
+ output_prefix: results/PHILM_test
4
+ model:
5
+ path: results/PHILM_best_model.pth
6
+ output_dim: #OUTDIM#
7
+ batch_size: 512
@@ -0,0 +1,7 @@
1
+ data:
2
+ test_X: data/Phage_train.tsv
3
+ output_prefix: results/PHILM_train
4
+ model:
5
+ path: results/PHILM_best_model.pth
6
+ output_dim: #OUTDIM#
7
+ batch_size: 512
@@ -0,0 +1,7 @@
1
+ data:
2
+ test_X: data/Phage_val.tsv
3
+ output_prefix: results/PHILM_val
4
+ model:
5
+ path: results/PHILM_best_model.pth
6
+ output_dim: #OUTDIM#
7
+ batch_size: 512
@@ -0,0 +1,7 @@
1
+ data:
2
+ test_X: data/Phage_train.tsv
3
+ test_Y: data/Bact_arc_train.tsv
4
+ output_prefix: results/PHILM_predict_train
5
+ model:
6
+ path: results/PHILM_best_model.pth
7
+ batch_size: 512
@@ -0,0 +1,7 @@
1
+ data:
2
+ test_X: data/Phage_val.tsv
3
+ test_Y: data/Bact_arc_val.tsv
4
+ output_prefix: results/PHILM_predict_val
5
+ model:
6
+ path: results/PHILM_best_model.pth
7
+ batch_size: 512
@@ -0,0 +1,7 @@
1
+ data:
2
+ test_X: data/Phage_test.tsv
3
+ test_Y: data/Bact_arc_test.tsv
4
+ output_prefix: results/PHILM_predict_test
5
+ model:
6
+ path: results/PHILM_best_model.pth
7
+ batch_size: 512
@@ -0,0 +1,12 @@
1
+ data:
2
+ train_X: data/Phage_train.tsv
3
+ train_Y: data/Bact_arc_train.tsv
4
+ val_X: data/Phage_val.tsv
5
+ val_Y: data/Bact_arc_val.tsv
6
+ model:
7
+ batch_size: 512 # Batch size
8
+ patience: 20 # Number of epochs with no improvement after which training will be stopped
9
+ num_epochs: 8000 # Maximal epochs for training
10
+ n_trials: 10 # Number of trials for choosing the optimal combinations of parameters, default=10
11
+ path: results/PHILM_best_model.pth
12
+ para_path: results/PHILM_best_params.yaml
@@ -0,0 +1,44 @@
1
+ # Fixed-parameter PHILM null-permutation training config.
2
+ # Place this file in config/config_train_permutation.yaml and run from the repo root:
3
+ # python model/train_permutation.py -c config/config_train_permutation.yaml --perm-id 0
4
+ # For 100 HPC array jobs, pass --perm-id ${SLURM_ARRAY_TASK_ID}.
5
+
6
+ data:
7
+ train_X: data/Phage_train.tsv
8
+ train_Y: data/Bact_arc_train.tsv
9
+ val_X: data/Phage_val.tsv
10
+ val_Y: data/Bact_arc_val.tsv
11
+
12
+ permutation:
13
+ # Overridden by --perm-id.
14
+ id: 0
15
+
16
+ # Seed used as: seed = base_seed + perm_id, unless --seed is supplied.
17
+ base_seed: 19910728
18
+
19
+ # Null model: shuffle phage/X sample rows relative to fixed bacteria/Y rows.
20
+ # This breaks the paired phage-bacteria relationship before training.
21
+ method: shuffle
22
+
23
+ # Avoid cases where a Y row is still paired with its original X row.
24
+ avoid_self_pairs: true
25
+
26
+ # Recommended: permute both train and validation pairs for a matched null-training workflow.
27
+ permute_train_X: true
28
+ permute_val_X: true
29
+
30
+ map_path_template: results/permutation_null/perm_{perm_id_padded}/PHILM_perm_{perm_id_padded}.sample_permutation.tsv
31
+
32
+ runtime:
33
+ device: auto
34
+
35
+ model:
36
+ batch_size: 512
37
+ patience: 20
38
+ num_epochs: 8000
39
+
40
+ # Output paths. {perm_id_padded} will be 000, 001, ..., 099.
41
+ path_template: results/permutation_null/perm_{perm_id_padded}/PHILM_perm_{perm_id_padded}.pth
42
+ para_path_template: results/permutation_null/perm_{perm_id_padded}/PHILM_perm_{perm_id_padded}.params.yaml
43
+ log_path_template: results/permutation_null/perm_{perm_id_padded}/PHILM_perm_{perm_id_padded}.train.log
44
+
File without changes
@@ -0,0 +1,27 @@
1
+ # data_loader.py
2
+ import torch
3
+ from torch.utils.data import Dataset, DataLoader
4
+
5
+ class MGX_MVX_Dataset(Dataset):
6
+ def __init__(self, train_X_data, train_Y_data):
7
+ self.train_X_data = train_X_data
8
+ self.train_Y_data = train_Y_data
9
+
10
+ def __len__(self):
11
+ return len(self.train_X_data)
12
+
13
+ def __getitem__(self, idx):
14
+ X = torch.tensor(self.train_X_data.loc[idx], dtype=torch.float32)
15
+ y = torch.tensor(self.train_Y_data.loc[idx], dtype=torch.float32)
16
+ return X, y
17
+
18
+ class Predict_Dataset(Dataset):
19
+ def __init__(self, X_data):
20
+ self.X_data = X_data
21
+
22
+ def __len__(self):
23
+ return len(self.X_data)
24
+
25
+ def __getitem__(self, idx):
26
+ X = torch.tensor(self.X_data.loc[idx], dtype=torch.float32)
27
+ return X
@@ -0,0 +1,104 @@
1
+ import torch
2
+ import sys, yaml, argparse
3
+ import pandas as pd
4
+ from torch.utils.data import DataLoader
5
+
6
+ from .data_loader import MGX_MVX_Dataset
7
+ from .model import MLP_NODE
8
+ from .utils import ft_pearson_corr_list, ft_cos_sim_list, ft_r2score_list, pearson_corr_list, cos_sim_list, r2score_list
9
+
10
+ # Device configuration
11
+ if torch.cuda.is_available():
12
+ device = torch.device(0) # Use GPU 0
13
+ else:
14
+ device = torch.device('cpu') # Fallback to CPU
15
+
16
+ def load_config(config_path):
17
+ with open(config_path, 'r') as file:
18
+ return yaml.safe_load(file)
19
+
20
+ def test_model(args):
21
+ # Load configuration
22
+ config = load_config(args.config)
23
+
24
+ # read data
25
+ test_X_data = pd.read_table(config['data']['test_X'], header=None)
26
+ test_Y_data = pd.read_table(config['data']['test_Y'], header=None)
27
+ dataset = MGX_MVX_Dataset(test_X_data, test_Y_data)
28
+
29
+ # load model
30
+ # define hyperparameters (must be the same as during training)
31
+ input_dim = test_X_data.shape[1] # Should match training input_dim
32
+ output_dim = test_Y_data.shape[1] # Use the output_dim from training
33
+
34
+ batch_size=config['model']['batch_size']
35
+ hidden_size=config['model']['hidden_size']
36
+
37
+ # load data
38
+ test_loader = DataLoader(dataset, batch_size=batch_size)
39
+
40
+ model = MLP_NODE(
41
+ input_dim=input_dim,
42
+ output_dim=output_dim,
43
+ hidden_size=hidden_size
44
+ )
45
+ model.load_state_dict(torch.load(config['model']['path'], weights_only=False, map_location=device))
46
+ model.to(device)
47
+ model.eval()
48
+
49
+ preds = torch.tensor([]).to(device)
50
+ grounds = torch.tensor([]).to(device)
51
+ with torch.no_grad():
52
+ for X_batch, Y_batch in test_loader:
53
+ X_batch, Y_batch = X_batch.to(device), Y_batch.to(device)
54
+
55
+ pred = model(X_batch)
56
+ pred = pred.to(device)
57
+ preds = torch.cat((preds, pred), 0)
58
+ grounds = torch.cat((grounds, Y_batch), 0)
59
+ torch.cuda.empty_cache()
60
+
61
+ prediction = preds
62
+ ground_truth = grounds
63
+
64
+ print(prediction.shape)
65
+ prefix=config['output_prefix']
66
+ dt = pd.DataFrame(prediction.cpu().numpy())
67
+ dt.to_csv(prefix+"_pred.tsv", sep="\t", header=False, index=False)
68
+
69
+ # feature-wise metrics
70
+ pcc_lst = ft_pearson_corr_list(prediction, ground_truth)
71
+ pcc_dt = pd.DataFrame(pcc_lst)
72
+ pcc_dt.to_csv(prefix+"_pcc_ft.tsv", sep="\t", header=["PCC"], index=False)
73
+
74
+ cos_lst = ft_cos_sim_list(prediction, ground_truth)
75
+ cos_dt = pd.DataFrame(cos_lst.cpu().numpy())
76
+ cos_dt.to_csv(prefix+"_cos_sim_ft.tsv", sep="\t", header=["Cos_Sim"], index=False)
77
+
78
+ r2_lst = ft_r2score_list(prediction, ground_truth)
79
+ r2_dt = pd.DataFrame(r2_lst)
80
+ r2_dt.to_csv(prefix+"_r2_ft.tsv", sep="\t", header=["R2"], index=False)
81
+
82
+ # sample-wise metrics
83
+ pcc_lst = pearson_corr_list(prediction, ground_truth)
84
+ pcc_dt = pd.DataFrame(pcc_lst)
85
+ pcc_dt.to_csv(prefix+"_pcc.tsv", sep="\t", header=["PCC"], index=False)
86
+
87
+ cos_lst = cos_sim_list(prediction, ground_truth)
88
+ cos_dt = pd.DataFrame(cos_lst.cpu().numpy())
89
+ cos_dt.to_csv(prefix+"_cos_sim.tsv", sep="\t", header=["Cos_Sim"], index=False)
90
+
91
+ r2_lst = r2score_list(prediction, ground_truth)
92
+ r2_dt = pd.DataFrame(r2_lst)
93
+ r2_dt.to_csv(prefix+"_r2.tsv", sep="\t", header=["R2"], index=False)
94
+
95
+ def main():
96
+ from philm import __version__
97
+ parser = argparse.ArgumentParser(description=f'PHILM {__version__} evaluate model')
98
+ parser.add_argument('-c', '--config', required=True, type=str,
99
+ help='config file path')
100
+ args = parser.parse_args()
101
+ test_model(args)
102
+
103
+ if __name__ == '__main__':
104
+ main()
@@ -0,0 +1,75 @@
1
+ import torch
2
+ import sys, yaml, argparse
3
+ import pandas as pd
4
+ from torch.utils.data import DataLoader
5
+
6
+ from .data_loader import MGX_MVX_Dataset, Predict_Dataset
7
+ from .model import MLP_NODE
8
+
9
+ # Device configuration
10
+ if torch.cuda.is_available():
11
+ device = torch.device(0) # Use GPU 0
12
+ else:
13
+ device = torch.device('cpu') # Fallback to CPU
14
+
15
+ def load_config(config_path):
16
+ with open(config_path, 'r') as file:
17
+ return yaml.safe_load(file)
18
+
19
+ def extract_repr(args):
20
+ # Load configuration
21
+ config = load_config(args.config)
22
+
23
+ # read data
24
+ test_X_data = pd.read_table(config['data']['test_X'], header=None)
25
+ dataset = Predict_Dataset(test_X_data)
26
+
27
+ # load model
28
+ # define hyperparameters (must be the same as during training)
29
+ input_dim = test_X_data.shape[1] # Should match training input_dim
30
+ output_dim = config['model']['output_dim']
31
+ batch_size=config['model']['batch_size']
32
+ hidden_size=config['model']['hidden_size']
33
+
34
+ # load data
35
+ dataloader = DataLoader(dataset, batch_size=batch_size)
36
+
37
+ model = MLP_NODE(
38
+ input_dim=input_dim,
39
+ output_dim=output_dim,
40
+ hidden_size=hidden_size
41
+ )
42
+
43
+ model.load_state_dict(torch.load(config['model']['path'], weights_only=False, map_location=device))
44
+ model.to(device)
45
+ model.eval()
46
+
47
+ repr1_list = torch.tensor([]).to(device)
48
+ repr2_list = torch.tensor([]).to(device)
49
+ with torch.no_grad():
50
+ for X_batch in dataloader:
51
+ X_batch = X_batch.to(device)
52
+ repr1 = model.get_repr1(X_batch)
53
+ repr1_list = torch.cat((repr1_list, repr1), 0)
54
+ repr2 = model.get_repr2(X_batch)
55
+ repr2_list = torch.cat((repr2_list, repr2), 0)
56
+ torch.cuda.empty_cache()
57
+
58
+ print(repr1_list.shape)
59
+ prefix=config['output_prefix']
60
+ dt = pd.DataFrame(repr1_list.cpu().numpy())
61
+ dt.to_csv(prefix+"_repr1.tsv", sep="\t", header=False, index=False)
62
+
63
+ dt = pd.DataFrame(repr2_list.cpu().numpy())
64
+ dt.to_csv(prefix+"_repr2.tsv", sep="\t", header=False, index=False)
65
+
66
+ def main():
67
+ from philm import __version__
68
+ parser = argparse.ArgumentParser(description=f'PHILM {__version__} extract representations')
69
+ parser.add_argument('-c', '--config', required=True, type=str,
70
+ help='config file path')
71
+ args = parser.parse_args()
72
+ extract_repr(args)
73
+
74
+ if __name__ == '__main__':
75
+ main()
philm/model/model.py ADDED
@@ -0,0 +1,48 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import torchdiffeq
4
+
5
+ class ODEFunc(nn.Module):
6
+ """
7
+ Defines the neural ODE function.
8
+ """
9
+ def __init__(self, hidden_dim):
10
+ super(ODEFunc, self).__init__()
11
+ layers = []
12
+ prev_dim = hidden_dim
13
+ for _ in range(2):
14
+ # print(prev_dim, h_dim)
15
+ layers.append(nn.Linear(prev_dim, hidden_dim))
16
+ layers.append(nn.Tanh())
17
+ prev_dim = hidden_dim
18
+ self.model = nn.Sequential(*layers)
19
+
20
+ def forward(self, t, x):
21
+ return self.model(x)
22
+
23
+ class MLP_NODE(nn.Module):
24
+ def __init__(self, input_dim, hidden_size, output_dim):
25
+ super(MLP_NODE, self).__init__()
26
+ self.fc1 = nn.Linear(input_dim, hidden_size)
27
+ self.ode_func = ODEFunc(hidden_size)
28
+ self.ode_solver = torchdiffeq.odeint # Using adjoint method for memory efficiency
29
+ self.fc2 = nn.Linear(hidden_size, output_dim)
30
+
31
+ def forward(self, x):
32
+ x = torch.flatten(x, 1) # Flatten the input tensor except for the batch dimension
33
+ x = self.fc1(x)
34
+ t = torch.tensor([0, 1], dtype=torch.float32, device=x.device) # Time interval for ODE solver, moved to the same device as x
35
+ x = self.ode_solver(self.ode_func, x, t, rtol=1e-4, atol=1e-4)[-1] # Get the result at the final time point
36
+ x = self.fc2(x)
37
+ return x
38
+
39
+ def get_repr1(self, x):
40
+ repr1 = self.fc1(x)
41
+ return repr1
42
+
43
+ def get_repr2(self, x):
44
+ x = torch.flatten(x, 1) # Flatten the input tensor except for the batch dimension
45
+ x = self.fc1(x)
46
+ t = torch.tensor([0, 1], dtype=torch.float32, device=x.device) # Time interval for ODE solver, moved to the same device as x
47
+ repr2 = self.ode_solver(self.ode_func, x, t, rtol=1e-4, atol=1e-4)[-1] # Get the result at the final time point
48
+ return repr2
@@ -0,0 +1,60 @@
1
+ import optuna
2
+ import torch, os
3
+ from .trainer import train_model
4
+
5
+ def determine_nodelayer_no(input_num, output_num):
6
+ pool = [4, 8, 16, 32, 64, 128, 256, 320, 512, 640, 1024, 2048, 2560, 4096]
7
+ print(input_num, output_num)
8
+ val = input_num + output_num
9
+ hidden_dim = min(pool, key=lambda x: abs(x - val/2))
10
+
11
+ # print(hidden_dim)
12
+ hidden_size_list = [hidden_dim]
13
+ indices = [pool.index(hidden_dim)]
14
+ if (pool.index(hidden_dim)-1) > 0:
15
+ indices.append(pool.index(hidden_dim)-1)
16
+ hidden_size_list.append(pool[pool.index(hidden_dim)-1])
17
+ if (pool.index(hidden_dim)+1) < len(pool):
18
+ indices.append(pool.index(hidden_dim)+1)
19
+ hidden_size_list.append(pool[pool.index(hidden_dim)+1])
20
+ return hidden_size_list
21
+
22
+ def objective(trial, train_X_data, train_Y_data, val_X_data, val_Y_data, batch_size, patience, num_epochs, device):
23
+
24
+ input_num = train_X_data.shape[1]
25
+ output_num = train_Y_data.shape[1]
26
+ hidden_size_list = determine_nodelayer_no(input_num, output_num)
27
+ print("candidates:", hidden_size_list)
28
+
29
+ # Suggest hyperparameters
30
+ learning_rate = trial.suggest_float("learning_rate", 1e-6, 1e-2, log=True)
31
+ hidden_size = trial.suggest_categorical("hidden_size", hidden_size_list)
32
+ weight_decay = trial.suggest_float("weight_decay", 1e-8, 1e-5, log=True)
33
+
34
+ # Ensure the directory exists
35
+ os.makedirs("checkpoints", exist_ok=True)
36
+
37
+ model_path = os.path.join("checkpoints", f"model_trial_{trial.number}.pth") # Save models in a folder named 'models'
38
+ log_path = os.path.join("checkpoints", f"train_{trial.number}.log")
39
+
40
+ # Call the train function and get the validation loss
41
+ best_val_loss = train_model(
42
+ train_X_data=train_X_data,
43
+ train_Y_data=train_Y_data,
44
+ val_X_data=val_X_data,
45
+ val_Y_data=val_Y_data,
46
+ device=device,
47
+ num_epochs=num_epochs,
48
+ batch_size=batch_size,
49
+ learning_rate=learning_rate,
50
+ hidden_size=hidden_size,
51
+ patience=patience,
52
+ weight_decay=weight_decay,
53
+ model_path=model_path,
54
+ log_path=log_path
55
+ )
56
+
57
+ # Store the model name in the trial's user attributes for later reference
58
+ trial.set_user_attr("model_path", model_path)
59
+
60
+ return best_val_loss
philm/model/predict.py ADDED
@@ -0,0 +1,72 @@
1
+ import torch
2
+ import sys, yaml, argparse
3
+ import pandas as pd
4
+ from torch.utils.data import DataLoader
5
+
6
+ from .data_loader import Predict_Dataset
7
+ from .model import MLP_NODE
8
+
9
+ # Device configuration
10
+ if torch.cuda.is_available():
11
+ device = torch.device(0) # Use GPU 0
12
+ else:
13
+ device = torch.device('cpu') # Fallback to CPU
14
+
15
+ def load_config(config_path):
16
+ with open(config_path, 'r') as file:
17
+ return yaml.safe_load(file)
18
+
19
+ def predict(args):
20
+ # Load configuration
21
+ config = load_config(args.config)
22
+
23
+ # read data
24
+ test_X_data = pd.read_table(config['data']['test_X'], header=None)
25
+ dataset = Predict_Dataset(test_X_data)
26
+
27
+ # load model
28
+ # define hyperparameters (must be the same as during training)
29
+ input_dim = test_X_data.shape[1] # Should match training input_dim
30
+ output_dim = config['model']['output_dim'] # Use the output_dim from training
31
+ batch_size=config['model']['batch_size']
32
+ hidden_size=config['model']['hidden_size']
33
+
34
+ # load data
35
+ test_loader = DataLoader(dataset, batch_size=batch_size)
36
+
37
+ model = MLP_NODE(
38
+ input_dim=input_dim,
39
+ output_dim=output_dim,
40
+ hidden_size=hidden_size
41
+ )
42
+ model.load_state_dict(torch.load(config['model']['path'], weights_only=False, map_location=device))
43
+ model.to(device)
44
+ model.eval()
45
+
46
+ preds = torch.tensor([]).to(device)
47
+ with torch.no_grad():
48
+ for X_batch in test_loader:
49
+ X_batch = X_batch.to(device)
50
+
51
+ pred = model(X_batch)
52
+ pred = pred.to(device)
53
+ preds = torch.cat((preds, pred), 0)
54
+ torch.cuda.empty_cache()
55
+
56
+ prediction = preds
57
+
58
+ print(prediction.shape)
59
+ prefix=config['output_prefix']
60
+ dt = pd.DataFrame(prediction.cpu().numpy())
61
+ dt.to_csv(prefix+"_pred.tsv", sep="\t", header=False, index=False)
62
+
63
+ def main():
64
+ from philm import __version__
65
+ parser = argparse.ArgumentParser(description=f'PHILM {__version__} predict model')
66
+ parser.add_argument('-c', '--config', required=True, type=str,
67
+ help='config file path')
68
+ args = parser.parse_args()
69
+ predict(args)
70
+
71
+ if __name__ == '__main__':
72
+ main()
philm/model/train.py ADDED
@@ -0,0 +1,75 @@
1
+ import yaml
2
+ import numpy as np
3
+ import pandas as pd
4
+ import torch
5
+ import sys, os, argparse
6
+ from .trainer import train_model
7
+ from .optimize import objective
8
+ import optuna
9
+ import shutil
10
+
11
+
12
+ def load_config(config_path):
13
+ with open(config_path, 'r') as file:
14
+ return yaml.safe_load(file)
15
+
16
+ def train_model(args):
17
+ # Load configuration
18
+ config = load_config(args.config)
19
+
20
+ # Load data
21
+ train_X_data = pd.read_table(config['data']['train_X'], header=None) # Shape: (samples, bacterial_features)
22
+ train_Y_data = pd.read_table(config['data']['train_Y'], header=None) # Shape: (samples, viral_features)
23
+ val_X_data = pd.read_table(config['data']['val_X'], header=None) # Shape: (samples, bacterial_features)
24
+ val_Y_data = pd.read_table(config['data']['val_Y'], header=None) # Shape: (samples, viral_features)
25
+
26
+ # Load fixed parameters
27
+ batch_size = config['model']['batch_size']
28
+ patience = config['model']['patience']
29
+ num_epochs = config['model']['num_epochs']
30
+ n_trials = config['model']['n_trials']
31
+
32
+ # Load target model path and parameter path
33
+ saved_model_path = config['model']['path']
34
+ para_path = config['model']['para_path']
35
+
36
+ # Device configuration
37
+ if torch.cuda.is_available():
38
+ device = torch.device(0) # Use GPU 0
39
+ else:
40
+ device = torch.device('cpu') # Fallback to CPU
41
+ print(f'Using device: {device}')
42
+
43
+ # Create an Optuna study and optimize
44
+ study = optuna.create_study(direction="minimize")
45
+ study.optimize(lambda trial: objective(trial, train_X_data, train_Y_data, val_X_data, val_Y_data, batch_size, patience, num_epochs, device), n_trials=n_trials)
46
+
47
+ # Print the best parameters, score, and model name
48
+ print("Best Parameters:", study.best_params)
49
+ print("Best Val Loss:", study.best_value)
50
+ print("Best Model Name:", study.best_trial.user_attrs["model_path"])
51
+
52
+ ori_model_path = study.best_trial.user_attrs["model_path"]
53
+ # os.rename(ori_model_path, saved_model_path)
54
+ os.makedirs(os.path.dirname(saved_model_path), exist_ok=True)
55
+ shutil.copy2(ori_model_path, saved_model_path)
56
+
57
+ # Save best parameters and model name to a YAML file
58
+ best_params = {
59
+ 'learning_rate': study.best_params.get('learning_rate', None),
60
+ 'hidden_size': study.best_params.get('hidden_size', None),
61
+ 'weight_decay': study.best_params.get('weight_decay', None)
62
+ }
63
+ with open(para_path, "w") as f:
64
+ yaml.dump(best_params, f, default_flow_style=False)
65
+
66
+ def main():
67
+ from philm import __version__
68
+ parser = argparse.ArgumentParser(description=f'PHILM {__version__} train model')
69
+ parser.add_argument('-c', '--config', required=True, type=str,
70
+ help='config file path')
71
+ args = parser.parse_args()
72
+ train_model(args)
73
+
74
+ if __name__ == '__main__':
75
+ main()