月度归档:2022年09月

第3章 矩阵基本运算

3.1矩阵加法

矩阵加法和乘法是矩阵运算中最常用的操作之一,两个矩阵相加,需要它们的形状相同,进行对应元素的相加,如:C=A+B,其中C_{i,j}=A_{i,j}+B_{i,j}。矩阵也可以和向量相加,只要它们的列数相同,相加的结果是矩阵每行与向量相加,这种隐式地复制向量b到很多位置的方式称为广播(broadcasting),以下我们通过一个代码实例来说明。

打印结果为:
[[11 22 33]
[14 25 36]]

3.2矩阵点积

两个矩阵相加,要求它们的形状相同,
如果两个矩阵相乘,如A和B相乘,结果为矩阵C,矩阵A和B需要什么条件呢?条件比较简单,只要矩阵A的列数和矩阵B的行数相同即可。如果矩阵A的形状为m×n,矩阵B的形状为n×p,那么矩阵C的形状就是m×p,例如:
C=AB,则它们的具体乘法操作定义为:
C_{i,j}=\sum_k A_{i,k} B_{k,j} \tag{2.11}
即矩阵C的第i,j个元素C_{i,j}为矩阵的A第i行与矩阵B的第j列的点积。
矩阵乘积有很多重要性质,如满足分配律A(B+C)=AB+AC 和结合律,A(BC)=(AB)C。大家思考一下是否满足交换律?
一般情况,交换律不成立,即AB\neq BA
两个矩阵可以相乘,矩阵也可和向量相乘,只要矩阵的列数等于向量的行数或元素个数。如:
WX=b \tag{2.12}
其中W\in R^{m\times n},b\in R^m ,X\in R^n

3.3转置(transpose)

转置以主对角线(左上到右下)为轴进行镜像操作,通俗一点来说就是行列互换。将矩阵A转置表示为A^T,定义如下:
\left(A^T\right)_{i,j}=A_{j,i}\tag{2.13}
例3:
A=\left[\begin{matrix}a_{1,1} & a_{1,2} &a_{1,3} \cr a_{2,1} & a_{2,2} &a_{2,3}\end{matrix}\right]
A^T=\left[\begin{matrix}a_{1,1} & a_{2,1} \\ a_{1,2} & a_{2,2}\cr a_{1,3} & a_{2,3} \end{matrix}\right]
向量可以看作只有一列的矩阵,把向量x进行转置,得到下式。
x^T=[x_1,x_2,\cdots,x_n]
另外,相乘矩阵的转置也有很好性质,如:\left(AB\right)^T=B^T A^T,满足穿脱原则,如A、B像两件衣服,A先穿、B后穿,脱时反过来,B^T在前,A^T在后。
用NumPy如何实现张量的转置?很简单,利用张量的T属性即可,示例如下:

打印结果如下:
[[1 2 3]
[4 5 6]]
[[1 4]
[2 5]
[3 6]]

3.4矩阵的阿达马积

与向量的阿达马积相同,两个矩阵(如A、B)的阿达马积也是对应元素相乘,记为:A\odot B
例4:
\begin{aligned}A\odot B &=\left[\begin{matrix}1 & 2 & 3 \\ 4 & 5 &6\end{matrix}\right]\left[\begin{matrix}1 & 2 & 4 \\ 3 & 5 &0\end{matrix}\right]\\&=\left[\begin{matrix}1\times 1 & 2\times 2 & 3\times 4 \\ 4\times 3 &5\times 5 & 6\times 0\end{matrix}\right]\\&=\left[\begin{matrix}1 & 4 & 12 \\ 12 & 25 &0\end{matrix}\right]\end{aligned}
两个矩阵对应元素的运算除相乘外,还有A+B、A-B、A/B等。
例5:用Python实现矩阵的阿达马积

运行结果如下:
[[ 1 4 12]
[12 25 0]]
例6:点积、对应元素运算在神经网络中的应用
神经网络的结构如下:

运行结果
[0.35434369 0.64565631]

3.5行列式

一个n\times n的方阵A的行列式记为det(A)或者|A|,一个2×2矩阵的行列式可表示如下:
\left|\begin{matrix}a & b \\ c & d\end{matrix}\right|=ad-bc
把一个n阶行列式中的元素a_{ij}所在的第i行和第j列划去后,留下来的n-1阶行列式叫做元素a_{ij}的余子式,
记作M_{ij}。记A_{ij}=\left(-1\right)^{i+j} M_{ij},叫做元素a_{ij}的代数余子式。一个n\times n矩阵的行列式等于其任意行(或列)的元素与对应的代数余子式乘积之和,即
行列式的性质:
n阶矩阵的转置矩阵A^T的行列式等于A的行列式,即:|A^T|=|A|
方程组或矩阵或行列式的初等行变换:
(1)将行列式的两行交换;
(2)将行列式的某一行乘以k倍之后加到另一行。
第一种变换将使行列式的值反号,第2种变换行列式的值不变。

3.6迹运算

迹运算返回的是矩阵对角元素的和:
Tr(A)=\sum_i A_{i,i}
迹运算在某些场合非常有用。若不使用求和符号,有些矩阵运算很难描述,而通过矩阵乘法和迹运算符号可以清楚地表示。例如,迹运算提供了另一种描述矩阵Frobenius 范数的方式:
||A||_F=\sqrt{Tr(AA^T)}
对迹运算的表达式,我们可以使用很多等式来表示。例如,迹运算在转置运算下是不变的:
Tr(A)=Tr(A^T)
多个矩阵相乘得到的方阵的迹,和将这些矩阵中的最后一个挪到最前面之后相乘的迹是相同的。当然,我们需要考虑挪动之后矩阵乘积依然有定义:
Tr(ABC)=Tr(CAB)=Tr(BCA)
利用Python的NumPy对矩阵求迹同样方便。请看以下示例。

打印结果:
15
15
171
171

第4章 预处理文本数据

4.1文本预处理一般流程

图4-1 典型的文本预处理流程

4.2 英文文本预处理实例

这里使用NLTK工具,NLTK,Natural Language Toolkit是一个Python模块,提供了多种语料库(Corpora)和词典(Lexicon)资源,比如WordNet等,以及一系列基本的自然语言处理工具集,包括:分句,标记解析(Tokenization),词干提取(Stemming),词性标注(POS Tagging)和句法分析(Syntactic Parsing)等,是对英文文本数据进行处理的常用工具。

4.2.1 安装配置NLTK

1、安装nltk库
pip install nltk
2、安装语料库
进入官网地址下官网地址
https://gitcode.net/mirrors/nltk/nltk_data?utm_source=csdn_github_accelerator
3、查看解压后的语料库可以放在本地某个些位置(如python安装目录中share目录下),
随后将下载的语料库中的packages包下的所有文件复制到nltk_data(没有就创建该目录)
另外,需要解压(注意:采用解压文件方式)tokenizers下的punkt压缩文件。

4.2.2导入语料库

运行结果
['adventure', 'belles_lettres', 'editorial', 'fiction', 'government', 'hobbies', 'humor', 'learned', 'lore', 'mystery', 'news', 'religion', 'reviews', 'romance', 'science_fiction']
共有57340个句子
共有1161192个单词

4.2.3分词(tokenize)

分词就是将句子拆分成具有语言语义学上意义的词,英文分词:单词之间是以空格作为自然分界符的;中文分词工具:结巴分词。

打印结果
['Python', 'is', 'a', 'widely', 'used', 'high-level', 'programming', 'language', 'for', 'general-purpose', 'programming', '.']
全模式: 欢迎/ 欢迎您/ 来到/ 上海/ 张/ 江/ 高/ 科
精确模式: 欢迎您/ 来到/ 上海/ 张江/ 高科
搜索引擎模式: 欢迎/ 欢迎您/ 来到/ 上海/ 张江/ 高科
【说明】
jieba.cut():返回的是一个迭代器。参数cut_all是bool类型,默认为False,即精确模式,当为True时,则为全模式。

精确模式,试图将句子最精确地切开,适合文本分析;
全模式,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义;
搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。

4.2.4词形归一化

词干提取(stemming),如look, looked, looking,词干提取,如将ing, ed去掉,只保留单词主干,这影响语料学习的准确度。

运行结果:
look
look
look
look
look
look

4.2.5词性标注 (Part-Of-Speech)

将单词的各种词形归并成一种形式,如go,went,gone
如果以前没有下载averaged_perceptron_tagger,可以以下方式下载:
nltk.download('averaged_perceptron_tagger')
词性标注

运行结果
[('Python', 'NNP'), ('is', 'VBZ'), ('a', 'DT'), ('widely', 'RB'), ('used', 'VBN'), ('programming', 'NN'), ('language', 'NN'), ('.', '.')]

4.2.6去除停用词

