深度学习项目基本知识目录:
1、多层神经网络、多层感知;
2、python基本操作、环境搭建。Pythorch介绍;
3、深度学习解决回归任务。回归代码实战:新冠人数预测。(看懂项目、模型保存、预测,训练集和验证集划分,测试评估)
4、图片分类任务。卷积神经网络,经典卷积神经网络
5、4点的实战——食物分类任务。(迁移学习、图片增广,学会写一个项目)
6、深度学习与特征的关系,承上启下
7、NLP介绍(RNN、LSTM介绍)。主要介绍sel-attention机制与bert模型代码讲解。(介绍无监督和自监督)
8、BERT实战,文本情感分类任务
9、初识生成任务,认识生成任务项目代码。
10、ViT模型 与 多模态任务,大模型简介
零、机器学习与深度学习
什么是深度学习? 机器学习的一个子集,利用多层神经网络从大量数据中进行学习
传统系统:给规则、给数据,据此做决策,生成答案。所有情况都当作规则传入系统
机器学习:输入数据和答案,妄图让电脑自己学习规则,因无法穷尽所有情况,电脑不知道所有情况,而是根据一部分情况推理出一套规则。见到新数据有自己的推测
狭义的机器学习:强可解释性,经典机器学习算法:KNN、决策树、朴素贝叶斯
KNN:(K-Neareast Neighbors)K最近邻居 通过看离他最近的一部分数据推测他的情况。一种监督学习的算法(“监督”的特征:有标签,类似某种属性)用于分类和回归。基本思想:通过测量不同数据点之间的距离来进行预测。
工作原理概括:1、距离度量:KNN使用距离度量(通常欧氏距离)来衡量数据点之间的相似性 2、确定邻居数量k 3、投票机制
决策树 :根据以往的数据画决策树,不善于没有见过的特征,需要添加其他特征
朴素贝叶斯:经典的后验公式
深度学习:“黑匣子”,无可解释性,以实践为主。深度学习就是找一个函数f。
初识神经网络任务 f( x ) = y 应用:人脸识别、文生图
常见的神经网络输入,一般有三种形式:
1、向量:如身高体重财富;
2、矩阵/张量:如图片颜色RGB;
3、序列:在前后语境中理解,被上下文决定
输出一般有几种:
1、回归任务(填空题)如根据以前的温度推测明天的温度大概有多高,结果可能落在一个范围内;
2、分类任务(选择题)如图片是猫/狗,可以用数字表示类别;
3、生成任务(结构化)(简答题)如chatgpt
练习:
任务 输入 输出
根据一条河十年内三月水位情况, 向量 回归
推测明年三月此河水位
根据视频生成字幕 序列 生成
自动填充代码 序列 生成
判断图中人物是谁 矩阵 回归×分类√
判断两部动漫是否为同一部 向量×2/3 分类
判断动漫声优是否为同一个人 向量×2/3 分类
判断淘宝商品配图和文字标题是否一致 序列 生成×分类√
圈出图片中的羊,并且识别为羊 矩阵 分类×+回归
根据车摄像头的画面,把人,路,车 矩阵 生成(通过分类实现)
的轮廓准确地画出来
CHATGPT 序列 生成
分类和回归是结构化的基础。分类时,用数字表示类别。有的时候需要多个模态的数据,如图片、文字、声音。
回归与神经元 从数据中找到到函数
如何开始深度学习?
1、定义一个函数(模型,model);2、定义一个合适的损失函数;3、根据损失,对模型进行优化
①如何找一个函数?
②损失函数
③ 优化函数
优化器是深度学习中用于更新模型参数以最小化损失函数的关键组件。
η:人为规定的超参数,其实模型也是一种超参数。learning rate作用就是调整优化一步走多长
一个节点(神经元)代表一个线性公式,现在只能拟合直线函数。
一、多层神经网路
线性函数与多层神经元
一个节点的计算方式
类比:人类大脑接收信息,加工,输出信息。
神经元与矩阵
神经元串联形成神经网络,最后达终点。全等的传递,单纯的串联没有意义。但是人类的神经元存在刺激的阈值,对应人工智能中为激活函数。
激活函数的位置:对r1的值进行运算。
激活函数和非线性因素
常见的激活函数:
1、sigmod
2、relu
激活函数的最重要的特征:能求导!
下面三个表示相同的东西
深度学习(神经网络)本质上就是矩阵运算。
神经网络的参数(可训练的)
联系(一个神经元的简单情况):
所有参数更新:
全连接网络(Fully Connected Network):上一层和下一层每个点都相连。多层感知机:多层的全连接网络。
深度学习的过程:
Neuron:神经元
模型并不是越深越好。
1、如果不加激活函数,最后预测拟合出来的结果只能是线性的,只有加入了激活函数后,才能预测非线性的函数。一般来说,relu函数拟合效果较比sigmoid函数好,但relu函数看起来没有那么平滑,因其本身就不平滑,可以研究不同激活函数对实验结果的影响;
2、不是说更深的层次跑出来拟合效果就一定好,需要不断地调试,过深就可能出现过拟合现象;
3、模型只对特定范围内的训练结果预测较准,而在该范围之外的预测效果较差。
欠拟合:预测与实际相差较大,模型预测结果无法表示真实情况
过拟合:模型觉得自己能力很强,把每个数据点都作为真实数据,把每个点都放在预测函数上,直接表现就是预测函数绘制出来曲曲折折。
函数的结构 = 模型的架构 全凭个人经验去尝试设计
神经网络透析2——神经网络,可以完成超级复杂的任务。诸如图片生成,人脸识别等。
但回归到一些原始纯粹的简单问题上,它表现得可能没那么好。如,判断一个数字是否为偶数,发现数字规律等。
二、python基础与线性表示代码带写
import torch
import matplotlib.pyplot as plt # 用于画图
import random # 用于产生随机数
# 生成数据
def create_data(w, b, data_num):
# 生成输入数据 x,形状为 (data_num, len(w))
x = torch.normal(0, 1, (data_num, len(w)))
y = torch.matmul(x, w) + b # matmul表示矩阵相乘
# 添加噪声
noise = torch.normal(0, 0.01, y.shape) # 噪声要加到y上
y += noise
return x, y # x代表features,y代表label
num = 500 # 代表生成的数据样本的数量
# 真实的w和b,下面的模型训练过程并不会用到
# 形状 (4,):表示张量有4个元素,且这些元素排列成一个一维数组
true_w = torch.tensor([8.1, 2, 2, 4]) # 注意形状为4×1
true_b = torch.tensor([1.1])
# 产生实验数据集
X, Y = create_data(true_w, true_b, num)
"""
plt.scatter(x, y, s, c, ...)
x:表示散点图中每个点的 x 坐标。
y:表示散点图中每个点的 y 坐标。
s:表示每个点的大小(可选参数)。
c:表示每个点的颜色(可选参数)。
"""
# 提取 X 的第 4 列, Y 是一个一维数组或张量,通常表示目标值(标签),每个点的大小被设置为 1
# plt.scatter(X[:, 3], Y, 1) # 绘制散点图
# plt.show() # 展示散点图
# 每次访问这个函数,就能提供一批数据
def data_provider(data, label, batchsize):
length = len(label)
indices = list(range(length)) # 创建一个列表,包含了从0到length-1的所有整数,这些整数对应于数据样本的索引
# 一般不能按顺序取, 把数据打乱
random.shuffle(indices)
# 生成的序列是一个从0开始,每次增加batchsize直到但不包括length的整数序列
for each in range(0, length, batchsize):
get_indices = indices[each:each + batchsize]
get_data = data[get_indices]
get_label = label[get_indices]
yield get_data, get_label # 有存档点的return
"""
这段代码的作用是测试 data_provider 函数是否正确工作,
通过打印出第一批数据来验证数据生成器的输出。
它在调试阶段是有用的,但在最终的训练代码中并不是必要的
"""
batchsize = 16
# for batch_x, batch_y in data_provider(X, Y, batchsize):
# print(batch_x, batch_y)
# break
# 计算y的预测值,即模型
def predict(x, w, b):
pred_y = torch.matmul(x, w) + b
return pred_y
# 计算Loss
def maenLoss(pre_y, y):
return torch.sum(abs(pre_y - y)) / len(y)
# 随机梯度下降,更新所有参数
def sgd(paras, lr):
# 在进行参数更新时,我们通常不需要再次计算梯度,这样可以节省计算资源
with torch.no_grad(): # 属于这句代码的部分,不计算梯度
for para in paras:
para -= para.grad * lr # 不能写成 para = para - para.grad * lr
para.grad.zero_() # 使用过的梯度归零,以便梯度信息不会累积
lr = 0.028
w_0 = torch.normal(0, 0.01, true_w.shape, requires_grad=True)
b_0 = torch.tensor([0.01], requires_grad=True)
# print(w_0, b_0)
epochs = 50 # 训练次数
for epoch in range(epochs):
data_loss = 0
for batch_x, batch_y in data_provider(X, Y, batchsize):
pred_y = predict(batch_x, w_0, b_0)
loss = maenLoss(pred_y, batch_y)
loss.backward()
sgd([w_0, b_0], lr)
data_loss += loss # 将当前批次的损失loss加到累积的损失data_loss上
print("epoch %03d: loss: %.6f" % (epoch, data_loss))
print("真实的函数值是 ", true_w, true_b)
print("深度训练得到的参数值是", w_0, b_0)
idx = 3
# 预测线
plt.plot(X[:, idx].detach().numpy(), X[:, idx].detach().numpy() * w_0[idx].detach().numpy() + b_0.detach().numpy())
# 真实值散点图
plt.scatter(X[:, idx], Y, 1)
plt.show() # 展示绘图
三、回归项目实战
一个深度学习项目一般包含四个部分:
1、数据处理:负责提供输入x和真实的y,给一个数据的地址(可能是文件或图片形式),提供转换为x(数据)和y(标签)的形式,最复杂的部分,最能提升模型性能的部分;
2、模型部分:定义model或定义f,输入x,输出预测值;
3、超参数部分:人为指定部分,涉及模型框架,比如lr、优化器、损失函数等;
4、训练流程:取数据,用数据得到预测值,与真实值比较得到loss,更新模型,反复训练。
具体地,训练步骤分为训练数据和验证数据。
训练数据:模型训练过的数据
测试数据:只有X没有标签Y
验证集:从训练集划出来的一部分数据,进行验证准确率,挑选最好的再用于外面的测试集。注意,所有用于验证集的数据,不能更新模型,即验证集的数据不能积攒梯度。
训练集:取的时候最好随机取
训练部分:取X取Y,让X通过model得到预测值,与Y进而算出loss,结合学习率,得到梯度,再更新模型,再进行下一次计算训练
验证部分:取X取Y,X通过模型得到预测值,让Y与预测值进行对比,得到一个效果的判断。比如回归模型,就是看预测值和Y是否接近。分类任务,就是看分类是否准确,准确率多少。但并不更新模型,模型只在验证集上验证而不做训练,意味着模型的参数与验证集的数据没有关系。
示例:新冠病毒感染人数预测
假设美国有40个州,这四十个州呢,统计了连续三天的新冠阳性人数,和每天的一些社会特征,比如带口罩情况,居家办公情况等等。现在有一群人比较坏,把第三天的数据遮住了,我们就要用前两天的情况以及第三天的特征,来预测第三天的阳性人数。但幸好的是,我们还是有一些数据可以作为参考的,就是我们的训练集。
一些该项目的参考资料:
1、one hot编码 :独热编码,标着一条数据独来自于一个州
2、pandas库:pandas是一个非常强大的数据分析和处理库, 它提供了丰富且高效的数据结构和数据分析工具,常用于处理结构化数据(如表格数据)
数据结构:pandas
提供了 DataFrame
和 Series
等数据结构,非常适合处理表格数据。
数据操作:可以轻松地进行数据筛选、排序、分组、合并等操作。
数据清洗:支持处理缺失值、重复值等常见问题。
数据导入导出:可以方便地读取和写入多种格式的数据(如 CSV、Excel、SQL 数据库等)。
3、在 PyTorch 中,Dataset
是一个抽象类,用于封装数据集的加载和预处理逻辑。用户可以通过继承 torch.utils.data.Dataset
并实现其方法来定义自己的数据集。
核心方法:
__init__
:初始化方法,用于加载数据集的元数据(如文件路径、标签等)。
__len__
:返回数据集的大小(即样本数量)。
__getitem__
:根据索引 idx
获取单个样本及其标签。
4、DataLoader
:是 PyTorch 中用于加载数据的类,它能够将数据集(Dataset
对象)封装成一个可迭代的加载器,方便批量加载和迭代数据
5、在 PyTorch 中,定义一个神经网络模型通常需要继承 torch.nn.Module
类,并实现两个核心方法:__init__
和 forward
。这两个方法是模型类的核心部分,分别用于初始化模型的结构和定义模型的前向传播逻辑。
__init__
:模型类的构造函数,用于初始化模型的结构,包括定义模型中的层,如全连接层(torch.nn.Linear
)、卷积层(torch.nn.Conv2d
)、激活函数(torch.nn.ReLU
)、池化层(torch.nn.MaxPool2d
)和参数。
forward:
定义了模型的前向传播逻辑,即输入数据如何通过模型的各个层并最终生成输出。
4、动量:一种优化算法的超参数,广泛应用于各种优化器(如SGD、Adam等)中,用于加速模型的收敛速度并提高训练的稳定性。动量是一种通过累积过去梯度信息来加速梯度下降的技术。它通过引入一个“动量项”,记录了之前梯度的累积效果,从而使得参数更新不仅依赖于当前的梯度,还考虑了历史梯度的方向和幅度。类似于一种惯性,梯度下降到到达一个点后还能继续向后走。
代码调试中(包括改进代码,第三点开始)出现的问题:
1、AttributeError: 'CovidDataset' object has no attribute 'y':问题出在 CovidDataset
类中没有正确定义 self.y
属性。在 __getitem__
方法中,代码尝试返回 self.y[idx].float()
,但 self.y
并没有在类的初始化方法 __init__
中被定义为类的属性。因此,当代码尝试访问 self.y
时,会抛出 AttributeError。
2、测试集数据少一列:对于训练集和验证集的不取最后一列,但是对于测试集数据要取最后一列。分析:
train的csv_data:2700*94,94中,x占93,y占1,不包括第一列id且不包括tested_positive是93
而test的csv_data:xxx*93,这93列完全是属于x的数据。
3、ValueError: At least one stride in the given numpy array is negative, and tensors with negative strides are not currently supported. (You can probably work around this by making a copy of your array with array.copy().)解决方法:加上col = col.tolist(),让col变为列表。
项目疑难一、取数据
Q:一定要所有的数据算一次loss吗?
答: 算出来可能很准确,走的方向正确,但这一步要走多长不知道,由于所有的数据都算一次所以走起来很慢,那么每一步走多远影响就很大,很可能走过了,故一般不推荐所有的数据算一次loss。
Q:那么,一个数据更新一次模型好吗?
答:也不行,单独样本具有随机性,可能会偏离中心点较多,所以一般一批一批的数据更新模型,既快又好。
项目疑难二,写模型部分与超参部分
模型维度如何变化?回归任务一般用全连接模型(Linear)
保存路径:模型训练跑到一个比较好的效果的点,需要保存好此时的模型。
基础版项目代码——写上去会被“嘲笑”
import matplotlib.pyplot as plt
import torch
import numpy as np
import csv
import pandas as pd
from torch.utils.data import DataLoader, Dataset
import torch.nn as nn
from torch import optim
import time
class CovidDataset(Dataset):
# init函数主要功能是加载COVID数据集,并根据指定的模式(train、val或test)对数据进行划分和处理
def __init__(self, file_path, mode="train"):
# with 语句:用于确保文件在操作完成后能够被正确关闭。这是一种安全的文件操作方式,即使在文件操作过程中发生异常,文件也会被自动关闭。
with open(file_path, "r") as f:
ori_data = list(csv.reader(f))
csv_data = np.array(ori_data[1:])[:, 1:].astype(float) # 去掉第一行和第一列,并转换类型为float
if mode == "train": # 逢5取1
indices = [i for i in range(len(csv_data)) if i % 5 != 0]
data = torch.tensor(csv_data[indices, :-1]) # x取除了最后一列的数据
self.y = torch.tensor(csv_data[indices, -1]) # y取最后一列数据
elif mode == "val":
indices = [i for i in range(len(csv_data)) if i % 5 == 0]
data = torch.tensor(csv_data[indices, :-1])
self.y = torch.tensor(csv_data[indices, -1])
else:
indices = [i for i in range(len(csv_data))]
data = torch.tensor(csv_data[indices, :]) # 测试集本身就不包含y,所以最后一列数据还是属于x的
self.y = None
# 标准化数据,好处:加速模型收敛、提高模型性能、减少数值计算问题、提高模型的泛化能力以及简化模型的调试和优化
self.data = (data - data.mean(dim=0, keepdim=True)) / data.std(dim=0, keepdim=True)
self.mode = mode
# 根据索引 idx 获取数据集中的单个样本
def __getitem__(self, idx):
if self.mode != "test":
return self.data[idx].float(), self.y[idx].float()
else:
return self.data[idx].float()
# 返回数据集的大小
def __len__(self):
return len(self.data)
# 训练模型,即代码版的神经网络
class MyModel(nn.Module):
# 初始化模型的结构
def __init__(self, inDim): # inDim代表输入维度
super(MyModel, self).__init__()
self.fc1 = nn.Linear(inDim, 64)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(64, 1)
def forward(self, x): # 模型前向过程
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
if len(x.size()) > 1:
x = x.squeeze(dim=1) # 去掉一个维度,与y保持一致
return x
def train_val(model, train_loader, val_loader, device, epochs, optimizer, loss, save_path):
model = model.to(device)
plt_train_loss = [] # 这是一个列表,记录训练集所有轮次的loss值,方便后续的可视化
plt_val_loss = [] # 记录验证集的loss值
min_val_loss = 999999999999
for epoch in range(epochs): # 开始训练
train_loss = 0.0
val_loss = 0.0
start_time = time.time()
"""
这段代码的作用是:
1、逐批次迭代训练数据集。2、对每个批次的数据进行前向传播,计算损失。3、通过反向传播计算梯度,并更新模型的参数。
4、累加每个批次的损失值,计算整个 epoch 的平均损失。5、将每个 epoch 的平均损失值存储到列表中,方便后续的可视化。
"""
model.train() # 模型调为训练模式
for batch_x, batch_y in train_loader:
# 将数据X和目标值(标签)移动到指定的设备(如CPU或GPU)。确保数据和模型在同一设备上
x, target = batch_x.to(device), batch_y.to(device)
pred = model(x) # 输入特征x传递给模型,直接进行前向传播,得到模型的预测值pred
# train_bat_loss = loss(pred, target,model)
train_bat_loss = loss(pred, target) # 存储模型的预测值 pred 和目标值 target 之间的损失
train_bat_loss.backward() # 反向传播的核心步骤,通过计算梯度,为优化器提供更新参数所需的信息
optimizer.step() # 更新模型的参数
optimizer.zero_grad() # 清空之前的梯度信息。在每次迭代开始时,需要清空之前的梯度,以防止梯度累积
# 放到cpu上,并取数值
train_loss += train_bat_loss.cpu().item() # 累加每个批次的损失值,用于计算整个epoch的平均损失
# 这是一个列表,用于存储每个 epoch 的平均损失值,方便后续的可视化
plt_train_loss.append(train_loss / train_loader.__len__()) # train_loader.__len__()代表一共多少批次
"""
这段代码的作用是:
1、将模型切换到评估模式。2、在验证集上逐批次计算验证损失。3、累加每个批次的验证损失,计算整个验证集的平均损失。
4、将每个 epoch 的验证平均损失记录到列表中,方便后续的可视化。5、如果当前 epoch 的验证损失是新的最小值,则保存当前模型。
"""
model.eval() # 模型调为验证模式
with torch.no_grad(): # 禁用梯度计算
for batch_x, batch_y in val_loader:
x, target = batch_x.to(device), batch_y.to(device)
pred = model(x)
# val_bat_loss = loss(pred, target,model)
val_bat_loss = loss(pred, target)
val_loss += val_bat_loss.cpu().item()
plt_val_loss.append(val_loss / val_loader.__len__())
if val_loss < min_val_loss:
torch.save(model, save_path)
min_val_loss = val_loss
# 打印当前训练的进度信息
print("[%03d/%03d] %2.2f sec(s) Trainloss: %.6f Valloss: %.6f" % (
epoch, epochs, time.time() - start_time, plt_train_loss[-1], plt_val_loss[-1]))
# 绘制损失曲线
plt.plot(plt_train_loss)
plt.plot(plt_val_loss)
plt.title("loss")
plt.legend(["train", "val"])
plt.show()
# 得出测试结果
def evaluate(save_path, test_loader, device, rel_path):
model = torch.load(save_path).to(device) #加载模型
rel = []
model.eval() # 模型调为验证模式
with torch.no_grad():
for x in test_loader:
pred = model(x.to(device))
rel.append(pred.cpu().item()) # 将预测值从设备移动到CPU,并转换为标量值
print(rel)
# 设置 newline=''可以防止在写入文件时出现多余的换行符,特别适用于写入 CSV 文件
with open(rel_path, "w", newline='') as f: # 保存结果到 CSV 文件
csv_writer = csv.writer(f)
csv_writer.writerow(["id", "tested_positive"])
for i in range(len(rel)):
csv_writer.writerow([str(i), str(rel[i])]) # writerow会自动换行
print("文件已经保存到{}".format(rel_path))
# 文件路径
train_file = "covid.train.csv"
test_file = "covid.test.csv"
# 创建数据集实例
train_dataset = CovidDataset(train_file, "train")
val_dataset = CovidDataset(train_file, "val")
test_dataset = CovidDataset(test_file, "test")
batch_size = 16
# 创建一个 DataLoader,用于从 train_dataset 中批量加载训练数据,并在每个epoch开始时随机打乱数据。
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=1, shuffle=False)
# for batch_x, batch_y in train_loader:
# print(batch_x, batch_y)
model = MyModel(inDim=93)
# predy = model(batch_x)
device = "cuda" if torch.cuda.is_available() else "cpu"
print(device)
# 超参数字典,方便随时调用
config = {
"lr": 0.001,
"epochs": 20,
"momentum": 0.9,
"save_path": "model_save/best_model.pth",
"rel_path": "pred.csv"
}
model = MyModel(inDim=93).to(device) # 用于实例化一个模型并将其移动到指定的设备(如CPU或GPU)
loss = nn.MSELoss() # 均方误差损失函数(Mean Squared Error Loss,简称 MSE)
# 创建一个 SGD 优化器实例,用于更新模型的参数
optimizer = optim.SGD(model.parameters(), lr=config["lr"], momentum=config["momentum"])
# 开始训练和验证流程并可视化loss值
train_val(model, train_loader, val_loader, device, config["epochs"], optimizer, loss, config["save_path"])
# 测试过程
evaluate(config["save_path"], test_loader, device, config["rel_path"])
项目代码改进——复试可以提到的
一、正则化:正则化通过在损失函数中添加一个正则化项,对模型的参数施加约束,从而减少模型的复杂度。起到缓解过拟合作用,使得预测函数曲线更加平滑。改进loss函数,在loss函数上加上正则项可以显著地提升模型性能。一般正则化时,w平方前面会乘上一个小的正则化系数,很小。
# 正则化代码
def mseLoss_with_reg(pred, target, model):
loss = nn.MSELoss(reduction='mean')
''' Calculate loss '''
regularization_loss = 0 # 正则项
for param in model.parameters():
# TODO: you may implement L1/L2 regularization here
# regularization_loss += torch.sum(abs(param)) L1正则项
# 使用L2正则项
regularization_loss += torch.sum(param ** 2) # 计算所有参数平方
return loss(pred, target) + 0.00075 * regularization_loss # 返回损失。
二、相关系数:线性相关(SelectKBest)。
在深度学习中,相关系数用于衡量特征与目标变量之间的线性相关性。由于每一列x对y(标签)的影响程度或相关程度不同,通过计算相关系数,可以评估特征对目标变量的影响,从而进行特征选择和模型优化。具体代码如下:
model = SelectKBest(chi2, k=k) # 定义一个选择k个最佳特征的函数
feature_data = np.array(feature_data, dtype=np.float64) # 类型转换
X_new = model.fit_transform(feature_data, label_data) # 用这个函数计算相关系数,选择k个最佳特征
# feature_data是特征数据,label_data是标签数据,该函数可以选择出k个特征
print('x_new', X_new)
scores = model.scores_ # scores,即features每一列与label的相关系数
# 按重要性排序,选出最重要的k个
indices = np.argsort(scores)[::-1]
# argsort函数,排序(从小到大)后是排好序的对应值的下标。
# [::-1]表示反转一个列表或者矩阵,逆置。
# 执行完,indices里面其实是scors最高的下标排在前面
三、主成分分析Principal Component Analysis,PCA(老师喜欢问)
作用降维,同时尽可能保留原始数据的方差信息。如果用一条轴最好地表示/区分这份数据,如何确定这条轴?那么这些数据在这条轴上投影,应该尽可能使得方差最大。具体算法做法感兴趣可以自行搜索。
项目复盘——一定要懂的两个点,反复听
①、一个深度学习的项目到底是在做什么?
答:整个项目做完的目的是得到一个好的模型,进而预测一个好的y。为了达到这个目的,我们要:让x通过模型得到预测值y,再与真实值y计算loss,根据loss计算梯度再更新模型(更新参数),反复如此训练,以达到这个模型可以预测一个比较准确的y。
具体训练过程:对于所有的数据,每次取一批数据,让这一批数据经过model得到预测y再求loss再更新模型,直到所有批次数据取完一轮,就完成了一个周期epoch,然后训练很多轮epoch,再保存一个比较好的模型,最后用这个最好的模型去预测其他未知的y。
②、Linear表示全连接,它可以进行维度的改变。并且一定要会计算神经网络的参数量,例如Linear(A,B),它的参数量为A*B(w的数量)+B(b的数量)
四、分类项目
类别如何表示?——独热编码
分类任务才真实进入深度学习领域。两把神器:nn.Linear(之前)和卷积(这节)。
nn.Linear作用是将矩阵从A维度转为B维度。
如何做分类的输出?根据Linear(?,1)值离谁近。问题:在连续空间上成立,如果仅根据距离判断,会导致每一个类别就不等价了,但实则ABCD类都是等价的。
独热编码:是一种等价的表示方法。所以这里也用独热编码表示类别。注意,标签的含义表示概率。
解决输入输出问题
图片分类任务:输入图片,输出类别。图片基本构成:C,H,W(channel,height,width)。
注意,输入224*224是固定的,因为模型需要一个固定的输入。求概率分布之间的loss,需要用Cross entropy交叉熵。
不妥输出处理方法:将三通道图片拉直,方的矩阵,变成长的向量,再通过Linear(224*224*3,1000),这会导致参数量过大,很容易导致过拟合,故抛弃不用。
正确的输出处理方法:卷积神经网络(Convolutional Neural Network)。下面是一个引入。
目标:如何卷积核变成我们想要的,通过不断地训练,最后得到一个有意义的特征图。注意,原始图片也是一种特征图。
卷积核、感受野、Zero padding技术
卷积核是卷积神经网络(CNN)中的一个核心组件,通常是一个小矩阵(如 3×3 或 5×5),用于在输入数据(如图像)上滑动并执行卷积操作。卷积核中的每个元素都是一个权重参数,这些参数在训练过程中会不断调整,以提取输入数据中的特征。
感受野:卷积核大小又称为神经元的感受野。就是你能看多大。
特征图在卷积的过程中变小了,那么为了统一规划,如何保持尺寸不变呢?
Zero padding:Zero Padding 是一种在卷积神经网络(CNN)中常用的技术,通过在输入数据(如图像)的边缘添加零值像素,来扩展输入数据的大小。padding=x/paddingx,就是在外面加x圈0。
更大的卷积核(可以拥有更大的感受野)和更多的卷积核层数
三个问题:原始特征图深度必须与卷积核深度一致;
一层卷积:原始特征图经过多个卷积核,卷积得到新的特征图;
卷积核的参数量,3(输入特征图数量/输入特征图数量)*3*3(卷积核大小)*5(*5代表一层的参数量/输出特征图数量/深度/卷积核数量),此处忽略bias。
神经网络计算规律:输入特征图大小-卷积核大小+1=输出特征图大小
新的问题:特征图大小一直不变,不管如何卷,参数展平后都需要大量参数。那么特征图如何变小?降采样(Subsampling) 方法1:扩大步长 方法2:依靠Pooling:池化
扩大步长:通过在卷积操作中直接设置步长大于1,可以减小特征图的尺寸,但可能会丢失部分特征信息且引入计算。
池化:通过在卷积操作后单独进行池化操作,可以减小特征图的尺寸,同时保留重要特征信息,增加模型的平移不变性。
池化(Pooling)通过在输入特征图上滑动一个池化窗口,对窗口内的值进行某种操作(如取最大值或平均值),从而减少特征图的空间维度。常见的池化操作有:最大池化(Max Pooling):取池化窗口内的最大值;平均池化(Average Pooling):取池化窗口内的平均值;适应性平均池化(Adaptive Average Pooling):根据输入特征图的尺寸和目标输出尺寸动态调整池化窗口的大小。一般而言,池化操作比步长用的多,最大池化比平均池化用的多。
在实战中, 常常卷积和池化相应变化。
模型:卷积到一个小的特征图后展平再经过全连接后到类别。
分类任务如何求Loss?
如何将模型预测结果转换为概率分布形式?借助softmax函数。
利用交叉熵公式求loss:会用就行
总结:一个基本的分类神经网络(图片过程)
经典的神经网络模型及代码(图片分类网络历史和发展)
Sota模型 Sota: the state of the art 最先进模型
经典网络AlexNet
AlexNet 是一种开创性的深度卷积神经网络,由 Alex Krizhevsky、Ilya Sutskever 和 Geoffrey Hinton 在 2012 年提出,并在当年的 ImageNet 大规模视觉识别挑战赛(ILSVRC)中取得了冠军。AlexNet 的出现引起了深度学习的狂潮。创新点:relu、dropout、池化、归一化。
Dropout与归一化
Dropout:随机丢弃部分神经元,可以缓解过拟合。
归一化可以在深度学习的各个角落看到,可以让模型关注数据的分布,而不受数据量纲的影响。 归一化可以保持学习有效性, 缓解梯度消失和梯度爆炸,主要有Feature Normalization(将特征x的某一列拿出来,减去均值再除标准差)、Batch Norm(对每个小批量(mini-batch)的数据进行归一化处理)。
输入数量和输出数量代表channels,Pool(3,2)还是减半效果(向下取整),蓝方框里代表输出特征图数量*大小(尺寸)
代码:
import torchvision.models as models
import torch
import torch.nn as nn
# alexnet = models.alexnet()
# print(alexnet)
class myAlexnet(nn.Module):
def __init__(self):
super(myAlexnet, self).__init__()
self.conv1 = nn.Conv2d(3, 64, 11, 4, padding=2)
self.pool1 = nn.MaxPool2d(3, 2)
self.conv2 = nn.Conv2d(64, 192, 5, 1, padding=2)
self.pool2 = nn.MaxPool2d(3, 2)
self.conv3 = nn.Conv2d(192, 384, 3, 1, 1)
self.conv4 = nn.Conv2d(384, 256, 3, 1, 1)
self.conv5 = nn.Conv2d(256, 256, 3, 1, 1)
self.pool3 = nn.MaxPool2d(3, 2)
self.pool4 = nn.AdaptiveAvgPool2d(6)
self.fc1 = nn.Linear(9216, 4096)
self.fc2 = nn.Linear(4096, 4096)
self.fc3 = nn.Linear(4096, 1000)
def forward(self, x):
x = self.conv1(x)
x = self.pool1(x)
x = self.conv2(x)
x = self.pool2(x)
x = self.conv3(x)
x = self.conv4(x)
x = self.conv5(x)
x = self.pool3(x)
x = self.pool4(x) # batch*256*6*6
"""
这行代码的作用是将张量 x 从多维形状(如 (32, 256, 6, 6))转换为二维形状(如 (32, 9216))。具体来说:
第一个维度保持为批量大小(32)。
第二个维度自动计算为特征向量的长度(256 * 6 * 6 = 9216)。
"""
x = x.view(x.size()[0], -1) # view展平
# x.size()[0]表示张量的第一个维度,通常是批量大小(batch size)
x = self.fc1(x)
x = self.fc2(x)
x = self.fc3(x)
return x
mymodel = myAlexnet() # 实例化
input = torch.ones((4, 3, 224, 224)) # batch * channel * H * W
out = mymodel(input)
print(out.shape)
# 计算模型参数量
def get_parameter_number(model):
total_num = sum(p.numel() for p in model.parameters())
trainable_num = sum(p.numel() for p in model.parameters() if p.requires_grad)
return {'Total': total_num, 'Trainable': trainable_num}
# print(get_parameter_number((mymodel)))
print(get_parameter_number((mymodel.conv1))) # 11*11*3*64+64=23296
print(get_parameter_number((mymodel.conv2))) # 5*5*64*192+192=307392
print(get_parameter_number((mymodel.conv3))) # 3*3*192*384+384=663936
print(get_parameter_number((mymodel.conv4))) # 3*3*384*256+256=884992
print(get_parameter_number((mymodel.conv5))) # 3*3*256*256+256=590080
经典网络VggNet介绍
2014年,VGG网络被提出,其在AlexNet的基础上, 运用了更小的卷积核,并且加深了网络, 达到了更好的效果。创新 : 更深 , 更小。用小卷积核代替大的卷积核(减少了卷积核参数的数量)。
代码:
import torchvision.models as models
import torch.nn as nn
import torch
vgg = models.vgg13()
print(vgg)
class vggLayer(nn.Module):
def __init__(self, in_cha, mid_cha, out_cha):
super(vggLayer, self).__init__()
self.relu = nn.ReLU()
self.pool = nn.MaxPool2d(2)
self.conv1 = nn.Conv2d(in_cha, mid_cha, 3, 1, 1)
self.conv2 = nn.Conv2d(mid_cha, out_cha, 3, 1, 1)
def forward(self, x):
x = self.conv1(x)
x = self.relu(x)
x = self.conv2(x)
x = self.relu(x)
x = self.pool(x)
return x
class MyVgg(nn.Module):
def __init__(self):
super(MyVgg, self).__init__()
self.layer1 = vggLayer(3, 64, 64)
self.layer2 = vggLayer(64, 128, 128)
self.layer3 = vggLayer(128, 256, 256)
self.layer4 = vggLayer(256, 512, 512)
self.layer5 = vggLayer(512, 512, 512)
self.adapool = nn.AdaptiveAvgPool2d(7)
self.relu = nn.ReLU()
self.fc1 = nn.Linear(25088, 4096)
self.fc2 = nn.Linear(4096, 4096)
self.fc3 = nn.Linear(4096, 1000)
def forward(self, x):
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.layer5(x)
x = self.adapool(x)
# x = self.adapool(x)
x = x.view(x.size()[0], -1) # view展平
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
x = self.relu(x)
x = self.fc3(x)
x = self.relu(x)
return x
myVgg = MyVgg()
img = torch.zeros((1, 3, 224, 224))
out = myVgg(img)
print(out.size())
def get_parameter_number(model):
total_num = sum(p.numel() for p in model.parameters())
trainable_num = sum(p.numel() for p in model.parameters() if p.requires_grad)
return {'Total': total_num, 'Trainable': trainable_num}
print(get_parameter_number(myVgg))
print(get_parameter_number(myVgg.layer1)) # (3*64*3*3+64)+(64*64*3*3+64)=38720
print(get_parameter_number(myVgg.layer1.conv1)) # 3*64*3*3+64=1792
print(get_parameter_number(myVgg.layer2)) # (64*128*3*3+128)+147584=221,440
print(get_parameter_number(myVgg.layer2.conv2)) # 128*128*3*3+128=147584
ResNet介绍 右边就是残差连接:输入值加到输出结果上。通过引入残差连接,允许网络中的信号绕过一些层直接传播,从而解决了深度网络中的梯度消失和梯度爆炸问题。
1*1卷积作用,降维,减少参数量。
输出与输入层大小维度不同怎么办?答:通过1*1卷积,并设置步长,使得维度统一。
且,我们注意到nn.BatchNorm2d()归一化函数的参数与上一次卷积完的输出通道数一致。
代码:
import torch
import torch.nn as nn
import torchvision.models as models
resNet = models.resnet18()
print(resNet)
# Residual_block是一个实现残差块的 PyTorch 模块,通过引入残差连接和通道匹配机制,解决了深度网络中的梯度消失和梯度爆炸问题,使得网络可以更深且更稳定。
class Residual_block(nn.Module):
def __init__(self, input_channels, out_channels, down_sample=False, strides=1):
super().__init__()
self.conv1 = nn.Conv2d(input_channels, out_channels,
kernel_size=3, padding=1, stride=strides)
self.conv2 = nn.Conv2d(out_channels, out_channels,
kernel_size=3, padding=1, stride=1)
"""
如果输入通道数和输出通道数不同,或者需要进行下采样,则通过 conv3 对输入 X 进行通道匹配和下采样。
如果不需要通道匹配或下采样,则直接使用输入 X
"""
if input_channels != out_channels: # 输入维度和输出维度不一样不能相加,进行1*1卷积,实现维度匹配
self.conv3 = nn.Conv2d(input_channels, out_channels,
kernel_size=1, stride=strides)
else:
self.conv3 = None
self.bn1 = nn.BatchNorm2d(out_channels) # 作用在第一个卷积层 (self.conv1) 的输出上,对第一个卷积层的输出进行归一化处理
self.bn2 = nn.BatchNorm2d(out_channels) # 作用在第二个卷积层 (self.conv2) 的输出上,对第二个卷积层的输出进行归一化处理
self.relu = nn.ReLU()
def forward(self, X):
out = self.relu(self.bn1(self.conv1(X)))
out = self.bn2(self.conv2(out))
if self.conv3:
X = self.conv3(X)
out += X
return self.relu(out)
class MyResNet18(nn.Module):
def __init__(self):
super(MyResNet18, self).__init__()
self.conv1 = nn.Conv2d(3, 64, 7, 2, 3)
self.bn1 = nn.BatchNorm2d(64)
self.pool1 = nn.MaxPool2d(3, stride=2, padding=1)
self.relu = nn.ReLU()
self.layer1 = nn.Sequential(
Residual_block(64, 64),
Residual_block(64, 64)
)
self.layer2 = nn.Sequential(
Residual_block(64, 128, strides=2),
Residual_block(128, 128)
)
self.layer3 = nn.Sequential(
Residual_block(128, 256, strides=2),
Residual_block(256, 256)
)
self.layer4 = nn.Sequential(
Residual_block(256, 512, strides=2),
Residual_block(512, 512)
)
self.flatten = nn.Flatten()
self.adv_pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Linear(512, 1000)
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.pool1(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.adv_pool(x)
x = self.flatten(x)
x = self.fc(x)
return x
myres = MyResNet18()
x = torch.rand((1, 3, 224, 224))
# out = resNet(x)
out = myres(x)
print(out.size())
def get_parameter_number(model):
total_num = sum(p.numel() for p in model.parameters())
trainable_num = sum(p.numel() for p in model.parameters() if p.requires_grad)
return {'Total': total_num, 'Trainable': trainable_num}
print(get_parameter_number(myres.layer1))
print(get_parameter_number(myres.layer1[0].conv1))
print(get_parameter_number(resNet.layer1[0].conv1))
# Residual_block中的第一个卷积层(conv1),输入通道数为64,输出通道数也为64,卷积核大小为3x3,参数量 = 64*64×3×3+64 = 36928
# Residual_block中的第二个卷积层(conv2)与第一个相同,因为它也有相同的输入和输出通道数,conv2参数量 = 36928
# 每个Residual_block,有两个批量归一化层(bn1和bn2),当 affine=True 时,每个通道都有一个可学习的缩放参数 γ 和一个可学习的平移参数 β
# 故两个归一化曾总参数量 = (64+64)*2 = 256
# layer1有两个Residual_block,参数量 = (36928+36928+256)*2 = 148224