加载数据
import torch
import numpy as np
from sklearn.model_selection import train_test_split
from torch.utils.data import Dataset, DataLoader
from torch import nn
from matplotlib import pyplot as plt
X = []
y = []
with open(file='boston_house_prices.csv') as f:
next(f)
next(f)
for line in f:
line = line.strip()
if line:
sample = [float(ele) for ele in line.split(",") if ele]
X.append(sample[:-1])
y.append(sample[-1])
#切分数据
X_train, X_test, y_train, y_test = train_test_split(X, y , test_size=0.2, random_state=0)
数据转numpy
X_train = np.array(X_train)
X_test = np.array(X_test)
标准化
mu = X_train.mean(axis=0)
sigma = X_train.std(axis=0)
X_train = (X_train-mu)/sigma
X_test = (X_test-mu)/sigma
数据批量打包
class HouseDataset(Dataset):
def __init__(self, X , y):
self.X = X
self.y = y
def __len__(self):
return len(self.X)
def __getitem__(self, idx):
x = self.X[idx]
y = self.y[idx]
#数据预处理, 转张量
x = torch.tensor(x, dtype=torch.float32)
y = torch.tensor([y], dtype=torch.float32)
return x,y
构建模型
class HouseModel(nn.Module):
def __init__(self, in_features, out_features):
super(HouseModel, self).__init__()
#线性层(全连接)
self.fc = nn.Linear(in_features=in_features, out_features=out_features)
#这里可以使用多次线性模型
#比如self.fc1 = nn.Linear(in_features=in_features, out_features=8)
#
#self.fc2 = nn.Linear(in_features=8, out_features=out_features)
#
def forward(self, X):
y_pred = self.fc1(X)
#使用激活函数
#y_pred = nn.Relu()(y_pred)
y_pred = self.fc2(y_pred)
return y_pred
准备训练
model = HouseModel(13, 1)
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)
loss_fn = nn.MSELoss()
epochs = 100
train_house_dataset = HouseDataset(X_train, y_train)
train_house_dataloader = DataLoader(train_house_dataset, batch_size=10, shuffle=True)
test_house_dataset = HouseDataset(X_test, y_test)
test_hoset_dataloader = DataLoader(test_house_dataset, batch_size=30, shuffle=True)
训练监听
def get_acc(dataloader):
losses = []
model.eval()
with torch.no_grad():
for X,y in dataloader:
y_pred = model(X)
loss = loss_fn(y_pred, y)
losses.append(loss.item())
final_loss = sum(losses)/len(losses)
final_loss = round(final_loss, ndigits=5)
return final_loss
训练
def train():
train_acces = []
test_acces = []
model.train()
for epoch in range(epochs):
for X,y in train_house_dataloader:
optimizer.zero_grad()
y_pred = model(X)
loss = loss_fn(y_pred , y)
loss.backward()
optimizer.step()
train_acc = get_acc(train_house_dataloader)
test_acc = get_acc(test_hoset_dataloader)
train_acces.append(train_acc)
test_acces.append(test_acc)
print(f"训练{epoch+1}轮, test_acc:{test_acc},test_acc:{test_acc}")
return train_acces, test_acces
train_acces, test_acces = train()
绘图
plt.plot(train_acces, color="r")
plt.plot(test_acces, color="b")
plt.xlabel(xlabel="epoch")
plt.ylabel(ylabel="acc")
plt.grid()
概率模拟
由于分类问题,模型是不会直接输出预测类别,而是输出每个类别的数字(或者叫权重),此时我们需要通过模拟概率将每个类别的数字转换为[0, 1]之间的概率。
常见方法
- 二分类概率模拟:Sigmoid函数,取值范围在(0, 1)。
def sigmod(x):
"""
sigmoid
- (0, 1)
- 单调递增函数
- x为0时,模拟概率为0.5
- x < 0时,模拟概率<0.5
- x > 0时,模拟概率>0.5
"""
return 1 / (1 + np.exp(-x))
x = np.linspace(start=-5, stop=5, num=30)
plt.plot(x, sigmod(x))
plt.grid()
Copy
- 多分类概率模拟:Softmax函数:
import numpy as np
def softmax(logits):
"""
softmax:
- 原来比较大的数,模拟概率也比较大
"""
# 转化为np数组
logits = np.array(logits)
# 转化为正数
logits = np.exp(logits)
# 模拟概率
return logits /logits.sum()
# 模型输出的都是原始数据
logits = [0.6 , -3.6, 18.9 ]
logtis = np.array(logits)
softmax(logits)
Copy
运行结果:
one-hot编码
在《【课程总结】Day6(上):机器学习项目实战–外卖点评情感分析预测》中,我们曾接触过one-hot编码。本次我们再做下回顾:
-
这是一种状态编码,适合于离散量内涵
-
特征编码时:
以汽车的行驶状态举例:
- 三个状态:左转0,右转1,直行2
- 左转:[1, 0, 0]
- 右转:[0, 1, 0]
- 直行:[0, 0, 1]
-
标签编码时:
以鸢尾花类别举例:
- 三种花:第一种0,第二种1,第三种2
- 第一种:[1, 0, 0]
- 第二种:[0, 1, 0]
- 第三种:[0, 0, 1]
-
特点:
- 互相垂直的向量,没有鲜艳的远近关系
- 都是比较长的向量,跟类别数量一致
- 每个向量只有1位是1,其余都是0(这种状态被称为高度稀疏sparse)
- 从计算和存储上来说,都比较浪费性能
神经网络在预测多类别分类任务时:n_classes个类别的预测问题,模型会输出n_classes个概率。
以鸢尾花为例,神经网络不会直接输出预测的结果是哪个花的类别,而是给出三种花各自的概率,只不过最高概率的哪种花可能就是我们需要的预测结果类别。
交叉熵
作用:从分布的角度来衡量两个概率的远近程度
计算过程:
# 以鸢尾花分类举例:
# 假设我们有一个真实结果y_true,结果是第2类,索引号是1
# 真实结果:y_true: 第2类,索引号为1
# 预测结果1:y_pred1: [12.5, -0.5, 2.7]
# 预测结果2:y_pred2: [-12.5, 6.4, 2.7]
# 第一步:使用one-hot对类别进行编码
y_true = np.array( [0, 1, 0])
# 第二步:进行第一次预测
# 1、对预测结果使用softmax进行概率模拟
y_pred1 = np.array([12.5, -0.5, 2.7])
y_pred1 = softmax(y_pred1)
# 执行结果:
# y_pred1
# array([9.99942291e-01, 2.26019897e-06, 5.54483994e-05])
# 2、计算交叉熵损失 y_true @ y_pred1
loss1 = -(0 * np.log(9.99942291e-01) + 1 * np.log(2.26019897e-06) + 0 * np.log(5.54483994e-05))
# 执行结果
# loss1
# 13.00005770873235
# 第三步:进行第二次预测
y_pred2 = np.array( [-12.5, 6.4, 2.7])
y_pred2 = softmax(y_pred2)
# 执行结果:
# y_pred2
# array([6.04265198e-09, 9.75872973e-01, 2.41270213e-02])
loss1 = -(np.log(9.75872973e-01))
# 执行结果:
# loss1
# 0.024422851654124902
Copy
通过上面计算可以看到:
-
loss1交叉损失熵最低时,也就是预测结果[-12.5, 6.4, 2.7]与真实结果(第2类)相匹配,由此得到交叉熵越低,预测结果与真实值差异越小。
-
交叉熵相比MSE,交叉熵计算代码要小。
- 在计算交叉熵时,计算方法是进行第i个类别one-hot编码与第i个类别概率相乘,最后再相加;
- 表面上看求了多个熵,实际上只求1个熵(因为one-hot编码为0的位置实际没有参与计算);
激活函数
-
Sigmoid 函数
- 公式:
sigmoid(x)=11+e−xsigmoid(x)=1+e−x1 - 特点:将输入值压缩到 0 到 1 之间,常用于输出层的二分类问题,但容易出现梯度消失问题.
- 公式:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-5, 5, 100)
sigmoid = 1 / (1 + np.exp(-x))
plt.plot(x, sigmoid, label='Sigmoid Function')
plt.xlabel('x')
plt.ylabel('sigmoid(x)')
plt.legend()
plt.show()
Copy
-
Tanh 函数
- 公式:
tanh(x)=ex−e−xex+e−xtanh(x)=ex+e−xex−e−x - 特点:将输入值压缩到 -1 到 1 之间,解决了 Sigmoid 函数的零中心问题,但仍存在梯度消失问题。
- 公式:
import numpy as np
import matplotlib.pyplot as plt
# 生成 x 值
x = np.linspace(-5, 5, 100)
# 计算双曲正切函数的 y 值
y = np.tanh(x)
# 绘制双曲正切函数图像
plt.figure(figsize=(8, 6))
plt.plot(x, y, label='tanh(x)')
plt.xlabel('x')
plt.ylabel('tanh(x)')
plt.title('Plot of Hyperbolic Tangent Function')
plt.grid(True)
plt.legend()
plt.show()
Copy
-
ReLU 函数(Rectified Linear Unit)
- 公式:
ReLU(x)=max(0,x)ReLU(x)=max(0,x) - 特点:简单且高效,解决了梯度消失问题,但可能导致神经元死亡问题(输出恒为 0)
- 公式:
import numpy as np
import matplotlib.pyplot as plt
# 定义 ReLU 函数
def relu(x):
return np.maximum(0, x)
# 生成 x 值
x = np.linspace(-5, 5, 100)
# 计算 ReLU 函数的 y 值
y = relu(x)
# 绘制 ReLU 函数图像
plt.figure(figsize=(8, 6))
plt.plot(x, y, label='ReLU(x)')
plt.xlabel('x')
plt.ylabel('ReLU(x)')
plt.title('Plot of Rectified Linear Unit (ReLU) Function')
plt.grid(True)
plt.legend()
plt.show()
Copy
内容小结
-
模型训练本质:固定w和b参数的过程
- 让模型更好→本质上就是让模型的损失值loss变小;
- 让loss变小→本质上就是求loss函数的最小值;
-
深度学习的整体项目路程
- 第一步:读取数据、切分数据、数据规范化
- 第二步:批量化打包数据
- 第三步:构建模型
- 第四步:筹备训练,定义损失函数、定义优化器、定义训练次数、定义学习率
- 第五步:训练模型,在训练的过程中需要对损失值进行监控
- 第六步:推理模型,即直接将待预测样本数据代入模型求预测值y_pred即可
-
第三步构建模型时
- 一般常用自定义class类的方法,这种方法更加灵活
- 为了降低模型的损失值,可以采用加入激活函数以及多层感知机的方式
-
第五步实现训练模型时
- 基本的五步法:1.正向传播→2.损失计算→3.反向传播→4.优化一步→5.清空梯度
-
使用深度学习进行分类问题时,流程与回归问题基本一致,不同的地方在于
- 损失函数计算方法不同:回归问题使用的是MSE,分类问题使用的是交叉熵。
- 训练过程中监控的指标不同,回归问题监控MSE,分类问题监控的是准确率。
-
神经网络在处理分类问题时,输出的不是某个类别,而是不同类别的概率。
- 在输出概率时,需要进行概率模拟
- 二分类问题概率模拟时,使用Sigmoid函数;多分类概率模拟时,使用的是softmax函数