TowardsDataScience 2023 博客中文翻译(三百零八)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

时间序列模型中的文本数据预处理

原文:towardsdatascience.com/text-data-pre-processing-for-time-series-models-162c0d01f5c5

你是否曾考虑过如何将文本数据中的情感用作时间序列模型中的回归器?

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

·发表于 Towards Data Science ·6 分钟阅读·2023 年 2 月 9 日

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

照片由 Kaleidico 在 Unsplash 提供

介绍

文本数据提供了可以量化、聚合并用作时间序列模型中的变量的定性信息。从 NLP 的早期开始,简单的文本数据表示方法,如独热编码的类别变量和词 n-gram,已经被使用。随着时间的推移,包括词袋模型在内的更复杂的方法,开始用于表示文本数据以供机器学习算法使用。基于 Harris [1] 和 Firth [2] 提出的分布假设,现代模型如 Word-to-Vec [3] 和 [4]、GloVe [5] 和 ELMo [6] 在其神经网络架构中使用词的向量表示。由于计算机将文本处理为向量,因此它可以作为时间序列计量经济模型中的变量使用。

通过这种方式,我们可以利用文本中的定性信息,扩展定量时间序列模型的可能性。

在本文中,你将深入了解:

  • 如何将文本中的定性信息用于定量建模

  • 如何清洗和表示时间序列模型中的文本数据

  • 如何高效处理100 万行文本数据

  • Python 中的端到端编码示例。

在我们最近的会议论文中,我们制定了一个文本数据预处理的结构性计划,可能用于以下领域:(1)利用社交网络的情感预测汇率,(2)使用公开新闻数据预测农业价格,(3)在各个领域进行需求预测。

1. 文本数据表示的结构计划

让我们从计划开始。开始时,我们有定性原始文本数据,随着时间的推移进行收集。最后,我们得到的是具有时间变化的数值向量(=定量数据)。这个图表更清楚地说明了我们将如何进行:

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

图 1. 文本数据表示的结构计划。来源:Poměnková 等人,提交至MAREW 2023

2. Python 中的经验示例

让我们通过新闻类别数据集展示编码,该数据集由 Rishabh Misra 编制[8],[9],并根据署名 4.0 国际许可证发布。数据包含 2012 年至 2022 年间在 huffpost.com 上发布的新闻标题,已扩展到 100 万行的数据集。

主要目标是从新闻标题中构建按月频率的时间序列,反映公众情绪。

数据集包含 100 万个标题。由于其规模,我使用了Polars库,这使得数据框操作更加高效。与主流的 Pandas 相比,它处理大型数据文件的效率更高。此外,代码在 Google Colab 上运行,使用了 GPU 硬件加速器。

Python 代码在这里,数据如下:

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

图 2. 新闻类别数据集

2.1. 文本数据预处理

文本数据预处理的目的是去除所有可能偏倚分析或导致结果不准确解释的冗余信息。我们将移除标点符号数字多余的空格、英语停用词(最常见的、信息量低或为零的词),并将文本转为小写

可能最简单且最有效的文本数据清理方法是使用cleantext库。

首先,定义一个清理函数以执行清理操作:

def preprocess(text):
    output = clean(str(text), punct=True,
                              extra_spaces=True,
                              stopwords=True,
                              lowercase=True,
                              numbers = True)
    return output

接下来,我们使用 Polars 清理 1 百万数据集:

data_clean = data.with_columns([
    pl.col("headline").apply(preprocess)
])

清理后的数据集包含最大信息量的文本,便于进一步处理。任何不必要的字符串和数字都会降低最终经验建模的准确性。

2.2. 文本数据表示

数据表示涉及在计算机中表示数据的方法。由于计算机处理的是数字,我们选择了一个适当的模型来向量化文本数据集。

在我们的项目中,我们正在构建情感的时间序列。对于这种用例,预训练的情感分类器**VADER (情感词典和情感推理器) **是一个不错的选择。阅读我的上一篇文章以了解更多关于该分类器的信息以及其他一些替代方案。

使用vaderSentiment库进行分类的代码如下。首先,创建分类函数:

from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer

# calculate the compound score
def sentiment_vader(sentence):

    # create a SentimentIntensityAnalyzer object
    sid_obj = SentimentIntensityAnalyzer()

    sentiment_dict = sid_obj.polarity_scores(sentence)

    # create overall (compound) indicator
    compound = sentiment_dict['compound']

    return compound

接下来,应用时间序列数据集的函数:

# apply the function with Polars

sentiment = data_clean.with_columns([
    pl.col("headline").apply(sentiment_vader)
])

结果如下:

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

图 3. 情感评估

标题 列包含情感,范围为[-1:1],反映每行标题中的主要情感内容。

2.3. 时间序列表示

时间序列文本数据表示的下一步是扩展数据矩阵的时间维度。可以通过 (a) 沿时间轴聚合数据和 (b) 选择实现时间序列文本数据表示的方法来实现。在我们的数据中,我们将做前者,并按月频率聚合每行的情感。

这段代码对情感进行平均聚合,并准备每月的时间序列:

# aggregate over months

timeseries = (sentiment.lazy()
    .groupby("date_monthly")
    .agg(
        [
            pl.avg("headline")
        ]
    ).sort("date_monthly")
).collect()

2.4. 定量建模

最后的步骤是使用时间序列进行建模。以我们的最近会议论文为例,我们类似地从前五名经济学期刊发表的研究文章标题中提取了情感。然后,我们使用 5 年窗口的滚动时间变动相关性,并观察情感与 GDP 及其他全球经济指标的关系(见图 2)。

我们假设情感与宏观经济环境在经济急剧衰退和通货膨胀冲击期间相关。结果支持这种考虑,除了一个特定的期刊,关于 1970 年代的石油冲击,这导致了急剧的衰退伴随着巨大的通货膨胀峰值。

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

图 4. 情感与 GDP 的滚动相关性。来源:Poměnková et al.,提交至MAREW 2023

结论

在这篇文章中,我们从 100 万行文本数据中构建了每月的情感时间序列。关键点包括:

  • 定性信息可以扩展定量时间序列模型的能力。

  • Polars 库使得即使在 Python 语言中,也能实现大规模文本数据预处理的可行性。

  • 诸如 Google Colab 的云服务使得处理大规模文本数据集的速度更快。

本教程的完整代码在我的 GitHub 上。推荐阅读 Python 中最受欢迎的预训练情感分类器

你喜欢这篇文章吗?你可以邀请我 喝咖啡 来支持我的写作。你也可以订阅我的 邮件列表 以便接收我的新文章通知。谢谢!

参考文献

[1] Z. Harris. 1954. 分布结构。Word,第 10 卷,第 23 期,第 146–162 页。

[2] J. R. Firth. 1957. 语言学理论的概述 1930–1955. 收录于《语言学分析研究》,第 1–32 页. 牛津:语言学会。重印于 F.R. Palmer (编),《J.R. Firth 1952–1959 选集》,伦敦:Longman 1968。

[3] Mikolov, T., Chen, K., Corrado, G. S., Dean, J. 2013b. 向量空间中词表示的高效估计。计算与语言:国际学习表示会议。

[4] T. Mikolov, I. Sutskever, K. Chen, G. S. Corrado 和 J. Dean. 2013. 词汇和短语的分布式表示及其组合性。神经信息处理系统进展,第 26 卷 (NIPS 2013)。

[5] M. E. Peters, M. Neumann, M. Iyyer, M. Gardner, C. Clark, K. Lee 和 L. Zettlemoyer, L. 2018. 2018 年北美计算语言学协会会议:人类语言技术会议论文集,第 1 卷。

[6] J. Pennington, R. Socher 和 C. D. Manning. 2014. GloVe:全局词向量表示。发表于 2014 年自然语言处理实证方法会议(EMNLP)论文集。

[7] Poměnková, J., Koráb, P., Štrba, D. 时间序列建模的文本数据预处理。提交至 MAREW 2023

[8] Misra, Rishabh. “新闻类别数据集。” arXiv 预印本 arXiv:2209.11429 (2022)。

[9] Misra, Rishabh 和 Jigyasa Grover. “为 ML 雕刻数据:机器学习的第一幕。” ISBN 9798585463570 (2021)。

自动化化学实体识别:创建你的 ChemNER 模型

原文:towardsdatascience.com/text-mining-for-chemists-a-diy-guide-to-chemical-compound-labeling-ea3145e24dc4

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

·发表于Towards Data Science ·15 分钟阅读·2023 年 11 月 16 日

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

照片由Aakash Dhage拍摄,来源于Unsplash

我一直对化学有浓厚的兴趣,它在塑造我的学术和职业旅程中发挥了重要作用。作为一个具有化学背景的数据专业人员,我发现有许多方法可以将我的科学和研究技能如创造力、好奇心、耐心、敏锐观察和分析应用于数据项目。在这篇文章中,我将引导你开发一个我称之为 ChemNER 的简单命名实体识别(NER)模型。这个模型可以识别文本中的化学化合物,并将它们分类为烷烃、烯烃、炔烃、醇、醛、酮或羧酸等类别。

TL;DR

如果你只是想玩玩 ChemNER 模型和/或使用我制作的 Streamlit 应用,你可以通过以下链接访问它们:

HuggingFace 链接: huggingface.co/victormurcia/en_chemner

Streamlit 应用: ChemNER 链接

介绍

NER 方法通常可以分为以下三类:

  • 基于词典:定义类别和术语的字典

  • 基于规则:定义每个类别对应的术语规则

  • 基于机器学习(ML):让模型从训练语料库中学习命名规则

这些方法各有其优缺点,并且一如既往,复杂而精细的模型并不总是最佳方案。

在这种情况下,基于词汇表的方法在范围上会有限,因为对于我们感兴趣的每一类化合物,我们都需要手动定义该类别中的所有化合物。换句话说,为了使这种方法全面,你需要手动输入每个化合物类别的所有化合物。

机器学习方法可能是最强大的选择,然而,注释数据集可能非常繁琐(剧透:我会训练一个模型,但我想展示整个过程以供学习)。那么,我们不妨从一些预定义的命名规则开始?

化学命名法有一套完善且明确的规则,使你能够轻松确定分子中存在的功能团。这些规则由国际纯粹与应用化学联合会(IUPAC)制定,可以通过IUPAC 蓝皮书、各种网站或任何有机化学教科书轻松获取。例如,烃是仅由碳和氢原子组成的化合物。烃主要有三类,分别是烷烃、烯烃和炔烃,可以根据其化学结构中是否包含单键、双键或三键来识别。下面我展示了三个化学化合物(乙烷、乙烯和乙炔)的例子。

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

乙烷、乙烯和乙炔。图片作者提供。

对我们来说,重要的是名字的结尾(即后缀),因为这将使我们能够区分化学化合物。例如,烷烃由后缀* -ane* 标识,烯烃由后缀* -ene* 标识,炔烃由后缀* -yne* 标识。每类化学化合物如醇、酮、醛、羧酸等都有独特的命名方案,这些方案将作为该项目的基础。

建立规则

现在我们有了一些背景知识来理解发生了什么,我将展示如何使用 Spacy 在 Python 中实现基于规则的方法。我将从处理烃开始,稍后会添加其他类别。为此,我们首先将使用 Spacy 加载一个空白的英语模型,并将‘实体规则器’组件添加到我们的管道中:

# Load a blank English model
nlp = spacy.blank("en")

#Create the EntityRuler
ruler = nlp.add_pipe("entity_ruler")

接下来,我们将建立定义每一类的规则/模式,并将其添加到规则组件中:

# Define patterns
patterns = [
    {"label": "ALKANE", "pattern": [{"TEXT": {"REGEX": ".*ane$"}}]},
    {"label": "ALKENE", "pattern": [{"TEXT": {"REGEX": ".*ene$"}}]},
    {"label": "ALKYNE", "pattern": [{"TEXT": {"REGEX": ".*yne$"}}]}
]

ruler.add_patterns(patterns)

就这样!现在让我们创建一些文本以供模型使用,看看效果如何!

text = "Ethane,  propene, and butyne are all examples of hydrocarbons."

doc = nlp(text)

#extract entities
for ent in doc.ents:
    print (ent.text, ent.start_char, ent.end_char, ent.label_)

结果如下:

Ethane 0 6 ALKANE
propene 9 16 ALKENE
butyne 22 28 ALKYNE

非常好!然而,你可能已经注意到这个初始方法有两个直接的局限性:

  1. 当前的正则表达式无法检测化合物的复数形式。

  2. 仅基于后缀的分类会导致很多错误标记的实体。

尽管化学化合物通常被视为不可数名词(想想像 air 或 music 这样的词),但在某些情况下,复数形式仍然可以使用。例如,如果你处理的是一组乙烷分子,有人可能会将其称为一组乙烷。因此,第一个问题可以通过将我们的正则表达式修改为以下形式来轻松解决:

# Define patterns
patterns = [
    {"label": "ALKANE", "pattern": [{"TEXT": {"REGEX": ".*anes?$"}}]},
    {"label": "ALKENE", "pattern": [{"TEXT": {"REGEX": ".*enes?$"}}]},
    {"label": "ALKYNE", "pattern": [{"TEXT": {"REGEX": ".*ynes?$"}}]},
]

现在,实体规则器将识别单数和复数形式。然而,第二点仍然存在。例如,如果文本中出现像 arcane、humane、thane、lane 和 mundane 这样的词,它们会被错误标记为烷烃。

尽管还有其他规则可以实施以增强这种方法,但它们会需要相当多的额外工作。因此,我考虑了三种方法来处理我们的限制:

  1. 构建一个语料库,以训练用于此应用程序的基于机器学习的命名实体识别模型

  2. 使用命名实体链接(NEL)来帮助纠正模型输出中出现的标注错误

  3. 对像 SciBERT 或 PubMedBERT 这样的变换器模型在自定义数据集上进行微调

对于这篇文章,我将仅涵盖前两种方法。然而,如果有兴趣,我将在未来的文章中展示如何完成微调过程。

创建数据集

创建语料库有多种不同的方法。生成这个语料库的快速而简单的方法是让 chatGPT 创建包含我想从文本中提取的各种类别的化合物的句子集。之所以这样做效果很好,是因为这种方法允许我策划和调整我的数据集,这使得后续的标注过程变得更容易。我的提示是:

Give me a set of 50 unique sentences each dealing with unique alkanes

然后,我对我感兴趣的其他类别(即,烯烃、炔烃、醇、酮、醛和羧酸)重复了那个提示。由于我有 7 个类别,我最终得到了 350 个句子组成的语料库。理想情况下,这个语料库会更大,但这是一个很好的开始,因为我主要想说明这是一个概念验证。再者,通常可以根据需要添加更多数据来提高性能。我将我的句子保存到一个名为 chem_text.txt 的文档中。

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

为 ChemNER 制作的语料库的截图。图片由作者提供

作为最后一步,我会使用句子分词器将文档中的每个句子分开。

doc = nlp(chem_text)

corpus = []

for sent in doc.sents:
    corpus.append(sent.text.strip())

现在我已经制作了这个语料库,我们需要开始对其进行标注。有几种方法可以做到这一点。例如,我们可以使用像Prodigy这样的注释工具(它非常棒,如果你做任何类型的自然语言处理,应该使用它),或者我们可以使用之前的基于规则的方法来帮助我们进行初步标注。现在,由于我不是在标注一个庞大的数据集,我会使用模型方法。

DATA = []

#iterate over the corpus again
for sentence in corpus:
    doc = nlp(sentence)

    #remember, entities needs to be a dictionary in index 1 of the list, so it needs to be an empty list
    entities = []

    #extract entities
    for ent in doc.ents:

        #appending to entities in the correct format
        entities.append([ent.start_char, ent.end_char, ent.label_])

    DATA.append([sentence, {"entities": entities}])

为了包含我感兴趣的所有类别,规则需要更新为以下内容:

# Define patterns
patterns = [
    {"label": "ALKANE", "pattern": [{"TEXT": {"REGEX": ".*anes?$"}}]},
    {"label": "ALKENE", "pattern": [{"TEXT": {"REGEX": ".*enes?$"}}]},
    {"label": "ALKYNE", "pattern": [{"TEXT": {"REGEX": ".*ynes?$"}}]},
    {"label": "ALCOHOL", "pattern": [{"TEXT": {"REGEX": ".*ols?$"}}]},
    {"label": "ALDEHYDE", "pattern": [{"TEXT": {"REGEX": ".*(al|als|aldehyde|aldehydes)$"}}]},
    {"label": "KETONE", "pattern": [{"TEXT": {"REGEX": ".*ones?$"}}]},
    {"label": "C_ACID", "pattern": [{"TEXT": {"REGEX": r"\b\w+ic\b"}}, {"TEXT": {"IN": ["acid", "acids"]}}]}
]

运行基于规则的方法的结果使我们可以快速标注我们的数据集,如下所示。

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

ChemNER 的标注语料库。图片由作者提供。

