深度学习实现步骤

121 阅读7分钟

加载数据

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函数