实体命名识别详解(四)

接下来我们来看build_data.py中的接下来几句。

    # Generators
    # 获取训练集、测试集、开发集
    dev   = CoNLLDataset(config.filename_dev, processing_word)
    test  = CoNLLDataset(config.filename_test, processing_word)
    train = CoNLLDataset(config.filename_train, processing_word)

Generator,字面意思是生成器,也比较好理解:这是生成了开发集(dev)、测试集(test)和训练集(train)。
CoNLLDateset是data_utils.py中定义的一个类,传入的参数是config.filename_(dev/test/train)和processing_word。
config.filename_(dev/test/train)是Config类中定义的几个参数。

    # dataset
    # filename_dev = "data/coNLL/eng/eng.testa.iob"
    # filename_test = "data/coNLL/eng/eng.testb.iob"
    # filename_train = "data/coNLL/eng/eng.train.iob"

    filename_dev = filename_test = filename_train = "data/test.txt" # test

这里有一个注释,原本是有训练集、开发集和测试集的,不过文件太大,而且我们的目的是跑一下程序,初步学习一下,所以这里就注释掉统一换成 "data/test.txt"路径的这个迷你文件。
至于另一个参数processing_word,之前我们介绍过,它返回一个函数闭包函数的引用,用于处理word,进行数据清理和(word--id)对标。
接下来我们进去data_utils.py文件看一下CoNLLDataset类。

class CoNLLDataset(object):
    """Class that iterates over CoNLL Dataset

    __iter__ method yields a tuple (words, tags)
        words: list of raw words
        tags: list of raw tags

    If processing_word and processing_tag are not None,
    optional preprocessing is appplied

    Example:
        ```python
        data = CoNLLDataset(filename)
        for sentence, tags in data:
            pass
        ```

    """
    def __init__(self, filename, processing_word=None, processing_tag=None,
                 max_iter=None):
        """
        Args:
            filename: path to the file
            processing_words: (optional) function that takes a word as input
            processing_tags: (optional) function that takes a tag as input
            max_iter: (optional) max number of sentences to yield

        """
        self.filename = filename
        self.processing_word = processing_word
        self.processing_tag = processing_tag
        self.max_iter = max_iter
        self.length = None
    def __iter__(self):
        niter = 0
        with open(self.filename) as f:
            words, tags = [], []
            for line in f:
                line = line.strip()
                if (len(line) == 0 or line.startswith("-DOCSTART-")):
                    if len(words) != 0:
                        niter += 1
                        if self.max_iter is not None and niter > self.max_iter:
                            break
                        yield words, tags
                        words, tags = [], []
                else:
                    ls = line.split(' ')
                    word, tag = ls[0],ls[1]
                    if self.processing_word is not None:
                        word = self.processing_word(word)
                    if self.processing_tag is not None:
                        tag = self.processing_tag(tag)
                    words += [word]
                    tags += [tag]


    def __len__(self):
        """Iterates once over the corpus to set and store length"""
        if self.length is None:
            self.length = 0
            for _ in self:
                self.length += 1

        return self.length

所以这里是建了三个类的实例化看一下类的介绍,这是基于CoNLL数据集的迭代。CoNLL 系列评测是自然语言处理领域影响力最大的技术评测,每年由 ACL 的计算自然语言学习会议(Conference on Computational Natural Language Learning,CoNLL)主办。我还在网上找到了一个CoNLL的NER数据集,这是地址,密码i0nq,有需求的同志们可以自行下载。
__iter__方法生成了一个(单词,标签)对的元组。如果processing_word和processing_tag是非空的,则应用可选择的预处理。
由于这里我们实例化的时候,给参数processing_word赋了值,接受一个word作为输入。
接下来是一个__iter__()函数的定义。首先__init__()函数我们知道是类的初始化函数,__iter__()是一个可迭代对象。在Python中,list、tuple等都是可迭代对象,我们可以通过iter()函数获取这些可迭代对象的迭代器。然后我们可以对获取到的迭代器不断使用next()函数来获取下条数据。__iter__()函数实际上就是调用了可迭代对象的__iter__()法。

