P with Spacy:自定义文本分类管道

一、说明

        Spacy 是一个功能强大的 NLP 库,其中许多 NLP 任务(如标记化、词干提取、词性标记和命名实体解析)均通过预训练模型提供开箱即用的功能。所有这些任务都由管道对象以及逐步应用于给定文本的不同函数的内部抽象来包装。该管道可以通过自编写的函数进行定制和扩展。

        在本文中,您将了解如何将文本分类添加到 Spacy 管道。给定带有文档和标签的训练数据,额外的分类任务可以学习预测其他文档的标签。您将了解如何构建训练数据、如何使用分类步骤扩展默认管道,以及如何添加您自己的分类实现。

        本文的技术背景是Python v3.11 和spacy v3.7.2。所有示例也应该适用于较新的库版本。

        本文最初出现在我的博客admantium.com。

二、自定义 Spacy 管道示例

注意:以下所有解释和代码片段均来自三个来源:官方 Rasa API 文档、关于 使用 Spacy 3.0 进行文本分类的博客文章,以及一篇关于构建生产级 Spacy 文本分类的博客文章。

添加文本分类任务可归结为以下步骤:

  1. 准备训练数据
  2. 将训练数据转换为 Spacy DocBin 对象
  3. 创建默认文本分类配置
  4. 训练模型
  5. 使用模型

以下部分详细介绍了这些步骤。

三、步骤一:准备训练数据

        训练数据需要是原始文本及其单标签或多标签类别。让我们通过使用 NLTK 库中包含的路透社新闻数据集来实现这一点。

以下代码片段显示了如何加载数据集,然后打印文本及其类别。


from nltk.corpus import reutersprint(len(reuters.fileids()))
# 10788print(reuters.raw(reuters.fileids()[42]))
# JAPAN GIVEN LITTLE HOPE OF AVOIDING U.S. SANCTIONS
#  A top U.S. Official said Japan has little
#  chance of persuading the U.S. to drop threatened trade
#  sanctions, despite the efforts of a Japanese team that left for
#  Washington today.print(reuters.categories(reuters.fileids()[42]))
# ['trade']

该数据集共有 90 个类别。此外,有些文章有多个标签。

print(len(reuters.categories()))
# 90print(reuters.categories())
# ['acq', 'alum', 'barley', 'bop', 'carcass', 'castor-oil', 'cocoa', 'coconut', 'coconut-oil', 'coffee', 'copper', 'copra-cake', 'corn', 'cotton', 'cotton-oil', 'cpi', 'cpu', 'crude', 'dfl', 'dlr', 'dmk', 'earn', 'fuel', 'gas', 'gnp', 'gold', 'grain', 'groundnut', 'groundnut-oil', 'heat', 'hog', 'housing', 'income', 'instal-debt', 'interest', 'ipi', 'iron-steel', 'jet', 'jobs', 'l-cattle', 'lead', 'lei', 'lin-oil', 'livestock', 'lumber', 'meal-feed', 'money-fx', 'money-supply', 'naphtha', 'nat-gas', 'nickel', 'nkr', 'nzdlr', 'oat', 'oilseed', 'orange', 'palladium', 'palm-oil', 'palmkernel', 'pet-chem', 'platinum', 'potato', 'propane', 'rand', 'rape-oil', 'rapeseed', 'reserves', 'retail', 'rice', 'rubber', 'rye', 'ship', 'silver', 'sorghum', 'soy-meal', 'soy-oil', 'soybean', 'strategic-metal', 'sugar', 'sun-meal', 'sun-oil', 'sunseed', 'tea', 'tin', 'trade', 'veg-oil', 'wheat', 'wpi', 'yen', 'zinc']

四、步骤 2:将训练数据转换为 Spacy DocBin 对象

        Spacy 要求其输入数据集使用附加的 属性来预处理 Doc 对象。类别应该是一个 对象,其中键是类别,值反映该类别是否适用于文章。这是一个整数值。catsdict

以下是如何转换路透社新闻数据集第一篇文章的示例:

