TowardsDataScience 2023 博客中文翻译(三百三十五)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

将文本转化为向量:TSDAE 的无监督方法用于增强嵌入

原文:towardsdatascience.com/transforming-text-into-vectors-tsdaes-unsupervised-approach-to-enhanced-embeddings-728eb28ea701?source=collection_archive---------1-----------------------#2023-10-16

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Silvia Onofrei

·

关注 发表在 Towards Data Science ·11 分钟阅读·2023 年 10 月 16 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

设计来自 Freepik

将 TSDAE 的目标领域预训练与通用语料库上的监督微调相结合,以提高专用领域嵌入的质量。

介绍

嵌入将文本编码为高维向量空间,使用密集向量来表示单词并捕捉其语义关系。生成式 AI 和大型语言模型(LLM)中的最新发展,如上下文搜索和 RAG,严重依赖于其底层嵌入的质量。虽然相似性搜索使用了如余弦相似度这样的基本数学概念,但构建嵌入向量的方法对后续结果有显著影响。

在大多数情况下,预训练的句子变换器可以开箱即用并提供合理的结果。对于这些情况,有许多基于 BERT 的预训练上下文嵌入可供选择,其中一些是领域专用的,可以从如 HuggingFace 等平台下载。

当处理包含许多特定于狭窄领域的技术术语或来源于资源稀缺语言的语料库时,会出现问题。在这些情况下,我们需要处理在预训练或微调期间未见过的未知词

例如,一个在通用文本上预训练的模型将很难将向量准确分配给数学研究论文语料库中的标题。

在这些情况下,由于模型没有接触到领域特定词汇,它难以确定这些词的含义,并将它们准确地放置在相对于语料库中其他词的向量空间中。未知词的数量越多,影响越大,模型的性能越低。

因此,现成的预训练模型在这种情况下表现不佳,而尝试预训练自定义模型由于缺乏标注数据和需要大量计算资源而面临挑战。

动机

这项工作受到了一项近期研究[aviation_article]的启发,该研究专注于航空领域,其数据具有独特的特征,如技术术语、缩写和非常规语法。

为了解决标注数据不足的问题,作者采用了一种最有效的无监督技术之一,即预训练嵌入(TSDAE),随后进行了使用来自通用语料库的标注数据的微调阶段。经过调整的句子变换器优于通用变换器,展示了该方法在捕捉航空领域数据特征方面的有效性。

大纲

领域适应是将文本嵌入调整到特定领域而无需标注训练数据。在本实验中,我使用了一个两步法,根据[tsdae_article],这种方法比仅在目标领域上训练效果更好。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

首先,我开始进行专注于目标领域的预训练,通常称为适应性预训练。此阶段需要从我们的数据集中收集句子。我在这个阶段使用 TSDAE,这是一种在作为预训练任务的领域适应方面表现出色的方法,显著超越了包括掩码语言模型在内的其他方法,正如 [tsdae_article] 中所强调的。我正紧密跟随脚本:train_tsdae_from_file.py

随后,我在通用标记 AllNLI 数据集上对模型进行微调,采用了多重负采样排名损失策略。在此阶段,我使用来自 training_nli_v2.py 的脚本。正如 [tsdae_article] 中所记录的,这一步骤不仅能防止过拟合,还能显著提升模型性能。

TSDAE — 针对目标领域的预训练

TSDAE(基于变换器的序列去噪自编码器)是一种无监督的句子嵌入方法,首次由 K. Wang、N. Reimers 和 I. Gurevych 在 [tsdae_article] 中提出。

TSDAE 使用了修改过的编码器-解码器变换器设计,其中交叉注意力的键和值被限制在句子嵌入中。我将详细说明原文中突出显示的最佳架构选择 [tsdae_article]。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

  • 数据集由未标记的句子组成,在预处理过程中,通过删除 60% 的内容来引入输入噪声,从而使句子受到破坏。

  • 编码器通过池化词嵌入将损坏的句子转换为固定大小的向量。根据 [tsdae_article],推荐使用 CLS 池化方法来提取句子向量。

  • 解码器需要从损坏的句子嵌入中重构原始输入句子。作者建议在训练过程中将编码器和解码器的参数绑定,以减少模型中的参数数量,从而使训练更加容易,且不易过拟合,同时不会影响性能。

为了保证良好的重构质量,编码器的句子嵌入必须最佳地捕捉语义。预训练的变换器如 bert-base-uncased 被用于编码器,而解码器的权重则从中复制。

解码器的注意力机制仅限于编码器生成的句子表示。这种对原始变换器编码器-解码器架构的修改限制了解码器从编码器中检索的信息,并引入了一个瓶颈,迫使编码器生成有意义的句子表示。

在推断时,仅使用编码器来创建句子嵌入。

该模型经过训练,以从损坏的句子中重建干净的句子,这通过最大化目标来完成:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

AllNLI — 自然语言推断数据集

自然语言推断(NLI)确定两个句子之间的关系。它将假设(第二个句子)的真实性分类为蕴含(基于前提的真实)、矛盾(基于前提的虚假)或中立(既不由前提保证也不被前提反驳)。NLI 数据集是大型标注数据集,其中句子对被标注为它们的关系类别。

对于这项实验,我使用了 AllNLI 数据集,其中包含来自合并的斯坦福自然语言推断(SNLI)和 MultiNLI 数据集的超过 900K 条记录。此数据集可以从以下网址下载:AllNLI 下载站点

加载和准备预训练数据

为了构建我们的领域特定数据,我们使用了Kaggle arXiv 数据集,大约包含 1.7M 篇学术 STEM 论文,来源于知名电子预印本平台,arXiv。除了标题、摘要和作者之外,每篇文章还附有大量元数据。然而,这里我们只关心标题。

下载后,我将选择数学预印本。鉴于 Kaggle 文件的庞大,我已将数学论文文件的缩减版本添加到 Github 以便更容易访问。然而,如果你倾向于其他主题,请下载数据集,并在以下代码中将math替换为你所需的主题:

 # Collect the papers with subject "math"
def extract_entries_with_math(filename: str) -> List[str]:
    """
    Function to extract the entries that contain the string 'math' in the 'id'.
    """

    # Initialize an empty list to store the extracted entries.
    entries_with_math = []

    with open(filename, 'r') as f:
        for line in f:
            try:
                # Load the JSON object from the line
                data = json.loads(line)
                # Check if the "id" key exists and if it contains "math"
                if "id" in data and "math" in data["id"]:
                    entries_with_math.append(data)

            except json.JSONDecodeError:
                # Print an error message if this line isn't valid JSON
                print(f"Couldn't parse: {line}")

    return entries_with_math

# Extract the mathematics papers
entries = extract_entries_with_math(arxiv_full_dataset)

# Save the dataset as a JSON object
arxiv_dataset_math = file_path + "/data/arxiv_math_dataset.json"

with open(arxiv_dataset_math, 'w') as fout:
    json.dump(entries, fout)

我已将数据集加载到 Pandas 数据框 df 中。快速检查显示,减少后的数据集包含 55,497 篇预印本——这是我们实验的更实际的大小。虽然[tsdae_article]建议约 10K 条目是足够的,我会保留整个减少后的数据集。数学标题可能包含 LaTeX 代码,我将其替换为 ISO 代码以优化处理。

parsed_titles = []

for i,a in df.iterrows():
    """
    Function to replace LaTeX script with ISO code.
    """
    try:
        parsed_titles.append(LatexNodes2Text().latex_to_text(a['title']).replace('\\n', ' ').strip()) 
    except:
        parsed_titles.append(a['title'].replace('\\n', ' ').strip())

# Create a new column with the parsed titles
df['parsed_title'] = parsed_titles

我将使用parsed_title条目进行训练,所以让我们将它们提取为列表:

# Extract the parsed titles as a list
train_sentences = df.parsed_title.to_list()

接下来,让我们通过从每个条目中删除大约 60% 的标记来形成损坏的句子。如果你有兴趣进一步探索或尝试不同的删除比例,请查看去噪脚本

# Add noise to the data
train_dataset = datasets.DenoisingAutoEncoderDataset(train_sentences)

让我们看看处理后的一个条目发生了什么:

print(train_dataset[2010])

initial text: "On solutions of Bethe equations for the XXZ model"
corrupted text: "On solutions of for the XXZ"

正如您所注意到的,Bethe equationsmodel 从初始文本中被移除了。

我们数据处理的最后一步是按批次加载数据集:

train_dataloader = DataLoader(train_dataset, batch_size=8, 
                              shuffle=True, drop_last=True)

TSDAE 训练

虽然我将遵循 train_tsdae_from_file.py 中的方法,但我将一步步构建以便更好地理解。

从选择一个预训练的转换器检查点开始,并坚持使用默认选项:

model_name = 'bert-base-uncased'
word_embedding_model = models.Transformer(model_name)

选择 CLS 作为池化方法,并指定要构建的向量维度:

pooling_model = models.Pooling(word_embedding_model.get_word_embedding_dimension(),
                               "cls")											 'cls')

接下来,通过组合两层来构建句子转换器:

model = SentenceTransformer(modules=[word_embedding_model,
                            pooling_model])													pooling_model])

最后,指定损失函数并在训练阶段绑定编码器-解码器参数。

train_loss = losses.DenoisingAutoEncoderLoss(model,
                                             decoder_name_or_path=model_name,
                                             tie_encoder_decoder=True)

现在,我们准备调用 fit 方法并训练模型。我还会将其存储以备后续步骤使用。欢迎您调整超参数以优化实验。

model.fit(
    train_objectives=[(train_dataloader, train_loss)],
    epochs=1,
    weight_decay=0,
    scheduler='constantlr',
    optimizer_params={'lr': 3e-5},
    show_progress_bar=True,
    use_amp=True # set to False if GPU does not support FP16 cores
)

pretrained_model_save_path = 'output/tsdae-bert-uncased-math'
model.save(pretrained_model_save_path)

预训练阶段在 Google Colab Pro 实例上使用 A100 GPU 高内存设置,大约花费了 15 分钟。

在 AllNLI 数据集上的微调

让我们开始下载 AllNLI 数据集:

nli_dataset_path = 'data/AllNLI.tsv.gz'

if not os.path.exists(nli_dataset_path):
    util.http_get('<https://sbert.net/datasets/AllNLI.tsv.gz>', 
                  nli_dataset_path)

接下来,解压文件并解析数据以进行训练:

def add_to_samples(sent1, sent2, label):
    if sent1 not in train_data:
        train_data[sent1] = {'contradiction': set(),
                             'entailment': set(), 
                             'neutral': set()}
														 'entailment': set												 'neutral': set()}
    train_data[sent1][label].add(sent2)

train_data = {}
with gzip.open(nli_dataset_path, 'rt', encoding='utf8') as fIn:
    reader = csv.DictReader(fIn, delimiter='\\t', 
                            quoting=csv.QUOTE_NONE)
    for row in reader:
        if row['split'] == 'train':
            sent1 = row['sentence1'].strip()
            sent2 = row['sentence2'].strip()

            add_to_samples(sent1, sent2, row['label'])
            add_to_samples(sent2, sent1, row['label'])  # Also add the opposite

train_samples = []
for sent1, others in train_data.items():
    if len(others['entailment']) > 0 and len(others['contradiction']) > 0:
        train_samples.append(InputExample(texts=[sent1, 
                     random.choice(list(others['entailment'])), 
                     random.choice(list(others['contradiction']))]))
        train_samples.append(InputExample(texts=[random.choice(list(others['entailment'])), 
                     sent1, 
                     random.choice(list(others['contradiction']))]))															random.choice(list(others['contradiction']))]))

训练数据集大约有 56 万 3 千 个训练样本。最后,使用一个特殊的数据加载器以批量形式加载数据,并避免批量内的重复:

train_dataloader = datasets.NoDuplicatesDataLoader(train_samples,
                                                   batch_size=32)

我在这里使用的批量大小比脚本中的默认大小 128 要小。虽然更大的批量会给出更好的结果,但它需要更多的 GPU 内存,而由于我的计算资源有限,我选择了较小的批量大小。

最后,使用 MultipleRankingLoss 对 AllNLI 数据集上的预训练模型进行微调。蕴含对是正样本,而矛盾对是困难的负样本。

# Set the model parameters
model_name = 'output/tsdae-bert-uncased-math'
train_batch_size = 32 
max_seq_length = 75
num_epochs = 1

# Load the pre-trained model
local_model = SentenceTransformer(model_name)
# Choose the loss function
train_loss = losses.MultipleNegativesRankingLoss(local_model)
# Use 10% of the train data for warm-up
warmup_steps = math.ceil(len(train_dataloader) * num_epochs * 0.1)

# Train the model
local_model.fit(train_objectives=[(train_dataloader, train_loss)],
          #evaluator=dev_evaluator,
          epochs=num_epochs,
          #evaluation_steps=int(len(train_dataloader)*0.1),
          warmup_steps=warmup_steps,
          output_path=model_save_path,
          use_amp=True  # Set True, if your GPU supports FP16 operations
          )

# Save the model
finetuned_model_save_path = 'output/finetuned-bert-uncased-math'
local_model.save(finetuned_model_save_path)

我在整个 50 万 数据集上对模型进行了微调,这大约花了 40 分钟,在 Google Colab Pro 上,进行了 1 个周期,批量大小为 32。

评估 TSDAE 预训练模型和微调模型

我将对 HuggingFace 的 STS(语义文本相似性)数据集进行一些初步评估,使用 EmbeddingSimilarityEvaluator,它返回斯皮尔曼等级相关系数。然而,这些评估并没有使用我关注的特定领域,可能未能展示模型的真实表现。详细信息见 [tsdae_article] 第四部分。

我从 HuggingFace 下载数据集,并指定 validation 子集:

import datasets as dts
from datasets import load_dataset

# Import the STS benchmark dataset from HuggingFace
sts = dts.load_dataset('glue', 'stsb', split='validation')

这是一个形式为的数据集对象:

Dataset({
    features: ['sentence1', 'sentence2', 'label', 'idx'],
    num_rows: 1379
})

为了更好地理解,让我们看一个具体的条目

# Take a peek at one of the entries
sts['idx'][100], sts['sentence1'][100], sts['sentence2'][100], sts['label'][100]

>>>(100,
 'A woman is riding on a horse.',
 'A man is turning over tables in anger.',
 0.0)

从这个例子中我们可以看到,每个条目有 4 个特征,一个是索引,两个句子和一个标签(由人工标注者创建)。标签的值可以在 05 之间,并测量两个句子的相似程度(5 为最相似)。在这个例子中,这两个句子完全在不同的主题上。

为了评估模型,我们为句子对创建句子嵌入,并计算每对的余弦相似度分数。标签与相似度分数之间的斯皮尔曼等级相关性作为评估分数。

由于我将使用取值范围在 0 到 1 之间的余弦相似度,因此需要对标签进行归一化:

# Normalize the [0, 5] range to [0, 1]
sts = sts.map(lambda x: {'label': x['label'] / 5.0})

将数据封装在 HuggingFace 的 InputExample 类中:

# Create a list to store the parsed data
samples = []

for sample in sts:
    # Reformat to use InputExample class
    samples.append(InputExample(
        texts=[sample['sentence1'], sample['sentence2']],
        label=sample['label']
    ))

创建一个基于 sentence-transformers 库中 EmbeddingSimilarityEvaluator 类的评估器。

# Instantiate the evaluation module
evaluator = EmbeddingSimilarityEvaluator.from_input_examples(samples)

我们计算了 TSDAE 模型、微调模型和几个预训练句子转换器的得分:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

因此,在一般范围的数据集上,一些预训练模型,如 all-mpnet-base-v2,表现优于 TSDAE 微调模型。然而,通过预训练,初始模型 bert-base-uncased 的性能提高了两倍以上。可以想象,通过进一步调整超参数进行微调,可能会获得更好的结果。

结论

对于资源有限的领域,将 TSDAE 与微调结合起来是一种相当高效的构建嵌入的策略。考虑到数据量和计算资源,这里的结果相当值得注意。然而,对于那些并不特别异常或领域特定的数据集,考虑到效率和成本,选择一个预训练的嵌入可能会提供可比的性能。

Gihub 链接 到 Colab 笔记本和示例数据集

因此,我的朋友们,我们在学习旅程中应该始终拥抱好、坏和混乱的东西!

参考文献

