神经网络是万能的吗?
1、神经元
神经元是构成神经网络的基本单元。一个神经元可以被看作是一个复合函数 f ( x ) = σ ( w x + b ) f(x)=\sigma(wx+b) f(x)=σ(wx+b),由两部分构成,即线性变换部分 w x + b wx+b wx+b,和激活函数部分 σ ( z ) = 1 1 + e − z \sigma(z)=\frac{1}{1+e^{-z}} σ(z)=1+e−z1。不同神经元会有不同的 w w w和 b b b,它们是神经元的参数,决定了神经元的功能。
2、一个简单的神经网络
这是一个两层的神经网络,有三个神经元。标有x的圆圈表示输入数据,两外三个没有标签的圆圈各代表一个神经元。最右边的箭头表示神经网络的计算结果。
s
1
、
s
2
s_1、s_2
s1、s2表示上下两个神经元的参数比
s
1
=
−
b
t
o
p
w
t
o
p
,
s
2
=
−
b
b
o
t
w
b
o
t
s_1=-\frac{b_{top}}{w_{top}},s_2=-\frac{b_{bot}}{w_{bot}}
s1=−wtopbtop,s2=−wbotbbot,
w
1
、
w
2
w_1、w_2
w1、w2表示右边神经元的线性变换的系数,其偏移值没有标出表示等于0。现用公式来表示这个神经网络。
z
1
=
w
t
o
p
x
+
b
t
o
p
z
2
=
w
b
o
t
x
+
b
b
o
t
a
1
=
σ
(
z
1
)
a
2
=
σ
(
z
2
)
y
=
w
1
z
1
+
w
2
z
2
z_1=w_{top}x+b_{top}\\z_2=w_{bot}x+b_{bot}\\a_1=\sigma(z_1)\\a_2=\sigma(z_2)\\y=w_1z_1+w_2z_2
z1=wtopx+btopz2=wbotx+bbota1=σ(z1)a2=σ(z2)y=w1z1+w2z2
下面是实现该网络的pytorch代码。
import numpy as np
import matplotlib.pyplot as plt
import torch
class M1(torch.nn.Module):
def __init__(self):
super(M1, self).__init__()
self.l1 = torch.nn.Linear(1,2)
self.l2 = torch.nn.Sigmoid()
self.l3 = torch.nn.Linear(2,1)
def forward(self, x):
x1 = self.l1(x)
x2 = self.l2(x1)
x3 = self.l3(x2)
return x3
def init_parameters(self, s1,s2, y1,y2, w0=100):
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200816112103431.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lneXMxMjM0,size_16,color_FFFFFF,t_70#pic_center)
self.l1.weight[:,0] = w0
self.l1.bias[0] = -w0*s1
self.l1.bias[1] = -w0*s2
self.l3.weight[0,0] = y1
self.l3.weight[0,1] = y2
self.l3.bias[0] = 0
m1 = M1()
m1.init_parameters(0.4,0.6, 0.6,1.2, 500)
n = 100
x = torch.linspace(0,1,n).reshape(-1,1)
y = m1(x).detach().numpy()
plt.plot(x,y)
上面的代码定义了有两级阶梯神经网络。
定义神经网络时,常继承torch.nn.Module类,以便于训练神经网络的参数。Module要求派生类定义两个函数,__init__和forward。__init__定义了神经网络的结构。我们的神经网络有两层,
l
1
l1
l1是第一层的变换部分,
l
2
l2
l2是激活部分。第二层只有变换,没有激活函数。forward是前向传播函数,它定义了神经网络根据输入计算输出的流程。
init_parameters用于指定网络参数。一般情况下,网络参数是无法预知的,而只能通过训练得到。
s
1
s_1
s1是第一级阶梯的横标,
s
2
s_2
s2是第二级阶梯的横标,
y
1
、
y
2
y_1、y_2
y1、y2是两级阶梯的纵标的增量,即
y
1
+
y
2
y_1+y_2
y1+y2才是第二级台阶的纵标。
w
0
w_0
w0取较小的值意味着拐角处有一定的弧度,取较大的值则拐角较为尖锐。
在对神经网络求值时,没有直接调用forward函数,而是用对象名
3、神经网络是万能的吗?
关于神经网络有一个万能近似定理(universal approximation theorem)。它是说神经网络可用来计算计算任何连续函数。这当然不是说某一个悟空级别的神经网络有这么大的魔力,而是说对于每个函数,都有能够计算它的神经网络,这个神经网络能够以您所要求的精确度计算出给定函数的近似值。连续函数的要求只是为了确保被神经网络近似的函数不要有过于激烈的变化幅度(导致难以模拟)。证明定理需要用到泛函分析、傅里叶变换等专门知识,不过Michael Nielsen在他的书中(
《Neural Networks and Deep Learning》)给出了一种简直观的验证方法:设想的一个复杂函数,然后造一个神经网络来模拟它。
我们先造一个看样子并不简单的函数。
def some_fun(x):
return 2+4*x**2+3*x*np.sin(15*x)+.5*np.cos(50*x)
x0 = np.linspace(0,1,100)
y0 = some_fun(x0)
plt.plot(x0,y0)
plt.text(0, 8, '$f(x)=2+4x^2+3xsin(15x)+\dfrac{1}{2}cos(50x)$',
fontsize=12)
先看看模拟的效果图。在
[
0
,
1
]
[0,1]
[0,1]区间上,points是给定分点的个数。显然,随着分点个数的增加,用神经网络拟合的效果越来越好。继续增加分点数,就能使精确带达到预期的程度。基本思路类似于定积分的概念,用一组小矩形的面积和去近似函数所围成的曲边梯形面积,这里用阶梯函数来拟合给定函数的曲线。
class M2(torch.nn.Module):
def __init__(self, n):
super(M2, self).__init__()
self.l1 = torch.nn.Linear(1,n)
self.l2 = torch.nn.Sigmoid()
self.l3 = torch.nn.Linear(n,1)
self.n = n
def forward(self, x):
x1 = self.l1(x)
x2 = self.l2(x1)
x3 = self.l3(x2)
return x3
def init_parameters(self, x, y, w0=100):
for i in range(self.n):
self.l1.weight[i,0] = w0
self.l1.bias[i] = -w0*x[i]
self.l3.weight[0,i] = y[i]
torch.nn.init.constant_(self.l3.bias, 0)
首先对M1做小调整,修改了参数初始化的部分代码,因为阶梯个数不定,分别用x、y表示跃阶的横标和纵标增量列表。
plt.figure(figsize=(12,3))
for i in range(1,5):
m = i*10
x = np.linspace(0, 1, m,dtype=np.float32)
y = some_fun(x)
y[1:] = np.diff(y) # 计算纵标增量,pytorch没有对应函数,故用numpy计算
x = torch.from_numpy(x)
y = torch.from_numpy(y)
m3 = M2(m)
m3.init_parameters(x, y, 500)
z = m3(x.reshape(-1,1)).detach().numpy()
plt.subplot(1,4,i)
plt.plot(x0,y0,x,z)
plt.title('points=%d' % m)
到此,我们看到了神经网络强大的一面。但此时神经网络还不是万能的。
对于简单函数,我们用多项式或其他插值函数可以完成对它的曲线的拟合操作。但是对于复杂函数,如把中文句子翻译成英文句子、生成描述给定图片内容的文本等的函数,我们既写不出它们的解析表达式,也不知道它们的结构和参数值。万能定理告诉我们有神经网络可以做好这些事情,但却没有告诉我们如何找出它们。机器有可能找到解决问题的神经网络的最佳结构吗?有可能。机器有可能找到神经网络的最佳参数吗?很有可能,即下面要讨论的如何训练神经网络。
【上一个谜题】代码急转弯——高维线性可分