回顾上一篇:深刻理解机器学习的: 目标函数,损失函数和代价函数
引言
前面我们讲了关于张量、张量运算、激活函数、代价函数相关的一系列文章,本篇将使用Python 3从头实现一个神经网络,用来逼近函数,有理论证明,神经网络可以逼近任何函数。本篇作为例子,我们使用神经网络逼近 f ( x ) = s i n ( x ) f(x)=sin(x) f(x)=sin(x)函数,让大家初步领略神经网络的魔力。
为了确保你能正确运行本章代码,确保你有:PyCharm IDE,安装好Python虚拟机,安装好Tensorflow以及相关Python包。
文章目录:
- 引出问题
- 定义神经网络结构
- 产生输入数据以及数据的组织
- 选择和实现激活函数
- 计算前馈网络的输出
- 选择和实现损失函数
- 计算BP网络梯度和更新权重
- 实现训练网络
- 运行自编写Python代码和展示结果
- 用TensorFlow实现以及结果对比
- 拓展本例
- 小结
引出问题
以上两张图,一张是加了噪点点数据集,一张是没有加点,对于神经网络来说,它并不管你是正弦或者余弦,它只知道使用神经网络的方法可以找到规律或者模型,以便你输入新点x值,它可以告诉你对应点y值。
定义神经网络结构
首先面临的第一个问题,使用什么样的神经网络结构?神经网络结构怎么定义?很显然,只需要使用浅层神经网络结构即可,是个简单线性回归问题,这里我们使用两层,一个隐藏层,一个输出层的网络来实现模型的训练,网络示意图如下:
x 1 , x 2 , x 3 x1,x2,x3 x1,x2,x3这层属于输入层; a 1 ~ a 10 a1~a10 a1~a10是隐藏层,图里面只画了6个,实际上有数据点个数相同多个神经元,这层的神经元要使用激活函数; z 1 z1 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])[