材料科学中的数据挖掘:晶体图神经网络解读与代码解析

家住魔仙堡 提交于 2020-12-15 19:51:22

©PaperWeekly 原创 · 作者|张玮玮

学校|东北大学硕士

研究方向|情绪识别

论文标题:

Crystal Graph Neural Networks for Data Mining in Materials Science

论文链接:

https://storage.googleapis.com/rimcs_cgnn/cgnn_matsci_May_27_2019.pdf

代码链接:

https://github.com/Tony-Y/cgnn

OQMD 数据库(这个数据库比较大,提供 python API 与晶体结构可视化,可以使用 MySQL 读入):OQMD [2]

晶体材料性质一般通过输入晶胞内的原子数目、原子分子坐标、晶格常数利用密度泛函理论(DFT)计算得出。机器学习方法在加速新材料设计方面正变得越来越流行,其预测材料性能的准确性接近从头算计算,但计算速度要快数量级。最近有人提出用称为晶体图来表示晶体材料 [1],适应于晶体图的卷积神经网络利用平衡键距作为空间信息成功地预测了材料的体积特性。

预备知识

1.1 晶格常数(点阵常数

晶胞三个棱的常数以及这三个棱之间的夹角 6 个参数组成。按晶格常数的不同可以分为七大晶系。在本文中点阵矢量定义为 。

1.2 原子坐标

原子坐标有两种表示形式:分数坐标和笛卡尔坐标。分数坐标是把点阵矢量看成是单位矢量下确定原子位置; 笛卡尔坐标是绝对坐标,是直角坐标系与斜角坐标系的统称,考虑点阵常数的大小。本文利用的是原子的分数坐标 。笛卡尔坐标可以和分数坐标相互转换,在 定义的晶胞中,第 个原子的笛卡尔位置计算为 ,单元格体积 必须大于 0。

1.3 晶体性质

晶体材料的总能量取决于晶体结构 ,并且可以使用 DFT 来近似计算,其中,原子数目为 。近似能量表示为: ,可以通过最小化总能量来得到平衡结构:

表示平衡能的最小值。DFT 计算也给出体积特性,如形成能 、单位单元体积 、带隙 和总磁化强度 。

1.4 数据集

现有的理论材料数据库包含了无机晶体材料的松弛结构及其性能。The Materials Project(MP)数据库拥有 86k 条目,大多与实验观察到的结构有关。The Open Quantum Materials Database(OQMD v1.2)包含 563k 个与实验和假设化合物相关的条目。 

晶体图神经网络

晶体图神经网络(CGNNs)引入了一种比例不变的图协调器(Crystal Graph Coordinator)来构成晶体图,用于在 OQMD 数据集上训练 CGNN 模型。CGNN模型对每一种测试材料的形成能、单元体积、带隙和总磁化强度等体积特性进行了预测,其平均误差小于数据库中相应的误差。将预测的带隙和总磁化率用于金属绝缘体和非磁-磁材料二元分类。 

CGNN 框架如图 1 所示,CG-Gen 是原子序数的函数,会产生平衡结构下的晶体图 , 是有向边的集合 。P-CGNN 是一个预测平衡特性的多任务模型, 。S-CGNN 是在 条件下,推导出晶胞形状 和原子分数坐标 。本文通过预测的晶胞体积来缩放 来获得平衡晶胞 。 

2.1 The Crystal Graph Coordinator

为了训练 CGNN 和 CG-Gen,框架需要一组由平衡结构组成的晶体图。在晶体学中,用泰森多边形找出原子的最近邻居,参考文献 [1] 运用了该方法。CGNN 引入了一种晶体图协调器,它通过聚类来连接原子间的原子间距。由于使用了体积校正因子和缩放距离,晶体图在单位单元的任何均匀变形下都具有不变性。伪代码如下图所示:

源代码如下所示:

def get_neighbors(geom):
    elems = [Element.from_Z(z) for z in geom.atomic_numbers]#通过原子序数来得到元素种类;OQMD数据库共有89种化学元素,原子序数为1到83,89到94 
    radii = np.array([get_radius(e) for e in elems])#原子半径是从PyMatGen库获得
    cutoff = radii[:,np.newaxis] + radii[np.newaxis, :]#将任意两个原子的半径相加,比如有4种原子,则返回一个4*4的矩阵。
    vol_atom = (4 * np.pi / 3) * np.array([r**3 for r in radii]).sum()#将晶胞内的原子体积相加
    factor_vol = (geom.volume / vol_atom)**(1.0/3.0)#体积校正因子通过晶胞体积和原子体积之和得来
    factor = factor_vol * RADIUS_FACTOR
    cutoff *= factor#得到截止半径
    candidates = get_nbrs(geom.cart_coords, geom.lattice.matrix, cutoff)#这部分代码函数在下文提供,主要是将原子的笛卡尔坐标转换为分子坐标并且得到当前原子小于截止半径范围内的候选邻居原子。
    neighbors = []
    for j in range(len(candidates)):
        dists = []
        for nbr in candidates[j]:
            i = nbr[0]#第一维表示原子索引
            d = nbr[1]#第二维表示距离
            r = nbr[2]
            dists.append(d / cutoff[j,i])#距离归一到[0,1],以便进行后续的聚类
        X = np.array(dists).reshape((-1, 1))
        nnc_nbrs = get_nnc_loop(X)#得到最近邻居原子的位置索引
        neighbors.append([candidates[j][i][0] for i in nnc_nbrs])
    return neighbors
def get_nbrs(crystal_xyz, crystal_lat, R_max):
    A = np.transpose(crystal_lat)
    B = np.linalg.inv(A)
    crystal_red = np.matmul(crystal_xyz, np.transpose(B))#笛卡尔坐标系转换为分数坐标系
    crystal_nbrs = pbc.get_shortest_distances(crystal_red, A, R_max,
                                              crdn_only=True)#得到当前原子小于截止半径范围内的候选邻居原子
    return crystal_nbrs

得到的晶体图如下例所示:

2.2 Crystal Graph Neural Networks

作者对模型结构的介绍可以见 Architectures-CGNN(tony-y.github.io)[3]

CGNN 体系结构从原子序数 的嵌入层开始构造,得到初始隐藏状态: 。 

T 个卷积块会产生一系列更高维的隐藏特征: 。卷积块由边神经网络 (EdgeNet)、门控卷积和多层全连接神经网络(MFCNet)组成,如图 3(b) 所示。图 3(c) 所示的 EdgeNet 可以表示为:

本文设计了 EdgeNet 层的三个变体:Original EdgeNet Layer,Fast EdgeNet Layer,Aggregate EdgeNet Layer。

门控卷积表示为 ,MFCNet 各层使用 权值矩阵。graph-level 表示 是由所有的隐藏层状态 ,按照以下步骤进行:在每一步中,隐藏状态使用 Gated Pooling 机制汇合为:

然后,graph-level 状态表示为 被加权平均为:

送入 graph-level MFCNet 输出 ,最后层的输出作为线性回归模型的输入来预测目标值。

class GGNN(nn.Module):
    """
    Gated Graph Neural Networks

    Nodes -> Embedding -> Gated Convolutions -> Graph Pooling -> Full Connections -> Linear Regression
    """
    def __init__(self, n_node_feat, n_hidden_feat, n_graph_feat, n_conv, n_fc,
                 activation, use_batch_norm, node_activation, use_node_batch_norm,
                 edge_activation, use_edge_batch_norm, n_edge_net_feat, n_edge_net_layers,
                 edge_net_activation, use_edge_net_batch_norm, use_fast_edge_network,
                 fast_edge_network_type, use_aggregated_edge_network, edge_net_cardinality,
                 edge_net_width, use_edge_net_shortcut, n_postconv_net_layers,
                 postconv_net_activation, use_postconv_net_batch_norm, conv_type,
                 conv_bias=False, edge_net_bias=False, postconv_net_bias=False,
                 full_pooling=False, gated_pooling=False,
                 use_extension=False):
        super(GGNN, self).__init__()
#设置激活函数,论文共设置了softplus,ssp,elu,relu,selu,celu六种激活函数
        act_fn = get_activation(activation)

        if node_activation is not None:
            node_act_fn = get_activation(node_activation)
        else:
            node_act_fn = None

        if edge_activation is not None:
            edge_act_fn = get_activation(edge_activation)
        else:
            edge_act_fn = None

        postconv_net_act_fn = get_activation(postconv_net_activation)
#EdgeNet层的三个变体
        if n_edge_net_layers < 1:
            edge_nets = [None for i in range(n_conv)]
        else:
            edge_net_act_fn = get_activation(edge_net_activation)
            if use_aggregated_edge_network:
            #AggregatedEdgeNetwork
                edge_nets = [AggregatedEdgeNetwork(n_hidden_feat, n_edge_net_feat,
                             n_edge_net_layers, cardinality=edge_net_cardinality,
                             width=edge_net_width, activation=edge_net_act_fn,
                             use_batch_norm=use_edge_net_batch_norm,
                             bias=edge_net_bias,
                             use_shortcut=use_edge_net_shortcut)
                             for i in range(n_conv)]
            elif use_fast_edge_network:
            #FastEdgeNetwork
                edge_nets = [FastEdgeNetwork(n_hidden_feat, n_edge_net_feat,
                             n_edge_net_layers, activation=edge_net_act_fn,
                             net_type=fast_edge_network_type,
                             use_batch_norm=use_edge_net_batch_norm,
                             bias=edge_net_bias,
                             use_shortcut=use_edge_net_shortcut)
                             for i in range(n_conv)]
            else:
               #Original EdgeNet Layer
                edge_nets = [EdgeNetwork(n_hidden_feat, n_edge_net_feat,
                             n_edge_net_layers, activation=edge_net_act_fn,
                             use_batch_norm=use_edge_net_batch_norm,
                             bias=edge_net_bias,
                             use_shortcut=use_edge_net_shortcut)
                             for i in range(n_conv)]

        if n_postconv_net_layers < 1:
            postconv_nets = [None for i in range(n_conv)]
        else:
            postconv_nets = [PostconvolutionNetwork(n_hidden_feat, n_hidden_feat,
                             n_postconv_net_layers,
                             activation=postconv_net_act_fn,
                             use_batch_norm=use_postconv_net_batch_norm,
                             bias=postconv_net_bias)
                             for i in range(n_conv)]

        self.embedding = NodeEmbedding(n_node_feat, n_hidden_feat)#节点嵌入层,使用线性层
        self.convs = [GatedGraphConvolution(n_hidden_feat, n_hidden_feat,
                      node_activation=node_act_fn,
                      edge_activation=edge_act_fn,
                      use_node_batch_norm=use_node_batch_norm,
                      use_edge_batch_norm=use_edge_batch_norm,
                      bias=conv_bias,
                      conv_type=conv_type,
                      edge_network=edge_nets[i],
                      postconv_network=postconv_nets[i])
                      for i in range(n_conv)]#门卷积层
        self.convs = nn.ModuleList(self.convs)
        if full_pooling:
            n_steps = n_conv
            if gated_pooling:
                self.pre_poolings = [GatedPooling(n_hidden_feat)
                                     for _ in range(n_conv)]
            else:
                self.pre_poolings = [LinearPooling(n_hidden_feat)
                                     for _ in range(n_conv)]
        else:
            n_steps = 1
            self.pre_poolings = [None for _ in range(n_conv-1)]
            if gated_pooling:
                self.pre_poolings.append(GatedPooling(n_hidden_feat))
            else:
                self.pre_poolings.append(LinearPooling(n_hidden_feat))
        self.pre_poolings = nn.ModuleList(self.pre_poolings)
       #门pooling层
        self.pooling = GraphPooling(n_hidden_feat, n_steps, activation=act_fn,
                                    use_batch_norm=use_batch_norm)
       #多层全连接神经网络
        self.fcs = [FullConnection(n_hidden_feat, n_graph_feat,
                    activation=act_fn, use_batch_norm=use_batch_norm)]
        self.fcs += [FullConnection(n_graph_feat, n_graph_feat,
                     activation=act_fn, use_batch_norm=use_batch_norm)
                     for i in range(n_fc-1)]
        self.fcs = nn.ModuleList(self.fcs)
        #线性回归
        self.regression = LinearRegression(n_graph_feat)
        if use_extension:
            self.extension = Extension()
        else:
            self.extension = None

    def forward(self, input):
    #模型顺序如下:Nodes -> Embedding -> Gated Convolutions -> Graph Pooling -> Full Connections -> Linear Regression
        x = self.embedding(input.nodes)
        y = []
        for conv, pre_pooling in zip(self.convs, self.pre_poolings):
            x = conv(x, input.edge_sources, input.edge_targets)
            if pre_pooling is not None:
                y.append(pre_pooling(x, input.graph_indices, input.node_counts))
        x = self.pooling(y)
        for fc in self.fcs:
            x = fc(x)
        x = self.regression(x)
        if self.extension is not None:
            x = self.extension(x, input.node_counts)
        return x


实验结果

研究使用的数据集包括从 OQMD 的 561,888 个不同条目中提取的松弛结构、形成能、晶胞体积、带隙和总磁化率。P-GCNN 模型分别进行了形成能预测、晶胞体积预测、带隙预测,其预测结果如下所示:

测试集中非磁体有 30,299 个,磁体有 25,629 个。非磁体组的总磁化率几乎为零,占检测组总磁化率的 54.2%,而磁性组的均值为 /atom,标准差为 /atom,最大值为 /atom。与带隙数据一样,全题磁化数据在零处非负偏置,但磁数据具有大于 1 的正偏度 1.66 和 4.72 的高峰度,表明具有重尾分布。金属绝缘体和非磁-磁材料分类结果如下:

结果与讨论

对于 OQMD 测试集中的每一种材料,P-CGNN 模型成功地预测了其形成能、单元体积、带隙和总磁化强度,并将其分为金属和绝缘体、非磁体和磁体。重要的是,预测没有使用空间距离键长信息。并且 CGNN 模型提供了与基于 DFT 的材料性质预测的准确性。

当 CG-Gen 从适当的材料数据库中几乎完全学习了晶体图上的合成化学时,就可以实现材料数据挖掘。由于晶体图形序列是由对应的图形转换序列生成的,因此整个图形转换序列集合可以看作是存储了材料合成的完整信息的物质基因组,就像人类基因组是人类核酸序列的完整集合一样。

参考文献

[1] Xie T, Grossman J C. Crystal graph convolutional neural networks for an accurate and interpretable prediction of material properties[J]. Physical review letters, 2018, 120(14): 145301.

[2] http://www.oqmd.org/

[3] https://tony-y.github.io/cgnn/architectures/

更多阅读

#投 稿 通 道#

 让你的论文被更多人看到 

如何才能让更多的优质内容以更短路径到达读者群体,缩短读者寻找优质内容的成本呢?答案就是:你不认识的人。

总有一些你不认识的人,知道你想知道的东西。PaperWeekly 或许可以成为一座桥梁,促使不同背景、不同方向的学者和学术灵感相互碰撞,迸发出更多的可能性。 

PaperWeekly 鼓励高校实验室或个人,在我们的平台上分享各类优质内容,可以是最新论文解读,也可以是学习心得技术干货。我们的目的只有一个,让知识真正流动起来。

???? 来稿标准:

• 稿件确系个人原创作品,来稿需注明作者个人信息(姓名+学校/工作单位+学历/职位+研究方向) 

• 如果文章并非首发,请在投稿时提醒并附上所有已发布链接 

• PaperWeekly 默认每篇文章都是首发,均会添加“原创”标志

???? 投稿邮箱:

• 投稿邮箱:hr@paperweekly.site 

• 所有文章配图,请单独在附件中发送 

• 请留下即时联系方式(微信或手机),以便我们在编辑发布时和作者沟通

????

现在,在「知乎」也能找到我们了

进入知乎首页搜索「PaperWeekly」

点击「关注」订阅我们的专栏吧

关于PaperWeekly

PaperWeekly 是一个推荐、解读、讨论、报道人工智能前沿论文成果的学术平台。如果你研究或从事 AI 领域,欢迎在公众号后台点击「交流群」,小助手将把你带入 PaperWeekly 的交流群里。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!