《动手学深度学习PyTorch版》阅读分享四 手写识别小试牛刀(基于CNN)
<div class='showpostmsg'> 本帖最后由 cc1989summer 于 2024-10-4 00:29 编辑<p><p> </p></p>
<p></p>
<p><p>上一篇我们学习了卷积神经网络(CNN)的概念与原理</p></p>
<p></p>
<p><p><a href="https://bbs.eeworld.com.cn/thread-1295074-1-1.html" target="_blank">https://bbs.eeworld.com.cn/thread-1295074-1-1.html</a></p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p>下面是运用卷积神经网络,进行经典的手写数字识别的过程。</p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p>输入是一张28*28像素的黑白照片。经过卷积、池化、激活、全链接,最后输出0~10个数字的概率。</p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p>但说了那么多,理论和实践总有隔阂,不如跑个例子实践实践,加深认识。</p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p><strong>在开始Pytorch进行手写识别前,首先安装Python、Anaconda、与Pytorch,设置好虚拟环境。</strong></p></p>
<p></p>
<p><p>详见:</p></p>
<p></p>
<p><p><a href="https://bbs.eeworld.com.cn/thread-1294276-1-1.html" target="_blank">https://bbs.eeworld.com.cn/thread-1294276-1-1.html</a></p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p>接下来安装Pycharm软件:(免费社区版)</p></p>
<p></p>
<p><p><a href="https://www.jetbrains.com/pycharm/download/?section=windows" target="_blank">https://www.jetbrains.com/pycharm/download/?section=windows</a></p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p>安装完成后需要<strong>关联好前面设置的虚拟环境。</strong></p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p><strong>Anaconda中设置好的虚拟环境:</strong></p></p>
<p></p>
<p><p><img src="https://12.eewimg.cn/bbs/data/attachment/forum/202409/19/230136w55ppee38egpan3k.png" /></p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p><strong>该虚拟环境目录下对应的Python程序</strong></p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p>Pycharm中<strong>关联好前面设置的虚拟环境</strong></p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p>设置完成后,进行测试:</p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><pre></p>
<p><code>import torch</p>
<p>print(torch.__version__)</code></pre></p>
<p></p>
<p><pre></p>
<p>运行结果正常: </p>
<p></pre></p>
<p></p>
<p><p>接下来安装几个基本的软件包。</p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><pre></p>
<p><code>pip install torch torchvision matplotlib</code></pre></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p>缺什么就在Pycharm命令行输入安装指令:</p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p>接下来我们步入正题,进行手写识别。</p></p>
<p></p>
<p><h2>需要用到MNIST数据集</h2></p>
<p></p>
<p><p>MNIST数据集来自美国国家标准与技术研究所, National Institute of Standards and Technology (NIST)。训练集(training set)由来自250个不同人手写的数字构成,其中50%是高中学生,50%来自人口普查局(the Census Bureau)的工作人员。测试集(test set)也是同样比例的手写数字数据,但保证了测试集和训练集的作者集不相交。</p></p>
<p></p>
<p><p>MNIST数据集一共有7万张图片,其中6万张是训练集,1万张是测试集。每张图片是28 × 28 28\times 2828×28的0 − 9 0-90−9的手写数字图片组成。每个图片是黑底白字的形式,黑底用0表示,白字用0-1之间的浮点数表示,越接近1,颜色越白。</p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p>其实就是下面几个文件,接下来的例程会自动帮我们下载。</p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p><span style="font-size:20px;"><strong>第一步,加载MNIST数据集</strong></span></p></p>
<p></p>
<p><pre></p>
<p><code>import torch</p>
<p>from torch.utils.data import DataLoader</p>
<p>from torchvision import datasets, transforms</p>
<p>import matplotlib.pyplot as plt</p>
<p></p>
<p># 数据预处理:将图像转换为张量,并进行标准化</p>
<p>transform = transforms.Compose()</p>
<p></p>
<p># 下载并加载 MNIST 训练集和测试集</p>
<p>train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)</p>
<p>test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)</p>
<p></p>
<p># 加载数据集</p>
<p>train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)</p>
<p>test_loader = DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)</p>
<p></p>
<p># 查看数据集的大小</p>
<p>print(f"训练集大小: {len(train_dataset)}")</p>
<p>print(f"测试集大小: {len(test_dataset)}")</p>
<p></p>
<p># 可视化部分样本</p>
<p>examples = enumerate(train_loader)</p>
<p>batch_idx, (example_data, example_targets) = next(examples)</p>
<p>plt.figure(figsize=(10, 3))</p>
<p>for i in range(6):</p>
<p> plt.subplot(1, 6, i + 1)</p>
<p> plt.imshow(example_data, cmap='gray')</p>
<p> plt.title(f"Label: {example_targets}")</p>
<p> plt.axis('off')</p>
<p>plt.show()</p>
<p></code></pre></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p>以下是运行结果。</p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p>还可以查看更多。</p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p><span style="font-size:20px;"><strong>第二步,构建卷积神经网络</strong></span></p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><pre></p>
<p><code>import torch.nn as nn</p>
<p>import torch.nn.functional as F</p>
<p></p>
<p># 定义 CNN 模型</p>
<p>class CNN(nn.Module):</p>
<p> def __init__(self):</p>
<p> super(CNN, self).__init__()</p>
<p> # 卷积层1: 输入通道为1(灰度图),输出通道为16,卷积核大小为3x3</p>
<p> self.conv1 = nn.Conv2d(1, 16, kernel_size=3)</p>
<p> # 卷积层2: 输入通道为16,输出通道为32,卷积核大小为3x3</p>
<p> self.conv2 = nn.Conv2d(16, 32, kernel_size=3)</p>
<p> # 全连接层1: 输入为32*5*5(展平后的特征图),输出为128</p>
<p> self.fc1 = nn.Linear(32 * 5 * 5, 128)</p>
<p> # 全连接层2: 输入为128,输出为10(10个类别)</p>
<p> self.fc2 = nn.Linear(128, 10)</p>
<p></p>
<p> def forward(self, x):</p>
<p> # 卷积层 + ReLU + 最大池化层</p>
<p> x = F.relu(F.max_pool2d(self.conv1(x), 2))</p>
<p> x = F.relu(F.max_pool2d(self.conv2(x), 2))</p>
<p> # 展平成一维向量</p>
<p> x = x.view(-1, 32 * 5 * 5)</p>
<p> # 全连接层 + ReLU</p>
<p> x = F.relu(self.fc1(x))</p>
<p> # 输出层</p>
<p> x = self.fc2(x)</p>
<p> return x</p>
<p></p>
<p># 实例化模型</p>
<p>model = CNN()</p>
<p>print(model)</p>
<p></code></pre></p>
<p></p>
<p><p>仔细研究代码,正是我们前面讲到的卷积层、池化、激活、全链接。</p></p>
<p></p>
<p><p>运行结果如下:</p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p><span style="font-size:20px;"><strong>第三步,训练模型</strong></span></p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><pre></p>
<p><code>import torch</p>
<p>from torch.utils.data import DataLoader</p>
<p>from torchvision import datasets, transforms</p>
<p>import matplotlib.pyplot as plt</p>
<p>import torch.nn as nn</p>
<p>import torch.nn.functional as F</p>
<p>import torch.optim as optim</p>
<p></p>
<p></p>
<p># 数据预处理:将图像转换为张量,并进行标准化</p>
<p>transform = transforms.Compose()</p>
<p></p>
<p># 下载并加载 MNIST 训练集和测试集</p>
<p>train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)</p>
<p>test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)</p>
<p></p>
<p># 加载数据集</p>
<p>train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)</p>
<p>test_loader = DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)</p>
<p></p>
<p></p>
<p></p>
<p># 定义 CNN 模型</p>
<p>class CNN(nn.Module):</p>
<p> def __init__(self):</p>
<p> super(CNN, self).__init__()</p>
<p> # 卷积层1: 输入通道为1(灰度图),输出通道为16,卷积核大小为3x3</p>
<p> self.conv1 = nn.Conv2d(1, 16, kernel_size=3)</p>
<p> # 卷积层2: 输入通道为16,输出通道为32,卷积核大小为3x3</p>
<p> self.conv2 = nn.Conv2d(16, 32, kernel_size=3)</p>
<p> # 全连接层1: 输入为32*5*5(展平后的特征图),输出为128</p>
<p> self.fc1 = nn.Linear(32 * 5 * 5, 128)</p>
<p> # 全连接层2: 输入为128,输出为10(10个类别)</p>
<p> self.fc2 = nn.Linear(128, 10)</p>
<p></p>
<p> def forward(self, x):</p>
<p> # 卷积层 + ReLU + 最大池化层</p>
<p> x = F.relu(F.max_pool2d(self.conv1(x), 2))</p>
<p> x = F.relu(F.max_pool2d(self.conv2(x), 2))</p>
<p> # 展平成一维向量</p>
<p> x = x.view(-1, 32 * 5 * 5)</p>
<p> # 全连接层 + ReLU</p>
<p> x = F.relu(self.fc1(x))</p>
<p> # 输出层</p>
<p> x = self.fc2(x)</p>
<p> return x</p>
<p></p>
<p># 实例化模型</p>
<p>model = CNN()</p>
<p></p>
<p># 定义损失函数和优化器</p>
<p>criterion = nn.CrossEntropyLoss()</p>
<p>optimizer = optim.Adam(model.parameters(), lr=0.001)</p>
<p></p>
<p># 将模型移动到 GPU(如果可用)</p>
<p>device = torch.device("cuda" if torch.cuda.is_available() else "cpu")</p>
<p>model.to(device)</p>
<p></p>
<p># 训练模型</p>
<p>epochs = 5</p>
<p>for epoch in range(epochs):</p>
<p> running_loss = 0.0</p>
<p> for images, labels in train_loader:</p>
<p> images, labels = images.to(device), labels.to(device)</p>
<p> # 前向传播</p>
<p> outputs = model(images)</p>
<p> loss = criterion(outputs, labels)</p>
<p> # 反向传播和优化</p>
<p> optimizer.zero_grad()</p>
<p> loss.backward()</p>
<p> optimizer.step()</p>
<p></p>
<p> running_loss += loss.item()</p>
<p> print(f"Epoch [{epoch + 1}/{epochs}], Loss: {running_loss / len(train_loader):.4f}")</p>
<p>print("训练完成!")</p>
<p></code></pre></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p>经过5次训练,损失函数越来越低,说明正确率提高了</p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><div style="text-align: left;"></div></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p><span style="font-size:20px;"><strong>第四步,测试模型</strong></span></p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p>测试环节,我们可以自己画一个图去进行识别,也可以从训练集里抽一个图去测试。</p></p>
<p></p>
<p><pre></p>
<p><code># 从测试集中取出一个样本</p>
<p>example_data, example_target = next(iter(test_loader))</p>
<p>example_data = example_data.to(device)</p>
<p></p>
<p># 使用模型进行预测</p>
<p>model.eval()</p>
<p>with torch.no_grad():</p>
<p> output = model(example_data)</p>
<p></p>
<p># 可视化预测结果</p>
<p>plt.figure(figsize=(10, 3))</p>
<p>for i in range(6):</p>
<p> plt.subplot(1, 6, i + 1)</p>
<p> plt.imshow(example_data.cpu(), cmap='gray')</p>
<p> plt.title(f"预测: {torch.argmax(output).item()}")</p>
<p> plt.axis('off')</p>
<p>plt.show()</p>
<p></code></pre></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p>运行结果如以下,百发百中有木有?</p></p>
<p></p>
<p><p>准确率98.93%</p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p>而如果我们把训练模型这段程序拿掉,正确率一下子就很低了,基本全错。</p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><pre></p>
<p><code># 训练模型</p>
<p>epochs = 5</p>
<p>for epoch in range(epochs):</p>
<p> running_loss = 0.0</p>
<p> for images, labels in train_loader:</p>
<p> images, labels = images.to(device), labels.to(device)</p>
<p> # 前向传播</p>
<p> outputs = model(images)</p>
<p> loss = criterion(outputs, labels)</p>
<p> # 反向传播和优化</p>
<p> optimizer.zero_grad()</p>
<p> loss.backward()</p>
<p> optimizer.step()</p>
<p></p>
<p> running_loss += loss.item()</p>
<p> print(f"Epoch [{epoch + 1}/{epochs}], Loss: {running_loss / len(train_loader):.4f}")</p>
<p>print("训练完成!")</p>
<p></p>
<p></p>
<p></code></pre></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p> </p></p>
<p></p>
<p><p>本次的分享就到这里。</p></p>
<p></p>
</div><script> var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;" style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
if(parseInt(discuz_uid)==0){
(function($){
var postHeight = getTextHeight(400);
$(".showpostmsg").html($(".showpostmsg").html());
$(".showpostmsg").after(loginstr);
$(".showpostmsg").css({height:postHeight,overflow:"hidden"});
})(jQuery);
} </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script> <p>如果我们把训练模型这段程序拿掉,正确率一下子就很低了,基本全错,好吧</p>
Jacktang 发表于 2024-10-6 09:29
如果我们把训练模型这段程序拿掉,正确率一下子就很低了,基本全错,好吧
<p>目前还没搞清楚,怎么把训练过的模型保存固定下来,后面做识别直接拿来用,而不用每次识别都要重新从头训练一遍,训练太耗时了(>2分钟)。</p>
<p>保存整个模型(序列化模型和权重,依赖python环境,不灵活)</p>
<p>torch.save(model, "model.pth")</p>
<p>加载整个模型</p>
<p>model = torch.load("model.pth")</p>
<p> </p>
<p>只保存权重文件(模型在代码里写好,保存和加载模型和优化器权重,更灵活)</p>
<p>save_dict = {}<br />
save_dict = model.state_dict()<br />
save_dict = optimizer.state_dict()<br />
torch.save(save_dict, "state_dict.pth")</p>
<p> </p>
<p>加载权重文件</p>
<p>checkpoint = torch.load("state_dict.pth")<br />
model.load_state_dict(checkpoint['model_state_dict'])</p>
<p>optimizer.load_state_dict(checkpoint['optimizer_state_dict'])</p>
<p>训练好的模型是可以保存的,下次使用直接load,无需重新训练</p>
MioChan 发表于 2024-10-12 17:50
保存整个模型(序列化模型和权重,依赖python环境,不灵活)
torch.save(model, "model.pth")
加 ...
<p>谢谢,很详细的解答,我跑跑试试。<img height="48" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/facebook/loveliness.gif" width="48" /></p>
xinmeng_wit 发表于 2024-10-12 20:50
训练好的模型是可以保存的,下次使用直接load,无需重新训练
<p>好的,谢谢解答。<img height="50" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/facebook/wanwan33.gif" width="58" /></p>
<p>感谢分享呀。。。。。。。。。。。。。。。。。。。。。。<img height="48" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/facebook/loveliness.gif" width="48" /></p>
hellokitty_bean 发表于 2024-10-14 09:19
感谢分享呀。。。。。。。。。。。。。。。。。。。。。。
<p>嘿嘿,大家共同进步。{:1_138:}</p>
页:
[1]