深度学习应用开发

初识深度学习

人工智能、机器学习和深度学习的关系

  1. 人工智能探索

    1956年被称为人工智能元年。在美国达特茅斯学院,约翰·麦卡锡(JohnMeCarthy,人工智能与认知学专家)、马文·闵斯基(Marvin Minsky,人工智能与认知学专家)、克劳德·香农(Clande Shannon,信息论的创始人)、艾伦·纽厄尔(AlanNewell,计算机科学家)赫伯特·西蒙(HerbentA.Simon,诺贝尔经济学奖得主)等科学家聚在一起,对“人工智能”的名称和任务进行定义和确定。后来,拉塞尔(StuartRussell)和诺文(Peter Norvig)的《人工智能:一种现代方法(第2版)》书中,归纳总结诸多专家对人工智能的定义。

  2. 人工智能的发展历程

    第一阶段(20世纪50年代至80年代):人工智能诞生,基于抽象数学推理的可编程数字计算机已经出现,符号主义快速发展,但由于很多事物不能进行形式化表达,建立的模型存在一定的局限性。随着计算任务的复杂性不断加大,人工智能的发展遇到瓶颈。

    第二阶段(20世纪80年代至90年代末):专家系统得到快速发展,数学型有重大突破。1980年,卡内基梅隆大学为数字设备公司设计的XCON专家系统,每年为公司省下4000万美元。由于专家系统在知识获取、推理能力等方面的不足,以及开发成本高等原因,人工智能的发展又一次进入低谷期。

    第三阶段(21世纪初至今):由于大量数据的积聚、理论算法的革新、计算能力的提升人工智能在很多应用领域取得了突破性进展。计算机视觉、语音识别、自然语义处理等这些主流人工智能新技术正不断被应用在人们的生活中,并改变着这个世界。

    在这里插入图片描述

  3. 人工智能的定义

  4. 定义种类专家定义
    像人一样思考的系统使之自动化与人类思维相关的活动,诸如决策、问题求解、学习等活动
    使计算机能够思考,就是:有头脑的机器
    理性地思考系统通过计算模型的使用进行心智能力的研究
    对知觉、推理和行动成为可能的计算研究
    像人一样行动的系统创造机器来执行人需要智能才能完成的功能
    如何让计算机能够做到,目前人类比计算机做的更好的事情
    理性地行动系统计算智能是对设计智能化智能体的研究
    AI关心的使人工制品中的智能行为

    智能是知识(知识是一切只能行为的基础)是和智力(获取知识并应用知识求解问题的能力)的总和,用人工的方法在机器(计算机)上实现的智能,或者说是人们使机器具有类似于人的智能

  5. 机器学习

    机器学习研究使用机器模拟人类的学习活动。1959年,机器学习之父亚瑟·塞缪尔(Arhur Samuel)设计了一个具有学习能力,可以在不断对弈中提高自己棋艺的下棋程序,展示了机器学习的能力。塞缪尔给出了机器学习(MachineLeamning,ML)的定义:在没有明确编程下,让计算机具有学习能力的研究领域。1997年,IBM开发的深蓝(DeepBlue)象棋程序击败了世界冠军。

    在这里插入图片描述
    在这里插入图片描述

  6. 机器学习的应用

    主要是解决给定数据的预测问题、数据清洗/特征选择、确定算法模型/参数优化、结果预测。

  7. 机器学习分类

    监督学习:监督学习利用已标记的有限训练数据集,通过某种学习策略/方法建立一个模型,实现对新数据/实例的标记(分类)/映射。最典型的监督学习算法包括回归和分类。监督学习要求训练样本的分类标签已知,分类标签精确度就越高,样本越具有代表性,学习模型的准确度就越高。监督学习在自然语言处理、信息检索、文本挖掘、手写体辨识、垃圾邮件检测等领域获得了广泛应用。

    无监督学习:无监督学习利用无标记的有限数据描述隐藏在未标记数据中的结构/规律。最典型的
    非监督学习算法包括单类密度估计、单类数据降维、聚类等。无监督学习不需要训练样本和人工标注数据,便于压缩数据存储、减少计算量、提升算法速度,还可以避免正、负样本偏移引起的分类错误问题。无监督学习主要用于经济预测、异常检测、数据挖掘、图像处理模式识别等领域,例如组织大型计算机集群、社交网络分析、市场分割、天文数据分析等。

    强化学习:强化学习是从环境到行为映射的学习,以使强化信号函数值最大。由于外部环境提供的信息很少,强化学习系统必须靠自身的经历进行学习。强化学习的目标是学习从环境状态到行为的映射,使得智能体选择的行为能够获得环境最大的奖赏,使得外部环境对学习系统在某种意义下的评价为最佳。其在机器人控制、无人驾驶、下棋、工业控制等领域获得成功应用。

  8. 深度学习

    深度学习(Deep Leamming)是机器学习的一个分支,放弃了可解释性,单纯追求学习的有效性。深度学习又称为深度神经网络(层数超过3层的神经网络),其基于大数据和学习算法,通过搭建深层的人工神经网络(ArifcialNeural Network)来学习样本数据的内在规律和表示层次,让机器像人一样具有分析与学习能力,使得神经元网络可以自动提取对学习有意义的数据特征,能够识别文字、图像和声音等数据。

  9. 深度学习标志性事件

    在这里插入图片描述

  10. 机器学习与深度学习的区别

  • 机器学习‌:像教小孩做数学题,先教规则(如“先乘除后加减”),再给少量例题练习‌。
  • 深度学习‌:像让小孩看1000张猫狗照片,自己总结出“尖耳朵是猫,长鼻子是狗”的规律‌。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

比较内容传统机器学习深度学习
隐藏层数目通常小于或等于3个隐藏层通常3个以上的隐藏层
数据点数量可以使用少量数据进行预测需要使用大量的训练数据进行预测
硬件依赖性可以在低端机器上工作
不需要大量的计算能力
以来高端机器
执行大量的矩阵 乘法运算
GPU可以有效优化操作
特征化过程需要用户参与从数据中自动学习特征
执行时间训练所需时间相对较少,从几秒到几个小时不等由于深度学习算法涉及多个层次,通常需要很长的事件进行训练
输出通常是一个数值,如分数或分类可以有多种格式,如文本、乐谱或声音
  1. 机器学习和深度学习的关系

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

神经元和感知器

  1. 生物神经元

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  2. 人工神经元模型

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  3. 感知机

    美国神经学家弗兰克· 罗森勃拉特提出可以模拟人类感知能力的机器,称为“感知器”或“感知机”。

    1957年,罗森勃拉特在Cornell航空实验室工作时,成功在IBM704机上完成了感知机的仿真,并于1960年搭建了能够识别一些英文字母的基于感知机的神经计算机——Mark1。

    感知器网络能够对简单的形状(如三角形、四边形)进行分类,人们逐渐认识到这种方法是使用机器实现类似于人类感觉、学习、记忆、识别的趋势,但感知器网络的特征提取层参数需要人手动调整,违背了“智能”的要求,也约了其发展。

  4. 单层感知机

    单层感知器(Single-Layer Perceptron,即SLP)是最简单的神经网络,它包含直接相连的输入层和输出层,是一个只有单层计算单元的前馈神经网络。单层结构限制了它的学习能力,只能学习线性可分离的模式,很多函数超出了它的学习范畴。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  5. 多层感知机

    多层感知器(Multi Layer Perceptron,即MLP)可以学习数据之间的非线性的关系,相对于单层感知器在输入层和输出层之间出现了一层神经元,被称为隐藏层,且至少要包含一个隐藏层。多层感知器是一种前向结构的人工神经网络,映射一组输入向量到一组输出向量,可以被看作是一个有向图,由多个的节点层所组成,每一层全连接到下一层,除了输入节点,每个节点都是一个带有非线性激活函数的神经元(或称处理单元)。当然隐藏层可以有多层增加模型的表达能力,但也会增加复杂度。输出层的神经元可以有多个输出,如图1-9所示输出层有4个神经元,可以灵活的应用于分类回归及其他的机器学习领域,比如降维和聚类等。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  6. 神经网络

    神经网络包括多个神经网络层,如卷积层、全连接层、LSTM等,每一层又包括很多神经元,超过三层的非线性神经网络可以被称为深度神经网络。通俗的讲,深度学习的模型可以视为输入到输出的映射函数,比如图像到高级语义(美女)的映射,足够深的神经网络理论上可以拟合任何复杂的函数。神经网络非常适合学习样本数据的内在规律和表示层次,对文字、图像和语音任务有很好的适用性,这几个领域的任务是人工智能的基础模块,所以深度学习被称为实现人工智能的基础。

    **神经元:**神经网络中每个节点称为神经元,由两部分组成,一部分是将所有输入加权求和的“加权和”,另一部分是加权和的结果经过一个非线性函数变换,让神经元计算具备非线性的能力的“非线性变换(激活函数)”。

    **多层连接:**大量这样的节点按照不同的层次排布,形成多层的结构连接起来,即称为神经网络。

    前向计算:从输入计算输出的过程,顺序从网络前至后。

    以图形化的方式展现神经网络的计算逻辑又称为计算图。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

神经网络架构

  1. 前馈神经网络

    前馈神经网络(Feed-forward Neural Network,即FNN)是一种最简单的神经网络,是目前应用最广泛、发展最迅速的人工神经网络之一,把神经细胞一层一层地联结在一起,网络的每一层神经细胞的输出都向前输送到了它们的下一层,直到获得整个网络的输出为止。每个神经元只与前一层的神经元相连,接收前一层的输出,并输出给下一层,各层间没有反馈。第0层叫输入层,最后一层叫输出层,其他中间层叫做隐藏层,可以是一层,也可以是多层,是由全连接层堆叠而成。前馈神经网络定义了一个映射y=f(x;θ),并学习了导致最佳函数逼近的参数θ的值。前馈神经网络包括单层感知器、多层感知器、BP网络和自适应线性神经网络等。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  2. 循环神经网络

    ​ 循环神经网络(Recurrent Neural Networks,即RNN)通过原封不动输入时间序列样本就可以使神经网络反映该样本时间信息的结构,和卷积神经网络CNN一样有多种模型,基本形态如图所示。它可以在神经网络内共享不同时间的信息并进行学习,可以应对输入和输出样本大小产生变化的情况。比如,一般的神经网络,输入和输出的样本大小是固定的,但是在文章翻译时,输入中文文章,输出英文文章的长短会发生变化,这种情况就需要循环神经网络RNN。它们对序列的每个元素执行相同的任务,并且输出取决于先前的计算。例如,如果序列是5个单词的句子,则由5层组成,每个单词一层。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  3. 深度神经网络

    在架构方面深度神经网络继承了多层感知器的架构之外,还发展了自身特性,包括提供损失函数的种类变得更多,例如感知机的激活函数是sign(z),而DNN激活函数包括tanh、softmax、sigmoid、relu等。网络的层数可以添加得更深,包含多个隐藏层的多层感知器如图1-10所示,按不同层的位置划分可分为输入层、隐藏层和输出层,一般来说第一层是输入层,最后一层是输出层,而中间的层数都是隐藏层。

    层与层之间是全连接的,第i层的任意一个神经元一定与第i+1层的任意一个神经元相连。虽然看起来复杂,但从小的局部模型来说,还是和感知机一样。由于层数多,线性关系系数w和偏倚b的数量也很多,具体参数定义首先看线性关系系数w的定义,再看偏倚b的定义。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  4. 卷积神经网络

    它的基本形式由卷积层和池化层相互交织构成。

    卷积神经网络中,卷积层的作用是将输入的三维图像转变为二维,并进一步获得保存图像位置信息的样本(获得图像特征),此时进行的操作称为卷积。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  5. 生成式对抗网络

    通过从某些图像样本中学习特征,生成并不存在的图像样本,并可以按照已存图像样本的特征改变原有的图像样本。比如,学习某些人脸生成新的人脸、学习游戏中的角色生成新的游戏角色、从文本生成逼真的图像、将手绘插图变为照片等。

    生成式对抗网络GAN的基本结构,有生成器和判别器两个部分。GAN中的生成器是指将某个值(假定为“Z”)作为输入值并输出图像样本,判别器是指将图像样本作为输入值,并识别样本是真实数据还是由生成器生成的。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

配置PyTorch开发环境

PyTorch概述

PyTorch是一个基于Python的科学计算库,是一个用于构建深度学习模型的开源机器学习框架。PyTorch 提供了一组用于构建、训练和部署深度学习模型的工具和接口,自2017年1月由Facebook人工智能研究院(FAIR)团队在GitHub上(https://www.github.comvpytorch)以开源方式推出以来,因其架构设计符合人类思维、语法简洁、开发高效、运行快速、学习资料丰富、官方答疑及时,特别是入门简单等优点,迅速占领了当时的GiHub热度榜榜首。时至今日,PyTorch已成为热门的深度学习框架之一。

PyTorch组成及应用

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

PyTorch硬件要求(参考)

硬件配置
CPUInter酷睿i7或AMD锐龙9及以上
显卡英伟达RTX系列,8GB显存以上
内存至少16GB,建议32G或64G
硬盘推荐2TB SSD,最低1TB SSD

Anaconda安装

  1. 访问清华大学开源镜像网站:https://mirror.tuna.tsinghua.edu.cn/help/anaconda/,并跳转到最新安装包下载页面(https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  2. 下载最新的安装包,注意时间顺序

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  3. 下载后按照步骤安装,对应步骤截图参考如下,详细可见教材,安装盘注意选择空间较大的磁盘

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  4. 在win11的搜索栏中可以输入【Anaconda Prompt】,快速弹出命令行界面

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

CUDA工具包的下载及安装

一般而言,计算机的中央处理器(CPU)会针对单线程性能进行优化,但不擅长处理计算密集型任务,因此需要借助显卡的图形处理单元(GPU)进行计算。

CUDA是英伟达专为GPU上的通用计算开发的并行计算平台和编程模型。

在经 GPU 加速的应用中,工作负载的串行部分在CPU上运行,而应用的计算密集型部分则以并行方式在数千个GPU核心上运行。

深度神经网络库 (cuDNN) 可以加速PyTorch等深度学习框架,能够以高度优化的方式实现神经网络中的前向和反向卷积、池化层、归一化和激活层等操作。

CUDA11.8和配套cuDnn:https://pan.baidu.com/s/1KJrZ8D3txpoIIQbSvRPL6Q?pwd=ebph

CUDA12.x:https://developer.nvidia.cn/cuda-downloads

​ https://developer.nvidia.cn/zh-cn/cudnn

–解压下载后的cuDNN压缩包,选中其中bin、include和lib三个文件夹,拷贝到cuda安装目录下,一般为:C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\vX.X

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

PyTorch安装与配置

  1. 打开Anaconda,切换国内镜像

    conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
    conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
    conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
    conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/msys2/
    conda config --set show_channel_urls yes
    
  2. 查看配置国内镜像源

    conda config --show channels
    
  3. 删除镜像源

    -- 删除指定镜像
    conda config --remove channels 镜像名称
    -- 删除所有镜像
    conda config --remove-key channels
    
  4. 完成国内镜像配置后,创建虚拟环境

    conda create -n learn-pytorch python=3.9
    
  5. 切换到PyTorch工作环境,激活该环境

    conda activate learn-pytorch
    
  6. 查看已安装的虚拟环境

    conda env list
    
  7. 设置安装工具pip为国内源,用来提高安装速度。通过pip config list指令,可以查看是否设置成功。

    pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
    
  8. 通过如下指令安装PyTorch,这里采用的是CUDA11.8版本。

    pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
    
  9. 安装CPU版本

    conda install pytorch torchvision torchaudio cpuonly -c pytorch
    pip3 install torch torchv\\\\]ision -i https://pypi.tuna.tsinghua.edu.cn/simple
    
  10. 卸载第三方包

    conda remove 包名
    pip uninstall 包名
    
  11. 验证PyTorch是否可以使用,依次输入:

python

import torch
print (torch.__version__)
print (torch.cuda.is_available())

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

PyChram的下载及安装

下载网址为https://www.jetbrains.com/pycharm/download/#section=windows

如果有edu邮箱,可以下载专业版(Professional),否则可以下载社区版(Community)。

