利用BP神经网络逼近函数(Python)

回顾上一篇:深刻理解机器学习的: 目标函数,损失函数和代价函数

引言

前面我们讲了关于张量、张量运算、激活函数、代价函数相关的一系列文章,本篇将使用Python 3从头实现一个神经网络,用来逼近函数,有理论证明,神经网络可以逼近任何函数。本篇作为例子,我们使用神经网络逼近 f ( x ) = s i n ( x ) f(x)=sin(x) 函数,让大家初步领略神经网络的魔力。
为了确保你能正确运行本章代码,确保你有:PyCharm IDE,安装好Python虚拟机,安装好Tensorflow以及相关Python包。

文章目录:

  • 引出问题
  • 定义神经网络结构
  • 产生输入数据以及数据的组织
  • 选择和实现激活函数
  • 计算前馈网络的输出
  • 选择和实现损失函数
  • 计算BP网络梯度和更新权重
  • 实现训练网络
  • 运行自编写Python代码和展示结果
  • 用TensorFlow实现以及结果对比
  • 拓展本例
  • 小结
引出问题

图1-0
图1-0-1
以上两张图,一张是加了噪点点数据集,一张是没有加点,对于神经网络来说,它并不管你是正弦或者余弦,它只知道使用神经网络的方法可以找到规律或者模型,以便你输入新点x值,它可以告诉你对应点y值。

定义神经网络结构

首先面临的第一个问题,使用什么样的神经网络结构?神经网络结构怎么定义?很显然,只需要使用浅层神经网络结构即可,是个简单线性回归问题,这里我们使用两层,一个隐藏层,一个输出层的网络来实现模型的训练,网络示意图如下:
图1-1

x 1 , x 2 , x 3 x1,x2,x3 这层属于输入层; a 1 a 10 a1~a10 是隐藏层,图里面只画了6个,实际上有数据点个数相同多个神经元,这层的神经元要使用激活函数; z 1 z1 是输出层,1个神经元,这个神经元里不用激活函数,这层的输出就是结果。输入层和隐藏层之间有权重W1和偏置B1,隐藏层与输出层之间有权重W2和和偏置B2。

我们定义一个Python类 ApproachNetwork 来实现,根据网络结构定义,可以在__init__里定义:

def __init__(self, hidden_size=100, output_size=1):
    self.params = {'W1': np.random.random((1, hidden_size)),
                   'B1': np.zeros(hidden_size),
                   'W2': np.random.random((hidden_size, output_size)),
                   'B2': np.zeros(output_size)}
产生输入数据以及数据的组织

怎么把数据输入到神经网络?以什么形式呢?这是需要好好思考的问题。
前面在这篇文章了解机器学习(深度学习)的几个特点里说过,输入数据的维度以及含义,这里我们把数据组织形状为 (samples, features)这样的形式, samples代表数据点个数,features代表每个数据点的特征数,在这里因为只有一个自变量,特征数为1,如果我们产生100个数据点,那么数据的形状是(100,1), 我们可以一次行把这个矩阵数据输入神经网络,即上面的输入层神经元个数为100.
为了产生这样的训练数据,可以利用文章np.random用法里面的Numpy方法,在类定义里增加产生数据的函数generate_data,函数的参数含义代码里有解释,这个函数具有一定的通用性,可以为不同函数产生训练数据集:

@staticmethod
def generate_data(fun, is_noise=True, axis=np.array([-1, 1, 100])):
    """
     产生数据集
    :param fun: 这个是你希望逼近的函数功能定义,在外面定义一个函数功能方法,把功能方法名传入即可 
    :param is_noise: 是否需要加上噪点,True是加,False表示不加
    :param axis: 这个是产生数据的起点,终点,以及产生多少个数据
    :return: 返回数据的x, y
    """
    np.random.seed(0)
    x = np.linspace(axis[0], axis[1], axis[2])[:, np.newaxis]
    x_size = x.size
    y = np.zeros((x_size, 1))
    if is_noise:
        noise = np.random.normal(0, 0.1, x_size)
    else:
        noise = None

    for i in range(x_size):
        if is_noise:
            y[i] = fun(x[i]) + noise[i]
        else:
            y[i] = fun(x[i])

    return x, y

