GSOC2011 Mocapy
Mocapy++ 是一个用于训练和使用贝叶斯网络的机器学习工具包。它已被用于开发生物分子结构的概率模型。该项目的目的是开发一个 Mocapy++ 的 Python 接口,并将它集成到 Biopython 中。这将允许使用从数据库中提取的数据来训练概率模型。Mocapy++ 与 Biopython 的集成将为蛋白质结构预测、设计和模拟领域提供强大的支持。
简介
发现生物分子的结构是生物学中最大的问题之一。给定一个氨基酸或碱基序列,其三维结构是什么?一种生物分子结构预测方法是构建概率模型。贝叶斯网络是一种概率模型,它由一组变量及其联合概率分布组成,以有向无环图的形式表示。动态贝叶斯网络是一种表示变量序列的贝叶斯网络。这些序列可以是时间序列或符号序列,例如蛋白质序列。方向统计主要关注平面或三维空间中的单位向量观测。样本空间通常是圆或球体。必须有特殊的定向方法来考虑样本空间的结构。图形模型和方向统计的结合使得可以开发生物分子结构的概率模型。通过使用具有方向输出的动态贝叶斯网络,可以构建序列和结构的联合概率分布。生物分子结构可以在几何上自然、连续的空间中表示。Mocapy++ 是一个开源工具包,用于使用支持方向统计的动态贝叶斯网络进行推理和学习。Mocapy++ 非常适合构建生物分子结构的概率模型;它已被用于开发原子级蛋白质和 RNA 结构模型。Mocapy++ 被用于几个高影响力的出版物,并将成为即将发布的分子建模包 Phaistos 的核心。该项目的目的是开发一个非常有用的 Mocapy++ 的 Python 接口,并将该接口集成到 Biopython 项目中。通过 Bio.PDB 模块,Biopython 提供了出色的数据挖掘生物分子结构数据库的功能。集成 Mocapy++ 和 Biopython 将允许使用从数据库中提取的数据来训练概率模型。将 Mocapy++ 集成到 Biopython 将为研究人员创建一个强大的工具包,让他们能够快速实施和测试新想法、尝试各种方法并改进其方法。它将为生物分子结构预测、设计和模拟领域提供强大的支持。
作者 & 指导老师
Michele Silva ([email protected])
指导老师
Thomas Hamelryck
Eric Talevich
项目时间表
工作计划
’'’了解 SEM 和方向统计 ‘’’
- 回顾生物信息学机器学习、马尔可夫链蒙特卡罗和动态贝叶斯网络背后的理论。
- 构建 Mocapy++ 中使用的算法的理论基础,例如使用随机期望最大化 (SEM) 学习贝叶斯网络的参数。
’'’研究 Mocapy++ 的用例 ‘’’
- 阅读几篇论文并尝试使用 Mocapy++ 复制其中描述的部分实验。
- 更好地理解通过蛋白质和核酸的概率模型完成的生物序列分析。
’'’使用 Mocapy++ ‘’’
- 通过探索其源代码并运行其测试用例来了解 Mocapy++ 的内部架构和算法。
- 研究 Mocapy++ 在生物信息学中的其他应用。
’'’设计 Mocapy++ 的 Python 接口 ‘’’
- 探索 Biopython 的源代码以了解其设计和实现。要包含在 Biopython 中的 Mocapy++ 接口必须与 Biopython 中解决问题的方法兼容。
- 根据其数据结构和算法设计 Mocapy++ 的 Python 接口。检查 Mocapy++ 的用例和现有测试用例,以提供接口设计的指导。
’'’实现 Python 绑定 ‘’’
- 使用新的 Mocapy++ 接口在 Python 中实现测试用例。
’'’探索 Mocapy++ 的应用 ‘’’
- 开发涉及使用 Biopython 对生物分子结构数据库进行数据挖掘的示例应用程序。
- 使用 Python-Mocapy++ 制定概率模型。应用模型来解决生物学问题。可以使用动态贝叶斯网络解决的问题示例包括确定一对序列是否在进化上相关、查找与已知进化家族同源的序列以及预测 RNA 二级结构。
项目代码
托管在 gSoC11 Mocapy 分支
项目进度
为 C++ 代码创建 Python 绑定的选项
Swig
已经有人努力使用 Swig 为 Mocapy++ 提供绑定。但是,如果需要性能,Swig 不是最佳选择。Sage 项目旨在为 Mathematica 或 Maple 提供一个开源替代方案。Cython 是与 Sage 共同开发的(虽然它是一个独立的项目),因此它是基于 Sage 的需求的。他们尝试了 Swig,但由于性能问题而放弃了它。根据 Sage 编程指南,“最初的想法是用 C++ 为 SAGE 编写需要快速运行的代码,然后用 SWIG 包装它。这最终停止了,因为结果不够快。首先,用 C++ 编写代码本身就存在开销。其次,SWIG 在 Python 和实际执行工作的代码之间生成几层代码”。这是在 2004 年写的,但似乎情况没有太大变化。我考虑 Swig 的唯一原因是为了将来在 BioJava 和 BioRuby 项目中包含 Mocapy++ 绑定。
Boost Python
Boost Python 非常全面,并且被 Python 社区广泛认可。我会因为它被广泛使用和测试而选择它。我会因为调试困难和构建系统复杂而拒绝它。我认为仅仅为了创建 Python 绑定而添加一个 Boost 依赖项并不值得,但是由于 Mocapy++ 已经依赖于 Boost,使用它就成为一个更有吸引力的选择。根据我个人的经验,Boost Python 非常成熟,并且在使用它时没有任何限制。在性能方面,Cython 仍然优于它。看看 Cython C++ 包装基准测试 并检查 Cython 与 Boost Python 的计时。还有一些 基准测试比较了 Swig 和 Boost Python。
Cython
根据网络上提供的几个基准测试,它比其他创建 C++ 代码 Python 绑定的选项快得多。检查 Cython 和 Boost.Python 之间的简单基准测试。它也非常干净、简单,但功能强大。Python 关于移植扩展模块的文档中提到了 cython:“如果您正在编写一个新的扩展模块,您可能需要考虑使用 Cython。”Cython 现在支持与 numpy 数组的有效交互。它是一种年轻但正在发展的语言,我一定会尝试它,因为它轻量级且速度快。
由于 Boost 受到了良好的支持,并且 Mocapy++ 已经依赖于它,因此我们决定使用 Boost.Python 来进行绑定。
有关更多信息,请参阅 Mocapy++Biopython - 想法集。
绑定原型
原型的源代码在 gSoC11 分支上: http://mocapy.svn.sourceforge.net/viewvc/mocapy/branches/gSoC11/bindings_prototype/
为一些 Mocapy++ 功能提供绑定以及几个示例,以查找可能的实现和性能问题。
过程
- 在 Python 中实现了 hmm_discrete 和 discrete_hmm_with_prior 示例,假设 Mocapy++ 已经提供了接口。
- 为了运行实现的示例,实现了绑定以提供最少的功能集。
Mocapy++ 的接口保持不变,因此测试看起来与 Mocapy/examples 中的测试类似。
在原型中,所有绑定都在单个模块中实现。对于实际实现,我们可以镜像 src 包的结构,为每个包(如 discrete、inference 等)提供独立的绑定。
能够实现运行示例所需的所有功能。在为 MDArray 的向量创建绑定时,无法使用 vector_indexing_suite。必须实现一些运算符(在 MDArray 中),以便将可索引的 C++ 容器导出到 Python。
在 Python 中实现了两个使用离散节点的 Mocapy++ 示例。公开 Mocapy 的数据结构和算法没有问题。Python 版本的性能与原始 Mocapy++ 非常接近。
有关更多详细信息,请查看 Mocapy++ 绑定原型 报告。
绑定实现
核心功能和数据结构的绑定
’’’ 数据结构 ‘’’
Mocapy 使用一个内部数据结构来表示数组:MDArray。为了使用户更轻松地与 Mocapy 的 API 交互,决定提供一个接受 numpy 数组的接口。因此,需要实现 numpy 数组和 MDArray 之间的转换。
通过使用 Boost.Python to_python_converter 完成了从 MDArray 到 python 的转换。我们实现了一个模板方法 convert_MDArray_to_numpy_array,它将任何基本类型的 MDArray 转换为相应的 numpy 数组。为了执行转换,将原始数组的形状和内部数据复制到一个新的 numpy 数组中。
使用 Numpy 数组 API 创建了 numpy 数组。使用现有数据 (PyArray_SimpleNewFromData) 创建新的 PyArrayObject 不会复制数组数据,它只会存储指向它的指针。因此,只有在没有对该对象的引用时才能释放数据。这是通过使用 Capsule 完成的。除了封装数据外,胶囊还存储一个在数组被销毁时使用的析构函数。PyArrayObject 有一个名为“base”的字段,它指向胶囊。
从 Python 到 C++ 的转换,即从 numpy 数组创建 MDArray 稍微复杂一些。Boost.Python 将提供一块内存,新创建的 C++ 对象必须在其中就地构造。有关更多详细信息,请参阅 如何编写 boost.python 转换器 文章。
还实现了基本类型 (double、int…) 的 std::vector 和 Python 列表之间的转换。对于 std::vector 的自定义类型,例如 Node,未执行到 Python 列表的转换。如果以与基本类型相同的方式完成,则会引发类型错误:“TypeError: No to_python (by-value) converter found for C++ type”。当使用 vector_indexing_suite 时,这个问题已经解决。参见 包装 std::vector<AbstractClass*>。使用 vector_indexing_suite 的唯一不便之处是创建新的类型(如 vector_Node),而不是使用标准的 Python 列表。
转换的代码位于 mocapy_data_structures 模块中。
’’’ 核心功能 ‘’’
mocapy Python 包遵循 Mocapy 当前的源代码树。 每个包都创建了一个包含绑定的共享库。 这样可以加快编译速度并简化调试。 此外,如果只创建了一个库,则无法定义包。
每个库都称为 libmocapy_。 例如,libmocapy\_gaussian 提供了对高斯节点和概率分布的绑定。 libmocapy\_data\_structures 被其他库使用,因此必须首先导入。 这是在 Python 侧完成的。 每个 libmocapy\_\* 库都在相应的包中导入。 请参见 [创建包](https://boost.ac.cn/doc/libs/1_46_1/libs/python/doc/tutorial/doc/html/python/techniques.html#python.creating_packages)。
绑定代码可以在 绑定目录 中找到。
目前,正在开发对新创建接口的测试。 在框架包下已经实现了一些测试:mocapy/framework/tests
对剩余 Mocapy++ 功能的绑定
’’’ 数据结构 ‘’’
在实现对剩余 Mocapy++ 功能的绑定时,使用指向 mdarray 的指针和引用的方法出现了问题
- 无法自动转换非 const 引用。 自定义右值转换器仅匹配具有以下签名的函数
void foo(std::vectorconst& array); // 通过 const 引用传递 void foo(std::vectorarray); // 按值传递 </cpp> 有关更多详细信息,请参见 [如何包装将 C++ 容器作为参数的函数?](https://boost.ac.cn/doc/libs/1_46_1/libs/python/doc/v2/faq.html#question2) mdarray 是使用 numpy.array 在 Python 中创建的,该数组使用 [自定义转换器](https://boost.ac.cn/doc/libs/1_42_0/libs/python/doc/v2/faq.html#custom_string) 转换为 c++。 自定义转换器在模块初始化函数的顶部附近注册到全局 Boost.Python 注册表中。 一旦控制流通过注册代码,就会进行来自 Python 的自动转换。 由于这种自动转换,需要为采用指针作为参数的函数创建包装器,并更改采用引用的函数,以获取 const 引用。 由于 Mocapy++ 不是 [const 正确](http://en.wikipedia.org/wiki/Const-correctness),因此需要进行更改才能正确使用 const 引用。 在进行更改时,已经使用了一些 const\_cast。 使用 const\_cast 时,必须注意 [它并不总是安全的](http://stackoverflow.com/questions/357600/is-const-cast-safe)。 还审查了 [调用策略](https://boost.ac.cn/doc/libs/1_46_1/libs/python/doc/tutorial/doc/html/python/functions.html#python.call_policies)。 使用错误的返回值策略时,不会出现编译错误,但代码会在运行时崩溃。 ''' 示例 ''' Mocapy++ 的示例在 Python 中实现,使用公开的 API 和数据类型转换。 <http://mocapy.svn.sourceforge.net/viewvc/mocapy/branches/gSoC11/python/examples/> #### 测试和改进 Mocapy 绑定 在集成到 Biopython 之前,需要进行一些单元测试,以检测可能出现的错误,并确保将来导致功能中断的更改不会被忽视。 对于每个 Python 包,都创建了一个 "tests" 目录,其中包含为每个模块创建的单元测试。 以下是为框架包创建的测试的一个示例:<http://mocapy.svn.sourceforge.net/viewvc/mocapy/branches/gSoC11/python/mocapy/framework/tests/> 在测试代码时,检测到一些问题: - **对象所有权:** 当将 C++ 侧创建的对象传递给采用指针作为参数的方法时,应注意该对象的生存期。 例如,set\_random\_gen 方法采用指向 RandomGen 对象的指针。 以下代码运行良好。 ``` python random_gen = RandomGen() node.set_random_gen(random_gen=random_gen) ``` 但是,如果我们不这样做,而是执行以下操作: ``` python node.set_random_gen(random_gen=RandomGen()) ``` 引用计数尚未增加,因此对象可能会被销毁。 解决此问题的办法是确保 C++ 对象由 auto\_ptr 持有class\_<RandomGen, std::auto\_ptr>("RandomGen") </cpp> 然后创建一个薄包装函数,它采用 auto\_ptr 参数void node\_set\_random\_gen(Node& node, std::auto\_ptrrandom\_gen) { ` node.set_random_gen(random_gen.get());` ` node.release();` } </cpp> 有关更多详细信息,请参见 [如何包装需要获取原始指针所有权的函数?](https://boost.ac.cn/doc/libs/1_46_0/libs/python/doc/v2/faq.html#ownership) 通过 [manage\_new\_object](http://wiki.python.org/moin/boost.python/CallPolicy#manage_new_object) 返回的指针也将由 auto\_ptr 持有,因此所有权的转移会正确地进行。 使用此调用策略时,调用者负责从堆中删除 C++ 对象。 - *' 从 numpy.array 到 float mdarray 的转换*' 如果 numpy 数组是整数数组,则转换会创建一个 mdarray而这传递给了一个需要浮点数 mdarray 的方法。这会产生不正确的结果。从用户的角度来看,解决这个问题的方法是使用浮点型数字来创建数组,或者在创建数组时设置 ndtype 参数: ``` python x = numpy.array([[1, 2, 3, 4, 5, 6]], dtype=numpy.float64) ``` #### 构建和分发 Mocapy 作为包 [Distutils](https://docs.pythonlang.cn/distutils/index.html) 用于分发 Mocapy 的 Python 模块。除了分发 Python 代码之外,还需要构建扩展模块。[使用 boost.python 构建扩展](http://wiki.python.org/moin/boost.python/BuildingExtensions) 描述了使用 distutils 构建扩展的方法。Mocapy 的 setup.py 可在 <http://mocapy.svn.sourceforge.net/viewvc/mocapy/branches/gSoC11/python/setup.py?revision=418&view=markup> 中找到。使用安装脚本,mocapy 的安装分几个步骤完成: - 使用 cmake 构建 mocapy 库(mocapy 文档中描述的常用流程); - 运行 “python setup.py build”,构建扩展模块; - 运行 “python setup.py install”,安装包(通常,安装过程会在你没有进行以上步骤的情况下进行)。 ### 与 Biopython 的集成 #### API 设计 为了将 Mocapy 与 Biopython 一起使用,在 Bio.PDB 中添加了一个新的模块来处理特定于 PDB 的功能。这就是 API 的设计所在。Mocapy 作为 Biopython 的可选依赖项被添加进来。在需要 Mocapy 的函数或模块内部,“import mocapy” 被包裹在一个 try/except 代码块中。如果导入失败,将发出 MissingPythonDependencyError。正在研究要包含在模块中的内容: - 从给定的结构集中提取主链二面角; - 使用这些数据来训练一个类似 TorusDBN 的模型; - 使用 BIC 准则自动决定最佳模型。 #### Barnacle Frellsen J, Moltke I, Thiim M, Mardia KV, Ferkinghoff-Borg J, et al. 2009 [RNA 构象空间的概率模型](http://www.ploscompbiol.org/article/info:doi/10.1371/journal.pcbi.1000406). PLoS Comput Biol 5(6): e1000406. <doi:10.1371/journal.pcbi.1000406>. RNA 3-D 结构预测方法需要一个准确的能量函数和一个构象采样过程。Barnacle 专注于构象采样问题。BARNACLE(使用循环分布和最大似然估计的 RNA 贝叶斯网络模型)的目标是捕获每个角度的边缘分布以及它们之间的局部依赖关系。Barnacle 在一个自然的连续空间中描述了 RNA 结构。它可以纯粹地用作提议分布,也可以用作强制执行现实局部构象的能量项。该模型将动态贝叶斯网络 (DBN) 与方向统计相结合。 DBN 代表了九个连续的二面角,其中七个中心角来自单个核苷酸。每个切片 j(三变量的一列)对应于 RNA 片段中的一个二面角。每个切片中的变量是:角度标识符 Dj、隐藏变量 Hj 和角度变量 Aj。角度标识符跟踪切片所代表的二面角,而角度节点对实际的二面角值进行建模。隐藏节点会在整个序列中所有角度之间产生依赖关系(而不仅仅是在连续切片中的角度之间)。Barnacle 的原始源代码(包含一个用 Python 编写的嵌入式 Mocapy 版本)可以在 <http://sourceforge.net/projects/barnacle-rna> 中找到。Barnacle 的修改版本(修改为与 Mocapy 绑定一起工作)可以在 <https://github.com/mchelem/biopython/tree/master/Bio/PDB/Barnacle> 中找到。这是一个使用示例: ``` python model = Barnacle("ACCU") model.sample() print("log likelihood = ", model.get_log_likelihood()) model.save_structure("structure01.pdb") ``` #### TorusDBN Wouter Boomsma, Kanti V. Mardia, Charles C. Taylor, Jesper Ferkinghoff-Borg, Anders Krogh, and Thomas Hamelryck. 局部蛋白质结构的生成性概率模型。Proc Natl Acad Sci U S A. 2008 年 7 月 1 日;105(26): 8932–8937。<http://www.ncbi.nlm.nih.gov/pmc/articles/PMC2440424/> TorusDBN 的目标是根据生物分子的氨基酸序列预测其 3D 结构。它是一个关于蛋白质在原子细节上局部序列-结构偏好的连续概率模型。蛋白质的主链可以用一系列二面角对 φ 和 ψ 来表示,这些二面角在 [Ramachandran 图](http://en.wikipedia.org/wiki/Ramachandran_plot) 中是众所周知的。两个角度(都取值范围从 -180° 到 180°)定义了环面上的一个点。因此,蛋白质的主链结构可以完全参数化为一系列这样的点。 圆形节点表示随机变量。箭头沿着的矩形框说明了它们之间条件概率分布的性质。一个隐藏节点会发出角度对、氨基酸信息、二级结构标签和顺式/反式信息。TorusDBN 模型最初是作为 backboneDBN 包的一部分实现的,该包可以在 <http://sourceforge.net/projects/phaistos/> 中免费获得。在本项目中实现了一个新的 TorusDBN 模型版本,可以在 <https://github.com/mchelem/biopython/tree/master/Bio/PDB/TorusDBN> 中找到。TorusDBNTrainer 可用于使用给定的训练集训练模型: ``` python trainer = TorusDBNTrainer() trainer.train(training_set) # training_set 是一个文件列表 model = trainer.get_model() ``` 然后可以使用该模型来采样新序列: ``` python model.set_aa("ACDEFGHIK") model.sample() print(model.get_angles()) # 采样的角度。 ``` 创建模型时,可以创建一个指定隐藏节点大小的新 DBN,或者从文件中加载 DBN。 ``` python model = TorusDBNModel() model.create_dbn(hidden_node_size=10) model.save_dbn("test.dbn") ``` ``` python model = TorusDBNModel() model.load_dbn("test.dbn") model.set_aa("ACDEFGHIK") model.sample() print(model.get_angles()) # 采样的角度。 ``` 还可以使用 find_optimal_model 方法选择隐藏节点的最佳大小: ``` python trainer = TorusDBNTrainer() hidden_node_size, IC = trainer.find_optimal_model(training_set) model = trainer.get_model() ``` IC 是 [贝叶斯信息准则](http://en.wikipedia.org/wiki/Bayesian_information_criterion) (BIC) 或 [赤池信息准则](http://en.wikipedia.org/wiki/Akaike_information_criterion) (AIC)(默认值为 BIC。AIC 可以通过设置 use_aic 标志来指定)。有关模型 API 的更多详细信息,请参阅测试文件:<https://github.com/mchelem/biopython/blob/master/Tests/test_TorusDBNTrainer.py> 和 <https://github.com/mchelem/biopython/blob/master/Tests/test_TorusDBNModel.py>。 ### 性能 对在 C++ 和 Python 中实现的测试用例进行了性能测量。测试在一台具有以下规格的计算机上运行:酷睿 2 双核 T7250 2.00GHz、内存双通道 4.0GB(2x2048)667 MHz DDR2 SDRAM、硬盘 200GB 7200RPM。没有明显的性能差异。对于两种实现,消耗大部分 CPU 时间的方法都是相同的: 性能测试使用 [Callgrind](http://valgrind.org/info/tools.html#callgrind) 进行,并使用 [Kcachegrind](http://kcachegrind.sourceforge.net/) 可视化。以下是 Mocapy 提供的示例的平均运行时间(10 次运行): | 测试名称 | C++ (s) | Python (s) | |----------------------------|---------|------------| | hmm_simple | 0.52 | 0.58 | | hmm_discrete | 48.12 | 43.45 | | discrete_hmm_with_prior | 55.95 | 50.09 | | hmm_dirichlet | 340.72 | 353.98 | | hmm_factorial | 0.01 | 0.12 | | hmm_gauss_1d | 53.97 | 63.39 | | hmm_gauss | 16.02 | 16.96 | | hmm_multinomial | 134.64 | 125.83 | | hmm_poisson | 11.00 | 10.60 | | hmm_vonmises | 7.22 | 7.36 | | hmm_torus | 53.79 | 53.65 | | hmm_kent | 61.35 | 61.06 | | hmm_bippo | 40.66 | 41.81 | | infenginehmm | 0.01 | 0.12 | | infenginemm | 0.01 | 0.15 | #### TorusDBN 尽管 PDB 文件以 mocapy 可以理解的格式读取、解析和转换,但最耗时的方法是那些在采样过程中执行数学运算的方法(例如 Chebyshev 和 exp)。 该模型已使用一个训练集进行训练,该训练集包含大约 950 个链,最大同源性为 20%,分辨率低于 1.6 Å,R 因子低于 25%。读取和训练整个数据集大约需要 67 分钟。生成的 DBN 可在 <https://github.com/mchelem/biopython/blob/master/Tests/TorusDBN/pisces_dataset.dbn> 中获得,可以像上面 TorusDBN 部分所述的那样直接加载到模型中。 ### 未来工作 虽然夏天已经结束,但工作仍在继续……我还有很多事情要做: - 测试训练后的模型以检查它们在蛋白质结构预测中的有效性。- 尝试减少动态分配,因为它会导致很多运行时间。- 保证绑定中没有内存泄漏。