当前位置: 首页 > news >正文

深度学习 - Ref

深度学习

01- 深度学习概述

1.1 深度学习简介

image-20260610104934541

深度学习作为机器学习的一个分支,专注于使用多层神经网络(深度神经网络)来建模和解决问题。
人脑中有很多相互连接的神经元,当大脑处理信息时,这些神经元之间通过电信号和化学物质相互作用,在大脑的不同区域之间传递信息。神经网络使用人工神经元模仿这种生物现象,这些人工神经元由称为节点的软件模块构成,使用数值计算来进行通信和传递信息。

image-20260610104955617

1.2 深度学习的特点

Ø 使用多层神经网络,能够自动提取数据的多层次特征。

Ø 适合处理非结构化数据,如图像、音频、文本等。

Ø 依赖大量数据和计算资源,训练时间较长。

模型复杂,通常被视为“黑箱”,解释性较差

1.3 深度学习应用场景

 金融服务:预测分析可推荐股票的算法交易,评估贷款审批的业务风险,检测欺诈行为,并帮助管理信贷和投资组合。
 媒体和娱乐:从网上购物到流媒体服务,跟踪用户活动及开发个性化推荐也应用到了深度学习。
 客户服务:聊天机器人、虚拟助手和拨入式客户服务门户网站利用语音识别等工具。
 医疗卫生:通过图像识别应用和深度学习技术,医学影像专家可以利用数字化的医疗记录和医学影像数据来支持和改进医学诊断过程,提供更精确和高效的医疗服务。
 工业自动化:在工厂和仓库中,深度学习应用可以自动检测人或物体何时处于机器的安全距离之外,或协助质量控制及预测性维护。
 自动驾驶汽车:汽车行业研究员使用深度学习来训练汽车检测停车标志、红绿灯、人行横道和行人等对象。
 航空航天和军事:深度学习技术可以用来在监控的大片地理区域中检测物体,从远处识别需要关注的区域,并为部队验证安全或不安全区域。
 执法:语音识别、计算机视觉和自然语言处理(NLP)有助于分析大量数据,从而节省时间和资源。

02- 神经网络基础

2.1 神经网络的构成

image-20260610105145037

在生物体中,神经元 是神经系统最基本的结构和功能单位。
神经元从树突接收其它神经元细胞发出的电化学刺激脉冲,这些脉冲叠加后,一旦强度达到临界值,这个神经元就会产生动作电位,沿着轴突发送电信号。
轴突将刺激传到末端的突触,电信号触发突触上面的电压敏感蛋白,把一个内含神经递质的小泡(突触小体)推到突触的膜上,从而释放出突触小体中的神经递质。这些化学物质会扩散到其它神经元的树突或轴突上。

2.1.1 基本概念和结构

人工神经网络(Artificial Neural Network,ANN)简称 神经网络(NN),是一种模仿生物神经网络结构和功能的计算模型。大多数情况下人工神经网络能在外界信息的基础上改变内部结构,是一种自适应系统(adaptive system),通俗地讲就是具备学习功能。

image-20260610105246112

image-20260610105301738

神经元中的信息逐层传递(一般称为 前向传播forward),上一层神经元的输出作为下一层神经元的输入。

2.1.2 复习感知机

image-20260610105346573

image-20260610105355554

感知机的多个输入信号都有各自的权重,这些权重发挥着控制各个信号的重要性的作用,权重越大,对应信号的重要性越高。偏置则可以用来控制神经元被激活的容易程度。

2.1.3 引入激活函数

image-20260610105432343

被称为 激活函数

2.2 激活函数

2.2.1 激活函数的作用

激活函数是连接感知机和神经网络的桥梁,在神经网络中起着至关重要的作用。
如果没有激活函数,整个神经网络就等效于单层线性变换,不论如何加深层数,总是存在与之等效的“无隐藏层的神经网络”。激活函数必须是非线性函数,也正是激活函数的存在为神经网络引入了非线性,使得神经网络能够学习和表示复杂的非线性关系。

2.2.2 阶跃(Binary step)函数

image-20260610105643694

def step_function(x):if x > 0:return 1else:return 0

这里的x只能取一个数值(浮点数)。如果我们希望直接传入Numpy数组进行批量化的操作,可以改进如下:

def step_function(x):return np.array(x > 0, dtype=int)

2.2.3 Sigmoid函数

image-20260610105759115

2.2.4 Tanh函数

image-20260610105820612

image-20260610105831222

2.2.5 ReLU函数

image-20260610105906678

image-20260610105918575

2.2.6 Softmax函数

image-20260610105948747

def softmax(x):if x.ndim == 2:x = x.Tx = x - np.max(x, axis=0)y = np.exp(x) / np.sum(np.exp(x), axis=0)return y.T x = x - np.max(x) # 溢出对策return np.exp(x) / np.sum(np.exp(x))

2.2.7 其他常见激活函数

image-20260610110028526

image-20260610110042401

image-20260610110051491

image-20260610110107005

image-20260610110115584

2.2.8 如何选择激活函数

image-20260610110139968

2.3 神经网络的简单实现

深度神经网络由多个层(layer)组成,通常将其称之为 模型(Model)。整个模型接受原始 输入(特征),生成 输出(预测),并包含一些 参数。而在模型内部,每个单独的层都会接受一些输入(由前一层提供),生成输出(到下一层的输入),并包含一组参数;层层向下传递,就可以得到最终的输出值。
神经网络中的参数,就是每一层的权重和偏置。

2.3.1 三层神经网络

image-20260610135951220

简单起见,我们的输入层(第0层)有2个神经元;第1个隐藏层(第1层)有3个神经元;第2个隐藏层(第2层)有2个神经元;输出层(第3层)有2个神经元。

2.3.2 各层之间的信号传递

上面只是三层网络的示意图,实际上每层还应该有偏置,各输入信号加权总和还要经过激活函数的处理。接下来逐层进行分析,考察信号在各层之间传递的过程。

image-20260610140123230

image-20260610140134252

image-20260610140148243

image-20260610140158862

image-20260610140211451

2.3.3 代码实现

我们可以将神经网络的所有参数(每一层的权重w和偏置b),保存在一个字典network中;并定义函数:

l init_network():对参数进行初始化,每一个权重参数都是一个矩阵(二维),每一个偏置参数则是一个数组(一维);

l forward():前向传播,将输入信号转换为输出信号的处理操作。

这里的激活函数,隐藏层用sigmoid,输出层用identity(恒等函数)。

具体代码如下:

import numpy as np
from common.functions import sigmoid, identity_functiondef init_network():network = {}network['W1'] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])network['b1'] = np.array([0.1, 0.2, 0.3])network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])network['b2'] = np.array([0.1, 0.2])network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]])network['b3'] = np.array([0.1, 0.2])return networkdef forward(network, x):w1, w2, w3 = network['W1'], network['W2'], network['W3']b1, b2, b3 = network['b1'], network['b2'], network['b3']a1 = np.dot(x, w1) + b1z1 = sigmoid(a1)a2 = np.dot(z1, w2) + b2z2 = sigmoid(a2)a3 = np.dot(z2, w3) + b3y = identity_function(a3)return ynetwork = init_network()
x = np.array([1.0, 0.5])
y = forward(network, x)print(y)

2.4 应用案例:手写数字识别

我们依然使用Digit Recognizer数据集来进行手写数字识别: https://www.kaggle.com/competitions/digit-recognizer 。

文件train.csv中包含手绘数字(从0到9)的灰度图像,每张图像为28×28像素,共784像素。每个像素有一个0到255的值表示该像素的亮度。

文件第1列为标签,之后784列分别为784个像素的亮度值。

我们的任务,就是要搭建一个神经网络,实现它的前向传播;也就是要根据输入的数据(28×28 = 784数据点表示的图像),推断出它到底是哪个数字,这个过程也被称为“推理”。

这里,我们构建的也是一个三层神经网络,输入层应该有784个神经元,输出层有10个神经元(表示0~9的分类结果);中间设置2个隐藏层,第一个隐藏层有50个神经元,第二个隐藏层有100个神经元。这里的参数是需要 学习 得到的;我们假设已经学习完毕,直接从保存好的文件nn_sample中进行读取即可。

具体代码如下:

import numpy as np
import pandas as pdimport joblib
from sklearn.model_selection import train_test_split 
from sklearn.preprocessing import MinMaxScaler
from common.functions import sigmoid, softmaxdef get_data():# 加载数据集data = pd.read_csv("../data/train.csv")# 划分训练集和测试集X = data.drop("label", axis=1)y = data["label"]x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.3)# 归一化preprocessor = MinMaxScaler()x_train = preprocessor.fit_transform(x_train)x_test = preprocessor.transform(x_test)return x_test, t_testdef init_network():# 加载模型network = joblib.load("../data/nn_sample")return networkdef predict(network, x):w1, w2, w3 = network['W1'], network['W2'], network['W3']b1, b2, b3 = network['b1'], network['b2'], network['b3']a1 = np.dot(x, w1) + b1z1 = sigmoid(a1)a2 = np.dot(z1, w2) + b2z2 = sigmoid(a2)a3 = np.dot(z2, w3) + b3y = softmax(a3)return yx, t = get_data()
network = init_network()batch_size = 100 # 批数量
accuracy_cnt = 0for i in range(0, len(x), batch_size):x_batch = x[i:i+batch_size]y_batch = predict(network, x_batch)p = np.argmax(y_batch, axis=1)accuracy_cnt += np.sum(p == t[i:i+batch_size])print("Accuracy:" + str(float(accuracy_cnt) / len(x)))

03- 神经网络的学习

神经网络的主要特点,就是可以从数据中进行“学习”。这个学习的过程,就是让训练数据自动决定最优的权重参数。
神经网络(深度学习)也是机器学习的一种;跟传统机器学习方法相比,神经网络不需要人工设置 特征量(如 SIFT、HOG等),这样就可以用同样的流程直接处理所有问题了。

3.1 损失函数

神经网络中,需要以某个指标为线索来寻找最优权重参数;这个指标就是 损失函数(loss function)。

3.1.1 常见损失函数

image-20260610150412916

image-20260610150423763

image-20260610150433916

def cross_entropy_error(y, t):if y.ndim == 1:t = t.reshape(1, t.size)y = y.reshape(1, y.size)# 监督数据是one-hot向量的情况下,转换为正确解标签的索引if t.size == y.size:t = t.argmax(axis=1)batch_size = y.shape[0]return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size

3.1.2 分类任务损失函数

1)二分类任务损失函数
二分类任务常用二元交叉熵损失函数(Binary Cross-Entropy Loss)。

image-20260610150544277

3.1.3 回归任务损失函数

image-20260610150612975

2)MSE

image-20260610150634291

image-20260610150642998

3.2 数值微分

损失函数的值越小,代表我们选取的参数越适合;想要求得损失函数的最小值,最基本的想法就是对函数求导,解出导数值为0的点,并判断它是否为极小值/最小值。

然而,实际的函数直接求导,不容易得到解析解。这时可以用数值微分的方式来求某点处的导数,这在工程上应用非常广泛。

3.2.1 导数和数值微分

image-20260610150805099

def numerial_diff(f, x):h = 1e-4  # 微小值 0.0001return (f(x+h) - f(x-h)) / (2 * h)

在这里,我们以x为中心,计算它两边各发生微小变化后的差分,可以避免只计算单向增大时的误差。这种方法称为 中心差分

另外,取微小值h时不能太小,这会导致计算机浮点数表示的精度不够,出现舍入误差。

数值微分 求导案例

image-20260610152607580

image-20260610152814365

image-20260610152833990

3.2.2 偏导数

如果函数f的自变量并非单个元素,而是多个元素,例如:

image-20260610150930894

3.2.3 梯度

image-20260610151006139

image-20260610151017567

def _numerical_gradient(f, x):h = 1e-4 # 0.0001grad = np.zeros_like(x)for idx in range(x.size):tmp_val = x[idx]x[idx] = float(tmp_val) + hfxh1 = f(x) # f(x+h)x[idx] = tmp_val - h fxh2 = f(x) # f(x-h)grad[idx] = (fxh1 - fxh2) / (2*h)x[idx] = tmp_val # 还原值return grad

image-20260611083237018

3.3 神经网络的梯度计算

image-20260610151121401

class simpleNet:def __init__(self):self.W = np.random.randn(2,3)def predict(self, x):return np.dot(x, self.W)def loss(self, x, t):z = self.predict(x)y = softmax(z)loss = cross_entropy_error(y, t)return lossx = np.array([0.6, 0.9])
t = np.array([0, 0, 1])
net = simpleNet()f = lambda w: net.loss(x, t)
dW = numerical_gradient(f, net.W)print(dW)

3.4 随机梯度下降法(SGD)

3.4.1 梯度下降法

image-20260610151227104

def gradient_descent(f, init_x, lr=0.01, step_num=100):x = init_xx_history = []for i in range(step_num):x_history.append( x.copy() )grad = numerical_gradient(f, x)x -= lr * gradreturn x, np.array(x_history)

3.4.2 模型训练相关概念

image-20260610151318916

3.4.3 SGD

image-20260610151343264

3.5 综合代码实现

应用案例:手写数字识别

我们还是考察之前手写数字识别的案例。方便起见,这次只实现一个2层的神经网络(中间只有1个隐藏层、后面是输出层),利用之前的数据集来进行学习,学习方法采用SGD。

首先我们先来实现一个TwoLayerNet类:

