跳转至
机器学习深度学习PyTorchNLPTransformers

机器学习、深度学习与 NLP 学习笔记

这份文档将机器学习、深度学习和自然语言处理笔记整理到同一条学习路径中。内容按照“传统机器学习 → 神经网络与 PyTorch → 自然语言处理”的顺序编排,便于理解这些技术之间的关系。

第一部分:机器学习

1.机器学习的概述

1.1人工智能的三大概述

人工智能(AI):仿智,使用计算机来模拟或者代替人类

机器学习(ML):机器自动学习,不是人为规则编程

深度学习(DL):大脑仿生,设计一层一层的神经元模拟万事万物

人工智能包含机器学习,机器学习包含深度学习

1.2应用领域和发展史

应用领域:人类看到的内容(图形识别)|人类交流的内容(自然语言处理)|数据分析数据挖掘

发展史:1956元年 机器学习 深度学习 transform(bert|gpt) AIGC

1.3人工智能发展的三要素

数据,算法,算力

算法决定了AI能不能学得“巧”,算力决定了AI能不能学得“快”,而数据决定了AI能不能学得“全”。

算力的核心硬件:

CPU(中央处理器):核心少,每个核心的能力极强。擅长处理复杂的串行任务。

GPU(图形处理器):核心极多,每个核心都很笨。擅长处理大规模的并行计算

TPU(张量处理器):专门针对大型网络训练而设计的一款处理器

1.4机器学习的常用术语

样本(sample):一行数据就是一个样本

数据集:所有的样本 由训练集和测试集构成:划分比例:7:3/8:2

特征(feature):一列数据就是一个特征,有时也被叫做属性

标签/目标(label/target) :模型要预测的那一列数据。

1.5机器学习算法的分类

有监督学习 -有特征,有标签 -预测

​ 标签的连续性 -连续回归问题,不连续分类问题

无监督学习 -有特征,无标签 -聚类

半监督 -有特征,少量标签,大多数无标签 -减少专家标注成本

强化学习 -环境、状态、行动、奖励 -决策

算法:

​ 回归问题:线性回归

​ 分类(核心):KNN,决策树,随机森林

​ 聚类问题:KMeans

1.6机器学习的建模流程

1.准备数据:(导包/离线数据)

2.数据预处理:数据集中异常值缺失值的处理

3.特征工程:

​ 3.1 -特征提取:原始数据中提取与任务相关的特征,构成特征向量

​ 3.2 - 特征预处理:特征对模型产生影响;因量纲问题,有些特征对模型影响大、有些影响小

​ 3.3 -特征降维

​ 3.4 -特征选择

​ 3.5 -特征组合

4.模型训练

5.模型预测

6.模型评估

1.7模型拟合问题

过拟合 -模型在训练集表现很好,测试集表现不好。特征过多,学习到了噪声 -减少特征

欠拟合 -模型在训练集表现不好,在测试集表现的也不好。特征过少,增加特征

**泛化 ** :模型在新数据集(非训练数据)上的表现好坏的能力。

奥卡姆剃刀原则:给定两个具有相同泛化误差的模型,较简单的模型比较复杂的模型更可取

1.8机器学习的开发环境

安装的包:pip install sickit-learn

使用的包:from sklearn.linear_model import LinearRegression

2.线性回归

2.1线性回归的分类与公式

y = kx + b -> y = wx + b w权重 b偏置

一元线性回归 多元线性回归

y = w1x1 + w2x2 +w3x3 +...+b

w = [[b],[w1],[w2],[w3],[w4]]

x = [[1],[x1],[x2],[x3],[x4]]

总结:y=w的转置*x

2.2线性回归的API

from sklearn.linear_model import LinearRegression
准备数据
x = [[1],[x1],[x2],[x3],[x4]]
y = [1,2,3,4]
准备模型
model = LinearRegression()
训练模型
model.fit(x,y)
预测
model.predict([[176]])

2.3线性回归实现的原理

误差 = 预测 - 真实

损失函数 - 衡量误差的函数
最小二乘
均方误差  MSE
均方根误差 RMSE
平均绝对误差 MAE

损失函数 = ([160𝑘+(100)56.3])²  + ([166𝑘+(100)60.6])²  + ([172𝑘+(100)65.1])²+ ([174𝑘+(100)58.5])² + ([180𝑘+(100)56.3])²

损失函数' = 函数的瞬时变化率

函数的瞬时变化率 = 0 -> 函数是最小值

损失函数求导 -> 函数最小值

损失函数求偏导 -> 对什么求导就让其他变量为常数

2.4正规方程法

正规方程法和梯度下降法的最终目标都是找出最拟合的线性回归方程使得损失函数最小。

一元线性方程 :损失函数' = 0,求解

多元线性方程: 损失函数’‘ ,求解

不一定有解,如果矩阵不可逆,那就没法求解,所以需要梯度下降法

2.5梯度下降法

通过一步步迭代,无限逼近那个“最优解”。一次下降一点梯度,逐步向0靠近

w新 = w旧 - 学习率*梯度

学习率不能太大,否则会出现梯度震荡

学习率不能太小,否则会出现梯度爆炸

学习率(步长):0.001 ~ 0.01

梯度是函数上升的方向,梯度的反方向是函数下降的方向。就像一个指北针一样

2.6拟合问题,L1L2正则化

过拟合

特征过多,需要删减无用的特征(删是直接不要特征,减少特征的权重)

欠拟合

特征过少,需要增加特征(一般都需要拉去新的特征,测试一般都是加平方,立方...作为测试数据)

正好拟合

解决过拟合的方案:L1正则化&&L2正则化

L1正则化Lasso -权重可能更新到0从而放弃当前的特征

例子:一个员工如果不听话,就进行开除

L2正则化Ridge(岭回归) - 权重不断靠近0,但是不会等于0(重点)

例子:一个员工如果不听话,就降薪

3.KNN

3.1概念

选出距离当前数据最近的K个样本

K值的选择

K值过大:K值的增大就意味着整体的模型变得简单 欠拟合

K值过小:容易受到异常点的影响K值的减小就意味着整体模型变得复杂,容易发生过拟合

分类

基于K个样本进行标签的投票,票数最多的就代表我的特征

回归

基于K个样本进行标签统计,求平均值代表我的标签

3.2API的使用

分类
from sklearn.neighbors import KNeighborsClassifier
回归
from sklearn.neighbors import KNeighborsRegressor
K值得定义默认是5
KNeighborsRegressor(n_neighbors=3)

3.3数据预处理

归一化和标准化主要解决量纲问题导致模型评估较低

归一化

公式 = (特征 - 最小值)/(最大值 - 最小值)

缺点:最大值最小值的异常会影响当前特征

API:MinMaxScaler

scaler = MinMaxScaler()
scaler.fit_transform #因为没标准需要先训练在使用

标准化

公式 = (特征 - 平均值)/标准差

标准差 = 方差**0.5

方差 = 1/n*∑(特征 - 平均值)²

API:StarndardScaler

scaler = StarndardScaler()
scaler.fit_transform  #因为没标准需要先训练在使用

3.4鸢尾花案例

from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
# 1.加载数据
datasets = load_iris()
def get_data():
    # 特征
    print(datasets.data[:5])
    # 标签
    print(datasets.target[:5])
    # 特征名称
    print(datasets.feature_names)
    # 标签名称
    print(datasets.target_names)
def draw_data():
    # 绘制三次,三次都有不同的颜色 - 准备一个颜色列表
    colors = ['red','blue','green']
    # 1.把特征做了处理
    df = pd.DataFrame(datasets.data, columns=datasets.feature_names)
    # 2.处理标签
    df['target'] = datasets.target
    print(df)
    # 3.绘制datasets.data(1.绘制类型为0的 2.绘制类型为1的 3.绘制类型为2的)
    # def_x = df.query('target==0')[datasets.feature_names[0]]
    # def_y = df.query('target==0')[datasets.feature_names[3]]
    # print(def_x[:5])
    # print(def_y[:5])
    plt.figure(figsize=(10,10))
    for i in range(len(colors)):
        plt.scatter(df.query(f'target=={i}')[datasets.feature_names[0]],df.query(f'target=={i}')[datasets.feature_names[3]],color=colors[i])
    plt.xlabel(datasets.feature_names[0])
    plt.ylabel(datasets.feature_names[3])
    plt.legend(datasets.target_names)
    plt.show()
def predict():
    # 2.数据预处理
    x_train,x_test,y_train,y_test = train_test_split(datasets.data,datasets.target,test_size=0.2,random_state=3)# random_state是随机种子
    # 3.特征工程(在这里是可以不需要)
    scaler = StandardScaler()
    x_train = scaler.fit_transform(x_train) # 第一次需要fit+transform
    x_test = scaler.transform(x_test) # 第二次不需要fit
    # 4.模型训练
    model = KNeighborsClassifier(n_neighbors=3) # k默认是5,问题1:k到底是几?
    model.fit(x_train,y_train)   #模型已经有结果了

    # 4.1 - 模型评估,model自身评分 (正确率)
    print(model.score(x_test,y_test)) # 细节点是,参数为:x的数据 + y的数据
    print(model.predict_proba(x_test)) # 问题2:正确率一条就能评估模型的好坏么?

    # 4.2 - 模型测试(验证)+评估
    y_pre = model.predict(x_test) # x_test是要预测的特征,y_pre是预测的结果,y_test是真实的结果
    print(accuracy_score(y_pre,y_test)) #细节点是,参数是:预测的结果 + 真实的结果

    # 4.3 模型的预测(数据需要自己准备,不能用数据集里面的)
    # --------------------------------------------------------------------
    # 数据集 = 训练集0.8 + 测试集0.2
    # 1.模型训练(0.8训练)
    # 2.模型验证(0.2验证)(拿着模型的测试的结果 和 y_test 对比评估模型)
    # 3.模型预测(超出样本集的数据在测试一次)
    test_xdata = [[4.8,3.3,1.1,0.1]] #数据是照着0的特征模拟的
    # 准备的数据也需要标准化
    test_xdata = scaler.transform(test_xdata)
    test_ydata = [0]  # 因为数据是照着0的标签模拟的,所有大概率也是0
    y_predata = model.predict(test_xdata)
    print(y_predata)

if __name__ == '__main__':
    # 1.查看数据
    # get_data()
    # 2.数据绘制
    # draw_data()
    # 3.预测数据
    predict()

第二部分:深度学习

1.为什么学习深度学习?

1.旧的技术不能解决当前问题

2.旧的技术解决当前问题过于复杂(安全)

机器学习与深度学习的关系

机器学习特征工程需要介入,深度学习算法自动计算

2.深度学习的模块

文本 - RNN -循环神经网络 - 词嵌入层+循环层+全连接层

图像 - CNN -卷积神经网络 -卷积神经网络+池化层+全连接层

全连接层 - ANN -人工神经网络

人工神经网络 - 张量

3.核心点提炼

image-20260721180430863

反向传播(梯度更新公式:W新 = W旧 - 学习率*梯度)

神经元(信息的求和 | 激活函数)

全连接层 = 当前层的每一个神经元都由上一层的所有神经元进行连接

4.深度学习的特点

1.由神经网络+神经搭建而成

输入层(特征)-------隐藏层--------输出层

2.特征工程不需要人工处理,而是由算法自动计算

3.大数据+算力(可以使用GPU加速)

4.可解释性差,隐藏层是一个黑盒子

5.张量的使用

5.1张量的创建

#基于小写的tensor
指定数据
#基于大写的Tensor
指定数据 + 指定维度
# 基于指定的类型进行创建int16 32 64 float16 32 64 <-> short int long half float double
torch.IntTensor(2,3)   IntTensor([1,2,3])

5.2线性张量

torch.arange(起始结束步长) ->包左不包右
torch.linspace(起始结束划分数量)->包左包右

5.3随机张量

随机种子
 - torch.initital_seed()
 - torch.manual_seed()
随机数
0-1 - torch.rand()
-3-3 - torch.randn()
随机整数
torch.randint(起始结束尺寸) ->包左不包右

5.4全0全1

ones(尺寸) 通过尺寸创建
ones_like(张量) 通过张量的形状创建
zeros(尺寸) 通过尺寸创建
zeros_like(张量) 通过张量创建

5.5创建指定张量

torch.full(size,填充值)
torch.full_like(张量填充值)

5.6数据类型转换

data = torch.tensor([1,2,3])
data.type(指定的类型) #int16 int32 int64 float16 float32 float64...
data.type(torch.IntTensor)

5.7tensor和numpy的转化

tensor -> numpy
tensor = sorch.tensor([1,2,3])
tensor = tensor.numpy()#转换,共享
tensor = tensor.numpy().copy()#转换,不共享

numpy -> tensor
tensor = torch.from_numpy(numpy)#转换,共享
tensor = torch.from_numpy(numpy.copy)#转换,不共享
tensor = torch.tensor(numpy)#创建,不共享

从一个数据的tensor中取出值
data1 = tensor = torch.tensor(1)
data2 = tensor = torch.tensor([1])
data1.item()

6.anaconda环境的使用说明

1.anaconda 可以进行python环境的管理内置很多的包

2.1新项目的环境 - python 3.11  自己的电脑 - 3.11 冲突了
2.2新项目的环境 - python 3.12  自己的电脑 - 3.12 冲突了
2.3anaconda 可以帮助你管理python环境让你自由切换当前的python版本呢

3.0 - 通过管理员模式打开anaconda prompt(终端)
3.1 查看电脑anaconda已有的环境(默认可能只有base)
 conda env list