我们已经接近将语料库分为训练集和测试集,但在继续之前,我们需要验证标注的质量。检查数据集时,我注意到出现了之前提到的错误标注问题。数据集中出现了“essential”、“crystals”、“potential”、“materials”等词语,这些词被标注为醛类,这突显了基于规则的方法的局限性。我使用下面的方法手动移除了这些标签,并重新处理了语料库上的标注:

# List of words to be ignored
ignore_set = {"essential", "crystals", "potential","materials","bioorthogonal","terminal","chemicals",
              "spiral","natural","positional","structural","special","yne","chemical","positional",
              "terminal","hormone","functional","animal","agricultural","typical","floral","pharmaceuticals",
              "medical","central","recreational"}  # Convert ignore list to set

DATA = []

# Iterate over the corpus
for sentence in corpus:
    doc = nlp(sentence)

    entities = []

    # Extract entities
    for ent in doc.ents:
        # Check if entity is not in the ignore set
        if ent.text.lower() not in ignore_set:
            # Appending to entities in the correct format
            entities.append([ent.start_char, ent.end_char, ent.label_])

    DATA.append([sentence, {"entities": entities}])

现在我们已经准备好创建训练集和测试集。这可以通过 scikit-learn 中的 train_test_split 函数轻松完成。我使用了标准的 80:20 训练:测试划分。

# Split the data
train_data, valid_data = train_test_split(DATA, test_size=0.2, random_state=42)

训练模型

我们的训练数据已经准备好,我们可以开始训练模型。为了训练模型,我使用了默认的 Spacy NER 训练参数,如 Adam 优化器和 0.001 的学习率。训练在 Google Colab 的 CPU 上花费了一个多小时,如果使用 GPU,则时间会大大缩短。训练结果如下所示:

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

ChemNER 模型的训练结果。图片由作者提供。

上述图表显示了该模型在训练过程中 F1 得分、准确率、召回率和整体得分的趋势都在上升,这很好。与 NER 组件相关的 NER 损失总体上趋向于最小值。该模型的最终性能得分为 0.97,看起来很有前景。

然而,Tok2Vec 损失在大约第 300 个 Epoch 时明显上升,这可能是由于学习率过高、梯度消失/爆炸导致数值不稳定或过拟合等问题。Tok2Vec 损失表示模型中负责将令牌转换为向量的 token-to-vector 部分的有效性。如果我们选择,可以有多种方式来处理这个问题,但现在,我会继续进行。

测试模型

让我们从简单的测试开始。我将输入几句话,看看它的分类效果如何。结果如下所示:

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

ChemNER 模型的初步测试。图片由作者提供。

很棒!它提取了所有相关实体,并且全部标注正确!这就是机器学习方法的酷炫之处。与其我们显式编写规则,不如算法在训练过程中自我学习。虽然这很酷,但现在我们来对模型施加更多压力。

查询维基百科(压力测试)

我想对我的模型进行更多的压力测试,因此我认为快速且简单的方法是将整个维基百科文章输入模型,看看它的表现。我将编写一个简单的程序,通过 Python 的 wikipedia-api 包来实现:

import wikipediaapi

# Define your user agent
user_agent = "MyApp/1.0 (your@email)"

# Initialize Wikipedia API and spaCy
wiki_wiki = wikipediaapi.Wikipedia(user_agent,'en')

# Function to get Wikipedia article
def get_wikipedia_article(page_title):
    page = wiki_wiki.page(page_title)
    return page.text if page.exists() else None

# Function to perform NER on text
def perform_ner(text):
    doc = nlp(text)
    return [(ent.text, ent.label_) for ent in doc.ents]

接下来,我将查找有关苯的维基百科文章:

# Query Wikipedia for an article
article_title = "Benzene"  # Replace with your desired article title
article_content = get_wikipedia_article(article_title)

这样产生的结果是:

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

苯维基百科文章查询的截图。

很棒!现在我们已经验证了查询工作正常,让我们运行 ChemNER 模型。ChemNER 模型从苯的文章中提取了总共 444 个实体。这些实体的提取时间不到一秒。我将结果放入数据框中,并在下面的计数图中可视化标签计数:

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

ChemNER 对苯维基百科文章的结果。图片由作者提供。

该文章中最常见的类别是烯烃,这很有意义,因为苯对应的正是这一化合物类别。我觉得有点意外的是,这篇文章中包含了每个类别的实体。

这很有趣,不过,通过快速检查数据框中提取的实体的前几行,我们可以看到模型存在一些问题。‘chemical’ 和 ‘hexagonal’ 被标注为醛,而‘one’ 被标注为酮。这些显然不是化学化合物,不应被分类为此。我手动识别了每个实体是否正确,并确定提取的准确率为 70.3%。尽管所有提取的实体根据模型学到的规则都被标注为‘正确’,但模型尚未真正理解词汇的上下文。

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

ChemNER 对苯的文章中正确和错误标注的实体比较。图片由作者提供。

不过我注意到一个有趣的地方,即正确标注的实体全都是化学化合物。换句话说,如果我们能确定一个实体是否是化学化合物,那么我们可以显著提高该应用程序的标注性能。

目前,我们可以采取几种途径。其中一种途径是回到语料库中生成更多的数据,以便为我们的模型提供学习示例。另一种途径是使用命名实体链接(NEL)来帮助纠正标注。由于后者耗时较少,我决定选择这个选项。

使用 PubChem 进行 NEL

ChemNER 模型在对化学物质进行标记时表现非常出色,只要实体是化学化合物。为了更好地告知模型,我将通过他们的 API 连接到PubChem并进行化学化合物的查询。这里的想法是,对化学化合物的查询会返回信息,而对非化学化合物的查询会返回空结果。我可以利用这些查询结果来提升应用的标记性能。

作为展示示例,我们先查询苯。以下代码可以用来查询 PubChem API。

def get_compound_info(compound_name):
    base_url = "https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name"
    response = requests.get(f"{base_url}/{compound_name}/JSON")
    if response.status_code == 200:
        return response.json()
    else:
        return None
compound_name = "benzene"
compound_info = get_compound_info(compound_name)

该查询的结果如下所示。

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

通过 PubMed API 查询苯的结果。图像来源:作者。

从这个查询中我们获得了大量关于苯的信息,后续可以使用。但现在,唯一重要的是查询返回了结果。另一方面,如果我使用相同的方法查询非化学化合物,如‘humans’或‘giraffe’,那么查询结果将是‘None’。

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

在 PubMed API 上查询非化学化合物。图像来源:作者。

我可以利用这一点来辅助我的应用程序。查询速度相当快,不过,为了加快处理速度,我将从数据框中删除任何重复的实体,以便只查询唯一的术语。此外,PubChem API 似乎假设我们是在查询单一化学化合物,因此像 cinammaldehydes 这样的词会返回空查询。可以通过去掉任何复数形式的终结‘s’来轻松解决此问题。我使用了以下代码在数据框中创建了一个名为‘Chemical Compound’的新列,这样我就可以根据查询结果将每个实体分类为化学化合物或非化学化合物。

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

这效果相当不错!不过,我在这样做时注意到的一件事是,类标签本身会导致空查询。换句话说,如果我查询 PubChem 的烷烃、烯烃、炔烃等,我会得到一个空查询,因为这些本身不是具体的化合物,而是化合物的类别。在这方面有一些细微的差别。我决定要让这些化合物类别被识别为化学实体,因为类标签可以独立地用于没有具体化合物的句子中(例如,烷烃常见于石化应用)。为了解决这个问题,我简单地添加了一个例程来检查实体列中的条目是否是我们类标签的单数或复数变体,如果实体与标签匹配,则将 Chemical Compound 列中的值设置为 1,否则为 0。

# List of specific chemical compound types
chemical_compounds = ['alkane', 'alkene', 'alkyne', 'ketone', 'aldehyde', 'alcohol', 'carboxylic acid']

# Function to update 'Chemical Compound' column
def update_chemical_compound(row):
    entity = row['Entity'].lower()
    if any(compound in entity for compound in chemical_compounds + [c + 's' for c in chemical_compounds]):
        return 1
    return row['Correct']

# Apply the function to each row
df_unique['Chemical Compound'] = df_unique.apply(update_chemical_compound, axis=1)

很棒!现在我可以将这些结果合并到包含所有 444 个结果的原始数据框中。

df_merged = pd.merge(df_ents2, df_unique[['Entity', 'Chemical Compound']], on='Entity', how='left')

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

使用 PubChem API 检查实体是否为化学化合物后的实体数据框。图片来源:作者。

接下来,我将删除任何与化学化合物不对应的行。

# Dropping rows where 'Chemical Compound' is 0
df_filtered = df_merged[df_merged['Chemical Compound'] != 0]

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

去除非化学化合物实体后的数据框。图片来源:作者。

现在让我们看看它的表现如何!

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

ChemNER 在通过 PubChem 执行 NEL 后的结果。图片来源:作者。

非常好!所有提取的实体现在都被正确标注。通过将我们的 NER 模型与 PubChem 的 NEL 结合使用,我们现在不仅能够从文本中提取实体,还能够消歧义结果,从而大大提高我们的标注准确性。

将模型部署到 HuggingFace

作为一个小奖励,我认为将我展示的所有这些例程部署到 HuggingFace 上会很酷,这样我就可以在 Streamlit 应用中展示它。你可以在 HuggingFace 找到这个模型:huggingface.co/victormurcia/en_chemner。下面展示了推断 API 的结果,看起来相当不错:

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

ChemNER 在 HuggingFace 推断 API 中的实际应用。图片来源:作者。

如果你使用了它或者有任何建议,请告诉我!我计划未来扩展模型,还有其他功能我想要探索。

使用 Streamlit 应用连接一切

现在模型已经部署,我可以在 Streamlit 应用中使用它。这个应用允许用户链接到维基百科文章或输入原始文本,然后由 ChemNER 模型处理。此过程的输出将是一个可下载的数据框,包含提取和标注的实体、显示每个标签在提供的文本中计数的计数图,以及文本的完全注释版本。你可以在这里找到 Streamlit 应用:chemner-5i7mrvyelw79tzasxwy96x.streamlit.app/

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

ChemNER Streamlit 应用的截图。图片来源:作者。

举个例子,我将使用应用对以下关于苯的维基百科文章进行查询。结果是文章的注释版本,如下所示,其中每个类别都有独特的颜色编码。

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

ChemNER 注释的文本。图片来源:作者。

输出也是一个数据框,你可以下载为 .csv 文件,包含实体及其相应的标签,以及一个显示计数的计数图

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

来自 Streamlit 应用的输出。图片来源:作者。

结论

希望你觉得这篇文章有信息量,并且对你构建自己的 NLP 应用有所帮助。我计划继续在这个模型和应用上做一些工作,因为我认为还有一些有趣的内容我想进一步探索。例如,经过一些测试,我注意到模型提取出的某些实体被 PubChem 方法归类为化学化合物,但实际上它们并不是有机化合物。例如,‘pm’这个词被提取为一个实体,并被标记为醛。PubChem 搜索返回了一个非空的查询,因为‘pm’(更准确地说是 Pm)是元素铽的化学符号。这个模型并不完美,但我希望它能展示出你可以在不需要 LLM 的情况下获得一个相当强大的工具。

一如既往,感谢你的阅读!

文本模式提取:比较 GPT-3 和 人工在环工具

原文:towardsdatascience.com/text-pattern-extraction-comparing-gpt-3-human-in-the-loop-tool-f2380fd13cf1

比较 LLM 和人工在环工具在文本模式提取中的初步实验和结果

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

·发布于 数据科学的前沿 ·10 分钟阅读·2023 年 1 月 26 日

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

图片来源于 Aaron BurdenUnsplash

在过去几年中,AI 在多个工业应用中引起了广泛关注。医生、分析师和记者等最终用户希望为其特定的使用案例构建 AI 模型。

然而,构建 AI 模型的工作流程需要技术专长,而最终用户可能不一定具备:

  • 准备数据,例如提取、清理和转换训练数据。

  • 训练 AI 模型,包括微调参数和重新训练模型的层。

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

动机:使最终用户能够构建 AI 模型。图片来源于作者。

最终用户可能会直接使用现有工具开箱即用。有几种工具适用于最终用户以开箱即用的方式构建 AI 模型。

最近,一类称为人工在环 (HITL) 工具的工具旨在降低最终用户构建 AI 模型的门槛。“人工在环”在模型构建过程中融入了人类知识。它本质上是一个人机协作的框架。在这个框架中,模型构建和用户输入之间有一个持续的反馈过程。

另一方面,AI 领域有一些广泛研究的模型,即大型生成语言模型,如 GPT-3、OPT、BLOOM 等。语言模型在大规模数据集上进行训练,具有惊人的语言理解能力。这些模型的规模达到数亿参数,并且在多个自然语言处理任务(例如提取、分类等)中表现出了少量样本的优异性能(即它们只需少量输入)。

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

现有工作。图像由作者提供。

我们想了解这些广泛研究的大型语言模型在帮助终端用户构建 AI 模型的背景下,与人机协作工具的表现如何。在我们的实验中,我们模拟了一个终端用户或业务用户(即没有太多技术培训的人)如何使用这些工具来执行文本模式提取任务。 在这篇简短的博客文章中,我想谈谈我们在 IBM 的几项实验,我们比较了人机协作系统与大型语言模型在模式提取任务中的表现。我们在 DaSH@EMNLP 2022 上展示了这项工作,你可以在 这里找到论文

具体来说,我们研究了以下工具:

  • 模式归纳 是 IBM Watson Discovery 上用于文本模式提取的 HITL 工具。

  • GPT-3 是一个流行的大型生成语言模型。GPT-3 是最大的语言模型之一,拥有 1750 亿个参数。

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

我们比较工作的目标。图像由作者提供。

文本模式提取

什么是文本模式提取?下面,我们有一个场景,其中包含一系列财务新闻稿,我们需要提取财政时间段。我们希望的提取结果包括“2014 年第一季度”和“2013 年第四季度”。注意,财政时间段的年份可以出现在提取结果的开始或结束,而季度则可以是“第一”、“第二”、“第三”或“第四”。

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

文本模式提取。图像由作者提供。

文本模式提取与模式归纳

模式归纳在 IBM Watson Discovery 上可用。它是一个用于文本模式提取的 HITL 工具。终端用户通过提供反馈来提取文本模式。该工具无需任何编码,用户也不需要提供大规模的训练数据集。

模式归纳支持两种用户操作:

  1. 终端用户高亮他们希望提取的文本示例。

  2. 终端用户还会向系统的提取结果提供反馈。用户可以接受或拒绝这些提取结果。

在我们的实验中,我们通过模拟两种用户操作来进行用户模拟:(1) 高亮示例文本,和 (2) 提供反馈。

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

通过模拟用户操作来评估 HITL 工具。图像作者提供。

在后台,模式诱导学习 提取规则。可以将它们视为类似于正则表达式的东西:描述一系列标记或单词模式的表达式。

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

模式诱导流程及其基础规则模型。图像作者提供。

GPT-3 的文本模式提取

虽然最终用户会在模式诱导中提供文本高亮和反馈,但在 GPT-3 中完成文本提取任务需要构造输入提示,而 GPT-3 输出一个 完成文本,我们期望其中包含提取的文本。

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

基本的 GPT-3 文本模式提取提示。图像作者提供。

假设我们需要从报告数据集中提取 ISO 编号,如“ISO 18788”或“ISO 223000”。在上面的图像中,我们有一个按照以下方式制作的输入提示:

  • 输入提示的第一部分是我们要提取的来自数据集的句子。我们将此句子放在方括号中。

  • 提示的第二部分包含以列表形式列出的示例提取,其中每个示例提取都放在竖线字符“|”之间。

输入提示的格式用于向 GPT-3 显示和演示我们希望从句子中提取哪些类型的文本。在上面的例子中,GPT-3 从句子中完成了文本,输出了“ISO 9001”。上述输入提示是一种相当天真的方法,它模仿了最终用户可能构造提示的方式。构造提示需要一定程度的工程技术,整个研究子领域都致力于提示工程。

提示

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

尝试了几种不同的输入格式。图像作者提供。

输入提示的格式有很多种。我们尝试了几种格式:

  • 基本提示:模仿可能没有太多技术专长的最终用户如何构造提示。

  • 结构化提示:不是列出提取示例,而是将每个提取示例与一个句子配对。

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

结构化提示。图像作者提供。

  • 带有附加和负面示例的结构化提示: 在最后一种提示中,我们添加了附加示例和负面示例。在模式诱导中,用户可以接受和拒绝提取。在 GPT-3 中,我们将这些类型的提取作为附加示例添加。

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

带有负面示例的结构化提示。图像作者提供。

要了解有关输入提示格式的更多细节,请参阅 论文

后处理 GPT-3 的输出

使用 GPT-3 进行文本模式提取的主要挑战之一是它有时会对输出进行创意性的处理。这是 GPT-3 常见的现象,考虑到其文本生成能力。GPT-3 提取的文本并不总是文档数据集的一部分。

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

GPT-3 输出的文本有创意。图片由作者提供。

