[Python] 从0到1实现一个简单的数字图像识别大模型

目录

前言介绍

神经网络

简单的神经网络

使用均方误差与正规方程实现神经网络

随机梯度下降与批量梯度下降实现神经网络

用更复杂的梯度下降实现一个神经网络

利用Sigmoid激活函数实现神经网络

使用 PyTorch 框架快速构建一个神经网络、

案例实战


前言介绍

        大模型的本质是机器学习, 机器学习的本质就是一种数学模型,而现在主流的大模型都是基于神经网络模型构建的数学模型,不论是基于卷积神经网络(CNN),还是循环神经网络(RNN),亦或者是Transformer神经网络等。‍‍‍‍‍‍‍‍所以所谓的大模型,就是一个很复杂的函数,训练它的样本集很大、参数很多。

        神经网络模型是一种基于人工神经元的数学模型,用于模拟人脑的神经网络结构和功能。 神经网络模型有很多层,每一层都有很多个神经元,每一层又是相互连接。每个神经元又由很多参数组成,平时我们常常所说的某个大模型有多少亿参数,就是指所有神经元加起来参数之和。参数越多,大模型的功能就越强大。

        

         一般情况下,大模型的参数是在网络架构时就设定好的,参数数量一般不会发生变化;但也有例外情况,比如动态神经网络就会对参数数量进行动态调整。‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

        大模型训练的本质就是调整参数,训练的过程其实就是把训练数据输入到大模型中,然后模型根据这些数据对参数进行调整的过程,以求达到一个最优解。因为模型有多个神经层,所以训练数据从输入层进入大模型之后;需要在模型的多个神经层之间进行流转,而这个过程术语叫做正向传播。‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

        数据从输入层,一层一层的传播到输出层,然后输出结果;但由于大模型刚开始就像一个小学生,所以它输出的结果往往不尽人意。‍‍‍‍‍‍‍‍所以,为了解决这个问题,大模型的输出结果需要跟实际结果进行匹配,术语叫做计算损失差,损失差越大说明输出结果越差。‍‍‍‍‍‍‍‍‍‍‍‍‍

        而有了损失差,说明当前的模型是有问题的;所以就需要对模型进行调整,这就是所谓的反向传播。‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

神经网络

        大模型是基于神经网络构建的。接下来,我们将通过多种数学模型来实现神经网络的优化。

  • 简单的神经网络

         对于 ypi = w * xi 函数,其实是属于神经网络中的一层,w 是参数,大模型训练调整的就是这个参数。而 xi 和 ypi 在数学中是自变量和因变量,而在神经网络中,它是样本数据,是固定的,也叫训练数据。

        在训练中,不断的根据预测值和真实值,算出他们之间的误差,然后调整 w 的值,误差越小,代码模型预测能力越好。

 训练数据代码

import numpy as np

def get(count):
    # 生成count个0-1之间随机数
    xs = np.sort(np.random.rand(count))

    ys = []
    for x in xs:
        y = 1.5 * x + np.random.rand() / 10
        ys.append(y)
    ys = np.array(ys)

    # 变为负数
    # xs = xs * -1
    # ys = ys * -1

    return xs, ys

 神经网络代码

import matplotlib.pyplot as plot

import house

xs, ys = house.get(100)
w = 0.5
for i in range(100):
    xi = xs[i]
    yi = ys[i]

    ypi = w * xi  # 预测值  正向传播
    e = yi - ypi  # 误差
    w = w + e * xi  # 根据误差修改w  反向传播

    # 以下只是为了画出新预测函数对应的直线
    yps = w * xs  # 新的预测函数
    plot.clf()  # 清空重新绘制,这样就能看到动态效果
    plot.xlim(0, 1) #设置了 x 轴的显示范围
    plot.ylim(0, 1.6) #设置了 y 轴的显示范围
    plot.scatter(xs, ys) #绘制了一个散点图
    plot.plot(xs, yps) #绘制了一条折线图
    plot.pause(0.1) #暂停图形的显示 0.1 秒

plot.show() #显示当前绘制的图形

