从头开始实现朴素贝叶斯
从头做起
只用 Python 和 NumPy 编写内部工作代码
我不喜欢“黑盒”。我个人有一种强烈的欲望想知道事情是如何运作的。我想摸摸它。我想修补一下。我想自己编码,即使已经存在一个即插即用的解决方案。这正是我们在这篇文章中要做的。
在接下来的部分中,我们将使用 Python 和 NumPy 一步一步地从头开始实现朴素贝叶斯分类器。
但是,在我们开始编码之前,让我们简单地谈谈朴素贝叶斯分类器的理论背景和假设。
朴素贝叶斯快速理论
朴素贝叶斯分类器的基本原理是贝叶斯定理——因此得名。在我们的例子中,我们可以将贝叶斯定理表述如下:
我们的总体目标是用给定的数据预测一个类的条件概率。这个概率也可以称为后验置信。那么我们如何计算后验概率呢?
首先,我们需要确定数据属于某一类分布P(Data|Class)
的可能性。然后我们需要将它乘以之前的P(Class)
。为了计算先验,我们需要计算特定类的样本数(行),然后除以数据集中的样本总数。
注意:为了简化计算,我们可以省略分母,因为
P(Data)
可以被视为一个归一化常数。然而,我们将不再收到从零到一的概率分数。
你可能会问,朴素贝叶斯有什么天真的?
朴素贝叶斯的一个重要假设是特征的独立性。这意味着,一个事件的发生不会影响另一个事件的发生。因此,这些特性之间的所有交互和关联都将被忽略。由于这个简化的前提,我们现在能够在计算具有多个特征的某个类的概率时应用乘法规则。
这基本上是我们从零开始实现朴素贝叶斯分类器所需要知道的全部。
概述
现在,我们简要地讨论了理论背景,我们可以考虑我们需要实施的不同步骤。这为我们提供了一个高层次的概述,我们可以将其用作某种蓝图。
- 拟合:计算(训练)数据集中每个类的汇总统计和先验
- 预测:计算(测试)数据集中每个样本的每个类的概率。因此,获得给定类别(高斯)分布的数据的概率,并将其与先验结合。
在下面,我们将只实现一个类。下面可以看到的骨架代码,我们将在下一节逐步完成。
从头开始实施
拟合数据
如概述中所述,我们需要计算每个类*(和特性)*以及先验的汇总统计数据。
首先,我们需要收集数据集的一些基本信息,并创建三个零矩阵来存储每个类的均值、方差和先验。
接下来,我们迭代所有的类,计算统计数据并相应地更新我们的零矩阵。
例如,假设我们的数据集中有两个唯一的类(0,1)和两个要素。因此,存储平均值的矩阵将具有两行和两列(2x2)。每个类一行,每个功能一列。
先验只是一个向量(1x2),包含单个类别的样本除以总样本量的比率。
汇总统计数据示例[图片由作者提供]
做一个预测
现在,稍微复杂一点的部分…
为了进行预测,我们需要获得数据属于某一类的概率,或者更具体地说,来自同一分布的概率。
为了方便起见,我们假设数据的基本分布是高斯分布。我们创建一个类方法,它返回新样本的概率。
高斯函数。μ =均值;σ =方差;σ =标准偏差。
我们的方法接收单个样本并计算概率。然而,从参数中我们可以看出,我们还需要提供平均值和方差。
因此,我们创建另一个类方法。该方法遍历所有类,收集汇总统计数据、先验信息,并为单个样本计算新的后验置信。
请注意,我们应用了对数变换,以便通过增加概率来简化计算。我们还返回具有最高后验置信度的类索引。
最后,我们可以用 predict 方法把它们联系起来。
测试我们的分类器
现在我们已经完成了朴素贝叶斯分类器,只剩下一件事要做了——我们需要测试它。
我们将使用虹膜数据集,它由 150 个样本组成,具有 4 个不同的特征*(萼片长度、萼片宽度、花瓣长度、花瓣宽度)*。我们的目标是在 3 种不同类型的虹膜中预测正确的类别。
通过绘制前两个特征对虹膜数据集进行概述[图片由作者提供]
通过运行上面的代码,我们加载并准备虹膜数据集,并训练我们的分类器。在对测试数据进行预测时,我们达到了大约 96.6%的准确率。
下面的混淆矩阵告诉我们,我们的分类器犯了一个错误,错误地将一类分类为二类。
混淆矩阵(预测数量)[图片由作者提供]
结论
在本文中,我们仅使用 Python 和 NumPy 从头实现了一个朴素贝叶斯分类器。我们了解了理论背景,并有机会以实际方式应用贝叶斯定理。
如果你不喜欢“黑盒”并且想要完全理解一个算法,从头开始实现它是获得关于内部工作的亲密和深入知识的最好方法之一。
你可以在我的 GitHub 上的这里找到完整的代码。
从零开始的 ML 算法
View list6 stories
喜欢这篇文章吗?成为 中级会员 继续无限学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
https://medium.com/@marvinlanhenke/membership
使用 pytesseract 实现光学字符识别(OCR)
我们如何将图像转换成机器编码的文本?
来源:维基百科
各位 python 爱好者,我想和你们分享一个简单但非常有效的 OCR 服务,它使用 pytesseract,并通过 Flask 提供 web 界面。
光学字符识别(OCR)可用于多种用途,例如用于支付目的的信用卡扫描,或将文档的.jpeg
扫描转换为.pdf
没有出卖使用 OCR 的想法,或者如何使用它,让我们开始编码过程!
先决条件
我们将使用 pytesseract(用于 OCR)和 Flask(用于 web 界面)
pip install pytesseractfrom PIL import Image
import pytesseract
注意:如果你在导入宇宙魔方时遇到一些问题,你可能需要下载并安装 pytesseract.exe,可以在这里找到。
安装后,您必须包含 pytesseract 可执行文件的路径,这可以通过一行代码完成:
pytesseract.pytesseract.tesseract_cmd = r'YOUR-PATH-TO-TESSERACT\tesseract.exe'
核心 OCR 功能
因为我们已经安装并导入了 pytesseract,所以让我们创建核心函数并检查它是否按预期工作:
def ocr_core(filename):
text = pytesseract.image_to_string(Image.open(filename))
return text
很简单,对吧? ocr_core 函数获取图像文件的路径并返回其文本组件。根据文档,image_to_string
函数使用英语作为默认识别语言,具体语言我们稍后再说。
此图片与 ocr_core 函数*【作者图片】*一起使用
结果如下:
ocr_core 函数将我们的图像作为字符串返回,耶!【作者图片】
一旦我们确定一切正常,就该为我们的 OCR 服务创建一个 web 界面了
使用 Flask 的 Web 界面
现在我们要创建一个简单的 html 表单,允许用户上传一个图像文件,并从中检索文本。
from flask import Flask, render_template, requestapp = Flask(__name__)# from ocr_core import ocr_core
# uncomment the line above, if your Flask fails to get access to your function, or your OCR & Flask are on different scripts
保持简单,让我们为上传的文件创建一些后端测试,例如允许的扩展名:
ALLOWED_EXTENSIONS = ['png', 'jpg', 'jpeg']def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
保持它的美丽和简单,就像《Python 的禅》建议的那样,对吗?
现在,让我们创建一个保存用户上传图像的路径:
import ospath = os.getcwd()UPLOAD_FOLDER = os.path.join(path, 'uploads\\')if not os.path.isdir(UPLOAD_FOLDER):
os.mkdir(UPLOAD_FOLDER)app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
如果您想知道我们为什么要导入os
: 我们将需要它来获取上传图像的路径(用于后端),并且在用户成功上传文件后为我们的<img>
标记创建一个源。如果你想特别指定一个文件夹,只需将os.getcwd()
更改为你想使用的任何路径。
如果你从未创建过“上传”文件夹,上面的代码也会为你创建(用爱)。
基于 HTML 表单的 web 界面
现在,让我们构建一个简单的表单,并将文件命名为upload.html
<!DOCTYPE html>
<html>
<head>
<title>OCR</title>
</head>
<body>
{% if msg %}
<h1 style="color: green"> {{ msg }} </h1>
{% endif %}<h1> Upload your file </h1><form method=post enctype=multipart/form-data>
<input type="file" name=file>
<input type="submit" name=Upload value="Upload">
</form>{% if extracted %}
<h1> Results: </h1>
{% endif %}{% if img_src %}
<img src="{{ img_src }}">
{% endif %}{% if extracted %}
<p> {{ extracted }} </p>
{% endif %}</body>
</html>
等等,为什么有大括号?嗯,不是完全 HTML 形式,我撒了个谎。实际上,这是 jinja 格式化,它允许我们相应地格式化我们的 HTML。这里没什么特别的,{% if %}
代表 if 语句,所以如果有什么消息,就会被包裹在<h1>
标签里,等等。
这个表单非常简单,但是不要忘记enctype=multipart/form-data
允许表单接受文件上传。
一旦后端进入操作,花括号内的所有变量就会出现,所以在上传任何东西之前,您的upload.html
应该是这样的:
你的upload.html should look like this.
【图片由作者提供】
重要提示:不要忘记将你的“upload.html”文件保存到与你的主 python 脚本相同的路径,不管你是在中心还是本地计算机上工作。如果你不知道你的工作目录在哪里,参考上面的
os.getcwd()
。
最终确定 OCR web 服务
一旦我们完成了以上所有的准备工作,就该通过 Flask 创建一个 web 界面了:
[@app](http://twitter.com/app).route('/', methods = ['GET', 'POST'])def upload_page():
if request.method == 'POST':
if 'file' not in request.files:
return render_template('upload.html', msg = 'No file selected')
file = request.files['file']if file.filename == '':
return render_template('upload.html', msg = 'No file')if file and allowed_file(file.filename):
file.save(os.path.join(app.config['UPLOAD_FOLDER'], file.filename))
extracted = ocr_core(file)
return render_template('upload.html',
msg = 'OCR completed',
extracted = extracted,
img_src = UPLOAD_FOLDER + file.filename)
else:
return render_template('upload.html')
if __name__ == '__main__':
app.run()
首先,我们检查请求方法是否为POST
,主要是为了安全措施。然后,我们必须检查文件是否被选择和上传(换句话说,文件是否到达后端)。一旦入门级测试完成,我们检查file
是否存在,以及它的格式是否被允许。
一旦满足所有这些条件,我们将上传的文件保存到上传文件夹,并将其赋给一个名为extracted
的变量
运行代码后,您应该会看到应用程序运行的地址:
应用程序运行的地址。【作者图片】
所以在上传之前,你的界面应该是这样的:
上传前的 App。【作者图片】
一旦您选择了您的文件,它应该会有如下的细微变化:
上传图片后的 App。【作者图片】
瞧啊。您刚刚创建了一个 OCR web 服务!
最后的想法
正如承诺的那样,我们已经创建了一个简单但非常有效的 OCR web 服务,它允许从图像中识别文本。但是有很多方法可以改善它。例如,您可以添加选项来选择 OCR 应该使用哪种语言。后端和前端都是这样:
def ocr_core(filename):
text = pytesseract.image_to_string(Image.open(filename), lang=selected_language)
return text
你所要做的就是在ocr_core
函数中指定lang
属性。并在您的upload.html
文件中添加一个<select>
标签。只是不要忘记从前端获取数据,并将其传递给你的ocr_core
函数
未来的改进。【图片作者】
希望这篇文章证明对你有用,并使一些事情变得更清楚。感谢您的时间,如果您有任何反馈或任何问题,请随时在下面留下评论。
再见。
从零开始用 Viterbi 算法实现英语单词的词性标注
词类是句子结构和意义的有用线索。以下是我们识别它们的方法。
本文的完整代码可以在这里找到
词性标注是为文本中的每个单词分配词性的过程,这是一项消除歧义的任务,单词可能有多个词性,我们的目标是找到适合这种情况的正确标签。
我们将使用一个经典的序列标记算法, 隐马尔可夫模型 来演示,序列标记是一个任务,其中我们给输入单词序列中的每个单词 x1 分配一个标签 y1,因此输出序列 Y 与输入序列 x 具有相同的长度。HMM 是一个基于扩充马尔可夫链的概率序列模型。马尔可夫链做了一个很强的假设,如果我们想预测序列中的未来,唯一重要的是当前状态。例如,预测我下周写一篇文章的概率取决于我本周写一篇文章,仅此而已。你可以观看视频这里更多的例子,我会说一个更详细的解释。
隐马尔可夫模型允许我们谈论观察到的事件——我们在 put 中看到的单词和隐藏事件——我们认为是概率模型中偶然因素的部分语音标签。
我们将使用 Brown Corpus 来实现我们的 tagger,其中每个文件包含标记化单词的句子,后跟 POS 标签,每行包含一个句子。您可以在这里找到描述标签的数据手册。注意,我们将使用二元模型 HMM 实现我们的 POS 标记器。
我们可以在下面看到我们的数据样本:
[('RB', 'manifestly'), ('VB', 'admit')]
首先,让我们创建一个生成 n 元文法的函数。
def ngrams(self, text, n):
n_grams = []
for i in range(len(text)): n_grams.append(tuple(text[i: i + n]))
return n_grams
转移概率
作者图片
一个 HMM 有两个组成部分,即 跃迁概率 A 和 发射概率 B 。
作者图片
转移概率是给定前一个标签时标签出现的概率,例如,动词 将 最有可能后跟另一种形式的动词,如 舞 ,因此它将具有高概率。我们可以使用上面的等式来计算这个概率,实现如下:
这里,我们将二元模型的计数除以我们创建的每个二元模型的一元模型计数,并将其存储在 transition_probabilities 字典中。
排放概率
作者图片
发射概率是给定标签,它将与给定单词相关联的概率。我们可以使用上面的等式来计算这个概率,实现如下:
在这里,我们将该单词后面的标签的计数除以同一标签的计数,并将其存储在 emission_probabilities 字典中。
HMM taggers 做了两个进一步简化的假设。第一个假设是,一个单词出现的概率只取决于它自己的标签,而与相邻的单词和标签无关;第二个假设,二元模型假设,是一个标签的概率只取决于前一个标签,而不是整个标签序列。将这两个假设代入我们的 bigram 标记器,得到最可能的标记序列的以下等式:
作者图片
解码 HMM
对于任何模型,例如包含隐藏变量(词性)的 HMM,确定对应于观察序列的隐藏变量序列的任务称为解码,这是使用 维特比算法 完成的。
维特比算法
维特比算法是一种动态规划 算法,用于获得最有可能的隐藏状态序列的最大后验概率估计—称为维特比路径——其产生一系列观察到的事件,特别是在马尔可夫信息源和隐藏马尔可夫模型 (HMM)的环境中。此外,一个很好的解释视频可以找到这里
维特比解码有效地从指数多的可能性中确定最可能的路径。它通过查看我们的传输和发射概率,将这些概率相乘,然后找到最大概率,从而找到一个单词相对于所有标签的最高概率。我们将为未知概率定义一个默认值0.000000000001。
我们将从计算初始概率/该状态的开始概率开始,这是单词开始句子的概率,在我们的例子中,我们使用了“开始”标记
def initial_probabilities(self, tag):
return self.transition_probabilities["START", tag]
测试
为了测试我们的解决方案,我们将使用一个已经分解成单词的句子,如下所示:
test_sent = ["We",
"have",
"learned",
"much",
"about",
"interstellar",
"drives",
"since",
"a",
"hundred",
"years",
"ago",
"that",
"is",
"all",
"I",
"can",
"tell",
"you",
"about",
"them",
]cleaned_test_sent = [self.clean(w) for w in test_sent]
print(self.vertibi(cleaned_test_sent, all_tags))
我们的结果一:
we,PPSS
have,HV-HL
learned,VBN
much,AP-TL
about,RB
interstellar,JJ-HL
drives,NNS
since,IN
a,AT
hundred,CD
years,NNS
ago,RB
that,CS
is,BEZ-NC
all,QL
i,PPSS
can,MD
tell,VB-NC
you,PPO-NC
about,RP
them,DTS
根据我们的文档,这是正确的。
我期待听到反馈或问题。
从头开始实施 PCA
将实现与 Scikit-Learn 的 PCA 进行比较
本文是故事的续篇主成分分析变量约简。在上一篇文章中,我谈到了一种最著名和最广泛使用的方法,称为主成分分析。它采用高效的线性变换,在获取最大信息量的同时降低高维数据集的维数。它生成主成分,即数据集中原始要素的线性组合。此外,我一步一步地展示了如何用 Python 实现这项技术。起初我认为这篇文章足以解释 PCA,但我觉得缺少了一些东西。我使用单独的代码行实现了 PCA,但是当您每次想为不同的问题调用它们时,它们是低效的。更好的方法是创建一个类,当您想在一个地方封装数据结构和过程时,这是很有效的。此外,由于所有代码都在这个独特的类中,修改起来真的很容易。
目录:
1.资料组
在实现 PCA 算法之前,我们将导入乳腺癌威斯康星数据集,该数据集包含关于在 569 名患者中诊断出的乳腺癌的数据
。
*import* pandas *as* pd
*import* numpy *as* np
*import* random
*from* sklearn.datasets *import* load_breast_cancer
import plotly.express as pxdata = load_breast_cancer(as_frame=True)
X,y,df_bre = data.data,data.target,data.frame
diz_target = {0:'malignant',1:'benign'}
y = np.array([diz_target[y1] for y1 in y])
df_bre['target'] = df_bre['target'].apply(lambda x: diz_target[x])
df_bre.head()
作者插图。
我们可以注意到有 30 个数字特征和一个目标变量,指定肿瘤是良性的(目标=1)还是恶性的(目标=0)。我将目标变量转换为字符串,因为 PCA 不使用它,我们只在以后的可视化中需要它。
在这种情况下,我们希望了解肿瘤是良性还是恶性时,其特征可变性的差异。这真的很难用简单的探索性分析来显示,因为我们有两个以上的协变量。例如,我们可以设想一个只有六个特征的散布矩阵,用目标变量来着色。
fig = px.scatter_matrix(df_bre,dimensions=list(df_bre.columns)[:5], color="target")
fig.show()
作者插图。
当然,我们可以在所有这些散点图中观察到两个不同的集群,但是如果我们同时绘制所有的特征,这将是混乱的。因此,我们需要这个多元数据集的一个紧凑的表示,它可以由主成分分析提供。
2.认证后活动的实施
PCA 程序的框图。作者插图。
上图总结了获取主成分(或 k 维特征向量)的步骤。将应用相同的逻辑来构建该类。
我们定义了PCA_impl
类,它在开始时初始化了三个属性。最重要的属性是我们想要提取的组件的数量。此外,我们还可以通过设置random_state
等于 True 并仅在需要时标准化数据集来每次重现相同的结果。
这个类也包括两个方法,fit
和fit_transform
,类似于 scikit-learn 的 PCA。虽然第一种方法提供了计算主成分的大部分程序,但fit_transform
方法也对原始特征矩阵 x 进行了变换。除了这两种方法,我还想可视化主成分,而无需每次指定 Plotly 表达式的函数。加速 PCA 产生的潜在变量的分析是非常有用的。
3.未经标准化的 PCA
最后,定义了PCA_impl
类。我们只需要毫不费力地调用类和相应的方法。
作者插图。
我们可以访问在fit
和fit_transform
方法中计算出的var_explained
和cum_var_explained
属性。值得注意的是,我们只用一个组件就捕获了 98%。让我们使用之前定义的方法来可视化 2D 和 3D 散点图:
pca1.pca_plot2d()
作者插图。
从可视化中,我们可以观察到出现了两个聚类,一个用蓝色标记,代表患有恶性癌症的患者,另一个关于良性癌症。此外,蓝色星团似乎比其他星团包含更多的可变性。此外,我们看到两组之间有轻微的重叠。
pca1.pca_plot3d()
作者插图。
现在,我们来看看包含前三个部分的 3D 散点图。它没有之前的散点图那么清晰,但即使在这个图中也出现了类似的行为。基于目标变量,肯定有两个不同的组。通过观察这种三维表示,我们发现了新的信息:两名恶性肿瘤患者相对于所有其他患者而言,似乎具有完全不同的价值观。这一点在我们之前展示的 2D 图或散点图中可以稍微注意到。
4.标准化的 PCA
让我们重复上一节的相同过程。我们只在开始时添加标准化,以检查结果中是否有任何差异。
作者插图。
与前一种情况不同,我们可以注意到关于主成分的值的范围更受限制,并且解释的方差的 80%被三个成分捕获。特别地,第一成分的贡献从 0.99 变为 0.44。这可以通过以下事实来证明:所有变量都具有相同的度量单位,因此,PCA 能够对每个特征赋予相同的权重。
pca1.pca_plot2d()
作者插图。
通过查看带有前两个分量的散点图,可以确认这些观察结果。聚类更加明显,并且具有更低的值。
pca1.pca_plot3d()
作者插图。
3D 表示更容易阅读和理解。最后,我们可以得出结论,两组患者具有不同的特征变异性。此外,仍有两个数据点与其余数据分开。
5.带 Sklearn 的 PCA
此时,我们可以应用 Sklearn 实现的 PCA 与我的实现进行比较。我应该指出,在这种比较中有一些差异需要考虑。虽然我的 PCA 实现基于协方差矩阵,但 scikit-learn 的 PCA 涉及输入数据的居中,并采用奇异值分解将数据投影到更低维的空间。
之前我们看到标准化是应用 PCA 前非常重要的一步。由于 Sklearn 的算法已经从每个特征的列中减去了平均值,所以我们只需要将每个数值变量除以它自己的标准偏差。
X_copy = X.copy().astype('float32')
X_copy /= np.std(X_copy, axis=0)
现在,我们将组件数量和 random_state 传递给 PCA 类,并调用fit_transform
方法来获取主组件。
作者插图。
sklearn 的 PCA 实现了与标准化实现的 PCA 相同的结果。
fig = px.scatter(components, x=0, y=1, color=df.label,labels={'0': 'PC 1', '1': 'PC 2'})
fig.show()
作者插图。
fig = px.scatter_3d(components, x=0, y=1,z=2, color=df.label,labels={'0': 'PC 1', '1': 'PC 2','2':'PC 3'})
fig.show()
作者插图。
同样,散点图复制了我们在上一节中看到的内容。
最终想法:
我希望这篇文章对你有用。本文的目的是提供主成分分析的一个更紧凑的实现。在这种情况下,我的实现和 Sklearn 的 PCA 提供了相同的结果,但如果您使用不同的数据集,有时它们可能会略有不同。这里的 GitHub 代码是。感谢阅读。祝您愉快!
参考文献:
[1] 乳腺癌威斯康星州(诊断)数据集
你喜欢我的文章吗? 成为会员 每天无限获取数据科学新帖!这是一种间接的支持我的方式,不会给你带来任何额外的费用。如果您已经是会员, 订阅 每当我发布新的数据科学和 python 指南时,您都会收到电子邮件!
从头开始实施 PCA
从头做起
仅用 Python 和 NumPy 来提高您的线性代数技能
Pawel Czerwinski 在 Unsplash 上的照片
我一直对简化数据的不同方式感到惊讶,这些方式使数据更容易获取和分析。这有时感觉像是巫术——简单地应用一个算法,它就以某种方式完成了工作。
主成分分析(PCA)就是其中一种算法。这是一种无监督的学习算法,目的是通过将一大组特征转换为一个较小的特征来降低维度,同时保留尽可能多的信息。
在本文中,我们将通过从头开始实现 PCA 来揭开一些巫术的神秘面纱。我们将使用 Python 和 NumPy 一步一步地完成这项工作。
概述
在直接进入实现细节之前,让我们从高层次上了解一下算法实际上是如何工作的。
主成分分析主要包括三个步骤:
- 首先,我们需要计算协方差矩阵。
- 一旦我们获得这个矩阵,我们需要使用特征分解来分解它。
- 接下来,我们可以基于特征值选择最重要的特征向量,以最终将原始矩阵投影到其降维的维度中。
在接下来的部分中,我们将仔细研究每个步骤,在一个单独的类中实现 PCA。下面我们已经可以看一下骨架类,它为我们提供了某种蓝图。
从头开始实施
计算协方差矩阵
如概述中所述,我们的第一步主要涉及协方差矩阵的计算。那么我们为什么需要这样做呢?
粗略地说,协方差矩阵告诉我们两个随机变量一起变化了多少。如果协方差为正,则两个变量相关,因此沿相同方向移动(增加或减少)。如果协方差是负的,那么变量是反向相关的,意味着它们向相反的方向移动*(例如,一个在增加,而另一个在减少)*
假设我们有一个包含三个要素的数据集。如果我们计算协方差矩阵,我们将得到一个 3x3 的矩阵,包含每一列与所有其他列及其自身的协方差。
协方差矩阵的例子[图片由作者提供]
当应用特征分解时,协方差矩阵将是我们的核心,允许我们选择主向量或主方向,这解释了我们数据集中的大多数方差。
现在我们知道了什么是协方差矩阵,我们仍然需要知道如何计算它。计算矩阵的公式可以表述如下:
如果我们预先将数据居中,我们可以分别省略*、【x-bar】、*、【y-bar】、的减法。简化我们的方程,用线性代数符号表示,我们得到如下结果:
其简单地是以平均值为中心的数据矩阵本身除以样本数量的点积。
有了这些新知识,我们就可以实现前两个类方法了。
注:我们通过减去平均值并除以标准差来标准化数据。这会将所有要素转换到相同的比例,从而为分析提供同等的贡献。也可以使用 NumPy 函数 numpy.cov(x)计算协方差矩阵。
分解协方差矩阵
我们实现的下一步主要关注协方差矩阵的分解,或者更具体地说,是特征分解。
特征分解描述了将矩阵分解为个特征向量和个特征值。特征向量为我们提供了关于数据方向的信息,而特征值可以解释为系数,告诉我们每个特征向量中包含多少方差。
长话短说,如果我们分解我们的协方差矩阵,我们获得特征向量,它解释了我们数据集中的大部分方差。我们可以用这些向量将原始矩阵投影到一个更低的维度。
那么我们如何分解协方差矩阵呢?
幸运的是,我们可以简单地依赖内置的 NumPy 函数,因为特征值和特征向量的*‘手动’*计算相当繁琐。我们唯一要做的事情,就是对特征值和特征向量进行相应的排序,允许我们根据指定的分量数选择最重要的特征向量。
投射并把它们放在一起
到现在为止,我们已经完成了大部分工作。
通过选择最重要的特征向量,我们现在能够将原始矩阵投影到低维空间中。这是通过取矩阵和特征向量的点积来实现的,这也可以解释为简单的线性变换。
在实现了剩下的两个方法之后,我们的最终类如下所示:
测试我们的实现
现在我们已经完成了我们的实现,只剩下一件事要做——我们需要测试它。
出于测试目的,我们将使用虹膜数据集,该数据集由 150 个样本组成,具有 4 个不同的特征*(萼片长度、萼片宽度、花瓣长度、花瓣宽度)*。让我们通过绘制前两个特征来看看原始数据。
通过绘制前两个特征对虹膜数据集进行概述[图片由作者提供]
我们现在可以实例化我们自己的 PCA 类,并将其放在数据集上。当运行下面的代码时,我们得到下面的结果。
投影到前两个主成分上的虹膜数据集[图片由作者提供]
通过应用 PCA,我们清楚地解开了一些类关系,并且更清楚地分离了数据。这种低维数据结构应该使分类任务变得容易得多。
结论
在本文中,我们从零开始用一种更加手动的方法实现了主成分分析。我们学习了主要的计算步骤,包括一些非常重要的线性代数概念。
主成分分析是一种简单而有效的方法来减少,压缩和解开高维数据。从零开始理解和实现该算法提供了一个很好的方法来构建强大的基础和修改我们的线性代数知识,因为它将许多重要的概念联系在一起。
你可以在我的 GitHub 上的这里找到完整的代码。
从零开始的 ML 算法
View list6 stories
喜欢这篇文章吗?成为 中级会员 继续无限学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
https://medium.com/@marvinlanhenke/membership
实现随机森林
雨果·德劳尼在 Unsplash 上拍摄的照片
在这篇文章中,我们将介绍随机森林背后的基本概念,讨论实现的实际问题,例如高度相关的特性、特性稀疏性、不平衡的类。然后,比较随机森林与 Boosting 树和决策树的概念和性能!如果你想讨论什么或发现错误,随时给我发电子邮件到 lvqingchuan@gmail.com:)
基本概念
构建随机森林
森林是如何建成的?随机森林通过 bagging(bootstrapping-aggregation 的简称)建立在决策树上。“随机性”来自两个部分:
- Bootstrapping 是一种通过重新采样来评估建模精度的通用工具。在随机森林的框架下,使用 bootstrapping 在每棵树之间创建随机变量。作为第一步,我们用替换从训练数据中取样。每个样本都具有与原始训练数据相同的数据点。我们用每个样本建立一棵树。为什么要替换取样?实际上,没有替换的采样可能以每棵树只有非常少的数据结束,例如,从 5,000 个数据点构建 100 棵树,每棵树只给我们 5 个数据点。这意味着高偏差。理论上,替换抽样有助于保持随机森林的低方差,并防止过度拟合。
- 为每个决策树随机选择部分特征。这再次增加了树之间的随机变化,并防止过度拟合。经验法则是从 sqrt(特征数量)开始,然后通过优化器(如梯度搜索)调整每棵树的随机特征数量。
一旦构建了树,我们通过每棵树输入一个数据点,并对分类问题进行多数投票,或者对回归问题计算每棵树输出的平均值。这叫做聚合。
边条:装袋和袋装
如上所述,Bagging 是一种集成方法(引导,然后聚合)。你不必在装袋系统中使用一棵树。然而,装袋的树使用了树的所有特征。
高度相关的特征
特征非常相似的情况下还能用随机森林吗?看情况。具有高相关性的特征可能在因果推理研究中引起问题。如果让随机森林通过移除多少杂质来评估要素的重要性,两个高度相关的要素的排名将会非常不同,因为第一个要素移除的杂质不会被第二个要素再次移除。因此,报告的第二个特征的重要性将明显低于第一个特征,尽管它们在与目标变量的关系方面密切相关。这种情况可以通过随机选择特性来稍微改善,但不能完全解决。此外,高相关性可能不是机器学习研究中的问题。当我们的目标是进行预测时,特征之间的高度相关性很少会损害预测分数。友情提示:当你有很多冗余特性和一个大数据集时,运行时间会很长。
特征稀疏度/密度
特征稀疏/密度意味着数据集不平衡,例如,零(一)个值支配二进制特征称为稀疏(密集)。特征稀疏/密度是一个严重的问题,对随机森林的性能有负面影响。在零值支配二进制特征的例子中,树将朝着零的方向生长。无信息分割导致运行时间长、树深度大和错误率高。
种植一棵有偏见的树
如果不考虑运行时,构建更多的树可能有助于解决这个问题,因为每棵树都有另一组随机选择的特性。但是,不建议使用这种方法,因为随机森林在设计上容易变得稀疏/密集。如果您喜欢使用树算法,XGBoost 对稀疏/密集数据不敏感,也值得一试。
不平衡的班级
类别不平衡意味着一个或几个类别占了大部分样本。在极端情况下,您预测的是一个二进制类,而几乎所有的训练样本都属于零类。然后,您的决策树可能会预测所有训练样本都落在零类中,并在训练阶段达到“假的”(或“夸大的”)高精度。当看不见的测试数据确实经常落在一个类中时,这可能会给你带来麻烦。您也可以将这种极端情况推广到多标签分类。
要检测不平衡的类是否会提高模型性能:
- 使用修改后的准确度分数。通常,准确度=正确预测的样本数/总样本数。要使其反映出阶级不平衡的影响,用精度* = 1/2 (正预测精度+负预测精度)。当你的分类器在每个类上表现得不一样好时(换句话说,多数类膨胀了模型性能),修改后的精度将低于常规精度;当你没有不平衡的阶级问题时,准确度* =准确度。
要解决这个问题,你有两个简单的方法:
- ’ class_weight ‘参数:这是一个可选的超参数,供您在决策树或随机森林模型中指定。对于不平衡的类,你可以设置’ class_weight’ = 'balanced '此模式自动调整输入数据中与类别频率成反比的权重,如 n _ samples/(n _ classes * NP . bin count(y))。
- 对代表性不足的类别进行过采样:通过制作属于少数类别的样本的副本,我们增加了少数类别的类别频率,从而朝着 50/50 的类别频率分割移动(用于二进制分类)。然而,我们需要确保原始数据没有偏见。再次考虑一个极端的二元情况,当我们在 A 类中有 10,000 个样本,在 B 类中有 10 个样本时,我们希望确保这 10 个样本确实包含关于 B 类人口的信息。事实上,10 个样本的特征很少能提供 B 类的所有信息;他们可能只是局外人。
随机森林和决策树
随机森林是一种基于决策树的集成方法。通过为每棵树引导样本和随机选择的特征,然后在所有树的最终输出上聚集决策(多数投票或平均),随机森林应该比决策树更好地处理过拟合问题和噪声。从统计学导论课程中,我们知道:
均方差=方差+偏差。
在大多数情况下,随机森林能够减少方差(过拟合)部分的误差。在我的沃尔玛食品销售预测和爱荷华州房价预测项目中,Random Forest 确实胜过决策树。但是决策树能胜过随机森林吗?的确是的!由于随机森林专注于解决上述公式的“方差”部分,您可以想象,当涉及到对没有太多噪声的训练数据进行预测时,决策树不一定表现得更差-其偏差不必比随机森林设计得更高。另一个原因是随机森林的性能随着树的数量而稳定。换句话说,当你有一个非常大的训练数据集和非常少的特征时,一个决策树就足够了。(再次考虑一个极端的例子,你有 10,000 个样本和两个特征。你的随机森林能在这里施展什么魔法?)
随机森林和梯度增强树
Boosting 是一个连续的过程,不断更新前一阶段弱分类器的权重,最终得到一个稳定的最大化分类器集合。把分类器想象成分区,你给那些样本应该在别处的分区分配更多的权重。构建 M 个梯度提升树序列的步骤:
- 初始化一个最优常数模型:单个终端节点树,每个样本的初始权重为 1/N,假设总共有 N 个样本。
- 对于接下来的每棵树:
1.为 N 个采样点中的每一个计算梯度下降(因此梯度提升非常耗时:/);然后,用梯度下降参数拟合树。
2.用当前树计算最小化损失函数的权重。
该权重将作为下一个树的一部分应用:
k 是树 m 的分区总数,I()是指示函数。
- 从最后一棵树输出预测。搞定!
一个简单的比较告诉你梯度提升树,不同于随机森林,不使用额外的采样方法,按顺序而不是并行构建树,最后不使用聚集策略。直观地说,如果你仔细调整参数,梯度推进树比随机森林更好地处理偏差。经验研究表明,当树的数量足够大时,梯度增强树几乎总是优于随机森林,稳定的梯度增强树通常比稳定的随机森林更好。然而,如上所述,当数据中有大量噪声时,随机森林非常好。在这种情况下,梯度增强树有很大的机会过度拟合。
注:如果你对基本机器学习模型的详细解释感兴趣,我推荐 Hastie、Tibshirani 和 Friedman 的《统计学习的要素》。
用 PyTorch 和 OpenCV 实现实时目标检测系统
使用 python 实现实时对象检测系统的实践指南
文森特·梵高(1853–1890),巴黎,1887 年 5 月至 7 月(来源)
无人驾驶汽车可能仍然很难理解人类和垃圾桶之间的区别,但这并不影响最先进的物体检测模型在过去十年中取得的惊人进展。
结合 OpenCV 等库的图像处理能力,现在在几个小时内构建一个实时对象检测系统原型要容易得多。在本指南中,我将尝试向您展示如何开发一个简单的对象检测应用程序的子系统,以及如何将所有这些放在一起。
Python vs C++
我知道你们中的一些人可能会想为什么我使用 Python,对于实时应用程序来说,它是不是太慢了,你是对的;在某种程度上。
大多数计算繁重的操作,如预测或图像处理,都是由 PyTorch 和 OpenCV 执行的,它们都在幕后使用 c++来实现这些操作,因此,如果我们在这里使用 c++或 python,不会有太大的区别。
但同样,它只是一个原型,只有很少的基础设施代码和附加开销。如果你想学习生产级的实时实现,我建议你不要选择 python,至少现在不要。
读取视频流
您的输入视频流源可以是任何东西,您可能想从您的网络摄像头读取,或解析已存在的视频,或从连接到网络的外部摄像机。无论是什么问题,OpenCV 都是解决方案。在这个例子中,我将展示如何从 youtube 或网络摄像头读取视频流。
您可以使用 OpenCV 创建一个视频流来支持您的应用程序。(来源
从你的试管中读取
对于您的原型,您可能不想出去创建一个新的视频,而是使用许多在线可用的视频之一。在这种情况下,你可以从 youtube 上阅读视频流。
import cv2 # opencv2 package for python.
import pafy # pafy allows us to read videos from youtube.URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" #URL to parse
play = pafy.new(self._URL).streams[-1] #'-1' means read the lowest quality of video.
assert play is not None # we want to make sure their is a input to read.
stream = cv2.VideoCapture(play.url) #create a opencv video stream.
从网络摄像头读取
有时候你只想看看自己的脸。在这种情况下,请随意使用内置网络摄像头。
import cv2stream = cv2.VideoCapture(0) # 0 means read from local camera.
读取 IP 摄像头
如果您正在构建一个将部署在服务器上的应用程序,您的摄像机将拥有一个 IP 地址,您可以从该地址访问视频流。
import cv2camera_ip = "rtsp://username:password@IP/port"
stream = cv2.VideoCapture(camera_ip)
加载模型
今天的机器学习工程师被选择宠坏了,或者我应该说被选择弄糊涂了。有许多伟大的对象检测模型,每一个都有其优点和缺点。为了简单起见,我们将使用 YoloV5 ,因为它为我们提供了快速推理,这对我们的实时应用程序至关重要。你也可以看看其他的模型,比如 FasterRCNN。
根据 Yolov5 文件,它是目前市场上最快的型号。(来源)
我们可以直接从 PyTorch hub 加载模型,第一次运行代码可能需要几分钟,因为它会从互联网上下载模型,但下一次它将直接从磁盘加载。
from torch import hub # Hub contains other models like FasterRCNNmodel = torch.hub.load( \
'ultralytics/yolov5', \
'yolov5s', \
pretrained=True)
对单帧进行评分
俗话说“千里之行,始于足下”,所以我们可以说“解析一个视频流,始于一帧”。让我们看看如何对单个帧进行评分和解析。
我们用来执行推理的设备对我们的推理速度产生了巨大的影响,现代深度学习模型在与 GPU 一起工作时效果最佳,所以如果你有一个带有 CUDA 内核的 GPU,它将大幅提高你的性能。在我的经验中,即使只有一个 GPU 的系统也可以达到每秒 45-60 帧,而 CPU 最多只能给你 25-30 帧。
"""
The function below identifies the device which is availabe to make the prediction and uses it to load and infer the frame. Once it has results it will extract the labels and cordinates(Along with scores) for each object detected in the frame.
"""def score_frame(frame, model):
device = 'cuda' if torch.cuda.is_available() else 'cpu'
model.to(device)
frame = [torch.tensor(frame)]
results = self.model(frame)
labels = results.xyxyn[0][:, -1].numpy()
cord = results.xyxyn[0][:, :-1].numpy()
return labels, cord
在框架上绘制方框
一旦我们对该帧进行了评分,我们就需要在将该帧写入输出流之前,在该帧上绘制出已标识的对象及其盒子。为此,我们可以使用 OpenCV 的图像处理工具包。
"""
The function below takes the results and the frame as input and plots boxes over all the objects which have a score higer than our threshold.
"""
def plot_boxes(self, results, frame):
labels, cord = results
n = len(labels)
x_shape, y_shape = frame.shape[1], frame.shape[0]
for i in range(n):
row = cord[i]
# If score is less than 0.2 we avoid making a prediction.
if row[4] < 0.2:
continue
x1 = int(row[0]*x_shape)
y1 = int(row[1]*y_shape)
x2 = int(row[2]*x_shape)
y2 = int(row[3]*y_shape)
bgr = (0, 255, 0) # color of the box
classes = self.model.names # Get the name of label index
label_font = cv2.FONT_HERSHEY_SIMPLEX #Font for the label.
cv2.rectangle(frame, \
(x1, y1), (x2, y2), \
bgr, 2) #Plot the boxes
cv2.putText(frame,\
classes[labels[i]], \
(x1, y1), \
label_font, 0.9, bgr, 2) #Put a label over box.
return frame
一旦完成,这个函数将产生类似这样的输出。
你可以尝试不同的物体有不同的颜色。(图片由作者提供)
一个将他们团结起来的功能
很抱歉引用了《指环王》,但是是的,现在我们把它们都放在一个单独的调用函数中,在一个循环中执行整个操作。
因此,让我们回顾一下我们的主函数要成功运行应用程序必须执行的步骤。
- 创建视频流输入。
- 加载模型。
- 当输入可用时,读取下一帧。
- 对框架进行评分,以获得标签和坐标。
- 在探测到的物体上画出方框。
- 将处理后的帧写入输出视频流。
六个简单的操作步骤,虽然我们会添加一些基础设施代码来帮助我们使应用程序更加健壮,但基本原理是一样的。所以让我们开始吧。
"""
The Function below oracestrates the entire operation and performs the real-time parsing for video stream.
"""
def __call__(self):
player = self.get_video_stream() #Get your video stream.
assert player.isOpened() # Make sure that their is a stream.
#Below code creates a new video writer object to write our
#output stream.
x_shape = int(player.get(cv2.CAP_PROP_FRAME_WIDTH))
y_shape = int(player.get(cv2.CAP_PROP_FRAME_HEIGHT))
four_cc = cv2.VideoWriter_fourcc(*"MJPG") #Using MJPEG codex
out = cv2.VideoWriter(out_file, four_cc, 20, \
(x_shape, y_shape))
ret, frame = player.read() # Read the first frame.
while rect: # Run until stream is out of frames
start_time = time() # We would like to measure the FPS.
results = self.score_frame(frame) # Score the Frame
frame = self.plot_boxes(results, frame) # Plot the boxes.
end_time = time()
fps = 1/np.round(end_time - start_time, 3) #Measure the FPS.
print(f"Frames Per Second : {fps}")
out.write(frame) # Write the frame onto the output.
ret, frame = player.read() # Read next frame.
您应该将所有这些组件打包到一个好的类中,该类可以与 URL 和您希望将输出流写入的输出文件一起被调用。您的最终产品将看起来像这样。
60 FPS 的实时处理视频输出流。(图片由作者提供)
结论
当然,生产级别的实时应用程序要比这复杂得多,但是本指南并不打算教这个。它将向您展示 Python 的惊人威力,它允许我们在几个小时内构建如此复杂的应用程序原型。这里的可能性只受到你的想象力的限制。
你可以在我的 Github 个人资料上查看完整的代码和更多这类令人敬畏的应用。
如果你喜欢这本指南。
留下评论让我知道你的想法。
通过 Twitter 、 Linkedin 或 Medium 与我联系。
与你的网络分享这篇文章。
编码快乐!!
在 Keras 中实现单触发探测器(SSD ):第二部分——损失函数
Keras 中的物体检测
在 Keras 中实现 SSD 丢失功能
一.导言
在本系列的第一部分中,我们已经了解了固态硬盘的网络结构。本文现在将深入探讨“网络如何学习”。更具体地说,我们将研究它的损失函数,以及如何用它来优化网络的参数。像往常一样,文章将首先单独讨论一些主要概念。然后,会解释那些概念是如何构成 SSD 损失函数的。最后,本文将以代码示例结束,向您展示如何在 Keras 中实现它。
这篇文章是一个更大的系列的一部分,叫做在 Keras 中实现单次检测器(SSD)。下面是系列的概要
二。概念
损失函数— L( ŷ,y )
损失函数是用于在训练期间产生损失值的数学公式。在训练期间,模型的性能通过模型为每个样本或每批样本产生的损失( L )来衡量。损失主要衡量预测值( ŷ )与期望值( y )之间的“距离”(Pere,2020)。如果 ŷ 离 y 很远(很不一样),那么损耗就高。然而,如果 ŷ 接近于 y ,则损耗较低。该模型使用损失作为“指标”来更新其参数,以便在未来的预测中产生非常小的损失。这意味着生产非常接近 y 的ŷ。参数更新过程(也称为反向传播)可以通过使用优化算法(梯度下降、Ada-grad、Adam 等)来实现。
优化算法本身就是一个巨大的研究领域。因此,我建议你把它添加到你的“要学习的 ML 主题”列表中。
回归和分类损失函数
机器学习中的两个常见任务是回归和分类。为了在训练期间测量模型性能,在每个任务中使用不同的损失函数。在分类任务中,模型试图预测一组“离散”的值(深度学习去神秘化,2020)。这项任务的损失函数倾向于将概率值作为输入。因此,模型最后一层的输出在用作损失函数的输入之前,首先通过适当的激活层(用于二分类的 Sigmoid,用于多类分类的 Softmax)。对于回归任务,模型试图预测“连续”值(深度学习非神秘化,2020)。回归任务的损失函数倾向于直接从网络的最后一层获取值,而不必将这些值转换为概率。
可以参考这篇文章通过深度学习去神秘化了解更多关于损失函数的知识。
平滑 L1 损耗
作者图片
平滑 L1 损失是一种回归损失函数。平滑 L1 损耗有几个变体,但 SSD 中使用的是休伯损耗的特例,δ = 1。你可以把它看作是 L1 损失和 L2 损失的组合。当| a| 小于或等于 1 时,则表现为 L2 损失。否则,它的行为就像 L1 的损失。它首先由 Ross Girshick 在他的论文“Fast-RCNN”中提出,作为物体检测的 L2 损失的替代。根据 Girshick (2015 年):
平滑 L1 损失是一种稳健的 L1 损失,与 R-CNN 和 SPPnet 中使用的 L2 损失相比,它对异常值不太敏感。当回归目标是无界的时,具有 L2 损失的训练可能需要仔细调整学习速率,以防止爆炸梯度。 (Girshick,2015 年,第 3 页)
软最大损失(又名分类交叉熵损失)
作者图片
Softmax 损失是一种分类损失函数。更具体地说,它用于多类分类。它由两个步骤组成。首先,将网络最后一层的输出输入到 Softmax 激活中,以概率分布的形式为每个类别生成一个置信度得分,其中所有值的总和为 1。其次,得到的概率分布然后被用作交叉熵损失的输入。交叉熵损失测量标签的预测概率分布和实际概率分布之间的差异。值得注意的是,为了将实际的标签 y 转换成概率分布,使用了一种称为一键编码的方法。
三。SSD 损失函数(学习目标)
SSD 学什么?
由作者编辑。来源:卡斯滕·怀恩吉尔特
回想一下,在本系列的第一部分中,我们了解到 SSD 输出的预测 ŷ 具有(total _ default _ box, num_classes + 1 + 4 + 8)的形状。每个total _ default _ box项目中包含的内容有:
- num _ classes+1背景类的置信度得分
- 4 边界框属性:到匹配的默认框中心的 x 偏移( cx )、到匹配的默认框中心的 y 偏移( cy )、边界框宽度的对数比例变换( w )、边界框高度的对数比例变换( h )
- 4 默认框值:默认框从图像左侧的中心 x 偏移、默认框从图像顶部的中心 y 偏移、默认框的宽度和默认框的高度
- 4 方差值:用于编码/解码边界框数据的值
在上述 4 点中,只有前两点可由 SSD 网络学习。其他值是常量,用于帮助将来的边界框解码过程。
为了找到基础事实标签匹配边界框,执行匹配过程。我们将在以后的文章中研究这个匹配过程,以及如何对基本事实边界框和置信度得分进行编码。
它是如何学习的?
作者图片
SSD 将回归损失( L_loc )和分类损失( L_conf )与α值组合在一起,作为定位损失的比例因子。 L_loc 是匹配的正框的所有边界框属性( cx , cy , w , h )的平滑 L1 损失的总和。这意味着它不考虑其类为背景类的默认框或不与任何基础真值框匹配的默认框。通过这种方式,它将因做出包含对象的边界框预测而获得奖励。为了产生 L_conf ,作者使用 Softmax loss 分别计算匹配的正默认框和负默认框的损失,然后将它们加在一起。负框也被考虑到分类损失中的原因是因为我们也想惩罚错误的预测,同时奖励背景类的正确预测。
四。代码
本文中显示的所有代码都可以在这个回购:https://github.com/Socret360/object-detection-in-keras。在本文中,我将展示代码的关键片段,并提供包含完整代码的相关文件的链接,而不是直接展示所有代码示例。这将确保文章不会因代码示例而过载。GitHub repo 中的大部分代码都是从https://github.com/pierluigiferrari/ssd_keras中获取和修改的。
在 Keras 中,损失函数可以传递给 model.compile 方法。损失函数将被赋予两个自变量 y_true 和 y_pred。这实质上是 y y 和ŷ的*。为了构建整体损失函数,我们首先需要对 Softmax 损失和 Smooth L1 损失的损失函数进行编码。你可以参考 smooth_l1_loss.py 和 softmax_loss.py 来实现这些目标。另一个需要注意的重要事情是,在计算 SSD 损耗时,你会发现负默认框比正默认框多。为了补救这种情况并防止类不平衡,执行硬负挖掘。这实质上意味着只保留适当数量的负默认框。*
以下代码片段演示了 Keras 中的 SSD 丢失功能:
结论
本文演示了在 Keras 中实现 SSD 丢失功能所需的概念和代码。在下一篇文章中,我们将了解用于为 SSD 生成训练数据的 Keras 数据生成器。
喜欢这篇文章并想表达你的支持?关注我或给我买咖啡
参考
布朗利,J. (2020 年 12 月 22 日)。机器学习交叉熵的温和介绍。机器学习精通。检索自https://machine learning mastery . com/cross-entropy-for-machine-learning/
深度学习去神秘化(2020)。损失函数解释。从 https://deeplearningdemystified.com/article/fdl-3取回
吉尔希克河(2015 年)。Fast-RCNN。https://arxiv.org/pdf/1504.08083.pdf
劳尔戈麦斯(2018 年 5 月 23 日)。理解范畴交叉熵损失、二元交叉熵损失、Softmax 损失、Logistic 损失、焦点损失以及所有那些容易混淆的名称。劳尔·戈麦斯博客。从 https://gombru.github.io/2018/05/23/cross_entropy_loss/取回
Koech,E. K. (2020 年 10 月 3 日)。交叉熵损失函数。走向数据科学。检索自https://towards data science . com/cross-entropy-loss-function-f 38 C4 EC 8643 e
刘,w,安盖洛夫,d,尔汉,d,塞格迪,c,里德,s,傅,C.Y,&伯格,A. C. (2016)。SSD:单次多盒探测器。https://arxiv.org/abs/1512.02325
Pere,C. (2020 年 6 月 17 日)。什么是损失函数?。走向数据科学。检索自https://towards data science . com/what-is-loss-function-1e 2605 aeb 904
d . rade ci(2020 年 6 月 19 日)。 Softmax 激活功能说明。走向数据科学。检索自https://towards data science . com/soft max-activation-function-explained-a 7 E1 BC 3 ad 60
在 Keras 中实现单触发探测器(SSD ):第三部分——数据准备
Keras 中的物体检测
实施 Keras 的 SSD 数据生成器
给狗的图像贴标签。由作者编辑。来源: Alvan Nee
一.导言
在之前的文章中,我们已经了解了 SSD 的网络结构(第一部分)和用于训练网络的 SSD 损失函数(第二部分)。本文关注在 Keras 中实现 SSD 的下一个重要步骤:为训练准备数据。本文将首先讨论用于训练 SSD 网络的 PASCAL VOC 数据集。然后,它将研究如何从该数据集中准备一个适合训练的格式的训练样本的细节。本文将以一个用于训练 SSD 网络的 Keras 数据生成器的完整代码示例结束。
本文是一个更大的系列的一部分,称为在 Keras 中实现单次检测器(SSD)。下面是这个系列的概要
第一部分:网络结构第二部分:损失函数 第三部分:数据准备(本文) 第四部分:数据扩充第五部分:预测解码</implementing-single-shot-detector-ssd-in-keras-part-vi-model-evaluation-c519852588d1?sk=797df0a4bf29d36ddd1e7ee9fe5c81a3>
二。SSD 数据准备
图一。为 SSD 网络准备训练数据的过程。来源:图片由作者提供。
你可以在他们的官方网页这里找到更多关于 PASCAL VOC 数据集的信息。
在 SSD 论文中,作者使用不同的数据集来训练/测试他们的 SSD 网络。对于 SSD 的实施,重点将放在 PASCAL VOC 数据集上。这是一个流行的数据集,用于训练/基准对象检测网络。数据集中有 20 个对象类。这些类别是:人、鸟、猫、牛、狗、马、羊、飞机、自行车、船、公共汽车、汽车、摩托车、火车、瓶子、椅子、餐桌、盆栽植物、沙发、电视/监视器。它由 xml 格式的图像及其相应的标签组成。在标签文件中,对象的类以字符串格式保存。每个对象的边界框以角格式保存(xmin、ymin、xmax、ymax)。
如前所述,PASCAL VOC 数据集中的每个训练样本都由一个图像及其对应的标签 xml 文件组成。为了将训练样本转换成适合训练 SSD 的格式,我们需要执行两个主要任务。首先,我们需要读取图像,将其调整到合适的尺寸(例如 300x300),然后将其归一化。为了实现 SSD,使用了相对于 ImageNet 平均值的零中心标准化。零中心归一化调整像素值的分布,使其以零为中心(Brownlee,2019)。其次,我们需要创建一个形状数组(num_default_boxes,num_classes + 4 + 8d ),它与 SSD 网络的输出形状相匹配。在此任务中,我们需要读取类标签并对它们进行一次性编码,将所有边界框读入内存,缩放它们以适应输入大小,将它们与正确的默认框匹配,并将它们编码到适合 SSD 网络预测的范围。在这些子任务中,最复杂的两个是匹配过程和编码过程。因此,我们将在下面详细讨论它们。
将基础事实框与默认框相匹配
匹配过程依赖于基础真值框和所有默认框之间的交集/并集(也称为 IOU,框重叠)的计算。检索 IOU 值后,匹配过程包括三个步骤。首先,每个基本事实框与具有最高 IOU 的默认框相匹配。这确保了基础事实框在训练期间将具有匹配的默认框。第二,剩余的默认框与 iou 大于某个阈值(例如 0.5)的基本事实框相匹配。这允许基础事实框与多个默认框匹配。第三,还会有既不是背景也没有足够的 IOU 分数来作为匹配的框,这些框被称为“中性框”。找到这样的默认框也很重要,这样我们就可以适当地设置它们的类标签。
编码边界框
图 2:编码边界框属性的公式。来源:作者图片
SSD 的这种实现使用基于质心的包围盒格式来训练网络。因此,为了对边界框属性进行编码,我们使用 Figure 2–1 中的公式。此外,SSD 的许多实现用相应的方差(σ)值来划分每个边界框属性,以进一步规范化它们的范围。根据毛(2019),这种方法可能不是正确的方式。他认为:
“在我看来,这实际上是一个标准归一化的过程,而不是用方差进行编码……这样,如果编码后的包围盒 X’遵循某种高斯分布,经过归一化后,该分布将变成均值为 0、方差为 1 的标准正态分布。这将是机器学习预测的理想选择。”
因此,不是用相应的方差(σ)值除每个编码的边界框属性,而是用相应的标准差(σ)除编码的边界框属性,如图 2–2 所示。
三。Keras 的固态硬盘数据生成器
本文中显示的所有代码都可以在这个回购:【https://github.com/Socret360/object-detection-in-keras。在本文中,我将展示代码的关键片段,并提供包含完整代码的相关文件的链接,而不是直接展示所有代码示例。这将确保文章不会因代码示例而过载。GitHub repo 中的许多代码都是从 https://github.com/pierluigiferrari/ssd_keras 的中截取并修改而来的。
下面的代码演示了如何编写 Keras 的数据生成器来训练 SSD 网络,如第一部分所示。你需要参考match _ gt _ boxes _ to _ default _ boxes . py来匹配边界框和缺省框,参考 encode_bboxes.py 来相应地编码边界框。
四。结论
从本文中,您了解了如何从 PASCAL VOC 数据集准备一个训练样本,并将其转换成与 SSD 网络训练兼容的格式。在下一篇文章中,我们将研究如何用物体包围盒来扩充训练图像。
喜欢这篇文章并想表达你的支持?关注我或给我买咖啡
动词 (verb 的缩写)参考
j . brown lee(2019 年 3 月 25 日)。深度学习如何手动缩放图像像素数据。机器学习精通。检索自https://machine learning mastery . com/how-to-manually-scale-image-pixel-data-for-deep-learning/
刘,w,安盖洛夫,d,尔汉,d,塞格迪,c,里德,s,傅,C.Y,&伯格,A. C. (2016)。SSD:单次多盒探测器。https://arxiv.org/abs/1512.02325
毛,L. (2019)。物体检测中的包围盒编码和解码。检索自https://lei Mao . github . io/blog/Bounding-Box-Encoding-Decoding/
在 Keras 中实现单发探测器(SSD ):第四部分——数据扩充
Keras 中的物体检测
用于增加 SSD 网络的训练样本的 Python 代码
由作者编辑。来源:贾斯汀·艾金
一.导言
在本系列的最后三部分中,我们已经完成了 SSD 网络训练的完整管道。我们对 SSD 网络、其损失函数进行编码,并将数据样本格式化/准备成适合训练的格式。虽然仅使用这些组件设置来训练网络是可能的,但 SSD 的作者建议,数据增强是提高网络泛化能力的关键(刘,Anguelov,Erhan,Szegedy,Reed,Fu & Berg,2016)。因此,本文主要关注 SSD 论文中提到的光度增强和几何增强这两种数据增强技术。
这篇文章是一个更大的系列的一部分,叫做在 Keras 中实现单次检测器(SSD)。下面是系列的概要
注意:尽管这篇文章单独讨论了每种增强技术中的不同方法,但在实践中,这些方法被链接在一起以创建一个增强管道,该管道能够产生足够随机化的新图像来训练 SSD 网络。
二。光度增强
光度增强通过转换原始图像的 RGB 通道来生成新的数据样本。这是通过将每个原始像素值( r 、 g 、 b )转换成新的像素值(r′、g′、b′)(泰勒&尼茨克,2015)。在对象检测中,这种增强技术影响原始图像的照明和颜色,但是不影响图像内对象周围的边界框。当我们想要用各种颜色和光照条件描绘物体时,这种增强技术是有用的。我们可以使用许多方法来转换图像,以实现光度增强。本节的剩余部分将讨论:随机亮度、随机对比度、随机色调、随机照明噪声和随机饱和度。
随机亮度
图 1:改变图像亮度的效果。由作者编辑。来源:贾斯汀·艾金
这种光度增强的方法是在原始像素值( r 、 g 、 b )的 [-255,255】范围内增加一个δ值。如果δ为正值,新图像将比原始图像更亮。相反,负的δ值会使新图像看起来比原始图像更暗。
随机对比
图 2:改变图像对比度的效果。由作者编辑。来源:贾斯汀·艾金
这种光度增强方法通过将那些像素值乘以系数δ来减少或增加原始像素值( r 、 g 、 b ),其中δ≥0。当0≤δ<1,新图像中的暗区和亮区之间的区别没有原始图像中的明显。另一方面,当δ>1时,新图像中的暗区域和亮区域变得比原始图像中更清晰。
随机色调
图 3:改变图像色调的效果。由作者编辑。来源:贾斯汀·艾金
要使用此方法实现光度增强,需要对原始图像的色调通道进行更改。更具体地,为了产生新的图像,将在 [-360,360] 范围内的δ值添加到原始图像的色调通道中的像素值。这导致在色轮上顺时针或逆时针移动图像的色调通道。
随机照明噪声
图 4:交换图像通道的效果。由作者编辑。来源:贾斯汀·艾金
这种光度增强方法可以通过交换原始图像的不同通道(RGB)的顺序来实现。类似于随机色调,这种方法允许模型关注对象的形状而不是它们的颜色。
随机饱和
图 5:改变图像饱和度的效果。由作者编辑。来源:贾斯汀·艾金
为了执行这种光度增强方法,通过将那些像素值乘以因子δ来增加或减少原始图像的饱和度通道的像素值,其中δ≥0。当0≤δ<1,新图像的颜色比原始图像更加柔和/苍白。相比之下,当δ>1时,新图像中的颜色比原始图像更强烈/更鲜明。
三。几何增强
几何增强通过将原始图像中的每个像素映射到新图像中的新位置来生成新的数据样本(Taylor & Nitschke,2015)。这种增强过程会影响对象周围边界框的几何形状,但这些对象的类别保持不变。当我们想要以各种形状和大小描绘对象时,这种增强技术是有用的。像光度增强一样,我们也可以使用许多方法来实现几何增强。这部分包括:随机垂直和水平翻转,随机扩展和随机裁剪。
随机垂直和水平翻转
图 6:垂直和水平翻转图像的效果。由作者编辑。来源:贾斯汀·艾金
这种几何增强方法在给定的轴上镜像原始图像。对于垂直翻转,轴是水平轴。至于水平翻转,镜像轴是垂直轴。在这两种情况下,对象周围的边界框需要相对于相同的轴进行镜像。
随机扩展
图 7:扩展图像的效果。由作者编辑。来源:贾斯汀·艾金
为了使用这种方法实现几何增强,原始图像被放置在比原始图像大一倍δ的新图像内,其中δ≥1。由于原始图像将不能覆盖新图像的所有区域,所以新图像上的剩余像素值被设置为平均像素值(例如,对于 ImageNet 平均值为[0.485,0.456,0.406])。
随机作物
图 8:裁剪图像的效果。由作者编辑。来源:贾斯汀·艾金
这种放大方法裁剪原始图像的某一部分,以创建可以具有不同纵横比的新图像。除非裁剪中存在对象,否则裁剪是有效的。要被视为包含在裁剪内,对象边界框的中心必须在裁剪内。此外,裁剪和对象边界框之间的重叠(IOU)必须超过随机选择的某个阈值。同样,作物的长宽比也是随机选择的。
四。结论
总之,这篇文章展示了许多用于实现光度和几何增强的方法。此时,我们可以有效地训练 SSD 网络。在下一篇文章中,我们将研究如何解码网络产生的结果。
喜欢这篇文章,想表示你的支持?关注我或者给我买咖啡
参考
KDNuggets。(2018).包围盒的数据扩充:重新思考对象检测的图像变换。检索自https://www . kdnugges . com/2018/09/data-augmentation-bounding-box-image-transforms . html/2
刘,w,安盖洛夫,d,尔汉,d,塞格迪,c,里德,s,傅,C.Y,&伯格,A. C. (2016)。SSD:单次多盒探测器。https://arxiv.org/abs/1512.02325
像素杂志。(2017).摄影师的饱和度和饱和度指南(及其差异)。检索自:https://medium . com/the-coffeelicious/a-photos-guide-to-vibrance-and-saturation-and-thes-differences-4 FDE 529 cc 19
泰勒和尼茨克(2015 年)。使用通用数据增强改进深度学习。https://arxiv.org/pdf/1708.06020.pdf
远程传感器。(2018).SSD(单发探测器)中的数据增强。检索自:https://www . telesens . co/2018/06/28/data-augmentation-in-SSD/# RandomLightingNoise
在 Keras 中实现单触发检测器(SSD ):第五部分——预测解码
Keras 中的物体检测
构建 Keras 层以解码由 SSD 网络产生的预测
预测解码过程。由作者编辑。来源:玛西娅·索利戈
一.导言
根据前面的四篇文章,我们能够训练 SSD 网络,并生成能够根据输入图像进行预测的模型权重文件。对于具有 VGG16 主干的 SSD300,网络产生(8732 * num_classes+1)个预测。显然,这是一个巨大的数量,其中大部分需要过滤掉。因此,本文将概述解码和过滤这些初步预测的步骤。此外,本文还提供了创建 Keras 层的代码片段,以达到同样的目的。
这篇文章是一个更大的系列的一部分,叫做在 Keras 中实现单次检测器(SSD)。下面是系列的概要
二。该算法
为了对 SSD 网络输出的预测进行解码,SSD 的作者设计了一种算法,该算法包括 4 个步骤:包围盒解码、置信阈值、非最大值抑制和 Top-K 滤波。下面详细讨论这些步骤。
下面的代码片段是基于 NumPy 的。下一节将把这些代码片段转换成 Keras,并把它们放到 Keras 的层中。
本文中显示的所有代码都可以在这个回购:https://github.com/Socret360/object-detection-in-keras。GitHub repo 中的许多代码都是从 https://github.com/pierluigiferrari/ssd_keras 的中获取并修改而来的。
步骤 1:边界框解码
图 1:解码 ssd(带有标准偏差编码的质心)边界框的公式。由作者创建。
从该系列的第三部分中,我们了解到 SSD 的预测是用标准差进行质心编码的。因此,第一步是解码那些编码的预测,使它们回到 cx,cy,width,height 的格式。这可以通过图 1 所示的公式来完成。
步骤 2:置信度阈值(针对每个类别)
在解码包围盒预测之后,我们需要移除置信度得分低于特定阈值的包围盒。这个过滤过程是针对每个类进行的。由于 SSD 网络通过 Softmax 函数(第二部分)产生类别预测,我们可以通过特定类别在 Softmax 输出中的位置来获得其置信度得分。这个置信度分数告诉我们,模型有多确定该特定的对象存在于边界框内。下面的代码片段假设您已经创建了一个形状为(total_default_boxes,1 + 4)的 y_pred numpy 数组,其中 1 是我们感兴趣的类的置信度得分的,4 是 xmin、ymin、xmax、ymax 值。
步骤 3:非最大抑制(针对每个类别)
一旦我们过滤掉了类别置信度较低的包围盒,我们就需要将重叠的包围盒合并在一起。这个过程被称为非最大值抑制(NMS)。通过将重叠的预测合并成一个预测,有助于进一步减少预测的数量。类似于本系列第三部分中的将默认框与基础真值框匹配的过程,为了测量两个边界框预测之间的重叠程度,我们计算它们之间的 IOU。以下代码片段执行了 NMS 版本:
步骤 4: Top-K 选择
即使在对每个类别执行置信度阈值和 NMS 之后,剩余预测的数量仍然可能是巨大的。然而,这些预测中的大多数是不需要的,因为可以出现在图像中的(我们感兴趣的)物体的数量是有限的。因此,我们可以根据它们的置信分值对这些预测进行排序,并选择最高置信分值的 k 。
第五步:产生最终结果
顶部 K 选择产生 k 预测。这些 k 预测每一个都有一定的置信度得分。为了产生最终结果,我们进一步缩小了预测的范围,只选择那些置信度高于某个阈值的预测。通常,该阈值是通过在模型评估期间选择产生最高贴图的阈值来选择的。我们将在下一篇文章中讨论如何评估这个模型。
三。Keras 的 SSD 预测解码层
在理解了解码 SSD 预测的每一个步骤之后,我们可以将它们放在一个 Keras 的层中。为解码过程创建 Keras 层的好处是,我们可以创建一个内置解码过程的模型文件。当我们想要在不同的平台上部署模型时,这是很有用的。下面是对 SSD 输出的预测进行解码的 Keras 层。
四。结论
本文概述了对 SSD 网络产生的预测进行解码的步骤,并提供了代码片段,说明如何实现 Keras 层来实现这一目的。在下一篇文章中,我们将学习如何评估模型的性能。
喜欢这篇文章并想表达您的支持?关注我或者给我买咖啡
参考
毛,L. (2019)。物体检测中的包围盒编码和解码。检索自https://lei Mao . github . io/blog/Bounding-Box-Encoding-Decoding/
Sambasivarao。K. (2019 年 10 月 1 日)。非最大抑制(NMS) 。走向数据科学。检索自https://towardsdatascience . com/non-maximum-suppression-NMS-93ce 178 e 177 c
在 Keras 中实现单触发探测器(SSD ):第六部分——模型评估
Keras 中的物体检测
评估经过训练的 SSD 模型
在 PASCAL VOC 2007 trainval 上训练的 SSD300-VGG16 的图在测试集上评估。图片作者。
一.导言
在本系列的前几部分中,我们深入探讨了 SSD 背后的概念以及这些概念是如何在代码中实现的。通过这个,我们能够构建 SSD 网络,训练它产生一个 Keras 模型,我们可以用它来进行预测。然而,我们仍然需要弄清楚模型的表现有多好。要回答这个问题,我们需要执行一个评估过程。目标检测的不同竞争/挑战有其自己的一套评估指标。由于在之前的文章中,我们在 PASCAL VOC 数据集上训练了我们的 SSD 网络,因此本文重点关注理解 PASCAL VOC 挑战评估过程所需的概念。特别地,我们将详细了解如何计算目标检测模型的平均精度(mAP) 。
**注意:**除了 PASCAL VOC challenge 为本次评估提供的 Matlab 代码,您还可以在评估过程中使用其他开源工具(例如review _ object _ detection _ metrics)。此处的目标是让您了解地图指标的使用和计算方式,以便您能够解读评估结果。
本文是一个更大的系列的一部分,称为在 Keras 中实现单次检测器(SSD)。以下是系列的概要
二。计算地图:一个简单的例子
图 1:类别“dog”的检测结果。图片作者。来源:丹尼尔·林肯,埃莉诺拉·卡塔拉诺,张轩睿,贾卡琳·比厄斯
图 2:类别“cat”的检测结果。图片作者。来源:丹尼尔·林肯,埃莉诺拉·卡塔拉诺,张轩睿,贾卡琳·比厄斯
为了更好地理解评估过程,让我们看一个简单的例子。假设我们为猫和狗这两个类训练了一个 SSD300-VGG16 模型。我们对模型在包含狗和猫的 4 幅图像的测试集上的表现感兴趣,因此我们在测试集中的所有图像中运行了我们训练的模型。基本事实边界框以绿色绘制,而检测(预测边界框)以红色绘制。图 1 显示了一组图像,这些图像显示了“狗”类的所有检测及其基本事实。类似地,图 2 显示了一组图像,这些图像显示了“cat”类的所有检测及其基本事实。我们可以看到总共有 12 个检测(5 猫 7 狗)和 7 个地面真相盒(4 猫 3 狗)。此外,如前几篇文章所述,每个检测都有一个置信度得分。请记住这些数字,因为它将在下面的步骤中对我们有用。
步骤 1-确定“狗”类的 AP
1.1-将每个“狗”检测状态确定为 TP 或 FP
在评估过程中,我们需要完成的第一件事是确定哪些检测是“正确的”,哪些是“不正确的”。这就是混淆矩阵的由来。一个类别(例如狗)的混淆矩阵将该类别的所有检测分为四类:真阳性(TP)、真阴性(TN)、假阳性(FP)和假阴性(FN)。在对象检测设置中,
- 真阳性(TP) —真实边界框的正确检测。
- 误报(FP) —对不存在的对象的错误检测或对现有对象的错误检测。
- 假阴性(FN) —一个未被检测到的真实边界框。
- 真底片(TN) —不适用,因为在一幅图像中有无限数量的边界框需要检测。
为了将检测分类为阳性或阴性,使用预测边界框和基本事实框之间的 IOU。PASCAL VOC 挑战将 IOU 阈值设置为 50% (Everingham 等人,2009 年)。如果有两个或更多的检测具有 50%或更高的 IOU,并且具有相同的基本事实,那么具有最高 IOU 的检测被标记为 TP,而所有其他的被标记为 FP。在我们的例子中,IOU 阈值设置为 30%,而不是 50%。浏览所有的检测和地面真理,我们有以上的类“狗”,我们可以构建下表:
表 1:“狗”类的检测及其 TP 和 FP 状态
请注意,检测列表也是按置信度得分降序排列的(从大到小)。这样做是为下一步做准备,在下一步中,我们需要计算所有置信度下的精确-召回对。
1.2 —计算每个置信度级别的精确度/召回率
你可以试试这个互动演示来更直观地了解精确/召回是如何工作的。
图 3:计算精度和召回率的公式。图片作者。
知道每个检测的 TP 和 FP 状态并不能为我们提供关于模型性能的任何有价值的信息。我们需要能够将这些检测的状态合并到一个指标中。为了解决这个问题,可以使用信息检索领域中的精确度和召回率之间的关系。在对象检测的情况下:
- 精度 —测量模型仅识别相关对象的能力。(帕迪拉等人,2020 年)
- 回忆——测量一个模型找到所有相关案例(所有真实边界框)的能力(Padilla 等人,2020)
根据上述概念,我们需要确定哪些检测是“相关”的,哪些是“不相关”的。为此,我们使用每个检测附带的置信度阈值(𝜏)。置信度得分≥ 𝜏的检测将被认为是相关的,而置信度得分
表 2:“狗”类的检测,它们的 TP 和 FP 状态,以及在每个置信度阈值的精度/召回值。作者图片
由于 1.1 中的列表是按照置信度得分以降序排序的,因此计算每个置信度得分级别的精确召回对只是遍历列表(自上而下),同时在每次迭代中将要考虑的项目数(从 1 开始)增加 1。因此,在第一次迭代中,要考虑的检测数量将仅为 1(检测 B)。因此,在这个置信度级别的精度是 1.00,而召回率是 0.33。在第二次迭代中,要考虑的检测的数量增加到 2(检测 B 和检测 E ),给我们 1.00 的精确度和 0.66 的召回率。重复这个过程,直到所考虑的项目数等于列表的大小。
1.3-绘制精度-召回(PR)曲线
计算特定置信度级别的精度和召回率,可以告诉我们特定类别的模型在特定置信度阈值下的性能。然而,要了解模型在特定类别上跨所有置信度得分阈值的整体性能,我们可以转向精确召回(PR)曲线。它显示了精确度和召回率之间的权衡。为了构建曲线,我们可以在不同的置信度下绘制精度/召回率对。PR 曲线向右上角倾斜得越多越好,因为它表明模型很可能识别相关对象(高精度),同时还能够找到所有相关对象中的大多数(高召回率)。对于我们的示例,类别“dog”的 PR 曲线如下所示:
图 4:“狗”类的精确召回曲线。作者图片
1.4-确定的 11 点插值精度/召回对和 AP
我们可以通过估计曲线的曲线下面积(AUC)将 PR 曲线的特征总结为一个值。AUC 值越大,该模型在所有阈值范围内对该类别的表现越好。尽管在 PR 曲线中精度和召回率之间存在权衡关系,但精度值可能会随着召回率值的增加而降低,也可能不会。这导致曲线有时具有之字形图案,这对于估计 AUC 来说不是直线的。为了解决这个问题,PASCAL VOC 挑战赛使用了 11 点插值平均精度(AP)方法。这种方法通过对一组 11 个等距召回水平的最大精度值进行平均来总结 PR 曲线(Padilla 等人,2020)。特定召回级别的最大精度值是该召回级别右侧的最大精度值。此外,由于回忆范围从 0 到 1,11 个等间隔的回忆水平是[0,0.1,0.2,… 0.8,0.9,1]。较高的 AP 值(接近 1)意味着该模型对于该特定类别具有较高的召回率和精确度。在我们的示例中,11 点插值精度是下图中的红点:
图 5:具有 11 点插值精度的“dog”类的精度-召回曲线。作者图片
因此,在我们的例子中,类“dog”的 AP 是:(1+1+1+1+1+1+0+0+0+0)/11 = 0.63 = 63%。
步骤 2—确定“cat”类别的 AP
为了确定类别“cat”的 AP,我们简单地再次重复步骤 1,其中我们的检测和基础事实列表是类别“cat”。通过这种方式,我们确定“cat”类的 AP 为 45%。
表 3:类别“cat”的检测,它们的 TP 和 FP 状态,以及在每个置信度阈值的精度/召回值。作者图片
图 6:具有 11 点插值精度的“cat”类的精度-召回曲线。作者图片
步骤 3-计算地图
在估计了每个类的 AP 之后,我们可以通过计算 mAP 来总结模型跨所有类的性能,mAP 的值也在 0 到 1 的范围内。它只是所有班级 AP 的平均值。高 mAP 值(接近 1)意味着模型在所有类别中表现良好。因此,在我们的例子中,模型的映射是(63 + 45) / 2 = 54%。
图 7:类别“猫”和“狗”的精确回忆曲线。作者图片
三。SSD300-VGG16 经过 PASCAL VOC 2007 培训
我用来执行评估过程的代码可以在 evaluate.py 中找到。
在从上述示例中了解了 mAP 计算之后,我将使用这一部分展示我在 PASCAL VOC 2007 trainval 数据集上训练的 SSD300-VGG16 模型的评估结果(批处理大小= 32,epochs = 275 ≈ 42k 迭代),并提供我对如何改进它的一些意见。评估是在同一数据集的测试集上完成的。SSD 模型产生的检测数量设置为 200(与 SSD 纸张相同),而 IOU 阈值设置为 50%,如 PASCAL VOC 挑战中一样。
图 8:在 PASCAL VOC 2007 trainval 上训练的 SSD300-VGG16 的图。图片作者。
图 PASCAL VOC 2007 数据集中 20 个类的精确召回曲线。图片作者。
很明显,一张 38.58%的地图与 SSD 的作者在他们的论文中所展示的(68%的地图)并不相符。以下是我对如何改进结果的看法:
- **进一步的训练:**由于时间/资源的限制,我在 42k 迭代时停止了我的训练。我相信进一步的训练可以改进模型的映射,因为 1)当我停止训练时,训练损失和验证损失仍然在很好地减少 SSD 的作者在没有随机扩展增强的情况下训练相同的模型配置多达 60k 次迭代,在有随机扩展增强的情况下训练多达 120k 次迭代。
- **改进数据扩充和生成器:**数据生成器的当前实现没有考虑过小的边界框或退化框(具有 xmax < xmin 和 ymax < ymin 的框)。移除这样的边界框将有助于模型产生更好的边界框预测。此外,可以改进增强管道,以更好地匹配 SSD 的原始 Caffe 实现中所示的管道。此外,在训练过程中使用的增强方法的更有效的实现也可以帮助加速训练过程。
三。结论
这标志着“在 Keras 中实现单次检测器(SSD)”系列的结束。就我个人而言,在这个系列的工作中,我学到了很多关于 SSD 的知识。因此,我希望这一系列文章对您有所帮助,就像它对我一样,帮助您实现了解 SSD 并自己实现它的目标。
喜欢这篇文章,想表示你的支持?关注我或者给我买咖啡
参考
j .戴维斯和 m .戈德里奇(2006 年)。精确回忆与 ROC 曲线的关系。2006 年 ICML 第 23 届国际机器学习会议论文集。https://doi.org/10.1145/1143844.1143874
Everingham,m .,Van Gool,l .,Williams,C. K .,Winn,j .,& Zisserman,A. (2009 年)。Pascal 视觉对象类(VOC)挑战。国际计算机视觉杂志, 88 (2),303–338。https://doi.org/10.1007/s11263-009-0275-4
Géron,A. (2020)。第 1 章——机器学习的前景。在使用 Scikit-Learn、Keras 和 TensorFlow 进行机器实践学习:构建智能系统的概念、工具和技术(第 3–34 页)中。论文,奥赖利。
谷歌。(未注明)。分类:真与假、正与负。谷歌。https://developers . Google . com/machine-learning/速成课程/分类/真-假-正-负。
谷歌。(未注明)。评估模型|自动视觉物体检测|谷歌云。谷歌。https://cloud . Google . com/vision/automl/object-detection/docs/evaluate。
马纳尔·埃尔·艾杜尼。(未注明)。https://manalelaidouni.github.io/.
r .帕迪拉,Netto,S. L .,& da Silva,E. A. (2020 年)。目标检测算法的性能度量综述。 2020 年系统、信号和图像处理国际会议(IWSSIP) 。【https://doi.org/10.1109/iwssip48289.2020.9145130 号
Phy,V. (2019 年 12 月 11 日)。分类任务精度不够。中等。https://towardsdatascience . com/accuracy-is-not-sufficient-for-class ification-task-47 FCA 7d 6 a 8 EC。
精确召回。sci kit-学习。(未注明)。https://sci kit-learn . org/stable/auto _ examples/model _ selection/plot _ precision _ recall . html
拉斐尔·帕迪拉。(未注明)。rafaelpadilla/对象检测指标。GitHub。https://github.com/rafaelpadilla/Object-Detection-Metrics.
t .沙阿(2020 年 7 月 10 日)。关于机器学习中的训练、验证和测试集。中等。https://towards data science . com/train-validation-and-test-sets-72 CB 40 CBA 9 e 7。
Solawetz,J. (2020 年 10 月 5 日)。什么是物体检测中的平均精度(mAP)? Roboflow 博客。https://blog.roboflow.com/mean-average-precision/.
在 Keras 中实现单触发探测器(SSD ):第一部分——网络结构
Keras 中的物体检测
在 Keras 中实施 SSD 网络
一.导言
当我试图在 Keras 中实现 SSD 时,我首先考虑的是 SSD 网络的结构。我只能通过首先理解所涉及的概念来理解 SSD 网络,这些概念包括网格检测器、默认框、特征图、基本网络和卷积预测器。因此,本文将首先讨论这些主要概念,然后再讨论它们如何构成 SSD 网络。本文将以在 Keras 中构建一个版本的 SSD 网络的代码示例结束。
这篇文章是一个更大的系列的一部分,叫做在 Keras 中实现单次检测器(SSD)。以下是系列的概要
二。概念
栅格探测器
图 1:网格检测器。输入图像被分成网格,以便检测不同位置的物体。来源: Alvan Nee
训练对象检测模型的一种简单方法是将边界框预测器添加到现有的图像分类网络中。当输入图像中只有一个对象时,这种方法效果很好。当输入图像中有两个或更多对象时,模型将无法生成正确的边界框来覆盖这些对象。这是因为没有约束来帮助边界框预测器知道它负责预测哪个对象(Hollemans,2018)。网格检测器有助于对象检测模型检测不同位置的多个对象。这是通过将输入图像分成多个网格单元(例如 3×3、10×10)来完成的,其中每个网格单元具有其自己的简单对象检测模型。因此,使用 3×3 网格的对象检测模型对于每个单独的网格单元将具有 9 个简单的对象检测模型。每个检测器专门检测和分类落入各自网格单元内的对象。
要深入了解为什么网格探测器是有用的,请参考 Matthijs Hollemans 的这篇伟大的文章单阶段物体探测。
默认框
图 2:默认框。这些盒子以一定的偏移量(通常是中心)放置。来源: Alvan Nee
再次想象通过仅仅将边界框预测器添加到图像分类模型来构建简单的对象检测模型。边界框预测器将很难预测具有许多不同形状的对象的边界框。虽然网格检测器允许对象检测模型检测输入图像上不同位置的对象,但是默认框允许对象检测模型检测具有不同形状的对象。这些默认框设置了更多的约束,允许边界框预测器仅专注于检测特定形状的对象(Hollemans,2018)。每个默认框的形状由其宽度和高度表示。每个默认框都有一个简单的对象检测模型,专门预测具有特定形状的对象。因此,使用 5 个默认框的对象检测模型将具有 5 个简单的对象检测模型,每个形状一个。我们稍后将研究如何选择这些盒子。
特征地图
图 3:特征地图。CNN 开头的特征映射对应于原始图像上的线条,而结尾的特征映射对应于更具描述性的特征。
CNN 中的每个卷积层都会产生一个相同比例的特征图(Seif,2019)。每个特征地图项目对应于输入图像上的特定位置/斑块(泽勒&弗格斯,2013 年)。CNN 中的卷积层越深,特征图的描述性就越强。在 CNN 的前几层中,这些层产生的特征地图项目对应于原始图像中的边缘、线或角的小块。在 CNN 的更下方,特征地图项目对应于输入图像上更大的补丁。这些大块可能是物体的一部分,甚至是完整的物体(泽勒&弗格斯,2013)。
基于 CNN 网络
基础网络(又名。主干网络)是一种 CNN 网络,用于对象检测中的特征地图提取(Amjoud & Amrouch,2020)。它们大多是为处理图像分类任务而开发的 CNN。这是因为在图像分类任务上表现良好的网络也被证明是对象检测模型的良好特征图提取器。流行的基础网络包括 AlexNet、VGG-16、ResNets、DarkNet-19、GoogLeNet、MobileNet、ShuffleNet 等。当训练对象检测模型(即 SSD)时,这些基本网络通常是预训练的,并且它们的权重在训练过程中保持不变。这些基本网络的预训练权重往往是在大型图像分类数据集(如 ImageNet、COCO 等)上训练的权重。这是因为由这些预先训练的权重产生的特征图已经显示为对象检测模型提供了有用的表示,以检测和分类新的或类似的对象。
卷积预测器
为处理图像分类任务而创建的 CNN 通过完全连接的层产生其最终输出。然而,全连接层的使用提出了两个主要问题。首先,它会导致特征地图所提供的空间信息的丢失(张、李普顿、李& Smola,2020)。第二,如果有大量的特征图,参数的数量会变得非常大。卷积预测器可以解决这些问题。不是将特征图展平成 1D 向量并将其馈入完全连接的层,而是使用小的卷积滤波器(例如大小为 1×1、3×3 等)来产生预测。由于我们没有将宽 x 高 x 通道大小的要素地图转换为完全连接图层的 1D 矢量,因此空间信息不会丢失。此外,如果我们使用完全连接的图层,大小为 38 x 38 x 512 的要素地图将产生 739,328 个参数,而不考虑我们需要预测的类的数量。另一方面,卷积预测器只会产生大小为 38 x 38 x num_classes 的预测。
三。SSD 网络结构
了解了上述概念后,我们可以开始研究这些部分如何组合在一起形成 SSD 网络结构。SSD 网络背后的核心思想是让 CNN 接受图像作为输入,并在不同的比例、形状和位置进行检测。
SSD 的论文中提到了两个版本的 SSD:SSD 300 和 SSD500。下面,我们将只看 SSD300,因为两者之间唯一的主要区别是输入大小。
检测不同比例的物体
图 3:基于 VGG16 网络的 SSD300 的功能图。来源:
为了产生不同尺度的检测,SSD 网络使用来自修改的 VGG16 网络的不同层的特征图。VGG16 网络的变化包括:
- 通过使用阿特鲁卷积,VGG16 的 fc6 和 fc7 变成卷积层。
- pool5 的池大小从(2,2)更改为(3,3),步长为(1,1)。
- VGG16 网络的 conv4_3 增加了 L2 归一化功能。然后,网络通过 SSD 的额外功能层进行扩展。
在 SSD 论文中,基础网络是 VGG16,更具体地说是 VGG16 配置 D(刘,安盖洛夫,尔汉,赛格迪,里德,傅,& Berg,2016)。即使选择的基础网络是 VGG16,SSD 的作者提到也可以使用任何其他基础网络(刘等人,2016)。由于每个特征映射项目对应于原始图像中的不同位置/小块,在网络起点的特征映射允许检测小物体,而在网络终点的特征映射允许检测大物体。用于利用 VGG16 基本网络构建 SSD300 的特性图为:conv4_3、fc7、conv8_2、conv9_2、conv10_2 和 conv11_2。
检测不同位置的物体
图 4:特征图的大小被用来将图像划分成网格。
为了在图像中产生不同位置的检测,SSD 网络使用网格检测器。特征图的前两个维度可以被认为是将输入图像划分成的网格大小。因此,38 x 38 x 512 的特征地图可以被视为将输入图像分成 38 x 38 的网格。conv4_3 的栅极尺寸为 38 x 38,fc7 为 19 x 19,conv8_2 为 10 x 10,conv9_2 为 5 x 5,conv10_2 为 3 x 3,conv11_2 为 1 x 1。这允许 SSD 网络检测每个要素地图图层的不同位置的对象。
检测不同形状的物体
图 5:默认框被添加到每个特征地图中的每个网格单元。
为了在图像中产生不同形状的检测,SSD 网络使用默认框。每个网格单元都被分配了 N 个默认框,其中心位于距网格单元一定偏移量处(通常是中心)。在 SSD 中,每个默认框的宽度和高度可以通过两个值来检索:框的纵横比和框相对于输入图像大小的比例(即,SSD300 为 300,SSD500 为 500,等等)(图 6,等式 1 和 2)。两个值(纵横比、比例)在不同的特征地图之间是不同的。特定要素地图的纵横比列表可通过查看数据集中对象的纵横比来确定,并由浮点值表示。在 SSD 论文中,对于在 Pascal VOC 数据集上训练的具有 VGG16 基础网络的 SSD300,作者说:
“对于 conv4_3、conv10_2 和 conv11_2,我们仅在每个功能图位置关联 4 个默认框,忽略 1/3 和 3 的纵横比。对于所有其他层,我们放置 6 个默认框(1,2,3,2/3,1/3)。”
图 6:计算默认框的宽度、高度和比例的公式。
同样,特定要素地图图层的默认框的比例由图 6 中的公式 3 确定。该公式的作用是根据 CNN 中的顺序输出要素地图的比例值。最低的要素地图的比例为 sₘᵢₙ,较高的图层的比例为 sₘₐₓ,而所有其他图层的比例值在 sₘᵢₙ和 sₘₐₓ.之间保持一定的间隔此外,对于默认框列表中长宽比为 1 的要素地图,作者添加了另一个长宽比为 1 的默认框,s’ₖ的比例由图 6 的等式 4 给出。
产生检测
在 SSD 中,预测的数量固定为( total_default_boxes , num_classes + 1 + 4 + 8),其中 total_default_boxes 是所有要素地图中默认框的总数,而 num_classes 是类的数量。为了对某个特征图进行预测,SSD 应用了两个小的 3 x 3 卷积滤波器:
- 3×3×(num _ default _ boxes * 4)来产生所有边界框的边界框预测。
- 3 x 3 x(num _ default _ boxes *(num _ classes+1))为数据集中的所有类加上一个背景类生成类预测。
然后,它使用过滤算法的组合来过滤掉这些预测。我们将在本系列的后续文章中研究如何过滤掉这些检测。
四。代码
本文中显示的所有代码都可以在这个回购:https://github.com/Socret360/object-detection-in-keras。在本文中,我将展示代码的关键片段,并提供包含完整代码的相关文件的链接,而不是直接展示所有代码示例。这将确保文章不会因代码示例而过载。GitHub repo 中的许多代码都是从 https://github.com/pierluigiferrari/ssd_keras 的中获取并修改而来的。
代码示例将展示如何使用 VGG16 作为基础网络构建 SSD300。以下是我在工作时采取的步骤:
- 创建一个配置文件来存储所有参数。
- 构建所有必要的自定义 Keras 层,以完成 SSD 网络。其中包括:默认盒子层和 L2 规范化层
- 构建固态硬盘网络
创建一个配置文件来存储所有参数
当我开始编写 SSD 网络结构时,我发现将所有必要的/参数分组到一个配置文件中很有帮助。我可以将这个配置文件加载到 python 字典中,然后将该字典传递给函数,而不是将大量参数传递给函数。你可以看一下 ssd300_vgg16.json 作为这个文件的样本。将 json 文件加载到 python 字典中相对容易。您可以使用下面的代码来实现:
构建默认框和 L2 归一化图层
为了构建一个完整的 SSD 网络,我们需要首先构建 2 个自定义 Keras 层,它们是默认的盒子和 L2 归一化层。以下是需要这些层的原因:
**默认框层:**该层用于构建特征地图的默认框。在训练阶段,不需要该层的值。然而,在推断阶段,来自该层的值对于解码由网络产生的边界框预测将是至关重要的。关于构建该层的完整代码,您可以参考 default_boxes.py 。
**L2 归一化层:**该层用于应用带有可学习标度值的 L2 归一化。它将仅用于 conv4_3 要素地图图层。据作者称,
“与其他图层相比,conv4_3 具有不同的特征比例,我们使用 L2 归一化技术将特征图中每个位置的特征范数缩放至 20,并在反向传播过程中学习该比例。”
要构建这一层,你可以遵循 l2_normalization.py 中的代码。
构建固态硬盘网络
由于前面几节已经完成了所有的基础工作,我们终于可以构建 SSD 网络了。实现这一点的思考过程概述如下:
- 构建基础网络,加载预训练的权重,并冻结基础网络层,使其权重在训练期间不会改变。
- 构建 SSD 的额外功能层。
- 确定默认框的所有可能比例。
- 对于每个特征图:(1)应用 3×3 卷积预测器来产生形状的分类预测(w,h,num _ default _ boxes (num_classes + 1))和形状的定位预测(w,h,num_default_boxes4),其中 w 和 h 分别是特征图的宽度和高度(2)将那些预测重新整形为用于分类的形状(whnum_default_boxes,num _ classes+1)和用于定位的形状(whnum_default_boxes,4)。(3)为该特定层生成默认框,并将其从(w,h,num_default_boxes,8)的形状重新整形为(whnum_default_boxes,8)的形状
- 将来自每个特征映射的分类预测连接在一起,并应用 Softmax 激活以获得最终分类结果。
- 将每个特征地图的本地化预测连接在一起
- 将每个要素地图图层的所有默认框连接在一起
- 将所有分类、本地化和默认框连接在一起,以产生 shape 的最终输出(total _ default _ box, num_classes + 1 + 4 + 8)
结论
看完这篇长文,希望它能给你提供足够的理解,让你在 Keras 中构造自己的 SSD 网络。在下一篇文章中,我们将探讨用于优化 SSD 网络的损失函数,当然还有如何在 Keras 中对它们进行编码。
喜欢这篇文章并想表达您的支持?关注我或者给我买咖啡
参考
阿姆茹德,文学学士和硕士(2020 年)。用于目标检测的卷积神经网络主干。图像和信号处理(第 282-290 页)。多伊:https://doi.org/10.1007/978-3-319-94211-7
霍利曼斯,M. (2018)。一阶段目标检测。检索自:https://machinethink.net/blog/object-detection/
刘,w,安盖洛夫,d,尔汉,d,塞格迪,c,里德,s,傅,C.Y,&伯格,A. C. (2016)。SSD:单次多盒探测器。https://arxiv.org/abs/1512.02325
Seif,G. (2019)。用于深度学习的可视化过滤器和特征地图。检索自:https://link.medium.com/EkLIlHHOycb
Simonyan 和 a . zisser man(2015 年)。用于大规模图像识别的非常深的卷积网络。2015 年学习代表国际会议(ICLR)。https://arxiv.org/abs/1409.1556
泽勒博士和弗格斯博士(2013 年)。可视化和理解卷积网络。检索自:https://arxiv.org/abs/1311.2901
张,李春钟,李,m,斯莫拉,J. A. (2020)。现代卷积神经网络。深入学习(第 268-270 页)。检索自:https://d2l.ai/d2l-en.pdf
实现从 RGB 到多通道图像的迁移学习
使用 ResNet50 主干+金字塔池的语义分割
介绍
最近,我有幸有机会与 Omdena 和 WeedBot 合作参加了一个计算机视觉挑战赛,这是一个受影响驱动的初创公司,开发了一种激光除草机械,让农民可以用激光束找到并清除杂草。
我们探索了作物与杂草分类的图像分割技术,并探索了语义和实例分割方法。在本文中,我们将探索在项目的语义分割部分中实现的两个不同的概念——
多通道输入的迁移学习
什么是迁移学习?
迁移学习是一种机器学习技术,用于在新问题上重用预先训练好的模型。
在迁移学习中,机器利用从不同任务中获得的知识,通过为新的但相关的任务从新样本中提取有用的特征来提高泛化能力。
例如,在训练一个分类器来预测狗的品种时,人们可以使用在训练中获得的知识来区分一般的动物。
优势
使用迁移学习有很多好处。好处是它节省时间,提供更好的性能,并且需要更少的数据。
自然语言处理和计算机视觉问题的深度学习模型通常需要大量数据供模型学习。这可能既耗时又昂贵,并且可能是个人和小组织采用机器学习的巨大障碍。
迁移学习减少了这一障碍,它允许一个人采用一个已经训练好的模型,并将其应用于一个不同但相关的问题。因为模型是预先训练的,这意味着我们不是完全从零开始训练,而是利用模型已经学到的东西。
为挑战提供的是分辨率为 3008x3008 的 775 幅图像。鉴于图片数量较少,迁移学习似乎是一个很好的探索途径。
公开可用的是在公开可用的数据集上训练的开源模型,例如 ResNet、AlexNet、VGG 等等。两个这样的常见数据集是 ImageNet 和 Coco 数据集。这些数据集分别由超过 14M 和 330K 的图像组成。
从 RGB 到多通道
我们的研究提出了三种方法,可以用来将 3 个通道上训练的模型转换为更多的通道。这些方法跨越不同的复杂程度。我们将简要讨论这些方法—
- 一种方法是简单地扩展权重维度以考虑额外的通道数量,并随机初始化这些值。
- 第二种方法与第一种类似,只是我们不是用随机初始化的值来填充这些值,而是用其他值的平均值来填充。我们在一篇科学论文中发现了这种方法,该论文描述这种方法比第一种方法更有效。这是我们将在本文中探索的方法。
- 理论上,最终方法应该提供最佳性能。然而,就训练时间而言,这种方法需要更长的时间。这种方法表明,前面讨论的方法会偏向于前三个通道,因为这是预训练模型最初训练的内容。相反,这种方法提出的是,我们创建第二个并行网络,对剩余的通道执行特征提取,然后将输出与原始预训练模型的输出连接起来。以这种方式,第二模型学习特定于附加通道的表示,并且我们仍然利用按原样使用预先训练的模型。这种方法将在另一篇文章中探讨。
ResNet50 主干和 15 通道图像
为此问题选择的主干模型是 ResNet50。ResNet50 是“残差网络”的缩写,是一个 50 层深度卷积神经网络,利用残差学习。
resnet50 架构
值得一提的是,使用预训练模型的一个特征是,模型期望新任务的输入维度与其预训练的旧任务的输入维度相同。
resnet50 模型在高度和宽度为 224x224 的输入维度上进行预训练,RGB 有 3 个通道。
对于这个分割任务,我们使用了许多特征生成技术,在原始的 3 通道 RGB 图像上增加了额外的 12 通道。有关如何生成额外通道的更多信息,请参阅本文。
当时的挑战是获得预训练的 resnet50 模型,以 480x400 的新图像尺寸作为输入,第三维具有 15 个通道。
我们将做一个代码演练,看看这是如何实现的。首先,我们使用 Keras 下载并导入 resnet50 模型—
在这里,我们指定希望下载 imagenet 的预训练权重。通常,在迁移学习中,我们会排除最后一层,用对新任务更具体的层来代替它。设置“include_top=False”允许我们排除最后一层。如果我们用预先训练好的模型进行推理,而不是实现迁移学习,那么这个应该设置为真。
此时,我们需要将分辨率(高度和宽度)从 224x224 更改为 480x400,并将通道数量从 3 更改为 15。因为更改输入的高度和宽度不会影响权重的尺寸,所以更改起来更简单。
另一方面,改变输入通道的数量确实会影响权重的维数。让我们更详细地研究一下这个问题。
出于比较目的,我们将使用修改后的 uNet 架构,我们可以通过首先改变高度-宽度,然后改变通道数量,来比较 224x224x3 输入和 480x400x15 输入的模型概要。
semantic_segmentation(224, 224, 3).summary()
semantic_segmentation(400, 480, 3).summary()
我们注意到参数的总数保持不变。这证实了输入的高度和宽度不会影响重量尺寸。现在,让我们来看看改变频道的数量—
semantic_segmentation(400, 480, 15).summary()
我们注意到参数的数量从 18,515 增加到 20,243。我们还注意到,这仅仅是因为第一卷积层的参数从 448 增加到 2176,而后续层的参数数量保持不变。
无需在 Keras 中进行尝试,通过回忆卷积层的权重维度由滤波器的高度和宽度、输入深度和输出深度决定,就可以从理论上确认这一点。图像的高度和宽度与此无关。
更改输入维度的第一步涉及复制模型的配置信息。这为我们提供了字典格式的模型组成。我们可以通过更改输入维度来编辑这个字典,并使用编辑过的配置字典创建一个新模型—
我们现在已经创建了一个与 ResNet50 具有相同网络结构的新模型。值得注意的是,这不会自动复制 resnet50 模型的重量,这是这样做的主要目的。
为此,我们需要遍历 resnet50 模型和新创建的模型的层,并复制权重。
然而,我们会遇到一个问题,因为尺寸不匹配。我们之前证实了改变通道的数量会影响权重的维度。为了解决这个问题,我们扩展了权重维度,以更准确地表示通道的增加,并复制权重的平均值。这是按如下方式完成的—
这负责语义分割模型的 ResNet50 主干。
金字塔池模块
在另一篇文章中,我讨论了在探索语义分割时,我们分成多个团队来探索不同的分割模型。在探索 PSPNet 的时候,我们注意到虽然这个模型不完全准确,但是它产生了平滑的分段。
我们推测这可能是模型利用金字塔池模块的结果。为了最终的模型。我们决定将它与 resnet50 一起用作主干。
金字塔池模块
金字塔池通过观察整个要素地图以及不同位置的子区域来工作。池内核覆盖图像的全部、一半、1/4 和 1/8,并被融合为全局先验。然后,这与来自主干的原始特征图相结合。
跟随这个 Colab 笔记本。
参考
林 T,梅尔 M,贝隆吉 S,布尔德夫 L,吉尔希克 R,海斯 J,佩罗娜 P,拉玛南 D,兹尼克 L,多尔 P 2014 微软可可:背景中的共同对象【https://arxiv.org/abs/1405.0312
甲等;魏东;理查德·索彻;李——;李凯;https://ieeexplore.ieee.org/document/5206848李菲菲 2009 ImageNet:一个大规模分层图像数据库
Donges N 2019 什么是迁移学习?探索流行的深度学习方法https://builtin.com/data-science/transfer-learning
何 K,张 X,任 S,孙 J 2015 用于图像识别的深度残差学习https://arxiv.org/pdf/1512.03385.pdf
赵等人 2016 传销资金池模块、
在 PyTorch 中实现视觉转换器(ViT)
作者图片
我在LinkedIn,过来打个招呼 👋
嗨,伙计们,新年快乐!今天我们要实现中提出的著名的Vi(sion)T(transformer)一个图像抵得上 16X16 个字:大规模图像识别的变形金刚。
代码在这里,本文的互动版可以从这里下载。
ViT 可在我的新计算机视觉库上获得,名为 眼镜
这是一个技术教程,而不是你的普通媒体文章,在那里你可以找到熊猫的五大秘密功能,让你变得富有。
因此,在开始之前,我强烈建议您:
-看看令人惊叹的图文并茂的变形金刚网站
-观看 Yannic Kilcher 关于 ViT的视频-阅读 Einops doc
因此,ViT 使用一个普通的转换器(在中提出的那个,你所需要的就是注意力)来处理图像。但是,怎么做呢?
下图是 ViT 的架构
论文作者(Alexey Dosovitskiy 等人)提供的图片
输入图像被分解成 16x16 的拼合面片(图像未按比例缩放)。然后,使用正常的全连接层嵌入它们,在它们前面添加一个特殊的cls
令牌,并对positional encoding
求和。产生的张量首先被传递到标准转换器,然后传递到分类头。就是这样。
这篇文章分为以下几个部分:
- Data
- Patches Embeddings
- CLS Token
- Position Embedding
- Transformer
- Attention
- Residuals
- MLP
- TransformerEncoder
- Head
- ViT
我们将采用自底向上的方法一个模块一个模块地实现这个模型。我们可以从导入所有需要的包开始
这里没有什么花哨的,只是 PyTorch +的东西
首先,我们需要一张图片,一只可爱的猫就可以了:)
图片来自维基百科
然后,我们需要对它进行预处理
torch.Size([1, 3, 224, 224])
第一步是将图像分解成多个小块并展平。
论文作者(Alexey Dosovitskiy 等人)提供的图片
引用报纸上的话:
使用 einops 可以很容易地做到这一点。
现在,我们需要使用一个正常的线性层来投影它们
论文作者(Alexey Dosovitskiy 等人)提供的图片
我们可以创建一个PatchEmbedding
类来保持代码整洁
torch.Size([1, 196, 768])
注意在检查了原始实现后,我发现作者使用 Conv2d 层而不是线性层来提高性能。这是通过使用等于“补丁大小”的内核大小和步幅来获得的。直观地说,卷积运算单独应用于每个面片。因此,我们必须首先应用 conv 层,然后平坦的结果图像。
CLS 代币
下一步是添加cls token
和位置嵌入。cls token
只是一个放在每个序列的中的数字
torch.Size([1, 197, 768])
cls_token
是一个随机初始化的火炬参数,在forward
方法中,它被复制b
(批处理)次,并使用torch.cat
添加在投影的补丁之前
位置嵌入
到目前为止,这个模型还不知道这些碎片的原始位置。我们需要传递这个空间信息。这可以用不同的方法来完成,在 ViT 中我们让模型学习它。位置嵌入只是一个形状为N_PATCHES + 1 (token), EMBED_SIZE
的张量,它被添加到投影的面片上。
论文作者(Alexey Dosovitskiy 等人)提供的图片
torch.Size([1, 197, 768])
我们在.positions
字段中添加了位置嵌入,并将其与.forward
函数中的面片相加
现在我们需要工具转换器。在 ViT 中,仅使用编码器,其架构如下图所示。
论文作者(Alexey Dosovitskiy 等人)提供的图片
让我们从注意力部分开始
注意力
因此,注意力接受三个输入,著名的查询、键和值,使用查询和值计算注意力矩阵,并使用它来“关注”值。在这种情况下,我们使用的是多头注意力,这意味着计算被分割到具有较小输入大小的n
头上。
论文作者(Alexey Dosovitskiy 等人)提供的图片
我们可以使用 PyTorch 的nn.MultiHadAttention
或者实现我们自己的。为了完整起见,我将展示它的样子:
所以,一步一步来。我们有 4 个完全连接的层,一个用于查询、键、值,最后一个用于删除。
好的,这个想法(真的去读一下图中的 Transformer )是使用查询和键之间的乘积来知道每个元素相对于其他元素的重要性。然后,我们使用这些信息来调整这些值。
forward
方法将来自前一层的查询、键和值作为输入,并使用三个线性层对它们进行投影。因为我们实现了多头注意力,我们必须在多头中重新排列结果。
这是通过使用 einops 的rearrange
完成的。
查询、键和值总是相同的,所以为了简单起见,我只有一个输入(x
)。
产生的键、查询和值具有BATCH, HEADS, SEQUENCE_LEN, EMBEDDING_SIZE
的形状。
为了计算注意力矩阵,我们首先必须执行查询和关键字之间的矩阵乘法,也就是最后一个轴上的总和。这可以使用torch.einsum
轻松完成
产生的矢量具有形状BATCH, HEADS, QUERY_LEN, KEY_LEN
。那么注意力最终是所得向量的 softmax 除以基于嵌入大小的缩放因子。
最后,我们用注意力来衡量价值
我们得到一个大小为BATCH HEADS VALUES_LEN, EMBEDDING_SIZE
的向量。我们将标题连接在一起,最终返回结果。
注意我们可以使用单个矩阵一次性计算queries, keys and values
。
残差
变压器块有残余连接
论文作者(Alexey Dosovitskiy 等人)提供的图片
我们可以创建一个漂亮的包装器来执行剩余的加法,这在以后会很方便
注意力的输出被传递到一个完全连接的层,该层由两层组成,并以输入的expansion
倍进行上采样
论文作者(Alexey Dosovitskiy 等人)提供的图片
简单补充一下。我不知道为什么,但我从未见过有人子类化nn.Sequential
来避免编写forward
方法。开始做吧,这就是对象编程的工作方式!
**最后,**我们可以创建变压器编码器模块
论文作者(Alexey Dosovitskiy 等人)提供的图片
ResidualAdd
允许我们以优雅的方式定义这个模块
让我们测试一下
torch.Size([1, 197, 768])
您也可以 PyTorch 内置多头注意力,但它需要 3 个输入:查询、键和值。您可以将其子类化并传递相同的输入。
变压器
在 ViT 中,只使用原始变压器的编码器部分。很容易,编码器是TransformerBlock
的L
块。
很简单!
最后一层是正常的全连接,给出了类概率。它首先对整个序列进行基本平均。
论文作者(Alexey Dosovitskiy 等人)提供的图片
我们可以组合PatchEmbedding
、TransformerEncoder
和ClassificationHead
来创建最终的 ViT 架构。
我们可以使用torchsummary
来检查参数的数量
summary(ViT(), (3, 224, 224), device='cpu')
好了
我检查了其他实现的参数,它们是相同的!
在本文中,我们看到了如何以一种良好的、可伸缩的、可定制的方式实现 ViT。我希望它是有用的。
顺便说一下,我正在开发一个名为 眼镜 的新计算机视觉库,如果你喜欢的话,可以去看看
保重:)
弗朗西斯科
在一行代码中导入所有 Python 库
为写多个导入语句而烦恼?让 PyForest 为你做这项工作
Python 是数据科学家用于数据科学项目的主要语言,因为存在数千个开源库,可以简化和执行数据科学家的任务。超过 235,000 个 Python 包可以通过 PyPl 导入。
在数据科学案例研究中,需要导入多个库和框架来执行任务。每次数据科学家或分析师启动新的 jupyter 笔记本或任何其他 IDE 时,他们都需要根据自己的需求导入所有的库。有时,反复编写多行相同的 import 语句会令人沮丧。在这里 pyforest 图书馆来拯救你,它为你工作。
Pyforest 图书馆:
Pyforest 是一个开源的 Python 库,使数据科学家能够感受到自动化导入的幸福。在进行数据科学案例研究时,需要导入多个包或库,如 pandas、matplotlib、seaborn、NumPy、SkLearn 等。每次都导入所有这些库可能会很无聊,并且会破坏您工作的自然流程。此外,您无需搜索确切的导入语句,如**from sklearn.ensemble import RandomForestClassifier**
。
使用 pyforest one 可以克服这些问题。Pyforest 使您能够使用所有您喜爱的库,而无需导入它们。Pyforest 通过自动导入您想要用于案例研究的库来为您完成这项工作。
一旦在一行中导入了 pyforest 库,现在就可以像平常一样使用所有的 python 库了。您使用的任何库都不会被导入,Pyforest 会自动为您导入。这些库只有在您调用它们或创建它们的对象时才会被导入。如果没有使用或调用某个库,pyforest 不会导入它。
安装:
可以使用以下命令从 Pypl 安装 Pyforest:
**pip install pyforest**
安装完这个库之后,您只需要用一行代码导入它。现在你可以像平常一样使用你最喜欢的库了,不用写导入。在下面的示例 jupyter 笔记本中,我们没有导入 pandas、seaborn 和 matplotlib 库,但是我们可以通过导入 pyforest 库来使用它们。
(作者代码)
pyforest 可以导入所有库吗?
这个包旨在添加所有占导入量 99%以上的库,包括热门库如pandas
为pd
、NumPy
为np
、matplotlob.pyplot
为plt
、seaborn
为sns
等等。除了这些库之外,它还有一些助手模块,如os
、tqdm
、re
等等。
如果您想查看库列表,请使用**dir(pyforest)**
。
要将您自己的导入语句添加到 pyforest 库列表中,请在您的主目录**~/.pyforest/user_imports.py**
中的文件中键入您的显式导入语句。
pyforest 的功能:
Pyforest 会在需要时自动调用 python 库。Pyforest 提供了一些函数来了解库的状态:
- active_imports() :返回已经导入并正在使用的库列表。
- lazy_imports() :返回 pyforest 库中要导入的所有库的列表。
结论:
在本文中,我们讨论了 pyforest 库,这是一个自动导入库。使用这个库可以减少导入大量必要库的压力,相反,它会自动导入需求。对于经常使用新的 jupyter 笔记本来探索数据科学案例研究的数据科学家来说,这个库很有帮助,现在他们可以不用导入库,从而加快他们的工作流程。
参考资料:
[1] Pyforest 文件(2020 年 4 月 17 日):https://pypi.org/project/pyforest/
喜欢这篇文章吗?成为 中等会员 继续无限制学习。如果你使用下面的链接,我会收到你的一小部分会员费,不需要你额外付费。
https://satyam-kumar.medium.com/membership
感谢您的阅读
损失函数在深度学习和 Python 实现中的重要性
来源:Unsplash 上的 Mikael Kristenson
我们知道在神经网络中,神经元用相应的权重、偏置和它们各自的激活函数工作。权重与输入相乘,然后在进入下一层之前对元素应用激活函数。最后通过输出层得到预测值(yhat)。但是预测总是更接近实际(y),我们称之为误差。因此,我们定义了损失/成本函数来捕捉误差,并试图通过反向传播来优化它。
基于问题陈述,有不同类型的损失函数,我们试图对其进行优化。在本文中,我们将讨论深度学习中的不同损失函数。我们将详细讨论下面的损失函数:
作者图片
1) 回归基础问题中的损失函数
a) 均方误差损失
均方差(MSE)是回归问题中非常常用的损失函数。
如果目标变量的分布是高斯分布,则 MSE 是优选的损失函数。均方误差被定义为预测值和实际值之间的平方差的平均值。成本函数看起来像:
作者图片
结果总是积极的。由于正方形,较大的错误比较小的错误导致更多的错误。换句话说,MSE 是在惩罚犯了更大错误的模型。
优点:
由于该方程本质上是二次的,梯度下降只有一个全局最小值。
二。不存在局部最小值。
三。惩罚犯较大错误的模型。
**缺点:**如果数据包含异常值,这个损失函数是不稳健的
注意:如果目标列的范围相当分散,在这种情况下,由于 MSE 的性质,预测大值可能会严重影响模型。在这种情况下,代替 MSE,使用均方对数误差(MSLE)损失。这里,首先计算实际值和预测值的自然对数,然后计算均方误差。成本函数看起来像:
作者图片
b) 平均绝对误差损失
平均绝对误差(MAE)也是回归问题中另一个重要的损失函数。它被定义为实际值和预测值之间的绝对差值的平均值。成本函数看起来像:
作者图片
**优点:**与 MSE 相比,MAE 对异常值更稳健。
缺点:
I .计算成本高,因为与平方误差相比,模数误差很难求解。
二。可能存在局部最小值。
三。即使损失很小,梯度也会变大,因为梯度在过程中保持不变,这不适合学习。为了解决这个问题,可以使用动态学习率。
c) 胡伯损失(平滑平均绝对误差)
Huber 损耗通过结合 MSE 和 MAE 起着重要的作用。如果损失更高,它将二次方程变为线性方程。如果误差小于临界值(ε),则使用 MSE,否则可以使用 MAE。损失函数定义为:
作者图片
Huber 损耗曲线围绕最小值,这降低了梯度,这与 MAE 相比更好,因为 MAE 具有恒定的大梯度。这导致在使用梯度下降的训练结束时丢失最小值。另一方面,与均方误差损失相比,Huber 损失对异常值不太敏感。
请注意,增量的选择很重要,因为它有助于确定异常值标准。
优点:
I .对异常值的处理是明智的。
二。不存在局部最小值。
三。它在 0 也是可微的。
**缺点:**需要优化额外的超参数(ε),这是一个迭代的过程。
从下图可以清楚地看出,Huber 结合了 MAE 和 MSE,并采用了一种理想的方法来克服 MSE 和 MAE 的缺点。
图片来自作者
2) 基于二值分类问题中的损失函数
**a)**二元交叉熵
交叉熵是用于分类问题的常用损失函数。它测量两个概率分布之间的差异。如果交叉熵很小,则表明两个分布彼此相似。
在二元分类的情况下,预测概率与目标/实际(0 或 1)进行比较。二元交叉熵计算分数,该分数提供了用于预测类别 1 的实际概率和预测概率之间的负平均差。该分数基于与期望值的距离来惩罚概率。损失函数定义为:
作者图片
b) 铰链损耗
铰链损失是二进制分类问题的另一个损失函数。它主要是为支持向量机(SVM)模型开发的。铰链损耗是根据“最大裕度”分类计算的。
如果目标值在集合(-1,1)中,则使用该损失函数。必须将目标变量修改为集合中的值(-1,1),这意味着如果 y 的值为 0,则需要将其更改为-1。
损失函数定义为:
作者图片
如果实际类别值和预测类别值之间的符号存在差异,则铰链损失函数试图通过分配更多误差来确保正确的符号。
3) 基于多类分类问题中的损失函数
**一)**多类交叉熵
在多类分类的情况下,将预测概率与目标/实际概率进行比较,其中每个类被分配一个唯一的整数值(0,1,3,…,t),假设数据有 t 个唯一的类。它计算一个分数,该分数提供所有类的实际概率和预测概率之间的负平均差。损失函数定义为:
作者图片
对于分类交叉熵损失函数,需要确保在 n 维向量中,除了对应于类别的条目为 1(一次热编码)之外,所有条目都为 0。
例如,对于 3 类分类问题,其中第一个观察属于第三类,第二个观察属于第一类,第三个观察属于第二类,目标(y)将是: y = [[0,0,1],[1,0,0],[0,1,0]]
b) 稀疏多类交叉熵损失
两者,多类交叉熵和稀疏多类交叉熵具有相同的损失函数,上面提到过。唯一的区别是真实标签(y)的定义方式。对于稀疏分类交叉熵,只需要提供一个整数单元,而不是一个 n 维向量。请注意,整数代表数据的类别。
对于多类交叉熵,实际目标(y)是一热编码的。对于三级分类[[0,0,1],[1,0,0],[0,1,0]]
对于稀疏多类交叉熵,实际目标(y)是整数。对于以上三类分类问题:[3]、[1]、[2]
与多类交叉熵相比的优势:以上示例表明,对于多类交叉熵,目标需要一个包含大量零值的热编码向量,这导致了显著的存储器需求。通过使用稀疏分类交叉熵,可以节省计算时间,降低内存需求,因为它只需要一个单一的整数作为一个类,而不是一个完整的向量。
**稀疏多类交叉熵的缺点:**多类交叉熵可用于任何一类分类问题。然而,稀疏分类交叉熵只能在每个输入只属于一个类时使用。
例如,如果我们有 3 个类(a,b,c ),假设一个输入属于 b 类和 c 类,那么多类交叉熵的标签可以表示为[0,1,1],但不能表示为稀疏多类。
**c)**kull back lei bler(KL)发散损失
Kullback Leibler 散度是一种度量,它显示了两个概率分布彼此之间的差异程度。损失函数定义为:
作者图片
KL 散度损失为 0 表明分布是相同的。
KL 散度在某种程度上类似于交叉熵。像多类交叉熵一样,这里也需要对实际目标(y)进行一次性编码。如果使用预测的概率分布来近似期望的目标概率分布,则它计算有多少信息丢失。
KL 散度主要用于变分自动编码器。在这里,自动编码器学习如何将样本编码成潜在的概率分布,该概率分布被进一步馈送到解码器以生成输出。此外,KL 散度可用于多类分类。
使用 Keras 定义不同损失函数的 Python 片段
作者图片
何时使用哪些损失函数
如果目标变量是连续的(回归问题),那么可以使用 MSE、MAE 和 Huber 损失。通常,MSE 是常用的损失函数,但如果数据有异常值,则可以使用 MAE。但是如果使用 MAE,由于模数函数,它的计算量很大,并且还会产生梯度下降的问题。为了克服这些问题,Huber 损失被公认为损失函数,尽管δ的选择是迭代的和重要的。
对于分类问题,如果目标类是二进制的,则使用二进制交叉熵损失。另一方面,对于多类分类,可以使用多类交叉熵损失。与多类交叉熵相比,稀疏多类交叉熵要快得多,因为输入目标考虑的是整数,而不是一次性编码的向量。然而,多类交叉熵更一般化,因为输入目标可以表示为前面提到的多个类。铰链损失主要用于 SVM 模型的二元分类。KL 散度主要用于比简单的多类分类更复杂的函数(如变分自动编码器)。
希望你喜欢这篇文章!!
免责声明:本文所表达的观点是作者以个人身份发表的意见,而非其雇主的意见
参考文献:
https://ml-cheat sheet . readthedocs . io/en/latest/loss _ functions . html #交叉熵
在 Neo4j 中导入 CSV 文件
实践教程
为简单或快速而设计的两种不同方法的比较
图由马丁格兰德让,马丁格兰德让,CC BY-SA 3.0<https://creativecommons.org/licenses/by-sa/3.0>,通过维基共享。我没有改变这个形象。
最近,基于图的数据科学和机器学习已经成为各个领域的热门话题,从欺诈检测到知识图生成,社交网络分析等等。 Neo4j 是世界上最受欢迎和使用最广泛的图形数据库之一,为数据科学社区提供了巨大的好处。虽然 Neo4j 在系统中内置了一些训练图,但在某些时候,数据科学家会希望用自己的数据填充它。
Neo4j 接收数据最简单的格式是 CSV。关于如何填充数据库的网络搜索揭示了几种潜在的方法,但是在这篇文章中,我将把重点放在两种最常见和最强大的方法上,您可能希望考虑每一种方法,并浏览一些如何使用它们的示例。
我们将要经历的方法是
LOAD CSV
:当图形很小时的简单方法- Neo4j 管理工具:当图形变大时的快速方法
我将在这篇文章中演示这两种方法,并讨论你什么时候会用到它们。
必要的工具
为了开始,我们需要在我们的主机上安装 Neo4j。您可以使用 Neo4j Desktop 浏览下面的数据加载示例,它提供了一个不错的 UI,是学习如何使用数据库的好地方。
然而,为了这篇教程,我选择使用一个简单的 Docker 容器 ,原因如下。
首先是,容器是凉的。我总是把事情搞砸,这是一个不会毁掉一切的非常安全的方法。
其次,如今 Docker 容器中发生了如此多的数据科学,以至于可以认为 Neo4j 也在容器中。
最后一点,再现性在数据科学中非常重要,因此使用容器可以实现这一点。
综上所述,您将需要以下内容来运行下面的示例:
- Docker(安装说明可以在这里找到)
- 一个 Neo4j Docker 镜像(我将使用
neo4j:latest
,在我写这篇文章的时候是 4.2.2 版本) - CSV 格式的数据集
对于数据集,我将使用流行的权力的游戏图来演示数据加载,该图可从 Andrew Beveridge 维护的知识库中获得。
https://github.com/mathbeveridge/gameofthrones
使用该图作为演练的一个原因是数据被很好地格式化并且相当干净— 您将发现在加载数据时属性非常有用!尽管如此,随着我们继续进行下去,我们将不得不做一些数据清理和重新格式化,但这些都不是太重要。
https://networkofthrones.wordpress.com/
说到清理数据集,请注意在其中一个文件名中存在拼写错误或命名约定不一致。你会看到第五季节点文件被命名为
got-s5-node.csv
,而不是我们期望的got-s5-nodes.csv
的模式。
最后,我假设读者对 Cypher 有些熟悉。如果这不是你目前拥有的技能,我强烈推荐 Neo4j 网站上的在线密码教程(esp。关于创建数据的部分)。特别是,如果你刚刚开始学习密码,我可能会建议你查看一下[LOAD CSV](https://neo4j.com/docs/cypher-manual/current/clauses/load-csv/)
、[MERGE](https://neo4j.com/docs/cypher-manual/current/clauses/merge/)
、[MATCH](https://neo4j.com/docs/cypher-manual/current/clauses/match/)
、[SET](https://neo4j.com/docs/cypher-manual/current/clauses/set/)
和[PERIODIC COMMIT](https://neo4j.com/docs/cypher-manual/current/clauses/load-csv/#load-csv-importing-large-amounts-of-data)
的文档,我们将在下面使用这些文档。
https://neo4j.com/graphacademy/training-intro-40/enrollment/
码头集装箱
在启动 Docker 容器之前,我们需要做一些整理工作,将数据文件放在正确的位置。
首先,我们要确保 CSV 文件在正确的位置。当然,你可以告诉 Docker 在你想放它们的地方找。在我的例子中,我创建了一个目录~/graph_data/gameofthrones/
,并将我所有的。csv 在那里。
所有这一切就绪后,从 CLI 运行以下命令:
**docker** run -p 7474:7474 -p 7687:7687 \
--volume=$HOME/graph_data/data:/data \
--volume=$HOME/graph_data/gameofthrones/data:/var/lib/neo4j/import \
--env NEO4JLABS_PLUGINS='["apoc", "graph-data-science"]' \
--env apoc.import.file.enabled=true \
--env NEO4J_AUTH=**neo4j/1234** \
neo4j:latest
让我们来分析一下。我们在那里进行了一些端口转发,这将允许您通过 BOLT 协议在端口 7687 连接到localhost:7474.
上的网络浏览器中的 Neo4j 浏览器 UI ,您可以通过 Python 或其他编程语言的程序进行访问数据库的连接。
接下来,我们将一系列文件夹转发到容器中,以便在本地机器和容器之间进行读/写。
之后,我们引入一些环境变量。这些几乎都是可选的,但我把它们包括在上面,以防你想使用像 APOC 或 GDS 这样的库。第一个命令告诉容器加载最新版本的 APOC 和 GDS 库作为插件。我们还传递了一个配置设置作为环境变量,告诉 Neo4j 允许 APOC 读取文件。
最后为默认的neo4j
用户设置一个密码(非常复杂的1234
)。(您可以选择不使用这个位,但是如果您这样做了,那么您必须在每次启动容器时为用户重置密码。)
唷,好了。
最后要注意的是,容器会自动改变文件的所有权和权限,并且只有根用户才能访问。所以如果你打算在
sudo
之外查看或编辑它们,你可以考虑在容器接触不到它们的地方保存它们的备份。
假设一切顺利,您应该能够将 web 浏览器指向localhost:7474
并看到一个正在运行的 UI。所以现在我们可以进入下一步了!
Neo4j 浏览器 UI
加载 CSV:简单的方法
LOAD CSV
命令是将数据存入数据库的最简单的方法之一。这是一个 Cypher 命令,通常可以通过 Neo4j UI 运行。然而,它也可以通过 Python 连接器(或者您选择的语言的连接器)传入。对于另一篇博文,我们将通过 Python 保存与数据库的接口。
https://neo4j.com/docs/cypher-manual/current/clauses/load-csv/
如果您有一个“小”图,这种方法非常好。但是什么构成了小呢?一个很好的经验法则是,如果你的节点和边少于 100,000 个,而权力的游戏图确实有,那么这是一个很好的选择。但是,这并不是最快的方法(就像批量加载器一样),所以如果您的图形有点大,您可能需要考虑切换到其他加载方法。
查看我们的节点文件,我们可以看到每个季度都有一个文件。文件本身遵循一种非常简单的格式Id, Label
,其中 ID 只是名称的大写,标签是实际的角色名称。
got-s1-nodes.csv
一般来说, 在节点上创建一些唯一性约束 是很好的实践,以确保没有重复。这样做的一个好处是,这将为给定的标签和属性创建一个索引。除了加快对该数据的查询搜索之外,它还将确保节点上的 MERGE 语句(比如我们的 load 语句中使用的那个)明显更快。为此,我们使用:
CREATE CONSTRAINT UniqueCharacterId ON (c:Character) ASSERT c.id IS UNIQUE
请注意,在 Neo4j 中,标签、关系类型和属性键是区分大小写的。所以id
不等同于Id.
常见错误!
现在,我们可以使用以下方法将节点数据加载到数据库中:
WITH "file:///got-s1-nodes.csv" AS uriLOAD CSV WITH HEADERS FROM uri AS rowMERGE (c:Character {id:row.Id})
SET c.name = row.Label
是的,您确实需要使用 3 个斜线,但是好消息是,如果您将您的数据链接到/var/lib/neo4j/import
,这是容器中用于读取文件的默认目录,并且您不需要指定冗长的目录结构,这是非常危险的!
从上面我们可以看到,我们一次加载一行字符,并创建一个名为Character
的节点标签和一个名为id
的属性,同时创建一个名为name
的新属性,我们将该属性设置为等于Label
的 CSV 值(不要将该名称与所有节点都有一个Character
标签的事实相混淆)。
(请注意,uri
实际上也可以替换为 CSV 文件的 web 位置,因此您不必受限于将实际文件保存在本地计算机上。)
注意,我们在这里使用了MERGE
命令。我们也可以使用CREATE
命令,但是它们的作用有很大的不同。MERGE
查看是否已经有节点的实例,然后不创建它。它充当MATCH
或CREATE
。只有在数据库中尚未找到新节点时,才会创建新节点。
如此这般, ***MERGE***
命令都是等幂的。
接下来是引入边缘文件的时候了。这些格式与Source, Target, Weight, Season
类似。
got-s1-edges.csv
要加载这些内容,我们将在浏览器中使用以下命令:
WITH "file:///got-s1-edges.csv" AS uri
LOAD CSV WITH HEADERS FROM uri AS row
MATCH (source:Character {id: row.Source})
MATCH (target:Character {id: row.Target})
MERGE (source)-[:SEASON1 {weight: toInteger(row.Weight)}]-(target)
同样,上面的命令逐行读取文件,并用源角色和目标角色设置边。在这种情况下,这些引用节点文件中Id
的值。
我还分配了一个边缘类型:SEASON1
(并在随后的几季中改变它),根据源角色和目标角色在那一季中的互动次数来加权边缘。
我还应该简单提一下,这个图是作为无向图加载的(如数据存储库中所指定的)。根据没有箭头显示从源到目标的方向,您可以在最后一行中看出这一点。如果我们希望这是一个有向图,我们可以通过使用箭头来表示,这将改变格式为(source)-[...]->(target)
。
请注意,Neo4j 将 CSV 中的每个值 视为一个字符串 ,因此我已经通过toInteger
将权重转换为整数,如果您希望使用算法,这对于一些计算是必要的。同样,如果你想引入其他季节,你只需冲洗和重复每个边缘文件。
关于以这种方式导入较大的图形,有一点需要注意:Neo4j 是事务性的,对于单个事务中的大量导入,它会占用大量内存。
您可能需要通过定期将数据写入数据库来减少导入的内存开销。为此,在查询前添加以下内容(前缀:auto
仅在 Neo4j 浏览器中有效):
:auto USING PERIODIC COMMIT 500
这告诉 Neo4j 每隔 500 行写入数据库。这是一个很好的实践,特别是当你的内存有限的时候。
现在我们有了一个用于未来图形分析的填充数据库!它应该大致看起来像这样(虽然我没有显示每个节点,并修补了毛团以画出某些字符):
Neo4j UI 中所有季节的《权力的游戏》图表
有许多可视化选项,有兴趣的读者可以参考列表中的选项。
https://neo4j.com/developer/tools-graph-visualization/ [## 图形可视化工具-开发人员指南
neo4j.com](https://neo4j.com/developer/tools-graph-visualization/)
neo4j-管理导入
既然我们已经看到了通过简单的 CSV 文件加载数据,我们将使它稍微复杂一些,但是 明显更快 。
让我们假设你有一个真正“大”的图表。在这种情况下,我说的是节点和边数超过 1000 万的图。上面的方法需要很长时间,主要是因为它是事务性的,而不是离线加载。
如果实时更新数据库,您可能需要使用 LOAD CVS。但是即使在这种情况下,相对于整个数据库的大小,更新通常会以较小的批次进行。
导入工具的数据格式
在填充图表之前,我们必须以一种非常特殊的方式格式化我们的数据。我们还需要确保数据异常干净。对于节点列表,我们将把格式改为(显示前几行):
Id**:ID**,name,**:LABEL**
ADDAM_MARBRAND,Addam,Character
AEGON,Aegon,Character
AERYS,Aerys,Character
ALLISER_THORNE,Allister,Character
ARYA,Arya,Character
对于这个数据集来说,这可能看起来相当简单,但是我们不应该低估它的力量。这是因为它允许一次轻松导入多个节点类型。
例如,也许你有另一种节点类型,它是故事中的一个位置。你所要做的就是改变:LABEL
的值来达到这个目的。此外,您可以通过添加一个像propertyName
这样的列来添加节点属性,然后在每一行中给出值作为另一个单元格条目。
以类似的方式,我们重新构造边缘文件,如下所示:
:START_ID,:END_ID,weight:int,:TYPE
NED,ROBERT,192,SEASON1
DAENERYS,JORAH,154,SEASON1
JON,SAM,121,SEASON1
LITTLEFINGER,NED,107,SEASON1
NED,VARYS,96,SEASON1
如您所料,我们需要图中的每条边都有一行,即使边的类型发生了变化(例如::SEASON1
和:SEASON2
中两个字符之间的关系)。
这里保持命名约定是非常重要的!例如,您的节点文件必须总是有一个标记为:ID
的列,并且可以有一个名为:LABEL
的可选列用于节点标签。此外,还可以在这里指定任意数量的节点属性(尽管这个数据集中没有任何节点属性)。您的 edge 文件必须始终有一个:START_ID
、:END_ID
和可选的:TYPE
。这些标记后缀的名称不能更改。
(注意,在这种情况下,我已经创建了新的文件和文件名来反映格式的变化。)
重要提示!!!第一季的 edge 列表中有一个关于 Vardis Egen 的错别字(别担心…我也必须查一下那是谁)。节点列表有他的
Id
拼写为VARDIS_EGEN
,但是边列表有几个地方,虽然不是全部,是拼写为VARDIS_EGAN
的地方。这个问题最近已经得到了解决,但是如果您有一个旧版本的存储库,您可能需要进行更新。
否则,假设您不关心这个特定字符,最简单的解决方法就是将他作为另一个节点添加到拼写错误的节点列表中,或者解决边列表中的拼写问题(这就是我所做的)。这不会导致前面的方法出现问题,但是导入工具对这类问题更加敏感。
有很多选项可以用于这种格式…太多了,这篇文章无法一一介绍。鼓励有兴趣的读者阅读这种格式的文档,可以在这里找到。
https://neo4j.com/docs/operations-manual/current/tutorial/neo4j-admin-import/
使用导入工具
在摄取大量数据的情况下,Neo4j 提供了一个用于摄取大量数据的命令行工具:neo4j-admin import
,可以在容器内部的/var/lib/neo4j/bin/neo4j-admin
处找到。
https://neo4j.com/docs/operations-manual/current/tools/neo4j-admin/
这个工具的问题是,当数据库(至少在 Neo4j Community Edition 中)正在运行时,您不能实际使用它来创建图表。数据库必须首先关闭,这给我们的 Docker 容器带来了一点问题。在这种情况下,我们将从数据库尚未运行的新容器开始。
然后,我们将在本地机器的命令行中发出以下命令:
docker run \
--volume=$HOME/graph_data/data:/data \
--volume=$HOME/graph_data/gameofthrones/data:/var/lib/neo4j/import \
neo4j:latest bin/neo4j-admin import --nodes import/got-nodes-batch.csv --relationships import/got-edges.batch.csv
这将启动一个容器,该容器立即运行导入工具。我们在容器中指定数据文件所在的目录(确保使用更复杂的 CSV 格式的文件),相对于/var/lib/neo4j
。在我们的例子中,本地机器上的数据将连接到import/
中。
一旦运行这个命令,就会创建一个数据库,您可以在$HOME/graph_data/data
本地访问这个数据库。从这里,我们可以使用本文顶部的命令启动容器。(但是,请注意,如果您想要使用新的数据库和容器重新开始,必须通过 root 删除整个目录。)
既然已经填充了数据库并且启动了容器,我们可以通过localhost:7474
进入 UI 并像平常一样与之交互。
总结想法
一旦所有东西都被加载(包括所有 8 个季节),不管你用什么方法,你都应该得到一个如下所示的模式:
看起来像泰里尔家族:)
你会发现你有 407 个节点,4110 个关系。
我介绍了两种将数据从 CSV 文件导入 Neo4j 数据库的常用方法。然而,像任何软件一样,实际上有无数种方法可以达到同样的效果。
我希望这篇文章提供了一种清晰的方式来理解主要的方法,希望这只是你数据科学和机器学习之旅的开始!
如果你正在寻找下一步如何实际做一些与图形数据科学相关的事情,请查看我在如何开始使用 Neo4j 的图形数据科学库的帖子。
特别感谢 Mark Needham 在一些查询调优方面的帮助!
PS:另一个注意事项是,告诉 Neo4j 应该给数据库分配多少内存通常是个好主意。数据科学家想要运行的许多 Neo4j 算法都是内存密集型的。当然,确切的配置取决于您运行的机器。数据库配置是您的独特需求,超出了本文的范围。有兴趣的读者可以在这里查阅文档进行微调。现在我只是继续使用(尽管有限的)内存设置。