from common.functions import *
from common.gradient import numerical_gradientclass TwoLayerNet:def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):# 初始化权重self.params = {}self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)              self.params['b1'] = np.zeros(hidden_size)self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)self.params['b2'] = np.zeros(output_size)def predict(self, x):W1, W2 = self.params['W1'], self.params['W2']b1, b2 = self.params['b1'], self.params['b2']a1 = np.dot(x, W1) + b1z1 = sigmoid(a1)a2 = np.dot(z1, W2) + b2y = softmax(a2)return y# x:输入数据, t:监督数据def loss(self, x, t):y = self.predict(x)return cross_entropy_error(y, t)def accuracy(self, x, t):y = self.predict(x)y = np.argmax(y, axis=1)t = t.reshape(-1)accuracy = np.sum(y == t) / float(x.shape[0])return accuracy# x:输入数据, t:监督数据def numerical_gradient(self, x, t):loss_W = lambda W: self.loss(x, t)grads = {}grads['W1'] = numerical_gradient(loss_W, self.params['W1'])grads['b1'] = numerical_gradient(loss_W, self.params['b1'])grads['W2'] = numerical_gradient(loss_W, self.params['W2'])grads['b2'] = numerical_gradient(loss_W, self.params['b2'])return grads

然后我们再利用SGD对神经网络进行训练学习:

import numpy as np
import matplotlib.pyplot as plt
from two_layer_net import TwoLayerNet# 读入数据
x_train, x_test, t_train, t_test = get_data ()network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)iters_num = 10000  # 适当设定循环的次数
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1train_loss_list = []
train_acc_list = []
test_acc_list = []iter_per_epoch = max(train_size / batch_size, 1)for i in range(iters_num):batch_mask = np.random.choice(train_size, batch_size)x_batch = x_train[batch_mask]t_batch = t_train[batch_mask]# 计算梯度grad = network.numerical_gradient(x_batch, t_batch)# 更新参数for key in ('W1', 'b1', 'W2', 'b2'):network.params[key] -= learning_rate * grad[key]loss = network.loss(x_batch, t_batch)train_loss_list.append(loss)if i % iter_per_epoch == 0:train_acc = network.accuracy(x_train, t_train)test_acc = network.accuracy(x_test, t_test)train_acc_list.append(train_acc)test_acc_list.append(test_acc)print("train acc, test acc | " + str(train_acc) + ", " + str(test_acc))# 绘制图形
markers = {'train': 'o', 'test': 's'}
x = np.arange(len(train_acc_list))
plt.plot(x, train_acc_list, label='train acc')
plt.plot(x, test_acc_list, label='test acc', linestyle='--')
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()

04- 反向传播算法

反向传播(Backward Propagation或Back Propagation,BP算法)指的是计算神经网络参数梯度的方法。简言之,该方法根据微积分中的链式法则,按相反的顺序从输出层到输入层遍历网络。该算法存储了计算某些参数梯度时所需的任何中间变量。

4.1 计算图

计算图将计算过程用图表示出来。这里说的图是数据结构中的图,通过多个节点和边表示(连接节点的直线称为边)。

image-20260612072840055

image-20260612072851082

image-20260612072908537

4.2 链式法则

image-20260612072946069

image-20260612072958824

4.3 反向传播

利用计算图的反向传播,可以很容易地计算出输出关于输入的偏导数。

4.3.1 加法节点的反向传播

image-20260612073105499

4.3.2 乘法节点的反向传播

image-20260612073133135

4.4 激活层的反向传播和实现

现在将计算图应用到神经网络中。神经网络中一步重要的计算操作就是激活函数,下面就来讨论各种激活函数的反向传播,基于这些我们就可以用代码实现激活层的完整功能了。

4.4.1 ReLU的反向传播

image-20260612073235952

class Relu:def __init__(self):self.mask = Nonedef forward(self, x):self.mask = (x <= 0)out = x.copy()out[self.mask] = 0return outdef backward(self, dout):dout[self.mask] = 0dx = doutreturn dx

4.4.2 Sigmoid的反向传播

image-20260612073328694

image-20260612073340097

class Sigmoid:def __init__(self):self.out = Nonedef forward(self, x):out = sigmoid(x)self.out = outreturn outdef backward(self, dout):dx = dout * (1.0 - self.out) * self.outreturn dx

4.5 Affine的反向传播和实现

image-20260612073437223

image-20260612073451325

class Affine:def __init__(self, W, b):self.W =Wself.b = bself.x = Noneself.original_x_shape = None# 权重和偏置参数的导数self.dW = Noneself.db = Nonedef forward(self, x):# 对应张量self.original_x_shape = x.shapex = x.reshape(x.shape[0], -1)self.x = xout = np.dot(self.x, self.W) + self.breturn outdef backward(self, dout):dx = np.dot(dout, self.W.T)self.dW = np.dot(self.x.T, dout)self.db = np.sum(dout, axis=0)dx = dx.reshape(*self.original_x_shape)  return dx

4.6 输出层的反向传播和实现

image-20260612073553112

image-20260612073603077

image-20260612073613823

class SoftmaxWithLoss:def __init__(self):self.loss = Noneself.y = None # softmax的输出self.t = None # 监督数据def forward(self, x, t):self.t = tself.y = softmax(x)self.loss = cross_entropy_error(self.y, self.t)return self.lossdef backward(self, dout=1):batch_size = self.t.shape[0]if self.t.size == self.y.size: # 监督数据是one-hot-vector的情况dx = (self.y - self.t) / batch_sizeelse:dx = self.y.copy()dx[np.arange(batch_size), self.t] -= 1dx = dx / batch_sizereturn dx

4.7 反向传播的综合代码实现

现在可以在之前手写数字识别案例的基础上,对SGD的计算过程进行优化。核心就是使用误差的反向传播法来计算梯度,而不是使用差分数值计算;这将大大提升学习的效率。

对于二层网络TwoLayerNet,隐藏层由一个Affine层和一个ReLU层组成,输出层则由一个Affine层和一个Softmax-with-Loss层组成。由于之前已经实现了各层的类,现在只要用“搭积木”的方式将它们拼接在一起就可以了。

将TwoLayerNet类的代码实现改进如下:

import numpy as np
from common.layers import *
from common.gradient import numerical_gradient
from collections import OrderedDictclass TwoLayerNet:def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01):# 初始化权重self.params = {}self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)self.params['b1'] = np.zeros(hidden_size)self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size) self.params['b2'] = np.zeros(output_size)# 生成层self.layers = OrderedDict()self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])self.layers['Relu1'] = Relu()self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])self.lastLayer = SoftmaxWithLoss()def predict(self, x):for layer in self.layers.values():x = layer.forward(x)return x# x:输入数据, t:监督数据def loss(self, x, t):y = self.predict(x)return self.lastLayer.forward(y, t)def accuracy(self, x, t):y = self.predict(x)y = np.argmax(y, axis=1)if t.ndim != 1 : t = np.argmax(t, axis=1)accuracy = np.sum(y == t) / float(x.shape[0])return accuracy# x:输入数据, t:监督数据def numerical_gradient(self, x, t):loss_W = lambda W: self.loss(x, t)grads = {}grads['W1'] = numerical_gradient(loss_W, self.params['W1'])grads['b1'] = numerical_gradient(loss_W, self.params['b1'])grads['W2'] = numerical_gradient(loss_W, self.params['W2'])grads['b2'] = numerical_gradient(loss_W, self.params['b2'])return gradsdef gradient(self, x, t):# forwardself.loss(x, t)# backwarddout = 1dout = self.lastLayer.backward(dout)layers = list(self.layers.values())layers.reverse()for layer in layers:dout = layer.backward(dout)# 设定grads = {}grads['W1'], grads['b1'] = self.layers['Affine1'].dW, self.layers['Affine1'].dbgrads['W2'], grads['b2'] = self.layers['Affine2'].dW, self.layers['Affine2'].dbreturn grads

接下来的训练过程,跟之前是一样的,只需要调用新的梯度计算函数即可。

05- 学习的技巧

5.1 深度神经网络及其问题

5.1.1 深度学习

当神经网络的层数加深时,往往可以有效地提高识别精度;特别是对于大规模的复杂问题,通常都需要用更多的层来分层次传递信息,而且可以有效减少网络的参数数量,从而使得学习更加高效。使用深度神经网络进行的学习就被称为 深度学习(Deep Learning)。
ILSVRC是近年来机器视觉领域最受追捧且最具权威的学术竞赛之一,代表了图像领域的最高水平。它包含多个测试项目,其中最有名的就是“类别分类”(Classification),该项目会进行 1000 个类别的分类,比试识别精度。自从 2012 年以后的 ILSVRC 比赛,优胜队伍采用的方法基本上都是以深度学习为基础的。
可以说,深度学习代表了目前人工智能领域的发展方向,正以超乎想象的速度发展并改变我们的生活。

5.1.2 梯度消失和梯度爆炸

在某些神经网络中,随着网络深度的增加,梯度在隐藏层反向传播时倾向于变小。这就意味着,前面隐藏层中的神经元要比后面的学习起来更慢。这种现象被称为“梯度消失”。
与之对应,如果我们进行一些特殊的调整(比如初始权重很大),可以让梯度反向传播时不会明显减小,从而解决梯度消失的问题;然而这样一来,前面层的梯度又会变得非常大,引起网络不稳定,无法再从训练数据中学习。这种现象被称为“梯度爆炸”。

基于梯度学习的深度神经网络中,梯度本身是不稳定的,前面层中的梯度可能“消失”,也可能“爆炸”。
对于梯度消失和梯度爆炸,一个容易理解的解释是:当反向传播进行很多层的时候,每一层都对前一层梯度乘以了一个系数;因此当这个系数比较小(小于1)时,越往前传递,梯度就会越小、训练越慢,导致梯度消失;而如果这个系数比较大,则越往前传递梯度就会越大,导致梯度爆炸。
所以,深度神经网络的训练是比较复杂的,会有一系列的问题。研究表明,激活函数的选择、权重的初始化,甚至学习算法的实现方式都是影响因素;另外,网络的架构和其它一些超参数也有重要影响。
为了让深度神经网络的学习更加稳定、高效,我们需要考虑进一步改进寻找最优参数的方法,以及如何设置参数初始值、如何设定超参数;此外还应该解决过拟合的问题。

5.2 更新参数方法的优化

对于深度神经网络的学习,之前更新参数的方法是随机梯度下降法(SGD)。这种方法比较简单,也非常经典,但在很多具体问题中并不是最高效的。

5.2.1 SGD的缺点

image-20260612083157716

class SGD:def __init__(self, lr=0.01):self.lr = lrdef update(self, params, grads):for key in params.keys():params[key] -= self.lr * grads[key] 

SGD有以下问题:
 局部最优解:陷入局部最优,尤其在非凸函数中,难以找到全局最优解。

 鞍点:陷入鞍点,梯度为0,导致训练停滞。
 收敛速度慢:高维或非凸函数中,收敛速度较慢。
 学习率选择:学习率过大导致震荡或不收敛,过小则收敛速度慢。

image-20260612083243434

5.2.2 Momentum

image-20260612083331492

image-20260612083350671

image-20260612083402176

Momentum的代码实现如下:

class Momentum:def __init__(self, lr=0.01, momentum=0.9):self.lr = lrself.momentum = momentumself.v = Nonedef update(self, params, grads):if self.v is None:self.v = {}for key, val in params.items():                                self.v[key] = np.zeros_like(val)for key in params.keys():self.v[key] = self.momentum*self.v[key] - self.lr*grads[key] params[key] += self.v[key]

5.2.3 学习率衰减

度学习模型训练中调整最频繁的当属学习率,好的学习率可以使模型逐渐收敛并获得更好的精度。较大的学习率可以加快收敛速度,但可能在最优解附近震荡或不收敛;较小的学习率可以提高收敛的精度,但训练速度慢。学习率衰减是一种平衡策略,初期使用较大学习率快速接近最优解,后期逐渐减小学习率,使参数更稳定地收敛到最优解。

1)等间隔衰减

image-20260612085502644

2)指定间隔衰减

image-20260612085524663

3)指数衰减

image-20260612085542942

5.2.4 AdaGrad

image-20260612091940312

image-20260612091954634

class AdaGrad:def __init__(self, lr=0.01):self.lr = lrself.h = Nonedef update(self, params, grads):if self.h is None:self.h = {}for key, val in params.items():self.h[key] = np.zeros_like(val)for key in params.keys():self.h[key] += grads[key] * grads[key]params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)

5.2.5 RMSProp

image-20260612092051684

image-20260612092102093

5.2.6 Adam

image-20260612092127128

image-20260612092144064

image-20260612092156199

image-20260612094712547

image-20260612094547238

5.3 参数初始化

参数初始化方案的选择在神经网络学习中起着举足轻重的作用,它对保持数值稳定性至关重要。此外,这些初始化方案的选择可以与激活函数的选择有趣的结合在一起。我们选择哪个激活函数以及如何初始化参数,可以决定优化算法收敛的速度有多快;糟糕选择可能会导致我们在训练时遇到梯度爆炸或梯度消失。

5.3.1 常数初始化

image-20260612094842166

注意:将权重初始值设为0将无法正确进行学习。严格地说,不能将权重初始值设成一样的值。因为这意味着反向传播时权重全部都会进行相同的更新,被更新为相同的值(对称的值)。这使得神经网络拥有许多不同的权重的意义丧失了。为了防止“权重均一化”(瓦解权重的对称结构),必须随机生成初始值。

5.3.2 秩初始化

image-20260612094925606

5.3.3 正态分布初始化

image-20260612094949098

5.3.4 均匀分布初始化

image-20260612095009647

5.3.5 Xavier初始化(Glorot初始化)

image-20260612095032110

5.3.6 He初始化(Kaiming初始化)

image-20260612095059311

5.4 正则化

机器学习的问题中,过拟合 是一个很常见的问题。
过拟合指的是能较好拟合训练数据,但不能很好地拟合不包含在训练数据中的其他数据。机器学习的目标是提高泛化能力,希望即便是不包含在训练数据里的未观测数据,模型也可以进行正确的预测。因此可以通过 正则化 方法来抑制过拟合。
常用的正则化方法有Batch Normalization、权值衰减、Dropout、早停法等。

5.4.1 Batch Normalization批量标准化

image-20260612095200152

image-20260612095210373

5.4.2 权值衰减

image-20260612095232657