3.2 anaconda创建环境的指令
 conda create -n 环境名字 python=3.11  #创建3.11环境
 conda create -n dl_torch3.11 python=3.11
3.3 切换anaconda环境(终端切换)
 conda activate 环境名字
conda activate dl_torch3.11

3.4 回到base(终端)
conda deactivate

3.5 删除环境
conda env remove -n 环境名字
conda env remove -n dl_torch3.11

强调后续装包一定是先切换环境再装包!!!


4.pytorch环境的安装(管理员模式打开anaconda prompt)
4.1查看当前conda环境支持的pytorch版本
conda search pytorch
4.2查看当前已安装的pytorch版本
conda list torch
4.3安装指定版本的python2.5.1
pip install torch==2.5.1  默认指向当前电脑的python环境

举个例子
电脑有两个python环境
anaconda - python 12  / python解释器 - python 12
如果pip install torch安装成功可能也无法使用torch
可以通过where python 查看当前python环境使用哪个默认的解释器环境
C:\Tools\Anaconda\python.exe     # 默认环境示例
C:\Projects\LLM\python3.10.9\python.exe

需要把包装到对应的python环境才能使用!!!

7.张量的基本运算

7.1简单计算

加 减 乘 除 负号

add sub mul div neg

add_ sub_ mul_ div_ neg_ 带下划线表示修改原数据

掌握:

1.+ — * /

2.pytorch中,所有带_都表示更新原数据

import torch

# 1.准备数据
data = torch.tensor([[1,2],[3,4],[5,6]])

# 2.加法
# 2.1加法:加常数
t1 = torch.tensor([[1,1],[2,2],[3,3]])
print(data+1)
# 2.2加法:加tensor
print(data+t1)
print(torch.add(data,t1))  # 同上
# 2.3把data加到t1上,然后更新t1
t1.add_(data)
print(t1)
# 其他运算演示
print(data-t1)
print(data.sub(t1))# 同上
print(data*t1)
print(data.mul(t1))# 同上
print(data/t1)
print(data.div(t1))# 同上
# 取负号
print(data.neg())
print(data*-1)# 同上

7.2点乘点积运算

点乘 -mul *

​ 两个形状相同(行列数都要相同)的张量相乘=每个位置相乘

点积(没有交换率)- @/malmul

​ 1.前提:俩个张量能点积的 = a的列等于b的行

​ 2.过程:a的行*b的列

​ 3.结果:a行b列

掌握:@matmul

import torch
t1 = torch.tensor([[1,2],[2,3]])
t2 = torch.tensor([[2,2],[3,3]])
# 点乘
print(t1*t2)
print(t1.mul(t2))

t3 = torch.tensor([[1,2],[2,3],[3,4]])
# 点积  # A的列数必须等于B的行数
print(t3@t2)
print(t3.matmul(t2))
# print(t2@t3)
# print(t2.matmul(t3)) # 报错t2的列数不等于t3的行数

# 补充:
# print(t3.sign()) # 带符号的全1
# print(t3.square()) # 平方
# print(t3.fmod(2)) # 取余
# .....

8.张量的运算函数

1.求和sum :dim表示第几维进行求和

2.平均mean/最大值max/最小值min

3.开方sqrt/平方square/幂pow/绝对值abs\absolute

4.指数exp

5.对数log/log2/log10

掌握: sum

# 掌握:sum
import torch

# 准备数据
data = torch.tensor(
    # 第0维度:将下面括号内的数据进行求和,合并成1条数据
    [
        # 第1维度:将下面括号内的数据进行求和,合并成1条数据
        [1,2,3],
        [4,5,6]
    ])

# 1.求和sum - 维度
print(data.sum(dim=0)) #列求和
print(data.sum(dim=1)) #行求和
# print(data.sum(dim=2)) #报错!!! 超过dim区间
print(data.sum()) #全部求和

# 2.平均、最大值、最小值
print(data.dtype)
# (1+2+3+4+5+6)/6 计算会有小数,所以需要的是float类型,但是现在是int类型
data = data.type(torch.float32)
print(data.mean()) #平均值一般都不是整数,所以只有float类型才能进行平均值的计算
print(data.max())
print(data.min())
# 开方、平方、幂、绝对值
data.sqrt_() # 开方
print(data)
data.square_() # 平方
print(data)
data.pow_(2) # 2次幂 == 平方
print(data)
data = data*-1
print('负1的data:', data)
data.abs_() # 绝对值
data.absolute_() # 绝对值
print('绝对值data',data)

# 4.指数
print(data.exp())

# 5.对数
print(data.log())
print(data.log2())
print(data.log10())

9.索引使用

掌握 :除了多维索引以外的所有

import torch
# 准备数据
torch.manual_seed(3)
data = torch.randint(1,10,(4,5))
# [
#     [5, 6, 2, 4, 2],
#     [4, 7, 3, 8, 2],
#     [8, 1, 3, 3, 1],
#     [5, 4, 7, 9, 9]
# ]
# 1.简单的行列索引
print(data[0]) #取第0行
print(data[:,1]) #取所有行的第1列

# 2.通过列表索引获取数据
# 语法:data[[行索引],[列索引]]
# 需求1:返回(0,1)(1,2)俩个位置的索引
print(data[[0,1],[1,2]]) # 6,3
# 需求2:返回(1,2)(3,4)俩个位置的索引
print(data[[1,3],[2,4]]) # 3,9
# 需求3:返回(1,3)(2,4)(1,4)(2,3)四个位置的索引
print(data[[1,2,1,2],[3,4,4,3]]) # 8,1,2,3
# 需求4:返回(1,3)(2,4)(1,4)(2,3)(1,1)(2,2)6个位置的索引
print(data[[1,2,1,2,1,2],[3,4,4,3,1,2]]) #8, 1, 2, 3, 7, 3
# 【拓展】面试题被拷打过的一个例子
print(data[[[0],[2]],[2,3]]) #第0行的第2,3个元素 第2行的第2,3个元素

# 3.通过范围索引获取数据
print(data[:3:2])#第0行的 第2行的 #与上面的拓展面试题效果相同(第一步)
print(data[:3:2,[2,3]])#第0行和第2行 取第2列和第3列 #与上面的拓展面试题效果相同(第一步)
# 需求1:前3行 前2列
print(data[:4, :3])
# 需求2:第2行到最后 前2列(默认数字为索引)
print(data[2:, :3]) #2是因为包头,3是因为不包尾

# 4.通过布尔索引获取数据(数字表示的索引)
# 布尔索引使用的示例
print(data>3) #每一项数据对应的布尔值,得到了布尔索引
print(data[data>3]) #通过这个布尔索引,去原数据获取相应的数据(如果为Ture保留,)
# 需求1:第3列大于5的行数据
print(data[:,3]) #第一步,取出所有行的第3列(3指的是索引)
print(data[:,3]>5) #第二步,拿这些值获得布尔索引
print(data[data[:,3]>5]) #第三步,通过布尔索引去原数据中取出对应的值
# 需求2:第2列大于5的列数据
print(data[:,2]) #第一步,取出所有行的第2列(2指的是索引)
print(data[:,2]>5) #第二步,拿这些值获得布尔索引
print(data[data[:,2]>5,2]) #第三步,通过布尔索引去原数据中取出对应的所有的列数据
print(data[data[:,2]>5]) #(歧义)第三步,通过布尔索引获取原数据对应的所有列数据

# 5.多维索引获取数据(能理解三维足以)
# 准备数据
mul_data = torch.randint(1,10,(3,4,5))
print(mul_data)
# 第0维数据([]里面的内容是第0维数据)-0轴 -3个数据
# [
#     第1维数据([]里面的内容是第1维数据)-1轴 -4个数据
#     [
#         第2维数据([]里面的内容是第2维数据)-2轴 -5个数据
#         [1, 6, 7, 2, 1],
#         [6, 6, 4, 5, 9],
#         [7, 2, 5, 5, 7],
#         [6, 9, 8, 9, 1]
#     ],
#     [
#         [5, 1, 6, 1, 8],
#         [7, 8, 9, 1, 1],
#         [3, 5, 2, 4, 3],
#         [5, 7, 8, 2, 6]
#     ],
#     [
#         [5, 6, 1, 7, 1],
#         [8, 6, 9, 8, 8],
#         [1, 3, 6, 2, 9],
#         [9, 9, 5, 2, 6]
#     ]
# ]
# 需求1:取出0轴第1个数据
print(mul_data[0])
print(mul_data[0,:,:])
# 需求2:取出1轴第一个数据(0轴第1个1轴第1个数据 0轴的第2个1轴第1个数据 0轴的第3个1轴第1个数据)
print(mul_data[:,0,:]) # 有3个1轴第1个数据,3是因为0轴有3条数据
# 需求3:取出2轴第1个数据
# 翻译:取出0轴所有的数据,取出1轴所有的数据,取出2轴的第1个数据
# 说明:
#   (
#    0轴第"1"个1轴第"1"个,
#    0轴第"1"个1轴第"2"个
#    0轴第"1"个1轴第"3"个
#    0轴第"1"个1轴第"4"个
#    )
#    结果: [1,6,7,6]
#   (
#    0轴第"2"个1轴第"1"个,
#    0轴第"2"个1轴第"2"个
#    0轴第"2"个1轴第"3"个
#    0轴第"2"个1轴第"4"个
#    )
#    结果: [5,7,3,5]
#   (
#    0轴第"3"个1轴第"1"个,
#    0轴第"3"个1轴第"2"个
#    0轴第"3"个1轴第"3"个
#    0轴第"3"个1轴第"4"个
#    )
#    结果:[5,8,1,9]
# 最终结果:[[1,6,7,6],[5,7,3,5],[5,8,1,9]]
print(mul_data[:,:,0])

# 总结:多维索引每一个维度都要考虑到,如果没有明确说明要或者不要,那表示全都要!!!

10.张量形状的改变

掌握:reshape squeeze unsqueeze transpose permute

10.1reshape修改张量的形状

reshape 不改变原数据的前提下,改变形状

import torch
# 准备数据
data = torch.tensor([[1,2,3],[4,5,6]])
# 1.使用reshape进行数据的修改(2,3 -> 1,6)
t1 = data.reshape(1,6) #[[1,2,3],[4,5,6]]->[[1,2,3,4,5,6]]
print(t1,t1.data_ptr(),t1.stride()) #data - pointer 数据指针,stride表示在内存中读取数据的步长

# 2.使用reshape进行数据的修改(2,3 -> 1,?)
t2 = data.reshape(1,-1) #会自动计算-1变成6
print(t2,t2.data_ptr(),t2.stride()) #data - pointer 数据指针,stride表示在内存中读取数据的步长

# 3.使用reshape进行数据的修改(2,3 -> 2,?)
t3 = data.reshape(3,-1)#会自动计算-1变成2
print(t3,t3.data_ptr(),t3.stride()) #data - pointer 数据指针,stride表示在内存中读取数据的步长

# 4.使用reshape进行数据的修改(2,3 -> 4,2)
# t4 = data.reshape(4,2)#报错
# print(t4)
# reshape可以在满足前提的情况下修改数据
# 【拓展】张量数据创建的时候,在底层就已经分类内存,将对应的值写入内存中
# 张量数据不变 ===> 内存中的内容没有发送变化
# |----|
# |  1 |
# |----|
# |----|
# |  2 |
# |----|
# |----|
# |  3 |
# |----|
# |----|
# |  4 |
# |----|
# |----|
# |  5 |
# |----|
# |----|
# |  6 |
# |----|
# 2.数据不变,为什么形状可以变化====>步长计算公式——》不同形状下的数据应该是什么样!!

10.2张量的升维和降维

squeeze降维 - 在原数据基础上将所有维度为1删除

unsqueeze升维 - 在原数据基础上提升一个维度

import torch
# 准备数据
t1 = torch.tensor([1,2,3,4,5]) #1维
# 升维 -在0维上进行1个维度的提升
data = t1.unsqueeze(dim=0) #2维 -行维度的提升
print(data)
data = t1.unsqueeze(dim=1) #2维 -列维度的提升
print(data)
# data = t1.unsqueeze(dim=2)
# print(data) #报错没有办法把1维升到3维

# 2.降维squeeze
t2 = torch.Tensor(3,1,5,1,3)
print(t2,t2.size())
data = t2.squeeze() # 会将所有维度为1的进行删除3,1,5,1,3->3,5,3
print(data,data.size())

10.3张量的维度交换

transpose交换任意两个维度

permute交换任意个数的维度

import torch
# 准备数据
data = torch.randint(1,10,(3,4,5))
print('data:',)
# 1.交换2个维度3,4,5 ==> 3,5,4
t1 = data.transpose(1,2)#transpose(原始数据的第几维度,原始数据的第几维度)
print(t1,t1.size())

# 2.交换多个维度3,4,5 ==> 4,5,3
t2 = data.permute(1,2,0) #permute(原始数据的第几维度,原始数据的第几维度,原始数据的第几维度,原始数据的第几维度)
print(t2,t2.size())

# 【拓展】
# 数据在内存中,使用数据起始就是从内存中读取数据,数据长什么样由数据读取方式决定(步长)
# 1.查看交换维度是否会引起数据本身的变化
# 2.查看交换维度是否会引起数据步长的变化
# 结论:交换数据维度起始只是改变了读取数据的步长,数据本身没有发生变化

10.4张量的连续性数据

[了解] view改变形状(数据保持连续性) + contiguous(修改数据内存,保持数据连续)

