使用PyTorch构建神经网络,并使用thop计算参数和FLOPs

news2024/12/25 23:58:31

文章目录

    • 使用PyTorch构建神经网络,并使用thop计算参数和FLOPs
      • FLOPs和FLOPS区别
      • 使用PyTorch搭建神经网络
        • 整体代码
        • 1. 导入必要的库
        • 2. 定义神经网络模型
        • 3. 打印网络结构
        • 4. 计算网络FLOPs和参数数量
        • 5. 结果如下
        • 手动计算params
        • 手动计算FLOPs
        • 注意

使用PyTorch构建神经网络,并使用thop计算参数和FLOPs

在这里插入图片描述

FLOPs和FLOPS区别

FLOPs(floating point operations)是指浮点运算次数,通常用来评估一个计算机算法或者模型的计算复杂度。在机器学习中,FLOPs通常用来衡量神经网络的计算复杂度,因为神经网络的计算主要由矩阵乘法和卷积操作组成,而这些操作都可以转化为浮点运算次数的形式进行计算。

FLOPS(floating point operations per second)是指每秒钟可以执行的浮点运算次数,通常用来评估一个计算机系统的计算能力。在机器学习中,FLOPS也可以用来衡量计算机系统的性能,因为神经网络的训练和推断需要大量的浮点运算,计算机系统的FLOPS越高,就越能够快速地完成神经网络的计算任务。

需要注意的是,FLOPs和FLOPS都是衡量计算复杂度和计算能力的指标,但它们的单位不同,FLOPs的单位是次,而FLOPS的单位是次/秒。FLOPs和FLOPS是两个不同的概念,FLOPs是指浮点运算次数,而FLOPS是指每秒钟可以执行的浮点运算次数。在实际应用中,我们通常会同时考虑这两个指标,以评估计算机算法或者模型在不同的计算机系统上的表现。

使用PyTorch搭建神经网络

整体代码

import torch
import torch.nn as nn
from torchsummary import summary
from thop import profile, clever_format


device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
class MyNet(nn.Module):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1)
        self.relu1 = nn.ReLU(inplace=True)
        self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=1, padding=1)
        self.relu2 = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=2, stride=2)
        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1)
        self.relu3 = nn.ReLU(inplace=True)
        self.conv4 = nn.Conv2d(128, 128, kernel_size=3, stride=1, padding=1)
        self.relu4 = nn.ReLU(inplace=True)
        self.fc1 = nn.Linear(128*8*8, 1024)
        self.relu5 = nn.ReLU(inplace=True)
        self.fc2 = nn.Linear(1024, 10)

    def forward(self, x):
        x = self.conv1(x)
        x = self.relu1(x)
        x = self.conv2(x)
        x = self.relu2(x)
        x = self.maxpool(x)
        x = self.conv3(x)
        x = self.relu3(x)
        x = self.conv4(x)
        x = self.relu4(x)
        x = x.view(-1, 128*8*8)
        x = self.fc1(x)
        x = self.relu5(x)
        x = self.fc2(x)
        return x
    
net = MyNet().to(device)
input_shape = (3, 224, 224)
summary(net, input_shape)

input_tensor = torch.randn(1, *input_shape).to(device)
flops, params = profile(net, inputs=(input_tensor,))
flops, params = clever_format([flops, params], "%.3f")
print("FLOPs: %s" %(flops))
print("params: %s" %(params))

这段代码是一个使用PyTorch实现的卷积神经网络。下面是一步一步的教程:

1. 导入必要的库

import torch
import torch.nn as nn
from torchsummary import summary
from thop import profile, clever_format

其中,torch是PyTorch深度学习框架的核心库,torch.nn是PyTorch中神经网络相关的模块,torchsummary是用于打印网络结构的库,thop是用于计算网络FLOPs和参数数量的库。

2. 定义神经网络模型

class MyNet(nn.Module):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1)
        self.relu1 = nn.ReLU(inplace=True)
        self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=1, padding=1)
        self.relu2 = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=2, stride=2)
        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1)
        self.relu3 = nn.ReLU(inplace=True)
        self.conv4 = nn.Conv2d(128, 128, kernel_size=3, stride=1, padding=1)
        self.relu4 = nn.ReLU(inplace=True)
        self.fc1 = nn.Linear(128*8*8, 1024)
        self.relu5 = nn.ReLU(inplace=True)
        self.fc2 = nn.Linear(1024, 10)

    def forward(self, x):
        x = self.conv1(x)
        x = self.relu1(x)
        x = self.conv2(x)
        x = self.relu2(x)
        x = self.maxpool(x)
        x = self.conv3(x)
        x = self.relu3(x)
        x = self.conv4(x)
        x = self.relu4(x)
        x = x.view(-1, 128*8*8)
        x = self.fc1(x)
        x = self.relu5(x)
        x = self.fc2(x)
        return x

这个网络模型包括了4个卷积层、2个全连接层和ReLU激活函数。其中,nn.Conv2d是PyTorch中的二维卷积层,nn.ReLU是ReLU激活函数层,nn.MaxPool2d是最大池化层,nn.Linear是全连接层。这个模型的输入是一个3通道、224x224大小的图像,输出是一个10维的向量,分别表示输入图像属于10个不同的类别的概率。

3. 打印网络结构

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
net = MyNet().to(device)
input_shape = (3, 224, 224)
summary(net, input_shape)

这里我们首先根据设备是否支持CUDA来选择使用CPU或GPU,然后将模型实例化为net并将其放到设备上。接着,我们定义输入图像的形状为(3, 224, 224),然后使用summary函数打印网络的结构信息,包括每一层的输入和输出形状、参数数量等。

4. 计算网络FLOPs和参数数量

input_tensor = torch.randn(1, *input_shape).to(device)
flops, params = profile(net, inputs=(input_tensor,))
flops, params = clever_format([flops, params], "%.3f")
print("FLOPs: %s" %(flops))
print("params: %s" %(params))

这里我们使用随机生成的输入图像进行前向传播,然后使用profile函数计算网络的FLOPs和参数数量。inputs参数接受一个元组,其中包含了网络的输入,这里我们将随机生成的输入图像封装成一个元组传入。计算完成后,使用clever_format函数将FLOPs和参数数量格式化成易读的字符串形式,最后打印出来。

5. 结果如下

在这里插入图片描述

----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1         [-1, 64, 224, 224]           1,792
              ReLU-2         [-1, 64, 224, 224]               0
            Conv2d-3         [-1, 64, 224, 224]          36,928
              ReLU-4         [-1, 64, 224, 224]               0
         MaxPool2d-5         [-1, 64, 112, 112]               0
            Conv2d-6        [-1, 128, 112, 112]          73,856
              ReLU-7        [-1, 128, 112, 112]               0
            Conv2d-8        [-1, 128, 112, 112]         147,584
              ReLU-9        [-1, 128, 112, 112]               0
           Linear-10                 [-1, 1024]       8,389,632
             ReLU-11                 [-1, 1024]               0
           Linear-12                   [-1, 10]          10,250
================================================================
Total params: 8,660,042
Trainable params: 8,660,042
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.57
Forward/backward pass size (MB): 153.14
Params size (MB): 33.04
Estimated Total Size (MB): 186.75
----------------------------------------------------------------
[INFO] Register count_convNd() for <class 'torch.nn.modules.conv.Conv2d'>.
[INFO] Register zero_ops() for <class 'torch.nn.modules.activation.ReLU'>.
[INFO] Register zero_ops() for <class 'torch.nn.modules.pooling.MaxPool2d'>.
[INFO] Register count_linear() for <class 'torch.nn.modules.linear.Linear'>.
FLOPs: 6.357G
params: 8.660M

这段代码定义了一个简单的卷积神经网络模型,然后使用thop库计算模型的FLOPs(浮点运算次数)和参数数量。

手动计算params

好的,这里是每一层的参数计算公式:

  • Conv2d层:参数数量 = (输入通道数 x 卷积核高度 x 卷积核宽度 x 输出通道数) + 输出通道数
  • Linear层:参数数量 = 输入大小 x 输出大小 + 输出大小
  • 没有参数的层(如ReLU和MaxPool)不需要计算参数数量。

具体来说,在这个神经网络中,每一层的参数计算公式和参数数量如下:

  • Conv2d-1:(3 x 3 x 3 x 64) + 64 = 1,792
  • ReLU-2:没有参数
  • Conv2d-3:(3 x 3 x 64 x 64) + 64 = 36,928
  • ReLU-4:没有参数
  • MaxPool2d-5:没有参数
  • Conv2d-6:(3 x 3 x 64 x 128) + 128 = 73,856
  • ReLU-7:没有参数
  • Conv2d-8:(3 x 3 x 128 x 128) + 128 = 147,584
  • ReLU-9:没有参数
  • Linear-10:(128 x 56 x 56 x 1024) + 1024 = 8,389,632
  • ReLU-11:没有参数
  • Linear-12:(1024 x 10) + 10 = 10,250

总参数数为8,660,042个。

手动计算FLOPs

Thop(PyTorch-OpCounter)是一个用于计算PyTorch模型浮点运算量(FLOPs)的库,它可以自动计算模型中每个操作的FLOPs,包括卷积层、池化层、全连接层等。

在Thop中,FLOPs的计算基于每个操作的输入张量大小、输出张量大小以及操作的参数数量。具体地,对于一个卷积层,FLOPs的计算公式为:

F L O P s = 2 × k 2 × C i n × C o u t × H o u t × W o u t s t r i d e 2 FLOPs = 2 \times k^2 \times C_{in} \times C_{out} \times \frac{H_{out} \times W_{out}}{stride^2} FLOPs=2×k2×Cin×Cout×stride2Hout×Wout

其中, k k k 是卷积核大小, C i n C_{in} Cin 是输入通道数, C o u t C_{out} Cout 是输出通道数, H o u t H_{out} Hout W o u t W_{out} Wout 是输出特征图的高度和宽度, s t r i d e stride stride 是步幅。这个公式中的常数2表示每个卷积操作需要2次乘法(一个是卷积核和输入的卷积,另一个是卷积结果和偏置的加法),因此需要乘以2。

对于其他层类型,Thop使用不同的公式计算FLOPs。例如,对于全连接层,FLOPs的计算公式为:

F L O P s = 2 × C i n × C o u t FLOPs = 2 \times C_{in} \times C_{out} FLOPs=2×Cin×Cout

其中, C i n C_{in} Cin C o u t C_{out} Cout 分别是输入和输出的特征数量。

通过使用Thop库,您可以方便地计算模型的总体FLOPs,以评估模型的计算复杂度和性能。

池化层和ReLU层的计算复杂度相对简单,可以用以下公式进行计算:

对于池化层,假设输入特征图大小为 W 1 × H 1 W_1\times H_1 W1×H1,池化尺寸为 k × k k\times k k×k,步幅为 s s s,则池化层的FLOPs计算公式为:

F L O P s = W 2 × H 2 × C × k 2 FLOPs = W_2\times H_2\times C\times k^2 FLOPs=W2×H2×C×k2

其中, W 2 = ⌊ ( W 1 − k ) / s ⌋ + 1 W_2=\lfloor(W_1-k)/s\rfloor+1 W2=⌊(W1k)/s+1 H 2 = ⌊ ( H 1 − k ) / s ⌋ + 1 H_2=\lfloor(H_1-k)/s\rfloor+1 H2=⌊(H1k)/s+1 C C C 是输入特征图的通道数。这个公式中的常数 k 2 k^2 k2表示每个池化操作需要 k × k k\times k k×k次取最大值。

对于ReLU层,假设输入特征图大小为 W × H × C W\times H\times C W×H×C,ReLU层的计算量可以近似为:

F L O P s = W × H × C FLOPs = W\times H\times C FLOPs=W×H×C

这是因为ReLU激活函数的计算本身非常简单,只需要比较输入数据与0,然后保留大于0的值即可,因此单个ReLU激活函数的计算量可以视为常数。

需要注意的是,这些公式只是近似计算,实际的计算复杂度可能会因为不同实现方式、硬件平台等因素而有所不同。对于更准确的计算,可以使用一些工具库(如Thop)来进行计算。

为了解释为什么得到的FLOPs是6.357G,我们需要逐层分析模型中的计算量。以下是计算步骤(不考虑relu和pool层):

  1. conv1: (3 × 3 × 3) × 64 × 224 × 224 = 86,704,128 FLOPs
  2. relu1: 0 FLOPs(ReLU激活函数的计算量通常不计入FLOPs) # 3211264
  3. conv2: (3 × 3 × 64) × 64 × 224 × 224 = 1,849,688,064 FLOPs
  4. relu2: 0 FLOPs # 3211264
  5. maxpool: 0 FLOPs(池化操作的计算量通常不计入FLOPs) # 32111254
  6. conv3: (3 × 3 × 64) × 128 × 112 × 112 = 924,844,032 FLOPs
  7. relu3: 0 FLOPs 128112112=1605632
  8. conv4: (3 × 3 × 128) × 128 × 112 × 112 = 1,849,688,064 FLOPs
  9. relu4: 0 FLOPs # 1605632
  10. fc1: 128 × 8 × 8 × 1024 = 8,388,608 FLOPs
  11. relu5: 0 FLOPs # 这个计算FLOPs我懵了。按20480吧
  12. fc2: 1024 × 10 = 10,240 FLOPs

将各层的FLOPs相加,得到总的FLOPs:

86,704,128 + 1,849,688,064 + 924,844,032 + 1,849,688,064 + 8,388,608 + 10240 = 4,719,323,136 FLOPs

然后将FLOPs转换为GigaFLOPs(10^9 FLOPs):

4,719,323,136 / 10^9 ≈ 4.719 GigaFLOPs

4.719远远不等于6.357啊!

如果按照全部乘以2,又变成9点多了,又远远超过6.357了。

听说profile算出来的FLOPs也需要乘以2。按照这么想的话,咱们手动计算的结果不乘2应该和thop计算出来的相当。但是结果相当打脸,远远不等。

所以relu层和pool层肯定带入FLOPs了。

加粗样式
为了计算方便和准确性,实际并没有多大意义。你可太会说话了。

恩。。。怎么说呢

注意

显然,结果与代码中计算得到的6.357G不符。这就有点奇怪了?

消失的1.638G去哪里了?

就是把relu层和池化层的全部加上也不够啊!加上relu和池化层的结果如下。

86,704,128 + 3211264 + 1,849,688,064 + 3211254 + 32111254 + 924,844,032 + 1605632 + 1,849,688,064 + 1605632 + 8,388,608 + 10240 + 10240 = 4,761,078,412 FLOPs

relu和maxpool再怎么套公式,也算不上去了,加不了那么多。算了。。。。以后有时间再慢慢算吧,谁和thop计算的一样可以留言公式,我去学习一下。

G去哪里了?

就是把relu层和池化层的全部加上也不够啊!加上relu和池化层的结果如下。

86,704,128 + 3211264 + 1,849,688,064 + 3211254 + 32111254 + 924,844,032 + 1605632 + 1,849,688,064 + 1605632 + 8,388,608 + 10240 + 10240 = 4,761,078,412 FLOPs

relu和maxpool再怎么套公式,也算不上去了,加不了那么多。算了。。。。以后有时间再慢慢算吧,谁和thop计算的一样可以留言公式,我去学习一下。

**可能原因:**这可能是因为thop库在计算FLOPs时考虑了一些其他因素,relu和pool也计算进去了,而且占比还挺多,莫名增加了1.638G。因此,实际计算得到的FLOPs可能会与手动计算的结果有所不同。即使如此,手动计算的结果可以帮助我们理解FLOPs的大致数量级。

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

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

相关文章

车联网强势发展下,有什么隐患?