PyChram的使用

  1. 打开PyChram,单击New Project按钮,创建新项目,在Location文本框中选择项目路径,项目名改为chp1.选择Previously configured interpreter,单击Add interperter

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  2. 在弹出的“Add Python Interpreter”对话框中,在“Conda executable”中选择之前创建的 Python环境,在已安装的Conda目录下,选择“_conda.exe”可执行文件,(如路径为D:\DevTools\anaconda_conda.exe)。接下来单击【Load Environments按钮,得到已安装的 Pyihon 环境,在“Use existingenvironment”下拉列表框中,选择安装的PyTorch环境即可。配置结束后,单击【0K】按钮,系统回到创建界面,单击【Create】按钮即可完成项目创建。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  3. 创建好的项目主界面如图所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  4. 右击项目chp1,选择New—>Python file,创建python文件,命名为main.py

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  5. 在创建的main.py文件中添加如下代码,单击左上角的绿色运行三角按钮,检查项目是否能按预期执行。

    import torch
    print(torch.__version__)
    print(torch.cuda.is_available())
    

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

手写数字识别功能的部署

  1. MNIST手写数字识别数据库概述

    MNIST 数据集( Mixed National Institute of Standards and Technology database)来源于美国国家标准与技术研究所,是一个用来训练各种图像处理系统的二进制图像数据集,广泛应用于机器学习中的训练和测试。作为一个人门级的计算机视觉数据集,发布20多年来,它已经被无数机器学习入门者学习千万遍,是最受欢迎的深度学习数据集之一
    MNIST数据集中的数字图片是由250个不同职业的人纯手写绘制,数据集官方网址为http://yann.lecun.com/exdb/mnisV,其中60000张图片作为训练集数据,10000张图片作为测试集数飆栈恂Ả絞郑,簹且鮒璆搀一个训练元素都是28x28像素的手写数字图片,每一张图片代表的是0~9中
    的每个数字。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    如果把每一张图片中的像素转换为向量,则得到长度为28x28=784维向量。因此可以把MNIST数据训练集看作是一个[60000,784]的张量,第一个维度表示图片的索引,第二个维度表示每张图片中的像素点,而图片里的每个像素点的值为0255,可以通过归一化方法将像素区间映射到01如图1-25 所示。
    MNIST数据集的类标是0~9的数字,共10个类别,因此需要1x10的数组来标识每个类别,其中类别0表示为[1,0,0,0,0,0,0,0.0,0],类别1表示为[0.1,0,0,0,0,0,0,0,0],类别2表示为[0,0,1,0,0,0,0,0,0,0],依此类推。这种形式被称为独热编码(0ne-HotEncoding),即用N个维度来对N个类别进行编码,并且对于每个类别,只有一个维度有效,记作数字1,其他维度均记作数字0。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    数据集大小[60000,784](60000,28x28)

    标签表示 : one-hot编码

    类别0: [1,0,0,0,0,0,0,0,0,0]

    类别1: [0,1,0,0,0,0,0,0,0,0]

    类别2: [0,0,1,0,0,0,0,0,0,0]

  2. 手写识别开发流程

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  3. 手写数字识别实现

    1. 在chp1项目中,创建utils.py文件

      import torch  # 导入PyTorch深度学习框架
      from matplotlib import pyplot as plt  # 导入Matplotlib的绘图模块
      
      def plot_curve(data, value):
          """
          绘制数据变化曲线图
          
          参数:
              data: 要绘制的数据列表或数组
              value: 曲线标签字符串(如'Loss')
          """
          fig = plt.figure()  # 创建新的绘图窗口
          plt.plot(range(len(data)), data, color='blue')  # 绘制蓝色折线图
          plt.legend([value], loc='upper right')  # 添加图例
          plt.xlabel('step')  # 设置x轴标签
          plt.ylabel(value)  # 设置y轴标签
          plt.show()  # 显示图形
      
      def plot_image(img, label, name):
          """
          以网格形式显示多张图片及其标签
          
          参数:
              img: 图片数据张量(形状为[6,1,28,28])
              label: 图片对应的标签
              name: 图片名称(如'MNIST')
          """
          fig = plt.figure()  # 创建新的绘图窗口
          for i in range(6):  # 循环处理6张图片
              plt.subplot(2, 3, i + 1)  # 设置2行3列的子图位置
              plt.tight_layout()  # 自动调整子图间距
              
              # 显示图片(注意:对MNIST数据需要标准化处理)
              plt.imshow(img[i][0] * 0.3081 + 0.1307, 
                        cmap='gray',  # 使用灰度色图
                        interpolation='none')  # 禁用插值
              
              # 设置标题和隐藏坐标轴
              plt.title("{}: {}".format(name, label[i].item()))
              plt.xticks([])
              plt.yticks([])
          plt.show()
      
      def one_hot(label, depth=10):
          """
          将标签转换为独热编码格式
          
          参数:
              label: 原始标签张量(如[3,5,2])
              depth: 分类类别数(默认为10)
          
          返回:
              独热编码后的张量(形状为[3,10])
          """
          out = torch.zeros(label.size(0), depth)  # 创建全零矩阵
          idx = torch.LongTensor(label).view(-1, 1)  # 转换标签为索引
          out.scatter_(dim=1, index=idx, value=1)  # 在对应位置填充1
          return out
      
      
    2. 创建名为“mnist_recognize.py”的文件,导入torch模块

      import torch
      from torch import nn
      from torch.nn import functional as F
      from torch import optim
      import torchvision
      from utils import plot_image, plot_curve, one_hot
      
      
    3. 在“mnist_recognize.py””文件中添加下载训练数据集代码。

      # step1: 读取训练集和测试集
      batch_size = 512
      train_loader = torch.utils.data.DataLoader(
          torchvision.datasets.MNIST('mnist_data', train=True, download=True,
                                     transform=torchvision.transforms.Compose([
                                         torchvision.transforms.ToTensor(),
                                         torchvision.transforms.Normalize(
                                             (0.1307,), (0.3081,)
                                         )
                                     ])),
          batch_size=batch_size, shuffle=True)
      
    4. 在“mnist_recognize.py”文件中添加下载训练数据集的代码。

      test_loader = torch.utils.data.DataLoader(
          torchvision.datasets.MNIST('mnist_data/', train=False, download=True,
                                     transform=torchvision.transforms.Compose([
                                         torchvision.transforms.ToTensor(),
                                         torchvision.transforms.Normalize(
                                             (0.1307,), (0.3081,)
                                         )
                                     ])),
          batch_size=batch_size, shuffle=False)
      
    5. 实现下载数据的可视化。

      x, y = next(iter(train_loader))
      print(x.shape, y.shape, x.min(), x.max())
      plot_image(x, y, "image_sample")
      
    6. 运行程序,可以看到,PyTorch将自动下载手写识别数据集和训练集,并输出训练集中前6个数据样本。

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    7. 完成数据下载后,定义一个网络结构。

      # step2:定义网络模型
      class Net(nn.Module):
          def __init__(self):
              super(Net, self).__init__()
              self.fc1 = nn.Linear(28 * 28, 256)
              self.fc2 = nn.Linear(256, 64)
              self.fc3 = nn.Linear(64, 10)
      
          def forward(self, x):
              # x: [b, 1, 28, 28]
              x = F.relu(self.fc1(x))
              x = F.relu(self.fc2(x))
              x = self.fc3(x)
              return x
      
      
      net = Net()
      
    8. 添加网络模型数据集训练代码。

      # step3: 网络模型训练
      optimizer = optim.Adam(net.parameters(), lr=0.01)
      train_loss = []
      for epoch in range(3):
          for batch_idx, (x, y) in enumerate(train_loader):
              # 加载进来的图片是一个四维的tensor,x: [b, 1, 28, 28], y:[512]
              # 但是我们网络的输入要是一个一维向量(也就是二维tensor),所以要进行展平操作
              x = x.view(x.size(0), 28 * 28)
              #  [b, 10]
              out = net(x)
              y_onehot = one_hot(y)
              # loss = mse(out, y_onehot)
              loss = F.cross_entropy(out, y_onehot)
              optimizer.zero_grad()
              loss.backward()
              # w' = w - lr*grad
              optimizer.step()
              train_loss.append(loss.item())
              if batch_idx % 10 == 0:
                 print(epoch, batch_idx, loss.item())
      plot_curve(train_loss, "train_loss")
      # we get optimal [w1, b1, w2, b2, w3, b3]
      
    9. 完成网络模型效果的测试

      # step4: 网络模型测试
      total_correct = 0
      for x, y in test_loader:
          x = x.view(x.size(0), 28 * 28)
          out = net(x)
          # out: [b, 10]
          pred = out.argmax(dim=1)
          correct = pred.eq(y).sum().float().item()
          total_correct += correct
      total_num = len(test_loader.dataset)
      acc = total_correct / total_num
      print("acc:", acc)
      x, y = next(iter(test_loader))
      out = net(x.view(x.size(0), 28 * 28))
      pred = out.argmax(dim=1)
      plot_image(x, pred, "test")
      
      torch.save(net, "mnist.pth")
      
    10. 运行结束后,该模型对于测试集准确率为97.1%

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

深度学习基础知识

模型拟合

  1. 深度学习基本原理

    深度学习算法是一种能够从数据中学习的算法。能够解决一些人为设计和使用确定性程序很难解决的问题。深度学习的主要挑战是模型必须在未见的样本上表现良好,而不是只在训练集上表现良好,也就是说需要泛化误差(不可直接获得)越小越好。

    任务分类

    1、分类任务:根据输入特征,将样本划分到预定义的类别中,输出是离散的类别标签,通过计算机程序判别输入数据属于k类中的哪一类。如猫狗分类、数字识别。

    2、回归任务:根据输入特征,预测一个连续的数值,输出是实数,通过计算机程序对输入的特定事件进行预测。如特定时间的房价预测。

    3、异常检测:通过计算机程序检测特定的事件。如信用卡欺诈检测,不合格产品检测。

    4、机器翻译:输入一种语言,通过计算机程序转化为另一种语言,媒体形式可能是声音也可 能是文字。

  2. 设计与运算的感知器

    输出关系:y=𝑓(𝑤1.𝑥1+𝑤2.𝑥2+𝑏)

    选择阶跃函数作为激活函数

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    w1=w2=0.5

    x1=x2=1

    b=-0.8

    此时这个感知器就可以实现与运算

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  3. 模型验证

    1. 留出法

      将数据随机分为两部分,比如选择70%的数据作为训练数据,剩下的30%数据作为测试数 数据。使用训练数据集训练模型使其获得最优的性能,然后在测试数据集上测试模型的性能。如果模型在两个数据集的性能都表现出色,则模型是合适的,否则,模型不合适。

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

      优点:简单,运算速度快

      缺点:造成了**数据资源浪费,**如例子中浪费了30%的数据

      ​ 模型性能严重依赖于训练数据和测试数据的划分

    2. K折交叉验证

      将训练集随机分为k个不相交的子集,将其中的第1个子集作为测试集,而将其余的数据作为训练集进行模型训练。然后,将其中的第2个子集作为测试集,而将其余的数据作为训练集进行模型训练。依次类推到第k个,从而得到k个测试结果,最后将这k个结果取平均来评价模型的性能。这样方法大大增加了模型的可靠性。

      img

  4. 模型误差

    误差(error)一般是指学习器根据模型计算得到的结果与样本真实值之间的差异。

    训练误差(training error)一般是指学习器计算结果与训练集中样本真实值之间的差异。

    测试误差(test error)一般是指学习器计算结果与测试集中样本真实值之间的差异。

    泛化误差(generalization error)一般是指学习器在新的样本上的误差。

曲线拟合需求

对于平面上给定的点(x_i,y_i )(i=0,1,2,⋯,m),可寻找y与x之间的近似函数关系y=φ(x),使得曲线y=φ(x)能近可能地靠近每个点(x_i,y_i ),这就是所谓的曲线拟合问题,亦称为离散函数最佳平方逼近问题

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

欠拟合和过拟合

欠拟合:欠拟合是指对于训练好的模型,数据的拟合程度不高,与拟合曲线相差较大;或指模型没有很好地捕捉到数据特征,不能够很好地拟合数据。欠拟合同样会导致泛化性能下降

过拟合:过于复杂地描述了数据,对当前采样数据表现良好,但是在新的数据点上预测精度下降外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

过拟合与欠拟合的区别

  • 欠拟合与过拟合在本质上都是模型对事物本质规律表达上的偏差。但两者的形成原因不同, 欠拟合主要是由于模型太简单,不能表达复杂关系。而过拟合可能是模型太复杂,也可能是 样本不典型。
  • 欠拟合与过拟合的表现不同,欠拟合在训练集和测试集上的性能都较差,而过拟合往往在训 练集上有好的表现,而在测试集上的性能较差。

欠拟合解决方案

欠拟合解决办法

过拟合和欠拟合都是机器学习算法要考虑的问题,其中欠拟合的情况比较容易克服,常见 解决方法有:

  • 增加新特征,可以考虑加入特征组合、高次特征,来增大表达能力;
  • 添加多项式特征,这个在机器学习算法里面用的很普遍,例如将线性模型通过添加二次项或 者三次项使模型泛化能力更强;
  • 使用非线性模型,比如SVM 、决策树、深度学习等模型。

总之,欠拟合的解决办法通常是提高模型的表达能力。但在深度学习中比较少见,因为,深层网络的表现 能力一般都足够强。

过拟合解决方案

  1. 增加典型数据 ,从数据源头获取更多数据;

  2. 数据增强(Data Augmentation):又名数据扩增,在不影响数据实质的情况下,让有限的数据产生更多的价值;

    ​ Translation :移位;

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    ​ Horizontal/Vertical Flip:水平/垂直翻转;

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    ​ Random Scale:尺度变换;

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    ​ Rotation:旋转变换;

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    ​ Noise:高斯噪声、模糊处理;

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    ​ Random Crop:采用随机图像差值方式,对图像进行裁剪;包括尺度和长宽比 增强变换;

    ​ Color Jittering:对颜色的数据增强:图像亮度、饱和度、对比度变化;

    ​ PCA Jittering:首先按照RGB三个颜色通道计算均值和标准差,再在整个训 练集上计算协方差矩阵, 进行特征分解,得到特征向量和特征值,用来做PCA Jittering;

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  3. Dropout,在训练过程中,每次临时删除模型中的部分节点对其余节点进行训练,不断重复 此过程。

    对于网络的每一层,随机的丢弃一些单元。

    正是因为在每一层随机地丢弃了一些单元,所以相当于训练出来的网络要比正常的网络 小的多,在一定程度避免过拟合的问题。

    如下图所示的一个简单网络,因为每一个节点都有可能被丢弃,所以整个网络不会把某个节点的权重值赋很大,类似于L2正则化,能够起到减轻过拟合的作用。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    工作流程:

    (1)首先随机(临时)删掉网络中一半的隐藏神经元,输入输出神经元保持不变

    (2)然后把输入x通过修改后的网络前向传播,然后把得到的损失结果通过修改的网络反向传 播。一小批训练样本执行完这个过程后,在没有被删除的神经元上按照随机梯度下降法更新 对应的参数(w,b)。

    (3)然后继续重复这一过程(1和2):

    恢复被删掉的神经元(此时被删除的神经元保持原样,而没有被删除的神经元已经有所更新)。

    关于Dropout需要注意的点:

    在测试集上(预测的时候)不要使用Dropout。

  4. 简化模型,减少网络的层数、神经元个数等均可以限制网络的拟合能力。

  5. 正则化,添加惩罚项,对复杂度高的模型进行“惩罚”,限制权值变大。

  6. 限制训练时间(early stop training),设置阈值,限制训练时间。

  7. 数据清洗(data cleaning/Pruning):将错误的label 纠正或者删除错误的数据。

  8. 结合多种模型, 用不同的模型拟合不同部分的训练集。

损失函数

概念:是定义在单个样本上的函数,是指一个样本的误差的映射。 是定义在单个样本上的函数,是指一个样本的误差的映射

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

分类

  1. 0-1损失函数(0-1lossfunction)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 平方损失函数(quadratic loss function)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 绝对值损失函数(absolute lossfunction)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 对数损失函数(logarithmic loss function)

𝐿 y, 𝑃 y|x = −𝑙𝑜𝑔𝑃(y|x)

