机器学习教程

[TOC]

img

机器学习(Machine Learning)是人工智能(AI)的一个分支,它使计算机系统能够利用数据和算法自动学习和改进其性能。

机器学习是让机器通过经验(数据)来做决策和预测。

机器学习已经广泛应用于许多领域,包括推荐系统、图像识别、语音识别、金融分析等。

举个例子,通过机器学习,汽车可以学习如何识别交通标志、行人和障碍物,以实现自动驾驶。

机器学习与传统编程的区别

在传统的编程方法中,程序员会编写一系列规则或指令,告诉计算机如何执行任务。而在机器学习中,程序员并不是直接编写所有规则,而是训练计算机从数据中自动学习和推断模式。具体的差异可以总结如下:

  • 传统编程: 程序员定义明确的规则和逻辑,计算机根据这些规则执行任务。
  • 机器学习: 计算机通过数据”学习”模式,生成模型基于这些模式进行预测决策

举个简单的例子,假设我们要训练一个模型来识别猫和狗的图片。

img

在传统编程中,程序员需要手动定义哪些特征可以区分猫和狗(如耳朵形状、鼻子形状等),而在机器学习中,程序员只需要提供大量带标签的图片数据,计算机会自动学习如何区分猫和狗。


常见机器学习任务

  • 回归问题预测连续值,例如房价预测。
  • 分类问题:将样本分为不同类别,例如垃圾邮件检测。
  • 聚类问题:将数据自动分组,例如客户细分。
  • 降维问题:将数据降到低维度,例如主成分分析(PCA)。

机器学习常见算法

监督学习:

  • 线性回归(Linear Regression)
  • 逻辑回归(Logistic Regression)
  • 支持向量机(SVM)
  • K-近邻算法(KNN)
  • 决策树(Decision Tree)
  • 随机森林(Random Forest)

无监督学习:

  • K-均值聚类(K-Means Clustering)
  • 主成分分析(PCA)

深度学习:

  • 神经网络(Neural Networks)
  • 卷积神经网络(CNN)
  • 循环神经网络(RNN)

机器学习简介

机器学习是如何工作的?

机器学习通过让计算机从大量数据中学习模式和规律来做出决策和预测。

  • 首先,收集并准备数据,然后选择一个合适的算法来训练模型。
  • 然后,模型通过不断优化参数最小化预测错误,直到能准确地对新数据进行预测。
  • 最后,模型部署到实际应用中,实时做出预测或决策,并根据新的数据进行更新。

机器学习是一个迭代过程,可能需要多次调整模型参数和特征选择,以提高模型的性能。

下面这张图展示了机器学习的基本流程:

img

  1. Labeled Data(标记数据)::图中蓝色区域显示了标记数据,这些数据包括了不同的几何形状(如六边形、正方形、三角形)。
  2. Model Training(模型训练)::在这个阶段,机器学习算法分析数据的特征,并学习如何根据这些特征来预测标签。
  3. Test Data(测试数据)::图中深绿色区域显示了测试数据,包括一个正方形和一个三角形。
  4. Prediction(预测)::模型使用从训练数据中学到的规则来预测测试数据的标签。在图中,模型预测了测试数据中的正方形和三角形。
  5. Evaluation(评估)::预测结果与测试数据的真实标签进行比较,以评估模型的准确性。

机器学习的工作流程可以大致分为以下几个步骤:

1. 数据收集

  • 收集数据:这是机器学习项目的第一步,涉及收集相关数据。数据可以来自数据库、文件、网络或实时数据流。
  • 数据类型:可以是结构化数据(如表格数据)或非结构化数据(如文本、图像、视频)。

2. 数据预处理

  • 清洗数据:处理缺失值、异常值、错误和重复数据。
  • 特征工程选择有助于模型学习的最相关特征,可能包括创建新特征或转换现有特征。
  • 数据标准化/归一化:调整数据的尺度,使其在同一范围内,有助于某些算法的性能。

3. 选择模型

  • 确定问题类型:根据问题的性质(分类、回归、聚类等)选择合适的机器学习模型
  • 选择算法:基于问题类型和数据特性,选择一个或多个算法进行实验。

4. 训练模型

  • 划分数据集:将数据分为训练集、验证集和测试集。
  • 训练:使用训练集上的数据来训练模型,调整模型参数以最小化损失函数
  • 验证:使用验证集来调整模型参数,防止过拟合

5. 评估模型

  • 性能指标:使用测试集来评估模型的性能,常用的指标包括准确率、召回率、F1分数等。
  • 交叉验证:一种评估模型泛化能力的技术,通过将数据分成多个子集进行训练和验证。

6. 模型优化

  • 调整超参数:超参数是学习过程之前设置的参数,如学习率、树的深度等,可以通过网格搜索、随机搜索或贝叶斯优化等方法来调整。
  • 特征选择:可能需要重新评估和选择特征,以提高模型性能。

7. 部署模型

  • 集成到应用:将训练好的模型集成到实际应用中,如网站、移动应用或软件中。
  • 监控和维护:持续监控模型的性能,并根据新数据更新模型。

8. 反馈循环

  • 持续学习:机器学习模型可以设计为随着时间的推移自动从新数据中学习,以适应变化。

技术细节

  • 损失函数:一个衡量模型预测与实际结果差异的函数,模型训练的目标是最小化这个函数。
  • 优化算法:如梯度下降,用于找到最小化损失函数的参数值。
  • 正则化:一种技术,通过添加惩罚项来防止模型过拟合。

机器学习的工作流程是迭代的,可能需要多次调整和优化以达到最佳性能。此外,随着数据的积累和算法的发展,机器学习模型可以变得更加精确和高效。

机器学习的类型

机器学习主要分为以下三种类型:

1. 监督学习(Supervised Learning)

  • 定义: 监督学习是指使用带标签的数据进行训练,模型通过学习输入数据与标签之间的关系,来做出预测或分类。
  • 应用: 分类(如垃圾邮件识别)、回归(如房价预测)。
  • 例子: 线性回归、决策树、支持向量机(SVM)。

2. 无监督学习(Unsupervised Learning)

  • 定义: 无监督学习使用没有标签的数据,模型试图在数据中发现潜在的结构或模式。
  • 应用: 聚类(如客户分群)、降维(如数据可视化)。
  • 例子: K-means 聚类、主成分分析(PCA)。

3. 强化学习(Reinforcement Learning)

  • 定义: 强化学习通过与环境互动,智能体在试错中学习最佳策略,以最大化长期回报。每次行动后,系统会收到奖励或惩罚,来指导行为的改进。
  • 应用: 游戏AI(如AlphaGo)、自动驾驶、机器人控制。
  • 例子: Q-learning、深度Q网络(DQN)。

img

这三种机器学习类型各有其应用场景和优势,监督学习适用于有明确标签的数据,无监督学习适用于探索数据内在结构,而强化学习适用于需要通过试错来学习最优策略的场景。

机器学习的应用领域

  • 推荐系统: 例如,抖音推荐你可能感兴趣的视频,淘宝推荐你可能会购买的商品,网易云音乐推荐你喜欢的音乐。
  • 自然语言处理(NLP): 机器学习在语音识别、机器翻译、情感分析、聊天机器人等方面的应用。例如,Google 翻译、Siri 和智能客服等。
  • 计算机视觉: 机器学习在图像识别、物体检测、面部识别、自动驾驶等领域有广泛应用。例如,自动驾驶汽车通过摄像头和传感器识别周围的障碍物,识别行人和其他车辆。
  • 金融分析: 机器学习在股市预测、信用评分、欺诈检测等金融领域具有重要应用。例如,银行利用机器学习检测信用卡交易中的欺诈行为。
  • 医疗健康: 机器学习帮助医生诊断疾病、发现药物副作用、预测病情发展等。例如,IBM 的 Watson 系统帮助医生分析患者的病历数据,提供诊断和治疗建议。
  • 游戏和娱乐: 机器学习不仅用于游戏中的智能对手,还应用于游戏设计、动态难度调整等方面。例如,AlphaGo 使用深度学习技术战胜了围棋世界冠军。

机器学习的未来

随着数据量的爆炸式增长和计算能力的提升,机器学习的应用将继续扩展,带来更加智能和高效的系统。例如:

  • 强化学习: 使计算机能够在没有明确指导的情况下通过试错来解决复杂问题。例如,AlphaGo 和 OpenAI 的 Dota 2 游戏 AI 都使用了强化学习。
  • 自监督学习: 目前的机器学习模型通常需要大量带标签的数据来进行训练,而自监督学习则能够在没有标签的数据下学习更有效的表示。
  • 深度学习: 深度学习是机器学习中的一个分支,主要关注神经网络的应用,它已经在图像识别、自然语言处理等方面取得了突破性进展。未来,深度学习将继续推动人工智能的发展。

机器学习如何工作

机器学习(Machine Learning, ML)的核心思想是让计算机能够通过数据学习,并从中推断出规律或模式,而不依赖于显式编写的规则或代码。

简单来说,机器学习的工作流程是让机器通过历史数据自动改进其决策和预测能力。

机器学习的工作流程可以简化为以下几个步骤:

  1. 收集数据:准备包含特征和标签的数据。
  2. 选择模型:根据任务选择合适的机器学习算法。
  3. 训练模型:让模型通过数据学习模式,最小化误差。
  4. 评估与验证:通过测试集评估模型性能,并进行优化。
  5. 部署模型:将训练好的模型应用到实际场景中进行预测。
  6. 持续改进:随着新数据的产生,模型需要定期更新和优化。

这个过程能够让计算机从经验中自动学习,并在各种任务中做出越来越准确的预测。

img

我们可以从以下几个方面来理解机器学习是如何工作的:

1. 数据输入:数据是学习的基础

机器学习的第一步是数据收集。没有数据,机器学习模型无法进行训练。数据通常包括”输入特征“和”标签“:

  • 输入特征(Features): 这些是模型用来做预测或分类的信息。例如,在房价预测问题中,输入特征可以是房子的面积、地理位置、卧室数量等。
  • 标签(Labels): 标签是我们想要预测或分类的结果,通常是一个数字或类别。例如,在房价预测问题中,标签是房子的价格。

机器学习模型的目标是从数据中找出输入特征与标签之间的关系,基于这些关系做出预测。

2. 模型选择:选择合适的学习算法

机器学习模型也叫做算法)是帮助计算机学习数据并进行预测的工具。根据数据的性质和任务的不同,常见的机器学习模型包括:

  • 监督学习模型: 给定带有标签的数据,模型通过学习输入和标签之间的关系来做预测。例如,线性回归逻辑回归支持向量机(SVM)决策树
  • 无监督学习模型: 没有标签的数据,模型通过探索数据中的结构或模式来进行学习。例如,K-means 聚类主成分分析(PCA)
  • 强化学习模型: 模型在与环境互动的过程中,通过奖励和惩罚来学习最佳行为。例如,Q-learning深度强化学习(Deep Q-Networks, DQN)。

3. 训练过程:让模型从数据中学习

在训练阶段,模型通过历史数据”学习”输入和标签之间的关系,通常通过最小化一个损失函数(Loss Function)来优化模型的参数。训练过程可以概括为以下步骤:

  • 初始状态: 模型从随机值开始。比如,神经网络的权重是随机初始化的。
  • 计算预测: 对于每个输入,模型会做出一个预测。这是通过将输入数据传递给模型,计算得到输出。
  • 计算误差(损失): 误差是指模型预测的输出与实际标签之间的差异。例如,对于回归问题,误差可以通过均方误差(MSE)来衡量。
  • 优化模型: 通过反向传播(在神经网络中)或梯度下降等优化算法,不断调整模型的参数(如神经网络的权重),使得误差最小化。这个过程就是训练,直到模型能够在训练数据上做出比较准确的预测。

4. 验证与评估:测试模型的性能

训练过程完成后,我们需要评估模型的性能。为了避免模型过度拟合训练数据,我们将数据分为训练集测试集,其中:

  • 训练集: 用于训练模型的部分数据。
  • 测试集: 用于评估模型性能的部分数据,通常不参与训练过程。

常见的评估指标包括:

  • 准确率(Accuracy): 分类问题中正确分类的比例。
  • 均方误差(MSE): 回归问题中,预测值与真实值差的平方的平均值
  • 精确率(Precision)与召回率(Recall): 用于二分类问题,尤其是类别不平衡时。
  • F1分数: 精确率召回率调和平均数,综合考虑分类器的表现。

5. 优化与调整:提高模型的精度

如果模型在测试集上的表现不理想,可能需要进一步优化。这通常包括:

  • 调整超参数(Hyperparameters): 比如学习率正则化系数树的深度等。这些超参数影响模型的学习能力。
  • 模型选择与融合: 尝试不同的模型或模型融合(比如集成学习方法,如随机森林、XGBoost 等)来提高精度。
  • 数据增强: 扩展训练数据集,比如对图像进行旋转、翻转等操作,帮助模型提高泛化能力。

6. 模型部署与预测:实际应用

一旦模型在训练和测试数据上表现良好,就可以将模型部署到实际应用中:

  • 模型部署: 将训练好的模型嵌入到应用程序、网站、服务器等系统中,供用户使用。
  • 实时预测: 在实际环境中,新的数据输入到模型中,模型根据之前学习到的模式进行实时预测或分类。

7. 持续学习与模型更新:

机器学习系统通常不是一次性完成的。在实际应用中,随着时间的推移,新的数据会不断产生,因此,模型需要定期更新和再训练,以保持其预测能力。这可以通过在线学习迁移学习等方法来实现。

机器学习基础概念

在学习机器学习时,理解其核心基础概念至关重要。

这些基础概念帮助我们理解数据如何输入到模型中、模型如何学习、以及如何评估模型的表现。

接下来,我们将详细讲解几个机器学习中的基本概念:

  • 训练集、测试集和验证集:帮助训练、评估和调优模型。
  • 特征与标签:特征是输入,标签是模型预测的目标
  • 模型与算法:模型是通过算法训练得到的,算法帮助模型学习数据中的模式
  • 监督学习、无监督学习和强化学习:三种常见的学习方式,分别用于不同的任务。
  • 过拟合与欠拟合:两种常见的问题,影响模型的泛化能力。
  • 训练误差与测试误差:反映模型是否能适应数据,并进行有效预测。
  • 评估指标:衡量模型好坏的标准,根据任务选择合适的指标。

这些基础概念是理解和应用机器学习的基础,掌握它们是进一步学习的关键。

训练集、测试集和验证集

  • 训练集(Training Set): 训练集是用于训练机器学习模型的数据集,它包含输入特征和对应的标签(在监督学习中)。模型通过学习训练集中的数据来调整参数,逐步提高预测的准确性。
  • 测试集(Test Set): 测试集用于评估训练好的模型的性能。测试集中的数据不参与模型的训练,模型使用它来进行预测,并与真实标签进行比较,帮助我们了解模型在未见过的数据上的表现
  • 验证集(Validation Set): 验证集用于在训练过程中调整模型的超参数(如学习率、正则化参数等)。它通常被用于模型调优,帮助选择最佳的模型参数避免过拟合。验证集的作用是对模型进行监控和调试

总结:

  • 训练集用于训练模型。
  • 测试集用于评估模型的最终性能。
  • 验证集用于模型调优

特征(Features)和标签(Labels)

  • 特征(Features): 特征是输入数据的不同属性,模型使用这些特征来做出预测或分类。例如,在房价预测中,特征可能包括房子的面积、地理位置、卧室数量等。
  • 标签(Labels): 标签是机器学习任务中的目标变量,模型要预测的结果。对于监督学习任务,标签通常是已知的。例如,在房价预测中,标签就是房子的实际价格。

总结:

  • 特征是模型输入的数据。
  • 标签是模型需要预测的输出。

模型(Model)与算法(Algorithm)

  • 模型(Model): 模型是通过学习数据中的模式而构建的数学结构。它接受输入特征,经过一系列计算和转化,输出一个预测结果。常见的模型有线性回归、决策树、神经网络等。
  • 算法(Algorithm): 算法是实现机器学习的步骤或规则,它定义了模型如何从数据中学习。常见的算法有梯度下降法、随机森林、K近邻算法等。算法帮助模型调整其参数以最小化预测误差。

总结:

  • 模型是学习到的结果,它可以用来进行预测。
  • 算法是训练模型的过程,帮助模型从数据中学习。

监督学习、无监督学习和强化学习

  • 监督学习(Supervised Learning): 在监督学习中,训练数据包含已知的标签。模型通过学习输入特征与标签之间的关系来进行预测或分类。监督学习的目标是最小化预测错误,使模型能够在新数据上做出准确的预测。
    • 例子: 线性回归、逻辑回归、支持向量机(SVM)、决策树。
  • 无监督学习(Unsupervised Learning): 无监督学习中,训练数据没有标签,模型通过分析输入数据中的结构或模式来进行学习。目标是发现数据的潜在规律,常见的任务包括聚类、降维等。
    • 例子: K-means 聚类、主成分分析(PCA)。
  • 强化学习(Reinforcement Learning): 强化学习是让智能体(Agent)通过与环境(Environment)的互动,采取行动并根据奖励或惩罚来学习最优策略。智能体的目标是通过最大化长期奖励来优化行为。
    • 例子: AlphaGo、自动驾驶、游戏AI。

总结:

  • 监督学习:有标签的训练数据,任务是预测或分类。
  • 无监督学习:没有标签的训练数据,任务是发现数据中的模式或结构。
  • 强化学习:通过与环境互动,智能体根据奖励和惩罚进行学习。

过拟合与欠拟合

  • 过拟合(Overfitting): 过拟合是指模型在训练数据上表现非常好,但在测试数据上表现很差。这通常发生在模型复杂度过高参数过多,导致模型”记住”了训练数据中的噪声或偶然性,而不具备泛化能力。过拟合的模型无法有效应对新数据。
  • 欠拟合(Underfitting): 欠拟合是指模型在训练数据上和测试数据上都表现不佳,通常是因为模型过于简单,无法捕捉数据中的复杂模式。欠拟合的模型无法从数据中学习到有用的规律。

解决方法:

  • 过拟合:可以通过简化模型、增加训练数据或使用正则化等方法来缓解。
  • 欠拟合:可以通过增加模型复杂度或使用更复杂的算法来改进。

训练与测试误差

  • 训练误差(Training Error): 训练误差是模型在训练数据上的表现,反映了模型是否能够很好地适应训练数据。如果训练误差很大,可能说明模型不够复杂,拟合;如果训练误差很小,可能说明模型太复杂,容易拟合。
  • 测试误差(Test Error): 测试误差是模型在未见过的数据上的表现,反映了模型的泛化能力。测试误差应当与训练误差相匹配,若测试误差远高于训练误差,通常是拟合。

总结:

  • 训练误差和测试误差的差距可以帮助我们判断模型的适应性。
  • 理想的情况是训练误差和测试误差都较小,并且相对接近

评估指标

根据任务的不同,机器学习模型的评估指标也不同。以下是常用的一些评估指标:

  • 准确率(Accuracy): 分类任务中,正确分类的样本占总样本的比例。
  • 精确率(Precision)和召回率(Recall): 主要用于处理不平衡数据集,精确率衡量的是被模型预测为正类的样本中,有多少是真正的正类;召回率衡量的是所有实际正类,有多少被模型正确识别为正类。
  • F1 分数: 精确率与召回率的调和平均数,用于综合考虑模型的表现。
  • 均方误差(MSE): 回归任务中,预测值与真实值之间差异的平方的平均值。

总结:
评估指标帮助我们衡量模型的表现,选择最合适的指标可以根据任务的需求来进行。

深度学习(Deep Learning)入门——基本概念

引言

本文是该系列文章中的第一篇,旨在介绍深度学习基础概念、优化算法、 调参基本思路、正则化方式等,后续文章将关注深度学习在自然语言处理、语音识别、和计算机视觉领域的应用。

基本概念

深度学习是为了解决表示学习难题而被提出的。本节,我们介绍这些深度学习相关的基本概念。

表示学习(representation learning) 机器学习旨在自动地学到从数据的表示(representation)到数据的标记(label)的映射。随着机器学习算法的日趋成熟,人们发现,在某些领域(如图像、语音、文本等),如何从数据中提取合适的表示成为整个任务的瓶颈所在,而数据表示的好坏直接影响后续学习任务(所谓garbage in,garbage out)。与其依赖人类专家设计手工特征(难设计还不见得好用),表示学习希望能从数据中自动地学到从数据的原始形式到数据的表示之间的映射。

深度学习(deep learning,DL) 表示学习的理想很丰满,但实际中人们发现从数据的原始形式直接学得数据表示这件事很难。深度学习是目前最成功的表示学习方法,因此,目前国际表示学习大会(ICLR)的绝大部分论文都是关于深度学习的。深度学习是把表示学习的任务划分成几个小目标,先从数据的原始形式中先学习比较低级的表示,再从低级表示学得比较高级的表示。这样,每个小目标比较容易达到,综合起来我们就完成表示学习的任务。这类似于算法设计思想中的分治法divide-and-conquer)。

深度神经网络(deep neural networks,DNN) 深度学习目前几乎唯一行之有效的实现形式。简单的说,深度神经网络就是很深的神经网络。我们利用网络中逐层对特征进行加工的特性,逐渐从低级特征提取高级特征。除了深度神经网络之外,有学者在探索其他深度学习的实现形式,比如深度森林。

深度神经网络目前的成功取决于三大推动因素。1. 大数据。当数据量小时,很难从数据中学得合适的表示,而传统算法+特征工程往往能取得很好的效果;2. 计算能力大的数据大的网络需要有足够的快的计算能力才能使得模型的应用成为可能。3. 算法创新。现在很多算法设计关注在如何使网络更好地训练、更快地运行、取得更好的性能。

多层感知机(multi-layer perceptrons,MLP) 多层全连接层组成的深度神经网络。多层感知机最后一层全连接层实质上是一个线性分类器,而其他部分则是为这个线性分类器学习一个合适的数据表示,使倒数第二层的特征线性可分

激活函数(activation function)神经网络的必要组成部分。如果没有激活函数,多次线性运算的堆叠仍然是一个线性运算,即不管用再多层实质只起到了一层神经网络的作用。一个好的激活函数应满足以下性质。1. 不会饱和。sigmoid和tanh激活函数在两侧尾端会有饱和现象,这会使导数在这些区域接近零,从而阻碍网络的训练。2. 零均值。ReLU激活函数的输出均值不为零,这会影响网络的训练。3. 容易计算

迁移学习(transfer learning) 深度学习下的迁移学习旨在利用源任务数据辅助目标任务数据下的学习。迁移学习适用于源任务数据比目标任务数据多,并且源任务中学习得到的低层特征可以帮助目标任务的学习的情形。在计算机视觉领域,最常用的源任务数据是ImageNet。对ImageNet预训练模型的利用通常有两种方式。1. 固定特征提取器。用ImageNett预训练模型提取目标任务数据的高层特征。2. 微调(fine-tuning)。以ImageNet预训练模型作为目标任务模型的初始化初始化权值,之后在目标任务数据上进行微调

多任务学习(multi-task learning) 与其针对每个任务训练一个小网络,深度学习下的多任务学习旨在训练一个大网络以同时完成全部任务。这些任务中用于提取低层特征的层是共享的,之后产生分支,各任务拥有各自的若干层用于完成其任务。多任务学习适用于多个任务共享低层特征,并且各个任务的数据很相似的情况。

端到端学习(end-to-end learning) 深度学习下的端到端学习旨在通过一个深度神经网络直接学习从数据的原始形式到数据的标记的映射。端到端学习并不应该作为我们的一个追求目标,是否要采用端到端学习的一个重要考虑因素是:有没有足够的数据对应端到端的过程,以及我们有没有一些领域知识能够用于整个系统中的一些模块。

优化算法

网络结构确定之后,我们需要对网络的权值(weights)进行优化。本节,我们介绍优化深度神经网络的基本思想。

梯度下降(gradient descent,GD) 想象你去野足但却迷了路,在漆黑的深夜你一个人被困住山谷中,你知道谷底是出口但是天太黑了根本看不清楚路。于是你确定采取一个贪心(greedy)算法:先试探在当前位置往哪个方向走下降最快(即梯度方向),再朝着这个方向走一小步,重复这个过程直到你到达谷底。这就是梯度下降的基本思想。

梯度下降算法的性能大致取决于三个因素。1. 初始位置。如果你初始位置就离谷底很近,自然很容易走到谷底。2. 山谷地形。如果山谷是“九曲十八弯”,很有可能你在里面绕半天都绕不出来。3. 步长。你每步迈多大,当你步子迈太小,很可能你走半天也没走多远,而当你步子迈太大,一不小心就容易撞到旁边的悬崖峭壁,或者错过了谷底。

误差反向传播(error back-propagation,BP) 结合微积分中链式法则和算法设计中动态规划思想用于计算梯度。 直接用纸笔推导出中间某一层的梯度的数学表达式是很困难的,但链式法则告诉我们,一旦我们知道后一层的梯度,再结合后一层对当前层的导数,我们就可以得到当前层的梯度动态规划是一个高效计算所有梯度的实现技巧,通过由高层往低层逐层计算梯度,避免了对高层梯度的重复计算。

滑动平均(moving average) 要前进的方向不再由当前梯度方向完全决定,而是最近几次梯度方向的滑动平均。利用滑动平均思想的优化算法有带动量(momentum)的SGD、Nesterov动量、Adam(ADAptive Momentum estimation)等。

自适应步长 自适应地确定权值每一维的步长。当某一维持续震荡时,我们希望这一维的步长小一些;当某一维一直沿着相同的方向前进时,我们希望这一维的步长大一些。利用自适应步长思想的优化算法有AdaGrad、RMSProp、Adam等。

学习率衰减 当开始训练时,较大的学习率可以使你在参数空间有更大范围的探索;当优化接近收敛时,我们需要小一些的学习率使权值更接近局部最优点。

深度神经网络优化的困难 有学者指出,在很高维的空间中,局部最优是比较少的,而大部分梯度为零的点是鞍点。平原区域的鞍点会使梯度在很长一段时间内都接近零,这会使得拖慢优化过程。

初始化

权值初始化对网络优化至关重要。早年深度神经网络无法有效训练的一个重要原因就是早期人们对初始化不太重视。本节,我们介绍几个适用于深度神经网络的初始化方法。

初始化的基本思想 方差不变,即设法对权值进行初始化,使得各层神经元的方差保持不变

Xavier初始化 从高斯分布或均匀分布中对权值进行采样,使得权值的方差是1/n,其中n是输入神经元的个数。该推导假设激活函数是线性的。

He初始化/MSRA初始化 从高斯分布或均匀分布中对权值进行采样,使得权值的方差是2/n。该推导假设激活函数是ReLU。因为ReLU会将小于0的神经元置零,大致上会使一半的神经元置零,所以为了弥补丢失的这部分信息,方差要乘以2

批量规范化(batch-normalization,BN) 每层显式地对神经元的激活值做规范化,使其具有零均值和单位方差。批量规范化使激活值的分布固定下来,这样可以使各层更加独立地进行学习。批量规范化可以使得网络对初始化和学习率不太敏感。此外,批量规范化有些许正则化的作用,但不要用其作为正则化手段。

偏差/方差(bias/variance)

