百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

为什么神经网络能计算任意函数?Talk is cheap,show me the code

toyiye 2024-08-27 21:56 4 浏览 0 评论

关于神经网络最引人注目的事实之一是它们可以计算任何函数。也就是说,假设有人给你一些复杂的、不稳定的函数F(x):

一定会存在一个神经网络,对于任意可能的输入x,都有输出f(x)。比如这个神经网络是输入、输出层都是1个神经元的简单网络:

且当输入、输出层有多个神经元时(如下图),上面结论仍然成立。

不仅如此,这个结论对只有一层隐含层的神经网络也适用。这些是我们知道的或默认的,不然我们不会用它解决各种各样的问题。下面就分别用几何直观的方式和代数的方式,并结合代码解释下神经网络为什么会这么强大。

几何直观证明

为了理解为什么神经网络是通用的,让我们首先了解如何构建一个神经网络,该网络近似只有一个输入和一个输出的函数,输出结果为:σ(wx+b)。其中,σ(z)为激活函数,

σ(z)=

但为了证明普遍性,我们将通过完全忽略代数,而是操纵和观察图中所示的形状来获得更多的见解。

1、单神经元输入、单神经元输出、单个隐含层的情况

上图中,右面曲线表示隐含层第一个神经元的输出-输入关系,输出是σ(wx+b)。曲线形状有下面特性:

(1)w增大,曲线会变得更陡;w减小,曲线更平缓;

(2)b增大,曲线会左移;b减小,曲线右移。

我们可以让w很大,b调整到合适的值,使曲线如下图那样陡峭,近似阶跃函数(step function)。

下面也用阶跃函数这个词来近似描述该函数。阶跃点表示图中竖线在横轴的位置,其中竖线与曲线的中间点相交。阶跃点的位置跟w、b都有关系,关系是s=-b/w。

为了方便表示,我们可以用s代替w、b,调整s大小就能调节阶跃点位置。

如果我们考虑隐含层的两个神经元,就可以输出下面曲线:

曲线的高矮用h参数来表示,h跟s类似(是w和b的函数),h能任意调高矮,s能任意调宽窄。


至此,我们得到了一个可灵活变化的矩形。我们可以让隐含层的神经元变成两对,得到如下曲线:

同理可得,多对神经元就能粗略模拟一个曲线了!

至此,你get到了神经网络一般性的本质!任何函数都可以被一个神经网络近似计算。上图中的神经元越多、越窄,计算结果越精确。

代数证明

神经网络中最关键的是引入了激活函数。激活函数其中一个重要的作用是加入非线性因素的,解决线性模型所不能解决的问题,下面我们从解释一下激活函数的作用——特征的充分组合。

首先抛出一个问题,现实解决复杂问题用的神经网络模型中如果没有激活函数可以吗?答案是不可以的。激活函数的作用如下:

  • 加入非线性因素,解决线性模型不能解决的问题;
  • 激活函数可以用来组合训练的数据特征,让特征进行充分组合。

下面我分别对激活函数的两个作用进行解释。

(1)加入非线性因素,解决非线性问题

好吧,很容易能够看出,我给出的样本点根本不是线性可分的,一个感知器无论得到的直线怎么动,都不可能完全正确的将三角形与圆形区分出来,那么我们很容易想到用多个感知器来进行组合,以便获得更大的分类问题,好的,下面我们上图,看是否可行。

好的,我们已经得到了多感知器分类器了,那么它的分类能力是否强大到能将非线性数据点正确分类开呢~我们来分析一下:

如果我们的每一个结点加入了阶跃函数作为激活函数的话,就是上图描述的

那么随着不断训练优化,我们也就能够解决非线性的问题了~

所以到这里为止,我们就解释了这个观点,加入激活函数是用来加入非线性因素的,解决线性模型所不能解决的问题。

(2)激活函数可以用来组合训练数据的特征,特征的充分组合

我们可以通过上图可以看出,立方激活函数已经将输入的特征进行相互组合了。

通过泰勒展开,我们可以看到,我们已经构造出立方激活函数的形式了。

于是我们可以总结如下:

(3)总结