该损失函数常用于极大似然估计的场合。P(y|x)通俗的解释就是:在当前模型的基础上,对 于样本x,其预测值为y,也就是预测正确的概率𝑃(𝑌|𝑋)。由于概率之间的同时满足需要使用乘 法,为了将其转化为加法,我们将其取对数。由于损失函数,当预测正确的概率越高时,其值 应该是越小,因此,需要对对数值乘以-1。

假设真实结果是“正确”(用1表示),模型预测正确的概率是P。对数损失函数就是 ‌**-log§**‌。这里的log是自然对数(以e为底),但不用纠结数学细节,只需记住:

  • 当P=1(100%确定正确)时,log(1)=0,损失值是-0×1=0(完美,没损失)。
  • 当P=0.5(50%概率正确)时,log(0.5)≈-0.693,损失值是-(-0.693)=0.693(有一定损失)。
  • 当P=0.1(10%概率正确)时,log(0.1)≈-2.303,损失值是-(-2.303)=2.303(损失很大)。

如果真实结果是“错误”(用0表示),模型预测错误的概率是1-P(比如预测下雨概率80%,则错误概率20%),损失函数就是 ‌**-log(1-P)**‌,道理和上面一样:错误概率越高(1-P越大),损失越小。

  1. 指数损失函数(exponential loss)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. Hinge损失函数(Hinge loss)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 感知损失函数(perceptron loss)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

代价函数

概念:是定义在整个训练集上的函数,是所有样本误差的映射, 也就是所有损失函数值的映射。

分类:

  1. L1和L2代价函数

    L1代价函数,也被称为最小绝对值偏差(LAD)或者最小绝对值误差(LAE),该误差是真实值和预测值之间的所有绝对差之和。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    L2代价函数也被称为最小平方误差(LSE),用于最小化平方和误差,该误差是真实值和预测值之间所有平方差的总和

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  2. 均方误差

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  3. 均方根误差

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  4. 平方绝对误差

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  5. 交叉熵代价函数

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    交叉熵是用来评估当前训练得到的概率分布与真实分布的差异情况,减少交叉熵损失就是在提高

    模型的预测准确率。其中 p(x) 是指真实分布的概率,q(x) 是模型通过数据计算出来的概率估计。N 为样本的总数,M为类别的数量。

案例演示(曲线拟合)


# -*- coding: utf-8 -*-
"""
线性回归模型演示代码(带超详细注释版)
功能:通过最小二乘法拟合二维数据的线性回归模型
"""

# 1. 导入必要的库
import numpy as np  # 数值计算库
import matplotlib.pyplot as plt  # 绘图库
import pandas as pd  # 数据处理库

# 2. 数据准备阶段
# 从CSV文件读取数据(假设文件有两列:X和Y)
points = pd.read_csv('data.csv')  # 使用pandas读取数据文件
points = points.values  # 将DataFrame转换为NumPy数组格式
x = points[:, 0]  # 提取第一列作为自变量X(特征)
y = points[:, 1]  # 提取第二列作为因变量Y(目标值)

# 3. 数据可视化(原始数据分布)
plt.scatter(x, y)  # 绘制散点图
plt.title('原始数据分布')  # 图表标题
plt.xlabel('X值')  # X轴标签
plt.ylabel('Y值')  # Y轴标签
plt.show()  # 显示图表

def compute_cost(w, b, points):
    """
    计算线性回归模型的成本函数(均方误差MSE)
    参数:
        w: 斜率参数
        b: 截距参数
        points: 包含所有数据点的二维数组
    返回:
        当前参数下的平均误差值
    """
    total_cost = 0  # 初始化总误差
    M = len(points)  # 获取数据点总数
    
    # 遍历每个数据点计算误差
    for i in range(M):
        x = points[i, 0]  # 当前点的X值
        y = points[i, 1]  # 当前点的Y值
        # 计算预测值与实际值的平方差并累加
        total_cost += (y - w * x - b) ** 2
    
    return total_cost / M  # 返回平均误差

def average(data):
    """
    计算数组的平均值(手动实现版)
    参数:
        data: 输入的一维数组
    返回:
        数组元素的平均值
    """
    sum_val = 0  # 初始化累加器
    num = len(data)  # 获取数组长度
    
    # 遍历数组累加所有元素
    for i in range(num):
        sum_val += data[i]  # 累加当前元素
    
    return sum_val / num  # 返回平均值

def fit(points):
    """
    使用最小二乘法拟合线性回归模型
    参数:
        points: 包含所有数据点的二维数组
    返回:
        w: 最佳拟合斜率
        b: 最佳拟合截距
    """
    M = len(points)  # 数据点总数
    x_bar = average(points[:, 0])  # 计算X的平均值
    
    # 初始化计算变量
    sum_yx = 0  # 用于计算斜率的分子部分
    sum_x2 = 0  # 用于计算斜率的分母部分
    sum_delta = 0  # 用于计算截距
    
    # 计算斜率w需要的各项累加值
    for i in range(M):
        x = points[i, 0]  # 当前点的X值
        y = points[i, 1]  # 当前点的Y值
        sum_yx += y * (x - x_bar)  # 计算协方差部分
        sum_x2 += x ** 2  # 计算X平方和
    
    # 计算最佳拟合斜率(公式推导见最小二乘法)
    w = sum_yx / (sum_x2 - M * (x_bar ** 2))
    
    # 计算最佳拟合截距
    for i in range(M):
        x = points[i, 0]
        y = points[i, 1]
        sum_delta += (y - w * x)  # 计算残差和
    
    b = sum_delta / M  # 截距等于残差的平均值
    
    return w, b  # 返回拟合参数

# 4. 模型训练与评估
w, b = fit(points)  # 调用拟合函数获取参数
print('回归系数w(斜率):', w)  # 打印斜率
print('截距b:', b)  # 打印截距
cost = compute_cost(w, b, points)  # 计算模型误差
print('模型误差(MSE):', cost)  # 打印误差值

# 5. 结果可视化
plt.scatter(x, y)  # 绘制原始数据点

# 绘制回归线
pred_y = w * x + b  # 计算预测值
plt.plot(x, pred_y, c='r', label=f'y={w:.2f}x+{b:.2f}')  # 红色回归线
plt.legend()  # 显示图例
plt.show()  # 显示图表

案例演示(平方损失函数)

#平方损失函数
def computerLoss(fx, y):
    loss = []      #记录每个损失函数输出值
    M= len(fx)
    #逐点计算【实际数据yi与模型数据f(xi)的差值】的平方
    for i in range(M):
        loss_temp = (y[i] - fx[i])**2
        loss.append(loss_temp)
    return loss


fx = [1, 2, 4]    #模拟预测值
y = [0, 1, 2]    #模拟实际结果
print(computerLoss(fx,y))

案例演示(均方误差代价函数)

#均方误差代价函数
def computerCost(fx, y):
    cost = 0.0      #记录结果,结果为标量
    M = len(fx)
    #逐点计算【实际数据yi与模型数据f(xi)的差值】的平方
    for i in range(M):
        cost += (y[i] - fx[i])**2
    return cost/M


fx = [1, 2, 4]    #模拟预测值
y = [0, 1, 2]    #模拟实际结果
print(computerCost(fx,y))

案例演示(L1L2代价函数)

import numpy as np
#定义L1损失函数
def L1_loss(y_true,y_pre):
    return np.sum(np.abs(y_true-y_pre))
#定义L2损失函数
def L2_loss(y_true,y_pre):
    return np.sum(np.square(y_true-y_pre))

y_true = np.array([1,2,3,4,5,6,7,8,9])
y_pre = np.array([1.2,2.3,3.5,4.3,4.6,5.6,6.1,7.1,8.8])

print('L1 loss is {}'.format(L1_loss(y_true,y_pre)))
"""L1 loss is 4.1000000000000005"""
print('L2 loss is {}'.format(L2_loss(y_true,y_pre)))
""""L2 loss is 2.450000000000001"""

案例演示(交叉熵代价函数)


# -*- coding: utf-8 -*-
"""
PyTorch分类损失函数全流程演示
包含:Softmax/LogSoftmax/NLLLoss/CrossEntropyLoss的数学原理和实现细节
"""

import torch
import torch.nn as nn

# ========== 数据准备阶段 ==========
# 模拟神经网络最后一层的输出(未归一化的logits)
# 3个样本,每个样本对应3个类别的原始分数
# 注意:这里的值是从标准正态分布N(0,1)采样的随机数
# 形状:(batch_size, num_classes) -> (3, 3)
x_input = torch.randn(3, 3)
"""
x_input数学含义:
[[z₁₁, z₁₂, z₁₃],  # 样本1的三个类别分数
 [z₂₁, z₂₂, z₂₃],  # 样本2的三个类别分数
 [z₃₁, z₃₂, z₃₃]]  # 样本3的三个类别分数
其中zᵢⱼ表示第i个样本在第j个类别上的得分
"""
print('原始logits:\n', x_input)

# 目标标签(真实类别索引)
# 每个样本对应的正确类别编号(0-based)
y_target = torch.tensor([1, 2, 0])  # 形状:(batch_size,) -> (3,)
"""
y_target数学含义:
[1, 2, 0] 表示:
- 样本1的真实类别是1
- 样本2的真实类别是2 
- 样本3的真实类别是0
"""
print('目标标签:\n', y_target)

# ========== Softmax计算 ==========
# 定义沿dim=1(行方向)的Softmax函数
# 公式:softmax(zᵢⱼ) = exp(zᵢⱼ) / ∑ₖexp(zᵢₖ)
softmax_func = nn.Softmax(dim=1)

# 应用Softmax转换logits为概率分布
soft_output = softmax_func(x_input)
"""
soft_output数学含义:
[[p₁₁, p₁₂, p₁₃],  # 样本1的类别概率分布(∑p₁ₖ=1)
 [p₂₁, p₂₂, p₂₃],  # 样本2的类别概率分布(∑p₂ₖ=1)
 [p₃₁, p₃₂, p₃₃]]  # 样本3的类别概率分布(∑p₃ₖ=1)
其中pᵢⱼ = exp(zᵢⱼ)/∑ₖexp(zᵢₖ)
"""
print('Softmax概率:\n', soft_output)

# ========== 对数概率计算 ==========
# 手动计算对数概率(仅用于演示,实际存在数值不稳定问题)
log_output = torch.log(soft_output)
"""
log_output数学含义:
[[log(p₁₁), log(p₁₂), log(p₁₃)],
 [log(p₂₁), log(p₂₂), log(p₂₃)],
 [log(p₃₁), log(p₃₂), log(p₃₃)]]
"""
print('手动对数概率:\n', log_output)

# PyTorch推荐的LogSoftmax实现(数值稳定版本)
# 公式:log_softmax(zᵢⱼ) = zᵢⱼ - log(∑ₖexp(zᵢₖ))
logsoftmax_func = nn.LogSoftmax(dim=1)
logsoftmax_output = logsoftmax_func(x_input)
print('LogSoftmax输出:\n', logsoftmax_output)

# ========== 负对数似然损失 ==========
# 定义NLLLoss函数
# 公式:L = -1/N ∑ᵢ log(pᵢ,yᵢ) 其中yᵢ是第i个样本的真实类别
nllloss_func = nn.NLLLoss()

# 计算NLLLoss(输入需为对数概率)
nlloss_output = nllloss_func(logsoftmax_output, y_target)
"""
计算过程示例:
对于y_target=[1,2,0],取:
- 样本1的log(p₁₁)
- 样本2的log(p₂₂) 
- 样本3的log(p₃₀)
然后求平均并取负值
"""
print('NLLLoss输出:\n', nlloss_output)

# ========== 交叉熵损失 ==========
# 定义CrossEntropyLoss(组合了Softmax+log+NLLLoss)
# 公式:CE = -1/N ∑ᵢ zᵢ,yᵢ + log(∑ₖexp(zᵢₖ))
crossentropyloss = nn.CrossEntropyLoss()

# 直接计算交叉熵(输入原始logits即可)
crossentropyloss_output = crossentropyloss(x_input, y_target)
print('CrossEntropyLoss输出:\n', crossentropyloss_output)

# ========== 数学等价性验证 ==========
# 验证两种计算方式的数值等价性(允许1e-6的浮点误差)
assert torch.allclose(
    nlloss_output,
    crossentropyloss_output,
    atol=1e-6
), f"计算结果不等价: NLLLoss={nlloss_output}, CE={crossentropyloss_output}"

print("验证通过:NLLLoss与CrossEntropyLoss计算结果等价")

最优化算法

概述:

1.训练⼀个复杂的深度学习模型可能需要数小时、数⽇, 甚⾄数周时间,最优化算法的表现 直接影响模型的训练效率和模型参数的准确程度;

2.理解各种最优化算法的原理以及其中超参数的意义将有助于更有针对性地调参,从而使深 度学习模型表现更好。

3.在⼀个深度学习问题中,我们通常会预先定义⼀个⽬标函数或者代价函数。有了⽬标函数以后,我们就可以使用最优化算法试图将其最小化。

4.任何最大化没有他问题都可以很容易地转化为最小化问题, 只需令⽬标函数的相反数为新的目标函数即可。

5.最优化为深度学习提供了最小化损失函数的方法。本质上,最优化与深度学习的目标是有 区别的。

6.由于最优化算法的⽬标函数通常是⼀个基于训练数据集的损失函数,最优化的⽬标在于降 低训练误差。而深度学习的目标在于降低泛化误差。

7.为了降低泛化误差,除了使用最优化算法降低训练误差以外,还需要注意应对过拟合。

梯度: 梯度是多元函数在某一点变化最快的方向,其方向指向函数值增长最快的路径,大小表示变化率的最大值‌12。例如,站在山坡上时,梯度方向就是最陡峭的上升方向,而梯度的大小对应坡度的陡峭程度‌13。

局部最小值

​ 对于目标函数f(x),如果f(x)在x上的值比在x邻近的其他点的值更小,那么f(x)可能是⼀个 局部最小值。如果f(x)在x上的值是⽬标函数在整个定义域上的最小值,那么 f(x)是全局最小值 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以大致找出该函数的局部最小值和全局最小值的位置。需要注意的是,图中箭头所指示的 只是大致位置。

当⼀个最优化问题的数值解在局部最优解 附近时,目标数有解的度接或变,最终迭代求得的数值解可能只令目标函数局部最小化而非全局最小化。

鞍点

​ 鞍点(saddle point)附近也可能造成梯度接近或变成零

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

img

优化方法

  1. 批量梯度下降

    批量梯度下降算法是梯度下降算法最原始的形式,通过批次所有样本的损失函数,来计算模型参数的更新值。

    梯度下降算法的原理:代价函数 关于参数 的梯度将是目标函数上升最快的方向。而要最小化代价函数,只需要将参数沿着梯度相反的方向前进一个步长,就可以实现目标函数的下降。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  2. 随机梯度下降

    随机梯度下降利用单个样本计算损失函数,直接用反向传播算法更新模型参数。

  3. 小批量梯度下降

    小批量梯度下降,是对批量梯度下降以及随机梯度下降的一个折中办法。

    随机梯度下降算法中模型参数的更新方向不稳定和,我们常采用平均随机梯度下降算法。

    其计算梯度不同的是,不是每得一个梯度就更新一次参数,而是得到几个梯度后,对其求平均,然后确定参数的更新值。

    批量****梯度下降算法随机梯度下降算法小批量****梯度下降算法
    更新方向稳定不稳定相对稳定
    梯度更新所需样本全部样本单个样本小批量(自定义)
    能否并行可以不可以可以

    preview

案例演示(梯度下降)

# 导入数值计算库(用于后续可视化扩展)
import numpy as np
# 导入绘图库(注意原代码应改为import matplotlib.pyplot as plt)
import matplotlib.pyplot as plt

# 训练数据集:输入特征和对应标签
x_data = [1.0, 2.0, 3.0]  # 输入特征值列表
y_data = [3.0, 6.0, 9.0]  # 对应目标值列表(真实值)

def forward(x):
    """前向传播计算预测值
    参数:
        x (float): 输入特征值
    返回:
        float: 预测值 w*x
    数学原理:
        y_pred = w * x  (线性模型)
    """
    return x * w  # 使用全局变量w作为权重参数


