自己动手实现神经网络分词模型

本文由**罗周杨stupidme.me.lzy@gmail.com**原创,转载请注明原作者和出处。

原文链接:https://luozhouyang.github.io/deepseg

分词作为NLP的基础工作之一,对模型的效果有直接的影响。一个效果好的分词,可以让模型的性能更好。

在尝试使用神经网络来分词之前,我使用过jieba分词,以下是一些感受:

  • 分词速度快
  • 词典直接影响分词效果,对于特定领域的文本,词典不足,导致分词效果不尽人意
  • 对于含有较多错别字的文本,分词效果很差

后面两点是其主要的缺点。根据实际效果评估,我发现使用神经网络分词,这两个点都有不错的提升。

本文将带你使用tensorflow实现一个基于BiLSTM+CRF的神经网络中文分词模型。

完整代码已经开源: luozhouyang/deepseg

怎么做分词

分词的想法和NER十分接近,区别在于,NER对各种词打上对应的实体标签,而分词对各个字打上位置标签。

目前,项目一共只有以下5中标签:

  • B,处于一个词语的开始
  • M,处于一个词语的中间
  • E,处于一个词语的末尾
  • S,单个字
  • O,未知

举个更加详细的例子,假设我们有一个文本字符串:

'上','海','市','浦','东','新','区','张','东','路','1387','号'

它对应的分词结果应该是:

上海市 浦东新区 张东路 1387 号

所以,它的标签应该是:

'B','M','E','B','M','M','E','B','M','E','S','S'

所以,对于我们的分词模型来说,最重要的任务就是,对于输入序列的每一个token,打上一个标签,然后我们处理得到的标签数据,就可以得到分词效果。

用神经网络给序列打标签,方法肯定还有很多。目前项目使用的是双向LSTM网络后接CRF这样一个网络。这部分会在后面详细说明。

以上就是我们分词的做法概要,如你所见,网络其实很简单。

Estimator

项目使用tensorflow的estimator API完成,因为estimator是一个高级封装,我们只需要专注于核心的工作即可,并且它可以轻松实现分布式训练。如果你还没有尝试过,建议你试一试。

estimator的官方文档可以很好地帮助你入门: estimator

使用estimator构建网络,核心任务是:

  • 构建一个高效的数据输入管道
  • 构建你的神经网络模型

对于数据输入管道,本项目使用tensorflow的Dataset API,这也是官方推荐的方式。

具体来说,给estimator喂数据,需要实现一个input_fn,这个函数不带参数,并且返回(features, labels)元组。当然,对于PREDICT模式,labelsNone

要构建神经网络给estimator,需要实现一个model_fn(features, labels, mode, params, config),返回一个tf.estimator.EstimatorSepc对象。

更多的内容,请访问官方文档。

构建input_fn

首先,我们的数据输入需要分三种模式TRAINEVALPREDICT讨论。

  • TRAIN模式即模型的训练,这个时候使用的是数据集是训练集,需要返回(features,labels)元组
  • EVAL模式即模型的评估,这个时候使用的是数据集的验证集,需要返回(features,labels)元组
  • PREDICT模式即模型的预测,这个时候使用的数据集是测试集,需要返回(features,None)元组

以上的featureslabels可以是任意对象,比如dict,或者是自己定义的python class。实际上,比较推荐使用dict的方式,因为这种方式比较灵活,并且在你需要导出模型到serving的时候,特别有用。这一点会在后面进一步说明。

那么,接下来可以为上面三种模式分别实现我们的inpuf_fn

对于最常见的TRAIN模式:


def build_train_dataset(params):
    """Build data for input_fn in training mode.

    Args:
        params: A dict

    Returns:
        A tuple of (features,labels).
    """
    src_file = params['train_src_file']
    tag_file = params['train_tag_file']

    if not os.path.exists(src_file) or not os.path.exists(tag_file):
        raise ValueError("train_src_file and train_tag_file must be provided")

    src_dataset = tf.data.TextLineDataset(src_file)
    tag_dataset = tf.data.TextLineDataset(tag_file)

    dataset = _build_dataset(src_dataset, tag_dataset, params)

    iterator = dataset.make_one_shot_iterator()
    (src, src_len), tag = iterator.get_next()
    features = {
   
        "inputs": src,
        "inputs_length": src_len
    }

    return features, tag

使用tensorflow的Dataset API很简单就可以构建出数据输入管道。首先,根据参数获取训练集文件,分别构建出一个tf.data.TextLineDataset对象,然后构建出数据集。根据数据集的迭代器,获取每一批输入的(features,labels)元组。每一次训练的迭代,这个元组都会送到model_fn的前两个参数(features,labels,...)中。

根据代码可以看到,我们这里的features是一个dict,每一个键都存放着一个Tensor

  • inputs:文本数据构建出来的字符张量,形状是(None,None)
  • inputs_length:文本分词后的长度张量,形状是(None)

而我们的labels就是一个张量,具体是什么呢?需要看一下_build_dataset()函数做了什么:


def _build_dataset(src_dataset, tag_dataset, params):
    """Build dataset for training and evaluation mode.

    Args:
        src_dataset: A `tf.data.Dataset` object
        tag_dataset: A `tf.data.Dataset` object
        params: A dict, storing hyper params

    Returns:
        A `tf.data.Dataset` object, producing features and labels.
    """
    dataset = tf.data.Dataset.zip((src_dataset, tag_dataset))
    if params['skip_count'] > 0:
        dataset = dataset.skip(params['skip_count'])
    if params['shuffle']:
        dataset = dataset.shuffle(
            buffer_size=params['buff_size'],
            seed=params['random_seed'],
            reshuffle_each_iteration=params['reshuffle_each_iteration'])
    if params['repeat']:
        dataset = dataset.repeat(params['repeat']).prefetch(params['buff_size'])

    dataset = dataset.map(
        lambda src, tag: (
            tf.string_split([src], delimiter=",").values,
            tf.string_split([tag], delimiter=",").values),
        num_parallel_calls=params['num_parallel_call']
    ).prefetch(params['buff_size'])

    dataset = dataset.filter(
        lambda src, tag: tf.logical_and(tf.size(src) > 0, tf.size(tag) > 0))
    dataset = dataset.filter(
        lambda src, tag: tf.equal(tf.size(src), tf.size(tag)))

    if params['max_src_len']:
        dataset = dataset.map(
            lambda src, tag: (src[:params['max_src_len']],
                              tag[:params['max_src_len']]),
            num_parallel_calls=params['num_parallel_call'
  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值