>>>    li    =    [11,    22,    33,    44,    55]
>>>    li_iter    =    iter(li)
>>>    next(li_iter) 11
>>>    next(li_iter) 22
>>>    next(li_iter) 33
>>>    next(li_iter) 44
>>>    next(li_iter) 55
>>>    next(li_iter)
Traceback (most recent call last):
File "<stdin>", line 1, in  <module>
StopIteration

可以看到,迭代完最后一个就会抛出StopIteration异常。
下面我们分析一下__iter__()函数体。
niter是当前迭代次数,初始化为0,然后这时打开文件,将其对象赋给f,创建words和tags两个空列表用于将来存放单词和标签,然后用一个for函数按行读取数据,每一行赋给line变量。
strip()函数用来移除字符串部头和尾部指定的字符,默认情况是空格或换行符,但是这里注意,strip()只能移除字符串头部或尾部的,中间字符不行。
下一句,如果长度为0或者以"-DOCSTART-"开头:如果长度不为0,即如果以"-DOCSTART-"开拓,则迭代数niter+1,如果最大迭代数max_iter已经有了定义且niter超过最大max_iter则跳出循环,返回words和tags,同时将words和tags两个列表置空。
yield是Python的关键字,关于yield的深入剖析,后期我还会出一个单独章节来讲解,这里先简要说一下。

  • yield返回一个可迭代的生成器,和传统的for循环相比,for循环所有的数据都存放在内存里,而yield不同,它就是return返回一个值,但比return多一步就是记录了当前返回值的位置,下次执行的时候从这个位置开始。举个栗子。
#encoding:UTF-8  
def yield_test(n):  
    for i in range(n):  
        yield call(i)  
        print("i=",i)  
    #做一些其它的事情      
    print("do something.")      
    print("end.")  

def call(i):
    return i*2  
  
#使用for循环  
for n in yield_test(5):  
    print("n = ", n)  

这里定义了一个yield_test函数和一个call函数,其中yield_test()是iterable类型,它的作用是从i到n打印并且调用call()函数,在for循环的外边,还有两个打印函数。
接下来 for i in yield_test(5),我们看一下运行结果。


image.png
  • 首先是一个0-5的循环(0、1、2、3、4),先进入yield_test()函数中是一个for循环(也是0-5),先返回一个call(0),也就是返回0*2 =0 给 n,此时 n仍然是0,接下来执行yield_test(1),此时先从yield的下一行进行,即先输出( i = 0 ),然后i变为1,同时返回call(1),call(1)返回2给 n,这是输出n就成了2,以此类推。

回到之前的函数,else:如果长度不为0且并不是以-DOCSTART-开头,用split()函数对文本进行切分,split(‘ ’)意思是以空格为分隔符,切分word-tag键值对并赋给word和tag,如果processing_word选项非空则运行processing_word()函数,如果processing_tag选项非空则运行processing_tag函数,这里我们传入的参数只有processing_word,就只进行processing_word处理。
最后,将word 和 tag分别追加到words和tags列表中去。

  • 最后是一个定义长度的函数__len__(),遍历语料库,如果self.length为None,则设置长度为0,否则,不断自增。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容

  • 本节课纲 可迭代对象 迭代器 生成器Python中内置的序列,如list、tuple、str、bytes、dict...
    郭_扬阅读 1,230评论 0 0
  • 写在前面的话 代码中的# > 表示的是输出结果 输入 使用input()函数 用法 注意input函数输出的均是字...
    FlyingLittlePG阅读 2,756评论 0 8
  • 一、Python简介和环境搭建以及pip的安装 4课时实验课主要内容 【Python简介】: Python 是一个...
    _小老虎_阅读 5,746评论 0 10
  • 一、总体内容 1.1、协程的介绍 1.2、迭代器以及迭代器的应用 1.3、生成器(生成器与迭代器保存的都是生成数据...
    IIronMan阅读 861评论 0 1
  • 今天给大家讲两点 --也许我们根本就问错了问题 关键问题一览 论题和结论是什么 理由是什么 哪些词语意思不明确 什...
    nafji阅读 199评论 0 0