上一节中,我们使用autograd的包来定义模型并求导。本节中,我们将使用torch.nn包来构建神经网络。
一个nn.Module包含各个层和一个forward(input)方法,该方法返回output.
上图是一个简单的前馈神经网络。它接受一个输入。然后一层接着一层地传递。最后输出计算的结果。
神经网络模型的训练过程
神经网络的典型训练过程如下:
定义包含一些可学习的参数(或者叫做权重)的神经网络模型。
在数据集上迭代。
通过神经网络处理输入。
计算损失函数(输出结果和正确值的差值大小)。
将梯度反向传播回网络的权重等参数。
更新网络的权重。主要使用以下的更新原则:weight = weight - learining_rate*gradient.
完整的代码如下(跟官方比,做了详细的中文注释):
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torch.utils.data.dataloader as Data
import torchvision
import matplotlib.pyplot as plt
train_data = torchvision.datasets.MNIST(root='./mnist' ,train=True ,transform=torchvision.transforms.ToTensor(),download=True )
test_data = torchvision.datasets.MNIST(root='./mnist' ,train=False ,transform=torchvision.transforms.ToTensor())
print ("train_data:" ,train_data.data.size())
print ("train_labels:" ,train_data.targets.size())
print ("test_data:" ,test_data.data.size())
train_loader = Data.DataLoader(dataset=train_data, batch_size=50 , shuffle=True )
test_loader = Data.DataLoader(dataset=test_data, batch_size=500 , shuffle=False )
class CNN (nn.Module):
def __init__ (self ):
super (CNN, self).__init__()
self.conv1 = nn.Conv2d(1 , 16 , 5 , 1 , 2 )
self.conv2 = nn.Conv2d(16 , 32 , 5 , 1 , 2 )
self.fc1 = nn.Linear(32 * 7 * 7 , 128 )
self.fc2 = nn.Linear(128 , 10 )
def forward (self, x ):
conv1_out = F.max_pool2d(F.relu(self.conv1(x)), (2 , 2 ))
conv2_out = F.max_pool2d(F.relu(self.conv2(conv1_out)), 2 )
res = conv2_out.view(conv2_out.size(0 ), -1 )
fc1_out = F.relu(self.fc1(res))
out = self.fc2(fc1_out)
return F.log_softmax(out),fc1_out
cnn = CNN()
cnn = cnn.cuda()
print (cnn)
def plot_with_labels (lowDWeights, labels ):
plt.cla()
X, Y = lowDWeights[:, 0 ], lowDWeights[:, 1 ]
for x, y, s in zip (X, Y, labels):
c = cm.rainbow(int (255 * s / 9 ));
plt.text(x, y, str (s),color=c,fontdict={'weight' : 'bold' , 'size' : 9 })
plt.xlim(X.min (), X.max ());
plt.ylim(Y.min (), Y.max ());
plt.title('Visualize last layer' );
plt.show();
plt.pause(0.01 )
loss_func = torch.nn.CrossEntropyLoss()
optimizer = optim.Adam(cnn.parameters(), lr=0.01 )
def train (epoch ):
print ('epoch {}' .format (epoch))
train_loss = 0
train_acc = 0
for step, (batch_x, batch_y) in enumerate (train_loader):
batch_x = batch_x.cuda()
batch_y = batch_y.cuda()
out,_ = cnn(batch_x)
loss = loss_func(out, batch_y)
train_loss += loss.item()
pred = torch.max (out, 1 )[1 ]
train_correct = (pred == batch_y).sum ()
train_acc += train_correct.item()
if step % 20 == 0 :
print ('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}' .format (epoch, step * len (batch_x), len (train_loader.dataset),100. * step / len (train_loader), loss.item()))
optimizer.zero_grad()
loss.backward()
optimizer.step()
print ('Train Loss: {:.6f}, Acc: {:.6f}' .format (train_loss / (len (train_data)), train_acc / (len (train_data))))
from matplotlib import cm
try :
from sklearn.manifold import TSNE; HAS_SK = True
except :
HAS_SK = False ; print ('Please install sklearn for layer visualization' )
def test ():
cnn.eval ()
eval_loss = 0
eval_acc = 0
plt.ion()
with torch.no_grad():
for step, (batch_x, batch_y) in enumerate (test_loader):
batch_x = batch_x.cuda()
batch_y = batch_y.cuda()
out,last_layer = cnn(batch_x)
loss = loss_func(out, batch_y)
eval_loss += loss.item()
pred = torch.max (out, 1 )[1 ]
num_correct = (pred == batch_y).sum ()
eval_acc += num_correct.item()
if step % 100 == 0 :
tsne = TSNE(perplexity=30 , n_components=2 , init='pca' , n_iter=5000 )
plot_only = 500
low_dim_embs = tsne.fit_transform(last_layer.cpu().data.numpy()[:plot_only, :])
labels = batch_y.cpu().numpy()[:plot_only]
plot_with_labels(low_dim_embs, labels)
print ('Test Loss: {:.6f}, Accuracy: {}/{} ({:.2f}%' .format (eval_loss / (len (test_data)),eval_acc, len (test_data) ,100. *eval_acc / (len (test_data))))
plt.ioff()
for epoch in range (1 , 21 ):
train(epoch)
test()
除了上面代码段中的实现,你可能还需要了解以下知识:
损失函数与autograd反向传播
一个损失函数需要一对输入:模型输出和目标,然后计算一个值来评估输出距离目标有多远。
根据之前学习的理论,一般是计算y和y'的交叉熵.
例如,对于线性拟合案例,损失函数就是均方误差,即MSE.
将输出:
现在,如果你跟随损失到反向传播路径,可以使用它的 .grad_fn 属性,你将会看到一个这样的计算图:
所以,当我们调用 loss.backward(),整个图都会微分,而且所有的在图中的requires_grad=True 的张量将会让他们的 grad 张量累计梯度。
为了演示,我们将跟随以下步骤来跟踪反向传播。
输出:
为了实现反向传播损失,我们所有需要做的事情仅仅是使用 loss.backward()。你需要清空现存的梯度,要不梯度将会和现存的梯度累计到一起。
现在我们调用 loss.backward()
,然后看一下 con1
的偏置项在反向传播之前和之后的变化。
输出:
上面介绍了如何使用损失函数 。
更新神经网络参数
最简单的更新规则就是随机梯度下降 :weight = weight - learning_rate * gradient
。
复杂的神经网络参数可这样使用:
t-SNE:数据可视化
本节摘自 这里 。
数据降维与可视化——t-SNE
t-SNE 是目前来说效果最好的数据降维与可视化方法,但是它的缺点也很明显,比如:占内存大,运行时间长。但是,当我们想要对高维数据进行分类,又不清楚这个数据集有没有很好的可分性(即同类之间间隔小,异类之间间隔大),可以通过 t-SNE 投影到 2 维或者 3 维的空间中观察一下。如果在低维空间中具有可分性,则数据是可分的;如果在高维空间中不具有可分性,可能是数据不可分,也可能仅仅是因为不能投影到低维空间。
t-distributed Stochastic Neighbor Embedding(t-SNE)
t-SNE(TSNE)将数据点之间的相似度转换为概率。原始空间中的相似度由高斯联合概率表示,嵌入空间的相似度由“学生 t 分布”表示。
虽然 Isomap,LLE 和 variants 等数据降维和可视化方法,更适合展开单个连续的低维的 manifold。但如果要准确的可视化样本间的相似度关系,如对于下图所示的 S 曲线(不同颜色的图像表示不同类别的数据),t-SNE 表现更好。因为t-SNE 主要是关注数据的局部结构。
通过原始空间和嵌入空间的联合概率的 Kullback-Leibler(KL)散度来评估可视化效果的好坏,也就是说用有关 KL 散度的函数作为 loss 函数,然后通过梯度下降最小化 loss 函数,最终获得收敛结果。
使用 t-SNE 的缺点大概是:
t-SNE 的计算复杂度很高,在数百万个样本数据集中可能需要几个小时,而 PCA 可以在几秒钟或几分钟内完成
Barnes-Hut t-SNE 方法(下面讲)限于二维或三维嵌入。
算法是随机的,具有不同种子的多次实验可以产生不同的结果。虽然选择 loss 最小的结果就行,但可能需要多次实验以选择超参数。
全局结构未明确保留。这个问题可以通过 PCA 初始化点(使用init ='pca')来缓解。
其参数及描述如下:
其具有的属性有:
其Method有:
下列代码详细解释了MNIST数据集可进行的可视化:
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异