从头推导与实现 BP 网络

从头推导与实现 BP 网络

回归模型

目标

学习 y=2x

模型

单隐层、单节点的 BP 神经网络

策略

Mean Square Error 均方误差

MSE=12(y^y)2

模型的目标是 min12(y^y)2

算法

朴素梯度下降。在每个 epoch 内,使模型对所有的训练数据都误差最小化。

网络结构

Forward Propagation Derivation

E=12(Y^Y)2Y^=ββ=Wbb=sigmoid(α)α=Vx

Back Propagation Derivation

模型的可学习参数为 w,v ,更新的策略遵循感知机模型:

参数 w 的更新算法

ww+ΔwΔw=ηEwEw=EY^Y^ββw=(Y^Y)1b

参数 v 的更新算法

vv+ΔvΔv=ηEvEv=EY^Y^ββbβααv=(Y^Y)1wβαxβα=sigmoid(α)[1sigmoid(α)]sigmoid(α)=11+eα

代码实现

C++ 实现

#include <iostream>
#include <cmath>

using namespace std;

class Network {
public :
    Network(float eta) :eta(eta) {}

    float predict(int x) {  // forward propagation
        this->alpha = this->v * x;
        this->b = this->sigmoid(alpha);
        this->beta = this->w * this->b;
        float prediction = this->beta;
        return prediction;
    }

    void step(int x, float prediction, float label) {
        this->w = this->w 
            - this->eta 
            * (prediction - label) 
            * this->b;
        this->alpha = this->v * x;
        this->v = this->v 
            - this->eta 
            * (prediction - label) 
            * this->w 
            * this->sigmoid(this->alpha) * (1 - this->sigmoid(this->alpha)) 
            * x;
    }
private:
    float sigmoid(float x) {return (float)1 / (1 + exp(-x));}
    float v = 1, w = 1, alpha = 1, beta = 1, b = 1, prediction, eta;
};

int main() {  // Going to learn the linear relationship y = 2*x
    float loss, pred;
    Network model(0.01);
    cout << "x is " << 3 << " prediction is " << model.predict(3) << " label is " << 2*3 << endl;
    for (int epoch = 0; epoch < 500; epoch++) {
        loss = 0;
        for (int i = 0; i < 10; i++) {
            pred = model.predict(i);
            loss += pow((pred - 2*i), 2) / 2;
            model.step(i, pred, 2*i);
        }
        loss /= 10;
        cout << "Epoch: " << epoch << "  Loss:" << loss << endl;
    }
    cout << "x is " << 3 << " prediction is " << model.predict(3) << " label is " << 2*3 << endl;
    return 0;
}

C++ 运行结果

初始网络权重,对数据 x=3, y=6的 预测结果为 y^=0.952534

训练了 500 个 epoch 以后,平均损失下降至 7.82519,对数据 x=3, y=6的 预测结果为 y^=11.242

PyTorch 实现

# encoding:utf8
# 极简的神经网络,单隐层、单节点、单输入、单输出

import torch as t
import torch.nn as nn
import torch.optim as optim


class Model(nn.Module):
    def __init__(self, in_dim, out_dim):
        super(Model, self).__init__()
        self.hidden_layer = nn.Linear(in_dim, out_dim)

    def forward(self, x):
        out = self.hidden_layer(x)
        out = t.sigmoid(out)
        return out


if __name__ == '__main__':
    X, Y = [[i] for i in range(10)], [2*i for i in range(10)]
    X, Y = t.Tensor(X), t.Tensor(Y)
    model = Model(1, 1)
    optimizer = optim.SGD(model.parameters(), lr=0.01)
    criticism = nn.MSELoss(reduction='mean')
    y_pred = model.forward(t.Tensor([[3]]))
    print(y_pred.data)
    for i in range(500):
        optimizer.zero_grad()
        y_pred = model.forward(X)
        loss = criticism(y_pred, Y)
        loss.backward()
        optimizer.step()
        print(loss.data)
    y_pred = model.forward(t.Tensor([[3]]))
    print(y_pred.data)

PyTorch 运行结果

初始网络权重,对数据 x=3, y=6的 预测结果为 y^=0.5164

训练了 500 个 epoch 以后,平均损失下降至 98.8590,对数据 x=3, y=6的 预测结果为 y^=0.8651

结论

居然手工编程的实现其学习效果比 PyTorch 的实现更好,真是奇怪!但是我估计差距就产生于学习算法的不同,PyTorch采用的是 SGD。

分类模型

目标

目标未知,因为本实验的数据集是对 iris 取前两类样本,然后把四维特征降维成两维,得到本实验的数据集。

数据简介:

-1.653443 0.198723 1 0  # 前两列为特正,最后两列“1 0”表示第一类
1.373162 -0.194633 0 1  # "0 1",第二类

模型

单隐层双输入输入节点的分类 BP 网络

策略