优化完成后,你发现网络的表现不尽如人意,这时诊断网络处于高偏差/高方差状态是对你下一步调参方向的重要指导。与经典机器学习算法有所不同,因为深度神经网络通常要处理非常高维的特征,所以网络可能同时处于高偏差/高方差的状态,即在特征空间的一些区域网络处于高偏差,而在另一些区域处于高方差。本节,我们对偏差/方差作一简要介绍。

img

偏差 偏差度量了网络的训练集误差贝叶斯误差(即能达到的最优误差)的差距。高偏差的网络有很高的训练集误差,说明网络对数据中隐含的一般规律还没有学好。当网络处于高偏差时,通常有以下几种解决方案。1. 训练更大的网络。网络越大,对数据潜在规律的拟合能力越强2. 更多的训练轮数。通常训练时间越久,对训练集的拟合能力越强3. 改变网络结构。不同的网络结构对训练集的拟合能力有所不同。

方差 方差度量了网络的验证集误差训练集误差的差距。方差的网络学习能力太强,把训练集中自身独有的一些特点也当作一般规律学得,使网络不能很好的泛化(generalize)到验证集。当网络处于高方差时,通常有以下几种解决方案。1. 更多的数据。这是对高方差问题最行之有效的解决方案。2. 正则化3. 改变网络结构。不同的网络结构对方差也会有影响。

正则化(regularization)

正则化是解决高方差问题的重要方案之一。本节,我们将对常用正则化方法做一介绍。

正则化的基本思想 正则化的基本思想是使网络的有效大小变小。网络变小之后,网络的拟合能力随之降低,这会使网络不容易过拟合到训练集。

L2正则化 L2正则化倾向于使网络的权值接近0。这会使一层神经元对一层神经元的影响降低,使网络变得简单,降低网络的有效大小,降低网络的拟合能力。L2正则化实质上是对权值做线性衰减,所以L2正则化也被称为权值衰减(weight decay)。

随机失活(dropout) 在训练时,随机失活随机选择一部分神经元,使其置零,不参与本次优化迭代。随机失活减少了每次参与优化迭代的神经元数目,使网络的有效大小变小。随机失活的作用有两点。1. 降低神经元之间耦合。因为神经元会被随机置零,所以每个神经元不能依赖于其他神经元,这会迫使每个神经元自身要能提取到合适的特征。2. 网络集成。随机失活可以看作在训练时每次迭代定义出一个新的网络,这些网络共享权值。在测试时的网络是这些网络的集成。

