我的工程实践选题是《针对领域知识的中文知识图谱自动化构建》,我将选取用于知识图谱的分布式表示计算框架OpenKE,代码目录如下图所示:
我们可以看到源代码目录中包括多个文件夹,其中主要为:
base:基础操作代码,包括文件读写,获取样本数,设置工作目录等
benchmarks:一些用于评测算法性能的数据集
config:设置运行所需的参数
examples:将本框架运用于不同数据集上的代码示例
models:本框架中包含的一些算法模型
release:由base打包得到的动态库
res:程序运行得到的一些结果
除此之外我们可以看到在主目录里还有一些文件,包括许可证,框架使用样例,编译文件,说明文件等。可以看出整个工程的组织比较清晰,文件夹命名比较规范。
下面我们来看一段代码:
1 #coding:utf-8 2 import numpy as np 3 import tensorflow as tf 4 from .Model import Model 5 6 if tf.__version__ > '0.12.1': 7 matmul_func = tf.matmul 8 else: 9 matmul_func = tf.batch_matmul 10 11 class TransR(Model): 12 r''' 13 TransR first projects entities from entity space to corresponding relation space 14 and then builds translations between projected entities. 15 ''' 16 def _transfer(self, transfer_matrix, embeddings): 17 return matmul_func(embeddings, transfer_matrix) 18 19 def _calc(self, h, t, r): 20 h = tf.nn.l2_normalize(h, -1) 21 t = tf.nn.l2_normalize(t, -1) 22 r = tf.nn.l2_normalize(r, -1) 23 return abs(h + r - t) 24 25 def embedding_def(self): 26 #Obtaining the initial configuration of the model 27 config = self.get_config() 28 #Defining required parameters of the model, including embeddings of entities and relations, and mapping matrices 29 self.ent_embeddings = tf.get_variable(name = "ent_embeddings", shape = [config.entTotal, config.ent_size], initializer = tf.contrib.layers.xavier_initializer(uniform = False)) 30 self.rel_embeddings = tf.get_variable(name = "rel_embeddings", shape = [config.relTotal, config.rel_size], initializer = tf.contrib.layers.xavier_initializer(uniform = False)) 31 self.transfer_matrix = tf.get_variable(name = "transfer_matrix", shape = [config.relTotal, config.ent_size * config.rel_size], initializer = tf.contrib.layers.xavier_initializer(uniform = False)) 32 self.parameter_lists = {"ent_embeddings":self.ent_embeddings, \ 33 "rel_embeddings":self.rel_embeddings, \ 34 "transfer_matrix":self.transfer_matrix} 35 36 def loss_def(self): 37 #Obtaining the initial configuration of the model 38 config = self.get_config() 39 #To get positive triples and negative triples for training 40 #The shapes of pos_h, pos_t, pos_r are (batch_size, 1) 41 #The shapes of neg_h, neg_t, neg_r are (batch_size, negative_ent + negative_rel) 42 pos_h, pos_t, pos_r = self.get_positive_instance(in_batch = True) 43 neg_h, neg_t, neg_r = self.get_negative_instance(in_batch = True) 44 #Embedding entities and relations of triples, e.g. pos_h_e, pos_t_e and pos_r_e are embeddings for positive triples 45 pos_h_e = tf.nn.embedding_lookup(self.ent_embeddings, pos_h) 46 pos_t_e = tf.nn.embedding_lookup(self.ent_embeddings, pos_t) 47 pos_r_e = tf.nn.embedding_lookup(self.rel_embeddings, pos_r) 48 neg_h_e = tf.nn.embedding_lookup(self.ent_embeddings, neg_h) 49 neg_t_e = tf.nn.embedding_lookup(self.ent_embeddings, neg_t) 50 neg_r_e = tf.nn.embedding_lookup(self.rel_embeddings, neg_r) 51 #Getting the required mapping matrices 52 pos_matrix = tf.reshape(tf.nn.embedding_lookup(self.transfer_matrix, pos_r), [-1, config.ent_size, config.rel_size]) 53 #Calculating score functions for all positive triples and negative triples 54 p_h = self._transfer(pos_matrix, pos_h_e) 55 p_t = self._transfer(pos_matrix, pos_t_e) 56 p_r = pos_r_e 57 if config.negative_rel == 0: 58 n_h = self._transfer(pos_matrix, neg_h_e) 59 n_t = self._transfer(pos_matrix, neg_t_e) 60 n_r = neg_r_e 61 else: 62 neg_matrix = tf.reshape(tf.nn.embedding_lookup(self.transfer_matrix, neg_r), [-1, config.ent_size, config.rel_size]) 63 n_h = self._transfer(neg_matrix, neg_h_e) 64 n_t = self._transfer(neg_matrix, neg_t_e) 65 n_r = neg_r_e 66 #The shape of _p_score is (batch_size, 1, hidden_size) 67 #The shape of _n_score is (batch_size, negative_ent + negative_rel, hidden_size) 68 _p_score = self._calc(p_h, p_t, p_r) 69 _n_score = self._calc(n_h, n_t, n_r) 70 #The shape of p_score is (batch_size, 1, 1) 71 #The shape of n_score is (batch_size, negative_ent + negative_rel, 1) 72 p_score = tf.reduce_sum(_p_score, -1, keep_dims = True) 73 n_score = tf.reduce_sum(_n_score, -1, keep_dims = True) 74 #Calculating loss to get what the framework will optimize 75 self.loss = tf.reduce_mean(tf.maximum(p_score - n_score + config.margin, 0)) 76 77 def predict_def(self): 78 config = self.get_config() 79 predict_h, predict_t, predict_r = self.get_predict_instance() 80 predict_h_e = tf.reshape(tf.nn.embedding_lookup(self.ent_embeddings, predict_h), [1, -1, config.ent_size]) 81 predict_t_e = tf.reshape(tf.nn.embedding_lookup(self.ent_embeddings, predict_t), [1, -1, config.ent_size]) 82 predict_r_e = tf.reshape(tf.nn.embedding_lookup(self.rel_embeddings, predict_r), [1, -1, config.rel_size]) 83 predict_matrix = tf.reshape(tf.nn.embedding_lookup(self.transfer_matrix, predict_r[0]), [1, config.ent_size, config.rel_size]) 84 h_e = tf.reshape(self._transfer(predict_matrix, predict_h_e), [-1, config.rel_size]) 85 t_e = tf.reshape(self._transfer(predict_matrix, predict_t_e), [-1, config.rel_size]) 86 r_e = predict_r_e 87 self.predict = tf.reduce_sum(self._calc(h_e, t_e, r_e), -1, keep_dims = True)
我们可以看到这是一个类的定义,整体比较规整,变量名和函数命名都和实际意义清晰对应,并且还有一定量的英文注释,这有助于世界各地的开发者使用和修改这个代码框架。
从整个代码框架的实现来看,OpenKE项目的代码实现层次清晰,注释明了,代码中命名规范,让阅读者很容易理解其代表的实际含义。除此之外,代码的缩进以及函数间空行是比较规范的,使得代码块之间有清楚的界限。对于一些比较长的语句能够做到合理的换行,不至于在阅读时不能完全显示。
但是代码仍有一些不足之处:①部分代码注释不够详尽,没有说明一些命名较简单的变量名代表的实际含义 ②整个项目缺乏对源代码组织以及代码编写理论的介绍,使得初学者不易理清代码和理论之间的对应关系。
下面我将列举一些Python代码风格规范,资料来源于谷歌开源项目风格指南:
分号
不要在行尾加分号, 也不要用分号将两条命令放在同一行。
行长度
每行不超过80个字符
例外:
- 长的导入模块语句
- 注释里的URL
不要使用反斜杠连接行。
Python会将圆括号,中括号和花括号中的行隐式的连接起来 ,你可以利用这个特点。 如果需要,你可以在表达式外围增加一对额外的圆括号。如果一个文本字符串在一行放不下, 可以使用圆括号来实现隐式行连接。在注释中,如果必要,将长的URL放在一行上。
括号
除非是用于实现行连接,否则不要在返回语句或条件语句中使用括号,不过在元组两边使用括号是可以的。
缩进
用4个空格来缩进代码,绝对不要用tab,也不要tab和空格混用。 对于行连接的情况,你应该要么垂直对齐换行的元素, 或者使用4空格的悬挂式缩进(这时第一行不应该有参数)。
空行
按照标准的排版规范来使用标点两边的空格,括号内不要有空格。不要在逗号、分号、冒号前面加空格,但应该在它们后面加(除了在行尾)。参数列表,、索引或切片的左括号前不应加空格。在二元操作符两边都加上一个空格,比如赋值(=), 比较(==, <, >, !=, <>, <=, >=, in, not in, is, is not), 布尔(and, or, not)。至于算术操作符两边的空格该如何使用, 需要你自己好好判断。当’=’用于指示关键字参数或默认参数值时, 不要在其两侧使用空格。不要用空格来垂直对齐多行间的标记,因为这会成为维护的负担(适用于:, #, =等)。
语句
通常每个语句应该独占一行,不过, 如果测试结果与测试语句在一行放得下,你也可以将它们放在同一行。 如果是if语句, 只有在没有else时才能这样做。特别地, 绝不要对 try/except
这样做, 因为try和except不能放在同一行。
命名
命名约定
- 所谓”内部(Internal)”表示仅模块内可用, 或者, 在类内是保护或私有的.
- 用单下划线(_)开头表示模块变量或函数是protected的(使用from module import *时不会包含).
- 用双下划线(__)开头的实例变量或方法表示类内私有.
- 将相关的类和顶级函数放在同一个模块里. 不像Java, 没必要限制一个类一个模块.
- 对类名使用大写字母开头的单词(如CapWords, 即Pascal风格), 但是模块名应该用小写加下划线的方式(如lower_with_under.py). 尽管已经有很多现存的模块使用类似于CapWords.py这样的命名, 但现在已经不鼓励这样做, 因为如果模块名碰巧和类名一致, 这会让人困扰.
应该避免的名称
- 单字符名称, 除了计数器和迭代器.
- 包/模块名中的连字符(-)
- 双下划线开头并结尾的名称(Python保留, 例如__init__)