时间序列预测模型实战案例(三)(LSTM)(Python)(深度学习)时间序列预测(包括运行代码以及代码讲解)

目录

引言

LSTM的预测效果图

LSTM机制

了解LSTM的结构

忘记门

输入门

输出门

LSTM的变体

只有忘记门的LSTM单元

独立循环(IndRNN)单元

双向RNN结构(LSTM)

运行代码

代码讲解


引言

LSTM(Long Short-Term Memory)是一种常用的循环神经网络(RNN)模型,用于处理序列数据,具有记忆长短期的能力。在时间序列预测中,LSTM既可以多元预测机制又可以作为单元预测机制使用。

作为多元预测机制,LSTM可以处理多个相关变量的历史数据,从而可以预测这些变量的未来值。具体地,我们可以将多个变量的历史数据作为LSTM的输入,将多个变量的未来值作为LSTM的输出。在训练过程中,我们可以使用误差反向传播算法来更新LSTM的参数,从而优化模型的预测性能。

作为单元预测机制,LSTM可以预测单一变量的未来值,例如股票价格、销售量等。在单元时间序列预测中,我们需要对历史数据进行分析,确定趋势、季节性和周期性等因素,并使用这些因素来预测未来的值。LSTM可以通过学习历史数据中的模式和规律,来预测未来的值。

LSTM作为多元预测机制和单元预测机制的优点是可以处理序列数据中的长期依赖关系,从而可以捕捉到数据中的复杂模式和规律。它可以自适应地学习和调整模型参数,从而提高模型的预测性能和泛化能力。

总的来说,LSTM作为多元预测机制和单元预测机制的应用广泛,可以用于预测股票价格、气象数据、交通流量等多个领域的数据。

(文末有复制粘贴即可运行的代码)

LSTM的预测效果图

这里先给大家展示一下LSTM的预测效果图(这里的预测指的是预测未知的数据并不是在测试集或者验证集上的预测),其中MAE误差为0.15,ME误差为-0.03。

其误差损失图为,其为MAE的误差图

LSTM机制

LSTM(长短期记忆,Long Short-Term Memory)是一种用于处理序列数据的深度学习模型属于循环神经网络(RNN)的一种变体,其使用一种类似于搭桥术结构的RNN单元。相对于普通的RNN,LSTM引入了门控机制,能够更有效地处理长期依赖和短期记忆问题,是RNN网络中最常使用的Cell之一。

了解LSTM的结构

LSTM通过刻意的设计来实现学习序列关系的同时,又能够避免长期依赖的问题。它的结构示意图如下所示。

在LSTM的结构示意图中,每一条黑线传输着一整个向量,从一个节点的输出到其他节点的输入。其中“+”号代表着运算操作(如矢量的和)而矩形代表着学习到的神经网络层。汇合在一起的线表示向量的连接,分叉的线表示内容被复制,然后分发到不同的位置。

如果上面的LSTM结构图你看着很难理解,但是其实LSTM的本质就是一个带有tanh激活函数的简单RNN,如下图所示。

LSTM这种结构的原理是引入一个称为细胞状态的连接。这个状态细胞用来存放想要的记忆的东西(对应简单LSTM结构中的h,只不过这里面不再只保存上一次状态了,而是通过网络学习存放那些有用的状态),同时在加入三个门,分别是

        忘记门:决定什么时候将以前的状态忘记。

        输入门:决定什么时候将新的状态加进来。

        输出门:决定什么时候需要把状态和输入放在一起输出。

从字面上可以看出,由于三个门的操作,LSTM在状态的更新和状态是否要作为输入,全部交给了神经网络的训练机制来选择。

下面分别来介绍一下三个门的结构和作用。

忘记门

下图所示为忘记门的操作,忘记门决定模型会从细胞状态中丢弃什么信息

忘记门会读取前一序列模型的输出h_{t-1}和当前模型的输入X_{t}来控制细胞状态中的每个数是否保留。

例如:在一个语言模型的例子中,假设细胞状态会包含当前主语的性别,于是根据这个状态便可以选择正确的代词。当我们看到新的主语时,应该把新的主语在记忆中更新。忘记们的功能就是先去记忆中找到一千那个旧的主语(并没有真正执行忘记的操作,只是找到而已。

在上图的LSTM的忘记门中,f_{t}代表忘记门的输出, α代表激活函数,W_{f}代表忘记门的权重,x_{t}代表当前模型的输入,h_{t-1}代表前一个序列模型的输出,b_{f}代表忘记门的偏置。

输入门

输入门可以分为两部分功能,一部分是找到那些需要更新的细胞状态。另一部分是把需要更新的信息更新到细胞状态里

在上面输入门的结构中,I_{t}代表要更新的细胞状态,α代表激活函数,x_{t}代表当前模型的输入,h_{t-1}代表前一个序列模型的输出,W_{t}代表计算I_{t}的权重,b_{t}代表计算I_{t}的偏置,_{}C_{t}代表使用tanh所创建的新细胞状态,W_{c}代表计算C_{t}的权重,b_{c}代表计算C_{t}的偏置。

忘记门找到了需要忘掉的信息f_{t}后,在将它与旧状态相乘,丢弃确定需要丢弃的信息。(如果需要丢弃对应位置权重设置为0),然后,将结果加上I_{t} * C_{t}使细胞状态获得新的信息。这样就完成了细胞状态的更新,如下图输入门的更新图所示。

再上图LSTM输入门的更新图中,B_{t}代表忘记门的输出结果, f_{t}代表忘记门的输出结果,B_{t-1}代表前一个序列模型的细胞状态,I_{t}代表要更新的细胞状态,\widetilde{C_{t}}代表使用tanh所创建的新细胞状态。

输出门

如下图LSTM的输出门结构图所示,在输出门中,通过一个激活函数层(实际使用的是Sigmoid激活函数)来确定哪个部分的信息将输出,接着把细胞状态通过tanh进行处理(得到一个在-1~1的值),并将它和Sigmoid门的输出相乘,得出最终想要输出的那个部分,例如,在语言模型中,假设已经输入了一个代词,便会计算出需要输出一个与该代词相关的信息(词向量)

在LSTM的输出门结构图中,O_{t}代表要输出的信息,α代表激活函数,W_{o}代表计算 O_{t}的权重,b_{o}代表计算O_{t}的偏置,B_{t}代表更新后的细胞状态,h_{t}代表当前序列模型的输出结果。

LSTM的变体

只有忘记门的LSTM单元

只有忘记门的JANET)单元也是LSTM单元的一个变种,发布于2018年。该单元结构源于一个大胆的猜测——当LSTM只有忘记门时会如何?

实验表明,只有忘记门的网络性能居然优于标准LSTM单元。同样,该优化方式也可以被用于其他RNN结构。

如果大家想了解更多LSTM的变体,可以查看一下其他相关资料例如该论文。

独立循环(IndRNN)单元

独立循环单元是一种新型的循环神经网络结构单元结构,发布于2018年其效果和速度均优于LSTM的单元。

IndRNN单元不但可以有效解决传统RNN模型存在的梯度爆炸和梯度小时问题,而且能够更好地学习样本中的长期依赖关系。

在搭建模型时:

        可以用堆叠、残差、全连接的方式使用IndRNN单元,搭建更深的网络结构;

        将IndRNN单元配合ReLU等非饱和激活函数一起使用,会使模型表现出更好的鲁棒性

双向RNN结构(LSTM)

双向RNN又称Bi-RNN,是采用两个方向的RNN模型

RNN模型擅长的是对连续数据的处理,既然是连续的数据,那么模型不但可以学习它的正向特征,而且可以学习它的反向特征,这种将正向和反向结合的结构,会比单向的循环网络有更高的拟合度。

双向RNN的处理过程就是在正向传播的基础上再进行一次反向传播。正向传播和反向传播都连接着一个输出层。这个结构提供给输出层输入序列中每一个点的完整的过去和未来的上下文信息。如图所示是一个沿着事件展开的双向循环神经网络(我们用LSTM举例实际可以是其他任何一个RNN结构)

双向RNN会比单项RNN多一个隐藏层,6个独特的权值在每一个时步被重复利用,6个权值分别对应:输入到向前和向后隐含层,隐含层到隐含层自身,向前和向后隐含层到输出层。

有关双向RNN(LSTM)我会在后续文章中讲到,这里只是大致介绍一下。 

(在大多数应用中,基于时间序列的分析和有关NLP中自动回答类的一些问题中,一般是以双向LSTM配合单向LSTM或RNN横向扩展来实现,效果非常好)

运行代码

下面的代码大家可以赋值到一个py文件中输入自己的文件预测文件即可运行,这只是一个简单LSTM模型搭建,其可以搭配许多其他模块进行组合例如:注意力机制(TPA)、其他的网络结构(GRU)、双向的RNN结构等。

import time
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from matplotlib import pyplot as plt
from sklearn.preprocessing import MinMaxScalernp.random.seed(0)def calculate_mae(y_true, y_pred):# 平均绝对误差mae = np.mean(np.abs(y_true - y_pred))return maetrue_data = pd.read_csv('ETTh1-Test.csv') # 填你自己的数据地址target = 'OT'# 这里加一些数据的预处理, 最后需要的格式是pd.seriestrue_data = np.array(true_data['OT'])# 定义窗口大小
test_data_size = 32
# 训练集和测试集的尺寸划分
test_size = 0.15
train_size = 0.85
# 标准化处理
scaler_train = MinMaxScaler(feature_range=(0, 1))
scaler_test = MinMaxScaler(feature_range=(0, 1))
train_data = true_data[:int(train_size * len(true_data))]
test_data = true_data[-int(test_size * len(true_data)):]
print("训练集尺寸:", len(train_data))
print("测试集尺寸:", len(test_data))
train_data_normalized = scaler_train.fit_transform(train_data.reshape(-1, 1))
test_data_normalized = scaler_test.fit_transform(test_data.reshape(-1, 1))
# 转化为深度学习模型需要的类型Tensor
train_data_normalized = torch.FloatTensor(train_data_normalized).view(-1)
test_data_normalized = torch.FloatTensor(test_data_normalized).view(-1)def create_inout_sequences(input_data, tw, pre_len):inout_seq = []L = len(input_data)for i in range(L - tw):train_seq = input_data[i:i + tw]if (i + tw + 4) > len(input_data):breaktrain_label = input_data[i + tw:i + tw + pre_len]inout_seq.append((train_seq, train_label))return inout_seqpre_len = 4
train_window = 16
# 定义训练器的的输入
train_inout_seq = create_inout_sequences(train_data_normalized, train_window, pre_len)class LSTM(nn.Module):def __init__(self, input_dim=1, hidden_dim=350, output_dim=1):super(LSTM, self).__init__()self.hidden_dim = hidden_dimself.lstm = nn.LSTM(input_dim, hidden_dim, batch_first=True)self.fc = nn.Linear(hidden_dim, output_dim)def forward(self, x):x = x.unsqueeze(1)h0_lstm = torch.zeros(1, self.hidden_dim).to(x.device)c0_lstm = torch.zeros(1, self.hidden_dim).to(x.device)out, _ = self.lstm(x, (h0_lstm, c0_lstm))out = out[:, -1]out = self.fc(out)return outlstm_model = LSTM(input_dim=1, output_dim=pre_len, hidden_dim=train_window)
loss_function = nn.MSELoss()
optimizer = torch.optim.Adam(lstm_model.parameters(), lr=0.001)
epochs = 10
Train = True  # 训练还是预测if Train:losss = []lstm_model.train()  # 训练模式start_time = time.time()  # 计算起始时间for i in range(epochs):for seq, labels in train_inout_seq:lstm_model.train()optimizer.zero_grad()y_pred = lstm_model(seq)single_loss = loss_function(y_pred, labels)single_loss.backward()optimizer.step()print(f'epoch: {i:3} loss: {single_loss.item():10.8f}')losss.append(single_loss.detach().numpy())torch.save(lstm_model.state_dict(), 'save_model.pth')print(f"模型已保存,用时:{(time.time() - start_time) / 60:.4f} min")plt.plot(losss)# 设置图表标题和坐标轴标签plt.title('Training Error')plt.xlabel('Epoch')plt.ylabel('Error')# 保存图表到本地plt.savefig('training_error.png')
else:# 加载模型进行预测lstm_model.load_state_dict(torch.load('save_model.pth'))lstm_model.eval()  # 评估模式results = []reals = []losss = []test_inout_seq = create_inout_sequences(test_data_normalized, train_window, pre_len)for seq, labels in train_inout_seq:pred = lstm_model(seq)[0].item()results.append(pred)mae = calculate_mae(pred, labels.detach().numpy())  # MAE误差计算绝对值(预测值  - 真实值)reals.append(labels.detach().numpy())losss.append(mae)print("模型预测结果:", results)print("预测误差MAE:", losss)plt.style.use('ggplot')# 创建折线图plt.plot(results, label='real', color='blue')  # 实际值plt.plot(reals, label='forecast', color='red', linestyle='--')  # 预测值# 增强视觉效果plt.grid(True)plt.title('real vs forecast')plt.xlabel('time')plt.ylabel('value')plt.legend()plt.savefig('test——results.png')

数据格式 

运行上述代码需要只需要一列数据即可,并且最后数据需要是一个pd.series类型,大家可以在读取进来文件后面加一些数据的处理操作。

(需要注意的是大家的数据不要打乱时间顺序否则预测出来的结果可能不准) 

代码讲解

在上面的运行代码中,主要填写的是在下面填上你的数据目录,

true_data = pd.read_csv('')  # 填你自己的数据地址

这里是定义了test_data_size是指根据过去多少条数据来预测未来的预测值

test_size和train_size代表训练集和测试集的尺寸划分。 

# 定义窗口大小
test_data_size = 350
# 训练集和测试集的尺寸划分
test_size = 0.15
train_size = 0.85

这里定义了数据加载器的操作 

def create_inout_sequences(input_data, tw):inout_seq = []L = len(input_data)for i in range(L - tw):train_seq = input_data[i:i + tw]if (i + tw + 4) > len(input_data):breaktrain_label = input_data[i + tw:i + tw + 1]inout_seq.append((train_seq, train_label))return inout_seqtrain_window = 350
# 定义训练器的的输入
train_inout_seq = create_inout_sequences(train_data_normalized, train_window)

模型的本身其中包含一个LSTM层和一个全连接层进行结果的输出 

class LSTM(nn.Module):def __init__(self, input_dim=1, hidden_dim=350, output_dim=1):super(LSTM, self).__init__()self.hidden_dim = hidden_dimself.lstm = nn.LSTM(input_dim, hidden_dim, batch_first=True)self.fc = nn.Linear(hidden_dim, output_dim)def forward(self, x):x = x.unsqueeze(1)h0_lstm = torch.zeros(1, self.hidden_dim).to(x.device)c0_lstm = torch.zeros(1, self.hidden_dim).to(x.device)out, _ = self.lstm(x, (h0_lstm, c0_lstm))out = out[:, -1]out = self.fc(out)return out

这里定义了模型损失函数和优化器,其中的Train代表着是否进行训练。 

lstm_model = LSTM()
loss_function = nn.MSELoss()
optimizer = torch.optim.Adam(lstm_model.parameters(), lr=0.001)
epochs = 20
Train = False  # 训练还是预测

当Train为True时则开始训练并在最好将模型保存到本地,训练好模型之后我门将Train设置为False则开始评估模式。 

if Train:losss = []lstm_model.train() # 训练模式for i in range(epochs):start_time = time.time()  # 计算起始时间for seq, labels in train_inout_seq:lstm_model.train()optimizer.zero_grad()y_pred = lstm_model(seq)single_loss = loss_function(y_pred, labels)losss.append(single_loss.detach().numpy())single_loss.backward()optimizer.step()if i % 1 == 1:print(f'epoch: {i:3} loss: {single_loss.item():10.8f}')torch.save(lstm_model.state_dict(), 'save_model.pth')print(f"模型已保存,用时:{(time.time() - start_time) / 60:.4f} min")plt.plot(losss)# 设置图表标题和坐标轴标签plt.title('Training Error')plt.xlabel('Epoch')plt.ylabel('Error')# 保存图表到本地plt.savefig('training_error.png')
else:# 加载模型进行预测lstm_model.load_state_dict(torch.load('save_model.pth'))lstm_model.eval()  # 评估模式results = []losss = []test_inout_seq = create_inout_sequences(test_data_normalized, train_window)for seq, labels in test_inout_seq:# 这里的pred = lstm_model(seq)[0].item()是结果的输出这里只保存了输出的第一个值如果想拿出更多的预测值可以修改数字0用切片的方式pred = lstm_model(seq)[0].item()results.append(pred)mae = calculate_mae(pred, labels.detach().numpy()) # MAE误差计算绝对值(预测值  - 真实值)losss.append(mae)print("模型预测结果:", results)print("预测误差MAE:", losss)

