首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >spaCy v1.0:自定义管道与Keras深度学习集成

spaCy v1.0:自定义管道与Keras深度学习集成

原创
作者头像
用户11764306
发布2026-05-24 08:07:41
发布2026-05-24 08:07:41
940
举报

spaCy v1.0:基于自定义管道和Keras的深度学习

2016年10月19日 · 7分钟阅读

博客 · spaCy · Matthew Honnibal

我很高兴宣布spaCy 1.0发布,这是世界上最快的NLP库。1.0版本最出色的部分是一个用于将自定义模型集成到spaPy中的新系统。本文将介绍这些变化,并展示如何使用新的自定义管道功能,将基于Keras的LSTM情感分析模型添加到spaCy管道中。

spaCy用户调查

感谢所有已经填写调查问卷的用户!如果您是spaCy用户且尚未看到调查问卷,您可以通过提供反馈来帮助改进该库。

spaCy用户调查收到了大量关于该库的宝贵反馈。最明确的发现是需要更多教程。我们目前正在为网站开发一个新的、改进的教程板块。同时,我们也优先为新的1.0功能制作教程——例如新的基于规则、具有实体感知能力的匹配器、模型训练API和自定义管道。

自定义管道尤其令人兴奋,因为它们允许您将自己的深度学习模型挂接到spaCy中。因此,话不多说,下面介绍如何使用Keras训练LSTM情感分析模型,并在spaCy中使用生成的标注。

如何使用Keras通过LSTM模型将情感分析添加到spaCy

有许多优秀的开源库用于研究、训练和评估神经网络。然而,这些库的关注点通常在您获得评估分数和模型文件时就结束了。spaCy一直旨在编排多个文本标注模型,并帮助您在应用程序中一起使用它们。spaCy 1.0现在使得使用自己的自定义模型计算这些标注变得更加容易。

什么是Keras?

Keras提供了一个高级别的声明式接口来定义神经网络。默认使用谷歌的TensorFlow训练模型,同时也支持Theano。

在本教程中,我们将使用Keras,因为它是Python中最流行的深度学习库。假设您已经编写了一个自定义的情感分析模型,用于预测文档是正面的还是负面的。现在您想要找出哪些实体通常与正面或负面文档相关联。以下是一个运行时示例:

运行时用法

代码语言:python
复制
def count_entity_sentiment(nlp, texts):
    '''计算文本中每个实体的净文档情感。'''
    entity_sentiments = collections.Counter()
    for doc in nlp.pipe(texts, batch_size=1000, n_threads=4):
        for ent in doc.ents:
            entity_sentiments[ent.text] += doc.sentiment
    return entity_sentiments

def load_nlp(lstm_path, lang_id='en'):
    def create_pipeline(nlp):
        return [nlp.tagger, nlp.entity, SentimentAnalyser.load(lstm_path, nlp)]
    return spacy.load(lang_id, create_pipeline=create_pipeline)

您需要做的就是将一个create_pipeline回调函数传递给spacy.load()。该函数应接受一个spacy.language.Language对象作为其唯一参数,并返回一个可调用对象的序列。每个可调用对象应接受一个Doc对象,就地修改它,并返回None

当然,对单个文档进行操作是低效的,尤其是对于深度学习模型。通常我们希望标注许多文本,并希望并行处理它们。因此,您应确保您的模型组件也支持.pipe()方法。.pipe()方法应该是一个行为良好的生成器函数,能够处理任意大的序列。它应消费一小批文档,并行处理它们,然后逐个产出。

自定义标注器类

代码语言:python
复制
class SentimentAnalyser(object):
    @classmethod
    def load(cls, path, nlp):
        with (path / 'config.json').open() as file_:
            model = model_from_json(file_.read())
        with (path / 'model').open('rb') as file_:
            lstm_weights = pickle.load(file_)
        embeddings = get_embeddings(nlp.vocab)
        model.set_weights([embeddings] + lstm_weights)
        return cls(model)

    def __init__(self, model):
        self._model = model

    def __call__(self, doc):
        X = get_features([doc], self.max_length)
        y = self._model.predict(X)
        self.set_sentiment(doc, y)

    def pipe(self, docs, batch_size=1000, n_threads=2):
        for minibatch in cytoolz.partition_all(batch_size, docs):
            Xs = get_features(minibatch)
            ys = self._model.predict(X)
            for i, doc in enumerate(minibatch):
                doc.sentiment = ys[i]

    def set_sentiment(self, doc, y):
        doc.sentiment = float(y[0])
        # 情感有一个原生槽位用于单个浮点数。
        # 对于任意数据存储,可以使用:
        # doc.user_data['my_data'] = y

def get_features(docs, max_length):
    Xs = numpy.zeros((len(docs), max_length), dtype='int32')
    for i, doc in enumerate(minibatch):
        for j, token in enumerate(doc[:max_length]):
            Xs[i, j] = token.rank if token.has_vector else 0
    return Xs

