在NLP(自然语言处理)中,NER(命名实体识别)是很多应用的关键一步,江湖地位毫无争议,它的研究主体一般包括3大类(实体类、时间类和数字类)和7小类(人名、地名、机构名、时间、日期、货币和百分比)命名实体。贝壳找房作为中国最大的居住服务平台,有丰富的数据和合适的场景,所以该技术也已被广泛应用,比如智能搜索、智能问答、智能推荐等系统中。我们到底是如何用命名实体识别这项技术来践行“技术驱动”和“为行业赋能”的理念呢?这就是今天我们要探讨的话题。

本文主要从以下五个方面来介绍我们的实践:

  1. 项目背景

  2. 相关算法

  3. 训练集的生成过程

  4. 进行过的对比实验

  5. 总结与展望

  6. 项目背景


说到“住”,我们的第一反应是“住在哪”,对于智能搜索来说,贝壳找房用户的搜索词中包含大量的和地址相关的query,识别出用户query里的地址实体,了解用户想要“住在哪”,能够为用户找出更符合预期的房源。其次,随着智能客服与语音找房的落地,这两项技术也同样面临着大量包含地址的query,地址实体识别能帮助智能客服准确定位,迅速回做出应。同样的,准确识别出用户语音找房里的地址也能提高用户的使用体验.

  1. 算法概览

命名实体识别任务主要有三类方法,分别是基于规则和字典,基于统计的方法和混合方法。我们选择了基于统计的方法和混合方法进行验证,最终选择了BiLSTM-CRF混合方法。

BiLSTM-CRF 的混合方法,目前已成为实体识别的主流。BiLSTM层负责每个字对应每个label的得分,CRF层能够学习到数据中的序列约束,负责保证句子序列的正确性。整个模型的结构如下图所示:

  • 第一层是word-embedding层。

如果将word看作文本的最小单元,可以将word-embedding理解为一种映射,其过程是:将文本空间中的某个word,通过一定的方法,映射或者说嵌入(embedding)到另一个数值向量空间。其依赖于一个字和id对应的字典,进而可以得到每个字的one-hot向量。利用随机初始化的embedding矩阵将句子中的每个字由one-hot向量映射为低维稠密的字向量(character embedding)。在输入下一层之前,设置dropout以防止过拟合。

  • 第二层是BiLSTM层。

它是由前向的LSTM与后向的LSTM结合而成。比如我们对“海淀区”编码,模型如图所示:

前向的LSTML依次输入“海”,“淀”,“区”得到三个向量{hL0,hL1,hL2}。后向的LSTMR依次输入“区”,“淀”,“海”得到三个向量{hR0,hR1,hR2}。最后将前向和后向的隐向量进行拼接得到{[hL0,hR0],[hL1,hR1], [hL2,hR2]},即{h0,h1,h2}。拼接后的隐向量包含了前向与后向的所有信息,我们依此为每个字进行分类。Tensorflow中提供了双向 rnn 的接口,它就是tf.nn.bidirectionaldynamicrnn(),该函数内部可自动实现序列的反转。在设置dropout后,接入一个线性层,将隐状态向量从高维映射到低维,维度是标注集的标签数,从而得到自动提取的句子特征,接下来将接入一个CRF层来进行序列标注。

  • 第三层是CRF层。

它可以为最后预测的标签添加一些约束来保证预测的标签是合法的。在训练数据训练过程中,这些约束可以通过CRF层自动学习到。

如图所示,当BiLSTM预测错误时,CRF学到的约束可以保证序列的正确性。这些约束可以是: 1.句子中第一个词总是以标签“B-“ 或 “O”开始,而不是“I-”。

2.标签序列“O I-label” is 非法的。实体标签的首个标签应该是 “B-“ ,而非 “I-“, 换句话说,有效的标签序列应该是“O B-label”。

CRF层的参数是一个K * K的矩阵 A, Aij表示的是从第i个标签到第j个标签的转移得分,进而在为一个位置进行标注的时候可以利用此前已经标注过的标签,如果记一个长度等于句子长度的标签序列y=(y1,y2,…,yn),那么模型对于句子x的标签等于y的打分为:

可以看出整个序列的打分等于各个位置的打分之和,而每个位置的打分由两部分得到,一部分是由BiLSTM输出的Pi决定,另一部分则由CRF的转移矩阵A决定。最后进行解码运算来求解最优路径。

  1. 训练集的生成

训练集的生成主要分为以下四个步骤:数据结构化,模板构建,样本抽取和将训练语料处理为模型的输入BIO标注模式。

3.1 数据结构化

数据是一个算法的灵魂,这个算法背后有着强大的数据支撑—贝壳找房在垂直领域私有的数据:楼盘字典,其覆盖类型有省、市、地区、商圈、社区、小区、开发商、地铁站、地铁线、学校、兴趣点等地理位置数据。拿到这些数据后,分别对这些地址实体进行结构化处理,使该11类文件形成统一的数据格式,各自按照城市id,地点id,地点名字,地点别名,经度,纬度和热度共7种属性进行排列。