这就把原来需要领域知识的专家对特征进行组合的情况,在激活函数运算后,其实也能够起到特征组合的作用。(只要激活函数中有能够泰勒展开的函数,就可能起到特征组合的作用)

代码验证

以上我们从几何直观以及代数方面针对神经网络可以模拟任意复杂函数进行了理论证明,接下来我们通过代码进行验证说明。

以下是一个简单的例子,说明神经网络如何逼近一个非线性函数。

考虑一个非线性函数 y = sin(x) + 0.5x,我们将尝试使用神经网络来逼近这个函数。

首先,让我们生成一些训练数据,并创建一个包含单个隐藏层的前馈神经网络来逼近这个函数:

import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt

# 创建一个包含单个隐藏层的前馈神经网络
class NeuralNetwork(nn.Module):
    def __init__(self):
        super(NeuralNetwork, self).__init__()
        self.hidden_layer = nn.Linear(1, 10)  # 1输入特征,10隐藏神经元
        self.output_layer = nn.Linear(10, 1)  # 10隐藏神经元,1输出

    def forward(self, x):
        x = torch.relu(self.hidden_layer(x))
        x = self.output_layer(x)
        return x

# 生成训练数据(函数 y = sin(x) + 0.5x)
x_train = np.random.rand(100, 1) * 4 * np.pi - 2 * np.pi  # 100个随机的数据点在[-2*pi, 2*pi]范围内
y_train = np.sin(x_train) + 0.5 * x_train

# 转换为PyTorch张量
x_train = torch.tensor(x_train, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.float32)

# 创建神经网络实例和损失函数
net = NeuralNetwork()
criterion = nn.MSELoss()
optimizer = optim.Adam(net.parameters(), lr=0.01)

# 训练神经网络
num_epochs = 10000
for epoch in range(num_epochs):
    optimizer.zero_grad()
    output = net(x_train)
    loss = criterion(output, y_train)
    loss.backward()
    optimizer.step()

    if (epoch + 1) % 1000 == 0:
        print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')

# 使用训练好的模型进行预测
x_test = np.linspace(-2 * np.pi, 2 * np.pi, 200).reshape(-1, 1)
x_test = torch.tensor(x_test, dtype=torch.float32)
y_pred = net(x_test).detach().numpy()

# 可视化训练结果和原始函数
plt.figure(figsize=(10, 6))
plt.scatter(x_train.numpy(), y_train.numpy(), label='Training Data', c='r', marker='o')
plt.plot(x_test.numpy(), y_pred, label='Neural Network Approximation', linewidth=2)
plt.plot(x_test.numpy(), np.sin(x_test) + 0.5 * x_test, label='True Function', linestyle='--', linewidth=2)
plt.xlabel('x')
plt.ylabel('y')
plt.title('Approximating y = sin(x) + 0.5x with a Neural Network')
plt.legend()
plt.grid(True)
plt.show()

在这个示例中,我们使用神经网络来逼近非线性函数 y = sin(x) + 0.5x。训练网络后,我们可以看到神经网络成功地逼近了原始函数,这表明神经网络能够在逼近各种函数方面具有很强的灵活性。这个例子突显了神经网络的强大功能,可以用来逼近各种复杂的函数关系。


如果要进一步提升上述逼近原函数的精度,应该如何修改原程序?

要提高神经网络逼近原函数的准确度,您可以尝试以下修改和改进:

  1. 增加隐藏层和神经元数量:增加神经网络的复杂性,例如增加隐藏层的数量或每层的神经元数量,以增加模型的容量。
  2. 增加训练周期:增加训练周期,以便模型更充分地学习数据的模式。但要小心不要过拟合,可以使用验证数据来监控模型的泛化性能。
  3. 尝试不同的激活函数:尝试使用不同的激活函数,例如LeakyReLU、Tanh或Sigmoid,以查看哪种激活函数对这个任务更适合。
  4. 调整学习率:尝试不同的学习率,从较小的值开始,并逐渐增加学习率,以找到一个合适的学习率。
  5. 尝试不同的优化器:除了Adam,尝试使用其他优化器,如SGD、RMSprop等,看看哪个在这个任务上表现更好。
  6. 正则化:添加正则化项,如L1或L2正则化,以减少过拟合。
  7. 批量归一化:在隐藏层之间添加批量归一化层,有助于加速训练并提高模型的稳定性。
  8. 自适应学习率调整:使用学习率调度器,如学习率衰减或学习率计划,以自适应地调整学习率。

以下是修改后的代码示例,其中包括了上述建议的改进:

import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt

# 创建一个包含多个隐藏层的前馈神经网络
class NeuralNetwork(nn.Module):
    def __init__(self):
        super(NeuralNetwork, self).__init__()
        self.hidden_layer1 = nn.Linear(1, 50)  # 1输入特征,50隐藏神经元
        self.hidden_layer2 = nn.Linear(50, 50)  # 50输入特征,50隐藏神经元
        self.output_layer = nn.Linear(50, 1)  # 50隐藏神经元,1输出

    def forward(self, x):
        x = torch.relu(self.hidden_layer1(x))
        x = torch.relu(self.hidden_layer2(x))
        x = self.output_layer(x)
        return x

# 创建神经网络实例
net = NeuralNetwork()

# 打印神经网络结构
print("Neural Network Structure:")
print(net)

# 打印神经网络的权重和偏差
print("\nNeural Network Parameters:")
for name, param in net.named_parameters():
    if param.requires_grad:
        print(f"{name}: {param.data}")

# 打印每一层的输入和输出尺寸
print("\nInput and Output Sizes for Each Layer:")
for name, layer in net.named_children():
    if isinstance(layer, nn.Linear):
        input_size = layer.in_features
        output_size = layer.out_features
        print(f"{name}: Input Size = {input_size}, Output Size = {output_size}")

# 打印每一层的参数数量
print("\nNumber of Parameters in Each Layer:")
for name, param in net.named_parameters():
    if param.requires_grad:
        print(f"{name}: {param.numel()}")

# 打印总参数数量
total_params = sum(p.numel() for p in net.parameters() if p.requires_grad)
print("\nTotal Number of Trainable Parameters:", total_params)

# 生成训练数据(函数 y = sin(x) + 0.5x),在区间 [-6.6, 6.6] 内
x_train = np.random.rand(1000, 1) * 13.2 - 6.6  # 1000个随机的数据点在[-6.6, 6.6]范围内
y_train = np.sin(x_train) + 0.5 * x_train

# 转换为PyTorch张量
x_train = torch.tensor(x_train, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.float32)

# 创建神经网络实例和损失函数
net = NeuralNetwork()
criterion = nn.MSELoss()
optimizer = optim.Adam(net.parameters(), lr=0.001)  # 调整学习率

# 训练神经网络
num_epochs = 20000  # 增加训练周期
for epoch in range(num_epochs):
    optimizer.zero_grad()
    output = net(x_train)
    loss = criterion(output, y_train)
    loss.backward()
    optimizer.step()

    if (epoch + 1) % 1000 == 0:
        print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')

# 使用训练好的模型进行预测
x_test = np.linspace(-6.6, 6.6, 200).reshape(-1, 1)
x_test = torch.tensor(x_test, dtype=torch.float32)
y_pred = net(x_test).detach().numpy()

# 可视化训练结果和原始函数
plt.figure(figsize=(10, 6))
plt.scatter(x_train.numpy(), y_train.numpy(), label='Training Data', c='r', marker='o')
plt.plot(x_test.numpy(), y_pred, label='Neural Network Approximation', linewidth=2)
plt.plot(x_test.numpy(), np.sin(x_test) + 0.5 * x_test, label='True Function', linestyle='--', linewidth=2)
plt.xlabel('x')
plt.ylabel('y')
plt.title('Improved Approximation of y = sin(x) + 0.5x with a Neural Network')
plt.legend()
plt.grid(True)
plt.show()

在这个修改后的代码中,我们增加了神经网络的复杂性、训练周期,并调整了学习率。这些改进有助于提高神经网络逼近原函数的准确度。您可以根据需要进一步调整超参数以获得更好的性能。