运行结果如下:

        通过运行结果可以看出,函数 ypi = w * xi 的直线不断的靠近散点样本数据,代表误差越来越小,预测能力也越来越好。

  •  使用均方误差与正规方程实现神经网络

           上面例子实现的简单的神经网络,是根据预测值和真实值,算出他们之间的误差,进而调整参数值,但受样本的数据影响很大,如果最后一个样本数据存在问题,就会导致整个训练过程报废。

        针对上面出现的问题,就可以使用均方误差和正规方程来解决,所谓均方误差,就是把所有的误差相加,再除以样本数。

        单个样本的误差计算方式为:(真实值-预测值​)²,用函数来表示就是 e = ( yi − w⋅xi )²,有人会很奇怪,误差为什么要加平方,其实是为了消除负值,那误差不是变大了?其实没关系,因为我们关注的是 w 这个参数值。

 将误差公式展开为:

这样,对于某个样本(xi,yi)而言,就可以通过上述公式来计算误差e了,我们稍微调整一下位置:

通过上述公式,可以看出,如果我们把w看成自变量,e看成因变量,xi和yi看成常量,那么其实就是一个一元二次函数,对应的就是一条抛物线。

而针对所有样本,我们要计算全部样本的整体误差,我们只需要计算所有样本的误差e,然后累计e,再除以样本个数就得到了全体样本的均方误差

        拆开后:

所以,针对全体样本的均方误差也是一个一元二次方程,也是一个抛物线,而且是开口向上的抛物线,所以抛物线的最低点就表示误差最小的点,而根据抛物线的最地方求解公式就能得到 。

而对应的抛物线的顶点就是所有样本的评价误差最小值。而根据抛物线的顶点坐标公式:

而w是自变量,所以最小w值为 -b/(2a),也就是

约分之后就是:

 而这就是正规方程。通过它就能直接得到误差e最小时的w值。

代码:

import matplotlib.pyplot as plot

import house

xs, ys = house.get(100)
ys[99] = 2

w = 0.5

sum_xy = 0
sum_xx = 0
for i in range(100):
    xi = xs[i]
    yi = ys[i]
    sum_xy += xi * yi
    sum_xx += xi * xi

w = sum_xy / sum_xx

yps = w * xs

plot.clf()
plot.xlim(0, 1)
plot.ylim(0, 1.6)
plot.scatter(xs, ys)
plot.plot(xs, yps)

plot.show()

  • 随机梯度下降与批量梯度下降实现神经网络

          使用均方误差与正规方程实现神经网络,虽然能很好解决因某个样本而导致训练的不准确,但需要对样本数据进行累加,如果样本量太多,就会过多占用服务器CPU,内存等资源,接下来使用梯度下降来解决这个问题。

        还是看误差抛物线,我们不直接取 w 的最低点计算,而且取 w 点的斜率 来不断地调整 w 值。让 w 值不断趋向于最低点。

              1.  抛物线上某个点的斜率如果小于0,那么表示在右边。

              2. 抛物线上某个点的斜率如果大于0,那么表示在左边。

              3. 抛物线上某个点的斜率如果等于0,那么表示在最低点。

误差抛物线图

那么某点的斜率该如何得出呢?实际上就是求导。

求导

假设抛物线上存在某点(w,e),那么该点的斜率为:

这样,我们就能知道某个w对应的斜率了。

其中:

对于给定的样本集而言,a,b的值是固定的,所以我们只需要不断调整 w 的值就可以了。对于 a 和 b 为什么等于上面两个值,大家可以上面 均方误差与正规方程那里。

那如何调整 w 的值呢?

        像我们上面所说的,抛物线上某一点的斜率小于 0 的话(参考上面误差抛物线图),那边代表在右边,这时我们只要增加 w 的值就可以,那增加多少呢?这时我们可以自己设置一个大小,步长越大,调整的越快,但是精确度越低,步长越小,调整的越慢,但是精确度越高。这个在深度学习中叫步长。调整公式如下:

w新 = w旧 - 步长 * 斜率

如果某一点上的斜率大于 0 的话,则相反,减小 w 的值就行。

代码实战 - 随机梯度下降

import matplotlib.pyplot as plot

import house

xs, ys = house.get(100)