为节省存储空间和提高搜索效率,NLP中会自动过滤掉某些字或词。
中文停用词表:
中文停用词库• 哈工大停用词表• 四川大学机器智能实验室停用词库• 百度停用词列表
英文使用NLTK去除停用词
使用函数stopwords.words()

运行结果
原始词: ['Python', 'is', 'a', 'widely', 'used', 'programming', 'language', '.']
去除停用词后: ['Python', 'widely', 'used', 'programming', 'language', '.']

4.3 中文文本数据处理

在自然语言处理(NLP)任务中,我们将自然语言交给机器学习算法来处理,但机器无法直接理解人类的语言,因此,首先要做的就是将语言数字化。如何对自然语言进行数字化呢?词向量提供了一种很好的方式。何为词向量?简单来说就是对字典D中的任意词w指定一个固定长度的实值向量,如v(w)∈R^m, v(w) 就称为w的词向量,m为词量的长度。
中文文本数据处理一般步骤,如图4-2所示。

图4-2 中文文本处理一般步骤
接下来,我们先用PyTorch的词嵌入模块把语句用词向量表示,然后把这些词向量导入GRU模型中,这是自然语言处理的基础,也是核心部分。
以下是中文文本处理代码示例。
1)收集数据,定义停用词。

2)利用jieba进行分词,并过滤停止词。

3)去重,然后对每个词加上索引或给一个整数。

4)词向量或词嵌入。
这里采用PyTorch的nn.Embedding层,把整数转换为向量,参数为(词总数,向量长度)。

运行结果如下:
[tensor([-1.2987, -1.7718, -1.2558, 1.1263, -0.3844, -1.0864, -1.1354, -0.5142]),
tensor([ 0.3172, -0.3927, -1.3130, 0.2153, -0.0199, -0.4796, 0.9555, -0.0238]),
tensor([ 0.9242, 0.8165, -0.0359, -1.9358, -0.0850, -0.1948, -1.6339, -1.8686]),
tensor([-0.3601, -0.4526, 0.2154, 0.3406, 0.0291, -0.6840, -1.7888, 0.0919]),
tensor([ 1.3991, -0.0109, -0.4496, 0.0665, -0.5131, 1.3339, -0.9947, -0.6814]),
tensor([ 0.8438, -1.5917, 0.6100, -0.0655, 0.7406, 1.2341, 0.2712, 0.5606])]

第3 章预处理图像数据

3.1 数据增强简介

提高模型泛化能力的最重要的三大因素是数据、模型、损失函数,其中数据又是三个因素中最重要的因素,但数据的获取往往不充分或成本比较高。是否有其他方法可以快速又便捷地增加数据量呢?在一些领域是存在的,如在图像识别、语言识别领域,可以通过水平或垂直翻转图像、裁剪、色彩变换、扩展和旋转等数据增强(Data Augmentation)技术来增加数据量。
通过数据增强技术不仅可以扩大训练数据集的规模、降低模型对某些属性的依赖,从而提高模型的泛化能力,也可以对图像进行不同方式的裁剪,使感兴趣的物体出现在不同位置,从而减轻模型对物体出现位置的依赖性,还可以通过调整亮度、色彩等因素来降低模型对色彩的敏感度等。当然,对图像做这些预处理时,不宜使用会改变其类别的转换,如对于手写的数字,如果旋转90度,就有可能把9变成6,或把6变为9。
此外,把随机噪声添加到输入数据或隐藏单元中也是增加数据量的方法之一。

3.2 使用OpenCV实现图像增强

3.2.1导入需要的库

3.2.2导入图像数据

运行结果

3.2.3增加高斯噪声

读取处理后的图像

运行结果

3.2.4图像缩小为0.5倍

运行结果

3.2.5图像水平翻转

读取处理后的图像

运行结果

3.2.6图像垂直翻转

读取处理后的图像

运行结果

3.2.6增强图像亮度

读取处理后的图像

运行结果

3.2.7混合增强

读取处理后的图像

运行结果

3.3 图像去雾

图像去雾的研究算法有很多,但是主要分为两类:基于图像增强的去雾算法和基于图像复原的去雾算法。 基于图像增强的去雾算法:基于图像增强的去雾算法出发点是尽量去除图像噪声,提高图像对比度,从而恢复出无雾清晰图像。代表性方法有:直方图均衡化(HLE)、自适应直方图均衡化(AHE)、限制对比度自适应直方图均衡化(CLAHE)、Retinex算法、小波变换、同态滤波等 基于图像复原的去雾算法:这一系列方法基本是基于大气退化模型,进行响应的去雾处理。代表性算法有来自何凯明博士的暗通道去雾算法、基于导向滤波的暗通道去雾算法、Fattal的单幅图像去雾算法(Single image dehazing)、Tan的单一图像去雾算法(Visibility in bad weather from a single image)、Tarel的快速图像恢复算法(Fast visibility restoration from a single color or gray level image)、贝叶斯去雾算法(Single image defogging by multiscale depth fusion),基于大气退化模型的去雾效果普遍好于基于图像增强的去雾算法,后面挑选的传统去雾算法例子也大多是基于图像复原的去雾算法。
这里主要介绍的基于图像增强的图像去雾,在此使用直方图均衡化和局部直方图均衡化进行图像的去雾处理。

3.3.1 显示原图

3.3.2全局直方图均衡化

运行结果

3.3.3 局部直方图均衡化

运行结果

3.3.4 比较直方图

运行结果

最明显的变化就是某一些像素点数比较少的亮度级别消失了,而且图像直方图的变化也没有那么突兀了,图像也就更加清晰了。

3.4 使用PyTorch实现图像增强

使用pytorch中的torchvision模块实现数据增强。

3.4.1 按比例缩放

随机比例缩放主要使用的是 torchvision.transforms.Resize()函数。
1)显示原图。

运行结果如图3-1所示。

图3-1 小猫原图
2)随机比例缩放。

运行结果如图3-2所示。
原图像大小: (500, 414)
缩放后大小: (200, 100)

图3-2 缩放后的图像

3.4.2 裁剪

随机裁剪有两种方式,一种是对图像在随机位置进行截取,可传入裁剪大小,使用的函数为torchvision.transforms.RandomCrop();另一种是在中心,按比例裁剪,函数为 torchvision.transforms.CenterCrop()。

运行结果如图3-3所示。

图3-3 剪辑后的图像

3.4.3 翻转

翻转猫还是猫,不会改变其类别。通过翻转图像可以增加其多样性,所以随机翻转也是一种非常有效的手段。在 torchvision 中,随机翻转使用的是 torchvision.transforms.RandomHorizontalFlip() 、torchvision.transforms.RandomVerticalFlip()和 torchvision.transforms.RandomRotation()等函数。

运行结果如图3-4所示。

图3-4 翻转后的图像

3.4.4改变颜色

除了形状变化外,颜色变化又是另外一种增强方式,其中可以设置亮度变化,对比度变化和颜色变化等,在 torchvision 中主要是用 torchvision.transforms.ColorJitter() 来实现的。

运行结果如图3-5所示。

图3-5 改变颜色后的图像

3.4.5组合多种增强方法

我们可用torchvision.transforms.Compose() 函数把以上这些变化组合在一起。

运行结果如图3-6所示。

图3-6实现图像增强后的部分图像

第2 章 预处理结构化数据

2.1 预处理缺失值

2.1.1 数据生成

运行结果为:

2.1.2查看缺失数据

查看各列缺失数据个数

A 0
B 0
C 1
D 1
dtype: int64
由此可知,C,D两列各有一个缺失或NaN数据。

2.1.3删除含缺失数据的特征或样本

2.1.4填充缺失值

【说明】
strategy还可以是:median,most_frequent
这里Imputer是sklearn中的转换器类,主要用于数据转换。这些类中常用方法是fit和transform。fit用于参数识别并构建相应数据的补齐模型,而transform方法则是根据fit构建的模型进行缺失数据的补齐。
Imputer方法创建一个预处理对象,其中strategy为默认缺失值的字符串,默认为NaN;示例中选择缺失值替换方法是均值(默认),还可以选择使用中位数和众数进行替换,即strategy值设置为median或most_frequent;后面的参数axis用来设置输入的轴,默认值为0,即使用列做计算逻辑。然后使用预处理对象的fit_transform方法对df(数据框对象)进行处理,该方法是将fit和transform组合起来使用。

2.1.5用前后数据填充缺失值

运行结果

运行结果

(6)不同列的缺失值,用不同值填充

运行结果

2.2 处理类别数据

类别数据分为标称特征和有序特征,有序特征指类别的值是有序的或可以排序的,如体恤衫的尺寸:XL>L>M.而标称特征是不具备排序的特性,如体恤衫的颜色,对颜色进行大小比较是不合常理的。一般类标(分类标签)采用标称特征。

2.2.1生成数据

2.2.2有序特征的映射

这里size特征为有序特征,对该特征可以采用有序数字化处理。