5.4.3 Dropout随机失活

image-20260612095257140

image-20260612095313631

06- PyTorch简介

6.1 什么是PyTorch

PyTorch是一个开源的Python机器学习库,基于Torch库(一个有大量机器学习算法支持的科学计算框架,有着与Numpy类似的张量(Tensor)操作,采用的编程语言是Lua),底层由C++实现,应用于人工智能领域,如计算机视觉和自然语言处理。
PyTorch主要有两大特征:
 类似于NumPy的张量计算,能在GPU或MPS等硬件加速器上加速。
 基于带自动微分系统的深度神经网络。
PyTorch官网: https://pytorch.org/ 。

6.2 PyTorch安装

PyTorch分为CPU和GPU版本。
PyTorch选择安装版本页面: https://pytorch.org/get-started/locally/ 。

image-20260612153947473

6.2.1 CPU版本PyTorch安装

直接通过pip命令安装即可:pip3 install torch torchvision torchaudio。
若需要离线安装,可以考虑下载whl包然后自行安装。下载whl的链接: https://download.pytorch.org/whl/torch/ 。
手动下载whl时,需要注意PyTorch与torchvision之间版本对应关系。可以到https://github.com/pytorch/vision或https://pytorch.org/get-started/previous-versions/查看。

image-20260612154015582

6.2.2 GPU版本PyTorch安装

绝大多数情况下我们会安装GPU版本的PyTorch。目前PyTorch不仅支持NVIDIA的GPU,还支持AMD的ROCm的GPU。
安装GPU版本的PyTorch步骤:
 根据NVIDIA驱动程序版本和要安装的PyTorch版本,确定安装哪个版本的CUDA。
 根据安装好的CUDA版本,安装对应版本的PyTorch。
1)GPU计算能力要求
对于N卡,需要计算能力(compute capability)≥3.0。

image-20260612154045016

可在https://developer.nvidia.cn/cuda-gpus#compute查看GPU计算能力。

2)CUDA版本选择
CUDA(Compute Unified Device Architecture)是NVIDIA开发的并行计算平台和编程平台,允许开发者利用NVIDIA GPU的强大计算能力进行通用计算。CUDA不仅用于图形渲染,还广泛应用于科学计算、深度学习、金融建模等领域。
(1)根据NVIDIA驱动程序版本确定支持的最高CUDA版本
打开NVIDIA控制面板→系统信息→组件,查看NVCUDA64.DLL的产品名称栏,可查看驱动程序支持的最高CUDA版本。

image-20260612154115524

或在命令行中输入nvidia-smi,在CUDA Version栏查看支持的最高CUDA版本。

image-20260612154138917

(2)根据PyTorch版本选择CUDA版本
需要安装特定版本的CUDA版本,才能使用特定版本的PyTorch。在PyTorch下载页面可查看该版本PyTorch支持的CUDA版本。

image-20260612154158241

或在https://download.pytorch.org/whl/torch/查看过往版本PyTorch支持的CUDA版本。

例如:

image-20260612154212150

此处的cu126表示支持CUDA12.6版本。

3)CUDA安装(可选)

NVIDIA官网通常只展示最新的CUDA版本,过往CUDA版本可在https://developer.nvidia.com/cuda-toolkit-archive下载。

选择相应CUDA版本后,选择要安装的平台,Installer Type安装方式选择exe(local)本地安装。

image-20260612154253316

双击.exe文件进行安装,首先需要输入临时解压路径,临时解压路径在安装结束后会自动被删除,保持默认即可。点击OK。

image-20260612154309727

若在系统检查环节提示“您正在安装老版本的驱动程序…”,说明安装包中包含的驱动程序版本比当前已安装的驱动程序的版本旧,可忽略。点击继续。

image-20260612154320501

同意安装协议并继续。

image-20260612154338869

选择精简,会安装所有组件并覆盖现有驱动程序。点击下一步。

image-20260612154351624

如果出现以下提示,表明缺少Visual Studio,部分组件不能正常工作。不用在意,选择I understand…。点击Next。

image-20260612154404972

点击下一步。

image-20260612154417591

安装完成,点击关闭。

image-20260612154432317

可在命令行使用nvcc --version查看CUDA版本信息。

image-20260612154448796

4)PyTorch安装

新建一个虚拟环境来安装PyTorch。
在命令行输入conda create -n pytorch-2.6.0-gpu python=3.12创建一个环境名为pytorch-2.6.0-gpu,Python版本为3.12的虚拟环境。
使用conda activate pytorch-2.6.0-gpu激活pytorch-2.6.0-gpu虚拟环境。

在官网https://pytorch.org/get-started选择要安装的版本,复制命令,在命令行中执行以安装PyTorch。

image-20260612154523457

若安装速度较慢或安装失败,可配置pip的国内镜像源pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple 。

要在新的虚拟环境中使用Jupyter Notebook,需使用conda install jupyter notebook安装。

编写代码时需在IDE中选择新创建的虚拟环境作为Python解释器。

6.3 张量创建

Tensor(张量)是PyTorch的核心数据结构。张量在不同学科中有不同的意义,在深度学习中张量表示一个多维数组,是标量、向量、矩阵的拓展。如一个RGB图像的数组就是一个三维张量,第1维是图像的高,第2维是图像的宽,第3维是图像的颜色通道。

6.3.1 基本张量创建

1)torch.tensor(data)创建指定内容的张量

import torch
import numpy as np# 创建标量张量
tensor1 = torch.tensor(10)
print(tensor1)# 使用列表创建张量
tensor2 = torch.tensor([1, 2, 3])
print(tensor2)# 使用numpy创建张量
tensor3 = torch.tensor(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]))
print(tensor3)

2)torch.Tensor(size)创建指定形状的张量

import torch# 创建指定形状的张量,默认类型为float32
tensor1 = torch.Tensor(3, 2, 4)
print(tensor1)
print(tensor1.dtype)# 也可以用来创建指定内容的张量
tensor2 = torch.Tensor([[1, 2, 3, 4], [5, 6, 7, 8]])
print(tensor2)

3)创建指定类型的张量

可通过torch.IntTensor()、torch.FloatTensor()等创建。
或在torch.tensor()中通过dtype参数指定类型。

import torch# 创建int32类型的张量
tensor1 = torch.IntTensor(2, 3)
tensor2 = torch.tensor([1, 2, 3], dtype=torch.int32)
print(tensor1)
print(tensor2)# 元素类型不匹配则会进行类型转换
tensor1 = torch.IntTensor([1.1, 2.2, 3.6])
tensor2 = torch.tensor([3.1, 2.2, 1.6], dtype=torch.int32)
print(tensor1)
print(tensor2)# 创建int64类型的张量
tensor1 = torch.LongTensor([1, 2, 3])
tensor2 = torch.tensor([1, 2, 3], dtype=torch.int64)
print(tensor1, tensor1.dtype)
print(tensor2, tensor1.dtype)# 创建int16类型的张量
tensor1 = torch.ShortTensor(2, 2)
tensor2 = torch.tensor([1, 2, 3], dtype=torch.int16)
print(tensor1, tensor1.dtype)
print(tensor2, tensor1.dtype)# 创建float32类型的张量
tensor1 = torch.FloatTensor([9, 8, 7])
tensor2 = torch.tensor([1, 2, 3], dtype=torch.float32)
print(tensor1, tensor1.dtype)
print(tensor2, tensor1.dtype)# 创建float64类型的张量
tensor1 = torch.DoubleTensor(2, 3, 1)
tensor2 = torch.tensor([1, 2, 3], dtype=torch.float64)
print(tensor1)
print(tensor2)

6.3.2 指定区间的张量创建

1)torch.arange(start, end, step)在区间内按步长创建张量

import torch# torch.arange(start, end, step) 在区间[start,end)中创建步长为step的张量
tensor1 = torch.arange(10, 30, 2)
print(tensor1)# torch.arange(end) 创建区间为[0,end),步长为1的张量
tensor2 = torch.arange(6)
print(tensor2)

2)torch.linspace(start, end, steps)在区间内按元素数量创建张量

import torch# torch.linspace(start, end, steps) 在区间按元素数量创建张量
tensor1 = torch.linspace(10, 30, 5)
print(tensor1)

3)torch.logspace(start, end, steps, base)在指数区间内按指定底数创建张量

import torch# torch.logspace(start, end, steps, base) 在区间[start,end]之间生成steps个数,并以base为底,区间内的数为指数创建张量
tensor1 = torch.logspace(1, 3, 3, 2)
print(tensor1)

6.3.3 按数值填充张量

Ø torch.zeros(size)创建指定形状的全0张量

Ø torch.ones(size)创建指定形状的全1张量

Ø torch.full(size, value)创建指定形状的按指定值填充的张量

Ø torch.empty(size)创建指定形状的未初始化的张量

Ø torch.zeros_like(input)创建与给定张量形状相同的全0张量

Ø torch.ones_like(input)创建与给定张量形状相同的全1张量

Ø torch.full_like(input, value)创建与给定张量形状相同的按指定值填充的张量

Ø torch.empty_like(input)创建与给定张量形状相同的未初始化的张量

import torch# torch.zeros(size) 创建指定形状的全0张量
tensor1 = torch.zeros(2, 3)
print(tensor1)# torch.ones_like(input) 创建与给定张量形状相同的全1张量
tensor2 = torch.ones_like(tensor1)
print(tensor2)# torch.full(size,fill_value) 创建指定形状的按指定值填充的张量
tensor1 = torch.full((2, 3), 6)
print(tensor1)# torch.empty_like(input) 创建与给定张量形状相同的未初始化的张量
tensor2 = torch.empty_like(tensor3)
print(tensor2)

Ø torch.eye(n, [m])创建单位矩阵

import torch# torch.eye(n) 创建n*n的单位矩阵
tensor1 = torch.eye(3)
print(tensor1)# torch.eye(n, m) 按指定的行和列创建
tensor2 = torch.eye(3, 4)
print(tensor2)

6.3.4 随机张量创建

 torch.rand(size)创建在[0,1)上均匀分布的,指定形状的张量
 torch.randint(low, high, size)创建在[low,high)上均匀分布的,指定形状的张量
 torch.randn(size)创建标准正态分布的,指定形状的张量
 torch.normal(mean,std,size)创建自定义正态分布的,指定形状的张量

Ø torch.rand_like(input)创建在[0,1)上均匀分布的,与给定张量形状相同的张量

Ø torch.randint_like(input, low, high)创建在[low,high)上均匀分布的,与给定张量形状相同的张量

Ø torch.randn_like(input)创建标准正态分布的,与给定张量形状相同的张量

import torch# torch.rand(size) 创建在[0,1)上均匀分布的,指定形状的张量
tensor1 = torch.rand(2, 3)
print(tensor1)# torch.rand_like(input) 创建在[0,1)上均匀分布的,与给定张量形状相同的张量
tensor2 = torch.randint_like(tensor1, 1, 10)
print(tensor2)# torch.randn(size) 创建标准正态分布的,指定形状的张量
tensor1 = torch.randn(4, 2)
print(tensor1)# torch.normal(mean,std,size) 创建自定义正态分布的,指定形状的张量。mean为均值,std为标准差
tensor2 = torch.normal(5, 1, tensor1.shape)
print(tensor2)

Ø torch.randperm(n)生成从0到n-1的随机排列,类似洗牌

import torch# torch.randperm(n) 生成从0到n-1的随机排列
tensor1 = torch.randperm(10)
print(tensor1)

Ø torch.random.initial_seed()查看随机数种子

Ø torch.manual_seed(seed)设置随机数种子

import torch# 查看随机数种子
print(torch.random.initial_seed())
# 设置随机数种子
torch.manual_seed(42)
print(torch.random.initial_seed())

6.4张量转换

6.4.1 张量元素类型转换

1)Tensor.type(dtype)修改张量的类型

import torchtensor1 = torch.tensor([1, 2, 3])
print(tensor1, tensor1.dtype)# 使用type方法修改张量的类型
tensor1 = tensor1.type(torch.float32)
print(tensor1, tensor1.dtype)

2)Tensor.double()等修改张量的类型

import torchtensor1 = torch.tensor([1, 2, 3])
print(tensor1, tensor1.dtype)# 使用double方法修改张量的类型
tensor1 = tensor1.double()
print(tensor1)
# 使用long方法修改张量的类型
tensor1 = tensor1.long()
print(tensor1, tensor1.dtype)

6.4.2 Tensor与ndarray转换

1)Tensor.numpy()将Tensor转换为ndarray,共享内存。使用copy()避免共享内存

import torch# 使用numpy()方法将Tensor转换为ndarray,共享内存
tensor1 = torch.rand(3, 2)
numpy_array = tensor1.numpy()
print(tensor1)
print(numpy_array)
print(type(tensor1), type(numpy_array))
print()
tensor1[:, 0] = 4
print(tensor1)
print(numpy_array)
print()# 使用copy()方法避免共享内存
numpy_array = tensor1.numpy().copy()
tensor1[:, 0] = -1
print(tensor1)
print(numpy_array)

2)torch.from_numpy(ndarray)将ndarray转换为Tensor,共享内存。使用copy()避免共享内存

import torch
import numpy as np# 使用from_numpy()方法将ndarray转换为Tensor,共享内存
numpy_array = np.random.randn(3)
tensor1 = torch.from_numpy(numpy_array)
print(numpy_array)
print(tensor1)
print()
numpy_array[0] = 100
print(numpy_array)
print(tensor1)
print()# 使用copy()方法避免共享内存
tensor1 = torch.from_numpy(numpy_array.copy())
numpy_array[0] = -1
print(numpy_array)
print(tensor1)

3)torch.tensor(ndarray)将ndarray转换为Tensor,不共享内存

import torch
import numpy as np# 使用torch.tensor()将ndarray转换为Tensor
numpy_array = np.random.randn(3)
tensor1 = torch.tensor(numpy_array)
print(numpy_array)
print(tensor1)
print()
numpy_array[0] = 100
print(numpy_array)
print(tensor1)

6.4.3 Tensor与标量转换

若张量中只有1个元素,Tensor.item()可提取张量中元素为标量。

import torchtensor1 = torch.tensor(1)
print(tensor1)
print(tensor1.item())

6.5 张量数值计算

6.5.1 基本运算

image-20260613102702166

import torchtensor1 = torch.randint(1, 9, (2, 3))
print(tensor1)
print(tensor1 + 10)
print()# add(),不修改原数据
print(tensor1.add(10))
print(tensor1)
print()# add_(),修改原数据
print(tensor1.add_(10))
print(tensor1)

2)-、neg()、neg_()取负

import torchtensor1 = torch.tensor([1, 2, 3])
print(-tensor1)
print()print(tensor1.neg())
print(tensor1)
print()print(tensor1.neg_())
print(tensor1)

3)**、pow()、pow_()求幂

import torchtensor1 = torch.tensor([1, 2, 3])
print(tensor1**2)
print()print(tensor1.pow(2))
print(tensor1)
print()print(tensor1.pow_(2))
print(tensor1)

4)sqrt()、sqrt_()求平方根

import torchtensor1 = torch.tensor([1.0, 2.0, 3.0])
print(tensor1.sqrt())
print(tensor1)
print()print(tensor1.sqrt_())
print(tensor1)

5)exp()、exp_()以e为底数求幂

import torchtensor1 = torch.tensor([1.0, 2.0, 3.0])
print(2.71828183**tensor1)
print()print(tensor1.exp())
print(tensor1)
print()print(tensor1.exp_())
print(tensor1)

6)log()、log_()以e为底求对数

import torchtensor1 = torch.tensor([1.0, 2.0, 3.0])
print(tensor1.log())
print(tensor1)
print()print(tensor1.log_())
print(tensor1)

6.5.2 哈达玛积(元素级乘法)

image-20260613103055804

6.5.3 矩阵乘法运算

image-20260613103130239

import torch# 2维矩阵的矩阵乘法
tensor1 = torch.tensor([[1, 2, 3], [4, 5, 6]])
tensor2 = torch.tensor([[1, 2], [3, 4], [5, 6]])
print(tensor1)
print(tensor2)
print(tensor1.mm(tensor2))
print(tensor1 @ tensor2)
print(tensor1.matmul(tensor2))
print()# 3维张量的矩阵乘法
tensor1 = torch.tensor([[[1, 2, 3], [4, 5, 6]], [[6, 5, 4], [3, 2, 1]]])
tensor2 = torch.tensor([[[1, 2], [3, 4], [5, 6]], [[6, 5], [4, 3], [2, 1]]])
print(tensor1)
print(tensor2)
print(tensor1 @ tensor2)
print(tensor1.matmul(tensor2))

6.5.4 节省内存

image-20260613103238546

import torch
X = torch.randint(1, 9, (3, 2, 4))
Y = torch.randint(1, 9, (3, 4, 1))
print(id(X))
X = X @ Y
print(id(X), end="\n\n")

如果后续X不再重复使用,可以使用X[:] = X @ Y来减少内存开销。

import torchX = torch.randint(1, 9, (3, 2, 4))
Y = torch.randint(1, 9, (3, 4, 1))
print(id(X))
X[:] = X @ Y
print(id(X))

6.6 张量运算函数

image-20260613103457915

import torchtensor1 = torch.randint(1, 9, (3, 2, 4))
tensor1 = tensor1.float()
print(tensor1)
print()# sum() 求和
print("求和")
print(tensor1.sum())
print("按第0个维度求和")
print(tensor1.sum(dim=0))
print()# mean() 求均值)
print("求均值")print(tensor1.mean())
print("按第1个维度求均值")
print(tensor1.mean(dim=1))
print()# max() 求最大值
print("求最大值")
print(tensor1.max())
print("按第2个维度求最大值与索引")
print(tensor1.max(dim=2))
print()# argmin() 求最小值索引
print("求最小值索引")
print(tensor1.argmin())
print()# std() 求标准差
print("求标准差")
print(tensor1.std())
print()# unique() 去重
print("去重")
print(tensor1.unique())
print()# sort() 排序
print("排序")
print(tensor1.sort())

image-20260613110722959

image-20260613110832212

image-20260613110920802

6.7 张量索引操作

6.7.1 简单索引

import torchtensor1 = torch.randint(1, 9, (3, 5, 4))
print(tensor1)
print()# 取 第0维第0
print(tensor1[0])
print()
# 取 第0维所有,第1维第1
print(tensor1[:, 1])
print()# 取 第0维所有,第1维第1,第2维第3
print(tensor1[2, 1, 3])

image-20260613111922236

image-20260613112034542

image-20260613112132800

6.7.2 范围索引

import torchtensor1 = torch.randint(1, 9, (3, 5, 4))
print(tensor1)
print()# 取 第0维第1到最后
print(tensor1[1:])
print()# 取 第0维最后,第1维1到3(包含3),第2维0到2(包含2)
print(tensor1[-1:, 1:4, 0:3])
print()

image-20260613112227687

image-20260613112431465

image-20260613112535981

image-20260613112623309

6.7.3 列表索引

import torchtensor1 = torch.randint(1, 9, (3, 5, 4))
print(tensor1)
print()# 取 第0维第0,第1维第1 和 第0维第1,第1维第2
print(tensor1[[0, 1], [1, 2]])
print()# 取 第0维第0,第1维第1、2 和 第0维第1,第1维第1、2
print(tensor1[[[0], [1]], [1, 2]])

image-20260613160612292

image-20260613160727755

image-20260613160852940

image-20260613161045440

6.7.4 布尔索引

import torchtensor1 = torch.randint(1, 9, (3, 5, 4))
print(tensor1)
print()# 取 第2维第0大于5的,返回(dim0,dim1)形状的索引
print(tensor1[:, :, 0] > 5)
print(tensor1[tensor1[:, :, 0] > 5])
print()# 取 第1维第1大于5的,返回(dim0,dim2)形状的索引
mask = tensor1[:, 1, :] > 5
print(mask)
tensor2 = tensor1.permute(0, 2, 1)  # 转换维度为(dim0,dim2,dim1)
print(tensor2[mask])
tensor2 = tensor2[mask].permute(1, 0)  # 转换维度为(dim1,?)
print(tensor2)
print()# 取 第1维第1,第2维第2大于5的,返回(dim0)形状的索引
print(tensor1[:, 1, 2] > 5)
print(tensor1[tensor1[:, 1, 2] > 5])

image-20260613161403163

image-20260613161506650

image-20260613161925159

image-20260613161945590

image-20260613162024515

image-20260613162255691

image-20260613162337757

image-20260613162457227

image-20260613162529600

6.8 张量形状操作

6.8.1 交换维度

1)transpose()交换两个维度

import torchtensor1 = torch.randint(1, 9, (2, 3, 6))
print(tensor1)
print(tensor1.transpose(1, 2))  # 交换第1维和第2维

image-20260613163845688

image-20260613163915828

2)permute()重新排列多个维度

import torchtensor1 = torch.randint(1, 9, (2, 3, 6))
print(tensor1)
print(tensor1.permute(2, 0, 1))  # (2, 3, 6)->(6, 2, 3)

image-20260613164129006

6.8.2 调整形状

1)reshape()调整张量的形状

import torchtensor1 = torch.randint(1, 9, (3, 5, 4))
print(tensor1)
print(tensor1.reshape(6, 10))
print(tensor1.reshape(3, -1))

image-20260613164256983

image-20260613164359941

image-20260613164508345

2)view()调整张量的形状,需要内存连续。共享内存

is_contiguous()判断是否内存连续
contiguous()转换为内存连续

import torchtensor1 = torch.randint(1, 9, (3, 5, 4))
print(tensor1)
print(tensor1.is_contiguous())  # is_contiguous()判断是否内存连续
print(tensor1.view(-1, 10))tensor1 = tensor1.T
print(tensor1.is_contiguous())  # is_contiguous()判断是否内存连续
print(tensor1.contiguous().view(-1))  # contiguous()强制内存连续

image-20260613164628879

image-20260613164941403

image-20260613165005659

6.8.3 增加或删除维度

1)unsqueeze()在指定维度上增加1个维度

import torchtensor1 = torch.tensor([1, 2, 3, 4, 5])
print(tensor1)
# 在0维上增加一个维度
print(tensor1.unsqueeze(dim=0))
# 在1维上增加一个维度
print(tensor1.unsqueeze(dim=1))
# 在-1维上增加一个维度
print(tensor1.unsqueeze(dim=-1))

image-20260613165255299

image-20260613165314198

image-20260613165430474

2)squeeze()删除大小为1的维度

import torchtensor1 = torch.tensor([1, 2, 3, 4, 5])
print(tensor1.unsqueeze_(dim=0))
print(tensor1.squeeze())

6.9 张量拼接操作

1)torch.cat()张量拼接,按已有维度拼接。除拼接维度外,其他维度大小须相同

import torchtensor1 = torch.randint(1, 9, (2, 2, 5))
tensor2 = torch.randint(1, 9, (2, 1, 5))
print(tensor1)
print(tensor2)
print(torch.cat([tensor1, tensor2], dim=1))

2)torch.stack()张量堆叠,按新维度堆叠。所有张量形状必须一致

import torchtorch.manual_seed(42)
tensor1 = torch.randint(1, 9, (3, 1, 5))
tensor2 = torch.randint(1, 9, (3, 1, 5))
print(tensor1)
print(tensor2)
tensor3 = torch.stack([tensor1, tensor2], dim=2)
print(tensor3)
print(tensor3.shape)

6.10 自动微分模块

image-20260613163145287

import torch# 输入x
x = torch.tensor(10.0)
# 目标值y
y = torch.tensor(3.0)# 初始化权重w
w = torch.rand(1, 1, requires_grad=True)
# 初始化偏置b
b = torch.rand(1, 1, requires_grad=True)
z = w * x + b
# 设置损失函数
loss = torch.nn.MSELoss()
loss_value = loss(z, y)
# 反向传播
loss_value.backward()
# 打印w,b的梯度
print("w的梯度:\n", w.grad)
print("b的梯度:\n", b.grad)

该计算图中x、w、b为叶子节点,即最基础的节点。叶子节点的数据并非由计算生成,因此是整个计算图的基石,叶子节点张量不可以执行in-place操作。而最终的loss为根节点。
可通过is_leaf属性查看张量是否为叶子节点:

print(x.is_leaf)  # True
print(w.is_leaf)  # True
print(b.is_leaf)  # True
print(z.is_leaf)  # False
print(y.is_leaf)  # True
print(loss_value.is_leaf)  # False

image-20260613163243555

import torchx = torch.ones(2, 2, requires_grad=True)
y = x * x
# 分离y来返回一个新变量u
u = y.detach()
z = u * x
# 梯度不会向后流经u到x
z.sum().backward()
# 反向传播函数计算z=u*x关于x的偏导数时将u作为常数处理,而不是z=x*x*x关于x的偏导数
x.grad == u
# tensor([[True, True],
#         [True, True]])

使用 tensor 对象 的 deatch 方法 和 和直接操作 tensor 对象 的 data 属性, 都可以更改 变量的值,

使用 deatch 方法,相当与官方方法,

变量的变化被 tensor 监管着,

而直接修改 data 属性 则没有被监管,直接修改 data 会导致 梯度计算错误,并且tensor 对象 发现不了。

image-20260613185856399

image-20260613190023892

image-20260613190053386

image-20260613190132173

image-20260613190149576

image-20260613190223555

image-20260613190430045

6.11 机器学习案例:线性回归

image-20260613190742830

import torch
import matplotlib.pyplot as plt
# nn  神经网络的英文缩写, 人工神经网络(Artificial Neural Network,即ANN )
from torch import nn, optim  # 模型、损失函数和优化器
from torch.utils.data import TensorDataset, DataLoader  # 数据集和数据加载器# 构建数据集
X = torch.randn(100, 1)  # 输入
w = torch.tensor([2.5])  # 权重
b = torch.tensor([5.2])  # 偏置
noise = torch.randn(100, 1) * 0.1  # 噪声
y = w * X + b + noise  # 目标
dataset = TensorDataset(X, y)  # 构造数据集对象
dataloader = DataLoader(dataset, batch_size=10, shuffle=True
)  # 构造数据加载器对象,batch_size为每次训练的样本数,shuffle为是否打乱数据# 构造模型
model = nn.Linear(in_features=1, out_features=1)  # 线性回归模型,1个输入,1个输出# 损失函数和优化器
loss = nn.MSELoss()  # 均方误差损失函数
optimizer = optim.SGD(model.parameters(), lr=1e-3)  # 随机梯度下降,学习率0.001# 模型训练
loss_list = []
for epoch in range(1000):total_loss = 0train_num = 0for x_train, y_train in dataloader:# 每次训练一个batch大小的数据y_pred = model(x_train)  # 模型预测loss_value = loss(y_pred, y_train)  # 计算损失total_loss += loss_value.item()train_num += len(y_train)optimizer.zero_grad()  # 梯度清零loss_value.backward()  # 反向传播optimizer.step()  # 更新参数loss_list.append(total_loss / train_num)print(model.weight, model.bias)  # 打印权重和偏置
plt.plot(loss_list)
plt.xlabel("epoch")
plt.ylabel("loss")
plt.show()

image-20260613190820729

image-20260613193459917

image-20260613193056488

降低 迭代次数,增大噪声,拟合效果变差(调整超参数)

image-20260613193151842

image-20260613193403326

07- 用PyTorch进行深度学习

7.1 激活函数

PyTorch中已经实现了神经网络中可能用到的各种激活函数,我们在代码中只要直接调用即可。

7.1.1 Sigmoid函数

image-20260613193745580

image-20260613193802737