Neural Network Structure (After Training):
NeuralNetwork(
  (hidden_layer1): Linear(in_features=1, out_features=50, bias=True)
  (hidden_layer2): Linear(in_features=50, out_features=50, bias=True)
  (output_layer): Linear(in_features=50, out_features=1, bias=True)
)

Neural Network Parameters (After Training):
hidden_layer1.weight: tensor([[ 0.0825],
        [ 0.7044],
        [-0.3375],
        [-0.2671],
        [-0.4813],
        [ 0.6558],
        [ 0.2793],
        [ 0.3227],
        [ 0.2140],
        [ 0.5008],
        [ 0.3725],
        [ 0.9927],
        [-0.8503],
        [-0.2389],
        [-0.3335],
        [-0.7489],
        [-0.5674],
        [ 0.2670],
        [ 0.4114],
        [ 0.7258],
        [ 0.9355],
        [-0.8015],
        [ 0.6182],
        [ 0.3130],
        [-0.2162],
        [ 0.4598],
        [ 0.2892],
        [-0.5683],
        [-0.6120],
        [-0.5978],
        [-0.6970],
        [ 0.1208],
        [ 0.4862],
        [ 0.8421],
        [ 0.5850],
        [ 0.1100],
        [ 0.0455],
        [-0.2747],
        [ 0.4374],
        [ 0.3533],
        [ 0.7137],
        [-0.8125],
        [-0.3950],
        [ 0.8710],
        [ 0.7468],
        [ 0.4855],
        [ 0.1949],
        [-0.2547],
        [ 0.9097],
        [ 0.7534]])
hidden_layer1.bias: tensor([ 4.4674e-01,  5.4609e-01, -1.5498e+00, -7.8404e-02,  2.9593e-02,
        -1.1261e+00,  1.1457e+00, -6.0130e-01, -1.1856e+00,  7.1336e-01,
        -5.6380e-01, -3.0472e-01,  6.9052e-01,  9.6752e-02, -9.7798e-01,
        -2.1741e-01, -5.9287e-01,  9.8672e-01, -8.8389e-01, -3.0103e-01,
        -4.5305e-02, -1.1050e+00,  4.1906e-04, -1.5105e+00,  1.5826e-01,
        -5.6970e-01, -5.6258e-01, -3.5113e-01, -4.8965e-01,  1.5346e-01,
        -1.3311e+00, -8.3694e-01,  1.0095e+00,  5.1924e-01, -7.9395e-01,
         6.5024e-01, -8.6198e-01, -7.5367e-01,  3.7591e-01, -2.9368e-01,
         6.0570e-03,  3.4727e-02,  6.9381e-01,  2.4336e-01,  2.1229e-01,
         6.5567e-01,  1.5767e-01,  9.7992e-01,  2.6099e-01,  7.8849e-01])
hidden_layer2.weight: tensor([[ 0.2374,  0.0302,  0.3109,  ..., -0.0719,  0.0751, -0.0359],
        [-0.0228,  0.0493, -0.0298,  ..., -0.0481, -0.0728, -0.0408],
        [-0.1225,  0.0192, -0.0970,  ..., -0.1036, -0.0098,  0.0010],
        ...,
        [ 0.1195, -0.1071,  0.0545,  ...,  0.0434, -0.0668, -0.0541],
        [ 0.6838, -0.2234,  0.2676,  ...,  0.3907, -0.1174, -0.2583],
        [ 0.0356,  0.1164, -0.0567,  ..., -0.1317,  0.0338,  0.0335]])
hidden_layer2.bias: tensor([-0.3827, -0.0810, -0.0334, -0.0394,  0.0522,  0.1076,  0.0140, -0.1231,
        -0.0695, -0.1611,  0.1111, -0.3494, -0.2981,  0.0385, -0.2837, -0.4027,
        -0.1075,  0.3430,  0.0015,  0.0015, -0.0333, -0.0666, -0.0516,  0.0278,
        -0.1814, -0.2531,  0.1020, -0.0110, -0.0753, -0.0058, -0.0629, -0.0411,
        -0.0534,  0.0453, -0.0044,  0.0709, -0.2969, -0.0448, -0.0362, -0.0971,
         0.1248, -0.1103, -0.0189, -0.0807, -0.0937, -0.0787,  0.1291,  0.0399,
         0.3934, -0.1087])
