用深度学习从非结构化文本中提取特定信息

这是我们在iki项目工作中的一系列技术文章中的第一篇,内容涵盖用机器学习和深度学习技术来解决自然语言处理与理解问题的一些应用案例。

在本文中,我们要解决的问题是从非结构化文本中提出某些特定信息。我们要从简历中提取出用户的技能,简历可以以任意格式书写,比如“曾经在生产服务器上部署定量交易算法”。

本文有一个演示页面,可以用你的简历试试我们的模型表现如何。

语言学模型

现代语言学模型(ULMfit,ELMo)使用无监督学习技术,比如在大型文本语料中加入RNN嵌入层(embeddings)用来“认识”基本的语言结构,然后再进行特定的监督训练。在某些情况下,你反而需要一个在非常特定的、小的数据集上训练出来的模型。这些模型对一般的语言结构几乎一无所知,只对特定的文本特征有效。一个典型的例子是影评或新闻数据集的简易情感分析工具,这些极简单的分析模型只能识别“好”或“坏”等形容词的同义词,或者判别是否有强调性词汇存在。在我们的研究中,这两种方法我们都采用。

通常,当进行文本语料分析时,我们会考虑文本中的全部词汇。一些流行的文本向量化算法,比如tfidf,word2vec或GloVe模型都使用整个文档的词汇表来生成向量,除了停用词(例如冠词、代词,和其它十分基本的语言元素,在统计平均法中几乎没有语义上的意义)。如果有更明确的目标,并且有关于语料的更多的信息,你也许会判断出哪些语料更有价值。比如说,要对菜谱的语料进行分析,把配料和菜品名称的类别从文本中提取出来就很重要。另外一个例子是从简历语料中提取专业技能。如果我们能够通过把每个简历与一个提取出来的技能的向量相关联,从而使之向量化,我们就可以对行业职位的分类做得好得多。

举例说明:

简历:数据科学家,机器学习、大数据、开发、统计和分析方面的实际经验。带领数据科学家团队实现了Python机器学习模型的大融合、分层和特征工程,展现出预测性分析方面的极高的准确度。使用Doc2Vec词汇嵌入和神经网络创立了一个推荐系统。

提取的专业技能:机器学习,大数据,开发,统计,分析,Python机器学习模型大融合,分层,特征工程,预测性分析,Doc2Vec,词汇嵌入,神经网络。

步骤一:词性标注

实体抽取是文本挖掘类问题的一部分,它从非结构化的文本中提取出某些结构化的信息。我们来仔细看看受到推崇的实体抽取方法的思路。如果技能主要都是通过所谓的名词短语体现的,那么我们的抽取动作的第一步就是实体识别,用的是NLTK库的内置函数(参阅“从文本中提出信息”,《NLTK全书》第7部分)。词性标注函数提取出名词短语(NP),并用树来表示名词短语和句中其它部分的关系。NLTK库有若干工具能进行这样的词语分解。

NLTK全书,第7章,图2.2:基于简单正则表达式的NP Chunker的一个示例

我们可以定义一个用正则表达式完成语句分解的模型(例如,我们可以把几个形容词加上一个名词定义为一个短语),或者我们能用NLTK中的已经提取出来的名词短语范例训练出一个关于本文标号的模型。这个步骤能让我们得到许多实体,其中一些是我们要的技能,而另一些不是。此外,技能简历也可能包含其它实体,比如地点、人物、对象、组织,诸如此类。

步骤二:候选词分类的深度学习架构

下一步是实体分类。这里的目标非常简单----把技能从“非技能”里区别开来。用于训练的特征集是根据候选短语和上下文的结构来构建的。显然,要训练一个模型,我们先要创建一个标注好的训练集,我们用1500个抽取出来的实体手工创建了训练集,里面包含了技能和“非技能”。

我们从不打算把模型应用于那些硬编码的有限的技能集合,模型的核心思想是从英文简历的技能中学习到语义,并用模型来提取出未见过的技能。

每个词的向量由二进制特征组成,这些特征包括数字或其它特殊字符的出现与否(技能通常包含数字和符号:C#,Python3),首字母或全词大写(SQL)。我们也看某个词是否在英语词汇表里以及是否在一些主题列表里,比如人名、地名等等。最终使用了这些特征的模型在实体测试集中的准确率是74.4%。如果把候选词中是否有英语里常见的前缀和后缀,做成二进制特征,放到模型里,则模型在测试集中的准确率高达77.3%。如果模型的特征集中再加上用单热(one-hot)向量编码的词性标注,准确率就可以推到84.6%。

一个可靠的语义词汇嵌入模型没法用简历数据集训练得来,这样的数据集太小,也不全面。针对这个问题,你应该使用在其它真正大型的数据集上训练出来的词汇嵌入层。我们使用了有50个维度的GloVe模型向量,这把模型在测试集上的准确率提升至高达89.1%。你可以上传一个简历文本到我们的最终模型的演示,试试效果。

流行的词性标注程序(NLTK 词性标注程序,Standford 词性标注程序)经常在做简历短语标注时犯错误。原因是简历文本为了突显经验和照顾格式(人们在句子中以谓语开头而不是主语,有时,短语没有用正确的语法结构)就经常忽略语法,以及有许多词是专用术语和名称。我们只得写一个自己的词性标注程序解决上述问题。

分类是通过Keras神经网络进行的,这个Keras神经网络有三个输入层,每一层都被设计用来接收特定类别的数据。第一个输入层接收一个可变长度的向量,构成这个向量的候选短语具有我们上面讨论过的特征,它可以包含任意数目的单词。这个特征向量由一个LSTM层进行处理。

第二个可变长度向量含有上下文结构信息。对于给定的窗口大小n,我们在候选短语左边和右边各取n个相邻单词,这些单词的向量拼接成一个可变长度的向量,并传到LSTM层。我们发现最优的n=3。

第三个输入层的长度固定,它使用候选短语的通用信息和上下文来处理向量——短语里的单词向量在坐标轴上的最大最小值,以及它的上下文所代表的在整个短语中的众多的二进制特征的存在与否以及其它信息。

我们把这个结构称作SkillExtractor,如下图所示:

Skills Extractor网络架构

通过 Keras 实现:

class SkillsExtractorNN:

    def __init__(self, word_features_dim, dense_features_dim):

        lstm_input_phrase = keras.layers.Input(shape=(None, word_features_dim))
        lstm_input_cont = keras.layers.Input(shape=(None, word_features_dim))
        dense_input = keras.layers.Input(shape=(dense_features_dim,))

        lstm_emb_phrase = keras.layers.LSTM(256)(lstm_input_phrase)
        lstm_emb_phrase = keras.layers.Dense(128, activation='relu')(lstm_emb_phrase)

        lstm_emb_cont = keras.layers.LSTM(256)(lstm_input_cont)
        lstm_emb_cont = keras.layers.Dense(128, activation='relu')(lstm_emb_cont)

        dense_emb = keras.layers.Dense(512, activation='relu')(dense_input)
        dense_emb = keras.layers.Dense(256, activation='relu')(dense_emb)

        x = keras.layers.concatenate([lstm_emb_phrase, lstm_emb_cont, dense_emb])
        x = keras.layers.Dense(128, activation='relu')(x)
        x = keras.layers.Dense(64, activation='relu')(x)
        x = keras.layers.Dense(32, activation='relu')(x)

        main_output = keras.layers.Dense(2, activation='softplus')(x)

        self.model = keras.models.Model(inputs=[lstm_input_phrase, lstm_input_cont, dense_input],
                                        outputs=main_output)

        optimizer = keras.optimizers.Adam(lr=0.0001)

        self.model.compile(optimizer=optimizer, loss='binary_crossentropy')

把学习率降到0.0001之后,用Adam优化法取得了最好的模型训练效果。我们选择binary_crossentropy作为损失函数,因为模型的设计是对两个类别进行分类的。

为了使用更方便,我们加入拟合函数来进行神经网络的训练并使用交叉检查和预测函数实现自动停止,从而实现对候选短语的未知的向量的预测。

    def fit(self, x_lstm_phrase, x_lstm_context, x_dense, y,
            val_split=0.25, patience=5, max_epochs=1000, batch_size=32):

        x_lstm_phrase_seq = keras.preprocessing.sequence.pad_sequences(x_lstm_phrase)
        x_lstm_context_seq = keras.preprocessing.sequence.pad_sequences(x_lstm_context)

        y_onehot = onehot_transform(y)

        self.model.fit([x_lstm_phrase_seq, x_lstm_context_seq, x_dense],
                       y_onehot,
                       batch_size=batch_size,
                       pochs=max_epochs,
                       validation_split=val_split,
                       callbacks=[keras.callbacks.EarlyStopping(monitor='val_loss', patience=patience)])


    def predict(self, x_lstm_phrase, x_lstm_context, x_dense):

        x_lstm_phrase_seq = keras.preprocessing.sequence.pad_sequences(x_lstm_phrase)
        x_lstm_context_seq = keras.preprocessing.sequence.pad_sequences(x_lstm_context)

        y = self.model.predict([x_lstm_phrase_seq, x_lstm_context_seq, x_dense])

        return y

pad_sequences函数把一系列特征序列转换成2维数组,这个数组的宽度等于这些序列中的最长者。这样做是为了让可变长度的数据传到LSTM层中,并转换成适合模型训练的格式。

onehot_transform函数把目标值0和1转换成单热(one-hot)向量[1, 0]和[0, 1]

def onehot_transform(y):

    onehot_y = []

    for numb in y:
        onehot_arr = np.zeros(2)
        onehot_arr[numb] = 1
        onehot_y.append(np.array(onehot_arr))

    return np.array(onehot_y)

只要实体及其上下文中的单词数是不确定的,那么,使用稀疏的固定长度的向量就会让人觉得不合理。因此,使用可以处理任意长度向量的循环神经网络就自然显得很方便了。我们的许多试验都证明了使用稠密层处理固定长度向量、使用LSTM层处理可变长度向量的架构是最合理的。

我们试验过不同的稠密层与LSTM层相组合而形成的多个架构。最后得到的架构配置(层的大小和数量)在交叉验证测试中取得了最优效果,同时训练数据的使用效果也达到最佳。以后的模型要做调优,可以增加训练数据集的大小并且适当增加层的数量和大小,如果在相同的数据集上只是单纯增加层的数量和大小,会导致模型过拟合。

结果

抽取的技能举例

用于模型训练的所有简历都是来自IT行业。我们很高兴看到我们的模型在其它行业(比如,设计和金融)的简历数据集上也有不错的表现。显然,处理完全不同结构和风格的简历会让模型的效果打折扣。我们也想指出,我们对技能这个概念的理解可能跟别人有所不同。我们的模型所遇到的难点之一是把技能跟新的公司的名称相区别开来,这是因为技能往往跟软件框架的名字一样,有时候,你区分不了一个名词到底是指所提到的初创公司还是指新的JS框架或Python库。但不管怎样,在大多数情况下,我们的模型都可以作为一个有效的简历自动化分析工具,而且借助一些统计方法,我们的模型可以大范围用于任意简历语料的数据科学课题中。

原文链接:

https://towardsdatascience.com/deep-learning-for-specific-information-extraction-from-unstructured-texts-12c5b9dceada

想要继续查看该篇文章相关链接和参考文献?

本文分享自微信公众号 - AI研习社(okweiwu)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-06-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券