import torch
import matplotlib.pyplot as pltx = torch.linspace(-10, 10, 1000, requires_grad=True)
fig, ax = plt.subplots(1, 2)
fig.set_size_inches(12, 4)ax[0].plot(x.data, torch.sigmoid(x).data, "purple")
ax[0].set_title("sigmoid(x)")
ax[0].spines["top"].set_visible(False)
ax[0].spines["right"].set_visible(False)
ax[0].spines["left"].set_position("zero")
ax[0].spines["bottom"].set_position("zero")
ax[0].axhline(0.5, color="gray", alpha=0.7, linewidth=1)
ax[0].axhline(1, color="gray", alpha=0.7, linewidth=1)torch.sigmoid(x).sum().backward()  # 反向传播计算梯度
ax[1].plot(x.data, x.grad, "purple")
ax[1].set_title("sigmoid'(x)")
ax[1].spines["top"].set_visible(False)
ax[1].spines["right"].set_visible(False)
ax[1].spines["left"].set_position("zero")
ax[1].spines["bottom"].set_position("zero")
ax[1].set_ylim(0, 0.3)plt.show()

7.1.2 Tanh函数

image-20260613212336672

image-20260613212348773

Tanh函数与其导数图像绘制代码:

import torch
import matplotlib.pyplot as pltx = torch.linspace(-5, 5, 1000, requires_grad=True)
fig, ax = plt.subplots(1, 2)
fig.set_size_inches(12, 4)ax[0].plot(x.data, torch.tanh(x).data, "purple")
ax[0].set_title("tanh(x)")
ax[0].spines["top"].set_visible(False)
ax[0].spines["right"].set_visible(False)
ax[0].spines["left"].set_position("zero")
ax[0].spines["bottom"].set_position("zero")
ax[0].axhline(-1, color="gray", alpha=0.7, linewidth=1)
ax[0].axhline(1, color="gray", alpha=0.7, linewidth=1)torch.tanh(x).sum().backward()  # 反向传播计算梯度
ax[1].plot(x.data, x.grad, "purple")
ax[1].set_title("tanh'(x)")
ax[1].spines["top"].set_visible(False)
ax[1].spines["right"].set_visible(False)
ax[1].spines["left"].set_position("zero")
ax[1].spines["bottom"].set_position("zero")plt.show()

7.1.3 ReLU函数

image-20260613212448520

image-20260613212459589

import torch
import matplotlib.pyplot as pltx = torch.linspace(-5, 5, 1000, requires_grad=True)
fig, ax = plt.subplots(1, 2)
fig.set_size_inches(12, 4)ax[0].plot(x.data, torch.relu(x).data, "purple")
ax[0].set_title("relu(x)")
ax[0].spines["top"].set_visible(False)
ax[0].spines["right"].set_visible(False)
ax[0].spines["left"].set_position("zero")
ax[0].spines["bottom"].set_position("zero")torch.relu(x).sum().backward()  # 反向传播计算梯度
ax[1].plot(x.data, x.grad, "purple")
ax[1].set_title("relu'(x)")
ax[1].spines["top"].set_visible(False)
ax[1].spines["right"].set_visible(False)
ax[1].spines["left"].set_position("zero")
ax[1].spines["bottom"].set_position("zero")plt.show()

7.1.4 Softmax函数

image-20260613212534160

image-20260613212607555

image-20260613213440503

image-20260613213556321

7.2 参数初始化和正则化

7.2.1 全连接层(nn.Linear)

在神经网络中,参数主要位于全连接层(仿射层Affine)中。
PyTorch提供了torch.nn模块,专门用于神经网络的构建和训练。其中全连接层被实现为Linear类,内部有两个属性:权重 weight和偏置bias;这就是神经网络的主要参数。

import torch.nn as nnlinear = nn.Linear(5, 2)

上面代码定义了一个有5个输入神经元、2个输出神经元的全连接层。

7.2.2 常数初始化

所有权重参数初始化为一个常数。

import torch.nn as nnlinear = nn.Linear(5, 2)# 全部参数初始化为0
nn.init.zeros_(linear.weight)
print(linear.weight)# 全部参数初始化为1
nn.init.ones_(linear.weight)
print(linear.weight)# 全部参数初始化为一个常数
nn.init.constant_(linear.weight, 10)
print(linear.weight)

注意:将权重初始值设为0将无法正确进行学习。严格地说,不能将权重初始值设成一样的值。因为这意味着反向传播时权重全部都会进行相同的更新,被更新为相同的值(对称的值)。这使得神经网络拥有许多不同的权重的意义丧失了。为了防止“权重均一化”(瓦解权重的对称结构),必须随机生成初始值。

7.2.3 秩初始化

权重参数初始化为单位矩阵。

import torch.nn as nnlinear = nn.Linear(5, 2)# 参数初始化为单位矩阵
nn.init.eye_(linear.weight)
print(linear.weight)

7.2.4 正态分布初始化

权重参数按指定均值与标准差正态分布初始化。

import torch.nn as nnlinear = nn.Linear(5, 2)# 参数初始化为按指定均值与标准差正态分布
nn.init.normal_(linear.weight, mean=0.0, std=1.0)
print(linear.weight)

7.2.5 均匀分布初始化

import torch.nn as nnlinear = nn.Linear(5, 2)# 参数初始化为在区间内均匀分布
nn.init.uniform_(linear.weight, a=0, b=10)
print(linear.weight)

7.2.6 Xavier初始化(Glorot初始化)

image-20260613214141222

import torch.nn as nnlinear = nn.Linear(5, 2)
# Xavier正态分布初始化
nn.init.xavier_normal_(linear.weight)
print(linear.weight)# Xavier均匀分布初始化
nn.init.xavier_uniform_(linear.weight)
print(linear.weight)

7.2.7 He初始化(Kaiming初始化)

image-20260613214215187

import torch.nn as nnlinear = nn.Linear(5, 2)# Kaiming正态分布初始化
nn.init.kaiming_normal_(linear.weight)
print(linear.weight)# Kaiming均匀分布初始化
nn.init.kaiming_uniform_(linear.weight)
print(linear.weight)

7.2.8 Dropout随机失活

image-20260613214255056

import torchdropout = torch.nn.Dropout(p=0.5)
x = torch.randint(1, 10, (10,), dtype=torch.float32)
print("Dropout前:", x)
print("Dropout后:", dropout(x))

如下图, 关闭了一些神经元,剩下的神经元要等比例缩放, 关闭了10 个中的5 个, 神经元的值扩大了二倍

image-20260613215808197

7.3 搭建神经网络

7.3.1 自定义模型

image-20260613220101067

image-20260613220202655

import torch
import torch.nn as nnclass Model(nn.Module):# 初始化def __init__(self):super(Model, self).__init__()  # 调用父类初始化self.linear1 = nn.Linear(3, 4)  # 第1个隐藏层,3个输入,4个输出nn.init.xavier_normal_(self.linear1.weight)  # 初始化权重参数self.linear2 = nn.Linear(4, 4)  # 第2个隐藏层,4个输入,4个输出nn.init.kaiming_normal_(self.linear2.weight)  # 初始化权重参数self.out = nn.Linear(4, 2)  # 输出层,4个输入,2个输出,默认使用He均匀分布初始化# 前向传播def forward(self, x):x = self.linear1(x)  # 经过第1个隐藏层x = torch.tanh(x)  # 激活函数x = self.linear2(x)  # 经过第2个隐藏层x = torch.relu(x)  # 激活函数x = self.out(x)  # 经过输出层x = torch.softmax(x, dim=1)  # 激活函数return xmodel = Model()
output = model(torch.randn(10, 3))
print("输出:\n", output)
print()# 使用named_parameters()查看各层参数
print("模型参数:")
for name, param in model.named_parameters():print(name, param)print()# 使用state_dict()查看各层参数
print("模型参数:\n", model.state_dict())

image-20260613221936944

image-20260613222235744

image-20260613222356984

7.3.2 查看模型结构和参数数量

image-20260613220303893

from torchsummary import summary# input_size:特征数,batch_size:样本数
summary(model, input_size=(3,), batch_size=10, device="cpu")

image-20260613220330416

image-20260613223302655

image-20260613223334244

image-20260613223429488

image-20260613223706059

7.3.3 使用Sequential构建模型

可以通过torch.nn.Sequential来构建模型,将各层按顺序传入。

# 构建模型
model = nn.Sequential(nn.Linear(3, 4),nn.Tanh(),nn.Linear(4, 4),nn.ReLU(),nn.Linear(4, 2),nn.Softmax(dim=1),
)# 初始化参数
def init_weights(m):# 对Linear层进行初始化if type(m) == nn.Linear:nn.init.xavier_uniform_(m.weight)m.bias.data.fill_(0.01)model.apply(init_weights)  # apply会遍历所有子模块并依次调用函数output = model(torch.randn(10, 3))
print("输出:\n", output)

Sequential类使模型构造变得简单,不必自定义类就可以组合新的架构。然而并不是所有的架构都是简单的顺序架构,当需要更强的灵活性时还是需要自定义模型。

7.4 损失函数

7.4.1 分类任务损失函数

image-20260613224649019

import torch
import torch.nn as nn# 真实值
target = torch.tensor([[1], [0], [0]], dtype=torch.float32)
# 预测值
input = torch.randn((3, 1))
prediction = torch.sigmoid(input)
# 实例化损失函数
loss = nn.BCELoss()
print(loss(prediction, target))

2)多分类任务损失函数

image-20260613224735729

image-20260613224750507

import torch
import torch.nn as nn# 真实值为标签
target = torch.tensor([1, 0, 3, 2, 5, 4])  # 真实值
input = torch.randn((6, 8))  # 预测值
loss = nn.CrossEntropyLoss()  # 实例化损失函数
print(loss(input, target))# 真实值为概率
target = torch.randn(6, 8).softmax(dim=1)  # 真实值
input = torch.randn((6, 8))  # 预测值
loss = nn.CrossEntropyLoss()  # 实例化损失函数
print(loss(input, target))

7.4.2 回归任务损失函数

image-20260613224837458

image-20260613224845958

image-20260613224854943

image-20260613224906312

image-20260613231319825

image-20260613231411931

import torch
from torch import nn, optimclass Model(nn.Module):# 初始化def __init__(self):# 调用父类初始化super(Model, self).__init__()# 全连接层self.linear1 = nn.Linear(5, 3)# 初始化权重self.linear1.weight.data = torch.tensor([[0.1, 0.2, 0.3],[0.4, 0.5, 0.6],[0.7, 0.8, 0.9],[0.10, 1.1, 1.2],[1.3, 1.4, 1.5],]).T# 初始化偏置self.linear1.bias.data = torch.tensor([1.0, 2.0, 3.0])# 前向传播def forward(self, x):x = self.linear1(x)return x# 实例化模型
model = Model()
# 输入值
X = torch.tensor([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]], dtype=torch.float)
# 目标值
target = torch.tensor([[0, 0, 0], [0, 0, 0]], dtype=torch.float)
# 计算出输出值
output = model(X)
# 损失函数
loss = nn.MSELoss()
# 反向传播
loss(output, target).backward()
# 优化器
optimizer = optim.SGD(model.parameters(), lr=1)
# 更新参数
optimizer.step()
# 清空梯度
optimizer.zero_grad()
# 打印参数
for i in model.state_dict():print(i)print(model.state_dict()[i])
print()

7.5 参数更新方法

7.5.1 Momentum

image-20260613225023192

import torch
import numpy as np
import matplotlib.pyplot as pltdef gradient_descent(X, optimizer, n_iters):X_arr = X.detach().numpy().copy()  # 拷贝,用于记录优化过程for epoch in range(n_iters):y = X**2 @ wy.backward()  # 反向传播optimizer.step()  # 更新参数optimizer.zero_grad()  # 清空梯度X_arr = np.vstack([X_arr, X.detach().numpy()])  # 记录优化过程return X_arr# 从(-7, 2)出发
X = torch.tensor([-7, 2], dtype=torch.float32, requires_grad=True)
w = torch.tensor([[0.05], [1.0]], requires_grad=True)
lr = 1e-2  # 学习率
n_iters = 500  # 迭代次数# 普通梯度下降
X_clone = X.clone().detach().requires_grad_(True)
X_arr1 = gradient_descent(X_clone, torch.optim.SGD([X_clone], lr=lr), n_iters=n_iters)
plt.plot(X_arr1[:, 0], X_arr1[:, 1], "r")# 动量法
X_clone = X.clone().detach().requires_grad_(True)
X_arr2 = gradient_descent(X_clone, torch.optim.SGD([X_clone], lr=lr, momentum=0.9), n_iters=n_iters)
plt.plot(X_arr2[:, 0], X_arr2[:, 1], "b")# 绘制等高线图
x1_grid, x2_grid = np.meshgrid(np.linspace(-7, 7, 100), np.linspace(-2, 2, 100))
y_grid = w.detach().numpy()[0, 0] * x1_grid**2 + w.detach().numpy()[1, 0] * x2_grid**2
plt.contour(x1_grid, x2_grid, y_grid, levels=30, colors="gray")
plt.legend(["SGD", "Momentum"])
plt.show()

image-20260614101429073

7.5.2 学习率衰减

image-20260613225117747

image-20260613225127940