数据扩充(data augmentation 这实质是获得更多数据的方法。当收集数据很昂贵,或者我们拿到的是第二手数据,数据就这么多时,我们从现有数据中扩充生成更多数据,用生成的“伪造”数据当作更多的真实数据进行训练。以图像数据做分类任务为例,把图像水平翻转、移动一定位置、旋转一定角度、或做一点色彩变化等,这些操作通常都不会影响这幅图像对应的标记。并且你可以尝试这些操作的组合,理论上讲,你可以通过这些组合得到无穷多的训练样本。

早停(early stopping) 随着训练的进行,当你发现验证集误差不再变化或者开始上升时,提前停止训练。

调参技巧

深度神经网络涉及很多的超参数,如学习率大小L2正则化系数动量大小批量大小隐层神经元数目层数学习率衰减率等。本节,我们介绍调参的基本技巧。

随机搜索 由于你事先并不知道哪些超参数对你的问题更重要,因此随机搜索通常是比网格搜索(grid search)更有效的调参策略

对数空间搜索 对于隐层神经元数目层数,可以直接从均匀分布采样进行搜索。而对于学习率、L2正则化系数、和动量,在对数空间搜索更加有效。例如:

1
2
3
4
import random
learning_rate = 10 ** random.uniform(-5, -1) # From 1e-5 to 1e-1
weight_decay = 10 ** random.uniform(-7, -1) # From 1e-7 to 1e-1
momentum = 1 - 10 ** random.uniform(-3, -1) # From 0.9 to 0.999

实现技巧

图形处理单元(graphics processing units, GPU) 深度神经网络的高效实现工具。简单来说,CPU擅长串行、复杂的运算,而GPU擅长并行、简单的运算。深度神经网络中的矩阵运算都十分简单,但计算量巨大。因此,GPU无疑具有非常强大的优势。

向量化(vectorization) 代码提速基本技巧。能少写一个for循环就少写一个,能少做一次矩阵运算就少做一次。实质是尽量将多次标量运算转化为一次向量运算;将多次向量运算转化为一次矩阵运算。因为矩阵运算可以并行,这将会比多次单独运算快很多。

计算机视觉基础

一、计算机视觉概述

1、计算机视觉的背景知识
对计算机视觉的第一印象:用计算机代替人类的眼睛,模仿人类视觉去完成各项任务

计算机视觉(Computer Vision) 是一门研究如何使机器“看”的科学,也可以看作是研究如何使人工系统从图像或多维数据中“感知”的科学。

终极目标:计算机视觉成为机器认知世界的基础,终极目的是使得计算机能够像人一样“看懂世界”。
2、计算机视觉与人类视觉的关系

在这里插入图片描述

二、计算机视觉的基本原理

1、计算机视觉的处理对象

数字图像

又称为数码图像或数位图像;
是用一个数字矩阵来表达客观物体的图像;
是由模拟图像数字化得到的;
是一个离散采样点的集合,每个点具有其各自的属性
以像素为基本元素的图像;
可以用数字计算机或数字电路存储和处理的图像。
数字图像处理包括的内容:

图像变换;
图像增强;
图像恢复;
图像压缩编码;
图像分割;
图像分析与描述;
图像的识别分类。
2、计算机视觉的工作原理
图像数字化的两个过程:

采样是将空间上连续的图像变换成离散的点,采样频率越高,还原的图像越真实
量化是将采样出来的像素点转换成离散的数量值,一幅数字图像中不同灰度值的个数称为灰度等级,级数越大,图像越清晰
计算机视觉的基础工作原理:
①构造多层神经网络 —> ②较低层识别初级的图像特征 —> ③若干底层特征组成更上一层特征 —> ④通过多个层级的组合 —> ⑤最终在顶层做出分类

3、计算机视觉的关键技术
图像分类:给定一组各自被标记为单一类别的图像,对一组新的测试图像的类别进行预测,并测量预测的准确性结果。

目标检测:给定一张图像,让计算机找出其中所有目标的位置,并给出每个目标的具体类别。

语义分割:将整个图像分成像素组,然后对像素组进行标记和分类;语义分割是在语义上理解图中每个像素是什么,还须确定每个物体的边界。如一张“人驾驶摩托车行驶在林间小道上”的图片

实例分割:在语义分割的基础上进行,将多个重叠物体和不同背景的复杂景象进行分类;同时确定对象的边界、差异和彼此之间的关系。

视频分类:分类的对象是由多帧图像构成的、包含语音数据、运动信息等的视频对象需要理解每帧图像包含内容,还需要知道上下文关联信息。

人体关键点检测:通过人体关键节点的组合和追踪来识别人的运动和行为对于描述人体姿态,预测人体行为至关重要。

场景文字识别:在图像背景复杂、分辨率低下、字体多样、分布随意等情况下,将图像信息转化为文字序列的过程。

目标跟踪任务:在特定场景跟踪某一个或多个特定感兴趣对象的过程。

三、图像分类基础

1、图像分类的定义

图像分类的定义:图像分类的核心是从给定的分类集合中给图像分配一个标签

在这里插入图片描述

图像分类:
1、根据大类、小类加标签
2、可以多单个或多个标签
3、不同的标签粒度和个数会形成不同的分类任务

2、图像分类的类别
单标签与多标签分类的区别
单标签:数据样本属于一个大类的;数据进行分类后用可以用一个值代表;单标签内有二分类(两个选项)和多分类(多个选项);例子:单标签三个样本的二分类整形(0/1)输出为:[0,1,0]。

多标签数据样本可以划分到几个大的不冲突主题类别中;在大主题中分别可以进行二分类和多分类问题;例子:多标签(假设为两个标签)三个样本的二分类整形输出为:[[0,1], [0,0],[1,1]]。

跨物种语义级别的图像分类定义

在不同物种层次上识别不同类别的对象,如猫狗分类;
各个类别之间属于不同的物种或大类,往往具有 较大的类间方差,而类内具有 较小的类内方差;
多类别图像分类由传统的特征提取方法转到数据驱动的深度学习方向来,取得了较大进展。
子类细粒度图像分类的定义

子类细粒度分类相较于跨物种图像分类难度更大;
是一个大类中的子类的分类,如不同鸟的分类等;
在区分出基本类别的基础上,进行更精细的子类划分;
由于图像之间具有更加相似的外观和特征,受采集过程中存在干扰影响,导致数据呈现类间差异性大,类内间差异小,分类难度也更高。在这里插入图片描述

多标签图像分类的定义

  • 给每个样本一系列的目标标签,表示的是样本各属性且不相互排斥的,预测出一个概念集合;
  • 标签数量较大且复杂;
  • 标签的标准很难统一,且往往类标之间相互依赖并不独立;
  • 标注的标签并不能完美覆盖所有概念面;
  • 标签往往较短语义少,理解困难。

在这里插入图片描述

图像分类会遇到的问题
1、一张图片包含的信息内容太多,不好分类
2、有些类别的图片太少,比如罕见害虫

3、图像分类遇到的挑战
虽然图像分类在大赛上的正确率已经接近极限,但在实际工程应用中,面临诸多挑战。如:类别不均衡;数据集小;巨大的类内差异;实际应用环境复杂

4、图像分类的常用数据集与网络
图像分类的常用数据集:CIFAR-10

介绍

CIFAR-10 :一个用于识别普适物体的小型图像数据集;
包含6万张大小为32 x 32的彩色图像;
共有10个类,每类有6000张图;
共5万张图组成训练集合,训练集合中每一类均等且有5000张图;
共1万张图组成测试集合,测试集合中每一类均等且有1000张图;
10个类别:飞机( airplane )、汽车( automobile )、鸟类( bird )、猫( cat )、鹿( deer )、狗( dog )、蛙类( frog )、马( horse )、船( ship )和卡车( truck );
完全互斥的:在⼀个类别中出现的图⽚不会出现在其它类中。使用的相关神经网络:LeNet-5、AlexNet
LeNet-5:是最早的卷积神经网络之一; 1998年第一次将LeNet-5应用到图像分类上,在手写数字识别任务中取得了巨大成功; LeNet-5通过连续使用卷积和池化层的组合提取图像特征,总共5层:3层卷积和2层全连接,池化层未计入层数; LeNet-5是卷积神经网络的开篇大作,完成了卷积神经网络从无到有的突破。
AlexNet:AlexNet将LeNet的思想发扬光大,把CNN的基本原理应用到了很深很宽的网络中。成功使用ReLU作为CNN的激活函数,并验证其效果优异;训练时使用数据增强和Dropout随机忽略一部分神经元,以避免模型过拟合,提升泛化能力;在CNN中使用重叠的最大池化,提升了特征的丰富性;提出了LRN层,增强了模型的泛化能力。

5、图像分类的典型应用
图像分类在图片搜索引擎中的应用

应用图像分类技术可以开发各种图片搜索引擎;
图片搜索引擎能通过用户上传图片,应用图像分类技术,识别出图片的内容并进行分类;
搜索互联网上与这张图片相同或相似信息的其他图片资源进行校对和匹配,识别图片的内容并提供相关信息。
图像分类在垃圾分类中的应用-智能环卫

为了破解传统分类投放模式可能存在的乱扔垃圾等问题,可在传统垃圾分类投放站点部署摄像头进行智能化改造;
阿里云提出“智能环卫”产品,提供垃圾分类投放点AI智能检测分析功能;
有效针对垃圾桶内的未破袋垃圾包、残余垃圾袋等进行检测和识别,检测效率高,真实环境下检测准确率超过95%。
四、目标检测基础
目标检测:框出来,用坐标表示

1、目标检测的定义
目标检测的定义:目标检测就是识别图中有哪些物体,确定他们的类别并标出各自在图中的位置。目标检测模型读取该图片;寻找识别出图中的物体目标,对其进行定位,框起和标注。

图像分类与目标检测的区别
图像分类:整幅图像经过识别后被分类为单一的标签。
目标检测:除了识别出图像中的一个或多个目标,还需要找出目标在图像中的具体位置

2、目标检测的评估指标
交并比:IoU

真实边界框:训练集中,人工标注的物体边界框;
预测边界框:模型预测到的物体边界框;
交并比:在分子项中,是真实边界框和预测边界框重叠的区域(Intersection)。分母是一个并集(Union),或者更简单地说,是由预测边界框和真实边界框所包括的区域。两者相除就得到了最终的得分
精确度(Precision)指目标检测模型判断该图片为正类,该图片确实是正类的概率

和召回率(Recall)是指的是一个分类器能把所有的正类都找出来的能力;

平均精度值:mAP :mAP,mean Average Precision, 即各类别平均精度均值;mAP是把每个类别的AP都单独拿出来,然后计算所有类别AP的平均值,代表着对检测到的目标平均精度的一个综合评价。每一个类别都可以根据Recall和Precision绘制一条曲线,那么AP就是该曲线下的面积,而mAP则是多个类别AP的平均值,这个值介于0到1之间。mAP是目标检测算法里最重要的一个评估指标。

3、目标检测遇到的挑战
目标量问题:在图片输入模型前不清楚图片中有多少个目标,无法知道正确的输出数量。
目标大小问题:目标的大小不一致,甚至一些目标仅有十几个像素大小,占原始图像中非常小的比例。
如何建模:需要同时处理目标定位以及目标物体识别分类这两个问题。

4、目标检测的常用数据集与网络
目标检测的常用数据集:PASCAL VOC

PASCAL VOC :一个常用于目标检测的小型图像数据集;
包含11530张彩色图像,标定了27450个目标识别区域;
从初始4个类发展成最终的20个类;
在整个数据集中,平均每张图片有2.4个目标;
20个类别:
动物:人、鸟、猫、狗、牛、马、羊;
运载工具:飞机、自行车、船、巴士、汽车、摩托车、火车;
物品:瓶子、椅子、餐桌、盆栽、沙发、电视机。

使用的相关神经网络:CenterNet

CenterNet结构优雅简单,直接检测目标的中心点和大小;
CenterNet把目标检测任务看作三个部分:寻找物体的中心点;计算物体中心点的偏移量;分析物体的大小;
CenterNet检测速度和精度相比于先前的框架都有明显且可观的提高,尤其是与著名的目标检测网络YOLOv3作比较,在相同速度的条件下,CenterNet的精度比YOLOv3提高了大约4个点。
5、目标检测的典型应用
智慧交通是目标检测的一个重要应用领域,主要包括如下场景:

检测各种交通异常事件,如车辆占用应急车道、车辆驾驶员的驾驶行为等;
第一时间将异常事件上报给交管部门,提高处理效率。
目标检测在智慧交通中的应用-智慧眼

通过目标检测算法,对道路视频图像进行分析;
根据分析车流量,调整红绿灯配时策略,提升交通通行能力。
五、图像分割基础
“抠图软件”的操作流程?

1、选中图片中的目标主体
2、对主体的边界进行分割
3、主体与背景分离,突出显示主体
1、图像分割的定义
图像分割就是把图像分成若干个特定的、具有独特性质的区域并提出感兴趣目标的技术和过程;

图像分割包括:语义分割、实例分割和全景分割。

图像作为分割算法的输入,输出一组区域;

区域可以表示为一种掩码(灰度或颜色),其中每个部分被分配一个唯一的颜色或灰度值来代表它

2、图像分割的类别
语义分割的定义:

语义分割是在像素级别上的分类,属于同一类的像素都要被归为一类;
语义分割是从像素级别来理解图像的。
实例分割的定义:

实例分割比语义分割更进一步;
对于语义分割来说,只要将所有同类别(猫、狗)的像素都归为一类;
实例分割还要在具体类别(猫、狗)像素的基础上区分开不同的实例(短毛猫、虎斑猫、贵宾犬、柯基犬)。
全景分割的定义

全景分割是语义和实例分割的相结合
每个像素都被分配一个类(比如:狗),如果一个类有多个实例,则可知道该像素属于该类的哪个实例(贵宾犬/柯基犬)。
3、图像分割遇到的挑战
分割边缘不准:因为相邻临的像素对应感受野内的图像信息太过相似导致。

样本质量不一:样本中的目标物体具有多姿态、多视角问题,会出现物体之间的遮挡和重叠;
受场景光照影响,样本质量参差不齐。

标注成本高:对于数据样本的标注成本非常高,而且标注质量难以保证不含有噪声。

4、图像分割的常用数据集与网络
图像分割的常用数据集:COCO

COCO:一个常用于图像分割的大型图像数据集;
包含33万张彩色图像,标定了50万个目标实例;
具有80个目标类、91个物品类以及25万个人物关键点标注;
每张图片包含5个描述;
每一类的图像多,利于提升识别更多类别位于特定场景的能力;
类别包括: person(人) 、bicycle(自行车) 、car(汽车) 、motorbike(摩托车) 、aeroplane(飞机) 、bus(公共汽车) 、train(火车)、truck(卡车) 、boat(船) 、traffic light(信号灯) 、fire hydrant(消防栓) 、stop sign(停车标志) 、parking meter(停车计费器) 、bench(长凳) 、bird(鸟) 、cat(猫) 、dog(狗) 、horse(马) 、sheep(羊) 、cow(牛) 等等。
使用的相关神经网络:FCN

FCN全卷积神经网络是图像分割的基础网络;
全卷积神经网络,顾名思义网络里的所有层都是卷积层
卷积神经网络卷到最后特征图尺寸和分辨率越来越小,不适合做图像分割,为解决此问题FCN引入- 上采样的方法,卷积完之后再上采样到大尺寸图;
为避免层数不断叠加后原图的信息丢失得比较多,FCN引入一个跳层结构,把前面的层特征引过来- 进行叠加;
FCN实现了端到端的网络
端到端学习是一种解决问题的思路,与之对应的是多步骤解决问题,也就是将一个问题拆分为多个步骤分步解决,而端到端是由输入端的数据直接得到输出端的结果。

5、图像分割的典型应用
图像分割在抠图软件中的应用

应用图像分割技术可以开发各种抠图软件;
用户在软件平台上传图片,应用图像分割技术,分辨出图片具有独特特征的区域并进行边缘识别分割;
返回给用户经过图像分割处理的结果图片。
图像分割在智能证件照中的应用

基于智能视觉生产的人像分割能力,阿里云为用户提供证件照的智能制作与编辑能力;
自动从上传的生活照中分割出人像区域,精确到像素级别的分割保证证件照的专业性与准确性,将生活照完美转换成专业证件照。

[NLP] 自然语言处理 —- NLP入门指南

NLP的全称是Natuarl Language Processing,中文意思是自然语言处理,是人工智能领域的一个重要方向

自然语言处理(NLP)的一个最伟大的方面是跨越多个领域的计算研究,从人工智能到计算语言学的多个计算研究领域都在研究计算机与人类语言之间的相互作用。它主要关注计算机如何准确并快速地处理大量的自然语言语料库。什么是自然语言语料库?它是用现实世界语言表达的语言学习,是从文本和语言与另一种语言的关系中理解一组抽象规则的综合方法。

人类语言是抽象的信息符号,其中蕴含着丰富的语义信息,人类可以很轻松地理解其中的含义。而计算机只能处理数值化的信息,无法直接理解人类语言,所以需要将人类语言进行数值化转换。不仅如此,人类间的沟通交流是有上下文信息的,这对于计算机也是巨大的挑战。

我们首先来看看NLP的任务类型,如下图所示:

img

主要划分为了四大类:

类别到序列
序列到类别
同步的序列到序列
异步的序列到序列
其中“类别”可以理解为是标签或者分类,而“序列”可以理解为是一段文本或者一个数组。简单概况NLP的任务就是从一种数据类型转换成另一种数据类型的过程,这与绝大多数的机器学习模型相同或者类似,所以掌握了NLP的技术栈就等于掌握了机器学习的技术栈。

传统方式和深度学习方式 NLP 对比

img

NLP的预处理

为了能够完成上述的NLP任务,我们需要一些预处理,是NLP任务的基本流程。预处理包括:收集语料库、文本清洗、分词、去掉停用词(可选)、标准化和特征提取等。

img

图中红色的部分就是NLP任务的预处理流程,有别于其它机器学习任务的流程

英文 NLP 语料预处理的 6 个步骤

  1. 分词 – Tokenization
  2. 词干提取Stemming
  3. 词形还原 – Lemmatization
  4. 词性标注 – Parts of Speech
  5. 命名实体识别 – NER
  6. 分块 – Chunking

中文 NLP 语料预处理的 4 个步骤

  1. 中文分词 – Chinese Word Segmentation
  2. 词性标注 – Parts of Speech
  3. 命名实体识别 – NER
  4. 去除停用词

第1步:收集您的数据—-语料库

对于NLP任务来说,没有大量高质量的语料,就是巧妇难为无米之炊,是无法工作的。

而获取语料的途径有很多种,最常见的方式就是直接下载开源的语料库,如:维基百科的语料库。

但这样开源的语料库一般都无法满足业务的个性化需要,所以就需要自己动手开发爬虫抓取特定的内容,这也是一种获取语料库的途径。当然,每家互联网公司根据自身的业务,也都会有大量的语料数据,如:用户评论、电子书、商品描述等等,都是很好的语料库。

示例数据源

每个机器学习问题都从数据开始,例如电子邮件,帖子或推文列表。常见的文字信息来源包括:

产品评论(在亚马逊,Yelp和各种应用商店)
用户生成的内容(推文,Facebook帖子,StackOverflow问题)
故障排除(客户请求,支持服务单,聊天记录)
现在,数据对于互联网公司来说就是石油,其中蕴含着巨大的商业价值。所以,小伙伴们在日常工作中一定要养成收集数据的习惯,遇到好的语料库一定要记得备份(当然是在合理合法的条件下),它将会对你解决问题提供巨大的帮助。

第2步:清理数据 —- 文本清洗
我们遵循的首要规则是:“您的模型将永远与您的数据一样好。”

数据科学家的关键技能之一是了解下一步是应该对模型还是数据进行处理。一个好的经验法则是首先查看数据然后进行清理。一个干净的数据集将允许模型学习有意义的功能,而不是过度匹配无关的噪音。

我们通过不同的途径获取到了想要的语料库之后,接下来就需要对其进行清洗。因为很多的语料数据是无法直接使用的,其中包含了大量的无用符号、特殊的文本结构。

数据类型分为:

结构化数据:关系型数据、json等
半结构化数据:XML、HTML等
非结构化**数据:Word、PDF、文本、日志等
需要将原始的语料数据转化成易于处理的格式,一般在处理HTML、XML时,会使用Python的lxml库,功能非常丰富且易于使用。对一些日志或者纯文本的数据,我们可以使用正则表达式进行处理。

正则表达式是使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。Python的示例代码如下:

1
2
3
4
5
6
7
8
9
10
import re
# 定义中文字符的正则表达式
re_han_default = re.compile("([\u4E00-\u9FD5]+)", re.U)
sentence = "我/爱/自/然/语/言/处/理"
# 根据正则表达式进行切分
blocks= re_han_default.split(sentence)
for blk in blocks:
# 校验单个字符是否符合正则表达式
if blk and re_han_default.match(blk):
print(blk)

除了上述的内容之外,我们还需要注意中文的编码问题,在windows平台下中文的默认编码是GBK(gb2312),而在linux平台下中文的默认编码是UTF-8。在执行NLP任务之前,我们需要统一不同来源语料的编码,避免各种莫名其妙的问题。

如果大家事前无法判断语料的编码,那么我推荐大家可以使用Python的chardet库来检测编码,简单易用。既支持命令行:chardetect somefile,也支持代码开发。

以下是用于清理数据的清单:

删除所有不相关的字符,例如任何非字母数字字符
令牌化通过将其分割成单个的单词文本
删除不相关的单词,例如“@”twitter提及或网址
将所有字符转换为小写,以便将诸如“hello”,“Hello”和“HELLO”之类的单词视为相同
考虑将拼写错误或交替拼写的单词组合成单个表示(例如“cool”/“kewl”/“cooool”)
考虑词开还原(将诸如“am”,“are”和“is”之类的词语简化为诸如“be”之类的常见形式)
按照这些步骤并检查其他错误后,我们可以开始使用干净的标记数据来训练模型!

第3步:分词
中英文分词的3个典型区别

区别1:分词方式不同,中文更难

英文有天然的空格作为分隔符,但是中文没有。所以如何切分是一个难点,再加上中文里一词多意的情况非常多,导致很容易出现歧义。下文中难点部分会详细说明。

区别2:英文单词有多种形态

英文单词存在丰富的变形变换。为了应对这些复杂的变换,英文NLP相比中文存在一些独特的处理步骤,我们称为词形还原(Lemmatization)词干提取(Stemming)。中文则不需要

词性还原:does,done,doing,did 需要通过词性还原恢复成 do。

词干提取:cities,children,teeth 这些词,需要转换为 city,child,tooth”这些基本形态

区别3:中文分词需要考虑粒度问题

例如「中国科学技术大学」就有很多种分法

中国科学技术大学
中国 \ 科学技术 \ 大学
中国 \ 科学 \ 技术 \ 大学
粒度越大,表达的意思就越准确,但是也会导致召回比较少。所以中文需要不同的场景和要求选择不同的粒度。这个在英文中是没有的。

中文分词是一个比较大的课题,相关的知识点和技术栈非常丰富,可以说搞懂了中文分词就等于搞懂了大半个NLP。

中文分词的3大难点
难点 1:没有统一的标准

目前中文分词没有统一的标准,也没有公认的规范。不同的公司和组织各有各的方法和规则。

难点 2:歧义词如何切分

例如「兵乓球拍卖完了」就有2种分词方式表达了2种不同的含义:

乒乓球 \ 拍卖 \ 完了
乒乓 \ 球拍 \ 卖 \ 完了
难点 3:新词的识别

信息爆炸的时代,三天两头就会冒出来一堆新词,如何快速的识别出这些新词是一大难点。比如当年「蓝瘦香菇」大火,就需要快速识别。

中文分词经历了20多年的发展,克服了重重困难,取得了巨大的进步,大体可以划分成两个阶段,如下图所示:

img

词典匹配与规则

优点:速度快、成本低

缺点:适应性不强,不同领域效果差异大

基本思想是基于词典匹配,将待分词的中文文本根据一定规则切分和调整,然后跟词典中的词语进行匹配,匹配成功则按照词典的词分词,匹配失败通过调整或者重新选择,如此反复循环即可。代表方法有基于正向最大匹配和基于逆向最大匹配及双向匹配法。

基于统计与机器学习

优点:适应性较强

缺点:成本较高,速度较慢

这类目前常用的是算法是HMM、CRF等算法,比如stanford、Hanlp分词工具是基于CRF算法。以CRF为例,基本思路是对汉字进行标注训练,不仅考虑了词语出现的频率,还考虑上下文,具备较好的学习能力,因此其对歧义词和未登录词的识别都具有良好的效果。

常见的分词器都是使用机器学习算法和词典相结合,一方面能够提高分词准确率,另一方面能够改善领域适应性。

目前,主流的中文分词技术采用的都是基于词典最大概率路径+未登录词识别(HMM)的方案,其中典型的代表就是jieba分词,一个热门的多语言中文分词包。

中文分词工具

下面排名根据 GitHub 上的 star 数排名:

Hanlp
Stanford 分词
ansj 分词器
哈工大 LTP
KCWS分词器
jieba
IK
清华大学THULAC
ICTCLAS
英文分词工具

Keras
Spacy
Gensim
NLTK
第4步:标准化
标准化是为了给后续的处理提供一些必要的基础数据,包括:去掉停用词、词汇表、训练数据等等。

当我们完成了分词之后,可以去掉停用词,如:“其中”、“况且”、“什么”等等,但这一步不是必须的,要根据实际业务进行选择,像关键词挖掘就需要去掉停用词,而像训练词向量就不需要。

词汇表是为语料库建立一个所有不重复词的列表,每个词对应一个索引值,并索引值不可以改变。词汇表的最大作用就是可以将词转化成一个向量,即One-Hot编码。

假设我们有这样一个词汇表:



自然
语言
处理
那么,我们就可以得到如下的One-Hot编码:

我: [1, 0, 0, 0, 0]
爱: [0, 1, 0, 0, 0]
自然:[0, 0, 1, 0, 0]
语言:[0, 0, 0, 1, 0]
处理:[0, 0, 0, 0, 1]
这样我们就可以简单的将词转化成了计算机可以直接处理的数值化数据了。虽然One-Hot编码可以较好的完成部分NLP任务,但它的问题还是不少的。

当词汇表的维度特别大的时候,就会导致经过One-Hot编码后的词向量非常稀疏,同时One-Hot编码也缺少词的语义信息。由于这些问题,才有了后面大名鼎鼎的Word2vec,以及Word2vec的升级版BERT。

除了词汇表之外,我们在训练模型时,还需要提供训练数据。模型的学习可以大体分为两类:

监督学习,在已知答案的标注数据集上,模型给出的预测结果尽可能接近真实答案,适合预测任务
非监督学习,学习没有标注的数据,是要揭示关于数据隐藏结构的一些规律,适合描述任务
根据不同的学习任务,我们需要提供不同的标准化数据。一般情况下,标注数据的获取成本非常昂贵,非监督学习虽然不需要花费这样的成本,但在实际问题的解决上,主流的方式还选择监督学习,因为效果更好

带标注的训练数据大概如下所示(情感分析的训练数据):

1
2
3
距离 川沙 公路 较近 公交 指示 蔡陆线 麻烦 建议 路线 房间 较为简单	__label__1
商务 大床 房 房间 很大 床有 2M 宽 整体 感觉 经济 实惠 不错 ! __label__1
半夜 没 暖气 住 ! __label__0

其中每一行就是一条训练样本,__label__0__label__1是分类信息,其余的部分就是分词后的文本数据。

第5步:特征提取
为了能够更好的训练模型,我们需要将文本的原始特征转化成具体特征,转化的方式主要有两种:统计和Embedding。

原始特征:需要人类或者机器进行转化,如:文本、图像。

具体特征:已经被人类进行整理和分析,可以直接使用,如:物体的重要、大小。

NLP表示方式
目前常用的文本表示方式分为:

离散式表示(Discrete Representation);
分布式表示(Distributed Representation);

离散式表示(Discrete Representation)

One-Hot
One-Hot 编码又称为“独热编码”或“哑编码”,是最传统、最基础的词(或字)特征表示方法。这种编码将词(或字)表示成一个向量,该向量的维度是词典(或字典)的长度(该词典是通过语料库生成的),该向量中,当前词的位置的值为1,其余的位置为0。

文本使用one-hot 编码步骤:

根据语料库创建 词典(vocabulary),并创建词和索引的 映射(stoi,itos);
将句子转换为用索引表示
创建OneHot 编码器;
使用OneHot 编码器对句子进行编码
One-Hot 编码的特点如下:

词向量长度是词典长度;
在向量中,该单词的索引位置的值为 1 ,其余的值都是 0
使用One-Hot 进行编码的文本,得到的矩阵是稀疏矩阵
缺点:

不同词的向量表示互相正交,无法衡量不同词之间的关系
该编码只能反映某个词是否在句中出现,无法衡量不同词的重要程度
使用One-Hot 对文本进行编码后得到的是高维稀疏矩阵,会浪费计算和存储资源
词袋模型(Bag Of Word,BOW)
例句:

Jane wants to go to Shenzhen.
Bob wants to go to Shanghai.
在词袋模型中不考虑语序和词法的信息,每个单词都是相互独立的,将词语放入一个“袋子”里,统计每个单词出现的频率。

词袋模型编码特点:

词袋模型是对文本(而不是字或词)进行编码;
编码后的向量长度是词典的长度
该编码忽略词出现的次序
在向量中,该单词的索引位置的值为单词在文本中出现的次数;如果索引位置的单词没有在文本中出现,则该值为 0 ;
缺点

该编码忽略词的位置信息,位置信息在文本中是一个很重要信息,词的位置不一样语义会有很大的差别(如 “猫爱吃老鼠” 和 “老鼠爱吃猫” 的编码一样);
该编码方式虽然统计了词在文本中出现的次数,但仅仅通过“出现次数”这个属性无法区分常用词(如:“我”、“是”、“的”等)和关键词(如:“自然语言处理”、“NLP ”等)在文本中的重要程度
TF-IDF(词频-逆文档频率)
为了解决词袋模型无法区分常用词(如:“是”、“的”等)和专有名词(如:“自然语言处理”、“NLP ”等)对文本的重要性的问题,TF-IDF 算法应运而生。

TF-IDF 全称是:term frequency–inverse document frequency 又称 词频-逆文本频率。其中:

统计的方式主要是计算词的词频(TF)和逆向文件频率(IDF):

TF (Term Frequency ):某个词在当前文本中出现的频率,频率高的词语或者是重要的词(如:“自然语言处理”)或者是常用词(如:“我”、“是”、“的”等);
IDF (Inverse Document frequency ):逆文本频率。文本频率是指:含有某个词的文本在整个语料库中所占的比例。逆文本频率是文本频率的倒数
那么,每个词都会得到一个TF-IDF值,用来衡量它的重要程度,计算公式如下:

img

优点

实现简单,算法容易理解且解释性较强;
从IDF 的计算方法可以看出常用词(如:“我”、“是”、“的”等)在语料库中的很多文章都会出现,故IDF的值会很小;而关键词(如:“自然语言处理”、“NLP ”等)只会在某领域的文章出现,IDF 的值会比较大;故:TF-IDF 在保留文章的重要词的同时可以过滤掉一些常见的、无关紧要的词;
缺点

不能反映词的位置信息,在对关键词进行提取时,词的位置信息(如:标题、句首、句尾的词应该赋予更高的权重);
IDF 是一种试图抑制噪声的加权,本身倾向于文本中频率比较小的词,这使得IDF 的精度不高;
TF-IDF 严重依赖于语料库(尤其在训练同类语料库时,往往会掩盖一些同类型的关键词;如:在进行TF-IDF 训练时,语料库中的 娱乐 新闻较多,则与 娱乐 相关的关键词的权重就会偏低 ),因此需要选取质量高的语料库进行训练;

分布式表示(Distributed Representation

理论基础:

1954年,Harris提出分布式假说(distributional hypothesis)奠定了这种方法的理论基础:A word’s meaning is given by the words that frequently appear close-by(上下文相似的词,其语义也相似);
1957年,Firth对分布式假说做出进一步的阐述和明确:A word is characterized by the company it keeps(词的语义由其上下文决定);
n-gram
n-gram 是一种 语言模型(Language Model, LM)。语言模型是一种基于概率的判别式模型,该模型的输入是一句话(单词的序列),输出的是这句话的概率,也就是这些单词的联合概率(joint probability)。(备注:语言模型就是判断一句话是不是正常人说的。)

共现矩阵(Co-Occurrence Matrix)
首先指定窗口大小,然后统计窗口(和对称窗口)内词语共同出现次数作为词的向量(vector)。

语料库:

I like deep learning.
I like NLP.
I enjoy flying.
备注: 指定窗口大小为1(即:左右的 window_length=1,相当于 bi-gram)统计数据如下:(I, like),(Iike, deep),(deep, learning),(learning, .),(I, like),(like, NLP),(NLP, .),(I, enjoy),(enjoy, flying), (flying, .)。则语料库的共现矩阵如下表所示:

img

从以上的共现矩阵可以看出,单词 like 和 enjoy 都在单词 I 附件出现且统计数目大概相等,则它们在 语义 和 语法 上的含义大概相同

优点

考虑了句子中词的顺序
缺点

词表的长度很大,导致词的向量长度也很大;
共现矩阵也是稀疏矩阵(可以使用 SVD、PCA 等算法进行降维,但是计算量很大);
Word2Vec
word2vec 模型是Google团队在2013年发布的 word representation 方法。该方法一出让 预训练词向量 的使用在NLP 领域遍地开花。

word2vec模型

word2vec有两种模型:CBOW 和 SKIP-GRAM;

  • CBOW:利用上下文的词预测中心词;

img

  • SKIP-GRAM:利用中心词预测上下文的词;

img

优点

  1. 考虑到词语的上下文,学习到了语义和语法的信息;
  2. 得到的词向量维度小,节省存储和计算资源;
  3. 通用性强,可以应用到各种NLP 任务中;

缺点

  1. 词和向量是一对一的关系,无法解决多义词的问题;
  2. word2vec是一种静态的模型,虽然通用性强,但无法真的特定的任务做动态优化;

GloVe
GloVe 是斯坦福大学Jeffrey、Richard 等提供的一种词向量表示算法,GloVe 的全称是Global Vectors for Word Representation,是一个基于全局词频统计(count-based & overall staticstics)的词表征(word representation)算法。该算法综合了global matrix factorization(全局矩阵分解) 和 local context window(局部上下文窗口) 两种方法的优点。

效果

img

优点

考虑到词语的上下文、和全局语料库的信息,学习到了语义和语法的信息;
得到的词向量维度小,节省存储和计算资源;
通用性强,可以应用到各种NLP 任务中;
缺点

词和向量是一对一的关系,无法解决多义词的问题;
glove也是一种静态的模型,虽然通用性强,但无法真的特定的任务做动态优化;
ELMO
word2vec 和 glove 算法得到的词向量都是静态词向量(静态词向量会把多义词的语义进行融合,训练结束之后不会根据上下文进行改变),静态词向量无法解决多义词的问题(如:“我今天买了7斤苹果” 和 “我今天买了苹果7” 中的 苹果 就是一个多义词)。而ELMO模型进行训练的词向量可以解决多义词的问题。

ELMO 的全称是“ Embedding from Language Models ”,这个名字不能很好的反映出该模型的特点,提出ELMO 的论文题目可以更准确的表达出该算法的特点“ Deep contextualized word representation ”。

该算法的精髓是:用语言模型训练神经网络,在使用word embedding 时,单词已经具备上下文信息,这个时候神经网络可以根据上下文信息对word embedding 进行调整,这样经过调整之后的word embedding 更能表达在这个上下文中的具体含义,这就解决了静态词向量无法表示多义词的问题。

网络模型

img

过程

上图中的结构使用字符级卷积神经网络(convolutional neural network, CNN)来将文本中的词转换成原始词向量(raw word vector)
将原始词向量输入双向语言模型中第一层 ;
前向迭代中包含了该词以及该词之前的一些词汇或语境的信息(即上文);
后向迭代中包含了该词以及该词之后的一些词汇或语境的信息(即下文) ;
这两种迭代的信息组成了中间词向量(intermediate word vector)
中间词向量被输入到模型的下一层
最终向量就是原始词向量和两个中间词向量的加权和

img

如上图所示:

  • 使用glove训练的词向量中,与 play 相近的词大多与体育相关,这是因为语料中与play相关的语料多时体育领域的有关;
  • 在使用elmo训练的词向量中,当 play 取 演出 的意思时,与其相近的也是 演出 相近的句子

Pytorch

img

PyTorch 是一个开源的机器学习库,主要用于进行计算机视觉(CV)自然语言处理(NLP)、语音识别等领域的研究和开发。

PyTorch 以其灵活性和易用性而闻名,特别适合于深度学习研究和开发。

谁适合阅读本教程?

只要您具备编程的基础知识,您就可以阅读本教程,学习 PyTorch 适合对深度学习和机器学习感兴趣的人,包括数据科学家、工程师、研究人员和学生。

阅读本教程前,您需要了解的知识:

在您开始阅读本教程之前,您必须具备的基础知识包括 Python 编程、基础数学(线性代数、概率论、微积分)、机器学习的基本概念、神经网络知识,以及一定的英语阅读能力来查阅文档和资料。

  • 编程基础:熟悉至少一种编程语言,尤其是 Python,因为 PyTorch 主要是用 Python 编写的。
  • 数学基础:了解线性代数、概率论和统计学、微积分等基础数学知识,这些是理解和实现机器学习算法的基石。
  • 机器学习基础:了解机器学习的基本概念,如监督学习、无监督学习、强化学习、模型评估指标(准确率、召回率、F1分数等)。
  • 深度学习基础:熟悉神经网络的基本概念,包括前馈神经网络、卷积神经网络(CNN)、循环神经网络(RNN)、长短期记忆网络(LSTM)等。
  • 计算机视觉和自然语言处理基础:如果你打算在这些领域应用 PyTorch,了解相关的背景知识会很有帮助。
  • Linux/Unix 基础:虽然不是必需的,但了解 Linux/Unix 操作系统的基础知识可以帮助你更有效地使用命令行工具和脚本,特别是在数据预处理和模型训练中。
  • 英语阅读能力:由于许多文档、教程和社区讨论都是用英语进行的,具备一定的英语阅读能力将有助于你更好地学习和解决问题。

实例

下面的是 PyTorch 中一些基本的张量操作:如何创建随机张量、进行逐元素运算、访问特定元素以及计算总和和最大值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import torch
# 设置数据类型和设备
dtype = torch.float # 张量数据类型为浮点型
device = torch.device("cpu") # 本次计算在 CPU 上进行
# 创建并打印两个随机张量 a 和 b
a = torch.randn(2, 3, device=device, dtype=dtype) # 创建一个 2x3 的随机张量
b = torch.randn(2, 3, device=device, dtype=dtype) # 创建另一个 2x3 的随机张量
print("张量 a:")
print(a)
print("张量 b:")
print(b)
# 逐元素相乘并输出结果
print("a 和 b 的逐元素乘积:")
print(a * b)
# 输出张量 a 所有元素的总和
print("张量 a 所有元素的总和:")
print(a.sum())
# 输出张量 a 中第 2 行第 3 列的元素(注意索引从 0 开始)
print("张量 a 第 2 行第 3 列的元素:")
print(a[1, 2])
# 输出张量 a 中的最大值
print("张量 a 中的最大值:")
print(a.max())

创建张量:

  • torch.randn(2, 3) 创建一个 2 行 3 列的张量,填充随机数(遵循正态分布)。
  • device=devicedtype=dtype 分别指定了计算设备(CPU 或 GPU)和数据类型(浮点型)。

张量操作:

  • a * b元素相乘。
  • a.sum():计算张量 a 所有元素的和。
  • a[1, 2]:访问张量 a 第 2 行第 3 列的元素(注意索引从 0 开始)。
  • a.max():获取张量 a 中的最大值。

输出:(每次运行时值会有所不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
张量 a:
tensor([[-0.1460, -0.3490, 0.3705],
[-1.1141, 0.7661, 1.0823]])
张量 b:
tensor([[ 0.6901, -0.9663, 0.3634],
[-0.6538, -0.3728, -1.1323]])
a 和 b 的逐元素乘积:
tensor([[-0.1007, 0.3372, 0.1346],
[ 0.7284, -0.2856, -1.2256]])
张量 a 所有元素的总和:
tensor(0.6097)
张量 a 第 2 行第 3 列的元素:
tensor(1.0823)
张量 a 中的最大值:
tensor(1.0823)

PyTorch 简介

PyTorch 是一个开源的 Python 机器学习库,基于 Torch 库底层由C++实现,应用于人工智能领域,如计算机视觉和自然语言处理。

PyTorch 最初由 Meta Platforms 的人工智能研究团队开发,现在属 于Linux 基金会的一部分。

许多深度学习软件都是基于 PyTorch 构建的,包括特斯拉自动驾驶、Uber 的 Pyro、Hugging Face 的 Transformers、 PyTorch Lightning 和 Catalyst。

PyTorch 主要有两大特征:

  • 类似于 NumPy 的张量计算,能在 GPU 或 MPS 等硬件加速器上加速。
  • 基于带自动微分系统的深度神经网络

PyTorch 包括 torch.autograd、torch.nn、torch.optim 等子模块。

PyTorch 包含多种损失函数,包括 MSE(均方误差 = L2 范数)、交叉熵损失和负熵似然损失(对分类器有用)等。

img

PyTorch 特性

  • 动态计算图(Dynamic Computation Graphs): PyTorch 的计算图是动态的,这意味着它们在运行时构建,并且可以随时改变。这为实验和调试提供了极大的灵活性,因为开发者可以逐行执行代码,查看中间结果。
  • 自动微分(Automatic Differentiation): PyTorch 的自动微分系统允许开发者轻松地计算梯度,这对于训练深度学习模型至关重要。它通过反向传播算法自动计算出损失函数对模型参数的梯度
  • 张量计算(Tensor Computation): PyTorch 提供了类似于 NumPy 的张量操作,这些操作可以在 CPU 和 GPU 上执行,从而加速计算过程。张量是 PyTorch 中的基本数据结构,用于存储和操作数据。
  • 丰富的 API: PyTorch 提供了大量的预定义层损失函数优化算法,这些都是构建深度学习模型的常用组件。
  • 多语言支持: PyTorch 虽然以 Python 为主要接口,但也提供了 C++ 接口,允许更底层的集成和控制。

动态计算图(Dynamic Computation Graph)

PyTorch 最显著的特点之一是其动态计算图的机制。

与 TensorFlow 的静态计算图(graph)不同,PyTorch 在执行时构建计算图,这意味着在每次计算时,图都会根据输入数据的形状自动变化。

动态计算图的优点:

  • 更加灵活,特别适用于需要条件判断或递归的场景。
  • 方便调试和修改,能够直接查看中间结果。
  • 更接近 Python 编程的风格,易于上手。

张量(Tensor)与自动求导(Autograd)

PyTorch 中的核心数据结构是 张量(Tensor),它是一个多维矩阵,可以在 CPU 或 GPU 上高效地进行计算。张量的操作支持自动求导(Autograd)机制,使得在反向传播过程中自动计算梯度,这对于深度学习中的梯度下降优化算法至关重要。

张量(Tensor):

  • 支持在 CPU 和 GPU 之间进行切换。
  • 提供了类似 NumPy 的接口,支持元素级运算。
  • 支持自动求导,可以方便地进行梯度计算。

自动求导(Autograd):

  • PyTorch 内置的自动求导引擎,能够自动追踪所有张量的操作,并在反向传播时计算梯度。
  • 通过 requires_grad 属性,可以指定张量需要计算梯度。
  • 支持高效的反向传播,适用于神经网络的训练。

模型定义与训练

PyTorch 提供了 torch.nn 模块,允许用户通过继承 nn.Module 类来定义神经网络模型。使用 forward 函数指定前向传播,自动反向传播(通过 autograd)和梯度计算也由 PyTorch 内部处理。

神经网络模块(torch.nn):

  • 提供了常用的层(如线性层、卷积层、池化层等)。
  • 支持定义复杂的神经网络架构(包括多输入、多输出的网络)。
  • 兼容与优化器(如 torch.optim)一起使用。

GPU 加速

PyTorch 完全支持在 GPU 上运行,以加速深度学习模型的训练。通过简单的 .to(device) 方法,用户可以将模型和张量转移到 GPU 上进行计算。PyTorch 支持多 GPU 训练,能够利用 NVIDIA CUDA 技术显著提高计算效率。

GPU 支持:

  • 自动选择 GPU 或 CPU。
  • 支持通过 CUDA 加速运算。
  • 支持多 GPU 并行计算(DataParalleltorch.distributed)。

生态系统与社区支持

PyTorch 作为一个开源项目,拥有一个庞大的社区和生态系统。它不仅在学术界得到了广泛的应用,也在工业界,特别是在计算机视觉、自然语言处理等领域中得到了广泛部署。PyTorch 还提供了许多与深度学习相关的工具和库,如:

  • torchvision:用于计算机视觉任务数据集模型
  • torchtext:用于自然语言处理任务数据集预处理工具
  • torchaudio:用于音频处理的工具包。
  • PyTorch Lightning:一种简化 PyTorch 代码的高层库,专注于研究和实验的快速迭代。

与其他框架的对比

PyTorch 由于其灵活性、易用性和社区支持,已经成为很多深度学习研究者和开发者的首选框架。

TensorFlow vs PyTorch:

  • PyTorch 的动态计算图使得它更加灵活,适合快速实验和研究;而 TensorFlow 的静态计算图在生产环境中更具优化空间。
  • PyTorch 在调试时更加方便,TensorFlow 则在部署上更加成熟,支持更广泛的硬件和平台。
  • 近年来,TensorFlow 也引入了动态图(如 TensorFlow 2.x),使得两者在功能上趋于接近。
  • 其他深度学习框架,如 Keras、Caffe 等也有一定应用,但 PyTorch 由于其灵活性、易用性和社区支持,已经成为很多深度学习研究者和开发者的首选框架。
特性 TensorFlow PyTorch
开发公司 Google Facebook (FAIR)
计算图类型 静态计算图(定义后再执行) 动态计算图(定义即执行)
灵活性 低(计算图在编译时构建,不易修改) 高(计算图在执行时动态创建,易于修改和调试)
调试 较难(需要使用 tf.debugging 或外部工具调试) 容易(可以直接在 Python 中进行调试
易用性 低(较复杂,API 较多,学习曲线较陡峭) 高(API 简洁,语法更加接近 Python,容易上手)
部署 强(支持广泛的硬件,如 TensorFlow Lite、TensorFlow.js) 较弱(部署工具和平台相对较少,虽然有 TensorFlow 支持)
社区支持 很强(成熟且庞大的社区,广泛的教程和文档) 很强(社区活跃,特别是在学术界,快速发展的生态)
模型训练 支持分布式训练,支持多种设备(如 CPU、GPU、TPU) 支持分布式训练,支持多 GPU、CPU 和 TPU
API 层级 高级API:Keras;低级API:TensorFlow Core 高级API:TorchVision、TorchText 等;低级API:Torch
性能 高(优化方面成熟,适合生产环境) 高(适合研究原型开发,生产性能也在提升)
自动求导 通过 tf.GradientTape 实现动态求导(较复杂) 通过 autograd 动态求导(更简洁和直观)
调优与可扩展性 强(支持在多平台上运行,如 TensorFlow Serving 等) 较弱(虽然在学术和实验环境中表现优越,但生产环境支持相对较少)
框架灵活性 较低(TensorFlow 2.x 引入了动态图特性,但仍不完全灵活) 高(动态图带来更高的灵活性)
支持多种语言 支持多种语言(Python, C++, Java, JavaScript, etc.) 主要支持 Python(但也有 C++ API)
兼容性与迁移 TensorFlow 2.x 与旧版本兼容性较好 与 TensorFlow 兼容性差,迁移较难

PyTorch 是一个强大且灵活的深度学习框架,适合学术研究和工业应用。它的动态计算图、自动求导机制、GPU 加速等特点,使得其成为深度学习研究和实验中不可或缺的工具。

PyTorch 基础

PyTorch 是一个开源的深度学习框架,以其灵活性和动态计算图而广受欢迎。

PyTorch 主要有以下几个基础概念:张量(Tensor)、自动求导(Autograd)、神经网络模块(nn.Module)、优化器(optim)等。

  • 张量(Tensor):PyTorch 的核心数据结构,支持多维数组,并可以在 CPU 或 GPU 上进行加速计算。
  • 自动求导(Autograd):PyTorch 提供了自动求导功能,可以轻松计算模型的梯度,便于进行反向传播和优化。
  • 神经网络(nn.Module):PyTorch 提供了简单且强大的 API 来构建神经网络模型,可以方便地进行前向传播和模型定义。
  • 优化器(Optimizers):使用优化器(如 Adam、SGD 等)来更新模型的参数,使得损失最小化。
  • 设备(Device):可以将模型和张量移动到 GPU 上以加速计算。

张量(Tensor)

张量(Tensor)是 PyTorch 中的核心数据结构,用于存储和操作多维数组。

张量可以视为一个多维数组,支持加速计算的操作。

在 PyTorch 中,张量的概念类似于 NumPy 中的数组,但是 PyTorch 的张量可以运行在不同的设备上,比如 CPU 和 GPU,这使得它们非常适合于进行大规模并行计算,特别是在深度学习领域。

  • 维度(Dimensionality):张量的维度指的是数据的多维数组结构。例如,一个标量(0维张量)是一个单独的数字,一个向量(1维张量)是一个一维数组,一个矩阵(2维张量)是一个二维数组,以此类推。
  • 形状(Shape):张量的形状是指每个维度上的大小。例如,一个形状为(3, 4)的张量意味着它有3行4列。
  • 数据类型(Dtype):张量中的数据类型定义了存储每个元素所需的内存大小和解释方式。PyTorch支持多种数据类型,包括整数型(如torch.int8torch.int32)、浮点型(如torch.float32torch.float64)和布尔型(torch.bool)。

张量创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import torch
# 创建一个 2x3 的全 0 张量
a = torch.zeros(2, 3)
print(a)
# 创建一个 2x3 的全 1 张量
b = torch.ones(2, 3)
print(b)
# 创建一个 2x3 的随机数张量
c = torch.randn(2, 3)
print(c)
# 从 NumPy 数组创建张量
import numpy as np
numpy_array = np.array([[1, 2], [3, 4]])
tensor_from_numpy = torch.from_numpy(numpy_array)
print(tensor_from_numpy)
# 在指定设备(CPU/GPU)上创建张量
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
d = torch.randn(2, 3, device=device)
print(d)

常用张量操作:

1
2
3
4
5
6
7
8
9
10
11
# 张量相加
e = torch.randn(2, 3)
f = torch.randn(2, 3)
print(e + f)
# 逐元素乘法(不同于矩阵乘法)
print(e * f)
# 张量的转置
g = torch.randn(3, 2)
print(g.t()) # 或者 g.transpose(0, 1)
# 张量的形状
print(g.shape) # 返回形状

张量与设备

PyTorch 张量可以存在于不同的设备上,包括CPU和GPU,你可以将张量移动到 GPU 上以加速计算:

1
2
if torch.cuda.is_available():
tensor_gpu = tensor_from_list.to('cuda') # 将张量移动到GPU

梯度和自动微分

PyTorch的张量支持自动微分,这是深度学习中的关键特性。当你创建一个需要梯度的张量时,PyTorch可以自动计算其梯度

1
2
3
4
5
6
7
# 创建一个需要梯度的张量
tensor_requires_grad = torch.tensor([1.0], requires_grad=True)
# 进行一些操作
tensor_result = tensor_requires_grad * 2
# 计算梯度
tensor_result.backward()
print(tensor_requires_grad.grad) # 输出梯度

内存和性能

PyTorch 张量还提供了一些内存管理功能,比如.clone()、.detach() 和 .to() 方法,它们可以帮助你优化内存使用和提高性能。


自动求导(Autograd)

自动求导(Automatic Differentiation,简称Autograd)是深度学习框架中的一个核心特性,它允许计算机自动计算数学函数的导数。

在深度学习中,自动求导主要用于两个方面:一是在训练神经网络时计算梯度二是进行反向传播算法的实现

自动求导基于链式法则(Chain Rule),这是一个用于计算复杂函数导数的数学法则。链式法则表明,复合函数的导数是其各个组成部分导数的乘积。在深度学习中,模型通常是由许多层组成的复杂函数,自动求导能够高效地计算这些层的梯度。

动态图与静态图:

  • 动态图(Dynamic Graph):在动态图中,计算图在运行时动态构建。每次执行操作时,计算图都会更新,这使得调试和修改模型变得更加容易。PyTorch使用的是动态图
  • 静态图(Static Graph):在静态图中,计算图在开始执行之前构建完成,并且不会改变。TensorFlow最初使用的是静态图,但后来也支持动态图。

PyTorch 提供了自动求导功能,通过 autograd 模块来自动计算梯度。

torch.Tensor 对象有一个 requires_grad 属性,用于指示是否需要计算该张量的梯度

当你创建一个 requires_grad=True 的张量时,PyTorch 会自动跟踪所有对它的操作,以便在之后计算梯度。

创建需要梯度的张量:

1
2
3
4
5
6
7
8
# 创建一个需要计算梯度的张量
x = torch.randn(2, 2, requires_grad=True)
print(x)
# 执行某些操作
y = x + 2
z = y * y * 3
out = z.mean()
print(out)

反向传播(Backpropagation)

一旦定义了计算图,可以通过 .backward() 方法来计算梯度。

1
2
3
4
# 反向传播,计算梯度
out.backward()
# 查看 x 的梯度
print(x.grad)

在神经网络训练中,自动求导主要用于实现反向传播算法。

反向传播是一种通过计算损失函数关于网络参数的梯度来训练神经网络的方法。在每次迭代中,网络的前向传播会计算输出和损失,然后反向传播会计算损失关于每个参数的梯度,并使用这些梯度来更新参数。

停止梯度计算

如果你不希望某些张量的梯度被计算(例如,当你不需要反向传播时),可以使用 torch.no_grad() 或设置 requires_grad=False

1
2
3
# 使用 torch.no_grad() 禁用梯度计算
with torch.no_grad():
y = x * 2

神经网络(nn.Module)

神经网络是一种模仿人脑神经元连接的计算模型,由多层节点(神经元)组成,用于学习数据之间的复杂模式和关系。

神经网络通过调整神经元之间的连接权重来优化预测结果,这一过程涉及前向传播、损失计算、反向传播和参数更新。

神经网络的类型包括前馈神经网络卷积神经网络(CNN)循环神经网络(RNN)长短期记忆网络(LSTM),它们在图像识别、语音处理、自然语言处理等多个领域都有广泛应用。

PyTorch 提供了一个非常方便的接口来构建神经网络模型,即 torch.nn.Module

我们可以继承 nn.Module 类并定义自己的网络层。

创建一个简单的神经网络:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import torch.nn as nn
import torch.optim as optim
# 定义一个简单的全连接神经网络
class SimpleNN(nn.Module):
def __init__(self):
super(SimpleNN, self).__init__()
self.fc1 = nn.Linear(2, 2) # 输入层到隐藏层
self.fc2 = nn.Linear(2, 1) # 隐藏层到输出层
def forward(self, x):
x = torch.relu(self.fc1(x)) # ReLU 激活函数
x = self.fc2(x)
return x
# 创建网络实例
model = SimpleNN()
# 打印模型结构
print(model)

训练过程:

  1. 前向传播(Forward Propagation): 在前向传播阶段,输入数据通过网络层传递,每层应用权重和激活函数直到产生输出
  2. 计算损失(Calculate Loss): 根据网络的输出真实标签,计算损失函数的值
  3. 反向传播(Backpropagation): 反向传播利用自动求导技术计算损失函数关于每个参数的梯度
  4. 参数更新(Parameter Update): 使用优化器根据梯度更新网络的权重和偏置。
  5. 迭代(Iteration): 重复上述过程,直到模型在训练数据上的性能达到满意的水平。

前向传播与损失计算

1
2
3
4
5
6
7
8
9
10
11
12
# 随机输入
x = torch.randn(1, 2)
# 前向传播
output = model(x)
print(output)
# 定义损失函数(例如均方误差 MSE)
criterion = nn.MSELoss()
# 假设目标值为 1
target = torch.randn(1, 1)
# 计算损失
loss = criterion(output, target)
print(loss)

优化器(Optimizers)

优化器在训练过程中更新神经网络的参数,以减少损失函数的值。

PyTorch 提供了多种优化器,例如 SGD、Adam 等。

使用优化器进行参数更新:

1
2
3
4
5
6
# 定义优化器(使用 Adam 优化器)
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 训练步骤
optimizer.zero_grad() # 清空梯度
loss.backward() # 反向传播
optimizer.step() # 更新参数

训练模型

训练模型是机器学习和深度学习中的核心过程,旨在通过大量数据学习模型参数,以便模型能够对新的、未见过的数据做出准确的预测。

训练模型通常包括以下几个步骤:

  1. 数据准备
    • 收集和处理数据,包括清洗、标准化和归一化。
    • 将数据分为训练集、验证集和测试集。
  2. 定义模型
    • 选择模型架构,例如决策树、神经网络等。
    • 初始化模型参数(权重和偏置)。
  3. 选择损失函数
    • 根据任务类型(如分类、回归)选择合适的损失函数。
  4. 选择优化器
    • 选择一个优化算法,如SGD、Adam等,来更新模型参数。
  5. 前向传播
    • 在每次迭代中,将输入数据通过模型传递,计算预测输出。
  6. 计算损失
    • 使用损失函数评估预测输出与真实标签之间的差异。
  7. 反向传播
    • 利用自动求导计算损失相对于模型参数的梯度。
  8. 参数更新
    • 根据计算出的梯度和优化器的策略更新模型参数。
  9. 迭代优化
    • 重复步骤5-8,直到模型在验证集上的性能不再提升或达到预定的迭代次数。
  10. 评估和测试
    • 使用测试集评估模型的最终性能,确保模型没有过拟合。
  11. 模型调优
    • 根据模型在测试集上的表现进行调参,如改变学习率、增加正则化等。
  12. 部署模型
    • 将训练好的模型部署到生产环境中,用于实际的预测任务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import torch
import torch.nn as nn
import torch.optim as optim
# 1. 定义一个简单的神经网络模型
class SimpleNN(nn.Module):
def __init__(self):
super(SimpleNN, self).__init__()
self.fc1 = nn.Linear(2, 2) # 输入层到隐藏层
self.fc2 = nn.Linear(2, 1) # 隐藏层到输出层
def forward(self, x):
x = torch.relu(self.fc1(x)) # ReLU 激活函数
x = self.fc2(x)
return x
# 2. 创建模型实例
model = SimpleNN()
# 3. 定义损失函数和优化器
criterion = nn.MSELoss() # 均方误差损失函数
optimizer = optim.Adam(model.parameters(), lr=0.001) # Adam 优化器
# 4. 假设我们有训练数据 X 和 Y
X = torch.randn(10, 2) # 10 个样本,2 个特征
Y = torch.randn(10, 1) # 10 个目标值
# 5. 训练循环
for epoch in range(100): # 训练 100 轮
optimizer.zero_grad() # 清空之前的梯度
output = model(X) # 前向传播
loss = criterion(output, Y) # 计算损失
loss.backward() # 反向传播
optimizer.step() # 更新参数
# 每 10 轮输出一次损失
if (epoch + 1) % 10 == 0:
print(f'Epoch [{epoch + 1}/100], Loss: {loss.item():.4f}')

在每 10 轮,程序会输出当前的损失值,帮助我们跟踪模型的训练进度。随着训练的进行,损失值应该会逐渐降低,表示模型在不断学习并优化其参数。

训练模型是一个迭代的过程,需要不断地调整和优化,直到达到满意的性能。这个过程涉及到大量的实验和调优,目的是使模型在新的、未见过的数据上也能有良好的泛化能力。


设备(Device)

PyTorch 允许你将模型和数据移动到 GPU 上进行加速。

使用 torch.device 来指定计算设备。

将模型和数据移至 GPU:

1
2
3
4
5
6
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 将模型移动到设备
model.to(device)
# 将数据移动到设备
X = X.to(device)
Y = Y.to(device)

PyTorch 张量(Tensor)

张量是一个多维数组,可以是标量、向量、矩阵或更高维度的数据结构。

在 PyTorch 中,张量(Tensor)是数据的核心表示形式,类似于 NumPy 的多维数组,但具有更强大的功能,例如支持 GPU 加速和自动梯度计算。

张量支持多种数据类型(整型、浮点型、布尔型等)。

张量可以存储在 CPU 或 GPU 中,GPU 张量可显著加速计算。

下图展示了不同维度的张量(Tensor)在 PyTorch 中的表示方法:

img

说明:

  • 1D Tensor / Vector(一维张量/向量): 最基本的张量形式,可以看作是一个数组,图中的例子是一个包含 10 个元素的向量。
  • 2D Tensor / Matrix(二维张量/矩阵): 二维数组,通常用于表示矩阵,图中的例子是一个 4x5 的矩阵,包含了 20 个元素。
  • 3D Tensor / Cube(三维张量/立方体): 三维数组,可以看作是由多个矩阵堆叠而成的立方体,图中的例子展示了一个 3x4x5 的立方体,其中每个 5x5 的矩阵代表立方体的一个”层”。
  • 4D Tensor / Vector of Cubes(四维张量/立方体向量): 四维数组,可以看作是由多个立方体组成的向量,图中的例子没有具体数值,但可以理解为一个包含多个 3D 张量的集合
  • 5D Tensor / Matrix of Cubes(五维张量/立方体矩阵): 五维数组,可以看作是由多个4D张量组成的矩阵,图中的例子同样没有具体数值,但可以理解为一个包含多个 4D 张量的集合。

创建张量

张量创建的方式有:

方法 说明 示例代码
torch.tensor(data) 从 Python 列表或 NumPy 数组创建张量。 x = torch.tensor([[1, 2], [3, 4]])
torch.zeros(size) 创建一个全为零的张量。 x = torch.zeros((2, 3))
torch.ones(size) 创建一个全为 1 的张量。 x = torch.ones((2, 3))
torch.empty(size) 创建一个未初始化的张量。 x = torch.empty((2, 3))
torch.rand(size) 创建一个服从均匀分布的随机张量,值在 [0, 1) x = torch.rand((2, 3))
torch.randn(size) 创建一个服从正态分布的随机张量,均值为 0,标准差为 1。 x = torch.randn((2, 3))
torch.arange(start, end, step) 创建一个一维序列张量,类似于 Python 的 range x = torch.arange(0, 10, 2)
torch.linspace(start, end, steps) 创建一个在指定范围内等间隔的序列张量。 x = torch.linspace(0, 1, 5)
torch.eye(size) 创建一个单位矩阵(对角线为 1,其他为 0)。 x = torch.eye(3)
torch.from_numpy(ndarray) 将 NumPy 数组转换为张量。 x = torch.from_numpy(np.array([1, 2, 3]))
1
2
3
import torch
tensor = torch.tensor([1, 2, 3])
print(tensor)

如果你有一个 NumPy 数组,可以使用 torch.from_numpy() 将其转换为张量:

1
2
3
4
import numpy as np
np_array = np.array([1, 2, 3])
tensor = torch.from_numpy(np_array)
print(tensor)

输出如下:

1
tensor([1, 2, 3])

创建 2D 张量(矩阵):

1
2
3
4
5
6
7
8
9
import torch
tensor_2d = torch.tensor([
[-9, 4, 2, 5, 7],
[3, 0, 12, 8, 6],
[1, 23, -6, 45, 2],
[22, 3, -1, 72, 6]
])
print("2D Tensor (Matrix):\n", tensor_2d)
print("Shape:", tensor_2d.shape) # 形状

其他维度的创建:

1
2
3
4
5
6
7
8
9
10
11
12
# 创建 3D 张量(立方体)
tensor_3d = torch.stack([tensor_2d, tensor_2d + 10, tensor_2d - 5]) # 堆叠 3 个 2D 张量
print("3D Tensor (Cube):\n", tensor_3d)
print("Shape:", tensor_3d.shape) # 形状
# 创建 4D 张量(向量的立方体)
tensor_4d = torch.stack([tensor_3d, tensor_3d + 100]) # 堆叠 2 个 3D 张量
print("4D Tensor (Vector of Cubes):\n", tensor_4d)
print("Shape:", tensor_4d.shape) # 形状
# 创建 5D 张量(矩阵的立方体)
tensor_5d = torch.stack([tensor_4d, tensor_4d + 1000]) # 堆叠 2 个 4D 张量
print("5D Tensor (Matrix of Cubes):\n", tensor_5d)
print("Shape:", tensor_5d.shape) # 形状

张量的属性

张量的属性如下表:

属性 说明 示例
.shape 获取张量的形状 tensor.shape
.size() 获取张量的形状 tensor.size()
.dtype 获取张量的数据类型 tensor.dtype
.device 查看张量所在的设备 (CPU/GPU) tensor.device
.dim() 获取张量的维度数 tensor.dim()
.requires_grad 是否启用梯度计算 tensor.requires_grad
.numel() 获取张量中的元素总数 tensor.numel()
.is_cuda 检查张量是否在 GPU 上 tensor.is_cuda
.T 获取张量的转置(适用于 2D 张量 tensor.T
.item() 获取单元素张量的值 tensor.item()
.is_contiguous() 检查张量是否连续存储 tensor.is_contiguous()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import torch
# 创建一个 2D 张量
tensor = torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.float32)
# 张量的属性
print("Tensor:\n", tensor)
print("Shape:", tensor.shape) # 获取形状
print("Size:", tensor.size()) # 获取形状(另一种方法)
print("Data Type:", tensor.dtype) # 数据类型
print("Device:", tensor.device) # 设备
print("Dimensions:", tensor.dim()) # 维度数
print("Total Elements:", tensor.numel()) # 元素总数
print("Requires Grad:", tensor.requires_grad) # 是否启用梯度
print("Is CUDA:", tensor.is_cuda) # 是否在 GPU 上
print("Is Contiguous:", tensor.is_contiguous()) # 是否连续存储
# 获取单元素值
single_value = torch.tensor(42)
print("Single Element Value:", single_value.item())
# 转置张量
tensor_T = tensor.T
print("Transposed Tensor:\n", tensor_T)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Tensor:
tensor([[1., 2., 3.],
[4., 5., 6.]])
Shape: torch.Size([2, 3])
Size: torch.Size([2, 3])
Data Type: torch.float32
Device: cpu
Dimensions: 2
Total Elements: 6
Requires Grad: False
Is CUDA: False
Is Contiguous: True
Single Element Value: 42
Transposed Tensor:
tensor([[1., 4.],
[2., 5.],
[3., 6.]])

张量的操作

张量操作方法说明如下。

基础操作:

操作 说明 示例代码
+, -, *, / 元素级加法、减法、乘法、除法。 z = x + y
torch.matmul(x, y) 矩阵乘法。 z = torch.matmul(x, y)
torch.dot(x, y) 向量点积(仅适用于 1D 张量)。 z = torch.dot(x, y)
torch.sum(x) 求和。 z = torch.sum(x)
torch.mean(x) 求均值。 z = torch.mean(x)
torch.max(x) 求最大值。 z = torch.max(x)
torch.min(x) 求最小值。 z = torch.min(x)
torch.argmax(x, dim) 返回最大值的索引(指定维度)。 z = torch.argmax(x, dim=1)
torch.softmax(x, dim) 计算 softmax(指定维度)。 z = torch.softmax(x, dim=1)

形状操作

操作 说明 示例代码
x.view(shape) 改变张量的形状不改变数据)。 z = x.view(3, 4)
x.reshape(shape) 类似于 view,但更灵活。 z = x.reshape(3, 4)
x.t() 转置矩阵。 z = x.t()
x.unsqueeze(dim) 指定维度添加一个维度。 z = x.unsqueeze(0)
x.squeeze(dim) 去掉指定维度为 1 的维度。 z = x.squeeze(0)
torch.cat((x, y), dim) 按指定维度连接多个张量。 z = torch.cat((x, y), dim=1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import torch
# 创建一个 2D 张量
tensor = torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.float32)
print("原始张量:\n", tensor)
# 1. **索引和切片操作**
print("\n【索引和切片】")
print("获取第一行:", tensor[0]) # 获取第一行
print("获取第一行第一列的元素:", tensor[0, 0]) # 获取特定元素
print("获取第二列的所有元素:", tensor[:, 1]) # 获取第二列所有元素
# 2. **形状变换操作**
print("\n【形状变换】")
reshaped = tensor.view(3, 2) # 改变张量形状为 3x2
print("改变形状后的张量:\n", reshaped)
flattened = tensor.flatten() # 将张量展平成一维
print("展平后的张量:\n", flattened)
# 3. **数学运算操作**
print("\n【数学运算】")
tensor_add = tensor + 10 # 张量加法
print("张量加 10:\n", tensor_add)
tensor_mul = tensor * 2 # 张量乘法
print("张量乘 2:\n", tensor_mul)
tensor_sum = tensor.sum() # 计算所有元素的和
print("张量元素的和:", tensor_sum.item())
# 4. **与其他张量的操作**
print("\n【与其他张量操作】")
tensor2 = torch.tensor([[1, 1, 1], [1, 1, 1]], dtype=torch.float32)
print("另一个张量:\n", tensor2)
tensor_dot = torch.matmul(tensor, tensor2.T) # 张量矩阵乘法
print("矩阵乘法结果:\n", tensor_dot)
# 5. **条件判断和筛选**
print("\n【条件判断和筛选】")
mask = tensor > 3 # 创建一个布尔掩码
print("大于 3 的元素的布尔掩码:\n", mask)
filtered_tensor = tensor[tensor > 3] # 筛选出符合条件的元素
print("大于 3 的元素:\n", filtered_tensor)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
原始张量:
tensor([[1., 2., 3.],
[4., 5., 6.]])
【索引和切片】
获取第一行: tensor([1., 2., 3.])
获取第一行第一列的元素: tensor(1.)
获取第二列的所有元素: tensor([2., 5.])
【形状变换】
改变形状后的张量:
tensor([[1., 2.],
[3., 4.],
[5., 6.]])
展平后的张量:
tensor([1., 2., 3., 4., 5., 6.])
【数学运算】
张量加 10:
tensor([[11., 12., 13.],
[14., 15., 16.]])
张量乘 2:
tensor([[ 2., 4., 6.],
[ 8., 10., 12.]])
张量元素的和: 21.0
【与其他张量操作】
另一个张量:
tensor([[1., 1., 1.],
[1., 1., 1.]])
矩阵乘法结果:
tensor([[ 6., 6.],
[15., 15.]])
【条件判断和筛选】
大于 3 的元素的布尔掩码:
tensor([[False, False, False],
[ True, True, True]])
大于 3 的元素:
tensor([4., 5., 6.])

张量的 GPU 加速

将张量转移到 GPU:

1
2
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
x = torch.tensor([1.0, 2.0, 3.0], device=device)

检查 GPU 是否可用:

1
torch.cuda.is_available()  # 返回 True 或 False

张量与 NumPy 的互操作

张量与 NumPy 的互操作如下表所示:

操作 说明 示例代码
torch.from_numpy(ndarray) 将 NumPy 数组转换为张量。 x = torch.from_numpy(np_array)
x.numpy() 将张量转换为 NumPy 数组(仅限 CPU 张量)。 np_array = x.numpy()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import torch
import numpy as np
# 1. NumPy 数组转换为 PyTorch 张量
print("1. NumPy 转为 PyTorch 张量")
numpy_array = np.array([[1, 2, 3], [4, 5, 6]])
print("NumPy 数组:\n", numpy_array)
# 使用 torch.from_numpy() 将 NumPy 数组转换为张量
tensor_from_numpy = torch.from_numpy(numpy_array)
print("转换后的 PyTorch 张量:\n", tensor_from_numpy)
# 修改 NumPy 数组,观察张量的变化(共享内存)
numpy_array[0, 0] = 100
print("修改后的 NumPy 数组:\n", numpy_array)
print("PyTorch 张量也会同步变化:\n", tensor_from_numpy)
# 2. PyTorch 张量转换为 NumPy 数组
print("\n2. PyTorch 张量转为 NumPy 数组")
tensor = torch.tensor([[7, 8, 9], [10, 11, 12]], dtype=torch.float32)
print("PyTorch 张量:\n", tensor)
# 使用 tensor.numpy() 将张量转换为 NumPy 数组
numpy_from_tensor = tensor.numpy()
print("转换后的 NumPy 数组:\n", numpy_from_tensor)
# 修改张量,观察 NumPy 数组的变化(共享内存)
tensor[0, 0] = 77
print("修改后的 PyTorch 张量:\n", tensor)
print("NumPy 数组也会同步变化:\n", numpy_from_tensor)
# 3. 注意:不共享内存的情况(需要复制数据)
print("\n3. 使用 clone() 保证独立数据")
tensor_independent = torch.tensor([[13, 14, 15], [16, 17, 18]], dtype=torch.float32)
numpy_independent = tensor_independent.clone().numpy() # 使用 clone 复制数据
print("原始张量:\n", tensor_independent)
tensor_independent[0, 0] = 0 # 修改张量数据
print("修改后的张量:\n", tensor_independent)
print("NumPy 数组(不会同步变化):\n", numpy_independent)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
1. NumPy 转为 PyTorch 张量
NumPy 数组:
[[1 2 3]
[4 5 6]]
转换后的 PyTorch 张量:
tensor([[1, 2, 3],
[4, 5, 6]])
修改后的 NumPy 数组:
[[100 2 3]
[ 4 5 6]]
PyTorch 张量也会同步变化:
tensor([[100, 2, 3],
[ 4, 5, 6]])
2. PyTorch 张量转为 NumPy 数组
PyTorch 张量:
tensor([[ 7., 8., 9.],
[10., 11., 12.]])
转换后的 NumPy 数组:
[[ 7. 8. 9.]
[10. 11. 12.]]
修改后的 PyTorch 张量:
tensor([[77., 8., 9.],
[10., 11., 12.]])
NumPy 数组也会同步变化:
[[77. 8. 9.]
[10. 11. 12.]]
3. 使用 clone() 保证独立数据
原始张量:
tensor([[13., 14., 15.],
[16., 17., 18.]])
修改后的张量:
tensor([[ 0., 14., 15.],
[16., 17., 18.]])
NumPy 数组(不会同步变化):
[[13. 14. 15.]
[16. 17. 18.]]

PyTorch 神经网络基础

神经网络是一种模仿人脑处理信息方式的计算模型,它由许多相互连接的节点(神经元)组成,这些节点按层次排列。

神经网络的强大之处在于其能够自动从大量数据中学习复杂的模式和特征,无需人工设计特征提取器

随着深度学习的发展,神经网络已经成为解决许多复杂问题的关键技术。

神经元(Neuron)

神经元是神经网络的基本单元,它接收输入信号,通过加权求和后与偏置(bias)相加,然后通过激活函数处理以产生输出

神经元的权重偏置是网络学习过程中需要调整的参数

输入和输出:

  • 输入(Input):输入是网络的起始点,可以是特征数据,如图像的像素值或文本的词向量。
  • 输出(Output):输出是网络的终点,表示模型的预测结果,如分类任务中的类别标签。

神经元接收多个输入(例如x1, x2, …, xn),如果输入的加权和大于激活阈值(activation potential),则产生二进制输出

img神经元的输出可以看作是输入的加权和加上偏置(bias),神经元的数学表示:

img

这里,wj 是权重,xj 是输入,而 Bias 是偏置项。

层(Layer)

输入层和输出层之间的层被称为隐藏层,层与层之间的连接密度和类型构成了网络的配置。

神经网络由多个层组成,包括:

  • 输入层(Input Layer):接收原始输入数据。
  • 隐藏层(Hidden Layer):对输入数据进行处理,可以有多个隐藏层。
  • 输出层(Output Layer):产生最终的输出结果。

典型的神经网络架构:

img

前馈神经网络(Feedforward Neural Network,FNN)

前馈神经网络(Feedforward Neural Network,FNN)是神经网络家族中的基本单元。

前馈神经网络特点是数据从输入层开始,经过一个或多个隐藏层,最后到达输出层,全过程没有循环或反馈。

img

前馈神经网络的基本结构:

  • 输入层: 数据进入网络的入口点。输入层的每个节点代表一个输入特征。
  • 隐藏层:一个或多个层,用于捕获数据的非线性特征。每个隐藏层由多个神经元组成,每个神经元通过激活函数增加非线性能力。
  • 输出层:输出网络的预测结果。节点数和问题类型相关,例如分类问题的输出节点数等于类别数。
  • 连接权重与偏置:每个神经元的输入通过权重进行加权求和,并加上偏置值,然后通过激活函数传递。

循环神经网络(Recurrent Neural Network, RNN)

循环神经网络(Recurrent Neural Network, RNN)络是一类专门处理序列数据的神经网络,能够捕获输入数据中时间或顺序信息的依赖关系。

RNN 的特别之处在于它具有”记忆能力”,可以在网络的隐藏状态中保存之前时间步的信息。

循环神经网络用于处理随时间变化的数据模式

在 RNN 中,相同的层被用来接收输入参数,并在指定的神经网络中显示输出参数。

img

PyTorch 提供了强大的工具来构建和训练神经网络。

神经网络在 PyTorch 中是通过 torch.nn 模块来实现的。

torch.nn 模块提供了各种网络层(如全连接层、卷积层等)、损失函数和优化器,让神经网络的构建和训练变得更加方便。

img

在 PyTorch 中,构建神经网络通常需要继承 nn.Module 类。

nn.Module 是所有神经网络模块的基类,你需要定义以下两个部分:

  • __init__():定义网络层。
  • forward():定义数据的前向传播过程。

简单的全连接神经网络(Fully Connected Network):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import torch
import torch.nn as nn
# 定义一个简单的神经网络模型
class SimpleNN(nn.Module):
def __init__(self):
super(SimpleNN, self).__init__()
# 定义一个输入层到隐藏层的全连接层
self.fc1 = nn.Linear(2, 2) # 输入 2 个特征,输出 2 个特征
# 定义一个隐藏层到输出层的全连接层
self.fc2 = nn.Linear(2, 1) # 输入 2 个特征,输出 1 个预测值
def forward(self, x):
# 前向传播过程
x = torch.relu(self.fc1(x)) # 使用 ReLU 激活函数
x = self.fc2(x) # 输出层
return x
# 创建模型实例
model = SimpleNN()
# 打印模型
print(model)
1
2
3
4
SimpleNN(
(fc1): Linear(in_features=2, out_features=2, bias=True)
(fc2): Linear(in_features=2, out_features=1, bias=True)
)

PyTorch 提供了许多常见的神经网络层,以下是几个常见的:

  • nn.Linear(in_features, out_features)全连接层,输入 in_features 个特征,输出 out_features 个特征。
  • nn.Conv2d(in_channels, out_channels, kernel_size)2D 卷积层,用于图像处理。
  • nn.MaxPool2d(kernel_size)2D 最大池化层,用于降维。
  • nn.ReLU():ReLU 激活函数,常用于隐藏层。
  • nn.Softmax(dim):Softmax 激活函数,通常用于输出层,适用于多类分类问题。

激活函数(Activation Function)

激活函数决定了神经元是否应该被激活。它们是非线性函数,使得神经网络能够学习和执行更复杂的任务。常见的激活函数包括:

  • Sigmoid:用于二分类问题,输出值在 0 和 1 之间。
  • Tanh:输出值在 -1 和 1 之间,常用于输出层之前。
  • ReLU(Rectified Linear Unit):目前最流行的激活函数之一,定义为 f(x) = max(0, x),有助于解决梯度消失问题
  • Softmax:常用于多分类问题的输出层,将输出转换为概率分布
1
2
3
4
5
6
7
import torch.nn.functional as F
# ReLU 激活
output = F.relu(input_tensor)
# Sigmoid 激活
output = torch.sigmoid(input_tensor)
# Tanh 激活
output = torch.tanh(input_tensor)

损失函数(Loss Function)

损失函数用于衡量模型的预测值与真实值之间的差异。

常见的损失函数包括:

  • 均方误差(MSELoss)回归问题常用,计算输出与目标值的平方差
  • 交叉熵损失(CrossEntropyLoss)分类问题常用,计算输出和真实标签之间的交叉熵
  • BCEWithLogitsLoss二分类问题,结合了 Sigmoid 激活和二元交叉熵损失。
1
2
3
4
5
6
# 均方误差损失
criterion = nn.MSELoss()
# 交叉熵损失
criterion = nn.CrossEntropyLoss()
# 二分类交叉熵损失
criterion = nn.BCEWithLogitsLoss()

优化器(Optimizer)

优化器负责在训练过程中更新网络的权重和偏置。

常见的优化器包括:

  • SGD(随机梯度下降)
  • Adam(自适应矩估计)
  • RMSprop(均方根传播)
1
2
3
4
5
import torch.optim as optim
# 使用 SGD 优化器
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 使用 Adam 优化器
optimizer = optim.Adam(model.parameters(), lr=0.001)

训练过程(Training Process)

训练神经网络涉及以下步骤:

  1. 准备数据:通过 DataLoader 加载数据。
  2. 定义损失函数和优化器
  3. 前向传播:计算模型的输出。
  4. 计算损失:与目标进行比较,得到损失值。
  5. 反向传播:通过 loss.backward() 计算梯度。
  6. 更新参数:通过 optimizer.step() 更新模型的参数。
  7. 重复上述步骤,直到达到预定的训练轮数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 假设已经定义好了模型、损失函数和优化器
# 训练数据示例
X = torch.randn(10, 2) # 10 个样本,每个样本有 2 个特征
Y = torch.randn(10, 1) # 10 个目标标签
# 训练过程
for epoch in range(100): # 训练 100 轮
model.train() # 设置模型为训练模式
optimizer.zero_grad() # 清除梯度
output = model(X) # 前向传播
loss = criterion(output, Y) # 计算损失
loss.backward() # 反向传播
optimizer.step() # 更新权重
if (epoch + 1) % 10 == 0: # 每 10 轮输出一次损失
print(f'Epoch [{epoch + 1}/100], Loss: {loss.item():.4f}')

测试与评估

训练完成后,需要对模型进行测试和评估。

常见的步骤包括:

  • 计算测试集的损失:测试模型在未见过的数据上的表现。
  • 计算准确率(Accuracy):对于分类问题,计算正确预测的比例。
1
2
3
4
5
6
# 假设你有测试集 X_test 和 Y_test
model.eval() # 设置模型为评估模式
with torch.no_grad(): # 在评估过程中禁用梯度计算
output = model(X_test)
loss = criterion(output, Y_test)
print(f'Test Loss: {loss.item():.4f}')

神经网络类型

  1. 前馈神经网络(Feedforward Neural Networks):数据单向流动,从输入层到输出层,无反馈连接。
  2. 卷积神经网络(Convolutional Neural Networks, CNNs):适用于图像处理,使用卷积层提取空间特征
  3. 循环神经网络(Recurrent Neural Networks, RNNs):适用于序列数据,如时间序列分析自然语言处理,允许信息反馈循环。
  4. 长短期记忆网络(Long Short-Term Memory, LSTM):一种特殊的RNN,能够学习长期依赖关系

PyTorch 第一个神经网络

本章节我们将介绍如何用 PyTorch 实现一个简单的前馈神经网络,完成一个二分类任务。

以下实例展示了如何使用 PyTorch 实现一个简单的神经网络进行二分类任务训练。

网络结构包括输入层、隐藏层和输出层,使用了 ReLU 激活函数和 Sigmoid 激活函数。

采用了均方误差损失函数和随机梯度下降优化器。

训练过程是通过前向传播、计算损失、反向传播和参数更新来逐步调整模型参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 导入PyTorch库
import torch
import torch.nn as nn
# 定义输入层大小、隐藏层大小、输出层大小和批量大小
n_in, n_h, n_out, batch_size = 10, 5, 1, 10
# 创建虚拟输入数据和目标数据
x = torch.randn(batch_size, n_in) # 随机生成输入数据
y = torch.tensor([[1.0], [0.0], [0.0],
[1.0], [1.0], [1.0], [0.0], [0.0], [1.0], [1.0]]) # 目标输出数据
# 创建顺序模型,包含线性层、ReLU激活函数和Sigmoid激活函数
model = nn.Sequential(
nn.Linear(n_in, n_h), # 输入层到隐藏层的线性变换
nn.ReLU(), # 隐藏层的ReLU激活函数
nn.Linear(n_h, n_out), # 隐藏层到输出层的线性变换
nn.Sigmoid() # 输出层的Sigmoid激活函数
)
# 定义均方误差损失函数和随机梯度下降优化器
criterion = torch.nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # 学习率为0.01
# 执行梯度下降算法进行模型训练
for epoch in range(50): # 迭代50次
y_pred = model(x) # 前向传播,计算预测值
loss = criterion(y_pred, y) # 计算损失
print('epoch: ', epoch, 'loss: ', loss.item()) # 打印损失值
optimizer.zero_grad() # 清零梯度
loss.backward() # 反向传播,计算梯度
optimizer.step() # 更新模型参数

定义网络参数:

1
n_in, n_h, n_out, batch_size = 10, 5, 1, 10
  • n_in:输入层大小为 10,即每个数据点有 10 个特征
  • n_h:隐藏层大小为 5,即隐藏层包含 5 个神经元
  • n_out:输出层大小为 1,即输出一个标量,表示二分类结果(0 或 1)。
  • batch_size:每个批次包含 10 个样本

生成输入数据和目标数据:

1
2
3
x = torch.randn(batch_size, n_in)  # 随机生成输入数据
y = torch.tensor([[1.0], [0.0], [0.0],
[1.0], [1.0], [1.0], [0.0], [0.0], [1.0], [1.0]]) # 目标输出数据
  • x:随机生成一个形状为 (10, 10) 的输入数据矩阵,表示 10 个样本,每个样本有 10 个特征。
  • y:目标输出数据(标签),表示每个输入样本的类别标签(0 或 1),是一个 10×1 的张量。

定义神经网络模型:

1
2
3
4
5
6
model = nn.Sequential(
nn.Linear(n_in, n_h), # 输入层到隐藏层的线性变换
nn.ReLU(), # 隐藏层的ReLU激活函数
nn.Linear(n_h, n_out), # 隐藏层到输出层的线性变换
nn.Sigmoid() # 输出层的Sigmoid激活函数
)

nn.Sequential 用于按顺序定义网络层。

  • nn.Linear(n_in, n_h):定义输入层到隐藏层的线性变换输入特征是 10 个,隐藏层有 5 个神经元。
  • nn.ReLU():在隐藏层后添加 ReLU 激活函数增加非线性
  • nn.Linear(n_h, n_out):定义隐藏层到输出层的线性变换,输出为 1 个神经元
  • nn.Sigmoid():输出层使用 Sigmoid 激活函数,将结果映射到 0 到 1 之间,用于二分类任务。

定义损失函数和优化器:

1
2
criterion = torch.nn.MSELoss()  # 使用均方误差损失函数
optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # 使用随机梯度下降优化器,学习率为 0.01

训练循环:

1
2
3
4
5
6
7
for epoch in range(50):  # 训练50轮
y_pred = model(x) # 前向传播,计算预测值
loss = criterion(y_pred, y) # 计算损失
print('epoch: ', epoch, 'loss: ', loss.item()) # 打印损失值
optimizer.zero_grad() # 清零梯度
loss.backward() # 反向传播,计算梯度
optimizer.step() # 更新模型参数
  • for epoch in range(50):进行 50 次训练迭代
  • y_pred = model(x):进行前向传播,使用当前模型参数计算输入数据 x 的预测值
  • loss = criterion(y_pred, y):计算预测值和目标值 y 之间的损失
  • optimizer.zero_grad()清除上一轮训练时的梯度值
  • loss.backward()反向传播,计算损失函数相对于模型参数的梯度
  • optimizer.step():根据计算出的梯度更新模型参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
# 定义输入层大小、隐藏层大小、输出层大小和批量大小
n_in, n_h, n_out, batch_size = 10, 5, 1, 10
# 创建虚拟输入数据和目标数据
x = torch.randn(batch_size, n_in) # 随机生成输入数据
y = torch.tensor([[1.0], [0.0], [0.0],
[1.0], [1.0], [1.0], [0.0], [0.0], [1.0], [1.0]]) # 目标输出数据
# 创建顺序模型,包含线性层、ReLU激活函数和Sigmoid激活函数
model = nn.Sequential(
nn.Linear(n_in, n_h), # 输入层到隐藏层的线性变换
nn.ReLU(), # 隐藏层的ReLU激活函数
nn.Linear(n_h, n_out), # 隐藏层到输出层的线性变换
nn.Sigmoid() # 输出层的Sigmoid激活函数
)
# 定义均方误差损失函数和随机梯度下降优化器
criterion = torch.nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # 学习率为0.01
# 用于存储每轮的损失值
losses = []
# 执行梯度下降算法进行模型训练
for epoch in range(50): # 迭代50次
y_pred = model(x) # 前向传播,计算预测值
loss = criterion(y_pred, y) # 计算损失
losses.append(loss.item()) # 记录损失值
print(f'Epoch [{epoch+1}/50], Loss: {loss.item():.4f}') # 打印损失值
optimizer.zero_grad() # 清零梯度
loss.backward() # 反向传播,计算梯度
optimizer.step() # 更新模型参数
# 可视化损失变化曲线
plt.figure(figsize=(8, 5))
plt.plot(range(1, 51), losses, label='Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss Over Epochs')
plt.legend()
plt.grid()
plt.show()
# 可视化预测结果与实际目标值对比
y_pred_final = model(x).detach().numpy() # 最终预测值
y_actual = y.numpy() # 实际值
plt.figure(figsize=(8, 5))
plt.plot(range(1, batch_size + 1), y_actual, 'o-', label='Actual', color='blue')
plt.plot(range(1, batch_size + 1), y_pred_final, 'x--', label='Predicted', color='red')
plt.xlabel('Sample Index')
plt.ylabel('Value')
plt.title('Actual vs Predicted Values')
plt.legend()
plt.grid()
plt.show()

img

a7d9d307c8172f34fc906368ab8cf0fe

另外一个实例

我们假设有一个二维数据集,目标是根据点的位置将它们分类到两个类别中(例如,红色和蓝色点)。

以下实例展示了如何使用神经网络完成简单的二分类任务,为更复杂的任务奠定了基础,通过 PyTorch 的模块化接口,神经网络的构建、训练和可视化都非常直观。

1、数据准备

首先,我们生成一些简单的二维数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
# 生成一些随机数据
n_samples = 100
data = torch.randn(n_samples, 2) # 生成 100 个二维数据点
labels = (data[:, 0]**2 + data[:, 1]**2 < 1).float().unsqueeze(1) # 点在圆内为1,圆外为0
# 可视化数据
plt.scatter(data[:, 0], data[:, 1], c=labels.squeeze(), cmap='coolwarm')
plt.title("Generated Data")
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.show()

数据说明:

  • data 是输入的二维点,每个点有两个特征。
  • labels 是目标分类,点在圆形区域内为 1,否则为 0。

显示如下:

img

2、定义神经网络

用 PyTorch 创建一个简单的前馈神经网络。

前馈神经网络使用了一层隐藏层,通过简单的线性变换和激活函数捕获数据的非线性模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class SimpleNN(nn.Module):
def __init__(self):
super(SimpleNN, self).__init__()
# 定义神经网络的层
self.fc1 = nn.Linear(2, 4) # 输入层有 2 个特征,隐藏层有 4 个神经元
self.fc2 = nn.Linear(4, 1) # 隐藏层输出到 1 个神经元(用于二分类)
self.sigmoid = nn.Sigmoid() # 二分类激活函数

def forward(self, x):
x = torch.relu(self.fc1(x)) # 使用 ReLU 激活函数
x = self.sigmoid(self.fc2(x)) # 输出层使用 Sigmoid 激活函数
return x

# 实例化模型
model = SimpleNN()

3、定义损失函数和优化器

1
2
3
# 定义二分类的损失函数和优化器
criterion = nn.BCELoss() # 二元交叉熵损失
optimizer = optim.SGD(model.parameters(), lr=0.1) # 使用随机梯度下降优化器

4、训练模型

用数据训练模型,让它学会分类

1
2
3
4
5
6
7
8
9
10
11
12
13
# 训练
epochs = 100
for epoch in range(epochs):
# 前向传播
outputs = model(data)
loss = criterion(outputs, labels)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 每 10 轮打印一次损失
if (epoch + 1) % 10 == 0:
print(f'Epoch [{epoch + 1}/{epochs}], Loss: {loss.item():.4f}')

5、测试模型并可视化结果

我们测试模型,并在图像上绘制决策边界

1
2
3
4
5
6
7
8
9
10
11
12
13
# 可视化决策边界
def plot_decision_boundary(model, data):
x_min, x_max = data[:, 0].min() - 1, data[:, 0].max() + 1
y_min, y_max = data[:, 1].min() - 1, data[:, 1].max() + 1
xx, yy = torch.meshgrid(torch.arange(x_min, x_max, 0.1), torch.arange(y_min, y_max, 0.1), indexing='ij')
grid = torch.cat([xx.reshape(-1, 1), yy.reshape(-1, 1)], dim=1)
predictions = model(grid).detach().numpy().reshape(xx.shape)
plt.contourf(xx, yy, predictions, levels=[0, 0.5, 1], cmap='coolwarm', alpha=0.7)
plt.scatter(data[:, 0], data[:, 1], c=labels.squeeze(), cmap='coolwarm', edgecolors='k')
plt.title("Decision Boundary")
plt.show()

plot_decision_boundary(model, data)
1
2
3
4
Epoch [10/100], Loss: 0.5247
Epoch [20/100], Loss: 0.3142
...
Epoch [100/100], Loss: 0.0957

图中显示了原始数据点(红色和蓝色),以及模型学习到的分类边界

img

PyTorch 数据处理与加载

在 PyTorch 中,处理和加载数据是深度学习训练过程中的关键步骤。

为了高效地处理数据,PyTorch 提供了强大的工具,包括 torch.utils.data.Datasettorch.utils.data.DataLoader,帮助我们管理数据集批量加载数据增强等任务。

PyTorch 数据处理与加载的介绍:

  • 自定义 Dataset:通过继承 torch.utils.data.Dataset 来加载自己的数据集。
  • DataLoaderDataLoader 按批次加载数据,支持多线程加载并进行数据打乱
  • 数据预处理与增强:使用 torchvision.transforms 进行常见的图像预处理和增强操作,提高模型的泛化能力
  • 加载标准数据集torchvision.datasets 提供了许多常见的数据集,简化了数据加载过程。
  • 多个数据源:通过组合多个 Dataset 实例来处理来自不同来源的数据。

自定义 Dataset

torch.utils.data.Dataset 是一个抽象类,允许你从自己的数据源中创建数据集。

我们需要继承该类并实现以下两个方法:

  • __len__(self):返回数据集中的样本数量。
  • __getitem__(self, idx):通过索引返回一个样本。

假设我们有一个简单的 CSV 文件或一些列表数据,我们可以通过继承 Dataset 类来创建自己的数据集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import torch
from torch.utils.data import Dataset
# 自定义数据集类
class MyDataset(Dataset):
def __init__(self, X_data, Y_data):
"""
初始化数据集,X_data 和 Y_data 是两个列表或数组
X_data: 输入特征
Y_data: 目标标签
"""
self.X_data = X_data
self.Y_data = Y_data

def __len__(self):
"""返回数据集的大小"""
return len(self.X_data)
def __getitem__(self, idx):
"""返回指定索引的数据"""
x = torch.tensor(self.X_data[idx], dtype=torch.float32) # 转换为 Tensor
y = torch.tensor(self.Y_data[idx], dtype=torch.float32)
return x, y
# 示例数据
X_data = [[1, 2], [3, 4], [5, 6], [7, 8]] # 输入特征
Y_data = [1, 0, 1, 0] # 目标标签
# 创建数据集实例
dataset = MyDataset(X_data, Y_data)

使用 DataLoader 加载数据

DataLoader 是 PyTorch 提供的一个重要工具,用于从 Dataset 中按批次(batch)加载数据。

DataLoader 允许我们批量读取数据并进行多线程加载,从而提高训练效率。

1
2
3
4
5
6
7
8
9
from torch.utils.data import DataLoader
# 创建 DataLoader 实例,batch_size 设置每次加载的样本数量
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)
# 打印加载的数据
for epoch in range(1):
for batch_idx, (inputs, labels) in enumerate(dataloader):
print(f'Batch {batch_idx + 1}:')
print(f'Inputs: {inputs}')
print(f'Labels: {labels}')
  • batch_size: 每次加载的样本数量
  • shuffle: 是否对数据进行洗牌,通常训练时需要将数据打乱。
  • drop_last: 如果数据集中的样本数不能被 batch_size 整除,设置为 True 时,丢弃最后一个不完整的 batch。
1
2
3
4
5
6
Batch 1:
Inputs: tensor([[3., 4.], [1., 2.]])
Labels: tensor([0., 1.])
Batch 2:
Inputs: tensor([[7., 8.], [5., 6.]])
Labels: tensor([0., 1.])

每次循环中,DataLoader 会返回一个批次的数据,包括输入特征(inputs)目标标签(labels)

预处理与数据增强

数据预处理和增强对于提高模型的性能至关重要。

PyTorch 提供了 torchvision.transforms 模块来进行常见的图像预处理和增强操作,如旋转、裁剪、归一化等。

常见的图像预处理操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
import torchvision.transforms as transforms
from PIL import Image
# 定义数据预处理的流水线
transform = transforms.Compose([
transforms.Resize((128, 128)), # 将图像调整为 128x128
transforms.ToTensor(), # 将图像转换为张量
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # 标准化
])
# 加载图像
image = Image.open('image.jpg')
# 应用预处理
image_tensor = transform(image)
print(image_tensor.shape) # 输出张量的形状
  • transforms.Compose():将多个变换操作组合在一起。
  • transforms.Resize():调整图像大小。
  • transforms.ToTensor():将图像转换为 PyTorch 张量,值会被归一化到 [0, 1] 范围。
  • transforms.Normalize():标准化图像数据,通常使用预训练模型时需要进行标准化处理。

图像数据增强

数据增强技术通过对训练数据进行随机变换,增加数据的多样性,帮助模型更好地泛化。例如,随机翻转、旋转、裁剪等。

1
2
3
4
5
6
7
transform = transforms.Compose([
transforms.RandomHorizontalFlip(), # 随机水平翻转
transforms.RandomRotation(30), # 随机旋转 30 度
transforms.RandomResizedCrop(128), # 随机裁剪并调整为 128x128
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

这些数据增强方法可以通过 transforms.Compose() 组合使用,保证每个图像在训练时具有不同的变换。

加载图像数据集

对于图像数据集,torchvision.datasets 提供了许多常见数据集(如 CIFAR-10、ImageNet、MNIST 等)以及用于加载图像数据的工具。

加载 MNIST 数据集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import torchvision.datasets as datasets
import torchvision.transforms as transforms
# 定义预处理操作
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,)) # 对灰度图像进行标准化
])
# 下载并加载 MNIST 数据集
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
# 创建 DataLoader
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
# 迭代训练数据
for inputs, labels in train_loader:
print(inputs.shape) # 每个批次的输入数据形状
print(labels.shape) # 每个批次的标签形状
  • datasets.MNIST() 会自动下载 MNIST 数据集并加载
  • transform 参数允许我们对数据进行预处理
  • train=Truetrain=False 分别表示训练集和测试集。