对于函数 f ( x ) = x 2 f(x)=x^2 调用上面的方法产生数据并把它显示出来,如下:

# 逼近函数 f(x)=sin(x)
def fun_sin(x0):
    return math.sin(x0)

x, y = network.generate_data(fun_sin, False, axis=np.array([-3, 3, 100]))
ax = plt.gca()
ax.set_title('data points')
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
plt.scatter(x, y)
plt.show()

图形如下:
图1-1

选择和实现激活函数

激活函数选择 s i g m o i d sigmoid r e l u relu 都可以,这里选择 s i g m o i d sigmoid ,Python实现如下,注意还需要实现其导数以便在BP反向传播时使用,导数推导过程这里不讲,自行百度:

@staticmethod
def sigmoid(x_):
    return 1 / (1 + np.exp(-x_))

def sigmoid_grad(self, x_):
    return (1.0 - self.sigmoid(x_)) * self.sigmoid(x_)
计算前馈网络的输出

从输入到输出结果计算,隐藏层用激活函数 s i g m o i d sigmoid ,输出层不用激活函数,用Python实现predict方法,如下:

def predict(self, x_):
    W1, W2 = self.params['W1'], self.params['W2']
    b1, b2 = self.params['B1'], self.params['B2']

    a1 = np.dot(x_, W1) + b1
    z1 = self.sigmoid(a1)
    a2 = np.dot(z1, W2) + b2

    return a2
选择和实现损失函数

对于本例,因为是对比输出值的偏差,所以选择均方差MSE作为损失函数或者叫优化函数,定义 l o s s loss 函数定义实现。

def loss(self, x_, t):
    y_ = self.predict(x_)
    return y_, np.mean((t - y_) ** 2)
计算BP网络梯度和更新权重

神经网络是根据计算出来的 l o s s loss 不断调整权重和偏置,以便不断减少 l o s s loss ,达到逼近函数的目的。Python实现代码如下:

def gradient(self, x, t):
    W1, W2 = self.params['W1'], self.params['W2']
    b1, b2 = self.params['B1'], self.params['B2']
    grads = {}

    batch_num = x.shape[0]

    # forward
    a1 = np.dot(x, W1) + b1
    z1 = self.sigmoid(a1)
    a2 = np.dot(z1, W2) + b2

    # backward
    dy = (a2 - t) / batch_num
    grads['W2'] = np.dot(z1.T, dy)
    grads['B2'] = np.sum(dy, axis=0)

    dz1 = np.dot(dy, W2.T)
    da1 = self.sigmoid_grad(a1) * dz1
    grads['W1'] = np.dot(x.T, da1)
    grads['B1'] = np.sum(da1, axis=0)

    return grads

# 根据上述计算的梯度,结合学习率,更新权重和偏置
for key in ('W1', 'B1', 'W2', 'B2'):
    self.params[key] -= self.learning_rate * grad[key]
实现训练网络

有了上面的步骤,就可以定义训练主函数,主要代码:

def train_with_own(self, x_, y_, max_steps=100):
    for k in range(max_steps):
        grad = self.gradient(x_, y_)
        for key in ('W1', 'B1', 'W2', 'B2'):
            self.params[key] -= self.learning_rate * grad[key]
        pred, loss = network.loss(x_, y_)

        if k % 150 == 0:
            # 动态绘制结果图,你可以看到训练过程如何慢慢的拟合数据点
            plt.cla()
            plt.scatter(x, y)
            plt.plot(x, pred, 'r-', lw=5)
            plt.text(0.5, 0, 'Loss=%.4f' % abs(loss), fontdict={'size': 20, 'color': 'red'})
            plt.pause(0.1)

    # 关闭动态绘制模式
    plt.ioff()
    plt.show()
