略.
1)验证成功
import torch
a=torch.ones(2,2)
a
tensor([[1., 1.],
[1., 1.]])
查看pytorch版本
print ("hello pytorch {}".format(torch.__version__))
3)查看是否支持GPU
print (torch.cuda.is_available())
Tensor与Variable:
参考1.
参考2
Variable:主要用于封装Tensor,进行自动求导,是torch.autograd中的数据类型。Variable是Pytorch的0.4.0版本之前的一个重要的数据结构,但是从0.4.0开始,它已经并入了Tensor中了。
data:被封装的Tensor
grad:data的梯度
grad_fn:创建Tensor的Function,是自动求导的关键
requires_grad:指示是否需要梯度
is_leaf:指示是否是叶子
从Pytorch0.4.0版本开始,Variable并入Tensor:
dtype: 张量的数据类型,三大类,共9种。torch.FloatTensor, torch.cuda.FloatTensor
shape: 张量的形状。如:(64,3,224,224)
decive: 所在设备
torch.tensor(data, dtype=None, device=None, requires_grad=False, pin_memory=False)
功能:从data 创建 tensor
data : 数据 , 可以是 list, numpy
dtype : 数据类型,默认与 data 的一致
device 所在设备 , cuda cpu
requires_grad :是否需要梯度
pin_memory :是否存于锁页内存
import torch
import numpy as np
# Create tensors via torch.tensor
flag = True
if flag:
arr = np.ones((3, 3))
print("type of data:", arr.dtype)
t = torch.tensor(arr, device='cuda')
print(t)
type of data: float64
tensor([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]], device='cuda:0', dtype=torch.float64)
其中,cuda表示采用了gpu,0是gpu的标号,由于只有一个gpu,因此是0。
# Create tensors via torch.from_numpy(ndarray)
arr = np.array([[1, 2, 3], [4, 5, 6]])
t = torch.from_numpy(arr)
print("numpy array: ", arr)
print("tensor : ", t)
print("\n修改arr")
arr[0, 0] = 0
print("numpy array: ", arr)
print("tensor : ", t)
print("\n修改tensor")
t[0, 0] = -1
print("numpy array: ", arr)
print("tensor : ", t)
numpy array: [[1 2 3]
[4 5 6]]
tensor : tensor([[1, 2, 3],
[4, 5, 6]], dtype=torch.int32)
修改arr
numpy array: [[0 2 3]
[4 5 6]]
tensor : tensor([[0, 2, 3],
[4, 5, 6]], dtype=torch.int32)
修改tensor
numpy array: [[-1 2 3]
[ 4 5 6]]
tensor : tensor([[-1, 2, 3],
[ 4, 5, 6]], dtype=torch.int32)
torch.zeros(*size, out=None, dtype=None,
layout=torch.strided, device=None, requires_grad=False)
可见,该out的值与t相同,因此out是一个输出的作用,将张量生成的数据赋值给另一个变量。
torch.zeros_like(input, dtype=None, layout=None, device=None, requires_grad=False)
t = torch.full((3, 3), 5)
print(t)
tensor([[5, 5, 5],
[5, 5, 5],
[5, 5, 5]])
t = torch.arange(start=0, end=100, step=1, out=None, dtype=None,
layout=torch.strided, device=None, requires_grad=False)
t = torch.arange(2, 10, 2)
print(t)
# tensor([2, 4, 6, 8])
t = torch.linspace(start=0, end=100, steps=5, out=None, dtype=None,
layout=torch.strided, device=None, requires_grad=False)
t = torch.linspace(2, 10, 6)
print(t)
# tensor([ 2.0000, 3.6000, 5.2000, 6.8000, 8.4000, 10.0000])
t = torch.logspace(start=0, end=100, steps=5, base=10, out=None,
dtype=None, layout=torch.strided, device=None, requires_grad=False)
torch.normal(mean, std, out=None)
torch.normal(mean, std, size, out=None)
# the mean and std both are tensors
mean = torch.arange(1, 5, dtype=torch.float)
std = torch.arange(1, 5, dtype=torch.float)
t_normal = torch.normal(mean, std)
print("mean:{}\nstd:{}".format(mean, std))
print(t_normal)
#######由结果可知,其生成的tensor是上面每一维度的参数生成的。
# mean:tensor([1., 2., 3., 4.])
# std:tensor([1., 2., 3., 4.])
# tensor([ 0.4750, 3.6384, -2.1488, 5.3180])
需要注意的是,对于mean和std都是标量的情况下,需要指定生成的size。
# mean: scalar std: scalar
t_normal = torch.normal(0., 1., size=(4,))
print(t_normal)
# tensor([0.6614, 0.2669, 0.0617, 0.6213])
torch.randn(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
参考1
参考2
t = torch.ones((2, 3))
t_0 = torch.cat([t, t], dim=0)
t_1 = torch.stack([t, t], dim=0)
print(t_0)
print(t_0.shape)
print(t_1)
print(t_1.shape)
tensor([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]])
torch.Size([4, 3])
tensor([[[1., 1., 1.],
[1., 1., 1.]],
[[1., 1., 1.],
[1., 1., 1.]]])
torch.Size([2, 2, 3])
与cat相比,stack创建在了一个新维度
t = torch.ones((2, 7))
print(t)
list_of_tensor = torch.chunk(t, dim=1, chunks=3)
print(list_of_tensor)
tensor([[1., 1., 1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1., 1., 1.]])
(tensor([[1., 1., 1.],
[1., 1., 1.]]),
tensor([[1., 1., 1.],
[1., 1., 1.]]),
tensor([[1.], [1.]]))
t = torch.ones((2, 7))
print(t)
list_of_tensor_2 = torch.split(t, 3, dim=1)
print(list_of_tensor_2)
list_of_tensor_3 = torch.split(t, [2, 2, 3], dim=1)
print(list_of_tensor_3)
tensor([[1., 1., 1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1., 1., 1.]])
(tensor([[1., 1., 1.],
[1., 1., 1.]]), tensor([[1., 1., 1.],
[1., 1., 1.]]), tensor([[1.],
[1.]]))
(tensor([[1., 1.],
[1., 1.]]), tensor([[1., 1.],
[1., 1.]]), tensor([[1., 1., 1.],
[1., 1., 1.]]))
list内元素之和等于维度上的长度
torch.index_select(): 在维度dim上,按index索引数据
t = torch.randint(0, 9, (3, 3))
print(t)
# index_select
idx=torch.tensor([0,2],dtype=torch.long)
t_index_select=torch.index_select(t,index=idx,dim=0)
print(t_index_select)
tensor([[7, 1, 5],
[1, 3, 4],
[3, 4, 0]])
tensor([[7, 1, 5],
[3, 4, 0]])
torch.masked_select(): 按mask中的True进行索引, 返回一维张量。
t = torch.randint(0, 9, (3, 3))
print(t)
# masked_select
mask = t.ge(5)
print(mask)
t_masked_select = torch.masked_select(t, mask)
print(t_masked_select)
tensor([[3, 8, 1],
[6, 4, 1],
[4, 8, 2]])
tensor([[False, True, False],
[ True, False, False],
[False, True, False]])
tensor([8, 6, 8])
orch.reshape()
# torch.reshape
t = torch.randperm(8)
print(t)
t_reshape = torch.reshape(t, (2, 4)) # -1代表不关心
print(t_reshape)
tensor([2, 3, 1, 4, 0, 5, 7, 6])
tensor([[2, 3, 1, 4],
[0, 5, 7, 6]])
torch.transpose(): 交换张量的两个维度
# torch.transpose
t = torch.rand((2, 3, 4))
print(t)
t_transpose = torch.transpose(t, dim0=1, dim1=2)
print(t_transpose)
tensor([[[0.5063, 0.6772, 0.8968, 0.4836],
[0.0820, 0.5198, 0.1273, 0.1895],
[0.3535, 0.9936, 0.7150, 0.4375]],
[[0.7801, 0.9114, 0.2901, 0.7171],
[0.0553, 0.9102, 0.4060, 0.4010],
[0.1037, 0.1053, 0.7860, 0.4523]]])
tensor([[[0.5063, 0.0820, 0.3535],
[0.6772, 0.5198, 0.9936],
[0.8968, 0.1273, 0.7150],
[0.4836, 0.1895, 0.4375]],
[[0.7801, 0.0553, 0.1037],
[0.9114, 0.9102, 0.1053],
[0.2901, 0.4060, 0.7860],
[0.7171, 0.4010, 0.4523]]])
torch.t(): 2维张量转置,对矩阵而言,等价于 torch.transpose(input, 0, 1)
torch.squeeze(): 压缩长度为1的维度(轴)
# torch.squeeze
t=torch.rand((1,2,3,1))
t1=torch.squeeze(t)
print(t1.shape)
t2=torch.squeeze(t,dim=2)
print(t2.shape)
torch.Size([2, 3])
torch.Size([1, 2, 3, 1])
torch.unsqueeze(): 依据dim扩展维度
这里重点演示一下加法这个函数,因为这个函数有一个小细节:torch.add(input, alpha=1, other, out=None):逐元素计算input+alpha * other。注意人家这里有个 alpha,叫做乘项因子。类似权重的个东西。这个东西让计算变得更加简洁, 比如线性回归我们知道有个 y = wx + b, 在这里直接一行代码torch.add(b, w, x) 就搞定。
# torch.add
t0=torch.rand((3,3))
t1=torch.ones_like(t0)
print(t0)
print(t1)
t_add=torch.add(t0,10,t1)
print(t_add)
t_0:
tensor([[ 0.6614, 0.2669, 0.0617],
[ 0.6213, -0.4519, -0.1661],
[-1.5228, 0.3817, -1.0276]])
t_1:
tensor([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]])
t_add_10:
tensor([[10.6614, 10.2669, 10.0617],
[10.6213, 9.5481, 9.8339],
[ 8.4772, 10.3817, 8.9724]])
-torch.addcdiv()
线性回归是分析一个变量与另外一(多)个变量之间关系的方法。因变量是 y,自变量是 x,关系线性:
我们的求解步骤:
这就是我上面说的叫做代码逻辑的一种思路,写代码往往习惯先有一个这样的一种思路,然后再去写代码的时候,就比较容易了。而如果不系统的学一遍 Pytorch,一上来直接上那种复杂的 CNN, LSTM 这种,往往这些代码逻辑不好形成,因为好多细节我们根本就不知道。所以这次学习先从最简单的线性回归开始,然后慢慢的到复杂的那种网络。下面我们开始写一个线性回归模型:
# -*- coding:utf-8 -*-
"""
@file name : lesson-03-Linear-Regression.py
@author : TingsongYu https://github.com/TingsongYu
@date : 2018-10-15
@brief : 一元线性回归模型
"""
import torch
import matplotlib.pyplot as plt
torch.manual_seed(10)
lr = 0.05 # 学习率 20191015修改
# 创建训练数据
x = torch.rand(20, 1) * 10 # x data (tensor), shape=(20, 1)
y = 2*x + (5 + torch.randn(20, 1)) # y data (tensor), shape=(20, 1)
# 构建线性回归参数
w = torch.randn((1), requires_grad=True)
b = torch.zeros((1), requires_grad=True)
for iteration in range(1000):
# 前向传播
wx = torch.mul(w, x)
y_pred = torch.add(wx, b)
# 计算 MSE loss
loss = (0.5 * (y - y_pred) ** 2).mean()
# 反向传播
loss.backward()
# 更新参数
b.data.sub_(lr * b.grad)
w.data.sub_(lr * w.grad)
# 清零张量的梯度 20191015增加
w.grad.zero_()
b.grad.zero_()
# 绘图
if iteration % 20 == 0:
plt.scatter(x.data.numpy(), y.data.numpy())
plt.plot(x.data.numpy(), y_pred.data.numpy(), 'r-', lw=5)
plt.text(2, 20, 'Loss=%.4f' % loss.data.numpy(), fontdict={'size': 20, 'color': 'red'})
plt.xlim(1.5, 10)
plt.ylim(8, 28)
plt.title("Iteration: {}\nw: {} b: {}".format(iteration, w.data.numpy(), b.data.numpy()))
plt.pause(1.5)
if loss.data.numpy() < 1:
break
今天的学习内容结束, 下面简单的梳理一遍,其实小东西还是挺多的。
这次整理了很多的函数,每个函数的用法不同,具体用法先不用刻意记住,先知道哪些函数具体完成什么功能,到时候用的时,边查边用,慢慢的多练才能熟。
深度学习就是对张量进行一系列的操作,随着操作种类和数量的增多,会出现各种值得思考的问题。比如多个操作之间是否可以并行,如何协同底层的不同设备,如何避免冗余的操作,以实现最高效的计算效率,同时避免一些 bug。因此产生了计算图 (Computational Graph)。
计算图是用来描述运算的有向无环图,有两个主要元素:节点 (Node) 和边 (Edge)。
用计算图表示:y=(x+w)*(w+1),如下所示:
可以看作, y = a × b y=a \times b y=a×b ,其中 a = x + w , b = w + 1 a=x+w,b=w+1 a=x+w,b=w+1。
计算图与梯度求导:这里求 y 对 w 的导数。根复合函数的求导法则,可以得到如下过程。
∂ y ∂ w = ∂ y ∂ a ∂ a ∂ w + ∂ y ∂ b ∂ b ∂ w = b ∗ 1 + a ∗ 1 = b + a = ( w + 1 ) + ( x + w ) = 2 ∗ w + x + 1 = 2 ∗ 1 + 2 + 1 = 5 \begin{aligned} \frac{\partial y}{\partial w} & =\frac{\partial y}{\partial a} \frac{\partial a}{\partial w}+\frac{\partial y}{\partial b} \frac{\partial b}{\partial w} \\ & =b * 1+a * 1 \\ & =b+a \\ & =(w+1)+(x+w) \\ & =2 * w+x+1 \\ & =2 * 1+2+1=5 \end{aligned} ∂w∂y=∂a∂y∂w∂a+∂b∂y∂w∂b=b∗1+a∗1=b+a=(w+1)+(x+w)=2∗w+x+1=2∗1+2+1=5
体现到计算图中,就是根节点 y 到叶子节点 w 有两条路径 y -> a -> w和y ->b -> w。根节点依次对每条路径的孩子节点求导,一直到叶子节点w,最后把每条路径的导数相加即可。
代码如下:
import torch
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)
# y=(x+w)*(w+1)
a = torch.add(w, x) # retain_grad()
b = torch.add(w, 1)
y = torch.mul(a, b)
# y 求导
y.backward()
# 打印 w 的梯度,就是 y 对 w 的导数
print(w.grad)
# 结果为tensor([5.])
回顾前面说过的 Tensor 中有一个属性is_leaf标记是否为叶子节点。
在上面的例子中,x 和 w 是叶子节点,其他所有节点都依赖于叶子节点。叶子节点的概念主要是为了节省内存,在计算图中的一轮反向传播结束之后,非叶子节点的梯度是会被释放的。
叶子结点 :用户创建的结点称为叶子结点,如 X 与 W
is_leaf: 指示张量是否为叶子结点
叶子节点的作用是标志存储叶子节点的梯度,而清除在反向传播过程中的变量的梯度,以达到节省内存的目的。当然,如果想要保存过程中变量的梯度值,可以采用retain_grad()
grad_fn: 记录创建该张量时所用的方法(函数)
代码示例:
# 查看叶子结点
print("is_leaf:\n", w.is_leaf, x.is_leaf, a.is_leaf, b.is_leaf, y.is_leaf)
# 查看梯度
print("gradient:\n", w.grad, x.grad, a.grad, b.grad, y.grad)
结果为:
is_leaf:
True True False False False
gradient:
tensor([5.]) tensor([2.]) None None None
非叶子节点的梯度为空,如果在反向传播结束之后仍然需要保留非叶子节点的梯度,可以对节点使用retain_grad()方法。
而 Tensor 中的 grad_fn 属性记录的是创建该张量时所用的方法 (函数)。而在反向传播求导梯度时需要用到该属性。
示例代码:
# 查看梯度
print("w.grad_fn = ", w.grad_fn)
print("x.grad_fn = ", x.grad_fn)
print("a.grad_fn = ", a.grad_fn)
print("b.grad_fn = ", b.grad_fn)
print("y.grad_fn = ", y.grad_fn)
结果为
w.grad_fn = None
x.grad_fn = None
a.grad_fn =
b.grad_fn =
y.grad_fn =
根据计算图搭建方式,可将计算图分为动态图和静态图.
动态图 vs 静态图:
在这里插入图片描述
PyTorch 采用的是动态图机制 (Dynamic Computational Graph),而 Tensorflow 采用的是静态图机制 (Static Computational Graph)。
动态图是运算和搭建同时进行,也就是可以先计算前面的节点的值,再根据这些值搭建后面的计算图。优点是灵活,易调节,易调试。PyTorch 里的很多写法跟其他 Python 库的代码的使用方法是完全一致的,没有任何额外的学习成本。
静态图是先搭建图,然后再输入数据进行运算。优点是高效,因为静态计算是通过先定义后运行的方式,之后再次运行的时候就不再需要重新构建计算图,所以速度会比动态图更快。但是不灵活。TensorFlow 每次运行的时候图都是一样的,是不能够改变的,所以不能直接使用 Python 的 while 循环语句,需要使用辅助函数 tf.while_loop 写成 TensorFlow 内部的形式。
本节课主要分为两部分:PyTorch 中的自动求导系统以及逻辑回归模型。我们知道,深度模型的训练就是不断地更新权值,而权值的更新需要求解梯度,因此,梯度在我们的模型训练过程中是至关重要的。然而,求解梯度通常十分繁琐,因此,PyTorch 中引入了自动求导系统帮助我们完成这一过程。在 PyTorch 中,我们无需手动计算梯度,只需要搭建好前向传播的计算图,然后根据 PyTorch 中的 autograd 方法就可以得到所有张量的梯度。
torch.autograd.backward(
tensors,
grad_tensors=None,
retain_graph=None,
create_graph=False
)
主要参数:
回顾一下如何通过计算图求解梯度:
y = ( x + w ) ∗ ( w + 1 ) y=(x+w) *(w+1) y=(x+w)∗(w+1)
代码示例:
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)
a = torch.add(w, x)
b = torch.add(w, 1)
y = torch.mul(a, b)
# 如果希望后面再次执行该计算图,可以将 retain_graph 参数设为 True
# y.backward(retain_graph=True)
y.backward()
print(w.grad)
输出结果:
tensor([5.])
当有多个 loss 需要计算梯度时,通过 grad_tensors 设置各 loss 权重比例:
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)
a = torch.add(w, x)
b = torch.add(w, 1)
# y0 = (x+w) * (w+1) dy0/dw = 2*w + x + 1 = 5
y0 = torch.mul(a, b)
# y1 = (x+w) + (w+1) dy1/dw = 2
y1 = torch.add(a, b)
# 这种情况下,loss 是一个向量 [y0, y1]
loss = torch.cat([y0, y1], dim=0)
# 梯度的权重:dy0/dw 权重为 1,dy1/dw 权重为 2
grad_tensors = torch.tensor([1., 2.])
# gradient 传入 torch.autograd.backward() 中的 grad_tensors
loss.backward(gradient=grad_tensors)
print(w.grad) # 5*1 + 2*2 = 9
输出结果:
tensor([9.])
torch.autograd.grad(
outputs,
inputs,
grad_outputs=None,
retain_graph=None,
create_graph=False
)
主要参数:
求取二阶梯度:
x = torch.tensor([3.], requires_grad=True)
y = torch.pow(x, 2) # y = x**2
# grad_1 = dy/dx = 2x = 2 * 3 = 6
grad_1 = torch.autograd.grad(y, x, create_graph=True)
print(grad_1)
# grad_2 = d(dy/dx)/dx = d(2x)/dx = 2
grad_2 = torch.autograd.grad(grad_1[0], x)
print(grad_2)
输出结果:
(tensor([6.], grad_fn=),)
(tensor([2.]),)
注意事项:
代码示例 1:
# 1. 梯度不会自动清零,重复求取会叠加,可以使用 .grad.zero_() 方法手动清零
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)
for i in range(3):
a = torch.add(w, x)
b = torch.add(w, 1)
y = torch.mul(a, b)
y.backward()
print(w.grad)
# 梯度清零,下划线表示原位操作 (in-place)
w.grad.zero_()
for i in range(3):
a = torch.add(w, x)
b = torch.add(w, 1)
y = torch.mul(a, b)
y.backward()
print(w.grad)
w.grad.zero_()
输出结果:
tensor([5.])
tensor([10.])
tensor([15.])
tensor([5.])
tensor([5.])
tensor([5.])
代码示例 2:
# 2. 依赖于叶子结点的结点, requires_grad 默认为 True
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)
a = torch.add(w, x)
b = torch.add(w, 1)
y = torch.mul(a, b)
print(a.requires_grad, b.requires_grad, y.requires_grad)
输出结果:
True True True
代码示例 3:
# 3. 叶子结点不可执行 in-place (原位操作)。因为 PyTorch 计算图中引用叶子结点的值是
# 直接引用其前向传播时的地址,为了防止计算出错,叶子结点不可执行 in-place 操作。
# in-place (原位操作): 从原始内存地址中直接改变数据。
# 非 in-place 操作: 开辟一块新的内存地址存储改变后的数据。
a = torch.ones((1, ))
print(id(a), a)
# 非 in-place 操作
a = a + torch.ones((1, ))
print(id(a), a)
# in-place 操作
a += torch.ones((1, ))
print(id(a), a)
输出结果:
4875211904 tensor([1.])
4875212336 tensor([2.])
4875212336 tensor([3.])
对叶子结点执行 in-place 操作将导致报错:
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)
a = torch.add(w, x)
b = torch.add(w, 1)
y = torch.mul(a, b)
# 对非叶子结点 a 执行非 in-place 操作
print(a.add(1))
# 对非叶子结点 a 执行 in-place 操作
print(a.add_(1))
# 对叶子结点 w 执行非 in-place 操作
print(w.add(1))
# 对叶子结点 w 执行 in-place 操作,会报错
print(w.add_(1))
y.backward()
输出结果:
tensor([4.], grad_fn=)
tensor([4.], grad_fn=)
tensor([2.], grad_fn=)
Traceback (most recent call last):
File "", line 1, in
File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_bundle/pydev_umd.py", line 197, in runfile
pydev_imports.execfile(filename, global_vars, local_vars) # execute the script
File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "/Users/andy/PycharmProjects/hello_pytorch/lesson/lesson-05/lesson-05-autograd.py", line 145, in
print(w.add_(1))
RuntimeError: a leaf Variable that requires grad is being used in an in-place operation.
逻辑回归 (Logistic Regression) 是 线性 的 二分类 模型。
模型表达式:
y = f ( W X + b ) f ( x ) = 1 1 + e − x \begin{aligned} & y=f(W X+b) \\ & f(x)=\frac{1}{1+e^{-x}} \end{aligned} y=f(WX+b)f(x)=1+e−x1
即:
y = 1 1 + e − ( W X + b ) y=\frac{1}{1+e^{-(W X+b)}} y=1+e−(WX+b)1
这里, 我们将 f ( x ) f(x) f(x) 称为 Sigmoid 函数, 又称 Logistic 函数:
class = { 0 , y < 0.5 1 , y ≥ 0.5 \text { class }= \begin{cases}0, & y<0.5 \\ 1, & y \geq 0.5\end{cases} class ={0,1,y<0.5y≥0.5
线性回归是分析 自变量 x x x 与 因变量 y y y (标量) 之间关系的方法;而逻辑回归是分析 自变量 x x x 与 因变量 y y y (概率) 之间关系的方法。
机器学习训练的 5 个步骤:
代码示例:
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import numpy as np
torch.manual_seed(10)
# ============================== Step 1/5: 生成数据 ===================================
sample_nums = 100
mean_value = 1.7
bias = 1
n_data = torch.ones(sample_nums, 2)
x0 = torch.normal(mean_value * n_data, 1) + bias # 类别0 数据 shape=(100, 2)
y0 = torch.zeros(sample_nums) # 类别0 标签 shape=(100, 1)
x1 = torch.normal(-mean_value * n_data, 1) + bias # 类别1 数据 shape=(100, 2)
y1 = torch.ones(sample_nums) # 类别1 标签 shape=(100, 1)
train_x = torch.cat((x0, x1), 0)
train_y = torch.cat((y0, y1), 0)
# ============================== Step 2/5: 选择模型 ===================================
class LR(nn.Module):
def __init__(self):
super(LR, self).__init__()
self.features = nn.Linear(2, 1)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
x = self.features(x)
x = self.sigmoid(x)
return x
lr_net = LR() # 实例化逻辑回归模型
# ============================== Step 3/5: 选择损失函数 ================================
loss_fn = nn.BCELoss() # 二分类交叉熵损失 Binary Cross Entropy Loss
# ============================== Step 4/5: 选择优化器 ==================================
lr = 0.01 # 学习率
optimizer = torch.optim.SGD(lr_net.parameters(), lr=lr, momentum=0.9) # 随机梯度下降
# ============================== Step 5/5: 模型训练 ====================================
for iteration in range(1000):
# 前向传播
y_pred = lr_net(train_x)
# 计算 loss
loss = loss_fn(y_pred.squeeze(), train_y)
# 反向传播
loss.backward()
# 更新参数
optimizer.step()
# 绘图
if iteration % 20 == 0:
mask = y_pred.ge(0.5).float().squeeze() # 以 0.5 为阈值进行分类
correct = (mask == train_y).sum() # 计算正确预测的样本个数
acc = correct.item() / train_y.size(0) # 计算分类准确率
plt.scatter(x0.data.numpy()[:, 0], x0.data.numpy()[:, 1], c='r', label='class 0')
plt.scatter(x1.data.numpy()[:, 0], x1.data.numpy()[:, 1], c='b', label='class 1')
w0, w1 = lr_net.features.weight[0]
w0, w1 = float(w0.item()), float(w1.item())
plot_b = float(lr_net.features.bias[0].item())
plot_x = np.arange(-6, 6, 0.1)
plot_y = (-w0 * plot_x - plot_b) / w1
plt.xlim(-5, 7)
plt.ylim(-7, 7)
plt.plot(plot_x, plot_y)
plt.text(-5, 5, 'Loss=%.4f' % loss.data.numpy(), fontdict={'size': 20, 'color': 'red'})
plt.title("Iteration: {}\nw0:{:.2f} w1:{:.2f} b:{:.2f} accuracy:{:.2%}".format(iteration, w0, w1, plot_b, acc))
plt.legend()
plt.show()
plt.pause(0.5)
if acc > 0.99:
break
本节课介绍了 PyTorch 自动求导系统中的 torch.autograd.backward
和 torch.autograd.grad
这两个常用方法,并演示了一阶、二阶导数的求导过程;理解了自动求导系统,以及数据载体 —— 张量,前向传播构建计算图,计算图求取梯度过程。有了这些知识之后,我们就可以开始正式训练机器学习模型。这里通过演示逻辑回归模型的训练,学习了机器学习回归模型的五大模块:数据、模型、损失函数、优化器和迭代训练过程。这五大模块将是后面学习的主线。