【问题标题】:How to enforce monotonicity for (regression) model outputs in Keras?如何在 Keras 中强制(回归)模型输出的单调性?
【发布时间】:2018-11-22 05:40:12
【问题描述】:

我目前正在研究一个问题,我为神经网络提供一个输入变量 a 和另一个输入 x 是一个单调递增的 N 个数序列。

所以我的网络基本上看起来像这样:

a_input = Input(shape=[1], name='a')
x_input = Input(shape=[N], name='x')
nn = concatenate([a_input, x_input])
nn = Dense(100, activation='relu')(nn)
nn = Dense(N, activation='relu')(nn)
model = Model(inputs=[a_input, x_input], outputs=[nn])
model.compile(loss='mean_squared_error', optimizer="adam")

我对输入空间执行回归(其中每个 a 序列 x 是唯一的) ,并且我希望网络为每组输入 a 输出一个单调递增的(非负)N 个数字序列>x

现在,我注意到到目前为止,我的输出严格来说并不是单调的,但如果你“缩小”,它们看起来就像是一样的。我的意思是,对于 ax 的给定选择,如果我希望我的输出数组看起来喜欢:

[0, 0.5, 0.51, 0.7, 0.75, 0.9, 1.], 

我可能会得到:

[0.001, 0.5, 0.48, 0.7, 0.75, 0.9, 1.].

因此,我想知道 Keras 中是否有标准方法或特定工具可以将模型限制为仅输出单调递增的序列?

【问题讨论】:

    标签: python keras deep-learning regression


    【解决方案1】:

    要强制执行非负输出,请在输出层中使用非负激活,例如 ReLU 或 sigmoid。

    我不知道任何 neural 方法可以在您的输出中强制执行单调性,但我认为一个明智的方法是更改​​输出表示以使网络预测两个连续元素之间的差异.例如,您可以转换输出数组:

    a=[0, 0.5, 0.51, 0.7, 0.75, 0.9, 1.]

    到:

    b=[0, 0.5, 0.01, 0.19, 0.05, 0.15, 0.1]

    b[0] = a[0]b[i] = a[i]-a[i-1] 用于 i>0。在这种情况下,使用循环层作为输出层是有意义的,因为每个输出单元现在都依赖于之前的输出单元。您的原始表示可以很容易地恢复为a[0] = b[0]a[i] = b[i]+a[i-1] for i>0,并且生成的序列将单调递增,因为每个输出b[i] 都是非负的。


    更新 1。 LSTM 应该返回完整的序列。您可以尝试如下构建模型:

    a_input = Input(shape=[1], name='a')
    x_input = Input(shape=[N], name='x')
    nn = concatenate([a_input, x_input])
    nn = Dense(100, activation='relu')(nn)
    nn = Dense(N, activation='relu')(nn)
    nn = Lambda(lambda x: x[..., None])(nn)  # Output shape=(batch_size, nb_timesteps=N, input_dim=1)
    nn = LSTM(1, return_sequences=True, activation='relu')(nn)  # Output shape=(batch_size, nb_timesteps=N, output_dim=1)
    nn = Lambda(lambda x: keras.backend.squeeze(x, axis=-1))(nn)  # Output shape=(batch_size, N)
    model = Model(inputs=[a_input, x_input], outputs=[nn])
    model.compile(loss='mean_squared_error', optimizer="adam")
    

    更新 2。具有一个隐藏单元的 LSTM 可能不够强大。我不确定这是否会有所帮助,但您可以尝试在最后一个之前添加另一个具有更多单元(即 10 个)的 LSTM 层:

    ...
    nn = Lambda(lambda x: x[..., None])(nn)  # Output shape=(batch_size, nb_timesteps=N, input_dim=1)
    nn = LSTM(10, return_sequences=True)(nn)  # Output shape=(batch_size, nb_timesteps=N, output_dim=10)
    nn = LSTM(1, return_sequences=True, activation='relu')(nn)  # Output shape=(batch_size, nb_timesteps=N, output_dim=1)
    nn = Lambda(lambda x: keras.backend.squeeze(x, axis=-1))(nn)  # Output shape=(batch_size, N)
    ...
    

    【讨论】:

    • 我喜欢你的想法并认为这对我来说很有意义,但需要注意的是我对使用 RNN 的经验很少。据我了解,您建议对于每个训练样本 x_train = {a, [x0, x1,..., xN]},我将 y_train 转换为 y_train' = {y_train[0], y_train[1]-y_train[0],...}?在构建模型时,只需将最后一个 Dense(N,activation='relu') 层(如原始帖子中的)更改为 LSTM(N,activation='relu/sigmoid') 就足够了吗?因此,例如,当在 keras 中使用 train_on_batch 时,我的输入和输出形状也将与我最初尝试时保持相同?
    • 对,这正是我的建议!我更新了我的答案,以展示如何使用 LSTM 保留输入和输出形状。
    • 感谢您提供极其方便的解决方案。我现在正在尝试,我注意到单调性已被严格执行! nn 输出不再准确地拟合所需的曲线,但我想我已经找到了一个原因 - 即我的 y_train 并不总是严格地从元素到元素(这里从左到右)而不是它有时看起来像这样:[0, 0.5, 0.5, 0.51, 0.7, 0.7, 0.7,0.75, 0.9, 1.]。你认为增加回顾的窗口(现在只增加一个元素)会有所帮助吗?还是在这种情况下可以使用任何其他基于 LSTM 的技巧?
    • 不客气!我不确定为什么输出不再准确地拟合曲线(我假设您正在撤消输出表示的转换)。为了清楚起见,LSTM 的第一个参数并不表示回溯的窗口,而是隐藏状态的维度。我用建议更新了我的答案
    • 是的,它肯定会撤消转换。到目前为止,链接 LSTM 层并不能解决问题,事实上,如果我增加所需序列中的元素数量,输出也会开始变为 nan(有和没有多个 LSTM 层)。但是,正如我上面提到的,我的数据中有很多情况下序列不是严格单调的(即 y[i] 可能等于 y[i+1]),也许这也是一个问题。跨度>
    猜你喜欢
    • 2019-04-08
    • 2018-12-14
    • 1970-01-01
    • 2021-04-19
    • 2018-08-06
    • 1970-01-01
    • 2016-09-30
    • 2022-01-12
    • 1970-01-01
    相关资源
    最近更新 更多