运行自编写Python代码和展示结果

编写运行主函数,其中实例化类ApproachNetwork,调用产生数据和训练方法即可完成。

if __name__ == '__main__':
    network = ApproachNetwork()
    x, y = network.generate_data(fun_sin, False, axis=np.array([-3, 3, 100]))
    # x, y = network.generate_data(fun_xx)

    # 使用 自编代码 训练
    network.train_with_own(x, y, 3500)

结果展示
以下图形是在设定最大循环步骤为3500,学习率为0.05下输出的,本例还采用了动态输出图形的办法,每150步,刷新输出当前图形,你可以直观看到训练过程中函数逼近的过程,本例是在PyCharm中开发、测试和运行。
图1-3

用TensorFlow实现以及结果对比

函数逼近的问题,也可以使用Tensorflow这种深度学习框架来实现,在本例中在ApproachNetwork类中定义一个方法 train_with_tf,代码如下,解释在代码中

 @staticmethod
def train_with_tf(x, y):
    tf_x = tf.placeholder(tf.float32, x.shape)  # input x
    tf_y = tf.placeholder(tf.float32, y.shape)  # input y

    # 神经网络定义, 激活函数使用 ReLU 或 sigmoid 都可以
    # l1 = tf.layers.dense(tf_x, 10, tf.nn.relu)  # hidden layer
    l1 = tf.layers.dense(tf_x, 10, tf.nn.sigmoid)  # hidden layer
    output = tf.layers.dense(l1, 1)  # output layer

    # 损失函数和优化器设置
    loss = tf.losses.mean_squared_error(tf_y, output)  # compute cost
    train_op = tf.train.GradientDescentOptimizer(learning_rate=0.5).minimize(loss)

    # 初始化
    sess = tf.Session()
    sess.run(tf.global_variables_initializer())

    # 设置动态绘制,不是训练完了才绘制结果
    plt.ion()

    for step in range(1500):
        _, l, pred = sess.run([train_op, loss, output], {tf_x: x, tf_y: y})
        if step % 100 == 0:
            # 动态绘制结果图,你可以看到训练过程如何慢慢的拟合数据点
            plt.cla()
            plt.scatter(x, y)
            plt.plot(x, pred, 'r-', lw=5)
            plt.text(0.5, 0, 'Loss=%.4f' % l, fontdict={'size': 20, 'color': 'red'})
            plt.pause(0.1)

    # 关闭动态绘制模式
    plt.ioff()
    plt.show()

可以看出,使用Tensorflow框架代码简洁,步骤清楚。但是自己用Python动手开发神经网络能使你清楚各个环节,对你提高能力和水平是大有裨益的。
在同样的条件设置下,逼近的结果如下:
图1-4
比较起来,自己编写代码精度似乎还要高些!Cheers!

扩展本例

这个类不仅可以逼近正弦函数,还可以逼近你喜欢试的函数,例如我想让它逼近 f ( x ) = x 2 f(x) = x^2 ,先定义该函数,然后在生成数据参数中传递函数名进去,第三个参数根据需要修改一下,保证你的数据集能够完整的表现改函数的形状特征。在训练过程中,其它参数可以适当调整以下,如学习率,最大迭代次数等,这种可以把函数作为参数传递的做法,也是Python作为动态语言的一个优势所在,自己可以去体会:

# 逼近函数 f(x)=x**2
def fun_xx(x0):
    return np.power(x0, 2)
    
x, y = network.generate_data(fun_xx)
小结

读者可能的疑惑是,BP反馈网络计算梯度不太明白,继续阅读 用计算图理解和计算BP神经网络的梯度

参考文献

利用BP神经网络逼近函数——Python实现
机器学习与神经网络(四):BP神经网络的介绍和Python代码实现
一个 11 行 Python 代码实现的神经网络

发布了36 篇原创文章 · 获赞 42 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_28710515/article/details/89407190
今日推荐