如果我们想还原,可以采用逆对应或逆字典inv_size_mapping = {v: k for k, v in size_mapping.items()}

2.2.3标签的转换

{'a': 0, 'b': 1}

运行结果

此外,也可以用sklearn中的LabelEncoder类将字符串类标转换为整数。

2.2.4 sklearn中常用的预处理函数

sklearn中常用的预处理函数及其功能大致有:
sklearn特征处理常用函数:

类名 功能 说明
StandardScaler 数据预处理(归一化) 标准化,基于特征矩阵的列,将特征值转换至服从标准正态分布
MinMaxScaler 数据预处理(归一化) 区间缩放,基于最大最小值,将特征值转换到[0, 1]区间上
Normalizer 数据预处理(归一化) 基于特征矩阵的行,将样本向量转换为“单位向量”
Binarizer 数据预处理(二值化) 基于给定阈值,将定量特征按阈值划分
OneHotEncoder 数据预处理(哑编码) 将定性数据编码为定量数据
Imputer 数据预处理(缺失值计算) 计算缺失值,缺失值可填充为均值等
PolynomialFeatures 数据预处理(多项式数据转换) 多项式数据转换
FunctionTransformer 数据预处理(自定义单元数据转换) 使用单变元的函数来转换数据
VarianceThreshold 特征选择(Filter) 方差选择法
SelectKBest 特征选择(Filter) 可选关联系数、卡方校验、最大信息系数作为得分计算的方法
RFE 特征选择(Wrapper) 递归地训练基模型,将权值系数较小的特征从特征集合中消除
SelectFromModel 特征选择(Embedded) 训练基模型,选择权值系数较高的特征
PCA 降维(无监督) 主成分分析法
LDA 降维(有监督) 线性判别分析法

运行结果为
array([0, 1, 0], dtype=int64)

2.2.5标称特征转换为独热编码(One-Hot)

可以利用有序特征一般转换为整数,具体可采用sklearn中的LabelEncoder类将字符串类标转换为整数,示例代码如下:

运行结果
array([[1, 1, 10.1],
[2, 2, 13.5],
[0, 3, 15.3]], dtype=object)
如果把标称特征转换为整数,算法将假定gree大于blue,red大于gree,虽然算法这一假设不很合理,而且能够生成有用的结果,然而,这个结果可能不是最优的。
如何生成更好或更合理的结果?这里就要引入独热编码(one-hot encoding)技术,这种技术的理念就是创建一个新的虚拟特征,该虚拟特征的列数就是类别个数,其值为二进制,每行只有一个1,其他都是0,值为1的对应该类别。
如将color特征,共有3种类别,所以将该转换为三个新的特征:blue、gree和red,
如果是blue,新特征就是[1,0,0],如果是gree,新特征就是[0,1,0].
独热编码可用sklearn.preprocessing中的OneHotEncoder类来实现,具体代码如下:

运行结果:
array([[ 0. , 1. , 0. , 1. , 10.1],
[ 0. , 0. , 1. , 2. , 13.5],
[ 1. , 0. , 0. , 3. , 15.3]])
这种方法先把字符转换为整数,然后把整数转换为虚拟特征,是否有更简单的方法?一步就实现呢?采用pandas的get_dummies方法就可以,它将把字符串直接转换为独热编码。以下为具体代码:

运行结果

2.3合并数据

在MySQL的多表查询章节中,我们介绍了两表关联问题,具体请看如下图形;
(图2-1 多表关联)

如果现在把表换成数据文件或数据集,该如何实现呢?Python中有类似方法,把连接的关键字有JION改为MERGE一下就可,其它稍作修改,具体请看下表:
(表2-1 两个DataFrame数据集关联格式表)

关联方式 关联语句
df1内连接df2 pd.merge(df1,df2,on='key')
pd.merge(df1,df2,on='key',how='inner')
pd.merge(df1,df2,left_on='key',right_on='key',how='inner')
df1左连接df2 pd.merge(df1,df2,on='key',how='left')
df1右连接df2 pd.merge(df1,df2,on='key',how='right')
df1全连接df2 pd.merge(df1,df2,on='key',how='outer')

下面同时示例来说明以上命令的具体使用。

两个集合连接以后,有些值可能为空或为NaN,NaN值有时计算不方便,我们可以把NaN修改为其它值,如为0值等,如果要修改或补充为0值,该如何操作呢?非常方便,利用DataFrame的fillna函数即可。其使用方法如下:

2.4 离散化连续数据

上节我们介绍了有时便于数据分析,需要把两个或多个数据集合并在一起,这在大数据的分析或竞赛中是经常干的事。不过有时为便于分析,需要把一些连续性数据离散化或进行拆分,这也是数据分析常用方法,如对年龄字段,可能需转换成年龄段,这样可能更好地对数据的进行分类或预测,这种处理方式往往能提升分类或预测性能,这种方法又称为离散化或新增衍生指标等。
如何离散化连续性数据?在一般开发语言中,可以通过控制语句来实现,但如果分类较多时,这种方法不但繁琐,效率也比较低。在Pandas中是否有更好方法?如果有,又该如何实现呢?
pandas有现成方法,如cut或qcut等,不需要编写代码,至于如何使用还是通过实例来说明。

对连续性字段进行离散化是机器学习常用方法,此外,对一些类型字段,如上例中type字段,含有1,2两种类型,实际上1,2两种类型是平等,它们只是代表不同类型,并无大小区别,如果在回归分析中如果用1、2代入算法中,则与业务含义就不相符了,对这种情况,我们该如何处理呢?
在机器学习中通常把这些分类变量或字段转换为“指标矩阵”或“哑变量矩阵”,具体做法就是,假设该字段或变量有k种取值(上例中type只有2中取值),则可派生出一个k列矩阵,矩阵值为0或1,0表示无对应分类,1表示有对应分类。我们可以编写程序实现,也可用Pandas中get_dummies函数来实现,具体实现请看以下示例。

【说明】
这种方法,在SparkML中有专门的算法--独热编码(OneHotEncoder),独热编码将标签指标映射为二值向量,其中最多一个单值。这种编码被用于将种类特征使用到需要连续特征的算法,如逻辑回归等。

2.5规范化数据

特征缩放是机器学习中常用方法,除少数算法(如决策树、随机森林)无需缩放外,大多算法可以通过缩放使其性能更佳。
缩放的方法大致有两种,归一化(normalization)和标准化(standardization)。归一化把特征值缩放到一个较小区间,如[0,1]或[-1,1]等,最大-最小缩放就是一个例子。
\hat x^i=\frac{x^i - x_{min}}{x_{max} - x_{min}}
通过标准化我们可以使特征均值为0,方差为1或某个参数,使其符合正态分布。标准化方法保持了奇异值的有用信息,且降低算法受这些值的影响。
\hat x^i=\frac{x^i-\mu_x}{\delta_x}
其中\mu_x ,\delta_x分别是样本某特征列的均值和标准差。以下是用sklearn.preprocessing的类来实现。

2.5.1导入数据

运行结果

 

2.5.2探索数据

运行结果
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 178 entries, 0 to 177
Data columns (total 14 columns):
酒等级 178 non-null int64
酒精 178 non-null float64
苹果酸 178 non-null float64
灰 178 non-null float64
灰分的碱度 178 non-null float64
镁 178 non-null int64
总酚 178 non-null float64
黄酮类 178 non-null float64
非类黄烷酚 178 non-null float64
原花青素 178 non-null float64
颜色强度 178 non-null float64
色调 178 non-null float64
稀释葡萄酒 178 non-null float64
脯氨酸 178 non-null int64
dtypes: float64(11), int64(3)
memory usage: 19.6 KB
由此可知:共有14列,178个样本

2.5.3查看各特征的统计信息

运行结果

2.5.4查看是否有缺失数据

2.5.5划分数据

2.5.6对数据进行归一化

运行结果

2.6 选择有意义的特征

当数据预处理完成后,我们需要选择有意义的特征输入机器学习的算法和模型进行训练。通常来说,从两个方面考虑来选择特征:
特征是否发散:
如果一个特征不发散,例如方差接近于0,也就是说样本在这个特征上基本上没有差异,这个特征对于样本的区分并没有什么用。
特征与目标的相关性:
这点比较显见,与目标相关性高的特征,应当优选选择。
降维
降维方法比较多,如正则化降维、PCA、LDA等降维。

2.6.1 利用方差过滤法选择特征

这种方法比较粗糙,有时可能性能反而会下降。

2.6.2利用相关系数法

这里利用卡方来计算相关系数。用feature_selection库的SelectKBest类和chi2类来计算相关系数,选择特征的代码如下:

2.6.3利用正则化方法选择特征

使用正则化惩罚项的模型,除了可用于筛选特征外,也可降维。这里使用feature_selection库的SelectFromModel类结合带L2惩罚项的逻辑回归模型来选择特征,具体代码如下:

2.6.4基于树模型来选择特征

树模型中GBDT也可用于特征选择,使用feature_selection库的SelectFromModel类结合GBDT模型,来选择特征的代码如下:

2.6.5利用随机森林来选择特征

2.7 特征组合

前面我们介绍了填充缺失值、对特征进行归一化、类别数据数值化或向量化、对连续型特征分段(或分箱)、找出更有意义的特征等预处理方法,这些方法在机器学习中经常使用。这些主要是对单个特征的处理,有时我们需要组合几个特征得到一个新特征,得到的这个新特征比原来的特征更有意义。假如我们预测房价的数据中包括经度和纬度,如果我们能组合这两个特征,得到一个有关街区的新特征,这个特征应该更具代表性。当然,组合前,我们可以把经度和纬度进行分组或分箱,然后在进行组合。

2.8 衍生新特征

这里以日期特征为例,由日期特征衍生出年、月、日、季度、星期几等新特征。这些特征有时更能刻画数据的规律,如是否周末、季度、工作日等的数据更有规律可循。具体代码如下:

运行结果如下:

1. Pandas基础篇

Python有了NumPy的Pandas,用Python处理数据就像使用Exel或SQL一样简单方便。
Pandas是基于NumPy的Python 库,它被广泛用于快速分析数据,以及数据清洗和准备等工作。可以把 Pandas 看作是 Python版的Excel或Table。Pandas 有两种数据结构:
Series和DataFrame,Pandas经过几个版本的更新,目前已经成为数据清洗、处理和分析的不二选择。

1.1 问题:Pandas有哪些优势?

科学计算方面NumPy是优势,但NumPy中没有标签,数据清理、数据处理就不是其强项了。而DataFrame有标签,就像SQL中的表一样,所以在数据处理方面DataFrame就更胜一筹了,具体包含以下几方面:
(1)读取数据方面
Pandas提供强大的IO读取工具,csv格式、Excel文件、数据库等都可以非常简便地读取,对于大数据,pandas也支持大文件的分块读取。
(2)在数据清洗方面
面对数据集,我们遇到最多的情况就是存在缺失值,Pandas把各种类型数据类型的缺失值统一称为NaN,Pandas提供许多方便快捷的方法来处理这些缺失值NaN。
(3)分析建模阶段
在分析建模阶段,Pandas自动且明确的数据对齐特性,非常方便地使新的对象可以正确地与一组标签对齐,由此,Pandas就可以非常方便地将数据集进行拆分-重组操作。
(4)结果可视化方面
结果展示方面,我们都知道Matplotlib是个数据视图化的好工具,Pandas与Matplotlib搭配,不用复杂的代码,就可以生成多种多样的数据视图。

1.2 Pandas数据结构

Pandas中两个最常用的对象是Series和DataFrame。使用pandas前,需导入以下内容:

Pandas主要采用Series和DataFrame两种数据结构。Series是一种类似一维数据的数据结构,由数据(values)及索引(indexs)组成,而DataFrame是一个表格型的数据结构,它有一组序列,每列的数据可以为不同类型(NumPy数据组中数据要求为相同类型),它既有行索引,也有列索引。

图1-1 DataFrame结构

1.3 Series

上章节我们介绍了多维数组(ndarray),当然,它也包括一维数组,Series类似一维数组,为啥还要介绍Series呢?或Series有哪些特点?
Series一个最大特点就是可以使用标签索引,序列及ndarray也有索引,但都是位置索引或整数索引,这种索引有很多局限性,如根据某个有意义标签找对应值,切片时采用类似[2:3]的方法,只能取索引为2这个元素等等,无法精确定位。
Series的标签索引(它位置索引自然保留)使用起来就方便多了,且定位也更精确,不会产生歧义。以下通过实例来说明。
(1)使用Series

0 1
1 3
2 6
3 -1
4 2
5 8
dtype: int64
(2)使用Series的索引

a 1
c 3
d 6
e -1
b 2
g 8
dtype: int64
(3)根据索引找对应值

1.4 DataFrame

DataFrame除了索引有位置索引也有标签索引,而且其数据组织方式与MySQL的表极为相似,除了形式相似,很多操作也类似,这就给操作DataFrame带来极大方便。这些是DataFrame特色的一小部分,它还有比数据库表更强大的功能,如强大统计、可视化等等。
DataFrame有几个要素:index、columns、values等,columns就像数据库表的列表,index是索引,values就是值。

图1-2 DataFrame结果

1.4.1 生成DataFrame

生成DataFrame有很多,比较常用的有导入等长列表、字典、numpy数组、数据文件等。

1.4.2 获取数据

获取DataFrame结构中数据可以采用obj[]操作、obj.iloc[]、obj.loc[]等命令。
(1)使用obj[]来获取列或行

(2)使用obj.loc[] 或obj.iloc[]获取行或列数据。
loc通过行标签获取行数据,iloc通过行号获取行数据。
loc 在index的标签上进行索引,范围包括start和end.
iloc 在index的位置上进行索引,不包括end.
这两者的主要区别可参考如下示例:

【说明】
除使用iloc及loc外,早期版本还有ix格式。pandas0.20.0及以上版本,ix已经丢弃,请尽量使用loc和iloc;

1.4.3 修改数据

我们可以像操作数据库表一样操作DataFrame,删除数据、插入数据、修改字段名、索引名、修改数据等,以下通过一些实例来说明。

图1-3 数据结构

1.4.4 汇总统计

Pandas有一组常用的统计方法,可以根据不同轴方向进行统计,当然也可按不同的列或行进行统计,非常方便。
常用的统计方法有:
表1-1 Pandas统计方法

以下通过实例来说明这些方法的使用
(1)把csv数据导入pandas

(2)查看df的统计信息

【说明】

即各项-均值的平方求和后再除以N 。
std:表示标准差,是var的平方根。

1.4.5选择部分列

这里选择学生代码、课程代码、课程名称、程程成绩,注册日期等字段

1.4.6删除重复数据

如果有重复数据(对df1的所有列),则删除最后一条记录。

1.4.7补充缺省值

(1)用指定值补充NaN值
这里要求把stat_date的缺省值(NaN)改为'2018-09-01'

(2)可视化,并在图形上标准数据

结果为:

(3)导入一些库及支持中文的库

(4)画图

运行结果


图1-4 可视化结果

1.4.8 Pandas操作MySQL数据库

(1)从MySQL数据库中获取学生基本信息表

(2)查看df_info前3行数据

(3)选择前两个字段

(4)df2 与df_info1 根据字段stud_code 进行内关联

(5)对df3 根据字段stud_code,sub_code进行分组,并求平均每个同学各科的平均成绩。

【备注】
如果需要合计各同学的成绩,可用如下语句。

(6)选择数学分析课程,并根据成绩进行降序。

(7)取前5名

注:DataFrame数据结构的函数或方法有很多,大家可以通过df.[Tab键]方式查看,具体命令的使用方法,如df.count(),可以在Ipython命令行下输入:?df.count() 查看具体使用,退出帮助界面,按q即可。

1.4.9 Pandas操作excel

把DataFrame数据写入excel中的多个sheet中

第4章 PyTorch数据处理工具箱

在3.5节我们利用PyTorch的torchvision、data等包,下载及预处理MNIST数据集。数据下载和预处理是机器学习、深度学习实际项目中耗时又重要的任务,尤其是数据预处理,关系到数据质量和模型性能,往往要占据项目的大部分时间。好在PyTorch为此提供了专门的数据下载、数据处理包,使用这些包,可极大提高我们的开发效率及数据质量。
本章将介绍以下内容:
 简单介绍PyTorch相关的数据处理工具箱
 utils.data简介
 torchvision简介
 TensorBoard简介及实例

4.1 数据处理工具箱概述

通过第3章,读者应该对torchvision、data等数据处理包有了初步的认识,但可能理解还不够深入,接下来我们将详细介绍。PyTorch涉及数据处理(数据装载、数据预处理、数据增强等)主要工具包及相互关系如图4-1所示。

图4-1 PyTorch主要数据处理工具
图4-1 的左边是torch.utils.data工具包,它包括以下4个类:
1) Dataset:是一个抽象类,其他数据集需要继承这个类,并且覆写其中的两个方法(__getitem__、__len__)。
2) DataLoader:定义一个新的迭代器,实现批量(batch)读取,打乱数据(shuffle)并提供并行加速等功能。
3) random_split:把数据集随机拆分为给定长度的非重叠新数据集。
4) *sampler:多种采样函数。
图4-1中间是PyTorch可视化处理工具(torchvision),它是PyTorch的一个视觉处理工具包,独立于PyTorch,需要另外安装,使用pip或conda安装即可:

4.2 utils.data简介