alpha = 0.1  # 学习率,步长
w = 0.1
for i in range(100):
    xi = xs[i]
    yi = ys[i]

    # 斜率
    k = 2 * (xi ** 2) * w + (-2 * xi * yi)

    w = w - alpha * k  # k大于0,要减少w,k小于0,要增加w

    yps = w * xs  # 新的预测函数

    plot.clf()  # 清空重新绘制,这样就能看到动态效果
    plot.xlim(0, 1)
    plot.ylim(0, 1.6)
    plot.scatter(xs, ys)
    plot.plot(xs, yps)
    plot.pause(0.01)

plot.show()

上面代码一个一个的取样本的数据,如果你觉得慢,想批量的 取,可以使用mini批量梯度下降。

代码实战 - mini批量梯度下降

import matplotlib.pyplot as plot
import numpy

import house

xs, ys = house.get(100)

w = 0.1
alpha = 0.1  # 学习率,步长
for _ in range(50):
    # 每次取10个样本
    for i in range(0, 100, 10):
        xsi = xs[i::i + 10]
        ysi = ys[i::i + 10]

        a = numpy.sum(xsi ** 2) / 10
        b = -2 * numpy.sum(xsi * ysi) / 10

        k = 2 * a * w + b  # k表示w点的斜率

        w = w - alpha * k  # k大于0,要减少w,k小于0,要增加w

        yps = w * xs  # 新的预测函数

        plot.clf()  # 清空重新绘制,这样就能看到动态效果
        plot.xlim(0, 1)
        plot.ylim(0, 1.6)
        plot.scatter(xs, ys)
        plot.plot(xs, yps)
        plot.pause(0.01)

plot.show()

  • 用更复杂的梯度下降实现一个神经网络

        如果样本数据这样的话,我们该如何设计的神经网络?

前面我们设计的函数都是y = wx,而忽略了 b ,而对于上面的样本,我们就需要用到 b 这个参数。

w 决定了直线的倾斜,而 b 决定了 直线的高度。

那我们该如何设计,使得函数直线更贴近样本数据呢?

        我们还是可以使用梯度下降来解决,通过不断地调整 w 和 b 的值,找到整体误差最小的 w 和 b的值,均方误差公式:

将误差公式展开调整为:

我们可以把样本和 b 都看成常量,e 和 w 之间还是一条抛物线。

因为我们不仅需要调整 w 的值,还需要调整 b 的值, 此时我们需要同时对 w 和 b进行求导。然后进行梯度下降。

e 对 w 的导数:

e 对 b 的导数:

代码实战 :

import numpy as np


def get(count):
    # 生成count个0-1之间随机数
    xs = np.sort(np.random.rand(count))

    ys = []
    for x in xs:
        y = 1.5 * x + np.random.rand() / 10
        ys.append(y)
    ys = np.array(ys)

    # 变为负数
    # xs = xs * -1
    # ys = ys * -1

    # 倒置
    xs = np.flip(xs)

    return xs, ys
import matplotlib.pyplot as plot
import house

xs, ys = house.get(100)

plot.plot(xs, ys)

w = 0.1
b = 0.1
alpha = 0.1  # 学习率,步长

# 15表示epoch
for _ in range(15):
    for i in range(100):
        xi = xs[i]
        yi = ys[i]
        dw = 2 * (xi ** 2) * w + 2 * xi * (b - yi)
        db = 2 * b - 2 * (yi - w * xi)

        w = w - alpha * dw
        b = b - alpha * db

        yps = w * xs + b  # 新的预测函数

        plot.clf()  # 清空重新绘制,这样就能看到动态效果
        plot.xlim(0, 1)
        plot.ylim(0, 1.6)
        plot.scatter(xs, ys)
        plot.plot(xs, yps)
        plot.pause(0.01)

plot.show()

运行结果如下:

  •  利用Sigmoid激活函数实现神经网络

        如果样本又是怎么样的呢?

 像这种样本数据是这种分散的, 我们再用直接来设计我们神经网络是不行的,这时我们就需要曲线。

 我们可以直接用激活函数,常见的激活函数有:

 而我们上面的样本数据,可以用 Sigmoid 激活函数,Sigmoid 函数的公式是

 Sigmoid 函数图如下, 竟然是一条直线,真是让人惊呆了。不是说好了曲线吗?竟然不按套路出牌,这其实是 Sigmoid 函数很受样本数据的影响,对 x 的取值范围是有要求的,如果样本集太小的话,就像下面那样,展示出来是一条直线。

 

 那如何解决这个问题?

