2024年秋季《软件工程原理与实践》实验报告
一、实验内容
1.1 测试——运行基本的Python代码
基本数据类型
数字:整数和浮点数的工作方式与其他语言的一样:
布尔值: Python 实现了布尔逻辑的所有常用运算符,但使用英文单词而不是符号(&&
、||
等):
字符串: Python 对字符串有很好的支持:
1.2 PyTorch 基础练习实验步骤
1. 什么是 PyTorch?
PyTorch是一个强大的Python库,专为深度学习和科学计算而设计。它提供了以下两大核心功能:
- GPU加速的张量计算:利用GPU进行高效的数值计算,极大地加速了模型训练和数据处理。
- 基于自动求导的深度神经网络构建:支持灵活构建和训练深度学习模型,使得研究者能够更轻松地进行实验和创新。
2. 定义数据
实验代码与结果:
创建张量的多种方式
张量是PyTorch的基本数据结构,可以看作是一个多维数组。以下是不同维度张量的创建方法及其应用:
-
标量张量
x = torch.tensor(666) print(x) # 输出: tensor(666)
- 思考: 这个简单的标量张量演示了PyTorch张量的基本构造。从这个简单的构造中可以看到,PyTorch以直观且一致的方式处理数值,便于在更复杂的模型中快速应用。
-
一维张量(向量)
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) # 输出: 形状为 (2, 3, 4) 的张量
- 思考: 高维张量在处理图像、视频等复杂数据时尤为重要。它们的灵活性让我意识到,随着数据维度的增加,所需的计算资源和算法复杂性也会相应提高,这促使我思考如何在高维空间中有效提取特征和信息。
创建不同类型的张量
# 创建空张量
x = torch.empty(5, 3)
print(x) # 输出: 具有未定义值的张量
# 随机初始化张量
x = torch.rand(5, 3)
print(x) # 输出: 随机数填充的张量
# 创建全0张量
x = torch.zeros(5, 3, dtype=torch.long)
print(x) # 输出: 全为0的长整型张量
- 思考: 使用不同的创建方法可以为不同的任务提供便利。例如,
torch.empty
虽然提供未定义值,但它可以用于节省内存,而torch.rand
则适用于初始化神经网络权重。
3. 定义操作
实验代码与结果:
基本运算
张量运算是PyTorch的核心。以下是一些常见的操作示例:
-
矩阵与向量乘法
m = torch.Tensor([[2, 5, 3, 7], [4, 2, 1, 9]]) v = torch.arange(1, 5).float() result = m @ v print(result) # 输出: tensor([49., 47.])
- 思考: 矩阵乘法是深度学习中最基本的操作之一。尤其是在反向传播算法中,矩阵乘法的有效实现对梯度计算至关重要。
-
矩阵转置与拼接
print(m.t()) # 输出: 转置后的矩阵 a = torch.Tensor([[1, 2, 3, 4]]) b = torch.Tensor([[5, 6, 7, 8]]) print(torch.cat((a, b), 0)) # 在Y方向拼接 print(torch.cat((a, b), 1)) # 在X方向拼接
其他操作示例
# 创建并展示等间隔的数
linspace = torch.linspace(3, 8, 20)
print(linspace)
# 使用matplotlib绘制直方图
from matplotlib import pyplot as plt
plt.hist(torch.randn(1000).numpy(), 100)
plt.show()
- 思考:
torch.linspace
在生成均匀分布的数值时非常有用,尤其是在模型训练中需要设定学习率或其他超参数时。通过数据可视化工具,如直方图,能够直观地理解数据分布,如实验中可以清楚看到当数据非常非常多的时候,正态分布会体现的非常明显。
1.3 螺旋数据分类实验步骤
实验代码与结果:
-
初始化重要参数
-
构建线性模型分类:
- 构建两层神经网络分类:
实验目的
本实验旨在通过螺旋数据集分类,探索线性模型与神经网络模型的性能差异,深刻理解激活函数的作用。
1. 下载绘图函数
首先,需要下载绘图函数以便可视化数据和模型。
!wget https://raw.githubusercontent.com/Atcold/pytorch-Deep-Learning/master/res/plot_lib.py
2. 引入库与初始化参数
import random
import torch
from torch import nn, optim
import math
from IPython import display
from plot_lib import plot_data, plot_model, set_default
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('device: ', device)
seed = 12345
random.seed(seed)
torch.manual_seed(seed)
N = 1000 # 每类样本数量
D = 2 # 特征维度
C = 3 # 类别数
H = 100 # 隐层单元数量
- 解读: 初始化随机种子确保实验的可复现性。特征维度和类别数的选择直接影响后续模型的复杂性,使用GPU加速则能大幅提高计算效率。
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
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())
plot_data(X, Y)
- 解读: 数据生成的方式使得样本呈现出螺旋形状。每个类别都有其独特的分布,这种结构使得简单的线性模型难以适应。通过可视化,我们能直观感受到数据的复杂性和分类的挑战。
4. 构建线性模型
learning_rate = 1e-3
lambda_l2 = 1e-5
model = nn.Sequential(
nn.Linear(D, H),
nn.Linear(H, C)
)
model.to(device)
criterion = torch.nn.CrossEntropyLoss()
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)
optimizer.zero_grad()
loss.backward()
optimizer.step()
-
解读: 在这个线性模型中,准确率仅达50%。这表明线性模型对于螺旋数据的复杂结构缺乏足够的表达能力。损失函数与准确率的监控过程有助于理解模型的学习动态。
5. 构建两层神经网络
model = nn.Sequential(
nn.Linear(D, H),
nn.ReLU(),
nn.Linear(H, C)
)
model.to(device)
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(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)
optimizer.zero_grad()
loss.backward()
optimizer.step()
- 解读: 加入ReLU激活函数后,模型的准确率跃升至94.9%。这充分展示了非线性激活函数在捕捉复杂模式中的重要性。通过层间引入激活函数,模型可以更好地拟合数据分布,实现更高的分类效果。
6. 可视化模型
print(model)
plot_model(X, Y, model)
二、问题总结与体会
2.1 实验中遇到的问题及解决方法
-
矩阵乘法类型不匹配
在进行矩阵乘法时,发现torch.arange(1, 5)
创建的张量为LongTensor
类型,而参与计算的矩阵m
为FloatTensor
类型。此类型不匹配导致无法进行有效的矩阵运算。为解决此问题,将张量v
转换为浮点数类型是必要的,通过调用v = torch.arange(1, 5).float()
,确保所有参与计算的张量具有相同的类型。
-
绘图函数缺少依赖文件
下载的绘图函数plot_lib.py
未包含名为ziegler.png
的图像文件,导致在绘图时出现错误。为解决此问题,需要修改plot_lib.py
文件的第40行代码,使其正确读取图片:zieger = plt.imread('ziegler.png')
。此外,还需将ziegler.png
图片放置在与plot_lib.py
同一文件夹内,以便绘图函数能够正确加载并使用该图像。2.2 问题总结与解答
-
AlexNet有哪些特点?为什么可以⽐LeNet取得更好的性能?
AlexNet引入了多个卷积层和ReLU激活函数,使用了较大的卷积核,并通过最大池化减少特征维度。此外,它采用了数据增强和Dropout技术来防止过拟合。这些设计使其能够有效捕捉特征并提高性能,相较于LeNet在处理复杂数据时具有更高的表达能力。 -
激活函数有哪些作⽤?
激活函数引入非线性,使得神经网络能够学习和表达复杂的特征。常见的激活函数如ReLU、Sigmoid和Tanh,分别具有不同的优势,如ReLU在深度网络中缓解了梯度消失的问题。 -
梯度消失现象是什么?
梯度消失是指在反向传播过程中,随着层数增加,梯度逐渐趋近于零,导致权重无法有效更新。这通常发生在使用Sigmoid或Tanh等饱和激活函数时。 -
神经网络是更宽好还是更深好?
这取决于具体任务和数据。更深的网络可以捕捉更复杂的特征,而更宽的网络能够在每层中提取更多信息。现代研究表明,深度和宽度的结合往往能取得更好的效果。 -
为什么要使用Softmax?
Softmax函数将模型输出的原始分数转换为概率分布,使得每个类别的预测值在0到1之间,并且所有预测值之和为1。这使得它适合于多类分类问题,便于进行模型评估和选择。 -
SGD 和 Adam 哪个更有效?
Adam优化器通常被认为比SGD更有效,因为它结合了动量和自适应学习率,有助于更快收敛,并且在处理稀疏数据时表现良好。然而,SGD在某些情况下也能提供更好的泛化能力,尤其是在大规模数据集上。
-