Pytorch实战(二):VGG神经网络

news2024/9/23 7:25:09

文章目录

  • 一、诞生背景
  • 二、VGG网络结构
    • 2.1VGG块
    • 2.2网络运行流程
    • 2.3总结
  • 三、实战
    • 3.1搭建模型
    • 3.2模型训练
    • 3.3训练结果可视化
    • 3.4模型参数初始化


一、诞生背景

在这里插入图片描述
在这里插入图片描述
  从网络结构中可看出,所有版本VGG均全部使用3×3大小、步长为1的小卷积核,3×3卷积核同时也是最小的能够表示上下左右中心的尺寸。
在这里插入图片描述
假设输入图像尺寸为假输入为5×5,使用2次3×3卷积后最终得到1×1的特征图,那么这个1×1的特征图的感受野为5×5。这和直接使用一个5×5卷积核得到1×1的特征图是一样的。也就是说2次3×3卷积可以代替一次5×5卷积同时,并且,2次3×3卷积的参数更少(2×3×3=18<5×5=25)而且会经过两次激活函数进行非线性变换,学习能力会更好。同样的3次3×3卷积可以替代一次7×7的卷积。并且,步长为1可以不会丢失信息,网络深度增加可以提高网络性能。

二、VGG网络结构

2.1VGG块

在这里插入图片描述
一个VGG_bolck的组成:

  • 带填充以保持分辨率的卷积层:指对输入特征图卷积操作时会带有填充,使得只改变通道数而不改变图像高、宽。
  • 非线性激活函数ReLU:卷积操作后将特征图输入激活函数,提供使之具有非线性性。
  • 池化层、最大池化层:使用最大池化函数,不改变图像通道数,但会缩小图像尺寸。

对于卷积层、池化层有:

  • 卷积层:使用3x3大小的卷积核,padding=1,stride=1,output=(input-3+2×1)/1+1=input,使得特征图尺寸不变。
  • 池化层:使用2x2大小的核,padding=0,stride=2,output=(input-2)/2+1=1/2input,特征图尺寸减半。

2.2网络运行流程

输入层:输入大小为 ( 224 , 224 , 3 ) (224,224,3) (224,224,3)的RGB图像。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.3总结

在这里插入图片描述

三、实战

3.1搭建模型

import torch
from torch import nn
from torchsummary import summary

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")