import torch
torch.manual_seed(3)
# 0.准备数据
data1 = torch.randint(1,5,(3,4,5))
print('初始化数据data1:',data1,data1.data_ptr(),data1.stride())

# # 1.用view修改数据(如果初始化的数据没有被修改维度,那么是可以直接view)
# new_data1 = data1.view(3,2)
# print(new_data1)

# 2.用view修改数据(交换数据维度) 报错
# data1.transpose_(0,1) #修改了步长,数据的表现形式和真实存储的形式不一致
# print('交换维度后的数据data1:',data1,data1.data_ptr(),data1.stride())
# new_data1 = data1.view(3,5,4) #报错,转换数据表现形式与真实存储形式不一致的数据
# print(new_data1)

# 3.修改要预览的数据,保证数据表现形式和真实存储的形式一致,就可以进行修改
# 3.1 用没有交换过的数据进行预览
# new_data1 = data1.view(3,5,4)
# print(new_data1)

# 3.2基于交换维度后的数据进行预览
# data1.transpose_(0,1) #步长发生变化了,表现形式和存储形式不一致
# data1 = data1.contiguous() #将当前的数据调整值表现形式与存储形式一致(从内存角度发力,改的是底层内存的数据)
# print('修正后数据data1:',data1,data1.data_ptr(),data1.stride())
# new_data1 = data1.view(3,5,4)
# print(new_data1)

# 最后的精华:
data1 = data1.transpose(0,1) #将表现形式与逻辑存储修改的不一致
print(data1.contiguous().view(2,5,6)) #reshape的底层原理:reshape会先看看能不能view,不能就先contiguous再view

# view报错原因:数据逻辑和表现形式不一致
# 结论:如果view报错了,就先contiguous再view就不报错啦~

11.张量的拼接

掌握 全部

cat - 不会提升维度 - 除了拼接的维度不一样,其他维度必须一样

stack - 会提升维度 - 所有数据的维度必须完全一致

总结:

cat - 其他维度不变,拼接维度做加法

stack - 其他维度不变,升哪个维度,哪个维度就等于拼接的数据条数

# 给豆包发消息:图片(张量)+文字(张量) = 1条消息(张量)

import torch
# ---------------------------------------cat拼接(不会提升维度数)
# 要求:俩个数据进行维度的拼接,除了指定的维度数可以不一样,其他数量必须一样
# 准备数据
data1 = torch.tensor([[1,2,3],[4,5,6]]) #2行3列 dim0=2 dim1=3
data2 = torch.tensor([[1,2,3],[4,5,6],[4,5,6]])  #3行3列 dim0=3 dim1=3
# 1.按照dim=0进行拼接data1和data2
data = torch.cat([data1,data2],dim=0)
print(data)

# 2.按照dim=1进行拼接data1和data2
data3 = torch.tensor([[1,2,3,4,5],[1,2,3,4,5]]) #2行5列
data = torch.cat([data1,data3],dim=1)
print(data)
# ---------------------------------------stack拼接(会提升维度数)
# 准备数据(维度必须完全一致)
data4 = torch.tensor([[1,2,3],[4,5,6]]) #2,3
data5 = torch.tensor([[1,2,3],[4,5,6]]) #2,3
data6 = torch.tensor([[1,2,3],[4,5,6]]) #2,3

data = torch.stack([data4,data5,data6],dim=0) #0维进行提升
print(data.shape) #[3],2,3
# 2,3 + 2,3 + 2,3 +dim=0 -> 3,2,3
data = torch.stack([data4,data5,data6],dim=1) #1维进行提升
print(data.shape) #2,[3],3
# 2,3 + 2,3 + 2,3 +dim=1 -> 2,3,3
data = torch.stack([data4,data5,data6],dim=2) #2维进行提升
print(data.shape) #2,3,[3]
# 2,3 + 2,3 + 2,3 +dim=2 -> 2,3,3

# 总结:其他维度不变,升哪个维度,哪个维度就等于数据的条数

# 需求:2行3列+2行3列
# 如果dim = 0  ->  2行3列+2行3列  = [2]个2行3列
# 如果dim = 1  ->  2行3列+2行3列  = 2个[2]行3列
# 如果dim = 2  ->  2行3列+2行3列  = 2个[3]行[2]列
# 都是2行3列进行拼接,指定哪个维度,哪个唯独就是新提升上来的
# 2,3 + 2,3 +dim=0 = >   【2】,2,3
# 2,3 + 2,3 +dim=1= >     2,【2】,3
# 2,3 + 2,3 +dim=2= >     2,3,【2】

12.模拟线性回归模型

12.1自动微分

函数:backward()

函数必须是设置了require_grad属性的相关函数

作用:求导

x = torch.linspace(-20,20,1000,requires_grad=True)
y = torch.sigmoid(x)    #定义一个函数
y.sum().backward()      #对函数求导

12.2自动微分的使用

import torch
# 思路:
# 1.准备数据
# 1.1准备输入的训练集(x_train,y_train)
X = torch.ones(2,5)   #样本数量2,特征数量5 -2,5
W = torch.randn(5,3,requires_grad=True,dtype=torch.float32)   #权重5行3列的数据 - 5,3 初始的值是随机的
B = torch.randn(3,requires_grad=True,dtype=torch.float32)   #偏置是为3的标量 - 3,
Z = X @ W + B      #(计算)预测值 - 2,3
Y = torch.zeros(2,3) #(准备)真实值 - 2,3

# 2.损失函数定义
lose_fn = torch.nn.MSELoss()
loss = lose_fn(Z,Y) #真正的进行了正向传播(计算了Z=W@X+B)
# 3.自动微分(pytorch只支持标量张量的求导,loss必须是标量)
print('loss:', loss)
loss.backward()
print('W.grad:', W.grad) # 更新W
print('B.grad:', B.grad) # 更新B

# 更新 - 梯度下降公式(这里不做,后面有API)

# 总结:

# 1.1准备数据:X W B Y
# 1.2计算预测值:Z = X@W+B
# 2.1定义损失函数:loss = lose_fn(Z,Y)
# 2.2计算损失 loss = loss_fn(Z,Y)
# 3.自动微分
# loss.backward()
# print(W.grad,B.grad)#得到W和B对应的下降梯度,后续就可以利用梯度下降公式更新W,B

12.3模拟线性回归

1.准备数据make_regressor(样本数量,特征数量,标签数量,噪声,coef,bias)

2.数据 ->张量 -> 数据集 ->数据加载器

3.训练模型

​ 准备model

​ 准备优化器optimizer

3.1模型预测

y_pre = mode(x)

3.2定义损失函数

loss_fn=MSEloss()

3.3计算损失

loss = loss_fn(y_pre,y)

3.4自动微分

loss.backward()

3.5更新梯度

optimizer.step()

3.6清空梯度

optimizer.zero_grad()