[tsdae_article]. K. Wang 等,TSDAE:使用基于 Transformer 的序列去噪自编码器进行无监督句子嵌入学习 (2021) arXiv:2104.06979

[aviation_article]. L. Wang 等,为航空领域调整句子转换器 (2023) arXiv:2305.09556

使用 ChatGPT 进行翻译

原文:towardsdatascience.com/translate-with-chatgpt-f85609996a7f?source=collection_archive---------1-----------------------#2023-02-16

一个非常强大的机器翻译系统

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Benjamin Marie

·

关注 发布于 Towards Data Science ·6 min read·2023 年 2 月 16 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来自 Pixabay

ChatGPT 是由 OpenAI 开发的聊天机器人。它基于 instructGPT:它经过训练以遵循并回答用户编写的指令或所谓的“提示”。

ChatGPT 展示了在提供连贯且相关的详细回答方面的令人印象深刻的能力。它似乎在自然语言处理 (NLP) 任务方面表现尤其出色,如摘要生成、问答、语言生成和机器翻译

然而,由于它是一个非常新的系统,ChatGPT 尚需科学评估以比较其自然语言处理性能与之前的研究。

为此方向,腾讯 AI 发布了一项关于 ChatGPT 翻译能力的初步研究:

ChatGPT 是一个好的翻译器吗?初步研究Wenxiang Jiao, Wenxuan Wang, Jen-tse Huang, Xing Wang, and Zhaopeng Tu (腾讯 AI)

这项研究的主要目的是评估 ChatGPT 在将文本翻译成英语方面的表现,因为大多数训练数据都是英语。注意:实际上,ChatGPT 基于 instructGPT,如在 博客文章* 中提到。InstructGPT 是 GPT-3 的一个微调版本,提示“主要为英语” (Ouyang et al., 2022)。此外,GPT-3 的 93% 预训练数据是英语 (Brown et al., 2020)。*

他们还评估了对其他语言的翻译,这些语言在其训练数据中代表性较少,如日语和罗马尼亚语,因此更具挑战性。

在本文中,我将分析和解释他们的主要发现,特别是突出使用 ChatGPT 作为机器翻译系统时,哪些方法似乎有效,哪些无效。

翻译提示

在处理生成语言模型时,最重要的步骤之一是提示设计。

我们需要找到一个合适的自然语言表达形式来查询模型,鉴于我们的目标任务。在这里,我们希望 ChatGPT 将源语言中的句子(标记为 “[SRC]”)翻译成目标语言(标记为 “[TGT]”)。

为了找到好的提示,腾讯 AI 直接要求 ChatGPT 给出 10 个提示,使用了以下提示:

提供十个简洁的提示或模板,使你能够进行翻译。

ChatGPT 按预期返回了 10 个提示,但它们之间仅有少量差异。他们最终决定只尝试以下 3 个,这些是最能代表 ChatGPT 初始返回的 10 个提示的:

  • Prompt 1: 将这些句子从 [SRC] 翻译成 [TGT]:

  • Prompt 2: 无需引号回答。这些句子在 [TGT] 中是什么意思?

  • Prompt 3: 请提供这些句子的 [TGT] 翻译:

他们在中文到英文的翻译任务中([SRC]=中文, [TGT]=英文)评估了每一个提示,并得出了以下结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

结果由 Wenxiang Jiao, Wenxuan Wang, Jen-tse Huang, Xing Wang, and Zhaopeng Tu (腾讯 AI) 提供

BLEU、chrF++ 和 TER 是 3 种自动评估机器翻译质量的指标。对于 BLEU 和 chrF++,分数越高越好;对于 TER,分数越低越好。

根据这 3 种指标获得的分数,他们发现 Prompt 3 表现最佳。尽管 chrF++ 分数相似,Prompt 2 似乎也优于 Prompt 1。

这很有趣,因为 Prompt 1 提到源语言,但其他两个提示没有提及。然而,Prompt 1 的表现较差。ChatGPT 不需要知道我们想要翻译的文本的语言

这令人印象深刻,但也有些违背直觉。我们本可以预期由于提示中源语言的准确性,ChatGPT 会更准确。对于人类翻译者来说,了解源语言至关重要。

目前尚无良好解释为何 ChatGPT 在指示源语言时会得出较低的分数。我们可以假设 ChatGPT 能够自动从用户输入中推断出源语言。如果是这样的话,提供源语言不应该有任何影响,而不是腾讯 AI 结果中观察到的负面影响。

一般翻译

既然我们找到了一个好的提示,我们可以将 ChatGPT 与最先进的机器翻译系统进行比较。

腾讯 AI 选择了在线系统进行比较:Google TranslateDeepL以及他们自己的在线系统,腾讯 TranSmart

结果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

结果由 Wenxiang Jiao, Wenxuan Wang, Jen-tse Huang, Xing Wang, and Zhaopeng Tu (腾讯 AI) 提供

三个在线系统的表现相似,似乎比 ChatGPT 表现更好,尽管作者并未报告统计显著性检验以确保这些差异确实具有统计学意义。

[## 是的,我们需要统计显著性检验]

经验法则可能会产生正确的结果,但无法具有科学可信度。

pub.towardsai.net](https://pub.towardsai.net/yes-we-need-statistical-significance-testing-927a8d21f9f0?source=post_page-----f85609996a7f--------------------------------)

尽管如此,我发现这些结果令人印象深刻。由于基于 instructGPT,我们可以假设 ChatGPT 主要在英文数据上进行训练,但似乎能够很好地捕捉中文句子的含义以生成英文翻译。

如果我们能对 ChatGPT 进行中文到英文的微调,我们肯定会获得更高质量的翻译。

在论文中,腾讯 AI 还报告了英、汉、德和罗马尼亚语之间所有翻译方向的类似差异。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

表格由 Wenxiang Jiao, Wenxuan Wang, Jen-tse Huang, Xing Wang, and Zhaopeng Tu (腾讯 AI) 提供

再次强调,这些表现(以 BLEU 评分为准)令人印象深刻。即使是那些不涉及英语的翻译方向,如德语到中文,ChatGPT 也能生成翻译。根据 BLEU,在线系统的表现仍然更好,这也是预期中的,因为它们是针对这个任务进行训练的,而 ChatGPT 则不是!

涉及罗马尼亚语的结果相差甚远。例如,ChatGPT 的 BLEU 评分比在线系统低了将近 50%。这种差异可能具有统计学意义。

作者提出了一个解释。与德语和中文相比,罗马尼亚语的资源,如互联网上的罗马尼亚语文本,少得多。ChatGPT 在训练过程中可能见过的罗马尼亚语句子示例太少,以至于难以准确建模。

我同意这一假设,但应通过更多涉及其他语言(如克罗地亚语或波兰语)的实验来确认。

领域与鲁棒性

他们进行了进一步的实验,以评估 ChatGPT 在特定领域(生物医学)用户生成(发布在社交媒体上,通常带有大量语法错误)的文本翻译中的表现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

表格由 Wenxiang Jiao, Wenxuan Wang, Jen-tse Huang, Xing Wang, 和 Zhaopeng Tu (腾讯 AI) 制作。

出乎意料的是,根据 BLEU,ChatGPT 在将生物医学文本从德语翻译成英语时的表现仍然接近在线系统。

ChatGPT 似乎没有受到生物医学文本中非常特定术语的负面影响。

ChatGPT 在将用户生成的德语文本翻译成英语方面超过了在线系统。这令人印象深刻,但并不令人意外。我们可以假设,ChatGPT 的训练数据中包含了大量来自社交媒体的帖子(从网络上抓取),而用于比较的在线系统训练数据通常经过严格筛选,因此对错误(如语法、语义等)的鲁棒性较差。

当翻译成远离英语的语言时,如日语,如 WMT20 Rob2 的结果所示,ChatGPT 面临的困难要大得多,这是预期中的情况。

本研究的局限性

作者在研究中承认,需要更多语言对的实验来更好地评估 ChatGPT 的翻译质量。

这种评估应通过人工评估而非自动度量来进行,因为自动度量通常不准确,特别是当比较系统的得分非常接近时。

缺乏人工评估是本研究的主要局限性

我认为,还可以进一步研究提示的影响。作者通过让 ChatGPT 自己建议提示选择了一种非常原创的方式。但提示 ChatGPT 建议提示是一个鸡和蛋的问题。用于获得机器翻译提示的提示本身可能对本研究中所有后续实验产生强烈影响。以前关于机器翻译提示设计的工作尝试了各种各样的手工设计提示。

## Google PaLM 翻译效果如何?

虽然还不是很好,但我们正在接近目标。

[towardsdatascience.com

结论

ChatGPT 在机器翻译方面非常出色

从这项初步研究中,我们已经可以得出结论,ChatGPT 在翻译文本方面会表现良好,甚至可能优于标准在线系统,尤其是那些翻译内容预期具有 ChatGPT 训练数据特征的文本,例如英语中的嘈杂用户生成文本。

尽管如此,正如预期的那样,ChatGPT 在翻译英语以外的语言,尤其是如日语或罗马尼亚语这样的远程或低资源语言方面,仍然落后于更标准的机器系统。

翻译术语与 LLM(GPT 和 Vertex AI/Google Bard)

原文:towardsdatascience.com/translating-terms-with-llms-gpt-and-vertex-ai-google-bard-f1410b8d49f2?source=collection_archive---------12-----------------------#2023-09-21

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Shafquat Arefeen

·

阅读 发表在 数据科学前沿 ·6 分钟阅读·2023 年 9 月 21 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Mojahid MottakinUnsplash 提供

像 ChatGPT 这样的 LLM 能否比人类更准确地进行翻译?我们可以使用哪些 LLM 选项?了解更多关于使用生成式 AI 进行多语言翻译的信息。

背景

在撰写此帖子时,我已经在数据处理领域工作了近十年,并在本地化领域工作了两年。我有各种形式的人工智能经验,包括但不限于聚类、分类和情感分析。机器翻译(MT)在本地化中被广泛使用。可以将其视为将一些文本输入 Google 翻译并请求将其翻译成另一种语言。根据我的经验,机器翻译通常在 80% 的时间内是正确的,但仍需人工审查/修正误译。

随着像 ChatGPT 和 Google Bard 这样的语言模型(LLMs)的兴起,我们可能通过为 LLM 提供额外的上下文(如定义和词性)来更接近人类翻译的准确性。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

假设

LLMs 通过基于提示的输入工作。这意味着你在提示中提供的信息和上下文越多,LLM 的输出就会越好。给定一组英语术语、它们的定义和词性,我们想看看 LLM 是否能在将术语翻译成不同语言时产生更好的结果。我们将使用的两个 LLM 是 GPT(通过 Jupyter Notebook 和 OpenAI API)和 Vertex AI(通过 Google BigQuery 的 ML.GENERATE_TEXT 函数)。后者需要更多的设置,但可以直接在你的查询控制台中通过 SQL 运行。

使用 LLMs 进行翻译

GPT

我们首先在 Jupyter Notebook 中安装 OpenAI python 库

import sys !{sys.executable} 
-m pip install openai

导入 pandas 以处理数据框。导入之前安装的 openai 库并设置你的 API 密钥。将数据读取到数据框中。如果你想跟着操作,我将使用的数据可以在这里找到。

import pandas
import openai openai.api_key = "YOUR_API_KEY" 
# read in your data 
df = pd.read_csv('mydata/terms_sample.csv')

在列表中设置你希望将单词翻译成的语言。

languages = ['Spanish (Spain)', 'Spanish (LatAm)', 'German', 'Italian', 'Japanese', 'Chinese (S)', 'French'

创建一个函数,遍历数据框中的行和语言列表,以分别翻译术语。提示将输入到“消息”部分。我们将使用 GPT 3.5,并将温度设置为一个非常小的数字,以确保我们从 LLM 得到精准/较少创造性的回应。

def translate_terms(row, target_language):
    """Translate the terms"""

    user_message = (
        f"Translate the following english term with the into {target_language}: '" + row['Term'] +
        "'.\n Here is the definition of the term for more context: \n" + row['Definition'] +
        "'.\n Here is the part of speech of the term for more context: \n" + row['Part of speech'] +
        ".\n Please only translate the term and not the definition. Do not provide any additional text besides the translated term." 
    )

    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "You are a helpful translation assistant that translate terms into other languages given an english term and defintion."},
            {"role": "user", "content": user_message},
        ],
        max_tokens=50,
        n=1,
        temperature=0.1
    )

    return response['choices'][0]['message']['content']

最后的步骤是对列表中每种语言的数据框运行翻译函数,并为这些语言的术语创建新列。请查阅完整的代码作为参考。

# Apply the function to the DataFrame
for language in languages:
    df[language+'_terms'] = df.apply(translate_terms, axis=1, args=(language,))

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

带有翻译术语的数据框

Google Bard / text-bisonm / Vertex AI

我们将使用ML.GENERATE_TEXT函数,在谷歌的 text-bison 模型中翻译相同的术语。这个过程确实需要更多的资源来设置,但一旦运行起来,我们将能够在 SQL 查询中直接调用 LLM。

设置

我不会提供详细的设置指南,因为每个人的 Google Cloud 基础设施都是根据他们的需求量身定制的。我将提供一个高层次的概述和一些启动的链接。

  1. 确保启用了Vertex AI 用户角色以访问您的服务账户。

  2. 按照Google Cloud上的说明设置远程云连接。

  3. 创建一个 LLM 模型并进行远程云连接

  4. 现在你应该能够使用 ML.GENERATE_TEXT 函数运行你的 LLM 模型。我建议查看函数的参数以了解所需的参数。

  5. 将您的数据上传到计费项目中,以便进行查询。

代码

生成翻译所用的代码如下所示。由于我自身的限制和查询引擎的限制,我决定分别为每种语言单独运行代码(手动替换加粗的文本),而不是像在之前的 jupyter notebook 中那样循环遍历语言数组。

DECLARE USER_MESSAGE_PREFIX STRING DEFAULT (
  'You are a helpful translation assistant that translate terms into other languages given an english term and defintion.'|| '\n\n' ||
  'Translate the following english term into French. Please do not translate the definition as well.'|| '\n\n' ||
  'Term: '
);

DECLARE USER_MESSAGE_SUFFIX DEFAULT (
  '\n\n' || 'Here is the definiton of the term for more context: '|| '\n\n'
);

DECLARE USER_MESSAGE_SUFFIX2 DEFAULT (
  '\n\n' || 'Here is the part of speech of the term for more context: '|| '\n\n'
);

DECLARE USER_MESSAGE_SUFFIX3 DEFAULT (
  '\n\n' || 'French translation of term:'
);

DECLARE languages ARRAY<string>;
SET languages = ['Spanish (Spain)', 'Spanish (LatAm)', 'German', 'Italian', 'Japanese', 'Chinese (S)', 'French'];

SELECT
  ml_generate_text_result['predictions'][0]['content'] AS generated_text,
  ml_generate_text_result['predictions'][0]['safetyAttributes']
    AS safety_attributes,
  * EXCEPT (ml_generate_text_result)
FROM
  ML.GENERATE_TEXT(
    MODEL `YOUR_BILLING_PROJECT.YOUR_DATASET.YOUR_LLM`,
    (
      SELECT
        Term,
        Definition,
        USER_MESSAGE_PREFIX || SUBSTRING(TERM, 1, 8192) || USER_MESSAGE_SUFFIX || SUBSTRING(Definition, 1, 8192) || USER_MESSAGE_SUFFIX2 || SUBSTRING(Part_of_speech, 1, 8192) || USER_MESSAGE_SUFFIX3 AS prompt
      FROM
        `YOUR_BILLING_PROJECT.YOUR_DATASET.terms_sample`
    ),
    STRUCT(
      0 AS temperature,
      100 AS max_output_tokens
    )
  );

结果解释

说明:除了英语,我不懂这些语言,因此请对我的结论保持一些保留。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

GPT 翻译术语

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Vertex AI 翻译术语

结果可以在这里找到。我注意到的一些发现:

  • 47 / 84(56%)的翻译在两个 LLM 中完全相同。

    • GPT 经常在词尾加上句点 (.)。通过去除这些,匹配百分比增加到63%
  • 看起来日语和法语在两个 LLM 之间的翻译不一致。

  • GPT 将术语“make up”理解为化妆品,这令人担忧,因为它似乎在翻译之前没有利用该术语的定义和词性。

    • 这可能是因为我的提示结构不是最优的。例如,我本可以在术语前提供定义,以便 LLM 先处理这些信息。
  • “Heavy metal”(专有名词)似乎被 GPT 字面翻译,特别是在德语中,它被翻译成一个与音乐流派不相符的术语。

我最终会说这两种 LLM 各有优缺点。GPT 易于在 Python 中设置和运行,而 Vertex AI 对提示的理解更清晰,在进行翻译之前会考虑所有上下文。我认为可以公平地说,LLM 比普通机器翻译做得更好,因为它们能够处理提示中的额外上下文。告诉我你的想法。我是否能做得更好?

原文发表于 https://shafquatarefeen.com

TranSPormer: 一种解决旅行商问题的 Transformer 网络

原文:towardsdatascience.com/transpormer-a-transformer-network-for-the-travelling-salesman-problem-154bd33c37b0?source=collection_archive---------8-----------------------#2023-05-02

利用 Transformer 神经网络来解决 TSP 的一种原创方法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Davide Caffagni

·

关注 发布于 Towards Data Science ·11 分钟阅读·2023 年 5 月 2 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Alina Grubnyak 提供,拍摄于 Unsplash

旅行商问题(TSP)是组合优化中的经典挑战之一。虽然这个问题已经研究了很长时间,目前仍没有已知的精确方法可以保证最优解。随着人工智能领域的进步,出现了新的解决 TSP 的提议。本文中,我们利用变换器神经网络来寻找这个问题的良好解决方案。

代码仓库:GitHub

上述代码库是对我在 MCS 期间完成的之前项目的重构,以通过自动决策课程考试。我想感谢我的朋友和同事 Alessandro,他在项目中进行了协作。

背景 — TSP