nlp = spacy.load('en_core_web_lg')fileid = reuters.fileids()[42]
cat_dict = {cat: 0 for cat in reuters.categories()}doc1 = nlp.make_doc(reuters.raw(fileid))
for cat in reuters.categories(fileid):cat_dict[cat] = 1doc1.cats = cat_dictprint(doc1.cats)
# {'acq': 0, 'alum': 0, 'barley': 0, 'bop': 0, ..., 'trade': 1, ...}

此外,数据需要转换为Spacy自定义数据对象DocBin。预计数据需要分为训练和测试,以下方法创建多标签分类,其中 doc 对象处理每篇文章的原始文本,并包含 dict 所有适用类别的对象。

    
def convert_multi_label(dataset, filename):db = DocBin()nlp = spacy.load('en_core_web_lg')total = len(dataset)print(f'{time()}: start processing {filename} with {total} files')for index, fileid in enumerate(dataset):print(f'Processing {index+1}/{total}')cat_dict = {cat: 0 for cat in reuters.categories()}for cat in reuters.categories(fileid):cat_dict[cat] = 1doc = nlp(get_text(fileid))doc.cats = cat_dictdb.add(doc)print(f'{time()}: finish processing {filename}')db.to_disk(filename)#convert(training, 'reuters_training.spacy')convert_multi_label(training, 'reuters_training_multi_label.spacy')
# 1688400699.331844: start processing reuters_training_single_label.spacy with 7769 files
# Processing 1/7769
# Processing 2/7769
# ...

二进制文件的预览:

五、步骤 3:创建默认文本分类配置

Spacy 提供了一个方便的命令行界面来创建用于多种目的的默认配置文件。从 spacy init config 开始,可以添加额外的 pipeline 步骤。对于分类,两个可能的值是 textcat(针对单标签)和 textcat_multilabel(针对多标签分类)。此外,通过包含选项 --optimize accuracy,训练过程将使用预训练模型中的词向量来表示文本。

这些考虑因素导致以下命令:

> python -m spacy init config --pipeline textcat_multilabel --optimize accuracy spacy_categorization_pipeline.cfg2023-07-02 13:26:37.103543: I tensorflow/core/platform/cpu_feature_guard.cc:182] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.
To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
ℹ Generated config template specific for your use case
- Language: en
- Pipeline: textcat
- Optimize for: efficiency
- Hardware: CPU
- Transformer: None
✔ Auto-filled config with all values
✔ Saved config
spacy_categorization_pipeline.cfg
You can now add your data and train your pipeline:
python -m spacy train spacy_categorization_pipeline.cfg --paths.train ./train.spacy --paths.dev ./dev.spacy

让我们看一下这个文件的某些部分:

[paths]
train = null
dev = null
vectors = "en_core_web_lg"
init_tok2vec = null#...
[nlp]
lang = "en"
pipeline = ["tok2vec","textcat_multilabel"]
batch_size = 1000
disabled = []
before_creation = null
after_creation = null
after_pipeline_creation = null
tokenizer = {"@tokenizers":"spacy.Tokenizer.v1"}[components]
[components.textcat_multilabel]
factory = "textcat_multilabel"
scorer = {"@scorers":"spacy.textcat_multilabel_scorer.v2"}
threshold = 0.5
[components.textcat_multilabel.model]
@architectures = "spacy.TextCatEnsemble.v2"
nO = null
[components.textcat_multilabel.model.tok2vec]
@architectures = "spacy.Tok2VecListener.v1"
width = ${components.tok2vec.model.encode.width}
upstream = "*"
[components.tok2vec]
factory = "tok2vec"
[components.tok2vec.model]
@architectures = "spacy.Tok2Vec.v2"
#...[training]
dev_corpus = "corpora.dev"
train_corpus = "corpora.train"
seed = ${system.seed}
gpu_allocator = ${system.gpu_allocator}
dropout = 0.1
accumulate_gradient = 1
patience = 1600
max_epochs = 0
max_steps = 20000
eval_frequency = 200
frozen_components = []
annotating_components = []
before_to_disk = null
before_update = null
#...[training.logger]
@loggers = "spacy.ConsoleLogger.v1"
progress_bar = false
[training.optimizer]
@optimizers = "Adam.v1"
beta1 = 0.9
beta2 = 0.999
L2_is_weight_decay = true
L2 = 0.01
grad_clip = 1.0
use_averages = false
eps = 0.00000001
learn_rate = 0.001

此配置公开了所有与训练相关的参数,从包含训练和测试数据的输入文件、使用的语料库、数据的记录和批量分离以及具有优化器和学习率的训练算法开始。对所有选项的详细讨论超出了本文的范围 - 目前,所有默认值均保持原样。

六、第 4 步:训练模型

首先,需要将数据分为训练数据集和测试数据集。方便的是,路透社的文章已经预先分割。以下代码片段划分数据并显示训练数据中的示例文章及其类别。

training = [doc for doc in reuters.fileids() if doc.find('training') == 0]
testing = [doc for doc in reuters.fileids() if doc.find('test') == 0]print(len(training))
# 7769print(len(testing))
# 3019

训练过程再次通过命令行启动。需要传递配置文件的名称、训练和测试数据集文件名以及输出目录。这是一个例子:

run python -m spacy train spacy_multi_label_categorization_pipeline.cfg \
--paths.train reuters_training_multi_label.spacy \
--paths.dev reuters_testing_multi_label.spacy \
--output textcat_multilabel_model

输出日志显示进度...

=========================== Initializing pipeline ===========================
[2023-07-03 18:24:10,639] [INFO] Set up nlp object from config
[2023-07-03 18:24:10,656] [INFO] Pipeline: ['tok2vec', 'textcat_multilabel']
[2023-07-03 18:24:10,660] [INFO] Created vocabulary
[2023-07-03 18:24:12,365] [INFO] Added vectors: en_core_web_lg
[2023-07-03 18:24:12,365] [INFO] Finished initializing nlp object
[2023-07-03 18:24:31,806] [INFO] Initialized pipeline components: ['tok2vec', 'textcat_multilabel']
✔ Initialized pipeline============================= Training pipeline =============================
ℹ Pipeline: ['tok2vec', 'textcat_multilabel']
ℹ Initial learn rate: 0.001
# ...
✔ Saved pipeline to output directory
textcat_multilabel_model/model-last

…以及一个显示训练过程和几个指标的表格。

E    #       LOSS TOK2VEC  LOSS TEXTC...  CATS_SCORE  SCORE
---  ------  ------------  -------------  ----------  ------0       0          2.51           0.39       48.98    0.490     200         24.56           5.04       39.44    0.390     400          0.00           2.86       39.94    0.400     600          0.01           3.87       40.27    0.400     800          1.02           3.39       46.44    0.460    1000          0.00           3.60       46.68    0.470    1200          0.00           3.69       45.55    0.460    1400          0.00           3.79       45.18    0.450    1600          0.00           3.80       45.04    0.45

该表包含以下信息:

  • E:训练运行的纪元数
  • #:训练步数
  • LOSS TOK2VEC:用于向量化输入数据的松散函数
  • LOSS TEXTCAT 和 SCORE:分类的损失函数及其归一化值。

七、第 5 步:使用模型

        在训练过程中,创建的模型存储在给定的文件夹名称中。 Spacy 保留了最新且最好的模型,即 F1 分数最高的模型。要使用这些模型,您可以直接使用 Spacy 加载它们,然后处理任何文本并导出计算的类别。

以下是与上面使用的同一文章的示例:

import spacynlp = spacy.load('textcat_multilabel_model/model-best')
fileid = reuters.fileids()[1024]
text = reuters.raw(fileid)
doc = nlp(text)print(doc.cats)
# {'acq': 0.9506559371948242, 'alum': 0.3440687954425812, 'barley': 0.044086653739213943, #...

doc.cats 属性是一个 Dictionary 对象,其中包含概率的键值映射,其中最可能的类别是估计的标签。

让我们比较一下使用单标签模型的一些文章的预期标签和估计标签:

import spacynlp = spacy.load('textcat_singlelabel_model/model-best')def compare_labels(fileid):text = reuters.raw(fileid)expected_cat =  reuters.categories(fileid)doc = nlp(text)estimated_cats = sorted(doc.cats.items(), key=lambda i:float(i[1]), reverse=True)estimated_cat = estimated_cats[0]print(f'fileid {fileid} :: expected = {expected_cat} // estimated = {estimated_cat}')compare_labels(training[1024])
# fileid training/1158 :: expected = ['ipi'] // estimated = ('cpi', 0.39067542552948)compare_labels(training[2678])
# fileid training/2080 :: expected = ['acq'] // estimated = ('acq', 0.9778384566307068)compare_labels(testing[1024])
# fileid test/16630 :: expected = ['orange'] // estimated = ('orange', 0.40097832679748535)compare_labels(testing[2678])
# fileid test/20972 :: expected = ['earn'] // estimated = ('earn', 0.9994577765464783)

正如您所看到的,在三种情况下,估计的类别是匹配的。

八、比较多标签和单标签分类训练

为了让您对训练过程有一个印象,让我们比较一下多标签和单标签分类过程。

8.1 多标签分类

对 7769 个条目的完整训练集进行基线多标签分类大约需要 30 分钟。

这是日志输出:

=========================== Initializing pipeline ===========================
[2023-07-03 18:24:10,639] [INFO] Set up nlp object from config
[2023-07-03 18:24:10,656] [INFO] Pipeline: ['tok2vec', 'textcat_multilabel']
[2023-07-03 18:24:10,660] [INFO] Created vocabulary
[2023-07-03 18:24:12,365] [INFO] Added vectors: en_core_web_lg
[2023-07-03 18:24:12,365] [INFO] Finished initializing nlp object
[2023-07-03 18:24:31,806] [INFO] Initialized pipeline components: ['tok2vec', 'textcat_multilabel']
✔ Initialized pipeline============================= Training pipeline =============================
ℹ Pipeline: ['tok2vec', 'textcat_multilabel']
ℹ Initial learn rate: 0.001
E    #       LOSS TOK2VEC  LOSS TEXTC...  CATS_SCORE  SCORE
---  ------  ------------  -------------  ----------  ------0       0          2.51           0.39       48.98    0.490     200         24.56           5.04       39.44    0.390     400          0.00           2.86       39.94    0.400     600          0.01           3.87       40.27    0.400     800          1.02           3.39       46.44    0.460    1000          0.00           3.60       46.68    0.470    1200          0.00           3.69       45.55    0.460    1400          0.00           3.79       45.18    0.450    1600          0.00           3.80       45.04    0.45
✔ Saved pipeline to output directory
textcat_multilabel_model/model-last

8.2 单标签分类

7769 个条目的完整训练集的基线单标签分类需要超过 3 小时 30 分钟。两个训练步骤本身都比较慢,并且训练会持续几个 epoch,因为 F1 分数增加非常缓慢。

要使用单一类别标签,我们首先需要更改提供训练和测试数据的方法。在与文章相关的所有类别中,仅使用第一个:

def convert_single_label(dataset, filename):db = DocBin()# ...for index, fileid in enumerate(dataset):cat_dict = {cat: 0 for cat in reuters.categories()}cat_dict[reuters.categories(fileid).pop()] = 1# ...

对于训练运行,使用特殊的单标签配置文件。

python -m spacy train spacy_single_label_categorization_pipeline.cfg \--paths.train reuters_training_single_label.spacy \--paths.dev reuters_testing_single_label.spacy \--output textcat_singlelabel_model

以下是训练结果:

To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
✔ Created output directory: textcat_singlelabel_model
ℹ Saving to output directory: textcat_singlelabel_model
ℹ Using CPU=========================== Initializing pipeline ===========================
[2023-07-04 16:38:34,515] [INFO] Set up nlp object from config
[2023-07-04 16:38:34,529] [INFO] Pipeline: ['tok2vec', 'textcat']
[2023-07-04 16:38:34,532] [INFO] Created vocabulary
[2023-07-04 16:38:37,277] [INFO] Added vectors: en_core_web_lg
[2023-07-04 16:38:37,278] [INFO] Finished initializing nlp object
[2023-07-04 16:39:02,505] [INFO] Initialized pipeline components: ['tok2vec', 'textcat']
✔ Initialized pipeline
============================= Training pipeline =============================
ℹ Pipeline: ['tok2vec', 'textcat']
ℹ Initial learn rate: 0.001
E    #       LOSS TOK2VEC  LOSS TEXTCAT  CATS_SCORE  SCORE
---  ------  ------------  ------------  ----------  ------0       0          0.00          0.01        0.09    0.000     200         31.16          2.00        1.29    0.010     400         34.35          1.92        1.51    0.020     600         41.41          1.76        2.27    0.020     800         56.29          1.76        2.12    0.020    1000        124.84          1.73        2.07    0.020    1200         45.42          1.75        2.42    0.020    1400         58.10          1.51        3.93    0.040    1600         90.27          1.51        2.10    0.020    1800         93.87          1.57        3.22    0.030    2000        172.70          1.48        3.64    0.040    2200        215.72          1.42        4.30    0.040    2400        177.34          1.30        4.72    0.050    2600        866.50          1.22        5.47    0.050    2800       1877.17          1.16        5.09    0.050    3000       4186.50          1.02        6.94    0.071    3200       4118.06          1.05        5.33    0.051    3400       6050.67          0.96        8.27    0.081    3600       7368.26          0.82        6.07    0.061    3800       9302.68          1.03        8.25    0.081    4000      12089.54          0.98        7.82    0.081    4200      10829.92          0.99        6.49    0.061    4400      10462.12          0.91        7.15    0.072    4600      13104.09          0.96        9.11    0.092    4800      14439.08          0.87       10.31    0.102    5000      12240.43          0.86        9.27    0.09

        尽管训练需要更长的时间,但估计的类别与预期的类别更加匹配。

九、结论

        本文介绍了如何向 Spacy 项目添加文本分类任务。使用默认模型,Spacy 可以执行多项基本和高级 NLP 任务,例如标记化、词形还原、词性标记和命名实体解析。要添加文本分类,需要执行以下步骤:a) 准备训练数据,b) 将训练数据转换为 DocBin 格式,以便预处理 Doc 对象提供带有字典属性 cats c) 生成和自定义 Spacy 训练配置,d) 使用 cli 命令训练模型。因此,特别是训练配置是高度可定制的,暴露了学习率和损失函数等细节。训练完成后,可以通过 Spacy 加载生成的模型,并且所有解析的文档现在都将具有估计的类别。

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

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

相关文章

Android--UML类图使用详解

明敕星驰封宝剑,辞君一夜取楼兰 一,定义 类图(Class diagram)是显示了模型的静态结构,特别是模型中存在的类、类的内部结构以及它们与其他类的关系等。类图不显示暂时性的信息。类图是面向对象建模的主要组成部分。它既用于应用程序的系统分…

【数字电路】MacBook使用iverilog进行数字电路仿真

安装流程 在终端中用brew包管理工具进行安装仿真工具: 编译verilog代码: brew install icarus-verilog编译verilog代码: brew install verilatorMacOS系统显示UNIX GUI brew install xquartz可视化仿真波形图: brew install gtk…

基于Springboot的任务发布平台设计与实现(源码齐全+调试)

项目描述 临近学期结束,还是毕业设计,你还在做java程序网络编程,期末作业,老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。你想解决的问题,今天给大家介绍…

t-io 程序执行后,jvm不退出的原因

基于t-io 1.7.3 版本分析源码 1、设定当前时间,每10毫秒执行一次 (非守护线程) 2、对应线程池的核心线程在AioServer启动时全部激活,并且添加空任务到阻塞队列,让核心线程(非守护线程)一直存活

IO流学习

