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中最流行的深度学习库。假设您已经编写了一个自定义的情感分析模型,用于预测文档是正面的还是负面的。现在您想要找出哪些实体通常与正面或负面文档相关联。以下是一个运行时示例:
运行时用法
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()方法应该是一个行为良好的生成器函数,能够处理任意大的序列。它应消费一小批文档,并行处理它们,然后逐个产出。
自定义标注器类
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训练
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的Doc、Token和Span对象提供的.similarity()方法:
多态相似性示例
span.similarity(doc)
token.similarity(span)
doc1.similarity(doc2)实现说明:钩子存在于Doc对象上,因为Span和Token对象是延迟创建的,不拥有任何数据。它们只是代理到其父级Doc。这在这里很方便——我们只需在一个地方考虑安装钩子。
默认情况下,这仅平均每个文档的向量并计算它们的余弦相似度。显然,spaCy应该让您轻松安装自己的相似度模型。这引入了一个棘手的设计挑战。当前的解决方案是向Doc对象添加三个额外的字典:
名称 | 描述 |
|---|---|
| 自定义 |
| 自定义 |
| 自定义 |
总结一下,以下是一个挂接自定义.similarity()方法的示例:
添加自定义相似度钩子
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 删除。