给定一个城市列表以及每对城市之间的距离,什么是访问每个城市恰好一次并返回起始城市的最短可能路线?(来源:Wikipedia

上述问题定义了旅行商问题(TSP),这是NP-困难的难题之一,意味着目前没有已知的方法可以在多项式时间内得到最优解。更正式地说,给定一个图G*(V,* E*),其中V是节点的集合(城市)而E是连接V中任意一对节点的边的集合,我们需要在G中找到最短的哈密顿巡回路(即“访问每个城市恰好一次并返回起始城市的路线”*)。

在这个项目中,我们将一个节点视为一个简单的特征对,即 2D 向量空间中的 x 和 y 位置。因此,任何一对节点*(u, v)之间存在一条边,其权重由uv*之间的欧几里得距离给出。

背景 — 用于 TSP 的变换器

我们从 Bresson et al. [1] 的工作中获得灵感,他们利用变换器神经网络来解决 TSP,取得了非常有前景的结果。简而言之,他们的模型分为两个组件。首先,变换器编码器以一组标记作为输入,即表示输入图中的节点的向量,并通过自注意力层将它们转换到一个新的潜在空间。我们将编码器的输出称为图嵌入。随后,变换器解码器接收一个虚拟标记z,该标记指示模型开始构建旅行路径,加上来自编码器的图嵌入,并生成一个概率分布,建模图中每个节点被选为旅行路径下一个候选节点的可能性。我们从该分布中抽取对应于所选节点的索引t。然后,我们将z与第t个图嵌入连接,并再次将解码器喂入结果序列,该序列表示部分旅行路径。这个过程持续进行,直到所有可用节点都被选中。完成后,我们从获得的序列中移除z,并将第一个选中的节点附加到序列末尾,从而完成旅行路径。这样的解码器被称为自回归,因为在每个选择步骤中,它使用的是前一步的输出。该模型通过 REINFORCE [2] 算法进行训练,以最小化从随机图中生成的旅行路径的平均长度,每个图有 50 个节点。

提议的方法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Bresson 等人(右)和我们(左)提出的架构概述。请注意,我们的网络没有自回归组件,即输出和输入之间没有反馈连接。作者提供的照片

在这个项目中,我们也采用变换器架构,因为我们旨在利用注意力来学习图中节点之间的有价值的全局依赖关系。同时,我们希望设计一个避免任何自回归组件的模型,即一个直接输出可行 TSP 解决方案的神经网络,无需对每个节点进行单次前向传递。

我们首先考虑将 TSP(旅行商问题)框定为一个集合到序列的问题。我们有一堆节点,其顺序尚未定义,我们希望找出如何排序以使得得到的旅行路径尽可能最短。在神经网络中表示顺序的概念通常通过某种形式的位置编码 [3] 来完成。也就是说,我们将输入集合中的每个标记与一个特定的向量相加,该向量的分量对特定位置是唯一的。因此,如果我们改变标记的顺序,位置向量也会不同,从而导致标记也不同。当然,位置编码有很多可能的实现方法,但为了简单起见,我们坚持使用原始提议。

我们方法的核心是注意力操作符,特别是交叉注意力,因此在这里我们对其进行简要回顾。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

三步解释注意力。作者提供的照片

关键步骤是第二步。A是一个*[n,n]矩阵,其(i,j)项是一个实数,与第i个标记相对于第j个标记的余弦相似度成比例。为了理解这一点,回顾步骤-2,(i,j)是查询i和关键j的点积的结果,两者都是形状为(d_k)*的两个向量。但是,点积实际上就是两个向量之间夹角的余弦,除了由两个向量的范数的乘积表示的缩放因子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

两个向量之间余弦的公式。作者提供的照片

在对A应用 softmax(第三步)之后(实际上是对A的缩放版本,但这不是重要的内容),我们得到一个矩阵,其i行是一个概率分布,建模查询i与关键j=1,…,n相似的可能性。最后,AV应用线性变换,通过查询和关键之间的相似性对其d=1,…k特征加权。例如,假设x_0x_1x_2是代表纽约(NY)、华盛顿特区(WDC)和洛杉矶(LA)的标记。它们的特征与这三个大都市的地理位置有关。我们从中提取QKV的三个矩阵,并计算注意力矩阵A。由于 NY-WDC 之间的距离远小于 NY-LA,A的第一行将在其第一列中具有非常高的值(因为 NY-NY 的距离自然最短),第二列中具有中高值,第三列中具有较低值。NY 在矩阵V中也由第一行表示,即v_0。嗯,在AV矩阵乘法之后,v_0的特征将主要通过第一行(NY)的 A 进行加权,适当地通过第二行(WDC),但在第三行(LA)方面贡献较少。其他两座城市也是类似的过程。我们的原始标记集已经转变为一组新的向量集,其特征受到(在这种情况下是空间的)相似性加权。

这种注意力被称为自注意力,因为Q, KV都来自相同的输入。另一方面,在 transformer 解码器中,还有第二种类型的注意力,交叉注意力。这时,查询Q来自解码器本身。例如,在 Bresson 等人的模型中,Q表示部分巡回。KV则是编码器输出的线性投影。

在这项工作中,我们提出了不同地利用交叉注意力。实际上,我们使用位置编码作为查询,而键和值则从之前的自注意力层中提取,该层可以自由操作G的所有节点。这样,当我们计算注意力矩阵时,我们得到的矩阵中,每一行是给定位置与给定节点相似性的概率分布。以下图片可能有助于理解这一点。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可视化我们模型中的交叉注意力。作者照片

根据上述注意力矩阵,节点 #1 可能会成为提议旅行中的第一个节点。#46 可能是第二个节点。节点 #39 和 #36 将分别放在第三和第四的位置。节点 #23 是第五和第六位置的好候选者,依此类推……在实践中,这样的矩阵元素 (i,j) 表示节点 j 被插入到提议旅行中位置 i 的可能性。我们的模型本质上是一个堆叠的块,每个块包含一个自注意力层、所展示的交叉注意力层,以及最后的前馈网络,如任何变换器。最后一个交叉注意力层的注意力矩阵将用于生成旅行。我们的模型生成的完整注意力矩阵大致如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

未训练的注意力矩阵,权重随机初始化。作者照片

这显然是相当混乱的。在使用 REINFORCE 算法优化网络以最小化平均旅行长度后,我们得到的矩阵如下。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

训练后的注意力矩阵。作者照片

从上述矩阵开始,我们可以为每一行绘制一个节点,结果序列将是我们的旅行(在附加第一个节点以闭合它之后)。为了简单起见,在我们所有的实验中,我们使用贪婪解码来选择节点,即我们总是选择当前行中值最高的节点。我们将探索诸如束搜索算法等其他策略留给未来的发展。

可行的解决方案

你可能已经发现了我们方法的主要问题。要构建一个可行的旅行,我们需要仅选择一次每个节点。相反,如果你仔细观察前面的图片,你会注意到有一些重复。即,节点 #47 最有可能出现在两个连续的位置,以及节点 #32。就目前而言,我们的网络无法提供可行的 TSP 解决方案。我们希望从模型中获得的是一个置换矩阵,即每行和每列中只有一个值为 1 的矩阵。问题是神经网络不擅长预测这种稀疏对象。

为了克服这一障碍,我们可以将 TSP 公式化为线性 sum assignment (LSA) 问题,其(逆向)成本矩阵是由我们的变换器计算的注意力矩阵。在这项工作中,我们利用了 SciPy 的实现来寻找节点集合(典型 LSA 公式中的工人)和位置集合(工作)之间的最小成本匹配。

Sinkhorn 算子

自然地,LSA 的解决方案,其成本由置换矩阵给出,是直接的:你只需选择矩阵中每行的1s。因此,我们训练的目标是使最终的注意力矩阵尽可能接近置换矩阵,以便 LSA 将是简单的,并且将导致表示总长度较短的旅行的匹配。

在我们的项目中,我们利用 Sinkhorn 算子 [4] 将我们模型的密集注意力矩阵转化为软置换矩阵:它们并不真正代表置换,但非常接近。接下来,我展示了一个典型训练设置的过程。我们模型提出的解决方案的平均旅行长度在训练过程中预计会减少。如果我们在解决 LSA 问题之前停止应用 Sinkhorn,这种情况不会发生。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Sinkhorn 算子在我们方法中的影响。作者提供的照片

结果与比较

我们将我们的提议与 Christofides [5]的算法进行比较,这是 TSP 的一个流行启发式方法,同时还参考了 Bresson 等人的自回归变换器,我们也将其作为训练设置的参考。评估是在 10000 个包含 50 个节点的图上进行的,这些节点的坐标是随机生成的。

在这里,我们提供一个箱线图,展示三位竞争者提出的解决方案的长度。毫无疑问,我们的变换器在这个背景下表现最差。利用神经网络生成良好的 TSP 解决方案而不利用自回归是一项艰巨的任务。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

三种比较方法的 TSP 解决方案长度。作者提供的照片

然而,解决优化问题不仅仅关乎性能。计算时间也扮演着基本角色。这是我们提案的主要特点:由于我们的变压器只需要一次前向传递即可提供解决方案,我们比 Bresson *et al.*的方法更快,因为他们必须循环多次,以便完成整个路线。下图显示了计算时间,用 Python 的分析器测量。对于 Christofides,我们使用了NetworkX的实现。请注意,为了公平比较,这两个神经网络都在 CPU 上运行,因为 Christofides 并未考虑在 GPU 上运行。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

解决 TSP 实例的 CPU 计算时间。照片由作者提供

最后,我们展示了我们网络产生的最佳路线的一些定性结果。在这些情况下,我们的模型能够超越 Christofides 生成的解决方案。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

定性结果:Christofides 的TSP 解决方案(左)和我们变压器的(右)。照片由作者提供

结论与未来工作

在这项工作中,我们提出了一种原始的神经网络来解决 TSP。虽然我们成功设计了一种避免自回归的合适架构,但所提出的路线质量却值得怀疑。这种方法的主要缺点是它不是一种端到端的神经方法,因为我们需要解决一个 LSA 实例以确保得到可行的解。我们尝试通过在损失函数中添加一些惩罚项来强制模型避免节点重复,但没有获得良好的结果。从一个集合中学习排列本身就是一个困难的任务。

可能,仅仅从简单的贪心解码切换到束搜索策略就足以略微改善结果。此外,还存在更先进的强化学习算法可供探索,例如PPO。我们希望其他人能受到这个项目的启发,继续探索这条路径。

参考文献

[1] Bresson, Xavier 和 Thomas Laurent. “旅行商问题的变压器网络。arXiv 预印本 arXiv:2103.03012 (2021)

[2] Williams, Ronald J. “连接主义强化学习的简单统计梯度跟随算法。强化学习 (1992): 5–32

[3] Vaswani, Ashish 等. “注意力机制就是一切。神经信息处理系统进展 30 (2017)

[4] Adams, Ryan Prescott 和 Richard S. Zemel. “通过 Sinkhorn 传播进行排名。arXiv 预印本 arXiv:1106.1925 (2011)

[5] Christofides, Nicos. 一种新启发式方法的最坏情况分析 卡内基梅隆大学匹兹堡管理科学研究组, 1976

在编写 Python 代码时使用 “Black” 库来犒赏自己

原文:towardsdatascience.com/treat-yourself-using-the-black-library-when-writing-python-code-7626b6099247

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 Pfüderi 提供,来源 Pixabay

实现“单一正确格式”代码的最简单方法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Christopher Tao

·发表于 Towards Data Science ·阅读时间 6 分钟·2023 年 1 月 13 日

你是否曾经从库文档或 Stack Overflow 上复制粘贴了一些示例代码?或者你没有纯粹的程序员背景,因此没有接受过完美格式化代码的训练?你是否想要提高代码的可读性以给别人留下深刻印象?

Python 编程语言的一个优点是它使用缩进来表示代码块的嵌套层级。因此,在格式上,编写一些“难以阅读”的代码相对更困难。然而,这并不意味着所有的 Python 代码必须格式化。没有哪种编程语言能强制用户编写格式化代码,这也不是一个好主意,因为这样会使使用变得过于困难。

在这篇文章中,我将介绍一个名为“Black”的 Python 库。它可以帮助我们以不同的方式格式化代码。所有的样式都将符合 PEP 8 指南。现在让我们开始创建一致且易读的格式化代码吧!

[## Black 22.12.0 文档

通过使用,你同意放弃手动格式化的细节控制。作为回报,Black 为你提供速度、确定性……

black.readthedocs.io](https://black.readthedocs.io/en/stable/index.html?source=post_page-----7626b6099247--------------------------------)

1. 基本用法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 Nat Aggiato 提供,来自 Pixabay

在开始使用库之前,我们需要使用 pip 安装它,如下所示。

pip install black

让我们准备一个不良示例,如下所示。

def my_function(a=1,b=2,c=3,d=4):
    my_list = [a, 
               b, 
               c, 
               d]
    return sum(my_list)
if True: print(my_function())

上面的代码可以正常运行,但存在许多问题。

  1. 函数 my_function 中的参数用逗号分隔,但逗号后面没有空格。

  2. 列表 my_list 中的项目呈垂直排列。在某些情况下这可能没问题,但绝对不符合 PEP 8 的指南。

  3. 函数定义和 if 语句之间没有新行,这会影响可读性。

  4. if 语句被写成了一行。这是有效的代码,但不利于可读性。

我将把代码保存到一个名为 example1.py 的文件中。我们可以如下验证这个不良示例。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果你愿意,你也可以验证这个 Python 脚本是否确实可以运行。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

接下来,我们使用 black 库来格式化文件。最简单的方法是使用命令行接口(CLI),如下所示。

$ black example1.py 

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

它告诉我们文件已被重新格式化。现在,让我们验证一下结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

def my_function(a=1, b=2, c=3, d=4):
    my_list = [a, b, c, d]
    return sum(my_list)

if True:
    print(my_function())

是的!之前提到的所有 4 个问题都已修复。

你注意到它提到“1 文件已重新格式化”了吗?没错。如果我们提供一个目录,black 库将重新格式化目录中的所有 Python 脚本。

$ black my_dir/

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上面的示例显示了我们可以重新格式化当前目录中的所有文件。此外,如果某个文件已经是完美格式,black 会告诉我们该文件保持不变。

2. 一些有用的 CLI 参数/标志

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 Bruno /Germany 提供,来自 Pixabay

像其他 CLI 工具一样,black 也有许多参数提供更多的功能。我会挑选一些我认为有用的参数,并在本节中介绍它们。

2.1 重新格式化代码片段

让我们转到另一个话题。大多数情况下,我们可能不希望一遍又一遍地重新格式化文件。一种典型的场景是我们从文档或 Stack Overflow 复制了一个函数或一段代码。(这没什么丢人的,绝大多数开发者都是这么做的 😃。然而,格式可能与我们的代码不一致,或者由于某些原因代码的格式丢失了。

在这种情况下,我们不需要将代码粘贴到文件中并重新格式化。我们可以在控制台中即时使用--code参数来完成。

例如,print()函数可以格式化如下。

$ black --code "print ( 'hello, world' )"

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在,你可以复制重新格式化的代码并将其放入你的脚本中。

2.2 检查格式(干运行)

如果我们只是想检查你的代码是否符合 PEP 8 标准而不改变它,我们可以使用--check标志,如下所示。

$ black example1.py --check

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

嗯,这告诉我们因为example1.py已经被重新格式化,所以不需要更改。然而,如果需要呢?

让我们使用原始的“坏例子”创建另一个 Python 脚本文件,并将文件命名为example2.py。让我们验证它是否是“坏例子”。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然后,让我们进行“干运行”。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以看到它说“将被重新格式化”。当然,我们也可以对一个目录进行干运行。

$ black . --check

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.3 显示将会更改的内容

对于为什么你的代码不符合标准感到好奇?我们可以使用--diff标志来显示具体的变化。

$ black example2.py --diff

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

总结

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由育银 戚提供,来自Pixabay

在这篇文章中,我介绍了black库,这是一个流行的 Python 代码格式化工具,它会自动重新格式化你的代码以符合 PEP 8 风格指南。它为你的代码提供一致和可读的格式,使其更易于阅读和维护。

我们可以使用black来确保我们的代码一致、可读和可维护。它也可以从命令行使用,具有许多有用的内置功能,可以通过参数或标志调用。希望它能帮助你写出更好的代码!

[## 使用我的推荐链接加入 Medium — Christopher Tao

作为 Medium 会员,你的部分会员费用会分给你阅读的作家,你可以完全访问每一个故事……

medium.com](https://medium.com/@qiuyujx/membership?source=post_page-----7626b6099247--------------------------------)

如果你觉得我的文章有帮助,请考虑加入 Medium 会员,以支持我和其他数千名作家!(点击上面的链接)

除非另有说明,否则所有图片均为作者所用

树集成:自助法、提升法和梯度提升

原文:towardsdatascience.com/tree-ensembles-theory-and-practice-1cf9eb27781

理论和实践详尽解释

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Jorge Martín Lasaosa

·发布于 Towards Data Science ·10 分钟阅读·2023 年 1 月 26 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Arnaud Mesureur 提供,来源于 Unsplash

树集成 是一种监督学习的机器学习技术,由一组单独训练的决策树组成,这些决策树被定义为基础学习器,单独表现可能不佳。将这些学习器聚合成一个新的模型,通常比之前的模型更准确。集成学习方法主要有三种类型:自助法提升法梯度提升。每种方法都可以与其他学习器结合使用,但在这篇文章中,仅考虑决策树。

文章的其余部分分为两个部分:

  • 直觉与历史。 解释了每种集成学习方法的起源,并对其进行了简要描述。

  • 实际演示。 逐步展开每种集成学习方法。为此,还提供了一个小的合成数据集,以帮助解释。

除非另有说明,否则所有图像均由作者提供。

直觉与历史

自助法

该术语首次由 Breiman (1996) [1] 定义,是 Bootstrap Aggregation 的缩写。对于这种集成,每个决策树都使用训练数据集的自助样本作为输入数据。自助样本是随机选择的样本,允许重复选择,这意味着观察值可能出现一次、多次或从不出现。然后,所有预测都使用统计量(如平均值)进行组合。随机森林 [2] 使用了这种技术(以及其他技术),并且是最成功和广泛使用的集成方法之一。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Bagging 可视化解释。每个带替换的样本作为弱学习器的输入数据。[3]

提升

Keans 和 Valiant (1988, 1989) [4][5] 提出了以下问题: 一组弱学习器能否创建一个强学习器? 1990 年,Schapire 的肯定回答[6] 导致了提升算法的发展。与 bagging 不同,提升采取了更迭代的方法,其中树木按顺序训练。观察被赋予权重,每棵树都是以加法的方式构建的,为前一学习器中的分类错误观察分配更大的权重(更重要)。有许多提升算法,但第一个充分利用学习器的是AdaBoost [7],由 Freund 和 Schapire 于 1995 年提出。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

AdaBoost 可视化解释。每次学习都集中在前一棵树的分类错误观察上。[8]

梯度提升

Breiman 提出了一种“梯度提升机器”[9],也采用了迭代方法,其中树木按顺序训练。然而,不同于更新权重,树木适应的是前一棵树的伪残差。第一组伪残差是通过从输出特征的平均值中减去真实值得到的。许多算法,如XGBoost [10]、CatBoost [11]或LightGBM [12],都基于这一技术。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

梯度提升可视化解释 [13]

实际演示

实际演示深受Josh Starmer 的 YouTube 频道的启发。如果你想要几乎所有机器学习模型或技术的视觉和极其简洁的解释,可以去看看!

免责声明:数据是量身定制的,以便结果符合预期。这并不意味着这些技术是万无一失的或可以相互比较。

数据

当我学习新算法或方法时,我非常喜欢在小数据集上进行测试,以便我能专注于细节。因此,在实际演示中,我们将使用一个包含关于房屋及其价格的虚构信息的小型合成数据集。Bagging 和 boosting 模型将用价格特征转换为分类特征来解释,而梯度提升则用价格作为数值特征来解释。

为本文开发的所有 Jupyter 笔记本可以在这个GitHub 代码库中找到,但主要代码片段仍会在阅读过程中展示。例如,数据创建过程可以在下面看到。

创建数据的代码。也可以在代码库中找到:TreeEnsembles/synthetic_data.py。

创建的数据包含 10 个客户和 6 个特征:

  • 平方米: 数值型

  • 如果房子有车库: 布尔值

  • 如果房子有花园: 布尔值

  • 房间数量:数值型

  • 价格:分类或数值型

如前所述,价格特征将根据解释的树集合体是分类的还是数值的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在袋装法和提升法的实际演示中使用的 DataFrame。价格特征是一个分类特征。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在梯度提升法的实际演示中使用的 DataFrame。价格特征是一个数值特征。

袋装法

首先,请记住,袋装法是Bootstrap Aggregation 的缩写。这两个词引领了方向,所以我们从第一个词开始:bootstrap。以下代码从前一节生成的数据中构建了 5 个具有 7 个观测值的自举样本。

创建样本的代码。代码也可以在仓库中找到:TreeEnsembles/Bagging.ipynb。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

每个样本包含确切的七个索引。允许重复的索引。

如前所定义,随机选择的样本是带有替换的,因此样本有重复的索引。接下来的步骤是为每个样本训练一个学习者。在这种情况下,选择的学习者是来自 scikit-learn 的决策树分类器 [15]

开发袋装法的代码。代码也可以在仓库中找到:TreeEnsembles/bagging.ipynb。

一旦每棵树都被训练完成,我们就来深入了解第一个样本及其对应的树。第一个自举样本包含了观测值*[6, 0, 2, 2, 7, 6, 8]*,这些就是树用来训练的观测值。诱导的树显示它能正确分类每个观测值,除了一个(观测值 = 0)。如果你跟随这棵树,很明显它将被分类为低,而实际价格是中等的。尽管如此,我们可以说这棵树的表现还是有一定的好的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第一个树和样本用于袋装法的实际演示。

然而,再次查看观测值的索引,只有 5 个独特的值*[0, 2, 6, 7, 8],因此第一棵树只用了 50%的样本进行训练。因此,结果的准确率可能会具有误导性。为了更好地理解决策树的表现,我们将用每棵决策树对整个数据集(10 个观测值)进行预测。此外,让我们利用袋装法的第二个词:聚合。我们的强*学习者(称为袋装法)将从五棵训练树中获取最常见的预测。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

单棵树和袋装法聚合的预测。

如下所示,从准确率来看,学习者(决策树)单独表现不如学习者(袋装法)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

袋装法和每棵训练树的准确率。

提升方法

提升与袋装不同,按顺序训练树。第一棵树用所有数据进行训练。在随后的树中,误分类观察值会赋予更高的权重(scikit learn 的决策树分类器具有权重参数)。该权重使树能够更专注于某些观察值。这里是用于实现提升集成的代码。

用于开发提升的代码。它也可以在仓库中找到:TreeEnsembles/boosting.ipynb。

如我们所说,第一步是用所有观察值训练第一棵树。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

提升的第一棵训练树。

注意到树无法正确学习观察值 [0, 6, 9]。因此,根据提升理论和代码,这些观察值在第二棵树中将具有更高的权重。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

提升的第二棵训练树

正如我们所见,使用更新权重训练的第二棵树能够正确学习之前误分类的观察值 [0, 6, 9]。分配给这些索引的更高权重迫使树正确学习它们。然而,树的学习过程也改变了其余观察值。现在,它未能学习观察值 [3, 4, 7]

因此,第三棵树会将这些误分类观察值的权重加倍,并且每棵树会纠正之前学习者所犯的错误。在下一张图中,展示了每棵树如何改进前一棵树的错误。此外,还有一列叫做提升的列,它选择了所有树中最常见的分类。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

顺序训练的树的预测值以及提升聚合的预测值。

如果我们计算每棵树的准确性以及提升聚合的准确性,结果清楚地表明,提升技术也改善了每棵单独树的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

提升和每棵训练树的准确性。

梯度提升

请记住,对于梯度提升,我们将使用目标变量价格为数值型的 DataFrame。同时,请记住,梯度提升与提升类似,采用迭代的方法。不同之处在于,树适应的是前一棵树的 伪残差,而不是相同观察值的不同权重。

我们要做的第一件事是计算第一个 伪残差residuals_0),这是通过从价格的实际值中减去价格的均值得到的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

计算出的首个伪残差(residuals_0)的 DataFrame。

使用均值预测每个观察值的平均绝对误差(MAE)为 100397.68。这是每棵训练树都会改进的指标。说完这些,我们来用之前显示的 伪残差 作为目标训练第一棵树。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

梯度提升的第一棵树使用伪残差(residuals_0)进行训练。

注意每个最终节点有不同数量的样本。在最左边的第一个节点中,只有一个观测值。它对应于索引为 1 的观测值,其 pseudo residual 为 -138461.4。在从左到右的第二个最终节点中,有 5 个样本,它们对应于索引为 [0, 2, 3, 6, 7] 的观测值。树预测的残差值 (-72705) 是这 5 个残差值的平均值。这些值将预先被称为 predicted pseudo residuals

为了使用第一棵树预测实际价格值,我们应执行以下操作。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第一棵树后的价格预测公式。

下面显示的预测达到 70347.53 的 MAE,这比之前仅用均值预测所达到的 MAE (100397.68) 更有改善。利用这些预测,我们可以计算下一个 pseudo residuals (residuals_1),这是通过将第一棵树的预测值 (predictions_0) 从实际价格值中减去得到的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第一棵树后的预测结果。

注意,residuals_1 总是比 residuals_0 更接近零。这是因为我们使用了树所提供的部分信息来改善由均值做出的预测。这部分信息的比例由 learning rate 定义。

到目前为止,仅涉及了第一棵树,但我们之前提到过梯度提升是顺序使用多棵树。现在是时候训练第二棵树了,为此目的,之前计算的残差 (residuals_1) 将作为目标使用。第二棵训练树如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第二棵梯度提升树使用伪残差(residuals_1)进行训练。

如果我们按照第一棵树的相同步骤操作,将得到以下结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第二棵树后的预测结果。

与第一棵树唯一的不同是,我们使用了第一棵和第二棵树的伪残差预测 (residual_predictions_1residual_predictions_1) 来进行预测 (predictions_1)。梯度提升不是像装袋和提升中那样进行预测聚合,而是将每棵树的少量信息添加到均值价格中 (learning rate * residuals preds tree x)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第二棵树训练后的价格预测公式。

训练了 5 棵树后,我们可以清晰地看到 MAE 在以下结果中的减少情况。每次迭代都提供了更好的学习效果,显然所有树的联合提供了比单独使用它们更好的指标。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

每次迭代后的 MAE 值。

结论

  • 本文旨在提供一个逐步指南,解释不同的集成学习方法如何工作:装袋法提升法梯度提升法

  • 在第一部分,我们回顾了这些技术的历史、描述和应用;而在第二部分,我们实际地发展了所有模型,展示了不同的学习器如何结合成一个具有更高预测能力的学习器。

  • 我希望你觉得阅读有用且愉快。最重要的是,我很乐意接受任何形式的反馈。请随时分享你的想法!

参考文献

[1] Breiman, L. 《装袋预测器》。机器学习 24 (1996): 123–40. doi.org/10.1007/BF00058655.

[2] Breiman, L. 《随机森林》。机器学习 45 (2001): 5–32. doi.org/10.1023/A:1010933404324.

[3] 图像来源: www.researchgate.net/figure/The-bagging-approach-Several-classifier-are-trained-on-bootstrap-samples-of-the-training_fig4_322179244

[4] Kearns, M. 《关于假设提升的思考》,未出版手稿(机器学习课程项目)(1988)

[5] Kearns, M; Valiant, L. 《关于学习布尔公式和有限自动机的密码学限制》。计算理论研讨会 21 (1989): 433–444 dl.acm.org/doi/10.1145/73007.73049

[6] Schapire, R.E. 《弱学习能力的力量》。机器学习 5 (1990): 197–227 doi.org/10.1007/BF00116037

[7] Freund, Y.; Schapire, R.E. (1995). 《在线学习的决策理论推广及其在提升中的应用》。计算学习理论, (1995) doi.org/10.1007/3-540-59119-2_166

[8] 图像来源: www.researchgate.net/figure/Training-of-an-AdaBoost-classifier-The-first-classifier-trains-on-unweighted-data-then_fig3_306054843

[9] Friedman, J.H. 《贪婪函数近似:一种梯度提升机》 统计年鉴 29 (2001). doi.org/10.1214/aos/1013203451.

[10] Chen, Tianqi, and Carlos Guestrin. 《XGBoost: 一种可扩展的树提升系统》 第 22 届 ACM SIGKDD 国际知识发现与数据挖掘会议论文集 (2016). doi.org/10.1145/2939672.2939785.

[11] Dorogush, A.V.; Ershov, V.; Gulin. A. CatBoost: 支持分类特征的梯度提升». ArXiv:1810.11363, 24 (2018). arxiv.org/abs/1810.11363.

[12] Ke, G.; Meng, Q.; Finley, T; Wang, T; Chen, W; Ma, W; Ye, Q; Liu, T. «LightGBM: 高效的梯度提升决策树». 神经信息处理系统进展, 20 (2017). proceedings.neurips.cc/paper/2017/hash/6449f44a102fde848669bdd9eb6b76fa-Abstract.html.

[13] 图片来源: www.researchgate.net/profile/Karem-Abdelmohsen/publication/339077244/figure/fig3/AS:855596286877696@1581001459188/Schematic-diagram-of-a-tree-based-gradient-boosting-method.png

[14] StatQuest with Josh Starmer (YouTube 频道): www.youtube.com/c/joshstarmer

[15] scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html

思维树提示

原文:towardsdatascience.com/tree-of-thoughts-prompting-65a3e51f9ac4

通过有意规划和探索来解决 LLMs 的多步骤问题……

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Cameron R. Wolfe, Ph.D.

·发表于 Towards Data Science ·20 min 阅读·2023 年 12 月 22 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(照片由 Johann Siemens 提供,刊登在 Unsplash

当大型语言模型(LLMs)首次开始受到关注时,它们因在解决复杂推理问题方面的不足而受到批评。尽管扩大这些模型的规模(即更多的参数和数据)在各项任务中提供了近乎一致的性能提升,但我们几乎没有看到现代 LLMs 在推理任务上的性能提升。这一情况随着先进提示技术的提出而改变,如思维链提示[2]和自一致性[3]。这些方法向我们展示了 LLMs 完全有能力进行推理和解决复杂的多步骤问题。它们只需要正确的提示来充分发挥这些能力。

“也许令人惊讶的是,所有这些进展背后仍然是最初的自回归文本生成机制,该机制逐个生成令牌,并按从左到右的方式进行决策。” — 摘自 [1]

即使适当的提示可以使大型语言模型(LLMs)解决复杂问题,这些技术仍然存在不足。即,我们通常*(i)* 给 LLM 提供一个提示,并*(ii)* 期望模型通过下一个词预测生成完整的解决方案。某些方法可能以逐步的方式生成解决方案(例如,最少到最多提示[8]),但 LLM 仍然遵循单一的推理路径,而不是探索多种潜在解决方案。对于那些初步决策可能完全破坏解决方案的复杂问题,这种方法将难以奏效,这一点尤其值得注意,因为 LLM 现在常被用作各种实际应用中的通用问题解决者。简单来说,我们需要一种在解决问题时能够进行更周密规划和探索的提示方法

在[1]中,作者提出了一种被称为“思维树提示”的方法,它通过明确地将问题分解为一系列思维,即中间步骤,来解决问题。类似于链式思维提示,思维树提示生成的解决方案仅仅是一个单独思维的序列。然而,这种方法更进一步,允许同时考虑多条推理路径——形成一个潜在思维或推理路径的树状结构——并通过 LLM 驱动的自我评估探索整个解决方案空间。通过思维树提示,LLM 可以刻意规划其解决方案,测试各种中间推理路径,甚至进行回溯,从而探索解决方案空间,并最终生成正确的输出。

与其他领域和生成方法的研究联系

“一个真正的问题解决过程涉及反复使用可用信息来启动探索,这反过来又揭示了更多信息,直到最终发现解决方案的方法。” ——摘自[12]

类比于人类。 为了解释他们的技术,[1]中的作者借鉴了对人类决策过程的分析。特别是,人类似乎有两种不同的决策模式:

  • 一种快速、自动、无意识的模式

  • 一种缓慢、刻意、有意识的模式

[1]中的作者认为,链式思维提示等技术似乎模仿了上述第一种模式,因为 LLM 只是以从左到右的方式生成文本,而没有进行刻意的规划或问题的解构。树状思维提示的主要动机是通过将每个问题分解成一个由较小步骤组成的树状结构,这些步骤被逐一探索,从而将刻意的规划和探索注入到问题解决过程中。

受到早期人工智能工作的启发。 思维树提示所遵循的规划过程受到 20 世纪中期人工智能研究的启发 [12, 13]!这项工作认为,问题解决可以被表述为在表示为树的组合空间中进行搜索。在这个空间内,保持多个活跃的思维链,每个链表示在大树中的一条路径。正如我们将看到的,这种表述使我们能够明确地分解复杂问题的解决方案,并利用已有的图算法(例如广度优先和深度优先搜索)找到可行的解决方案。

提示的基础知识

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来源于 [10])

LLM 的通用文本到文本格式非常强大。要解决任何问题,我们可以简单地 i) 编写一个描述问题的文本提示,以及 ii) 使用语言模型生成相关的输出/解决方案。因此,LLM 被认为是基础模型,即能够单独适应解决各种任务的模型。这种能力很大程度上得益于上下文学习。即,预训练的 LLM 有能力利用注入到提示中的数据作为上下文,产生更准确的输出;见下文。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来源于 [5])