import torch
from sklearn.datasets import make_regression #创建线性回归数据
from torch.utils.data import TensorDataset,DataLoader
import matplotlib.pyplot as plt
import matplotlib
# matplotlib中文支持
matplotlib.rcParams['font.sans-serif'] = ['SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False

# 1.创建数据
def create_data():
    # Z = KX +B   k斜率   b偏置
    # 已知X  已知Y  -用于训练模型,让模型可以进行预测
    # 已知k  已知b  -用于检测最终我们的模型预测结果的
    # bias = 3,x,y,coef?
    x,y,coef = make_regression(
        n_samples=100,  #样本数量
        n_features=1,   #特征数量
        n_targets=1,    #标签数量
        bias=3,         #偏置的值(自定义)
        noise=10,       #噪声(如果没噪声就成了完美直线了)
        coef=True,       #是否计算斜率
        random_state=3
    )
    # print(x)
    # print(y.reshape(-1,1))
    print(coef)  # 斜率:27.478050549563925
    # bias = 3      #偏置
    return x,y.reshape(-1,1),coef

# 2.模型训练
def train(x,y,coef):
    # 数据->张量->数据集->数据加载器(多轮训练的依赖)
    # x特征,y标签 -> coef bias
    # 1.数据(100) -> 张量
    x = torch.tensor(x, dtype=torch.float32)
    y = torch.tensor(y, dtype=torch.float32)
    # 2.张量 -> 数据集
    dataset = TensorDataset(x,y)
    # 3.数据集 -> 数据加载器
    # 参数说明:
    # 1.数据集 - 100条数据
    # 2.批次训练数据量 - 16条数据,每次从100条中取16条,训练7次才能结束
    # 3.数据是否随机
    dataloader = DataLoader(dataset,batch_size=16,shuffle=True)
    # 查看数据
    # for x_train,y_train in dataloader:
    #     print(x_train.shape)
    #     print(y_train.shape)
    # 4.模型训练
    # 0.准备工作
    # 模型创建 - 参数说明:1.特征数量 2.标签数量
    model = torch.nn.Linear(in_features=1,out_features=1)
    # 优化器创建 - 参数说明:1.模型参数 2.学习率
    optimizer = torch.optim.SGD(params= model.parameters(),lr=0.01)
    # 5.计算损失
    # 5.1 -总损失 - 记录每一批次的损失(画图)
    loss_list = []
    # 100轮 = 每轮7次 = 700次
    for i in range(100):
        # 5.2 - 每一轮的详细损失:总损失,平均损失
        total_loss,train_count = 0,0
        # 训练7次,每批16条数据 / 一轮 100->16/16/16/16/16/16/4
        for x_train,y_train in dataloader:
            # ------------------------------前向传播
            # 1.模型预测
            y_pre = model(x_train)
            # 2.损失函数
            loss_fn = torch.nn.MSELoss()
            loss = loss_fn(y_pre, y_train)
            # loss_list.append(loss) #记录到损失列表中(记每一次,100*7)
            total_loss += loss.item() # 记录每一轮的总损失
            train_count += 1 #几轮训练的次数
            # ------------------------------反向传播
            # 3.自动微分:获取到梯度
            loss.backward()
            # 4.更新梯度:优化器实现
            optimizer.step()
            # 5.梯度清零
            optimizer.zero_grad()
        print(f"第{i+1}轮训练,平均损失:{total_loss/train_count}")
        loss_list.append(total_loss/train_count) #每一轮的平均损失
    # 训练结束:训练结果在模型身上
    print(f"训练结束,查看模型结果:{model.state_dict()}")
    print(f"训练结束,查看真实的结果:{coef},3")

    # 6.数据可视化
    print(len(loss_list))
    # 6.1绘制损失下降的曲线 -关于损失的
    plt.scatter(range(len(loss_list)),loss_list)
    plt.title("损失下降曲线")
    plt.xlabel("训练次数")
    plt.ylabel("损失值")
    plt.show()
    # 6.2绘制样本数据的点
    plt.scatter(x,y)
    # 6.3绘制的真实的直线
    # x , y = coef*x+3
    # 细节点1-报错原因:'numpy.ndarray' and 'Tensor' -> numpy数据和tensor数据不能进行运算,但是tensor数据可以和numpy数据进行运算
    plt.plot(x,x*coef+3,color='red')
    # 6.4绘制预测的直线
    # 细节点2-报错原因:因为i不能直接转numpy需要先detach()再numpy()
    plt.plot(x, [i.detach().numpy() for i in model(x)], color="green")
    plt.title("预测结果对比")
    plt.xlabel("特征值")
    plt.ylabel("标签值")
    plt.legend(["样本数据", "真实直线", "预测直线"])
    plt.show()
if __name__ == '__main__':
    # 已知x特征 y标签 以及真实的斜率/偏置
    x,y,coef =create_data()
    # 模型训练(给你x,y,自己算W B)
    train(x, y, coef)

# 模型训练总结:(神经元的加权求和)
# 1.数据准备:数据->张量->数据集->数据加载器
# 2.模型准备:模型创建 / 优化器创建
# 3.模型训练:训练几轮?每轮训几批?每批训几条?
# 4.训练流程:正向(前向)传播 / 反向传播
# 前向传播:模型预测/损失函数/损失
# 反向传播:自动微分/更新梯度/梯度清零

# 神经元(加权求和|激活函数)


# 绘图:
# 统计损失,看一下损失的变化 ,预测直线和真实直线以及样本之间的差异

13.人工神经网络

人工神经网络: 输入层|隐藏层|输出层

全连接层 = 每一个神经元都是和上一层的所有神经元相连接,每一层的神经元互不连接

神经元(加权求和|激活函数)

加权求和 - 线性转换

激活函数(4)-非线性因素

使用场景:

隐藏层(激活函数:Relu、tanh) 输出层(激活函数:Sigmoid、Softmax)

sigmoid 值域 0~1 有效定义域-6~6 高效定义域-3,3

sigmoid 导函数值域 0~0.25

13.1激活函数

隐藏层(导函数):

ReLU : 导函数值域 0-1

tanh: 导函数值域 0-1

输出层(原函数):

Sigmoid: 原函数的值域 0~1

softmax:将内容转换为概率

image-20260725193904037

image-20260725193812664

13.2搭建神经网络(掌握)

import torch.nn
from torchsummary import summary

# 1.定义一个模型类,必须继承nn.module
class Model(torch.nn.Module):
    # 2.给这个类实现__init__方法并且神经网络层数
    def __init__(self):
        # TODO:1.定义神经网络层数
        # 2.0 - 调用父类方法完成初始化参数
        super().__init__() #但凡重写父类init,一定记得调用super().init()
        # 2.1 - 定义隐藏层和输出层
        self.fc1 = torch.nn.Linear(in_features=3,out_features=3)
        self.fc2 = torch.nn.Linear(in_features=3,out_features=2)
        self.out = torch.nn.Linear(in_features=2,out_features=2)
        # 2.2 - 初始化每层的权重
        torch.nn.init.xavier_uniform_(self.fc1.weight)
        torch.nn.init.kaiming_uniform_(self.fc2.weight)
        # 2.3 - 初始化每层的偏置
        torch.nn.init.ones_(self.fc1.bias)
        torch.nn.init.zeros_(self.fc2.bias)
        pass
    # 3.给这个类实现forward,实现前向传播
    def forward(self,x):
        # TODO:2.实现前向传播:把输入的x经过神经网络得到输出
        # x1是第一个隐藏层经过加权求和和激活函数的结果
        x1 = torch.sigmoid(self.fc1(x))
        # x2是第二个隐藏层经过加权求和和激活函数的结果
        x2 = torch.relu(self.fc2(x1))
        # 将x2转化为输出的结果
        result = torch.softmax(self.out(x2),dim=-1)
        return result


if __name__ == '__main__':
    # 测试模型的正向传播 - 结果肯定不可靠
    model = Model()
    # 预测x -传递特征3
    data = torch.randn(2,3) #2个样本3个特征
    # 没报错~OK
    model(data)
    # 要查看的模型,input_size参数有几个 batch_size当前样本有几个
    summary(model,input_size=(2,3))
    # summary(model,input_size=(3,),batch_size=2) #效果同上

    # 查看模型参数 - 各个层的详细信息(权重、偏置)查看
    for name,param in model.named_parameters():
        print(name,param)


# 查看模型信息:torchsummary
# 查看模型参数大小
# summary(模型,输入的形状(几条样本,每个样本几个参数))
# 查看神经网络各层的权重和偏置信息
# # for name,param in model.name_parameters():
#     print(name,param)

参数的初始化

# 1.参数始化指的是初始化神经网络中每个层的权重和偏置
# 2.我们需要只关注每个层的权重,不关心偏置
# 3.需要创建1个隐藏层 + 设置隐藏层的权重
import torch
torch.manual_seed(3)
# 创建隐藏层fc1
fc1 = torch.nn.Linear(in_features=3,out_features=5)
# 神经网络的权重都是随机初始化生成的
# print(fc1.weight)
# 也可以修改权重:torch.nn.init.方法(fc1.weight)

# 验证可以修改的7种方法
# 方法1:均匀分布(0,1) -uniform
torch.nn.init.uniform_(fc1.weight)
print(fc1.weight)

# 方法2:正态分布(-3,3) -normal
torch.nn.init.normal_(fc1.weight)
print(fc1.weight)

# 方法3:全0 -zeros
torch.nn.init.zeros_(fc1.weight)
print(fc1.weight)

# 方法4:全1 -ones
torch.nn.init.ones_(fc1.weight)
print(fc1.weight)

# 方法5:固定,需要自己指定一个值
torch.nn.init.constant_(fc1.weight,0.123)
print(fc1.weight)

# 方法6:kaiming - 比一般的normal要好一些(只关心输入)
torch.nn.init.kaiming_normal_(fc1.weight)
print(fc1.weight)

# 方法7:xavier - 比一般的uniform要好一些(关心输入和输出)
torch.nn.init.xavier_uniform_(fc1.weight)
print(fc1.weight)

# 1.隐藏层有没有初始化的权重
# 2.能自定义
# 掌握
# kaiming_normal_ 结合Relu
# xavier_uniform_ 结合非Relu
# zeros_ 给bias设置初始化的时候会用

13.3损失函数

衡量真实值与预测值之间误差的函数

回归问题:

​ L1Loss

​ MSELoss

​ SmoothL1Loss

分类问题:

​ BCEloss

​ CrossEntropyLoss

交叉熵:交叉熵是一种损失函数,用来衡量预测模型与真实分布之间的差距,广泛应用于分类问题,预测越准,交叉熵越小。

二分类交叉熵损失计算

import torch
# 1.定义预测值
y_pre = torch.tensor([0.1,0.2,0.3],requires_grad=True,dtype=torch.float32)
# 2.定义真实值
y_true = torch.tensor([0,1,0],requires_grad= True,dtype=torch.float32)
# 3.得到损失函数 - BCELoss
loss_fn = torch.nn.BCELoss()
# 4.计算损失 - 2分类交叉熵损失 - BCELoss
loss = loss_fn(y_pre,y_true)
print(f'loss:{loss}')

# 分类的损失函数
# 2分类: BCELoss
# 多分类: CrossEntropyLoss

多分类交叉熵损失计算

# 损失函数:衡量预测值和真实值之间的差异
# 计算多分类的预测和真实值的差异(多分类交叉熵损失)-CrossEntropyLoss
import torch
# 真实值
# y_true = torch.tensor([[0,1,0],[0,0,1]],dtype=torch.float32)
# 热编码后
y_true = torch.tensor([1,2],dtype=torch.int64)
# 损失值
y_pre = torch.tensor([[0.2,0.6,0.2],[0.1,0.8,0.1]],requires_grad= True,dtype=torch.float32)
# 定义损失函数 - 要看模型解决的是什么问题?
# 如果是多分类,使用CrossEntropyLoss
# 如果是2分类,使用??2分类?Loss
# 如果还是线性回归,继续使用MSELoss、?L1loss?、SmoothL1Loss?
loss_fn = torch.nn.CrossEntropyLoss()
loss = loss_fn(y_pre,y_true)
print(f'loss:{loss}')
# 前置知识回顾:
# 定义损失函数
# 计算损失
# 损失自动微分

线性回归的三种损失计算

import torch
y_pre = torch.tensor([0.1,0.2,0.3])
y_true = torch.tensor([0.08,0.17,0.33])

# 1.使用L1Loss函数计算损失   -   不会出现梯度爆炸
loss_fn = torch.nn.L1Loss()
loss = loss_fn(y_pre,y_true)
print(f'loss:{loss}')

# 2.使用(MSELoss)L2Loss函数计算损失  -0点可导
loss_fn = torch.nn.L1Loss()
loss = loss_fn(y_pre,y_true)
print(f'loss:{loss}')

# 3.使用SmoothL1Loss函数计算损失(掌握)
loss_fn = torch.nn.SmoothL1Loss()
loss = loss_fn(y_pre,y_true)
print(f'loss:{loss}')

# 损失函数:
# 回归问题-
#     SmoothL1Loss
#     MSELoss
#     L1Loss
# 分类问题
#     BCELoss
#     CrossEntropyLoss

14.网络优化方法

梯度下降法:一种寻找使损失函数最小化的方法。

14.1梯度下降算法种可能遇到的问题

1.碰到平缓区域,梯度值较小,参数优化变慢

2.碰到 “鞍点” ,梯度为 0,参数无法优化

3.碰到局部最小值,参数不是最优

对于这些问题, 出现了一些对梯度下降算法的优化方法

14.2模型训练时的基础概念:

image-20260727102903156

14.3梯度下降的方式

image-20260727103023865

14.4反向传播的原理

前向传播

    隐藏层1输入 z_in1 = x1*w1 + x2*w2 ... +b
    隐藏层1输出 z_out1 = sigmoid(z_in1)
    隐藏层2输入 z_in2 = z_out1*w1 + z_out1*w2 ... +b
    隐藏层2输出 z_out2 = sigmoid(z_in2)

反向传播
d_total     d_total     d_z_out2    d_z_in2     d_z_out1     d_z_in1
———————  =  ———————— *   ———————— *  ———————  *  ————————  * ————————
w1          d_z_out2     d_z_in2     d_z_out1    d_z_in1      w1

求激活函数的偏导 求加权求和的偏导 -> 从输出层逐层传递到输入层依次计算偏导

14.5梯度更新优化的4种方案

梯度更新优化方案的思想:指数移动加权平均的思想

β * (tn-1) + (1-β) * (tn) # β:0.9

 w新 =  w旧  -     学习率    *      梯度
                                |度量法|
                |Adagrad|
                |RMSProp|
                        | Adam |
随机梯度下降(默认)

optim.SGD(要更新的参数,lr=0.01)

动量法-梯度

参数说明:1.要更新的权重,是一个列表 2.学习率 3.动量的系数β0.9~0.99

optim.SGD(要更新的参数,lr=0.01,momentum=0.9)

# 动量法:
# 梯度 = β*(tn-1) + (1-β)*(tn)
# 优点:不会频繁的梯度震荡
# 缺点:当前的变化会受之前梯度的影响,可能出现变化反应慢的情况
Adagrad - 学习率

参数说明:1.要更新的权重,是一个列表 2.学习率

optim.Adagrad(要更新的参数,lr=0.01)

# 新学习率 = 旧学习率 / (累计梯度平方**0.5 + 小常数)
# 因为每次训练都会累计梯度,所有梯度的累计是不断变大的,意味着学习率是在不断衰减的
# 先走大步,找到适合的方向,降低学习率,寻找最优解
RMSProp - 学习率

参数说明:1.要更新的权重,是一个列表 2.学习率 3.动量的系数0.9~0.99

optim.RMSProp(要更新的参数,lr=0.01,alpha=0.9)

# Adagrad: 累计梯度平方(t) = tn -1  + gt@gt
# RMSProp: 指数加权平均梯度(t) = β*(tn-1) + (1-β)gt@gt
# 区别就是Adagrad使用了累计梯度平方,而RMSProp使用了指数加权平均梯度
Adam - 梯度+学习率

参数说明:

1.要更新的权重,是一个列表 2.学习率 3.梯度加学习率的系数 历史数据的参考数量β1 历史数据的参考程度β2 学习率推荐:0.001

optim.Adam(要更新的参数,lr=0.01,betas=(0.9,0.99)

# 梯度更新 - β1
# 学习率更新 - β2
# 衰减速率: 先快后慢
# 变化周期: 偏长,会有延迟
总结
# SGD 度量  - demo,少样本

# RMSProp / Adagrad - 文本处理

# Adam(掌握) - 大多数情况,多样本

15.学习率衰减的三种方案

为什么要学习学习率衰减的方案学习率设置不当可能出现梯度下降缓慢以及梯度震荡的情况

import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'True'

import torch
import matplotlib.pyplot as plt

# x看成是权重,y看成是loss,下面通过代码来理解学习率的作用
def func(x_t):
    return torch.pow(2*x_t, 2)  # y = 4 x ^2

# 采用较小的学习率,梯度下降的速度慢
# 采用较大的学习率,梯度下降太快越过了最小值点,导致不收敛,甚至震荡
def dm01():

    x = torch.tensor([2.], requires_grad=True)
    # 记录loss迭代次数,画曲线
    iter_rec, loss_rec, x_rec = list(), list(), list()

    # 实验学习率: 0.01 0.02 0.03 0.1 0.2 0.3 0.4
    # lr = 0.1    # 正常的梯度下降
    # lr = 0.125      # 当学习率设置0.125 一下子求出一个最优解
                    # x=0 y=0 在x=0处梯度等于0 x的值x=x-lr*x.grad就不用更新了
                    # 后续再多少次迭代 都固定在最优点

    lr = 0.1      # x从2.0一下子跨过0点,到了左侧负数区域
    # lr = 0.3      # 梯度越来越大 梯度爆炸
    max_iteration = 4
    for i in range(max_iteration):
        y = func(x)   # 得出loss值
        y.backward()  # 计算x的梯度
        print("Iter:{}, X:{:8}, X.grad:{:8}, loss:{:10}".format(
            i, x.detach().numpy()[0], x.grad.detach().numpy()[0], y.item()))
        x_rec.append(x.item())      # 梯度下降点 列表
        # 更新参数
        x.data.sub_(lr * x.grad)    # x = x - x.grad
        x.grad.zero_()
        iter_rec.append(i)          # 迭代次数 列表
        loss_rec.append(y.item())  # 损失值 列表,这里将y改为y.item()以获取标量值
    # 迭代次数-损失值 关系图
    plt.subplot(121).plot(iter_rec, loss_rec, '-ro')
    plt.grid()
    plt.xlabel("Iteration X")
    plt.ylabel("Loss value Y")
    # 函数曲线-下降轨迹 显示图
    x_t = torch.linspace(-3, 3, 100)
    y = func(x_t)
    plt.subplot(122).plot(x_t.detach().numpy(), y.detach().numpy(), label="y = 4*x^2")
    y_rec = [func(torch.tensor(i)).item() for i in x_rec]
    print('x_rec--->', x_rec)
    print('y_rec--->', y_rec)
    # 指定线的颜色和样式(-ro:红色圆圈,b-:蓝色实线等)
    plt.subplot(122).plot(x_rec, y_rec, '-ro')
    plt.grid()
    plt.legend()
    plt.show()

dm01()

15.1等间距学习率衰减

衰减的步长5衰减率0.1

初始学习率 = 0.1

1 -> 0.1

2 -> 0.1

3 -> 0.1

4 -> 0.1

5 -> 0.1

6 -> 0.01

1.设置学习率衰减的策略
scheduler = optim.lr_scheduler.StepLR(优化器,step-size=,gamma=)
2.更新学习率
scheduler.step()
3.查看学习率
scheduler.get_last_lr()

15.2指定间距学习率衰减

衰减的步长[3,5,8]衰减率0.1

初始学习率 = 0.1

1 -> 0.1

2 -> 0.1

3 -> 0.1

4 -> 0.01

5 -> 0.01

6 -> 0.001

1.设置学习率衰减的策略
scheduler = optim.lr_scheduler.MultiStepLR(优化器,milestones=[],gamma=)
2.更新学习率
scheduler.step()
3.查看学习率
scheduler.get_last_lr()

15.3按指数学习率衰减

衰减率 0.95

初始学习率 = 0.95

1 -> 0.89

2 -> 0.85

3 -> 0.82

4 -> ...

1.设置学习率衰减的策略
scheduler = optim.lr_scheduler.ExponentialLR(优化器,gamma=0.9)
2.更新学习率
scheduler.step()
3.查看学习率
scheduler.get_last_lr()

16. 正则化

16.1Dropout正则化

作用的时机:

​ 1.缓解过拟合问题

​ 2.训练模型的时候使用

​ 3.激活函数后使用

逻辑:

​ 训练的过程中,以P的概率随机让神经元失活,未失活的神经元以1/(1-p)进行放缩

语法:

​ 实例化(自动挡):dropout = torch.nn.Dropout(p=)

​ API(手动挡):torch.dropout(data,p=,train=True)

16.2批量归一化

作用时机:

​ 激活函数前,加权求和后

逻辑:

​ 1.每个神经元输出的内容标准化 2.对标准化的内容学习一个λ和β(防止标准化后数据被ReLu进行杀死,以及数据的有效性)

语法:

​ bn = torch.nn.BatchNorm1d() #全连接层,最多3

​ bn = torch.nn.BatchNorm2d() #卷积层,最多4

​ bn = torch.nn.BatchNorm3d() #医学图像,最多5

17.卷积神经网络

17.1卷积层

提取图像特征

conv = torch.nn.Conv2d(输入的通道,输出的通道(卷积核的数量),卷积核的大小,步长,填充)

17.2池化层

降维

pool = torch.nn.MaxPool2d(池化大小)

pool = torch.nn.AvgPool2d(池化大小)

18.ANN案例-手机价格预测

第三部分:自然语言处理

1 .自然语言处理 NLP 概念

  • NLP 全称:Natural Language Processing
  • 概念:让计算机理解人类语言和生成人类语言
  • 应用广泛:
  • 自然语言理解 NLU – Natural Language Understanding
  • 自然语言生成 NLG - Natural Language Generation

2. NLP 发展简史

  • NLP 出现了基于规则派和基于统计学两大阵营
  • 2013 年词向量表示技术成熟(2013 年 google Word2Vec)
  • 2017 年是 NLP 大发展一年(2017 年 google Transformer 框架)

3.自然语言的预处理

基本方法 - 分词 、 命名实体 、词性标注

4.分词

精准模式

适合文本分析保证语义完整
jieba.cut(sentence)  - 生成器
jieba.lcut(sentence)  - 列表

全模式

尽可能的多切词全但是不注重语义
jieba.cut(sentence,cut_all=True)  - 生成器
jieba.lcut(sentence,cut_all=True)  - 列表

搜索模式

介于精准和全模式之间适合搜索引擎
jieba.cut_for_search(sentence)  - 生成器
jieba.lcut_for_search(sentencee)  - 列表

繁体字切分

import jieba
sentence = '煩惱即是菩提,我暫且不提'

# 1.精准模式 - 适合文本分析
result1 = jieba.cut(sentence)
# 2.全模式 - 尽可能的切分,词全但是不注重语义
result2 = jieba.cut(sentence,cut_all=True)
# 3.搜索模式 - 适合搜索引擎
result3 = jieba.cut_for_search(sentence)

# cut的结果是生成器对象,lcut是列表
l1 = [i for i in result1]

print(l1)

自定义用户词典

1.准备一个userdict.txt词典

2.自定义词
   词频如果包含关系需要设置的大一些才可能修改是否切词   词性

3.加载  jiaba.load_user_dict('userdict.txt')

5.命名实体 NER

命名实体: 将人名, 地名, 机构名等专有名词统称命名实体。

6.词性标注 POS

n a v 将词与词性组成一个新的元组,以列表的形式返回

from jieba import posseg   as pseg

res = pseg.cut(sentence)


res -> [(词1,词性)(词2,词性)(词3,词性)(词4,词性)  ]

eg: pos是标准操作流程

7.文本张量的表示方法

•文本张量表示

​ •定义:将一段文本使用张量进行表示这个过程就是文本张量表示

​ 词表示成向量叫词向量,那么一句话构成词向量矩阵

​ •作用:将文本表示成张量(矩阵)形式,方便输入到计算机程序中 进行解析

image-20260803220330532

•NLP中文本词向量表示常用方法:

•(1)one-hot编码 (2)Word2vec (3)Word Embedding

one-hot编码

•One-hot编码(One-Hot Encoding),也叫读热编码

将词转化为稀疏向量,当前值为1其他值为0

•在One-hot编码中,对于一个具有n个不同类别的分类变量,将其表示为一个n维的向量其中只有一个维度的值为1(代表该样本属于这个类别),其他维度的值均为0。

记忆释义:一个位置为1,其他全为零

one-hot也叫01编码也叫独热编码

将词转化为稀疏向量当前值为1其他值为0

cabs = {'关羽','张飞','赵云','马超','黄忠','诸葛亮','刘备','小林'}

# 1.将词列表转成词表(字典)
# word_index = {cab:i+1 for i,cab in enumerate(cabs)}
# print(word_index)
word_index = {'赵云': 1, '马超': 2, '刘备': 3, '张飞': 4, '小林': 5, '关羽': 6, '黄忠': 7, '诸葛亮': 8}

# 2.将某一个词转化成稀疏向量
# print([0]*24)
# one_hot_list = [0] * len(word_index)
# print(one_hot_list)

for cab in cabs:
    # print(cab) #词
    one_hot_list = [0] * len(word_index)
    # print(word_index[cab]) #索引
    idx = word_index[cab]-1
    one_hot_list[idx] = 1
    print(cab,one_hot_list)


刘备 [0, 0, 1, 0, 0, 0, 0, 0]
赵云 [1, 0, 0, 0, 0, 0, 0, 0]
张飞 [0, 0, 0, 1, 0, 0, 0, 0]
马超 [0, 1, 0, 0, 0, 0, 0, 0]
小林 [0, 0, 0, 0, 1, 0, 0, 0]
关羽 [0, 0, 0, 0, 0, 1, 0, 0]
诸葛亮 [0, 0, 0, 0, 0, 0, 0, 1]
黄忠 [0, 0, 0, 0, 0, 0, 1, 0]

ps: enumerate(iterable, start=0)

​ 参数说明:iterable:可迭代对象(list、字符串、元组等),必填

start:起始数字,可选,默认 0

​ 返回值:

​ enumerate 对象,循环时每次产出一组 (序号, 元素)

import os
os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0'
from tensorflow.keras.preprocessing.text import Tokenizer  #虽然看这报错,但是能运行
#安装tensorflow 2.19.0
import joblib

cabs = {'关羽','张飞','赵云','马超','黄忠','诸葛亮','刘备','小林'}

# 1-通过tokenizer词映射器进行训练
def train_tokenizer():
    tokenizer = Tokenizer()
    tokenizer.fit_on_texts(cabs)
    # print(tokenizer.word_index)
    # {'赵云': 1, '诸葛亮': 2, '马超': 3, '张飞': 4, '小林': 5, '黄忠': 6, '关羽': 7, '刘备': 8}
    # -----------------到此tokenizer已经训练结束
    joblib.dump(tokenizer, 'model/tokenizer.pkl') # 用来保存模型
    # 查看one-hot编码
    for cab in cabs:
        one_hot_list = [0] * len(tokenizer.word_index)
        idx = tokenizer.word_index[cab]-1
        one_hot_list[idx] = 1
        print(cab,one_hot_list)

# 2-加载词映射器查看one-hot编码
def use_tokenizer():
    tokenizer = joblib.load('model/tokenizer.pkl')
    for cab in cabs:
        one_hot_list = [0] * len(tokenizer.word_index)
        idx = tokenizer.word_index[cab] - 1
        one_hot_list[idx] = 1
        print(cab,one_hot_list)
if __name__ == '__main__':
    # train_tokenizer()
    use_tokenizer()

one-hot词向量表示 – 优缺点:

  • 优点: 操作简单,容易理解

  • 缺点: 完全割裂了词与词之间的联系;大语料集下,每个向量的长度过大,占据大量内存 属于稀疏词向量表示

  • 抛砖 - 正因为one-hot编码隔离了词和词的联系,又易浪费内存空间 - 出现了:稠密向量的表示方法:word2vec 和 word embedding

word2vec

•是一种将单词转换为词向量的自然语言处理技术

•是利用深度学习网络来探索单词和单词之间的语义关系,用深度学习的网络权重参数表示词向量

•是在无监督的语料上构建了一个有监督的任务

CBOW和Skip-gram

word2vec 搭建神经网络,用权重表示词向量

CBOW 基于两侧预测中间

​ 选择滑动窗口给我上一个词的稀疏向量 和下一个词的稀疏向量,经过神经网络的前向传播(加权求和),得到预测值,将预测值与真实值的损失进行自动微分,得到梯度进行反向传播

​ 反向传播更新W权重,最终用W表示词的向量

Skip-gram 基于中间预测两端

​ 给我一个词的稀疏向量,经过神经网络的前向传播,得到上一个和下一个的预测值

Word2Vec的训练和使用

• 有关词向量训练工具包FastText

​ fasttext有两大作用: 进行文本分类训练词向量

• 训练词向量步骤

​ •第一步: 获取训练数据

​ •第二步: 词向量的训练、保存、加载、查看

​ •第三步: 模型效果检验

​ •第四步: 模型超参数设定

词嵌入word embedding

•通过一定的方式将词汇映射到指定维度(一般是更高维度)的空间

•word2vec可认为是word embedding的一种。

**Word2Vec方式产生词向量 和 word embedding(nn. Embedding)方式产生有何异同? **

•相同点:都可用来对文本训练词向量

•不同点如下:

•word2vec产生词向量后,某一个词向量比如单词“the”词向量就固定下来了,是静态的。

•nn.Embedding层产生词向量后,词嵌入层作为整体神经网络的一部分,权重参数会参与更新,是动态的

•word2vec使用起来一般需要两步:1)输入单词the拿到词向量,2)再送给神经网络进行使用