通过新一代信息通信技术&#xff0c;车联网实现了汽车与云平台&#xff0c;车辆和汽车&#xff0c;道路&#xff0c;汽车和人以及内部的全方位网络链接。车联网使用传感器技术感知车辆的状态信息&#xff0c;并利用无线通信网络和现代智能信息处理技术的帮助实现交通智能化管理…

FPGA入门系列10--按键消抖

文章简介 本系列文章主要针对FPGA初学者编写&#xff0c;包括FPGA的模块书写、基础语法、状态机、RAM、UART、SPI、VGA、以及功能验证等。将每一个知识点作为一个章节进行讲解&#xff0c;旨在更快速的提升初学者在FPGA开发方面的能力&#xff0c;每一个章节中都有针对性的代码…

Redis高可用系列——Set类型底层详解

文章目录 概述intsetintset 和 hashtable 的转换为什么加入了listpackhashtable 的空间开销高hashtable 的碰撞概率高intset 、listpack和hashtable的转换 概述 在讲解set结构之前&#xff0c;需要先说明一下set结构编码的更替&#xff0c;如下 在Redis7.2之前&#xff0c;se…

Ansys Lumerical | CMOS - 光学仿真方法

通过使用更小的像素尺寸和更大的填充因子&#xff0c;基于CMOS图像传感器像素的数码相机系统的成本正在降低。但是&#xff0c;只有在不牺牲图像质量的情况下&#xff0c;CMOS像素尺寸减小才是可以接受的。随着CMOS像素尺寸的不断减小&#xff0c;图像信噪比降低&#xff0c;相…

《我命由我不由天》蔡志忠——笔记一

目录 简介 经典摘录 三岁决定一生 父母该什么时候放手 确定将来要成为什么 积极主动为目标而努力 叛逆是最伟大的创意 父亲给蔡志忠最大的影响是教会他两件事 价值观缺陷导致的后果 人有三个阶段 简介 作者 蔡志忠&#xff0c;李虹。 蔡志忠&#xff1a;漫画家、哲…

力扣-1769. 移动所有球到每个盒子所需的最小操作数

题目&#xff1a; 有 n 个盒子。给你一个长度为 n 的二进制字符串 boxes &#xff0c;其中 boxes[i] 的值为 ‘0’ 表示第 i 个盒子是 空 的&#xff0c;而 boxes[i] 的值为 ‘1’ 表示盒子里有 一个 小球。 在一步操作中&#xff0c;你可以将 一个 小球从某个盒子移动到一个与…

搜索引擎优化SEO和SEM有什么不一样

SEO&#xff08;搜索引擎优化&#xff09;和SEM&#xff08;搜索引擎营销&#xff09;都是用于提高网站在搜索引擎中的排名和能见度的技术。虽然它们的目标是相同的&#xff0c;但它们的方法和重点略有不同&#xff0c;今天和大家聊聊SEO和SEM有什么不同。 一、SEO SEO是指通…

红帽8配置yum源

使用传输工具 上传文件到/etc/yum.repos.d/ 或 wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-8.repo 注&#xff1a;不能下载wget直接上传文件 关闭订阅插件提示 [redhatroot ~]$ vi /etc/yum.conf #添加 plugins0 [redhatroot ~]$ …

docker+jenkins自动化部署springboot项目

前置:环境配置 阿里云服务器1核2GBjava 1.8.0._371maven apache-maven3.8.8git 1.8.3.1 docker和jenkins在一台服务器上&#xff0c;环境的配置&#xff0c;对于java和maven可以自己下载对应tar包进行配置&#xff0c;记得配置环境&#xff0c;也可以使用yum进行安装。记得配置…

FS5175AE降压型1-4节锂电池充电芯片

FS5175AE是一款工作于5V到24V的多串锂电池同步开关降压充电管理芯片。内置MOS管集成了低导通阻抗的NMOS&#xff0c;FS5175AE采用1MHz同步开关架构&#xff0c;实现高 效率充电并简化外围器件&#xff0c;降低BOM成本。通过调节检测电阻&#xff0c;可实现**2A充电电流&#xf…