在一个用例中,我们希望从犯罪报告中提取犯罪事件的百分比,如“财产犯罪”或“人身犯罪”的百分比。但 GPT-3 生成了有关其他主题的百分比,如种族或性别,例如“0.6%为美洲印第安人或阿拉斯加土著”。这些文本甚至没有出现在数据集中,这对精确度评分产生了负面影响。因此,我们对 GPT-3 的所有输出进行了后处理。后处理步骤包括清理输出文本(去除分隔符)和删除那些不属于文档数据集的输出。

实验设置

这是我们用于将 GPT-3 与模式归纳进行比较的实验设置的详细信息:

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

用户模拟实验设置。图片由作者提供。

用户模拟在模式归纳和 GPT-3 上运行。每个工具都进行了 7 个用例任务:

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

我们给 GPT-3 和模式归纳的用例任务。图片由作者提供。

用户模拟记录了每次运行的精确度和召回率。

GPT-3 实验

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

图片由作者提供。

在对 GPT-3 的评估过程中,我们使用了用户模拟将作为高亮文本的相同种子示例,种子示例取自模式归纳用户模拟的日志,然后这些种子示例被用作 GPT-3 的输入提示。这是为了使其与模式归纳的运行结果可比。由于 GPT-3 的输入提示有令牌数量限制(约 4K 个令牌),我们还将文档拆分成部分。

结果:精确度评分

我们计算了平均精确度,这些精确度是对每个 7 个用例中的 100 次用户模拟运行的综合结果。结果显示 GPT-3 的精确度评分低于模式归纳的精确度评分(绿色线条带三角标记):

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

GPT-3 的精确度评分低于模式归纳。图片由作者提供。

图表中有几条 GPT-3 的线,每条线属于某种不同的输入提示格式变体。虽然不同的输入提示格式提高了精确度评分,但 GPT-3 的精确度评分平均值并没有超过 HITL 的精确度评分。总体而言,在精确度方面,模式归纳的表现比最佳 GPT-3 模型(结构化提示和额外示例)平均高出 38.8%

结果:召回率评分

我们还计算了平均召回率,即对每个 7 个用例任务的 100 次用户模拟运行的汇总。

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

GPT-3 的召回分数相似且更高。图片来自作者。

总体而言,在召回率方面,模式诱导的表现比最好的 GPT-3 模型平均好 4.0%。 然而,当对每个用例任务分析召回分数时,我们注意到 GPT-3 的运行在召回分数上要么相似,要么甚至高于模式诱导的运行:

  • 任务 U1、U3、U4:在这些任务中,预期的提取不遵循某些语法模式。例如,任务 U3 需要提取不同类型的犯罪,例如“财产犯罪”、“人身犯罪”。或者,任务 U4 需要提取整数和分数类型的量杯。这些任务提取的是概念,GPT-3 在这些任务中表现得相当好。GPT-3 理解某些词指代概念和实体,例如国家、犯罪类型或整数和分数量。GPT-3 对文本中的概念理解得非常好。

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

GPT-3 对文本中的概念理解得非常好。图片来自作者。

  • 任务 U2、U5、U6、U7:另一方面,我们看到,具有更严格语法模式的任务在模式诱导中处理得更好。例如,在任务 U2 中,我们希望提取事件的计数。字面上的“事件”通常出现在一个 6 位整数的末尾。这个模式适用于所有预期的提取。这些任务更具语法性,模式诱导在处理具有更严格语法模式的提取任务时表现更好。 这很合理,因为底层的模式诱导模型是基于规则的。

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

模式诱导更好地学习严格模式。图片来自作者。

改进提示格式提高了召回分数

我们在 GPT-3 运行中观察到的另一个关键点是,改进输入提示格式会提高召回分数。

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

结构化提示提高了召回率。图片来自作者。

从上面的图表可以看出,从基础提示分数(橙色线)到结构化提示分数(黄色线),召回分数平均提高了 59.8%。这张图的关键结论是,提示工程和格式化对于提高召回分数非常重要。

比较分析总结

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

图片来自作者。

HITL 方法用于文本模式提取的结果具有更高的精度,适合那些没有太多技术背景的最终用户。此外,有两个方面在 GPT-3 中找不到,但在 HITL 中存在:

  1. HITL 方法能够引出有针对性的用户反馈,并

  2. 这允许采用迭代方法来构建模型。

然而,HITL 方法的召回率较低,并且更适合处理句法文本模式。

在大型生成模型中,给定结构化的提示和后处理步骤,GPT-3 提供了更高的召回率。GPT-3 能够对提示进行上下文化并学习更通用的模型。然而,GPT-3 在文本模式提取中的缺点是,它本身无法像 HITL 方法那样执行提取任务。实际上,为了获得与 HITL 模型相当的结果,我们不得不

  1. 设计并构建提示的结构,以利用 GPT-3 强大的语言能力和

  2. 对 GPT-3 的字符串输出进行后处理。

然而,这些步骤可能不会被没有太多技术培训的终端用户采取。

结论与未来工作

在我们的初步工作中,我们比较了 HITL 和预训练的大型生成语言模型在文本模式提取任务中的表现。我们想了解终端用户如何使用广泛研究的模型进行文本模式提取。我们发现,HITL 方法在精确度上平均表现更好,而 GPT-3 在召回率上具有相当或更高的得分。

我们的未来工作在这些结果的基础上进行。我们如何结合 HITL 和预训练的大型生成语言模型的优点? 我们如何利用用户输入来改进提示设计,并进而利用大型语言模型的上下文和语言能力?

文本搜索与向量搜索:更好地结合?

原文:towardsdatascience.com/text-search-vs-vector-search-better-together-3bd48eb6132a?source=collection_archive---------2-----------------------#2023-02-16

了解如何使用 OpenSearch 设置混合搜索系统,以便您可以同时受益于文本搜索和向量搜索的优势

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

·

关注 发表在 数据科学前沿 ·8 分钟阅读·2023 年 2 月 16 日

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

图片由 Aarón Blanco Tejedor 提供,来源于 Unsplash

文本数据库在许多业务工作负载中扮演着关键角色,特别是在电子商务中,客户依赖产品描述和评论来做出明智的购买决定。向量搜索,利用文本嵌入来找到语义相似的文档,是另一个强大的工具。然而,由于对将其实施到现有工作流程的复杂性的担忧,一些企业可能会对尝试向量搜索持保留态度。但是,如果我告诉你这可以很容易地完成并且带来显著的好处呢?

在这篇博客文章中,我将展示如何轻松创建一个结合文本和向量搜索的混合设置。这个设置将给你最全面和准确的搜索结果。我将使用 OpenSearch 作为搜索引擎,并使用 Hugging Face 的 Sentence Transformers 来生成嵌入。我为这个任务选择的数据集是“XMarket”数据集(更详细的描述见 这里),在索引过程中,我们将标题字段嵌入到向量表示中。

准备数据集

首先,我们将使用 Sentence Transformers 来索引我们的文档。这个库有预训练的模型,可以生成句子或段落的嵌入。这些嵌入作为文本的独特指纹。在索引过程中,我将标题字段转换为向量表示,并将其在 OpenSearch 中索引。你可以通过简单地导入模型并编码任何文本字段来实现这一点。

可以通过编写以下两行来导入模型:

from sentence_transformers import SentenceTransformer 

model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2') 

embedding = model.encode(text_field)

就这么简单!

我们将通过传递以下映射来创建一个名为“products”的索引:

{ 
   "products":{ 
      "mappings":{ 
         "properties":{ 
            "asin":{ 
               "type":"keyword" 
            }, 
            "description_vector":{ 
               "type":"knn_vector", 
               "dimension":384 
            }, 
            "item_image":{ 
               "type":"keyword" 
            }, 
            "text_field":{ 
               "type":"text", 
               "fields":{ 
                  "keyword_field":{ 
                     "type":"keyword" 
                  } 
               }, 
               "analyzer":"standard" 
            } 
         } 
      } 
   } 
} 

asin — 从产品元数据中获取的文档唯一 ID。

description_vector — 这是我们存储编码后的产品标题字段的地方。

item_image- 这是产品的图片网址

text_field — 这是产品的标题

请注意,我们使用的是标准的 OpenSearch 分析器,它会将字段中的每个单词标记为单个关键词。OpenSearch 使用这些关键词来进行 Okapi BM25 算法。我还将标题字段保存了两次;一次是其原始格式,一次是向量表示。

然后,我将使用该模型来编码标题字段,并创建将批量上传到 OpenSearch 的文档:

def store_index(index_name: str, data: np.array, metadata: list, os_client: OpenSearch): 
    documents = [] 
    for index_num, vector in enumerate(data): 
        metadata_line = metadata[index_num] 
        text_field = metadata_line["title"] 
        embedding = model.encode(text_field) 
        norm_text_vector_np = normalize_data(embedding) 
        document = { 
            "_index": index_name, 
            "_id": index_num, 
            "asin": metadata_line["asin"], 
            "description_vector": norm_text_vector_np.tolist(), 
            "item_image": metadata_line["imgUrl"], 
            "text_field": text_field 
        } 
        documents.append(document) 
        if index_num % 1000 == 0 or index_num == len(data): 
            helpers.bulk(os_client, documents, request_timeout=1800) 
            documents = [] 
            print(f"bulk {index_num} indexed successfully") 
            os_client.indices.refresh(INDEX_NAME) 

    os_client.indices.refresh(INDEX_NAME) 

混合搜索实现

计划是创建一个客户端,该客户端将从用户那里获取输入,使用 Sentence Transformers 模型生成嵌入,并执行我们的混合搜索。用户还会被要求提供一个提升级别,即他们希望给予文本搜索或向量搜索的相对重要性。这样,用户可以选择优先考虑一种搜索类型。例如,如果用户希望他的查询的语义意义比描述中的简单文本出现更重要,他可以给向量搜索更高的提升等级。

搜索

我们将首先使用 OpenSearch 的搜索方法在索引上进行文本搜索。此方法接受一个查询字符串,并返回与查询匹配的文档列表。OpenSearch 通过利用 Okapi BM25 作为排名算法来获取文本搜索结果。使用 OpenSearch 进行文本搜索是通过发送以下请求体进行的:

bm25_query = {
    "size": 20,
    "query": {
        "match": {
            "text_field": query
        }
    },
    "_source": ["asin", "text_field", "item_image"],
}

其中 textual_query 是用户输入的文本。为了使我的结果以干净的方式返回,我添加了“_source”,以便 OpenSearch 仅返回我感兴趣的特定字段。

由于文本和向量搜索的排名分数算法不同,我们需要将分数调整到相同的尺度,以便结合结果。为此,我们将对每个文档的文本搜索分数进行归一化。最大 BM25 分数是针对特定查询在集合中分配给文档的最高分数。它表示文档与查询的最大相关性。最大 BM25 分数的值取决于 BM25 公式的参数,例如平均文档长度、术语频率和逆文档频率。因此,我取了 OpenSearch 为每个查询收到的最大分数,并将每个结果分数除以它,得到 0 到 1 之间的分数。以下函数演示了我们的归一化算法:

def normalize_bm25_formula(score, max_score):
    return score / max_score

接下来,我们将使用向量搜索方法进行向量搜索。此方法接受一个嵌入列表,并返回与这些嵌入在语义上相似的文档列表。

对 OpenSearch 的搜索查询如下所示:

cpu_request_body = {
    "size": 20,
    "query": {
        "script_score": {
            "query": {
                "match_all": {}
            },
            "script": {
                "source": "knn_score",
                "lang": "knn",
                "params": {
                    "field": "description_vector",
                    "query_value": get_vector_sentence_transformers(query).tolist(),
                    "space_type": "cosinesimil"
                }
            }
        }
    },
    "_source": ["asin", "text_field", "item_image"],
}

其中 get_vector_sentence_transformers 将文本发送到 model.encode(text_input),该方法返回文本的向量表示。还要注意,您的 topK 结果越高,结果越准确,但这也会增加延迟。

插值结果并应用提升

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

Amol Tyagi 提供的照片,来源于 Unsplash

现在我们需要将两个搜索结果结合起来。为此,我们将对结果进行插值处理,使得在两个搜索中都出现的每个文档在混合结果列表中排名更高。这样,我们可以利用文本和向量搜索的优势,获得最全面的结果。

以下函数用于插值关键词搜索和向量搜索的结果。它返回一个字典,包含两个命中的结果集之间的共同元素及每个文档的分数。如果文档只出现在一个搜索结果中,则我们将其分配为检索到的最低分数。

def interpolate_results(vector_hits, bm25_hits):
    # gather all product ids
    bm25_ids_list = []
    vector_ids_list = []
    for hit in bm25_hits:
        bm25_ids_list.append(hit["_source"]["asin"])
    for hit in vector_hits:
        vector_ids_list.append(hit["_source"]["asin"])
    # find common product ids
    common_results = set(bm25_ids_list) & set(vector_ids_list)
    results_dictionary = dict((key, []) for key in common_results)
    for common_result in common_results:
        for index, vector_hit in enumerate(vector_hits):
            if vector_hit["_source"]["asin"] == common_result:
                results_dictionary[common_result].append(vector_hit["_score"])
        for index, BM_hit in enumerate(bm25_hits):
            if BM_hit["_source"]["asin"] == common_result:
                results_dictionary[common_result].append(BM_hit["_score"])
    min_value = get_min_score(common_results, results_dictionary)
    # assign minimum value scores for all unique results
    for vector_hit in vector_hits:
        if vector_hit["_source"]["asin"] not in common_results:
            new_scored_element_id = vector_hit["_source"]["asin"]
            results_dictionary[new_scored_element_id] = [min_value]
    for BM_hit in bm25_hits:
        if BM_hit["_source"]["asin"] not in common_results:
            new_scored_element_id = BM_hit["_source"]["asin"]
            results_dictionary[new_scored_element_id] = [min_value]

    return results_dictionary

最终我们将得到一个以文档 ID 为键,以分数数组为值的字典。数组中的第一个元素是向量搜索分数,第二个元素是文本搜索归一化分数。

最后,我们对搜索结果应用提升。我们将遍历结果的分数,将第一个元素乘以向量提升水平,第二个元素乘以文本提升水平。

def apply_boost(combined_results, vector_boost_level, bm25_boost_level):
    for element in combined_results:
        if len(combined_results[element]) == 1:
            combined_results[element] = combined_results[element][0] * vector_boost_level + \
                                        combined_results[element][0] * bm25_boost_level
        else:
            combined_results[element] = combined_results[element][0] * vector_boost_level + \
                                        combined_results[element][1] * bm25_boost_level
    #sort the results based on the new scores
    sorted_results = [k for k, v in sorted(combined_results.items(), key=lambda item: item[1], reverse=True)]
    return sorted_results

现在是时候看看我们有什么了!这就是完整的工作流程:

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

作者制作的 GIF

我搜索了一个句子“冰淇淋勺”,为向量搜索和文本搜索分别设置了 0.5 的提升,这就是我在前几个结果中得到的:

向量搜索返回 —

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

来自 XMarket 数据集的图片

文本搜索返回 —

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

来自 XMarket 数据集的图片

混合搜索返回 —

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

来自 XMarket 数据集的图片

在这个例子中,我们使用文本和向量搜索来搜索“冰淇淋勺”。文本搜索返回包含关键词“an”、“ice”、“cream”和“scoop”的文档。文本搜索排名第四的结果是一个冰淇淋机,它显然不是一个勺子。它排名如此靠前的原因是其标题“Breville BCI600XL Smart Scoop Ice Cream Maker”包含了句子中的三个关键词:“Scoop”、“Ice”、“Cream”,因此在 BM25 中的评分很高,尽管它与我们的搜索不匹配。而向量搜索则返回语义上与查询相似的结果,无论关键词是否出现在文档中。它知道“scoop”出现在“ice cream”之前意味着匹配度较低。因此,我们得到了一个更全面的结果集,其中包含了比单纯提到“冰淇淋勺”的文档更多的信息。

很明显,如果你只使用一种搜索方式,你将错过有价值的结果或显示不准确的结果,从而使客户感到沮丧。当利用两者的优势时,我们会得到更准确的结果。所以,我相信我们的答案是,"更好地结合"已经证明是正确的。

但等等,更好的可以变得更好吗?改善搜索体验的一种方法是利用 OpenSearch 中的 APU 力量(联想处理单元)。通过使用 Searchium.ai 的插件在 APU 上进行向量搜索,我们可以利用先进的算法和处理能力来进一步改善延迟并显著降低成本(例如,$0.23 对比 $8.76),同时仍然获得类似的结果

我们可以安装插件将索引上传到 APU,并通过发送略微修改过的请求体进行搜索:

apu_request_body = {
    "size": 20,
    "query": {
        "gsi_knn": {
            "field": "description_vector",
            "vector": get_vector_sentence_transformers(query).tolist(),
        }
    },
    "_source": ["asin", "text_field", "item_image"],
} 

其他所有步骤都是相同的!

总结一下,通过结合使用 OpenSearch 和 Sentence Transformers 的文本与向量搜索,企业可以轻松改善搜索结果。而通过利用 APU,企业可以将搜索结果提升到一个新的水平,同时降低基础设施成本。不要让复杂性的问题阻碍你。试一试,看看它能带来哪些好处。祝搜索愉快!

