Tensorflow:带非负约束的线性回归

5

我正在尝试在Tensorflow中实现线性回归模型,并加入额外的限制(来自领域),即Wb项必须为非负数。

我相信有几种方法可以做到这一点。

  1. 我们可以修改成本函数,以惩罚负权重[Lagrangian方法] [参见:TensorFlow-最佳实现权重约束的方法
  2. 我们可以自己计算梯度并将其投影到[0,无穷大] [投影梯度方法]

方法1:Lagrangian

当我尝试第一种方法时,我经常会得到负数的b

我已经从以下内容修改了成本函数:

cost = tf.reduce_sum(tf.pow(pred-Y, 2))/(2*n_samples)

to:

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

Here is the complete code.

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})

以下是在10个不同运行中 b 的平均值。
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

显然,第一种方法并不是最优的。此外,在选择惩罚项系数时需要有很多技巧。

方法二:投影梯度

然后我考虑使用第二种方法,这种方法更有保证可以奏效。

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中实现此操作

https://github.com/HIPS/autograd/issues/207


我没有读完所有内容,所以可能不适用于您,但如果您只想使用正的w,您可以尝试学习b的对数而不是b。实际上,这意味着在预测中使用exp(b)而不是b...然后有效偏差将始终>0。 - etarion
感谢@etarion的评论,Wb都应该是非负的。那么您建议我们学习Wb的对数吗?这不会改变Y = W.X + b吗? - Nipun Batra
2个回答

8
如果您修改线性模型如下:
pred = tf.add(tf.multiply(X, tf.abs(W)), tf.abs(b))

使用仅具有正值的W和b值将产生相同的效果。

你的第二种方法之所以慢是因为你在tensorflow图之外剪辑了W和b值。(另外,它不会收敛,因为(W - W_del)*learning_rate必须改为W - W_del*learning_rate

编辑:

你可以使用tensorflow图实现剪辑,像这样:

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}))

感谢您的回答。我遇到了以下错误:---> 11 optimizer_step = tf.identity(optimizer_step). TypeError: 无法将操作“GradientDescent_2”转换为张量(目标dtype = None,名称= u'input',as_ref = False)我认为如果您能提供第二种方法的最小完整示例,那将是非常好的。 - Nipun Batra
@Nipun Batra 是的,我在那里犯了一个错误,身份运算符只能用于张量。我编辑了答案,并提供了一个示例。 - BlueSun
谢谢。这个答案非常有用。我现在会更新我的问题,以反映出简单虚拟数据情况下的答案!我也授予了你赏金 :) - Nipun Batra

3

我并没有能够重现你在第一种方法中出现负数 b 的问题。

但我同意这对于你的使用情景来说并不是最佳解决方案,可能会导致出现负值。

你应该可以通过以下方式将参数限制为非负值:

W *= tf.cast(W > 0., tf.float32)
b *= tf.cast(b > 0., tf.float32)
< p >(如果需要,用>=代替>,强制转换是必要的,因为比较运算符将生成布尔值。然后,您应该针对“标准成本”进行优化,而不考虑额外的约束条件。但是,这并不适用于每种情况。例如,在开始时避免使用负值来初始化Wb。)

第二种方法(可能更好)可以通过在一般计算图中定义更新逻辑来加速,即在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优化器进行更新
在我的解决方案中,并不是裁剪梯度,而是裁剪结果更新值。仿照这个答案,您可以将梯度裁剪为最多为更新参数的值:
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的值。 后者可能实际上永远不会发生,但我并不百分之百确定。


谢谢。这似乎是达到所需解决方案的方法。但如果计算出的(和剪裁后的)梯度可以传递给优化算法,那将是非常好的。这样,优化器将负责适应学习率等问题。我还认为它可能会加速优化器代码的高效率。 - Nipun Batra
谢谢。我认为更有原则的方法是tf.assign(param, tf.maximum(0., param - grad*learning_rate))。如果你能提供一个完整的工作示例,那将非常有用。 - Nipun Batra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接