我们的目的是尽可能的扩充训练集,因此分别为其做相应的别名处理并将别名加入到地址实体中。例如:小区名“东湖湾二期”本身自带别名“东湖湾名苑”,考虑到用户的输入习惯,我们为其加上别名“东湖湾2期”, 合并之后,“东湖湾名苑”,“东湖湾2期”和“东湖湾二期”都是小区类的地址实体;再如地铁线“1号线”,别名处理后,该地铁线的别名格式为“地铁1号线,1号线地铁,1号线地铁站,一号线,地铁一号线,一号线地铁,一号线地铁站”。除此之外,对于一些含地点的学校名,为泛化数据,在生成别名时就去掉其限定地点,例如将“大连市第四中学”处理为“第四中学,第4中学”,而大学类的地名,根据国内大学的命名规则,地名+某某大学的数据(如“北京理工大学”)不再做特殊处理。

3.2 训练集的构造

拿到结构化的数据后,根据用户的常用问法习惯,需要将这些数据生成相应的模板,并依经验根据模板的出现频率为其设置相应的权重。模板构建直接关系到训练集。为方便后续统一处理,我们将以上10类地址实体(开发商除外)和用户可能的细节搜索分别命名:省— PRO, 市—CIT , 地区—DIS, 商圈—BIZ, 社区–COM, 小区—RES, 地铁站—SUB, 地铁线—LIN, 学校—SCH,兴趣点—POI, 房屋具体细节—DET(例如:具体位置几号楼,房屋朝向,房屋结构等), 高频询问—INQ(例如:房屋产权,住房公积金等)以及其他—OTH(例如:住宅,别墅等词)。

就贝壳找房的用户而言,关于地址实体的最高频次的搜索是直接搜索某某小区,因此我们将其模板设置为RES:50, 表示用户直接搜索小区的权重是50,其次,某市某地区某小区等从大地区到小地区再到具体小区的该类搜索也是用户的高频搜索方式, 其相应的模板就对应为CIT|DIS|RES。

另外值得一提的是,为保证训练数据的正确性,我们基于楼盘字典建立了对应关系,将省—市,市—区,区—商圈,商圈—小区,市—小区,区—小区对应起来,为之后训练集的随机抽取限定条件,例如只能出现北京市海淀区而不会出现河北省郑州市昌平区这样的情况。

由于数据量巨大,生成训练集时将每种模板对应的地址实体全部排列组合是不可能实现的,因此我们的做法是:对于每种模板,在每类地址实体中随机抽取地址与其匹配。例如:BIZ|RES这类模板对应的权重为15,这就意味着需要在商圈和小区的地址表里分别随机抽取一个地址实体作为模板的替代,一共需要抽取15次,抽取同时仍需要符合上述映射条件。

除了正样本之外,我们也生成了一部分不含地址的负样本模板,就非地址实体而言,小区+房屋细节是用户的高频搜索,例如:某某小区三室一厅,或者某某小区x号楼等,其对应的模板就是RES|DET。

为了增加模型的泛化能力,我们也将语音找房的数据作为负样本扩展到训练集中。语音找房数据有准确的地址定位和明显的意图,比如“我想在五道口买套房”,但和搜索数据相比,语音找房的数据有两个较为明显的特点:其一,语音找房中有大量口语化类的字词。其二,关于房屋细节(DET)的问法多样化。这两个特点均为负样本的构造提供了丰富的数据来源。

针对上述第一个问题,我们为语音找房数据构造了特有的模板,对可能出现的问法的开头和结尾分别命名ASK和END。例如用户的语音输入模板为ASK|RES|END,那么ASK便可能是:“我想要”,“请问”,“想了解一下”等,END可能是:“的房子”,“定居”或者“买套房”等。对每种问法设置相应的权重并在生成模板时随机组合,便可获得大量包含地址且符合用户口语输入习惯的训练数据。对于第二个问题,我们丰富了房屋具体细节(DET)的种类,按照面积、房型结构、房屋单元、价格、距离、具体位置、楼层、装修、电梯等不同的问法归类,随机抽取产生样本。


1.  `如下所示为 DET--房屋单元  的部分模板与设置的权重`

2.  `房屋单元:(带|有|包括|)(露台|阳台|平台|落地窗|飘窗)  2`

3.  `房屋单元:带(花园|院|阁楼)  3`

4.  `房屋单元:(双天井|双阳台|大阳台|大露台|大平台|大落地窗)  1`

5.  `房屋单元:x(居|室|房|厅)  1`

6.  `房屋单元:x(房|室)x厅  5`

7.  `房屋单元:x(室|房)x厅(1|一)厨  1`

8.  `房屋单元:x(室|房)x厅(1|2|一|二)卫  2`

9.  `房屋单元:x(室|房)x厅(1|一)厨(1|2|一|二)卫  1`

10.  `房屋单元:x(室|房)x厅(1|2|一|二)卫(1|一)厨  1`