def cost(xs, ys):
    """计算均方误差损失(MSE)
    参数:
        xs (list): 特征值列表
        ys (list): 目标值列表
    返回:
        float: 平均损失值
    数学公式:
        MSE = (1/N)*Σ(y_pred-y)^2  (N为样本数)
    """
    total_cost = 0
    sample_count = len(xs)  # 获取样本数量
    for x, y in zip(xs, ys):
        y_pred = forward(x)  # 计算当前样本预测值
        total_cost += (y_pred - y) ** 2  # 累加平方误差
    return total_cost / sample_count  # 返回平均损失


def gradient(xs, ys):
    """计算损失函数关于权重w的梯度
    参数:
        xs (list): 特征值列表
        ys (list): 目标值列表
    返回:
        float: 梯度值
    数学推导:
        ∂J/∂w = (2/N)*Σ(y_pred-y)*x  (N为样本数)
    """
    grad = 0
    sample_count = len(xs)
    for x, y in zip(xs, ys):
        grad += (forward(x) - y) * x  # 累加梯度分量
    return 2 * grad / sample_count  # 返回平均梯度(乘以2来自MSE导数)


# 模型参数初始化
w = 1.0  # 初始化权重参数(将收敛到真实值3.0附近)

# 训练前测试(验证初始参数效果)
print('训练前预测(4):', forward(4))  # 预期输出:4.0(1.0*4)

# 梯度下降训练过程
cost_history = []
w_history = []
for epoch in range(1, 100):  # 迭代100个epoch
    # 计算当前损失值
    current_cost = cost(x_data, y_data)
    cost_history.append(current_cost)

    # 计算当前梯度值
    current_grad = gradient(x_data, y_data)

    # 更新权重参数(学习率0.03)
    w -= 0.03 * current_grad  # 梯度下降公式:w = w - η*∇J
    w_history.append(w)

    # 输出训练日志
    print(f'Epoch={epoch:3d}, w={w:.4f}, cost={current_cost:.6f}')

# 训练后测试(验证模型效果)
print('训练后预测(4):', forward(4))  # 预期输出接近12.0(3.0*4)


# 可视化部分

# 创建一个图形窗口,设置尺寸为15英寸宽、5英寸高
plt.figure(figsize=(15, 5))

# 设置中文字体(解决中文乱码问题)
# 指定使用黑体作为默认字体
plt.rcParams['font.sans-serif'] = ['SimHei']  # 使用黑体
# # 解决负号显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

# 子图1:拟合曲线
# 创建1行3列的子图布局,当前选择第1个子图
plt.subplot(1, 3, 1)
# 生成从0到4的100个等间距点,用于绘制平滑曲线
x_plot = np.linspace(0, 4, 100)
# 根据学习到的权重w计算对应的y值
y_plot = w * x_plot
# 绘制原始数据点的散点图,红色,点大小为80,添加图例标签
plt.scatter(x_data, y_data, color='red', s=80, label='训练数据')
# 绘制拟合直线,蓝色,线宽为2,图例显示权重值(保留2位小数)
plt.plot(x_plot, y_plot, color='blue', linewidth=2, label=f'拟合直线: y = {w:.2f}x')
# 注释掉的真实值参考线(绿色虚线,透明度0.7)
# plt.axhline(y=3.0 * 1, color='green', linestyle='--', alpha=0.7, label='真实值 y=3x')
# 设置x轴标签
plt.xlabel('输入特征 x')
# 设置y轴标签
plt.ylabel('目标值 y')
# 设置子图标题
plt.title('线性回归拟合结果')
# 显示图例
plt.legend()

# 子图2:损失函数变化
# 选择第2个子图
plt.subplot(1, 3, 2)
# 绘制损失函数随训练轮次的变化曲线,橙色,线宽为2
plt.plot(cost_history, color='orange', linewidth=2)
# 设置x轴标签
plt.xlabel('训练轮次')
# 设置y轴标签
plt.ylabel('损失值')
# 设置子图标题
plt.title('损失函数收敛过程')
# 添加网格线,透明度0.3
plt.grid(True, alpha=0.3)

# 子图3:权重变化
# 选择第3个子图
plt.subplot(1, 3, 3)
# 绘制权重参数随训练轮次的变化曲线,紫色,线宽为2
plt.plot(w_history, color='purple', linewidth=2)
# 添加真实权重的参考线,红色虚线,透明度0.7,添加图例标签
plt.axhline(y=3.0, color='red', linestyle='--', alpha=0.7, label='真实权重 w=3.0')
# 设置x轴标签
plt.xlabel('训练轮次')
# 设置y轴标签
plt.ylabel('权重 w')
# 设置子图标题
plt.title('权重参数更新过程')
# 显示图例
plt.legend()

# 自动调整子图间距,使布局更紧凑
plt.tight_layout()
# 显示所有绘制的图形
plt.show()

深度学习框架概述

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. Theano框架

    第一个Python深度学习框架

    深度学习框架基本设计方向:以计算图为框架的核心,采用GPU加速计算

    定义、优化和计算数学表达式,特别是多维数组

    2017年9月28日,LISA实验宣布Theano停止开发

    深度学习新手仍可使用Theano来练习,对于职业开发者建议使用其他主流深度学习框架

  2. TensorFlow框架

    2015年11月10日,Google宣布推出全新的机器学习开源工具TensorFlow

    TensorFlow和Theano都是基于计算图实现自动微分系统

    TensorFlow使用数据流图进行数值计算

    在工业界拥有完备的解决方案和用户基础

    TensorFlow 2无缝部署网络模型至工业系统

    TensorFlow框架是业界使用最为广泛的深度学习框架之一

  3. Keras框架

    Keras是一个高层神经网络API,使用TensorFlow、Theano及CNTK作为后端Keras支持快速实验,能迅速把想法转换为结果。

    Keras的缺点:过度封装导致丧失灵活性,程序运行过于缓慢,许多bug都隐藏于封装之中,用户主要在调用接口。

    严格意义上讲,Keras构建于第三方框架之上,更像一个深度学习接口。

  4. caffe框架

    Caffe是一个清晰、高效的深度学习框架,核心语言是C++,支持命令行、Python和MATLAB接口,可以在CPU上运行,也可以在GPU上运行

    Caffe的优点是简洁快速,缺点是缺少灵活性

    2017年,Facebook正式宣布开源Caffe2,兼具表现力、速度和模块性

    Caffe2追求轻量级,在保有扩展性和高性能的同时,强调便携性

    Caffe2的核心C++库能提供速度和便携性,使Python和C++ API用户可以轻松地在Linux、Windows、iOS、Android等操作系统上进行原型设计、训练和部署。

  5. MXNet框架

    MXNet支持C++、Python、R、Scala、Julia、MATLAB及JavaScript等语言,支持命令和符号编程,可以运行在CPU、GPU、集群、服务器、台式机或者移动设备上

    2017年1月,MXNet项目进入Apache基金会,成为Apache的孵化器项目

    MXNet具有超强的分布式支持能力,卓越的内存、显存优化技术

    MXNet的缺点是接口文档不够完善

  6. CNTK框架

    2016年1月25日,微软公司在GitHub仓库上正式开源了微软研究院开发的计算网络工具集CNTK

    CNTK的性能比Caffe、Theano、TensorFlow等主流工具都要强,支持CPU和GPU模式,同TensorFlow/Theano一样,把神经网络描述成一个计算图的结构

    CNTK表现比较均衡,没有明显短板,且在语音领域效果比较突出

  7. 飞浆框架

    飞桨(PaddlePaddle)集深度学习核心训练和推理框架、基础模型库、端到端开发套件、丰富的工具组件于一体,是中国首个自主研发、功能丰富、开源开放的产业级深度学习平台。

  8. Pytorch框架

    ​ PyTorch是一个基于Torch的开源Python机器学习库,用于自然语言处理等应用程序,更加灵活,支持动态图,提供了Python接口,能够实现强大的GPU加速,支持动态神经网络

pytorch功能

1、提供强大的N维数组操作,包括索引、切片和转置等。

2、通过LuaJIT实现C接口。

3、线性计算和数值优化。

4、生成神经网络以及能量模型,能量模型是生成模型的一种形式。

5、快速高效GPU支持,可嵌入到iOS和Android后端。

pytorch优势

1、简洁。PyTorch的设计追求最少的封装,设计遵循Tensor→Variable(autograd)→nn.Module三个由低到高的抽象层次,且联系紧密,可以同时进行修改和操作。代码易于理解,更少的抽象、更直观的设计。

2、速度。PyTorch的灵活性不以速度为代价,PyTorch的速度表现胜过TensorFlow和Keras等框架,是相当简洁且高效快速的框架。

3、易用。入门简单,设计符合人类思维,让用户尽可能专注于实现自己的想法,所思即所得,不需要考虑太多关于框架本身的束缚。

4、活跃的社区。PyTorch提供完整的文档和循序渐进的指南,Facebook人工智能研究院对PyTorch提供了强力支持。

