深度学习DeepLearning-基础知识
文章目录
欢迎大家访问我的GitHub博客
https://lunan0320.cn
求单个样本的loss function是通过forward propagation
求梯度,即loss值关于参数w、b的偏导数,需要backward propagation
1.前言
与传统机器学习方法相比,深度学习的一个主要优势是可以处理不同长度的数据。
深度学习模型由神经网络错综复杂的交织在一起,包含层层数据转换
我们需要定义模型的优劣程度的度量,这个度量在大多数情况是**“可优化”的,我们称之为目标函数*(objective function)**
定义一个目标函数,并希望优化它到最低点。 因为越低越好,所以这些函数有时被称为损失函数(loss function,或cost function)
试图预测数值时,最常见的损失函数是平方误差(squared error)
通常将可用数据集分成两部分:训练数据集用于拟合模型参数,测试数据集用于评估拟合的模型。
优化算法:梯度下降(gradient descent)
在回归中,我们训练一个回归函数来输出一个数值; 而在分类中,我们训练一个分类器,它的输出即为预测的类别。
回归问题的常见的随时函数是平方差,分类问题的常见损失函数被称为交叉熵(cross-entropy)
学习预测不相互排斥的类别的问题称为多标签分类(multi-label classification)
搜索结果的排序也十分重要,我们的学习算法需要输出有序的元素子集
我们称这类数据中不含有“目标”的机器学习问题为无监督学习(unsupervised learning)
聚类(clustering)问题、主成分分析(principal component analysis)问题、因果关系(causality)和概率图模型(probabilistic graphical models)问题、生成对抗性网络(generative adversarial networks)
机器学习开发与环境交互并采取行动专注于*强化学习(reinforcement learning)
每个分类对应一个“动作”。 然后,我们可以创建一个环境,该环境给予agent的奖励。 这个奖励与原始监督学习问题的损失函数是一致的。
并不假设环境告诉agent每个观测的最优动作。 一般来说,agent只是得到一些奖励
当环境可被完全观察到时,我们将强化学习问题称为马尔可夫决策过程(markov decision process)
2.预备知识
2.1数据操作
张量类(在MXNet中为ndarray
, 在PyTorch和TensorFlow中为Tensor
)都与Numpy的ndarray
类似
首先,GPU很好地支持加速计算,而NumPy仅支持CPU计算
其次,张量类支持自动微分
具有一个轴的张量对应数学上的向量(vector); 具有两个轴的张量对应数学上的矩阵(matrix);
import torch
x = torch.arange(12)#arange按序产生12个数字
x.shape #访问张量形状
x.numel()#知道张量元素总数
X = x.reshape(3, 4) #改变张量形状
torch.ones((2,3,4)) #初始化全为0或者全为1的张量
torch.zeros((1,2,3))
torch.randn(3,4)#每个元素都从均值为0、标准差为1的标准高斯分布(正态分布)中随机采样
x = torch.tensor([1.0,2,4,8]) #初始化数值
y = torch.tensor([2,2,2,2])
x+y, x*y, x/y,x**y
torch.exp(x) #对x这个tensor,求e的多少次
x = torch.arange(12, dtype = torch.float32).reshape(3,4)
y = torch.tensor([[2.0,3,4,5],[2,1,4,3],[6,1,3,4]])
#dim = 0表示按行拼接,dim = 1表示按列拼接
torch.cat((x,y),dim = 0), torch.cat((x,y),dim = 1)
x.sum()#对张量中的所有元素求和
#广播机制
a = torch.arange(3).reshape(3,1) #a是3*1的矩阵
b = torch.arange(2).reshape(1,2) #b是1*2的矩阵
a+b #a+b其实是无效的,但是a和b分别按列、行扩展为3*2的矩阵,然后相加是可以的
#索引和切片
x = torch.arange(12).reshape(3,4)
x[-1],x[1:3]
x[0:2,:] = 12 #修改第1、2行的所有数据为12,:表示所有的列元素
内存问题
通常情况下,我们希望原地执行这些更新。 其次,如果我们不原地更新,其他引用仍然会指向旧的内存位置, 这样我们的某些代码可能会无意中引用旧的参数。
可以使用X[:] = X + Y
或X += Y
来减少操作的内存开销。
转换为python对象
A = X.numpy() #tensor可以直接转numpy
B = torch.tensor(A)#numpy也可以直接转tensor
type(A), type(B)
a = torch.tensor([3.5])
a.item() #将张量转换为python标量
2.2数据预处理
创建数据集
在Python中常用的数据分析工具中,我们通常使用pandas
软件包。
#创建人工数据集
import os
os.makedirs(os.path.join('..','data'),exist_ok = True)
data_file = os.path.join('..','data','house_tiny.csv')
with open(data_file,'w') as f:
f.write('NumRooms,Alley,Price\n')
f.write('NA,Pave,106000\n')
f.write('4,NA,178100\n')
f.write('NA,NA,140000\n')
#pandas读取数据
import os
import pandas as pd
my_file = os.path.join('..','data','house_tiny.csv')
data = pd.read_csv(my_file)
print(data)
处理缺失值
插值法或者删除法,此处展示插值法
inputs, output = data.iloc[:,0:2],data.iloc[:,2]#此处iloc不需要()
print(inputs)
inputs = inputs.fillna(inputs.mean()) #用inputs中每一列的均值来替换NA
print(inputs)
inputs = pd.get_dummies(inputs,dummy_na = True) #对于离散型,或者类别值,存在则设置1,否则设置nan为0,类似ont-hot编吗
print(inputs)
##如下:
'''
NumRooms Alley_Pave Alley_nan
0 3.0 1 0
1 2.0 0 1
2 4.0 0 1
3 3.0 0 1
'''
转换为张量类型
import torch
#直接对pandas数据用values属性取值
x,y = torch.tensor(inputs.values),torch.tensor(outputs.values)
x,y
2.3线性代数
标量
实例化两个标量
import torch
x = torch.tensor(3.0)
y = torch.tensor(2.0)
x+y,x*y
向量
标量是向量的元素或者分量
列向量是向量的默认方向
通过张量的索引来访问向量的任意元素
x = torch.arange(4)
x[3]
直接可以用python 的内置len()函数来访问张量的长度
len(x)
或者当向量是由一维张量组成时,可以用shape来访问向量的长度
x.shape
矩阵
import torch
a = torch.arange(20).reshape(4,5)
a,a.T
张量属性
将张量乘以或加上一个标量不会改变张量的形状,其中张量的每个元素都将与标量相加或相乘。
a = 2
x = torch.arange(24).reshape(2,3,4)
x, a+x
降维
axis = 0表示在y轴上求和,降维到轴0
x = torch.arange(4,dtype = torch.float32)
x.shape,x.sum()
x = torch.arange(100,dtype = torch.float32).reshape(5,20)
x_axis0 = x.sum(axis = 0)
x_axis0, x_axis0.shape
//取平均
A.mean(), A.sum() / A.numel()
//按轴取平均
A.mean(axis=0), A.sum(axis=0) / A.shape[0]
点积
torch.dot(x,y)
x = torch.arange(4, dtype = torch.float32)
y = torch.ones(4, dtype = torch.float32)
x, y , torch.dot(x,y)
矩阵向量积
torch.mv(A,x)
矩阵矩阵乘法
torch.mm(x,y)
a = torch.arange(20).reshape(4,5)
b = torch.ones(5,4,dtype = torch.long)
torch.mm(a,b)
范数
L1和L2范数
L1 直接求和
L2平方求和再开方
torch.norm(x)
2.4微积分
2.5自动微分
import torch
#此处必须是float型,否则不能用梯度
#计算梯度,先要对其设置梯度属性为True
x = torch.arange(4.0,requires_grad = True)
#默认梯度是None,输出没有结果
x.grad
分离计算
y是关于x 的函数,z是关于x和y的函数,想对z求关于x的偏导,此时需要将y视为一个常数,不希望反向传播的时候从y流向x
此时可以使用detach()方法构建新的变量,从y中分离,这样求偏导的时候,就不会从y流向x
x.grad.zero_()
y = x * x
u = y.detach()
z = u * x
z.sum().backward()
x.grad
但是这个过程会记录y的计算结果,因此直接可以在y上调用反向传播,得到y关于x的导数
x.grad.zero_()
y.sum().backward()
x.grad
3.线性神经网络
3.1线性回归
常见回归问题:预测价格(房屋、股票等)、预测住院时间(针对住院病人等)、 预测需求(零售销量等)
给定一个数据集,我们的目标是寻找模型的权重w和偏置b, 使得根据模型做出的预测大体符合数据里的真实价格
损失函数(loss function)能够量化目标的实际值与预测值之间的差距。
l
(
i
)
(
w
,
b
)
=
1
2
(
y
^
(
i
)
−
y
(
i
)
)
2
.
l^{(i)}(\mathbf{w}, b) = \frac{1}{2} \left(\hat{y}^{(i)} - y^{(i)}\right)^2.
l(i)(w,b)=21(y^(i)−y(i))2.
目标是找到一组参数,使得总的损失值最小:
w
∗
,
b
∗
=
argmin
w
,
b
L
(
w
,
b
)
.
\mathbf{w}^*, b^* = \operatorname*{argmin}_{\mathbf{w}, b}\ L(\mathbf{w}, b).
w∗,b∗=w,bargmin L(w,b).
随机梯度下降
通常会在每次需要计算更新的时候随机抽取一小批样本, 这种变体叫做小批量随机梯度下降
更难做到的是找到一组参数,这组参数能够在我们从未见过的数据上实现较低的损失, 这一挑战被称为泛化(generalization)
可以调整但不在训练过程中更新的参数称为超参数(hyperparameter)
调参(hyperparameter tuning)是选择超参数的过程
正态分布与平方损失
损失函数是平方误差的原因是:根据似然函数求出
对于给定的x观测特定的y的似然likehood:
P
(
y
∣
x
)
=
1
2
π
σ
2
exp
(
−
1
2
σ
2
(
y
−
w
⊤
x
−
b
)
2
)
.
P(y \mid \mathbf{x}) = \frac{1}{\sqrt{2 \pi \sigma^2}} \exp\left(-\frac{1}{2 \sigma^2} (y - \mathbf{w}^\top \mathbf{x} - b)^2\right).
P(y∣x)=2πσ21exp(−2σ21(y−w⊤x−b)2).
根据极大似然估计法,参数w和b的最优值就是使得似然值最大的值:
P
(
y
∣
X
)
=
∏
i
=
1
n
p
(
y
(
i
)
∣
x
(
i
)
)
.
P(\mathbf y \mid \mathbf X) = \prod_{i=1}^{n} p(y^{(i)}|\mathbf{x}^{(i)}).
P(y∣X)=i=1∏np(y(i)∣x(i)).
因为优化一般都是找到一个最小值,因此,这里求最小化的负对数似然
−
log
P
(
y
∣
X
)
-\log P(\mathbf y \mid \mathbf X)
−logP(y∣X)
即求:
−
log
P
(
y
∣
X
)
=
∑
i
=
1
n
1
2
log
(
2
π
σ
2
)
+
1
2
σ
2
(
y
(
i
)
−
w
⊤
x
(
i
)
−
b
)
2
.
-\log P(\mathbf y \mid \mathbf X) = \sum_{i=1}^n \frac{1}{2} \log(2 \pi \sigma^2) + \frac{1}{2 \sigma^2} \left(y^{(i)} - \mathbf{w}^\top \mathbf{x}^{(i)} - b\right)^2.
−logP(y∣X)=i=1∑n21log(2πσ2)+2σ21(y(i)−w⊤x(i)−b)2.
然而,前面的是与超参数 σ 有关的常数项,其余部分和前面介绍的均方误差是一样的。 然而,前面的是与超参数\sigma有关的常数项,其余部分和前面介绍的均方误差是一样的。 然而,前面的是与超参数σ有关的常数项,其余部分和前面介绍的均方误差是一样的。
最小化均方误差等价于对线性模型的极大似然估计
全连接层(fully-connected layer)或称为稠密层(dense layer):每个输入与每个输出都相连
indices = list(range(num_examples)) #生成num_examples个自然数的列表
random.shuffle(indices) #对生成的顺序的数字,打乱,即把indices列表中的数字打乱
yield features[batch_indices], labels[batch_indices] #返回一个迭代器,一个for循环不断返回generator
初始化模型参数
在w和b中引入梯度属性
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
定义模型
此处是定义的经典的线性回归model,其中X是 n * 2的矩阵,w是2 * 1的向量,Xw是一个向量,b是一个标量,但是由于广播机制,b会分别加到向量的每个分量上
def linreg(X, w, b):
"""线性回归模型"""
return torch.matmul(X, w) + b
定义损失函数
这里要将真实值y的形状转换为预测值y_hat的形状
def squared_loss(y_hat,y):
return (y_hat - y.reshape(y_hat.shape))**2 / 2
定义优化算法
抽取一个小批量batch_size,根据参数计算损失的梯度,不断更新参数
def sgd(params,lr, batch_size):
with torch.no_grad():
for param in params:
#此处要除以batch_size,这是对loss求导时可以推得的
param -= lr * param.grad / batch_size
#梯度用完后要清零
param.grad.zero_()
训练
重复训练:
g
←
∂
(
w
,
b
)
1
∣
B
∣
∑
i
∈
B
l
(
x
(
i
)
,
y
(
i
)
,
w
,
b
)
(
w
,
b
)
←
(
w
,
b
)
−
η
g
\mathbf{g} \leftarrow \partial_{(\mathbf{w},b)} \frac{1}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} l(\mathbf{x}^{(i)}, y^{(i)}, \mathbf{w}, b) \\ (\mathbf{w}, b) \leftarrow (\mathbf{w}, b) - \eta \mathbf{g}
g←∂(w,b)∣B∣1i∈B∑l(x(i),y(i),w,b)(w,b)←(w,b)−ηg
每个epoch,都用data_iter遍历数据集
num_epochs和学习率lr都是超参数
(超参数需要反复实验调整)
一个向量是不进行backward操作的,而sum()后,由于梯度为1,所以对结果不产生影响。反向传播算法一定要是一个标量才能进行计算。
3.3 线性回归的简洁实现
import numpy as np
import torch
from torch.utils import data
#生成数据集
true_w = torch.tensor([2,-3.4])
true_b = 4.2
def synthetic_data(w,b ,num_examples):
X = torch.normal(0, 1, size=(num_examples,len(w)))
y = torch.matmul(X, w) + b
y += torch.normal(0,0.01,y.shape)
return X, y.reshape(-1,1)
features,labels = synthetic_data(true_w,true_b,1000)
features.shape,labels.shape
#读取数据集
def load_array(datas,batch_size, is_train = True):
dataset = data.TensorDataset(*datas)
#此处返回的是一个data_loader对象
return data.DataLoader(dataset,batch_size,shuffle = is_train)
batch_size = 10
data_iter = load_array((features,labels),batch_size)
#查看data_iter的方法:先转换位迭代器的格式,然后next()打印出第一项
next(iter(data_iter))
#这里打印的是两个tensor,一个是X,一个是y
'''
[tensor([[ 0.7882, -0.7068],
[ 0.5081, 0.2577],
[-0.5769, 0.1545],
[-0.3271, -0.6080],
[-0.2716, -1.4628],
[-1.1530, -1.4643],
[ 0.1635, -0.2018],
[-0.0753, -1.1161],
[ 3.4251, 0.1953],
[ 0.3589, -0.9478]]),
tensor([[ 8.1742],
[ 4.3357],
[ 2.5157],
[ 5.6106],
[ 8.6395],
[ 6.8726],
[ 5.2155],
[ 7.8377],
[10.3918],
[ 8.1590]])]
'''