文章目录
- 导读
- 普通的输出方式
- 上下求索
- TensorBoard是个不错的切入点
- 与Callback参数对应的Callback方法
- 官方的内置Callback
- 官方进度条
- 简单的猜测与简单的验证
- 拼图凑齐了!
导读
在训练模型的过程中往往会有日志一堆一堆的困扰。我并不想知道,因为最后我会在变量里面查询,反正训练过程中也没心思看。于是,就想把进度条简化一下。下面给出解决方案。
普通的输出方式
对于一般的训练过程,我们可能在Tensorflow
中的fit
方法中,将verbose
置为
1
1
1,或者不设置verbose
而让Tensorflow
默认verbose
为
1
1
1。这样的话就会有如下图一样长篇大论的输出。
虽然不至于很烦躁,但是实在不愿意去管这些事情。又不是做生物实验,完全不需要人在这边守着嘛。趁着这时间泡杯咖啡多好。
于是呢,就想着源码里面是如何将输出显示出来的。
上下求索
TensorBoard是个不错的切入点
在Tensorflow
海量的源码中寻找一个输出无疑是大海捞针,对于Windows
用户来说找起来超级麻烦,除非Linux
用户直接用grep
命令作弊。
但是呢,突然就注意到,Tensorflow
还有一个Tensorboard
,是在fit
方法里面的callback
参数中出现。既然日志能够从callback
参数中获得,那么这里是有什么玄机吗?
与Callback参数对应的Callback方法
于是找到了文件中tensorflow/tensorflow/python/keras/engine/training.py
,也就是官方GitHub的这一页(点击直达callbacks注释那一行),他是这么说明的:
'''
callbacks: List of `keras.callbacks.Callback` instances.
List of callbacks to apply during training.
See `tf.keras.callbacks`. Note `tf.keras.callbacks.ProgbarLogger`
and `tf.keras.callbacks.History` callbacks are created automatically
and need not be passed into `model.fit`.
`tf.keras.callbacks.ProgbarLogger` is created or not based on
`verbose` argument to `model.fit`.
Callbacks with batch-level calls are currently unsupported with
`tf.distribute.experimental.ParameterServerStrategy`, and users are
advised to implement epoch-level calls instead with an appropriate
`steps_per_epoch` value.
'''
这也就是说,官方已经把进度条内置到ProgbarLogger
这个类里面了,并通过callbacks
调用。
其中,对于callbacks
是这么调用的:
callbacks.on_train_begin()
而这个on_train_begin
又是属于CallbackList
类中:
# in CallbackList class
def on_train_begin(self, logs=None):
"""Calls the `on_train_begin` methods of its callbacks.
Args:
logs: Dict. Currently no data is passed to this argument for this method
but that may change in the future.
"""
logs = self._process_logs(logs)
for callback in self.callbacks:
callback.on_train_begin(logs)
也就是说,是遍历callbacks
中的所有callback
然后一一执行。
执行过程的源码在Callback
类中(类名跟上一个不一样哦):
# in Callback class
@doc_controls.for_subclass_implementers
def on_train_begin(self, logs=None):
"""Called at the beginning of training.
Subclasses should override for any actions to run.
Args:
logs: Dict. Currently no data is passed to this argument for this method
but that may change in the future.
"""
但很明显,这个源码就是想让我们自定义。
刚刚好发现进度条也在这里。那接下来的事情就更明确了,去找这个类就行了。一方面是查看进度条的原理,另一方面则是按照官方的进度条仿写一个简单的进度条。
官方的内置Callback
于是就找到了tensorflow/tensorflow/python/keras/callbacks.py
文件中,也就是官方GitHub的这一页(点击直达ProgbarLogger类定义的那一行)。但比较可惜的是,这个类定义的时候注释太少了,并不能很确定每个类中的各个方法都在做什么。怎么办呢?
别忘了官方给的提示:【内置了ProgbarLogger
类与History
类】。我们既然需要了解如何在Callback
里面调用,那么就需要了解这两个东西是如何插入进去的。但是这个类注释实在是太少了,很多东西看得不明不白的,该怎么办呢?
当然是【贪心搜索】了呀,按照【命名】与【个人经验】去判断哪一个最可能是我们想要的方法。虽然很不靠谱,但是万一运气好撞上了呢?于是呢,就找到了_add_default_callbacks
方法,也就是默认插入的一些东西。他是这么写的:
def _add_default_callbacks(self, add_history, add_progbar):
"""Adds `Callback`s that are always present."""
self._progbar = None
self._history = None
for cb in self.callbacks:
if isinstance(cb, ProgbarLogger):
self._progbar = cb
elif isinstance(cb, History):
self._history = cb
if self._progbar is None and add_progbar:
self._progbar = ProgbarLogger(count_mode='steps')
self.callbacks.insert(0, self._progbar)
if self._history is None and add_history:
self._history = History()
self.callbacks.append(self._history)
总之就是一些判断,如果空就创建。
看来插入就是insert
方法与append
方法了。不难猜测,也不用猜测,callbacks
将是一个数组。
官方进度条
既然知道了进度条是如何被调用的,那么接下来就是得了解官方的进度条是怎么添加的。
当然,还是在ProgbarLogger
类中,为了方便点击这里就能传送。这里面明显的给出了输出Epoch
,正好就是我们需要找到的输出。源码是这样的:
def on_epoch_begin(self, epoch, logs=None):
self._reset_progbar()
self._maybe_init_progbar()
if self.verbose and self.epochs > 1:
print('Epoch %d/%d' % (epoch + 1, self.epochs))
而且条件是需要verbose
非
0
0
0。怪不得我们把verbose
置为
0
0
0就什么都没有了。
简单的猜测与简单的验证
既然官方这么设计能够输出,那么我们也就简单的提出一个想法:
我们首先需要定义一个类A
,然后像这个ProgbarLogger
类一样继承自Callback
,然后再自定义on_epoch_begin
或者on_epoch_end
方法。这个方法需要具有三个参数:
- 首先是
on_epoch_begin
方法作为A
类一个成员的self
- 其次是与
ProgbarLogger
的on_epoch_end
方法一样传入epoch
变量,从而获取到当前学习过程进行到哪一个epoch
中了 - 最后就是一个暂时是
None
的logs
变量
那么,如何去验证呢?如果我们有一定的Java
基础的话,那么我们其实大概可以猜出来,所有的东西都是有一个接口可以实现,或者一个抽象类可以继承。那么Tensorflow
这种超大体量的框架也大概需要借鉴这种设计思想,否则很多东西都会乱糟糟的,没有一个统一的规范。所以,我们寻找一下有没有这类东西。
当然,我最终也是找到了:其实就是Callback
类,他的注释是:
Abstract base class used to build new callbacks.
其中的on_epoch_begin
、on_epoch_end
、on_train_begin
、on_train_end
等方法都是可以让子类实现的。
当然,在这里官方也很贴心的给了一个例子:
'''
Example:
>>> training_finished = False
>>> class MyCallback(tf.keras.callbacks.Callback):
... def on_train_end(self, logs=None):
... global training_finished
... training_finished = True
>>> model = tf.keras.Sequential([tf.keras.layers.Dense(1, input_shape=(1,))])
>>> model.compile(loss='mean_squared_error')
>>> model.fit(tf.constant([[1.0]]), tf.constant([[1.0]]),
... callbacks=[MyCallback()])
>>> assert training_finished == True
'''
看来我们的猜想是正确的。
这么一想我这找了这么久都是没用的吗
拼图凑齐了!
找了这么久,我们所需要了解的一切就都明白了。
那就自定义一个进度条:
import tensorflow as tf
class TensorflowProgressBar(tf.keras.callbacks.Callback):
def on_epoch_end(self, epoch, logs = None):
print('\r', f'Now Processing: {epoch}, Progress: {round(epoch / EPOCHS * 100, 2)}%',
end = '', flush = True)
这样的话,每当一个epoch
结束的时候,就会显示当前是第几个epoch
,并计算出当前进度的百分比。
这样的话就能在等结果的过程中做点别的事情,顺便时不时抬头看一眼进度。当老板问到的时候,随便看一下百分比就能报告,非常方便。
最后,在fit
方法里调用一下:
record = model.fit(X_train, y_train,
batch_size = BATCH_SIZE, epochs = EPOCHS,
callbacks=[TensorflowProgressBar()], verbose = 0)
其中,verbose
置为
1
1
1的话,Tensorflow
还会继续输出大量的进度,这是我们并不想看到的。所以为了让他只输出我们想要看到的进度,就必须将verbose
置为
0
0
0。