如何向神经网络教授抛物线函数

How to teach a parabolic function to a neural net

我的目标是建立一个具有两个神经元的顺序神经网络,该神经元能够再现二次函数。为此,我选择第一个神经元的激活函数为 lambda x: x**2,第二个神经元的激活函数为 None.

每个神经元输出 A(ax+b),其中 A 是激活函数,a 是给定神经元的权重,b 是偏置项。第一个神经元的输出传递给第二个神经元,那个神经元的输出就是结果。

那么我的网络输出的形式是:

训练模型就是调整每个神经元的权重和偏差。选择一组非常简单的参数,即:

将我们引向一条抛物线,上面描述的 2 神经元神经网络应该可以完全学习它:

为了实现神经网络,我这样做:

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

定义要学习的函数:

f = lambda x: x**2 + 2*x + 2

使用上述函数生成训练输入和输出:

np.random.seed(42)
questions = np.random.rand(999)
solutions = f(questions)

定义神经网络架构:

model = tf.keras.Sequential([
  tf.keras.layers.Dense(units=1, input_shape=[1],activation=lambda x: x**2),
  tf.keras.layers.Dense(units=1, input_shape=[1],activation=None)
])

编译网:

model.compile(loss='mean_squared_error',
              optimizer=tf.keras.optimizers.Adam(0.1))

训练模型:

history = model.fit(questions, solutions, epochs=999, batch_size = 1, verbose=1)

使用新训练的模型生成 f(x) 的预测:

np.random.seed(43)
test_questions = np.random.rand(100)
test_solutions = f(test_questions)

test_answers = model.predict(test_questions)

可视化结果:

plt.figure(figsize=(10,6))
plt.scatter(test_questions, test_solutions, c='r', label='solutions')
plt.scatter(test_questions, test_answers, c='b', label='answers')
plt.legend()

红点形成了我们的模型应该学习的抛物线曲线,蓝点形成了它已经学习的曲线。这种方法显然行不通。

上面的方法有什么问题,如何让神经网络真正学习到抛物线?

使用建议的架构进行修复

将学习率降低到 0.001 就可以了,改为这样编译:

model.compile(loss='mean_squared_error',
              optimizer=tf.keras.optimizers.Adam(0.001))

可视化新结果:

plt.figure(figsize=(10,6))
plt.scatter(test_questions, test_solutions, c='r',marker='+', s=500, label='solutions')
plt.scatter(test_questions, test_answers, c='b', marker='o', label='answers')
plt.legend()

很合身。要检查实际权重以了解究竟学到了什么抛物线,我们可以这样做:

[np.array(layer.weights) for layer in model.layers]

输出:

[array([-1.3284513, -1.328055 ], dtype=float32),
 array([0.5667597, 1.0003909], dtype=float32)]

预期 1, 1, 1, 1,但将这些值代入等式

formula

x^2 项的系数:

0.5667597*(-1.3284513)**2 # result: 1.0002078022990382

x 项的系数:

2*0.5667597*-1.3284513*-1.328055 # result: 1.9998188460235597

常数项:

0.5667597*(-1.328055)**2+1.0003909 # result: 2.000002032736224

即学习到的抛物线是:

1.0002078022990382 * x**2 + 1.9998188460235597 * x + 2.000002032736224

非常接近 f,即 x**2 + 2*x + 2

令人欣慰的是,学习抛物线和真实抛物线的系数之间的差异小于学习率。


请注意,我们可以使用更简单的架构

即:

model = tf.keras.Sequential([
  tf.keras.layers.Dense(units=1, input_shape=[1],activation=lambda x: x**2),
])

即我们有一个输出为 (a*x+b)**2 的神经元,并且通过训练 a & b 进行了调整 -> 我们也可以像这样描述任何抛物线. (实际上也尝试过这个,它起作用了。)

添加到@Zabob 的回答中。您使用了对初始学习率敏感的 Adam 优化器,虽然它被认为非常稳健,但我发现它对初始学习率敏感 - 并且可能导致意外结果(如您正在学习的情况相反的曲线)。如果将优化器更改为 SGD:

model.compile(loss='mean_squared_error',
              optimizer=tf.keras.optimizers.SGD(0.01))

然后在不到100个epochs的时间内,你可以得到一个优化的网络: