杰克真的会死在泰坦尼克号上吗?
机器学习如何回答这个问题
逻辑回归和朴素贝叶斯的演练。
图像来源
那是 1912 年,强大的泰坦尼克号开始了它的处女航。杰克,一个“ *20 岁”“三等”“男”*乘客,赢了一手扑克和他去自由之地的票。在 4 月 14 日的最后一个小时,泰坦尼克号撞上了冰山,它的命运已经注定。杰克能在这场灾难中幸存下来吗?
(是的,我知道他在电影里死了,但如果他是真人,他会活下来吗?)
这是一个二元分类问题,因为我们试图预测两种结果之一:生存或不生存。有许多分类算法,根据数据的不同,有些算法比其他算法效果更好。
我准备训练两个不同的机器学习模型来回答这个问题,对这些不同的算法做一个深入的比较。
我正在使用来自 Kaggle 的泰坦尼克号数据集。
https://www.kaggle.com/c/titanic/data
这个数据集有很多关于乘客的信息:姓名、年龄、性别、舱位等级(一等、二等或三等)、票价、车上兄弟姐妹的数量等等。
我们应该选择这些特征中的哪一个来预测杰克的命运?
特性选择的艺术和科学应该有自己的文章。现在,让我们应用一些推理。性别和年龄可能很重要(记得在电影中他们就像“妇女和儿童优先”)。乘客等级可能也很重要。让我们挑选这三个特征。
我将使用的第一个算法是逻辑回归。
算法 1:逻辑回归
逻辑回归预测一种结果相对于另一种结果的可能性。在这种情况下,我们使用模型来预测杰克活下来的概率。因为模型计算概率,所以模型的输出总是在 0 和 1 之间。
**在 2D,模型是最适合数据集的逻辑曲线。**在下图中,每个蓝点都是乘客,x 轴是年龄,y 轴是他们是否幸存。1 表示存活,0 表示未存活。模型是红色曲线。
作者图片
有几种方法可以找到最佳拟合的函数。梯度下降法是其中之一,牛顿法是另一种。要深入了解实现,请阅读本文。
给定 x 轴上的一个新输入点(比如年龄= 39),我们看看曲线落在 y 轴上的位置,看看存活的概率是多少。
注意:该图并不代表真实的数据集,它仅用于说明目的。
在 3D+中,模型是最适合数据集的超平面。
我们来做一些预测吧!
在训练模型之前,我们需要先进行一些数据处理。
- 将数据分为训练集和测试集。训练集用于训练模型,测试集用于测试模型的准确性。
- 通过一键编码将分类变量转换成二进制格式。
分类变量是具有两个或更多类别的变量,这些类别没有内在的顺序。本例中的性别是一个分类变量,有两个类别(男性和女性)。
我们有两个分类变量(性别和阶级)。我们不能用这些变量原始形式的值来进行训练。换句话说,我们不能将格式[male, 3rd class]
传入训练模型。我们必须使用一键编码来转换它们。
通过一键编码,变量的每个类别(如性别变量的男性和女性)成为输入向量中自己的二进制列。如果乘客属于该类别,则该列的值为 1,否则为 0。总的来说,我们将得到 6 列。1 代表年龄,2 代表性别,3 代表阶级。
输入向量的格式是:
[age, female?, male?, 1st class?, 2nd class?, 3rd class?]
年龄只是一个数字。第二列female?
女性为 1,男性为 0。第三列male?
为 1 表示男性,为 0 表示女性,以此类推。
杰克的数据点[20, male, 3rd class]
变成了[20, 0, 1, 0, 0, 1]
。
为了训练我的模型,我使用了一个名为 SciKit learn 的库。SciKit learn 是一个很棒的机器学习工具,提供了很多学习算法。
import pandas as pd
import numpy as np
import math
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import OneHotEncoder
from sklearn.model_selection import train_test_split# read the data from csv file
data = pd.read_csv('train.csv').T.to_dict()X_categorical = []
X_age = []
y = []for idx in data:
info = data[idx]
sex = info['Sex']
p_class = info['Pclass']
survived = info['Survived']
age = info['Age']
# don't use data if age is absent
if not math.isnan(age):
X_categorical.append([sex, p_class])
X_age.append([age])
y.append(survived)# one hot encoding to transform the categorical data:
enc = OneHotEncoder()
enc.fit(X_categorical)
features = enc.transform(X_categorical).toarray()# Combine the age vector with the transformed matrix
X = np.hstack((X_age, features))# split data into train and test set
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.30, random_state=40)# logistic regression to fit the model
clf = LogisticRegression().fit(X_train, y_train)# Print out the prediction
print(clf.predict([[20,0,1,0,0,1]]))
print(clf.predict_proba([[20,0,1,0,0,1]]))
要查看更漂亮的原始代码,请在 Github 上下载我的 Jupyter 笔记本。
杰克会活下来吗?很可能不是。这个模型预测存活的概率是 0.1078528,因此死亡的概率是 0.8921472。
现在让我们看看另一种学习算法,朴素贝叶斯。
算法 2:朴素贝叶斯
朴素贝叶斯是一个概率公式。贝叶斯定理求给定 b 的概率。
在我们的例子中,我们需要给定男性、20 岁和三等兵的生存概率。
数学表示为:
P(survive)
是幸存的乘客数除以乘客总数。
根据链式法则,
查看这篇文章,我在这里解释了如何更详细地计算提名者和分母。
SciKit Learn 也有朴素贝叶斯。训练数据X_train
和标签y_train
与上面的逻辑回归示例相同。拟合模型后,我们用它来预测杰克的命运。还是那句话,Jack 一热编码后的数据点是[20,0,1,0,0,1]。
from sklearn.naive_bayes import MultinomialNBnb = MultinomialNB()
nb.fit(X_train, y_train)print(nb.predict([[20,0,1,0,0,1]]))
print(nb.predict_proba([[20,0,1,0,0,1]]))
杰克会活下来吗?可能没有。该模型预测存活概率为 0.10072895,因此死亡概率为 0.89927105。
我的结论是,现实地说,杰克确实已经死了。
那么我们的模型有多可靠呢?
评估模型性能的指标有很多。我们来看其中的两个:准确率和 F1 成绩。
准确性就是正确预测的百分比。
F1 得分是“精确度和召回率之间的调和平均值”。
说什么?
我们来分解一下。
调和平均值两个数(精度和召回)之间的关系是:
精度是模型预测的所有存活数中正确预测的存活数。
Recall 是数据集中所有实际存活数据中正确预测存活数据的数量。
F1 值越高,模型越稳健。
注意:这些指标确实有警告,当数据平衡时(即在训练样本中存活和死亡的数量大致相等),它们工作得很好。在 Kaggle 的数据中,有 59%的人死亡,所以分布相对均匀。
我们可以使用 SciKit learn 和测试数据集来获得准确性和 F1 分数。
from sklearn.metrics import f1_score
from sklearn.metrics import accuracy_scorey_pred_logistic_reg = clf.predict(X_test)
y_pred_naive_bayes = nb.predict(X_test)print(f'logistic regression accuracy: {accuracy_score(y_test, y_pred_logistic_reg)}')
print(f'logistic regression f1 score: {f1_score(y_test, y_pred_logistic_reg)}')
print(f'naive bayes accuracy: {accuracy_score(y_test, y_pred_naive_bayes)}')
print(f'naive bayes f1 score: {f1_score(y_test, y_pred_naive_bayes)}')
这些指标是:
逻辑回归得分高于朴素贝叶斯。
我们能通过不同的特征使模型更精确吗?如果我们包括票价或机上兄弟姐妹的数量呢?这个数据有没有比 Logistic 回归更好的算法?答案很可能是肯定的。我将在以后的文章中跟进这些开放性的问题。
现在,你会在泰坦尼克号上幸存吗?请继续关注我的网络应用程序,在那里您可以输入您的信息,并获得您的生存概率!
您可以从我的 Github repo 中获取这些示例的代码。
感谢您的阅读。你想让我谈什么话题吗?在下面回复或者在 Twitter 或者 LinkedIn 上给我发消息。
你会相信一种算法来选择你的下一个度假目的地吗?
我们有时会想:如果我们让一个人工智能来策划这份简讯,它会和 TDS 的 100%人类团队一样吗?它会依赖于观点、掌声和社交分享吗,或者它会以某种方式检测一篇文章不太有效的品质——作者的声音、原创性或清晰度? Carolina Bento 在她的关于决策树算法的精彩解释中提出了类似的问题。以选择度假目的地的过程为例,她展示了这样一个系统将如何工作,以及它将面临的限制。
照片由 Ricardo Gomez Angel 在 Unsplash 拍摄
许多专家认为,理解一个模型为什么会产生某种结果比满足于它的输出更重要。在某些方面,一个人为的决定是一样的;幸运的是,通过我们的每周精选,解释我们的选择非常容易。罗伯特·兰格的每月深度学习研究论文综述是 TDS 上的常年最爱,读者不断涌向它,因为它不仅列出并总结了该领域的重要发展,还添加了围绕它们的背景和分析。在文章的另一端, Elena Etter 深入探讨了一个很少讨论但至关重要的话题:融入数据可视化的各层主观性,以及它们如何影响媒体的所谓中立性和透明性。
正如你现在可能知道的,我们对执行良好的教程和解释者情有独钟。当作者成功地将一个复杂的话题引入一个吸引人的帖子,激励其他人学习并采取行动时,这总是一种享受。本周,我们特别欣赏了 CJ Sullivan 的动手演示:它专注于注入在 Neo4j 中创建的图形嵌入,并用 Streamlit 仪表板可视化它们。 Pierre Blanchart 转向模型可解释性,并展示了我们如何在 XGBoost 这样的树集合模型中使用反事实解释方法。从理论到实践,博尔哈·维拉斯科(和合著者)向我们介绍了双机器学习的新兴方法,并解释了它在因果推理环境中的应用。对于任何对计算智能越来越好奇的人来说,布兰登·摩根刚刚启动了一个令人兴奋的新项目:一个关于进化计算的完整课程。(如果你已经看过布兰登的介绍,单位一和二已经有了!)
我们对坚实、实用的指南的欣赏,只能与我们在了解一些不经常出现在我们视野中的问题和对话时的喜悦相匹配。TDS 播客正是这种讨论的场所,Jeremie Harris最近与 Jeffrey Ding 的关于中国蓬勃发展的人工智能生态系统的一集也不例外。丹尼尔·安杰洛夫提出了一个发人深省的问题给在工业领域工作的人工智能从业者:“你怎么知道你开发的系统足够可靠,可以在现实世界中部署?”他继续探索软件开发的测试实践,并检验它们在机器学习中是否同样有用。最后,我们在过去的一周主持了一场关于 TDS 的热烈辩论,帖子权衡了数学技能对数据科学家的重要性。我们留给你 Sarem Seitz 的慷慨激昂的案例,他们称之为“ML 中最不受重视的技能”,以及为什么学习一个职业的理论基础与一个人发布好的、干净的代码的能力一样重要。
感谢您接受我们的阅读推荐——我们希望您和我们一样喜欢它们。并且,一如既往地感谢你们让我们的工作成为可能。
直到下一个变量,
TDS 编辑器
我们策划主题的最新内容:
入门
- 提高你作为数据科学家影响力的 10 个策略作者丹尼斯·艾勒斯
- 分位数是理解概率分布的关键
- 数据科学如何掌握熊猫作者 Chanin Nantasenamat
实践教程
- 语义搜索:由詹姆斯·布里格斯测量从 Jaccard 到 BERT 的意义
- 结构方程建模由 Joos Korstanje
- 用这三个工具加速你的命令行导航由 Khuyen Tran
深潜
思想与理论
- 通过代理正常化激活消除 CNN 中的批次依赖性Antoine Labatie
- 节点件:由迈克尔·高尔金标记知识图
- 机器学习中的多任务学习由 Devin Soni
争论我们的大脑:我们能使用数据科学来加强直觉吗?
使用数据科学技术在日常生活中做出直觉决策
paweczerwi ski 在 Unsplash 上的照片
什么是直觉?
直觉的力量
韦氏词典将直觉定义为“一种天生的能力或力量,无需任何证明或证据就能知道某事 : 一种引导一个人以某种方式行动而不完全理解原因的感觉。”
有人称之为第六感。其他人将它与“直觉”互换使用一个人日常生活中直觉的例子可能是这样的:
登上飞机,强烈感觉认识一个人,他的行李在同一个箱子里。
尽管没有不良行为的证据,但对某人的真实意图感到怀疑。
考虑给朋友打电话,然后接到他们的电话。
我们生活中的许多直觉时刻是无法解释的。然而,这是我们大多数人一生中至少经历过一次的事情。我自己在生活中也经历过这样的情况,直觉让我有了发现,否则我是不可能有所发现的。而每次出现这种情况,我都会忍不住想知道自己直觉的来源。更重要的是,我想知道是否有办法加强我的直觉,这样我就可以在需要的时候依靠它。
如何激活直觉
当你在网上搜索“如何激活直觉”时,会弹出无数的博客和文章。从商业杂志到关系博客,关于如何激活第六感的文献数不胜数。通读许多博客,可以发现一些关键的共性:
- 沉思
- 在大自然中度过时光
- 多感受,少思考
这些都相当模糊,其中一些是不可测量的。在什么时候你会感觉更多,思考更少?
但是随着我继续研究,我发现了其他一些似乎更容易实现和衡量的常见实践:
与上面的第一组方法相比,这些方法看起来非常不同。虽然它们仍然是旨在激活你的直觉的方法,但从过去学习和记日记等概念是数据收集的形式,这是数据科学领域的第一步,也是最关键的一步。
真的是直觉吗?直觉和数据科学之间的细微差别。
当一个拳击手在看到拳之前挡住了一个毫不起眼的左勾拳,这是直觉的例子吗?或者可能是肌肉记忆,这是他们大脑的潜意识模式识别的结果,这些模式识别来自多年来与特定体型和身材的对手的战斗。
同样,当一个盲人能够在繁忙的街道上行走,同时避开汽车和其他潜在的危险因素时,他们是凭直觉行事,还是已经掌握了使用其他感官收集尽可能多的环境数据的困难任务?
直觉的终点和数据科学的起点之间有一点灰色地带。这就引出了另一个问题:什么是数据科学?
什么是数据科学?
数据科学的力量
关于什么是数据科学,有很多定义和解释,但维基百科说得最好:
“数据科学是一个跨学科领域,它使用科学方法、流程、算法和系统从结构化和非结构化数据中提取知识和见解,并将数据中的知识和可行见解应用于广泛的应用领域。”
换句话说,数据科学是一种利用科学手段分析相关数据,得出关于特定事物或事件的有意义见解的方法。
数据科学的应用
数据科学——特别是机器学习(ML)——似乎是最近的热门话题。大多数企业开始在其行业的各个方面应用一种形式的 ML,无论他们是在金融部门、娱乐部门、各种医疗领域还是在其他任何地方经营。
机器学习有很多方面。根据您试图解决的问题(或者您试图收集的洞察力的类型),您可以应用各种算法。
机器学习算法的应用可以很快变得非常复杂,这取决于你试图解决的问题的类型。一般来说,算法分为四种通用类型,这取决于底层数据集是受监督的(分类和回归)还是不受监督的(聚类或降维)。
回想之前的例子,拳击手在拳击场上迅速躲过一拳,人们可能会说他们的直觉很高。然而,一个相反的论点(也许是更可能的解释)是,拳击手正在练习一种复杂形式的模式识别(分类算法的一个子集)。在拳台上花费无数时间训练和陪练的拳手,对步法、刺拳技术和整体反应的模式和节奏有着更深刻的理解。
在盲人安全地在街上导航的情况下,这也可以是模式识别的一种形式,这一次是在聚类算法的子集中。因为聚类涉及没有先前知识可用于识别新目标的情况(换句话说,盲人可能无法识别行走过程中可能出现的所有潜在危险),所以这一类别中的算法(例如 K-means 聚类)使用模式来分配新对象(例如,汽车、自行车)到一个组中用于预测目的(例如致命的、潜在有害的、无害的)。
通过这些例子,数据科学和直觉之间的相似之处似乎显而易见。此外,这让我想知道我们的大脑产生这些直觉思维的一些原因是否可以用数据科学来解释。如果数据科学可以用来合理化至少一部分我们为什么以看似直觉的方式行事,那么有没有一种方法可以有意地在我们的大脑中应用数据科学来加强我们的直觉?
我将以一篇关于聪明主义的文章中我最喜欢的一段来结束这一部分,这篇文章叫做“直觉如何帮助我们做出更好的决定”
人脑由两部分组成,一部分是我们可以控制的意识,另一部分是我们几乎无法控制的潜意识。人类的大脑处理大量的信息,其中大部分是在潜意识中完成的。因此,源于潜意识的直觉思维可能非常强大,让我们获得意识意识无法掌握的信息。”
我们可以用数据科学来加强我们的直觉吗?一个简短的案例研究:鲨鱼池上的天使投资人
你看过《鲨鱼坦克》这部剧吗?这个节目可能是即时应用数据科学技术(有意或无意)做出直觉决策的最佳描述:交易或不交易。虽然我确信该节目是为了满足娱乐电视的标准而编辑的,但它仍然有助于说明一个人如何使用数据科学来帮助指导他们的直觉的基础。
《鲨鱼池》的前提是围绕一群投资者,该节目称他们为“鲨鱼”,根据企业家的简短推介做出即时投资决定。鲨鱼之间发生的戏谑,以及它们与企业家之间的交流,都很有趣。
“你卖了多少台?”
“你的估值和利润率是多少?”
虽然这些问题说明了任何潜在投资者为了解初创公司的财务状况而进行的标准尽职调查,但最好的鲨鱼提出的问题不太涉及初创公司目前的状况,而是更多地涉及初创公司有可能成为什么样的公司。潜力是非常依赖直觉的东西,但也是可以用数据科学来衡量的东西。通过训练大脑像数据科学家一样思考,鲨鱼可以加强他们的直觉,并最终做出投资决定。
“你的竞争对手是谁?”
“什么样的人会购买这种产品?”
“在这次创业之前,你已经创建了多少家初创公司?”
数据科学技术,如竞争对手分析、推荐系统和预测分析(例如。预测)帮助回答诸如此类的问题。当鲨鱼最终被留下来对一家尚未盈利的公司做出直觉判断时,最好的鲨鱼自然会提出问题,展示他们对数据科学的应用,以指导他们的直觉决策。
《鲨鱼池》是利用数据科学加强直觉的一个很好的例子。在生活中,我们有时会遇到需要直觉反应的机会。通过实践在我们的思维过程中应用数据科学的方法,我们可能能够加强我们的直觉,以做出更好的生活决策。
将数据科学技术应用于日常生活
照片由 Christelle Hayek 在 Unsplash 拍摄
大多数人在一生中都会做出一些重大的人生决定。例子包括没有后备计划的辞职,跟随爱人环游世界,或者拿你一生的积蓄去投资一个有前途的商业想法。虽然这些人生决定通常是经过深思熟虑后做出的,但它们也可能是在一个人经历了支持或反对该决定的强烈直觉时做出的。
当谈到评估你与他人的关系时,使用数据科学技术可以节省你在那些可能不关心你最佳利益的人身上投入的时间。
让我们来看看数据科学可以帮助加强你的直觉的三种方式。
注意危险信号(分类系统中的逻辑回归)
在拿你一生的积蓄冒险投资一个新企业之前,想想所有可能让这个初创企业成为潜在独角兽或失败的变量。在心里给这些变量赋予一个权重,以及它们如何影响结果(在这种情况下,是一个商业想法的成功或失败)。
类似于银行机构使用机器学习来检测欺诈性交易的方式,您可以使用逻辑来直观地检测敌友和操纵者。一开始就注意关系中的“危险信号”可以让你在未来避免不必要的头疼。
逻辑回归是根据关键变量的存在或不存在确定二元结果(0 或 1)的完美例子。递归特征消除是一种分配变量重要性顺序的方法,这将对您的结果产生影响。
执行自然语言处理(抓取、词云和情感分析)
在没有后备计划的情况下辞职之前,研究一下其他这样做的人,看看他们的生活如何变得更好(或更坏),可能会有所帮助。研究可以是任何事情,从采访人们到研究他们的行为。例如,如果你和一个朋友喝咖啡,他最近辞职了,没有后备计划,你可以根据他们使用的词语类型、他们使用这些词语的频率以及他们不得不说的话背后的情绪来了解他们的生活质量。如果他们的大部分对话都涉及到诸如“压力大”、“不确定”和“孤独”之类的词,你就能理解他们对自己决定的总体负面情绪。
就像你可以抓取 LinkedIn 或 Twitter 上的关键词进行分析一样。通过 wordcloud 和情感分析,你可以使用自然语言处理来衡量对某人的整体情感。人们用什么词来定义这个人?社区对他们的性格有什么看法?换句话说,想办法在各种圈子里认可这个人的名声。
信任模式(分类和聚类)
模式识别是本文前面讨论过的内容,并且仍然是直觉的一个非常重要的驱动因素。例如,如果你倾向于做出不会带来积极结果的非理性决定,比如开始几个新项目,但从未完成其中任何一个,也许你最近将毕生积蓄投资于一项业务的直觉不是一个好主意。
同样,如果一个朋友有 X 模式,尽管他们承诺做 Y,你可以肯定他们的真实意图——或者至少倾向——将总是默认为 X。
理解这样的模式有助于确认或重新评估你最初的直觉。
将情感与逻辑分开,增强你的直觉
有一句名言是这样说的:“不要在生气的时候做决定,不要在高兴的时候做承诺。”
这是一个非常重要的引用,因为它揭示了可能会妨碍使用数据科学来加强直觉的东西:情感。
数据科学的应用需要逻辑的、不带偏见的思维。然而,如果你在特定的情况下情绪化,逻辑思维会变得非常困难。
快乐是一种滋生乐观主义的情绪,它会蒙蔽你的头脑,影响你客观处理数据的能力。当你心情愉快时,你的大脑可能会自我选择与那种情绪相关的积极记忆和经历。反过来,在这种情绪中向你提出的任何要求和提议都可能获得积极的直觉反应。
当巧克力商把手工巧克力样品放在鲨鱼池上时,他们希望鲨鱼品尝他们的产品。虽然主要目的可能是让鲨鱼有机会评估巧克力的质量,但另一个动机可能是提升鲨鱼的情绪,以引导它们走向乐观。如果鲨鱼心情愉快(吃了巧克力后谁不会呢!),他们可能愿意忽略巧克力是一个极度饱和市场的现实!悲观主义以类似的方式起作用。愤怒或悲伤时做出的直觉决定很可能会导致未来的后悔。
快乐和愤怒并不是唯一会严重影响我们逻辑思维能力的情绪。有多少次,直到关系结束后,我们才意识到浪漫关系中的危险信号?这是因为爱是一个强大的过滤器。这种强烈的情绪很容易掩盖显而易见的事情。只有在你离开这段关系后,你才能客观地认识到这段关系中所有的危险信号,并“清醒过来”
其他强烈的情绪包括恐惧、钦佩、厌倦和同情。
结论
虽然数据科学和直觉不是一回事,但有意将数据科学应用到你的思维过程中可能有助于增强你的直觉。
感谢阅读!
【www.ayaspencer.com】上 上找我 。我们连线吧!
包装 numpy 的数组
集装箱方法。
记得使用右边的“关注”按钮来关注我→:你会收到新文章的通知,并帮助我达到 100 个关注者的目标:)
Numpy 的数组是功能强大的对象,通常被用作更复杂对象的基础数据结构,如 pandas 或 xarray 。也就是说,您当然也可以在自己的类中使用 numpy 的强大数组——为此,您基本上有两种方法:
- 子类方法:创建从 numpy.ndarray 继承的类
- 容器方法:创建属性为数组的类
在本文中,我们将看到如何使用容器方法包装 numpy 的数组来正确地创建自己的自定义类*。*
照片由 Guillaume Bolduc 在 Unsplash 上拍摄
让我们以一个示例项目为例:我们想要创建一个简单的项目来处理物理单位和维度,创建长度类似于[1, 2, 3] meter
或重量类似于[55 65 8] kilogram
的数组,然后使用这些数组来计算平均身高或【身体质量指数】(https://en.wikipedia.org/wiki/Body_mass_index)。我们希望依靠 numpy 来完成繁重的数字计算(如加、减、幂),但我们也希望能够处理 numpy 数组之类的实例,如np.sort(weights)
或np.min(heights)
。
为此,我们将创建一个使用容器方法包装 numpy 数组的新类。数值将存储为普通的 numpy 数组,物理维度存储为字符串:
物理阵列的第一种实现
这将简单地打印:[55.6 45.7 80.3] kilogram
。同样,这个字符串后面的数字列表是存储在self.value
中的实际 numpy 数组。
现在这是完全无用的:我们不能让这个对象与任何其他东西交互,所以我们添加了基本的操作,比如与其他Physical
实例的加法或乘法:
现在,物理阵列可以与其他物理阵列相加或相乘。
注意,在增加或减少物理量之前,我们首先检查它们是否有相同的单位:你不能用重量来增加长度(或用胡萝卜增加土豆,或用驴子增加马)。
这太棒了,我们现在可以计算一组体重指数(身体质量指数),给定一组以米为单位的身高和一组以千克为单位的体重。身体质量指数简单地通过将重量除以高度的平方给出,即:
BMI =weight(kg)/height(m)^2
万岁!我们用一个高度数组和一个高度数组计算体重指数数组,用后面的 numpy 数组进行实际的数值计算。但是 numpy 的阵列提供了更多的东西,这就是它真正有趣的地方。
实现 numpy 功能支持
Numpy 提供了许多有用的函数用于数组。仅举几个例子:
np.sin
、np.cos
、np.tan
等np.exp
、np.log
、np.log10
等np.add
、np.multiply
、np.divide
等np.min
、np.max
、np.argmin
、np.argmax
等np.floor
、np.ceil
、np.trunc
等np.concatenate
、np.vstack
等
诸如此类。你可以在 numpy 的网站上找到他们所有的东西:https://numpy.org/doc/stable/reference/routines.html。
让我们试着在课堂上使用其中一个:
试图在我们的物理实例bmi
上调用np.mean
会引发一个AttributeError
,因为 numpy 依赖于整数的加法和除法,而我们的类不能正确地实现这种操作。所以我们必须在某个地方告诉 numpy,我们希望np.mean(bmi)
如何表现。
这就是__array_function__
接口发挥作用的地方。
接口只是一个规范化的过程,用来重载(某些)numpy 函数如何处理来自你的类的参数。
让我们看一个简单的例子来处理我们的np.mean(bmi)
呼叫:
使用 array_function 接口实现 np.mean 支持
再次欢呼,np.mean(bmi)
返回我们的物理数组的“平均值”,它确实是一个物理量,单位为“kilogram/meter^2".”
让我们回顾一下为了实现这一点我们在代码中添加了什么。有 4 件事需要注意:
- 首先,我们在类定义之上创建一个名为
HANDLED_FUNCTION = {}
的空字典。 - 其次,我们向我们的类中添加了一个名为
**__array_function__**
的方法,该方法带有一个名为func
的参数。我们一会儿将回到这个方法的内容。 - 第三,我们创建一个装饰器构造函数:这是一个返回装饰器的函数(即另一个接受函数作为参数的函数)。我们的
implements
装饰器只是在我们的HANDLED_FUNCTION
字典中创建一个 numpy 函数和一个func
函数之间的对应关系,这是我们的 numpy 函数版本。 - 第四,当使用作为物理实例的
x
调用np.mean(x)
时,我们实现了 numpy 的 mean 来处理物理实例。它具有与np.mean
大致相同的签名,并执行以下操作:
- 使用 x 的值计算数值平均值,
x._value
,这是一个简单的数组。 - 然后使用平均值作为值,输入的单位作为单位,创建一个新的物理实例。
- 最后,我们在那个函数上使用
implements
装饰器。
那么当我们调用np.mean(bmi)
时会发生什么呢?
嗯,因为 numpy 无法计算平均值,正如我们在上面看到的,它检查bmi
是否有一个__array_function__
方法,并用在bmi
上使用的函数调用它,即np.mean
: bmi.__array_function__(np.mean, *args, **kwargs)
。
由于np.mean
已经在HANDELED_FUNCTIONS
中注册,我们用它来代替来称呼np.mean
的我们版本:这里HANDLED_FUNCTIONS[np.mean](*args, **kwargs)
相当于np_mean_for_physical(*args, **kwargs)
。
这就是如何让 numpy 的函数与您的自定义类一起工作。
不幸的是,这并不完全正确。这个接口只适用于一些 numpy 函数,而不是所有的函数。
还记得上面的函数列表吗?我们可以将它们分为两个子列表:常规的 numpy 函数和 numpy 通用函数——或简称为“ufuncs ”:
- 数字功能:
np.min
、np.max
、np.argmin
、np.argmax
、np.concatenate
、np.vstack.
- Numpy ufuncs :
np.sin
、np.cos
、np.tan
、np.exp
、np.log
、np.log10
、np.add
、np.multiply
、np.divide
、np.floor
、np.ceil
、np.trunc
我们看到了如何使用__array_function__
实现 numpy 函数支持。在下一篇文章中,我们将看到如何使用__array_ufunc__
接口添加对“ufuncs”的支持。
总结一下:
- 使用 numpy 数组的容器方法在于将数组设置为自定义类实例中的属性(与数组的子类化相反)。
- 要让你的类使用 numpy 函数调用,比如
np.mean(my_array_like_instance)
,你必须在你的类中实现__array_function__
接口。 - 这基本上是通过在你的类中添加一个
__array_function__
方法,编写你自己的包装器(就像我们对np_mean_for_physical
所做的那样),并将它们链接在一起(就像我们对查找字典HANDLED_FUNCTIONS
所做的那样)。 - 请注意,这只适用于“常规”numpy 函数。对于 numpy 的“通用”函数,您也需要实现
__array_ufunc__
接口。
这个主题非常广泛,因此您应该阅读以下几个链接,以便更好地了解什么是最重要的:
- 集装箱进场:https://numpy.org/doc/stable/user/basics.dispatch.html
__array_function__
参考:https://numpy . org/doc/stable/reference/arrays . classes . html # numpy . class . _ _ array _ function _ _- ufuncs 参考:https://numpy.org/doc/stable/reference/ufuncs.html
以下是我们在本文中编写的完整代码:
干杯!
使用 Python 中的装饰模式包装 PySpark 数据帧
图多尔·巴休在 Unsplash 上的照片
如何包装 PySpark 数据帧?
在我的一个项目中,我需要增强现有的 DataFrame 功能。一种方法是实现实用程序方法,这些方法可以获取数据帧并根据需要实现附加功能。另一种方法是实现 decorator 模式,其中 Decorator 类将接受数据帧并实现其他方法。
让我拿 1
首先,让我们创建一个简单的 DataFrameDecorator 类,通过用常量参数 1 实现 take 方法来增强 DataFrame 的功能。让我们称这个方法为 take1。因此,如果修饰的数据帧不为空,该方法将返回一条记录。
上面的实现正是我们要做的。它实现了 take1 方法,该方法通过在修饰的 df 上调用 take(1) 来显式声明 take1 行。
上面的代码测试了我们刚刚实现的内容,两个打印命令返回相同的值。这是包装数据帧并获得一条记录的简单部分。如果我们想通过 DataFrameDecorator 访问 DataFrame 上所有现有的方法会怎么样?为了解决这个问题,我们将使用 getattr,但是在我们跳到这个问题之前,让我们激励一下我们为什么要使用它。
方法
实例对象理解两种属性名:数据和方法属性。方法只是实例对象的一个属性。对于本文来说,这可能没什么价值,但重要的是要提到,类有函数对象,而类实例有绑定到函数对象的方法对象。
如果你仍然不明白方法是如何工作的,看看实现也许可以澄清问题。当引用实例的非数据属性时,会搜索实例的类。如果名字表示一个有效的类属性,该类属性是一个函数对象,则通过将实例对象和函数对象打包(指向)来创建一个方法对象,这是一个抽象对象方法对象**。当用参数列表调用方法对象时,从实例对象和参数列表构造新的参数列表,并且用这个新的参数列表调用函数对象。**
来源:https://docs . python . org/3/tutorial/classes . html # class-objects
要查看此操作,让我们看看下面的代码:
类上的 take1 是函数对象,而实例上的 take 1 是类函数对象的绑定方法。
getattr
回到我们的问题,即 DataFrameDecorator 类不能处理所有的 DataFrame 函数。 getattr 是找不到属性时调用的方法。我们能做的就是在 DataFrameDecorator 类上实现 getattr 来处理 DataFrame 的所有功能。让我们首先看看下面的代码,以了解当我们在 DataFrameDecorator 上调用 take 时会发生什么,此时 getattr 被实现来为在 DataFrameDecorator 上未找到的任何属性返回默认字符串“function not found”。
理想情况下, getattr 返回属性,所以在这种情况下,我们返回一个函数 lambda,它在执行时只打印出没有找到原始函数。此外,显式打印 df_decorated.take 可以清楚地表明,它不是一个显式函数,而是 lambda 函数,是类 DataFrameDecorator 上的 getattr 方法的一部分。
现在,这给了我们一种方法来实现所有的 DataFrame 函数,只需在底层的 df 上调用 DataFrameDecorator 中的方法。让我们看看那会是什么样子。
上面的代码将所有这些放在一起。现在,即使没有在 DataFrameDecorator 上定义方法 take,使用 getattr 我们也可以在底层 df 上调用 DataFrame 方法。
结论
在这篇文章中,我讲述了如何使用装饰模式包装 DataFrame 以增强其功能。我希望你喜欢它。
在 LinkedIn 上与我联系或在 Medium 上关注我。如果你喜欢这个故事,你可能会喜欢我关于 python decorators 的其他故事:
https://betterprogramming.pub/decorator-pattern-and-python-decorators-b0b573f4c1ce
用 Code ML 再现性挑战结束论文—2021 年春季
DagsHub x 论文,带代码,图片由作者提供
2021 年春季 ML 再现性挑战正式结束,我们有一些由 DagsHub 社区贡献的鼓舞人心的项目来分享!
数据科学的可再现性是 DagsHub 成立的核心原因之一,我们正在不断开发新的工具和集成来支持完全可再现的工作流。这就是为什么我们对论文与代码-再现性挑战如此兴奋,并决定第二次支持其参与者(剧透 : 我们也支持 2021 年秋季挑战!).
“数据的独立验证是跨学科科学研究的基本原则。科学方法的自我修正机制依赖于研究人员复制已发表研究结果的能力,以便加强证据并在现有工作的基础上更进一步。” 性质
在 2021 年春季版中,我们支持 3 个团队提交了完全开源且可复制的 ML 论文,您现在可以轻松使用它们了!在我们深入项目之前,我们想对组织这次活动的代码为的论文以及投入时间和精力复制论文并使其可访问的社区成员表示敬意。
图片来自期限
因此,没有进一步的到期,我想欢迎 2021 年春季版的转载论文!
上下文分解解释惩罚
贡献者:
“为了有效地解释深度学习模型,它必须提供对模型的洞察,并建议相应的行动,以实现某些目标。太多时候,一连串可解释的深度学习方法在第一步就停止了,为从业者提供了对模型的洞察力,但没有办法对其采取行动。”论文作者
该论文提出了上下文分解解释惩罚,CDEP,其允许在训练期间使用解释来惩罚模型,从而不学习虚假的相关性。CDEP 在解释的帮助下将特征空间分解为相关和不相关特征,并惩罚模型来查看相关特征进行分类。例如,在 ISIC 皮肤癌分类任务中,数据集包含阳性患者佩戴创可贴的偏倚图像。通过使用 CDEP,模型可以被训练成忽略有偏差的创可贴特征,并学习正确的特征。在存储库中,CDEP 已经被应用于跨多种模态的不同架构。
图 S4。来自 ISIC 的良性样本热图,图片来自官方文件
在这个项目中,Shailesh、阿兹哈尔和 Midhush 在 Tensorflow 中重新实现了原来的 PyTorch 项目。这要求他们从头开始编写 PyTorch 的“un pool”函数,以便与 Tensorflow 一起使用。这后来成为一个补丁被推送到 Tensorflow 插件库,让它们以一个的价格贡献给两个开源项目!
少投学习的自我监督
投稿人:
- 哈斯旺斯·艾库拉
- 阿尔琼·阿肖克
在本文中,研究者在https://research.aimultiple.com/few-shot-learning/**【FSL】的背景下考察了** 自监督学习 (SSL)的作用。尽管最近的研究显示了 SSL 在大型无标签数据集上的优势,但它在小型数据集上的效用相对来说还未被探索。他们发现,SSL 将少数元学习者的相对错误率降低了 4%-27%,即使数据集很小,并且只利用数据集中的图像。
结合监督和自我监督损失进行少镜头学习,图片来自官方论文
“我们选择这篇论文是因为少镜头学习是一种新兴的、越来越受欢迎的机器学习范式,而自我监督学习似乎是一种在 FSL 获得更好性能的简单方法,不需要花里胡哨。”Arjun 和 Haswanth
Arjun 和 Haswanth 基于作者的代码库,在五个基准数据集上复制并验证了论文的主要结果。此外,他们从头实现了域选择算法,并验证了它的好处。
论文使用了 224x224 的图像尺寸,这引起了 Arjun 和 Haswanth 的兴趣,所以他们决定研究它如何影响模型性能。他们将图像大小修改为 84x84,这是 FSL 论文中常用的设置,同时还简化了架构。他们发现这种设置是失败的,它降低了模型的性能。
可解释的 GAN 控制
贡献者
本文描述了一种简单的技术,用于分析 GANs 模型 并为图像合成创建可解释的控件,如视点变化、老化、光照和一天中的时间。
图 1:使用我们的方法发现的控件执行的图像编辑序列,应用于三个不同的 GANs,将官方文件成像
Vishnu 和 Midhush 使用了 StyleGAN 和 StyleGAN2 模型来重现论文的结果。这两种模型都是通过计算映射网络输出的主成分分析来对几个采样的潜在向量进行工作的。这给出了映射网络空间的基础,从中我们可以通过改变 PCA 坐标来编辑新的向量。扩充的向量然后被馈送到合成网络,以获得具有修改的属性的图像。
Vishnu 和 Midhush 将最初的 PyTorch 实现转换为 Tensorflow,并验证了论文中提出的主张。他们用论文中使用的基准数据集训练模型,比如 FFHQ 、 LSUN Car 和 CelebAHQ 。为了验证他们的结果,他们用原始论文中没有使用的数据集测试了模型的性能,如甲虫和动漫肖像。
“最初,我们试图使用原始 PyTorch 代码和我们在 Tensorflow 中修改的代码重新创建具有相同 RGB 值的图像。然而,由于 PyTorch 和 Tensorflow 中随机数生成器的差异,即使使用相同的种子,随机值也不相同。这导致了一些生成图像的背景伪影的微小差异。一旦我们确定这是微小差异的原因,我们就能够在 Tensorflow 实现中插入 PyTorch 的随机数生成器,并成功地再现这些图像。 最终,我们能够验证与 StyleGAN 和 StyleGAN2 型号 相关的所有声明。”毗湿奴和 Midhush
摘要
**我们要感谢所有参与这项挑战的了不起的数据科学家。DagsHub 团队喜欢与你们每一个人一起工作,并在这个过程中学到了很多。你对社区产生了巨大的影响,让我们离开源数据科学又近了一步。如前所述,我们正在支持**2021 年秋季版的代码可再现性挑战论文。如果你想参与并推动机器学习领域向前发展,请前往新指南页面并加入我们的 Discord 社区开始吧!团队是来帮忙的!
为你的 Keras 模型编写一个定制的训练程序
小窍门
当简单性和易用性开始阻碍你时
对于开始学习深度学习的人来说,Keras 工具箱是无可匹敌的。它拥有你所需要的一切,令人困惑的底层内容保持在最少。该 API 非常直观,让您专注于设计网络的重要部分,允许快速实验而没有太多麻烦。例如,本指南中使用的网络是用不到 25 行 Python 代码指定和训练的。
然而,有时基本 Keras 功能的易用性会受到限制。许多更高级的神经网络训练方案和损失函数变得不必要地复杂,难以用本地 Keras 进行编码。在本指南中,我旨在展示如何将训练神经网络的基本 Keras 方法分解为其基础部分,从而为用户根据需要更改每个部分提供可能性。我没有在示例中包含任何这些自定义部件;本指南的目的只是给你一些工具,让你自己去做更多的实验。
在本指南中,我将使用 FashionMNIST 数据集来设置和展示两种不同的训练神经网络的方法,以对不同服装对象的图片进行分类。该数据集包含来自 10 个对象类的 70000 幅图像,在 60000 幅训练图像和 10000 幅验证图像之间有预定义的分割。这个例子改编自 Keras 教程,在那里你可以找到更多有趣的教程。
时尚主义者数据集的一些示例图像
古典希腊
使用 Keras 构建神经网络非常简单。您可以一层一层地定义它,同时指定层的属性。这些属性是卷积滤波器的数量、内核大小、激活函数等。示例模型由两个 3x3 卷积块组成,中间有一个 4x4 最大池操作。最终的要素地图通过全局平均池操作运行,激活 softmax 的最终密集图层提供类别预测。下面的函数构建神经网络并返回包含所有层的 tf.keras.Model 对象。该对象稍后用于训练和测试神经网络。
在训练模型之前,Keras 要求我们指定一些关于训练过程的细节,比如优化器和损失函数。例如,我们还告诉 Keras 在训练过程中跟踪网络的准确性。
真正的奇迹发生在现在,在网络的训练下。训练神经网络包括在训练样本的大数据集上最小化损失函数。使用取自大数据集的小批量样本来最小化该损失函数。为这些小批量计算损失函数,并且对于每个批量,用梯度下降算法的小步骤更新网络的参数。Keras 只需调用“fit”函数,使用适当的参数,就可以处理所有这些问题。
这告诉 Keras 在带有相应标签“y_val”的训练数据集“x_train”上训练我们的网络。小批量包含 64 个图像。我们的网络将训练 10 个纪元,或者在整个训练数据集上进行 10 次传递。在这样一个时期结束时,为验证数据计算性能,允许我们在训练期间监控网络的泛化潜力。
默认情况下,Keras 会在训练过程运行时向您显示有关训练过程的有价值的信息,例如您让它跟踪的损失和潜在指标,并告诉您它通过数据集的速度。
风俗习惯
仔细想想,“fit”功能会为您处理很多细节。它组成批次,计算损失函数,推断我们应该在参数空间中的哪些方向移动,跟踪验证性能。对于大多数用例,这将是你所需要的。所有这些细节在用例之间不会有太大的变化,将它们留给 API 可以腾出时间来调整和修补重要的东西。当细节发生变化时,Tensorflow 提供了足够的工具。然而,你需要自己做更多的工作。
首先是数据集的批处理。Tensorflow 提供了两种解决这个问题的方法:“TF . data”API 和“tf.keras.utils.Sequence”类。这个例子将使用后者。“tf.data”有可能提供改进的性能,但是对于我自己编写的许多“定制”训练例程来说,“Sequence”类更容易使用。您必须创建自己的子类,实现几个在训练期间使用的函数:
- init,初始化子类的对象
- len,指定批次的数量
- getitem,编写从完整训练集中抽取一个批次的指令(这也是您经常执行某种形式的数据扩充的地方)
- on_epoch_end,可以在训练时期结束时调用,例如,执行一些数据混洗,以改变下一个时期的图像排序
接下来是实际训练的设置。您必须指定您的优化器并获得损失函数的实例。您可能还想初始化一些簿记变量。在这里,我跟踪训练和验证数据集的损失和准确性。你可以把这看作是为 Keras 的“编译”函数编写你自己的替代函数。
最后,我们到了关键的一步:训练网络。Tensorflow 允许我们将使用 Keras API 函数构建的相同模型用于自定义训练循环。然而,其他一切都将改变。现在,训练需要两个嵌套的 for 循环,而不是一个函数调用。外部循环跟踪不同的时期,内部循环提供了迭代批处理的机制。对于每个批处理迭代步骤,我们使用自定义的“Sequence”子类生成相应的批处理。Tensorflow 使用“tf”监视该批次通过网络的转发。GradientTape ',因此我们可以稍后使用损耗的梯度来确定对网络参数的必要改变。在每个时期结束时,平均训练损失和准确度存储在我们的簿记变量中。这也是根据验证数据确定网络性能的时候。
这就是全部内容:在神经网络的训练过程中,将不同部分分开的所有步骤。编写定制的训练循环将允许您更容易地跟踪各种奇异的性能度量,或者构建依赖于直接批量训练输入和标签之外的信息的损失函数。把它看作是你的神经网络工具箱的扩展。
为了完整起见,您可以在下面找到一个 Jupyter 笔记本,它将所有不同的步骤都包含在一个包中,供您自己测试。
包含所有不同步骤的 Jupyter 笔记本
在不到 30 分钟的时间内编写一个文档分类器。
让我们看看如何快速实现一个模型来对文档进行分类。
图片来自皮克斯库
在我过去的一次面试中,有人要求我实现一个模型来对论文摘要进行分类。我的目标不是得到一个完美的调优模型,而是看看我在最短时间内完成整个过程的能力。以下是我所做的。
数据
数据由 PubMed 数据库中的论文摘要组成。PubMed 是所有生物医学文献的资料库。管理 PubMed 的机构 NCBI 提供了一个下载论文的 API。已经有很多库可以用几种语言与 API 交互。我使用了 Python,我找到的最简单的库是 Bio 及其用于这个特定数据库的模块 Entrez。
我们导入该模块,并配置一个电子邮件,这是强制性的,让他们跟踪每秒的请求数。您甚至可以要求 API_KEY 将每秒的文档数增加到 10。
from Bio import EntrezEntrez.email = '[your@email.com](mailto:ya.netsec@gmail.com)'
Entrez.api_key = "abcdefghijklmnopqrstuvwxyz42"
为了从 PubMed 获取文章,我们首先执行一个查询,返回每个文档的元数据,比如它的 ID。然后,我们使用 id 来获取细节(在我的案例摘要中)。
def search(query, max_documents=1000):
handle = Entrez.esearch(db=’pubmed’,
sort=’relevance’,
retmax=max_documents,
retmode=’xml’,
term=query)
results = Entrez.read(handle)
return results
该函数将在 PubMed 数据库的参数中执行查询,根据相关性对结果进行排序,并将结果的数量限制为 max_documents。
查询其实很简单。您可以使用文档关键字和逻辑运算符。PubMed 文档详细解释了如何构建查询。
在面试中,我被要求拿到 4 门课(题目)的资料。我们通过在查询中指定每个类的相关关键字来做到这一点。
该函数的结果是一个没有内容的文档细节列表。然后,我们使用这些 id 来获取文档的所有细节。
def fetch_details(id_list):
handle = Entrez.efetch(db=”pubmed”, id=’,’.join(map(str, id_list)),rettype=”xml”, retmode=”text”)
records = Entrez.read(handle)
abstracts = [pubmed_article[‘MedlineCitation’][‘Article’] [‘Abstract’][‘AbstractText’][0] for pubmed_article in records[‘PubmedArticle’] if ‘Abstract’ in pubmed_article[‘MedlineCitation’][‘Article’].keys()]
return abstracts
该函数将获取一个 id 列表,并返回一个包含所有摘要的数组。获取特定类的所有摘要的完整函数是:
def get_abstracts_for_class(ab_class):
list_abstracts = [] ## get keywords of the class query = " AND ".join(keywords[ab_class]) res = search(query)
list_abstracts = fetch_details(res["IdList"]) return list_abstracts
我将所有关键字保存在字典中,并使用它们来构建查询。
我们为每个类调用函数,以获取所有类的所有摘要。最后,我们将它们重新格式化,得到一个可用的熊猫数据帧。
list_all_classes = []list_all_classes += [{“abs”: a, “class”: 1} for a in list_abs_class1]
list_all_classes += [{“abs”: a, “class”: 2} for a in list_abs_class2]
list_all_classes += [{“abs”: a, “class”: 3} for a in list_abs_class3]
list_all_classes += [{“abs”: a, “class”: 4} for a in list_abs_class4]abs_df = pd.DataFrame(list_all_classes)
数据清理
同样,这里的目标不是完美地清理数据集,但是一个小的标准预处理是必要的。我个人大部分时间使用 NLTK,但是你可以用几乎所有的 NLP 库做同样的事情。
from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
import string## 1) Lower
abs_df[“abs”] = abs_df[“abs”].str.lower()## 2) Remove tags
abs_df[“abs”] = abs_df.apply(lambda x: re.sub(“<[^>]*>”, “”, x[“abs”]), axis=1)## 3) Tokenize
abs_df[“abs_proc”] = abs_df.apply(lambda x: word_tokenize(x[“abs”]), axis=1)## 4) Remove punctuationnltk.download('punkt')
table = str.maketrans(‘’, ‘’, string.punctuation)
abs_df[“abs_proc”] = abs_df.apply(lambda x: [w.translate(table) for w in x[“abs_proc”]], axis=1)## 5) Remove non-alpha
abs_df[“abs_proc”] = abs_df.apply(lambda x: [w for w in x[“abs_proc”] if w.isalpha()], axis=1)## 6) Remove stop-words
nltk.download('stopwords')
stop_words = set(stopwords.words(‘english’))abs_df[“abs_proc”] = abs_df.apply(lambda x: [w for w in x[“abs_proc”] if not w in stop_words], axis=1)## 7) Reformat to have a single text. abs_df[“abs_proc_res”] = abs_df.apply(lambda x: ‘ ‘.join(x[“abs_proc”]), axis=1)
我们使用 Pandas 应用功能的强大功能,对整个数据帧应用相同的处理:
- 降低所有文本
- 我发现文本中有一些标签,比如来表示粗体文本。即使这些标签可能很重要,但这对于一个小时的练习来说太复杂了。所以我决定用正则表达式删除它们。
- 我们首先对文本进行记号化:即,将它分割成一系列单独的单词。
- 删除所有标点符号,如问号(?)或逗号(,)。
- 我们移除非字母符号,即数字。
- 我们删除停用词。我们首先使用 NLTK 检索英语停用词词汇,然后使用它过滤掉我们的标记。
- 最后,我们将处理过的数据连接起来,使每个摘要都有一个单独的文本。
数据嵌入
如果你熟悉 NLP 问题,那么你知道在处理文本数据时最重要的部分可能是向量表示,即嵌入。关于这一点已经取得了很多进展,并且已经提出了一些强大的模型,如谷歌的 BERT 或 OpenAI 的 GPT。然而,这些模型很难调优,绝对不适合 1 小时的练习。此外,对于许多实际问题,一个非常简单的嵌入就足以获得数据的正确矢量表示。
最简单的大概就是 TF-IDF(词频-逆文档频),也就是我用过的那个。
sklearn 库已经有一个 TF-IDF 模块,可以直接在 dataframe 上使用。
from sklearn.feature_extraction.text import TfidfVectorizervec = TfidfVectorizer()
x = vec.fit_transform(abs_df["abs_proc_res"])
此时,我们有一个矩阵 X 对应于我们所有的矢量化摘要。然而,看着 X 的形状,我们注意到了一些东西:
print(x.shape)(25054, 60329)
我们最终得到大量的列(即 60329)。这是正常的,因为这个数字对应于整个语料库(即整个数据集)的词汇大小。这个数字有两个问题。
首先,它会使模型的训练复杂化。
第二,即使我们做了大量的预处理,词汇表中的大多数单词与分类无关,因为它们没有添加任何相关信息。
幸运的是,有一种方法可以减少列数,同时避免丢失相关信息。最常见的方法称为 PCA(主成分分析),它将矩阵分解为一组不相关的低维矩阵。我们应用 SVD(奇异值分解),这是一种 PCA。还是那句话,有一个 sklearn 模块可以轻松做到。
**from** **sklearn.decomposition** **import** TruncatedSVDsvd = TruncatedSVD(n_components=100)
res = svd.fit_transform(x)print(res.shape)
(25054, 100)
我选择将我们的初始矩阵减少到 100 个组件(即特性)。这是一个要优化的参数:我们越接近初始维度,我们在缩减过程中丢失的信息就越少,而较小的数量将降低我们的模型训练的复杂性。
我们现在准备训练一个分类器。
模型
有很多分类模型。理解和实现最简单的方法之一可能是 SVM(支持向量机)。简而言之,它将试图画一条线,尽可能地将每个类的点分开。
我们还使用交叉验证来提高指标的代表性。
**from** **sklearn** **import** svm
**from** **sklearn.model_selection** **import** RepeatedKFold
**from** **sklearn.model_selection** **import** cross_val_score
**from** **sklearn.model_selection** **import** cross_validate
**from** **numpy** **import** mean
**from** **numpy** **import** stdy = abs_df["class"].values
X = rescv = RepeatedKFold(n_splits=10, n_repeats=3, random_state=1)model = svm.SVC(kernel='linear', C=1, decision_function_shape='ovo')
我们使用线性内核,也就是说,它会尝试绘制一条线来分隔数据。其他核存在,如多项式,试图找到一个多项式函数,更好地分离点。
决策函数被设置为 ovo,即一对一,这将花费每次尝试来分离每对类别,而忽略其他类别。
我们训练吧!
metrics = cross_validate(model, res, y, scoring=['precision_macro', 'recall_macro'], cv=cv, n_jobs=-1) print('Precision: **%.3f** (**%.3f**)' % (mean(metrics["test_precision_macro"]), std(metrics["test_precision_macro"])))
print('Recall: **%.3f** (**%.3f**)' % (mean(metrics["test_recall_macro"]), -std(metrics["test_recall_macro"])))-----------------------------------Precision: 0.740 (0.021)
Recall: 0.637 (0.014)
这里有两个有趣的指标:精确度和召回率。
精度是指,对于每一类,在预测的文档中,有 74%是正确预测的,这已经不错了。
另一方面,召回意味着在某个类的所有文档中,我们能够捕获 63%。
结论和展望
正如你所看到的,只使用机器学习的基础知识,实现一个快速分类器是相对容易的。当然它并不完美,但是当你什么都没有的时候,即使是一个糟糕的模型也是可以接受的。
很明显,还有很多可以改进的地方。预处理可能是对模型影响最大的部分。例如,不使用 TF-IDF,我们可以尝试更复杂的算法,如 BERT。从模型的角度来看,我们也可以尝试其他分类器,甚至堆叠几个分类器以获得更好的性能。
也就是说,如果您的目标是有一个工作模型来分类您的文档,这是一个很好的起点。
下一步就是把这个投入生产!我将在另一篇文章中讨论这一部分。
写几行代码检测人脸,从复杂图像中画出地标~MediaPipe
图片由皮克斯拜的 Gerd Altmann 提供
使用 MediaPipe 从复杂图像中检测人脸既简单又有趣
媒体管道概述
MediaPipe 是 Google 内部最广泛共享和可重用的媒体处理库之一。”— Kahye Song
G oogle 开源 MediaPipe 于 2019 年 6 月首次推出。它旨在通过提供一些集成的计算机视觉和机器学习功能来使我们的生活变得简单。媒体管道是一个框架,用于构建多模态(如视频、音频或任何时间序列数据)、跨平台(如 eAndroid、IOS、web、边缘设备)的应用 ML 管道。Mediapipe 还有助于将机器学习技术部署到各种不同硬件平台上的演示和应用程序中。
值得注意的应用
- 人脸检测
- 多手跟踪
- 头发分割
- 目标检测和跟踪
- 反对:3D 物体检测和跟踪
- 自动裁剪:自动视频裁剪管道
以此类推…
您为什么需要 MediaPipe
有效管理资源(CPU 和 GPU)以实现低延迟性能,处理音频和视频帧等时序数据的同步,以及 MediaPipe 必不可少的更多有效理由。MediaPipe 将每个感知模型抽象成一个模块,并用维护图将它们连接起来。除了上述特性,MediaPipe 还支持 TensorFlow 和 TF Lite 推理引擎。任何 TensorFlow 和 TF Lite 模型都可以在 MediaPipe 上使用。同时在移动和嵌入式平台上,MediaPipe 也支持设备本身的 GPU 加速。
是时候介绍 MediaPipe 的一个令人惊叹的应用了,人脸检测。
人脸检测
考虑这样一个场景,“一个零售商要求你统计访客的数量,并跟踪访客在他们店里的活动。”
似乎很难!! 怎样才能解决问题? 嗯嗯……
哦,是的!我们将使用人脸跟踪来解决这个问题。现在的问题是怎样才能检测出顾客的脸。这个问题的答案是人脸跟踪技术使用人脸检测作为检测人脸的第一步。
人脸检测是计算机视觉中定位和定位照片中的一个或多个人脸的问题。
这个问题的一般陈述可以定义如下:给定一幅静止或视频图像,检测并定位未知数量(如果有的话)的人脸——人脸检测:一项调查,2001。
使用 MediaPipe 执行面部检测的模型:
为了执行面部检测,使用了三种模型:
- 近距离模式(最适合距离摄像头 2 米以内的人脸)
- 全范围模型(密集,最适合距离摄像机 5 米以内的人脸)
- 全范围模型(稀疏,最适合距离摄像机 5 米以内的人脸)
全范围密集和稀疏模型在**F-score方面具有相同的质量,但是在底层指标方面有所不同。密集模型在 召回 上稍好,而稀疏模型在 精度 上优于密集模型。**
是时候用 MediaPipe 的动手人脸检测模型让我们的手变脏了。
循序渐进方针
安装必要的库
要执行面部检测,您必须首先在机器上安装 MediaPipe。如果您是 windows 用户,那么您可以在计算机的命令提示符下运行下面的代码。
pip install mediapipe
有关详细说明,您可以点击以下链接:
https://Google . github . io/media pipe/getting _ started/python . html
你还需要安装 OpenCV 用于网络摄像头或图像输入。如果您是 windows 用户,您可以在命令提示符下运行下面的代码。
pip install opencv-python
有关详细说明,您可以点击以下链接:
https://pypi.org/project/opencv-python/
写代码了解 API 的使用:
我们使用 Google Colab 来运行代码。你可以随意使用它。导入必要的库。我们需要cv2
模块来读取和显示图像,以及 MediaPipe 模块,它公开了我们执行面部检测所需的功能
项目中提供了 google colab 版本和 jupyter 笔记本版本的全部代码。本文结论部分的末尾给出了链接。
*import cv2
import mediapipe as mp*
然后我们将访问两个子模块face_detection
和drawing_utils
**。face_detection
为用于加载所有功能以执行人脸检测,而drawing_utils
用于在图像上绘制检测到的人脸。****
*****mp_face_detection = mp.solutions.face_detection
mp_drawing = mp.solutions.drawing_utils*****
是时候深入挖掘代码了。首先,我们将一幅图像作为输入。这里我们使用两种类型的图像
(i)包含两米内的面部的图像
(ii)五米内包含人脸的图像。
我们使用来自 colab 的files
直接从本地目录加载图像。当您在本地计算机上工作时,也可以使用cv2.imread()
加载图像。
(a)拍摄的第一张图像
*****from google.colab import files
uploaded_short_range = files.upload()*****
(b)拍摄的第二张图像
*****from google.colab import files
uploaded_full_range = files.upload()*****
在本地电脑上工作时,您可以使用
*****cv2.imread() #to take input.*****
点击这里 了解更多cv2.imread()
现在,我们将调整图像的大小并显示图像。为了显示图像,我们必须使用来自 colab 的**cv2_imshow**
模块,或者在本地机器上工作时使用cv2.imshow(frame name, iamge)
模块。我们可以使用下面的代码来调整和显示谷歌 colab 中的图像。
用于调整大小和显示图像的代码
上述代码的输出示例(照片由 Radu Florin 在 Unsplash 上拍摄)
现在,我们将在面上绘制地标。
我们可以如下改变thickness
和circle_radius
的值。
*****drawing_spec = mp_drawing.DrawingSpec(thickness=1, circle_radius=1)*****
要了解关于mp.solutions.face_detection
运行的详细信息
代码下方:
*****help(mp_face_detection.FaceDetection)*****
在这之后,我们将创建一个类为Face detection的对象。这个对象将允许我们处理图像并执行面部标志估计。此类的构造函数支持以下参数:
**(一)型号选择:整数索引 0 或 1。使用 0 选择最适合距离摄像机 2 米以内的人脸的短程模型,使用 1 选择最适合距离摄像机 5 米以内的人脸的全程模型。对于全范围选项,使用稀疏模型以提高推理速度。具体请参考 型号卡 。如果未指定,则默认为 0。
****(ii)MIN _ DETECTION _ CONFIDENCE:来自面部检测模型的最小置信度值([0.0,1.0]),用于检测被认为是成功的。默认为 0.5。
*****with mp_face_detection.FaceDetection(min_detection_confidence=0.5, model_selection=0) as face_detection:*****
上面的代码model_selection = 0
表示我们选择近距离模型进行人脸检测。使用下面的代码,我们使用一个简短的图像模型进行最终的人脸检测,并绘制出地标。
短长度(2 米以内)图像的人脸检测模型
作者图片
现在对于model_selection = 1
来说,这意味着我们选择人脸检测 full-range model
。使用下面的代码,我们使用完整的图像模型执行最终的人脸检测,并绘制地标。
全长(5 米以内)图像的人脸检测模型
作者图片
我们也可以用全长人脸检测模型的代码在团体照片上执行这个过程。
作者图片
使用 MediaPipe,我们可以超越人脸检测。下面这篇关于algo scale的文章,将为你展示一个使用 OpenCV 和 MediaPipe 估算姿态的方向。
使用 OpenCV 和 MediaPipe 进行姿态估计
结论
人脸检测是计算机视觉中最常见的问题之一。有许多用于人脸检测和人脸标志绘制的技术。最有效的技术是在深度学习模型的帮助下创建的。但是如果我们试图从零开始制作模型,这需要巨大的计算能力、复杂的知识以及数据集。大多数情况下,是老百姓的问题。Mediapipe 库在使困难的任务变得简单方面是惊人的。该库提供定制的内置模型。在本文中,我们刚刚展示了使用 MediaPipe 进行面部检测和面部标志绘制的简单易行的过程。在接下来的文章中,将在 MediaPipe 的帮助下展示更多简单而有用的技术。
github 中的源代码
Colab 源代码………
Jupyter 笔记本源代码……
给你一些更有趣的文章
祖贝尔·侯赛因
使用 PyCaret 编写和训练您自己的自定义机器学习模型
一步一步的,初学者友好的教程,关于如何用 PyCaret 编写和训练定制的机器学习模型
罗布·兰伯特在 Unsplash 上拍摄的照片
PyCaret
PyCaret 是一个开源的低代码机器学习库和端到端的模型管理工具,内置于 Python 中,用于自动化机器学习工作流。它因其易用性、简单性以及快速高效地构建和部署端到端 ML 原型的能力而广受欢迎。
PyCaret 是一个替代的低代码库,可以用几行代码代替数百行代码。这使得实验周期成倍地快速和有效。
py caret简单 好用。PyCaret 中执行的所有操作都顺序存储在一个管道中,该管道对于**部署是完全自动化的。**无论是输入缺失值、一键编码、转换分类数据、特征工程,甚至是超参数调整,PyCaret 都能实现自动化。
本教程假设您对 PyCaret 有一定的了解和经验。如果您以前没有使用过,没关系,您可以通过这些教程快速入门:
- PyCaret 2.2 已经发布——新功能
- 宣布 PyCaret 2.0
- 关于 PyCaret 你不知道的五件事
正在安装 PyCaret
安装 PyCaret 非常容易,只需要几分钟。我们强烈建议使用虚拟环境来避免与其他库的潜在冲突。
PyCaret 的默认安装是 pycaret 的精简版本,只安装这里列出的硬依赖项。
**# install slim version (default)** pip install pycaret**# install the full version**
pip install pycaret[full]
当你安装 pycaret 的完整版本时,这里列出的所有可选依赖项也会被安装。
👉我们开始吧
在我们开始讨论定制模型训练之前,让我们来看一个 PyCaret 如何处理开箱即用模型的快速演示。我将使用 PyCaret 的存储库上的“保险”数据集。该数据集的目标是基于一些属性预测患者费用。
👉数据集
**# read data from pycaret repo** from pycaret.datasets import get_data
data = get_data('insurance')
保险数据集中的样本行
👉数据准备
对于 PyCaret 中的所有模块来说,setup
是在 PyCaret 中执行的任何机器学习实验中的第一个也是唯一一个强制步骤。该函数负责训练模型之前所需的所有数据准备。除了执行一些基本的默认处理任务,PyCaret 还提供了一系列预处理功能。要了解 PyCaret 中所有预处理功能的更多信息,您可以查看这个链接。
**# initialize setup** from pycaret.regression import *
s = setup(data, target = 'charges')
pycaret.regression 模块中的设置函数
每当在 PyCaret 中初始化setup
函数时,它都会分析数据集并推断所有输入要素的数据类型。如果所有数据类型都推断正确,您可以按 enter 键继续。
设置的输出—为显示而截断
👉可用型号
要查看所有可用于训练的模型列表,您可以使用名为models
的功能。它显示一个表格,其中包含模型 ID、名称和实际评估者的参考。
**# check all the available models** models()
models 的输出()-出于显示目的,输出被截断
👉模型训练和选择
PyCaret 中训练任何模型使用最多的函数是create_model
。它需要一个你想要训练的估计器的 ID。
**# train decision tree** dt = create_model('dt')
create_model 的输出(’ dt ')
输出显示了带有平均值和标准差的 10 倍交叉验证指标。这个函数的输出是一个经过训练的模型对象,它本质上是一个scikit-learn
对象。
print(dt)
打印输出(dt)
要在一个循环中训练多个模型,您可以编写一个简单的列表理解:
**# train multiple models**
multiple_models = [create_model(i) for i in ['dt', 'lr', 'xgboost']]**# check multiple_models** type(multiple_models), len(multiple_models)
>>> (list, 3)print(multiple_models)
打印输出(多种型号)
如果您想训练库中所有可用的模型,而不是选定的几个,您可以使用 PyCaret 的compare_models
函数,而不是编写自己的循环(结果将是相同的,尽管)。
**# compare all models**
best_model = compare_models()
compare_models 函数的输出
compare_models
返回显示所有模型的交叉验证指标的输出。根据这个输出,梯度推进回归器是最好的模型,在训练集上使用 10 重交叉验证,平均绝对误差**【MAE】为 2702 美元。**
****# check the best model**
print(best_model)**
打印输出(最佳模式)
上面表格中显示的指标是交叉验证分数,用于检查保留集上的best_model
的分数:
****# predict on hold-out** pred_holdout = predict_model(best_model)**
预测模型(最佳模型)函数的输出
要在看不见的数据集上生成预测,您可以使用相同的predict_model
函数,但只需传递一个额外的参数data
:
****# create copy of data drop target column**
data2 = data.copy()
data2.drop('charges', axis=1, inplace=True)**# generate predictions** predictions = predict_model(best_model, data = data2)**
预测模型的输出(最佳模型,数据=数据 2)
👉编写和培训自定义模型
到目前为止,我们看到的是 PyCaret 中所有可用模型的训练和模型选择。然而,PyCaret 为定制模型工作的方式是完全相同的。只要您的估算器与sklearn
API 风格兼容,它就会以同样的方式工作。我们来看几个例子。
在向您展示如何编写自己的定制类之前,我将首先演示如何使用定制的非 sklearn 模型(sklearn 或 pycaret 的基库中没有的模型)。
👉 GPLearn 车型
虽然遗传编程(GP)可以用来执行非常多种多样的任务,gplearn
被有目的地限制于解决符号回归问题。
符号回归是一种机器学习技术,旨在识别最佳描述关系的基础数学表达式。它首先构建一组简单的随机公式来表示已知自变量与其因变量目标之间的关系,以预测新数据。每一代程序都是通过从种群中选择最适合的个体进行遗传操作而从上一代进化而来的。
要使用gplearn
的型号,您必须先安装它:
****# install gplearn** pip install gplearn**
现在您可以简单地导入未训练的模型,并在create_model
函数中传递它:
****# import untrained estimator**
from gplearn.genetic import SymbolicRegressor
sc = SymbolicRegressor()**# train using create_model** sc_trained = create_model(sc)**
create_model 的输出(sc_trained)
**print(sc_trained)**
打印输出(sc_trained)
您还可以检查这方面的坚持分数:
****# check hold-out score** pred_holdout_sc = predict_model(sc_trained)**
预测模型(sc_trained)的输出
👉NGBoost 型号
ngboost 是一个实现自然梯度提升的 Python 库,如“NGBoost:概率预测的自然梯度提升”所述。它建立在 Scikit-Learn 的基础上,在选择适当的评分规则、分布和基础学习者方面设计为可扩展和模块化的。本幻灯片提供了 NGBoost 基础方法的教学介绍。
要使用 ngboost 中的模型,您必须首先安装 ngboost:
****# install ngboost**
pip install ngboost**
安装后,您可以从 ngboost 库中导入未经训练的评估器,并使用create_model
来训练和评估模型:
****# import untrained estimator**
from ngboost import NGBRegressor
ng = NGBRegressor()**# train using create_model** ng_trained = create_model(ng)**
create_model 的输出(ng)
**print(ng_trained)**
打印输出(ng_trained)
👉编写自定义类
上面的两个例子gplearn
和ngboost
是 pycaret 的定制模型,因为它们在默认库中不可用,但是您可以像使用任何其他现成模型一样使用它们。然而,可能有一个用例涉及编写你自己的算法(即算法背后的数学),在这种情况下,你可以从sklearn
继承基类并编写你自己的数学。
让我们创建一个简单的估计器,它在fit
阶段学习target
变量的平均值,并预测所有新数据点的相同平均值,而不考虑 X 输入(可能在现实生活中没有用,只是为了演示功能)。
****# create custom estimator**
import numpy as npfrom sklearn.base import BaseEstimatorclass MyOwnModel(BaseEstimator):
def __init__(self):
self.mean = 0
def fit(self, X, y):
self.mean = y.mean()
return self
def predict(self, X):
return np.array(X.shape[0]*[self.mean])**
现在让我们使用这个估计器进行训练:
****# import MyOwnModel class**
mom = MyOwnModel()**# train using create_model** mom_trained = create_model(mom)**
create_model(mom)的输出
****# generate predictions on data**
predictions = predict_model(mom_trained, data=data)**
predict_model 的输出(mom,data=data)
请注意,Label
列实际上是所有行的预测,是相同的数字$13,225,这是因为我们以这样的方式创建了该算法,它从训练集的平均值中学习并预测相同的值(只是为了保持简单)。
我希望您会喜欢 PyCaret 的易用性和简单性。只需几行代码,您就可以执行端到端的机器学习实验,并编写自己的算法,而无需调整任何本机代码。
即将推出!
下周我将写一篇教程来推进这篇教程。我们将编写一个更复杂的算法,而不仅仅是一个均值预测。我将在下一个教程中介绍一些复杂的概念。请在 Medium 、 LinkedIn 、 Twitter 关注我,获取更多更新。
使用 Python 中的这个轻量级工作流自动化库,您可以实现的目标是无限的。如果你觉得这很有用,请不要忘记给我们 GitHub 库上的⭐️。
要了解更多关于 PyCaret 的信息,请关注我们的 LinkedIn 和 Youtube。
加入我们的休闲频道。此处邀请链接。
您可能还对以下内容感兴趣:
使用 PyCaret 2.0
在 Power BI 中构建您自己的 AutoML 使用 Docker 在 Azure 上部署机器学习管道
在 Google Kubernetes 引擎上部署机器学习管道
在 AWS Fargate 上部署机器学习管道
构建并部署您的第一个机器学习 web 应用
使用 AWS Fargate serverless
部署 PyCaret 和 Streamlit 应用
重要链接
文档
博客
GitHub
stack overflow
安装 PyCaret 笔记本教程 贡献于 PyCaret
想了解某个特定模块?
单击下面的链接查看文档和工作示例。
使用爱因斯坦符号编写更好更快的 Python
使用“einsum”让你的代码更易读、简洁、高效
刘易斯·康的照片在 Unsplash
在 Python 中处理线性或多线性代数时,求和循环和 NumPy 函数可能会变得相当混乱,难以阅读,甚至很慢。在我发现 NumPy 的einsum
功能之前,情况就是这样,我很惊讶不是每个人都在谈论它。
我将向您展示如何使用 NumPy 、 **TensorFlow、**或 PyTorch 中的爱因斯坦符号来使您的代码更具可读性、简洁和高效。
理解爱因斯坦符号
爱因斯坦符号的基础是去掉求和符号σ,当它不会引起歧义时(当我们可以确定指数的界限时)。
例#1:矩阵的乘积
在下面的公式中,矩阵 A 的形状为(m, n)
,矩阵 B 的形状为(n, p)
。
因为我们从矩阵的形状中知道了 I,j 和 k 的界限。我们可以将公式简化为:
例 2:两个向量的点积
两个 n 维向量的点积为:
我们可以用爱因斯坦符号把它写成:
例 3:两个矩阵的点积
我们可以使用以下公式定义两个矩阵的点积:
在爱因斯坦的符号中,这很简单:
例子#4:张量
我们可以使用两个以上的指数。张量(高阶矩阵)。
例如,我们可以这样写:
或者甚至像这样:
你明白了!
什么时候使用爱因斯坦符号?
这主要发生在你处理向量、矩阵和/或张量的时候,你必须:以特定的方式对它们进行乘法、转置和/或求和。
用爱因斯坦符号写出这些运算的组合结果会更简单。
使用 Python 的 einsum
einsum
在numpy
、torch
、tensorflow
中实现。在所有这些模块中,它都遵循语法einsum(equation, operands)
。
这里我们用指数代替 ■ 。在->
之后,我们将输出指数。
这相当于:
如果输入或输出是标量(它没有索引),我们可以让索引为空。
下面是上面的例子。
例子#1:矩阵乘法
einsum("ik,kj->ij", A, B)
示例 2:矢量点积
einsum("i,i->",u, v)
示例#3:矩阵点积
einsum("ij,ij->", A, B)
例子#4:张量
einsum("ijkl,klij->ij", A, B)
einsum("iqrj,klqmr->ijklm", A, B)
你可以用它来处理几乎任何涉及线性代数和多线性代数的公式。
表演
那么与使用循环或 numpy 函数相比,einsum
的性能如何呢?
我决定使用三种方法运行示例#3 :
运行 100 万次测试并使用timeit
后:
- **循环:**24.36 秒
- 内置函数:7.58 秒
- 爱因斯坦:3.78 秒
einsum
显然是更快。实际上,比 numpy 的内置函数快两倍,在这种情况下,比循环快 6 倍。
einsum 为什么快?
这归结为 numpy 是用 c 写的。
当使用本地 Python 循环时,所有的数据操作都发生在 Python 解释器中。
当使用内置的 numpy 函数时,它发生在 C 中,这为 numpy 开发人员提供了优化代码的能力。这就是 numpy 更快的原因。
但在使用einsum
时,numpy 在 C 中处理一次数据并返回最终结果,而使用多个 numpy 函数则花费更多时间返回多个值。
在某些情况下,这可能是一个很好的俏皮话。虽然这不是提高代码可读性和效率的唯一方法,但在可能的情况下,使用它肯定是不需要动脑筋的。
不过,还有其他方法可以优化 Python 代码,比如使用缓存,我将在以后的文章中介绍这一点。
在 Rust 中编写更好的匹配语句
使用闭包来编写更清晰的匹配语句
如果你用 Rust 编程,你肯定用过 match 语句。这是 Rust 中决策的首选方式,还有 if-let 语句。然而,许多初学者在使用 match 语句时可能会有冗余,重复的模式会导致可读性降低和糟糕的代码。
情况
为了理解如何优化匹配语句,从而避免冗余,让我们举一个简单的例子。假设我们正在为一个基本的基于文本的应用程序编写解析用户输入的逻辑,该应用程序添加、删除或获取特定部门的雇员。
实现的五个基本命令是~
1。GET-获取特定部门中所有员工的姓名,如果没有指定部门,则获取公司中所有员工的姓名。
2。添加<姓名> <部门> -向给定部门添加新员工。
3。删除<姓名> <部门> -从部门中删除一个员工。
4。帮助- 显示帮助信息,最后是
5。退出- 退出程序。
考虑到获取、添加和移除命令的逻辑已经被编程。使用它,我们得到了一个接受CmdType
对象的函数。CmdType
枚举如下所示。
解析命令
现在,我们可以快速编写一个输入解析机制,如下所示,用于从标准输入中获取命令。
我们想到的第一个实现是为输入字符串的第一个单词创建一个match
。让我们现在就这样做,然后我们会看到我们如何优化它,以及由此产生的任何问题。
注意,我们没有向
handle_command
传递任何其他参数,因为它是一个已经捕获了变量inp
的闭包,并将其传递给命令逻辑。以这种方式使用闭包可以防止代码重复,并稍微清理代码。
似乎不错?不完全是。使用当前的 match 语句,我们允许 ADD 和 REMOVE 命令,用户可能没有添加这两个参数,现在需要将处理这些参数的代码添加到处理命令的逻辑中,这并不理想。
解决这个问题的一个方法是在运行 add 和 remove 的匹配语句时检查参数的数量,有点像下面的代码。
这段代码的问题很明显。我们正在重复添加和删除命令的模式。重用的一种方法是用"add" | "remove"
替换匹配臂,然后如果参数是正确的,再次检查值,但这也是重复的,因为每个要添加的新命令都需要插入到布尔表达式中。
相反,有两种更好的方法来解决这个问题。
1。在 match 语句中添加 2 个参数要求作为参数。
2。使用闭包来检查参数的数量,并在需要验证时使用闭包。
第二种方法的可伸缩性稍强,因为它不需要对所需的参数数量进行硬编码,但是我们将对这两种解决方案都进行编码。
现在让我们编写第一个方法。为此,我们可以使用第一个单词的元组和命令是否满足 2 个参数的要求,作为匹配参数。
好多了,不是吗!但是,此方法将参数的数量硬编码为 2。如果我们有一个需要三个参数的命令会怎么样?这就引出了我们的最后一个方法。
为此,让我们编写闭包代码。回想一下,闭包应该将命令类型和参数数量作为输入,如果输入符合参数要求,就调用handle_command
。
整洁!现在剩下要做的就是从比赛队伍中调用这个关闭。这看起来有点像下面的。
太神奇了!这段代码与我们的第一段代码非常相似,尽管有不同的闭包和另一个参数。这本身就是对我们最终代码的证明,因为它看起来非常简单,同时也是可伸缩和可实现的。
结论,谢谢!
图片来自作者,使用碳生成
在本文中,我们简要介绍了使用闭包重构匹配语句,并涵盖了边缘情况。我希望你能从这篇文章中学到一些东西,并在以后的文章中写得更干净。为任何反馈留下评论。
感谢您的阅读,祝您有美好的一天!
使用管道编写干净的 Python 代码
一种简洁明了的处理迭代的方法
动机
map
和filter
是处理 iterables 的两种有效的 Python 方法。然而,如果同时使用map
和filter
,代码看起来会很混乱。
作者图片
如果可以使用管道|
在一个可迭代对象上应用多种方法不是很好吗?
作者图片
库管道允许您这样做。
什么是管道?
管道是一个 Python 库,使你能够在 Python 中使用管道。管道(|
)将一个方法的结果传递给另一个方法。
我喜欢 Pipe,因为它使我的代码在对 Python iterable 应用多个方法时看起来更干净。由于 Pipe 只提供了几种方法,所以学习 Pipe 也非常容易。在这篇文章中,我将向你展示一些我认为最有用的方法。
要安装管道,请键入:
pip install pipe
其中—可迭代中的过滤元素
与 SQL 类似,Pipe 的where
方法也可以用来过滤 iterable 中的元素。
作者图片
选择—将函数应用于可迭代对象
select
方法类似于map
方法。select
对 iterable 的每个元素应用一个方法。
在下面的代码中,我使用select
将列表中的每个元素乘以 2。
作者图片
现在,你可能想知道:如果方法where
和select
具有与map
和filter
相同的功能,为什么我们还需要它们?
这是因为您可以使用管道将一个方法插入到另一个方法中。因此,使用管道可以删除嵌套的括号,使代码更具可读性。
作者图片
展开迭代
chain —将一系列可重复项链接起来
使用嵌套的 iterable 可能会很痛苦。幸运的是,您可以使用chain
来链接一系列可重复项。
作者图片
即使在应用了chain
之后 iterable 的嵌套减少了,我们仍然有一个嵌套列表。为了处理深度嵌套的列表,我们可以使用traverse
来代替。
遍历-递归展开迭代
traverse
方法可用于递归展开可重复项。因此,您可以使用此方法将深度嵌套的列表转换为平面列表。
作者图片
让我们将这个方法与select
方法集成在一起,以获取字典的值并展平列表。
作者图片
相当酷!
将列表中的元素分组
有时,使用某个函数对列表中的元素进行分组可能会很有用。用groupby
方法很容易做到这一点。
为了了解这种方法是如何工作的,让我们将一个数字列表转换成一个字典,根据数字是奇数还是偶数对其进行分组。
作者图片
在上面的代码中,我们使用groupby
将数字分组到Even
组和Odd
组。应用此方法后的输出如下所示:
[('Even', <itertools._grouper at 0x7fbea8030550>),
('Odd', <itertools._grouper at 0x7fbea80309a0>)]
接下来,我们使用select
将元组列表转换为字典列表,字典的键是元组中的第一个元素,值是元组中的第二个元素。
[{'Even': [2, 4, 6, 8]}, {'Odd': [1, 3, 5, 7, 9]}]
酷!为了只获取大于 2 的值,我们可以在select
方法中添加where
方法:
作者图片
请注意,输出中不再有2
和1
。
重复数据删除—使用密钥删除重复数据
方法删除了列表中的重复项。
作者图片
这听起来可能没什么意思,因为set
方法可以做同样的事情。但是,这种方法更加灵活,因为它使您能够使用键获得唯一的元素。
例如,您可以使用此方法获取一个小于 5 的唯一元素和另一个大于或等于 5 的唯一元素。
作者图片
现在,让我们将这个方法与select
和where
结合起来,以获得具有重复键和None
值的字典的值。
作者图片
在上面的代码中,我们:
- 移除具有相同
name
的项目 - 获取
count
的值 - 只选择整数。
在几行代码中,我们可以对一个 iterable 应用多种方法,同时仍然保持代码的整洁。很酷,不是吗?
结论
恭喜你!您刚刚学习了如何使用管道来保持代码简洁。我希望这篇文章能给你一些知识,把复杂的迭代运算变成一行简单的代码。
随意发挥,并在这里叉这篇文章的源代码:
https://github.com/khuyentran1401/Data-science/blob/master/productive_tools/pipe.ipynb
我喜欢写一些基本的数据科学概念,并尝试不同的算法和数据科学工具。你可以在 LinkedIn 和 T2 Twitter 上与我联系。
星这个回购如果你想检查我写的所有文章的代码。在 Medium 上关注我,了解我的最新数据科学文章,例如:
</4-pre-commit-plugins-to-automate-code-reviewing-and-formatting-in-python-c80c6d2e9f5> 💔-python-tricks-to-read-create-and-run-multiple-files-automatically-5221ebaad2ba>
用人工智能助手编写高质量的代码
跟上潮流,提高工作效率,并遵循 Tabnine 的最佳实践
你开发新功能的流程是什么?例如,如何向 API 发出新的请求来检索一些关键信息?如何实例化客户端?你用什么方法发布你的 JSON 数据?它的标志是什么?你期望得到什么回报?
全屏显示您的 IDE,开始编写代码,并有一个助手在您的指尖为您提供基于上下文的高质量建议,这不是很好吗?
我猜你的第一个想法是查看库的文档。然后呢?也许可以试着跟随一些教程或者建立几个例子。你也可以在 StackOverflow 停下来复制粘贴,我的意思是,寻找答案,获得一些灵感。
StackOverflow 的愚人节恶作剧——作者截图
然而,这种工作流程会把你扔出那个区域,让你的工作效率降低。此外,您不会从复制粘贴的固定解决方案中学到任何新东西,同时,您会用质量可疑的代码污染您的代码库。
全屏你的 IDE,开始写代码,指尖有一个助手根据上下文给你高质量的建议不是很好吗?
让我们看看世界领先的人工智能代码完成工具如何让我们离最终目标更近一步。
学习率是为那些对 AI 和 MLOps 的世界感到好奇的人准备的时事通讯。你会在每周五收到我关于最新人工智能新闻和文章的更新和想法。订阅这里!
泰伯宁
Tabnine 是世界领先的人工智能代码完成工具,可用于 30 多种语言,受到 100 多万开发人员的信任。
Tabnine —作者图片
Tabnine 的底层模型(主要是 GPT-2 )是在高质量的公共 GitHub 库上训练出来的。然而,如果你选择加入team
计划(每个用户每月 24.00 美元),你可以在你的代码库上个性化和培训他们。
您可以利用它的预测来更快地编写代码,提高生产率,减少错误,并遵循使那些伟大的开源项目大放异彩的编码实践。
Tabnine 在 15 个 ide 上支持 30 多种语言。因此,在大多数情况下,它独立于语言和工具。为此,tabnine 为 Python、JavaScript TypeScript、Java、Ruby、PHP、Go、C++等等提供了代码完成功能。此外,它可以在 VS Code、IntelliJ、Android Studio、Atom、PyCharm、Sublime Text 和任何其他主流 IDE 或代码编辑器上使用。
因此,如果您正在用 tabnine 支持的任何语言编写代码,并且可以在您的 IDE 中安装 tabnine 扩展,那么您可以:
- **跟上潮流:**停止在网上寻找答案,滚动文档页面,或者浏览论坛主题,让你的代码发挥作用。专注于你的工作环境,减少上下文切换。
- **更快地编码:**拥有一个强大的开源库让你更加自信和高效。再也不需要键入整行的代码,记住特定操作的语法,并担心打字错误。
- 发现最佳实践: Tabnine 后端使用 GPT-2,一种可以预测序列中下一个单词的语言模型。使用高质量的 GitHub 库,对 GPT-2 进行了扩展和再利用,以涵盖代码完成的用例。因此,它为您提供了最佳的通用编码实践,因此您可以将精力集中在尚未解决的问题上。
- 保护你的隐私:你写的代码会保存在你的机器上。Tabnine 将模型下载到您的本地环境,并在您的机器上进行预测。如果你愿意,你甚至可以离线工作!
技术细节
正如我们已经看到的,tabnine 使用深度学习,主要是 GPT-2,来执行代码完成。GPT-2 是一个语言模型,这意味着它是在一个大型文本语料库上训练的,以预测句子中的下一个单词。
tabnine 团队通过在高质量的 GitHub repos 上训练它,重新利用它来学习常见的代码习惯用法和模式。tabnine 最强大的模型使用超过 3.8 亿个参数,并将 GPT-2 扩展到编程世界。
因此,tabnine 使用语言模型(如 GPT-2 和语义模型)的组合,根据同一文件中的所有其他内容来预测最有可能的命令。此外,tabnine 模型会定期更新,以跟上公共 GitHub 存储库和其他可靠来源中的新模式。
Tabnine 支持所有主流编程语言:Javascript、Python、Typescript、PHP、Java、C++、C、C#、Objective-C、Go、Rust、Perl、Ruby、Swift、Haskell、Scala、F#、Kotlin、Julia、Lua、SQL 和 Bash。
它还支持最常见的配置语言和 web 技术:JSON、YAML、TOML、HTML、CSS 和 Markdown。
最后,由于 tabnine 使用的模型理解英语,它提供了注释完成支持,这在其他地方是不容易找到的!
结论
编写代码时,上下文切换越多,效率就越低。在一个理想的场景中,您将全屏显示您的 IDE,开始编写代码,并且有一个助手在您的指尖为您提供基于上下文的高质量建议。
这个故事介绍了 tabnine,世界领先的人工智能代码完成工具,可用于 30 多种语言,受到 100 多万开发人员的信任。您可以使用 tabnine 更快地编写代码,提高生产率,减少错误,并遵循让那些伟大的开源项目大放异彩的编码实践。
首先,在 IDE 或代码编辑器中安装 tabnine,然后像往常一样开始编写代码!例如,在这里你可以找到如何在 VS 代码上设置标签的说明。此外,您可以通过它的创建者之一使用 VS 代码来观看这个演练。
关于作者
我的名字是迪米特里斯·波罗普洛斯,我是一名为阿里克托工作的机器学习工程师。我曾为欧洲委员会、欧盟统计局、国际货币基金组织、欧洲央行、经合组织和宜家等主要客户设计和实施过人工智能和软件解决方案。
如果你有兴趣阅读更多关于机器学习、深度学习、数据科学和数据操作的帖子,请关注我的 Medium 、 LinkedIn 或 Twitter 上的 @james2pl 。请访问我的网站上的资源页面,这里有很多好书和顶级课程,开始构建您自己的数据科学课程吧!
用 Python 集合编写超简洁的代码
内置的 Python collections
库是有用工具的宝库。我将把重点放在我认为最有用的两个结构上:Counter
和defaultdict
。理解这些数据结构将有助于你使你的代码更加简洁、易读和易于调试。
计数器
Counter
对象接受一个 iterable 并将项目聚合成 iterable 中唯一值的计数。结果存储在一个类似字典的结构中,其中唯一项是键,计数是值。例如,以下代码获取单词列表并返回每个单词的计数:
from collections import Countertext = "apple banana orange apple apple orange"
counts = Counter(text.split())print(counts.most_common())
# [('apple', 3), ('orange', 2), ('banana', 1)]print(counts['apple'])
# 3print(counts['pear'])
# 0
您可以像使用普通 Python 字典一样从Counter
中检索值。注意,Counter
有一个非常好的特性,如果你查询一个不存在的键(比如上面的‘pear’),它会返回 0 而不是给你一个KeyError
。
Counter
对象的另一个非常有用的特性是它们可以用一个简单的+
操作符合并。这使得合并来自不同位置/文件的项目计数变得轻而易举:
这节省了大量时间和代码行。我在文本处理/NLP 任务中经常使用Counter
,这无疑让我的生活变得更加轻松。以下是使用Counter
的一些最终提示和技巧:
- 使用
dict()
将计数器转换成普通的 Python 字典。 - 使用不带参数的
most_common()
函数返回(item,count)元组列表,按计数降序排序。 - 使用
Counter
计算字符串中的字符数——这很有效,因为字符串在 Python 中是可迭代的。
默认字典
当你不想担心KeyErrors
和特殊情况时,这是基本字典数据结构的一个很好的替代。您只需用您选择的默认值创建一个defaultdict
,数据结构会自动将默认值分配给任何以前看不见的键。需要理解的重要一点是,defaultdict
构造函数的参数应该是callable
。这包括以下内容:
list
:默认为空列表int
:默认为 0lambda
表情:非常灵活,可以让任何东西随时调用set
:默认为空集
这是一个非常有用的数据结构,因为它消除了在增加/修改一个条目的值之前检查它是否存在于字典中的需要。
让我们看一个实际的例子,看看什么时候我们可以使用 defaultdict 来编写真正优雅、简洁的代码。下面的代码使用defaultdict
在 5 行代码中从头开始实现了一个二元语法语言模型的训练循环!关于 n-gram 语言模型的更多内容,你可以查看我以前写的一篇文章这里,在这篇文章中我没有使用defaultdict
。注意我们通过使用defaultdict
节省的代码量!
诀窍是在上面的第 6 行嵌套使用defaultdict
。语言模型被训练来学习单词在上下文中的概率。我们希望有一个嵌套的数据结构,其中外层键指定上下文(即在二元模型的情况下是前一个单词),内层键指定当前单词。我们希望能够提出这样的问题:“在训练数据中,单词后面跟着单词 cat 出现了多少次?
请注意,内部的defaultdict
实际上只是在做与Counter
完全相同的事情,所以我们可以用下面的代码行替换上面的代码行,得到相同的结果:
self.d = defaultdict(Counter)
结论
谢谢你读到这里!我希望你在下一个 Python 项目中尝试使用Counter
和defaultdict
结构。如果你有其他好的用例,请在评论中告诉我!如果您对此感兴趣,请查看我的其他 Python 相关文章:
</7-essential-python-skills-for-research-496e1888e7c2>
撰写您自己的令人惊叹的数据科学博客
给我的数据初学者的建议
帮助你开始旅程的 5 大技巧
源不溅
问题陈述
我对机器学习特别是深度学习感兴趣,也在谷歌工作。当你说你被谷歌采访是因为你在 Medium 上有一个博客时,也许很快我会开始我的 Medium 博客。
如果我可以问的话,你有一些关于如何开始在媒体上写博客的建议吗?再次感谢你接受我的请求,祝你有美好的一天。
—一位富有灵感的数据科学家
2019 年,作为一个新手,开始写博客。我以前是云系统软件工程师,没有机器学习和数据科学方面的经验。但是我还是开始写博客,通过阅读和分享我的知识来学习。
写了 3 年博客后,我觉得写作给了我被动收入、更好的沟通软技能,以及在谷歌宝贵的职业机会。
从那以后,我也收到了大量关于如何开博客和积累专业知识的问题。
总的来说,我想和你分享五个小技巧:
- 沟通:了解你的受众
- 交付:教育和激励
- 节奏:调整自己的节奏
- 共享:识别分发内容的渠道
- 庆祝:产生技能、品牌、收入
沟通:了解你的受众
你的读者是你写作的关键原因。
每一个写作和交流都需要一个听众。如何从一个到另一个取决于谁是观众。
这意味着一篇有如此多方程式和公式的过于技术性的文章可能不为该领域的一些新手所接受。这就是为什么专家会根据不同受众的专业知识水平来识别不同复杂程度的重要概念。
因此,知道你在为谁写作,并据此制定你的成功标准是至关重要的。
一般来说,这些是你写作的关键受众:
- 你自己:分享学习之旅、集思广益和未来有影响力的项目。
- 团队成员/同事:确定协作点、升级和系统特定影响。为同事写作的一个好例子是设计文档,它是指导你的数据项目的概念灯塔。
- 有抱负的/专业的数据从业者:传授相关的技巧和诀窍,以帮助有抱负的数据从业者。比如这个博客就是为了指导你写数据科学博客而创建的。
就我而言,我的数据科学博客的最大受众是 25-35 岁的年轻人,他们勇敢地迈出了涉足数据科学世界的第一步,并积累了数据科学和技术的各种专业知识。
当我 25 岁的时候,我面临着类似的问题。因此,我创建了这个博客,以帮助有类似需求的人在数据科学职业生涯中导航。
同样,你应该知道你在和谁一起建设,以及你为建设你的博客所提供的价值。
交付:教育和激励
教育为可能性铺路,而灵感推动影响
一般来说,写作有两个目标:
- 教育:分享知识,创造可能性,避免陷阱,减少时间沉淀。
- 激发:分享趣闻轶事和故事,煽动观众行动起来。
记住你的读者时间有限。他们来看你的文章是因为你能带给他们的价值。
例如,我给我的读者简单的指示,让他们在 10 分钟内创建一个谷歌数据工作室仪表板。在我的分析工具箱中,我展示了代码片段,供读者快速启动他们的数据科学项目。我还创造了一个关于员工保留的数据驱动故事,意在教育和激励员工。
这意味着,如果你能从你的指令(教育)中节省读者的时间,同时将行动转化为真正的结果(灵感),你就是成功的。
节奏:调整自己
决定你想多久发送一次
决定你发布博客和文章的频率。这可以是每月一次、每两周一次、每周一次,甚至每天一次。
我见过许多新手每天和每周都发布文章,但是过一会儿就停止发布了。对我来说,我每周写一次,但由于其他生活优先事项,我经常拨回每月。重要的不是你写了多少次,而是你将如何按照一个管道或时间表去写。
例如,我的写作管道如下:
- 头脑风暴:思考对我的听众可能有用并且与我的案例相关的话题。例如,当我使用 tmux 时,我写了关于 tmux 的文章,并认为这将帮助其他人提高他们的生产力。
- 自由写作:我把我所知道的关于某个话题的所有观点都写下来,并把它们组织起来,没有任何事先的批评或太多的思考。主要目的是让你的思路不受任何评判地自由驰骋。
- 我进一步在我的文章中加入了更多的想法,这进一步支持了我的观点。
- **回顾:**我让我的一两个朋友调查这件事,并给了我反馈。这帮助我发现了我的盲点并修复了它。
- **校对:**我修正了关键的语法错误以增加可读性。
- **启动:**我在周六或周一启动它,以便在第二天休息时完成管道。最近,我一直在周一发布我的博客,以吸引工作日的读者。
我的总体写作计划(可能因繁忙程度而异)
总之,你可以创建一个写作管道,并随着你生活重心的改变而调整节奏。
就像跑马拉松一样,你不能一直冲刺,相反,你需要调整你的速度并保持你的耐力。暂时停下来关注生活中的其他优先事项是可以的。
共享:确定分发内容的渠道
决定你想如何传授你的知识。
在你喜欢的任何平台上写作。理解你写作的核心价值和目标。
由于我总是在媒体上写作,我想在这里发表一些关于写作的建议。
媒体博客和个人博客写作的利弊
在我看来,你可以从 Medium 开始快速启动,然后一旦你达到一定的势头和读者数量,就在你自己的博客上投入更多的时间。这可以让你平坦的学习曲线,开始更简单,直到你建立你的写作管道和声誉,然后再去主持你自己的网站。
庆祝:创造技能、品牌和收入
写博客如何帮助我成为一名更好的数据科学家并增加我的收入
写作的回报是多方面的:
- 创造共享影响:除了帮助他人,使用你的技能的最佳方式是什么。知识是上帝赐予我们的礼物。分享的越多,知识越丰富。你分享的越多,钱就会用完,但知识永远不会用完。事实上,它在每次分享后都会扩大。
- 建立你的技能:通过分享,你正在建立你的技能。通过教学,你正在学习超越。
- **培养更好的个人品牌:**这为合作和职业机会铺平了道路。这就是我如何被谷歌挖走的。
**顺便说一下,你也可以创造被动收入:**我目前每月收到 500 美元的博客周刊/月刊,偶尔还有顶级作家的奖金。虽然不多,但是写作的收入足以让我在朝九晚五的工作之外靠写数据科学博客生活。
结论
希望一旦你读到这里,你知道如何开始你的旅程。如果你有进一步的想法,请在评论中告诉我。
- 沟通:了解你的受众
- 交付:教育和激励
- 节奏:调整自己的节奏
- 共享:识别分发内容的渠道
- 庆祝:产生技能、品牌、收入
下一步是什么
如果你想了解更多关于我的写作历程,请随时访问我的以下帖子:
还有更多对你有用的职业指导
最后,感谢 Albert Bingei 和 ranon sim 快速阅读和评论本文。反馈很好!
索利·德奥·格洛丽亚
关于作者
文森特用 ML @ Google 对抗网络滥用。文森特使用高级数据分析、机器学习和软件工程来保护 Chrome 和 Gmail 用户。
除了在谷歌的工作,文森特还是佐治亚理工学院计算机科学硕士校友、三项全能运动员和面向数据科学媒体的特约作家,该媒体在全球拥有 100 万以上的观众,为有志的 ML 和数据从业者提供指导。
最后,请通过 LinkedIn , Medium 或 Youtube 频道 联系文森特
编写自己的 C-extension,将 Python 的速度提高 100 倍
如何编写、编译、打包和导入你自己的超高速 C 模块到 Python 中
这就像在你的卡车上安装喷气发动机一样!(图片由 Y S 在 Unsplash 上拍摄)
Python 是一种非常棒的语言,非常容易掌握,开发速度非常快,而且读起来非常清晰。所有这些好处都是有代价的:与其他一些语言相比,Python 相当慢。在继续弄清楚我们正在试图解决的问题之前,我强烈推荐阅读 这篇文章 。本文的目标是回答这个问题:
如何在不牺牲速度的情况下,结合 Python 的易开发性?
讽刺的答案是用另一种语言重写项目,但这不是我们在这里的目的。你是一名 Python 程序员,已经有很多用 Python 编写的程序,并且只想加快其中一小部分的速度。另外:如果你习惯于用 Python 来写,转换到另一种语言如 C#或 Java 可能会很困难。
我们将结合两个世界的精华:我们用一个用 C 编写的小模块来扩展我们的程序。Python 程序员只需导入这个包,不需要知道一行 C 语言,仍然可以享受 100 倍的速度提升。
将 C 模块导入我们的 Python 程序(图片由(Kat sazo novaonUnsplash)提供)
用 C 写?听起来很难
”用 C 写的!?“我听到你问了。
"你刚刚谈到了向 Java 的粗略过渡,现在我们要转向 C 了?!”。的确,用 C 写代码可能有点挑战性,但是你会发现 100 倍的速度提升绝对是值得的!
此外,我们只需用 C 语言重写一小部分代码(在我们的例子中,只是一个函数)。
这和用另一种语言重新写项目不一样吗?
本文描述的解决方案的美妙之处在于,您只需重写代码中较慢的部分。假设我们用 Python 编写了一个 API 来接收和分析音频文件。用 C 重写分析音频文件的函数是有意义的,因为这是项目的瓶颈。我们会浪费很多时间来重写我们的 API。
这个不能做的简单一点吗?
是的,有更简单的方法来创建 C 扩展,这将大大加快我们的程序。在 这篇文章 中,我们使用 Cython 将一些类似 Python 的代码转换成 C 模块,实现了大致相同的性能提升。
然而,在这篇文章中,我们将努力用 C 编写我们自己的模块,因为它让我们对 Python 的内部工作以及它如何集成用 C 编写的模块有了一个非常有趣的了解。
什么时候创建 C 模块有意义?
我们能够优化的任务类型是 CPU 密集型任务,而不是等待响应之类的 I/O 任务。在“更快的语言”中,等待 API 并不会更快。
我们希望优化一小部分执行 CPU 密集型任务的代码。这类任务非常适合在 c 语言中进行优化。
让我们开始工作,加速这个项目(图片由 Damir Kopezhanov 在 Unsplash 上提供)
设置
首先要做的事情是:建立一个虚拟环境。这并不是绝对必要的,但是保持你的依赖关系不混乱是最好的做法。
正如你在前面读到的,我们需要一个做大量计算的函数。我们将使用一个简单的例子:计算一个范围内的素数。下面是实现这一点的普通 Python 代码。
上面的代码看起来有点乱,我听说你认为“ WHILE LOOPS?!旗帜?!”。相信我,他们进去是有原因的。
还要注意,这不是计算质数最有效的方法,但这不是重点:我们只需要一个需要大量计算的函数!
您已经在使用 C 编译的功能
代替 while 循环和标志,我们可以使用内置函数range()
。这通过了生成、迭代,并检查我们是否完成了更快的 C 模块。让我们用range()
升级那个讨厌的功能:
请注意,这段代码不仅可读性更好,而且速度更快。我们可以使用这两个函数来查找 0 到 100.000 之间的素数:
[Vanilla] examined 100000 numbers; found 9592 primes in 30.38632 sec
[V+range] examined 100000 numbers; found 9592 primes in 20.00026 sec
使用一些内置的 C 模块已经稍微提高了执行速度,但是我们才刚刚开始。
我们必须让我们的手有点脏,但结果将是惊人的(图片由 Adi Goldstein 在 Unsplash 上提供)
为 Python 编写 C 模块
我们要做的第一件事是将寻找素数的函数转换成 C 语言。然后,我们必须让 Python 与 C 语言的函数进行对话。这个问题的解决方案是将 C 函数封装在 Python 模块中。
你对这些已经很熟悉了。想想time
、os
和sys
,例如,我们称我们的模块为Fastcount
。
在这一部分的最后,我们将安装我们的模块,以便您可以导入模块并在模块上执行如下方法:
import Fastcount
res = Fastcount.primecounter(0, 100)
print(res)
# 25
我们将分三步完成:
- 用 C 重写求素数函数
- 将 C 函数打包到 Python 模块中
- 构建并安装模块
第一步:C 功能
如果你不熟悉 C 语言,这部分可能有点难。它很像 Python,但有更多的限制。看看这个:
除了这里或那里的一些语法,这个函数看起来很像我们在前一章写的那个讨厌的函数。
2.在模块中包装 C 函数
好,我们有一个用 C 写的函数和一个 Python 文件。我们如何从 Python 文件中访问 C 函数?我们必须采取一些措施:
所有元素如何组合在一起(图片由作者提供)
我们已经在上面定义了我们的 C 函数,所以让我们将 C 函数包装在一个 Python 对象和(黑色)中,并向外展开:
2.1 将 **C-function**
包装成 **Python Object**
。 我们来看一下代码:
记住 Python 中的一切都是对象?事实上 Python 中的一切都是一个PyObject
。即使声明 integer 也会导致一个PyObject
。在幕后,Python 引擎使用这种结构来支持动态类型。
在上面的代码中,我们将 C 函数包装在一个PyObject
中。这个函数使用PyArg_ParseTuple
函数解析 Python 发送给我们的参数。ii
表示我们期望两个整数(更多信息在这里)。
接下来,我们称之为 C-函数,即发现的素数。最后,在我们把它们转换成一个PyLong
之后,我们返回找到的素数;Python 可以解释为类型的对象long
。
2.2 将 **Python Object**
增加到一个 **list of methods**
。下面的代码指定了我们在 Python 中调用的函数名:
在这里,我们定义了模块拥有的所有方法的列表。
在这个例子中,我们将primecounter
定义为函数名;这就是我们在 Python 中称之为函数的东西。然后我们引用上一步中创建PyObject
的函数。METH_VARAGS
定义了签名:它期望来自 Python 的self
和*args
。最后,我们为 docstring 定义了一个方法描述。
如果我们希望我们的模块有更多的功能,我们可以在那里添加更多的对象,但是为了这个演示的目的,我们将保持简单。这个PyMethodDef
也需要 3 号线;包含所有NULL
的行。
2.3 创建 **Module Definition**
并在上面注册模块名称、描述和 **list of methods**
。 下面是代码:
这段代码定义了我们的模块。创建PyModuleDef
需要第一项。在第 4 行和第 5 行,我们指定了模块的名称和描述。
在第 6 行中,我们可以指定存储程序状态所需的内存量。当您的程序在多个子解释器中使用时,这是必需的。
负值表示这个模块不支持子解释器。用一个非负值指定要在每个子解释器会话上分配的模块的内存需求。
第 7 行的最后一项引用了我们在上一步中指定的方法列表。
2.4 创建一个 **Initialization function**
,从 **Module Definition**
创建我们的模块。 最后一块!这是 Python 第一次导入我们的模块时将调用的函数:
我们使用PyModule_Create
并传递给它一个对前一部分的PyModuleDef
的引用。这将返回一个包装了我们的 C 函数的PyObject
。点击查看全部代码。
3.构建、安装和运行扩展
这部分类似于创建您自己的公共或私有 Python 包的过程中的步骤。我们必须创建一个setup.py
文件,该文件指向上一步中的 C 代码,然后创建包。我们走吧:
上面的代码非常简单明了;最重要的一行是第 11 行,在这里我们指定了在哪里可以找到我们在步骤 2 中编写的 C 文件。
下一步:简单地给python setup.py install
打电话。这将把我们所有的代码打包到一个名为 Fastcount 的模块中。现在在 Python 中,我们可以:
故障排除
Windows:调用python setup.py install
可能会得到一个类似如下的错误:
Microsoft Visual C++ 14.0 or greater is required. Get it with "Microsoft C++ Build Tools": [https://visualstudio.microsoft.com/visual-cpp-build-tools](https://visualstudio.microsoft.com/visual-cpp-build-tools)
你可以通过安装 C++构建工具来解决这个问题,你可以在这里下载。
让我们比赛算法(图片由 Jonathan Chng 在 Unsplash 上提供)
标杆管理
让我们测试一下我们的代码;我们想计算 0 到 500.000 之间的质数。为了做到这一点,我们需要检查大约 13 亿个数字;我可怜的笔记本有很多工作要做。我们将进行基准测试:
- 香草蟒
- 普通 Python +内置(比如 range)
- 快速计数(我们的 C 模块)
- 快速计数 MP
在多次执行所有方法并花费最少的时间后,结果就出来了。所有方法都找到了正确的素数(41.538),这是它们花费的时间(越少越好):
查找一个范围内素数的所有方法的执行时间(越低越好,图片由作者提供)
使用像range()
这样的内置函数已经节省了大约 35%的完成时间。尽管这是一个相当不错的增加,但我们自己的模块几乎比普通 Python 完成了这个任务。
为了获得更快的速度,我们通过多重处理我们的函数将所有计算分散到多个 CPU 上,与普通 Python 相比,这个函数完成了我们的计算。查看 这篇文章 或 这篇文章 了解更多关于使用线程和进程在 Python 中安全执行多任务的信息。
结论
这是一篇很长很复杂的文章,但是我们学到了很多关于 Python 如何在幕后工作的知识。我们已经编写、编译、打包并导入了我们自己的定制 C 模块。尽管这篇文章相当长,但最终,我们将执行速度从 10 分钟以上降低到了近 6 秒:减少了 99%的执行时间!
如果你有建议/澄清,请评论,以便我可以改进这篇文章。同时,看看我的其他关于各种编程相关主题的文章:
- Python 为什么这么慢,如何加速
- 【Cython 入门:如何在 Python 中执行>每秒 17 亿次计算
- Python 中的多任务处理:通过同时执行,将程序速度提高 10 倍
- Python 中的高级多任务处理:应用线程池和进程池并进行基准测试
- 用 FastAPI 用 5 行代码创建一个快速自动归档、可维护且易于使用的 Python API
- 创建并发布你自己的 Python 包
- 创建您的定制私有 Python 包,您可以从您的 Git 库 PIP 安装该包
- 绝对初学者的虚拟环境——什么是虚拟环境,如何创建虚拟环境(+示例)
- 通过简单的升级大大提高您的数据库插入速度
编码快乐!
—迈克
页(page 的缩写)学生:比如我正在做的事情?跟我来!
写你自己的朱莉娅
Julia 是一门很棒的语言,但是如果你根据自己的喜好对它进行定制,它会变得更好!
(src =https://pixabay.com/images/id-918449/
介绍
J ulia 是一种编程语言,它带来了各种令人敬畏的可能性。真正让 Julia 语言令人敬畏的一点是,它主要是自己编写的。这意味着大多数 Julia 用户可以解释和重写 Julia 的几乎任何部分,而无需学习任何其他编程语言。既然如此,这意味着我们实际上可以改变朱莉娅做完全不同的事情,并按照我们的想法编程,而不必做任何出格的事情。没有多少其他编程语言可以完成这样的目标,所以 Julia 在这方面确实很酷。今天我想通过一些方法来定制我们的 Julia 安装。这将是新功能的增加,以及改变一些方法来处理不同的类型。
Startup.jl
每次实例化 Julia 时,它都会运行一系列文件,这些文件被加载到新的 Julia 环境中。其中一个很好的例子就是 Base.jl 文件。Julia 库中所有可用的方法和类型都存储在这里,无论何时启动 Julia,它们都会自动存在以供使用。为了改变或增加 Julia 语言已经拥有的功能,您可能有很多原因想要这样做。
Julia 记住,用户可能希望在启动时对他们的环境进行一些更改,并为我们提供了使用 startup.jl 进行更改的能力。在 Linux 和其他类似 Unix 的系统上,这将是~/.julia。julia/config 路径不存在,那么您可以简单地创建它:
shell> cd ~/.julia
/home/emmett/.juliashell> ls
artifacts compiled environments makiegallery pluto_notebooks registries
clones conda logs packages prefs scratchspacesshell> mkdir configshell> cd config
/home/emmett/.julia/configshell> nano startup.jlshell>
改变朱莉娅
现在我们有了 startup.jl,我们可以使用 dispatch 修改 Julia 中的任何代码。我写了一篇文章,详细介绍了我们将要对其他一些类型做些什么,我认为这肯定值得一读。它更详细地介绍了在 Julia 中使用类型和分派,我认为代码很好地展示了 Julia 的全部内容:
我将在其中添加一些代码,根据我的喜好稍微改变一下 Julia。对于这个定制,我决定对加法运算符+进行修改。每当我们通过这个操作符传递两个数组时,我们都会得到一个元素相加的返回。这很奇怪,因为还有另一个操作符,元素相加操作符。+.很容易理解为什么这是他们附带的功能,我想代码应该是这样的:
+(x::Array, y::Array) = .+(x::Array, y::Array)
但这不是我想要它做的。当然,加法运算符不仅用作数字加法的符号,也用于组合。我认为看到这个操作符的另一个用途,组合数组,会很酷。为此,我们只需显式导入操作符,然后添加一行 dispatch。
import Base: +
+(x::Array, y::Array) = [x;y]
现在,这将连接我们的两个数组,而不是元素相加。我将这段代码添加到我的 startup.jl 中,现在在我的本地系统上总是这样!
结论
Julia 的一个非常酷的特性是,不管你知道什么,只要你知道语言本身,你就能在最底层使用它。它本身就是写出来的,真的很酷。我们可以通过简单地使用 dispatch 非常快速地改变不同基础组件的功能,这真的很酷。虽然我的例子非常简单,但是可以用其他类型和方法来做得更好。我认为这甚至对&&位操作符也有意义,我认为它可以产生一些非常棒的结果。感谢你的阅读,我希望这有助于使朱莉娅成为你自己!
py 使用 Python 在 30 分钟内编写一个命令行界面模拟游戏
用一些简单的 Python 代码在几分钟内制作的快速游戏
(图片由作者提供)
介绍
我非常喜欢给计算机编程,并让编程和软件工程变得如此诱人和有益的一点是,你可以通过一路上创建的数据系统,真正释放你的创造力和对问题的思考。我觉得有时候我有很酷的想法来处理问题,而且通常所有这些想法都可以很好地结合在一起,有时候——没那么好。这当然可以用来说明编码前计划的有效性。
也就是说,在最近写一篇关于 Click 模块的文章时,我决定用一个完整的软件来演示这个模块会很有趣,但是我也发现很难想象我到底想做什么。我认为这将是一个有趣的项目,因为这将是一个伟大的点击模块的应用程序,但我也认为这将是非常有趣的编程。
看一下我的 Github 统计就知道了!
多有问题啊,看来我这辈子都没离开过笔记本。然而,本例中使用的语言有点像骗局,因为它是根据文件大小来确定的——Jupyter 笔记本包含各种不同内容的大量数据,它的代码比普通代码多得多,这就是我的观点。也就是说,Python 只占我在 Github 上的文件大小的 0.08%,我认为我应该着手开始一个 Python 项目。该项目的代码可在以下存储库中找到:
https://github.com/emmettgb/characterclash/tree/main
获得基本视觉效果
今天,我们将创建一个简洁的可视化界面,通过带有 ASCII 艺术的 CLI 来查看游戏的输出…这是本文前半部分的 Github 链接。以下是该分支机构的链接:
https://github.com/emmettgb/characterclash/tree/0.0.1-basic-functionality
让我们首先导入我们所有的依赖项,以及我们可能得到的任何新的依赖项:
import click as clk
from numpy import random as rnd
from time import sleep
from os import system, name
现在让我们开始学习一个基础课程,让我们可以开始制作这个的视觉效果:
class PlayGrid:def __init__(self, players):
self.players = players
我们真正需要初始化这个新类的是一个未来的玩家字典,每当我们加载这个游戏时,我们将在我们的主函数中提供这个字典。现在,我将编写一些未来的函数,随着我们逐步编写这些函数,它们将变得更加有用,让我们来看看结果:
class PlayGrid:def __init__(self, players):
self.players = players
draw_grid()
def update(self, message):
passdef move(dir, speed):
pass
def draw_grid():
pass
像往常一样,我要练习提取技术。如果你想更深入地了解这项技术,以及它将如何应用到这个项目的未来代码中,我有一整篇关于它的文章,你可以在这里阅读——它有助于清理你的代码,使它运行得更好!
[## 更多的方法意味着更好的代码
towardsdatascience.com](/more-methods-means-better-code-1d3b237f6cf2)
回到我们的网格,我还将创建一个单独的函数来绘制一个空网格。因为我们在其他地方不需要这个函数,所以我将私下声明它。现在,我不会太关注任何细节,所以我会画一些空白的地方。我将把这些值存储在一个字典中。字典将包含带有整数索引的字符串,类似于我可能用来处理不同玩家数量的玩家类的方法。
无论如何…如果我先写代码,然后再解释,这将会更好,因为我认为这样的组合可能会更合适,并使系统作为一个整体更有意义。
网格
def empty_grid():
self.grid = dict()
str = ""
for row in range(1, 10):
str = ""
for i in range(1, 10):
str += "`"
self.grid[row] = str
return(self.grid)
我添加到这个类的第一个函数是用来创建一个空版本来添加我们的小玩家的函数。我将简单地用“
"`字符填充一些字符串。我们将能够通过调用 self.grid 字典来索引我们正在处理的实际行,并且我们将能够使用字典字符串值对来分别按 char 设置索引。我们将在另一个网格函数中调用它:
def draw_grid():
self.empty_grid()
请允许我通过一些简单的交互代码来解释这将会是什么,我将把这些代码写入我们的主函数中。在我们通过调用刚刚编写的 empty_grid()函数清空网格之后,我们现在将有一个新的表面可以查看。让我们继续绘制网格,首先用正则表达式创建一个新的打印字符串,返回 0 并跳过当前行。
def draw_grid():
self.empty_grid()
print_s = "\n"
别名 print_s 是 print string 的缩写。我们之前的数据,以及它未来的变化和一系列的正则表达式将成为我们最终的单行打印语句,只提供一种类型,这非常方便。我们将使用这个函数来测试 empty_grid()函数,方法是迭代地连接字符串,然后打印它们。这是我想到的:
def draw_grid():
empty_grid()
print_s = "\n"
for key in self.grid:
print_s = print_s + self.grid[key] + "\n"
return(print_s)def empty_grid():
self.grid = dict()
str = ""
for row in range(1, 10):
str = ""
for i in range(1, 10):
str += "`"
self.grid[row] = str
这两个函数完美地结合在一起,在视觉上改变了我们的类型。我想说的一件事是,如果这没有意义,或者看起来像我们在随机组件上工作,这是对形势的明智看法。目前,这看起来并不多,但是在编程中最大的障碍总是开始。这是您开始软件流程的地方,事物被抽象地定义,以便它们可以适合另一个组件。在许多情况下,程序员可能会选择先做逻辑,再做视觉。然而,在这种情况下,我认为首先处理视觉效果是很有意义的,这样我们就可以根据屏幕上实际需要发生的事情来设计逻辑。毕竟,这个项目的主要组成部分是视觉效果。不管怎样,现在已经完成了,我们要测试这两个函数,以确保它能正常工作。
布局初始化
现在,我们将花一点时间来关注初始化,以及更新整个打印输出所需的函数。这个更新函数现在将调用网格函数。我还必须创建一个清晰的()函数。
def clear():
if name == 'nt':
_ = system('cls')
else:
_ = system('clear')
这个函数是系统化的,并且是全局定义的,因为它的目的是用这个命令快速清除整个 REPL。它只是为相应的终端类型调用系统的 clear 命令。我们问名字是不是 NT,就像在 Windows NT 中一样,如果是就用 cls。如果不是这样,我们使用 clear,因为在大多数情况下,其他系统将是 Unix-line。下面是更新函数:
def update(self, message):
clear()
grid = self.draw_grid()
print(grid)
print(string("\n", message))
这里发生了几件事,首先,任何先前的输出被清除。之后我们在这个函数的作用域里赋一个变量叫做 grid,这个变量就是 self.draw_grid 的返回。我们不需要在这里调用 return,但是在这种情况下这是很方便的,因为我们根本不希望我们的网格在工作的时候被改变。如果我们为这样的定义使用类作用域,那么当这个函数运行时,它可能会在其他地方发生变化。
我们的 init 函数就是用来总结所有这些的。将有更多的功能添加到这将扩展功能,但目前这是这个项目的核心功能。
def __init__(self, players):
self.players = players
update()
我在这里做的只是把类属性 players 赋给提供的参数 players,然后调用 update。现在,让我们全面看看这个类:
class PlayGrid:def __init__(self, players):
self.players = players
self.update("Hello")def update(self, message):
clear()
grid = self.draw_grid()
print(grid)
print(string("\n", message))def draw_grid(self):
self.empty_grid()
print_s = "\n"
for key in self.grid:
print_s = print_s + self.grid[key] + "\n"
return(print_s)def empty_grid(self):
self.grid = dict()
str = ""
for row in range(1, 10):
str = ""
for i in range(1, 10):
str += "`"
self.grid[row] = str
希望里面的一切都是正确的,但我想我们很快就会知道了。让我们运行这个宝贝:
[emmac@fedora CharacterClash]$ python3 character_clash.py
`````````Traceback (most recent call last):
File "/home/emmac/dev/CharacterClash/character_clash.py", line 48, in <module>
main()
File "/home/emmac/dev/CharacterClash/character_clash.py", line 8, in main
game = PlayGrid(players)
File "/home/emmac/dev/CharacterClash/character_clash.py", line 16, in __init__
self.update("Hello")
File "/home/emmac/dev/CharacterClash/character_clash.py", line 22, in update
print(string("\n", message))
```
> 哎哟
我犯了一个严重的“对不起,我是 Julia 程序员”的错误。我们需要使用加法运算符来连接这些字符串:
```
print("\n" + message)`````````
Hello
```
# 演员
幸运的是,这个游戏是关于输出的—
> 在你的网络浏览器中,看着迷失的人工智能灵魂为你的娱乐而战斗到死。
因此,这些类在某种程度上可以完全随机化。这就是作为旁观者的好处,你不必参与其中。因此,对于这个奇观,我们不需要编程任何类型的输入,这使得这个玩家的过程容易得多。在以后的文章中,我将添加命令行参数,并进一步推进这个小项目。
另一件要注意的事情是,我们需要对网格做一些工作,也许是它未来的内容。我在想,如果我们在网格中添加一些不同的角色,它可能看起来更像随机的地面纹理——也就是说,因为它们不是让核心游戏工作所必需的,也许我会在未来的文章中讨论这个问题。让我们继续用所有的标准初始化材料创建一个玩家类,在此之前,我将为这个类列出一些数据值以供参考,以便为每个单独的类获得不同的统计数据:
```
# Sword # bow # assassin
# Classes = ["o/", "o)", "o-"]
# stats = [speed, damage, range, time]stats_dict = {"o/" : [2, 25, 2, 3],
"o)" : [2, 35, 3, 5],
"o-" : [3, 20, 3, 2]}
```
这是我们的基本类:
```
class Player:
def __init__(self, pos):
pass
```
## 加载数据
接下来,我们将把这些数据加载到这个类型中,还有一些其他的默认数据。
```
class Player:
def __init__(self, pos):
self.pos = []
self.health = 100
self.blocking = True
self.attacking = False
type = random.choice(stats_dict.keys())
self.speed = stats_dict[type][1]
```
注意最后两行,首先我得到一个键的数组放入 random.choice 函数,然后产生一个选择,然后我们通过索引该键并从数组中提取值来应用数据。另外,Python 中的索引是从零开始的——所以我刚刚意识到代码中有一个小错误。无论如何,我们将对这些属性中的每一个都这样做:
```
class Player:
def __init__(self, pos):
self.pos = []
self.health = 100
self.blocking = True
self.attacking = False
type = random.choice(stats_dict.keys())
self.speed = stats_dict[type][0]
self.damage = stats_dict[type][1]
self.range = stats_dict[type][2]
self.time = stats_dict[type][3]
```
现在所有的数据都已经初始化了,让我们开始移动实际的字符。虽然我们可以使用 vector two 类型,或者类似的东西——也许可以创建我们自己的,但这不是我在这个例子中要做的。我觉得没有必要,因为索引这个位置向量很容易。查看添加了两个新方法头的完整类:
```
stats_dict = {"o/" : [2, 25, 2, 3],
"o)" : [2, 35, 3, 5],
"o-" : [3, 20, 3, 2]}
class Player:
def __init__(self, pos):
self.pos = []
self.health = 100
self.blocking = True
self.attacking = False
type = random.choice(stats_dict.keys())
self.speed = stats_dict[type][0]
self.damage = stats_dict[type][1]
self.range = stats_dict[type][2]
self.time = stats_dict[type][3]
self.symbol = type def walk(self, pos):
pass def move(self, players):
pass
```
move(players)函数用于获取玩家数组,并基于此做出某种选择。现在,这一切都将被搁置,因为我们现在实际上要回到我们的旧 PlayGrid 类,然后开始映射这些球员的位置。
# 组合元素
我们需要在我们的 PlayGrid 类中定义一个新函数,以便从前面的网格字典中获取并修改它来包含这些字符。好消息是,我们可以简单地使用索引在适当的位置显示我们的玩家。
```
class PlayGrid:
# Essentials
def __init__(self, players):
self.players = players
self.update("Character Crash Game Started")def update(self, message):
clear()
grid = self.draw_grid()
print(grid)
print("\n" + message)# Player Management
def draw_players(grid):
```
## 绘图播放器
我们的新功能 draw_players(grid)将简单地获取玩家及其各自的索引,然后将玩家角色放在这些索引处,取代之前的位置。我们要做的第一件事是决定向哪个方向绘制字符。换句话说,字符是面向右还是面向左:
```
for player in self.players:
# True = right, False = left
if player.facing == True
modifier = 1
else:
modifier = -1
```
我们需要将这个修饰符添加到一个索引中,以确定该值应该在字符的后面还是前面。我们将在玩家类中处理剩下的部分。我们暂时不会做所有的事情,因为我们有一个函数要写。你可能已经注意到我也打开了一个 for 循环。这是至关重要的,因为我们需要单独调用每个球员的数据来做我们需要用它做的工作。
这背后的方法很简单。在字典中,坐标平面的 y 是键。正如我们在 empty_grid()函数中所做的那样,这些只是由一个范围生成器生成的。然后我们有 x,它是字典的值对。记住,要在一个特定的位置设置一个字符,我们需要用我们的 y 键索引字典,这是我们的 pos 列表中的位置 2,然后我们需要用我们的 x 值索引它的返回,这是我们需要替换的字符在我们的字符串中的位置。
```
# [0] = x, [1] = y
newpos = player.pos
x, y = newpos[0]
grid[y][x] = player.symbol[0]
grid[y][x + modifier] = player.symbol[1]
```
最后,我们将返回网格,正如我之前提到的,我们将不再使用 class 属性,因为我们将在最后更新它。
```
def draw_players(grid):
for player in self.players:
# True = right, False = left
if player.facing == True
modifier = 1
else:
modifer = -1
# [0] = x, [1] = y
newpos = player.pos
x, y = newpos[0]
grid[y][x] = player.symbol[0]
grid[y][x + modifier] = player.symbol[1]
return(grid)
```
现在我们已经写好了这个函数,我们要再写一个函数,叫做 make_moves(players)。这个函数将调用我们玩家的移动函数。
```
def make_moves(players):
[player.move(players) for player in players]
return
```
从这个意义上说,移动不像从[x,y]到[x,y],这就是我们 walk()函数的作用。相反,我们的移动功能指定轮到他们做什么。现在,我们将回到我们的球员类,并整理出一个基本的结构,这个东西最初可能会对其环境作出反应。在未来,我将为这个项目实现一个机器学习算法,这将使这个项目变得更酷。
现在,我只想看看一些运动可能是什么样子,记住,我要试着写一个简单的小行走模式:
```
def move(self, players):
if self.blocking == False
self.pos += 1
self.blocking = True
elif self.blocking == True:
self.pos -= 1
self.blocking = Falseself.blocking = False
```
这个基本的小函数只是让我们的玩家在网格上走来走去,让我们稍微包装一下我们的主函数,测试一下数据和显示的关系。
```
def move(self, players):
if self.blocking == False:
self.pos += 1
self.blocking = True
if self.blocking == True:
self.pos -= 1
self.blocking = False# self.blocking = False
```
在不久的将来,唯一保留下来的代码是被注释掉的部分。在未来,这将完全是随机的,直到我在下一篇文章中加入一些人工智能。既然我们在这里,我们不妨添加前面的 facing 属性:
```
class Player:
def __init__(self, pos):
self.pos = []
self.health = 100
self.blocking = True
self.attacking = False
type = random.choice(stats_dict.keys())
self.speed = stats_dict[type][0]
self.damage = stats_dict[type][1]
self.range = stats_dict[type][2]
self.time = stats_dict[type][3]
self.symbol = type
self.facing = True
```
# 到目前为止…
到目前为止,我们已经制作了一个玩家网格和将居住在该网格上的玩家。我们需要看看到目前为止所有的代码是否都有效。我们现在需要做的就是稍微调整一下我们的主函数,将一个玩家添加到我们的玩家列表中。另一个随机注意,我也调整了 REPL 打印输出的尺寸。这意味着网格现在比以前大得多。
```
def main():
players = []
game = PlayGrid(players)
game.update("Hello")
# while len(game.players) < 1:
sleep(2)
```
我们需要在玩家列表中添加一个玩家。这相当简单,我们将创建一个新玩家——毕竟,它目前唯一需要的是一个位置。下面是修改后的 main()函数:
```
def main():
players = []
players.append(Player([5, 6]))
game = PlayGrid(players)
game.update("Up")
# while len(game.players) < 1:
sleep(2)
game.update("Down")
sleep(2)
game.update("Up")
sleep(2)
game.update("Down")
```
希望我没记错!
```
[emmac@fedora CharacterClash]$ python3 character_clash.pyFile "/home/emmac/dev/CharacterClash/character_clash.py", line 43, in draw_players
x, y = newpos[0], newpos[1]
IndexError: list index out of range
```
> 让我们看一看…
问题来自这里:
```
class Player:
def __init__(self, pos):
self.pos = []
```
我的意思是提供 pos,然后把它设置成那样,但是它被设置成一个空列表——有趣。
```
File "/home/emmac/dev/CharacterClash/character_clash.py", line 44, in draw_players
grid[y][x] = player.symbol[0]
TypeError: 'str' object does not support item assignment
```
哦,糟糕,看起来我解决这个问题的方法是愚蠢的。这可能比预期的要多一点。幸运的是,有一些很好的方法可以解决这个问题,其中一些我可能在我的 Pythonic 标签处理综合指南中提到过,您可以在这里查看:
</essential-python-string-processing-techniques-aa5be43a4f1f>
> 这次失败的真正原因是,Julia 允许你做任何事情,所以如果我想替换一个字符串索引,我可以导入并扩展索引方法来实现… Julia 太棒了,它毁了我的这个项目。
# 解决我们的问题
为了解决这个问题,我们将通过使它变得非常简单来反抗 Python 之类的东西。我们要做的第一件事是将字符串转换成列表类型。我们知道我们可以设置这种类型的索引,所以我们知道这种方法会有效。然后,我们将使用 str.join()将我们的字符串与新的字符串列表连接起来。
```
>>> list("Hello")
['H', 'e', 'l', 'l', 'o']
>>> "".join(list("Hello"))
'Hello'
>>>
```
让我们回头看看导致这种情况的函数:
```
def draw_players(self, grid):
for player in self.players:
# True = right, False = left
if player.facing == True:
modifier = 1
else:
modifer = -1
# [0] = x, [1] = y
newpos = player.pos
x, y = newpos[0], newpos[1]
grid[y][x] = player.symbol[0]
grid[y][x] + modifier] = player.symbol[1]
return(grid)
```
我们将从将网格转换成 for 循环底部的列表开始:
```
newpos = player.pos
current = list(grid[newpos[1]])
```
现在我们有了 current,这是我们当前列的一个字符串,它是通过获取我们的 y 值获得的,y 值是我们的 newpos 列表中的第二个位置(1,不是 2)。
这是最后一个新函数:
```
def draw_players(self, grid):
for player in self.players:
# True = right, False = left
if player.facing == True:
modifier = 1
else:
modifer = -1
# [0] = x, [1] = y
newpos = player.pos
current = list(grid[newpos[1])current[newpos[0]] = player.symbol[0]
current [newpos + modifier] = player.symbol[2]
current = "".join(current)
grid[y] = current
return(grid)
```
老实说,这里有很多地方我不得不修改,但这里是对该函数的最后一次检查,它现在工作得非常完美:
```
def draw_players(self, grid):
for player in self.players:
# True = right, False = left
if player.facing == True:
modifier = 1
else:
modifer = -1
# [0] = x, [1] = y
newpos = player.pos
current = list(grid[newpos[1]])
current[newpos[0]] = player.symbol[0]
current[newpos[0] + modifier] = player.symbol[1]
current = "".join(current)
self.grid[newpos[1]] = current
```
我也不得不在这里和那里做一些调整,主要是
* 不得不调整播放器的 move()函数,位置正在调用。由于某种原因,它们没有被编入索引。
* 有几个愚蠢的索引错误,还有一些地方我忘了写自己。
* 我必须稍微修改一下主函数,以及 draw_grid()、empty_grid()和 update()函数的返回。
这是我们的新班级:
```
class PlayGrid:
# Essentials
def __init__(self, players):
self.players = players
self.update("Character Clash Game Started")def update(self, message):
clear()
self.empty_grid()
self.draw_players(self.grid)
self.make_moves()
print(self.draw_grid())
print("\n" + message)# Player Management
def draw_players(self, grid):
for player in self.players:
# True = right, False = left
if player.facing == True:
modifier = 1
else:
modifer = -1
# [0] = x, [1] = y
newpos = player.pos
current = list(grid[newpos[1]])
current[newpos[0]] = player.symbol[0]
current[newpos[0] + modifier] = player.symbol[1]
current = "".join(current)
self.grid[newpos[1]] = currentdef make_moves(self):
[player.move(self.players) for player in self.players]# Grid
def draw_grid(self):
print_s = "\n"
for key in self.grid:
print_s = print_s + self.grid[key] + "\n"
return(print_s)def empty_grid(self):
self.grid = dict()
str = ""
for row in range(1, 30):
str = ""
for i in range(1, 100):
str += "`"
self.grid[row] = str
```
这是我们新的 main()函数:
```
def main():
players = []
players.append(Player([5, 6]))
game = PlayGrid(players)
game.update("Up")
# while len(game.players) < 1:
sleep(2)
game.update("Down")
sleep(2)
game.update("Up")
sleep(2)
game.update("Down")
```
现在让我们运行它!
```
[emmac@fedora CharacterClash]$ python3 character_clash.py
```

> 还不错!
# 运动/寻路
我们需要克服的下一个大障碍是运动。我们如何让角色决定如何在每一帧上移动?好吧,我们将从在课堂上加入一些新的数据开始,来表明我们周围世界的一些事情。每当我为这个项目编写一些人工智能程序时,这些都会成为我们模型的特征。在我们深入研究之前,我还想提一件事——到目前为止,该项目的代码在核心功能分支中,我们现在将转移到战斗分支。这个分支将会更加专注于移动和战斗,这样我们的新外形将会真正的发挥作用。
这是我们之前工作过的分支的链接:
<https://github.com/emmettgb/characterclash/tree/0.0.1-basic-functionality>
这里有一个链接指向我们现在所在的网站:
<https://github.com/emmettgb/characterclash/tree/0.0.2-combat>
让我们回到移动函数:
```
def move(self, players):
if self.blocking == False:
self.pos[1] -= 1
self.blocking = True
elif self.blocking == True:
self.pos[1] += 1
self.blocking = False
```
如前所述,我们可以删除所有这些代码,除了将 blocking 设置为 false 的第一件事,如果玩家决定阻止,可以在最后将其切换回来。
## 移动()
```
def move(self, players):
self.blocking = False
```
我们需要做的第一件事是评估其他玩家的位置,以及我们作为玩家的状态。这个类可以帮我们做到这一点,所以我添加了更多的属性:
```
class Player:
def __init__(self, pos):
self.pos = pos
self.health = 100
self.blocking = True
self.attacking = False
type = random.choice(["o/", "o)", "o-"])
self.speed = stats_dict[type][0]
self.damage = stats_dict[type][1]
self.range = stats_dict[type][2]
self.time = stats_dict[type][3]
self.symbol = type
self.facing = True
self.attacking = False
self.pursuing = None
self.attackavailable = False
self.attacks_available = []
self.turns = 0
```
self.turns 值将在 turn 系统中发挥作用,我们将在此之后为其创建一个经理。一旦我们到了那里,我们将详细讨论这个问题。现在,让我们把重点放在指导这些玩家做什么的功能上:
```
def move(self, players):
self.blocking = False
self.attacking = False
selection = 1
selections = []
param = ""
```
第一件事是初始化一些变量。有很多这样的方法,但有一个很好的理由——这是一种创建一些基于条件的行为的简单方法,但该算法肯定是有意义的,并且有可能被扩展。一旦该说的都说了,该做的都做了,我打算让这个类调用 AI。我还添加了将攻击设置为假,因为如果我们现在移动,我们不能做任何一件事——当我们回顾核心游戏规则和管理系统如何工作时,这可能更有意义。
接下来,我们将进入一个奇怪的迭代循环,它只需要评估事物,以得出三个结论之一,走到某个地方,阻止或攻击某个东西:
```
for player in players:
if player.pos[1] == self.pos[1] and player.pos[0] == self.pos[0]:
pass
else:
if attackavailable == True:
# walk = 1, block = 2, attack = 3
if self.health > 45 and index in attacks_available:
if player.attacking == True:
selection = 3
self.pursuing = index
else:
if player.health > 35 and self.health < 50:
self.pursuing = player.pos
selection = 2
else:
selection = random.choice([1, 2, 3])
selections.append(selection)
```
那里的格式转换很糟糕,但仍然清晰可辨,只是不要把这种缩进当成现实。这个循环也很可怕,而且它出现在主事件循环中有点吓人,但是我怀疑我们会遇到很多这样的问题,此外,这只是我将在以后的文章中做的一些迭代工作的临时占位符。无论如何,接下来我要对选择进行舍入,得到一组选择的平均值。
```
mu = sum(selections) / len(selections)
selection = int(round(mu))
```
最后,我会对每个决策进行函数调用:
```
if selection == 1:
if self.pursuing != None:
self.pursue()
else:
self.random_walk()
if selection == 2:
pass
if selection == 3:
pass
```
这也使得机器学习部分主要只是猜测分类特征,尽管只有一个参数。目前,我们所有可能被调用或实际执行的操作将是 random_walk()方法,这是我刚刚编写的——然而,我实际上并没有添加 pursue()函数,这是一个原因,我想用一秒钟的时间在这里展示,但首先让我们看看 random_walk 函数:
```
def random_walk(self):
# 1 r, 2 l, 3 up, 4, down
dir = random.choice([1, 2, 3, 4])
if dir == 1:
self.pos[0] += self.speed
elif dir == 2:
self.pos[0] -= self.speed
elif dir == 3:
self.pos[1] += self.speed
self.turns = 1
```
这个函数所做的基本上就是选择一个随机的方向行走,然后朝那个方向行进。你可能已经注意到了最后的回合功能,每当我们用主控制器完成这个并运行我们的第一个 REPL 中角色间战争的模拟时,这个功能会更有意义。
## 阻挡/攻击
如果您还记得,前面我说过我没有在这个类中添加 pursue()函数。我这样做的原因是为了测试追求价值的保真度。这是因为无论何时调用该方法,我们都会得到一个错误。然而,我们还需要一个函数来实现这个功能,这个函数就是攻击可用函数。为了开始这个函数,我要写一个和我们之前写的一样的循环。唯一不同的是,这次我想确定一个值是否在攻击范围内,这是我第一次尝试这样的函数:
```
def attack_available(self, players):
for player in players:
if player.pos[0] != self.pos[0] and self.pos[1] != player.pos[1]:
if abs(player.pos[0] - self.pos[0]) <= self.range:
self.attacks_available.append(player.id)
elif abs(player.pos[1] - self.pos[1]) <= self.range:
self.attacks_available.append(player.id)
```
这有些完美,有些不完美。现在,我相信它会很好地为我们服务。现在让我们转到 main()函数,并向我们的输出添加另一个播放器类:
```
def main():
players = []
players.append(Player([5, 6], 1))
players.append(Player([40, 20], 2))
game = PlayGrid(players)
for i in range(1, 25):
sleep(3)
game.update("".join(["Iteration: ", str(i)]))
```
这段代码运行完美。现在让我们稍微润色一下。
## 润色
我决定放弃任何级别的路径寻找,并期待将人工智能放在它的位置上,因为代码相当粗糙,也不是真的需要。所有这一切意味着,就目前而言,这些角色没有遵循任何策略,除了随机性。我想复习一下我做的修饰。首先,我重写了行走函数,包括随机行走和行走函数。
```
def random_walk(self):
# 1 r, 2 l, 3 up, 4, down
dir = random.choice([1, 2, 3, 4])
self.walk(dir)def walk(self, dir):
if dir == 1:
if not self.pos[0] + self.speed >= CHAR_H - 1:
self.pos[0] += self.speed
self.facing = Trueelif dir == 2:
if not self.pos[0] - self.speed <= 2:
self.pos[0] -= self.speed
self.facing = Falseelif dir == 3:
if not self.pos[0] - self.speed <= 2:
self.pos[1] -= self.speed
elif dir == 4:
if not self.pos[0] + self.speed >= CHAR_W - 1:
self.pos[1] += self.speed
self.turns += 1
```
在这个函数中,我还必须添加一个条件来确保字符不会离开边缘。最后,我更新了主函数,它现在将运行 50 步棋,假设所有 50 步棋都有效,那么这应该是一个工作项目!这是最后一次查看这些类和 main()函数:
```
def main():
players = []
players.append(Player([11, 20], 0))
players.append(Player([5, 6], 1))
players.append(Player([20, 10], 2))
game = PlayGrid(players)
for i in range(1, 50):
sleep(.5)
game.update("".join(["Iteration: ", str(i)]))class PlayGrid:
# Essentials
def __init__(self, players):
self.players = players
self.update("Character Clash Game Started")def update(self, message):
clear()
self.empty_grid()
self.draw_players(self.grid)
self.make_moves()
print(self.draw_grid())
print("\n" + message)# Player Management
def draw_players(self, grid):
for player in self.players:
modifier = 0
# True = right, False = left
if player.facing == True:
modifier = 1
else:
modifer = -1
# [0] = x, [1] = y
newpos = player.pos
current = list(grid[newpos[1]])
current[newpos[0]] = player.symbol[0]
current[newpos[0] + modifier] = player.symbol[1]
current = "".join(current)
self.grid[newpos[1]] = currentdef make_moves(self):
[player.move(self.players) for player in self.players]# Grid
def draw_grid(self):
print_s = "\n"
for key in self.grid:
print_s = print_s + self.grid[key] + "\n"
return(print_s)def empty_grid(self):
self.grid = dict()
str = ""
for row in range(1, CHAR_W):
str = ""
for i in range(1, CHAR_H):
str += "`"
self.grid[row] = str# Sword # bow # assassin
# Classes = ["o/", "o)", "o-"]
# stats = [speed, damage, range, time]stats_dict = {"o/" : [2, 25, 2, 3],
"o)" : [2, 35, 3, 4],
"o-" : [3, 20, 1, 2]}
class Player:
def __init__(self, pos, id):
self.id = id
self.pos = pos
self.health = 100
self.blocking = True
self.attacking = False
type = random.choice(["o/", "o)", "o-"])
self.speed = stats_dict[type][0]
self.damage = stats_dict[type][1]
self.range = stats_dict[type][2]
self.time = stats_dict[type][3]
self.symbol = type
self.facing = True
self.attacking = False
self.pursuing = None
self.attackavailable = False
self.attacks_available = []
self.turns = 0
# Base
def random_walk(self):
# 1 r, 2 l, 3 up, 4, down
dir = random.choice([1, 2, 3, 4])
self.walk(dir)def walk(self, dir):
if dir == 1:
if not self.pos[0] + self.speed >= CHAR_H - 1:
self.pos[0] += self.speed
self.facing = Trueelif dir == 2:
if not self.pos[0] - self.speed <= 2:
self.pos[0] -= self.speed
self.facing = Falseelif dir == 3:
if not self.pos[0] - self.speed <= 2:
self.pos[1] -= self.speed
elif dir == 4:
if not self.pos[0] + self.speed >= CHAR_W - 1:
self.pos[1] += self.speed
self.turns += 1# Behaviors
def attack_available(self, players):
for player in players:
if player.pos[0] != self.pos[0] and self.pos[1] != player.pos[1]:
if abs(player.pos[0] - self.pos[0]) <= self.range:
self.attacks_available.append(player.id)
elif abs(player.pos[1] - self.pos[1]) <= self.range:
self.attacks_available.append(player.id)def move(self, players):
self.attacks_available = []
self.blocking = False
self.attacking = False
self.attack_available(players)
selection = 1
selections = [1, 1, 1, 1, 2, 2]
if len(self.attacks_available) > 0:
selections.append(3)
selection = random.choice(selections)
if selection == 1:
if self.pursuing != None:
self.pursue()
else:
self.random_walk()
if selection == 2:
self.blocking = True
self.turns += 1
if selection == 3:
self.attacking = True
self.call_attack()
def call_attack(self):
pass
def pursue(self):
pass
```
# 结论
我发现这个项目非常有趣和令人兴奋,我希望那些阅读的人也一样。我想通过构建这个软件来展示这么多随机的很酷的东西,但总的来说,我认为做这样的事情然后交流它们只是娱乐性的。这个项目肯定是一个了不起的项目。
当我们继续这部分的工作时,这段代码所需要的只是一些攻击代码,以及一个运行这些攻击的管理器,以及其他与游戏逻辑规则相比较的东西。非常感谢你的阅读,我希望这个项目对你来说是尽可能愉快的,我希望你对我将要把它进行到的长度感到兴奋!祝你有美好的一天!
> 还有一件事,这是我们创作的 GIF:
