深度学习作业 - 作业十一 - LSTM

问题一

推导LSTM网络中参数的梯度,并的分析其避免梯度消失的效果

LSTM网络是为了解简单RNN中存在的长程依赖问题而提出的一种新型网络结构,其主要思想是通过引入门控机制来控制数据的流通,门控机制包括输入门、遗忘门与输出门,同时在LSTM结构中,还存在一个内部记忆单元来存储每一个时间步的内部记忆,用于相关运算,具体的LSTM介绍与引入部分我们已经在上次作业中进行了相关叙述:

深度学习作业 - 作业十 - BPTT-CSDN博客

那么本次作业我们就不进行进一步的详细赘述了,直接进行推导。

首先,一个完整的LSTM网络如上图所示,各种状态与门值的计算已经写在图片里面了,我们要想推到反向传播参数,得先知道前向传播的计算过程,下面我们来总结一下这个过程。

前向传播

前向传播过程在每个时间步t上的发生顺序为

(1)更新遗忘门输出

f^{\left( t \right)}=\sigma \left( W_fh^{\left( t-1 \right)}+U_fx^{\left( t \right)}+b_f \right)

(2)更新输入门和其控制对象

i^{\left( t \right)}=\sigma \left( W_ih^{\left( t-1 \right)}+U_ix^{\left( t \right)}+b_i \right)

a^{\left( t \right)}=\sigma \left( W_ah^{\left( t-1 \right)}+U_ax^{\left( t \right)}+b_a \right) 

(3)更新细胞状态,从C^{\left( t-1 \right)}C^{\left( t \right)}

C^{\left( t \right)}=C^{\left( t-1 \right)}\odot f^{\left( t \right)}+a^{\left( t \right)}\odot i^{\left( t \right)} 

(4)更新输出门和其控制对象,从h^{\left( t-1 \right)}h^{\left( t \right)}

 o^{\left( t \right)}=\sigma \left( W_oh^{\left( t-1 \right)}+U_ox^{\left( t-1 \right)}+b_o \right)

h^{\left( t \right)}=o^{\left( t \right)}\odot \tan\text{h}\left( C^{\left( t \right)} \right)

 (5)得到当前时间步t的预测输出

\hat{y}^{\left( t \right)}=\sigma \left( Vh^{\left( t \right)}+c \right)

反向传播

之前的反向传播中,我们都是仅仅定义了一个隐藏状态误差项\delta,这是由于之前的网络结构只有一个隐藏状态,在LSTM中,隐层不止有h_t还有一个C_t,因此这里我们定义两个\delta,即

\delta _{h}^{\left( t \right)}=\frac{\partial L}{\partial h^{\left( t \right)}}

\delta _{C}^{\left( t \right)}=\frac{\partial L}{\partial C^{\left( t \right)}}

为了方便找到梯度的递推模式,下面是根据前向传播公式给出数据在LSTM中数据的前向流动示意图

我们首先看最后一个时间步t=T

我们可以发现,在t=T时,误差只有L^{\left( T \right)}\rightarrow h^{\left( T \right)}这一条路径,因此\delta ^{\left( T \right)}可以很轻易的求出来,这里先假设损失函数是SSE,方便求解梯度(实际过程中这个损失函数可以改变,改变的话再对应求就可以了)

\delta ^{\left( T \right)}=\frac{\partial L}{\partial h^{\left( T \right)}}=\frac{\partial L}{\partial \hat{y}^{\left( T \right)}}\cdot \frac{\partial \hat{y}^{\left( T \right)}}{\partial h^{\left( T \right)}}=V^T\left( \hat{y}^{\left( T \right)}-y^{\left( T \right)} \right)

下面求\delta _{C}^{\left( t \right)},由于链式法则可以得到

\delta _{C}^{\left( T \right)}=\left( \frac{\partial h^{\left( T \right)}}{\partial C^{\left( T \right)}} \right) ^T\frac{\partial L}{\partial h^{\left( T \right)}}

又有

 h^{\left( t \right)}=o^{\left( t \right)}\odot \tan\text{h}\left( C^{\left( t \right)} \right)

最终可求得

\delta _{C}^{\left( T \right)}=\delta _{h}^{\left( T \right)}\odot o^{\left( T \right)}\odot \tan\text{h'}\left( C^{\left( T \right)} \right) 

知道了最后一个时间步T的梯度值,下一步就是求得每一步的梯度反向传播递推公式,即可由此推得前面每一个时刻t的梯度公式。

下面求t<T时的梯度

根据LSTM中数据的前向流动示意图可以得到,\delta _{h}^{\left( t \right)}的误差来源如下:

(1)l_{(t)}\rightarrow h^{(t)}(注意这里的l代表单元损失,而L是整体损失,是单元损失的累加)。

(2)h^{t+1}\rightarrow o^{(t+1)}\rightarrow h^{(t)}(来自输出门)

(3)C^{t+1}\rightarrow i^{(t+1)}\rightarrow h^{(t)}(来自输入门)

(4)C^{t+1}\rightarrow a^{(t+1)}\rightarrow h^{(t)}a代表输入门激活前的状态)

(5)C^{t+1}\rightarrow f^{(t+1)}\rightarrow h^{(t)}(来自遗忘门)

由此,我们知道,误差主要来自于l_{(t)}h^{t+1}C^{t+1},下面是推导过程

由此我们求得了\delta _{h}^{\left( t \right)}\delta _{C}^{\left( t+1 \right)}\delta _{h}^{\left( t+1 \right)}之间的递推关系。

下面继续求\delta _{C}^{\left( t \right)}

根据LSTM中数据的前向流动示意图可以得到,\delta _{C}^{\left( t \right)}的误差来源如下:

(1)h^t\rightarrow C^t(来自隐层状态)

(2)C^{t+1}\rightarrow C^{t}(来自隐层状态记忆单元)

由此,我们知道,误差主要来自于h^{t}C^{t+1},下面是推导过程

由此我们求得了\delta _{C}^{\left( t \right)}\delta _{C}^{\left( t+1 \right)}\delta _{h}^{\left( t \right)}之间的递推关系。 

有了递推公式,现在计算梯度就比较容易了。

梯度计算

总结一下,对于所有门参数(遗忘门、输入门、候选状态、输出门)W_g,其统一的梯度表达为:

\frac{\partial L}{\partial W_g}=\sum_{t=1}^T{\left[ \delta _{g}^{\left( t \right)} \right] \left( h^{\left( t-1 \right)} \right) ^T}

其中:

\delta _{g}^{\left( t \right)}是各门的误差信号,具体为:

遗忘门:\delta _{f}^{\left( t \right)}=\delta _{C}^{\left( t \right)}\odot C^{\left( t-1 \right)}\odot f^{\left( t \right)}\odot \left( 1-f^{\left( t \right)} \right)

输入门:\delta _{i}^{\left( t \right)}=\delta _{C}^{\left( t \right)}\odot a^t\odot i^{\left( t \right)}\odot \left( 1-i^{\left( t \right)} \right)

候选状态:\delta _{a}^{\left( t \right)}=\delta _{C}^{\left( t \right)}\odot i^{\left( t \right)}\odot \left( 1-a^{\left( t \right)}\odot a^{\left( t \right)} \right)

输出门:\delta _{o}^{\left( t \right)}=\delta _{h}^{\left( t \right)}\odot \tanh\left( C^{\left( t \right)} \right) \odot o^{\left( t \right)}\odot \left( 1-o^{\left( t \right)} \right)

其他参数的计算

其他参数的计算均与W的计算类似。

对于输入权重矩阵U,对应的梯度计算类似于W,只需要将h^{(t-1)}替换为x^{(t)}

而偏置项的梯度是激活前状态的偏导的累加,即\sum_{t=1}^T{\left[ \delta _{g}^{\left( t \right)} \right]}

为什么LSTM能够避免梯度消失

这里要明确的一点是,RNN的梯度消失/爆炸并不是我们所说的传统意义上的梯度消失/爆炸。比如CNN中,各个层有各个层的不同参数,梯度各自不同,而RNN中权重在各个时间步是共享的,最终梯度是所有时间步的梯度之和

因此,RNN 中总的梯度是不会消失的。即便梯度越传越弱,那也只是远距离的梯度消失,由于近距离的梯度不会消失,所有梯度之和便不会消失。RNN 所谓梯度消失的真正含义是,梯度被近距离梯度主导,导致模型难以学到远距离的依赖关系。

回到问题中,LSTM中有很多条传播的路径,但是有一条路径C^{\left( t \right)}=C^{\left( t-1 \right)}\odot f^{\left( t \right)}+a^{\left( t \right)}\odot i^{\left( t \right)}能够永远为梯度总和贡献远距离的梯度,因为这条路径上只涉及到了逐元素相乘与相加两个操作(之前SRN梯度消失就是因为涉及到了矩阵连乘,会导致梯度越乘越偏),梯度流是非常稳定的。同时对于LSTM中其他路径来说,由于梯度的计算还是矩阵连乘,照样会发生一些梯度消失或爆炸现象。

不过由于总的远距离梯度 = 各条路径的远距离梯度之和,即便其他远距离路径梯度消失了,只要保证有一条远距离路径(就是上面说的那条高速公路)梯度不消失,总的远距离梯度就不会消失(正常梯度 + 消失梯度 = 正常梯度)。因此 LSTM 通过改善一条路径上的梯度问题拯救了总体的远距离梯度

问题二

编程实现LSTM的运行过程

这张图是老师用于让我们清楚看到LSTM内部运作的图,定义了每个序列由三个变量x_1x_2x_3组成,网络内部存在一个记忆单元Memory。

x_2取1时,将x_1输入进记忆单元Memory中,模拟了输入门的效果。

x_2取-1时,将记忆单元Memory清空为0,模拟了遗忘门的效果。

x_3取1时,将记忆单元Memory中的数据输出为y

按照如下图设置权重,来模拟这一过程,相应的在程序中也定义相同的权重,再编写一个算子即可。

实际上的LSTM网络结构比这个复杂,并且每个门控结构并不是全开或者全关,是以一定权重开启一部分的,这个例子还是比较形象的,下面分别使用Numpy与Pytorch实现。

1. 使用Numpy实现LSTM算子

代码

import numpy as np


# 激活函数
def sigmoid(x):
    return 1 / (1 + np.exp(-x))


# 权重参数
W_i = np.array([1, 0, 0, 0])
W_IGate = np.array([0, 100, 0, -10])
W_fGate = np.array([0, 100, 0, 10])
W_OGate = np.array([0, 0, 100, -10])

# 输入数据
input = np.array(
    [[1, 0, 0, 1], [3, 1, 0, 1], [2, 0, 0, 1], [4, 1, 0, 1], [2, 0, 0, 1], [1, 0, 1, 1], [3, -1, 0, 1], [6, 1, 0, 1],
     [1, 0, 1, 1]])

y = []  # 输出
c_t = 0  # 内部状态

for x in input:
    g_t = np.matmul(W_i, x)  # 计算候选状态
    IGate = np.round(sigmoid(np.matmul(W_IGate, x)))  # 计算输入门
    after_IGate = g_t * IGate  # 候选状态经过输入门
    FGate = np.round(sigmoid(np.matmul(W_fGate, x)))  # 计算遗忘门
    after_fGate = FGate * c_t  # 内部状态经过遗忘门
    c_t = np.add(after_IGate, after_fGate)  # 新的内部状态
    OGate = np.round(sigmoid(np.matmul(W_OGate, x)))  # 计算输出门
    after_OGate = OGate * c_t  # 新的内部状态经过输出门
    y.append(after_OGate)  # 输出
print(f"输出:{y}")

运行结果 

可见,输出结果与我们预期的相同,其实就是简单在循环里模拟了一下LSTM的计算过程,按照PPT上的权重与计算过程实现即可。 

2. 使用nn.LSTMCell实现

代码

import numpy as np
import torch.nn

# 设置参数
input_size = 4
hidden_size = 1
# 模型实例化
Cell = torch.nn.LSTMCell(input_size=input_size, hidden_size=hidden_size)
# 权重
Cell.weight_ih.data = torch.tensor([[0, 100, 0, -10], [0, 100, 0, 10], [1, 0, 0, 0], [0, 0, 100, -10]],
                                   dtype=torch.float32)
Cell.weight_hh.data = torch.zeros(4, 1)
# 初始化内部状态
h_t = torch.zeros(1, 1)
c_t = torch.zeros(1, 1)
# 输入的数据[batch_size,seq_len,input_size]
input_0 = torch.tensor(
    [[[1, 0, 0, 1], [3, 1, 0, 1], [2, 0, 0, 1], [4, 1, 0, 1], [2, 0, 0, 1], [1, 0, 1, 1], [3, -1, 0, 1], [6, 1, 0, 1],
      [1, 0, 1, 1]]], dtype=torch.float32)
# 交换前两维顺序,方便遍历
input = torch.transpose(input_0, 1, 0)
y = []
# 计算
for x in input:
    h_t, c_t = Cell(x, (h_t, c_t))  # 传入序列输入与各个状态
    y.append(np.round(h_t.item(), decimals=3))
print(f"输出:{y}")

输出结果 

使用实例化的LSTMCell,每次循环分别传入输入,上一时间步的隐层状态与内部记忆单元值,即可自动计算,需要注意的是,由于LSTMCell内部有激活函数tanh,并且每一步计算都是数值计算,无法取整等,故输出不是严格的与示例相同,但是大小关系与0-1关系都是存在的。

3. 使用nn.LSTM实现

代码

import numpy as np
import torch.nn

# 设置参数
input_size = 4
hidden_size = 1
# 模型实例化
Lstm = torch.nn.LSTM(input_size=input_size, hidden_size=hidden_size, batch_first=True)
# 权重
Lstm.weight_ih_l0.data = torch.tensor([[0, 100, 0, -10], [0, 100, 0, 10], [1, 0, 0, 0], [0, 0, 100, -10]],
                                      dtype=torch.float32)
Lstm.weight_hh_l0.data = torch.zeros(4, 1)
# 初始化内部状态
h_t = torch.zeros(1, 1, 1)
c_t = torch.zeros(1, 1, 1)
# 输入的数据[batch_size,seq_len,input_size]
input = torch.tensor([[[1, 0, 0, 1], [3, 1, 0, 1], [2, 0, 0, 1], [4, 1, 0, 1], [2, 0, 0, 1], [1, 0, 1, 1],
                       [3, -1, 0, 1], [6, 1, 0, 1], [1, 0, 1, 1]]], dtype=torch.float32)
y, (h_t, c_t) = Lstm(input, (h_t, c_t))
y = torch.round(y * 1000) / 1000
print(f"输出:{y}")

运行结果 

输出与使用LSTMCell相同,这里Numpy版本的程序与这两个API实现差在了tanh激活函数与内部取整操作。 

LSTM就无需我们自己进行循环计算每一步骤的数据了,初始化数据后直接计算,输出结果即可。

总结

1.LSTM的推导主要是参照SumWaiLiu的博客园自己梳理复现了一遍,这里强烈推荐这个博客,写得又清楚又好,推导的过程再一次加深了对反向传播算法的认识,其实本质就是找到梯度的反向递归式,通过对求导链式法则的推导,一步一步由后面的损失函数计算前面的损失函数。得到递推公式后,想要计算任何一个参数的梯度直接使用已经计算好的损失函数代入求导式子即可,这也是本学期最后一次推导反向传播的作业了(大概),从开始到现在,接触并学会了推导式子,而不是像之前一样只会拿到一个特例来算了,这是一个很大的进步。

2.在大佬的博客园认识到,其实对于RNN来说,梯度消失不是对于整体来说的,而是对于时间间隔较长的两个时间步之前,反向传播计算梯度时,涉及到矩阵连乘,故会丢失这一部分的梯度信息,也就降低了学习这一部分数据的能力,而对于间隔较短的时间步来说,发生梯度消失或梯度爆炸的情况比较少,还是能够学得相邻时间步的信息的。也就是说对于RNN梯度消失与爆炸是说丢失了较远时间步的信息,以较近时间步的信息为主导。在LSTM中,引入了内部记忆状态这一概念,为梯度的计算提供了一条“高速公路”,其计算方式保证了不会发生矩阵连乘,也就不容易发生梯度消失或爆炸,所以在每一次求梯度都有这条路径作为保证,从而改善了梯度消失。

3. 在编程实现PPT上的例子时,从应用的角度再次认识了LSTMCell与LSTM,对于这两个函数,只需要实例化时传入参数,并且在调用之前做好初始化工作,即可成功运行。实际上由于这两个API内部存在tanh激活函数与精确的数值计算,无法与课上的模拟例子绝对相同,但是这就是我们正常应用的状态,故也不需要特别进行调整。

参考 

【1】LSTM参数梯度推导与实现:对抗梯度消失,

【2】《神经网络的梯度推导与代码验证》之LSTM的前向传播和反向梯度推导 - SumwaiLiu - 博客园

【3】NNDL 作业十一 LSTM-CSDN博客

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

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

相关文章

Sigrity System Explorer DC IR Drop Analysis模式进行直流压降仿真分析操作指导

Sigrity System Explorer DC IR Drop Analysis模式进行直流压降仿真分析操作指导 Sigrity System Explorer DC IR Drop Analysis模式可以用于直流压降仿真分析,通过搭建简易拓扑用于前仿真分析,下面搭建一个简易的直流系统进行说明,以下图为例,准备好PCB的SPICE模型SpiceNe…

华为HarmonyOS实现跨多个子系统融合的场景化服务 -- 4 设置打开App Button

场景介绍 本章节将向您介绍如何使用Button组件打开APP功能&#xff0c;可调用对应Button组件打开另一个应用。 效果图展示 单击“打开APP”按钮&#xff0c;出现提示弹窗&#xff0c;单击“允许”&#xff0c;跳转至新的应用界面。 说明 弹窗是否弹出以及弹窗效果与跳转目标…

Spring Security 6 系列之二 - 基于数据库的用户认证和认证原理

之所以想写这一系列&#xff0c;是因为之前工作过程中使用Spring Security&#xff0c;但当时基于spring-boot 2.3.x&#xff0c;其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0&#xff0c;结果一看Spring Security也升级为6.3.0&#xff0c;关键是其风…

[笔记] Ubuntu Server 24.04安装MySql8,并配置远程连接

1、MySql安装 #更新列表 sudo apt update ​ #安装mysql sudo apt install mysql-server ​ #运行状态 mysql sudo service mysql status ​ # 安装完成&#xff0c;已自动启动&#xff0c;该步可以不用 启动 mysql sudo /etc/init.d/mysql start ​ # 该步骤可以不配置&…

软件开发中 Bug 为什么不能彻底消除

在软件开发中&#xff0c;Bug无法彻底消除的原因主要包括&#xff1a;软件复杂度高、人员认知与沟通受限、需求和环境不断变化、工具与测试覆盖不足、经济与时间成本制约。其中“需求和环境不断变化”尤为关键&#xff0c;因为在实际开发中&#xff0c;业务逻辑随着市场与用户反…

使用ElasticSearch实现全文检索

文章目录 全文检索任务描述技术难点任务目标实现过程1. java读取Json文件&#xff0c;并导入MySQL数据库中2. 利用Logstah完成MySQL到ES的数据同步3. 开始编写功能接口3.1 全文检索接口3.2 查询详情 4. 前端调用 全文检索 任务描述 在获取到数据之后如何在ES中进行数据建模&a…

《拉依达的嵌入式\驱动面试宝典》—C/CPP基础篇(三)

《拉依达的嵌入式\驱动面试宝典》—C/CPP基础篇(三) 你好,我是拉依达。 感谢所有阅读关注我的同学支持,目前博客累计阅读 27w,关注1.5w人。其中博客《最全Linux驱动开发全流程详细解析(持续更新)-CSDN博客》已经是 Linux驱动 相关内容搜索的推荐首位,感谢大家支持。 《拉…

调用完BAPI_PO_CREATE1创建采购订单之后,如果不调用BAPI_TRANSACTION_COMMIT,数据库里面没有数

在调用完BAPI_PO_CREATE1创建采购订单之后&#xff0c;如果不调用BAPI_TRANSACTION_COMMIT&#xff0c;那么就无法生成真正的采购订单号&#xff0c;在数据库里面没有数 运行结果 特别注意

linux(CentOS8)安装PostgreSQL16详解

文章目录 1 下载安装包2 安装3 修改远程连接4 开放端口 1 下载安装包 官网下载地址&#xff1a;https://www.postgresql.org/download/ 选择对应版本 2 安装 #yum源 yum -y install wget https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redha…

如何通过递延型指标预测项目的长期成果?

递延型指标&#xff08;Deferred Metrics&#xff09;是指那些并不立即反映或直接影响当前操作、决策或行为的指标&#xff0c;而是随着时间的推移&#xff0c;才逐渐显现出影响效果的指标。这类指标通常会在一段时间后反映出来&#xff0c;或者需要一定的周期才能展现其成果或…

Reactor 响应式编程(第四篇:Spring Security Reactive)

系列文章目录 Reactor 响应式编程&#xff08;第一篇&#xff1a;Reactor核心&#xff09; Reactor 响应式编程&#xff08;第二篇&#xff1a;Spring Webflux&#xff09; Reactor 响应式编程&#xff08;第三篇&#xff1a;R2DBC&#xff09; Reactor 响应式编程&#xff08…

力扣-图论-14【算法学习day.64】

前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向和记录学习过程&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非…

Leetcode经典题11--加油站

题目描述 在一条环路上有 n 个加油站&#xff0c;其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车&#xff0c;从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发&#xff0c;开始时油箱为空。 给定两个整数数组 gas 和…

前端退出对话框也就是点击右上角的叉,显示灰色界面,已经解决

文章目录 遇到一个前端bug&#xff0c;点击生成邀请码 打开对话框 然后我再点击叉号&#xff0c;退出对话框&#xff0c;虽然退出了对话框&#xff0c;但是显示灰色界面。如下图&#xff1a; 导致界面就会失效&#xff0c;点击任何地方都没有反应。 发现是如下代码的问题&am…

软件需求概述(尊享版)

软件需求与软件分析 软件需求&#xff1a;用户角度&#xff0c;注重软件外在表现 软件分析&#xff1a;开发者角度&#xff0c;注重软件内部逻辑结构 面向对象分析模型 类/对象模型&#xff08;全部的类和对象&#xff09; 对象-关系模型&#xff08;对象之间的静态关系&…

配置Hugging_face国内镜像

目录 随着人工智能技术的蓬勃发展&#xff0c;Huggingface这一开源平台已成为研究者和开发者的宝贵资源&#xff0c;提供了丰富的预训练模型&#xff0c;助力自然语言处理任务的快速推进。然而&#xff0c;对于身处国内的我们而言&#xff0c;访问Huggingface主仓库时&#xff…

Rust之抽空学习系列(四)—— 编程通用概念(下)

Rust之抽空学习系列&#xff08;四&#xff09;—— 编程通用概念&#xff08;下&#xff09; 1、函数 函数用来对功能逻辑进行封装&#xff0c;能够增强复用、提高代码的可读 以下是函数的主要组成部分&#xff1a; 名称参数返回类型函数体 1.1、函数名称 在Rust中&…

电脑游戏运行时常见问题解析:穿越火线提示“unityplayer.dll丢失”的修复指南

电脑游戏运行时常见问题解析&#xff1a;穿越火线提示“unityplayer.dll丢失”的修复指南 在探索电脑游戏的无限乐趣时&#xff0c;我们时常会遇到一些不期而遇的挑战。今天&#xff0c;我们将聚焦于一个常见的游戏运行错误——穿越火线&#xff08;或其他使用Unity引擎的游戏…

Mac系统下 jdk和maven 安装教程

一、jdk安装教程 1、先去官网选择对应版本下载 官网网址&#xff1a;Java SE | Oracle Technology Network | Oracle 中国 这里我选择的是jdk8的版本&#xff0c;如果你们想下载更高的版本就选择其他版本&#xff0c;目前大部分公司和教程使用jdk8的版本比较多。 点击macos&a…

Python -- Linux中的Matplotlib图中无法显示中文 (中文为方框)

目的 用matplotlib生成的图中文无法正常显示 方法 主要原因: 没找到字体 进入windows系统的C:\Windows\Fonts目录, 复制自己想要的字体 粘贴到Linux服务器中对应python文件所处的文件夹内 设置字体: 设置好字体文件的路径在需要对字体设置的地方设置字体 效果 中文正常显…