class VGG16(nn.Module):
    def __init__(self):
        super(VGG16, self).__init__()
        self.block1 = nn.Sequential(
            # 本案例中使用FashionMNIST数据集,所以输入通道数为1
            nn.Conv2d(in_channels=1, out_channels=64, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.block2 = nn.Sequential(
            nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.block3 = nn.Sequential(
            nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.block4 = nn.Sequential(
            nn.Conv2d(in_channels=256, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.block5 = nn.Sequential(
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.fc = nn.Sequential(
            nn.Flatten(),
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, 4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, 10)
        )

    def forward(self, x):
        x = self.block1(x)
        x = self.block2(x)
        x = self.block3(x)
        x = self.block4(x)
        x = self.block5(x)
        x = self.fc(x)
        return x


model = VGG16().to(device)
summary(model, (1, 224, 224))

在这里插入图片描述

3.2模型训练

  使用模板:

import torch
from torch import nn
import copy
import time
from torchvision.datasets import FashionMNIST
from torchvision import transforms
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import torch.utils.data as Data
train_data = FashionMNIST(root='./', train=True, download=True,
                               transform=transforms.Compose([transforms.Resize(size=224), transforms.ToTensor()]))
def train_val_process(train_data, batch_size=128):
    train_data, val_data = Data.random_split(train_data,
                                             lengths=[round(0.8 * len(train_data)), round(0.2 * len(train_data))])
    train_loader = Data.DataLoader(dataset=train_data,
                                   batch_size=batch_size,
                                   shuffle=True,
                                   num_workers=8)
    val_loader = Data.DataLoader(dataset=val_data,
                                 batch_size=batch_size,
                                 shuffle=True,
                                 num_workers=8)
    return train_loader, val_loader

train_dataloader, val_dataloader = train_val_process(train_data, batch_size=64)

def train(model, train_dataloader, val_dataloader, epochs=30, lr=0.001, model_saveName=None, model_saveCsvName=None ):
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    optimizer = torch.optim.Adam(model.parameters(), lr=lr)
    criterion = nn.CrossEntropyLoss()

    model = model.to(device)
    # 复制当前模型的参数
    best_model_params = copy.deepcopy(model.state_dict())
    # 最高准确率
    best_acc = 0.0
    # 训练集损失函数列表
    train_loss_list = []
    # 验证集损失函数列表
    val_loss_list = []
    # 训练集精度列表
    train_acc_list = []
    # 验证集精度列表
    val_acc_list = []
    # 记录当前时间
    since = time.time()
    for epoch in range(epochs):
        print("Epoch {}/{}".format(epoch + 1, epochs))
        print("-" * 10)
        # 当前轮次训练集的损失值
        train_loss = 0.0
        # 当前轮次训练集的精度
        train_acc = 0.0
        # 当前轮次验证集的损失值
        val_loss = 0.0
        # 当前轮次验证集的精度
        val_acc = 0.0
        # 训练集样本数量
        train_num = 0
        # 验证集样本数量
        val_num = 0
        # 按批次进行训练
        for step, (x, y) in enumerate(train_dataloader):  # 取出一批次的数据及标签
            x = x.to(device)
            y = y.to(device)
            # 设置模型为训练模式
            model.train()
            out = model(x)
            # 查找每一行中最大值对应的行标,即为对应标签
            pre_label = torch.argmax(out, dim=1)
            # 计算损失函数
            loss = criterion(out, y)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            # 累计损失函数,其中,loss.item()是一批次内每个样本的平均loss值(因为x是一批次样本),乘以x.size(0),即为该批次样本损失值的累加
            train_loss += loss.item() * x.size(0)
            # 累计精度(训练成功的样本数)
            train_acc += torch.sum(pre_label == y.data)
            # 当前用于训练的样本数量(对应dim=0)
            train_num += x.size(0)
        # 按批次进行验证
        for step, (x, y) in enumerate(val_dataloader):
            x = x.to(device)
            y = y.to(device)
            # 设置模型为验证模式
            model.eval()
            torch.no_grad()
            out = model(x)
            # 查找每一行中最大值对应的行标,即为对应标签
            pre_label = torch.argmax(out, dim=1)
            # 计算损失函数
            loss = criterion(out, y)
            # 累计损失函数
            val_loss += loss.item() * x.size(0)
            # 累计精度(验证成功的样本数)
            val_acc += torch.sum(pre_label == y.data)
            # 当前用于验证的样本数量
            val_num += x.size(0)
        # 计算该轮次训练集的损失值(train_loss是一批次样本损失值的累加,需要除以批次数量得到整个轮次的平均损失值)
        train_loss_list.append(train_loss / train_num)
        # 计算该轮次的精度(训练成功的总样本数/训练集样本数量)
        train_acc_list.append(train_acc.double().item() / train_num)
        # 计算该轮次验证集的损失值
        val_loss_list.append(val_loss / val_num)
        # 计算该轮次的精度(验证成功的总样本数/验证集样本数量)
        val_acc_list.append(val_acc.double().item() / val_num)
        # 打印训练、验证集损失值(保留四位小数)
        print("轮次{} 训练 Loss: {:.4f}, 训练 Acc: {:.4f}".format(epoch+1, train_loss_list[-1], train_acc_list[-1]))
        print("轮次{} 验证 Loss: {:.4f}, 验证 Acc: {:.4f}".format(epoch+1, val_loss_list[-1], val_acc_list[-1]))
        # 如果当前轮次验证集精度大于最高精度,则保存当前模型参数
        if val_acc_list[-1] > best_acc:
            # 保存当前最高准确度
            best_acc = val_acc_list[-1]
            # 保存当前模型参数
            best_model_params = copy.deepcopy(model.state_dict())
            print("保存当前模型参数,最高准确度: {:.4f}".format(best_acc))
        # 训练耗费时间
        time_use = time.time() - since
        print("当前轮次耗时: {:.0f}m {:.0f}s".format(time_use // 60, time_use % 60))
    # 加载最高准确率下的模型参数,并保存模型
    torch.save(best_model_params, model_saveName)
    train_process = pd.DataFrame(data={'epoch': range(epochs),
                                       'train_loss_list': train_loss_list,
                                       'train_acc_list': train_acc_list,
                                       'val_loss_list': val_loss_list,
                                       'val_acc_list': val_acc_list
                                       })
    train_process.to_csv(model_saveCsvName, index=False)
    return train_process
model_saveName="VGG16_best_model.pth"
model_saveCsvName="VGG16_train_process.csv"
train_process = train(model, train_dataloader, val_dataloader, epochs=15, lr=0.001, model_saveName=model_saveName, model_saveCsvName=model_saveCsvName)

3.3训练结果可视化

def train_process_visualization(train_process):
    plt.figure(figsize=(12, 4))
    plt.subplot(1, 2, 1)
    plt.plot(train_process['epoch'], train_process['train_loss_list'], 'ro-', label='train_loss')
    plt.plot(train_process['epoch'], train_process['val_loss_list'], 'bs-', label='val_loss')
    plt.legend()
    plt.xlabel('epoch')
    plt.ylabel('loss')

    plt.subplot(1, 2, 2)
    plt.plot(train_process['epoch'], train_process['train_acc_list'], 'ro-', label='train_acc')
    plt.plot(train_process['epoch'], train_process['val_acc_list'], 'bs-', label='val_acc')
    plt.legend()
    plt.xlabel('epoch')
    plt.ylabel('acc')
    plt.legend()
    plt.show()
train_process_visualization(train_process)

  训练后可能会出现如下结果:
在这里插入图片描述
训练结果可能会时好时坏。事实上,VGG16共有16层网络,当进行反向传播从输出层向输入层运算时,可能会出现梯度消失使得参数无法收敛的情况。由于参数初始化是随机的,可能相对于真实值过大或过小,此时梯度消失就可能会使得参数值无法收敛。此时就需要按照一定的方式初始化参数。

3.4模型参数初始化

import torch
from torch import nn

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")


class VGG16(nn.Module):
    def __init__(self):
        super(VGG16, self).__init__()
        self.block1 = nn.Sequential(
            # 本案例中使用FashionMNIST数据集,所以输入通道数为1
            nn.Conv2d(in_channels=1, out_channels=64, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.block2 = nn.Sequential(
            nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.block3 = nn.Sequential(
            nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.block4 = nn.Sequential(
            nn.Conv2d(in_channels=256, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.block5 = nn.Sequential(
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.fc = nn.Sequential(
            nn.Flatten(),
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, 4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, 10)
        )
        # 参数初始化
        for m in self.modules():
            # 判断是否是具有参数的网络层,无参数就无需初始化
            if isinstance(m, nn.Conv2d):
                # Kaiming初始化方法常用于初始化卷积层参数w,需指定下一层使用的激活函数
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                if m.bias is not None: # 偏置项bias有约定俗成的初始化方式(初始化为0)
                    nn.init.constant_(m.bias, val=0)
            elif isinstance(m, nn.Linear):
                # 全连接层参数初始化往往使用正态分布的方式
                nn.init.normal_(m.weight, mean=0, std=0.01)
                nn.init.zeros_(m.bias)
                if m.bias is not None:
                    nn.init.constant_(m.bias, val=0)


    def forward(self, x):
        x = self.block1(x)
        x = self.block2(x)
        x = self.block3(x)
        x = self.block4(x)
        x = self.block5(x)
        x = self.fc(x)
        return x


model = VGG16().to(device)

事实上,批次大小会影响模型学习到的特征和参数更新的方向。较大的批次可以获得更稳定的梯度更新,但可能会丢失一些细节信息;较小的批次则可以捕捉到更细节的模式,但更新的梯度可能会更加不稳定。合理的批次大小选择可以在训练速度和模型性能之间达到平衡。一般的,建议批次大小为64、128左右,而若硬件性能不够,也可通过减少全连接层参数个数以换取较大的批次,因为全连接层参数过多,往往并不全都需要:

self.fc = nn.Sequential(
    nn.Flatten(),
    nn.Linear(512 * 7 * 7, 256),
    nn.ReLU(),
    nn.Dropout(0.5),
    nn.Linear(256, 128),
    nn.ReLU(),
    nn.Dropout(0.5),
    nn.Linear(128, 10)
)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1906613.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Jenkins 常用的 Linux 指令

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

MNN安装和编译 Win10

如何优化和解决问题 步骤 1: 以管理员权限打开 Windows PowerShell 导航到 C:\Windows\System32\WindowsPowerShell\v1.0 目录。右键点击 powershell.exe 并选择“以管理员身份运行”。执行以下命令&#xff0c;设置执行策略为不受限制&#xff1a;set-executionpolicy -exec…

视频号矩阵管理系统:短视频内容营销的智能助手

随着短视频行业的蓬勃发展&#xff0c;视频号矩阵管理系统应运而生&#xff0c;为内容创作者和品牌提供了一站式的短视频管理和营销解决方案。本文将深入探讨视频号矩阵管理系统的核心功能&#xff0c;以及它如何助力用户在短视频营销领域取得成功。 视频号矩阵管理系统概述 …

Lumerical Algorithm 查找最接近给定透射率值的波长值

Lumerical Algorothm 查找最接近给定透射率值的波长值 引言正文引言 在 Lumerical Script 算法,查找数组中对应值的所有索引值 一文中我们简单介绍了 Lumerical 中的索引值获取算法,这里,我们来介绍一下如何查找最接近给定透射率值的波长值。 正文 比如我们有如下透射率图…

北斗在高铁轨道位移监测中的应用

随着高速铁路的飞速发展&#xff0c;轨道的监测与维护变得至关重要。传统的监测方法已难以满足现代高铁的需求。 近年来&#xff0c;北斗卫星导航系统凭借其高精度、全天候、全球覆盖的优势&#xff0c;在高铁轨道位移监测中发挥了重要作用。 高铁轨道监测系统通过集成北斗卫星…

如何评价Flutter?

哈喽&#xff0c;我是老刘 我们团队使用Flutter已经快6年了。 有很多人问过我们对Flutter的评价。 今天在这里回顾一下6年前选择Flutter时的原因&#xff0c;以及Flutter在这几年中的实际表现如何。 选择Flutter时的判断 1、性能 最开始吸引我们的就是其优秀的性能。 特别是…

影刀RPA | 作业实操02 | 循环

步骤1 总流程 先完成搜索商品-获取最大页码的步骤&#xff0c;跑通看下日志&#xff0c;是正确的再继续写指令 步骤2 具体指令 由嵌套循环完成 外循环&#xff08;ForEach列表循环&#xff09;&#xff1a;遍历商品内循环-1&#xff08;For次数循环&#xff09;&#xff1…

用 MATLAB Function 模块在 Simulink 中实现 MATLAB 函数

MATLAB Function 模块使您能够使用 MATLAB 语言在 Simulink 模型中定义自定义函数。MATLAB Function 模块支持从 Simulink Coder 和 Embedded Coder生成 C/C 代码。 在以下情况下使用这些模块&#xff1a; 您有现有 MATLAB 函数可用于对自定义功能进行建模&#xff0c;或您可…

240707_昇思学习打卡-Day19-基于MindSpore通过GPT实现情感分类

240707_昇思学习打卡-Day19-基于MindSpore通过GPT实现情感分类 今天基于GPT实现一个情感分类的功能&#xff0c;假设已经安装好了MindSpore环境。 # 该案例在 mindnlp 0.3.1 版本完成适配&#xff0c;如果发现案例跑不通&#xff0c;可以指定mindnlp版本&#xff0c;执行!pip…

2024HW必修高危漏洞集合_v3.0

高危风险漏洞一直是企业网络安全防护的薄弱点&#xff0c;也成为HW攻防演练期间红队的重要突破口;每年 HW期间爆发了大量的高危风险漏洞成为红队突破网络边界防护的一把利器,很多企业因为这些高危漏洞而导致整个防御体系被突破、甚至靶标失守而遗憾出局。 HW 攻防演练在即&…

vue3关于在线考试 实现监考功能 推流拉流

vue3 关于在线考试 实现监考功能&#xff0c; pc端考试 本质是直播推流的功能 使用腾讯云直播: 在线文档 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><link rel"icon" href"/f…

AI产品经理发展与规划

今天引用高飞老师的讲课内容&#xff0c;分享一下&#xff0c;何为AI产品经理&#xff1f;这个话题不仅仅希望介绍AI产品经理的工作方式等方面的内容&#xff0c;更多的在于讨论未来产品经理这个行业应该如何发展&#xff1f;行业壁垒在何处&#xff1f;如何应对中年危机&#…

AI大模型+软件开发,计算机从业者转行的契机?

自从大模型吹响新一轮技术革命的号角后&#xff0c;整个行业各个层次都面临大模型带来的范式转换。我今年在 4 月份上海举办的全球机器学习技术大会上演讲时曾提出&#xff0c;大模型为计算产业带来了计算范式、开发范式、交互范式的三大范式改变。今天是软件研发技术大会&…

使用雨云Ubuntu搭建Mc服务器

快两年没写文了吧&#xff0c;好久不见(╹ڡ╹ ) 开门见山吧&#xff0c;网上搜了很多&#xff0c;发现没有使用雨云ubuntu搭建mc服务器的教程&#xff0c;所以准备写一篇&#xff08;顺便恰米 该文章涵盖了很多我自己搭建时遇到的问题&#xff0c;没有提到的大家可以评论或…

四川蔚澜时代电子商务有限公司持续领跑抖音电商

在当今这个数字化飞速发展的时代&#xff0c;电子商务已成为推动经济增长的重要引擎。而在众多电商平台中&#xff0c;抖音电商以其独特的社交属性和年轻化的用户群体&#xff0c;逐渐崭露头角。四川蔚澜时代电子商务有限公司正是这股潮流中的佼佼者&#xff0c;他们专注于抖音…

如何手工DIV一个小车:基于树莓派和总线舵机的智能小车实现

成品演示&#xff1a;bilibili - 悄悄的魔法书 代码仓库&#xff1a;github - flying forever 或者 gitee - 清风莫追 文章目录 1 引言1.1 课题背景1.2 课题意义1.3 课题目的 2 课题相关知识与开发环境3 课题的总体设计4 课题的详细设计与实现4.1 小车物理结构4.1.1 轮子4.1.2 …

食品制造业为什么需要EHS管理,EHS要怎么做才有效?

近年来&#xff0c;随着公众健康意识的显著提升&#xff0c;"舌尖上的安全"已成为广大消费者日益关注的焦点话题。这一趋势促使食品安全的监管力度不断加码&#xff0c;旨在构建一个更加安全、可靠的食品消费环境。 与此同时&#xff0c;ESG&#xff08;环境、社会与…

在 PostgreSQL 里如何实现数据的自动清理和过期处理?

文章目录 一、使用 TIMESTAMP 列和定期任务二、使用事件触发器&#xff08;Event Triggers&#xff09;三、使用分区表&#xff08;Partitioned Tables&#xff09;四、结合存储过程和定时任务示例场景实现步骤测试与验证 在 PostgreSQL 中&#xff0c;可以通过多种方式实现数据…

微信商城自定义小程序源码系统,PHP+MySQL组合开发 带完整的源代码包以及搭建教程

系统概述 传统电商模式面临着诸多挑战&#xff0c;如用户体验不够个性化、运营成本较高等。而微信商城小程序凭借其轻量级、便捷性和与微信生态系统的紧密结合&#xff0c;为企业提供了新的发展机遇。小编给大家分享一款功能强大、易于定制和扩展的源码系统&#xff0c;帮助企…

MPC学习资料汇总

模型预测控制MPC学习资料汇总 需要的私信我~ 需要的私信我~ 需要的私信我~ 【01】课件内容 包含本号所有MPC课程的课件&#xff0c;以及相关MATLAB文档。 【02】课件源代码 本号所有MPC课程的源代码。 【03】MPC仿真案例 三个MPC大型仿真案例&#xff1a; 1&#xff09;…