所以我还发现关于权重修剪的 Tensorflow 文档非常稀疏,所以我花了一些时间在调试器上弄清楚一切是如何工作的。
修剪计划的工作原理
在最基本的层面上,修剪计划只是一个将步骤作为输入并产生稀疏百分比的函数。然后使用该稀疏值生成一个掩码,该掩码用于修剪绝对值小于由绝对值权重分布和稀疏百分比给出的 k - 1 值的权重。
多项式衰减
类定义:Github Link
上面类定义中包含的 cmets 帮助我理解了 PolynomialDecay 调度器的工作原理。
剪枝率从 initial_sparsity 开始快速增长,但随后
缓慢稳定到目标稀疏度。
应用的函数是
current_sparsity = final_sparsity + (initial_sparsity - final_sparsity)
* (1 - (step - begin_step)/(end_step - begin_step)) ^ 指数
通过上述等式,当step == begin_step 时,则current_sparsity = initial_sparsity。因此,权重将在begin_step 参数指定的步骤上被修剪为initial_sparsity。
我同意您的评估,因为您通常希望以低于 50% 的稀疏度开始修剪,但我没有任何已发表的研究可以引用来支持该主张。您可以在 PolynomialDecay 类定义中引用的 paper 中找到更多信息,尽管我自己没有机会阅读。
恒定稀疏度
类定义:Github Link
这个调度器的目的似乎非常有限。对于每个有效的修剪步骤,都会返回 target_sparsity。因此,多个修剪步骤非常多余。此调度程序的用例似乎是在训练期间进行一次修剪。使用此调度程序进行多次修剪的能力是将其与其父抽象类和其他修剪调度程序对齐。
创建自己的修剪调度程序
如果上面的两个调度器没有让你的船浮起来,抽象类PruningSchedule 暴露了一个端点,这使得创建你自己的修剪调度器变得非常容易,尽管它可能很复杂。以下是我自己创建的示例。
免责声明:这个调度器是一个 19 岁大学生的想象的创造,没有任何已发表的文献依据。
PruningSchedule = tfmot.sparsity.keras.PruningSchedule
class ExponentialPruning(PruningSchedule):
def __init__(self, rate=0.01, begin_step=0, frequency=100, max_sparsity=0.9):
self.rate = rate
self.begin_step = begin_step
self.frequency = frequency
self.max_sparsity = max_sparsity
# Validation functions provided by the parent class
# The -1 parameter is for the end_step
# as this pruning schedule does not have one
# The last true value is a boolean flag which says it is okay
# to have no end_step
self._validate_step(self.begin_step, -1, self.frequency, True)
self._validate_sparsity(self.max_sparsity, 'Max Sparsity')
def __call__(self, step):
# Sparsity calculation endpoint
# step is a integer tensor
# The sparsity returned by __call__ must be a tensor
# of dtype=tf.float32, so tf.math is required.
# In the logic below, you can assume that a valid
# pruning step is passed.
p = tf.math.divide(
tf.cast(step - self.begin_step, tf.float32),
tf.constant(self.frequency, dtype=tf.float32)
)
sparsity = tf.math.subtract(
tf.constant(1, dtype=tf.float32),
tf.math.pow(
tf.constant(1 - self.rate, dtype=tf.float32),
p
)
)
sparsity = tf.cond(
tf.math.greater(sparsity, tf.constant(self.max_sparsity, dtype=tf.float32)),
lambda: tf.constant(self.max_sparsity, dtype=tf.float32),
lambda: sparsity
)
# This function returns a tuple of length 2
# The first value determines if pruning should occur on this step
# I recommend using the parent class function below for this purpose
# The negative one value denotes no end_step
# The second value is the sparsity to prune to
return (self._should_prune_in_step(step, self.begin_step, -1, self.frequency),
sparsity)
def get_config(self):
# A function required by the parent class
# return the class_name and the input parameters as
# done below
return {
'class_name': self.__class__.__name__,
'config': {
'rate': self.rate,
'begin_step': self.begin_step,
'frequency': self.frequency,
'max_sparsity': self.max_sparsity
}
}
使用修剪调度程序
如果您只想修剪某些层,而不是所有可修剪的层,您可以在要添加到模型的层上调用 prune_low_magnitude 函数。
prune_low_magnitude = tfmot.sparsity.keras.prune_low_magnitude
model = keras.models.Sequential()
...
model.add(prune_low_magnitude(keras.layers.Dense(8, activation='relu', kernel_regularizer=keras.regularizers.l1(0.0001)),
ExponentialPruning(rate=1/8)))
还要确保将UpdatePruningStep 实例传递给训练回调:
m.fit(train_input, train_labels, epochs=epochs, validation_data=[test_input, test_labels],
callbacks=[UpdatePruningStep()])