Lesson 1. 张量的创建与常用方法
Lesson 2. 张量的索引、分片、合并及维度调整
Lesson 3. 张量的广播和科学运算
Lesson 4. 张量的线性代数运算
Lesson 5. 基本优化方法与最小二乘法
Lesson 6. 动态计算图与梯度下降入门
Lesson 7. 认识深度学习,认识PyTorch
1. 认识深度学习
2. 深度学习前言研究成果及酷炫应用
3. 机器学习中的基本概念
3.1 样本、特征、标签
样本:输入
特征:属性
标签:输出
3.2 分类与回归
分类:离散型变量
回归:连续型变量
3.3 有监督算法与无监督算法
有监督学习:有标签的任务,例如:线性回归、逻辑回归
无监督学习:无标签的任务,例如:聚类分析、协同过滤
3.4 如何判断我的模型是一个好模型?
- 模型预测效果
- 运算速度
- 可解释性
- 服务于业务
4. 深度认识PyTorch框架
Lesson 8. 单层神经网络
1. 单层神经网络:线性回归
1.1 单层回归网络的理论基础
1.2 tensor实现单层神经网络的正向传播
import torch
X = torch.tensor([[1, 0, 0], [1, 1, 0], [1, 0, 1], [1, 1, 1]], dtype = torch.float32)
w = torch.tensor([-0.2, 0.15, 0.15])
def LinearR(X, w):
zhat = torch.mv(X, w)
return zhat
zhat = LinearR(X, w)
z = torch.tensor([[-0.2], [-0.05], [-0.05], [0.1]], dtype = torch.float32)
z = z.view(4)
SSE = sum((zhat - z) ** 2)
torch.allclose(zhat, z)
1.3 tensor计算中的新手陷阱
- 创建tenosr时指定类型,dtpye = float32
- 当精度不够时,使用torch.float63
- 矩阵和向量乘积使用 torch.mv()
1.4 torch.nn.Linear实现单层回归神经网络的正向传播
import torch
X = torch.tensor([[0, 0], [1, 0], [0, 1], [1, 1]], dtype = torch.float32)
# 随机数种子
torch.random.manual_seed(420)
# 实例化 torch.nn.Linear
# 上一层给本层传输数据的神经元个数(不需要自己设置b),本层接收数据神经元个数
output = torch.nn.Linear(2, 1)
zhat = output(X)
zhat
2. 二分类神经网络:逻辑回归
2.1 二分类神经网络的理论基础
sigmoid函数:
![]()
![]()
将连续型变量z转化为离散型变量σ,将回归问题转化为分类问题
2.2 tensor实现二分类神经网络的正向传播
import torch
X = torch.tensor([[1, 0, 0], [1, 1, 0], [1, 0, 1], [1, 1, 1]], dtype = torch.float32)
andgate = torch.tensor([[0], [0], [0], [1]], dtype = torch.float32)
w = torch.tensor([-0.2, 0.15, 0.15], dtype = torch.float32)
def SigmoidR(X, w):
zhat = torch.mv(X, w)
sigma = 1 / (1 + torch.exp(-zhat))
# sigma = torch.sigmoid(zhat)
andhat = torch.tensor([int(x) for x in sigma >= 0.5], dtype = torch.float32)
return sigma, andhat
sigma, andhat = SigmoidR(X, w)
2.3 符号函数sign,Relu,Tanh
- 符号函数sign
![]()
- Relu
![]()
- 双曲正切函数Tanh
![]()
2.4 torch.functional实现单层二分类神经网络的正向传播
import torch
from torch.nn import functional as F
X = torch.tensor([[0, 0], [1, 0], [0, 1], [1, 1]], dtype = torch.float32)
torch.random.manual_seed(420)
dense = torch.nn.Linear(2, 1)
zhat = dense(X)
sigma = F.sigmoid(zhat)
y = torch.tensor([int(x) for x in sigma >= 0.5], dtype = torch.float32)
y
3. 多分类神经网络:Softmax回归
import torch
from torch.nn import functional as F
X = torch.tensor([[0, 0],[1, 0], [0, 1], [1, 1]], dtype = torch.float32)
torch.random.manual_seed(450)
dense = torch.nn.Linear(2, 3)
zhat = dense(X)
soft_max = F.softmax(zhat, 1)
soft_max
4. 回归vs二分类vs多分类
Lesson 9. 深层神经网络
1. 异或门问题
如何把直线的决策边界变成曲线呢?答案是将单层神经网络变成多层
2. 黑箱:深层神经网络的不可解释性
3. 探索多层神经网络:层 vs h(z)
在神经网络的隐藏层中,存在两个关键的元素,一个是加和函数 ,另一个是h(z) 。
当隐藏层的是恒等函数或不存在时,叠加层并不能够解决XOR这样的非线性问题
“层”本身不是神经网络解决非线性问题的关键,层上的h(z)才是。
如果h(z)是线性函数,或不存在,那增加再多的层也没有用。
即便是使用了h(z),也不一定能够解决曲线分类的问题。在不适合的非线性函数加持下,神经网络的层数再多也无法起效。
所以,h(z)是真正能够让神经网络算法“活起来”的关键,没有搭配合适h(z)的神经网络结构是无用的,而h(z)正是神经网络中最关键的概念之一激活函数(activation function)。
4. 激活函数
在人工神经网络的神经元上,根据一组输入定义该神经元的输出结果的函数,就是激活函数。激活函数一般都是非线性函数,它出现在神经网络中除了输入层以外的每层的每个神经元上。
- Softmax与恒等函数几乎不会出现在隐藏层上
- Sign、Tanh几乎不会出现在输出层上
- ReLU与Sigmoid则是两种层都会出现
输出层的g(z)与隐藏层的h(z)之间的区别:
1. 输出层的激活函数是为了让神经网络能够输出不同类型的标签而存在的。其中恒等函数用于回归,sigmoid函数用于二分类,softmax用于多分类。g(z)仅仅与输出结果的表现形式有关,与神经网络的效果无关,也因此它可以使用线性的恒等函数。但隐藏层的激活函数就不同了,如我们之前尝试的XOR,隐藏层上的激活函数的选择会影响神经网络的效果,而线性的是会让神经网络的结构失效的。
2. 在同一个神经网络中,g(z)与h(z) 可以是不同的,并且在大多数运行回归和多分类的神经网络时,他们也的确是不同的。每层上的h(z)可以是不同的,但是同一层上的激活函数必须一致。
5. 从0实现深度神经网络的正向传播
import torch
import torch.nn as nn
from torch.nn import functional as F
# 确定数据
torch.random.manual_seed(420)
X = torch.rand((500, 20), dtype = torch.float32)
y = torch.randint(low = 0, high = 3, size = (500, 1), dtype = torch.float32)
# 定义模型
class Model(nn.Module):
def __init__(self, in_features = 20, out_features = 3):
super(Model, self).__init__()
self.linear1 = nn.Linear(in_features, 13, bias = True)
self.linear2 = nn.Linear(13, 8, bias = True)
self.output = nn.Linear(8, out_features, bias = True)
def forward(self, x):
z1 = self.linear1(x)
sigma1 = F.relu(z1)
z2 = self.linear2(sigma1)
sigma2 = torch.sigmoid(z2)
z3 = self.output(sigma2)
sigma3 = F.softmax(z3, dim = 1)
return sigma3
input_ = X.shape[1] # X.shape = [500, 20]
output_ = len(y.data.unique())
# 实例化神经网络
torch.random.manual_seed(420)
net = Model(in_features = input_, out_features = output_)
net.forward(X)
# net(X)
sigma = net.forward(X)
sigma.max(axis = 1)
net.linear1.weight
net.linear1.bias
Lesson 10. 神经网络的损失函数
10.1 机器学习中的优化思想
- 模型训练的目标:求解一组最适合的权重向量,令神经网络的输出结果与真实值尽量接近。
- 损失函数:衡量真实值与预测结果的差异,评价模型学习过程中产生的损失的函数。在数学上,表示为以需要求解的权重向量w为自变量的函数L(w)。
- 将损失函数L(w)转变成凸函数的数学方法,常见的有拉格朗日变换等
- 在凸函数上求解L(w)的最小值对应的w的方法,也就是以梯度下降为代表的优化算法
- 确定适合的优化算法
- 利用优化算法,最小化损失函数,求解最佳权重 (训练)
10.2 回归:误差平方和SSE
# MSELoss的使用
import torch
import torch.nn as nn
torch.random.manual_seed(420)
yhat = torch.rand(size = (50,), dtype = torch.float32)
y = torch.rand(size = (50,), dtype = torch.float32)
criterion = nn.MSELoss(reduction = "mean") # mean / sum
loss = criterion(yhat, y)
loss
10.3 二分类交叉熵损失函数
# 使用tensor实现二分类交叉熵损失函数
# loss = -(y * ln(sigma) + (1 - y) * ln(1 - sigma))
# y、sigma、w、X
import torch
import torch.nn as nn
torch.random.manual_seed(420)
N = 3 * pow(10, 6)
X = torch.rand(size = (N, 4), dtype = torch.float32) # N个样本,4个特征
w = torch.rand(size = (4, 1), dtype = torch.float32) # X要能与w相乘,并生成一个标签
y = torch.randint(low = 0, high = 2, size = (N, 1), dtype = torch.float32) # 分类标签
zhat = torch.mm(X, w)
sigma = torch.sigmoid(zhat)
# sigma.shape
# loss = -torch.sum((y * torch.log(sigma) + (1 - y) * torch.log(1 - sigma)))
loss = -sum((y * torch.log(sigma) + (1 - y) * torch.log(1 - sigma)))
loss
# pytorch二分类交叉熵损失函数的使用
# loss = -(y * ln(sigma) + (1 - y) * ln(1 - sigma))
import torch
import torch.nn as nn
torch.random.manual_seed(420)
X = torch.rand(size = (50, 4), dtype = torch.float32)
w = torch.rand(size = (4, 1), dtype = torch.float32)
y = torch.randint(low = 0, high = 2, size = (50, 1), dtype = torch.float32)
zhat = torch.mm(X, w)
sigma = torch.sigmoid(zhat)
criterion1 = nn.BCELoss()
criterion2 = nn.BCEWithLogitsLoss() # 自带sigmoid
# loss = criterion1(sigma, y)
loss = criterion2(zhat, y)
loss
10.4 多分类交叉熵损失函数
import torch
import torch.nn as nn
torch.random.manual_seed(420)
N = 3 * pow(10, 3)
X = torch.rand(size = (N, 4), dtype = torch.float32)
w = torch.rand(size = (4, 3), dtype = torch.float32) # 三分类
y = torch.randint(low = 0, high = 3, size = (N, ), dtype = torch.float32) # 要一维的
zhat = torch.mm(X, w)
# sigma = torch.softmax(zhat, dim = 1)
# 第一种方式:LogSoftmax + NLLLoss
logsm = nn.LogSoftmax(dim = 1) # LogSoftmax实例化
logsigma = logsm(zhat)
criterion1 = nn.NLLLoss() # NLLLoss实例化
criterion1(logsigma, y.long()) # 由于交叉熵损失需要将标签转化为独热编码,因此不接受浮点数作为标签的输入
# 第二种方式:CrossEntyopyLoss
criterion2 = nn.CrossEntropyLoss()
criterion2(zhat, y.long())
Lesson 11. 神经网络的学习
11.1 梯度下降中的两个关键问题
我们从起始点开始,让自变量w向损失函数L(w)减小最快的方向移动。每步的方向就是当前坐标点对应的梯度向量的反方向,每步的距离就是步长 * 当前坐标点所对应的梯度向量的大小(也就是梯度向量的模长),梯度向量的性质保证了沿着其反方向、按照其大小进行移动,就能够接近损失函数的最小值。在这个过程中,存在两个关键问题:
1. 找出梯度向量的方向和大小
梯度向量:梯度向量是多元函数上,各个自变量的偏导数组成的向量,比如损失函数是L(w1, w2, b),在损失函数上对w1,w2,b这三个自变量求偏导数,求得的梯度向量的表达式就是 ,简写为:grad L(w1, w2),
梯度向量中的具体元素就是各个自变量的偏导数,这些偏导数的具体值必须依赖于当前所在坐标点的值进行计算。
2. 让坐标点移动起来(进行一次迭代)
这就是我们迭代权重的迭代公式,其中偏导数的大小影响整体梯度向量的大小,偏导数前的减号影响整体梯度向量的方向。
11.2 找出距离和方向:反向传播
11.2.1 反向传播的定义与价值
反向传播BP解决了损失函数对w求导过于复杂的问题
![]()
11.2.2 pytorch实现反向传播
import torch
import torch.nn as nn
from torch.nn import functional as F
torch.random.manual_seed(420)
# 为了减少backward()的计算量,X和y设置requires_grad = False
X = torch.rand(size = (500, 20), dtype = torch.float32) * 100
# 在生成权重w时,自动设置requires_grad = True
y = torch.randint(low = 0, high = 3, size = (500,), dtype = torch.float32) # y需要是1维的
class Model(nn.Module):
def __init__(self, in_features = 20, out_features = 3):
super(Model, self).__init__()
self.linear1 = nn.Linear(in_features, 13, bias = False)
self.linear2 = nn.Linear(13, 8, bias = False)
self.output = nn.Linear(8, out_features, bias = True)
def forward(self, x):
# z1 = self.linear1(x)
sigma1 = torch.relu(self.linear1(x))
# z2 = self.linear2(sigma1)
sigma2 = torch.sigmoid(self.linear2(sigma1))
zhat = self.output(sigma2)
# sigma3 = F.softmax(z3, dim = 1) # 损失函数自带softmax
return zhat
input_ = X.shape[1]
output_ = len(y.unique())
net = Model(in_features = input_, out_features = output_)
# 正向传播
zhat = net.forward(X)
criterion = nn.CrossEntropyLoss()
loss = criterion(zhat, y.long())
# 反向传播
loss.backward() # 重复执行设置 retain_graph = True
net.linear1.weight.grad # 查看w的梯度
11.3 移动坐标点
11.3.1 走出第一步
# w(t + 1) = w(t) - 步长 * grad
w = net.linear1.weight.data # 权重
lr = 10 # 步长
dw = net.linear1.weight.grad # 梯度
w -= lr * grad # 梯度下降,更新权重
w
11.3.2 从第一步到第二步:动量法Momentum
动量法:更高效的方法,我们让上一步的梯度向量与现在这一点的梯度向量以加权的方式求和,求解出受到上一步大小和方向影响的真实下降方向,再让坐标点向真实下降方向移动。与上一步的梯度向量类似则走的远,区别大则走的更小心翼翼
![]()
![]()
# 手动实现动量法迭代 Momentum
# v(t) = gamma * v(t - 1) - lr * dw
# w(t + 1) = w(t) + v(t)
w = net.linear1.weight.data
dw = net.linear1.weight.grad
lr = 0.1
gamma = 0.9
v = torch.zeros(dw.shape[0], dw.shape[1]) # v和d相减,形状一样
v = gamma * v - lr * dw
w += v # w -= lr * dw
w
11.3.3 torch.optim实现带动量的梯度下降
# 动量法的pytorch实现 torch.optim
# 一、准备工作:
# 导入库
# 确定数据、超参数的确定(lr,gamma)
# 定义神经网络的架构类Model,类Model需要输入的参数
# 实例化神经网络的类 - 让神经网络准备好进行正向传播
# 定义损失函数
# 定义优化算法
import torch
import torch.nn as nn
import torch.optim as optim
from torch.nn import functional as F
class Model(nn.Module):
def __init__(self, in_features = 20, out_features = 3):
super(Model, self).__init__()
self.linear1 = nn.Linear(in_features, 13)
self.linear2 = nn.Linear(13, 8)
self.output = nn.Linear(8, out_features)
def forward(self, x):
z1 = self.linear1(x)
sigma1 = torch.relu(z1)
z2 = self.linear2(sigma1)
sigma2 = torch.sigmoid(z2)
z3 = self.output(sigma2)
return z3
# 准备数据
torch.random.manual_seed(420)
X = torch.rand(size = (500, 20), dtype = torch.float32)
y = torch.randint(low = 0, high = 3, size = (500,), dtype = torch.float32)
# 设置超参数
lr = 0.1
gamma = 0.9
# 实例化模型
input_ = X.shape[1]
output_ = len(y.unique())
net = Model(input_, output_)
# 定义损失函数
criterion = nn.CrossEntropyLoss()
# 定义优化算法:动量法,随机梯度下降
# net.parameters() # 一次导出现有神经网络架构下全部的权重和截距
opt = optim.SGD(net.parameters(), lr = lr, momentum = gamma)
# 二、开始训练
# 正向传播
# 本轮向前传播的损失函数值
# 反向传播 - 得到了梯度
# 更新权重(和动量)
# 清空梯度 - 清除原来计算出来的,基于上一个点的坐标计算的梯度
正向传播
zhat = net.forward(X)
# 计算损失
loss = criterion(zhat, y.long())
# 反向传播
loss.backward()
# 更新权重
opt.step() # 走一步,更新权重w,更新动量v
# 清空梯度
opt.zero_grad() # 清空保存的权重数据
print(loss)
print(net.linear1.weight.data[0][:10])
11.4 开始迭代:batch_size与epoch
11.4.1 为什么要有小批量?
好处:
- mini-batch SGD更可能找到全局最小值
- mini-batch SGD可以提升神经网络的计算效率,让神经网络计算更快
坏处:增加迭代次数
11.4.2 batch_size与epoches
算法对同样的数据进行学习的次数并不是越多越好。让算法每次学习batch_size个数据,以此来减少对全部数据进行学习的次数
epoch是衡量训练数据被使用次数的单位,一个epoch表示优化算法将全部训练数据都使用了一次。它与梯度下降中的迭代次数有非常深的关系,我们常使用“完成1个epoch需要n次迭代”这样的语言。
11.4.3 TensorDataset与DataLoader
# TensorDataset与DataLoader
# TensorDataset:合并特征张量与标签
import torch
import torch.nn as nn
from torch.nn import functional as F
import torch.optim as optim
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader
torch.random.manual_seed(420)
# X = torch.rand(size = (500, 20), dtype = torch.float32)
a = torch.randn(500, 3, 2) # 三维数据 - 二维表格
b = torch.randn(500, 3, 4, 5) # 四维数据 - 图像
c = torch.randn(500, 1) # 二维数据 - 标签
# y = torch.randint(low = 0, high = 3, size = (500,), dtype = torch.float32)
bs = 120
# 被合并对象的第一维度上的值相等
data = TensorDataset(b, c)
for x in data:
print(x)
# DataLoader:用来切割小批量的类
dataset = DataLoader(data, batch_size = bs, shuffle = True, drop_last = True)
for x in dataset:
print(x[0].shape) # 查看每个批次的形状
len(dataset) # 查看被分成了几个batch
#可以使用.datasets查看数据集相关的属性
len(dataset.dataset) # 查看总共有多少数据
dataset.dataset[0] # 查看第一个样本
dataset.dataset[0][0] # 查看第一个样本的特征张量
dataset.dataset[0][1] # 查看第一个样本的标签
dataset.batch_size # 查看现在的batch_size
11.5 在Fashion Mnist数据集上实现完整的神经网络
# 在Fashion Mnist数据集上实现完整的神经网络
# 流程
# 1. 设置超参数:步长lr,动量gamma,迭代次数epochs,batch_size等信息,(初始权重w0)
# 2. 导入数据,将数据分成batches
# 3. 定义神经网络结构
# 4. 定义损失函数L(w),如果需要的话,将损失函数调整成凸函数以便求解最小值
# 5. 定义所使用的优化算法
# 6. 开始在epoches和batch上循环,执行优化算法
# 6.1 调整数据结构,确定数据能够在神经网络、损失函数和优化算法中顺利进行
# 6.2 完成向前传播,计算初始损失 .forward()
# 6.3 利用反向传播,在损失函数L(w)上对每一个w求偏导数 .backward()
# 6.4 迭代当前权重 .step()
# 6.5 清空本轮梯度 .zero_grad()
# 6.6 完成模型进度与效果监控 # loss、准确率accuracy
# 7. 输出结果
# 导包
import torch
from torch import nn
from torch import optim
from torch.nn import functional as F
from torch.utils.data import DataLoader, TensorDataset
import torchvision
import torchvision.transforms as transforms
mnist = torchvision.datasets.FashionMNIST(
root = "D:\Documents\LearningResources\DeepLearning\TsaiTsaiDeepLearning\dataset",
download = True,
train = True,
transform = transforms.ToTensor()
)
# 放入进行迭代的数据结构是什么样的?
batchdata = DataLoader(mnist,
batch_size = batch_size,
shuffle = True
)
input_ = mnist.data[0].numel() # 返回这个张量有多少元素
output_ = len(mnist.targets.unique())
# 定义神经网络架构
class Model(nn.Module):
def __init__(self, in_features = 10, out_features = 10):
super(Model, self).__init__()
self.linear1 = nn.Linear(in_features, 128, bias = False)
self.output = nn.Linear(128, out_features, bias = False)
def forward(self, x):
x = x.view(-1, 28*28) # view(-1,) 表示需要对数据结构进行改变, -1作为占位符,表示请pytorch自动计算-1这个位置的维度应该是多少
sigma1 = torch.relu(self.linear1(x))
sigma2 = F.log_softmax(self.output(sigma1), dim = 1) # 为了计算准确率,需要softmax
return sigma2
# 定义损失函数、优化算法、梯度下降流程
# 定义一个训练函数
def fit(net, batchdata, lr = 0.01, epochs = 5, gamma = 0):
criterion = nn.NLLLoss()
opt = optim.SGD(net.parameters(), lr = lr, momentum = gamma)
samples = 0 # 查看过的样本数
correct = 0 # 预测正确的样本数
for epoch in range(epochs): # 全数据被训练几次
for batch_index, (x, y) in enumerate(batchdata): # enumerate()给数据加上标号(索引)
y = y.view(x.shape[0]) # 降维,将y变成一维
sigma = net.forward(x) # 正向传播
loss = criterion(sigma, y)
loss.backward() # 反向传播
opt.step() # 更新传播
opt.zero_grad()
# 求解准确率
yhat = torch.max(sigma, 1)[1]
correct += torch.sum(yhat == y)
# 监督进度
samples += x.shape[0]
if ((batch_index + 1) % 125 == 0): # 每N个batch就打印一次
print("Epoch{}:[{}/{}({:.0f}%), Loss:{:.6f}, Accuracy:{:.2f}%]".format(
epoch + 1,
samples,
epochs * len(batchdata.dataset),
100 * samples / (epochs * len(batchdata.dataset)),
loss.data.item(),
float(100 * correct / samples))
)
torch.random.manual_seed(420)
net = Model(input_, output_)
fit(net, batchdata, lr = lr, epochs = epochs, gamma = gamma)