我们可以结合线性函数和sigmoid函数来解决这个问题:

  1. 保持样本集不变,将样本输入线性函数,得到线性函数的结果y
  2. 再把线性函数的输出结果,输入到 sigmoid 函数,得到 sigmoid 函数的结果 z
  3. 判断 z 和样本真实值之间的误差 e,通过不断调整线性函数的 w 和 b,使得误差 e 越来越小

在这个过程中,就能通过不断调整w和b的值,使得原本的样本x的值,经过线性函数后,得出的结果能符合sigmoid的范围。

线性函数为:

 Sigmoid 函数为:

 线性函数的值,需要传给 Sigmoid 函数,则预测函数为

 我们还是使用梯度下降的方式来找到误差最小的点,来调整 w 和 b 的值,也就是求 导。

均方误差:

 对于这种复杂函数的求导,我们可以做拆分

那么:

  • e对w的导数,为e对k的导数*k对t的导数*t对w的导数
  • e对b的导数,为e对k的导数*k对t的导数*t对b的导数

e对k的导数为:

k对t的导数为:

t对w的导数为:

t对b的导数为:

代码实战:

import numpy as np

def get(counts):
    xs = np.sort(np.random.rand(counts))
    ys = []
    for i in range(counts):
        if i < counts / 2:
            ys.append(0)  
        else:
            ys.append(1) 
    ys = np.array(ys)

    return xs, ys
import matplotlib.pyplot as plot
import numpy

import house

xs, ys = house.get(100)

w = 0.1
b = 0.1

for j in range(1000):
    for i in range(len(xs)):
        xi = xs[i]
        yi = ys[i]

        t = w * xi + b
        k = 1 / (1 + numpy.exp(-t))
        e = (yi - k) ** 2

        dedk = -2 * (yi - k)
        dkdt = k * (1 - k)
        dtdw = xi
        dtdb = 1

        dedw = dedk * dkdt * dtdw
        dedb = dedk * dkdt * dtdb

        alpha = 0.1  # 学习率,步长
        w = w - alpha * dedw
        b = b - alpha * dedb

    if (j % 100 == 0):
        yps = 1 / (1 + numpy.exp(-(w * xs + b)))  # 新的预测函数
        plot.clf()  # 清空重新绘制,这样就能看到动态效果
        plot.xlim(0, 1)
        plot.ylim(-0.2, 1.2)
        plot.scatter(xs, ys)
        plot.plot(xs, yps)
        plot.pause(0.01)

plot.show()

运行结果如下:

  •  使用 PyTorch 框架快速构建一个神经网络、

      上面创建神经网络的流程都好复杂,可以直接使用 PyTorch 框架,快速构建一个神经网络 , PyTorch 是 Python 语言开发的深度学习框架,专门针对 GPU加速的深度神经网络编程。

Github 地址:https://github.com/pytorch/pytorch

官网:https://pytorch.org/

论坛:https://discuss.pytorch.org/

代码实战:

import matplotlib.pyplot as plt
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader

# 数据
xs = np.array([0.00335400, 0.01282081, 0.03225714, 0.0418231, 0.06224218, 0.06963194,
               0.08320899, 0.08896305, 0.10781348, 0.12513706, 0.12787409, 0.15146274,
               0.16579344, 0.17624051, 0.18054, 0.19480951, 0.29320166, 0.29657286,
               0.31641167, 0.32839255, 0.33380987, 0.34495281, 0.37497198, 0.39868539,
               0.44614808, 0.46220023, 0.48874932, 0.51609881, 0.52824768, 0.54047126,
               0.54360899, 0.54395718, 0.55205861, 0.56033965, 0.57219896, 0.65985412,
               0.66736228, 0.67859612, 0.68135888, 0.68427166, 0.68967927, 0.73490296,
               0.76824992, 0.77612128, 0.79795515, 0.84028841, 0.90513846, 0.92928611,
               0.93766008, 0.9655463])