用多个数据源(Multi-source Dataset)

如果你的数据集由多个文件、多个来源(例如多个图像文件夹)组成,可以通过继承 Dataset 类自定义加载多个数据源。

PyTorch 提供了 ConcatDataset 和 ChainDataset 等类来连接多个数据集。

例如,假设我们有多个图像文件夹的数据,可以将它们合并为一个数据集:

1
2
3
4
from torch.utils.data import ConcatDataset
# 假设 dataset1 和 dataset2 是两个 Dataset 对象
combined_dataset = ConcatDataset([dataset1, dataset2])
combined_loader = DataLoader(combined_dataset, batch_size=64, shuffle=True)

PyTorch 线性回归

线性回归是最基本的机器学习算法之一,用于预测一个连续值

线性回归是一种简单且常见的回归分析方法,目的是通过拟合一个线性函数来预测输出

对于一个简单的线性回归问题,模型可以表示为:

img

  • y 是预测值(目标值)。
  • x1,x2,xn 是输入特征。
  • w1,w2,wn是待学习的权重(模型参数)。
  • b 是偏置项。

img

在 PyTorch 中,线性回归模型可以通过继承 nn.Module 类来实现。我们将通过一个简单的示例来详细说明如何使用 PyTorch 实现线性回归模型。

数据准备