PyTorch基本语法

  1. 张量的使用

    张量(Tensor)是一种数据结构,一个可以运行在GPU上的多维数组类型,是专门针对GPU设计的,可以运行在GPU上来加快计算效率。Tensor和Numpy数组、向量、矩阵的格式基本一样,但Numpy数组等数据类型的数据只能运行在CPU上。Pytorch中张量的常见属性包括数据、类型、大小、设备及求导相关属性。

    1. 设备分配

      import torch   #导入包
      torch.device('cuda:0')
      

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    Tensor类型

    数据类型CPU tensorGPU tensor
    32-bit floating pointtorch.FloatTensorTorch.cuda.FloatTensor
    64-bit floating pointtorch.DoubleTensorTorch.cuda.DoubleTensor
    16-bit floating pointN/ATorch.cuda.HalfTensor
    8-bit integer(unsigned)torch.ByteTensorTorch.cuda.ByteTensor
    8-bit integer(signed)torch.CharTensorTorch.cuda.CharTensor
    16-bit integer(signed)torch.ShortTensorTorch.cuda.ShortTensor
    32-bit integer(signed)torch.IntTensorTorch.cuda.IntTensor
    64-bit integer(signed)torch.LongTensorTorch.cuda.LongTensor
    import torch
    
    # 标量
    scalar = torch.tensor(1# 向量
    vector = torch.tensor([1,1])
    # 矩阵
    matrix = torch.tensor([[1,2],[3,4]])
    # N维张量
    tensor_n = torch.rand(3,5,2)      #创建一个随机数组成的3*5*2维张量
                    
    print(scalar)
    print(vector)
    print(matrix)
    print(matrix.shape)
    print(tensor_n.shape)
    
  2. Tensor索引操作

    索引位置对应为[维数1,维数2,维数3,…,维数n]。其中:

    • “:”为返回该维度的所有值;

    • “m:n”可以返回该从起始位置m到结束位置n-1的数组元素;

    •首个位置的索引编号为0;

    •负数为从尾部计算索引位置,尾部的首个位置索引编号为“-1”

    # 访问向量中的第一个元素
    print(vector[0])
    # 访问矩阵中的第一行元素
    print(matrix[0, :])
    # 访问矩阵中的第二列元素
    print(matrix[:,1])
    # 访问3维张量中的最后一组矩阵中,除了第一行以外的数据
    print(tensor_n[-1,1:-1,:])
    

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  3. Tensor形状变换
    1. 维度交换

      228×228×3(高度×宽度×通道数)的RGB图片转换为3×228×228 (通道数×高度×宽度)的变换

      # 模拟一张RGB图像
      image = torch.randint(0, 255, (114, 228, 3))
      print(image.shape)
      #  输出为:torch.Size([114, 228, 3]),分别对应图像的高度、宽度和通道数
      # 将H × W × C 转换为 C × H × W
      image = image.permute(2, 1, 0)
      print(image.shape)
      #  输出为:torch.Size([3, 228, 114]),分别对应图像的通道数、宽度和高度
      

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    2. 升降维度

      可以通过unsqueeze() 和 squeeze()函数来实现维度的提升和降低

      # 提升维度
      image = image.unsqueeze(0)
      print(image.shape)
      # 输出为:torch.Size([1, 3, 228, 114])
      #torch.Size([3, 228, 114])
      # 降低维度
      image = image.squeeze(0)
      print(image.shape)
      # 输出为:torch.Size([3, 228, 114])
      

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    3. 改变形状

      可以通过reshape()和view()函数改变张量的维度

      data = torch.randn(4, 4)
      print("数据变形前的形状:{}".format(data.shape))  #torch.Size([4, 4])
      
      data = data.view(2,8)
      print("数据view变形后的形状:{}".format(data.shape)) #torch.Size([2, 8])
      
      data = data.permute(1,0) #会让数据不连续
      print("数据维度转换后的形状:{}".format(data.shape)) #torch.Size([8, 2])
      
      data =data.reshape(4, 4)  #此处不能用view, 因其不能处理内存不连续 Tensor 的结构
      print("数据reshape变形后的形状:{}".format(data.shape)) #torch.Size([4, 4])
      

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  4. PyTorch的数学运算

    PyTorch提供了包括加(add)、减(sub)、乘(mul)、除(div)、矩阵运算、指数、均值和方差等多种计算方法。

    # 标量
    scalar = torch.tensor(1# 向量
    vector = torch.tensor([1,1])
    # 标量与向量相加,广播
    print(vector+scalar)  #输出为tensor([2, 2])
    print(torch.add(vector, scalar))  #输出为tensor([2, 2])
    print(vector.add(scalar))  #输出为tensor([2, 2])
    print(vector)  #输出为tensor([1, 1]),vector本身没有被改变
    print(vector.add_(scalar)) #in_place:当在一个张量tensor上进行操作之后,直接修改了这个tensor本身
    print(vector)  #输出为tensor([2, 2]),带有_的函数操作,会对当前值产生影响
    
    

    矩阵运算

    内项不相等不能进行矩阵乘法

    外相相乘为结果的形状外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    a = torch.full([2, 2], 3, dtype=torch.float)
    
    b = torch.ones(2, 2)
    
    c = a*b #矩阵元素相乘,行列数必须相等
    
    torch.mm(a, b)  # 矩阵相乘,只适用于二维
    
    torch.matmul(a, b)  # 矩阵相乘,适用于任意维
    
    a @ b  # 矩阵相乘
    
    

    tensor.type()

    返回Tensor类型,如 torch.LongTensor等

    tensor.dtype

    返回tensor数据自身的类型,如 torch.int8, torch.long等

    指数运算

    a = torch.full([2, 2], 3)
    
    a.pow(2# 平方
    b = a ** 2  # 平方
    b ** (0.5# 开根号
    
    torch.sqrt(b.to(torch.double))  # 开根号
    torch.rsqrt(b.to(torch.double))  # 开根号之后求倒数
    
    a = torch.exp(torch.ones(2, 2)) # 指数操作
    torch.log(a)  # 默认以e为底
    
    

    均值、方差、标准差运算

    data = torch.Tensor([1, 2, 3])
    
    # 1.求均值
    dat_mean = torch.mean(data)  # => dat_mean = tensor(2.),均值为2
    
    # 2.求方差
    dat_var = torch.var(data)  # => dat_var = tensor(1.),方差为1
    
    # 3. 求标准差
    data_std = torch.std(data) # => data_std = tensor(1.),标准差为1
    
    

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  5. 广播机制

    在进行逐元素操作时,如果两个张量的形状不完全匹配,PyTorch会自动使用广播机制来进行形状的扩展,使得两个张量的形状相容,从而进行逐元素操作

    规则:

    张量维度个数不同:将维度较少的张量通过在前面插入长度为1的维度来扩展,直到两个张量具有相同的维度个数

    张量在某个维度上的长度不匹配:如果其中一个张量在该维度上的长度为1,那么可以通过复制该张量的值来扩展该维度,使得两个张量在该维度上的长度相同

    **报错:**如果以上两个步骤无法使得两个张量的形状匹配,那么会抛出形状不兼容的错误

    # 创建两个形状不同的张量
    a = torch.tensor([[1, 2, 3], [4, 5, 6]])  # 形状为(2, 3)
    b = torch.tensor([10, 20, 30])  # 形状为(3,)
    
    # 使用广播机制进行逐元素相加
    c = a + b  
    # 广播机制会自动将b扩展为(2, 3),使得a和b的形状相同
    print(c)
    # 输出为 tensor([[11, 22, 33],
    #              [14, 25, 36]])
    
    
  6. Tensor和NumPy的转换

    NumPy是机器学习和数据处理的Python基础库,其操作和Tensor类似,很多第三方库,如OpenCV-Python等,基于NumPy实现了数据预处理、数据增强等操作

    import numpy as np
    a = torch.tensor([4.0, 6])
    print(a.dtype) # => torch.float32
    b = a.numpy() # tensor转换为numpy
    print(b.dtype) # => float32
    c = torch.from_numpy(b) # numpy转换为tensor
    print(c.dtype) # => torch.float32
    
  7. 在GPU操作

    深度学习的训练过程非常耗时,一个模型训练几个小时是家常便饭,训练几天也是常有的事情,有时候甚至要训练几十天。训练过程的耗时主要来自于两个部分,一部分来自数据准备,另一部分来自参数迭代。当数据准备过程是模型训练时间的主要瓶颈时,可以使用更多进程来准备数据。当参数迭代过程成为训练时间的主要瓶颈时,通常的方法是应用GPU来进行加速。继续在“tensor-learn.py”中输入GPU运算的代码,并查看运行结果。

    if torch.cuda.is_available():
      device = torch.device("cuda")
      x = torch.randn(2, 3)
      print(x)
    
    y = x.to(device)
    
    z = torch.randn(2, 3, device="cuda")
    
    print(y + z) # 同时在GPU上才能相加
    
    print(z.to("cpu")) # 转换回cpu
    
    

搭建二维曲线数据集


"""
PyTorch曲线数据集教学示例
用于机器学习课堂教学,演示如何创建自定义数据集类
主要功能:生成带噪声的函数数据,用于回归任务训练
"""

import numpy as np
import torch
from torch.utils.data import Dataset
from matplotlib import pyplot as plt


class CurveDataSetX2(Dataset):
    """
    二次函数数据集类 y = x² + 噪声
    继承自PyTorch的Dataset类,用于生成带噪声的二次函数数据
    适用于机器学习回归任务的训练数据准备
    """
    
    def __init__(self, start, end, num=100):
        """
        初始化数据集 - 生成从start到end的均匀分布x值,并计算对应的带噪声y值
        
        参数说明:
            start (float): x的起始值,如-2.0
            end (float): x的结束值,如2.0
            num (int): 数据点数量,默认100个点
            
        功能流程:
            1. 生成等间距的x值
            2. 添加高斯噪声模拟真实数据
            3. 计算y = x² + noise
        """
        # 计算步长:(结束值-起始值)/点数
        step = (end - start) * 1.0 / num
        
        # 生成一维张量:从start到end,步长为step,数据类型为float32
        # unsqueeze(1)将一维张量[100]转换为二维张量[100, 1],符合神经网络输入格式要求
        self.x = torch.arange(start, end, step, dtype=torch.float32).unsqueeze(1)
        
        # 生成高斯噪声:均值为0,标准差为0.1,形状与x相同
        # 噪声模拟现实世界测量中的随机误差,使数据更真实
        noise = torch.normal(0, 0.1, self.x.shape)
        
        # 计算带噪声的y值:y = x² + 噪声
        # pow(2)计算x的平方,然后加上随机噪声
        self.y = self.x.pow(2) + noise

    def __getitem__(self, index):
        """
        获取指定索引的数据点 - 实现Dataset类的必需方法
        
        参数:
            index (int): 数据索引,从0到len(dataset)-1
            
        返回:
            tuple: 包含x值和对应y值的数据对 (xi, yi)
            
        教学意义:
            该方法使得数据集可以像列表一样通过索引访问
            支持DataLoader的批量数据加载功能
        """
        xi = self.x[index]  # 获取指定索引的x值
        yi = self.y[index]  # 获取对应索引的y值
        return xi, yi

    def __len__(self):
        """
        返回数据集大小 - 实现Dataset类的必需方法
        
        返回:
            int: 数据集中样本的总数量
            
        教学意义:
            让DataLoader知道需要加载多少个样本
            支持len(dataset)操作
        """
        return len(self.x)

    @staticmethod
    def x2(x):
        """
        静态方法:计算纯净的二次函数值 y = x²
        用于可视化对比和模型评估
        
        参数:
            x: 输入值,可以是标量、张量或numpy数组
            
        返回:
            x的平方值,无噪声的理想函数值
        """
        return x ** 2


class CurveDataSetSinX(Dataset):
    """
    正弦函数数据集类 y = sin(x) + 噪声
    继承自PyTorch的Dataset类,用于生成带噪声的正弦函数数据
    适用于周期性数据的回归任务训练
    """
    
    def __init__(self, start, end, num=100):
        """
        初始化数据集 - 生成正弦函数数据
        
        参数说明与CurveDataSetX2类似
        """
        # 生成均匀分布的x值
        step = (end - start) * 1.0 / num
        self.x = torch.arange(start, end, step, dtype=torch.float32).unsqueeze(1)
        
        # 生成高斯噪声
        noise = torch.normal(0, 0.1, self.x.shape)
        
        # 计算带噪声的y值:y = sin(x) + 噪声
        self.y = torch.sin(self.x) + noise

    def __getitem__(self, index):
        """
        获取指定索引的数据点
        实现方式与CurveDataSetX2相同
        """
        xi = self.x[index]
        yi = self.y[index]
        return xi, yi

    def __len__(self):
        """
        返回数据集大小
        """
        return len(self.x)

    @staticmethod
    def sin(x):
        """
        静态方法:计算纯净的正弦函数值 y = sin(x)
        
        注意: 这里使用numpy的sin函数,因为x可能是numpy数组
        用于数据可视化时计算真实函数曲线
        """
        return np.sin(x)


def visualizing_data(func, x, y_predict, epoch=0, save=False, savePrefix=''):
    """
    数据可视化函数:对比真实函数曲线与带噪声的预测数据
    用于教学演示和模型效果评估
    
    参数详解:
        func (function): 真实函数引用,如CurveDataSetX2.x2或CurveDataSetSinX.sin
        x (array): x坐标值数组,通常是numpy数组
        y_predict (array): 带噪声的y预测值数组
        epoch (int): 训练轮次,用于图表标题显示训练进度
        save (bool): 是否将图表保存为SVG矢量图文件
        savePrefix (str): 保存文件名的前缀,用于区分不同数据集
    """
    # 计算真实函数值(无噪声的理想曲线)
    y = func(x)
    
    # 创建图表对象,设置图表尺寸
    plt.figure(figsize=(10, 6))
    plt.title('曲线对比 (训练轮次={})'.format(epoch), fontsize=14)
    
    # 绘制真实函数曲线:红色实线,线宽2.0
    # 代表理论上的完美函数关系
    plt.plot(x, y, color='red', linewidth=2.0, linestyle='-', 
             label='真实函数')
    
    # 绘制带噪声的预测数据:蓝色虚线,线宽1.5,透明度0.7
    # 代表实际观测到的带噪声数据
    plt.plot(x, y_predict, color='blue', linewidth=1.5, linestyle='--',
             label='带噪声数据', alpha=0.7)
    
    # 添加图例,位置在右上角
    plt.legend(loc='upper right', fontsize=12)
    
    # 添加坐标轴标签
    plt.xlabel('x', fontsize=12)
    plt.ylabel('y', fontsize=12)
    
    # 添加网格线,便于观察数据点位置
    plt.grid(True, alpha=0.3)
    
    # 如果需要保存图片
    if save:
        # 生成文件名:前缀-epoch-轮次.svg
        filename = '{0}-epoch-{1}.svg'.format(savePrefix, epoch)
        plt.savefig(filename, format='svg', dpi=300, bbox_inches='tight')
        print(f'图片已保存为: {filename}')
    
    # 显示图表
    plt.show()


def demonstrate_dataset_usage():
    """
    演示数据集的使用方法
    这个函数展示了如何创建、访问和使用数据集
    用于课堂教学的实际操作演示
    """
    print("=" * 50)
    print("PyTorch数据集使用演示")
    print("=" * 50)
    
    # 演示1:二次函数数据集
    print("\n1. 二次函数数据集演示 (y = x² + 噪声)")
    print("-" * 40)
    
    # 创建二次函数数据集:从-2到2,100个数据点
    data_x2 = CurveDataSetX2(-2, 2, 100)
    
    # 打印数据集基本信息
    print(f'数据集大小: {len(data_x2)}')
    print(f'x张量形状: {data_x2.x.shape}')  # 应为[100, 1]
    print(f'y张量形状: {data_x2.y.shape}')  # 应为[100, 1]
    
    # 访问第一个和最后一个数据点
    x_first, y_first = data_x2[0]
    x_last, y_last = data_x2[len(data_x2)-1]
    
    print(f'第一个数据点: x={x_first.item():.4f}, y={y_first.item():.4f}')
    print(f'最后一个数据点: x={x_last.item():.4f}, y={y_last.item():.4f}')


if __name__ == '__main__':
    """
    主函数:程序执行入口
    演示整个数据集创建、访问和可视化的完整流程
    """
    # 演示数据集使用方法
    demonstrate_dataset_usage()
    
    print("\n3. 数据可视化演示")
    print("-" * 40)
    
    # 可视化二次函数数据
    print("正在可视化二次函数数据...")
    data_x2 = CurveDataSetX2(-2, 2, 100)
    visualizing_data(
        CurveDataSetX2.x2, 
        data_x2.x.squeeze(1).numpy(), 
        data_x2.y.squeeze(1).numpy(), 
        epoch=0, 
        save=True, 
        savePrefix='x2'
    )
    
    # 可视化正弦函数数据
    print("正在可视化正弦函数数据...")
    data_sin = CurveDataSetSinX(-4, 4, 100)
    visualizing_data(
        CurveDataSetSinX.sin, 
        data_sin.x.squeeze(1).numpy(), 
        data_sin.y.squeeze(1).numpy(), 
        epoch=0, 
        save=True, 
        savePrefix='sinx'
    )
    
    print("\n演示完成!")
    print("这个示例展示了:")
    print("✓ 如何创建自定义PyTorch数据集")
    print("✓ 如何生成带噪声的函数数据")
    print("✓ 如何实现数据集的标准接口")
    print("✓ 如何进行数据可视化对比")

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

简单神经网络模型

"""
PyTorch神经网络训练程序
手动实现前向传播和反向传播,包含实时可视化
作者: Hiway
日期: 2025/11/17 9:52
"""

import torch
import matplotlib.pyplot as plt

# ==================== 配置参数部分 ====================
# 设置数据类型为32位浮点数,这是深度学习训练的标准选择
dtype = torch.float

# 定义网络参数:
# N: 批量大小,表示每次训练使用的样本数量
# D_in: 输入维度,每个输入样本的特征数量
# H: 隐藏层维度,隐藏层神经元的数量
# D_out: 输出维度,网络预测输出的维度
N, D_in, H, D_out = 64, 1000, 100, 10

# ==================== 数据准备部分 ====================
# 生成随机训练数据
# x: 输入数据,形状为[64, 1000],包含64个样本,每个样本1000个特征
x = torch.randn(N, D_in, dtype=dtype)
# y: 目标输出,形状为[64, 10],表示期望的网络输出
y = torch.randn(N, D_out, dtype=dtype)

# ==================== 模型参数初始化 ====================
# 初始化网络权重参数
# w1: 第一层权重矩阵,连接输入层到隐藏层,形状为[1000, 100]
# requires_grad=True 表示这些张量需要计算梯度,用于反向传播
w1 = torch.randn(D_in, H, dtype=dtype, requires_grad=True)
# w2: 第二层权重矩阵,连接隐藏层到输出层,形状为[100, 10]
w2 = torch.randn(H, D_out, dtype=dtype, requires_grad=True)

# ==================== 训练超参数设置 ====================
# 设置学习率,控制每次参数更新的步长
learning_rate = 1e-6

# ==================== 可视化设置 ====================
# 配置matplotlib支持中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']  # 使用黑体显示中文
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示为方块的问题

# 开启交互模式,用于实时更新图表
plt.ion()
# 创建图形窗口,包含两个子图
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))   #图形尺寸
# 创建列表用于记录每次迭代的损失值
loss_history = []

print("开始神经网络训练...")

# ==================== 训练循环 ====================
# 进行500次训练迭代
for t in range(500):
    # ========== 前向传播过程 ==========
    # 计算输入数据与第一层权重的矩阵乘法,得到隐藏层输入
    h = x.mm(w1)
    # 应用ReLU激活函数:将负值置为0,正值保持不变
    h_relu = h.clamp(min=0)
    # 计算隐藏层输出与第二层权重的矩阵乘法,得到最终预测
    y_pred = h_relu.mm(w2)

    # ========== 损失计算 ==========
    # 计算损失函数:使用均方误差(MSE)
    # 先计算预测值与真实值的差值平方,然后对所有元素求和
    loss = (y_pred - y).pow(2).sum()

    # 记录当前迭代的损失值
    loss_history.append(loss.item())

    # 每50次迭代打印训练进度
    if t % 50 == 0:
        print(t, loss.item())

    # ========== 手动反向传播 ==========
    # 计算损失函数对预测输出的梯度
    # 根据链式法则,loss = sum((y_pred-y)^2) 对y_pred的导数为 2*(y_pred-y)
    grad_y_pred = 2.0 * (y_pred - y)
    # 计算损失函数对w2的梯度:grad_w2 = h_relu^T * grad_y_pred
    grad_w2 = h_relu.t().mm(grad_y_pred)
    # 计算损失函数对隐藏层输出的梯度:grad_h_relu = grad_y_pred * w2^T
    grad_h_relu = grad_y_pred.mm(w2.t())
    # 复制隐藏层梯度,准备计算ReLU的梯度
    grad_h = grad_h_relu.clone()
    # ReLU函数的梯度:当输入小于0时梯度为0,否则保持原梯度值
    grad_h[h < 0] = 0
    # 计算损失函数对w1的梯度:grad_w1 = x^T * grad_h
    grad_w1 = x.t().mm(grad_h)

    # ========== 参数更新 ==========
    # 使用torch.no_grad()避免原地操作,确保计算图的完整性
    with torch.no_grad():
        # 梯度下降更新:新参数 = 旧参数 - 学习率 * 梯度
        w2 -= learning_rate * grad_w2
        w1 -= learning_rate * grad_w1

    # ========== 实时可视化更新 ==========
    # 每10次迭代更新一次可视化图表
    if t % 10 == 0:
        # 清空当前子图,准备绘制新数据
        ax1.clear()
        ax2.clear()

        # 在第一个子图中绘制完整的损失曲线
        ax1.plot(loss_history, 'b-', linewidth=1.5)
        ax1.set_title(f'训练损失 (迭代: {t})')
        ax1.set_xlabel('迭代')
        ax1.set_ylabel('损失')
        ax1.grid(True, alpha=0.3)

        # 在第二个子图中绘制最近50次迭代的损失
        # 这样可以更清晰地观察最近的训练趋势
        recent_losses = loss_history[-50:] if len(loss_history) > 50 else loss_history
        ax2.plot(recent_losses, 'r-', linewidth=1.5)
        ax2.set_title('最近50次迭代')
        ax2.set_xlabel('相对迭代')
        ax2.set_ylabel('损失')
        ax2.grid(True, alpha=0.3)

        # 调整子图布局,确保显示美观
        plt.tight_layout()
        # 短暂暂停,让图表有时间更新显示
        plt.pause(0.01)

# ==================== 训练完成后的处理 ====================
# 关闭交互模式
plt.ioff()
# 生成最终的可视化图表
plt.tight_layout()
# 保存训练结果图表
plt.savefig('training_results.png', dpi=300, bbox_inches='tight')
# 显示最终的训练结果
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Python进阶语法

通过代码自动分配设备

device = torch.device(“cuda” if torch.cuda.is_available() else “cpu”) #通用 
data = torch.Tensor([1]) # 声明一个Tensor
data.to(device)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

创建一维、二维、三维张量

torch.Tensor(list):根据列表创建多维张量,维度一致

a = torch.Tensor([1, 2, 3])
b = torch.Tensor([[1, 2, 3],[4, 5, 6]])
c = torch.Tensor([[[1, 2, 3],[4, 5, 6],[7,8,9]]])
print('一维张量:', a)
print('二维张量:', b)
print('三维张量:', c)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

创建全零/全1张量

torch.zeros(*sizes, out=None, …)/torch.ones(*sizes, out=None, …):返回大小为sizes的对应全零/全1张量

torch.zeros_like(input, …) /torch.ones_like(input, …):返回与input相同尺寸的对应全零/全1张量

input = torch.zeros(2)          #一维全0张量
print(input)           
print(torch.zeros(2,3))         #2行3列的全0张量
print(torch.ones(2, 3))           #2行3列的全1张量
print(torch.zeros_like(input))      #打印与input相同大小的全0张量
print(torch.ones_like(input))    #打印与input相同大小的全1张量

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

创建全x张量

torch.full(size, fill_value, …)返回大小为size,各元素值都为fill_value的张量

print(torch.full((3,4), 3.14))       #3行4列全3.14张量
print(torch.full_like(input, 2.3))    #打印与input相同大小的全2.3矩阵量

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

等差数列张量

torch.arange(start=0, end, step=1, …) 函数,返回结果为[start, end),步长为step的张量

torch.linspace(start, end, steps=100) 返回从[start, end],间隔中的插值数目为steps的tensor。

print(torch.arange(2,8))             #默认单位步长是1的等差数列张量
print(torch.linspace(2,10,steps=5))   #步长为2的等差数列

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

等比数列张量

torch.logspace(start, end, steps=100, base=10) 返回steps个从basestart到baseend的等比数列。

print(torch.logspace(1, 3, steps=3, base=10))        #以10为公倍数的等比

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

随机张量

torch.rand(size, out=None, dtype=None, …)返回*[0,1]****之间均匀分布**的随机数值,形状大小由size决定

torch.rand_like(input) 随机数满足**[0,1]**均匀分布,形状大小由input决定

torch.randint(low=0, high, size,…) 返回均匀分布的[low,high]之间的整数随机值

torch.randn(*sizes, out=None, …) 返回大小为sizes,均值为0,方差为1的标准正态分布的随机数值。

input = torch.eye(3)      #3维单位对角张量
print(torch.rand(2,2))    #2行2列的[0, 1]均匀分布随机数
print(torch.rand_like(input)) #3行3列的[0, 1]均匀分布随机数
print(torch.randint(1, 10, (2,2))) #2行2列的[1, 10]均匀分布随机数
print(torch.randn(2,2))     #2行2列的标准正态分布随机数

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

张量维度扩大

Tensor.expand(sizes):返回tensor的一个新视图,单个维度扩大为更大的尺寸,也可以扩大为更高维。扩大tensor不需要分配新内存,只是仅仅新建一个tensor的视图。

x = torch.tensor([[1], [2], [3]])
print(x.size())
print(x.expand(3, 4))   #维度扩大为3行4列
print(x.expand(-1, 2))  #-1表示自动计算数值
print(x)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

张量维度重复

Tensor.repeat**(*sizes)**沿着指定的维度重复tensor,不同于expand(),本函数复制的是tensor中的数据。

x = torch.tensor([1,2,3])    # 定义一个1*3tensor
print(x.repeat(4,2))             # 复制x中的元素值使变成4*6的tensor(1*4,2*3)
print(x)
print(x.repeat(4,2,1).size())    # 复制x中的元素变成3个维度: [4, 2, 3]

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

维度扩展

前面学到过**torch.squeeze****(****)**函数进行压缩,去除tensor维度。

torch.unsqueeze**(input, dim, out=None)**函数主要是进行扩展,第二个参数为0时,数据是行方向扩,为1时是列方向扩。

x = torch.tensor([1, 2, 3, 4])
print(torch.unsqueeze(x, 0))        # 向行方向扩展
print(x.unsqueeze(1))               # 向列方向扩展

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

改变维度

方式一:torch.reshape**(input, shape)**返回与输入相同的数据,但具有指定形状的tensor。

方式二:**Tensor.reshape(shape)**返回与输入相同的数据,但具有指定形状的tensor。

a = torch.arange(6)            #生成tensor
b = torch.reshape(a, (3, 2))   #重新组合形状为3*2
c = a.reshape(2,-1)            #重新组合形状为2*3
print(b)
print(c)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

维度交换

torch.transpose**(input, dim0, dim1)** 将张量按指定的2个维度交换位置,实现类似矩阵转置的操作。

x = torch.randint(1, 10, (2,3))        #生成2行3列[1,10]之间的随机整数
print(x)
print(torch.transpose(x, 0, 1))        # 将0维与1维调换位置

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

准换成数值 item()

在提取损失值的时候,常常会用到loss.item****(),其返回值是一个Python数值 (python number)。item()将tensor格式转化为python的数据类型格式。

注意 item() 只适用于 tensor 只包含一个元素的时候。因为大多数情况下我们的损失值就只有一个元素,所以就经常会用到 loss.item()。

如果想把含多个元素的 tensor 转换成 Python list 的话,要使用 tensor.tolist()。

x = torch.randn(1)                  # 随机生成1个1*1的正态分布tensor
print(x)
y = x.item()                        # 使用item()获取数值
print(y, type(y))                   # 打印类型
x = torch.randn([2, 2])             # 随机生成1个2*2的正态分布tensor
y = x.tolist()                      # 使用tolist获取转换为list
print(y)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Tensor张量求导机制

在PyTorch中,autograd是所有神经网络的核心内容,为Tensor所有操作提供自动求导方法。autograd.Variable 是autograd中最核心的类。它包装了一个Tensor,并且几乎支持所有在其上定义的操作。

Variable是一种可以不断变化的量,符合反向传播,参数更新的属性,可以称作变量。完成运算之后,在后续可以调用.backward()来计算出所有的梯度。

**新版本中,torch.autograd.Variable和torch.Tensor将同属一类。**Varible封装仍旧可以像以前一样工作,但返回的对象类型是torch.Tensor,这意味着代码不再需要变量封装器。requires_grad是Tensor类的一个属性,表示是否需要求求解梯度。grad和grad_fn分别表示梯度值及待求解梯度的函数类型。

import torch  # 导入PyTorch深度学习框架的主模块

from torch.autograd import Variable  # 导入Variable类,用于包装张量并启用梯度跟踪

# 创建一个可求导的变量x
# torch.Tensor([5]):创建一个值为5的张量(一维张量,包含一个元素5)
# Variable():将张量包装成Variable对象,便于自动微分
# requires_grad=True:启用梯度计算,允许在反向传播时计算该变量的梯度
x = Variable(torch.Tensor([5]), requires_grad=True)

# 定义计算图:执行线性运算 y = 2*x + 2
# 这里构建了一个简单的计算图,x是输入,y是输出
# 当x=5时,y=2*5+2=12
y = 2*x + 2

# 执行反向传播计算梯度
# backward():从y开始反向传播,自动计算y关于所有requires_grad=True的变量的梯度
# 对于y=2x+2,dy/dx=2,所以x的梯度将被计算为2
y.backward()

# 打印y的计算结果
# y.data:获取y的实际数值(张量形式)
print('y的打印结果为', y)

# 打印y的详细信息
# y:显示y的值和梯度函数信息
# y.type():显示y的数据类型,这里是torch.FloatTensor
print(y, '\n', 'y的类型是 ', y.type())

# 打印y的梯度函数
# grad_fn:记录创建y的操作类型,这里是AddBackward,表示加法操作
print('y的函数类型为', y.grad_fn)

# 打印x的梯度值
# x.grad:存储反向传播后计算出的梯度值
# 对于y=2x+2,dy/dx=2,所以x.grad应该为2
print('y关于x的梯度为', x.grad)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Autograd自动求导计算图

Autograd机制有一个计算图,可以将每个变量的计算流程以图结构表示出来,以下面计算过程为例,损失计算图可以表示为下图。

因为每个变量到损失需要经过那些运算都非常清楚,所以Autograd才能实现自主反向求导的过程。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当我们创建一个张量的时候,如果没有特殊指定的话,那么这个张量是默认是不需要求导的。我们可以通过 tensor.requires_grad**()**来检查一个张量是否需要求导。

在张量间的计算过程中,如果在所有输入中,有一个输入需要求导,那么输出一定会需要求导;相反,只有当所有输入都不需要求导的时候,输出才不需要求导。

import torch
from torch.autograd import Variable

# 创建三个3x3的随机张量变量
# a和b:默认不需要梯度计算(requires_grad=False)
# c:明确设置需要梯度计算(requires_grad=True)
a = Variable(torch.rand(3, 3))            # 创建变量a,随机3x3矩阵,不启用梯度跟踪
b = Variable(torch.rand(3, 3))            # 创建变量b,随机3x3矩阵,不启用梯度跟踪
c = Variable(torch.rand(3, 3), requires_grad=True)    # 创建变量c,随机3x3矩阵,启用梯度跟踪

# 计算x = a + b
# 由于a和b都不需要梯度,结果x也不需要梯度
x = a + b
print(x.requires_grad)        # 输出False,因为a和b都不需要梯度

# 计算y = a + c
# 虽然a不需要梯度,但c需要梯度,结果y会自动继承梯度需求
y = a + c
print(y.requires_grad)       # 输出True,因为c需要梯度,结果y也需要梯度

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在训练一个网络的时候,那么损失怎么还能自动求导呢?其实原因就是上边那条规则,虽然输入的训练数据是默认不求导的,但是,我们的model中的所有参数,它默认是求导的,这么一来,其中只要有一个需要求导,那么输出的网络结果必定也会需要求导的。

注意:不要把网络的输入和 Ground Truth(标注正确的数据) 的 requires_grad 设置为 True。虽然这样设置不会影响反向传播,但是需要额外计算网络的输入和 Ground Truth 的导数,增大了计算量和内存占用。

# 创建一个随机输入张量,形状为[8, 3, 50, 100]
# 8: batch_size(批次大小,一次处理8个样本)
# 3: 输入通道数(如RGB图像的3个颜色通道)
# 50, 100: 特征图的高度和宽度(像素尺寸)
input = torch.randn(8, 3, 50, 100)

# 检查输入张量是否需要梯度计算
# 由于创建时未指定requires_grad,默认值为False
print(input.requires_grad)                # 输出:False

# 使用torch.nn.Sequential构建一个顺序神经网络
# Sequential将多个网络层按顺序连接,形成一个完整的网络结构
net = torch.nn.Sequential(
    # 第一个卷积层:3输入通道 → 16输出通道
    # Conv2d(3, 16, 3, 1)参数说明:
    # 3: 输入通道数(与输入张量的通道数匹配)
    # 16: 输出通道数(卷积核数量)
    # 3: 卷积核大小(3×3)
    # 1: 步长(stride)
    torch.nn.Conv2d(3, 16, 3, 1),
    
    # 第二个卷积层:16输入通道 → 32输出通道
    # 输入通道数必须与前一层输出通道数匹配
    torch.nn.Conv2d(16, 32, 3, 1)
)

# 将输入张量传入网络进行前向传播计算
# 网络会自动执行两个卷积层的计算:
# 第一层:输入[8,3,50,100] → 输出[8,16,48,98]
# 第二层:输入[8,16,48,98] → 输出[8,32,46,96]
output = net(input)

# 检查输出张量是否需要梯度计算
# 虽然输入不需要梯度,但网络参数需要梯度,因此输出也需要梯度
print(output.requires_grad)             # 输出:True

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在训练的过程中冻结部分网络,让这些层的参数不可求梯度,观察输出的求导状态。这在迁移学习中很有用处。

input = torch.randn(8, 3, 50, 100)    # 创建一个tensor
print(input.requires_grad)                # 查看是否需要求导
net =torch.nn.Sequential(          # 定义两个卷积操作,并通过函数sequential连接          
 torch.nn.Conv2d(3, 16, 3, 1),
 torch.nn.Conv2d(16, 32, 3, 1))
for param in net.named_parameters():
 param[1].requires_grad = False           # 对模型中所有参数设置不求导
 print(param[0], param[1].requires_grad)    #输出参数信息以及对应求导状态
output = net(input)                          # 计算得到网络结构
print(output.requires_grad)             # 输出结果是否需要求导

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当我们在做模型评估的时候,不需要计算导数,我们可以将推断(inference)的代码包裹在“with torch.no_grad():”之中,以达到暂时不追踪网络参数中的导数的目的,通过这样的方式,可以减少计算和内存的消耗。

在深度学习中,**测试数据仅用于测试模型性能,不需要更新模型参数,因此常用torch.no_grad()**设置不自动求导

x = torch.randn(3, requires_grad=True)  # 设置需要求导
print(x.requires_grad)
with torch.no_grad():
    print((x ** 2).requires_grad)   # 使用不求导的函数则不会进行求导

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

多输出函数的梯度计算和梯度权重分配

代码功能分析

这段代码计算了一个具有两个输出的函数对输入w的梯度,并为每个输出分配了不同的权重。

数学表达式

  • 输入:w = 1.0, x = 2.0
  • 中间变量:a = w + x = 3.0, b = w + 1 = 2.0
  • 输出1:y0 = a × b = (w+x) × (w+1)
  • 输出2:y1 = a + b = (w+x) + (w+1)

梯度计算

1. 首先计算每个输出的梯度:

对于y0:

y0 = (w+x)(w+1) = w² + w + w·x + x
∂y0/∂w = 2w + 1 + x
当 w=1, x=2 时:
∂y0/∂w = 2×1 + 1 + 2 = 5

对于y1:

y1 = (w+x) + (w+1) = 2w + x + 1
∂y1/∂w = 2
当 w=1, x=2 时:
∂y1/∂w = 2

2. 梯度加权求和:

grad_tensors = [1., 2.]  # y0权重=1, y1权重=2
总梯度 = 1×5 + 2×2 = 9

关键概念解释

1. loss.backward(gradient=grad_tensors)

  • loss是一个向量(不是标量)时,必须提供gradient参数
  • grad_tensors指定了每个输出分量的梯度权重
  • 这相当于计算:∇L = Σ(weight_i × ∇y_i)

2. 为什么需要grad_tensors

# 如果没有grad_tensors,对于向量输出:
loss = torch.cat([y0, y1], dim=0)
loss.backward()  # 这会报错:grad can be implicitly created only for scalar outputs

# 正确做法:提供梯度权重
loss.backward(gradient=torch.tensor([1., 0.]))  # 只考虑y0的梯度
loss.backward(gradient=torch.tensor([0., 1.]))  # 只考虑y1的梯度
loss.backward(gradient=torch.tensor([1., 2.]))  # y0权重1,y1权重2

3. 与标量情况的对比

# 如果是标量损失,不需要gradient参数
scalar_loss = y0 + 2*y1  # 等价于权重[1, 2]
scalar_loss.backward()  # 直接计算梯度
print(w.grad)  # 同样输出9

实际应用场景

场景1:多任务学习

# 假设有两个任务:分类和回归
classification_loss = cross_entropy(pred_class, true_class)
regression_loss = mse_loss(pred_value, true_value)

# 将两个损失合并为一个向量
loss_vector = torch.stack([classification_loss, regression_loss])

# 为两个任务分配不同权重
task_weights = torch.tensor([0.7, 0.3])  # 分类任务权重70%,回归30%
loss_vector.backward(gradient=task_weights)

场景2:自定义梯度

# 手动控制不同输出的梯度流向
w1 = torch.tensor([1.], requires_grad=True)
w2 = torch.tensor([2.], requires_grad=True)

output1 = w1 * 2
output2 = w2 * 3

# 只让output1的梯度影响w1,output2的梯度影响w2
loss = torch.cat([output1, output2])
# 设置梯度掩码:output1对应w1,output2对应w2
grad_mask = torch.tensor([1., 0.])  # 或根据需要调整
loss.backward(gradient=grad_mask)

场景3:梯度裁剪的特殊应用

# 对不同输出的梯度进行不同方式的裁剪
loss_components = torch.cat([loss1, loss2, loss3])

# 为不同组件设置不同的梯度缩放
component_weights = torch.tensor([0.5, 1.0, 2.0])

# 先计算原始梯度
loss_components.backward(gradient=torch.ones(3))

# 然后应用自定义权重
with torch.no_grad():
    for param in model.parameters():
        if param.grad is not None:
            # 这里可以按组件权重调整梯度
            pass

代码验证

# 导入PyTorch库,PyTorch是一个开源的深度学习框架
import torch

# ==================== 第一部分:初始化可训练参数 ====================
# 创建一个标量张量w,初始值为1.0,并设置requires_grad=True
# 这意味着PyTorch会跟踪w的所有操作,以便计算梯度
w = torch.tensor([1.], requires_grad=True)

# 创建一个标量张量x,初始值为2.0,同样需要计算梯度
x = torch.tensor([2.], requires_grad=True)

# ==================== 第二部分:构建计算图 ====================
# 计算中间变量a = w + x
# 这里执行数学运算:a = 1.0 + 2.0 = 3.0
# 由于w和x都需要梯度,所以a也会自动需要梯度(requires_grad=True)
a = torch.add(w, x)  # 等价于 a = w + x
# 注意:retain_grad()注释表示如果想保留a的梯度,可以调用a.retain_grad()
# 但默认情况下,非叶子节点的梯度在backward后会被释放以节省内存

# 计算中间变量b = w + 1
# b = 1.0 + 1.0 = 2.0
# 常数1会被自动广播为与w相同形状的张量
b = torch.add(w, 1)  # 等价于 b = w + 1

# ==================== 第三部分:定义两个输出函数 ====================
# 计算第一个输出y0 = a * b
# 即 y0 = (w + x) * (w + 1)
# 代入值:y0 = (1+2) * (1+1) = 3 * 2 = 6.0
y0 = torch.mul(a, b)  # 等价于 y0 = a * b

# 计算第二个输出y1 = a + b
# 即 y1 = (w + x) + (w + 1)
# 代入值:y1 = (1+2) + (1+1) = 3 + 2 = 5.0
y1 = torch.add(a, b)  # 等价于 y1 = a + b

# ==================== 第四部分:合并输出并设置梯度权重 ====================
# 将两个输出合并为一个向量 loss = [y0, y1]
# dim=0 表示在第0维(行方向)拼接
# 结果:loss = tensor([6., 5.])
loss = torch.cat([y0, y1], dim=0)

# 创建梯度权重张量,指定每个输出分量的梯度权重
# grad_tensors = [1.0, 2.0] 表示:
# - y0的梯度权重为1.0
# - y1的梯度权重为2.0
# 这相当于我们要最小化的目标函数是:L = 1*y0 + 2*y1
grad_tensors = torch.tensor([1., 2.])

# ==================== 第五部分:反向传播计算梯度 ====================
# 执行反向传播,计算loss对w的梯度
# gradient参数告诉PyTorch如何组合各个输出的梯度:
# 总梯度 = grad_tensors[0] * ∇y0 + grad_tensors[1] * ∇y1
# 其中 ∇y0 是y0对w的梯度,∇y1 是y1对w的梯度
loss.backward(gradient=grad_tensors)

# ==================== 第六部分:数学验证 ====================
# 手动验证梯度计算:
# 1. 计算y0对w的梯度:
#    y0 = (w+x)(w+1) = w^2 + w + w*x + x
#    ∂y0/∂w = 2w + 1 + x
#    代入 w=1, x=2:∂y0/∂w = 2*1 + 1 + 2 = 5
#
# 2. 计算y1对w的梯度:
#    y1 = (w+x) + (w+1) = 2w + x + 1
#    ∂y1/∂w = 2
#    代入 w=1, x=2:∂y1/∂w = 2
#
# 3. 加权总梯度:
#    total_grad = 1*∂y0/∂w + 2*∂y1/∂w = 1*5 + 2*2 = 9
print(f"w的梯度值为: {w.grad.item()}")  # 预期输出: 9.0
print(f"理论计算验证: 1*5 + 2*2 = {1*5 + 2*2}")

# ==================== 第七部分:扩展理解 ====================
print("\n=== 详细计算过程 ===")
print(f"初始值: w = {w.item()}, x = {x.item()}")
print(f"中间变量: a = w + x = {a.item()}, b = w + 1 = {b.item()}")
print(f"输出1: y0 = a * b = {y0.item()}")
print(f"输出2: y1 = a + b = {y1.item()}")
print(f"合并loss: loss = [{loss[0].item()}, {loss[1].item()}]")
print(f"梯度权重: grad_tensors = [{grad_tensors[0].item()}, {grad_tensors[1].item()}]")
print(f"计算得到w的梯度: {w.grad.item()}")

# ==================== 第八部分:查看其他变量的梯度 ====================
print("\n=== 其他变量的梯度状态 ===")
print(f"w是否要求梯度: {w.requires_grad}")
print(f"w的梯度: {w.grad}")  # 这是主要关注的梯度
print(f"x是否要求梯度: {x.requires_grad}")
print(f"x的梯度: {x.grad}")  # x也有梯度,但代码中没有打印
print(f"a是否要求梯度: {a.requires_grad}")
print(f"a的梯度: {a.grad}")  # 通常为None,因为a是中间变量,默认不保留梯度

# ==================== 第九部分:实际应用场景解释 ====================
print("\n=== 实际应用场景 ===")
print("1. 多任务学习: y0和y1可以是不同任务的损失,通过grad_tensors调整任务重要性")
print("2. 多目标优化: 同时优化多个目标,为每个目标设置不同权重")
print("3. 自定义损失组合: 灵活组合不同损失函数")

# ==================== 第十部分:对比单一损失的情况 ====================
print("\n=== 对比:单一标量损失的情况 ===")
# 清空梯度重新计算
w.grad.zero_()
x.grad.zero_()

# 创建标量损失:L = 1*y0 + 2*y1
scalar_loss = 1*y0 + 2*y1
scalar_loss.backward()  # 标量可以直接backward,不需要gradient参数
print(f"标量损失下w的梯度: {w.grad.item()}")  # 同样应该是9.0
print("说明:向量损失的backward(gradient=grad_tensors)等价于标量损失 L = Σ(weight_i * y_i)")

Pytorch二阶求导

📌 代码核心功能概述

这段代码演示了如何使用PyTorch的自动求导(autograd)系统计算函数的高阶导数,特别是二阶导数。它展示了PyTorch如何通过保留计算图来实现链式求导。

🏗️ 代码结构解析

python

import torch
x = torch.tensor([3.], requires_grad=True)
y = torch.pow(x,2)
grad_1 = torch.autograd.grad(y, x, create_graph=True)
print(grad_1)
grad_2 = torch.autograd.grad(grad_1[0], x)
print(grad_2)

🔧 核心功能详解

1. 张量初始化与梯度跟踪

python

x = torch.tensor([3.], requires_grad=True)
  • 功能:创建需要梯度计算的张量
  • 关键参数requires_grad=True
  • 作用:告诉PyTorch跟踪x的所有操作,构建计算图

2. 函数定义与计算图构建

python

y = torch.pow(x, 2)  # y = x²
  • 功能:定义数学函数 y = f(x) = x²
  • 计算图记录x → pow运算 → y
  • 计算值:当x=3时,y=9

3. 一阶导数计算(关键步骤)

python

grad_1 = torch.autograd.grad(y, x, create_graph=True)
参数解析:
参数说明作用
y输出张量指定求导的函数输出
x输入张量指定对哪个变量求导
create_graph=True关键参数保留梯度的计算图
数学计算:

text

dy/dx = d(x²)/dx = 2x
当 x=3 时:dy/dx = 2×3 = 6
create_graph=True的重要性:
  • 启用高阶导数:使梯度本身也能被求导
  • 保留计算信息:梯度如何计算(2×x
  • 添加grad_fn:梯度张量获得grad_fn属性

4. 二阶导数计算

python

grad_2 = torch.autograd.grad(grad_1[0], x)
计算过程:

text

一阶导数:grad_1[0] = 2x
二阶导数:d²y/dx² = d(2x)/dx = 2
grad_1[0]解析:
  • grad_1是元组:(tensor([6.], grad_fn=),)
  • grad_1[0]:实际的梯度张量
  • grad_fn=:表示通过乘法计算得到

📊 计算流程图示

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

🔄 计算机制详解

计算图构建过程:

text

初始状态:
x (requires_grad=True, grad_fn=None)
    ↓ pow运算
y = x² (grad_fn=PowBackward0)

一阶求导后:
grad_1 = dy/dx = 2x
grad_1有grad_fn=MulBackward0(因为2×x)

二阶求导:
对grad_1[0]求导 → d(2x)/dx = 2

关键函数对比:

方法语法梯度存储高阶导数支持适用场景
.backward()y.backward()存储在.grad不支持常规训练
.autograd.grad()grad(y, x)返回梯度值支持(需create_graph)高阶导数计算

💡 核心概念解析

1. 计算图保留机制

python

# 正确:可计算二阶导数
grad_1 = torch.autograd.grad(y, x, create_graph=True)

# 错误:无法计算二阶导数
grad_1 = torch.autograd.grad(y, x, create_graph=False)  # 梯度无grad_fn

2. 梯度返回格式

python

# grad_1是元组,因为可能对多个输入求导
grad_1 = (tensor([6.], grad_fn=<MulBackward0>),)
           ↑               ↑
       梯度值        梯度计算方式

3. 链式求导原理

text

原始计算:y = x²
一阶导数:dy/dx = 2x
二阶导数:d²y/dx² = d(dy/dx)/dx = d(2x)/dx = 2

代码演示

import torch
# 创建一个张量x,值为3.0,并设置requires_grad=True以跟踪梯度计算
x = torch.tensor([3.], requires_grad=True)

# 定义函数y = x²,即计算x的平方
# 当x=3时,y = 9
y = torch.pow(x,2)  # y = x**2,等价于y = x²

# 计算y对x的一阶导数:grad_1 = dy/dx
# torch.autograd.grad函数:
#   - 参数1:需要求导的输出(y)
#   - 参数2:需要对哪个输入求导(x)
#   - create_graph=True:保留计算图以便计算高阶导数
# 对于y = x²,一阶导数dy/dx = 2x
# 当x=3时,grad_1 = 2*3 = 6
# 注意:grad_1返回的是元组(tensor([6.], grad_fn=<MulBackward0>),)
# 其中grad_fn属性表示梯度是通过乘法运算计算得到的
grad_1 = torch.autograd.grad(y, x, create_graph=True)  # grad_1 = dy/dx = 2x = 2*3=6

# 打印一阶导数结果
# grad_1是元组,包含一个张量:[6.],并有grad_fn属性
# 这个grad_fn属性很重要,它使得我们可以继续对这个梯度求导
print(grad_1)  # 输出:(tensor([6.], grad_fn=<MulBackward0>),)

# 计算y对x的二阶导数:grad_2 = d²y/dx² = d(dy/dx)/dx
# 这里grad_1[0]是一阶导数2x,我们对其再求导
# 对于grad_1[0] = 2x,它的导数是2(常数)
# 注意:这里没有设置create_graph=True,因为我们不需要计算更高阶的导数
grad_2 = torch.autograd.grad(grad_1[0], x)  # grad_2 = d(dy/dx)/dx = 2

# 打印二阶导数结果
# grad_2也是元组,包含一个张量:[2.]
print(grad_2)  # 输出:(tensor([2.]),)

梯度累加问题分析

🔍 核心功能概述

这段代码演示了PyTorch梯度累加的默认行为,展示了在多次迭代中如果不手动清零梯度,梯度会如何不断累积。

📊 执行过程分析

第1次迭代

  • 初始:w.grad = None
  • 计算:∂y/∂w = 2*1 + 2 + 1 = 5
  • 结果:w.grad = 5 (实际上是 None + 5 = 5

第2次迭代

  • 初始:w.grad = 5
  • 计算:∂y/∂w = 5(同样的计算)
  • 结果:w.grad = 5 + 5 = 10

第3次迭代

  • 初始:w.grad = 10
  • 计算:∂y/∂w = 5
  • 结果:w.grad = 10 + 5 = 15

第4次迭代

  • 初始:w.grad = 15
  • 计算:∂y/∂w = 5
  • 结果:w.grad = 15 + 5 = 20

⚙️ PyTorch梯度累加机制

数学表达式

text

# PyTorch的默认行为
w.grad = w.grad + ∂loss/∂w

# 而不是
w.grad = ∂loss/∂w

代码实现

import torch
# 创建可训练参数w,初始值为1.0,设置requires_grad=True以跟踪梯度
# 在PyTorch中,requires_grad=True表示该张量的所有操作都会被记录到计算图中
# 这样可以在反向传播时自动计算梯度
w = torch.tensor([1.], requires_grad=True)

# 创建输入张量x,初始值为2.0,同样需要计算梯度
# 在神经网络中,这相当于一个可学习的参数或输入特征
x = torch.tensor([2.], requires_grad=True)

# 开始4次循环迭代,模拟训练过程中的多个步骤
# 在真实训练场景中,这相当于处理多个batch或多次优化步骤
for i in range(4):
    # 计算中间变量a = w + x
    # 当w=1, x=2时,a = 1 + 2 = 3
    a = torch.add(w, x)  # 等价于 a = w + x
    
    # 计算中间变量b = w + 1
    # 当w=1时,b = 1 + 1 = 2
    # 注意:常数1会被自动广播为与w相同形状的张量
    b = torch.add(w,1)   # 等价于 b = w + 1
    
    # 计算输出y = a * b
    # 当a=3, b=2时,y = 3 * 2 = 6
    y = torch.mul(a, b)  # 等价于 y = a * b
    
    # 执行反向传播,计算y对w的梯度
    # 对于y = (w+x)(w+1),梯度计算如下:
    # ∂y/∂w = ∂[(w+x)(w+1)]/∂w = (w+1) + (w+x) = 2w + x + 1
    # 当w=1, x=2时:∂y/∂w = 2*1 + 2 + 1 = 5
    y.backward()
    
    # 打印w的梯度
    # 注意:PyTorch默认会累加梯度,而不是覆盖
    # 这意味着每次调用backward(),新计算的梯度会加到已有的w.grad上
    print(w.grad)  # 输出会显示梯度累加的效果

传播机制

🎯 核心功能概述

这段代码演示了PyTorch中**requires_grad属性的传播机制**,展示了当一个张量的操作依赖于需要梯度的输入时,输出张量如何自动继承梯度需求。

🔍 代码执行流程

张量初始化阶段

python

w = torch.tensor([1.], requires_grad=True)  # 需要梯度
x = torch.tensor([2.], requires_grad=True)  # 需要梯度
  • wx叶子节点(由用户直接创建)
  • 它们的 requires_grad 属性被显式设置为 True
  • 这意味着它们的梯度将被计算和存储

计算图构建阶段

python

a = torch.add(w, x)      # a = w + x = 3
b = torch.add(w, 1)      # b = w + 1 = 2
y = torch.mul(a, b)      # y = a * b = 6

构建的计算图结构:

text

    w (requires_grad=True)
    ├── a = w + x
    │   └── y = a * b
    └── b = w + 1
            └── y = a * b
    
    x (requires_grad=True)
    └── a = w + x
        └── y = a * b

输出结果

text

True True True

所有三个变量 a, b, yrequires_grad 属性都是 True

⚙️ requires_grad传播规则

规则1:输入决定输出

python

# 如果至少一个输入需要梯度,输出就需要梯度
output = operation(input1, input2, ...)
requires_grad(output) = requires_grad(input1) OR requires_grad(input2) OR ...

规则2:梯度需求沿计算图传播

python

# 示例1:所有输入都需要梯度
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)
result = w + x  # result.requires_grad = True

# 示例2:部分输入需要梯度
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=False)  # 或默认False
result = w + x  # result.requires_grad = True (因为w需要梯度)

# 示例3:没有输入需要梯度
w = torch.tensor([1.], requires_grad=False)
x = torch.tensor([2.], requires_grad=False)
result = w + x  # result.requires_grad = False

📊 实际输出验证

python

# 验证各个张量的属性
print("=== 张量属性验证 ===")
print(f"w: value={w.item()}, requires_grad={w.requires_grad}, grad_fn={w.grad_fn}")
print(f"x: value={x.item()}, requires_grad={x.requires_grad}, grad_fn={x.grad_fn}")
print(f"a: value={a.item()}, requires_grad={a.requires_grad}, grad_fn={a.grad_fn}")
print(f"b: value={b.item()}, requires_grad={b.requires_grad}, grad_fn={b.grad_fn}")
print(f"y: value={y.item()}, requires_grad={y.requires_grad}, grad_fn={y.grad_fn}")

# 执行反向传播验证梯度计算
y.backward()
print(f"\n=== 梯度计算验证 ===")
print(f"∂y/∂w = {w.grad.item()}")  # 应该为5 (2w + x + 1 = 2*1 + 2 + 1)
print(f"∂y/∂x = {x.grad.item()}")  # 应该为1 (w + 1 = 1 + 1 = 2)

代码演示

# 导入PyTorch库,这是进行深度学习计算的核心框架
import torch

# 创建张量w,初始值为1.0,并设置requires_grad=True
# requires_grad=True 表示PyTorch需要跟踪该张量的所有操作
# 以便在反向传播时自动计算梯度,这对于训练神经网络至关重要
w = torch.tensor([1.],requires_grad=True)

# 创建张量x,初始值为2.0,同样需要计算梯度
# 在神经网络中,x可以代表输入特征或另一个可学习的参数
x = torch.tensor([2.], requires_grad=True)

# 计算中间变量a = w + x
# torch.add()执行逐元素加法运算
# 当w=1, x=2时,a = 1 + 2 = 3
# 关键:由于w和x都设置了requires_grad=True,a会自动继承这个属性
a = torch.add(w,x)

# 计算中间变量b = w + 1
# 这里1是标量,会自动广播为与w相同形状的张量
# 当w=1时,b = 1 + 1 = 2
# 同样,b也会继承requires_grad属性
b = torch.add(w, 1)

# 计算最终输出y = a * b
# torch.mul()执行逐元素乘法运算
# 当a=3, b=2时,y = 3 * 2 = 6
# y也会自动获得requires_grad=True属性
y = torch.mul(a, b)

# 打印三个中间变量的requires_grad属性值
# 这个输出将展示PyTorch中梯度需求的传播机制
print(a.requires_grad, b.requires_grad, y.requires_grad)

神经网络

Pytorch提供了几个设计的非常好的模块和类,比如torch.nn、torch.optim、Dataset及DataLoader,使用好它们有助于设计和训练神经网络。

torch.nn.Moudle基类

torch.nn.Module是从所有神经网络结构中 抽象出来的基类,它实现了所有网络结构中共同的部分代码,在定义一个具体的网络模型时继承这个类,就相当于拷贝了所有共有部分的代码,这部分代码就不用自己再写了。

import torch.nn as nn  # 导入神经网络模块

# 1. 定义自己的网络类,继承nn.Module
class MyNet(nn.Module):
    # 2. 初始化函数:定义网络有哪些"零件"
    def __init__(self):
        super().__init__()  # 一定要调用父类的初始化
        
        # 3. 定义网络层(就像汽车的方向盘、发动机)
        self.fc = nn.Linear(10, 5)  # 全连接层:10维输入,5维输出
    
    # 4. 前向传播:定义数据如何流动
    def forward(self, x):
        x = self.fc(x)  # 数据通过全连接层
        return x

# 5. 创建网络实例
net = MyNet()
print("我的第一个网络创建成功!")
print(net)

代码说明:

  1. 定义了两个卷积层,分别处理灰度图像
  2. 使用ReLU激活函数提升非线性表达能力
  3. 模型结构简单,适合初学者理解卷积神经网络
  4. 可直接运行打印模型结构,验证网络定义正确性
# author:"Hiway"
# datetime:2025/12/8 22:12

import torch.nn as nn
import torch.nn.functional as F

class Model(nn.Module):
 def __init__(self):
     super(Model, self).__init__()
     # 定义第一个卷积层,输入通道1(灰度图像),输出通道20,卷积核大小5x5
        self.conv1 = nn.Conv2d(1, 20, 5)
        # 定义第二个卷积层,输入通道20,输出通道20,卷积核大小5x5
        self.conv2 = nn.Conv2d(20, 20, 5)
   
    def forward(self, x):
        # 通过第一个卷积层并应用ReLU激活函数
     x = F.relu(self.conv1(x))
        # 通过第二个卷积层并应用ReLU激活函数
        return F.relu(self.conv2(x))
   
   # 创建模型实例
   model = Model()
# 打印模型结构
print(model)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

torch.nn.Sequential()方法

torch.nn.Sequential(*args)是一个时序容器,modules以它们传入的顺序添加到容器中。通过Squential()将网络层和激活函数结合起来。

# 方法1:直接创建(适合简单网络)
model1 = nn.Sequential(
 nn.Linear(10, 8),  # 第一道工序
 nn.ReLU(),         # 激活函数(就像给产品加热)
 nn.Linear(8, 1)    # 第二道工序
   )
   
   print("流水线网络1:")
print(model1)

# 方法2:在自定义网络中使用(更常用)
class BetterNet(nn.Module):
 def __init__(self):
     super().__init__()
     self.pipeline = nn.Sequential(
            nn.Linear(20, 10),
            nn.ReLU(),
            nn.Linear(10, 5),
            nn.ReLU(),
            nn.Linear(5, 1)
        )
    
    def forward(self, x):
        return self.pipeline(x)  # 一句话搞定!
   
   net2 = BetterNet()
   print("\n流水线网络2:")
print(net2)

代码说明:

  1. 定义了一个全连接神经网络模型,包含3层线性变换和ReLU激活函数。
  2. 输入维度20,隐藏层1维度10,隐藏层2维度5,输出维度1。
  3. 使用PyTorch的nn.Module和nn.Sequential构建网络结构。
  4. 通过print(model)输出模型结构,显示各层参数和连接关系。
  5. 添加了输出层,使模型完整可运行。
  6. 代码可直接运行,输出模型结构信息。
  7. 项目最终文件标识为true,表示该文件为项目完成状态。
import torch.nn as nn

class Net(nn.Module):
 def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
     super().__init__()
     # 定义网络层结构
        self.layer = nn.Sequential(
            nn.Linear(in_dim, n_hidden_1),  # 输入层到隐藏层1
            nn.ReLU(True),                  # ReLU激活函数
            nn.Linear(n_hidden_1, n_hidden_2),  # 隐藏层1到隐藏层2
            nn.ReLU(True),                  # ReLU激活函数
            nn.Linear(n_hidden_2, out_dim)  # 隐藏层2到输出层
        )
    
    def forward(self, x):
        # 前向传播过程
        x = self.layer(x)
        return x
    
   # 创建模型实例
   model = Net(20,10,5,1)
   # 打印模型结构
print(model)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

torch.nn.Sequential(*args)的参数变量是一个指针,可以是多种形式的,如上面例子中的是子模型的每层的网络结构和激活函数,其参数可以是有序字典。

代码说明:

  1. 定义了一个卷积神经网络模型,使用OrderedDict确保层的有序性
  2. 包含两个卷积层和对应的ReLU激活函数,通道数从1增加到64
  3. 使用PyTorch的nn.Sequential构建顺序网络结构
  4. 每个网络层都有明确的命名,便于调试和参数访问
  5. 模型结构清晰,适合处理图像数据
  6. 代码可直接运行,输出完整的模型架构信息
  7. 添加了详细注释说明每层功能和参数设置
  8. 项目最终文件标识为true,表示该文件为项目完成状态

import torch.nn as nn
from collections import OrderedDict

class Net(nn.Module):
 def __init__(self):
     super().__init__()
     # 使用OrderedDict定义有序的神经网络层序列
        self.layer = nn.Sequential(OrderedDict([
                  ('conv1', nn.Conv2d(1,20,5)),  # 第一层卷积:输入通道1,输出通道20,卷积核5x5
                  ('relu1', nn.ReLU()),         # 第一层ReLU激活函数
                  ('conv2', nn.Conv2d(20,64,5)), # 第二层卷积:输入通道20,输出通道64,卷积核5x5
                  ('relu2', nn.ReLU())          # 第二层ReLU激活函数
                	]))
    
    def forward(self, x):
        # 前向传播:依次通过所有网络层
        x = self.layer(x)
        return x
   
   # 创建模型实例
   model = Net()
# 打印模型结构,显示各层详细信息
print(model)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

torch.nn.ModuleList()方法

torch.nn.ModuleList(modules=None)将子模块保存在一个列表中。ModuleList可以向一般的Python列表(List)一样被索引。而且MoudleList中包含的moudles已经被正确定义,对所有的Moudle方法均可见。

代码说明:

  1. 定义了一个自定义神经网络模块MyModule,包含4个线性层
  2. 使用nn.ModuleList管理线性层列表,确保参数可被PyTorch跟踪
  3. 前向传播中,每两个线性层的输出相加,实现特征融合
  4. 通过print(model)输出模型结构,显示各层参数和连接关系
  5. 添加了详细注释说明每层功能和参数设置
  6. 代码可直接运行,输出完整的模型架构信息
  7. 项目最终文件标识为true,表示该文件为项目完成状态

import torch.nn as nn

class MyModule(nn.Module):
 def __init__(self):
     super(MyModule, self).__init__()
     # 创建一个包含4个线性层的列表
        self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(4)])
    
    def forward(self, x):
        # 遍历所有线性层
        for i, l in enumerate(self.linears):
            # 每两个线性层的输出相加
            x = self.linears[i // 2](x) + l(x)
        return x
   
   # 创建模型实例
   model = MyModule()
# 打印模型结构
print(model)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

卷积操作

卷积

import torch                   # 导入torch包
import torch.nn as nn            # 导入神经网络模块
m = nn.Conv2d(5, 5, 3, padding=0, stride=2, dilation=1)       # 定义二维卷积层
input = torch.randn(5,5,3,3)       # 生成5个5通道的3*3元素的tensor
print(m(input).shape)            # 输出卷积层之后的结果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

反卷积

x = m(input)
n = torch.nn.ConvTranspose2d(5, 5, 3, stride=2,dilation=1)
print(n(x).shape)       

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

池化方法

最大池化方案

import torch # 导入torch包
import torch.autograd as autograd # 导入autograd包
from torch.autograd import Variable                  
pool = nn.MaxPool2d(2, stride=2, return_indices=True) # 定义最大池化操作
unpool = nn.MaxUnpool2d(2, stride=2) # 定义最大池化的逆过程
input = Variable(torch.Tensor([[[[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]]]])) 
output, indices = pool(input) # 池化
print(output)  # 输出查看池化之后的结果
print(unpool(output, indices)) # 输出查看反池化之后的结果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

平均池化

import torch    # 导入torch包
import torch.autograd as autograd    # 导入autograd包
m0 = nn.AvgPool2d(3, stride=2)    # 定义平均池化操作(正方形模板)
m = nn.AvgPool2d((3, 2), stride=(2, 1))    # 定义平均池化操作(长方形模板)
input = autograd.Variable(torch.randn(5, 5, 3, 3))    # 生成5个5通道的3*3元素的tensor
print(m(input).shape)    # 输出查看平均池化之后的形状

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

自适应池化

m = nn.AdaptiveMaxPool2d((6,8))    # 设置自适应最大池化输出的尺寸
input = autograd.Variable(torch.randn(5, 5, 3, 3)) 
# 随机生成5个5通道的宽高为3*3的tensor
print(m(input).shape)    # 输出查看自适应最大池化后结果的尺寸
mA = nn.AdaptiveAvgPool2d((6,8))    # 设置自适应平均池化输出的尺寸
print(mA(input).shape)    # 输出查看自适应平均池化后结果的尺寸

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

激活函数

m = nn.ReLU()    # 设置ReLU()函数
n = nn.Tanh()    # 设置Tanh()函数
k = nn.Sigmoid()    # 设置Sigmoid()函数
input = autograd.Variable(torch.randn(2))
print(m(input))    # 输出ReLU()函数之后的结果
print(n(input))    # 输出Tanh()函数之后的结果
print(k(input))    # 输出Sigmoid()函数之后的结果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

标准化方法

m = nn.BatchNorm2d(2, affine=False) # 设置归一化操作
input = autograd.Variable(torch.randn(2,2,3,3)) # 随机生成2个2通道的宽高为3*3的tensor
print(m(input)) # 输出归一化的结果

[外链图片转存中...(img-VNPeLl6p-1772436315008)]

## torch.nn.ModuleList()方法

torch.nn.ModuleList(modules=None)将子模块保存在一个列表中。ModuleList可以向一般的Python列表(List)一样被索引。而且MoudleList中包含的moudles已经被正确定义,对所有的Moudle方法均可见。

代码说明:

1. 定义了一个自定义神经网络模块MyModule,包含4个线性层
2. 使用nn.ModuleList管理线性层列表,确保参数可被PyTorch跟踪
3. 前向传播中,每两个线性层的输出相加,实现特征融合
4. 通过print(model)输出模型结构,显示各层参数和连接关系
5. 添加了详细注释说明每层功能和参数设置
6. 代码可直接运行,输出完整的模型架构信息
7. 项目最终文件标识为true,表示该文件为项目完成状态

```python

import torch.nn as nn

class MyModule(nn.Module):
 def __init__(self):
     super(MyModule, self).__init__()
     # 创建一个包含4个线性层的列表
        self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(4)])
    
    def forward(self, x):
        # 遍历所有线性层
        for i, l in enumerate(self.linears):
            # 每两个线性层的输出相加
            x = self.linears[i // 2](x) + l(x)
        return x
   
   # 创建模型实例
   model = MyModule()
# 打印模型结构
print(model)

[外链图片转存中…(img-MuXzJ3I0-1772436315008)]

卷积操作

卷积

import torch                   # 导入torch包
import torch.nn as nn            # 导入神经网络模块
m = nn.Conv2d(5, 5, 3, padding=0, stride=2, dilation=1)       # 定义二维卷积层
input = torch.randn(5,5,3,3)       # 生成5个5通道的3*3元素的tensor
print(m(input).shape)            # 输出卷积层之后的结果

[外链图片转存中…(img-pQL2bB02-1772436315008)]

反卷积

x = m(input)
n = torch.nn.ConvTranspose2d(5, 5, 3, stride=2,dilation=1)
print(n(x).shape)       

[外链图片转存中…(img-vi8G0GKG-1772436315009)]

池化方法

最大池化方案

import torch # 导入torch包
import torch.autograd as autograd # 导入autograd包
from torch.autograd import Variable                  
pool = nn.MaxPool2d(2, stride=2, return_indices=True) # 定义最大池化操作
unpool = nn.MaxUnpool2d(2, stride=2) # 定义最大池化的逆过程
input = Variable(torch.Tensor([[[[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]]]])) 
output, indices = pool(input) # 池化
print(output)  # 输出查看池化之后的结果
print(unpool(output, indices)) # 输出查看反池化之后的结果

[外链图片转存中…(img-MkKwBIm7-1772436315009)]

平均池化

import torch    # 导入torch包
import torch.autograd as autograd    # 导入autograd包
m0 = nn.AvgPool2d(3, stride=2)    # 定义平均池化操作(正方形模板)
m = nn.AvgPool2d((3, 2), stride=(2, 1))    # 定义平均池化操作(长方形模板)
input = autograd.Variable(torch.randn(5, 5, 3, 3))    # 生成5个5通道的3*3元素的tensor
print(m(input).shape)    # 输出查看平均池化之后的形状

[外链图片转存中…(img-OXeR8tIn-1772436315009)]

自适应池化

m = nn.AdaptiveMaxPool2d((6,8))    # 设置自适应最大池化输出的尺寸
input = autograd.Variable(torch.randn(5, 5, 3, 3)) 
# 随机生成5个5通道的宽高为3*3的tensor
print(m(input).shape)    # 输出查看自适应最大池化后结果的尺寸
mA = nn.AdaptiveAvgPool2d((6,8))    # 设置自适应平均池化输出的尺寸
print(mA(input).shape)    # 输出查看自适应平均池化后结果的尺寸

[外链图片转存中…(img-V315ofP1-1772436315009)]

激活函数

m = nn.ReLU()    # 设置ReLU()函数
n = nn.Tanh()    # 设置Tanh()函数
k = nn.Sigmoid()    # 设置Sigmoid()函数
input = autograd.Variable(torch.randn(2))
print(m(input))    # 输出ReLU()函数之后的结果
print(n(input))    # 输出Tanh()函数之后的结果
print(k(input))    # 输出Sigmoid()函数之后的结果

[外链图片转存中…(img-RHZLAxi8-1772436315009)]

标准化方法

m = nn.BatchNorm2d(2, affine=False) # 设置归一化操作
input = autograd.Variable(torch.randn(2,2,3,3)) # 随机生成2个2通道的宽高为3*3的tensor
print(m(input)) # 输出归一化的结果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

更多推荐