ys = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
               1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
               1, 1])

class CustomDataset(Dataset):
    def __init__(self, xs, ys):
        self.xs = torch.tensor(xs, dtype=torch.float32).view(-1, 1)
        self.ys = torch.tensor(ys, dtype=torch.float32).view(-1, 1)

    def __len__(self):
        return len(self.xs)

    def __getitem__(self, idx):
        return self.xs[idx], self.ys[idx]

# 模型定义
class SimpleModel(nn.Module):
    def __init__(self):
        super(SimpleModel, self).__init__()
        self.layer1 = nn.Linear(1, 3)
        self.layer2 = nn.Linear(3, 1)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        x = self.sigmoid(self.layer1(x))
        x = self.sigmoid(self.layer2(x))
        return x

dataset = CustomDataset(xs, ys)
batch_size = 8
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)

model = SimpleModel()


# 损失函数和优化器
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=1.0)

# 训练模型
epochs = 20000
for epoch in range(epochs):
    for batch_xs, batch_ys in dataloader:
        model.train()
        optimizer.zero_grad()
        outputs = model(batch_xs)
        loss = criterion(outputs, batch_ys)
        loss.backward()
        optimizer.step()
    if (epoch + 1) % 5000 == 0:
        print(f'Epoch [{epoch + 1}/{epochs}], Loss: {loss.item()}')

# 预测
model.eval()
with torch.no_grad():
    predictions = model(torch.tensor(xs, dtype=torch.float32).view(-1, 1)).numpy()

# 绘图
plt.scatter(xs, ys)
plt.plot(xs, predictions)
plt.show()

运行结果如下:

案例实战

自定义神经网络实现手写体数字图像识别

# 定义模型
import torch
from torch import nn


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(784, 200)
        self.fc2 = nn.Linear(200, 200)
        self.fc3 = nn.Linear(200, 200)
        self.fc4 = nn.Linear(200, 10)

    def forward(self, x):
        x = x.view(-1, 784)  # 展平图像为一维向量
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = torch.relu(self.fc3(x))
        x = self.fc4(x)
        return x
import torch
import torch.nn as nn
import torch.optim as optim
from torch.nn.functional import one_hot
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

from net import Net

device = torch.device('cpu')

# 定义转换操作
transform = transforms.Compose([
    transforms.ToTensor()
])

# 加载MNIST数据集
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)

# 创建数据加载器
train_loader = DataLoader(dataset=train_dataset, batch_size=6000, shuffle=True)
test_loader = DataLoader(dataset=test_dataset, batch_size=1000, shuffle=False)


# 实例化模型
model = Net()
model = model.to(device)

# 定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=1)

# 训练模型
for epoch in range(200):
    for i, (images, labels) in enumerate(train_loader):
        images = images.to(device)
        labels = labels.to(device)

        # 转换标签为one-hot编码
        labels = one_hot(labels, num_classes=10).float()

        # 前向传播
        outputs = model(images)
        loss = criterion(outputs, labels)

        # 反向传播和优化
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    print(f'Epoch [{epoch+1}/200], Loss: {loss.item():.4f}')

# 保存模型
torch.save(model.state_dict(), "best.pt")
import torch
from PIL import Image
from torchvision import transforms

from net import Net

device = torch.device('cpu')

model = Net()
model = model.to(device)

# 加载训练好的模型权重
model.load_state_dict(torch.load("best.pt"))

# 定义转换操作
transform = transforms.Compose([
    transforms.ToTensor()
])


def load_image(image_path):
    image = Image.open(image_path).convert('L')  # 转换为灰度图像
    image = transform(image)
    image = image.unsqueeze(0)  # 添加批次维度
    return image

# 从https://huggingface.co/datasets/ylecun/mnist?image-viewer=image-0-5F2BE6C77CB0003C82CE3E8D89EDAE0F36E709EC下载图片到项目中
image_path = './img.png'

image = load_image(image_path)
image = image.to(device)

model.eval()
with torch.no_grad():
    outputs = model(image)
    _, predicted = torch.max(outputs.data, 1)
    print(f'Predicted label: {predicted.item()}')

 运行结果如下:

Predicted label: 5

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值