import torch
import numpy as np
import matplotlib.pyplot as plt# 从(-7, 2)出发
X = torch.tensor([-7, 2], dtype=torch.float32, requires_grad=True)
w = torch.tensor([[0.05], [1.0]], requires_grad=True)
lr = 0.9  # 初始学习率
n_iters = 1000  # 迭代次数optimizer = torch.optim.SGD([X], lr=lr)
scheduler_lr = torch.optim.lr_scheduler.StepLR(optimizer, step_size=20, gamma=0.7)  # 学习率衰减
X_arr = X.detach().numpy().copy()  # 拷贝,用于记录优化过程
lr_list = []  # 记录学习率变化
for epoch in range(n_iters):y = X**2 @ wy.backward()  # 反向传播optimizer.step()  # 更新参数optimizer.zero_grad()  # 清空梯度X_arr = np.vstack([X_arr, X.detach().numpy()])  # 记录优化过程lr_list.append(optimizer.param_groups[0]["lr"])  # 记录学习率变化scheduler_lr.step()  # 学习率衰减plt.rcParams["font.sans-serif"] = ["KaiTi"]
plt.rcParams["axes.unicode_minus"] = False
fig, ax = plt.subplots(1, 2, figsize=(12, 4))
x1_grid, x2_grid = np.meshgrid(np.linspace(-7, 7, 100), np.linspace(-2, 2, 100))
y_grid = w.detach().numpy()[0, 0] * x1_grid**2 + w.detach().numpy()[1, 0] * x2_grid**2
ax[0].contour(x1_grid, x2_grid, y_grid, levels=30, colors="gray")
ax[0].plot(X_arr[:, 0], X_arr[:, 1], "r")
ax[0].set_title("梯度下降过程")ax[1].plot(lr_list, "k")
ax[1].set_title("学习率衰减")
plt.show()

image-20260614102301158

image-20260613225203889

image-20260613225212407

import torch
import numpy as np
import matplotlib.pyplot as plt# 从(-7, 2)出发
X = torch.tensor([-7, 2], dtype=torch.float32, requires_grad=True)
w = torch.tensor([[0.05], [1.0]], requires_grad=True)
lr = 0.9  # 初始学习率
n_iters = 400  # 迭代次数optimizer = torch.optim.SGD([X], lr=lr)
scheduler_lr = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[10, 50, 200], gamma=0.7)  # 学习率衰减
X_arr = X.detach().numpy().copy()  # 拷贝,用于记录优化过程
lr_list = []  # 记录学习率变化
for epoch in range(n_iters):y = X**2 @ wy.backward()  # 反向传播optimizer.step()  # 更新参数optimizer.zero_grad()  # 清空梯度X_arr = np.vstack([X_arr, X.detach().numpy()])  # 记录优化过程lr_list.append(optimizer.param_groups[0]["lr"])  # 记录学习率变化scheduler_lr.step()  # 学习率衰减plt.rcParams["font.sans-serif"] = ["KaiTi"]
plt.rcParams["axes.unicode_minus"] = False
fig, ax = plt.subplots(1, 2, figsize=(12, 4))
x1_grid, x2_grid = np.meshgrid(np.linspace(-7, 7, 100), np.linspace(-2, 2, 100))
y_grid = w.detach().numpy()[0, 0] * x1_grid**2 + w.detach().numpy()[1, 0] * x2_grid**2
ax[0].contour(x1_grid, x2_grid, y_grid, levels=30, colors="gray")
ax[0].plot(X_arr[:, 0], X_arr[:, 1], "r")
ax[0].set_title("梯度下降过程")ax[1].plot(lr_list, "k")
ax[1].set_title("学习率衰减")
plt.show()

image-20260613225245630

image-20260613225254887

import torch
import numpy as np
import matplotlib.pyplot as plt
# 从(-7, 2)出发
X = torch.tensor([-7, 2], dtype=torch.float32, requires_grad=True)
w = torch.tensor([[0.05], [1.0]], requires_grad=True)
lr = 0.9  # 初始学习率
n_iters = 400  # 迭代次数optimizer = torch.optim.SGD([X], lr=lr)
scheduler_lr = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.99)  # 学习率衰减
X_arr = X.detach().numpy().copy()  # 拷贝,用于记录优化过程
lr_list = []  # 记录学习率变化
for epoch in range(n_iters):y = X**2 @ wy.backward()  # 反向传播optimizer.step()  # 更新参数optimizer.zero_grad()  # 清空梯度X_arr = np.vstack([X_arr, X.detach().numpy()])  # 记录优化过程lr_list.append(optimizer.param_groups[0]["lr"])  # 记录学习率变化scheduler_lr.step()  # 学习率衰减plt.rcParams["font.sans-serif"] = ["KaiTi"]
plt.rcParams["axes.unicode_minus"] = False
fig, ax = plt.subplots(1, 2, figsize=(12, 4))
x1_grid, x2_grid = np.meshgrid(np.linspace(-7, 7, 100), np.linspace(-2, 2, 100))
y_grid = w.detach().numpy()[0, 0] * x1_grid**2 + w.detach().numpy()[1, 0] * x2_grid**2
ax[0].contour(x1_grid, x2_grid, y_grid, levels=30, colors="gray")
ax[0].plot(X_arr[:, 0], X_arr[:, 1], "r")
ax[0].set_title("梯度下降过程")ax[1].plot(lr_list, "k")
ax[1].set_title("学习率衰减")
plt.show()

7.5.3 AdaGrad

image-20260613225345902

image-20260613225353657

import torch
import numpy as np
import matplotlib.pyplot as pltdef adagrad(X, lr, n_iters):"""AdaGrad手动实现"""X_arr = X.detach().numpy().copy()  # 拷贝,用于记录优化过程H = torch.zeros_like(X)  # 存放历史梯度信息for epoch in range(n_iters):grad = 2 * X * w.T  # 当前梯度grad.squeeze_()  # 降维H += grad**2  # 平方和X.data -= lr / (torch.sqrt(H) + 1e-8) * grad  # 更新参数,这里H后面加1e-8防止分母为0X_arr = np.vstack([X_arr, X.detach().numpy()])  # 记录优化过程return X_arrdef gradient_descent(X, optimizer, n_iters):X_arr = X.detach().numpy().copy()  # 拷贝,用于记录优化过程for epoch in range(n_iters):y = X**2 @ wy.backward()  # 反向传播optimizer.step()  # 更新参数optimizer.zero_grad()  # 清空梯度X_arr = np.vstack([X_arr, X.detach().numpy()])  # 记录优化过程return X_arr# 从(-7, 2)出发
X = torch.tensor([-7, 2], dtype=torch.float32, requires_grad=True)
w = torch.tensor([[0.05], [1.0]], requires_grad=True)
lr = 0.9  # 学习率
n_iters = 500  # 迭代次数# 普通梯度下降
X_clone = X.clone().detach().requires_grad_(True)
X_arr1 = gradient_descent(X_clone, torch.optim.SGD([X_clone], lr=lr), n_iters=n_iters)
plt.plot(X_arr1[:, 0], X_arr1[:, 1], "r")# AdaGrad
X_clone = X.clone().detach().requires_grad_(True)
X_arr2 = gradient_descent(X_clone, torch.optim.Adagrad([X_clone], lr=lr), n_iters=n_iters)
plt.plot(X_arr2[:, 0], X_arr2[:, 1], "b")# AdaGrad手动实现
X_clone = X.clone().detach().requires_grad_(True)
X_arr1 = adagrad(X_clone, lr=lr, n_iters=n_iters)
plt.plot(X_arr1[:, 0], X_arr1[:, 1], c="orange", linestyle="--", linewidth=3)# 绘制等高线图
x1_grid, x2_grid = np.meshgrid(np.linspace(-7, 7, 100), np.linspace(-2, 2, 100))
y_grid = w.detach().numpy()[0, 0] * x1_grid**2 + w.detach().numpy()[1, 0] * x2_grid**2
plt.contour(x1_grid, x2_grid, y_grid, levels=30, colors="gray")
plt.legend(["SGD", "AdaGrad", "Manual AdaGrad"])
plt.show()

image-20260614103118723

7.5.4 RMSProp

image-20260613225445241

import torch
import numpy as np
import matplotlib.pyplot as pltdef rmspropp(X, lr, alpha, n_iters):"""rmspropp手动实现"""X_arr = X.detach().numpy().copy()  # 拷贝,用于记录优化过程H = torch.zeros_like(X)  # 存放历史梯度信息for epoch in range(n_iters):grad = 2 * X * w.T  # 当前梯度grad.squeeze_()  # 降维H = alpha * H + (1 - alpha) * grad**2  # 历史梯度平方和的指数加权平均X.data -= lr / (torch.sqrt(H) + 1e-8) * grad  # 更新参数,这里H后面加1e-8防止分母为0X_arr = np.vstack([X_arr, X.detach().numpy()])  # 记录优化过程return X_arrdef gradient_descent(X, optimizer, n_iters):X_arr = X.detach().numpy().copy()  # 拷贝,用于记录优化过程for epoch in range(n_iters):y = X**2 @ wy.backward()  # 反向传播optimizer.step()  # 更新参数optimizer.zero_grad()  # 清空梯度X_arr = np.vstack([X_arr, X.detach().numpy()])  # 记录优化过程return X_arr# 从(-7, 2)出发
X = torch.tensor([-7, 2], dtype=torch.float32, requires_grad=True)
w = torch.tensor([[0.05], [1.0]], requires_grad=True)
lr = 1e-1  # 学习率
n_iters = 1000  # 迭代次数# 普通梯度下降
X_clone = X.clone().detach().requires_grad_(True)
X_arr1 = gradient_descent(X_clone, torch.optim.SGD([X_clone], lr=lr), n_iters=n_iters)
plt.plot(X_arr1[:, 0], X_arr1[:, 1], "r")# RMSProp
X_clone = X.clone().detach().requires_grad_(True)
X_arr2 = gradient_descent(X_clone, torch.optim.RMSprop([X_clone], lr=lr, alpha=0.99), n_iters=n_iters)
plt.plot(X_arr2[:, 0], X_arr2[:, 1], "b")# RMSProp手动实现
X_clone = X.clone().detach().requires_grad_(True)
X_arr1 = rmspropp(X_clone, lr=lr, alpha=0.99, n_iters=n_iters)
plt.plot(X_arr1[:, 0], X_arr1[:, 1], c="orange", linestyle="--", linewidth=3)# 绘制等高线图
x1_grid, x2_grid = np.meshgrid(np.linspace(-7, 7, 100), np.linspace(-2, 2, 100))
y_grid = w.detach().numpy()[0, 0] * x1_grid**2 + w.detach().numpy()[1, 0] * x2_grid**2
plt.contour(x1_grid, x2_grid, y_grid, levels=30, colors="gray")
plt.legend(["SGD", "RMSProp", "Manual RMSProp"])
plt.show()

image-20260614103327153

image-20260614103411867

7.5.5 Adam

image-20260613225548461

image-20260613225600350

import torch
import numpy as np
import matplotlib.pyplot as pltdef adam(X, lr, betas, n_iters):"""Adam手动实现"""X_arr = X.detach().numpy().copy()  # 拷贝,用于记录优化过程V = torch.zeros_like(X)  # 存放历史梯度信息H = torch.zeros_like(X)  # 存放历史梯度信息for epoch in range(n_iters):grad = 2 * X * w.T  # 当前梯度grad.squeeze_()  # 降维V = betas[0] * V + (1 - betas[0]) * grad  # 历史梯度的指数加权平均H = betas[1] * H + (1 - betas[1]) * grad**2  # 历史梯度平方和的指数加权平均V_hat = V / (1 - betas[0] ** (epoch + 1))H_hat = H / (1 - betas[1] ** (epoch + 1))X.data -= lr * V_hat / (torch.sqrt(H_hat) + 1e-8)  # 更新参数,这里H后面加1e-8防止分母为0X_arr = np.vstack([X_arr, X.detach().numpy()])  # 记录优化过程return X_arrdef gradient_descent(X, optimizer, n_iters):X_arr = X.detach().numpy().copy()  # 拷贝,用于记录优化过程for epoch in range(n_iters):y = X**2 @ wy.backward()  # 反向传播optimizer.step()  # 更新参数optimizer.zero_grad()  # 清空梯度X_arr = np.vstack([X_arr, X.detach().numpy()])  # 记录优化过程return X_arr# 从(-7, 2)出发
X = torch.tensor([-7, 2], dtype=torch.float32, requires_grad=True)
w = torch.tensor([[0.05], [1.0]], requires_grad=True)
lr = 1e-1  # 学习率
n_iters = 1000  # 迭代次数# 普通梯度下降
X_clone = X.clone().detach().requires_grad_(True)
X_arr1 = gradient_descent(X_clone, torch.optim.SGD([X_clone], lr=lr), n_iters=n_iters)
plt.plot(X_arr1[:, 0], X_arr1[:, 1], "r")# Adam
X_clone = X.clone().detach().requires_grad_(True)
X_arr2 = gradient_descent(X_clone, torch.optim.Adam([X_clone], lr=lr, betas=(0.9, 0.999)), n_iters=n_iters)
plt.plot(X_arr2[:, 0], X_arr2[:, 1], "b")# Adam手动实现
X_clone = X.clone().detach().requires_grad_(True)
X_arr1 = adam(X_clone, lr=lr, betas=(0.9, 0.999), n_iters=n_iters)
plt.plot(X_arr1[:, 0], X_arr1[:, 1], c="orange", linestyle="--", linewidth=3, alpha=0.7)# 绘制等高线图
x1_grid, x2_grid = np.meshgrid(np.linspace(-7, 7, 100), np.linspace(-2, 2, 100))
y_grid = w.detach().numpy()[0, 0] * x1_grid**2 + w.detach().numpy()[1, 0] * x2_grid**2
plt.contour(x1_grid, x2_grid, y_grid, levels=30, colors="gray")plt.legend(["SGD", "Adam", "Manual Adam"])
plt.show()

7.6 应用案例:房价预测

先安装pandas和scikit-learn库:pip install pandas scikit-learn

使用House Prices数据集: https://www.kaggle.com/c/house-prices-advanced-regression-techniques 。

image-202606141041259397.6.1 导入所需的模块

```python
import torch
import pandas as pd
import torch.nn as nn
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline

from sklearn.impute import SimpleImputer
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from torch.utils.data import TensorDataset, DataLoader

```

7.6.2 特征工程

对特征进行处理,数值型特征使用均值填充缺失值,再标准化;类别型特征使用字符串“NaN”填充缺失值,再独特编码。之后构造数据集:

def create_dataset():"""构造数据集"""# 读取数据data = pd.read_csv("data/house_prices.csv")# 去除无关特征data.drop(["Id"], axis=1, inplace=True)# 划分特征和目标X = data.drop("SalePrice", axis=1)y = data["SalePrice"]# 筛选出数值型特征numerical_features = X.select_dtypes(exclude="object").columns# 筛选出类别型特征categorical_features = X.select_dtypes(include="object").columns# 划分训练集和测试集x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)# 特征预处理#   数值型特征先用平均值填充缺失值,再进行标准化numerical_transformer = Pipeline(steps=[("fillna", SimpleImputer(strategy="mean")),("std", StandardScaler()),])#   类别型特征先将缺失值替换为字符串"NaN",再进行独热编码categorical_transformer = Pipeline(steps=[("fillna", SimpleImputer(strategy="constant", fill_value="NaN")),("onehot", OneHotEncoder(handle_unknown="ignore")),])#   组合特征预处理器preprocessor = ColumnTransformer(transformers=[("num", numerical_transformer, numerical_features),("cat", categorical_transformer, categorical_features),])#   进行特征预处理x_train = pd.DataFrame(preprocessor.fit_transform(x_train).toarray(), columns=preprocessor.get_feature_names_out())x_test = pd.DataFrame(preprocessor.transform(x_test).toarray(), columns=preprocessor.get_feature_names_out())# 构建数据集train_dataset = TensorDataset(torch.tensor(x_train.values).float(), torch.tensor(y_train.values).float())test_dataset = TensorDataset(torch.tensor(x_test.values).float(), torch.tensor(y_test.values).float())# 返回训练集,测试集,特征数量return train_dataset, test_dataset, x_train.shape[1]train_dataset, test_dataset, feature_num = create_dataset()

7.6.3 搭建模型

# 搭建模型
model = nn.Sequential(nn.Linear(feature_num, 128),nn.BatchNorm1d(128),nn.ReLU(),nn.Dropout(0.2),nn.Linear(128, 1),
)

7.6.4 损失函数

image-20260614104403784

# 损失函数
def log_rmse(pred, target):mse = nn.MSELoss()pred.squeeze_()pred = torch.clamp(pred, 1, float("inf"))  # 限制输出在1到正无穷之间return torch.sqrt(mse(torch.log(pred), torch.log(target)))

7.6.5 模型训练

# 模型训练
def train(model, train_dataset, test_dataset, lr, epoch_num, batch_size, device):def init_weight(layer):# 对线性层的权重进行初始化if type(layer) == nn.Linear:nn.init.xavier_normal_(layer.weight)model.apply(init_weight)  # 初始化参数model = model.to(device)  # 将模型加载到设备中optimizer = torch.optim.Adam(model.parameters(), lr=lr)  # 优化器train_loss_list = []  # 记录训练损失test_loss_list = []  # 记录验证损失for epoch in range(epoch_num):# 训练过程model.train()  # 将模型设置为训练模式train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)train_loss_accumulate = 0# 训练模型for batch_count, (X, y) in enumerate(train_loader):# 前向传播X, y = X.to(device), y.to(device)output = model(X)# 反向传播loss_value = log_rmse(output, y)optimizer.zero_grad()loss_value.backward()optimizer.step()# 累加损失train_loss_accumulate += loss_value.item()# 打印进度条print(f"\repoch:{epoch:0>3}[{'='*(int((batch_count+1) / len(train_loader)* 50 )):<50}]", end="")this_train_loss = train_loss_accumulate / len(train_loader)  # 计算平均损失train_loss_list.append(this_train_loss)  # 记录训练损失# 验证过程model.eval()  # 将模型设置为评估模式test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=True)test_loss_accumulate = 0with torch.no_grad():  # 关闭梯度计算for X, y in test_loader:# 前向传播X, y = X.to(device), y.to(device)output = model(X)# 累加损失test_loss_accumulate += loss_value.item()this_test_loss = test_loss_accumulate / len(test_loader)  # 计算平均损失test_loss_list.append(this_test_loss)  # 记录验证损失# 打印训练损失,验证损失print(f" train_loss:{this_train_loss:.6f}, test_loss:{this_test_loss:.6f}")return train_loss_list, test_loss_listdevice = torch.device("cuda" if torch.cuda.is_available() else "cpu")  # 如果cude可用则使用cuda,否则使用cpu
train_loss_list, test_loss_list = train(model, train_dataset, test_dataset, 0.1, 200, 64, device)
plt.plot(train_loss_list, "r-", label="train_loss", linewidth=3)  # 绘制训练损失
plt.plot(test_loss_list, "k--", label="test_loss", linewidth=2)  # 绘制验证损失
plt.legend()
plt.show()

image-20260614113014191

08- 卷积神经网络

8.1 CNN概述

卷积神经网络(Convolutional Neural Network,CNN)常被用于图像识别、语音识别等各种场合。它在计算机视觉领域表现尤为出色,广泛应用于图像分类、目标检测、图像分割等任务。

卷积神经网络的灵感来自于动物视觉皮层组织的神经连接方式,单个神经元只对有限区域内的刺激作出反应,不同神经元的感知区域相互重叠从而覆盖整个视野。

CNN中新出现了卷积层(Convolution层)和池化层(Pooling层),下图是一个CNN的结构:

image-20260614114308412

8.2 卷积层

image-20260614114331391

8.2.1 卷积运算

image-20260614121545781

卷积层对数据进行卷积运算,卷积运算相当于图像处理中的滤波器运算。

image-20260614114404568

image-20260614114415911

8.2.2 填充

在进行卷积层的处理之前,有时要向输入数据的周围填入固定的数据(比如0),这称为填充(padding)。例如,对形状为4×4的数据进行幅度为1的填充,即用幅度为1、值为0的数据填充周围:

image-20260614114449213

8.2.3 步幅

image-20260614114519736

image-20260614114541741

image-20260614114552526

8.2.4 3维数据的卷积运算

图像是3维数据,除了长、宽外还需要处理通道方向。在3维数据的卷积运算中,输入数据的通道数和卷积核的通道数须设为相同的值。当有多个通道时,会按通道进行输入数据和卷积核的卷积运算,并将结果相加得到输出数据。

image-20260614114817289

image-20260614114833117

image-20260614114845537

8.2.5 API使用

torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding)
# in_channels:输入通道数
# out_channels:输出通道数
# kernel_size:卷积核大小
# stride:步幅
# padding:填充幅度

例:

import torch
import matplotlib.pyplot as plt# 读取图片
img = plt.imread("data/duck.jpg")
print("图片数据形状:", img.shape)# 将图片数据转换为张量并改变形状
input = torch.tensor(img).permute(2, 0, 1).float()
print("输入特征图形状:", input.shape)
# 初始化卷积核
conv = torch.nn.Conv2d(in_channels=3, out_channels=3, kernel_size=9, stride=3, padding=0, bias=False)# 对输入特征图进行卷积操作
output = conv(input)
print("输出特征图形状:", output.shape)# 将输出特征图转换为图片
output = torch.clamp(output.int(), 0, 255)  # 限制输出在0到255之间
output = output.permute(1, 2, 0).detach().numpy()
fig, ax = plt.subplots(1, 2, figsize=(10, 5))
ax[0].imshow(img)
ax[1].imshow(output)
plt.axis("off")
plt.show()

image-20260614130844996

image-20260614131110246

8.3 池化层

image-20260614115017438

image-20260614115033981

image-20260614115044871

API使用:

# 最大池化
torch.nn.MaxPool2d(kernel_size, stride, padding)
# 平均池化
torch.nn.AvgPool2d(kernel_size, stride, padding)

例:

import torch
import matplotlib.pyplot as plt# 读取图片
img = plt.imread("data/duck.jpg")
print("图片数据形状:", img.shape)# 将图片数据转换为张量并改变形状
input = torch.tensor(img).permute(2, 0, 1).float()
print("输入特征图形状:", input.shape)# 初始化卷积核
conv = torch.nn.Conv2d(in_channels=3, out_channels=3, kernel_size=9, stride=3, padding=0, bias=False)# 对输入特征图进行卷积操作
output1 = conv(input)
print("卷积后输出特征图形状:", output1.shape)# 初始化池化层
pool = torch.nn.MaxPool2d(kernel_size=6, stride=6, padding=1)# 进行池化操作
output2 = pool(output1)
print("池化后输出特征图形状:", output2.shape)# 将输出特征图转换为图片
output1 = torch.clamp(output1.int(), 0, 255)  # 限制输出在0到255之间
output1 = output1.permute(1, 2, 0).detach().numpy()
output2 = torch.clamp(output2.int(), 0, 255)  # 限制输出在0到255之间
output2 = output2.permute(1, 2, 0).detach().numpy()fig, ax = plt.subplots(1, 3, figsize=(15, 5))
ax[0].imshow(img)
ax[1].imshow(output1)
ax[2].imshow(output2)
plt.axis("off")
plt.show()

image-20260614132146194

8.4 深度卷积神经网络

image-20260614132409634

image-20260614132420322

image-20260614132446117

image-20260614132504794

image-20260614132515942

ResNet有效地解决了深度网络中的梯度消失和梯度爆炸问题,在加深层的同时,提高了网络性能。

image-20260614133744086

image-20260614142018509

image-20260614142055941

8.5 服装分类

使用Fashion MNIST数据集: https://www.kaggle.com/datasets/zalando-research/fashionmnist 。

image-20260614142555753

数据集中每个样本都是28×28的灰度图像,与来自10个类别的标签相关联。标签对应如下:

Ø 0:T恤/上衣

Ø 1:裤子

Ø 2:套头衫

Ø 3:连衣裙

Ø 4:外套

Ø 5:凉鞋

Ø 6:衬衫

Ø 7:运动鞋

Ø 8:包

Ø 9:靴子

8.5.1 加载数据

数据在csv文件中,每行数据为1个样本,第1列为标签,2到785列为784个像素。我们需要将其转换为28×28的形状:

import torch
import pandas as pd
import torch.nn as nn
import matplotlib.pyplot as plt
from torch.utils.data import TensorDataset, DataLoader# 读取数据
fashion_mnist_train = pd.read_csv("data/fashion-mnist_train.csv")
fashion_mnist_test = pd.read_csv("data/fashion-mnist_test.csv")
# 将数据转换为张量,原数据形状为n×1×784,转换为n×1×28×28的张量
X_train = torch.tensor(fashion_mnist_train.iloc[:, 1:].values, dtype=torch.float32).reshape(-1, 1, 28, 28)
y_train = torch.tensor(fashion_mnist_train.iloc[:, 0].values, dtype=torch.int64)
X_test = torch.tensor(fashion_mnist_test.iloc[:, 1:].values, dtype=torch.float32).reshape(-1, 1, 28, 28)
y_test = torch.tensor(fashion_mnist_test.iloc[:, 0].values, dtype=torch.int64)
plt.imshow(X_train[12345, 0, :, :], cmap="gray")
plt.show()
# 构建数据集
train_dataset = TensorDataset(X_train, y_train)
test_dataset = TensorDataset(X_test, y_test)

image-20260614143637677

8.5.2 搭建模型

搭建如下结构的模型:

image-20260614142717154

# 搭建模型
model = nn.Sequential(nn.Conv2d(1, 6, kernel_size=5, padding=2),nn.Sigmoid(),nn.AvgPool2d(kernel_size=2, stride=2),nn.Conv2d(6, 16, kernel_size=5),nn.Sigmoid(),nn.AvgPool2d(kernel_size=2, stride=2),nn.Flatten(),  # 拉平nn.Linear(400, 120),nn.Sigmoid(),nn.Linear(120, 84),nn.Sigmoid(),nn.Linear(84, 10),
)

给模型一个输入,测试各层输出值形状是否符合预期:

# 查看各层输出数据的形状
X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32)
for layer in model:X = layer(X)print(f"{layer.__class__.__name__:<12}output shape: {X.shape}")

image-20260614144604834

8.5.3 模型训练

先初始化线性层和卷积层的权重参数。使用交叉熵损失函数和SGD优化方法。每个epoch中在训练集上训练模型,并在验证集上验证模型的准确率。

# 模型训练
def train(model, train_dataset, test_dataset, lr, epoch_num, batch_size, device):def init_weights(layer):# 对线性层和卷积层使用Xavier均匀分布初始化参数if type(layer) == nn.Linear or type(layer) == nn.Conv2d:nn.init.xavier_uniform_(layer.weight)model.apply(init_weights)  # 初始化参数model.to(device)  # 将模型加载到设备loss = nn.CrossEntropyLoss()  # 损失函数optimizer = torch.optim.SGD(model.parameters(), lr=lr)  # 优化器for epoch in range(epoch_num):# 训练过程model.train()  # 将模型设置为训练模式train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)loss_accumulate = 0train_correct_accumulate = 0for batch_count, (X, y) in enumerate(train_loader):# 前向传播X, y = X.to(device), y.to(device)output = model(X)# 反向传播loss_value = loss(output, y)optimizer.zero_grad()loss_value.backward()optimizer.step()# 累加损失loss_accumulate += loss_value.item()# 累加正确输出的数量# 所有的分类中最大的数值的索引_, pred = output.argmax(dim=1)train_correct_accumulate += pred.eq(y).sum()# 打印进度条print(f"\repoch:{epoch:0>2}[{'='*(int((batch_count+1) / len(train_loader) * 50)):<50}]", end="")this_loss = loss_accumulate / len(train_loader)  # 计算平均损失this_train_correct = train_correct_accumulate / len(train_dataset)  # 计算训练准确率# 验证过程model.eval()  # 将模型设置为评估模式test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=True)test_correct_accumulate = 0with torch.no_grad():  # 关闭梯度计算for X, y in test_loader:# 前向传播X, y = X.to(device), y.to(device)output = model(X)# 累加正确输出的数量_, pred = output.argmax(1)test_correct_accumulate += pred.eq(y).sum()this_test_correct = test_correct_accumulate / len(test_dataset)  # 计算验证准确率# 打印损失,训练准确率,验证准确率print(f" loss:{this_loss:.6f}, train_acc:{this_train_correct:.6f}, test_acc:{this_test_correct:.6f}")device = torch.device("cuda" if torch.cuda.is_available() else "cpu")  # 如果cude可用则使用cuda,否则使用cpu
train(model, train_dataset, test_dataset, lr=0.9, epoch_num=20, batch_size=256, device=device)

