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类!!!刺激。下回分解吧!