第一次作业:深度学习基础
1. 图像处理基本练习
展示不同通道上的图像
# 展示三通道图像
plt.subplot(121)
plt.imshow(colony[:,:,:])
plt.title('3-channel image')
plt.axis('off')
# 展示只有一个通道的图像
plt.subplot(122)
plt.imshow(colony[:,:,0])
plt.title('1-channel image')
plt.axis('off');
Canny 算子用于边缘检测
from skimage.feature import canny
from scipy import ndimage as ndi
img_edges = canny(img)
img_filled = ndi.binary_fill_holes(img_edges)
# Plot
plt.figure(figsize=(18, 12))
plt.subplot(121)
plt.imshow(img_edges, 'gray')
plt.subplot(122)
plt.imshow(img_filled, 'gray')
2. Pytorch基础
2.1 基本数据Tensor
Tensor是Pytorch中的基本操作对象,是存储和变换数据的主要工具。与NumPy的ndarrays非常类似,不过Tensor还支持GPU运算。Tensor有多种创建方法,如基础的Tensor(),还有多种与NumPy十分类似的方法,如ones()、eye()、zeros()和randn()等,其中PyTorch中默认的数据类型是 torch.FloatTensor。
a = torch.Tensor(2, 2)
# Tensor()与tensor的区别在于Tensor返回的数据类型是默认的,而tensor类型根据数据进行推断
# 参数可以为Python的list、NumPy的ndarray
x = torch.tensor((2, 3, 4, 9))
# 使用ones()函数,所有元素均为1
x2 = torch.ones((2, 3))
# 通过现有的Tensor来创建,此方法会默认重用输入Tensor的一些属性,例如数据类型,除非自定义数据类型
# 使用dtype来定义数据类型
x3 = torch.zeros_like(x2, dtype=torch.long)
# 使用randn()函数,生成标准分布的随机数矩阵
x4 = torch.randn(3, 4)
# 使用arange(start, end, step)函数,表示从start到end,间距为step,一维向量
v = torch.arange(1,4)
# 使用linspace(start, end, steps)函数,表示从start到end,一共steps份,一维向量
x5 = torch.linspace(10, 20, 15)
# 查看Tensor中总的元素个数
a.numel()
# 通过shape或者size()来获取Tensor的形状或维度
print(x.size())
print(x.shape)
2.2 Tensor的各种操作
索引
可以使用类似NumPy的索引操作来访问Tensor
的一部分,但是索引出来的结果与原数据共享内存,即修改一个,另一个会跟着修改。
Input:
m = torch.tensor([[2, 3, 4],[5, 6, 7]])
print(m[:,1])
print(m[0])
print(m[0][1])
Output:
tensor([3, 6])
tensor([2, 3, 4])
tensor(3)
# 选择符合条件的元素并返回
Input:
a = torch.tensor([[1, 2], [3, 4]])
print(a > 2)
ind = a > 2
print(a[ind])
OUtput:
tensor([[False, False],
[ True, True]])
tensor([3, 4])
改变形状
变形操作则是指改变Tensor的维度,有四类不同的基本操作。
变形操作 | 功能 |
---|---|
view()、resize()、reshape() | 调整形状同时元素总数相同 |
transpose()、permute() | 各维度之间的变换 |
squeeze()、unsqueeze() | 处理size为1的维度 |
expand()、expand_as() | 复制元素来扩展维度 |
# 将数据的第二维度与第三维度进行变换
Input:
a = torch.randn((2, 3, 2))
print(a)
res = a.transpose(1, 2)
print(res)
OUtput
tensor([[[-1.6705, -1.0614],
[ 0.9338, 1.2305],
[ 0.3439, -0.6650]],
[[-0.9470, -0.8022],
[ 0.8864, -0.6687],
[-0.8743, 2.5200]]])
tensor([[[-1.6705, 0.9338, 0.3439],
[-1.0614, 1.2305, -0.6650]],
[[-0.9470, 0.8864, -0.8743],
[-0.8022, -0.6687, 2.5200]]])
归并运算
归并运算可以沿着某一维度进行指定操作或者逐个元素进行操作。
# 按照第0维即按行选取最大值,将每一列的最大值选取出来
# 返回符合操作的数值及其下标
Input:
test = torch.tensor([[1, 8, 3],[4, 5,6]])
num, ind = torch.max(test, 0)
print(num,ind)
Output:
tensor([4, 8, 6]) tensor([1, 0, 1])
数据类型转换
- GPU上Tensor数据与CPU上Tensor数据互相转换,可以通过
data.cpu()
、data.cuda()
或data.to(device)
实现。
# 将CPU上的Tensor数据转换为GPU 方法一
Input:
device = torch.device('cuda:0')
t1 = torch.tensor([1, 2, 3])
print(t1.type())
t1 = t1.to(device)
print(t1.type())
Output:
torch.LongTensor
torch.cuda.LongTensor
# 方法二
t1 = t1.cuda()
# 将GPU上的Tensor数据转换为CPU
t2 = t1.cpu()
- Tensor与Numpy Array之间的转换,CPU上Tensor数据可以通过
data.numpy()
进行转换,但是CPU上Tensor数据要先转换为CPU后在进行Numpy类型转换。
# 将GPU上Tensor数据转换为Numpy类型
t2 = t1.cpu().numpy()
# 将Numpy类型转换为Tensor数据
Input:
t3 = torch.from_numpy(t2)
t3.type()
Output:
torch.LongTensor
- Tensor的基本类型t转换,可以通过
torch.tensor(data, dtype=)
来实现
3. 螺旋数据分类
3.1 加载相应的包
!wget https://raw.githubusercontent.com/Atcold/pytorch-Deep-Learning/master/res/plot_lib.py
import torch
import random
import math
from torch import nn, optim
from IPython import display
from plot_lib import plot_data, plot_model, set_default
3.2 生成数据集
由于PyTorch为数据在GPU上运行提供了便利的操作。因此可以在GPU进行运算,torch.cuda.is_available()
可以用来判断当前环境下GPU是否可用,使用tensor.to(device)
可以将数据转移到GPU上。
# 将所有最开始读取数据时的tensor变量copy一份到device所指定的GPU上去,之后的运算都在GPU上进行。
# 这句话需要写的次数等于需要保存GPU上的tensor变量的个数;
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('device',device)
seed=1234
random.seed(seed)
torch.manual_seed(seed)
构造一个简单的训练数据集,训练数据集样本数为1000,特征数为2,样本标签数为3
N = 1000 # 每类样本的数量
D = 2 # 每个样本的特征维度
C = 3 # 样本的类别
H = 100 # 神经网络里隐层单元的数量
X = torch.zeros(N*C, D).to(device)
Y = torch.zeros(N*C, dtype=torch.long).to(device)
for c in range(C):
index = 0
t = torch.linspace(0, 1, N) # 在[0,1]间均匀的取10000个数,赋给t
# 下面的代码不用理解太多,总之是根据公式计算出三类样本(可以构成螺旋形)
# torch.randn(N) 是得到 N 个均值为0,方差为 1 的一组随机数,注意要和 rand 区分开
inner_var = torch.linspace( (2*math.pi/C)*c, (2*math.pi/C)*(2+c), N) + torch.randn(N) * 0.2
# 每个样本的(x,y)坐标都保存在 X 里
# Y 里存储的是样本的类别,分别为 [0, 1, 2]
for ix in range(N * c, N * (c + 1)):
X[ix] = t[index] * torch.FloatTensor((math.sin(inner_var[index]), math.cos(inner_var[index])))
Y[ix] = c
index += 1
print("Shapes:")
print("X:", X.size())
print("Y:", Y.size())
数据如图所示:
3.3 定义模型
torch.nn.Sequential
是一个Sequential容器,模块将按照在传入构造器的顺序依次被添加到计算图中执行,当模型中只是简单的前馈网络时,即上一层的输出直接作为下一层的输入,可以采用nn.Sequential()模块来快速搭建模型。
nn.optim
中包含了各种常见的优化算法,包括随机梯度下降算法SGD、Adam等。SGD容易陷入局部最优难以跳出,因此相对于SGD,Adam通常会有更好的效果。
# 网络模块
# # 每一个线性模型都包含 weight 和 bias
model = nn.Sequential(
nn.Linear(D, H),
nn.Linear(H, C)
)
model.to(device)
# 损失函数的定义,为交叉熵(cross entropy loss)损失函数
loss_dev = torch.nn.CrossEntropyLoss()
# 梯度下降的学习率
learnrate = 1e-3
decay = 1e-5
# 优化参数定义,为随机梯度下降(stochastic gradient descent)
optimize = torch.optim.SGD(model.parameters(), lr=learnrate, weight_decay=decay)
3.4 模型训练
for t in range(1000):
# 模型的预测输出
y_hat = model(X)
# 求得预测输出的结果值及其下标
score, predict = torch.max(y_hat, 1)
# 模型预测的准确率和损失
acc = (Y==predict).sum().float()/len(Y)
loss = loss_dev(y_hat, Y)
display.clear_output(wait=True)
if t % 100 == 0:
print('epoch:%d loss:%.6f accuracy:%.3f'%(t, loss.item(), acc))
# 反向传播前把梯度置 0
optimize.zero_grad()
# # 反向传播优化
loss.backward()
# 通过step函数来迭代模型参数
optimize.step()
最终运行结果为:
epoch:900 loss:0.772457 accuracy:0.502
模型的结构为
Sequential(
(0): Linear(in_features=2, out_features=100, bias=True)
(1): Linear(in_features=100, out_features=3, bias=True)
)
我们从model获得需要的层,并访问其权重(weight
)和偏差(bias
)
dense = model[0]
print(dense.weight.size())
print(dense.bias)
3.5 改变模型结构
由于之前的模型训练出来的结果不太理想,所以这次改变了模型的结构,增加了ReLu(),ReLu激活函数将模型从线性变成非线性,并将优化算法变为了Adam。
model = nn.Sequential(
nn.Linear(D, H),
nn.ReLU(),
nn.Linear(H, C)
)
model.to(device)
loss_dev = torch.nn.CrossEntropyLoss()
optimize = torch.optim.Adam(model.parameters(), lr=learnrate, weight_decay=decay)
最终运行结果为:
epoch:900 loss:0.220317 accuracy:0.925
模型的结构为
Sequential(
(0): Linear(in_features=2, out_features=100, bias=True)
(1): ReLU()
(2): Linear(in_features=100, out_features=3, bias=True)
)
4. 回归分析
4.1 加载相应的包
import torch
import math
import random
from torch import nn, optim
from matplotlib import pyplot as plt
from IPython import display
from plot_lib import plot_data, plot_model, set_default
4.2 生成数据集
N = 1000 # 每类样本的数量
D = 1 # 每个样本的特征维度
C = 1 # 类别数
H = 100 # 隐层的神经元数量
X = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1).to(device)
y = X.pow(3) + 0.3 * torch.rand(X.size()).to(device)
展示数据:
plt.figure(figsize=(6, 6))
plt.scatter(X.cpu().numpy(), y.cpu().numpy())
plt.axis('equal')
plt.show()
4.3 定义模型与训练
模型与训练过程与螺旋数据分析一样
训练结果:
epoch:900 loss:0.037766
画图展示训练模型结果:
plt.figure(figsize=(6, 6))
plt.scatter(X.cpu().numpy(), y.cpu().numpy())
plt.plot(X.cpu().numpy(), y_hat.data.cpu().numpy(), 'r')
plt.axis('equal')
plt.show()
4.5 改变模型结构
第一种改变
在模型中加入了ReLu(),并将优化算法变为了Adam。
model = nn.Sequential(
nn.Linear(D, H),
nn.ReLU(),
nn.Linear(H, C)
)
model.to(device)
训练结果:
epoch:900 loss:0.006540
第二种改变
在模型中加入了Tanh(),并将优化算法变为了Adam。
model = nn.Sequential(
nn.Linear(D, H),
nn.Tanh(),
nn.Linear(H, C)
)
model.to(device)
训练结果:
epoch:900 loss:0.009457
画图展示两种训练模型结果: