[Python] 从0到1实现一个简单的数字图像识别大模型
目录
前言介绍
神经网络
简单的神经网络
使用均方误差与正规方程实现神经网络
随机梯度下降与批量梯度下降实现神经网络
用更复杂的梯度下降实现一个神经网络
利用Sigmoid激活函数实现神经网络
使用 PyTorch 框架快速构建一个神经网络、
案例实战
前言介绍
大模型的本质是机器学习, 机器学习的本质就是一种数学模型,而现在主流的大模型都是基于神经网络模型构建的数学模型,不论是基于卷积神经网络(CNN),还是循环神经网络(RNN),亦或者是Transformer神经网络等。所以所谓的大模型,就是一个很复杂的函数,训练它的样本集很大、参数很多。
神经网络模型是一种基于人工神经元的数学模型,用于模拟人脑的神经网络结构和功能。 神经网络模型有很多层,每一层都有很多个神经元,每一层又是相互连接。每个神经元又由很多参数组成,平时我们常常所说的某个大模型有多少亿参数,就是指所有神经元加起来参数之和。参数越多,大模型的功能就越强大。
一般情况下,大模型的参数是在网络架构时就设定好的,参数数量一般不会发生变化;但也有例外情况,比如动态神经网络就会对参数数量进行动态调整。
大模型训练的本质就是调整参数,训练的过程其实就是把训练数据输入到大模型中,然后模型根据这些数据对参数进行调整的过程,以求达到一个最优解。因为模型有多个神经层,所以训练数据从输入层进入大模型之后;需要在模型的多个神经层之间进行流转,而这个过程术语叫做正向传播。
数据从输入层,一层一层的传播到输出层,然后输出结果;但由于大模型刚开始就像一个小学生,所以它输出的结果往往不尽人意。所以,为了解决这个问题,大模型的输出结果需要跟实际结果进行匹配,术语叫做计算损失差,损失差越大说明输出结果越差。
而有了损失差,说明当前的模型是有问题的;所以就需要对模型进行调整,这就是所谓的反向传播。
神经网络
大模型是基于神经网络构建的。接下来,我们将通过多种数学模型来实现神经网络的优化。
-
简单的神经网络
对于 ypi = w * xi 函数,其实是属于神经网络中的一层,w 是参数,大模型训练调整的就是这个参数。而 xi 和 ypi 在数学中是自变量和因变量,而在神经网络中,它是样本数据,是固定的,也叫训练数据。
在训练中,不断的根据预测值和真实值,算出他们之间的误差,然后调整 w 的值,误差越小,代码模型预测能力越好。
训练数据代码
import numpy as npdef get(count):# 生成count个0-1之间随机数xs = np.sort(np.random.rand(count))ys = []for x in xs:y = 1.5 * x + np.random.rand() / 10ys.append(y)ys = np.array(ys)# 变为负数# xs = xs * -1# ys = ys * -1return xs, ys
神经网络代码
import matplotlib.pyplot as plotimport housexs, 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 plotimport housexs, ys = house.get(100)
ys[99] = 2w = 0.5sum_xy = 0
sum_xx = 0
for i in range(100):xi = xs[i]yi = ys[i]sum_xy += xi * yisum_xx += xi * xiw = sum_xy / sum_xxyps = w * xsplot.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 plotimport housexs, 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,要增加wyps = 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 numpyimport housexs, 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) / 10b = -2 * numpy.sum(xsi * ysi) / 10k = 2 * a * w + b # k表示w点的斜率w = w - alpha * k # k大于0,要减少w,k小于0,要增加wyps = 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 npdef get(count):# 生成count个0-1之间随机数xs = np.sort(np.random.rand(count))ys = []for x in xs:y = 1.5 * x + np.random.rand() / 10ys.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 housexs, 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 * dwb = b - alpha * dbyps = 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函数来解决这个问题:
- 保持样本集不变,将样本输入线性函数,得到线性函数的结果y
- 再把线性函数的输出结果,输入到 sigmoid 函数,得到 sigmoid 函数的结果 z
- 判断 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 npdef 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 numpyimport housexs, ys = house.get(100)w = 0.1
b = 0.1for j in range(1000):for i in range(len(xs)):xi = xs[i]yi = ys[i]t = w * xi + bk = 1 / (1 + numpy.exp(-t))e = (yi - k) ** 2dedk = -2 * (yi - k)dkdt = k * (1 - k)dtdw = xidtdb = 1dedw = dedk * dkdt * dtdwdedb = dedk * dkdt * dtdbalpha = 0.1 # 学习率,步长w = w - alpha * dedwb = b - alpha * dedbif (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 xdataset = 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 nnclass 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, transformsfrom net import Netdevice = 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 transformsfrom net import Netdevice = 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