•nn.Embedding层使用起来更方便就1步。直接嵌入到神经网络中,更易使用

tensorboard可视化案例流程

•1、文本数值:使用jieb对句子进行分词,使用Tokenizer对分词进行编号

•2 、文本张量化:使用nn.Embedding() 创建词向量层

•3 、把词向量矩阵 和 词向量单词列表添加到SummaryWriter对象中

•4 、通过tensorboard观察词向量相似性

•5、输入web地址通过浏览器进行查看

8.文本语料的数据分析

•概念:拿到数据以后,对特征x、标签y进行分析整理

•作用: 帮助我们理解语料、快速发现语料中的问题、指导模型训练、超参数选择 比如:关于标签Y:分类问题查看标签是否均匀;关于数据X:数据有没有 脏数据 数据长度分布等等

•常用方法:标签数量分布、句子长度分布、词频和词云

map

map(处理的函数,处理的数据) 执行完成后会返回新的处理后的数据

词云的绘制

import pandas as pd
import jieba.posseg as pseg
from wordcloud import WordCloud
import matplotlib
import matplotlib.pyplot as plt
matplotlib.use('TkAgg')

# # 1.训练集
train_data =pd.read_csv('./data/train.tsv',sep='\t')
# # 2.正样本
train_positive_data = train_data[train_data['label']==1]
# 3.样本的句子取出来
train_positive_sentences = train_positive_data['sentence']
# -----------------------------------------------------
# text = '今天天气很好,我也很开心,今天的学习也很顺利!'
text = ''.join(train_positive_sentences)
# 带有词性的列表
cut_word_list = pseg.lcut(text)
# 用于词云展示的列表
word_list = []
for word in cut_word_list:
    print('词:',word.word,'词性:',word.flag)
    if word.flag == 'a':
        word_list.append(word.word)
# print(word_list)
# 词云的展示
# 1.准备就绪
wc = WordCloud(
    background_color='white',
    font_path='./data/SimHei.ttf',
    max_words=100,
    max_font_size=100
).generate(' '.join(word_list))
# 2.绘制
plt.figure()
plt.imshow(wc,interpolation='bilinear')
plt.axis('off')
plt.show()
# 总结:
# 1.训练集数据
# 2.筛选出满足的数据
#  正样本 -> 布尔索引实现
#  样本 -> 拼接成字符串
#  字符串 -> pseg切词
#  过滤词性为形容词的词
#   得到了满足条件的词列表
# 3.初始化wc对象
#  对象.generate(''.join(词列表))
# 4.使用plt绘制imshow(wc)

*、chain函数、zip函数

  • *:拆包:将最外层的括号进行拆包

  • chain函数:将多个列表合并成1个列表(直接合并:list1+list2)

  • zip函数:将多个列表压缩成1个列表,按照相同维度进行压缩

from itertools import chain
data = [[1,2],[3,4]]
# 1-*的作用

# 列表前使用,作用就是拆包
print(*[[1,2],[3,4]])  # [1, 2] [3, 4]

# 2-chain函数的使用
result = chain(* data) # 把data最外层括号拆掉,然后合并成一个新列表
print(result) # 返回一个itertools对象 可迭代
print(list(data)) # 返回一个list对象
for i in result:
    print(i)

# 3-list把itertools对象转化成列表