utils.data包括Dataset和DataLoader。torch.utils.data.Dataset为抽象类。自定义数据集需要继承这个类,并实现两个函数,即__len__和__getitem__。前者提供数据的大小(size),后者通过给定索引获取数据和标签或一个样本。 __getitem__一次只能获取一个样本,所以通过torch.utils.data.DataLoader来定义一个新的迭代器,实现batch读取。首先我们来定义一个简单的数据集,然后具体使用Dataset及DataLoader,给读者一个直观的认识。 1)导入需要的模块。

2)定义获取数据集的类。
该类继承基类Dataset,自定义一个数据集及对应标签。

3)获取数据集中数据。

以上数据以元组格式返回,每次只返回一个样本。实际上,Dateset只负责数据的抽取,一次调用__getitem__只返回一个样本。如果希望批量处理(batch),同时还要进行shuffle和并行加速等操作,可选择DataLoader。DataLoader的格式为:

主要参数说明如下。
 dataset: 加载的数据集。
 batch_size: 批大小。
 shuffle:是否将数据打乱。
 sampler:样本抽样。
 num_workers:使用多进程加载的进程数,0代表不使用多进程。
 collate_fn:如何将多个样本数据拼接成一个batch,一般使用默认的拼接方式即可。
 pin_memory:是否将数据保存在锁页内存(pin memory区,pin memory中的数据转到GPU会快一些。
 drop_last:dataset 中的数据个数可能不是 batch_size的整数倍,drop_last为True会将多出来不足一个batch的数据丢弃。
使用函数DataLoader加载数据。

运行结果如下:
i: 0
data: tensor([[1, 2],
[3, 4]])
Label: tensor([0, 1])
i: 1
data: tensor([[2, 1],
[3, 4]])
Label: tensor([0, 1])
i: 2
data: tensor([[4, 5]])
Label: tensor([2])
从这个结果可以看出,这是批量读取。我们可以像使用迭代器一样使用它,如对它进行循环操作。不过它不是迭代器,我们可以通过iter命令转换为迭代器。

一般用data.Dataset处理同一个目录下的数据。如果数据在不同目录下,不同目录代表不同类别(这种情况比较普遍),使用data.Dataset来处理就不很方便。不过,可以使用PyTorch提供的另一种可视化数据处理工具(即torchvision)就非常方便,不但可以自动获取标签,还提供很多数据预处理、数据增强等转换函数。

4.3 torchvision简介

torchvision有4个功能模块,model、datasets、transforms和utils。其中model后续章节将介绍,利用datasets下载一些经典数据集,3.5小节有实例,读者可以参考一下。本节主要介绍如何使用datasets的ImageFolder处理自定义数据集,如何使用transforms对源数据进行预处理、增强等。下面重点介绍transforms及ImageFolder。

4.3.1 transforms

transforms提供了对PIL Image对象和Tensor对象的常用操作。
1)对PIL Image的常见操作如下。
 Scale/Resize: 调整尺寸,长宽比保持不变。
 CenterCrop、RandomCrop、RandomSizedCrop:裁剪图像,CenterCrop和RandomCrop在crop时是固定size,RandomResizedCrop则是random size的crop。
 Pad: 填充。
 ToTensor: 把一个取值范围是[0,255]的PIL.Image 转换成 Tensor。形状为(H,W,C)的numpy.ndarray,转换成形状为[C,H,W],取值范围是[0,1.0]的torch.FloatTensor。
 RandomHorizontalFlip:图像随机水平翻转,翻转概率为0.5。
 RandomVerticalFlip: 图像随机垂直翻转。
 ColorJitter: 修改亮度、对比度和饱和度。
2)对Tensor的常见操作如下。
 Normalize: 标准化,即减均值,除以标准差。
 ToPILImage:将Tensor转为PIL Image。
如果要对数据集进行多个操作,可通过Compose将这些操作像管道一样拼接起来,类似于nn.Sequential。以下为示例代码。

还可以自己定义一个python lambda表达式,如将每个像素值加10,可表示为:transforms.Lambda(lambda x: x.add(10))。
更多内容可参考官网,地址为https://pytorch.org/docs/stable/torchvision/transforms.html。

4.3.2 ImageFolder

当文件依据标签处于不同文件下时,如:
─── data
├── zhangliu
│ ├── 001.jpg
│ └── 002.jpg
├── wuhua
│ ├── 001.jpg
│ └── 002.jpg
.................
我们可以利用 torchvision.datasets.ImageFolder 来直接构造出 dataset,代码如下:

ImageFolder 会将目录中的文件夹名自动转化成序列,那么DataLoader载入时,标签自动就是整数序列了。
下面我们利用ImageFolder读取不同目录下图像数据,然后使用transorms进行图像预处理,预处理有多个,我们用compose把这些操作拼接在一起。然后使用DataLoader加载。
将处理后的数据用torchvision.utils中的save_image保存为一个png格式文件,然后用Image.open打开该png文件,详细代码如下:

运行结果如下,结果如图4-2所示。
tensor([2, 2, 0, 0, 0, 1, 2, 2])

图4-2 make_grid拼接在一起的图形
打开test01.png文件:

运行结果如图4-3所示。

图4-3 用Image查看png文件

4.4 可视化工具

TensorBoard是Google TensorFlow 的可视化工具,可以记录训练数据、评估数据、网络结构、图像等,并且可以在Web上展示,对于观察神经网路训练的过程非常有帮助。PyTorch支持tensorboard_logger、 visdom等可视化工具。

4.4.1 TensorBoard简介

TensorBoard功能很强大,支持scalar、image、figure、histogram、audio、text、graph、onnx_graph、embedding、pr_curve、videosummaries等可视化方式。
使用TensorBoard的一般步骤如下。
1)导入tensorboard,实例化SummaryWriter类,指明记录日志路径等信息。

【说明】
(1)其中logs指生成日志文件路径,如果是在Windows环境下,需要注意其logs路径格式与Linux环境不同,需要使用转义字符或在字符串前加r,如
writer = SummaryWriter(log_dir=r'D:\myboard\test\logs')
(2)SummaryWriter的格式为:

(3)如果不写log_dir,系统将在当前目录创建一个runs的目录。

2)调用相应的API接口,接口一般格式为:

3)启动tensorboard服务。cd到logs目录所在的同级目录,在命令行输入如下命令,logdir等式右边可以是相对路径或绝对路径。

4)Web展示。在浏览器输入:

便可看到logs目录保存的各种图形,如图4-4所示。

图4-4 TensorBoard示例图形
鼠标在图形上移动,还可以看到对应位置的具体数据。

4.4.2用TensorBoard可视化神经网络

4.4.1节介绍了TensorBoard的主要内容,为帮助大家更好地理解,下面我们将介绍几个实例。实例内容涉及如何使用TensorBoard可视化神经网络模型、可视化损失值、图像等。
1)导入需要的模块。

2)构建神经网络。

3)把模型保存为graph。

打开浏览器,便可看到图4-5所示的可视化计算图。
图4-5 TensorBoard可视化计算图

4.4.3用TensorBoard可视化损失值

可视化损失值,使用add_scalar函数,这里利用一层全连接神经网络,训练一元二次函数的参数。

运行结果如图4-6所示。

图4-6 可视化损失值与迭代步的关系

4.4.4用TensorBoard可视化特征图

利用TensorBoard对特征图进行可视化,不同卷积层的特征图的抽取程度是不一样的。
x从cifair10数据集获取。注意:因PyTorch1.7 utils有一个bug,这里使用了PyTorch1.10版的utils。

运行结果如图4-7、图4-8所示。

图4-7 conv1的特征图

图4-8 conv2的特征图

4.5 小结

本章详细介绍了PyTorch有关数据下载、预处理方面的一些常用包,以及可视化计算结果的TensorBoard工具,并通过一些实例详细说明如何使用这些包或工具。第1-4章介绍了有关NumPy及PyTorch的基础知识,这有助于读者更好理解和使用接下来的深度学习方面的基本概念、原理和算法等内容。

第3章 PyTorch神经网络工具箱

前面已经介绍了PyTorch的数据结构及自动求导机制,充分运行这些技术可以大大提高我们的开发效率。这章将介绍PyTorch的另一利器:神经网络工具箱。利用这个工具箱,设计一个神经网络就像搭积木一样,可以极大简化我们构建模型的任务。
本章主要讨论如何使用PyTorch神经网络工具箱来构建网络,主要内容如下:
 介绍神经网络核心组件
 如何构建一个神经网络
 构建神经网络的主要工具
 如何训练模型
 实现神经网络实例

3.1 神经网络核心组件

神经网络看起来很复杂,节点很多,层数多,参数更多。但核心部分或组件不多,把这些组件确定后,这个神经网络基本就确定了。这些核心组件包括:
 层:神经网络的基本结构,将输入张量转换为输出张量。
 模型:层构成的网络。
 损失函数:参数学习的目标函数,通过最小化损失函数来学习各种参数。
 优化器:如何是损失函数最小,这就涉及到优化器。