我们首先准备一些假数据,用于训练我们的线性回归模型。这里,我们可以生成一个简单的线性关系的数据集,其中每个样本有两个特征 x1,x2。

1
2
3
4
5
6
7
8
9
10
11
12
13
import torch
import numpy as np
import matplotlib.pyplot as plt
# 随机种子,确保每次运行结果一致
torch.manual_seed(42)
# 生成训练数据
X = torch.randn(100, 2) # 100 个样本,每个样本 2 个特征
true_w = torch.tensor([2.0, 3.0]) # 假设真实权重
true_b = 4.0 # 偏置项
Y = X @ true_w + true_b + torch.randn(100) * 0.1 # 加入一些噪声
# 打印部分数据
print(X[:5])
print(Y[:5])

输出结果如下:

1
2
3
4
5
6
tensor([[ 1.9269,  1.4873],
[ 0.9007, -2.1055],
[ 0.6784, -1.2345],
[-0.0431, -1.6047],
[-0.7521, 1.6487]])
tensor([12.4460, -0.4663, 1.7666, -0.9357, 7.4781])

这段代码创建了一个带有噪声的线性数据集,输入 X 为 100x2 的矩阵,每个样本有两个特征,输出 Y 由真实的权重和偏置生成,并加上了一些随机噪声

定义线性回归模型

我们可以通过继承 nn.Module 来定义一个简单的线性回归模型。在 PyTorch 中,线性回归的核心是 nn.Linear() 层,它会自动处理权重和偏置的初始化。

1
2
3
4
5
6
7
8
9
10
11
import torch.nn as nn
# 定义线性回归模型
class LinearRegressionModel(nn.Module):
def __init__(self):
super(LinearRegressionModel, self).__init__()
# 定义一个线性层,输入为2个特征,输出为1个预测值
self.linear = nn.Linear(2, 1) # 输入维度2,输出维度1
def forward(self, x):
return self.linear(x) # 前向传播,返回预测结果
# 创建模型实例
model = LinearRegressionModel()

这里的 nn.Linear(2, 1) 表示一个线性层,它有 2 个输入特征和 1 个输出。forward 方法定义了如何通过这个层进行前向传播。

定义损失函数与优化器

线性回归的常见损失函数是 均方误差损失(MSELoss),用于衡量预测值与真实值之间的差异。PyTorch 中提供了现成的 MSELoss 函数。

我们将使用 SGD(随机梯度下降)Adam 优化器来最小化损失函数。

1
2
3
4
# 损失函数(均方误差)
criterion = nn.MSELoss()
# 优化器(使用 SGD 或 Adam)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # 学习率设置为0.01
  • MSELoss:计算预测值与真实值的均方误差。
  • SGD:使用随机梯度下降法更新参数。

训练模型

在训练过程中,我们将执行以下步骤:

  1. 使用输入数据 X 进行前向传播,得到预测值。
  2. 计算损失(预测值与实际值之间的差异)。
  3. 使用反向传播计算梯度。
  4. 更新模型参数(权重和偏置)。

我们将训练模型 1000 轮,并在每 100 轮打印一次损失。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 训练模型
num_epochs = 1000 # 训练 1000 轮
for epoch in range(num_epochs):
model.train() # 设置模型为训练模式
# 前向传播
predictions = model(X) # 模型输出预测值
loss = criterion(predictions.squeeze(), Y) # 计算损失(注意预测值需要压缩为1D)
# 反向传播
optimizer.zero_grad() # 清空之前的梯度
loss.backward() # 计算梯度
optimizer.step() # 更新模型参数
# 打印损失
if (epoch + 1) % 100 == 0:
print(f'Epoch [{epoch + 1}/1000], Loss: {loss.item():.4f}')
  • predictions.squeeze():我们在这里将模型的输出从 2D 张量压缩为 1D,因为目标值 Y 是一个一维数组。
  • optimizer.zero_grad():每次反向传播前需要清空之前的梯度。
  • loss.backward():计算梯度。
  • optimizer.step():更新权重和偏置。

评估模型

训练完成后,我们可以通过查看模型的权重和偏置来评估模型的效果。我们还可以在新的数据上进行预测并与实际值进行比较

1
2
3
4
5
6
7
8
9
10
11
# 查看训练后的权重和偏置
print(f'Predicted weight: {model.linear.weight.data.numpy()}')
print(f'Predicted bias: {model.linear.bias.data.numpy()}')
# 在新数据上做预测
with torch.no_grad(): # 评估时不需要计算梯度
predictions = model(X)
# 可视化预测与实际值
plt.scatter(X[:, 0], Y, color='blue', label='True values')
plt.scatter(X[:, 0], predictions, color='red', label='Predictions')
plt.legend()
plt.show()
  • model.linear.weight.datamodel.linear.bias.data:这些属性存储了模型的权重和偏置。
  • torch.no_grad():在评估模式下,不需要计算梯度,节省内存。

结果分析

在训练过程中,随着损失逐渐减小,我们希望最终的模型能够拟合我们生成的数据。通过查看训练后的权重和偏置,我们可以比较其与真实值(true_wtrue_b)的差异。理论上,模型的输出权重应该接近 true_wtrue_b

