人工智能小白日记之13 ML学习篇之9稀疏性正则化 Regularization for Sparsity
前言
老实说,这篇我看的有点抽象,就像之前的L2正则化,只知道执行它可以使得权重接近于0,越小该项的影响力越小,在避免过拟合这样的问题或者“去噪”这样的问题上非常有利,但是其原理是什么呢,一脸懵逼。不过摸石头过河的过程就是这样的,太多的不明确,走一步算一步,不然花太多时间可能效率会低,先记住结论然后应用就行了。
课程内容
1 稀疏特征的组合
这是视频里提到的,里面举了个例子,这个例子超级形象666。如果我们想训练一个模型,想找出视频库中包含某些词汇的某种视频。这里涉及到两个特征:某个特征是搜索查询中的字词, 另一个特征是需要查询的独特视频。如果有数百万个字词和数百万个视频,cross一下结果是很庞大的,会消耗很多内存。而且里面会产生一些很罕见的组合,这种组合就是之前说的噪音,没有价值,需要“去噪”,这时候就需要正则化了。
2 再聊正则化
关于上面的问题,为了达到去噪的目的,最好是噪音的权重都是0,文中引出了三种正则化:
L2正则化:执行 L2 正则化可以使权重变小,但是并不能使它们正好为 0.0
L0 正则化:尝试创建一个正则化项,减少模型中的非零系数值的计数。只有在模型能够与数据拟合时增加此计数才有意义。遗憾的是,虽然这种基于计数的方法看起来很有吸引力,但它会将我们的凸优化问题变为非凸优化问题,即 NP 困难。
L1正则化:L1 正则化这种正则化项的作用类似 L0,但它具有凸优化的优势,可有效进行计算。因此,我们可以使用 L1 正则化使模型中很多信息缺乏的系数正好为 0,从而在推理时节省 RAM。
ps:看完之后是不是一脸懵逼,所以我果断放弃了研究L0和L1这两句话里的含义。去外面papa 资料,https://blog.csdn.net/qq_29133371/article/details/54894415 ,比如这么一篇。虽然还是不太明白,至少大概有点了解了。比较重要的几段话如下:
“
L0范数是指向量中非0的元素的个数。如果我们用L0范数来规则化一个参数矩阵W的话,就是希望W的大部分元素都是0。换句话说,让参数W是稀疏的。
L1范数是指向量中各个元素绝对值之和,也有个美称叫“稀疏规则算子”(Lasso regularization)。
既然L0可以实现稀疏,为什么不用L0,而要用L1呢?个人理解一是因为L0范数很难优化求解(NP难问题),二是L1范数是L0范数的最优凸近似,而且它比L0范数要容易优化求解。所以大家才把目光和万千宠爱转于L1范数。“
3 L1 和 L2 正则化
L2 和 L1 采用不同的方式降低权重:
- L2 会降低权重2。
- L1 会降低 |权重|。
因此,L2 和 L1 具有不同的导数:
- L2 的导数为 2 * 权重。
- L1 的导数为 k(一个常数,其值与权重无关)。
后面强行解释了一波,为何L2正则化无法使权重刚好为0,而L1正则化可以,不过我没看明白,直接看结果吧?:
4 练习
本练习包含一个有些混乱的小型训练数据集。在这种情况下,过拟合问题比较令人担忧。 正则化可能会有所帮助,但采用哪种形式的正则化呢?
本练习包含 5 个相关的任务。为了简化这 5 个任务之间的比较,请在单独的标签中运行每个任务。 请注意,连接 FEATURES 和 OUTPUT 的线的厚度表示每个特征的相对权重。
我们先来跑下结果:
看下问题:
1)从 L2 正则化转换到 L1 正则化对测试损失与训练损失之间的差值有何影响?
可以看出,L1的训练损失更高,但是测试损失更小了。而且测试损失与训练损失之间的差值也减小了。
2)从 L2 正则化转换到 L1 正则化对已知权重有何影响?
这题不好截图比较,最后结论是,从 L2 正则化转换到 L1 正则化之后,所有已知权重都有所减少。
3)增加 L1 正则化率 (lambda) 对已知权重有何影响?
这题需要进行测试,在L1的情况下不断加大lambda,结果就是前面lambda增加可以减小已知权重,但时候太高的时候,不能模型收敛了,直接废废了。
5 编程练习
进入指定练习
降低复杂性的一种方法是使用正则化函数,它会使权重正好为零。对于线性模型(例如线性回归),权重为零就相当于完全没有使用相应特征。除了可避免过拟合之外,生成的模型还会更加有效。
L1 正则化是一种增加稀疏性的好方法。
5-1 前戏
前戏跟上节中逻辑回归实战是一样的,包括了导入数据,指定特征,还有线性回归转化为逻辑回归的那个标签也在。后面定义特征列就有点不一样了:
def get_quantile_based_buckets(feature_values, num_buckets):
quantiles = feature_values.quantile(
[(i+1.)/(num_buckets + 1.) for i in range(num_buckets)])
return [quantiles[q] for q in quantiles.keys()]
def construct_feature_columns():
"""Construct the TensorFlow Feature Columns.
Returns:
A set of feature columns
"""
bucketized_households = tf.feature_column.bucketized_column(
tf.feature_column.numeric_column("households"),
boundaries=get_quantile_based_buckets(training_examples["households"], 10))
bucketized_longitude = tf.feature_column.bucketized_column(
tf.feature_column.numeric_column("longitude"),
boundaries=get_quantile_based_buckets(training_examples["longitude"], 50))
bucketized_latitude = tf.feature_column.bucketized_column(
tf.feature_column.numeric_column("latitude"),
boundaries=get_quantile_based_buckets(training_examples["latitude"], 50))
bucketized_housing_median_age = tf.feature_column.bucketized_column(
tf.feature_column.numeric_column("housing_median_age"),
boundaries=get_quantile_based_buckets(
training_examples["housing_median_age"], 10))
bucketized_total_rooms = tf.feature_column.bucketized_column(
tf.feature_column.numeric_column("total_rooms"),
boundaries=get_quantile_based_buckets(training_examples["total_rooms"], 10))
bucketized_total_bedrooms = tf.feature_column.bucketized_column(
tf.feature_column.numeric_column("total_bedrooms"),
boundaries=get_quantile_based_buckets(training_examples["total_bedrooms"], 10))
bucketized_population = tf.feature_column.bucketized_column(
tf.feature_column.numeric_column("population"),
boundaries=get_quantile_based_buckets(training_examples["population"], 10))
bucketized_median_income = tf.feature_column.bucketized_column(
tf.feature_column.numeric_column("median_income"),
boundaries=get_quantile_based_buckets(training_examples["median_income"], 10))
bucketized_rooms_per_person = tf.feature_column.bucketized_column(
tf.feature_column.numeric_column("rooms_per_person"),
boundaries=get_quantile_based_buckets(
training_examples["rooms_per_person"], 10))
long_x_lat = tf.feature_column.crossed_column(
set([bucketized_longitude, bucketized_latitude]), hash_bucket_size=1000)
feature_columns = set([
long_x_lat,
bucketized_longitude,
bucketized_latitude,
bucketized_housing_median_age,
bucketized_total_rooms,
bucketized_total_bedrooms,
bucketized_population,
bucketized_households,
bucketized_median_income,
bucketized_rooms_per_person])
return feature_columns
ps:这里在干啥?前面见过了哈,包括了分桶操作,get_quantile_based_buckets函数则是用来计算分桶边界;还有long_x_lat = tf.feature_column.crossed_column,这里在将经纬度进行cross,所谓特征组合。
5-2 减小模型大小
您的团队需要针对 SmartRing 构建一个准确度高的逻辑回归模型,这种指环非常智能,可以感应城市街区的人口统计特征(median_income
、avg_rooms
、households
等等),并告诉您指定城市街区的住房成本是否高昂。
由于 SmartRing 很小,因此工程团队已确定它只能处理参数数量不超过 600 个的模型。另一方面,产品管理团队也已确定,除非所保留测试集的对数损失函数低于 0.35,否则该模型不能发布。
您可以使用秘密武器“L1 正则化”调整模型,使其同时满足大小和准确率限制条件吗?
这里提供了计算模型大小的方法
def model_size(estimator):
variables = estimator.get_variable_names()
size = 0
for variable in variables:
if not any(x in variable
for x in ['global_step',
'centered_bias_weight',
'bias_weight',
'Ftrl']
):
size += np.count_nonzero(estimator.get_variable_value(variable))
return size
5-3 任务 1:查找合适的正则化系数。
查找可同时满足以下两种限制条件的 L1 正则化强度参数:模型的参数数量不超过 600 个且验证集的对数损失函数低于 0.35。
以下代码可帮助您快速开始。您可以通过多种方法向您的模型应用正则化。在此练习中,我们选择使用 FtrlOptimizer
来应用正则化。FtrlOptimizer
是一种设计成使用 L1 正则化比标准梯度下降法得到更好结果的方法。
重申一次,我们会使用整个数据集来训练该模型,因此预计其运行速度会比通常要慢。
def train_linear_classifier_model(
learning_rate,
regularization_strength,
steps,
batch_size,
feature_columns,
training_examples,
training_targets,
validation_examples,
validation_targets):
"""Trains a linear regression model.
In addition to training, this function also prints training progress information,
as well as a plot of the training and validation loss over time.
Args:
learning_rate: A `float`, the learning rate.
regularization_strength: A `float` that indicates the strength of the L1
regularization. A value of `0.0` means no regularization.
steps: A non-zero `int`, the total number of training steps. A training step
consists of a forward and backward pass using a single batch.
feature_columns: A `set` specifying the input feature columns to use.
training_examples: A `DataFrame` containing one or more columns from
`california_housing_dataframe` to use as input features for training.
training_targets: A `DataFrame` containing exactly one column from
`california_housing_dataframe` to use as target for training.
validation_examples: A `DataFrame` containing one or more columns from
`california_housing_dataframe` to use as input features for validation.
validation_targets: A `DataFrame` containing exactly one column from
`california_housing_dataframe` to use as target for validation.
Returns:
A `LinearClassifier` object trained on the training data.
"""
periods = 7
steps_per_period = steps / periods
# Create a linear classifier object.
my_optimizer = tf.train.FtrlOptimizer(learning_rate=learning_rate, l1_regularization_strength=regularization_strength)
my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0)
linear_classifier = tf.estimator.LinearClassifier(
feature_columns=feature_columns,
optimizer=my_optimizer
)
# Create input functions.
training_input_fn = lambda: my_input_fn(training_examples,
training_targets["median_house_value_is_high"],
batch_size=batch_size)
predict_training_input_fn = lambda: my_input_fn(training_examples,
training_targets["median_house_value_is_high"],
num_epochs=1,
shuffle=False)
predict_validation_input_fn = lambda: my_input_fn(validation_examples,
validation_targets["median_house_value_is_high"],
num_epochs=1,
shuffle=False)
# Train the model, but do so inside a loop so that we can periodically assess
# loss metrics.
print("Training model...")
print("LogLoss (on validation data):")
training_log_losses = []
validation_log_losses = []
for period in range (0, periods):
# Train the model, starting from the prior state.
linear_classifier.train(
input_fn=training_input_fn,
steps=steps_per_period
)
# Take a break and compute predictions.
training_probabilities = linear_classifier.predict(input_fn=predict_training_input_fn)
training_probabilities = np.array([item['probabilities'] for item in training_probabilities])
validation_probabilities = linear_classifier.predict(input_fn=predict_validation_input_fn)
validation_probabilities = np.array([item['probabilities'] for item in validation_probabilities])
# Compute training and validation loss.
training_log_loss = metrics.log_loss(training_targets, training_probabilities)
validation_log_loss = metrics.log_loss(validation_targets, validation_probabilities)
# Occasionally print the current loss.
print(" period %02d : %0.2f" % (period, validation_log_loss))
# Add the loss metrics from this period to our list.
training_log_losses.append(training_log_loss)
validation_log_losses.append(validation_log_loss)
print("Model training finished.")
# Output a graph of loss metrics over periods.
plt.ylabel("LogLoss")
plt.xlabel("Periods")
plt.title("LogLoss vs. Periods")
plt.tight_layout()
plt.plot(training_log_losses, label="training")
plt.plot(validation_log_losses, label="validation")
plt.legend()
return linear_classifier
也给了训练模型的方法,最后是训练:
linear_classifier = train_linear_classifier_model(
learning_rate=0.1,
# TWEAK THE REGULARIZATION VALUE BELOW
regularization_strength=0.0,
steps=300,
batch_size=100,
feature_columns=construct_feature_columns(),
training_examples=training_examples,
training_targets=training_targets,
validation_examples=validation_examples,
validation_targets=validation_targets)
print("Model size:", model_size(linear_classifier))
运行一下:
可以看到LogLoss已经符合要求了,就是Model Size = 788 ,不满足条件,这时候我们调整下regularization_strength,正则化强度?这应该就是之前一直见过的正则率。
linear_classifier = train_linear_classifier_model(
learning_rate=0.1,
regularization_strength=0.6,
steps=300,
batch_size=100,
feature_columns=construct_feature_columns(),
training_examples=training_examples,
training_targets=training_targets,
validation_examples=validation_examples,
validation_targets=validation_targets)
print("Model size:", model_size(linear_classifier))
ps:这东西很难调,运行一次好久,所以这次正则率0.6的时候勉强达到要求,估计还要再大点,懒得调了。让我诧异的是官方答案是不是写错了,0.1?好吧,尴尬。然后logloss有点增大的赶脚,官方说过正则可能导致误差偏大是有道理的,因为大部分权重归0后,会导致某些因子没有参与训练,最终欠拟合。