实体命名识别详解(十五)

from model.data_utils import CoNLLDataset
from model.ner_model import NERModel
from model.config import Config


def main():
    # create instance of config
    config = Config()

    # build model
    model = NERModel(config)
    model.build()
    # model.restore_session("results/crf/model.weights/") # optional, restore weights
    # model.reinitialize_weights("proj")

    # create datasets
    dev   = CoNLLDataset(config.filename_dev, config.processing_word,
                         config.processing_tag, config.max_iter)
    train = CoNLLDataset(config.filename_train, config.processing_word,
                         config.processing_tag, config.max_iter)

    # train model
    model.train(train, dev)

if __name__ == "__main__":
    main()

这是我们最近一直在分析的train.py函数。
接下来创建开发集和训练集,CoNLLDataset这个类我们之前讲过,返回的是可迭代类型的(word,tag)键值对,不再赘述,我在实体命名识别详解(四)这篇中讲过。
在程序的最后是train函数,讲道理应该是最关键的一步了吧,是骡子是马该见分晓了。
train函数这里是继承了BaseModel类,,嗯嗯嗯,分析到现在,我越来越开始发现面向对象编程的优点了,各种实例化,各种继承,这是啥?这模拟了世界万物的自然法则啊!传承与发展,整体与个体,抽象与具象,感觉有点触摸到了DAO的意味(笑。
咳咳,扯远了,先来看train函数。

    def train(self, train, dev):
        """Performs training with early stopping and lr exponential decay

        Args:
            train: dataset that yields tuple of (sentences, tags)
            dev: dataset

        """
        best_score = 0
        nepoch_no_imprv = 0 # for early stopping
        self.add_summary() # tensorboard

        for epoch in range(self.config.nepochs):
            self.logger.info("Epoch {:} out of {:}".format(epoch + 1,
                        self.config.nepochs))

            score = self.run_epoch(train, dev, epoch)
            self.config.lr *= self.config.lr_decay # decay learning rate

            # early stopping and saving best parameters
            if score >= best_score:
                nepoch_no_imprv = 0
                self.save_session()
                best_score = score
                self.logger.info("- new best score!")
            else:
                nepoch_no_imprv += 1
                if nepoch_no_imprv >= self.config.nepoch_no_imprv:
                    self.logger.info("- early stopping {} epochs without "\
                            "improvement".format(nepoch_no_imprv))
                    break

先看函数体介绍:使用即时停止(early stopping)学习率衰减(learning rate decay)进行训练。传入两个参数:训练集和开发集。
首先我们定义了best_score:最佳分数,先初始化为0,我们在后面可以看到,每次score>best_score,就更新best_score。还设置了nepoch_no_imprv,就是说在多少个epoch之后仍未提升,这里先初始化为0,紧接着add_summary(),TensorFlow的可视化操作for TensorBoard
接下来一个for循环来训练每个epoch,先使用self.logger.info打印日志。
使用run_pepoch()进行训练。
我彻底糊涂了。。train函数这里是base_model.py文件下的函数啊,对啊没错,但是它里面眉有run_epoch()函数啊。我找了半天,发现,,,这他妈run_epoch在子类ner_model.py中实现的!!??这是咋回事呢?父亲调用儿子的方法??所以先有鸡还是先有蛋呢?
先让大家看一下ner_model.py下的run_epoch()

    def run_epoch(self, train, dev, epoch):
        """Performs one complete pass over the train set and evaluate on dev

        Args:
            train: dataset that yields tuple of sentences, tags
            dev: dataset
            epoch: (int) index of the current epoch

        Returns:
            f1: (python float), score to select model on, higher is better

        """
        # progbar stuff for logging
        batch_size = self.config.batch_size
        nbatches = (len(train) + batch_size - 1) // batch_size
        prog = Progbar(target=nbatches)

        # iterate over dataset
        for i, (words, labels) in enumerate(minibatches(train, batch_size)):
            fd, _ = self.get_feed_dict(words, labels, self.config.lr,
                    self.config.dropout)

            _, train_loss, summary = self.sess.run(
                    [self.train_op, self.loss, self.merged], feed_dict=fd)

            prog.update(i + 1, [("train loss", train_loss)])

            # tensorboard
            if i % 10 == 0:
                self.file_writer.add_summary(summary, epoch*nbatches + i)

        metrics = self.run_evaluate(dev)
        msg = " - ".join(["{} {:04.2f}".format(k, v)
                for k, v in metrics.items()])
        self.logger.info(msg)

        return metrics["f1"]

还是稍等下吧,我出去吃个火锅先2333,换套心情。2019年7月21日19:31:41

2019年7月22日09:31:25 我想了想,大概明白思路了。

train函数里,首先传入的参数是self本体和训练集、测试集。
先是一个add_summary()函数,它构建了一个TensorBoard的summary方法集合。

    def add_summary(self):
        """Defines variables for Tensorboard

        Args:
            dir_output: (string) where the results are written

        """
        self.merged      = tf.summary.merge_all()
        self.file_writer = tf.summary.FileWriter(self.config.dir_output,
                self.sess.graph)

这俩句我就不赘述了,看看即可。
然后一个for循环遍历所有的epoch,我懂了,为什么base_model.py里会有ner_mkdel.py中的函数?简单来说这里传入的参数self,其实是给子类NERModel的,只不过NERModel这里嫌麻烦(可能是2333),没有重写train方法,直接在train里调用NERModel中的run_epoch方法,因为self本身就服务于NERModel,所以当然也是阔以的。。目前来讲我的理解就是这样的。


那我们初步分析一下子类的这个run_epoch()方法吧。

    def run_epoch(self, train, dev, epoch):
        """Performs one complete pass over the train set and evaluate on dev

        Args:
            train: dataset that yields tuple of sentences, tags
            dev: dataset
            epoch: (int) index of the current epoch

        Returns:
            f1: (python float), score to select model on, higher is better

        """
        # progbar stuff for logging
        batch_size = self.config.batch_size
        nbatches = (len(train) + batch_size - 1) // batch_size
        prog = Progbar(target=nbatches)

        # iterate over dataset
        for i, (words, labels) in enumerate(minibatches(train, batch_size)):
            fd, _ = self.get_feed_dict(words, labels, self.config.lr,
                    self.config.dropout)

            _, train_loss, summary = self.sess.run(
                    [self.train_op, self.loss, self.merged], feed_dict=fd)

            prog.update(i + 1, [("train loss", train_loss)])

            # tensorboard
            if i % 10 == 0:
                self.file_writer.add_summary(summary, epoch*nbatches + i)

        metrics = self.run_evaluate(dev)
        msg = " - ".join(["{} {:04.2f}".format(k, v)
                for k, v in metrics.items()])
        self.logger.info(msg)

        return metrics["f1"]

先看函数体介绍,在训练集上执行一个完整的遍历并在开发集上评估一下。
传入的参数是训练集、开发集、epoch(int 类型),返回一个f1分数,f1分数是平衡与准确率和查全率的指标,越高越好。
batch_size我们在Config类实例化中配置过,为20。
num_batches我们这里设置(len(train) + batch_size - 1) // batch_size,这里//双斜杠表示向下整除,len(train) + batch_size - 1也很好理解,为了不漏掉每一个数据。比方说我这里有39个训练集,那么这样按一个batch有20个数据来看,我一共(39 + 20 - 1)// 20 = 2个batch。。。那为啥要-1捏?假如我有40个数据,我(40 + 20)// 20,我他妈,成3个batch了,所以-1为了确保在训练数据能被batch_size整除时依然不出错!
再接下来,prog = Progbar(target=nbatches)
这是新建了一个类的实例啊,我们从train.py的短短一句函数model.train(train, dev),跳转到base_model.py中的train()函数,在train()中的短短一句score = self.run_epoch(train, dev, epoch)跳转进ner_model.py中的run_epoch()函数,在run_epoch()中的短短一句prog = Progbar(target=nbatches),我们又他妈得进入general_utils.py中的Progbar类!!!刺激。下回分解吧!

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