torch-molecule 0.1.0.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- torch_molecule/__init__.py +70 -0
- torch_molecule/base/__init__.py +8 -0
- torch_molecule/base/base.py +396 -0
- torch_molecule/base/encoder.py +44 -0
- torch_molecule/base/generator.py +24 -0
- torch_molecule/base/old_encoder.py +322 -0
- torch_molecule/base/old_predictor.py +904 -0
- torch_molecule/base/old_predictor2.py +436 -0
- torch_molecule/base/predictor.py +112 -0
- torch_molecule/encoder/attrmask/__init__.py +3 -0
- torch_molecule/encoder/attrmask/model.py +113 -0
- torch_molecule/encoder/attrmask/modeling_attrmask.py +304 -0
- torch_molecule/encoder/constant.py +13 -0
- torch_molecule/encoder/contextpred/__init__.py +3 -0
- torch_molecule/encoder/contextpred/model.py +207 -0
- torch_molecule/encoder/contextpred/modeling_contextpred.py +299 -0
- torch_molecule/encoder/contextpred/utils.py +206 -0
- torch_molecule/encoder/edgepred/__init__.py +3 -0
- torch_molecule/encoder/edgepred/model.py +104 -0
- torch_molecule/encoder/edgepred/modeling_edgepred.py +285 -0
- torch_molecule/encoder/graphmae/__init__.py +3 -0
- torch_molecule/encoder/graphmae/dataloader.py +206 -0
- torch_molecule/encoder/graphmae/model.py +137 -0
- torch_molecule/encoder/graphmae/modeling_graphmae.py +323 -0
- torch_molecule/encoder/infograph/__init__.py +3 -0
- torch_molecule/encoder/infograph/loss_function.py +128 -0
- torch_molecule/encoder/infograph/model.py +92 -0
- torch_molecule/encoder/infograph/modeling_infograph.py +301 -0
- torch_molecule/encoder/infograph/module.py +31 -0
- torch_molecule/encoder/moama/__init__.py +3 -0
- torch_molecule/encoder/moama/model.py +131 -0
- torch_molecule/encoder/moama/modeling_moama.py +307 -0
- torch_molecule/encoder/moama/utils.py +143 -0
- torch_molecule/encoder/pretrained/__init__.py +3 -0
- torch_molecule/encoder/pretrained/modeling_pretrained.py +274 -0
- torch_molecule/encoder/supervised/__init__.py +3 -0
- torch_molecule/encoder/supervised/model.py +97 -0
- torch_molecule/encoder/supervised/modeling_supervised.py +354 -0
- torch_molecule/generator/digress/__init__.py +3 -0
- torch_molecule/generator/digress/diffusion.py +194 -0
- torch_molecule/generator/digress/modeling_digress.py +503 -0
- torch_molecule/generator/digress/transformer.py +360 -0
- torch_molecule/generator/digress/utils.py +196 -0
- torch_molecule/generator/gdss/__init__.py +3 -0
- torch_molecule/generator/gdss/layers.py +257 -0
- torch_molecule/generator/gdss/model.py +204 -0
- torch_molecule/generator/gdss/modeling_gdss.py +474 -0
- torch_molecule/generator/gdss/sde.py +334 -0
- torch_molecule/generator/gdss/solver.py +227 -0
- torch_molecule/generator/gdss/toremove/ScoreNetworkA.py +167 -0
- torch_molecule/generator/gdss/toremove/ScoreNetworkX.py +85 -0
- torch_molecule/generator/gdss/utils.py +240 -0
- torch_molecule/generator/graph_dit/__init__.py +3 -0
- torch_molecule/generator/graph_dit/diffusion.py +188 -0
- torch_molecule/generator/graph_dit/modeling_graph_dit.py +581 -0
- torch_molecule/generator/graph_dit/transformer.py +207 -0
- torch_molecule/generator/graph_dit/utils.py +220 -0
- torch_molecule/generator/graph_ga/__init__.py +3 -0
- torch_molecule/generator/graph_ga/crossover.py +182 -0
- torch_molecule/generator/graph_ga/modeling_graph_ga.py +310 -0
- torch_molecule/generator/graph_ga/mutate.py +136 -0
- torch_molecule/generator/graph_ga/oracle.py +95 -0
- torch_molecule/generator/jtvae/__init__.py +3 -0
- torch_molecule/generator/jtvae/jtnn/chemutils.py +450 -0
- torch_molecule/generator/jtvae/jtnn/datautils.py +127 -0
- torch_molecule/generator/jtvae/jtnn/jtmpn.py +140 -0
- torch_molecule/generator/jtvae/jtnn/jtnn_dec.py +350 -0
- torch_molecule/generator/jtvae/jtnn/jtnn_enc.py +131 -0
- torch_molecule/generator/jtvae/jtnn/mol_tree.py +111 -0
- torch_molecule/generator/jtvae/jtnn/mpn.py +129 -0
- torch_molecule/generator/jtvae/jtnn/nnutils.py +66 -0
- torch_molecule/generator/jtvae/jtnn/vocab.py +31 -0
- torch_molecule/generator/jtvae/jtnn_vae.py +246 -0
- torch_molecule/generator/jtvae/modeling_jtvae.py +247 -0
- torch_molecule/generator/lstm/__init__.py +3 -0
- torch_molecule/generator/lstm/action_sampler.py +91 -0
- torch_molecule/generator/lstm/lstm.py +59 -0
- torch_molecule/generator/lstm/modeling_lstm.py +263 -0
- torch_molecule/generator/lstm/smiles_char_dict.py +123 -0
- torch_molecule/generator/lstm/utils.py +62 -0
- torch_molecule/generator/molgpt/__init__.py +3 -0
- torch_molecule/generator/molgpt/dataset.py +87 -0
- torch_molecule/generator/molgpt/gpt.py +251 -0
- torch_molecule/generator/molgpt/modeling_molgpt.py +427 -0
- torch_molecule/generator/pretrained/modeling_pretrained.py +1 -0
- torch_molecule/nn/__init__.py +16 -0
- torch_molecule/nn/attention.py +63 -0
- torch_molecule/nn/embedder.py +183 -0
- torch_molecule/nn/gnn.py +631 -0
- torch_molecule/nn/mlp.py +34 -0
- torch_molecule/predictor/bfgnn/__init__.py +3 -0
- torch_molecule/predictor/bfgnn/model.py +116 -0
- torch_molecule/predictor/bfgnn/modeling_bfgnn.py +81 -0
- torch_molecule/predictor/dir/__init__.py +3 -0
- torch_molecule/predictor/dir/model.py +235 -0
- torch_molecule/predictor/dir/modeling_dir.py +161 -0
- torch_molecule/predictor/dir/utils.py +40 -0
- torch_molecule/predictor/gnn/__init__.py +3 -0
- torch_molecule/predictor/gnn/model.py +114 -0
- torch_molecule/predictor/gnn/modeling_gnn.py +706 -0
- torch_molecule/predictor/gnn/old_modeling_gnn.py +707 -0
- torch_molecule/predictor/grea/__init__.py +3 -0
- torch_molecule/predictor/grea/model.py +190 -0
- torch_molecule/predictor/grea/modeling_grea.py +107 -0
- torch_molecule/predictor/grea/old1_features.py +229 -0
- torch_molecule/predictor/grea/old1_modeling_grea.py +493 -0
- torch_molecule/predictor/grea/utils.py +97 -0
- torch_molecule/predictor/grin/__init__.py +3 -0
- torch_molecule/predictor/grin/model.py +116 -0
- torch_molecule/predictor/grin/modeling_grin.py +91 -0
- torch_molecule/predictor/irm/__init__.py +3 -0
- torch_molecule/predictor/irm/model.py +127 -0
- torch_molecule/predictor/irm/modeling_irm.py +179 -0
- torch_molecule/predictor/lstm/__init__.py +3 -0
- torch_molecule/predictor/lstm/model.py +145 -0
- torch_molecule/predictor/lstm/modeling_lstm.py +627 -0
- torch_molecule/predictor/lstm/token_from_smiles.py +85 -0
- torch_molecule/predictor/old_modeling_graph.py +650 -0
- torch_molecule/predictor/rpgnn/__init__.py +3 -0
- torch_molecule/predictor/rpgnn/model.py +185 -0
- torch_molecule/predictor/rpgnn/modeling_rpgnn.py +76 -0
- torch_molecule/predictor/sgir/__init__.py +3 -0
- torch_molecule/predictor/sgir/modeling_sgir.py +260 -0
- torch_molecule/predictor/sgir/strategy.py +266 -0
- torch_molecule/predictor/smiles_transformer/__init__.py +3 -0
- torch_molecule/predictor/smiles_transformer/model.py +424 -0
- torch_molecule/predictor/smiles_transformer/modeling_transformer.py +252 -0
- torch_molecule/predictor/ssr/__init__.py +3 -0
- torch_molecule/predictor/ssr/model.py +283 -0
- torch_molecule/predictor/ssr/modeling_ssr.py +267 -0
- torch_molecule/predictor/ssr/utils.py +185 -0
- torch_molecule/utils/__init__.py +31 -0
- torch_molecule/utils/checker.py +158 -0
- torch_molecule/utils/checkpoint.py +294 -0
- torch_molecule/utils/format.py +140 -0
- torch_molecule/utils/generic/metrics.py +530 -0
- torch_molecule/utils/generic/pseudo_tasks.py +11 -0
- torch_molecule/utils/generic/weights.py +30 -0
- torch_molecule/utils/graph/features.py +178 -0
- torch_molecule/utils/graph/graph_from_smiles.py +134 -0
- torch_molecule/utils/graph/graph_to_smiles.py +312 -0
- torch_molecule/utils/hf.py +232 -0
- torch_molecule/utils/search.py +64 -0
- torch_molecule-0.1.0.dev2.dist-info/METADATA +243 -0
- torch_molecule-0.1.0.dev2.dist-info/RECORD +148 -0
- torch_molecule-0.1.0.dev2.dist-info/WHEEL +5 -0
- torch_molecule-0.1.0.dev2.dist-info/licenses/LICENSE +21 -0
- torch_molecule-0.1.0.dev2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
class SmilesCharDictionary(object):
|
|
2
|
+
"""
|
|
3
|
+
A fixed dictionary for druglike SMILES.
|
|
4
|
+
Enables smile<->token conversion.
|
|
5
|
+
|
|
6
|
+
With a space:0 for padding, Q:1 as the start token and end_of_line \n:2 as the stop token.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
PAD = ' '
|
|
10
|
+
BEGIN = 'Q'
|
|
11
|
+
END = '\n'
|
|
12
|
+
|
|
13
|
+
def __init__(self) -> None:
|
|
14
|
+
self.forbidden_symbols = {'Ag', 'Al', 'Am', 'Ar', 'At', 'Au', 'D', 'E', 'Fe', 'G', 'K', 'L', 'M', 'Ra', 'Re',
|
|
15
|
+
'Rf', 'Rg', 'Rh', 'Ru', 'T', 'U', 'V', 'W', 'Xe',
|
|
16
|
+
'Y', 'Zr', 'a', 'd', 'f', 'g', 'h', 'k', 'm', 'si', 't', 'te', 'u', 'v', 'y'}
|
|
17
|
+
|
|
18
|
+
self.char_idx = {self.PAD: 0, self.BEGIN: 1, self.END: 2, '#': 20, '%': 22, '(': 25, ')': 24, '+': 26, '-': 27,
|
|
19
|
+
'.': 30,
|
|
20
|
+
# '*': 30,
|
|
21
|
+
'*': 47,
|
|
22
|
+
'0': 32, '1': 31, '2': 34, '3': 33, '4': 36, '5': 35, '6': 38, '7': 37, '8': 40,
|
|
23
|
+
'9': 39, '=': 41, 'A': 7, 'B': 11, 'C': 19, 'F': 4, 'H': 6, 'I': 5, 'N': 10,
|
|
24
|
+
'O': 9, 'P': 12, 'S': 13, 'X': 15, 'Y': 14, 'Z': 3, '[': 16, ']': 18,
|
|
25
|
+
'b': 21, 'c': 8, 'n': 17, 'o': 29, 'p': 23, 's': 28,
|
|
26
|
+
"@": 42, "R": 43, '/': 44, "\\": 45, 'E': 46,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
self.idx_char = {v: k for k, v in self.char_idx.items()}
|
|
30
|
+
self.encode_dict = {"Br": 'Y', "Cl": 'X', "Si": 'A', 'Se': 'Z', '@@': 'R', 'se': 'E'}
|
|
31
|
+
self.decode_dict = {v: k for k, v in self.encode_dict.items()}
|
|
32
|
+
|
|
33
|
+
def allowed(self, smiles) -> bool:
|
|
34
|
+
"""
|
|
35
|
+
Determine if smiles string has illegal symbols
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
smiles: SMILES string
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
True if all legal
|
|
42
|
+
"""
|
|
43
|
+
for symbol in self.forbidden_symbols:
|
|
44
|
+
if symbol in smiles:
|
|
45
|
+
# print('Forbidden symbol {:<2} in {}'.format(symbol, smiles))
|
|
46
|
+
return False
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
def encode(self, smiles: str) -> str:
|
|
50
|
+
"""
|
|
51
|
+
Replace multi-char tokens with single tokens in SMILES string.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
smiles: SMILES string
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
sanitized SMILE string with only single-char tokens
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
temp_smiles = smiles
|
|
61
|
+
for symbol, token in self.encode_dict.items():
|
|
62
|
+
temp_smiles = temp_smiles.replace(symbol, token)
|
|
63
|
+
return temp_smiles
|
|
64
|
+
|
|
65
|
+
def decode(self, smiles):
|
|
66
|
+
"""
|
|
67
|
+
Replace special tokens with their multi-character equivalents.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
smiles: SMILES string
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
SMILES string with possibly multi-char tokens
|
|
74
|
+
"""
|
|
75
|
+
temp_smiles = smiles
|
|
76
|
+
for symbol, token in self.decode_dict.items():
|
|
77
|
+
temp_smiles = temp_smiles.replace(symbol, token)
|
|
78
|
+
return temp_smiles
|
|
79
|
+
|
|
80
|
+
def get_char_num(self) -> int:
|
|
81
|
+
"""
|
|
82
|
+
Returns:
|
|
83
|
+
number of characters in the alphabet
|
|
84
|
+
"""
|
|
85
|
+
return len(self.idx_char)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def begin_idx(self) -> int:
|
|
89
|
+
return self.char_idx[self.BEGIN]
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def end_idx(self) -> int:
|
|
93
|
+
return self.char_idx[self.END]
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def pad_idx(self) -> int:
|
|
97
|
+
return self.char_idx[self.PAD]
|
|
98
|
+
|
|
99
|
+
def matrix_to_smiles(self, array):
|
|
100
|
+
"""
|
|
101
|
+
Converts an matrix of indices into their SMILES representations
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
array: torch tensor of indices, one molecule per row
|
|
105
|
+
|
|
106
|
+
Returns: a list of SMILES, without the termination symbol
|
|
107
|
+
"""
|
|
108
|
+
smiles_strings = []
|
|
109
|
+
|
|
110
|
+
for row in array:
|
|
111
|
+
predicted_chars = []
|
|
112
|
+
|
|
113
|
+
for j in row:
|
|
114
|
+
next_char = self.idx_char[j.item()]
|
|
115
|
+
if next_char == self.END:
|
|
116
|
+
break
|
|
117
|
+
predicted_chars.append(next_char)
|
|
118
|
+
|
|
119
|
+
smi = ''.join(predicted_chars)
|
|
120
|
+
smi = self.decode(smi)
|
|
121
|
+
smiles_strings.append(smi)
|
|
122
|
+
|
|
123
|
+
return smiles_strings
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from rdkit import Chem
|
|
2
|
+
from typing import Set, Any, Optional
|
|
3
|
+
import torch
|
|
4
|
+
from .smiles_char_dict import SmilesCharDictionary
|
|
5
|
+
|
|
6
|
+
def canonicalize(smiles: str, include_stereocenters=True) -> Optional[str]:
|
|
7
|
+
"""
|
|
8
|
+
Canonicalize the SMILES strings with RDKit.
|
|
9
|
+
The algorithm is detailed under https://pubs.acs.org/doi/full/10.1021/acs.jcim.5b00543
|
|
10
|
+
Args:
|
|
11
|
+
smiles: SMILES string to canonicalize
|
|
12
|
+
include_stereocenters: whether to keep the stereochemical information in the canonical SMILES string
|
|
13
|
+
Returns:
|
|
14
|
+
Canonicalized SMILES string, None if the molecule is invalid.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
if smiles is None:
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
if len(smiles) > 0:
|
|
21
|
+
mol = Chem.MolFromSmiles(smiles)
|
|
22
|
+
|
|
23
|
+
if mol is not None:
|
|
24
|
+
return Chem.MolToSmiles(mol, isomericSmiles=include_stereocenters)
|
|
25
|
+
else:
|
|
26
|
+
return None
|
|
27
|
+
else:
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
def remove_duplicates(list_with_duplicates):
|
|
31
|
+
"""
|
|
32
|
+
Removes the duplicates and keeps the ordering of the original list.
|
|
33
|
+
For duplicates, the first occurrence is kept and the later occurrences are ignored.
|
|
34
|
+
Args:
|
|
35
|
+
list_with_duplicates: list that possibly contains duplicates
|
|
36
|
+
Returns:
|
|
37
|
+
A list with no duplicates.
|
|
38
|
+
"""
|
|
39
|
+
unique_set: Set[Any] = set()
|
|
40
|
+
unique_list = []
|
|
41
|
+
for element in list_with_duplicates:
|
|
42
|
+
if element not in unique_set:
|
|
43
|
+
unique_set.add(element)
|
|
44
|
+
unique_list.append(element)
|
|
45
|
+
|
|
46
|
+
return unique_list
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def rnn_start_token_vector(batch_size, device='cpu'):
|
|
50
|
+
"""
|
|
51
|
+
Returns a vector of start tokens for LSTM.
|
|
52
|
+
This vector can be used to start sampling a batch of SMILES strings.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
batch_size: how many SMILES will be generated at the same time in LSTM
|
|
56
|
+
device: cpu | cuda
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
a tensor (batch_size x 1) containing the start token
|
|
60
|
+
"""
|
|
61
|
+
sd = SmilesCharDictionary()
|
|
62
|
+
return torch.LongTensor(batch_size, 1).fill_(sd.begin_idx).to(device)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from torch.utils.data import Dataset
|
|
2
|
+
import torch
|
|
3
|
+
|
|
4
|
+
class SmilesDataset(Dataset):
|
|
5
|
+
"""Dataset for SMILES strings for MolGPT training"""
|
|
6
|
+
|
|
7
|
+
def __init__(self, data, regex, max_len, properties=None,
|
|
8
|
+
scaffolds=None, scaffold_maxlen=None):
|
|
9
|
+
"""
|
|
10
|
+
Initialize the dataset.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
data : List[str]
|
|
15
|
+
List of SMILES strings
|
|
16
|
+
regex : str
|
|
17
|
+
Regex for tokenization
|
|
18
|
+
max_len : int
|
|
19
|
+
Maximum sequence length
|
|
20
|
+
properties : Optional[List[List[float]]]
|
|
21
|
+
Property values for conditional generation
|
|
22
|
+
scaffolds : Optional[List[str]]
|
|
23
|
+
Scaffold SMILES for conditional generation
|
|
24
|
+
scaffold_maxlen : Optional[int]
|
|
25
|
+
Maximum length of scaffold SMILES
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# Generate content by decomposing all SMILES strings
|
|
29
|
+
content = set()
|
|
30
|
+
for smile in data:
|
|
31
|
+
tokens = regex.findall(smile.strip())
|
|
32
|
+
content.update(tokens)
|
|
33
|
+
|
|
34
|
+
# Add padding token
|
|
35
|
+
content.add('<')
|
|
36
|
+
|
|
37
|
+
chars = sorted(list(set(content)))
|
|
38
|
+
data_size, vocab_size = len(data), len(chars)
|
|
39
|
+
|
|
40
|
+
self.stoi = {ch: i for i, ch in enumerate(chars)}
|
|
41
|
+
self.itos = {i: ch for i, ch in enumerate(chars)}
|
|
42
|
+
self.max_len = max_len
|
|
43
|
+
self.vocab_size = vocab_size
|
|
44
|
+
self.data = data
|
|
45
|
+
self.properties = properties
|
|
46
|
+
self.scaffolds = scaffolds
|
|
47
|
+
self.scaffold_maxlen = scaffold_maxlen
|
|
48
|
+
self.regex = regex
|
|
49
|
+
|
|
50
|
+
def __len__(self):
|
|
51
|
+
return len(self.data)
|
|
52
|
+
|
|
53
|
+
def __getitem__(self, idx):
|
|
54
|
+
# Get SMILES and property
|
|
55
|
+
smiles = self.data[idx].strip()
|
|
56
|
+
prop = self.properties[idx] if self.properties is not None else [0.0]
|
|
57
|
+
|
|
58
|
+
# Get scaffold if available
|
|
59
|
+
if self.scaffolds is not None:
|
|
60
|
+
scaffold = self.scaffolds[idx].strip()
|
|
61
|
+
else:
|
|
62
|
+
scaffold = ""
|
|
63
|
+
|
|
64
|
+
# Tokenize SMILES
|
|
65
|
+
tokens = self.regex.findall(smiles)
|
|
66
|
+
# Pad with '<' if needed
|
|
67
|
+
tokens += ['<'] * (self.max_len - len(tokens))
|
|
68
|
+
# Convert to indices
|
|
69
|
+
indices = [self.stoi.get(t, 0) for t in tokens]
|
|
70
|
+
|
|
71
|
+
# Create input and target tensors
|
|
72
|
+
x = torch.tensor(indices[:-1], dtype=torch.long)
|
|
73
|
+
y = torch.tensor(indices[1:], dtype=torch.long)
|
|
74
|
+
|
|
75
|
+
# Create property tensor
|
|
76
|
+
prop_tensor = torch.tensor(prop, dtype=torch.float)
|
|
77
|
+
|
|
78
|
+
# Create scaffold tensor if needed
|
|
79
|
+
if self.scaffolds is not None:
|
|
80
|
+
scaffold_tokens = self.regex.findall(scaffold)
|
|
81
|
+
scaffold_tokens += ['<'] * (self.scaffold_maxlen - len(scaffold_tokens))
|
|
82
|
+
scaffold_indices = [self.stoi.get(t, 0) for t in scaffold_tokens]
|
|
83
|
+
scaffold_tensor = torch.tensor(scaffold_indices, dtype=torch.long)
|
|
84
|
+
else:
|
|
85
|
+
scaffold_tensor = torch.tensor([], dtype=torch.long)
|
|
86
|
+
|
|
87
|
+
return x, y, prop_tensor, scaffold_tensor
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GPT model:
|
|
3
|
+
- the initial stem consists of a combination of token encoding and a positional encoding
|
|
4
|
+
- the meat of it is a uniform sequence of Transformer blocks
|
|
5
|
+
- each Transformer is a sequential combination of a 1-hidden-layer MLP block and a self-attention block
|
|
6
|
+
- all blocks feed into a central residual pathway similar to resnets
|
|
7
|
+
- the final decoder is a linear projection into a vanilla Softmax classifier
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import math
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
import torch
|
|
14
|
+
import torch.nn as nn
|
|
15
|
+
from torch.nn import functional as F
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CausalSelfAttention(nn.Module):
|
|
19
|
+
"""
|
|
20
|
+
A vanilla multi-head masked self-attention layer with a projection at the end.
|
|
21
|
+
It is possible to use torch.nn.MultiheadAttention here but I am including an
|
|
22
|
+
explicit implementation here to show that there is nothing too scary here.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, config):
|
|
26
|
+
super().__init__()
|
|
27
|
+
assert config.hidden_size % config.num_head == 0
|
|
28
|
+
# key, query, value projections for all heads
|
|
29
|
+
self.key = nn.Linear(config.hidden_size, config.hidden_size)
|
|
30
|
+
self.query = nn.Linear(config.hidden_size, config.hidden_size)
|
|
31
|
+
self.value = nn.Linear(config.hidden_size, config.hidden_size)
|
|
32
|
+
# regularization
|
|
33
|
+
self.attn_drop = nn.Dropout(config.attn_pdrop)
|
|
34
|
+
self.resid_drop = nn.Dropout(config.resid_pdrop)
|
|
35
|
+
# output projection
|
|
36
|
+
self.proj = nn.Linear(config.hidden_size, config.hidden_size)
|
|
37
|
+
# causal mask to ensure that attention is only applied to the left in the input sequence
|
|
38
|
+
num = int(bool(config.num_task)) + int(config.scaffold_maxlen) #int(config.lstm_layers) # int(config.use_scaffold)
|
|
39
|
+
# num = 1
|
|
40
|
+
self.register_buffer("mask", torch.tril(torch.ones(config.max_len + num, config.max_len + num))
|
|
41
|
+
.view(1, 1, config.max_len + num, config.max_len + num))
|
|
42
|
+
|
|
43
|
+
self.n_head = config.num_head
|
|
44
|
+
|
|
45
|
+
def forward(self, x, layer_past=None):
|
|
46
|
+
B, T, C = x.size()
|
|
47
|
+
|
|
48
|
+
# calculate query, key, values for all heads in batch and move head forward to be the batch dim
|
|
49
|
+
k = self.key(x).view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs)
|
|
50
|
+
q = self.query(x).view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs)
|
|
51
|
+
v = self.value(x).view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs)
|
|
52
|
+
|
|
53
|
+
# causal self-attention; Self-attend: (B, nh, T, hs) x (B, nh, hs, T) -> (B, nh, T, T)
|
|
54
|
+
att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.size(-1)))
|
|
55
|
+
att = att.masked_fill(self.mask[:,:,:T,:T] == 0, float('-inf'))
|
|
56
|
+
att = F.softmax(att, dim=-1)
|
|
57
|
+
attn_save = att
|
|
58
|
+
att = self.attn_drop(att)
|
|
59
|
+
y = att @ v # (B, nh, T, T) x (B, nh, T, hs) -> (B, nh, T, hs)
|
|
60
|
+
y = y.transpose(1, 2).contiguous().view(B, T, C) # re-assemble all head outputs side by side
|
|
61
|
+
|
|
62
|
+
# output projection
|
|
63
|
+
y = self.resid_drop(self.proj(y))
|
|
64
|
+
return y, attn_save
|
|
65
|
+
|
|
66
|
+
class Block(nn.Module):
|
|
67
|
+
""" an unassuming Transformer block """
|
|
68
|
+
|
|
69
|
+
def __init__(self, config):
|
|
70
|
+
super().__init__()
|
|
71
|
+
self.ln1 = nn.LayerNorm(config.hidden_size)
|
|
72
|
+
self.ln2 = nn.LayerNorm(config.hidden_size)
|
|
73
|
+
self.attn = CausalSelfAttention(config)
|
|
74
|
+
self.mlp = nn.Sequential(
|
|
75
|
+
nn.Linear(config.hidden_size, 4 * config.hidden_size),
|
|
76
|
+
nn.GELU(),
|
|
77
|
+
nn.Linear(4 * config.hidden_size, config.hidden_size),
|
|
78
|
+
nn.Dropout(config.resid_pdrop),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def forward(self, x):
|
|
82
|
+
y, attn = self.attn(self.ln1(x))
|
|
83
|
+
x = x + y
|
|
84
|
+
x = x + self.mlp(self.ln2(x))
|
|
85
|
+
return x, attn
|
|
86
|
+
|
|
87
|
+
class GPTConfig:
|
|
88
|
+
""" base GPT config, params common to all GPT versions """
|
|
89
|
+
embd_pdrop = 0.1
|
|
90
|
+
resid_pdrop = 0.1
|
|
91
|
+
attn_pdrop = 0.1
|
|
92
|
+
|
|
93
|
+
def __init__(self, vocab_size, max_len, **kwargs):
|
|
94
|
+
self.vocab_size = vocab_size
|
|
95
|
+
self.max_len = max_len
|
|
96
|
+
for k,v in kwargs.items():
|
|
97
|
+
setattr(self, k, v)
|
|
98
|
+
|
|
99
|
+
class GPT(nn.Module):
|
|
100
|
+
""" the full GPT language model, with a context size of max_len """
|
|
101
|
+
|
|
102
|
+
def __init__(self, vocab_size, max_len, num_task, num_layer, num_head, hidden_size, use_scaffold, scaffold_maxlen, use_lstm, lstm_layers):
|
|
103
|
+
super().__init__()
|
|
104
|
+
config = GPTConfig(vocab_size, max_len, num_task = num_task, num_layer = num_layer, num_head = num_head, hidden_size = hidden_size, use_scaffold = use_scaffold, scaffold_maxlen = scaffold_maxlen, use_lstm = use_lstm, lstm_layers = lstm_layers)
|
|
105
|
+
|
|
106
|
+
# input embedding stem
|
|
107
|
+
self.config = config
|
|
108
|
+
self.tok_emb = nn.Embedding(config.vocab_size, config.hidden_size)
|
|
109
|
+
self.type_emb = nn.Embedding(2, config.hidden_size)
|
|
110
|
+
if config.num_task:
|
|
111
|
+
self.prop_nn = nn.Linear(config.num_task, config.hidden_size)
|
|
112
|
+
|
|
113
|
+
self.pos_emb = nn.Parameter(torch.zeros(1, config.max_len, config.hidden_size))
|
|
114
|
+
self.drop = nn.Dropout(config.embd_pdrop)
|
|
115
|
+
# transformer
|
|
116
|
+
self.blocks = nn.Sequential(*[Block(config) for _ in range(config.num_layer)])
|
|
117
|
+
# decoder head
|
|
118
|
+
self.ln_f = nn.LayerNorm(config.hidden_size)
|
|
119
|
+
self.head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
|
|
120
|
+
|
|
121
|
+
self.max_len = config.max_len
|
|
122
|
+
|
|
123
|
+
if config.use_lstm:
|
|
124
|
+
self.lstm = nn.LSTM(input_size = config.hidden_size, hidden_size = config.hidden_size, num_layers = config.lstm_layers, dropout = 0.3, bidirectional = False)
|
|
125
|
+
|
|
126
|
+
def get_max_len(self):
|
|
127
|
+
return self.max_len
|
|
128
|
+
|
|
129
|
+
def initialize_parameters(self, seed=None):
|
|
130
|
+
if seed is not None:
|
|
131
|
+
torch.manual_seed(seed)
|
|
132
|
+
self.apply(self._init_weights)
|
|
133
|
+
|
|
134
|
+
def _init_weights(self, module):
|
|
135
|
+
if isinstance(module, (nn.Linear, nn.Embedding)):
|
|
136
|
+
module.weight.data.normal_(mean=0.0, std=0.02)
|
|
137
|
+
if isinstance(module, nn.Linear) and module.bias is not None:
|
|
138
|
+
module.bias.data.zero_()
|
|
139
|
+
elif isinstance(module, nn.LayerNorm):
|
|
140
|
+
module.bias.data.zero_()
|
|
141
|
+
module.weight.data.fill_(1.0)
|
|
142
|
+
|
|
143
|
+
def configure_optimizers(self, train_config):
|
|
144
|
+
"""
|
|
145
|
+
This long function is unfortunately doing something very simple and is being very defensive:
|
|
146
|
+
We are separating out all parameters of the model into two buckets: those that will experience
|
|
147
|
+
weight decay for regularization and those that won't (biases, and layernorm/embedding weights).
|
|
148
|
+
We are then returning the PyTorch optimizer object.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
# separate out all parameters to those that will and won't experience regularizing weight decay
|
|
152
|
+
decay = set()
|
|
153
|
+
no_decay = set()
|
|
154
|
+
whitelist_weight_modules = (torch.nn.Linear, torch.nn.LSTM)
|
|
155
|
+
blacklist_weight_modules = (torch.nn.LayerNorm, torch.nn.Embedding)
|
|
156
|
+
for mn, m in self.named_modules():
|
|
157
|
+
for pn, p in m.named_parameters():
|
|
158
|
+
fpn = '%s.%s' % (mn, pn) if mn else pn # full param name
|
|
159
|
+
|
|
160
|
+
if pn.endswith('bias') or ('bias' in pn):
|
|
161
|
+
# all biases will not be decayed
|
|
162
|
+
no_decay.add(fpn)
|
|
163
|
+
elif (pn.endswith('weight') or ('weight' in pn)) and isinstance(m, whitelist_weight_modules):
|
|
164
|
+
# weights of whitelist modules will be weight decayed
|
|
165
|
+
decay.add(fpn)
|
|
166
|
+
elif pn.endswith('weight') and isinstance(m, blacklist_weight_modules):
|
|
167
|
+
# weights of blacklist modules will NOT be weight decayed
|
|
168
|
+
no_decay.add(fpn)
|
|
169
|
+
|
|
170
|
+
# special case the position embedding parameter in the root GPT module as not decayed
|
|
171
|
+
no_decay.add('pos_emb')
|
|
172
|
+
|
|
173
|
+
# validate that we considered every parameter
|
|
174
|
+
param_dict = {pn: p for pn, p in self.named_parameters()}
|
|
175
|
+
inter_params = decay & no_decay
|
|
176
|
+
union_params = decay | no_decay
|
|
177
|
+
assert len(inter_params) == 0, "parameters %s made it into both decay/no_decay sets!" % (str(inter_params), )
|
|
178
|
+
assert len(param_dict.keys() - union_params) == 0, "parameters %s were not separated into either decay/no_decay set!" \
|
|
179
|
+
% (str(param_dict.keys() - union_params), )
|
|
180
|
+
|
|
181
|
+
# create the pytorch optimizer object
|
|
182
|
+
optim_groups = [
|
|
183
|
+
{"params": [param_dict[pn] for pn in sorted(list(decay))], "weight_decay": train_config["weight_decay"]},
|
|
184
|
+
{"params": [param_dict[pn] for pn in sorted(list(no_decay))], "weight_decay": 0.0},
|
|
185
|
+
]
|
|
186
|
+
optimizer = torch.optim.AdamW(optim_groups, lr=train_config["learning_rate"], betas=train_config["betas"])
|
|
187
|
+
return optimizer
|
|
188
|
+
|
|
189
|
+
def compute_loss(self, x, targets, prop = None, scaffold = None):
|
|
190
|
+
logits, attn_maps = self.forward(x, targets, prop, scaffold)
|
|
191
|
+
loss = F.cross_entropy(logits.reshape(-1, logits.size(-1)), targets.view(-1))
|
|
192
|
+
return loss
|
|
193
|
+
|
|
194
|
+
def forward(self, idx, targets=None, prop = None, scaffold = None):
|
|
195
|
+
b, t = idx.size()
|
|
196
|
+
assert t <= self.max_len, "Cannot forward, model max_len is exhausted."
|
|
197
|
+
|
|
198
|
+
if self.config.num_task:
|
|
199
|
+
assert prop.size(-1) == self.config.num_task, "num_task should be equal to last dim of property vector"
|
|
200
|
+
|
|
201
|
+
# forward the GPT model
|
|
202
|
+
token_embeddings = self.tok_emb(idx) # each index maps to a (learnable) vector
|
|
203
|
+
position_embeddings = self.pos_emb[:, :t, :] # each position maps to a (learnable) vector
|
|
204
|
+
type_embeddings = self.type_emb(torch.ones((b,t), dtype = torch.long, device = idx.device))
|
|
205
|
+
x = self.drop(token_embeddings + position_embeddings + type_embeddings)
|
|
206
|
+
|
|
207
|
+
if self.config.num_task:
|
|
208
|
+
type_embd = self.type_emb(torch.zeros((b, 1), dtype = torch.long, device = idx.device))
|
|
209
|
+
if prop.ndim == 2:
|
|
210
|
+
p = self.prop_nn(prop.unsqueeze(1)) # for single property
|
|
211
|
+
else:
|
|
212
|
+
p = self.prop_nn(prop) # for multiproperty
|
|
213
|
+
p += type_embd
|
|
214
|
+
x = torch.cat([p, x], 1)
|
|
215
|
+
|
|
216
|
+
if self.config.use_scaffold:
|
|
217
|
+
type_embd = self.type_emb(torch.zeros((b, 1), dtype = torch.long, device = idx.device))
|
|
218
|
+
|
|
219
|
+
scaffold_embeds = self.tok_emb(scaffold) # .mean(1, keepdim = True)
|
|
220
|
+
if self.config.use_lstm:
|
|
221
|
+
scaffold_embeds = self.lstm(scaffold_embeds.permute(1,0,2))[1][0]
|
|
222
|
+
# scaffold_embeds = scaffold_embeds.reshape(scaffold_embeds.shape[1], scaffold_embeds.shape[0], 2, self.config.hidden_size).mean(2)
|
|
223
|
+
scaffold_embeds = scaffold_embeds.permute(1,0,2) # mean(0, keepdim = True)
|
|
224
|
+
# scaffold_embeds = scaffold_embeds.reshape(self.config.lstm_layers, 1, -1, self.config.hidden_size)[-1].permute(1,0,2)
|
|
225
|
+
# scaffold_embeds = scaffold_embeds.reshape(scaffold_embeds.shape[1], scaffold_embeds.shape[0], self.config.hidden_size)
|
|
226
|
+
scaffold_embeds += type_embd
|
|
227
|
+
x = torch.cat([scaffold_embeds, x], 1)
|
|
228
|
+
|
|
229
|
+
# x = self.blocks(x)
|
|
230
|
+
attn_maps = []
|
|
231
|
+
|
|
232
|
+
for layer in self.blocks:
|
|
233
|
+
x, attn = layer(x)
|
|
234
|
+
attn_maps.append(attn)
|
|
235
|
+
|
|
236
|
+
x = self.ln_f(x)
|
|
237
|
+
logits = self.head(x)
|
|
238
|
+
|
|
239
|
+
# print(logits.shape)
|
|
240
|
+
if self.config.num_task and self.config.use_scaffold:
|
|
241
|
+
num = int(bool(self.config.num_task)) + int(self.config.scaffold_maxlen)
|
|
242
|
+
elif self.config.num_task:
|
|
243
|
+
num = int(bool(self.config.num_task))
|
|
244
|
+
elif self.config.use_scaffold:
|
|
245
|
+
num = int(self.config.scaffold_maxlen)
|
|
246
|
+
else:
|
|
247
|
+
num = 0
|
|
248
|
+
|
|
249
|
+
logits = logits[:, num:, :]
|
|
250
|
+
|
|
251
|
+
return logits, attn_maps # (num_layers, batch_size, num_heads, max_seq_len, max_seq_len)
|