当然这些核心组件不是独立的,它们之间、它们与神经网络其他组件之间有密切关系。为便于大家理解,我们把这些关键组件及相互关系用图3-1表示。

图3-1 神经网络关键组件及相互关系示意图
多个层链接在一起构成一个模型或网络,输入数据通过这个模型转换为预测值。预测值与真实值共同构成损失函数的输入,损失函数输出损失值(损失值可以是距离、概率值等),该损失值用于衡量预测值与目标结果的匹配或相似程度。优化器利用损失值更新权重参数,目标是使损失值越来越小。这是一个循环过程,当损失值达到一个阀值或循环次数到达指定次数时,循环结束。
接下来利用PyTorch的nn工具箱,构建一个神经网络实例。nn中对这些组件都有现成包或类,可以直接使用,非常方便。

3.2 PyTorch构建神经网络的主要工具

使用PyTorch构建神经网络使用的主要工具(或类)及相互关系,如图3-2所示。

图3-2 PyTorch实现神经网络主要工具及相互关系
从图3-2可知,可以基于Module类或函数(nn.functional)构建网络层。nn中的大多数层(layer)在functional中都有与之对应的函数。nn.functional中的函数与nn.Module中的layer的主要区别是后者继承自Module类,可自动提取可学习的参数,而nn.functional更像是纯函数。两者功能相同,性能也没有很大区别,那么如何选择呢?卷积层、全连接层、dropout层等含有可学习参数,一般使用nn.Module,而激活函数、池化层不含可学习参数,可以使用nn.functional中对应的函数。

3.2.1 nn.Module

前面我们使用autograd及Tensor实现机器学习实例时,需要做不少设置,如对叶子节点的参数requires_grad设置为True,然后调用backward,再从grad属性中提取梯度。对于大规模的网络,autograd太过于底层和烦琐。为了简单、有效解决这个问题,nn是一个有效工具。它是专门为深度学习设计的一个模块,而nn.Module是nn的一个核心数据结构。nn.Module可以是神经网络的某个层,也可以是包含多层的神经网络。在实际使用中,最常见的做法是继承nn.Module,生成自己的网络/层,如3.4节实例中,我们定义的Net类就采用这种方法(class Net(torch.nn.Module))。nn中已实现了绝大多数层,包括全连接层、损失层、激活层、卷积层、循环层等。这些层都是nn.Module的子类,能够自动检测到自己的参数,并将其作为学习参数,且针对GPU运行进行了CuDNN优化。

3.2.2 nn.functional

nn中的层,一类是继承了nn.Module,其命名一般为nn.Xxx(第一个是大写),如nn.Linear、nn.Conv2d、nn.CrossEntropyLoss等。另一类是nn.functional中的函数,其名称一般为nn.funtional.xxx,如nn.funtional.linear、nn.funtional.conv2d、nn.funtional.cross_entropy等。从功能来说两者相当,基于nn.Mudle能实现的层,也可以基于nn.funtional实现,反之亦然,而且性能方面两者也没有太大差异。不过在具体使用时,两者还是有区别的,主要区别如下。
1) nn.Xxx继承于nn.Module,nn.Xxx 需要先实例化并传入参数,然后以函数调用的方式调用实例化的对象并传入输入数据。它能够很好的与nn.Sequential结合使用,而nn.functional.xxx无法与nn.Sequential结合使用。
2) nn.Xxx不需要自己定义和管理weight、bias参数;而nn.functional.xxx需要你自己定义weight、bias,每次调用的时候都需要手动传入weight、bias等参数, 不利于代码复用。
3) dropout操作在训练和测试阶段是有区别的,使用nn.Xxx方式定义dropout,在调用model.eval()之后,自动实现状态的转换,而使用nn.functional.xxx却无此功能。
总的来说,两种功能都是相同的,但PyTorch官方推荐:具有学习参数的(例如,conv2d、 linear、batch_norm、dropout等)情况采用nn.Xxx方式,没有学习参数的(例如,maxpool, loss func, activation func)等情况选择使用nn.functional.xxx或者nn.Xxx方式。3.5节中使用激活层,我们采用无学习参数的F.relu方式来实现,即nn.functional.xxx方式。

3.3 构建模型

第2章介绍使用PyTorch实现机器学习任务的几个实例,具体步骤好像不少,但关键就是选择网络层,构建网络,然后选择损失和优化器。在nn工具箱中,可以直接引用的网络很多,有全连接层、卷积层、循环层、正则化层、激活层等。接下来将介绍PyTorch的主要工具或模块,采用不同方法构建如图3-3所示的神经网络。

图3-3 神经网络结构
如图3-3所示,先把28x28的图像展平为784向量,layer1和layer2分别包括一个全连接层、一个批量归一化层,激活函数都是ReLU,输出层的激活函数为softmax。
PyTorch构建模型大致有以下3种方式。
1) 继承nn.Module基类构建模型。
2) 使用nn.Sequential按层顺序构建模型。
3) 继承nn.Module基类构建模型,又使用相关模型容器(如nn.Sequential,nn.ModuleList,nn.ModuleDict等)进行封装。
在这3种方法中,第1种方式最为常见;第2种方式比较简单,非常适合与初学者;第3种方式较灵活但复杂一些。

3.3.1 继承nn.Module基类构建模型

利用这种方法构建模型,先定义一个类,使之继承nn.Module基类。把模型中需要用到的层放在构造函数__init__()中,在forward方法中实现模型的正向传播。具体代码如下。
1)导入模块。

2) 构建模型。

3)查看模型。

运行结果如下:

3.3.2 使用nn.Sequential按层顺序构建模型

使用nn.Sequential构建模型,因其内部实现了forward函数,因此可以不用写forward函数。nn.Sequential里面的模块按照先后顺序进行排列的,所以必须确保前一个模块的输出大小和下一个模块的输入大小是一致的。使用这种方法一般构建较简单的模型。 以下是使用nn.Sequential搭建模型的几种等价方法。
1.利用可变参数
Python中的函数参数个数是可变(或称为不定长参数),PyTorch中的有些函数也类似,如nn.Sequential(*args)就是一例。
1)导入模块。

2)构建模型。

3)查看模型。

运行结果如下:
Sequential(
(0): Flatten(start_dim=1, end_dim=-1)
(1): Linear(in_features=784, out_features=300, bias=True)
(2): BatchNorm1d(300, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(3): ReLU()
(4): Linear(in_features=300, out_features=100, bias=True)
(5): BatchNorm1d(100, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(6): ReLU()
(7): Linear(in_features=100, out_features=10, bias=True)
(8): Softmax(dim=1)
)
这种方式构建时不能给每个层指定名称,如果需要给每个层指定名称,可使用add_module方法或OrderedDict方法。
2.使用add_module方法
1)构建模型。

2)查看模型。

运行结果如下:
Sequential(
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear1): Linear(in_features=784, out_features=300, bias=True)
(bn1): BatchNorm1d(300, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu1): ReLU()
(linear2): Linear(in_features=300, out_features=100, bias=True)
(bn2): BatchNorm1d(100, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu2): ReLU()
(out): Linear(in_features=100, out_features=10, bias=True)
(softmax): Softmax(dim=1)
)
3.使用OrderedDict
1)导入模块。

2)构建模型。

3)查看模型。

运行结果如下:
Sequential(
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear1): Linear(in_features=784, out_features=300, bias=True)
(bn1): BatchNorm1d(300, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu1): ReLU()
(linear2): Linear(in_features=300, out_features=100, bias=True)
(bn2): BatchNorm1d(100, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu2): ReLU()
(out): Linear(in_features=100, out_features=10, bias=True)
(softmax): Softmax(dim=1)
)

3.3.3 继承nn.Module基类并应用模型容器来构建模型

当模型的结构比较复杂时,可以应用模型容器(如nn.Sequential,nn.ModuleList,
nn.ModuleDict)对模型的部分结构进行封装,以增强模型的可读性,或减少代码量。
1.使用nn.Sequential模型容器
1)导入模块。

2)构建模型。

3)查看模型。

运行结果如下:
Model_lay(
(flatten): Flatten(start_dim=1, end_dim=-1)
(layer1): Sequential(
(0): Linear(in_features=784, out_features=300, bias=True)
(1): BatchNorm1d(300, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(layer2): Sequential(
(0): Linear(in_features=300, out_features=100, bias=True)
(1): BatchNorm1d(100, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(out): Sequential(
(0): Linear(in_features=100, out_features=10, bias=True)
)
)
2.使用nn.ModuleList模型容器
1)导入模块。

2)构建模型。

3)查看模型。

运行结果如下:
Model_lst(
(layers): ModuleList(
(0): Flatten(start_dim=1, end_dim=-1)
(1): Linear(in_features=784, out_features=300, bias=True)
(2): BatchNorm1d(300, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(3): ReLU()
(4): Linear(in_features=300, out_features=100, bias=True)
(5): BatchNorm1d(100, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(6): ReLU()
(7): Linear(in_features=100, out_features=10, bias=True)
(8): Softmax(dim=1)
)
)
3.使用nn.ModuleDict模型容器
1)导入模块。

2)构建模型。

其中激活函数ReLU在模型中应该出现2次,但函数相同,故在定义字典时,只需定义一次,但在定义forward函数的列表中需要出现2次。
3)查看模型。