new_list = list(chain(* data))
print(new_list) # [1, 2, 3, 4]
# 需求1:我需要一个[(1,2),(2,3),(3,4),(4,5)]列表
# 需求2:我需要一个[(1,2,3),(2,3,4),(3,4,5),(4,5,6)]列表

# 手搓
def my_zip(data):
    result = []
    # TODO:
    for index,item in enumerate(data):
        if index == len(data)-2:
            break
        result.append((item,data[index+1],data[index+2]))
    return result


if __name__ == '__main__':
    data = [1, 2, 3, 4, 5]
    print(my_zip(data))
    data1 = [2, 3, 4, 5]
    # print(list(zip(data, data1)))
    # print(list(zip(data, data[1:])))
    print(list(zip(data, data[1:], data[2:])))
    # zip就是一个压缩函数,给我几个值,将相同维度的值进行压缩

# *:拆包:将最外层的括号进行拆包
# chain函数:将多个列表合并成1个列表(直接合并:list1+list2)
# zip函数:将多个列表压缩成1个列表,按照相同维度进行压缩

9.文本特征处理

概念:语料添加具有普适性的文本特征,让模型更有效的处理数据,提高模型性能指标

常用方法:

​ •添加n-gram特征 (两个单词总是相邻并共现,可以认为是一个特征)

​ •规范文本长度规范 (文本的长度是多少,也可以认为是一个特征)

n-gram特征

给定一段文本序列, 其中n个字或词的相邻共现,可以做为一个特征,即n-gram特征

常用的n-gram特征是bi-gram和tri-gram特征, 分别对应n为2和3

文本长度规范和作用

送给模型的数据一般都是长度要求的;比如批量(每次送8个样本)样本长度要一样,这样需要对批量数据进行文本长度规范

比如:文本过长需要截断,文本过短需要打pad补齐(补零)这个操作就是文本长度规范

image-20260805103451343

import os
os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0'

# 背景:因为模型训练是,语料长度基本都是固定的,但是数据的长度可能参差不齐,要统一
# 处理的方案:要么截断,要么补全
# API:不论是截断还是补全,API都相同sequence.pad_sequences
from keras.preprocessing import sequence

data = [
        [1,2,3,4,5], # 补全
        [1,2,3,4,5,6,7,8], # 刚好
        [1,2,3,4,5,6,7,8,9,10] # 截断
        ]

# 4种场景:(默认就是前面补全,前面截断)
# 补全的属性是padding,值是pre或者post
# 1.如果我定义的长度标准为8-前面补全
result1 = sequence.pad_sequences(sequences=data,maxlen=8)
# 2.如果我定义的长度标准为8-后面补全
result2 = sequence.pad_sequences(sequences=data,maxlen=8,padding='post')
# 3.如果我定义的长度标准为3-前面截断
result3 = sequence.pad_sequences(sequences=data,maxlen=8,truncating='pre')
# 4.如果我定义的长度标准为3-后面截断
result4 = sequence.pad_sequences(sequences=data,maxlen=8,truncating='post',padding='post')

print('前面截断,前面补全',result1)
# [[ 0  0  0  1  2  3  4  5]
#  [ 1  2  3  4  5  6  7  8]
#  [ 3  4  5  6  7  8  9 10]]
print('前面截断,后面补全',result2)
# [[ 1  2  3  4  5  0  0  0]
#  [ 1  2  3  4  5  6  7  8]
#  [ 3  4  5  6  7  8  9 10]]
print('后面截断,前面补全',result3)
# [[ 0  0  0  1  2  3  4  5]
#  [ 1  2  3  4  5  6  7  8]
#  [ 3  4  5  6  7  8  9 10]]
print('后面截断,后面补全',result4)
# [[1 2 3 4 5 0 0 0]
#  [1 2 3 4 5 6 7 8]
#  [1 2 3 4 5 6 7 8]]

10.文本数据增强

概念:一般基于google/百度/获取其他翻译接口,将文本数据翻译成另外一种语言(一般选择小语种),之后再翻译回原语言;即可认为得到与与原语料同标签的新语料。

新语料加入到原数据集中即可认为是对原数据集数据增强。

更多的翻译次数将产生效率低下, 语义失真等问题

11.RNN模型

循环神经网络RNN

​ 一般以序列数据为输入, 通过网络内部的结构设计有效捕捉序列之间的关系特 征, 一般也是以序列形式进行输出

RNN网络结构特点

​ 由输入层、隐藏层、输出层组成;上一时间步的隐藏层输出作为下一个时间 步的隐藏层输入

​ 每个时间步的输入有2个:数据端输入,隐藏层输入

​ 每个时间步的输出有2个:数据端输出,隐藏层输出

作用

​ RNN结构能够连续性的输入序列数据,进行特征提取。比如:人类的语言,语音特别适合RNN进行处理。

​ RNN模型广泛应用于NLP领域的各项任务, 如文本分类, 情感分析, 意图识别,机器翻译等。

分类

按照输入和输出的角度进行分类

N vs N – RNN 输入N个序列,输出N个序列 写诗 写对联 固定场景表达

N vs 1 – RNN 输入N个序列,输出1个的值 情感分类 意图识别

1 vs N – RNN 输入1个序列,输出N个的值 看图说话

N vs M – RNN 输入N个序列,输出M个序列  翻译、语音转换;文本生成、摘要

按照RNN的内部构造进行分类

​ 传统RNN

​ LSTM

​ Bi-LSTM

​ GRU

​ Bi-GRU

RNN的计算过程

image-20260805202747508

h 表示隐藏状态, 保存了序列数据中的历史信息,并将这些信息传递给下一个时间步,从而允许RNN处理和预测序列数据中的元素。

每一次的输入包含两个值: 上一个时间步的隐藏状态、当前状态的输入值x

每一次的输出也会包含两个值:当前时间步的隐藏状态、当前时间步的预测结果y

隐藏状态作用:

1.记忆功能:隐藏状态就像RNN的记忆,它能够在不同的时间步之间传递信息。当一个新的输入进入网络时,当前的隐藏状态会结合这个新输入来生成新的隐藏状态。

2.上下文理解:由于隐藏状态携带了过去的信息,它可以用于理解和生成与上下文相关的输出。这对于语言模型、机器翻译等任务尤其重要。

3.连接不同时间步:隐藏状态通过网络内部的循环连接将各个时间步连接起来,使得网络可以处理变长的序列数据。

RNN api的9个参数

import torch
import torch.nn as nn

# 1.创建RNN层
# 参数说明:RNN层输入的维度(词向量),RNN层输出的维度(自定义),隐藏层数量
rnn = nn.RNN(2,3,1)

# 2.准备输入的x和初始化的隐藏h0
# 输入数据的参数说明:
# 1-seq-len句子的长度 2-batch-size批次样本数量 3-input-dim输入维度
x = torch.randn(1,4,2)
# 隐藏状态的参数说明
# 1-隐藏层数量 2-batch-size批次样本数量 3-hidden(output)-dim输入维度
h0 = torch.randn(1,4,3)

# 3.将上一时刻的隐藏 + 当前输入  传入RNN层,得到  当前的隐藏 + 输出
output,h1 = rnn(x,h0)

文本的长度处理

# 处理的方案:要么截断,要么补全
# API:不论时截断还是补全,API都相同sequence.pad_sequences
from keras.preprocessing import sequence #虽然报错,但是能运行
data = [
    [1,2,3,4,5],  #补全
    [1,2,3,4,5,6,7,8],  #刚好
    [1,2,3,4,5,6,7,8,9,10]  #截断
]
# 4种场景:(默认就是前面补全,前面截断)
# 补全的属性是:padding,值是pre或者post
# 截断的属性是:truncating,值是pre或者post
# 参数说明:1-要进行处理的数据 2-长度的标准
# 1.如果我定义长度标准为8-前面补全
result1 = sequence.pad_sequences(sequences=data,maxlen=8)
# 2.如果我定义长度标准为8-后面补全
result2 = sequence.pad_sequences(sequences=data,maxlen=8,padding='post')
# 3.如果我定义长度标准为3-前面截断
result3 = sequence.pad_sequences(sequences=data,maxlen=8,truncating='post')
# 4.如果我定义长度标准为3-后面截断
result4 = sequence.pad_sequences(sequences=data,maxlen=8,truncating='post',padding='post')
print('前面截断,前面补全',result1)
# [[ 0  0  0  1  2  3  4  5]
#  [ 1  2  3  4  5  6  7  8]
#  [ 3  4  5  6  7  8  9 10]]
print('前面截断,后面补全',result2)
# [[ 1  2  3  4  5  0  0  0]
#  [ 1  2  3  4  5  6  7  8]
#  [ 3  4  5  6  7  8  9 10]]
print('后面截断,前面补全',result3)
# [[0 0 0 1 2 3 4 5]
#  [1 2 3 4 5 6 7 8]
#  [1 2 3 4 5 6 7 8]]
print('后面截断,后面补全',result4)
# [[1 2 3 4 5 0 0 0]
#  [1 2 3 4 5 6 7 8]
#  [1 2 3 4 5 6 7 8]]

RNN模型的数学公式

h_t = tanh(W_t [ X_t, h_(t-1)] + b_t) 用矩阵参数对数据进行加权求和,再通过激活层添加非线性因素

RNN模型隐藏层个数不为1时,隐藏层hn和output的结果有什么特点?

output的输出结果和最后一个隐藏层结果一致

12.LSTM模型

概念

LSTM(Long Short-Term Memory)也称长短时记忆结构

与经典RNN相比,能够有效捕捉长序列之间的语义关联, 缓解梯度消失或爆炸现象

LSTM结构更加复杂,内部有3个门+1个细胞状态:遗忘门、输入门、细胞状态、输出门

image-20260805220705146

双向BI-LSTM模型

•它没有改变LSTM本身任何的内部结构

•“我爱中国"这句话从左到右和从右到左两次LSTM处理, 将得到的结果张量进行了拼接作为最终输出

•双向LSTM会模型参数和计算复杂度也随之增加了一倍

API

import torch
import torch.nn as nn
# 1.新建LSTM层
# 参数:输入的维度,输出的维度,隐藏层数量
# lstm = nn.LSTM(2,3,1,batch_first=True) # 双向 biLSTM
lstm = nn.LSTM(2,3,1)

# 2.准备输入的数据
# 句子长度,批次数量,输入维度
x = torch.randn(2,3,2)
# h/c参数:隐藏层数量,批次数量,输出维度
h0 = torch.zeros(1,3,3)
c0 = torch.zeros(1,3,3)

# 将输出传入LSTM
output, (h1,c1) = lstm(x,(h0,c0))

print('output',output.shape)
print('h1',h1.shape)
print('h1',c1.shape)

LSTM模型 – 优缺点

优点:

LSTM的门结构能够有效减缓长序列问题中可能出现的梯度消失或爆炸,虽然并不能杜绝这种现象,但在更长的序列问题上表现优于传统RNN

缺点:

由于内部结构相对较复杂,因此训练效率在同等算力下较传统RNN低很多

13.GRU模型

概念

GRU(Gated Recurrent Unit)也称门控循环单元结构

同LSTM一样,能够有效捕捉长序列之间的语义关联, 缓解梯度消失或爆炸现象

同时它的结构比LSTM要简单,比RNN要复杂

GRU结构有2个门:更新门、重置门

结构

image-20260805221505029

API

import torch
import torch.nn as nn
# 1-搭建GRU隐藏层
# 参数:输入维度,输出维度,隐藏层数量
gru = nn.GRU(2,3,2)#默认单向
# gru = nn.GRU(2,3,1,bidirectional=True)#双向-BI-GRU

# 2-准备输入的数据
# 参数:句子长度,批次数量,输入维度
x = torch.randn(2,3,2)
# 参数:隐藏层数量,批次数量,输出维度
h0 = torch.randn(2,3,3)

# 3-将输出输入GRU
output,h1 = gru(x,h0)

print('output:', output.shape) #233
print('h1:', h1.shape) #133

print('output:', output)
print('h1:', h1)

优缺点

优点:

GRU和LSTM作用相同, 在捕捉长序列语义关联时, 能有效抑制梯度消失或爆炸, 效果都优于传统RNN

且计算复杂度相比LSTM要小

缺点:

GRU仍然不能完全解决梯度消失问题, 同时其作用RNN的变体, 有着RNN结构本身的一大弊端, 即不可并行计算, 这在数据量和模型体量逐步增大的未来, 是RNN发展的关键瓶颈

14.迁移学习

fasttext工具介绍

fasttext是自然语言处理(NLP)任务的开源工具包,由Facebook AI Research(FAIR)开发

主要作用

1.进行文本分类 2.训练词向量

它被广泛用于各种任务,包括情感分析、文本分类、命名实体识别、机器翻译等

优势

在保持较高精度的情况下, 快速的进行训练和预测是fasttext的最大优势

优势原因

fasttext工具包中内含的fasttext模型,模型结构简单

使用fasttext模型训练词向量时,使用层次softmax(hs)结构, 来提升超多类别下的模型性能

采用负采样(ns),每次训练仅仅更新一小部分的权重, 降低梯度下降过程中的计算量

fasttext模型过于简单无法捕捉词序特征, 可采用n-gram特征提取文本特征以弥补模型缺陷提升精度

fasttext三层架构

image-20260806104057669

层次softmax(hs)

计算超多类别分类时,为了提高效率, 不再使用传统的softmax来进行多分类的计算

可使用哈夫曼树, 使用层次化的softmax来进行概率的计算

霍夫曼树

image-20260806101911502

构建霍夫曼树的过程

image-20260806175854975

叶子节点:没有子节点的节点

根节点:既不是最顶层的根,也不是最底层的叶子。

构建好霍夫曼树如何训练霍夫曼树?

image-20260806103915990

负采样

为什么需要负采样?

神经网络经过一个训练样本的训练, 它的权重就会进行一次调整。

比如利用Skip-Gram进行词向量训练:如果词汇量的数量为上万个,利用softmax计算概率时, 需要计算上万个概率值, 且每个值都需要进行反向传播更新模型参数,这是非常消耗计算资源的,并且实际中训练起来会非常慢。

负采样每次让一个训练样本仅仅更新一小部分的权重, 这样就会降低梯度下降的计算量.

提高了训练速度、增加负样本能够模拟真实场景下的噪声情况, 能够让模型的稳健性更强

image-20260806111436774

image-20260806111856668

负采样的优势

提高训练速度, 选择了部分数据计算损失, 损失计算更加简单

改进效果, 增加部分负样本, 能够模拟真实场景下的噪声情况, 能够让模型的稳健性更强

文本分类

概念

文本分类的是将文档(例如电子邮件,帖子,文本消息,产品评论等)分配给一个或多个类别

训练文本分类模型需要是有监督学习,需要标签

文本分类种类

二分类:文本被分类两个类别

​ 比如: 判断一句评论是好评还是差评

单标签多分类:文本可被分成多个类别中的一个类别(即被打上某一个标签)

​ 比如: 输入一个人名, 判断它是来自哪个国家的人名

多标签多分类:文本可被分成多个类别中的一多类别(即被打上多个个标签)

​ 比如: 输入一段讨论,话题可以美食、体育新闻、游戏

释义: 二选一,(要么是A,要么是B);单选题( 要么是A要么是B 要么是C或者其他);多选题(是A 也可以是B,也可以是C)

fasttext 文本分类的实现

步骤

•第一步: 获取数据

•第二步: 训练集与验证集的划分

•第三步: 训练模型

•第四步: 使用模型进行预测并评估

•第五步: 模型调优

第六步: 模型保存与重加载

1.直接训练

# 1.准备模型(直接使用fasttext)
model = fasttext.train_supervised('./data/cooking_train.txt')
# 2.模型预测
# 单样本测试
result1 = model.predict('How much does potato starch affect a cheese sauce recipe?')
print(f"result1:{result1}")
# 验证集测试
result2 = model.test('./data/cooking_valid.txt')
print(f"result2:{result2}")

2.基于直接训练,修改原数据

 # 1-准备模型
 model = fasttext.train_supervised('./data/cooking.pre.train')
 # 2-模型预测
 result1 = model.predict('How much does potato starch affect a cheese sauce recipe?')
 print(f"result1:{result1}")
 result2 = model.test('./data/cooking.pre.valid')
 print(f"result2:{result2}")

3.基于上次训练,新增训练轮数

 # 1-准备模型
 model = fasttext.train_supervised('./data/cooking.pre.train', epoch=30)
 # 2-模型预测
 result1 = model.predict('How much does potato starch affect a cheese sauce recipe?')
 print(f"result1:{result1}")
 result2 = model.test('./data/cooking.pre.valid')
 print(f"result2:{result2}")

4.基于上次训练,新增学习率调整

 # 1-准备模型
 model = fasttext.train_supervised('./data/cooking.pre.train', epoch=30,lr=0.3)
 # 2-模型预测
 result1 = model.predict('How much does potato starch affect a cheese sauce recipe?')
 print(f"result1:{result1}")
 result2 = model.test('./data/cooking.pre.valid')
 print(f"result2:{result2}")

5.基于上次训练,新增ngram特征

 # 1-准备模型
 model = fasttext.train_supervised('./data/cooking.pre.train', epoch=30, lr=0.3,wordNgrams=2)
 # 2-模型预测
 result1 = model.predict('How much does potato starch affect a cheese sauce recipe?')
 print(f"result1:{result1}")
 result2 = model.test('./data/cooking.pre.valid')
 print(f"result2:{result2}")

6.基于上次训练,修改损失的计算方式 ns->hs

 # 1-准备模型
 model = fasttext.train_supervised('./data/cooking.pre.train', epoch=30, lr=0.3, wordNgrams=2,loss='hs')
 # 2-模型预测
 result1 = model.predict('How much does potato starch affect a cheese sauce recipe?')
 print(f"result1:{result1}")
 result2 = model.test('./data/cooking.pre.valid')
 print(f"result2:{result2}")

7.自动超参数调优

model = fasttext.train_supervised(
        './data/cooking.pre.train',
        autotuneValidationFile='./data/cooking.pre.valid',
        autotuneDuration=60*2,
    )
# 2.模型预测
result1 = model.predict('How much does potato starch affect a cheese sauce recipe?')
print(f"result1:{result1}")
result2 = model.test('./data/cooking.pre.valid')
print(f"result2:{result2}")

8.修改损失方式,适配多标签问题

# 1.准备模型
    model = fasttext.train_supervised(
        './data/cooking.pre.train',
        epoch=30,
        lr=0.2,
        wordNgrams=2,
        loss='ova' # one vs all 多标签
    )
# 2.模型预测
# 要2个标签,阈值为0.5
result1 = model.predict('How much does potato starch affect a cheese sauce recipe?',k=2,threshold=0.5)
print(f"result1:{result1}")
# 全都要
result2 = model.predict('Regulation and balancing of readymade packed mayonnaise and other sauces', k=-1,
                            threshold=0.5)
print(f"result2:{result2}")
result3 = model.test('./data/cooking.pre.valid')
print(f"result3:{result3}")

9.基于上一步,模型保存模型加载

# 1.准备模型
model = fasttext.train_supervised(
    './data/cooking.pre.train',
    epoch=30,
    lr=0.2,
    wordNgrams=2,
    loss='ova' # one vs all 多标签
)
# 2.模型保存
model.save_model('./model/cooking.model')
print("模型保存成功")

10.使用训练后的模型

# 1.加载训练好的模型
model = fasttext.load_model('./model/cooking.model')
# 2.模型预测
# 要2个标签,阈值为0.5
result1 = model.predict('How much does potato starch affect a cheese sauce recipe?', k=2, threshold=0.5)
print(f"result1:{result1}")
# 全都要
result2 = model.predict('Regulation and balancing of readymade packed mayonnaise and other sauces', k=-1,
                            threshold=0.5)
print(f"result2:{result2}")
result3 = model.test('./data/cooking.pre.valid')
print(f"result3:{result3}")

词向量迁移

概念

使用在大型语料库上已经进行训练完成的词向量模型

释义:已经存在的、已经训练好的词向量,加载一下拿过来直接用

举个例子:下载别人已经训练好的词向量(模型),用fasttext工具再训练一下,这就是词向量迁移

词向量迁移的步骤

第一步: 下载词向量模型压缩的bin.gz文件

第二步: 解压bin.gz文件到bin文件

第三步: 加载bin文件获取词向量

model = fasttext.load_model("cc.zh.300.bin")

model.get_word_vector("音乐")

第四步: 利用邻近词进行效果检验

model.get_nearest_neighbors("音乐")

迁移学习(Transfer Learning)概念

就是把已经学习知识迁移到另外一个地方,形象比喻成迁移学习

迁移学习的形式

特征迁移: 将在一个任务上训练的模型的一部分(如词嵌入或者更高层的特征)应用于另一个任务。

模型迁移: 将在一个任务上训练的模型应用于另一个任务,通常会对源模型进行微调以适应新任务。

知识迁移: 将从源任务中学到的知识(例如,词嵌入、模型参数等)应用于目标任务。

相关术语

预训练模型

一般情况下预训练模型都是大型模型

特点:网络复杂,参数多,需要大量的数据进行训练

在NLP领域,预训练模型往往是语言模型

因语言模型的训练是无监督的,可以获得大规模语料。

比如:输入一句话,生成下一个单词、或者句子,这就是语言模型

比如:打字软件是一个Ai程序,是一个语言模型

语言模型又是许多典型NLP任务的基础,如机器翻译,文本生成,阅读理解等

常见预训练模型BERT, GPT, roBERTa, transformer-XL

微调

微调的对象 :预训练模型

微调的方式: 1:微调预训练模型的参数;2:增加1层或2层输出结构再用小批量标准数据进行重新训练

微调的目标:让模型更加适应特性任务

释义:说白了,就是下载一个预训练模型比如bert,再用自己的小数据重新训练一下,这个动作就是微调

迁移学习的迁移方式

1.直接使用预训练模型,也就是开箱即用

进行相同任务的处理,不需要调整参数或模型结构,这种情况一般只适用于普适任务

​ 比如 fasttest工具包中预训练的词向量模型

模型开发者为了达到开箱即用的效果,将模型结构分各个部分保存为不同的预训练模型,提供对应的加载方法来完成特定目标

**2.微调预训练模型(继承预训练模型+微调参数) **

​ 比如:1:微调预训练模型的参数;2:增加1层或2层输出结构再用小批量标准数据进行重新训练

需要提供小部分的标注数据来进行监督学习

NLP常见的预训练模型

image-20260806175639880

Transformer库的使用

transformers的使用方式为3种:管道、自动模型、指定模型

image-20260808204148557

使用管道完成任务

1.实例化 pipeline 2.模型预测 3.查看模型预测结果

# transformers的使用方式为3种:管道、自动模型、指定模型,当前是管道模型的使用
from transformers import pipeline
import numpy as np
Model_Path = r"C:\Models\PretrainedModel"

# 文本分类任务:'sentiment-analysis'
# 模型:\chinese_sentiment
def dmo1():
    # 1.实例化 pipeline
    # 参数说明:task任务类型,model预训练模型
    model = pipeline(task='sentiment-analysis',model=Model_Path+r'\chinese_sentiment')

    # 2.模型预测
    output = model('这个衣服太丑了')

    # 3.查看模型预测结果
    print(output)

# 特征提取任务:'feature-extraction'
# 模型:\bert-base-chinese
def dm02():
    # 1-实例化 pipeline
    # 模型:\bert-base-chinese
    model = pipeline(task='feature-extraction', model=Model_Path + r'\bert-base-chinese')

    # 2-模型预测
    output = model('小林今天状态很好,穿了一件短袖')

    # 3-查看模型预测结果
    print(np.array(output).shape)  # 1,16,768
    # 样本 = 1
    # 词长度 = 16,为什么不是14? [cls(分类)] [句子=14] [sep(分隔符)]
    # 词向量 = 768



# 3-完形填空:'fill-mask'
# 模型:\chinese-bert-wwm
def dm03():
    # 1-实例化准备模型
    model = pipeline(task='fill-mask',model=Model_Path+r'\chinese-bert-wwm')
    # 2-模型预测
    output = model('我[MASK]小林的工作能力') # 不支持俩[MASK],一次只能MASK一次
    # [{'score': 0.49518275260925293, 'token': 3221, 'token_str': '是', 'sequence': '我 是 小 林'},
    #  {'score': 0.23845022916793823, 'token': 1373, 'token_str': '叫', 'sequence': '我 叫 小 林'},
    #  {'score': 0.1277691274881363, 'token': 4638, 'token_str': '的', 'sequence': '我 的 小 林'},
    #  {'score': 0.047018248587846756, 'token': 2695, 'token_str': '愛', 'sequence': '我 愛 小 林'},
    #  {'score': 0.009361522272229195, 'token': 4263, 'token_str': '爱', 'sequence': '我 爱 小 林'}]
    # 3-查看模型预测结果
    print(output)

# 4-阅读理解任务:'question-answering'(已废弃)
# 模型:\chinese_pretrain_mrc_roberta_wwm_ext_large
def dm04():
    pass

# 5-文本摘要任务:'summarization'(已废弃)
# 模型:\distilbart-cnn-12-6
def dm05():
    pass

# 6-NER任务:'ner'
# 模型:\roberta-base-finetuned-cluener2020-chinese
def dm06():
    # 1-实例化 pipeline
    model = pipeline(task='ner',model=Model_Path+r'\roberta-base-finetuned-cluener2020-chinese')
    # 2-模型预测
    # output = model('小林热爱北京,也喜欢自然语言处理')
    output = model('我爱北京天安门 ,吴彦祖在 天安门   当  保安')
   # output = model('B IIIIIIIII  OOOOO  B I I  O   BI ') #B(begin)I(inside)O(outside)规则
    # 3-查看模型预测结果
    print(output)

    # [{'entity': 'B-address', 'score': np.float32(0.9105133), 'index': 3, 'word': '北', 'start': 2, 'end': 3},
    #  {'entity': 'I-address', 'score': np.float32(0.8755957), 'index': 4, 'word': '京', 'start': 3, 'end': 4},
    #  {'entity': 'I-address', 'score': np.float32(0.71874446), 'index': 5, 'word': '天', 'start': 4, 'end': 5},
    #  {'entity': 'I-address', 'score': np.float32(0.8853819), 'index': 6, 'word': '安', 'start': 5, 'end': 6},
    #  {'entity': 'I-address', 'score': np.float32(0.8807422), 'index': 7, 'word': '门', 'start': 6, 'end': 7},
    #  {'entity': 'B-address', 'score': np.float32(0.7637378), 'index': 12, 'word': '天', 'start': 11, 'end': 12},
    #  {'entity': 'I-address', 'score': np.float32(0.8155359), 'index': 13, 'word': '安', 'start': 12, 'end': 13},
    #  {'entity': 'I-address', 'score': np.float32(0.8217234), 'index': 14, 'word': '门', 'start': 13, 'end': 14},
    #  {'entity': 'B-position', 'score': np.float32(0.7644305), 'index': 16, 'word': '保', 'start': 15, 'end': 16},
    #  {'entity': 'I-position', 'score': np.float32(0.535145), 'index': 17, 'word': '安', 'start': 16, 'end': 17}]