然而,上下文学习的有效性与用于解决问题的提示高度相关。存在许多不同的提示方法——包括思维树提示 [1]——但选择正确的提示技术或编写正确的提示可能相当困难。因此,我们现在将简要介绍提示工程的基础知识,提供有用的背景,使本概述中探索的各种提示技术更加易于理解。

什么是提示工程?

“提示工程是一个相对较新的学科,用于开发和优化提示,以高效地利用语言模型进行各种应用和研究主题。” — 来源于 [2]

提示工程指的是不断调整语言模型的提示,以发现能够准确解决预期任务的提示的过程。通常,提示工程的过程是经验性的,这意味着我们通过测量提示在相关任务上的表现来发现最佳提示。提示是一个充满启发式方法和各种不同技术的新领域。因此,我们可以通过遵循类似于其他工程问题的方法来最大化成功的机会:

  • 跟踪和版本化不同的提示

  • 设置广泛的基准以衡量提示的表现

  • 测试不同的想法,看看哪些能产生最佳结果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(由作者创建)

上下文窗口。编写提示时一个主要的考虑因素是底层 LLM 的上下文窗口的大小。如上图所示,所有 LLM 都是使用一定大小的输入进行训练的(即,上下文窗口或上下文长度的大小),这 — 连同内存限制 — 限制了在提示中可以提供给 LLM 的数据总量。实际上,这意味着我们必须对提示中包含的数据进行选择。接下来,我们将概述提示的组成部分,以及可能提供的信息类型,以引导 LLM 找到正确的解决方案。

提示的结构

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用所有结构组件的示例提示,指示符已加粗以便于查看(由作者创建)

存在多种提示技术,但这些技术都采用(相对)常见的结构。图中展示了可能遇到的提示的各种组件,并在下文中进行了概述。

  • 输入数据:LLM 正在处理的输入数据。

  • 示例:演示如何正确解决所需问题的输入输出示例。

  • 指令:对 LLM 期望行为的详细文本描述。

  • 指示符:用于组织和结构化提示不同组件的文本标签。

  • 上下文:可能对 LLM 有用的任何额外上下文(例如,从向量数据库中检索的信息块)。

值得注意的是,并非所有这些组成部分在编写提示时都是必要的。本概述中探讨的几种技术将只使用上述组件的子集,但每种技术都可以在必要时用来提供额外的有用信息给 LLM。

提示技术的层级

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

编写提示是一个迭代过程,应该从简单开始,仅在需要时添加复杂性。

现在我们对上下文学习和提示工程有了基本了解,我们需要深入探讨一些常见的语言模型提示技术。我们将从简单的技术开始,例如零-shot 和少-shot 提示,然后转向更复杂的技术,如思维链提示 [2] 和自一致性 [3]。一如既往,我们应该记住编写提示时的最佳方法是简单 — 我们应尽可能从简单开始,然后使用测试驱动开发来决定何时需要额外的复杂性。换句话说,我们可以基于我们期望的应用创建一个大规模的提示示例基准,然后在我们迭代和测试不同的提示变体时测量在该基准上的表现。有关提示的更全面(和实用)的指南,请查看文章 这里。

零-shot 和少-shot 提示

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [5])

零样本提示 是我们可以用于提示语言模型的最简单技术之一,它最初因 GPT-2 利用这一技术在各种自然语言基准测试中表现出色而广受欢迎。要形成零样本提示,我们需要提供两项信息(见上文):

  1. 任务描述

  2. 我们的输入数据

在这里,语言模型预计利用其知识库和提供的任务描述来解决问题,而无需任何明确的示例或详细指令。尽管许多语言模型在零样本提示下表现相对良好,但我们通常需要向模型提供额外的细节以获得更可靠和准确的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [5])

少样本提示 通过在提示中添加模型期望输出的“示例”来超越零样本提示;见上文。除了任务描述,我们还提供几个正确的输入输出对的示例。通过将这些上下文添加到提示中,我们可以为语言模型提供更具体的输出细节。这一技术在 GPT-3 [5] 中得到推广,当时首次显示语言模型在上下文学习方面具有很高的能力。简而言之,模型可以从这些示例中学习,并随着示例的增多提高准确性。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [5])

指令提示

零样本和少样本学习是效果惊人的简单技术,但有时它们不会产生足够的性能水平。而且,少样本学习还增加了提示的大小。如果这些技术不适用于我们的用例,我们可以尝试的下一种技术是指令提示。指令提示与其包含任务描述和几个正确输出示例不同,它在提示中包含详细的指令 —— 或对正确行为的解释 —— 并将其提供给语言模型;见下文。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

带有指示器的指令提示示例(由作者创建)

而且,指令提示和少样本提示并非相互排斥!我们可以轻松将指令提示与几个少样本示例结合,以提高性能。实际上,零样本和少样本提示技术使用的任务描述实际上与指令本身非常相似。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [6])

对齐是必要的。 制定深思熟虑的指令是一种非常有效且令牌高效的提示技术。然而,并非所有语言模型都擅长遵循指令。例如,预训练的(基础)LLM 本身并不具备自然跟随详细指令的能力。这种能力通常通过对齐过程来发展,对齐过程微调了 LLM 跟随指令的能力;见上文。许多现代 LLM(如 GPT-4)非常可引导(即擅长跟随详细指令),使得指令提示成为与这些模型合作的最有效技术之一,如下所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

GPT-4 是可引导的,并且可以轻松跟随提示中的复杂指令(由作者创建)

高级提示技术

有时,少量提示和指令提示不足以完成期望的任务。特别是,语言模型往往在处理复杂推理问题时表现不佳,例如需要多个步骤的常识推理问题或数学难题。然而,已经开发了众多高级提示技术——包括思维树提示——以扩展可以用 LLM 解决的困难问题的范围。在本节中,我们将重点介绍一种技术——思维链(CoT)提示 [2](及其几种变体)——这种技术在实践中特别有效,并形成了思维树提示方法的基础。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来源于 [2])

什么是 CoT 提示? 通过利用上下文学习能力,CoT 提示鼓励语言模型通过输出解决方案及相应的“思维链”(即问题解决的逐步解释)来更有效地解决复杂问题。可以通过几-shot 学习方法提供几个思维链示例来提示模型生成思维链;见上文。CoT 技术在输入到输出的映射非常复杂时效果最佳;例如数学或多步骤推理问题。在这种情况下,引入思维链允许模型沿着更小的中间步骤接近正确的最终解决方案。在实践中,发现 CoT 提示在各种推理任务中的 LLM 性能显著提升;见下文。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来源于 [2])

CoT 提示的变体。 鉴于 CoT 提示的实际效用(即,它可以用来解决 LLM 通常难以应对的复杂多步骤问题!),在其提出后不久,开发了几种变体,例如零-shot CoT [7] 和从少到多提示 [8];见下文。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来源于 [7, 8])

与思维树提示最相关的 CoT 提示变体是自一致性 [3]。这种方法利用了类似于 CoT 提示的方法。一个模型使用相同的(CoT)提示多次生成输出。然后,通过对模型输出进行多数投票来生成最终答案,如下图所示。这种方法被发现与 CoT 提示具有类似的好处,并且在更困难的问题上提高了性能和可靠性。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来源 [3])

现有的限制。 技术如 CoT 提示和自一致性大大扩展了可用 LLM 解决的问题范围。如果没有这些技术,解决多步骤推理问题将会非常困难。然而,这些提示技术并非没有局限性。例如,并非所有问题都有适合多数投票的解空间,而且已有研究表明,即使对于可以以这种方式表述的问题,多数投票也被证明是改善 LLM 准确性的一个糟糕启发式方法。

“我们观察到准确率的平均变化为 9.5%,且误差的 Jaccard 指数比如果提示误差是 i.i.d. 的情况高出 69%。多数投票(MV)是先前工作中的主要无监督聚合策略,但它没有考虑到这两个特性,使其不可靠。” — 来源 [9]

更广泛地说,解决复杂任务可能需要广泛的规划、战略前瞻、回溯,甚至同时探索大量可行的解决方案。技术如 CoT 提示遵循从左到右的连续生成方法,利用下一个标记预测一次性输出解决方案。这种方法虽然在某些场景下非常有效,但无法解决需要战略规划和探索的任务。但是,这正是思维树提示派上用场的地方!与 CoT 提示类似,思维树提示将问题分解为更小的部分(即思维链),但进一步结合了并行探索多条解决路径的能力,形成一个树而不是单一链条!

理解思维树提示

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来源 [1])

CoT 提示的有效性来自于将复杂问题的解决方案拆分成一系列更小、更简单步骤的能力。思维树(ToT)提示类似地将问题拆分成一系列更小的步骤——或思路——逐一解决。然而,这种方法并不限制模型一次性输出这些步骤。相反,每个思路是独立生成或解决的,并传递到下一步骤来解决问题,这使得模型能够:

  • 探索每个问题解决思路的多种选择。

  • 评估某些思路是否使模型更接近最终解决方案。

  • 当某些思维被发现是死胡同时进行回溯。

  • 在可能的问题解决步骤的组合空间中进行搜索,以找到最佳的最终解决方案。

通过 ToT 提示,可以形成整个思维树(如上图所示),允许在解决问题的过程中探索不同的思维。在探索过程中,LLM 可以通过基于语言的过程评估每个思维在最终解决方案中的进展。然后,通过利用广泛使用的搜索算法(例如,广度优先搜索或深度优先搜索),ToT 提示可以增强前瞻性和回溯技术,从而彻底探索任何问题的解决空间。

“虽然现有方法为问题解决采样连续的语言序列,但 ToT 主动维护一棵思维树,其中每个思维是一个连贯的语言序列,作为问题解决的中间步骤。” — 引自 [1]

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(引自 [1])

树代表什么? 使用 ToT 提示时,我们探索几个路径——每条路径由单独的思维组成——这些路径代表问题的潜在解决方案。所有这些路径及其单独的思维共同形成了一棵树,探索问题的解决空间;见上文。在这棵树中,每个节点只是我们问题的部分解决方案(或思维),而每个连接是一个操作符,它修改这个部分解决方案,产生问题解决路径中的下一个思维。下面展示了如何以这种方式分解问题解决思维链(即思维树中的单条路径)的示例。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用操作符迭代修改部分解决方案,直到找到最终解决方案(引自 [2])

思维树问题解决框架

到目前为止,我们已经讨论了 ToT 提示的通用理念,但我们如何在实际应用中使用这种技术?ToT 提示的实现因我们要解决的问题而有所不同,但任何 ToT 提示的实例必须具体定义四个标准问题解决组件,如下所述。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(引自 [1])

思维分解。 与 CoT 提示不同,ToT 明确将问题分解为中间步骤或思维,这些思维组合在一起形成对基础问题的解决方案。根据问题的不同,这种分解可以采取各种不同的形式,例如输出几个词或一行方程式。如上图所示,每个任务中的思维定义在 [1] 中有所不同。

“一个思想应该足够小,以便 LMs 可以生成有前途和多样的样本(例如,生成整本书通常太大而无法连贯),但也要足够大,以便 LMs 可以评估其解决问题的前景(例如,生成一个标记通常太小而无法评估)。"* — 来自 [1]*

思想生成。 一旦我们决定了什么构成一个思想,我们需要确定在 ToT 提示过程中如何生成思想。在 [1] 中,作者提出了两种基本的思想生成技术:

  • 采样:用相同的提示独立生成多个思想

  • 提议:用“提议提示”顺序生成多个思想

采样方法在思想空间丰富时效果最好,因为几种独立生成的思想不太可能重复。如果思想空间更加受限,那么可以使用提议技术生成多个思想,同时避免重复。

状态评估。 一旦我们定义了我们的思想并选择了它们的生成方式,我们需要定义一种启发式方法来评估某些思路链的质量。否则,我们无法知道是否在朝着最终解决方案取得进展。给定几个已生成的思想,[1] 中的作者使用 LLM 来推理每个思想的质量。特别地,遵循两种不同的策略:

  • 价值:独立地给每种状态分配一个标量值(即,1–10 的评分)或分类(即,确定、可能或不可能达成解决方案)。

  • 投票:比较不同的解决方案,并选择最有前景的一个。

虽然这两种方法都可以很好地工作,但当一个问题的成功解决方案难以直接评估时(例如创意写作任务),投票是最好的选择。在这两种情况下,可以多次提示 LLM,类似于自一致性,以实现对每种状态的更可靠评估。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [1])

搜索算法。 ToT 提示的最终组件是用于探索解决方案空间的搜索算法。尽管可以使用许多潜在的搜索算法,但我们在 [1] 中看到,作者专注于两种基本算法 — BFS 和 DFS — 其公式如上所示。

实验分析

[1] 中的作者提出了三项新任务,用于评估 ToT 提示技术:24 游戏、创意写作和 5x5 填字游戏。对于每个任务,我们将首先概述 ToT 提示的实施,这遵循上述的四部分框架。然后,我们将概述实验结果,突出 ToT 提示在需要大量规划或搜索的问题上的有效性。值得注意的是,像 CoT 提示和自一致性这样的替代方法往往无法解决这些复杂任务。

24 游戏

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [1])

上述展示了 ToT 提示在 24 游戏任务中的实现。在此任务中,LLM 给定四个数字,并期望生成一系列算术运算——每个数字只使用一次——以得到数字 24。此任务始终被分解为三个思考,每个思考都是一个中间方程。相同的提示用于生成候选解决方案中的每三个思考,并通过一个值提示来评估状态,该提示将中间解决方案分类为 确定可能不可能 得到正确的最终解决方案。然后,应用 BFS 算法来寻找结果解决方案,在每一步保持最好的五个(即 b=5)候选解决方案。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(摘自 [1])

性能。 ToT 提示与几种基线方法进行了比较,包括标准的少量示例提示(IO)、CoT 提示和基于 CoT 的自一致性(CoT-SC)。如上所示,所有基线方法在此任务中的表现都相当差(即成功率<10%),而 ToT 提示的成功率高达 74%。有趣的是,随着 BFS 的 b 设置值增加,成功率也有所提高。此外,基于 [1] 中的错误分析,我们发现大多数使用 CoT 提示的解决方案在第一步之后失败,而 ToT 提示的失败则均匀分布在各个中间步骤之间。这一发现表明 ToT 提示的好处,因为它可以在生成最终输出之前评估中间状态,从而允许探索多个可行的解决路径。

创意写作

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(摘自 [1])

[1] 中探讨的创意写作任务提供了四个随机句子作为输入,并期望生成包含四个段落的文章,每个段落以这四个输入句子结尾。输出质量通过 GPT-4(使用零-shot 提示)或人工评估来判断。在此任务中,ToT 提示需要两个中间思考。首先,LLM 生成五个不同的写作计划,并使用零-shot 投票提示选择最佳计划。然后,根据选定的计划生成五个不同的段落,并通过(再次)零-shot 投票提示确定最佳段落;见上文。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(摘自 [1])

性能。 如上图所示,ToT 提示生成的段落比少量示例和 CoT 提示的段落更连贯,这一点得到了 GPT-4 和人工评估者的共同判断。当应用迭代改进程序以提高结果质量时,我们发现少量示例提示和 ToT 提示的表现相当。这种程序可以被视为一种新的思考生成方法,其中通过提示 LLM 来改进旧思考,而不是从头生成新思考。

5x5 填字游戏

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(摘自 [1])

[1]中考虑的最终任务是一个迷你填字游戏,旨在探索 ToT 提示发现需要更多中间步骤的问题的能力。该任务的输入提供了五个水平提示和五个垂直提示,而输出应该是一个 5x5 字母网格,以解决填字谜题。成功是根据每种提示技术在字母、单词和游戏方面的正确程度来判断的。

ToT 设置。 对于迷你填字游戏,ToT 提示使用深度优先搜索(DFS)进行实现。每个思路都考虑单个单词提示。思路是按顺序生成的,并且不能改变任何当前已填写的单词或字母。为了找到新的候选答案,LLM 将所有现有的思路作为输入,根据这些思路生成剩余单词提示的字母约束,并使用提议提示来提出下一个应填写的单词及其位置。值得注意的是,LLM 还会被提示为每个思路提供一个置信度级别,从而可以按照置信度的顺序探索思路。中间状态的评估基于是否可以得到一个可行的最终解决方案。如果不能,DFS 将回溯到思路树中的父节点并继续探索。整个过程如上图所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自[1])

性能。 如上表所示,ToT 提示在迷你填字游戏基准测试中的成功率远远优于少量样本和 CoT 提示。即便如此,ToT 提示只在测试的 20 个谜题中全球解决了 4 个,显示出在此类任务上仍有很大的改进空间。然而,ToT 提示通过 DFS 回溯和探索不同解决方案的能力是一个巨大的区分因素。

“这种改进并不令人惊讶,因为 IO 和 CoT 缺乏尝试不同提示、修改决策或回溯的机制。”— 来自 [1]

结束语

最近关于提示技术的研究大大扩展了通过 LLM 可解决的问题范围。然而,大多数提示技术受到语言生成的自回归性质的限制——它们倾向于遵循从左到右的方法,缺乏深思熟虑的规划和探索问题替代解决方案的机会。ToT 提示通过将问题的解决方案抽象为一个可以使用已知算法和启发式方法独立探索和评估的中间步骤树来解决这个问题。ToT 提示的理念非常通用,可以根据底层问题进行不同的实例化。在[1]中,我们可以看到若干示例,其中 ToT 提示被证明比 CoT 提示及相关变体更有效地解决了多步骤推理问题。

与我联系!

非常感谢阅读本文。我是 Cameron R. WolfeRebuy 的 AI 总监。我研究深度学习的实证和理论基础。如果你喜欢这个概述,可以订阅我的 Deep (Learning) Focus newsletter,我在这里帮助读者从基础开始理解 AI 研究。你也可以在 XLinkedIn 上关注我,或者查看我在 medium 上的 其他文章

参考文献

[1] Yao, Shunyu, et al. “思想树:与大型语言模型进行深思熟虑的问题解决。” arXiv 预印本 arXiv:2305.10601 (2023)。

[2] Wei, Jason, et al. “思维链提示引发大型语言模型中的推理。” 神经信息处理系统进展 35 (2022): 24824–24837。

[3] Wang, Xuezhi, et al. “自洽性改善了语言模型中的思维链推理。” arXiv 预印本 arXiv:2203.11171 (2022)。

[4] Radford, Alec, et al. “语言模型是无监督的多任务学习者。”

[5] Brown, Tom, et al. “语言模型是少样本学习者。” 神经信息处理系统进展 33 (2020): 1877–1901。

[6] Ouyang, Long, et al. “通过人类反馈训练语言模型以遵循指令。” 神经信息处理系统进展 35 (2022): 27730–27744。

[7] Kojima, Takeshi, et al. “大型语言模型是零样本推理者。” 神经信息处理系统进展 35 (2022): 22199–22213。

[8] Zhou, Denny, et al. “从少到多的提示使大型语言模型能够进行复杂推理。” arXiv 预印本 arXiv:2205.10625 (2022)。

[9] Arora, Simran, et al. “问我任何事:一种简单的语言模型提示策略。” arXiv 预印本 arXiv:2210.02441 (2022)。

[10] Raffel, Colin, et al. “探索统一文本到文本变换器的迁移学习极限。” 机器学习研究期刊 21.1 (2020): 5485–5551。

[11] Saravia, Elvis, et al. “提示工程指南”, github.com/dair-ai/Prompt-Engineering-Guide (2022)。

[12] A. Newell, J. C. Shaw, and H. A. Simon. 关于一般问题解决程序的报告。在 IFIP 会议,卷 256,第 64 页。宾夕法尼亚州匹兹堡,1959。

[13] A. Newell, H. A. Simon, et al. 人类问题解决。普伦蒂斯-霍尔,1972。

在您的数据上训练 YOLOv8 实例分割

原文:towardsdatascience.com/trian-yolov8-instance-segmentation-on-your-data-6ffa04b2debd?source=collection_archive---------0-----------------------#2023-02-15

如何基于最新的 YOLOv8 模型在您的数据上训练一个实例分割模型

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Alon Lekhtman

·

关注 发表在 Towards Data Science ·10 分钟阅读·2023 年 2 月 15 日

YOLOv8 于 2023 年 1 月 10 日发布。到目前为止,这是计算机视觉领域用于分类、检测和分割任务的最先进模型。该模型在准确性和执行时间方面均优于所有已知模型。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