运行结果如下:
Model_dict(
(layers_dict): ModuleDict(
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear1): Linear(in_features=784, out_features=300, bias=True)
(bn1): BatchNorm1d(300, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU()
(linear2): Linear(in_features=300, out_features=100, bias=True)
(bn2): BatchNorm1d(100, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(out): Linear(in_features=100, out_features=10, bias=True)
(softmax): Softmax(dim=1)
)
)

3.3.4 自定义网络模块

利用以上方法,自定义一些典型的网络模块,如残差网络(ResNet18)中的残差块,如图3-4所示。

图3-4 残差块网络结构
残差块有两种,一种是正常的模块方式,如图3-4左图,将输入与输出相加,然后应用激活函数ReLU。 另一种是为使输入与输出形状一致,需添加通过1×1卷积调整通道和分辨率,如图3-4中的右图所示。这些模块中用到卷积层、批量规范化层,具体将在第6章详细介绍,这里我们只需要了解这些是网络层即可。
1)定义图3-4左图的残差模块。

2)定义图3-4右图的残差模块。

3)组合这两个模块得到现代经典RetNet18网络结构。

3.4 训练模型

构建模型(假设为model)后,接下来就是训练模型。PyTorch训练模型主要包括加载数据集、损失计算、定义优化算法、反向传播、参数更新等主要步骤。
1.加载预处理数据集
加载和预处理数据集,可以使用PyTorch的数据处理工具,如torch.utils和torchvision等,这些工具将在第4章详细介绍。
2.定义损失函数
定义损失函数可以通过自定义方法或使用PyTorch内置的损失函数,如回归使用的losss_fun=nn.MSELoss(),分类使用的nn.BCELoss等损失函数,更多内容可参考本书5.2.4节。
3.定义优化方法
Pytoch常用的优化方法都封装在torch.optim里面,其设计很灵活,可以扩展为自定义的优化方法。所有的优化方法都是继承了基类optim.Optimizer,并实现了自己的优化步骤。
最常用的优化算法就是梯度下降法及其各种变种,具体将在5.4节详细介绍,这些优化算法大多使用梯度更新参数。
如使用SGD优化器时,可设置为optimizer = torch.optim.SGD(params,lr = 0.001)。
4.循环训练模型
1)设置为训练模式:
model.train()
调用model.train()会把所有的module设置为训练模式。
2)梯度清零:
optimizer. zero_grad()
在默认情况下梯度是累加的,需要手工把梯度初始化或清零,调用optimizer.zero_grad() 即可。
3)求损失值:
y_prev=model(x)
loss=loss_fun(y_prev,y_true)
4)自动求导,实现梯度的反向传播:
loss.backward()
5)更新参数:
optimizer.step()
5.循环测试或验证模型
1)设置为测试或验证模式:
model.eval()
调用model.eval()会把所有的training属性设置为False。
2)在不跟踪梯度模式下计算损失值、预测值等:
with.torch.no_grad():
6.可视化结果
下面我们通过实例来说明如何使用nn来构建网络模型、训练模型。
【说明】model.train()与model.eval()的使用
如果模型中有BN (Batch Normalization)层和Dropout,需要在训练时添加model.train(),
在测试时添加model.eval()。其中model.train()是保证BN层用每一批数据的均值和方差,而model.eval()是保证BN用全部训练数据的均值和方差;而对于Dropout,model.train()是随机取一部分网络连接来训练更新参数,而model.eval()是利用到了所有网络连接。

3.5实现神经网络实例

前面我们介绍了使用PyTorch构建神经网络的一些组件、常用方法和主要步骤等,本节通过一个构建神经网络的实例把这些内容有机结合起来。

3.5.1背景说明

本节将利用神经网络完成对手写数字进行识别的实例,来说明如何借助nn工具箱来实现一个神经网络,并对神经网络有个直观了解。在这个基础上,后续我们将对nn的各模块进行详细介绍。实例环境使用PyTorch1.5+,GPU或CPU,源数据集为MNIST。
主要步骤如下。
 利用PyTorch内置函数mnist下载数据。
 利用torchvision对数据进行预处理,调用torch.utils建立一个数据迭代器。
 可视化源数据。
 利用nn工具箱构建神经网络模型。
 实例化模型,并定义损失函数及优化器。
 训练模型。
 可视化结果。
神经网络的结构如图3-5所示。

图3-5 神经网络结构图
使用两个隐含层,每层使用ReLU激活函数,输出层使用softmax激活函数,最后使用torch.max(out,1)找出张量out最大值对应索引作为预测值。

3.5.2准备数据

1)导人必要的模块。

2)定义一些超参数。

3)下载数据并对数据进行预处理。

【说明】
1) transforms.Compose可以把一些转换函数组合在一起。
2) Normalize([0.5], [0.5])对张量进行归一化,这里两个0.5分别表示对张量进行归一化的全局平均值和方差。因图像是灰色的只有一个通道,如果有多个通道,需要有多个数字,如三个通道,应该是Normalize([m1,m2,m3], [n1,n2,n3])。
3) download参数控制是否需要下载,如果./data目录下已有MNIST,可选择False。
4) 用DataLoader得到生成器,这可节省内存。
5) torchvision及data的使用第4章将详细介绍。

3.5.3可视化源数据

对数据集中部分数据进行可视化。

运行结果如图3-6所示。

图3-6 MNIST源数据示例

3.5.4 构建模型

数据预处理之后,我们开始构建网络,创建模型。
1)构建网络。

2)实例化网络。

3.5.5 训练模型

训练模型,这里使用for循环进行迭代。其中包括对训练数据的训练模型,然后用测试数据验证模型。
1)训练模型。

最后5次迭代的结果如下:
学习率:0.006561
epoch: 15, Train Loss: 1.4681, Train Acc: 0.9950, Test Loss: 1.4801, Test Acc: 0.9830
epoch: 16, Train Loss: 1.4681, Train Acc: 0.9950, Test Loss: 1.4801, Test Acc: 0.9833
epoch: 17, Train Loss: 1.4673, Train Acc: 0.9956, Test Loss: 1.4804, Test Acc: 0.9826
epoch: 18, Train Loss: 1.4668, Train Acc: 0.9960, Test Loss: 1.4798, Test Acc: 0.9835
epoch: 19, Train Loss: 1.4666, Train Acc: 0.9962, Test Loss: 1.4795, Test Acc: 0.9835
这个神经网络的结构比较简单,只用了两层,也没有使用dropout层,迭代20次,测试准确率达到98%左右,效果还可以。不过,还是有提升空间,如果采用cnn,dropout等层,应该还可以提升模型性能。
2)可视化训练及测试损失值。

运行结果如图3-7所示。

图3-7 MNIST数据集训练的损失值

3.6 小结

本章我们首先介绍了神经网络的核心组件,即层、模型、损失函数及优化器。然后,从一个完整实例开始,看PyTorch是如何使用其包、模块等来搭建、训练、评估、优化神经网络。最后详细剖析了PyTorch的工具箱nn以及基于nn的一些常用类或模块等,并用相关实例演示这些模块的功能。这章介绍了神经网络工具箱,下一章将介绍PyTorch的另一个强大工具箱,即数据处理工具箱。

第2 章 向量基本运算

2.1转置运算

向量的转置(Transpose)将列向量变成行向量,或将行向量变成列向量。
向量X的转置记为X^T
例1:X=\left[\begin{matrix} 2\cr 0 \cr 3\end{matrix}\right]X^T=[2,0,3]
用Python表示:

2.2 两个向量的点积

两个向量(如X、Y,它们的维数相同)的点积(或称为内积)定义为它们对应元素乘积之和,记为:X^T Y
例2:X=\left[\begin{matrix} 2\cr 0 \cr 3\end{matrix}\right]Y=\left[\begin{matrix} 1\cr 5 \cr 9\end{matrix}\right]
X^T Y=[2,0,3]\cdot \left[\begin{matrix} 1\cr 5 \cr 9\end{matrix}\right]=2\times 1+0\times 5+3\times 9=29
向量与自身的点积为所有元素的平方和:
X^T X=\sum_{i=1}^n x_i^2
如果两个向量的点积为0,则称它们正交。
点积运算满足如下规律:
X^TY=Y^TX
(kX^T)Y=kX^TY
(X+Y)^T)Z=X^TZ+Y^TZ
Z^T(X+Y)=Z^TX+Z^TY
利用点积可以简化线性函数的表述,这种方法在机器学习中经常可以看到。
如表示权重(\omega_i)与输入(x_i)、偏置项(b)的线性模型预测函数:
 \omega_1 x_1+\omega_2 x_2+\dots+\omega_n x_n+b \tag{2.5}