IO流:存储和读取数据的解决方案 import java.io.FileOutputStream; import java.io.IOException;public class Test {public static void main(String[] args) throws IOException {//1.创建对象//写出 输入流 OutputStream//本地文件fileFileOutputStream fos new FileOutputS…

RNN和LSTM学习笔记-初学者

提示: 目录 前言一、RNN介绍二、LSTM介绍总结 前言 提示: 提示: 一、RNN介绍 RNN是一种短时记忆,而LSTM是长短时记忆网络 二、LSTM介绍 总结

【专题】最小生成树(prim算法、kruscal算法)

目录 一、最小生成树二、Prim算法1. 算法思想2. 例题3. 性能分析 三、Kruscal算法1. 算法思想2. 例题3. 性能分析 一、最小生成树 生成树中边的权值(代价)之和最小的树。 二、Prim算法 1. 算法思想 设N(V,{E})是连通网,TE是N上最小生成树…

数据库02-04 中级SQL

01.on关键字: 主要用join…on来用多关系查询,和where关键字的相同 student关系: takes关系: 02.一般外连接 自然连接: 这个外连接(自然连接)会缺少空值的元祖(本例子中的stude…

算法--数据结构基础

文章目录 数据结构单链表栈表达式求值前缀表达式中缀表达式后缀表达式 队列单调栈单调队列KMPTrie并查集堆哈希表字符串哈希 数据结构 单链表 用数组模拟(静态链表)效率比定义Node类(动态链表)效率高些 使用数组模拟单链表&am…

Java 使用mybatis的BaseTypeHandler实现数据自动AES加密解密,通过Hutool工具类自定义注解实现数据脱【附有完整步骤和代码】

一、AES加密 1 加密工具类 使用KeyGenerator生成AES算法生成器 public class AESUtil {/*** 密钥长度: 128, 192 or 256*/private static final int KEY_SIZE 256;/*** 加密/解密算法名称*/private static final String ALGORITHM "AES";/*** 随机数生成器&#…

【CDP】CDP 集群通过Knox 访问Yarn Web UI,无法跳转到Flink Web UI 问题解决

一、前言 记录下在CDP 环境中,通过Knox 访问Yarn Web UI,无法跳转到Flink Web UI 的BUG 解决方法。 二、问题复现 登录 Knox Web UI 找到任一 Flink 任务 点击 ApplicationMaster 跳转 Flink WEB UI 出问题 内容空白,无法正常跳转到…

JS基本语法

JS基本语法 变量数据类型原始数据类型 函数定义第一种方式第二种方式 JS 对象ArrayStringJavaScript 自定义对象JSONDOMBOM JS 事件事件监听事件绑定常见事件 变量 数据类型 原始数据类型 函数定义 第一种方式 第二种方式 JS 对象 Array String JavaScript 自定义对象 JSON …

向华为学习:基于BLM模型的战略规划研讨会实操的详细说明,含研讨表单(一)

前面,华研荟用了三篇文章介绍华为战略规划的时候使用的其中一个工具:五看三定。一句话来说,五看三定是通过“五看”来知己知彼,然后设计业务,在选定的业务领域(方向)确定战略控制点,…

STM32_HAL库—IWDG看门狗

一、CubeMX设置 1、晶振配置(72M) 2、数据配置 超时时间 Tout prv / LSI * rlv (s) 其中prv是预分频器寄存器的值,rlv是重装载寄存器的值,而LSI值默认是40kHz,如下所示。 3、代码实现 int main(){while(1){HAL_IW…

【c++】stl_priority_queue优先级队列

目录 一、priority_queue的介绍 二、 priority_queue的本质 三、priority_queue的使用 四、priority_queue的模拟实现 总结 一、priority_queue的介绍 首先让我们通过阅读优先级队列的官方文档 简单翻译一下 1. 优先队列是一种容器适配器,根据严格的弱排序标准…

MySQL数据库遇到不规范建表问题解决方案

简介: 需要建立的关联表如上图所示。 问题发现: 好,问题来了,大伙儿请看:我们的organizations表中的Industry字段居然存储了两个IndustryName,这就很恶心了,就需要我们进行拆分和去重后放到In…

【vtkWidgetRepresentation】第十二期 vtkBalloonRepresentation

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 前言 本文分享vtkBalloonRepresentation,用于标注文字或图片,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 1. vtkBalloonRepre…

竞赛保研 opencv 图像识别 指纹识别 - python

0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 基于机器视觉的指纹识别系统 🥇学长这里给一个题目综合评分(每项满分5分) 难度系数:3分工作量:3分创新点:4分 该项目较为新颖,适…

标书设计:目录的必要性与优化建议

标书,作为商务文件的一种,旨在展示公司实力、产品优势和服务水平,是企业开展商业活动的一项重要工具。在进行标书制作时,有人认为是否需要目录,成为了一个值得讨论的问题。 目录作为标书的导航,是否必要呢&…