在可视化的散点图中,蓝色点表示真实值,红色点表示模型的预测值。我们希望看到红色点与蓝色点尽可能接近,表明模型成功学习了数据的线性关系。

PyTorch 卷积神经网络

PyTorch 卷积神经网络 (Convolutional Neural Networks, CNN) 是一类专门用于处理具有网格状拓扑结构数据(如图像)深度学习模型。

CNN 是计算机视觉任务(如图像分类、目标检测和分割)的核心技术。

下面这张图展示了一个典型的卷积神经网络(CNN)的结构和工作流程,用于图像识别任务。

img

在图中,CNN 的输出层给出了三个类别的概率:Donald(0.2)、Goofy(0.1)和Tweety(0.7),这表明网络认为输入图像最有可能是 Tweety。

以下是各个部分的简要说明:

  • 输入图像(Input Image):网络接收的原始图像数据。
  • 卷积(Convolution):使用卷积核(Kernel)在输入图像上滑动,提取特征生成特征图(Feature Maps)
  • 池化(Pooling):通常在卷积层之后,通过最大池化或平均池化减少特征图的尺寸,同时保留重要特征,生成池化特征图(Pooled Feature Maps)
  • 特征提取(Feature Extraction):通过多个卷积和池化层的组合,逐步提取图像的高级特征。
  • 展平层(Flatten Layer):将多维的特征图转换为一维向量,以便输入到全连接层。
  • 全连接层(Fully Connected Layer):类似于传统的神经网络层,用于将提取的特征映射到输出类别。
  • 分类(Classification):网络的输出层,根据全连接层的输出进行分类。
  • 概率分布(Probabilistic Distribution):输出层给出每个类别的概率,表示输入图像属于各个类别的可能性。

卷积神经网络的基本结构

1、输入层(Input Layer)

接收原始图像数据,图像通常被表示为一个三维数组,其中两个维度代表图像的宽度和高度,第三个维度代表颜色通道(例如,RGB图像有三个通道)。

2、卷积层(Convolutional Layer)

用卷积核提取局部特征,如边缘、纹理等。

img

  • x:输入图像。
  • k:卷积核(权重矩阵)。
  • b:偏置。

应用一组可学习的滤波器(或卷积核)在输入图像上进行卷积操作,以提取局部特征。

每个滤波器在输入图像上滑动,生成一个特征图(Feature Map),表示滤波器在不同位置的激活。

卷积层可以有多个滤波器,每个滤波器生成一个特征图,所有特征图组成一个特征图集合。

3、激活函数(Activation Function)

通常在卷积层之后应用非线性激活函数,如 ReLU(Rectified Linear Unit),以引入非线性特性,使网络能够学习更复杂的模式。

ReLU 函数定义为 :f(x)=max(0,x),即如果输入小于 0 则输出 0,否则输出输入值

4、池化层(Pooling Layer)

  • 用于降低特征图的空间维度,减少计算量和参数数量,同时保留最重要的特征信息。
  • 最常见的池化操作是最大池化(Max Pooling)和平均池化(Average Pooling)。
  • 最大池化选择区域内的最大值,而平均池化计算区域内的平均值。

5、归一化层(Normalization Layer,可选)

  • 例如,局部响应归一化(Local Response Normalization, LRN)或批归一化(Batch Normalization)。
  • 这些层有助于加速训练过程,提高模型的稳定性

6、全连接层(Fully Connected Layer)

  • CNN 的末端,将前面层提取的特征图展平(Flatten)成一维向量,然后输入到全连接层。
  • 全连接层的每个神经元都与前一层的所有神经元相连,用于综合特征并进行最终的分类或回归

7、输出层(Output Layer)

根据任务的不同,输出层可以有不同的形式。

对于分类任务,通常使用 Softmax 函数将输出转换为概率分布,表示输入属于各个类别的概率。

8、损失函数(Loss Function)

用于衡量模型预测与真实标签之间的差异

常见的损失函数包括交叉熵损失(Cross-Entropy Loss)用于多分类任务,均方误差(Mean Squared Error, MSE)用于回归任务。

9、优化器(Optimizer)

用于根据损失函数的梯度更新网络的权重。常见的优化器包括随机梯度下降(SGD)、Adam、RMSprop等。

10、正则化(Regularization,可选)

包括 Dropout、L1/L2 正则化等技术,用于防止模型过拟合

这些层可以堆叠形成更深的网络结构,以提高模型的学习能力。

CNN 的深度和复杂性可以根据任务的需求进行调整。

PyTorch 实现一个 CNN 实例

以下示例展示如何用 PyTorch 构建一个简单的 CNN 模型,用于 MNIST 数据集的数字分类。

主要步骤:

  • 数据加载与预处理:使用 torchvision 加载和预处理 MNIST 数据。
  • 模型构建:定义卷积层、池化层和全连接层。
  • 训练:通过损失函数和优化器进行模型训练。
  • 评估:测试集上计算模型的准确率。
  • 可视化:展示部分测试样本及其预测结果。

1、导入必要库

1
2
3
4
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

2、数据加载

使用 torchvision 提供的 MNIST 数据集,加载和预处理数据。

1
2
3
4
5
6
7
8
9
transform = transforms.Compose([
transforms.ToTensor(), # 转为张量
transforms.Normalize((0.5,), (0.5,)) # 归一化到 [-1, 1]
])
# 加载 MNIST 数据集
train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
test_dataset = datasets.MNIST(root='./data', train=False, transform=transform, download=True)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)

3、定义 CNN 模型

使用 nn.Module 构建一个 CNN。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
# 定义卷积层:输入1通道,输出32通道,卷积核大小3x3
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)
# 定义卷积层:输入32通道,输出64通道
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)
# 定义全连接层
self.fc1 = nn.Linear(64 * 7 * 7, 128) # 输入大小 = 特征图大小 * 通道数
self.fc2 = nn.Linear(128, 10) # 10 个类别
def forward(self, x):
x = F.relu(self.conv1(x)) # 第一层卷积 + ReLU
x = F.max_pool2d(x, 2) # 最大池化
x = F.relu(self.conv2(x)) # 第二层卷积 + ReLU
x = F.max_pool2d(x, 2) # 最大池化
x = x.view(-1, 64 * 7 * 7) # 展平操作
x = F.relu(self.fc1(x)) # 全连接层 + ReLU
x = self.fc2(x) # 全连接层输出
return x
# 创建模型实例
model = SimpleCNN()

4、定义损失函数与优化器

使用交叉熵损失和随机梯度下降优化器。

1
2
criterion = nn.CrossEntropyLoss()  # 多分类交叉熵损失
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9) # 学习率和动量

5、训练模型

训练模型 5 个 epoch,每个 epoch 后输出训练损失。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
num_epochs = 5
model.train() # 设为训练模式
for epoch in range(num_epochs):
total_loss = 0
for images, labels in train_loader:
# 前向传播
outputs = model(images)
loss = criterion(outputs, labels)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {total_loss / len(train_loader):.4f}")

6、测试模型

在测试集上评估模型的准确率。

1
2
3
4
5
6
7
8
9
10
11
12
13
model.eval()  # 设置为评估模式
correct = 0
total = 0

with torch.no_grad(): # 评估时不需要计算梯度
for images, labels in test_loader:
outputs = model(images)
_, predicted = torch.max(outputs, 1) # 预测类别
total += labels.size(0)
correct += (predicted == labels).sum().item()

accuracy = 100 * correct / total
print(f"Test Accuracy: {accuracy:.2f}%")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms


# 1. 数据加载与预处理
transform = transforms.Compose([
transforms.ToTensor(), # 转为张量
transforms.Normalize((0.5,), (0.5,)) # 归一化到 [-1, 1]
])

# 加载 MNIST 数据集
train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
test_dataset = datasets.MNIST(root='./data', train=False, transform=transform, download=True)

train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)

# 2. 定义 CNN 模型
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
# 定义卷积层
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1) # 输入1通道,输出32通道
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1) # 输入32通道,输出64通道
# 定义全连接层
self.fc1 = nn.Linear(64 * 7 * 7, 128) # 展平后输入到全连接层
self.fc2 = nn.Linear(128, 10) # 10 个类别

def forward(self, x):
x = F.relu(self.conv1(x)) # 第一层卷积 + ReLU
x = F.max_pool2d(x, 2) # 最大池化
x = F.relu(self.conv2(x)) # 第二层卷积 + ReLU
x = F.max_pool2d(x, 2) # 最大池化
x = x.view(-1, 64 * 7 * 7) # 展平
x = F.relu(self.fc1(x)) # 全连接层 + ReLU
x = self.fc2(x) # 最后一层输出
return x

# 创建模型实例
model = SimpleCNN()

# 3. 定义损失函数与优化器
criterion = nn.CrossEntropyLoss() # 多分类交叉熵损失
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

# 4. 模型训练
num_epochs = 5
model.train() # 设置模型为训练模式

for epoch in range(num_epochs):
total_loss = 0
for images, labels in train_loader:
outputs = model(images) # 前向传播
loss = criterion(outputs, labels) # 计算损失

optimizer.zero_grad() # 清空梯度
loss.backward() # 反向传播
optimizer.step() # 更新参数

total_loss += loss.item()

print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {total_loss / len(train_loader):.4f}")

# 5. 模型测试
model.eval() # 设置模型为评估模式
correct = 0
total = 0

with torch.no_grad(): # 关闭梯度计算
for images, labels in test_loader:
outputs = model(images)
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()

accuracy = 100 * correct / total
print(f"Test Accuracy: {accuracy:.2f}%")

运行结果说明

1. 输出的训练损失

代码中每个 epoch 会输出一次平均损失,例如:

1
2
3
4
5
Epoch [1/5], Loss: 0.2325
Epoch [2/5], Loss: 0.0526
Epoch [3/5], Loss: 0.0366
Epoch [4/5], Loss: 0.0273
Epoch [5/5], Loss: 0.0221

解释:损失逐渐下降表明模型在逐步收敛。

2. 测试集的准确率

代码在测试集上输出最终的分类准确率,例如:

1
Test Accuracy: 98.96%

解释:模型对 MNIST 测试集的分类准确率为 98.96%,对于简单的 CNN 模型来说是一个不错的结果。

7、可视化结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import matplotlib.pyplot as plt

# 1. 数据加载与预处理
transform = transforms.Compose([
transforms.ToTensor(), # 转为张量
transforms.Normalize((0.5,), (0.5,)) # 归一化到 [-1, 1]
])

# 加载 MNIST 数据集
train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
test_dataset = datasets.MNIST(root='./data', train=False, transform=transform, download=True)

train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)

# 2. 定义 CNN 模型
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
# 定义卷积层
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1) # 输入1通道,输出32通道
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1) # 输入32通道,输出64通道
# 定义全连接层
self.fc1 = nn.Linear(64 * 7 * 7, 128) # 展平后输入到全连接层
self.fc2 = nn.Linear(128, 10) # 10 个类别

def forward(self, x):
x = F.relu(self.conv1(x)) # 第一层卷积 + ReLU
x = F.max_pool2d(x, 2) # 最大池化
x = F.relu(self.conv2(x)) # 第二层卷积 + ReLU
x = F.max_pool2d(x, 2) # 最大池化
x = x.view(-1, 64 * 7 * 7) # 展平
x = F.relu(self.fc1(x)) # 全连接层 + ReLU
x = self.fc2(x) # 最后一层输出
return x

# 创建模型实例
model = SimpleCNN()

# 3. 定义损失函数与优化器
criterion = nn.CrossEntropyLoss() # 多分类交叉熵损失
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

# 4. 模型训练
num_epochs = 5
model.train() # 设置模型为训练模式

for epoch in range(num_epochs):
total_loss = 0
for images, labels in train_loader:
outputs = model(images) # 前向传播
loss = criterion(outputs, labels) # 计算损失

optimizer.zero_grad() # 清空梯度
loss.backward() # 反向传播
optimizer.step() # 更新参数

total_loss += loss.item()

print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {total_loss / len(train_loader):.4f}")

# 5. 模型测试
model.eval() # 设置模型为评估模式
correct = 0
total = 0

with torch.no_grad(): # 关闭梯度计算
for images, labels in test_loader:
outputs = model(images)
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()

accuracy = 100 * correct / total
print(f"Test Accuracy: {accuracy:.2f}%")

# 6. 可视化测试结果
dataiter = iter(test_loader)
images, labels = next(dataiter)
outputs = model(images)
_, predictions = torch.max(outputs, 1)

fig, axes = plt.subplots(1, 6, figsize=(12, 4))
for i in range(6):
axes[i].imshow(images[i][0], cmap='gray')
axes[i].set_title(f"Label: {labels[i]}\nPred: {predictions[i]}")
axes[i].axis('off')
plt.show()

PyTorch 循环神经网络(RNN)

循环神经网络(Recurrent Neural Networks, RNN)是一类神经网络架构,专门用于处理序列数据,能够捕捉时间序列或有序数据的动态信息,能够处理序列数据,如文本、时间序列或音频。

RNN 在自然语言处理(NLP)、语音识别、时间序列预测等任务中有着广泛的应用。

RNN 的关键特性是其能够保持隐状态(hidden state),使得网络能够记住先前时间步的信息,这对于处理序列数据至关重要。

RNN 的基本结构

在传统的前馈神经网络(Feedforward Neural Network)中,数据是从输入层流向输出层的,而在 RNN 中,数据不仅沿着网络层级流动,还会在每个时间步骤上传播到当前的隐层状态,从而将之前的信息传递到下一个时间步骤。

隐状态(Hidden State): RNN 通过隐状态来记住序列中的信息

隐状态是通过上一时间步的隐状态和当前输入共同计算得到的。

公式:

img

  • ht:当前时刻的隐状态。
  • ht-1:前一时刻的隐状态。
  • Xt:当前时刻的输入
  • Whh、Wxh:权重矩阵
  • b:偏置项。
  • f:激活函数(如 Tanh 或 ReLU)

输出(Output): RNN 的输出不仅依赖当前的输入,还依赖于隐状态的历史信息

公式:

img

  • yt:当前时刻的隐状态。
  • Why:当前时刻的隐状态。

RNN 如何处理序列数据

循环神经网络(RNN)在处理序列数据时的展开(unfold)视图如下:

img

RNN 是一种处理序列数据的神经网络,它通过循环连接来处理序列中的每个元素,并在每个时间步传递信息,以下是图中各部分的说明:

  • 输入序列(Xt, Xt-1, Xt+1, …):图中的粉色圆圈代表输入序列中的各个元素,如Xt表示当前时间步的输入,Xt-1表示前一个时间步的输入,以此类推。
  • 隐藏状态(ht, ht-1, ht+1, …):绿色矩形代表RNN的隐藏状态,它在每个时间步存储有关序列的信息。ht是当前时间步的隐藏状态,ht-1是前一个时间步的隐藏状态。
  • 权重矩阵(U, W, V)
    • U输入到隐藏状态的权重矩阵,用于将输入Xt转换为隐藏状态的一部分
    • W隐藏状态到隐藏状态的权重矩阵,用于将前一时间步的隐藏状态ht-1转换为当前时间步隐藏状态的一部分。
    • V隐藏状态到输出的权重矩阵,用于将隐藏状态ht转换为输出Yt
  • 输出序列(Yt, Yt-1, Yt+1, …):蓝色圆圈代表RNN在每个时间步的输出,如Yt是当前时间步的输出。
  • 循环连接:RNN的特点是隐藏状态的循环连接,这允许网络在处理当前时间步的输入时考虑到之前时间步的信息
  • 展开(Unfold):图中展示了RNN在序列上的展开过程,这有助于理解RNN如何在时间上处理序列数据。在实际的RNN实现中,这些步骤是并行处理的,但在概念上,我们可以将其展开来理解信息是如何流动的。
  • 信息流动:信息从输入序列通过权重矩阵U传递到隐藏状态,然后通过权重矩阵W在时间步之间传递,最后通过权重矩阵V从隐藏状态传递到输出序列。

PyTorch 中的 RNN 基础

在 PyTorch 中,RNN 可以用于构建复杂的序列模型。

PyTorch 提供了几种 RNN 模块,包括:

  • torch.nn.RNN基本的RNN单元。
  • torch.nn.LSTM长短期记忆单元,能够学习长期依赖关系。
  • torch.nn.GRU:门控循环单元,是LSTM的简化版本,但通常更容易训练。

使用 RNN 类时,您需要指定输入的维度、隐藏层的维度以及其他一些超参数。

PyTorch 实现一个简单的 RNN 实例

以下是一个简单的 PyTorch 实现例子,使用 RNN 模型来处理序列数据并进行分类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
import numpy as np

# 数据集:字符序列预测(Hello -> Elloh)
char_set = list("hello")
char_to_idx = {c: i for i, c in enumerate(char_set)}
idx_to_char = {i: c for i, c in enumerate(char_set)}

# 数据准备
input_str = "hello"
target_str = "elloh"
input_data = [char_to_idx[c] for c in input_str]
target_data = [char_to_idx[c] for c in target_str]

# 转换为独热编码
input_one_hot = np.eye(len(char_set))[input_data]

# 转换为 PyTorch Tensor
inputs = torch.tensor(input_one_hot, dtype=torch.float32)
targets = torch.tensor(target_data, dtype=torch.long)

# 模型超参数
input_size = len(char_set)
hidden_size = 8
output_size = len(char_set)
num_epochs = 200
learning_rate = 0.1

# 定义 RNN 模型
class RNNModel(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(RNNModel, self).__init__()
self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)

def forward(self, x, hidden):
out, hidden = self.rnn(x, hidden)
out = self.fc(out) # 应用全连接层
return out, hidden

model = RNNModel(input_size, hidden_size, output_size)

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

# 训练 RNN
losses = []
hidden = None # 初始隐藏状态为 None
for epoch in range(num_epochs):
optimizer.zero_grad()

# 前向传播
outputs, hidden = model(inputs.unsqueeze(0), hidden)
hidden = hidden.detach() # 防止梯度爆炸

# 计算损失
loss = criterion(outputs.view(-1, output_size), targets)
loss.backward()
optimizer.step()
losses.append(loss.item())

if (epoch + 1) % 20 == 0:
print(f"Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}")

# 测试 RNN
with torch.no_grad():
test_hidden = None
test_output, _ = model(inputs.unsqueeze(0), test_hidden)
predicted = torch.argmax(test_output, dim=2).squeeze().numpy()

print("Input sequence: ", ''.join([idx_to_char[i] for i in input_data]))
print("Predicted sequence: ", ''.join([idx_to_char[i] for i in predicted]))

# 可视化损失
plt.plot(losses, label="Training Loss")
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.title("RNN Training Loss Over Epochs")
plt.legend()
plt.show()

代码解析:

  1. 数据准备
    • 使用字符序列 hello,并将其转化为独热编码。
    • 目标序列为 elloh,即向右旋转一个字符。
  2. 模型构建
    • 使用 torch.nn.RNN 创建循环神经网络。
    • 加入全连接层 torch.nn.Linear 用于映射隐藏状态到输出。
  3. 训练部分
    • 每一轮都计算损失并反向传播。
    • 隐藏状态通过 hidden.detach() 防止梯度爆炸
  4. 测试部分
    • 模型输出字符的预测结果。
  5. 可视化
    • 用 Matplotlib 绘制训练损失的变化趋势。

假设你的模型训练良好,输出可能如下:

1
2
3
4
5
6
7
8
9
10
11
Epoch [20/200], Loss: 0.0013
Epoch [40/200], Loss: 0.0003
Epoch [60/200], Loss: 0.0002
Epoch [80/200], Loss: 0.0001
Epoch [100/200], Loss: 0.0001
Epoch [120/200], Loss: 0.0001
Epoch [140/200], Loss: 0.0001
Epoch [160/200], Loss: 0.0001
Epoch [180/200], Loss: 0.0001
Epoch [200/200], Loss: 0.0001
Input sequence: hello

从结果来看,图像显示损失逐渐减少,表明模型训练有效。

PyTorch 数据集

在深度学习任务中,数据加载和处理是至关重要的一环。

PyTorch 提供了强大的数据加载和处理工具,主要包括:

  • torch.utils.data.Dataset:数据集的抽象类,需要自定义并实现 __len__(数据集大小)和 __getitem__(按索引获取样本)
  • torch.utils.data.TensorDataset基于张量的数据集,适合处理数据-标签对,直接支持批处理和迭代。
  • torch.utils.data.DataLoader封装 Dataset 的迭代器,提供批处理、数据打乱、多线程加载等功能,便于数据输入模型训练。
  • torchvision.datasets.ImageFolder从文件夹加载图像数据,每个子文件夹代表一个类别,适用于图像分类任务。

PyTorch 内置数据集

PyTorch 通过 torchvision.datasets 模块提供了许多常用的数据集,例如:

  • MNIST手写数字图像数据集,用于图像分类任务。
  • CIFAR:包含 10 个类别、60000 张 32x32 的彩色图像数据集,用于图像分类任务。
  • COCO通用物体检测、分割、关键点检测数据集,包含超过 330k 个图像和 2.5M 个目标实例的大规模数据集。
  • ImageNet:包含超过 1400 万张图像,用于图像分类和物体检测等任务
  • STL-10:包含 100k 张 96x96 的彩色图像数据集,用于图像分类任务
  • Cityscapes:包含 5000 张精细注释的城市街道场景图像,用于语义分割任务
  • SQUAD用于机器阅读理解任务的数据集

以上数据集可以通过 torchvision.datasets 模块中的函数进行加载,也可以通过自定义的方式加载其他数据集。

torchvision 和 torchtext

  • torchvision: 一个图形库,提供了图片数据处理相关的 API 和数据集接口,包括数据集加载函数和常用的图像变换。
  • torchtext自然语言处理工具包,提供了文本数据处理和建模的工具,包括数据预处理和数据加载的方式。

torch.utils.data.Dataset

Dataset 是 PyTorch 中用于数据集抽象的类。

自定义数据集需要继承 torch.utils.data.Dataset 并重写以下两个方法:

  • __len__:返回数据集的大小。
  • __getitem__:按索引获取一个数据样本及其标签。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import torch
from torch.utils.data import Dataset

# 自定义数据集
class MyDataset(Dataset):
def __init__(self, data, labels):
# 数据初始化
self.data = data
self.labels = labels

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

def __getitem__(self, idx):
# 按索引返回数据和标签
sample = self.data[idx]
label = self.labels[idx]
return sample, label

# 生成示例数据
data = torch.randn(100, 5) # 100 个样本,每个样本有 5 个特征
labels = torch.randint(0, 2, (100,)) # 100 个标签,取值为 0 或 1

# 实例化数据集
dataset = MyDataset(data, labels)

# 测试数据集
print("数据集大小:", len(dataset))
print("第 0 个样本:", dataset[0])
1
2
数据集大小: 100
0 个样本: (tensor([-0.2006, 0.7304, -1.3911, -0.4408, 1.1447]), tensor(0))

torch.utils.data.DataLoader

DataLoader 是 PyTorch 提供的数据加载器,用于批量加载数据集。

提供了以下功能:

  • 批量加载:通过设置 batch_size
  • 数据打乱:通过设置 shuffle=True
  • 多线程加速:通过设置 num_workers
  • 迭代访问:方便地按批次访问数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import torch
from torch.utils.data import Dataset
from torch.utils.data import DataLoader

# 自定义数据集
class MyDataset(Dataset):
def __init__(self, data, labels):
# 数据初始化
self.data = data
self.labels = labels

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

def __getitem__(self, idx):
# 按索引返回数据和标签
sample = self.data[idx]
label = self.labels[idx]
return sample, label

# 生成示例数据
data = torch.randn(100, 5) # 100 个样本,每个样本有 5 个特征
labels = torch.randint(0, 2, (100,)) # 100 个标签,取值为 0 或 1

# 实例化数据集
dataset = MyDataset(data, labels)
# 实例化 DataLoader
dataloader = DataLoader(dataset, batch_size=10, shuffle=True, num_workers=0)

# 遍历 DataLoader
for batch_idx, (batch_data, batch_labels) in enumerate(dataloader):
print(f"批次 {batch_idx + 1}")
print("数据:", batch_data)
print("标签:", batch_labels)
if batch_idx == 2: # 仅显示前 3 个批次
break
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
批次 1
数据: tensor([[ 0.4689, 0.6666, -1.0234, 0.8948, 0.4503],
[ 0.0273, -0.4684, -0.7762, 0.7963, 0.2168],
[ 1.0677, -0.3502, -0.9594, -1.1318, -0.2196],
[-1.4989, 0.0267, 1.0405, -0.7284, 0.2335],
[-0.5887, -0.4934, 1.6283, 1.4638, 0.0157],
[-1.1047, -0.6550, -0.0381, 0.3617, -1.2792],
[ 0.3592, -0.8264, 0.0231, -1.5508, 0.6833],
[-0.6835, 0.6979, 0.9048, -0.4756, 0.3003],
[ 1.1562, -0.4516, -1.2415, 0.2859, 0.5837],
[ 0.7937, 1.5316, -0.6139, 0.7999, 0.5506]])
标签: tensor([0, 1, 1, 1, 1, 0, 1, 1, 0, 0])
批次 2
数据: tensor([[-0.0388, -0.3658, 0.8993, -1.5027, 1.0738],
[-0.6182, 1.0684, -2.3049, 0.8338, 0.1363],
[-0.5289, 0.1661, -0.0349, 0.2112, 1.4745],
[-0.3304, -1.2114, -0.2982, -0.3006, 0.5252],
[-1.4394, -0.3732, 1.0281, 0.5754, 1.0081],
[ 0.8714, -0.1945, -0.2451, -0.2879, -2.0520],
[ 0.0235, 0.4360, 0.1233, 0.0504, 0.5908],
[ 0.5927, 0.1785, -0.9052, -0.9012, 0.8914],
[ 0.4693, 0.5533, -0.1903, 0.0267, 0.4077],
[-1.1683, 1.6699, -0.4846, -0.7404, 0.3370]])
标签: tensor([1, 1, 0, 1, 0, 1, 1, 0, 1, 1])
批次 3
数据: tensor([[ 0.2103, -0.7839, 1.4899, 2.2749, -0.7548],
[-1.2836, 1.0025, -1.1162, -0.4261, 1.0690],
[-0.7969, 1.0418, -0.7405, 0.8766, 0.2347],
[-1.1071, 1.8560, -1.2979, -0.8364, -0.2925],
[-1.0488, 0.4802, -0.6453, 0.2009, 0.5693],
[ 0.8883, 0.4619, -0.2087, 0.2189, -0.3708],
[-1.4578, 0.3629, 1.8282, 0.5353, -1.1783],
[-1.2813, 0.5129, -0.4598, -0.2131, -1.2804],
[ 1.7831, 1.1730, -0.2305, -0.6550, 0.1197],
[-0.9384, -0.0483, 1.9626, 0.3342, 0.1700]])
标签: tensor([0, 0, 0, 1, 0, 1, 1, 1, 0, 1])

使用内置数据集

PyTorch 提供了多个常用数据集,存放在 torchvision 中,特别适合图像任务。

加载 MNIST 数据集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader

# 定义数据预处理
transform = transforms.Compose([
transforms.ToTensor(), # 转换为张量
transforms.Normalize((0.5,), (0.5,)) # 标准化
])

# 加载训练数据集
train_dataset = torchvision.datasets.MNIST(
root='./data', train=True, transform=transform, download=True)