后期我也会讲一些最新的预测模型包括Informer,TPA-LSTM,ARIMA,XGBOOST,Holt-winter,移动平均法等等一系列关于时间序列预测的模型,包括深度学习和机器学习方向的模型我都会讲,你可以根据需求选取适合你自己的模型进行预测,如果有需要可以+个关注,包括本次模型我自己的代码大家有需要我也会放出百度网盘下载链接!!

其它时间序列预测模型的讲解!

--------------------------------------------------------MTS-Mixers---------------------------------------------------------

【全网首发】(MTS-Mixers)(Python)(Pytorch)最新由华为发布的时间序列预测模型实战案例(一)(包括代码讲解)实现企业级预测精度包括官方代码BUG修复Transform模型

 --------------------------------------------------------Holt-Winters--------------------------------------------------------

时间序列预测模型实战案例(二)(Holt-Winter)(Python)结合K-折交叉验证进行时间序列预测实现企业级预测精度(包括运行代码以及代码讲解)

如果大家有不懂的也可以评论区留言一些报错什么的大家可以讨论讨论看到我也会给大家解答如何解决!
 

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

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

相关文章

Android 如何启用user版本的adb源码分析

通过adb shell中执行getprop persist.sys.usb.config,可以看到系统usb的相关选项,persist.sys.usb.config显示的就是当前系统关于usb选项的系统配置【RK3188Android4.4刚移植的例子】: 全编脚本中make命令会调用build/core/main.mk,在里面可以看到一段…

安全工程师面试题

安全工程师面试题安全工程师是一个非常重要的职位,他们负责保护公司的网络和系统免受黑客和恶意软件的攻击。如果你想成为一名安全工程师,那么你需要准备好面试。下面是一… 1安全工程师面试题 安全工程师是一个非常重要的职位,他们负责保护…

图解HTTP(2、简单的 HTTP 协议)

HTTP 协议用于客户端和服务器端之间的通信 请求访问文本或图像等资源的一端称为客户端,而提供资源响应的一端称为服务器端。 通过请求和响应的交换达成通信 请求必定由客户端发出,而服务器端回复响应报文 请求报文是由请求方法、请求 URI、协议版本、…

鸿蒙HarmonyOS开发:List列表组件的使用详解及案例演示(二)

文章目录 一、List组件简介1、List组件2、ListItem组件3、ListItemGroup组件 二、使用ForEach渲染列表三、设置列表分割线四、设置List排列方向五、索引值计算规则六、示例演示1、AlphabetIndexer组件2、代码3、效果 一、List组件简介 在我们常用的手机应用中,经常…

SinoDB数据库导入导出工具External table

External table又叫SinoDB外部表,外部表采用多线程机制,支持多线程读取、写入数据文件以及多线程数据转换、插入操作。多线程机制只需要消耗相对较少的系统资源,但是能提供高速数据导入、导出,可以应用在数据采集、表重建、数据库…

OpenGL入门第四步:摄像机视角变换与交互

OpenGL入门第一步:创建窗口、重写虚函数-CSDN博客 OpenGL入门第二步:颜色、纹理设置(解析)-CSDN博客 OpenGL入门第三步:矩阵变换、坐标系统-CSDN博客 目录 函数解析 具体代码 函数解析 相机视角变换需要与鼠标键盘进行交互,需要重写鼠标和键盘响应函数。 初始化 …

【机器学习】 人工智能和机器学习辅助决策在空战中的未来选择

🚀传送门 🚀文章引言🔒技术层面📕作战结构🌈替代决策选项🎬选项 1:超级战争(Hyperwar)🌠选项 2:超越OODA🌂选项 3:阻止其他…

MySQL的表级锁