在整个模型的优化过程中,使得在整个训练集上交叉熵最小:

argminθH(Y,Y^)

交叉熵:

(1)H(y,y^)=i=12yilogy^i(2)=(y1logy^1+y2logy^2)

算法

梯度下降,也即在每个 epoch 内,使模型对所有的训练数据都误差最小。

网络结构

如图

softmax

Forward Propagation

公式推导如下

a1=w11x1+w21x2a2=w12x1+w22x2b1=sigmoid(a1)b2=sigmoid(a2)y1^=exp(b1)exp(b1)+exp(b2)y2^=exp(b2)exp(b1)+exp(b2)

(3)E(k)=H(y(k),y^(k))(4)=(y1logy^1+y2logy^2)

Back Propagation

Ew11=(Ey^1y^1b1+Ey^2y^2b1)b1a1a1w11

其中,

Ey^1=y1y^1Ey^2=y2y^2y^1b1=y^1(1y^1)y^2b1=y^1y^2b1a1=sigmoid(a1)[1sigmoid(a1)]a1w11=x1

所以,

Ew11=(y^1y1)sigmoid(a1)[1sigmoid(a1)]x1

类似的,可得

Ew21=(y^1y1)sigmoid(a1)[1sigmoid(a1)]x2Ew12=(y^2y2)sigmoid(a2)[1sigmoid(a2)]x1Ew22=(y^2y2)sigmoid(a2)[1sigmoid(a2)]x2

代码实现

Python 3 实现

# encoding:utf8

from math import exp, log
import numpy as np


def load_data(fname):
    X, Y = list(), list()
    with open(fname, encoding='utf8') as f:
        for line in f:
            line = line.strip().split()
            X.append(line[:2])
            Y.append(line[2:])
    return X, Y


class Network:
    eta = 0.5
    w = [[0.5, 0.5], [0.5, 0.5]]
    b = [0.5, 0.5]
    a = [0.5, 0.5]
    pred = [0.5, 0.5]

    def __sigmoid(self, x):
        return 1 / (1 + exp(-x))

    def forward(self, x):
        self.a[0] = self.w[0][0] * x[0] + self.w[1][0] * x[1]
        self.a[1] = self.w[0][1] * x[0] + self.w[1][1] * x[1]
        self.b[0] = self.__sigmoid(self.a[0])
        self.b[1] = self.__sigmoid(self.a[1])
        self.pred[0] = self.__sigmoid(self.b[0]) / (self.__sigmoid(self.b[0]) + self.__sigmoid(self.b[1]))
        self.pred[1] = self.__sigmoid(self.b[1]) / (self.__sigmoid(self.b[0]) + self.__sigmoid(self.b[1]))
        return self.pred

    def step(self, x, label):
        g = (self.pred[0] - label[0]) * self.__sigmoid(self.a[0]) * (1-self.__sigmoid(self.a[0])) * x[0]
        self.w[0][0] = self.w[0][0] - self.eta * g
        g = (self.pred[0] - label[0]) * self.__sigmoid(self.a[0]) * (1 - self.__sigmoid(self.a[0])) * x[1]
        self.w[1][0] = self.w[1][0] - self.eta * g
        g = (self.pred[1] - label[1]) * self.__sigmoid(self.a[1]) * (1 - self.__sigmoid(self.a[1])) * x[0]
        self.w[0][1] = self.w[0][1] - self.eta * g
        g = (self.pred[1] - label[1]) * self.__sigmoid(self.a[1]) * (1 - self.__sigmoid(self.a[1])) * x[1]
        self.w[1][1] = self.w[1][1] - self.eta * g


if __name__ == '__main__':
    X, Y = load_data('iris.txt')
    X, Y = np.array(X).astype(float), np.array(Y).astype(float)

    model = Network()
    pred = model.forward(X[0])
    print("Label: %d %d, Pred: %f %f" % (Y[0][0], Y[0][1], pred[0], pred[1]))

    epoch = 100
    loss = 0
    for i in range(epoch):
        loss = 0
        for j in range(len(X)):
            pred = model.forward(X[j])
            loss = loss - Y[j][0] * log(pred[0]) - Y[j][1] * log(pred[1])
            model.step(X[j], Y[j])
        print("Loss: %f" % (loss))

    pred = model.forward(X[0])
    print("Label: %d %d, Pred: %f %f" % (Y[0][0], Y[0][1], pred[0], pred[1]))

网络在训练之前,预测为:

Label: 1 0, Pred: 0.500000 0.500000
Loss: 55.430875

学习率 0.5, 训练 100 个 epoch 以后:

Label: 1 0, Pred: 0.593839 0.406161
Loss: 52.136626

结论

训练后损失减小,模型预测的趋势朝着更贴近标签的方向前进,本次实验成功。

只不过模型的参数较少,所以学习能力有限。

Reference

Derivative of Softmax Loss Function

posted @   健康平安快乐  阅读(299)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示