我正在看...... 分子自动编码器允许我们对化合物https://arxiv.org/pdf/1610.02415.pdf
进行插值和基于梯度的优化本文采用输入Smiles字符串(分子的文本表示),然后使用变分编码器将其映射到2D潜在空间。
示例微笑己烷-3-醇“CCCC(O)CC”的字符串
在论文中,他们将短字符串填充为带有空格的120个字符。
该论文使用一堆1D卷积网络将字符串编码为微笑字符串的潜在表示
然后使用3门控复现单位GRU然后将潜在空间中的位置映射回微笑字符串。
我在理解本文时遇到的问题是确定输入和输出结构是什么样的。
本文对输入和输出结构有点模糊。从使用1D转换网我怀疑输入是类似于
的矢量化表示'C' = 1
'O' = 2
'(' = 3
')' =4
' ' = 0 #for padding
#so the hexan-3-ol smiles above would be
[1,1,1,1,3,2,4,1,1,0...padding to fixed length]
关于输出,论文说
RNN解码器的最后一层定义了SMILES字符串中每个位置的所有可能字符的概率分布
因此,对于纸张中使用的最大微笑长度为120,可能有35个微笑字符,这意味着输出是[120x35]数组吗?
向前推进该逻辑它是否表明输入是扁平的[120 * 35]阵列 - 牢记其自动编码器。
我的问题是1dConv,它使用的最大长度为9,如果它是一个扁平的[120 * 35]数组,则不足以覆盖序列中的下一个原子
感谢您的帮助......
答案 0 :(得分:2)
SMILES的定义比您预期的更复杂,因为它是图形的线性表示。
https://en.wikipedia.org/wiki/Simplified_molecular-input_line-entry_system
简而言之,一个字母表示一个原子,如C =碳,O =氧。该图可以用parens分支,即C(C)C将形成“Y”结构。最后,可以使用表示为数字的闭包创建循环。即“C1CCC1”形成一个正方形(即字母1与另一个字母1相连)。
请注意,此描述并不完整,但应该是一个良好的基础。
如果字符串是有效的微笑字符串,只需将其添加到另一个有效的微笑字符串中,通常会生成另一个有效的字符串。即“C1CC1”+“C1CC1”=> “C1CC1C1CC1”有效。
通常,on可以提取微笑字符串的线性部分并将其“嵌入”另一个字符串中,并形成一个有效的微笑字符串。
我相信自动编码器正在学习,是如何进行这些转换的。替换上述示例中的卤化物(氯,溴,碘)的愚蠢示例可能是:
C1CCC1Cl
C1CCC1Br
C1CCC1I
自动编码器学习常量部分和可变部分 - 但是在线性字符串空间中。现在这并不完美,如果你在论文中注意到,当探索连续可微的空间时,他们需要找到最接近的有效微笑字符串。
如果你想探索微笑字符串,本文中使用的所有字符串都是使用rdkit生成的:
,在完全披露中,我帮助维护。希望这会有所帮助。
答案 1 :(得分:1)
您可以在此处找到源代码:
https://github.com/maxhodak/keras-molecules
我一直在玩它,输入和输出结构是MxN矩阵,M是SMILES字符串的最大长度(在这种情况下是120),N是字符集的大小。除了位置M_i处的字符与字符N_j匹配的位置之外,每行M是零的向量。要将输出矩阵解码为SMILE,您可以逐行进行匹配并匹配字符集中的字符位置。
此编码的一个问题是它占用了大量内存。使用keras图像迭代器方法,您可以执行以下操作:
首先将所有微笑编码为一个稀疏的' format是集合中每个微笑的字符集位置列表。
现在,您在所有SMILES(字符集)上都有一个已定义的字符集,每个SMILE现在都是一个数字列表,表示字符集中每个字符的位置。然后,您可以开始使用迭代器在使用fit_generator函数训练keras模型时动态执行此操作。
import numpy as np
import threading
import collections
class SmilesIterator(object):
def __init__(self, X, charset, max_length, batch_size=256, shuffle=False, seed=None):
self.X = X
self.charset = charset
self.max_length = max_length
self.N = len(X)
self.batch_size = batch_size
self.shuffle = shuffle
self.batch_index = 0
self.total_batches_seen = 0
self.lock = threading.Lock()
self.index_generator = self._flow_index(len(X), batch_size, shuffle, seed)
def reset(self):
self.batch_index = 0
def __iter__(self):
return self
def _flow_index(self, N, batch_size, shuffle=False, seed=None):
self.reset()
while True:
if self.batch_index == 0:
index_array = np.arange(N)
if shuffle:
if seed is not None:
np.random.seed(seed + total_batches_seen)
index_array = np.random.permutation(N)
current_index = (self.batch_index * batch_size) % N
if N >= current_index + batch_size:
current_batch_size = batch_size
self.batch_index += 1
else:
current_batch_size = N - current_index
self.batch_index = 0
self.total_batches_seen += 1
yield(index_array[current_index: current_index + current_batch_size],
current_index, current_batch_size)
def next(self):
with self.lock:
index_array, current_index, current_batch_size = next(self.index_generator)
#one-hot encoding is not under lock and can be done in parallel
#reserve room for the one-hot encoded
#batch, max_length, charset_length
batch_x = np.zeros(tuple([current_batch_size, self.max_length, len(self.charset)]))
for i, j in enumerate(index_array):
x = self._one_hot(self.X[j])
batch_x[i] = x
return (batch_x, batch_x) #fit_generator returns input and target
def _one_hot(self, sparse_smile):
ss = []
counter = 0
for s in sparse_smile:
cur = [0] * len(self.charset)
cur[s] = 1
ss.append(cur)
counter += 1
#handle end of line, make sure space ' ' is first in the charset
for i in range(counter, len(self.charset)):
cur = [0] * len(self.charset)
cur[0] = 1
ss.append(cur)
ss = np.array(ss)
return(ss)