完整的代码可以在这里找到。

特别感谢Yaniv VakninDaphna Idelson的所有帮助!

文本切分正确实施:为您的个人 LLM 打下坚实的基础

原文:towardsdatascience.com/text-tiling-done-right-building-solid-foundations-for-your-personal-llm-e70947779ac1?source=collection_archive---------3-----------------------#2023-06-04

如何从头开始使用语义和词汇相似性构建文本切分模型

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

·

关注 发表于 Towards Data Science · 11 分钟阅读 · 2023 年 6 月 4 日

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

图片由 Gary Butterfield 提供,来自 Unsplash

现在似乎每个人都在尝试获得自己的大型语言模型(LLM),并调整其以适应他们的私人文档集。隐私因素在这里起着重要作用,进一步推动了对更多私人 GPT 模型的需求。然而,创建个人聊天机器人的过程并不简单,你基本上有两个主要选项来实现这一目标。

首先,你可以从头开始构建一个定制的问题-答案数据集,并用它来微调你的 LLM。但说实话,由于高成本和显著的时间投入,这对大多数人来说并不是一个可行的选项。另一种更具成本效益的方法是动态生成上下文。这是通过基于用户查询从文档中检索相关部分来完成的,借助嵌入技术。尽管有很多教程解释如何做这件事,但很少有人强调适当地切分或“切块”文档的重要性。

这之所以至关重要,是因为如果你的文档切分不准确,你的上下文可能会出现偏差,从而导致你的 LLM 给出的答案完全偏离主题,或者更糟的是,生成虚假的信息——在机器学习中,这种现象通常被称为“幻觉”。这就是文本切分艺术发挥作用的地方。这一过程的核心在于将文档拆分成连贯且有意义的块,以便于精确、相关的上下文检索。这样做,你很可能会提高 LLM 的整体表现,使其更擅长理解查询并提供准确的回应。

现在,可能会让你感到惊讶(就像我一样惊讶)的是,在 Python 编程的世界里,文本切分的选项并不多。我们主要的工具是nltk.tokenize.texttiling,但这个工具的文档并不完善。意识到这一点的缺乏和改进的潜力,我决定开始开发自己的文本切分模型,利用自然语言处理(NLP)和变换器提供的革命性技术。

文本切分模型的评估机制

每当我开始开发一个新模型时,我总是尝试以最终目标为出发点,然后从那里向后工作。在这种情况下,我们的“终点”是评估模型的输出。如果没有评估手段,我们就无法衡量性能,因此无法进行改进。因此,在尝试开发模型之前,创建一个评估机制是至关重要的。然而,评估文本切分面临独特的挑战,因为它涉及到文档中出现的主题。这给我们带来了两个主要难题:

  1. 我们没有带有对应切分的数据集。

  2. 即使我们有这样的数据集,由于按主题划分文档高度主观,利用起来也会异常困难。

为了应对这些问题,我们将采取一种简单的方法:创建一个合成文档。这个文档将是各种文档的拼接,确保我们知道原始文档之间的确切阈值。这些阈值应由我们的模型识别。在这篇文章中,我将用一个文档作为示例(可以在这里找到)。不过,这种方法也可以用于组装大量文档进行全面的模型测试。这个复合文档是由以下 Medium 文章拼接而成的(对于这些文章的作者,算是免费推广,稍后可以感谢我😀):