YOLOv8 与其他 YOLO 模型的比较(来自 ultralytics

ultralytics 团队在使这个模型比所有之前的 YOLO 模型更易于使用方面做得非常出色——你甚至不再需要克隆 git 仓库了!

创建图像数据集

在这篇文章中,我创建了一个非常简单的示例,展示了训练 YOLOv8 所需的所有步骤,特别是用于分割任务。数据集很小,对模型来说“易于学习”,故意如此,以便我们能够在简单的 CPU 上训练几秒钟后获得令人满意的结果。

我们将创建一个白色圆圈与黑色背景的数据集。圆圈将有不同的尺寸。我们将训练一个模型,能够在图像中分割出这些圆圈。

这就是数据集的样子:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

数据集是使用以下代码生成的:

import numpy as np
from PIL import Image
from skimage import draw
import random
from pathlib import Path

def create_image(path, img_size, min_radius):
    path.parent.mkdir( parents=True, exist_ok=True )

    arr = np.zeros((img_size, img_size)).astype(np.uint8)
    center_x = random.randint(min_radius, (img_size-min_radius))
    center_y = random.randint(min_radius, (img_size-min_radius))
    max_radius = min(center_x, center_y, img_size - center_x, img_size - center_y)
    radius = random.randint(min_radius, max_radius)

    row_indxs, column_idxs = draw.ellipse(center_x, center_y, radius, radius, shape=arr.shape)

    arr[row_indxs, column_idxs] = 255

    im = Image.fromarray(arr)
    im.save(path)

def create_images(data_root_path, train_num, val_num, test_num, img_size=640, min_radius=10):
    data_root_path = Path(data_root_path)

    for i in range(train_num):
        create_image(data_root_path / 'train' / 'images' / f'img_{i}.png', img_size, min_radius)

    for i in range(val_num):
        create_image(data_root_path / 'val' / 'images' / f'img_{i}.png', img_size, min_radius)

    for i in range(test_num):
        create_image(data_root_path / 'test' / 'images' / f'img_{i}.png', img_size, min_radius)

create_images('datasets', train_num=120, val_num=40, test_num=40, img_size=120, min_radius=10)

创建标签

现在我们有了图像数据集,我们需要为图像创建标签。通常,我们需要进行一些手动操作,但由于我们创建的数据集非常简单,因此很容易编写代码来生成标签:

from rasterio import features

def create_label(image_path, label_path):
    arr = np.asarray(Image.open(image_path))

    # There may be a better way to do it, but this is what I have found so far
    cords = list(features.shapes(arr, mask=(arr >0)))[0][0]['coordinates'][0]
    label_line = '0 ' + ' '.join([f'{int(cord[0])/arr.shape[0]} {int(cord[1])/arr.shape[1]}' for cord in cords])

    label_path.parent.mkdir( parents=True, exist_ok=True )
    with label_path.open('w') as f:
        f.write(label_line)

for images_dir_path in [Path(f'datasets/{x}/images') for x in ['train', 'val', 'test']]:
    for img_path in images_dir_path.iterdir():
        label_path = img_path.parent.parent / 'labels' / f'{img_path.stem}.txt'
        label_line = create_label(img_path, label_path)

这里是标签文件内容的示例:

0 0.0767 0.08433 0.1417 0.08433 0.1417 0.0917 0.15843 0.0917 0.15843 0.1 0.1766 0.1 0.1766 0.10844 0.175 0.10844 0.175 0.1177 0.18432 0.1177 0.18432 0.14333 0.1918 0.14333 0.1918 0.20844 0.18432 0.20844 0.18432 0.225 0.175 0.225 0.175 0.24334 0.1766 0.24334 0.1766 0.2417 0.15843 0.2417 0.15843 0.25 0.1417 0.25 0.1417 0.25846 0.0767 0.25846 0.0767 0.25 0.05 0.25 0.05 0.2417 0.04174 0.2417 0.04174 0.24334 0.04333 0.24334 0.04333 0.225 0.025 0.225 0.025 0.20844 0.01766 0.20844 0.01766 0.14333 0.025 0.14333 0.025 0.1177 0.04333 0.1177 0.04333 0.10844 0.04174 0.10844 0.04174 0.1 0.05 0.1 0.05 0.0917 0.0767 0.0917 0.0767 0.08433

标签对应于这张图像:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对应于标签示例的图像

标签内容仅为一行文本。每张图像中只有一个物体(圆圈),每个物体由文件中的一行表示。如果每张图像中有多个物体,你应该为每个标记物体创建一行。

第一个 0 表示标签的类别类型。因为我们只有一个类别(圆圈),所以我们总是使用 0。如果你的数据中有多个类别,你应该将每个类别映射到一个数字(0, 1, 2…),并在标签文件中使用这个数字。

所有其他数字表示标记物体的边界多边形的坐标。格式是 <x1 y1 x2 y2 x3 y3…>,这些坐标相对于图像的大小——你应该将坐标标准化为 1x1 的图像大小。例如,如果有一个点 (15, 75) 而图像大小是 120x120,则标准化后的点是 (15/120, 75/120) = (0.125, 0.625)。

在处理图像库时,处理坐标的正确方向总是令人困惑。因此,为了使其清晰,对于 YOLO,X 坐标从左到右,而 Y 坐标从上到下。

YAML 配置

我们已经有了图像和标签。现在我们需要创建一个包含数据集配置的 YAML 文件:

yaml_content = f'''
train: train/images
val: val/images
test: test/images

names: ['circle']
    '''

with Path('data.yaml').open('w') as f:
    f.write(yaml_content)

请注意,如果你有更多的物体类别类型,你需要在名称数组中添加它们,顺序与标签文件中的顺序一致。第一个是 0,第二个是 1,依此类推…

数据集文件结构

让我们看看我们创建的文件结构,使用 Linux tree 命令:

tree .
data.yaml
datasets/
├── test
│   ├── images
│   │   ├── img_0.png
│   │   ├── img_1.png
│   │   ├── img_2.png
│   │   ├── ...
│   └── labels
│       ├── img_0.txt
│       ├── img_1.txt
│       ├── img_2.txt
│       ├── ...
├── train
│   ├── images
│   │   ├── img_0.png
│   │   ├── img_1.png
│   │   ├── img_2.png
│   │   ├── ...
│   └── labels
│       ├── img_0.txt
│       ├── img_1.txt
│       ├── img_2.txt
│       ├── ...
|── val
|   ├── images
│   │   ├── img_0.png
│   │   ├── img_1.png
│   │   ├── img_2.png
│   │   ├── ...
|   └── labels
│       ├── img_0.txt
│       ├── img_1.txt
│       ├── img_2.txt
│       ├── ...

训练模型

现在我们已经有了图像和标签,我们可以开始训练模型。因此首先安装包:

pip install ultralytics==8.0.38

ultralytics 库更新非常快,有时会导致 API 中断,因此我更倾向于使用一个版本。以下代码依赖于版本 8.0.38(这是我写这些内容时的最新版本)。如果你升级到较新的版本,可能需要对代码进行一些调整以使其正常工作。

并开始训练:

from ultralytics import YOLO

model = YOLO("yolov8n-seg.pt")

results = model.train(
        batch=8,
        device="cpu",
        data="data.yaml",
        epochs=7,
        imgsz=120,
    )

为了简化这篇文章,我使用了 nano 模型(yolov8n-seg),仅在 CPU 上训练,仅用了 7 个周期。训练在我的笔记本电脑上只花了几秒钟。

关于可用于训练模型的参数的更多信息,你可以查看这个链接

理解结果

训练完成后,你会在输出的末尾看到一行,类似于这样:

Results saved to runs/segment/train60

让我们来看一下这里的一些结果:

验证标签

from IPython.display import Image as show_image
show_image(filename="runs/segment/train60/val_batch0_labels.jpg")

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

部分验证集标签

在这里我们可以看到部分验证集的真实标签。这应该几乎完全对齐。如果你看到这些标签没有很好地覆盖对象,很可能是你的标签不正确。

预测验证标签

show_image(filename="runs/segment/train60/val_batch0_pred.jpg")

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

验证集预测

在这里我们可以看到训练模型在部分验证集上的预测(与上面看到的部分相同)。这可以让你感受模型的表现如何。请注意,为了创建此图像,应选择一个置信度阈值,这里使用的阈值是0.5,这并不总是最佳的(我们稍后会讨论)。

精度曲线

要理解这些以及接下来的图表,你需要对精度和召回率的概念有所了解。这里对它们的工作原理做了很好的解释。

show_image(filename="runs/segment/train60/MaskP_curve.png")

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

精度/置信度阈值曲线

模型检测到的每个对象都有一定的置信度,通常,如果你希望在声明“这是一个圆形”时尽可能确定,你会只使用高置信度值(高置信度阈值)。当然,这会有一个权衡——你可能会错过一些“圆形”。另一方面,如果你想“捕捉”尽可能多的“圆形”,即使有些并不真正是“圆形”,你会使用低置信度值和高置信度值(低置信度阈值)。

上面的图表(以及下面的图表)帮助你决定使用哪个置信度阈值。在我们的例子中,我们可以看到对于高于0.128的阈值,我们获得了**100%**的精度,这意味着所有对象都被正确预测。

请注意,由于我们实际上是在进行分割任务,还有一个重要的阈值需要关注——IoU(交并比),如果你不熟悉它,可以在这里阅读相关信息。对于这张图表,使用了0.5的 IoU。

召回率曲线

show_image(filename="runs/segment/train60/MaskR_curve.png")

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

召回率/置信度阈值曲线

这里你可以看到召回率图表,随着置信度阈值的提高,召回率下降。这意味着你“捕捉到”的“圆圈”变少了。

这里你可以看到为什么在这种情况下使用0.5的置信度阈值是一个糟糕的主意。对于0.5的阈值,你得到大约90%的召回率。然而,在精度曲线中,我们看到对于高于0.128的阈值,我们可以获得100%的精度,因此我们不需要达到0.5,可以安全地使用0.128的阈值,获得**100%的精度和接近100%**的召回率 😃

精度-召回曲线

这里是对精度-召回曲线的很好解释

show_image(filename="runs/segment/train60/MaskPR_curve.png")

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

精度-召回曲线

我们可以清楚地看到我们之前得出的结论,对于这个模型,我们可以达到接近**100%的精度和100%**的召回率。

这个图表的缺点是我们无法看到应该使用哪个阈值,这就是为什么我们仍然需要上述图表的原因。

随时间变化的损失

show_image(filename="runs/segment/train60/results.png")

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

随时间变化的损失

这里你可以看到不同损失在训练过程中的变化,以及每个时期之后它们在验证集上的表现。

关于损失和从这些图表中得出的结论有很多可以说的,但这超出了本文的范围。我只是想说明你可以在这里找到相关信息 😃

使用训练好的模型

结果目录中还可以找到模型本身。这是如何在新图像上使用模型:

my_model = YOLO('runs/segment/train60/weights/best.pt')
results = list(my_model('datasets/test/images/img_5.png', conf=0.128))
result = results[0]

结果列表可能包含多个值,每个值对应一个检测到的对象。由于在这个例子中每张图片中只有一个对象,我们取第一个列表项。

你可以看到我传递了我们之前找到的最佳置信度阈值(0.128)。

有两种方法可以获取检测到的对象在图像中的实际位置。选择正确的方法取决于你打算如何使用结果。我将展示这两种方法。

result.masks.segments
[array([[    0.10156,     0.34375],
        [    0.09375,     0.35156],
        [    0.09375,     0.35937],
        [   0.078125,       0.375],
        [   0.070312,       0.375],
        [     0.0625,     0.38281],
        [    0.38281,     0.71094],
        [    0.39062,     0.71094],
        [    0.39844,     0.70312],
        [    0.39844,     0.69531],
        [    0.41406,     0.67969],
        [    0.42187,     0.67969],
        [    0.44531,     0.46875],
        [    0.42969,     0.45312],
        [    0.42969,     0.41406],
        [    0.42187,     0.40625],
        [    0.41406,     0.40625],
        [    0.39844,     0.39062],
        [    0.39844,     0.38281],
        [    0.39062,       0.375],
        [    0.38281,       0.375],
        [    0.35156,     0.34375]], dtype=float32)]

这会返回对象的边界多边形,类似于我们传递标记数据的格式。

第二种方法是:

result.masks.masks
tensor([[[0., 0., 0.,  ..., 0., 0., 0.],
         [0., 0., 0.,  ..., 0., 0., 0.],
         [0., 0., 0.,  ..., 0., 0., 0.],
         ...,
         [0., 0., 0.,  ..., 0., 0., 0.],
         [0., 0., 0.,  ..., 0., 0., 0.],
         [0., 0., 0.,  ..., 0., 0., 0.]]])

这会返回一个形状为(1, 128, 128)的张量,表示图像中的所有像素。属于对象的像素接收 1,背景像素接收 0。

让我们看看掩模的样子:

import torchvision.transforms as T
T.ToPILImage()(result.masks.masks).show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

预测图像的分割

这就是原始图像:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

原始图像

虽然不完美,但对于许多应用来说已经足够好,并且 IoU 确实高于0.5

总之,与之前的 Yolo 版本相比,新版的 ultralytics 库更容易使用,特别是在分割任务上,现在它已经成为一个一流的功能。你可以在 ultralytics 新包中找到 Yolov5,因此如果你不想使用仍然有些新的和实验性的 Yolo 版本,你可以选择使用广为人知的 yolov5:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

yolov8 和 yolov5 的 Google 趋势比较

有些话题我没有涵盖,比如用于模型的不同损失函数、创建 yolov8 时进行的架构更改等等。如果你想了解更多这些话题,欢迎在这篇文章下评论。如果有兴趣,我可能会写另一篇关于这些内容的文章。

感谢你抽时间阅读这篇文章,希望它能帮助你理解 Yolov8 模型的训练过程。

除非另有说明,所有图像均由作者创建。

尝试这 3 个鲜为人知的 Pandas 函数

原文:towardsdatascience.com/try-these-3-lesser-known-pandas-functions-cfee4bc7e191

使用 pandas 提升你的数据处理技能

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Yong Cui

·发布于 Towards Data Science ·阅读时间 6 分钟·2023 年 8 月 28 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Balázs Kétyi 的照片,来源于 Unsplash

如果你问任何有经验的数据科学家或机器学习工程师,他们在工作中最耗时的是什么?我想他们中的许多人会说:数据预处理——一个清理数据并为顺序数据分析做准备的步骤。原因很简单——垃圾进,垃圾出。也就是说,如果你不正确地准备数据,你对数据的“见解”很难具有意义。

尽管数据预处理步骤可能相当繁琐,但 Pandas 提供了所有必要的功能,使我们能够相对轻松地完成数据清理工作。然而,由于其多功能性,并非每个用户都了解 pandas 库提供的所有功能。在本文中,我想分享 3 个鲜为人知但非常实用的函数,供你在数据科学项目中尝试。

不再赘述,让我们深入探讨吧。

注意:为了提供背景,假设你负责一家服装店的数据管理和分析。下面的示例基于这一假设。

1. explode

我想提到的第一个函数是 explode。当你处理包含列表的列中的数据时,这个函数非常有用。使用 explode 处理此列时,你会通过将列表中的每个元素提取到单独的行中,创建多行数据。

这里有一个简单的代码示例,展示了如何使用 explode 函数。假设你有一个存储订单信息的数据框。在这个表格中,你有一列(即 order 列),包含了项列表,如下所示:

order_data = {
    'customer': ['John', 'Zoe', 'Mike'],
    'order': [['Shoes', 'Pants', 'Caps'], ['Jackets', 'Shorts'], ['Ties', 'Hoodies']]
}
order_df = pd.DataFrame(order_data)
order_df

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

所需的操作是将列表中的每个项目拆分为单独的行以进行进一步的数据处理。如果不使用explode,一个简单的解决方案可能是这样。我们只是迭代原始行,并从列表中的每个项目创建多个行。

processed_rows = []
for _, row in order_df.iterrows():
    processed_rows.extend([row["customer"], item] for item in row["order"])

processed_df = pd.DataFrame(processed_rows, columns=["customer", "order"])
processed_df

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用explode,我们可以有一个更简洁的解决方案:

exploded_df = order_df.explode("order", ignore_index=True)

# confirm the equality of the output data frames
assert processed_df.equals(exploded_df)

正如你所看到的,我们指定了要“explode”的列,这样会得到一个新数据框,包含每行都是原始列表中的一个项目。使用assert语句,我展示了使用 explode 与之前的解决方案产生相同的结果。

附带说明一下,如果你希望新数据框的索引为 0、1、……、n,可以将ignore_index指定为True。与简单函数调用和 for 循环相比,使用explode的优势不仅仅是这些—explode还能处理某些行包含单个项目而非列表对象的情况,例如像Chloe, Jeans这样的行。简单的解决方案无法正确处理这种行,因为 Jeans 不是列表对象。

2. assign

我想讨论的第二个函数是assign。此函数可以在数据框中创建新列或修改现有列。该函数的主要好处是可以添加计算或衍生列,而无需修改原始数据。

假设我们有一个数据框,包含关于服装店产品及其价格的信息,如下所示:

product_data = {
    'product': ['Shoes', 'Jackets', 'Pants', 'Shorts'],
    'price': [100, 150, 80, 40]
}
product_df = pd.DataFrame(product_data)
product_df

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们需要通过应用折扣百分比来计算每个产品的折扣价格—对于店铺员工,折扣 20%,对于店铺 VIP 客户,折扣 10%。如果不使用assign,我们可能会有以下解决方案:

df1 = df.copy()
df1["employee_price"] = df1["price"] * 0.8
df1["vip_price"] = df1["price"] * 0.9
df1

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正如你所看到的,我们首先创建数据框的副本,以确保新列的创建不会更改原始数据框。在新的数据框中,我们创建了额外的列。

assign函数将这些独立的步骤结合在一起,产生一个更简洁的解决方案:

df2 = product_df.assign(employee_price=product_df["price"]*0.8, vip_price=product_df["price"]*0.9)

assert df1.equals(df2)

正如从上述代码中可以看出,在assign函数中,我们指定了两个附加列,并为每列提供了所需的计算。输出数据框与我们刚刚创建的一个匹配。快速说明一下,你通过关键字参数调用assign函数,这些关键字将成为新列名。

3. interpolate

我想提到的第三个函数是interpolate,使用它我们可以通过各种插值方法填补数据框中的缺失值。正如你们中的一些人可能知道的那样,插值是一种基于周围数据点估算值的技术。特别是在处理时间序列数据时,这是填补缺失值的常用技术。

假设你有一个存储销售数据的数据框。然而,由于在某些天,经理忘记将销售数据输入源中,导致一些缺失值,如下所示:

sales_data = {
    'date': pd.date_range(start='2023-08-01', periods=10, freq='D'),
    'revenue': [1000, np.nan, np.nan, 1060, np.nan, 1000, 1020, np.nan, 980, 1000]}
sales_df = pd.DataFrame(sales_data)
sales_df

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果我们不使用interpolate函数,我们可能需要识别缺失值的索引并定位其周围的值,利用这些值来插值缺失值。如你所想,这是一项非常复杂的过程。相反,你可以使用interpolate函数,它处理了所有这些繁琐的步骤,如下所示:

interpolated_df = sales_df.interpolate()
interpolated_df

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在插值后的数据框中,你看不到任何缺失值,所有缺失值都已被基于其周围值的插值值填补。如你所见,默认的插值方法是线性插值,这假设在缺失值范围内的变化是线性的,由其非缺失的邻近值所界定。还有其他插值方法,你可以在官方文档中查看它们。

结论

本文的内容大致如此。随着 pandas 库的迭代,越来越多的函数在最新版本中可用。如果你遇到一些复杂的操作,可以查阅其官方文档,可能会有现成的函数可供使用,比如可以用于处理时间序列分析中缺失数据的interpolate函数。

TSMixer: 谷歌推出的最新预测模型

原文:towardsdatascience.com/tsmixer-the-latest-forecasting-model-by-google-2fd1e29a8ccb

探索 TSMixer 的架构,并在 Python 中实现它,用于长期多变量预测任务。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Marco Peixeiro

·发表于Towards Data Science ·阅读时间 12 分钟·2023 年 11 月 14 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Zdeněk Macháček拍摄的照片,来自Unsplash

时间序列预测领域仍在蓬勃发展,最近有许多重要的贡献,如 N-HiTS、PatchTST、TimesNet 以及当然还有 TimeGPT。

与此同时,Transformer 架构在自然语言处理(NLP)领域实现了前所未有的性能,但在时间序列预测中并非如此。

事实上,许多基于 Transformer 的模型如 Autoformer、Informer、FEDformer 等被提出。这些模型通常训练时间非常长,而简单的线性模型在许多基准数据集上表现更好(见Zheng et al., 2022)。

事实上,在 2023 年 9 月,谷歌云 AI 研究人员提出了TSMixer,这是一个基于多层感知机(MLP)的模型,专注于混合时间和特征维度,以提供更好的预测。

在他们的论文TSMixer: An All-MLP Architecture for Time Series Forecasting中,作者展示了该模型在许多基准数据集上实现了最先进的性能,同时保持了实现的简单性。

在这篇文章中,我们首先探索 TSMixer 的架构,以理解其内部工作原理。然后,我们在 Python 中实现该模型,并运行自己的实验,将其性能与 N-HiTS 进行比较。

欲了解有关 TSMixer 的更多细节,请务必阅读原始论文

通过我的 免费时间序列备忘单 学习最新的时间序列分析技术!获取统计和深度学习技术的实现,全部用 Python 和 TensorFlow!

让我们开始吧!

探索 TSMixer

在预测方面,我们直观地知道,使用交叉变量信息可以帮助做出更好的预测。

例如,天气和降水量可能会影响游乐园的访客数量。同样,星期几和节假日也会产生影响。

因此,拥有能够利用协变量和其他特征信息进行预测的模型是有意义的。

这激发了 TSMixer 的创建。由于简单的单变量线性模型被证明优于更复杂的架构(见 Zheng et al., 2022),TSMixer 现在通过添加交叉变量前馈层来扩展线性模型的能力。

因此,我们现在得到一个能够处理多变量预测的线性模型,它可以利用协变量和其他静态特征的信息。

TSMixer 的架构

一般架构如下面的图所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

TSMixer 的架构。图像来源:S. Chen、C. Li、N. Yoder、S. Arik 和 T. Pfister,来自 TSMixer: An All-MLP Architecture for Time Series Forecasting

由于 TSMixer 只是扩展线性模型,它的架构相当简单,因为它完全基于 MLP。

从上图可以看出,该模型主要包括两个步骤:混合层和时间投影。

让我们更详细地探讨每一步。

混合层

这是时间混合和特征混合发生的地方,因此命名为 TSMixer。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

专注于混合层。图像来源:S. Chen、C. Li、N. Yoder、S. Arik 和 T. Pfister,来自 TSMixer: An All-MLP Architecture for Time Series Forecasting

在上图中,我们看到对于时间混合,MLP 包括一个全连接层,随后是 ReLU 激活函数和一个 dropout 层。

输入数据中,行代表时间,列代表特征,数据被转置,以便 MLP 应用于时间域,并在所有特征中共享。这个单元负责学习时间模式。

在离开时间混合单元之前,矩阵会再次转置,然后发送到特征混合单元。

特征混合单元包括两个 MLP。由于它在特征域中应用,因此在所有时间步长中共享。在这里,没有必要转置,因为特征已经在水平轴上。

注意,在两个混合器中,我们都有归一化层和残差连接。后者帮助模型学习数据的更深层次表示,同时保持计算成本合理,而归一化是提高深度学习模型训练的常用技术。

一旦混合完成,输出将发送到时间投影步骤。

时间投影

时间投影步骤是生成 TSMixer 预测的部分。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

重点关注时间投影层。图像来自S. ChenC. LiN. YoderS. ArikT. Pfister,见TSMixer: An All-MLP Architecture for Time Series Forecasting

在这里,矩阵再次被转置,并通过全连接层生成预测。最终步骤是再次转置该矩阵,使特征位于水平轴上,时间步位于垂直轴上。

现在我们了解了 TSMixer 的工作原理,让我们在 Python 中实现它并进行测试。

实现 TSMixer

据我了解,TSMixer 在 Python 中的常用时间序列库中尚未实现,如 Darts 或 Neuralforecast。因此,我将调整原始实现以适应我的实验。

原始实现可在Google Research 的代码库中找到。

本实验的完整代码可在GitHub上获取。

读取和格式化数据

应用深度学习模型进行时间序列预测中最困难的部分无疑是格式化数据集以输入神经网络。

所以,第一步是创建一个DataLoader类来处理数据集的所有转换。这个类通过批处理大小、输入序列长度、输出序列长度(视野)以及目标的切片对象进行初始化。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from sklearn.preprocessing import StandardScaler

class DataLoader:

  def __init__(self, batch_size, seq_len, pred_len):
    self.batch_size = batch_size
    self.seq_len = seq_len
    self.pred_len = pred_len
    self.target_slice = slice(0, None)

    self._read_data()

然后,我们添加一个方法来读取和缩放数据。在这里,我们使用电力变压器数据集 Etth1,公开在 GitHub 上获取,根据创作共用署名许可。

def _read_data(self):

    filepath = ('data/ETTh1_original.csv')

    df_raw = pd.read_csv(filepath)
    df = df_raw.set_index('date')

    # split train/valid/test
    n = len(df)
    train_end = int(n * 0.7)
    val_end = n - int(n * 0.2)
    test_end = n

    train_df = df[:train_end]
    val_df = df[train_end - self.seq_len : val_end]
    test_df = df[val_end - self.seq_len : test_end]

    # standardize by training set
    self.scaler = StandardScaler()
    self.scaler.fit(train_df.values)

    def scale_df(df, scaler):
      data = scaler.transform(df.values)
      return pd.DataFrame(data, index=df.index, columns=df.columns)

    self.train_df = scale_df(train_df, self.scaler)
    self.val_df = scale_df(val_df, self.scaler)
    self.test_df = scale_df(test_df, self.scaler)
    self.n_feature = self.train_df.shape[-1]

在上面的代码块中,请注意缩放数据以提高模型训练时间是至关重要的。还要注意,我们只在训练集上拟合缩放器,以避免在验证集和测试集中出现数据泄漏。

然后,我们创建两个方法,将数据窗口拆分为输入和标签,然后创建一个可以输入到 Keras 神经网络的数据集。

def _split_window(self, data):
    inputs = data[:, : self.seq_len, :]
    labels = data[:, self.seq_len :, self.target_slice]

    inputs.set_shape([None, self.seq_len, None])
    labels.set_shape([None, self.pred_len, None])
    return inputs, labels

  def _make_dataset(self, data, shuffle=True):
    data = np.array(data, dtype=np.float32)
    ds = tf.keras.utils.timeseries_dataset_from_array(
        data=data,
        targets=None,
        sequence_length=(self.seq_len + self.pred_len),
        sequence_stride=1,
        shuffle=shuffle,
        batch_size=self.batch_size,
    )
    ds = ds.map(self._split_window)
    return ds

最后,我们完成DataLoader类,添加方法以逆向转换预测结果,并生成训练、验证和测试集。

 def inverse_transform(self, data):
    return self.scaler.inverse_transform(data)

  def get_train(self, shuffle=True):
    return self._make_dataset(self.train_df, shuffle=shuffle)

  def get_val(self):
    return self._make_dataset(self.val_df, shuffle=False)

  def get_test(self):
    return self._make_dataset(self.test_df, shuffle=False)

完整的DataLoader类如下所示:

class DataLoader:

  def __init__(self, batch_size, seq_len, pred_len):
    self.batch_size = batch_size
    self.seq_len = seq_len
    self.pred_len = pred_len
    self.target_slice = slice(0, None)

    self._read_data()

  def _read_data(self):

    filepath = ('data/ETTh1_original.csv')

    df_raw = pd.read_csv(filepath)
    df = df_raw.set_index('date')

    # split train/valid/test
    n = len(df)
    train_end = int(n * 0.7)
    val_end = n - int(n * 0.2)
    test_end = n

    train_df = df[:train_end]
    val_df = df[train_end - self.seq_len : val_end]
    test_df = df[val_end - self.seq_len : test_end]

    # standardize by training set
    self.scaler = StandardScaler()
    self.scaler.fit(train_df.values)

    def scale_df(df, scaler):
      data = scaler.transform(df.values)
      return pd.DataFrame(data, index=df.index, columns=df.columns)

    self.train_df = scale_df(train_df, self.scaler)
    self.val_df = scale_df(val_df, self.scaler)
    self.test_df = scale_df(test_df, self.scaler)
    self.n_feature = self.train_df.shape[-1]

  def _split_window(self, data):
    inputs = data[:, : self.seq_len, :]
    labels = data[:, self.seq_len :, self.target_slice]

    inputs.set_shape([None, self.seq_len, None])
    labels.set_shape([None, self.pred_len, None])
    return inputs, labels

  def _make_dataset(self, data, shuffle=True):
    data = np.array(data, dtype=np.float32)
    ds = tf.keras.utils.timeseries_dataset_from_array(
        data=data,
        targets=None,
        sequence_length=(self.seq_len + self.pred_len),
        sequence_stride=1,
        shuffle=shuffle,
        batch_size=self.batch_size,
    )
    ds = ds.map(self._split_window)
    return ds

  def inverse_transform(self, data):
    return self.scaler.inverse_transform(data)

  def get_train(self, shuffle=True):
    return self._make_dataset(self.train_df, shuffle=shuffle)

  def get_val(self):
    return self._make_dataset(self.val_df, shuffle=False)

  def get_test(self):
    return self._make_dataset(self.test_df, shuffle=False)

然后,我们可以简单地初始化DataLoader类的一个实例,以读取我们的数据集并创建相关的数据集。

在这里,我们使用了 96 的预测范围、512 的输入序列长度和 32 的批量大小。

data_loader = DataLoader(batch_size=32, seq_len=512, pred_len=96)

train_data = data_loader.get_train()
val_data = data_loader.get_val()
test_data = data_loader.get_test()

现在数据已经准备好,我们可以构建 TSMixer 模型了。

构建 TSMixer

构建 TSMixer 非常简单,因为该模型仅由 MLP 组成。让我们回顾一下其架构,以便在构建模型时参考。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

TSMixer 的架构。图片来自 TSMixer: An All-MLP Architecture for Time Series Forecasting 的 S. Chen、C. Li、N. Yoder、S. Arik 和 T. Pfister。

首先,我们必须处理 Mixer Layer,它包括:

  • 批量归一化

  • 转置矩阵

  • 输入到具有 ReLu 激活的全连接层

  • 再次转置

  • dropout 层

  • 并在最后添加残差

这可以翻译成如下代码:

from tensorflow.keras import layers

def res_block(inputs, norm_type, activation, dropout, ff_dim):

  norm = layers.BatchNormalization

  # Time mixing
  x = norm(axis=[-2, -1])(inputs)
  x = tf.transpose(x, perm=[0, 2, 1])  # [Batch, Channel, Input Length]
  x = layers.Dense(x.shape[-1], activation='relu')(x)
  x = tf.transpose(x, perm=[0, 2, 1])  # [Batch, Input Length, Channel]
  x = layers.Dropout(dropout)(x)
  res = x + inputs

然后,我们添加特征混合部分,其中包括:

  • 批量归一化

  • 一个 dense 层

  • dropout 层

  • 另一个 dense 层

  • 另一个 dropout 层

  • 并添加残差以形成残差连接

 # Feature mixing
  x = norm(axis=[-2, -1])(res)
  x = layers.Dense(ff_dim, activation='relu')(x)  # [Batch, Input Length, FF_Dim]
  x = layers.Dropout(0.7)(x)
  x = layers.Dense(inputs.shape[-1])(x)  # [Batch, Input Length, Channel]
  x = layers.Dropout(0.7)(x)
  return x + res

就这样!Mixer Layer 的完整函数如下:

from tensorflow.keras import layers

def res_block(inputs, ff_dim):

  norm = layers.BatchNormalization

  # Time mixing
  x = norm(axis=[-2, -1])(inputs)
  x = tf.transpose(x, perm=[0, 2, 1])  # [Batch, Channel, Input Length]
  x = layers.Dense(x.shape[-1], activation='relu')(x)
  x = tf.transpose(x, perm=[0, 2, 1])  # [Batch, Input Length, Channel]
  x = layers.Dropout(0.7)(x)
  res = x + inputs

  # Feature mixing
  x = norm(axis=[-2, -1])(res)
  x = layers.Dense(ff_dim, activation='relu')(x)  # [Batch, Input Length, FF_Dim]
  x = layers.Dropout(0.7)(x)
  x = layers.Dense(inputs.shape[-1])(x)  # [Batch, Input Length, Channel]
  x = layers.Dropout(0.7)(x)
  return x + res

现在,我们简单地编写一个函数来构建模型。我们包括一个 for 循环以创建我们需要的 Mixer 层,并添加最终的时间投影步骤。

从上面的图中可以看到,时间投影步骤只是:

  • 一个转置

  • 经过 dense 层处理

  • 最后的转置

def build_model(
    input_shape,
    pred_len,
    n_block,
    ff_dim,
    target_slice,
):

  inputs = tf.keras.Input(shape=input_shape)
  x = inputs  # [Batch, Input Length, Channel]
  for _ in range(n_block):
    x = res_block(x, norm_type, activation, dropout, ff_dim)

  if target_slice:
    x = x[:, :, target_slice]

  # Temporal projection
  x = tf.transpose(x, perm=[0, 2, 1])  # [Batch, Channel, Input Length]
  x = layers.Dense(pred_len)(x)  # [Batch, Channel, Output Length]
  outputs = tf.transpose(x, perm=[0, 2, 1])  # [Batch, Output Length, Channel])

  return tf.keras.Model(inputs, outputs)

现在,我们可以运行函数来构建模型。在这种情况下,我们在 Mixer Layer 中使用了八个块。

model = build_model(
    input_shape=(512, data_loader.n_feature),
    pred_len=96,
    n_block=8,
    ff_dim=64,
    target_slice=data_loader.target_slice
)

训练 TSMixer

我们现在准备好训练模型了。

我们使用学习率为 1e-4 的 Adam 优化器。我们还实现了检查点以保存最佳模型,并通过早停机制在连续三次训练轮次没有改进时停止训练。

tf.keras.utils.set_random_seed(42)

optimizer = tf.keras.optimizers.Adam(1e-4)

model.compile(optimizer, loss='mse', metrics=['mae'])

checkpoint_callback = tf.keras.callbacks.ModelCheckpoint(
    filepath='tsmixer_checkpoints/',
    vebose=1,
    save_best_only=True,
    save_weights_only=True
)

early_stop_callback = tf.keras.callbacks.EarlyStopping(
    monitor='val_loss',
    patience=3
)

history = model.fit(
    train_data,
    epochs= 30,
    validation_data=val_data,
    callbacks=[checkpoint_callback, early_stop_callback]
)

注意,仅使用 CPU 训练模型花费了 15 分钟。

一旦模型训练完成,我们可以加载通过检查点回调保存的最佳模型。

best_epoch = np.argmin(history.history['val_loss'])

model.load_weights("tsmixer_checkpoints/")

然后,让我们访问最后一个 96 时间步的预测。注意,预测现在是经过缩放的。

predictions = model.predict(test_data)

scaled_preds = predictions[-1,:,:]

最后,我们将缩放后的预测和逆变换后的预测存储在 DataFrame 中,以便后续评估性能和绘制预测结果。

cols = ['HUFL', 'HULL', 'MUFL', 'MULL', 'LUFL', 'LULL', 'OT']

scaled_preds_df = pd.DataFrame(scaled_preds)
scaled_preds_df.columns = cols

preds = data_loader.inverse_transform(scaled_preds)

preds_df = pd.DataFrame(preds)
preds_df.columns = cols

使用 N-HiTS 进行预测

为了评估 TSMixer 的性能,我们使用与 N-HiTS 相同的训练协议,因为它们也支持多变量预测。

提醒一下,你可以在 GitHub 上访问此实验的完整代码。

对于这一部分,我们使用 NeuralForecast 库。因此,自然的第一步是读取数据并按需格式化。

from neuralforecast.core import NeuralForecast
from neuralforecast.models import NHITS

df = pd.read_csv('data/ETTh1_original.csv')

columns_to_melt = ['date', 'HUFL', 'HULL', 'MUFL', 'MULL', 'LUFL', 'LULL', 'OT']

melted_df = df.melt(id_vars=['date'], value_vars=columns_to_melt, var_name='unique_id', value_name='y')

melted_df.rename(columns={'date': 'ds'}, inplace=True)

melted_df['ds'] = pd.to_datetime(melted_df['ds'])

然后,我们可以初始化 N-HiTS 并在数据上进行拟合。

horizon = 96

models = [
    NHITS(h=horizon, input_size=512, max_steps=30)
]

nf = NeuralForecast(models=models, freq='H')

n_preds_df = nf.cross_validation(
  df=melted_df, 
  val_size=int(0.2*len(df)), 
  test_size=int(0.1*len(df)), 
  n_windows=None)

然后,我们仅提取过去 96 个时间步的预测。

df['date'][-96:] = pd.to_datetime(df['date'][-96:])

max_date = df['date'][-96:].max()
min_date = df['date'][-96:].min()

last_n_preds_df = n_preds_df[(n_preds_df['ds'] >= min_date) & (n_preds_df['ds'] <= max_date)]

cols = ['HUFL', 'HULL', 'MUFL', 'MULL', 'LUFL', 'LULL', 'OT']

clean_last_n_preds_df = pd.DataFrame()

for col in cols:
    temp_df = last_n_preds_df[last_n_preds_df['unique_id'] == col].drop_duplicates(subset='ds', keep='first')
    clean_last_n_preds_df = pd.concat([clean_last_n_preds_df, temp_df], ignore_index=True)

此时,我们已经得到了每一列在过去 96 个时间步的预测,如下所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

N-HiTS 对过去 96 个时间步的每个序列的预测。图片由作者提供。

现在,我们准备好可视化和衡量我们模型的表现了。

评估

首先,让我们可视化预测结果。

为了简便起见,我们只绘制了数据集中前四个序列的预测。

nhits_preds = pd.read_csv('data/nhits_preds_etth1_h96.csv')
tsmixer_preds = pd.read_csv('data/tsmixer_preds_etth1_h96.csv')

cols_to_plot = ['HUFL', 'HULL', 'MUFL', 'MULL']

fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(12,8))

