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,140 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.nn as nn
|
|
3
|
+
import torch.nn.functional as F
|
|
4
|
+
from .nnutils import create_var, index_select_ND
|
|
5
|
+
from .chemutils import get_mol
|
|
6
|
+
import rdkit.Chem as Chem
|
|
7
|
+
|
|
8
|
+
ELEM_LIST = ['C', 'N', 'O', 'S', 'F', 'Si', 'P', 'Cl', 'Br', 'Mg', 'Na', 'Ca', 'Fe', 'Al', 'I', 'B', 'K', 'Se', 'Zn', 'H', 'Cu', 'Mn', 'unknown']
|
|
9
|
+
|
|
10
|
+
ATOM_FDIM = len(ELEM_LIST) + 6 + 5 + 1
|
|
11
|
+
BOND_FDIM = 5
|
|
12
|
+
MAX_NB = 15
|
|
13
|
+
|
|
14
|
+
def onek_encoding_unk(x, allowable_set):
|
|
15
|
+
if x not in allowable_set:
|
|
16
|
+
x = allowable_set[-1]
|
|
17
|
+
return list(map(lambda s: x == s, allowable_set))
|
|
18
|
+
|
|
19
|
+
def atom_features(atom):
|
|
20
|
+
return torch.Tensor(onek_encoding_unk(atom.GetSymbol(), ELEM_LIST)
|
|
21
|
+
+ onek_encoding_unk(atom.GetDegree(), [0,1,2,3,4,5])
|
|
22
|
+
+ onek_encoding_unk(atom.GetFormalCharge(), [-1,-2,1,2,0])
|
|
23
|
+
+ [atom.GetIsAromatic()])
|
|
24
|
+
|
|
25
|
+
def bond_features(bond):
|
|
26
|
+
bt = bond.GetBondType()
|
|
27
|
+
return torch.Tensor([bt == Chem.rdchem.BondType.SINGLE, bt == Chem.rdchem.BondType.DOUBLE, bt == Chem.rdchem.BondType.TRIPLE, bt == Chem.rdchem.BondType.AROMATIC, bond.IsInRing()])
|
|
28
|
+
|
|
29
|
+
class JTMPN(nn.Module):
|
|
30
|
+
|
|
31
|
+
def __init__(self, hidden_size, depth):
|
|
32
|
+
super(JTMPN, self).__init__()
|
|
33
|
+
self.hidden_size = hidden_size
|
|
34
|
+
self.depth = depth
|
|
35
|
+
|
|
36
|
+
self.W_i = nn.Linear(ATOM_FDIM + BOND_FDIM, hidden_size, bias=False)
|
|
37
|
+
self.W_h = nn.Linear(hidden_size, hidden_size, bias=False)
|
|
38
|
+
self.W_o = nn.Linear(ATOM_FDIM + hidden_size, hidden_size)
|
|
39
|
+
|
|
40
|
+
def forward(self, fatoms, fbonds, agraph, bgraph, scope, tree_message): #tree_message[0] == vec(0)
|
|
41
|
+
fatoms = create_var(fatoms)
|
|
42
|
+
fbonds = create_var(fbonds)
|
|
43
|
+
agraph = create_var(agraph)
|
|
44
|
+
bgraph = create_var(bgraph)
|
|
45
|
+
|
|
46
|
+
binput = self.W_i(fbonds)
|
|
47
|
+
graph_message = F.relu(binput)
|
|
48
|
+
|
|
49
|
+
for i in range(self.depth - 1):
|
|
50
|
+
message = torch.cat([tree_message,graph_message], dim=0)
|
|
51
|
+
nei_message = index_select_ND(message, 0, bgraph)
|
|
52
|
+
nei_message = nei_message.sum(dim=1) #assuming tree_message[0] == vec(0)
|
|
53
|
+
nei_message = self.W_h(nei_message)
|
|
54
|
+
graph_message = F.relu(binput + nei_message)
|
|
55
|
+
|
|
56
|
+
message = torch.cat([tree_message,graph_message], dim=0)
|
|
57
|
+
nei_message = index_select_ND(message, 0, agraph)
|
|
58
|
+
nei_message = nei_message.sum(dim=1)
|
|
59
|
+
ainput = torch.cat([fatoms, nei_message], dim=1)
|
|
60
|
+
atom_hiddens = F.relu(self.W_o(ainput))
|
|
61
|
+
|
|
62
|
+
mol_vecs = []
|
|
63
|
+
for st,le in scope:
|
|
64
|
+
mol_vec = atom_hiddens.narrow(0, st, le).sum(dim=0) / le
|
|
65
|
+
mol_vecs.append(mol_vec)
|
|
66
|
+
|
|
67
|
+
mol_vecs = torch.stack(mol_vecs, dim=0)
|
|
68
|
+
return mol_vecs
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def tensorize(cand_batch, mess_dict):
|
|
72
|
+
fatoms,fbonds = [],[]
|
|
73
|
+
in_bonds,all_bonds = [],[]
|
|
74
|
+
total_atoms = 0
|
|
75
|
+
total_mess = len(mess_dict) + 1 #must include vec(0) padding
|
|
76
|
+
scope = []
|
|
77
|
+
|
|
78
|
+
for smiles,all_nodes,ctr_node in cand_batch:
|
|
79
|
+
mol = Chem.MolFromSmiles(smiles)
|
|
80
|
+
Chem.Kekulize(mol) #The original jtnn version kekulizes. Need to revisit why it is necessary
|
|
81
|
+
n_atoms = mol.GetNumAtoms()
|
|
82
|
+
ctr_bid = ctr_node.idx
|
|
83
|
+
|
|
84
|
+
for atom in mol.GetAtoms():
|
|
85
|
+
fatoms.append( atom_features(atom) )
|
|
86
|
+
in_bonds.append([])
|
|
87
|
+
|
|
88
|
+
for bond in mol.GetBonds():
|
|
89
|
+
a1 = bond.GetBeginAtom()
|
|
90
|
+
a2 = bond.GetEndAtom()
|
|
91
|
+
x = a1.GetIdx() + total_atoms
|
|
92
|
+
y = a2.GetIdx() + total_atoms
|
|
93
|
+
#Here x_nid,y_nid could be 0
|
|
94
|
+
x_nid,y_nid = a1.GetAtomMapNum(),a2.GetAtomMapNum()
|
|
95
|
+
x_bid = all_nodes[x_nid - 1].idx if x_nid > 0 else -1
|
|
96
|
+
y_bid = all_nodes[y_nid - 1].idx if y_nid > 0 else -1
|
|
97
|
+
|
|
98
|
+
bfeature = bond_features(bond)
|
|
99
|
+
|
|
100
|
+
b = total_mess + len(all_bonds) #bond idx offseted by total_mess
|
|
101
|
+
all_bonds.append((x,y))
|
|
102
|
+
fbonds.append( torch.cat([fatoms[x], bfeature], 0) )
|
|
103
|
+
in_bonds[y].append(b)
|
|
104
|
+
|
|
105
|
+
b = total_mess + len(all_bonds)
|
|
106
|
+
all_bonds.append((y,x))
|
|
107
|
+
fbonds.append( torch.cat([fatoms[y], bfeature], 0) )
|
|
108
|
+
in_bonds[x].append(b)
|
|
109
|
+
|
|
110
|
+
if x_bid >= 0 and y_bid >= 0 and x_bid != y_bid:
|
|
111
|
+
if (x_bid,y_bid) in mess_dict:
|
|
112
|
+
mess_idx = mess_dict[(x_bid,y_bid)]
|
|
113
|
+
in_bonds[y].append(mess_idx)
|
|
114
|
+
if (y_bid,x_bid) in mess_dict:
|
|
115
|
+
mess_idx = mess_dict[(y_bid,x_bid)]
|
|
116
|
+
in_bonds[x].append(mess_idx)
|
|
117
|
+
|
|
118
|
+
scope.append((total_atoms,n_atoms))
|
|
119
|
+
total_atoms += n_atoms
|
|
120
|
+
|
|
121
|
+
total_bonds = len(all_bonds)
|
|
122
|
+
fatoms = torch.stack(fatoms, 0)
|
|
123
|
+
fbonds = torch.stack(fbonds, 0)
|
|
124
|
+
agraph = torch.zeros(total_atoms,MAX_NB).long()
|
|
125
|
+
bgraph = torch.zeros(total_bonds,MAX_NB).long()
|
|
126
|
+
|
|
127
|
+
for a in range(total_atoms):
|
|
128
|
+
for i,b in enumerate(in_bonds[a]):
|
|
129
|
+
if i == MAX_NB: break
|
|
130
|
+
agraph[a,i] = b
|
|
131
|
+
|
|
132
|
+
for b1 in range(total_bonds):
|
|
133
|
+
x,y = all_bonds[b1]
|
|
134
|
+
for i,b2 in enumerate(in_bonds[x]): #b2 is offseted by total_mess
|
|
135
|
+
if b2 < total_mess or all_bonds[b2-total_mess][0] != y:
|
|
136
|
+
if i == MAX_NB: break
|
|
137
|
+
bgraph[b1,i] = b2
|
|
138
|
+
|
|
139
|
+
return (fatoms, fbonds, agraph, bgraph, scope)
|
|
140
|
+
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.nn as nn
|
|
3
|
+
import torch.nn.functional as F
|
|
4
|
+
from .mol_tree import Vocab, MolTree, MolTreeNode
|
|
5
|
+
from .nnutils import create_var, GRU
|
|
6
|
+
from .chemutils import enum_assemble, set_atommap
|
|
7
|
+
import copy
|
|
8
|
+
|
|
9
|
+
MAX_NB = 15
|
|
10
|
+
MAX_DECODE_LEN = 100
|
|
11
|
+
|
|
12
|
+
class JTNNDecoder(nn.Module):
|
|
13
|
+
|
|
14
|
+
def __init__(self, vocab, hidden_size, latent_size, embedding):
|
|
15
|
+
super(JTNNDecoder, self).__init__()
|
|
16
|
+
self.hidden_size = hidden_size
|
|
17
|
+
self.vocab_size = vocab.size()
|
|
18
|
+
self.vocab = vocab
|
|
19
|
+
self.embedding = embedding
|
|
20
|
+
|
|
21
|
+
#GRU Weights
|
|
22
|
+
self.W_z = nn.Linear(2 * hidden_size, hidden_size)
|
|
23
|
+
self.U_r = nn.Linear(hidden_size, hidden_size, bias=False)
|
|
24
|
+
self.W_r = nn.Linear(hidden_size, hidden_size)
|
|
25
|
+
self.W_h = nn.Linear(2 * hidden_size, hidden_size)
|
|
26
|
+
|
|
27
|
+
#Word Prediction Weights
|
|
28
|
+
self.W = nn.Linear(hidden_size + latent_size, hidden_size)
|
|
29
|
+
|
|
30
|
+
#Stop Prediction Weights
|
|
31
|
+
self.U = nn.Linear(hidden_size + latent_size, hidden_size)
|
|
32
|
+
self.U_i = nn.Linear(2 * hidden_size, hidden_size)
|
|
33
|
+
|
|
34
|
+
#Output Weights
|
|
35
|
+
self.W_o = nn.Linear(hidden_size, self.vocab_size)
|
|
36
|
+
self.U_o = nn.Linear(hidden_size, 1)
|
|
37
|
+
|
|
38
|
+
#Loss Functions
|
|
39
|
+
self.pred_loss = nn.CrossEntropyLoss(reduction='sum')
|
|
40
|
+
self.stop_loss = nn.BCEWithLogitsLoss(reduction='sum')
|
|
41
|
+
|
|
42
|
+
def aggregate(self, hiddens, contexts, x_tree_vecs, mode):
|
|
43
|
+
if mode == 'word':
|
|
44
|
+
V, V_o = self.W, self.W_o
|
|
45
|
+
elif mode == 'stop':
|
|
46
|
+
V, V_o = self.U, self.U_o
|
|
47
|
+
else:
|
|
48
|
+
raise ValueError('aggregate mode is wrong')
|
|
49
|
+
|
|
50
|
+
tree_contexts = x_tree_vecs.index_select(0, contexts)
|
|
51
|
+
input_vec = torch.cat([hiddens, tree_contexts], dim=-1)
|
|
52
|
+
# print('input_vec: ', input_vec.shape)
|
|
53
|
+
# print('V W', self.W.weight.shape)
|
|
54
|
+
# print('V U', self.U.weight.shape)
|
|
55
|
+
# print('tree_contexts: ', tree_contexts.shape)
|
|
56
|
+
# print('hiddens: ', hiddens.shape)
|
|
57
|
+
output_vec = F.relu( V(input_vec) )
|
|
58
|
+
return V_o(output_vec)
|
|
59
|
+
|
|
60
|
+
def forward(self, mol_batch, x_tree_vecs):
|
|
61
|
+
pred_hiddens,pred_contexts,pred_targets = [],[],[]
|
|
62
|
+
stop_hiddens,stop_contexts,stop_targets = [],[],[]
|
|
63
|
+
traces = []
|
|
64
|
+
for mol_tree in mol_batch:
|
|
65
|
+
s = []
|
|
66
|
+
dfs(s, mol_tree.nodes[0], -1)
|
|
67
|
+
traces.append(s)
|
|
68
|
+
for node in mol_tree.nodes:
|
|
69
|
+
node.neighbors = []
|
|
70
|
+
|
|
71
|
+
#Predict Root
|
|
72
|
+
batch_size = len(mol_batch)
|
|
73
|
+
pred_hiddens.append(create_var(torch.zeros(len(mol_batch),self.hidden_size)))
|
|
74
|
+
pred_targets.extend([mol_tree.nodes[0].wid for mol_tree in mol_batch])
|
|
75
|
+
pred_contexts.append( create_var( torch.LongTensor(range(batch_size)) ) )
|
|
76
|
+
|
|
77
|
+
max_iter = max([len(tr) for tr in traces])
|
|
78
|
+
padding = create_var(torch.zeros(self.hidden_size), False)
|
|
79
|
+
h = {}
|
|
80
|
+
|
|
81
|
+
for t in range(max_iter):
|
|
82
|
+
prop_list = []
|
|
83
|
+
batch_list = []
|
|
84
|
+
for i,plist in enumerate(traces):
|
|
85
|
+
if t < len(plist):
|
|
86
|
+
prop_list.append(plist[t])
|
|
87
|
+
batch_list.append(i)
|
|
88
|
+
|
|
89
|
+
cur_x = []
|
|
90
|
+
cur_h_nei,cur_o_nei = [],[]
|
|
91
|
+
|
|
92
|
+
for node_x, real_y, _ in prop_list:
|
|
93
|
+
#Neighbors for message passing (target not included)
|
|
94
|
+
cur_nei = [h[(node_y.idx,node_x.idx)] for node_y in node_x.neighbors if node_y.idx != real_y.idx]
|
|
95
|
+
pad_len = MAX_NB - len(cur_nei)
|
|
96
|
+
cur_h_nei.extend(cur_nei)
|
|
97
|
+
cur_h_nei.extend([padding] * pad_len)
|
|
98
|
+
|
|
99
|
+
#Neighbors for stop prediction (all neighbors)
|
|
100
|
+
cur_nei = [h[(node_y.idx,node_x.idx)] for node_y in node_x.neighbors]
|
|
101
|
+
pad_len = MAX_NB - len(cur_nei)
|
|
102
|
+
cur_o_nei.extend(cur_nei)
|
|
103
|
+
cur_o_nei.extend([padding] * pad_len)
|
|
104
|
+
|
|
105
|
+
#Current clique embedding
|
|
106
|
+
cur_x.append(node_x.wid)
|
|
107
|
+
|
|
108
|
+
#Clique embedding
|
|
109
|
+
cur_x = create_var(torch.LongTensor(cur_x))
|
|
110
|
+
cur_x = self.embedding(cur_x)
|
|
111
|
+
|
|
112
|
+
#Message passing
|
|
113
|
+
cur_h_nei = torch.stack(cur_h_nei, dim=0).view(-1,MAX_NB,self.hidden_size)
|
|
114
|
+
new_h = GRU(cur_x, cur_h_nei, self.W_z, self.W_r, self.U_r, self.W_h)
|
|
115
|
+
|
|
116
|
+
#Node Aggregate
|
|
117
|
+
cur_o_nei = torch.stack(cur_o_nei, dim=0).view(-1,MAX_NB,self.hidden_size)
|
|
118
|
+
cur_o = cur_o_nei.sum(dim=1)
|
|
119
|
+
|
|
120
|
+
#Gather targets
|
|
121
|
+
pred_target,pred_list = [],[]
|
|
122
|
+
stop_target = []
|
|
123
|
+
for i,m in enumerate(prop_list):
|
|
124
|
+
node_x,node_y,direction = m
|
|
125
|
+
x,y = node_x.idx,node_y.idx
|
|
126
|
+
h[(x,y)] = new_h[i]
|
|
127
|
+
node_y.neighbors.append(node_x)
|
|
128
|
+
if direction == 1:
|
|
129
|
+
pred_target.append(node_y.wid)
|
|
130
|
+
pred_list.append(i)
|
|
131
|
+
stop_target.append(direction)
|
|
132
|
+
|
|
133
|
+
#Hidden states for stop prediction
|
|
134
|
+
cur_batch = create_var(torch.LongTensor(batch_list))
|
|
135
|
+
stop_hidden = torch.cat([cur_x,cur_o], dim=1)
|
|
136
|
+
stop_hiddens.append( stop_hidden )
|
|
137
|
+
stop_contexts.append( cur_batch )
|
|
138
|
+
stop_targets.extend( stop_target )
|
|
139
|
+
|
|
140
|
+
#Hidden states for clique prediction
|
|
141
|
+
if len(pred_list) > 0:
|
|
142
|
+
batch_list = [batch_list[i] for i in pred_list]
|
|
143
|
+
cur_batch = create_var(torch.LongTensor(batch_list))
|
|
144
|
+
pred_contexts.append( cur_batch )
|
|
145
|
+
|
|
146
|
+
cur_pred = create_var(torch.LongTensor(pred_list))
|
|
147
|
+
pred_hiddens.append( new_h.index_select(0, cur_pred) )
|
|
148
|
+
pred_targets.extend( pred_target )
|
|
149
|
+
|
|
150
|
+
#Last stop at root
|
|
151
|
+
cur_x,cur_o_nei = [],[]
|
|
152
|
+
for mol_tree in mol_batch:
|
|
153
|
+
node_x = mol_tree.nodes[0]
|
|
154
|
+
cur_x.append(node_x.wid)
|
|
155
|
+
cur_nei = [h[(node_y.idx,node_x.idx)] for node_y in node_x.neighbors]
|
|
156
|
+
pad_len = MAX_NB - len(cur_nei)
|
|
157
|
+
cur_o_nei.extend(cur_nei)
|
|
158
|
+
cur_o_nei.extend([padding] * pad_len)
|
|
159
|
+
|
|
160
|
+
cur_x = create_var(torch.LongTensor(cur_x))
|
|
161
|
+
cur_x = self.embedding(cur_x)
|
|
162
|
+
cur_o_nei = torch.stack(cur_o_nei, dim=0).view(-1,MAX_NB,self.hidden_size)
|
|
163
|
+
cur_o = cur_o_nei.sum(dim=1)
|
|
164
|
+
|
|
165
|
+
stop_hidden = torch.cat([cur_x,cur_o], dim=1)
|
|
166
|
+
stop_hiddens.append( stop_hidden )
|
|
167
|
+
stop_contexts.append( create_var( torch.LongTensor(range(batch_size)) ) )
|
|
168
|
+
stop_targets.extend( [0] * len(mol_batch) )
|
|
169
|
+
|
|
170
|
+
#Predict next clique
|
|
171
|
+
pred_contexts = torch.cat(pred_contexts, dim=0)
|
|
172
|
+
pred_hiddens = torch.cat(pred_hiddens, dim=0)
|
|
173
|
+
pred_scores = self.aggregate(pred_hiddens, pred_contexts, x_tree_vecs, 'word')
|
|
174
|
+
pred_targets = create_var(torch.LongTensor(pred_targets))
|
|
175
|
+
|
|
176
|
+
pred_loss = self.pred_loss(pred_scores, pred_targets) / len(mol_batch)
|
|
177
|
+
_,preds = torch.max(pred_scores, dim=1)
|
|
178
|
+
pred_acc = torch.eq(preds, pred_targets).float()
|
|
179
|
+
pred_acc = torch.sum(pred_acc) / pred_targets.nelement()
|
|
180
|
+
|
|
181
|
+
#Predict stop
|
|
182
|
+
stop_contexts = torch.cat(stop_contexts, dim=0)
|
|
183
|
+
stop_hiddens = torch.cat(stop_hiddens, dim=0)
|
|
184
|
+
stop_hiddens = F.relu( self.U_i(stop_hiddens) )
|
|
185
|
+
stop_scores = self.aggregate(stop_hiddens, stop_contexts, x_tree_vecs, 'stop')
|
|
186
|
+
stop_scores = stop_scores.squeeze(-1)
|
|
187
|
+
stop_targets = create_var(torch.Tensor(stop_targets))
|
|
188
|
+
|
|
189
|
+
stop_loss = self.stop_loss(stop_scores, stop_targets) / len(mol_batch)
|
|
190
|
+
stops = torch.ge(stop_scores, 0).float()
|
|
191
|
+
stop_acc = torch.eq(stops, stop_targets).float()
|
|
192
|
+
stop_acc = torch.sum(stop_acc) / stop_targets.nelement()
|
|
193
|
+
|
|
194
|
+
return pred_loss, stop_loss, pred_acc.item(), stop_acc.item()
|
|
195
|
+
|
|
196
|
+
def decode(self, x_tree_vecs, prob_decode):
|
|
197
|
+
assert x_tree_vecs.size(0) == 1
|
|
198
|
+
|
|
199
|
+
stack = []
|
|
200
|
+
init_hiddens = create_var( torch.zeros(1, self.hidden_size) )
|
|
201
|
+
zero_pad = create_var(torch.zeros(1,1,self.hidden_size))
|
|
202
|
+
contexts = create_var( torch.LongTensor(1).zero_() )
|
|
203
|
+
|
|
204
|
+
#Root Prediction
|
|
205
|
+
root_score = self.aggregate(init_hiddens, contexts, x_tree_vecs, 'word')
|
|
206
|
+
_,root_wid = torch.max(root_score, dim=1)
|
|
207
|
+
root_wid = root_wid.item()
|
|
208
|
+
|
|
209
|
+
root = MolTreeNode(self.vocab.get_smiles(root_wid))
|
|
210
|
+
root.wid = root_wid
|
|
211
|
+
root.idx = 0
|
|
212
|
+
stack.append( (root, self.vocab.get_slots(root.wid)) )
|
|
213
|
+
|
|
214
|
+
all_nodes = [root]
|
|
215
|
+
h = {}
|
|
216
|
+
for step in range(MAX_DECODE_LEN):
|
|
217
|
+
node_x,fa_slot = stack[-1]
|
|
218
|
+
cur_h_nei = [ h[(node_y.idx,node_x.idx)] for node_y in node_x.neighbors ]
|
|
219
|
+
if len(cur_h_nei) > 0:
|
|
220
|
+
cur_h_nei = torch.stack(cur_h_nei, dim=0).view(1,-1,self.hidden_size)
|
|
221
|
+
else:
|
|
222
|
+
cur_h_nei = zero_pad
|
|
223
|
+
|
|
224
|
+
cur_x = create_var(torch.LongTensor([node_x.wid]))
|
|
225
|
+
cur_x = self.embedding(cur_x)
|
|
226
|
+
|
|
227
|
+
#Predict stop
|
|
228
|
+
cur_h = cur_h_nei.sum(dim=1)
|
|
229
|
+
stop_hiddens = torch.cat([cur_x,cur_h], dim=1)
|
|
230
|
+
stop_hiddens = F.relu( self.U_i(stop_hiddens) )
|
|
231
|
+
stop_score = self.aggregate(stop_hiddens, contexts, x_tree_vecs, 'stop')
|
|
232
|
+
|
|
233
|
+
if prob_decode:
|
|
234
|
+
backtrack = (torch.bernoulli( torch.sigmoid(stop_score) ).item() == 0)
|
|
235
|
+
else:
|
|
236
|
+
backtrack = (stop_score.item() < 0)
|
|
237
|
+
|
|
238
|
+
if not backtrack: #Forward: Predict next clique
|
|
239
|
+
new_h = GRU(cur_x, cur_h_nei, self.W_z, self.W_r, self.U_r, self.W_h)
|
|
240
|
+
pred_score = self.aggregate(new_h, contexts, x_tree_vecs, 'word')
|
|
241
|
+
|
|
242
|
+
if prob_decode:
|
|
243
|
+
sort_wid = torch.multinomial(F.softmax(pred_score, dim=1).squeeze(), 5)
|
|
244
|
+
else:
|
|
245
|
+
_,sort_wid = torch.sort(pred_score, dim=1, descending=True)
|
|
246
|
+
sort_wid = sort_wid.data.squeeze()
|
|
247
|
+
|
|
248
|
+
next_wid = None
|
|
249
|
+
for wid in sort_wid[:5]:
|
|
250
|
+
slots = self.vocab.get_slots(wid)
|
|
251
|
+
node_y = MolTreeNode(self.vocab.get_smiles(wid))
|
|
252
|
+
if have_slots(fa_slot, slots) and can_assemble(node_x, node_y):
|
|
253
|
+
next_wid = wid
|
|
254
|
+
next_slots = slots
|
|
255
|
+
break
|
|
256
|
+
|
|
257
|
+
if next_wid is None:
|
|
258
|
+
backtrack = True #No more children can be added
|
|
259
|
+
else:
|
|
260
|
+
node_y = MolTreeNode(self.vocab.get_smiles(next_wid))
|
|
261
|
+
node_y.wid = next_wid
|
|
262
|
+
node_y.idx = len(all_nodes)
|
|
263
|
+
node_y.neighbors.append(node_x)
|
|
264
|
+
h[(node_x.idx,node_y.idx)] = new_h[0]
|
|
265
|
+
stack.append( (node_y,next_slots) )
|
|
266
|
+
all_nodes.append(node_y)
|
|
267
|
+
|
|
268
|
+
if backtrack: #Backtrack, use if instead of else
|
|
269
|
+
if len(stack) == 1:
|
|
270
|
+
break #At root, terminate
|
|
271
|
+
|
|
272
|
+
node_fa,_ = stack[-2]
|
|
273
|
+
cur_h_nei = [ h[(node_y.idx,node_x.idx)] for node_y in node_x.neighbors if node_y.idx != node_fa.idx ]
|
|
274
|
+
if len(cur_h_nei) > 0:
|
|
275
|
+
cur_h_nei = torch.stack(cur_h_nei, dim=0).view(1,-1,self.hidden_size)
|
|
276
|
+
else:
|
|
277
|
+
cur_h_nei = zero_pad
|
|
278
|
+
|
|
279
|
+
new_h = GRU(cur_x, cur_h_nei, self.W_z, self.W_r, self.U_r, self.W_h)
|
|
280
|
+
h[(node_x.idx,node_fa.idx)] = new_h[0]
|
|
281
|
+
node_fa.neighbors.append(node_x)
|
|
282
|
+
stack.pop()
|
|
283
|
+
|
|
284
|
+
return root, all_nodes
|
|
285
|
+
|
|
286
|
+
"""
|
|
287
|
+
Helper Functions:
|
|
288
|
+
"""
|
|
289
|
+
def dfs(stack, x, fa_idx):
|
|
290
|
+
for y in x.neighbors:
|
|
291
|
+
if y.idx == fa_idx: continue
|
|
292
|
+
stack.append( (x,y,1) )
|
|
293
|
+
dfs(stack, y, x.idx)
|
|
294
|
+
stack.append( (y,x,0) )
|
|
295
|
+
|
|
296
|
+
def have_slots(fa_slots, ch_slots):
|
|
297
|
+
if len(fa_slots) > 2 and len(ch_slots) > 2:
|
|
298
|
+
return True
|
|
299
|
+
matches = []
|
|
300
|
+
for i,s1 in enumerate(fa_slots):
|
|
301
|
+
a1,c1,h1 = s1
|
|
302
|
+
for j,s2 in enumerate(ch_slots):
|
|
303
|
+
a2,c2,h2 = s2
|
|
304
|
+
if a1 == a2 and c1 == c2 and (a1 != "C" or h1 + h2 >= 4):
|
|
305
|
+
matches.append( (i,j) )
|
|
306
|
+
|
|
307
|
+
if len(matches) == 0: return False
|
|
308
|
+
|
|
309
|
+
fa_match,ch_match = zip(*matches)
|
|
310
|
+
if len(set(fa_match)) == 1 and 1 < len(fa_slots) <= 2: #never remove atom from ring
|
|
311
|
+
fa_slots.pop(fa_match[0])
|
|
312
|
+
if len(set(ch_match)) == 1 and 1 < len(ch_slots) <= 2: #never remove atom from ring
|
|
313
|
+
ch_slots.pop(ch_match[0])
|
|
314
|
+
|
|
315
|
+
return True
|
|
316
|
+
|
|
317
|
+
def can_assemble(node_x, node_y):
|
|
318
|
+
node_x.nid = 1
|
|
319
|
+
node_x.is_leaf = False
|
|
320
|
+
set_atommap(node_x.mol, node_x.nid)
|
|
321
|
+
|
|
322
|
+
neis = node_x.neighbors + [node_y]
|
|
323
|
+
for i,nei in enumerate(neis):
|
|
324
|
+
nei.nid = i + 2
|
|
325
|
+
nei.is_leaf = (len(nei.neighbors) <= 1)
|
|
326
|
+
if nei.is_leaf:
|
|
327
|
+
set_atommap(nei.mol, 0)
|
|
328
|
+
else:
|
|
329
|
+
set_atommap(nei.mol, nei.nid)
|
|
330
|
+
|
|
331
|
+
neighbors = [nei for nei in neis if nei.mol.GetNumAtoms() > 1]
|
|
332
|
+
neighbors = sorted(neighbors, key=lambda x:x.mol.GetNumAtoms(), reverse=True)
|
|
333
|
+
singletons = [nei for nei in neis if nei.mol.GetNumAtoms() == 1]
|
|
334
|
+
neighbors = singletons + neighbors
|
|
335
|
+
cands,aroma_scores = enum_assemble(node_x, neighbors)
|
|
336
|
+
return len(cands) > 0# and sum(aroma_scores) >= 0
|
|
337
|
+
|
|
338
|
+
if __name__ == "__main__":
|
|
339
|
+
smiles = ["O=C1[C@@H]2C=C[C@@H](C=CC2)C1(c1ccccc1)c1ccccc1","O=C([O-])CC[C@@]12CCCC[C@]1(O)OC(=O)CC2", "ON=C1C[C@H]2CC3(C[C@@H](C1)c1ccccc12)OCCO3", "C[C@H]1CC(=O)[C@H]2[C@@]3(O)C(=O)c4cccc(O)c4[C@@H]4O[C@@]43[C@@H](O)C[C@]2(O)C1", 'Cc1cc(NC(=O)CSc2nnc3c4ccccc4n(C)c3n2)ccc1Br', 'CC(C)(C)c1ccc(C(=O)N[C@H]2CCN3CCCc4cccc2c43)cc1', "O=c1c2ccc3c(=O)n(-c4nccs4)c(=O)c4ccc(c(=O)n1-c1nccs1)c2c34", "O=C(N1CCc2c(F)ccc(F)c2C1)C1(O)Cc2ccccc2C1"]
|
|
340
|
+
for s in smiles:
|
|
341
|
+
print(s)
|
|
342
|
+
tree = MolTree(s)
|
|
343
|
+
for i,node in enumerate(tree.nodes):
|
|
344
|
+
node.idx = i
|
|
345
|
+
|
|
346
|
+
stack = []
|
|
347
|
+
dfs(stack, tree.nodes[0], -1)
|
|
348
|
+
for x,y,d in stack:
|
|
349
|
+
print(x.smiles, y.smiles, d)
|
|
350
|
+
print('------------------------------')
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.nn as nn
|
|
3
|
+
import torch.nn.functional as F
|
|
4
|
+
from collections import deque
|
|
5
|
+
from .mol_tree import Vocab, MolTree
|
|
6
|
+
from .nnutils import create_var, index_select_ND
|
|
7
|
+
|
|
8
|
+
class JTNNEncoder(nn.Module):
|
|
9
|
+
|
|
10
|
+
def __init__(self, hidden_size, depth, embedding):
|
|
11
|
+
super(JTNNEncoder, self).__init__()
|
|
12
|
+
self.hidden_size = hidden_size
|
|
13
|
+
self.depth = depth
|
|
14
|
+
|
|
15
|
+
self.embedding = embedding
|
|
16
|
+
self.outputNN = nn.Sequential(
|
|
17
|
+
nn.Linear(2 * hidden_size, hidden_size),
|
|
18
|
+
nn.ReLU()
|
|
19
|
+
)
|
|
20
|
+
self.GRU = GraphGRU(hidden_size, hidden_size, depth=depth)
|
|
21
|
+
|
|
22
|
+
def forward(self, fnode, fmess, node_graph, mess_graph, scope):
|
|
23
|
+
fnode = create_var(fnode)
|
|
24
|
+
fmess = create_var(fmess)
|
|
25
|
+
node_graph = create_var(node_graph)
|
|
26
|
+
mess_graph = create_var(mess_graph)
|
|
27
|
+
messages = create_var(torch.zeros(mess_graph.size(0), self.hidden_size))
|
|
28
|
+
|
|
29
|
+
fnode = self.embedding(fnode)
|
|
30
|
+
fmess = index_select_ND(fnode, 0, fmess)
|
|
31
|
+
messages = self.GRU(messages, fmess, mess_graph)
|
|
32
|
+
|
|
33
|
+
mess_nei = index_select_ND(messages, 0, node_graph)
|
|
34
|
+
node_vecs = torch.cat([fnode, mess_nei.sum(dim=1)], dim=-1)
|
|
35
|
+
node_vecs = self.outputNN(node_vecs)
|
|
36
|
+
|
|
37
|
+
max_len = max([x for _,x in scope])
|
|
38
|
+
batch_vecs = []
|
|
39
|
+
for st,le in scope:
|
|
40
|
+
cur_vecs = node_vecs[st] #Root is the first node
|
|
41
|
+
batch_vecs.append( cur_vecs )
|
|
42
|
+
|
|
43
|
+
tree_vecs = torch.stack(batch_vecs, dim=0)
|
|
44
|
+
return tree_vecs, messages
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def tensorize(tree_batch):
|
|
48
|
+
node_batch = []
|
|
49
|
+
scope = []
|
|
50
|
+
for tree in tree_batch:
|
|
51
|
+
scope.append( (len(node_batch), len(tree.nodes)) )
|
|
52
|
+
node_batch.extend(tree.nodes)
|
|
53
|
+
|
|
54
|
+
return JTNNEncoder.tensorize_nodes(node_batch, scope)
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def tensorize_nodes(node_batch, scope):
|
|
58
|
+
messages,mess_dict = [None],{}
|
|
59
|
+
fnode = []
|
|
60
|
+
for x in node_batch:
|
|
61
|
+
fnode.append(x.wid)
|
|
62
|
+
for y in x.neighbors:
|
|
63
|
+
mess_dict[(x.idx,y.idx)] = len(messages)
|
|
64
|
+
messages.append( (x,y) )
|
|
65
|
+
|
|
66
|
+
node_graph = [[] for i in range(len(node_batch))]
|
|
67
|
+
mess_graph = [[] for i in range(len(messages))]
|
|
68
|
+
fmess = [0] * len(messages)
|
|
69
|
+
|
|
70
|
+
for x,y in messages[1:]:
|
|
71
|
+
mid1 = mess_dict[(x.idx,y.idx)]
|
|
72
|
+
fmess[mid1] = x.idx
|
|
73
|
+
node_graph[y.idx].append(mid1)
|
|
74
|
+
for z in y.neighbors:
|
|
75
|
+
if z.idx == x.idx: continue
|
|
76
|
+
mid2 = mess_dict[(y.idx,z.idx)]
|
|
77
|
+
mess_graph[mid2].append(mid1)
|
|
78
|
+
|
|
79
|
+
max_len = max([len(t) for t in node_graph] + [1])
|
|
80
|
+
for t in node_graph:
|
|
81
|
+
pad_len = max_len - len(t)
|
|
82
|
+
t.extend([0] * pad_len)
|
|
83
|
+
|
|
84
|
+
max_len = max([len(t) for t in mess_graph] + [1])
|
|
85
|
+
for t in mess_graph:
|
|
86
|
+
pad_len = max_len - len(t)
|
|
87
|
+
t.extend([0] * pad_len)
|
|
88
|
+
|
|
89
|
+
mess_graph = torch.LongTensor(mess_graph)
|
|
90
|
+
node_graph = torch.LongTensor(node_graph)
|
|
91
|
+
fmess = torch.LongTensor(fmess)
|
|
92
|
+
fnode = torch.LongTensor(fnode)
|
|
93
|
+
return (fnode, fmess, node_graph, mess_graph, scope), mess_dict
|
|
94
|
+
|
|
95
|
+
class GraphGRU(nn.Module):
|
|
96
|
+
|
|
97
|
+
def __init__(self, input_size, hidden_size, depth):
|
|
98
|
+
super(GraphGRU, self).__init__()
|
|
99
|
+
self.hidden_size = hidden_size
|
|
100
|
+
self.input_size = input_size
|
|
101
|
+
self.depth = depth
|
|
102
|
+
|
|
103
|
+
self.W_z = nn.Linear(input_size + hidden_size, hidden_size)
|
|
104
|
+
self.W_r = nn.Linear(input_size, hidden_size, bias=False)
|
|
105
|
+
self.U_r = nn.Linear(hidden_size, hidden_size)
|
|
106
|
+
self.W_h = nn.Linear(input_size + hidden_size, hidden_size)
|
|
107
|
+
|
|
108
|
+
def forward(self, h, x, mess_graph):
|
|
109
|
+
mask = torch.ones(h.size(0), 1)
|
|
110
|
+
mask[0] = 0 #first vector is padding
|
|
111
|
+
mask = create_var(mask)
|
|
112
|
+
for it in range(self.depth):
|
|
113
|
+
h_nei = index_select_ND(h, 0, mess_graph)
|
|
114
|
+
sum_h = h_nei.sum(dim=1)
|
|
115
|
+
z_input = torch.cat([x, sum_h], dim=1)
|
|
116
|
+
z = F.sigmoid(self.W_z(z_input))
|
|
117
|
+
|
|
118
|
+
r_1 = self.W_r(x).view(-1, 1, self.hidden_size)
|
|
119
|
+
r_2 = self.U_r(h_nei)
|
|
120
|
+
r = F.sigmoid(r_1 + r_2)
|
|
121
|
+
|
|
122
|
+
gated_h = r * h_nei
|
|
123
|
+
sum_gated_h = gated_h.sum(dim=1)
|
|
124
|
+
h_input = torch.cat([x, sum_gated_h], dim=1)
|
|
125
|
+
pre_h = F.tanh(self.W_h(h_input))
|
|
126
|
+
h = (1.0 - z) * sum_h + z * pre_h
|
|
127
|
+
h = h * mask
|
|
128
|
+
|
|
129
|
+
return h
|
|
130
|
+
|
|
131
|
+
|