目录
前言介绍
大模型的本质是机器学习, 机器学习的本质就是一种数学模型,而现在主流的大模型都是基于神经网络模型构建的数学模型,不论是基于卷积神经网络(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函数来解决这个问题:
- 保持样本集不变,将样本输入线性函数,得到线性函数的结果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 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://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