默认情况下,spaCy 1.0会下载并使用300维的GloVe通用爬虫向量。也很容易将这些向量替换为您自己训练的向量,或者完全禁用词向量。如果您已经将词向量安装到spaCy的Vocab对象中,以下是如何在Keras模型中使用它们:

使用Keras训练

代码语言:python
复制
def train(train_texts, train_labels, dev_texts, dev_labels,
        lstm_shape, lstm_settings, lstm_optimizer, batch_size=100, nb_epoch=5):
    nlp = spacy.load('en', parser=False, tagger=False, entity=False)
    embeddings = get_embeddings(nlp.vocab)
    model = compile_lstm(embeddings, lstm_shape, lstm_settings)
    train_X = get_features(nlp.pipe(train_texts))
    dev_X = get_features(nlp.pipe(dev_texts))
    model.fit(train_X, train_labels, validation_data=(dev_X, dev_labels),
                nb_epoch=nb_epoch, batch_size=batch_size)
    return model

def compile_lstm(embeddings, shape, settings):
    model = Sequential()
    model.add(
        Embedding(
            embeddings.shape[1],
            embeddings.shape[0],
            input_length=shape['max_length'],
            trainable=False,
            weights=[embeddings]
        )
    )
    model.add(Bidirectional(LSTM(shape['nr_hidden'])))
    model.add(Dropout(settings['dropout']))
    model.add(Dense(shape['nr_class'], activation='sigmoid'))
    model.compile(optimizer=Adam(lr=settings['lr']), loss='binary_crossentropy',
                    metrics=['accuracy'])
    return model

def get_embeddings(vocab):
    max_rank = max(lex.rank for lex in vocab if lex.has_vector)
    vectors = numpy.ndarray((max_rank+1, vocab.vectors_length), dtype='float32')
    for lex in vocab:
        if lex.has_vector:
            vectors[lex.rank] = lex.vector
    return vectors

def get_features(docs, max_length):
    Xs = numpy.zeros((len(list(docs)), max_length), dtype='int32')
    for i, doc in enumerate(docs):
        for j, token in enumerate(doc[:max_length]):
            Xs[i, j] = token.rank if token.has_vector else 0
    return Xs

对于大多数应用,建议使用预训练的词嵌入而不进行“微调”。这意味着您将在不同模型间使用相同的嵌入,避免在训练数据上学习对它们的调整。嵌入表很大,预训练向量提供的值已经相当不错。因此,微调嵌入表会浪费您的“参数预算”。通常更好的做法是以其他方式增大网络规模,例如添加另一个LSTM层、使用注意力机制、使用字符特征等。

属性钩子(实验性)

前面我们看到了如何将数据存储在新的通用user_data字典中。这种方式泛化性很好,但不够令人满意。理想情况下,我们希望让自定义数据驱动更“原生”的行为。例如,考虑spaCy的DocTokenSpan对象提供的.similarity()方法:

多态相似性示例

代码语言:python
复制
span.similarity(doc)
token.similarity(span)
doc1.similarity(doc2)

实现说明:钩子存在于Doc对象上,因为SpanToken对象是延迟创建的,不拥有任何数据。它们只是代理到其父级Doc。这在这里很方便——我们只需在一个地方考虑安装钩子。

默认情况下,这仅平均每个文档的向量并计算它们的余弦相似度。显然,spaCy应该让您轻松安装自己的相似度模型。这引入了一个棘手的设计挑战。当前的解决方案是向Doc对象添加三个额外的字典:

名称

描述

user_hooks

自定义doc.vectordoc.has_vectordoc.vector_normdoc.sents的行为

user_token_hooks

自定义token.similaritytoken.vectortoken.has_vectortoken.vector_normtoken.conjuncts的行为

user_span_hooks

自定义span.similarityspan.vectorspan.has_vectorspan.vector_normspan.root的行为

总结一下,以下是一个挂接自定义.similarity()方法的示例:

添加自定义相似度钩子

代码语言:python
复制
class SimilarityModel(object):
    def __init__(self, model):
        self._model = model

    def __call__(self, doc):
        doc.user_hooks['similarity'] = self.similarity
        doc.user_span_hooks['similarity'] = self.similarity
        doc.user_token_hooks['similarity'] = self.similarity

    def similarity(self, obj1, obj2):
        y = self._model([obj1.vector, obj2.vector])
        return float(y[0])

下一步是什么?

属性钩子可能会略有演变,并且肯定需要进行一些调整以完全保持一致。我也期待为词性标注器、句法解析器和实体识别器发布改进的模型。在过去的十二个月里,研究表明双向LSTM模型是这些任务的简单而有效的方法。最终生成的模型在内存占用上也会显著更小。

资源

您可以在Semantic Scholar上找到更多描述基于双向LSTM模型的论文。

教程代码:GitHub上的完整代码。

spaCy v1.0 发布说明

spaCy文档

Keras文档FINISHED

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档