PythonLearn

这样玩神经网络

神经网络就是一堆矩阵乘来乘去,而矩阵里是什么数字全靠算法计算,我们只需要提供一堆输入数据和输出, 那么依靠反向传播算法,神经网络就能自动计算出最合适的参数,从而实现训练。

PyTorch

这是Python的深度学习框架,我们用它实现神经网络, cpu版本直接执行pip install torch,gpu版本请去官网获取安装命令

案例

假如有个函数,输入30个1~100的数字,返回他们的和,如何用神经网络实现这个函数呢?多神经的案例啊

创建神经网络

定义输入层大小,输出层大小,中间层大小,中间层数量,创建神经网络

import torch
class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = torch.nn.Linear(30, 60)
        self.fc2 = torch.nn.Linear(60, 60)
        self.fc3 = torch.nn.Linear(60, 1)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = torch.relu(self.fc3(x))
        return x

该类就是个神经网络,输入层大小为30,输出层大小为1,中间层大小为60,中间层数量为2
内部其实就是传入nx30的矩阵,乘以30x60的矩阵,再乘以60x60的矩阵,最后乘以60x1的矩阵,最后返回nx1的矩阵

而矩阵内部是什么值并不是人能猜的,所以只能训练,给他输入和输出,让他通过反向传播算法,自己计算出最合适的参数

激活函数

一个数学线性函数与另一个线性函数组合能得到一个新的线性函数。同样,没有激活函数的多个线性层实际上等价于单个线性层。
所以激活函数的作用就是增加神经网络的非线性,比如torch.relu(),用于使神经网络能够学习和表示复杂的非线性关系

归一化

将数据按比例缩放到特定范围(如[0,1]或[-1,1])的过程
有些激活函数遇到很大的数值时,输出会接近0,导致无法训练,所以需要归一化。
就好比在山坡上开车,归一化就是将坡度降低,让车能正常行驶

输入,输出和结果

# 创建神经网络实例
net = Net()
# 输入,30个0~100的随机数,并归一化(全部除100)
inputs = torch.FloatTensor(np.random.randint(0, 101, 30) / 100.0)
# 神经网络输出
outputs = net(inputs)
# 目标值,30个(0~100)/100的和
targets = torch.sum(inputs)
print(inputs)
print(outputs)
print(targets)

输入

[0.4200, 0.6600, 0.0300, 0.2600, ...]
[0.2]
[6]

反向传播

计算输出与目标值的误差,然后清除梯度,并反向传播误差,最后更新参数

# 定义损失函数和优化器
criterion = torch.nn.MSELoss()
optimizer = torch.optim.Adam(net.parameters(), lr=0.001)
# 计算损失
loss = criterion(outputs, targets)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()

训练

def train_network():
    net = Net()
    criterion = torch.nn.MSELoss()
    optimizer = torch.optim.Adam(net.parameters(), lr=0.001)
    
    # 训练循环
    for epoch in range(3000):
        # 32行,代表32个批次,每行30个数字,并归一化
        inputs = torch.FloatTensor(np.random.randint(0, 101, (32, 30)) / 100)
        outputs = net(inputs)
        # 目标值,每行求和,返回32行1列的矩阵
        targets = torch.sum(inputs, dim=1, keepdim=True)

        loss = criterion(outputs, targets)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        # 每100个epoch打印一次损失
        if (epoch + 1) % 100 == 0:
            print(f'Epoch [{epoch+1}/3000], Loss: {loss.item():.4f}')
    return net

测试

print("开始训练网络...")
trained_net = train_network()
print("开始测试!")
trained_net.eval()  # 设置为评估模式

test_inputs_raw = np.random.randint(0, 101, (10, 30)) / 100
test_inputs = torch.FloatTensor(test_inputs_raw)
with torch.no_grad():  # 测试时不需要计算梯度
    test_outputs = trained_net(test_inputs)

test_targets = torch.sum(torch.FloatTensor(test_inputs_raw), dim=1, keepdim=True)

for i in range(len(test_outputs)):
    predicted = test_outputs[i].item()
    target = test_targets[i].item()
    error = abs(predicted - target)
    # 修改显示输入数据的前5个元素
    input_str = "[" + ", ".join(map(str, test_inputs_raw[i][:5])) + ", ...]"
    print(f"{i+1:<4} {input_str:<20} {predicted:<12.2f} {target:<12.2f} {error:<10.2f}")

输出

D:\Python310\python.exe D:\PythonProjects\Learn\temp.py 
开始训练网络...
Epoch [100/3000], Loss: 0.3356
Epoch [200/3000], Loss: 0.1059
...
Epoch [1400/3000], Loss: 0.0011
Epoch [1500/3000], Loss: 0.0005
...
Epoch [3000/3000], Loss: 0.0000
1    [0.66, 0.76, 0.95, 0.53, 0.81, ...] 16.09        16.09        0.00      
2    [0.93, 0.15, 0.13, 0.01, 0.87, ...] 14.62        14.62        0.00      
3    [0.51, 1.0, 0.87, 0.28, 0.64, ...] 14.10        14.10        0.00      
4    [0.6, 0.07, 0.74, 0.41, 0.04, ...] 17.24        17.24        0.00      
5    [0.95, 0.85, 0.3, 0.44, 0.42, ...] 15.30        15.30        0.00      
...

可以看到测试结果和目标值误差都为0,说明训练成功

最后

全部代码

import torch
import numpy as np


class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = torch.nn.Linear(30, 60)
        self.fc2 = torch.nn.Linear(60, 60)
        self.fc3 = torch.nn.Linear(60, 1)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = torch.relu(self.fc3(x))
        return x


def train_network():
    net = Net()
    criterion = torch.nn.MSELoss()
    optimizer = torch.optim.Adam(net.parameters(), lr=0.001)

    # 训练循环
    for epoch in range(3000):
        # 32行,每行30个数字,代表32个批次,并归一化
        inputs = torch.FloatTensor(np.random.randint(0, 101, (32, 30)) / 100)
        outputs = net(inputs)
        # 目标值,每行求和,返回32行1列的矩阵
        targets = torch.sum(inputs, dim=1, keepdim=True)

        loss = criterion(outputs, targets)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        # 每100个epoch打印一次损失
        if (epoch + 1) % 100 == 0:
            print(f'Epoch [{epoch + 1}/3000], Loss: {loss.item():.4f}')
    return net


def main():
    # 训练网络
    print("开始训练网络...")
    trained_net = train_network()
    print("开始测试!")
    trained_net.eval()  # 设置为评估模式

    test_inputs_raw = np.random.randint(0, 101, (10, 30)) / 100
    test_inputs = torch.FloatTensor(test_inputs_raw)
    with torch.no_grad():  # 测试时不需要计算梯度
        test_outputs = trained_net(test_inputs)

    test_targets = torch.sum(torch.FloatTensor(test_inputs_raw), dim=1, keepdim=True)

    for i in range(len(test_outputs)):
        predicted = test_outputs[i].item()
        target = test_targets[i].item()
        error = abs(predicted - target)
        # 修改显示输入数据的前5个元素
        input_str = "[" + ", ".join(map(str, test_inputs_raw[i][:5])) + ", ...]"
        print(f"{i+1:<4} {input_str:<20} {predicted:<12.2f} {target:<12.2f} {error:<10.2f}")


if __name__ == "__main__":
    main()