📝个人主页:五敷有你 🔥系列专栏:面经 ⛺️稳中求进,晒太阳 表级锁 介绍 对于表锁,分为两类: 表共享读锁表独占写锁 语法 1. 加锁:lock tables 表名... read/write 2.…

MySQL数据库的安装和部署

1.数据库的相关介绍 关系型数据库管理系统:(英文简称:RDBMS) 为我们提供了一种存储数据的特定格式,所谓的数据格式就是表, 在数据库中一张表就称为是一种关系. 在关系型数据库中表由两部分组成&#xf…

电信网关配置管理系统 rewrite.php 文件上传致RCE漏洞复现

0x01 产品简介 中国电信集团有限公司(英文名称“China Telecom”、简称“中国电信”)成立于2000年9月,是中国特大型国有通信企业、上海世博会全球合作伙伴。电信网关配置管理系统是一个用于管理和配置电信网络中网关设备的软件系统。它可以帮助网络管理员实现对网关设备的远…

Linux 进程信号【信号产生】

💓博主CSDN主页:麻辣韭菜💓   ⏩专栏分类:Linux知识分享⏪   🚚代码仓库:Linux代码练习🚚   🌹关注我🫵带你学习更多Linux知识   🔝 目录 前言 信号概念 1. 生活角度的信号 2…

Java入门基础学习笔记1——初识java

1、为什么学习java? 几乎统治了服务端的开发;几乎所有的互联网企业都使用;100%国内大中型企业都用;全球100亿的设备运行java。开发岗位薪资高。 Java的流行度很高,商用占有率很高。 可移植性。 2、Java的背景知识 …

手机录屏怎么录?简单易懂的教程,让你轻松上手!

随着科技的不断发展,手机录屏已经成为人们日常生活中一个非常普遍的需求。无论是录制游戏精彩瞬间、分享App使用教程,还是保存线上会议、录制网课,手机录屏都发挥着重要作用。可是你知道手机录屏怎么录吗?本文将详细介绍三种手机录…

【漏洞复现】RuvarOA协同办公平台 WorkFlow接口处存在SQL注入

免责声明:文章来源互联网收集整理,请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该…

亲测有效!关键点检测——COCO格式转YOLO格式代码!!!

话不多收,直接上代码,这个我也是找了好久的,分享不易,给个鼓励!(记得点赞收藏) 大家可以直接使用此代码转换你自己的数据集,路径换成你自己的就行了,注意路径格式&#x…

【https】怎么免费实现https!

一、简介 实现 HTTPS(Hyper Text Transfer Protocol Secure)通常需要购买和配置 SSL/TLS 证书,这是确保网站或应用程序安全传输数据的关键步骤。然而,有一些方法可以在某些情况下“免费”地实现 HTTPS,但这通常涉及到…

React - Input框绑定动态State和监听onChange事件,输入时失去焦点

React - Input框绑定动态State和监听onChange事件,输入时失去焦点 一. 案例复现二. 解决方案 一. 案例复现 案例代码如下: import React, { useState } from react; import { Table, Input } from antd; const Column Table.Column; const mockData …

linux打包流程

因为linux有俩个python版本,我们需要切换到python3这个版本,默认是python 2.7 alias pythonpython3 切换到python3 再次执行:python -V 显示出python的版本了,然后查看pip的配置,我们打包里面需要的第三方需要放到pip…

C++面向对象程序设计-北京大学-郭炜【课程笔记(八)】

C面向对象程序设计-北京大学-郭炜【课程笔记(八)】 1、虚函数和多态的基本概念1.1、虚函数1.2、多态多态的表现形式一多态的表现形式二 2、多态实例:魔法门之英雄无敌2.1、**非多态的实现方法:**2.2、**多态的实现方法** 3、多态实…

C#实现长方体棱锥圆柱棱柱圆锥展开折叠旋转缩放

C#实现长方体棱锥圆柱棱柱圆锥展开折叠旋转缩放 C#实现 模型边数 长方体 棱锥 圆柱 棱柱 圆锥 实现功能 展开 折叠 颜色 边框颜色 旋转 缩放 大小 视图方向 项目获取: 项目获取:typora: typora/img (gitee.com) 备用项目获取链接1:yife…