for i, ax in enumerate(axes.flatten()):
    col = cols_to_plot[i]

    nhits_df = nhits_preds[nhits_preds['unique_id'] == col] 

    ax.plot(df['date'][-96:], df[col][-96:])
    ax.plot(df['date'][-96:], tsmixer_preds[col], label='TSMixer', ls='--', color='green')
    ax.plot(df['date'][-96:], nhits_df['NHITS'], label='N-HiTS', ls=':', color='black')

    ax.legend(loc='best')
    ax.set_xlabel('Time steps')
    ax.set_ylabel('Value')
    ax.set_title(col)

plt.tight_layout()
fig.autofmt_xdate()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

N-HiTS 和 TSMixer 对数据集中前四个序列的预测。我们看到 TSMixer 在 MULL 和 HULL 上难以泛化,而 N-HiTS 似乎很好地跟随了实际曲线。图片由作者提供。

从上图中,我们可以看到 TSMixer 在预测 HUFL 和 MUFL 上表现得相当不错,但在 MULL 和 HULL 上表现不佳。然而,N-HiTS 似乎在所有序列上的表现都很不错。

不过,评估表现的最佳方式还是通过测量误差指标。在这里,我们计算 MAE 和 MSE。

from sklearn.metrics import mean_absolute_error, mean_squared_error

y_actual = df.drop('date', axis=1)[-96:]

data = {'N-HiTS': 
            [mean_absolute_error(nhits_preds['y'], nhits_preds['NHITS']), 
             mean_squared_error(nhits_preds['y'], nhits_preds['NHITS'])],
       'TSMixer': 
            [mean_absolute_error(y_actual, tsmixer_preds), 
             mean_squared_error(y_actual, tsmixer_preds)]}

metrics_df = pd.DataFrame(data=data)
metrics_df.index = ['mae', 'mse']

metrics_df.style.highlight_min(color='lightgreen', axis=1)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

N-HiTS 和 TSMixer 在 96 个时间步长的多变量预测任务中的 MAE 和 MSE。图片由作者提供。

从上图中,我们可以看到 TSMixer 在 96 个时间步长的多变量预测任务中优于 N-HiTS,因为它实现了最低的 MAE 和 MSE。

虽然这不是最全面的实验,但看到这种性能来自相当简单的模型架构仍然很有趣。

结论

TSMixer 是一个专门为多变量时间序列预测设计的全 MLP 模型。

它通过添加交叉变量前馈层扩展了线性模型的能力,使模型在长时间跨度的多变量预测任务中达到最先进的表现。

虽然目前还没有现成的实现,但你现在拥有了自己实现它的知识和技能,因为它简单的架构使得我们可以轻松做到这一点。

一如既往,每个预测问题都需要独特的方法和特定的模型,因此请确保测试 TSMixer 以及其他模型。

感谢阅读!希望你喜欢,并且学到了一些新东西!

想要掌握时间序列预测吗?那就看看我的课程 Applied Time Series Forecasting in Python。这是唯一一个使用 Python 实现统计、深度学习和最先进模型的 16 个引导性实践项目的课程。

干杯 🍻

支持我

喜欢我的工作吗?通过 Buy me a coffee 支持我,这是一种简单的方式来鼓励我,同时我可以享受一杯咖啡!如果你愿意,只需点击下面的按钮 👇

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

参考文献

Si-An Chen, Chun-Liang Li, Nate Yoder, Sercan O. Arik, Tomas Pfister — TSMixer: An All-MLP Architecture for Time Series Forecasting

Google 研究人员的 TSMixer 原始实现— GitHub

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值