image-20260614170001937

image-20260614170147845

09- 循环神经网络

9.1 自然语言处理概述

我们平日使用的语言,如汉语或英语,称为自然语言。自然语言处理(Natural Language Processing,NLP)的目标就是让计算机理解人类语言,进而完成对我们有帮助的事情。
说到计算机可以理解的语言,我们可能会想到编程语言或者标记语言等。这些语言的语法定义可以唯一性地解释代码含义,计算机也能根据确定的规则分析代码。编程语言是一种机械的、缺乏活力的语言。换句话说,它是一种“硬语言”。而汉语或英语等自然语言是“软语言”,其含义和形式会灵活变化,并且会不断出现新的词语或新的含义。要让计算机去理解自然语言,使用常规方法是无法办到的。

9.1.1 基于同义词词典的方法

具有相同(同义词)或类似(近义词)含义的单词,可以归到同一个类别中;而根据单词“整体-部分”或者“上位-下位”关系,可以构建出层级的树状图。
这样,就可以构成一个庞大的“单词网络”,用它就可以教会计算机单词之间的关系,从而计算出单词的“相似度”。
主要缺点:
 需要人工逐个定义单词之间的相关性,非常费时费力;
 新词不断出现,语言不断变化,词典维护成本极高;
 在表现力上也有限制。

9.1.2 基于计数的方法

大量的文本数据,构成了 语料库(corpus)。
我们的目的,就是从语料库中,自动且高效地提取出语言的“本质”。最简单的做法,就是统计“词频”。
 分词:对词进行统计,首先需要对文本内容进行切分,找出一个个基本单元;
 词关联ID:给单词标上一个 ID,构建单词和ID的关联字典(称为“词表”);
 词向量化:用一个固定长度的向量来表示单词,也称为词的“分布式表示”。
对每一个词,可以统计它周围出现了什么单词、出现了多少次(称为“上下文”);把这些词频统计出来,就构成了一个向量;这个向量就可以表示当前的词了,称为“词向量”(word vector)。这样,所有词对应的向量,汇总起来就是一个矩阵,被称为 共现矩阵(co-occurrence matrix)。
主要缺点:
 对所有词进行向量化表示的计算复杂度极高。

9.1.3 基于推理的方法

除了基于计数的方法,还可以使用推理的方法把词用向量表示出来。
我们希望在已知上下文的前提下,“推测”当前位置的词是什么。

利用神经网络,接收上下文信息作为输入,通过模型计算,输出各个单词可能得出现概率;从而就可以根据上下文,预测该出现的单词了。

9.2 词嵌入层

9.2.1 什么是词嵌入

自然语言是由文字构成的,而语言的含义是由单词构成的。即单词是含义的最小单位。因此为了让计算机理解自然语言,首先要让它理解单词含义。

词向量是用于表示单词意义的向量,也可以看作词的特征向量。将词映射到向量的技术称为 词嵌入(Word Embedding)。

image-20260614170653355

image-20260614170708279

9.2.2 API使用

可使用torch.nn.Embedding来初始化词嵌入矩阵:

torch.nn.Embedding(num_embeddings, embedding_dim)
# num_embeddings:词的数量
# embedding_dim:词向量的维度

例:
先安装jieba库用于分词:pip install jieba。

import torch
import torch.nn as nn
import jieba# 设置随机种子
torch.manual_seed(42)
text = "自然语言是由文字构成的,而语言的含义是由单词构成的。即单词是含义的最小单位。因此为了让计算机理解自然语言,首先要让它理解单词含义。"
# 自定义停用词和标点符号
stopwords = {"的", "是", "而", "由", ",", "。", "、"}
# 分词,过滤停用词和标点,去重,构建词表
words = [word for word in jieba.lcut(text) if word not in stopwords]
vocab = list(set(words))  # 词表
# 构建词到索引的映射
word2idx = dict()
for idx, word in enumerate(vocab):word2idx[word] = idx
# 初始化嵌入层
embed = nn.Embedding(num_embeddings=len(word2idx), embedding_dim=5)
# 打印词向量
for idx, word in enumerate(vocab):word_vec = embed(torch.tensor(idx))  # 通过索引获取词向量print(f"{idx:>2}:{word:8}\t{word_vec.detach().numpy()}")

image-20260614170810214

9.3 循环网络层

9.3.1 RNN层介绍

文本是连续的,具有序列特性。如果其序列被重排可能就会失去原有的意义。比如“狗咬人”这段文本具有序列关系,如果文字的序列颠倒可能就会表达不同的意思。

image-20260614170901184

image-20260614170916657

9.3.2 API使用

可使用torch.nn.RNN来初始化RNN层:

rnn = torch.nn.RNN(input_size, hidden_size, num_layers)
# input_size:输入数据的特征数量
# hidden_size:隐藏状态的特征数量
# num_layers:隐藏层的层数,如果设置多个层,前一个隐藏层的输出作为下一个隐藏层的输入

调用时需要传入2个参数:

output, hn = rnn(input, hx)
# input:输入数据[seq_len序列长度, batch_size批量大小, input_size]
# hx:初始隐状态[num_layers, batch_size, hidden_size]
# output:输出数据[seq_len, batch_size, hidden_size]
# hn:隐状态[num_layers, batch_size, hidden_size]

例:

import torchrnn = torch.nn.RNN(input_size=8, hidden_size=16, num_layers=2)
input = torch.rand(1, 3, 8)
hx = torch.randn(2, 3, 16)
output, hn = rnn(input=input, hx=hx)
print(output.shape)  # torch.Size([1, 3, 16])
print(hn.shape)  # torch.Size([2, 3, 16])

9.4案例:古诗生成

9.4.1 数据预处理

image-20260614171135415

数据在.txt文件中,每行为一首诗。

首先将每个字作为一个词构建词表。并将原文转换为索引序列。

import re
import torch
from torch import nn, optim
from torch.utils.data import Dataset, DataLoader
# 数据预处理
def process_poems(file_path):poems = []  # 保存处理后的诗char_set = set()  # 保存所有不重复的字with open(file_path, "r", encoding="utf-8") as f:for line in f:# 逐行处理line = re.sub(r"[,。、?!:]", "", line).strip()  # 去掉标点符号与两侧空白# 按字分割并去重char_set.update(list(line))# 按字保存诗poems.append(list(line))# 构建词表vocab = list(char_set) + ["<UNK>"]# 创建词到索引的映射word2idx = {word: idx for idx, word in enumerate(vocab)}# 将诗转换为索引序列sequences = []for poem in poems:seq = [word2idx.get(word) for word in poem]sequences.append(seq)return sequences, word2idx, vocabsequences, word2idx, vocab = process_poems("data/poems.txt")

9.4.2 自定义Dataset

image-20260614171235408

# 自定义Dataset
class PoetryDataset(Dataset):def __init__(self, sequences, seq_len):self.seq_len = seq_lenself.data = []for seq in sequences:for i in range(0, len(seq) - self.seq_len):self.data.append((seq[i : i + self.seq_len], seq[i + 1 : i + 1 + self.seq_len]))def __len__(self):return len(self.data)def __getitem__(self, idx):x = torch.LongTensor(self.data[idx][0])y = torch.LongTensor(self.data[idx][1])return x, ydataset = PoetryDataset(sequences, 24)

9.4.3 搭建模型

词嵌入层→RNN→全连接层

# 搭建模型
class PoetryRNN(nn.Module):def __init__(self, vocab_size, embedding_dim=128, hidden_size=256, num_layers=1):super().__init__()self.embed = nn.Embedding(num_embeddings=vocab_size, embedding_dim=embedding_dim)self.rnn = nn.RNN(input_size=embedding_dim, hidden_size=hidden_size, num_layers=num_layers)self.linear = nn.Linear(in_features=hidden_size, out_features=vocab_size)def forward(self, input, hx=None):embed = self.embed(input)output, hidden = self.rnn(embed, hx)output = self.linear(output)return output, hiddendevice = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = PoetryRNN(len(vocab), 256, 512, 2).to(device)

9.4.4 模型训练

使用交叉熵损失函数,Adam优化方法。

# 模型训练
def train(model, dataset, lr, epoch_num, batch_size, device):model.train()  # 设置为训练模式dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)loss = nn.CrossEntropyLoss()  # 损失函数optimizer = optim.Adam(model.parameters(), lr=lr)  # 优化器for epoch in range(epoch_num):loss_accumulate = 0  # 累加损失for batch_count, (x, y) in enumerate(dataloader):# 前向传播x, y = x.to(device), y.to(device)output, _ = model(x)# 反向传播loss_value = loss(output.transpose(1, 2), y)optimizer.zero_grad()loss_value.backward()optimizer.step()# 累加损失loss_accumulate += loss_value.item()print(f"\repoch:{epoch:0>2}[{'='*(int((batch_count+1) / len(dataloader) * 50)):<50}]", end="")print(f" loss:{loss_accumulate/len(dataloader):.6f}")train(model=model, dataset=dataset, lr=1e-3, epoch_num=20, batch_size=32, device=device)

9.4.5 测试

输入一个起始词让模型开始生成。

# 生成
def generate_poem(model, word2idx, vocab, start_token, line_num=4, line_length=7):model.eval()  # 设置为预测模式poem = []  # 记录生成结果current_line_length = line_length  # 当前句的剩余长度start_token = word2idx.get(start_token, word2idx["<UNK>"])  # 起始token# 如果起始token在词典中,添加到结果中if start_token != word2idx["<UNK>"]:poem.append(vocab[start_token])current_line_length -= 1input = torch.LongTensor([[start_token]]).to(device)  # 输入hidden = None  # 初始化隐状态with torch.no_grad():  # 关闭梯度计算for _ in range(line_num):  # 生成的行数for interpunction in [",", "。\n"]:  # 每行两句while current_line_length > 0:  # 每句诗line_length个字output, hidden = model(input, hidden)prob = torch.softmax(output[0, 0], dim=-1)  # 计算概率next_token = torch.multinomial(prob, 1)  # 从概率分布中随机采样poem.append(vocab[next_token.item()])  # 将采样结果添加到结果中input = next_token.unsqueeze(0)current_line_length -= 1current_line_length = line_lengthpoem.append(interpunction)  # 每句结尾添加标点符号return "".join(poem)  # 将列表转换为字符串print(generate_poem(model, word2idx, vocab, start_token="一", line_num=4, line_length=7))

image-20260614210918847

http://www.zskr.cn/news/1526039.html

相关文章:

  • MPC7450指令时序深度解析:从流水线原理到性能优化实战
  • MPC7450处理器信号接口深度解析:L3缓存、中断复位与时钟配置实战
  • Qt-UI StyleKit 使用说明 - Qt
  • Windows窗口管理终极指南:如何用Traymond彻底释放任务栏空间
  • ok-ww鸣潮自动化框架:基于图像识别的智能游戏操作引擎技术解析
  • Qlib实战指南:从零开始构建AI量化策略的7个关键步骤
  • GHelper:华硕笔记本轻量级控制工具,彻底取代Armoury Crate的终极方案
  • 嵌入式处理器e300核心机制解析:缓存、中断与内存管理实战
  • 如何在Windows 11上玩转经典局域网游戏?IPXWrapper给你答案!
  • 2026权威树洞陪聊|不泄密不存痕,正能量陪你聊到天亮 - 时时资讯
  • 《星源纪》七境心法拆解:修心+成事终极操作手册
  • MPC8260 I2C控制器与并行I/O端口配置详解及实战指南
  • 南阳黄金回收门店推荐:卖金不踩坑,开心把钱拿 - 衡金阁
  • 2026年6月最新版商丘正规房屋漏水防水补漏维修口碑名单:创维修缮机构等5家深度测评 - 一休咨询
  • MTI 对消滤波器:从静止杂波抑制到盲速边界的工程博弈
  • FreeCAD绘图尺寸标注解决方案:工程图纸智能标注的专业架构
  • 德邦物流邮寄200斤费用多少?德邦物流200斤多少钱?这样寄更省钱 - 快递物流资讯
  • 2026年6月最新版汕尾正规房屋漏水防水补漏维修口碑名单:创维修缮机构等5家深度测评 - 一休咨询
  • UniversalUnityDemosaics:Unity游戏视觉还原的终极方案
  • # 20254121 2025-2026-2 《Python程序设计》实验4报告
  • 2026年6月最新版深圳正规房屋漏水防水补漏维修口碑名单:创维修缮机构等5家深度测评 - 一休咨询
  • 国家级全领域硬核卡脖子痛点白皮书(连载目录·第一季+第二季 1–60)
  • 2026年海安车灯维修到店前先看什么?夜间视野、密封和尾灯状态这样问更省时间 - Ayu8888
  • 终极HTML5视频播放速率控制技术:Video Speed Controller深度解析
  • 3个技巧让你轻松玩转跨平台模组下载
  • 2026无锡网站建设哪家口碑好:本土靠谱建站公司甄选与行业避坑攻略 - wxxwlm
  • 2026年6月最新版三明正规房屋漏水防水补漏维修口碑名单:创维修缮机构等5家深度测评 - 一休咨询
  • 2026年6月最新版沈阳正规房屋漏水防水补漏维修口碑名单:创维修缮机构等5家深度测评 - 一休咨询
  • APK-Installer:如何在Windows上轻松安装Android应用的终极指南
  • 2026年6月最新版三门峡正规房屋漏水防水补漏维修口碑名单:创维修缮机构等5家深度测评 - 一休咨询