# 使用 DataLoader 加载数据
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)

# 查看一个批次的数据
data_iter = iter(train_loader)
images, labels = next(data_iter)
print(f"批次图像大小: {images.shape}") # 输出形状为 [batch_size, 1, 28, 28]
print(f"批次标签: {labels}")

输出结果为:

1
2
3
批次图像大小: torch.Size([32, 1, 28, 28])
批次标签: tensor([0, 4, 9, 8, 1, 3, 8, 1, 7, 2, 1, 1, 1, 2, 6, 3, 9, 7, 6, 9, 4, 9, 7, 1,
3, 7, 3, 0, 7, 7, 6, 7])

Dataset 与 DataLoader 的自定义应用

以下是一个将 CSV 文件 作为数据源,并通过自定义 Dataset 和 DataLoader 读取数据

CSV 文件内容如下(下载runoob_pytorch_data.csv):

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import torch
import pandas as pd
from torch.utils.data import Dataset, DataLoader

# 自定义 CSV 数据集
class CSVDataset(Dataset):
def __init__(self, file_path):
# 读取 CSV 文件
self.data = pd.read_csv(file_path)

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

def __getitem__(self, idx):
# 使用 .iloc 明确基于位置索引
row = self.data.iloc[idx]
# 将特征和标签分开
features = torch.tensor(row.iloc[:-1].to_numpy(), dtype=torch.float32) # 特征
label = torch.tensor(row.iloc[-1], dtype=torch.float32) # 标签
return features, label

# 实例化数据集和 DataLoader
dataset = CSVDataset("runoob_pytorch_data.csv")
dataloader = DataLoader(dataset, batch_size=4, shuffle=True)

# 遍历 DataLoader
for features, label in dataloader:
print("特征:", features)
print("标签:", label)
break

输出结果为:

1
2
3
4
5
6
7
8
9
10
11
特征: tensor([[ 1.2000,  2.1000, -3.0000],
[ 1.0000, 1.1000, -2.0000],
[ 0.5000, -1.2000, 3.3000],
[-0.3000, 0.8000, 1.2000]])
标签: tensor([1., 0., 1., 0.])
tianqixin@Mac-mini runoob-test % python3 test.py
特征: tensor([[ 1.5000, 2.2000, -1.1000],
[ 2.1000, -3.3000, 0.0000],
[-2.3000, 0.4000, 0.7000],
[-0.3000, 0.8000, 1.2000]])
标签: tensor([0., 1., 0., 0.])

PyTorch 数据转换

在 PyTorch 中,数据转换(Data Transformation) 是一种在加载数据时对数据进行处理的机制,将原始数据转换成适合模型训练的格式,主要通过 torchvision.transforms 提供的工具完成。

数据转换不仅可以实现基本的数据预处理(如归一化、大小调整等),还能帮助进行数据增强(如随机裁剪、翻转等),提高模型的泛化能力

为什么需要数据转换?

数据预处理

  • 调整数据格式、大小和范围,使其适合模型输入
  • 例如,图像需要调整为固定大小、张量格式并归一化到 [0,1]。

数据增强

  • 在训练时对数据进行变换,以增加多样性
  • 例如,通过随机旋转、翻转和裁剪增加数据样本的变种,避免过拟合

灵活性

  • 通过定义一系列转换操作,可以动态地对数据进行处理,简化数据加载的复杂度。

在 PyTorch 中,torchvision.transforms 模块提供了多种用于图像处理的变换操作。

基础变换操作

变换函数名称 描述 实例
transforms.ToTensor() PIL图像或NumPy数组转换为PyTorch张量,并自动将像素值归一化到 [0, 1] transform = transforms.ToTensor()
transforms.Normalize(mean, std) 对图像进行标准化,使数据符合零均值和单位方差 transform = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
transforms.Resize(size) 调整图像尺寸,确保输入到网络的图像大小一致 transform = transforms.Resize((256, 256))
transforms.CenterCrop(size) 从图像中心裁剪指定大小的区域。 transform = transforms.CenterCrop(224)

1、ToTensor

将 PIL 图像或 NumPy 数组转换为 PyTorch 张量。

同时将像素值从 [0, 255] 归一化为 [0, 1]。

1
2
3
from torchvision import transforms

transform = transforms.ToTensor()

2、Normalize

对数据进行标准化,使其符合特定的均值和标准差。

通常用于图像数据,将其像素值归一化为零均值和单位方差。

1
transform = transforms.Normalize(mean=[0.5], std=[0.5])  # 归一化到 [-1, 1]

3、Resize

调整图像的大小。

1
transform = transforms.Resize((128, 128))  # 将图像调整为 128x128

4、CenterCrop

从图像中心裁剪指定大小的区域。

1
transform = transforms.CenterCrop(128)  # 裁剪 128x128 的区域

数据增强操作

变换函数名称 描述 实例
transforms.RandomHorizontalFlip(p) 随机水平翻转图像。 transform = transforms.RandomHorizontalFlip(p=0.5)
transforms.RandomRotation(degrees) 随机旋转图像。 transform = transforms.RandomRotation(degrees=45)
transforms.ColorJitter(brightness, contrast, saturation, hue) 调整图像的亮度、对比度、饱和度和色调 transform = transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1)
transforms.RandomCrop(size) 随机裁剪指定大小的区域。 transform = transforms.RandomCrop(224)
transforms.RandomResizedCrop(size) 随机裁剪图像并调整到指定大小。 transform = transforms.RandomResizedCrop(224)

1、RandomCrop

从图像中随机裁剪指定大小。

1
transform = transforms.RandomCrop(128)

2、RandomHorizontalFlip

以一定概率水平翻转图像。

1
transform = transforms.RandomHorizontalFlip(p=0.5)  # 50% 概率翻转

3、RandomRotation

随机旋转一定角度。

1
transform = transforms.RandomRotation(degrees=30)  # 随机旋转 -30 到 +30 度

4、ColorJitter

随机改变图像的亮度、对比度、饱和度或色调。

1
transform = transforms.ColorJitter(brightness=0.5, contrast=0.5)

组合变换

变换函数名称 描述 实例
transforms.Compose() 将多个变换组合在一起,按照顺序依次应用。 transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), transforms.Resize((256, 256))])

自定义转换

如果 transforms 提供的功能无法满足需求,可以通过自定义类或函数实现。

1
2
3
4
5
6
class CustomTransform:
def __call__(self, x):
# 这里可以自定义任何变换逻辑
return x * 2

transform = CustomTransform()

实例

对图像数据集应用转换

加载 MNIST 数据集,并应用转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# 定义转换
transform = transforms.Compose([
transforms.Resize((128, 128)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.5], std=[0.5])
])
# 加载数据集
train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
# 使用 DataLoader
train_loader = DataLoader(dataset=train_dataset, batch_size=32, shuffle=True)
# 查看转换后的数据
for images, labels in train_loader:
print("图像张量大小:", images.size()) # [batch_size, 1, 128, 128]
break

输出结果为:

1
图像张量大小: torch.Size([32, 1, 128, 128])

可视化转换效果

以下代码展示了原始数据和经过转换后的数据对比。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import matplotlib.pyplot as plt
from torchvision import datasets
from torchvision import datasets, transforms
# 原始和增强后的图像可视化
transform_augment = transforms.Compose([
transforms.RandomHorizontalFlip(),
transforms.RandomRotation(30),
transforms.ToTensor()
])
# 加载数据集
dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform_augment)
# 显示图像
def show_images(dataset):
fig, axs = plt.subplots(1, 5, figsize=(15, 5))
for i in range(5):
image, label = dataset[i]
axs[i].imshow(image.squeeze(0), cmap='gray') # 将 (1, H, W) 转为 (H, W)
axs[i].set_title(f"Label: {label}")
axs[i].axis('off')
plt.show()
show_images(dataset)

img

Pytorch torch 参考手册

PyTorch 软件包包含了用于多维张量的数据结构,并定义了在这些张量上执行的数学运算。此外,它还提供了许多实用工具,用于高效地序列化张量和任意类型的数据,以及其他有用的工具。

它还有一个 CUDA 版本,可以让你在计算能力 >= 3.0 的 NVIDIA GPU 上运行张量计算。

PyTorch torch API 手册

类别 API 描述
Tensors is_tensor(obj) 检查 obj 是否为 PyTorch 张量。
is_storage(obj) 检查 obj 是否为 PyTorch 存储对象。
is_complex(input) 检查 input 数据类型是否为复数数据类型。
is_conj(input) 检查 input 是否为共轭张量。
is_floating_point(input) 检查 input 数据类型是否为浮点数据类型。
is_nonzero(input) 检查 input 是否为非零单一元素张量。
set_default_dtype(d) 设置默认浮点数据类型为 d
get_default_dtype() 获取当前默认浮点 torch.dtype
set_default_device(device) 设置默认 torch.Tensor 分配的设备为 device
get_default_device() 获取默认 torch.Tensor 分配的设备。
numel(input) 返回 input 张量中的元素总数。
Creation Ops tensor(data) 通过复制 data 构造无自动梯度历史的张量。
sparse_coo_tensor(indices, values) 在指定的 indices 处构造稀疏张量,具有指定的值。
as_tensor(data) data 转换为张量,共享数据并尽可能保留自动梯度历史。
zeros(size) 返回一个用标量值 0 填充的张量,形状由 size 定义。
ones(size) 返回一个用标量值 1 填充的张量,形状由 size 定义。
arange(start, end, step) 返回一个 1-D 张量,包含从 startend 的值,步长为 step
rand(size) 返回一个从 [0, 1) 区间均匀分布的随机数填充的张量。
randn(size) 返回一个从标准正态分布填充的张量。
Math operations add(input, other, alpha) other(由 alpha 缩放)加到 input 上。
mul(input, other) inputother 相乘。
matmul(input, other) 执行 inputother 的矩阵乘法。
mean(input, dim) 计算 input 在维度 dim 上的均值。
sum(input, dim) 计算 input 在维度 dim 上的和。
max(input, dim) 返回 input 在维度 dim 上的最大值。
min(input, dim) 返回 input 在维度 dim 上的最小值。

Tensor 创建

函数 描述
torch.tensor(data, dtype, device, requires_grad) 从数据创建张量。
torch.as_tensor(data, dtype, device) 将数据转换为张量(共享内存)。
torch.from_numpy(ndarray) 从 NumPy 数组创建张量(共享内存)。
torch.zeros(*size, dtype, device, requires_grad) 创建全零张量。
torch.ones(*size, dtype, device, requires_grad) 创建全一张量。
torch.empty(*size, dtype, device, requires_grad) 创建未初始化的张量。
torch.arange(start, end, step, dtype, device, requires_grad) 创建等差序列张量。
torch.linspace(start, end, steps, dtype, device, requires_grad) 创建等间隔序列张量。
torch.logspace(start, end, steps, base, dtype, device, requires_grad) 创建对数间隔序列张量。
torch.eye(n, m, dtype, device, requires_grad) 创建单位矩阵。
torch.full(size, fill_value, dtype, device, requires_grad) 创建填充指定值的张量。
torch.rand(*size, dtype, device, requires_grad) 创建均匀分布随机张量(范围 [0, 1))。
torch.randn(*size, dtype, device, requires_grad) 创建标准正态分布随机张量。
torch.randint(low, high, size, dtype, device, requires_grad) 创建整数随机张量。
torch.randperm(n, dtype, device, requires_grad) 创建 0 到 n-1 的随机排列。

Tensor 操作

函数 描述
torch.cat(tensors, dim) 沿指定维度连接张量。
torch.stack(tensors, dim) 沿新维度堆叠张量。
torch.split(tensor, split_size, dim) 将张量沿指定维度分割。
torch.chunk(tensor, chunks, dim) 将张量沿指定维度分块。
torch.reshape(input, shape) 改变张量的形状。
torch.transpose(input, dim0, dim1) 交换张量的两个维度。
torch.squeeze(input, dim) 移除大小为 1 的维度。
torch.unsqueeze(input, dim) 在指定位置插入大小为 1 的维度。
torch.expand(input, size) 扩展张量的尺寸。
torch.narrow(input, dim, start, length) 返回张量的切片。
torch.permute(input, dims) 重新排列张量的维度。
torch.masked_select(input, mask) 根据布尔掩码选择元素。
torch.index_select(input, dim, index) 沿指定维度选择索引对应的元素。
torch.gather(input, dim, index) 沿指定维度收集指定索引的元素。
torch.scatter(input, dim, index, src) src 的值散布到 input 的指定位置。
torch.nonzero(input) 返回非零元素的索引。

数学运算

函数 描述
torch.add(input, other) 逐元素加法。
torch.sub(input, other) 逐元素减法。
torch.mul(input, other) 逐元素乘法。
torch.div(input, other) 逐元素除法。
torch.matmul(input, other) 矩阵乘法。
torch.pow(input, exponent) 逐元素幂运算。
torch.sqrt(input) 逐元素平方根。
torch.exp(input) 逐元素指数函数。
torch.log(input) 逐元素自然对数。
torch.sum(input, dim) 沿指定维度求和。
torch.mean(input, dim) 沿指定维度求均值。
torch.max(input, dim) 沿指定维度求最大值。
torch.min(input, dim) 沿指定维度求最小值。
torch.abs(input) 逐元素绝对值。
torch.clamp(input, min, max) 将张量值限制在指定范围内。
torch.round(input) 逐元素四舍五入。
torch.floor(input) 逐元素向下取整。
torch.ceil(input) 逐元素向上取整。

随机数生成

函数 描述
torch.manual_seed(seed) 设置随机种子。
torch.initial_seed() 返回当前随机种子。
torch.rand(*size) 创建均匀分布随机张量(范围 [0, 1))。
torch.randn(*size) 创建标准正态分布随机张量。
torch.randint(low, high, size) 创建整数随机张量。
torch.randperm(n) 返回 0 到 n-1 的随机排列。

线性代数

函数 描述
torch.dot(input, other) 计算两个向量的点积。
torch.mm(input, mat2) 矩阵乘法。
torch.bmm(input, mat2) 批量矩阵乘法。
torch.eig(input) 计算矩阵的特征值和特征向量。
torch.svd(input) 计算矩阵的奇异值分解。
torch.inverse(input) 计算矩阵的逆。
torch.det(input) 计算矩阵的行列式。
torch.trace(input) 计算矩阵的迹。

设备管理

函数 描述
torch.cuda.is_available() 检查 CUDA 是否可用。
torch.device(device) 创建一个设备对象(如 'cpu''cuda:0')。
torch.to(device) 将张量移动到指定设备。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import torch
# 创建张量
x = torch.tensor([1, 2, 3])
y = torch.zeros(2, 3)
# 数学运算
z = torch.add(x, 1) # 逐元素加 1
print(z)
# 索引和切片
mask = x > 1
selected = torch.masked_select(x, mask)
print(selected)
# 设备管理
if torch.cuda.is_available():
device = torch.device('cuda')
x = x.to(device)
print(x.device)

PyTorch torch.nn 参考手册

PyTorch 的 torch.nn 模块是构建和训练神经网络的核心模块,它提供了丰富的类和函数来定义和操作神经网络。

以下是 torch.nn 模块的一些关键组成部分及其功能:

1、nn.Module 类

  • nn.Module所有自定义神经网络模型的基类。用户通常会从这个类派生自己的模型类,并在其中定义网络层结构以及前向传播函数(forward pass)

