深度学习week1
Pytorch基础
数据定义
不同类型张量的创建方法
#数
x = torch.tensor(666)
print(x)
#输出为
#tensor(666)
#数组
x = torch.tensor([1,2,3,4,5,6])
print(x)
#输出为
#tensor([1, 2, 3, 4, 5, 6])
#矩阵
x = torch.ones(2,3)
print(x)
#输出为
#tensor([[1., 1., 1.],
# [1., 1., 1.]])
#高维数组
x = torch.ones(2,3,4)
print(x)
#输出为
#tensor([[[1., 1., 1., 1.],
# [1., 1., 1., 1.],
# [1., 1., 1., 1.]],
# [[1., 1., 1., 1.],
# [1., 1., 1., 1.],
# [1., 1., 1., 1.]]])
一些特殊的张量创建方法
#空张量
x = torch.empty(2,3)
#随机数
x = torch.rand(2,3)
#全为1
x = torch.ones(2,3)
print(x)
#输出为
#tensor([[1., 1., 1.],
# [1., 1., 1.]])
#全为0
x = torch.zeros(2,3)
print(x)
#输出为
#tensor([[0., 0., 0.],
# [0., 0., 0.]])
#根据原有张量的结构构造
y = x.new_ones(2,3)
print(y)
#输出为
#tensor([[1., 1., 1.],
# [1., 1., 1.]])
z = torch.randn_like(y)
print(z)
#输出为
#tensor([[ 1.0164, -0.5973, -0.7917],
# [ 0.7803, 0.7656, 0.1471]])
#指定元素数据类型
x = torch.ones(2,3,dtype=torch.long)
y = torch.ones(2,3)
print(x.dtype, y.dtype)
#输出为
#torch.int64 torch.float32
#a和b区间内按照步长c取数
x = torch.arange(1, 3, 0.5)
print(x)
#输出为
#tensor([1.0000, 1.5000, 2.0000, 2.5000])
#a和b之间均匀取c个数
x = torch.linspace(0, 3, 5)
print(x)
#输出为
#tensor([0.0000, 0.7500, 1.5000, 2.2500, 3.0000])
pytorch中有两种不同的随机创建张量的方法rand和randn,这两种方法的区别是rand是按照均匀分布取数的,randn是按照正态分布取数的,以下为使用两种方法取随机数构成张量后绘制而成的图像。
一些常用方法
输出信息
x = torch.rand(2,3)
#输出张量的大小
print(x.size(0), x.size(1), x.size(), sep=' -- ')
#输出为
#4 -- 6 -- torch.Size([4, 6])
print(x.shape)
#输出为
#torch.Size([4, 6])
#输出元素个数
print(x.numel())
#输出为
#24
索引
#某一元素
print(x[0][1])
#一行
print(x[0])
#一列
print(x[:,0])
#一个区域,索引包括左边不包括右边
print(x[0:2,2:4])
#每n个元素取一个
print(x[0,::2])
拼接
#用cat方法连接张量
a = torch.Tensor([[1, 2, 3, 4]])
b = torch.Tensor([[5, 6, 7, 8]])
#0方向即Y方向
print( torch.cat((a,b), 0))
#1方向即X方向
print( torch.cat((a,b), 1))
#多个张量连接
print( torch.cat((a,b,a), 0))
#输出为
#tensor([[1., 2., 3., 4.],
# [5., 6., 7., 8.]])
#tensor([[1., 2., 3., 4., 5., 6., 7., 8.]])
#tensor([[1., 2., 3., 4.],
# [5., 6., 7., 8.],
# [1., 2., 3., 4.]])
转置
x = torch.rand(2,3)
print(x.t())
#转置,将第a维和第b维交换
x = torch.rand(2,3,4)
print(x.size())
print(torch.transpose(x,0,2).size())
#输出为
#torch.Size([2, 3, 4])
#torch.Size([4, 3, 2])
螺旋数据分类
构造螺旋形分布的数据
N = 1000 # 每类样本的数量
D = 2 # 每个样本的特征维度
C = 3 # 样本的类别
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)
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
构造得到的数据绘制在图像上如下图所示。
使用线性模型分类
H = 100 # 神经网络里隐层单元的数量
learning_rate = 1e-3
lambda_l2 = 1e-5
# nn 包用来创建线性模型
# 每一个线性模型都包含 weight 和 bias
model = nn.Sequential(
nn.Linear(D, H),
nn.Linear(H, C)
)
# nn 包含多种不同的损失函数,这里使用的是交叉熵(cross entropy loss)损失函数
criterion = torch.nn.CrossEntropyLoss()
# 这里使用 optim 包进行随机梯度下降(stochastic gradient descent)优化
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay=lambda_l2)
# 开始训练
for t in range(1000):
# 把数据输入模型,得到预测结果
y_pred = model(X)
# 计算损失和准确率
loss = criterion(y_pred, Y)
score, predicted = torch.max(y_pred, 1)
acc = (Y == predicted).sum().float() / len(Y)
print('[EPOCH]: %i, [LOSS]: %.6f, [ACCURACY]: %.3f' % (t, loss.item(), acc))
display.clear_output(wait=True)
# 反向传播前把梯度置 0
optimizer.zero_grad()
# 反向传播优化
loss.backward()
# 更新全部参数
optimizer.step()
下图是训练之后的分类效果,可以看出在仅使用线性层的情况下分类的线大体上是呈直线分布的,对于螺旋形分布的数据分类并不理想。
使用两层神经网络进行分类
# 和上面方法不同的是,在模型的两层之间加入了一个 ReLU 激活函数,其他部分完全相同
model = nn.Sequential(
nn.Linear(D, H),
nn.ReLU(),
nn.Linear(H, C)
)
下图是使用激活函数训练之后的分类效果,可以看出此时分类的界限为曲线,且有较好的分类效果。
问题总结
AlexNet有哪些特点?为什么可以比LeNet取得更好的性能?
LeNet是由卷积神经网络之父Yan LeCun提出的,它确立了CNN的基本结构由卷积层、池化层、激活层等构成。而AlexNet是在LeNet的基本结构上进行了网络结构的加深,从而达到更好的效果。
AlexNet的特点有:
1.网络结构更深
2.使用ReLu作为激活函数而不是sigmoid函数
3.使用多GPU进行训练
等。
AlexNet能够比LeNet取得更好的性能的原因有:
1.使用了计算效率更高且更不容易导致过拟合的ReLu激活函数
2.使用了DropOut技术使得全连接层的一些神经元不再计算降低了运算量且更不容易过拟合
3.使用最大池化代替平均池化避免了平均池化造成的模糊化下过
4.使用了多GPU进行双线程并行训练提高了效率
等。
激活函数有哪些作用?
激活函数是用于增加神经网络数据复杂度的函数,一般被添加在一层神经网络之后,对该层的输出进行运算。
激活函数使得神经网络能够处理非线性问题,提高了神经网络的表达能力。神经网络中每一层的输入到输出是一个线性求和的过程,在没有激活函数的情况下,每一层的输出仅仅是输入的一个线性变换,无论构造多么复杂的网络,最终的输出只是输入的线性组合,无法解决复杂的问题。激活函数在层与层之间引入了非线性元素,从而能让神经网络应用到非线性场景中。
梯度消失现象是什么?
由于反向传播算法中各层之间的权重系数是通过导数来修正的。根据链式法则可以知道,当导数值小于1时,求导的次数越多,值越趋近于0。这导致了越靠近输入层,导数越接近0,修正的效果也越差。
梯度消失就是指当神经网络层数很多时,越靠近输入层的层与层之间的权重由于导数值趋近于0而无法得到有效修正,导致神经网络效果不好。ReLu函数大于0的部分导数为1,一定程度上改善了梯度消失问题。
神经网络是更宽好还是更深好?
神经网络的深度提高带来了更强大的表达能力和逐层的特征学习。
神经网络的宽度提高让神经网络有更好的泛化能力,每一层学习到更加丰富的特征。
为什么要使用Softmax?
softmax与haodmax相对,hardmax的目标是求得一个最大值,而不关心其他值;softmax则是求出取每个值的概率,并且取概率最大的值。
softmax函数的目的是在神经网络的结果中为每个类别赋一个概率值而不是单纯地确定一个结果,从而更加有利于构建合理的损失来优化神经网络。
SGD 和 Adam 哪个更有效?
SGD(随机梯度下降)
- 优点:与BGD(全梯度下降)相比,更新参数时速度快;SGD可能会跳出局部极小值点,因为在极小值的时候它计算梯度是随机选择的一个样本,这个梯度未必是0;结果具有很好的一般性。
- 缺点:SGD每次的更新并不是向着整体最优化方向,虽然速度快,准确度下降,并不是全局最优。
Adam
- 优点:有更好的收敛性,易于调参,学习率自适应
- 缺点:计算复杂,内存占比高,泛化性差