第16周:LSTM-火灾温度预测

news2024/9/25 17:16:40

目录

前言

一、LSTM简介

1.1 LSTM的本质

1.2 LSTM的提出

1.3 LSTM的原理

1.3.1 RNN原理介绍

1.3.2 LSTM原理介绍

二、前期准备

2.1 导入库、设置GPU

2.2 导入数据

2.3 构建数据集 

2.3.1 数据集预处理

2.3.2 设置X,y

2.3.3 缺失值检测

2.3.4 划分数据集

三、模型训练

3.1 构建模型

3.1.1 nn.LSTM()函数详解

3.1.2 模型搭建

3.2 训练函数

3.3 测试函数

3.4 正式训练

四、模型评估

4.1 Loss图

4.2 调用模型进行预测

4.3 R^2值评估

五、拔高尝试

总结


前言

  • 🍨 本文为[🔗365天深度学习训练营](https://mp.weixin.qq.com/s/0dvHCaOoFnW8SCp3JpzKxg) 中的学习记录博客
  • 🍖 原作者:[K同学啊](https://mtyjkh.blog.csdn.net/)

说在前面

本周目标:对时序数据进行预测,了解LSTM是什么,并使用其构建一个完整的程序,并使最后的决定系数R^2达到0.83;拔高目标——使用第1~8个时刻的数据预测第9~10个时刻的温度数据

我的环境:Python3.8、Pycharm2020、torch1.12.1+cu113

数据来源:[K同学啊](https://mtyjkh.blog.csdn.net/)


一、LSTM简介

LSTM全称为长短期记忆网络(Long Short Term Memeory networks),是一种特殊的RNN,能够

析任务而创建的。LSTM由Hochreiter & Schmidhuber(1997)提出,许多研究者进行一系列的工作对其改进并使之发扬光大。LSTM在许多问题上效果非常好,现在被广泛使用。

1.1 LSTM的本质

RNN面临的问题:RNN在处理长序列时面临的主要问题——短时记忆和梯度消失/爆炸

1)短时记忆:

问题:RNN在处理长序列时,由于信息的传递是通过隐藏状态进行的,随着时间的推移,较早时间步的信息可能会在传递到后面的时间步时逐渐消失或被覆盖。

影响:导致RNN难以捕捉和利用序列中的长期依赖关系,从而限制其在处理复杂任务时的性能

2)梯度消失/梯度爆炸

问题:在RNN的反向传播过程中,梯度会随着时间步的推移而逐渐消失(变得非常小)或爆炸(变得非常大)

影响:梯度消失使得RNN在训练时难以学习到长期依赖关系,因为较早时间步的梯度信息在反向传播到初始层时几乎为零。梯度爆炸则可能导致训练过程不稳定,权重更新过大,甚至导致数值溢出。

1.2 LSTM的提出

 LSTM是一种RNN的辩题,解决了传统RNN在处理长序列数据时的问题,特别是在面对长时间滞后关系时容易出现的梯度消失或梯度爆炸问题。

LSTM通过引入“门”的结构,包括输入门、遗忘门和输出门,以及细胞状态,解决了一下问题:

1)长期依赖问题:在传统的RNN中,当序列非常长时,网络往往会遇到梯度消失或梯度爆炸的问题,导致难以捕捉到序列中长距离的依赖关系。LSTM通过细胞状态来传递信息,避免了梯度消失或爆炸,从而更好地捕捉长期依赖关系;

2)遗忘和记忆:LSTM的门控结构允许网络选择性地忘记或记住某些信息,这使得网络可以更好地处理序列中的噪声或不重要的信息,同时保留重要的长期依赖关系;

3)梯度传播:LSTM通过门控结构,似的梯度可以在时间更好地传播,从而使得网络的训练更加稳定和高效。

所以LSTM通过引入门控结构和细胞状态,有效地解决了传统RNN在处理长序列数据时遇到的梯度消失、长期依赖等问题,使得其在语言建模、时间序列预测、机器翻译等任务中取得显著的进展。

1.3 LSTM的原理

1.3.1 RNN原理介绍

1)隐藏状态的传递

过程描述:在处理序列数据时,RNN将前一时间步的隐藏状态传递给下一个时间步

作用:隐藏状态充当了神经网络的“记忆”,它包含了网络之前所见过的数据的相关信息

重要性:这种传递机制使得RNN能够捕捉序列中的时序依赖关系

   将隐藏状态传递给下一个时间步

2)隐藏状态的计算

细胞结构:RNN的一个细胞接收当前时间步的输入和前一时间步的隐藏状态

组合方式:当前输入和先前隐藏状态被组合成一个向量,这个向量融合了当前和先前的信息

激活函数:组合后的向量经过一个tanh激活函数的处理,输出新的隐藏状态。这个新的隐藏状态既包含了当前输入的信息,也包含了之前所有输入的历史信息。

tanh激活函数(区间-1~1)

输出:新的隐藏状态被输出,并传递给下一个时间步,继续参与序列的处理过程

RNN的细胞结构和运算

1.3.2 LSTM原理介绍

原始RNN的隐藏层只有一个状态,即h,它对于短期的输入非常敏感。那么如果我们再增加一个门(gate)机制用于控制特征的流通和损失,即c,让它来保存长期的状态,这就是长短时记忆网络(Long Short Term Memory,LSTM)

新增加的状态c称为单元状态,把LSTM按照时间维度展开:

在t时刻,LSTM的输入有三个:当前时刻网络的输出值x_{t}、上一时刻LSTM的输出值h_{t-1}、以及上一时刻的记忆单元向量c_{t-1};LSTM的输出有两个:当前时刻的隐藏状态向量h_{t}、和当前时刻的记忆单元状态向量c_{t}

1)遗忘门

f_{t}叫做遗忘门,表示C_{t-1}的哪些特征被用于计算C_{t}f_{t}是一个向量,向量的每个元素均位于(0~1)范围内。通常我们使用sigmoid作为激活函数,sigmoid的输出是一个介于(0~1)区间内的值,但是当观察一个训练好的LSTM时,会发现门的值绝大多数都非常接近0或者1,其余的值少之又少。

作用:决定哪些旧信息应该从记忆单元中遗忘或移除

组成:遗忘门仅由一个sigmoid激活函数组成

sigmoid激活函数(区间0~1)

2)输入门

作用:决定哪些新信息应该被添加到记忆单元中

组成:输入门由一个sigmoid激活函数和一个tanh激活函数组成。sigmoid函数决定哪些信息是重要的,而tanh函数则生成新的候选信息

运算:输入门的输出与候选信息相乘,得到的结果将在记忆单元更新时被考虑

输入门(sigmoid激活函数+tanh激活函数)

3)输出门

作用:决定记忆单元中的哪些信息应该被输出到当前时间步的隐藏状态h_{t}

组成:输出门同样由一个sigmoid激活函数和一个tanh激活函数组成。sigmoid函数决定哪些信息应该被输出,而tanh函数则处理记忆单元的状态以准备输出

运算:sigmoid函数的输出与经过tanh函数处理的记忆单元状态相乘,得到的结果即为当前时间步的隐藏状态h_{t}

输出门(sigmoid激活函数+tanh激活函数)

二、前期准备

2.1 导入库、设置GPU

代码如下:

import torch.nn.functional as F
import numpy as np
import pandas as pd
import torch
from torch import nn
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import MinMaxScaler
from torch.utils.data import TensorDataset, DataLoader
from sklearn import metrics
import warnings
warnings.filterwarnings("ignore")

device = "cuda" if torch.cuda.is_available() else "cpu"
print("Using {} device".format(device))

打印输出:Using cuda device

2.2 导入数据

代码如下:

data = pd.read_csv("woodpine2.csv")
#data.to(device)
print(data.head())

#数据集可视化
plt.rcParams['savefig.dpi'] = 500  #图片像素
plt.rcParams['figure.dpi'] = 500   #分辨率

fig, ax = plt.subplots(1, 3, constrained_layout=True, figsize=(14, 3))

sns.lineplot(data=data["Tem1"], ax=ax[0])
sns.lineplot(data=data["CO 1"], ax=ax[1])
sns.lineplot(data=data["Soot 1"], ax=ax[2])
plt.show()

dataFrame = data.iloc[:, 1:]
print(dataFrame)

打印输出:

Time  Tem1  CO 1  Soot 1
0  0.000  25.0   0.0     0.0
1  0.228  25.0   0.0     0.0
2  0.456  25.0   0.0     0.0
3  0.685  25.0   0.0     0.0
4  0.913  25.0   0.0     0.0

       Tem1      CO 1    Soot 1
0      25.0  0.000000  0.000000
1      25.0  0.000000  0.000000
2      25.0  0.000000  0.000000
3      25.0  0.000000  0.000000
4      25.0  0.000000  0.000000
...     ...       ...       ...
5943  295.0  0.000077  0.000496
5944  294.0  0.000077  0.000494
5945  292.0  0.000077  0.000491
5946  291.0  0.000076  0.000489
5947  290.0  0.000076  0.000487

[5948 rows x 3 columns]

2.3 构建数据集 

数据集介绍:数据集中提供了火灾温度(Tem1)、一氧化碳浓度(CO 1)、烟雾浓度(Soot 1)随着时间变化的数据

2.3.1 数据集预处理

代码如下:

#数据集处理
dataFrame = data.iloc[:, 1:].copy()
sc = MinMaxScaler(feature_range=(0, 1))

for i in ['CO 1', 'Soot 1', 'Tem1']:
    dataFrame[i] = sc.fit_transform(dataFrame[i].values.reshape(-1, 1))

print(dataFrame.shape)

打印输出:(5948, 3)

2.3.2 设置X,y

代码如下:

#设置X、y
width_X = 8
width_y = 1
##取前8个时间段的Tem11、 CO 1、Soot 1为x,第9个时间段的Tem1为y
X = []
y = []

in_start = 0
for _, _ in data.iterrows():
    in_end = in_start + width_X
    out_end = in_end + width_y
    if out_end < len(dataFrame):
        X_ = np.array(dataFrame.iloc[in_start:in_end, ])
        y_ = np.array(dataFrame.iloc[in_end:out_end, 0])

        X.append(X_)
        y.append(y_)

    in_start += 1

X = np.array(X)
y = np.array(y).reshape(-1, 1, 1)
print(X.shape, y.shape)

打印输出:(5939, 8, 3) (5939, 1, 1)

2.3.3 缺失值检测

代码如下:

##检查数据集中是否有空值
print(np.any(np.isnan(X)))
print(np.any(np.isnan(y)))

打印输出:

False
False

2.3.4 划分数据集

代码如下:

#划分数据集
X_train = torch.tensor(np.array(X[:5000]), dtype=torch.float32)
y_train = torch.tensor(np.array(y[:5000]), dtype=torch.float32)

X_test = torch.tensor(np.array(X[5000:]), dtype=torch.float32)
y_test = torch.tensor(np.array(y[5000:]), dtype=torch.float32)
print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)

train_dl = DataLoader(TensorDataset(X_train, y_train), batch_size=64, shuffle=False)
test_dl = DataLoader(TensorDataset(X_test, y_test), batch_size=64, shuffle=False)

打印输出:

torch.Size([5000, 8, 3]) torch.Size([5000, 1, 1]) torch.Size([939, 8, 3]) torch.Size([939, 1, 1])

三、模型训练

3.1 构建模型

3.1.1 nn.LSTM()函数详解

3.1.2 模型搭建

代码如下:

#模型训练
##构建模型
class model_lstm(nn.Module):
    def __init__(self):
        super(model_lstm, self).__init__()
        self.lstm0 = nn.LSTM(input_size=3, hidden_size=320, num_layers=1, batch_first=True)
        self.lstm1 = nn.LSTM(input_size=320, hidden_size=320, num_layers=1, batch_first=True)
        self.fc0 = nn.Linear(320, 1)

    def forward(self, x):
        out, hidden1 = self.lstm0(x)
        out, _ = self.lstm1(out, hidden1)
        out = self.fc0(out)
        return out[:, -1:, :]        #取两个预测值,否则经过lstm会得到8*2个预测

model = model_lstm()
print(model)
  #查看模型的输出数据集格式
print(model(torch.rand(30,8,3)).shape)

打印输出:

torch.Size([30, 1, 1])

3.2 训练函数

代码如下:

##定义训练函数
import copy
def train(train_dl, model, loss_fn, opt, lr_scheduler=None):
    size = len(train_dl.dataset)
    num_batches = len(train_dl)
    train_loss =0        #初始化训练损失和正确率

    for x, y in train_dl:
        x, y = x.to(device), y.to(device)

        #计算预测误差
        pred = model(x)    #网络输出
        loss = loss_fn(pred, y)   #计算预测值与真实值之间的差距

        #反向传播
        opt.zero_grad()     #梯度归零
        loss.backward()     #反向传播
        opt.step()          #每一步自动更新

        #记录loss
        train_loss += loss.item()

    if lr_scheduler is not None:
        lr_scheduler.step()
        print("learning rate = {:.5f}".format(opt.param_groups[0]['lr']), end=" ")
    train_loss /= num_batches
    return train_loss

3.3 测试函数

代码如下:

##定义测试函数
def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    test_loss = 0

    with torch.no_grad():
        for x, y in dataloader:
            x, y = x.to(device), y.to(device)

            y_pred = model(x)
            loss = loss_fn(y_pred, y)
            test_loss += loss.item()

    test_loss /= num_batches
    return test_loss

3.4 正式训练

代码如下:

##正式训练模型
device = "cuda" if torch.cuda.is_available() else "cpu"
print(device)

model = model.to(device)
loss_fn = nn.MSELoss()
learn_rate = 1e-1
opt = torch.optim.SGD(model.parameters(), lr=learn_rate, weight_decay=1e-4)
epochs = 50
train_loss = []
test_loss = []
lr_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(opt, epochs, last_epoch=-1)
best_val = [0, 1e5]
for epoch in range(epochs):
    model.train()
    epoch_train_loss = train(train_dl, model, loss_fn, opt, lr_scheduler)

    model.eval()
    epoch_test_loss = test(test_dl, model, loss_fn)
    if best_val[1] > epoch_test_loss:
        best_val = [epoch, epoch_test_loss]
        best_model_wst = copy.deepcopy(model.state_dict())

    train_loss.append(epoch_train_loss)
    test_loss.append(epoch_test_loss)

    template = ('Epoch:{:2d}, Train_loss:{:.5f}, Test_loss:{:.5f}')
    print(template.format(epoch+1, epoch_train_loss, epoch_test_loss))

print("="*20, 'Done', "="*20)

训练过程如下:

截取部分

四、模型评估

4.1 Loss图

代码如下:

#模型评估
##LOSS图
plt.figure(figsize=(5, 3),dpi=120)
plt.plot(train_loss, label='LSTM Training Loss')
plt.plot(test_loss, label='LSTM Validation Loss')

plt.title('Training and Validation loss')
plt.legend()
plt.show()

打印输出:

4.2 调用模型进行预测

代码如下:

##调用模型进行预测
model.load_state_dict(best_model_wst)
model.to("cpu")
predicted_y_lstm = sc.inverse_transform(model(X_test).detach().numpy().reshape(-1, 1))   #对经过LSTM模型预测得到的数据进行逆值转换,将其转换回原始的数据空间中
y_test_1 = sc.inverse_transform(y_test.reshape(-1, 1))
y_test_one = [i[0] for i in y_test_1]
predicted_y_lstm_one = [i[0] for i in predicted_y_lstm]

plt.figure(figsize=(5, 3), dpi=120)
plt.plot(y_test_one[:2000], color='red', label='real_temp')
plt.plot(predicted_y_lstm_one[:2000], color='blue', label='prediction')

plt.title('Title')
plt.xlabel('X')
plt.ylabel('Y')
plt.legend()
plt.show()

输出如下:

4.3 R^2值评估

代码如下:

RMSE_lstm = metrics.mean_squared_error(predicted_y_lstm_one, y_test_1) ** 0.5
R2_lstm = metrics.r2_score(predicted_y_lstm_one, y_test_1)
print('均方根误差: %.5f' % RMSE_lstm)
print('R2: %.5f' % R2_lstm)

打印输出:

均方根误差: 6.80718
R2: 0.84027

五、拔高尝试

使用第1~8个时刻的数据预测第9~10个时刻的温度数据,实际上也是把原来的单步预测修改为多步预测,主要改动部分在模型输出部分

这是原始模型

修改后的代码

预测代码如下:

#拔高练习
test_1 = torch.tensor(dataFrame.iloc[:8 , ].values,dtype=torch.float32).reshape(1,-1,3)
pred_ = model(test_1)
pred_ = np.round(sc.inverse_transform(pred_.detach().numpy().reshape(1,-1)).reshape(-1),2)  #NumPy中的round函数将结果四舍五入保留两位小数
real_tem = data.Tem1.iloc[:2].values
print(f"第9~10时刻的温度预测:",  pred_)
print("第9~10时刻的真实温度:",  real_tem)

打印输出:

第9~10时刻的温度预测: [33.61 32.73]
第9~10时刻的真实温度: [25. 25.]


总结

  • 学习对比了LSTM与RNN的区别,以及LSTM的优势
  • 实际操作搭建了LSTM模型,运行也达到了预期结果
  • 对于时序数据导出以及处理步骤
  • 拔高部分实现了将LSTM修改为多步输出

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

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

相关文章

数据分析师技术和必备知识汇总-----Excle-----持续更新

数据分析师技术和必备知识汇总-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/140174015 文章目录 一、数据清洗1. 去除空格2. 字符串拼接3. 字符串截取4. 替换函数5. 求长度6. 相对引用和绝对引用7. 查找函数和排名…

单链表详解(2)

三、函数定义 查找节点 //查找结点 SLTNode* SLTNodeFind(SLTNode* phead, SLTDataType x) {assert(phead);SLTNode* pcur phead;while (pcur){if (pcur->data x){return pcur;}pcur pcur->next;}return NULL; } 查找节点我们是通过看数据域来查找的&#xff0c;查…

什么?这动物图片可以上国家地理?

stable difussion中大部分的模型都是关于人的,今天交给大家一些不一样的:如何生成动物图片。在这篇文章中我们将会学到如何生成逼真的动物&#xff0c;可爱的动物&#xff0c;还有幻想中的动物。 准备工作 当然前提是你需要一个SD的软件&#xff0c;你可以用本地的SD webUI或…

高清图片压缩无水印小程序源码系统 前后端分离 带完整的安装代码包以及搭建教程

系统概述 在当今的数字化时代&#xff0c;图片作为信息传播的重要载体&#xff0c;其质量和传输效率直接影响到用户体验。然而&#xff0c;高清图片往往伴随着较大的文件体积&#xff0c;这不仅会占用大量存储空间&#xff0c;还会拖慢网页或应用的加载速度。因此&#xff0c;…

【VUE基础】VUE3第七节—Vue Router路由基础

Vue Router 是 Vue 官方的客户端路由解决方案。 客户端路由的作用是在单页应用 (SPA) 中将浏览器的 URL 和用户看到的内容绑定起来。当用户在应用中浏览不同页面时&#xff0c;URL 会随之更新&#xff0c;但页面不需要从服务器重新加载。 Vue Router 基于 Vue 的组件系统构建&…

代码随想录-Day55

42. 接雨水 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高…

【VS2019】安装下载库HtmlAgilityPack,可解析 HTML (图文详情)

目录 0.背景 1.环境 2.详细步骤 0.背景 项目需要&#xff0c;搭建WCF服务&#xff0c;需求是输入一个string类型字符串&#xff08;网页代码&#xff0c;如<html><body><p>Hello, <b>World</b>!</p></body></html>&#xf…

[嵌入式 C 语言] 按位与、或、取反、异或

一、按位与 & 有0则0&#xff0c;全1则11010 & 0011 00100xef & 0xfe 0xee &#xff08; 0x1110 1111 & 0x1111 1110 0x1110 1110&#xff09; 若协议中如下图所示&#xff1a; 1.1 配合左移运算符 << 取指定的位 说明&#xff1a;DEC表示十进制、…

原生APP开发的优势

原生APP开发是指使用特定的编程语言和开发工具&#xff0c;针对特定的操作系统平台进行开发的应用程序。相比于混合开发和Web开发&#xff0c;原生APP开发具有以下优势。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1. 性能更优 原…

echartsBug-dataZoom拖拽无效果

项目情况&#xff1a;"vue": "3.2.4",使用vue2语法开发 遇到问题&#xff1a;想要给图表增加dataZoom,但是拉拽的时候不生效 解决方法&#xff1a; 这个情况同样也会发生在tooltip不生效上。 之前的写法是将myChart定义在data()之中&#xff0c;但是无效…

怎么用js语句动态调整 .jstree.jstree-2.jstree-default 高度

&#x1f3c6;本文收录于《CSDN问答解惑》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&…

PHP全民投票微信小程序系统源码

&#x1f5f3;️【全民参与的力量】全民投票系统小程序&#xff0c;让决策更民主&#xff01; &#x1f310; 一键启动&#xff0c;全民参与 全民投票系统小程序&#xff0c;是连接每一个声音的高效桥梁。只需简单几步&#xff0c;即可在线发起投票活动&#xff0c;无论是社区…

java基础01—根据源码分析128陷阱以及如何避免128陷阱

源码分析128陷阱 如上图所示&#xff0c;int类型数据超过127依旧能正常比较&#xff0c;但Integer类型就无法正确比较了 /*** Cache to support the object identity semantics of autoboxing for values between* -128 and 127 (inclusive) as required by JLS.** The cache …

vue3在defineProps中使用多语言t,打包报错

报错原因 代码如下 打包后就会报错 defineProps() in script setup cannot reference locally declared variables because it will be hoisted outside of the setup() function. If your component options require initialization in the module scope, use a separate no…

LLm与微调

推荐尝试的微调模型 internlm2-20b-chat&#xff0c;internlm2-7b-chat&#xff0c; Qwen2-7B-Instruct, Qwen2-1.5B-Instruct, Qwen1.5-32B-Chat (Qwen2-0.5B、Qwen2-1.5B, qwen1.5的4B&#xff0c;7B&#xff0c;14B&#xff0c;32B) glm-4-9b-chat, glm-4-9b-chat-1m, gl…

设计资料:520-基于ZU15EG 适配AWR2243的雷达验证底板 高速信号处理板 AWR2243毫米波板

基于ZU15EG 适配AWR2243的雷达验证底板 一、板卡概述 本板卡系北京太速科技自主研发&#xff0c;基于MPSOC系列SOC XCZU15EG-FFVB1156架构&#xff0c;搭载两组64-bit DDR4&#xff0c;每组容量32Gb&#xff0c;最高可稳定运行在2400MT/s。另有1路10G SFP光纤接口、1路40G…

记录一次MySql锁等待 (Lock wait timeout exceeded)异常

[TOC](记录一次MySql锁等待 (Lock wait timeout exceeded)异常) Java执行一个SQL查询未提交&#xff0c;遇到1205错误。 java.lang.Exception: ### Error updating database. Cause: java.sql.SQLException: Lock wait timeout exceeded; try restarting transactionCluster…

用Canvas绘制一个高可配置的圆形进度条

&#x1f680; 用Canvas绘制一个高可配置的圆形进度条 问题分析与拆解第一步&#xff0c;初始化一些默认参数&#xff0c;处理canvas模糊问题第二步&#xff0c;定义绘制函数1. 定义绘制主函数入口&#xff0c;该函数汇集了我们定义的其它绘制方法2. 定义绘制圆环函数3. 定义绘…

Linux-多线程

线程的概念 在一个程序里的一个执行路线就叫做线程&#xff08;thread&#xff09;。更准确的定义是&#xff1a;线程是“一个进程内部的控制序列”一切进程至少都有一个执行线程线程在进程内部运行&#xff0c;本质是在进程地址空间内运行在Linux系统中&#xff0c;在CPU眼中…

IO之反序列化漏洞

hutool之XmlUtil反序列化漏洞 同样存在漏洞的方法还有IoUtil.readObject方法&#xff0c;存在反序列化漏洞&#xff0c;这些方法的漏洞在JDK中本身就存在&#xff0c;而且JDK的做法是要求用户自行检查内容&#xff0c;作为工具类&#xff0c;这块没法解决。hutool在新版本中把这…