实体命名识别详解(四)

接下来我们来看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 "", line 1, in  
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,否则,不断自增。

你可能感兴趣的:(实体命名识别详解(四))