11.  `房屋单元:x(居|居室)  10`

12.  `房屋单元:x(居|居室)(到|至|-|~|或者|或|)x(居|居室)  5`

最后需要将每条语料编码成模型的输入BIO标注形式,地址实体的首字,非首字和非地址实体分别对应B-LOC, I-LOC和O,每条训练语料以句号结尾。例如上述图例中的语料“北京市昌平区金域华府。”就会被编码成“B-LOC, I-LOC,I-LOC,B-LOC, I-LOC,I-LOC,B-LOC, I-LOC,I-LOC,I-LOC,O”至此,我们便可以开展模型的训练过程。

  1. 对比实验

我们也选择了基于统计的方法——CRF做对比实验。在深度学习的模型中,只用CRF层时,仍用word-embedding作为输入,但不计算前向和后向参数,线性层之后得到序列标注的结果,这种基于Tensorflow的方法不经过特征提取。而在传统的机器学习中,CRF依赖于特征工程,它的目标函数不仅考虑输入的状态特征函数,而且还包含了标签转移特征函数。

测试集的格式与训练集一致,同样为BIO标注格式,共1370条语料,其中包含1000条搜索数据和370条语音找房数据。测评方式统一采用CRF实体识别的测评工具conlleval.pl得出准确率,精确率,召回率与F1值。

对比实验结果如下:

以上的三种算法,前两者均采用Tensorflow的框架搭建,设置相同的训练参数:epochnumber = 5,embeddingdim=300,hiddendim=300 ,batchsize=256 ,dropout=0.8。


1.  `# BiLSTM层`

2.  `def biLSTM_layer_op(self):`

3.   `with tf.variable_scope("bi-lstm"):`

4.   `# 计算前向后向参数`

5.   `cell_fw =  LSTMCell(self.hidden_dim)`

6.   `cell_bw =  LSTMCell(self.hidden_dim)`

7.   `(output_fw_seq,output_bw_seq), _ =`

8.   `tf.nn.bidirectional_dynamic_rnn(`

9.   `cell_fw=cell_fw,`

10.   `cell_bw=cell_bw,`

11.   `inputs=self.word_embeddings,`

12.   `sequence_length=self.sequence_lengths,`

13.   `dtype=tf.float32)`

14.   `# 参数全连接`

15.   `output = tf.concat([output_fw_seq,output_bw_seq], axis=-1)`

16.   `output = tf.nn.dropout(output, self.dropout_pl)`

17.   `# 通过一个线性层,得到每个标签的得分`

18.   `# 当只有CRF模型时,只有该线性层,不经过上述参数计算且维度不需乘2`

19.   `with tf.variable_scope("proj"):`

20.   `W = tf.get_variable(`

21.   `name="W",`

22.   `shape=[2  * self.hidden_dim,self.num_tags],`

23.   `initializer=tf.contrib.layers.xavier_initializer(),`

24.   `dtype=tf.float32)`

25.

26.   `b = tf.get_variable(`

27.   `name="b",`

28.   `shape=[self.num_tags],`

29.   `initializer=tf.zeros_initializer(),`

30.   `dtype=tf.float32)`

31.

32.   `s = tf.shape(output)`

33.   `output = tf.reshape(output,  [-1,  2  * self.hidden_dim])`

34.   `pred = tf.add(tf.matmul(output, W) , b, name='pred')`

35.   `self.logits = tf.reshape(`

36.   `pred,  [-1, s[1], self.num_tags], name='logits')`

37.

38.

39.  `#CRF层`

40.  `def CRF(self):`

41.   `#得到转移矩阵`

42.   `log_likelihood, self.transition_params = crf_log_likelihood(`

43.   `inputs=self.logits,`

44.   `tag_indices=self.labels,`

45.   `sequence_lengths=self.sequence_lengths)`

46.   `self.loss =  -tf.reduce_mean(log_likelihood)`

47.   `#解码`

48.   `self.outputs, _ = crf_decode(`

49.   `potentials=self.logits,`

50.   `transition_params=self.transition_params,`

51.   `sequence_length=self.sequence_lengths)`

52.   `self.outputs0 = tf.reshape(`

53.   `self.outputs[0],  [-1, self.sequence_lengths[0]], name='outputs')`

第三种算法需要构建特征,本实验采用Unigram类型,并去除出现次数仅为1次的特征。

从各模型的测试结果我们可以看出,加入特征后,CRF模型的预测能力大大提升,但和混合模型相比仍有明显差距。混合模型BiLSTM-CRF在测试集中有最好的表现,预测的各项评估指标均高于92%,未加特征的CRF模型表现最差,准召率仅有60%左右,这也符合了我们对比实验之前的猜想。

  1. 总结与展望

本文针对命名实体识别这一热点问题进行了研究,并在实验部分选取了其中的两种方法:BiLSTM与CRF结合的混合方法和基于统计的CRF方法。在训练集的生成中结合语音找房的数据,采用模板生成与随机抽取的