Pytorch 卷积神经网络-手写数字识别

卷积神经网络是深度学习中的一个里程碑式的技术,有了这个技术,才会让计算机有能力理解图片和视频信息,才会有计算机视觉的众多应用。 本文讨论卷积神经网络模型(CNN)的Hello World。前面讨论的是一个二分类问题,本文讨论多分类问题。每张图片是一个28*28的灰度图片,所以本文的任务是给出一张图片,能识别这个图片是0-9数字中的哪一个。不过在此之前,还得学习一下卷积神经网络的基础知识。

1 卷积神经网络基础

之前我们学习的案例,对于模型的输入都是向量,但是当输入是一个图片的时候该怎么做呢?最直接的方式是把图片的像素点按照行列拉成一个长长的向量。这样就可以采用之前的方式来训练模型。但是如上图所示,一个“猫”的照片是1000_1000,由于是彩色的,具有R,G,B三个通道,那么输入的数据大小就是1000_1000_3,如果一个神经网络第一层有1000个神经元,那么总的参数量为1000_1000_3_1000 = 3 * 109个参数,这对硬件资源提出了太高的要求。

传统的方式,进行可以先对上面的图片进行模糊化处理,这个是怎么做到的呢?

RGB可以看出3个2维矩阵,在模糊化的过程中,需要用上面的这个3_3矩阵,称之为核-kernel,核的大小为3_3,被称为kernel-size,这个核和原来矩阵作用的过程称为卷积。这个kernel就类似全连接层中的Weights一样,所以卷积核里的数值,也是通过反向传播的方法学习到的。

1.1 卷积层

卷积的运算规则:卷积核在输入矩阵中上下滑动,然后和对应的元素相乘求和。

如上图,卷积核大小是3x3的,也就是说其卷积核每次覆盖原图像的9个像素,行和列都滑动了3次,一共滑动3x3=9次,得到了一个3*3的二维矩阵。卷积核在矩阵横向或者纵向一次移动的大小叫做步长(stride),步长可以为1,2,3,4。我们直接看一下代码。

import numpy as np
import torch
from torch import nn
from torch.autograd import Variable
import torch.nn.functional as F
from PIL import Image
import matplotlib.pyplot as plt

if __name__ == "__main__":
   # 使用convert('L')读入一个灰度图片
   image = Image.open('../digital_recognition/digital_data/cat.png').convert('L')
   # 将图片转成矩阵
   image = np.array(image, dtype='float32')
   # 将图片显示出来
   plt.imshow(image.astype('uint8'), cmap='gray')
   print("finish")

torch.nn.Conv2d(
   in_channels, #输入特征矩阵的深度即channel,比如输入一张RGB彩色图像,那in_channels = 3;
   out_channels, #代表卷积核的个数,使用n个卷积核输出的特征矩阵深度即channel就是n;
   kernel_size, #卷积核大小
   stride=1, # 卷积的步长
   padding=0, #卷积核对图像四周的填充边界
   dilation=1, 
   groups=1, 
   bias=True, 
   padding_mode='zeros', 
   device=None, 
   dtype=None
)

# 将图片矩阵转换成pytorch tensor,并适配卷积的输入的要求
image = torch.from_numpy(image.reshape(1,1,image.shape[0],image.shape[1]))

# 构建一个卷积,输入和输出通道都是1(因为是黑白的),卷积核大小是3,
conv = nn.Conv2d(1,1,3,bias=False)
# 构建卷积核
sobel_kernel = np.array([[-1,-1,-1],[-1,-8,-1],[-1,-1,-1]],dtype='float32')
sobel_kernel = sobel_kernel.reshape((1, 1, 3, 3))
# 给卷积的kernel赋值
conv.weight.data = torch.from_numpy(sobel_kernel)
edge = conv(Variable(image))
# 将输出转成图片的格式
edge = edge.data.squeeze().numpy()
plt.imshow(edge,cmap='gray')
print("finish")

卷积操作图片结果如下:

下面表格列举了其他卷积核的效果

卷积核里面的数字现在是固定的,只能表现图像的某些特性,那么我们可以不固定卷积核里面的数值,然后通过监督学习的方式去自动学习他,“这个可以学习的卷积操作”就是构成卷积神经网络里面最重要的概念。

1.2 池化层

池化是一个对输入进行下采样的操作,能快速减少输入大小,从而减少神经网络后面的参数量,便于训练模型。相对于卷积的下采样,有不需要参数的优点(没有卷积核参数)。一般有两种池化方式:
● 最大值池化层(max pooling)
● 平均值池化层(average pooling)

下面图是一个最大值池化层,每种颜色的矩阵取一个最大值构成右边的图。

我们从代码来看一下:

#池化核大小是2,移动步长是2
max_pool = nn.MaxPool2d(2, 2)
print('before max pool, image shape:{} * {}'.format(image.shape[2], image.shape[3]))

image = max_pool(Variable(image))

image = image.data.squeeze().numpy()
plt.imshow(image,cmap='gray')
print('before max pool, image shape:{} * {}'.format(image.shape[0], image.shape[1]))
print("max_pool finish")

image shape= (886, 878)
before max pool, image shape:886 * 878
before max pool, image shape:443 * 439

和原图对比,内容没有变化但是尺寸发生了变化。说明池化只能改变图片的大小,不会影响图片的内容。

1.3 总结

理解了卷积层和池化层,那么卷积神经网络就是卷积层+池化层作用神经网络的隐藏层反复出现的多层神经网络结构,如下图所示。

我们分析一下它的层级结构:
● 原始的输入是一张图片,可以是彩色的,也可以是灰度的或黑白的。这里假设是只有一个通道的图片,目的是识别0~9的手写体数字;
● 第一层卷积,我们使用了4个卷积核,得到了4张feature map;激活函数层没有单独画出来,这里我们紧接着卷积操作使用了Relu激活函数;
● 第二层是池化,使用了Max Pooling方式,把图片的高宽各缩小一倍,但仍然是4个feature map;
● 第三层卷积,我们使用了4x6个卷积核,其中4对应着输入通道,6对应着输出通道,从而得到了6张feature map,当然也使用了Relu激活函数;
● 第四层再次做一次池化,现在得到的图片尺寸只是原始尺寸的四分之一左右;
● 第五层把第四层的6个图片展平成一维,成为一个全连接层;
● 第六层再接一个小一些的全连接层;
● 最后接一个softmax函数,判别10个分类,这个后面实战会在介绍一下。
所以,在一个典型的卷积神经网络中,会至少包含以下几个层:
● 卷积层
● 激活函数层
● 池化层
● 全连接分类层

2 手写数字识别实战

2.1 ReLU激活函数

上面了解了卷积神经网络的基础知识,这里在介绍一个新的激活函数,ReLU函数(Rectified Linear Unit)。

ReLU和Sigmoid函数是常用的激活函数,它们在神经网络中起到非线性映射的作用,下面是它们的优缺点对比:
ReLU函数优点:
● 计算简单,只需要判断输入是否大于零。
● 解决了sigmoid函数的梯度消失问题,能更好地应对梯度下降算法。
● 可以使一部分神经元的输出为零,从而实现稀疏性,减少模型的复杂度。
ReLU函数缺点:
● ReLU函数在输入小于零时,梯度为零,导致神经元无法更新权重,称为“神经元死亡”问题。
● 对于输入小于零的情况,ReLU函数不是严格的非线性函数,可能导致模型的表达能力受限。

Sigmoid函数优点:
● Sigmoid函数的输出范围在(0,1)之间,可以将输出解释为概率。
● Sigmoid函数是严格的非线性函数,具有较强的表达能力。
● 可以将Sigmoid函数的输出直接作为分类器的输出,适用于二分类问题。
Sigmoid函数缺点:
● Sigmoid函数存在梯度饱和问题,当输入的绝对值很大时,梯度接近于零,导致梯度下降算法收敛缓慢。
● Sigmoid函数的计算量较大,使用指数运算,计算时间较长。

2.2 交叉熵损失函数

2.3 读取数据集
def load_dataset():
   train_data = mnist.MNIST('.data', train=True, download=True)
   test_data = mnist.MNIST('.data', train=False, download=True)
   return train_data, test_data


if __name__ == '__main__':
   train_data, test_data = load_dataset()
   data, label = train_data[0]
   print("data", data)
   print("label", label)

   image = np.array(data, dtype='float32')
   print("image shape =", image.shape)
   print("image =", image)
   # 将图片显示出来
   plt.imshow(image.astype('uint8'), cmap='gray')
   print("finish")


首次执行的时候,需要下载。

输出:

label 5
image shape = (28, 28)
image = [[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   3.  18.
  18.  18. 126. 136. 175.  26. 166. 255. 247. 127.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.  30.  36.  94. 154. 170. 253.
 253. 253. 253. 253. 225. 172. 253. 242. 195.  64.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.  49. 238. 253. 253. 253. 253. 253.
 253. 253. 253. 251.  93.  82.  82.  56.  39.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.  18. 219. 253. 253. 253. 253. 253.
 198. 182. 247. 241.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.  80. 156. 107. 253. 253. 205.
  11.   0.  43. 154.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.  14.   1. 154. 253.  90.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0. 139. 253. 190.
   2.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.  11. 190. 253.
  70.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.  35. 241.
 225. 160. 108.   1.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.  81.
 240. 253. 253. 119.  25.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
  45. 186. 253. 253. 150.  27.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.  16.  93. 252. 253. 187.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0. 249. 253. 249.  64.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
  46. 130. 183. 253. 253. 207.   2.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.  39. 148.
 229. 253. 253. 253. 250. 182.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.  24. 114. 221. 253.
 253. 253. 253. 201.  78.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.  23.  66. 213. 253. 253. 253.
 253. 198.  81.   2.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.  18. 171. 219. 253. 253. 253. 253. 195.
  80.   9.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.  55. 172. 226. 253. 253. 253. 253. 244. 133.  11.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0. 136. 253. 253. 253. 212. 135. 132.  16.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]]

2.4 定义模型

class CNN(nn.Module):  # 我们建立的CNN继承nn.Module这个模块
   def __init__(self):
       super(CNN, self).__init__()
       # 建立第一个卷积(Conv2d)-> 激活函数(ReLU)->池化(MaxPooling)
       self.conv1 = nn.Sequential(
           # 第一个卷积con2d
           nn.Conv2d(  # 输入图像大小(1,28,28)
               in_channels=1,  # 输入图片的高度,因为minist数据集是灰度图像只有一个通道
               out_channels=16,  # n_filters 卷积核的高度
               kernel_size=5,  # filter size 卷积核的大小 也就是长x宽=5x5
               stride=1,  # 步长
               padding=2,  # 想要con2d输出的图片长宽不变,就进行补零操作 padding = (kernel_size-1)/2
           ),  # 输出图像大小(16,28,28)
           # 激活函数
           nn.ReLU(),
           # 池化,下采样
           nn.MaxPool2d(kernel_size=2),  # 在2x2空间下采样
           # 输出图像大小(16,14,14)
       )
       # 建立第二个卷积(Conv2d)-> 激励函数(ReLU)->池化(MaxPooling)
       self.conv2 = nn.Sequential(
           # 输入图像大小(16,14,14)
           nn.Conv2d(  # 也可以直接简化写成nn.Conv2d(16,32,5,1,2)
               in_channels=16,
               out_channels=32,
               kernel_size=5,
               stride=1,
               padding=2
           ),
           # 输出图像大小 (32,14,14)
           nn.ReLU(),
           nn.MaxPool2d(2),
           # 输出图像大小(32,7,7)
       )
       # 建立全卷积连接层
       self.out = nn.Linear(32 * 7 * 7, 10)  # 输出是10个类

   # 下面定义x的传播路线
   def forward(self, x):
       x = self.conv1(x)  # x先通过conv1
       x = self.conv2(x)  # 再通过conv2
       # 把每一个批次的每一个输入都拉成一个维度,即(batch_size,32*7*7)
       # 因为pytorch里特征的形式是[bs,channel,h,w],所以x.size(0)就是batchsize
       x = x.view(x.size(0), -1)  # view就是把x弄成batchsize行个tensor
       output = self.out(x)
       return output

2.5 训练模型
# 超参数
EPOCH = 10 
BATCH_SIZE = 50
LR = 0.001  # 学习率
DOWNLOAD_MNIST = True  # 表示还没有下载数据集,如果数据集下载好了就写False

if __name__ == '__main__':
   # 训练集
   train_data = torchvision.datasets.MNIST(
       root='./data/',  # 保存或提取的位置  会放在当前文件夹中
       train=True,  # true说明是用于训练的数据,false说明是用于测试的数据
       transform=torchvision.transforms.ToTensor(),  # 转换PIL.Image or numpy.ndarray
       download=DOWNLOAD_MNIST,  # 已经下载了就不需要下载了
   )

   # 测试集
   test_data = torchvision.datasets.MNIST(
       root='./data/',
       train=False
   )
   # 加载数据
   train_loader = Data.DataLoader(
       dataset=train_data,
       batch_size=BATCH_SIZE,
       shuffle=True  # 是否打乱数据,一般都打乱
   )

   # 图像的pixel本来是0到255之间,除以255对图像进行归一化使取值范围在(0,1)
   # torch.unsqueeze(a) 是用来对数据维度进行扩充,这样shape就从(x,28,28)->(x,1,28,28)
   test_x = torch.unsqueeze(test_data.train_data, dim=1).type(torch.FloatTensor) / 255

   test_y = test_data.test_labels
   cnn = CNN()
   print(cnn)

   # 优化器选择Adam
   optimizer = torch.optim.Adam(cnn.parameters(), lr=LR)
   # 损失函数
   loss_func = nn.CrossEntropyLoss() 
   # 开始训练
   for epoch in range(EPOCH):
       for step, (b_x, b_y) in enumerate(train_loader):  # 分配batch data
           output = cnn(b_x)  # 先将数据放到cnn中计算output
           loss = loss_func(output, b_y)  # 输出和真实标签的loss,二者位置不可颠倒
           optimizer.zero_grad()  # 清除之前学到的梯度的参数
           loss.backward()  # 反向传播,计算梯度
           optimizer.step()  # 应用梯度
           if step % 100 == 0:
               test_output = cnn(test_x)
               pred_y = torch.max(test_output, 1)[1].data.numpy()
               accuracy = float((pred_y == test_y.data.numpy()).astype(int).sum()) / float(test_y.size(0))
               print('Epoch: ', epoch, '| train loss: %.4f' % loss.data.numpy(), '| test accuracy: %.2f' % accuracy)

   # 保存模型
   torch.save(cnn.state_dict(), 'cnn2.pkl')

跑了7个Epoch之后,模型的准确率已经到99%。

Epoch:  0 | train loss: 2.3034 | test accuracy: 0.14
Epoch:  0 | train loss: 0.5552 | test accuracy: 0.89
Epoch:  0 | train loss: 0.1276 | test accuracy: 0.95
Epoch:  0 | train loss: 0.0556 | test accuracy: 0.96
.....
Epoch:  7 | train loss: 0.0666 | test accuracy: 0.99
Epoch:  7 | train loss: 0.0071 | test accuracy: 0.99
Epoch:  7 | train loss: 0.0054 | test accuracy: 0.99

2.6 模型验证
cnn.load_state_dict(torch.load('cnn2.pkl'))
cnn.eval()

test_output = cnn(test_x)
pred_y = torch.max(test_output, 1)[1].data.numpy()
print(pred_y, 'prediction number')
print(test_y.numpy(), 'real number')
# 检查元素是否相等并统计不相等的个数
unequal_count = sum(pred_y[i] != test_y[i] for i in range(len(pred_y)))

# 计算不相等的比例
equal_ratio = 1 - unequal_count / len(pred_y)
# 输出结果
print("总个数:{},不相等的个数{}".format(len(pred_y), unequal_count))
print("准确率:", equal_ratio.item())

[7 2 1 ... 4 5 6] prediction number
[7 2 1 ... 4 5 6] real number
总个数:10000,不相等的个数165
准确率: 0.9835000038146973

那么,我们该如何学习大模型?

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

一、大模型全套的学习路线

学习大型人工智能模型,如GPT-3、BERT或任何其他先进的神经网络模型,需要系统的方法和持续的努力。既然要系统的学习大模型,那么学习路线是必不可少的,下面的这份路线能帮助你快速梳理知识,形成自己的体系。

L1级别:AI大模型时代的华丽登场

L2级别:AI大模型API应用开发工程

L3级别:大模型应用架构进阶实践

L4级别:大模型微调与私有化部署

一般掌握到第四个级别,市场上大多数岗位都是可以胜任,但要还不是天花板,天花板级别要求更加严格,对于算法和实战是非常苛刻的。建议普通人掌握到L4级别即可。

以上的AI大模型学习路线,不知道为什么发出来就有点糊,高清版可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

三、大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

img

四、AI大模型商业化落地方案

img

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

线代知识点总结

目录 一.初等行/列变换 1.计算行列式时,行列变换都可 2.求矩阵的秩时,行列变换都可 3.解线性方程组时,仅能使用初等行变换 4.判定解的情况,单纯求r(A),r(A,b)的过程行列变换都可 5.求向量组极大无关组、线性表出关系&#x…

基于springboot的宠物商城网站

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于springboot的宠物商城网站,java项目…

QT QFileDialog文件选择对话框

QT QFileDialog文件选择对话框 选择txt或者cpp文件,读取内容并显示 参考: QT写入文件与读取文件内容_qt往一个文件写东西-CSDN博客 #include "QtFilePreview.h" #include "qfiledialog.h" #include "qfile.h" #includ…

TCGAbiolinks包学习

TCGAbiolinks 写在前面学习目的GDCquery GDCdownload GDC prepare中间遇到的报错下载蛋白质数据 写在前面 由于别人提醒我TCGA的数据可以利用TCGAbiolinks下载并处理,所以我决定阅读该包手册,主要是该包应该是有更新的,我看手册进行更新了&…

IO流及字符集

IO流 作用: 用于读写文件中的数据 分类: 图来自黑马程序员网课 纯文本文件:Windows自带的记事本打开能读懂的文件,word excel不是纯文本文件 图来自黑马程序员网课 FileOutputStream: 操作本地文件的字节输出流,可…

SQLServer 借助Navcate做定时备份的脚本

首先创建SQLServer链接,然后在Query标签种创建一个查询 查询内容如下 use ChengYuMES declare ls_time varchar(1000) declare ls_dbname varchar(1000) set ls_time convert(varchar, getdate(), 112) _ replace(convert(varchar, getdate(), 108), :, )-- 需…

【单片机毕业设计选题24006】-基于STM32的智能鱼缸系统

系统功能: 采用STM32最小系统板控制,采集传感器数据显示在OLED上 并可通过按键或蓝牙APP控制两路继电器和舵机。 1. 使用DHT11模块采集环境温湿度 2. 使用DS18B20采集鱼缸水温 3. 继电器控制水泵(水位过低时加水) 4. 继电器模拟控制增氧…

【docker】如何解决artalk的跨域访问问题

今天折腾halo的时候,发现artalk出现跨域访问报错,内容如下。 Access to fetch at https://artk.musnow.top/api/stat from origin https://halo.musnow.top has been blocked by CORS policy: The Access-Control-Allow-Origin header contains multipl…

汽车电子零部件(15):车载摄像头

前言: 在车载IVI、智能座舱系统中,有一个重要的应用场景就是视频。视频应用又可分为三种,一种是直接解码U盘、SD卡里面的视频文件进行播放,一种是手机投屏,就是把手机投屏软件已视频方式投屏到显示屏上显示,另外一种就是对视频采集设备(主要就是摄像头Camera)的视频源…

FreeRTOS移植:STM32L476 nucleo-L476RG 开发板《02》

系列文章 FreeRTOS移植:STM32L476 nucleo-L476RG 开发板《01》 说明 上一篇 FreeRTOS移植:STM32L476 nucleo-L476RG 开发板《01》 主要讲了一下如何快速搭建一个 STM32 裸机工程,其实 STM32CubeMX 可以生成 FreeRTOS 的工程,这就…

扣子arixv论文检索工作流开源(Sam老师作品)

因为在扣子上找不到好用的AI论文检索Bot,之前借助工作流和arxiv.org API开发了一个工具,当时写了一篇介绍。 核心流程比较简单,但有很多同学咨询了解,今天抽了点时间把工作流完全开源了。为了方便对比,在公共Team中通过…

LabVIEW开发中的常见通讯协议详解

介绍LabVIEW开发中常见的通讯协议,包括RS-232、RS-485、I2C、SPI、CAN、Ethernet、Modbus和GPIB等。通过对各协议的具体内容、特点、使用范围及注意事项进行全面解析,帮助开发者选择合适的通讯协议,提高系统性能和可靠性。 1. RS-232 内容&a…

进口二手RS FSH4规格3.6G罗德与施瓦茨 FSH6参数

Rohde & Schwarz FSH4 的规格包括&#xff1a; 频率范围从 9 kHz 到 3.6 GHz 高灵敏度&#xff08;<-141dBm&#xff08;1Hz&#xff09;&#xff0c;或使用可选前置放大器<-161dBm&#xff08;1Hz&#xff09;&#xff09; 低测量不确定度&#xff08; 内部跟踪发生…

开源医疗大模型Llama3-Aloe-8B-Alpha,性能超越 MedAlpaca 和 PMC-LLaMA

前言 近年来&#xff0c;大型语言模型 (LLM) 在医疗领域展现出巨大潜力&#xff0c;能够帮助医生和研究人员更快地获取信息、分析数据&#xff0c;并提高医疗服务效率。然而&#xff0c;目前市场上大多数医疗 LLM 都是闭源模型&#xff0c;限制了其在学术研究和应用领域的推广…

【Golang】Go语言中defer与return的精妙交织:探索延迟执行与返回顺序的微妙关系

【Golang】Go语言中defer与return的精妙交织&#xff1a;探索延迟执行与返回顺序的微妙关系 大家好 我是寸铁&#x1f44a; 总结了一篇defer 和 return 返回值 的执行顺序探讨的文章✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 在Go语言中&#xff0c;defer 和return是两…

宝藏速成秘籍(5)插入排序法

一、前言 1.1、概念 插入排序&#xff08;Insertion Sort&#xff09;是一种简单直观的排序算法&#xff0c;其工作原理类似于人们整理一手扑克牌。插入排序通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插入…

log4j漏洞学习

log4j漏洞学习 总结基础知识属性占位符之Interpolator&#xff08;插值器&#xff09;模式布局日志级别 Jndi RCE CVE-2021-44228环境搭建漏洞复现代码分析日志记录/触发点消息格式化 Lookup 处理JNDI 查询触发条件敏感数据带外漏洞修复MessagePatternConverter类JndiManager#l…

15. STUN协议和ICE工作原理

NET介绍 NAT是一种地址转换技术&#xff0c;它可以将IP数据报文头中的IP地址转换为另一个IP地址&#xff0c;并通过转换端口号达到地址重用的目的。 在大多数网络环境中&#xff0c;我们都需要通过 NAT 来访问 Internet。 NAT作为一种缓解IPv4公网地址枯竭的过渡技术&#xff…

[C++]使用C++部署yolov10目标检测的tensorrt模型支持图片视频推理windows测试通过

【测试通过环境】 vs2019 cmake3.24.3 cuda11.7.1cudnn8.8.0 tensorrt8.6.1.6 opencv4.8.0 【部署步骤】 获取pt模型&#xff1a;https://github.com/THU-MIG/yolov10训练自己的模型或者直接使用yolov10官方预训练模型 下载源码&#xff1a;https://github.com/laugh12321/yol…

五、Nginx配置文件-server模块

目录 一、概述 二、虚拟主机设置的三种形式 1、基于端口号配置 2、基于域名配置 3、基于ip配置 三、常用参数 1、listen 2、server_name 3、location 3.1、常见的Nginx正则表达式 3.2、location正则&#xff1a; 3.3示例 4、root 5、index 6、error_page 7、deny…