output_layer.weight: tensor([[-0.1301, -0.0562, -0.0303, -0.0658,  0.0995, -0.1212,  0.0019, -0.1240,
          0.0579, -0.2233,  0.1451, -0.1807, -0.2540,  0.1468, -0.3866, -0.5546,
         -0.1173,  0.5713, -0.1438, -0.0490, -0.0422,  0.1370, -0.1795,  0.0840,
         -0.4097,  0.0875,  0.1765,  0.0689,  0.0159,  0.1222, -0.0978,  0.3691,
          0.1910, -0.0181, -0.0789,  0.1321, -0.2032, -0.0645,  0.0875,  0.1084,
         -0.0642, -0.1513,  0.3670,  0.0797,  0.0685,  0.2305,  0.2901,  0.0727,
         -0.4801, -0.0257]])
output_layer.bias: tensor([0.0420])

我们通过geoGebra作图,发现两者的图像几乎一致。这充分说明了神经网络对任意给定的函数进行模拟。

相关推荐

# Python 3 # Python 3字典Dictionary(1)

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中,格式如...

Python第八课:数据类型中的字典及其函数与方法

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值...

Python中字典详解(python 中字典)

字典是Python中使用键进行索引的重要数据结构。它们是无序的项序列(键值对),这意味着顺序不被保留。键是不可变的。与列表一样,字典的值可以保存异构数据,即整数、浮点、字符串、NaN、布尔值、列表、数...

Python3.9又更新了:dict内置新功能,正式版十月见面

机器之心报道参与:一鸣、JaminPython3.8的热乎劲还没过去,Python就又双叒叕要更新了。近日,3.9版本的第四个alpha版已经开源。从文档中,我们可以看到官方透露的对dic...

Python3 基本数据类型详解(python三种基本数据类型)

文章来源:加米谷大数据Python中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。在Python中,变量就是变量,它没有类型,我们所说的"类型"是变...

一文掌握Python的字典(python字典用法大全)

字典是Python中最强大、最灵活的内置数据结构之一。它们允许存储键值对,从而实现高效的数据检索、操作和组织。本文深入探讨了字典,涵盖了它们的创建、操作和高级用法,以帮助中级Python开发...

超级完整|Python字典详解(python字典的方法或操作)

一、字典概述01字典的格式Python字典是一种可变容器模型,且可存储任意类型对象,如字符串、数字、元组等其他容器模型。字典的每个键值key=>value对用冒号:分割,每个对之间用逗号,...

Python3.9版本新特性:字典合并操作的详细解读

处于测试阶段的Python3.9版本中有一个新特性:我们在使用Python字典时,将能够编写出更可读、更紧凑的代码啦!Python版本你现在使用哪种版本的Python?3.7分?3.5分?还是2.7...

python 自学,字典3(一些例子)(python字典有哪些基本操作)

例子11;如何批量复制字典里的内容2;如何批量修改字典的内容3;如何批量修改字典里某些指定的内容...

Python3.9中的字典合并和更新,几乎影响了所有Python程序员

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

Python3大字典:《Python3自学速查手册.pdf》限时下载中

最近有人会想了,2022了,想学Python晚不晚,学习python有前途吗?IT行业行业薪资高,发展前景好,是很多求职群里严重的香饽饽,而要进入这个高薪行业,也不是那么轻而易举的,拿信工专业的大学生...

python学习——字典(python字典基本操作)

字典Python的字典数据类型是基于hash散列算法实现的,采用键值对(key:value)的形式,根据key的值计算value的地址,具有非常快的查取和插入速度。但它是无序的,包含的元素个数不限,值...

324页清华教授撰写【Python 3 菜鸟查询手册】火了,小白入门字典

如何入门学习python...

Python3.9中的字典合并和更新,了解一下

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

python3基础之字典(python中字典的基本操作)

字典和列表一样,也是python内置的一种数据结构。字典的结构如下图:列表用中括号[]把元素包起来,而字典是用大括号{}把元素包起来,只不过字典的每一个元素都包含键和值两部分。键和值是一一对应的...

取消回复欢迎 发表评论:

请填写验证码