算法是不够的
意见
人工智能的下一个突破需要重新思考我们的硬件
现在的 AI 有一个问题:贵。训练 Resnet-152,一个现代计算机视觉模型,估计花费大约 100 亿次浮点运算,这与现代语言模型相比相形见绌。OpenAI 最近的自然语言模型 GPT-3 的训练预计将花费 3000 亿万亿次浮点运算,这在商用 GPU 上至少需要 500 万美元。相比之下,人脑可以识别人脸,回答问题,驾驶汽车,只需一根香蕉和一杯咖啡。
我们是怎么到这里的?
我们已经走了很长一段路。第一台计算机是专用机器。1822 年,英国数学家查尔斯·巴贝奇创造了“差分机”,它的唯一目的是计算多项式函数。1958 年,康奈尔大学教授弗兰克·罗森布拉特(Frank Rosenblatt)创造了“Mark I”,这是一种用于机器视觉任务的单层感知机的物理化身。在早期,硬件和算法是一回事。
随着冯诺依曼架构的引入,硬件和算法的统一性发生了变化,这是一种由用于计算的处理单元和用于存储数据和程序指令的存储单元组成的芯片设计。这种范式转变使得制造通用机器成为可能,这些机器可以被编程来完成任何期望的任务。冯-诺依曼体系结构已经成为现代数字计算机的蓝图。
但是,有一个条件。数据密集型程序需要内存和计算单元之间的大量通信,从而降低了计算速度。这种“冯-诺依曼瓶颈”是人工智能早期尝试失败的原因。标准 CPU 在大型矩阵乘法(深度神经网络中的核心计算操作)方面效率低下。由于现有硬件的瓶颈,早期的神经网络太浅,性能不佳。
这个问题的解决方案不是来自学术界,而是来自游戏行业,这是历史的讽刺之一。GPU 是 20 世纪 70 年代为加速视频游戏而开发的,它通过数千个计算核心并行处理数据密集型操作。这种并行性是解决冯-诺依曼瓶颈的有效方法。GPU 能够训练更深层次的神经网络,并已成为现代人工智能的现状硬件。
Riho Kroll 在 Unsplash 上拍摄的照片
硬件彩票
人工智能的研究有一点纯粹的运气成分。谷歌研究员萨拉·胡克称之为“硬件彩票”:早期的人工智能研究人员只是运气不好,因为他们被缓慢的 CPU 卡住了。在 GPU 出现的时候,恰好在该领域的研究人员“赢得”了硬件彩票。他们可以通过训练由高效 GPU 加速提供动力的深度神经网络来取得快速进展。
硬件彩票的问题是,一旦这个领域作为一个整体确定了赢家,就很难探索新的东西。硬件开发速度缓慢,而且需要芯片制造商在回报不确定的情况下进行大量前期投资。一个安全的赌注是简单地优化矩阵乘法,这已经成为现状。但从长远来看,这种对硬件和算法的特定组合的关注会限制我们的选择。
让我们回到最初的问题。为什么当今的人工智能如此昂贵?答案可能是我们还没有合适的硬件。硬件彩票的存在,加上商业激励,使得经济上很难打破目前的现状。
作为一个例子,考虑杰弗里辛顿的胶囊神经网络,一种计算机视觉的新方法。谷歌研究人员 Paul Barham 和 Michael Isard 发现这种方法在 CPU 上工作得相当好,但在 GPU 和 TPU 上表现不佳。原因?加速器已经针对最频繁的操作进行了优化,例如标准矩阵乘法,但是缺少针对胶囊卷积的优化。他们的结论(这也是他们论文的标题): ML 系统停滞不前。
人工智能研究人员存在“过度适应”现有硬件的风险,从长远来看,这将抑制该领域的创新。
前进的道路
“下一个突破可能需要一种根本不同的方式,用硬件、软件和算法的不同组合来建模世界。”莎拉·胡克,谷歌大脑
在人脑中,记忆和计算不是两个独立的组件,而是发生在同一个地方:神经元。记忆来自神经元通过突触连接在一起的方式,而计算来自神经元放电和传播来自感觉输入的信息的方式。硬件和算法是一回事,很像早期的计算机。这与我们今天做人工智能的方式完全不同。
由 GPU 和 TPU 支持的深度神经网络,即使它们在今天的许多任务中表现得非常好,从长远来看可能不是前进的方向。也许在硬件和算法架构的可能组合的广阔前景中,它们只是局部最优。
前进的道路始于认识到算法是不够的。对于下一代人工智能,我们需要在硬件和算法上进行创新。在 GPU 之前,AI 研究被卡住了。硬件上没有新的突破,就有可能再次卡住。
为了在人工智能方面取得进展,我们的模型需要学会应对混乱的现实世界
towardsdatascience.com](/supervised-learning-is-not-enough-8254814dfcc5) [## 智能行为的起源
为什么真正的人工智能需要的不仅仅是模式识别
towardsdatascience.com](/the-origin-of-intelligent-behavior-3d3f2f659dc2)
源于我们偏见的算法
算法有辨别能力吗?恐怕是的。让问题变得复杂的是,算法开发者很难被指控有恶意。那么,一个数学公式怎么会让个人和社区处于危险之中呢?
马库斯·斯皮斯克在 Unsplash 上的照片
尽管数学方程看起来遥远而冷漠,但它们通常也与可靠的硬科学联系在一起。然而,时不时地,事实证明一系列数字和符号隐藏着更不祥的潜在危险。是什么导致应用程序变坏,而这些应用程序原本是为好的事业服务的?可能有很多原因。首先浮现在脑海中的一个问题与人性有关。众所周知,人们遵循一种熟悉的机制,让刻板印象和偏见指导他们的生活。他们将它们应用于其他个人、社会团体和价值体系。这种认知模式很容易被缺乏想象力和不愿意给予事情适当的考虑所驱动。由此产生的爆炸性混合物会产生负面后果。盲目相信计算机数据的人看不到情况的复杂性,容易放弃对事件的主观评估。一旦发生这种情况,不幸的事情就会发生,给每个相关的人带来巨大的问题。我们的无知和算法越来越大的自主性(事实证明算法远非万无一失)产生了一个令人不安的混合体。
为警察服务的算法
警察非常适合测试智能技术。这种技术有其独特之处,业界非常清楚一个有用的算法有时会引发问题。但是让我们公平一点。智能数据处理允许警方计算机有效地将犯罪、历史数据和环境分组到类别和数据集。毫无疑问,应用程序有助于将地点、人物、心理特征、犯罪时间和使用的工具联系起来。孟菲斯大学的犯罪学家和数据处理学者选择使用 IBM 设计的预测分析软件。该项目团队创建了一个分析机制,该机制考虑了气温、当地地理、人口分布、商店和餐馆的位置、居民偏好和犯罪统计等变量。底层算法使用这些变量来识别城市中的潜在爆发点。它们确实有效。对该系统的测试表明,确实有可能在一定程度上预测未来,尽管没有给出具体的程度。然而,这种确定性足以证明向以这种方式确定的“高风险”地区派遣警察是合理的。还有人声称,这有助于将警方从报告事件那一刻起的反应时间缩短三分之一。我只能想象在这样的地方仅仅有警察在场就能阻止犯罪活动。虽然这个例子对于外行人来说可能难以理解,但它证明了现代技术提供了具有产生惊人结果潜力的“爆炸性”创新。
当计算机出错时
创业公司 Azavea 的 HunchLab 系统已经在美国推出,该系统筛选各种类型的海量数据(包括月相),以帮助警方调查犯罪。和前面的例子一样,这个想法是创建一个犯罪发生概率特别高的地点的地图。该计划的重点是整个城市的酒吧,学校和公共汽车站的位置。事实证明这很有帮助。虽然它的一些发现是显而易见的,但其他发现可能会令人惊讶。很容易解释为什么天气越冷犯罪越少。然而,要找到停在费城学校附近的汽车更容易被偷的原因却相当困难。没有这种软件的警察会想到去调查学校和汽车盗窃之间的联系吗?以上都是正面情景。然而,很难接受的事实是,智能机器不仅在处理过程中出错,而且还会导致错误的解释。通常,他们无法理解情境背景。不是完全不像人。
软件不可靠的可信度
2016 年,联合调查记者的独立新闻编辑室 ProPublica 发表了“机器偏见” 一文,内容是美国法院使用 Northpointe 的专业软件对罪犯进行侧写。文章指出,该软件旨在评估有前科的人再次犯罪的可能性,事实证明该软件很受美国法官的欢迎。Northpointe tool 估计黑人罪犯再次犯罪的可能性为 45%。与此同时,白人再次犯罪的风险为 24%。为了得出这些有趣的结论,算法假设黑人居住区比白人居住区有更高的犯罪行为风险。软件传播的假设受到了质疑,最终结束了 Northpointe 软件套件的分析生涯。问题的根本原因在于仅仅根据历史数据进行评估,以及缺乏认识,或者说未能设计出算法来说明最新的人口趋势。
算法和白脸
凯茜·奥尼尔(Cathy O’Neil)在她 2016 年的著作《数学毁灭的武器》中,探索了一个有趣的假设,即算法极大地影响了人们生活的各个领域。她指出,人们倾向于过于相信数学模型。她声称,这导致了以多种方式和多种层次形成的偏见。她说,偏见很早就产生了,甚至在算法用于分析的数据被收集之前。亚马逊的经理们也发现了同样的机制。他们注意到他们使用的招聘程序经常歧视女性。在寻找有前途的人时,女性总是占少数。是什么导致了偏差?对历史数据的依赖表明有更多的男性申请特定的职位。这破坏了就业中的性别平等,使天平向有利于男性的方向倾斜,最终导致了有偏见的就业政策的制定。
算法没有改变文化
上述评估软件是建立在基于性别的严重不平等困扰就业的时代开发的算法基础上的。那个特定时刻的特点是男性人数过多。在历史数据的基础上训练出来的算法致力于世界没有改变的“信念”。这意味着他们的假设和简化(如黑人意味着更高的犯罪概率,男性更有可能成为优秀的专业人士)是被误导的。
令人不安的问题
如果你认为类似上述的机制在职业和个人生活中可能很普遍,你可能会有所发现。有多少情况是我们不知道的,在这些情况下,数据是根据错误的假设组织起来的?算法未能解释经济和文化变化的频率有多高?
黑匣子是一个术语,用来指人类面对人工智能“大脑”中发生的事情时的无助。我们的无知和算法越来越大的自主性(事实证明算法远非万无一失)产生了一个令人不安的混合体。算法的偏见不会随着魔杖的挥动而消失。因此,关键问题是,他们的开发人员(他们经常自己完成设计和培训工作)是否会承担起这项任务,并意识到人类的偏见和行为模式会多么容易影响软件。
作品引用:
IBM,孟菲斯警察局,IBM SPSS:孟菲斯警察局,详细 ROI 案例研究, 链接 ,2015。
《边缘》,莫里斯·查马,马克·汉森补充报道,《维持未来》。弗格森事件后,圣路易斯警察采用犯罪预测软件, 链接 ,2018。
ProPublica,机器偏见:全国各地都有用来预测未来罪犯的软件。而且是对黑人的偏见,作者茱莉亚·安格温(Julia Angwin)、杰夫·拉森(Jeff Larson)、苏亚·马特图(Surya Mattu)和劳伦·基什内尔(Lauren Kirchner),ProPublica、 链接 ,2018。
相关文章:
从零开始的算法:决策树
约翰·西门子在 Unsplash 上拍摄的照片
从零开始的算法
从头开始详述和构建决策树模型
熟悉我早期作品的人会记得,我曾经写过一篇关于随机森林算法的概述。决策树的坚实基础是理解随机森林内部工作的先决条件;随机森林构建多个决策树,并输出回归问题中每棵树预测的平均值,在分类问题中,它输出每棵树预测的相对多数。
随机森林的概念概述
towardsdatascience.com](/random-forest-overview-746e7983316)
基于上面的故事,我将更加关注决策树学习算法,因为它是机器学习领域的一个基本算法。许多模型的结构基于决策树模型,如随机森林和梯度提升树。此外,我将从头开始做这个算法的 Python 实现,以进一步扩展我们对算法中发生的事情的直觉。
定义术语
- 参数模型:用于对总体参数进行推断,但是如果所有假设都不满足,这些推断就无效。
- 非参数模型:不假设数据或总体有任何特征结构。
决策树(购物车)
由于其可理解性和简单性而广受欢迎,决策树是最容易可视化和解释的算法之一,在向非技术观众展示结果时非常方便,这是行业中经常需要的。如果我们简单地考虑一棵处于类似流程图状态的树,从根到叶,其中从根到叶的路径定义了关于特征的决策规则,那么我们已经有了理解决策树学习所需的良好的直觉水平。
与我们在从零开始的算法系列中介绍的前两种算法(线性回归和逻辑回归)不同,决策树算法是一种非参数算法,这意味着它不对数据或总体做出假设。这确实对我们的模型有影响,因为我们在训练期间在模型中用偏差换取方差,使得决策树更容易过度拟合。
在机器学习领域,有两种主要的决策树模型。我们使用的方法取决于我们试图预测的目标变量的类型:
C 分类树:用于预测取离散值的目标变量的树模型。因此,叶节点代表一个类,而分支代表导致这些类标签的特征的合取。
R回归树:用于预测取连续值的目标变量的树模型。与分类树相反,在回归树中,每个叶节点包含一个连续值(即房价);分支代表导致每个连续变量的特征的合取。
注:分类和回归树(CART)是指这两个过程的总称,由 Breiman 等人于 1984 年首次提出。
在图 1 的中,我们可以看到 CART 算法遵循的结构。尽管这种结构是为两棵树设置的,但是分类树和回归树之间还是有一些细微的差别,比如每棵树的输出;分类树返回叶节点的模式类别,而回归树返回平均值。
图 1:决策树图表。
这两种算法之间的另一个显著区别是我们在划分特征空间时试图最小化的标准。一般来说,我们希望选择特征 j 和分割点 s ,它们能够最好地将特征空间分割成两个区域,但是如何在回归树和分类树中测量这一点是不同的,如图 2所示。
图 sklearn 中使用的杂质公式。[来源:Stacey Ronaghan——Scikit-learn 和 Spark 中的决策树、随机森林和特性重要性的数学
分块算法
注意:我们将构建一个决策树分类器,以基尼系数作为分割的标准。
- 考虑特征 j 和分割点 s 的所有可能分割
- 找到最佳分割后,将数据分成两个结果区域
- 重复 1 和 2,直到达到停止标准
上面的伪代码演示了计算机科学中称为递归的现象:一种解决问题的方法,其中解决方案取决于同一问题的较小实例的解决方案(来源: Wikipedia ),以及二进制分裂因此在一些示例中,步骤 1-2 被称为递归二进制分裂。
履行
对于此次实施,我们将利用以下框架:
- 线性代数和数据处理
- 熊猫(数据处理)
- Sci-kit 学习(机器学习)
- 图形可视化软件
点击此处查看完整代码…
permalink dissolve GitHub 是超过 5000 万开发人员的家园,他们一起工作来托管和审查代码,管理…
github.com](https://github.com/kurtispykes/ml-from-scratch/blob/master/decision_tree.ipynb)
import numpy as np
import pandas as pd
import graphviz
from sklearn.metrics import accuracy_score
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import export_graphviz
from sklearn.model_selection import train_test_split
我们将使用的数据集是来自 Scikit learn 的虹膜数据集—参见文档
# loading the data set
dataset = load_iris(as_frame=True)
df= pd.DataFrame(data= dataset.data)
# adding the target and target names to dataframe
target_zip= dict(zip(set(dataset.target), dataset.target_names))
df["target"] = dataset.target
df["target_names"] = df["target"].map(target_zip)
print(df.shape)
df.head()(150, 6)
图 3:虹膜数据集(前 5 行)
# Seperating to X and Y
X = df.iloc[:, :4]
y = df.iloc[:, -1]
# splitting training and test
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.75, shuffle=True, random_state=24)dt = DecisionTreeClassifier()
dt.fit(X_train, y_train)DecisionTreeClassifier()dot_data = export_graphviz(dt, out_file=None,
feature_names=X.columns,
class_names=dataset.target_names,
filled=True, rounded=True,
special_characters=True)
graph = graphviz.Source(dot_data)
graph
图 4:经过训练的决策树可视化——上面代码单元的输出。
sklearn_y_preds = dt.predict(X_test)
print(f"Sklearn Implementation:\nACCURACY: {accuracy_score(y_test, sklearn_y_preds)}")Sklearn Implementation:
ACCURACY: 0.9473684210526315
我的实现
我们将需要根据特定分支的决策规则,将我们的数据分为真索引和假索引。如果满足决策规则的条件,我们说该分支为真(我们将表示为left
)和假(表示为right
)。
划分数据
def partition(data, column, value):
"""
Partition the data into left (indicating True) and right (indicating false).
Inputs
data: The data to partition
Outputs
left: index of values that meet condition
right: index of values that fail to meet the condition
"""
left = data[data[column] <= value].index
right = data[data[column] > value].index
return left, right
为了检查我们的函数是否正常工作,我们将对所有数据执行拆分,并手动将最佳列和值传递给它,以查看我们的数据是否相对于上图进行了分离。
# performing a split on the root node
left_idx, right_idx = partition(X_train, "petal length (cm)", 2.45)
print("[petal length (cm) <= 2.45]")
# print results --> left_idx = 38 setosa | right index = 42 versicolor, 32 virginica
print(f"left_idx: {dict(zip(np.unique(y_train.loc[left_idx], return_counts=True)[0], np.unique(y_train.loc[left_idx], return_counts=True)[1]))}\n\
right_idx: {dict(zip(np.unique(y_train.loc[right_idx], return_counts=True)[0], np.unique(y_train.loc[right_idx], return_counts=True)[1]))}")[petal length (cm) <= 2.45]
left_idx: {'setosa': 38}
right_idx: {'versicolor': 42, 'virginica': 32}
完美!
接下来,我们需要一个我们想要最小化的标准。在这个实现中,我们将使用一个名为gini_impurity
的函数来最小化基尼系数。在不触及技术层面的情况下,基尼系数只是简单地衡量了我们的数据在某个节点上的混合程度;为了帮助理解这个概念,想想黄金。当黄金不纯时,它指的是其中其他物质的混合物,然而当它纯时,我们可以说没有杂质(这并不完全正确,因为精炼黄金纯度高达 99.99%,所以技术上仍有一些杂质)。
理想情况是节点是纯的,这意味着目标标签被分成单独的节点。—要深入了解基尼系数杂质的技术细节,请参见此处
基尼杂质
def gini_impurity(label, label_idx):
"""
A measure of how often a randomly chosen element from the set would
be incorrectly labelled if it was randomly labelled according to the
distribution of labels in the subset (Soure: Wikipedia)
Inputs
label: The class label available at current node
Outputs
impurity: The gini impurity of the node
"""
# the unique labels and counts in the data
unique_label, unique_label_count = np.unique(label.loc[label_idx], return_counts=True)
impurity = 1.0
for i in range(len(unique_label)):
p_i = unique_label_count[i] / sum(unique_label_count)
impurity -= p_i ** 2
return impurity
如果你向上滚动回到*图 4,*你会看到在根节点的杂质是 0.663。因此,为了确定我们的gini_impurity
函数是否正常工作,我们应该在输出中看到这个数字
# Gini impurity of the first node
impurity = gini_impurity(y_train, y_train.index)
impurity`0.6626275510204082
太好了!
为了在一个特性(和价值)上分裂,我们需要一种量化的方法,如果我们在那个点上分裂,什么会产生最好的结果。信息增益是量化在每个节点上分割什么特征和特征值的有用方法。对于树的每个节点,信息值“表示在给定实例到达该节点的情况下,指定新实例应该被分类为是还是否所需的预期信息量”。(来源:维基百科)
信息增益
def information_gain(label, left_idx, right_idx, impurity):
"""
For each node of the tree, the information gain "represents the
expected amount of information that would be needed to specify whether
a new instance should be classified yes or no, given that the example
reached that node. (Source: Wikipedia)
Inputs
left: The values that met the conditions of the current node
right: The values that failed to meet the conditions of the current noode
gini_impurity: the uncertainty at the current node
Outputs
info_gain: The information gain at the node
"""
p = float(len(left_idx)) / (len(left_idx) + len(right_idx))
info_gain = impurity - p * gini_impurity(label, left_idx) - (1 - p) * gini_impurity(label, right_idx)
return info_gain
最佳的第一分割是提供最多信息增益的分割。对每个不纯节点重复这个过程,直到树完成。(来源:维基百科)
基于上面的陈述,我们现在可以明白为什么值为 2.45 的花瓣长度(cm)被选为第一个分割。
# testing info gain of the first split at root node
info_gain = information_gain(y_train, left_idx, right_idx, impurity)
info_gain0.33830322669608387# testing a random feature and value to see the information gain
left_idx, right_idx = partition(X_train, "petal width (cm)", 1.65)
impurity = gini_impurity(y_train, y_train.index)
info_gain = information_gain(y_train, left_idx, right_idx, impurity)
info_gain0.25446843371494937
以上助手功能现在要发挥作用了。我们必须手动选择特性和值,对吗?下一个函数现在将自动搜索特征空间,并找到最佳分割数据的特征和特征值。
寻找最佳分割
def find_best_split(df, label, idx):
"""
Splits the data on the best column and value
Input
df: the training data
label: the target label
idx: the index of the data
Output:
best_gain: the max information gain
best_col: the column that produced best information gain
best_val: the value of the column that produced best information gain
"""
best_gain = 0
best_col = None
best_value = None
df = df.loc[idx] # converting training data to pandas dataframe
label_idx = label.loc[idx].index # getting the index of the labels
impurity = gini_impurity(label, label_idx) # determining the impurity at the current node
# go through the columns and store the unique values in each column (no point testing on the same value twice)
for col in df.columns:
unique_values = set(df[col])
# loop thorugh each value and partition the data into true (left_index) and false (right_index)
for value in unique_values:
left_idx, right_idx = partition(df, col, value)
# ignore if the index is empty (meaning there was no features that met the decision rule)
if len(left_idx) == 0 or len(right_idx) == 0:
continue
# determine the info gain at the node
info_gain = information_gain(label, left_idx, right_idx, impurity)
# if the info gain is higher then our current best gain then that becomes the best gain
if info_gain > best_gain:
best_gain, best_col, best_value = info_gain, col, value
return best_gain, best_col, best_valuefind_best_split(X_train, y_train, y_train.index)(0.33830322669608387, 'petal length (cm)', 1.9)
太好了,我们有了算法运行所需的所有组件。然而,上面的函数只对我们的训练数据(树桩/树根)执行一次分割。
附加助手功能
# helper function to count values
def count(label, idx):
"""
Function that counts the unique values
Input
label: target labels
idx: index of rows
Output
dict_label_count: Dictionary of label and counts
"""
unique_label, unique_label_counts = np.unique(label.loc[idx], return_counts=True)
dict_label_count = dict(zip(unique_label, unique_label_counts))
return dict_label_count# check counts at first node to check it aligns with sci-kit learn
count(y_train, y_train.index){'setosa': 38, 'versicolor': 42, 'virginica': 32}
这里有一些类,我们将使用它们来存储决策树中的特定数据并打印我们的树。
# [https://github.com/random-forests/tutorials/blob/master/decision_tree.ipynb](https://github.com/random-forests/tutorials/blob/master/decision_tree.ipynb)class Leaf:
"""
A Leaf node classifies data.
This holds a dictionary of class (e.g., "Apple") -> number of times
it appears in the rows from the training data that reach this leaf.
"""
def __init__(self, label, idx):
self.predictions = count(label, idx)# [https://github.com/random-forests/tutorials/blob/master/decision_tree.ipynb](https://github.com/random-forests/tutorials/blob/master/decision_tree.ipynb)class Decision_Node:
"""
A Decision Node asks a question.
This holds a reference to the question, and to the two child nodes.
"""
def __init__(self,
column,
value,
true_branch,
false_branch):
self.column = column
self.value = value
self.true_branch = true_branch
self.false_branch = false_branch# [https://github.com/random-forests/tutorials/blob/master/decision_tree.ipynb](https://github.com/random-forests/tutorials/blob/master/decision_tree.ipynb)def print_tree(node, spacing=""):
"""
World's most elegant tree printing function.
Input
node: the tree node
spacing: used to space creating tree like structure
"""
# Base case: we've reached a leaf
if isinstance(node, Leaf):
print (spacing + "Predict", node.predictions)
return
# Print the col and value at this node
print(spacing + f"[{node.column} <= {node.value}]")
# Call this function recursively on the true branch
print (spacing + '--> True:')
print_tree(node.true_branch, spacing + " ")
# Call this function recursively on the false branch
print (spacing + '--> False:')
print_tree(node.false_branch, spacing + " ")
递归二进制分裂
为了使算法工作,我们将需要分裂递归地发生,直到我们满足停止标准——在这种情况下,直到每个叶节点都是纯的。
def build_tree(df, label, idx):
"""
Recursively Builds the tree until is leaf is pure.
Input
df: the training data
label: the target labels
idx: the indexes
Output
best_col: the best column
best_value: the value of the column that minimizes impurity
true_branch: the true branch
false_branch: the false branch
"""
best_gain, best_col, best_value = find_best_split(df, label, idx)
if best_gain == 0:
return Leaf(label, label.loc[idx].index)
left_idx, right_idx = partition(df.loc[idx], best_col, best_value)
true_branch = build_tree(df, label, left_idx)
false_branch = build_tree(df, label, right_idx)
return Decision_Node(best_col, best_value, true_branch, false_branch)my_tree = build_tree(X_train, y_train, X_train.index)print_tree(my_tree)[petal length (cm) <= 1.9]
--> True:
Predict {'setosa': 38}
--> False:
[petal width (cm) <= 1.6]
--> True:
[petal length (cm) <= 4.9]
--> True:
Predict {'versicolor': 40}
--> False:
[sepal length (cm) <= 6.0]
--> True:
[sepal width (cm) <= 2.2]
--> True:
Predict {'virginica': 1}
--> False:
Predict {'versicolor': 1}
--> False:
Predict {'virginica': 2}
--> False:
[petal length (cm) <= 4.8]
--> True:
[sepal width (cm) <= 3.0]
--> True:
Predict {'virginica': 3}
--> False:
Predict {'versicolor': 1}
--> False:
Predict {'virginica': 26}
超级!现在,您已经看到了如何从头开始实现决策树,并且我们已经根据我们的训练数据对其进行了训练。然而,它并没有就此停止,首先构建算法的目的是自动对新的观察结果进行分类。下一节将致力于推理…
推理
def predict(test_data, tree):
"""
Classify unseen examples
Inputs
test_data: Unseen observation
tree: tree that has been trained on training data
Output
The prediction of the observation.
"""
# Check if we are at a leaf node
if isinstance(tree, Leaf):
return max(tree.predictions)
# the current feature_name and value
feature_name, feature_value = tree.column, tree.value
# pass the observation through the nodes recursively
if test_data[feature_name] <= feature_value:
return predict(test_data, tree.true_branch)
else:
return predict(test_data, tree.false_branch)
为了检查我们的函数是否正确运行,我将使用一个观察示例。
# taking one instance to test function
example, example_target = X_test.iloc[6], y_test.iloc[6]
example, example_target(sepal length (cm) 5.3
sepal width (cm) 3.7
petal length (cm) 1.5
petal width (cm) 0.2
predictions setosa
Name: 48, dtype: object,
'setosa')
当我们将我们的predict
函数应用到示例中时,我们应该希望观察相应地遍历树并输出setosa
,让我们检查…
# if working correctly should output setosa
predict(example, my_tree)'setosa'
至高无上!!然而,这只是一个例子。如果我们想将这个函数应用到我们测试集中的每一个观察值,我们可以使用df.apply
——参见文档
# create a new col of predictions
X_test["predictions"] = X_test.apply(predict, axis=1, args=(my_tree,))
好了,关键时刻到了。我们需要检查我们的算法是否返回与 scikit 学习模型相同的预测,作为检查我们是否正确实现了我们的算法的一种方式。我们通过简单地执行sklearn_y_preds == X_true["predictions"]
来做到这一点,它为每个观察返回一个布尔数组——在我们的例子中,它们都是真的。
print(f"Sklearn Implementation:\nACCURACY: {accuracy_score(y_test, sklearn_y_preds)}\n\n\
My Implementation:\nACCURACY: {accuracy_score(y_test, X_test['predictions'])}")Sklearn Implementation:
ACCURACY: 0.9736842105263158
My Implementation:
ACCURACY: 0.9736842105263158
赞成的意见
- 简单易懂
- 能够处理数字和分类数据
- 几乎不需要数据准备
- 适用于大型数据集
- 内置功能选择
骗局
- 树的不稳定性(改变数据中的某些东西可以改变一切)
- 缺乏平滑度(回归问题的特殊问题)
- 倾向于过度拟合
包裹
建立决策树的良好基础将有助于理解许多其他重要的机器学习算法。它是一种非常强大的算法,经常被用作集成模型来赢得各种数据科学比赛。虽然很容易概念化,但决策树很难从头开始构建,因此我总是主张尽可能使用已建立的机器学习框架。
感谢您阅读到文章结尾!如果你想和我保持联系,你可以在 LinkedIn 上找到我。
[## Kurtis Pykes -人工智能作家-走向数据科学| LinkedIn
在世界上最大的职业社区 LinkedIn 上查看 Kurtis Pykes 的个人资料。Kurtis 有一个工作列在他们的…
www.linkedin.com](https://www.linkedin.com/in/kurtispykes/)
从头开始的算法:K-最近邻
从零开始的算法
从头开始详述和构建 K-NN 算法
介绍
能够执行分类和回归的非参数算法;斯坦福大学教授 Thomas Cover 于 1967 年首次提出了 K 近邻算法的思想。
许多人经常把 K-NN 称为懒惰学习器或一种基于实例的学习器,因为所有的计算都推迟到函数求值。我个人认为,当我们开始概念化机器学习算法时,这将 K-最近邻推向了不太复杂的一端。
无论我们是在做分类还是回归风格问题,输入都将由原始特征空间中的 k 个最近的训练样本组成。然而,算法的输出当然取决于问题的类型——关于不同输出的更多信息,请参见术语部分。
链接到文章中生成的代码…
通过在 GitHub 上创建一个帐户,为 kurtispykes/ml 的从头开发做出贡献。
github.com](https://github.com/kurtispykes/ml-from-scratch/tree/master)
术语
K-最近邻分类 →输出将确定类成员,并且通过其邻居的多数投票来进行预测。因此,新实例将被分配到最常见的 k 个最近邻居的类中。
K-最近邻回归 →输出将确定对象的属性值。因此,新实例将被分类为第 k 个最近邻居的平均值
基于实例的学习 →一系列机器学习算法,不执行显式归纳,而是将新的问题实例与存储在内存中的训练中看到的实例进行比较。(来源: 维基百科 )
惰性学习 →一种机器学习方法,在这种方法中,训练数据的概括在理论上被延迟,直到向系统发出查询,这与系统在接收查询之前试图概括训练数据的急切学习相反。(来源: 百科 )
创建模型
创建 K-NN 算法非常简单。训练阶段实际上是存储训练样本的特征向量和标签,但是我们需要为 k. 通常为*,*确定一个正整数。当我们选择一个较大的值 k 时,我们会减少噪声对分类的影响,从而使类别之间的边界不那么明显。最终, k 的选择很大程度上受到数据的影响,这意味着我们无法知道,直到我们尝试了数据,然而我们可以使用许多不同的试探法来为我们的数据选择 k 。
注:要阅读关于调整 k 超参数的更多信息,请参见维基百科页面的 超参数优化 。
太好了,我们选择了 k。为了对分类任务的新实例进行预测,识别出与新观察最接近的 k 个记录(来自训练数据)。在对 K 个近邻进行评估后,将进行预测——参见术语部分的K-最近邻分类,了解如何进行预测。
为了识别与新实例最接近的 k 个记录,我们必须对所有实例进行测量。这可以通过多种方式实现,尽管作为一种指导,当我们有连续变量时,许多从业者经常使用欧几里德距离,而对于离散变量则使用汉明距离。
图像汉明和欧氏距离
分块算法
- 计算欧几里德距离
- 定位邻居
- 预测
实现
为了实现我们的 K-最近邻分类算法,我们将使用来自 Scikit-Learn 的虹膜数据集。在这项任务中,我们面临的挑战是在给定花朵尺寸的情况下,预测花朵是setosa
、versicolor
还是virginica
——这使它成为一项多类分类任务。
注意:在这个实现中,我没有执行任何试探法来选择最佳的k——我只是随机选择了一个 k 值。
import numpy as np
import pandas as pd
from collections import Counterimport matplotlib.pyplot as plt
%matplotlib inlinefrom sklearn.metrics import accuracy_score
from sklearn.datasets import load_iris
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_splitiris = load_iris()
X, y = iris.data, iris.targetX_train, X_test, y_train, y_test = train_test_split(X, y, test_size= 0.2, random_state=1810)X_train.shape, y_train.shape, X_test.shape, y_test.shape((120, 4), (120,), (30, 4), (30,))
我们现在有了数据,并使用了基于维持的交叉验证方案来拆分数据——如果您不熟悉这个术语,请参见下面的链接。
验证机器学习模型的性能
towardsdatascience.com](/cross-validation-c4fae714f1c5)
第一步是计算两行之间的欧几里德距离。
def euclidean(x1, x2):
return np.sqrt(np.sum((x1 - x2)**2))
为了测试这个函数,我从 Jason brown lee那里取了一些代码,他用这些代码来测试他的距离函数。如果我们有正确的实现,那么我们的输出应该是相同的。
# dataset from [https://machinelearningmastery.com/tutorial-to-implement-k-nearest-neighbors-in-python-from-scratch/](https://machinelearningmastery.com/tutorial-to-implement-k-nearest-neighbors-in-python-from-scratch/)dataset = [[2.7810836,2.550537003,0],
[1.465489372,2.362125076,0],
[3.396561688,4.400293529,0],
[1.38807019,1.850220317,0],
[3.06407232,3.005305973,0],
[7.627531214,2.759262235,1],
[5.332441248,2.088626775,1],
[6.922596716,1.77106367,1],
[8.675418651,-0.242068655,1],
[7.673756466,3.508563011,1]]row0 = dataset[0]for row in dataset:
**print**(euclidean(np.array(row0), np.array(row)))0.0
1.3290173915275787
1.9494646655653247
1.5591439385540549
0.5356280721938492
4.952940611164215
2.7789902674782985
4.3312480380207
6.59862349695304
5.084885603993178
我们得到完全相同的输出——请随意查看提供的链接。
如前所述,新观察的 k —邻居是来自训练数据的 k 最近实例。使用我们的距离函数euclidean
,我们现在可以计算训练数据中的每个观察值与我们已经传递的新观察值之间的距离,并从最接近我们的新观察值的训练数据中选择 k 个实例。
def find_neighbors(X_train, X_test, y_train, n_neighbors):
distances = [euclidean(X_test, x) for x in X_train]
k_nearest = np.argsort(distances)[:n_neighbors]
k_nearest_label = [y_train[i] for i in k_nearest]
most_common = Counter(k_nearest_label).most_common(1)[0][0]
return most_common
此函数计算新观察到定型数据中所有行的距离,并将其存储在一个列表中。接下来,我们使用 NumPy 模块np.argsort()
找到 k 最低距离的索引—参见文档。然后我们使用索引来识别 k 个实例的类。之后,我们使用 Pythons 内置模块中的Counter
函数计算k_nearest_labels
列表中实例的数量,并返回最常见的(计数最高的标签)。然而,在我们做出预测之前,我们不会看到它的实际运行,所以让我们来构建预测函数。
def predict(X_test, X_train, y_train, n_neighbors=3):
predictions = [find_neighbors(X_train, x, y_train, n_neighbors) for x in X_test]
return np.array(predictions)predict(X_test, X_train, y_train, n_neighbors=3)
**array**([0, 0, 2, 2, 0, 1, 0, 0, 1, 1, 2, 1, 2, 0, 1, 2, 0, 0, 0, 2, 1, 2, 0, 0, 0, 0, 1, 1, 0, 2])
在predict
函数中,我们使用列表理解来为测试集中的每个新实例找到最近的邻居,并返回一个数组。使用 3 个邻居,我们在任务上获得了 100%的准确性,并且我们可以将其与 scikit-learning 实现进行比较,以查看我们是否获得了相同的结果——确实如此。
注意:K 近邻分类器的文档可以在这里找到
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, y_train)sklearn_preds = knn.predict(X_test)
preds = predict(X_test, X_train, y_train, n_neighbors=3)**print**(f"My Implementation: {accuracy_score(y_test, preds)}\nScikit-Learn Implementation: {accuracy_score(y_test, sklearn_preds)}")My Implementation: 1.0
Scikit-Learn Implementation: 1.0
优点
- 直观简单
- 没有训练步骤
- 可用于分类和回归(以及无监督学习)
- 对于多类问题易于实现
缺点
- 随着数据的增长,算法变得非常慢
- 对异常值敏感
- 不平衡的数据会导致问题—可以使用加权距离来克服这个问题。
包裹
在本故事中,您了解了 K-最近邻算法、如何在 Python 中从头开始实现 K-最近邻分类算法,以及使用 K-最近邻的利弊。
让我们继续 LinkedIn 上的对话……
[## Kurtis Pykes -人工智能作家-走向数据科学| LinkedIn
在世界上最大的职业社区 LinkedIn 上查看 Kurtis Pykes 的个人资料。Kurtis 有一个工作列在他们的…
www.linkedin.com](https://www.linkedin.com/in/kurtispykes/)
从头开始的算法:线性回归
从零开始的算法
从头开始详述和构建线性回归模型
线性回归是一种流行的线性机器学习算法,用于基于回归的问题。由于它的简单性以及它如何构建到逻辑回归和神经网络等其他算法中,它通常是第一次学习机器学习时学习的第一批算法之一。在这个故事中,我们将从头开始实现它,这样我们就可以对线性回归模型中发生的事情建立直觉。
链接到 Github Repo…
[## kurtispykes/ml-从零开始
permalink dissolve GitHub 是超过 5000 万开发人员的家园,他们一起工作来托管和审查代码,管理…
github.com](https://github.com/kurtispykes/ml-from-scratch/blob/master/linear_regression.ipynb)
注意:许多框架都有高度优化的代码,如 Scikit-Learn、Tensorflow 和 PyTorch,因此通常没有必要从头构建自己的算法。然而,当我们从零开始构建模型时,它为我们对模型中正在发生的事情的直觉提供了一个很好的目的,这有助于我们尝试改进我们的模型性能。
线性模型是一种算法,它通过简单地计算输入要素加上偏差项(也称为截距项)的加权和来进行预测。考虑到这一点,当我们使用线性回归模型时,我们希望解释因变量(即房价)和一个或多个自变量(即位置、卧室、面积等)之间的关系。
图 1:多元线性回归
当我们训练一个模型时,我们试图设置参数以得到一条最适合训练数据的线。因此,当我们训练线性回归模型时,我们试图找到最能最小化成本函数的θ值。回归模型最常见的成本函数是 RMSE ,然而,最小化 MSE 要容易得多,因为它会导致相同的结果。
创建模型
如果你从未从头开始编写过机器学习算法,我非常鼓励你这样做。约翰·苏利文写了一个非常有用的故事,名为 从零开始编写任何机器学习算法的 6 个步骤:感知机案例研究 w 这是我在互联网上找到的关于从零开始编写算法的最好建议。
分块算法
- 随机初始化假设函数的参数
- 计算偏导数(点击阅读更多关于这个的信息)
- 更新参数
- 重复 2-3,重复 n 次迭代(直到成本函数最小化)
- 推理
实施
对于本节,我将利用 3 个 Python 包。NumPy 用于线性代数,Scikit-Learn 是一个流行的机器学习框架,Matplotlib 用于可视化我们的数据。
**import** **numpy** **as** **np**
**import** **matplotlib.pyplot** **as** **plt**
**from** **sklearn.datasets** **import** make_regression
**from** **sklearn.linear_model** **import** LinearRegression
**from** **sklearn.model_selection** **import** train_test_split
**from** **sklearn.metrics** **import** mean_squared_error
首先,我们需要一个数据集。为此,我将sklearn.datasets.make_regression
允许您生成一个随机回归问题—参见文档。接下来,我将用sklearn.model_selection.train_test_split
— 文档将我的数据分成训练集和测试集。
# creating the data set
X, y = make_regression(n_samples=100, n_features=1, n_targets=1, noise=20, random_state=24)
# splitting training and test
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.75, random_state=24)
让我们用matplotlib.pyplot
来看看我们的数据是什么样子的— 文档。
*# visualize*
plt.scatter(X, y)
plt.show()
图 2:我们生成的回归问题
现在我们可以开始实施线性回归了。我们程序块的第一步是为我们的假设函数随机初始化参数。
**def** param_init(X):
*"""*
*Initialize parameters for linear regression model*
*__________________*
*Input(s)*
*X: Training data*
*__________________*
*Output(s)*
*params: Dictionary containing coefficients*
*"""*
params = {} *# initialize dictionary*
_, n_features = X.shape *# shape of training data*
*# initializing coefficents to 0*
params["W"] = np.zeros(n_features)
params["b"] = 0
**return** params
非常好。接下来我们要计算偏导数并更新我们的参数。我们使用一种非常重要的叫做梯度下降的机器学习算法来做到这一点。因此,我们可以用梯度下降实现步骤 2-4。
**def** gradient_descent(X, y, params, alpha, n_iter):
*"""*
*Gradient descent to minimize cost function*
*__________________*
*Input(s)*
*X: Training data*
*y: Labels*
*params: Dictionary contatining random coefficients*
*alpha: Model learning rate*
*__________________*
*Output(s)*
*params: Dictionary containing optimized coefficients*
*"""*
W = params["W"]
b = params["b"]
m = X.shape[0] *# number of training instances*
**for** _ **in** range(n_iter):
*# prediction with random weights*
y_pred = np.dot(X, W) + b
*# taking the partial derivative of coefficients*
dW = (2/m) * np.dot(X.T, (y_pred - y))
db = (2/m) * np.sum(y_pred - y)
*# updates to coefficients*
W -= alpha * dW
b -= alpha * db
params["W"] = W
params["b"] = b
**return** params
使用这些函数,我们可以根据训练数据训练我们的线性回归模型,以获得我们进行推理所需的模型参数。
**def** train(X, y, alpha=0.01, n_iter=1000):
*"""*
*Train Linear Regression model with Gradient decent*
*__________________*
*Input(s)*
*X: Training data*
*y: Labels*
*alpha: Model learning rate*
*n_iter: Number of iterations*
*__________________*
*Output(s)*
*params: Dictionary containing optimized coefficients*
*"""*
init_params = param_init(X)
params = gradient_descent(X, y, init_params, alpha, n_iter)
**return** params
现在,当我们运行这个函数时,我们将从我们的训练数据中获得优化的权重,我们将使用这些权重对我们的测试数据进行推断。接下来,我们需要使用我们存储的权重为我们的推断创建一个预测函数。
**def** predict(X_test, params):
*"""*
*Train Linear Regression model with Gradient decent*
*__________________*
*Input(s)*
*X: Unseen data*
*params: Dictionary contianing optimized weights from training*
*__________________*
*Output(s)*
*y_preds: Predictions of model*
*"""*
y_preds = np.dot(X_test, params["W"]) + params["b"]
**return** y_preds
太好了!让我们运行这些函数并绘制它们,看看会发生什么…
params = train(X_train, y_train) *# train model*
y_preds = predict(X_test, params) *# inference*plt.scatter(X_test, y_test)
plt.plot(X_test, y_preds, color="red")
plt.title("Predictions Dummy Regression Data")
plt.xlabel("X axis")
plt.ylabel("Y axis")
plt.show()
图 3:来自自定义线性回归模型的预测。
我们的最佳系列似乎相当不错。为了完全确定我们的实现,我们很幸运有许多机器学习库,带有优化的代码,可以用来比较我们的实现。为了进行比较,我将简单地检查我们的实现与 Scikit-learn 的 RMSE 。
注意:在同一个图上绘制它们的实现也是值得的,如果它们的最佳拟合线覆盖了你的,那么你就在正确的轨道上。
lin_reg = LinearRegression()
lin_reg.fit(X_train, y_train)
sklearn_y_preds = lin_reg.predict(X_test)print(f"My implementation: {np.sqrt(mean_squared_error(y_test, y_preds))}**\n**Sklearn implementation: {np.sqrt(mean_squared_error(y_test, sklearn_y_preds))}")>>>> My implementation: 20.986105292320207
Sklearn implementation: 20.986105292320207
匹配了!
对于这种实现,我们使用了过程化编程,这种编程维护起来会变得非常复杂和麻烦,并且没有最大限度地发挥 Python 的潜力,Python 是一种面向对象编程(OOP) 语言。关于这一点,这里是我们的线性回归模型的 OOP 实现。
**class** **LinReg**():
*"""*
*Custom made Linear Regression class*
*"""*
**def** __init__(self, alpha=0.01, n_iter= 1000):
self.alpha = alpha
self.n_iter = n_iter
self.params = {}
**def** param_init(self, X_train):
*"""*
*Initialize parameters for linear regression model*
*__________________*
*Input(s)*
*X: Training data*
*"""*
_, n_features = self.X.shape *# shape of training data*
*# initializing coefficents to 0*
self.params["W"] = np.zeros(n_features)
self.params["b"] = 0
**return** self
**def** gradient_descent(self, X_train, y_train):
*"""*
*Gradient descent to minimize cost function*
*__________________*
*Input(s)*
*X: Training data*
*y: Labels*
*params: Dictionary contatining random coefficients*
*alpha: Model learning rate*
*__________________*
*Output(s)*
*params: Dictionary containing optimized coefficients*
*"""*
W = self.params["W"]
b = self.params["b"]
m = X_train.shape[0]
**for** _ **in** range(self.n_iter):
*# prediction with random weights*
y_pred = np.dot(X_train, W) + b
*# taking the partial derivative of coefficients*
dW = (2/m) * np.dot(X_train.T, (y_pred - y_train))
db = (2/m) * np.sum(y_pred - y_train)
*# updates to coefficients*
W -= self.alpha * dW
b -= self.alpha * db
self.params["W"] = W
self.params["b"] = b
**return** self
**def** train(self, X_train, y_train):
*"""*
*Train Linear Regression model with Gradient decent*
*__________________*
*Input(s)*
*X: Training data*
*y: Labels*
*alpha: Model learning rate*
*n_iter: Number of iterations*
*__________________*
*Output(s)*
*params: Dictionary containing optimized coefficients*
*"""*
self.params = param_init(X_train)
gradient_descent(X_train, y_train, self.params , self.alpha, self.n_iter)
**return** self
**def** predict(self, X_test):
*"""*
*Train Linear Regression model with Gradient decent*
*__________________*
*Input(s)*
*X: Unseen data*
*params: Dictionary contianing optimized weights from training*
*__________________*
*Output(s)*
*y_preds: Predictions of model*
*"""*
y_preds = np.dot(X_test, self.params["W"]) + self.params["b"]
**return** y_preds
让我们调用这个类,让它对我们的测试数据进行预测…
linreg = LinReg()
linreg.train(X_train, y_train)
linreg.predict(X_test)>>>>
array([ 4.73888182, -90.06369632, 80.39799712, 66.76983607,
-49.97207144, 93.77905208, 34.30778991, -38.2209702 ,
78.03331698, 53.81416352, 102.96993005, 151.71946744,
95.52801857, 104.82707085, 98.0492089 , 45.05150211,
-7.29917923, -78.41675446, -27.14118529, -98.52923336,
170.75840972, -106.22126739, 24.86194847, -21.39127805,
50.24074837])
作为健全性检查,我们将测试预测是否与我们的过程实现相同(因为我们知道这已经与 scikit-learn 实现类似)。
linreg.predict(X_test) == y_preds>>>>
array([ True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True])
现在你知道了!
注意:我们作为例子的问题是简单线性回归问题(一元线性回归),y = WX + b,其中 W 是权重,b 是偏倚。
假设
在决定我们要使用线性回归之前,了解模型对我们的数据所做的假设是很重要的,这样我们就可以执行必要的特征工程来与我们的模型保持一致。
关于特征工程的更多信息,请参阅我以前关于这个主题的文章…
工程数值的技巧
towardsdatascience.com](/feature-engineering-for-numerical-data-e20167ec18)
线性 —线性回归假设特征和我们想要预测的目标之间存在线性关系。我们可以通过绘制与目标变量相关的特征散点图来检查这一点的有效性。
多重共线性——指多元回归模型中两个或两个以上解释变量高度相关的情况(来源:维基百科)。当我们实施线性回归时,我们假设很少或没有多重共线性,因为多重共线性的存在会削弱回归模型的统计能力。要确定数据中是否存在多重共线性,我们可以使用相关矩阵来查看哪些要素高度相关,并移除其中一个高度相关的要素。
同方差 —在统计学中,一个随机变量序列(或向量)如果其所有随机变量都具有相同的有限方差,则该序列(或向量)是同方差的(来源:维基百科)。线性回归假设所有观测值中的噪声是相同的(也可称为误差项或随机扰动),并且不依赖于独立变量的值。我们可以用残差值与预测值的散点图来检验同方差性。我们应该期望分布中没有模式,因为如果有模式,那么数据就是异方差的。
误差分布的正态性 —数据点正态分布在回归线周围。换句话说,我们假设残差遵循正态分布。我们可以使用直方图或 QQ 图来检查这一点——我们可以使用广义线性模型(GLM) 来克服这一点。
自相关——信号与自身延迟副本的相关性,作为延迟的函数——通俗地说,就是观测值之间的相似性,作为它们之间时滞的函数(来源:维基百科)。简而言之,当残差彼此不独立时,就会出现自相关。我们可以使用散点图来直观地检查自相关性,或者使用 Durbin-Watsons 检验来测试残差不是线性自相关的零假设。
赞成的意见
- 易于实施
- 易于解释输出系数
- 不太复杂
骗局
- 对异常值敏感
- 假设自变量和因变量之间呈线性关系
- 假设特征之间相互独立
关于线性回归的更多信息,你可以阅读维基百科页面。
在统计学中,线性回归是一种建模标量响应(或变量)之间关系的线性方法
en.wikipedia.org](https://en.wikipedia.org/wiki/Linear_regression#:~:text=In%20statistics%2C%20linear%20regression%20is,is%20called%20simple%20linear%20regression.)
包裹
线性回归是最简单的机器学习模型之一,也最有可能是你将要学习或者应该学习的第一个模型。它是一个非常有用的分析变量之间关系的工具,但是由于模型的假设很多,现实问题的简化,所以在大多数实际应用中不推荐使用。
感谢您花时间阅读这个故事。如果我错过了什么或者你想让我澄清什么,请在评论中回复。另外,如果你想和我联系,我在 LinkedIn 上是最容易联系到的。
[## Kurtis Pykes -人工智能作家-走向数据科学| LinkedIn
在世界上最大的职业社区 LinkedIn 上查看 Kurtis Pykes 的个人资料。Kurtis 有一个工作列在他们的…
www.linkedin.com](https://www.linkedin.com/in/kurtispykes/)
从零开始的算法:逻辑回归
从零开始的算法
从头开始详述和构建逻辑回归模型
与普遍的看法相反,我在此声明,逻辑回归是而不是一种分类算法(本身是 ) —事实上,逻辑回归实际上是一种回归模型,所以不要对其命名中出现“回归”感到惊讶。回归分析是一套统计过程,用于估计因变量与一个或多个自变量之间的关系(来源:维基百科)。如上所述,逻辑回归不是一种分类算法,它不执行统计分类,因为它只是估计逻辑模型的参数。
逻辑回归是一种统计模型,其最基本的形式是使用逻辑函数来模拟二元因变量,尽管存在许多更复杂的扩展。(来源:维基百科)
允许将逻辑回归用作分类算法的是使用阈值(也可称为截止或决策边界),这反过来会将概率大于阈值的输入分类为一个类别,而将概率低于阈值的输入分类为另一个类别,正如我们在机器学习中通常所做的那样。
参见 这一环节 来看我们如何处理多类分类问题。
现在,让我们把注意力从零开始回到算法系列的目的。
链接到 Github 存储库…
permalink dissolve GitHub 是超过 5000 万开发人员的家园,他们一起工作来托管和审查代码,管理…
github.com](https://github.com/kurtispykes/ml-from-scratch/blob/master/logistic_regression.ipynb)
注 :有很多机器学习框架的代码经过高度优化,这使得从头开始编写机器学习算法在实际设置中成为一项多余的任务。然而,当我们从零开始构建算法时,它有助于我们对模型中正在发生的事情获得更深入的直觉,这可能会在尝试改进我们的模型时带来高回报。
二元分类的线性回归
在《从零开始的算法:线性回归》的最后一集里,我说“它通常是第一次学习机器学习时学习的第一批算法之一,因为它很简单,而且它如何构建到其他算法里,如逻辑回归和神经网络”——你现在会明白我的意思了。
**我们如何从预测连续变量到伯努利变量(即“成功”或“失败”)?**由于响应数据(我们试图预测的数据)是二元的(取值 0 和 1),因此仅由 2 个值组成,我们可以假设我们的响应变量的分布现在来自二项式分布——这需要一个完美的时间来引入由约翰·内尔德和罗伯特·威德伯恩制定的广义线性模型(GLM)。
GLM 模型允许响应变量具有不同于正态分布的误差分布。在我们的情况下,我们现在有一个二项分布,通过使用 GLM 模型,我们可以通过链接函数将线性模型与响应变量相关联,并允许每个测量的方差大小是其预测值的函数,从而推广线性回归(来源:维基百科)。
长话短说,逻辑回归是具有二项式条件响应和 logit 链接的 GLM 的特例。
停止…
在继续之前,我们应该弄清楚一些统计术语(用外行人的术语来说):
- 几率——某事发生与某事未发生的比率。例如,切尔西赢得接下来 4 场比赛的赔率是 1 比 3。发生的事情(切尔西赢得比赛)1 与没有发生的事情(切尔西没有赢得比赛)3 的比值可以写成一个分数,1/3。
- 概率——发生的事情与可能发生的事情的比率。使用上面的例子,正在发生的事情(切尔西获胜)1 与所有可能发生的事情(切尔西获胜和失败)4 的比率也可以写成分数 1/4,这是获胜的概率——因此失败的概率是 1–1/4 = 3/4。
概率的范围在 0 和 1 之间,而赔率并不局限于 0 和 1 之间,而是可以取 0 到无穷大之间的任何值。
我们可以通过将获胜概率的比率(用我们的例子来说是 1/4)除以失败概率的比率(3/4)来得到概率的赔率,从而得到 1/3,即赔率。参见图 1 了解我们如何用数学方法表达它。
图 1:从概率中得出的赔率。
如果切尔西是一支糟糕的足球队(难以想象,我知道),他们获胜的几率会在 0 到 1 之间。然而,由于我们都知道切尔西是世界上最伟大的球队之一(毫无疑问是伦敦最好的球队),因此,切尔西获胜的几率将在 1 到无穷大之间。不对称使得很难比较支持或反对切尔西获胜的几率,所以我们采用几率的对数来使一切对称。
图 1 向我们展示了我们可以用概率计算赔率,既然如此,我们也可以使用图 1 中的公式计算赔率的对数。概率比的对数被称为 logit 函数,它构成了逻辑回归的基础。让我们通过考虑具有给定参数的逻辑模型来更好地理解这一点,并看看如何从数据中估计系数。
注:下面这个例子来源于逻辑回归的例子部分 维基百科 。
考虑一个有两个独立变量( X 1 和 X 2)和一个伯努利响应变量 Y 的模型,我们用 p = P (Y=1)表示。我们假设独立变量和 Y=1 事件的对数概率之间存在线性关系,可以用数学方法表示为:
图 2:独立变量与 Y=1 事件的对数比数之间的线性关系表达式。
通过对对数赔率求幂,我们恢复赔率如下:
图 3:对对数赔率求幂以恢复赔率。
通过简单的代数运算,Y=1 的概率为:
图 4:代数操作
考虑到这一点,图 4 向我们展示了,如果我们有线性模型的参数,我们可以很容易地计算给定观察值的对数优势或 Y= 0 的概率。逻辑回归仍然是一个回归模型,没有与使反应的预测概率二分法的阈值结合使用。
分块算法
- 随机初始化假设函数的参数
- 将逻辑函数应用于线性假设函数
- 计算偏导数( Saket Thavanani 写了一篇很好的文章,题目是 逻辑回归成本函数的导数 )
- 更新参数
- 重复 2-4,重复 n 次迭代(直到成本函数最小化)
- 推理
实现
在本节中,我使用了 3 个 Python 框架:NumPy 用于线性代数,Pandas 用于数据操作,Scikit-Learn 用于机器学习工具。
**import** **numpy** **as** **np**
**import** **pandas** **as** **pd**
**from** **sklearn.metrics** **import** accuracy_score
**from** **sklearn.datasets** **import** load_breast_cancer
**from** **sklearn.linear_model** **import** LogisticRegression
**from** **sklearn.model_selection** **import** train_test_split
首先,我们需要一个数据集。我使用的是经典的二元分类数据集sklearn.datasets.load_breast_cancer
——参见文档。
*# loading the data set*
dataset = load_breast_cancer(as_frame=**True**)
df= pd.DataFrame(data= dataset.data)
df["target"] = dataset.target
df.head()
图 5:上面代码单元的输出。注意:数据帧有 31 列,太大而无法显示,因此出现省略号(仍有一些列看不到)。
接下来,我们将预测变量和响应变量分开,然后创建一个训练和测试集。
*# Seperating to X and Y*
X = df.iloc[:, :-1]
y = df.iloc[:, -1]
*# splitting training and test*
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.75, shuffle=**True**, random_state=24)
我们从头开始构建线性回归所做的大量工作(见下面的链接)可以借用一些微小的变化来使用逻辑回归调整我们的分类模型。
从头开始详述和构建线性回归模型
towardsdatascience.com](/algorithms-from-scratch-linear-regression-c654353d1e7c)
**def** param_init(X):
*"""*
*Initialize parameters*
*__________________*
*Input(s)*
*X: Training data*
*__________________*
*Output(s)*
*params: Dictionary containing coefficients*
*"""*
params = {} *# initialize dictionary*
_, n_features = X.shape *# shape of training data*
*# initializing coefficents to 0*
params["W"] = np.zeros(n_features)
params["b"] = 0
**return** params**def** get_z(X, W, b):
*"""*
*Calculates Linear Function*
*__________________*
*Input(s)*
*X: Training data*
*W: Weight coefficients*
*b: bias coefficients*
*__________________*
*Output(s)*
*z: a Linear function*
*"""*
z = np.dot(X, W) + b
**return** z**def** sigmoid(z):
*"""*
*Logit model*
*_________________*
*Input(s)*
*z: Linear model*
*_________________*
*Output(s)*
*g: Logit function applied to linear model*
*"""*
g = 1 / (1 + np.exp(-z))
**return** g**def** gradient_descent(X, y, params, lr, n_iter):
*"""*
*Gradient descent to minimize cost function*
*__________________*
*Input(s)*
*X: Training data*
*y: Labels*
*params: Dictionary contatining coefficients*
*lr: learning rate*
*__________________*
*Output(s)*
*params: Dictionary containing optimized coefficients*
*"""*
W = params["W"]
b = params["b"]
m = X.shape[0] *# number of training instances*
**for** _ **in** range(n_iter):
*# prediction with random weights*
g = sigmoid(get_z(X, W, b))
*# calculate the loss*
loss = -1/m * np.sum(y * np.log(g)) + (1 - y) * np.log(1-g)
*# partial derivative of weights*
dW = 1/m * np.dot(X.T, (g - y))
db = 1/m * np.sum(g - y)
*# updates to coefficients*
W -= lr * dW
b -= lr * db
params["W"] = W
params["b"] = b
**return** params**def** train(X, y, lr=0.01, n_iter=1000):
*"""*
*Train Linear Regression model with Gradient decent*
*__________________*
*Input(s)*
*X: Training data*
*y: Labels*
*lr: learning rate*
*n_iter: Number of iterations*
*__________________*
*Output(s)*
*params: Dictionary containing optimized coefficients*
*"""*
init_params = param_init(X)
params = gradient_descent(X, y, init_params, lr, n_iter)
**return** params**def** predict(X_test, params):
*"""*
*Train Linear Regression model with Gradient decent*
*__________________*
*Input(s)*
*X: Unseen data*
*params: Dictionary contianing optimized weights from training*
*__________________*
*Output(s)*
*prediction of model*
*"""*
z = np.dot(X_test, params["W"]) + params["b"]
y_pred = sigmoid(z) >= 0.5
**return** y_pred.astype("int")
值得注意的区别是,我们现在将 logit 函数应用于我们的线性模型,根据推断,我们将 logit 模型中大于 0.5 的每个输出都分类为第一类(否则为第 0 类),并且我们使用不同的成本函数来工作于我们的分类模型,因为 MSE 会使我们的损失函数为非凸的-要了解更多关于所使用的成本函数的信息,您一定要阅读 逻辑回归的成本函数的导数 。
params = train(X_train, y_train) *# train model*
y_pred = predict(X_test, params) *# inference*lr = LogisticRegression(C=0.01)
lr.fit(X_train, y_train)
sklearn_y_pred = lr.predict(X_test)print(f"My Implementation: {accuracy_score(y_test, y_pred)}**\n**Sklearn Implementation: {accuracy_score(y_test, sklearn_y_pred)}")>>>> My Implementation: 0.9300699300699301
Sklearn Implementation: 0.9300699300699301
很好,我们获得了与 Scikit-Learn 实现相同的精度。
现在,我们将用面向对象编程来重复这一点,面向对象编程被认为更适合协作。
**class** **LogReg**():
*"""*
*Custom made Logistic Regression class*
*"""*
**def** __init__(self, lr=0.01, n_iter= 1000):
self.lr = lr
self.n_iter = n_iter
self.params = {}
**def** param_init(self, X_train):
*"""*
*Initialize parameters*
*__________________*
*Input(s)*
*X: Training data*
*"""*
_, n_features = self.X.shape *# shape of training data*
*# initializing coefficents to 0*
self.params["W"] = np.zeros(n_features)
self.params["b"] = 0
**return** self
**def** get_z(X, W, b):
*"""*
*Calculates Linear Function*
*__________________*
*Input(s)*
*X: Training data*
*W: Weight coefficients*
*b: bias coefficients*
*__________________*
*Output(s)*
*z: a Linear function*
*"""*
z = np.dot(X, W) + b
**return** z
**def** sigmoid(z):
*"""*
*Logit model*
*_________________*
*Input(s)*
*z: Linear model*
*_________________*
*Output(s)*
*g: Logit function applied to linear model*
*"""*
g = 1 / (1 + np.exp(-z))
**return** g
**def** gradient_descent(self, X_train, y_train):
*"""*
*Gradient descent to minimize cost function*
*__________________*
*Input(s)*
*X: Training data*
*y: Labels*
*params: Dictionary contatining random coefficients*
*alpha: Model learning rate*
*__________________*
*Output(s)*
*params: Dictionary containing optimized coefficients*
*"""*
W = self.params["W"]
b = self.params["b"]
m = X_train.shape[0]
**for** _ **in** range(self.n_iter):
*# prediction with random weights*
g = sigmoid(get_z(X, W, b))
*# calculate the loss*
loss = -1/m * np.sum(y * np.log(g)) + (1 - y) * np.log(1 - g)
*# partial derivative of weights*
dW = 1/m * np.dot(X.T, (g - y))
db = 1/m * np.sum(g - y)
*# updates to coefficients*
W -= self.lr * dW
b -= self.lr * db
self.params["W"] = W
self.params["b"] = b
**return** self
**def** train(self, X_train, y_train):
*"""*
*Train model with Gradient decent*
*__________________*
*Input(s)*
*X: Training data*
*y: Labels*
*alpha: Model learning rate*
*n_iter: Number of iterations*
*__________________*
*Output(s)*
*params: Dictionary containing optimized coefficients*
*"""*
self.params = param_init(X_train)
gradient_descent(X_train, y_train, self.params , self.lr, self.n_iter)
**return** self
**def** predict(self, X_test):
*"""*
*Inference*
*__________________*
*Input(s)*
*X: Unseen data*
*params: Dictionary contianing optimized weights from training*
*__________________*
*Output(s)*
*y_preds: Predictions of model*
*"""*
g = sigmoid(np.dot(X_test, self.params["W"]) + self.params["b"])
**return** g
为了检查我们是否正确地实现了它,我们可以看看预测是否与我们的过程实现相同,因为我们已经知道这大约等于 Scikit-learn 的实现。
logreg = LogReg()
logreg.train(X_train, y_train)
oop_y_pred = logreg.predict(X_test)oop_y_pred == y_preds
这将返回一个对每个值都为真的数组。
假设
- 二进制或序数 —响应变量在二进制逻辑回归中要求为二进制,在序数逻辑回归中要求为序数
- 独立性 —要求观测值相互独立
- 多重共线性-预测变量之间很少或没有多重共线性。
- 线性 —自变量和对数优势的线性
赞成的意见
- 低方差
- 提供概率
- 易于实施
骗局
- 高偏差
包裹
在大多数在线课程中,逻辑回归往往是在线性回归之后教授的内容。虽然通常逻辑回归用于不同领域的回归,但通过将其与阈值相结合,我们能够将其用作非常有用、易于实现的分类器,这证明是在处理分类问题时实现的良好的第一模型。
感谢您花时间阅读这个故事。如果有什么我错过了,说错了,或者你想让我澄清的,请在评论中留下你的回复。另外,如果你想和我联系,我在 LinkedIn 上是最容易联系到的。
[## Kurtis Pykes -人工智能作家-走向数据科学| LinkedIn
在世界上最大的职业社区 LinkedIn 上查看 Kurtis Pykes 的个人资料。Kurtis 有一个工作列在他们的…
www.linkedin.com](https://www.linkedin.com/in/kurtispykes/)
您可以从这里访问从头开始系列的完整算法:
阅读《走向数据科学》中关于算法的文章。分享概念、想法和…
towardsdatascience.com](https://towardsdatascience.com/tagged/algorithms-from-scratch)
从零开始的算法:朴素贝叶斯分类器
从零开始的算法
从头开始详述和构建朴素贝叶斯分类器
介绍
T 朴素贝叶斯分类器是一种 急切学习 算法,属于基于贝叶斯定理的简单概率分类器家族。
虽然贝叶斯定理(简而言之,是一种在没有联合概率的情况下计算条件概率的原则方法)假设每个输入都依赖于所有其他变量,但为了将其用作分类器,我们删除了这一假设,并认为每个变量都是相互独立的,并将这种用于预测建模的贝叶斯定理简化称为朴素贝叶斯分类器。换句话说,朴素贝叶斯假设类中某个预测值的存在与任何其他预测值的存在无关。这是一个非常强有力的假设,因为预测者在现实世界的数据中不发生相互作用的可能性非常小。
顺便说一下,如果你对渴望学习不熟悉,渴望学习是指在系统的训练过程中,系统旨在构造一个通用的、与输入无关的目标函数的学习方法。相反,像K-最近邻 这样的算法,是一个懒惰的学习者,要等到进行查询之后,才能在训练数据之外进行任何泛化。
本质上,朴素贝叶斯(或白痴贝叶斯)赢得了它的名字,因为每个类的计算被简化以使它们的计算易于处理,然而,分类器证明了自己在许多现实世界的情况下是有效的,无论是二元分类还是多类分类,尽管它的设计简单且假设过于简化。
对于本笔记本中使用的完整代码…
permalink dissolve GitHub 是超过 5000 万开发人员的家园,他们一起工作来托管和审查代码,管理…
github.com](https://github.com/kurtispykes/ml-from-scratch/blob/master/naive_bayes.ipynb)
创建模型
如前所述,如果我们要对条件概率分类模型(朴素贝叶斯模型)应用贝叶斯定理,那么我们需要简化计算。
在讨论如何简化模型之前,我将简要介绍边际概率、联合概率和条件概率:
边际概率 —不考虑其他随机变量的事件概率,例如 P(A ),这意味着事件发生的概率。
联合概率 —两个或两个以上同时发生事件的概率,例如 P(A 和 B)或 P(A,B)。
条件概率 —给定一个或多个事件发生的概率,例如 P(A|B)可以表述为给定 B 的概率
关于这些概率的一件很酷的事情是,我们可以用它们来计算彼此。联合概率可以通过使用条件概率来计算,这被称为乘积规则;参见图 1。
图 1:使用条件概率计算联合概率
关于乘积法则的一个有趣的事实是,它是对称的,意味着 P(A,B) = P(B,A)。另一方面,条件概率是不对称的,这意味着 P(A|B)!= P(B|A ),但是我们可以利用联合概率来计算条件概率,见图 2。
图 2:使用联合概率计算条件概率
图 2 有一个问题;计算联合概率通常很困难,所以当我们想要计算条件概率时,我们使用另一种方法。我们使用的另一种方法称为贝叶斯法则或贝叶斯定理,它是通过使用一个条件概率来计算另一个条件来完成的——参见图 3**
图 3:贝叶斯定理——这个等式的逆等式也成立,因此 P(B|A) = P(A|B) * P(B) / P(A)
注:当反向条件概率可用或更容易计算时,我们也可以决定使用替代方法来计算条件概率。
为了给贝叶斯定理中的术语命名,我们必须考虑使用该方程的上下文—参见图 4。
图 4:命名贝叶斯定理的术语
因此,我们可以把贝叶斯定理重述为…
图 5:重述贝叶斯定理
为了将此建模为分类模型,我们这样做…
图 6:用分类模型表示的贝叶斯定理
然而,这个表达式在我们的计算中是一个复杂的原因,因此为了简化它,我们去除了依赖性的假设,并且考虑到每个变量独立,我们简化了我们的分类器。
注意:我们去掉了分母(本例中观察到数据的概率),因为它对于所有计算都是常数。
图 7:朴素贝叶斯分类器
现在你明白了…嗯,不完全是。这个计算是针对每个类标签执行的,但是我们只想知道对于给定的实例最有可能的类。因此,我们必须找到最有可能被选为给定实例的分类的标签;这个决策规则的名字叫做最大后验概率(MAP)——现在你有了朴素贝叶斯分类器。
分块算法
- 将数据按类分段,然后计算每个类中 x 的均值和方差。
- 使用高斯概率密度函数计算概率
- 获取类别概率
- 获得最终预测
实施
我们将使用 iris 数据集,由于该数据集中使用的变量是数值型的,因此我们将构建一个高斯朴素贝叶斯模型。
注:不同的朴素贝叶斯分类器的区别主要在于它们对 P(Xi | y)分布的假设(来源 : Scikit-Learn 朴素贝叶斯)
import numpy as np
import pandas as pdfrom sklearn.datasets import load_iris
from sklearn.naive_bayes import GaussianNB
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split# loading the data
iris = load_iris()
X, y = iris.data, iris.target# spliting data to train and test
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size= 0.2, random_state=1810)
X_train.shape, y_train.shape, X_test.shape, y_test.shape((120, 4), (120,), (30, 4), (30,))# scikit learn implementation
nb = GaussianNB()
nb.fit(X_train, y_train)
sklearn_preds = nb.predict(X_test)print(f"sklearn accuracy:{accuracy_score(y_test, sklearn_preds)}")
print(f"predictions: {sklearn_preds}")sklearn accuracy:1.0
predictions: [0 0 2 2 0 1 0 0 1 1 2 1 2 0 1 2 0 0 0 2 1 2 0 0 0 0 1 1 0 2]
Scikit-Learn 实现在推理上给了我们一个完美的准确度分数,让我们构建自己的模型,看看我们是否可以匹配 Scikit-learn 实现。
我构建了一个效用函数get_params
,这样我们就可以为我们的训练数据获取一些参数。
def get_params(X_train, y_train):
"""
Function to get the unique classes, number of classes and number of features in training data
"""
num_examples, num_features = X_train.shape
num_classes = len(np.unique(y_train))
return num_examples, num_features, num_classes# testing utility function
num_examples, num_features, num_classes = get_params(X_train, y_train)
**print**(num_examples, num_features, num_classes)120 4 3
我们的效用函数工作得很好,所以我们可以进行第一步,按类获取统计数据(特别是均值、方差和先验)。
def get_stats_by_class(X_train, y_train, num_examples=num_examples, num_classes=num_classes):
"""
Get stats of dataset by the class
"""
# dictionaries to store stats
class_mean = {}
class_var = {}
class_prior = {}
# loop through each class and get mean, variance and prior by class
for cls in range(num_classes):
X_cls = X_train[y_train == cls]
class_mean[str(cls)] = np.mean(X_cls, axis=0)
class_var[str(cls)] = np.var(X_cls, axis=0)
class_prior[str(cls)] = X_cls.shape[0] / num_examples
return class_mean, class_var, class_prior# output of function
cm, var, cp = get_stats_by_class(X_train, y_train)
cm, var, cp# output of function
cm, var, cp = get_stats_by_class(X_train, y_train)
print(f"mean: {cm}\n\nvariance: {var}\n\npriors: {cp}")mean: {'0': array([5.06111111, 3.48611111, 1.44722222, 0.25833333]), '1': array([5.90952381, 2.80714286, 4.25238095, 1.33809524]), '2': array([6.61904762, 2.97857143, 5.58571429, 2.02142857])}
variance: {'0': array([0.12570988, 0.15564043, 0.0286034 , 0.01243056]), '1': array([0.26324263, 0.08542517, 0.24582766, 0.04045351]), '2': array([0.43678005, 0.10930272, 0.31884354, 0.0802551 ])}
priors: {'0': 0.3, '1': 0.35, '2': 0.35}
我们将从get_params
获得的num_classes
和num_examples
传递给函数,因为它们需要按类分离数据并按类计算先验。既然我们已经有了足够的信息来计算类别概率——嗯,不完全是,我们正在处理连续数据,一个典型的假设是与每个类别相关的连续值按照高斯分布分布(来源 : 维基百科)。因此,我们建立了一个函数来计算密度函数,它将帮助我们计算高斯分布的概率。
def gaussian_density_function(X, mean, std, num_examples=num_examples, num_features=num_features, eps=1e-6):
num_exambles, num_features = X_train.shape
const = -num_features/2 * np.log(2*np.pi) - 0.5 * np.sum(np.log(std + eps))
probs = 0.5 * np.sum(np.power(X - mean, 2)/(std + eps), 1)
return const - probsgaussian_density_function(X_train, cm[str(0)], var[str(0)])array([-4.34046349e+02, -1.59180054e+02, -1.61095055e+02, 9.25593725e-01,
-2.40503860e+02, -4.94829021e+02, -8.44007497e+01, -1.24647713e+02,
-2.85653665e+00, -5.72257925e+02, -3.88046018e+02, -2.24563508e+02,
2.14664687e+00, -6.59682718e+02, -1.42720100e+02, -4.38322421e+02,
-2.27259034e+02, -2.43243607e+02, -2.60192759e+02, -6.69113243e-01,
-2.12744190e+02, -1.96296373e+00, 5.27718947e-01, -8.37591818e+01,
-3.74910393e+02, -4.12550151e+02, -5.26784003e+02, 2.02972576e+00,
-7.15335962e+02, -4.20276820e+02, 1.96012133e+00, -3.00593481e+02,
-2.47461333e+02, -1.60575712e+02, -2.89201209e+02, -2.92885637e+02,
-3.13408398e+02, -3.58425796e+02, -3.91682377e+00, 1.39469746e+00,
-5.96494272e+02, -2.28962605e+02, -3.30798243e+02, -6.31249585e+02,
-2.13727911e+02, -3.30118570e+02, -1.67525014e+02, -1.76565131e+02,
9.43246044e-01, 1.79792264e+00, -5.80893842e+02, -4.89795508e+02,
-1.52006930e+02, -2.23865257e+02, -3.95841849e+00, -2.96494860e+02,
-9.76659579e+01, -3.45123893e+02, -2.61299515e+02, 7.51925529e-01,
-1.57383774e+02, -1.13127846e+02, 6.89240784e-02, -4.32253752e+02,
-2.25822704e+00, -1.95763452e+02, -2.54997829e-01, -1.66303411e+02,
-2.94088881e+02, -1.47028139e+02, -4.89549541e+02, -4.61090964e+02,
1.22387847e+00, -8.22913900e-02, 9.67128415e-01, -2.30042263e+02,
-2.90035079e+00, -2.36569499e+02, 1.42223431e+00, 9.35599166e-01,
-3.74718213e+02, -2.07417873e+02, -4.19130888e+02, 7.79051525e-01,
1.82103882e+00, -2.77364308e+02, 9.64732218e-01, -7.15058948e+01,
-2.82064236e+02, -1.89898997e+02, 9.79605922e-01, -6.24660543e+02,
1.70258877e+00, -3.17104964e-01, -4.23008651e+02, -1.32107552e+00,
-3.09809542e+02, -4.01988565e+02, -2.55855351e+02, -2.25652042e+02,
1.00821726e+00, -2.24154135e+02, 2.07961315e+00, -3.08858104e+02,
-4.95246865e+02, -4.74107852e+02, -5.24258175e+02, -5.26011925e+02,
-3.43520576e+02, -4.59462733e+02, -1.68243666e+02, 1.06990125e+00,
2.04670066e+00, -8.64641201e-01, -3.89431048e+02, -1.00629804e+02,
1.25321722e+00, -5.07813723e+02, -1.27546482e+02, -4.43687565e+02])
这是一个计算类概率的函数…
def class_probabilities(X, class_mean, class_var, class_prior, num_classes=num_classes):
"""
calculate the probability of each class given the data
"""
num_examples = X.shape[0]
probs = np.zeros((num_examples, num_classes))for cls in range(num_classes):
prior = class_prior[str(cls)]
probs_cls = gaussian_density_function(X, class_mean[str(cls)], class_var[str(cls)])
probs[:, cls] = probs_cls + np.log(prior)
return probs
现在我们需要使用 MAP 进行预测,所以让我们将所有这些步骤放在一个预测函数中,并输出最大概率类。
def predict(X_test, X_train, y_train):
num_examples, num_features, num_classes = get_params(X_test, y_train)
class_mean, class_std, class_prior = get_stats_by_class(X_train, y_train)
probs = class_probabilities(X_test, class_mean, class_std, class_prior)
return np.argmax(probs, 1)my_preds = predict(X_test, X_train, y_train)**print**(f"my predictions accuracy:{accuracy_score(y_test, my_preds)}")
**print**(f"predictions: {my_preds}")my predictions accuracy:1.0
predictions: [0 0 2 2 0 1 0 0 1 1 2 1 2 0 1 2 0 0 0 2 1 2 0 0 0 0 1 1 0 2]
作为健全检查…
sklearn_preds == my_preds**array**([ True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True])
饼干就是这样碎的!
赞成的意见
- 需要少量的训练数据来估计必要的参数
- 与复杂的方法相比,速度极快
骗局
- 众所周知是一个糟糕的估计器(在 Scikit-Learn 框架中,
predict_proba
的输出没有被太认真对待。 - 独立预测者的假设在现实世界中并不成立(大部分时间)
包裹
现在,您已经了解了朴素贝叶斯分类器以及如何使用 Python 从头开始构建一个分类器。是的,该算法有非常过于简化的假设,但它在许多现实世界的应用中仍然非常有效,如果你想要非常快速的预测,值得一试。
让我们继续 LinkedIn 上的对话…
[## Kurtis Pykes -人工智能作家-走向数据科学| LinkedIn
在世界上最大的职业社区 LinkedIn 上查看 Kurtis Pykes 的个人资料。Kurtis 有两个工作列在他们的…
www.linkedin.com](https://www.linkedin.com/in/kurtispykes/)
从零开始的算法:PCA
从零开始的算法
从头开始详述和构建 PCA 算法
图一 : PCA(来源:深度学习。AI Twitter ) —原创者;劳纳克·乔希
介绍
主成分分析(PCA)是卡尔·皮尔逊在 1901 年发明的一种技术,通常用于降低数据的维度,以进行探索性数据分析,也用于构建预测模型时的特征选择——下面将详细介绍特征选择和数据可视化。
选择有用功能的初学者指南
towardsdatascience.com](/getting-started-with-feature-selection-3ecfb4957fd4) [## 有效的数据可视化
构建有效的数据可视化的技巧可以简化为 3 个简单的步骤
towardsdatascience.com](/effective-data-visualization-ef30ae560961)
通常,当我们谈到 PCA 时,我们指的是计算主成分,然后使用这些主成分对数据进行基变换的过程。用人类的话来说,我们会说 PCA 允许我们降低数据的维数。
更多从零开始的算法,可以访问“ 从零开始的算法系列 ”。
共享概念、想法和代码的媒体出版物。
towardsdatascience.com](https://towardsdatascience.com)
创建模型
正如我们之前提到的,PCA 通常用于降维。这是通过将每个数据点投影到前几个分量来实现的,这样我们最终得到的是低维数据,但保留了尽可能多的数据方差。
关于这一点,需要知道两件事:
注:以下列表摘自维基百科(来源 : 维基百科)
- 第一主分量可以等效地定义为使投影数据的方差最大化的方向
i^th
主分量可以取为与第一个i - 1
主分量正交的方向,其最大化投影数据的方差。
但是主要成分是什么呢?很棒的问题!主成分是数据协方差矩阵的特征向量。因此,我们不必深入研究数学元素,观看接下来的两个视频可以更好地理解特征向量、特征值和协方差矩阵。
特征向量 →(线性变换的)是一个非零向量,当对其应用线性变换时,它会改变一个标量因子。
特征值 →特征向量缩放的因子
协方差矩阵 →协方差矩阵是一个方阵,给出给定随机向量的每对元素之间的协方差。任何协方差矩阵都是对称正半定的,其主对角线包含方差(即每个元素与其自身的协方差)(来源 : 维基百科)。
为了执行 PCA,我们应该从数据的协方差矩阵中获得特征向量和特征值。为了做到这一点,我们的意思是标准化我们的数据,然后获得协方差矩阵,最后执行奇异值分解(SVD)。
图 2 :获取一组不相关的特征(图片由作者提供)
接下来,我们必须使用从上一步任务中检索到的特征向量和特征值,将我们的数据投影到一组新的特征。为此,我们取我们的特征和我们的特征向量的前 n 列的点积。如果我们想把数据的维数减少到 2 维,我们将使用特征向量的前两列。
图 3 :将特征数据投影到一组新的特征。 U 表示特征向量,S 表示特征值(图片由作者提供)
我们现在准备实现 PCA,我们将使用它来可视化虹膜数据集。
分块算法
- 获取一组不相关的特征
- 将数据投影到新要素
实施
让我们从使用sklearn
框架对 iris 数据集执行 PCA 开始…
图 4 : Sklearn 实现 PCA
对于我们自己的实施,我们从获取不相关的特征开始,为此我们遵循图 2 中的步骤。
均值归一化数据→获取协方差矩阵→执行奇异值分解
图 5 :步骤 1——获取不相关的特征。我的实现
下一步是将数据投影到一组新的特征上——参见图 3。
图 6 :将数据投影到一组新的特征上
现在你知道了!
总结
PCA 是降低数据维度的一种很好的方法,这些数据可能出于不同的原因而需要,即在构建预测模型时,2 维数据用于数据可视化,或 n 维数据用于特征选择。在这篇文章中,我们还学习了特征向量和特征值,以及它们在将高维数据投射到 n 维中的作用。
这可能是我写过的最难的帖子,我非常希望听到反馈,所以请留下回复或在 LinkedIn 上与我联系,让我们继续对话…
[## Kurtis Pykes -数据科学家-自由职业者,自由职业者| LinkedIn
在世界上最大的职业社区 LinkedIn 上查看 Kurtis Pykes 的个人资料。Kurtis 有 3 个工作列在他们的…
www.linkedin.com](https://www.linkedin.com/in/kurtispykes/)
如果你对写博客感兴趣(尤其是在媒体上),并且想要一些关于开始、扩大你的频道或任何你需要帮助的方面的建议……你会很高兴地知道,我已经创建了一个专门帮助你写博客的 Youtube 频道——订阅并耐心等待本周日的第一个视频!
欣赏您喜爱的视频和音乐,上传原创内容,并在上与朋友、家人和全世界分享这些内容…
www.youtube.com](https://www.youtube.com/channel/UCu6zdBQhvEY5_j-ifHWljYw?view_as=subscriber)
从头开始的算法:支持向量机
从零开始的算法
从头开始详述和构建支持向量机
支持向量机是一种流行的算法,能够执行线性或非线性分类和回归,在深度学习兴起之前,由于令人兴奋的内核技巧,支持向量机是一个热门话题——如果这个术语现在对你没有意义,不要担心。在这篇文章结束时,你会对支持向量机的直觉有很好的理解,在线性支持向量机下发生了什么,以及如何用 Python 实现一个支持向量机。
要从头开始查看完整的 算法 系列,请点击下面的链接。
阅读《走向数据科学》中关于算法的文章。分享概念、想法和…
towardsdatascience.com](https://towardsdatascience.com/tagged/algorithms-from-scratch)
直觉
在分类问题中,SVM 的目标是拟合两个类别之间的最大可能差值。相反,回归任务改变了分类任务的目标,并试图在裕度内适应尽可能多的实例——我们将首先关注分类。
如果我们只关注数据的极端值(位于聚类边缘的观察值),并且我们将阈值定义为两个极端值之间的中点,那么我们就剩下了一个用于分隔两个类的裕度,这通常被称为超平面。当我们应用一个给我们最大余量的阈值(意味着我们严格确保没有实例落在余量内)来进行分类时,这被称为硬余量分类(一些文本称之为最大余量分类)。
当详细说明硬利润分类时,直观地看到发生了什么总是有帮助的,因此图 2 是硬利润分类的一个例子。为此,我们将使用来自 scikit 的虹膜数据集-学习和实用函数plot_svm()
,您可以在下面的 github 链接上获得完整代码。
permalink dissolve GitHub 是超过 5000 万开发人员的家园,他们一起工作来托管和审查代码,管理…
github.com](https://github.com/kurtispykes/ml-from-scratch/blob/master/support_vector_machine.ipynb)
注意:这个故事是使用 python 包
*jupyter_to_medium*
直接从 jupyter 笔记本上写的——要了解关于这个包 的更多信息,请点击这里——github 上提交的版本是初稿,因此你可能会注意到这篇文章的一些改动。
import pandas as pd
import numpy as np
from sklearn.svm import LinearSVC
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
%matplotlib inline# store the data
iris = load_iris()
# convert to DataFrame
df = pd.DataFrame(data=iris.data,
columns= iris.feature_names)
# store mapping of targets and target names
target_dict = dict(zip(set(iris.target), iris.target_names))
# add the target labels and the feature names
df["target"] = iris.target
df["target_names"] = df.target.map(target_dict)
# view the data
df.tail()
图 1:原始数据集
# setting X and y
X = df.query("target_names == 'setosa' or target_names == 'versicolor'").loc[:, "petal length (cm)":"petal width (cm)"]
y = df.query("target_names == 'setosa' or target_names == 'versicolor'").loc[:, "target"]
# fit the model with hard margin (Large C parameter)
svc = LinearSVC(loss="hinge", C=1000)
svc.fit(X, y)
plot_svm()
图 2:SVM 的可视化决策边界
图 2 显示了线性 SVM 如何使用硬边界分类来确保没有实例落入边界内。尽管这对于我们当前的场景来说看起来不错,但我们必须小心考虑执行硬利润分类带来的陷阱:
- 对异常值非常敏感
- 只有当类是线性可分的时候它才起作用
处理异常值和非线性数据
硬边界分类的一种更灵活的替代方法是软边界分类,这是一种很好的解决方案,可以克服上面列出的硬边界分类中的缺陷,主要是解决对异常值的敏感性问题。当我们允许存在一些错误分类时(意味着一些负面观察可能被分类为正面,反之亦然),从阈值到观察的距离被称为 软余量 。在软裕度分类中,我们的目标是在最大化裕度大小和限制裕度中的违规数量(落在裕度中的观察数量)之间实现良好的平衡。
是的,线性 SVM 分类器(硬边界和软边界)非常有效,并且在许多情况下工作得非常好,但是当数据集不是线性可分的时候,就像许多数据集经常出现的情况一样,更好的解决方案是利用支持向量机内核技巧(一旦你理解了内核技巧,你可能会注意到它不是支持向量机独有的)。内核技巧将非线性可分离数据映射到更高维度,然后使用超平面来分离类别。让这个技巧如此令人兴奋的是,将数据映射到更高的维度实际上并没有增加新的功能,但我们仍然得到了相同的结果,就好像我们做了一样。因为我们不需要在数据中加入新的特征,我们的模型计算效率更高,效果也一样好。
你会在下面看到这种现象的一个例子。
术语
- 决策边界:将数据集分成两类的超平面
- 支持向量:观察值位于聚类的边缘(离分离超平面最近)。
- 硬边界:当我们严格规定所有观察值都不在边界内时
- 软边界:当我们允许一些错误分类时。我们试图找到一个平衡点,既保持尽可能大的差值,又限制违规次数(偏差/方差权衡)
from sklearn.datasets import make_moons
from mlxtend.plotting import plot_decision_regions
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC# loading the data
X, y = make_moons(noise=0.3, random_state=0)
# scale features
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# fit the model with polynomial kernel
svc_clf = SVC(kernel="poly", degree=3, C=5, coef0=1)
svc_clf.fit(X_scaled, y)
# plotting the decision regions
plt.figure(figsize=(10, 5))
plot_decision_regions(X_scaled, y, clf=svc_clf)
plt.show()
图 3:应用于非线性数据的内核技巧
注意:我们对这个数据集应用了一个多项式核,但是 RBF 也是一个非常受欢迎的核,应用于许多机器学习问题,并且经常在数据不是线性可分时用作默认核。
创建模型
既然我们已经对 SVM 正在做的事情建立了概念上的理解,让我们来理解在这个模型的引擎盖下正在发生什么。线性 SVM 分类器计算决策函数w.T * x + b
并预测结果为肯定的肯定类别,否则为否定类别。训练线性 SVM 分类器意味着找到使边缘尽可能宽的值w
和b
,同时避免边缘违规(硬边缘分类)或限制它们(软边缘分类)
图 4:线性 SVM 分类器预测
决策函数的斜率等于权重向量的范数,因此为了实现最大可能的裕度,我们希望最小化权重向量的范数。但是,我们有办法实现硬利润分类和软利润分类。
硬边界优化问题如下:
图 5:线性 SVM(硬边界分类器)目标
和软利润:
图 6:线性 SVM(软间隔分类器)目标;请注意,为了实现软余量,我们为每个实例添加了一个松弛变量(zeta ≥ 0),该变量测量每个实例允许违反余量的程度。
履行
注意:对于这个实现,我将进行硬边界分类,但是进一步的工作将包括软边界的 Python 实现和对不同数据集执行的内核技巧,包括基于回归的任务——要获得这些帖子的通知,您可以在 Github 上关注我。
from sklearn.datasets.samples_generator import make_blobs
# generating a dataset
X, y = make_blobs(n_samples=50, n_features=2, centers=2, cluster_std=1.05, random_state=23)def initialize_param(X):
"""
Initializing the weight vector and bias
"""
_, n_features = X.shape
w = np.zeros(n_features)
b = 0
return w, bdef optimization(X, y, learning_rate=0.001, lambd=0.01, n_iters=1000):
"""
finding value of w and b that make the margin as large as possible while
avoiding violations (Hard margin classification)
"""
t = np.where(y <= 0, -1, 1)
w, b = initialize_param(X)
for _ in range(n_iters):
for idx, x_i in enumerate(X):
condition = t[idx] * (np.dot(x_i, w) + b) >= 1
if condition:
w -= learning_rate * (2 * lambd * w)
else:
w -= learning_rate * (2 * lambd * w - np.dot(x_i, t[idx]))
b -= learning_rate * t[idx]
return w, bw, b = gradient_descent(X, y)def predict(X, w, b):
"""
classify examples
"""
decision = np.dot(X, w) + b
return np.sign(decision)# my implementation visualization
visualize_svm()
# convert X to DataFrame to easily copy code
X = pd.DataFrame(data=X,
columns= ["x1", "x2"])
# fit the model with hard margin (Large C parameter)
svc = LinearSVC(loss="hinge", C=1000)
svc.fit(X, y)
# sklearn implementation visualization
plot_svm()
赞成的意见
- 非常好的线性分类器,因为它找到了最佳决策边界(在硬边界分类意义上)
- 易于转换成非线性模型
骗局
- 不适合大型数据集
包裹
SVM 是一种很难编码的算法,它很好地提醒了我们为什么应该感谢机器学习库,让我们可以用几行代码实现它们。在这篇文章中,我没有深入到支持向量机的全部细节,仍然有相当多的空白,你可能想要阅读,例如计算支持向量机和经验风险最小化。
此外,可能值得观看吴恩达关于支持向量机的讲座— 点击此处
感谢您花时间通读这个故事(因为它被称为媒体)。现在,您对支持向量机、SVM 下发生的事情以及如何用 Python 编写硬边界分类器有了很好的概念性理解。如果你想和我联系,我在 LinkedIn 上很容易找到。
[## Kurtis Pykes -人工智能作家-走向数据科学| LinkedIn
在世界上最大的职业社区 LinkedIn 上查看 Kurtis Pykes 的个人资料。Kurtis 有一个工作列在他们的…
www.linkedin.com](https://www.linkedin.com/in/kurtispykes/)
算法、迭代和洗衣:我们能把它们联系起来吗?
帮助您更好地理解代码的简单解释
简·安东宁·科拉尔在 Unsplash 上拍摄的照片
我坚信,如果你能理解洗衣,你就能理解算法和多级迭代。
现在,我必须指出,洗衣服是我每天都要做的事情之一。我和它邪恶的表亲,熨衣服,过得特别艰难。对我来说,这几乎是不可能的,这就是为什么我很高兴我们正在进入北半球的毛衣天气(当你穿着一件不那么皱的毛衣时,没有人会注意到一件皱的衬衫)。
在寻找洗衣艺术和科学的动机时,我开始意识到它的工作有点像算法和迭代,更好的是,如果我们能让自己像对待那些数据科学概念一样看待洗衣,也许我们可以做得更好。因此,我尝试用一些常识来解决一个非常常见的数据科学问题。
披露:在我们开始之前,请记住,我在这里的目标不是给出算法和迭代的完整技术解释。如果那是你要找的,这里不适合你。有大量的文章/书籍/视频可以提供这方面的信息,所以我恳请你不要在这里浪费时间。然而,如果你对数据科学和日常生活之间意想不到的联系感兴趣,请继续阅读。
好了,现在我们已经解决了法律上的问题,算法就是“用来解决问题的一组指令”。另一方面,迭代是“一次又一次做某事的过程”。好的,这很好,但问题是这些只是教科书上的定义,(根据定义)这些在现实生活中几乎没有用处。它们往往只是抽象的概念,大学课程喜欢把它们扔给你,这样他们就可以收取一大笔钱。所以让我们转而去洗衣服吧。
我想让你想象你已经很久没有洗衣服了,拖延症已经到了极限。你必须面对严酷的现实,你不能再用同一件衬衫出门,你必须照顾自己的肮脏,所以你把所有的衬衫扔在一个巨大的篮子里。我还想让你想象一下,你有一台神奇的电脑为你洗衣服(我愿意为此花大价钱),你用它来处理你危险堆积的脏衬衫。
第一个例子
让我们首先清楚地定义我们在这里有什么。假设你只有绿色、蓝色和黄色的衬衫:
作者图片
而且你想洗、干、烫它们,所以你的神奇洗衣电脑的基本指令是:
作者图片
很直接,是吧?但问题是,你想让你的洗衣过程井井有条,所以你想按颜色洗、干、熨衬衫。从概念上讲,这是你会指示你的神奇计算机做的事情:
作者图片
你就完了。你已经成功地按颜色洗完了所有的衬衫。你所做的就是在三个独立的迭代中执行指令,每种颜色一次。
第二个例子
好的,现在我想让你认为你也有有袖和无袖的衬衫:
作者图片
坚持我们在前面的例子中所说的,这些衬衫可以是绿色,蓝色或黄色。还是那句话,作为一个非常有条理的洗衣人,你想按颜色洗你的衬衫,但你也想先洗袖子再洗无袖的。这些是你的神奇电脑的说明:
作者图片
现在,请注意,我们只洗了我们的绿色衬衫(有袖和无袖),但我们还没有接触其他颜色。换句话说,当我们迭代套筒变量时,我们保持圆“固定”在绿色正方形上。为了完成这项工作,我们对蓝衫重复这个过程:
作者图片
再次注意,当我们迭代 sleeve 变量时,圆圈是如何“固定”在蓝色方块上的。最后,我们以完全相同的方式处理黄色衬衫:
作者图片
仅此而已。基本上,您以下面的方式进行了两个不同“级别”的迭代:
- 您在遍历底部 iterable 的对象时,在顶部 iterable 的第一个对象(绿色)上固定了圆圈,并在每个对象上执行指令。
- 然后,您只需切换到顶部 iterable 的第二个元素(蓝色),并遍历底部元素中的对象,再次执行指令。
- 最后,重复相同的过程,但是这次将圆固定在顶部 iterable 的第三个对象(黄色)上。
就这样,你洗完了衣服。
第三个例子
正因为迭代和算法如此有趣,让我们假设你也有带纽扣和不带纽扣的衬衫,这可以是三种颜色中的任何一种,可以有袖子也可以没有袖子(就我个人而言,我从未见过带纽扣的无袖衬衫,但嘿,这里没有判断)。
那么,让我们看看这次的指令是什么样的:
作者图片
如果你一直跟着这里,你会意识到我们只关心我们的绿衬衫。所以我们对蓝色的重复这个过程:
作者图片
最后是黄色的:
作者图片
修复圆圈的逻辑同样适用于此,只是我们现在讨论的是三个级别,而不是两个。实际上,这是这篇文章的要点,所以让我们扩展一下,称之为迭代和算法的‘洗衣原则’。
迭代和算法的“洗衣原则”
里卡多·戈麦斯·安吉尔在 Unsplash 上的照片
那么,为什么你在过去的几分钟里一直在看一篇关于洗衣的文章呢?
事实证明,上面的例子有助于使迭代和算法更容易理解,因为它们揭示了一个简单但非常有用的想法,我们从现在开始称之为洗衣原理。
正如我们在一开始所看到的,算法只是一组指令,它们被传递给计算机来解决一个特定的问题。但是这些指令的执行顺序使得算法如此有用。正如我们在上面看到的,指令是按照特定的顺序执行的,这允许以一种结构化的有效方式处理问题,这就是迭代思维发挥作用的地方。
洗衣原则:“自下而上”每天都胜过“自上而下”。
这里的主要思想是,你必须总是自下而上地考虑多级迭代*。让我们从三个层次来看这个问题,就像我们的洗衣例子一样。除了最后一层,所有的层都固定在它们的第一个对象上,最后一层是你将按照你的指令移动的那一层。让我们称这个过程为:***
作者图片
一旦最后一层上的所有对象都用完了,您就可以激活中间层上的迭代,并对那里的每个对象重复过程 A。我们将用字母 B 来命名这个过程,并且记住这是一个复合过程。换句话说,流程 B 包括:
- 对最后一级(进程 A)中的所有对象执行指令,中间一级有一个固定对象。
- 移动到中间层的下一个对象并重复过程 a。
- 这样做,直到中间级别的所有对象都用完为止。
作者图片
一旦中间层的所有对象都用完了,就激活顶层的迭代,并对那里的每个对象重复复合过程 B:
作者图片
如果你有更多的层次,那么,你将永远继续下去(或者直到你的迭代层次用完)。
值得注意的是,这些例子非常简单,目的是清楚地传达想法。和往常一样,现实生活没有那么温和,所以当你试图理解别人的代码时,你肯定仍然会时不时地抓狂。例如,一些代码片段将只在特定的迭代级别上执行指令(与我们的示例相反,在我们的示例中,指令总是在底层执行),并且一些迭代可能相当长。然而,原则是相同的。
衣物包装
我们在这里学到了什么?嗯,几件事:
- 洗衣服很烂,但最终还是要洗(冬天熨烫更容易躲闪)。
- 多级迭代从下往上看更容易理解(洗衣原理)。
- 现实生活中的代码比我们有时希望的要复杂得多,但是对迭代如何工作有一个清晰的想法会让你脚踏实地。
所以下次当你试图理解 Kaggle 上的一些密集代码时,你应该庆幸至少你没有在洗衣服。
外星人、费米悖论和黑暗森林理论:博弈论观点
银河系。来源:亚历克·法瓦利,转自 Unsplash (CC0)。
德雷克方程
20 世纪 60 年代,时任康奈尔大学天文学教授的弗兰克·德雷克提出了 T4·德雷克方程。这是对银河系中智慧文明数量的概率估计。正如他所定义的,智慧文明是一种先进到足以与其他地外文明交流的文明。
德雷克的方程式不是试图精确量化智慧文明,而是唤起人类对外星人存在的好奇心。如果 60 年后你还在读这篇文章,那么德雷克已经成功了。
在这篇文章中,我们讨论了人类寻找外星人的尝试。我们评论费米悖论,这是宇宙的浩瀚和缺乏任何外星生命证据之间的明显矛盾。然后,我们解释了取自我与人合著的调和这一悖论的一篇课堂论文的博弈论模型,以及我们是否应该尝试接触外星生命。所以,系好安全带,准备在社会宇宙的海洋中漫游吧。
我们生活在一个巨大的宇宙中,那里的星星比地球上的沙粒还要多。许多恒星都有行星围绕它们旋转。甚至还有一份由阿雷西博的波多黎各大学保存的潜在宜居系外行星清单。德雷克方程表明,这种外星人存在的可能性很高,不容忽视。排除它们已经在这里的可能,我们必须问自己为什么在地球上没有发现它们。我们当然尽力了。
在 20 世纪 70 年代,我们试图越来越多地了解这些文明。1972 年,我们向木星发射了一艘宇宙飞船——先驱者 10 号。当你阅读这篇文章时,这艘宇宙飞船正朝着金牛座前进,在最终离开我们的太阳系之前,终于给了外星人一个捕捉它的机会!如果它们存在,并且它们确实感染了,我们已经发送了一个铭牌(如下),描述我们的长相和我们生活的世界的象征。
该牌匾在左上角有一个中性氢的超精细跃迁,一个男人和一个女人的形象,太阳和银河系的地标,底部是太阳系,以及人类身后的飞船剪影。来源:维基媒体常用。
1974 年,我们发送了阿雷西博信息,这是由著名的科学传播者卡尔·萨根和弗兰克·德雷克创造的星际调频无线电信息。这是技术成就的展示,而不是接触外星人,但这并没有给我们,阴谋论者,足够的数据来工作。
阿雷西博信息描述了当时的科学技术:白色的是数字 1 到 10。紫色表示组成 DNA 的元素的原子序数:氢、碳、氮、氧和磷。绿色、白色和蓝色的 DNA 结构和分子式。红色的是人类的图形,白色的是人类的数量。黄色是太阳系的图形,表示信息来自哪个行星。紫色的是阿雷西博射电望远镜和发射天线盘的照片。来源:维基媒体常用。
这种星际调频无线电信息包含了许多假设。FM 代表频率调制,对于外星人来说,这种技术可能太先进(或者不够先进)。谁知道呢?无线电信号也随着进入宇宙而衰减,但是我们不能排除外星人拥有敏感的技术来发现每一个微弱的信号,对吗?
卡尔·萨根是天文学家、宇宙学家和科学传播者。来源:维基媒体常用。
那绝不是我们最后一次试图联系外星人并泄露我们所有的秘密!1977 年,卡尔·萨根编辑了自然的声音和图像,以描绘地球上生命和文化的多样性,并将其载入旅行者号宇宙飞船的黄金唱片。截至 2004 年,旅行者 1 号已经离开了我们的太阳系,进入了星际空间,即恒星之间的空间,终于给了外星人一个了解我们的机会。美国宇航局于 2015 年 7 月将该唱片的音频内容上传至 SoundCloud。是时候了!
这些图像包括植物、昆虫、动物、人类和风景。他们展示了文化的各个部分,如博物馆、机场、比赛和交通。其他图像显示了像太阳系和 DNA 这样的科学发现。这些声音包括用 55 种语言表达的问候和从巴赫、莫扎特到贝多芬和斯特拉文斯基的精选音乐。自然声音包括海浪声、风声、雷声和黑猩猩的声音。这张唱片还包含了莫尔斯电码的励志信息。来源:维基媒体常用。
在这一点上,你可能认为这是一种单方面的关系;我们一直在联系,但是没有任何回复。但是在 1977 年夏天,这一切都要改变了。俄亥俄州立大学大耳射电望远镜的科学家们收到了“哇!来自人马座方向的信号。这个信号的特别之处在于它是一个强有力的信号。好像我们注定要得到这个。也许外星人用他们的语言打招呼?
费米悖论
恩利克·费密,核时代的建筑师,1938 年诺贝尔物理学奖得主,涉足宇宙学。在一篇关于宇宙辐射起源的论文中,费米引入了假设的外星生命概率和我们没有收到任何联系的事实之间的矛盾。这个矛盾后来被称为费米悖论。
恩利克·费密是一位天才物理学家,在统计力学、量子理论、核物理学和粒子物理学方面都有重大贡献。来源:维基媒体常用。
既然我们已经介绍了德雷克方程、费米悖论和我们与外星人交流的尝试,让我们探索一下为什么同样聪明的文明避免了交流。
慈欣的前提
尽管对费米悖论有大量的假设解释,我们还是讨论了黑暗森林理论,并将其建模为一个具有不完全信息的序列博弈。黑暗森林理论是由中国科幻作家刘在他的三部曲《地球往事追忆》中描述的
“宇宙是一片黑暗的森林。每个文明都是一个武装的猎人,像幽灵一样在树林中潜行,轻轻地推开阻挡道路的树枝,试图无声无息地行走。连呼吸都是用心做的。猎人必须小心,因为森林里到处都是像他一样的隐形猎人。如果他找到了另一种生活——另一个猎人、天使或恶魔,一个蹒跚老人的娇弱婴儿,一个仙女或半神——他只能做一件事:开火并消灭他们。”
黑暗森林理论指出,我们的星系确实包含德雷克方程中描述的丰富的文明。这些文明仍然有意放弃与其他文明的交流,因为害怕其他文明会摧毁它们。该理论还指出,没有实践这种谨慎的文明已经在这种情况下被摧毁了。
位于加州山景城的搜寻地外文明研究所(SETI) ,以前是一个政府机构,现在是一个非营利组织,认为这个理论并非不可信。SETI 社区的官方政策是只收集信息,不回应任何信号或外星智慧的证据,因为担心这可能是地球生命的终结。
在这里,我们用基于非正式激励的推理来验证慈欣的结论,从两个公理开始:
- 任何特定文明的目标都是生存。
- 文明不断发展壮大,但宇宙中的资源是有限的。
鉴于这些公理,以及宇宙的物理性质,即恒星之间的距离非常遥远,文明之间的交流最初会以几十年到几百年的极慢速度进行,因为光速限制了我们。慈欣描述了任何两种文明之间产生的“怀疑链”,因为它们无法自信地评估对方的真实意图或潜在威胁。当一个文明已经收集了足够的信息来考虑另一个不可协商的时候,另一个文明可能已经开始摧毁他们了。
此外,由于一个文明可能会经历指数级和不可预测的技术进步速度,让技术不太先进的文明——因此威胁较小的文明独自存在不一定是安全的选择。即使一个文明的技术进步从未超过另一个文明,它也可以向其他文明传播关于该文明的信息,而这些文明本身可能在技术上更先进,并决定摧毁它。
黑暗森林理论的博弈论解释
我们用两个场景来解释黑暗森林理论,然后我们进一步推广它们,得到一个忠实于黑暗森林理论的博弈论模型。
第一个场景
两个不同星球上的两个文明已经知道彼此的存在。他们都有足够的技术来摧毁对方,这样做可以让他们获得额外的资源。
从数学上讲,被摧毁的收益是-inf,什么都不做的收益是零。然而,摧毁另一个文明的回报是某个数字θ,其中θ> 0,因为宇宙中的一些有限资源现在已经变得可用。这些新释放的资源允许毁灭者使用它们来扩张,服务于慈欣的第二公理。
因此,第一个场景是一个扩展形式的游戏,有两个回合和以下属性:
- 有两种文明(C1 和 C2)相互了解。
- 首先轮到 C1,然后轮到 C2。
- 每个文明都有同样的两种可能的行动:毁灭(对方文明)或者什么都不做。
很明显,C1 的优势策略和子博弈完美是摧毁 C2。通过选择破坏行动,C1 确保了收益θ>0。如果 C1 选择“什么都不做”,C1 将任由 C2 摆布。使用逆向归纳法,“毁灭”是 C1 唯一安全的选择。
推论:如果一个文明能够毁灭另一个文明,它就会。
第二种场景
一个文明可以向其他文明广播自己的存在。第二个场景是一个扩展形式的游戏,有两个回合,具有以下属性:
- 有两种文明(C1 和 C2)彼此并不了解。
- 首先轮到 C1,然后轮到 C2。
- 每个文明都有同样的三种可能的行动:
- 摧毁一个文明:这个行动只能针对那些知道他们存在的文明。
- 广播:让其他文明知道它的存在。
- 什么都不做。
请注意,C2 不能摧毁一个它没有听说过的文明。
C1 什么都不做是最优策略和子博弈。广播再次将 C1 置于 C2 的控制之下。通过逆向归纳,什么都不做是 C1 唯一安全的选择。
推论:一个文明永远不会与一个能毁灭它的文明分享关于它存在的信息。
黑暗森林理论
黑暗森林理论建立在前面的场景之上,并有更多的概括:
- 这些游戏会随着时间无限重复。
- 文明很多(两个以上)。
- 随着时间的推移,技术在某种程度上是随机增长的。
在重复的游戏中,文明 A 不能让文明 B 活下来,仅仅是因为文明 B 在未来的一个回合中,如果它的科技水平提高了,就可以消灭文明 A。这与第一种情况密切相关。
文明也可以向更强大的文明广播其他文明的存在信息,威胁它们被启示的任何其他文明摧毁启示的文明。这使得任何文明都没有动力与任何其他文明分享其存在的知识,无论它在技术进步方面更弱还是更强。这与第二种情况密切相关。
很明显,摧毁任何已知的文明,并且因为害怕被更强大的文明甚至是更弱小的文明摧毁而不分享存在信息,是帕累托最优的,甚至是纳什均衡的。我们也可以更进一步说,共享它们存在的文明被毁灭了。
在广阔的宇宙中不交朋友听起来可能有点沮丧,有点反社会。然而,我们对其他星球和系统知之甚少,在缺乏共同语言和理解的情况下,在怀疑链的存在下,保持沉默或面临毁灭是有道理的!
这篇文章最初发表在 ProjectNash 上。