如何使用 Python 熊猫和 tabula-py 从 PDF 中提取表格
数据收集
一个从 PDF 中提取重复表格的快捷脚本
这篇教程是我上一篇文章的改进,在那篇文章中,我在没有 Python pandas
的情况下提取了多个表。在本教程中,我将使用与我上一篇文章中相同的 PDF 文件,不同之处在于我使用 Python pandas
处理提取的表格。
本教程的代码可以从我的 Github 库下载。
几乎所有被分析的 PDF 文件的页面都具有以下结构:
作者图片
在页面的右上角有意大利地区的名称,而在页面的右下角有一个表格。
作者图片
我想提取所有页面的区域名称和表格。我需要提取两个表格的边界框。测量边距的完整程序在我的上一篇文章的定义边距一节中有说明。
该脚本实现了以下步骤:
- 定义边界框,它通过一个如下形状的列表来表示:
[top,left,bottom,width]
。边界框内的数据用厘米表示。它们必须转换成 PDF 点,因为tabula-py
需要这种格式。我们设定转换因子fc = 28.28
。 - 使用
read_pdf()
功能提取数据 - 将数据保存到
pandas
数据帧。
在这个例子中,我们扫描 pdf 两次:第一次提取地区名称,第二次提取表格。因此,我们需要定义两个边界框。
提取区域名称
首先,我定义边界框来提取区域:
box = [1.5, 22,3.8,26.741]
fc = 28.28
for i in range(0, len(box)):
box[i] *= fc
然后,导入tabula-py
库,我们定义必须从中提取信息的页面列表,以及文件名。
import tabula as tbpages = [3,5,6,8,9,10,12,14,16,18,22,24,26,28,30,32,34,36,38,40]
file = “source/Bolletino-sorveglianza-integrata-COVID-19_17-marzo-2020_appendix.pdf”
现在我可以从 pdf 中读取地区列表。我使用了read_pdf()
函数,我们将输出格式设置为json
。
regions_raw = tb.read_pdf(file, pages=pages,area=[box],output_format="json")
我注意到生成的输出非常复杂。然而,通用结构在位置regions_raw[i]['data'][0][0]['text']
包含第 I 个区域的区域名称。通过循环进入region_raw
列表,我建立了一个包含所有地区的列表。
regions = []
for i in range(0,len(regions_raw)):
regions.append(regions_raw[i]['data'][0][0]['text'])
提取第一页的表格(皮德蒙特地区)
我定义了边界框,我们将转换因子fc
的每个值相乘。为了理解这个机制是如何工作的,首先,我提取第一页的表格,然后我们推广到所有的页面。在此示例中,第一页对应于第 3 页。
box = [8,10,25,26]
for i in range(0, len(box)):
box[i] *= fc
现在我可以看 pdf 了。在这种情况下,我将output_format
设置为DataFrame
。结果存储在tl
中,这是一个列表。我可以简单地用tl[0]
把它转换成数据帧。
page = 3
tl = tb.read_pdf(file, pages=page,area=[box],output_format="dataframe", stream=True)
df = tl[0]
df.head()
作者图片
我注意到列名是错误的。另外,前三排都是错的。出于这个原因,我可以使用 dataframe 函数rename()
来重命名列名。
df.rename(columns={ df.columns[0]: "Fascia d'età" , df.columns[1]: "Casi"}, inplace = True)
df.head()
作者图片
现在我可以使用dropna()
函数删除前两行。
df = df.dropna()
df.head()
作者图片
我可以通过选择不包含该值的所有行来删除新的第一行。
df = df[df["Fascia d'età"] != "Fascia d'età"]
df.head(8)
作者图片
现在我向df
添加一个新列,称为Regione
,它包含地区名称。我扫描pages
列表来提取当前区域的索引。
region_column = []
for i in range(0, len(df)):
index = pages.index(page)
region_column.append(regions[index])df['Regione'] = region_column
df.head()
作者图片
提取所有页面
现在我可以概括前面的代码来提取所有页面的表格。首先,我构建了一个空的DataFrame
,它将包含所有区域的值。我将使用pd.concat()
函数连接所有页面的所有表格。我扫描了包含在pages
列表中的所有页面。
import pandas as pd
df = pd.DataFrame()
for page in pages:
index = pages.index(page)
region = regions[index]
print(region)
tl = tb.read_pdf(file, pages=page,area=[box],output_format="dataframe", stream=True)
dft = tl[0]
dft.rename(columns={ dft.columns[0]: "Fascia d'età", dft.columns[1]: "Casi"}, inplace = True)
region_column = []
for i in range(0, len(dft)):
region_column.append(region)
dft['Regione'] = region_column
df = pd.concat([df, dft])
与前面的情况类似,我丢弃了所有错误的记录。
df.dropna(inplace=True)
df = df[df["Fascia d'età"] != "Fascia d'età"]
将结果保存到 CSV
现在,我可以将结果保存为 csv 文件。
df.to_csv('output.csv')
摘要
在本教程中,我演示了如何将多个 PDF 表格转换成一个单独的pandas
DataFrame
并将其导出为一个 CSV 文件。
该过程包括三个步骤:定义边界框,通过tabula-py
库提取表格,并将它们导出到一个 CSV 文件。
如果你想了解我的研究和其他活动的最新情况,你可以在 Twitter 、 Youtube 和 Github 上关注我。
相关文章
https://medium.com/analytics-vidhya/how-to-extract-multiple-tables-from-a-pdf-through-python-and-tabula-py-6f642a9ee673 https://alod83.medium.com/how-to-extract-data-from-a-search-engine-through-python-and-selenium-35dfe6b20db
新到中?您可以每月订阅几美元,并解锁无限的文章— 单击此处。
如何使用 Tesseract OCR 引擎和 Python 从图像中提取文本
从 100 多种语言中选择
W 帽子是宇宙魔方?这是一个开源的 OCR(光学字符识别)引擎,可以识别超过 100 种支持 Unicode 的语言。此外,它可以被训练识别其他语言。OCR 引擎可以通过数字化文档而不是手动键入文档内容来节省时间。在这篇文章中,您将学习如何使用 Tesseract OCR 引擎和 Python 从图像中提取文本。
设置
对于设置,我将使用 Tesseract OCR 引擎、 Python 和 macOS。首先,我们需要安装宇宙魔方 OCR 引擎。在终端中键入以下命令。
brew install tesseract
您可以使用以下命令来查看您的计算机上运行的是哪个版本的 Tesseract。
tesseract --version
使用以下命令列出 Tesseract OCR 引擎的可用语言。
tesseract --list-langs
在这种情况下,我有三种语言。
eng #English
osd #Special data file for orientation and script detection
snum #Serial number identification
如果你想下载额外的语言,你可以从这里得到。我将下载孟加拉语的列车数据。一旦下载到您的机器上,您需要将该文件移动到以下文件夹中。
/usr/local/Cellar/tesseract/4.1.1/share/tessdata/
现在从这里安装 Python,如果你还没有的话。之后,我们将安装 Python-tesseract ,这是 Tesseract OCR 引擎的包装器。还有,我们需要安装一个 Python 映像库枕头。在终端中键入以下命令。
pip install pytesseract
pip install Pillow
出于演示的目的,我创建了一个 GitHub 库,你可以从这里获得。在存储库中,我包含了两张英语和孟加拉语的测试图片。此外,我有一个名为main.py
的 Python 脚本。让我们来分解一下main.py
文件中的个人贫困人口。
main.py
这里我创建了一个方法process_image
,它将图像名称和语言代码作为参数。在方法内部,我使用了一个 pytesseract 方法image_to_string
,它以字符串形式从 Tesseract OCR 返回未修改的输出。此外,我还添加了两个助手方法。print_data
方法打印字符串输出,output_file
方法将字符串输出写入文本文件。下面,我得到了字符串输出。
test_eng.png 文件的输出。
_ The'quick brown fox' .
-jumps over the lazy:
dog.
test_ben.png 文件的输出。
পথের দেবতা প্রসন্ন হাসিয়া বলেন-মূর্খ বালক, পথ তো
আমার শেষ হয়নি তোমাদের গ্রামে, বাশের বনে, ঠ্যাঙাড়ে
'বীরু রায়ের বটতলায় কি ধলচিতের খেয়াঘাটের সীমানায়.
তোমাদের সোনাডাঙা মাঠ ছাড়িয়ে ইচ্ছামতী পার হয়ে
পদ্মফুলে ভরা মধূখালি বিলের পাশ কাটিয়া বেত্রবতীর
খেয়ায় পাড়ি দিয়ে, পথ আমার চলে গেল সামনে, সামনে,
'শুধুই সামনে...দেশ ছেড়ে দেশান্তরের দিকে, সূর্যোদয় ছেড়ে
সূর্যাস্তের দিকে, জানার গন্ডী এড়িয়ে অপরিচয়ের উদ্দেশে.
包裹
文档数字化使机器可读。例如,我们可以转换重要的扫描文档来执行搜索、翻译和文字处理。另外,我们可以用它来自动接收和识别车牌。OCR 的潜力是无穷的。
查看我关于 Python 的其他帖子。
如何使用 Python 和 Google Cloud Vision API 从 pdf 中提取文本
今年冬天,我发现韦尔斯利学院(Wellesley College)有超过 100 年的课程目录、招生指南和年度公告。我立即被从这些文档中提取的迷人数据的潜力所吸引,但第一步必须将它们转换为文本,因为没有多少分析方法可以在旧的棕色 pdf 扫描上运行。
因此,我开始寻找一种方法,既能快速有效地对大量 PDF 文件运行 OCR,又能尽可能保持格式和准确性。在尝试了几种方法之后,我发现使用 Google Cloud Vision API 产生了迄今为止我尝试过的所有公开可用的 OCR 工具中最好的结果。
由于我找不到任何单一的、全面的指南来使用这个神奇的工具运行简单的 OCR 应用程序,所以我决定写这一篇,这样任何具有一点编程知识的人都可以使用这个奇妙的工具。
按照这些说明你需要什么
- 在您的计算机上安装 Python 3 和 pip
- 用于编辑代码的文本编辑器—我使用 Visual Studio 代码
- 一种在你的电脑上运行 Python 程序的方法。
- 您还需要一种支付方式来进入您的 Google Cloud 帐户,尽管您不需要花任何钱来完成本教程。借记卡、信用卡或谷歌钱包账户都可以。
设置您的 Google Cloud Vision 帐户
为了使用谷歌云视觉运行光学字符识别,您首先需要有一个谷歌帐户。这将允许您登录谷歌的云服务仪表板。从该控制面板可以访问的众多服务之一是文件存储,我们将使用它来托管我们将转换为文本的 PDF 文件。
因为我们将通过 Cloud Vision API 访问的高级机器学习算法在云中运行,所以我们需要将我们的 PDF 上传到 Google 托管的文件“桶”中,以便可以访问它。
本教程将向您展示如何将最终结果(包含 PDF 中所有文本的文本文件)写入您计算机上的某个位置。
- 如果您尚未登录谷歌帐户,请前往google.com登录或创建一个帐户。我假设我的读者在这一步不需要进一步的指导。
- 一旦你登录到你的谷歌账户,跟随这个链接到你的谷歌云仪表板。如果要求您接受服务条款,请接受。你应该登陆一个看起来像这样的页面。
谷歌云平台仪表板
3.点击图标右边的下拉菜单,上面写着谷歌云平台。我的写着“OCR 测试”,这是我当前打开的项目的名称,但你的会说一些不同的东西。将弹出一个窗口,列出最近的项目,在右上角有一个“新建项目”按钮。单击按钮创建一个新项目。给你的项目起一个名字,这将帮助你记住你使用它的目的。你不需要担心任何其他领域。点击“创建”。创建项目后,请确保再次打开窗口并从最近的项目列表中选择它。
4.现在,您应该可以看到新创建项目的项目信息、API 和其他信息面板,如上面的屏幕截图所示。在左下方的“入门”面板中,单击“浏览并启用 API”。这将允许您选择您希望能够用于这个项目的 Google APIs。
API 和服务
5.在屏幕顶部的菜单栏中,单击“启用 API 和服务”。这将把你带到 API 库。搜索“云视觉 API”并选择它。
6.单击“启用”使 API 可用于您的项目。这将带您进入 Cloud Vision API 的概述页面。在屏幕的右上角,单击“创建凭据”。
7.从“您正在使用哪个 API?”下的下拉菜单中选择“Cloud Vision API”在“您是否计划将此 API 与 App Engine 或 Computer Engine 一起使用”下,选择“不,我不使用它们”。单击蓝色的“我需要什么凭证?”按钮。
8.现在,您将能够创建一个密钥,以便在尝试连接到 Cloud Vision API 时验证自己的身份。选择一个您将记住的服务帐户名称,并将您的角色设置为“所有者”。将密钥类型设置为 JSON。单击继续。您现在可以下载包含您的凭证的 JSON 文件了。
您现在在 Google 云平台上有一个项目,它将能够使用 Cloud Vision API。下一步是上传您的 PDF 文档,以便将其存储在云中。然后,您可以编写脚本将其转换为文本。
9.如果尚未打开,请单击谷歌云平台左侧的导航菜单,向下滚动直到看到“存储”。点击它——这将打开一个下拉菜单。从下拉菜单中选择“浏览器”。此时,如果您尚未启用计费,则需要启用计费。如果你有 Google Pay,你可以在这里使用它——否则,你需要输入外部支付信息。这将取决于你如何支付,所以我不会给指示。完成后,您应该会看到一个带有“创建一个桶”选项的对话框。
10.为您的铲斗取一个唯一的名称。这是您之前创建的项目中的一个存储库。将数据存储位置设置为“多区域”,并将数据的默认存储类别设置为“标准”。点击“创建”。
现在您已经设置了一个存储桶,您可以在其中上传文件,以便它们可以被为当前项目启用的任何 API 访问。您可以上传您想要转录的 PDF 文件,方法是将其拖放到您电脑上的任何位置。
您已经准备好编写一个程序,通过连接到 Google Cloud services 并提供您之前下载的密钥,可以访问这个文件和 Cloud Vision API。
安装必要的库
现在你已经在 Google Cloud 上设置好了你需要的一切,我们将在你的电脑上安装必要的工具,并使用它们从 PDF 文件中提取文本。
首先,您可能需要进行一些安装。打开终端,导航到保存您编写的 python 脚本的文件夹。输入以下命令。
pip install google-cloud-vision
pip install google-cloud-storage
它们使用 pip 来安装两个 Python 库,分别带有与 Google Cloud Vision 和云存储 API 交互的工具。接下来,运行
pip freeze
这将检查您是否已经安装了所有应该安装的东西。您应该有以下版本,尽管大多数可能是更新的版本。
google-api-core==1.14.3google-api-python-client==1.7.11google-auth==1.6.3google-auth-httplib2==0.0.3google-cloud==0.34.0google-cloud-core==1.0.3google-cloud-storage==1.20.0google-cloud-vision==0.39.0google-resumable-media==0.4.1googleapis-common-protos==1.6.0google-api-core==1.14.3
如果你没有它们中的任何一个,使用 pip 来安装缺失的那些。
最后,您需要设置您的 Google 应用程序凭证——也就是说,您需要注册您之前下载的 json 密钥的保存位置,以便当您使用 Google 云服务运行程序时,您的计算机可以验证自己属于您的 Google 帐户。
你可以在这里找到关于如何在任何平台上这样做的极好的指导。一旦你这样做了,你将能够从命令行运行使用谷歌云服务的程序。
写剧本
现在我们开始有趣的部分——编写一个脚本,在我们选择的 PDF 上实际执行光学字符识别!创建一个新的 Python 文件,并使用您喜欢的代码编辑器打开它。我将解释我使用的脚本的每一部分,以便您在替换您的信息时能够理解它。你也可以在我的 Github 上找到完整的脚本这里。在下载之前,试着按照每一步进行修改。
- 第一步是导入我们需要的库。
我们需要导入 json,以便处理 Cloud Vision 的输出。re 是一个库,它允许我们使用正则表达式来匹配字符串中的特定模式。
来自 google.cloud 的愿景和存储将允许我们使用 google Cloud 愿景和 Google 云存储 API。
2.下一步是使用 Google Cloud Vision API 编写一个函数来检测 PDF 文件中所有可读文本的位置。请务必阅读该函数中的注释,以便了解每个步骤在做什么。
函数来注释 PDF 文件中的文本
除了解释这个函数的注释之外,还有一些需要注意的地方。你可能会期望,当我们在一个文档上运行谷歌令人惊叹的 OCR 工具时,我们将得到一个文本文件作为回报。实际上,这个函数将只输出一个 json 文件——或者几个,这取决于 PDF 的大小——包含关于文件中文本位置的信息。实际上,获取文本以便我们能够阅读是下一步。
这个函数有两个输入。首先,gcs_source_uri 是您的 PDF 文件在 Google 云存储中的位置。第二个,gcs_destination_uri 是您希望包含文件注释的 json 文件在 Google 云存储中的位置。
URI 是谷歌云存储中文件位置的术语。你可以把它想象成谷歌云存储中的一个 URL,或者像你电脑上的一个路径。它描述了在你保存在 google cloud 上的文件的层次结构中,在哪里可以找到一个特定的文件。要查找文件的 URI,您可以双击它来查看它的详细信息,并从您将打开的数据表中复制 URI。
为了生成注释,您将在 Python 文件的底部编写一行代码,调用 async_detect_document 函数。我的看起来像这样。
第一个 URI 是存储在我的 google 云存储桶中的一个 PDF 文档的路径,我想从中读取内容。第二个链接指向一个文件夹,我在其中保存了我所有的文档注释。
3.现在我们已经注释了我们的 PDF,我们终于可以使用 Cloud Vision 到每个有文本的位置,并将其读入一个文本文件!我的代码如下。还是那句话,一定要看评论。
这个函数只有一个参数:我们存储注释的位置的 URI。它将把转录的结果输出到当前活动目录中的文本文件中,并在终端中打印出来。
我是这样调用它的,使用和以前一样的目录。
恭喜你!如果一切顺利,您现在应该处于一个文本文件的位置,该文件包含 PDF 中所有机器可读文本的逐行转录。你可能会惊讶有多少可以阅读——它甚至可以在一些笔迹上工作。
下面是我的一些结果的横向比较。这是我从 1889 年韦尔斯利学院档案中提取的课程目录中的一页。尽管我使用了一个完全未经预处理的 PDF 文件作为测试的输入文件,但测试结果非常准确,即使是人名和外来词也是如此。
在我的下一篇文章中,我将演示一些预处理旧文本文件的方法,以便进一步提高准确性,敬请关注。如果你有任何麻烦或者只是想聊天,请联系我——我喜欢聊天!
如何使用人工智能生成“corridos tumbados”失败
“Corridos tumbados”和机器学习
俄罗斯摄影师在 Unsplash 上拍摄的照片
Eliud Gilberto Rodríguez Martínez、Hugo Francisco Cano Beyliss、Martin joséVega Noriega。
索诺拉大学,埃莫西约,索诺拉,
mjvnor@outlook.com,slabxcobra@gmail.com,eliud.giroma@gmail.com。
**摘要。**该项目旨在使用神经网络为 Julio Waissman Vilanova 教授的神经网络课程生成“corridos tumbados”。我们承担了使用不同方法生成歌曲的任务,使用不同的神经网络,如 RNNs、WaveNets 和合成器。在这里,我们解释了为什么我们能够执行这项任务,但不便之处在于,由于执行这项任务时出现的各种问题,如数据集有大量噪声(他们有不止一台仪器),以及没有处理大型网络所需的设备,结果不如预期。
关键词: corridos tumbados,神经网络,lstm,wavenet,n-synth,keras
为什么我们决定做这个项目
我们决定做这个项目是因为 Julio Waissman Vilanova 教授在神经网络课上分配给我们的一项任务,这项任务是关于使用递归神经网络(RNN)创作音乐,我们决定借此机会创作主要在墨西哥北部非常流行的“corridos tumbados”。
什么是 corrido tumbado?
Corridos tumbados 是墨西哥地区的变体,是传统 corrido 的变体,但包含了 hip-hop 和 trap,他们的歌手通常穿着 trap 的特色服装。你绝对应该听听。
我们所做的…
我们尝试的第一件事
在我们的例子中,没有 corridos tumbados 的 MIDIs 数据集,所以我们开始创建一个。首先,我们想用这些类型歌曲的所有乐器来生成 corridos tumbados,但我们意识到这太费时且难以实现,所以我们使用了“SpleeterGui”软件来获取我们手头的 corridos tumbados 的 requintos。为什么要求?requinto 是 corridos tumbados 的主要乐器,没有这个乐器就不是 corridos tumbados。我们使用谷歌提供的一个名为“Onsets and Frames”的人工智能,这是一个带有钢琴和鼓模型的自动音乐转录框架,它将帮助我们将安魂曲转换为 MIDIs。
- Colab:
我们生成了大约 241 个 MIDI 格式的 corridos tumbados,在一个名为“music21”的库的帮助下,我们从中提取音符和和弦,并将它们用作我们网络的输入。然后,我们用一个长短期记忆递归神经网络(LSTM)来产生谎言运行,这是结果:
- Colab:
我们失败的原因
我们失败了,因为我们的数据集不够好,它包含了太多的噪音和除了 requintos 以外的其他乐器。我们还需要做更多的 epoch 来更好地生成歌曲,这是一个问题,因为如果我们给它更多的 epoch,它需要超过 15 个小时才能完成,当你超过这个时间时,google colab 不再允许你使用他们的 gpu。
我们尝试的第二件事
我们尝试的第二件事是使用一个名为 N-SYNTH 的 magenta 项目,这是一个神经合成器。N-SYNTH 合成器操纵数据而不是操纵声音;分析音频文件,了解数据之间的关系,然后从头开始制作新的音频文件。要听质量接近现实生活的音乐,需要 48000 个样本。这个合成器基于上面的几千个样本,以及经过训练的音频,一次生成一个样本,听起来有点差,因为使用了 16,000 个样本,而不是 48,000 个样本,这仍然是相当多的。
-
结果:
-
Colab:
该算法用高音声音训练,它用来自一千种不同乐器的 30 万个音符训练。当您组合两种完全不同的声音时,合成器会更加复杂,因为它必须处理更多的声音,而不仅仅是单个音符,除了合成器在高音方面受过训练,所以它会尝试将任何其他频率解释为高音,这意味着,除了通过使用 16,000 个样本来创建低质量的旋律之外,由于可能缺乏乐器,并希望将频率提高,从而产生看起来像恐怖电影一样的音频畸变。
我们尝试的最后一件事
我们最后尝试使用的是波网。使用 WaveNet 架构的简化版本,而不添加剩余连接或跳过连接,因为这些层的作用是提高快速收敛,并且 WaveNet 将原始音频波作为输入,但是在我们的情况下,输入将是一组节点和和弦,因为我们正在生成音乐。
结果:
少年 H(最佳 5 名)| 50 岁
少年 H(最佳 5 名)| 500 纪元
完整数据集| 50 个纪元
完整数据集| 200 个历元
- Colab:
我们失败的原因
有几种可能性,但我们认为该模型未能提取歌曲的重要特征,这是因为该模型仅适用于一种类型的乐器,并且由于我们的数据集不如我们希望的那样干净,这引入了影响学习阶段的噪声,此外,该模型可能需要更多的时期来训练或更多的歌曲。
什么是 LSTM?
长短期记忆(LSTM)网络是一种递归神经网络,它扩展了递归神经网络的记忆,它们有短期记忆问题,所以它们只使用以前的信息,所以它们没有神经节点可用的所有上述信息的列表。由于它们特有的行为,它们被用于解决语音识别问题、手写识别、音乐创作等。递归神经网络无法解决的复杂问题。
什么是 Wavenet?
WaveNet 是一种称为深度卷积神经网络的神经网络,用于生成逐个样本的音频。该技术能够通过使用直接来自真实人类语音样本的经过训练的神经网络直接对波形进行建模来生成逼真的声音,例如人类语音。Wavenet 生成波形的能力允许对任何类型的音频进行建模,包括音乐。在 WaveNet 中,网络采样信号作为输入,并逐个采样输出进行合成。
什么是插值?
插值包括利用特定数据集的信息进行有根据的猜测。根据你手头的信息,这是一个“最佳猜测”。插值是我们经常使用的人工智能。把数据输入计算机,让它为我们做出有根据的猜测。理论上,内插法对于提取有关情况的数据和利用已知经验将知识扩展到不熟悉的领域也很有用。然而,这通常被称为外推。
结论
我们的结论是,我们必须努力改进我们的数据集,如果它更干净,我们第二次尝试的输出就会有很大的改进。除了 google colab 之外,我们还应该使用另一个系统来长时间运行模型。
Github 知识库
https://github.com/MJVNOR/DatasetMidiCorridosTumbadosAcustico
参考
霍桑,c .、埃尔森,e .、宋,j .、罗伯茨,a .、西蒙,I .、拉弗尔,c .、… &埃克,D. (2017)。双目标钢琴改编曲。arXiv 预印本 arXiv:1710.11153。
https://deepai.org/machine-learning-glossary-and-terms/interpolation https://hub.packtpub.com/what-is-lstm/
如何在 LinkedIn 上展示你的 GitHub 知识库
把你的工作放在聚光灯下!
Abhyuday Majhi 在 Unsplash 上拍摄的照片
你是一名数据科学家还是软件开发人员,想要在你的 LinkedIn 个人资料上突出你来自 GitHub 的最佳作品?如果是这样,请继续阅读。
步骤 1)在 GitHub 上设置社交媒体预览图片。
- 记住一句老话,“一图胜千言”,选择一个合适的图像来代表你的作品。 Unsplash 、 Pexels 和 Canva 是一些可免费使用的图片资源。
- 转到您的 repo 并点击设置。
- 进入社交预览部分,点击编辑上传图片。
- 对您想要的每个回购重复上述步骤。
作者图片
第 2 步)在你的 LinkedIn 个人资料中展示你的回购。
- 转到你的 LinkedIn 个人资料的特色部分(就在关于部分的下方)。
- 单击加号以显示不同类型的项目。
- 点击链接。
- 在添加链接弹出窗口中输入您的回购的 URL,然后点击添加。
- 输入您希望项目显示的标题和描述,然后点击保存。
- 同样,对您想要的每个回购重复上述步骤。
从我的错误中吸取教训!
当我最初在我的 LinkedIn 个人资料上展示我的项目时,我忘记了事先在 GitHub 上设置社交媒体预览图像。结果呢?我的 GitHub 档案头像显示在每个…单个…项目上。多么重复,无聊,没有信息!
当然,我想尽快解决这个问题,所以我赶紧回到我的 GitHub 并添加了社交媒体预览图片。我查看了我的 LinkedIn 个人资料,什么都没有!我刷新了网页,看到了…同样的旧的无聊头像,而不是我深思熟虑选择的完美代表我作品的图像。
英国广播公司 GIF
谢天谢地,有一个快速修复(谢谢,谷歌)!我需要清空 LinkedIn 上的图片缓存。
我上了 LinkedIn 帖子检查器 ,输入我的储存库的网址,点击检查。这清除了图像缓存,我的时髦的新图像显示在我的个人资料上!
现在你知道一个简单的方法来在你的 LinkedIn 个人资料上展示你的 GitHub repos!如果你觉得这有用,请分享,如果这对你有帮助,请随时告诉我。
如何从箱线图中获取精确值(Python)
从 matplotlib 盒图中提取数据集描述性统计数据的有效方法
来自 Unsplash
箱线图是一种可视化类型,用于显示数据集描述性统计数据的五位数集合:最小值和最大值(不包括异常值)、中值、第一(Q1)和第三(Q3)四分位数。在 Python 中,可以在各种数据可视化库中创建箱线图,包括最基本的库— matplotlib。
虽然箱线图的主要作用域是可视化关于数据集的统计信息,但是如果我们还需要提取并打印出这些统计信息的确切数字呢?在本文中,我们将讨论在 matplotlib 库中这样做的最简单的方法。
首先,让我们创建 3 个虚拟数据集,并在 matplotlib 中显示它们的箱线图。为了能够进一步提取必要的值,我们必须将plt.boxplot()
方法的结果赋给一个变量(bp
):
import matplotlib.pyplot as plt
import numpy as npnp.random.seed(1)
data_1 = np.random.normal(50, 30, 300)
data_2 = np.random.normal(100, 40, 300)
data_3 = np.random.normal(70, 10, 300)
data = [data_1, data_2, data_3]bp = plt.boxplot(data)
plt.show()
作者图片
产生的变量bp
是一个 Python 字典:
type(bp)**Output:** dict
以下关键字代表箱线图的主要元素:
bp.keys()**Output:** dict_keys(['whiskers', 'caps', 'boxes', 'medians', 'fliers', 'means'])
这是字典本身:
bp**Output:**
{'whiskers': [<matplotlib.lines.Line2D at 0x1eaf6131b50>,
<matplotlib.lines.Line2D at 0x1eaf6131eb0>,
<matplotlib.lines.Line2D at 0x1eaf61533a0>,
<matplotlib.lines.Line2D at 0x1eaf6153700>,
<matplotlib.lines.Line2D at 0x1eaf6162b80>,
<matplotlib.lines.Line2D at 0x1eaf6162ee0>],
'caps': [<matplotlib.lines.Line2D at 0x1eaf614a250>,
<matplotlib.lines.Line2D at 0x1eaf614a5b0>,
<matplotlib.lines.Line2D at 0x1eaf6153a60>,
<matplotlib.lines.Line2D at 0x1eaf6153dc0>,
<matplotlib.lines.Line2D at 0x1eaf616d280>,
<matplotlib.lines.Line2D at 0x1eaf616d5e0>],
'boxes': [<matplotlib.lines.Line2D at 0x1eaf61317f0>,
<matplotlib.lines.Line2D at 0x1eaf6153040>,
<matplotlib.lines.Line2D at 0x1eaf6162820>],
'medians': [<matplotlib.lines.Line2D at 0x1eaf614a910>,
<matplotlib.lines.Line2D at 0x1eaf6162160>,
<matplotlib.lines.Line2D at 0x1eaf616d940>],
'fliers': [<matplotlib.lines.Line2D at 0x1eaf614ac70>,
<matplotlib.lines.Line2D at 0x1eaf61624c0>,
<matplotlib.lines.Line2D at 0x1eaf616dca0>],
'means': []}
我们看到字典的值实际上是 matplotlib 对象的列表(特别是,matplotlib.lines.Line2D
)。总之,我们有 6 个须(每个箱线图 2 个),6 个帽(每个箱线图 2 个),然后是 3 个盒子,3 个中间值和 3 组异常值(传单),每个箱线图一个。奇怪的是means
有一个空列表作为它的值。这是因为,默认情况下,箱线图不显示样本平均值。为了在箱线图上显示平均值,我们必须通过可选参数showmeans
指定它:
bp = plt.boxplot(data, showmeans=True)
bp['means']**Output:** [<matplotlib.lines.Line2D at 0x1eaf6262790>,
<matplotlib.lines.Line2D at 0x1eaf627b340>,
<matplotlib.lines.Line2D at 0x1eaf6286e80>][<matplotlib.lines.Line2D at 0x1eaf6262790>,
<matplotlib.lines.Line2D at 0x1eaf627b340>,
<matplotlib.lines.Line2D at 0x1eaf6286e80>]
作者图片
现在,means
键的值是一个包含 3 个 matplotlib 对象的列表,而不是一个空列表。此外,在 3 个箱线图中的每一个上,我们都可以看到一个代表每个数据集平均值的新标记。
在提取准确的数字之前,让我们先来看看均值箱线图的主要元素(即字典的关键字),以及我们将要获得的描述性统计数据:
作者图片
现在,让我们使用get_ydata()
方法从字典的每个条目中提取初步的统计值,并记住上图,尝试理解它们的结构:
for key in bp:
print(f'{key}: {[item.get_ydata() for item in bp[key]]}\n')**Output:** whiskers: [array([ 32.27380667, -23.04513292]), array([ 70.59264691, 125.8497712 ]), array([75.68154245, -0.65215444]), array([131.88978143, 185.5131227 ]), array([63.74451462, 46.95092062]), array([76.33158616, 95.05980285])]
caps: [array([-23.04513292, -23.04513292]), array([125.8497712, 125.8497712]), array([-0.65215444, -0.65215444]), array([185.5131227, 185.5131227]), array([46.95092062, 46.95092062]), array([95.05980285, 95.05980285])]
boxes: [array([32.27380667, 32.27380667, 70.59264691, 70.59264691, 32.27380667]), array([ 75.68154245, 75.68154245, 131.88978143, 131.88978143,
75.68154245]), array([63.74451462, 63.74451462, 76.33158616, 76.33158616, 63.74451462])]
medians: [array([52.64528282, 52.64528282]), array([100.43803244, 100.43803244]), array([70.10978367, 70.10978367])]
fliers: [array([-33.79255]), array([-11.30137871, 221.23428449, 258.34410816]), array([ 42.09003593, 43.01638258, 39.4623562 , 103.21078756])]
means: [array([52.23199524]), array([102.26099759]), array([69.8400676])]
我们获得了字典中每个键的数组列表。我们实际想要获取的内容包括每个数据集的以下信息:
- 中间值,
- 意思是,
- 没有异常值的最小值,
- 没有异常值的最大值,
- 第一个四分之一 Q1,
- 第三个四分位数 Q3,
- 较低的异常值,
- 上层异常值。
看上面的图片,应该注意的是,我们可以从caps
或whiskers
中提取最小和最大值(我们将使用第一种方法),并且类似地,对于 Q1 和 Q3:从boxes
或whiskers
(同样,我们将使用第一种方法)。
中位数和平均值
从上面提取的原始值中,我们看到对于medians
,我们有 2 个相同项目的 3 个 n 数组,而对于means
— 3 个单项目 n 数组。对于中位数,每个 n 数组描述相应框中的中值线(从左到右),对于平均值-平均值的点。要获得每个箱线图的中值和平均值的精确值,我们必须选择相应 n 数组的第一项:
medians = [item.get_ydata()[0] for item in bp['medians']]
means = [item.get_ydata()[0] for item in bp['means']]
print(f'Medians: {medians}\n'
f'Means: {means}')**Output:** Medians: [52.64528282246805, 100.43803243566403, 70.10978366813102] Means: [52.23199524458482, 102.26099759095463, 69.84006759933192]
最有可能的是,我们可能不想要如此精确的结果。因此,在这里和其他地方,让我们考虑将数字四舍五入到小数点后第一位:
medians = [round(item.get_ydata()[0], 1) for item in bp['medians']]
means = [round(item.get_ydata()[0], 1) for item in bp['means']]
print(f'Medians: {medians}\n'
f'Means: {means}')**Output:** Medians: [52.6, 100.4, 70.1]
Means: [52.2, 102.3, 69.8]
最小值和最大值
对于最小值和最大值,我们可以使用从caps
中提取的 ndarrays。让我们仔细看看它们:
[item.get_ydata() for item in bp['caps']]**Output:** [array([-23.04513292, -23.04513292]),
array([125.8497712, 125.8497712]),
array([-0.65215444, -0.65215444]),
array([185.5131227, 185.5131227]),
array([46.95092062, 46.95092062]),
array([95.05980285, 95.05980285])]
两个相同项目的排列顺序如下:第一个(最左边的)箱线图最小,第一个箱线图最大,第二个箱线图最小,依此类推。因此,为了获得所有箱线图的最小值,我们必须使用列表切片来获得奇数 n 数组(并从每个数组中选择第一项),以获得最大值——偶数。同样在这种情况下,为了方便起见,我们将对第一个小数进行舍入:
minimums = [round(item.get_ydata()[0], 1) for item in bp['caps']][::2]
maximums = [round(item.get_ydata()[0], 1) for item in bp['caps']][1::2]
print(f'Minimums: {minimums}\n'
f'Maximums: {maximums}')**Output:** Minimums: [-23.0, -0.7, 47.0]
Maximums: [125.8, 185.5, 95.1]
Q1 和第三季度
让我们从boxes
中提取第一个和第三个四分位数。要提醒箱子的原始数据:
[item.get_ydata() for item in bp['boxes']]**Output:** [array([32.27380667, 32.27380667, 70.59264691, 70.59264691, 32.27380667]),
array([ 75.68154245, 75.68154245, 131.88978143, 131.88978143,
75.68154245]),
array([63.74451462, 63.74451462, 76.33158616, 76.33158616, 63.74451462])]
观察每个 ndarray(代表每个盒子)的图案,我们可以注意到盒图多边形(矩形)是从左边的最小值开始向右绘制的,最后,在最小值处再次闭合。实际上,我们这里需要的是从每个盒子中提取最小值以获得 Q1,并提取最大值以获得 Q3:
q1 = [round(min(item.get_ydata()), 1) for item in bp['boxes']]
q3 = [round(max(item.get_ydata()), 1) for item in bp['boxes']]
print(f'Q1: {q1}\n'
f'Q3: {q3}')**Output:** Q1: [32.3, 75.7, 63.7]
Q3: [70.6, 131.9, 76.3]
较低和较高异常值
我们可以在字典键fliers
中找到异常值:
[item.get_ydata() for item in bp['fliers']]**Output:** [array([-33.79255]),
array([-11.30137871, 221.23428449, 258.34410816]),
array([ 42.09003593, 43.01638258, 39.4623562 , 103.21078756])]
目前,它们按箱线图分组。如果我们希望有两个单独的列表来显示较低和较高的异常值,我们可以应用以下代码:
fliers = [item.get_ydata() for item in bp['fliers']]
lower_outliers = []
upper_outliers = []for i in range(len(fliers)):
lower_outliers_by_box = []
upper_outliers_by_box = []
for outlier in fliers[i]:
if outlier < q1[i]:
lower_outliers_by_box.append(round(outlier, 1))
else:
upper_outliers_by_box.append(round(outlier, 1))
lower_outliers.append(lower_outliers_by_box)
upper_outliers.append(upper_outliers_by_box)print(f'Lower outliers: {lower_outliers}\n'
f'Upper outliers: {upper_outliers}')**Output:** Lower outliers: [[-33.8], [-11.3], [42.1, 43.0, 39.5]]
Upper outliers: [[], [221.2, 258.3], [103.2]]
由于第一个箱线图没有任何异常值,我们为它获得了一个空列表。
现在,让我们以一种优雅的方式总结我们从每个数据集提取的所有描述性统计数据。尽管下面的代码看起来有点吓人,但每次我们使用一组新的类别(数据集)时,我们唯一需要更新的是用横向注释# to be updated
标记的两行代码(当然,我们必须删除用于创建数据集的特定于案例的部分)。可选地,我们可以考虑更新舍入结果(当前设置为第一个小数点的round()
方法实例):
# Gather all the previous code
import matplotlib.pyplot as plt
import numpy as np#--------------------------------------
# Creating datasets
np.random.seed(1)
data_1 = np.random.normal(50, 30, 300)
data_2 = np.random.normal(100, 40, 300)
data_3 = np.random.normal(70, 10, 300)
#--------------------------------------data = [data_1, data_2, data_3] # to be updatedbp = plt.boxplot(data, showmeans=True)medians = [round(item.get_ydata()[0], 1) for item in bp['medians']]
means = [round(item.get_ydata()[0], 1) for item in bp['means']]
minimums = [round(item.get_ydata()[0], 1) for item in bp['caps']][::2]
maximums = [round(item.get_ydata()[0], 1) for item in bp['caps']][1::2]
q1 = [round(min(item.get_ydata()), 1) for item in bp['boxes']]
q3 = [round(max(item.get_ydata()), 1) for item in bp['boxes']]
fliers = [item.get_ydata() for item in bp['fliers']]
lower_outliers = []
upper_outliers = []
for i in range(len(fliers)):
lower_outliers_by_box = []
upper_outliers_by_box = []
for outlier in fliers[i]:
if outlier < q1[i]:
lower_outliers_by_box.append(round(outlier, 1))
else:
upper_outliers_by_box.append(round(outlier, 1))
lower_outliers.append(lower_outliers_by_box)
upper_outliers.append(upper_outliers_by_box)
# New code
stats = [medians, means, minimums, maximums, q1, q3, lower_outliers, upper_outliers]
stats_names = ['Median', 'Mean', 'Minimum', 'Maximum', 'Q1', 'Q3', 'Lower outliers', 'Upper outliers']
categories = ['DATASET 1', 'DATASET 2', 'DATASET 3'] # to be updated
for i in range(len(categories)):
print(f'\033[1m{categories[i]}\033[0m')
for j in range(len(stats)):
print(f'{stats_names[j]}: {stats[j][i]}')
print('\n')**Output:****DATASET 1**
Median: 52.6
Mean: 52.2
Minimum: -23.0
Maximum: 125.8
Q1: 32.3
Q3: 70.6
Lower outliers: [-33.8]
Upper outliers: []
**DATASET 2**
Median: 100.4
Mean: 102.3
Minimum: -0.7
Maximum: 185.5
Q1: 75.7
Q3: 131.9
Lower outliers: [-11.3]
Upper outliers: [221.2, 258.3]
**DATASET 3**
Median: 70.1
Mean: 69.8
Minimum: 47.0
Maximum: 95.1
Q1: 63.7
Q3: 76.3
Lower outliers: [42.1, 43.0, 39.5]
Upper outliers: [103.2]
结论
在本文中,我们探讨了如何从 matplotlib 库中创建的箱线图中提取并打印出数据集描述性统计数据的精确值。这种统计信息可以包括没有异常值的中值、平均值、最小值和最大值、第一和第三四分位数、较低和较高异常值。在某些情况下,这些精确的数据可以作为箱线图本身的视觉信息的有价值的补充。
感谢阅读!
你会发现这些文章也很有趣:
[## 在 Python 中对两个字典执行逻辑运算的最简单方法
towardsdatascience.com](/the-easiest-ways-to-perform-logical-operations-on-two-dictionaries-in-python-88c120fa0c8f) </5-pandas-methods-youve-never-used-and-you-didn-t-lose-anything-37277fae7c55> https://levelup.gitconnected.com/when-a-python-gotcha-leads-to-wrong-results-2447f379fdfe
如何在 Matplotlib 中用模式填充绘图
向条形图、直方图、箱线图和饼图添加图案的方法
作者图片
创建地块时使用填充图案是使用颜色的一种很好的替代方法。在下列情况下,它尤其有用:
- 这个情节将会刊登在一份黑白出版物上,
- 如果我们想要减少每个绘图使用的颜色数量(例如对于饼图),
- 如果我们想要强调图中的一些元素(例如条形图中的一些条)。
不幸的是,目前 matplotlib 在这方面的功能相当有限。此外,对于不同类型的地块没有独特的方法。
在本文中,我们将探讨如何向条形图、直方图、盒图和饼图添加模式。为了最大化数据-墨水比率,我们将通过在各处添加fill=False
来创建仅黑白的图。
条形图
要用图形填充条形图,我们只需将以下值之一作为字符串赋给可选参数hatch
:/
、\\
、|
、-
、+
、x
、o
、O
、.
、*
:
import matplotlib.pyplot as plt
import numpy as np
x = range(1,5)
y = range(1,5)
plt.bar(x, y, fill=False, hatch='/')
plt.show()
输出:
作者图片
为了获得更密集的填充,我们应该在字符串中添加更多的同类符号。所以,让我们用'///'
代替'/'
:
plt.bar(x, y, fill=False, hatch='///')
plt.show()
输出:
作者图片
注意:由于反斜杠(\
)是 Python 中的一个特殊字符,如果我们想用这个模式填充我们的条形图,我们就必须使用一个双反斜杠 ( '\\'
)。在这种情况下,为了获得更密集的模式,有必要将偶数个反斜杠分配给hatch
参数('\\\\'
、'\\\\\\'
等)。).
也可以在相同的条上组合两种或多种模式,将相应的字符添加到字符串中:
plt.bar(x, y, fill=False, hatch='.O')
plt.show()
输出:
作者图片
绘制堆积或分组条形图时,填充变得更加有用:
y1 = range(1,5)
y2 = range(5,9)
plt.bar(x, y1, fill=False, hatch='xx')
plt.bar(x, y2, fill=False, hatch='..', bottom=y1)
plt.show()
输出:
作者图片
此外,与颜色一样,我们可以使用填充来强调最重要的线条。这里的算法如下。plt.bar()
方法返回一个包含 n 个条形的容器,可以将这些条形赋给一个变量:
bars = plt.bar(x, y)
print(bars)**Output:** <BarContainer object of 4 artists>
在我们的例子中,容器中有 4 个条。让我们为条形图案设置一个条件。例如,我们只想强调1 < y < 3
所在的小节。我们可以遍历 y 值列表,对于满足上述条件的值,分配星号('*'
),否则不分配任何值(空字符串)。因此,我们将拥有一个与每个 y 值相对应的hatch
参数值列表:
hatches = ['*' if y>1 and y<3 else '' for y in y]
print(hatches)**Output:** ['', '*', '', '']
现在,我们可以遍历容器的各个条,并为每个条设置相应的hatch
值:
bars = plt.bar(x, y)
for i in range(len(bars)):
bars[i].set(hatch = hatches[i], fill=False)
plt.show()
输出:
作者图片
柱状图
为了填充直方图,我们使用了与条形图相同的可选参数hatch
,以及相同的可选值:
data = np.random.normal(loc=10, scale=10, size=500)
plt.hist(data, histtype='step', edgecolor='black', fill=False, hatch='.')
plt.show()
输出:
作者图片
当绘制多个堆叠或重叠的直方图时,它变得更加有用:
data1 = np.random.normal(30, 20, 5000)
data2 = np.random.normal(80, 15, 5000)
plt.hist(data1, bins=30, histtype='step', edgecolor='black', fill=False, hatch='.')
plt.hist(data2, bins=30, histtype='step', edgecolor='black', fill=False, hatch='o')
plt.show()
输出:
作者图片
箱形图
然而,对于箱线图,这种方法不太直接,因为plt.boxplot()
方法没有hatch
参数。因此,我们必须使用一个变通办法。这里的算法如下。plt.boxplot()
方法返回一个字典,以盒图的不同元素作为键:胡须、盒子、中线、传单等。该字典可以分配给一个变量:
data = np.random.rand(100)
boxplots = plt.boxplot(data, patch_artist=True)
boxplots**Output:** {'whiskers': [<matplotlib.lines.Line2D at 0x195176862e0>,
<matplotlib.lines.Line2D at 0x19517686640>],
'caps': [<matplotlib.lines.Line2D at 0x195176869a0>,
<matplotlib.lines.Line2D at 0x19517686d00>],
'boxes': [<matplotlib.patches.PathPatch at 0x19517667f10>],
'medians': [<matplotlib.lines.Line2D at 0x195176900a0>],
'fliers': [<matplotlib.lines.Line2D at 0x195176903a0>],
'means': []}
**注意:**为了以后能够使用hatch
属性,需要在调用plt.boxplot()
时设置patch_artist=True
。
我们从上面的字典中需要的是'boxes'
键。我们必须遍历所有的方框(即使在我们的例子中,我们只有一个方框图,因此只有一个方框),并为每个方框设置相应的hatch
值,就像我们在条形图中只强调一个条形一样:
boxplots = plt.boxplot(data, patch_artist=True)
for box in boxplots['boxes']:
box.set(hatch = 'x', fill=False)
plt.show()
输出:
作者图片
当创建两个或多个盒状图时,这种技术变得更加有用:只强调其中的一部分,或者只为每个盒状图分配不同的模式。在这种情况下,我们必须创建一个hatch
属性的值列表(根据条件,如果必要,或者只是随机的),并遍历它:
data1 = np.random.rand(10)
data2 = np.random.rand(20)
data3 = np.random.rand(500)
hatches = ['o', '++', 'x']
boxplots = plt.boxplot([data1, data2, data3], patch_artist=True)
for i in range(len(boxplots['boxes'])):
boxplots['boxes'][i].set(hatch = hatches[i], fill=False)
plt.show()
输出:
作者图片
圆形分格统计图表
和盒子图一样,plt.pie()
方法没有hatch
参数,所以用模式填充饼图也不简单。这一次,plt.pie()
方法返回一个元组:
data = np.random.rand(5)
patches = plt.pie(data)
patches**Output:** ([<matplotlib.patches.Wedge at 0x195177aa4c0>,
<matplotlib.patches.Wedge at 0x195177aa970>,
<matplotlib.patches.Wedge at 0x195177aadf0>,
<matplotlib.patches.Wedge at 0x195177b72b0>,
<matplotlib.patches.Wedge at 0x195177b7730>],
[Text(1.046742554077009, 0.3381272326866619, ''),
Text(-0.00440567017664189, 1.0999911772692974, ''),
Text(-1.0992106691773433, -0.0416641904601184, ''),
Text(-0.5217111246565277, -0.9684097802116732, ''),
Text(0.7338182292945598, -0.8194576293836047, '')])
这个元组的第一个元素(patches[0]
)包含了我们饼图的所有楔形。在这种情况下,有 5 个楔子。我们可以根据特定的条件或随机地给每个人分配一个特定的模式。让我们强调最小和最大的楔子,其他的不填。为此,我们必须遍历数据,对于满足上述条件的值(即最小值和最大值),将'o'
和'O'
符号分配给相应的hatch
值,否则不分配任何值(空字符串):
hatches = ['o' if value==min(data) else 'O' if value==max(data) else '' for value in data]
现在,让我们遍历楔形区和hatch
值,并用选择的模式填充最小和最大的楔形区:
patches = plt.pie(data)
for i in range(len(patches[0])):
patches[0][i].set(hatch = hatches[i], fill=False)
plt.show()
输出:
作者图片
结论
在本文中,我们探索了在 matplolib 中用不同的模式填充等高线图的方法,如条形图、直方图、箱线图和饼图,并考虑了这种方法特别有用的情况。
我希望你喜欢阅读,并发现这些信息对你未来的项目有用。
你可能也会对这些文章感兴趣:
https://levelup.gitconnected.com/python-can-be-lots-of-fun-999552d69d21 https://levelup.gitconnected.com/when-a-python-gotcha-leads-to-wrong-results-2447f379fdfe https://medium.com/geekculture/creating-toyplots-in-python-49de0bb27ec1
如何在 Python 中过滤掉相似的文本
复杂问题的简单解决方案!
图片来源:宁石
有什么问题?
假设您在一个归档中有成千上万的文档,其中许多都是彼此的副本。还假设即使文档的内容相同,但标题不同。
现在想象一下,现在是年初的平静期,所以你的老板想利用这段时间做一些家务。她要求您通过删除不必要的重复文档来释放一些归档空间。
问题就在这里:如何过滤掉标题足够相似、内容可能完全相同的文本?接下来,如何实现这一点,以便在完成后,不会删除太多文档,并且保留唯一的文档集?
让我们用一些代码来说明这一点:
为什么是这篇文章?
我已经不得不解决这个问题几次了,我还没能在网上找到一个直截了当的解决方案,所以这就是我在这里尝试做的。
由于代码背后的理论有些复杂,所以我不会深入研究任何东西。
相反,这篇文章是为那些想要快速、实用地了解如何解决这样的问题并同时大致了解他们在做什么的人准备的!
解决方案是什么样的?
接下来,我将介绍解决这个问题的不同步骤。下面是控制流的概要:
- 预处理所有标题文本
- 生成所有标题的配对
- 测试所有配对的相似性
- 如果一对文本没有通过相似性测试,则删除其中一个文本,并创建一个新的文本列表
- 继续测试新列表中的相似文本,直到没有相似文本
用 Python 来表达,这可以很好地映射到递归函数上!
代码
下面是 Python 中实现这一功能的两个函数。
第一个是预处理标题文本的简单函数;它删除像“the”、“a”、“and”这样的停用词,只返回标题中单词的词条。
因此,为了大致了解这意味着什么,如果您将“2020 年末回顾”输入到此函数中,您将会收到“2020 年末回顾”作为输出;如果你输入“一月销售预测”,你会得到“一月销售预测”。
为此,它使用了 python 中非常易于使用的 spacy 库;你可以在这里了解更多。
第二个函数——脚本中的第 30 行——为所有标题创建配对,然后确定它们是否通过了余弦相似性测试。如果它没有找到任何相似的标题,那么它输出一个不相似标题的列表。但是,如果它确实找到了相似的标题,在删除未通过相似性测试的标题对之后,它会将这些过滤后的标题再次发送给自己,并检查是否还有任何相似的标题。
这就是递归的原因!简单明了地说,这意味着该函数将不断检查输出,以确保在返回“最终”输出之前没有类似的标题。
余弦相似度是什么?
嗯,这里有一篇关于这个问题的很好的文章。
但是简单地说,这就是斯派西正在做的事情…
首先,还记得那些像“一月销售预测”这样的预处理标题吗?首先 spacy 把它变成了一个数字矩阵。
一旦完成,你就可以把这些数字变成向量,这意味着你可以把它们画在图上。
一旦你完成了,计算两条线之间角度的余弦,基本上给了你一个方法,告诉你它们是否指向相似的方向。
来源:Adobe Stock
所以,在上图中,想象一下 b 线代表一个特定的文本或句子,c 线代表不同的文本或句子。
在这种情况下,b 行和 c 行都对应于 spacy 为它们各自的句子创建的数字矩阵。两条线之间的角度——在上图中用希腊字母 alpha 表示——非常有用!你可以通过计算它的余弦来判断这些线是否指向同一个方向。
这听起来似乎太明显而无法计算,但关键是这种方法为我们提供了一种自动化整个过程的方法。
结论
所以,就这样了!概括地说,我已经解释了递归 python 函数如何使用余弦相似性和 spacy 自然语言处理库来获取相似文本的输入,然后返回彼此不太相似的文本。
这可能有相当多的用例…类似于我在本文开头提到的归档,您可以使用这种方法来过滤数据集中具有独特歌词的歌曲,甚至过滤具有独特内容类型的社交媒体帖子。
或者,你可以把整个事情颠倒过来,只获取重复最多的内容——如果你在大量的推文中发现哪些推文几乎互相重复呢?
如何在 2022 年最终在 Windows 10 上安装 TensorFlow 2 GPU
百分百管用!
照片由 卢卡斯 上传。除非另有说明,所有图片均为作者所有。
我觉得包括我在内的 Windows 用户已经受够了。在尝试了几个小时甚至几天之后,你可能会被这个问题绊倒。因此,出于好意,我将直接切入正题,向您展示在 Windows 10 上安装 TensorFlow GPU 所需的步骤,而不会给出通常的博客介绍。
2022 年 11 月 27 日更新:该指南适用于 Windows 11,以及 tensor flow 2.11 之前的版本(目前是最新版本)。
第一步:找出 TF 版本及其驱动程序。
第一步,也是非常重要的一步,就是到这个链接,决定你要安装哪个 TF 版本。基于此,CUDA 驱动程序版本和其他软件版本会发生变化。
在撰写本指南时,TF 2.6.0 是最新的,我们将安装那个版本。
我们只对 TF 版本、cuDNN 和 CUDA 版本感兴趣。我们保持此选项卡打开,并继续下一步。
https://ibexorigin.medium.com/membership
获得由强大的 AI-Alpha 信号选择和总结的最佳和最新的 ML 和 AI 论文:
https://alphasignal.ai/?referrer=Bex
步骤 2:安装 Microsoft Visual Studio
接下来,我们安装 Microsoft Visual Studio。请注意,这与 Visual Studio 代码不同,后者是很多人喜欢的轻量级 IDE。
转到此链接并点击下载:
运行下载的可执行文件,下载需求需要一分钟时间。然后,它会要求您选择要安装的工作负载。我们不想要任何,所以只需单击不带工作负载的安装,然后单击继续。安装完成后,它会要求您登录,但您不必这样做。
步骤 3:安装 NVIDIA CUDA 工具包
NVIDIA CUDA 工具包包含您的 NVIDIA GPU 的驱动程序。根据您的 Windows,它们可能已经安装,也可能没有安装。如果安装了,我们应该检查它们的版本,看看它们是否与我们要安装的 TensorFlow 版本兼容。
在 Windows 上转到您的设置,然后选择“应用和功能”。然后,搜索 NVIDIA:
我们希望安装 TF 2.6.0,这需要 NVIDIA CUDA Toolkit 版本 11.2(请参见第一个链接进行仔细检查)。如果你的驱动是任何其他版本,删除所有标题中有“NVIDIA CUDA”的驱动(留下其他的)。然后,转到Local Disk (C:) > Program Files > NVIDIA GPU Computing Toolkit > CUDA
。在那里,您将看到一个以 CUDA 版本为名称的文件夹。删除那个文件夹。
如果你搜索 NVIDIA,没有找到 CUDA 工具包,就去这个页面。它看起来是这样的:
在这里,我们看到了三个 11.2 版本,这是我们需要的(我们从我提供的第一个 TF 版本链接中得到版本)。点击其中任何一个,选择 Windows 10,下载网络安装程序:
按照屏幕上的提示,使用默认参数安装驱动程序。然后,重新启动计算机并返回。
步骤 4:安装 cuDNN
对于 TensorFlow 2.6.0,需要 cuDNN 8.1。进入页面并按下载:
它会要求您提供一个 NVIDIA 开发者帐户:
如果您还没有帐户,请点击“立即加入”并输入您的电子邮件。填写表格——标准的东西。然后,回到 cuDNN 下载页面:
在顶部,它会要求你填写一份调查。填写它,你会看到上面的页面。单击第一个,因为它与 CUDA Toolkit v. 11 兼容。*.在那里,你会看到一个 Windows 版本,你应该下载。
步骤 5:解压缩 ZIP 文件夹并复制核心目录
提取下载的 ZIP 文件夹:
打开cuda
文件夹,复制顶部的三个文件夹(bin, include, lib
)。然后,去C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.2
把它们贴在那里。
资源管理器告诉你这些文件夹已经存在,你应该按下替换目标中的文件。就这样,我们完成了软件需求!再次重新启动计算机。
步骤 6:将 CUDA 工具包添加到 PATH 中
现在,是时候向环境变量添加一些文件夹了。在最后一个目的地,C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.2
,有一个bin
和文件夹:
打开它并复制文件路径。然后,按“开始”(Windows)按钮并键入“环境变量”:
打开它,转到“环境变量”。这将打开弹出窗口:
从顶部选择“路径”,然后按编辑。按“新建”并将复制的链接粘贴到那里。
然后,回到 GPU toolkit 文件夹,打开libnvvp
文件夹:
复制它的路径并粘贴到环境变量中,就像您对bin
文件夹所做的那样。然后,关闭所有弹出窗口,保存更改。
步骤 7:使用 Jupyter Lab 在虚拟环境中安装 TensorFlow
最后,我们准备安装 TensorFlow。使用您喜欢的软件包管理器创建一个虚拟环境。我使用conda
,所以我用 Python 版创建了一个名为tf
的conda
环境。
conda create -n tf python==3.8
conda activate tf
pip install --upgrade tensorflow
pip install jupyterlab ipykernel
重要的是 TensorFlow 和 JupyterLab 都安装了pip
或conda
。如果它们是从不同的渠道安装的,你会在 JupyterLab 中得到一个ModelNotFoundError
。
接下来,我们应该将conda
环境添加到 Jupyterlab,这样当我们启动一个会话时,它就会被列为有效的内核:
ipython kernel install --user --name=<name of the kernel, `tf` for our case>
如果您启动 JupyterLab,您应该能够将环境视为一个内核。创建一个新的笔记本,并运行以下代码片段来检查 TF 是否可以检测到您的 GPU:
import tensorflow as tf
from tensorflow.python.client import device_lib
print("Num GPUs Available: ", len(tf.config.list_physical_devices('GPU')))
device_lib.list_local_devices()
正如输出所说,我有一个单一的 GPU,在最后,它显示其名称。如果您有类似的输出,那么我在这里的工作就完成了!
说明摘自这个视频的 YouTube 视频。
如何找到数据科学导师
以及为什么这是个好主意。
数据科学、分析和工程领域可能会让新人望而生畏。这是一个不断发展和分化的领域。
随着行业的发展,新的角色、利基和工具正在快速产生。直到最近,我才了解到对分析工程师的需求越来越大,这些人可以充当数据工程师和数据分析师之间的桥梁。MOOCs 和新兵训练营千篇一律的课程只能让你到此为止。在一个需求技能变化如此之快的世界里,课程本来就很脆弱。
有一个好的导师可能是一个主要的催化剂。一位导师可以帮助你从噪音中分辨出信号——从现实中辨别出炒作。它们可以减少就业市场固有的信息不对称,引导你找到最好的资源和工具,并帮助你向招聘经理推销自己。鉴于面向大众的数据科学课程必须具有广泛的吸引力,导师可以针对你的目标角色和领域给你提供建议。
但是,如果没有现有的职业关系网——即使有——实际上找到一个导师也是困难和尴尬的。
时不时地,我会收到一条类似这样的消息:
你好,我想成为一名数据科学家,但我不知道该如何着手。请帮帮忙。
我通常会回复这些问题。但我是个例外。我正在建立并指导sharpes minds——一个数据科学、分析和机器学习领域的指导平台——这使我成为指导请求的自然目标。另外,我有一个既得利益去理解那些正在寻找导师的人们的问题。
但这不是正确的方法。这个要求太含糊了。如果不多问一些问题,我无法提供任何实际的建议。当你向潜在的导师提出模糊的、开放式的请求时——比如“请帮忙”,或者“你能指导我吗?”—你为他们创造了更多的工作。
也不是个性化的。它读起来像是被复制粘贴到其他几十个地方。如果看起来他们没怎么努力,我也不会努力。
在你向任何人寻求指导之前,试着弄清楚你到底需要什么帮助。同样重要的是,为什么你需要这个人的帮助。
最好的导师是那些经历过和你一样的事情,并在另一边成功脱颖而出的人——在和你目标相似的职位上工作。但是这些人现在很忙。他们是全职的数据科学家、数据分析师和数据工程师。然而,他们中的许多人很乐意帮忙。你只需要让他们容易些。
在你寻求指导之前,试着自己回答这些问题:
- 你在寻找什么样的角色(例如,数据科学家、数据分析师、数据工程师、机器学习工程师等。)?
- 你和那个角色之间最大的障碍是什么?
- 你已经拥有哪些相关的技能和经验?你上过 MOOCs 吗?建了什么项目?
- 你缺少什么工具、技能或信息?
- 有没有你想从事的特定行业(如医疗保健、金融等)。)?
- 你希望从导师那里得到什么样的指导?
知道这些问题的答案(并保持透明)会让潜在的导师更容易理解你的需求并提供指导。
对所有这些没有一个明确的答案是没关系的——知道你的无知在哪里是件好事。一位导师也许能帮你回答这些问题。例如,我在 SharpestMinds 采访过的许多导师都提到,他们喜欢帮助人们理解和优先考虑各种与数据相关的职位。然而,其他人更希望你已经有了一个明确的职业目标。这就是为什么坦白你知道什么和不知道什么很重要。
在sharpes minds上,向陌生人寻求指导是可以接受的。但是,在现实世界中,你最好先建立关系。你不会想突然出现并请求指导的。
开始关注活跃在网上(LinkedIn、Twitter、Medium 等)的业内人士。).与他们的内容互动——留下一些评论,提出后续问题。如果你从他们写的东西中获得了价值,让他们知道!奉承会大有帮助。
当你这样做的时候,开始在公共场合(在 LinkedIn、Twitter、Medium 等)学习或建立。)在同样的人可能会看到的地方。你想向潜在的导师展示你有上进心和动力。导师不是老师。他们不想填鸭式地喂你——他们没有时间。如果你能证明你是可训练的,那就加分。获得导师关注的最佳方式是展示你已经从他们那里学到了一些东西。
这是一个很好的介绍信息的例子:
“你好<名字>,我看了你关于培养基的烧瓶教程,从中获得了很多价值。感谢分享!我采纳了你的一些建议,为我的项目做了一个简单的前端:< link-to-your-repo >。我在想,<具体跟进问题>?
有了这样的信息,你就表明了你有理由专门去接触他们,你是自我驱动的,可以接受指导的。你也向他们提出了一个具体的问题。比“我想成为一名数据科学家,请帮忙”更容易回答的问题。
如何找到子集和问题的所有解
照片由 Antoine Dautry 在 Unsplash 上拍摄
适用于负数、正数和重复数字的动态编程解决方案
子集和问题涉及确定整数列表中的子集是否可以和为目标值。比如考虑一下nums = [1, 2, 3, 4]
的列表。如果target = 7
,有两个子集达到这个和:{3, 4}
和{1, 2, 4}
。如果target = 11
,没有解决方案。
一般来说,确定子集和是否有甚至任意个解是 NP 难:如果nums
列表中有n
个整数,则存在2^n — 1
个子集需要检查(不包括空集)。在这篇文章中,我们将会看到一个更有效的解决方法,使用动态规划 (DP)。然而,与大多数教程不同,我们不仅要确定是否存在一个解决方案,还要看看如何发现所有解决方案。该算法适用于负的和正的输入值,以及nums
中重复的非唯一整数。
TLDR;Python 包🐍
寻找快速解决方案,但不一定想知道底层细节?我创建了一个名为“ subsetsum 的 Python 包,带有一个超快的求解器:pip install subsetsum
求解器的逻辑用 C++实现,并使用 Pybind11 来公开一个 Python 接口。GitHub 上的提供了源代码。
1.预处理
在进入 DP 解决方案之前,我们将对问题输入(nums
和target
)进行一些预处理。
翻转标志🔄
我们要做的第一件事是翻转nums
中所有整数的符号,如果目标是负的,则翻转target
、。这确保了我们的target
将总是 0 或更大,这只是让整个算法的生活更容易!我们可以不用担心,因为翻转符号前后的解是相同的:
x0 + x1 + ... + x10 = target
# Multiply both sides by -1
-x0 - x1 - ... - x10 = -target
# The equations are equivalent!
对➡️的数字进行排序
下一个预处理步骤是按升序对nums
进行排序,这是 DP 算法工作所必需的(稍后描述)。如果你需要记住nums
的原始顺序,你可以执行一个 argsort ,它从一个整数在列表中的当前位置映射到它在排序列表中的位置。如果需要,您可以随时重新映射回原始位置。
nums = [-2, 1, -3, 0, 4, 5]
index = argsort(nums) = [2, 0, 3, 1, 4, 5]
nums[index] = [-3, -2, 0, 1, 4, 5]
检查目标是否过低或过高🛑
考虑nums = [-3, -2, -1, 1, 2, 3, 4]
的情况。可达到的最小总和是多少? -6 。最大的金额可能是多少? 10 。如果target
之和比nums
中所有负整数之和少或者比nums
中所有正整数之和多则无解。**
我们将在变量a
中存储所有负整数的和,在变量b
中存储所有正整数的和。如果target < a
或者target > b
,我们可以以“无解!”
2.动态规划
预处理完成后,我们准备填充一个名为DP
的动态编程表。DP
表将有n
行(给定n
数字)和target — a + 1
列。存储在表格中的值将只是真或假。行和列索引从 0 开始。
- 如果我们在
row i
上,我们会考虑使用整数的所有子集,直到并包括排序后的nums
中的第i
个整数。第i
个整数不需要包含,但是如果我们想使用它,它是“可用”的。 - 如果我们在
column j
上,我们将试图找到总和为a + j
的“中间”目标的子集。注意,因为我们有target — a + 1
列,所以j
的最终值将是target — a
,这意味着最后,我们试图找到总和为a + j = a + target — a = target
的子集。 - 因此,
DP[i, j] = 1
意味着nums[0...i]
中存在一个子集,其总和为a + j
。如果DP[i, j] = 0
不存在这样的子集。 - 恢复解:如果
DP[n — 1, target — a] == 1
,存在nums
的子集,总和为target
。
更新规则⭐️
我们首先用零初始化DP
表,并填充DP
的第一行,注意如果nums[0]
等于a + j
,则DP[0, j]
只能是 1 。
for j in range(0, target - a + 1):
DP[0, j] = (nums[0] == a + j)
对于剩余的行,DP[i, j]
可以在以下情况下标记为 1 :
DP[i — 1, j] == 1
:如果仅使用来自nums[0...(i — 1)]
的子集就可以实现“中间”目标a + j
,那么很明显,如果允许第i
个数字在该子集中,也可以实现该目标。nums[i] == a + j
:在这种情况下,中间目标a + j
可以从单整数子集{nums[i]}
中得到。DP[i — 1, j — nums[i]] == 1
:最棘手的规则,让人想起背包问题的动态编程解决方案。如果有一个nums[0...(i — 1)]
的子集总计为a + j — nums[i]
,那么我们知道有也是的一个子集,通过将nums[i]
包含在该子集中而总计为a + j
。
for i in range(1, n):
for j in range(0, target - a + 1):
DP[i, j] = DP[i - 1, j] or nums[i] == (a + j)
if DP[i, j] == False:
next_j = j - nums[i]
if 0 <= next_j < target - a + 1:
DP[i, j] = DP[i - 1, next_j]
时间和空间复杂性⏱
这里描述的 DP 解决方案是所谓的伪多项式算法。DP 表格的大小不仅(线性地)取决于nums
中元素的数量,还(线性地)取决于nums
和target
的值,因为表格中的列数是target
和nums
中负整数之和之间的距离。
表的每个单元格必须设置为 0 或 1 一次,这是使用对其他单元格的恒定数量的查询来确定的,因此算法的运行时与表的大小成比例。
DP 有缺点,蛮干可以更好💪
先说nums = [-1000000, 1000000]
和target = 999999
。使用 DP 方法,我们将有 2 行和999999 + 1000000 + 1 = 2000000
列。对于一个显然无法解决的问题来说,内存使用量太大了!如果nums
中的数字很少,但是值的范围很大,你最好强力检查所有可能的子集。
3.寻找所有解决方案🤯
我们会用栈,可惜不是这种栈!照片由布里吉特·托姆在 Unsplash 上拍摄
让我们继续讨论可能是最具挑战性的话题,也是其他教程中最少讨论的话题:如何实际找出哪些子集达到了target
和!
要做到这一点,我们需要使用我们的DP
表,并通过它回溯。我们将使用一种非递归技术:一个堆栈。堆栈中的每一项都是一个简单的数据结构,我称之为StackItem
。
class StackItem:
row: int # Row index in the DP table
col: int # Column index in the DP table
take: list[int] # **Indices** of integers to include in the subset
togo: int # Value "to go" until reaching the `target` sum
现在我们知道,如果DP
表格右下角的单元格是 1 ,那么就有一个解决方案。否则,就不要费心去寻找解决方案了,因为根本就没有解决方案!我们将从右下角的单元格开始初始化堆栈/回溯。我们假设nums
中的最后一个整数是子集包含的,那么togo
将是target
值减去nums[n — 1]
。
stack.push(
StackItem(
row=n - 1,
col=target - a,
take=[n - 1],
togo=target - nums[n - 1]
)
)
现在,我们不断从堆栈中弹出项目,并添加新的项目,直到它是空的。一旦完成,我们将列举所有可能的解决方案。
假设我们刚刚弹出了一个项目:item = stack.pop()
的row = i
和col = j
。我们将检查item
的三个场景:
- 如果
DP[i — 1, j] == 1
,那么可能有一个不使用nums
中第i
个整数的解决方案。在这种情况下,我们将添加一个新的StackItem
,就好像第i
个整数不包含在子集中,但是第(i-1)
个整数是**。** - 如果
DP[i — 1, j — nums[i]] == 1
,那么有一个解决方案,使用剩余的nums[0...(i — 1)]
整数形成一个“中间”子集,其总和为a + j — nums[i]
。在这种情况下,我们将添加一个新的StackItem
,假设第i
个整数是包含在“最终”子集中的。 - 如果
item.togo == 0
那么我们已经有了解决方案!然后item.take
将整数的索引存储在(排序的)nums
中,形成一个子集,其和为target
。
while len(stack) > 0:
item = stack.pop()
i, j, take, togo = item.unpack() # Scenario 1
if i > 0 and DP[i - 1, j]:
new_take = take.copy()
new_take[-1] = i - 1 # Replace the last element
new_togo = togo + nums[i] - nums[i - 1]
stack.push(StackItem(i - 1, j, new_take, new_togo)) # Scenario 2
next_j = j - nums[i]
if i > 0 and 0 <= next_j < (target - a + 1):
if DP[i - 1, next_j]:
new_take = take.copy()
new_take.append(row - 1) # Add a new element
new_togo = togo - nums[i - 1]
stack.push(StackItem(i - 1, next_j, new_take, new_togo)) # Scenario 3
if togo == 0:
yield [nums[t] for t in take]
结论🎉
我希望您喜欢学习如何使用动态编程解决子集和问题!有相当多的在线资源解释了如何确定是否对于特定的一组(nums
,target
)存在解,但是这些教程通常假设所有的数字都是正数。这里描述的算法也适用于负数!💡此外,几乎没有现有的教程解释如何利用 DP 表回溯所有解决方案,这是本文的主要动机。
更具体的实现(C++和 Python,不是伪代码)请访问 GitHub 或通过pip install subsetsum
下载 pip 模块。
如何使用 Python 找到餐馆的最佳位置
用 CVXPY 解决集合覆盖问题
动机
在模拟你的餐厅中的真实事件后,你的餐厅开始吸引更多的顾客,所以你决定在其他地方开设连锁餐厅。
由于许多顾客喜欢在附近就餐,您希望您的餐厅距离 1、2、3、4 和 5 区最多 15 英里。最佳解决方案是在所有其他区域的 15 英里范围内建立最少数量的餐馆。
假设您的餐厅只能位于 1 区、2 区、3 区、4 区或 5 区,您应该将餐厅建在哪个位置?
作者图片
这就是所谓的集合覆盖问题。在本文中,您将学习如何使用 CVXPY 解决这个问题。
CVXPY 是什么?
CVXPY 是一种嵌入 Python 的建模语言,用于如上所述的凸优化问题。它类似于纸浆,但是它的语法更简单、更直观。
要安装 CVXPY,请键入:
pip install cvxpy
问题陈述
在深入研究数学公式和代码之前,让我们先写下我们拥有什么和我们想要实现什么。
- 输入参数:我们被提供区域之间的距离:
作者图片
目标:我们想选择最少的区域来建造餐馆
约束:我们需要确保每个区域都在至少一家餐馆的 15 英里范围内
决策变量:我们将根据目标和约束条件决定是否在某一特定区域开店。
解决
输入参数
要解决这个问题,首先,把距离矩阵变成一个表示两个区域之间的距离是否在 15 英里以内的矩阵。具体来说,
- 小于或等于 15 英里的距离变为 1****
- 大于 15 英里距离**变成 0**
接下来,将数据帧转换成矩阵:
作者图片
作者图片
决策变量
我们将决定是否在某个特定的地区开店。
作者图片
限制
我们需要确保每个区域都在至少一家餐馆的 15 英里范围内。
例如,因为我们知道区域 1 在区域 1 和区域 2 的 15 英里范围内,所以我们需要确保其中一个餐馆建在区域 1 或区域 2。
作者图片
作者图片
这意味着我们需要如下内容:
作者图片
为了将上述等式推广到所有领域,我们可以使用矩阵乘法:
作者图片
目标
尽量减少用于建造餐馆的面积。
作者图片
解决
现在我们有了约束和目标,让我们来解决问题吧!
'optimal'
厉害!因为问题的状态是最优的,所以找到了最优解。要查看解决方案,请键入:
array([0., 1., 1., 0., 0.])
该结果表明,区域 2 和 3 是建造餐馆的两个理想位置。
解决方案的解释
为什么选择 2 区和 3 区?在下面的图片中,我们可以看到,通过在 2 号和 3 号区域建造餐馆,每个区域都在至少一家餐馆的 15 英里范围内!
作者图片
这个问题的解决方案似乎很直观,因为我们只需要考虑 5 个方面。但是当你需要考虑 10 个或者更多的区域时,这种求解方法会非常有帮助。
结论
恭喜你!您刚刚学习了如何使用 CXVPY 解决集合覆盖问题。希望这篇文章能给你解决类似问题所需的知识。
在 Github repo 中,您可以随意使用本文的代码:
我喜欢写一些基本的数据科学概念,并尝试不同的算法和数据科学工具。你可以通过 LinkedIn 和 Twitter 与我联系。
如果你想查看我写的所有文章的代码,请点击这里。在 Medium 上关注我,了解我的最新数据科学文章,例如:
[## 使用 SimPy 在 Python 中模拟真实事件
towardsdatascience.com](/simulate-real-life-events-in-python-using-simpy-e6d9152a102f)
参考
陈(2010)。应用整数规划:建模与求解。j .威利&的儿子们。
当 A/B 测试不合适时,如何找到因果推断
回归不连续设计(RDD)是数据科学中经常被忽视的一个计量经济学主要内容。在这里,我用 Python 中的 RDD 来决定是否应该允许孩子喝啤酒。
数据科学目前风靡一时,这是很正确的。计量经济学,数据科学的兄弟(统计学是父亲),有时会觉得有点被冷落。尽管它们的大部分技术和程序是相同的,但在现代数据科学中,一些真正有用的计量经济学技术经常被遗忘。
我在期刊上发表的一些计量经济学作品的例子可以在这里看到。
数据科学主要关注预测,试图确定因果推断通常不被认为是重要的。只要底层系统和关系不变,数据科学预测技术就非常棒,但它们在现实世界中经常发生变化。了解因果关系也有助于推动政策和决策的制定。
尽管计量经济学以令人难以置信的枯燥而闻名(尽管不知何故它的兄弟被称为 21 世纪最性感的工作),但它通常是一门艺术科学。
因为根据定义,计量经济学研究经济和经济中的人类互动,A/B 测试通常很难获得,因为它们在伦理上具有挑战性。我们不能只是在宏观尺度上玩弄人们的生活,否则人们可能会有点恼火。想象一下,我们刚刚开始随机分配大学名额。因此,计量经济学提出了一些后门方法,可以有一些很大的用途。
在这篇文章中,我将通过一个最有趣,但也最简单的计量经济学技术— 回归不连续设计。我将使用来自 Carpenter(2011 年)的一个例子来分析法定饮酒年龄,以及降低法定饮酒年龄对经济的影响。
作为一名苏格兰人,这是我最关心的事情。
我也会用 Python 来编码。
回归不连续设计
在计量经济学(以及统计学、流行病学和统计学)中,回归不连续设计是一种准实验技术,试图从 A/B 检验不适合的数据中得出因果推断。这通常是事后利用现有数据完成的。这是通过在“干预点”上下指定一个阈值来实现的。然而,仍然不可能用这种方法要求明确的因果推断,因为它不能自动拒绝任何潜在的或未考虑的变量的因果影响。然而,研究表明 RCT(A/B 测试)和 rdd 不会产生太不相似的结果(在进行适当调整后,在 0.07 标准偏差内)。
最常见的例子是奖学金和平均绩点。
我们正在寻找的平均治疗效果的例子。来源:https://www.rpubs.com/muntasir_masum/rdd
考虑一所高中,其中前 x 名学生(按 GPA 排序)获得了奖学金,我们想知道奖学金对这些人的影响。我们不能简单地比较获得奖学金的人和没有获得奖学金的人,因为有明显的选择偏差。更优秀的个人更有可能获得奖学金,因此更有可能在未来出类拔萃(假设未来的成功可以通过 GPA 来预测,这并不夸张)。
奖学金可能根本没有任何效果,只是获得奖学金的学生已经准备好做得更好。所以在上图中,以 C 为奖学金分界点,高于 C 的每个人都能获得奖学金,低于 C 的人则不能。
RDD 所做的,是将分界点附近的点(或学生)进行比较。例如,在 78-82%范围内的学生。考虑到阅卷考试和其他个体随机变量的随机性,这一组被认为在潜在能力上有些平等。比较该组中的个体,一些接受治疗(奖学金),一些没有,我们可以推断奖学金对个体的影响,并因此估计因果推断(如前所述,这不是真正的因果推断,而是创造的平均治疗效果)。
是否应该降低最低饮酒年龄?
在他们的论文中,Carpenter&DOB kin(2011)使用这种精确的技术来分析如果在美国饮酒年龄从 21 岁降低会有什么影响。考虑到我们在苏格兰大约八岁就开始喝威士忌,我完全赞成削减威士忌,这样我们在池塘那边的兄弟姐妹就可以和我们一样享受生活的果实(尽管你可能不想缩短我们的预期寿命)。让我们看看数据是否一致。
在这里,RDD 是一个很好的工具,因为已经有了一个特定的截止日期——一个人的 21 岁生日。一个人在 21 岁生日的前一天和后一天实际上是同一个人,除了他们现在可以享受生活。用更科学的术语,和原作者的话;“如果在 21 岁时除了法律制度没有发生变化,那么 21 岁时的离散死亡率似乎可以归因于饮酒年龄”。
作者比较了三种不同类型的死亡;机动车死亡、内因死亡和自杀死亡。
可以看出,第一组和最后一组中的最佳拟合线明显不连续。
资料来源:【nih.gov 最低法定饮酒年龄和公共健康(T2)】
为了估计不连续性并检验其在统计上是否显著,他们使用以下等式:
y=β0+β1MLDA+β2生日 + f( 年龄 ) + ε
*“y”*为分年龄死亡率,每年、每月各一个数据点。
*“MLDA”*是一个虚拟变量,对于观测值 21 和更老的观测值,其取值为 1,否则取值为 0。
“f(年龄)”是与“MLDA”假人完全交互的二次多项式。
*【生日】*是死者 21 岁生日所在月份的虚拟变量,旨在吸收生日庆祝对死亡率的显著影响。
“ε”当然是一如既往的未被观察到的误差项。
因此,该模型中感兴趣的参数为β1。
然后,作者用上述方程进行线性回归,并给出了下面的结果表。
上表中的结果与图表证据一致,并显示当人们年满 21 岁时,总死亡率增加了 8.7%,具有统计学意义(从 93.07 例死亡的基数开始,每 100,000 人年增加 8.06 例死亡,相当于 8.06/93.07 = 0.087,即增加了 8.7%)。
总的来说,上图中的视觉证据和表中相应的回归估计提供了强有力的证据,证明最低法定饮酒年龄对 21 岁时自杀、机动车事故和酒精过量的死亡率有显著影响。
该死,看来不让孩子喝酒是个好主意。
自己怎么做
现在,我将简要地向您展示如何用 Python 自己运行这个实验的简化版本。这非常简单。因此,首先我通过逆向工程作者用于回归的方程制造了一些虚假数据。规模有点不同,但这没关系。在经历了 23 年的伤痛之后,苏格兰终于获得了欧洲足球锦标赛的参赛资格,这可能是今年的数据
作者创建的虚构数据散点图
由作者创建的用于此分析的数据帧的标题
我使用了同样的 48 个月的周期,也集中了年龄(无论如何,它将在回归中被标准化)。
然后,我们可以像往常一样简单地运行线性回归!我们可以使用“统计模型”来得到一个更加“计量经济学”风格的输出。Sklearn 产生 R 平方,但不产生系数 P 值,这是我们在这种情况下想知道的。
链接到我的 github —作者代码。
因此,输出如下:
作者运行的回归的输出。
不足为奇的是,回归预测了我制作数据集时使用的几乎准确的数字(5 代表年龄,50 代表 21 岁)。我确实添加了一个随机误差项,但显然还不够,因为 R 平方仍然非常高,为 0.98。
可以看出,年龄和 21 岁生日的 P 值都非常显著,因此我们可以拒绝 21 岁生日不影响死亡率的无效假设(当然我们可以这样做,因为我是以这种方式获得数据的)。
因此,正如你所看到的,它实际上与运行正常的线性回归是一样的,只是在指定方程和控制关系时要格外小心。原始论文的作者投入了更多的努力来控制所有其他年龄变量,但这只是一个如何做到这一点的简化示例。
外卖
如果你能做 A/B 测试,那就做吧。但是如果你不能,那么 RDD 是你最好的选择。如果你正确地指定了方程,并且没有违反任何假设,你应该不会有任何问题。小心陷阱,如虚拟变量陷阱%20as%20an%20example.)或过多的[多重共线性](https://en.wikipedia.org/wiki/Multicollinearity#:~:text=In%20statistics%2C%20multicollinearity%20(also%20collinearity,a%20substantial%20degree%20of%20accuracy.)。
如前所述,这种方法不能推导出真正的因果推理。例如,我们并不确切知道这个等式中的所有变量。美国青少年长时间等待饮酒可能是他们 21 岁后不久死亡的原因之一。所以在做大胆的陈述时要小心。
最后,总结一下,Carpenter&DOB kin(2011)的研究结果表明,饮料将不得不收取 15 美元,以弥补将饮酒年龄降低到 18 岁的经济成本(使用大约 800 万美元作为生命的统计价值)。
就我个人而言,我不赞成为一杯酒支付 15 美元(尽管我现在在伦敦支付的价格与此相差不远)。
所以,抱歉,年轻的美国人,你得等到 21 岁,反正这对你没好处。
我希望你喜欢这篇文章,如果你喜欢,这里还有一些我的类似的文章。
If I’ve inspired you to join medium I would be really grateful if you did it through this [link](https://jamesasher4994.medium.com/membership) — it will help to support me to write better content in the future.If you want to learn more about data science, become a certified data scientist, or land a job in data science, then checkout [365 data science](https://365datascience.pxf.io/c/3458822/791349/11148) through my [affiliate link.](https://365datascience.pxf.io/c/3458822/791349/11148)
[## 如何预测没有数据的事物——还有盆景树
towardsdatascience.com](/how-to-predict-something-with-no-data-and-bonsai-trees-b6ebc6471da3)
干杯,
詹姆斯。
如何用 TensorFlow 找到最佳的神经网络结构——最简单的方法
在任何数据集上优化前馈神经网络模型的最佳指南
深度学习归结为实验。手动训练数百个模型既繁琐又耗时。我宁愿用我的时间做些别的事情,我想你也一样。
想象一下,你想为你的深度神经网络找到最佳架构。你从哪里开始?多少层?每层有多少个节点?激活功能呢?有太多的活动部件。
您可以在一定程度上自动化这个过程,本文将向您展示如何实现。阅读后,您将有一个函数用于在给定特定参数的情况下生成神经网络架构,另一个函数用于寻找最佳架构。
不想看书?请观看我的视频:
你可以在 GitHub 上下载源代码。
使用的数据集和数据预处理
我不打算在这里花太多时间。我们将使用与上一篇文章中相同的数据集——来自 Kaggle 的葡萄酒质量数据集:
图片 1——来自 Kaggle 的葡萄酒质量数据集(图片由作者提供)
您可以使用以下代码将其导入 Python,并随机打印几行:
我们忽略警告并更改默认的 TensorFlow 日志级别,这样我们就不会被输出淹没。
以下是数据集的外观:
图 2——葡萄酒质量数据集的随机样本(图片由作者提供)
数据集基本上是干净的,但默认情况下不是为二元分类(好酒/劣酒)而设计的。取而代之的是,葡萄酒是按等级来评定的。我们现在将解决这个问题,还有许多其他问题:
- 删除缺失值 —它们为数不多,所以我们不会在插补上浪费时间。
- 处理分类特征——唯一的一个是
type
,指示葡萄酒是白还是红。 - 转换为二分分类任务——我们将把任何 6 分及以上的葡萄酒宣布为好,任何低于的为差。
- 训练/测试拆分——经典的 80:20 拆分。
- 缩放数据 —预测值之间的比例差异很大,因此我们将使用
StandardScaler
来拉近数值。
下面是完整的数据预处理代码片段:
同样,如果您想更详细地了解数据预处理背后的逻辑,请参考上一篇文章。
这样一来,让我们看看如何优化神经网络架构。
如何优化神经网络模型?
寻找最佳神经网络模型的方法将具有一些可调整的常数。今天的网络将有 3 个隐藏层,每层最少 64 个节点,最多 256 个节点。我们将节点之间的步长设置为 64,因此可能是 64、128、192 和 256:
让我们验证节点数的可能性。您可以通过创建最小和最大节点数之间的范围列表来做到这一点,请记住步长:
以下是您将看到的内容:
图 3——节点数的可能性(图片由作者提供)
将这一逻辑延伸到两个隐藏层,您最终会得到以下可能性:
或者视觉上:
图 4-两个隐藏层的节点数可能性(图片由作者提供)
要获得两层之间选项的每一种可能的排列,您可以使用itertools
中的product()
函数:
以下是输出结果:
图 5 —两层深度神经网络架构排列(图片由作者提供)
我们的目标是优化一个三层深度的神经网络,所以我们最终会有更多的排列。您可以通过首先将节点选项列表乘以num_layers
来声明可能性,然后计算排列:
有很多选择——总共 64 种。在优化过程中,我们将迭代排列,然后再次迭代单个排列的值,以获得每个隐藏层的节点数。
简而言之,我们将有两个for
循环。前两种排列的逻辑如下:
第二个打印声明放在这里只是为了让型号之间有个空隙,不要想太多。以下是输出结果:
图 6-每层的节点数量(作者提供的图片)
我们将在每次迭代中创建一个新的tf.keras.Sequential
模型,并为其添加一个具有单个训练行形状的tf.keras.layers.InputLayer
((12,)
)。然后,我们将迭代单个排列中的项目,并向模型添加一个tf.keras.layers.Dense
层,将节点数设置为单个排列的当前值。最后,我们将添加一个tf.keras.layers.Dense
输出层。
将名称设置为模型是一个好主意,这样以后比较容易。我们将对 no 的输入形状和激活函数进行硬编码,并在下一节中将这些部分设置为动态的。
代码如下:
现在让我们来看看单个模型是什么样子的:
图 7 —单一模型架构(图片由作者提供)
这就是我们的逻辑。不过,有一种方法可以改进它,因为每次想要运行优化时运行几十个笔记本单元并不方便。为激活函数、输入形状等硬编码值也不是最好的主意。
出于这个原因,接下来我们将声明一个用于生成序列模型的函数。
用于优化神经网络的模型生成函数
该函数接受许多参数,但不包含我们之前没有提到的任何内容。它为您提供了更改输入形状、隐藏和输出层的激活函数以及输出层的节点数量的选项。
代码如下:
让我们测试一下——我们将坚持使用一个具有三个隐藏层的模型,每个隐藏层最少 64 个节点,最多 256 个节点:
请随意检查all_models
列表的值。它包含 64 个顺序模型,每个模型都有唯一的名称和体系结构。训练这么多模型需要时间,所以让我们通过编写另一个助手函数来使事情变得格外简单。
用于优化神经网络的模型训练函数
这个函数接受模型列表、训练和测试数据,以及可选的一些时期和详细级别。建议将 verbosity 设置为 0,这样您就不会被控制台输出淹没。该函数返回一个 Pandas DataFrame,其中包含测试集的性能指标,用准确度、精确度、召回率和 F1 来衡量。
代码如下:
现在,让我们开始优化。
运行优化
请记住,优化将需要一些时间,因为我们正在为 50 个时期训练 64 个模型。以下是开始这一过程的方法:
优化在我的机器(M1 MacBook Pro)上运行了 34 分钟,并打印了以下内容:
图 8 —优化输出(作者提供的图片)
您看到这个输出是因为optimize()
函数中的print()
语句。它给你一种进步的感觉。
我们现在有了一个数据框架,可以按照准确度、精确度、召回率或 F1 进行排序。以下是按精度降序排序的方法,因此首先显示值最高的型号:
图 9-模型优化结果(图片由作者提供)
看起来最简单的模型产生了最好的精确度。您还可以测试具有两个和四个隐藏层的模型的优化,甚至更多,但是我将让您来决定。只是调用get_models()
函数,传入不同的参数值。
这就是我今天想讲的全部内容。接下来让我们总结一下。
离别赠言
为您的数据集找到最佳的神经网络架构归结为一件事,而且只有一件事-实验。手工训练和评估上百个模型是相当繁琐的,所以你今天看到的两个函数可以帮你节省一些时间。您仍然需要等待模型训练,但是在这个数据集上整个过程很快。
从这里开始的一个好方法是选择一个你认为最好的架构,并调整学习速度。
如果处理图像数据和卷积层,事情会变得更加复杂,训练时间也会变得更长。这就是下一篇文章将涉及的内容——我们将开始深入计算机视觉,并训练一个简单的卷积神经网络。别担心,它不会出现在 MNIST 的数据集中。
*您如何优化前馈神经网络?是类似的东西,还是你使用的是专用的 AutoML 库?*请在下面的评论区告诉我。
喜欢这篇文章吗?成为 中等会员 继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
https://medium.com/@radecicdario/membership
保持联系
如何找到正确的集群数量
了解课本上没有教的技术
当运行一个聚类/分段项目时,最具挑战性的任务之一是确定存在多少个聚类。
好消息是有很多统计技术可以尝试回答这个问题,从肘法到 t-SNE 可视化到差距统计。
坏消息是这些技术很少是决定性的。机器学习课程使用诸如 Iris flower 数据集之类的例子的原因是,聚类的数量是预先知道的,并且它们相当容易找到。
当你完成学习并开始作为一名数据科学家工作时,你会很快意识到真实的数据更混乱,当数据不能给你一个清晰的画面时,你必须使用艺术和科学的混合。
肘法举例。教科书上的例子有一个清晰的信号,4 个集群就可以很好地工作。然而,现实世界的例子并不那么确定。图片作者。
因此,当你下一次被要求进行细分时,不要扔掉统计分析,只需使用下面的技术来创建一个在实践中有效的聚类,而不仅仅是在理论上。
谁在问?
开始之前不要失败;深入思考是谁要求进行细分,以及他们打算用它来做什么。
如果是营销团队试图更好地了解他们的客户,那么你可能会将搜索重点放在 3 到 10 之间。为什么?因为你不能指望人们脑子里带着超过 10 个集群和他们的轮廓。
如果团队必须为提议提出新的想法,你可能不需要太关心有多少集群描述了整个客户群。相反,你只需要找到尽可能多的有趣的群体,这些群体定义明确,足以激发一些新的想法,但又足够大,足以代表一个有价值的机会。
不要遇到第一个障碍,确保您只考虑适合您的用例的集群范围。GIF via GIPHY 。
或者,你的细分可能会直接进入产品,让顾客获得更个性化的体验。在这种情况下,你不太关心有没有一个人类能记住的数字,你只是想找到尽可能多的对个性化有用的数字,这可能有几千个!
并非所有的先入之见都是不好的
对于您的数据科学团队来说,这可能不是一条很好的原则,但是您不需要为所有事情都使用数据。
利益相关者将非常好地理解客户群中可能存在的集群类型的业务和期望。
虽然让数据说话很重要,而不是简单地产生一个给利益相关者他们想听的聚类,但提前了解他们的期望是一个有用的练习,原因有三:
- 验证。如果每个人都相信之前的分析表明有客户只在周末使用你的服务,那么一旦你超过一定数量的集群,这个群体开始出现,这应该会给你信心,你正朝着正确的方向前进。
- 惊喜。单个利益相关者不太可能描述您将在数据中发现的所有聚类,因此当您可以展示超出他们已知范围的新见解时,它就成为了数据科学的强大广告。
- 特色工程。如果您对存在的聚类有假设,您可以重新访问您的要素,并确保您将足够丰富的数据输入到您的算法中。如果没有捕捉一周中某一天的特征,您怎么能期望找到仅周末的聚类呢?
在执行分析之前,与利益相关者谈论他们期望看到的群体。GIF via GIPHY 。
你的集群稳定吗?
假设您的分析和用例表明适当的集群数量在 5 到 8 之间。与任何机器学习模型一样,您希望对您的数据进行有意义的反映,而不是在它接受训练的数据中出现异常。
考虑到这一点,根据不同时间点的数据来查看您的聚类会产生什么结果是一个有用的练习。您可能会发现,随着时间的推移,所生成的 5 个集群具有相当一致的大小和轮廓(通过检查它们的质心),而 8 个集群的粒度太细,一些集群会从一个月到下一个月消失或呈现不同的含义。
在这种情况下,您应该倾向于更加一致和稳定的集群。
你的客户稳定吗?
就像你希望集群稳定一样,你也希望它们之间的移动有意义。
要做出决定,使用上一步的相同输出,但是这次检查段之间的迁移级别。例如,当您使用一月份的数据运行聚类分析时,如果 90%的客户属于不同的细分市场,那么二月份和三月份的数据就应该敲响警钟。
如果客户的行为是相同的,或者如果他们已经搬家,那么这是因为他们的构成发生了变化,那么你希望找到许多属于同一细分市场的客户群。因此,再次检查有多少客户搬家,以及这些客户是如何改变的,看看你是否对他们重新分类的原因感到满意。
如果客户迁移到一个新的集群,那应该是因为他们的行为发生了变化。GIF via GIPHY 。
用三个字告诉我
最后,了解你的集群。如果您能够理解属于每一个客户的客户以及为什么算法将他们分组在一起,您就有信心拥有一个合适的数量。
如果你的集群太宽泛,很难定义,或者有一个群体看起来是“其他所有人”的混杂集群,那么你可能没有足够的集群。相反,您可能有一些属于极少数客户的集群,这可能意味着您有太多的客户。
一个经常被引用的经验法则是三形容词法则;如果你能用三个形容词来描述你的集群,那么它们是定义明确的,易于交流,如果你需要更多,那么它们可能太细或难以解释。这只是一个指导原则,但是如果您希望集群在整个企业中为人所知,这是一个有用的检查。
总结
请记住以下问题,以补充您的分析并增强您对聚类的信心:
- 谁在要求细分,他们将如何使用细分?
- 利益相关者期望提前发现什么?
- 即使您在一个月后再次运行您的算法,这些聚类仍然相同吗?
- 随着时间的推移,客户通常会出现在同一个细分市场中,还是因为正确的原因而转移?
- 您能用三个形容词描述每个集群吗?
快乐聚类!
当你在这里的时候
请随意查看我的其他文章:
如何在你的机器学习模型中找到弱点
FreaAI:来自 IBM 研究人员的新方法
任何时候使用汇总统计简化数据,都会丢失信息。模型精度也不例外。当将模型简化为汇总统计数据时,您将无法确定哪里的性能最低/最高以及原因。
图 1:模型性能较低的数据区域示例。图片作者。
为了解决这个问题,IBM 的研究人员最近开发了一种叫做 FreaAI 的方法,可以识别给定模型准确性较差的可解释数据切片。从这些切片中,工程师可以采取必要的步骤来确保模型按预期运行。
不幸的是,FreaAI 不是开源的,但是许多概念可以很容易地在您喜欢的技术栈中实现。让我们开始吧…
技术 TLDR
FreaAI 在测试数据中找到具有统计显著低性能的切片。它们被返回给工程师检查。方法步骤如下:
- **使用最高先验密度(HPD)方法寻找低精度的单变量数据切片。**这些单变量数据切片缩小了搜索空间,并显示了我们的数据在哪里更有可能出现问题。
- **使用决策树寻找低精度的二元数据切片。**这些双变量数据切片减少了分类预测因子和二阶相互作用的搜索空间,以显示我们的数据在哪里更有可能出现问题。
- **删除所有不符合特定启发式规则的数据切片。**主要的两个是测试集的最小支持度和误差的统计显著增加。
但是,到底是怎么回事呢?
这是一大堆术语,所以让我们慢一点,真正理解发生了什么…
1.问题是
当开发一个模型时,我们经常使用“准确性”度量来确定适合度。一个例子是均方误差,用于线性回归,如图 2 所示。
图 2:均方误差公式。图片作者— src 。
但是这个平均误差仅仅告诉我们平均起来我们做的有多好。我们不知道我们是否在数据的某些方面表现得很好,或者在其他方面表现得很差。
这是预测建模中一个长期存在的问题,最近得到了很多关注。
2.解决方案
一种解决方案是 FreaAI。该方法由 IBM 开发,旨在确定我们的模型在数据中表现不佳的地方。
有两个主要步骤。第一步涉及创建数据切片,第二步涉及确定模型在这些数据切片中是否表现不佳。FreaAI 的输出是我们数据中模型性能较低的一组“位置”。
2.1。数据切片
组合测试(CT)是一个框架,它依次查看所有预测器组,以找到表现不佳的区域。例如,如果我们有两个分类预测值,颜色和形状,我们会查看所有可能的组合,看看准确性在哪里下降。
然而,在大型数据集上利用组合测试在计算上是不可能的——随着每个新列的出现,我们看到所需的组合数量呈指数级增长。因此,我们需要定义一种方法来帮助我们搜索特性,找到潜在的不准确的地方。
图 3:蓝色的 50%最高密度区域(HDR)的例子。图片作者——src。
FreaAI 使用的第一种方法是所谓的最高密度区域(HDR) (图 3)。简而言之,HDR 找到了数字特征中可以找到一定比例数据的最小区域,即高密度区域。在图 3 中,该区域由水平蓝色虚线区分,50%的数据位于该线之上。
从这里开始,我们将这个范围迭代地减小一个值ε(默认为 0.05),并寻找精度的增加。如果在给定的迭代中精度确实增加了,我们知道该模型在前一次迭代和当前迭代之间的区域中表现不好。
为了确定数值预测因子的不良拟合区域,我们对测试集中的所有预测因子反复运行 HDR 方法。
很酷,对吧?
第二种方法利用决策树来处理所有非数字预测器以及两种特征的组合。简而言之,我们拟合一个决策树,并寻找这些特征的哪些分裂降低了准确性。
图 4:连续单变量预测因子“年龄”的决策树示例。图片作者。
在图 4 中,每个决策节点(蓝色)是我们特征的一个分割,每个结束节点(数字)是该分割的精度。通过拟合这些树,我们可以真正减少搜索空间,更快地找到性能差的区域。此外,因为树对于许多不同种类的数据都非常稳健,所以我们可以对分类预测器或多个预测器进行分析,以捕捉交互效应。
这种决策树方法对所有特征组合以及非数字的单个特征重复进行。
2.2.数据切片的试探法
到目前为止,我们只关心使用准确性开发数据切片,但是还有其他启发方法可以帮助我们找到有用的数据切片:
- 统计显著:为了确保我们只查看准确性有意义下降的数据切片,我们只保留性能比我们的误差置信区间的下限低 4%的切片。通过这样做,我们可以用概率 α 声明我们的数据切片具有更高的误差。
- 可解释的:我们也想确保发现的有问题的地方可以被采取行动,所以我们在创建组合时只看两三个特征。通过限制低阶交互,我们的工程师有更大的机会开发解决方案。
- 最小支持度:最后,数据切片必须有足够的误差才值得研究。我们要求必须至少有 2 个错误分类,或者它必须覆盖 5%的测试误差——无论哪个值更大都是我们采用的标准。
有趣的是,您可以根据自己的业务需求定制其他启发式方法。对一些用户进行错误分类比其他用户更糟糕,因此您可以将这一点纳入您的数据切片标准——想想精度/召回权衡。
3.总结和要点
所以,这就是你要的——荣耀的法国。
还是那句话,FreaAI 不是开源的,但希望它将来会向公众发布。与此同时,您可以将我们讨论的框架应用到您自己的预测模型中,并确定哪里存在系统性的表现不佳。
3.1.摘要
概括地说,FreeAI 使用 HDR 和决策树来减少预测器的搜索空间。然后,它反复查看单个特性以及组合,以确定哪里的性能较低。这些低性能领域有一些额外的启发,以确保调查结果是可操作的。
3.2.你为什么要在乎?
首先,这个框架帮助工程师识别模型的弱点。当这些弱点被发现时,它们可以(有希望)被纠正,从而改进预测。这种增益对于黑盒模型(如神经网络)特别有吸引力,因为没有模型系数。
通过隔离表现不佳的数据区域,我们获得了一个了解黑盒的窗口。
FreaAI 在其他应用方面也有着令人感兴趣的潜力。一个例子是识别模型漂移,这是当经过训练的模型随着时间变得越来越不有效时发生的情况。IBM 刚刚发布了一个用于确定模型漂移的假设测试框架。
另一个有趣的应用是确定模型偏差。在这种情况下,偏见是不公平的概念,例如基于性别拒绝给某人贷款。通过查看模型性能较低的不同数据分割,您可能会发现存在偏差的区域。
感谢阅读!我会再写 37 篇文章,把学术研究带到 DS 行业。查看我的评论,链接到这篇文章的主要来源以及一些有用的资源。
如何在不同类型的 SQL 中找到自己的路
入门,SQL 方言:什么,为什么,如何
MySQL、PostgreSQL、SQL Server……为什么会有这么多 SQL 方言存在?你应该选择哪一个?
在 SQL 丛林中寻找正确的方向(由伊森·赛克斯在 Unsplash 上拍摄)
无论你是数据库管理领域的初学者还是整天操纵表和视图的 SQL 专家,你可能已经听说过一些关于 SQL 的术语,比如“Transact-SQL”或“PostgreSQL”。为什么不简单的用“SQL”来谈… SQL?顺便问一下,你念" S. Q. L . “还是"续集”?
我最初认为,用来谈论 SQL 作为一种编程语言的术语的多样性是无聊的工程师或营销人员为了以不同的名称销售相同的“产品”而制造的过度复杂化的结果。然而,我必须承认这不是真的:SQL 编程语言和 SQL 编程语言确实存在差异。
这篇文章有几个目标。首先,它介绍了 SQL 创建和扩展背后的历史背景,这是这种编程语言进一步发展的基础。这将引导我们进入第二部分,旨在更好地理解各种 SQL 方言之间的差异。最后,我们将把这些见解放到中,帮助您选择学习和使用 SQL 作为管理数据库工具的最佳方式。
一点历史:为什么存在不同的 SQL 方言?
为了理解 SQL 方言的起源,我们必须回到关系数据库管理的基础(RDBM) 。1970 年,在 IBM 工作的英国计算机科学家埃德加·弗兰克·科德发表了一篇名为“大型共享数据库数据的关系模型”的论文。这就是他发明数据库管理关系模型的时候,从那时起它就成为了关系数据库和关系数据库管理系统的理论基础。
E.F. Codd 研究论文摘要(来源)
在这个突破性的发明之后,设计者选择用两种方式来表达关系模型:微积分(像 Ingres 那样)或者代数(像 IBM 那样)。代数赢得了这场战斗,这就是现在使用的方法。在此之后,在 80 年代后期做了一些努力来创建一个 SQL 标准。SQL 于 1986 年成为美国国家标准协会(ANSI)的标准,并于 1987 年成为国际标准化组织(ISO)的标准。自 20 世纪 80 年代以来,SQL 标准已经过多次修订,尽管标准 SQL 的核心特性自 1992 年以来一直保持稳定。
然而,新的变化不断出现,因为在不同厂商工作的数据库实现者需要解决新的问题或规避标准中没有解决的已经存在的问题。这解释了为什么多种 SQL 方言出现了,并且今天仍然共存。
总结一下这段历史介绍,目前的情况如下:只有一种 SQL 语言**,但是不同的数据库管理系统已经扩展了原始的 SQL 标准,以添加它们自己的功能或者使语法适应它们自己的运行方式。通过这样做,他们诞生了各种 SQL 方言。**
SQL 方言:有什么区别?
如您所知, SQL 是一种编程语言,用于提取数据和处理数据库中的数据。其中,SQL Server、Oracle、MySQL 和 PostgreSQL 是关系数据库管理系统(RDBMS)** ,出于我们前面提到的原因,它们有自己的 SQL 方言。**
对于新手或中级用户来说,这些差异主要可以在语法中看到,而对于我们大多数人(包括我自己)来说,这些差异背后的技术原因是相当模糊的。然而,这不会妨碍我们以最恰当的方式使用 SQL!
为了更好地理解四种 SQL 方言之间的主要差异,这里概述了 PostgreSQL、MySQL、Microsoft SQL Server 和标准 SQL 在 Google BigQuery 中使用时的一些语法特性。
四种 SQL 方言的语法差异(按作者分类的图表)
更进一步,你会在一个名为“关系数据库管理系统的比较”的专门维基百科页面上找到各种 RDBMS 之间差异的相对详尽的清单。
选择的时间:应该使用哪种 SQL 方言?
正如我们刚刚讨论的, PostgreSQL 是最接近标准 SQL 的,同时它是一种广泛使用的 SQL 方言。这就是为什么从学习 PostgreSQL 开始会给你最灵活的工具**,以便以后可能适应其他 SQL 方言并将你的技能转化到其他 RDBMS。**
做出决定的另一种方法可能是检查劳动力市场当前的技术技能需求。在其最新的开发者调查(2021) 中,堆栈溢出位列最受欢迎的数据库技术。顶级技术都包括 SQL,这加强了开始学习 SQL(无论何种方言)的必要性。在这项调查中, MySQL 在很大程度上主导了排名。
基于栈溢出上的问题标签看趋势时, MySQL 也排在其他 SQL 方言之前。微软 SQL Server 排名第二,因为它仍然被大公司广泛使用。
网站 DB-Engines 提出了另一个排名顺序,其中甲骨文排名第一。为了每年建立这个列表,他们分析了从搜索引擎(Google,Bing),Google Trends,IT 相关网站(Stack Overflow,DBA Stack Exchange),工作搜索引擎(实际上,只是雇佣),社交网络(LinkedIn,Twitter)检索的数据。
根据不同来源列出的前 3 种 SQL 方言(按作者排列的图表)
最后,还是务实一点吧。如果你在一家使用特定 RDBMS 的公司工作,那么你应该学习与之相关的 SQL 方言。例如,微软 SQL Server 在许多行业仍然占有很大的市场份额。如果你正在某个行业找工作,你可以浏览职位空缺,看看数据库管理所需的技能。这可能会给你一些宝贵的提示,告诉你为了自己的职业目标应该开始学习哪种 SQL 方言。
最后的想法
掌握 SQL 是数据库管理的必备技能。无论您选择从哪种 SQL 方言开始,基本原理都是相同的,并且是使用数据库的一个关键元素。
在你学习了基础知识之后,当你沿着职业道路前进时,你可能不得不学习几种 SQL 方言**。我的最后一条建议是:选择第一种 SQL 方言并坚持下去,直到你获得足够的知识,然后你将能够适应使用 SQL 的其他环境。**
举个例子,我在大学和在线课程中学到了 MySQL。但自从我在一家初创公司担任数据分析师以来,我已经将切换到了标准 SQL** ,因为它被用于谷歌大查询。希望这篇文章能帮助你在 SQL 之旅中选择正确的道路!**
你喜欢阅读这篇文章吗? 成为 的一员,加入一个不断成长的充满好奇心的社区吧!
https://marie-lefevre.medium.com/membership
如何微调问答转换器
了解如何微调问答的 ML 模型
Transformer 模型无疑是 NLP 中的领导者——在几乎所有其他任务中,它都胜过几乎所有其他模型架构。
变形金刚已经证明自己有能力处理的最有趣的基于语言的任务之一是问答。
在本文中,我们将了解 NLP 中的问答领域,并了解如何下载和微调世界上最先进的 transformer 模型。简而言之:
> Our Dataset
- Downloading SQuAD> Preparing The Data
- Extraction
- Encoding
- Initializing the Dataset> Fine-Tuning> Measuring Performance
- Implementing Accuracy
如果你喜欢视频——我在这里也涵盖了一切:
我们的数据集
对于我们的例子,我们将使用斯坦福问答数据集(SQuAD) 2.0 。我们的模型将被期望正确地选择回答我们提供的问题的文本片段的特定部分。
下载小队
我们可以像这样用 Python 下载数据:
这将返回两个文件,我们的训练数据train-v2.0.json
——和我们的验证数据dev-v2.0.json
。两者看起来都像这样:
JSON 预览——它包括几个层,这些层导致一个上下文(底部)和几个问题 - 答案对用于每个上下文
这里我们的数据有三个关键部分,它们是:
- 问题 —包含我们将向模型提出的问题的字符串
- 上下文 —包含我们问题答案的更大的文本片段
- 答案 —较短的字符串,是给定上下文的“摘录”,为我们的问题提供答案
因此,我们将为模型提供一个问题和相应的答案。然后,模型必须读取两者,并从上下文返回预测答案的标记位置。
准备数据
提取,血统
准备这些数据进行微调的第一步是将我们的问题、上下文和答案从 JSON 文件提取到训练和验证集中。我们可以这样做:
答案
这使我们的两个数据集在三个列表(每个列表)之间拆分:
相应的上下文、问题和答案集
我们的上下文和问题是简单的字符串——彼此对应。每个问题的答案都可以在上下文中找到。
答案列表略有不同,因为每个条目都是一个字典,其中答案包含在'text'
中,并且该答案在上下文中的起始位置也在'answer_start'
中提供。
这没问题,但是我们需要训练我们的模型在上下文中找到答案的开始和结束——所以我们也需要添加一个'answer_end'
值:
这给了我们:
字典的答案数组——现在包括一个修正的 answer_start 和一个 answer_end 位置
编码
我们的数据几乎准备好了;我们只需要将我们的字符串转换成记号——然后将我们的answer_start
和answer_end
索引从字符位置转换成记号位置。
使用内置的 HuggingFace 标记器很容易完成标记化,如下所示:
我们的上下文问题对现在被表示为Encoding
对象。这些对象合并每个相应的上下文和问题字符串,以创建 BERT 所期望的 Q & A 格式——它简单地将上下文和问题连接在一起,但用一个[SEP]
标记分隔开:
我们输入到模型中的数据(显示出来的数据被解码成人类可读的文本)只是上下文(包含答案)和问题的连接,用一个**【SEP】**标记分隔
这个串联版本存储在我们的Encoding
对象的input_ids
属性中。但是,数据存储为 BERT 可读的令牌 id,而不是人类可读的文本。
这个tokenizer
很棒,但是它没有产生我们的答案开始-结束令牌位置。为此,我们定义了一个定制的add_token_positions
函数:
这个函数向我们的Encoding
对象添加了另外两个属性— start_positions
和end_positions
。其中每一个都是一个简单的列表,包含答案的开始/结束标记位置,对应于它们各自的问题-上下文对。
train_encodings 和存储在对象内部的属性——包括令牌 start_positions 和 end_positions
正在初始化数据集
现在,我们已经准备好了数据,并且拥有了我们需要的一切—我们只需要将它转换成正确的格式,以便使用 PyTorch 进行培训。
为此,我们需要构建一个数据集对象,我们可以像这样轻松地完成:
我们将能够在训练和验证期间将这些数据集对象输入到我们的模型中。
微调
我们的数据现在完全可以供我们的模型使用了。我们现在要做的就是设置我们的 PyTorch 环境,初始化我们将在训练期间用来加载数据的DataLoader
——最后,开始微调我们的模型:
与大多数变压器模型相比,DistilBert 是一个小模型——但这仍需要一些时间来训练。
完成后,我们可以继续保存我们新微调的模型(如果需要,还有 tokenizer)——我们将把它保存到一个新目录models/distilbert-custom
:
拯救我们的
当再次加载我们的模型时,我们可以使用与从 HuggingFace 加载模型时相同的from_pretrained
方法——我们所做的就是用我们的本地路径替换在线模型路径/名称——在这种情况下:
model_path = 'models/distilbert-custom'
model = DistilBertForQuestionAnswering.from_pretrained(model_path)
tokenizer = DistilBertTokenizerFast.from_pretrained(model_path)
或者使用通用AutoModel
加载器:
model = AutoModel.from_pretrained(model_path)
tokenizer = AutoTokenizer.from_pretrained(model_path)
衡量绩效
一旦我们训练了我们的模型,我们就可以开始进行预测,并向我们的模型提出一些问题。
请记住,我们已经在使用一个高度优化的基础模型——这使我们成功了一半——但是我们仍然可以从不同模型的实验中受益。
但是现在,我们只需要学习这种方法——以及如何将它应用到我们自己更具体的用例中。所以让我们来学习一下后培训应该怎么做。
为了从我们的模型中提取开始-结束令牌范围,我们可以访问start_logits
和end_logits
张量并执行argmax
函数,如下所示:
start_pred = torch.argmax(outputs['start_logits'], dim=1)
end_pred = torch.argmax(outputs['end_logits'], dim=1)
start_pred 和 end_pred 张量将看起来像这样——这些是模型对开始-结束答案位置的预测。
我们在这里可以测量的最简单的度量是准确性——更具体地称为精确匹配(EM)——模型是否获得了正确的开始或结束标记?当然,这并没有考虑到模型越来越接近,可能会丢失一两个令牌——但这是一个开始。
为了计算每批的 EM,我们取每批匹配数的sum
,然后除以总数。我们用 PyTorch 这样做:
acc = ( (start_pred == start_true).sum() / len(start_pred) ).item()
最后的.item()
将张量值提取为一个简单的 Python int
。
精确匹配
现在我们知道了如何计算我们的问答准确度——让我们为我们的val_dataset
实现它吧!
我们再次设置了一个DataLoader
——然后遍历每一批。不过这一次,我们调用model.eval()
将相关层的行为从训练模式切换到推理模式,并使用torch.no_grad()
阻止 PyTorch 计算模型梯度(仅在训练期间需要):
在所有这些之后,我们有了一个——尽管是粗略的——精确度的衡量标准。
在 63.6% 的时间里,该模型设法获得正确答案的确切跨度(开始和/或结束)。63.6%当然不是一个特别令人印象深刻的数字,除非我们更深入地了解该模型的表现。
打印出我们数据集中最后一批问答对——尽管在大约 50%的情况下,它与真实答案的差距不到一个符号(或更少),但它只获得了 18.75%的 EM。
EM 指标并没有描绘出全貌——尽管在最后一批中 EM 得分仅为 18.75%——该模型在 4/8 的问题中确实非常接近,这并没有反映在 EM 得分中。
尽管如此,当测量和比较模型时,EM 是一个完美的起点。
本次演练到此结束!我们采用了一个预训练的 DistilBert 模型,给它安装了一个问答头,并使用小队数据集对它进行了微调。制作我们自己的问答模型。
然而,更重要的是,我们引入了获取问答数据集的过程,并使用它来“微调”预训练的 transformer 模型。
学习和理解这个过程非常重要,如果应用正确,可以将问答模型在特定用例上的性能提高很多。
您很可能不会使用 SQuAD 数据集进行任何微调,而是使用您自己的数据或开源问答数据集:
现在已经有大量的数据集可用,而且越来越多的数据集经常可用,所以有很多东西可以学习!
我希望你喜欢这篇文章!如果你有任何问题,请通过 Twitter 或在下面的评论中告诉我。如果你想知道更多类似的内容,我也会在 YouTube 上发布。
感谢阅读!
参考
使用自定义数据集进行微调,HuggingFace 的变形金刚文档
*除非另有说明,所有图片均出自作者之手