2、预定义层(Modules)

  • 包括各种类型的层组件,例如卷积层(nn.Conv1d, nn.Conv2d, nn.Conv3d全连接层(nn.Linear激活函数(nn.ReLU, nn.Sigmoid, nn.Tanh等。

3、容器类

  • nn.Sequential:允许将多个层按顺序组合起来,形成简单的线性堆叠网络
  • nn.ModuleListnn.ModuleDict:可以动态地存储和访问子模块,支持可变长度或命名的模块集合。

4、损失函数(Loss Functions)

  • torch.nn 包含了一系列用于衡量模型预测与真实标签之间差异的损失函数,例如均方误差损失(nn.MSELoss交叉熵损失(nn.CrossEntropyLoss等。

5、实用函数接口(Functional Interface)

  • nn.functional(通常简写为 F,包含了许多可以直接作用于张量上的函数,它们实现了与层对象相同的功能,但不具有参数保存和更新的能力。例如,可以使用 F.relu() 直接进行 ReLU 操作,或者 F.conv2d() 进行卷积操作

6、初始化方法

  • torch.nn.init 提供了一些常用的权重初始化策略,比如 Xavier 初始化 (nn.init.xavier_uniform_()) 和 Kaiming 初始化 (nn.init.kaiming_uniform_()),这些对于成功训练神经网络至关重要。

PyTorch torch.nn 模块参考手册

神经网络容器

类/函数 描述
torch.nn.Module 所有神经网络模块的基类。
torch.nn.Sequential(*args) 按顺序组合多个模块。
torch.nn.ModuleList(modules) 将子模块存储在列表中。
torch.nn.ModuleDict(modules) 将子模块存储在字典中。
torch.nn.ParameterList(parameters) 将参数存储在列表中。
torch.nn.ParameterDict(parameters) 将参数存储在字典中。

线性层

类/函数 描述
torch.nn.Linear(in_features, out_features) 全连接层。
torch.nn.Bilinear(in1_features, in2_features, out_features) 双线性层。

卷积层

类/函数 描述
torch.nn.Conv1d(in_channels, out_channels, kernel_size) 一维卷积层。
torch.nn.Conv2d(in_channels, out_channels, kernel_size) 二维卷积层。
torch.nn.Conv3d(in_channels, out_channels, kernel_size) 三维卷积层。
torch.nn.ConvTranspose1d(in_channels, out_channels, kernel_size) 一维转置卷积层。
torch.nn.ConvTranspose2d(in_channels, out_channels, kernel_size) 二维转置卷积层。
torch.nn.ConvTranspose3d(in_channels, out_channels, kernel_size) 三维转置卷积层。

池化层

类/函数 描述
torch.nn.MaxPool1d(kernel_size) 一维最大池化层。
torch.nn.MaxPool2d(kernel_size) 二维最大池化层。
torch.nn.MaxPool3d(kernel_size) 三维最大池化层。
torch.nn.AvgPool1d(kernel_size) 一维平均池化层。
torch.nn.AvgPool2d(kernel_size) 二维平均池化层。
torch.nn.AvgPool3d(kernel_size) 三维平均池化层。
torch.nn.AdaptiveMaxPool1d(output_size) 一维自适应最大池化层。
torch.nn.AdaptiveAvgPool1d(output_size) 一维自适应平均池化层。
torch.nn.AdaptiveMaxPool2d(output_size) 二维自适应最大池化层。
torch.nn.AdaptiveAvgPool2d(output_size) 二维自适应平均池化层。
torch.nn.AdaptiveMaxPool3d(output_size) 三维自适应最大池化层。
torch.nn.AdaptiveAvgPool3d(output_size) 三维自适应平均池化层。

激活函数

类/函数 描述
torch.nn.ReLU() ReLU 激活函数。
torch.nn.Sigmoid() Sigmoid 激活函数。
torch.nn.Tanh() Tanh 激活函数。
torch.nn.Softmax(dim) Softmax 激活函数。
torch.nn.LogSoftmax(dim) LogSoftmax 激活函数。
torch.nn.LeakyReLU(negative_slope) LeakyReLU 激活函数。
torch.nn.ELU(alpha) ELU 激活函数。
torch.nn.SELU() SELU 激活函数。
torch.nn.GELU() GELU 激活函数。

损失函数

类/函数 描述
torch.nn.MSELoss() 均方误差损失。
torch.nn.L1Loss() L1 损失。
torch.nn.CrossEntropyLoss() 交叉熵损失。
torch.nn.NLLLoss() 负对数似然损失。
torch.nn.BCELoss() 二分类交叉熵损失。
torch.nn.BCEWithLogitsLoss() 带 Sigmoid 的二分类交叉熵损失。
torch.nn.KLDivLoss() KL 散度损失。
torch.nn.HingeEmbeddingLoss() 铰链嵌入损失。
torch.nn.MultiMarginLoss() 多分类间隔损失。
torch.nn.SmoothL1Loss() 平滑 L1 损失。

归一化层

类/函数 描述
torch.nn.BatchNorm1d(num_features) 一维批归一化层。
torch.nn.BatchNorm2d(num_features) 二维批归一化层。
torch.nn.BatchNorm3d(num_features) 三维批归一化层。
torch.nn.LayerNorm(normalized_shape) 层归一化。
torch.nn.InstanceNorm1d(num_features) 一维实例归一化层。
torch.nn.InstanceNorm2d(num_features) 二维实例归一化层。
torch.nn.InstanceNorm3d(num_features) 三维实例归一化层。
torch.nn.GroupNorm(num_groups, num_channels) 组归一化。

循环神经网络层

类/函数 描述
torch.nn.RNN(input_size, hidden_size) 简单 RNN 层。
torch.nn.LSTM(input_size, hidden_size) LSTM 层。
torch.nn.GRU(input_size, hidden_size) GRU 层。
torch.nn.RNNCell(input_size, hidden_size) 简单 RNN 单元。
torch.nn.LSTMCell(input_size, hidden_size) LSTM 单元。
torch.nn.GRUCell(input_size, hidden_size) GRU 单元。

嵌入层

类/函数 描述
torch.nn.Embedding(num_embeddings, embedding_dim) 嵌入层。

Dropout 层

类/函数 描述
torch.nn.Dropout(p) Dropout 层。
torch.nn.Dropout2d(p) 2D Dropout 层。
torch.nn.Dropout3d(p) 3D Dropout 层。

实用函数

函数 描述
torch.nn.functional.relu(input) 应用 ReLU 激活函数。
torch.nn.functional.sigmoid(input) 应用 Sigmoid 激活函数。
torch.nn.functional.softmax(input, dim) 应用 Softmax 激活函数。
torch.nn.functional.cross_entropy(input, target) 计算交叉熵损失。
torch.nn.functional.mse_loss(input, target) 计算均方误差损失。

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import torch
import torch.nn as nn
# 定义一个简单的神经网络
class SimpleNet(nn.Module):
def __init__(self):
super(SimpleNet, self).__init__()
self.fc1 = nn.Linear(10, 20)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(20, 1)
def forward(self, x):
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
return x
# 创建模型和输入
model = SimpleNet()
input = torch.randn(5, 10)
output = model(input)
print(output)

Transformer 模型

Transformer 模型是一种基于注意力机制的深度学习模型,最初由 Vaswani 等人在 2017 年的论文《Attention is All You Need》中提出。

Transformer 彻底改变了自然语言处理(NLP)领域,并逐渐扩展到计算机视觉(CV)等领域

Transformer 的核心思想是完全摒弃传统的循环神经网络(RNN)结构,仅依赖注意力机制来处理序列数据,从而实现更高的并行性更快的训练速度

以下是 Transformer 架构图,左边为编码器,右边为解码器。

img

Transformer 模型由 编码器(Encoder) 和 解码器(Decoder) 两部分组成,每部分都由多层堆叠的相同模块构成。

img编码器(Encoder)

编码器由 N 层相同的模块堆叠而成,每层包含两个子层:

  • 多头自注意力机制(Multi-Head Self-Attention):计算输入序列中每个词与其他词的相关性
  • 前馈神经网络(Feed-Forward Neural Network):对每个词进行独立的非线性变换

每个子层后面都接有 残差连接(Residual Connection)层归一化(Layer Normalization)

解码器(Decoder)

解码器也由 N 层相同的模块堆叠而成,每层包含三个子层:

  • 掩码多头自注意力机制(Masked Multi-Head Self-Attention):计算输出序列中每个词与前面词的相关性(使用掩码防止未来信息泄露)
  • 编码器-解码器注意力机制(Encoder-Decoder Attention):计算输出序列与输入序列的相关性
  • 前馈神经网络(Feed-Forward Neural Network):对每个词进行独立的非线性变换

同样,每个子层后面都接有残差连接和层归一化。

在 Transformer 模型出现之前,NLP 领域的主流模型是基于 RNN 的架构,如长短期记忆网络(LSTM)和门控循环单元(GRU)。这些模型通过顺序处理输入数据来捕捉序列中的依赖关系,但存在以下问题:

  1. 梯度消失问题长距离依赖关系难以捕捉
  2. 顺序计算的局限性:无法充分利用现代硬件的并行计算能力,训练效率低下。

Transformer 通过引入自注意力机制解决了这些问题,允许模型同时处理整个输入序列,并动态地为序列中的每个位置分配不同的权重。


Transformer 的核心思想

1. 自注意力机制(Self-Attention)

自注意力机制是 Transformer 的核心组件。

自注意力机制允许模型在处理序列时,动态地为每个位置分配不同的权重,从而捕捉序列中任意两个位置之间的依赖关系。

  • 输入表示:输入序列中的每个词(或标记)通过词嵌入(Embedding)转换为向量表示
  • 注意力权重计算:通过计算查询(Query)、键(Key)和值(Value)之间的点积,得到每个词与其他词的相关性权重
  • 加权求和:使用注意力权重对值(Value)进行加权求和,得到每个词的上下文表示

公式如下:

192f2ceca2b507875d3c7eef6f0367e5

其中:

  • Q 是查询矩阵,K 是键矩阵,V是值矩阵。
  • dk是向量的维度,用于缩放点积,防止梯度爆炸

多头注意力(Multi-Head Attention)

为了捕捉更丰富的特征,Transformer 使用多头注意力机制。它将输入分成多个子空间,每个子空间独立计算注意力,最后将结果拼接起来。

  • 多头注意力的优势:允许模型关注序列中不同的部分,例如语法结构、语义关系等。
  • 并行计算:多个注意力头可以并行计算,提高效率。

位置编码(Positional Encoding)

由于 Transformer 没有显式的序列信息(如 RNN 中的时间步)位置编码被用来为输入序列中的每个词添加位置信息。通常使用正弦和余弦函数生成位置编码

b92014de131411175caef93b06e4006f

Transformer 的应用

  1. 自然语言处理(NLP)
    • 机器翻译(如 Google Translate)
    • 文本生成(如 GPT 系列模型)
    • 文本分类、问答系统等。
  2. 计算机视觉(CV)
    • 图像分类(如 Vision Transformer)
    • 目标检测、图像生成等。
  3. 多模态任务
    • 结合文本和图像的任务(如 CLIP、DALL-E)。

PyTorch 实现 Transformer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import torch
import torch.nn as nn
import torch.optim as optim
class TransformerModel(nn.Module):
def __init__(self, input_dim, model_dim, num_heads, num_layers, output_dim):
super(TransformerModel, self).__init__()
self.embedding = nn.Embedding(input_dim, model_dim)
self.positional_encoding = nn.Parameter(torch.zeros(1, 1000, model_dim)) # 假设序列长度最大为1000
self.transformer = nn.Transformer(d_model=model_dim, nhead=num_heads, num_encoder_layers=num_layers)
self.fc = nn.Linear(model_dim, output_dim)
def forward(self, src, tgt):
src_seq_length, tgt_seq_length = src.size(1), tgt.size(1)
src = self.embedding(src) + self.positional_encoding[:, :src_seq_length, :]
tgt = self.embedding(tgt) + self.positional_encoding[:, :tgt_seq_length, :]
transformer_output = self.transformer(src, tgt)
output = self.fc(transformer_output)
return output
# 超参数
input_dim = 10000 # 词汇表大小
model_dim = 512 # 模型维度
num_heads = 8 # 多头注意力头数
num_layers = 6 # 编码器和解码器层数
output_dim = 10000 # 输出维度(通常与词汇表大小相同)
# 初始化模型、损失函数和优化器
model = TransformerModel(input_dim, model_dim, num_heads, num_layers, output_dim)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 假设输入数据
src = torch.randint(0, input_dim, (10, 32)) # (序列长度, 批量大小)
tgt = torch.randint(0, input_dim, (20, 32)) # (序列长度, 批量大小)
# 前向传播
output = model(src, tgt)
# 计算损失
loss = criterion(output.view(-1, output_dim), tgt.view(-1))
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
print("Loss:", loss.item())

PyTorch 构建 Transformer 模型

Transformer 是现代机器学习中最强大的模型之一。

Transformer 模型是一种基于自注意力机制(Self-Attention) 的深度学习架构,它彻底改变了自然语言处理(NLP)领域,并成为现代深度学习模型(如 BERT、GPT 等)的基础。

Transformer 是现代 NLP 领域的核心架构,凭借其强大的长距离依赖建模能力和高效的并行计算优势,在语言翻译和文本摘要等任务中超越了传统的 长短期记忆 (LSTM) 网络。

使用 PyTorch 构建 Transformer 模型

构建 Transformer 模型的步骤如下:

1、导入必要的库和模块

导入 PyTorch 核心库、神经网络模块、优化器模块、数据处理工具,以及数学和对象复制模块,为定义模型架构、管理数据和训练过程提供支持。

1
2
3
4
5
6
import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data
import math
import copy

说明:

  • torch:PyTorch 的核心库用于张量操作和自动求导
  • torch.nn:PyTorch 的神经网络模块,包含各种层和损失函数
  • torch.optim优化算法模块,如 Adam、SGD 等。
  • math:数学函数库,用于计算平方根等。
  • copy:用于深度复制对象

定义基本构建块:多头注意力、位置前馈网络、位置编码

多头注意力通过多个”注意力头”计算序列中每对位置之间的关系,能够捕捉输入序列的不同特征和模式。

img

MultiHeadAttention 类封装了 Transformer 模型中常用的多头注意力机制,负责将输入拆分成多个注意力头,对每个注意力头施加注意力,然后将结果组合起来,这样模型就可以在不同尺度上捕捉输入数据中的各种关系,提高模型的表达能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super(MultiHeadAttention, self).__init__()
assert d_model % num_heads == 0, "d_model必须能被num_heads整除"
self.d_model = d_model # 模型维度(如512)
self.num_heads = num_heads # 注意力头数(如8)
self.d_k = d_model // num_heads # 每个头的维度(如64)
# 定义线性变换层(无需偏置)
self.W_q = nn.Linear(d_model, d_model) # 查询变换
self.W_k = nn.Linear(d_model, d_model) # 键变换
self.W_v = nn.Linear(d_model, d_model) # 值变换
self.W_o = nn.Linear(d_model, d_model) # 输出变换
def scaled_dot_product_attention(self, Q, K, V, mask=None):
"""
计算缩放点积注意力
输入形状:
Q: (batch_size, num_heads, seq_length, d_k)
K, V: 同Q
输出形状: (batch_size, num_heads, seq_length, d_k)
"""
# 计算注意力分数(Q和K的点积)
attn_scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
# 应用掩码(如填充掩码或未来信息掩码)
if mask is not None:
attn_scores = attn_scores.masked_fill(mask == 0, -1e9)
# 计算注意力权重(softmax归一化)
attn_probs = torch.softmax(attn_scores, dim=-1)
# 对值向量加权求和
output = torch.matmul(attn_probs, V)
return output
def split_heads(self, x):
"""
将输入张量分割为多个头
输入形状: (batch_size, seq_length, d_model)
输出形状: (batch_size, num_heads, seq_length, d_k)
"""
batch_size, seq_length, d_model = x.size()
return x.view(batch_size, seq_length, self.num_heads, self.d_k).transpose(1, 2)
def combine_heads(self, x):
"""
将多个头的输出合并回原始形状
输入形状: (batch_size, num_heads, seq_length, d_k)
输出形状: (batch_size, seq_length, d_model)
"""
batch_size, _, seq_length, d_k = x.size()
return x.transpose(1, 2).contiguous().view(batch_size, seq_length, self.d_model)
def forward(self, Q, K, V, mask=None):
"""
前向传播
输入形状: Q/K/V: (batch_size, seq_length, d_model)
输出形状: (batch_size, seq_length, d_model)
"""
# 线性变换并分割多头
Q = self.split_heads(self.W_q(Q)) # (batch, heads, seq_len, d_k)
K = self.split_heads(self.W_k(K))
V = self.split_heads(self.W_v(V))
# 计算注意力
attn_output = self.scaled_dot_product_attention(Q, K, V, mask)
# 合并多头并输出变换
output = self.W_o(self.combine_heads(attn_output))
return output

说明:

  • 多头注意力机制:将输入分割成多个头,每个头独立计算注意力,最后将结果合并。
  • 缩放点积注意力:计算查询和键的点积,缩放后使用 softmax 计算注意力权重,最后对值进行加权求和。
  • 掩码:用于屏蔽无效位置(如填充部分)。

位置前馈网络(Position-wise Feed-Forward Network)

1
2
3
4
5
6
7
8
9
class PositionWiseFeedForward(nn.Module):
def __init__(self, d_model, d_ff):
super(PositionWiseFeedForward, self).__init__()
self.fc1 = nn.Linear(d_model, d_ff) # 第一层全连接
self.fc2 = nn.Linear(d_ff, d_model) # 第二层全连接
self.relu = nn.ReLU() # 激活函数
def forward(self, x):
# 前馈网络的计算
return self.fc2(self.relu(self.fc1(x)))

前馈网络:由两个全连接层和一个 ReLU 激活函数组成,用于进一步处理注意力机制的输出。

位置编码

位置编码用于注入输入序列中每个 token 的位置信息

使用不同频率的正弦和余弦函数来生成位置编码

1
2
3
4
5
6
7
8
9
10
11
12
13
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_seq_length):
super(PositionalEncoding, self).__init__()
pe = torch.zeros(max_seq_length, d_model) # 初始化位置编码矩阵
position = torch.arange(0, max_seq_length, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term) # 偶数位置使用正弦函数
pe[:, 1::2] = torch.cos(position * div_term) # 奇数位置使用余弦函数
self.register_buffer('pe', pe.unsqueeze(0)) # 注册为缓冲区

def forward(self, x):
# 将位置编码添加到输入中
return x + self.pe[:, :x.size(1)]

构建编码器块(Encoder Layer)

img

编码器层:包含一个自注意力机制和一个前馈网络,每个子层后接残差连接和层归一化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class EncoderLayer(nn.Module):
def __init__(self, d_model, num_heads, d_ff, dropout):
super(EncoderLayer, self).__init__()
self.self_attn = MultiHeadAttention(d_model, num_heads) # 自注意力机制
self.feed_forward = PositionWiseFeedForward(d_model, d_ff) # 前馈网络
self.norm1 = nn.LayerNorm(d_model) # 层归一化
self.norm2 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout) # Dropout
def forward(self, x, mask):
# 自注意力机制
attn_output = self.self_attn(x, x, x, mask)
x = self.norm1(x + self.dropout(attn_output)) # 残差连接和层归一化
# 前馈网络
ff_output = self.feed_forward(x)
x = self.norm2(x + self.dropout(ff_output)) # 残差连接和层归一化
return x

构建解码器模块

img

解码器层:包含一个自注意力机制、一个交叉注意力机制和一个前馈网络,每个子层后接残差连接和层归一化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class DecoderLayer(nn.Module):
def __init__(self, d_model, num_heads, d_ff, dropout):
super(DecoderLayer, self).__init__()
self.self_attn = MultiHeadAttention(d_model, num_heads) # 自注意力机制
self.cross_attn = MultiHeadAttention(d_model, num_heads) # 交叉注意力机制
self.feed_forward = PositionWiseFeedForward(d_model, d_ff) # 前馈网络
self.norm1 = nn.LayerNorm(d_model) # 层归一化
self.norm2 = nn.LayerNorm(d_model)
self.norm3 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout) # Dropout
def forward(self, x, enc_output, src_mask, tgt_mask):
# 自注意力机制
attn_output = self.self_attn(x, x, x, tgt_mask)
x = self.norm1(x + self.dropout(attn_output)) # 残差连接和层归一化
# 交叉注意力机制
attn_output = self.cross_attn(x, enc_output, enc_output, src_mask)
x = self.norm2(x + self.dropout(attn_output)) # 残差连接和层归一化
# 前馈网络
ff_output = self.feed_forward(x)
x = self.norm3(x + self.dropout(ff_output)) # 残差连接和层归一化
return x

构建完整的 Transformer 模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Transformer(nn.Module):
def __init__(self, src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout):
super(Transformer, self).__init__()
self.encoder_embedding = nn.Embedding(src_vocab_size, d_model) # 编码器词嵌入
self.decoder_embedding = nn.Embedding(tgt_vocab_size, d_model) # 解码器词嵌入
self.positional_encoding = PositionalEncoding(d_model, max_seq_length) # 位置编码
# 编码器和解码器层
self.encoder_layers = nn.ModuleList([EncoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)])
self.decoder_layers = nn.ModuleList([DecoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)])
self.fc = nn.Linear(d_model, tgt_vocab_size) # 最终的全连接层
self.dropout = nn.Dropout(dropout) # Dropout
def generate_mask(self, src, tgt):
# 源掩码:屏蔽填充符(假设填充符索引为0)
# 形状:(batch_size, 1, 1, seq_length)
src_mask = (src != 0).unsqueeze(1).unsqueeze(2)
# 目标掩码:屏蔽填充符和未来信息
# 形状:(batch_size, 1, seq_length, 1)
tgt_mask = (tgt != 0).unsqueeze(1).unsqueeze(3)
seq_length = tgt.size(1)
# 生成上三角矩阵掩码,防止解码时看到未来信息
nopeak_mask = (1 - torch.triu(torch.ones(1, seq_length, seq_length), diagonal=1)).bool()
tgt_mask = tgt_mask & nopeak_mask # 合并填充掩码和未来信息掩码
return src_mask, tgt_mask
def forward(self, src, tgt):
# 生成掩码
src_mask, tgt_mask = self.generate_mask(src, tgt)
# 编码器部分
src_embedded = self.dropout(self.positional_encoding(self.encoder_embedding(src)))
enc_output = src_embedded
for enc_layer in self.encoder_layers:
enc_output = enc_layer(enc_output, src_mask)
# 解码器部分
tgt_embedded = self.dropout(self.positional_encoding(self.decoder_embedding(tgt)))
dec_output = tgt_embedded
for dec_layer in self.decoder_layers:
dec_output = dec_layer(dec_output, enc_output, src_mask, tgt_mask)
# 最终输出
output = self.fc(dec_output)
return output

说明:

  • Transformer 模型:包含编码器和解码器部分,每个部分由多个层堆叠而成。
  • 掩码生成:用于屏蔽无效位置和未来信息。
  • 前向传播:依次通过编码器和解码器,最后通过全连接层输出。

模型初始化参数说明:

1
2
3
4
5
6
7
8
9
10
11
12
class Transformer(nn.Module):
def __init__(
self,
src_vocab_size, # 源语言词汇表大小(如英文单词数)
tgt_vocab_size, # 目标语言词汇表大小(如中文单词数)
d_model=512, # 模型维度(每个词向量的长度)
num_heads=8, # 多头注意力的头数
num_layers=6, # 编码器/解码器的堆叠层数
d_ff=2048, # 前馈网络隐藏层维度
max_seq_length=100, # 最大序列长度(用于位置编码)
dropout=0.1 # Dropout概率
):

训练 PyTorch Transformer 模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 超参数
src_vocab_size = 5000 # 源词汇表大小
tgt_vocab_size = 5000 # 目标词汇表大小
d_model = 512 # 模型维度
num_heads = 8 # 注意力头数量
num_layers = 6 # 编码器和解码器层数
d_ff = 2048 # 前馈网络内层维度
max_seq_length = 100 # 最大序列长度
dropout = 0.1 # Dropout 概率
# 初始化模型
transformer = Transformer(src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout)
# 生成随机数据
src_data = torch.randint(1, src_vocab_size, (64, max_seq_length)) # 源序列
tgt_data = torch.randint(1, tgt_vocab_size, (64, max_seq_length)) # 目标序列
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss(ignore_index=0) # 忽略填充部分的损失
optimizer = optim.Adam(transformer.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9)
# 训练循环
transformer.train()
for epoch in range(100):
optimizer.zero_grad() # 清空梯度,防止累积
# 输入目标序列时去掉最后一个词(用于预测下一个词)
output = transformer(src_data, tgt_data[:, :-1])
# 计算损失时,目标序列从第二个词开始(即预测下一个词)
# output形状: (batch_size, seq_length-1, tgt_vocab_size)
# 目标形状: (batch_size, seq_length-1)
loss = criterion(
output.contiguous().view(-1, tgt_vocab_size),
tgt_data[:, 1:].contiguous().view(-1)
)
loss.backward() # 反向传播
optimizer.step() # 更新参数
print(f"Epoch: {epoch+1}, Loss: {loss.item()}")

模型评估

1
2
3
4
5
6
7
8
9
10
11
12
13
transformer.eval()
# 生成验证数据
val_src_data = torch.randint(1, src_vocab_size, (64, max_seq_length))
val_tgt_data = torch.randint(1, tgt_vocab_size, (64, max_seq_length))
# 假设输入为一批英文和对应的中文翻译(已转换为索引)
# 示例数据:
# src_data: [[3, 14, 25, ..., 0, 0], ...] # 英文句子(0为填充符)
# tgt_data: [[5, 20, 36, ..., 0, 0], ...] # 中文翻译(0为填充符)
# 注意:实际应用中需对文本进行分词、编码、填充等预处理
with torch.no_grad():
val_output = transformer(val_src_data, val_tgt_data[:, :-1])
val_loss = criterion(val_output.contiguous().view(-1, tgt_vocab_size), val_tgt_data[:, 1:].contiguous().view(-1))
print(f"Validation Loss: {val_loss.item()}")

Python 入门机器学习

在使用 Python 进行机器学习时,整个过程一般遵循以下步骤:

  1. 导入必要的库 - 例如,NumPy、Pandas 和 Scikit-learn。
  2. 加载和准备数据 - 数据是机器学习的核心。你需要加载数据并进行必要的预处理(例如数据清洗、缺失值填补等)。
  3. 选择模型和算法 - 根据任务选择适合的机器学习算法(如线性回归、决策树等)。
  4. 训练模型 - 使用训练集数据来训练模型。
  5. 评估模型 - 使用测试集评估模型的准确性,并根据评估结果优化模型。
  6. 调整模型和超参数 - 根据评估结果调整模型的超参数,进一步优化模型性能。

一个简单的机器学习例子:使用 Scikit-learn 做分类

Scikit-learn(简称 Sklearn)是一个开源的机器学习库,建立在 NumPy、SciPy 和 matplotlib 这些科学计算库之上,提供了简单高效的数据挖掘和数据分析工具。

Scikit-learn 包含了许多常见的机器学习算法,包括:

  • 线性回归、岭回归、Lasso回归
  • 支持向量机(SVM)
  • 决策树、随机森林、梯度提升树
  • 聚类算法(如K-Means、层次聚类、DBSCAN)
  • 降维技术(如PCA、t-SNE)
  • 神经网络

接下来我们通过一个简单的分类任务——使用鸢尾花数据集(Iris Dataset)来演示机器学习的流程,鸢尾花数据集是一个经典的数据集,包含 150 个样本,描述了三种不同类型的鸢尾花的花瓣和萼片的长度和宽度。

步骤 1:导入库

导入需要的 Python 库:

1
2
3
4
5
6
7
8
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score

步骤 2:加载数据

加载鸢尾花数据集:

1
2
3
4
5
6
7
8
9
# 加载鸢尾花数据集
iris = load_iris()

# 将数据转化为 pandas DataFrame
X = pd.DataFrame(iris.data, columns=iris.feature_names) # 特征数据
y = pd.Series(iris.target) # 标签数据

# 显示前五行数据
print(X.head())

打印输出数据如下所示:

1
2
3
4
5
6
   sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)
0 5.1 3.5 1.4 0.2
1 4.9 3.0 1.4 0.2
2 4.7 3.2 1.3 0.2
3 4.6 3.1 1.5 0.2
4 5.0 3.6 1.4 0.2

步骤 3:数据集划分

将数据集划分为训练集和测试集,通常使用 70% 训练集和 30% 测试集的比例:

1
2
# 划分训练集和测试集(80% 训练集,20% 测试集)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

步骤 4:特征缩放(标准化)

许多机器学习算法都依赖于特征的尺度,特别是像 K 最近邻算法。为了确保每个特征的均值为 0,标准差为 1,我们使用标准化来处理数据:

1
2
3
4
# 标准化特征
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

步骤 5:选择模型并训练

在这个例子中,我们选择 K-Nearest Neighbors(KNN) 算法来进行分类:

1
2
3
4
# 创建 KNN 分类器
knn = KNeighborsClassifier(n_neighbors=3)
# 训练模型
knn.fit(X_train, y_train)

步骤 6:评估模型

训练完成后,我们使用测试集评估模型的准确性:

1
2
3
4
5
# 预测测试集
y_pred = knn.predict(X_test)
# 计算准确率
accuracy = accuracy_score(y_test, y_pred)
print(f'模型准确率: {accuracy:.2f}')

完成以上代码,输出结果为:

1
模型准确率: 1.00

步骤 7:可视化结果(可选)

你可以通过可视化来进一步了解模型的表现,尤其是在多维数据集的情况下。例如,你可以用二维图来显示 KNN 分类的结果(不过在这里需要对数据进行降维,简化为二维)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score

# 加载鸢尾花数据集
iris = load_iris()

# 将数据转化为 pandas DataFrame
X = pd.DataFrame(iris.data, columns=iris.feature_names) # 特征数据
y = pd.Series(iris.target) # 标签数据

# 划分训练集和测试集(80% 训练集,20% 测试集)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 标准化特征
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

# 创建 KNN 分类器
knn = KNeighborsClassifier(n_neighbors=3)

# 训练模型
knn.fit(X_train, y_train)

# 预测测试集
y_pred = knn.predict(X_test)

# 计算准确率
accuracy = accuracy_score(y_test, y_pred)

# 可视化 - 这里只是一个简单示例,具体可根据实际情况选择绘图方式
plt.scatter(X_test[:, 0], X_test[:, 1], c=y_pred, cmap='viridis', marker='o')
plt.title("KNN Classification Results")
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.show()

img

机器学习算法

机器学习算法可以分为监督学习、无监督学习、强化学习等类别。

监督学习算法

  • 线性回归(Linear Regression):用于回归任务,预测连续的数值。
  • 逻辑回归(Logistic Regression):用于二分类任务,预测类别。
  • 支持向量机(SVM):用于分类任务,构建超平面进行分类。
  • 决策树(Decision Tree):基于树状结构进行决策的分类或回归方法。

无监督学习算法

  • K-means 聚类通过聚类中心将数据分组。
  • 主成分分析(PCA):用于降维,提取数据的主成分。

每种算法都有其适用的场景,在实际应用中,可以根据数据的特征(如是否有标签、数据的维度等)来选择最合适的机器学习算法。


监督学习算法

线性回归(Linear Regression)

线性回归是一种用于回归问题的算法,它通过学习输入特征与目标值之间的线性关系,来预测一个连续的输出。

应用场景:预测房价、股票价格等。

线性回归的目标是找到一个最佳的线性方程:

img

  • y 是预测值(目标值)。
  • x1,x2,xn 是输入特征。
  • w1,w2,wn是待学习的权重(模型参数)。
  • b 是偏置项。

img

接下来我们使用 sklearn 进行简单的房价预测:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
import pandas as pd

# 假设我们有一个简单的房价数据集
data = {
'面积': [50, 60, 80, 100, 120],
'房价': [150, 180, 240, 300, 350]
}
df = pd.DataFrame(data)

# 特征和标签
X = df[['面积']]
y = df['房价']

# 数据分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 训练线性回归模型
model = LinearRegression()
model.fit(X_train, y_train)

# 预测
y_pred = model.predict(X_test)

print(f"预测的房价: {y_pred}")

输出结果为:

1
预测的房价: [180.8411215]

逻辑回归(Logistic Regression)

逻辑回归是一种用于分类问题的算法,尽管名字中包含”回归”,它是用来处理二分类问题的

逻辑回归通过学习输入特征与类别之间的关系,来预测一个类别标签。

应用场景:垃圾邮件分类、疾病诊断(是否患病)。

逻辑回归的输出是一个概率值,表示样本属于某一类别的概率。

通常使用 Sigmoid 函数:

img

使用逻辑回归进行二分类任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 加载鸢尾花数据集
iris = load_iris()
X = iris.data
y = iris.target

# 只取前两类做二分类任务
X = X[y != 2]
y = y[y != 2]

# 数据分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 训练逻辑回归模型
model = LogisticRegression()
model.fit(X_train, y_train)

# 预测
y_pred = model.predict(X_test)

# 评估模型
print(f"分类准确率: {accuracy_score(y_test, y_pred):.2f}")

输出结果为:

1
分类准确率: 1.00

支持向量机(SVM)

支持向量机是一种常用的分类算法,它通过构造超平面来最大化类别之间的间隔(Margin),使得分类的误差最小。

应用场景:文本分类、人脸识别等。

使用 SVM 进行鸢尾花分类任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from sklearn.svm import SVC
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 加载鸢尾花数据集
iris = load_iris()
X = iris.data
y = iris.target

# 数据分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 训练 SVM 模型
model = SVC(kernel='linear')
model.fit(X_train, y_train)

# 预测
y_pred = model.predict(X_test)

# 评估模型
print(f"SVM 分类准确率: {accuracy_score(y_test, y_pred):.2f}")

决策树(Decision Tree)

决策树是一种基于树结构进行决策的分类和回归方法。它通过一系列的”判断条件”来决定一个样本属于哪个类别。

应用场景:客户分类、信用评分等。

使用决策树进行分类任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 加载鸢尾花数据集
iris = load_iris()
X = iris.data
y = iris.target

# 数据分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 训练决策树模型
model = DecisionTreeClassifier(random_state=42)
model.fit(X_train, y_train)

# 预测
y_pred = model.predict(X_test)

# 评估模型
print(f"决策树分类准确率: {accuracy_score(y_test, y_pred):.2f}")

输出结果为:

1
决策树分类准确率: 1.00

无监督学习算法

K-means 聚类(K-means Clustering)

K-means 是一种基于中心点的聚类算法,通过不断调整簇的中心点,使每个簇中的数据点尽可能靠近簇中心。

应用场景:**客户分群、市场分析、图像压缩。**

使用 K-means 进行客户分群:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt

# 生成一个简单的二维数据集
X, _ = make_blobs(n_samples=300, centers=4, cluster_std=0.60, random_state=0)

# 训练 K-means 模型
model = KMeans(n_clusters=4)
model.fit(X)

# 预测聚类结果
y_kmeans = model.predict(X)

# 可视化聚类结果
plt.scatter(X[:, 0], X[:, 1], c=y_kmeans, s=50, cmap='viridis')
plt.show()

img

主成分分析(PCA)

PCA 是一种降维技术,它通过线性变换将数据转换到新的坐标系中,使得大部分的方差集中在前几个主成分上。

应用场景:图像降维、特征选择、数据可视化。

使用 PCA 降维并可视化高维数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from sklearn.decomposition import PCA
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt

# 加载鸢尾花数据集
iris = load_iris()
X = iris.data
y = iris.target

# 降维到 2 维
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)

# 可视化结果
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='viridis')
plt.title('PCA of Iris Dataset')
plt.show()

img

线性回归 (Linear Regression)

线性回归(Linear Regression)是机器学习中最基础且广泛应用的算法之一。

线性回归 (Linear Regression) 是一种用于预测连续值的最基本的机器学习算法,它假设目标变量 y 和特征变量 x 之间存在线性关系,并试图找到一条最佳拟合直线来描述这种关系。

1
y = w * x + b

其中:

  • y 是预测值
  • x 是特征变量
  • w 是权重 (斜率)
  • b 是偏置 (截距)

线性回归的目标是找到最佳的 wb,使得预测值 y 与真实值之间的误差最小。常用的误差函数是均方误差 (MSE)

1
MSE = 1/n * Σ(y_i - y_pred_i)^2

其中:

  • y_i 是实际值。
  • y_pred_i 是预测值。
  • n 是数据点的数量。

我们的目标是通过调整 w 和 b ,使得 MSE 最小化。

如何求解线性回归?

1、最小二乘法

最小二乘法是一种常用的求解线性回归的方法,它通过求解以下方程来找到最佳的 ( w ) 和 ( b )。

最小二乘法的目标是最小化残差平方和(RSS),其公式为:

647bf95e98352a445cab7d547a88ede5

其中:

  • yi 是实际值。
  • y^i 是预测值,由线性回归模型 y^i=wxi+by^i=wxi+b 计算得到。

通过最小化 RSS,可以得到以下正规方程:

c4cfc420f1878f532910b15d481b66a3

矩阵形式

将正规方程写成矩阵形式

4b024cba97bb200af12c9907fadae8f0

求解方法

通过求解上述矩阵方程,可以得到最佳的 w 和 b

9a03245250f7f00499476096b0df39b2

2、梯度下降法

梯度下降法的目标是最小化损失函数 J(w,b)。对于线性回归问题,通常使用均方误差(MSE)作为损失函数

836d14994f1d38d667b8f5556328c600

其中:

  • m样本数量。
  • yi 是实际值。
  • y^i 是预测值,由线性回归模型 y^i=wxi+by^i=wxi+b 计算得到。

梯度是损失函数对参数的偏导数,表示损失函数在参数空间中的变化方向。对于线性回归,梯度计算如下:

4fc791e09f80dc2129464eb7a3791883

参数更新规则

梯度下降法通过以下规则更新参数 wb

83363dfa9d7249a76cf8fb13d3a73467

其中:

  • α 是学习率(learning rate),控制每次更新的步长。

梯度下降法的步骤

  1. 初始化参数:初始化 wb 的值(通常设为 0 或随机值)。
  2. 计算损失函数:计算当前参数下的损失函数值 J(w,b)
  3. 计算梯度:计算损失函数对 wb偏导数
  4. 更新参数根据梯度更新 wb
  5. 重复迭代:重复步骤 2 到 4,直到损失函数收敛或达到最大迭代次数

使用 Python 实现线性回归

下面我们通过一个简单的例子来演示如何使用 Python 实现线性回归。

1、导入必要的库

1
2
3
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression

2、生成模拟数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression

# 生成一些随机数据
np.random.seed(0)
x = 2 * np.random.rand(100, 1)
y = 4 + 3 * x + np.random.randn(100, 1)

# 可视化数据
plt.scatter(x, y)
plt.xlabel('x')
plt.ylabel('y')
plt.title('Generated Data From Runoob')
plt.show()