最近开始了机器学习与深度学习的学习与编程,大概也还是磕磕绊绊的走过来了,便记录一下历程与心得
线性回归理论
线性回归模型概述
线性回归的内容就是我们假设一些数据,输出(标签)与输入之间存在线性关系,也就是可以用 y = w T x + b y=w^Tx+b y=wTx+b来表示,然后我们需要找到一组最合适的的 w 与 b w与b w与b,来使得模型最符合真实情况,在这里我们将我们的模型叫做H模型,因为吴恩达课程中说的就是一个hypothesis(也就是假设),我们想让我们的假设最接近真实情况
数据集
在这里,我们将数据集这样定义
{
(
x
(
1
)
,
y
(
1
)
)
,
(
x
(
2
)
,
y
(
2
)
)
,
⋯
,
(
x
(
N
)
,
y
(
N
)
)
}
\{(x^{(1)},y^{(1)}),(x^{(2)},y^{(2)}),\cdots,(x^{(N)},y^{(N)})\}
{(x(1),y(1)),(x(2),y(2)),⋯,(x(N),y(N))}里面的每一个样本的数学形式是这样的
(
x
(
i
)
,
y
(
i
)
)
,
i
=
1
,
2
,
⋯
,
N
(x^{(i)},y^{(i)}),i=1,2,\cdots,N
(x(i),y(i)),i=1,2,⋯,N,其中
x
x
x被称为输入,
y
y
y被称为标签或者输出,
i
i
i表示第几个样本
当然也有的教程或者书里面是采用下标定义的
{
(
x
1
,
y
1
)
,
(
x
2
,
y
2
)
,
⋯
,
(
x
N
,
y
N
)
}
\{(x_1,y_1),(x_2,y_2),\cdots,(x_N,y_N)\}
{(x1,y1),(x2,y2),⋯,(xN,yN)}这里的
x
x
x和
y
y
y一般是不同形状的,比如说在目标检测或者图像分类里面,
x
x
x是一个图像数据(大概理解为就是一个矩阵或者三维数组),
y
y
y代表这个图像的标签,比如说有五类图像,这个
y
y
y属于第几类,或者在房价预测里面,
x
x
x就是一个向量,包括了某一个房子可以量化的各种特征(比如说面积,地理位置等等),
y
y
y表示这个房子的售价,可能不太符合大家对向量的认知,但是这里看成一个数据对就可以,也就是相互关联的一对数据,这个也是后面程序中数据集的数学定义
损失函数
我们有了假设模型,那么我们怎么去判断这个模型是不是接近甚至等于真实模型呢?比如说我们想预测房价,那么我们根据一套房子的各种特征去进行预测,那么预测值是不是就接近或者等于它实际的售价呢?如果H模型接近甚至等于真实模型,那么我们就认为它是一个好模型,如果相差甚远,那么这个模型就不好,所以我们需要判断这个模型的好坏。
因为输入输出都是连续变量,所以我们可以想到距离这个概念,我们是不是可以把预测值和真实值之间的距离当做判断指标?
我们设这个判断模型好坏的函数叫做
L
L
L,也就是损失函数,理解为真实模型与H模型之间的差距,也就是不同模型之间的损失
所以我们第一眼想这么定义损失函数:直接作差
L
=
y
^
−
y
=
w
T
x
+
b
−
y
L=\hat{y}-y=w^Tx+b-y
L=y^−y=wTx+b−y也就是使用
H
H
H模型的预测值
y
^
\hat{y}
y^和真实值
y
y
y之间的距离作为损失,但是只在一个样本上衡量损失没有意义,我们需要在数据集上做衡量
L
=
∑
i
=
1
N
(
y
^
(
i
)
−
y
(
i
)
)
=
∑
i
=
1
N
(
w
T
x
(
i
)
+
b
−
y
(
i
)
)
L=\sum^N_{i=1}(\hat{y}^{(i)}-y^{(i)})=\sum^N_{i=1}(w^Tx^{(i)}+b-y^{(i)})
L=i=1∑N(y^(i)−y(i))=i=1∑N(wTx(i)+b−y(i))
但是我们发现了一个弊端,就是这个距离是带有正负的,不同样本之间会出现相互抵消距离的情况,假如说有两个样本
(
0
,
1
)
,
(
1
,
0
)
(0,1),(1,0)
(0,1),(1,0),那么明显
y
=
−
x
+
1
y=-x+1
y=−x+1才是一个最合适的
H
H
H模型,但是我们发现
y
=
x
y=x
y=x也可以实现损失为0的情况,甚至还会出现损失为负的情况,所以我们需要改进模型。然后我们想到的是绝对值函数,可以让距离变为非负,或者平方函数,也可以,所以我们就使用新的损失函数
然后我们想让损失函数最小,这样就可以得到最接近真实模型的
H
H
H模型,那么我们怎么让模型损失函数最小呢?
我们发现,在这个线性模型里面,损失函数的取值完全取决于
w
w
w和
b
b
b两个参数,所以损失函数也可以记为
L
=
L
(
w
,
b
)
L=L(w,b)
L=L(w,b)然后我们发现,这就是一个多元函数,然后我们联想到导数——函数对自变量的的变化率,那么我们就可以求偏导
在这里我们发现,使用绝对值的损失函数难以求导,所以我们使用平方差的损失函数
L
(
w
,
b
)
=
∑
i
=
1
N
∥
(
w
T
x
i
+
b
)
−
y
i
∥
2
L(w,b)=\sum\limits^N_{i=1}\Vert(w^Tx_i+b)-y_i\Vert^2
L(w,b)=i=1∑N∥(wTxi+b)−yi∥2分别对
w
w
w和
b
b
b求偏导
L
(
w
,
b
)
=
∑
i
=
1
N
∥
(
w
T
x
i
+
b
)
−
y
i
∥
2
=
∑
i
=
1
N
[
(
w
T
x
i
+
b
)
2
−
2
y
i
(
w
T
x
i
+
b
)
+
y
i
2
]
∂
L
(
w
,
b
)
∂
w
=
∑
i
=
1
N
x
i
(
w
T
x
i
+
b
−
y
i
)
可以看到,对
w
的
偏导数也是一个与
x
同型的向量
∂
L
(
w
,
b
)
∂
b
=
2
∑
i
=
1
N
(
b
+
w
T
x
i
−
y
i
)
\begin{aligned} L(w,b)&=\sum\limits^N_{i=1}\Vert(w^Tx_i+b)-y_i\Vert^2 \\ &=\sum\limits^N_{i=1}\left[ (w^Tx_i+b)^2-2y_i(w^Tx_i+b)+y_i^2 \right]\\ \frac{ \partial L(w,b) }{ \partial w }&=\sum\limits^N_{i=1}x_i\left( w^Tx_i+b-y_i \right)\\ 可以看到,对w的&偏导数也是一个与x同型的向量\\ \frac{ \partial L(w,b) }{ \partial b }&=2\sum\limits^N_{i=1}\left(b+w^Tx_i-y_i\right)\\ \end{aligned}\\
L(w,b)∂w∂L(w,b)可以看到,对w的∂b∂L(w,b)=i=1∑N∥(wTxi+b)−yi∥2=i=1∑N[(wTxi+b)2−2yi(wTxi+b)+yi2]=i=1∑Nxi(wTxi+b−yi)偏导数也是一个与x同型的向量=2i=1∑N(b+wTxi−yi)然后我们联想一下,在
x
=
x
0
x=x_0
x=x0处的函数导数为正的话,当
x
x
x增大,函数值变大,当
x
x
x减小,函数值减小,当导数为负的时候,
x
x
x增大函数值减小,
x
x
x减小函数值增大,那么我们是不是可以通过不断计算导数来说实现损失函数的最小呢?答案是肯定的
w
=
w
−
∂
L
(
w
,
b
)
∂
w
w=w-\frac{ \partial L(w,b) }{ \partial w}
w=w−∂w∂L(w,b)
b
=
b
−
∂
L
(
w
,
b
)
∂
b
b=b-\frac{ \partial L(w,b) }{ \partial b}
b=b−∂b∂L(w,b)
这样,我们就可以实现让模型朝着损失函数最小化的方向移动
PyTorch基础
我们想让电脑进行计算,而不是人工去计算,这样才可以实现人工智能,所以我们就需要进行编程,幸运的是,随着人工智能的发展,已经有了非常多的人工智能编程框架出现,里面包括了多种非常强大的框架,这样我们无需纠结于程序的细枝末节,而是专注于整体框架和理论。
我们选用的是PyTorch框架,这个框架对于初学者友好,而且部署方便,目前深度学习科研领域的主流框架也是PyTorch,至于为什么不选择TensorFlow,是因为目前TensorFlow的API函数各种混乱,会对初学者造成巨大阻碍,相比之下PyTorch的函数就兼容性很好。
我们创建一个文件,在开头进行导包
import torch
首先,PyTorch的基本数据类型是张量,PyTorch的各种运算也是基于张量进行的,所以理解张量,是进行深度学习编程最重要的一步。
什么是张量呢?张量就是可以进行数学运算的多维数组,如果直接这样说的话,大家可能也无法理解,那我们就从最简单的概念说起。
数列大家是否还记得?一个下标为整数的序列,大家在各种编程语言里面也可能接触过各种程序里面的数列,比如说C++中的一维数组,Python中的列表等等,这些数据结构的特征很明显,有一个下标或者索引,然后是由相同或者不同数据元素构成的序列,为了不出现歧义,我们只考虑元素类型相同的情况。
Python里面列表是这样
list=[10,20,30,40,50]
print(list[0])
这个其实就可以看成一个数列,只有一个维度,维度所代表的属性不同,就可以表示不同的信息
比如说我们可以让让这个维度表示时间(等间隔的情况),然后我们可以用这个数列表示气温变化情况,房价变化情况等等
比如说我们设下标
i
i
i代表第几个小时,那么一天内的气温就可以这样表示
q
0
,
q
1
,
⋯
,
q
i
,
⋯
,
q
23
,
i
=
0
,
1
,
⋯
,
23
q_0,q_1,\cdots,q_i,\cdots,q_{23},i=0,1,\cdots,23
q0,q1,⋯,qi,⋯,q23,i=0,1,⋯,23这样可以表示0点到23点间每个小时所测得的气温数据
这个维度也可以表示其他属性,比如我们有100个要卖的房子,那么这个维度表示的就是样本序号,比如说我们可以用一个长度为100的数列表示这一百个房子的售价,下标代表是哪一个样本
a
0
,
a
1
,
⋯
.
a
99
a_0,a_1,\cdots.a_{99}
a0,a1,⋯.a99
到这里,大家可能有所明悟,一个数列,就是一个一维张量,有一个维度,包含了有相同属性的不同数据,这个数据可以来自于一个对象的某种会变化的属性(如某地一天的气温),也可以来自不同对象的同一特征(如一百个房子就有一百个售价),但是都是一种信息,比如说一天的气温都是温度,而不是湿度,一百个房子的售价也都是价格信息,而不是其他信息。
然后我们动手创建一个一维张量
t1=torch.tensor([1,2,3,4])
这里,我们是使用torch库中的tensor方法创建张量,我们可以传入数列来初始化,然后就可以返回一个一维张量,长度为4,可以使用下标索引的方法来调用不同位置的元素
那么,二维张量呢?其实就是两个维度,每个维度代表一种信息,比如说我们想表示十个地区一天内的气温信息,可不可以呢?那么我们就可以使用一个二维张量来表示,这个张量有两个维度,第一个维度表示不同的地区,第二个维度表示气温,类似于矩阵