if __name__ == '__main__':
    # dmo1()
    # dm02()
    # dm03()
    dm06()

自动模型的使用方式

整体流程: 1-准备tokenizer 2-准备模型 3-数据转张量 4-模型预测 5-数据处理 (question,decode,zip,根据任务不同,处理方式会有差异)

# transformers的使用方式为3种:管道、自动模型、指定模型,当前是自动模型的使用
Model_Path = r"C:\Models\PretrainedModel"
# 自动加载配置:AutoConfig
# 自动加载模型:AutoModel
# 自动tokenizer:AutoTokenizer
from transformers import AutoConfig,AutoModel,AutoTokenizer
# 文本分类:AutoModelForSequenceClassification
# 掩码任务:AutoModelForMaskedLM
# 阅读理解:AutoModelForQuestionAnswering
# 摘要识别:AutoModelForSeq2SeqLM
# ner:AutoModelForTokenClassification
from transformers import AutoModelForSequenceClassification,AutoModelForMaskedLM,AutoModelForQuestionAnswering
from transformers import AutoModelForSeq2SeqLM,AutoModelForTokenClassification
import numpy as np
import torch

# 1-文本分类任务:'sentiment-analysis'
# 模型:\chinese_sentiment
def dm01():
    # 1-准备tokenizer - 词表
    tokenizer = AutoTokenizer.from_pretrained(Model_Path+r'\chinese_sentiment')
    # 2-准备模型 - 模型
    model = AutoModelForSequenceClassification.from_pretrained(Model_Path+r'\chinese_sentiment')
    # 3-数据转张量 - 基于词表把句子切分,转成词索引送给模型即可
    msg = '这次服务体验很差'
    # 把文本转张量encode
    # 参数说明:1.要编码的数据,2.返回的张量类型pt(pytorch)|tf(tensorflow)|np(numpy),3是否补全,4.最大长度,5.是否截取
    # 新版本的差异:padding='max_length'
    msg_tensor = tokenizer.encode(msg,return_tensors='pt',padding='max_length',truncation=True,max_length=10)
    print(msg_tensor)
    # 4-预测,查看结果
    result = model(msg_tensor)
    # print(result)
    print(f"预测结果为:star{torch.argmax(result.logits.squeeze())+1}")

# 2-特征提取任务:'feature-extraction'
# 模型:\bert-base-chinese
def dm02():
    #效果:输入一个文本 -> 得到文本对应的向量
    # 不带头任务输出 -> 文本对应的向量
    # 大白话:自己组装电脑,只能拿到零件
    # 带头任务输出 -> 向量结果
    # 大白话:品牌机,不需要组装直接拿结果
    # 1-准备tokenizer
    tokenizer = AutoTokenizer.from_pretrained(Model_Path+r'\bert-base-chinese')
    # 2-准备模型
    model = AutoModel.from_pretrained(Model_Path+r'\bert-base-chinese')
    # 3-文本转向量
    msgs = ['今天的服务很好','明天的体验会更好']
    msgs_tensor = tokenizer.encode(msgs,return_tensors='pt',padding='max_length',truncation=True,max_length=5)
    # 4-模型预测
    result = model(msgs_tensor)
    print(result.last_hidden_state.shape)
    print(result.pooler_output.shape)
    # result.last_hidden_state [最后隐藏层的状态]
    # result.pooler_output [池化层的状态]



# 3-完形填空:'fill-mask'
# 模型:\chinese-bert-wwm
def dm03():
    # 1-准备tokenizer
    tokenizer = AutoTokenizer.from_pretrained(Model_Path+r'\chinese-bert-wwm')
    # 2-准备模型
    model = AutoModelForMaskedLM.from_pretrained(Model_Path+r'\chinese-bert-wwm')
    # 3-数据转张量
    msg = '我[MASK]小林的[MASK]能力'
    msg_tensor = tokenizer.encode(msg,return_tensors='pt',padding='max_length',truncation=True,max_length=10)
    # 4-预测,查看结果
    result = model(msg_tensor)
    print(result.logits.shape) # 1,10,21128 1句话10个词,每个词的概率
    print('1-top1:',torch.argmax(result.logits[0][2])) #找出概率最高的词 /top1
    print('2-top1:',torch.argmax(result.logits.squeeze()[6])) #找出概率最高的词 /top1
    # top5
    f_top5 = torch.topk(result.logits.squeeze()[2],k=5)
    s_top5 = torch.topk(result.logits.squeeze()[6],k=5)
    print('f_top5:',f_top5.indices) #indices概率最高的索引值,values表示对应的概率
    print('s_top5:',s_top5.indices) #indices概率最高的索引值,values表示对应的概率
    print(tokenizer.convert_ids_to_tokens(torch.argmax(result.logits.squeeze()[2]).item()))#单个字的索引转文字
    print(tokenizer.convert_ids_to_tokens(f_top5.indices))#多个字的索引转文字

# 4-阅读理解任务:'question-answering'(已废弃)
# 模型:\chinese_pretrain_mrc_roberta_wwm_ext_large
def dm04():
    #1-创建tokenizer
    tokenizer = AutoTokenizer.from_pretrained(Model_Path+r'\chinese_pretrain_mrc_roberta_wwm_ext_large')
    #2-创建模型
    model = AutoModelForQuestionAnswering.from_pretrained(Model_Path+r'\chinese_pretrain_mrc_roberta_wwm_ext_large')
    #3-数据转张量
    context = '小林是一位经验丰富的数据工程师,主要负责自然语言处理项目'
    questions = ['小林是什么职业?','小林具有什么特点?','小林负责什么方向?']
    for question in questions:
        # 参数:1-问题,2-上下文
        question_context_tensor = tokenizer.encode(question,context,return_tensors='pt')
        print(question_context_tensor.shape)
        #4-模型预测
        model.eval()
        result = model(question_context_tensor) #start_logits,end_logits
        # 通过开始的概率预测开始的索引 ,通过结束的概率预测结束的索引 ,答案就是开始索引到结束索引的内容
        start,end = torch.argmax(result.start_logits.squeeze()),torch.argmax(result.end_logits.squeeze())
        # 把答案的张量找到,通过答案的张量去对应的位置
        # 1-需要获取上下文的张量(question_tensor) 2维,需要降维
        # 2-需要获取答案的起始和结束索引(torch.argmax(result.start_logits.squeeze()),torch.argmax(result.end_logits.squeeze()))
        # 3-在上下文中截取对应内容作为答案(索引值):question_tensor[0][start:end+1]
        # 4-把索引值转化成对应的文字:tokenizer.convert_ids_to_tokens(索引)
        print('问题:',question,'答案:',tokenizer.convert_ids_to_tokens(question_context_tensor[0][start:end+1]))

# 5-文本摘要任务:'summarization'(已废弃)
# 模型:\distilbart-cnn-12-6
def dm05():
    # 1-创建tokenizer
    tokenizer = AutoTokenizer.from_pretrained(Model_Path+r'\distilbart-cnn-12-6')
    # 2-创建模型
    model = AutoModelForSeq2SeqLM.from_pretrained(Model_Path+r'\distilbart-cnn-12-6')
    # 3-数据转张量
    text = "BERT is a transformers model pretrained on a large corpus of English data " \
           "in a self-supervised fashion. This means it was pretrained on the raw texts " \
           "only, with no humans labelling them in any way (which is why it can use lots " \
           "of publicly available data) with an automatic process to generate inputs and " \
           "labels from those texts. More precisely, it was pretrained with two objectives:Masked " \
           "language modeling (MLM): taking a sentence, the model randomly masks 15% of the " \
           "words in the input then run the entire masked sentence through the model and has " \
           "to predict the masked words. This is different from traditional recurrent neural " \
           "networks (RNNs) that usually see the words one after the other, or from autoregressive " \
           "models like GPT which internally mask the future tokens. It allows the model to learn " \
           "a bidirectional representation of the sentence.Next sentence prediction (NSP): the models" \
           " concatenates two masked sentences as inputs during pretraining. Sometimes they correspond to " \
           "sentences that were next to each other in the original text, sometimes not. The model then " \
           "has to predict if the two sentences were following each other or not."
    text_tensor = tokenizer.encode(text,return_tensors='pt')
    # 4-模型预测
    model.eval()
    # 生成摘要generate(会有特殊的符号和空格)
    result = model.generate(text_tensor)
    # print(result) #二维
    # art = tokenizer.convert_ids_to_tokens(result.squeeze()) #这里不能用这个方法,因为会有特殊符号和空格无法转换
    # print(''.join(art))
    # 解码:decode
    # 去除特殊符号:skip_special_tokens
    # 去除空格:clean_up_tokenization_spaces
    res = tokenizer.decode(result[0],skip_special_tokens=True,clean_up_tokenization_spaces=True)
    print(res)

# 6-NER任务:'ner'
# 模型:\roberta-base-finetuned-cluener2020-chinese
def dm06():
    # 0-自动加载配置
    config = AutoConfig.from_pretrained(Model_Path+r'\roberta-base-finetuned-cluener2020-chinese')
    # 1-创建tokenizer
    tokenizer = AutoTokenizer.from_pretrained(Model_Path+r'\roberta-base-finetuned-cluener2020-chinese')
    # 2-创建模型
    model = AutoModelForTokenClassification.from_pretrained(Model_Path+r'\roberta-base-finetuned-cluener2020-chinese')
    # 3-数据转张量
    msg = '小林在北京工作,周末经常参观故宫'
    # msg = '我爱北京天安门,我在天安门当保安'
    msg_tensor = tokenizer.encode(msg,return_tensors='pt')
    # 4-模型预测
    model.eval()
    result = model(msg_tensor) #logits
    # print(result.logits.shape)  #1-logits分数的形状:1(1个句子), 23(23个字), 32(32个类型选项)
    # logits分数转成对应的文字(23个文字分别的概率分布)
    input_tokens = tokenizer.convert_ids_to_tokens(msg_tensor[0])
    # print(len(input_tokens)) #2-input_tokens的形状:23
    # print(input_tokens) #2-input_tokens的形状:23  (23个文字)
    zip_data = zip(input_tokens,result.logits.squeeze()) #压缩(字,字的概率)
    # print(zip_data) # 迭代器
    # (字,类型)
    # convert_ids_to_tokens无法识别特殊符号
    # [(tokenizer.convert_ids_to_tokens(index),torch.argmax(result)) for index,result in zip_data]
    output = []
    for index,result in zip_data:
        # 如果是特殊字符,不做处理
        if index in tokenizer.all_special_tokens:
            continue
        #  获取最大概率的索引
        idx = torch.argmax(result).item()
        # print(index,config.id2label[idx])
        output.append((index,config.id2label[idx])) #字,类型
    print('result:',output)
if __name__ == '__main__':
    # dm01()
    # dm02()
    dm03()
    # dm04()
    # dm05()
    # dm06()

指定模型使用方式

# 指定模型和自动模型基本一摸一样,只有模型加载的API需要进行调整,其他基本不变
# 演示: 完形填空修改成指定模型
Model_Path = r'C:\Models\PretrainedModel'
from transformers import BertTokenizer,BertForMaskedLM
import torch
# 3-完形填空:'fill-mask'
# 模型:\chinese-bert-wwm
def dm03():
    # 1-准备tokenizer(from_pretrained谁提供的API?transformers)
    tokenizer = BertTokenizer.from_pretrained(Model_Path+r'\chinese-bert-wwm')
    # 2-准备模型
    model = BertForMaskedLM.from_pretrained(Model_Path+r'\chinese-bert-wwm')
    # 3-数据转张量
    msg = '我[MASK]小林的[MASK]能力'
    msg_tensor = tokenizer.encode(msg,return_tensors='pt',padding='max_length',truncation=True,max_length=10)
    # 4-预测,查看结果
    result = model(msg_tensor)
    print(result.logits.shape) # 1,10,21128 1句话10个词,每个词的概率
    print('1-top1:',torch.argmax(result.logits[0][2])) #找出概率最高的词 /top1
    print('2-top1:',torch.argmax(result.logits.squeeze()[6])) #找出概率最高的词 /top1
    # top5
    f_top5 = torch.topk(result.logits.squeeze()[2],k=5)
    s_top5 = torch.topk(result.logits.squeeze()[6],k=5)
    print('f_top5:',f_top5.indices) #indices概率最高的索引值,values表示对应的概率
    print('s_top5:',s_top5.indices) #indices概率最高的索引值,values表示对应的概率
    print(tokenizer.convert_ids_to_tokens(torch.argmax(result.logits.squeeze()[2]).item()))#单个字的索引转文字
    print(tokenizer.convert_ids_to_tokens(f_top5.indices))#多个字的索引转文字

if __name__ == '__main__':
    dm03()

学习路径总结

  1. 先掌握监督学习、回归、分类、数据预处理和模型评估。
  2. 再通过 PyTorch 张量、自动微分和神经网络理解深度学习训练过程。
  3. 最后进入分词、词向量、循环神经网络、迁移学习和 Transformers。

返回笔记开头 ↑