【Java校招面试】基础知识(八)——Linux服务器

目录 前言一、基础概念二、常用命令后记 前言 本篇主要介绍Linux服务器的相关内容。 “基础知识”是本专栏的第一个部分&#xff0c;本篇博文是第八篇博文&#xff0c;如有需要&#xff0c;可&#xff1a; 点击这里&#xff0c;返回本专栏的索引文章点击这里&#xff0c;返回…

仿抖音开发需要注意的问题

一、版权问题 仿抖音开发需要注意版权问题&#xff0c;包括内容的版权和软件的版权。在开发的过程中&#xff0c;不要直接抄袭他人的作品&#xff0c;应该注重保护知识产权。 二、安全性问题 仿抖音开发需要重视应用的安全性问题&#xff0c;避免应用在使用过程中发生安全漏…

OpenCv 图像的算数运算

1. 图像加法 函数 cv.add(img1, img2) 参数中的img1 和 img2 应该是相同的深度和类型&#xff0c; 或者第二个图像可以是像素值 代码示例: >>> x np.uint8([250]) >>> y np.uint8([10])>>> print(cv.add(x,y)) #250 10 260 > 255 [[255]]&g…

Winform控件数据绑定 DataBindings

目录 引言 绑定的方式 双向绑定 验证时更改数据源 立即更改数据源 单向绑定 绑定方法 属性界面选择绑定 通过代码手动绑定 绑定自定义数据类型 引言 DataBindings 的出现显然是为了解决后台数据与前端界面的同步问题&#xff0c;通过绑定控件属性与对象属性&#xff0c;解决…

Spring Cloud整合XXL-Job

目录 第一步&#xff1a; 第二步&#xff1a; 第三步&#xff1a; 第四步&#xff1a; 重点&#xff1a; 第一步&#xff1a; 整合pom文件&#xff0c;在Spring Cloud中添加XXL-Job的依赖 <!-- xxl-job-core --> <dependency><groupId>com.xuxueli<…

工业设备安装可视化AR互动培训降低企业成本

随着污水处理厂构筑物、设备、管阀及电器、仪表、自控等工艺设施逐步大型化、复杂化、多样化,污水厂日常运行安全检查尤为重要。通过对不同污水处理工艺运行厂家实际调研分析&#xff0c;发现开发污水厂AR远程可视化巡检系统是很多厂家的实际需求。 AR远程可视化巡检系统也被称…

JAVA—— Steam流

一、 引言 初识Stream流的作用&#xff1a; 需求&#xff1a;按照下面的要求完成集合的创建和遍历&#xff0c;创建一个集合&#xff0c;存储多个字符串元素 通过下面代码&#xff0c;显然我们清晰的看到使用Stream流更为方便&#xff0c;而使用不同的集合遍历就有些复杂。 i…

【CMIP6月、日数据】【ERA5-LAND陆面再分析数据】【全球VIPPHEN物候数据】

国际耦合模式比较计划进入新的阶段——第六阶段&#xff08;CMIP6&#xff09;&#xff0c;这将为气候变化研究领域提供更丰富的全球气候模式数据。相比于 CMIP5&#xff0c;CMIP6 模式有两个主要的特点&#xff1a;一是 CMIP6 考虑的过程更为复杂&#xff0c;很多模式实现了大…

js - typeof与instanceof类型判断的区别

1&#xff0c;typeof 描述&#xff1a;运算符返回一个字符串&#xff0c;表示操作数的类型。 常用的类型判断 console.log(typeof 42); // numberconsole.log(typeof "blubber"); // stringconsole.log(typeof true); // booleanconsole.log(…

【Linux】Linux入门学习之常用命令一

介绍 这里是小编成长之路的历程&#xff0c;也是小编的学习之路。希望和各位大佬们一起成长&#xff01; 以下为小编最喜欢的两句话&#xff1a; 要有最朴素的生活和最遥远的梦想&#xff0c;即使明天天寒地冻&#xff0c;山高水远&#xff0c;路远马亡。 一个人为什么要努力&a…