设权重向量w=[\omega_1, \omega_2,\dots, \omega_n] ,输入向量X=[x_1, x_2,\dots, x_n] ,式(2.5)可写成:
WX^T+b
点积运算用于在机器学习的正向传播过程。

2.3 两个向量的阿达马 (Hadamard) 积

两个向量的阿达马积或称为遂元乘积、对应元素的乘积,是它们对应元素相乘。
记为:X*Y X\bigodot Y
例3:X=[1 2 3] Y=[2 2 1]
X*Y=[1×2 2×2 3×1]=[2 4 3]
当向量X、Y的元素个数相同时,还可进行对应元素的加、减、乘、除等算术运算。
X+Y=[3 4 4 ]
X/Y=[0.5 1 3]
用Python代码实现

经过阿达马运算的向量或矩阵维度不变,如:

运行结果:
A向量运行后形状:(3,),B矩阵运行后形状:(2, 3)
阿达马积在机器学习中的正向传播和反向传播、梯度下降法中经常出现。

2.4向量的范数

数有大小,向量也有大小,向量的大小我们通过范数(Norm)来衡量。范数在机器学习、深度学习中运用非常广泛,特别在限制模型复杂度、提升模型的泛化能力方面效果不错。p范数的定义如下:
||x||_p=(\sum_i |x_i| ^p)^{\frac 1p}\tag{2.6}
其中p\in R,P\geq 1
直观上来看,向量x的范数是度量从原点到点x的距离,范数是将向量映射到非负值的函数,如果从广义来说,任意一个满足以下三个条件的函数,都可称为范数:
(1)非负性:f(x)≥0,且当f(x)=0时,必有x=0;
(2)三角不等式性:f(x+y)≤f(x)+f(y);
(3)齐次性:\forall \alpha\in R,\forall x \in R^n f(\alpha x)=| \alpha|f(x)

当p=1时,即L^1范数,也称为绝对值范数,大小等于向量的每个元素绝对值之和,即:
||x||=\sum_i|x_i|
当p=2时,即L^2范数,也称为欧几里得范数,其大小表示从原点到当前点的欧几里得距离,即:
||x||_2=\sqrt{(x_1^2+x_2^2+\cdots+x_n^2)}\tag{2.7}
当p为\infty时,即L^{\infty}范数,也称为最大范数,它的值等于向量中每个元素的绝对值的最大值,即:
||x||_{\infty}=max_i (|x_i|)\tag{2.8}
前面主要介绍了利用范数来度量向量的大小,矩阵的大小如何度量呢?我们可以用类似的方法。在深度学习中,常用Frobenius范数来描述,即:
||A||_F=\sqrt{(\sum_{i,j}A_{i,j}^2 )}
它有点类似向量的L^2范数。
两个向量的点积可以用范数来表示,即:
x^T y=||x||_2 ||y||_2 \cos\theta \tag{2.9}
其中\theta表示x与y之间的夹角。 以上说了向量一种度量方式,即通过范数来度量向量或矩阵的大小,并有具体公式,在实际编程中如何计算向量的范数呢?这里我们还是以Python为例进行说明。

打印结果如下:
[ 0. 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9]
4.5
1.68819430161
0.9
由此看出利用Python求向量的范数还是很方便的。
例3:点积与范数的应用实例:
在平面解析几何中,点(x_0,y_0)到直线ax+by+c=0的距离为:
d=\frac{|ax_0+by_0+c|}{a^2+b^2}
在空间解析几何中,点(x_0,y_0,z_0)到平面ax+by+cz+d=0的距离为:
l=\frac{|ax_0+by_0+cz_0+d|}{a^2+b^2+c^2}
将其推广到n维空间,点(x_1,x_2,\cdots,x_n)到超平面\omega_1 x_1+\omega_2 x_2+\cdots+\omega_n x_n+b=0(或w^T X+b=0)的距离为:
d=\frac{|w^T X+b|}{||w||_2}

2.5线性相关性

前面我们介绍了向量、矩阵等概念,接下来我们将介绍向量组、线性组合、线性相关性、秩等重要概念。
由多个同维度的列向量构成的集合称为向量组,矩阵可以看成是由行向量或列向量构成的向量组。

2.5.1线性组合

给定向量组X:x_1,x_2,\cdots,x_n其中x_i\in R^m,对任何一组实数k_1,k_2,\cdots,k_n,构成的表达式:
k_1 x_1+k_2 x_2+\cdots+k_n x_n\tag{2.10}
称为向量组X的一个线性组合,k_1,k_2,\cdots,k_n称为向量组的系数。
对于任意一个m维向量b,如果存在一组实数k_1,k_2,\cdots,k_n,使得:
k_1 x_1+k_2 x_2+\cdots+k_n x_n=b
成立,则称向量b可以被向量组X:x_1,x_2,\cdots,x_n线性表示。
对于任意实数集\{k_1,k_2,\cdots,k_n\},由(2.10)式构成的所有向量集合,称为向量空间\{k_1 x_1+k_2 x_2+\cdots+k_n x_n\},其中k_i\in R
向量空间的概念有点抽象,我们举一个简单实例来说明这个概念,比如由三个向量构成的向量组\{(1,0,0),(0,1,0),(0,0,1)\} 和任何一组实数\{k_1,k_2,k_3\}就构成了一个三维空间。

2.5.2线性相关

线性相关性主要分析向量之间的关系,一组向量他们之间有哪些关系呢?这些关系有何重要应用?
向量组x_1,x_2,\cdots,x_n线性相关 <=> 存在不全为0的实数k_1,k_2,\cdots,k_n,使得:
k_1 x_1+k_2 x_2+\cdots+k_n x_n=0\tag{2.11}

如果不存在一组不全为0的数使k_1 x_1+k_2 x_2+\cdots+k_n x_n=0<=>向量组x_1,x_2,\cdots,x_n线性无关或线性独立。

例1:x_1=\binom{1}{0}, x_2=\binom{0}{1}线性无关,因为k_1 x_1+k_2 x_2=k_1 \binom{1}{0}+k_2 \binom{0}{1}=\binom{k_1}{k_2}=0,只有k_1=k_2=0,所以x_1,x_2线性无关。
例2:x_1=\binom{1}{0}, x_2=\binom{0}{1},x_3=\binom{1}{1}线性有关,因为x_1+x_2-x_3=0,k_1=1,k_2=1,k_3=-1
都不为0(至少一个不为0),所以x_1,x_2,x_3线性相关。
线性相关和线性无关的等价定义:利用矩阵乘法公式,式(2.11)可表示为:
\left(x_1,\cdots,x_n\right)\left(\begin{matrix}k_1 \cr \vdots \cr k_n\end {matrix}\right)=0,\Rightarrow Ax=0
这里假设:A=(a_1,\cdots,a_n),x=\left(\begin{matrix}x_1 \cr \vdots \cr x_n\end {matrix}\right),由线性方程组的理论,可得:
Ax=0只有零解,\Rightarrow a_1,\cdots,a_n线性无关;
Ax=0有非零解,\Rightarrow a_1,\cdots,a_n线性相关;
由线性方程组解的存在性与矩阵的秩的关系,有如下定理:
R(A)=n,\Rightarrow a_1,\cdots,a_n线性无关;
R(A)<n, \Rightarrow a_1,\cdots,a_n线性相关;

2.5.3向量组的秩

假设在原向量组X:x_1,x_2,\cdots,x_n存在一个子向量组,不妨设为X_0:x_1,x_2,\cdots,x_r,r<n,满足: (1)x_1,x_2,\cdots,x_r线性无关; (2)向量组X的中任意r+1个向量构成的子向量组都是线性相关的。 那么,称向量组X_0:x_1,x_2,\cdots,x_r,r 秩是一个重要概念,运用非常广泛,实际上矩阵我们可以看成是一个向量组。如果把矩阵看成是由所有行向量构成的向量组,这样矩阵的行秩就等于行向量组的秩;如果把矩阵看成是由所有列向量构成的向量组,这样矩阵的列秩就等于列向量组的秩。矩阵的行秩与列秩相等,因此,把矩阵的行秩和列秩统称为矩阵的秩。

2.5.4.向量空间

如果在n维向量集合S上的加法、数乘运算封闭,则称S为向量空间或线性空间。
即,对任意x,y\in S,都有x+y\in S,kx\in S,其中k是任意实数.
如通常看到的2维向量构建的集合R^2,3维向量构成的集合R^3,就是向量空间或线性空间。