3 个生成型 AI 的令人不快的后果 [## 生成型 AI 的 3 个令人不快的后果

‘快速行动,打破常规’的标准操作程序正处于极速运转中。各行各业都在…

平均合同价值在 SaaS [## 平均合同价值在 SaaS

平均合同价值是 SaaS 中的一个重要指标。如果它在上升并趋向右侧,这表明客户…

平均合同价值在 SaaS [## 我们治疗压力、焦虑和抑郁的方法完全错了

心理健康问题的最佳疗法显而易见,但很少被开处方

我们治疗压力、焦虑和抑郁的方法完全错了 [## 你的副业不如在大公司工作

尽管你获得了所有的自由,但在自己工作时你错过了许多东西

你在大公司工作的体验远不如你副业的效果

现在我们已经建立了评估模型的方法,我们需要定义如何衡量其效能。首先考虑我们的合成文档,其中有预先知道的明确阈值。在我们的例子中,这些阈值为:(0, 56, 74, 118, 163)。这意味着第一篇文章在 56 句话后结束,第二篇在 74 句话后结束,以此类推。我们的模型将根据它在每篇文章中识别的子主题,输出一个类似但更详细的阈值列表。一个示例输出可能是:(0, 26, 54, 67, 74, 90, 112, 120, 130, 163)。

那么,我们如何评估模型的有效性?我能设计出的最合乎逻辑的方法是计算原始向量与模型输出之间的“编辑距离”。这个过程如下:

  1. 确定模型输出中所有接近真实阈值的数字(排除第一个和最后一个)。使用上面的例子,我们将得到(54, 74, 120)。

  2. 如果数字少于真实阈值,则用‘None’填补空白(在我们的例子中不会发生)。

  3. 计算每个相应阈值之间的距离,遇到‘None’时用原始向量的最大值代替。在我们的例子中,这将生成(2, 0, 2)。

  4. 将这些距离求和,并通过将其除以原始向量的最大阈值乘以向量的长度来标准化。这提供了一个从 0 到 1 的距离,可以通过计算:1-距离来轻松转换为分数。在我们的例子中:1-4/163 → 1-0.0245 → 0.975

  5. (可选)根据阈值总数对分数施加惩罚。这是为了惩罚生成过多阈值的模型。尽管这些阈值在统计上可能接近真实值,但它们不一定有意义。

这里是实现上述步骤中描述的评分计算的code

这个函数目前远未完美。具体来说,它倾向于产生偏向 1 的值。然而,它足以用于比较我们模型生成的结果。如果你有任何改进其实现的建议,我非常乐意在评论中听取。

生成句子相似度分数

现在我们已经建立了评估模型性能的方法,我们可以开始考虑如何提取阈值。我们模型的基本概念相当简单:文档的块本质上是具有某种相似性水平的句子簇。换句话说,位于同一块中的句子对应该产生较高的相似度分数,而来自不同块的句子对应该产生较低的分数。无论我们决定使用哪种聚类方法,我们都可以安全地说,我们需要一种句子相似度度量。在自然语言处理(NLP)中,最常见的两种相似度度量形式是词汇(基于词汇比较)和语义(基于意义,更技术性地基于嵌入)。在我们的模型中,我们将测试不同类型的相似度分数,并比较它们在测试文档上的表现。具体而言,我们将利用以下模型/算法:

计算相似度分数的函数本质上是一个大型的“IF-ELSE”代码块,根据用户选择的输入模型在不同方法之间切换。

为了性能优化,该函数接受两个句子列表作为输入,并返回相应的相似度度量列表。

接下来,我们需要决定实际要计算哪些相似度分数。一个方法是通过比较每个句子与所有其他句子的对比来计算逐对相似度。这种方法虽然全面,但不仅效率低下且难以扩展,而且也不理想。我们所寻求的簇将由连续的句子组成,这意味着我们不希望识别文档中相距较远的句子之间的联系——实际上,我们的目标是避免这种情况!在另一端,我们可以考虑仅计算给定句子与其后面句子之间的相似度。虽然合理地假设相邻句子具有相同的意义,但这种方法也存在风险。考虑“填充句子”——那些用于修饰文本但不传达任何特定意义或对上下文没有贡献的句子。例子包括“我会尝试不同的说法”或“但是我跑题了”(出现在我们的测试文档中!)。这样的句子可能会产生人为的阈值,我们肯定想要避免这种情况。

因此,我们将采用一种混合方法,只计算每个句子与接下来的 K 个句子的相似度(是的,K 是一个超参数)。如下面的图示所示,一旦设置了 K 参数,每个句子将连接到 2*K 个句子——K 个前面的句子和 K 个后面的句子。

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

作者提供的图片。

生成这种相似度分数的代码封装在函数create_similarity_graph中:

正如函数名称所示,这个函数构建的是一个图,其中节点代表句子,相似性分数作为边的权重。输出格式如下:

(句子 1,句子 2,相似度比 1)

(句子 1,句子 3,相似度比 2)

(句子 1,句子 K,相似度比 K-1)

(句子 2,句子 3,相似度比 K)

确实,这本质上是一个形式为(父节点,子节点,边权重)的图。还需要注意第 27 行的系数math.exp(-l/2),它会乘以相似性分数。我们使用这个系数来适应“距离效应”——即两个句子之间的相似性应随着它们的距离增加而减少。

基于图的文本分割

有了我们的相似度分数,下一步是找到有效的方式对其进行聚类。当前数据的图结构暗示了选择合适算法的方向。虽然有许多图聚类的选项,但我特别偏爱Louvain 社区检测方法,主要是因为它在 Python 中的实现简单且能够高效处理大规模数据。

在算法的上下文中,“社区”指的是一个节点密集互连的簇,同时与其他社区的节点连接稀疏。将这个概念转化到我们的文档分块任务中,这些“社区”就是我们寻求的块。每个块或社区是一个高度相关的句子簇,形成文档中的一个连贯主题或子主题。

鉴于上述图结构,提取社区仅需几行代码。

虽然 Louvain 算法在寻找图中的社区方面表现出色,但重要的是要记住,这些社区在文档分块的上下文中可能并不总是对应于连贯的句子序列。这是因为该算法本身并不意识到它处理的是一个文本文档,其中句子的顺序和连续性很重要。

在社区检测过程中,Louvain 算法可能会生成包含非连续句子的聚类。例如,它可能会生成一个类似(1,2,3,4,6,7)的聚类,遗漏了句子 5。虽然这个聚类可能在内部相似性上仍然很高,但它并未在文档中形成一个逻辑上的块,因为从句子 4 到句子 6 有一个“间隙”或“跳跃”。在将基于图的聚类应用于文档分块时,这是一个关键点。我们的期望是找到连贯、不间断的文本段落——即代表相关内容连续块的块。

为了解决这个问题,我在代码中加入了一个后处理步骤,特别是在第 46 行调用 compact_clusters 函数。由于我没有找到任何现成的算法来执行此任务(如果你知道一个,我很乐意了解),我设计了一个基于以下步骤的简单算法:

  1. 对于每一对聚类,识别它们之间的重叠范围。考虑到聚类 a=(1, 2, 3, 6, 7, 8) 和 b=(4, 5, 9, 10, 11),重叠范围将是 (4, 5, 6, 7, 8)。

  2. 确定消除重叠所需的最少移动次数。在我们的示例中,我们可以将 (6, 7, 8) 从聚类 ‘a’ 转移到 ‘b’,或者将 (4, 5) 从聚类 ‘b’ 移动到 ‘a’。最优选择是后者,因为需要的移动次数更少。如果出现平局,可以做出随机决策。

  3. 根据此调整重新配置聚类。调整后,我们将得到 a = (1, 2, 3, 4, 5, 6, 7, 8) 和 b = (9, 10, 11)。

这种方法确保了我们生成的最终瓦片不仅在内部是一致的,而且在原始文档序列的上下文中也是有意义的。

这是函数的实现:

汇总

现在我们已经构建了所有必要的函数,编写一个确定目标文档瓦片的脚本是很简单的任务。只需几行代码:

如前所述,我们仅测试了四种模型,但集成更多模型只需通过添加新的 ‘elif’ 部分来修改 get_similarity_scores 函数。鉴于我们问题的性质,构建计算瓦片的可视化表示也是很有启发性的。这种图形描述提供了一个立即了解我们算法相对于原始文档表现的方式。这是我使用的绘图函数:

以下是结果图:

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

图片来源于作者。

第一条柱状图展示了原始文档,分割成四篇不同的文章。可以明显看出,BERT Score 的表现非常出色,完美匹配了所有三个主要阈值,而 paraphrase-MiniLM-L6-v2 在三个阈值中有两个未能匹配(第二个几乎匹配,第三个则偏差较大)。值得注意的是,这两个语义模型在文章中识别出了非常相似的子主题,暗示了可以使用集成方法来确定实际应用中的准确阈值。令人惊讶的是,词汇模型的表现并不差,尽管 Jaccard 引入了一些与文档结构不相关的虚假阈值。

总结一下,以下是四种测试算法的得分:

  • BERT Score: 0.9950

  • paraphrase-MiniLM-L6-v2: 0.9321

  • SequenceMatcher: 0.9208

  • Jaccard: 0.9830

收获与下一步

根据我们涉及目标文档的简短测试,很明显,提出的文档标题方法展示了显著的前景。正如我们预期的那样,由于语义方法能够捕捉文本中更深层次的上下文关系,因此在这个特定任务中相比词汇方法具有优势。本文中解释的工作代码的代码库可在此处找到。可能的改进领域包括:

  1. 评分函数的优化:当前的标题评分函数表现出对值 1 的偏倚。解决这一偏倚,使评分函数更加平衡,将提高结果的可靠性,并提供对模型性能的更准确评估。

  2. 探索额外的模型:本研究中我们只测试了四种模型。测试更多的模型,特别是不同类型的语义模型,可能会揭示新的见解,并进一步提高性能。这也可以包括尝试结合多个模型优点的集成方法。

  3. 跨多个文档的验证:我们的测试仅涉及一个文档。对各种文档进行性能评估将使我们更清楚其稳健性和普遍性。不同类型的文本、体裁或主题可能会影响标题生成过程的表现。

  4. 子主题识别的提升:尽管我们的模型能够识别文章中的子主题,但仍有改进的空间。可以使用集成方法或其他高级策略来提高子主题确定的准确性,确保衍生的标题反映出文档的细致结构。

你喜欢这篇文章吗?如果你对人工智能、自然语言处理、机器学习和数据分析在解决实际问题中的应用感兴趣,你可能也会喜欢我的其他作品。我的目标是创作能够展示这些变革性技术在实际场景中的可操作性文章。如果这也是你的兴趣,关注我在 Medium 上的最新作品吧!

文本新颖性检测

原文:towardsdatascience.com/textual-novelty-detection-ce81d2e689bf?source=collection_archive---------6-----------------------#2023-10-02

如何使用最小协方差判别法(MCD)检测新颖的新闻头条

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

·

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

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

图片由 Ali Shah Lakhani 提供,发布于 Unsplash

在今天的信息时代,我们每天都被新闻文章淹没。这些文章中的许多只是对相同事实的重复陈述,但也有一些包含真正的新信息,这些信息可能会对我们的决策产生重大影响。例如,想要投资 Meta 的人可能希望关注那些包含独家信息的文章,而不是那些仅仅重复先前发布数据的文章。能够区分新颖的新闻和冗余的新闻至关重要,这样我们才能在面对信息洪流时做出明智的决策。

这就是新颖性检测发挥作用的地方。新颖性检测是识别新数据或未知数据的任务,这些数据与以前见过的数据有所不同。这是一种无监督学习技术,用于检测数据中的异常、离群值或新模式。关键思想是建立一个“正常”数据的模型,然后利用该模型识别偏离正常的数据点。

在新闻文章的背景下,这涉及到检测文章是否包含在其他地方不可获得的新信息。为此,我们可以开发一个已知或可用的信息基线,然后将新信息与该基线进行比较。如果新信息与基线之间存在显著差异,则可以认为该信息是新颖的。

最小协方差行列式(MCD)

最小协方差行列式(MCD)方法是一种估计数据集协方差矩阵的技术。它可以用来创建一个包围高斯分布中心模式的椭圆形,任何位于该形状外的数据点都可以被视为新颖性(有时称为异常值)。MCD 方法对于噪声大或含有离群值的数据集特别有用,因为它可以帮助识别那些可能不符合整体数据模式的异常数据点。(见示例

MCD 可以用来检测新闻头条中的新颖性。虽然该方法可以推广到完整文章中,我们的目标是提供一个简明的示例,展示如何在短文本中应用 MCD 进行新颖性检测。MCD 是一个多变量位置和散布的鲁棒估计量,使其非常适合在高维数据(如文本)中识别离群值。在新闻头条的数据集上,MCD 将基于协方差学习一个“正常”头条的模型。然后我们可以使用该模型来评分新头条,并标记那些显著偏离正常的头条,作为潜在的新颖或异常故事。示例代码和实验将说明 MCD 新颖性检测在实践中的运作方式。

步骤方法

嵌入: 在机器学习中,我们使用嵌入作为一种更紧凑和高效的方式来表示数据。嵌入将原始数据转换为捕捉数据最重要特征的低维表示。

文本嵌入是一种特定类型的嵌入,用于将文本数据转换为向量表示。它考虑了单词、短语和句子之间的语义和关系,并将它们转换为捕捉文本含义的数值表示。这使我们能够执行诸如查找相似文本、基于语义意义对文本进行聚类等操作。

假设我们收集了过去几个月有关 Meta 的以下头条新闻:

news = [
    "Mark Zuckerberg touts potential of remote work in metaverse as Meta threatens employees for violating return-to-office mandate",
    "Meta Quest 3 Shows Us the Metaverse Dream isn’t Dead Yet",
    "Meta has Apple to thank for giving its annual VR conference added sizzle this year",
    "Meta launches AI chatbots for Instagram, Facebook and WhatsApp",
    "Meta Launches AI Chatbots for Snoop Dogg, MrBeast, Tom Brady, Kendall Jenner, Charli D’Amelio and More",
    "Llama 2: why is Meta releasing open-source AI model and are there any risks?",
    "Meta's Mandatory Return to Office Is 'a Mess'",
    "Meta shares soar on resilient revenue and $40bn in buybacks",
    "Facebook suffers fresh setback after EU ruling on use of personal data",
    "Facebook owner Meta hit with record €1.2bn fine over EU-US data transfers"
]

我们可以使用 OpenAI 生成每个句子的文本嵌入,方法如下:

def get_embedding(text, 
                 model = 'text-embedding-ada-002'):
    text = text.replace("\n", " ")
    return openai.Embedding.create(input = [text], engine = model)['data'][0]['embedding']

df['embedding'] = df.news.apply(lambda x: get_embedding(x))
df['embedding'] = df['embedding'].apply(np.array)

matrix = np.vstack(df['embedding'].values)
matrix.shape

# Output: (10, 1536)

来自 OpenAI 的 text-embedding-ada-002 模型 是一个前沿的嵌入模型,它接受一个句子作为输入,并输出长度为 1536 的嵌入向量。该向量表示输入句子的语义含义,可以用于语义相似性、文本分类等任务。该模型的最新版本采用了最先进的语言表示技术,以生成高度准确和鲁棒的嵌入。如果你无法访问 OpenAI,你可以使用其他嵌入模型,如 Sentence Transformers

一旦生成了嵌入,我们会创建一个矩阵变量,用于存储来自 df[‘embedding’] 列的嵌入矩阵表示。这是通过使用 NumPy 库中的 vstack 函数完成的,该函数将列中的所有向量(每个表示一个句子)垂直堆叠以创建一个矩阵。这使我们能够在下一步中使用矩阵运算。

计算 MCD: 我们使用嵌入作为特征,并计算 MCD 以估计中心数据云(多变量高斯分布的中心模式)的位置和形状。

拟合椭圆包络: 然后,我们使用计算得到的 MCD 拟合一个椭圆包络到中心模式。这个包络作为边界,将正常点与新颖点分开。

预测新颖句子: 最后,我们使用椭圆包络对嵌入进行分类。位于包络内部的点被认为是正常的,而位于包络外部的点被认为是新颖的或异常的。

为了完成这一切,我们使用 Python 中的 scikit-learn 库中的 EllipticEnvelope 类来应用 MCD:

# Reduce the dimensionality of the embeddings to 2D using PCA
pca = PCA(n_components=2)
reduced_matrix = pca.fit_transform(matrix)
reduced_matrix.shape

# Fit the Elliptic Envelope (MCD-based robust estimator)
envelope = EllipticEnvelope(contamination=0.2)  
envelope.fit(reduced_matrix)

# Predict the labels of the sentences
labels = envelope.predict(reduced_matrix)

# Find the indices of the novel sentences
novel_indices = np.where(labels == -1)[0]
novel_indices

#Output: array([8, 9])

contamination 是一个参数,你可以根据期望的新颖句子数量进行调整。它表示数据集中异常值的比例。predict 方法返回一个标签数组,其中 1 表示正常点(内点),而 -1 表示异常点(新颖点)。

此外,为了将高维嵌入可视化为 2D 以及节省计算时间,我们使用 PCA 将高维嵌入向量投影到较低维度的 2D 空间,我们将其称为 reduced_matrix

我们可以看到 novel_indices 输出了 array([8, 9]),这是被认为是新颖的句子索引。

绘制结果: 我们可以通过绘制嵌入和椭圆包络来可视化结果。正常点(内点)可以用一种颜色或标记绘制,而异常点(新颖点)可以用另一种颜色或标记绘制。椭圆包络可以通过绘制与马氏距离对应的椭圆来可视化。

为了实现可视化,我们:

  1. 提取拟合的椭圆包络模型的位置和协方差矩阵。

  2. 计算协方差矩阵的特征值和特征向量,以确定椭圆的方向和轴长。

  3. 计算每个样本到拟合椭圆模型中心的马氏距离。

  4. 根据污染参数确定一个阈值距离,该参数指定了预期的异常值百分比。

  5. 根据阈值马氏距离缩放椭圆的宽度和高度。

  6. 将椭圆内部的点标记为内点,外部的点标记为外点。

  7. 绘制内点和外点,并添加缩放的椭圆补丁。

  8. 用索引标注每个数据点以识别异常值。

# Extract the location and covariance of the central mode
location = envelope.location_
covariance = envelope.covariance_

# Compute the angle, width, and height of the ellipse
eigenvalues, eigenvectors = np.linalg.eigh(covariance)
order = eigenvalues.argsort()[::-1]
eigenvalues, eigenvectors = eigenvalues[order], eigenvectors[:, order]
vx, vy = eigenvectors[:, 0]
theta = np.arctan2(vy, vx)

# Compute the width and height of the ellipse based on the eigenvalues (variances)
width, height = 2 * np.sqrt(eigenvalues)

# Compute the Mahalanobis distance of the reduced 2D embeddings
mahalanobis_distances = envelope.mahalanobis(reduced_matrix)

# Compute the threshold based on the contamination parameter
threshold = np.percentile(mahalanobis_distances, (1 - envelope.contamination) * 100)

# Scale the width and height of the ellipse based on the Mahalanobis distance threshold
width, height = width * np.sqrt(threshold), height * np.sqrt(threshold)

# Plot the inliers and outliers
inliers = reduced_matrix[labels == 1]
outliers = reduced_matrix[labels == -1]

# Re-plot the inliers and outliers along with the elliptic envelope with annotations
plt.scatter(inliers[:, 0], inliers[:, 1], c='b', label='Inliers')
plt.scatter(outliers[:, 0], outliers[:, 1], c='r', label='Outliers', marker='x')
ellipse = Ellipse(location, width, height, angle=np.degrees(theta), edgecolor='k', facecolor='none')
plt.gca().add_patch(ellipse)

# Annotate each point with its index
for i, (x, y) in enumerate(reduced_matrix):
    plt.annotate(str(i), (x, y), textcoords="offset points", xytext=(0, 5), ha='center')

plt.title('Novelty Detection using MCD with Annotations')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.legend()
plt.grid(True)
plt.show()

最后,我们得到内点和外点的可视化效果如下:

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

绘制包络线以及标记为内点或外点的点

现在让我们查看标题,第 8 条和第 9 条是:

Facebook 在欧盟关于个人数据使用的裁决后遭遇新挫折。

Facebook 母公司 Meta 因欧盟-美国数据传输问题被罚创纪录的 12 亿欧元。

这两个标题都与欧盟调节 Meta 在其平台上如何使用和传输个人数据的努力有关。

而内点标题则主要讨论 Meta 如何全力投入人工智能和虚拟现实。人工智能的重点在于新发布的 AI 聊天机器人,而虚拟现实的重点在于新发布的 Meta Quest 3 头戴设备。你还可以注意到第 0 条和第 6 条标题涉及远程办公设置,因此它们在图中的位置较为接近。

总结

在这篇文章中,我们展示了如何根据分布区分正常点和新颖点。简而言之,正常点是指位于数据分布的高密度区域的点,即它们在特征空间中接近大多数其他点。而新颖点则是位于数据分布的低密度区域的点,即它们在特征空间中远离大多数其他点。

在 MCD 和椭圆包络的背景下,正常点是指位于椭圆包络内部的点,椭圆包络是拟合于数据分布的中央模式的。而新颖点则位于椭圆包络外部。

我们还了解到有一些参数影响 MCD 的结果,这些参数包括:

  • 阈值: 决策边界或阈值在确定一个点是正常还是新颖方面至关重要。例如,在椭圆包络方法中,位于包络线内部的点被认为是正常的,而那些在包络线外部的点被认为是新颖的。

  • 污染参数: 这个参数通常用于新颖性检测方法中,定义了预期为新颖或被污染的数据比例。它影响包络线或阈值的紧密程度,从而影响一个点是否被分类为正常或新颖。

我们还应该注意,对于新的文章,由于每篇新闻文章来自不同的周,新颖性检测方法应考虑新闻的时间因素。如果该方法本身没有考虑时间顺序,你可能需要手动纳入这一方面,例如通过考虑话题或情感的变化,这将超出本帖的范围。

1958 年的感知机作为肿瘤分类器

原文:towardsdatascience.com/the-1958-perceptron-as-a-breast-cancer-classifier-672556186156?source=collection_archive---------18-----------------------#2023-01-03

在 Mathematica 中实现的实际示例

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

·

关注 发表在Towards Data Science ·14 分钟阅读·2023 年 1 月 3 日

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

美国海军使用 Mark I Perceptron 读取字母。美国海军国家博物馆,1960 年。图片来源于维基共享资源(公共领域)。

介绍

1958 年由弗兰克·罗森布拉特开发的罗森布拉特感知机[1][2],被认为是神经网络的起源,因为它是第一个展示机器从数据中学习能力的算法。

感知机是一个简单的模型,由一层人工神经元或单元组成,这些单元可以被训练来识别输入数据中的模式。这标志着人工智能领域的开始,并为更复杂的神经网络的发展铺平了道路,这些神经网络已被用于各种应用。

第一个实现是 1950 年代末由麻省理工学院林肯实验室开发的 Mark I 感知机。它是第一个能够从示例中学习的机器,用于执行模式识别任务,如读取手写信件和识别口语。Mark I 感知机使用真空管和其他电子组件构建,由一系列按层结构连接的单元组成。每个单元能够处理输入数据,并调整其内部权重和偏置,以识别数据中的模式。

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

图 1. Mark 1 的细节。来源:维基共享资源。

这台机器在 1960 年代被美国海军用于读取手写信件。那时,海军接收到大量的信件,需要一种快速而准确的处理信件的方法。他们转向了感知机,这种机器能够从示例中学习并识别输入数据中的模式。通过在大量手写信件数据集上训练感知机,海军能够开发出一个能够准确读取和分类信件的系统,且只需最少的人力干预。这在当时是一个重要的成就,因为它展示了人工智能和机器学习在自动化先前由人类完成的任务方面的潜力。感知机在这一应用中的成功帮助确立了这项技术作为一种强大的模式识别工具,并为其在各种应用中的使用打开了大门。

单神经元网络

罗斯伯拉特感知机是一个简单的人工神经元模型。在这个模型中,单个神经元有多个输入,这些输入是输入数据中特征的值,以及一个输出,这是一个二进制值,指示输入数据是否属于两个类别之一。感知机通过为每个输入分配权重来工作,权重表示该输入在确定输出中的重要性。然后,使用一个数学函数来计算输出,这个函数将加权输入与偏置项结合在一起。偏置项是一个可以调整的固定值,用来移动感知机的输出。通过根据预测中的误差调整权重和偏置,感知机可以被训练以识别输入数据中的模式。这种调整权重和偏置以最小化误差的过程称为训练感知机。

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

图 2. 感知器对应于一个神经元神经网络。在图像中未显示偏置,偏置必须单独添加,作为一个常数应用到一个权重中,或完全不添加。图片来自Chrislb,维基共享资源

该模型由一个单层单元组成,每个单元具有d个特征作为输入,和一个可以取值为*-1+1*的二进制输出。感知器利用这些输入和输出学习如何将数据分类为两个类别。

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

图 3. 训练观测方程。

为了训练感知器,我们需要一个训练集,其中包含多个观测值,每个观测值包括 d 个特征(X向量)和实际输出(y)。感知器利用这些观测值来学习如何根据特征预测输出。感知器的输出通过权重向量和特征向量的点积的符号计算得出。

预测函数

罗斯布拉特感知器的预测函数用于根据输入数据和单位的内部权重及偏置计算感知器的输出。感知器的输出是一个二进制值,表示输入数据是否属于两个类别之一。预测函数定义如下:

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

图 4. 无偏置的预测函数。

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

图 5. 带偏置的预测函数。

其中W是与输入特征对应的权重向量,X是特征的输入值向量。模型可以有偏置或没有偏置,其中b是偏置项。符号函数如果括号中的值为负数,则返回**-1**,如果为正数,则返回+1。感知器在训练过程中调整权重和偏置,以最小化预测中的误差并提高准确性。

偏置

罗斯布拉特感知器中的偏置项代表了应用于所有输入的额外权重。它用于移动感知器的输出,并可以在训练过程中进行调整以提高模型的准确性。偏置可以通过两种方式之一实现:作为一个常数+1 添加到一个特征中,或作为一个单独调整的外部参数。

如果偏置作为常数+1 特征(特征技巧)实现,它将像任何其他输入特征一样处理,并赋予其自己的权重。这意味着偏置项包含在输出计算中。

另外,偏置也可以作为一个外部参数来实现,与其他权重单独调整。然后将偏置添加到通过权重向量和特征向量计算的输出中。

当特征变量的均值已居中,但二元类预测的均值不为 0 时,Rosenblatt 感知器中的偏置项是有用的,因为它允许模型调整决策边界,以更好地适应数据。当二元类别分布高度不平衡时,这一点尤为重要,因为模型可能倾向于更频繁地预测多数类,以最小化错误。在这种情况下,偏置可以用来调整决策边界的位置,提升模型正确分类少数类的能力。

损失函数

Rosenblatt 感知器模型没有包含损失函数的正式定义,即使其目标是最小化预测值和实际值之间的误差。为了实现这一点,感知器根据预测中的误差调整模型的权重和偏置。定义感知器模型中的误差的一种方法是使用最小二乘法,即最小化预测值和实际值之间平方差的总和。这个损失函数可以用数学形式表示如下:

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

图 6. 感知器的损失函数。

尽管梯度下降是机器学习中最常用的损失函数最小化方法,但它不能应用于 Rosenblatt 感知器,原因在于该函数不连续。相反,感知器使用了一种称为感知器收敛定理的学习规则,该规则基于调整模型的权重和偏置以最小化预测误差的思想。

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

图 7. 相当于损失函数的梯度下降。

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

图 8. 优化权重向量的迭代方程。

Rosenblatt 感知器模型中的学习参数是一个超参数,它决定了学习算法的步长。它用于控制模型权重和偏置根据预测误差的更新速度。较大的学习参数会导致权重和偏置的更新幅度较大,这可能导致更快的学习,但也可能增加过拟合的风险。较小的学习参数会导致较小的更新幅度,这可能导致学习速度较慢,但也可能减少过拟合的风险。

尽管感知机模型是在引入梯度下降概念之前开发的,但通常描述为具有随机梯度下降(SGD)学习算法。这是因为感知机学习规则,即感知机收敛定理,精神上类似于梯度下降,因为它涉及迭代调整模型的权重和偏置,以最小化预测误差。与梯度下降一样,感知机使用学习参数来控制更新的步长,并且可以被视为一种在线学习形式,因为它一次处理一个训练样本。

总的来说,感知机模型中的学习参数在控制学习过程的速度和准确性方面起着至关重要的作用。通过调整学习参数,可以微调感知机模型的性能,并在各种分类任务中获得更好的结果。

感知机在行动中:一个乳腺癌预测器

威斯康星州乳腺癌数据集[3]是一个常用的数据集,用于展示罗森布拉特感知机模型的能力。该数据集包含 1989 年至 1992 年期间拍摄的 699 个乳腺癌活检图像样本,这些样本根据某些特征的存在被分类为良性或恶性。数据集中包括从图像中计算出的 9 个特征,包括肿块厚度、细胞大小的均匀性、细胞形状的均匀性、边缘粘附、单个上皮细胞大小、裸核、平淡的染色质、正常核仁和肿瘤的有丝分裂。

该数据集是观察感知机实际应用的一个好例子,因为它是一个相对简单的数据集,良性和恶性类别之间有明显的分隔。这意味着感知机应该能够以较高的准确度正确分类样本。此外,数据集中的特征定义明确且易于理解,这使得解释感知机模型的结果变得容易。

威斯康星州乳腺癌数据集包括以下特征:

 #  Attribute                     Domain
   -- -----------------------------------------
   1\. Sample code number            id number
   2\. Clump Thickness               1 - 10
   3\. Uniformity of Cell Size       1 - 10
   4\. Uniformity of Cell Shape      1 - 10
   5\. Marginal Adhesion             1 - 10
   6\. Single Epithelial Cell Size   1 - 10
   7\. Bare Nuclei                   1 - 10
   8\. Bland Chromatin               1 - 10
   9\. Normal Nucleoli               1 - 10
  10\. Mitoses                       1 - 10
  11\. Class :                       (2 for benign, 4 for malignant)

在这个示例中,感知机将使用 Wolfram Mathematica 语言实现(可以很容易地适应任何其他语言)。

实现的步骤包括:

  1. 定义特征。

  2. 加载数据(包括清理)。

  3. 将数据集划分为训练集和测试集。

  4. 分配初始权重向量。

  5. 训练模型(优化权重向量)。

  6. 使用训练好的模型比较测试数据集。

  7. 计算准确率。

  8. 计算召回率。

  9. 计算混淆矩阵。

第 1 步:定义特征

features = {
   "Sample code number", 
   "Clump Thickness", 
   "Uniformity of Cell Size", 
    "Uniformity of Cell Shape",
    "Marginal Adhesion",  
   "Single Epithelial Cell Size" ,
   "Bare Nuclei",
   "Bland Chromatin",
   "Normal Nucleoli",
   "Mitoses",
   "Class"
   };
features = ToUpperCase[features];
features = StringReplace[features, " " -> "_"]

---
{"SAMPLE_CODE_NUMBER", "CLUMP_THICKNESS", "UNIFORMITY_OF_CELL_SIZE", \
"UNIFORMITY_OF_CELL_SHAPE", "MARGINAL_ADHESION", \
"SINGLE_EPITHELIAL_CELL_SIZE", "BARE_NUCLEI", "BLAND_CHROMATIN", \
"NORMAL_NUCLEOLI", "MITOSES", "CLASS"}

第 2 步:加载数据并清理

SetDirectory[
  "/data/uci_breast_cancer/"];
data = Import[
   "/data/uci_breast_cancer/breast-cancer-wisconsin.data"];
data = DeleteCases[data, {___, x_ /; ! NumberQ[x], ___}];

第 3 步:将数据集划分为训练集和测试集

(* OBTAIN DATA LENGTH *)
n = Length[data];

(* SET THE RATIO BETWEEN TEST AND TRAINING, TEST IS 20 PERCENT *)
testFraction = 0.2;

(* SET THE ALPHA VALUE *)
alpha = 0.9;

(* SHUFFLE THE DATA *)
randomizedData = RandomSample[data];

(* EXTRACT THE TRAINING AND TEST SETS *)
testData = Take[randomizedData, Round[testFraction*n]];
trainingData = Drop[randomizedData, Round[testFraction*n]];

(* GET THE LENGTHS OF EACH DATASET *)
lengthTestData = Length[testData]
lengthTrainingData = Length[trainingData]

---
137

546

第 4 步:分配初始权重向量

W = ConstantArray[1, Length[features[[2 ;; 10]]]]

---
{1, 1, 1, 1, 1, 1, 1, 1, 1}

第 5 步:训练模型(优化权重向量)

nonZeroSign[x_] := If[x > 0.0, 1.0, -1.0];
Do[
  X = trainingData[[i, 2 ;; 10]];
  Y = trainingData[[i, 11]] - 3;
  EY = nonZeroSign[Dot[X, W]];
  W = (W  + alpha (Y - EY) X);
  , {i, 1, lengthTrainingData}
  ];
W

---
{-2.6, 42.4, 6.4, 20.8, -78.2, 19., -26., 53.2, 6.4}

第 6 步:使用训练好的模型比较测试数据集

results = ConstantArray[0, lengthTestData];
Do[
  X = testData[[i, 2 ;; 10]];
  results[[i]] = nonZeroSign[Dot[X, W]];
  , {i, 1, lengthTestData}
  ];
Y = testData[[All, 11]] - 3.0
EY = results

---
{-1., -1., 1., -1., -1., -1., 1., 1., -1., -1., -1., -1., -1., -1., \
-1., -1., -1., -1., -1., -1., 1., 1., -1., -1., -1., -1., 1., -1., \
1., -1., -1., -1., -1., -1., -1., -1., 1., -1., -1., -1., -1., -1., \
-1., -1., -1., -1., 1., -1., -1., 1., 1., -1., 1., 1., 1., -1., 1., \
-1., 1., -1., -1., -1., -1., 1., -1., 1., -1., -1., -1., 1., -1., \
-1., -1., 1., 1., 1., 1., 1., -1., 1., -1., 1., -1., -1., 1., 1., \
-1., -1., 1., 1., -1., -1., 1., 1., 1., -1., -1., -1., 1., -1., 1., \
-1., -1., 1., 1., 1., -1., -1., -1., -1., -1., -1., -1., -1., -1., \
-1., -1., 1., -1., 1., 1., -1., 1., -1., 1., -1., -1., 1., -1., -1., \
-1., -1., -1., -1., -1., -1., -1.}

{-1., -1., -1., -1., -1., -1., 1., 1., -1., -1., -1., -1., -1., -1., \
-1., -1., -1., -1., -1., 1., 1., 1., -1., -1., -1., -1., 1., -1., 1., \
-1., -1., -1., -1., -1., 1., 1., 1., -1., -1., -1., -1., 1., -1., 1., \
-1., 1., -1., 1., -1., -1., 1., -1., 1., 1., 1., -1., 1., -1., 1., \
1., -1., -1., -1., 1., 1., 1., 1., -1., -1., -1., -1., -1., -1., 1., \
1., 1., 1., -1., -1., 1., -1., 1., 1., -1., 1., 1., -1., 1., 1., 1., \
-1., -1., 1., 1., 1., -1., -1., 1., 1., -1., 1., -1., -1., 1., 1., \
-1., -1., -1., 1., -1., -1., 1., 1., 1., -1., -1., -1., 1., -1., 1., \
1., -1., 1., -1., 1., -1., -1., 1., -1., -1., -1., -1., -1., -1., 1., \
1., -1.}

第 7 步:计算准确率

testDataHit = MapThread[Equal, {EY, Y}]
testDataHitCount = Count[testDataHit, True]
EYAccuracy = testDataHitCount/Length[Y]*1.0

---
{True, True, False, True, True, True, True, True, True, True, True, \
True, True, True, True, True, True, True, True, False, True, True, \
True, True, True, True, True, True, True, True, True, True, True, \
True, False, False, True, True, True, True, True, False, True, False, \
True, False, False, False, True, False, True, True, True, True, True, \
True, True, True, True, False, True, True, True, True, False, True, \
False, True, True, False, True, True, True, True, True, True, True, \
False, True, True, True, True, False, True, True, True, True, False, \
True, True, True, True, True, True, True, True, True, False, True, \
True, True, True, True, True, True, False, True, True, False, True, \
True, False, False, False, True, True, True, True, True, True, True, \
True, True, True, True, True, True, True, True, True, True, True, \
True, True, False, False, True}

112

0.817518

第 8 步:计算召回率

recall =
 Count[
    Thread[Thread[Y == 1.0] && Thread[EY == 1.0]]
    , True]/(
    Count[
      Thread[Thread[Y == 1.0] && Thread[EY == 1.0]]
      , True]
     +
     Count[
      Thread[Thread[Y == 1.0] && Thread[EY == -1.0]]
      , True]
    )*1.0

---
0.863636

第 9 步:混淆矩阵

beningPredictedBening = 
 Count[Thread[Thread[Y == -1.0] && Thread[EY == -1.0]], True]
beningPredictedMalignant = 
 Count[Thread[Thread[Y == -1.0] && Thread[EY == 1.0]], True]
malignantPredictedBening = 
 Count[Thread[Thread[Y == 1.0] && Thread[EY == -1.0]], True]
malignantPredictedMalignant = 
 Count[Thread[Thread[Y == 1.0] && Thread[EY == 1.0]], True]
MatrixPlot[
 {
  {beningPredictedBening, beningPredictedMalignant},
  {malignantPredictedBening, malignantPredictedMalignant}
  },
 ImageSize -> 300,
 ColorFunction -> "TemperatureMap",
 FrameTicks -> {
   {{1, "TUMOUR\nBENING"}, {2, "TUMOUR\nMALIGNANT"}},
   {{1, "PREDICTED\nBENING"}, {2, "PREDICTED\nMALIGNANT"}},
   {{1, ""}, {2, ""}},
   {{1, ""}, {2, ""}}
   },
 PlotLabel -> "CONFUSSION MATRIX PERCEPTRON",
 Epilog -> {
   Text[beningPredictedBening, {1/2, 3/2}],
   Text[beningPredictedMalignant, {3/2, 3/2}],
   Text[malignantPredictedBening, {1/2, 1/2}],
   Text[malignantPredictedMalignant, {3/2, 1/2}]}
 ]

---
74

19

6

38

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

图 9. 我们感知机的混淆矩阵

测量我们分类器的性能

测量一个感知机(或任何其他分类器)表现的一个方法是评估其在测试数据集上的表现。可以用来评估分类器表现的几个指标有准确率、精确度、召回率和 F1 分数。

准确率是分类器做出正确预测的百分比。它是通过将正确预测的数量除以总预测数量来计算的。然而,当类别不平衡(即一个类别比另一个类别更常见)时,准确率可能会具有误导性。

召回率是分类器正确识别的正例的百分比。它是通过将真实正例预测的数量除以总正例数量来计算的。召回率在需要最小化假阴性数量的应用中尤为重要(例如癌症检测器)。在我们的例子中,我们获得了 86%的召回率,这意味着我们的预测器遗漏了 14%的恶性肿瘤。

混淆矩阵是一个表格,显示了分类器做出的真实正例、真实负例、假正例和假负例的预测数量。它是理解分类器优缺点以及比较不同分类器性能的有用工具。

在癌症检测器中,混淆矩阵特别重要,因为它可以帮助识别分类器做出错误预测的情况。例如,如果分类器产生了大量的假阴性(即漏掉了很多癌症病例),可能需要调整分类器或收集更多的训练数据以提高其性能。另一方面,如果分类器产生了大量的假阳性(即将许多良性病例误判为癌症),可能需要调整分类器以使其在预测时更加保守。

总结

这篇文章提供了对感知机的基本数学描述,感知机是一种在 1950 年代开发的单层神经网络。它解释了感知机背后的数学原理,包括它们如何用于将数据分类到不同的类别中。

感知机被应用于一个在数据科学中广为人知的数据集,即 1995 年的威斯康星乳腺癌数据集,并演示了如何使用不同的指标来评估分类器的性能。感知机在 Mathematica 中的实现展示了如何在现代编程语言中轻松表示这些概念,实施中的不同步骤展示了如何从头设计分类器并评估其性能。

尽管感知器由于更先进的神经网络架构的出现而不再被现代机器学习实践使用,但文章显示它们仍然是理解神经网络基本原理的有价值工具。

参考文献

[1] news.cornell.edu/stories/2019/09/professors-perceptron-paved-way-ai-60-years-too-soon

[2] psycnet.apa.org/record/1959-09865-001

[3] archive.ics.uci.edu/ml/datasets/breast+cancer+wisconsin+(diagnostic) | archive-beta.ics.uci.edu/dataset/15/breast+cancer+wisconsin+original (CC BY 4.0 许可,见致谢)。

致谢

使用的数据集由 UCI 机器学习库提供。数据集由以下人员创建:

1. Dr. William H. Wolberg,普通外科系。

威斯康星大学,临床科学中心

麦迪逊,WI 53792

2. W. Nick Street,计算机科学系。

威斯康星大学,1210 West Dayton St.,麦迪逊,WI 53706

3. Olvi L. Mangasarian,计算机科学系。

威斯康星大学,1210 West Dayton St.,麦迪逊,WI 53706

捐赠者:Nick Street。

UCI 机器学习库 archive.ics.uci.edu/ml。加州欧文:加州大学信息与计算机科学学院。 (archive.ics.uci.edu/ml/about.html / archive.ics.uci.edu/ml/citation_policy.html)。

每位数据领导者成功所需的 3 项核心技术技能

原文:towardsdatascience.com/the-3-essential-technical-skills-every-data-leader-needs-to-be-successful-a1800c644469

赋能数据领导者掌握关键技术技能,以推动业务洞察

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

·发布于 Towards Data Science ·4 min read·2023 年 6 月 19 日

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

图片由 Nick Fewings 提供,来源于 Unsplash

根据Gartner的调查,CDO 的平均任期仅为 2.4 年。这一令人震惊的发现突显了数据领导者面临的挑战,并强调了为他们提供实现持久成功所需的核心技术技能的重要性。

尽管作为数据领导者,软技能的关注度非常高,但不幸的是,在早期阶段,技术技能的缺乏有时会导致知识不足,从而导致任期较短。

今天,让我们深入探讨三大关键技术知识领域,赋能数据领导者应对数据分析的复杂性,推动创新,并最终在其组织中产生持久影响。

1. 解释和传达架构的能力

架构知识对数据领导者的成功至关重要。

你需要处理的首要任务之一是确保为组织提供数据的平台稳健可靠。然而,缺乏架构知识的领导者往往难以理解和表达其重要性。

数据架构是一项技术技能,应与沟通这一软技能结合,以便将信息传达给技术和非技术利益相关者。这还包括分析和解释各种数据架构组件,如数据库、数据仓库、数据湖和数据管道。

有效的架构沟通促进了协作和决策。

你应该能够提出/回答的示例问题:

  • 我们的客户-facing 系统是什么?

  • 我们多久从中提取一次信息?

  • 组织中的所有数据存储在哪里?

  • 通常的数据流是什么样的?

  • 捕获数据后我们能多快分析它?

  • 哪些数据支持我们的 AI / ML 模型?

  • 我们目前如何管理这些数据/应该如何管理这些数据?

  • 数据质量如何?

  • 我们的约定数据定义是什么?

2. 从用例定义数据价值

如果数据没有提供价值,它就是负担,而不是资产。

作为数据领导者,你应该能够透过繁杂和噪音,准确找出最终解决业务问题的数据。数据领导者还必须具备评估组织战略目标和需求的能力,并将其转化为可操作的数据驱动用例。

这一技能需要对业务领域和现有数据资产有深入了解。

你应该能够提出/回答的示例问题:

  • 我们的关键数据资产是什么?

  • 数据资产如何转化为产品?

  • 数据质量不足如何影响最终业务用例?

  • 我们用这些数据解决了哪些业务用例?

  • 如何利用这些数据生成收入和降低风险?

  • CFO / CRO / CxO 的优先事项是什么,我的数据功能如何支持这些优先事项?

3. 理解数据资产中的复杂性和冗余

简化数据资产,提升效率。

除了收入生成机会,还应关注成本效率。成功的组织很快就会超越其 IT 和数据资产。数据领导者必须了解他们的数据资产以及可以改进或简化的领域。发现冗余可以帮助你简化操作、优化存储和提高数据质量。

这一技能带来简化、高效且现代的数据资产,推动有价值的洞察和业务成果。

你应该能够提出/回答的示例问题:

  • 有多少数据源或存储区域是冗余的?

  • 组织中有哪些数据孤岛,这些数据孤岛的影响是什么?

  • 如果某些操作数据存储被停用,成本效率会如何变化?

  • 如何减少复杂性以简化数据治理和质量?

  • 哪些业务流程依赖于数据资产中的复杂部分?

结论

软技能仍然至关重要;你必须能够与同事谈判,与不同受众沟通,简化复杂主题等。然而,你还必须具备技术技能,以赢得团队和领导的信任。

实施数据质量(DQ)是工作中最困难的部分之一。如果你想向你的领导团队推销 DQ 并实施其核心方面,请查看我的免费终极数据质量手册通过领取你的副本,你还将成为我们教育社区的一部分,通过我们的邮件列表获得有价值的见解和更新。

[## 终极数据质量手册 - 免费!

介绍《终极数据质量手册:完善数据的最佳实践》。在今天这个数据驱动的世界里……

hanzalaqureshi.gumroad.com](https://hanzalaqureshi.gumroad.com/l/cfijx?layout=profile&source=post_page-----a1800c644469--------------------------------)

如果你还没有订阅 Medium,可以考虑使用我的推荐链接订阅。它比 Netflix 便宜,并且显然更值得你的时间。 如果你使用我的链接,我会获得少量佣金,而你将获得 Medium 上的无限故事,双赢。

我永久切换从 Pandas 到 Polars 的 3 个理由

原文:towardsdatascience.com/the-3-reasons-why-i-have-permanently-switched-from-pandas-to-polars-b41d013a787b

我来是为了速度,但我留在这里是为了语法

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

·发表于 Towards Data Science ·阅读时间 10 分钟·2023 年 3 月 28 日

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

照片Hans-Jurgen Mager 供稿,刊登在 Unsplash

在撰写这篇文章时,我已经在数据科学领域工作了六年。而在这六年中,[Pandas](https://pandas.pydata.org/) 一直是我所有工作的基础:探索性数据分析、影响分析、数据验证、模型实验等。我的职业生涯是建立在Pandas之上的!

不用说,我曾经在Pandas中有过严重的锁定

也就是说,直到我发现了[Polars](https://github.com/pola-rs/polars),这个新的“极其快速的 DataFrame 库”。

在这篇文章中,我将解释:

  1. Polars 是什么,以及是什么让它如此快速;

  2. 我永久切换从PandasPolars的 3 个理由;

    • .list 命名空间;

    • .scan_parquet().sink_parquet()

    • 面向数据的编程。

介绍 Polars:你可能从未听说过的最快 Python 数据框库。

也许你听说过Polars,也许你没有!无论如何,它正在慢慢改变 Python 的数据处理领域,就从这里的 Towards Data Science 开始:

  • Leonie Monigatti 最近写了一篇 Pandas 与 Polars 的全面时间比较。

  • Wei-Meng Lee 已于去年夏天发布了 入门指南。

  • Carl M. Kadie 几个月前在 一个关于 [Pandas](https://medium.com/towards-data-science/understand-polars-lack-of-indexes-526ea75e413) [Polars](https://medium.com/towards-data-science/understand-polars-lack-of-indexes-526ea75e413) 的最大表面差异之一 中写道 [Polars](https://medium.com/towards-data-science/understand-polars-lack-of-indexes-526ea75e413)没有索引

那么,是什么让 Polars 如此快速?来自 [Polars](https://pola-rs.github.io/polars-book/user-guide/#introduction) 用户指南

Polars 完全用 [Rust](https://www.rust-lang.org/) 编写(没有运行时开销!),并使用 [Arrow](https://arrow.apache.org/) – 作为其基础的 原生 arrow2 [Rust](https://github.com/jorgecarleitao/arrow2) 实现……

Polars 是用 Rust 编写的,这使它具备了 C/C++ 的性能,并且能够完全控制查询引擎中的性能关键部分……

…与像 dask 这样的工具不同,dask 试图并行化现有的单线程库如 NumPy 和 Pandas,而 Polars 从零开始编写,专为 DataFrame 上的查询并行化设计。

就是这样。Polars 不仅仅是一个用于缓解 Pandas 单线程特性的框架,像 [dask](https://docs.dask.org/en/stable/)[modin](https://modin.readthedocs.io/en/latest/#);相反,它是对 Python 数据框的全面改造,包含了作为基础的高度优化的 Apache Arrow 列存储内存格式,还有其自身的查询优化引擎。而速度方面的结果令人惊叹(根据 h2oai 的数据基准测试):

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

图片来源于 h2oai 的数据基准工具

对于一个 5GB 的数据框进行 groupby 操作时,PolarsPandas 快 6 倍以上!

仅仅是这速度就足以引起任何人的兴趣。但正如你将在本文其余部分看到的,速度吸引了我,但真正让我爱上的是其语法。

我为什么永久切换到 Polars 的 3 个理由

1. .list 命名空间

想象一下在Pandas中的以下场景:你有一个包含家庭及其信息的数据集,其中包括家庭成员的所有成员列表:

import pandas as pd
df = pd.DataFrame({
    "last_name": ["Johnson", "Jackson", "Smithson"],
    "members": [["John", "Ron", "Con"], ["Jack", "Rack"], ["Smith", "Pith", "With", "Lith"]],
    "city_of_residence": ["Boston", "New York City", "Dallas"]
})
print(df)

>>>>   last_name                    members city_of_residence
     0   Johnson           [John, Ron, Con]            Boston
     1   Jackson               [Jack, Rack]     New York City
     2  Smithson  [Smith, Pith, With, Lith]            Dallas

对于你的分析,你想从members列表的第一个元素创建一个新列。你该怎么做?搜索Pandas API 会让你迷失,但简单的 stack overflow 搜索会告诉你答案!

提取Pandas列中列表元素的主要方法是使用.str命名空间(stackoverflow ref1, stackoverflow ref2),像这样:

df["family_leader"] = df["members"].str[0]
print(df)

>>>>   last_name                    members city_of_residence family_leader
     0   Johnson           [John, Ron, Con]            Boston          John
     1   Jackson               [Jack, Rack]     New York City          Jack
     2  Smithson  [Smith, Pith, With, Lith]            Dallas         Smith

如果你像我一样,你可能会想,“为什么我必须使用.str命名空间来处理list数据类型?”。

不幸的是,Pandas.str命名空间不能完成所有可能需要的list操作;例如,有些操作需要代价高昂的.apply。而在Polars中,这不是问题。通过符合 Apache Arrow 的列式数据格式,Polars具有所有标准数据类型,并有适当的命名空间来处理所有这些类型——包括list

import polars as pl
df = pl.DataFrame({
    "last_name": ["Johnson", "Jackson", "Smithson"],
    "members": [["John", "Ron", "Con"], ["Jack", "Rack"], ["Smith", "Pith", "With", "Lith"]],
    "city_of_residence": ["Boston", "New York City", "Dallas"]
})
df = df.with_columns([
    pl.col("members").list.get(0).alias("family_leader")])
print(df)

>>>> ┌───────────┬─────────────────────────────┬───────────────────┬───────────────┐
     │ last_name ┆ members                     ┆ city_of_residence ┆ family_leader │
     │ ------------           │
     │ strlist[str]strstr           │
     ╞═══════════╪═════════════════════════════╪═══════════════════╪═══════════════╡
     │ Johnson   ┆ ["John", "Ron", "Con"]      ┆ Boston            ┆ John          │
     │ Jackson   ┆ ["Jack", "Rack"]            ┆ New York City     ┆ Jack          │
     │ Smithson  ┆ ["Smith", "Pith","Lith"] ┆ Dallas            ┆ Smith         │
     └───────────┴─────────────────────────────┴───────────────────┴───────────────┘

没错:Polars对数据类型非常明确,甚至在每次打印数据框时都会告诉你每一列的数据类型!

但这还不是全部。Pandas的 API 不仅需要使用一个数据类型的命名空间来处理另一个数据类型,而且 API 变得非常臃肿,往往有许多种方法来做同一件事。这可能会令人困惑,尤其是对新手而言。考虑以下代码片段:

import pandas as pd

df = pd.DataFrame({
    "a": [1, 1, 1],
    "b": [4, 5, 6]
})

column_name_indexer = ["a"]
boolean_mask_indexer = df["b"]==5
slice_indexer = slice(1, 3)

for o in [column_name_indexer, boolean_mask_indexer, slice_indexer]:
    print(df[o])

在这个代码片段中,相同的Pandas语法df[...]可以执行三种不同的操作:检索数据框的一列,在数据框上执行基于行的布尔掩码,以及按索引检索数据框的切片。

另一个令人困扰的例子是,要处理dict列时,使用Pandas通常需要执行一个代价高昂的apply()函数;而Polars则有一个struct数据类型,可以直接处理dict列!

Pandas中,你不能完成所有你想做的事情,对于可以做的事情,有时有多种方式来完成。与此相比,Polars可以做所有事情,数据类型明确,通常只有一种方法来做同一件事。

2. .scan_parquet().sink_parquet()

Polars最棒的地方之一是它提供了两个 API:一个急切的 API 和一个延迟的 API。

急切的 API 会像Pandas一样在内存中运行所有命令。

然而,延迟 API 只有在明确要求响应时(例如,通过.collect()语句)才会执行所有操作,有点像dask。而且,一旦被要求响应,Polars会依赖其查询优化引擎,以最快的时间提供结果。

考虑以下代码片段,比较Polars的急切DataFrame与其延迟对应物LazyFrame的语法:

import polars as pl
eager_df = pl.DataFrame({
    "a": [1, 2, 3],
    "b": [4, 5, 6]
})
lazy_df = pl.LazyFrame({
    "a": [1, 2, 3],
    "b": [4, 5, 6]
})

语法非常相似!实际上,急切 API 和延迟 API 之间唯一的主要区别在于数据框的创建、读取和写入,这使得在两者之间切换变得非常容易:

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

作者表

这就引出了.scan_parquet().sink_parquet()

通过使用[.scan_parquet()](https://pola-rs.github.io/polars-book/user-guide/lazy-api/streaming.html) 作为数据输入函数,[LazyFrame](https://pola-rs.github.io/polars-book/user-guide/lazy-api/streaming.html) 作为数据框,和[.sink_parquet()](https://pola-rs.github.io/polars-book/user-guide/lazy-api/streaming.html) 作为数据输出函数,你可以处理超出内存的数据集! 这真的很酷,尤其是当你把它与Pandas的创始人 Wes McKinney 在 2017 年发布的“Apache Arrow 和‘我讨厌 Pandas 的 10 件事’”中关于Pandas内存占用的说法进行比较时:

“我的 Pandas 使用规则是,你的 RAM 应该是数据集大小的 5 到 10 倍。”

3. 数据导向编程

Pandas将数据框视为对象,支持面向对象编程;但Polars将数据框视为数据表,支持数据导向编程。

让我解释一下。

使用数据框时,我们大多数时候要做的是运行查询或转换;我们要添加列,按两个变量进行透视,聚合,分组,等等。即使当我们想将数据集分为训练集和测试集以训练和评估机器学习模型时,这些本质上也是类似 SQL 的查询表达式。

这是真的——使用Pandas,你可以对数据进行大多数你想要的转换、操作和查询。然而,让人沮丧的是,有些转换和查询只能在多个表达式或查询中完成。与 SQL 或 Spark 等其他查询和数据处理语言不同,Pandas中的许多查询需要多个连续的、独立的赋值表达式,这可能使事情变得复杂。考虑以下代码片段,我们创建了一个包含人员及其年龄的数据框,并且我们想要查看每个十年中有多少人:

import pandas as pd
df = (
    pd.DataFrame({
        "name": ["George", "Polly", "Golly", "Dolly"],
        "age": [3, 4, 13, 44]
    })
)
df["decade"] = (df["age"] / 10).astype(int) * 10
decade_counts = (
    df
    .groupby("decade")
    ["name"]
    .agg("count")
)
print(decade_counts)

>>>> decade
     0     2
     10    1
     40    1

这无可避免——我们必须在三个赋值表达式中完成查询。为了将其缩减为两个表达式,我们本可以使用不常见的[.assign()](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.assign.html)操作符来代替df["decade"] = ...操作,但仅此而已!这可能在这里看起来不是大问题,但当你发现自己需要七、八、九个连续的赋值表达式来完成任务时,情况可能会变得有点难以阅读和维护。

Polars中,这个查询可以被干净地写成一个表达式:

import polars as pl
decade_counts = (
    pl.DataFrame({
        "name": ["George", "Polly", "Golly", "Dolly"],
        "age": [3, 4, 13, 44]
    })
    .with_columns([
        ((pl.col("age") / 10).cast(pl.Int32) * 10).alias("decade")
    ])
    .groupby("decade")
    .agg(
        pl.col("name").count().alias("count")
    )
)
print(decade_counts)
>>>> ┌────────┬───────┐
     │ decade ┆ count │
     │ ------   │
     │ i32    ┆ u32   │
     ╞════════╪═══════╡
     │ 02     │
     │ 101     │
     │ 401     │
     └────────┴───────┘

真的很顺畅。

你可能会读到这些内容后,心里想着“为什么我还要在一个表达式中做所有事情呢?”。确实,也许你不需要。毕竟,许多数据管道使用中间查询,将中间结果保存到表中,并查询这些中间表以获得最终结果,甚至用于监控数据质量。

但像 SQL、Spark 或其他非Pandas的数据处理语言一样,Polars给予你 100%的灵活性来在你希望的地方拆分查询,以最大化可读性,而Pandas则强制你根据其 API 的限制拆分查询。这不仅对代码可读性是一个巨大的好处,而且对开发的便利性也是如此!

更进一步,作为额外的好处,如果你使用Polars的惰性 API,你可以在任何地方将查询拆分为任意多的部分,而整个过程最终会在后台被优化成一个查询。

结论

我在这篇文章中讨论的只是Polars相对于Pandas的优越性的一个缩影;Polars中仍有许多功能继承自 SQL、Spark 和其他数据处理语言(例如[pipe()](https://pola-rs.github.io/polars/py-polars/html/reference/dataframe/api/polars.DataFrame.pipe.html#polars.DataFrame.pipe)[when()](https://pola-rs.github.io/polars/py-polars/html/reference/expressions/api/polars.when.html#polars.when)[filter()](https://pola-rs.github.io/polars/py-polars/html/reference/dataframe/api/polars.DataFrame.filter.html#polars.DataFrame.filter),仅举几例)。

尽管现在Polars是我在 Python 中进行数据处理和分析的首选库,我仍然使用Pandas来处理一些特定的用例,比如为报告和演示文稿中的数据框进行样式设置或与电子表格进行沟通。也就是说,我完全预计Polars会随着时间的推移逐步取代Pandas

接下来是什么?

开始使用新工具是困难的,尤其是当它是一个新的 dataframe 库时,这对我们作为数据科学家来说至关重要!我通过参加Liam Brannigan的 Udemy 课程“使用 Polars 进行数据分析”,并且强烈推荐这个课程——它涵盖了Polars的所有基础知识,并且让我过渡得非常顺利(我推荐这个课程并没有获得任何推荐奖金;我只是觉得它非常好!)。

作为另一种选择,尽管 Polars 通常被学习为 Pandas 的直接替代品,但由于两者之间语法差异较大,从 Pandas 过渡到 Polars 可能会有些奇怪。因为 Polars 更类似于 SQL 而不是 Pandas,所以通过将 Polars 与 SQL 进行比较来学习 Polars 可能是一条更简单的路径,这正是我在后面的文章中所做的,“Polars 查询的结构:Polars 与 SQL 的语法比较”

## Polars 查询的结构:Polars 与 SQL 的语法比较

从 Pandas 过渡到 Polars 的简单方法——先在 SQL 上停留一下。

towardsdatascience.com

致谢

特别感谢Liam BranniganPolars课程,没有它我不确定自己是否能顺利过渡到Polars。当然,也要感谢Richie VinkPolars的创始人!你不仅创建了一个很棒的库,还迅速回应了我在 LinkedIn 和 Github 上关于Polars的问题和评论——你不仅创造了一个了不起的工具,还建立了一个友好的社区。还有你,读者——感谢你的阅读;祝你数据处理愉快 😃

联系方式

喜欢你所读到的内容吗?随时与我联系:

联系我我的网站 | 预约电话

社交媒体LinkedIn | Twitter | Instagram | YouTube

支持请给我买杯咖啡!

查看我网站上的博客文章 我的网站

今年提升数据技能的 4 种小而强大的方法

原文:towardsdatascience.com/the-4-small-but-powerful-ways-to-improve-your-data-skills-this-year-290b06dc892a

通过掌握这四项技能提升你的数据能力

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

·发布在 Towards Data Science ·4 分钟阅读·2023 年 1 月 23 日

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

图片来源:Miguel A Amutio 通过 Unsplash

一位同事最近问如何在今年提升他们的数据技能。

数据技能可以有多种形式。从数据工程、分析到数据科学和可视化。然而,它们都有一个共同的目标:驱动业务价值。这意味着,与技能相关的问题的答案更多是价值驱动的,而不是技术性的。

所以,让我们探讨一下提升数据技能的四种方法。

1. 找到数据问题的根本原因

数据问题总是存在的。

无论是质量、基础设施还是性能。你的组织将面临许多数据问题。

拥有分析技能以准确定位数据生命周期中的问题是至关重要的。最近,一个组织的执行指标报告被延迟了。IT 团队因数据交付延迟而受到主要影响,而延迟是由于核心逻辑更改导致的。事情往往不像它们看起来的那样。因此,具备分析技能以提出正确的问题是至关重要的。

快速高效地找到数据问题的根本原因是一项重要技能。分析你组织的最新十大数据问题可以帮助你提升这项技能。一旦问题得到解决,人们通常会继续前进;如果你花时间去理解它,你可以在下一个问题上更有效。

投资时间学习分析问题、找到根本原因并提供修复步骤。

2. 用简单语言解释数据概念

如果你不能用简单的术语解释某事,你就不够理解它 — 理查德·费曼。

数据是一个复杂的话题,有许多聪明的人在解决挑战性问题。然而,只有少数人能用没有术语的方式向更广泛的业务解释概念。

成为一个好的讲故事者在数据行业中是一个被低估的技能。在我早期的日子里,我记得总是难以简明扼要地提供工作更新。实际上,很多人并不在乎细节;他们想要的是“TL;DR”版本。学会简化话题,使用类比,避免术语。如果信息没有传达,调整并重试。

好的讲故事能帮助你传达信息,让听众保持参与感并建立可信度。将你的工作提炼成简单的概念/类比并练习讲解。这就是提升你讲故事技能的方法。

简单地解释,甚至一个五岁的孩子也能理解。

3. 理解数据背后的业务目标

如果数据无法解决业务问题,那它就是无用的。

作为数据知识工作者,我们花费大量时间调整优先级。然而,关于这些活动如何推动整体进展的更大图景常常被忽视。

将你的日常工作与最终业务目标关联起来是一个被低估的技能。我们曾有一个分析师团队花了四周时间找到数据质量问题的根本原因,但最终发现解决方案对业务没有任何影响。虽然解决了某些无聊的技术挑战,但并没有改善客户体验或减轻任何业务风险。如果它不帮助业务目标,那为什么它重要?

牢记业务目标以避免上述情形是至关重要的。总是问“为什么”,直到你满意这确实是核心问题。如果不满意,再问一次“为什么”。

连接数据与业务之间的点;只有少数人在做这件事。

4. 战略思考

我们都花费太多时间进行应急处理。

应急处理让你成为一个出色的战术思考者。然而,你会失去战略目标和未来规划的思考过程。

解决数据问题的方法可能是快速但战术性的,或者是长期但战略性的。十有八九,我们会选择战术性的解决方案来止血;我自己也这么做过。但几个月后,未采取的战略解决方案会反噬回来。考虑你提议的结果的长期影响。否则,你只是扑灭了一个火焰,却点燃了未来更大的火焰。

无论你在组织中的资历如何,战略性思考都能给你带来优势。质疑你即将实施、展示、修复、书写等的内容是否会对业务和你的信誉产生长期影响。如果是的话,寻找替代方案。

成为一个战略思考者;这是一个稀有的技能。

结论

是的,你需要了解 SQL 和 Python 以及选择的可视化工具,但这些只是手段或支持者。技术领域不断发展,但这些软技能将始终适用。优先提升它们,以提升你的技能。

提升数据质量技能是投资回报最大之一,查看终极数据质量手册的免费副本,帮助你掌握这一技能并加入我的电子邮件订阅列表。

## 终极数据质量手册 - 免费!

介绍《终极数据质量手册:完善数据的最佳实践》。在当今数据驱动的世界里……

hanzalaqureshi.gumroad.com

如果你还没有订阅 Medium,可以考虑使用我的推荐链接订阅。它比 Netflix 更便宜,而且显然是更好的时间利用。 如果你使用我的链接,我会获得少量佣金,你则可以无限制访问 Medium 上的故事,双赢。

数据故事讲述中的 4D:将科学变为艺术

原文:towardsdatascience.com/the-4ds-in-data-storytelling-making-art-out-of-science-c4998ed7875e

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

图片来源:Melanie DezielUnsplash

是的,这远远超出了数据可视化的范围

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

·发表于 Towards Data Science ·14 分钟阅读·2023 年 10 月 3 日

数据无处不在。任何具备一定训练的人,如今借助 AI 的帮助,都可以从数据中生成一些科学洞察,并构建华丽的数据可视化。然而,解释和传达数字和图表背后的含义是一门艺术。当 ChatGPT 和生成型 AI 登上前台时,许多关于被 AI 取代的担忧浮现。通过明确的指令,AI 可以帮助我们生成代码、可视化,甚至构建包含有用洞察的高效模型,但它们在基于这些洞察编写引人入胜的可信且令人难忘的故事方面却力不从心。它们可以做科学,但艺术是人类独有的技能,至少目前是这样。

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

图片来源:Brett JordanUnsplash

根据受众的不同,这些数据故事在建立信任、促进合作或影响业务决策方面至关重要。没有故事讲述的数据科学家的工作仅仅是数字占卜。本文将分享一个4D 框架,帮助数据科学家破解数据故事讲述过程,提高数据洞察的效率和影响力,并在最后附上实际建议的额外部分。

定义

讲故事的第一步是定义故事。什么才是故事?虽然虚构作家可能会给出更全面的答案,但本质上,故事是传达一系列事件的叙述,包括背景设置、角色和情节。为了使故事有趣,它必须吸引人、引人入胜、娱乐性强或具有信息性。数据讲故事的过程始于定义一个故事,通过建立相关的背景引人注目的角色迷人的情节来保持观众的兴趣。

背景

当你从数据中发现有趣的结果时,你需要在传递发现之前为接收者设置一个相关的背景。故事的背景为后续的沟通建立了上下文。我们首先需要了解这次沟通的媒介是什么。是演示文稿还是书面报告?是深度技术会议还是高层结果评审?这将指导你的故事朝不同的方向发展。

然后,我们需要定义故事中的什么和为什么。背景是什么?所有参与沟通的人需要达成一致。问题是什么?我们为什么要进行这次沟通?

此外,这次沟通的行动点是什么?假设每个人都同意你的故事,下一步是什么?

在展开你的故事之前设置背景是至关重要的。这有助于你更有条理和高效地准备整个沟通,并帮助观众与你的故事产生共鸣,使他们与你处于同一页面上。

角色

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

照片由 Kenny Eliason 提供,来源于 Unsplash

角色是故事的灵魂。一个好的数据故事应该把你和观众都融入故事中。这里有两个方面:观众是谁,他们与你的关系是什么?不幸的是,没有一种灵丹妙药能让所有类型的观众与你和你的故事产生共鸣。不同的观众有不同的痛点,这使得有必要相应地调整你的信息。在准备故事时,问自己这些问题:

  • 观众是谁?他们在技术上强大还是以业务为驱动?他们的日程是否很忙?他们只关心总体概述,还是关注细节?他们如何从这次沟通中受益?

  • 他们与你的关系是什么?他们是需要你建立信任和信誉的新对象吗?还是你们已经建立了关系?你需要他们的合作,还是在给他们结果?你需要他们做出决定或采取行动吗?

这些问题将帮助你导航准备工作。请注意,有时你可能对观众做出了错误的假设。例如,你认为他们不关心细节,但实际上,他们对你的详细思路非常感兴趣。因此,与大家进行事前沟通以对齐期望是至关重要的。或者,你也可以准备备用幻灯片或证据,以防后续问题进入次轨道。

情节

情节是故事的脊梁。考虑传统的三幕剧结构,这是一个广泛使用的框架,用于组织和呈现文学、戏剧和电影中的叙述。这种结构将故事分为三个不同的幕,每一幕都有其独特的目的和事件顺序:

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

image by author

我们可以在讲述数据故事时使用相同的框架。在我们数据故事的第一幕中,我们可以简要介绍背景和环境,以确保每个人都了解背景。然后,在第二幕中,我们深入探讨数据和发现,介绍你的观众关心的挑战或试图解决的问题。在这里,你会转向沟通的重点。可能是识别出一个令人惊讶的趋势或一个重要的发现。立刻,你展示一个突破,解决这些挑战,也许发现一个隐藏的模式,重新塑造大家的视角。第三幕是关于解决方案的,我们提出解决方案和行动点。最后,花一点时间考虑我们数据故事的广泛影响,让观众带走难忘的收获或所需的行动。沟通的逻辑和流程将定义观众如何接收和反应这些信息。一个难忘的故事情节起伏跌宕,观众对这些故事产生共鸣,因为它们帮助他们解决他们面临的痛点。

展示

现在我们有了结构框架,我们需要搞清楚如何在骨架上构建内容。数据故事包含叙述和视觉支持,如幻灯片或图表。在本节中,我将主要关注如何选择与数据故事匹配的数据可视化,以及使用哪些工具来帮助更好地传达信息。

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

照片由Daria Nepriakhina 🇺🇦拍摄,来自Unsplash

不同的情节

在可视化类型中有许多选择来表达观点。根据情况,我们可以选择折线图、柱状图、散点图、饼图、表格或只是文本。以下是一些实际中的常见用例:

  • 折线图: 折线图有助于展示一个或多个变量随时间的变化,使其成为可视化数据趋势和模式的绝佳选择。折线图可以用来显示一个城市在一年的温度变化。这使观众可以轻松识别任何季节性模式或温度趋势。

  • 饼图: 饼图是展示数据集或变量组成部分或比例的有用工具。当你想强调不同组件的相对大小时,它们特别有效。例如,饼图常用于描绘家庭每月支出的分布,包括租金、公用事业费、食品杂货和娱乐等项目。这提供了资金分配的清晰视觉表示。

  • 条形图: 条形图非常适合用于比较类别之间的值。饼图也能显示比较,但我们可以在条形图中添加时间范围,将比较扩展到时间维度。例如,我们可以使用条形图显示过去五年不同产品的销售情况。此外,条形图也广泛用于显示数据分布的直方图。

  • 散点图: 散点图有助于分析两个连续变量之间的关系和相关性。它可以帮助识别数据中的模式、集群或异常值。例如,如果你想确定某一产品的销售额和价格变化是否存在相关性,散点图可以可视化这两个变量之间的关系。

  • 表格: 表格是呈现详细数据的有价值工具。当观众需要获取特定数据点时,它们尤其有效。需要注意的是,表格通常显示一个数据维度,要么是横截面数据,要么是时间序列数据。横截面表格展示跨主题的值比较,而时间序列表格展示一个主题在时间上的值比较。包含主题和时间范围的面板表格可能过于详细,不适合口头呈现,但在书面形式中常用作有力的支持证据或进行更深入的分析。虽然表格可以用于显示时间序列比较,但折线图通常更有效。因此,我们更多地使用表格来处理横截面数据。

  • 文本: 将文本视为可视化并不常见,但它可以是传达信息的最有效方式之一。它可以是一个突出关键统计数据的句子。像下面这样的文本信息可以立即吸引观众的注意。

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

image by author

可以在图表中添加文本以提供背景、注释或解释。这有助于更好地呈现信息。

不同的可视化工具:

我们可以使用许多可视化工具来生成理想的图表。我们有专业工具如 Tableau 和 PowerBI,流行的 Python 包如 Plotly,以及用户友好的 Excel。无论使用哪种工具,主要目的是正确有效地传达信息,而不是展示你能写出多么复杂的代码或生成多么华丽的图表。如果你对 Excel 更为熟悉,那就不要浪费时间去弄清楚 Python 的语法。

另一个要考虑的因素是你是否需要共享可视化以及如何共享。是静态图表或截图,还是需要与接收者进行互动?此外,这个图表是一次性展示,还是需要在数据更新时定期重新生成?如果需要重新生成图表,设置模板或编写模块化代码以节省未来的时间是有帮助的。

清理冗余

少即是多,除非你在撰写研究论文或纪录片,需要为几乎每一句话提供大量的证据。一旦你有了故事线和支持的可视化效果,下一步就是考虑如何去除不必要的细节,以便观众能更容易地跟随你的故事而不会分心。

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

图片来源:Prateek KatyalUnsplash

探索性 vs 解释性

虽然可能需要几周的时间来发现洞察或创建模型,但展示整个思考过程是不必要的,除非观众特别要求。专注于观众最关心的内容。以商业为导向的观众关心你的发现如何有利于他们的业务 KPI,而技术观众关心你为什么以这种方式处理问题,以及你的模型表现如何。根据时间和格式,根据观众的需求调整故事的长度。

去除多余内容

对于特定的幻灯片或图表,考虑去除冗余内容。思考主要信息是什么,哪些是多余的细节。当你展示的信息较少时,观众更容易把握主要信息。爱德华·塔夫提(Dr. Edward Tufte)提出了一个公式来量化图表中的冗余:

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

图片由作者提供

理想的数据墨水比率应为 1,即每一滴墨水都应支持你所讲述的故事。否则,你需要去除所谓的 图表垃圾。常见的图表垃圾包括:

  • 网格线,你可以标注所选的数据点;

  • 冗余的坐标轴,尤其是当数据点已被标记时;

  • 图表区域中的不必要的标注或标记;

  • 分散注意力的图像或图标。

这是简化前后的示例:

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

作者提供的图片

请注意,简化并不等于去除重复。多次强调主要信息以加深观众的印象是必要的。此外,记住你的观众可能不如你对数据熟悉,因此你可能需要通过重复来更多地构建和强调背景。

直接

尽管观众的注意力最终不在你的控制之下,但有一些好的实践可以帮助抓住观众的注意力,从而帮助你正确高效地传达信息。这样,你更有可能影响商业决策,获得未来的支持,建立信任和信誉等。一般来说,你可以消除干扰突出主要信息调整和结构化交付来帮助引导观众的注意力。**我们在上一部分讨论了通过简化来消除干扰。让我们在这一部分关注后两者。

突出主要信息

在构建数据故事时,我们有主要信息和支持证据或比较,以帮助更好地表达观点。在这种情况下,我们需要突出主要信息,以便观众不会在信息的海洋中迷失。当确定主要信息时,从你发现的内容开始,然后问自己哪些发现会引起观众的兴趣,你希望观众如何使用这些信息。通过消除观众可能已经同意的点来关注影响。之后,我们可以通过预注意属性来引导观众的注意力。这些属性包括:

  • 大小。增加主要信息或关键统计数据的大小;

  • 颜色。注意不要在一页或一张幻灯片上使用太多颜色。保持相同的颜色调色板。有时,调整透明度也会有效。我们通常不需要彩虹;

  • 通过使用不同的字体、加粗、斜体或下划线来突出显示;

  • 添加特殊符号,如箭头或文本注释以帮助解释;

  • 如果你使用 PowerPoint 进行演示,请使用动画逐步展示你的故事;

  • 根据情境,我们可以添加互动图表,以根据观众的反馈和评论显示不同的信息。

调整和结构化交付

你已经弄清楚了要向观众展示的内容。现在是时候找出一个结构,以逻辑顺序传达信息,让观众容易接受。我们需要考虑水平流程垂直流程

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

Maksym Tymchyk 🇺🇦Unsplash 上的照片

我们在“定义”部分提到的三幕结构帮助我们将故事的逻辑横向流动逐步展示。常见的叙事流程有:

  • 按时间顺序: 按事件的时间顺序展示,并连接其影响;

  • 从大到小: 首先展示高层次的大图,然后再将其拆解为更多的细节,并提供更多的支持证据;

  • 概念性: 按逻辑顺序展示,比如 IF -> THEN -> So;

  • 按组别: 首先展示整体情况,然后再深入到每个感兴趣的组别;

通常,无论你最终遵循哪种横向流程,我们都应该始终保持“总结 — > 细节 — > 总结”的格式。开始时向观众提供一个大纲,在过程中与观众的痛点相连接,并以行动点结束。采取行动是数据讲故事的影响所在。

保持故事中的合理纵向流动也至关重要。幻灯片中的纵向流动是在标题和数据视觉准确突出相同观点的不同细节层次时实现的。在商业演示中,确保每张幻灯片传达清晰的信息,数据和视觉效果支持幻灯片标题,并清晰展示见解。

附加部分

现在你已经学习了一个实用的框架来构建故事,那么在沟通过程中有哪些良好的做法可以帮助你更好地传达信息呢?以下是一些有用的建议:

保持自信

你可能没有观众那么有经验或技术高超,但记住,没有人比你自己更了解你的工作。你是你数据故事唯一的专家。

征求反馈

在沟通之前、期间和之后征求反馈。你可以事先了解观众的期望。然后,和其他人练习,询问他们是否理解了你希望传达的信息。在演讲过程中,设置一定的中断点,询问观众是否有任何问题,是否都跟上了。演讲结束后,跟进行动点、演讲中未回答的问题,并询问观众对未来沟通的反馈。

观察和练习

观察他人在不同场景中如何传达信息。观察经验丰富的同事有效地展示技术发现,经理如何说服利益相关者延长项目时间,或产品负责人如何在发布日挑选产品特性。这不仅限于你的工作环境,也可以来自日常生活,比如当你被销售人员接触,或者观看最新的苹果开发者大会(WWDC)等。

最重要的是,不要忘记总结学习内容并付诸实践。与自己或他人进行彩排,了解整个故事的呈现时间,以及你是否通过这个故事讲解得清楚。

练习向非技术观众进行技术演讲

数据科学家向非技术观众展示黑箱模型预测是很常见的。在讲述故事之前,弄清楚他们需要什么。平衡技术证据与令人兴奋的结果。为了向非技术观众解释必要的技术术语,我发现保持高层次的讲解、给出相关的例子并始终与结果联系起来是有益的。不要忘记在继续之前检查你的观众是否理解了内容。

注意时间

尊重观众的时间非常重要,要将你的数据故事限制在约定的时间内。为了准确知道需要包含多少信息,你应该提前进行排练。然而,演讲过程中总会发生意外,比如话题偏离或要求更多细节。在这种情况下,除了始终在结尾留出缓冲时间外,你还需要在观众要求过多细节或话题偏离时保持警觉。你可以说:“为了节省时间,我们可以离线讨论这个问题”或“由于我们只有 X 分钟了,让我们转到下一个话题。”你还可以请一个时间记录员帮助你跟踪时间,同时专注于内容。你绝不希望因为时间不足而不得不缩短故事。

我最近读了一本由摩根·豪瑟尔(Morgan Housel)所著的《财富心理学》的书。在其中一章,摩根说:

但故事无疑是经济中最强大的力量。它们是使经济的有形部分运转的燃料,或者是限制我们能力的刹车。

如果你考虑一下股市的波动性以及经济预期如何通过自我实现的预言实际影响未来经济,你会明白当每个人都相信一个故事时,这个故事的力量有多大。数据让我们更可信,但故事让我们更难忘。 我们的数据科学家需要让观众记住我们,信任我们,并且相信我们,这样他们才会愿意采取行动。数据讲故事超越了科学,它是一种艺术。然而,这并不意味着我们不能通过观察和实践来提高这一技能。尝试使用 4D 框架**“定义、展示、简化和引导”**来进行下次沟通。不要忘记在下方评论你在使用这个框架时看到的不同之处或你对数据讲故事的其他建议。

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

图片由埃蒂安·吉拉尔代拍摄,来源于Unsplash

感谢阅读。如果你喜欢这篇文章,请不要忘记:

参考资料:

[1] 用数据讲故事:面向商业专业人士的数据可视化指南 作者:Cole Nussbaumer Knaflic

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值