Spaces:
Sleeping
Sleeping
Deploy MOFScreen-Agent FastAPI backend
Browse files- Dockerfile +2 -0
- coprinet/LICENSE +21 -0
- coprinet/data/models/trained_inStock/lightning_logs/version_0/checkpoints/epoch=238-step=1295857.ckpt +3 -0
- coprinet/pricePrediction/ArgParser_base.py +112 -0
- coprinet/pricePrediction/config.py +50 -0
- coprinet/pricePrediction/dataManager/__init__.py +0 -0
- coprinet/pricePrediction/dataManager/dataManager.py +255 -0
- coprinet/pricePrediction/dataManager/dataset.py +171 -0
- coprinet/pricePrediction/evaluation/__init__.py +0 -0
- coprinet/pricePrediction/evaluation/evaluation.py +198 -0
- coprinet/pricePrediction/nets/FDS_imbalance.py +189 -0
- coprinet/pricePrediction/nets/__init__.py +0 -0
- coprinet/pricePrediction/nets/basicArchitectures.py +165 -0
- coprinet/pricePrediction/nets/netsGraph.py +236 -0
- coprinet/pricePrediction/predict/__init__.py +0 -0
- coprinet/pricePrediction/predict/__main__.py +4 -0
- coprinet/pricePrediction/predict/predict.py +137 -0
- coprinet/pricePrediction/preprocessData/__init__.py +0 -0
- coprinet/pricePrediction/preprocessData/prepareDataMol2Price.py +185 -0
- coprinet/pricePrediction/preprocessData/serializeDatapoints.py +15 -0
- coprinet/pricePrediction/preprocessData/smilesToDescriptors.py +239 -0
- coprinet/pricePrediction/preprocessData/smilesToGraph.py +160 -0
- coprinet/pricePrediction/selectFromRawData/__init__.py +0 -0
- coprinet/pricePrediction/selectFromRawData/selectFromRawDataNonVirtualAll.py +41 -0
- coprinet/pricePrediction/selectFromRawData/selectFromRawDataNonVirtualAll_base.py +76 -0
- coprinet/pricePrediction/selectFromRawData/selectFromRawDataOnlyVirtualAll.py +55 -0
- coprinet/pricePrediction/train/__init__.py +0 -0
- coprinet/pricePrediction/train/trainNet.py +147 -0
- coprinet/pricePrediction/utils.py +32 -0
- requirements.txt +5 -0
- tools/online_screening.py +3 -10
Dockerfile
CHANGED
|
@@ -13,6 +13,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
| 13 |
|
| 14 |
COPY requirements.txt /app/requirements.txt
|
| 15 |
RUN python -m pip install --upgrade pip && \
|
|
|
|
| 16 |
python -m pip install --no-cache-dir -r /app/requirements.txt
|
| 17 |
|
| 18 |
COPY agent /app/agent
|
|
@@ -21,6 +22,7 @@ COPY configs /app/configs
|
|
| 21 |
COPY data /app/data
|
| 22 |
COPY tools /app/tools
|
| 23 |
COPY trained_toxicity_models /app/trained_toxicity_models
|
|
|
|
| 24 |
|
| 25 |
EXPOSE 7860
|
| 26 |
|
|
|
|
| 13 |
|
| 14 |
COPY requirements.txt /app/requirements.txt
|
| 15 |
RUN python -m pip install --upgrade pip && \
|
| 16 |
+
python -m pip install --no-cache-dir --index-url https://download.pytorch.org/whl/cpu torch==2.6.0 && \
|
| 17 |
python -m pip install --no-cache-dir -r /app/requirements.txt
|
| 18 |
|
| 19 |
COPY agent /app/agent
|
|
|
|
| 22 |
COPY data /app/data
|
| 23 |
COPY tools /app/tools
|
| 24 |
COPY trained_toxicity_models /app/trained_toxicity_models
|
| 25 |
+
COPY coprinet /app/coprinet
|
| 26 |
|
| 27 |
EXPOSE 7860
|
| 28 |
|
coprinet/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2022 rsanchezgarc
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
coprinet/data/models/trained_inStock/lightning_logs/version_0/checkpoints/epoch=238-step=1295857.ckpt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ef10cad50629f328a2302d0301c50efbf2666d091692a4382beef8a18dfab152
|
| 3 |
+
size 25121165
|
coprinet/pricePrediction/ArgParser_base.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import abc
|
| 2 |
+
import argparse
|
| 3 |
+
import inspect
|
| 4 |
+
from argparse import ArgumentParser
|
| 5 |
+
|
| 6 |
+
from pricePrediction import config
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class ArgParseable(abc.ABC):
|
| 10 |
+
|
| 11 |
+
@property
|
| 12 |
+
@abc.abstractmethod
|
| 13 |
+
def DESIRED_PARAMS_TO_ASK(self): #TOOD: Properties always apply to instances, not classes. to be modified
|
| 14 |
+
raise NotImplementedError()
|
| 15 |
+
|
| 16 |
+
@classmethod
|
| 17 |
+
def fromTypeStrToType(cls, strType: str):
|
| 18 |
+
if strType == "str":
|
| 19 |
+
return str
|
| 20 |
+
elif strType == "float":
|
| 21 |
+
return float
|
| 22 |
+
elif strType == "int":
|
| 23 |
+
return int
|
| 24 |
+
elif strType == "bool":
|
| 25 |
+
return bool
|
| 26 |
+
else:
|
| 27 |
+
raise ValueError("Parser not recognized type: "+str(strType))
|
| 28 |
+
|
| 29 |
+
@classmethod
|
| 30 |
+
def fromDefaultStrToValue(cls, argname:str, typeFun:type, defaultStr: str): #Currently not used
|
| 31 |
+
'''
|
| 32 |
+
TO BE USED if defaults in the docstring E.g. :param int batch_size: Batch size. defaults to %(config.BATCH_SIZE)s
|
| 33 |
+
|
| 34 |
+
:param argname:
|
| 35 |
+
:param typeFun:
|
| 36 |
+
:param defaultStr:
|
| 37 |
+
:return:
|
| 38 |
+
'''
|
| 39 |
+
|
| 40 |
+
if defaultStr is None or defaultStr == "None":
|
| 41 |
+
return None
|
| 42 |
+
return typeFun(defaultStr)
|
| 43 |
+
|
| 44 |
+
@classmethod
|
| 45 |
+
def getArgsForParser(cls):
|
| 46 |
+
from docstring_parser import parse
|
| 47 |
+
docstring = cls.__init__.__doc__
|
| 48 |
+
config_vars = vars(config)
|
| 49 |
+
config_vars = { "config."+key: val for key,val in config_vars.items() }
|
| 50 |
+
docstring = docstring%config_vars
|
| 51 |
+
docstring = parse(docstring)
|
| 52 |
+
|
| 53 |
+
signature = inspect.signature(cls.__init__)
|
| 54 |
+
name_to_default = {
|
| 55 |
+
k: v.default
|
| 56 |
+
for k, v in signature.parameters.items()
|
| 57 |
+
if v.default is not inspect.Parameter.empty
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
params = []
|
| 61 |
+
for elem in docstring.params:
|
| 62 |
+
if elem.arg_name in cls.DESIRED_PARAMS_TO_ASK:
|
| 63 |
+
typeFun = cls.fromTypeStrToType(elem.type_name)
|
| 64 |
+
# print(elem.default, elem.description)
|
| 65 |
+
### default = cls.fromDefaultStrToValue(elem.arg_name, typeFun, elem.default)
|
| 66 |
+
default = name_to_default[elem.arg_name]
|
| 67 |
+
params.append((elem.arg_name, typeFun, default, elem.description))
|
| 68 |
+
return params
|
| 69 |
+
|
| 70 |
+
# @classmethod
|
| 71 |
+
# def _addParamToArgParse(cls, parser:ArgumentParser, paramTuple):
|
| 72 |
+
# name, typeFun, default, help= paramTuple
|
| 73 |
+
# if typeFun == bool:
|
| 74 |
+
# if default is True:
|
| 75 |
+
# name = "no_"+name
|
| 76 |
+
# help = "Deactivate "+help
|
| 77 |
+
# parser.add_argument("--" + name, help=help+" Default=%(default)s", action="store_true")
|
| 78 |
+
# else:
|
| 79 |
+
# parser.add_argument("--"+name, type=typeFun, help=help+" Default=%(default)s", default=default)
|
| 80 |
+
# return parser
|
| 81 |
+
|
| 82 |
+
@classmethod
|
| 83 |
+
def addParamsToArgParse(cls, parser: ArgumentParser):
|
| 84 |
+
'''
|
| 85 |
+
|
| 86 |
+
:param parser: Argparser to which methods will be added
|
| 87 |
+
:return: the input parser but with added arguments
|
| 88 |
+
'''
|
| 89 |
+
for paramTuple in cls.getArgsForParser():
|
| 90 |
+
name, typeFun, default, help= paramTuple
|
| 91 |
+
if typeFun == bool:
|
| 92 |
+
if default is True:
|
| 93 |
+
action="store_false"
|
| 94 |
+
else:
|
| 95 |
+
action="store_true"
|
| 96 |
+
help += " Action: "+action
|
| 97 |
+
parser.add_argument("--" + name, help=help, action=action)
|
| 98 |
+
else:
|
| 99 |
+
parser.add_argument("--"+name, type=typeFun, help=help+" Default=%(default)s", default=default)
|
| 100 |
+
return parser
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
class MyArgParser(argparse.ArgumentParser):
|
| 105 |
+
def parse_args(self, args=None, namespace=None):
|
| 106 |
+
args = super().parse_args(args=args, namespace=namespace)
|
| 107 |
+
arg_groups = {}
|
| 108 |
+
for group in self._action_groups:
|
| 109 |
+
group_dict = {a.dest: getattr(args, a.dest, None) for a in group._group_actions}
|
| 110 |
+
arg_groups[group.title] = group_dict
|
| 111 |
+
|
| 112 |
+
return arg_groups
|
coprinet/pricePrediction/config.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
################## Common options ##################
|
| 4 |
+
NUM_BUCKETS = 100
|
| 5 |
+
N_CPUS = 12
|
| 6 |
+
|
| 7 |
+
MULTIPROC_PREFETCH_FACTOR = 4
|
| 8 |
+
MULTIPROC_TIMEOUT = 60
|
| 9 |
+
PERSISTENT_WORKERS = False
|
| 10 |
+
|
| 11 |
+
BATCH_SIZE = 512
|
| 12 |
+
NUM_WORKERS_PER_GPU = 2
|
| 13 |
+
BUFFER_N_BATCHES_FOR_PRED=50 #Process as many batches together for speed. Set it as large as possible to fit your RAM memory
|
| 14 |
+
|
| 15 |
+
USE_MMOL_INSTEAD_GRAM = True
|
| 16 |
+
|
| 17 |
+
RANDOM_SEED = 121
|
| 18 |
+
|
| 19 |
+
################## Net ##################
|
| 20 |
+
N_LAYERS=10
|
| 21 |
+
N_HIDDEN_NODE=75
|
| 22 |
+
N_HIDDEN_EDGE=50
|
| 23 |
+
|
| 24 |
+
LEARNING_RATE= 1e-5
|
| 25 |
+
PATIENT_REDUCE_LR_PLATEAU_N_EPOCHS = 25
|
| 26 |
+
COSINE_LR_SCHEDULE_N_EPOCHS = 10
|
| 27 |
+
|
| 28 |
+
############# Data ##################
|
| 29 |
+
|
| 30 |
+
TEST_SIZE= int(2e5)
|
| 31 |
+
CSV_CHUNK_SIZE= int(1e5)
|
| 32 |
+
|
| 33 |
+
DATA_DIR = os.path.abspath(os.path.join(__file__, "../.."))
|
| 34 |
+
|
| 35 |
+
RAW_DATA_DIRNAME= "data/MCule" #Can be provided via command line arguments
|
| 36 |
+
BUILDING_BLOCKS_FNAME= os.path.join(RAW_DATA_DIRNAME, "mcule_purchasable_building_blocks_210806.smi.gz")
|
| 37 |
+
|
| 38 |
+
DATASET_DIRNAME = "data/mcule_chunks"
|
| 39 |
+
DATASET_IN_MEMORY = True # Set to true to load the dataset into memory
|
| 40 |
+
ENCODED_DIR = os.path.join(DATASET_DIRNAME, "encoded")
|
| 41 |
+
|
| 42 |
+
RAW_DATA_FILE_SUFFIX = r"mcule_full_(train|test|val)\.csv_split_\w+\.csv$"
|
| 43 |
+
|
| 44 |
+
DEFAULT_MODEL = os.path.join( DATA_DIR, "data/models/trained_inStock/lightning_logs/version_0/checkpoints/epoch=238-step=1295857.ckpt")
|
| 45 |
+
# DEFAULT_MODEL = os.path.join( DATA_DIR, "data/models/trained_virtual/lightning_logs/version_0/checkpoints/epoch=172-step=3231639.ckpt")
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
######################### Ignore the next lines if not running a comparison ################
|
| 49 |
+
USE_FEATURES_NET = False #set this to True to use a MLP instead of GNN
|
| 50 |
+
NORMALIZE_NODE_FEATS = False #set this to True if USE_FEATURES_NET==True
|
coprinet/pricePrediction/dataManager/__init__.py
ADDED
|
File without changes
|
coprinet/pricePrediction/dataManager/dataManager.py
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import atexit
|
| 2 |
+
import logging
|
| 3 |
+
import os
|
| 4 |
+
import shutil
|
| 5 |
+
|
| 6 |
+
import dirsync
|
| 7 |
+
import joblib
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
import pytorch_lightning as pl
|
| 11 |
+
from pytorch_lightning import seed_everything
|
| 12 |
+
|
| 13 |
+
from torch.utils.data import DataLoader, Subset
|
| 14 |
+
|
| 15 |
+
from pricePrediction import config
|
| 16 |
+
from pricePrediction.ArgParser_base import ArgParseable
|
| 17 |
+
from pricePrediction.dataManager.dataset import Dataset_graphPrice
|
| 18 |
+
from pricePrediction.preprocessData.prepareDataMol2Price import DataBuilder
|
| 19 |
+
from pricePrediction.utils import EncodedDirNamesAndTemplates
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class GraphPriceDatamodule(pl.LightningDataModule, ArgParseable):
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
DESIRED_PARAMS_TO_ASK= ['encodedDir', 'batch_size', 'num_workers', 'use_lds', 'use_weights', 'force_compute_data',
|
| 26 |
+
'do_undersample', 'augment_labels', 'copy_data_to_wdir', 'debug']
|
| 27 |
+
|
| 28 |
+
def __init__(self, encodedDir=config.ENCODED_DIR, deg_fname=None, batch_size: int = config.BATCH_SIZE,
|
| 29 |
+
num_workers: int = config.NUM_WORKERS_PER_GPU, random_seed: int = config.RANDOM_SEED,
|
| 30 |
+
use_log_for_target: bool = True, use_lds: bool = False,
|
| 31 |
+
use_weights: bool = False, force_compute_data: bool = False, do_undersample: bool = False,
|
| 32 |
+
augment_labels: bool =True, shuffle_training: bool =True, copy_data_to_wdir: str = None, debug =False):
|
| 33 |
+
'''The DataManager that will create several DataLoader(s)
|
| 34 |
+
|
| 35 |
+
:param str encodedDir: The directory where the dataset has been prepared as lmdb files.
|
| 36 |
+
:param str deg_fname: The file containing the statistics about the nodes degree. By default is automatically searched within
|
| 37 |
+
encodedDir.
|
| 38 |
+
:param int batch_size: Batch size.
|
| 39 |
+
:param int num_workers: Number of workers as in "pytorch.DataLoader".
|
| 40 |
+
:param int random_seed: Random seed.
|
| 41 |
+
:param bool use_log_for_target: Apply log to the target (log(price)).
|
| 42 |
+
:param bool use_lds: Use label density smoothing to reduce data imbalance.
|
| 43 |
+
:param bool use_weights: Apply weights to reduce data imbalance.
|
| 44 |
+
:param bool force_compute_data: Recompute all data before using the DataManager. Takes some time.
|
| 45 |
+
:param bool do_undersample: Use undersampling to reduce data imbalance.
|
| 46 |
+
:param bool augment_labels: Add random perturbation to training labels to reduce overfitting.
|
| 47 |
+
:param bool shuffle_training: Shuffle training data.
|
| 48 |
+
:param str copy_data_to_wdir: An optional working dir where data can be copied. Useful to avoid network disk usage.
|
| 49 |
+
:param bool debug: Use only a small subset of the dataset
|
| 50 |
+
'''
|
| 51 |
+
|
| 52 |
+
seed_everything(random_seed)
|
| 53 |
+
super().__init__()
|
| 54 |
+
|
| 55 |
+
if encodedDir is None:
|
| 56 |
+
encodedDir = config.ENCODED_DIR
|
| 57 |
+
if deg_fname is None:
|
| 58 |
+
names = EncodedDirNamesAndTemplates(encodedDir)
|
| 59 |
+
deg_fname = names.DEGREES_FNAME
|
| 60 |
+
|
| 61 |
+
self.deg_fname = deg_fname
|
| 62 |
+
self.do_undersample = do_undersample
|
| 63 |
+
self.use_weights = use_weights or use_lds
|
| 64 |
+
self.use_lds = use_lds
|
| 65 |
+
self.use_log_for_target = use_log_for_target
|
| 66 |
+
self.random_seed = random_seed
|
| 67 |
+
|
| 68 |
+
self.copy_data_to_wdir = copy_data_to_wdir
|
| 69 |
+
|
| 70 |
+
self.encodedDir = encodedDir
|
| 71 |
+
self.batch_size = batch_size
|
| 72 |
+
self.num_workers = num_workers
|
| 73 |
+
self.force_compute_data = force_compute_data
|
| 74 |
+
self.augment_labels = augment_labels
|
| 75 |
+
self.shuffle_training = shuffle_training
|
| 76 |
+
|
| 77 |
+
self.debug = debug
|
| 78 |
+
|
| 79 |
+
###### Delayed variables
|
| 80 |
+
self._dims = None
|
| 81 |
+
self._train_n_instances = None
|
| 82 |
+
self.setup_done = False
|
| 83 |
+
self.dataset_train = None
|
| 84 |
+
self.dataset_val = None
|
| 85 |
+
self.dataset_test = None
|
| 86 |
+
|
| 87 |
+
def _getBucketsFname(self, training_phase):
|
| 88 |
+
names = EncodedDirNamesAndTemplates(self.encodedDir)
|
| 89 |
+
return names.BUCKETS_FNAME_TEMPLATE % training_phase
|
| 90 |
+
|
| 91 |
+
@property
|
| 92 |
+
def dims(self):
|
| 93 |
+
if self._dims is None:
|
| 94 |
+
metadata_fname = EncodedDirNamesAndTemplates(self.encodedDir).DATASET_METADATA_FNAME_TEMPLATE % "train"
|
| 95 |
+
size_metadata_info = joblib.load(metadata_fname)
|
| 96 |
+
self._dims = (size_metadata_info["nodes_n_features"], size_metadata_info["edges_n_features"])
|
| 97 |
+
self._train_n_instances = size_metadata_info["total_size"]
|
| 98 |
+
return self._dims
|
| 99 |
+
|
| 100 |
+
def get_nodes_degree(self):
|
| 101 |
+
if os.path.isfile( self.deg_fname ):
|
| 102 |
+
return joblib.load(self.deg_fname)
|
| 103 |
+
else:
|
| 104 |
+
self.prepare_data()
|
| 105 |
+
return self.get_nodes_degree()
|
| 106 |
+
|
| 107 |
+
def prepare_data(self):
|
| 108 |
+
# Compute if not already done
|
| 109 |
+
if self.force_compute_data:
|
| 110 |
+
dataBuilder = DataBuilder()
|
| 111 |
+
dataBuilder.prepareDataset(encodedDir=self.encodedDir, datasetSplit="train")
|
| 112 |
+
dataBuilder.prepareDataset(encodedDir=self.encodedDir, datasetSplit="test")
|
| 113 |
+
dataBuilder.prepareDataset(encodedDir=self.encodedDir, datasetSplit="val")
|
| 114 |
+
else:
|
| 115 |
+
dataBuilder = DataBuilder()
|
| 116 |
+
if not os.path.exists(os.path.join(self.encodedDir, "train")):
|
| 117 |
+
dataBuilder.prepareDataset(encodedDir=self.encodedDir, datasetSplit="train")
|
| 118 |
+
if not os.path.exists(os.path.join(self.encodedDir, "test")):
|
| 119 |
+
dataBuilder.prepareDataset(encodedDir=self.encodedDir, datasetSplit="test")
|
| 120 |
+
if not os.path.exists(os.path.join(self.encodedDir, "val")):
|
| 121 |
+
dataBuilder.prepareDataset(encodedDir=self.encodedDir, datasetSplit="val")
|
| 122 |
+
|
| 123 |
+
# if self.copy_data_to_wdir:
|
| 124 |
+
# tmpArgs = {}
|
| 125 |
+
# print(self.copy_data_to_wdir, os.path.isdir(self.copy_data_to_wdir))
|
| 126 |
+
# if not os.path.isdir(self.copy_data_to_wdir):
|
| 127 |
+
# tmpArgs["prefix"] = self.copy_data_to_wdir
|
| 128 |
+
# self._tmpdir = TemporaryDirectory(**tmpArgs)
|
| 129 |
+
# def cleanTmpdir():
|
| 130 |
+
# del self._tmpdir
|
| 131 |
+
# atexit.register( cleanTmpdir)
|
| 132 |
+
#
|
| 133 |
+
# self.copy_data_to_wdir = self._tmpdir.name
|
| 134 |
+
|
| 135 |
+
if self.copy_data_to_wdir:
|
| 136 |
+
if not os.path.isdir(self.copy_data_to_wdir):
|
| 137 |
+
os.makedirs(self.copy_data_to_wdir)
|
| 138 |
+
_tmpdir = self.copy_data_to_wdir
|
| 139 |
+
def cleanTmpdir():
|
| 140 |
+
shutil.rmtree( _tmpdir)
|
| 141 |
+
atexit.register(cleanTmpdir)
|
| 142 |
+
|
| 143 |
+
print("copying training data from %s to %s" % (self.encodedDir, self.copy_data_to_wdir))
|
| 144 |
+
dirsync.sync(self.encodedDir, self.copy_data_to_wdir, 'sync', verbose=True, logger=logging.getLogger('dummy'))
|
| 145 |
+
|
| 146 |
+
def setup(self, stage=None):
|
| 147 |
+
if self.dataset_train is not None and self.dataset_val is not None and self.dataset_test is not None:
|
| 148 |
+
return
|
| 149 |
+
|
| 150 |
+
if self.copy_data_to_wdir is not None:
|
| 151 |
+
encodedDir = self.copy_data_to_wdir
|
| 152 |
+
else:
|
| 153 |
+
encodedDir = self.encodedDir
|
| 154 |
+
|
| 155 |
+
self.dataset_train = Dataset_graphPrice(encodedDir, datasetSplit="train",
|
| 156 |
+
use_log_for_target=self.use_log_for_target,
|
| 157 |
+
use_weights=self.use_weights, augment_labels = self.augment_labels,
|
| 158 |
+
buckets_fname=self._getBucketsFname("train"),
|
| 159 |
+
use_lds=self.use_lds, do_undersample=self.do_undersample)
|
| 160 |
+
|
| 161 |
+
self.dataset_val = Dataset_graphPrice(encodedDir, datasetSplit="val",
|
| 162 |
+
use_log_for_target=self.use_log_for_target,
|
| 163 |
+
use_weights=self.use_weights, augment_labels = False,
|
| 164 |
+
buckets_fname=self._getBucketsFname("val"),
|
| 165 |
+
use_lds=self.use_lds, do_undersample=False)
|
| 166 |
+
|
| 167 |
+
self.dataset_test = Dataset_graphPrice(encodedDir, datasetSplit="test",
|
| 168 |
+
use_log_for_target=self.use_log_for_target,
|
| 169 |
+
use_weights=self.use_weights, augment_labels = False,
|
| 170 |
+
buckets_fname=self._getBucketsFname("test"),
|
| 171 |
+
use_lds=self.use_lds, do_undersample=False)
|
| 172 |
+
|
| 173 |
+
atexit.register( self.dataset_train.close )
|
| 174 |
+
atexit.register( self.dataset_val.close )
|
| 175 |
+
atexit.register( self.dataset_test.close )
|
| 176 |
+
|
| 177 |
+
def _generic_dataloader(self, dataset, num_workers=None, **kwargs):
|
| 178 |
+
if num_workers is None:
|
| 179 |
+
num_workers = self.num_workers
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
if num_workers>0 and "timeout" not in kwargs:
|
| 183 |
+
kwargs["timeout"] = config.MULTIPROC_TIMEOUT
|
| 184 |
+
|
| 185 |
+
if num_workers > 0 and "prefetch_factor" not in kwargs:
|
| 186 |
+
kwargs["prefetch_factor"] = config.MULTIPROC_PREFETCH_FACTOR
|
| 187 |
+
|
| 188 |
+
if num_workers > 0 and "persistent_workers" not in kwargs:
|
| 189 |
+
kwargs["persistent_workers"] = config.PERSISTENT_WORKERS
|
| 190 |
+
|
| 191 |
+
print("Using num_workers:", num_workers)
|
| 192 |
+
#print(self.trainer.reload_dataloaders_every_epoch)
|
| 193 |
+
|
| 194 |
+
from torch_geometric.data import Batch
|
| 195 |
+
def from_list_of_examples_to_batch(examples):
|
| 196 |
+
graphs, ys = zip(* examples)
|
| 197 |
+
g_batch = Batch.from_data_list(graphs)
|
| 198 |
+
ys = torch.stack(ys)
|
| 199 |
+
return [g_batch, ys]
|
| 200 |
+
if self.debug:
|
| 201 |
+
dataset = Subset(dataset, range(21*self.batch_size))
|
| 202 |
+
return DataLoader(dataset, num_workers=num_workers, batch_size=self.batch_size,
|
| 203 |
+
pin_memory=num_workers>0, collate_fn = from_list_of_examples_to_batch,
|
| 204 |
+
**kwargs)
|
| 205 |
+
|
| 206 |
+
def train_dataloader(self, **kwargs):
|
| 207 |
+
if self.trainer:
|
| 208 |
+
print("Generating a train_dataloader at epoch: %d"% ( self.trainer.current_epoch ))
|
| 209 |
+
if "persistent_workers" not in kwargs:
|
| 210 |
+
kwargs["persistent_workers"] = False
|
| 211 |
+
return self._generic_dataloader(self.dataset_train, shuffle=True, **kwargs)
|
| 212 |
+
|
| 213 |
+
def val_dataloader(self, **kwargs):
|
| 214 |
+
return self._generic_dataloader(self.dataset_val, shuffle=False, **kwargs)
|
| 215 |
+
|
| 216 |
+
def test_dataloader(self, **kwargs):
|
| 217 |
+
return self._generic_dataloader(self.dataset_test, shuffle=False, **kwargs)
|
| 218 |
+
|
| 219 |
+
def predict_dataloader(self, dataset=None):
|
| 220 |
+
|
| 221 |
+
if dataset == "train":
|
| 222 |
+
raise NotImplementedError()
|
| 223 |
+
elif dataset == "test":
|
| 224 |
+
raise NotImplementedError()
|
| 225 |
+
elif dataset == "val":
|
| 226 |
+
raise NotImplementedError()
|
| 227 |
+
if dataset is not None: # TODO: check type
|
| 228 |
+
raise NotImplementedError()
|
| 229 |
+
else:
|
| 230 |
+
raise NotImplementedError()
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
if __name__ == "__main__":
|
| 235 |
+
|
| 236 |
+
from pricePrediction.ArgParser_base import ArgParseable, MyArgParser
|
| 237 |
+
|
| 238 |
+
parser = MyArgParser(prog=None, usage=None, description=None, )
|
| 239 |
+
|
| 240 |
+
group = parser.add_argument_group(title="data")
|
| 241 |
+
GraphPriceDatamodule.addParamsToArgParse(group)
|
| 242 |
+
cmd_args = parser.parse_args()
|
| 243 |
+
dm = GraphPriceDatamodule(**cmd_args.get("data", {}))
|
| 244 |
+
dm.setup()
|
| 245 |
+
allPrices = []
|
| 246 |
+
for batchG, batchY in dm.test_dataloader():
|
| 247 |
+
allPrices.extend( batchY.tolist() )
|
| 248 |
+
|
| 249 |
+
import matplotlib.pyplot as plt
|
| 250 |
+
plt.hist(allPrices, bins=200)
|
| 251 |
+
plt.show()
|
| 252 |
+
|
| 253 |
+
'''
|
| 254 |
+
python -m pricePrediction.dataManager.dataManager
|
| 255 |
+
'''
|
coprinet/pricePrediction/dataManager/dataset.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import operator
|
| 2 |
+
import os
|
| 3 |
+
import joblib
|
| 4 |
+
import lmdb
|
| 5 |
+
import numpy as np
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
from torch.utils.data import Dataset
|
| 9 |
+
|
| 10 |
+
from pricePrediction import config
|
| 11 |
+
from pricePrediction.preprocessData.serializeDatapoints import getExampleId, deserializeExample
|
| 12 |
+
from pricePrediction.utils import search_buckedId, EncodedDirNamesAndTemplates
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class Dataset_graphPrice(Dataset):
|
| 16 |
+
def __init__(self, encodedDir, datasetSplit, use_log_for_target=True, use_weights=False, buckets_fname=None,
|
| 17 |
+
use_lds=False, do_undersample=False, augment_labels=False, store_in_memory=config.DATASET_IN_MEMORY,
|
| 18 |
+
normalize_node_feats=config.NORMALIZE_NODE_FEATS):
|
| 19 |
+
|
| 20 |
+
self.use_log_for_target = use_log_for_target
|
| 21 |
+
self.use_weights = use_weights
|
| 22 |
+
self.buckets_fname = buckets_fname
|
| 23 |
+
self.use_lds = use_lds
|
| 24 |
+
self.do_undersample = do_undersample
|
| 25 |
+
self.augment_labels = augment_labels
|
| 26 |
+
self.store_in_memory = store_in_memory
|
| 27 |
+
self.normalize_node_feats = normalize_node_feats
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
assert (self.buckets_fname is not None and self.use_weights) or not self.use_weights
|
| 31 |
+
if self.do_undersample: raise NotImplementedError()
|
| 32 |
+
|
| 33 |
+
dataNames = EncodedDirNamesAndTemplates(encodedDir)
|
| 34 |
+
size_metadata_info = joblib.load( dataNames.DATASET_METADATA_FNAME_TEMPLATE % datasetSplit)
|
| 35 |
+
|
| 36 |
+
if self.normalize_node_feats:
|
| 37 |
+
self.min_x = torch.FloatTensor(size_metadata_info["feature_stats"]["min"])
|
| 38 |
+
self.max_x = torch.FloatTensor(size_metadata_info["feature_stats"]["max"])
|
| 39 |
+
self.range_x = self.max_x - self.min_x
|
| 40 |
+
assert not torch.isclose(self.range_x, torch.zeros(1)).any(), f"Error, range close to 0, {torch.where(torch.isclose(self.range_x, torch.zeros(1)))}"
|
| 41 |
+
assert not torch.isinf(self.range_x).any()
|
| 42 |
+
|
| 43 |
+
self.total_size = size_metadata_info["total_size"]
|
| 44 |
+
self.size_per_file = size_metadata_info["sizes_list"]
|
| 45 |
+
self.db_fnames = [ os.path.join( encodedDir, datasetSplit, os.path.basename(fname))
|
| 46 |
+
for fname in size_metadata_info["fnames_list"]]
|
| 47 |
+
self.cum_size_at_file = np.cumsum(np.array(size_metadata_info["sizes_list"], dtype=int))
|
| 48 |
+
self.last_cum_elemIdx_at_file = self.cum_size_at_file - 1
|
| 49 |
+
|
| 50 |
+
self.fileIdx_to_fileBasename = [os.path.basename(fname) for fname in self.db_fnames]
|
| 51 |
+
|
| 52 |
+
#Delayed load
|
| 53 |
+
self.db_managers= [None]*len(self.size_per_file)
|
| 54 |
+
|
| 55 |
+
self._bucket_ranges = None
|
| 56 |
+
self._n_per_bucket = None
|
| 57 |
+
self._cache = {}
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def _idx_to_file_and_num(self, idx):
|
| 61 |
+
fileIdx = search_buckedId(idx, self.last_cum_elemIdx_at_file)
|
| 62 |
+
idx = idx - (self.cum_size_at_file[fileIdx - 1] if fileIdx - 1 >= 0 else 0)
|
| 63 |
+
return fileIdx, idx
|
| 64 |
+
|
| 65 |
+
def _init_db(self, fileIdx):
|
| 66 |
+
env = lmdb.open(self.db_fnames[fileIdx],
|
| 67 |
+
readonly=True, lock=False,
|
| 68 |
+
readahead=False, meminit=False)
|
| 69 |
+
txn = env.begin()
|
| 70 |
+
self.db_managers[fileIdx] = (env, txn)
|
| 71 |
+
return env, txn
|
| 72 |
+
|
| 73 |
+
@property
|
| 74 |
+
def bucket_ranges(self):
|
| 75 |
+
if self._bucket_ranges is None:
|
| 76 |
+
self._getBuckets()
|
| 77 |
+
return self._bucket_ranges
|
| 78 |
+
|
| 79 |
+
@property
|
| 80 |
+
def n_per_bucket(self):
|
| 81 |
+
if self._n_per_bucket is None:
|
| 82 |
+
self._getBuckets()
|
| 83 |
+
return self._n_per_bucket
|
| 84 |
+
|
| 85 |
+
def n_batches(self, batch_size):
|
| 86 |
+
return len(self)//batch_size + int(bool(len(self)%batch_size))
|
| 87 |
+
|
| 88 |
+
def _getBuckets(self):
|
| 89 |
+
buckets = joblib.load(self.buckets_fname)
|
| 90 |
+
bucket_ranges, n_per_bucket = buckets["bucket_ranges"], buckets["n_per_bucket"]
|
| 91 |
+
|
| 92 |
+
if self.use_lds:
|
| 93 |
+
print("using lds")
|
| 94 |
+
from scipy.ndimage import gaussian_filter1d
|
| 95 |
+
n_per_bucket = gaussian_filter1d(n_per_bucket, sigma=2)
|
| 96 |
+
self._bucket_ranges = bucket_ranges
|
| 97 |
+
self._n_per_bucket = n_per_bucket
|
| 98 |
+
|
| 99 |
+
def compute_weight(self, label):
|
| 100 |
+
if not self.use_log_for_target:
|
| 101 |
+
label = np.exp(label)
|
| 102 |
+
bucket_num = search_buckedId(label, self.bucket_ranges)
|
| 103 |
+
w = self.total_size / (1 + self.n_per_bucket[bucket_num])
|
| 104 |
+
return w
|
| 105 |
+
|
| 106 |
+
def prepare_undersample(self, maximum_percentile=80):
|
| 107 |
+
maximum_per_bucket = np.percentile(self.n_per_bucket, maximum_percentile)
|
| 108 |
+
prob_per_bucket = maximum_per_bucket / (1e-10 + self.n_per_bucket)
|
| 109 |
+
prob_per_bucket = np.where(prob_per_bucket < 1, prob_per_bucket, 1)
|
| 110 |
+
print("maximum_per_bucket while sampling: %d" % maximum_per_bucket)
|
| 111 |
+
new_len = sum([min(maximum_per_bucket, n_elems) for n_elems in self.n_per_bucket])
|
| 112 |
+
|
| 113 |
+
def predicate(graph_y):
|
| 114 |
+
y = graph_y[1]
|
| 115 |
+
bucket_num = search_buckedId(y, self.bucket_ranges)
|
| 116 |
+
return np.random.rand() < prob_per_bucket[bucket_num]
|
| 117 |
+
|
| 118 |
+
# dataset = dataset.select(predicate)
|
| 119 |
+
raise NotImplementedError() #This code is obsolete and requires refactoring
|
| 120 |
+
|
| 121 |
+
def normalize_x(self, x):
|
| 122 |
+
return (x-self.min_x)/self.range_x
|
| 123 |
+
|
| 124 |
+
def __getitem__(self, index):
|
| 125 |
+
|
| 126 |
+
out = self._cache.get(index, None)
|
| 127 |
+
if out is not None:
|
| 128 |
+
return out
|
| 129 |
+
else:
|
| 130 |
+
if index >= self.total_size:
|
| 131 |
+
raise IndexError()
|
| 132 |
+
fileIdx, pointIdx = self._idx_to_file_and_num(index)
|
| 133 |
+
db_manager = self.db_managers[fileIdx]
|
| 134 |
+
if db_manager is None:
|
| 135 |
+
txn= self._init_db(fileIdx)[-1]
|
| 136 |
+
else:
|
| 137 |
+
txn = db_manager[-1]
|
| 138 |
+
|
| 139 |
+
dataBytes = txn.get(getExampleId(self.fileIdx_to_fileBasename[fileIdx], pointIdx))
|
| 140 |
+
if dataBytes is None:
|
| 141 |
+
msg = "Error:\n%s %s %s %s"%(index, fileIdx, pointIdx, getExampleId(self.fileIdx_to_fileBasename[fileIdx], pointIdx))
|
| 142 |
+
raise Exception(msg)
|
| 143 |
+
graph, label = deserializeExample(dataBytes)
|
| 144 |
+
|
| 145 |
+
if self.normalize_node_feats:
|
| 146 |
+
graph.x = self.normalize_x(graph.x)
|
| 147 |
+
|
| 148 |
+
if self.use_log_for_target:
|
| 149 |
+
label = np.log(label)
|
| 150 |
+
|
| 151 |
+
if self.augment_labels:
|
| 152 |
+
label = label * (1+0.05*np.random.rand()-0.025)
|
| 153 |
+
|
| 154 |
+
if self.use_weights:
|
| 155 |
+
graph.w = self.compute_weight(label)
|
| 156 |
+
else:
|
| 157 |
+
graph.w = 1
|
| 158 |
+
graph.y = label
|
| 159 |
+
if self.store_in_memory:
|
| 160 |
+
self._cache[index] = (graph, label)
|
| 161 |
+
return graph, label
|
| 162 |
+
|
| 163 |
+
def __len__(self):
|
| 164 |
+
return self.total_size
|
| 165 |
+
|
| 166 |
+
def close(self):
|
| 167 |
+
for db_handler in self.db_managers:
|
| 168 |
+
if db_handler:
|
| 169 |
+
env, txn = db_handler
|
| 170 |
+
env.close()
|
| 171 |
+
|
coprinet/pricePrediction/evaluation/__init__.py
ADDED
|
File without changes
|
coprinet/pricePrediction/evaluation/evaluation.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, io
|
| 2 |
+
from datetime import datetime
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
import matplotlib.pyplot as plt
|
| 7 |
+
from scipy.stats import pearsonr, spearmanr
|
| 8 |
+
from pytorch_lightning.callbacks import Callback
|
| 9 |
+
|
| 10 |
+
def plt_to_numpy(fig):
|
| 11 |
+
#it should be called after having done the plt.plot stuff, in replacement of plt.show()
|
| 12 |
+
#TO be used with torch tensorboard
|
| 13 |
+
from skimage.color import rgba2rgb
|
| 14 |
+
io_buf = io.BytesIO()
|
| 15 |
+
fig.savefig(io_buf, format='raw', dpi=fig.dpi)
|
| 16 |
+
io_buf.seek(0)
|
| 17 |
+
img_arr = np.reshape(np.frombuffer(io_buf.getvalue(), dtype=np.uint8),
|
| 18 |
+
newshape=(int(fig.bbox.bounds[3]), int(fig.bbox.bounds[2]), -1))
|
| 19 |
+
img_arr = rgba2rgb( img_arr )
|
| 20 |
+
img_arr = img_arr.astype(np.float32)
|
| 21 |
+
io_buf.close()
|
| 22 |
+
return img_arr
|
| 23 |
+
|
| 24 |
+
class InferencePlotter():
|
| 25 |
+
def __init__(self, pl_module: 'pl.LightningModule'):
|
| 26 |
+
self.pl_module = pl_module
|
| 27 |
+
self.data_loaders = {}
|
| 28 |
+
|
| 29 |
+
def get_dataloader(self, mode, n_workers=None):
|
| 30 |
+
|
| 31 |
+
if mode in self.data_loaders:
|
| 32 |
+
return self.data_loaders[mode]
|
| 33 |
+
else:
|
| 34 |
+
if mode == "train":
|
| 35 |
+
dataloader = self.pl_module.train_dataloader(num_workers=n_workers)
|
| 36 |
+
elif mode == "val":
|
| 37 |
+
dataloader = self.pl_module.val_dataloader(num_workers=n_workers)
|
| 38 |
+
elif mode == "test":
|
| 39 |
+
dataloader = self.pl_module.test_dataloader(num_workers=n_workers)
|
| 40 |
+
else:
|
| 41 |
+
raise ValueError("Error. Option mode=%s not supported" % mode)
|
| 42 |
+
|
| 43 |
+
self.data_loaders[mode] = dataloader
|
| 44 |
+
return dataloader
|
| 45 |
+
|
| 46 |
+
@classmethod
|
| 47 |
+
def do_plots(cls, ys, y_preds, label="test", tensorboard_wrapper=None, step=0):
|
| 48 |
+
|
| 49 |
+
if tensorboard_wrapper:
|
| 50 |
+
figArgs = dict(figsize=(5, 4), dpi=450)
|
| 51 |
+
else:
|
| 52 |
+
figArgs = {}
|
| 53 |
+
fig = plt.figure(**figArgs)
|
| 54 |
+
plt.hist(ys, bins=200)
|
| 55 |
+
name = label + " price hist"
|
| 56 |
+
plt.title(name)
|
| 57 |
+
img_arr = plt_to_numpy(fig)
|
| 58 |
+
if tensorboard_wrapper:
|
| 59 |
+
tensorboard_wrapper.add_image(name, img_arr.transpose([2, 0, 1]), step)
|
| 60 |
+
plt.close()
|
| 61 |
+
else:
|
| 62 |
+
plt.show()
|
| 63 |
+
|
| 64 |
+
if y_preds is not None:
|
| 65 |
+
fig = plt.figure(**figArgs)
|
| 66 |
+
plt.hist(y_preds, bins=200)
|
| 67 |
+
name = label + " pred hist"
|
| 68 |
+
plt.title(name)
|
| 69 |
+
img_arr = plt_to_numpy(fig)
|
| 70 |
+
if tensorboard_wrapper:
|
| 71 |
+
tensorboard_wrapper.add_image(name, img_arr.transpose([2, 0, 1]), step)
|
| 72 |
+
plt.close()
|
| 73 |
+
else:
|
| 74 |
+
plt.show()
|
| 75 |
+
|
| 76 |
+
r, p = pearsonr(ys, y_preds)
|
| 77 |
+
rho, p2 = spearmanr(ys, y_preds)
|
| 78 |
+
if tensorboard_wrapper:
|
| 79 |
+
tensorboard_wrapper.add_scalar(label + '_pearson-r', r, step)
|
| 80 |
+
tensorboard_wrapper.add_scalar(label + '_spearman-r', rho, step)
|
| 81 |
+
|
| 82 |
+
if label == "test":
|
| 83 |
+
print("pearson-r", r, "p-val", p)
|
| 84 |
+
print("spearman-r", rho, "p-val", p)
|
| 85 |
+
else:
|
| 86 |
+
print("pearson-r", r, "p-val", p)
|
| 87 |
+
print("spearman-r", rho, "p-val", p)
|
| 88 |
+
|
| 89 |
+
fig = plt.figure(**figArgs)
|
| 90 |
+
max_val = max([9, ys.max()])
|
| 91 |
+
min_val = 1
|
| 92 |
+
plt.hexbin(ys, y_preds, bins="log", gridsize=100, extent=[min_val, max_val, min_val, max_val] )
|
| 93 |
+
plt.axline((1,1), slope=1, c="r", lw=1, ls="--", alpha=0.5)
|
| 94 |
+
plt.xlabel("log(price)")
|
| 95 |
+
plt.ylabel("Pred")
|
| 96 |
+
plt.colorbar()
|
| 97 |
+
name = label + " price vs pred"
|
| 98 |
+
plt.title(name)
|
| 99 |
+
img_arr = plt_to_numpy(fig)
|
| 100 |
+
|
| 101 |
+
if tensorboard_wrapper:
|
| 102 |
+
tensorboard_wrapper.add_image(name, img_arr.transpose([2, 0, 1]), step)
|
| 103 |
+
plt.close()
|
| 104 |
+
else:
|
| 105 |
+
plt.show()
|
| 106 |
+
|
| 107 |
+
# fig = plt.figure(**figArgs)
|
| 108 |
+
# plt.boxplot( abs(y_preds - ys)/ys )
|
| 109 |
+
# name = method_name + "_relative_error_boxplot"
|
| 110 |
+
# plt.title(name)
|
| 111 |
+
# img_arr = plt_to_numpy(fig)
|
| 112 |
+
# if tensorboard_wrapper:
|
| 113 |
+
# tensorboard_wrapper.add_image(name, img_arr.transpose([2,0,1]), step )
|
| 114 |
+
# plt.close()
|
| 115 |
+
# else:
|
| 116 |
+
# plt.show()
|
| 117 |
+
return r
|
| 118 |
+
|
| 119 |
+
def get_ytrue_ypred(self, mode="test"):
|
| 120 |
+
with torch.no_grad():
|
| 121 |
+
ytrue_ypred = []
|
| 122 |
+
dataLoader = self.get_dataloader(mode=mode)
|
| 123 |
+
n_elems = len(dataLoader)
|
| 124 |
+
for i, (batch, y) in enumerate(dataLoader):
|
| 125 |
+
ytrue_ypred.append((y, self.pl_module(batch.to(self.pl_module.device))))
|
| 126 |
+
if i % 10 == 0: print("%07d/%07d " % (i,n_elems), end="\r")
|
| 127 |
+
print()
|
| 128 |
+
ytrue_train, ypred_train = zip(*ytrue_ypred)
|
| 129 |
+
ytrue = torch.cat(ytrue_train).to("cpu").numpy()
|
| 130 |
+
ypred = torch.cat(ypred_train).to("cpu").numpy()
|
| 131 |
+
return ytrue, ypred
|
| 132 |
+
|
| 133 |
+
def pred_and_plot(self, mode="test", step=0):
|
| 134 |
+
ytrue, ypred = self.get_ytrue_ypred(mode=mode)
|
| 135 |
+
r = self.do_plots(ytrue, ypred, label=mode,
|
| 136 |
+
tensorboard_wrapper=self.pl_module.logger.experiment,
|
| 137 |
+
step=step)
|
| 138 |
+
return r, (ytrue, ypred)
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
class PlotFigsTensorboardCallback(Callback):
|
| 142 |
+
|
| 143 |
+
def __init__(self, output_dir: str = None, frequency=10, save_csv=True):
|
| 144 |
+
super().__init__()
|
| 145 |
+
self.output_dir = output_dir
|
| 146 |
+
self.frequency = frequency
|
| 147 |
+
self.current_epoch = 0
|
| 148 |
+
self.plotter = None # InferencePlotter(pl_module=)
|
| 149 |
+
self.save_csv = save_csv
|
| 150 |
+
|
| 151 |
+
def setup(self, trainer: 'pl.Trainer', pl_module: 'pl.LightningModule', stage=None) -> None:
|
| 152 |
+
self.plotter = InferencePlotter(pl_module=pl_module)
|
| 153 |
+
|
| 154 |
+
# def on_init_end(self, trainer):
|
| 155 |
+
# if self.output_dir is None:
|
| 156 |
+
# self.output_dir = trainer.log_dir
|
| 157 |
+
|
| 158 |
+
def on_train_start(self, trainer: 'pl.Trainer', pl_module: 'pl.LightningModule') -> None:
|
| 159 |
+
if trainer.is_global_zero:
|
| 160 |
+
if self.output_dir is None:
|
| 161 |
+
self.output_dir = trainer.log_dir
|
| 162 |
+
if self.save_csv:
|
| 163 |
+
self.csv_fname = os.path.join(self.output_dir, "logs.csv")
|
| 164 |
+
with open(self.csv_fname, "w") as f:
|
| 165 |
+
f.write("Epoch,Date,trainL1,valL1,trainR,valR")
|
| 166 |
+
|
| 167 |
+
def _getDateHourStr(self):
|
| 168 |
+
return datetime.now().strftime("%d/%m-%H:%M")
|
| 169 |
+
|
| 170 |
+
def on_train_epoch_end(
|
| 171 |
+
self, trainer: 'pl.Trainer', pl_module: 'pl.LightningModule', unused=None
|
| 172 |
+
) -> None:
|
| 173 |
+
|
| 174 |
+
if trainer.is_global_zero:
|
| 175 |
+
self.current_epoch += 1
|
| 176 |
+
if self.current_epoch % self.frequency != 0:
|
| 177 |
+
return
|
| 178 |
+
print("Evaluating preds on epoch %d [%s]" % (self.current_epoch - 1, self._getDateHourStr()))
|
| 179 |
+
r_train, lossL1_train = self.process_data(mode="train")
|
| 180 |
+
r_val, lossL1_val = self.process_data(mode="val")
|
| 181 |
+
|
| 182 |
+
if self.save_csv:
|
| 183 |
+
with open(self.csv_fname, "a") as f:
|
| 184 |
+
f.write("\n%d,%s,%f,%f,%1.4f,%1.4f" % (self.current_epoch - 1, self._getDateHourStr(),
|
| 185 |
+
lossL1_train, lossL1_val, r_train, r_val,))
|
| 186 |
+
|
| 187 |
+
def process_data(self, mode="train"):
|
| 188 |
+
r, (ytrue, ypred) = self.plotter.pred_and_plot(mode=mode, step=self.current_epoch)
|
| 189 |
+
lossL1 = float(np.mean(np.abs(ytrue - ypred)))
|
| 190 |
+
return r, lossL1
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
if __name__ == "__main__":
|
| 194 |
+
import numpy as np
|
| 195 |
+
|
| 196 |
+
ys = 1 + np.random.rand(4000) * 7
|
| 197 |
+
y_preds = ys - np.random.rand()
|
| 198 |
+
InferencePlotter.do_plots(ys, y_preds, label="test", tensorboard_wrapper=None)
|
coprinet/pricePrediction/nets/FDS_imbalance.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import numpy as np
|
| 3 |
+
from scipy.ndimage import gaussian_filter1d
|
| 4 |
+
from scipy.signal.windows import triang
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
|
| 9 |
+
'''
|
| 10 |
+
Modified from:
|
| 11 |
+
|
| 12 |
+
https://github.com/YyzHarry/imbalanced-regression
|
| 13 |
+
https://arxiv.org/pdf/2102.09554.pdf
|
| 14 |
+
'''
|
| 15 |
+
|
| 16 |
+
print = logging.info
|
| 17 |
+
|
| 18 |
+
def calibrate_mean_var(matrix, m1, v1, m2, v2, clip_min=0.1, clip_max=10):
|
| 19 |
+
if torch.sum(v1) < 1e-10:
|
| 20 |
+
return matrix
|
| 21 |
+
if (v1 == 0.).any():
|
| 22 |
+
valid = (v1 != 0.)
|
| 23 |
+
factor = torch.clamp(v2[valid] / v1[valid], clip_min, clip_max)
|
| 24 |
+
matrix[:, valid] = (matrix[:, valid] - m1[valid]) * torch.sqrt(factor) + m2[valid]
|
| 25 |
+
return matrix
|
| 26 |
+
|
| 27 |
+
factor = torch.clamp(v2 / v1, clip_min, clip_max)
|
| 28 |
+
return (matrix - m1) * torch.sqrt(factor) + m2
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class FDS(nn.Module):
|
| 32 |
+
|
| 33 |
+
def __init__(self, feature_dim, max_val, min_val=1, bucket_step=0.1, start_update=0, start_smooth=1,
|
| 34 |
+
kernel='gaussian', ks=5, sigma=2, momentum=0.9):
|
| 35 |
+
super(FDS, self).__init__()
|
| 36 |
+
self.feature_dim = feature_dim
|
| 37 |
+
|
| 38 |
+
self.bucket_start = min_val
|
| 39 |
+
self.bucket_step = bucket_step
|
| 40 |
+
|
| 41 |
+
bucket_end = min_val
|
| 42 |
+
bucket_ranges = []
|
| 43 |
+
while bucket_end < max_val:
|
| 44 |
+
next_bucket = bucket_end + bucket_step
|
| 45 |
+
bucket_ranges.append((bucket_end, next_bucket))
|
| 46 |
+
bucket_end = next_bucket
|
| 47 |
+
|
| 48 |
+
n_buckets = len( bucket_ranges )
|
| 49 |
+
self.n_buckets = n_buckets
|
| 50 |
+
self.bucket_end = bucket_end
|
| 51 |
+
|
| 52 |
+
self.kernel_window = self._get_kernel_window(kernel, ks, sigma)
|
| 53 |
+
self.half_ks = (ks - 1) // 2
|
| 54 |
+
self.momentum = momentum
|
| 55 |
+
self.start_update = start_update
|
| 56 |
+
self.start_smooth = start_smooth
|
| 57 |
+
|
| 58 |
+
self.register_buffer('epoch', torch.zeros(1).fill_(start_update))
|
| 59 |
+
self.register_buffer('running_mean', torch.zeros(n_buckets, feature_dim))
|
| 60 |
+
self.register_buffer('running_var', torch.ones(n_buckets, feature_dim))
|
| 61 |
+
self.register_buffer('running_mean_last_epoch', torch.zeros(n_buckets , feature_dim))
|
| 62 |
+
self.register_buffer('running_var_last_epoch', torch.ones(n_buckets , feature_dim))
|
| 63 |
+
self.register_buffer('smoothed_mean_last_epoch', torch.zeros(n_buckets , feature_dim))
|
| 64 |
+
self.register_buffer('smoothed_var_last_epoch', torch.ones(n_buckets , feature_dim))
|
| 65 |
+
self.register_buffer('num_samples_tracked', torch.zeros(n_buckets))
|
| 66 |
+
|
| 67 |
+
@staticmethod
|
| 68 |
+
def _get_kernel_window(kernel, ks, sigma):
|
| 69 |
+
assert kernel in ['gaussian', 'triang', 'laplace']
|
| 70 |
+
half_ks = (ks - 1) // 2
|
| 71 |
+
if kernel == 'gaussian':
|
| 72 |
+
base_kernel = [0.] * half_ks + [1.] + [0.] * half_ks
|
| 73 |
+
base_kernel = np.array(base_kernel, dtype=np.float32)
|
| 74 |
+
kernel_window = gaussian_filter1d(base_kernel, sigma=sigma) / sum(gaussian_filter1d(base_kernel, sigma=sigma))
|
| 75 |
+
elif kernel == 'triang':
|
| 76 |
+
kernel_window = triang(ks) / sum(triang(ks))
|
| 77 |
+
else:
|
| 78 |
+
laplace = lambda x: np.exp(-abs(x) / sigma) / (2. * sigma)
|
| 79 |
+
kernel_window = list(map(laplace, np.arange(-half_ks, half_ks + 1))) / sum(map(laplace, np.arange(-half_ks, half_ks + 1)))
|
| 80 |
+
|
| 81 |
+
print(f'Using FDS: [{kernel.upper()}] ({ks}/{sigma})')
|
| 82 |
+
return torch.tensor(kernel_window, dtype=torch.float32).cuda()
|
| 83 |
+
|
| 84 |
+
def _update_last_epoch_stats(self):
|
| 85 |
+
self.running_mean_last_epoch = self.running_mean
|
| 86 |
+
self.running_var_last_epoch = self.running_var
|
| 87 |
+
|
| 88 |
+
self.smoothed_mean_last_epoch = F.conv1d(
|
| 89 |
+
input=F.pad(self.running_mean_last_epoch.unsqueeze(1).permute(2, 1, 0),
|
| 90 |
+
pad=(self.half_ks, self.half_ks), mode='reflect'),
|
| 91 |
+
weight=self.kernel_window.view(1, 1, -1), padding=0
|
| 92 |
+
).permute(2, 1, 0).squeeze(1)
|
| 93 |
+
self.smoothed_var_last_epoch = F.conv1d(
|
| 94 |
+
input=F.pad(self.running_var_last_epoch.unsqueeze(1).permute(2, 1, 0),
|
| 95 |
+
pad=(self.half_ks, self.half_ks), mode='reflect'),
|
| 96 |
+
weight=self.kernel_window.view(1, 1, -1), padding=0
|
| 97 |
+
).permute(2, 1, 0).squeeze(1)
|
| 98 |
+
|
| 99 |
+
def reset(self):
|
| 100 |
+
self.running_mean.zero_()
|
| 101 |
+
self.running_var.fill_(1)
|
| 102 |
+
self.running_mean_last_epoch.zero_()
|
| 103 |
+
self.running_var_last_epoch.fill_(1)
|
| 104 |
+
self.smoothed_mean_last_epoch.zero_()
|
| 105 |
+
self.smoothed_var_last_epoch.fill_(1)
|
| 106 |
+
self.num_samples_tracked.zero_()
|
| 107 |
+
|
| 108 |
+
def update_last_epoch_stats(self, epoch):
|
| 109 |
+
if epoch == self.epoch + 1:
|
| 110 |
+
self.epoch += 1
|
| 111 |
+
self._update_last_epoch_stats()
|
| 112 |
+
print(f"Updated smoothed statistics on Epoch [{epoch}]!")
|
| 113 |
+
|
| 114 |
+
def get_bucket_id(self, ys):
|
| 115 |
+
bucket_id = (ys - self.bucket_start) // self.bucket_step
|
| 116 |
+
return bucket_id.type(torch.int32)
|
| 117 |
+
|
| 118 |
+
def update_running_stats(self, features, ys, epoch):
|
| 119 |
+
if epoch < self.epoch:
|
| 120 |
+
return
|
| 121 |
+
|
| 122 |
+
assert self.feature_dim == features.size(1), "Input feature dimension is not aligned!"
|
| 123 |
+
assert features.size(0) == ys.size(0), "Dimensions of features and labels are not aligned!"
|
| 124 |
+
|
| 125 |
+
bucket_ids = self.get_bucket_id(ys)
|
| 126 |
+
lower_mask = ys <= self.bucket_start
|
| 127 |
+
upper_mask = ys >= self.bucket_end
|
| 128 |
+
# for y in torch.unique(ys):
|
| 129 |
+
# bucket_id = self.get_bucket_id(y)
|
| 130 |
+
for bucket_id in torch.unique(bucket_ids):
|
| 131 |
+
if bucket_id > self.n_buckets - 1 or bucket_id < self.bucket_start:
|
| 132 |
+
continue
|
| 133 |
+
elif bucket_id == 0:
|
| 134 |
+
curr_feats = features[lower_mask]
|
| 135 |
+
elif bucket_id == self.n_buckets - 1:
|
| 136 |
+
curr_feats = features[upper_mask]
|
| 137 |
+
else:
|
| 138 |
+
curr_feats = features[bucket_ids == bucket_id]
|
| 139 |
+
|
| 140 |
+
curr_num_sample = curr_feats.size(0)
|
| 141 |
+
curr_mean = torch.mean(curr_feats, 0)
|
| 142 |
+
curr_var = torch.var(curr_feats, 0, unbiased=True if curr_feats.size(0) != 1 else False)
|
| 143 |
+
self.num_samples_tracked[bucket_id] += curr_num_sample
|
| 144 |
+
factor = self.momentum if self.momentum is not None else \
|
| 145 |
+
(1 - curr_num_sample / float(self.num_samples_tracked[bucket_id]))
|
| 146 |
+
factor = 0 if epoch == self.start_update else factor
|
| 147 |
+
self.running_mean[bucket_id] = \
|
| 148 |
+
(1 - factor) * curr_mean + factor * self.running_mean[bucket_id]
|
| 149 |
+
self.running_var[bucket_id] = \
|
| 150 |
+
(1 - factor) * curr_var + factor * self.running_var[bucket_id]
|
| 151 |
+
|
| 152 |
+
print(f"Updated running statistics with Epoch [{epoch}] features!")
|
| 153 |
+
|
| 154 |
+
def smooth(self, features, ys, epoch): #TODO: rename to forward
|
| 155 |
+
if epoch < self.start_smooth:
|
| 156 |
+
return features
|
| 157 |
+
|
| 158 |
+
bucket_ids = self.get_bucket_id(ys)
|
| 159 |
+
lower_mask = ys <= self.bucket_start
|
| 160 |
+
upper_mask = ys >= self.bucket_end
|
| 161 |
+
|
| 162 |
+
# ys = ys.squeeze(1)
|
| 163 |
+
# for y in torch.unique(ys):
|
| 164 |
+
for bucket_id in torch.unique(bucket_ids):
|
| 165 |
+
if bucket_id > self.n_buckets - 1 or bucket_id < self.bucket_start:
|
| 166 |
+
continue
|
| 167 |
+
elif bucket_id == self.bucket_start:
|
| 168 |
+
features[lower_mask] = calibrate_mean_var(
|
| 169 |
+
features[lower_mask], #TODO: indexing many times is not efficient
|
| 170 |
+
self.running_mean_last_epoch[bucket_id],
|
| 171 |
+
self.running_var_last_epoch[bucket_id],
|
| 172 |
+
self.smoothed_mean_last_epoch[bucket_id],
|
| 173 |
+
self.smoothed_var_last_epoch[bucket_id])
|
| 174 |
+
elif bucket_id == self.n_buckets - 1:
|
| 175 |
+
features[upper_mask] = calibrate_mean_var(
|
| 176 |
+
features[upper_mask],
|
| 177 |
+
self.running_mean_last_epoch[bucket_id],
|
| 178 |
+
self.running_var_last_epoch[bucket_id],
|
| 179 |
+
self.smoothed_mean_last_epoch[bucket_id],
|
| 180 |
+
self.smoothed_var_last_epoch[bucket_id])
|
| 181 |
+
else:
|
| 182 |
+
bucket_mask = bucket_ids == bucket_id
|
| 183 |
+
features[bucket_mask] = calibrate_mean_var(
|
| 184 |
+
features[bucket_mask],
|
| 185 |
+
self.running_mean_last_epoch[bucket_id],
|
| 186 |
+
self.running_var_last_epoch[bucket_id],
|
| 187 |
+
self.smoothed_mean_last_epoch[bucket_id],
|
| 188 |
+
self.smoothed_var_last_epoch[bucket_id])
|
| 189 |
+
return features
|
coprinet/pricePrediction/nets/__init__.py
ADDED
|
File without changes
|
coprinet/pricePrediction/nets/basicArchitectures.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import numpy as np
|
| 3 |
+
from torch import nn
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
from torch_geometric.nn import PNAConv, BatchNorm, global_add_pool, AttentiveFP, Set2Set
|
| 6 |
+
from torch.nn import ModuleList, Linear, ReLU, Sequential, Dropout
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class MLP(nn.Module):
|
| 10 |
+
|
| 11 |
+
def __init__(self,dims, n_layers, hidden_size, dropout=0 ):
|
| 12 |
+
|
| 13 |
+
super().__init__()
|
| 14 |
+
|
| 15 |
+
self.n_layers = n_layers
|
| 16 |
+
self.hidden_size = hidden_size
|
| 17 |
+
self.dims = dims
|
| 18 |
+
self.dropout = dropout
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def block(in_size, n_hidden):
|
| 22 |
+
layers = [
|
| 23 |
+
nn.Linear(in_size, n_hidden),
|
| 24 |
+
nn.BatchNorm1d(n_hidden),
|
| 25 |
+
nn.ReLU(),
|
| 26 |
+
]
|
| 27 |
+
if self.dropout > 0:
|
| 28 |
+
layers.append(
|
| 29 |
+
nn.Dropout(self.dropout),
|
| 30 |
+
)
|
| 31 |
+
return layers
|
| 32 |
+
|
| 33 |
+
# Define PyTorch model
|
| 34 |
+
self.model = nn.Sequential(
|
| 35 |
+
*block(np.prod(dims), self.hidden_size)
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
self.latent_size = self.hidden_size
|
| 39 |
+
|
| 40 |
+
def forward(self, x):
|
| 41 |
+
return self.model(x)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class GNN_PNAConv(torch.nn.Module):
|
| 45 |
+
def __init__(self,
|
| 46 |
+
nodes_n_features: int,
|
| 47 |
+
edges_n_features: int,
|
| 48 |
+
deg: torch.tensor ,
|
| 49 |
+
n_layers: int = 6,
|
| 50 |
+
hidden_size_node: int = 75,
|
| 51 |
+
hidden_size_edges: int = 50,
|
| 52 |
+
towers: int = 5,
|
| 53 |
+
fcc_hidden_size: int = 50,
|
| 54 |
+
dropout: float = 0,
|
| 55 |
+
use_fds: bool = False,
|
| 56 |
+
**args
|
| 57 |
+
):
|
| 58 |
+
super(GNN_PNAConv, self).__init__()
|
| 59 |
+
|
| 60 |
+
self.node_emb = MLP(nodes_n_features, 1, hidden_size_node)
|
| 61 |
+
self.edge_emb = MLP(edges_n_features, 1, hidden_size_edges)
|
| 62 |
+
|
| 63 |
+
aggregators = ['mean', 'min', 'max', 'std']
|
| 64 |
+
scalers = ['identity', 'amplification', 'attenuation']
|
| 65 |
+
|
| 66 |
+
self.convs = ModuleList()
|
| 67 |
+
self.batch_norms = ModuleList()
|
| 68 |
+
for _ in range(n_layers):
|
| 69 |
+
conv = PNAConv(in_channels=hidden_size_node, out_channels=hidden_size_node,
|
| 70 |
+
aggregators=aggregators, scalers=scalers, deg=deg,
|
| 71 |
+
edge_dim=hidden_size_edges, towers=towers, pre_layers=2, post_layers=2,
|
| 72 |
+
divide_input=False)
|
| 73 |
+
self.convs.append(conv)
|
| 74 |
+
self.batch_norms.append(BatchNorm(hidden_size_node))
|
| 75 |
+
self.set2set = Set2Set(hidden_size_node, processing_steps=6)
|
| 76 |
+
fc_layers = [
|
| 77 |
+
Linear(2*hidden_size_node, hidden_size_node), BatchNorm(hidden_size_node), ReLU(),
|
| 78 |
+
Linear(hidden_size_node, fcc_hidden_size), BatchNorm(fcc_hidden_size), ReLU(),
|
| 79 |
+
]
|
| 80 |
+
self.fcc_hidden_size = fcc_hidden_size
|
| 81 |
+
if dropout>0:
|
| 82 |
+
fc_layers += [ Dropout(p=dropout) ]
|
| 83 |
+
|
| 84 |
+
self.mlp = Sequential( *fc_layers )
|
| 85 |
+
|
| 86 |
+
def forward(self, x, edge_index, edge_attr, batch):
|
| 87 |
+
x = self.node_emb(x.squeeze())
|
| 88 |
+
edge_attr = self.edge_emb(edge_attr)
|
| 89 |
+
|
| 90 |
+
for conv, batch_norm in zip(self.convs, self.batch_norms):
|
| 91 |
+
x = F.relu(batch_norm(conv(x, edge_index, edge_attr)))
|
| 92 |
+
|
| 93 |
+
x = self.set2set(x, batch) #Set2Set #GlobalAttention #GraphMultisetTransformer
|
| 94 |
+
|
| 95 |
+
return self.mlp(x)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
class QdolarAR(torch.nn.Module):
|
| 99 |
+
def __init__(self,
|
| 100 |
+
nodes_n_features: int,
|
| 101 |
+
*args, **kargs
|
| 102 |
+
):
|
| 103 |
+
super(QdolarAR, self).__init__()
|
| 104 |
+
|
| 105 |
+
n_layers: int = 5
|
| 106 |
+
hidden_size_node: int = nodes_n_features
|
| 107 |
+
fcc_hidden_size: int = 100
|
| 108 |
+
dropout: float = 0.28
|
| 109 |
+
|
| 110 |
+
self.fcc_hidden_size = fcc_hidden_size
|
| 111 |
+
self.dropout = dropout
|
| 112 |
+
self.n_layers = n_layers
|
| 113 |
+
self.hidden_size_node = hidden_size_node
|
| 114 |
+
|
| 115 |
+
assert n_layers >=3
|
| 116 |
+
fc_layers = [
|
| 117 |
+
BatchNorm(hidden_size_node),
|
| 118 |
+
Linear( hidden_size_node, fcc_hidden_size), BatchNorm(fcc_hidden_size), ReLU(),
|
| 119 |
+
]
|
| 120 |
+
for i in range(n_layers-2):
|
| 121 |
+
fc_layers += [
|
| 122 |
+
Linear(fcc_hidden_size, fcc_hidden_size), BatchNorm(fcc_hidden_size), ReLU(),
|
| 123 |
+
]
|
| 124 |
+
if dropout>0:
|
| 125 |
+
fc_layers += [ Dropout(p=dropout) ]
|
| 126 |
+
|
| 127 |
+
self.mlp = Sequential( *fc_layers )
|
| 128 |
+
|
| 129 |
+
def forward(self, x, edge_index, edge_attr, batch):
|
| 130 |
+
assert len(batch) == len(batch.unique())
|
| 131 |
+
assert not torch.isnan(x).any(), "Error, x contains nan"
|
| 132 |
+
return self.mlp(x)
|
| 133 |
+
|
| 134 |
+
class GNN_AttentiveFP(torch.nn.Module):
|
| 135 |
+
def __init__(self,
|
| 136 |
+
nodes_n_features: int,
|
| 137 |
+
edges_n_features: int,
|
| 138 |
+
deg: torch.tensor ,
|
| 139 |
+
n_layers: int = 4,
|
| 140 |
+
hidden_size_node: int = 75,
|
| 141 |
+
hidden_size_edges: int = 50,
|
| 142 |
+
towers: int = 5,
|
| 143 |
+
fcc_hidden_size: int = 50,
|
| 144 |
+
dropout: float = 0,
|
| 145 |
+
use_fds: bool = False,
|
| 146 |
+
**args
|
| 147 |
+
):
|
| 148 |
+
super(GNN_AttentiveFP, self).__init__()
|
| 149 |
+
|
| 150 |
+
self.attent_net = AttentiveFP(nodes_n_features, hidden_size_node, hidden_size_node, edges_n_features,
|
| 151 |
+
n_layers, n_layers, dropout= dropout)
|
| 152 |
+
|
| 153 |
+
fc_layers = [
|
| 154 |
+
Linear(hidden_size_node, hidden_size_edges), ReLU(),
|
| 155 |
+
Linear(hidden_size_edges, fcc_hidden_size), ReLU(),
|
| 156 |
+
]
|
| 157 |
+
self.fcc_hidden_size = fcc_hidden_size
|
| 158 |
+
if dropout>0:
|
| 159 |
+
fc_layers += Dropout(p=dropout)
|
| 160 |
+
|
| 161 |
+
self.mlp = Sequential( *fc_layers )
|
| 162 |
+
|
| 163 |
+
def forward(self, x, edge_index, edge_attr, batch):
|
| 164 |
+
x = self.attent_net(x, edge_index, edge_attr, batch)
|
| 165 |
+
return self.mlp(x)
|
coprinet/pricePrediction/nets/netsGraph.py
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections import OrderedDict
|
| 2 |
+
from typing import Iterable
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
import numpy as np
|
| 6 |
+
from torch import nn
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
import pytorch_lightning as pl
|
| 9 |
+
|
| 10 |
+
from pricePrediction import config
|
| 11 |
+
from pricePrediction.ArgParser_base import ArgParseable
|
| 12 |
+
from pricePrediction.config import USE_FEATURES_NET
|
| 13 |
+
from pricePrediction.nets.FDS_imbalance import FDS
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class PricePredictorModule(pl.LightningModule, ArgParseable):
|
| 17 |
+
|
| 18 |
+
DESIRED_PARAMS_TO_ASK= ['n_layers', 'hidden_size_node', 'hidden_size_edges', 'fcc_hidden_size', 'use_fds', 'lr',
|
| 19 |
+
'dropout', 'gnn_class', 'weight_decay', 'training_loss']
|
| 20 |
+
def __init__(
|
| 21 |
+
self,
|
| 22 |
+
n_layers: int = config.N_LAYERS,
|
| 23 |
+
hidden_size_node: int = config.N_HIDDEN_NODE,
|
| 24 |
+
hidden_size_edges: int = config.N_HIDDEN_EDGE,
|
| 25 |
+
gnn_class="GNN_PNAConv",
|
| 26 |
+
towers: int = 5,
|
| 27 |
+
fcc_hidden_size: int = 25,
|
| 28 |
+
dropout: float = 0,
|
| 29 |
+
use_fds: bool = False,
|
| 30 |
+
training_loss : str= "l2", # l1, l2 or huber
|
| 31 |
+
last_layer_size: int = 1,
|
| 32 |
+
|
| 33 |
+
nodes_n_features: int = None,
|
| 34 |
+
edges_n_features: int = None,
|
| 35 |
+
deg: Iterable[float] = None,
|
| 36 |
+
|
| 37 |
+
lr: float = config.LEARNING_RATE,
|
| 38 |
+
b1: float = 0.5,
|
| 39 |
+
b2: float = 0.999,
|
| 40 |
+
weight_decay=1e-8,
|
| 41 |
+
data_hparams =None,
|
| 42 |
+
logs_only_in_epoch = False
|
| 43 |
+
):
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
'''
|
| 47 |
+
|
| 48 |
+
:param str encodedDir: The directory where the dataset has been prepared as lmdb files. defaults to %(config.ENCODED_DIR)s
|
| 49 |
+
:param str deg_fname: The file containing the statistics about the nodes degree. By default is automatically searched within
|
| 50 |
+
encodedDir defaults to None
|
| 51 |
+
:param int n_layers: The number of layers.
|
| 52 |
+
:param int hidden_size_node: Channels for nodes.
|
| 53 |
+
:param int hidden_size_edges: Channels for edges.
|
| 54 |
+
:param str gnn_class: Name of the model type.
|
| 55 |
+
:param int towers: Only for gnn_class="GNN_PNAConv", The number of towers of the model.
|
| 56 |
+
:param int fcc_hidden_size: Size of fully connected.
|
| 57 |
+
:param float dropout: Dropout rate.
|
| 58 |
+
:param bool use_fds: Use feature density smoothing for data imbalance
|
| 59 |
+
:param str training_loss: Training loss: "l2" "l1" or "huber"
|
| 60 |
+
:param int last_layer_size: The number of neurons for last layer. Set it to 1 for regression
|
| 61 |
+
:param int nodes_n_features: The number of features used to encode a node
|
| 62 |
+
:param int edges_n_features: The number of features used to encode an edge
|
| 63 |
+
:param Iterable deg: The degree count of the dataset
|
| 64 |
+
:param float lr: Learning rate
|
| 65 |
+
:param float b1: Adam optimizer b1
|
| 66 |
+
:param float b2: Adam optimizer b1
|
| 67 |
+
'''
|
| 68 |
+
|
| 69 |
+
super().__init__()
|
| 70 |
+
self.save_hyperparameters() #copies __init__() kwargs to self.hparams
|
| 71 |
+
deg = self.hparams.deg
|
| 72 |
+
if isinstance(deg, list):
|
| 73 |
+
deg = torch.tensor(deg[:], dtype=torch.long)
|
| 74 |
+
else:
|
| 75 |
+
deg = deg.clone().detach()
|
| 76 |
+
self.hparams.deg = deg
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
if gnn_class == "GNN_PNAConv":
|
| 81 |
+
from pricePrediction.nets.basicArchitectures import GNN_PNAConv as GNN
|
| 82 |
+
elif gnn_class == "GNN_AttentiveFP":
|
| 83 |
+
from pricePrediction.nets.basicArchitectures import GNN_AttentiveFP as GNN
|
| 84 |
+
elif gnn_class == "QdolarAR":
|
| 85 |
+
assert USE_FEATURES_NET
|
| 86 |
+
from pricePrediction.nets.basicArchitectures import QdolarAR as GNN
|
| 87 |
+
|
| 88 |
+
else:
|
| 89 |
+
raise ValueError("Error, gnn_class not supported")
|
| 90 |
+
|
| 91 |
+
self.net = GNN(** self.hparams)
|
| 92 |
+
|
| 93 |
+
self.dropoutLayer = nn.Dropout(self.hparams.dropout)
|
| 94 |
+
|
| 95 |
+
self.final_layer = nn.Linear(self.net.fcc_hidden_size, 1)
|
| 96 |
+
|
| 97 |
+
if use_fds:
|
| 98 |
+
self.fds = FDS(self.net.latent_size, max_val=10, start_update=1)
|
| 99 |
+
else:
|
| 100 |
+
self.fds = None
|
| 101 |
+
if training_loss=="l1":
|
| 102 |
+
self.trainLossF = F.l1_loss
|
| 103 |
+
elif training_loss=="l2":
|
| 104 |
+
self.trainLossF = F.mse_loss
|
| 105 |
+
elif training_loss=="huber":
|
| 106 |
+
self.trainLossF = F.smooth_l1_loss
|
| 107 |
+
else:
|
| 108 |
+
raise ValueError("Loss option not recognized")
|
| 109 |
+
|
| 110 |
+
if logs_only_in_epoch: #TODO: check if this prevents multi-gpu hang
|
| 111 |
+
self.logs_kwargs = dict(on_step=False, on_epoch=True, sync_dist=True)
|
| 112 |
+
else:
|
| 113 |
+
self.logs_kwargs = dict(on_step=True, on_epoch=False, sync_dist=False)
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def compute_training_loss(self, y_pred, y, w=None):
|
| 117 |
+
loss = self.trainLossF(y_pred, y, reduction='none')
|
| 118 |
+
if w is not None:
|
| 119 |
+
loss *= w
|
| 120 |
+
loss = torch.mean(loss)
|
| 121 |
+
return loss
|
| 122 |
+
|
| 123 |
+
def forward(self, g):
|
| 124 |
+
return self.compute_y_pred( g, training=False)
|
| 125 |
+
|
| 126 |
+
def compute_y_pred(self, g, y=None, training=True):
|
| 127 |
+
x = self.net(g.x, g.edge_index, g.edge_attr, g.batch)
|
| 128 |
+
if training and self.fds is not None:
|
| 129 |
+
self.fds.smooth(x, y, self.current_epoch)
|
| 130 |
+
x = self.dropoutLayer(x)
|
| 131 |
+
y_pred = self.final_layer(x)
|
| 132 |
+
return y_pred.view(-1)
|
| 133 |
+
|
| 134 |
+
def resolve_batch(self, batch):
|
| 135 |
+
if isinstance(batch, list):
|
| 136 |
+
return batch[0]
|
| 137 |
+
else:
|
| 138 |
+
return batch
|
| 139 |
+
|
| 140 |
+
def training_step(self, batch, batch_idx):
|
| 141 |
+
graphs = self.resolve_batch(batch)
|
| 142 |
+
y_pred = self.compute_y_pred(graphs, training=True)
|
| 143 |
+
loss = self.compute_training_loss(y_pred, graphs.y, graphs.w)
|
| 144 |
+
|
| 145 |
+
loss_l1 = F.l1_loss(y_pred, graphs.y)
|
| 146 |
+
|
| 147 |
+
self.log('loss', loss, **self.logs_kwargs)
|
| 148 |
+
self.log('loss_l1', loss_l1, prog_bar=True, **self.logs_kwargs)
|
| 149 |
+
|
| 150 |
+
tqdm_dict = {'loss': loss.detach()}
|
| 151 |
+
output = OrderedDict({
|
| 152 |
+
'loss': loss,
|
| 153 |
+
'targets': graphs.y,
|
| 154 |
+
'preds': y_pred.detach(),
|
| 155 |
+
'progress_bar': tqdm_dict,
|
| 156 |
+
'log': tqdm_dict
|
| 157 |
+
})
|
| 158 |
+
return output
|
| 159 |
+
|
| 160 |
+
def _validation_step(self, graphs, batch_idx):
|
| 161 |
+
with torch.no_grad():
|
| 162 |
+
y_pred = self.compute_y_pred(graphs, training=False)
|
| 163 |
+
loss = self.compute_training_loss(y_pred, graphs.y, graphs.w)
|
| 164 |
+
loss_l1 = F.l1_loss(y_pred, graphs.y)
|
| 165 |
+
return y_pred, loss, loss_l1
|
| 166 |
+
|
| 167 |
+
def validation_step(self, batch, batch_idx):
|
| 168 |
+
graphs = self.resolve_batch(batch)
|
| 169 |
+
y_pred, loss, loss_l1 = self._validation_step(graphs, batch_idx)
|
| 170 |
+
# tensorboard = self.logger.experiment
|
| 171 |
+
# tensorboard.add_histogram("val_price", graphs.y)
|
| 172 |
+
# tensorboard.add_histogram("val_preds", y_pred)
|
| 173 |
+
self.log('val_loss', loss, prog_bar=True, sync_dist=True) #rank_zero_only=True
|
| 174 |
+
self.log('val_lossL1', loss_l1, prog_bar=True, sync_dist=True)
|
| 175 |
+
return loss
|
| 176 |
+
|
| 177 |
+
def training_epoch_end(self, outputs ):
|
| 178 |
+
# tensorboard = self.logger.experiment
|
| 179 |
+
# tensorboard.add_histogram("train_price", y)
|
| 180 |
+
# tensorboard.add_histogram("train_preds", torch.cat([ x["preds"] for x in outputs]))
|
| 181 |
+
# tensorboard.add_histogram("train_price", torch.cat([ x["targets"] for x in outputs]))
|
| 182 |
+
print("Epoch %d done"%self.current_epoch)
|
| 183 |
+
|
| 184 |
+
def test_step(self, batch, batch_idx):
|
| 185 |
+
graphs = self.resolve_batch(batch)
|
| 186 |
+
y_pred, loss, loss_l1 = self._validation_step(graphs, batch_idx)
|
| 187 |
+
tensorboard = self.logger.experiment
|
| 188 |
+
# tensorboard.add_histogram("test_price", graphs.y)
|
| 189 |
+
# tensorboard.add_histogram("test_preds", y_pred)
|
| 190 |
+
self.log('test_loss', loss, prog_bar=True, sync_dist=True)
|
| 191 |
+
self.log('test_lossL1', loss_l1, prog_bar=True, sync_dist=True)
|
| 192 |
+
return loss
|
| 193 |
+
|
| 194 |
+
def configure_optimizers(self):
|
| 195 |
+
lr = self.hparams.lr
|
| 196 |
+
b1 = self.hparams.b1
|
| 197 |
+
b2 = self.hparams.b2
|
| 198 |
+
|
| 199 |
+
opt = torch.optim.Adam(self.parameters(), lr=lr, betas=(b1, b2), weight_decay=self.hparams.weight_decay)
|
| 200 |
+
|
| 201 |
+
# for param_group in opt.param_groups:
|
| 202 |
+
# print(param_group["lr"])
|
| 203 |
+
|
| 204 |
+
conf = {
|
| 205 |
+
'optimizer': opt,
|
| 206 |
+
'scheduler': torch.optim.lr_scheduler.ReduceLROnPlateau(opt, verbose=True, cooldown=2,
|
| 207 |
+
patience=config.PATIENT_REDUCE_LR_PLATEAU_N_EPOCHS, ),
|
| 208 |
+
'monitor': 'val_loss'
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
return conf
|
| 212 |
+
|
| 213 |
+
if __name__ == "__main__":
|
| 214 |
+
from torch_geometric.data import Batch
|
| 215 |
+
|
| 216 |
+
degree =[0, 41130, 117278, 70152, 3104]
|
| 217 |
+
|
| 218 |
+
gnn_class = "QdolarAR" #"GNN_PNAConv"# "GNN_AttentiveFP"
|
| 219 |
+
|
| 220 |
+
if gnn_class != "QdolarAR":
|
| 221 |
+
from pricePrediction.preprocessData.smilesToGraph import smiles_to_graph
|
| 222 |
+
else:
|
| 223 |
+
from pricePrediction.preprocessData.smilesToDescriptors import smiles_to_graph
|
| 224 |
+
|
| 225 |
+
g = smiles_to_graph("CCCCCCCCCCC")
|
| 226 |
+
nodes_n_features = g.x.shape[1]
|
| 227 |
+
edges_n_features = g.edge_attr.shape[1]
|
| 228 |
+
|
| 229 |
+
net = PricePredictorModule(nodes_n_features=nodes_n_features, edges_n_features=edges_n_features,
|
| 230 |
+
deg=degree, lr=1e-3, gnn_class=gnn_class)
|
| 231 |
+
|
| 232 |
+
print( net )
|
| 233 |
+
|
| 234 |
+
graphs = [smiles_to_graph(smi) for smi in ["CCCCCO", "CCCCCNCCCCN"]]
|
| 235 |
+
print(graphs[0])
|
| 236 |
+
print( net(Batch.from_data_list(graphs)))
|
coprinet/pricePrediction/predict/__init__.py
ADDED
|
File without changes
|
coprinet/pricePrediction/predict/__main__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pricePrediction.predict.predict import main
|
| 2 |
+
|
| 3 |
+
if __name__ == '__main__':
|
| 4 |
+
main()
|
coprinet/pricePrediction/predict/predict.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import os, sys
|
| 3 |
+
from itertools import islice
|
| 4 |
+
import numpy as np
|
| 5 |
+
import pandas as pd
|
| 6 |
+
import torch
|
| 7 |
+
from joblib import Parallel, delayed
|
| 8 |
+
from more_itertools import chunked
|
| 9 |
+
from torch.utils.data import IterableDataset, DataLoader
|
| 10 |
+
from torch_geometric.data import Batch
|
| 11 |
+
from tqdm import tqdm
|
| 12 |
+
import pytorch_lightning as pl
|
| 13 |
+
from pricePrediction.nets.netsGraph import PricePredictorModule
|
| 14 |
+
from pricePrediction.config import NUM_WORKERS_PER_GPU, BATCH_SIZE, DEFAULT_MODEL, USE_FEATURES_NET, \
|
| 15 |
+
BUFFER_N_BATCHES_FOR_PRED
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class GraphPricePredictor():
|
| 19 |
+
name = "price_GNN"
|
| 20 |
+
|
| 21 |
+
def __init__(self, model_path=DEFAULT_MODEL, n_gpus = 1, n_cpus= NUM_WORKERS_PER_GPU, batch_size:int=BATCH_SIZE,
|
| 22 |
+
**kwargs):
|
| 23 |
+
self.model_path = model_path
|
| 24 |
+
self.n_gpus = n_gpus
|
| 25 |
+
self.n_cpus = n_cpus
|
| 26 |
+
self.batch_size = batch_size
|
| 27 |
+
self.trainer = pl.Trainer(gpus=self.n_gpus, logger=False)
|
| 28 |
+
self.model = PricePredictorModule.load_from_checkpoint(
|
| 29 |
+
self.model_path,
|
| 30 |
+
batch_size=self.batch_size,
|
| 31 |
+
strict=False,
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
if USE_FEATURES_NET:
|
| 35 |
+
from pricePrediction.preprocessData.smilesToDescriptors import smiles_to_graph
|
| 36 |
+
|
| 37 |
+
else:
|
| 38 |
+
from pricePrediction.preprocessData.smilesToGraph import smiles_to_graph
|
| 39 |
+
|
| 40 |
+
self.smiles_to_graph = smiles_to_graph
|
| 41 |
+
|
| 42 |
+
def prepare_smi(self, idx_smi):
|
| 43 |
+
idx, smi = idx_smi
|
| 44 |
+
graph = self.smiles_to_graph(smi)
|
| 45 |
+
if graph is None:
|
| 46 |
+
return None
|
| 47 |
+
graph.input_idx = idx
|
| 48 |
+
return graph
|
| 49 |
+
|
| 50 |
+
def yieldPredictions(self, smiles_generator, buffer_n_batches=BUFFER_N_BATCHES_FOR_PRED):
|
| 51 |
+
buffer_size = buffer_n_batches * self.batch_size
|
| 52 |
+
preds_iter = map(lambda x: self.predictListOfSmiles(x), tqdm(chunked(smiles_generator, buffer_size)))
|
| 53 |
+
for preds_batch in preds_iter:
|
| 54 |
+
for pred in preds_batch:
|
| 55 |
+
yield pred
|
| 56 |
+
|
| 57 |
+
def predictListOfSmiles(self, smiles_list):
|
| 58 |
+
smiles_list = list(smiles_list)
|
| 59 |
+
graphs_list = list(filter(None.__ne__, map(self.prepare_smi, enumerate(smiles_list) )))
|
| 60 |
+
graphs_fn = lambda : graphs_list
|
| 61 |
+
dataset = MyIterableDataset(graphs_fn, self.n_cpus)
|
| 62 |
+
dataloader = DataLoader(dataset=dataset, batch_size=self.batch_size, collate_fn=Batch.from_data_list,
|
| 63 |
+
num_workers=self.n_cpus)
|
| 64 |
+
|
| 65 |
+
preds = self.trainer.predict(self.model, dataloader)
|
| 66 |
+
n_smiles = len(smiles_list)
|
| 67 |
+
all_preds = np.nan * np.ones(n_smiles)
|
| 68 |
+
for i, batch in enumerate(dataloader):
|
| 69 |
+
batch_preds = preds[i].to("cpu").numpy()
|
| 70 |
+
idxs = batch.input_idx.to("cpu").numpy().astype(np.int64).tolist()
|
| 71 |
+
all_preds[idxs] = batch_preds
|
| 72 |
+
return all_preds
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class MyIterableDataset(IterableDataset):
|
| 76 |
+
def __init__(self, generator_fun, num_workers):
|
| 77 |
+
super().__init__()
|
| 78 |
+
self.generator_fun = generator_fun
|
| 79 |
+
self.num_workers = num_workers
|
| 80 |
+
|
| 81 |
+
def __iter__(self):
|
| 82 |
+
worker_info = torch.utils.data.get_worker_info()
|
| 83 |
+
if worker_info is not None:
|
| 84 |
+
uid = torch.utils.data.get_worker_info().id
|
| 85 |
+
return islice( self.generator_fun(), uid, None, self.num_workers)
|
| 86 |
+
else:
|
| 87 |
+
return iter(self.generator_fun())
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def main():
|
| 91 |
+
parser = argparse.ArgumentParser(prog="CoPriNet")
|
| 92 |
+
parser.add_argument("input_csv_file", type=str, help="The input csv filename containing a smiles column")
|
| 93 |
+
parser.add_argument("-o", "--output_file", type=str, required=False, default=None, help="The output filename that will be "
|
| 94 |
+
"identical to the input_csv_file but with one additional column for the f1")
|
| 95 |
+
parser.add_argument("--smiles_colname", type=str, required=False, default="SMILES", help="The colname for SMILES "
|
| 96 |
+
"in the input file. Default: %(default)s")
|
| 97 |
+
parser.add_argument("--model_path", type=str, required=False, default=DEFAULT_MODEL,
|
| 98 |
+
help="The CoPriNet model checkpoing path. Default: %(default)s")
|
| 99 |
+
|
| 100 |
+
parser.add_argument("--n_cpus", type=int, required=False, default=NUM_WORKERS_PER_GPU,
|
| 101 |
+
help="The number of cpu workers. Default: %(default)s")
|
| 102 |
+
|
| 103 |
+
parser.add_argument("--batch_size", type=int, required=False, default=BATCH_SIZE,
|
| 104 |
+
help="Batch size. Default: %(default)s")
|
| 105 |
+
|
| 106 |
+
parser.add_argument("--convert_to_g", action="store_true", help="Convert the f1 from $/mmol to $/g")
|
| 107 |
+
|
| 108 |
+
coprinet_colname = "CoPriNet"
|
| 109 |
+
|
| 110 |
+
args = parser.parse_args()
|
| 111 |
+
|
| 112 |
+
df = pd.read_csv(args.input_csv_file)
|
| 113 |
+
nans = df[args.smiles_colname].isna()
|
| 114 |
+
df = df[~nans]
|
| 115 |
+
smiles_list = df[args.smiles_colname]
|
| 116 |
+
predictor = GraphPricePredictor( **vars(args))
|
| 117 |
+
preds = predictor.yieldPredictions(smiles_list)
|
| 118 |
+
if args.convert_to_g:
|
| 119 |
+
from rdkit import Chem
|
| 120 |
+
def convert_pred(smi, pred):
|
| 121 |
+
mol = Chem.MolFromSmiles(smi)
|
| 122 |
+
if mol is None:
|
| 123 |
+
return np.nan
|
| 124 |
+
mw = Chem.Descriptors.ExactMolWt(mol)
|
| 125 |
+
price = np.log( np.exp(pred)*1000/mw)
|
| 126 |
+
return price
|
| 127 |
+
|
| 128 |
+
preds = Parallel(n_jobs=args.n_cpus)(delayed(convert_pred)(smi, pred) for smi, pred in zip(smiles_list, preds))
|
| 129 |
+
if args.output_file is None:
|
| 130 |
+
for smi, pred in zip(smiles_list, preds):
|
| 131 |
+
print("%s\t%.4f" % (smi, pred))
|
| 132 |
+
else:
|
| 133 |
+
df[coprinet_colname] = list(preds)
|
| 134 |
+
df.to_csv(args.output_file, index=False)
|
| 135 |
+
|
| 136 |
+
if __name__ == '__main__':
|
| 137 |
+
main()
|
coprinet/pricePrediction/preprocessData/__init__.py
ADDED
|
File without changes
|
coprinet/pricePrediction/preprocessData/prepareDataMol2Price.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gzip
|
| 2 |
+
import os
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
import time
|
| 6 |
+
from functools import reduce
|
| 7 |
+
from itertools import chain
|
| 8 |
+
from multiprocessing import cpu_count
|
| 9 |
+
|
| 10 |
+
import lmdb
|
| 11 |
+
import psutil
|
| 12 |
+
import joblib
|
| 13 |
+
import torch
|
| 14 |
+
from joblib import Parallel, delayed
|
| 15 |
+
import numpy as np
|
| 16 |
+
from pricePrediction import config
|
| 17 |
+
from pricePrediction.config import USE_MMOL_INSTEAD_GRAM
|
| 18 |
+
from pricePrediction.preprocessData.serializeDatapoints import getExampleId, serializeExample
|
| 19 |
+
from pricePrediction.utils import tryMakedir, getBucketRanges, search_buckedId, EncodedDirNamesAndTemplates
|
| 20 |
+
from .smilesToGraph import compute_nodes_degree, fromPerGramToPerMMolPrice
|
| 21 |
+
|
| 22 |
+
if config.USE_FEATURES_NET:
|
| 23 |
+
from .smilesToDescriptors import smiles_to_graph
|
| 24 |
+
|
| 25 |
+
else:
|
| 26 |
+
from .smilesToGraph import smiles_to_graph
|
| 27 |
+
|
| 28 |
+
PER_WORKER_MEMORY_GB = 2
|
| 29 |
+
|
| 30 |
+
class DataBuilder():
|
| 31 |
+
|
| 32 |
+
def __init__(self, n_cpus= config.N_CPUS):
|
| 33 |
+
if n_cpus is None:
|
| 34 |
+
mem_gib = psutil.virtual_memory().available / (1024. ** 3)
|
| 35 |
+
n_cpus = int(max(1, min(cpu_count(), mem_gib // PER_WORKER_MEMORY_GB)))
|
| 36 |
+
|
| 37 |
+
self.n_cpus = n_cpus
|
| 38 |
+
|
| 39 |
+
def processOneFileOfSmiles(self, encodedDir, fileNum, datasetSplit, fname, nrows=None):
|
| 40 |
+
print("processing %s"%fname)
|
| 41 |
+
if fname.endswith(".csv"):
|
| 42 |
+
open_fun = open
|
| 43 |
+
decode_line = lambda line: line
|
| 44 |
+
elif fname.endswith(".csv.gz"):
|
| 45 |
+
open_fun = gzip.open
|
| 46 |
+
decode_line = lambda line : line.decode('utf-8')
|
| 47 |
+
else:
|
| 48 |
+
raise ValueError("Bad file format")
|
| 49 |
+
|
| 50 |
+
cols = ['SMILES','price']
|
| 51 |
+
|
| 52 |
+
names = EncodedDirNamesAndTemplates(encodedDir)
|
| 53 |
+
outFname_base = datasetSplit + "_" + str(fileNum) + "_lmdb"
|
| 54 |
+
outFname = os.path.join(encodedDir, datasetSplit,outFname_base)
|
| 55 |
+
env = lmdb.open(outFname, map_size=10737418240)
|
| 56 |
+
one_example_graph = smiles_to_graph("CCOCCC")
|
| 57 |
+
degs = compute_nodes_degree(None)
|
| 58 |
+
min_in_x = torch.ones(one_example_graph.x.shape[-1])*float('inf')
|
| 59 |
+
max_in_x = -torch.ones(one_example_graph.x.shape[-1])*float('inf')
|
| 60 |
+
|
| 61 |
+
num_examples = 0
|
| 62 |
+
bucket_ranges = getBucketRanges()
|
| 63 |
+
n_per_bucket = np.zeros(len(bucket_ranges), dtype= np.int64)
|
| 64 |
+
|
| 65 |
+
with open_fun(fname) as f_in:
|
| 66 |
+
header = decode_line(f_in.readline()).strip().split(",")
|
| 67 |
+
try:
|
| 68 |
+
smi_index, price_index = [ header.index(col) for col in cols]
|
| 69 |
+
except ValueError:
|
| 70 |
+
smi_index, price_index = 0,1
|
| 71 |
+
with env.begin(write=True) as sink, \
|
| 72 |
+
gzip.open(names.SELECTED_DATAPOINTS_TEMPLATE % (datasetSplit, fileNum), "wt") as f_out:
|
| 73 |
+
f_out.write("SMILES,price\n")
|
| 74 |
+
cur_time = time.time()
|
| 75 |
+
for i, line in enumerate(f_in):
|
| 76 |
+
lineArray = decode_line(line).strip().split(",")
|
| 77 |
+
smi, price = lineArray[smi_index], lineArray[price_index]
|
| 78 |
+
price = float(price)
|
| 79 |
+
graph = smiles_to_graph(smi)
|
| 80 |
+
if graph is None:
|
| 81 |
+
continue
|
| 82 |
+
#Save the original smiles-price
|
| 83 |
+
f_out.write("%s,%s\n"%(smi, price))
|
| 84 |
+
|
| 85 |
+
# Use the per mmol price
|
| 86 |
+
if USE_MMOL_INSTEAD_GRAM:
|
| 87 |
+
price = fromPerGramToPerMMolPrice(price, smi)
|
| 88 |
+
bucketId = search_buckedId( np.log(price), bucket_ranges)
|
| 89 |
+
n_per_bucket[bucketId] += 1
|
| 90 |
+
|
| 91 |
+
degs += compute_nodes_degree([graph])
|
| 92 |
+
min_in_x = torch.stack([min_in_x, torch.min(graph.x, 0)[0]]).min(0)[0]
|
| 93 |
+
max_in_x = torch.stack([max_in_x, torch.max(graph.x, 0)[0]]).max(0)[0]
|
| 94 |
+
|
| 95 |
+
fileId= getExampleId(outFname_base, num_examples)
|
| 96 |
+
sink.put(fileId, serializeExample(price, graph))
|
| 97 |
+
num_examples += 1
|
| 98 |
+
if nrows is not None and num_examples > nrows:
|
| 99 |
+
break
|
| 100 |
+
if i % 10000 == 0 and fileNum % self.n_cpus == 0:
|
| 101 |
+
new_time = time.time()
|
| 102 |
+
print("Current iteration: %d # task: %d (%.2f s) " % (i, fileNum, new_time - cur_time), end="\r")
|
| 103 |
+
cur_time = new_time
|
| 104 |
+
if fileNum % self.n_cpus == 0:
|
| 105 |
+
print()
|
| 106 |
+
return ((outFname, degs, min_in_x, max_in_x, num_examples, n_per_bucket),)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def getNFeatures(self):
|
| 110 |
+
one_graph = smiles_to_graph("CCCCCCO")
|
| 111 |
+
# print(one_graph)
|
| 112 |
+
return dict(nodes_n_features=one_graph["x"].shape[-1], edges_n_features=one_graph["edge_attr"].shape[-1])
|
| 113 |
+
|
| 114 |
+
def prepareDataset(self, inputDir=config.DATASET_DIRNAME, encodedDir=config.ENCODED_DIR, datasetSplit="train",
|
| 115 |
+
nrows=None, **kwargs):
|
| 116 |
+
|
| 117 |
+
assert datasetSplit in ["train", "val", "test"]
|
| 118 |
+
print("Computing %s dataset" % datasetSplit)
|
| 119 |
+
print("Using %d workers for data preparation"%self.n_cpus)
|
| 120 |
+
|
| 121 |
+
# os.environ["OMP_NUM_THREADS"] = "1"
|
| 122 |
+
# os.environ["MKL_NUM_THREADS"] = "1"
|
| 123 |
+
|
| 124 |
+
names = EncodedDirNamesAndTemplates(encodedDir)
|
| 125 |
+
|
| 126 |
+
tryMakedir(encodedDir, remove=False)
|
| 127 |
+
tryMakedir(os.path.join(encodedDir, datasetSplit))
|
| 128 |
+
tryMakedir(names.DIR_RAW_DATA_SELECTED)
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
fnames = [os.path.join(inputDir, fname) for fname in os.listdir(inputDir) if
|
| 132 |
+
re.match(config.RAW_DATA_FILE_SUFFIX, fname) and datasetSplit in fname]
|
| 133 |
+
|
| 134 |
+
assert len(fnames) > 0
|
| 135 |
+
|
| 136 |
+
results = Parallel(n_jobs=self.n_cpus, batch_size=1,
|
| 137 |
+
verbose=10)(delayed(self.processOneFileOfSmiles)(encodedDir, i, datasetSplit, fname, nrows=nrows)
|
| 138 |
+
for i, fname in enumerate(fnames))
|
| 139 |
+
results = chain.from_iterable(results)
|
| 140 |
+
results = list(results)
|
| 141 |
+
# print( results )
|
| 142 |
+
fnames_list, degrees, min_in_x, max_in_x, sizes_lis, n_per_bucket = zip(*results)
|
| 143 |
+
|
| 144 |
+
degrees = reduce(lambda prev, x: prev + x, degrees).numpy().tolist()
|
| 145 |
+
min_in_x = reduce(lambda prev, x: torch.stack([prev, x]).min(0)[0], min_in_x).numpy().tolist()
|
| 146 |
+
max_in_x = reduce(lambda prev, x: torch.stack([prev, x]).max(0)[0], max_in_x).numpy().tolist()
|
| 147 |
+
|
| 148 |
+
n_per_bucket = reduce(lambda prev, x: prev + x, n_per_bucket)
|
| 149 |
+
metadata_dict = {"name":datasetSplit, "fnames_list":fnames_list, "sizes_list": sizes_lis,
|
| 150 |
+
"total_size": sum(sizes_lis), "feature_stats":{"min":min_in_x, "max":max_in_x}}
|
| 151 |
+
metadata_dict.update(self.getNFeatures())
|
| 152 |
+
# print(metadata_dict)
|
| 153 |
+
joblib.dump(metadata_dict,
|
| 154 |
+
names.DATASET_METADATA_FNAME_TEMPLATE % datasetSplit)
|
| 155 |
+
joblib.dump(degrees, names.DEGREES_FNAME)
|
| 156 |
+
|
| 157 |
+
# print(n_per_bucket)
|
| 158 |
+
joblib.dump({"bucket_ranges": getBucketRanges(), "n_per_bucket":n_per_bucket}, names.BUCKETS_FNAME_TEMPLATE % datasetSplit)
|
| 159 |
+
print("Dataset %s computed" % datasetSplit)
|
| 160 |
+
return fnames_list
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
if __name__ == "__main__":
|
| 165 |
+
print( " ".join(sys.argv))
|
| 166 |
+
import argparse
|
| 167 |
+
parser = argparse.ArgumentParser()
|
| 168 |
+
parser.add_argument("-i", "--inputDir", type=str, default=config.DATASET_DIRNAME, help="Directory where smiles-price pairs are located")
|
| 169 |
+
parser.add_argument("-o", "--encodedDir", type=str, default=config.ENCODED_DIR)
|
| 170 |
+
parser.add_argument("-n", "--ncpus", type=int, default=config.N_CPUS)
|
| 171 |
+
parser.add_argument("--nrows", type=int, default=None, help="The number of rows to process in each file")
|
| 172 |
+
|
| 173 |
+
args = vars( parser.parse_args())
|
| 174 |
+
config.N_CPUS = args.get("ncpus", config.N_CPUS)
|
| 175 |
+
|
| 176 |
+
dataBuilder = DataBuilder(n_cpus=config.N_CPUS)
|
| 177 |
+
dataBuilder.prepareDataset(datasetSplit="train", **args)
|
| 178 |
+
dataBuilder.prepareDataset(datasetSplit="val", **args)
|
| 179 |
+
dataBuilder.prepareDataset(datasetSplit="test", **args)
|
| 180 |
+
|
| 181 |
+
'''
|
| 182 |
+
python -m pricePrediction.preprocessData.prepareDataMol2Price
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
'''
|
coprinet/pricePrediction/preprocessData/serializeDatapoints.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pickle
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def getExampleId(outFname_base, num_example):
|
| 6 |
+
return ("%s-%08d" % (outFname_base, num_example)).encode("UTF-8")
|
| 7 |
+
|
| 8 |
+
def serializeExample(price, graph):
|
| 9 |
+
price = torch.tensor(float(price), dtype=torch.float32)
|
| 10 |
+
one_data_serialized = pickle.dumps( {"graph": graph, "price" :price})
|
| 11 |
+
return one_data_serialized
|
| 12 |
+
|
| 13 |
+
def deserializeExample(exampleBytes):
|
| 14 |
+
example_dict = pickle.loads(exampleBytes)
|
| 15 |
+
return example_dict["graph"], example_dict["price"]
|
coprinet/pricePrediction/preprocessData/smilesToDescriptors.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import numpy as np
|
| 3 |
+
from rdkit import Chem
|
| 4 |
+
from rdkit.Chem import rdMolDescriptors
|
| 5 |
+
from rdkit.Chem.Descriptors import ExactMolWt
|
| 6 |
+
from rdkit.ML.Descriptors.CompoundDescriptors import GetAllDescriptorNames
|
| 7 |
+
from torch_geometric.data import Data as GraphData
|
| 8 |
+
from rdkit.ML.Descriptors.MoleculeDescriptors import MolecularDescriptorCalculator
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
calculator = {}
|
| 14 |
+
def smiles_to_graph(smi):
|
| 15 |
+
mol = Chem.MolFromSmiles(smi)
|
| 16 |
+
if mol is None:
|
| 17 |
+
return None
|
| 18 |
+
if not calculator:
|
| 19 |
+
chosen_descriptors = ['BertzCT',
|
| 20 |
+
'Chi0',
|
| 21 |
+
'Chi0n',
|
| 22 |
+
'Chi0v',
|
| 23 |
+
'Chi1',
|
| 24 |
+
'Chi1n',
|
| 25 |
+
'Chi1v',
|
| 26 |
+
'Chi2n',
|
| 27 |
+
'Chi2v',
|
| 28 |
+
'Chi3n',
|
| 29 |
+
'Chi3v',
|
| 30 |
+
'Chi4n',
|
| 31 |
+
'Chi4v',
|
| 32 |
+
'EState_VSA1',
|
| 33 |
+
'EState_VSA10',
|
| 34 |
+
'EState_VSA11',
|
| 35 |
+
'EState_VSA2',
|
| 36 |
+
'EState_VSA3',
|
| 37 |
+
'EState_VSA4',
|
| 38 |
+
'EState_VSA5',
|
| 39 |
+
'EState_VSA6',
|
| 40 |
+
'EState_VSA7',
|
| 41 |
+
'EState_VSA8',
|
| 42 |
+
'EState_VSA9',
|
| 43 |
+
'ExactMolWt',
|
| 44 |
+
'FpDensityMorgan1',
|
| 45 |
+
'FpDensityMorgan2',
|
| 46 |
+
'FpDensityMorgan3',
|
| 47 |
+
'FractionCSP3',
|
| 48 |
+
'HallKierAlpha',
|
| 49 |
+
'HeavyAtomCount',
|
| 50 |
+
'HeavyAtomMolWt',
|
| 51 |
+
'Kappa1',
|
| 52 |
+
'Kappa2',
|
| 53 |
+
'Kappa3',
|
| 54 |
+
'LabuteASA',
|
| 55 |
+
'MaxAbsEStateIndex',
|
| 56 |
+
'MaxEStateIndex',
|
| 57 |
+
'MinAbsEStateIndex',
|
| 58 |
+
'MinAbsPartialCharge',
|
| 59 |
+
'MinEStateIndex',
|
| 60 |
+
'MinPartialCharge',
|
| 61 |
+
'MolLogP',
|
| 62 |
+
'MolMR',
|
| 63 |
+
'MolWt',
|
| 64 |
+
'NHOHCount',
|
| 65 |
+
'NOCount',
|
| 66 |
+
'NumAliphaticCarbocycles',
|
| 67 |
+
'NumAliphaticHeterocycles',
|
| 68 |
+
'NumAliphaticRings',
|
| 69 |
+
'NumAromaticCarbocycles',
|
| 70 |
+
'NumAromaticHeterocycles',
|
| 71 |
+
'NumAromaticRings',
|
| 72 |
+
'NumHAcceptors',
|
| 73 |
+
'NumHDonors',
|
| 74 |
+
'NumHeteroatoms',
|
| 75 |
+
'NumRotatableBonds',
|
| 76 |
+
'NumSaturatedCarbocycles',
|
| 77 |
+
'NumSaturatedHeterocycles',
|
| 78 |
+
'NumSaturatedRings',
|
| 79 |
+
'NumValenceElectrons',
|
| 80 |
+
'PEOE_VSA1',
|
| 81 |
+
'PEOE_VSA10',
|
| 82 |
+
'PEOE_VSA11',
|
| 83 |
+
'PEOE_VSA12',
|
| 84 |
+
'PEOE_VSA13',
|
| 85 |
+
'PEOE_VSA14',
|
| 86 |
+
'PEOE_VSA2',
|
| 87 |
+
'PEOE_VSA3',
|
| 88 |
+
'PEOE_VSA4',
|
| 89 |
+
'PEOE_VSA5',
|
| 90 |
+
'PEOE_VSA6',
|
| 91 |
+
'PEOE_VSA7',
|
| 92 |
+
'PEOE_VSA8',
|
| 93 |
+
'PEOE_VSA9',
|
| 94 |
+
'RingCount',
|
| 95 |
+
'SMR_VSA1',
|
| 96 |
+
'SMR_VSA10',
|
| 97 |
+
'SMR_VSA2',
|
| 98 |
+
'SMR_VSA3',
|
| 99 |
+
'SMR_VSA4',
|
| 100 |
+
'SMR_VSA5',
|
| 101 |
+
'SMR_VSA6',
|
| 102 |
+
'SMR_VSA7',
|
| 103 |
+
'SMR_VSA9',
|
| 104 |
+
'SlogP_VSA1',
|
| 105 |
+
'SlogP_VSA10',
|
| 106 |
+
'SlogP_VSA11',
|
| 107 |
+
'SlogP_VSA12',
|
| 108 |
+
'SlogP_VSA2',
|
| 109 |
+
'SlogP_VSA3',
|
| 110 |
+
'SlogP_VSA4',
|
| 111 |
+
'SlogP_VSA5',
|
| 112 |
+
'SlogP_VSA6',
|
| 113 |
+
'SlogP_VSA7',
|
| 114 |
+
'SlogP_VSA8',
|
| 115 |
+
'TPSA',
|
| 116 |
+
'VSA_EState1',
|
| 117 |
+
'VSA_EState10',
|
| 118 |
+
'VSA_EState2',
|
| 119 |
+
'VSA_EState3',
|
| 120 |
+
'VSA_EState4',
|
| 121 |
+
'VSA_EState5',
|
| 122 |
+
'VSA_EState6',
|
| 123 |
+
'VSA_EState7',
|
| 124 |
+
'VSA_EState8',
|
| 125 |
+
'VSA_EState9',
|
| 126 |
+
'fr_Al_COO',
|
| 127 |
+
'fr_Al_OH',
|
| 128 |
+
'fr_Al_OH_noTert',
|
| 129 |
+
'fr_ArN',
|
| 130 |
+
'fr_Ar_COO',
|
| 131 |
+
'fr_Ar_N',
|
| 132 |
+
'fr_Ar_NH',
|
| 133 |
+
'fr_Ar_OH',
|
| 134 |
+
'fr_COO',
|
| 135 |
+
'fr_COO2',
|
| 136 |
+
'fr_C_O',
|
| 137 |
+
'fr_C_O_noCOO',
|
| 138 |
+
'fr_C_S',
|
| 139 |
+
'fr_HOCCN',
|
| 140 |
+
'fr_Imine',
|
| 141 |
+
'fr_NH0',
|
| 142 |
+
'fr_NH1',
|
| 143 |
+
'fr_NH2',
|
| 144 |
+
'fr_N_O',
|
| 145 |
+
'fr_Ndealkylation1',
|
| 146 |
+
'fr_Ndealkylation2',
|
| 147 |
+
'fr_Nhpyrrole',
|
| 148 |
+
'fr_SH',
|
| 149 |
+
'fr_aldehyde',
|
| 150 |
+
'fr_alkyl_carbamate',
|
| 151 |
+
'fr_alkyl_halide',
|
| 152 |
+
'fr_allylic_oxid',
|
| 153 |
+
'fr_amide',
|
| 154 |
+
'fr_amidine',
|
| 155 |
+
'fr_aniline',
|
| 156 |
+
'fr_aryl_methyl',
|
| 157 |
+
'fr_azide',
|
| 158 |
+
'fr_azo',
|
| 159 |
+
'fr_barbitur',
|
| 160 |
+
'fr_benzene',
|
| 161 |
+
'fr_benzodiazepine',
|
| 162 |
+
'fr_bicyclic',
|
| 163 |
+
'fr_dihydropyridine',
|
| 164 |
+
'fr_epoxide',
|
| 165 |
+
'fr_ester',
|
| 166 |
+
'fr_ether',
|
| 167 |
+
'fr_furan',
|
| 168 |
+
'fr_guanido',
|
| 169 |
+
'fr_halogen',
|
| 170 |
+
'fr_hdrzine',
|
| 171 |
+
'fr_hdrzone',
|
| 172 |
+
'fr_imidazole',
|
| 173 |
+
'fr_imide',
|
| 174 |
+
'fr_isothiocyan',
|
| 175 |
+
'fr_ketone',
|
| 176 |
+
'fr_ketone_Topliss',
|
| 177 |
+
'fr_lactam',
|
| 178 |
+
'fr_lactone',
|
| 179 |
+
'fr_methoxy',
|
| 180 |
+
'fr_morpholine',
|
| 181 |
+
'fr_nitrile',
|
| 182 |
+
'fr_nitro',
|
| 183 |
+
'fr_nitro_arom',
|
| 184 |
+
'fr_nitro_arom_nonortho',
|
| 185 |
+
'fr_nitroso',
|
| 186 |
+
'fr_oxazole',
|
| 187 |
+
'fr_oxime',
|
| 188 |
+
'fr_para_hydroxylation',
|
| 189 |
+
'fr_phenol',
|
| 190 |
+
'fr_phenol_noOrthoHbond',
|
| 191 |
+
'fr_phos_acid',
|
| 192 |
+
'fr_phos_ester',
|
| 193 |
+
'fr_piperdine',
|
| 194 |
+
'fr_piperzine',
|
| 195 |
+
'fr_priamide',
|
| 196 |
+
'fr_pyridine',
|
| 197 |
+
'fr_quatN',
|
| 198 |
+
'fr_sulfide',
|
| 199 |
+
'fr_sulfonamd',
|
| 200 |
+
'fr_sulfone',
|
| 201 |
+
'fr_term_acetylene',
|
| 202 |
+
'fr_tetrazole',
|
| 203 |
+
'fr_thiazole',
|
| 204 |
+
'fr_thiocyan',
|
| 205 |
+
'fr_thiophene',
|
| 206 |
+
'fr_unbrch_alkane',
|
| 207 |
+
'fr_urea',
|
| 208 |
+
'qed']
|
| 209 |
+
|
| 210 |
+
calculator["calculator"] = MolecularDescriptorCalculator(chosen_descriptors)
|
| 211 |
+
|
| 212 |
+
x = np.array(calculator["calculator"].CalcDescriptors(mol))
|
| 213 |
+
x = x.reshape((1,-1))
|
| 214 |
+
x = torch.FloatTensor(x)
|
| 215 |
+
if torch.isnan(x).any():
|
| 216 |
+
return None
|
| 217 |
+
graph = GraphData(x=x, edge_index=torch.zeros(2,0, dtype=torch.long), edge_attr=torch.zeros(0,1))
|
| 218 |
+
return graph
|
| 219 |
+
|
| 220 |
+
def test(smi = "C1=CC=C(C=C1)CC(C(=O)O)N"):
|
| 221 |
+
|
| 222 |
+
mol = Chem.MolFromSmiles(smi)
|
| 223 |
+
# print(mol)
|
| 224 |
+
graph = smiles_to_graph(smi)
|
| 225 |
+
print(graph.keys)
|
| 226 |
+
print(graph.x)
|
| 227 |
+
print(graph.edge_index)
|
| 228 |
+
print( graph.edge_attr )
|
| 229 |
+
print( graph.x.shape, graph.edge_index.shape, graph.edge_attr.shape)
|
| 230 |
+
print(graph.x.max(), graph.x.min())
|
| 231 |
+
for idx in range(mol.GetNumAtoms()):
|
| 232 |
+
mol.GetAtomWithIdx(idx).SetProp('molAtomMapNumber', str(mol.GetAtomWithIdx(idx).GetIdx()))
|
| 233 |
+
from matplotlib import pyplot as plt; from rdkit.Chem import Draw; plt.imshow(Draw.MolsToGridImage([mol], molsPerRow=2)); plt.show()
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
if __name__ == "__main__":
|
| 237 |
+
smis_list = ["[C@H](C)1CCCO1", "O[C@@H](N)C", "C1=CC=C(C=C1)CC(C(=O)O)N"]
|
| 238 |
+
for smi in smis_list:
|
| 239 |
+
test(smi)
|
coprinet/pricePrediction/preprocessData/smilesToGraph.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import numpy as np
|
| 3 |
+
from rdkit import Chem
|
| 4 |
+
from rdkit.Chem.Descriptors import ExactMolWt
|
| 5 |
+
from torch_geometric.data import Data as GraphData
|
| 6 |
+
|
| 7 |
+
ALLOWED_ATOMIC_NUMS = {6, 7, 8, 9, 15, 16, 17, 35}
|
| 8 |
+
MIN_NUM_ATOMS = 3
|
| 9 |
+
MAX_NUM_ATOMS = 60
|
| 10 |
+
|
| 11 |
+
def get_node_feats(mol: Chem.Mol, atom: Chem.Atom):
|
| 12 |
+
'''
|
| 13 |
+
|
| 14 |
+
:param mol: The mol from which the node features will be computed. Currently not used
|
| 15 |
+
:param atom: The atom from which the node features will be computed
|
| 16 |
+
:return:
|
| 17 |
+
'''
|
| 18 |
+
atom_num = atom.GetAtomicNum()
|
| 19 |
+
valence = atom.GetTotalValence()
|
| 20 |
+
charge = atom.GetFormalCharge()
|
| 21 |
+
degree = atom.GetDegree()
|
| 22 |
+
is_aromatic = atom.GetIsAromatic()
|
| 23 |
+
return [float(elem) for elem in [atom_num, valence, charge, degree, is_aromatic]]
|
| 24 |
+
# return [float(elem) for elem in [atom_num, valence, charge, is_aromatic]]
|
| 25 |
+
|
| 26 |
+
# results = one_of_k_encoding_unk(
|
| 27 |
+
# atom.GetSymbol(),
|
| 28 |
+
# [
|
| 29 |
+
# 'B',
|
| 30 |
+
# 'C',
|
| 31 |
+
# 'N',
|
| 32 |
+
# 'O',
|
| 33 |
+
# 'F',
|
| 34 |
+
# 'Si',
|
| 35 |
+
# 'P',
|
| 36 |
+
# 'S',
|
| 37 |
+
# 'Cl',
|
| 38 |
+
# 'As',
|
| 39 |
+
# 'Se',
|
| 40 |
+
# 'Br',
|
| 41 |
+
# 'Te',
|
| 42 |
+
# 'I',
|
| 43 |
+
# 'At',
|
| 44 |
+
# 'other'
|
| 45 |
+
# ]) + one_of_k_encoding(atom.GetDegree(),
|
| 46 |
+
# [0, 1, 2, 3, 4, 5]) + \
|
| 47 |
+
# [atom.GetFormalCharge(), atom.GetNumRadicalElectrons()] + \
|
| 48 |
+
# one_of_k_encoding_unk(atom.GetHybridization(), [
|
| 49 |
+
# Chem.rdchem.HybridizationType.SP, Chem.rdchem.HybridizationType.SP2,
|
| 50 |
+
# Chem.rdchem.HybridizationType.SP3, Chem.rdchem.HybridizationType.
|
| 51 |
+
# SP3D, Chem.rdchem.HybridizationType.SP3D2, 'other'
|
| 52 |
+
# ]) + [atom.GetIsAromatic()]
|
| 53 |
+
|
| 54 |
+
def get_bond_feats(mol: Chem.Mol, bond: Chem.Bond):
|
| 55 |
+
'''
|
| 56 |
+
|
| 57 |
+
:param mol: The mol from which the edge features will be computed. Currently not used
|
| 58 |
+
:param bond: The bond from which the edge features will be computed
|
| 59 |
+
:return:
|
| 60 |
+
'''
|
| 61 |
+
if bond.GetIsAromatic():
|
| 62 |
+
bond_num = 1.5
|
| 63 |
+
else:
|
| 64 |
+
bond_num = bond.GetBondType()
|
| 65 |
+
bond_num = float(bond_num)
|
| 66 |
+
if bond_num > 3:
|
| 67 |
+
bond_num = 4
|
| 68 |
+
|
| 69 |
+
# return [bond_num ]
|
| 70 |
+
bondInfo = [bond_num, int(bond.GetIsAromatic()), int(bond.GetIsConjugated()), int(bond.IsInRing()) ]
|
| 71 |
+
# bondInfo += [["STEREONONE", "STEREOANY", "STEREOZ", "STEREOE"].index(str(bond.GetStereo()))]
|
| 72 |
+
|
| 73 |
+
return bondInfo
|
| 74 |
+
|
| 75 |
+
def smiles_to_graph(smi):
|
| 76 |
+
mol = Chem.MolFromSmiles(smi)
|
| 77 |
+
if mol is None:
|
| 78 |
+
return None
|
| 79 |
+
|
| 80 |
+
# remove stereo information, such as inward and outward edges
|
| 81 |
+
Chem.RemoveStereochemistry(mol)
|
| 82 |
+
|
| 83 |
+
nodes = []
|
| 84 |
+
edges_index, edges_attr = [], []
|
| 85 |
+
|
| 86 |
+
cur_atom_id = 0
|
| 87 |
+
rdkit_atomId_to_atom_id = {}
|
| 88 |
+
for bond in mol.GetBonds():
|
| 89 |
+
atom1_idx = bond.GetBeginAtomIdx()
|
| 90 |
+
atom2_idx = bond.GetEndAtomIdx()
|
| 91 |
+
atom1 = mol.GetAtomWithIdx(atom1_idx)
|
| 92 |
+
atom2 = mol.GetAtomWithIdx(atom2_idx)
|
| 93 |
+
atomicNum1 = atom1.GetAtomicNum()
|
| 94 |
+
atomicNum2 = atom2.GetAtomicNum()
|
| 95 |
+
|
| 96 |
+
if atomicNum1 == 0 or atomicNum2 == 0:
|
| 97 |
+
continue
|
| 98 |
+
if atomicNum1 not in ALLOWED_ATOMIC_NUMS or atomicNum2 not in ALLOWED_ATOMIC_NUMS:
|
| 99 |
+
return None
|
| 100 |
+
if atom1_idx not in rdkit_atomId_to_atom_id:
|
| 101 |
+
rdkit_atomId_to_atom_id[atom1_idx] = cur_atom_id
|
| 102 |
+
nodes.append( get_node_feats(mol, atom1) )
|
| 103 |
+
cur_atom_id += 1
|
| 104 |
+
if atom2_idx not in rdkit_atomId_to_atom_id:
|
| 105 |
+
rdkit_atomId_to_atom_id[atom2_idx] = cur_atom_id
|
| 106 |
+
nodes.append( get_node_feats(mol, atom2) )
|
| 107 |
+
cur_atom_id += 1
|
| 108 |
+
idx1, idx2 = rdkit_atomId_to_atom_id[atom1_idx], rdkit_atomId_to_atom_id[atom2_idx]
|
| 109 |
+
# print(atom1_idx, idx1, atom2_idx, idx2)
|
| 110 |
+
edges_index.append([idx1, idx2])
|
| 111 |
+
edges_index.append([idx2, idx1])
|
| 112 |
+
|
| 113 |
+
bond_feats = get_bond_feats(mol, bond)
|
| 114 |
+
edges_attr.extend([bond_feats, bond_feats])
|
| 115 |
+
|
| 116 |
+
x = np.array(nodes, dtype=np.float32)
|
| 117 |
+
|
| 118 |
+
if x.shape[0] < MIN_NUM_ATOMS or x.shape[0] > MAX_NUM_ATOMS:
|
| 119 |
+
return None
|
| 120 |
+
edges_index = np.array( edges_index).T.copy().astype( np.int64 )
|
| 121 |
+
edges_attr = np.array(edges_attr, dtype=np.float32)
|
| 122 |
+
graph_dict = dict(x=x, edge_index=edges_index, edge_attr=edges_attr)
|
| 123 |
+
graph = GraphData(**{key: torch.tensor(val) for key, val in graph_dict.items()} )
|
| 124 |
+
return graph
|
| 125 |
+
|
| 126 |
+
def fromPerGramToPerMMolPrice(price, smi):
|
| 127 |
+
mol = Chem.MolFromSmiles(smi)
|
| 128 |
+
if mol is None:
|
| 129 |
+
return None
|
| 130 |
+
return price * ExactMolWt(mol) / 1000
|
| 131 |
+
|
| 132 |
+
def compute_nodes_degree(graphs, max_degree=7):
|
| 133 |
+
from torch_geometric.utils import degree
|
| 134 |
+
import torch
|
| 135 |
+
deg = torch.zeros(max_degree, dtype=torch.long)
|
| 136 |
+
if graphs is None:
|
| 137 |
+
return deg
|
| 138 |
+
for data in graphs:
|
| 139 |
+
d = degree(data.edge_index[1], num_nodes=data.num_nodes, dtype=torch.long)
|
| 140 |
+
deg += torch.bincount(d, minlength=deg.numel())[:deg.numel()]
|
| 141 |
+
return deg
|
| 142 |
+
|
| 143 |
+
def test(smi = "C1=CC=C(C=C1)CC(C(=O)O)N"):
|
| 144 |
+
|
| 145 |
+
mol = Chem.MolFromSmiles(smi)
|
| 146 |
+
# print(mol)
|
| 147 |
+
graph = smiles_to_graph(smi)
|
| 148 |
+
print(graph.keys)
|
| 149 |
+
print(graph.x)
|
| 150 |
+
print(graph.edge_index)
|
| 151 |
+
print( graph.edge_attr )
|
| 152 |
+
print( graph.x.shape, graph.edge_index.shape, graph.edge_attr.shape)
|
| 153 |
+
for idx in range(mol.GetNumAtoms()):
|
| 154 |
+
mol.GetAtomWithIdx(idx).SetProp('molAtomMapNumber', str(mol.GetAtomWithIdx(idx).GetIdx()))
|
| 155 |
+
from matplotlib import pyplot as plt; from rdkit.Chem import Draw; plt.imshow(Draw.MolsToGridImage([mol], molsPerRow=2)); plt.show()
|
| 156 |
+
|
| 157 |
+
if __name__ == "__main__":
|
| 158 |
+
smis_list = ["[C@H](C)1CCCO1", "O[C@@H](N)C", "C1=CC=C(C=C1)CC(C(=O)O)N"]
|
| 159 |
+
for smi in smis_list:
|
| 160 |
+
test(smi)
|
coprinet/pricePrediction/selectFromRawData/__init__.py
ADDED
|
File without changes
|
coprinet/pricePrediction/selectFromRawData/selectFromRawDataNonVirtualAll.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, sys
|
| 2 |
+
from argparse import ArgumentParser
|
| 3 |
+
|
| 4 |
+
from pricePrediction.config import TEST_SIZE
|
| 5 |
+
from pricePrediction.selectFromRawData.selectFromRawDataNonVirtualAll_base import RawDataSelector
|
| 6 |
+
from pricePrediction import config
|
| 7 |
+
|
| 8 |
+
class SelectFromRawDataNonVirtualAll(RawDataSelector):
|
| 9 |
+
|
| 10 |
+
DESIRED_PARAMS_TO_ASK= ['full_dataset_fname', 'computed_datadir', 'test_size', 'nrows']
|
| 11 |
+
def __init__(self, full_dataset_fname: str =
|
| 12 |
+
os.path.join(config.RAW_DATA_DIRNAME, "mcule_purchasable_full_prices_210616_O9Jw1D_only_READILY.csv.gz"),
|
| 13 |
+
computed_datadir: str = config.DATASET_DIRNAME,
|
| 14 |
+
test_size:int=TEST_SIZE, nrows:int=None):
|
| 15 |
+
'''
|
| 16 |
+
|
| 17 |
+
:param str full_dataset_fname: The fname for the whole Mcule csv file (csv.gz)
|
| 18 |
+
:param str bb_fname: The fname for the building blocks csv file (csv.gz)
|
| 19 |
+
:param str computed_datadir: The directory where the selected records will be saved
|
| 20 |
+
:param int test_size: The number of entries to include in the test set
|
| 21 |
+
:param int nrows: The number of rows to process in the full_dataset_fname. If None, process all rows
|
| 22 |
+
'''
|
| 23 |
+
super().__init__(full_dataset_fname, None, computed_datadir, test_size=test_size, nrows=nrows)
|
| 24 |
+
|
| 25 |
+
def operate_data(self, data):
|
| 26 |
+
|
| 27 |
+
# data = data[ ~ data.iloc[:,-1].isna() ]
|
| 28 |
+
data = data[self.COLNAMES[:3]]
|
| 29 |
+
data = data.rename(columns={self.COLNAMES[2]: "price"})
|
| 30 |
+
del data["Mcule ID"]
|
| 31 |
+
return data
|
| 32 |
+
|
| 33 |
+
if __name__ == "__main__":
|
| 34 |
+
parser = ArgumentParser(description="extract from raw data train/test/val partitions")
|
| 35 |
+
SelectFromRawDataNonVirtualAll.addParamsToArgParse(parser)
|
| 36 |
+
args = parser.parse_args()
|
| 37 |
+
SelectFromRawDataNonVirtualAll(**vars(args)).compute()
|
| 38 |
+
|
| 39 |
+
'''
|
| 40 |
+
python -m pricePrediction.selectFromRawData.selectFromRawDataNonVirtualAll
|
| 41 |
+
'''
|
coprinet/pricePrediction/selectFromRawData/selectFromRawDataNonVirtualAll_base.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import dask.dataframe as dd
|
| 3 |
+
import pandas as pd
|
| 4 |
+
|
| 5 |
+
from pricePrediction import config
|
| 6 |
+
from pricePrediction.ArgParser_base import ArgParseable
|
| 7 |
+
from pricePrediction.config import TEST_SIZE
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class RawDataSelector(ArgParseable):
|
| 11 |
+
COLNAMES = "Mcule ID,SMILES,price 1 (USD),amount 1 (mg),delivery time 1 (w.days),available amount 1 (mg)".split(",")
|
| 12 |
+
|
| 13 |
+
DESIRED_PARAMS_TO_ASK= ['full_dataset_fname', 'bb_fname', 'computed_datadir', 'test_size', 'nrows']
|
| 14 |
+
def __init__(self, full_dataset_fname: str, bb_fname: str= config.BUILDING_BLOCKS_FNAME,
|
| 15 |
+
computed_datadir: str= config.DATASET_DIRNAME, test_size:int=TEST_SIZE, nrows:int=None):
|
| 16 |
+
'''
|
| 17 |
+
|
| 18 |
+
:param str full_dataset_fname: The fname for the whole Mcule csv file (csv.gz)
|
| 19 |
+
:param str bb_fname: The fname for the building blocks csv file (csv.gz)
|
| 20 |
+
:param str computed_datadir: The directory where the selected records will be saved
|
| 21 |
+
:param int test_size: The number of entries to include in the test set
|
| 22 |
+
:param int nrows: The number of rows to process in the full_dataset_fname. If None, process all rows
|
| 23 |
+
'''
|
| 24 |
+
self.full_dataset_fname = full_dataset_fname
|
| 25 |
+
self.bb_fname = bb_fname
|
| 26 |
+
self.computed_datadir = computed_datadir
|
| 27 |
+
self.test_size = test_size
|
| 28 |
+
self.nrows = nrows
|
| 29 |
+
|
| 30 |
+
def operate_data(self, data):
|
| 31 |
+
raise NotImplementedError()
|
| 32 |
+
|
| 33 |
+
def compute(self):
|
| 34 |
+
if self.full_dataset_fname.endswith(".gz"):
|
| 35 |
+
data = pd.read_csv(self.full_dataset_fname, usecols=self.COLNAMES, nrows=self.nrows)
|
| 36 |
+
data = dd.from_pandas(data, npartitions=30)
|
| 37 |
+
else:
|
| 38 |
+
data = dd.read_csv(self.full_dataset_fname, usecols=self.COLNAMES,blocksize=None)
|
| 39 |
+
if self.nrows is not None:
|
| 40 |
+
data = data.head(n=self.nrows, compute=False)
|
| 41 |
+
|
| 42 |
+
data = self.operate_data(data)
|
| 43 |
+
self.saveDataSplits(data)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def saveDataSplits(self, data, computed_datadir=None):
|
| 47 |
+
if computed_datadir is None:
|
| 48 |
+
computed_datadir = self.computed_datadir
|
| 49 |
+
print("Saving results to %s"%computed_datadir)
|
| 50 |
+
if isinstance(data, dd.DataFrame):
|
| 51 |
+
n_elems = data.shape[0].compute()
|
| 52 |
+
print("Total size: ", n_elems)
|
| 53 |
+
test_frac = self.test_size/n_elems
|
| 54 |
+
data_train, data_test = data.random_split([1-test_frac, test_frac])
|
| 55 |
+
|
| 56 |
+
del data
|
| 57 |
+
|
| 58 |
+
data_test = data_test.repartition(npartitions= int(n_elems*test_frac//self.test_size)+1)
|
| 59 |
+
data_test.to_csv(os.path.join(computed_datadir, "mcule_full_test.csv_split_*.csv"), index=False)
|
| 60 |
+
del data_test
|
| 61 |
+
|
| 62 |
+
n_elems = data_train.shape[0].compute()
|
| 63 |
+
val_frac = self.test_size/n_elems
|
| 64 |
+
data_train, data_val = data_train.random_split([1-val_frac, val_frac])
|
| 65 |
+
data_val= data_val.repartition(npartitions= int(n_elems*val_frac//self.test_size)+1)
|
| 66 |
+
|
| 67 |
+
data_val.to_csv(os.path.join(computed_datadir, "mcule_full_val.csv_split_*.csv"), index=False)
|
| 68 |
+
del data_val
|
| 69 |
+
|
| 70 |
+
data_train= data_train.repartition(npartitions= int(n_elems*(1-val_frac)//self.test_size)+1)
|
| 71 |
+
data_train.to_csv(os.path.join(computed_datadir, "mcule_full_train.csv_split_*.csv"), index=False)
|
| 72 |
+
with open(os.path.join(computed_datadir,"params.txt"), "w") as f:
|
| 73 |
+
f.write(str(type(self))+"\n" )
|
| 74 |
+
f.write(str(self.full_dataset_fname)+"\n" )
|
| 75 |
+
f.write(str(self.bb_fname)+"\n" )
|
| 76 |
+
f.write(str(self.computed_datadir))
|
coprinet/pricePrediction/selectFromRawData/selectFromRawDataOnlyVirtualAll.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, sys
|
| 2 |
+
import dask.dataframe as dd
|
| 3 |
+
from argparse import ArgumentParser
|
| 4 |
+
|
| 5 |
+
from pricePrediction.config import TEST_SIZE
|
| 6 |
+
from pricePrediction.selectFromRawData.selectFromRawDataNonVirtualAll_base import RawDataSelector
|
| 7 |
+
import pandas as pd
|
| 8 |
+
from pricePrediction import config
|
| 9 |
+
|
| 10 |
+
class SelectFromRawDataOnlyVirtualAll(RawDataSelector):
|
| 11 |
+
DESIRED_PARAMS_TO_ASK= ['full_dataset_fname', 'bb_fname', 'computed_datadir',]
|
| 12 |
+
def __init__(self, full_dataset_fname: str =
|
| 13 |
+
os.path.join(config.RAW_DATA_DIRNAME, "mcule_purchasable_full_prices_210319_kLp4Zt_march.csv.gz"),
|
| 14 |
+
bb_fname: str = config.BUILDING_BLOCKS_FNAME,
|
| 15 |
+
computed_datadir: str = config.DATASET_DIRNAME,
|
| 16 |
+
max_size:int=10000000,
|
| 17 |
+
test_size:int=TEST_SIZE, nrows:int=None):
|
| 18 |
+
'''
|
| 19 |
+
:param str full_dataset_fname: The fname for the whole Mcule csv file (csv.gz)
|
| 20 |
+
:param str bb_fname: The fname for the building blocks csv file (csv.gz)
|
| 21 |
+
:param str computed_datadir: The directory where the selected records will be saved
|
| 22 |
+
:param int test_size: The number of entries to include in the test set
|
| 23 |
+
:param int nrows: The number of rows to process in the full_dataset_fname. If None, process all rows
|
| 24 |
+
'''
|
| 25 |
+
super().__init__(full_dataset_fname, bb_fname, computed_datadir,test_size=test_size,nrows=nrows)
|
| 26 |
+
self.max_size = max_size
|
| 27 |
+
|
| 28 |
+
def read_csv(self, csv_name):
|
| 29 |
+
data = dd.read_csv(csv_name, usecols=self.COLNAMES,blocksize=None)
|
| 30 |
+
data = data[ data[self.COLNAMES[-1]].isna() ] #To select compounds that are not in stock
|
| 31 |
+
data = data[self.COLNAMES[:3]]
|
| 32 |
+
data = data.rename(columns={self.COLNAMES[2]: "price"})
|
| 33 |
+
return data
|
| 34 |
+
|
| 35 |
+
def operate_data(self, data):
|
| 36 |
+
data = self.read_csv(self.full_dataset_fname)
|
| 37 |
+
size = data.shape[0].compute()
|
| 38 |
+
bb = pd.read_csv(self.bb_fname, sep="\t", header=None)
|
| 39 |
+
bb.columns = ["SMILES", "Mcule ID"]
|
| 40 |
+
del bb["SMILES"]
|
| 41 |
+
data = data[ (~data["Mcule ID"].isin(bb["Mcule ID"])) ]
|
| 42 |
+
size = data.shape[0].compute()
|
| 43 |
+
print("Total size: ",size)
|
| 44 |
+
sampling_size = min(size, self.max_size)
|
| 45 |
+
data = data.sample(frac= sampling_size/size)
|
| 46 |
+
print("Sampled size: ",sampling_size)
|
| 47 |
+
data = data[["SMILES", "price"]]
|
| 48 |
+
del bb
|
| 49 |
+
return data
|
| 50 |
+
|
| 51 |
+
if __name__ == "__main__":
|
| 52 |
+
parser = ArgumentParser(description="extract from raw data train/test/val partitions")
|
| 53 |
+
SelectFromRawDataOnlyVirtualAll.addParamsToArgParse(parser)
|
| 54 |
+
args = parser.parse_args()
|
| 55 |
+
SelectFromRawDataOnlyVirtualAll(**vars(args)).compute()
|
coprinet/pricePrediction/train/__init__.py
ADDED
|
File without changes
|
coprinet/pricePrediction/train/trainNet.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import sys, os
|
| 3 |
+
|
| 4 |
+
# import torch
|
| 5 |
+
# torch.multiprocessing.set_start_method('spawn')
|
| 6 |
+
# torch.multiprocessing.set_sharing_strategy('file_system')
|
| 7 |
+
|
| 8 |
+
from pytorch_lightning import seed_everything
|
| 9 |
+
from pytorch_lightning.callbacks import EarlyStopping, ModelCheckpoint, StochasticWeightAveraging
|
| 10 |
+
import pytorch_lightning as pl
|
| 11 |
+
|
| 12 |
+
from pytorch_lightning.plugins import DDPPlugin
|
| 13 |
+
|
| 14 |
+
from pricePrediction import config
|
| 15 |
+
from pricePrediction.ArgParser_base import MyArgParser
|
| 16 |
+
from pricePrediction.dataManager.dataManager import GraphPriceDatamodule
|
| 17 |
+
from pricePrediction.evaluation.evaluation import PlotFigsTensorboardCallback, InferencePlotter
|
| 18 |
+
from pricePrediction.nets.netsGraph import PricePredictorModule
|
| 19 |
+
|
| 20 |
+
def parse_args():
|
| 21 |
+
|
| 22 |
+
parser = MyArgParser(prog=None, usage=None, description=None, )
|
| 23 |
+
|
| 24 |
+
#### Program args ####
|
| 25 |
+
parser.add_argument("-e", "--n_epochs", type=int, help="Number of epochs to use", default=1000)
|
| 26 |
+
parser.add_argument("-m", "--msg", type=str, help="A message describing the run", required=True)
|
| 27 |
+
parser.add_argument("-r", "--restore", type=str, help="Directory to trained checkpoint to continue training", default=None)
|
| 28 |
+
parser.add_argument("-c", "--config", type=str, help="Path to json file with the config arguments to create/update the "
|
| 29 |
+
"model")
|
| 30 |
+
parser.add_argument("-o", "--default_root_dir", type=str, help="The directory where to save "
|
| 31 |
+
"checkpoints and logs", default=None)
|
| 32 |
+
parser.add_argument("-g", "--gpus", type=int, help="Number of gpus to use", default=1)
|
| 33 |
+
parser.add_argument("-n", "--num_nodes", type=int, help="Number of nodes to use", default=None)
|
| 34 |
+
parser.add_argument("-s", "--random_seed", type=int, help="Random seed", default=121)
|
| 35 |
+
|
| 36 |
+
parser.add_argument( "--limit_train_batches", type=float, help="Train only with a fraction of the tranining set", default=None)
|
| 37 |
+
parser.add_argument("--swa", action="store_true", default=False,
|
| 38 |
+
help="Perform Stochastic Weight Averaging")
|
| 39 |
+
#### Model and data args ####
|
| 40 |
+
|
| 41 |
+
group = parser.add_argument_group(title="data")
|
| 42 |
+
GraphPriceDatamodule.addParamsToArgParse(group)
|
| 43 |
+
group = parser.add_argument_group(title="model")
|
| 44 |
+
PricePredictorModule.addParamsToArgParse(group)
|
| 45 |
+
|
| 46 |
+
cmd_args = parser.parse_args()
|
| 47 |
+
del cmd_args['positional arguments']
|
| 48 |
+
cmd_args['main'] = cmd_args['optional arguments']
|
| 49 |
+
del cmd_args['optional arguments']
|
| 50 |
+
return cmd_args
|
| 51 |
+
|
| 52 |
+
if __name__ == "__main__":
|
| 53 |
+
print( " ".join(sys.argv))
|
| 54 |
+
cmd_args = parse_args()
|
| 55 |
+
|
| 56 |
+
if cmd_args.get("config"):
|
| 57 |
+
print("Loading configuration file:", cmd_args.get("config"))
|
| 58 |
+
with open(cmd_args.get("config")) as f:
|
| 59 |
+
args = json.load(f)
|
| 60 |
+
|
| 61 |
+
for groupName in cmd_args:
|
| 62 |
+
args[groupName].update(cmd_args[groupName])
|
| 63 |
+
else:
|
| 64 |
+
args = cmd_args
|
| 65 |
+
|
| 66 |
+
print(args)
|
| 67 |
+
|
| 68 |
+
seed_everything(args["main"]["random_seed"])
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
if args['main']["restore"]:
|
| 72 |
+
prev_run_dir = os.path.expanduser(args['main']["restore"])
|
| 73 |
+
# hparams = yaml.load( os.path.join(prev_run_dir, "hparams.yaml"))
|
| 74 |
+
checkpointsDir = os.path.join(prev_run_dir, "checkpoints")
|
| 75 |
+
most_recent_checkpoint_fname = max([os.path.join(checkpointsDir, basename) for basename in os.listdir(checkpointsDir)],
|
| 76 |
+
key=os.path.getctime)
|
| 77 |
+
|
| 78 |
+
pl_model = PricePredictorModule.load_from_checkpoint(most_recent_checkpoint_fname, **args["model"])
|
| 79 |
+
data_args = pl_model.hparams.get("data_hparams")
|
| 80 |
+
data_args.update(args["data"])
|
| 81 |
+
dataModule = GraphPriceDatamodule(**data_args)
|
| 82 |
+
|
| 83 |
+
print("Previous model loaded!!")
|
| 84 |
+
|
| 85 |
+
else:
|
| 86 |
+
dataModule = GraphPriceDatamodule(**args["data"])
|
| 87 |
+
|
| 88 |
+
dataModule.prepare_data()
|
| 89 |
+
nodes_n_features, edges_n_features = dataModule.dims
|
| 90 |
+
nodes_degree = dataModule.get_nodes_degree()
|
| 91 |
+
args["model"].update( dict(
|
| 92 |
+
nodes_n_features=nodes_n_features, edges_n_features=edges_n_features,
|
| 93 |
+
deg=nodes_degree, data_hparams= args["data"], logs_only_in_epoch= args["main"].get("gpus", 0) > 1)
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
pl_model = PricePredictorModule( **args["model"] )
|
| 97 |
+
|
| 98 |
+
print( pl_model.hparams )
|
| 99 |
+
|
| 100 |
+
callbacks = [
|
| 101 |
+
EarlyStopping(monitor='val_loss', patience=60),
|
| 102 |
+
ModelCheckpoint(monitor='val_loss', verbose=True),
|
| 103 |
+
PlotFigsTensorboardCallback(frequency=5, save_csv=True)
|
| 104 |
+
]
|
| 105 |
+
|
| 106 |
+
if args["main"].get("swa", False):
|
| 107 |
+
callbacks += [
|
| 108 |
+
StochasticWeightAveraging(annealing_epochs=config.COSINE_LR_SCHEDULE_N_EPOCHS, swa_lrs= pl_model.hparams.lr)
|
| 109 |
+
]
|
| 110 |
+
|
| 111 |
+
default_root_dir = args['main'].get('default_root_dir', os.getcwd())
|
| 112 |
+
trainer_args = dict(gpus=1, max_epochs=args['main']["n_epochs"], progress_bar_refresh_rate=20,
|
| 113 |
+
default_root_dir=default_root_dir, auto_lr_find=True,
|
| 114 |
+
callbacks = callbacks)
|
| 115 |
+
if args["main"]["limit_train_batches"]:
|
| 116 |
+
trainer_args["limit_train_batches"] = args["main"]["limit_train_batches"]
|
| 117 |
+
|
| 118 |
+
ngpus = args['main'].get("gpus", -1)
|
| 119 |
+
if ngpus > 1:
|
| 120 |
+
trainer_args["gpus"] = ngpus
|
| 121 |
+
trainer_args["accelerator"] = "ddp"
|
| 122 |
+
trainer_args["plugins"] = DDPPlugin(find_unused_parameters=False),
|
| 123 |
+
os.environ["NCCL_NSOCKS_PERTHREAD"]="4"
|
| 124 |
+
os.environ["NCCL_SOCKET_NTHREADS"] = "2"
|
| 125 |
+
elif ngpus ==0:
|
| 126 |
+
trainer_args["gpus"] = 0
|
| 127 |
+
trainer_args["accelerator"] = "ddp_cpu"
|
| 128 |
+
|
| 129 |
+
if args['main'].get("num_nodes", None):
|
| 130 |
+
trainer_args["num_nodes"] = int(args['main'].get("num_nodes"))
|
| 131 |
+
trainer_args["accelerator"] = "ddp"
|
| 132 |
+
raise NotImplementedError()
|
| 133 |
+
# import torch.distributed as dist
|
| 134 |
+
# dist.init_process_group("gloo", rank=int(os.environ.get("NODE_RANK")), world_size=trainer_args["num_nodes"] )
|
| 135 |
+
|
| 136 |
+
try:
|
| 137 |
+
print(trainer_args)
|
| 138 |
+
trainer = pl.Trainer( **trainer_args )
|
| 139 |
+
trainer.fit(pl_model, dataModule)
|
| 140 |
+
res = trainer.test(pl_model, dataModule.test_dataloader(), verbose=True)[0]
|
| 141 |
+
r, __ = InferencePlotter(pl_module=pl_model).pred_and_plot( mode="test")
|
| 142 |
+
res["test_pearson-r"] = r
|
| 143 |
+
print(res)
|
| 144 |
+
finally:
|
| 145 |
+
if args['main'].get("num_nodes", None):
|
| 146 |
+
# dist.destroy_process_group()
|
| 147 |
+
pass
|
coprinet/pricePrediction/utils.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import shutil
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
from pricePrediction import config
|
| 6 |
+
|
| 7 |
+
def tryMakedir(dirname, remove=False):
|
| 8 |
+
if os.path.isdir(dirname):
|
| 9 |
+
if remove:
|
| 10 |
+
# print("removing %s"%dirname)
|
| 11 |
+
shutil.rmtree(dirname)
|
| 12 |
+
os.mkdir(dirname)
|
| 13 |
+
else:
|
| 14 |
+
os.mkdir(dirname)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def getBucketRanges(x0=1,x1=11, n_buckets=config.NUM_BUCKETS):
|
| 18 |
+
return np.linspace(x0, x1, num=n_buckets)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def search_buckedId(y, bucket_ranges):
|
| 22 |
+
n_buckets = len(bucket_ranges)
|
| 23 |
+
return min(n_buckets - 1, np.searchsorted(bucket_ranges, y))
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class EncodedDirNamesAndTemplates(object):
|
| 27 |
+
def __init__(self, encodedDir):
|
| 28 |
+
self.DEGREES_FNAME = os.path.join(encodedDir, "graphs_degrees.joblib.pckl")
|
| 29 |
+
self.BUCKETS_FNAME_TEMPLATE = os.path.join(encodedDir, "n_per_bucket.%s.joblib.pckl")
|
| 30 |
+
self.DIR_RAW_DATA_SELECTED= os.path.join(encodedDir, "selected_smiles_price")
|
| 31 |
+
self.SELECTED_DATAPOINTS_TEMPLATE = os.path.join(self.DIR_RAW_DATA_SELECTED, "selected_smiles_price.%s.%s.csv.gz")
|
| 32 |
+
self.DATASET_METADATA_FNAME_TEMPLATE = os.path.join(encodedDir, "%s_metadata.joblib.pckl")
|
requirements.txt
CHANGED
|
@@ -19,3 +19,8 @@ uvicorn[standard]>=0.30.0
|
|
| 19 |
python-multipart>=0.0.9
|
| 20 |
supabase>=2.31.0
|
| 21 |
huggingface_hub>=0.28.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
python-multipart>=0.0.9
|
| 20 |
supabase>=2.31.0
|
| 21 |
huggingface_hub>=0.28.0
|
| 22 |
+
pytorch-lightning==1.9.5
|
| 23 |
+
torch-geometric==2.8.0
|
| 24 |
+
torchmetrics==1.8.1
|
| 25 |
+
more-itertools==10.3.0
|
| 26 |
+
tqdm>=4.66.0
|
tools/online_screening.py
CHANGED
|
@@ -10,7 +10,7 @@ from typing import Any
|
|
| 10 |
|
| 11 |
import numpy as np
|
| 12 |
from pymatgen.core import Structure
|
| 13 |
-
from rdkit import Chem
|
| 14 |
|
| 15 |
from tools.adsorption import predict_benzene, predict_toluene
|
| 16 |
from tools.descriptors import compute_scm_eigenvalues
|
|
@@ -19,12 +19,7 @@ from tools.toxicity import predict_toxicity
|
|
| 19 |
|
| 20 |
|
| 21 |
ROOT = Path(__file__).resolve().parent.parent
|
| 22 |
-
|
| 23 |
-
DOC_ROOT = ALL_DES_ROOT.parent
|
| 24 |
-
SA_DIR = ALL_DES_ROOT / "0629_revise" / "Screening" / "筛选顺序" / "筛选顺序" / "2 配体可合成性"
|
| 25 |
-
PRICE_ROOT = DOC_ROOT / "price_filter"
|
| 26 |
-
COPRINET_ROOT = PRICE_ROOT / "CoPriNet-main"
|
| 27 |
-
PYG201 = PRICE_ROOT / ".deps" / "pyg201_nodeps"
|
| 28 |
|
| 29 |
ACTIVE_TOOLS = [
|
| 30 |
"parse_cif",
|
|
@@ -150,7 +145,7 @@ def check_metals_tool(context: dict[str, Any], cif_path: str) -> dict[str, Any]:
|
|
| 150 |
|
| 151 |
@lru_cache(maxsize=1)
|
| 152 |
def _sa_module():
|
| 153 |
-
path =
|
| 154 |
spec = importlib.util.spec_from_file_location("mofscreen_sascorer", path)
|
| 155 |
if spec is None or spec.loader is None:
|
| 156 |
raise RuntimeError(f"cannot load sascorer: {path}")
|
|
@@ -210,8 +205,6 @@ def predict_aquatic_toxicity_tool(context: dict[str, Any]) -> dict[str, Any]:
|
|
| 210 |
|
| 211 |
@lru_cache(maxsize=1)
|
| 212 |
def _price_predictor():
|
| 213 |
-
if str(PYG201) not in sys.path:
|
| 214 |
-
sys.path.insert(0, str(PYG201))
|
| 215 |
if str(COPRINET_ROOT) not in sys.path:
|
| 216 |
sys.path.insert(0, str(COPRINET_ROOT))
|
| 217 |
|
|
|
|
| 10 |
|
| 11 |
import numpy as np
|
| 12 |
from pymatgen.core import Structure
|
| 13 |
+
from rdkit import Chem, RDConfig
|
| 14 |
|
| 15 |
from tools.adsorption import predict_benzene, predict_toluene
|
| 16 |
from tools.descriptors import compute_scm_eigenvalues
|
|
|
|
| 19 |
|
| 20 |
|
| 21 |
ROOT = Path(__file__).resolve().parent.parent
|
| 22 |
+
COPRINET_ROOT = ROOT / "coprinet"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
ACTIVE_TOOLS = [
|
| 25 |
"parse_cif",
|
|
|
|
| 145 |
|
| 146 |
@lru_cache(maxsize=1)
|
| 147 |
def _sa_module():
|
| 148 |
+
path = Path(RDConfig.RDContribDir) / "SA_Score" / "sascorer.py"
|
| 149 |
spec = importlib.util.spec_from_file_location("mofscreen_sascorer", path)
|
| 150 |
if spec is None or spec.loader is None:
|
| 151 |
raise RuntimeError(f"cannot load sascorer: {path}")
|
|
|
|
| 205 |
|
| 206 |
@lru_cache(maxsize=1)
|
| 207 |
def _price_predictor():
|
|
|
|
|
|
|
| 208 |
if str(COPRINET_ROOT) not in sys.path:
|
| 209 |
sys.path.insert(0, str(COPRINET_ROOT))
|
| 210 |
|