Tensorflow:具有非负约束的线性回归
Tensorflow: Linear regression with non-negative constraints
我正在尝试在 Tensorflow 中实现线性回归模型,附加约束(来自领域)W
和 b
项必须是非负的。
我相信有几种方法可以做到这一点。
- 我们可以修改代价函数来惩罚负权重[拉格朗日方法][参见:TensorFlow - best way to implement weight constraints
- 我们可以自己计算梯度并将它们投影到 [0, infinity] [投影梯度方法]
方法 1:拉格朗日
当我尝试第一种方法时,我经常会得到否定 b
。
我修改了代价函数:
cost = tf.reduce_sum(tf.pow(pred-Y, 2))/(2*n_samples)
至:
cost = tf.reduce_sum(tf.pow(pred-Y, 2))/(2*n_samples)
nn_w = tf.reduce_sum(tf.abs(W) - W)
nn_b = tf.reduce_sum(tf.abs(b) - b)
constraint = 100.0*nn_w + 100*nn_b
cost_with_constraint = cost + constraint
保持 nn_b
和 nn_w
的系数非常高会导致不稳定和非常高的成本。
这是完整的代码。
import numpy as np
import tensorflow as tf
n_samples = 50
train_X = np.linspace(1, 50, n_samples)
train_Y = 10*train_X + 6 +40*np.random.randn(50)
X = tf.placeholder("float")
Y = tf.placeholder("float")
# Set model weights
W = tf.Variable(np.random.randn(), name="weight")
b = tf.Variable(np.random.randn(), name="bias")
# Construct a linear model
pred = tf.add(tf.multiply(X, W), b)
# Gradient descent
learning_rate=0.0001
# Initializing the variables
init = tf.global_variables_initializer()
# Mean squared error
cost = tf.reduce_sum(tf.pow(pred-Y, 2))/(2*n_samples)
nn_w = tf.reduce_sum(tf.abs(W) - W)
nn_b = tf.reduce_sum(tf.abs(b) - b)
constraint = 1.0*nn_w + 100*nn_b
cost_with_constraint = cost + constraint
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost_with_constraint)
training_epochs=200
with tf.Session() as sess:
sess.run(init)
# Fit all training data
cost_array = np.zeros(training_epochs)
W_array = np.zeros(training_epochs)
b_array = np.zeros(training_epochs)
for epoch in range(training_epochs):
for (x, y) in zip(train_X, train_Y):
sess.run(optimizer, feed_dict={X: x, Y: y})
W_array[epoch] = sess.run(W)
b_array[epoch] = sess.run(b)
cost_array[epoch] = sess.run(cost, feed_dict={X: train_X, Y: train_Y})
以下是 b
在 10 个不同的 运行 中的平均值。
0 -1.101268
1 0.169225
2 0.158363
3 0.706270
4 -0.371205
5 0.244424
6 1.312516
7 -0.069609
8 -1.032187
9 -1.711668
显然,第一种方法不是最优的。此外,选择惩罚项的系数涉及很多艺术。
方法 2:投影梯度
然后我想用第二种方法,这种方法更有保障。
gr = tf.gradients(cost, [W, b])
我们手动计算梯度并更新 W 和 b。
with tf.Session() as sess:
sess.run(init)
for epoch in range(training_epochs):
for (x, y) in zip(train_X, train_Y):
W_del, b_del = sess.run(gr, feed_dict={X: x, Y: y})
W = max(0, (W - W_del)*learning_rate) #Project the gradient on [0, infinity]
b = max(0, (b - b_del)*learning_rate) # Project the gradient on [0, infinity]
这个方法好像很慢。
我想知道是否有更好的方法运行第二种方法,或者第一种方法保证结果。我们能否以某种方式让优化器确保学习到的权重是非负的?
编辑:如何在 Autograd 中执行此操作
我实际上无法用你的第一种方法重现你得到负面 b
s 的问题。
但我同意这不是您的用例的最佳选择,可能会导致负值。
您应该能够将参数限制为非负值,如下所示:
W *= tf.cast(W > 0., tf.float32)
b *= tf.cast(b > 0., tf.float32)
(如有必要,将 >
与 >=
交换,强制转换是必要的,因为比较运算符将产生布尔值。
然后,您将在没有额外约束的情况下针对 "standard cost" 进行优化。
但是,这并非在所有情况下都有效。例如W
或b
开头应避免初始化为负值
你的第二种(可能更好)方法可以通过在通用计算图中定义更新逻辑来加速,即在 cost
的定义之后
params = [W, b]
grads = tf.gradients(cost, params)
optimizer = [tf.assign(param, tf.maximum(0., param - grad*learning_rate))
for param, grad in zip(params, grads)]
我认为你的解决方案很慢,因为它每次都会创建新的计算节点,这可能非常昂贵并且在循环中重复了很多次。
使用 tensorflow 优化器更新
在我上面的解决方案中,不是渐变被裁剪而是生成的更新值。
沿着 this answer 的路线,您可以将梯度裁剪为最多更新参数的值,如下所示:
params = [W, b]
opt = tf.train.GradientDescentOptimizer(learning_rate)
grads_and_vars = opt.compute_gradients(cost, params)
clipped_grads_vars = [(tf.clip_by_value(grad, -np.inf, var), var) for grad, var in grads_and_vars]
optimizer = opt.apply_gradients(clipped_grads_vars)
这样更新永远不会将参数值降低到 0 以下。
但是,我认为这在更新变量已经为负的情况下不起作用。
此外,如果优化算法以某种方式将剪切梯度乘以大于 1 的值。
后者可能实际上永远不会发生,但我不是 100% 确定。
如果将线性模型修改为:
pred = tf.add(tf.multiply(X, tf.abs(W)), tf.abs(b))
这将与仅使用正 W 和 b 值具有相同的效果。
第二种方法速度慢的原因是您将 W 和 b 值剪裁在张量流图之外。 (它也不会收敛,因为 (W - W_del)*learning_rate
必须改为 W - W_del*learning_rate
)
编辑:
您可以像这样使用张量流图实现裁剪:
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)
with tf.control_dependencies([train_step]):
clip_W = W.assign(tf.maximum(0., W))
clip_b = b.assign(tf.maximum(0., b))
train_step_with_clip = tf.group(clip_W, clip_b)
在这种情况下,W 和 b 值将被裁剪为 0 而不是小的正数。
这是一个带裁剪的小 mnist 示例:
import tensorflow as tf
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x = tf.placeholder(tf.uint8, [None, 28, 28])
x_vec = tf.cast(tf.reshape(x, [-1, 784]), tf.float32) / 255.
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
y = tf.matmul(x_vec, W) + b
y_target = tf.placeholder(tf.uint8, [None])
y_target_one_hot = tf.one_hot(y_target, 10)
cross_entropy = tf.reduce_mean(
tf.nn.softmax_cross_entropy_with_logits(labels=y_target_one_hot, logits=y))
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
with tf.control_dependencies([train_step]):
clip_W = W.assign(tf.maximum(0., W))
clip_b = b.assign(tf.maximum(0., b))
train_step_with_clip = tf.group(clip_W, clip_b)
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_target_one_hot, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
with tf.Session() as sess:
tf.global_variables_initializer().run()
for i in range(1000):
sess.run(train_step_with_clip, feed_dict={
x: x_train[(i*100)%len(x_train):((i+1)*100)%len(x_train)],
y_target: y_train[(i*100)%len(x_train):((i+1)*100)%len(x_train)]})
if not i%100:
print("Min_W:", sess.run(tf.reduce_min(W)))
print("Min_b:", sess.run(tf.reduce_min(b)))
print("Accuracy:", sess.run(accuracy, feed_dict={
x: x_test,
y_target: y_test}))
我正在尝试在 Tensorflow 中实现线性回归模型,附加约束(来自领域)W
和 b
项必须是非负的。
我相信有几种方法可以做到这一点。
- 我们可以修改代价函数来惩罚负权重[拉格朗日方法][参见:TensorFlow - best way to implement weight constraints
- 我们可以自己计算梯度并将它们投影到 [0, infinity] [投影梯度方法]
方法 1:拉格朗日
当我尝试第一种方法时,我经常会得到否定 b
。
我修改了代价函数:
cost = tf.reduce_sum(tf.pow(pred-Y, 2))/(2*n_samples)
至:
cost = tf.reduce_sum(tf.pow(pred-Y, 2))/(2*n_samples)
nn_w = tf.reduce_sum(tf.abs(W) - W)
nn_b = tf.reduce_sum(tf.abs(b) - b)
constraint = 100.0*nn_w + 100*nn_b
cost_with_constraint = cost + constraint
保持 nn_b
和 nn_w
的系数非常高会导致不稳定和非常高的成本。
这是完整的代码。
import numpy as np
import tensorflow as tf
n_samples = 50
train_X = np.linspace(1, 50, n_samples)
train_Y = 10*train_X + 6 +40*np.random.randn(50)
X = tf.placeholder("float")
Y = tf.placeholder("float")
# Set model weights
W = tf.Variable(np.random.randn(), name="weight")
b = tf.Variable(np.random.randn(), name="bias")
# Construct a linear model
pred = tf.add(tf.multiply(X, W), b)
# Gradient descent
learning_rate=0.0001
# Initializing the variables
init = tf.global_variables_initializer()
# Mean squared error
cost = tf.reduce_sum(tf.pow(pred-Y, 2))/(2*n_samples)
nn_w = tf.reduce_sum(tf.abs(W) - W)
nn_b = tf.reduce_sum(tf.abs(b) - b)
constraint = 1.0*nn_w + 100*nn_b
cost_with_constraint = cost + constraint
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost_with_constraint)
training_epochs=200
with tf.Session() as sess:
sess.run(init)
# Fit all training data
cost_array = np.zeros(training_epochs)
W_array = np.zeros(training_epochs)
b_array = np.zeros(training_epochs)
for epoch in range(training_epochs):
for (x, y) in zip(train_X, train_Y):
sess.run(optimizer, feed_dict={X: x, Y: y})
W_array[epoch] = sess.run(W)
b_array[epoch] = sess.run(b)
cost_array[epoch] = sess.run(cost, feed_dict={X: train_X, Y: train_Y})
以下是 b
在 10 个不同的 运行 中的平均值。
0 -1.101268
1 0.169225
2 0.158363
3 0.706270
4 -0.371205
5 0.244424
6 1.312516
7 -0.069609
8 -1.032187
9 -1.711668
显然,第一种方法不是最优的。此外,选择惩罚项的系数涉及很多艺术。
方法 2:投影梯度
然后我想用第二种方法,这种方法更有保障。
gr = tf.gradients(cost, [W, b])
我们手动计算梯度并更新 W 和 b。
with tf.Session() as sess:
sess.run(init)
for epoch in range(training_epochs):
for (x, y) in zip(train_X, train_Y):
W_del, b_del = sess.run(gr, feed_dict={X: x, Y: y})
W = max(0, (W - W_del)*learning_rate) #Project the gradient on [0, infinity]
b = max(0, (b - b_del)*learning_rate) # Project the gradient on [0, infinity]
这个方法好像很慢。
我想知道是否有更好的方法运行第二种方法,或者第一种方法保证结果。我们能否以某种方式让优化器确保学习到的权重是非负的?
编辑:如何在 Autograd 中执行此操作
我实际上无法用你的第一种方法重现你得到负面 b
s 的问题。
但我同意这不是您的用例的最佳选择,可能会导致负值。
您应该能够将参数限制为非负值,如下所示:
W *= tf.cast(W > 0., tf.float32)
b *= tf.cast(b > 0., tf.float32)
(如有必要,将 >
与 >=
交换,强制转换是必要的,因为比较运算符将产生布尔值。
然后,您将在没有额外约束的情况下针对 "standard cost" 进行优化。
但是,这并非在所有情况下都有效。例如W
或b
开头应避免初始化为负值
你的第二种(可能更好)方法可以通过在通用计算图中定义更新逻辑来加速,即在 cost
params = [W, b]
grads = tf.gradients(cost, params)
optimizer = [tf.assign(param, tf.maximum(0., param - grad*learning_rate))
for param, grad in zip(params, grads)]
我认为你的解决方案很慢,因为它每次都会创建新的计算节点,这可能非常昂贵并且在循环中重复了很多次。
使用 tensorflow 优化器更新
在我上面的解决方案中,不是渐变被裁剪而是生成的更新值。 沿着 this answer 的路线,您可以将梯度裁剪为最多更新参数的值,如下所示:
params = [W, b]
opt = tf.train.GradientDescentOptimizer(learning_rate)
grads_and_vars = opt.compute_gradients(cost, params)
clipped_grads_vars = [(tf.clip_by_value(grad, -np.inf, var), var) for grad, var in grads_and_vars]
optimizer = opt.apply_gradients(clipped_grads_vars)
这样更新永远不会将参数值降低到 0 以下。 但是,我认为这在更新变量已经为负的情况下不起作用。 此外,如果优化算法以某种方式将剪切梯度乘以大于 1 的值。 后者可能实际上永远不会发生,但我不是 100% 确定。
如果将线性模型修改为:
pred = tf.add(tf.multiply(X, tf.abs(W)), tf.abs(b))
这将与仅使用正 W 和 b 值具有相同的效果。
第二种方法速度慢的原因是您将 W 和 b 值剪裁在张量流图之外。 (它也不会收敛,因为 (W - W_del)*learning_rate
必须改为 W - W_del*learning_rate
)
编辑:
您可以像这样使用张量流图实现裁剪:
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)
with tf.control_dependencies([train_step]):
clip_W = W.assign(tf.maximum(0., W))
clip_b = b.assign(tf.maximum(0., b))
train_step_with_clip = tf.group(clip_W, clip_b)
在这种情况下,W 和 b 值将被裁剪为 0 而不是小的正数。
这是一个带裁剪的小 mnist 示例:
import tensorflow as tf
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x = tf.placeholder(tf.uint8, [None, 28, 28])
x_vec = tf.cast(tf.reshape(x, [-1, 784]), tf.float32) / 255.
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
y = tf.matmul(x_vec, W) + b
y_target = tf.placeholder(tf.uint8, [None])
y_target_one_hot = tf.one_hot(y_target, 10)
cross_entropy = tf.reduce_mean(
tf.nn.softmax_cross_entropy_with_logits(labels=y_target_one_hot, logits=y))
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
with tf.control_dependencies([train_step]):
clip_W = W.assign(tf.maximum(0., W))
clip_b = b.assign(tf.maximum(0., b))
train_step_with_clip = tf.group(clip_W, clip_b)
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_target_one_hot, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
with tf.Session() as sess:
tf.global_variables_initializer().run()
for i in range(1000):
sess.run(train_step_with_clip, feed_dict={
x: x_train[(i*100)%len(x_train):((i+1)*100)%len(x_train)],
y_target: y_train[(i*100)%len(x_train):((i+1)*100)%len(x_train)]})
if not i%100:
print("Min_W:", sess.run(tf.reduce_min(W)))
print("Min_b:", sess.run(tf.reduce_min(b)))
print("Accuracy:", sess.run(accuracy, feed_dict={
x: x_test,
y_target: y_test}))