深度学习训练营之DCGAN网络学习
深度学习训练营之DCGAN网络学习
- 原文链接
- 环境介绍
- DCGAN简单介绍
- 生成器(Generator)
- 判别器(Discriminator)
- 对抗训练
- 前置工作
- 导入第三方库
- 导入数据
- 数据查看
- 定义模型
- 初始化权重
- 定义生成器generator
- 定义判别器
- 模型训练
- 定义参数
- 模型训练
- 结果可视化
原文链接
- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍦 参考文章:365天深度学习训练营-第G2周:深度学习训练营之DCGAN网络学习
- 🍖 原作者:K同学啊|接辅导、项目定制
环境介绍
- 语言环境:Python3.11.4
- 编译器:jupyter notebook
- 深度学习环境:TensorFlow2
DCGAN简单介绍
DCGAN(Deep Convolutional Generative Adversarial Network)是一种基于生成对抗网络(GAN)的深度学习模型,用于生成逼真的图像。它通过将生成器和判别器两个网络相互对抗地训练,以实现生成高质量图像的目标。
DCGAN 的核心思想是使用卷积神经网络(CNN)作为生成器和判别器的网络结构。下面是 DCGAN 的一般工作原理:
生成器(Generator)
生成器接受一个随机噪声向量作为输入,并使用反卷积层(或称为转置卷积层)将其逐渐放大和转换为图像。
通过层层上采样处理和卷积操作,生成器逐渐学习到将低分辨率噪声向量转化为高分辨率逼真图像的映射。
生成器的目标是尽可能接近真实图像的分布,从而生成看起来真实的图像。
判别器(Discriminator)
判别器是一个二分类的CNN网络,用于区分真实图像和生成器生成的假图像。
判别器接受输入图像并输出一个概率,表示输入图像是真实图像的概率。
判别器通过对真实图像分配较高的概率值,并对生成器生成的假图像分配较低的概率值,来辨别真实和假的图像。
对抗训练
DCGAN 的核心是通过对抗训练生成器和判别器来提升它们的性能(属于是无监督的学习)。
在训练过程中,生成器试图生成逼真的图像以欺骗判别器,而判别器则努力区分真实和生成的图像。
这里就可以理解为生成器通过尽可能地生成逼近于真实图片的图像来尝试骗过判别器,而判别器就是通过尽可能地将假图片和真图片进行区分,当两种之间发生冲突的时候,就会进行进一步的优化,直到达到平衡,在后续的代码当中我们也可以看到生成器和判别器之间的网络价格正好是相反的
生成器和判别器相互对抗地进行训练,通过最小化生成器生成图像被判别为假的概率(对抗损失)和最大化真实图像被判别为真的概率(真实损失)来优化网络。
通过反复训练生成器和判别器,并使它们相互对抗地提升,最终可以得到一个生成器能够生成高质量逼真图像的模型。

前置工作
导入第三方库
import torch,random,os
import torch.nn as nn
import torch.nn.parallel
import torch.optim as optim
import torch.utils.data
import torchvision.datasets as dset
import torchvision.transforms as transforms
import torchvision.utils as vutils
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from IPython.display import HTML
manualSeed=999#随机数种子
print("Random Seed:",manualSeed)
random.seed(manualSeed)
torch.manual_seed(manualSeed)
torch.use_deterministic_algorithms(True)
999
导入数据
导入数据并设置超参数
dataroot="./DCGAN/"
# 数据集和上一周的一样,所以就放在一起了
batch_size=128
image_size=64
nz=100 #z潜在的向量大小(生成器generator的尺寸)
ngf=64 #生成器中的特征图大小
ndf=64
num_epochs=50
lr=0.00002
beta1=0.5
print(dataroot)
数据查看
进行数据的导入,
-
用
ImageFolder类来创建数据集对象, -
Transforms.Compose组合成一系列的图像变换操作来对图像进行预处理 -
DataLoder类来创建一个数据加载器的对象 -
Matplotlib库来绘制这些图像
dataset=dset.ImageFolder(root=dataroot,transform=transforms.Compose([transforms.Resize(image_size),transforms.CenterCrop(image_size),transforms.ToTensor(),#转换成张量transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5)),]))
dataloader=torch.utils.data.DataLoader(dataset,batch_size=batch_size,shuffle=True,num_workers=5#使用多个线程加载数据的工作进程数)
device=torch.device=("cuda:0"if (torch.cuda.is_available())else "cpu")
print("使用的设备为 " +device)real_batch=next(iter(dataloader))
plt.figure(figsize=(8,8))
plt.axis("off")
plt.title("Training Images")
plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:24],padding=2,normalize=True).cpu(),(1,2,0)))

定义模型
初始化权重
def weights_init(m):#获取当前层类名classname=m.__class__.__name__#包含conv,表示当前层是卷积层if classname.find('Conv')!=-1:#j均值设为0.0,标准差为0.02nn.init.normal_(m.weight.data,0.0,0.02)#直接在张量上进行参数初始化elif classname.find('BatchNorm')!=-1:nn.init.normal_(m.weight.data,1.0,0.02)nn.init.constant_(m.bias.data,0)
定义生成器generator
class Generator(nn.Module):def __init__(self):super(Generator, self).__init__()## 模型中间块儿self.main=nn.Sequential(nn.ConvTranspose2d(nz,ngf*8,4,1,0,bias=False),nn.BatchNorm2d(ngf*8),nn.ReLU(True),nn.ConvTranspose2d(ngf*8,ngf*4,4,2,1,bias=False),nn.BatchNorm2d(ngf*4),nn.ReLU(True),nn.ConvTranspose2d(ngf*4,ngf*2,4,2,1,bias=False),nn.BatchNorm2d(ngf*2),nn.ReLU(True),nn.ConvTranspose2d(ngf*2,ngf,4,2,1,bias=False),nn.BatchNorm2d(ngf),nn.ReLU(True),nn.ConvTranspose2d(ngf,3,4,2,1,bias=False),nn.Tanh()#Tanh激活函数)def forward(self, input): return self.main(input)
#创建生成器
netG=Generator().to(device)
netG.apply(weights_init)
print(netG)
大家可以注意一下这个网络的架构,会和后面的判别器是相反的

定义判别器
class Discriminator(nn.Module):def __init__(self):super(Discriminator,self).__init__()self.main=nn.Sequential(nn.Conv2d(3,ndf,4,2,1,bias=False),nn.LeakyReLU(0.2,inplace=True),nn.Conv2d(ndf,ndf*2,4,2,1,bias=False),nn.BatchNorm2d(ndf*2),nn.LeakyReLU(0.2,inplace=True),nn.Conv2d(ndf*2,ndf*4,4,2,1,bias=False),nn.BatchNorm2d(ndf*4),nn.LeakyReLU(0.2,inplace=True),nn.Conv2d(ndf*4,ndf*8,4,2,1,bias=False),nn.BatchNorm2d(ndf*8),nn.LeakyReLU(0.2,inplace=True),nn.Conv2d(ndf*8,1,4,1,0,bias=False),nn.Sigmoid()#Sigmoid激活函数)def forward(self, input):return self.main(input)
#创建判别器
netD=Discriminator().to(device)
netD.apply(weights_init)#weights_init初始化所有权重
print(netD)

模型训练
定义参数
criterion=nn.BCELoss()
fixed_noise=torch.randn(64,nz,1,1,device=device)
real_label=1.#1表示真实
fake_label=0.#0表示虚假生成#设置优化器
optimizerD=optim.Adam(netD.parameters(),lr=lr,betas=(beta1,0.999))
optimizerG=optim.Adam(netG.parameters(),lr=lr,betas=(beta1,0.999))
模型训练
img_list=[]#用存储生成的图像列表
G_losses=[]
D_losses=[]
iters=0#迭代次数
print("开始训练Starting Training Loop..")
for epoch in range(num_epochs):#dataloader中的每个batchfor i,data in enumerate(dataloader,0):#####最大化log(D(x))+log(1-D(G(z)))####netD.zero_grad()#清除判别器网络的梯度 real_cpu=data[0].to(device)b_size=real_cpu.size(0)label=torch.full((b_size,),real_label,dtype=torch.float,device=device) #输入判别器进行前向传播output=netD(real_cpu).view(-1)errD_real=criterion(output,label)errD_real.backward()D_x=output.mean().item()#计算批判别器对真实图像样本的输出平均值'''使用生成图像样本进行训练'''noise=torch.randn(b_size,nz,1,1,device=device)fake=netG(noise)label.fill_(fake_label)output=netD(fake.detach()).view(-1)errD_fake=criterion(output,label)errD_fake.backward()D_G_z1=output.mean().item()errD=errD_fake+errD_realoptimizerD.step()'''更新生成网络'''netG.zero_grad()label.fill_(real_label)output=netD(fake).view(-1)errG=criterion(output,label)errG.backward()D_G_z2=output.mean().item()optimizerG.step()if i % 400 == 0:print('[%d/%d][%d/%d]\tLoss_D:%.4f\tLoss_G:%.4f\tD(x):%.4f\tD(G(z)):%.4f / %.4f'% (epoch, num_epochs, i, len(dataloader), errD.item(), errG.item(), D_x, D_G_z1, D_G_z2))#保存损失值G_losses.append(errG.item())D_losses.append(errD.item())#保存固定噪声上的输出来检查生成器的性能 if(iters%500==0)or((epoch==num_epochs-1)and(i==len(dataloader)-1)):with torch.no_grad():fake=netG(fixed_noise).detach().cpu()img_list.append(vutils.make_grid(fake,padding=2,normalize=True))iters+=1
开始训练Starting Training Loop..
[0/50][0/36] Loss_D:1.3728 Loss_G:1.0315 D(x):0.6877 D(G(z)):0.5443 / 0.4221
[1/50][0/36] Loss_D:0.3502 Loss_G:2.3366 D(x):0.9120 D(G(z)):0.1921 / 0.1283
[2/50][0/36] Loss_D:0.1925 Loss_G:3.2138 D(x):0.9384 D(G(z)):0.0957 / 0.0582
[3/50][0/36] Loss_D:0.1281 Loss_G:3.6822 D(x):0.9570 D(G(z)):0.0674 / 0.0370
[4/50][0/36] Loss_D:0.1669 Loss_G:4.0574 D(x):0.9308 D(G(z)):0.0563 / 0.0262
[5/50][0/36] Loss_D:0.1337 Loss_G:4.2146 D(x):0.9428 D(G(z)):0.0551 / 0.0209
[6/50][0/36] Loss_D:0.0729 Loss_G:4.5967 D(x):0.9696 D(G(z)):0.0344 / 0.0138
[7/50][0/36] Loss_D:0.0770 Loss_G:4.6592 D(x):0.9747 D(G(z)):0.0344 / 0.0133
[8/50][0/36] Loss_D:0.0932 Loss_G:4.8994 D(x):0.9742 D(G(z)):0.0303 / 0.0105
[9/50][0/36] Loss_D:0.0790 Loss_G:5.0675 D(x):0.9819 D(G(z)):0.0269 / 0.0083
[10/50][0/36] Loss_D:0.0496 Loss_G:5.0618 D(x):0.9807 D(G(z)):0.0278 / 0.0085
[11/50][0/36] Loss_D:0.0452 Loss_G:5.2256 D(x):0.9800 D(G(z)):0.0221 / 0.0069
[12/50][0/36] Loss_D:0.0332 Loss_G:5.4038 D(x):0.9833 D(G(z)):0.0148 / 0.0058
[13/50][0/36] Loss_D:0.0370 Loss_G:5.2032 D(x):0.9815 D(G(z)):0.0171 / 0.0064
[14/50][0/36] Loss_D:0.0326 Loss_G:5.5015 D(x):0.9838 D(G(z)):0.0149 / 0.0053
[15/50][0/36] Loss_D:0.0368 Loss_G:5.4651 D(x):0.9872 D(G(z)):0.0162 / 0.0055
[16/50][0/36] Loss_D:0.0349 Loss_G:5.6891 D(x):0.9849 D(G(z)):0.0186 / 0.0047
[17/50][0/36] Loss_D:0.0214 Loss_G:5.5402 D(x):0.9925 D(G(z)):0.0133 / 0.0048
[18/50][0/36] Loss_D:0.0216 Loss_G:5.6668 D(x):0.9912 D(G(z)):0.0123 / 0.0041
[19/50][0/36] Loss_D:0.0219 Loss_G:5.6475 D(x):0.9919 D(G(z)):0.0132 / 0.0046
[20/50][0/36] Loss_D:0.0165 Loss_G:5.7313 D(x):0.9956 D(G(z)):0.0118 / 0.0040
[21/50][0/36] Loss_D:0.0203 Loss_G:5.7859 D(x):0.9939 D(G(z)):0.0138 / 0.0040
[22/50][0/36] Loss_D:0.0266 Loss_G:5.7094 D(x):0.9850 D(G(z)):0.0104 / 0.0040
[23/50][0/36] Loss_D:0.0207 Loss_G:5.7429 D(x):0.9899 D(G(z)):0.0101 / 0.0038
...
[46/50][0/36] Loss_D:0.0100 Loss_G:6.6160 D(x):0.9945 D(G(z)):0.0044 / 0.0024
[47/50][0/36] Loss_D:0.0114 Loss_G:7.1434 D(x):0.9927 D(G(z)):0.0025 / 0.0017
[48/50][0/36] Loss_D:0.0039 Loss_G:7.2856 D(x):0.9980 D(G(z)):0.0019 / 0.0012
[49/50][0/36] Loss_D:0.0198 Loss_G:6.2926 D(x):0.9882 D(G(z)):0.0048 / 0.0029
结果可视化
plt.figure(figsize=(10, 5))
plt.title('Generator and Discriminator Loss During Training')
plt.plot(G_losses, label='G')
plt.plot(D_losses, label='D')
plt.xlabel('Iterations')
plt.ylabel('Loss')
plt.legend()
plt.show()
阿哲,训练效果好差,不知道是不是硬件的问题

fig = plt.figure(figsize=(8, 8))
plt.axis('off')ims = [[plt.imshow(np.transpose(i, (1, 2, 0)), animated=True)] for i in img_list]
ani = animation.ArtistAnimation(fig, ims, interval=1000, repeat_delay=1000, blit=True)HTML(ani.to_jshtml())

相关文章:
深度学习训练营之DCGAN网络学习
深度学习训练营之DCGAN网络学习 原文链接环境介绍DCGAN简单介绍生成器(Generator)判别器(Discriminator)对抗训练 前置工作导入第三方库导入数据数据查看 定义模型初始化权重定义生成器generator定义判别器 模型训练定义参数模型训…...
自定义MVC增删改查
目录 mymvcdemo是自定义mvc框架的使用示例 1.1 实体类 1.2 dao方法 1.3 写Service / biz 三层架构 1.4 建action 相当于selvert 1.5 con连接MySQL 8.0 版本 1.6 配置文件 XML 1.7 主界面布局 1.8 增加界面布局 1.9 写tld配置文件 2.0 注意架包 我是已经打包好的 mymv…...
RabbitMQ 教程 | 第2章 RabbitMQ 入门
👨🏻💻 热爱摄影的程序员 👨🏻🎨 喜欢编码的设计师 🧕🏻 擅长设计的剪辑师 🧑🏻🏫 一位高冷无情的编码爱好者 大家好,我是 DevO…...
双网卡如何配置DNS?我是一个仅主机模式配置静态(static)IP、一个NET或桥接(dhcp获取)
目录 一、所有主机初始化 二、135、136服务器,部署DNS调度服务器 1、更改主机主从DNS服务器的主机名称 2、安装bind软件、修改主配置文件 3、修改区域配置文件 4、修改数据文件 5、启动named服务、修改网卡信息 6、解析 7、双网卡的话记得注释以下内容、注…...
Android10: 动态隐藏导航栏和状态栏总结
(1)全屏相关设置 //(1)主题添加 <item name"android:windowFullscreen">true</item>//(2)setContentView之前添加 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCRE…...
roop 视频换脸
roop: one click face swap. 只用一张人脸图片,就能完成视频换脸。 项目地址: https://github.com/s0md3v/roopColab 部署: https://github.com/dream80/roop_colab 本文是本地部署的实践记录。 环境基础 OS: Ubuntu 22.04.2 LTSKernel: 5…...
Java类集框架(一)
目录 1.Collection集合接口 2.List 接口 (常用子类 ArrayList ,LinkedList,Vector) 3.Set 集合 接口(常用子类 HashSet LinkedHashSet,TreeSet) 4.集合输出(iterator , Enumeration) 1.Collection集合接口 Collection是集合中最大父接口,在接口中定义了核心的…...
Jsp+Ssh+Mysql实现的简单的企业物资信息管理系统项目源码附带视频指导运行教程
由jspssh(springstruts2mysql)实现的企业物资信息管理系统,系统功能比较简单,实现了基本的管理员、操作员等用户管理、物品分类管理、物品管理、入库管理、出库管理、库存预警、客户管理、供应商管理等基本功能需要的可以联系我分…...
【Spring】深究SpringBoot自动装配原理
文章目录 前言1、main入口2、SpringBootApplication3、EnableAutoConfiguration4、AutoConfigurationImportSelector4.1、selectImports()4.2、getAutoConfigurationEntry()4.3、getCandidateConfigurations()4.4、loadFactoryNames() 5、META-INF/spring.factories6、总结 前言…...
阿里云负载均衡SLB网络型NLB负载均衡架构性能详解
阿里云网络型负载均衡NLB是阿里云推出的新一代四层负载均衡,支持超高性能和自动弹性能力,单实例可以达到1亿并发连接,帮您轻松应对高并发业务。网络型负载均衡NLB具有超强性能、自动弹性伸缩、高可用、TCPSSL卸载、多场景流量分发和丰富的高级…...
JavaScript学习 -- SM4算法应用实例
SM4算法,也被称为国密算法,是中国公布的一种高效且安全的对称加密算法。在JavaScript中,我们可以通过使用CryptoJS库来实现SM4算法的加密和解密。本篇博客将为您介绍如何在JavaScript中使用SM4算法,并提供一个实际的案例。 首先&…...
【JVM】什么是双亲委派机制
文章目录 1、类加载机制2、双亲委派模型2.1、介绍2.2、为什么需要双亲委派2.3、源码解析 3、破坏双亲委派3.1、介绍3.2、破坏实现3.3、破坏双亲委派的例子 4、线程上下文类加载器 1、类加载机制 类加载阶段分为加载、连接、初始化三个阶段,而加载阶段需要通过类的全…...
网络安全 Day24-select高级用法和多表连接
select高级用法和多表连接 1. select 多子句单表高级实践1.1 select 多子句高级语法1.2 聚合函数1.3 group by 实践1.4 having 筛选1.5 order by 排序1.6 limit 2. 多表连接 1. select 多子句单表高级实践 1.1 select 多子句高级语法 where 和 having 区别是后者是分组后进行…...
JUC并发编程之volatile详解
目录 1. volatile 1.1 volatile关键字的作用 1.1.1 变量可见性 1.1.2 禁止指令重排序 1.2 volatile可见性案例 1.3 volatile非原子性案例 1.4 volatile 禁止重排序 1.5 volatile 日常使用场景 送书活动 1. volatile 在并发编程中,多线程操作共享的变量时&a…...
swing布局详解
1. 布局管理器接口 (1)说明 布局管理器接口为LayoutManager和LayoutManager2,LayoutManager2是LayoutManager的子类。 (2)常用方法 方法描述LayoutManageraddLayoutComponent(String name, Component comp) removeL…...
el-table某一列嵌套使用el-popover,使用click触发,导致页面下拉框组件无法触发弹框关闭(解决办法)
在弹框触发的方法里加上document.body.click() 即可 尝试了很多其他的方法都没用,只有这个解决了 完整代码: <el-select change"sourceChange" clearable ><el-optionv-for"option in list1":key"option.code":…...
正泰电力携手图扑:VR 变电站事故追忆反演
VR(Virtual Reality,虚拟现实)技术作为近年来快速发展的一项新技术,具有广泛的应用前景,支持融合人工智能、机器学习、大数据等技术,实现更加智能化、个性化的应用。在电力能源领域,VR 技术在高性能计算机和专有设备支…...
报错 -bash: wget: command not found
1、报错 -bash: wget: command not found 可以重装 wget 工具: 卸载 wget 工具 yum remove wget下载 wget 工具 yum -y install wget最后尝试 wget “url” 又OK了,一般是原来的wget初始化有文件损坏造成的。...
HashMap扩容和Redis中Dict 扩容
扩容时机: Hash Map:要在某个临界点进行扩容处理,该临界点就是HashMap中元素的数量在数值上等于threshold(table数组长度*加载因子) Dict: 当每次新增键值对的时 , 会检测 负载因子(LoadFactor) , 判断以…...
【Redis】内存数据库Redis进阶(Redis持久化)
目录 分布式缓存 Redis 四大问题Redis 持久化RDB (Redis DataBase)RDB执行时机RDB启动方式——save指令save指令相关配置save指令工作原理save配置自动执行 RDB启动方式——bgsave指令bgsave指令相关配置bgsave指令工作原理 RDB三种启动方式对比RDB特殊启动形式RDB优点与缺点 A…...
智能图像分层革命:5分钟将任何图片转换为可编辑PSD图层
智能图像分层革命:5分钟将任何图片转换为可编辑PSD图层 【免费下载链接】layerdivider A tool to divide a single illustration into a layered structure. 项目地址: https://gitcode.com/gh_mirrors/la/layerdivider 你是否曾面对一张精美的插画ÿ…...
6.6k Star 这个内网穿透神器,一行命令开通公网域名,前后端联调神器!
👉 这是一个或许对你有用的社群🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料: 《项目实战(视频)》:从书中学,往事中…...
黎阳之光人员无感技术——赋能边防与城市智慧发展
无感戍边 数筑屏障|黎阳之光人员无感技术赋能智慧边防建设在国家边境安全防控体系建设中,边防工作始终承担着守护国土、防范风险、维护边境稳定的重要职责。我国边境线地理环境复杂,涵盖高原、荒漠、口岸、界江等多元场景,气候条件…...
写论文用什么软件?精选7款AI论文生成工具深度测评,AI率精准控制无压力!
论文写作的痛点,AI工具来化解! 面对开题报告、文献综述到正文撰写的全流程压力,选对AI论文写作工具能让效率提升数倍。本文将基于真实体验,为你深度测评7款主流工具,帮你找到最适合的学术助手。 测评围绕四大核心维度…...
北邮数电实验:用Verilog在FPGA上实现4位加法器,从全加器到数码管显示(附完整代码与管脚绑定)
北邮数电实验:从全加器到4位加法器的FPGA实现全流程解析 第一次接触FPGA上的数字电路实验时,看着开发板上密密麻麻的管脚和闪烁的LED,我完全不知道从何入手。直到亲手实现了一个4位加法器,才真正理解了数字系统设计的精髓——用硬…...
跨越生态鸿沟:Windows如何优雅解码苹果的HEIC格式
跨越生态鸿沟:Windows如何优雅解码苹果的HEIC格式 【免费下载链接】windows-heic-thumbnails Enable Windows Explorer to display thumbnails for HEIC/HEIF files 项目地址: https://gitcode.com/gh_mirrors/wi/windows-heic-thumbnails 你知道吗ÿ…...
Kernel-Bridge API完全参考手册:从CPU操作到内存管理
Kernel-Bridge API完全参考手册:从CPU操作到内存管理 【免费下载链接】Kernel-Bridge Windows kernel hacking framework, driver template, hypervisor and API written on C 项目地址: https://gitcode.com/gh_mirrors/ke/Kernel-Bridge Kernel-Bridge是一…...
GD32 RISC-V BSP框架设计:从硬件抽象到跨平台移植实战
1. 项目概述:为什么我们需要一个专属的BSP框架?如果你正在使用GD32的RISC-V内核MCU,比如GD32VF103系列,并且是从STM32或者其他ARM Cortex-M平台转过来的,那你大概率踩过这样的坑:官方提供的固件库ÿ…...
不知道怎么挖漏洞?吐血整理40个网络安全漏洞挖掘姿势,看完不信你还挖不到
各位靓仔,搞网络安全,就像在雷区蹦迪,一不小心就BoomShakalaka!Web漏洞这玩意儿,说白了就是信任危机 验证掉链子。开发者们啊,总是对用户输入、权限边界和系统交互爱的太深,结果翻车了…...
用STM32F103和LORA模块,从零搭建一个轮询式本地传感网(附避坑点)
基于STM32F103与LoRa的工业级轮询传感网实战指南 在工业物联网和智能农业领域,稳定可靠的无线传感网络是数据采集的基石。当我们手头有几个STM32F103开发板和LoRa模块时,如何构建一个抗干扰性强、响应及时的轮询式传感网络?本文将深入解析从硬…...
