TowardsDataScience-博客中文翻译-2022-四十二-
TowardsDataScience 博客中文翻译 2022(四十二)
Python 常量-您需要知道的一切
原文:https://towardsdatascience.com/python-constants-everything-you-need-to-know-c3fc66609821
Python 有常量吗?阅读来找出答案。

Python 常量有助于设计架构的动态和可更新特性,这对于任何编码架构都是必不可少的。提供具有这些特性的构造与代码块可以被其他人理解和开发的事实有关。
为了满足这些多方面的条件,代码中的表达式应该清晰、明确、简单。编程语言有许多有用的表达式来提供这种灵活性。在本文中——常量——是其中之一,它被详细分析并得到 Python 中编码实现的支持。
Python 中的常量有哪些?
常量是编程语言的组成部分之一,也是为用户提供必要灵活性的工具之一。顾名思义,常量是允许用户分配值的单位,这些值在定义后不能编辑。
这里棘手的一点是,虽然常数不能被编辑,但它可以被更新。另一方面,有些情况下常量是可变的,下一节将通过例子详细讨论这些情况。常量不可变的 Python 示例如下:
VALUE = "Python"
VALUE[0] = 'X'

图片 1-不支持的项目分配错误(作者图片)
打算把分配给VALUE“xy thon”的单词“Python”的首字母改一下,但是改不了。VALUE被定义为常量,不可编辑。当上面的代码块运行时,会出现错误“TypeError:“str”对象不支持项赋值。”,发生。如果我们想得到单词“Xython ”,我们需要给它赋值,就像我们定义单词“Python”的VALUE。也可以通过将常量“Python”赋给VALUE来将其更新为“Xython”。
常数对模型发展的促进作用也是不可否认的。某个数字、文本、列表或元组可以在程序中的多个地方使用。
例如,假设我们将从本地或互联网上的不同链接导入多个数据集。在这种情况下,在每个操作中,这个链接必须单独引入到要重新导入的命令中。但是,如果将这个链接赋给某个常量,并且在运行中需要链接的地方编写这个表达式,那么只改变这个赋给的链接就足够了,而不用逐个改变整个代码块中的数据。这也将使代码系统更容易理解和管理。
常量通常被赋给由大写字母组成的表达式,当然它们不是强制性的,但它们就像是开发人员随着时间的推移而发展起来的一种文化。
类似地,像“int”或“float”这样的值,和“str”值一样,可以赋给某些常量,使得编码模型的构建更加高效。例如,图像处理中使用的图像宽度和图像高度等参数在作品中不止一处出现。当这些表达式在运行开始时作为常量分配给IMAGE_WIDTH和IMAGE_HEIGHT时,只改变代码块开始处的值将节省改变整个结构的时间,并提供便利。
在 C 编程语言中,在对数据赋值时,是通过添加“int”等表达式来定义的,即 integer,它决定了数据的类型。另一方面,Python 在这种情况下提供了便利,并且自己分配数据类型。例如:
X = 22
print(type(X))
Y = 22.22
print(type(Y))
VALUE = "Python"
print(type(VALUE))
当通过运行上面的代码块检查常量的类型时,输出是:

图 2 —代码块输出(作者提供的图片)
尽管用户没有指定,但它将数字 22 指定为整数,22.22 指定为浮点数,单词“Python”指定为字符串。当然,这些类型也可以由用户指定,如下所示:
X = float(22)
print(type(X))
Y = str(22.22)
print(type(Y))
print("Length of Y:", len(Y))
当浮点值 22.22 被定义为字符串时,每个对象被定义为一个元素。因为有 4 个“2”值和 1”,长度值为 5:

图 3 —代码块输出(2)(图片由作者提供)
Python 中用于数据类型的常数
有两种类型的对象:可变的(在创建后可以修改(编辑)的对象)和不可变的(在创建后不能修改(编辑)的对象。
常量可变的 Python 数据类型:
- 词典
- 目录
- 一组
构造了一个简单的列表对象类型,并观察到其修改如下:
CONTINENTS = ["Asia", "Europe", "Africa"]
print(CONTINENTS)
CONTINENTS[1] = "Antartica"
print(CONTINENTS)
print(type(CONTINENTS))
上述代码块的输出是:

图 4 —代码块输出(3)(图片由作者提供)
在CONTINENTS名单中把欧洲改成南极洲是成功的。由于 Python 列表对象是可变的,Python 执行操作时没有任何错误。
常量不可变的 Python 数据类型:
- (同 Internationalorganizations)国际组织
- 元组
- 采用双字节对字符进行编码
- 漂浮物
- 线
- 弯曲件
更改 int、float 和 string 类型时,会出现上述错误信息。下面对元组类型进行了同样的操作:
X = (1,2,3)
print(type(X))
X[1] = 25
定义为X的元组类型常量的第二个元素“2”想要替换为“25”。输出如下:

图 5 —代码块输出(4)(图片由作者提供)
这里需要做的是重新定义X = (1, 25, 3)来避免这种错误。
Python 中的命名常量
命名元组结构是一个类类型,它将给定值映射到collections模块下。通过这个映射过程分配的常数可以很容易地通过准备好的操作进行传递。
用一个更具体的例子:假设一节课的小考权重为 30%,期末考试权重为 70%,用namedtuple计算学生的平均分:
from collections import namedtuple
Grade = namedtuple('Grade', 'quiz final_exam')
student_1 = Grade(60.0, 75.0)
student_2 = Grade(60.0, 90.0)
def average_grade(student_ID):
student_ID_average = (student_ID.quiz) * 0.3 + (student_ID.final_exam) * 0.7
return student_ID_average
student_1_average = average_grade(student_1)
student_2_average = average_grade(student_2)
通过与 namedtuple 的映射,Grade被分配测验和final_exam结果。用户以student_1、student_2格式检索这些结果后,如上所述创建average_grade函数。因此,计算平均成绩时,小考占 30%,期末考试占 70%。
Python 类中的常数
编码结构中有两种类型的常量:局部常量和全局常量。如果常量在类和 def 块之外定义,则称为全局常量,如果在类和 def 块之内定义,则称为局部常量。要在一个类中调用另一个类中的常数:
class Value:
constant_f = 30.05
constant_s = "Hello World"
class FinalValue(Value):
def const(self):
print("float constant is:", self.constant_f, "\n","string constant is:", self.constant_s)
value_in_class = FinalValue()
value_in_class.const()
constant_f被赋予浮点值 30.05,而constant_s被赋予字符串值“Hello World”。上面的代码块用于在FinalValue类中调用这个。输出是:

图片 6 —代码块输出(5)(图片由作者提供)
结论
常量结构不仅在 Python 中,而且在所有编程语言中都有非常重要的地位。它使构建的系统更容易理解,使工作更容易。
由于 Python 在语法上是一种用户友好的编程语言,所以它为常量的使用提供了一个非常有利的环境。通过理解常数,可以用更长的代码块完成的操作可以用更少的工作量完成。
你对 Python 中的常量有什么看法?请在下面的评论区告诉我。
喜欢这篇文章吗?成为 中等会员 继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
https://medium.com/@radecicdario/membership
原载于 2022 年 6 月 3 日 https://betterdatascience.com**的 。
Python 上下文管理器 10 分钟—使用“with”关键字
使用示例简化上下文管理器
上下文管理器本质上是产生单个值的生成器。这个主题现在可能对您来说很陌生,但是您以前很可能使用过上下文管理器。最常用的上下文管理器是 Python 内置的打开文件的open命令,通常与关键字with一起使用,
with open("file.txt", "r") as file:
data = file.read()
类似的用法是使用open命令打开文件,读取数据,然后使用close命令关闭文件。为了防止用户忘记关闭文件并释放资源,使用with关键字将利用上下文管理器并自动处理文件关闭。这个例子被称为open-close模式。上下文管理器也可以用于其他模式,如lock-release、change-reset、enter-exit、start-stop、setup-teardown、connect-disconnect。
这篇文章将涉及编写你自己的上下文管理器,高级行为,例如将上下文管理器分配给一个变量,错误处理,接受参数,嵌套上下文管理器,最后是上下文管理器的一些示例用法。
更新 :本文是系列文章的一部分。查看其他“10 分钟内”话题 此处 !
目录
上下文管理器的结构
上下文管理器可以定义为一个函数或一个类。下面是作为功能实现的上下文管理器的结构,
import contextlib
@contextlib.contextmanager
def sample_context():
# Enter context manager
# Performs <do-something-here>
yield
# Exit context manager
with sample_context():
# <do-something-here>
pass
上下文管理器函数使用contextlib.contextmanager装饰器,它将函数转换成上下文管理器。你可以在我的另一篇文章中找到更多关于装饰者的信息。还有一个yield关键字,它现在什么也不做,但是作为命令的分隔符,这些命令将在进入和退出上下文管理器时运行。
为了将上下文管理器实现为一个类,在进入和退出上下文管理器时运行的命令将分别在__enter__和__exit__ dunder 方法中定义。这不会使用任何修饰或yield关键字,这对于面向对象编程爱好者来说可能更容易理解。
class SampleContext:
def __enter__(self):
# Enter context manager
pass
def __exit__(self, *args):
# Exit context manager
pass
with SampleContext():
# <do-something>
pass
在open-close模式的情况下,文件将在进入上下文管理器时打开,在退出上下文管理器时关闭。这样,步骤和复杂性从用户那里抽象出来,并且与文件相关的资源以由用户控制的方式被释放。
上下文管理器是一种函数或类,它为代码运行设置上下文,运行代码,然后移除上下文。上下文管理器最适合用于控制资源的分配和释放。
上下文经理的高级行为
将上下文管理器分配给变量
可以使用关键字as将上下文管理器分配给一个变量,比如with sample_context() as variable。我们可以使用as关键字,仍然运行上一节中的代码,只是变量将是None,因为它是未定义的。
为了定义变量,我们可以对上下文管理器函数使用yield关键字,或者对上下文管理器类使用__enter__方法中的return关键字。
# Context Manager Function
@contextlib.contextmanager
def sample_context_variable():
# Enter context manager
# Performs <do-something>
yield "something"
# Exit context manager
with sample_context_variable() as variable:
# <do-something>
print(variable) # "something"
# Context Manager Class
class SampleContextVariable:
def __enter__(self):
# Enter context manager
return "something"
def __exit__(self, *args):
# Exit context manager
pass
with SampleContextVariable() as variable:
# <do-something>
print(variable) # "something"
用于错误处理的上下文管理器
上下文管理器将遇到错误,例如编码错误、文件无法打开或锁没有释放。因此,最好使用try-except-finally块进行错误处理。这将在下一节用一个例子来说明。
接受参数的上下文管理器
上下文管理器本质上是一个函数或类,应该能够接受参数。我们将定义一个简单的open_file上下文管理器,它使用一个open-close模式来说明过去的三个部分——使用yield将上下文管理器分配给一个变量,使用try-finally块和接受参数的上下文管理器进行错误处理,
import contextlib
@contextlib.contextmanager
def open_file(file_name):
try:
file = open(file_name, "r")
yield file
file.close()
except FileNotFoundError:
raise FileNotFoundError("File cannot be found")
with open_file("file.txt") as file:
data = file.read()
嵌套上下文管理器
最后,可以堆叠上下文管理器,并且可以同时设置多个上下文。唯一需要注意的是,父上下文中的变量可以被子上下文访问,但不能被子上下文访问(根据 scope 的概念),所以要合理地计划嵌套。一个例子是这样的,
with open(path_input) as file_input:
with open(path_output, "w") as file_output:
for line in file_input:
# Read and write lines simultaneously
file_output.write(line)
用法:连接到数据库
可以在一个上下文中建立到数据库的连接,其中可以有一个在进入上下文管理器时连接到数据库的设置脚本和一个在退出上下文管理器时断开数据库连接的拆卸脚本。
在下面的代码片段中,我们定义了一个database上下文管理器。进入上下文管理器后建立数据库连接,然后连接cur返回,代码中的yield关键字作为db_conn接收。最后,使用 teardown 脚本关闭数据库连接。
import contextlib
import psycopg2
@contextlib.contextmanager
def database(params):
print("Connecting to PostgreSQL database...")
# Setup script
conn = psycopg2.connect(**params)
cur = conn.cursor()
try:
yield cur
finally:
# Teardown script
cur.close()
conn.close()
print("Database connection closed.")
db_params = dict(
host="localhost", database="sample_db", user="postgres", password="Abcd1234"
)
with database(db_params) as db_conn:
data = db_conn.execute("SELECT * FROM table")
上面的代码片段可以推广到涉及资源共享的其他用途,比如在多线程场景中实现锁。
用法:捕获打印报表
我发现上下文管理器的另一个有用的用法是在一个列表中捕获所有打印到控制台的打印语句,在那里可以进一步处理该列表或将其保存为一个文件。这在使用外部 Python 包时很有用,在这种情况下,您不能修改源代码,但函数会将大量信息打印到控制台,您希望捕获这些信息或希望阻止这些信息显示出来。
import sys
from io import StringIO
def print_function():
print("Hello")
print("World")
print("New\nLine")
class Capturing(list):
def __enter__(self):
self._stdout = sys.stdout
sys.stdout = StringIO()
self._current_string = sys.stdout
return self
def __exit__(self, *args):
self.extend(self._current_string.getvalue().splitlines())
del self._current_string
sys.stdout = self._stdout
with Capturing() as output:
print_function()
# Nothing will be printed to console since it is captured
print(output)
# ['Hello', 'World', 'New', 'Line']
更新 :从 Python 3.4 开始,可以使用contextlib库中的上下文管理器捕获打印语句。不同之处在于,该方法将打印语句捕获为字符串,而不是字符串列表。
import sys
from io import StringIO
def print_function():
print("Hello")
print("World")
print("New\nLine")
f = StringIO()
with contextlib.redirect_stdout(f):
print_function()
output = f.getvalue()
# output is 'Hello\nWorld\nNew\nLine\n'
希望这篇文章已经介绍了上下文管理器的基础知识,如何创建定制的上下文管理器以及一些示例用法。有许多 Python 内置的上下文管理器和来自 Python 包的上下文管理器,所以在自己实现它之前一定要四处看看,以节省一些时间和精力。
感谢您的阅读!如果你喜欢这篇文章,请随意分享。
相关链接
contextlib文献:https://docs.python.org/3/library/contextlib.html
PostgreSQL Python 文档:https://www . PostgreSQL tutorial . com/PostgreSQL-Python/connect/
捕获打印报表:https://stackoverflow.com/questions/16571150
Python 仪表板生态系统和景观
原文:https://towardsdatascience.com/python-dashboarding-ecosystem-and-landscape-cc055b50c668
Plotly 仪表板,面板,瞧,和 Streamlit

mate usz waca wek 在 Unsplash 上的照片
你是数据科学家还是数据分析师,喜欢通过可视化和仪表板从数据中获得洞察力?你是一个 Python 用户,对应该使用哪个 Python 库感到困惑吗?Python 为可视化数据和构建仪表板提供了许多令人惊叹的工具。可能会很混乱。
之前,我写过一篇关于 Python 可视化前景的博文,涵盖了基本的可视化工具。本文讨论 Python dashboarding 工具。据PyViz.org称,有 11 个库用于创建用户可以与之交互的实时 Python 支持的 web 应用或仪表盘——bokeh、dash、streamlit、panel、gradio、visdom、voila、wave、jupyter-flex、ipyflex 和 bloxs。最流行的仪表板工具是【Plotly Dash】Panel瞧吧 和 Streamlit 。
这些工具之间有什么区别,您应该使用哪种工具?
为了帮助用户做出决定,James Bednar(代表 PyViz,同时也是一个面板维护者)主持了一个 PyData 全球研讨会,每个工具的代表可以讨论和辩论它们的相似之处和不同之处。做出贡献的有 Marc Skov Madsen(用户和面板开发者,awesome-panel.org和awesome-streamlit.org的创建者)、Adrien Treuille(Streamlit 的联合创始人兼首席执行官)、Nicolas Kruchten(Plotly Express 和 Dash 开发者的创建者)和 Sylvain Corlay(Voila 和 Jupyter 核心开发者的创建者)。
PyData talk 讨论的详细比较总结在本文末尾的表格中。根据我从演讲中学到的和我的经验,这里是最大的区别因素。
该工具是否支持您最喜欢的绘图库?
你最喜欢的绘图库有哪些?喜欢用 Matplotlib,Bokeh,Plotly,Hvplot,还是其他?
- Dash 主要与 Plotly 联系在一起。如果你是一个 Plotly 用户,Dash 是你的自然选择。
- Voila 通过 ipywidgets 接口支持各种库,比如 Matplotlib 和 Plotly。对于那些不是 Bokeh 之类的小工具的库来说,Voila 并不能很好地工作。
- Streamlit 和 Panel 支持许多绘图库,包括 Matplotlib、Seaborn、Altair、Plotly、Bokeh、PyDeck、GraphViz 等等。
- 此外,如果您需要在一个情节中进行交互,例如创建相互关联的情节,Panel 为高级交互功能提供了丰富的支持,而 Streamlit 不允许与一个情节进行交互。
你用 Jupyter 笔记本吗?
作为一名数据科学家,我的工作流程通常从 Jupyter 笔记本或 JupyterLab 开始。对我来说,能够直接在 Jupyter 笔记本中与我的绘图进行交互并构建我的仪表板是非常重要的。Panel 和 Voila 与 data science Jupyter 笔记本工作流配合良好,而 Dash 和 Streamlit 则不然。具体来说:
- Panel 和 Voila 完全支持在仪表板中使用 Jupyter 笔记本中的组件,以便轻松地在数据探索和共享结果之间切换。
- Dash 无法与 Jupyter 顺利集成。JupyterDash 确实可以让你在笔记本上编写和显示 Dash 应用,但是需要额外的配置,而且不像 Panel/Voila 那样优雅。
- Streamlit 无法与 Jupyter 顺利集成。
您是否需要构建复杂的应用程序或使用大数据?
有时,我们需要构建复杂的可视化应用程序。例如,我们可能希望我们的应用程序支持多个页面,每个页面显示不同的功能和见解。只有 Panel 和 Dash 对多页应用有很好的坚实支持。Streamlit 一周前刚刚发布了多页面应用支持。
为了可视化大数据,Python 可视化世界经常使用 Datashader 。查看我之前的博客文章关于什么是 Datashader 以及 Datashader 如何工作。Panel 和 Datashader 都来自于 Holoviz 生态系统,并且被设计成可以很好地协同工作。除了 Panel,Dash 和 Voila 还支持 Datashader 进行大数据的服务器端渲染,而 Streamlit 不支持。
您需要支持多少并发用户?
我们经常需要将我们的仪表板发送给许多利益相关者,如果我们的仪表板能够很好地扩展并支持许多并发用户就好了。Dash 伸缩性好;瞧,这是最糟糕的。Steamlit 和 Panel 在中间。具体来说:
- Dash 是无状态的,不为每个访问者在服务器上存储数据,这使得支持许多用户变得简单,但编程却困难得多。
- 瞧,仪表板的每个访问者都需要一个完整的 Python 流程,这就限制了一次只能有几个访问者。
- Streamlit 和 Panel 在中间,默认支持每访问者状态,但也允许缓存和共享状态。
您想与公众分享您的应用吗?
如果你需要与公众分享你的仪表板应用,Steamlit 是最简单的。Streamlit 使用 Streamlit 云提供了简单的部署,因此它可能拥有最好的社区演示和贡献。其他工具都可以使用标准的云提供商共享,但它们需要单独配置。
为了突出上面讨论的要点:
- 该工具是否支持您最喜欢的绘图库?Streamlit 和 Panel 支持许多绘图库。Panel 为高级交互功能提供了丰富的支持。
- 你用 Jupyter 笔记本吗?如果是,那么选择面板或瞧。
- 您是否需要构建复杂的应用程序或使用大数据?如果是,则选择面板或仪表板。
- 您需要支持多少并发用户?如果您的仪表板需要高度可伸缩,请使用 Dash。
- 您想与公众共享您的应用程序吗?为了便于部署,请使用 Streamlit。
我自己最喜欢的 dashboarding 库是面板。我使用 Panel 构建了许多仪表板,因为我喜欢使用任何我喜欢的绘图库,有时需要构建复杂的应用程序,在 Jupyter 和 dashboarding 之间来回切换,通常在公司内部共享应用程序。
我希望本文和下表能够帮助您理解四个主要 Python dashboarding 库之间的异同,并帮助您选择最适合您的用例的库。快乐的仪表板!


作者制作的图像
鸣谢:感谢马克·斯科夫·麦德森、吉姆·贝德纳尔和菲利普·鲁迪格的反馈和支持。
参考:
Python Dashboarding 枪战和摊牌| PyData 全球 2021:https://youtu.be/4a-Db1zhTEw
. . .
2022 年 6 月索菲亚杨
原发布于https://www . anaconda . cloud。
Python 数据工程师面试问题
原文:https://towardsdatascience.com/python-data-engineer-interview-questions-18a218c0bce1
顶级公司为所有初露头角的数据工程师提供的关于 Python 概念和问题的全面指南,帮助他们准备下一次面试。

作者在 Canva 上创建的图片
今天我们将讨论数据工程面试中的 Python 面试问题。本文将涵盖 Python 中解决数据工程面试所需的概念和技能。作为一名数据工程师,你需要精通 SQL 和 Python。本博客将只涉及 Python,但如果你有兴趣了解 SQL,那么有一篇综合文章“ 数据工程师面试问题 ”。
根据对各种编程语言教程在谷歌上搜索频率的分析,Python 在 PYPL 编程语言流行指数中排名第一。对于数据工程和数据科学来说,Python 是许多其他语言中的佼佼者。
数据工程师通常处理各种数据格式,Python 使得处理这些格式变得更加容易。此外,数据工程师需要使用 API 从不同的来源检索数据。通常数据是 JSON 格式的,Python 也使得使用 JSON 更加容易。数据工程师不仅从不同的来源提取数据,还负责处理数据。最著名的数据引擎之一是 Apache spark,如果你懂 Python,你可以很好地使用 Apache Spark,因为他们为它提供了一个 API。Python 在最近一段时间成为了一个数据工程师的必备技能!现在让我们看看怎样才能成为一名优秀的数据工程师。
怎样才能成为一名优秀的数据工程师
数据工程是一个广泛的学科,有许多不同的角色和职责。数据工程活动的最终目的是为业务提供一致的数据流,使组织能够进行数据驱动的决策,并帮助数据科学家/分析师进行机器学习。
这种数据流可以通过许多不同的方式完成,其中一种方式是使用 Python。
一名优秀的数据工程师应该能够:
- 建立和维护数据库系统
- 精通 SQL 和 Python
- 构建数据流管道
- 了解云架构和系统设计概念
- 了解数据库建模
除了技术技能之外,一名优秀的数据工程师还应具备出色的沟通技巧。当你向公司里的非技术人员解释这些概念时,这些技能是特别需要的。现在你知道了什么是一个好的数据工程师,让我们看看数据工程师是如何使用 Python 的。
面向数据工程师的 Python

作者在 Canva 上创建的图像
现在,您已经对优秀数据工程师的素质有了一个大致的了解,让我们看看数据工程师是如何使用 Python 的,以及它的重要性。对于数据工程师来说,Python 最重要的用途是构建数据和分析管道。这些管道从多个来源获取数据,将其转换为可用的格式,然后放入数据湖或数据仓库,以便数据分析师和数据科学家可以使用这些数据。
- 出于许多原因,Python 是数据工程师中流行的语言,因为它易于使用和实用。它是世界上三大主流编程语言之一。事实上,根据 2020 年 11 月 TIOBE 社区的数据,它是第二大最常用的编程语言。
- Python 在机器学习和人工智能中应用广泛。当生产中需要机器学习模型时,数据科学家和数据工程师密切合作。因此,在数据工程师和数据科学家之间有一个共同的语言是一个优点。
- Python 是一种通用语言,它提供了许多用于访问数据库和存储技术的库。这是一种非常流行的运行 ETL 作业的语言。
- Python 在各种应用程序和技术中使用,其中一个应用是在 Apache Spark 等数据引擎中的使用。Spark 有一个 API,数据工程师可以通过它运行 Python 代码。
因此,Python 因其多才多艺的特性而在所有数据工程师中变得非常流行。
现在是时候开始 Python 实践了。StrataScratch 上还有另一篇文章,关注数据科学家的 Python 面试问题。那篇文章有一个全面的 Python 问题列表,集中在一个数据科学访谈上。
Python 数据工程师面试练习题
Python 数据工程师面试问题#1:工资最高的员工
问题链接:https://platform . stratascratch . com/coding/10353-工资最高的工人
问题:

截图来自 StrataScratch
该问题要求您找出工资最高的员工头衔。在这个问题中,有两个表。
工作人员:

标题:

方法:
有两张桌子;工人和职称,它们都可以通过工人标识和工人引用标识连接。为了连接 pandas 中的表,让我们使列名保持一致。让我们使用“重命名”功能将标题表中的名称从 worker_ref_id 更改为 worker_id。
*# Import your libraries*
import pandas as pd
*# Change the columm name*
title_worker_id = title.rename(columns = {'worker_ref_id':'worker_id'})
使用 pandas 的“合并”功能,将工人 ID 上的两个表连接起来,以在同一个数据框中获得 ID 和职称。将连接结果保存到不同的数据框中。
merge_df = pd.merge(worker, title_worker_id, on = 'worker_id')
查看最高工资:
merged_df['salary'].max()
勾选与最高薪资相对应的行:
rows_with_max_salary = merged_df[merged_df['salary'] == merged_df['salary'].max()]
这些问题仅询问员工职称,因此,我们将从上述数据框中仅选择员工职称。
result = rows_with_max_salary['worker_title']
最终代码:
下面是这个 python 数据工程师面试问题的最终代码。请注意,您应该在顶部导入所有相关/必要的库。
*# Import your libraries*
import pandas as pd
*# Change the columm name*
title_worker_id = title.rename(columns = {'worker_ref_id':'worker_id'})merged_df = pd.merge(worker, title_worker_id, on = 'worker_id')*# Get Max Salary*rows_with_max_salary = merged_df[merged_df['salary'] == merged_df['salary'].max()]result = rows_with_max_salary['worker_title']
代码输出:

Python 数据工程师面试问题#2:活动等级
问题链接:https://platform . stratascratch . com/coding/10351-activity-rank
问题:

截图来自 StrataScratch
这个 python 数据工程师面试问题有一个包含 4 个字段的表。
google_gmail_emails

我们需要找到每个用户的电子邮件活动,然后根据他们的电子邮件活动按降序排列所有用户。如果有用户有相同的电子邮件活动,那么按字母顺序排列这些用户。
方法
第一步是计算每个用户的电子邮件活动。我们将使用 pandas 的“分组”功能对用户进行分组,然后统计该用户发送的电子邮件数量。结果将是一个序列,因此我们需要使用“to_frame”函数将其转换为数据帧,然后重置索引。
之后,根据发送的电子邮件总数按降序对数据进行排序,如果任何两个用户发送了相同的电子邮件,则按“发件人用户”按升序排序。为了在代码中包含这一点,我们将使用 sort_values,它将在第一个参数中接受两个变量,在函数的“升序”参数中接受“真/假”值。
*# Import your libraries*
import pandas as pd
*# Start writing code*
df = google_gmail_emails
df1 = df.groupby('from_user').id.count().to_frame('total_emails').reset_index().sort_values(['total_emails','from_user'],ascending=[False,True])
一旦我们根据电子邮件活动和用户对数据进行了分类,就该对数据进行排序了。这类似于在 SQL 中使用 RANK() OVER()函数。Pandas 有一个名为“rank()”的函数,它接受多个参数。
对于 rank()函数,第一个参数是一个等于“first”的方法。rank()函数提供了多种方法,您可以在这里查看详细内容。“First”方法的等级是按照它们在数组中出现的顺序来分配的,这符合我们的需要。
df1['rank'] = df1['total_emails'].rank(method='first',ascending = False)
因此,我们根据发送的电子邮件总数对每个用户进行排名。如果发送的电子邮件数量相等,我们将根据 from_user 字段按字母顺序排序。
最终代码:
为了更好的可读性,不要忘记在代码中包含注释。
*# Import your libraries*
import pandas as pd
*# Start writing code*
df = google_gmail_emails*# Group by from_user*
*# Count IDs*
*# Convert to Data Frame using to_frame*
*# Sort data using sort_values()*
df1 = df.groupby('from_user').id.count().to_frame('total_emails').reset_index().sort_values(['total_emails','from_user'],ascending=[False,True])*# Rank() the data*
df1['rank'] = df1['total_emails'].rank(method='first',ascending = False)
df1
代码输出:

Python 数据工程师面试问题 3:已完成的任务
问题链接:https://platform . stratascratch . com/coding/2096-已完成-任务
问题:

这个问题有两个表格。
Asana_Users

该表包含用户 id 和用户信息,如姓名和公司。
体式 _ 动作

这看起来像一个事件表,其中每一行代表特定用户的一个操作、用户的操作数和操作名称。
方法:
有两张桌子;asana_users 和 asana_tables。所以第一步是连接 user_id 上的两个表。
*# Import your libraries*
import pandas as pd*# Start writing code*
asana_actions.head()df = asana_users.merge(asana_actions, left_on = 'user_id', right_on = 'user_id', how = 'left')
我们只需要关注 ClassPass 公司。因此,只为 ClassPass 过滤该数据帧。
df = df[df['company'] == 'ClassPass']
我们只需要关注 2022 年 1 月,因此过滤数据框架。我们可以在 pandas 'to_period '中使用一个函数。
df = df[df['date'].dt.to_period('m') =="2022-01"]
一旦我们过滤了 ClassPass 和一月份,就该计算每个用户的动作数量了。你可以使用熊猫的 groupby 函数。
df = df.groupby(['user_id','action_name'])['num_actions'].sum().unstack().fillna(0).reset_index()
最后,我们只需要选择两列;user_id 和 CompleteTask。就这么办吧!
df = df[['user_id','CompleteTask']]
最终代码:
*# Import your libraries*
import pandas as pd
*# Start writing code*
asana_actions.head()
df = asana_users.merge(asana_actions, left_on = 'user_id', right_on = 'user_id', how = 'left')
df = df[df['company'] == 'ClassPass']
df = df[df['date'].dt.to_period('m') =="2022-01"]
df = df.groupby(['user_id','action_name'])['num_actions'].sum().unstack().fillna(0).reset_index()
df = df[['user_id','CompleteTask']]
df
代码输出:

Python 数据工程师面试问题#4:每个邮政编码的街道数量
问题链接:https://platform . stratascratch . com/coding/10182-number-of-streets-per-zip-code
问题:

在这个 python 数据工程师面试问题中,我们需要根据问题中给出的一些条件,计算每个邮政编码的街道名称的数量。例如,如果街道名称有多个单词(East Main 可以算作 East),我们需要只计算名称的第一部分。
这个问题有助于你练习拆分和操作文本进行分析。
有一个表格可以解决这个问题:
Sf _ 餐厅 _ 卫生 _ 违规

方法:
我们需要拆分公司地址以获得街道名称的第一部分。例如,“350 百老汇街”街道名称可以使用拆分功能拆分成不同的元素。让我们使用列表理解(Lambda 函数)来创建一个函数。
df = sf_restaurant_health_violationsdf['street'] = df['business_address'].apply(lambda x: x.split(' '))
上述代码的输出将给出一个元素列表。该字符串将被分解成多个元素,这取决于所使用的分隔符的类型。在这种情况下,我们使用“空格”作为分隔符,因此,“百老汇街 350 号”将被转换为列表[“350”、“百老汇”、“St”]。
这个问题提到了不区分大小写的街道名,因此我们需要降低()或提高()所有的街道名。让我们用小写来回答这个问题。此外,列表的第一个元素应该是一个单词,所以在第一个元素是一个数字的情况下,然后我们采取下一个元素。下面是为此实现的代码。
df['street'] = df['business_address'].apply(lambda x: x.split(' ')[1].lower() if str(x.split(' ')[0][0]).isdigit() == True else x.split(' ')[0].lower())
现在找出非唯一街道名称的数量。在 group by 函数之后,数据被转换为一个序列,因此,我们需要 _frame 函数将其转换回数据帧。让我们将街道的数量命名为“n_street ”,并使用 sort_values 函数对其进行降序排序。
result = sf_restaurant_health_violations.groupby('business_postal_code')['street'].nunique().to_frame('n_street').reset_index().sort_values(by='business_postal_code', ascending=True).sort_values(by='n_street',ascending=False)
最终代码:
import pandas as pddf = sf_restaurant_health_violationsdf['street'] = df['business_address'].apply(lambda x: x.split(' ')[1].lower()
if str(x.split(' ')[0][0]).isdigit() == True else x.split(' ')[0].lower())
result = sf_restaurant_health_violations.groupby('business_postal_code')['street'].nunique().to_frame('n_street').reset_index().sort_values(by='business_postal_code', ascending=True).sort_values(by='n_street',ascending=False)
代码输出:

Python 数据工程师面试问题 5:连续几天
问题链接:https://platform . stratascratch . com/coding/2054-continuous-days
问题:

这个问题有一个表格如下。
sf_events

在这个问题中,你需要找到所有连续三天活跃的用户。
方法:
首先,这是 events 表,没有任何惟一的标识符,比如 event_id。因此,让我们使用 pandas 中的 drop_duplicates()函数删除这个表中的重复项。
*# Import your libraries*
import pandas as pd
*# Start writing code*
df = sf_events.drop_duplicates()
在这个表中,我们有用户 id、帐户 id 和日期,但是我们只需要两列作为问题;用户 ID 和日期。让我们只选择这两列,并使用 pandas 的 sort_values()函数对每个用户和每个日期的数据进行升序排序。
df = df[['user_id', 'date']].sort_values(['user_id', 'date'])
在下一步中,您需要考虑一个类似于 SQL 中的 LEAD()或 LAG()的函数。有一个叫做 SHIFT()的函数可以帮助我们移动日期。该函数采用一个称为周期的标量参数,该参数代表在所需轴上移动的次数。这个函数在处理时序数据时非常有用。让我们使用这个函数,通过添加 2 天来创建另一个列,因为我们需要连续 3 天活跃的用户。
df['3_days'] = df['date'] + pd.DateOffset(days=2)
df['shift_3'] = df.groupby('user_id')['date'].shift(-2)
*# Check Output*
df.head()
上述步骤的输出:

现在我们有了所需格式的数据,让我们比较“3_days”和“shift_3”列,看看它们是否相等。如果它们相等,那么我们提取相应的用户 ID,因为这就是问题所要问的。
df[df['shift_3'] == df['3_days']]['user_id']
最终代码:
*# Import your libraries*
import pandas as pd
*# Start writing code*
df = sf_events.drop_duplicates()df = df[['user_id', 'date']].sort_values(['user_id', 'date'])df['3_days'] = df['date'] + pd.DateOffset(days=2)
df['shift_3'] = df.groupby('user_id')['date'].shift(-2)
df[df['shift_3'] == df['3_days']]['user_id']
代码输出:

Python 数据工程师面试问题#6:按床位排列主机
问题链接:https://platform . stratascratch . com/coding/10161-ranking-hosts-by-beds
问题:

提供了一个表格来解决这个问题。在这个问题中,我们需要根据主机列出的床位数对主机进行排名。一个主机可能有多个列表。此外,一些主机可能有相同数量的床位,因此它们的等级应该相同。请查看提供的表格中的示例输出:
Airbnb _ 公寓

方法:
作为第一步,我们需要找到每个主机列出的床位总数。问题提到一个主机可能列出了多个属性,因此,我们需要使用 python 中的 groupby()函数将每个主机的床位数相加,然后将结果转换为数据框
*# Import your libraries*
import pandas as pd*# Start writing code*
result = airbnb_apartments.groupby('host_id')['n_beds'].sum().to_frame('number_of_beds').reset_index()
下一步是根据主机列出的床位数量对主机进行排名。我们已经有了在前面步骤中计算的主机和床位数列。让我们将 rank()函数与 Dense 方法结合使用,因为我们需要为网站上列出的具有相同床位数的主机提供相同的排名。
result['rank'] = result['number_of_beds'].rank(method = 'dense', ascending = False)
最后,我们需要根据排名对数据进行升序排序。让我们使用 sort_values()按照每个主机排名的升序对数据进行排序。
result = result.sort_values(by='rank')
最终代码:
*# Import your libraries*
import pandas as pd*# Start writing code*
result = airbnb_apartments.groupby('host_id')['n_beds'].sum().to_frame('number_of_beds').reset_index()*# Rank*
result['rank'] = result['number_of_beds'].rank(method = 'dense', ascending = False)
result = result.sort_values(by='rank')
代码输出:

Python 数据工程师面试问题 7:昂贵的项目
问题链接:https://platform . stratascratch . com/coding/10301-贵-项目
问题:

这个问题有两个表格。第一个名为“project_title”的表包含项目的名称/标题、项目 ID 及其预算。
ms_projects

下一个表称为“ms_emp_project”,它将员工 ID 映射到每个项目 ID。
ms_emp_projects

该问题要求您计算分配给每个员工的项目预算金额。
方法:
问题中有两个表格,每个表格都有不同的信息。所以第一步是连接项目 ID 上的两个表。我们将使用 merge()函数将这些表连接在一起。结果将是一个数据帧,我们将它保存到一个名为 df 的变量中。
*# Import Libraries*
import pandas as pd
*# Start writing code*
df= pd.merge(ms_projects, ms_emp_projects, how = 'inner',left_on = ['id'], right_on=['project_id'])
下一步是统计每个项目的员工总数。因此,我们将使用 groupby()函数,按照项目标题和预算对数据进行分组,并计算分配给每个项目的员工数量。我们将重置结果数据帧的索引,并将其保存在一个名为 df1 的新变量中。
df1=df.groupby(['title','budget'])['emp_id'].size().reset_index()
下一步,我们将计算每个员工的预算。为此,我们将总预算除以分配给特定项目的员工人数。如问题中所述,我们将把它四舍五入到最接近的整数。
df1['budget_emp_ratio'] = (df1['budget']/df1['emp_id']).round(0)
下一步是根据分配给每个员工的预算按降序对数据进行排序。我们将使用 sort_values()函数来做到这一点,以保持函数的“升序”参数为 FALSE,因为我们需要降序。
df2=df1.sort_values(by='budget_emp_ratio',ascending=False)
最后,问题只提到将每个雇员的职称和预算作为输出,因此,我们将使用 df[[ ]]语法在 pandas 中只选择那些列。
result = df2[["title","budget_emp_ratio"]]
最终代码:
*# Import your libraries*
import pandas as pd*# Start writing code*
df= pd.merge(ms_projects, ms_emp_projects, how = 'inner',left_on = ['id'], right_on=['project_id'])df1=df.groupby(['title','budget'])['emp_id'].size().reset_index()df1['budget_emp_ratio'] = (df1['budget']/df1['emp_id']).round(0)df2=df1.sort_values(by='budget_emp_ratio',ascending=False)result = df2[["title","budget_emp_ratio"]]
代码输出:

Python 编码动手练习到此结束。我们希望你感到自信。如果你需要更多的练习,这里有一个 python 面试问题的综合列表,你可以参考一下。
结论
我们希望你对下一次数据工程面试有个好印象。在本文中,我们研究了数据工程面试的各个方面。我们探讨了如何成为一名优秀的数据工程师,需要哪些技能,并深入研究了 Python 及其对数据工程师的重要性,同时进行了一些实践。
Python 数据科学项目从实验到生产
Python 打包工作流的分步说明

图片由梅尔·普尔在 Unsplash 上拍摄。
动机
生产是机器学习(ML)项目的一个重要里程碑。正如你可能知道的,一旦机器学习算法可以生产,数据科学家必须与 ML 工程师携手合作,在生产中部署该算法。因此,为了增强团队协作并加速进入生产的过程,数据科学家和机器学习工程师都必须了解整个过程。
本文解释了这个过程,详细说明了要遵循的 3 个主要步骤,并介绍了整个 Python 工作流。
实验与生产
首先,让我们澄清实验和生产 ML 的区别。让我们假设我们有一个新项目。例如,一家零售公司希望预测新产品销售的成功。企业所有者为动员数据科学团队调查数据开了绿灯。我们假设技术委员会已经检查了数据是否可用。
在 ML 实验阶段:
- 数据科学家使用笔记本设计 ML 算法,并访问一部分历史数据,如以前销售的产品、客户资料、产品元数据和历史收入。
- 数据是静态的,这意味着由于内存和处理能力的限制,我们只能访问历史原始零售数据的快照。
- 模型训练是最优调整的,意味着ML 模型的参数最大化所选择的评估度量。
- 设计优先级是达到最高整体精度。
- ****代码是非必然版本化的、模块化的、和简单的。
- 此阶段忽略沿袭、处理时间、和并行化。
- 可解释性和公平性通常很好,但并不重要。
- 这个阶段的挑战是如何构思一个高精度算法。
在 ML 生产阶段:
- 开发环境是隔离的、安全的、和可扩展的 (Docker,Kubernetes)。我们可以安全地访问生产零售数据。
- 数据是动态变化的。
- 生产中的模特培训是持续评估和再培训。****
- 设计优先级目标快速推断和良好的可解释性。
- 代码为 、模块化、和简单。
- 自动化,血统,处理时间,和并行化 是必需的。
- ****可解释性和公平性至关重要。
- 这个阶段的挑战是如何让整个系统工作,同时达到令人满意的 ML 结果。
我们可以在张秀坤·克罗伊茨伯格等人的《机器学习操作:概述、定义和架构》的图 4 中看到,一个生产系统是多么复杂。
从实验转向生产
要将在笔记本中设计的 ML 算法投入生产,您必须经历三个一般步骤:

图片由作者提供。
1。包装
这一步指的是preparing the code for production、building a package、uploading the pachakeg to a Python repository、 ( 官方库是 Python 包索引(PyPI))。一旦包在 Python 存储库中可用,就可以下载并安装到生产机器上。
在继续之前,我邀请您查看一下我的文章,它推荐了几个最佳实践。我非常确定所有列出的最佳实践都会对您有所帮助。
代码准备
在构建源代码发行版之前,我们必须为生产准备代码(在笔记本中设计)。因此,我们必须考虑两个方面:代码重构和使用适当工具的代码交付自动化。
代码重构建议:
- 去掉无用的评论。
- 重构部分代码,确保它易于维护。
- 使用 load_XX,preprocess_XX,training,evaluate,export 等功能。
- 为每个函数添加单元测试&注释。
- 将笔记本转换成 python 模块。
代码交付自动化建议:
- 使用 Git 对代码进行版本化。
- 建立持续集成和交付(CI/CD)流程。
- 使用Docker可以快速构建、测试和部署应用程序。参见 我的文章**详细说明如何创建一个 docker 图像。**
- 在每次代码演化时自动运行代码验证(lint 检查、单元测试、安全检查)。
构建 Python 包
你可能会问什么是 Python 包。
A Python package is a collection of Python modules whereas a module is a file that contains Python functions (definitions & statements).
下图显示了打包过程的概述。

图片由作者提供。
Python 包包含:
- 用户开发的 Python 模块
- 数据/资源
- 元数据(版本、包名等。)
- 共享库
要构建 Python 包,你可以使用构建系统[setuptools](https://setuptools.pypa.io/en/latest/). Setuptools 是由 Python 打包权威维护的[distutils](https://docs.python.org/3/library/distutils.html)的扩展。请注意,Python 3.12 不再支持 distutils 构建系统。Setuptools 有很多好处:它易于使用,自动查找包,支持构建隔离,允许修复 setuptools 的版本,并且在 Python 版本之间保持一致。
它可以与 pip 一起安装:
*pip install setuptools*
每个构建系统都需要一个文件描述符,其中包含项目属性,例如元数据、依赖项、项目模块以及要包含在包中的附加工具(如 flake8)的配置。 PEP 518 推荐使用pyproject.toml文件。它代替了[setup.py](https://docs.python.org/fr/3/distutils/setupscript.html)。pyproject.toml 位于项目的根目录中。查看PEP 621了解如何在 pyproject.toml 中写入项目元数据。
找到下一个基本 pyproject.toml 配置:
关于 pyproject.toml 部分的几句话:
project:包含项目元数据build-system:指定构建系统和包的格式,在我们的例子中是 wheeltool.setuptools:定义包含源代码的目录optional-dependencie:保存附加库的列表setuptools.dynamic:指定在构建过程中计算的动态元数据字段。
所有上述属性让我们在使用我们的包时只捆绑用户需要的文件。使用属性的一个优点是生成的包更小。这意味着用户只需下载 pyproject.toml 中列出的所需依赖项。
由构建系统创建的包有两种类型:源代码发行版(sdist)或构建发行版(bdist)。
sdist是平台不可知的,但缺点是我们不能立即使用它。相反,要使用它,我们需要自己构建包。bdist有点复杂,它构建了包,因此我们可以马上使用它。生成的包是特定于平台的,取决于 Python 版本和构建它的操作系统。尽管如此,normPEP 427还是推出了一种新型的 bdist 叫做wheel。车轮分布允许跳过建设阶段,提高安装速度。因此,最好使用轮式组件。****
要构建轮子包,我们需要安装构建包:
pip install build
使用以下命令开始构建过程:
python -m build --wheel
成功的构建将在dist目录中生成一个. whl 文件(例如 template-packaging-python-0 . 1 . 1-py3-none-any . whl ),格式如下:
{library name}-{library version}-{python version}-{aplication binary interface tag}-{platform tag (os:os sdk version:cpu architecture)}
为了将包上传到 PyPI,我们使用了 Twine 。因此,让我们用 pip 安装麻绳:
***pip install twine***
然后,我们使用以下命令将包上传到您的 PyPI:
***twine upload dist/****
现在,该软件包可以安装在目标系统上了。
如果你想避免使用 pip-tools 来安装包,我建议你检查下一代的 Python 打包工具,如poem、Flit、Pipenv、*或其他替代工具。*******
2.部署
建议通过 CI/CDs 自动部署软件包。为此,选择一个基于云的托管服务,如 Gitlab 或其他由贵公司维护的替代服务,如Jenkins或*circle ci。*******
部署本身指的是从 Python Artifactory 中自动检索构建包,并继续在集群中运行的容器中安装该包。
3.班长
最后但同样重要的是,我们需要跟踪数据异常值、偏差和漂移。如果您使用顶点人工智能管道部署模型,您可以使用顶点人工智能监控解决方案,否则请寻找由您的云提供商维护的适当解决方案。
概述
这篇文章强调了实验和生产之间的区别。它展示了将您的 ML 项目转移到生产中所要遵循的三个主要步骤,并详细描述了 Python 打包工作流。
请在我的下一篇文章中继续关注关于部署和监控的更多细节。
感谢您的阅读!
如果你想在收件箱里收到我未来的故事,别忘了订阅。
如果你喜欢阅读我的故事,并想支持我成为一名作家,考虑注册成为一名媒体成员,并获得数以千计的数据工程和数据科学文章。
*****https://medium.com/@anna.bildea/membership
查看我收集的 MLops 文章

MLOps
View list4 stories

*****
Python Decorator:什么,为什么,以及如何
原文:https://towardsdatascience.com/python-decorator-what-why-and-how-f6b2d86e858e

朱莉安娜·马尔他在 Unsplash 上拍摄的照片
Python decorator 是帮助我们高效编码的非常有用的工具。正如我在上一篇文章中提到的,高效编码是我成为更好的数据科学家所遵循的原则之一:
在本文中,我想揭示 Python decorators 的用例,并讨论它们如何对您的日常编程任务有所帮助。
什么是 Python 装饰师
Python decorator 的名字听起来可能很花哨,但本质上,它只不过是一个工具,您可以使用它来定制现有函数的输出,而无需修改函数本身。之所以称之为“ 【装饰者】 是因为你可以把同样的概念应用到,就好像你在包装礼物一样。我们可以在不同的节日用不同的包装纸装饰礼物。即使礼物本身是一样的,我们也可以通过不同的装饰,在不同的场合送出。将这个比喻应用于 python 编程语言,礼物是 python 函数,包装器是 python 装饰器,而您的任务是不同的假日。在本文中,我将讨论 python decorators 的两个主要用例:一个是修改函数,另一个是缓存函数输出以保存未来的运行时。我们将在下一节看到这两个用例的例子。
为什么以及如何使用 Python Decorator
1 -修改功能输出而不修改功能本身
在讨论如何用 Python decorator 修改函数之前,让我们从一个简单的函数开始:
def add_five(x):
return x+5
这个函数只是将输入 x 加 5,为了简单起见,我们假设 x 是数字。

作者代码
我们可以看到,当你调用这个函数时,它产生一个输出,在输入的基础上加 5。这是一个非常简单的函数,操作简单明了。现在,如果我们想使用这个函数,但是对输出应用一个条件,说输出不能大于 10,那该怎么办呢?我们可以在 add_five() 函数的基础上添加以下函数:
def ensure_ceiling(func):
ceiling = 10
def wrapper(x):
value = func(x)
return value if value < ceiling else x
return wrapper
assure _ ceiling()函数接受一个函数作为输入。它首先将上限定义为 10。然后定义一个包装器函数,它接受输入函数的输入 x 并产生一个输出值。然后包装器函数的输出将使用上限条件进行修改。最后,确保上限函数返回包装器函数作为输出。然后我们可以修改 add_five 函数,将其作为确保 _ 上限函数的输入。让我们看看它是如何工作的:

现在,如果我们把 input 设为 3,assure _ ceiling函数会使用里面的包装函数产生一个值,这个值就是 add_five(3) 。包装器函数的输出是 8,因为 8 小于上限 10,这也是确保上限(加五)将返回的值。到目前为止,它的工作方式类似于 add_five() 。如果我们给的初始输入是 6 呢?那么 add_five(6)将是 11,高于 10 的上限。因此,它将返回输入本身,而不是返回 11,6:

我们在这里没有真正使用装饰器,尽管我们已经定义了装饰器函数。使用它的最有效方法是通过下面的代码:

写完确保 _ 上限函数后,我们可以在我们要修饰的函数的正上方用“@”符号“【wrap】”。应用后,我们可以直接调用修饰函数来获得修改后的输出。
2 —缓存之前的运行结果
装饰器也可以作为函数级缓存。使用同一个 add_five() 函数,我们可以在每次运行函数时保存输出,这样当后面给出相同的输入时,就可以快速抓取结果,而不需要再次运行函数。这是非常有用的,尤其是当函数需要很长时间才能运行并且会被重用的时候。让我们看看这里的例子。我们首先定义装饰者:
def ensure_ceiling_cache(func):
ceiling = 10
cache = {}
def wrapper(x):
if x in cache.keys():
print(f’Using cache for {x}’)
else:
print(f’Using function for {x}’)
value = func(x) if func(x) < ceiling else x
cache[x] = value
return cache[x]
return wrapper
在装饰器中,我们包含一个“缓存”字典来保存每次运行的输出,然后定义一个 if/else 语句来决定我们是需要运行函数来获得结果,还是简单地使用缓存的字典。我们来看看它在 add_five() 中的用法:

现在,当我们调用 add_five() 函数时,它不仅会为输出应用一个上限,而且它还有一个保存在装饰器内部的缓存字典。第二次调用 add_five(3) 时,不会使用函数产生结果,只是简单的查找前面的结果是什么。

总之,我们已经使用 python decorator 修改了现有的函数,并缓存了以前的输出,以便在重复运行函数时节省时间。希望带有示例的用例简单易懂。你还遇到过哪些使用 python decorators 的场景?请在下面的评论中告诉我们!
感谢您的阅读!以下是我写的一些关于 Python 编程的其他文章:
我还有一份关于机器学习、统计、概率、案例研究、行为问题的数据科学家面试准备问题清单,如果需要可以查阅一下。
还有我的文章如果感兴趣的话:
https://zzhu17.medium.com/my-blog-posts-gallery-ac6e01fe5cc3
订阅我的电子邮件列表:
https://zzhu17.medium.com/subscribe
注册成为中级会员:
https://zzhu17.medium.com/membership
或者在 YouTube 上关注我:
https://youtube.com/channel/UCMs6go1pvY5OOy1DXVtMo5A
10 分钟后 Python Decorators
原文:https://towardsdatascience.com/python-decorators-in-10-minutes-c8bca1020235
装饰速成课程,包含流行的现实生活中的使用示例
你可能已经注意到在函数的正上方添加了@<something>的代码,比如在类方法的正上方添加了@staticmethod、@classmethod,这些实际上是 Python 的装饰器。Decorators 允许在不修改源代码的情况下扩展现有的函数。
在本文中,我们将通过理解装饰器的结构、探索高级行为(如克服装饰器的缺点、多嵌套装饰器、堆叠装饰器)以及一些实际应用来编写我们自己的 Python 装饰器。
更新 :本文是系列文章的一部分。查看其他“10 分钟内”话题 此处 !
目录
装饰者的结构
Decorators 可以被认为是一个函数包装器,这意味着它接受一个函数作为参数,并返回该函数的修改版本——向其添加扩展或功能。
def sample_decorator(func):
def wrapper(*args, **kwargs):
# do something before function execution
result = func(*args, **kwargs)
# do something after function execution
return result
return wrapper
从上面显示的结构中,我们可以看到函数执行发生在第 4 行,但是我们可以修改函数执行之前、期间甚至之后发生的事情。装饰者有可能改变函数的输入、输出或行为,但是最好以不降低它所包装的函数的可理解性的方式来实现它。
装饰者最适合用来向多个函数添加公共行为,而不用手动修改每个函数
装饰者的高级行为
保留包装函数的元数据
使用 decorator 的一个警告是,函数的元数据会被 decorator 隐藏。在上一节的代码片段中,我们返回了一个wrapper函数,而不是原始函数,这意味着任何修饰过的函数都将把它们的__name__元数据覆盖到wrapper。
@sample_decorator
def func_add(a, b):
return a + b
print(func_add.__name__)
# wrapper
从技术角度来说,这不会影响函数或装饰器的预期执行,但这仍然是避免使用装饰器的任何意外后果的最佳实践。这可以通过为wrapper函数添加一个@wraps装饰器来轻松完成,如下所示。装饰器可以以同样的方式使用,但是包装函数的元数据现在不会被覆盖。
from functools import wraps
def sample_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# do something before function execution
result = func(*args, **kwargs)
# do something after function execution
return result
return wrapper
接受参数的装饰者
装饰器本质上是一个函数(包装另一个函数),应该能够接受参数。我发现传入一个布尔变量很有用,这样我就可以切换装饰器的行为,比如当我想进入或退出调试模式时打开或关闭打印。这可以通过用另一个函数包装器包装装饰器来实现。
在下面的例子中,我有一个装饰器debug_decorator,它可以接受参数并返回一个decorator装饰器,该装饰器将原始函数包装在wrapper中。由于多个嵌套函数,这一开始看起来相当复杂。建议首先编写原始的装饰器,最后包装它以接受参数。
from functools import wraps
debug_mode = True
def debug_decorator(debug_mode):
"""Example: Passing arguments to a decorator"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
if debug_mode:
print(f"Function called: {func.__name__}")
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@debug_decorator(debug_mode)
def func_add(a, b):
return a + b
func_add(1, 2)
# Function called: func_add
# 3
堆叠装饰者
如前所述,decorators 允许扩展现有的功能。可以在一个函数上堆叠多个 decorators 来添加更多的扩展。执行的顺序将遵循装饰器的堆叠顺序。
需要注意的一点是,时间敏感的 decorators 如果是堆叠的,应该最后添加。比如,衡量一个函数执行时间的 decorator 应该是最后执行的,这样才能准确反映执行时间,不受其他 decorator 的影响。
现在我们已经理解了装饰器的结构和它的高级行为,我们可以深入到它们的实际应用中去了!
用法:测量执行时间
timer decorator 可以通过记录函数执行的开始时间和结束时间并将结果打印到控制台来测量包装函数的执行时间。
在下面的代码片段中,我们测量了函数执行前后的start_time和end_time。
import time
from functools import wraps
def timer(func):
"""Example: Measure execution time of function"""
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"Execution time: {round(end_time - start_time, 4)}")
return result
return wrapper
@timer
def func_add(a, b):
time.sleep(2)
return a + b
func_add(1, 2)
# Execution time: 2.0064
用法:使用日志调试
logging装饰器可用于将信息记录到控制台或日志文件中,对于调试非常有用。在下面的代码片段中,我们将使用logging python 包来执行日志记录。
import logging
from datetime import datetime
from functools import wraps
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def logging(func):
"""Example: Logging with decorator"""
@wraps(func)
def wrapper(*args, **kwargs):
log_time = datetime.today().strftime("%Y-%m-%d %H:%M:%S")
logger.info(f"{log_time}: {func.__name__} called")
result = func(*args, **kwargs)
return result
return wrapper
用法:创建单例类
singleton装饰器可以用来创建一个单例类。Singleton 是一种创造性的设计模式,它限制一个类只能有一个实例。这在对共享资源的并发访问或资源的全局访问点有限制的情况下很有用,例如对数据库的并发访问或数据库的单点访问施加限制。
单例类可以专门编码以确保单个实例化。然而,如果有多个单独的类,使用 decorators 是为多个类重用代码的好方法。
from functools import wraps
def singleton(cls):
"""Example: Create singleton class with decorator"""
instances = {}
@wraps(cls)
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class SampleClass:
def __init__(self):
pass
singleton_class = SampleClass()
singleton_class2 = SampleClass()
print(singleton_class == singleton_class2)
# True
希望你已经学会了装修工的基础知识,有用的小技巧,以及装修工的实际例子。还有其他的用法,比如使用 decorators 进行超时操作、记忆和缓存。那些 decorator 更高级,最好使用内置的 Python decorators 或者 Python 包中的 decorator,而不是自己实现。
感谢您的阅读!如果你喜欢这篇文章,请随意分享。
相关链接
伐木文件:https://docs.python.org/3/howto/logging.html
超时装饰:https://pypi.org/project/timeout-decorator/
缓存文档:https://docs.python.org/3/library/functools.html
面向数据科学的 Python 依赖管理
原文:https://towardsdatascience.com/python-dependency-management-for-data-science-4f2d272db039
关于 Python 依赖关系,每个数据科学家都应该知道什么
依赖管理到底是什么?
依赖管理是管理项目所依赖的所有外部部分的行为。它有污水系统的风险。当它起作用时,你甚至不知道它在那里,但当它失败时,它变得令人痛苦,几乎无法忽视。
每个项目都是建立在别人的汗水和泪水之上的。工程师醒来,煮咖啡,通过编写引导程序(从头启动计算机的程序)开始一个新项目的日子已经成为历史。我们下面有大量的软件和库。我们只是在上面撒上一层薄薄的糖。

作者图片
我的电脑和你的电脑有不同的软件。不仅堆栈不同,而且它们永远在变化。任何事情的运作方式都令人惊讶,但它确实如此。所有这些都要感谢依赖管理的污水系统和许多聪明的人抽象层,这样我们就可以调用我们最喜欢的熊猫函数并得到可预测的结果。
Python 依赖管理的基础
先说清楚一件事。简单地安装和升级 Python 包不是依赖管理。依赖关系管理是为您的项目记录所需的环境,并使它对其他人复制它变得容易和确定。
你可以在一张纸上写下安装说明。您可以将它们写在源代码注释中。您甚至可以将安装命令直接硬编码到程序中。依赖管理?是的。推荐?没有。
推荐的方法是以一种标准化的、可复制的、被广泛接受的格式将依赖信息从代码中分离出来。这允许版本锁定和简单的确定性安装。有许多选项,但是在本文中我们将描述 pip 和 requirements.txt 文件的经典组合。
但是在我们开始之前,让我们先介绍一下 Python 依赖的原子单元:包。
什么是包?
包是 Python 中一个定义明确的术语。像库、框架、工具包这样的术语不是。在本文的剩余部分,我们将使用术语包,甚至是一些被称为库、框架或工具包的东西。
模块是在一个 Python 文件中定义的一切(类、函数等)。).
包是模块的集合。
Pandas 是包,Matplotlib 是包,print()-function 不是包。一个包的目的是成为一个易于分发的、可重用的、版本化的模块集合,它与其他包有明确的依赖关系。
您可能每天都在用 Python import语句在代码中引用包。
安装包的艺术
虽然您可以通过简单地将软件包手动下载到您的项目来安装它们,但是安装软件包最常见的方式是使用著名的pip install命令通过 PyPi (Python 包索引)。
注意:千万不要用sudo pip install。从来没有。这就像运行一个病毒。结果是不可预测的,会给你未来带来巨大的痛苦。
也不要全局安装 Python 包。始终使用虚拟环境。
什么是虚拟环境?

作者图片
Python 虚拟环境是一个安全的泡泡。您应该在本地计算机上的所有项目周围创建一个保护气泡。如果不这样做,项目就会互相伤害。不要让下水道系统漏水!
如果你在泡泡外面呼叫pip install pandas,它将被全球安装。这很糟糕。世界在前进,包装也在前进。一个项目需要 2019 年的 Matplotlib,另一个项目需要 2021 年的版本。单一的全球安装无法同时服务于两个项目。所以保护性气泡是必要的。让我们看看如何使用它们。
转到您的项目根目录并创建一个虚拟环境:
python3 -m venv mybubble
现在我们有一个泡沫,但我们还没有陷入其中。我们进去吧!
source mybubble/bin/activate
现在我们在泡沫中。您的终端应该在括号中显示虚拟环境名称,如下所示:
(mybubble) johndoe@hello:~/myproject$
现在我们在泡泡中,安装包是安全的。从现在开始,任何 pip 安装命令将只在虚拟环境中有效。您运行的任何代码都将只使用气泡内的包。
如果您列出已安装的软件包,您应该会看到一个非常短的当前已安装的默认软件包的列表(就像 pip 本身一样)。
pip list
Package Version
------------- -------
pip 20.0.2
pkg-resources 0.0.0
setuptools 44.0.0
这个列表不再适用于机器中的所有 Python 包,而是适用于虚拟环境中的所有 Python 包。另外,请注意,气泡中使用的 Python 版本是您用来创建气泡的 Python 版本。
要离开气泡,只需调用deactivate命令。
始终为所有本地项目创建虚拟环境,并在这些虚拟环境中运行代码。项目之间包版本冲突的痛苦是那种让人辞职的痛苦。不要成为那种人。
什么是版本锁定?

作者图片
想象一下,你有一个依赖于熊猫包的项目,你想把它传达给世界上的其他人(和你未来的自己)。应该很容易吧?
首先,仅仅说:“你需要熊猫”是有风险的。
风险较小的选项是“你需要熊猫 1.2.1”,但即使这样也不总是足够的。
假设你正确地将熊猫版本锁定在 1.2.1。Pandas 本身有一个对 numpy 的依赖,但不幸的是,它并没有将这个依赖绑定到一个精确的 numpy 版本上。Pandas 本身只是说“你需要 numpy ”,并没有明确的版本。
起初,一切都很好,但六个月后,一个新的 numpy 版本 1.19.6 发布了,带有一个 showstopper 错误。
现在,如果有人安装了你的项目,他们会得到 pandas 1.2.1 和错误百出的 numpy 1.19.6,当你的软件吐出奇怪的错误时,可能会有一些白发。下水道系统在漏水。安装过程不确定!
最靠谱的办法就是把一切都压住。别动依赖关系的依赖关系的依赖关系的依赖关系的依赖关系…你会明白的。把他们钉得像兔子洞一样深。幸运的是,有一些工具可以让你做到这一点。
注意:如果您正在构建一个可重用的包,而不是一个典型的项目,您不应该如此激进地固定它(这就是为什么 Pandas 不固定到确切的 Numpy 版本)。由包的最终用户决定 pin 的内容和积极程度被认为是最佳实践。如果您作为一个包创建者,将所有东西都固定下来,那么您就向最终用户关上了大门。
如何固定 Python 依赖关系?
每当你打电话给pip install让一些热门的新包进入你的项目,你应该停下来想一想。这将为您的项目创建新的依赖项。我如何记录这一点?
您应该将新的库及其版本号写到 requirements.txt 文件中。pip 理解这种一次安装多个软件包的格式。
# requires . txt
pandas==1.2.1 matplotlib==3.4.0
#安装
pip install -r requirements.txt
这已经比人们遇到的大多数数据科学项目好得多,但我们仍然可以做得更好。还记得上一章关于版本锁定的递归依赖兔子洞吗?我们如何使安装更具确定性?
答案是pip-compile命令和requirements.in文本文件。
#要求输入
matplotlib==3.4.0
#自动生成需求. txt
pip-compile requirements.in
#生成的需求. txt
cycler==0.11.0
# via matplotlib
kiwisolver==1.3.2
# via matplotlib
matplotlib==3.4.0
# via -r requirements.in
numpy==1.22.0
# via
# matplotlib
# pandas
pandas==1.2.1
# via -r requirements.in
pillow==9.0.0
# via matplotlib
pyparsing==3.0.6
# via matplotlib
python-dateutil==2.8.2
# via
# matplotlib
# pandas
pytz==2021.3
# via pandas
six==1.16.0
# via python-dateutil
在 requirements.in 中,你应该只放你的直接依赖项。
然后,pip-compile 将生成所有库到 requirements.txt 中的完美连接,这为确定性安装提供了所有信息。很简单!记住也要将这两个文件提交到 git 存储库中。
Python 版本怎么钉?

作者图片
固定 Python 版本是很棘手的。没有直接的方法来固定 Python 本身的版本依赖性(例如不使用 conda)。
您可以从您的项目中制作一个 Python 包,这允许您用键python_requires>=3.9在setup.py或setup.cfg中定义 Python 版本,但是对于一个典型的数据科学项目来说,这是多余的,因为它通常不具有可重用包的特征。
如果你真的很重视特定的 Python,你也可以在你的代码中这样做:
import sys
if sys.version_info < (3,9):
sys.exit("Python >= 3.9 required.")
最防弹的强制 Python 版本的方法是使用 Docker 容器,我们将在下一章讨论!
主要要点
不要回避依赖管理 —当你把咖啡倒在你的 MacBook 上时,你未来的自己会欣赏那些记录在案的依赖。
总是在你的本地计算机上使用虚拟环境——当你安全地呆在保护罩内时,尝试一下有两颗 GitHub 星的深奥的 Python 库没什么大不了的。
固定版本比不固定要好——当你的项目不固定时,固定版本可以防止包向前移动。
包变化很大,Python 变化不大——即使一个包也可能有几十个嵌套的依赖项,它们在不断变化,但 Python 相对稳定,经得起未来考验。
云呢?
当您的项目足够成熟,并提升到云和生产时,您应该着眼于锁定整个环境,而不仅仅是 Python 的东西。
这就是 Docker 容器是你最好的朋友的地方,因为它们不仅能让你锁定 Python 版本,还能锁定操作系统中的任何东西。它就像一个虚拟环境,但规模更大。
我们的下一章将涵盖你需要知道的关于 Docker 的一切,敬请期待!
想要更多实用的工程技巧吗?
数据科学家越来越多地成为 R&D 团队的一部分,并致力于生产系统,这意味着数据科学和工程领域正在发生碰撞。我想让没有工程背景的数据科学家更容易理解,就这个主题写了一本免费电子书。
下载电子书: 数据科学家的工程实践
Python 描述符及其使用方法
原文:https://towardsdatascience.com/python-descriptors-and-how-to-use-them-5167d506af84
用例子解释 Python 描述符

介绍
您可能不知道什么是 Python 描述符,但是如果您一直在用 Python 编程,那么您可能已经间接地使用过它们了!它们是属性(通过@property decorator 定义)和方法之类的东西的背后。
这是什么?
描述符是一个对象,它:
- 实现描述符协议和
- 被分配给一个属性。
简单来说,对象需要定义以下任何一种方法:
__get__(self, instance, owner=None) -> value
__set__(self, instance, value) -> None
__delete__(self, instance) -> value
对于 Python 3.6 及以上版本:
__set_name__(self, owner, name)
当这种情况发生时:
- 访问属性 do
obj.attribute调用__get__方法, - 设置一个值做
obj.attribute = new_value调用__set__方法, - 删除属性做
del obj.attribute调用__delete__方法,
假设描述符定义了所有这些方法。你可能想知道__set_name__是做什么的?
它允许描述符知道它被赋予的属性的名称。当实现描述符协议的对象被分配给一个属性时,它会自动被调用。
为了查看所有这些操作,让我们创建一个定义描述符协议中所有方法的对象,并添加一组print语句——不要在生产代码中这样做:)。
*class* ExplainDescriptors:
*def __init__*(self):
print('__init__')
*def __set_name__*(self, *owner*, *name*):
print(f'__set_name__(owner={*owner*}, name={*name*})')
self.name = *name
def __get__*(self, *instance*, *owner*=*None*):
print(f'__get__(instance={*instance*}, owner={*owner*})')
*return instance*.__dict__.get(self.name)
*def __set__*(self, *instance*, *value*):
print(f'__set__(instance={*instance*}, value={*value*})')
*instance*.__dict__[self.name] = *value
def __delete__*(self, *instance*):
print(f'__delete__(instance={*instance*})')
*del instance*.__dict__[self.name]
现在我们可以看到每个方法何时被调用以及它的参数是什么。

调用描述符的所有方法。图片由作者提供。
instance被设置为相关对象,owner被设置为其类。
再来看一个更实际的例子(这次没有print语句)!
class ReadOnlyAttribute:
def __init__(self, value):
self.value = value
def __set_name__(self, owner, name):
self.name = name def __get__(self, instance, owner=None):
return self.valuedef __set__(self, instance, value):
msg = '{} is a read-only attribute'.format(self.name)
raise AttributeError(msg) class StubbornPikachu:
speak = ReadOnlyAttribute('pika pika')
在上面的例子中,我们创建了一个只读描述符,并将其分配给属性speak。我们没有在instance中存储任何东西,因为我们希望所有的Pikachu对象说同样的事情——它们都共享相同的描述符。
任何改变我们的StubbornPikachu所说的话的尝试都会遭遇失败(或者更具体地说是AttributeError)。

pika1 拒绝说别的。图片由作者提供。
描述符类型
描述符有两种类型。
- 数据描述符:定义
__get__和__set__或__delete__, - 非数据描述符:仅定义
__get__。
我们上面定义的**ReadOnlyAttribute** 是一个数据描述符,因为它定义了__set__,即使当你试图使用它时它会产生一个错误。
Python 方法就是一个非数据描述符的例子。为什么?嗯,Python 函数是实现__get__的对象,方法是分配给属性的函数,这使它们成为非数据描述符!
了解两者之间的区别很重要,因为在查找链中,数据描述符的优先级高于非数据描述符。那是什么意思?
当您使用obj.attribute语法访问属性时,Python
- 首先查找名为
attribute的数据描述符, - 如果没有找到,它就在
obj.__dict__中检查一个attribute键, - 如果失败,它将检查非数据描述符。
让我们看一个例子。
*class* DataDescriptor:
*def __set_name__*(self, *owner*, *name*):
self.name = *name
def __get__*(self, *instance*, *owner*=*None*):
default_value = '{} not defined'.format(self.name)
print('In __get__ of {}'.format(self))
*return instance*.__dict__.get(self.name, default_value)
*def __set__*(self, *instance*, *value*):
print('In __set__ of {}'.format(self))
*instance*.__dict__[self.name] = *value
class* NonDataDescriptor:
*def __set_name__*(self, *owner*, *name*):
self.name = *name
def __get__*(self, *instance*, *owner*=*None*):
default_value = '{} not defined'.format(self.name)
print('In __get__ of {}'.format(self))
*return instance*.__dict__.get(self.name, default_value)
*class* Example:
a = DataDescriptor()
b = NonDataDescriptor()
在上面的代码中,我们创建了两个描述符,并将它们分配给类Example的属性a和b。除了DataDescriptor具有__set__方法之外,它们是相同的。

这两个属性都是通过描述符的 get 方法来访问的。图片由作者提供。
我们可以看到,由于我们没有为example定义a和b——因此example.__dict__是空的——这两个属性都是通过它们的描述符的__get__方法来访问的。让我们看看当我们将它们设置为一些值时会发生什么。

在示例中出现“b”之后。dict 不再使用分配给 b 的 NonDataDescriptor 的 get 方法。图片由作者提供。
访问a仍然使用__get__方法,因为数据描述符优先于__dict__中的内容。然而,为了访问b,我们直接进入example.__dict__,因为它在非数据描述符之前被检查。
如何使用描述符
当您有多个具有相似行为的属性时,使用描述符是有意义的。
例如,假设我们正在实现一个名为Person的类,其属性有name、age、weight和height。我们可能希望检查这些属性的值是否有意义(例如,我们不能让一个人的年龄为负)。一种方法是在__init__中有一堆if语句。然而,如果您必须为许多属性这样做,这可能会很快变得很糟糕。有一些像下面这样的东西看起来更好,也更容易阅读!
*class* Person:
age = BoundedNumber(1, 120)
weight = BoundedNumber(1, 250)
height = BoundedNumber(1, 230)
*def __init__*(self, name, *age*, *weight*, *height*):
self.name = name
self.age = *age* self.weight = *weight* self.height = *height*
在上面的代码中BoundedNumber是一个数据描述符,它检查分配给属性的值是否在两个数字之间。下面给出了实现。
*class* BoundedNumber:
*def __init__*(self, *min_val*, *max_val*):
self.min_val = *min_val* self.max_val = *max_val
def __set_name__*(self, *owner*, *name*):
self.name = *name
def __set__*(self, *instance*, *value*):
*if* self.min_val > *value or value* > self.max_val:
msg = '{} takes values between {} and {}'.format(
self.name,
self.min_val,
self.max_val,
)
*raise* ValueError(msg)
*instance*.__dict__[self.name] = *value
def __get__*(self, *instance*, *owner*=*None*):
*return instance*.__dict__[self.name]
注意在__set__方法中,我们如何将value存储在instance(当前对象)中,而不是描述符中,即self。所有的Person对象将共享相同的描述符,所以在那里存储值是没有意义的。
现在,当我们试图用无效值初始化一个对象时,我们会得到一个错误,这是意料之中的。

行动中的描述符。图片由作者提供。
结论
在这篇文章中,我们学习了 Python 描述符以及如何使用它们。有关更多细节和示例,请务必查看下面的参考资料。
https://eminik355.medium.com/subscribe
出自同一作者。
参考
[1]https://docs.python.org/3/howto/descriptor.html
[2]https://realpython.com/python-descriptors/
用 doctest 测试 Python 文档:简单的方法
原文:https://towardsdatascience.com/python-documentation-testing-with-doctest-the-easy-way-c024556313ca
PYTHON 编程
doctest 允许文档、单元和集成测试,以及测试驱动的开发

doctest 允许保持最新的代码文档。照片由伊斯哈格·罗宾在 Unsplash 上拍摄
代码测试不一定很难。更重要的是,测试使编码变得更容易和更快——甚至,至少对一些开发人员来说,更令人愉快。然而,为了让测试变得愉快,我们使用的测试框架需要对用户友好。
Python 提供了几个测试框架,目前最流行的三个是[pytest](https://docs.pytest.org/en/7.2.x/contents.html)和内置的[unittest](https://docs.python.org/3/library/unittest.html)和[doctest](https://docs.python.org/3/library/doctest.html)。前两种侧重于单元测试。doctest是不同的,它的主要目的——但肯定不是唯一的目的——是文档测试。
doctest也是这篇文章的主旨。我将介绍这个特别有趣的工具,希望您能看到,尽管它很简单,但它非常有用。老实说,doctest是我最喜欢的 Python 包,正如我在这次“本周 PyDev”采访中强调的。在上面,我写道,如果我们希望测试是愉快的,我们需要一个用户友好的工具。说[doctest](https://docs.python.org/3/library/doctest.html)用户友好是一种保守的说法——它是我见过的最简单、最友好的测试工具。
说到这里,我觉得很困惑,为什么我认识的大多数 python 爱好者很少使用或者根本不使用doctest。我希望这篇文章能让他们——还有你——相信将doctest引入日常编码程序是值得的。
我提到的doctest很简单。的确,它是如此的简单,以至于看完这篇短文就足够你使用它了!
doctest是关于什么的?
doctest是一个标准库测试框架,所以它与您的 Python 安装一起提供。这意味着你不必安装它。虽然它不是为复杂的单元测试场景而设计的,但它使用简单,而且在大多数情况下功能强大。
doctest模块提供了pytest和unittest都没有的: 文档测试 。简而言之,文档测试对于测试您的代码文档是否是最新的非常有用。这是相当多的功能,尤其是在大型项目中:只需一个命令,您就可以检查所有示例是否正确。因此,在每次新的提交、合并和发布之后,它取代了反复阅读文档中的代码示例。您还可以将doctest ing 添加到 CI/CD 管道中。
doctest采用的方法使用了我们称之为回归测试的方法。原则上,您从示例中运行代码,保存输出,并且在将来您可以运行doctest来检查示例是否提供相同的输出——这意味着它们以它们应该的方式工作。如您所见,这将该工具的使用限制在不会随会话变化的对象上。因此,您不能使用这种方法来测试输出的随机元素。一个例子是对象的,它们在不同的会话中是不同的。这个限制并不是特定于doctest的,但是它在这个测试框架中似乎比在其他框架中更重要。幸运的是,doctest提供了处理这种限制的工具。
与任何测试工具一样,您可以——也应该——在对代码进行任何更改之后运行doctest,以便检查这些更改是否没有影响输出。有时他们会,由于他们的性格(例如,你修复了一个 bug,现在功能工作正常了——但是不同了);如果是这种情况,您应该更新相应的测试。如果更改不应该影响输出,但是它们影响了输出,正如失败的测试所表明的,代码有问题,因此是代码需要进一步的更改。
利用 **doctest** ,结合实例
要编写doctest s,您需要提供代码和预期的输出。在实践中,您以类似于在Python 解释器中的代码及其输出的方式编写测试。
让我用一个简单的例子来描述一下。考虑下面的函数,它是doctest的一部分:
>>> def foo():
... return 42
(您可以导入该函数,但我们稍后将返回到这一点。)这是我在 Python 解释器中看到的情况:

Python 解释器中 foo()函数的定义。作者图片
我们看到了一个微小的区别。当你在解释器中定义一个函数时,你需要按两次回车键,这就是为什么我们会在函数的末尾看到额外的省略号。doctest代码不需要这个额外的省略号行。更重要的是,在解释器中编码时,不必在每个省略号行之前(省略号之后)额外添加空格,而需要在doctest代码中做;否则,缩进看起来好像是由三个空格组成的,而不是四个。还有一个区别——实际上是一个很好的区别——在 Python 解释器中,您不会看到代码高亮显示。例如,当您编写文档测试时,您可能会在 Markdown 或 reStructuredText 文件中看到它。
我想你会同意这些不是很大的差异,而是好的差异。多亏了他们,doctest代码看起来很漂亮。
因此,>>>和...(Python 省略号)构成了测试的基本部分,因为doctest使用它们来识别新命令刚刚开始(>>>)以及前一行的命令正在继续(...)。
上面定义foo()函数的 cope 片段仍然不是有效的doctest,因为它只定义了函数。要编写一个测试,我们需要调用函数并包含它的预期输出:
>>> foo()
42
就是这样!这是我们的第一个doctest ——你已经了解了关于这个工具你需要知道的大部分内容!总之,此doctest将如下所示:
>>> foo()
42
>>> def foo():
... return 42
您可以从 shell 运行测试。您也可以从 Python 解释器中完成,但是由于它需要与其中一个 shell 方法相同的代码,所以我不会重点介绍这一点;你也很少需要它。???不清楚的句子!!!
假设测试保存为doctest_post_1.md。要运行它,请打开 shell,导航到测试文件所在的文件夹,然后运行以下命令(它在 Windows 和 Linux 中都可以工作):
$ python -m doctest doctest_post_1.md
如果测试通过,你将什么也看不到。如果失败,您将在 shell 中看到这个。为了了解其工作原理,让我们将测试中的 42 改为 43:
>>> foo()
43
这将是输出:

在 shell 中运行的 doctest(失败)的输出。作者图片
你可以用另一种方法做这件事。考虑以下模块:
# mathmodule.py
"""The simplest math module ever.
You can use two functions:
>>> add(2, 5.01)
7.01
>>> multiply(2, 5.01)
10.02
"""
def add(x, y):
"""Add two values.
>>> add(1, 1)
2
>>> add(-1.0001, 1.0001)
0.0
"""
return x + y
def multiply(x, y):
"""Multiple two values.
>>> multiply(1, 1)
1
>>> multiply(-1, 1)
-1
"""
return x * y
if __name__ == "__main__":
import doctest
doctest.testmod()
使用这个 name-main 块,您可以简化 shell 命令:
$ python doctest_post_1.md
这运行模块,运行它意味着运行它所有的doctest。
当您想要查看详细的输出时,添加一个-v标志。下面,我使用了这个标志,它导致了下面的输出:
$ python doctest_post_1.md -v
Trying:
add(2, 5.01)
Expecting:
7.01
ok
Trying:
multiply(2, 5.01)
Expecting:
10.02
ok
Trying:
add(1, 1)
Expecting:
2
ok
Trying:
add(-1.0001, 1.0001)
Expecting:
0.0
ok
Trying:
multiply(1, 1)
Expecting:
1
ok
Trying:
multiply(-1, 1)
Expecting:
-1
ok
3 items passed all tests:
2 tests in __main__
2 tests in __main__.add
2 tests in __main__.multiply
6 tests in 3 items.
6 passed and 0 failed.
Test passed.
它相当杂乱,说实话,我几乎从不使用它。它的主要优势是我们在最后看到的,测试的总结。然而,在开发时,我不需要它,因为我通常运行测试来检查某个特定的功能是否通过测试。因此,这一点很重要,doctest s 应该相当快,因为你通常不会请求一个特定的测试,你只需从一个模块中运行所有的测试。
上面,我们展示了如何编写和运行doctest,所以是时候考虑实际问题了,也许还有更复杂的例子。doctest测试通常有两个位置:
- 在函数/类/方法文档字符串中
- 在文档文件中
因为它们可能有显著的不同,所以让我们逐一讨论它们。
**doctest** 文档字符串中的 s
考虑以下函数:
# mean_module.py
from typing import List, Union
def mean(x: List[Union[float, int]]) -> float:
"""Calculate the mean of a list of floats.
>>> x = [1.12, 1.44, 1.123, 6.56, 2.99]
>>> round(mean(x), 2)
2.65
"""
return sum(x) / len(x)
这就是如何在 docstring 中编写doctest测试的方法。这里,我们在函数 docstring 中做了,但是您可以将它写在任何 docstring 中,放在四个doctest位置:
- 模块文档字符串
- 函数 docstring(就像上面的例子)
- 类文档字符串
- 方法文档字符串
使用 docstrings,我认为是doctest最常见的用法。请记住,无论一个函数的 docstring 包含什么,它都会出现在它的帮助页面中,您可以通过调用help()函数在 Python 解释器中看到它。例如:
>>> from mean_module import mean
>>> help(mean)
解释器将消失,取而代之的是您将看到下面的mean()功能的帮助页面:

mean()函数的帮助页面。作者图片
这是我在doctest测试中欣赏的另一件事:与 docstrings 结合,它们产生帮助页面,因此代码文档是干净的和最新的。有时函数或方法非常复杂,以至于很难看出应该如何使用它们。然后,通常,像添加到 docstring 中的一两个doctest测试这样小的东西会比类型提示和代码以及 docstring 的其余部分一起提供更多的信息。
但是,请记住,不要让用文档测试淹没文档字符串。虽然在其中包含许多这样的测试可能很诱人,但是您不应该这样做。阅读这样的文档字符串是不愉快的。最好将关键的测试放在 docstring 中,将剩余的测试移到别处。您可以将它们移动到一个专用的doctest文件中,或者将它们翻译成pytest测试。两种解决方案我都用过。
**doctest**文档文件中的年代
您可以在各种类型的文件中编写文档测试。我更喜欢降价。md)但是可以在 reStructuredText(。rst)甚至文本(。txt)文件。但是,不要试图在使用特定编码的文件中这样做,比如 e of。例如,doctest不能与。rtf 文件。
下面的代码块展示了一个带有 doctests 的 Markdown 文件的例子。为了节省空间,我将在这里只包括基本的测试,但它们足以显示如何创建这样的文件。
考虑mathmodule模块的以下自述文件(README.md ):
# Introduction
You will find here documentation of the mathmodule Python module.
It contains as many as two functions, `add()` and `multiply()`.
First, let's import the functions:
```python
>>> from mathmodule import add, multiply
Using add()
You can use the add() function in the following way:
>>> add(1, 1)
2
>>> add(2, 5.01)
7.01
Using `multiply()
You can use the multiply() function in the following way:
>>> multiply(1, 1)
1
>>> multiply(-1, 1)
-1
如您所见,这里没有哲学:您在典型的代码块中添加了`doctest`。有两件事你应该记住:
* 在完成代码块之前添加一个空行。否则,`doctest`会将`````视为输出的一部分。
* 不要忘记导入您将在测试文件中使用的所有对象;这里,这些是`add()`和`multiply()`功能。这在你看来可能是一个基本的错误,甚至可能太基本了。即使这是最基本的,我也经常这样做;我甚至在这里,当写上述自述文件。
如您所见,我在代码块中包含了所有的测试,但是即使是在代码块之外编写的测试也会运行。然而,我认为这样做没有任何意义。
# `doctest`工具
上面,我们已经学习了`doctest`的基本用法。然而,我们可以用它做更多的事情。通过使用所谓的指令,我们可以使用大多数附加的`doctest`功能。
指令直接添加在被测试的代码之后,使用如下所示的特定语法。当需要指令的命令被分成几行时,您可以将指令添加到其中的任何一行中(我马上会给出一个例子)。指令改变`doctest`的行为;例如,测试可以忽略输出的一部分,规范化空白,捕捉异常,等等。
## 省略
也许最重要和最常用的指令是省略号:`# doctest: +ELLIPSIS`。下面,你可以看到上面的`multiply`函数的两个测试,一个没有,另一个有`ELLIPSIS`指令:
multiply(2.500056, 1/2.322)
1.0766821705426355
multiply(2.500056, 1/2.322) # doctest: +ELLIPSIS
1.076...
因此,您需要在输出中的测试代码和省略号之后添加指令,输出中省略号所在的任何内容都将被忽略。这是另一个例子:
multiply
<function multiply at 0x7...>
在上面的例子中,您可以使用结束的`>`字符,但不是必须的。
## 输出中的长行
长队可能是一种负担。我们有两种方法来处理输出中过长的行:(i) `\`和(ii)*`NORMALIZE_WHITESPACE`*指令。**
***方法(一):使用* `*\*`。像在 Python 代码中一样,我们可以使用反斜杠(`\`)将输出分成几行,如下所示:**
**>>> s1 = "a"*20
s2 = "b"*40
add(s1, s2)
'aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'**
**这场考验确实会通过。上面的输出格式非常难看,但是当我们需要在函数、类或方法 docstring 中拆分行时,情况会更糟:**
**def add(x, y):
"""Add two values.
>>> add(1, 1)
2
>>> add(-1.0001, 1.0001)
0.0
>>> add(s1, s2)
'aaaaaaaaaaaaaaaaaaaa\
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
"""
return x + y**
**如您所见,将这一行分成两部分需要我们将第二行移动到它的最开始;否则,`doctest`会看到以下输出:**
'aaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
**所以测试会失败。**
**注意*这是处理行间字符串分割的唯一方法*。当您需要拆分长输出(例如,一个列表、一个元组等)时,下一个方法会更好。**
***方法(二)* `NORMALIZE_WHITESPACE` *指令*。这个方法不使用难看的反斜杠。相反,它使用了`NORMALIZE_WHITESPACE`指令:**
>>> multiply([1, 2], 10) #doctest: +NORMALIZE_WHITESPACE
[1, 2, 1, 2, 1, 2, 1, 2, 1, 2,
1, 2, 1, 2, 1, 2, 1, 2, 1, 2]
**如果没有指令,该测试将无法通过:**
Failed example:
multiply([1, 2], 10)
Expected:
[1, 2, 1, 2, 1, 2, 1, 2, 1, 2,
1, 2, 1, 2, 1, 2, 1, 2, 1, 2]
Got:
[1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2]
**但是它通过了指令。只要有可能,我就用这个指令代替反斜杠,除了拆分长字符串。**
## **例外**
**上面,我们讨论了省略号指令。然而,省略号也有助于异常处理,也就是说,当我们想测试一个抛出错误的例子时:**
>>> add(1, "bzik")
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +: 'int' and 'str'
**输出清楚地显示已经引发了一个异常,并且我们测试了它是否是预期的异常。**
**你能看出这个省略号的例子和前面的例子有什么不同吗?请注意,尽管我们在这里使用了省略号,但是在测试异常输出时,我们并不需要提供省略号指令。我想这是因为在测试中捕捉异常是如此频繁,以至于`doctest`创建者决定将省略号的这种用法构建到`doctest`语法中,而不需要添加省略号指令。**
> **当测试异常输出时,我们不必提供省略号指令。**
## **排除故障**
**`doctest`为[调试](https://docs.python.org/3/library/doctest.html#debugging)提供了几种机制。既然我们在讨论基础,我将只介绍一种机制,这是最简单的,但至少对我来说,也是最直观和最有用的。说实话,自从我开始用`doctest`以来,我只用这种方法,满足我的需求绰绰有余。**
**该方法使用内置的`pdb`模块和内置的`breakpoint()`函数。我们可以通过两种方式使用`doctest`进行调试:**
1. **调试通过`doctest`测试的特定功能。**
2. **调试一个`doctest`会话。**
***公元 1 年。调试通过* `doctest`测试的特定功能。在这种情况下,使用`breakpoint()`在函数中设置一个点,并运行 doctest。无非是使用`breakpoint()`进行标准调试,而是通过运行`doctest`来进行。**
**让我们使用`doctest`来调试下面的模块,它被命名为`play_debug.py`,只包含一个函数。**
**def play_the_debug_game(x, y):
"""Let's play and debug.
>>> play_the_debug_game(10, 200)
142
"""
x *= 2
breakpoint()
x += 22
y *= 2
y -= 300
return x + y**
**下面,您将看到 shell 截图,其中包含在模块上运行`doctest`后的调试会话。在输出中,我用省略号替换了路径。**
**$ python -m doctest play_debug.py
.../play_debug.py(9)play_the_debug_game()
-> x += 22
(Pdb) l
4 >>> play_the_debug_game(10, 200)
5 142
6 """
7 x *= 2
8 breakpoint()
9 -> x += 22
10 y = 2
11 y -= 300
12 return x + y
[EOF]
(Pdb) x
20
(Pdb) n
.../play_debug.py(10)play_the_debug_game()
-> y = 2
(Pdb) x
42
(Pdb) c
**所以,这是`pdb`调试器的标准用法。这不是真正的`doctest`调试,而是通过`doctest`运行的代码的调试。我经常在代码开发过程中使用这种方法,尤其是当代码还没有包含在工作应用程序中的时候。**
***公元 2 年。调试一个* `doctest` *会话*。与前一种方法不同,这种方法意味着调试实际的测试会话。你以类似的方式做它,但是这一次,你在一个测试中增加一个点,而不是在一个函数中。例如:**
**def play_the_debug_game(x, y):
"""Let's play and debug.
>>> game_1 = play_the_debug_game(10, 200)
>>> game_2 = play_the_debug_game(1, 2)
>>> breakpoint()
>>> game_1, game_2
(142, -272)
"""
x *= 2
x += 22
y *= 2
y -= 300
return x + y**
**现在开始一个环节:**
**$ python -m doctest play_debug.py
--Return--
<doctest play_debug.play_the_debug_game[2]>(1)
()->None
-> breakpoint()
(Pdb) game_1
142
(Pdb) game_2
-272
(Pdb) game_1 + game_2
-130
(Pdb) c**
**这种类型的调试在各种情况下都很有用,在这些情况下,您想要检查测试会话中正在发生什么;例如,当测试以一种意想不到的方式进行,所以你想检查发生了什么。**
# **先进的例子**
**在前面的章节中,我展示了使用基本的`doctest`工具的简单例子。正如已经提到的,我很少使用更高级的工具。这是因为`doctest`是以一种方式设计的,它的基本工具确实能使一个人取得很多成就。也许这就是我如此喜欢这个测试框架的原因。**
**作为一个高级使用`doctest`的例子,我将使用两个文件——一个表示文档,另一个表示单元测试——来自我的一个 Python 包`[perftester](https://github.com/nyggus/perftester)`。如果你想看更多的例子,你可以去包的 [GitHub](https://github.com/) 仓库。正如你在[中看到的测试/文件夹的自述文件](https://github.com/nyggus/perftester/tree/main/tests),我把`perftester`当作一个实验:**
> **在`perftester`中使用`doctest` ing 作为唯一的测试框架是一个实验。`perftester`中的测试非常丰富,收集在四个位置:[主 README](https://github.com/nyggus/perftester/blob/main/README.md) ,主 perftester 模块中的 docstrings,[docs 文件夹](https://github.com/nyggus/perftester/blob/main/docs),以及 [this 文件夹](https://github.com/nyggus/perftester/blob/main/tests)。**
**请注意,软件包的主自述文件充满了`doctest`——但这仅仅是开始。要了解更多信息,请访问 [docs/](https://github.com/nyggus/perftester/tree/main/docs) 文件夹,里面全是 Markdown 文件,每个都是一个`doctest` ing 文件。虽然这些是可测试的*文档文件*,但是[测试/文件夹](https://github.com/nyggus/perftester/tree/main/tests)中的文件构成了实际的测试文件。换句话说,我使用`doctest`作为唯一的测试框架,用于文档、单元和集成测试。在我看来,实验是成功的,最终的结论是,当你的项目中不需要复杂的单元和集成测试时,你可以使用`doctest`作为唯一的测试框架。**
**我在这个项目中没有使用`pytest`并不意味着我决定完全停止使用`pytest`。我的常规方法是连接`doctest`和`pytest`。我使用`doctest`进行基于测试的开发,但是一旦一个函数、类或方法准备好了,我会将一些测试转移到`pytest`文件中,只保留一些测试作为`doctest` s。我通常用`doctest` s 编写 READMEs,如果我需要添加一个 doc 文件,我通常会用`doctest`测试将它变成一个 Markdown 文件。**
**让我给你看一下`perftester`的 docs 文件夹`[docs/most_basic_use_time.md](https://github.com/nyggus/perftester/blob/main/docs/most_basic_use_time.md)`中的一个文档文件的片段(大约一半)。在下面的代码中,我换行以缩短它们。**
**# Basic use of perftester.time_test()
>>> import perftester as pt
>>> def preprocess(string):
... return string.lower().strip()
>>> test_string = " Oh oh the young boy, this YELLOW one, wants to sing a song about the sun.\n"
>>> preprocess(test_string)[:19]
'oh oh the young boy'
First step - checking performance
We will first benchmark the function, to learn how it performs:
>>> first_run = pt.time_benchmark(preprocess, string=test_string)
first_run gives the following results:
# pt.pp(first_run)
{'raw_times': [2.476e-07, 2.402e-07, 2.414e-07, 2.633e-07, 3.396e-07],
'raw_times_relative': [3.325, 3.226, 3.242, 3.536, 4.56],
'max': 3.396e-07,
'mean': 2.664e-07,
'min': 2.402e-07,
'min_relative': 3.226}
Fine, no need to change the settings, as the raw times are rather short,
and the relative time ranges from 3.3 to 4.6.
Raw time testing
We can define a simple time-performance test, using raw values, as follows:
>>> pt.time_test(preprocess, raw_limit=2e-06, string=test_string)
As is with the assert statement, no output means that the test has passed.
If you overdo with the limit so that the test fails, you will see the following:
>>> pt.time_test(preprocess, raw_limit=2e-08, string=test_string) #doctest: +ELLIPSIS
Traceback (most recent call last):
...
perftester.perftester.TimeTestError: Time test not passed for function preprocess:
raw_limit = 2e-08
minimum run time = ...
Relative time testing
Alternatively, we can use relative time testing, which will be more or
less independent of a machine on which it's run:
>>> pt.time_test(preprocess, relative_limit=10, string=test_string)
If you overdo with the limit so that the test fails, you will see the following:
>>> pt.time_test(preprocess, relative_limit=1, string=test_string) #doctest: +ELLIPSIS
Traceback (most recent call last):
...
perftester.perftester.TimeTestError: Time test not passed for function preprocess:
relative_limit = 1
minimum time ratio = ...
**请注意以下几点:**
* **我使用的唯一指令是省略号指令。我不需要更高级的东西。**
* **我想展示命令`# pt.pp(first_run)`的输出。因为它包含许多随机值(代表基准测试结果),所以我没有让这个代码块进行`doctest`测试。我只是简单地陈述了结果。**
**下面,我给出一个测试文件`[tests/doctest_config.md](https://github.com/nyggus/perftester/blob/main/tests/doctest_config.md)`的一个部分(*默认值*)。与上面的 doc 文件不同,这个文件包括单元测试,而不是文档。因此,您不会在整个文件中看到太多文本——在这一部分也没有文本。这些是单元测试,仅此而已:**
**## Defaults
>>> import perftester as pt
>>> pt.config.defaults
{'time': {'number': 100000, 'repeat': 5}, 'memory': {'repeat': 1}}
>>> original_defaults = pt.config.defaults
>>> pt.config.set_defaults("time", number=100)
>>> pt.config.defaults
{'time': {'number': 100, 'repeat': 5}, 'memory': {'repeat': 1}}
>>> pt.config.set_defaults("time", repeat=20)
>>> pt.config.defaults
{'time': {'number': 100, 'repeat': 20}, 'memory': {'repeat': 1}}
>>> pt.config.set_defaults("time", repeat=2, number=7)
>>> pt.config.defaults
{'time': {'number': 7, 'repeat': 2}, 'memory': {'repeat': 1}}
>>> pt.config.set_defaults("memory", repeat=100)
>>> pt.config.defaults
{'time': {'number': 7, 'repeat': 2}, 'memory': {'repeat': 100}}
>>> pt.config.set_defaults("memory", number=100)
Traceback (most recent call last):
...
perftester.perftester.IncorrectArgumentError: For memory tests, you can only set repeat, not number.
>>> pt.config.set_defaults("memory", number=100, repeat=5)
Traceback (most recent call last):
...
perftester.perftester.IncorrectArgumentError: For memory tests, you can only set repeat, not number.
>>> pt.config.set_defaults("memory", repeat=5, number=100)
Traceback (most recent call last):
...
perftester.perftester.IncorrectArgumentError: For memory tests, you can only set repeat, not number.
>>> pt.config.defaults = original_defaults
**请注意,在这个片段中,除了输出之外,我使用的唯一其他工具是省略号,用于表示在一些命令后抛出的异常。**
# ****结论****
**也许你认为我没有给你展示太多的`doctest` …你是对的。但是你所学到的将足以让你编写甚至相当高级的测试;在我看来,这就是你需要了解的关于`doctest` ing 的大部分内容。**
**这并不意味着我们`doctest`旅程的结束。我们将在下一篇文章中回到这个问题,因为`doctest` ing 提供了更高级的工具,也许您会想要使用。例如,我们将学习如何在测试驱动开发(TDD)中使用`doctest`;以及如何在 PoC 项目中有效地使用它。**
# **脚注**
**在`doctest`的情况下,对于初学者来说基本不代表。虽然我几乎每天都在使用这个包,但我很少使用比本文中介绍的工具更高级的工具。**
# **资源**
**<https://medium.com/geekculture/make-yourself-enjoy-writing-unit-tests-e639711c10bd> <https://www.markdownguide.org/> <https://github.com/nyggus/perftester> <https://medium.com/@nyggus/membership> **
# 如何在 Python 中测试函数是否抛出异常
> 原文:<https://towardsdatascience.com/python-exception-test-290366618e7d>
## 编写测试用例来验证函数是否会用预期的消息引发异常

杰里米·珀金斯在 [Unsplash](https://unsplash.com/s/photos/error?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上拍摄的照片
软件工程中最基本的方面之一是错误处理。软件应用程序可能由于许多不同的原因而中断,因此能够以一种能够有效处理和报告异常的方式编写代码是非常重要的。
然而,在源代码中处理异常是不够的。测试您的代码是否按预期处理和报告异常更加重要。测试可确保在适当的时候引发异常,并给出正确的消息。
在今天的文章中,我们将演示如何编写测试用例来测试某个函数在某个事件发生时是否会引发预期的异常。更具体地说,我们将使用 Python 中最流行的两个测试包,即`unittest`和`pytest`,展示如何做到这一点。
首先,让我们创建一个在特定事件发生时引发异常的示例函数。然后,我们将通过一步一步的指导,最终帮助您测试异常处理。
def greet(greeting):
if greeting not in ('hello', 'hi'):
raise ValueError(f'{greeting} is not allowed')
print(greeting + ' world!')
如果输入的`greeting`参数不等于`hello`或`hi`,我们的`greet`函数将引发一个`ValueError`。现在让我们在实践中看看如何正确地测试函数使用`unittest`和`pytest`引发了预期的异常。
## 用 unittest 测试异常处理
为了创建一个测试用例来测试当输入参数不包含预期值时`greet()`是否正在引发`ValueError`,我们可以利用下面概述的`assertRaises`方法:
import unittestclass MyTestCase(unittest.TestCase): def test_greet_raises(self):
self.assertRaises(ValueError, greet, 'bye')if name == 'main':
unittest.main()
或者,您甚至可以使用上下文管理器,如下例所示:
import unittestclass MyTestCase(unittest.TestCase):def test_greet_raises(self):
with self.assertRaises(ValueError) as context:
greet('bye')if name == 'main':
unittest.main()
我们甚至可以使用下面的方法测试异常所报告的实际消息:
import unittestclass MyTestCase(unittest.TestCase):def test_greet_raises(self):
with self.assertRaises(ValueError) as context :
greet('bye')
self.assertEqual('bye is not allowed', str(context.exception))if name == 'main':
unittest.main()
## 用 pytest 测试异常处理
同样,我们可以使用`pytest`来断言一个函数引发了一个异常。
import pytestdef test_greet():
with pytest.raises(ValueError):
greet('bye')
同样,我们也可以测试我们预期会引发的异常所报告的错误消息:
import pytestdef test_greet():
with pytest.raises(ValueError, match='bye is not allowed'):
greet('bye')
## 最后的想法
测试覆盖率是最重要的指标之一,您可以用它来判断源代码测试的好坏。除了良好的设计和高效的代码,确保代码的每个方面都按预期运行也很重要,这样你就可以将错误减到最少。这只能通过彻底的测试来实现。
在今天的简短教程中,我们演示了如何测试引发异常的函数。除了测试流程的“良好路径”,确保在源代码中正确处理错误也很重要。
[**成为会员**](https://gmyrianthous.medium.com/membership) **阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。**
<https://gmyrianthous.medium.com/membership>
**相关文章你可能也喜欢**
</diagrams-as-code-python-d9cbaa959ed5> </big-o-notation-32fb458e5260> </how-to-merge-pandas-dataframes-221e49c41bec>
# Python 异常——什么、为什么和如何?
> 原文:<https://towardsdatascience.com/python-exceptions-what-why-and-how-44661cad3cd4>
## 理解 Python 中异常的工作方式以及如何正确使用它们

在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上由 [Erwan Hesry](https://unsplash.com/@erwanhesry?utm_source=medium&utm_medium=referral) 拍摄的照片
# 什么是例外?
异常就像一些恼人的朋友,在您的 Python 之旅中,您会以这样或那样的方式遇到它们。任何时候你试图做一些不守规矩的事情或者让 Python 做一些 Python 不喜欢的事情,你很可能会被移交给一些*异常*,它们会根据“冒犯”的程度停止或者只是警告你
> Python 用自己的方式告诉你,你所要求的要么无法实现,要么需要做些事情来避免将来执行失败。
在这篇文章中,我们将深入 Python *异常*的细节。我们将学习如何处理它们,将它们理解为 Python *类*,如何基于异常类型定制我们的响应,还将知道如何创建自己的异常。
由于异常本身就是类,我们将使用大量对 Python 类的引用。因此,如果你需要快速复习,你可以阅读我的关于 Python 中面向对象编程的系列文章。
# 处理异常
异常是 Python 告诉你事情没有按计划或预期进行的方式。在交互式和特别的编码场景中,比如数据分析,我们通常不需要处理异常,因为我们在面对异常并继续前进时会修复它们。但是如果你想把你的脚本用于任何类型的自动化任务,比如一个 [ETL](https://en.wikipedia.org/wiki/Extract,_transform,_load) 任务,不处理异常就像把一把上膛的枪放在无人看管的地方,对准你的脚。你永远不知道什么时候会有人给它一个晃动,无意中拍摄它!
> *我们所说的*处理异常*基本上是指设置一组命令,这样如果发生*异常*,Python 就知道下一步该做什么,而不仅仅是抛出一个 fit。*
## 最简单的方法
为了捕获异常,我们使用了一个名为`try-except`的代码块。我们将被怀疑是错误来源的那段代码放在`try`块中,然后在`except`块中捕获并设计响应。查看以下示例,其中函数`calcArea`被定义为从用户处接收圆的`radius`值,并返回圆的计算面积。
Area for radius 5 = 78.53999999999999Something is wrong with aArea for radius 4 = 50.2656Area for radius 8 = 201.0624Something is wrong with bArea for radius 0 = 0.0
在`for`循环中,
Python 首先执行`try`块内的`calcArea()`。
* 它试图计算面积并将其保存到名为`area`的变量中。
* 如果没有遇到任何异常,它打印出新创建的变量`area`。
* 但是如果它遇到了问题,它就转移到`except`代码块。
在`except`代码块内,
* `Exception`类被捕获或存储,并被命名为`e`,或者给它你想要的任何名字。这个命名对象随后用于访问*异常*类*对象*的元素。
但是异常处理有什么好处呢?
请注意,一旦`for`循环获得意外值,即`strings`,它不会中断。如果我们不处理从`calcArea()`抛出的异常,那么`for`循环就会在执行完第一个元素后中断。
🛑看你自己试试看!尝试在不使用`try-except`的情况下在循环中运行`calcArea()`。
## 让我们稍微投入一点
`try-except`代码块有两个附加分支:`else`和`finally`。
* `else`代码块只有在没有异常发生的情况下才会执行。我们可以用它以一种更简洁的方式打印出一个成功操作的定制消息——而不是把它塞在`try`块中。
* `finally`无论有无异常,代码块**总是**被执行。通常用于留下一个脚印以标志操作的结束。
calcArea() ran successfully for 5
Area for input 5 = 78.53999999999999
calcArea() run completed on 2022-03-24 10:36:49.778314
Something is wrong for a.
Area for input a = None
calcArea() run completed on 2022-03-24 10:36:49.778314
calcArea() ran successfully for 4
Area for input 4 = 50.2656
calcArea() run completed on 2022-03-24 10:36:49.779315
calcArea() ran successfully for 8
Area for input 8 = 201.0624
calcArea() run completed on 2022-03-24 10:36:49.779315
Something is wrong for b.
Area for input b = None
calcArea() run completed on 2022-03-24 10:36:49.779315
calcArea() ran successfully for 0
Area for input 0 = 0.0
calcArea() run completed on 2022-03-24 10:36:49.779315
## 更有表现力怎么样!
我们的`try-except`模块告诉我们有问题,但没有告诉我们到底哪里出了问题。在本节中,我们将对此进行研究。
正如我们已经知道的,Python 中的异常基本上是类本身,它们还带有一些 Python 类的内置变量。在下面的例子中,我们将使用这些类属性中的一些来使错误消息更有表现力。
让我们先看看例子,然后我们会回来讨论例子中使用的类属性。
calcArea() ran successfully for 5
Area for input 5 = 78.53999999999999
calcArea() run completed on 2022-03-24 10:36:52.476687
Area couldn't be calculated for a.
Error type: ValueError
Error msg: could not convert string to float: 'a'.
Area for input a = None
calcArea() run completed on 2022-03-24 10:36:52.476687
calcArea() ran successfully for 4
Area for input 4 = 50.2656
calcArea() run completed on 2022-03-24 10:36:52.476687
calcArea() ran successfully for 8
Area for input 8 = 201.0624
calcArea() run completed on 2022-03-24 10:36:52.476687
Area couldn't be calculated for b.
Error type: ValueError
Error msg: could not convert string to float: 'b'.
Area for input b = None
calcArea() run completed on 2022-03-24 10:36:52.477706
calcArea() ran successfully for 0
Area for input 0 = 0.0
calcArea() run completed on 2022-03-24 10:36:52.477706
* `type(e).__name__`:打印出*异常*的*子类*名称。`except`块在遇到`string`作为输入时打印出`TypeError`。
* `print(e)`:打印出整个错误信息。
**但是如果** `**e**` **是一个异常类的*对象*,我们不应该用类似** `**e.print_something()**` **的东西吗?**
这是可能的,因为 Python *异常*类带有一个名为`__str__()`的内置方法。在一个类中定义这个方法可以直接打印出一个类。
🛑试试看!不要调用`e`,而是调用`e.__str__()`来打印错误信息。
## 如果我们需要一些定制呢?
从我们的`calcArea()`函数中,我们可以看到`ValueError`是一个重复的错误类型——用户输入的是`string`而不是数值,因此 Python 无法执行数值运算。如果我们定制`try-except`块来适应这种常见的错误类型,并在完全忽略用户的输入之前给用户另一个机会,怎么样?
为此,我们可以专门为`ValueError`异常调用一个单独的代码块。在这里我们将检查或验证输入`type`以确保它只是`int`或`float`类型。否则,我们将继续提示用户输入一个数值作为输入。
为了简化验证过程,让我们定义一个名为`validate_input()`的函数——它将进行检查,如果输入变量是`float`类型,则返回`True`,否则返回`False`状态。
def validate_input(value):
try:
value = float(value)
return True
except: return False
calcArea() ran successfully for 5
Area for input 5 = 78.53999999999999
calcArea() run completed on 2022-03-24 10:36:57.001287
Input data is not numeric type.
Please input a numeric value: 8
Area for input 8 = 201.0624
calcArea() run completed on 2022-03-24 10:37:00.426775
calcArea() ran successfully for 4
Area for input 4 = 50.2656
calcArea() run completed on 2022-03-24 10:37:00.426775
calcArea() ran successfully for 8
Area for input 8 = 201.0624
calcArea() run completed on 2022-03-24 10:37:00.426775
Input data is not numeric type.
Please input a numeric value: k
Please input a numeric value: 999
Area for input 999 = 3135319.9416
calcArea() run completed on 2022-03-24 10:37:04.927187
calcArea() ran successfully for 0
Area for input 0 = 0.0
calcArea() run completed on 2022-03-24 10:37:04.927187
为了定制对`ValueError`的响应,我们添加了一个专门的代码块来只捕获`ValueError`异常。在代码异常块内部,
* 我们正在给用户打印一条消息。
* 然后我们在一个循环中请求一个数字输入,直到输入类型是数字。
* 最后,在收到数字类型输入时,我们再次调用`calcArea()`。
注意一些事情,
📌我们将`ValueError`代码块放在了`Exception`代码块的上面。否则,我们的代码永远不会到达`ValueError`部分。因为它是 Exception 类的子类,所以它会被`Exception`代码块捕获。
📌我们没有在`ValueError`代码块中使用`ValueError as e`,但是**可以有**。因为我们不需要任何*类*属性来打印或用于其他用途,所以我们没有捕获 *TypeError* 类作为对象。
🛑考虑这样一个场景,你可能想要合并其他特定的*异常*类型。你将如何整合它们?
# 将异常理解为一个类
我在这篇文章中多次提到过*异常*是类,但是除了展示一些应用之外,并没有太多的细节。让我们在本节中尝试这样做。
> *在 Python 中,所有的*异常*都是从被称为* `*BaseException*` *的*类*中派生出来的。*
在`BaseException`下面,所有的内置异常被安排成一个层次结构。`BaseException`下的四个主要子类是:
* 系统退出
* 键盘中断
* 发电机出口
* 例外
我们通常关心或想要采取行动的异常是*异常*子类下的异常。*异常*子类又包含几组*子类*。下面是 Python 类的部分树层次结构。详情请参考来自 python.org 的官方文件。
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StopAsyncIteration
+-- ArithmeticError
| +-- FloatingPointError
| +-- OverflowError
| +-- ZeroDivisionError
+-- AssertionError
+-- AttributeError
+-- BufferError
+-- EOFError
+-- ImportError
| +-- ModuleNotFoundError
+-- LookupError
🛑你可以在这些类上运行用于检查公共类层次关系的方法来自己检查上面的层次结构。试试跑步,
* `print(BaseException.__subclasses__())`:你应该看到`BaseException`类下的主要子类。
* `print(issubclass(ValueError, Exception))`:你应该看到`True`作为返回值,因为`ValueError`是`Exception`的子类。
# 实现我们自己的异常
到目前为止,我们只使用了 Python 已经定义的异常。如果我们想要一个进一步定制的异常怎么办?为此,我们可以定义自己的异常类。
> *由于*异常*是*类*,我们可以像编写常规 Python* 类*一样编写自己的自定义*异常*类。*
在我们的例子中,假设我们希望将用户输入限制在一个可管理的范围内,这样用户就不能要求程序计算任意大半径的面积。出于演示的目的,假设我们想将其限制在`50`英寸以内。
我们可以通过在`calcArea()`函数中添加一个`if`条件来轻松做到这一点。但是使用`exceptions`有一种更好的方法。
# 编写自定义异常
对于我们的解决方案,我们将首先定义我们自己的异常类`Above50Error`。
class Above50Error(Exception):
def init(self, value):
Exception.init(self)
self.value = value
def __str__(self):
return "Input {} is larger than 50 inches".format(self.value)
`Above50Error`异常类被创建为内置*异常*异常*类*的子类,这样它就继承了所有的属性。
* 我们用一个参数初始化它:`value`,然后将它存储为一个名为`value`的类变量。
* 然后我们重写从异常类继承的`__str__()` *方法*来打印出一条定制消息。
注意,要打印出自定义消息,我们可以在初始化`Above50Error`时添加一条消息作为参数,并将其传递给`Exception`初始化。`Exception` *类*会将参数作为消息传递并自定义`__str__()`方法。但是我们用更长的方式实现它只是为了演示。
🛑试试看!修改`Above50Error`,使其不需要`__str__()`方法来打印消息。
🛑:还有,你能想出一个方法来确认`Above50Error`是否真的是`Exception`的子类吗?
要了解更多关于 Python 类和子类关系的知识,你可以查看我的帖子[Python 中的面向对象编程——继承和子类](https://curious-joe.net/post/2022-03-13-oop-python-inheritance-and-subclass/oop-python-inheritance-subclass/)
## 实现自定义异常
由于`Above50Error`是一个自定义异常,所以当我们想要将它注册为异常时,我们需要推动 Python 将其提升为异常。因此,当我们将代码放在一个`try-except`代码块中时,它可以将行为捕获为异常。为此,我们使用了`raise`关键字。
所以我们修改了`calcArea()`函数,添加了一个条件来检查输入值是否高于 50。如果是,它抛出`Above50Error`异常,否则继续计算面积。
def calcArea(radius):
pi = 3.1416
radius = float(radius)
if radius > 50:
raise Above50Error(radius)
else:
area = pi * radius ** 2
return area
calcArea() ran successfully for 5
Area for input 5 = 78.53999999999999
calcArea() run completed on 2022-03-24 10:37:15.619721
calcArea() ran successfully for 5
Area for input 5 = 78.53999999999999
calcArea() run completed on 2022-03-24 10:37:15.619721
Area couldn't be calculated for 55.
Error type: Above50Error
Error msg: Input 55.0 is larger than 50 inches.
Area for input 55 = None
calcArea() run completed on 2022-03-24 10:37:15.619721
calcArea() ran successfully for 4
Area for input 4 = 50.2656
calcArea() run completed on 2022-03-24 10:37:15.619721
calcArea() ran successfully for 8
Area for input 8 = 201.0624
calcArea() run completed on 2022-03-24 10:37:15.619721
Input data is not numeric type.
Please input a numeric value: k
Please input a numeric value: 7
Area for input 7 = 153.9384
calcArea() run completed on 2022-03-24 10:37:20.308639
calcArea() ran successfully for 0
Area for input 0 = 0.0
calcArea() run completed on 2022-03-24 10:37:20.308639
注意`Exception`的 except 代码块是如何捕获我们定制的*异常*的。在总结之前,让我们强调一下自定义异常类的一些特性:
* 自定义异常类类似于常规的内置异常类。例如,我们可以为`Above50Error`创建一个单独的 except 代码块,就像我们为`ValueError`所做的那样。试试看!
* 此外,由于这些是类,我们可以创建它们自己的子类,以便对错误和异常进行更多的定制。
# 下一步是什么?
这将是我的 Python 系列中面向对象编程的总结。在这个系列中,我试图解释:
* [什么是 OOP,为什么要关注它?](https://curious-joe.net/post/2022-03-09-oop-in-python-what-why/oop-python-what-why/)
* [了解一个类。](https://curious-joe.net/post/2022-03-11-oop-in-python-elements-of-class/oop-python-understanding-class/)
* [理解这个概念在子类中的继承和应用。](https://curious-joe.net/post/2022-03-13-oop-python-inheritance-and-subclass/oop-python-inheritance-subclass/)
* [在类上下文中理解 Python 变量。](https://curious-joe.net/post/2202-03-16-oop-python-variables-in-class/oop-python-inheritance-subclass/)
* 理解 Python 方法。
最后,在这篇博客中,我们深入探讨了 Python 异常,作为 Python 类的一个例子。
感谢您阅读这篇文章,希望这个博客系列能让您对 Python 中的面向对象编程有一个初步的了解。在未来的系列文章中,我们将踏上另一段旅程,进入 Python *类*的世界,并有更深入的理解。
# Python f-strings 比您想象的更强大
> 原文:<https://towardsdatascience.com/python-f-strings-are-more-powerful-than-you-might-think-8271d3efbd7d>
## 了解 Python 的 f 字符串(格式化的字符串文字)的未知特性,并提升您的文本格式化知识和技能

照片由[阿玛多·洛雷罗](https://unsplash.com/@amadorloureiro?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)在 [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上拍摄
格式化字符串文字——也称为 *f 字符串*——从 Python 3.6 开始就已经存在,所以我们都知道它们是什么以及如何使用它们。然而,有一些事实和 f 弦方便的特点,你可能不知道。所以,让我们来看看一些令人敬畏的 f-string 特性,你会想在你的日常编码中使用它们。
# 日期和时间格式
用 f 字符串应用数字格式很常见,但是您知道您还可以格式化日期和时间戳字符串吗?
f 字符串可以格式化日期和时间,就像你使用`datetime.strftime`方法一样。当你意识到有更多的格式,而不仅仅是文档中提到的几种格式时,这就更好了。Python 的`strftime`也支持底层 C 实现所支持的所有格式,这可能因平台而异,这也是为什么在文档中没有提到它的原因。也就是说,你可以利用这些格式,例如使用`%F`,它相当于`%Y-%m-%d`或`%T`,它相当于`%H:%M:%S`,同样值得一提的是`%x`和`%X`,它们分别是本地首选的日期和时间格式。这些格式的使用显然不仅限于 f 字符串。有关格式的完整列表,请参考 [Linux 联机帮助页](https://manpages.debian.org/bullseye/manpages-dev/strftime.3.en.html)。
# 变量名和调试
f-string 特性(从 Python 3.8 开始)最近增加的一项功能是打印变量名和值的能力:
该功能称为*“调试”*,可与其他修改器结合使用。它还保留了空格,所以`f"{x = }"`和`f"{x=}"`会产生不同的字符串。
# 字符串表示
当打印类实例时,默认使用该类的`__str__`方法进行字符串表示。然而,如果我们想强制使用`__repr__`,我们可以使用`!r`转换标志:
我们也可以只在 f 字符串中调用`repr(some_var)`,但是使用转换标志是一个很好的原生且简洁的解决方案。
# 性能优越
强大的功能和语法糖经常伴随着性能损失,然而,当涉及到 f 字符串时,情况并非如此:
上面的例子是用`timeit`模块测试的,比如:`python -m timeit -s 'x, y = "Hello", "World"' 'f"{x} {y}"'`,正如你所看到的,f 字符串实际上是 Python 提供的所有格式化选项中最快的。因此,即使您更喜欢使用一些旧的格式选项,您也可以考虑切换到 f 字符串来提高性能。
# 格式化规范的全部功能
f 字符串支持 Python 的[格式规范迷你语言](https://docs.python.org/3/library/string.html#formatspec),所以你可以在它们的修饰符中嵌入很多格式化操作:
Python 的格式化迷你语言不仅仅包括格式化数字和日期的选项。它允许我们对齐或居中文本,添加前导零/空格,设置千位分隔符等等。显然,所有这些不仅适用于 f 字符串,也适用于所有其他格式选项。
# 嵌套 F 字符串
如果基本的 f 字符串不能满足您的格式需求,您甚至可以将它们嵌套在一起:
您可以将 f 字符串嵌入到 f 字符串中,以解决棘手的格式问题,比如向右对齐的浮点数添加美元符号,如上所示。
如果需要在格式说明符部分使用变量,也可以使用嵌套的 f 字符串。这也可以使 f 弦更具可读性:
# 条件格式
在上述嵌套 f 字符串示例的基础上,我们可以更进一步,在内部 f 字符串中使用三元条件运算符:
这可能会很快变得非常不可读,所以你可能想把它分成多行。
# λ表达式
如果你想突破 f 字符串的限制,并让阅读你代码的人感到愤怒,那么——稍加努力——你也可以使用 lambdas:
在这种情况下,lambda 表达式两边的括号是强制的,因为有了`:`,f-string 就会解释它。
# 结束语
正如我们在这里看到的,f 弦真的非常强大,比大多数人想象的要多得多。然而,这些*【未知】*特性中的大多数在 Python 文档中都有提及,所以我建议不要只是通读 f 字符串的文档页面,还要通读您可能正在使用的 Python 的任何其他模块/特性。钻研文档通常会帮助你发现一些非常有用的特性,这些特性是你在钻研 *StackOverflow* 时不会发现的。
*本文原帖*[*martinheinz . dev*](https://martinheinz.dev/blog/70?utm_source=medium&utm_medium=referral&utm_campaign=blog_post_70)
[成为会员](https://medium.com/@martin.heinz/membership)阅读 Medium 上的每一个故事。**你的会员费直接支持我和你看的其他作家。**你还可以在媒体上看到所有的故事。
<https://medium.com/@martin.heinz/membership>
你可能也喜欢…
</ultimate-ci-pipeline-for-all-of-your-python-projects-27f9019ea71a> </optimizing-memory-usage-in-python-applications-f591fc914df5>
# 如何在 Python 中创建文件系统触发器
> 原文:<https://towardsdatascience.com/python-file-change-trigger-6f8a6c94c13e>
## 如何以编程方式轻松监控文件的创建、修改和删除

照片由 [Pixabay](https://www.pexels.com/photo/multi-colored-folders-piled-up-159519/) 拍摄
假设您使用一个客户端系统,该系统将文件上传到 FTP 文件夹。一旦文件出现在文件夹中,我们就必须对其进行处理,并将其推送到数据库中。
实时仪表板正在访问数据库。因此,我们必须毫不拖延地更新数据库。
您可以运行定期任务并检查文件夹内容。但是让我们假设你更新数据库越快,对用户越好。使用周期性任务时,小延迟的成本很高。随着任务运行得越来越频繁,周期越来越短可能需要更多的资源。
我们需要构建一个文件系统触发器来完成这项任务。
# 监控文件夹中的新文件创建。
我们可以使用 Python 脚本主动监听文件夹中的文件系统事件。
我们可以从安装一个名为 Watchdog 的 Python 包开始。它可以通过 PyPI 存储库获得。
pip install watchdog# If you're using Poetry instead of Virtualenv
poetry add watchdog.
这里有一个例子。以下 Python 脚本将监视当前目录中的文件更改。它会记录所有发生的变化。
使用 Watchdog 在 Python 中监控文件更改的示例。
上述代码的重要部分是 FileProcessor 类。但是在进入之前,我们应该创建一个 observer 对象来附加一个事件处理程序。
我们可以使用 schedule 方法将事件处理程序附加到观察者。在上面的例子中,我们已经附加了它来监视当前目录及其所有下游目录中的事件。
如果您运行这段代码并在当前目录中创建新文件,我们可以看到 Python 脚本在终端上打印事件。

Python 脚本监听当前文件夹的变化-作者截屏。
我们在 observer 对象的`schedule`方法中使用了当前文件夹。您也可以使用您选择的任何路径。您也可以选择从命令行参数获取它。
下面是对将我们的脚本转换成 CLI 的代码的修改。现在,您可以使用命令行参数将路径传递给 monitor。
现在,您可以在终端中运行脚本,如下所示
python
在我之前的文章中,你可以了解更多关于使用 Python 创建命令行界面的内容。
</a-simplified-guide-to-create-clis-for-your-data-science-project-bf9bcaa26e1>
# 处理程序类中的文件更改
在我们的例子中,我们通过子类化“FileSystemEventHandler”类创建了一个事件处理程序。所有的事件处理程序都应该这样。
父类有几个文件系统事件方法的占位符。我们使用了“on_create”方法来处理所有新文件的创建。同样,您也可以使用 on_deleted、on_modified、on_moved 和 on_any_event 方法来处理其他类型的事件。
让我们更新 on_create 来处理文件并将值插入数据库。如果与您的用例无关,请随意跳过这一部分。
如果您曾经使用过 Sqlalchemy 和 Pandas,这段代码可能看起来非常熟悉。值得注意的是我们如何获得新创建文件的路径。
“FileSystemEventHandler”类中的每个事件触发器都引用其事件参数中的路径。我们可以用代码中所示的`src-path`标签来访问它。
如果您运行代码并让 Python 脚本监听更改,它也会立即将这些更改推送到数据库。
# 在后台提供您的应用程序。
到目前为止,您已经注意到我们的应用程序运行在一个活动的终端上。但在生产中这样做并不明智。终端会话的任何内容都会影响应用程序。
在后台运行此类服务的最佳方式是通过系统服务。Windows 用户可以使用工具 [NSSM](https://nssm.cc/) (非吸吮服务管理器。)如果您浏览一下他们的文档,这将非常简单。
但是在这篇文章中,我将讲述 Linux 使用 [systemctl](https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units) 的方式。
您可以通过在“/etc/systemd/system”文件夹中创建包含以下内容的文件来创建新的系统服务。你可以给它起任何扩展名为'的名字。'服务'
[Unit]
Description="Process Data"[Service]
Restart=always
WorkingDirectory=<PATH_TO_PROJECT_DIRECTORY>
ExecStart=<PATH_TO_PYTHON_EXECUTABLE> <YOUR_SCRIPT>.py[Install]
WantedBy=multi-user target
完成后,您可以在终端上运行以下命令来激活服务。
To make your new service available to systemctl utility.
$ systemctl daemon-reload # To start the service
$ systemctl start <YOUR_SERVICE_FILE_NAME>.service
这将启动该过程。现在,随着新文件在我们的 FTP 目的地上被创建,该服务将处理它们并将其上传到数据库。我们的实时数据库将毫无延迟地获得新数据。
您可以使用以下命令检查您的服务是否正常运行。
systemctl status <YOUR_SERVICE_FILE_NAME>.service

Systemctl 运行 Python 脚本——截图由[作者提供。](https://thuwarakesh.medium.com)
# 最后的想法
随着世界向更健壮的系统间集成发展,处理文件系统变化现在已经很少见了。但是这并不意味着文件系统触发器没有用处。
在许多情况下,我们需要监控新文件的创建或修改。以日志流处理为例。您可以使用这里描述的技术来处理新的日志行,并将其推送到数据仓库。
我已经用了很长时间了。我也不认为有必要降低它。
> 感谢阅读,朋友!在[**LinkedIn**](https://www.linkedin.com/in/thuwarakesh/)[**Twitter**](https://twitter.com/Thuwarakesh)[**Medium**](https://thuwarakesh.medium.com/)上跟我打招呼。
>
> 还不是中等会员?请使用此链接 [**成为会员**](https://thuwarakesh.medium.com/membership) 因为,在没有额外费用的情况下,我赚取了一点佣金。
# 面向数据科学家的 Python:选择你自己的冒险
> 原文:<https://towardsdatascience.com/python-for-data-scientists-choose-your-own-adventure-7280cc892c9c>
我们正处于八月的特殊时期,对我们许多人来说,工作项目可能会以更悠闲的速度进行,而忙碌的九月即将来临。这难道不是提升您专注于数据科学的编程技能的绝佳时机吗?
不管你有多少编码经验,你都会在本周的亮点中发现新的尝试。我们选择的九篇文章从初学者友好的指南和资源到高级主题,所以我们鼓励你创建自己的定制阅读清单。
* [**探索 Python**](/best-python-built-ins-for-beginners-17322ce1e8a4) 中的强大特性。在扩展 Python 能力的许多库和包之前,[凯蒂·哈格蒂](https://medium.com/u/94ed6e69690?source=post_page-----7280cc892c9c--------------------------------)建议经验较少的程序员熟悉 Python 的基本内置功能。
* [**没有编码经验?从这里开始**](/getting-started-with-python-for-data-scientists-a9b20058b46e) 。对于任何发现变量、数据类型和其他基本概念令人望而生畏的有抱负的数据科学家来说, [Philip Wilkinson](https://medium.com/u/ec0e018f30da?source=post_page-----7280cc892c9c--------------------------------) 面向初学者的介绍是克服最初困惑的一个很好的一站式资源。
* [**JSON 和 Python:一个有力的组合**](/how-to-work-with-json-in-python-bdfaa3074ee4) **。Lynn Kwong 这本有帮助的入门书展示了 Python 优先的数据专业人员如何充分利用 JSON (JavaScript 对象表示法)格式,这种格式通常用于不同应用程序之间的数据交换。**
* [**大数据?你的代码可以处理好**](/how-to-handle-large-datasets-in-python-1f077a7e7ecf) 。随着数据集变得越来越大,处理它们会变得更加复杂和耗时。如果你的 CSV 文件变得笨拙,Leonie Monigatti 提出了四种可供选择的文件格式供你考虑——特别是如果你仍然希望在 Python 的 Pandas 库中舒适地工作。

伊万·班杜拉在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的照片
* [**需要新的学习方法?走老派**](/top-5-python-programming-books-for-data-scientists-af6caf4ff7b) 。如果你在一个接一个地查找 StackOverflow 的答案时感到毫无灵感,本杰明·恩韦克建议你求助于书籍。他根据自己的经验分享了五个推荐选项——它们都以 Python 为中心,涵盖了与数据科学家日常工作流程相关的广泛主题。
* [**当你有心情做一个机器学习项目**](/model-selection-and-hyperparameter-tuning-on-amazon-kindle-book-reviews-with-python-6d17ec46d318) 。如果你已经准备好卷起袖子,Giovanni Valdata 的深度探讨展示了一个端到端的情感分析项目,并展示了如何用 Python 实现模型选择和超参数。
* [**熟悉机器学习系统的要素**](/components-of-a-production-ml-system-using-only-python-d6cf27405c82) 。如果你想了解 MLOps 的实际方面和生产模型的工作,凯尔·加拉丁的新帖子是一个很好的起点——它只涉及使用 Python 的过程!
* [**什么是封装,为什么要关心**](/how-to-define-nonpublic-methods-in-a-python-class-f477a1ddf3c0) 。“严格地说,在 Python 类中定义的一切都是公共的,”崔永解释道,他继续解释如何在代码中创建非公共(或私有)方法。
* [**如何在 Python 中精简单元测试**](/pytest-with-marking-mocking-and-fixtures-in-10-minutes-678d7ccd2f70) 。 [Kay Jan Wong](https://medium.com/u/fee8693930fb?source=post_page-----7280cc892c9c--------------------------------) 回到了单元测试这个始终相关的话题,并带领我们通过一个使用 pytest 包创建单元测试工作流的简化过程。
我们希望本周的选择对您有所帮助!如果你这样做了,如果你考虑通过[成为媒体成员](https://bit.ly/tds-membership)来支持写这些文章的作者,我们将不胜感激。
直到下一个变量,
TDS 编辑
# 面向数据科学家的 Python,从头到尾
> 原文:<https://towardsdatascience.com/python-for-data-scientists-from-a-to-z-12adf56713f1>
数据科学家仍在争论,严肃的编程技能是否是他们在工作中脱颖而出的必要条件。如果有什么共识的话,那就是扩展你的技能肯定没有坏处。对于一些从业者来说,这可能意味着投入时间学习更多的语言。然而,鉴于 Python 在数据科学中的优势,同样清楚的是,用这种语言为您的工具包增加深度和多功能性从来都不是一个坏主意。
本周,为了庆祝夏季最长的日子(至少在北半球),我们分享了大量的、单一焦点的 10 篇近期文章,这些文章探索了各种各样的 Python 主题。尽情享受吧!
* [**如何用右脚开始你的旅程**](/how-to-learn-new-programming-languages-easily-1e6e29d3898a) 。如果你刚刚开始学习编程,在基础上站稳脚跟是很重要的。 [Kay Jan Wong](https://medium.com/u/fee8693930fb?source=post_page-----12adf56713f1--------------------------------) 的概述与语言无关,但是初出茅庐的 python 爱好者会发现它特别有用。
* [**保持高编码标准**](/how-to-write-high-quality-python-as-a-data-scientist-cde99f582675) 。“数据科学家”和“软件工程师”是两个截然不同的角色:毫无疑问。但是正如 Eirik Berge 在他出色的初级读本中所强调的,这并不意味着你不应该关心你的 Python 输出的质量——事实上,恰恰相反,考虑到草率的代码往往会带来风险和错误。

照片由 [Georg Eiermann](https://unsplash.com/@georgeiermann?utm_source=medium&utm_medium=referral) 在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上拍摄
* [**从别人的错误中吸取教训**](/3-rookie-mistakes-to-avoid-with-python-lists-625c0e8e57df) 。当然,犯错是人之常情。尽可能主动地避免错误也是如此。在一个简明的教程中,[凯蒂·哈格蒂](https://medium.com/u/94ed6e69690?source=post_page-----12adf56713f1--------------------------------)涵盖了初学者在使用 Python 列表时容易遇到的三个常见问题,并解释了你应该做些什么来永远告别它们。
* [**扎实文档的好处**](/how-to-write-good-code-documentation-for-data-scientists-c9940aebb4f0) 。数据科学工作通常是协作性的:项目可能持续几周甚至几个月,并且需要频繁的接力棒传递。 [Madison Hunter](https://medium.com/u/6a8c6841e521?source=post_page-----12adf56713f1--------------------------------) 认为没有什么比好的文档更能让你的代码适应未来,更具可扩展性,并分享了一些可以遵循的最佳实践。
* [**让你的工具随你的数据伸缩**](/how-can-pandas-cope-with-big-datasets-c5a08446230) 。如果您是一名使用 Python 的数据科学家,那么您极有可能使用 Pandas 来完成各种数据操作任务。 [Aashish Nair](https://medium.com/u/3087ba81e065?source=post_page-----12adf56713f1--------------------------------) 探讨了解决该库主要缺点的几种策略:处理大型数据集时的性能问题。
* [**如何有效可视化多元数据**](/histogram-on-function-space-4a710241f026) 。随着数据集越来越复杂,需要利用不常用的可视化方法。[格泽戈尔兹·西科拉](https://medium.com/u/332bbbf1d16?source=post_page-----12adf56713f1--------------------------------)致力于深入研究函数直方图——这是“每一位处理多维数据的数据科学家的必备工具”—**—**配有 Python 实现,您可以根据自己的需要进行调整。
* 你的数据有层次结构吗?调和它们 。当您使用跨几个层次组织的数据时,做出准确和无偏见的预测会变得更加复杂。本顿·特里普(Benton Tripp)在此提供帮助,他提供了一个清晰的教程,教你如何编写自己的协调算法,为你的时间序列分析带来连贯性。
* [**不要忘记过去的模范国家**](/five-ways-to-remember-the-past-model-state-in-python-2c8430d29679) 。如何在不同的函数调用之间保持变量的状态?您如何确保跟踪并访问数据过去的迭代? [Theophano Mitsa](https://medium.com/u/7709c007f0ca?source=post_page-----12adf56713f1--------------------------------) 耐心地带领我们通过五种 Python 方法来解决这个关键问题(包括代码片段)!).
* [**色彩提取实用指南**](/image-color-extraction-with-python-in-4-steps-8d9370d9216e) 。您知道吗,您可以使用 Python 根据您在网上图像中找到的颜色创建自己的自定义调色板? [Boriharn K](https://medium.com/u/e20a7f1ba78f?source=post_page-----12adf56713f1--------------------------------) 将这个过程提炼为四个简单的步骤,只需要五个 Python 库和可管理的代码量。
* [**解决优化问题,Python 式**](/pymoode-differential-evolution-in-python-78e4221e5cbe) 。作为结尾,布鲁诺·斯卡利亚·c·f·莱特(Bruno Scalia c . f . Leite)[阅读了这篇文章,他专注于 pymoode 包:一个使用差分进化算法解决单目标和多目标优化问题的强大资源。](https://medium.com/u/3ce9b7482ef0?source=post_page-----12adf56713f1--------------------------------)
如果你觉得我们与你分享的帖子有价值:首先,这让我们非常高兴。第二,你会考虑让[成为](https://bit.ly/tds-membership)的媒体成员来支持写这些文章的作者吗?感谢您本周与我们共度时光!
直到下一个变量,
TDS 编辑
# 什么是 Python 全局解释器锁(GIL)?
> 原文:<https://towardsdatascience.com/python-gil-e63f18a08c65>
## 理解 Python 中全局解释器锁的用途以及它如何影响多线程

由[奥斯曼·拉纳](https://unsplash.com/@osmanrana?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)在 [Unsplash](https://unsplash.com/s/photos/lines?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 拍摄的照片
## 介绍
在我最近的一篇文章中,我讨论了编程中的一些基本概念,即*并发性*、*并行性*、*多线程*和*多处理*和[它们如何在 Python](/multithreading-multiprocessing-python-180d0975ab29) 中实现。
在这种情况下最有争议的话题之一,无疑是 Python 的**全局解释器锁**,它本质上保护解释器——在 Python 中——不是线程安全的。
在今天的文章中,我们将重温线程和多处理,并介绍全局解释器锁。此外,我们还将讨论 GIL 带来的限制,以及如何找到解决办法。我们还将讨论一些相关的概念,如线程安全和竞争条件。
## 全局解释器锁
在 **CPython** 中,全局解释器锁(GIL)是一个互斥锁,一次只允许一个线程控制 Python 解释器。换句话说,锁确保在任何给定时间只有一个线程在运行。因此,不可能利用具有线程的多个处理器。
> **GIL** ,是一个互斥体,保护对 Python 对象的访问,防止多个线程同时执行 Python 字节码— [Python Wiki](https://wiki.python.org/moin/GlobalInterpreterLock)
由于 **CPython 的内存管理不是线程安全的**, **GIL** 防止竞争情况,而**确保线程安全**。Python 中的线程共享相同的内存——这意味着当多个线程同时运行时,我们并不知道线程访问共享数据的确切顺序。
## 线程安全和竞争条件
线程安全代码以不干扰其他线程的方式操作共享数据。因此,每次只有一个线程运行,GIL 确保永远不会有**竞争条件**。
为了更好地理解什么是竞争条件,让我们考虑一个线程示例,其中我们有一个名为`x`的共享变量:
x = 10
现在让我们假设两个线程正在运行,执行下面概述的操作:
Thread 1
x += 10# Thread 2
x *= 5
现在,根据线程访问共享变量`x`的顺序,我们可能会得到不同的结果。例如,如果我们假设`Thread 1`首先访问共享变量`x`,结果将是`100`。
x += 10 # Thread 1: x = 20
x *= 5 # Thread 2: x = 100
或者,如果`Thread 2`先访问`x`,结果会不同:
x *= 5 # Thread 2: x = 50
x += 10 # Thread 1: x = 60
甚至还有第三种情况,线程 1 和 2 同时读取共享变量。在这种情况下,它们都将读入初始值`x`(等于`10`),并且根据哪个线程将最后写入其结果,`x`的结果值将是`20`(如果线程 1 最后写入其结果)或`50`(如果第二个线程最后写入其结果)。
这是一个我们称之为**竞争条件**的例子。换句话说,当系统或代码的行为依赖于由不可控事件定义的执行顺序时,就会出现争用情况。
这正是 CPython GIL 所做的。它通过确保在任何给定时间只有一个线程在运行来防止竞争情况。这让一些 Python 程序员的生活变得更加轻松,但同时也带来了限制,因为多核系统不能在线程环境中使用。
## Python 中的线程与多处理
如前所述,Python 进程不能并行运行线程,但它可以在 I/O 绑定操作期间通过上下文切换并发运行线程。请注意,并行性和并发性听起来可能是等价的术语,但实际上它们不是。
下图说明了在使用`[threading](https://docs.python.org/3/library/threading.html)` Python 库时,两个线程是如何执行的以及上下文切换是如何工作的。

Python 中的线程(并发)——来源:作者
现在,如果您想利用多处理器和多核系统的计算能力,您可能需要看看允许进程并行执行的`[multiprocessing](https://docs.python.org/3/library/multiprocessing.html)`包**。当**执行 CPU 受限的任务**时,这通常是有用的。**
****
**Python 中的多处理(并行)—来源:作者**
**多进程旁路全局解释器锁,因为它允许每个进程拥有自己的解释器,从而拥有自己的 GIL。**
**要更全面地了解 Python 中的**多处理和线程**,请务必阅读下面分享的相关文章,该文章本质上是对 Python 中的**并发**和**并行**的深入探究。**
**</multithreading-multiprocessing-python-180d0975ab29> **
## **在 Python 中避开 GIL**
**值得一提的是,一些替代的 Python 实现,即 [Jython](https://wiki.python.org/moin/Jython) 和 [IronPython](https://wiki.python.org/moin/IronPython) 没有全局解释器锁,因此它们可以利用多处理器系统。**
**现在回到 CPython,尽管对于许多程序员来说,GIL 的概念非常方便,因为它使事情变得容易得多。此外,开发人员并不真的必须与全局解释器锁交互(甚至接触),除非他们需要用 C 扩展编写。在这种情况下,当扩展阻塞 I/O 时,您必须释放 GIL,以便进程中的其他线程可以接管并执行。**
**一般来说,GIL 的概念肯定不是理想的,因为在某些情况下,现代多处理器系统不能被充分利用。然而与此同时,许多长时间运行或阻塞的操作正在 GIL 之外**执行。这些操作包括输入输出、图像处理和数字运算。因此,只有在**花费时间在 GIL 内部**的多线程操作中,GIL 才会成为瓶颈。****
**摆脱全局解释器锁是 Python 社区中的一个常见话题。取代 GIL 绝对不是一件容易的事情,因为这些特性和要求都需要满足。**
**然而,Sam Gross 最近提出了一个 CPython 的概念验证实现,它支持多线程,而没有被称为`[nogil](https://github.com/colesbury/nogil/)`的全局解释器锁。这个概念验证实际上演示了如何删除 GIL,从而使 CPyhton 解释器可以随着 CPU 内核的增加而扩展。**
**你可以在`nogil`作者的[这篇文章](https://docs.google.com/document/d/18CXhDb1ygxg-YXNBJNzfzZsDFosB5e6BfnXLlejd9l0)中找到更多细节和潜在问题的答案。即使这可能是一个潜在的解决方案,也不要期望任何改变会很快发生。**
## **最后的想法**
**在今天的文章中,我们讨论了围绕 Python 最有争议的话题之一,即全局解释器锁(又名 GIL)。我们已经看到了它的用途以及它最初被实现的原因,但是此外,我们还讨论了它的存在所带来的限制。此外,我们讨论了线程安全,并通过一个例子演示了什么是竞争条件以及 GIL 如何防止它。**
**最后,我们讨论了通过利用多核系统,最终避开 Python GIL 并实现真正并行的潜在方法。**
**[**成为会员**](https://gmyrianthous.medium.com/membership) **阅读媒体上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。****
**<https://gmyrianthous.medium.com/membership> **
****你可能也会喜欢****
**</augmented-assignments-python-caa4990811a0> ** **</dynamic-typing-in-python-307f7c22b24e> **
# Python If-Else 语句在一行中-三元运算符解释
> 原文:<https://towardsdatascience.com/python-if-else-statement-in-one-line-ternary-operator-explained-eca2be64b7cc>
## **Python 中的单行条件句?以下是何时使用和何时不使用它们。**

Emile Perron 在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的照片
Python 不是最快的编程语言,但是写起来可读性和效率都很好。大家都知道什么是[条件语句](https://docs.python.org/3/tutorial/controlflow.html),但是你知道你可以用一行 Python 代码写`if`语句吗?事实证明你可以,今天你将会了解到这一切。
读完之后,你会在一行中了解 Python 的 If Else 语句的一切。你会明白什么时候使用它们,什么时候最好避免它们,坚持传统的条件语句。
不想看书?请观看我的视频:
# 正常的 If 语句有什么问题?
完全没有。将条件语句拆分成多行代码已经是多年来的惯例。大多数编程语言要求使用花括号,因此单行的`if`语句不是一个选项。其他语言只允许在一行中编写简单的条件句。
然后是 Python。在深入一行 If Else 语句之前,让我们先简单回顾一下常规条件句。
例如,您可以使用以下语法检查条件是否为真:
age = 16if age < 18:
print('Go home.')
在这种情况下,变量`age`小于 18,因此`Go home.`被打印到控制台。您可以通过添加一个`else`条件来增加趣味,如果第一个条件是`False`,就会对该条件进行评估:
age = 19if age < 18:
print('Go home.')
else:
print('Welcome!')
这次`age`大于 18,所以`Welcome!`被打印到控制台。最后,可以添加一个或多个`elif`条件。这些用于捕获中间情况。例如,如果`age`介于 16(包括)和 18(不包括)之间,您可以打印完全不同的内容:
age = 17if age < 16:
print('Go home.')
elif 16 <= age < 18:
print('Not sure...')
else:
print('Welcome!')
变量`age`为 17,意味着`elif`下的条件为`True`,因此`Not sure...`被打印到控制台。
非常基本的东西,所以我们自然不想花这么多行代码来编写它。事实证明,您可以在 Python 中使用**三元运算符**来计算单行中的条件。
# Python 中的三元运算符
三元运算符存在于一些编程语言中,它允许您缩短简单的 If-Else 块。它接受 3 个或更多操作数:
1. **真值** —条件评估为`True`时返回的值。
2. **条件** —如果为真,则返回*值所必须满足的布尔条件。*
3. **假值** —条件评估为`False`时返回的值。
在代码中,它看起来像这样:
a if condition else b
甚至可以用 Python 的三元运算符编写 else-if 逻辑。在这种情况下,语法略有变化:
a if condition1 else b if condition2 else c
我不得不承认——这样写的时候看起来有点抽象。从下一节开始,您将看到大量的实际例子。
# 单行 If 语句(不带 Else)
单行的`if`语句仅仅意味着你删除了新的一行和缩进。您仍然在编写相同的代码,唯一的变化是它只需要一行而不是两行。
**注意**:单行`if`语句只有在条件后面有一行代码的情况下才有可能。在任何其他情况下,将将要执行的代码包装在函数中。
下面是如何将我们的两行`if`语句转换成单行条件语句:
age = 17if age < 18: print('Go home.')
和以前一样,`age`小于 18,所以`Go home.`被打印出来。
如果你想打印三行而不是一行呢?如前所述,最佳实践是将代码包装在函数中:
def print_stuff():
print('Go home.')
print('.......')
print('Now!')
age = 17
if age < 18: print_stuff()
Python 中的单行`if`语句相当无聊。当您添加一个`else`条件时,真正的时间和空间节省优势就出现了。
> 如果您添加一个或多个`else`条件,您将从单行`if`语句中获益最多。
# 单行 If-Else 语句
现在我们可以充分利用 Python 的三元运算符的威力。如果`age`小于 18,下面的代码片段将`Go home.`存储到新变量`outcome`中,否则将`Welcome!`存储到新变量中:
age = 19outcome = 'Go home.' if age < 18 else 'Welcome!'
print(outcome)
正如您所猜测的,`Welcome!`被打印到控制台,因为`age`被设置为 19。如果你想打印多行或者处理更复杂的逻辑,把你想执行的所有东西都打包到一个函数里——就像以前一样。
现在,您对三元运算符如何在简单的单行 if-else 语句中工作有了清晰的了解。我们可以通过向操作符添加更多的条件来增加复杂性。
# 单行 If-Elif-Else 语句
在单行代码中编写多个条件时,一定要小心。如果这一行有 500 个字符,逻辑仍然有效,但是几乎不可能阅读和维护它。
在一行中包含两个条件应该没问题,因为代码仍然易于阅读。如果`age`小于 16,以下示例将打印`Go home.`,如果`age`介于 16(含)和 18(不含)之间,将打印`Not Sure...`,否则将打印`Welcome`:
age = 17outcome = 'Go home.' if age < 16 else 'Not sure...' if 16 <= age < 18 else 'Welcome'
outcome
# 示例:列表操作的单行条件
将一些逻辑应用于列表包括将逻辑应用于每个列表项,从而遍历整个列表。在考虑真实世界的例子之前,让我们看看如何在一行代码中为每个列表项编写条件语句。
# 如何在一行中写 IF 和 FOR
您需要对三元运算符进行两处修改:
* 用括号将整行代码括起来`[]`
* 在最后一个`else`后面追加列表迭代代码(`for element in array`
下面是通用语法的样子:
[a if condition else b for element in array]
这并不难,但让我们用一个例子来说明这一点。下面的代码片段显示了如果范围的当前数字大于 5,则输出`+`,否则输出`-`。数字范围从 1 到 10(包括 1 和 10):
['+' if i > 5 else '-' for i in range(1, 11)]

图片 Python 中单行的 If 和 For(图片由作者提供)
现在让我们再看一个真实世界的例子。
# 学生通过考试了吗?
首先,我们将宣布一份学生名单。每个学生都是一个 Python 字典对象,有两个键:姓名和考试分数:
students = [
{'name': 'Bob', 'score': 42},
{'name': 'Kelly', 'score': 58},
{'name': 'Austin', 'score': 99},
{'name': 'Kyle', 'score': 31}
]
如果分数为 50 分或以上,我们希望打印该学生已通过考试。如果分数低于 50 分,我们希望打印该学生未通过考试。
在传统的 Python 语法中,我们将手动迭代列表中的每个学生,并检查分数是否大于 50:
outcomes = []for student in students:
if student['score'] > 50:
outcomes.append(f"{student['name']} passed the exam!")
else:
outcomes.append(f"{student['name']} failed the exam!")
print(outcomes)

图 2-使用传统 Python 语法的列表迭代(图片由作者提供)
代码可以工作,但是我们需要 5 行代码来进行简单的检查并存储结果。您可以使用新获得的知识将代码量减少到一行:
outcomes = [f"{student['name']} passed the exam!" if student['score'] > 50 else f"{student['name']} failed the exam!" for student in students]
print(outcomes)

图 3-一行条件语句和一个 Python 循环(图片由作者提供)
结果是相同的,但是我们有一个更短更整洁的代码。它只是处于不可读的边界上,这通常是与三元运算符和单行循环的权衡。你通常不能同时拥有可读的代码和简短的 Python 脚本。
> 仅仅因为你能在一行中写一个条件,并不意味着你应该。可读性是优先考虑的。
让我们看看在哪些情况下你更适合传统的`if`陈述。
# 小心单行条件句
代码占用更少的垂直空间并不意味着它更容易阅读。现在你会看到一个完美的例子。
下面的代码片段检查每个可能等级(1–5)的条件,最后一个`else`条件捕获无效输入。这些条件需要编写 12 行代码,但是整个代码片段可读性很强:
grade = 1if grade == 1:
print('Grade = 1')
elif grade == 2:
print('Grade = 2')
elif grade == 3:
print('Grade = 3')
elif grade == 4:
print('Grade = 4')
elif grade == 5:
print('Grade = 5')
else:
print('Impossible grade!')
正如所料,您将看到`Grade = 1`被打印到控制台,但这不是我们感兴趣的。我们想把上面的代码片段翻译成一行带有三元运算符的 if-else 语句。
这是可能的——但是最终的结果是混乱和不可读的:
grade = 1outcome = 'Grade = 1' if grade == 1 else 'Grade = 2' if grade == 2 else 'Grade = 3' if grade == 3 else 'Grade = 4' if grade == 4 else 'Grade = 5' if grade == 5 else 'Impossible grade'
print(outcome)
这是一个极端情况的例子,你有多个条件需要评估。最好坚持使用传统的`if`语句,即使它们占用更多的垂直空间。
**带回家点**:拥有两个以上条件的三元运算符简直是编写和调试的噩梦。
# 结论
这就是你需要知道的关于 Python 中单行 if-else 语句的一切。您已经学习了关于三元运算符的所有内容,以及如何编写从单个`if`到中间五个条件的条件。
记住保持你的代码简单。最终,更易于阅读和维护的代码是写得更好的代码。仅仅因为你可以把所有的东西都塞进一行,并不意味着你应该这样做。一旦你需要做出一些改变,你会后悔的。
编写长条件的一种更简洁的方法是使用[结构模式匹配](https://betterdatascience.com/python-structural-pattern-matching/)——这是 Python 3.10 中引入的新特性。它为 Python 带来了受人喜爱的`switch`语句,以获得额外的可读性和开发速度。
*大家怎么看待 Python 中的单行 if-else 语句?你经常使用它们吗?还是你已经转向结构模式匹配了?请在下面的评论区告诉我。*
*喜欢这篇文章吗?成为* [*中等会员*](https://medium.com/@radecicdario/membership) *继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。*
[https://medium.com/@radecicdario/membership](https://medium.com/@radecicdario/membership)
# 保持联系
* 注册我的[简讯](https://mailchi.mp/46a3d2989d9b/bdssubscribe)
* 在 YouTube[上订阅](https://www.youtube.com/c/BetterDataScience)
* 在 [LinkedIn](https://www.linkedin.com/in/darioradecic/) 上连接
*原载于 2022 年 1 月 10 日 https://betterdatascience.com**[*。*](https://betterdatascience.com/python-if-else-one-line/)*
# Python 接口:数据科学家为什么要关心?
> 原文:<https://towardsdatascience.com/python-interfaces-why-should-a-data-scientist-care-2ed7ff80f225>
## 类接口,抽象层,继承,那不是软件开发者的问题吗?作为一名数据科学家,您为什么要关心呢?

图片作者。
接口使得几乎所有我们喜爱的数据科学库成为可能。这是一个足够好的理由,至少对我来说,去关心。但是让我们深入探讨这个问题。在当前故事的上下文中,**接口**是一个**面向对象(OO)** 的概念,用于**定义其他对象的属性和行为**。
当我们要设计一个软件时,界面是很方便的:
* 依赖于尚不存在但将来会存在的对象(例如,插件或用户定义的对象)
* 允许具有相同核心行为但功能和内部逻辑略有不同的可互换对象
* 将核心逻辑与外部依赖项(如数据库或外部 API)隔离开来
这些想法听起来很有前途,但是说实话,它们听起来更像软件开发人员的行话(事实上,是一个面向对象的软件开发人员)。作为一名数据科学家,你为什么要关心呢?
* 我们喜欢的大多数库,如 Keras 或 scikit-learn,都使用接口来定义模型属性。它们中的大多数允许您在模型中编写定制对象。
* 如果你需要用像 sklearn 的 API 那样令人愉悦的 API 来编写你自己的工具,你将需要接口。
* 在 Python 中,一切都是对象,所以了解 OO 的基础知识是必须的。
## 为什么是界面?
思考下面这个问题。我们有两个模型(每一个都是一个类,*层 1* 和*层 2* ),具有*拟合*和*预测*方法。我们还有另一个模型(另一个类, *LayeredModel* ),它将两个*层*模型以某种方式组合在一起。此时,我们测试您的代码,一切正常;是吗?

类图:没有接口的模型组合。图片作者。
虽然 *LayeredModel* 可以工作,但是它有几个弱点:
* 如果你改变了*图层 1* 或*图层 2、*中的某个东西,很可能你也需要改变*图层模型*;这是导致灾难的原因。
* 两个模型, *Layer1* 和 *Layer2* ,行为相同( *fit* 和 *predict* ),但是没有明确的方式陈述这一点。
* 如果我们想添加更多的层会发生什么?
* 如果 *LayerdModel* 使用图层,那让 *LayerdModel* 自己做一个图层不是很有意义吗?这样做将允许构建更复杂的组合。
我们如何解决这样的问题?
进入界面。
我们为模型定义了一个接口(*基础层*)。一个复合模型,如 *LayeredModel,*将依赖于实现该接口的模型。然后,我们对这些层进行编码,使它们都实现(遵守)接口。这样做可以解决我们的大部分问题。

类图:中间有接口依赖的模型组合。图片作者。
唯一缺少的是让*layer model*也实现接口,我们将创建一个简单干净的方法来构建可堆叠模型的管道。
在 Python 中有许多定义和使用接口的方法,它们都有各自的优缺点。在这个故事中,我们将回顾最常见的接口声明方式,并浏览一些示例和常见模式。
## 故事结构
* 声明接口
-非正式接口
-抽象基类(ABC)
-协议
- zope.interface
-利弊总结
* 使用 ABC 构建复杂模型
* 使用协议构建复杂模型
* ABC:部分实施
* ABC 对协议和多重继承问题
* 最后的话
## 声明接口—非正式接口
在 Python 中定义接口最简单的方法是通过一个常规类;以下示例定义了一个类,用作 sklearn 的 API 样式模型的接口:
您可以看到 *fit* 和 *predict* 方法没有实现,只是用类型(注释)和一个 docstring 描述了它们应该做的事情。
我们将通过继承该接口并覆盖 *fit* 和 *predict* 方法来使用该接口。我们假设从接口继承的所有类都实现了接口:
这种方法有一个严重的缺点;如果我们从接口继承,但是什么都不做(“传递”),那么子类将有方法,但是它们不会被实现(它们不返回任何东西)。因此,我们不能假设所有的子类都实现了接口。这种行为会给我们的代码带来问题:
解决这个问题的一个方法是定义一个更强的接口,在这个接口中,我们在所有方法中引发一个“NotImplementedError”异常。这样做不允许我们重复相同的继承然后什么都不做(“传递”)的反模式:
这种声明接口的方式(非正式的)有一个很大的缺点;除了在名称中包含 interface 一词之外,没有明确的方法来说明该类是一个接口。记住,我们使用接口是为了让代码更加清晰。另一个缺点是,这些接口类仍然可以像普通类一样被实例化,这并不是很好。
## 声明接口—抽象基类(ABC)
Python 的 ABC(抽象基类)来自 *abc* 模块,解决了非正式接口产生的大部分问题。
为了创建相同的接口,我们创建一个类并从 ABC 继承,然后对未实现的方法使用 *abstractmethod* decorator:
通过这样做,我们实现了:
* **清晰:**很明显,这不是一个普通的班级;它显然是一个抽象层,应该被继承。
* **实现约束:**如果我们从接口继承而不实现方法,就会引发错误。
* 实例约束:如果我们试图实例化接口,将会引发一个异常。
实施限制的示例:
正确实施的示例:
使用抽象基类是声明接口的首选方法,但是有一点需要注意。它们太笼统了;ABC 可以用于许多其他事情。当一个 ABC 仅仅是一个接口或者更多的东西时,这一点并不总是清楚的(我们将在随后的部分中对此进行研究)。
## 声明接口—协议
协议是接口领域的新生事物。它首先出现在 PEP 544 for Python 3.8 的类型模块中。这是一种定义接口的隐式方式;然而,它只有在使用类型提示和进行静态类型检查(例如,mypy)时才有帮助。然而,我发现这种声明接口的方式是我个人最喜欢的。无论如何,如果你现在不使用类型提示和静态类型检查,你可能应该这样做。
声明接口的方式与 ABC 非常相似,但是我们从协议继承,并且不使用 *abatractmethod* decorator。
要实现接口,我们只需遵循协议类型:
您可以看到没有任何引用(除了 docstring)表明这个类与接口有某种联系。
这是另一个例子:
这是一个不遵守协议的类的示例:
如您所见,没有实际引用该协议;事实上,你可以问问自己协议这种东西有什么用。
最后,协议的用途来了。
现在让我们定义一些使用模型接口(协议)的代码(带有静态类型信息),在本例中是一个函数:
然后使用带有 *DummyLayer* 和 *AnotherDummyLayer* 的函数,如果我们进行静态类型检查,一切都是完美的。但是,如果我们使用 *WrongDummyLayer* 作为函数参数,就会出现错误;因为我们声明该函数的参数符合协议,而 *WrongDummyLayer* 不符合协议。
对协议的主要批评是它不符合 Python 的[Zen(PEP 20)](https://peps.python.org/pep-0020/)
> …显式比隐式好…
>
> Python 的禅
## 声明接口— zope.interface
界面之旅的最后一站是 zope.interface,它是一个第三方库。我没有使用过这种声明接口的方式;然而,出于传统原因,我不得不将它包括在内。从 Python 2 开始就有了。
下面是我们声明接口的方式:
我们从 zope.interface.Interface 继承,到目前为止,一切顺利。我们使用 Zope . interface . implementer decorator 来声明某个类实现了接口:
这里是我发现第一个问题的地方。在这个特例中, *fit* 方法返回类本身的一个实例(“return self”);目的是像 *fit()那样做链接。预测()*。在前面的例子中,实现者的 *fit* 方法返回的对象类型是接口类型。静态类型检查没有错误。然而,在本例中,有一个错误。我们必须将其修改为:
这听起来不是什么大事,但确实如此。就类型而言,说一个类实现一个接口意味着实现的是与接口相同的类型。所以一般来说,我们想说 *fit* 方法返回一个接口类型的对象。
## 声明接口——利弊总结
**非正式接口**优点:
* 直觉的
* 不需要依赖
**非正式接口**缺点:
* 声明接口时意图不太明确
**ABC** 优点:
* 直觉的
* 强大的功能
**ABC** 缺点:
* 很难说基类只是一个接口还是一个更一般的抽象层,因此是另一种设计模式
**协议**优点:
* 简单的
* 直觉的
* 优雅的
**协议**缺点:
* 隐式(搅乱了 Python 的禅)
* 没有静态类型检查是没有用的
赞成者:
* 非常明确的意图,名字说它“接口”
zope.interface 缺点:
* 第三方依赖性
* 不太擅长类型提示
## 使用 ABC 构建复杂模型
现在我们来看一个使用用 ABCs 声明的接口的实际例子。让我们定义我们的*基础层接口;这个*接口*是*与前面的例子非常相似:
然后,我们构建一个模型,该模型使用遵循*基础层*接口的模型(层)。这个模型*layereddmeanmodel*本身就是一个接口实现者:
然后我们建立两个简单的模型,一个总是预测 0,另一个总是预测 1:
我们使用模型:
注意,我们可以用接口定义做很多事情。我们本可以在*layereddmeanmodel 中使用 N 层;事实上,我们可以使用 *LayeredMeanModels* 作为另一个 *LayeredMeanModel 的层。这就是接口的力量。**
## 使用协议构建复杂模型
现在我们重复上一个例子,但是使用协议。代码非常相似,除了 ABC 继承和 *abstractmethod* decorator。
同样,接口(协议)仍然是隐式的,只有在静态类型检查器中出现类型错误时才会出现。
## ABC:部分实施
当我们谈到用 ABC 声明接口时,我们说我们可以将 ABC 用于比接口更多的地方。ABC 的主要用途之一是用于部分实施。创建一个包含一些抽象方法(未实现)和一些可能使用抽象方法的已实现方法的 ABC 是很常见的。
让我们对这个对话进行编码,我们使用与之前类似的例子,但是现在我们包括了一个 *fit_predict* 方法(已实现),它使用了 *fit* 和 *predict* 方法(未实现):
因此,当我们从基类继承并实现 *fit* 和 *predict* 方法时, *fit_predict* 方法通过继承变得可用:
这个例子清楚地显示了 ABC 是如何比接口更普遍地被使用的。
## ABC 对协议和多重继承问题
当仅仅使用 ABC 作为接口时,一个潜在的“问题”是多重继承。当我们有两个实现相同接口的对象时:
它们之间非常相似:
除了一些属性(在这种情况下,name 属性),我们希望避免重复的代码,而是让两个 *LayerZeros* 从第四个类继承 fit 和 predict 方法,在这种情况下,LayerZerosMixin:
我们最终会发现两个分层的人都有多个父母。大多数 OO 开发人员不赞成多重继承;有些人甚至认为它是反模式。这就是为什么许多 OO 语言不允许多重继承。在 Python 中,将单词 Mixin 添加到额外继承类的标题中,可以神奇地解决这个问题。这是面向对象社区中正在进行的辩论。
逻辑层面的问题,问题是多重继承有时会打破继承的规范意义,一个*就是一个*关系。例如,假设我们有雇员和一个收银机来支付他们的工资。假设我们有一个支付员工工资的 *Person* 类和一个 *CashRegister* 类。如果我们做了类似于 *Employee(Person)* 的事情,即 *Employee* 类继承自 *Person* ,那么一切都没问题,因为该雇员是一个人(至少目前是这样)。但是我们可以做一些类似于 *Employee(Person,CashRegister)* 的事情,在 *Employee* 类中包含支付功能。毕竟,报酬决定了就业。这很棘手,因为员工不是收银员。在 Python 和其他支持多重继承的语言中,一种表明*雇员*不是*收银机*的方法是将类名重写为*收银机混合信息*。
正如我所说,这是一场持续的辩论。
## 最后的话
正确的选择是一个偏好的问题。使用 ABC 还是协议由你决定。就个人而言,我对接口使用协议,对部分实现使用 ABCs 这是我自己的规则。
数据科学家的唯一工作是创建 jupyter 笔记本和腌模型的日子已经一去不复返了。很可能你会被期望开发成品而不是模型和情节。这意味着通过数据采集,一直到模型的已部署 API,都要进行编码。换句话说,您需要将您的数据科学技能与软件开发结合起来。在这一点上,接口将使你的生活更容易,你的代码更干净。
我希望这个故事对你有用。如果你想知道更多类似的故事,请订阅。
<https://medium.com/subscribe/@diego-barba>
喜欢这个故事吗?通过我下面的推荐链接成为一个媒体成员来支持我的写作。无限制地访问我的故事和许多其他内容。
<https://medium.com/@diego-barba/membership>
如果我错过了什么,请让我知道。对于任何质疑、批评等。,留言评论。
# Python 将快 64%——Python 3.10 与 Python 3.11 基准测试
> 原文:<https://towardsdatascience.com/python-is-about-to-become-64-faster-python-3-10-vs-python-3-11-benchmark-3a6abcc25b90>
## 为 Python 3.11 激动吧——终于到了显著提高性能的时候了

马修·布罗德尔在 Unsplash[上的照片](https://unsplash.com?utm_source=medium&utm_medium=referral)
众所周知,Python 不是最快的编程语言。这种情况即将改变,或者至少会朝着正确的方向发展。最新的 Python 版本——Python 3.11——预计将于 2022 年 10 月发布。更好的是有一个发布候选(RC)版本可供我们使用。
这正是我们今天要做的。我们将在不同的 Docker 容器中安装 Python 3.10 和 3.11,并在一组基准测试中比较它们。我计划使用 [pyperformance 包](https://pyperformance.readthedocs.io/)来完成这项工作,因为它将完成所有繁重的工作。
**TL;DR** —平均来说,Python 3.11 比 Python 3.10 快 14%。在一些基准测试中,新版本稍微慢了一些,但在其他测试中,它的速度提高了 64%。我在配有 10 核 CPU 的 M1 Pro MacBook Pro 16 上运行了基准测试。每个 Python 版本都安装在 Docker 中,Docker 使用了 5 个逻辑 CPU 内核。相对而言,您的里程可能会有所不同,但您应该会看到类似的结果。
不想看书?请观看我的视频:
# 如何在 Docker 中安装 Python 3.11
如果你想跟着做,你需要安装 Docker。它是任何数据科学工具带中的必备工具,因此不应该成为问题。Docker 运行后,打开两个终端标签。
首先,我们将在后台获取并运行 Python 3.10:
docker run -t -d python:3.10.4-bullseye
在第二个例子中,我们将做同样的事情,但针对 Python 3.11:
docker run -t -d python:3.11-rc-bullseye
Docker 将需要一些时间来下载和启动这两个图像,这取决于您的硬件配置和互联网速度。完成后,您可以打开两个 Visual Studio 代码窗口,并将它们附加到 Docker 容器(使用 Docker VSCode 扩展,右键单击图像,并选择“附加 Visual Studio 代码”)。或者,您也可以只附加壳。
进入容器后,启动新的 VSCode 集成终端并检查 Python 版本:

图 Visual Studio 代码中附加的 Python 3.10 和 3.11 容器(图片由作者提供)
你可以看到,我左边是 Python 3.10,右边是 Python 3.11。接下来,我们将安装`pyperformance`包并运行基准测试。
# 如何用 PyPerformance 运行 Python 基准测试
好了,我们让两个容器都运行并附加在 VSCode 中。那是困难的部分。
下一步是安装`pyperformance`包。在**的两个**容器中运行以下命令:
python3 -m pip install pyperformance
安装后,在 Python 3.10 容器附带的 VSCode 窗口中运行以下 shell 命令:
pyperformance run -o py310.json
并在 Python 3.11 容器中运行类似的命令:
pyperformance run -o py311.json
这些命令将运行几十个基准测试,所以去喝杯咖啡吧——这需要一些时间。

图 2 —在 Docker 中的 3.10 和 3.11 上运行 Python 基准测试(图片由作者提供)
一旦完成,`pyperformance`会将输出分别保存到`py310.json`和`py311.json`文件中:

图 3 —两个 Python 版本的基准测试都已完成(图片由作者提供)
每个文件都在一个单独的 Docker 容器中,所以为了实际比较这两个文件,您必须将 JSON 文件下载到您的本地机器上(或者将文件从一个容器上传到另一个容器)。
我已经在本地下载了它们,并在 Python 虚拟环境中安装了 pyperformance 包。接下来看看他们怎么比。
# Python 3.10 和 Python 3.11——哪个更快?
最好将两个 JSON 文件放在同一个文件夹中。在终端窗口中打开该文件夹,并运行以下 shell 命令:
pyperf compare_to py310.json py311.json --table
以下是输出结果:

图 4 — Python 3.10 与 3.11 的基准测试结果(图片由作者提供)
我统计了五次测试,其中 Python 3.10 比 Python 3.11 略快。在其他方面,Python 3.11 比 Python 3.10 快 64%。按照几何平均数,Python 3.11 比 Python 3.10 快 14%。
# Python 3.10 与 Python 3.11 基准对比总结
这就是 Python 3.10 和 Python 3.11 之间的性能比较。值得注意的是,Python 3.11 还没有发布,所以我们将一个完全稳定的版本与一个候选版本进行比较。可能正式发布后两者差距会更大。只有时间能证明一切。
*你对即将到来的 Python 版本有什么看法?看到 Python 跑得更快,你是兴奋,还是根本不在乎?请在下面的评论区告诉我。*
*喜欢这篇文章吗?成为* [*中等会员*](https://medium.com/@radecicdario/membership) *继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。*
<https://medium.com/@radecicdario/membership>
## 推荐阅读
* [学习数据科学先决条件(数学、统计和编程)的 5 本最佳书籍](https://betterdatascience.com/best-data-science-prerequisite-books/)
* [2022 年学习数据科学的前 5 本书](https://betterdatascience.com/top-books-to-learn-data-science/)
* [用 Python 打印列表的 7 种方法](https://betterdatascience.com/python-list-print/)
## 保持联系
* 雇用我作为一名[技术作家](https://betterdatascience.com/contact/)
* 在 YouTube[上订阅](https://www.youtube.com/c/BetterDataScience)
* 在 [LinkedIn](https://www.linkedin.com/in/darioradecic/) 上连接
*原载于 2022 年 4 月 28 日 https://betterdatascience.com*<https://betterdatascience.com/python-310-vs-python-311/>**。**
# Python 中的 Iterables vs 迭代器
> 原文:<https://towardsdatascience.com/python-iterables-vs-iterators-688907fd755f>
## 理解 Python 中 Iterables 和迭代器的区别

亨利&公司在 [Unsplash](https://unsplash.com/s/photos/loop?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上拍摄的照片
## 介绍
术语 **iterable** 和 **iterator** 经常(错误地)互换使用,以描述支持迭代的对象,即允许迭代其元素的对象。事实上,Python 中的迭代器和可迭代对象是两个截然不同的概念,通常会引起混淆,尤其是对新手而言。
在今天的文章中,我们将讨论 Python 中的迭代协议,以及迭代器和可迭代对象如何参与其实现。此外,我们将探索 iterable 和 iterator 对象之间的主要区别,并提供一个示例来帮助您理解 iterable 和 iterator 对象是如何工作的。
## Python 中的迭代协议
Python 中的**所有迭代工具**使用迭代协议,并通过各种对象类型(如 for 循环、综合、映射等)实现。本质上,协议由两种对象类型组成,即 **iterable** 和 **iterator** 。
* **iterable** 对象是您**迭代**其元素的对象
* **迭代器**对象是**在迭代**过程中产生值的对象,也是由可迭代对象返回的
**在接下来的部分中,我们将详细了解这两种对象类型是如何工作的,以及它们需要实现什么来满足迭代协议。**
## **什么是可迭代的**
**在 Python 中,Iterable 是一个对象,它由**实现** `**__iter__()**`方法,由**返回一个 iterator 对象**或者实现`__getitem__()`方法的对象(并且应该在索引用尽时引发一个`IndexError`)。内置的可迭代对象包括**列表、集合和字符串**,因为这样的序列可以在一个 for 循环中迭代。**
**注意**在最近的 Python 版本中,实现 Iterables 的首选方式是通过实现** `**__iter__()**` **方法**。`__getitem__()`方法是一种在现代迭代器之前使用的遗留功能。但是 Python 仍然认为实现了`__getitem__()`方法的对象是可迭代的。这意味着如果没有定义`__iter__()`,Python 解释器将使用`__getitem__()`。更多详情可以参考 [PEP-234](https://www.python.org/dev/peps/pep-0234/) 。**
**总而言之,Python 中的 Iterable 是任何对象,只要它**
* **可以迭代(例如,可以迭代一个字符串的字符或一个文件的行)**
* **实现`__iter__()`方法(或`__getitem__`),因此可以用返回迭代器的`iter()`调用它**
* **可以出现在 for 循环的右边(`for i in myIterable:`)**
## **什么是迭代器**
**另一方面,Python 中的迭代器是**以某种方式实现** `**__next__()**` **方法**的对象**
* **返回 iterable 对象的下一个值,更新迭代器的状态,使其指向下一个值**
* **当 iterable 对象的元素用尽时引发一个`StopIteration`**
**此外,**迭代器本身也是可迭代的,因为它还必须实现** `**__iter__()**` **方法**,其中它只是返回`self`。**
> **每个迭代器也是可迭代的,但不是每个可迭代的都是迭代器**
## **Python 中的可迭代对象和迭代器**
**正如我们已经提到的,Python List 是可迭代的内置对象类型之一。现在让我们假设我们有下面的整数列表,如下所示:**
my_lst = [5, 10, 15]
**由于`my_lst`是一个 iterable,我们可以运行`iter()`方法来从 iterable 中获得一个 iterator 对象:**
my_iter = iter(my_lst)
**我们可以验证`my_iter`是否属于`list_iterator`类型:**
type(my_iter)
list_iterator
**现在由于`my_iter`是一个迭代器,因此它实现了`__next__()`方法,该方法将返回列表 iterable 的下一个元素:**
my_iter.next()
5
**一旦迭代器在`__next__()`调用后返回下一个值,它应该改变它的状态,以便它现在指向下一个元素:**
my_iter.next()
10
**注意,`next(iter_name)`也是一个有效的语法,相当于`iter_name.__next__()`:**
next(my_iter)
15
**现在我们到达了最后一个元素,对`__next__()`方法的下一次调用应该会产生一个`StopIteration`,这是实现`__next__()`方法的迭代器必须满足的一个要求:**
my_iter.next()Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/IPython/core/interactiveshell.py", line 3331, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "", line 1, in
my_iter.next()
StopIteration
## **最后的想法**
**在今天的文章中,我们讨论了 Python 中的迭代协议,以及迭代中如何涉及迭代和迭代器。此外,我们讨论了可迭代和迭代器的主要特征,并介绍了它们的主要区别。最后,我们展示了 Iterable 和 Iterator 对象是如何工作的。**
**在我的下一篇文章中,我将讨论如何创建用户定义的迭代器,以使用户定义的类可迭代。**
**[**成为会员**](https://gmyrianthous.medium.com/membership) **阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。****
**<https://gmyrianthous.medium.com/membership> **
****你可能也会喜欢****
**</python-linked-lists-c3622205da81> ** **</switch-statements-python-e99ea364fde5> ** **</augmented-assignments-python-caa4990811a0> **
# 用于网格、点云和数据可视化的 Python 库(第 1 部分)
> 原文:<https://towardsdatascience.com/python-libraries-for-mesh-and-point-cloud-visualization-part-1-daa2af36de30>
## 八个最好的 Python 库,用于惊人的 3D 可视化、绘图和动画
> 关于用 Python 创建令人惊叹的 3D 可视化、绘图和动画的最佳库的教程。谁说你需要 C++知识来创建快速响应的点云、网格或数据集可视化?本实践教程将为您提供纲要和代码片段,帮助您启动并运行这 8 个库— [Open3D](http://www.open3d.org/) 、 [Trimesh](https://trimsh.org/index.html) 、 [Vedo](https://vedo.embl.es/) (V3do)、 [Pyrender](https://github.com/mmatl/pyrender) 、 [PlotOptiX](https://plotoptix.rnd.team/) 、 [Polyscope](https://polyscope.run/py/) 、 [PyVista](https://docs.pyvista.org/) 和 [Simple-3dviz](https://simple-3dviz.com/) 。这是第 1 部分,详细介绍了 Open3D、Trimesh、PyVista 和 Vedo(V3do)。

PyVista(左)、Trimesh(中)和 Vedo(右)的输出示例|图片由作者提供
这是针对 3D 工作和可视化的 Python 库的计划教程的第 1 部分。在本部分中,我们将介绍前 4 个可视化库——open 3d、Trimesh、PyVista 和 Vedo,而第 2 部分将重点介绍 Pyrender、PlotOptiX、Polyscope 和 Simple-3dviz。如果您对用于分析和处理网格、点云或数据集的库感兴趣,我还将在后面的文章中介绍广泛使用的任务库,如体素化、特征提取、距离计算、表面生成和网格划分等。在前两篇文章中,我们将主要关注可视化和动画。

作者使用 PyVista | Image 以 3D 方式显示不同月份的天气(温度/湿度)数据
很长一段时间,人们需要使用 Matplotlib 来可视化 Python 中的 3D 内容。使用它的好处是,如果您安装了 Python,那么您很可能拥有使用 Matplotlib 在 3D 中可视化数据所需的一切。坏的方面是它不支持 GPU 硬件加速,这意味着一旦你在 3D 中通过了一定数量的点或面,事情往往会变得缓慢和无反应。这导致许多人使用[点云库](https://pointclouds.org/) ( **PCL** )作为网格和点云可视化和分析的一揽子解决方案。PCL 仍然是最好的 3D 分析库之一,它是用 C++构建的这一事实保证了它的通用性和响应性。两个主要问题阻止了它的完善 Python 包装器只包含主 PCL 库的一小部分功能,在 Windows 上运行它是一项令人沮丧的工作,有大量的编译错误、缺失或损坏的功能,以及不太理想的教程。简而言之——如果你在 Linux 中工作,并且不害怕 C++,那么 PCL 就是你要走的路。但是其他人呢?
近年来,越来越多的 Python 库开始涌现,试图填补这一空白。其中一些库(如 Open3D、Trimesh 和 Vedo)非常健壮,包含许多不同的功能,用于分析、生成和处理网格和点云。其他如 Simple-3dviz、Polyscope 和 Pyrender 则更倾向于创建快速而漂亮的可视化和动画。对于本文,我选择了 8 个库,它们提供了不同级别的可视化和许多附加选项——从内置的手动控件到大量的照明和动画选项以及光线跟踪视觉效果。所有的代码都可以在 GitHub 库[这里](https://github.com/IvanNik17/python-3d-analysis-libraries)获得。下图展示了一些可视化的可能性,以及提取的特征。

一些可能的可视化选项和提取特征的示例。这些是作者用 PyVista | Image 做的
这篇文章远不是所有可用库的详尽列表,值得注意的是,缺少了像 [pyntcloud](https://github.com/daavoo/pyntcloud) 、 [vpython](https://vpython.org/) 、 [Mayavi、](https://docs.enthought.com/mayavi/mayavi/index.html)和其他库所基于的 base [VTK](https://vtk.org/) 这样的库。我选择跳过这些库,因为它们的安装相对复杂,或者因为它们对大型点云和网格的可视化能力不够理想。但是我计划将它们包含在下一篇探索 3D 分析和操作的文章中。
此外,还有 Python 包装库,用于直接通过 Python 使用广为人知的可视化和操作应用程序,如 [CloudCompare](https://www.danielgm.net/cc/) 和 [Meshlab](https://www.meshlab.net/) 。 [CloudComPy](https://github.com/CloudCompare/CloudComPy) 和 [PyMeshLab](https://github.com/cnr-isti-vclab/PyMeshLab) 创建直接连接到 Python 的接口,绕过使用 GUI 甚至打开应用程序的必要性。我们将在后面的文章中更详细地讨论这些包装器库。
让我们直接进入每一个库——研究如何安装它们,进行初始设置,最后编写一些简单的可视化代码。为了展示网格和点云以及数据集的可视化可能性,我提供了两者。首先,一个天使雕像出现在**中。obj** 格式 [**这里**](https://github.com/IvanNik17/python-3d-analysis-libraries/tree/main/mesh) 和点云在**。txt** 格式[此处 。该物体已在许多文章[1]、[2]、[3]中介绍,也可作为大型摄影测量数据集[4]、[5]的一部分下载。第二,在**中包含天气数据的时间序列数据集。csv** 格式](https://github.com/IvanNik17/python-3d-analysis-libraries/tree/main/point_cloud) [**此处**](https://github.com/IvanNik17/python-3d-analysis-libraries/tree/main/dataset) 。气象元数据被用作检测监控录像中长期异常的研究的一部分[6]。这些数据是使用[丹麦气象研究所(DMI)](https://confluence.govcloud.dk/display/FDAPI) 提供的开源 API 提取的,并且[可以在商业和非商业、公共和私人项目中免费使用](https://www.dmi.dk/friedata/guides-til-frie-data/vilkar-for-brug-af-data/)。为了使用数据集,熊猫是必需的。它默认出现在 Anaconda 安装中,可以通过调用`conda install pandas`轻松安装。
# 使用 Open3D 进行可视化

Open3D 结果|作者图片
Python 中发展最快的 3D 处理库之一。它构建于 C++之上,但公开了 C++和 Python 的所有功能,使其快速、灵活且健壮。该库的目标是 3D 处理,就像 OpenCV 是计算机视觉一样——一站式解决方案,包含您需要的一切,并可以轻松连接到其他库。例如 PyTorch 和 Tensorflow,用于通过名为 [Open3D-ML](http://www.open3d.org/docs/release/open3d_ml.html) 的扩展来构建深度学习网络,以处理点云、体素和深度图。
安装非常简单,提供了下载选择器来定制您的安装首选项。该库也可以在 Linux、Mac 和 Windows 上开箱即用,并支持从 3.6 开始的 Python 版本。
如果您正在使用 Anaconda,我们可以首先创建一个新环境`open3d_env`,然后安装 Open3D。在我的例子中,我直接指定我希望在新环境中使用 Python 3.8,但是您可以选择任何支持的版本。
conda create -n open3d_env python=3.8
conda activate open3d_env
pip install open3d
一旦安装完毕,我们可以通过导入它`import open3d as o3d`并调用`print(o3d.__version__)`来检查一切是否正常。如果没有错误,我们就可以开始了!
Open3d 的 io 模块包含加载网格`o3d.io.read_triangle_mesh`和点云`o3d.io.read_point_cloud`的便捷函数。如果网格有纹理和材质,那么加载它们的更简单的方法是在调用读取网格时通过设置标志`enable_post_processing = True`来启用网格后处理。在我们的例子中,读取天使雕像网格的代码是:
在我们的例子中,我也调用 numpy,因为默认情况下,Open3D 结构不能被查看和打印,但是它们可以很容易地转换成 numpy 数组,如上面的代码所示。可以轻松访问导入网格的所有信息。
为了可视化导入的网格,我们需要首先创建一个`Visualizer()`对象,它将包含所有的 3D 对象、灯光和摄像机,并将监听键盘、鼠标和动画回调。下面的代码设置场景,创建一个窗口,并导入所有的 3D 对象。
我们可以使用`create_window`函数在 visualizer 中创建一个新窗口,它接受名称、宽度、高度以及在屏幕上的位置的输入。然后我们可以通过调用`add_geometry`方法将任何 3D 几何图形添加到可视化工具中。在我们的例子中,我们还通过调用`create_sphere`创建了一个原始对象——围绕天使雕像的球体。我们还可以创建其他基本体,如立方体、圆柱体、圆环体等。我们可以通过调用`compute_vertex_normals()`或`compute_triangle_normals()`来计算特定 3D 对象的法线。最后,我们可以通过直接在对象上调用`translate()`方法来翻译对象。
接下来,我们设置希望在更新周期中运行的任何回调函数。在我们的例子中,我们想要创建一个动画,所以我们调用`register_animation_callback(rotate_around)`,其中 rotate_around 是函数的名称。键盘和鼠标回调也可以做同样的事情。该函数如下所示。
在这个函数中,我们首先通过调用`get_rotation_matrix_from_xyz`为天使网格和球体创建旋转矩阵,并给它每个方向所需的旋转值,用弧度指定。然后,我们在每个网格上调用`rotate`方法,创建 3D 旋转矩阵和一个中心,这是它们将绕其旋转的枢轴。最后,为了能够可视化这些变化,我们为每个网格调用`update_geometry()`方法,并调用`update_renderer()`来更新整个可视化器。完整的代码如下所示。
Open3D 主要针对网格和 3D 点云的分析和操作,但我们也可以在 3D 中可视化多维数据。我们可以通过首先从天气数据集中提取感兴趣的列来证明这一点。出于我们的目的,我们将查看“温度”和“湿度”列。由于数据包含时间戳,我们使用这些时间戳来提取每个天气读数的月份。我们最后将这三列转换成一个 numpy 数组,因为 Open3D 可以将数组转换成点云对象。代码如下。

按月计算的温度和湿度|图片由作者提供
一旦我们有了数据,我们可以通过在 Open3D 中初始化一个点云来可视化它。为了更好地观察这些点,我们也可以通过调用`get_view_control()`来旋转摄像机。下面给出了可视化的代码。
# 使用 Trimesh 进行可视化

Trimesh 结果|作者图片
是一个纯粹基于 Python 的库,用于网格和点云的加载、分析和可视化。它广泛用于在统计分析和机器学习之前对 3D 资产进行预处理。以及像 Cura 这样的 3D 打印应用的一部分。它也是我们将要讨论的其他库的基础,比如 Pyrender 和 Vedo。
Trimesh 在基础库中不包含可视化部分,但使用可选的 [pyglet](http://pyglet.org/) 游戏和交互式应用程序库作为基础。它在其基础上实现了可视化、回调和操作特性,并包含了大量开箱即用的内置可视化交互。它还可以在 Linux、Mac 和 Windows 上运行,没有任何问题或解决方法。它也可以用于 Python 2 和 Python 3。
对于安装,唯一需要的必备库是 numpy。同样,我们使用 Anaconda 环境来创建一个环境,并在其中安装所有东西。同样,我们安装 pyglet 来可视化网格。如果只需要分析和操纵网格,可以跳过最后一行。
conda create -n trimesh_env python=3.8
conda activate trimesh_env
conda install numpy
pip install trimeshpip install pyglet
一旦所有东西都安装好了,我们就可以通过调用`import trimesh`来检查所有东西是否工作。如果我们想要从 trimesh 获取所有消息到控制台,那么我们需要调用`trimesh.util.attach_to_log()`,在调用 trimesh 函数之后会打印出一个详细的输出。这在调试阶段非常有用。
加载网格是通过调用`trimesh.load()`方法来完成的,带有可选的 file_type 输入,其中可以明确说明具体的网格类型。一旦网格被加载,就可以直接提取很多信息——例如,它是否防水`mesh.is_watertight`,它的凸包`mesh.convex_hull`,体积`mesh.volume`等。如果网格由多个对象组成,可以通过调用`mesh.split()`将它们分割成多个网格。下面给出了加载 3D 网格、创建球体图元和设置场景的代码。
通过调用`trimesh.primitives`,我们可以访问不同的图元,如盒子、胶囊和挤压,以及从点和面的数组构建网格。在我们的例子中,我们创建一个球体并给它一个半径。我们设置了一个包含天使网格和球体的场景。作为场景的一部分,每个节点都是独立的节点。最后,我们显示场景并指定一个名为 rotate_objects 的回调函数。每次场景更新时都会调用该函数。它在下面的代码中给出。
由于 Trimesh 不包含自动更新的转换函数,我们需要`import time`并使用 time.time()来获得一个增量时间变化,我们可以用它来更新旋转。我们首先使用 numpy 创建一个空的齐次变换矩阵来改变球体的位置。我们只需要改变矩阵的平移部分,我们使用的想法是,如果我们使用正弦和余弦函数改变 X 和 Z 位置,我们将实现圆周运动。
我们使用内置函数从 Trimesh `trimesh.tranformations.rotation_matrix()`生成一个旋转矩阵,它使用角度和轴作为输入并返回一个旋转矩阵。我们选择代表网格和球体的节点,方法是根据它们添加到场景中的时间来选择它们。如果我们有多个对象,我们可以根据它们在场景中的名称来获取它们,或者在字典中保存一个引用,指示哪个对象包含哪个节点。我们最后从场景中调用 update 函数,并给它我们想要更新的节点,以及转换矩阵。完整的代码可以在下面看到。
Trimesh 与 Open3D 一样,是针对网格和点云的分析和操作,但其渲染潜力可以用于统计可视化。我们将通过创建一个 3D 柱形图来演示这一点,该柱形图用于可视化气象数据集中每个月的平均“温度”数据。为此,我们首先使用 Pandas 的`groupby`函数计算每月的平均温度,并根据日期-时间列的月频率对数据进行分组。

各月平均气温|图片由作者提供
一旦我们提取了每月的平均温度读数,我们就使用 trimesh 圆柱体对象来表示读数。这种表现形式既有圆柱体的高度,也有从红色到蓝色的颜色梯度,红色代表最高温度,蓝色代表最低温度。为了生成色图,我们使用`trimesh.visual.interpolate()`函数来生成标准化的颜色值。由于 trimesh 中的圆柱体从它们的中心开始缩放,我们也基于平均温度将它们向下移动,因此我们得到底部对齐的柱形图。下面给出了可视化的代码。
# 使用 PyVista 进行可视化

PyVista 结果|作者图片
P [yVista](https://docs.pyvista.org/index.html) 是一个强大的功能齐全的绘图和网格分析库,它建立在可视化工具包(VTK)之上。它简化了 VTK 接口,使得对不同函数的调用更容易、更 pythonic 化。它可以用于点云和网格,它使用 OpenGL,使其易于创建平滑的可视化和动画。该库有大量的示例和教程,从简单的可视化到复杂的分析和转换工具,如切片、重采样、点云表面重建、网格平滑、光线跟踪、体素化等。
该库运行在 Linux、Mac 和 Windows 上,需要 Python 3.7+。因为它是建立在 VTK 之上的,所以它要求至少与 numpy 一起安装。安装非常简单,可以通过 pip 或 Anaconda 完成。对于我们的用例,我们再次创建一个 anaconda 环境,并使用 Anaconda 来安装库。这还会安装所有依赖项。
conda create -n pyvista_env python=3.8
conda activate pyvista_env
conda install numpy
conda install -c conda-forge pyvista
一旦安装了库,可以通过首先导入它`import pyvista as pv`然后调用`pv.demos.plot_wave()`来测试它。如果带有正弦波的绘图窗口开始,那么库已经成功安装。
通过连接到 [meshio](https://github.com/nschloe/meshio) 可以很容易地导入网格和点云。为了导入它们,我们可以调用`pyvista.read()`,为了加载网格的纹理,我们可以调用`pyvista.read_texture()`。一旦网格被加载,额外的信息可以从中提取,如边,网格质量,顶点之间的距离等。它也可以很容易地重新采样。
这里我们需要提到的重要一点是,如果我们想要使用 PyVista 实现动画或实现键盘、鼠标或 UI 交互的非阻塞可视化,我们需要安装 [pyvistaqt](https://qtdocs.pyvista.org/) ,它用 qt 功能扩展了 PyVista。可以通过调用以下命令轻松安装附加库:
`conda install -c conda-forge pyvistaqt`
完成后,我们可以通过`from pyvistaqt import BackgroundPlotter`导入,使用后台绘图仪结构创建非阻塞图。如果你不需要非阻塞可视化,那么直接调用`pyvista.Plotter`并使用它就足够了。下面给出了加载天使网格和纹理、创建背景绘图仪、旋转球体和光源的代码。
一旦我们创建了`BackgroundPlotter`,我们就可以设置它的大小、背景等。我们改变 camera_position,使其停留在 *xy* 轴。这也可以通过在 X、Y、Z 以及旋转或向上方向上设置一组摄像机位置来实现。我们还设置了相机的剪辑范围,因为有时对象会被缺省值剪辑。我们通过给它一个半径和中心位置来创建一个球体。其他图元也可以用同样的方法创建——圆柱体、箭头、平面、长方体、圆锥体、圆盘等。最后,我们通过调用`pyvista.Light`创建一个点光源,默认是创建一个点光源,但是我们可以隐式地创建平行光、聚光灯等。我们设置灯光的颜色、位置和焦点。接下来,我们将所有这些对象添加到绘图仪,并创建一个回调监听器来更新场景和生成简单的动画。
这里我们需要指明两个要点。使用`add_callback`创建回调时,强烈建议明确指定更新周期的间隔。第二件事是当使用 pyvistaqt 绘图仪时,总是在 show 函数后调用`app.exec_()`,因为这确保了创建的绘图仪窗口不会在一次迭代后关闭。最后,回调`update_scene()`中调用的函数如下所示。
在这里,我们使用 PyVista 提供的简化旋转函数在网格和球体的每个轴`rotate_y`上旋转,同时调用`implace=True`来确保旋转将直接设置到网格。由于灯光不包含直接旋转的方法,我们根据`sphere.center`的位置更新它的位置。最后,我们更新整个绘图仪。PyVista 可视化的完整代码可以在下面看到。
PyVista 致力于 3D 数据的分析和可视化,这使得它成为生成多维和时序数据集可视化的完美库。我们将通过建立在前一章中使用 Open3D 创建的简单可视化来演示这一点。为此,我们从天气数据集中提取相同的列——“温度”和“湿度”,并从日期时间信息中生成“月份”列。完成初始设置后,我们可以直接使用 PyVista 创建 3D 绘图。PyVista 有许多与 2D 绘图库类似的功能,如 matplotlib 和 seaborn,其中的值可以直接用于创建伪彩色,标签可以直接添加到点。下面的代码给出了最初的熊猫操作。

通过 3D 网格和图例按月显示温度和湿度|图片由作者提供
一旦 pandas 数据经过预处理并提取了所需的信息,我们就通过调用`pyvista.add_points`建立一个 3D 点云对象来包含所选的数据集列。该函数也可以直接获取一个`scalars`字段,该字段用于给点着色并创建一个图例。然后,我们设置标签对象,并在每个月的级别上给它们一个位置。我们最后初始化一个 3D 网格,命名轴并创建一个简单的回调函数来旋转摄像机。可视化和回调函数的代码如下所示。
# 使用视频进行可视化

视频结果|作者图片
Vedo(或 V3do)是一个用于科学分析和可视化 3D 对象的 Python 库。它可用于绘制 1d、2d 和 3d 数据、点云、网格以及体积可视化。Vedo 使用 Trimesh 作为一些网格和点云处理、导入/导出和生成的后端,并通过提供大量科学指导的功能来构建它。它可以生成直接导入 Latex 的矢量插图和可视化,并用于生成物理模拟、深度学习层概述以及机械和 CAD 级别的切片和图表。
Vedo 基于 VTK,安装前需要 it 和 numpy 在场。它可以在 Linux、Mac 和 Windows 上运行,并且需要 Python 3.5 及更高版本。对于我们的用例,我们再次创建一个 anaconda 环境,并使用 Anaconda 来安装库。
conda create -n vedo_env python=3.8
conda activate vedo_env
conda install numpy
conda install -c conda-forge vedo
一旦所有东西都安装好了,我们可以通过调用`import vedo`然后可视化一个原语`vedo.Sphere().show(axis=1).close()`来测试安装。这只是生成一个球体的简写,直接用轴显示,然后设置一旦我们关闭窗口程序就结束。如果我们看到一个旁边有轴的球体,一切都按预期运行。
Vedo 可以在 CLI 界面中直接调用`vedo path_to_your_3D_object`来运行。一旦一个窗口被打开,就有许多键和组合可以被按下以改变可视化,连同鼠标旋转、平移和缩放。最后,通过调用`vedo --convert path_input_3D_object --to path_output_3d_object`或者直接在`--to`后添加需要的文件格式,可以直接调用 Vedo 进行文件格式转换。
使用`vedo.load(path_to_3d_object)`加载网格和点云很简单。如果网格有纹理,需要使用`mesh.texture(path_to_texture)`将其额外加载到创建的 3D 对象中。一旦加载了网格,就可以进行许多快速交互和分析步骤。一个切割工具,通过调用`addCutterTool(your_mesh, mode = "sphere")`,使用“饼干刀”形状切割网格的部分——球体、平面、盒子。另一个是徒手切割,可以使用`FreeHandCutPlotter(your_mesh)`手动分割网格。网格上的其他操作也可以完成——比如通过调用`your_mesh.fillHoles(size=size_to_fill)`来填充孔洞。例如,通过调用`vedo.Sphere`或`vedo.Box`也可以很容易地生成原语。下面给出了加载天使网格和生成图元的代码。
网格的材料,以及它们的属性,如颜色、金属和粗糙度,可以通过调用 3D 对象的`lighting()`方法来设置。有多种预制样式可供选择,包括“默认”、“金属”、“塑料”、“闪亮”、“光滑”,还可以定制。我们还创建了一个盒子作为天使网格停留的表面,并投射阴影,以及一个球体。我们可以通过直接调用`rotate`和`translate`或者调用`x(), y(), z()`方法来移动对象。一旦所有的 3D 对象被创建和定位,我们制作两个灯,并设置绘图仪对象来可视化一切。这方面的代码如下所示。
我们创建了两个点光源——一个白色的,强度较小的作为环境光,另一个强度较大的,蓝色的,随着球体移动。我们在视频设置中设置了我们想要实现的交互性。我们创建一个绘图仪,并通过调用`plotter.addShadows()`指定生成阴影。最后,我们调用 show 方法,以及所有想要显示的 3D 对象和灯光。如果需要生成大量的对象,我们也可以将它们添加为一个列表。我们将交互性设置为 0,就像我使用的视频版本一样,如果不这样做,动画有时会无法运行。最后,由于维多是建立在 VTK 的基础上,它使用了它的事件系统。与 PyVista 的不同之处在于,Vedo 没有在每次更新循环中运行的隐式定时器功能。这就是为什么我们需要通过调用`plt.timerCallback('create', dt=delta_time_for_updating)`首先创建我们的定时器函数。一旦创建完成,我们就可以给这个定时器添加一个回调函数。该函数将在每次调用定时器时运行。请注意,要使回调起作用,需要将“计时器”名称写成如图所示。下面给出了功能`rotate_object`。
在函数中,我们再次使用一种速记方法`rotateY`来旋转天使雕像和球体。在该方法中,我们还可以设置对象旋转的支点。由于灯光不包含旋转方法,我们采用球体的位置,并直接设置灯光位置。我们最后调用`plt.render()`函数,这样渲染器就可以更新了。下面是完整的代码。
像 PyVista 一样,Vedo 是一个专门的库,用于在 3D 中可视化数据和数据集。它扩展了 PyVista 中的许多功能,只需对数据调用函数就可以轻松地创建统计分析可视化。它还有一个更好的添加标签和文本、科学符号甚至 LateX 的实现。Vedo 和 PyVista 也可以利用 VTK,在渲染的基础上创建多个子图和渲染。我们将通过为“温度”和“湿度”数据创建多个子图以及数据密度图,并将它们与直方图可视化相结合来探索这一点。我们准备数据的方式与前面的例子相同——使用 pandas 初始提取列,并创建 month 和 month name 列。代码如下所示。

温度和湿度密度图,以及作者提供的 3D 直方图|图像
数据经过预处理后,可以通过首先调用 Vedo `Plotter`对象并设置将要创建的子图数量来创建可视化。在这里,我们还通过设置`sharecam = 0`来指定每个子图都有一个单独的摄像机。这使得可视化更加沉重,但是每个子图形都可以单独操作。然后对于每个月,我们用 3D 轴创建一个图,我们调用`.density()`函数来计算点密度体积,然后用一个标量条叠加。对于最后一个子图,我们将`vedo.pyplot.histogram`与数据集、可视化模式和轴名称一起调用。下面给出了可视化的代码。
# 结论
W 当我开始写这篇文章时,它被计划为只是一个部分,然后去不同的库分析和操作三维网格和统计数据。我决定将这篇文章分成两部分,这样我可以更好地解释这些库的细节。这一点很重要,因为无论您想做什么——纯粹的结果可视化[1,6],从网格和点云生成 3D 特征[2],或者创建高清渲染[3],了解 Python 中可用于 3D 任务的工具都很重要。天使雕像可以自由地用于可视化和实验,以及大量其他使用 SfM 从[4]和[5]重建的 3D 对象。更多关于天气时间序列数据集的信息可以在[这里](https://www.kaggle.com/datasets/ivannikolov/longterm-thermal-drift-dataset)阅读。
接下来的第 2 部分将关注接下来的 4 个可视化库— [Pyrender](https://github.com/mmatl/pyrender) 、 [PlotOptiX](https://plotoptix.rnd.team/) 、 [Polyscope](https://polyscope.run/py/) 和 [Simple-3dviz](https://simple-3dviz.com/) 。从这些,我们将把重点放在 PlotOptiX 和 Pyrender 上,它们可以产生光线跟踪结果(PlotOptiX)或高度动态的照明场景(Pyrender)。我们还将讨论 Polyscope 和 Simple-3dviz,它们是轻量级、简单易用的 3D 可视化工具,可用于日常使用和科学数据演示。
# **参考文献**
1. **尼科洛夫,I.** ,&麦德森,C. (2016 年 10 月)。在不同的拍摄条件下,对 motion 3D 重建软件的近距离结构进行基准测试。在*欧洲-地中海会议*(第 15-26 页)。施普林格、湛;【https://doi.org/10.1007/978-3-319-48496-9_2
2. **尼科洛夫,I.** ,&麦德森,C. (2020)。粗暴还是吵闹?SfM 重建中噪声估计的度量。*传感器*、 *20* (19)、5725;[https://doi.org/10.3390/s20195725](https://doi.org/10.3390/s20195725)
3. **尼科洛夫,I. A.** ,&麦德森,C. B. (2019,2 月)。测试 SfM 图像捕获配置的交互式环境。在*第十四届计算机视觉、成像和计算机图形学理论与应用国际联合会议(Visigrapp 2019)* (第 317–322 页)。科学出版社数字图书馆;[https://doi.org/10.5220/0007566703170322](https://doi.org/10.5220/0007566703170322)
4. **尼科洛夫,我.**;Madsen,C. (2020),“GGG 基准 SfM:在不同捕获条件下对近距离 SfM 软件性能进行基准测试的数据集”,门德利数据,第 4 版;[https://doi.org/10.17632/bzxk2n78s9.4](https://doi.org/10.17632/bzxk2n78s9.4)
5. **尼科洛夫一世**;麦德森,C. (2020),“GGG——粗糙还是嘈杂?SfM 重建中的噪声检测指标”,门德利数据,V2;[https://doi.org/10.17632/xtv5y29xvz.2](https://doi.org/10.17632/xtv5y29xvz.2)
6. **Nikolov,I.** ,Philipsen,M. P .,Liu,j .,Dueholm,J. V .,Johansen,A. S .,Nasrollahi,k .,& Moeslund,T. B. (2021)。漂移中的季节:研究概念漂移的长期热成像数据集。在*第三十五届神经信息处理系统会议上;*[https://openreview.net/forum?id=LjjqegBNtPi](https://openreview.net/forum?id=LjjqegBNtPi)
# 用于网格、点云和数据可视化的 Python 库(第 2 部分)
> 原文:<https://towardsdatascience.com/python-libraries-for-mesh-point-cloud-and-data-visualization-part-2-385f16188f0f>
> 这是本教程的第 2 部分,探索一些用于数据集、点云和网格的可视化和动画的最佳库。在本部分中,您将获得一些见解和代码片段,帮助您使用<https://github.com/mmatl/pyrender>**,*[*PlotOptiX*](https://plotoptix.rnd.team/)*,*[*poly scope*](https://polyscope.run/py/),*和*[*Simple-3d viz*](https://simple-3dviz.com/)*。制作令人惊叹的交互式可视化和光线跟踪渲染可以很容易!**
******
*作者的 PlotOptiX(左)、Pyrender(中)和 Simple-3dviz(右)|图像的输出示例*
*在 Python 可视化库概述的第 2 部分中,我们继续讨论一些广为人知和晦涩难懂的例子。本文中展示的两个库是相当轻量级的,可以通过手动和编程方式快速生成可视化效果。Polyscope 有一大套可视化选项,其中许多可以在显示 3D 对象时通过 GUI 界面手动设置。Simple-3dviz 是一个非常轻量级的实现,它提供了有限数量的可视化,但非常适合快速原型开发。另一方面,还有 Pyrender 和 PlotOptiX。这两个库都需要更多的硬件资源(在 PlotOptiX 的情况下,需要 Nvidia 卡),但它们提供了更好的照明和光线跟踪渲染。*
*如果你对使用 Open3D、Trimesh、PyVista 或 Vedo 感兴趣,可以看看概述教程的第 1 部分——这里[这里](https://medium.com/@inikolov17/python-libraries-for-mesh-and-point-cloud-visualization-part-1-daa2af36de30)。*
*</python-libraries-for-mesh-and-point-cloud-visualization-part-1-daa2af36de30> 
作者使用 Simple-3dviz|图像的平滑闭环摄像机轨迹可视化示例
同样,对于每个库,本教程将介绍安装和设置过程,以及一个简单的实践示例,演示如何为可视化网格和点云以及数据集构建一个最小的工作原型。所有例子的代码都可以在 GitHub 库[这里](https://github.com/IvanNik17/python-3d-analysis-libraries)找到。

使用 Polyscope|作者提供的图片,可视化不同时间点的天气(温度/湿度)数据变化
为了跟进,我在**中提供了天使雕像网格。obj** 格式 [**这里**](https://github.com/IvanNik17/python-3d-analysis-libraries/tree/main/mesh) 和点云在**。txt** 格式[此处 。该物体已在几篇文章[1],[2],[3]中介绍过,也可作为大型摄影测量数据集[4],[5]的一部分下载。为了演示 3D 图的可视化,在**中显示了包含天气数据的时间序列数据集。csv** 格式在](https://github.com/IvanNik17/python-3d-analysis-libraries/tree/main/point_cloud) 这里也提供 [**。天气元数据涵盖了 8 个月的各种天气条件,是自动编码器和物体探测器长期数据漂移研究的一部分[6]。数据是开源的,使用由**](https://github.com/IvanNik17/python-3d-analysis-libraries/tree/main/dataset)**[丹麦气象研究所(DMI)](https://confluence.govcloud.dk/display/FDAPI) 提供的 API 提取。它可以在商业和非商业、公共和私人项目中免费使用。为了使用数据集,熊猫是必需的。它默认出现在 Anaconda 安装中,可以通过调用`conda install pandas`轻松安装。**
# 使用 Simple-3dviz 进行可视化

简单-3dviz |作者图片
Simple-3dviz 库是一个轻量级且易于使用的工具集,用于网格和点云的可视化。它建立在 wxpython 之上,支持动画和屏幕外渲染。通过屏幕外渲染,该库还支持深度图像的提取。除此之外,作为库的一部分,还提供了两个帮助函数— `mesh_viewer`用于快速可视化 3D 对象并保存截图和渲染图,以及`func_viewer`用于快速可视化各种功能的 3D 效果。
该库被限制为每个查看器窗口只有一个光源,但它可以很容易地在场景中移动和重新定位。该库最强大的部分之一是可以轻松设置相机轨迹。这个库有几个依赖项,其中一些是必需的——比如 NumPy 和 [moderngl](https://github.com/moderngl/moderngl) ,而其他的只有在你想保存渲染或者可视化一个 GUI 的时候才是必需的,比如 OpenCV 和 wxpython。这使得这个库相当轻便。如果您正在使用 Anaconda,通常最好从创建一个环境开始,然后通过 pip 安装必要的部分。该库在 Linux 和 Windows 上运行,在 Mac 上有小的可视化错误。
conda create -n simple-3dviz_env python=3.8
conda activate simple-3dviz_env
pip install simple-3dviz
一旦安装完毕,通过调用`import simple-3dviz`和`print(simple-3dviz.__version__)`就可以很容易地检查所有的东西是否安装正确。如果显示了版本,并且没有打印错误,那么一切都准备好了。
要在任何地方使用 Windows 上的`mesh_viewer`和`func_viewer`,需要将 Python 和 conda 环境添加到系统的环境变量 path 中。出于测试目的,你也可以在`Anaconda3\envs\your_conda_environment_name\Scripts`中找到这两个应用。在这两种情况下,您都需要用它们的。exe 扩展名。例如,使用 mesh_viewer 可视化天使雕像是用`python mesh_viewer.exe \mesh\angelStatue_lp.obj`完成的,而可视化函数 x*y -y*x 可以用`python func_viewer.exe x*y**3-y*x**3`完成(这里要注意的是,不要把函数放在引号中,音节之间也不要有空格)。func_viewer 的结果如下所示。

内置的应用程序,查看 2D 函数在简单的三维图像作者
加载一个 3D 对象可以通过调用`simple-3dviz.from_file()`来完成,如果需要一个纹理网格,它会有额外的输入。在天使雕像的情况下,将纹理的路径添加到该函数会导致不正确的 UV 坐标和纹理映射。更迂回的方法是在加载网格和材质后调用`Material.with_texture_image()`。最后,设置材质对象,作为网格的材质。
同样,为场景创建球体图元也不太明显。Simple-3dviz 不包含创建图元的函数,但具有通过使用`Mesh.from_superquadrics()`创建[超二次曲面](https://en.wikipedia.org/wiki/Superquadrics)的函数,以及另外通过`Sphereclouds`方法创建用于表示点云的球体的函数。在我的例子中,我决定为了简单起见,使用点云中的一个球体。下面给出了生成图元和加载网格的代码。
为网格创建材质时,我们可以设置它的视觉属性。为了生成球体,我们首先创建一个单一的坐标位置并扩展其维度。然后,我们将它与大小和颜色一起添加到 Spherecloud 方法中。接下来,可视化和动画直接在对`show`函数的调用中完成。该函数还可以调用按键、摄像机移动和对象操作。代码如下所示。
从代码中可以看出,我使用了一点 hacky 实现。由于我没有找到直接旋转点云的方法,我使用了`RotateModel`的方法来旋转场景中的所有物体。然后我使用`RotateRenderables`来指定网格,并在另一个方向旋转它。最后,设置相机位置、向上方向、背景和图像大小。最终代码如下所示。
有几种方法可以扩展这个例子。如前所述,您可以将球体表示为超二次曲面。你也可以尝试使用不同的相机轨迹,因为 Simple-3dviz 包含了很多选项— `Lines`、`QuadraticBezierCurves`、`Circle`,以及重复移动、在点之间前进和后退等。或者您可以添加键盘和鼠标交互。
# 使用 PlotOptiX 进行可视化(需要支持 CUDA 的 GPU)

PlotOptiX 结果|作者图片
作为这些文章的一部分,我们将探索更有趣的库之一, [PlotOptiX](https://github.com/rnd-team-dev/plotoptix) 是一个 3D 光线跟踪包,用于网格、点云和非常大的数据集的可视化。它产生高保真效果,受益于最现代的后期处理和效果,如色调校正、去噪、抗锯齿、动态景深、色差等。该库被 Meta、Google 和 Adobe 广泛使用。它使用英伟达的 [Optix 7.3](https://developer.nvidia.com/rtx/ray-tracing/optix) 框架,在基于 RTX 的卡上,它运行时进行了许多优化。它既提供高分辨率的静态渲染,也提供实时动画和交互。它可以直接与其他库接口,如用于有限元网格生成的 Python 包装器 [pygmsh](https://github.com/meshpro/pygmsh) 或本文第 1 部分中已经提到的[Trimesh](https://medium.com/@inikolov17/python-libraries-for-mesh-and-point-cloud-visualization-part-1-daa2af36de30)。
该库可以在 Linux、Mac 和 Windows 上运行,需要 3.6 及更高版本的 64 位 Python。在 Windows 上,需要安装[。Net Framework](https://dotnet.microsoft.com/download/dotnet-framework) ≥ 4.6.1,而在 Linux 上需要安装 [Mono](https://www.mono-project.com/download/stable/#download-lin) 、 [pythonnet](http://pythonnet.github.io/) 、 [FFmpeg](https://ffmpeg.org/download.html) 。关于安装的更多信息可以在 PlotOptix 的 GitHub 页面上阅读。一旦安装了所有的先决条件,就可以在新的 Anaconda 环境中安装这个库了。
conda create -n plototix_env python=3.8
conda activate plototix_env
pip install plotoptixpip install tk
pip install pyqt5
在我的例子中,我需要按照建议安装 Tkinter 和 PyQt,以便能够可视化结果。最后,请注意 PlotOptiX **需要 Nvidia 卡,**,因为它使用了许多 CUDA 功能。就我而言,我有一台英伟达 GTX 1070 TI,在编写 512.15 时,该库要求我将驱动程序更新到最新版本。如果您在运行代码时遇到神秘的错误,一个好的第一步故障诊断是更新您的驱动程序。
一旦所有东西都安装好了,你可以通过调用`import plotoptix`然后调用`print(plotoptix.__version__)`来检查它。如果没有检测到问题,下一步是尝试 PlotOptiX GitHub 页面上提供的“Hello World”示例— [此处](https://github.com/rnd-team-dev/plotoptix/blob/master/examples/1_basics/0_try_plotoptix.py)。其他示例也可以通过调用
python -m plotoptix.install examples
我们可以使用`load_merged_mesh_obj`在 PlotOptiX 中加载网格。由于我们的网格也有纹理,我们用`load_texture()`加载它,并通过调用`name_of_material['ColorTexture'] = texture_name`用纹理更新材质。这样,当我们将材质赋予网格时,纹理将被自动分配。我们还需要初始化场景的查看器。在 PlotOptiX 中,有许多对可视化质量和速度有负面影响的调整选项。其中最重要的是`min_accumulation_step`和`max_accumulation_step`。第一个参数控制在显示可视化之前每次迭代将累积多少帧,而第二个参数指定每个场景的最大累积帧。这两个值越高,产生的结果越清晰,噪波越少,但是会大大降低可视化的速度。此外,可以通过调用`set_uint()`或`set_float()`方法以及所需的变量和新值来显式调整着色器的值。在我们的例子中,我们改变了`path_seg_range`,它改变了每条射线被追踪的范围。我们这样做是因为我们需要更高的值来正确地可视化赋予一个基本对象的玻璃材质。最后,我们还可以通过调用`add_postproc`并为其指定所需的效果和值,在渲染的视觉效果上应用后期处理效果。在我们的例子中,我们使用伽马校正和去噪后处理效果来清洁和锐化视觉效果。下面给出了加载天使网格和设置环境的代码。
在 PlotOptiX 中,我们还可以创建有限数量的图元——球体、平行六面体、平行四边形、四面体等。要创建一个,需要带有指定`geom`参数的函数`set_data()`,其中也可以设置对象的位置、大小和材料。使用`setup_light()`方法创建灯光,通过调用`in_geometry=True`可以将灯光设置为物理对象并可视化,并且可以显式设置其大小、颜色和位置。下面给出了用玻璃材质创建一个平行六面体的示例代码。
PlotOptiX 处理动画和交互性的方式是通过两种类型的回调函数— `on_scene_compute`和`on_rt_completed`。第一个在渲染前被调用,用于计算 CPU 端需要的一切。在我们的例子中,我们用它来增加一个计数器并计算光线的旋转位置。第二个在光线跟踪之后调用,用于计算和绘制 GPU 端的所有内容。在我们的例子中,我们使用它来调用对象和灯光的所有旋转和平移函数。这两种功能都可以随时暂停、恢复或停止。在我们的例子中,我们还使用一个简单的类来保存两个函数都会用到的所有必要变量。这两个函数被设置为在查看器初始化时调用。下面给出了代码。
`rotate_geometry`用于方便旋转,这里的中心可以是物体的质心,也可以是空间中的一个特定点。对于光的移动,我们没有特定的旋转函数,所以我们在`compute_changes`函数中计算新位置,并使用`update_light`函数设置新位置。网格可视化的完整代码如下所示。
PlotOptiX 的一个非常好的用例是创建 3D 数据图的光线跟踪渲染。这种可视化在观众面前的演示以及视频演示和数据概述中特别有用,其中这种渲染可用于吸引人们的注意力。目前,该库缺少一种可视化 3D 轴和原生 3D 文本标签的方法,但从 pandas 或 numpy 绘制数据非常容易。

使用 PlotOptiX 以 3D 方式可视化天气(温度/湿度/露点)数据。噪波像素是光线跟踪帧通道数较少的结果。|图片作者
PlotOptiX 还经过优化,可以显示大量图元,并利用 RTX 卡进一步加快可视化速度。我们可以通过从天气数据集中选择三列来证明这一点——温度、湿度和露点。我们将使用总计约 9K 个数据点的所有列。我们使用 pandas 加载数据,并将这三列转换成一个 NumPy 数组,以便加载到 PlotOptiX 的函数中。一旦数据在 NumPy 中,我们可以直接将它输入到`set_data()`方法的位置,我们也可以根据特定的列改变数据点的颜色和半径。下面的代码给出了数据的加载和数据点的设置。
这里我们还使用了`map_to_colors`方法将数据的温度列映射到 matplotlib 中的颜色图,PlotOptiX 使用这个颜色图。当我们设置数据时,我们也可以根据数据集列设置半径。在我们的例子中,我们将其设置为湿度列,并用标量值对其进行缩放。不同的几何图元也可以用于可视化数据点。还为数据点周围的侧板和底板创建了两个平面。我们创建了一个平行六面体和一个 2D 平行四边形,来演示如何创建它们,并给它们不同的预定义材质。最后,我们再次使用回调为摄像机创建一个简单的运动模式,并设置后期处理效果。下面是完整的代码。
# 使用 Polyscope 可视化

Polyscope 结果|作者图片
如果你需要一个轻量级的、易于设置和使用的浏览器和用户界面生成器, [Polyscope](https://polyscope.run/py/) 是最好的、易于使用的、成熟的库之一。它有 C++和 Python 两个版本,可用于通过代码或通过内置 GUI 手动轻松可视化和操作点云、数据集和网格。
该程序包含几个预构建的材质和着色器,以及体积网格,曲线网络,曲面网格和点云。它可以在 Linux、Mac 和 Windows 上运行。唯一的要求是操作系统需要支持 OpenGL>3.3 核心,并且能够打开显示窗口。这意味着 Polyscope 不支持无头渲染。它需要 3.5 以上的 Python 版本。同样,我们使用 Anaconda 环境来创建一个环境,并通过 pip 或 conda 在其中安装库。
conda create -n polyscope_env python=3.8
conda activate polyscope_env
pip install polyscope
OR
conda install conda-forge polyscope
一旦我们安装了库,我们可以通过调用`import polyscope`和`polyscope.init()`来检查一切是否正常。如果没有显示错误,则库安装正确。
Polyscope 一开始就没有包含的一个东西是读取网格数据的方法。这可以通过使用其他库来完成,如 Trimesh 或 Open3D,然后使用这些库中的顶点、面和法线数据在 Polyscope 中创建和可视化网格。由于这些库不是使用 Polyscope 的先决条件,本着分别提供每个库的清晰概述的精神,我们将在中加载天使雕像的点云。txt 格式通过 NumPy。点云包含天使雕像中每个点的 XYZ 位置、颜色和法线。我们可以使用`numpy.loadtxt`加载点云,然后将数组分成点位置、颜色和法线。通过调用`register_point_cloud`然后通过调用`add_color_quantity`添加颜色,这些可以用来生成多边形范围点云。下面给出了代码。
为了创建围绕天使雕像的旋转球体,我们创建了一个单点云,并将其渲染为一个半径较大的球体。由于天使雕像使用了额外的颜色,我们选择了一种可以与颜色混合的材料,而旋转球体使用了一种不能混合的材料。更多关于材料的信息可以在[这里](https://polyscope.run/py/features/materials/)看到。为了生成动画、按钮、鼠标和 GUI 交互,Polyscope 利用了在后台执行的回调,而不暂停主线程。由于 Polyscope 不包含简单的平移和旋转方法,我们实现了一个简单的方法来旋转天使雕像并围绕它移动球体。这在下面给出。
更新渲染点位置的主要方法是调用`update_point_positions`。对于这个例子,我们创建一个绕 Y 轴旋转的旋转矩阵,并计算天使雕像的所有点位置的点积。对于球体,我们计算新的 X 和 Z 位置。对于这两种情况,我们都使用`time.time`计数器。下面是完整的代码。
对于 3D 数据可视化,我们将利用 Polyscope 提供的简单动画工具。我们使用 pandas 导入数据集,并提取其中的三列——温度、湿度和风速。为了可视化这三个天气特征对于每个捕获点是如何变化的,我们利用了数据点之间边缘的可视化,或 Polyscope 中指定的曲线。我们首先把点本身想象成一个点云,其大小取决于温度。然后,我们根据捕捉时间制作点之间的边的动画。下面给出了读取和预处理数据集以及可视化数据点的代码。

温度、湿度和风速随时间的变化通过 3D 边缘图展示|图片由作者提供
一旦使用`add_scalar_quantity`和`set_point_radius_quantity`基于温度值对初始点云进行可视化和缩放,我们可以在每个更新周期调用回调来旋转相机并使用`register_curve_network`重建边缘。使用内置方法`look_at()`旋转摄像机,其中给出摄像机的新位置,摄像机的目标设置为数据集的中点。因为我们想要构建一个显示温度、湿度和风速如何从一个数据点变化到另一个数据点的边的动画,所以我们一次只在全部数据的子集上绘制边。多边形范围曲线网络需要 Ex2 形式的边数据,其中 E 是边的数量,每条边的起点和终点在单独的行上。我们再次利用单独的计数器值来选择回调中的子集。回调函数的代码如下所示。
在回调中,我们需要反复去掉之前的曲线段,然后重新构建。这样做是因为没有简单的方法将元素添加到已经创建的曲线网络中。我们通过得到特定的曲线网络并用`get_curve_network("network_name").remove()`移除它的所有部分来做到这一点。然后,我们添加代表每个边的温度颜色的标量。一旦我们到达数据集的末尾,我们重置计数器并重新开始。下面给出了制作数据集可视化动画的完整代码。
# 使用 Pyrender 的可视化

Pyrender 结果|作者图片
另一个相对轻量级但功能强大的可视化和动画库是 Pyrender。它建立在 Trimesh 之上,用于导入网格和点云。该库的一个有用功能是它附带了一个查看器和一个屏幕外渲染器,这使得它非常适合在无头模式下工作,并集成到深度学习数据提取、预处理和结果聚合中。Pyrender 是使用纯 Python 构建的,可以在 Python 2 和 Python 3 上工作。
集成的查看器带有一组预建的命令,用于简单的动画、法线和面部可视化、改变照明以及保存图像和 gif。它还带有有限但易于使用的金属粗糙材料支持和透明度。最后,屏幕外渲染器也可以用来生成深度图像。
该库可以在 Linux、Mac 和 Windows 上运行。安装直接使用 pip,并将所有依赖项与 Trimesh 一起安装。像往常一样,我们为库创建一个 Anaconda 环境并安装它。
conda create -n pyrender_env python=3.6
conda activate pyrender_env
pip install pyrender
一旦安装了库,我们可以通过导入它`import pyrender`并调用`print(pyrender.__version__)`来检查一切是否正常。如果没有出现错误,则安装成功。按照规定,Pyrender 无法导入网格和点云,但具有与 Trimesh 的内置互操作性。要加载天使雕像网格,我们首先使用`trimesh.load(path_to_angel)`加载,然后我们可以调用`pyrender.Mesh.from_trimesh(trimesh_object)`将其转换为可以在 Pyrender 中使用的对象。我们用同样的方法创建球体和地平面。我们首先通过调用 Trimesh 中的`trimesh.creation.uv_sphere()`和`trimesh.creation.box()`来创建一个 UV 球体图元和盒子,然后通过分别调用`pyrender.Mesh.from_trimesh()`将它们转换成 Pyrender 对象。Pyrender 可以渲染三种灯光——平行光、聚光灯和点光源,以及两种类型的相机——透视和正交。在我们的例子中,我们使用一个点光源`pyrender.PointLight()`,和一个透视相机`pyrender.PerspectiveCamera()`。下面是加载天使雕像,创建原始物体,相机和灯光的代码。
Pyrender 严重依赖于其场景的节点结构,其中每个对象都作为一个单独的节点添加到场景集中。每个节点可以包含网格、摄影机或灯光,这些对象可以通过用于更改其变换和属性的方法来显式调用或引用。每个创建的对象都被添加到一个节点,并设置它们的初始位置。这些节点将在稍后设置动画时被引用。下面给出了代码。
一旦创建了所有的节点,我们初始化查看器,并用包含[可能选项](https://pyrender.readthedocs.io/en/latest/generated/pyrender.viewer.Viewer.html)的字典设置查看器和呈现器标志。为了创建动画,Pyrender 有一个方法`viewer.is_active`,该方法返回 True,直到查看器关闭。我们用它来创建一个 while 循环,在这里我们改变对象的位置和方向。我们把所有用于改变对象的代码放在`viewer.render_lock.acquire()`和`viewer.render_lock.release()`之间,它们停止并释放渲染器,所以可以进行改变。代码如下。
在循环中,我们利用`trimesh.transformations.rotation_matrix()`来计算天使雕像绕 Y 轴的旋转。一旦计算出新的变换矩阵,就通过调用`scene.set_pose(name_of_node, transformation_matrix)`将其应用于特定对象。在循环结束时,`time.sleep()`被调用,因为没有它,观看者无法进行可视化。Pyrender 的完整代码如下。
Pyrender 可以从 Trimesh 访问所有图元,并可以基于 NumPy 数组中的 3D 坐标直接创建大量对象。我们可以利用它来可视化数据集的温度、湿度和风速列。此外,我们将结合使用胶囊原语可视化风向。我们再次使用熊猫来加载数据。我们提取必要的列,并从中创建一个 NumPy 数组,作为 Pyrender 对象的输入。为了生成风向胶囊,我们取[0:360]之间的值,将其转换成弧度,并输入到`trimesh.transformations.rotation_matrix`。例如,任意选择围绕世界 Y 轴旋转胶囊。所得到的变换矩阵被保存并在以后用于定位和旋转胶囊图元。代码如下。

温度、湿度、风速图,用“箭头”表示每个点的风向|图片由作者提供
一旦我们导入了所有必要的数据并预处理了旋转,Pyrender 对象就被创建并从数据集中填充。创建一个`trimesh.creation.uv_sphere()`和`trimesh.creation.capsule()`对象。这些对象然后被用作`pyrender.Mesh.from_trimesh()`方法的输入,连同数据集数组作为位置数据。为此,我们使用`numpy.tile()`函数为每个点创建一个 4x4 的变换矩阵。对于球体,我们只添加来自数据集的定位数据,而对于胶囊,我们还添加计算的旋转矩阵。每个这样创建的点云然后作为节点添加到场景中。最后,我们创建一个相机对象,将其添加到一个节点,并给它一个俯视数据集中点的位置。在我们的例子中,我们只需调用浏览器中的预建功能,按下“A”键即可旋转摄像机。为了确保为旋转选择正确的轴,我们通过给观察者一个`viewer_flag = {"rotation_axis":[0,1,0]}`来明确指定它。下面给出了数据集可视化的完整代码。
# 结论
恭喜你完成这篇教程文章!这是一个很长的阅读。通过本文的这两个部分,我希望更多的人将使用这些非常有用、通用和直观的库,使他们的数据、网格和点云可视化脱颖而出。每一个被探索的库都有优点和缺点,但是它们结合在一起形成了一个非常强大的武器库,每个研究人员和开发人员都可以利用,还有更广为人知的包,如 Matplotlib、Plotly、Seaborn 等。在接下来的文章中,我将关注具体的用例,如体素化、特征提取、距离计算、RANSAC 实现等。这对数据科学家、机器学习工程师和计算机图形程序员都很有用。
如果你想了解更多关于从点云和网格中提取特征的内容,你可以看看我的一些关于 3D 表面检查和噪声检测的文章[2]和[7]。你可以在我的 [**页面**](https://ivannikolov.carrd.co/) 上找到文章,加上我的其他研究,如果你发现一些有趣的东西或者只是想聊天,请随时给我留言。敬请关注更多内容!
# 参考
1. **尼科洛夫,I.** ,&麦德森,C. (2016,10 月)。在不同的拍摄条件下,对 motion 3D 重建软件的近距离结构进行基准测试。在*欧洲-地中海会议*(第 15-26 页)。施普林格、湛;[https://doi.org/10.1007/978-3-319-48496-9_2](https://doi.org/10.1007/978-3-319-48496-9_2)
2. **尼科洛夫,I.** ,&麦德森,C. (2020)。粗暴还是吵闹?SfM 重建中噪声估计的度量。*传感器*、 *20* (19)、5725;[https://doi.org/10.3390/s20195725](https://doi.org/10.3390/s20195725)
3. **尼科洛夫,I. A.** ,&麦德森,C. B. (2019,2 月)。测试 SfM 图像捕获配置的交互式环境。在*第十四届计算机视觉、成像和计算机图形学理论与应用国际联合会议(Visigrapp 2019)* (第 317–322 页)。科学出版社数字图书馆;[https://doi.org/10.5220/0007566703170322](https://doi.org/10.5220/0007566703170322)
4. **尼科洛夫一世**;Madsen,C. (2020),“GGG 基准 SfM:在不同捕获条件下对近距离 SfM 软件性能进行基准测试的数据集”,门德利数据,第 4 版;[https://doi.org/10.17632/bzxk2n78s9.4](https://doi.org/10.17632/bzxk2n78s9.4)
5. **尼科洛夫一世**;麦德森,C. (2020),“GGG——粗糙还是嘈杂?SfM 重建中的噪声检测指标”,门德利数据,V2;[https://doi.org/10.17632/xtv5y29xvz.2](https://doi.org/10.17632/xtv5y29xvz.2)
6. **Nikolov,I.** ,Philipsen,M. P .,Liu,j .,Dueholm,J. V .,Johansen,A. S .,Nasrollahi,k .,& Moeslund,T. B. (2021)。漂移中的季节:研究概念漂移的长期热成像数据集。第三十五届神经信息处理系统会议;[https://openreview.net/forum?id=LjjqegBNtPi](https://openreview.net/forum?id=LjjqegBNtPi)
7. **尼科洛夫,I.** ,&马德森,C. B. (2021)。使用砂纸粒度量化风力涡轮机叶片表面粗糙度:初步探索。在*第 16 届计算机视觉理论与应用国际会议*(第 801–808 页)。科学出版社数字图书馆;[https://doi.org/10.5220/0010283908010808](https://doi.org/10.5220/0010283908010808)*
# Python 列表打印——打印列表的 7 种不同方式,你必须知道
> 原文:<https://towardsdatascience.com/python-list-print-7-different-ways-to-print-a-list-you-must-know-9b790fbb24d5>
## **探索用 Python 打印和格式化列表的无限可能性**

阿诺德·弗朗西斯卡在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的照片
Python 的列表数据结构是为了简单和灵活而构建的。我们将看看任何人如何利用列表作为自动化和完成乏味任务的重要工具。
我将展示几种不同的用 Python 打印列表的技术。我将介绍使用 Python 内置的`print()`方法打印列表的基础知识,使用循环打印列表,以及一些巧妙的格式化技巧,比如在多行上打印列表。
不想看书?请观看我的视频:
# Python 中的列表是什么?
任何编程语言最重要的特性之一就是定义变量的能力。变量允许程序员存储信息,如数字或文本串,并有效地重用它。这是使计算机成为如此了不起的工具的部分原因;我们可以给它们一些信息,它们可以比人类更容易地记住和操作这些信息。
像变量一样,Python 列表也存储信息。更具体地说,它们储存信息序列。这与您可能熟悉的模拟列表没有什么不同。如果一个人想储存一系列要做的事情,这样他们一整天都会记得,他们可以写一个要做的事情清单。购物清单是另一个例子。
虽然一些编程语言中的数组和列表结构需要学习时髦的语法,并对它们可以保存的数据类型有严格的规则,但 Python 列表没有这些!这使得它们使用起来非常简单,而且同样灵活。现在我们来看看如何定义列表和访问列表中的信息。看看这个:
my_list = ['pizza', 'cookies', 'soda']
在这里,我定义了一个名为`my_list`的列表,它的元素是字符串,但是列表的元素也可以是数字,比如整数和浮点数,其他列表,或者不同类型的混合。你可能会认为这个列表代表了购物清单上的项目和我想要购买的数量。
如果我想专门访问这些信息,我会使用一种叫做列表索引的技术。使用列表索引,只需将希望访问的元素的编号(从 0 开始)放在列表名称后面的方括号中。例如,如果我想打印`my_list`的第一个和第三个元素,我会使用下面的代码:
print(my_list[0], my_list[2])
输出:

图 1 —用 Python 打印单个列表元素(图片由作者提供)
请注意我是如何使用索引 0 和 2 来表示第一个和第三个元素的,因为 Python 索引是从 0 开始的。
现在,这肯定是有用的,但你可能想知道这比处理普通变量更有效。毕竟,在上面的例子中,我们必须指定想要单独打印的每个元素。
接下来,我将向您展示如何开始利用 Python 的一些其他内置特性来更高效地访问列表中的信息。
# 使用 For 循环打印 Python 列表
也许访问和操作列表中信息的最直观的方式之一是使用循环的*。这种技术如此直观的原因是,它读起来不像编程,更像书面英语!*
假设你想向朋友描述你购买购物清单上物品的意图。你可能会说这样的话,“对于我购物清单上的每一件商品,我都想购买这个数量”。你可以用 Python 写几乎相同的东西来遍历你的 Python 列表。让我们根据 Python 列表的内容打印一些格式化的字符串!
for element in my_list:
print(f"I bought {element}!")
输出:

图 2-在 for 循环中打印 Python 列表(图片由作者提供)
在这里,您可以看到我能够遍历列表中的每个元素,而不必手动指定每个元素。想象一下,如果你的列表有 50,000 个元素,而不是只有 3 个,这会节省多少工作量!
您还可以结合使用 for 循环和内置的 Python `range()`方法来遍历列表:
for element in range(len(my_list)):
print(f"I bought {my_list[element]}!")
这里的输出是相同的,但是`element`现在引用的是索引位置,而不是像上一个例子那样引用列表中的实际元素。我们再次使用列表索引来访问由`element`指定位置的元素。
现在,我们有所进展。希望您能看到 Python 为访问存储在列表中的信息序列提供了一些很好的工具。让我们来看看其他一些打印 Python 列表的方法!
# 使用*运算符打印 Python 列表
另一种打印列表所有内容的方法是使用`*`或“splat”操作符。splat 操作符可用于将列表的所有内容传递给函数。例如,内置的 Python 方法`print()`可以在 splat 操作符之前输入列表名称时,逐个元素地打印 Python 列表。
让我们看看这在代码中是什么样子的:
print(*my_list)
输出:

图 3-使用*运算符打印 Python 列表(图片由作者提供)
就这么简单!
# 用 Print()方法打印列表
如果您想要一种比这更容易使用的技术,您可以简单地将列表的名称传递给`print()`方法本身!您应该注意到,在这种情况下,输出看起来会有点不同。看看你是否能发现不同之处:
print(my_list)
输出:

图 4-使用 print()方法打印 Python 列表(图片由作者提供)
这种情况下的输出更加“原始”。列表周围仍然有方括号,每个元素周围都有引号。
# 用 map()方法打印一个列表
打印列表元素的另一种方法是使用 Python 内置的`map()`方法。这个方法有两个参数:一个应用于列表中每个元素的函数和一个列表。这里,我们将传递`print`作为应用于每个元素的函数,并将传递`my_list`作为我们的列表。此外,`map()`返回一个 map 对象,所以我们必须将结果转换成一个列表,以便正确打印元素。
list(map(print, my_list))
输出:

图 5 —使用 map()方法打印列表(图片由作者提供)
可以忽略输出中的`[None, None, None]`。这仅仅是指`print()`方法不返回任何值。它只是打印!
# 用 join()方法打印列表
另一种打印 Python 列表的简便方法是使用`join()`方法。`join()`是一个字符串方法,意味着它必须在字符串上被调用。我们一会儿就会明白这意味着什么。使用这种方法,您可以指定一个分隔符,当列表中的每个元素都被打印出来时,该分隔符将出现在它们之间。如果听起来很复杂,我相信代码片段会让它变得清晰。
print(' '.join(my_list))
输出:

图 6-使用 join()方法打印 Python 列表(图片由作者提供)
您可以在代码中看到,我们正在使用我们的好朋友`print()`方法来打印列表。不同的是,我们从指定一个字符串开始。在这种情况下,字符串只是一个空格。然后,正如我之前提到的,我们在这个字符串上调用字符串方法`join()`,并在我们的列表中传递它。这有效地连接了我们列表中的每个元素,并用一个空格将它们连接起来。我们打印出这个结果,输出让人想起我们已经看过的一些以前的方法的输出。
# 使用自定义分隔符在单行上打印列表
正如我在上一节中提到的,我们也可以使用`join()`方法打印带有自定义分隔符或分隔符的列表。我们需要对上一个例子中的代码做的唯一改变是改变我们调用`join()`的字符串。我们在第一个例子中使用了空格,但是你可以使用任何字符串!这里,我们将使用字符串`" I like "`(注意开头和结尾的空格)。
print(' I like '.join(my_list))
输出:

图 7-打印带有自定义分隔符的列表(作者图片)
您可以看到,我们没有用空格连接列表元素,而是用自定义分隔符`" I like "`将它们连接在一起。当您想要创建 CSV 或 TSV(逗号分隔或制表符分隔值)文件时,这很有用。这些是数据科学应用中非常常见的格式。您只需要指定一个`","`或`"\t"`作为您的分隔符字符串。
# 高级:如何在 Python 中垂直打印列表?
在一些高级用例中,您可能有一个列表列表,并且希望垂直打印列表项。用单个列表很容易做到,但用更复杂的数据结构就不那么直观了。
检查以下列表—它包含三个列表,每个列表代表三名学生在测试中的分数(总共 3 次测试):
test_scores = [[91, 67, 32], [55, 79, 83], [99, 12, 49]]
要获得第一个学生的分数,我们可以使用以下语法访问元素:
test_scores[0][0], test_scores[1][0], test_scores[2][0]
但是写起来很乏味,而且不可伸缩。相反,我们可以使用两个 for 循环——第一个获取三个列表,第二个获取单个列表项。然后,您可以使用自定义结束字符调用`print()`方法。不要忘记在与第一个循环相同的缩进级别再次调用`print()`方法——否则,所有元素都将打印在同一行:
for i in range(len(test_scores)):
for j in test_scores:
print(j[i], end=' ')
print()
输出:

图 8-垂直打印 Python 列表(图片由作者提供)
您可以通过使用`zip()`方法进一步简化语法:
for t1, t2, t3 in zip(*test_scores):
print(t1, t2, t3)
输出是相同的,但是代码要短得多。
# 结论
我希望通过这些例子,您能更好地理解 Python 提供的访问存储在 Python 列表中的信息的各种方法。这些技术为程序员提供了对格式的大量控制,只需一行代码,您就可以得到想要的结果输出!Python 做了出色的工作,使得这样的任务变得非常简单,有时就像用英语写作一样简单。
请务必继续关注更深入的 Python 教程!
## 推荐阅读
* [学习数据科学先决条件(数学、统计和编程)的 5 本最佳书籍](https://betterdatascience.com/best-data-science-prerequisite-books/)
* [2022 年学习数据科学的前 5 本书](https://betterdatascience.com/top-books-to-learn-data-science/)
* [如何在本地安装阿帕奇气流](https://betterdatascience.com/apache-airflow-install/)
## 保持联系
* 雇用我作为一名技术作家
* 订阅 [YouTube](https://www.youtube.com/c/BetterDataScience)
* 在 [LinkedIn](https://www.linkedin.com/in/darioradecic/) 上连接
*喜欢这篇文章吗?成为* [*中等会员*](https://medium.com/@radecicdario/membership) *继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。*
<https://medium.com/@radecicdario/membership>
*原载于 2022 年 3 月 21 日 https://betterdatascience.com**的* [*。*](https://betterdatascience.com/python-list-print/)
# Python 列出了| 10 个数据操作的必备操作
> 原文:<https://towardsdatascience.com/python-lists-10-must-know-operations-for-data-manipulation-8ee99fb130a2>

Python 列表(作者图片)
Python list 是 Python 中最灵活的内置数据类型之一,因为它可以容纳一组数据类型—字符串、整数、布尔等。这个博客将讨论你需要知道的所有重要的 python 列表概念。
# 列表的属性
Python 列表是有序且可变的。让我们进一步理解这一点:
1. **有序**:列表中的元素有一个定义好的顺序。新元素被添加到列表的末尾。
2. **可变**:我们可以修改列表,即添加、删除和改变 python 列表的元素。
# 如何创建 Python 列表?
Python 列表是使用方括号创建的,其中定义了列表的元素。列表中的元素用逗号分隔。使用下面的代码,我们可以创建一个数字列表(num_list)和一个具有整数和字符串变量类型的混合数据类型列表(mix_list)。
我们也可以用两个方括号创建一个列表列表。例如,我们可以使用下面的列表来存储房子的尺寸。
# 列表上的操作
以下是开始使用 python 列表执行数据操作任务时需要了解的 10 个最重要的操作。
## 1.访问列表的元素
我们可以使用索引[0]、[1]等来访问 python 列表的元素。索引从 0 开始,所以列表的第 n 个元素的索引是“n-1”。

要访问一个列表的连续元素,我们可以使用冒号“:”并像这样设置索引的范围[1:5]。输出中不考虑范围内的最后一个索引,因此[1:5]将给出从索引 1 到 4 的元素。
我们可以使用负索引来访问最后一个。索引 1 将给出最后一个元素,索引 2 将给出倒数第二个元素,依此类推。

## 2.向列表中添加(或追加)元素
可以使用 append 和 insert 函数将元素添加(或追加)到 python 列表中。
使用 insert 函数,我们可以在列表的任何位置添加元素。该函数有两个参数:1)我们要插入元素的索引。2)我们想要插入的元素。
append 函数用于将元素添加到列表的末尾。

## 3.排序列表
我们可以对列表中的元素进行升序或降序排序。在下面的代码中,我们按照升序和降序对主题列表进行排序(使用“reverse = True”)。

## 4.更新列表的元素
因为列表是可变的,我们可以使用元素的索引来更新列表的元素。
例如,在下面的代码中,我们使用索引“科学”将“科学”更新为“计算机”。

## 5.删除列表元素
我们可以使用非常直观的 remove 函数轻松删除列表中的元素。
假设,我们想从主题列表中删除‘Java ’,那么我们可以使用 remove 函数删除它,并提供‘Java’作为参数。

## 6.弹出列表的元素
pop 函数用于从列表中移除或弹出最后一个元素,并输出剩余的元素。

## 7 .**。列表中元素的长度/数量**
我们可以使用 length 函数找到列表中元素的总数。

## **8。列表中的最大元素**
使用非常直观的 max 函数,我们可以很容易地找到列表中的最大值。
Max 函数仅适用于同质列表,即包含相同数据类型元素的列表。

## **9。连接列表**
我们可以使用“+”操作符很容易地连接两个列表。(串联相当于追加两个列表)。

## 10。遍历一个列表
我们还可以遍历列表中的元素,这是一个非常有用的操作,在数据分析中经常使用。
> *如果你想学习 python 进行数据科学和分析,那么你可以查看我的 Udemy* [***课程***](https://www.udemy.com/course/python-course-for-data-analysis-numpy-pandas-matplotlib/?referralCode=C8C67D9CADF02A263E24) ***。***
# 谢谢大家!
如果你觉得我的博客有用,那么你可以 [***关注我***](https://anmol3015.medium.com/subscribe) *每当我发表一个故事时,你都可以直接得到通知。*
*如果你自己喜欢体验媒介,可以考虑通过* [***报名成为会员***](https://anmol3015.medium.com/membership) *来支持我和其他几千位作家。它每个月只需要 5 美元,它极大地支持了我们,作家,而且你也有机会通过你的写作赚钱。*
# 绝对初学者的 Python 日志记录
> 原文:<https://towardsdatascience.com/python-logging-for-absolute-beginners-8e89032b1b4c>
## 停止使用 print 语句进行调试,转而使用更高级的语句

一堆漂亮的原木(图片由 Sebastian Pociecha 在 Unsplash 上拍摄)
日志对于理解应用程序中发生了什么是必不可少的。您不仅可以更容易地捕捉错误,调试也变得更快,因为您有一个很好的跟踪线索,可以直接找到问题。
日志不仅使开发和调试变得更加容易,还有许多其他的优势,我们将在本文中讨论。我们来编码吧!
# 但是首先
让我们首先通过将日志与调试 Python 代码最常用的方式进行比较来理解日志:仅仅使用`print()`语句。简而言之:日志记录提供了打印所提供的一切,甚至更多。让我们来看一下为什么日志记录优于打印的主要优点。
## 设置重要性—在调试模式下执行
有 5 种主要的日志记录类型,它们都有不同的重要性级别
1. 调试
2. 信息
3. 警告
4. 错误
5. 批评的
所以你可以用`logger.debug(somevalue)`或`logger.error(somevalue)`来代替`print(somevalue)`。您可以**设置应用程序范围内的日志级别**。这意味着,如果您将日志记录器设置为`warning`级别,它将只处理`warning`日志和以上所有内容,忽略较低的重要性级别,如`logger.debug()`和`logger.info()`语句。
这样做的最大好处是,您可以在调试模式下启动您的应用程序,此时记录器被设置为最低级别(`debug`)。然后,您将看到应用程序中发生的一切,使开发和调试变得更加容易。一旦你的代码准备好生产,你可以设置你的日志记录器到`error`级别,例如,只捕捉严重的日志。
## 附加信息—元数据
日志记录提供了更多信息。假设您记录了一个错误。除了打印错误消息之外,您还可以添加各种元数据,将您直接引向错误:
* **日期时间**
* 发出日志的文件的**文件名**和**路径**
* **发出日志的函数名称**
* **行号**
* 用户定义的**消息**
假设您在大型应用程序中收到一条日志,指出了问题所在。您可以像下面的例子一样记录错误:
有了这些额外的信息,你可以超快的调试和修复你的代码。
## 流式传输和保存日志—将日志记录到文件或 api
除了打印消息之外,你还可以给你的日志添加处理程序,让**把你的每个日志添加到磁盘上的一个文件**中,甚至**把它发送到一个 API** !
这些功能非常有用。你的应用崩溃了吗?只需阅读日志文件,看看崩溃前发生了什么。
使用 HTTP 处理程序将您的日志发送到一个 API 让您的日志立即到达需要它们的地方。您可以创建一个 API 来监视所有传入的日志(来自各种应用程序),并在需要时通知您。这也是在你的应用上收集用户统计数据的好方法。
</create-a-fast-auto-documented-maintainable-and-easy-to-use-python-api-in-5-lines-of-code-with-4e574c00f70e>
# 实例—代码部分
解释够了,给我看看代码!在这一部分中,我们将重点关注日志记录的基础知识:
* 设置记录器
* 设置日志记录级别
* 确定我们想要记录哪些信息(字段),将日志写入文件
*在* [***这篇文章***](https://mikehuls.medium.com/colored-logs-for-python-2973935a9b02)**中,我们将着重于为日志添加颜色,以便我们可以更容易地分析它们,而在* [***这篇文章***](https://mikehuls.medium.com/python-logging-saving-logs-to-a-file-sending-logs-to-an-api-75ec5964943f) *中,我们将使用处理程序将日志写入文件和 API。**
## *设置记录器*
*这部分不是很难;我们将导入这个包并使用`getLogger`方法获得一个日志记录器。*
**我们也可以使用* `*logging.warning(msg="a message")*` *,这将使用 root logger。最佳实践是使用 getLogger 方法,这样我们的记录器就不会混淆。**
*现在我们可以像这样注销:*
*我的控制台中的输出如下所示:*
**
*记录一些消息(图片由作者提供)*
*您会注意到我们正确地忽略了调试和信息消息。**这是因为默认情况下记录器被设置为** `**warning**` **级别**。让我们解决这个问题。*
## *设置日志记录级别*
*设置日志默认值很简单:*
**
*现在我们所有的日志都被处理了(图片由作者提供)*
*正如您将看到的,它现在将打印出我们所有的日志。此外,我们还会收到一些**更多的信息**:我们会看到日志的**级别名称**和日志记录器的**名称**。这些被称为**字段**。在下一部分中,我们将定制我们的字段以及我们的日志消息的外观。*
*设置 logging.basicConfig 的级别后,您可以使用`logger.setLevel(level=logging.ERROR)`动态设置您的记录器的级别。*
*</cython-for-absolute-beginners-30x-faster-code-in-two-simple-steps-bbb6c10d06ad> *
## *设置字段*
*在前一部分中,您已经看到了默认情况下记录的两个字段:级别名称和记录器名称。让我们定制:*
*让我们首先检查我们的输出:*
**
*我们的日志现在承载了更多的信息(图片由作者提供)*
*我们没有看到调试消息,因为我们已经将日志级别设置为 INFO。此外,您可以看到我们已经在`logging.basicConfig`中指定了日志字段和日期时间格式。*
*字段是这样指定的`%(THEFIELDNAME)s`。有许多可用字段:*
*Python 日志模块中最常用的可用于日志记录的字段*
**查看更多字段* [*此处*](https://gist.github.com/mike-huls/86763de2233d4cbc2b2a1b84bba19b9c) *。**
*另请注意,我们可以对我们的字段进行一点样式化。使用`%(levelname)-8s`中的`-8`,我们指定它应该是 8 个字符长,如果它比这个长度短,就添加空格。*
## *打印出堆栈跟踪*
*当您的代码出错时,您会看到如下所示的堆栈跟踪:*
**
*堆栈跟踪示例(作者图片)*
*日志模块还允许我们捕捉这个异常并记录下来!这很容易做到:*
*添加`exc_info=True`会将堆栈跟踪放入我们的日志消息中,并为我们提供更多关于导致我们错误的原因的信息。在下面的文章中,我们将看到,我们可以将这个堆栈跟踪写到一个文件中,甚至可以将它发送到一个 API,以便将它存储在一个数据库中,或者对它做任何事情。*
*</a-complete-guide-to-using-environment-variables-and-files-with-docker-and-compose-4549c21dc6af> *
# *结论*
*在本文中,我们已经介绍了 Python 中日志记录的基础知识,我希望向您展示日志记录相对于打印的所有优势。下一步是添加文件处理程序,这样我们就可以将日志保存到文件中,通过电子邮件或 API 发送。请查看这篇文章。*
*如果你有任何改进这篇文章的建议;请评论!与此同时,请查看我的其他关于各种编程相关主题的文章,比如:*
* *[面向绝对初学者的 cyt hon——两步代码速度提高 30 倍](https://mikehuls.medium.com/cython-for-absolute-beginners-30x-faster-code-in-two-simple-steps-bbb6c10d06ad)*
* *[Python 为什么这么慢,如何加速](https://mikehuls.medium.com/why-is-python-so-slow-and-how-to-speed-it-up-485b5a84154e)*
* *绝对初学者的 Git:借助视频游戏理解 Git*
* *[在 Python 中使用相对路径的简单技巧](https://mikehuls.medium.com/simple-trick-to-work-with-relative-paths-in-python-c072cdc9acb9)*
* *[Docker:图像和容器的区别](https://mikehuls.medium.com/docker-for-absolute-beginners-the-difference-between-an-image-and-a-container-7e07d4c0c01d)*
* *[Docker 对于绝对初学者——什么是 Docker 以及如何使用它(+示例)](https://mikehuls.medium.com/docker-for-absolute-beginners-what-is-docker-and-how-to-use-it-examples-3d3b11efd830)*
* *[绝对初学者的虚拟环境——什么是虚拟环境,如何创建虚拟环境(+示例](https://mikehuls.medium.com/virtual-environments-for-absolute-beginners-what-is-it-and-how-to-create-one-examples-a48da8982d4b))*
* *[创建并发布你自己的 Python 包](https://mikehuls.medium.com/create-and-publish-your-own-python-package-ea45bee41cdc)*
* *[用 FastAPI 用 5 行代码创建一个快速自动记录、可维护且易于使用的 Python API](https://mikehuls.medium.com/create-a-fast-auto-documented-maintainable-and-easy-to-use-python-api-in-5-lines-of-code-with-4e574c00f70e)*
*编码快乐!*
*—迈克*
*附注:喜欢我正在做的事吗? [*跟我来!*](https://mikehuls.medium.com/membership)*
*<https://mikehuls.medium.com/membership> *
# Python 日志记录——将日志保存到文件中,并将日志发送到 API
> 原文:<https://towardsdatascience.com/python-logging-saving-logs-to-a-file-sending-logs-to-an-api-75ec5964943f>
## 将日志放在调试代码需要的地方

我们将我们的日志发送到需要它们的地方(图片由 [Unsplash](https://unsplash.com/photos/M0ubHuffWmY) 上的 [Dineo Motau](https://unsplash.com/@kingdineo) 提供)
包括登录到您的 Python 应用程序对于了解您的程序做什么和快速调试是必不可少的。它允许您尽可能快速、轻松地解决错误。你的程序可以记录有用的信息,但是当出现问题时*它如何通知你*?我们无法在崩溃的应用程序中读取终端日志!
本文向您展示了使用处理程序保存日志的两种方法。第一种是最简单的;只需**使用 FileHandler 将所有日志写入文件**。第二种方法使用 HttpHandler**将您的日志发送到 HTTP 端点**(类似于 API)。
<https://mikehuls.medium.com/python-logging-for-absolute-beginners-8e89032b1b4c>
让我们先了解一下日志记录时使用处理程序的概念。如您所知,您可以通过创建一个记录器,然后调用该记录器上的一个日志记录方法来记录消息,如下所示:
当我们在上面的例子中调用`error`方法的`debug`、`info`时,我们的日志记录器必须*处理这些日志记录调用。默认情况下,它只是将消息(带有一些元数据,按照`basicConfig`中的格式指定)打印到控制台。*
在下面的部分中,我们向日志记录器添加了更多的处理程序,它们对日志做其他的事情。对于每个处理程序,我们可以指定级别、字段和格式,如下所示。
</create-your-custom-python-package-that-you-can-pip-install-from-your-git-repository-f90465867893>
# 代码示例—实现处理程序
在本文中,我们的目标是为我们的日志记录器添加三个处理程序,每个都有自己的格式:
* **流处理程序**
用于打印到控制台。我们想打印所有日志(`debug`及以上)
* **文件处理器**
将日志保存在一个文件中。我们希望保存除`debug`日志之外的所有日志
* **HTTP 处理器**
通过 HTTP 发送日志(例如发送到一个 API)。我们只想发送`error`和`critical`日志
所有这些记录器都将单独配置;他们将有自己的格式和级别。
</multi-tasking-in-python-speed-up-your-program-10x-by-executing-things-simultaneously-4b4fc7ee71e>
## 1:设置我们的记录器
首先,我们将创建我们的记录器,简单明了:
请注意,我们不再对`basicConfig`做任何事情。我们用`setLevel`方法设置默认级别,接下来我们将分别为每个处理程序指定格式和级别。

溪流中的一根木头(我几乎讲完了木头笑话)(图片由[扎克·史密斯](https://unsplash.com/@zacksmith)在 [Unsplash](https://unsplash.com/photos/kSxzRXZJrag) 上拍摄)
## 2.添加流处理程序
我们将配置流处理程序,将日志发送到控制台,并打印出来:
注意,我们首先专门为流处理程序创建了一个`Formatter`。然后我们将定义实际的 StreamHandler,指定我们希望输出到 sys.stdout(控制台),然后我们将格式化程序设置为处理程序。然后,我们将处理程序添加到日志记录对象中。
结果是:

我们的控制台输出(图片来自作者)
我们已经成功地将日志打印到了控制台上!查看下面的文章,了解如何在您的控制台上创建**彩色日志:**
<https://mikehuls.medium.com/colored-logs-for-python-2973935a9b02>
## 3.添加文件处理程序
这些步骤与流处理程序完全相同。不同之处在于我们指定了另一种格式和不同的级别。
结果:

颜色是由 PyCharm 插件添加的(图片由作者提供)
打开`test.log`文件表明我们不仅仅是写了一个文件,我们还可以清楚地看到日志与 streamHandler 的格式不同。还要注意,我们没有将`debug`日志保存到文件中。就像我们想要的一样。
一旦您使用 filehandler 一段时间,您的日志文件就会变得相当大。查看 [**这篇文章**](https://mikehuls.medium.com/my-logging-file-gets-too-big-python-logging-to-multiple-files-c7fccf97d3a4) 可以解决那个问题。
</simple-trick-to-work-with-relative-paths-in-python-c072cdc9acb9>
## 4.添加 HTTP 处理程序
这些步骤与上一步没有太大区别:
这几行简短的代码将我们的日志通过 HTTP 发送到一个 URL。
***重要的*** *:然而,这个处理程序有一个主要的缺点:它阻塞线程直到它完成请求。在未来,我将重点关注为非阻塞 HTTP 处理程序实现自定义日志处理程序。**[***关注我***](https://mikehuls.medium.com/membership) *敬请关注!**
*</git-for-absolute-beginners-understanding-git-with-the-help-of-a-video-game-88826054459a> *
# *结论*
*在本文中,我们已经讨论了流、文件和 HTTP 的处理程序。还有一些我们没有处理的,比如套接字处理程序和 SMTP 处理程序。因为这篇文章已经很长了,我还没有在这篇文章中涉及到这些,但也许在未来的文章中会涉及到,所以请一定要关注我。*
*我希望一切都像我希望的那样清楚,但如果不是这样,请让我知道我能做些什么来进一步澄清。同时,看看我的其他关于各种编程相关主题的文章,比如:*
* *[面向绝对初学者的 cyt hon——两步代码速度提高 30 倍](https://mikehuls.medium.com/cython-for-absolute-beginners-30x-faster-code-in-two-simple-steps-bbb6c10d06ad)*
* *[Python 为什么这么慢,如何加速](https://mikehuls.medium.com/why-is-python-so-slow-and-how-to-speed-it-up-485b5a84154e)*
* *[Git 绝对初学者:借助视频游戏理解 Git](https://mikehuls.medium.com/git-for-absolute-beginners-understanding-git-with-the-help-of-a-video-game-88826054459a)*
* *[在 Python 中使用相对路径的简单技巧](https://mikehuls.medium.com/simple-trick-to-work-with-relative-paths-in-python-c072cdc9acb9)*
* *面向绝对初学者的 Docker:图像和容器的区别*
* *[Docker(面向绝对初学者)——Docker 是什么以及如何使用(+示例)](https://mikehuls.medium.com/docker-for-absolute-beginners-what-is-docker-and-how-to-use-it-examples-3d3b11efd830)*
* *[绝对初学者的虚拟环境——什么是虚拟环境,如何创建虚拟环境(+示例](https://mikehuls.medium.com/virtual-environments-for-absolute-beginners-what-is-it-and-how-to-create-one-examples-a48da8982d4b))*
* *[创建并发布自己的 Python 包](https://mikehuls.medium.com/create-and-publish-your-own-python-package-ea45bee41cdc)*
* *[用 FastAPI 用 5 行代码创建一个快速自动记录、可维护且易于使用的 Python API](https://mikehuls.medium.com/create-a-fast-auto-documented-maintainable-and-easy-to-use-python-api-in-5-lines-of-code-with-4e574c00f70e)*
*编码快乐!*
*—迈克*
**又及:喜欢我正在做的事吗?* [*跟我来!*](https://mikehuls.medium.com/membership)*
*<https://mikehuls.medium.com/membership> *
# Python 循环:如何在 Python 中迭代的完整指南
> 原文:<https://towardsdatascience.com/python-loops-a-complete-guide-on-how-to-iterate-in-python-b29e0d12211d>
## 利用 Python 中循环的力量

Photo by [愚木混株 cdd20](https://unsplash.com/@cdd20?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/loop?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)
几个月前,我给 T2 写了一篇关于 Python 中的循环和语句的文章。这篇文章是为了让你理解循环和语句。
相反,在本文中,我想通过一些练习更深入地讨论使用循环的一些不同的可能性。
# 1.列表理解
我想给你看的第一件事是列表理解,以及为什么和什么时候使用它。首先,我建议你阅读我的[上一篇关于循环和语句的文章](/loops-and-statements-in-python-a-deep-understanding-with-examples-2099fc6e37d7),因为你需要深入理解这些主题。
当我们想要基于列表中已经存在的值创建新的列表时,我们使用列表理解。举个例子吧。
假设我们有一个包含 10 个数字的列表:
creating alist of 10 numbers
a = list(range(10))
我们希望两个创建两个新列表:
* 值小于 3 的一个
* 另一个值大于 3
对于传统的方法,我们必须编写一个 for 循环,它遍历列表“a ”,并将小于 3 的值附加到一个新列表中,将大于 3 的值附加到另一个新列表中;下面的代码可以做到这一点:
creating a list of 10 numbers
a = list(range(10))#creating two new and empty new lists
y_1, y_2 = [], []#creating the cycle
for y in a:
if y>3:
y_1.append(y)
else:
y_2.append(y)#printing the new lists
print(y_1)
print(y_2)>>> [4, 5, 6, 7, 8, 9]
[0, 1, 2, 3]
使用列表理解方法,我们可以简单地在一行代码中声明一个新列表;就这样:
creating a list of 10 numbers
a = list(range(10))#creating a new list with a condition
c = [x for x in a if x>3]#creating another new list with a condition
d = [x for x in a if x<=3]#printing the new lists
print(c)
print(d)>>>
[4, 5, 6, 7, 8, 9]
[0, 1, 2, 3]
我相信这个例子很好地展示了当我们想要基于现有的列表创建新的列表时,使用列表理解是多么容易。现在,想一个你可能有两个以上条件的情况;例如,您可能想要基于现有的列表创建 5 个列表…您可以真正了解使用列表理解比使用带有“if”、“elif”、“else”的经典 for 循环节省了多少时间和代码。
抱歉,现在我意识到我还没有引入‘elif’结构。让我们快速看一下:
creating a list in a range
a = list(range(30))#creating 3 empty lists
y_1, y_2, y_3 = [], [], []#creating the cycle
for y in a:
if y>=0 and y<10:
y_1.append(y)
elif y>=10 and y<20:
y_2.append(y)
else:
y_3.append(y)#printing the lists
print(y_1)
print(y_2)
print(y_3)>>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
因此,“elif”构造允许我们在第一个“if”构造之后获取数据。
# 2.对于字典中的循环
字典是一个非常有用的“数据容器”,因为每个元素都有一个“键”和一个“值”。例如,假设我们想要存储一些房屋的价格。我们可以创建一个这样的字典:
my dictionary
house_prices = {'house_1':100000, 'house_2':250000, 'house_3':125000}
这里,“key”是字符串部分,“value”是数字。例如,在这些情况下,我们可以遍历键或值。例如,假设我们想打印所有的值,我们可以这样做:
accessing ant printing the values
for values in house_prices.values():
print(values)>>> 100000
250000
125000
请注意,在字典中,“值”甚至可以是字符串。比如,假设我们要存储一些人的名字和姓氏;然后,我们希望将姓名保存在一个列表中并打印该列表:
creating the dictionary
people = {'Jhon':'Doe', 'Mick':'Brown', 'James':'White'}#creaitng an empty list
my_list = []#accessing the values and appending to the list
for keys in people:
my_list.append(keys) #printing the list
print(f'people names are:{my_list}')>>>
people names are:['Jhon', 'Mick', 'James']
你能猜到如何简化这个符号吗?哦,太好了!使用列表理解!
names = [name for name in people]print(names)>>> ['Jhon', 'Mick', 'James']
当然,字典的值也是如此:
surnames = [surname for surname in people.values()]print(surnames)>>> ['Doe', 'Brown', 'White']
现在,有了字典,我们可以创造许多美丽的记录。假设我们已经记录了一些房屋的数据,并且我们知道房屋的坐标、范围、价格以及它是否有一些土地。我们可以将所有这些数据存储在字典中,并根据它们创建一个列表,如下所示:
creating an empty list
estates = []#storing the house data and appending to the list
e1 = {'coordinates':[12.3456, -56.7890], 'extension':2000, 'has_land':False, 'price':10}estates += [e1] #appending to the list#storing the house data and appending to the list
e2 = {'coordinates':[-8.9101, 60.1234], 'extension':12000, 'has_land':False, 'price':125}estates += [e2] #appending to the list#storing the house data and appending to the list
e3 = {'coordinates':[45.6789, 10.3456], 'extension':100, 'has_land':True, 'price':350}estates += [e3] #appending to the list
现在,在我们将字典添加到“estates”列表后,我们可以遍历它并打印如下信息:
for i, e in enumerate(estates):
print(f'\nHouse #:{i}')
print(f"Coordinates: {e['coordinates']}")
print(f"Extension (mq): {e['extension']}")
print(f"Has land?:{'Yes' if e['has_land']==True else 'No'}")
print(f"Price: {e['price']}K $")>>> House #:0
Coordinates: [12.3456, -56.789]
Extension (mq): 2000
Has land?:No
Price: 10K $
House #:1
Coordinates: [-8.9101, 60.1234]
Extension (mq): 12000
Has land?:No
Price: 125K $
House #:2
Coordinates: [45.6789, 10.3456]
Extension (mq): 100
Has land?:Yes
Price: 350K $
请注意,你不能写`print(f'Coordinates:{e['coordinates']}')`,否则会产生一个`f-string:unmatched '['`错误。这是因为,在这种情况下,我在“f”后面和“坐标”中都使用了一个“”。如果像上面的代码一样在“f”后面使用双“,”就不会有任何问题(请参考[关于栈溢出的讨论](https://stackoverflow.com/questions/67540413/f-string-unmatched-in-line-with-function-call))。
# 3.while 循环
我发现虽然周期很容易理解,但可能很难看到它们的真正用途。
顾名思义,当某个特定的条件被满足时,一个 while 循环起作用。一个典型的例子是这样的:
> 打印`i`只要`i`小于 6
我们可以用下面的代码来做这件事:
i = 1
while i < 6:
print(i)
i += 1>> 1
2
3
4
5
6
我认为这个例子可能有点令人困惑,因为我们将“I”设置为等于 1,所以人们可以说:“嗯,而“i <6” is obvious since we set i=6; so, what’s the point?”
While loops are very useful when we have a condition that dynamically changes, for example over time, and I believe that it can be better understood with a practical example.
Consider for example the website “ [worldometers](https://www.worldometers.info/) ”。这个网站实时收集世界统计数据。
让我们假设你想得到世界人口的数据;所以你写一个机器人来获取这些数据。分析家说,世界人口很快将达到 80 亿;所以,你的 bot 可以有一个`while`周期,门槛 80 亿;虽然世界人口比这个门槛要少,但我们可以越过它;当人口达到 80 亿时,我们可以`print(f'we are 8 billions in the world!!')`
# 结论
我希望我给了你一个很好的指导,让你在不同的情况下深刻理解循环。现在是你做一些循环练习和迷你项目的时候了!
**剧透提示**:如果你是一个新手或者你想学习数据科学,并且你喜欢这篇文章,那么考虑一下在接下来的几个月里**我将开始辅导像你一样有抱负的数据科学家**。我会在接下来的几周告诉你我什么时候开始辅导,如果你想预订座位……[*订阅我的邮件列表*](https://federicotrotta.medium.com/subscribe) *:我会通过它和在接下来的文章中传达我辅导之旅的开始。*
考虑成为会员:你可以免费支持我和其他像我一样的作家。点击 [*这里*](https://federicotrotta.medium.com/membership) *成为会员。*
*我们一起连线吧!*
[*中等*](https://federicotrotta.medium.com/)
[*LINKEDIN*](https://www.linkedin.com/in/federico-trotta/)*(给我发送连接请求)*
[推特](https://twitter.com/F_Trotta90)
# 你可能没听说过的 Python 魔术方法
> 原文:<https://towardsdatascience.com/python-magic-methods-you-havent-heard-about-53d11eba448f>
## 还有许多鲜为人知的 Python 魔术方法——让我们来看看它们的作用以及如何在我们的代码中使用它们

[Aaron Huber](https://unsplash.com/@aahubs?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 在 [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上拍摄的照片
Python 的神奇方法——也称为 *dunder* (双下划线)方法——可以用来实现很多很酷的东西。大多数时候我们用它们来做简单的事情,比如构造函数(`__init__`)、字符串表示(`__str__`、`__repr__`)或者算术运算符(`__add__` / `__mul__`)。然而,还有很多你可能没有听说过的神奇方法,在这篇文章中,我们将探索所有这些方法(甚至是隐藏的和未记录的)!
# 迭代器长度
我们都知道可以用来在容器类上实现`len()`功能的`__len__`方法。但是,如果你想得到一个实现迭代器的类对象的长度呢?
你所需要做的就是实现`__length_hint__`方法,正如你在上面看到的,它也存在于内置迭代器(而不是生成器)中。此外,如您所见,它还支持动态长度变化。尽管如此——顾名思义——这实际上只是一个*提示*,而且可能完全不准确:对于列表迭代器,你会得到精确的结果,而对于其他迭代器则不一定。然而,即使它不准确,它对优化也很有帮助,正如不久前介绍它的 [PEP 424](https://peps.python.org/pep-0424/) 中所解释的。
# 元编程
您很少看到的大部分神奇方法都与元编程有关,虽然元编程可能不是您每天都必须使用的,但是有一些方便的技巧您可以使用它。
其中一个技巧是使用`__init_subclass__`作为扩展基类功能的快捷方式,而不必处理元类:
在这里,我们使用它向基类添加关键字参数,可以在定义子类时设置。在真实的用例中,您可能会在想要处理所提供的参数的情况下使用它,而不仅仅是分配给一个属性。
虽然这可能看起来非常晦涩,很少有用,但您可能已经遇到过很多次了,因为它可以在构建 API 时使用,用户可以像在 [SQLAlchemy models](https://docs.sqlalchemy.org/en/14/orm/inheritance.html) 或 [Flask Views](https://github.com/pallets/flask/blob/9b44bf2818d8e3cde422ad7f43fb33dfc6737289/src/flask/views.py#L162) 中那样子类化您的父类。
您可能会发现使用的另一个元类魔术方法是`__call__`。这个方法允许你定制当你*调用*一个类实例时会发生什么:
有趣的是,您可以用它来创建不能被调用的类:
如果你有一个只有静态方法的类,因此没有很好的理由去创建这个类的实例,这是很有用的。
我想到的另一个类似的用例是单例模式——一个最多只能有一个实例的类:
在这里,我们通过实现一个只能有一个实例的全局记录器类来演示这一点。这个概念可能看起来有点复杂,但是这个实现非常简单——`Singleton`类拥有一个私有的`__instance`——如果没有,它就被创建并赋给属性,如果已经存在,它就被返回。
现在,假设您有一个类,并且您想在不调用`__init__`的情况下创建它的一个实例。这个`__new__`魔法方法可以帮上忙:
有些情况下,您可能需要绕过创建实例的通常过程,上面的代码显示了您如何做到这一点。我们不调用`Document(...)`,而是调用`Document.__new__(Document)`,这将创建一个裸实例,而不调用`__init__`。因此,实例属性——在本例中是`text`——没有初始化,为了解决这个问题,我们可以使用`setattr`函数(顺便说一下,这也是一个神奇的方法——`__setattr__`)。
你可能想知道为什么你会想这么做。一个例子是实现可选的构造函数,如下所示:
这里我们定义`from_file`方法,它作为一个构造器,首先用`__new__`创建实例,然后在不调用`__init__`的情况下配置它。
接下来元编程相关的魔术方法我们将在这里看一看是`__getattr__`。当普通属性访问失败时,调用此方法。这可以用来将对缺失方法的访问/调用委托给另一个类:
假设我们想用一些额外的函数定义 string 的自定义实现,比如上面的`custom_operation`。然而,我们不想重复实现每一个单独的字符串方法,例如`split`、`join`、`capitalize`等等。因此,我们使用`__getattr__`来调用这些现有的字符串方法,以防在我们的类中找不到它们。
虽然这对于普通方法来说效果很好,但是请注意,在上面的例子中,魔法方法`__add__`提供的连接等操作并没有被委托。因此,如果我们希望它们也能工作,那么我们就必须重新实现它们。
# 反省
我们将试用的最终元编程相关魔术方法是`__getattribute__`。这张看起来和之前的`__getattr__`很像。然而有一点小小的不同——正如已经提到的,`__getattr__`仅在属性查找失败时被调用,而`__getattribute__`在属性查找尝试之前被调用*。*
因此,您可以使用`__getattribute__`来控制对属性的访问,或者您可以创建一个 decorator 来记录每次访问实例属性的尝试:
`logger` decorator 函数从记录它所修饰的类的原始`__getattribute__`方法开始。然后用自定义方法替换它,在调用原始的`__getattribute__`方法之前,首先记录被访问属性的名称。
# 魔法属性
到目前为止,我们只讨论了魔术方法,但是 Python 中也有相当多的魔术变量/属性。其中一个是`__all__`:
这个神奇的属性可以用来定义从一个模块中导出哪些变量和函数。在这个例子中,我们用单个文件(`__init__.py`)在`.../some_module/`中创建了一个 Python 模块。在这个文件中,我们定义了 2 个变量和一个函数,我们只导出其中的 2 个变量(`func`和`some_var`)。如果我们试图在其他 Python 程序中导入`some_module`的内容,我们只能得到 2 个导出的内容。
不过要注意的是,`__all__`变量只影响上面显示的`*`导入,你仍然可以通过`import some_other_var from some_module`这样的导入来导入未导出的函数和变量。
您可能见过的另一个双下划线变量(模块属性)是`__file__`。这个变量只是标识了访问它的文件的路径:
结合`__all__`和`__file__`,你可以加载一个文件夹中的所有模块:
最后一个我们要尝试的是`__debug__`属性。显然,这可以用于调试,但更具体地说,它可以用于更好地控制断言:
如果我们使用`python example.py`正常运行这段代码,我们会看到`"debugging logs"`被打印出来,但是如果我们使用`python3 -O example.py`,优化标志(`-O`)会将`__debug__`设置为假,并去掉调试消息。因此,如果您在生产环境中使用`-O`运行您的代码,您将不必担心调试遗留下来的被遗忘的`print`调用,因为它们将被全部去除。
# 隐藏和未记录
以上所有的方法和属性可能有些陌生,但是它们都在 Python 文档中。然而,有几个没有明确记录和/或有些隐藏。
例如,您可以运行以下代码来发现几个新代码:
除了这些,还有很多在 Python bug tracker[BPO 23639](https://github.com/python/cpython/issues/67827)中列出。正如在那里指出的,它们中的大多数是不应该被访问的实现细节或私有名称。所以他们不被记录可能是最好的。
# 自己做?
现在,有了这么多神奇的方法和属性,你真的能自己创造吗?你可以,但你不应该。
双下划线名称是为 Python 语言的未来扩展保留的,不应用于您自己的代码。如果您决定在您的代码中使用这样的名称,那么您将冒着在将来将它们添加到 Python 解释器中的风险,这很可能会破坏您的代码。
# 结束语
在这篇文章中,我们看了一些我认为有用或有趣的鲜为人知的神奇方法和属性,然而在文档中列出了更多可能对你有用的方法和属性。大多数可以在 [Python 数据模型文档](https://docs.python.org/3/reference/datamodel.html#special-method-names)中找到。然而,如果你想更深入地挖掘,你可以尝试在 Python 文档中搜索`"__"`,这将会出现[更多的方法和属性](https://docs.python.org/3/search.html?q=__&check_keywords=yes&area=default)来探索和使用。
*本文原帖*[*martinheinz . dev*](https://martinheinz.dev/blog/87)
[成为会员](https://medium.com/@martin.heinz/membership)阅读媒体上的每一个故事。**你的会员费直接支持我和你看的其他作家。**你还可以在媒体上看到所有的故事。
<https://medium.com/@martin.heinz/membership>
你可能也喜欢…
<https://medium.com/@martin.heinz/python-cli-tricks-that-dont-require-any-code-whatsoever-e7bdb9409aeb> <https://betterprogramming.pub/all-the-ways-to-introspect-python-objects-at-runtime-80e6991b4cc6>
# Python 中 if __name__ == "__main__ "是做什么的?
> 原文:<https://towardsdatascience.com/python-main-b729fab7a8c3>
## Python 中主方法的执行时间和方式

由[布莱克·康纳利](https://unsplash.com/@blakeconnally)在[unsplash.com](https://unsplash.com/photos/B3l0g6HLxr8)拍摄的照片
如果您是 Python 的新手,您可能已经注意到,可以使用或不使用 main 方法来运行 Python 脚本。Python 中定义 one 的符号(即`if __name__ == ‘__main__'`)显然不是一目了然的,尤其是对于新手来说。
在今天的教程中,我们将探索和讨论 main 方法的用途,以及在 Python 应用程序中定义一个 main 方法时会遇到什么情况。
## `__name__`的目的是什么?
在执行程序之前,python 解释器将 Python 模块的名称赋给一个名为`__name__`的特殊变量。根据您是通过命令行执行程序还是将模块导入另一个模块,`__name__`的分配会有所不同。
例如,如果您将模块作为脚本调用
python my_module.py
然后 Python 解释器会自动将字符串`'__main__'`赋给特殊变量`__name__`。另一方面,如果您的模块被导入到另一个模块中
Assume that this is another_module.py
import my_module
那么字符串`'my_module'`将被分配给`__name__`。
## main 方法是如何工作的?
现在让我们假设我们有以下模块,它包含以下代码行:
first_module.py
print('Hello from first_module.py')
if name == 'main':
print('Hello from main method of first_module.py')
所以在上面的模块中,我们有一个 print 语句在 main 方法之外,还有一个 print 语句在 main 方法之内。main 方法下的代码只有在模块作为脚本从命令行调用时才会被执行,如下所示:
python first_module.py
Hello from first_module.py
Hello from main method of first_module.py
现在,假设我们不是将模块`first_module`作为脚本调用,而是想将其导入另一个模块:
second_module.py
import first_module
print('Hello from second_module.py')
if name == 'main':
print('Hello from main method of second_module.py')
最后,我们调用`second_module`作为脚本:
python second_module.py
Hello from first_module.py
Hello from second_module.py
Hello from main method of second_module.py
注意,第一个输出来自模块`first_module`,特别是来自 main 方法之外的 print 语句。因为我们没有将`first_module`作为脚本调用,而是将其导入到了`second_module`中,first_module 中的 main 方法将被忽略,因为`if __name__ == ‘__main__'`的计算结果是`False`。回想一下,在上面的调用中,`second_module`的`__name__`变量被赋予了字符串`'__main__'`,而`first_module`的`__name__`变量被赋予了模块名,即`’first_module’`。
虽然`if __name__ == ‘__main__'`下的所有东西都被认为是我们所说的“main 方法”,但是定义一个适当的 main 方法是一个好的实践,如果条件评估为 True,就调用这个方法。举个例子,
my_module.py
def main():
"""The main function of my Python Application"""
print('Hello World')
if name == 'main':
main()
*注意:我通常不鼓励你在一个 Python 应用程序中使用多个主函数。为了举例,我使用了两种不同的主要方法。*
## 最后的想法
在本文中,我描述了如何在 Python 中执行 main 方法,以及在什么条件下执行。当一个模块作为一个字符串被调用时,Python 解释器会将字符串`'__main__'`赋给一个名为`__name__`的特殊变量,然后在条件`if __name__ == ‘__main__'`下定义的代码会被执行。另一方面,当一个模块被导入到另一个模块中时,Python 解释器会将带有该模块名称的字符串赋给特殊变量`__name__`。这意味着在这种情况下,`if __name__ == ‘__main__'`将评估为`False`,这意味着一旦导入,只有该条件之外的代码才会被执行。
[**成为**](https://gmyrianthous.medium.com/membership) **会员,阅读媒体上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。**
<https://gmyrianthous.medium.com/membership>
**相关文章你可能也喜欢**
</args-kwargs-python-d9c71b220970> </python-poetry-83f184ac9ed1> </pycache-python-991424aabad8>
# Python map()函数解释
> 原文:<https://towardsdatascience.com/python-map-function-explained-c2bb23a7c8da>
## 在本文中,我们将探索如何使用 Python **map()** 函数

由[安妮·斯普拉特](https://unsplash.com/@anniespratt?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)在 [Unsplash](https://unsplash.com/photos/AFB6S2kibuk?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上拍摄
**目录**
* 介绍
* 使用 Map()将函数映射到列表上
* 使用 Map()在多个列表上映射一个函数
* 使用 Map()在多个列表上映射 lambda 函数
* 结论
# 介绍
在 Python 中,当我们处理多个可迭代对象([列表](https://pyshark.com/python-list-data-structure/)、[元组](https://pyshark.com/python-tuple-data-structure/)或字符串)时,我们经常需要对可迭代对象中的每个元素应用一个函数。
Python **map()** 函数是一个内置函数,它允许在 iterable 中的每个元素上“映射”或应用特定的函数,并返回一个包含修改元素的新 iterable。
**map()** 功能过程定义为:
map(function, iterable) -> map object
其中*映射对象*可以被转换成一个 iterable(例如使用 **list()** 构造函数的 list)。
# 使用 Map()将函数映射到列表上
让我们从一个简单的例子开始,使用 **map()** 函数将一个函数应用于一个列表。
我们将定义一个 *square* 函数,该函数将一个数字作为参数并返回其平方值,然后将该函数应用于一系列数字以获得它们的平方值列表。
首先,我们将创建一个数字列表:
Create a list of numbers
nums = [1, 2, 3, 4, 5]
接下来,我们将定义一个*平方*函数并测试它:
Define a function to square values
def square(x):
return x*x
Test function
squared_val = square(3)
print(squared_val)
您应该得到:
9
现在这个函数工作了,我们可以在 **nums** 列表中应用它。
Square values in nums list
squared_vals = map(square, nums)
Convert map object to a list
squared_vals = list(squared_vals)
Print squared values
print(squared_vals)
您应该得到:
[1, 4, 9, 16, 25]
如您所见,我们已经能够快速应用 **nums** 列表上的 *square* 函数来获得平方值。
# 使用 Map()在多个列表上映射一个函数
在上一节中,我们有一个*平方*函数,它接受一个参数并将其应用于一个 **nums** 列表。
在本节中,我们将创建一个带两个参数的函数,并使用 **map()** 函数将它应用于两个列表。
首先,我们将创建两个数字列表:
Create two lists of numbers
nums1 = [1, 3, 5, 7, 9]
nums2 = [2, 4, 6, 8, 10]
接下来,我们将定义一个 *add* 函数并测试它:
Define a function to add values
def add(x, y):
return x+y
Test function
add_val = add(3, 5)
print(add_val)
您应该得到:
8
现在该函数已经工作,我们可以在 **nums1** 和 **nums2** 列表上应用它。
Add values from two lists
add_vals = map(add, nums1, nums2)
Convert map object to a list
add_vals = list(add_vals)
Print added values
print(add_vals)
您应该得到:
[3, 7, 11, 15, 19]
这是将列表 **nums1** 和 **nums2** 中的数字相加后的单个值列表。
# 使用 Map()在多个列表上映射 lambda 函数
在上一节中,我们创建了一个 *add* 函数,它接受两个参数,然后可以应用于两个列表。
但是我们能在不定义一个多参数函数的情况下做同样的操作吗?
我们可以使用一个 [Python **lambda** 函数](https://pyshark.com/python-lambda-functions/)和 **map()** 函数来实现。
让我们重复使用上一节中的数字列表:
Create two lists of numbers
nums1 = [1, 3, 5, 7, 9]
nums2 = [2, 4, 6, 8, 10]
现在我们可以在 **nums1** 和 **nums2** 列表上应用一个 **lambda** 函数。
Add values from two lists
add_vals = map(lambda x, y: x + y, nums1, nums2)
Convert map object to a list
add_vals = list(add_vals)
Print added values
print(add_vals)
您应该得到:
[3, 7, 11, 15, 19]
这与[前一节](https://pyshark.com/python-map-function/#map-a-function-over-multiple-lists)中的结果相同。
# 结论
在本文中,我们探索了如何使用 [Python **map()** 函数](https://docs.python.org/3/library/functions.html?highlight=map#map)。
现在你已经知道了基本的功能,你可以练习使用它和其他可迭代的数据结构来完成更复杂的用例。
如果你有任何问题或对编辑有任何建议,请随时在下面留下评论,并查看我的更多 [Python 函数](https://pyshark.com/category/python-functions/)教程。
*原载于 2022 年 12 月 24 日 https://pyshark.com**[*。*](https://pyshark.com/python-map-function/)*
# Python Miniproject:在 PyGame 中从头开始制作游戏
> 原文:<https://towardsdatascience.com/python-miniproject-making-the-game-of-go-from-scratch-in-pygame-d94f406d4944>
围棋这个古老的[游戏已经玩了几千年了。围棋的棋盘配置比宇宙中的原子还要多,它是一种非常复杂和抽象的游戏,产生于一套简单的规则。2016 年,DeepMind 的 AlphaGo 程序在五局比赛中击败了顶级围棋职业选手 Lee Sedol,成为头条新闻。在观看了一部关于 AlphaGo-Lee 比赛的精彩纪录片后,我认为尝试用 Python 从头开始编写一个基本的围棋游戏会很有趣。作为一名程序员,做这样有趣的小项目是我自身发展的一个重要部分,所以我希望你喜欢跟着做,并且在处理自己的项目时也有所启发!](https://en.wikipedia.org/wiki/Go_(game))

图片来自维基共享(CC BY-SA 4.0)
在本文中,我们将探索如何使用 Python 和 PyGame 从头开始创建 Go。在这个过程中,我们将看到如何使用数据科学中的工具来帮助我们更轻松地编写这个游戏。到文章结束时,你会明白:
* 如何用 PyGame 建立一个基本的游戏图形用户界面
* 如何使用集合和 itertools 编写简洁优雅的代码
* 如何使用 NumPy 运算符进行快速矩阵运算
* 如何使用 Networkx 图形库分解复杂问题
## Go 规则快速概述
围棋的规则其实挺简单的。黑白双方轮流将石头放在 19x19 的棋盘上(其他尺寸也是可以的,但这是标准尺寸)。形成连续链(由网格线连接)的一组宝石组成一组。如果一个或一组石头完全被另一种颜色包围,那么这些游戏被捕获并从棋盘上移走。游戏结束时,石头包围更多领土的玩家获胜。我在这里有意掩饰一些细节,但这是基本要点。我们现在的目标是设计一个程序,允许两名玩家在棋盘上放置石头,同时执行围棋规则,例如禁止非法移动和实施捕捉。
## 设计过程
当处理一个复杂的项目时,将问题分解成容易理解的部分会很有帮助。我试图创建封装和模块化的程序,用不同的功能来处理一个问题的各个子部分。在 Go 的例子中,我认为我需要函数来完成以下任务:
* 使用 PyGame 绘制棋盘
* 在 PyGame 的(x,y)坐标和 19x19 网格的离散棋盘位置之间转换
* 根据用户动作(如鼠标点击)更新游戏状态
* 检查尝试的移动是否有效
我尽可能地将游戏的输入/输出与内部游戏状态分开。我创建了一个`Game`类,它存储了关于一个给定游戏的所有数据,比如棋盘的状态和每个玩家捕获的石头或“囚犯”的数量。`Game`类有一个`draw()`函数来处理将棋盘状态呈现给 PyGame GUI,还有一个`update()`函数来检查用户动作,比如鼠标点击和按键。
## 代码
下面的 GitHub 库包含了围棋游戏的完整代码。请注意,这只是一个基本的、最基本的实现,缺少许多你可以在 Go 应用程序和网站中找到的功能,但我选择保持项目简单,专注于本质。您可以看一下代码,然后继续阅读我对一些代码亮点的解释。请随意在 GitHub 上分叉代码,摆弄它,并留下任何评论或问题。
<https://github.com/thomashikaru/gogame/blob/master/gogame.py>
## 用 PyGame 创建一个基本游戏
[PyGame](https://www.pygame.org/docs/) 是一个用于创建基本 GUI 游戏的 Python 库。它支持在屏幕上绘制图形、监视鼠标点击和按键、播放声音以及游戏中可能出现的其他操作。导入 PyGame 后,您可以使用`pygame.init()`来初始化一个 PyGame GUI。其他有用的函数包括`pygame.mouse.get_pos()`获取当前鼠标位置、`blit()`向屏幕发送文本、`flip()`用新的形状更新屏幕。请查看上面的代码,了解这些 PyGame 函数在实践中是如何工作的。

我的围棋游戏截图。
## **NumPy 操作的魔力**
正如你所知,围棋棋盘是一个二维矩阵。NumPy 库有很多非常方便的函数来处理数组和矩阵。
为了生成均匀间隔的网格线,我使用了`np.linspace()`和`np.full()`函数。第一个函数`np.linspace()`在给定的起始值和结束值之间创建一个等距值列表。同时,`np.full()`创建一个所需形状的相同值的数组。这允许我快速地为形成围棋棋盘网格的线段生成一组起点和终点,然后我将它们输入到`pygame.draw.line()`函数中。
另一个非常有用的 NumPy 函数是`np.where()`函数,它返回某个条件为真的数组中的索引。例如,行`np.where(self.board == 1)`返回存储值 1 的棋盘位置(在我的游戏惯例中,这表示存在黑石)。
## Itertools 和集合
我使用了`itertools.product()`来替换嵌套的 for 循环。例如,要快速创建 Go 棋盘上所有网格点的列表(例如(0,0)到(18,18)),我可以使用`itertools.product(range(19), range(19))`。
与此同时,我使用`collections.defaultdict()`创建了一个分数计数器,它优雅地处理递增,即使是空的。第`self.prisoners = collections.defaultdict(int)`行创建了一个类似于 Python 字典的数据结构,除了当一个键第一次访问字典时,它被赋予一个相应的值 0。我喜欢使用`defaultdicts`让我的代码对键错误更加健壮,而不必编写一堆特殊的边缘情况检查。
## 用图论方法识别石材群
围棋游戏的一个基本单位是石头群。一群石头同生共死。当一群石头被完全包围时,所有的石头都可以被一次捕获。但是只要一组中的一块石头至少有一个自由空间(未被占据的相邻棋盘空间),整个组就仍然活着。
为了我们的游戏程序的目的,我们有下面的数学问题:给定一个填充有三个不同值(0 代表空白,1 代表黑石,2 代表白石)的 N×N 矩阵,产生每种颜色的石头组的列表,其中每个石头组是一个石头列表,使得该组中的所有石头形成一个连续的块。这个描述可能有点难以理解,所以请参考下图,看看你能不能算出每种颜色有多少个石组。

每种颜色有多少组石头?
正确答案是有 6 个黑石组和 2 个白石组。不要被对角线方向放置的 3 块黑色石头误导——它们彼此不相连,因为它们没有通过网格线直接连接,所以每块石头都是自己的石头组。我很快意识到,编写一个程序来获取一个游戏板并自动找到所有的石头组并不简单,因此我开始考虑是否可以将这个问题简化为一个已经存在算法的现有问题。
我发现使用图论和 [Networkx 图形库](https://networkx.org/documentation/stable/reference/index.html)得到了一个惊人的解决石头群计数问题的优雅方案。假设我们想找到所有的黑石组。基本的直觉是,我们从一个“网格图”开始,它看起来就像围棋棋盘的网格:每个顶点都连接到它上面、下面、左边和右边的顶点。然后我们移除所有没有黑色石头的顶点。这给我们留下了一个图,其中连接的组件直接对应于石头组!我们可以简单的用 Networkx 内置的`connected_components()`函数返回答案!使用这个函数,我现在可以实现在游戏中检测捕获的代码。这意味着游戏将自动检测何时石头群被包围,将他们从棋盘上移除,并相应地增加囚犯的数量。

游戏现在可以检测石头群的捕获并更新棋盘。
## 后续步骤
试用过代码的严肃围棋玩家可能已经注意到,游戏并没有 100%完成。这个游戏并没有执行 Ko 的规则,不允许玩家重复之前的游戏位置。如果能在游戏结束时自动计算分数,以及像“撤销”这样的便利功能,那就更好了。最后(这是一个很大的挑战),如果有一个人工智能可以对抗一个单独的人类玩家,那就太好了,因为现在这个游戏需要两个人类玩家。
我希望你喜欢阅读我用 Python 构建围棋游戏的经历,并且在这个过程中你学到了一些新东西。如果您有任何其他建议或反馈,请在下面留下评论!
## 参考
[1] PyGame 文档:[https://www.pygame.org/docs/](https://www.pygame.org/docs/)
[2] Networkx 文档:[https://Networkx . org/Documentation/stable/reference/index . html](https://networkx.org/documentation/stable/reference/index.html)
[3]看看这部关于 AlphaGo 与 Lee Sedol 比赛的精彩纪录片:
# 用于贝叶斯测试的 Python 包
> 原文:<https://towardsdatascience.com/python-package-for-bayesian-a-b-testing-86ea3ff5c963>
## 使用贝叶斯方法快速评估 A/B(或 A/B/C/…)测试

杰森·登特在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上拍摄的照片
我从事过几个 A/B 测试项目,我总是更喜欢使用贝叶斯方法和度量标准 ***最佳概率*** ,而不是传统的频率方法和 p 值。这是我选择的两个主要原因:
* **可解释性**:能够说出变体 XYZ 在测试中是所有变体中最好的概率非常方便,尤其是在向企业解释结果的时候。
* **可伸缩性**:对多个变体测试进行最佳伸缩的概率。
这篇文章不会关注 A/B 测试的细节。事实上,有许多文章(甚至在 Medium 或 TDS 上)是关于传统方法与贝叶斯方法的比较,也有许多文章是关于贝叶斯方法如何真正工作的细节。这些文章中有许多附有代码示例,展示了如何使用各自分布中的抽样来生成概率,但是大多数文章只关注二进制数据测试(例如转换)。
我总是缺少一些好的实现,特别是每会话收入数据,其中大多数会话都是非转换(收入为 0),但转换携带收入信息(例如,用户购物会话的收入)。
# Python 包
我为贝叶斯 A/B(或 A/B/C/…)测试创建了一个小的 [python 包](https://pypi.org/project/bayesian-testing/),它可以用于上面提到的两种情况。要安装它,只需使用 pip:
pip install bayesian_testing
# 使用示例
为了这个例子,我用收入信息生成了一些转换数据。在 GitHub [这里](https://github.com/Matt52/bayesian-testing/blob/main/examples/data/session_data.csv)有。它看起来是这样的:

记录数和数据集头
每行代表一个会话,如果会话被转换,则它还包含一个正收入值。以下是一些基本的统计数据:

数据集统计
对于这个生成的数据,我们有最高转换率(5.3%)的变体 ***B*** ,而变体 ***C*** 每会话的收入最高(1.20)。在现实中,可能会发生这样的情况,一种变体转换得更好(可能是因为一些折扣),而另一种变体仍然能够产生更多的收入。
使用该软件包,我们可以分别运行转换率和每次会话收入的测试。
## 1.转化试验
要生成仅使用转换二进制数据(使用 Beta 分布)的最佳概率,我们可以使用 *BinaryDataTest* 类:

转化试验
从转换的角度来看,变体 ***B*** 概率 92.435%最好。
## 2.收入测试
对于收入数据,我们将使用 Delta-Lognormal 测试,该测试结合了使用 Beta 分布和对数正态分布的转换方法,用于非零收入值:

收入测试
从每节收益的角度来看,变体 ***C*** 的概率最好,为 96.85%。
# 评论
* 查看 [GitHub 页面](https://github.com/Matt52/bayesian-testing)查看包实现的细节。
* 除了上面例子中的二进制和对数正态增量数据之外,这个包也有用于正态数据测试的类。
* 在所有情况下,也有可能在添加变量数据时使用参数来指定先验分布(默认设置为低信息先验)。
* 变量数据也可以以聚合形式添加,这有时可能更实用(添加总计和转换,而不是二进制数据的向量)。为此,请看一下`add_variant_data_agg`方法。
# Python Pandas 让您完全控制时间戳
> 原文:<https://towardsdatascience.com/python-pandas-gives-you-full-control-over-timestamps-10fe26c96a9e>
## 从几年到几纳秒。

照片由[洪林肖](https://unsplash.com/@mshaw_1234?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)在 [Unsplash](https://unsplash.com/s/photos/cockpit?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)
Pandas 是使用最广泛的数据分析和操作库之一。我已经广泛使用 Pandas 大约 3 年了,我可以告诉你,我使用它进行数据操作的次数比使用它进行数据分析的次数多得多。
我很确定对于大多数数据科学家来说都是一样的。原因是现实生活中的数据集或原始数据通常不是适合数据分析和建模的格式。因此,它需要大量的清洁、操作和预处理。
我使用 Pandas 三年来的经验是,它提供了灵活有效的方法来解决数据操作任务。在本文中,我将尝试展示操纵熊猫时间戳是多么容易。
> 如果你想在我发表新文章时收到电子邮件,别忘了订阅。
# 时间戳
Timestamp 相当于 Python 的 Datetime,在大多数情况下可以互换。时间戳可以用来表示特定日期的日期或时间。它们可以下降到纳秒级。以下是一些时间戳示例:
import pandas as pd# Date
pd.Timestamp("2021-10-05")# output
Timestamp('2021-10-05 00:00:00')# Date, hour, minute
pd.Timestamp("2021-10-05 15:40")# output
Timestamp('2021-10-05 15:40:00')
我们还可以通过使用整数指定组件来创建时间戳。
Date, hour, minute
pd.Timestamp(year=2021, month=10, day=5, hour=15, minute=40)# output
Timestamp('2021-10-05 15:40:00')
我们刚刚看到了如何创建一个时间戳。然而,我们几乎总是处理大型数据集,而不是单一的信息。幸运的是,Pandas 提供了几个函数和方法来对数据帧执行矢量化操作。
让我们首先创建一个示例 DataFrame,它包含一个日期列和一些其他列。
df = pd.DataFrame({
"Date" : pd.Series(["2022-01-01","2021-10-12",
"2021-10-17","2021-06-12",
"2021-04-01"],
dtype="datetime64[ns]"),
"Amount": [125, 240, 130, 540, 410],
"Price": [24.1, 25.5, 26.6, 26.7, 31.5]
})

df(作者图片)
用于包含日期和时间信息的列的数据类型是`datetime64[ns]`。
df.dtypes# output
Date datetime64[ns]
Amount int64
Price float64
dtype: object
日期列中的每个值都是一个时间戳。
type(df["Date"][0])# output
pandas._libs.tslibs.timestamps.Timestamp
# 日期偏移
我们可以使用两个函数来操作包含日期和时间值的列。第一个是`DateOffset`。
让我们在数据框的日期列中添加 2 个月。
df["Date"] + pd.DateOffset(months=2)# output
0 2022-03-01
1 2021-12-12
2 2021-12-17
3 2021-08-12
4 2021-06-01
Name: Date, dtype: datetime64[ns]
日期列中的每个值都加上 2 个月。
虽然日期列中的值只显示日期而不显示任何时间信息,但是我们可以添加时间间隔,如小时、分钟等。
df["Date"] + pd.DateOffset(hours=5)# output
0 2022-01-01 05:00:00
1 2021-10-12 05:00:00
2 2021-10-17 05:00:00
3 2021-06-12 05:00:00
4 2021-04-01 05:00:00
Name: Date, dtype: datetime64[ns]
为了做减法,我们可以将加号改为减号,或者在函数中使用一个负数。
df["Date"] - pd.DateOffset(years=1)
df["Date"] + pd.DateOffset(years=-1)# output
0 2021-01-01
1 2020-10-12
2 2020-10-17
3 2020-06-12
4 2020-04-01
Name: Date, dtype: datetime64[ns]
我们也可以使用浮点数来指定要加或减的值。例如,以下代码行将 1.5 天添加到日期列中。
df["Date"] + pd.DateOffset(days=1.5)# output
0 2022-01-02 12:00:00
1 2021-10-13 12:00:00
2 2021-10-18 12:00:00
3 2021-06-13 12:00:00
4 2021-04-02 12:00:00
Name: Date, dtype: datetime64[ns]
要加或减的值可以通过使用不同单位的组合来指定。以下代码行添加了 1 个月 1 天 2 小时的间隔。
df["Date"] + pd.DateOffset(months=1, days=1, hours=2)# output
0 2022-02-02 02:00:00
1 2021-11-13 02:00:00
2 2021-11-18 02:00:00
3 2021-07-13 02:00:00
4 2021-05-02 02:00:00
Name: Date, dtype: datetime64[ns]
`DateOffset`功能支持以下单元:
* `years`
* `months`
* `weeks`
* `days`
* `hours`
* `minutes`
* `seconds`
* `microseconds`
* `nanoseconds`
# 时间增量
我们可以用来操作日期和时间的另一个函数是`Timedelta`。逻辑与`DateOffset`功能相同,但语法不同。
> `Timedelta`功能不再支持`year`和`month`单元,但我们可以将它用于其他几个单元,如`week`、`day`、`hour`等。
让我们做一些例子来演示如何使用这个函数。以下代码行将 2 周添加到日期列中。
df["Date"] + pd.Timedelta(value=2, unit="W")# output
0 2022-01-15
1 2021-10-26
2 2021-10-31
3 2021-06-26
4 2021-04-15
Name: Date, dtype: datetime64[ns]
分别使用`value`和`unit`参数分别定义数量和单位。支持以下单位:
* `W`:周
* `D`:日
* `H`:小时
* `T`:分钟
* `S`:秒
* `L`:毫秒
* `U`:微秒
* `N`:纳秒
我们也可以使用小写字母,如 w 代表星期,h 代表小时,等等。
我们可以用减号代替正号,或者在函数中用负数来减去一个值。
df["Date"] + pd.Timedelta(value=-5, unit="d")# output
0 2021-12-27
1 2021-10-07
2 2021-10-12
3 2021-06-07
4 2021-03-27
Name: Date, dtype: datetime64[ns]
`Timedelta`函数也接受字符串来定义要增加或减少的持续时间。我们只需要确保正确地写出单位。例如,以下代码行将 6 周添加到日期列中。
df["Date"] + pd.Timedelta("6 W")# output
0 2022-02-12
1 2021-11-23
2 2021-11-28
3 2021-07-24
4 2021-05-13
Name: Date, dtype: datetime64[ns]
使用字符串的一个好处是我们可以用多个单位来定义持续时间。以下是添加 3 周 4 天 2 小时的示例。
df["Date"] + pd.Timedelta("3 W 4 D 2 H")# output
0 2022-01-26 02:00:00
1 2021-11-06 02:00:00
2 2021-11-11 02:00:00
3 2021-07-07 02:00:00
4 2021-04-26 02:00:00
Name: Date, dtype: datetime64[ns]
顺序无关紧要,因此下面的代码行执行与上面相同的操作。
df["Date"] + pd.Timedelta("4 D 3 W 2 H")
与其他数据类型一样,Pandas 能够操作和处理时间戳。它提供了高度灵活和高效的方法,加快了典型数据清理和处理工作流中的任务。
如果你想在我发表新文章时收到电子邮件,别忘了订阅。
*你可以成为* [*媒介会员*](https://sonery.medium.com/membership) *解锁我的全部写作权限,外加其余媒介。如果您使用以下链接,我将收取您的一部分会员费,无需您支付额外费用。*
<https://sonery.medium.com/membership>
感谢您的阅读。如果您有任何反馈,请告诉我。
# Python 熊猫把戏:连接数据集的 3 种最佳方法
> 原文:<https://towardsdatascience.com/python-pandas-tricks-3-best-methods-4a909843f5bc>
## 计算机编程语言
## Python 大师的 merge、concat、join 在你的咖啡时间!

照片由 [Duy Pham](https://unsplash.com/@miinyuii?utm_source=medium&utm_medium=referral) 在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上拍摄
**Python 是数据分析的最佳工具包!**🔋
数据分析任务所需的数据通常来自多个来源。因此,学习整合这些数据的方法非常重要。
在本文中,我列出了使用 Python pandas 方法组合多个数据集的三种最佳且最省时的方法。
merge(): To combine the datasets on common column or index or both.
concat(): To combine the datasets across rows or columns.
join(): To combine the datasets on key column or index.
我把这篇文章写得很短,这样您就可以边喝咖啡边完成它,掌握最有用、最省时的 Python 技巧。
您可以使用下面的索引快速导航到您最喜欢的技巧。
> *🚩*注意:我将使用我为练习创建的[虚拟课程数据集](https://github.com/17rsuraj/data-curious/blob/master/TowardsDataScience/Dummy_course_data.xlsx)。它可以在 [Github](https://github.com/17rsuraj/data-curious/blob/master/TowardsDataScience/Dummy_course_data.xlsx) 上找到供您使用。我在整篇文章中使用熊猫。
好了,我们开始吧…🚀
在开始之前,让我们使用下面的代码在 dataframes 中获取两个数据集— ***df1*** ( *表示课程费用*)和 ***df2*** ( *表示课程折扣*)。
import pandas as pd
df1 = pd.read_excel("Dummy_course_data.xlsx",
sheet_name="Fees")
df2 = pd.read_excel("Dummy_course_data.xlsx",
sheet_name="Discounts")

示例数据集|按作者分类的图像
这些是简单的 7 x 3 数据集,包含所有虚拟数据。
让我们使用 pandas 来探索结合这两个数据集的最佳方式。
# `merge`
`pandas.merge()`以数据库方式合并两个数据集,即数据帧在公共列或索引上连接🚦
如果数据集与列上的列组合,DataFrame 索引将被忽略。
要使用`***merge()***`,你至少需要提供下面两个参数。
1. Left DataFrame
2. Right DataFrame
例如,组合上述两个数据集,而不提及任何其他内容,例如,我们希望在哪些列上组合这两个数据集。
pd.merge(df1,df2)

熊猫`merge` 默认设置|作者图片
`pd.merge()`自动检测两个数据集之间的公共列,并在该列上组合它们。
在这种情况下,`pd.merge()`使用默认设置并返回一个最终数据集,该数据集只包含两个数据集的公共行。因此,这会导致内部连接。
这可能是合并两个数据集的最简单的方法。
然而,`**merge()**`是定义合并行为的最灵活的选项。

熊猫合并选项列表|作者图片
虽然这个列表看起来很令人生畏,但是通过练习,你会掌握合并各种数据集。
目前,需要记住的重要选项是`**how**`,它定义了进行何种合并。此选项的其他可能值有— `‘outer’` 、`‘left’`、`‘right’`。
让我告诉你这是如何工作的。♻️
> **how = 'outer'**
pd.merge(df1, df2, how='outer')

`pandas merge`外部连接|作者图片
这个`‘outer’`连接类似于 SQL 中的连接。它从两个数据集中返回匹配的行和不匹配的行。
一些单元格用`**NaN**`填充,因为这些列在两个数据集中没有匹配的记录。例如,对于课程*德语*、*信息技术*、*市场营销*,在 *df1* 中没有*费用 _ 美元*值。因此,在合并后, *Fee_USD* 列将被这些课程的`NaN`填充。
由于第二个数据集 df2 与 df1 在列 Course 和 Country 上有 3 行不同,合并后的最终输出包含 10 行。即 *df1* 的 *7 行*加上 *df2 的 *3 行*。*
✔️:在数据科学工作面试中经常会有这样的问题— *将数据集与外部连接组合后,输出中总共有多少行。上面提到的这一点可能是这个问题的最佳答案。*
> **怎么= '左'**
pd.merge(df1, df2, how='left')

熊猫`merge` 左加入|图片作者
根据定义,left join 返回左侧数据帧中的所有行,并且只返回右侧数据帧中的匹配行。
这里发生了完全相同的情况,对于在 *Discount_USD* 列中没有任何值的行,`NaN`被替换。
现在让我们看看使用`right`连接的完全相反的结果。
> **how = '右'**
pd.merge(df1, df2, how='right')

熊猫`merge` 右加入|作者图片
右连接返回右数据帧即 *df2* 的所有行,并且只返回左数据帧即 *df1* 的匹配行
使用`how = ‘left’`也可以得到相同的结果。你需要做的只是将`pd.merge()`中提到的数据帧的顺序从`df1, df2`改为`df2, df1`。
最终输出中各列的✔️ ️The 顺序将根据您在`pd.merge()`中提到数据帧的顺序而改变
> **左 _ 开**和**右 _ 开**
如果您想要合并两个具有不同列名的数据集,即列本身具有相似的值,但两个数据集中的列名不同,则必须使用此选项。
您可以在`left_on`中提及左侧数据集的列名,在`right_on`中提及右侧数据集的列名。
你可以在这里 进一步探索熊猫`merge()` [***下的所有选项。🌍***](https://pandas.pydata.org/docs/reference/api/pandas.merge.html)
在他的文章 [**中,Admond Lee**](/why-and-how-to-use-merge-with-pandas-in-python-548600f7e738) 已经很好地解释了所有的熊猫`merge()`用例为什么以及如何在 Python 中使用与熊猫的合并 **。**
还有更简单的熊猫`merge()`的实现,你可以在下面看到。
# `Join`
与 pandas 模块中的函数`merge()`不同,`join()`是一个在数据帧上操作的实例方法。这使得我们可以灵活地只提及一个数据帧来与当前数据帧组合。
事实上,`pandas.DataFrame.join()`和`pandas.DataFrame.merge()`被认为是访问`pd.merge()`功能的便捷方式。因此它没有`merge()`本身灵活,提供的选项也很少。

作者图片
在 join 中,只有`‘other’`是必需的参数,它可以取单个或多个数据帧的名称。因此,您可以灵活地在单个语句中组合多个数据集。💪🏻
比如,我们用`**join()**`把 *df1* 和 *df2* 组合起来。由于这两个数据集具有相同的列名课程和国家,我们也应该使用`**lsuffix**`和`**rsuffix**`选项。
df3 = df1.join(df2,
lsuffix = '_df1',
rsuffix = '_df2')
df3

使用 pandas DataFrame `join` |作者图片合并索引上的两个数据集
根据定义,`*join()*` 在索引上组合两个数据帧(默认为*)这就是为什么输出包含来自两个数据帧的所有行&列。*
✔️如果您想使用公共列— *Country* 连接两个数据框架,您需要将 *Country* 设置为 *df1* 和 *df2* 中的索引。可以像下面这样做。
> 为了简单起见,我将`df1`和`df2`分别复制到`df11`和`df22`中。
df11 = df1.copy()
df11.set_index('Course', inplace=True)df22 = df2.copy()
df22.set_index('Course', inplace=True)
上面的代码块将使列 *Course* 成为两个数据集中的索引。
df4 = df11.join(df22,
lsuffix = '_df1',
rsuffix = '_df2',
on='Course')
df4

使用 pandas `DataFrame join()` |作者图片在公共列上组合数据框架
如上所示,生成的数据帧将把*国家*作为其索引。
> *🚩*注意:`pandas.DataFrame.join()`默认返回“左”连接,而`pandas.DataFrame.merge()`和`pandas.merge()`默认返回“内”连接。
> 在合并和加入❓❓之间感到困惑
看看 [**熊猫加入 vs .合并**](/pandas-join-vs-merge-c365fd4fbf49#:~:text=We%20can%20use%20join%20and,join%20on%20for%20both%20dataframes.) 作者[饶彤彤](https://medium.com/u/840a3210fbe7?source=post_page-----4a909843f5bc--------------------------------)他非常好地描述了这些工具之间的区别,并解释了何时使用什么。
转到合并数据集的最后一种方法..
# 串联
Concat 函数沿行或列连接数据集。因此,当在索引上对齐时,它只是将多个数据帧堆叠在一起。📚
它还提供了一系列选项来提供更大的灵活性。

[pandas.pydata.org 提供的信息](https://pandas.pydata.org/docs/reference/api/pandas.concat.html)
只有`‘objs’`是必需的参数,您可以传递要组合的数据帧列表,作为`axis = 0`,数据帧将沿行组合,即它们将一个叠一个,如下所示。
pd.concat([df1, df2])

使用作者提供的【T2 |图片】合并两个数据集
与基于公共列中的值组合数据帧的`pandas.merge()`不同,`pandas.concat()`只是将它们垂直堆叠。数据帧中不存在的列用`NaN`填充。
两个数据集也可以通过制作`axis = 1`并排堆叠,如下所示。
pd.concat([df1,df2], axis=1)

使用 concat | Image by Author 并排连接两个数据集
简单吧??但是等等,
> 我怎么知道哪个数据来自哪个数据框架❓
这时,分层索引开始出现,`pandas.concat()`通过选项`keys`为它提供了最佳解决方案。你可以如下使用它,
pd.concat([df1,df2], axis=1,
keys = ["df1_data","df2_data"])

使用 concat | Image by Author 的分层索引
这种数据标记实际上使得提取对应于特定数据帧的数据变得容易。✔️
> *🚩*注意:键中标签的顺序必须与`pandas.concat()`第一个参数中数据帧的写入顺序相匹配
仅此而已!!😎
我希望你喝着咖啡读完这篇文章,发现它超级有用,令人耳目一新。我使用 Python 已经有 4 年了,我发现这些组合数据集的技巧非常省时,而且在一段时间内非常有效
您可以通过 [**成为中级会员**](https://medium.com/@17.rsuraj/membership) 来探索中级物品。相信我,你可以**在 Medium 和每日有趣的 Medium digest 上获得无限的**故事。当然,你的费用的一小部分会作为支持给我。🙂
别忘了在我的电子邮件列表 中注册 [**来接收我文章的第一份拷贝。**](https://medium.com/subscribe/@17.rsuraj)
**感谢您的阅读!**
# python pip reqs——如何像正常人一样创建 requirements.txt 文件
> 原文:<https://towardsdatascience.com/python-pipreqs-how-to-create-requirements-txt-file-like-a-sane-person-c82da5d5cd13>
## **想在 requirements.txt 中只包含您使用的库吗?试试 pipreqs,这是一个用于创建更精简的需求文件的 Python 模块。**

文章缩略图(图片由作者提供)
每个 Python 项目都应该有一个`requirements.txt`文件。它存储项目运行所需的所有库的信息,并且在部署 Python 项目时是必不可少的。传统上,这是通过`pip freeze`命令完成的,该命令输出安装在虚拟环境中的所有库。
但是如果您只想要项目中使用的那些呢?这就是 pipreqs 发挥作用的地方。它做的和`pip freeze`一样,但是更好。
想象一下,您创建了一个新的虚拟环境,并安装了一系列依赖项。在项目期间,您决定不使用一些库,但是您忘记了从环境中删除它们。用`pip freeze`生成的`requirements.txt`文件会包含使用过的和未使用过的库,这只是浪费资源。
有一个更好的方法,今天你会了解到这一切。
不想看书?好吧,你不必:
# 如何使用 Python Pipreqs 创建 requirements.txt 文件
我们开始吧。基于 Python 3.10,我用 Anaconda 创建了一个名为`pipreqs_test`的新虚拟环境。您可以自由使用 Anaconda 或任何其他环境管理器:
conda create --name pipreqs_test python=3.10 -y
conda activate pipreqs_test
从这里开始,让我们用 pip 安装一些 Python 库:
pip install numpy pandas matplotlib seaborn statsmodels plotly scikit-learn
下面是 shell 输出:

图 1 —使用 pip 安装 Python 库(图片由作者提供)
现在,创建一个只使用 Numpy 和 Pandas 的 Python 脚本:
import numpy as np
import pandas as pd
arr = np.array([1, 2, 3])
df = pd.DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]})
我已经把我的命名为`script.py`:

图片 2-Python 文件的内容(图片由作者提供)
让我们首先来看看发出一个`pip freeze`命令会将什么保存到需求文件中:
pip freeze > requirements.txt
事实证明,大量的库——包括使用过的和未使用过的,以及它们的依赖关系:

图 3——使用 pip 冻结生成的需求文件(图片由作者提供)
如果您在一台新机器上运行这个脚本,并从一个`requirements.txt`文件安装 Python 依赖项,将会安装许多未使用的库。如果您能设法只安装项目中使用的库,那就更好了。
这就是 pipreqs 的闪光点。但是首先,我们必须安装它:
pip install pipreqs
Pipreqs 的工作方式是扫描给定目录中的所有`.py`文件,并在 Python 文件中寻找导入。这样,它应该只将您实际使用的库写到`requirements.txt`。
下面是保存需求的一般命令:
pipreqs
如果您在 Python 项目文件夹中,只需运行以下命令:
pipreqs .
您将看到以下输出:

图 4——使用 pipreqs 创建 requirements.txt 文件(图片由作者提供)
依赖项现在被保存到`requirements.txt`中,所以让我们看看里面有什么:

图 5——使用 pipreqs 生成的需求文件(图片由作者提供)
令人惊讶的是——只有熊猫和熊猫被包括在内!这些都是在新机器或新环境中运行项目所需的依赖项。
但你对 Pipreqs 能做的就这些吗?接下来让我们来看几个“高级”用例。
# Pipreqs 还能做什么?
您可以通过将它们打印到控制台来检查项目中使用了哪些库:
pipreqs
结果如下:

图 6 —打印依赖关系(作者提供的图片)
这不会将依赖关系写到文件中,所以请记住这一点。
您也可以强制覆盖`requirements.txt`文件。如果你已经有了需求文件,这个命令是需要的,因为单独运行`pipreqs .`不会覆盖它:
pipreqs
更新后的`requirements.txt`文件现已保存:

图 7 —强制覆盖 requirements.txt 文件(图片由作者提供)
您也可以将`requirements.txt`文件保存到不同的目的地。请确保包含完整的文件路径,包括文件名:
pipreqs
我是这样把文件保存到桌面上的:

图 8 —将 requirements.txt 保存到不同的位置(图片由作者提供)
这几乎就是你对 Pipreqs 所能做的一切。还有一些额外的选项和参数,但这些是您在 99%的时间里都会用到的。
接下来让我们做一个简短的回顾。
# 总结 Python Pipreqs
通过`pip freeze`用老方法生成需求文件没有错。它只是包括一堆不必要的库和它们的依赖项,因为安装在一个环境中的所有东西(或者上帝禁止的,全球范围内)都会被选中。
Pipreqs 通过扫描给定项目文件夹中的 Python 文件并寻找实际导入的库来解决这一不便。这不是什么开创性的东西,但它会让你的项目更整洁,这总是受欢迎的。
你用什么来跟踪项目的依赖关系?你认为 Pipreqs 是更好的选择吗?请在下面的评论区告诉我。
*喜欢这篇文章吗?成为* [*中等会员*](/@radecicdario/membership) *继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。*
<https://medium.com/@radecicdario/membership>
## 保持联系
* 雇用我作为一名技术作家
* 订阅 [YouTube](https://www.youtube.com/c/BetterDataScience)
* 在 [LinkedIn](https://www.linkedin.com/in/darioradecic/) 上连接
*原载于 2022 年 12 月 5 日 https://betterdatascience.com**[*。*](https://betterdatascience.com/python-pipreqs/)*
# 用诗歌管理 Python 依赖关系
> 原文:<https://towardsdatascience.com/python-poetry-83f184ac9ed1>
## 依赖性管理和用诗歌打包

[张彦宏](https://unsplash.com/@danielkcheung?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)在 [Unsplash](https://unsplash.com/s/photos/lego?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上的照片
依赖性管理是 Python 项目最基本的方面之一。这很快就会变成一场噩梦,尤其是当一个包由许多开发者维护的时候。因此,使用正确的工具非常重要,这些工具最终可以帮助项目的维护者以正确的方式处理依赖关系。
依赖性管理的概念也涉及到包升级——仅仅因为您指定了一个 pin 并不意味着您将忽略更新。
在今天的文章中,我们将讨论诗歌——一种可以帮助你处理依赖性的依赖性管理工具。此外,我们还将讨论诸如*dependent bot*之类的工具,这些工具用于自动化依赖性更新。
## 为什么固定依赖关系很重要
首先,让我强调一个事实,即**依赖项必须被钉住**。一直都是。是的,甚至致力于概念验证。是的,即使只有你一个人在做一个项目。
钉住的依赖项将允许维护者(或者甚至其他可能使用你的包作为依赖项的包)复制一个环境,该环境具有应该被安装的包依赖项的精确版本,以使你的包完全起作用。
有了固定的依赖项,并不意味着您不应该关心 PyPI 上发布的更新版本。确保固定的软件包是最新的非常重要,因为更新的版本通常会修复错误并减少安全漏洞。
这敲响了良好和可靠的单元测试的警钟。如果您未能创建一个可靠的测试套件,最终覆盖您源代码的大部分方面,那么您将无法判断依赖升级是否会破坏您的代码。
## 什么是诗歌
诗歌,是 Python 项目的依赖管理和打包工具。换句话说,诗歌将处理您在`pyproject.toml`文件中定义的依赖关系。
如果您的机器上尚未安装诗歌,您可以按照官方文档[中提供的指南进行安装。](https://python-poetry.org/docs/#installation)
现在让我们假设你即将开始一个全新的项目。在这种情况下,您可以通过运行`poetry new`命令来创建一个:
$ poetry new myproject
该命令将在名为`myproject`的目录下创建一个新项目,包含以下结构:
poetry-demo
├── pyproject.toml
├── README.md
├── myproject
│ └── init.py
└── tests
└── init.py
## pyproject.toml 文件
很长一段时间以来,`setuptools`是用于管理 Python 项目中依赖关系的“事实上的”工具。然而,自从 [PEP-518](https://peps.python.org/pep-0518/) 提出后,这种情况发生了变化。这个 Python 增强提案引入了一个名为`pyproject.toml`的 TOML 文件,它基本上包含了每个项目的所有构建系统依赖项。
现在让我们快速浏览一下默认的由诗歌创造的`pyproject.toml`:
[tool.poetry]
name = "myproject"
version = "0.1.0"
description = ""
readme = "README.md"
packages = [{include = "myproject"}]
[tool.poetry.dependencies]
python = "^3.7"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
第一部分(`[tool.poetry]`)包含一些关于你的项目的一般信息(如果`poetry`被用来在 PyPI 上发布你的包,这些细节也会被用到。
第二部分`[tool.poetry.dependencies]`用于指定 Python 版本以及包的依赖关系,无论是强制的还是可选的依赖关系。举个例子,
[tool.poetry.dependencies]
python = "^3.7"
pandas = "1.1.1"# Optional dependencies
dbt-core = { version = "1.1.1", optional = true }[tool.poetry.extras] dbt_core = ["dbt-core"]
最后,`poetry`生成的`pyproject.toml`文件的最后一段叫做`[build-system]`。PEP 与 PEP-517 兼容,PEP-517 引入了一种新的标准方法,用于在维护项目时指定构建系统。因此,本节用于指定具体的构建系统(例如,这可能是`setuptools`或`poetry`):
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
有关部分和有效字段的完整列表,请随意参考官方[文档](https://python-poetry.org/docs/pyproject/)。在下面的一节中,我们还将讨论一些常用于操作`pyproject.toml`文件的诗歌命令。
## 用诗歌安装依赖项
现在我们已经在`pyproject.toml`中定义了依赖项,我们可以继续安装它们了。为此,只需运行`install`命令:
$ poetry install
该命令将读取 TOML 文件,解析所有的依赖项,并最终将它们安装在一个虚拟环境中,默认情况下,poems 将在`{cache-dir}/virtualenvs/`下创建这个虚拟环境。注意`cache-dir`目录可以作为[诗歌配置](https://python-poetry.org/docs/configuration/#cache-dir)的一部分进行修改。
如果出于某种原因想要激活环境,只需运行以下命令创建一个新的 shell
$ poetry shell
现在,如果您想停用虚拟环境,同时退出 shell,运行`exit`。或者,要停用虚拟环境但保持 shell 活动,运行`deactivate`。
## 锁定文件
一旦你运行`poetry install`命令,两件事之一就会发生。如果这是你第一次运行`poetry install`,那么一个名为`poetry.lock`的文件将不会出现。因此,诗歌将读取依赖项,并下载您的`pyproject.toml`文件中指定的所有最新版本。一旦安装了这些依赖项,这些包及其特定版本将被写入一个名为`poetry.lock`的文件中。
现在,如果`poetry.lock`文件已经存在,那么这是因为你之前已经运行过`poetry install`,或者`poetry.lock`已经被维护代码库的其他人推送了。不管是哪种情况,只要存在一个`poetry.lock`文件并且执行了`poetry install`,那么依赖项将直接从锁文件中安装。
每当您手动将依赖项添加到`pyproject.toml`文件中时,您可能还希望`poetry.lock`文件反映这些更改。为此,你必须跑步
$ poetry lock --no-update
即使您的锁文件由于任何原因已经过期,也可以使用相同的命令。
请注意,您应该始终对`poetry.lock`文件进行版本控制,因为确保所有贡献者使用相同版本的依赖项非常重要。每当用户添加或删除一个依赖项时,他们必须确保他们也更新了`poetry.lock`并将其包含在提交中。
## 为开发指定和安装依赖项
现在,除了项目运行所需的标准依赖项之外,您可能还需要定义额外的依赖项,作为测试的一部分。此类依赖关系可在`[tool.poetry.dev-dependencies]`一节中定义:
[tool.poetry.dev-dependencies] pytest = "==3.4"
现在每次运行`poetry install`时,开发依赖项也会被安装。如果不希望在环境中安装`dev-dependencies`部分列出的包,那么您可以提供`--no-dev`标志:
$ poetry install --no-dev
请注意,在较新的版本中,上述符号已被否决。您可以改为使用`--without`选项:
$ poetry install --without dev
## 有用的诗歌命令
您甚至可以使用`poetry add`命令在`pyproject.toml`中插入额外的依赖项,而不是在`pyproject.toml`文件中指定依赖项:
$ poetry add pytest
上面的命令将执行两个动作——它将把`pytest`包添加到`pyproject.toml`的 dependencies 部分,并安装它(及其子依赖项)。
同样,您甚至可以从依赖关系中删除一个包:
$ poetry remove pytest
注意,您甚至可以通过传递`--dev|-D`标志来添加或删除开发依赖关系:
$ poetry add pytest -D
现在让我们假设您想要使用`pytest`来运行项目的单元测试。因为您的测试套件需要已经安装在诗歌环境中的依赖项,所以您可以使用`poetry run`命令在该环境中运行`pytest`:
$ poetry run pytest
事实上,您可以运行任何您喜欢的 Python 脚本
$ poetry run python my_script.py
诗歌还支持`extras`,它本质上对应于我们之前讨论过的可选依赖项。
[tool.poetry.extras]
dbt_core = ["dbt-core"]
pandas = ["pandas"]
如果你想安装`extras`,你只需要使用`-E|--extras`选项:
poetry install --extras "dbt_core pandas"
poetry install -E dbt_core -E pandas
## 保持依赖关系最新
既然我们已经学习了诗歌,以及如何利用诗歌来有效地管理包的依赖关系,那么强调你应该确保这些依赖关系不时地被更新是很重要的。
这将确保您的源代码是安全的(因为许多包升级将减少安全漏洞),具有尽可能少的错误,并且它还可以访问包的最新特性。
由于维护这样的过程相当困难——尤其是如果您的 Python 包有大量的依赖项——您可能不得不依赖能够自动更新依赖项的外部工具。
如果您在 GitHub 上托管您的存储库,一个选择是[dependent bot](https://github.com/dependabot),它可以自动搜索依赖项的最新更新并创建 Pull 请求。你所需要做的就是准备好良好的单元测试,以便能够捕获软件包更新可能引入的任何 bug。
## 最后的想法
无论如何,您都必须为依赖项(也称为 pin)指定特定的包版本。鉴于您的 Python 项目将有一组特定的需求,需要在运行它之前安装这些需求,因此使用能够简化依赖关系管理的工具非常重要。
其中一个工具是 poem——您可以使用它来安装、添加和删除依赖项,以及打包您的 Python 项目,以便它可以在 PyPI 上发布和分发,从而可以被更广泛的社区访问。
[**成为会员**](https://gmyrianthous.medium.com/membership) **阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。**
<https://gmyrianthous.medium.com/membership>
**相关文章你可能也喜欢**
</requirements-vs-setuptools-python-ae3ee66e28af> </setuptools-python-571e7d5500f2> </how-to-upload-your-python-package-to-pypi-de1b363a1b3> [## 如何将 Python 包上传到 PyPI
towardsdatascience.com](/how-to-upload-your-python-package-to-pypi-de1b363a1b3)
# Python 项目:如何在图像中隐藏秘密数据
> 原文:<https://towardsdatascience.com/python-project-how-to-hide-secret-data-in-images-2a835ed6b9b1>
## 开发 Python 项目来加密和解密隐藏在图像中的信息

斯蒂芬·斯坦鲍尔在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上拍摄的照片
安全和隐私是现代世界的两大关注点。如果你想把某种秘密信息传递给你的朋友或家人,但又不想让其他人知道,最好的方法是什么?—在本文中,我们将开发一个有趣的 Python 项目,它可以安全地加密图像中的文本信息,以便它可以成功地传输给接收者,接收者可以相应地解码它。
通过加密编码在图像中隐藏信息和秘密数据的技术通常被称为**隐写术**。一旦秘密信息被编码在图像中,只有专家或接收带有秘密密钥的数据的终端用户才能解码(或解密)该秘密信息。下面是来自 [wiki](https://en.wikipedia.org/wiki/Steganography) 的更正式的隐写术定义
> **隐写术**是将一条消息隐藏在另一条消息或物理对象中的做法。在计算/电子环境中,计算机文件、消息、图像或视频隐藏在另一个文件、消息、图像或视频中。
在本文中,我们将致力于构建一个有趣的 Python 项目,从中我们可以了解隐写术的基础知识以及如何在图像中隐藏秘密信息。只要发送方和接收方(目的地)共享公共密钥,您就可以加密您的秘密数据,并与您的朋友一起解密所需的信息。在下一节中,我们将详细了解如何构建这个项目。
# 图像中数据隐藏的应用;
在我们开始隐写术项目之前,让我们看看一些关键的应用和用例,您可能会发现隐藏在图像中的数据很有用。
1. 两个用户之间的机密通信
2. 数据变更保护
3. 您还可以使用图像隐写术在图像中隐藏图像
4. 为了确保机密数据的存储
5. 用于数字内容分发的访问控制系统
现在我们已经对隐写术的一些用例有了一个简单的概念,我们可以进入下一部分,开始开发这个项目。
# 发展隐写术项目:

Anukrati Omar 在 [Unsplash](https://unsplash.com/s/photos/scenary?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上拍摄的照片
在文章的这一部分,我们将学习如何通过隐藏在图像中来加密和解密秘密的文本数据和信息。我们将把这个部分分成几个部分,以便更容易理解成功开发这个项目需要遵循的所有基本步骤。在我们开始之前,请确保将上面的图像下载到您的工作目录中,并在。jpg 格式。现在,事不宜迟,让我们开始隐写术项目。
## 导入基本库:
该项目的第一步是导入完成任务所需的所有基本库。最重要的库模块是 Open-CV (cv2 ),用于计算机视觉应用,如图像操作、读取和写入图像以及其他关键操作。字符串库可用于处理字符串数据和 ASCII 值。OS 库对于管理与操作系统相关的功能,以便在 Windows 或其他类似平台中保存或创建文件非常有用。
Importing the essential libraries
import cv2
import string
import os
如果您不完全熟悉 Open-CV 库,我强烈推荐您通过下面提供的链接查阅我以前的一篇文章,这篇文章涵盖了这个概念的全部基础知识。掌握这个库的知识是掌握 Python 中计算机视觉的最重要的步骤之一。
</opencv-complete-beginners-guide-to-master-the-basics-of-computer-vision-with-code-4a1cd0c687f9>
## 声明和定义所需的变量和参数:
在本文的这一部分,我们将声明计算项目所需的所有变量和参数。首先,我们将声明两个字典数据结构,如下面的代码块所示。第一个字典将 ASCII 对象及其各自的 id 作为键存储,而第二个字典执行类似的任务,反之亦然。你可以打印这两本字典来帮助弄清楚它们是如何工作的。
在同一个代码块中,我们可以继续读取之前存储在工作目录中的图像“test_image.jpg”。分析图像的参数通常是一种健康的做法,比如高度、宽度和通道数。图像的通道数定义了它是灰度图像还是 RGB 图像。下面是执行以下操作的代码块。
Declaring the essential Characters
dict1 = {}
dict2 = {}for i in range(255):
dict1[chr(i)]=i
dict2[i]=chr(i)# print(dict1)
print(dict2)# Reading and analyzing our image
img = cv2.imread("test_image.jpg")height = img.shape[0]
width = img.shape[1]
channels = img.shape[2]print(f"Height: {height}, Width: {width}, Number of channels: {channels}")
## 输出:
Height: 3585, Width: 5378, Number of channels: 3
如果您已经按照教程学习了这么久,那么在运行下面的代码时,您应该会获得下面的输出参数。如果用户不太熟悉数据结构和字典的概念,我建议查看我以前的一篇文章,通过下面的链接更熟悉这个概念。
</mastering-dictionaries-and-sets-in-python-6e30b0e2011f>
## 加密:
现在我们已经定义了所有需要的变量和参数,我们可以继续在我们想要的图像中添加一些加密的文本数据。我们将输入一个共同的密钥,最终用户必须知道这个密钥,这样他们才能读取加密和解密的数据。您可以添加您的关键和各自的文本,你想隐藏在图像中。
这项任务的加密算法非常简单。我们利用实际输入文本和之前声明的字典值提供的键之间的按位操作。我们定义的 x、y 和 z 值用于确定文本在图像中是垂直、水平还是对角隐藏。在每次模运算迭代之后,密钥也被修改。
Encryption
key = input("Enter Your Secret Key : ")
text = input("Enter text to hide In the Image : ")kl=0
tln=len(text)
x = 0 # No of rows
y = 0 # no of columns
z = 0 # plane selectionl=len(text)for i in range(l):
img[x, y, z] = dict1[text[i]] ^ dict1[key[kl]]
y = y+1
x = x+1
x = (x+1)%3
kl = (kl+1)%len(key)
cv2.imwrite("encrypted_img.jpg", img)
os.startfile("encrypted_img.jpg")
print("Data Hiding in Image completed successfully.")
最后,一旦我们执行了加密算法,我们就可以将加密的图像保存在工作目录中。但是,一旦图像打开,您会发现几乎不可能注意到原始图像和加密图像之间的任何差异。文本隐藏得很好,图像中的秘密信息可以与解密过程所需的目的地共享。
## 解密:

作者加密的图像
一旦信息在相应的图像中被加密,我们就可以继续执行解密。请注意,只有当发送方和接收方具有相同的输入密钥时,才能成功解密。如果目的地的接收者输入了正确的密钥,则执行下面代码块中的解密算法。此任务的方法类似于加密代码,因为我们用第二个和第一个字典的按位操作来反转前面的操作。
Decryption
kl=0
tln=len(text)
x = 0 # No of rows
y = 0 # no of columns
z = 0 # plane selectionch = int(input("\nEnter 1 to extract data from Image : "))if ch == 1:
key1=input("\n\nRe-enter secret key to extract text : ")
decrypt=""if key == key1 :
for i in range(l):
decrypt+=dict2[img[x, y,z] ^ dict1[key[kl]]]
y = y+1
x = x+1
x = (x+1)%3
kl = (kl+1)%len(key)print("Encrypted text was : ", decrypt)
else:
print("Enter Key doesn't match the original records.")
else:
print("Exiting the code...")
如果代码(密钥)输入正确,图像中的文本信息将被相应地解密和解码。您将收到最初在相应映像中加密的数据。但是,如果您没有正确的密钥,程序将终止,并让您知道输入了错误的密钥。
## 最终结果、改进和未来工作:

作者图片
上面的截图显示了下面的隐写术项目的工作方法。上图显示了加密和解密过程,以及相应的密钥和文本数据。请随意尝试各种变化,测试他们的工作程序。我强烈推荐查看下面的 GitHub [链接](https://github.com/pypower-codes/Steganography/blob/master/stagenography_1.py),这段代码的大部分都是从这里开始的。
为了进一步改进,将这种想法扩展到图像之外会很棒,比如 pdf 文件、文本文件、gif 等等。通过使用 GUI 界面,您还可以添加几个额外的项目来使这个项目更具展示性。最后,您可以扩展代码,使加密和解密过程变得更加复杂。
密码学是一个庞大的课题,还有很多安全措施对于用户在网上或现实生活中维护他们的隐私和安全是必不可少的。虽然这样的项目是一个很好的起点,但是检查其他类似的项目来增加您的安全措施也是一个很好的主意。我建议通过下面提供的链接查看我的另一个关于使用 Python 生成高度安全的密码的项目。
</highly-secure-password-generation-with-python-852da86565b9>
# 结论:

摄影:[飞:D](https://unsplash.com/@flyd2069?utm_source=medium&utm_medium=referral) 上 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)
> “我已经变得喜欢保密。对我们来说,这似乎是能使现代生活变得神秘或不可思议的一件事。最普通的东西是令人愉快的,如果只有一个隐藏它。”
> *——****王尔德***
在现代社会,安全和隐私至关重要。为了确保每个人都能够安全可靠地使用众多日常生活设施,例如互联网、信用卡或借记卡、电话和其他类似设备,人们正在研究大量的发展来保持这种安全性。使用 Python 编码的隐写术是众多这类有趣项目中的一个,你可以在图像中隐藏一些秘密信息并共享数据。
在本文中,我们了解了图像数据隐藏和隐写术的一些基本概念。我们还获得了执行这样一个项目所需的计算机视觉库的知识。我建议尝试这个项目的许多变化,通过有趣的实验和探索来测试各种可能性。
如果你想在我的文章发表后第一时间得到通知,请点击下面的[链接](https://bharath-k1297.medium.com/subscribe)订阅邮件推荐。如果你希望支持其他作者和我,请订阅下面的链接。
<https://bharath-k1297.medium.com/membership>
如果你对这篇文章中提到的各点有任何疑问,请在下面的评论中告诉我。我会尽快给你回复。
看看我的一些与本文主题相关的文章,你可能也会喜欢阅读!
</7-python-programming-tips-to-improve-your-productivity-a57802f225b6> </develop-your-own-calendar-to-track-important-dates-with-python-c1af9e98ffc3> </develop-your-weather-application-with-python-in-less-than-10-lines-6d092c6dcbc9>
谢谢你们坚持到最后。我希望你们都喜欢这篇文章。祝大家有美好的一天!
# 用于快速设置的 Python 项目模板
> 原文:<https://towardsdatascience.com/python-project-template-for-a-quick-setup-d3ba1821e853>
## 分享一个 python 模板,它帮助我加速了使用 FastAPI 和 pydantic 的开发设置
在过去的几个月里,我发现自己需要为面试任务建立一个快速的 python 环境。在工作中,我通常有可以从其他项目中导入的模板,在那里我删除了不必要的逻辑并开始工作。我发现从头开始设置环境比我预期的要花更长的时间,而寻找不是针对初学者或小项目的文档甚至要花更长的时间。
我创建了一个模板并上传到 GitHub,你可以在这里得到它[。](https://github.com/MirYeh/PythonTemplate)

创建于 [ExcaliDraw](http://excalidraw.com)
# TL;速度三角形定位法(dead reckoning)
* 建立一个虚拟环境。
* 定义 API。
* 使用 FastAPI 公开它。
# 虚拟环境
设置虚拟环境对于保持项目依赖关系与其他项目分离非常重要。这样,我们可以创建一个稳定、可靠且易于在团队成员之间复制的项目环境。
为了设置虚拟环境,我将带你去 [Daniela Brailovsky](https://medium.com/u/faecd39bdbb4?source=post_page-----d3ba1821e853--------------------------------) 的[关于 Python 中的虚拟环境的博客文章](https://medium.com/@dbrailo/virtual-environments-in-python-2773ac8dd9ae)。

Daniela Brailovsky 用 Python 写的关于 venvs 的博客——3 分钟阅读
# 模板概述
对于这个例子,我创建了一个名为`PythonTemplate`的项目,它包含几个文件,比如入口点`main.py`文件和需求文件。需要注意两个主要目录— `template`包含代码(除了入口点之外的所有内容)和`tests`包含测试代码的代码:

建议的项目结构
为了简单起见,我创建了一个接收用户的 HTTP POST 请求并输出问候的项目。
输入—包含用户名的 JSON 结构:
curl -X 'POST'
'http://127.0.0.1:8000/users/'
-H 'accept: application/json'
-H 'Content-Type: application/json'
-d '{
"name": "Miri"
}'
输出—问候:
"Hey Miri!"
正如您在 FastAPI UI 中看到的:

预期的输入和输出
现在您已经看到了它应该是什么样子,让我们来看一下模板!我们将从业务逻辑开始。
# Python 项目模板
## 商业逻辑
* 在`template.models.user`下创建一个用户模型类。因为我使用的是 FastAPI,所以我将使用`[pydantic.BaseModel](https://pydantic-docs.helpmanual.io/usage/models/)`来表示这个类:
* 在`template.logic.user_service`下创建一个服务类:
现在我们有了一些逻辑,让我们看看如何使用 FastAPI 来公开这个逻辑。
## 暴露逻辑
在`template.ctrls.users`目录下创建一个控制器模块。
在这个模块中,我们调用我们的逻辑,并使用 [APIRouter](https://fastapi.tiangolo.com/tutorial/bigger-applications/) 通过 FastAPI 的路由公开它:
如您所见,公开控制器逻辑需要 3 行代码——导入`fastapi.APIRouter`,用路径前缀创建“用户”`APIRouter`,并用路由器和 HTTP 方法注释您想要公开的函数!

控制器逻辑&通过 FastAPI 公开
这是我们创建的`template`目录结构:

模板目录结构
现在让我们转移到`main.py`入口点。
## 服务器端入口点
在 main 中,我们创建我们的 FastAPI 应用程序,添加我们的`users`路由,并使用`[uvicorn](https://www.uvicorn.org/#:~:text=from%20your%20application...-,example.py,-%3A)`运行它:
通过[本地服务器](http://127.0.0.1:8000/docs)试用您的应用程序!

实际应用
就这样——你收到了来自你的应用程序的问候!
使用模板时不要忘记添加测试!
本文中的 gif 是使用 [Windows 屏幕捕捉功能](https://www.tomshardware.com/how-to/screen-record-in-windows)、 [ezgif](https://ezgif.com/video-to-gif) 裁剪视频和 [xconvert](https://www.xconvert.com/convert-video-to-gif) 将视频转换为 gif 创建的。
特别感谢[埃拉·谢尔](https://medium.com/u/3f4ff048c2b7?source=post_page-----d3ba1821e853--------------------------------)、[丽娅·科恩](https://medium.com/u/c9af24f665e5?source=post_page-----d3ba1821e853--------------------------------)和[娜奥米·克里格](https://medium.com/u/ce7969d594d?source=post_page-----d3ba1821e853--------------------------------),他们帮助改进了这篇文章!
# Python 集合差异——初学者完全指南
> 原文:<https://towardsdatascience.com/python-set-difference-a-complete-beginner-guide-3ce54ed0bf07>
## 什么是 Python 集合差?Python 中的 Difference 方法是做什么的?所有这些以及更多的问题都在这本综合指南中得到了解答。

由[阿纳斯·阿尔尚蒂](https://unsplash.com/@anasalshanti?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)在 [Unsplash](https://unsplash.com/s/photos/code?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上拍摄
在上周的文章的[中,你已经深入了解了 Python set `union()`是如何工作的。本周我们将探索另一个 set 函数,那就是 set `difference()`。使用 Python 集合差异,您可以轻松找到两个或多个集合之间的差异。简单地说,这意味着只返回第一个集合特有的不同值。](https://betterdatascience.com/python-set-union/)
在这篇文章中你会得到更深入的理解,所以继续读下去。
或者干脆不要读——我已经用视频的形式讲述了这个话题:
# Python 集合差异-基础知识
那么,*Python 集合区别是什么?这就是我们在这一部分要回答的问题。通过可视化示例,您将对定义、语法和返回值有一个完整的理解。*
## 定义和用法
Set difference 函数返回第一个集合中没有在第二个集合中找到的元素。您可以找到多组之间的差异,同样的逻辑也适用。为了简单起见,我们将在下面的例子中使用两个。
看看下面两组— **A** 和 **B** :

图 1 —两套编程语言(图片由作者提供)
计算这些集合之间的差异意味着我们将得到一个只有一个元素的新集合——PHP。*为什么?*因为它是集合 **A** 中唯一没有在集合 **B** 中找到的元素:

图 2 —设定行动差异(作者提供的图片)
类似地, **B — A** 将导致*红宝石*,因为该元素特定于 set **B** 。Python 集合的差异通常用维恩图来表示。它看起来是这样的:

图 3 —将差异设置为文氏图(图片由作者提供)
元素 *Python* 和 *JavaScript* (JS)是两个集合共有的。在计算集合差异时,我们只关心第一个集合中的唯一元素——这就是为什么在新集合中只返回 PHP。
*差方法在 Python 中是做什么的,如何在 Python 中求集合的差?让我们复习一下语法来回答这个问题。*
## 句法
Difference between two sets
set1.difference(set2)
Difference between multiple sets
set1.difference(set2, set3, ...)
其中:
* `set1` -从中寻找差异的 iterable。
* `set2`、`set3` -其他集合用于从`set1`中“取消”元素
## 返回值
difference 函数返回一个新集合,它是第一个集合与作为参数传递的所有其他集合之间的差,但仅当集合或可迭代对象被传递给该函数时。
如果没有参数被传入`difference()`函数,则返回集合的一个副本。
# Python 集合差分函数示例
我们将声明两个集合,正如*图 1* 所示:
* `A`:包含 *Python* 、 *JavaScript* 和 *PHP*
* `B`:包含 *Python* 、 *JavaScript* 和 *Ruby*
正如你所看到的,前两种语言在两个集合中都存在。将差值计算为 **A — B** 应该会返回一个只有 *PHP* 的新集合。同样, **B — A** 返回一个只有*红宝石*的新集合:
A = {'Python', 'JavaScript', 'PHP'}
B = {'JavaScript', 'Python', 'Ruby'}
print(f"A - B = {A.difference(B)}")
print(f"B - A = {B.difference(A)}")
输出:
A - B = {'PHP'}
B - A = {'Ruby'}
如果没有为 difference 函数指定任何参数,则返回集合的副本:
print(f"A - B = {A.difference()}")
输出:
A - B = {'JavaScript', 'PHP', 'Python'}
您可以通过打印内存地址来验证它是否被复制:
A = {'Python', 'JavaScript', 'PHP'}
A_copy = A.difference()
print(hex(id(A)))
print(hex(id(A_copy)))
输出:
0x1107d3f20
0x1107d3d60
你不会看到相同的值,这不是重点。重要的是它们是不同的,表明集合被复制到不同的内存地址。
现在让我们探索一种更短的方法来获得集合差——通过使用减号运算符。
# Python 使用—运算符设置差异
不用每次都调用`difference()`函数。您可以使用减号(`-`)运算符来代替:
A = {'Python', 'JavaScript', 'PHP'}
B = {'JavaScript', 'Python', 'Ruby'}
print(f"A - B = {A - B}")
输出:
A - B = {'PHP'}
其他一切都保持不变。请记住,两个操作数都必须是 set 类型。
# Python 集合差异常见错误
当您第一次开始使用集合时,很可能会遇到错误。这些是常见的,但通常很容易调试。
## attribute error:“list”对象没有属性“difference”
这是最常见的错误类型,当您试图对错误的数据类型调用 set `difference()`函数时会出现这种错误。只有器械包可以使用此功能。
下面是一个示例—如果使用列表,则会引发一个异常:
A = ['Python', 'JavaScript', 'PHP']
B = ['JavaScript', 'Python', 'Ruby']
print(f"A - B = {A.difference(B)}")
输出:

图片 4-无属性错误(图片由作者提供)
确保两者都是 set 类型,你就可以开始了。
## TypeError:不支持的操作数类型-:“set”和“list”
当您试图对无效数据类型使用速记符号(减号)时,会出现此错误。两者都必须设置,负号才能工作。这里有一个例子:
A = {'Python', 'JavaScript', 'PHP'}
B = ['JavaScript', 'Python', 'Ruby']
print(f"A - B = {A - B}")
输出:

图 5 —不支持的操作数类型错误(作者图片)
如你所见, **A** 是一个集合, **B** 是一个列表,所以减号不起作用。
# Python 集合差异常见问题
我们现在来看几个关于 Python 集合和 Python 集合差异函数的常见问题(FAQ)。
## Python set()是做什么的?
Python 中的`set()`方法用于将任何可迭代的数据类型转换为具有不同元素集的元素。
## 集合可以有重复吗?
集合是重复和顺序被忽略的集合,所以集合不能有重复。
## Python 中的集合差算子可交换吗?
集合差不可交换— **A — B** 与 **B — A** 不同。这里有一个例子:
A = {1, 2, 3}
B = {3, 4, 5}
print(f"A - B = {A.difference(B)}")
print(f"B - A = {B.difference(A)}")
输出:
A - B = {1, 2}
B - A = {4, 5}
# 结论
Python 集合差异非常容易理解。我们经历了直觉和定义,并逐步理解了更高级的用法和你在某个时候必然会看到的典型错误。你必须承认——这比你想象的要容易。
我希望这篇文章能够帮助您更好地理解 Python 集合并集函数。一如既往,如果你有任何问题或意见,请随时在下面的评论区提问。编码快乐!
## 了解更多信息
* [Python 集合并集](https://betterdatascience.com/python-set-union/)
* [一行 Python If-Else 语句—三元运算符讲解](https://betterdatascience.com/python-if-else-one-line/)
* [Python 单引号和双引号——您应该使用哪个,为什么?](https://betterdatascience.com/python-single-vs-double-quotes/)
## 保持联系
* 雇用我作为一名技术作家
* 在 YouTube[上订阅](https://www.youtube.com/c/BetterDataScience)
* 在 [LinkedIn](https://www.linkedin.com/in/darioradecic/) 上连接
*喜欢这篇文章吗?成为* [*中等会员*](https://medium.com/@radecicdario/membership) *继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。*
<https://medium.com/@radecicdario/membership>
*原载于 2022 年 2 月 2 日*[*【https://betterdatascience.com】*](https://betterdatascience.com/python-set-difference/)*。*
# Python 单引号和双引号——应该使用哪个,为什么?
> 原文:<https://towardsdatascience.com/python-single-vs-double-quotes-which-should-you-use-and-why-c02dd011f86c>
## 何时以及为什么应该使用其中一个而不是另一个?解释最佳实践。

莎伦·麦卡琴在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的照片
Python 经常给你留下选项,引号也不例外。Python 单引号和双引号有什么区别?你应该用哪一个?什么时候一个比另一个有优势?什么是三重引号?如果你是初学者,很容易被弄糊涂。
你看,Python 和 Java 有很大不同。在 Java 中,字符串必须使用双引号,字符只允许使用单引号。这不是 Python 的交易。与静态类型语言相比,您拥有更大的灵活性。**但这真的是一件好事吗?**继续阅读寻找答案。
不想看书?请观看我的视频:
# Python 中单引号有什么用?
在 Python 中,最好用单引号将短字符串括起来,比如字符串文字或标识符。但是这里有一个交易——这不是一个要求。你可以用单引号将整段文章括起来。
这里有几个例子:
name = 'Bob'
print(name)
channel = 'Better Data Science'
print(channel)
paragraph = 'Bob likes the content on Better Data Science. Bob is cool. Be like Bob.'
print(paragraph)

图 1-单引号示例(作者图片)
单引号之间的内容没有字符限制,但是您不可避免地会遇到一些问题。第一个是字符串中的引号。
## 字符串中引号的问题是
英语中充满了单引号(撇号)。比如,我可以写*我们是*或者*我们是*,两者代表同一个东西。如果您想在 Python 中使用单引号将字符串括起来,那么选择第二个选项是有问题的:
print('We're going skiing this winter.')

图 2-打印带引号的字符串时出现语法错误(作者图片)
Python 认为字符串在`We`之后结束,因此其后的所有内容都被认为是语法错误。您可以很容易地在代码编辑器中发现这样的错误,因为`We`之后的部分颜色不同。
有三种解决方法:
1. **停止使用缩写** ( *我们是* - > *我们是*)——一点都不实用。
2. **转义字符串**——这是我们接下来要探讨的一种可能性。
3. **使用双引号**——我们将在后面介绍。
## 在 Python 中转义字符串
对字符串进行转义的主要目的是防止某些字符被用作编程语言的一部分。例如,我们不希望撇号被视为引号。
在 Python 中,可以使用反斜杠(`\`)符号来转义字符串字符:
print('We're going skiing this winter.')

图 3 —对 Python 中的字符进行转义(图片由作者提供)
这很酷,但反斜杠通常在字符串中用作文字字符——例如,表示计算机上的路径。让我们看看如果您尝试使用转义符打印路径会发生什么:
print('C:\Users\Bob')

图 4 —使用无效转义字符时出现语法错误(图片由作者提供)
可能不是你想看到的。事实证明,您可以通过两种方式来转义转义字符:
1. **通过使用原始字符串** —在第一个引号前写`r`。
2. **使用双反斜杠**——这基本上会对转义字符进行转义。
以下是两者的使用方法:
print(r'C:\Users\Bob')
print('C:\Users\Bob')

图 5 —在字符串中使用反斜杠的两种方式(作者图片)
这两个都适用于用单引号和双引号括起来的字符串。我们还没有讨论双引号,所以接下来让我们讨论一下。
# Python 中双引号是用来做什么的?
对自然语言消息、字符串插值以及任何你知道字符串中会有单引号的地方使用双引号被认为是最佳实践。
这里有几个例子:
name = 'Bob'
Natural language
print("It is easy to get confused with single and double quotes in Python.")
String interpolation
print(f"{name} said there will be food.")
No need to escape a character
print("We're going skiing this winter.")
Quotation inside a string
print("My favorite quote from Die Hard is 'Welcome to the party, pal'")

图 6-在 Python 中使用双引号(图片由作者提供)
正如你所看到的,我们可以很容易地将引用嵌入到双引号包围的字符串中。此外,没有必要像我们用单引号那样转义字符。
**记住**:在双引号包围的字符串中不能再次使用双引号。这样做将导致与单引号相同的语法错误:
print("You can't use "double quotes" inside this string.")

图 7-在双引号字符串中使用双引号时出现语法错误(图片由作者提供)
为了减轻这种影响,您可以重用上一节中的解决方案,但是也可以用单引号将字符串括起来:
print('You can use "double quotes" like this.')

图 8 —在字符串中使用双引号(作者图片)
现在你知道如何在 Python 中使用单引号和双引号了。接下来,我们将回顾差异并介绍最佳实践。
# Python 中单引号和双引号有什么区别?
这里有一个回答问题*的汇总表,在 Python* 中应该使用单引号还是双引号:

图 Python 中单引号和双引号的区别(图片由作者提供)
简而言之,您可以在所有情况下都使用这两个词,但是双引号更常用于文本和较长的字符串。没有人禁止你在任何地方使用单引号,但是你必须更加小心,因为单引号对特定的字符更加敏感。
有一些来自 Python 创作者的官方推荐,接下来让我们来看一下。
## Python 单引号与双引号 PEP8
根据 [PEP8](https://www.python.org/dev/peps/pep-0008/#string-quotes) :
* PEP 不建议使用单引号还是双引号——选择一个规则并坚持下去。
* 当字符串用单引号括起来时,使用双引号来避免反斜杠。
* 当字符串用双引号括起来时,使用单引号以避免反斜杠。
* 使用三重引号字符串时,请始终在字符串中使用双引号字符。
我们将很快讨论三重引号字符串及其用例。
## 单引号与双引号最佳实践
单引号字符串的最佳实践:
* 确保字符串有点短,或者您正在处理一个字符串文字
* 确保字符串中没有单引号,因为添加转义字符会影响可读性。
双引号字符串的最佳实践:
* 对文本和字符串插值使用双引号。
* 当字符串中有引号时,使用双引号——您可以很容易地用单引号将引号括起来。
# Python 中应该用单引号还是双引号?
最终的答案是——这**取决于**,主要取决于你的风格偏好。Python 对单引号和双引号没有任何区别,所以决定权在你。
您不应该在单个 Python 文件或模块中不断地在单引号和双引号之间切换。挑一个自己比较喜欢的,并保持一致。
# Python 中的三重引号
是的 Python 中甚至还有第三种类型的引用。这些都有自己的优势:
* 您可以在其中使用单引号和双引号。
* 您可以将字符串拆分成多行。
* 在编写文档字符串时,它们被视为最佳实践。
让我们来看几个例子:
print("""Triple quote string example 1""")
print('''Triple quote string example 2''')
print("""Here's a string I'll split into
mulitple
lines.""")
print("""You can use 'single quotes' and "double quotes" inside!""")

图 10—Python 中的三重引号(图片由作者提供)
如你所见,键盘上没有专用的三引号字符,所以我们用三个单引号或三个双引号来写它们。最大的好处是你可以通过按*回车*将字符串拆分成多行,这从根本上提高了可读性。
不过,三重引号的主要用例是函数的文档字符串(docstrings ):
def sum_list(lst: list):
"""Iterates over every list element and sums them.
Keyword arguments:
lst -- the input sequence of numbers.
"""
res = 0
for num in lst:
res += num
return res
您可以自由地使用`'''`和`"""`来包围 docstring,但是[约定](https://www.python.org/dev/peps/pep-0257/)是使用后者。
# 结论
长话短说 Python 中单引号和双引号字符串之间的差别很小。您可以在任何情况下使用其中任何一种——只要确保遵守您的编程约定。在一些用例中,一种类型比另一种类型有优势,但这种情况很少。
如果字符串中有引号,单引号字符串可能会给你带来一些麻烦,所以请记住这一点。双引号字符串是一个更安全的赌注,但不足以选择一个。
*喜欢这篇文章吗?成为* [*中等会员*](https://medium.com/@radecicdario/membership) *继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。*
<https://medium.com/@radecicdario/membership>
# 了解更多信息
* [Python If-Else 语句一行—三元运算符讲解](https://betterdatascience.com/python-if-else-one-line/)
* [Python 结构模式匹配—帮助您入门的 3 大用例](https://betterdatascience.com/python-structural-pattern-matching/)
* Dask 延迟——如何轻松并行化你的 Python 代码
# 保持联系
* 注册我的[简讯](https://mailchi.mp/46a3d2989d9b/bdssubscribe)
* 订阅 [YouTube](https://www.youtube.com/c/BetterDataScience)
* 在 [LinkedIn](https://www.linkedin.com/in/darioradecic/) 上连接
*原载于 2022 年 1 月 17 日 https://betterdatascience.com**[*。*](https://betterdatascience.com/python-single-vs-double-quotes/)*
# Python 平方根:Python 中求平方根的 5 种方法
> 原文:<https://towardsdatascience.com/python-square-roots-5-ways-to-take-square-roots-in-python-445ea9b3fb6f>
## **Python 开平方的方法不止一种。学习 Python 中平方根的 5 种方法+一些额外的高级技巧。**

丹·克里斯蒂安·pădureț在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的照片
在学习了 Python 中求一个数的平方的 [4 种方法之后,现在是时候处理相反的运算了——Python 平方根。本文将教你用 Python 计算平方根的五种不同方法,并以一个关于 Python 列表的立方根和平方根的附加部分结束。](https://betterdatascience.com/python-squaring/)
让我们从介绍主题开始,并解决在 Python 中计算平方根时必须考虑的潜在问题。
# Python 平方根介绍
简单地说,一个数的平方根是一个值,当它与自身相乘时,返回相同的数。这是平方的逆运算。
例如,3 的平方是 9,9 的平方根是 3,因为 3×3 是 9。这是一个有点难以用一句话来解释的概念,但是当你看到它在行动时,你就明白了。
在深入研究 Python 中求平方根的不同方法之前,让我们先来看看哪些数字可以和不可以求平方根。
## 正数的平方根
平方根只适用于正数。现在,忽略负责计算的代码,只关注结果。
以下代码片段打印数字 1 和 25 的平方根:
import math
a = 1
b = 25
Square root of a positive number
a_sqrt = math.sqrt(a)
b_sqrt = math.sqrt(b)
print("Square root of a positive number")
print("--------------------------------------------------")
print(f"Square root of {a} = {a_sqrt}")
print(f"Square root of {b} = {b_sqrt}")
以下是输出结果:

图 1-正数的平方根(图片由作者提供)
因此,1x1 = 1,5x5 = 25,这就是平方根的基本工作原理。但是如果你取零的平方根呢?
## 零的平方根
现在,零既不是质数,也不是合数,所以找不到它的[质因数分解](https://www.cuemath.com/numbers/prime-factorization/)。因此,零的平方根是零:
import math
a = 0
Square root of a zero
a_sqrt = math.sqrt(a)
print("Square root of a zero")
print("--------------------------------------------------")
print(f"Square root of {a} = {a_sqrt}")
以下是输出结果:

图 2 —零的平方根(图片由作者提供)
只剩下一个用例,那就是负数。
## 负数的平方根
用[实数](https://study.com/learn/lesson/negative-square-root-overview-examples.html)无法计算负数的平方根。两个负数相乘总会得到一个正数。
尽管如此,让我们试一试:
import math
a = -10
Square root of a negative number
a_sqrt = math.sqrt(a)
print("Square root of a negative number")
print("--------------------------------------------------")
print(f"Square root of {a} = {a_sqrt}")
它会导致一个错误:

图 3-负数的平方根(作者提供的图片)
有很多方法可以计算负数的平方根,那就是把它们写成-1 的倍数。例如,-9 可以写成-1 x 9。结果将是 **3i** 。进入虚数领域超出了今天的范围,所以我就讲到这里。
接下来,让我们来看一下处理 Python 平方根的 5 种方法。
# 用 sqrt()函数计算平方根
第一种方法实际上是您在上一节中看到的方法。它依靠`math.pow()`函数来完成这个任务。这个模块附带了默认的 Python 安装,所以不需要安装任何外部库。
下面是一段代码,演示了如何在 Python 中使用此函数求平方根:
import math
a = 1
b = 25
c = 30.82
d = 100
e = 400.40
Method #1 - math.sqrt() function
a_sqrt = math.sqrt(a)
b_sqrt = math.sqrt(b)
c_sqrt = math.sqrt(c)
d_sqrt = math.sqrt(d)
e_sqrt = math.sqrt(e)
print("Method #1 - math.sqrt() function")
print("--------------------------------------------------")
print(f"Square root of {a} = {a_sqrt}")
print(f"Square root of {b} = {b_sqrt}")
print(f"Square root of {c} = {c_sqrt}")
print(f"Square root of {d} = {d_sqrt}")
print(f"Square root of {e} = {e_sqrt}")
结果如下:

图 4 — Math sqrt()函数(图片由作者提供)
这可能是您需要的唯一方法,但是让我们看看其他的方法。
# 用 pow()函数计算平方根
如果平方一个数意味着把它提升到 2 的幂,那么开平方根实质上就是把它提升到 0.5 的幂。这正是您可以用`math.pow()`函数实现的行为。它有两个参数——数字和指数。
让我们来看几个例子:
import math
a = 1
b = 25
c = 30.82
d = 100
e = 400.40
Method #2 - math.pow() function
a_sqrt = math.pow(a, 0.5)
b_sqrt = math.pow(b, 0.5)
c_sqrt = math.pow(c, 0.5)
d_sqrt = math.pow(d, 0.5)
e_sqrt = math.pow(e, 0.5)
print("Method #2 - math.pow() function")
print("--------------------------------------------------")
print(f"Square root of {a} = {a_sqrt}")
print(f"Square root of {b} = {b_sqrt}")
print(f"Square root of {c} = {c_sqrt}")
print(f"Square root of {d} = {d_sqrt}")
print(f"Square root of {e} = {e_sqrt}")
输出与我们之前的输出相同:

图 5 — Math pow()函数(图片由作者提供)
很好,但是我们能完全消除图书馆的使用吗?当然,这是方法。
# Python 的指数运算符(**)用于计算平方根
上一个函数的逻辑同样适用于此处。您可以使用 Python 的指数运算符将一个数字提升到 0.5 的幂。它和`math.pow(x, 0.5)`做的一样,但是语法更短并且不依赖任何库。
下面是如何在 Python 中使用它:
a = 1
b = 25
c = 30.82
d = 100
e = 400.40
Method #3 - Python exponent operator
a_sqrt = a0.5
b_sqrt = b0.5
c_sqrt = c0.5
d_sqrt = d0.5
e_sqrt = e**0.5
print("Method #3 - Python exponent operator")
print("--------------------------------------------------")
print(f"Square root of {a} = {a_sqrt}")
print(f"Square root of {b} = {b_sqrt}")
print(f"Square root of {c} = {c_sqrt}")
print(f"Square root of {d} = {d_sqrt}")
print(f"Square root of {e} = {e_sqrt}")
结果又是一样的,毫无疑问:

图片 6-Python 指数运算符(图片由作者提供)
接下来,让我们看看用 Numpy 计算数字和数组的平方根。
# Numpy —计算数字或数组的根
Numpy 是 Python 中数值计算的首选库。它内置了一个`sqrt()`函数,你可以用它对数字和数组求平方根。
请记住返回类型——对于单个数字是`numpy.float64`,对于数组是`numpy.ndarray`。当然,每个数组元素的类型都是`numpy.float64`:
import numpy as np
a = 1
b = 25
c = 30.82
d = 100
e = 400.40
arr = [a, b, c]
Method #4 - Numpy square roots
a_sqrt = np.sqrt(a)
b_sqrt = np.sqrt(b)
c_sqrt = np.sqrt(c)
d_sqrt = np.sqrt(d)
e_sqrt = np.sqrt(e)
arr_sqrt = np.sqrt(arr)
print("Method #4 - Numpy square roots")
print("--------------------------------------------------")
print(f"Square root of {a} = {a_sqrt}")
print(f"Square root of {b} = {b_sqrt}")
print(f"Square root of {c} = {c_sqrt}")
print(f"Square root of {d} = {d_sqrt}")
print(f"Square root of {e} = {e_sqrt}")
print(f"Square root of {arr} = {arr_sqrt}")
以下是控制台输出:

图 7——Numpy 中的平方根(图片由作者提供)
这是迄今为止最方便的方法,因为它依赖于广泛使用的 Python 库,并且不管输入的数据类型是什么,计算过程都是相同的。
# Cmath —取复数的 Python 平方根
还记得平方根和负数的故事吗?Python 的`math`模块引发了一个错误,但是`cmath`在这里拯救了这一天。这个模块用于处理复数。
在下面的代码片段中,您将看到从正整数、浮点数、复数和负数中提取的平方根:
import cmath
a = 1
b = 25.44
c = cmath.pi
d = 10+10j
e = -100
Method #5 - Square roots of complex numbers
a_sqrt = cmath.sqrt(a)
b_sqrt = cmath.sqrt(b)
c_sqrt = cmath.sqrt(c)
d_sqrt = cmath.sqrt(d)
e_sqrt = cmath.sqrt(e)
print("Method #5 - Square roots of complex numbers")
print("--------------------------------------------------")
print(f"Square root of {a} = {a_sqrt}")
print(f"Square root of {b} = {b_sqrt}")
print(f"Square root of {c} = {c_sqrt}")
print(f"Square root of {d} = {d_sqrt}")
print(f"Square root of {e} = {e_sqrt}")
这次没有错误:

图 8——复数的平方根(图片由作者提供)
我从来没有使用这个模块的需求,但是知道它的存在是很好的。
接下来,让我们来看看 Python 平方根的一些更高级的用法示例。
# 额外收获:高级 Python 根主题
我们现在将改变话题,讨论几个更高级的话题。其中包括在 Python 中计算立方根的方法,以及对普通 Python 列表求平方根的方法。让我们从立方根开始。
## Python 中的立方根
如果求平方根意味着将一个数提升到 0.5 的幂,那么立方根必须用 0.333 的幂来表示,即 1/3。
以下是如何在不使用任何外部库的情况下用 Python 实现这一逻辑:
a = 1
b = 27
c = 30.82
d = 1000
e = 400.40
Bonus #1 - Cube roots
a_cbrt = a ** (1./3.)
b_cbrt = b ** (1./3.)
c_cbrt = c ** (1./3.)
d_cbrt = d ** (1./3.)
e_cbrt = e ** (1./3.)
print("Bonus #1 - Cube roots")
print("--------------------------------------------------")
print(f"Cube root of {a} = {a_cbrt}")
print(f"Cube root of {b} = {b_cbrt}")
print(f"Cube root of {c} = {c_cbrt}")
print(f"Cube root of {d} = {d_cbrt}")
print(f"Cube root of {e} = {e_cbrt}")
结果打印如下:

图 9-立方根(1)(图片由作者提供)
Numpy 提供了一种在 Python 中求立方根的更简单的方法。它内置了一个`cbrt()`函数,代表*立方根*。您可以在数字和数组上使用它,就像平方根一样:
import numpy as np
a = 1
b = 27
c = 30.82
d = 1000
e = 400.40
arr = [a, b, c]
Bonus #1.2 - Cube roots with Numpy
a_cbrt = np.cbrt(a)
b_cbrt = np.cbrt(b)
c_cbrt = np.cbrt(c)
d_cbrt = np.cbrt(d)
e_cbrt = np.cbrt(e)
arr_cbrt = np.cbrt(arr)
print("Bonus #1.2 - Cube roots with Numpy")
print("--------------------------------------------------")
print(f"Cube root of {a} = {a_cbrt}")
print(f"Cube root of {b} = {b_cbrt}")
print(f"Cube root of {c} = {c_cbrt}")
print(f"Cube root of {d} = {d_cbrt}")
print(f"Cube root of {e} = {e_cbrt}")
print(f"Cube root of {arr} = {arr_cbrt}")
让我们来看看结果:

图 10-立方根(2)(图片由作者提供)
是的,就是这么简单。
## Python 中列表的平方根
还有一种简单的方法来计算 Python 列表的平方根,不需要 Numpy。您可以简单地遍历列表,求出单个列表项的平方根:
import math
arr = [1, 25, 30.82]
arr_sqrt = []
Bonus #2 - Square root of a Python list
for num in arr:
arr_sqrt.append(math.sqrt(num))
print("Bonus #2 - Square root of a Python list")
print("--------------------------------------------------")
print(f"Square root of {arr} = {arr_sqrt}")
结果如下:

图 11 —列表的平方根(1)(图片由作者提供)
或者,如果您喜欢更 Pythonic 化的方法,没有理由不使用列表理解,而只是将上面的计算简化为一行代码:
import math
arr = [1, 25, 30.82]
Bonus #2.2 - Square root of a Python list using list comprehension
arr_sqrt = [math.sqrt(num) for num in arr]
print("Bonus #2.2 - Square root of a Python list using list comprehension")
print("--------------------------------------------------")
print(f"Square root of {arr} = {arr_sqrt}")
输出是相同的:

图 12 —列表的平方根(2)(图片由作者提供)
这就是在 Python 中求平方根有多容易——对于整数、浮点数、列表,甚至是复数。接下来让我们做一个简短的回顾。
# 结论
您现在知道了计算 Python 平方根的 5 种不同方法。实际上,你只需要一个,但是知道几个选择也无妨。您可以使用内置的`math`模块,选择`numpy`,或者使用指数运算符并完全避免使用库。所有的方法都可行,选择权在你。
如果您想了解更多基本的 Python 概念,请继续关注博客。感谢阅读!
喜欢这篇文章吗?成为 [*中等会员*](/@radecicdario/membership) *继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。*
<https://medium.com/@radecicdario/membership>
## 保持联系
* 雇用我作为一名技术作家
* 订阅 [YouTube](https://www.youtube.com/c/BetterDataScience)
* 在 [LinkedIn](https://www.linkedin.com/in/darioradecic/) 上连接
*原载于 2022 年 12 月 1 日*[*https://betterdatascience.com*](https://betterdatascience.com/python-square-roots/)*。*
# Python:面向对象还是面向编程?
> 原文:<https://towardsdatascience.com/python-to-oop-or-to-fp-13ac79a43b16>
## 这是一个问题

苏珊·霍尔特·辛普森在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 拍摄的照片
程序员永远无法在任何事情上达成一致,但迄今为止,不断困扰互联网的最大争论之一是面向对象编程(OOP)和函数式编程(FP)之间的斗争。
提醒一下,OOP 围绕着将所有的业务逻辑和数据包装在类中,然后可以创建共享相同功能的对象。它还包括继承和多态等概念,这使得拥有功能相似但略有不同的类变得更加容易。
通常用来演示 OOP 的语言是 Java。在 Java 中,所有东西都必须包装在一个类中,包括程序的主执行循环。
另一方面,函数式编程更关心——你猜对了——函数。在函数式编程中,数据通常通过管道从一个函数传递到另一个函数,每个函数对数据执行一个操作。如果给定相同的输入,函数通常被设计成产生完全相同的输出。
最流行的函数式编程语言是 Clojure、Elixir 和 Haskell。
# 但是 Python 呢?
Python 是一个有趣的例子。它共享了面向对象语言的许多共同特性,允许您创建类并从超类继承,但它也具有您通常在函数式语言中看到的功能。你可以在程序的主体中定义函数,函数也是一等公民,这意味着你可以把它们作为对象传递。
事实是,Python 非常灵活。如果您来自 Java,并且想用纯面向对象的风格编写所有东西,那么您将能够完成您想要的大部分事情。如果您以前是 Clojure 开发人员,用 Python 复制 FP 模式也不会有太大的困难。
然而,Python 的美妙之处在于,你不局限于任何一种做事方式。您可以使用这两种范例的特性来创建可读的、可扩展的代码,这将保持您的代码库的可维护性,即使它在增长。
下面是三个用 OOP、FP 和两者混合编写的相同(非常简单)程序的例子。我将强调每种方法的优点和缺点,这将为您设计下一个 Python 项目打下良好的基础。
# 该计划
用于演示的程序非常简单——它创建了两只动物(一只狗和一条鱼),并让它们执行一些非常简单的动作。在这个例子中,动作只是记录到`stdout`,但是它们显然可以做得更多。
## OOP 示例
from abc import ABC, abstractmethod
class Logger(ABC):
@abstractmethod
def log(self, message: str):
...
class MyLogger(Logger):
def init(self, name: str):
self.name = name
def log(self, message: str):
print(f'{self.name}: {message}')
class Animal:
def init(self, name: str, logger: Logger):
self.name = name
self.logger = logger
def speak(self):
self.logger.log('Speaking')
...
class Dog(Animal):
def speak(self):
self.logger.log('Woof!')
...
def run(self):
self.logger.log('Running')
...
class Fish(Animal):
...
class App:
@staticmethod
def run():
fido = Dog(name='Fido', logger=MyLogger('Fido'))
goldie = Fish(name='Goldie', logger=MyLogger('Goldie'))
fido.speak()
fido.run()
goldie.speak()
if name == 'main':
App.run()
Fido: Woof!
Fido: Running
Goldie: Speaking
如您所见,代码创建了一个用于将事件记录到`stdout`的`MyLogger`类,一个`Animal`基类,然后是用于更具体动物的`Dog`和`Fish`类。
为了更好地遵循 OOP 范例,它还定义了一个具有运行程序的单一方法`run`的`App`类。
OOP 和继承的好处是我们不必在`Fish`类上定义一个`speak`方法,它仍然能够说话。
然而,如果我们想要有更多可以运行的动物,我们就必须在`Animal`和`Dog`之间引入一个定义`run`方法的`RunningAnimal`类,并且可能为`Fish`引入一个类似的`SwimmingAnimal`类,但是之后我们的层次结构开始变得越来越复杂。
另外,`MyLogger`和`App`类在这里几乎没有用。每个都只做一件事,实际上使代码可读性稍差。这些最好放在一个`log`和一个`main`(或`run`)函数中。
我们还必须创建一个纯粹的`Logger`抽象基类,这样代码就可以正确地进行类型提示,并允许我们 API 的用户传入其他日志程序,如果他们想登录到`stdout`之外的地方,或者如果他们想用不同的格式登录。
## FP 示例
只是提醒一下——我对 FP 的熟悉程度不如 OOP,所以这可能不是实现这种行为的最像 FP 的方式,但这是我要做的。
import functools
from typing import Callable
Logger = Callable[[str], None]
def log(message: str, name: str):
print(f'{name}: {message}')
def bark(
name: str,
log_fn: Logger,
) -> (str, Logger):
log_fn('Woof!')
return name, log_fn
def run(
name: str,
log_fn: Logger,
) -> (str, Logger):
log_fn('Running')
return name, log_fn
def speak(
name: str,
log_fn: Logger,
) -> (str, Logger):
log_fn('Speaking')
return name, log_fn
def main():
run(
*bark(
'Fido',
functools.partial(log, name='Fido'),
),
)
speak(
'Goldie',
functools.partial(log, name='Goldie'),
)
if name == 'main':
main()
Fido: Woof!
Fido: Running
Goldie: Speaking
很快,我们可以看到我们的`Logger`类已经成为`Callable[[str], None]`的一个方便的类型别名。我们还定义了一个`log`函数来处理我们的打印。我们没有为我们的动物定义类,而是简单地定义了以动物的名字和一个`Logger`函数命名的函数。
您会注意到,`run`、`speak`和`bark`函数也都返回它们的名称和日志记录函数参数,以便将它们组合到管道中,就像我们对 Fido 的`run`和`bark`所做的那样。
我们还将我们的逻辑移到了一个`main`函数中,消除了仅仅为了运行我们的程序而定义整个类的需要。
为了避免我们的`log`函数与`Logger`类型不匹配的事实,我们使用`functools.partial`来创建一个匹配的部分函数。这允许我们用我们喜欢的任何东西来替换我们的记录器,只要我们可以使用一个部分函数来减少它,以便它匹配我们的`Logger`类型。
然而,由于我们没有将数据封装在任何东西中,如果我们想给我们的动物添加更多的属性,我们可能不得不开始使用`dict`对象来表示它们并传递它们,但这样总是会担心字典创建不正确,从而缺少我们的一个函数所依赖的键。
为了避开*和*,我们需要为我们的动物创建初始化函数,这时代码又变得越来越乱。
## 两者都有点
那么,如果我们将一点 OOP 和一点 FP 结合起来,会发生什么呢?我将引入一些 Pythonic 代码来脱离传统的 OOP 和 FP 范例,并希望使代码更加简洁易读。
from dataclasses import dataclass
from functools import partial
from typing import Callable
Logger = Callable[[str], None]
def log(message: str, name: str):
print(f'{name}: {message}')
@dataclass
class Animal:
name: str
log: Logger
def speak(self):
self.log('Speaking')
@dataclass
class Dog(Animal):
breed: str = 'Labrador'
def speak(self):
self.log('Woof!')
def run(self):
self.log('Running')
@dataclass
class Fish(Animal):
...
def main():
fido = Dog('Fido', partial(log, name='Fido'))
goldie = Fish('Goldie', partial(log, name='Goldie'))
fido.speak()
fido.run()
goldie.speak()
if name == 'main':
main()
Fido: Woof!
Fido: Running
Goldie: Speaking
在这个例子中,我使用 python`dataclasses`模块来避免为我的类编写构造函数。这不仅减少了我需要编写的一些代码,而且如果我需要的话,还可以更容易地添加新的属性。
类似于 OOP 的例子,我们有一个带有`Dog`和`Fish`子类的`Animal`基类。然而,就像在 FP 示例中一样,我使用了`Logger`类型别名和`functools.partial`来为动物创建记录器。Python 支持作为一等公民的函数使得这变得更加容易。
还有,`main`函数只是一个函数。我永远不会明白为什么 Java 是 Java 的样子。
# 在生产中混合 OOP 和 FP
好的,我承认这个例子非常简单,虽然它为我们的讨论提供了一个很好的起点,但是我现在想给你们一个例子来说明这些概念是如何在生产中使用的,我将使用我最喜欢的两个 Python 库: [FastAPI](https://fastapi.tiangolo.com/) 和 [Pydantic](https://pydantic-docs.helpmanual.io/) 。FastAPI 是 Python 的轻量级 API 框架,Pydantic 是数据验证和设置管理库。
我不打算详细介绍这些库,但是 Pydantic 有效地允许您使用 Python 类定义数据结构,然后验证传入的数据并通过对象属性访问它。这意味着您不会遇到使用字典带来的问题,并且您总是知道您的数据是您期望的格式。
FastAPI 允许您将 API 路由定义为函数,用一个装饰器(这是一个非常类似 FP 的概念)包装每一个路由,以封装您的逻辑。
下面是如何使用它的一个例子。同样,这是一个简单的示例,但它相当能代表您在生产中可能看到的情况。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Baz(BaseModel):
qux: int
class Foo(BaseModel):
bar: str
baz: Baz
@app.get('/foo')
async def get_foo(name: str, age: int) -> Foo:
... # Some logic here
return Foo(
bar=name,
baz=Baz(qux=age),
)
GET /foo?name=John&age=42
{
"bar": "John",
"baz": {
"qux": 42
}
}
如您所见,FastAPI 使用 Pydantic 的能力将嵌套对象转换为 JSON,从而为我们的端点创建一个 JSON 响应。`app.get`装饰器还向`app`对象注册了我们的`get_foo`函数,允许我们向`/foo`端点发出`GET`请求。
我希望这篇文章对你有所帮助。我很想听听你们的想法,以及你们在编写 Python 时倾向于哪种范式。
显然,这并不是 Python 中结合 FP 和 OOP 的唯一方式,使用这种结合可以实现和改进很多设计模式。
我将在未来写下这些,通过在[媒体](https://medium.isaacharrisholt.com/)上跟随我,你将不会错过。我也在 Twitter 上发关于 Python 和我当前项目的推文,并且(最近)也在乳齿象上发关于它们的帖子。
我相信我们很快会再见的!
-艾萨克
# Python 回溯-解释
> 原文:<https://towardsdatascience.com/python-tracebacks-explained-538caaac434>
## 调试的好帮手

安妮·尼加德在 [Unsplash](https://unsplash.com/s/photos/trace?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上拍摄的照片
我们很少能在第一次尝试时运行一个没有任何错误的脚本,这在编写代码时是绝对正常的。重要且有时具有挑战性的部分是修复这些错误。
修复错误并使脚本按预期工作的过程可能需要多次迭代,这取决于程序员的经验和我们对错误的了解。编程语言给了我们一些提示,告诉我们是什么导致了错误,这基本上就是 Python 回溯所做的事情。
Python 中的回溯可以看作是一份报告,帮助我们理解和修复代码中的问题。在本文中,我们将学习什么是 Python 中的回溯,如何阅读回溯消息以便能够更有效地使用它们,以及不同的错误类型。
# Python 中的回溯是什么?
Python 中的程序在遇到错误时会停止执行,错误的形式可能是语法错误或异常。当解释器检测到无效的语法时,就会出现语法错误,这相对来说比较容易解决。
语法错误的一个例子是不匹配的括号。另一方面,当语法正确但程序导致错误时,会引发异常。
回溯是帮助我们理解异常原因的报告。它包含代码中的函数调用以及它们的行号,这样我们就不会对导致代码失败的问题一无所知。
让我们看一个简单的例子。
下面的代码片段创建了一个函数,该函数将两个数字相加,并将总和乘以第一个数字。然后,它调用带有参数 5 和 4 的函数。但是,4 是作为字符串传递的,所以它实际上不是一个数字。
def add_and_multiply(x, y):
return (x + y) * x
add_and_multiply(5, "4")
执行此代码时,Python 会引发以下异常:

(图片由作者提供)
最后一行显示了错误类型以及简短的解释。这种情况下的错误是由整数和字符串之间不受支持的操作数导致的类型错误。加号运算符不能用于将字符串与整数相加,因此代码会导致异常。
最后一行上面的行根据函数名和行号告诉我们异常发生的位置。我们这里的例子非常简单,但是当处理非常长的脚本或具有多个脚本的程序时,关于函数名和行号的信息对于诊断和修复问题非常有帮助。
回溯还显示了模块和文件名,这在处理从其他文件或脚本导入模块的脚本时非常有用。根据您的工作环境(如终端或 REPL),文件名和模块名的显示方式会略有不同。
例如,当我将上面的代码片段保存为“sample_script.py”并尝试在终端中运行它时,我得到以下回溯:
Traceback (most recent call last):
File "/Users/sonery/sample_script.py", line 6, in
add_and_multiply(5, "6")
File "/Users/sonery/sample_script.py", line 2, in add_and_multiply
print((x + y) * x)
TypeError: unsupported operand type(s) for +: 'int' and 'str'
在任何情况下,我们都可以从回溯消息中获得有用的线索。
# 常见追溯类型
创建高效程序并在生产中维护它们的大量时间都花在了调试错误上。因此,利用 Python 回溯至关重要。
否则,可能需要花费数小时来查找和修复问题,如果程序已经部署到生产环境中,这可能会产生严重的后果。
回溯消息最重要的部分是错误类型,因为它提示我们是哪种错误导致脚本停止执行。
让我们回顾一下回溯消息中一些常见的错误类型。
## 类型错误
当对象的数据类型与定义的操作不兼容时,会发生类型错误。我们在开始时做的添加整数和字符串的例子就是这种错误的一个例子。
## 属性错误
在 Python 中,一切都是一个具有整数、字符串、列表、元组、字典等类型的对象。使用类来定义类型,类也具有用于与类的对象交互的属性。
类可以有数据属性和过程属性(即方法):
* 数据属性:创建一个类的实例需要什么
* 方法(即过程属性):我们如何与类的实例交互。
假设我们有一个 list 类型的对象。我们可以使用 append 方法向列表中添加新项。如果对象没有我们试图使用的属性,就会引发一个属性错误异常。
这里有一个例子:
mylist = [1, 2, 3, 4, 5]
mylist.add(10)
output
Traceback (most recent call last):
File "
exec(code_obj, self.user_global_ns, self.user_ns)
File "
mylist.add(10)
AttributeError: 'list' object has no attribute 'add'
由于 list 类没有名为“add”的属性,我们得到一个显示属性错误的回溯。
## ImportError 和 ModuleNotFoundError
Python 有大量的第三方库(即模块),这使得用几行代码完成大量任务成为可能。
为了使用这样的库,以及内置的 Python 库(例如操作系统、请求),我们需要导入它们。如果在导入它们时出现问题,则会引发导入错误或模块未找到错误异常。
例如,在下面的代码片段中,我们试图从 Scikit-learn 导入逻辑回归类。
from sklearn import LogisticRegression
output
Traceback (most recent call last):
File "
exec(code_obj, self.user_global_ns, self.user_ns)
File "
from sklearn import LogisticRegression
ImportError: cannot import name 'LogisticRegression' from 'sklearn' (/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/sklearn/init.py)
由于线性模型模块中提供了逻辑回归类,因此引发了导入错误异常。正确的导入方法如下。
from sklearn.linear_model import LogisticRegression
如果模块在工作环境中不可用,则会引发模块未找到错误异常。
import openpyxl
output
Traceback (most recent call last):
File "
exec(code_obj, self.user_global_ns, self.user_ns)
File "
import openpyxl
File "/Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pydev/_pydev_bundle/pydev_import_hook.py", line 21, in do_import
module = self._system_import(name, *args, **kwargs)
ModuleNotFoundError: No module named 'openpyxl'
## 索引错误
一些数据结构有一个索引,可以用来访问它们的项目,如列表、元组和数据帧。我们可以通过使用序列下标来访问一个特定的项目。
names = ["John", "Jane", "Max", "Emily"]
Get the third item
names[2]
output
"Max"
如果下标超出范围,将引发索引错误异常。
names = ["John", "Jane", "Max", "Emily"]
Get the sixth item
names[5]
output
Traceback (most recent call last):
File "
exec(code_obj, self.user_global_ns, self.user_ns)
File "
names[5]
IndexError: list index out of range
由于列表包含 4 项,当我们试图访问不存在的第 6 项时,会引发一个异常。
让我们用熊猫数据框架做另一个例子。
import pandas as pd
import numpy as np
df = pd.DataFrame(np.random.randint(10, size=(5, 2)), columns=["A", "B"])
df
output
A B
0 1 6
1 6 3
2 8 8
3 3 5
4 5 6
变量 df 是一个具有 5 行 2 列的数据帧。以下代码行试图获取第一行第三列中的值。
df.iloc[0, 3]
output
Traceback (most recent call last):
File "
exec(code_obj, self.user_global_ns, self.user_ns)
File "
df.iloc[0, 3]
File "
return self.obj._get_value(*key, takeable=self._takeable)
File "
series = self._ixs(col, axis=1)
File "
label = self.columns[i]
File "
return getitem(key)
IndexError: index 3 is out of bounds for axis 0 with size 2
正如我们在回溯的最后一行看到的,这是一个不言自明的错误消息。
## 名称错误
当我们引用代码中未定义的变量时,会引发名称错误异常。
这里有一个例子:
members = ["John", "Jane", "Max", "Emily"]
member[0]
output
Traceback (most recent call last):
File "
exec(code_obj, self.user_global_ns, self.user_ns)
File "
name[5]
NameError: name 'member' is not defined
变量的名称是 members,所以当我们试图使用 member 而不是 members 时,我们会得到一个错误。
## 值错误
当我们试图给一个变量赋一个不合适的值时,就会出现值错误异常。回想一下我们的 5 行 2 列的数据帧。
import pandas as pd
import numpy as np
df = pd.DataFrame(np.random.randint(10, size=(5, 2)), columns=["A", "B"])
df
output
A B
0 1 6
1 6 3
2 8 8
3 3 5
4 5 6
假设我们想向这个数据帧添加一个新列。
df["C"] = [1, 2, 3, 4]
output
Traceback (most recent call last):
File "
exec(code_obj, self.user_global_ns, self.user_ns)
File "
df["C"] = [1, 2, 3, 4]
File "
self._set_item(key, value)
File "
value = self._sanitize_column(value)
File "
com.require_length_match(value, self.index)
File "
raise ValueError(
ValueError: Length of values (4) does not match length of index (5)
如错误消息中所述,数据帧有 5 行,因此每列有 5 个值。当我们试图创建一个包含 4 个条目的新列时,我们得到一个值错误。
# 结论
错误消息在调试代码或使代码第一次正确执行时非常有用。幸运的是,Python 回溯有清晰的解释性错误消息。
在本文中,我们学习了什么是回溯,如何阅读它,以及一些常见的回溯类型。
*你可以成为* [*媒介会员*](https://sonery.medium.com/membership) *解锁我的全部写作权限,外加其余媒介。如果你已经是了,别忘了订阅*<https://sonery.medium.com/subscribe>**如果你想在我发表新文章时收到电子邮件。**
*感谢您的阅读。如果您有任何反馈,请告诉我。*
# 用 BigTree 实现 Python 树
> 原文:<https://towardsdatascience.com/python-tree-implementation-with-bigtree-13cdabd77adc>
## 将树与 Python 列表、字典和熊猫数据框架集成在一起

[简·侯伯](https://unsplash.com/@jan_huber?utm_source=medium&utm_medium=referral)在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上拍照
Python 内置了列表、数组和字典的数据结构,但没有树状数据结构。在 LeetCode 中,针对树的问题仅限于二分搜索法树,并且其实现不具有许多功能。
`**bigtree**` Python 包可以在 Python 列表、字典和 pandas 数据帧之间构建和导出树,与现有的 Python 工作流无缝集成。
> 树状数据结构可以用来显示层次关系,如家谱和组织结构图。
本文将介绍基本的树概念,如何用`**bigtree**` Python 包构建树,树的遍历、搜索、修改和导出方法。本文以使用树实现待办事项列表的方式结束,并将树实现扩展到 [Trie](https://en.wikipedia.org/wiki/Trie) 和[有向无环图](https://en.wikipedia.org/wiki/Directed_acyclic_graph)数据结构。
如果你有兴趣为`**bigtree**` Python 包做贡献,那就来合作吧!
# 目录
* [树基础和术语](https://medium.com/p/13cdabd77adc/#c220)
* [大树设置](https://medium.com/p/13cdabd77adc/#8552)
* [建造树木](https://medium.com/p/13cdabd77adc/#3e14)
* [树遍历算法](https://medium.com/p/13cdabd77adc/#509b)
* [树形搜索方法](https://medium.com/p/13cdabd77adc/#1ada)
* [树形修改方法](https://medium.com/p/13cdabd77adc/#4bd0)
* [导出树木](https://medium.com/p/13cdabd77adc/#2251)
* [附加:与](https://medium.com/p/13cdabd77adc/#4d13)和`[**bigtree**](https://medium.com/p/13cdabd77adc/#4d13)`一起使用待办事项
* [附加:延伸至 Trie](https://medium.com/p/13cdabd77adc/#660e)
* [附加:有向无环图(DAG)](https://medium.com/p/13cdabd77adc/#185d)
# 树的基础和术语
树是分层存储数据的非线性数据结构,由通过**边**连接的**节点**组成。例如,在家谱中,一个节点代表一个人,一条边代表两个节点之间的关系。

图 1:树的例子——作者的图片
了解了构成树的组件后,有几个术语可以扩展到这些组件,
* **根**:没有任何父节点,整棵树都源于它的节点。在图 1 中,根是节点`a`
* **叶**:没有任何子节点的节点。在图 1 中,叶节点是节点`c`、`d`和`e`
* **父节点**:节点的直接前任。在图 1 中,节点`a`是节点`b`和`c`的父节点
* **子节点**:节点的直接继承者。在图 1 中,节点`b`是节点`a`的子节点
* **祖先**:一个节点的所有前辈。在图 1 中,节点`a`和`b`是节点`d`的祖先
* **子孙**:一个节点的所有继承者。在图 1 中,节点`d`和`e`是节点`b`的后代
* **兄弟节点**:具有相同父节点的节点。在图 1 中,节点`d`和`e`是兄弟节点
* **左同级**:节点左侧的同级。在图 1 中,节点`d`是节点`e`的左兄弟
* **右同级**:节点右侧的同级。在图 1 中,节点`e`是节点`d`的右兄弟
* **深度**:从节点到根的路径长度。在图 1 中,节点`b`的深度是 2,节点`d`的深度是 3
* **高度/最大深度**:根到叶节点的最大深度。在图 1 中,树的高度是 3
# 大树设置
大树很容易设置,只需在终端上运行以下命令。
$ pip install bigtree
如果您想要将树导出到图像,请改为在终端上运行以下命令。
$ pip install 'bigtree[image]'
事不宜迟,让我们开始实现树吧!
# 构建树
要构造树,首先要定义节点,通过指定节点的`parent`和`children`来链接节点。
例如,要构建一个家族树,
from bigtree import Node, print_tree
a = Node("a", age=90)
b = Node("b", age=65, parent=a)
c = Node("c", age=60, parent=a)
a.children
(Node(/a/b, age=65), Node(/a/c, age=60))
a.depth, b.depth
(1, 2)
a.max_depth
2
print_tree(a, attr_list=["age"])
"""
a [age=90]
├── b [age=65]
└── c [age=60]
"""
在上面的例子中,我们用 3 行代码将节点`b`和`c`定义为节点`a`的子节点。我们还可以添加属性,比如节点的`age`属性。要查看树结构,我们可以使用`print_tree`方法。
我们还可以查询上一节中提到的节点的`root`、`leaves`、`parent`、`children`、`ancestors`、`descendants`、`siblings`、`left_sibling`、`right_sibling`、`depth`、`max_depth`。
> 上述定义每个节点和边的方法可能是手动的和繁琐的。用列表、字典和熊猫数据框架构建树还有其他方法!
如果没有节点属性,构建树的最简单方法是使用 Python 列表和`list_to_tree`方法。
from bigtree import list_to_tree, print_tree
path_list = ["a/b/d", "a/b/e", "a/c"]
root = list_to_tree(path_list)
print_tree(root)
"""
a
├── b
│ ├── d
│ └── e
└── c
"""
如果有节点属性,建议用字典或 pandas DataFrame 构造一棵树,分别用`dict_to_tree`和`dataframe_to_tree`方法。
from bigtree import dict_to_tree, print_tree
path_dict = {
"a": {"age": 90},
"a/b": {"age": 65},
"a/c": {"age": 60},
"a/b/d": {"age": 40},
"a/b/e": {"age": 35},
}
root = dict_to_tree(path_dict)
print_tree(root, attr_list=["age"])
"""
a [age=90]
├── b [age=65]
│ ├── d [age=40]
│ └── e [age=35]
└── c [age=60]
"""
from bigtree import dataframe_to_tree, print_tree
path_data = pd.DataFrame(
[
["a", 90],
["a/b", 65],
["a/c", 60],
["a/b/d", 40],
["a/b/e", 35],
],
columns=["PATH", "age"],
)
root = dataframe_to_tree(path_data)
print_tree(root, attr_list=["age"])
"""
a [age=90]
├── b [age=65]
│ ├── d [age=40]
│ └── e [age=35]
└── c [age=60]
"""
更多节点属性和方法以及其他数据结构到树的方法,请参考`[**bigtree**](https://bigtree.readthedocs.io)` [文档](https://bigtree.readthedocs.io)。
# 树遍历算法
有两种类型的树遍历,深度优先搜索(DFS)和宽度优先搜索(BFS),
* 深度优先搜索从根开始,在移动到下一个分支之前,探索每个分支到它的叶节点
* 广度优先搜索从根节点开始,探索每个子节点,并对每个节点递归地这样做
## 前序遍历(DFS,NLR)
前序遍历是一种深度优先搜索(DFS)方法,
1. 访问当前节点(N)
2. 递归遍历当前节点的左子树(L)
3. 递归遍历当前节点的右子树(R)

图 2:树的例子——作者的图片
对于前序遍历,它将按顺序遍历图 2 中的树,
['a', 'b', 'd', 'e', 'g', 'h', 'c', 'f']
## 后序遍历(DFS,LRN)
后序遍历是一种深度优先搜索(DFS)方法,
1. 递归遍历当前节点的左子树(L)
2. 递归遍历当前节点的右子树(R)
3. 访问当前节点(N)

图 3:树的例子——作者的图片
对于后序遍历,它将按顺序遍历图 3 中的树,
['d', 'g', 'h', 'e', 'b', 'f', 'c', 'a']
## 层级顺序遍历(BFS)
层次顺序遍历是一种广度优先的搜索方法。
对于级别顺序遍历,它将按照顺序遍历图 3 中的树,
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
## 级别顺序组遍历(BFS)
级别顺序组遍历类似于级别顺序遍历,区别在于每一级都将作为嵌套列表返回;`list[idx]`表示深度`idx + 1`中的项目。
对于级别顺序组遍历,它将按照顺序遍历图 3 中的树,
[['a'], ['b', 'c'], ['d', 'e', 'f'], ['g', 'h']]
注意还有**按序遍历(DFS,LNR)** 只适用于二叉树,不适用于类属树。
# 树搜索方法
我们可以使用树搜索方法来获得满足特定标准的一个或多个节点,方法`find`用于一个节点,方法`findall`用于多个节点。
from bigtree import Node, print_tree, find, findall
root = Node("a", age=90)
b = Node("b", age=65, parent=root)
c = Node("c", age=60, parent=root)
d = Node("d", age=40, parent=b)
print_tree(root, attr_list=["age"])
"""
a [age=90]
├── b [age=65]
│ └── d [age=40]
└── c [age=60]
"""
find(root, lambda node: node.age == 65)
Node(/a/b, age=65)
findall(root, lambda node: node.age >= 60)
(Node(/a, age=90), Node(/a/b, age=65), Node(/a/c, age=60))
对于没有定义 lambda 函数的通用搜索方法,有一些内置方法,
* `**find_attr**`和`**find_attrs**`:根据属性查找一个/多个节点
* `**find_name**`和`**find_names**`:根据名称查找一个/多个节点
* `**find_path**`、`**find_paths**`:通过完整或部分路径查找一个或多个节点
* `**find_full_path**`:根据节点的完整路径找到一个节点
* `**find_children**`:通过名称查找节点的子节点,无需搜索整个树
# 树木改造方法
`**bigtree**` 支持节点必须从一个位置移动或复制到一个目的地的情况。例如,我们可以在待办事项列表实现中移动和重新排序节点。
from bigtree import Node, shift_nodes, print_tree
root = Node("List")
groceries = Node("Groceries", parent=root)
urgent = Node("Urgent", parent=root)
groceries_milk = Node("Milk", parent=groceries)
shift_nodes(
root,
from_paths=["Groceries/Milk"],
to_paths=["Urgent/Milk"],
)
print_tree(root)
List
├── Groceries
└── Urgent
└── Milk
还有其他树修改方法,
* `**copy_nodes**`:将节点从一个位置复制到目的地,节点将存在于两个位置
* `**copy_nodes_from_tree_to_tree**`:将节点从一棵树复制到另一棵树,节点将存在于两棵树中
# 导出树
正如文章开头提到的,`**bigtree**` 与 Python 字典和 pandas DataFrame 无缝集成。可以将树导出到字典、嵌套字典、pandas DataFrame 和更多格式。
## 将树打印到控制台
给定一个树,我们可以使用`print_tree`将树打印到控制台,并且能够指定要打印的属性和树的样式。更多定制也可在`[**bigtree**](https://bigtree.readthedocs.io)` [文档](https://bigtree.readthedocs.io)中获得。
from bigtree import print_tree
print_tree(root, attr_list=["age"], style="ascii")
"""
a [age=90]
|-- b [age=65]
| |-- d [age=40]
| +-- e [age=35]
| |-- g [age=10]
| +-- h [age=6]
+-- c [age=60]
+-- f [age=38]
"""
对于一个生成器的方法,可以用`**yield_tree**` 的方法代替。
## 将树导出到字典
给定一个树,我们可以使用`tree_to_dict`将树导出到一个字典中,能够存储所有属性的名称,或者使用字典将树属性映射到自定义属性名称。
from bigtree import tree_to_dict
tree_to_dict(root, all_attrs=True)
{
'/a': {'name': 'a', 'age': 90},
'/a/b': {'name': 'b', 'age': 65},
'/a/b/d': {'name': 'd', 'age': 40},
'/a/b/e': {'name': 'e', 'age': 35},
'/a/b/e/g': {'name': 'g', 'age': 10},
'/a/b/e/h': {'name': 'h', 'age': 6},
'/a/c': {'name': 'c', 'age': 60},
'/a/c/f':
}
tree_to_dict(root, attr_dict={"age": "AGE"})
{
'/a': {'name': 'a', 'AGE': 90},
'/a/b': {'name': 'b', 'AGE': 65},
'/a/b/d': {'name': 'd', 'AGE': 40},
'/a/b/e': {'name': 'e', 'AGE': 35},
'/a/b/e/g': {'name': 'g', 'AGE': 10},
'/a/b/e/h': {'name': 'h', 'AGE': 6},
'/a/c': {'name': 'c', 'AGE': 60},
'/a/c/f':
}
使用`dict_to_tree`方法可以重新构建原始树!
## 将树导出到数据框架
给定一个树,我们可以使用`tree_to_dataframe`将树导出到一个数据帧,能够将所有属性存储为列,名称不变,或者使用字典将树属性映射到定制的列名。
from bigtree import tree_to_dataframe
tree_to_dataframe(root, all_attrs=True)
path name age
0 /a a 90
1 /a/b b 65
2 /a/b/d d 40
3 /a/b/e e 35
4 /a/b/e/g g 10
5 /a/b/e/h h 6
6 /a/c c 60
7 /a/c/f f 38
使用`dataframe_to_tree`方法可以重新构建原始树!
## 将树导出为图像(等等)
给定一棵树,我们可以使用`tree_to_dot`将树导出到图像或其他图形或文件中。这个用的是引擎盖下的`[pydot](https://pypi.org/project/pydot/)`,用的是点语言,可以和 [Graphviz](https://www.graphviz.org) 接口。
from bigtree import tree_to_dot
graph = tree_to_dot(root)
graph.write_png("tree.png")
graph.write_dot("tree.dot")
graph.to_string()
在上面的代码片段中,`graph`属于`pydot.Dot`数据类型,它内置了写入 dot、PNG、SVG 等文件格式的实现。输出类似于图 3。
# 附加:通过`**bigtree**`使用待办事项列表
如果在这一点上,你仍然想知道你可以用`**bigtree**`、`**bigtree**`、**、**做些什么,它们带有一个内置的待办事项工作流,能够从 JSON 文件导入和导出。
这个待办事项列表实现有三个级别——应用名称、列表名称和项目名称。您可以将列表添加到应用程序或将项目添加到列表。举个例子,
from bigtree import AppToDo
app = AppToDo("To-Do App")
app.add_item(item_name="Homework 1", list_name="School")
app.add_item(item_name=["Milk", "Bread"], list_name="Groceries", description="Urgent")
app.add_item(item_name="Cook")
app.show()
To Do App
├── School
│ └── Homework 1
├── Groceries
│ ├── Milk [description=Urgent]
│ └── Bread [description=Urgent]
└── General
└── Cook
app.save("list.json")
app2 = AppToDo.load("list.json")
在上面的例子中,
* 应用名称指`To-Do App`
* 列表名称指`School`、`Groceries`、`General`
* 项目名称是指`Homework 1`、`Milk`、`Bread`和`Cook`
# 附加:延伸至 Trie
[Trie](https://en.wikipedia.org/wiki/Trie) 是一种 k 元搜索树,用于存储和搜索集合中的特定关键字,它源自单词 re **TRIE** val。Trie 可用于按字母顺序对字符串集合进行排序,或者搜索某个字符串是否有前缀。

图 Trie 示例—作者提供的图片
为了用 Trie 扩展`**bigtree**` ,我们可以给每个单词加上前导符号`**^**`和尾随符号`**$**`,用树搜索的方法找到一个带有`**find_path**`的特定单词或子单词。Trie 可以这样构造,
from bigtree import list_to_tree, find_path
bag_of_words = ["ant", "and", "angry", "buoy", "buoyant"]
list_of_paths = ["/".join(["^"] + list(x) + ["$"]) for x in bag_of_words]
list_of_paths
[
"^/a/n/t/$",
"^/a/n/d/$",
"^/a/n/g/r/y/$",
"^/b/o/y/$",
"^/b/o/y/a/n/t/$"
]
trie = list_to_tree(list_of_paths)
find_path(trie, "/".join(list("^boy$")))
如果使用`**tree_to_dot**` 方法导出,上面的代码片段会产生图 4 中的图像。
# 附加:有向无环图(DAG)
[有向无环图(DAG)](https://en.wikipedia.org/wiki/Directed_acyclic_graph) 是一种图形数据结构,其中每个节点可以有多个父节点。树被认为是图的限制形式。这种差异导致以下差异,
* 根:DAG 中没有根的概念,因为一个节点可以有多个父节点
* **深度**:DAG 中没有深度的概念,因为没有根节点
* **高度/最大深度**:DAG 中没有高度的概念,因为没有根节点
Dag 最适合用于表示工作流,因为工作流具有一定的顺序(定向),并且不会无限重复;没有循环(非循环)。
与树类似,`**bigtree**` 中的 Dag 可以手动构造,分别使用`**list_to_dag**`、`**dict_to_dag**`和`**dataframe_to_dag**` 方法从 Python 列表、字典或 pandas 数据帧中构造。
from bigtree import DAGNode, dag_to_dot
a = DAGNode("Ingest Data from Source A")
b = DAGNode("Ingest Data from Source B")
c = DAGNode("Clean data", parents=[a, b])
d = DAGNode("Feature Engineering", parents=[c])
graph = dag_to_dot(a, node_colour="gold")
graph.write_png("dag.png")
上面的代码片段产生了下图,

图 5:作者的 DAG 图像示例
希望你已经更好地理解了树结构以及如何使用`**bigtree**` Python 包来实现它们。如果有任何建议、反馈、错误或新的用例,请随时联系我们!
# 相关链接
`**bigtree**` 文档:[https://bigtree . readthedocs . io](https://bigtree.readthedocs.io)
`**bigtree**` 官方 GitHub:[https://github.com/kayjan/bigtree](https://github.com/kayjan/bigtree)
# Python 虚拟环境简介
> 原文:<https://towardsdatascience.com/python-virtual-environments-22c0d5f9689c>
## 它们是什么?我们什么时候使用它们?为什么?怎么会?

Billy Huynh 在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上拍摄的照片
您是否曾经在开始一个数据科学项目时发现没有安装合适的依赖项?到达代码块 2 时,发现您的 Pandas 版本与 Pandas-Profiling 不兼容,或者 Graphviz 无法导入到您的笔记本中,这是非常令人沮丧的。之前, [Manuel Treffer](https://medium.com/u/27ed7eb16994?source=post_page-----22c0d5f9689c--------------------------------) 发表了一篇与这个问题相关的[文章,主要关注 python 依赖冲突以及如何管理它们。作者列出了一些常见的问题以及解决这些问题的方法,但是我想重点介绍另一个可以解决更多问题的选项—管理虚拟环境。](/pythons-dependency-conflicts-are-depriving-data-scientists-of-sleep-ff52c5689336)
# 什么是虚拟环境?
作为初学者,您可能会像这样进行 python 编码——在全球环境中为您的所有数据科学项目安装所有必需的包。这样做有一些好处,其中之一就是简单。然而,一些缺点包括面临依赖冲突,更重要的是全球环境恶化。你有没有经历过你的包裹“再也找不到”的时候?如果是这样,你可能遇到了全球环境腐败,是时候找到一种新的方法来解决这个问题了。
[Python 文档将虚拟环境定义为](https://docs.python.org/3/tutorial/venv.html):
> 一个自包含的目录树,其中包含特定 Python 版本的 Python 安装,以及许多附加包
基本上,虚拟环境是您可以创建来隔离您的数据科学项目的东西。
# 我们何时使用虚拟环境,为什么?
这似乎是一个相当简单的问题,事实也的确如此。有一个最重要的答案…您正在处理多个基于 python 的项目。
例如,如果您某一天在做一个 NLP 项目,第二天又在做一个 CNN 项目,那么没有必要在一个环境中为这两个项目安装所有必需的包。相反,你可以为 NLP 创造一个环境,为你的 CNN 创造一个环境。
[Athreya Anand](https://medium.com/u/7947b7445b03?source=post_page-----22c0d5f9689c--------------------------------) 在他的文章[中很好地总结了这一点,即为什么你应该为每个 python 项目使用虚拟环境](/why-you-should-use-a-virtual-environment-for-every-python-project-c17dab3b0fd0)。如果你读了这篇文章,你会发现创建虚拟环境的主要原因是为了提高效率。
你的电脑就像一个公共图书馆。每次你在你的全球环境中安装软件包,你都在添加越来越多的书。最终,这导致了材料的混乱和冲突。想想看,如果你有一个图书馆,你会把它分成几个部分。你也可以定期检查书籍,以确保内容是最新的。你的电脑也一样。
当您在从事一个重要的数据科学项目时,您最不想浪费时间的事情就是纠正包的依赖性。这降低了工作效率,坦率地说,它真的磨我的齿轮。作为一名新的数据科学家,我花了一些时间来了解虚拟环境,但我可以向您保证,这是数据科学家工具包中的核心技能。因此,我想介绍一下虚拟环境是如何工作的。
# 如何使用 python 虚拟环境
创建和管理虚拟环境有几个选项,我们将在下面介绍:
1. [Venv](https://docs.python.org/3/library/venv.html)
这为创建虚拟环境提供了一个轻量级的选项。首先,您需要在命令行中运行以下命令:
python3 -m venv /path/to/new/virtual/environment
该命令使用新虚拟环境的名称创建一个目录,其中包含几个不同的项目:
* `pyvenv.cfg`
* `home`键
* 一个`bin`(或 Windows 上的`Scripts`)
* `lib/pythonX.Y/site-packages`子目录(在 Windows 上,这是`Lib\site-packages`)
一旦设置好这些项目,您的环境就被激活了,您可以开始工作了。
# venv 有哪些优点?
* 您并不局限于 python 的特定版本
* 您的代码是可复制的。如果有人想运行你的脚本,他们只需要创建一个具有相同需求的 venv。
* 全球环境不会被你并不总是需要的 python 包轰炸
# venv 有什么缺点吗?
占用了一些空间…但真的就这样了。如果你能腾出几百兆字节,那么这应该不是一个问题。
# 2.康达环境
如果您通常使用 anaconda navigator 或 conda,那么您可能更倾向于使用 conda 环境。这也很容易实现。
创建 conda 环境有几个选项:
* 运行以下命令行:
conda create — name myenv
* 使用一个`environment.yml`文件
* 使用 anaconda navigator 的 UI 来创建您的环境

作者形象
对于希望使用点击式界面来创建环境的新程序员来说,这是一个很好的选择。如果你想了解更多关于 Anaconda Navigator 的信息,点击这里。
# 3.Docker 容器
对于那些稍微精通 python 编程的人来说,你可能想看看 docker——特别是 [docker 容器](https://stephen-odaibo.medium.com/docker-containers-python-virtual-environments-virtual-machines-d00aa9b8475)。
根据[码头工人的说法,](https://www.docker.com/resources/what-container/)集装箱是
> “一个标准的软件单元,它将代码及其所有依赖项打包,以便应用程序能够快速可靠地从一个计算环境运行到另一个计算环境。”
这意味着 docker 有三个核心优势:
* **代码隔离**—将软件从全球环境中隔离出来,并确保它按照标准运行
* 独立性——Docker 容器确保其他人可以在另一个平台上运行你的代码
* **可移植性** — Docker 不需要每个应用都有操作系统,这提高了服务器效率,降低了成本
如果您在 Docker 映像中打包 python 应用程序,以下代码可用作激活虚拟环境的模板:
FROM python:[VERSION]
CMD ["python", "yourcode.py"]
RUN virtualenv /ve
ENV PATH="/ve/bin:$PATH"
RUN /ve/bin/[PIP INSTALL PACKAGES]
CMD ["/ve/bin/python", "CODE.py"]
# 使用 Python 虚拟环境:示例
现在您已经有了创建和激活虚拟环境(通过 venv、conda 和/或 docker)的基础,下一步是为一个新的 python 项目实践这个过程。
一个可能对实践有帮助的项目的例子是一个使用熊猫轮廓的项目。这是因为 pandas-profiling 依赖于 python、numpy、pandas 等的版本。你在吸毒。在这个简单的例子中,我将介绍如何使用 pandas-profiling 2.9,这要求我使用 python 3.8.15 而不是我通常使用的 3.10.9 来创建 venv。
我首先打开了我在[Data.gov(2020 年至今的犯罪数据)](https://catalog.data.gov/dataset/crime-data-from-2020-to-present)上找到的一个数据集。我在 VS 代码中的全局环境中使用了以下代码:
import pandas as pd
df=pd.read_csv('crime_data.csv')
df.head()
如您所见,这正如预期的那样有效:

作者形象
注意 python 版本在右上角(Python 3.10.9)。
当我试图导入熊猫概况时,我遇到了一个问题:

作者的错误图像
具体来说,这是最后一条错误消息:
ImportError: cannot import name 'ABCIndexClass' from 'pandas.core.dtypes.generic'
如果您在 google 上快速搜索一下这个错误,您会发现在使用 pandas-profiling 时,由于依赖项名称的变化,这个错误太常见了。要解决这个问题,最好在虚拟环境中工作,在虚拟环境中可以安装特定的 python 和包版本。
我用 Crtl+Shift+P 命令在 VS 代码中直接做到了这一点。我选择了创建环境。接下来,您可以选择 venv 或 conda:

作者图像
我创建了一个 conda 环境,用 python 3.8.15 命名为 pandas-profiling2

python 版本 conda 环境
一旦设置好了,我就在我的熊猫档案笔记本上运行代码,瞧!我在下面附上了一些熊猫简介报告的截图:

# 常见问题
1. 您如何激活您的虚拟环境?
要激活 python 虚拟环境,请运行以下命令:
source env/bin/activate
**2。如何停用虚拟环境?**
在命令行中运行以下代码
~ deactivate
**3。什么是需求文件?**
需求文件是所有项目依赖项的列表。要生成您的需求文件,请运行以下命令:
pip freeze > requirements.txt
创建需求文件后,您可以使用该文件运行 python 项目,而无需手动安装项目所需的单个包。
# Python 虚拟环境:为什么以及何时应该使用它们?
> 原文:<https://towardsdatascience.com/python-virtual-environments-why-and-when-should-you-use-them-be57b0c0323d>
## 使用虚拟环境。几乎总是如此。

由[马库斯·斯皮斯克](https://unsplash.com/@markusspiske?utm_source=medium&utm_medium=referral)在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 拍摄的照片
经常能听到初学者经常用他们的系统安装 Python,这是不对的。异端。你不应该使用你的系统安装,因为你可以做很多伤害。所以,不要。去吧。那个。
这是真的吗?我们应该永远不使用安装在我们系统中的 Python 吗?或者我们应该使用他们所谓的虚拟环境?本文讨论了何时应该使用虚拟环境,以及当意味着*总是*时,是否应该使用这个*。在讨论这个方面之前,我将不得不介绍虚拟环境,以便你们所有人掌握它们背后的基本思想。事实是,任何使用 Python 完成比计算器更高级任务的人都应该知道如何使用虚拟环境。*
我记得我第一次尝试使用虚拟环境。它们……它们令人沮丧——我的尝试和我的虚拟环境都令人沮丧。过了一段时间,我了解到它们之所以如此,是因为我使用的 IDE。它是哪个 IDE 并不重要。也许是我的错误,也许是 IDE 有问题——没关系。重要的是我自己无法解决这个问题。我决定检查 Visual Studio 代码,我的所有问题都消失了,就这样。一切都正常运转。
在本文中,我不会讨论创建虚拟环境和管理依赖关系的各种方法。相反,我将解释*为什么*我们应该使用虚拟环境,以及*何时*。我还将讨论它们和使用它们的一些基本方面。
然而,为了说明我的观点,我将不得不使用一些东西,我将使用`venv`。这是一个用于创建虚拟环境的流行且非常简单的工具,我发现它在大多数情况下都足够有效。当我开始我的 Python 冒险时,我确实有过几次沮丧的时刻。过了一段时间,我欣赏了`venv`的简单。我本可以更早了解它,但是我错过了一个关于虚拟环境的好资源。这是我写这篇文章的主要原因。我希望它能把许多初学者从这种沮丧和气馁的时刻中拯救出来。我也相信大多数初学者应该像我一样欣赏`venv`和它的简单。这就是为什么我们将在本文中使用它。
看完这篇文章后,即使是初级的数据科学家和 Python 爱好者也应该知道什么是虚拟环境,为什么应该使用它们,以及如何做。
# 什么是虚拟环境?
根据`[venv](https://docs.python.org/3/library/venv.html#venv-def)`的文档,
> 虚拟环境是在现有 Python 安装的基础上创建的,称为虚拟环境的“基础”Python,并且可以选择与基础环境中的包隔离,因此只有明确安装在虚拟环境中的包才可用。
让我们把它翻译成初学者的语言。假设你已经在电脑上安装了 Python。这是“现有的 Python 安装”,它安装在您的操作系统中。这也是您的系统安装,这意味着当您在 shell 中运行`python`(在 Linux 中,您可能仍然需要运行`python3`,以区别于 Python 2)命令时,这个安装将会打开(为了简单起见,让我们假设您的系统上还没有任何虚拟环境)。您可以使用这个现有的 Python 安装来创建一个虚拟环境。当您这样做时,它将成为您的虚拟环境的“基础”Python。
*注意*:当你的机器上安装了更多版本的 Python 时,在`python`命令之后打开的是系统 Python。请记住,您可以使用这些版本中的任何一个来创建一个虚拟环境。为了简单起见,我们不会在本文中使用这些附加版本中的任何一个;相反,我们将使用 Python 的系统安装。
现在假设您在 Python 的系统安装中安装了(来自 [PyPi](https://pypi.org) )附加包。为此,您可以在 shell 中运行以下命令:
$ pip install makepackage perftester0.4.0 easycheck0.3.3
这三个包只是例子:`[makepackage](https://github.com/nyggus/makepackage)`是一个创建新 Python 包的 Python 包,`[perftester](https://github.com/nyggus/perftester)`致力于 Python 函数的性能测试,`[easycheck](https://github.com/nyggus/easycheck/)`帮助你在代码内部编写可读的类似断言的检查(而不是`assert`语句,它不应该在生产代码中使用)。我使用它们,因为它们是我(共同)创作的。
这三个包现在可以在 Python 的系统安装中获得。因此,如果您运行 Python(我们仍然假设您的机器中没有 Python 虚拟环境),您将能够导入它们。

*关于包装安装的重要说明*。注意,我们用两种方式安装了三个包:`makepackage`安装时没有注明其版本;对于`perftester`,我们要求版本`0.4.0`,而对于`easycheck`版本`0.3.3`。这意味着无论何时以这种方式安装这三个包,`makepackage`将被安装在最新版本中,`perftester`将被安装在`0.4.0`版本中,`easycheck`将被安装在`0.3.3`版本中。

现在想象一下,在你当前的项目中,比如说在之前的*项目,你确实需要`perftester==0.4.0`和`easycheck==0.3.3`。你完成了项目,一切正常。过了一段时间,你开始第二个项目,比如说*之后的*项目,你需要`easycheck==0.5.0`。`easycheck`中从`0.3.3`到`0.5.0`的变化非常重要,因为处理消息的方式发生了变化。我们需要升级软件包:*
$ pip install --upgrade easycheck==0.5.0
你在之后完成*项目,一切正常。但是一个月后,你需要回到*之前的*项目。你运行这个项目,但是它并不像你在*之后开始*项目之前那样工作。为什么?*
*项目在*的应用程序之前已经改变了它的行为,因为你改变了 Python 环境!你改的是`easycheck`的版本。根据代码的不同方面,这样的小变化可能会导致项目中更小或更大的变化。在极端情况下(不一定使用`easycheck`,应用程序甚至可以完全停止运行。
因为在之前的*项目已经准备好了,你不能因为这么不重要的原因而改变它的代码。因此,你降低了`easycheck`的等级。降级后,*之前的*项目工作正常,就像以前一样……但是第二天你的老板要求你在*之后运行*项目的应用程序,所以你必须再次升级软件包;一切都很好。嗯,并不是所有的… *项目在*申请之前都不行…同时,`perftester`已经更新;您在系统安装中更新您的版本。*项目在*之前再……*项目在*之后……`makepackage`升级了……*项目在*之前、*项目在*之后、*项目在*之前、`makepackage`、*项目在*之后、`easycheck` …*
真是一团糟!
尽管只做两个项目,你的生活已经变成了一场噩梦。为什么?发生了什么事?
你是否注意到我们在一个单一的环境中操作,由我们安装在系统中的基础 Python 组成?这就是为什么我们不得不疯狂地降级和升级`easycheck`,每次我们都想在两个项目之间切换。
一定有更好的办法!
请注意,这里我们使用了一个环境,拥有*两个*环境,一个用于之前的*项目,另一个用于*之后的*项目,不是更容易吗?嗯……还有*还有另一个*用于`makepackage`,因为我们想用它为新项目创建包?如果我们开始另一个 Python 项目,为什么我们不能使用一个全新的环境…?*
这就是虚拟环境发挥作用的地方。
## 虚拟环境:本质
让我们抛开虚拟环境的技术细节。如果你是初学者,你不需要关于它们的太详细的知识——足以知道它们是什么,如何使用它们以及何时使用;如果您是一名高级 Python 开发人员,您可能知道所有这些,甚至更多。但是,如果您确实想要或需要了解更多关于虚拟环境的知识,您将需要寻找更高级的资源。如果你知道一些特别好的,请在评论中与我们分享,并告诉我们你能从他们那里学到什么关于虚拟环境的东西。
在本文中,我们将讨论虚拟环境的实用性。现在你已经知道它们是什么了,我想让你了解它们能提供什么,以及如何利用它们。我还想回答副标题提出的问题:为什么你应该几乎总是使用虚拟环境?
您可以将 Python 虚拟环境视为开发 Python 代码的项目环境。环境包括
* 特定版本的 Python
* 这个 Python 的标准库;
* 附加安装的 Python 包,无论是否在指定版本中。
当然,主要元素是 Python。你可以使用任何 Python 版本;如果你使用`venv`,这个版本必须安装在你的机器上。当然,标准库是附带的。请记住,虚拟环境的 Python 不必与您的系统安装相同。您可以使用不同的 Python 版本创建多个虚拟环境。在每个虚拟环境中,您可以安装任何包(从 PyPi 或任何其他包注册表,或者从本地文件)。
如你所见,虚拟环境使你的环境几乎是独立的。它不像 Docker 容器那样独立于操作系统。但是就 Python 及其依赖项而言,它是独立的。
让我们为上面的两个项目创建这样的虚拟环境。让我们假设您正在使用 Python 3.9.5 的系统安装。
***环境为*项目先于**
$ mkdir project_before
$ python -m venv venv-before
嗯…就这样?是的,就是这样,或者说差不多就是这样。这两行你已经创建了一个全新的虚拟环境,用 Python 和标准库。这个环境叫做`venv-before`,你可以看到它有一个专用的文件夹,名为`venv-before`。它的结构取决于你使用的是 Linux 还是 Windows。我建议你检查你的虚拟环境包含什么,因为这可以帮助你了解一些细节。你会发现基本 Python、标准库和外部包都在那里。
我们的下一步将是安装站点包。但首先,我们需要激活环境。如何做到这一点取决于操作系统:
------ Windows ------
venv-before\Scripts\activate
------ Linux ------
$ venv-before/bin/activate
从现在开始,shell 提示符应该显示环境是激活的,除非它的结构不允许显示这样的信息。默认情况下,每个 shell 提示符都显示此信息,例如,提示符开头的`(venv-before)`;下面,你会看到它的样子。
现在是时候安装我们需要的包了。在 Windows 和 Linux 中使用了相同的命令:
(venv-before) $ python -m pip install perftester0.4.0 easycheck0.3.3
这将安装当前版本的`perftester`(版本 0.4.0)和`easycheck`(版本`0.3.3`)。这些是之前*项目的要求。就是这样!在*之前,您的环境已经准备好用于*项目。*
请记住,一旦您在之前完成了*项目的工作,并且想要在项目之间切换,您需要停用您的虚拟环境。您可以从任何位置使用命令`deactivate`来完成:*
(venv-before) $ deactivate
停用有时在后台完成。例如,当您在一个环境中激活另一个环境时,在激活第二个环境之前,第一个环境会自动停用。
这种去激活是必要的。如果没有它,无论您做什么,比如安装一个新的包,都将在`venv-before`虚拟环境中完成,因此它会影响这个环境。
不管你是不是一个细心有组织的开发者,你都应该采取预防措施。一种方法是创建一个包含需求(您需要的依赖项)的文件`requirements.txt`,并将需求保存在那里:
requirements.txt
easycheck0.3.3
perftester0.4.0
这个文件被称为需求文件。如果您需要重新创建虚拟环境或将其安装在不同的计算机上(例如,团队中的所有开发人员都应该使用相同的虚拟环境),您可以使用下面的命令,而不是手动安装站点包:
(venv-before) $ python -m pip install -r requirements.txt
我们已经介绍了基础知识。这个主题还有更多:[代码打包](https://packaging.python.org/en/latest/)、`[pip-tools](https://github.com/jazzband/pip-tools)`、`[makepackage](https://gitbuh.com/nyggus/makepackage/)`,以及其他通常更高级的工具,比如`[poetry](https://python-poetry.org/)`和`[Cookiecutter](https://github.com/cookiecutter/cookiecutter)`。但是到目前为止,我们所学到的应该足够用于大多数初级和中级项目——有时甚至是高级项目。
有时你可能会遇到一些权限问题。你必须解决它们;它们不一定与 Python 相关,而是与您的操作系统和用户权限相关。
***环境为***后的项目
现在我们有了`venv-before`虚拟环境,我们可以在之前处理*项目,并使用生成的应用程序。然而,为了在*之后开发*项目,我们需要创建它的虚拟环境。让我们从根文件夹开始,这是我们希望项目所在的位置。*
(venv-before) $ deactivate
$ mkdir project-after
$ python -m venv venv-after
$ source project-after/bic/activate
(venv-after) $ python -m pip install easycheck0.5.0 perftester0.4.0
就是这样!我们现在可以在环境之间切换(通过激活您想要使用的环境),并在其中开发或运行应用程序。这正是虚拟环境存在的目的:使开发人员/用户能够在专用于特定项目的环境中工作。
我们现在应该创建第三个环境,比如说。它不会在项目中使用,而是为了创建新的 Python 包。我将把这个练习留给您:自己做并检查生成的虚拟环境是否工作正常。请记住,我们不想使用任何特定版本的`makepackage`,这基本上意味着我们将使用它的最新版本。
# 关于虚拟环境的更多信息
上面,我们已经介绍了虚拟环境的基础知识。通常,这些基础知识足以开发 Python 项目。在继续之前,我建议你花些时间练习这些基础知识。这将帮助你感受到在虚拟环境中工作的感觉。在几个项目之后,你应该对这种开发方法没有问题。
然而,虚拟环境还有更多。在接下来的内容中,我将讨论几个重要的问题,尽管我不会深入探讨它们,因为这会使文章变得太长太复杂,这是我想要避免的。我计划在以后的文章中讨论其中的一些问题。
## 包装
当我还是一名初级 Python 开发人员时,我曾经通过在虚拟环境中开发代码来开发 Python 应用程序——正如本文所描述的那样。往往你不需要别的东西;然而,有时你可能需要更多。
还有各种比这个基本方法更先进的方法。包装是其中之一——实际上,这是我最近在几乎所有项目中使用的一种方法。虽然看起来很复杂,但是打包在很多方面都是有效的。
在这里,我只想提一下包装代码,我会在另一篇文章中写得更多。如果你想了解更多,你可以阅读一下`[makepackage](https://github.com/nyggus/makepackage/)` [Python 包](https://github.com/nyggus/makepackage/)的文档,我创建这个包是为了让打包更简单。您可以使用它来创建 Python 包的结构。
## 属国
当您在虚拟环境中安装软件包时,它可以带依赖项安装,也可以不带依赖项安装。当它有自己的依赖项时,意味着它需要其他站点包才能工作,那么`pip install`将默认安装这些依赖项。
您应该记住这一点,因为当您使用命令`pip list`分析您的虚拟环境中已安装软件包的列表时,您将看到您已安装的软件包,以及它们的依赖项——以及这些依赖项的依赖项,等等。如果您需要创建虚拟环境的另一个实例(例如,在不同的计算机上),为了让您的环境正常工作,您可能需要确保所有这些依赖项与原始虚拟环境中的版本相同。
有很多方法可以实现这一点,比如`pip freeze`或`poetry`,有些方法比其他方法更好。在未来,我们将讨论其中的一些。
## ***糟蹋虚拟环境***
假设您在虚拟环境中开发应用程序。有一天,你需要尝试一个新的网站包;因此,您将它安装在环境中,并检查您的应用程序如何使用它。不幸的是,它并不像你预期的那样工作,所以你放弃了使用它的想法。一周后,您检查另一个站点包,这个故事又重复了一遍,所以它看起来没有用。
虚拟环境已经开始看起来像一个垃圾场,有所有那些您不需要的包,以及它们的依赖项…简单地说,您的虚拟环境被破坏了!
要清理这些乱七八糟的东西,你可以卸载你安装的软件包。您可以使用与安装包时相同的`pip`命令来完成这个操作,但是用`uninstall`代替了`install`。例如,要删除`perftester`,可以使用下面的命令
(venv-before) $ python -m uninstall perftester
这里不需要提供软件包版本,因为在虚拟环境中只能安装一个软件包版本。
这有——或者说可能有——一个问题。如果您删除的包有自己的依赖项,这些依赖项会被添加到虚拟环境中。的确,`perftester`确实有一个依赖,就是`memory_profiler`;更何况,`memory_profiler`有自己的依赖,`psutil`,也被安装了。当您删除`perftester`时,您既没有删除它的依赖项(`memory profiler`)也没有删除它的依赖项(`psutil`)!
毕竟,你的环境确实变成了垃圾场。我没有提到其他可能的问题,这些问题时有发生。例如,一个包可以有一个依赖项,它需要与我们已经安装的*版本不同,并且在我们的项目中需要*。这种冲突可能很难解决。
下面,我将解释当你已经接近开发的这个点时该如何继续:你的虚拟环境被破坏了。在我看来,这里预防是最好的方法,代表着 [*万无一失*](https://medium.com/codex/better-safe-than-sorry-2c9b6bfe5686) 的方法。虚拟环境不是很轻,但也不是很重。在 [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) 中,一个新的虚拟环境,没有任何站点包,占用空间约 7.3 MB 在 Windows 中,它是 15.3 MB。例如,安装`pandas`及其依赖项会将大小分别增加到 138 和 124 MB。
在使用虚拟环境的情况下,*安全胜于遗憾的*解决方案大致如下。当您想要检查环境的新版本如何工作时(例如,使用新的站点包),*创建一个新的环境*并在其中工作。一旦您决定使用这个包,就在实际的虚拟环境中安装它。如果没有,只需删除此环境。
这样,您将始终拥有一个工作和最新的虚拟环境。如果您需要检查一个需要对环境进行更改的新想法,请在一个新的环境中进行,并且:
* 如果想法实现了,你可以在主环境中做这些改变,去掉其他的虚拟环境;或者你可以把另一个环境当作主要环境。
* 如果该想法被拒绝,则移除另一个虚拟环境。
## 安装
如上所述,没有必要依附于虚拟环境。您可以轻松地删除一个并创建另一个。如果您的虚拟环境已经被破坏,只需删除它并创建一个新的。
这要求您跟踪虚拟环境中需要安装的所有依赖项。因此,您需要一个好的需求文件,以便您可以轻松地从头创建一个新的虚拟环境。
永远不要害怕创建新的虚拟环境和删除旧的虚拟环境。当我还是一个初学者时,我花了太多的时间试图修复我的虚拟环境。真的没有意义。虚拟环境*不是*解决方案的一部分—关于如何创建虚拟环境的信息是;也就是 Python 版本和依赖项。
简单来说,虚拟环境只是工具,你可以同时使用其中的几个。不需要依附于他们中的任何一个。因此,你应该不时地创建一个虚拟环境——经常比不经常好;它使您能够检查解决方案是否仍按预期工作。
如果代码在以前的虚拟环境中运行良好,但在新的环境中却不能运行,该怎么办?最有可能的是,这意味着我们错误地记录了虚拟环境的创建——在某个地方有一个 bug。为了发现这个 bug,您可以比较两个环境,因为前一个环境可能包含新环境所没有的依赖项。某些依赖项的版本也可能存在差异。
这样的错误时有发生,这就是为什么不时地用一个新的虚拟环境替换正在工作的虚拟环境是明智的。
# 使用 Python 的系统安装
上面,我们使用 Python 的系统安装只有一个目的:安装虚拟环境。但是 system Python 还有其他用途吗?
我遇到了一种激进的方法,根据这种方法,这是其唯一的*T2 应用程序,无论我们在 Python 中做什么,我们都应该在专用的虚拟环境中完成。同样激进的方法指出,您不应该在虚拟环境之外安装任何额外的包。*
事实果真如此吗?我们应该限制自己那么多吗?除了创建虚拟环境之外,我们是否应该如此小心,不要*使用*Python 系统安装?
老实说,我不完全同意这种说法。虽然我完全同意每个项目都应该有自己专用的虚拟环境,但我不认为如果我们需要做一些次要的事情,就一定不能使用 system Python。我确实时不时地使用 Python 系统安装——但从来不是为了重要的问题。
比如,有时候我想计算一些东西,或者看看如何用 Python 做一些事情。我真的应该为此安装一个新的虚拟环境吗?那岂不是矫枉过正了?当然,当我想检查给定项目的一部分时,我会使用项目的虚拟环境。但是假设我想回忆一下[如何用](https://medium.com/towards-data-science/should-we-use-custom-exceptions-in-python-b4b4bca474ac) `[.__ init __()](https://medium.com/towards-data-science/should-we-use-custom-exceptions-in-python-b4b4bca474ac)` [和](https://medium.com/towards-data-science/should-we-use-custom-exceptions-in-python-b4b4bca474ac) `[.__ str __()](https://medium.com/towards-data-science/should-we-use-custom-exceptions-in-python-b4b4bca474ac)` [方法](https://medium.com/towards-data-science/should-we-use-custom-exceptions-in-python-b4b4bca474ac)创建自定义异常类;或者[如何使用](https://medium.com/towards-data-science/parallelization-in-python-the-easy-way-aa03ed04c209)`[multiprocessing.Pool](https://medium.com/towards-data-science/parallelization-in-python-the-easy-way-aa03ed04c209)`;或者`.__repr__()`如何在类中工作;或者检查一下`dataclasses`是如何工作的;或者检查`{}`比`dict()`快多少,用`[timeit](https://medium.com/towards-data-science/benchmarking-python-code-with-timeit-80827e131e48)`;诸如此类…我是不是每次想做那种事的时候都应该创建一个虚拟环境?我不这么认为。相反,我简单地使用系统安装。
您实际上可以创建一个虚拟环境并将其用于此目的,而不是系统安装。您可以考虑这样一种方法,即使用虚拟环境来检查各种事情,这是一种额外的预防措施,比使用系统安装稍微安全一些。
在 Python 系统安装中安装站点包是另一回事。小心点。假设您想要安装`pandas`,这样您就可以启动一个交互式会话并对数据集进行探索性分析。在我看来,你应该在专门为数据分析而创建的虚拟环境中这样做;您可以在那里安装其他分析包,包括使您能够可视化数据的包。毕竟,`pandas`或`numpy`或你在那里安装的任何东西都可以随时更新,所以在虚拟环境中更新它们比在系统安装中更安全。
换句话说,我不认为你应该在任何情况下都不使用 Python 系统安装;无论我们在 Python 中做什么,我们都必须在虚拟环境中完成,没有任何例外。有时候我们可以放心地使用系统 Python。你为什么要这么做?简单来说,就是这样。然而,如果您想要做的任何事情都需要安装站点包,那就是另一回事了——建立一个新的虚拟环境,在那里安装包,并在其中做您需要的任何事情,这将会安全得多。
# 结论
您可以使用许多不同的方法来组织您的软件项目。Python 的基本工具是虚拟环境。尽管是基本的,虚拟环境提供了很多。一旦你知道如何使用它们,你将能够在没有更大问题的情况下用 Python 组织你的工作。随着时间的推移,您的编程技能将会提高,您将会学到更多组织项目的工具和技术。
然而,首先要做的是。学习 Python 时,要学会在虚拟环境中工作。不要推迟;从第一天开始学习。
虽然*虚拟环境*是描述同名概念的通用术语,但是您可以使用各种工具。前段时间,`[virtualenv](https://pypi.org/project/virtualenv/)`可能是最常用的工具。现在,它是`[venv](https://docs.python.org/3/library/venv.html)`。但是你也可以使用`[conda](https://docs.conda.io/en/latest/)`环境和其他工具。
在这篇文章中,我描述了`venv`。原因是双重的。首先,我认为它是初学者的一个非常好的工具。初学的时候,`venv`帮了我很多。当我开始使用它时,我的 Python 开发速度加快了。我没有*而不是*从第一天开始使用`venv`,我后悔了。但我很快就开始了,对此我很高兴。
第二,我找到我的工具`venv` *。我认为它足够轻便和简单。在开发我的开源包时,我使用虚拟环境,我用`venv`创建它们。当我在业内从事软件项目时,我也几乎总是使用`venv`。当我决定的时候,总是*,虽然我把`venv`和包装结合在一起。有时项目中的开发人员决定我们应该使用另一种工具,比如开发容器;我没意见。但是这样的工具要复杂得多,我不会向初学者甚至中级 python 爱好者推荐它们。**
*总结一下,我的建议如下。当你学习 Python 或者从事你的项目时,使用虚拟环境。要创建它们,使用`venv`。我甚至在数据科学项目中也这样做,尽管`conda`是数据科学家管理虚拟环境的常用工具。然而,我喜欢完全控制我编码的环境,而`venv`使我能够拥有它。*
*我不是说其他工具不好或者你不应该使用它们。但是我想说的是,我喜欢使用`venv`,并且认为它是初学者的好工具。你学习和使用其他工具的时间会到来,但是要慢慢来;不要着急。*
*感谢你阅读这篇文章。你不必同意我写的所有东西。也许你不喜欢`venv`或者和它有过不愉快的经历?如果有,请在评论中告诉我们。当你喜欢另一个工具时也这样做,但是不要忘记告诉我们为什么。*
# *资源*
** *<https://pypi.org/> <https://github.com/nyggus/makepackage> <https://github.com/nyggus/perftester> <https://pypi.org/project/virtualenv/> <https://github.com/cookiecutter/cookiecutter> <https://github.com/jazzband/pip-tools> *
# Python vs. Julia:这也是关于一致性
> 原文:<https://towardsdatascience.com/python-vs-julia-its-also-about-consistency-236812dd64ba>

由[克劳迪奥·施瓦茨](https://unsplash.com/@purzlbaum?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)在 [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上拍摄的照片
## 与 Python 相比,Julia 的主要优势无疑是它的速度和多任务分派等概念。但是还有更多:在日常使用中,一致性起着重要的作用。
学习和使用一种编程语言的容易(或困难)程度在很大程度上取决于该语言不同构造的一致性。高度的一致性也有助于避免错误,从而使其成为构建高质量软件的一个重要方面。在本文中,我将展示 Julia 和 Python 在这方面的表现。
以 *ranges* 和 *indexing* 为例,我将首先说明这两种语言在这些概念的用法上有何不同,然后解释其背后的基本原理。
# 范围
在编程中,由一个下限和一个上限指定的数字范围是一个经常使用的概念。让我们来看看它在两种语言中是如何应用的。
## 整数范围
如果我们需要,比方说一个从 4 到 10 的整数范围,这表示如下:
Python: **range(4,11) ** Julia: 4:10
在 Julia 中,当指定范围时,下限和上限是包含的,而在 Python 中,下限是包含的,上限是不包含的。所以我们必须给出一个 11 的上限,如果我们想让 Python 中的数字达到 10 的话。
## 数组中的一组元素
如果我们有一个数组`a`并且想要提取第 4 到第 10 个元素,我们必须写:
Python: a[3:10] Julia: a[4:10]
在 Python 中,数组(列表)的索引从零开始,所以第 4 个元素的索引为 3。同样,上限是唯一的。因此,要指定我们想要的元素一直到第 10 个元素(它的索引是 9),我们必须在那里放一个 10。
正如我们在这里看到的,在 Julia 中使用了与上面完全相同的表达式。而且不仅仅是完全相同的表达式,还有*相同的* (range)对象。它可以像上面一样“独立”使用,也可以作为一个数组的参数进行索引。
## 从中抽取随机数的范围
以下表达式用于生成范围从 4 到 10 的随机数:
Python: **randint(4,10) ** Julia: rand(4:10)
在 Python 中,上限在这种情况下是包含的,因此偏离了常见的概念。在 Julia 中,我们将上面介绍的*完全相同的* range 对象作为参数传递给函数`rand`。
## 引入步长
如果我们不希望*每个*元素都在一个范围内,而是例如每隔一个元素,这可以在两种语言中使用*步长*来指定(在本例中,步长为 2,而不是 1)。所以为了得到从 4 到 10 的每隔一秒的数字,我们写:
Python: **range(4,11,2) ** Julia: 4:2:10
在 Julia 中,这个扩展版本也只是一个范围对象。所以它可以用在每一个指定 range 对象的地方,特别是在上面的例子中指定一个数组的子范围或者指定从中抽取随机数的范围。
Julia: a[4:2:10]
rand(4:2:10)
这在 Python 中是不可能的。
## 其他种类的范围
当然不仅仅是整数的范围。下一个示例显示了 2022 年 4 月 8 日的三个**小时时间戳**(从 12:00 到 14:00):
Python: pandas.data_range(start='08/04/2022 12:00',
periods=3, freq='H')Julia: DateTime(2022,4,8,12):Hour(1):DateTime(2022,4,8,14)
可以看出,在 Julia 中,使用了与整数范围相同的基本语法(`lower:step:upper`)。
在 Julia 中,这也可以应用于其他数据类型,比如字符。这里我们指定了一个从‘d’到‘k’的字符范围:
Julia: 'd':'k'
这在 Python 中是不可能的。
# 索引
本文中使用的第二个示例演示了如何应用一致(或不一致)的概念,它是二维数组式数据结构的索引。
## 访问矩阵中的元素
为了访问一个矩阵`m`(一个二维数组)的第 2 行第 4 列的元素,我们写:
Python: m[1][3] Julia: m[2,4]
在 Python 中使用了列表的列表(因为在基本库中没有 *n* 维数组)。因此,索引这样的结构是一个两步过程(并且需要两倍的括号)。同样,在 Python 中,索引从零开始,第 2 行的索引是 1,第 4 列的索引是 3。
## 访问数据帧中的元素
要获得*数据帧* `df`(类似于数据库表的二维数据结构)第 2 行第 4 列的元素,我们必须编写:
Python: df.iloc[1,3] Julia: df[2,4]
在 Julia 中,这与上面的符号完全相同。
与 Python 中用来表示矩阵的列表相比, *DataFrame* 是真正的二维结构。因此指数可以写在*一对*括号内。
## 访问数据帧中的一系列元素
数据框中的每一列都有一个名称。这些名称也可以用来引用列。因此,如果我们希望将第 4 行到第 10 行的元素放在名为“A”和“B”的列中,我们编写:
Python: df.loc[3:10,[“A”,“B”]] Julia: df[4:10,[“A”,“B”]]
## 为矩阵元素赋值
为了给矩阵`m`的第 2 行第 4 列中的矩阵元素赋值 5,我们必须写出:
Python: m[1][3] = 5 Julia: m[2,4] = 5
## 为数据帧中的元素赋值
如果我们想将值 5 赋给*数据帧* `df`的第 2 行第 4 列的元素,使用以下表达式:
Python: df.iat[1,3] = 5 Julia: df[2,4] = 5
要将该值赋给“A”列第 2 行中的元素,我们编写:
Python: df.at[1,”A”] = 5 Julia: df[2,”A”] = 5
## 摘要
为了在 Python 中索引一个矩阵(这是一个列表的列表),使用了由两个括号对组成的符号,而对于数据帧,必须使用一个与方法调用结合的括号对。每种分度(`iloc, loc, iat, at`)都有不同的方法*。*
在 Julia 中,所有这些情况都有一个共同的符号:两个索引总是放在一对括号中。就是这样。
# 但是为什么呢?
我认为很明显,Julia 中更高程度的一致性使得这种语言更容易学习、阅读和使用。那么问题就来了:如果优势这么明显,为什么不用 Python(或者其他某种一致性程度较低的编程语言)来做呢?
简短的回答是:因为他们不能!
## 更长的答案
现在来看一个更长的答案:如果我们看一下幕后,那么例如在 Julia 中访问一个矩阵中的元素(比如`m[2,4]`)就转化为对函数`getindex`的调用,如下所示:
getindex(m, 2, 4)
带括号的符号只是这个函数调用的语法糖(由操作符重载启用)。这适用于所有的例子:
使用“getindex”访问值[作者图片]
这些函数调用都是用不同类型的参数完成的:

“getindex”的不同参数类型[图片由作者提供]
根据参数使用的数据类型,调用适当的`getindex`实现。你可能已经猜到了:这就是著名的*多重调度*的工作原理!
所以*多调度*最终有利于*一个*函数(`getindex`)的一致使用,可以应用于*多个*类似的变体。此外,它被裹在漂亮的衣服里(用括号表示);但这只是为了更好的可读性的包装。核心是基于多重调度。因此,没有这个概念的编程语言不能提供这种程度的一致性。
目前,仅 Julia 的基础库就有 220 种`getindex`变体。所以它确实允许这个概念的广泛应用。
## 赋值
向矩阵状结构的元素赋值的例子以类似的方式工作。他们用`setindex!`代替`getindex`。所以这里的机制是一样的。
使用“setindex”赋值[图片作者]
## 靶场的幕后
现在来看使用范围的例子:上面所有指定范围的表达式都表示类型为`UnitRange`(默认步长为 1)或`StepRange`(使用其他步长)的对象。两种类型都是抽象类型`AbstractRange`的子类型,如下图所示:

范围的类型层次结构[按作者分类的图像]
举个例子
* `4:10`是一个`UnitRange{Int64}`类型的对象(花括号中的`Int64`告诉我们,范围的下限和上限都是这个整数类型),
* `4:2:10`一个类型为`StepRange{Int64, Int64}`的对象(这里的边界和步骤都属于类型`Int64`)和
* `DateTime(2022,4,8,12):Hour(1):DateTime(2022,4,8,14)`
属于`StepRange{DateTime, Hour}`类型。
然后再一次*多重分派*来到游戏中:
* 正如我们在上面所学的,用`a[4:10]`访问一个数组的一系列元素会转化为`getindex(a, 4:10)`。因此有一个`getindex`的实现,它接受`AbstractRange`的一个子类型作为索引参数(除了使用整数的“经典”索引和相当多的其他变化)。
* 使用`rand(4:10)`从一系列数字中抽取一个随机数意味着有一个`rand`的实现(在其他版本中),它接受一个`AbstractRange`的子类型作为它的参数。
所以最终它也归结为*多重分派*的应用(它本质上依赖于 Julia 类型系统,可以由用户定义的类型任意扩展)。
## 和更多的句法糖
对于那些感兴趣的人来说:使用冒号如`4:10`或`4:2:10`的范围符号也只是普通函数调用的语法上更好的版本(同样基于操作符重载)。在这种情况下,它是功能`range`。
所以`4:10`实际上是`range(4, 10)``4:2:10`相当于`range(4, 10, step = 2)`(其中*步长*是可选的关键字自变量;这就是为什么在这个例子中关键字`step`是必要的)。
# 结论
使用像*索引*和*范围*这样的例子,我们可以看到事物可以在 Julia 中以非常一致的方式表达,这使得语言易于学习,易于阅读,最终也易于使用。
这种一致性是通过一些强大的概念实现的,如*多重分派*,一个*可扩展类型系统*和*操作符重载*,这些概念都经过精心制作,以便无缝地协同工作。
# Python vs R for Data Science:你做了正确的选择吗?
> 原文:<https://towardsdatascience.com/python-vs-r-for-data-science-did-you-make-the-right-choice-7d090dca4786>
## 两者对数据科学都有好处,但哪一个选项最适合你?

图片来自 Shutterstock,授权给 Frank Andrade
是的,Python 和 R 都是数据科学的好选择,但它们各有利弊。这意味着,如果你是数据科学的新手,一个选项可能比另一个更合适,如果你已经知道其中一个,学习另一个可能仍然是值得的。
使用 Python 和 R,您可以完成大多数您可以想象的数据科学任务,因此关于它们的能力没有争议,但是其他因素会使您选择一个而不是另一个。
一种工具可能对某些特定的任务更方便,对某些类型的用户来说可能比其他人更容易学习,可能会带来不同的工作机会,等等。
学习新事物是艰难的,所以确保你做出了正确的选择。在学习 Python 和/或 R for data science 之前,您需要了解以下一些事情。
## 你的背景是什么?
如果您是数据科学的新手,在 Python 和 R 之间做出选择的一个简单方法是考虑您的背景。如果你有多年的编程经验,学习像 Python 或 R 这样的新编程语言并不困难,但是如果你过去很少使用像 Excel 或 SPSS 这样的工具,情况就不同了。
让我们看看谁使用 Python 和 R,他们用它们做什么。
r 是一种由统计学家创造的编程语言,主要用于统计计算。也就是说,R 不仅被统计学家使用,还被数据挖掘者、生物信息学家和其他使用它们进行数据分析和开发统计软件的专业人士使用。
另一方面,Python 是一种通用语言,不仅用于数据科学,还用于构建 GUI、开发游戏、网站等。软件工程师、web 开发人员、数据分析师和业务分析师等专业人员使用 Python 来完成各种各样的任务。
总而言之,如果您来自 Excel、SAS 或 SPSS,R 可能更容易掌握,但是如果您已经用其他编程语言编写了一段时间的代码,并且已经形成了一种编程思维,Python 将更容易使用和习惯。
## 数据科学哪个更受欢迎?雇主在 Python 和 R 专家身上寻求什么?
在学习一个工具之前,它的受欢迎程度是需要记住的一个重要因素。相信我,你不会想学一些在现实世界中根本用不到的东西。
Google Trends 上的关键词“python 数据科学”(蓝色)和“r 数据科学”(红色)之间的快速比较揭示了过去 5 年全球对这两种编程语言的兴趣。

谷歌趋势
毫无疑问,对于数据科学来说,Python 比 R 更受欢迎。
另一方面,当谈到数据科学时,雇主在 Python 和 R 专家中寻求不同的东西。在包含术语数据科学和 R(但不是 python)以及术语数据科学和 Python(但不是 R)的招聘公告中进行的[比较揭示了在每组招聘公告中出现的最常见的数据科学工具和技术。](/r-vs-python-comparing-data-science-job-postings-seeking-r-or-python-specialists-2c39ba36d471)
在 [wordcloud](/r-vs-python-comparing-data-science-job-postings-seeking-r-or-python-specialists-2c39ba36d471) 中,我们可以看到带有数据科学和 R 术语的职位发布通常包括“研究”、“SQL”和“统计”等内容,而带有数据科学和 Python 术语的职位发布包括“机器学习”、“SQL”、“研究”以及 AWS 和 Spark 等工具。
## 哪一个为数据科学提供了最好的工具?
数据科学工作流涉及数据收集、探索和可视化等内容。虽然 Python 和 R 都可以完成这项工作,但是它们提供的工具和软件包各有利弊。
**数据收集:**R 和 Python 都支持各种各样的格式,比如 CSV 和 JSON,除此之外,R 还允许你将 Minitab 或 SPSS 中构建的文件转换成数据集。此外,两者都允许你从网站提取数据,以建立自己的数据集,但 Python 有更先进的工具,如 Selenium 和完整的框架,如 Scrapy。
**数据探索**:这是数据科学家花费大量时间的一个步骤,所以看看 R 和 Python 中使用的包。在 Python 中,我们主要使用 [Pandas](/a-python-pandas-introduction-to-excel-users-1696d65604f6) 和 [Numpy](/numpy-basics-for-people-in-a-hurry-8e05781503f) 来探索数据集,而 R 则有不同的为数据探索而构建的包。一张图片胜过千言万语,所以请查看在 [R](https://www.r-bloggers.com/2018/11/explore-your-dataset-in-r/) 和 [Python](/a-complete-yet-simple-guide-to-move-from-excel-to-python-d664e5683039) 中完成的这些简单的探索性数据分析,以更详细地了解所使用的工具。
**数据可视化**:在 Python 中,你可以使用 Pandas 库来制作[基本图形](/the-easiest-way-to-make-beautiful-interactive-visualizations-with-pandas-cdf6d5e91757),但是当你想要创建可定制的高级可视化时,你需要学习诸如 Matplotlib 和 Seaborn 之类的库。问题是它们可能很难学习(并记住它们的语法),而且用 Python 创建的可视化效果也不是最美观的。相比之下,数据可视化才是 R 擅长的。r 内置了对许多标准图形的支持,并提供了像 [ggplot2](https://www.youtube.com/watch?v=rfR9Nrpfnyg) 这样的高级工具来提高图形的质量和美观度。
## 那么应该学 R、Python,还是两者都学?
至此,你大概知道哪个才是最适合你的工具了,不过我来和你分享一下我认识的人都是怎么做的。
有些人选择 R 而不是 Python,因为它强大的面向统计的特性和强大的可视化功能,而其他人更喜欢 Python 而不是 R,因为它的多功能性和灵活性不仅允许他们执行强大的数据科学任务,而且还不止于此。
如果你已经知道一门,学习另一门是值得的,因为它们提供了不同的工作机会和工具。
用 Python 学习数据科学? [**通过加入我的 10k+人电子邮件列表,获取我的免费 Python for Data Science 备忘单。**](https://frankandrade.ck.page/26b76e9130)
如果你喜欢阅读这样的故事,并想支持我成为一名作家,可以考虑报名成为一名媒体成员。每月 5 美元,让您可以无限制地访问数以千计的 Python 指南和数据科学文章。如果你使用[我的链接](https://frank-andrade.medium.com/membership)注册,我会赚一小笔佣金,不需要你额外付费。
<https://frank-andrade.medium.com/membership>
# 为 Python APIs 和 ML 模型创建 Web UIs
> 原文:<https://towardsdatascience.com/python-web-apps-for-machine-learning-e29d075e6459>
## 快速制作原型并与其他人共享 web 应用程序。收集关于预测的反馈,以提高 ML 模型的性能。

由[基利安·卡蒂涅斯](https://unsplash.com/@kikisad?utm_source=medium&utm_medium=referral)在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 拍摄的照片
你如何与他人分享你的 ML 模型?尤其是和那些没有编程技能的人?
我们需要一个界面,不是吗?
一个友好的用户界面可以得到每个人的认同,并产生更多的反馈来改进。
以前,你需要 web 开发人员的帮助来将你的模型和 API 转换成应用程序。但是今天,数据科学家有许多选择来开发一个不离开他们的笔记本。
在过去,我曾经写过关于 Streamlit 的文章,这是一个非常好的工具。这部电影以格拉迪欧为特色。一个同样神奇的工具,为 ML 模型提供了一个非常酷的选择。
</how-to-create-stunning-web-apps-for-your-data-science-projects-c7791102134e>
这篇文章讨论了:
* 如何为你的 APIs Gradio 开发 UI 组件;
* 如何使用它部署 scikit-learn 模型;
* 如何在 Huggingface 上部署您的解决方案(免费),以及;
* 一个非常酷的选择。
# 用 Python 开发 API 的用户界面
Gradio 帮助我们在熟悉的 Python 环境中开发 UI 组件。它的声明式小部件库会让你有宾至如归的感觉。你也可以在笔记本上做。然而,首先,它非常简单。
</5-python-gui-frameworks-to-create-desktop-web-and-even-mobile-apps-c25f1bcfb561>
您可以从 PyPI 存储库中安装 Gradio。以下命令将带您开始。
pip install gradio
一旦安装完毕,再有三行代码就会把你的 API 转换成一个应用程序。
这里有一个例子。
上面的代码有一个在文本中查找回文的功能。
回文是前后相同的单词。该函数接受一个文本字符串并返回一个回文字符串。
该脚本还有一个 gradio 接口。它允许您以图形方式与函数进行交互。
在终端中输入:python3 app.py 即可启动 app。一旦服务器运行,您就可以访问浏览器来访问应用程序。
如果你在 Jupyter 笔记本上,运行代码块`app.launch()`就足够了。
python
运行上述命令后,控制台将打印 web 链接以访问您的应用程序。该应用程序将如下所示。

基本 Python(Gradio) web 应用——作者截图[。](https://thuwarakesh.medium.com)
你可以选择一个 API 来代替回文查找函数。
## 更多的小部件和更好的配置选项。
Gradio 可以灵活地将多个小部件作为输入和输出。这些小部件可以是文本、图像、音频文件、滑块等。您还可以显示数据框架和图表。
下面的例子使用了一堆其他的小部件。

Python (Gradio)应用程序有更多的小部件。每个小部件都接受参数来进一步配置其行为。—作者截图[。](https://thuwarakesh.medium.com)
这不是可用选项的完整列表。有关您可以使用的组件的详细列表,请参考文档。写这篇文章的时候,他们有 25 个人!
# 使用 Gradio 部署 Scikit-learn 模型
Gradio 也是部署您的 ML 模型的一种方式。
在一个扩展的 ML 应用程序中,你会有一个更复杂的安装。但是一切都应该从小事做起。在我看来,你可以从 Gradio 开始测试你的模型的性能。
Python 开发者大多使用 [Scikit learn](https://scikit-learn.org/stable/) 来创建和训练 ML 模型。您可以将训练好的模型保存为 pickle 文件供以后使用。以下是如何处理模型并将其保存在文件系统中的方法。
现在,您可以在函数中使用它并创建预测服务。Gradio 将负责为您的模型构建一个界面。
gradio 应用程序接收温度和湿度,并返回降雨量。当用户点击按钮时,应用程序调用 predict _ rain 函数,该函数使用 pickled 模型来预测降雨。

在 web 应用程序中包装机器学习模型的演示。—[作者](https://thuwarakesh.medium.com)截图。
# 部署 Gradio 应用程序 Hugginggace
Huggingface 是一个构建、训练和部署 ML 模型的平台。这也是一个数据科学家相互交流的全球社区。
如果你想和别人分享你的应用, [Huggingface](https://huggingface.co/) 是最好的选择。如果你是一个组织的成员(在 Huggingface 中),你只能在组织内部分享你的作品。如果没有,你可以选择私有或者公开。
您可以通过从 Huggingface 主页创建新空间来开始部署。
您还可以在不同的可用许可类型中进行选择。这些都是有用的,尤其是如果你在公共领域下制作你的应用。

将 Gradio 应用程序部署到 Huggingface 空间。—截图作者[作者](https://thuwarakesh.medium.com)。
在发布更改方面,Huggingface 与 git 没有太大的不同。事实上,您将使用本地 git 在云上更新您的应用程序。
让我们克隆 Huggingface 存储库,并将我们的应用程序代码转移到新的 repo 中。然后我们可以通过推送代码在 Huggingface spaces 上发布应用。
$ git clone https://huggingface.co/spaces/Thuwarakesh/predict_rainfall
Replace the URL with your huggingface space url.$ mv app.py requirements.txt predict_rainfll # to your folder$ cd predict_rainfall
$ git add app.py requirements.txt
$ git commit -m 'Create rainfall predictor'$ git push
以上步骤将在 Huggingface spaces 上创建一个新的 Docker 容器,并部署您的应用程序。如果您在浏览器上访问共享空间的 URL,就会看到您的应用程序。

Huggingface 中托管的 Gradio 应用程序—作者截图[。](https://thuwarakesh.medium.com)
如果您选择将空间公开,您可以与全世界共享 URL 并展示您的新 ML 应用程序。
# 在您的 ML 应用上获得用户的实时反馈。
有经验的 ML 工程师知道,构建和部署机器学习模型是一个迭代的过程。改进模型的核心是反馈回路。
<https://levelup.gitconnected.com/concept-drift-in-machine-learning-1fc7a4396b10>
Gradio 的创建者在设计时就考虑到了反馈收集。除了输出,您还会看到一个 Flag 按钮。如果那些测试你的应用的人觉得预测是错误的,他们可以标记它。

接收关于最大似然预测的反馈。Gradio 有标志按钮来报告错误的预测。—[作者](https://thuwarakesh.medium.com)截图。
Gradio 在文件系统上以 CSV 格式保存标记的记录。它记录所有输入值和预测以及其他相关信息。

Gradio 将标记的预测收集到一个 CSV 文件中——作者 T4 截图。
标记的记录是需要 ML 工程师注意的例外情况。这些是您的模型中表现不佳的区域。ML 工程师可以在审核标记并根据问题的重要性进行权衡后采取纠正措施。
# 最后的想法。
作为数据科学家,我们需要快速构建工作原型。原型会经常改变。在开发阶段,将 JavaScript 框架和其他技术引入堆栈是不明智的。
借助 Streamlit 和 Gradio 等工具,我们现在可以为我们的 ML 模型构建快速而肮脏的应用程序。Gradio 特别适合 ML 项目,因为它收集反馈。
此外,Gradio 和 Streamlit 都帮助我们在 Huggingface 上发布应用程序。这让我们的应用覆盖了更广泛的受众。
这两个工具都允许我们在舒适的笔记本环境中构建应用程序。数据科学家可以构建专业外观的应用程序,全部用 Python 编写。
> 感谢阅读,朋友!在 [**LinkedIn**](https://www.linkedin.com/in/thuwarakesh/) 、 [**Twitter**](https://twitter.com/Thuwarakesh) 、 [**Medium**](https://thuwarakesh.medium.com/) 上跟我打招呼。
>
> 还不是中等会员?请使用此链接使 [**成为**](https://thuwarakesh.medium.com/membership) 的会员,因为,在不为你额外付费的情况下,我为你引荐赚取一小笔佣金。
# Pythonflow:从渴望到图形 Python 编程
> 原文:<https://towardsdatascience.com/pythonflow-from-eager-to-graph-python-programming-6ee51fb9779f>
## Spotify 库概述

照片由 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的 [Clément Hélardot](https://unsplash.com/@clemhlrdt?utm_source=medium&utm_medium=referral) 拍摄
正如开发者在官方[文档](https://pythonflow.readthedocs.io/en/latest/index.html)、**中所报道的,Pythonflow** 是一个数据流编程的 Python 库,其语法与 **Tensorflow** 在图形类型上非常相似。这个库,简单但在我看来极其强大,是为人工智能模型预处理流程(管道)而设计的。在最初的目标中,出现了:
* ✅ **管理**特别繁重的逻辑和数学运算链
* ✅能够执行图中包含的子操作,具有强大的**调试**和计算优化潜力
* ✅并行化、**分布式计算**,以及跨多台机器的工作负载分割
但是让我们后退一步,理解我是如何以及为什么会遇到这个工具的。
## 图形执行和数据流编程:为什么
我遇到这个工具是一种需要的结果。作为一名 Python/AI 工程师和开发人员,经常需要优化自己的工作流程。
在为工程界开发人工智能算法的过程中,肯定会遇到一些这样的问题:
* ⏰**优化**开发计算时间
* 💻保存过去完成的工作,并在新的问题上利用它
* 🎯**管理源自点云、视频流、图像的大型数据集**
工作流水线可以被解释为一系列相互连接的连续操作,其中每个操作都是独立的,能够读取输入数据流并输出多个输出。从脚本优化的角度来看,数据流编程允许轻松地将工作管道从一个项目转移到另一个项目,只改变一些输入参数(我称之为**占位符**和**常量**)或链的一个或多个操作。
就**开发时间**而言,处理大量数据可能是一个大问题,这个问题在初始开发阶段最为明显,在此期间,在远程机器或云上启动我们的批处理工作管道之前,操作将被一个接一个地调试以验证其有效性。使用图形,我们可以完全简化编程、导出和整个图形,并仅在需要时部分或完全执行它。
此外,**连接多个图形**的可能性成倍地增加了这种编程的通用性。
> 目标是开发**我的管道**,通过一次编程,然后在未来的新项目中重用它们,简单地使用过去制作的子图组装一个新的整体图,旨在作为一系列工作的、独立的和互连的操作。

照片由 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的 [Thibault Penin](https://unsplash.com/@thibaultpenin?utm_source=medium&utm_medium=referral) 拍摄
语法,正如我们将看到的,非常简单,但我认为这个库的真正有效性可能来自它的图形包装(我正在研究,将来会告诉你)。它的脚本使用被开发人员变得非常简单和直观,但是对于复杂工作流的参数化实现来说,语法往往变得沉重和乏味,特别是如果像我一样,您需要连接几十个不同的图形。
## Pythonflow 库:主要特性
安装非常简单:作为 pip 上的一个包,准备好我们的 python/conda 环境(或 Colab 笔记本)就足够了。
(venv) >>> pip install pythonflow
我预料到了我基于数据流进行 python 编程的原因。但是,这涉及到编程模式的改变,可以总结为两个步骤:
* 📈定义定义我们工作流的操作的 **DAG** (直接非循环图)
* 🏃🏼♂️ **通过给占位符赋值来评估图形。**
图( **DAG** )由一系列用边连接的节点(操作)组成。
<https://en.wikipedia.org/wiki/Directed_acyclic_graph#/media/File:Tred-G.svg>
> 当 Pythonflow 对一个操作求值时,它会检查当前的`context`是否为该操作提供了一个值,如果可能就立即返回。如果当前的`context`没有为操作提供值,Pythonflow 将评估操作的依赖关系,评估感兴趣的操作,将计算出的值存储在上下文中,并返回该值。
在该图中,可以实例化**操作**、**占位符、**或**常量**,每一个都用唯一自动生成的标识符或我们指定的名称进行唯一标识。
> Pythonflow 将强制要求名称确实是唯一的。
* ⚖️ A **常量**是我们图形中的一个参数,我们不希望通过外部调用以任何方式改变它
* 💡一个**占位符**是一个我们希望在调用我们的图形时动态变化的参数
* 🐍一个**操作**是任何 Python 函数、语句、类...
这最后一点在我看来是最有趣的。可以执行简单的 Python 构建,也可以包装任何库(甚至是人工智能库,如 Tensorflow 或 Pytorch ),并创建复杂的类和宏来组成我们的图。
## Pythonflow 的多功能性
该库允许我们用三种不同的复杂程度包装 python 代码。
> 我们可以使用`pf.func_op`类从一个可调用对象创建一个操作。
必须考虑的一个方面是,操作只执行一次,然后保存在缓存中。这一方面极大地优化了我们的代码,尤其是在运算量很大的情况下。
同时,如果我们需要包装我们的一个函数,可以使用一个<https://peps.python.org/pep-0318/>****。`opmethod`的参数不是强制性的:指定`length`参数可以解包操作。****
****我认为这是这个库的真正潜力,也就是将我们的图形片段组合成一系列复杂静态操作的可能性。为此,我们需要执行 pf.operation 类的子类化,如下例所示。****
****在这个简单的例子中,我创建了一个操作,将来自 **TensorFlow 的模板作为参数传递。Keras.models.Model** ,然后用指定的设置进行编译。****
****在工作流程的开发中,根据我们指定的参数(占位符),可能需要执行图形的一部分或另一部分。Pythonflow 允许您使用 *pf.conditional* 处理条件操作,只执行您需要完成的那部分图形。****
****就像在我们的日常代码中一样,我们将需要**处理异常**,并且只在某些部分执行失败时才执行部分代码****
****如上所述,该库自动且有效地定义了要执行的操作序列。然而,在某些情况下,指定依赖链可能是有用的。例如,官方文档中的示例显示了如何使用该功能进行调试。****
****让我们来谈谈**断言:**你要确保一切都按预期运行。让我们再次检查一下官方文档示例:****
****对我来说,执行代码的一个关键方面是监控执行时间和瓶颈。为此,该库实现了几个方便的分析系统。这些回调(上下文管理器)必须接受两个参数:被分析的操作和上下文。****
# ****创建管道:使用 Tensorflow 进行培训****
****在这一节中,我提出了一个非常简单的例子,这个例子是我为我们的人工智能管道应用 Pythonflow 而做的,展示了我认为的优势。****
****你可以在这里找到完整的示例代码。目标是展示如何创建一个简单的 DAG(带有一些额外的功能)专门用于人工智能,生成一个参数序列模型,并使用 Keras Functional API 对一些简单数据进行训练。****
****<https://github.com/GitMarco27/TMML/blob/main/Notebooks/012_pythonflow.ipynb>
和往常一样,我使用 Google Colab 创建简短的代码和示例。默认情况下不会安装 Pythonflow,所以您需要安装这个依赖项。
## 定制模块化张量流运算
如上所述,利用 Pythonflow 开发者提供的不同语法,有可能以几种方式实现我们的直接非循环操作图。在这个例子中,我使用了 pf 的继承。操作类来实现我的工作流链中的三个基本操作:
* 创建参数化 Keras 模型(带有密集层)
* 编译模型
* 对一系列时期的(虚拟)数据进行训练
注意,需要多次指定我们自定义操作的参数:在 **__init__** 中,在 **super、**中,然后在 **_evaluate** 函数中。然而,在后者中,可以用我们熟悉的 Python 语法指定任何操作链。
* 第一个函数允许将一个 [**密集**](https://keras.io/api/layers/core_layers/dense/) 层添加到传入的通用模型中,具有指定数量的神经元和激活函数。
* 第二个类**用默认参数编译**我们的模型:优化器(Adam)和损失函数(mse)。
* 最后一个以参数化的方式执行通用模型的**训练**:它读入特征、标签、训练时间和批量。
这个琐碎的例子是可以优化改进的,但是在我看来,一个致胜的理念浮现出来了:e **非常定制的操作,做好了就是独立。它读取一组输入(依赖项)并返回一组输出,在几个工作链中变得可重用。**
## 图形生成
对于 **DAG** 的生成,有必要定义一组占位符、操作、常量以及它们之间的交互。显然,没有必要为占位符指定一个值,因为只有在执行图形时才会给它们赋值。
由于 *pf.func_op* ,输入层被实例化,这是一种特别方便包装单个可调用程序的语法,而 Tensorflow 模型(Keras)的生成、编译和训练的剩余部分是通过前面介绍的自定义操作来执行的。
在这个简单的例子中,数据是在图中生成的(使用 numpy 随机生成输入/输出集)。
> **注意**:一个优点是可以生成整个图,但是调用图内的任何中间对象总是可以的。Pythonflow 将确定依赖关系链,并且在计算上只高效地执行获得该值所必需的操作。
## **Extra n 1** :嵌套图。
在给出的代码中,输入数据的大小是通过**另一个图**指定的:这允许我展示如何以一种**递归方式**链接操作图,释放与我们工作链的模块化相关的大量潜力。
## 运行我们的图表
要执行该图,必须指定要提取的值(通过在图生成期间指定的名称)、依赖链中所需占位符的值以及任何分析器和回调。
让我们检查一下模型摘要:
或者开始虚拟数据的训练:
## 额外的 n 2: pickle 并重新加载图表
这个库使得使用 **pickle** 导出我们的图形变得容易。
我发现这非常有用:例如,让我们考虑在我们的本地机器上创建一个复杂的模块化操作图:我们可以**导出图**并且**使用简单的命令在**远程机器**上执行**。
## 额外的 n 3:在单独的线程上运行
Pythonflow 允许您在一个**外部线程**上运行您的图形,从而允许您利用进程**并行化**和**避免在运行长工作链时冻结**我们的应用程序。
在上面的例子中,我们在一个单独的线程上运行简单的基于密集的模型的生成、编译和简单训练,监视进程的数量。如果需要,我们可以存储进程的 PID 来处理它。
# 总结
在本文中,我们已经讨论了 **Pythonflow** ,它的潜力,语法,并且我们已经从官方文档中看到了一些例子。然后,我们看到了一个生成人工智能工作流的简单示例和一些在我们日常工作中有用的额外功能。
📚在我的[个人资料](https://marcosanguineti.medium.com/)上阅读更多文章
📨成为电子邮件订阅者,了解最新动态
💻加入媒体没有阅读限制
✏️对疑点或新内容的评论
下次见,马可****
# Python 的 F 字符串比你想象的要有用得多
> 原文:<https://towardsdatascience.com/pythons-f-strings-are-a-lot-more-useful-than-you-might-have-thought-54a2ca6d2df8>
## 大多数人都没有意识到 f 字符串在 Python 中可以做的一些很酷的事情

(图片由 [Pixabay](http://pixabay.com) 上的 [StarzySpringer](https://pixabay.com/images/id-2667529/) 提供)
# 介绍
我们很可能都熟悉弦乐;自计算历史开始以来,大多数编程语言中都有这种核心数据类型。字符串很棒,因为它们能够用 ASCII 或 Unicode 表示一系列对人类来说非常明显的字符。作为人类,我们可能不会说组成 unicode 字符的 0 和 1 的语言,但字符串是完全可以理解的。
如果您用来编程的编程语言本质上至少是高级的,那么您可能也熟悉字符串插值的概念。字符串插值是编程语言通过定义字符串来读取和修改字符串的能力。在 Python 中,像这样的字符串插值是通过一个叫做 F 字符串的特性来完成的。F 字符串允许你用字符串做很多事情,有些人甚至没有意识到 Python 中 F 字符串的全部功能。幸运的是,互联网上有一些随机的博客作者可以为你的大脑深入探究 F 弦,这就是我今天要做的。如果你想与接下来的代码进行交互,我在这个小项目中使用的笔记本也可以在 Github 上找到:
<https://github.com/emmettgb/Emmetts-DS-NoteBooks/blob/master/Python3/f%20strings.ipynb>
# 什么是 F 弦?
在我们讨论 F 弦能做什么之前,我们先简单了解一下 F 弦到底是什么,这可能很重要。在 Python 中,普通字符串是不插值的。这当然可以帮助语言减少一些延迟,因为它是被解释的。我认为这也是一件好事,因为没有隐式插值的理由,它可能会导致比它解决的问题更多的问题。例如,在 Julia 中,字符串总是内插的,所以有可能你试图创建一个字符串
x = 5
mystr = "and I said for $x dollars in my wallet, I will buy one icecream"
当你想要 x 美元的时候却意外地得到了 5 美元。不确定所有这些会有多频繁地同时发生,但这肯定是**可能**发生的事情。无论如何,Python 中的 F 字符串是通过在字符串前提供一个 F 来表示的,就像这样:
f""
F 字符串最明显的用法是在 throws,output,等等里面插入简单的值。这是通过使用{}语法并提供值名来完成的。当然,如果可能的话,该值也将被隐式转换。
x = 5
f"One icecream is worth {x} dollars"
这会产生一个实际上看起来更像这样的字符串:
'One icecream is worth 5 dollars'
# 更多关于 F 弦的信息
我们已经确定了 F 字符串可以用来插入 Python 代码,但是它能做什么呢?我们可以对 F 字符串做的一件值得注意的事情是提供一种“this = that”类型的插值,这可能对调试和错误处理有用。我们只需添加一个等号:
f"If you are wondering what x is, {x=}"'If you are wondering what x is, x=5'
我们可以利用的另一件事是所谓的转换。转换允许我们在插值之前对其应用函数,例如
* !r —显示字符串分隔符,调用 repr()方法。
* !a-显示字符的 Ascii。
* !s —将值转换为字符串。
这些都很有价值,只要打个电话就能发挥作用!r 在最后,经常!s 是不需要的,因为通常我们打印的数据类型,正如我提到的,是隐式转换的,不是典型的结构,但在某些情况下,你可能需要提供这个。以下是 repr 版本的使用示例:
food = "cabbage"
food2brand = "Mcdonalds"
food2 = "French fries"f"I like eating {food} and {food2brand} {food2!r}""I like eating cabbage and Mcdonalds 'French fries'"
我们也可以使用对象格式化来改变插值的格式。Python 中的每个对象都可以决定它们的格式在字符串中的表示方式。为了表示这就是我们想要做的,我们使用了一个冒号,后跟我们想要添加的格式,例如在这里我们这样做是为了日期-时间。
import datetime
date = datetime.datetime.utcnow()
f"The date is {time:%m-%Y %d}"'The date is 02-2022 15'
这将调用 __format__ 方法,该方法至少提供给大多数类。
# 结论
所以你可以用 F 弦做一些很棒的事情。一般来说,大多数人认为 F 字符串的功能非常有限,但是它们确实可以做很多不同的事情。字符串在 Python 中是一个非常重要的概念,虽然 Python 可能不是元编程和将字符串解析为代码的最佳语言,但在 Python 的这个领域肯定也有用例。非常感谢您的阅读,并快乐插值!
# Python 的 Map()、Filter()和 Reduce()函数解释
> 原文:<https://towardsdatascience.com/pythons-map-filter-and-reduce-functions-explained-2b3817a94639>
## **用代码示例、数学和表情符号展示 Python 中函数式编程的构建模块**

Filter()是 Python 中可以使用的三个高阶函数之一,它允许为您的代码注入函数式编程结构
从根本上说,Python 是一种**面向对象的编程语言**,建立在命令式编程范式之上。当执行一系列语句时,通常使用如下结构(for-loop ):
my_list= [17.43, -0.99, 6.81, -12.25, 4.52]
my_list_squared = []for i in my_list:
i_squared = i**2
my_list_squared.append(i_squared)
通过对感兴趣的对象(原始列表)执行操作,每条语句都使我们离期望的最终结果(在本例中是平方数列表)更近了一步。
或者,我们可以构建一个**列表理解**来用更少的代码行完成同样的任务:
my_list_squared = [i**2 for i in my_list]
在这两种情况下,*数据*是中心,对`my_list` 执行重复操作以达到结束状态。
在函数式编程中,事情有点不同。与 OOP 不同,它依赖于声明性编程模型,其中*函数*是核心。尽管**不是函数式编程语言**,Python 确实包含了几个强大的函数来迎合这种类型的编程。
在**功能表**中,使用`map()`功能,上述程序如下所示:
my_list_squared = list(map(lambda i: i**2, my_list))
本文(非常)简要地讨论了函数式编程的概念及其潜在的好处,并描述了在 Python 中应用函数式编程原则的三个关键构建块——`map()`、`filter()`和`reduce()`函数。
# **什么是函数式编程?**
函数式编程的本质属性是它强调我们执行的操作,而不是我们执行操作的对象。对于我们在相对有限数量的对象上执行**许多不同操作**(即许多功能)的问题,这可能是首选的编程范例。
函数式编程的核心是**高阶函数**。这些函数接受另一个函数作为输入,使它们成为强大的通用表达式。用数学术语来说,人们可以认为是一个导数 *d/dx* ,以函数 *f* 作为输入。本文讨论了 Python 中可以使用的三个关键高阶函数:`map()`、`filter()`和`reduce()`。
尽管首选方法严重依赖于问题,但函数式编程比面向对象编程有几个(潜在的)好处。每个函数代表一段独立的代码。它接受输入,转换输入,然后输出。这种简洁的模块化结构通常可以简化调试和维护。此外,其固有的惰性评估(仅在请求时产生输出)可以更加节省内存。最后,函数式编程通常会简化并行化。
当使用**许多数据操作和少量“事物”**(例如,相对少量的变量、集合等)时,函数式编程可能更可取。).相比之下,当“事情”很多但操作相对较少时,OOP 往往会工作得更好。请记住,两者通常都能完成工作,并且许多编程语言都包含了两者的元素。
虽然越来越受欢迎,但与 Python 或 C++等广泛使用的 OOP 语言相比,LISP 或 Haskell 等函数式语言的采用有限。然而,OOP 语言越来越多地**结合了函数式编程**的元素,从而可以应用类似的结构。
既然已经介绍了函数式编程的基础,是时候转向具体的 Python 实现了。
# **地图()**
作为高阶函数,map 函数将另一个**函数和一个可迭代的**(例如,列表、集合、元组)作为输入,将该函数应用于可迭代的,并返回输出。它的语法定义如下:
> 映射(函数,可迭代)
对于那些喜欢数学的人来说,考虑从域 *X* 到域 *Y* 的映射可能更方便:
> f:X →Y
或者:
> f(x)=y,∀x∈X
对于视觉学习者来说,一个 map() — *的表情符号示例归功于*[*global nerdy*](https://www.globalnerdy.com/2016/06/23/map-filter-and-reduce-explained-using-emoji/)*对于灵感*——可能更有帮助:
> *地图(库克,[* 🐷*,🌽,*🥚,🍠 *]) → [* 🥓*,🍿,*🍳,🍟
*输出 *y* 是一个 **map 对象**(一个 iterable),我们仍然需要将它转换成一个集合或列表。要结合这两个步骤,我们可以简单地将 map 语句包装在里面,例如,`set()`函数:`my_set=set(map(function, iterable))`。*
*让我们举一个具体的例子。假设我们有大量的字符串,我们想用**将**和 *"_2022"* 连接起来。数据科学充满了这种需要在大型数据集上执行的相对简单的操作。对于`map()`,我们可以表达为*
string_map_22 = map(lambda my_string: my_string + '_2022', set_of_strings)
*这种工作方式非常强大——只需一下子,我们就可以更新整个集合!但是不要忘记将贴图对象转换为集合:*
set_of_strings_22 = set(string_map_22)
*再来一杯?假设我们想在一个薪水列表上转换一个**对数转换**。尽管使用 lambda 函数会产生简洁的代码,但为了提高效率,建议**使用`def`来预定义函数**。此外,我们现在直接将`map()`函数包装在一个`list()`函数中:*
def get_log_value(salary: int):
return np.log(salary)log_salaries = list(map(get_log_value, salaries))
# ***过滤器()***
*与`map()`类似,`filter()`高阶函数将一个函数和一个可迭代函数作为输入。该函数需要具有**布尔性质**,返回与过滤条件相对应的真/假值。作为输出,它返回满足函数规定的条件的输入数据的子集。*
*从数学上讲,过滤操作可以大致指定如下:*
> *f:X →X ',带 X'⊆X*
**(更具体地说,我们可能会定义一个布尔向量来剔除集合中的假值元素,但这里让我们保持简洁)**
*回到食物的例子,假设我们定义了一个过滤函数`non_vegan()`。然后,我们可以应用以下过滤器:*
> *过滤器(非素食者,[🥓,🍿,🍳,🍟]) → [🥓,🍳]*
*我们再来看一个例子。假设我们有一个包含 tweets 的数据帧,并希望**过滤 retwetes**(由 tweets 的前两个字符*‘RT’*表示)。我们可以这样做:*
def check_retweet(tweet: str):
return tweet[:2]=='RT'list_retweets = list(filter(check_retweet, tweets_df['Tweet text']))
*很漂亮,对吧?*
# ***减少***
*与前两个函数不同,`reduce()`必须从`functools`导入才能正常工作。它返回单个值(即,*减少*单个元素的输入)。通常,这是一个列表中所有元素的总和。*
*事实上,Python 3 中没有内置`reduce()`的原因就是它通常只是用来计算总和。根据 Python 创始人 Van Rossum 的说法:*
> *“我最终讨厌 reduce(),因为它几乎专门用于(a)实现 sum(),或者(b)编写不可读的代码。所以我们添加了 builtin sum(),同时将 reduce()从 builtin 降级为 functools 中的某个东西(这是一个我并不真正关心的东西的垃圾场:-)”*
*数学表示:*
> *外宾:→ℝ*
*表情符号的例子:*
> *减少(混合,[🥬,🥕,🍅,🥒]) →🥗*
*如上所述,reduce 函数通常用于对列表中的所有元素求和或相乘的操作,如**。让我们看看乘法器的实现:***
from functools import reduceproduct = reduce(lambda x, y: xy, list_integers)*
*同样,执行该操作只需要很少的代码。*
*对于所有三个高阶函数,都有更深奥的用例(例如,将多个迭代器作为输入、嵌套结构),但这些超出了这篇介绍性文章的范围。*
# ***结束语***
*函数式编程将*函数*——而不是*对象*——置于编程实践的核心。尽管 Python 主要是一种面向对象的语言,但是三个**高阶函数** — `map()`、`function()`和`reduce()` —对利用 Python 中的函数式编程概念大有帮助。每个都接受一个函数和一个 iterable 作为输入。让我们再回顾一遍:*
* ***Map()** :对一个 iterable 中的所有元素执行相同的操作。一个例子是对每个元素执行对数转换。*
* ***Filter()** :过滤满足特定(一组)条件的元素子集。一个例子是过滤出包含特定字符串的句子。*
* ***Reduce()** :对可迭代对象执行运算,产生单值结果。一个常见的例子是对列表中的所有元素求和,产生一个数字作为输出。*
*和许多问题一样,没有放之四海而皆准的解决方案。大问题是:**Python 中什么时候使用函数式编程?**如前所述,有些情况下函数式编程是最合适的。然而,如果我们致力于 Python,具体什么时候我们会采用本文中讨论的功能呢?*
*老实说,不经常。列表理解通常(实质上)更快,而且——有点武断——更容易阅读。此外, **NumPy 操作**通常在大型重复数组操作方面更胜一筹。很可能,大多数 Python 程序员永远不会*需要*来使用本文中列出的函数(反高潮,我知道)。*
*最终,Python——尽管提供了一些更高级的函数——只是**不是函数式编程语言**。它不是那样设计的。在赛车上拍打泥土轮胎并不能使它成为拉力赛车。Python 没有能最大限度发挥函数式编程优势的编译器,因此也没有纯函数式语言的优势。*
*最后一次引用范·罗森的话:*
> *“如果我想到函数式编程,我通常会想到拥有强大编译器的语言,比如 Haskell。对于这样一个编译器来说,函数范式是有用的,因为它提供了大量可能的转换,包括并行化。但是 Python 的编译器不知道你的代码意味着什么,这也是有用的。所以,我认为尝试在 Python 中添加“函数式”原语没有多大意义,因为这些原语在函数式语言中工作良好的原因并不适用于 Python,并且它们使得不习惯函数式语言的人(也就是大多数程序员)很难读懂代码。*
>
> *我也不认为当前的函数式语言已经为主流做好了准备。诚然,除了 Haskell 之外,我对这个领域了解不多,但是任何不如 Haskell 受欢迎的语言肯定都没有什么实用价值,我也没有听说过比 Haskell 受欢迎的函数式语言。至于 Haskell,我认为它是关于编译器技术的各种想法的一个很好的试验场,但我认为它的“纯度”将永远保持在被采用的道路上。对大多数人来说,不得不与单子打交道是不值得的。"*
# ***延伸阅读***
*<https://www.analyticsvidhya.com/blog/2021/07/python-most-powerful-functions-map-filter-and-reduce-in-5-minutes/> <https://developers.slashdot.org/story/13/08/25/2115204/interviews-guido-van-rossum-answers-your-questions> <https://www.learnpython.org/en/Map%2C_Filter%2C_Reduce#:~:text=Map%2C%20Filter%2C%20and%20Reduce%20are,intricacies%20like%20loops%20and%20branching> <https://www.educba.com/functional-programming-vs-oop/> <https://www.knowledgehut.com/blog/programming/python-map-list-comprehension> *
# PyTorch 定制数据集在 5 分钟内帮助詹姆斯·邦德
> 原文:<https://towardsdatascience.com/pytorch-custom-datasets-in-5-minutes-to-help-james-bond-c7e454aa1aab>
## 如何利用自定义数据集来提高我们最喜爱的英雄的机会

马塞尔·埃伯勒在 [Unsplash](https://unsplash.com/s/photos/007?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上的照片
> 最近我看了詹姆斯·邦德的新电影《T2》。因为詹姆斯·邦德在电影中有很多敌人,所以我一直很担心。在一个场景中,他正驾驶着他的阿斯顿·马丁 DB 5 穿过一片美丽的雪景,突然几辆吉普车不知从哪里冒了出来,瞄准了他。他所有敌人的吉普车都是路虎的。如果詹姆斯·邦德事先简单地训练了一个神经网络来探测敌人的汽车会怎么样?电影中有一个简单的逻辑:好人开阿斯顿马丁,坏人开路虎,其他人都开其他品牌(好吧詹姆斯·邦德也开过一次经典的路虎和一次丰田,但我们想在这一点上保持简单……)。q 也有同样的想法,但是无法正确地组织数据。让我们帮助他,创建一个自定义数据集类。

照片由[雷·哈灵顿](https://unsplash.com/@raymondo600?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)在 [Unsplash](https://unsplash.com/s/photos/aston-martin?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上拍摄
在下面的文章中,我们将经历几个步骤:
1. 我们将使用 PyTorch 创建一个自定义数据集。
2. 我们将利用迁移学习的优势,我们将调整一个结果。
3. 最后,我们将在 PyTorch 中有一个多类分类问题的蓝图。
您将学到的内容:
1. 几乎所有的 CNN 教程都从 MNIST 或 CIFAR10 数据集开始。不要误解我,这些数据集是了解 CNN 的很好的资源。但是在现实世界中,数据的组织方式不允许您用一行程序将其加载到 Python 中。因此,我们将进一步了解自定义数据集。
2. 我们还将进一步探讨如何利用迁移学习在有限的资源下取得非常好的效果。
## 数据集
我们将使用斯坦福大学的汽车数据集。它包含 196 个汽车类别的 16,185 幅图像。你可以直接从他们的网站下载所有图片。让我们通过 Python 获取文件:
正如你所看到的,这些图片是有编号的,除了图片本身,不包含任何其他信息。所以我们需要某种密钥文件来添加关于单个图像的信息。幸运的是,斯坦福大学提供了这样一个关键文件。否则,我们不得不自己给每张图片贴标签。我们可以得到*。mat* 文件用 Python 也一样:
现在,我们将图像复制到我们的图像文件夹,并存储元信息。下一步,我们将结合这两个来源。mat-file 包含一个名为 *annotations* 的键,它保存了关于相应文件、所有图像的边界框和类名的信息。我们将忽略由每个数组中的前四个元素定义的边界框。第五个元素包含我们需要的汽车名称 id。让我们创建一个完成这项工作的函数:
因此,我们会收到一个以图像文件名为关键字的字典,其中包含以下值:品牌和汽车品牌名称、每个品牌的数字表示(例如,奥迪为 3)以及我们感兴趣的 3 个类别的数字表示。 *brand_translation_dict* 帮助我们跟踪哪个数字代表哪个品牌。
幸运的是,奥迪 R8 跑车在跑车世界里是众所周知的,我们可以确认我们的形象标签是正确的。我们还看到元组中的最后一个整数是 2,代表类别*其他*,这也是正确的。
现在我们可以创建 PyTorch 数据集类了。PyTorch 数据集是 [*表示数据集*](https://pytorch.org/tutorials/beginner/data_loading_tutorial.html) 的抽象类。它允许我们向数据加载器提供正确的信息,数据加载器用于将训练样本发送到我们的神经网络。必要的信息是数据集的长度和神经网络的输入结构。在我们的例子中,我们希望提取图像作为数字表示和每个图像的标签:
让我们一行一行地检查代码。在 init-part 中,我们指定图像的路径。我们还添加了一个转换操作。这将用于预处理图像(例如,调整大小,将 numpy 数组转换为张量等)。我们还添加了我们的 *translation_dict* 来从文件名中提取正确的标签。
下一部分是关于数据集的长度。这个函数将返回数据集的大小。最后一部分是 getitem 函数。这个函数帮助我们组织数据集的索引,允许数据加载器使用数据集[x]获取第 x 个样本。第一步,我们定义路径和文件夹。然后,我们打开索引图像并对其应用变换操作。在我们通过访问我们的 *translation_dict* 加载两个定义好的标签之后。最后,我们创建了一个包含三个键的字典,调用这个函数将返回这三个键。让我们试一试:
不出所料,我们得到了一个包含关键字 *image* 、 *label_brand* 和 *label_james_bond* 的字典。图像张量的形状是[16,3,224,224],其中 16 是批量大小,3 是通道的数量,以及[224,224]每个图像的大小。标签张量的大小为[16],因为每个图像有一个标签。
## 模型
最后,我们可以定义一个模型。这项任务并不容易,因为一些模型和汽车看起来非常相似。因此,我们将使用预训练网络并对其进行微调:
现在我们可以创建一个非常基本的训练循环。我们将以 1e-4 的学习速率训练网络 5 个时期:
训练结束后,我们将分别计算网络的总精度和每个类别的精度:
阿斯顿·马丁的准确率约为 87%,路虎的准确率约为 88%。其他类别的正确识别率为 99%。对于一个非常简单的结构来说一点也不差。有很大的改进空间:
* 使用其他变换操作,例如随机翻转、随机裁剪
* 创建一个更复杂的自定义头,而不是只添加一个线性层
* 使用更复杂的训练策略,例如冻结主干层,使用逐层区别学习率,使用其他优化器
* 尝试其他一些预先训练好的神经网络
* 组装
不管怎样,Q 是个天才,我相信他会让我们的网络变得更好。在我们结束之前,让我们看一下混淆矩阵。
对詹姆斯·邦德来说最重要的是:我们在阿斯顿·马丁和路虎之间没有分类失误。然而,8 辆阿斯顿马丁和 4 辆路虎被错误地归类为*其他*。
## 结论
PyTorch 自定义数据集类是一个强大的工具,可以为非完美结构化的数据创建管道。我们在 *getitem* 部分使用了自己的翻译词典。我们能够创建和加载我们自己的定制培训标签。甚至有更多的可能性。我们可以创建修改后的文件列表(例如,只过滤特定的品牌)并将它们添加为条件。我们也可以在 *getitem* 部分中集成 *if else* 语句。如果您希望将数据集类用于生产中的未标记数据,这将非常有用。例如,通过添加一个 *test == true* 语句,我们可以返回一个没有标签的字典。PyTorch 自定义数据集类提供了无限的可能性来单独构建您的训练数据。感谢阅读!
## 进一步阅读
**笔记本:**[https://jovian . ai/droste-benedikt/01-article-py torch-custom-datasets](https://drive.google.com/file/d/14AuCCmXW-Tjk02NCQONrHouRb2gVEiEw/view?usp=sharing)
**FastAI CNN 简介:**[https://colab . research . Google . com/github/FastAI/fastbook/blob/master/13 _ convolutions . ipynb](https://colab.research.google.com/github/fastai/fastbook/blob/master/13_convolutions.ipynb)
**py torch 定制数据集简介:** [https://py torch . org 澳大利亚悉尼。2013 年 12 月 8 日。](https://pytorch.org/tutorials/beginner/data_loading_tutorial.html) [【pdf】](https://ai.stanford.edu/~jkrause/papers/3drr13.pdf)[【BibTex】](https://ai.stanford.edu/~jkrause/papers/3drr13.bib)[【幻灯片】](https://ai.stanford.edu/~jkrause/papers/3drr_talk.pdf)
[如果您喜欢中级数据科学,并且还没有注册,请随时使用我的推荐链接加入社区。](https://medium.com/@droste.benedikt/membership)
# 火炬闪电 vs 极速 vs FSDP vs FFCV vs …
> 原文:<https://towardsdatascience.com/pytorch-lightning-vs-deepspeed-vs-fsdp-vs-ffcv-vs-e0d6b2a95719>
## 通过混合使用 PyTorch Lightning 的这些技术来组合其优势,从而扩大 PyTorch 模型培训
PyTorch Lightning 已经成为世界上使用最广泛的深度学习框架之一,它允许用户**专注于研究而不是工程**。Lightning 用户受益于 PyTorch 模型训练的大规模加速,从而节省了大量成本。
PyTorch Lightning 不仅仅是一个深度学习框架,它还是一个允许最新和最棒的技巧完美结合的平台。
> 闪电是一个̶f̶r̶a̶m̶e̶w̶o̶r̶k̶平台

闪电是一个平台(鸣谢:作者自有)
在过去的几年中,有许多库已经开始优化培训管道的每个阶段,当单独使用时,通常会导致 2-10 倍的速度提升。有了 Lightning,这些库可以一起用于**合成 PyTorch 模型的效率**。
在本帖中,我们将从概念上理解这些技术,并展示如何在 PyTorch Lightning 中启用它们。
# 标杆 PyTorch 闪电
PyTorch Lightning 通过*默认、*给用户提供了大量有用的东西,比如检查点、Tensorboard 日志、进度条等等……当对 PyTorch 进行基准测试时,这些必须关闭(是的,PyTorch + tensorboard 比没有它的 PyTorch 慢得多)。

要关闭的标志(鸣谢:作者自己的)
> 要比较 PyTorch Lightning 和 PyTorch **禁用“附加功能”**
请记住 PyTorch Lightning 是由 PyTorch 组织的,因此与 PyTorch 进行比较是没有意义的。每个👏单一的👏将请求拉至 PyTorch Lightning 基准,并与 PyTorch 进行比较,以确保两者在解决方案或速度上没有分歧([查看代码!](https://github.com/PyTorchLightning/pytorch-lightning/blob/master/tests/benchmarks/test_basic_parity.py))
以下是其中一个基准的示例:
PyTorch vs 闪电基准代码(鸣谢:作者自有)
# 深度速度
[DeepSpeed](https://github.com/microsoft/DeepSpeed) 是微软创造的一种大规模训练大规模十亿参数模型的技术。如果你有 800 个 GPU,你也可以得到[万亿个参数模型](https://www.microsoft.com/en-us/research/blog/deepspeed-extreme-scale-model-training-for-everyone/)😉。
DeepSpeed 如何实现规模的关键是通过引入**零冗余优化器(** [**零**](https://www.deepspeed.ai/tutorials/zero/) **)。零有三个阶段:**
1. 优化器状态跨进程进行分区。
2. 渐变是跨进程划分的。
3. 模型参数跨流程进行划分。

DeepSpeed 解释(鸣谢:作者自己)
要在闪电中启用 DeepSpeed,只需将`strategy='deepspeed'`传递给你的闪电训练器( [docs](https://pytorch-lightning.readthedocs.io/en/1.2.0/advanced/multi_gpu.html?highlight=deepspeed#deepspeed) )。

在 Lightning 中启用 DeepSpeed(鸣谢:作者所有)
# FSDP
完全分片数据并行( [FSDP](https://fairscale.readthedocs.io/en/stable/api/nn/fsdp.html) )是 Meta 的分片版本,灵感来自 DeepSpeed(阶段 3),针对 PyTorch 兼容性进行了优化([阅读他们最新的 1 万亿参数尝试](https://pytorch.medium.com/training-a-1-trillion-parameter-model-with-pytorch-fully-sharded-data-parallel-on-aws-3ac13aa96cff))。
FSDP 是由脸书(现在的 Meta)的 FairScale 团队开发的,重点是优化 PyTorch 兼容性。尝试两种方法,看看你是否能注意到不同之处!
要在 PyTorch Lightning ( [docs](https://pytorch-lightning.readthedocs.io/en/stable/advanced/advanced_gpu.html) )中使用 FSDP,请使用:

《闪电》中的 FSDP(鸣谢:作者自己)
# FFCV
DeepSpeed 和 FSDP 优化了负责跨机器分发模型的管道部分。
当您有一个图像数据集时,FFCV 通过利用数据集中的共享结构来优化管道中的数据处理部分。

FFCV 优化了更广泛的管道的一部分(鸣谢:作者自己)
[FFCV](https://github.com/libffcv/ffcv) 当然是对极速和 FSDP 的补充,因此也可以在 PyTorch Lightning 中使用。
*注意:FFCV 团队报告的基准测试并不完全公平,因为他们没有禁用 PyTorch Lightning 中的 tensorboard、logging 等功能,也没有启用 Deepspeed 或 FSDP 来合成使用 FFCV 的效果。*
FFCV 集成目前正在进行中,将在未来几周内在 PyTorch Lightning 中提供。
<https://github.com/PyTorchLightning/pytorch-lightning/issues/11538>
# 设计者

Composer 优化了培训管道的一部分(鸣谢:作者自己的)
Composer 是另一个处理培训管道不同部分的库。Composer 添加了某些技术,如 BlurPooling、ChannelsLast、CutMix 和 LabelSmoothing。
这些技术可以在优化开始之前添加到模型中。这意味着,要将 composer 与 PyTorch Lightning 一起使用,您只需在开始训练循环之前对模型手动运行优化即可。

马赛克优化适用于兼容 PyTorch 闪电(信用:作者自己的)
Lightning 团队正在研究将 mosaic 与 PyTorch Lightning 整合的最佳方式。
<https://github.com/PyTorchLightning/pytorch-lightning/issues/12360>
# 结束语
PyTorch Lightning 不仅仅是一个深度学习框架,它还是一个平台!
PyTorch Lightning 允许您集成最新的技术,这样它们可以很好地协同工作,并保持您的代码高效有序,而不是试图重新发明轮子。
新推出的解决管道不同部分的优化将在几周内集成。如果您正在创建一个新的社区,请联系 lightning 团队,以便 lightning 团队可以与您合作,使您的集成成为社区的一个平稳体验!
# 时序数据的 Pytorch LSTMs
> 原文:<https://towardsdatascience.com/pytorch-lstms-for-time-series-data-cd16190929d7>
## 使用 Pytorch 函数 API 构建单变量时间序列的时态模型

[天一马](https://unsplash.com/@tma?utm_source=medium&utm_medium=referral)在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上拍照
Y 您可能已经注意到,尽管我们在现实世界中经常会遇到序列数据,但是网上并没有大量的内容展示如何使用 Pytorch 函数式 API 从头开始构建简单的 LSTMs。即使 Pytorch 官方文档中的 LSTM 例子也只适用于自然语言问题,当试图让这些递归模型处理时间序列数据时,这可能会令人迷惑。在本文中,我们将为构建端到端 LSTM 打下坚实的基础,从张量输入和输出形状到 LSTM 本身。
本文的目标是能够实现任何单变量时间序列 LSTM。我们首先检查传统神经网络对于这些任务的缺点,以及为什么 LSTM 的输入与简单神经网络的形状不同。然后,我们将直观地描述让 LSTM“记忆”的机制有了这个大概的理解,我们可以使用从`nn.Module`继承的传统模型类结构实现 Pytorch LSTM,并为它编写一个 forward 方法。我们用这个来看看我们是否能让 LSTM 学习一个简单的正弦波。最后,我们尝试编写代码来概括如何根据手头的问题初始化 LSTM,并在前面的例子中进行测试。
# 最简单的神经网络
L 假设我们有以下时间序列数据。我们不使用复杂的递归模型,而是将时间序列视为简单的输入输出函数:输入是时间,输出是我们测量的任何因变量的值。这本质上只是简化了一个单变量时间序列。
> Y 你可能想知道我们上面概述的问题和时间序列问题的实际顺序建模方法(如 LSTMs 中使用的)之间有什么*差异。区别在于解的递归性。这里,我们只是传递当前的时间步长,并希望网络能够输出函数值。然而,在递归神经网络中,我们不仅传入当前输入,还传入以前的输出。这样,网络可以学习先前函数值和当前函数值之间的相关性。在这里,网络没有办法学习这些依赖关系,因为我们根本不把以前的输出输入到模型中。*
让我们假设我们正在试图模拟克莱·汤普森伤愈复出后的上场时间。金州勇士队的教练史蒂夫·科尔不希望克莱回来后立即打很多分钟。取而代之的是,他将让克莱每场比赛打几分钟,并随着赛季的进行增加他的上场时间。我们将是克莱·汤普森的理疗师,我们需要预测克莱每场比赛将打多少分钟,以确定他的膝盖需要多少绷带。
因此,伤愈复出后的比赛次数(代表输入时间步长)为自变量,克莱·汤普森的上场分钟数为因变量。假设我们观察克莱 11 场比赛,记录他每次出场的上场时间,得到以下数据。
在这里,我们已经生成了每场比赛的分钟数,作为回归以来与比赛次数的线性关系。我们将使用 9 个样本作为训练集,2 个样本用于验证。
我们知道比赛次数和分钟数的关系是线性的。然而,我们仍然要使用非线性激活函数,因为这是神经网络的全部要点。(否则,这只会变成线性回归:线性运算的合成只是一个线性运算。)像往常一样,我们使用`nn.Sequential`来构建一个隐藏层的模型,有 13 个隐藏神经元。
我们现在需要编写一个训练循环,就像我们在使用梯度下降和反向传播来迫使网络学习时经常做的那样。提醒您,每个训练步骤都有几个关键任务:
1. 通过将模型应用于训练示例来计算通过网络的前向传递。
2. 基于定义的损失函数计算损失,该损失函数将模型输出与实际训练标签进行比较。
3. 通过网络反向传播损耗相对于模型参数的导数。在用`.zero_grad()`将当前参数梯度设置为零后,通过对损耗调用`.backward()`来完成。
4. 通过减去梯度乘以学习率来更新模型参数。这是通过我们的优化器完成的,使用`optimiser.step()`。
现在,我们需要做的就是实例化所需的对象,包括我们的模型,我们的优化器,我们的损失函数和我们要训练的时期数。
Epoch 1, Training loss 422.8955, Validation loss 72.3910
Epoch 10000, Training loss 0.1970, Validation loss 324.8314
Epoch 20000, Training loss 0.1950, Validation loss 323.0615
Epoch 30000, Training loss 0.1922, Validation loss 320.4883
Epoch 40000, Training loss 0.1883, Validation loss 317.0684
Epoch 50000, Training loss 0.1825, Validation loss 313.0166
Epoch 60000, Training loss 0.1736, Validation loss 308.5492
...
Epoch 500000, Training loss 0.0007, Validation loss 299.8014
正如我们所看到的,该模型很可能明显过度拟合(这可以通过许多技术来解决,如正则化,或降低模型参数的数量,或实施线性模型形式)。培训损失基本为零。由于我们的因变量中固有的随机变化,上场时间在最后几场比赛中逐渐减少成一条平坦的曲线,导致该模型认为这种关系更像一条日志而不是一条直线。

十字代表我们在训练神经网络 50,000 个时期后的预测。蓝点代表实际数据点(上场时间)。由于由故意噪声引起的训练样本变平,该模型不能完美地捕捉线性关系。移除模型中的 tanh()图层将移除非线性,从而迫使模型近似于回归输出。(图片由作者提供)
虽然它不是很成功,但这个初始的神经网络是一个概念验证,我们可以仅仅通过输入所有的时间步骤来开发序列模型。但是,如果没有关于过去的更多信息,并且没有存储和调用这些信息的能力,那么模型在顺序数据上的性能将会非常有限。
# LSTMs 背后的直觉
最简单的神经网络假设输入和输出之间的关系与先前的输出状态无关。它假设函数形状可以仅从输入中学习。在像顺序数据这样的情况下,这个假设是不成立的。任何一个特定时间步长的函数值都可以被认为直接受到过去时间步长的函数值的影响。这些值之间存在时间相关性。长短期记忆网络(LSTMs)是一种递归神经网络,擅长学习这种时间依赖性。
LSTMs 的关键是**单元状态**,它允许信息从一个单元流向另一个单元。这代表了 LSTM 的记忆,可以随着时间的推移而更新、改变或遗忘。进行这种更新的 LSTM 组件被称为**门**,它调节细胞包含的信息。门可以被视为神经网络层和逐点操作的组合。
如果你还不知道 LSTM 是如何工作的,数学很简单,基本的 LSTM 方程可以在 [Pytorch 文档](https://pytorch.org/docs/stable/generated/torch.nn.LSTMCell.html)中找到。网上有很多很棒的资源,[比如这个](https://colah.github.io/posts/2015-08-Understanding-LSTMs/)。作为快速复习,以下是每个 LSTM 细胞进行的四个主要步骤:
1. *决定从单元格状态中删除哪些不再相关的信息。*这由被称为**遗忘门**的神经网络层(具有 sigmoid 激活功能)控制。我们将前一个单元的输出输入到遗忘门,遗忘门依次输出一个介于 0 和 1 之间的数字,决定遗忘多少。
2. *用新信息更新单元状态。*一个称为**输入门**的神经网络层将前一个单元的输出和当前输入连接起来,并决定更新什么。tanh 层采用相同的连接,并创建可添加到状态的新候选值的向量。
3. *更新旧的单元格状态以创建新的单元格状态。*我们将旧状态乘以步骤 1 中确定的值,忘记了我们之前决定忘记的事情。然后,我们添加在步骤 2 中找到的新候选值。这些构成了新的单元格状态,根据我们决定更新每个状态值的程度进行缩放。此单元已完成;我们可以将它直接传递给模型中的下一个单元。
4. *根据以前的输出和当前的输入生成模型输出。*首先,我们获取更新后的单元状态,并将其传递给 NN 层。然后,我们找到通过 sigmoid 层的输出/输入向量的输出,然后用修改后的单元状态逐点组合它。这允许单元完全控制组成单元状态和当前单元输入,这给我们一个适当的输出。

单个 LSTM 细胞的简单表示。黄色方框显示的是神经网络层——与常规的 rnn 不同,我们每个细胞中有四层,所有层都以高度指定的方式相互作用。(图片由作者提供)
注意,在上图中我们给出了两次输出。这些输出中的一个将被存储为模型预测,用于绘图等。另一个被传递到下一个 LSTM 单元,就像更新后的单元状态被传递到下一个 LSTM 单元一样。
# 皮托尔彻·LSTM
我们的问题是看 LSTM 是否能“学习”正弦波。这实际上是 Pytorch 社区中一个相对著名(阅读:声名狼藉)的例子。这是 Pytorch 的 Examples Github 知识库中唯一一个针对时序问题的 LSTM 的例子。然而,这个例子已经过时了,大多数人发现代码要么不能为他们编译,要么不能收敛到任何合理的输出。(在这个例子中,快速的 Google 搜索给出了一连串的堆栈溢出问题。)在这里,我们将逐步分解和修改他们的代码。
## 生成数据
我们从生成 100 个不同正弦波的样本开始,每个正弦波具有相同的频率和幅度,但开始于 *x* 轴上略有不同的点。
让我们浏览一下上面的代码。 *N* 为样本数;也就是说,我们正在产生 100 个不同的正弦波。许多人直觉地在这一点上犯错。由于我们习惯于在单个数据点上训练神经网络,例如上面的简单克莱·汤普森例子,因此很容易将这里的 *N* 视为我们测量正弦函数的点的数量。这是错误的;我们正在生成 N 个不同的正弦波,每个都有许多点。LSTM 网络通过检测多个正弦波而不是一个正弦波来学习。
接下来,我们实例化一个空数组 *x* 。将该数组视为沿 *x* 轴的点的样本。该阵列有 100 行(代表 100 个不同的正弦波),每行有 1000 个元素长(代表 *L* ,或正弦波的粒度,即每个波中不同采样点的数量)。然后,我们通过对前 1000 个整数点进行采样来填充 *x* ,然后在由 *T* 控制的特定范围内添加一个随机整数,其中`x[:]`只是将整数沿行相加的语法。请注意,我们必须将第二个随机整数整形为整形【T21(N,1)】,以便 Numpy 能够将其广播到 *x* 的每一行。
最后,我们简单地将 Numpy 正弦函数应用于 *x* ,并让 broadcasting 将该函数应用于每行中的每个样本,每行创建一个正弦波。我们将其转换为类型`float32`。我们可以选择任意一个正弦波,并用 Matplotlib 绘制出来。让我们选择索引为 0 的第一个采样正弦波。

将在我们的 LSTM 模型中使用的 100 个生成的正弦波之一。每个正弦波都有 1000 个采样点,我们使用正弦函数将这些点转换成一个波。(图片由作者提供)
为了构建 LSTM 模型,我们实际上只为 LSTM 单元调用了一个`nn`模块。首先,我们将呈现整个模型类(一如既往地从`nn.Module`继承),然后一点一点地浏览它。
## 初始化
初始化的关键步骤是 Pytorch `LSTMCell`的声明。你可以在这里找到文档。该单元有三个主要参数:
* `input_size`:输入中预期特征的数量 *x* 。
* `hidden_size`:隐藏状态下的特征数量 *h* 。
* 这默认为真,一般情况下我们保持不变。
> 你们中的一些人可能知道一个叫做`LSTM`的独立`torch.nn`类。这两者之间的区别在这里并不重要,但是只要知道`LSTMCell`在使用函数式 API 从头定义我们自己的模型时更加灵活。
请记住,LSTM 单元的**参数**不同于**输入**。这里的参数很大程度上决定了预期输入的形状,因此 Pytorch 可以设置适当的结构。输入是我们输入到单元中的实际训练示例或预测示例。
我们使用两个 LSTM 单元定义两个 LSTM 层。与卷积神经网络非常相似,设置输入和隐藏大小的关键在于两层之间的连接方式。对于第一个 LSTM 单元格,我们传入一个大小为 1 的输入。回想一下为什么会这样:在 LSTM 中,我们不需要传入输入的切片数组。我们不需要数据上的滑动窗口,因为记忆和遗忘门为我们处理细胞状态。我们不需要每次都专门用旧数据手工输入模型,因为模型有能力回忆这些信息。这就是 LSTMs 如此特别的原因。
然后,当我们声明我们的类时,我们给第一个 LSTM 单元格一个由变量控制的隐藏大小,`n_hidden`。这个数字是相当随意的;在这里,我们选择 64。如上所述,这成为我们传递到下一个 LSTM 单元的各种输出,很像 CNN:上一步的输出大小成为下一步的输入大小。在这个单元格中,我们有一个大小为`hidden_size`的输入,还有一个大小为`hidden_size`的隐藏层。然后,我们将这个大小为`hidden_size`的输出传递给一个线性层,该层本身输出一个大小为 1 的标量。我们输出一个标量,因为我们只是试图预测在特定时间步的函数值 *y* 。
> 在构建模型的这个阶段,需要记住的最重要的事情之一是输入和输出的大小:我映射的是什么?
## 正向方法
在正向方法中,一旦 LSTM 的各个层都以正确的大小进行了实例化,我们就可以开始关注通过网络传输的实际输入。LSTM 单元格接受以下输入:`input, (h_0, c_0).`
* `input`:形状为`(batch, input_size)`的输入张量,我们在创建 LSTM 单元时声明了`input_size`。
* `h_0`:一个张量,包含批处理中每个元素的初始隐藏状态,形状为`(batch, hidden_size).`
* `c_0`:一个张量,包含批处理中每个元素的初始单元格状态,形状`(batch, hidden_size)`。
为了连接两个 LSTM 细胞(以及第二个具有线性全连接层的 LSTM 细胞),我们还需要知道 LSTM 细胞实际输出的是什么:形状张量`(h_1, c_1)`。
* `h_0`:一个张量,包含批处理中每个元素的下一个隐藏状态,形状为`(batch, hidden_size).`
* `c_0`:一个张量,包含批处理中每个元素的下一个单元格状态,形状为`(batch, hidden_size)`。
这里我们的批量是 100,由我们输入的第一维度给出;于是,我们取`n_samples = x.size(0)`。因为我们知道隐藏和单元状态的形状都是`(batch, hidden_size)`,我们可以实例化这个大小的零张量,并且对我们的两个 LSTM 单元都这样做。
下一步可以说是最困难的。我们必须输入一个适当形状的张量。在这里,这将是一个由 *m* 个点组成的张量,其中 *m* 是我们在每个序列上的训练规模。然而,在 Pytorch `split()`方法(文档[这里是](https://pytorch.org/docs/stable/generated/torch.split.html))中,如果参数`split_size_or_sections`没有传入,它将简单地将每个张量分割成大小为 1 的块。我们希望将它拆分到每个单独的批处理中,因此我们的维度将是行,这相当于维度 1。
当我们以这种方式对数组进行矢量化时,检查输出形状总是一个好主意。假设我们选择三条正弦曲线作为测试集,其余的用于训练。我们可以在我们的分割方法中检查我们的训练输入看起来像什么:
因此,对于每个样本,我们传递 97 个输入的数组,用一个额外的维度来表示它来自一个批次。(Pytorch 通常是这样操作的。即使我们向世界上最简单的 CNN 传递单个图像,Pytorch 也需要一批图像,因此我们必须使用`unsqueeze().`)然后输出一个新的隐藏单元格状态。从上面我们知道,隐藏状态输出被用作下一个 LSTM 单元的输入。然后,从第二个单元输出的隐藏状态被传递到线性层。
很好——我们已经完成了基于实际数据点的模型预测。但是 LSTM 的全部意义在于根据过去的输出预测曲线的未来形状。因此,在向前传递的下一阶段,我们将预测下一个`future`时间步长。回想一下,在前一个循环中,我们通过将第二个 LSTM 输出传递到一个线性层来计算要附加到我们的`outputs`数组的输出。这个变量仍然在运行——我们可以访问它,并将其再次传递给我们的模型。这是一个好消息,因为我们可以预测未来的下一个时间步,在我们有数据的最后一个时间点之后的一个时间步。该模型将其对最后一个数据点的预测作为输入,并预测下一个数据点。
然后,我们再次这样做,将预测作为输入输入到模型中。总的来说,我们这样做`future`次,以产生一条长度为`future`的曲线,除此之外,我们已经对我们实际拥有数据的 1000 个点进行了 1000 次预测。
我们要做的最后一件事是,在返回输出之前,连接代表输出的标量张量的数组。就是这样!我们已经建立了一个 LSTM,它接受一定数量的输入,并一个接一个地预测未来一定数量的时间步长。
## 训练模型
在 Pytorch 中定义一个训练循环对于各种常见的应用程序来说是非常相似的。然而,在我们的情况下,我们不能通过检查损失来直观地了解模型是如何收敛的。是的,低损失是好的,但有很多次我在实现低损失后去看模型输出,看到了绝对的垃圾预测。这通常是由于我的绘图代码中的错误,或者更可能是我的模型声明中的错误。因此,我们可以应用于模型评估和调试的最有用的工具是绘制每个训练步骤的模型预测,以查看它们是否有所改进。
我们的第一步是找出输入和目标的形状。我们知道我们的数据`y`具有形状`(100, 1000)`。即 100 条不同的正弦曲线,每条 1000 个点。接下来,我们想弄清楚我们的训练测试分割是什么。我们将为测试集保存 3 条曲线,因此沿着`y`的第一维索引,我们可以将最后 97 条曲线用于训练集。
现在是时候考虑我们的模型输入了。一次一个,我们希望输入上一个时间步长,并得到一个新的时间步长预测。为此,我们输入每个正弦波的前 999 个样本,因为输入最后 1000 个样本将导致预测第 1001 个时间步长,我们无法验证这一点,因为我们没有相关数据。同样,对于训练目标,我们使用前 97 个正弦波,并从每个波中的第*第 2 个*样本开始,使用每个波中的最后*999 个样本;这是因为我们需要一个先前的时间步骤来实际输入到模型中—我们不能输入`nothing`。因此,第二维中目标的起始索引(代表每个波中的样本)是 1。这给了我们两个数组的形状`(97, 999)`。*
测试输入和测试目标遵循非常相似的推理,除了这一次,我们只沿着第一维索引前三个正弦波。正如我们所期望的,其他一切都是完全一样的:除了批量输入大小(97 比 3)之外,我们需要训练集和测试集有相同的输入和输出。
我们现在需要实例化训练循环的主要组件:模型本身、损失函数和优化器。该模型只是我们的`LSTM`类的一个实例,我们将用于回归问题的损失函数是`nn.MSELoss()`。唯一不同于平常的是我们的乐观主义者。代替 Adam,我们将使用所谓的有限内存 BFGS 算法,该算法本质上归结为估计 Hessian 矩阵的逆作为变量空间的向导。你不需要担心细节,但你需要担心`optim.LBFGS`和其他乐观主义者之间的区别。我们将在下面的培训循环中介绍这一点。
> 您可能想知道为什么我们要费心从像 Adam 这样的标准优化器转换到这种相对未知的算法。LBFGS 解算器是一种准牛顿方法,它使用 Hessian 的逆来估计参数空间的曲率。在序列问题中,参数空间的特点是有大量长而平坦的谷,这意味着 LBFGS 算法通常优于 Adam 等其他方法,尤其是在没有大量数据的情况下。
最后,我们开始构建训练循环。公平的警告,虽然我尽量让这看起来像一个典型的 Pytorch 训练循环,但还是会有一些不同。这些主要在我们必须传递给优化器的函数中,`closure`,它代表了网络中典型的前向和后向传递。通过传递这个函数,我们用`optimiser.step()`来更新权重。[根据 Pytorch](https://pytorch.org/docs/1.9.1/generated/torch.optim.LBFGS.html) ,函数`closure`是一个重新评估模型(正向传递)并返回损失的可调用函数。所以这正是我们所做的。
训练循环开始时和其他普通的训练循环一样。但是,请注意,向前和向后传递的典型步骤是在函数闭包中捕获的。这只是 Pytorch 中优化器函数设计的一个特点。我们在`closure`中返回损失,然后在`optimiser.step()`中将这个函数传递给优化器。这就是培训步骤的大部分内容。
接下来,我们想要绘制一些预测,这样我们就可以在进行过程中检查我们的结果。为此,我们需要获取测试输入,并将其传递给模型。这就是我们包含在模型中的参数`future`派上用场的地方。回想一下,在实际样本的最后一次输出之后,将一些非负整数`future`传递给模型的前向传递将为我们提供`future`预测。这让我们可以看到模型是否推广到未来的时间步骤。然后,我们将这个输出从当前的计算图中分离出来,并将其存储为一个 numpy 数组。
最后,我们编写一些简单的代码来绘制模型在每个时期对测试集的预测。只有三条测试正弦曲线,所以我们只需要调用我们的`draw`函数三次(我们将用不同的颜色绘制每条曲线)。绘制的线表示未来的预测,实线表示当前数据范围内的预测。

(图片由作者提供)
随着时间的推移,预测会明显改善,损失也会下降。我们的模型工作:到第 8 个纪元时,模型已经学会了正弦波。但是,如果您继续训练模型,您可能会看到预测开始变得有趣。这是因为,在每个时间步,LSTM 依赖于前一个时间步的输出。如果对于第 1001 次预测,预测稍有变化,这将一直干扰预测,直到预测 2000,导致无意义的曲线。有许多方法可以解决这个问题,但是它们超出了本文的范围。现在最好的策略是观察曲线图,看看这种误差累积是否开始发生。然后,你可以回到更早的时代,或者训练过去,看看会发生什么。

(图片由作者提供)
如果您的 LSTM 收敛有问题,您可以尝试以下几种方法:
* 通过改变隐藏层的大小来减少模型参数的数量(甚至可以减少到 15 个)。这减少了模型搜索空间。
* 通过减少传递给第二个单元格的`hidden_size`,尝试从第一个 LSTM 单元格向下采样到第二个单元格。从第二个 LSTM 单元到线性全连接层,也可以这样做。
* 添加 batchnorm 正则化,通过对较大的权重值进行惩罚来限制权重的大小,从而使损失的地形更加平滑。
* 添加 dropout,在每个时期将整个模型中神经元输出的随机部分清零。这每次都会生成略有不同的模型,这意味着模型被迫更少地依赖单个神经元。
如果实现最后两个策略,请记住在训练期间调用`model.train()`来实例化正则化,并在使用`model.eval()`的预测和评估期间关闭正则化。
# 自动构建模型
如果我们仍然不能将 LSTM 应用于其他形状的输入,则整个练习毫无意义。让我们生成一些新数据,除了这一次,我们将随机生成曲线的数量和每条曲线中的样本。我们不知道这些参数的实际值是什么,所以这是一个完美的方式来看看我们是否可以基于输入和输出形状之间的关系来构造 LSTM。
然后,我们可以通过确定要用于训练集的每个曲线中的样本百分比来更改以下输入和输出形状。
因此,输入和输出形状变为:
您可以通过 LSTM 运行这些输入和目标来验证这是否有效(提示:确保您为将来基于输入的长度实例化了一个变量)。
让我们看看我们是否能把这个应用到最初的克莱·汤普森例子中。如果我们要把它提供给我们的 LSTM,我们需要生成一组以上的分钟。也就是说,我们将生成 100 个不同的假设分钟集,克莱·汤普森在 100 个不同的假设世界中演奏。我们将其中的 95 个输入进行培训,并绘制其余 5 个中的 3 个,以了解我们的模型是如何学习的。
在使用上述代码基于 *L* 和 *N* 重新整形输入和输出之后,我们运行该模型并实现以下内容:
这给了我们下面的图像(我们只显示第一个和最后一个):

(图片由作者提供)
很有意思!最初,LSTM 也认为曲线是对数的。虽然经过一些训练后,它得出了前 11 场比赛的曲线是线性的,但它坚持为未来的比赛提供对数曲线。这其中最吸引人的一点是,LSTM 是对的——Klay 不能一直线性增加他的游戏时间,因为一场篮球比赛只持续 48 分钟,而且大多数这样的过程都是对数的。显然,LSTM 不可能知道这一点,但不管怎样,看看这个模型如何解释我们的玩具数据是很有趣的。未来的任务可能是玩弄 LSTM 的超参数,看看是否有可能让它也学习未来时间步长的线性函数。此外,我还喜欢创建一个 Python 类来将所有这些函数存储在一个位置。然后,可以使用数据创建一个对象,并且可以编写函数来读取数据的形状,并将其馈送给相应的 LSTM 构造函数。
# 结论
总之,在 Pytorch 中为单变量时间序列数据创建 LSTM 不需要太复杂。然而,缺乏可用的在线资源(尤其是不关注自然语言形式的序列数据的资源)使得学习如何构建这种递归模型变得困难。希望本文提供了关于设置输入和目标、为 LSTM 向前方法编写 Pytorch 类、用我们新的优化器的特性定义训练循环以及使用可视化工具(如绘图)进行调试的指导。
如果你想了解更多关于 LSTM 细胞背后的数学知识,我强烈推荐[这篇文章](https://colah.github.io/posts/2015-08-Understanding-LSTMs/),它漂亮地列出了 LSTMs 的基本方程(我与作者没有联系)。我还建议尝试将上述代码应用于多元时间序列。所有的核心思想都是一样的——你只需要考虑如何扩展输入的维度。
# PyTorch 多权重支持 API 让迁移学习再次变得琐碎
> 原文:<https://towardsdatascience.com/pytorch-multi-weight-support-api-makes-transfer-learning-trivial-again-899071fa2844>
## 一个新的 Pytorch API 使微调流行的神经网络架构变得容易,并使它们为您工作

艾莉娜·格鲁布尼亚克在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的照片
微调深度学习(DL)模型从未如此简单。像 [TensorFlow](https://www.tensorflow.org/) 和 [PyTorch](https://pytorch.org/) 这样的现代 DL 框架使得这个任务变得微不足道。你可以在几分钟内拥有一个经过战斗考验的适合你需求的人工神经网络。
然而,微调您的模型只是您实验中的一步。因此,像这样的实验通常遵循以下算法:
1. 实例化您想要微调的神经网络架构。例如,如果您的领域是计算机视觉,您可能希望加载 ResNet 架构。
2. 为此架构加载一组权重。坚持我们的计算机视觉示例,通过在 ImageNet 数据集上训练模型获得的一组权重通常是首选。
3. 创建预处理函数或操作组合,将数据塑造成所需的形状。
4. 将你在第一步中实例化的神经网络与你的数据相匹配。您可以根据需要调整模型的输出层,或者冻结一些层以保持权重子集不变。这些决定取决于您的用例。
5. 在保留的测试数据集上评估您训练的神经网络。您必须以处理定型数据集的相同方式处理测试数据集。任何微小的差异都会导致模型性能的恶化,这是非常难以调试的。
6. 存储实验的元数据,例如数据集的类名,以便在以后的应用程序中使用它们,甚至执行健全性检查。
</xresnet-from-scratch-in-pytorch-e64e309af722>
这就是你说的重点,"*等一下,这不是那么直截了当的!*“你说得对,细节决定成败;如果你什么都自己做,有很多陷阱要避免。
因此,让我们看看一个新的 PyTorch API 如何使这一切变得更加机械化,从而节省您的时间和精力。
> [Learning Rate](https://www.dimpo.me/newsletter?utm_source=medium&utm_medium=article&utm_campaign=pytorch-pretrained-api) 是为那些对 AI 和 MLOps 的世界感到好奇的人准备的时事通讯。你会在每周五收到我关于最新人工智能新闻和文章的更新和想法。订阅[这里](https://www.dimpo.me/newsletter?utm_source=medium&utm_medium=article&utm_campaign=pytorch-pretrained-api)!
# 古老的方式:徒步旅行
为了对正在发生的事情有一个坚实的理解,我们将首先检查旧的方式。我们不会训练一个模特,但我们会做几乎所有其他的事情:
* 加载神经网络架构的预训练版本
* 预处理数据集
* 使用神经网络在测试集上获得预测
* 使用数据集的元数据获得人类可读的结果
下面的代码片段总结了您需要做什么来勾选上面列表中的所有框:
在此示例中,首先加载 ResNet 神经网络体系结构。您将`pretrained`标志设置为`True`,告诉 PyTorch 您不希望它随机初始化模型的权重。相反,它应该使用通过在 ImageNet 数据集上训练模型而获得的权重。
然后,定义并初始化数据转换的组合。因此,在将图像输入模型之前,PyTorch 将:
* 将图像调整为`224x224`
* 把它转换成张量
* 将张量的每个值转换为类型`float`
* 使用一组给定的平均值和标准偏差将其标准化
接下来,您准备处理图像,并通过神经网络层获得输出。这一步是最简单的一步。
最后,您希望以人类可读的方式打印结果。这意味着你不想打印出你的模型预测的图像是`4`类。这对您或您的应用程序的用户来说没有任何意义。您希望打印出您的模型预测图像显示一只狗有 95%的可信度。
为此,您首先需要加载包含类名的元数据文件,并为您的预测确定正确的类名。
我承认这个剧本不算太大的工作量;然而,它有两个缺点:
1. 您处理测试数据集的方式的微小变化可能会导致难以调试的错误。请记住,您应该像处理训练数据集一样转换测试数据集。如果你不知道你是怎么做到的,或者别人运行了训练程序,你就完了。
2. 您必须随身携带元数据文件。同样,对该文件的任何篡改都可能导致意想不到的结果。这些错误会让你的头在键盘上撞上几天。
现在让我们看看一个新的 PyTorch API 如何让这一切变得更好。
# 走向
正如我们之前看到的,您有两个痛点需要解决:(I)总是以相同的方式处理您的训练和测试子集,(ii)消除携带单独的元数据文件的需要。
让我们通过一个示例来看看新的 PyTorch API 如何应对这些挑战:
您会立即看到脚本明显变小了。但这不是重点;该脚本更小,因为您不必从头定义一个`preprocess`函数,也不必加载任何元数据文件。
在第 9 行,您会看到一些新的东西。不是告诉 PyTorch 你需要一个预训练版本的 ResNet,首先,你实例化一个新的`weights`对象,然后用它实例化模型。在这个`weights`对象中,您可以找到在训练期间应用于数据的转换和数据集的元数据。这太棒了。
另一个观察结果是,您现在可以轻松地选择要预加载的重量。例如,您可以选择加载一组不同的权重,只需对代码进行微小的更改:
# New weights with accuracy 80.674% model = resnet50(weights=ResNet50_Weights.ImageNet1K_V2)
或者询问在 ImageNet 上产生最佳结果的答案:
# Best available weights (currently alias for ImageNet1K_V2) model = resnet50(weights=ResNet50_Weights.default)
# 结论
微调深度学习(DL)模型从未如此简单。像 TensorFlow 和 PyTorch 这样的现代 DL 框架使得这个任务变得微不足道。
然而,仍然有一些陷阱要避免。最值得注意的是,您应该总是以相同的方式处理您的训练和测试子集,并且消除携带单独的元数据文件的需要。
此外,如果您需要使用一组不同的权重作为起点,或者您已经有了一组权重,并且希望在某个中央存储库中共享它们,会发生什么情况呢?有没有一种简单的方法来实现这一点?
如您所见,新的 PyTorch 多权重支持 API 涵盖了所有这些挑战。你可以通过安装 PyTorch 的夜间版本进行试验,并对这个 [GitHub 问题](https://github.com/pytorch/vision/issues/5088)提供反馈。
# 关于作者
我的名字是[迪米特里斯·波罗普洛斯](https://www.dimpo.me/?utm_source=medium&utm_medium=article&utm_campaign=pytorch-pretrained-api),我是一名为[阿里克托](https://www.arrikto.com/)工作的机器学习工程师。我曾为欧洲委员会、欧盟统计局、国际货币基金组织、欧洲央行、经合组织和宜家等主要客户设计和实施过人工智能和软件解决方案。
如果你有兴趣阅读更多关于机器学习、深度学习、数据科学和数据操作的帖子,请关注我的 [Medium](https://towardsdatascience.com/medium.com/@dpoulopoulos/follow) 、 [LinkedIn](https://www.linkedin.com/in/dpoulopoulos/) 或 Twitter 上的 [@james2pl](https://twitter.com/james2pl) 。
所表达的观点仅代表我个人,并不代表我的雇主的观点或意见。
# 使用拥抱面和英特尔弧的 PyTorch 稳定扩散
> 原文:<https://towardsdatascience.com/pytorch-stable-diffusion-using-hugging-face-and-intel-arc-77010e9eead6>
## 使用英特尔 GPU 运行稳定的扩散

作者创造的稳定扩散的图像
# 介绍
文本到图像的人工智能模型在过去几年里变得非常流行。最受欢迎的模型之一是稳定扩散,由[康普维斯](https://github.com/CompVis)、[稳定人工智能](https://stability.ai/)和[莱恩](https://laion.ai/)合作创建。尝试稳定扩散的最简单方法之一是通过[拥抱面部扩散器库](https://huggingface.co/blog/stable_diffusion)。
随着最新英特尔 Arc GPU 的发布,我们收到了许多关于英特尔 Arc 卡是否支持运行 Tensorflow 和 PyTorch 模型的问题,答案是肯定的!使用 oneAPI 规范构建的[面向 TensorFlow*](https://www.intel.com/content/www/us/en/developer/articles/guide/optimization-for-tensorflow-installation-guide.html) 的英特尔优化和[面向 PyTorch 的英特尔扩展](https://www.intel.com/content/www/us/en/developer/tools/oneapi/extension-for-pytorch.html)允许用户在最新的英特尔 GPU 上运行这些框架。
为了帮助人们了解如何在英特尔 GPU 上运行 PyTorch,本文将快速展示我们如何在过去几年中在英特尔 Arc A770 GPU 上运行一个更有趣的人工智能工作负载。
# 设置
从我之前的[帖子](https://medium.com/@tonymongkolsmai)中,你可能知道我有一个英特尔 Alder Lake Core i9–12900 KF 外星人 R13 系统。实际上,我不会使用该系统作为本演练的基础,因为我刚刚使用 MSI z690 Carbon WiFi 主板和 64GB 5600 DDR 5 RAM 以及全新的英特尔 Arc A770 组装了一个 Raptor Lake 第 13 代英特尔酷睿 i7-13700KF 系统进行测试。系统正在运行全新安装的 Ubuntu 22.04.1。

测试系统图片由作者提供
# 英特尔弧上的稳定扩散
以此为硬件基础,让我们完成在该系统上实现稳定扩散所需的所有步骤。
## 设置基础软件堆栈
首先,我们需要安装英特尔 Arc 驱动程序和[英特尔 oneAPI 基础工具包](https://www.intel.com/content/www/us/en/developer/tools/oneapi/base-toolkit.html),因此我们遵循以下说明:
<https://www.intel.com/content/www/us/en/develop/documentation/installation-guide-for-intel-oneapi-toolkits-linux/top.html>
具体来说,我正在使用位于[这里](https://www.intel.com/content/www/us/en/develop/documentation/installation-guide-for-intel-oneapi-toolkits-linux/top/installation/install-using-package-managers/apt.html#apt)的 APT 指令,特别注意准确地遵循安装英特尔 GPU 驱动程序(步骤 2)中的指令。我用 Linux 6.0.x 内核中包含的驱动程序尝试了这一点,遇到了一些问题,所以我建议您尝试 DKMS 指令,并在指令中使用内核 5.17.xxx。
因为我们将使用拥抱脸库,我们安装 Git 和 Git 大文件存储(Git LFS ),因为拥抱脸库需要它们。
sudo apt-get install git git-lfs
## Python 设置
接下来,让我们设置 Python 环境,以便与英特尔 Arc 协同工作。我使用的是 Python 3.9.15,所以如果你有不同版本的 Python,说明可能会稍有不同。
由于这是一个全新的 Ubuntu 安装,由于某种原因我没有安装 pip Python 包管理器,所以快速修复方法是运行:
wget https://bootstrap.pypa.io/get-pip.py
python get-pip.py
安装 pip 有很多种方法,根据你的 Ubuntu 版本,你可以直接使用 APT。
为了保持我们的 Python 环境整洁,我们可以设置 Python virtualenv 模块并创建一个虚拟环境。
python3 -m pip install --user virtualenv
python3 -m venv venv
source venv/bin/activate
之后的每一步都将在我们的 Python 虚拟环境中运行。
## 拥抱面部设置
下一步是建立稳定的扩散。大多数情况下,我们只是按照[拥抱面部的指示来做](https://huggingface.co/blog/stable_diffusion),但我会把它们嵌入以使它更简单。
如果您没有拥抱脸帐户,您需要在此处创建一个:
<https://huggingface.co>
回到我们的系统,我们设置了 Hugging Face Hub 和一些基本库,并从 Hugging Face 中获取了扩散器和稳定的扩散代码:
pip install transformers scipy ftfy huggingface_hub
git clone https://github.com/huggingface/diffusers.git
git clone https://huggingface.co/CompVis/stable-diffusion-v1-4 -b fp16
这些签出应该要求您使用 huggingface-cli 登录,如下所示:
Username for ‘https://huggingface.co': <your_user_name>
Password for ‘https://<your_user_name>@huggingface.co':
最后的步骤是使用 pip 安装扩散器库需要的一些基本组件和库本身,并将其指向扩散器目录:
pip install diffusers
## PyTorch 和用于 PyTorch 安装的英特尔扩展
最后,我们需要设置我们的英特尔 GPU 配置。下载 PyTorch 和 PyTorch 的英特尔扩展:
wget https://github.com/intel/intel-extension-for-pytorch/releases/download/v1.10.200%2Bgpu/intel_extension_for_pytorch-1.10.200+gpu-cp39-cp39-linux_x86_64.whl
wget https://github.com/intel/intel-extension-for-pytorch/releases/download/v1.10.200%2Bgpu/torch-1.10.0a0+git3d5f2d4-cp39-cp39-linux_x86_64.whl
并使用 pip 安装车轮:
source /opt/intel/oneapi/setvars.sh
pip install torch-1.10.0a0+git3d5f2d4-cp39-cp39-linux_x86_64.whl
pip install intel_extension_for_pytorch-1.10.200+gpu-cp39-cp39-linux_x86_64.whl
现在,一切都已就绪,可以在英特尔 Arc GPU 上运行 PyTorch 工作负载了。如果您希望使用低 CPU 内存使用模式,可以安装 accelerate 库。这一步应该在安装 Intel wheels 之后完成,否则您将在 NumPy 中得到一些错误。
pip install accelerate
## 运行稳定扩散
接下来是有趣的部分!通过拥抱脸扩散器库,稳定扩散已准备好使用,英特尔 Arc GPU 已准备好通过 oneAPI 加速它。为了便于运行,我创建了这个简单的 Python 脚本,它提示用户输入,然后打开结果输出图像:
我们可以简单地运行脚本,输入一些内容,然后查看输出:
python run-stable-diffusion.py
Enter keywords:
AI GPU image for medium post
它在这篇文章的顶部输出图像。
# 结论
在独立英特尔 GPU 上支持 PyTorch 和 Tensorflow!虽然许多人对英特尔 GPU 如何影响游戏 GPU 市场感到兴奋,但也有许多人使用 GPU 来加速非游戏工作负载。
正如[英特尔 GPU 对 Blender](https://code.blender.org/2022/09/intel-arc-gpu-support-for-cycles/) 的支持令许多人兴奋一样,对流行的人工智能框架的支持只是英特尔 GPU 故事的另一个里程碑。对于我们这些从事渲染、视频编辑、人工智能和其他计算工作负载的人来说,现在是对英特尔 GPU 感到兴奋的时候了。
*如果你想看看我在看什么科技新闻,你可以在 Twitter 上关注我*<https://twitter.com/tonymongkolsmai>**。此外,一起查看* [*代码*](https://connectedsocialmedia.com/category/code-together/) *,这是我主持的面向开发者的英特尔播客,我们在这里讨论技术。**
**Tony 是英特尔的一名软件架构师和技术宣传员。他开发过多种软件开发工具,最近领导软件工程团队构建了数据中心平台,实现了 Habana 的可扩展 MLPerf 解决方案。**
**英特尔和其他英特尔标志是英特尔公司或其子公司的商标。其他名称和品牌可能是其他人的财产。**
# PyTorch 与 TensorFlow 在基于变压器的 NLP 应用中的比较
> 原文:<https://towardsdatascience.com/pytorch-vs-tensorflow-for-transformer-based-nlp-applications-b851bdbf229a>
## 当使用基于 BERT 的模型时,部署考虑应该是首要的

照片由[皮查拜](https://www.pexels.com/@pixabay?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels)从[派克斯](https://www.pexels.com/photo/gray-scale-photo-of-gears-159298/?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels)拍摄。
*TL;DR: BERT 是 NLP 中一个不可思议的进步。两个主要的神经网络框架都成功地完全实现了 BERT,尤其是在 HuggingFace 的支持下。然而,尽管乍一看 TensorFlow 更容易进行原型设计和部署,但 PyTorch 在量化和一些 GPU 部署方面似乎更有优势。在开始一个基于 BERT 的项目时,应该考虑到这一点,这样你就不必像我们一样中途重新构建代码库。*
像人工智能领域的许多事情一样,机会在于你能多快地改变和适应改进的性能。伯特及其衍生物无疑建立了一个新的基线。它很大,而且掌管一切。(事实上,[我们最近有](https://lemay.ai)这么多基于 BERT 的项目同时启动,我们需要全公司范围的培训,以确保每个人都有相同的编程风格。)
[我们的另一家公司](https://auditmap.ai)最近遇到了一些与基于 Tensorflow 的模型相关的令人头疼的问题,希望你能从中学习。以下是我们在这个项目中学到的一些方面。
# 模型可用性和存储库

购买模特有时感觉像逛市场。[好人](https://www.pexels.com/photo/photo-of-a-pathway-with-gray-and-black-buildings-792832/)在[Pexels.com](https://pexels.com)上的照片。
如果你想使用那些出版物刚出版的模特,你仍然需要通过 GitHub。否则可以直接进入 transformer 模型库 hubs,比如 [HuggingFace](https://huggingface.co/) 、 [Tensorflow Hub](https://www.tensorflow.org/hub) 和 [PyTorch Hub](https://www.tensorflow.org/hub) 。
在 [BERT 发布](https://arxiv.org/abs/1810.04805)几个月后,让它运行起来有点笨拙。自从[拥抱脸](https://huggingface.co/)推动整合[变形金刚模型库](https://arxiv.org/abs/1910.03771)以来,这就有点悬而未决了。由于大多数(几乎所有)模型都可以在 HuggingFace 上正确检索,hugging face 是任何变形金刚的第一和主要来源,所以这些天关于模型可用性的问题越来越少。
然而,在某些情况下,模型只能在专有的存储库中获得。例如,Google 的[通用句子编码器似乎仍然只能在](https://arxiv.org/pdf/1803.11175.pdf) [TensorFlow Hub](https://www.tensorflow.org/hub) 上使用。(在[发布](https://arxiv.org/abs/1803.11175)的时候,这是最好的单词和句子嵌入模型之一,所以这是一个问题,但是它已经被类似 [MPNet](https://huggingface.co/sentence-transformers/all-mpnet-base-v2) 和 [Sentence-T5](https://huggingface.co/sentence-transformers/sentence-t5-base) 的东西取代了。)
在撰写本文时,HuggingFace 上有[2669 个 Tensorflow 模型,相比之下](https://huggingface.co/models?library=tf&sort=downloads)[有 31939 个 PyTorch 模型](https://huggingface.co/models?library=pytorch&sort=downloads)。这主要是由于较新的模型首先作为 PyTorch 模型发布;学术界偏爱 PyTorch 模型,尽管这不是一个普遍的模型。
***要点:PyTorch 有更多的模型,但是主要的模型在两个框架上都可用。***
# 跨库 GPU 争用

纯粹的火力。照片由[娜娜·杜瓦](https://www.pexels.com/photo/industry-technology-grey-music-8622912/)在[Pexels.com](https://www.pexels.com/)拍摄
毫不奇怪,这些利维坦模型有巨大的计算需求,GPU 将在训练和推理周期的不同点上涉及。此外,您可能将这些模型用作 NLP/文档智能管道的一部分,其他库在预处理或自定义分类器期间会争夺 GPU 空间。
幸运的是,有许多流行的库已经在它们的后端使用 Tensorflow 和 PyTorch,所以与其他模型友好相处应该很容易。例如,两个流行的 NLP 库,主要运行在 Torch 上( [1](https://github.com/explosion/spacy-transformers/blob/5a36943fccb66b5e7c7c2079b1b90ff9b2f9d020/requirements.txt#L3) , [2](https://github.com/flairNLP/flair/blob/016cd5273f8f3c00cac119debd1a657d5f86d761/requirements.txt#L2) )。
* *注意:SpaCy 使用*[*Thinc*](https://thinc.ai/docs/usage-frameworks)*来实现框架之间的可互换性,但是我们注意到,如果我们坚持使用基本 PyTorch 模型,会获得更多的稳定性、原生支持和可靠性。*
对于单个框架,在定制的 BERT 模型和特定于库的模型之间共享 GPU 要容易得多。如果您可以共享一个 GPU,那么部署成本就会降低。(稍后在“[量子化](#9499)”中对此有更多介绍。)在理想的部署中,有足够的资源来有效地扩展每个库;事实上,计算与成本之间的制约发生得非常快。
如果您正在运行一个多步骤部署(比如说文档智能),那么您将拥有一些通过将它们转移到 GPU 而得到改进的功能,比如句子化和分类。
PyTorch 具有本机 GPU 增量使用,通常为给定模型保留正确的内存边界。从他们的[到 *CUDA 语义*文档](https://pytorch.org/docs/stable/notes/cuda.html#memory-management):
> PyTorch 使用缓存内存分配器来加速内存分配。这允许在没有设备同步的情况下快速释放内存。然而,由分配器管理的未使用的内存仍然会显示为在`nvidia-smi`中使用过。可以使用`[memory_allocated()](https://pytorch.org/docs/stable/generated/torch.cuda.memory_allocated.html#torch.cuda.memory_allocated)`和`[max_memory_allocated()](https://pytorch.org/docs/stable/generated/torch.cuda.max_memory_allocated.html#torch.cuda.max_memory_allocated)`来监控张量占用的内存,使用`[memory_reserved()](https://pytorch.org/docs/stable/generated/torch.cuda.memory_reserved.html#torch.cuda.memory_reserved)`和`[max_memory_reserved()](https://pytorch.org/docs/stable/generated/torch.cuda.max_memory_reserved.html#torch.cuda.max_memory_reserved)`来监控缓存分配器管理的内存总量。调用`[empty_cache()](https://pytorch.org/docs/stable/generated/torch.cuda.empty_cache.html#torch.cuda.empty_cache)`从 PyTorch 释放所有**未使用的**缓存内存,以便其他 GPU 应用程序可以使用这些内存。但是,张量占用的 GPU 内存不会被释放,因此它不能增加 PyTorch 可用的 GPU 内存量。
与 TensorFlow 相比,tensor flow 具有默认的完全内存接管,您需要[来指定](https://www.tensorflow.org/guide/gpu#limiting_gpu_memory_growth) `[incremental_memory_growth()](https://www.tensorflow.org/guide/gpu#limiting_gpu_memory_growth)`:
> 默认情况下,TensorFlow 会映射进程可见的所有 GPU 的几乎所有 GPU 内存(以`[CUDA_VISIBLE_DEVICES](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#env-vars)`为准)。这样做是为了通过减少内存碎片来更有效地使用设备上相对珍贵的 GPU 内存资源。要将 TensorFlow 限制到一组特定的 GPU,请使用`[tf.config.set_visible_devices](https://www.tensorflow.org/api_docs/python/tf/config/set_visible_devices)`方法。
***外卖:两个框架都在单个 GPU 上具备多模型部署能力,但 Tensorflow 的管理稍逊一筹。小心使用。***
# 量化

在[Pexels.com](https://pexels.com)上 [Pixabay](https://www.pexels.com/photo/low-angle-view-of-lighting-equipment-on-shelf-257904/) 拍摄的照片。
[量化](https://en.wikipedia.org/wiki/Quantization)主要涉及将 [Float64](https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.double) 转换为 [Unsigned Int8](https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.ubyte) 或 [UInt16](https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.ushort) 以减少模型大小和完成单次计算所需的位数,这也是[一种广为接受的模型压缩技术](https://arxiv.org/abs/1710.09282)。这类似于图像的像素化和颜色损失。它还考虑了权重的分布,在它们的通用模型生成管道中, [Tensorflow](https://www.tensorflow.org/lite/performance/post_training_quantization) 和 [PyTorch](https://pytorch.org/docs/stable/quantization.html) 都支持固定和动态范围量化。
量化在模型性能优化中是一个值得采取的步骤,其主要原因是*性能*随时间推移的典型损失(由于延迟增加)比*质量*随时间推移的损失(例如 F1 下降)更昂贵。对此的另一种解释是“现在好不如以后更好”。
有趣的是,我们看到训练后量化的 F1 平均分数下降了 0.005(相比之下,训练中量化的 F1 平均分数下降了 0.03–0.05),对于我们的大多数客户和主要应用程序来说,这是一个可以接受的质量下降,特别是如果这意味着在更便宜的基础设施上和在合理的时间框架内运行的话。
*举个例子:考虑到我们在* [*审计图*](https://auditmap.ai) *应用程序中分析的文本量,我们发现的大部分风险洞察都是有价值的,因为我们能够快速检索它们,向我们的审计师和风险经理客户传达他们的风险状况。我们大多数模型的 F1 分数在 0.85 到 0.95 之间,完全可以接受基于大规模分析的决策支持。*
这些模型确实需要在 GPU 上训练和([通常是](https://paperswithcode.com/paper/distilbert-a-distilled-version-of-bert))运行才能有效。然而,如果我们想只在 CPU 上运行这些模型,我们需要从 Float64 表示转移到 int8 或 uint8,以便在可接受的时间框架内运行。根据我的实验和检索到的例子,我将把我的观察范围限制在以下方面:
**我还没能找到一个简单或直接的机制来量化基于张量流的拥抱脸模型。**
与 PyTorch 相比:
我在 PyTorch 中写的动态量化的一个简单例子。
***要点:PyTorch 中的量化是单行代码,可以部署到 CPU 机器上。Tensorflow 不太流线型。***
# 奖励类别:圈复杂度和编程风格
那么,如果 PyTorch 在它所提供的东西上有如此好的差异化,为什么 TensorFlow 仍然是一个考虑因素?在我看来,这是因为用 TensorFlow 编写的代码具有更少的活动部分,也就是说圈复杂度更低。
[圈复杂度](https://en.wikipedia.org/wiki/Cyclomatic_complexity)是一种软件开发度量,用于评估一段代码中所有可能的代码路径。它被用作全面性、可维护性和每行代码的[错误](https://en.wikipedia.org/wiki/Cyclomatic_complexity#Correlation_to_number_of_defects)的代理。考虑到代码的可读性,类继承是一个循环步骤,而内置函数不是。从机器学习的角度来看,圈复杂度可以用来评估模型训练和推理代码的可读性。
继续往下,PyTorch 受面向对象编程的影响很大,而 Tensorflow(通常,不总是)在其模型生成流程中更程序化。
我们为什么关心?因为复杂会滋生 bug。库越简单,故障诊断和修复就越容易。简单的代码是可读的代码,可读的代码是可用的代码。
在 PyTorch BERT 管道中,圈复杂度随着数据加载器、模型实例化和训练而增加。
让我们看看 FashionMNIST 数据加载器的公开例子。
这是 PyTorch :
PyTorch Exampleimport os
import pandas as pd
from torchvision.io import read_imageclass CustomImageDataset(Dataset):
def init(self, annotations_file, img_dir, transform=None, target_transform=None):
self.img_labels = pd.read_csv(annotations_file)
self.img_dir = img_dir
self.transform = transform
self.target_transform = target_transformdef len(self):
return len(self.img_labels)def getitem(self, idx):
img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
image = read_image(img_path)
label = self.img_labels.iloc[idx, 1]
if self.transform:
image = self.transform(image)
if self.target_transform:
label = self.target_transform(label)
return image, label
这里是 [TensorFlow](https://www.tensorflow.org/tutorials/keras/classification) 预建加载器:
TensorFlow Exampleimport tensorflow as tf
import numpy as npfashion_mnist = tf.keras.datasets.fashion_mnist
(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()
虽然这是 TensorFlow 中的一个预建函数,但它是典型的训练/测试拆分的示例。
(*奖励:有人在 TensorFlow 中用 PyTorch 编写代码:* [*使用 BERT 和 TensorFlow*](/building-a-multi-label-text-classifier-using-bert-and-tensorflow-f188e0ecdc5d) *)*
# 结论
如果您有可用的 GPU,您通常不会看到任何框架之间的任何重大差异。但是,请记住上面提到的边缘情况,因为您可能会发现自己正在重建从一个框架到另一个框架的整个管道。就像我一样。
省时快乐!
马特。
*如果您对本文或我们的 AI 咨询框架有其他问题,请通过*[***LinkedIn***](https://www.linkedin.com/in/mnlemay/)**或通过*[***email***](mailto:matt@lemay.ai)*联系。**
# *你可能喜欢的其他文章*
* *[用于批处理的 MLOps:在 GPU 上运行气流](/mlops-for-batch-processing-running-airflow-on-gpus-dc94367869c6)*
* *数据集偏差:制度化的歧视还是足够的透明度?*
* *[AI 如何创造价值?](https://medium.com/@lsci/how-does-artificial-intelligence-create-value-bec14c785b40)*
* *[实施企业人工智能战略](/implementing-a-corporate-ai-strategy-a64e641384c8)*
* *[离群点感知聚类:超越 K 均值](/outlier-aware-clustering-beyond-k-means-76f7bf8b4899)*
* *[深度学习图像分类器的罗夏测试](/rorschach-tests-for-deep-learning-image-classifiers-68c019fcc9a9)*
# PyWhatKit:如何用 Python 自动化 Whatsapp 消息
> 原文:<https://towardsdatascience.com/pywhatkit-how-to-automate-whatsapp-messages-with-python-9aa73b77389>
## **有 Whatsapp 账号?知道怎么调用 Python 函数吗?这就是你自动化 Whatsapp 信息所需要的一切**

照片由[克里斯蒂安·威迪格](https://unsplash.com/@christianw?utm_source=medium&utm_medium=referral)在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上拍摄
你曾经想自动化你的 Whatsapp 信息吗?现在你可以了,假设你至少有一周的 Python 编程经验。`pywhatkit`库允许你发送单独的 Whatsapp 消息,向群组发送消息,甚至发送图像——所有这些都来自 Python!还有播放 YouTube 视频和浏览互联网的选项,但这些将在接下来的文章中介绍。
今天你将学习如何通过`pywhatkit`和 Python 向自己或他人发送 Whatsapp 消息。这些是我们将关注的领域:
* 如何安装 PyWhatKit
* 如何即时发送 Whatsapp 消息
* 为以后安排 Whatsapp 消息
* 通过 Python 脚本发送 Whatsapp 消息
* 高级用法:组和图像
# 如何安装 PyWhatKit
这个包可以在 [PyPI](https://pypi.org/project/pywhatkit/) 上获得,这意味着你可以通过`pip`安装它。在此之前,让我们基于 Python 3.10 创建一个新的虚拟环境。创建完成后,我们可以发出一个`pip`命令来安装`pywhatkit`:
conda create --name pywhatkit_env python=3.10 -y
conda activate pywhatkit_env
pip install pywhatkit
此外,我还安装了 Jupyter 和 JupyterLab,因为这是我将在本文的第一部分使用的开发环境。您也可以这样做,但是您可以自由地使用文本编辑器。
要安装和启动 Jupyter:
pip install jupyter jupyter lab jupyter lab
这就是我们开始玩`pywhatkit`所需要的。
# 如何即时发送 Whatsapp 消息
假设您已经运行了 Jupyter(或文本编辑器),继续导入库:
import pywhatkit
`sendwhatmsg_instantly()`函数会在你运行代码时发送一条 Whatsapp 消息,因此名字中有“立即”二字。需要两个参数:
* `phone_no` -您要向其发送信息的电话号码。不要忘记包括国家代码。
* `message` -您想要发送的实际信息。
让我们来看看它的实际应用:
pywhatkit.sendwhatmsg_instantly(
phone_no="
message="Howdy! This message will be sent instantly!",
)
如果您没有登录 Whatsapp Web,您会看到此屏幕提示您用手机扫描二维码:

图 1 — Whatsapp 网络登录提示(图片由作者提供)
扫描完成后,您将进入一个新的聊天屏幕,您的信息将显示在输入区。出于某种原因,邮件不会自动发送,你必须手动点击发送按钮。这是库中的一个**错误**,可能会在未来的版本中得到修复。
稍后我会告诉你如何解决这个**错误**,但是现在,只要点击发送按钮。
你会在 Whatsapp 网站和手机上看到以下内容:

图 2 —通过 PyWhatKit 发送第一条消息(图片由作者提供)
这是你发送的第一条 Whatsapp 消息!
但是,如果您希望将消息安排在特定的时间,该怎么办呢?接下来让我们讨论这个选项。
# 为以后安排 Whatsapp 消息
有时您希望在特定的时间点发送消息。`pywhatkit`包有一个专用于被调用的`sendwhatmsg()`的函数。除了`phone_no`和`message`之外,还需要另外两个参数:
* `time_hour` -整数,表示您要发送消息的小时(24 小时制)。
* `time_min` -整数,表示您要发送消息的分钟。
它的工作方式与我们之前的例子几乎相同:
pywhatkit.sendwhatmsg(
phone_no="
message="This is a scheduled message.",
time_hour=9,
time_min=47
)
但是与前面的例子不同,这个函数将输出发送消息前的剩余时间:

图 3 —使用 PyWhatKit 的消息调度(图片由作者提供)
在这 53 秒之后,Whatsapp 网络浏览器窗口将会打开,您将会看到输入字段中填充的消息文本。和以前一样,邮件不会自动发送,你必须手动点击发送按钮。
发送后,您将在网络和手机聊天中看到该消息:

图 4 —通过 PyWhatKit 发送预定消息(图片由作者提供)
现在你知道如何即时发送信息,以及如何安排它们。如果我们不必每次都手动点击按钮来发送消息,那该多好。
当开发人员致力于修复这个 bug 时,让我们来看看如何解决它。
# 通过 Python 脚本发送 Whatsapp 消息
您需要两个额外的 Python 库来自动触发 Send 按钮。这些是`pyautogui`和`pynput`。
`send_whatsapp_message()`功能执行以下操作:
1. 打开 Whatsapp Web 并用指定的消息填充输入字段。
2. 休眠 10 秒钟,以确保所有内容都已正确加载。
3. 单击屏幕以确保选择了正确的窗口/选项卡。
4. 按下并释放键盘上的 Enter 键以发送消息。
如果任何步骤失败,异常将被打印到控制台。
下面是该函数的完整代码片段和一个使用示例:
import time
import pywhatkit
import pyautogui
from pynput.keyboard import Key, Controller
keyboard = Controller()
def send_whatsapp_message(msg: str):
try:
pywhatkit.sendwhatmsg_instantly(
phone_no="
message=msg,
tab_close=True
)
time.sleep(10)
pyautogui.click()
time.sleep(2)
keyboard.press(Key.enter)
keyboard.release(Key.enter)
print("Message sent!")
except Exception as e:
print(str(e))
if name == "main":
send_whatsapp_message(msg="Test message from a Python script!")
我已经将这段代码保存到了`tester.py`文件中,现在我将通过终端运行它:
python tester.py

图 5-通过终端运行 Python 脚本(图片由作者提供)
您将看到相同的 Whatsapp Web 浏览器标签打开,消息字段填充,但这次消息将在几秒钟后发送,无需您的干预。
以下是完整的聊天记录:

图 6 —通过 Python 发送最后一条自动消息(图片由作者提供)
这就是你如何解决库中当前的 bug。在今天结束之前,让我们简要回顾一些更高级的用例。
# 高级用法:组和图像
还可以给 Whatsapp 群发消息,用`pywhatkit`发图片。请参考库中内置的这些方便的函数:
* `sendwhatmsg_to_group(group_id: str, message: str, time_hour: int, time_min: int)` -用于向一个群组发送消息。
* `sendwhatmsg_to_group_instantly(group_id: str, message: str)` -就像前面的函数一样,但是在您运行代码时立即发送消息。
* `sendwhats_image(receiver: str, img_path: str)` -立即向一个号码或一个群组发送图像。有一个可选的`caption`参数用来给图片加标题。请记住,目前不支持 PNG 文件格式。
这就是 Python 库的全部内容,至少对于 Whatsapp 来说是这样。你可以自己随意摆弄这三个功能,我相信你能搞定!
该库还为 YouTube 和一般的 web 浏览打包了额外的功能,但是我将把这些主题留待以后讨论。
喜欢这篇文章吗?成为 [*中等会员*](https://medium.com/@radecicdario/membership) *继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。*
<https://medium.com/@radecicdario/membership>
# 保持联系
* 雇用我作为一名技术作家
* 在 YouTube[上订阅](https://www.youtube.com/c/BetterDataScience)
* 在 [LinkedIn](https://www.linkedin.com/in/darioradecic/) 上连接
*原载于 2022 年 10 月 27 日*[*【https://betterdatascience.com】*](https://betterdatascience.com/pywhatkit-python-whatsapp/)*。*
# q-学习算法:如何成功地教一个智能代理玩游戏
> 原文:<https://towardsdatascience.com/q-learning-algorithm-how-to-successfully-teach-an-intelligent-agent-to-play-a-game-933595fd1abf>
## 强化学习
## 通过一个循序渐进的 Python 示例详细解释了一种称为 Q-Learning 的强化学习算法

Q 学习的强化学习。图片由[作者](https://solclover.com/)提供。
# **简介**
**强化学习(RL)** 是机器学习的一个令人兴奋的部分,它帮助我们创造出能够执行各种任务的**智能代理**。
在这一系列文章中,我将深入研究不同的 RL 技术,并向您展示如何使用 Python 代码来应用它们。这一次我将讨论 Q-Learning(Q 代表质量),这是最直接的 RL 方法之一。
如果你对 RL 的主要思想不熟悉,可以随意参考我的介绍文章:[强化学习(RL)——它是什么,是如何工作的?](/reinforcement-learning-rl-what-is-it-and-how-does-it-work-1962cf6db103)
# **内容**
* Q-Learning 在更广泛的机器学习(ML)宇宙中处于什么位置
* 强化学习术语
* Q-Learning 是如何工作的?
-基于策略和基于值的方法之间的区别
- Q 函数和 Q 表
- Q 学习算法
- Q 学习输出
* Python 示例向您展示了如何训练一个代理来玩一个冰湖游戏
# 机器学习领域中的 q 学习
下图是我对最常用的机器学习算法进行分类的尝试。当然,它永远不会是完美的,因为一些算法可以分为多个类别。然而,观想 ML 宇宙是非常有帮助的,尤其是在学习过程中。
Q-Learning 属于强化学习算法家族,更具体地说,属于基于值的方法分支(在下面的章节中有更多的介绍)。
下面的**图是交互式的**,请点击👇在不同的类别上探索和揭示更多。
机器学习算法分类。互动图表由[作者](https://solclover.com/)创作。
***如果你喜欢数据科学和机器学习*** *,请* [*订阅*](https://bit.ly/3sItbfx) *获取我的新文章邮件。如果不是中等会员,可以在这里* *加入* [*。*](https://bit.ly/36Mozgu)
# 强化学习术语
在我们研究 Q-Learning 之前,先快速回顾一下强化学习的基本要素:
* **代理**——可以与其环境交互的“智能角色”,例如游戏中的玩家。
* **环境**——代理“生活”或操作的“世界”。
* **动作空间** —代理可以执行的动作的列表或范围。
* **状态/观察空间** —可能的环境配置的列表或范围。状态/观察向代理提供关于其环境的信息(例如,其位置)。
* **奖励** —当代理在不同状态下执行期望的(不期望的)行为时,我们给予代理的激励(或抑制)。我们可以通过使用**贴现因子{gamma(𝛾)}** 来减少相对于当前奖励的未来奖励。
* **探索/开发{epsilon(𝜖)}** —使我们能够设置代理应该花费多少时间来探索环境和开发其关于环境的现有知识。
* **第**集——从开始位置到结束位置的一个完整循环。例如,在游戏的上下文中,一集将从你的代理开始新的级别的时刻持续到它死亡或完成该级别。
* **Alpha(𝛼)** —学习率,它影响学习速度和向最优策略的收敛。
* **policy(𝜋)**—代理人追求目标的策略。
# Q-Learning 是如何工作的?
## **基于政策和基于价值的方法之间的差异**
Q-Learning 属于**基于价值的方法**的范畴,所以让我们从理解基于价值的方法和基于政策的方法之间的区别开始。
* **基于策略的方法** —我们直接训练代理**在哪个状态下采取什么行动**。它由一个**策略函数**描述,该函数可以是**确定性的**(给出每个状态的精确动作)或**随机性的**(提供动作的概率分布)。
* **基于价值的方法** —我们通过教代理**识别哪些状态(或状态-动作对)更有价值来间接训练代理**,以便它可以被价值最大化所引导。它由一个**价值函数**描述,其中一个状态的价值是代理在该状态开始时可以获得的预期贴现回报。
不管我们使用哪种方法来训练我们的代理,找到最优策略函数或最优值函数等同于发现**最优 policy(𝜋).**
## **Q 功能和 Q 表**
由于 Q-Learning 是一种基于价值的方法,我们必须有一个**价值函数**,我们称之为 **Q 函数**。在内部,它将有一个包含每个**状态动作对**的 **Q 表**。
训练一个 Q 函数就是简单地找到与存储在 Q 表中的每个状态-动作对相关联的值。了解这些值使代理能够在每个状态下选择最佳操作。
在后面的 Python 部分,我们将教代理玩一个冰湖游戏,所以让我们用同一个游戏来演示 Q 表是什么样子的。
我们的冰湖环境将是一个 4x4 的网格,由冰冻的正方形和带孔的正方形组成,总共 16 个正方形。每个方块代表一种可能的状态,我们可以通过给它们编号来标记。

[冰湖游戏](https://www.gymlibrary.dev/environments/toy_text/frozen_lake/?highlight=frozen+lake)状态空间,每个状态分配有数字。图片由[作者](https://solclover.com/)提供。
**动作空间**将由我们的代理可以采取的 4 个不同动作组成:**向左(0)、向下(1)、向右(2)、向上(3)** 。
现在我们可以形成一个 Q 表,状态作为行,动作作为列。

空 Q 表。图片由[作者](https://solclover.com/)提供。
游戏的目标是让代理人从起点(方块 0)导航到终点(包含礼物的方块 15)而不掉进洞里。因此,训练一个 Q 函数将教会代理如何避免漏洞,找到到达目标的最快路线。
*请注意,当我们第一次初始化 Q-table 时,它在每个单元格中都有零,因为代理在开始探索它之前不知道任何关于它的环境的信息。*
## **Q-学习算法**
在我们研究实际的 Q-Learning 算法之前,还有几件事需要注意:
* **On-policy vs Off-policy:**Q-Learning 是一种 **off-policy** 算法,这意味着在训练过程中,我们使用不同的策略让 agent 进行动作(acting policy)和更新 Q-function (updating policy)。同时,On-policy 意味着相同的策略用于动作和更新。关于这一点,请参阅 Python 部分。
* **时间差分(TD) vs 蒙特卡罗:** Q-Learning 使用一种 **TD 方法**,这意味着在训练过程中,它在每一步之后更新 Q-function。同时,蒙特卡罗方法是等到一集结束后再对价值函数进行更新。
现在让我们来看看 Q 学习算法,看看它如何训练 Q 函数(更新 Q 表):
* Q-Q 函数
* 𝑆𝑡——现状(观察)
* 𝐴𝑡——当前行动
* 𝑅𝑡+1 —当前行动后收到的奖励
* 𝑆𝑡+1——下一个国家(观察)
* 𝛼(阿尔法)—学习率参数
* 𝛾(伽玛)-折扣系数参数
* 𝑚𝑎𝑥𝑎𝑄(𝑆𝑡+1,𝑎) —在可能的动作空间中,下一个状态(观察)的最大值

q-学习算法。图片由[作者](https://solclover.com/)提供。
* 左边 Q(𝑆𝑡,𝐴𝑡的项)是特定状态-动作对的新值**。**
* 右手边的第一项,Q(𝑆𝑡,𝐴𝑡),是同一状态-动作对的**电流值**。
* 为了修改当前值,我们将代理𝑅𝑡+1 采取行动后的**奖励**加上我们可以从下一个状态𝛾𝑚𝑎𝑥𝑎𝑄(𝑆𝑡+1,𝑎**中得到的**最大值乘以γ**,并减去**当前值** Q(𝑆𝑡,𝐴𝑡).**
* 因此,方括号中的项可能导致正值、零值或负值。因此,该状态-动作对的新 Q 值将增加、保持不变或减少。请注意,我们还应用了一个学习率来控制每次更新的“大小”。
由于 Q-Learning 使用**时间差异(TD)方法**,该算法将在每一步之后不断更新 Q-table,直到我们到达一个不能再进行更新的位置,即它已经收敛到一个最优解。
## q-学习输出
得出 q 函数的最优解意味着我们已经找到了最优的**policy(𝜋】**。然后,代理可以使用此策略成功地导航其环境,并根据起始状态获得可能的最高累积奖励。
下面是一个冰湖游戏的最佳 q 表,使用 0.95 的𝛾 (gamma)和游戏的默认奖励函数:
* 实现目标(G): +1
* 到达孔(H): 0
* 达到冻结状态(F): 0

为[冰湖游戏](https://www.gymlibrary.dev/environments/toy_text/frozen_lake/?highlight=frozen+lake)优化的 q 表。图片来自[作者](https://solclover.com/)。
我们将在 Python 部分看到如何得到这个结果,但是现在,让我们看看它告诉我们什么。
如果我们的代理使用上述 Q 表中描述的政策,那么:
* 从状态 0 开始,向下或向右都同样有价值。根据 Q 表中的动作顺序,代理将进入状态 4。
* 从状态 4 开始,最高值动作是再次下降,因此它将这样做,到达状态 8。
* 从状态 8 开始,最高值动作是向右,将代理带到状态 9。
* 从状态 9 开始,向下或向右同样有价值。但是,由于基于 Q-表中的动作顺序,down 首先出现,代理进入状态 13。
* 从状态 13,它将向右到状态 14,然后再向右到状态 15,达到目标。
这是遵循上述政策的代理的 gif 图像。

Gif 图片由[作者](https://solclover.com/)使用来自[冰湖游戏](https://www.gymlibrary.dev/environments/toy_text/frozen_lake/?highlight=frozen+lake)的组件创建。
[](https://solclover.com/membership)[](https://www.linkedin.com/in/saulius-dobilas/)
# **Python 示例向你展示了如何训练一个代理玩冰湖游戏**
现在让我们使用 Python 来实现我们刚刚学到的内容。我们将使用 [Open AI 的健身房 Python 库](https://www.gymlibrary.dev/environments/toy_text/frozen_lake/?highlight=frozen+lake)包含各种游戏环境,包括冰封湖。
## 设置
我们需要获得以下库:
* [为](https://www.gymlibrary.dev/)[冰湖](https://www.gymlibrary.dev/environments/toy_text/frozen_lake/?highlight=frozen+lake)游戏环境打开艾的健身房库
* [Numpy](https://numpy.org/) 用于数据操作
* [Matplotlib](https://matplotlib.org/stable/index.html) 和内置的 **IPython** 和 **time** 库,用于显示代理如何导航其环境
让我们导入库:
上面的代码打印了本例中使用的包版本:
numpy: 1.23.3
gym: 0.26.0
matplotlib: 3.6.0
接下来,我们建立一个冰湖环境:
注意,我们在这个例子中使用了一个不滑的游戏版本。同时,渲染选项如下:
* **无(默认):**不计算渲染。
* **人类:**渲染返回无。环境在当前显示器或终端中被连续渲染,通常用于人类消费。
* **rgb_array:** 返回代表环境状态的帧。帧是具有 shape (x,y,3)的 numpy.ndarray,表示 x 乘 y 像素图像的 RGB 值。
* **ansi:** 返回字符串列表(str)或 StringIO。StringIO,包含每个时间步长的终端样式文本表示。文本可以包括换行符和 ANSI 转义序列(例如颜色)。
让我们检查我们在上面设置的环境的环境描述、状态空间和动作空间:
Environment Array:
[[b'S' b'F' b'F' b'F']
[b'F' b'H' b'F' b'H']
[b'F' b'F' b'F' b'H']
[b'H' b'F' b'F' b'G']]State(Observation) space: Discrete(16)
Action space: Discrete(4)
您可以看到环境与上一节中显示的布局相匹配。但是,您可以在 gym.make()中使用 **desc** 选项来指定您自己的地图。提醒:S =开始,F =冻结,H =球洞,G =进球。
同样,正如所料,状态空间包含 16 个离散状态(4x4),动作空间有 4 个离散动作(0:左,1:下,2:右,3:上)。
作为最后一项检查,在我们进行任何培训之前,我们将让我们的代理在其环境中自由活动(在每个步骤中采取随机行动),并将其呈现在 Jupyter 笔记本中。
以下是上述代码的输出:

由[作者](https://solclover.com/)使用来自[冰湖游戏](https://www.gymlibrary.dev/environments/toy_text/frozen_lake/?highlight=frozen+lake)的组件创建的 Gif 图像。
## **训练 Q 函数以找到最佳策略**
设置完成后,让我们使用 **Q-Learning** 为我们的代理在这个游戏中找到最好的 **policy(𝜋)** 。
我们首先初始化几个参数:
请注意,我们将在整个培训过程中改变 epsilon。我们将从ε= 1 开始,这意味着我们的代理人的行为在开始时都是随机的。但是,我们会随着每一集而衰减 epsilon,所以我们的代理人逐渐从纯探索走向剥削。
接下来,让我们初始化 Q 表。正如我们在上一节中看到的,这将是一个 16x4 的表格,其中 16 行代表 16 个状态,4 列代表 4 个可能的动作。我们用全 0 初始化 Q 表,因为在开始训练之前,我们不知道每个状态的价值。
上述代码显示初始化的 Q 表:
array([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
回想一下,Q-Learning 是一种**非策略**算法。因此,我们将为**动作** (epsilon_greedy)定义一个函数,为**更新 Q 表(update_Q)定义另一个函数。更新策略使用贪婪方法,即没有探索。**
您应该能够发现 update_Q 函数包含前一节中分析的 Q 学习算法等式。
最后,让我们定义我们的培训功能:
现在让我们调用训练函数,看看结果:
在训练之后,我们得到了优化的 Q 表,它与我们在上一节中展示的结果相匹配。现在,代理可以使用它来始终达到目标,而不会掉进洞里。
array([[0.73509189, 0.77378094, 0.77378094, 0.73509189],
[0.73509189, 0. , 0.81450625, 0.77378094],
[0.77378094, 0.857375 , 0.77378094, 0.81450625],
[0.81450625, 0. , 0.77378094, 0.77378094],
[0.77378094, 0.81450625, 0. , 0.73509189],
[0. , 0. , 0. , 0. ],
[0. , 0.9025 , 0. , 0.81450625],
[0. , 0. , 0. , 0. ],
[0.81450625, 0. , 0.857375 , 0.77378094],
[0.81450625, 0.9025 , 0.9025 , 0. ],
[0.857375 , 0.95 , 0. , 0.857375 ],
[0. , 0. , 0. , 0. ],
[0. , 0. , 0. , 0. ],
[0. , 0.9025 , 0.95 , 0.857375 ],
[0.9025 , 0.95 , 1. , 0.9025 ],
[0. , 0. , 0. , 0. ]])
## 估价
让我们通过运行一些模拟来评估这个策略,并检查代理是否总是设法获得最大的回报。
上面的代码打印出以下结果:
Mean Reward = 1.00 +/- 0.00
如你所见,在测试的 100 集每集中,代理人设法获得了最高奖励(1.00)。
让我们通过让代理遵循策略并将其呈现在屏幕上来直观地评估它:
正如我们在上一节中看到的那样,结果是意料之中的:

Gif 图片由[作者](https://solclover.com/)使用来自[冰湖游戏](https://www.gymlibrary.dev/environments/toy_text/frozen_lake/?highlight=frozen+lake)的组件创建。
# **结束语**
我们已经成功地使用 Q-Learning 为代理人找到了在冰湖游戏中使用的最佳策略。我希望这款游戏的简单本质能让你很容易理解 Q-Learning 是如何工作的。
请不要忘记[订阅](https://solclover.com/subscribe),这样您就可以**从我即将发表的文章**中学习其他强化学习算法,并了解如何使用 **Python 将它们应用到不同的环境中!**
你可以在我的 [**GitHub 库**](https://github.com/SolClover/Art056_RL_Q_Learning) 上找到本文使用的完整 Python 代码,作为 Jupyter 笔记本。
干杯!🤓
**索尔·多比拉斯**
如果你已经花光了这个月的学习预算,下次请记得我。 *我的个性化链接加入媒介:*
<https://bit.ly/3J6StZI>
# q-初学者学习
> 原文:<https://towardsdatascience.com/q-learning-for-beginners-2837b777741>
## 训练一个 AI 来解决冰冻的湖泊环境

作者图片
T 这篇文章的目标是**教人工智能如何使用强化学习解决❄️Frozen 湖环境问题**。我们不再阅读维基百科的文章和解释公式,而是从零开始,尝试重新创建🤖q-自学算法。我们不仅要了解**它是如何工作的**,更重要的是**它为什么会工作**:为什么要这样设计?有哪些隐藏的假设,常规课程和教程中从来不解释的细节?
在本文结束时,您将**掌握 Q 学习算法**,并能够**将其应用于其他环境和现实世界的问题**。这是一个很酷的迷你项目,让人们更好地了解强化学习是如何工作的,并且有望激发原创应用的想法。
让我们首先安装❄️ **冰封湖**环境并导入必要的库:`gym`用于游戏,`random`用于生成随机数,`numpy`用于做一些数学运算。
# ❄️岛冰封湖
现在,让我们来谈谈我们将在本教程中解决的游戏。❄️ **冰湖**是一个由瓷砖组成的简单环境,在这里 AI 必须**从最初的瓷砖**移动到**目标**。瓷砖可以是一个安全的✅冰湖,也可以是一个让你永远陷进去的❌。人工智能或代理有 4 种可能的动作:走◀️ **左**,🔽**向下**,▶️ **向右**,或者🔼**涨**。代理必须学会避开洞,以便**在**最少的动作**中达到目标**。默认情况下,环境**总是处于相同的配置**。在环境代码中,**每个图块由字母**表示,如下所示:
S F F F (S: starting point, safe)
F H F H (F: frozen surface, safe)
F F F H (H: hole, stuck forever)
H F F G (G: goal, safe)

作者图片
我们可以尝试手动解决上面的例子来理解游戏。看看下面的动作顺序是不是正确的解法:**右** → **右** → **右** → **下** → **下** → **下**。我们的代理人从瓦**s**开始,所以我们在冰冻的表面上向右移动✅,然后再一次是✅,然后再一次是✅,然后我们下去找一个洞❌.
其实找几个正确的解法真的很容易:**右** → **右** → **下** → **下** → **下** → **右**就是很明显的一个。但是我们可以做一系列动作,在到达目标之前绕着一个洞转 10 圈。这个序列是有效的,但是它不满足我们的最终需求:**代理需要在最少的动作数内达到目标**。在这个例子中,完成游戏的最少动作数是 **6** 。我们需要记住这个事实,以检查我们的代理商是否真的掌握了❄️ **冰封湖**。

作者图片
让我们通过`gym`库来初始化环境。游戏有两个版本:一个有**滑冰**,其中选择的动作有**随机几率被代理**忽略;和一个**防滑器**,其中**的动作不能忽略**。我们将使用**防滑**开始,因为它更容易理解。
🟥FFF
FHFH
FFFH
HFFG
我们可以看到,创建的游戏**与我们的示例**具有完全相同的配置:这是相同的谜题。我们代理的位置由一个红色矩形**表示。解决这个难题可以用一个简单的脚本和 if…else 条件来完成,这实际上对于比较我们的人工智能和一个更简单的方法是有用的。但是,我们想尝试一个更刺激的方案:**强化学习**。**
# 🏁二。q 表
在 ❄️ **冰封湖**中,有 16 个瓷砖,这意味着我们的代理可以在 16 个不同的位置找到,称为**状态**。对于每个状态,有 4 种可能的动作:走◀️ **左**,🔽**向下**,▶️ **向右**,以及🔼**向上**。学习如何玩冰封湖就像**学习在每个状态下你应该选择哪个动作**。为了知道在给定的状态下哪一个动作是最好的,我们想给我们的动作分配一个**质量值**。我们有 16 个状态和 4 个动作,所以要计算 16 x 4 = 64 个值。
表示它的一个好方法是使用一个表格,称为 Q-table,其中**行列出每个州的**,而**列列出每个动作 a** 。在这个 Q-table 中,每个单元格都包含一个值 Q(s,a),它是状态 s 中动作 a 的**值(如果是可能的最佳动作,则为 1;如果真的很差,则为 0)。当我们的代理处于特定状态 s 时,它**只需检查这个表,看看哪个动作具有最高值**。采取价值最高的行动是有道理的,但是我们稍后会看到我们可以设计出更好的东西**
*Q 表示例,其中每个单元格包含给定状态* s *(行)*下动作 a *(列)的值* Q(a,s)
*让我们创建我们的 Q 表并用零填充它,因为**我们仍然不知道每个状态**中每个动作的值。*
Q-table =
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]
*太好了!我们的 Q-table 有 16 行(我们的 16 个州)和 4 列**(我们的 4 个动作),正如预期的那样。让我们试着看看下一步我们能做什么:每个值都被设置为零,所以我们完全没有信息。假设代理采取了一个**随机动作** : ◀️ **左**,🔽**向下**,▶️ **向右**,或者🔼**向上**。***
***我们可以使用带有`choice`方法的`random`库来随机选择一个动作。***
'LEFT'
***等等,实际上代理当前处于初始状态 **S** ,这意味着只有两个动作是可能的:▶️ **右**和🔽**向下**。代理也可以采取措施🔼**向上**和◀️ **向左**,但是它不动:它的状态不变。因此,我们**不会对可能的行为**施加任何约束:代理人**自然会理解他们中的一些人不做任何事情**。***
***我们可以继续使用`random.choice()`,但是`gym`库**已经实现了随机选择一个动作**的方法。以后可能会省去我们一些麻烦,所以让我们试试吧。***
0
***哎哟...这次是一个**号**。我们可以阅读`[gym](https://gym.openai.com/docs/)` [的文档](https://gym.openai.com/docs/),但不幸的是,这是非常稀缺的。不过不用担心, [**我们可以查看 GitHub**](https://github.com/openai/gym/blob/master/gym/envs/toy_text/frozen_lake.py#L10) 上的源代码来理解**这些数字的含义**。这其实非常简单:***
◀️ LEFT = 0
🔽 DOWN = 1
▶️ RIGHT = 2
🔼 UP = 3
******
***作者图片***
***好了,现在**我们了解了** `**gym**` **如何将数字与方向**联系起来,让我们试着用它来**将我们的代理移动到右边的** ▶️.这一次,可以使用`step(action)`方法来执行。我们可以尝试**直接给它提供数字 2** ,对应我们选择的方向(右),检查代理是否移动。***
(Right)
S🟥FF
FHFH
FFFH
HFFG
*****万岁**!红色方块从初始状态 **S** 向右移动:**我们的预测是正确的**。为了与环境互动,我们只需要知道这些:***
1. ***如何**使用`action_space.sample()`随机选择一个动作**;***
2. ***如何**执行该动作,并使用`step(action)`将我们的代理移动到期望的方向**。***
***为了完全详尽,我们可以加上:***
1. ***如何**显示当前地图,看看我们在用`render()`做什么**;***
2. ***如何**重新开始游戏**当代理人掉进一个洞或者用`reset()`到达目标 **G** 。***
***既然我们理解了如何与我们的`gym`环境交互,让我们回到我们的算法。在强化学习中,**智能体在完成预定目标**时会得到环境的奖励。在❄️ **冰封湖**中,代理人只有达到状态 **G** 才会获得奖励(见[源代码](https://github.com/openai/gym/blob/master/gym/envs/toy_text/frozen_lake.py#L85))。我们无法控制这个奖励,它是在环境中设置的:**当代理达到 G 时为 1,否则为 0**。***
***让我们每次执行一个动作就把它打印出来。奖励通过`step(action)`的方式给出。***
(Left)
🟥FFF
FHFH
FFFH
HFFG
Reward = 0.0
***奖励确实是 0…😱哇,我想我们陷入困境了,因为在整个游戏中只有一个州能给我们正奖励。当我们唯一的确认是在最后的时候,我们怎么能从一开始就选择正确的方向呢?如果我们想要看到 1 的奖励,我们需要足够幸运,能够**偶然找到正确的行动顺序。不幸的是,这正是它的工作方式…**Q 表将保持填充零,直到代理随机达到目标 G** 。*****
***如果我们能有中间的、较小的奖励来引导我们走向目标,问题会简单得多。唉,这实际上是强化学习的**主要问题之一:这种被称为**稀疏奖励**的现象,使得代理很难在问题**上接受训练,因为唯一的奖励是在一长串动作**的结尾。人们提出了不同的技术来缓解这个问题,但我们将在另一个时间讨论它。*****
# ***🤖三。q 学习***
***让我们回到我们的问题上来。好吧,我们需要足够幸运,偶然发现目标 **G** 。但是一旦完成,如何将信息反向传播到初始状态?这🤖Q 学习算法为这个问题提供了一个巧妙的解决方案。我们需要更新我们的状态-动作对(Q 表中的每个单元格)的值,考虑 1/到达下一个状态的**奖励**,以及 2/下一个状态中的**最高可能值。*****
******
***作者图片***
***我们知道,当我们移动到 **G** 时,我们得到的回报是 1。正如我们刚才所说的,G (姑且称之为 **G-1** )与**到达 G** 的相关动作的**的值由于奖励而增加。好的,这一集的结尾:代理赢了,我们重新开始游戏。现在,下一次代理处于 G-1** 旁边的 **a 状态时,它会用**的相关动作增加这个状态的值(姑且称之为 **G-2** )到达 G-1** 。下一次代理处于 **G-2** 旁边的状态时,它也会这样做。清洗并重复,直到更新达到初始状态 **S** 。*****
*让我们试着找到**更新公式**将值从 **G** 反向传播到 **S** 。记住:数值表示在特定状态下**一个动作的**质量**(0 表示很糟糕,1 表示在这种状态下可能是最好的动作)。我们尝试**在 sₜ状态下(例如,当代理处于初始状态 **S** 时,sₜ = 0)更新动作 aₜ的值**(例如,如果动作为左,则 aₜ=为 0)。这个**值只是我们的 q 表**中的一个单元格,对应于**行号 s** ₜ **和列号 a** ₜ:这个值的正式名称是 aₜ).q(sₜ***
*如前所述,我们需要使用 1/ **下一个状态**(正式标注为 rₜ)的奖励,以及 2/ **下一个状态** (maxₐ *Q(s* ₜ₊₁,a))的最大可能值来更新它。因此,更新公式必须如下所示:*
**
*新值是当前值+奖励值+下一个状态的最高值。我们可以手动尝试我们的公式来检查它看起来是否正确:让我们假设我们的代理人第一次在目标 G 旁边的状态 G-1 中**。我们可以用以下方式更新对应于此状态下获胜动作的值 **G-1** :***
**
*其中 Q(G-1,aₜ) = 0 和 maxₐ *Q(G* ,a) = 0 是因为 q 表是空的,rₜ *= 1* 是因为我们在这个环境中得到唯一的奖励。我们得到 Q{new}(G-1,aₜ) = 1。下次代理处于下一个状态( **G-2** )时,我们也使用公式更新它,得到相同的结果: *Q* {new}(G-2,aₜ) = 1。最后,**我们将 Q 表**中的 1 从 **G** 反向传播到 **S** 。好吧,它工作了,但是结果是**二元的**:要么是**错误的状态-动作对,要么是最好的状态-动作对**。我们想要更多的细微差别…*
*其实我们差不多**用常识找到了真正的 Q-learning 更新公式**。我们寻找的细微差别增加了两个参数:*
* ***α** 是💡**学习率**(在 0 和 1 之间),也就是我们应该改变多少原来的 q(sₜ(aₜ)值。如果α = 0,数值**永远不变**,但如果α = 1,数值**变化极快**。在我们的尝试中,我们没有限制学习速率,所以α = 1。但这在现实中太快了:下一个状态的奖励和最大值很快**压倒了当前值**。我们需要在过去和新知识的重要性之间找到一个平衡。*
* ***γ** 是📉**贴现因子**(0 到 1 之间),决定了相对于眼前的回报,代理人对未来回报的关心程度(俗话说“一鸟在手胜过双鸟在林”)。如果γ = 0,代理只关注**即时奖励**,但是如果γ = 1,任何**潜在的未来奖励与当前奖励**具有相同的价值。在❄️ **冰封湖**,我们想要一个高折扣系数,因为在游戏的最后只有一个可能的奖励。*
*使用实数 Q 学习算法,新值计算如下:*
**
*好吧,让我们在实施这个新公式之前先试一试。再一次,我们可以假装我们的代理人是第一次目标 G 旁边的**。我们可以使用我们的公式更新状态-动作对以赢得游戏:Q{new}(G-1,aₜ)= 0+α(1+γ00)*。*我们可以给α和γ赋值任意值来计算结果。当α = 0.5,γ = 0.9 时,我们得到 Q{new}(G-1,aₜ)= 0+0.5(1+0.9 0)= 0.5。代理第二次处于这种状态时,我们会得到:Q{new}(G-1,aₜ)= 0.5+0.5(1+0.9 0.5)= 0.75,然后是 0.875,0.9375,0.96875,等等。***
******
***作者图片***
***所以用代码训练我们的特工意味着:***
1. *****选择一个随机动作**(使用`action_space.sample()`)如果当前状态的值正好为零。否则,我们用函数`np.argmax()`取当前状态下值最高的**动作;*****
2. *****通过`step(action)`向所需方向移动来执行此动作**;***
3. *****使用关于新状态的信息和`step(action)`给出的奖励,用我们采取的动作更新原始状态的值**;***
***我们不断重复这 3 个步骤,直到代理**卡在洞内**或**到达目标 G** 。当它发生时,我们只需**用`reset()`重启环境**,开始新的一集,直到我们达到 1000 集。此外,我们可以绘制每次运行的**结果(如果没有达到目标,则为失败,否则为成功),以便**观察我们代理的进度**。*****
***Q-table before training:
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]
===========================================
Q-table after training:
[[0. 0. 0.59049 0. ]
[0. 0. 0.6561 0. ]
[0. 0.729 0. 0. ]
[0. 0. 0. 0. ]
[0. 0.02050313 0. 0. ]
[0. 0. 0. 0. ]
[0. 0.81 0. 0. ]
[0. 0. 0. 0. ]
[0. 0. 0.17085938 0. ]
[0. 0. 0.49359375 0. ]
[0. 0.9 0. 0. ]
[0. 0. 0. 0. ]
[0. 0. 0. 0. ]
[0. 0. 0. 0. ]
[0. 0. 1. 0. ]
[0. 0. 0. 0. ]]***
******
***作者图片***
***特工训练有素!图上的每个蓝条对应一次胜利,因此我们可以看到代理在培训开始时很难找到目标。但是一旦它连续几次发现,就开始**一致赢**。🥳训练过的 q 表也很有趣:这些值表明了代理为达到目标而学习的独特的行动顺序。***
***现在通过 100 集的评测来看看它的表现如何。我们认为培训已经结束,因此**我们不再需要更新 Q 表**。要查看代理的表现,我们可以**计算它设法达到目标**的次数百分比(成功率)。***
Success rate = 100.0%
***我们的代理不仅接受了培训,而且成功地达到了 100%的成功率。大家干得好,防滑的❄️ **冰湖**解决了!***
***我们甚至可以**通过执行下面的代码来可视化代理在地图**上的移动,并打印**所采取的动作序列**来检查它是否是最好的一个。***
(Right)
SFFF
FHFH
FFFH
HFF🟥
Sequence = [2, 2, 1, 1, 1, 2]
***代理可以学习几种正确的动作顺序:[2,2,1,1,1,2],[1,1,2,2,1,2]等。好的一面是在我们的序列中只有**6 个动作,这是我们统计的**最小可能动作数**:这意味着我们的代理学会了以最佳方式解决游戏。在[2,2,1,1,1,2]的情况下,对应的是右→右→下→下→下→右,正是我们在文章最开始预测的顺序。📣*****
# ***📐四。ε-贪婪算法***
***D 尽管取得了成功,但我们之前的方法有一点让我感到困扰:代理总是选择具有最高**值的动作。因此,每当一个状态-动作对**开始具有非零值时,代理将总是选择它**。其他的动作将永远不会被执行,这意味着我们永远不会更新它们的值…但是如果这些动作中的一个比代理总是执行的那个更好呢?难道我们不应该鼓励代理人时不时地尝试新的东西,看看是否可以改进吗?*****
***换句话说,我们希望允许我们的代理:***
* *****采取值最高的动作**(剥削);***
* *****选择一个随机动作,尝试找到更好的动作**(探索)。***
***这两种行为之间的权衡很重要:如果代理只关注于**开发**,它不能尝试新的解决方案,因此**不再学习**。另一方面,如果代理只采取**随机行动**,那么**培训是没有意义的**,因为它不使用 Q 表。所以我们想**随着时间的推移改变这个参数**:在训练开始的时候,我们想**尽可能的探索环境**。但是探索变得越来越没意思,因为代理已经知道每一个可能的状态-动作对。该参数代表动作选择中的**随机量。*****
***这种技术通常被称为**ε贪婪算法**,其中ε是我们的参数。这是一个**简单但极其有效的**方法来找到一个好的折衷方案。每次代理必须采取行动时,它有一个**概率ε选择随机的一个**,和一个**概率 1-ε选择具有最高值的一个**。我们可以在每集结束时将ε**的值减少一个固定的量(**线性衰减**,或者基于ε的当前值(**指数衰减**)。*****
******
***作者图片***
***让我们实现一个线性衰减 T2。在此之前,我想看看任意参数下的曲线。我们将从ε = 1 开始进入完全探索模式,并在每集之后将该值减少 0.001。***
******
***作者图片***
***好了,现在我们对它有了很好的理解,我们可以真正实现它,看看**它如何改变代理的行为**。***
***Q-table before training:
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]
===========================================
Q-table after training:
[[0.531441 0.59049 0.59049 0.531441 ]
[0.531441 0. 0.6561 0.56396466]
[0.58333574 0.729 0.56935151 0.65055117]
[0.65308668 0. 0.33420534 0.25491326]
[0.59049 0.6561 0. 0.531441 ]
[0. 0. 0. 0. ]
[0. 0.81 0. 0.65519631]
[0. 0. 0. 0. ]
[0.6561 0. 0.729 0.59049 ]
[0.6561 0.81 0.81 0. ]
[0.72899868 0.9 0. 0.72711067]
[0. 0. 0. 0. ]
[0. 0. 0. 0. ]
[0. 0.81 0.9 0.729 ]
[0.81 0.9 1. 0.81 ]
[0. 0. 0. 0. ]]***
******
***作者图片***
***嘿,**代理现在需要更多时间来持续赢得游戏**!Q 表中的非零值**比前一个多得多,这意味着代理已经学习了**几个动作序列**来达到目标。这是可以理解的,因为这个新的代理**被迫探索状态-动作对,而不是总是利用具有非零值的状态-动作对**。*****
***让我们看看**是否和上一个**一样成功赢得比赛。在评估模式中,我们**不想再探索了**,因为代理现在已经训练好了。***
Success rate = 100.0%
***唷,又是一个 **100%的成功率**!我们没有贬低这个模型。😌这种方法的好处在这个例子中可能并不明显,但是我们的模型变得不那么静态了,而 T21 变得更加灵活了。它学习了从 **S** 到 **G** 的不同路径(动作序列),而不是像以前的方法那样只有一条路径。更多的探索**会降低性能**但是有必要训练能够**适应新环境的代理**。***
# ***❄️四世。挑战:湿滑的冰湖***
***我们没有解决**整个❄️Frozen 湖环境**:我们只是在防滑版本上训练了一个代理,在初始化的时候使用`is_slippery = False`。在易变型中,代理采取的行动只有 **33%的机会成功**。如果失败,则随机采取其他三个动作中的一个。这个特点给训练增加了很多随机性,给我们的代理增加了难度。让我们看看我们的代码在这个新环境中表现如何...***
***Q-table before training:
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]
===========================================
Q-table after training:
[[0.06208723 0.02559574 0.02022059 0.01985828]
[0.01397208 0.01425862 0.01305446 0.03333396]
[0.01318348 0.01294602 0.01356014 0.01461235]
[0.01117016 0.00752795 0.00870601 0.01278227]
[0.08696239 0.01894036 0.01542694 0.02307306]
[0. 0. 0. 0. ]
[0.09027682 0.00490451 0.00793372 0.00448314]
[0. 0. 0. 0. ]
[0.03488138 0.03987256 0.05172554 0.10780482]
[0.12444437 0.12321815 0.06462294 0.07084008]
[0.13216145 0.09460133 0.09949734 0.08022573]
[0. 0. 0. 0. ]
[0. 0. 0. 0. ]
[0.1606242 0.18174032 0.16636549 0.11444442]
[0.4216631 0.42345944 0.40825367 0.74082329]
[0. 0. 0. 0. ]]***
******
***作者图片***
Success rate = 17.0%
***不太好。但是你能通过调整我们谈到的不同参数来提高性能吗?我鼓励你接受这个**小挑战**,自己动手**享受强化学习的乐趣**,并检查你是否理解了**我们所说的关于 Q-learning 的一切**。为什么不为ε贪婪算法实现指数衰减呢?在这个快速练习中,您可能会意识到**稍微修改超参数会完全破坏结果**。这是强化学习的另一个怪癖:超参数是相当情绪化的,如果你想调整它们,理解它们的含义是很重要的。测试和尝试新的组合来建立你的直觉并变得更有效率总是好的。祝你好运,玩得开心!***
# ***🔚五.结论***
***Q-learning 是一个简单而强大的算法,是强化学习的核心。在这篇文章中,***
* ***我们学会了**与** `**gym**` **环境**交互来选择动作和移动我们的代理;***
* ***我们引入了一个 **Q 表**的概念,其中**行是状态** , **列是动作**,**单元格是给定状态下动作的值**;***
* ***我们实验性地重新创建了 **Q 学习更新公式**来解决**稀疏回报问题**;***
* ***我们实施了一整套培训和评估流程,以 100%的成功率解决了 **❄️Frozen 湖**的环境问题;***
* ***我们实现了著名的**ε-贪婪算法**,以便在未知状态-动作对的**探索和最成功状态-动作对**的**利用之间建立一个折衷。*****
*****❄️Frozen 湖**是一个非常简单的环境,但是其他人可以有**如此多的状态和动作,以至于不可能在内存**中存储 q 表。在事件**不是离散的,而是连续的**的环境中尤其如此(像《超级马里奥兄弟》或《《我的世界》》)。当问题出现时,一种流行的技术包括训练一个**深度神经网络来逼近 Q 表**。这种方法增加了几层复杂性,因为神经网络**不是很稳定**。但是我将在另一个教程中用不同的技术来稳定它们。***
***在此之前,**分享这篇文章**如果对你有帮助的话[和**在 Twitter 上关注我**T5 和](https://twitter.com/maximelabonne)[中 了解更多关于机器学习和深度学习的**实用内容**。📣](https://medium.com/@mlabonne)***
# 使用 CVXOPT 在 Python 中实现带约束的二次优化
> 原文:<https://towardsdatascience.com/quadratic-optimization-with-constraints-in-python-using-cvxopt-fc924054a9fc>

弗洛里安·施梅兹在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的照片
二次优化是很多领域都会遇到的问题,从最小二乘回归[1]到投资组合优化[2]以及路过模型预测控制[3]。在所有这些问题中,必须优化对不同资产或代理的资源分配(这通常对应于线性项),知道在这些资产或代理之间可能存在有益或无益的交互(这对应于二次项),同时满足一些特定的约束(不将所有资源分配给同一代理或资产,确保所有分配的资源的总和不超过总的可用资源,等等。).当约束不能用线性公式表示时,可能会出现困难。在本文中,我们将看到如何使用一个非常强大的 python 库 CVXOPT [4,5]来解决这些优化问题,该库依赖于 LAPACK 和 BLAS 例程(这些是用 Fortran 90 编写的高效线性代数库)[6]。
从数学上来说,目标是找到最小化多元二次函数的自变量,同时满足一些等式和不等式约束。要优化的函数具有以下一般形式:

二次函数
其中 **x** 是大小为 n 的未知向量, **r** 是与 **x** 大小相同的向量,并且 **Q** 是 n 乘 n 维的对称方阵。约束可以被公式化为一组等式和不等式,使得:

限制
其中 **A** 是一个 n 乘 m 的矩阵(其中 m 是等式约束的数量), **b** 是一个大小为 m 的向量, **G** 是一个 n 乘 m’的矩阵(其中 m’是不等式约束的数量),而 **h** 是一个大小为 m’的向量。卷曲的不等式符号意味着不等式适用于向量的每个元素。
在 CVXOPT 形式中,我们如何写这个?考虑下面的代码:
Import Librariesimport numpy as np
import cvxopt as opt
from cvxopt import matrix, spmatrix, sparse
from cvxopt.solvers import qp, options
from cvxopt import blas # Generate random vector r and symmetric definite positive matrix Qn = 50
r = matrix(np.random.sample(n))
Q = np.random.randn(n,n)
Q = 0.5 * (Q + Q.T)
Q = Q + n * np.eye(n)
Q = matrix(Q)# Solvesol = qp(Q, -r)
解决方案“sol”是一个字典,其中包含在关键字“x”下最小化损失函数的向量,以及在关键字“status”下是否找到最佳解决方案的信息。
如何在这种形式主义中实现约束?所有需要做的就是提供矩阵 **A** 和 **G** 以及之前定义的向量 **b** 和 **h** 。假设我们希望 **x** 的元素之和等于 1,并且 **x** 的所有元素都是正的。数学上,这些条件是:

并且可以用矩阵格式写成:

因此,我们可以将矩阵 A、G、b 和 h 定义为:

在 CVXOPT 形式中,这些变成:
Add constraint matrices and vectorsA = matrix(np.ones(n)).T
b = matrix(1.0)
G = matrix(- np.eye(n))
h = matrix(np.zeros(n))# Solve and retrieve solutionsol = qp(Q, -r, G, h, A, b)['x']
现在找到的解决方案遵循强加的约束。
# 摩擦效应
现在让我们添加一种不同类型的非线性约束。假设在某一时刻找到了最优解。稍后,矩阵 **Q** 和向量 **r** 已经用新值更新。然而,改变资源或资产的分配是有成本的。因此,改变旧向量 **x** 中的一个值必须是值得的,以便证明这个成本是合理的。这个问题现在可以表述为:

有周转费用的二次函数
用 **c** 表示从一个解决方案到另一个解决方案的摩擦效应的向量,或者分配和取消分配资源的成本。这个新的损失不再是二次的,因为有一项包含绝对值,这是有问题的,因为它是不可微的。如何解决这个问题?解决方案是添加额外的变量,这些变量将对应于从一个状态到下一个状态的变化,然后线性化损失函数。上述等式的线性部分变为:

线性化周转成本
在上面的等式中,我们已经考虑了对于不同的代理/资产分配和不分配资源,摩擦效应或成本可能是不同的。然后,我们必须添加额外的约束,以确保这些额外的变量很好地对应于从一个解决方案到下一个解决方案的变化:

额外周转限制
我们通过将 **x** 与 **x 的变量**连接起来获得新的未知向量 **X** ,我们对新的 **Q** 和 **r** 矩阵和向量做同样的事情:

串接 **X** 、 **Q** 和 **r**
我们实施约束条件:

具有摩擦效应的约束
然后以下列方式修改代码:
Modify Loss Functionn = 200
c = 0.001
max_weight = 0.05r = matrix(np.block([np.random.sample(n), -c * np.ones(2n)]))
Q = np.random.randn(n,n)
Q = 0.5 * (Q + Q.T)
Q = Q + n * np.eye(n)
Q = matrix(np.block([[Q, np.zeros((n,n)), np.zeros((n,n))], [np.zeros((n,n)), np.zeros((n,n)), np.zeros((n,n))], [np.zeros((n,n)), np.zeros((n,n)), np.zeros((n,n))]]))# Modify constraint matrices and vectorsA = matrix(np.block([[np.ones(n), c * np.ones(n), -c * np.ones(n)], [np.eye(n), np.eye(n), -np.eye(n)]]))
old_x = np.zeros(n)
old_x[np.random.choice(n, n_assets_r, replace=False)] = max_weight
b = matrix(np.block([1.0, old_x]))
G = matrix(- np.eye(3n))
h = matrix(np.zeros(3*n))# Solve and retrieve solutionsol = qp(Q, -r, G, h, A, b)['x']
因此,我们已经了解了如何考虑从一种解决方案过渡到另一种解决方案时的摩擦效应。
# 实际例子:投资组合优化
让我们考虑一个实际的例子来充分理解这种技术的使用:投资组合优化。在 Markowitz 的投资组合优化理论[2]中, **r** 向量对应的是对不同资产收益的预测。这种预测是由我们在这里不考虑的任何预测模型给出的。Q 矩阵对应于这些相同资产收益的协方差矩阵。在这种情况下,可以采用历史协方差矩阵。我们将稍微改变一下这里的符号,使用ω作为未知向量。ω值对应于投资组合中不同资产的权重。损失函数现在可以写成:

投资组合优化的二次函数
这里我们还引入了λ,它代表了用户的风险厌恶。等式的第一项代表这个投资组合的预期收益。第二项代表投资组合的风险。λ值越低,意味着可以承受的风险越大。最后一项代表从一个投资组合到另一个投资组合的交易成本。
我们想增加一些投资组合优化中常见的约束条件。我们希望我们的投资组合在某种程度上多样化,这可以通过增加权重的上限来确保。我们还可能希望进一步减少从一个投资组合到另一个投资组合的变动,这种变动转化为周转限制。数学上,这些可以写成:

投资组合优化的约束
其中 T 对应于允许的最大营业额,可以取 0(不允许修改)和 2(没有营业额限制)之间的值。下面列出的约束中的最后一项是前一个约束的修改,其中权重之和应该等于 1。这种修改反映了这样一个事实,即当资产被买卖时,交易费用被支付,因此投资组合的资本减少[6]。在矩阵形式中,这些约束变成:

投资组合优化的约束
并且以下面的方式修改代码:
max_weight = 0.05
turnover = 2.0# Modify the Q matrix so that it resembles
the covariance matrix of returnsT = np.random.randn(n,100)
Q = np.cov(T)
Q = matrix(np.block([[Q, np.zeros((n,n)), np.zeros((n,n))], [np.zeros((n,n)), np.zeros((n,n)), np.zeros((n,n))], [np.zeros((n,n)), np.zeros((n,n)), np.zeros((n,n))]]))# Create constraint matricesG = matrix(0.0, (6 * n + 1, 3 * n))
h = opt.matrix(0.0, (6 * n + 1, 1))
for k in range(3 * n):
# wi > 0 constraint
G[k, k] = -1# wi > max_weight
G[k + 3 * n, k] = 1
h[k + 3 * n] = max_weightfor k in range(2 * n):
# sum dwi+ + dwi- < turnover
G[6 * n, k + n] = 1
h[6 * n] = turnover
然后我们计算有效边界,这是给定风险厌恶的最佳投资组合的集合
Compute random portfolios in order to have a baselinen_random = 10000
random_returns = np.zeros(n_random)
random_risks = np.zeros(n_random)
n_assets_r = 20for i in range(n_random):
w0 = np.zeros(n)
w0[np.random.choice(n, n_assets_r, replace=False)] = 1 / n_assets_r
random_returns[i] = np.dot(w0, r[:n])
random_risks[i] = np.dot(w0, np.dot(Q[:n,:n], w0))# Compute the optimal portfolio for different values
of lambda, the risk aversionlmbdas = [10 ** (5.0 * t / N - 1.0) for t in range(N)]sol = [qp(lmbda / 2 * Q, -r, G, h, A, b)['x'] for lmbda in lmbdas]optimal_returns = np.array([blas.dot(x, r) for x in sol])
optimal_risks = np.array([blas.dot(x, Q * x) for x in sol])

有效边界(图片来自作者)
在这个图中,我们绘制了一组随机投资组合的风险和回报,以此作为基线。我们看到,对于给定的风险,最佳计算的投资组合总是比任何随机投资组合有更大的回报。还描述了初始投资组合的风险和回报。
为了形象化最大周转率的重要性,我们可以重复计算有效边界的不同值(25%、50%、100%和 200%)。完全改变投资组合意味着出售所有资产(周转 100%的资产),然后购买一套全新的资产(再次周转 100%),相当于 200%的周转率。因此,投资组合的最大周转率是 200%。我们预计有效边界会随着更小的最大换手率而收缩,因为该算法改变初始投资组合权重的选项更少。

有效边界(图片来自作者)
这一假设在一定程度上得到了验证:对于这一特定的初始投资组合,将最大周转率从 100%增加到 200%似乎不会过多地阻碍优化过程。这可能是由于预测的性质,在我们的情况下,从一个时间步到另一个时间步变化不大。因此,稍微优化的投资组合不需要太多的改变就可以完全优化。
## 结束语
在本文中,我们已经看到了如何使用 CVXOPT,这是一个强大而快速的求解器,以解决带约束的二次优化问题。我们已经看到了如何调整一些既不是线性的也不是二次的约束和损失类型(例如交易成本损失和周转约束),以便求解器可以处理它们。然而,尽管规划求解非常高效且非常灵活,但它无法处理所有类型的约束。事实上,如果我们希望添加稀疏性约束(我们希望最多有 N 个非零权重),这不能以线性或二次方式重新表述。在这种情况下,可能值得研究更灵活且能处理任何类型损失函数的其他方法,例如模拟退火。
**参考文献**
[1]https://mathworld.wolfram.com/LeastSquaresFitting.html
[2][https://online library . Wiley . com/doi/ABS/10.1111/j . 1540-6261.1952 . TB 01525 . x](https://onlinelibrary.wiley.com/doi/abs/10.1111/j.1540-6261.1952.tb01525.x)
[3][https://arxiv.org/abs/2002.06835](https://arxiv.org/abs/2002.06835)
[https://cvxopt.org/](https://cvxopt.org/)
[5] [机器学习的优化,Suvrit Sra,Sebastian Nowozin 和 Stephen J. Wright](https://mitpress.mit.edu/books/optimization-machine-learning)
[6][https://www.netlib.org/lapack/](https://www.netlib.org/lapack/)
[7] [风险平价和预算简介,Thierry Roncalli](http://www.thierry-roncalli.com/RiskParityBook.html)
## 关于我们
[**Advestis**](https://www.advestis.com/) 是一家欧洲合同研究组织(CRO),对统计学和可解释机器学习技术有着深刻的理解和实践。Advestis 的专长包括复杂系统的建模和时间现象的预测分析。
*领英*:[https://www.linkedin.com/company/advestis/](https://www.linkedin.com/company/advestis/)
# 量化您的数据科学项目的业务影响
> 原文:<https://towardsdatascience.com/quantify-the-business-impact-of-your-data-science-project-b742e4b3208f>

作者使用 Canva 制作的图片
## 让你的工作得到重视和认可…
您是否曾与业务利益相关者共处一室,试图证明您的数据科学(DS)项目的影响?您是否遇到过这样的问题,“*您的项目的预计投资回报率是多少?*“您的 DS 项目是否曾因为其业务影响未被识别或不显著而从投资组合中退出?我们大多数人都曾一次或多次面临过类似的情况。至少,我经历过这样的困境。我向那些幸运的人们致意,他们从来不需要回答这些令人困惑的问题!
事实是,我们——*即*DS 或数据分析社区——总是难以量化 DS 项目的影响。多年来,我在这个领域遇到了许多挑战。然而,我总是通过实验来改变我的方法,这让我很好地完成了我的任务。因此,我想与你分享我的经历。我并不声称能够为您的所有 DS 项目问题提供完整的解决方案,但是我的文章将会给您一个快速的开始。我希望这篇文章可能会引发一场健康的讨论,让我们所有人分享宝贵的想法,我们可以一起学习各种解决方案。
在我们讨论数据科学解决方案的影响之前,您必须始终自问以下几个基本问题:“*我是否选择了对业务有实际和有意义(+ ROI)影响的正确业务问题?*、*我如何确定我(和我的团队)是否正在解决对我们公司有意义的业务问题*、*我该从哪里开始?*
这里还是现实一点吧!如果您正在解决一个为您的团队节省了几个小时时间的问题,或者它在一个不产生任何可观的商业结果的过程中自动化了一个手工任务,那么您的 DS 项目的影响可能是有限的和无关紧要的。
首先,如果你总是认为你正在解决的问题与你公司的目标是一致的,这将会有所帮助。例如,假设您的公司正在制造(或开发)一种产品或提供一些服务,但它无法生产足够的产品来满足客户需求。在这种情况下,贵公司的战略目标会自动转变为提高产量、减少产品浪费、增加创新和先进的设施和资源以制造更多产品*等*。在这种情况下,我们可以创建一个 DS 解决方案来提高产量并减少产品浪费。
您可能会问,“*那么,如果公司目标是在很高的层面上讨论的,而我无法将手头的业务问题与公司层面的目标联系起来,那该怎么办呢?*“在这种情况下,您可以努力将目标向下链接一级,比如说,在业务职能级别。这个层次包括研发、制造、运营、销售、营销、供应链、客户服务等。在这里,我们可以假设所有潜在的业务功能目标和流程都应该与公司的总体目标保持一致。尽管如此,你也可以考虑甚至下降到组织结构的下一个级别来指定目标。
将你的 DS 项目与公司的目标联系起来的另一个好处是让商业利益相关者站在你这边。如果他们能看到你的项目与他们公司的目标一致,他们会认为你是他们的合作伙伴。对于 DS 项目来说,拥有一个商业赞助商(*即*利益相关方)是至关重要的,这一点我再强调也不为过。这些赞助商将帮助你获得必要的资金和与任何障碍相关的变革管理。此外,他们将促进您的 DS 解决方案在其部门的采用。根据我的经验,大多数项目在实施之前失败是由于各种相关活动的管理不善,如入职、意识、培训、实施后支持、*等*。以下是一些关键的学习点:
**关键学习#1:** 采用自上而下的方法,从公司或业务职能或业务子职能的目标开始。在这里,想法是识别那些 DS 问题,这些问题可以在某个特定的层次上与公司的目标相一致。据观察,这些问题对本组织有着更广泛和更大的影响。
**关键学习#2:** 如果你不能确定你的 DS 项目的业务成果,它就不值得你去做。因此,你应该继续探索你任务清单上的另一个问题。如果你仍然继续处理一个问题,而没有具体说明它的影响,你可能会在最后感到惊讶。
**关键学习#3:** 如果你的业务赞助商能够通过你的 DS 解决方案看到他们项目的成功,那么说服他们会更方便、更容易。
**关键学习#4:** 您的 DS 项目是否有业务赞助商/支持者可以决定您的解决方案的命运。因此,如果你找不到赞助商,在投入精力和精力开发解决方案之前,你必须三思而行。
好吧!现在,您的下一个逻辑问题应该是:"*我应该定义什么指标来量化我的 DS 解决方案的业务影响?*“在下表中,我添加了影响(或结果)的各个方面,以考虑您的 DS 项目。您可以评估这些维度中的一个或多个如何为您的数据科学项目增加价值。每个维度中给出的 KPI 和指标仅作为示例提及。您可以立即将这些度量标准或 KPI 用于您自己的项目场景:(定义度量标准:度量 KPI:用目标量化)。

降低成本:作者图片
**成本降低:**成本降低指标可能有多种变化。例如,单位原材料成本降低、单位销售成本、单位制造成本*等*。

产量提高:作者图片
**提高产量:**通过测量关键生产阶段的各种工艺参数并进行调整以获得最佳产量,从而提高产量;例如,测量减少的浪费/拒绝的百分比。

节省人类时间:作者图片
**节省人力时间:**如果一个 DS 解决方案可以通过自动化节省人工劳动时间,对一个企业来说是有利的。一些例子包括:每天、每批、每次冲刺或任何其他合适的指标减少 30%的人工。

过程改进:作者图片
**流程改进:**如果你的 DS 解决方案可以帮助改进一个业务流程,它可以节省时间和成本。例如,将周期时间减少 20 小时,将等待时间减少 2 小时,将退出周期时间减少 8%,将客户退货处理时间减少 6%。

速度:作者图片
**速度:**一个数据科学项目可以提高一个流程的完成速度,这对一个企业来说可以是值得的。速度和过程改进可以是重叠的指标,它们可以结合起来。例如,将上市时间缩短 100 天,平均交付时间缩短 10 分钟。

质量:作者提供的图片
**更好的质量:**许多指标可以量化产品的整体质量、产品健康、客户服务、客户体验;*例如*客户退货数量减少 20%,客户投诉数量减少 14%,保修电话数量减少 10%,故障数量减少 12%。

市场份额:作者图片
**市场份额:**DS 项目的成功可以通过其对市场份额的影响来量化。例如,与前一年相比,某个品牌、地理区域、客户群或整体消费者群的市场份额增长了 5%。

更多客户:作者图片
**更多客户:**提高客户数量也是 DS 项目的一个重要结果。例如,客户群同比增长 6%,每个用户会话的平均花费时间增加 10%,客户转化率增加 25%,客户保持率增加 12%。
现在你一定很想知道:“我如何在我的项目中利用这些度量?”为了回答这个问题,让我们把使用它们的过程分成以下几个步骤:
**步骤 1:** 为您的场景选择正确的*指标。安排与业务用户的发现会议,并向他们介绍上面列出的指标维度。确定哪些指标将适用于您的用例。*
***步骤 2:** 要量化 DS 解决方案的影响,您必须为每个指标建立基线。例如,如果您的公司经营半导体产品,您当前的平均产量为 94%,这意味着您的产品有 6%被剔除。这 6%的产品可能是有缺陷的,也可能是*错误剔除* — *,即*在质量检测过程中剔除的合格产品。我们可以在这里创建一个场景,假设每天生产 20,000 件产品,其中 1200 件(*即* 6%)被丢弃。因此,94%(或(18800/20000)x100)是您的基线,您想要衡量您的 DS 解决方案是否可以提高产量。但是,您的公司可能会也可能不会维护历史原始数据来得出基线值。假设你不能获得现成的历史数据。在这种情况下,您可能需要与多个业务团队或用户合作,做出假设并估算原始数字,以得出所需的基线值。因此,你的数字不必精确,而是一个高层次的近似值。*
***第 3 步:**要衡量影响,您需要在部署 DS 解决方案后等待一段时间,然后收集与第 1 步相同指标的新原始数据(*即*平均每月生产和拒收的单位)。或者,您可以比较实施 DS 解决方案前后六个月的累积数据。我在此重申,您必须等待一段合理的时间(*例如* ~6 个月或任何与您的用例相关的时间)来见证变更的真正影响。*
***关键学习#5:** 如果你没有任何计算基线的原始数据,通过与你的团队成员讨论做出假设,这些成员非常了解你的解决方案正在实现的业务流程。毕竟,作为领导者,如果我们没有完整的可用信息,我们总是会做出假设。*
***关键学习#6:** 您需要等待一段时间(通常为六个月)才能看到您的 DS 解决方案的影响。*
***关键学习#7:** 商业价值计算不是一个简单的过程,它需要毅力和耐心。为了实现您的 DS 解决方案,您需要与在业务流程中工作的团队成员密切协作。因此,如果您反复地、勤奋地评估您的 DS 解决方案的商业价值或影响,那将是最好的。*
*现在,我详细说明最具挑战性的部分。您如何评估您的 DS 解决方案是否最终提供了预期的结果?可能会有这样一种情况,公司的其他一些并行计划可能已经取得了与您的目标相同的结果。此外,在某些情况下,您的 DS 解决方案可能会提供一些见解,供经理或其他人用来获得结果。在这种情况下,您能否将项目的成功归功于您的解决方案或在正确的时间采取行动使事情发生的个人,或者是这两种情况的混合?我认为我们可以将成功的归因按 50-50 或其他合理的比例进行划分。不幸的是,没有科学的方法来解决这个问题,因为它高度依赖于特定的情况。因此,如果你和其他参与者一起分配这个归因比例,将会有所帮助——这是所有利益相关者都同意的。*
***关键学习#8:** 时机决定一切。当您准备好部署 DS 解决方案时,必须准备好确定解决方案的影响。在企业层面上,永远要从更广阔的角度来评估影响您公司结果的其他计划的可能性,因为他们可能也在朝着与您一样的解决方案努力。*
*欢迎任何问题和反馈!如果您认为量化 DS 项目成果有其他方法,请在您的反馈意见中分享。*
# 量化语音识别的不确定性
> 原文:<https://towardsdatascience.com/quantifying-the-uncertainty-for-speech-recognition-1932dc198c49>
## 何时以及如何信任您的语音识别模型

[Attendi 语音服务的标志](https://www.attendi.nl/en/assistant/)。*经作者许可展示*
# 目录
* 什么是不确定性?
* 语音识别的不确定性估计方法
* 我们如何从不确定性评估中获益?
* 摘要
* 关于我
* 参考
在过去的几年里,自动语音识别(ASR)已经转向更复杂和更大的神经网络架构。更高的复杂性有利于模型的性能,但另一方面,它会变得更加难以信任的结果。一个复杂的神经网络模型就像一个黑匣子,我们只能希望它对看不见的数据同样有效。
在 [Attendi](https://www.attendi.nl/) ,我们为荷兰的(健康)保健专业人士提供量身定制的演讲服务。用户录制的音频可能有明显的噪声背景、行话或口音,而模型没有见过(或在这种情况下,听到了[🙂](https://emojipedia.org/slightly-smiling-face/))之前。在这种情况下,知道我们可以在多大程度上相信预测是有益的。
通过测量预测的不确定性,我们有可能:
* 找出最有可能出错的话语并突出显示出来,以便用户检查
* 根据最“不确定”的样本微调模型
* 监控模型的性能
但是我们如何测量不确定性呢?幸运的是,有一些不确定性估计的相关方法可以应用于 ASR 系统。在本文中,我们将研究如何定义不确定性,以及哪些方法可以应用于语音识别模型。最后,我们将了解在哪些实际设置中,我们可以将不确定性估计应用于 ASR 系统。
# 什么是不确定性?
不确定性衡量模型做出预测的可信度[1]。根据不确定性的来源,我们可以将其分为两种类型:*任意的*和*认知的。*
*随机*型是由于固有的随机过程而出现的统计不确定性。例如,掷硬币的结果具有任意的不确定性。掷硬币的结果要么是正面,要么是反面,
两者都有 50%的几率发生。任意型是不确定性的不可约部分。不可能改变正面或反面的概率,因为这个事件是由一个固有的随机过程描述的。
*认知*不确定性是由于不适当的模型架构、训练程序、数据的分布变化或先前未知数据的出现(例如用于狗/猫
图像分类的鸟的图像)[1]。这是不确定性的可减少部分,这意味着我们可以通过改进模型及其训练参数,或者通过改进训练数据的可变性来增加预测的可靠性。
在更实际的设置中,我们最感兴趣的是*预测*不确定性,即在网络单次前向传递后可以估计的不确定性。这种类型的不确定性非常有用,因为它很容易在现有的 ASR 管道中实现。我们也更喜欢关注认知类型的方法,因为这是不确定性中唯一可约的部分。
# *语音识别的不确定性估计方法*
理想情况下,我们想知道对于给定的输入数据 **X** ,模型的输出是否是不确定的。在 ASR 领域中,通常定义一种方法,在每个模型的预测之后输出分数 S*(****)X****)*。对于一个取值范围 *s* ₀,和 *s* ₁,我们可以确定模型的输出是否不确定。
指示不确定性的简单阈值函数 Sel( **X** )可以定义如下:

Eq 1。给定得分函数 S( **X** ,我们可以定义哪些数据使得模型的输出不确定。
其中 *s* ₀和*s*₁*t30】分别是下限和上限。 *s* ₀和 *s* ₁ 的值根据具体情况确定,因为它们取决于许多因素,例如模型架构或不确定性估计方法。*
通过这个简单的阈值函数,我们可以识别 ASR 模型需要处理的数据。然而,主要的问题是评分函数 S( **X** )无法访问基本事实标签,因为我们对未标记的“实时”数据进行预测。因此,这些评分函数只能使用原始模型的输出来计算不确定性,或者必须对真实标签分布做出假设。
在下面的小节中,我们将看看这些方法的选择,并了解它们如何生成分数 S( **X** )。
## 最大软最大概率
一种简单的方法是使用模型的 softmax 分布来估计不确定性[2]。为了计算得分 S( **X** ),我们可以取模型的 softmax 输出的最大值*。*
**
*Eq 2。从 softmax 输出中取最大值*
*但是我们如何将这种方法推广到 ASR 呢?上面的等式只考虑了非顺序输出([ *batch_size,no. classes]* ),当我们想要预测整个话语(*【batch _ size,no. predicted tokens,no . classes】)*时,情况并非如此。我们需要定义另一个函数来聚合整个话语的得分:*
**
*Eq 3。在[预测令牌数]维度上平均 MSP 分数*
*其中,softmax( **Y** ₜ)是令牌 *t.* 的 softmax 分发输出*
*在上面的等式中,我们计算整个话语的平均不确定性得分。也可以使用最大值、最小值或其他聚合函数来代替平均值。然而,尽管这种方法为整个话语提供了单个分数,但是这些分数不如每个标记的分数那样具有信息量。即,在聚合之后,我们不能再标记不正确的单词/标记。我们能做的最好的事情就是找到一个不正确的发音。简单来说,我们失去了细节的颗粒度。*
## *欧丁神*
*MSP 方法有一些缺点。神经网络模型倾向于对域外(OOD)数据做出过于自信的预测[3]。通常,它们没有很好地校准,这意味着 softmax 输出与进行预测的置信度没有很好的关联[3]。*
*ODIN 试图通过向输入数据添加温度标度和小扰动来解决这个问题[4]。这些方程非常类似于 MSP 方法。*
*首先,温度缩放用于校准 softmax 输出,使其更好地符合预测的置信度:*
**
*Eq 4。ODIN 通过添加温度比例参数 *T* 来修改 softmax 方程*
*其中神经网络 **f** =(f₁,…,fₙ)被训练来分类 n 个类。为了获得最终得分 S( **x** ),我们可以从得分 Si 中取最大值(如等式 2 所示)。*
*在论文[4]中,作者指出温度比例必须足够大,并指出他们的模型在 T=1000 时性能最佳。*
*第二步,我们加入输入扰动。这有助于扩大分布内和分布外样本之间的 softmax 得分差距。要添加输入扰动,我们需要执行以下操作:*
1. *计算*最大值* softmax 分数(等式。4 和 Eq。2)*
2. *将交叉熵损失 w.r.t .反向传播到输入端 **x***
3. *计算梯度 w.r.t 输入 **x***
4. *取一个[符号](https://en.wikipedia.org/wiki/Sign_function)(梯度),用ε缩放,并从输入 **x** 中减去它*
*这个过程可以用下面的等式来解释:*
**
*Eq 5。输入扰动有助于区分分布内和分布外样本的分数*
*要将其应用于顺序 ASR 任务,我们可以遵循与 MSP 相同的方法。将它聚集在一个[预测令牌数]上,会将其减少到一个数字分数。*
## *GradNorm*
*MSP 和 ODIN cherrypicks 中的 *max* 函数从 softmax 分布中选取一个值,并忽略其他输出值。最重要的是,这些方法不考虑输入信号如何在网络中传播。所有这些方法只是盲目地从最终的 softmax 层中查看单个元素。*
*GradNorm [5]从不同的角度处理这个问题,并从整体上看待这个模型。为了计算不确定性得分,GradNorm 研究梯度如何在网络中传播以及最终的输出分布。*
*梯度范数的主要概念如下。我们假设如果输入数据 **X** 不确定,那么最终 softmax 层的输出将(或多或少)均匀分布。如果模型对其预测有信心,那么我们将在 softmax 层中看到某个类的峰值。*
*为了计算不确定性,我们首先计算 softmax 分布和均匀分布之间的 [KL 发散损失](https://machinelearningmastery.com/divergence-between-probability-distributions/):*
**
*Eq 6。我们假设地面真值分布 **u** 是均匀的。基于此,我们计算 softmax(f( **x** )和 **u** 之间的 KL 发散损失。*
*为了计算最终得分,我们反向传播 KL 散度损失,然后计算 pₜₕ层的梯度幅度(p-范数)。在论文中,他们发现最好的分数是从最后一层开始计算的,所以 *p=1:**
**
*Eq 7。为了计算最终得分,反向传播 KL 发散损失并计算 pth 层的 p 范数。p=1 给出了最好的结果,也是计算效率最高的。*
# **我们如何从不确定性评估中获益?**
*了解预测的不确定性在实际应用中非常有用。在这一部分,我们将看看一些最有用的应用。*
## *用户校正输入*
**
*[来自 Attendi 应用](https://www.attendi.nl/en/speech-service/)的用户修正输入。经作者许可展示。*
*不确定性分数允许我们找到可能不正确的话语。我们可以为用户突出显示这些话语,这样他/她就可以快速找到并纠正它们。*
## *主动学习*
*主动学习假设我们有一定的配额来给新数据添加基础事实标签。在 ASR 的环境中,我们可以雇佣注释者转录一定数量的音频样本来帮助微调模型。但是我们应该优先考虑手动转录哪个音频呢?*
*在这种情况下,我们可以计算不确定性分数,并选择具有某个期望 S( **X** )的音频。我们可以采取最‘不确定’的样本,或者选择特定范围的 S( **X** )。*
## *监视*
*部署的 ASR 模型可以暴露于不同的音频信号。他们可能有不同的环境噪音、行话或口音,这些都会影响模型的性能。我们希望,通过监控音频的不确定性分数,我们可以检测到模型的性能何时下降并采取行动。*然而,对我们来说,这仍然是一项正在进行的工作。我们首先需要确定模型的性能(在这种情况下是 WER)是否与不确定性相关。**
# *摘要*
*在本文中,我们定义了什么是不确定性,以及如何使用它作为分数 S( **X** )来衡量模型对其预测的信心。*
*有不同的方法来计算不确定性。最大 Softmax 概率(MSP)和 ODIN 使用 softmax 输出计算不确定性得分。然而,这些方法的缺点是,它们只是盲目地查看模型的输出。GradNorm 采用了一种不同的方法,既关注输出分布,也关注梯度如何在整个模型中反向传播。*
*最后,我们看了不确定性分数估计的潜在用例。*
## *信用*
**我要感谢来自 Attendi 的 Jan-Willem van Leussen、Omar Elbaghdadi 和 Berend Jutte 为我的文章提供了有用的反馈和更正。**
# *关于我*
*我是阿姆斯特丹大学的人工智能硕士学生。在我的业余时间,你可以发现我摆弄数据或者调试我的深度学习模型(我发誓这很有效!).我也喜欢徒步旅行:)*
*如果你想了解我的最新文章和其他有用的内容,以下是我的其他社交媒体资料:*
* *[领英](https://www.linkedin.com/in/kacperkubara/)*
* *[GitHub](https://github.com/KacperKubara)*
# *参考*
*[1] [深度神经网络中不确定性的调查](https://arxiv.org/pdf/2107.03342.pdf)*
*[2] [用于检测神经网络中错误分类和非分布样本的基线](https://arxiv.org/abs/1610.02136)*
*[3] [关于现代神经网络的校准](https://arxiv.org/pdf/1706.04599.pdf)*
*[4] [增强神经网络中非分布图像检测的可靠性](https://arxiv.org/abs/1706.02690)*
*[5] [关于梯度对检测野外分布变化的重要性](https://arxiv.org/abs/2110.00218)*
# 数量输入影响(QII)——超越 SHAP 的最大似然解释度
> 原文:<https://towardsdatascience.com/quantitative-input-influence-qii-an-ml-explainability-measure-f16debc9ed10>
## **功能的独立性不再是一个约束**

奥拉夫·阿伦斯·罗特内在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的照片
任何数据科学家都会大声证明这一说法——“真实世界的数据至少违背了一些众所周知的方法的假设”。机器学习毕竟不简单。
在 ML 模型的可解释性领域,SHAP 和莱姆是两种值得信赖的方法。然而,它们中的每一个都有一些限制。在我的上一篇文章中,我讨论了 SHAP 的一些惊人的局限性。如果您想参考,以下是快速参考:
</using-shap-for-explainability-understand-these-limitations-first-1bed91c9d21>
在所有的 SHAP 局限性中,我认为两个(尽管有些关联)最令人担忧的是:
1)假设特征不相互依赖。在现实世界中,不可能找到特征相互独立的数据。在输出上,总是存在一定程度的相互依赖和共同依赖。
2)相关性而非因果关系 **:** 重要的是要承认,SHAP 只是“解释”了根据模型结构定义的变量“相关性”。这并不意味着定义的变量也有因果关系。
在寻找问题的潜在解决方案的过程中,我看到了一篇研究论文(“通过量化输入影响的算法透明度”),展示了一种叫做“量化输入影响”(QII)的方法。QII 与 SHAP 的方法有一些相似之处,也有一些有意义的不同。
关键的区别在于使用了“干预技术”。相比之下,SHAP 使用“有条件的方法”。
在深入研究干预技术之前,先快速回顾一下模型中变量的边际贡献是如何计算的。对于给定的模型,特性的边际贡献计算如下:
通过包含和排除特征来分析输出的差异
求所有 N 的平均值!可能的订单
具有剩余特征所有子集。
**“介入”技术有什么帮助?**
让我们考虑几个例子来解释这个概念。
1.为了**确定公司的增长**(分类问题:1 如果增长> 10%,0 如果增长< = 10%),在许多变量中有 2 个与销售相关的变量:1)销售人员的数量 2)销售额。现在,这两个变量是相互关联的,也就是说,更多的销售人员意味着更高的销售额。为了评估因果关系,在边际贡献计算中,我们可以首先用一组随机值替换销售人员的数量,并查看模型结果如何变化,然后我们可以用一组随机值替换销售额,并评估模型结果的变化。两者改变模型输出的程度有助于理解输出中两个变量的“因果关系”(不仅仅是相关性)。这里用一组随机值代替一个值的想法是“随机干预”。
2.想象一个**收入分类问题**,输出是收入大于 50k (GT)还是小于 50k(LT)。再次假设有两个变量:
-“教育”的价值有高、中、低
——价值观蓝领、白领、粉领的“职业”
假设模型将输出标记为 GT,如果“教育程度”是“高”,而“职业”是“白领”,对于所有其他情况,它将输出标记为 LT。
现在,对于一个“教育程度”低而“职业”为蓝领的数据点,变量的影响/贡献是什么?在这里,根据敏感性机制,仅仅使用“教育”或“职业”的随机值不会产生任何结果差异——因为在所有情况下,结果只会是 LT。

图片来自作者
可以承认,这并不意味着“教育”和“职业”对产出没有影响。这仅仅意味着只有一起移动的值才会给输出带来敏感性,并且可以推断每个变量的影响。这正是 QII 所做的。QII 不仅解释了个体特征的变化,也解释了“特征集合”的变化。利用这一点,QII 计算了一组功能的影响,然后使用合作博弈论的概念(与 SHAP 使用的方法相同)计算边际贡献。从逻辑上讲,这里 QII 打破了特征之间的相关性,以衡量特征对模型输出的单独贡献。
如果你不熟悉 SHAP 使用的合作博弈理论,请参考以下文章:
</can-shap-trigger-a-paradigm-shift-in-risk-analytics-c01278e4dd77>
在解决 SHAP 带来的两个关键限制方面,QII 无疑已经向前迈出了一大步。此外,QII 据称比 SHAP 计算量少。
我个人认为,随着数据科学家继续揭示可解释性,QII 肯定会走上舞台中央。
时间会证明一切!!
免责声明:本文中表达的观点是作者以个人身份发表的意见,而不是他们各自雇主的意见。
# 深度网络量化训练
> 原文:<https://towardsdatascience.com/quantized-training-with-deep-networks-82ea7f516dc6>
## 如何以最小的努力将神经网络训练时间减少一半

*在神经网络的正向/反向传递中应用量化(由作者创建)*
# 量子化一瞥
存在许多方法来减少神经网络训练的开销,但是最有希望的方法之一是低精度/量化训练。方法很简单— *只需在整个训练过程中减少网络中用于表示激活和梯度的位数*。然后,如上图所示,这些低精度表示使网络的正向和反向传递中的常见操作更快,从而在训练时间和能量效率方面产生改进。
尽管低精度训练背后的想法很简单,但由于可能遇到的几个问题,有效实施这一想法更加困难:
* 梯度表示对较低精度的敏感性
* 批处理规范化模块中的数值不稳定性
* 将精度级别设置得太低,无法获得良好的性能
尽管存在这些问题,但最近的研究发现,神经网络训练对低精度激活和梯度表示的使用具有惊人的鲁棒性,使得能够在神经网络训练的最基本位水平上获得显著的成本节约。然而,除了简单地利用低精度表示之外,现有方法还探索了在训练期间对网络精度的动态适应(例如,循环或依赖于输入的精度),从而在训练效率方面产生了更大的改进。
在整个概述中,我将阐述量化培训的现有方法,围绕它们的背景,以及如何正确地解决人们可能遇到的问题——如上所述。在我讨论这些主题时,我将试图强调对从业者最有用的量化训练的各个方面,揭示如何利用这些技术以最小的努力显著减少神经网络训练时间。
# 背景资料
在这里,我将提供全面理解神经网络量化训练所需的所有背景信息。首先,我将简要概述可能在量化训练中使用的不同浮点表示。然后,我将解释神经网络内量化的一般方法,它可以被修改以形成具有各种属性的不同量化训练变量。
## 浮点表示法
虽然浮点表示的完整概述超出了本概述的范围,但是基本掌握数字在计算机中的表示方式对于理解低精度训练技术非常重要。关于这个话题的众多、[深入讨论](https://www3.ntu.edu.sg/home/ehchua/programming/java/datarepresentation.html#:~:text=In%2032%2Dbit%20single%2Dprecision,bits%20represents%20fraction%20(%20F%20).)可以在网上找到。
要理解量化训练,首先要理解浮点数在 PyTorch 这样的深度学习包中是如何表示的,因为这种表示方式将用于神经网络训练。这种包使用 32 位浮点表示,如下图所示。

*32 位浮点表示(* [*来源*](https://www3.ntu.edu.sg/home/ehchua/programming/java/datarepresentation.html#:~:text=In%2032%2Dbit%20single%2Dprecision,bits%20represents%20fraction%20%28%20F%20%29.) *)*
因为计算机表示是二进制的,所以它们是实际的全精度数字的近似值,有一些误差(希望很小)。这种误差由表示的精度控制,其中增加(减少)表示中的位数将提供更精确(更不精确)的近似。例如,浮点数也可以用 64 位或 16 位(即“双”或“半”表示)来表示,以提供不同级别的精度。
简而言之,修改浮点表示的精度类似于允许在表示数字时使用更多/更少的小数位数。例如,在试图表示数字 pi 时,可以声称 pi 等于 3.14,尽管 3.14159265 是更准确/精确的表示。
## 低精度的好处
假设浮点数可以用不同的精度表示,人们可能会开始怀疑为什么低精度表示有用。难道我们不想总是使用最精确的表示吗?嗯,这取决于我们的目标是什么…
例如,考虑一个科学计算应用程序。在这些情况下,精度往往至关重要——数值精度的微小误差都会对结果产生重大影响。因此,在这些情况下,应该尽可能以最高的精度来表示数字(例如,64 位双精度表示)。
在神经网络应用中,可以使用较低精度的表示(例如,16 位[9]),而性能不会明显下降。此外,神经网络训练中的常见操作(例如,向量/矩阵乘法和加法)在较低精度输入的情况下要快得多,因此提供了显著的效率益处。这个基本思想是量化训练的关键——我们希望找到降低精度的方法,以便:
* 没有观察到网络性能的恶化
* 减少了训练开销(例如,时间、能量、计算、等待时间)
## 提高神经网络效率的技术
尽管低精度训练是用于改进神经网络训练和能量效率的流行方法,但是存在许多替代方法。例如,已经提出了许多方法来减少神经网络 vai 修剪的大小(感兴趣的读者可以查看我的[之前的概述](https://cameronrwolfe.me/blog/lottery-ticket-hypothesis)本主题)、权重共享、低秩近似,甚至权重量化[10,11,12]。
尽管这种方法能够极大地减少神经网络中的参数数量,但是在提高神经网络效率方面的许多早期工作集中在网络评估/推断上,考虑到与网络评估/推断相比,训练迭代需要大约三倍以上的计算(即,由于除了前向传递之外的梯度计算),这种方法的影响有限。然而,后来的工作开始侧重于量化网络参数和训练阶段的中间激活,从而节省更多的成本和能源[9,13]。
## 量子化的全局观点

*描述神经网络层中的前向和反向传播(由作者创建)*
此时,我们应该理解量化训练利用低精度浮点表示来减少训练开销。然而,现在还不清楚这种量化在神经网络中应用于何处/如何应用。为了更清楚地说明这一点,我在上图中示意性地图示了单个神经网络层的向后和向前传递的组件。对于那些不熟悉反向传播的人,我鼓励你用网上众多[高质量描述](https://www.jeremyjordan.me/neural-networks-training/)中的一个来补充这个插图。
可以看出,可以应用量化的前向/后向传递有两个主要部分,即**激活和梯度**。也就是说,可以使用低精度表示来存储激活,以使得前向传递更便宜,同时可以量化层梯度,以使得权重梯度计算和梯度到之前层的传播更便宜。可以看出,反向传递在每一层都需要两个单独的操作,计算量是正向传递的两倍,这揭示了为什么在训练期间执行量化(与仅执行正向传播的推断相反)是如此有益。
应当注意,在前向和后向传递中可以采用不同的量化级别(例如,4 位前向传递和 6 位后向传递)。相对于前向通道,后向通道中的量化级别通常不太积极,因为梯度通常对量化更敏感。
## 静态与动态量化方案
神经网络中量化训练的方法可以大致分为两类——**静态和动态方案**。早期的量化工作采用了静态方法,在整个训练过程中执行相同数量的量化——网络梯度和激活采用了固定数量的比特,在训练过程中不会改变。这种方法非常受欢迎,因为它得到了现代混合精度深度学习工具的支持,如 [Apex](https://nvidia.github.io/apex/) ,这些工具可以通过最少的代码更改轻松加速网络训练。
尽管静态方法流行,但最近的工作已经探索了动态低精度训练,其沿着训练轨迹改变网络精度(即,用于激活/梯度的精度随着训练的进行而改变)。这些方法通常应用于静态量化方案之上,使得精度水平在精度的下限和上限之间动态变化。这个上限与静态方法[2,3]使用的精度相匹配。因此,通过在网络训练的某些阶段将精度降低到静态水平之外,可以节省成本。
# 出版物
现在我们已经对量化培训和相关背景有了基本的了解,我将概述一些关于这个主题的有用论文。我将从一种高性能的静态量化技术开始,这种技术大大降低了神经网络有效训练的精度。然后,我将概述最近提出的两种动态量化方法。第一种提出了一种独特的方法,该方法既(I)沿着训练轨迹动态地适应量化,又(ii)训练补充神经网络以根据输入的方式适应精度。另一个实验性地分析了在训练期间用于适应神经网络精度的不同的循环的、动态的方案,并且为量化的训练提供了许多实际的见解。
## 神经网络 8 位训练的可扩展方法[1]
**主旨*。*** 在训练期间尝试超过 16 位精度的量化的先前工作遇到了网络性能的显著降级。在这项工作中,作者探索了量化过程和一般网络架构的修改,使网络梯度,激活和权重的 8 位量化。令人惊讶的是,他们达成了一个静态的低精度训练程序,称为 SBM,实现了这个目标,*证明神经网络训练比以前认为的对量化更鲁棒。*
导出的训练过程使用 8 位精度执行除参数更新和权重梯度计算之外的所有操作。为了能够以如此低的精度进行训练,必须解决神经网络训练中最敏感的部分——批量标准化和反向传播。特别地,作者通过以下方式降低了这些分量对量化的敏感性:
* 开发一种量化友好的批量标准化变体,称为**范围批量标准化**,它不太容易出现数值不稳定性。
* 使用**梯度分叉**在每一层维护网络梯度的两个副本,以便能够以更高的 16 位精度计算对使用较低精度敏感的权重梯度。
总之,这种修改显示出能够在量化训练中探索显著较低的精度,从而在神经网络训练成本、等待时间和能量使用方面提供大量节约。

*普通批处理规范化与范围批处理规范化(由作者创建)*
**方法论。**如上所述,作者发现,在执行低精度训练时,批量标准化和反向传播操作是神经网络训练过程和架构中最有问题的部分。为了理解这背后的原因,我们必须分别检查每个组件。
批处理规范化——如上所述,更全面的描述参见此处的——通过 *:* 转换一些输入中的每个组件
* 减去分量的平均值
* 除以成分的标准偏差
计算该标准偏差需要平方和,这可能会导致在较低精度下处理大值时出现数值不稳定和算术溢出。为了避免这种不稳定性,作者提出了范围批量归一化,如上图所示,它通过输入分布的范围进行归一化(通过依赖于维度的常数进行缩放)。由于不涉及导致数值不稳定的大数之和,该量对量化更为宽容,并且可证明接近高斯数据假设下的标准偏差(更多详细信息,请参见[1]的第 3 节)。
在背景信息部分中描述了在单个网络层上执行的反向传播过程。可以看出,在每一层执行两个操作:
1. 计算前一层梯度
2. 计算重量梯度
该过程的第一步对网络中的每一层顺序重复,使其成为完成反向传播的瓶颈。因此,该过程必须加速(即,使用较低精度的运算)以实现提高的效率。相比之下,权重梯度是针对每一层单独计算的,并且没有顺序相关性,这意味着该步骤可以以更高的精度(并且是并行的)执行,而不会恶化网络性能。
考虑到这一点,作者引入了梯度分叉过程,该过程在 8 位和 16 位保持层梯度的单独副本。然后,可以使用较低精度的表示来计算层梯度,以加速顺序计算,同时可以以较高精度单独计算权重梯度。这种量化反向传播的更明智的方法,如下图所示,对于实现较低精度的训练至关重要。

*量化反向传播的梯度分叉(作者创建)*
**发现。**
* 即使在 ImageNet 上的大规模实验中,也可以用范围批处理规范化替换普通批处理规范化,而没有明显的性能差异。
* 当采用较低精度时,范围批处理规范化显示出相对于普通批处理规范化保持较高的性能水平。
* 将范围批量归一化与所提出的用于量化反向传播的方法相结合,使得能够以令人惊讶的低精度进行训练,而不会恶化网络性能。
## FracTrain:在时间和空间上分数压缩比特节省,用于有效的 DNN 训练[2]

*FracTrain 方法中 PFQ 训练的描述(来源【2】)*
**大意。** 以前的量化训练工作通常采用静态方法,在整个训练过程中使用固定精度[1,4,5]。除了静态方法,FracTrain 还探索了训练过程中精度的动态适应。特别地,所提出的方法在两个方面适应精度:
* **暂时**通过在训练过程的不同阶段采用不同的精度水平
* **在空间上**通过基于输入的属性学习适应网络的每一层/块中使用的精度水平
这种用于动态适应精度的时间和空间策略分别被称为**渐进分数量化(PFQ)** 和**动态分数量化(DFQ)** 。为了最大限度地降低培训成本,这些策略被串联应用,形成了 **FracTrain** 策略。
在实践中,FracTrain 应用于静态量化方案之上,这意味着训练期间使用的最高精度水平与静态精度基线相匹配。以这种方式使用 FracTrain,可以大大减少训练过程中产生的训练、能量和延迟成本。此外,生成的模型与通过静态精度基线获得的模型性能相当,没有观察到对网络性能的显著影响。
**方法论。** 如上所述,FracTrain 方法由两个部分组成——PFQ 和 DFQ——在整个训练过程中串联应用。这些方法应用于静态的、低精度的训练方法之上。因此,训练期间使用的最高精度与该静态基线的精度相匹配,而 FracTrain 进一步降低了超过该点的精度。
PFQ 背后的想法很简单——在训练的早期阶段使用较低的精度,然后随着训练的进行慢慢提高精度。这种方法受到以下事实的启发:在网络训练的早期阶段学习到的特征对噪声(例如,来自量化)和更高的学习速率具有鲁棒性[6,7,8]。为了在实践中应用 PFQ,必须选择低的初始精度(例如,4 位前向和 6 位后向精度)。然后,当网络损耗在几个时期内没有改善时,该精度增加,最终在训练的后期达到静态基线的精度。

*经由 DFQ 的动态的、依赖于输入的量化(来源【2】)*
不幸的是,DFQ 不像 PFQ 那样直白。为了在整个网络中以依赖于输入的方式修改精度,作者利用每个层/块的单独的、学习过的[递归神经网络](https://www.jeremyjordan.me/introduction-to-recurrent-neural-networks/),该网络:
* 接受与网络本身相同的输入
* 预测要使用的正确精度级别
上图中描述的这个补充网络可以是轻量级的,并且通过在网络的目标函数中加入正则化项来训练,该目标函数捕获训练成本,允许在网络性能和训练效率之间实现折衷。
**发现。**
* 当一起应用时,相对于静态的低精度训练方法,PFQ 和 DFQ(或 FracTrain)在网络训练成本、能量使用和延迟方面产生显著改善,同时保持类似的性能水平。这种结果是在使用现代卷积神经网络(例如,各种 ResNets 和 MobileNetV2)和变压器架构的大规模实验中获得的。
* 孤立地应用,PFQ 和 DFQ 都被发现相对于静态的,低精度的训练提高了训练效率。有趣的是,发现 DFQ 在非常低的精度水平下特别有效,尽管静态基线方法的性能显著下降,但它仍保持令人印象深刻的精度水平。
## CPT:通过循环精度的高效深度神经网络训练[3]

*循环与静态精确训练(来源[3])*
**主旨。**与 FracTrain 有些类似,【3】中提出的循环精度训练(CPT)策略进一步探索了训练过程中时间量化的可能性。特别地,采用循环调度来在整个训练过程中在最小值和最大值之间改变网络精度。有趣的是,作者发现,相对于静态量化过程,在整个训练过程中循环变化的精度可以产生显著的计算节省,甚至在许多情况下提高网络的泛化能力。
为了激励循环精度时间表的使用,作者对网络精度和学习速率进行了比较。也就是说,在训练期间使用较低的精度提供了帮助网络“探索”损失情况的噪声,类似于对学习率使用较大的值。相比之下,更高的精度水平允许模型收敛到最终解,类似于使用低学习率。
作者通过证明改变这两个超参数对神经网络训练具有相似的影响,发现学习速率和精度之间的这种联系在经验上是有效的。CPT 方法受这一类比的启发,因为它根据余弦时间表循环改变精度,这与实践中经常用于设置学习率的超参数时间表相同。事实上,利用这种学习率的循环时间表是如此普遍,以至于在 PyTorch 中专门实现了。

*描述 CPT 中使用的循环精确时间表(来源[3])*
**方法论。***CPT 背后的方法论相当简单。首先,必须选择训练中使用的最小和最大精度。然后,在整个训练过程中,精度水平按照循环余弦时间表在该最小和最大水平之间变化,如上图所示。与 FracTrain 类似,这种程序应用于静态、低精度训练方法之上,这意味着循环时间表中使用的最高精度水平与静态精度基线相匹配。*
*虽然训练中使用的最大精度可以采用任何静态的低精度基线方法,但还必须确定 CPT 使用的精度下限。为了根据经验做到这一点,作者提出了一个简单的**精度范围测试**,其操作如下:*
* *从最低的可能精度(例如,2 位)开始*
*逐步提高精度,监控训练精度*
* *将所述精度下限设置为第一精度,所述第一精度使得训练精度的增加超过预设阈值*
*这种精度范围测试可以在 CPT 的第一个周期中执行——它不需要与网络训练过程本身分开执行。然后,未来的循环简单地采用在第一个循环中进行的范围测试所确定的精度下限。在执行精度范围测试时,只损失了几次迭代训练,这使得它成为确定 CPT 中使用的理想精度范围的低成本和有效的方法。*
***调查结果。***
* *发现精度水平与学习速率类似地影响神经网络训练过程。使用低精度是高学习速率的伪替代,反之亦然,因此鼓励使用模拟学习速率的精度循环调度。*
* *发现在训练期间使用精确的动态时间表有助于网络泛化。使用 CPT 训练的网络往往比使用静态、低精度基线训练的网络更容易泛化。*
* *相对于静态精度基线,CPT 显著降低了计算成本、能量使用和神经网络训练的延迟,同时匹配或改善了它们的性能,即使在大规模实验中也是如此。*
* *发现更紧凑的网络(例如 MobileNetV2 与 ResNet)对较低的量化级别不太鲁棒,这意味着 CPT 中使用的精度下限必须稍微提高。*
## *我们在实践中可以用什么?*
*尽管动态量化方案取得了令人印象深刻的结果,但这种方法不能在当前的硬件上实际使用。特别是,当前的 NVIDIA GPUs 仅支持半精度的混合精度训练(例如,16 位格式)。然而,这种精度的降低仍然可以在许多常见深度学习工作流的模型训练时间方面产生高达 3 倍的加速。另外,当前的语言/软件包(例如 PyTorch、TensorFlow、Julia 等。)开始建立对任意的、低精度的算术的支持,因为量化训练的研究证明了它的好处。*
*尽管当前的硬件不支持更复杂的量化技术,但使用半精度对于深度学习实践者来说非常有用,因为它可以显著减少模型训练时间,而不会明显降低性能。此外,半精度的培训是由 Apex 等工具抽象的,允许从业者在不进行大量实施更改的情况下显著减少培训时间。请看[这里](https://github.com/NVIDIA/apex/tree/master/examples/imagenet)的例子。*
*鉴于在本概述中描述的许多量化方法还不被现代硬件支持,人们可能想知道我们实际上如何知道这样的方法实际上有益于训练效率。有许多指标和方法可以量化这一优势,例如:*
* *计算训练期间使用的有效 FLOPS 数,如[4](也称为 MACs [2]或 GBitOPs [3])所述*
* *在 [FPGA](https://www.xilinx.com/products/silicon-devices/fpga/what-is-an-fpga.html#:~:text=Field%20Programmable%20Gate%20Arrays%20%28FPGAs,or%20functionality%20requirements%20after%20manufacturing.) 上实施低精度训练,并测量实际性能指标(例如,能耗、延迟等。)*
* *使用模拟器测量性能指标(如 BitFusion [14])*
*通过利用这些技术,量化训练中的现有工作可以证明,尽管当前硬件中的支持有限,但所提出的方法对训练效率提供了显著的益处。*
# *外卖食品*
*这一概述的主要要点可以简单陈述如下:*
* *神经网络训练对于网络激活和梯度的低精度表示的使用是鲁棒的*
* *在训练期间利用低精度可以在训练时间、能量效率和计算成本方面产生显著的改进*
*这些发现在本质上是非常重要的。此外,即使是操纵神经网络精度的更复杂的方法也只是利用这些基本发现并扩展它们,以产生进一步降低精度的更智能/更复杂的方法。因此,量化培训是一种简单易懂的方法,具有不可思议的潜力和广泛的实际影响。*
*在了解低精度训练时,出现了几个基本的深度学习概念:*
* ***前向/后向传播**(以及量化可能发生的地方)*
* ***批量归一化**(及其对数值不稳定性的敏感性)*
* ***超参数计划**(例如,循环学习率/精度)*
* ***梯度对噪声的敏感度**(例如,来自较低精度)*
*这些概念中的每一个都经常出现在深度学习文献中,对于任何想要更深入了解的深度学习实践者来说,理解这些概念都很重要。这些想法大多很容易理解。然而,对于那些有兴趣了解更多量化梯度难度背后的细节的人,我推荐阅读[1]中的第 4 节到第 6.1 节。*
## *结论*
*非常感谢您阅读这篇文章——非常感谢您对我的内容的支持和兴趣。如果你喜欢它,请订阅我的[深度(学习)焦点时事通讯](https://cameronrwolfe.me/signup),在那里,我每两周对深度学习研究中的一个相关主题进行情境化、解释和检查。欢迎在 [medium](https://medium.com/@wolfecameron) 上关注我,或者浏览我网站的其他部分,那里有我的社交媒体和其他内容的链接。如果您有任何建议/反馈,请直接联系我或在此帖子上留言!*
# *文献学*
*[1] Banner,Ron 等,“神经网络 8 位训练的可扩展方法”*神经信息处理系统进展* 31 (2018)。*
*[2]傅,,等.“分形训练:在时间和空间上分数压缩比特节省,用于有效的 dnn 训练”*神经信息处理系统进展*33(2020):12127–12139。*
*[3]傅,,等.“CPT:基于循环精度的高效深度神经网络训练”arXiv 预印本 arXiv:2101.09868 (2021)。*
*[4]周,舒畅,等.“Dorefa-net:用低位宽梯度训练低位宽卷积神经网络” *arXiv 预印本 arXiv:1606.06160* (2016)。*
*[5]杨,于宽等,“用全 8 位整数训练高性能大规模深度神经网络。”神经网络 125(2020):70–82。*
*[6] Rahaman,Nasim,等,“论神经网络的谱偏差”*机器学习国际会议*。PMLR,2019。*
*[7]阿奇利、亚历山德罗、马特奥·罗韦尔和斯特凡诺·索阿托。"深度网络中的关键学习期."*国际学术交流会议*。2018.*
*[8]李,,魏,马腾宇."解释神经网络训练中初始大学习率的正则化效应."*神经信息处理系统进展* 32 (2019)。*
*[9] Das,Dipankar 等,“使用整数运算的卷积神经网络的混合精度训练” *arXiv 预印本 arXiv:1802.00930* (2018)。*
*[10]陈,文林,等.“用散列法压缩神经网络”*机器学习国际会议*。PMLR,2015 年。*
*[11] Jaderberg,Max,Andrea Vedaldi 和 Andrew Zisserman。"用低秩展开加速卷积神经网络." *arXiv 预印本 arXiv:1405.3866* (2014)。*
*[12]乌烈芝、卡伦、爱德华·密德斯和马克斯·韦林。"用于神经网络压缩的软加权共享." *arXiv 预印本 arXiv:1702.04008* (2017)。*
*[13] Gupta,Suyog,等,“有限数值精度的深度学习”*机器学习国际会议*。PMLR,2015 年。*
*Sharma,Hardik 等人,“位融合:用于加速深度神经网络的位级动态组合架构” *2018 年 ACM/IEEE 第 45 届计算机体系结构国际年会(ISCA)* 。IEEE,2018。*
# 量子深度学习:量子卷积神经网络快速指南
> 原文:<https://towardsdatascience.com/quantum-deep-learning-a-quick-guide-to-quantum-convolutional-neural-networks-d65284e21fc4>
## *你需要知道的关于量子卷积神经网络(QCNNs)的一切,包括与经典计算方法相比,这些方法的优势和局限性*

芬兰埃斯波的 IQM 量子计算机 [Ragsxl](https://commons.wikimedia.org/wiki/File:IQM_Quantum_Computer_Espoo_Finland.jpg)
近年来[对量子计算的投资显著增加](https://semiengineering.com/progress-in-quantum-computing/),安全和网络通信等领域的量子方法有望颠覆现有的经典计算技术。
Garg 和 Ramakrishnan 等研究人员认为,量子计算的核心是“通过计算成本更低的技术解决经典难题”。或许不足为奇的是,正如近年来深度学习和量子计算的研究平行增长一样,许多人现在正在研究这两个领域交汇的可能性:量子深度学习。
在本文中,我们将在高层次上讨论量子深度学习的现有研究和应用,重点是混合量子卷积神经网络(QCNNs)。首先,提供了与经典计算相比的量子计算的简要定义。从这里,纠缠被定义,以及纠缠态和它们的应用。
接下来,讨论了经典卷积神经网络(CCNNs,或 CNN)的概述。最后,讨论了 QCNNs 及其性能,以及这些方法的优点和局限性。
# 经典计算和量子计算的区别
如果你对量子计算完全陌生,一个重要的介绍性概念是经典计算(我们通常用于计算任务)和量子计算之间的区别。在传统的计算机上,当一个程序被执行时,编译器被用来把程序的语句翻译成二进制位上的操作。
不像经典计算机上的比特在任何时候都代表 1 或 0,量子比特能够在这两种状态之间“徘徊”。只有在测量时,量子位才会“坍缩”成它的一种状态。
这种性质被称为叠加,对量子计算任务至关重要(Ganguly,Cambier,2021)。通过叠加,量子计算机可以并行执行任务,不需要完全并行的架构或 GPU 来处理并行计算任务。这是因为如果每个叠加态对应于不同的值,如果叠加态被作用,那么该作用在所有状态上同时执行。
叠加量子态的一个例子如下:

和 **a** 和 **b** 指的是概率幅度,其给出了一旦执行测量就投射到一个状态的概率。叠加量子态是通过使用量子逻辑门产生的。如果 Bra-ket 符号对你来说是新的,那么强烈推荐 Perry 的量子计算的*圣殿。*
# 纠缠的简要介绍
正如叠加是量子物理中的一个重要原理一样,另一个要讨论的关键领域是纠缠。纠缠是指在两个或多个粒子之间产生或导致相互作用的行为,这意味着这些粒子的量子状态不再能够彼此独立地描述,即使当[相隔很远](https://www.ofcom.org.uk/__data/assets/pdf_file/0013/222601/Executive-Summary.pdf)时也是如此。当粒子变得纠缠时,如果一个粒子被测量,那么与之纠缠的另一个粒子将测量为相反的状态,瞬间(这些粒子没有局域态)。
# 纠缠——贝尔态
随着对量子位和纠缠的理解,现在可以讨论贝尔态了。这些是量子比特的最大纠缠态,它们是:
|00⟩ → β → 1 √ 2 (|00⟩ + |11⟩) = |β00⟩,
|01⟩ → β → 1 √ 2 (|01⟩ + |10⟩) = |β01⟩
|10⟩ → β → 1 √ 2 (|00⟩ − |11⟩) = |β10⟩
|11⟩ → β → 1 √ 2 (|01⟩ − |10⟩) = |β11⟩
贝尔态是由以下量子电路产生的:

*贝尔态电路来自佩里的量子计算圣殿。*
这里显示了一个贝尔态电路,它接受量子位输入,并应用哈达玛和 CNOT 门来创建一个纠缠的贝尔态。
虽然理解不同的量子门超出了本文的范围,但是考虑到旋转和 CNOT 门将作为 QCNNs 部分的一部分来讨论,建议使用指南的[。](https://qiskit.org/textbook/ch-states/single-qubit-gates.html..)
贝尔态已经被用来开发一系列的量子计算应用。例如, [Hegazy、Bahaa-Eldin 和 Dakroury 已经从理论上证明了贝尔态和超密集编码可以用来获得“无条件安全”。](https://arxiv.org/abs/1402.6219)
# 经典深度学习:卷积神经网络
随着对量子计算的介绍,我们现在将讨论深度学习的经典方法,特别是卷积神经网络(CNN)。
正如 Franç ois Chollet 在*用 Python 进行深度学习*中指出的那样,卷积神经网络(CNN)已被证明在图像分类等任务中很受欢迎,因为它们能够建立模式的层次结构,例如首先表示线条,然后表示这些线条的边缘。这允许 CNN 建立在层之间的信息上,并表示复杂的视觉数据。
CNN 有卷积层,它由过滤器组成,过滤器“滑过”输入并产生一个[“特征图”,允许检测输入中的模式](https://books.google.co.uk/books/about/Practical_Convolutional_Neural_Networks.html?id=v3XyvQEACAAJ&redir_esc=y)。CNN 还使用池层来减少特征图的大小,从而减少学习所需的资源。关于这方面的更多信息,[哦,崔和金的 2020 年 CNN 指南被强烈推荐](https://arxiv.org/abs/2009.09423)。

[Cecbur](https://commons.wikimedia.org/wiki/File:Convolutional_Neural_Network_NeuralNetworkFeatureLayers.gif) 所示的卷积神经网络(CNN)
# 量子卷积神经网络——一个混合网络例子
回到手头的主题,定义了经典 CNN,现在可以探索量子 CNN 如何利用这些传统方法并扩展它们。Garg 和 Ramakrishnan 发现,开发量子神经网络的一种常见方法是开发一种“混合”方法,引入所谓的“量子层,一种基于随机量子电路的变换,作为经典 CNN 的附加组件”。
在这一节中,我们将讨论由 [Lü等人](https://arxiv.org/abs/2107.03630)开发并在 MNIST 手写数字数据集上测试的混合 QCNN。对于他们的混合 QCNN, [Lü等人,](https://arxiv.org/abs/2107.03630)在他们 2021 年的论文中,使用量子电路和纠缠作为经典模型的一部分来获取输入图像,然后生成预测作为输出。
在这种方法中,量子卷积神经网络(QCNN)将图像数据作为输入,并将其编码为量子态 **|x >** ,然后进行转换,并使用量子卷积层和池层提取特征(Lü等人,2021)。
最后,使用强纠缠电路的全连接层用于执行分类,并通过测量获得预测(Lü等人,2021)。
用于减少训练数据标签和由 QCNN 预测的标签之间的差异的优化由随机梯度下降(SGD)处理。关注量子电路,量子卷积层中使用的门如下所示,结合了旋转和 CNOT 门运算符。
在汇集层,测量量子位的子集,然后得出的结果决定是否对它们的邻居应用单量子位门:
全连接层由“通用单量子比特量子门”和产生纠缠态的 CNOT 门组成。为了将 QCNN 与其他方法进行比较,Lü等人使用了 MNIST 数据集和模拟的 QCNN。按照典型方法,创建了一个训练/测试数据集,并开发了一个由以下层组成的 QCNN:
* 2 个量子卷积层
* 2 个量子池层
* 1 量子全连接层
该 QCNN 对数据集的测试集准确率达到了 96.65%。相比之下,根据代码为的[论文,这个数据集的最高准确率为 99.91%。然而,值得注意的是,对于这个实验,只有两类 MNIST 数据集被分类,这意味着与其他 MNIST 模型性能的全面比较是有限的。](https://paperswithcode.com/sota/image-classification-on-mnist)
# 评估 QCNNs 的可行性
虽然 Lü等人的研究人员已经开发了量子 CNN 的方法,但该领域的一个关键挑战是实现理论模型所需的[硬件还不可用](https://arxiv.org/pdf/2005.04316.pdf)。除此之外,还有与混合方法特别相关的挑战,混合方法在经典计算方法的基础上引入了量子层。
如果我们认为量子计算的一个关键好处是,它可以通过计算成本更低的技术来解决“经典的棘手问题”,这些解决方案的一个重要方面是“量子加速”:当探索量子机器学习的好处时,Phillipson (2020)提出,与经典实现相比,预计量子算法将[具有多项式甚至指数加速时间](http://ceur-ws.org/Vol-2561/paper5.pdf)。然而,Lü等人的方法的局限性在于,对于需要经典数据和测量的一致解码/编码的算法,例如 QCNN,“量子加速”增益是有限的。阿伦森和[亨德森等人](https://arxiv.org/abs/1904.04767)对此进行了讨论。,在他们各自的论文中。目前,关于如何最好地设计编码/解码和需要最小测量的协议以受益于“量子加速”的信息有限。
更一般地,纠缠已经被证明是量子机器学习的一个重要属性。Lü等人提出的 QCNN 利用强纠缠电路,可以产生纠缠态作为其全连接层,允许模型进行预测。纠缠已经在其他地方被用来帮助深度学习模型,例如[刘等人利用纠缠](https://www.frontiersin.org/articles/10.3389/fams.2021.716044/full)从图像中提取重要特征。此外,Sharma 等人[发现,在数据集中使用纠缠可能意味着模型能够从比之前预期的更小的训练数据集中学习](https://arxiv.org/abs/2007.04900),从而完善了所谓的“没有免费的午餐”定理。
# 关于 QCNNs 的结论
在这篇文章中,提供了经典和量子深度学习方法的比较,以及利用量子层(包括强纠缠电路)来生成预测的 QCNN 的概述。量子深度学习的好处和局限性已经讨论过了,包括纠缠在机器学习中更普遍的应用。
考虑到这一点,现在可以考虑量子深度学习的下一步,特别是 QCNNs。Garg 和 Ramakrishnan 发现,除了图像识别,量子方法已经开始用于自然语言处理(NLP)等领域,例如 [Galofaro 等人检测仇恨言论的工作](https://arxiv.org/abs/1811.03275v1)。
与此同时,我们也看到了量子硬件的进步,像 [PsiQuantum 这样的公司致力于开发百万量子位量子处理器](https://www.forbes.com/sites/moorinsights/2022/09/21/psiquantum-has-a-goal-for-its-million-qubit-photonic-quantum-computer-to-outperform-every-supercomputer-on-the-planet/?sh=16f56ea18db3)。因此,尽管我们已经看到了与应用量子神经网络相关的挑战,但随着研究在深度学习和量子计算的“交界处”继续进行,我们可以期待看到量子深度学习的进一步发展。
# 相关资源
对于那些感兴趣的人,提供了一个关于相关量子计算和深度学习资源的小型参考书目,以及文章中的链接。
Aaronson,S. (2015)“阅读小字”,《自然物理学》,11(4),第 291–293 页。doi: 10.1038/nphys3272。
比亚蒙特,j .等人..(2017)《量子机器学习》,Nature,549(7671),第 195–202 页。doi: 10.1038/nature23474。
Chollet,(2021 年)。用 Python 进行深度学习。第二版。庇护岛(纽约,Estados Unidos):曼宁。
Ganguly 和 t . Cambier,2021 年。量子计算与 Silq 编程。打包。
Garg,s .和 Ramakrishnan,G. (2020)量子深度学习的进展:概述,arXiv.org。地点:【https://arxiv.org/abs/2005】T2。
Hegazy,o .,Bahaa-Eldin,a .和 Dakroury,Y. (2014 年)使用纠缠和超密集编码的量子安全直接通信,arXiv.org。地点:【https://arxiv.org/abs/1402.6219
Henderson,m .等人,(2019)量子神经网络:用量子电路为图像识别提供动力,arXiv.org。上市地点:[https://arxiv.org/abs/1904.04767](https://arxiv.org/abs/1904.04767)
Karn,U. (2016)卷积神经网络的直观解释— KDnuggets。可从以下网址获得:[https://www . kdnugges . com/2016/11/intuitive-explain-convolutionary-neural-networks . html](https://www.kdnuggets.com/2016/11/intuitive-explanation-convolutional-neural-networks.html)
刘,杨等,(2021)“基于纠缠的张量网络机器学习特征提取”,应用数学与统计前沿,7。doi: 10.3389/fams.2021.716044。
吕,y .等人,(2021)用于图像分类的量子卷积神经网络,arXiv.org。可在:[https://arxiv.org/abs/2107.03630](https://arxiv.org/abs/2107.03630)买到。
北达科他州梅塔,2020 年。量子计算。【S.l .】:实用主义书架。
Ofcom,(2021)量子通信:未来通信的新潜力可从以下网址获得:[https://www . ofcom . org . uk/_ _ data/assets/pdf _ file/0013/222601/Executive-summary . pdf](https://www.ofcom.org.uk/__data/assets/pdf_file/0013/222601/Executive-Summary.pdf)
Oh,s .,Choi,j .和 Kim,J. (2020)量子卷积神经网络教程(QCNN),arXiv.org。地点:【https://arxiv.org/abs/2009.09423
s . pattanayak(2021 年)。使用 Python 的量子机器学习:使用来自 Google Research 和 IBM Qiskit 的 Cirq。阿普瑞斯。
菲利普森,F (2020)。量子机器学习:好处和实例。地点:【http://ceur-ws.org/Vol-2561/paper5.pdf
T. R .佩里(2004 年)。量子计算的殿堂:1.1 版—2006 年 4 月 29 日。莱利·t·佩里。
Sewak,Karim,Pujari,P .(2018 年)。实用卷积神经网络。伯明翰:Packt。
Sharma,k .等人(2022) “纠缠数据集的无免费午餐定理的重新表述”,《物理评论快报》,128(7)。doi:10.1103/physrevlett . 128.070501。
Voorhoede,D. (2022)叠加和纠缠,量子激励。可从以下网址获得:[https://www . quantum-inspire . com/kbase/叠加和纠缠/](https://www.quantum-inspire.com/kbase/superposition-and-entanglement/)
# 用 Qiskit 减轻量子测量
> 原文:<https://towardsdatascience.com/quantum-measurement-mitigation-with-qiskit-bb35b3d28eec>
## 参与 IBM 量子开放科学奖的下一步
量子机器学习要不要入门?看看 [**动手量子机器学习用 Python**](https://www.pyqml.com/page?ref=medium_measmitig&dest=/) **。**
今天,我们将使用 Qiskit 实现一种量子测量误差缓解方法。我们这样做是为了参加 IBM 的第二届量子开放科学奖。他们要求解决一个量子模拟问题。他们希望我们在他们的 7 量子位 Jakarta 系统上使用 Trotterization 模拟一个三粒子系统的海森堡模型哈密顿量。难的部分不是模拟海森堡模型的哈密顿量。对一个三粒子系统来说,这样做也不成问题。并且,使用 Trotterization 是—是的,没错—也没问题。
令人惊讶的是,问题是在一个实际的 7 量子位设备上做所有这些。量子系统极其脆弱。它们易受环境干扰,容易出错。不幸的是,我们没有资源来纠正这些硬件或低软件级别的错误。我们能做的最好的事情就是减少噪声对计算的影响。
幸运的是,有很多减少量子错误的方法。例如,在以前的帖子中,我们[仔细研究了 Clifford 数据回归(CDR)方法](https://pyqml.medium.com/mitigating-quantum-errors-using-clifford-data-regression-98ab663bf4c6)。我们甚至在模拟和实际的量子设备上使用它来降低噪声[(](/how-to-implement-quantum-error-mitigation-with-qiskit-and-mitiq-e2f6a933619c)[https://towards data science . com/practical-error-impression-on-a-real-quantum-computer-41 a99 dddf 740](/practical-error-mitigation-on-a-real-quantum-computer-41a99dddf740))。
CDR 降低了计算可观察值的期望值时的噪声。但是要参与 IBM 的挑战,我们需要减少测量计数中的误差。IBM 使用量子状态层析算法来计算结果状态与预期状态的匹配程度。这种算法适用于测量计数。
不幸的是,虽然我们可以从计数中计算期望值,但我们不能反过来做。因此,相反,我们需要调整我们的量子误差缓解方法来处理测量计数。换句话说,我们正在实施量子测量误差缓解。
我说过用 Trotterization 模拟一个三粒子系统的海森堡模型哈密顿量不成问题吗?我是一个守信用的人。下面的源代码提供了所需的模拟。IBM 提供这些代码作为他们的典型 GitHub 项目的一部分。
只有一个变化。如果我们将变量`measure`设置为`True`,我们将在电路中添加一个`ClassicalRegister`来接收测量值。
今天,我们想把重点放在度量缓解上。因此,我们照原样接受代码。我们最终以`qc`作为我们的量子电路。
下图描述了电路图。

接下来,我们需要一个运行时环境。今天,我们可以接受模拟环境。但是为了尽可能接近真实的量子计算机,我们从真实的设备加载噪声特征。为此,我们连接到我们的 IBM 帐户并从中获取雅加达后端。[这篇文章](/how-to-run-code-on-a-real-quantum-computer-c1fc61ff5b4)详细阐述了如果你还没有一个免费的 IBM 帐户,你如何获得它。
其实我们需要两个环境。在嘈杂的模拟器旁边,我们需要一个没有噪音的环境。Qiskit `QasmSimulator`为我们做了这项工作。
现在让我们用两个模拟器运行我们的量子电路。
noisy: {'101': 106, '111': 124, '011': 141, '000': 113, '010': 124, '100': 115, '110': 207, '001': 94}
noise-free: {'101': 34, '110': 866, '011': 124}
计数差异很大。所以,让我们去除噪音吧!我们可以为每个状态计算一个将噪声值变为无噪声值的修改量。
例如,我们在有噪声的模拟中观察到状态|101⟩ 105 次,但在无噪声的模拟中只有 30 次。当我们将噪声值乘以修正值 30/105 时,我们得到无噪声值。
让我们对所有八个州都这样做。
modifier: [0.0, 0.0, 0.0, 0.8794326241134752, 0.0, 0.32075471698113206, 4.183574879227053, 0.0]
首先,我们添加一个小助手函数`sorted_counts`。它做两件事。一方面,它确保所有的状态键都存在。例如,代表州|000⟩的关键字`000`在无噪声计数中不存在。另一方面,该函数按键对计数进行排序,从`000`开始,到`111`结束。
这是下一步的前提。我们将两个`counts`字典转换成列表,然后`zip`它们。这意味着我们得到成对的值。位置 0 时无噪声,位置 1 时有噪声。因此,我们通过将无噪声值除以有噪声值来计算每个状态的修正值。
我们可以使用这些修改器来减轻电路的后续测量。所以,让我们重新运行电路。
noise-free: {'000': 0, '001': 0, '010': 0, '011': 124, '100': 0, '101': 34, '110': 866, '111': 0}
noisy: {'000': 114, '001': 109, '010': 120, '011': 114, '100': 126, '101': 105, '110': 211, '111': 125}
mitigated: {'000': 0.0, '001': 0.0, '010': 0.0, '011': 100.25531914893618, '100': 0.0, '101': 33.679245283018865, '110': 882.7342995169082, '111': 0.0}
我们一眼就可以看出,与未减轻的计数相比,减轻的计数更接近无噪声。因此,让我们将这种缓解用于 IBM 挑战。
IBM 基于量子状态层析成像的保真度来评估我们的缓解性能。这听起来比实际更复杂。
简单来说,量子态层析术是在测量的基础上重建量子态。保真度告诉我们重建的状态和预期的状态有多接近。保真度为 1 表示完全符合。
不过,有一个问题。量子位的量子状态是不可见的。它是两个基态的复合(如在复数中)线性组合。这些基本状态是零个|0⟩和一个|1⟩.布洛赫球是这种量子态最受欢迎的代表之一。

基本上,量子态是矢量|𝜓⟩.它的头部位于球体表面的任何地方。该表面还穿过基础状态,顶部是|0⟩,底部是|1⟩。
不幸的是,每当你看量子态时,你只会看到 0 或 1。状态向量与基态的接近度表示测量任一值的概率。所以,你可以通过重复运行电路来估计接近度。但是,我们谈论的是一个球体。有无限多种可能的量子态具有相同的概率。例如,看看球体的赤道。那里的任何一点到位于球体两极的基态都有相同的距离。
为了重现量子态,我们需要从不同的角度来看这个球体。这就是我们在量子态层析成像中所做的。这些是 X、Y 和 Z 轴。如果我们有两个量子位,就有九个角度:XX,XY,XZ,YX,YY,YZ,ZX,ZY 和 ZZ。如果我们有一个由三个量子位组成的量子系统,就有 27 个不同的角度。因此,我们必须创建 27 个版本的量子电路,每个版本代表一个视角。
幸运的是,Qiskit 为我们提供了创建这些电路的功能。这个函数要求输入量子电路不包含任何测量,但是我们在调用函数时定义了要测量的量子位。因此,我们设置`measure=False`。
There are 27 circuits in the list
尽管这些电路几乎相同,但它们的测量结果却大相径庭。因此,我们为每个回路计算单独的修正系数。
noisy: {'000': 125, '001': 120, '010': 136, '011': 123, '100': 115, '101': 125, '110': 132, '111': 148}
nose-free: {'000': 144, '001': 88, '010': 208, '011': 84, '100': 66, '101': 213, '110': 93, '111': 128}
modifier: [1.152, 0.7333333333333333, 1.5294117647058822, 0.6829268292682927, 0.5739130434782609, 1.704, 0.7045454545454546, 0.8648648648648649]
noisy: {'000': 139, '001': 112, '010': 127, '011': 133, '100': 113, '101': 127, '110': 136, '111': 137}
nose-free: {'000': 162, '001': 88, '010': 233, '011': 33, '100': 29, '101': 213, '110': 118, '111': 148}
modifier: [1.1654676258992807, 0.7857142857142857, 1.8346456692913387, 0.24812030075187969, 0.25663716814159293, 1.6771653543307086, 0.8676470588235294, 1.0802919708029197]...
接下来,我们需要在状态层析成像函数中使用修饰符。我从 IBM 的源代码中取出原始代码并进行修改。进一步,我们用`OwnResult`。这是我们在上一篇文章中开发的一个类。它是 Qiskit `Result`的包装器,允许我们更改计数。
函数`state_tomo`将结果、层析成像电路和是否减轻的标志作为参数。`result`必须包含所有 27 个断层扫描电路的结果。我们从`target_state`的定义开始。这是我们与重建状态进行比较的状态。
`idx`变量是电流电路的计数器。我们用它来得到相应的修饰符。现在,我们遍历电路,计算减轻的计数,并把它们放入状态层析成像装配器的`OwnResult`对象中。最后,我们计算保真度。
让我们运行所有这些电路。无声的工作作为对比。
noisy state tomography fidelity = 0.2161 ± 0.0000
noise-free state tomography fidelity = 0.8513 ± 0.0000
mitigated state tomography fidelity = 0.8317 ± 0.0000
结果显示,通过我们的缓解措施,情况有了显著改善。我们几乎实现了无噪声保真度。然而,我们需要批判性地看待这些结果。
我们使用无噪声结果计算修改量。这是唯一可能的,因为我们的电路是经典可模拟的。所以,我们知道预期的结果。但是,当然,承担使用真正的量子计算机的所有压力只有在经典计算机难以完成的任务中才是有意义的。但是,如果我们不能经典地模拟它,我们就不能计算修改量。
然而,我们的结果作为概念的证明。我们看到,量子测量缓解效果很好,可以用于量子状态层析成像。
量子机器学习要不要入门?看看 [**动手量子机器学习用 Python**](https://www.pyqml.com/page?ref=medium_measmitig&dest=/) **。**

在这里免费获得前三章。
# 量子力学及其对现实的意义
> 原文:<https://towardsdatascience.com/quantum-mechanics-and-its-implications-for-reality-e88e33a53c09>
## 多重世界(多元宇宙),回溯因果关系(时间对称)和意识导致崩溃

马库斯·斯皮斯克在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的照片
这篇文章将触及量子力学对现实本质的三个非常规含义。它将提供高水平的量子实验和假设的描述,试图在量子水平上符合奇异的观察。值得注意的是,不同的著名物理学家团体对十几个学派的不同解释没有达成共识。
机器学习和研究量子力学之间有一些相似之处。在机器学习中,我们试图根据可用的数据建立模型。同样,物理学家正试图从宏观和量子两个层面建立一个基于实验数据的现实模型。
这篇文章中涉及的三个量子解释与下面的常识逻辑相矛盾。
1. 事件的结果与观察者无关。
2. 只有一种版本的事件会及时结束。
3. 时间在前进,过去的事情影响着现在。
这些解释可以在下面的实验中描述。
1. 双缝实验
2. 延迟选择量子擦除器
## 双缝实验
这是最受欢迎的量子实验,已经存在了一个多世纪。它证实了亚原子粒子的波粒二象性,并提出测量(观察)是粒子被观察属性的一个因素。在实验中,亚原子粒子(如光子、电子)被射向一面有两个狭缝的墙(一次一个)。当狭缝周围有一个观察者(或测量设备)时,粒子一次只通过一个狭缝,我们在探测器屏幕上得到两个条纹图案,即每个狭缝一个条纹。当没有测量装置时,电子同时穿过两个狭缝,如图 1 所示,在狭缝另一侧的检测屏上有多个条纹的可见干涉图案证明了这一点。

图 1:量子物理中“双缝实验”的图解|来源链接 [1](https://commons.wikimedia.org/wiki/Category:Double-slit_experiments#/media/File:Double-slit.svg) / [2](https://commons.wikimedia.org/wiki/Category:Double-slit_experiments) (CC BY-SA 4.0)
换句话说,当有观察者(或观察)时,电子的行为类似于粒子(物质)。当没有观测或没有办法知道粒子的路径时,它就像一个波,采取所有可能的路线(即,表现出所有的可能性)。这意味着观察或了解的行为会以某种方式影响粒子的渲染。随后,它提升了 ***意识*** 在粒子事件中的重要性。事实上,量子力学的两位创始人之一马克斯·普朗克(尼尔斯·玻尔是另一位)甚至说:
> “我视意识为根本。我认为物质是意识的衍生物。我们不能落后于意识。我们谈论的一切,我们认为存在的一切,都以意识为前提。”
> 来源:[https://big think . com/words-of-wisdom/max-Planck-I-视意识为根本/](https://bigthink.com/words-of-wisdom/max-planck-i-regard-consciousness-as-fundamental/)
请参阅我之前的帖子,了解量子特性的基础知识(例如*叠加、纠缠等)。*)和量子计算。
</what-is-a-quantum-computer-f1c44e87a379>
如果仅仅一次观察就能把一个*叠加*的可能性瓦解成一个粒子的可能性,那么物质真的是意识的衍生物吗?许多人认为这种解释过于主观,不予考虑。还有另一种更客观的解释反驳了*因果观察者*和*坍缩波函数*的说法,称为 ***多世界解释*** 或***【MWI】***(***多元宇宙*** 假说的一个分支)。MWI 消除了对波函数坍缩的需要,也消除了对拥有无限多个宇宙的观察者的需要,在这些宇宙中,每一种可能性都被分别表现出来。
美国物理学家休·埃弗雷特最初提出了 MWI,并得到了包括斯蒂芬·霍金在内的许多杰出物理学家的支持,霍金称其为“*不言自明的正确*”([来源](https://www.nbcnews.com/mach/science/weirdest-idea-quantum-physics-catching-there-may-be-endless-worlds-ncna1068706))。同一篇文章援引埃弗雷特的话说:
> "..所有可能的结果都真的发生了——但是在我们居住的世界里只有一个版本。所有其他的可能性都从我们身上分离出来,各自产生了自己独立的世界。按照这种观点,没有什么东西会被浪费掉,因为任何可能发生的事情都会在某个世界发生。”
有些人认为 MWI 过于假设,而且从经验上看是不可证伪的。
## 延迟选择量子擦除器
这是最近(1999 年)对*双缝实验*的延伸,在这里事情变得更加怪异。在 DCQE 中,从双缝出来的亚原子粒子被分裂成纠缠粒子,并通过/反射通过棱镜(PS)、分束器(BSₓ)和反射镜(Mₓ),如图 2 所示。他们走不同的路线到达 D₀-D₄.的 5 个探测器屏幕该实验不需要狭缝附近的测量装置,以消除装置干扰结果的可能性。

图 2:一个“延迟选择量子橡皮擦实验”的例子来源链接 [1](https://en.wikipedia.org/wiki/Delayed-choice_quantum_eraser) / [2](https://en.wikipedia.org/wiki/File:Kim_EtAl_Quantum_Eraser.svg) , [GNU 自由文档许可证](https://en.wikipedia.org/wiki/en:GNU_Free_Documentation_License),版本 1.2 / [知识共享](https://en.wikipedia.org/wiki/en:Creative_Commons)归属共享 [4.0 国际](https://creativecommons.org/licenses/by-sa/4.0/)
初级纠缠粒子经过最短的路径到达检测器 D₀,而其余的纠缠粒子经过较长的路径到达各自的检测器 D₁-D₄.沿着较长路径的分束器有 50%的机会通过,有 50%的机会反射纠缠的粒子。正如你在图 2 的架构中所看到的,如果在 D₃和 D₄检测到一个粒子,我们知道该粒子所经过的*路径*(狭缝),并通过双条纹图案进行确认。如果在 D₁或 D₂探测到一个粒子,就不可能知道*哪条路径*(狭缝)的信息,这也通过干涉图样得到了证实。因此,分束器反射或通过粒子的这种延迟选择保留或擦除了先前的*哪个路径*(或狭缝)信息,并且匹配图案被记录在粒子在到达 D₁-D₄.之前到达的 D₀这意味着**追溯因果关系**(时间对称),即过去的事件与现在的事件相关联。杰出的物理学家如理查德·费曼和约翰·阿奇博尔德·惠勒通过在他们的*吸收体理论* ( [来源](https://authors.library.caltech.edu/11095/1/WHErmp45.pdf))中使用追溯因果关系,赋予了它可信度。下面是惠勒的一段话,来自[https://en . Wikipedia . org/wiki/Wheeler % 27s _ delayed-choice _ experiment](https://en.wikipedia.org/wiki/Wheeler%27s_delayed-choice_experiment),
> “……过去不存在,除非记录在现在”,[宇宙也不存在]“存在,在那里独立于所有的观察行为。”
追溯因果关系是有争议的,大多数物理学家对此持怀疑态度,并拒绝它。
## 摘要
图 3 显示了本文中讨论的量子力学的三种解释与四种属性的比较。

图 3:根据[https://en . Wikipedia . org/wiki/interpretions _ of _ quantum _ mechanics](https://en.wikipedia.org/wiki/Interpretations_of_quantum_mechanics#Comparisons)中的信息创建的插图
这三种解释是量子力学十几种常见解释的一部分。使水更加浑浊的是,一个多世纪以来,科学家们无法调和物理学中的另一个主要矛盾:广义相对论(支配大型物体,如重力、恒星、星系等的定律)。)和*量子力学*(亚原子层面观察到的规律)。希望我们不会经历另一个世纪,而没有一个更巩固的框架来涵盖万物理论(T21)。
## 资源
1. [https://en . Wikipedia . org/wiki/Interpretations _ of _ quantum _ mechanics](https://en.wikipedia.org/wiki/Interpretations_of_quantum_mechanics)
2. https://en.wikipedia.org/wiki/Many-worlds_interpretation
3. 【https://en.wikipedia.org/wiki/Multiverse
4. [https://en.wikipedia.org/wiki/Retrocausality](https://en.wikipedia.org/wiki/Retrocausality)
5. [https://en . Wikipedia . org/wiki/Von _ Neumann % E2 % 80% 93 wign er _ interpretation](https://en.wikipedia.org/wiki/Von_Neumann%E2%80%93Wigner_interpretation)
6. [https://en.wikipedia.org/wiki/Double-slit_experiment](https://en.wikipedia.org/wiki/Double-slit_experiment)
7. [https://en.wikipedia.org/wiki/Delayed-choice_quantum_eraser](https://en.wikipedia.org/wiki/Delayed-choice_quantum_eraser)
8. [https://en . Wikipedia . org/wiki/Wheeler % 27s _ delayed-choice _ experiment](https://en.wikipedia.org/wiki/Wheeler%27s_delayed-choice_experiment)
9. [https://en.wikipedia.org/wiki/Measurement_problem](https://en.wikipedia.org/wiki/Measurement_problem)
10. [https://en . Wikipedia . org/wiki/Wheeler % E2 % 80% 93 费曼 _ 吸收器 _ 理论](https://en.wikipedia.org/wiki/Wheeler%E2%80%93Feynman_absorber_theory)
11. [https://big think . com/words-of-wisdom/max-Planck-I-视意识为根本/](https://bigthink.com/words-of-wisdom/max-planck-i-regard-consciousness-as-fundamental/)
12. [https://big think . com/hard-science/a-new-quantum-theory-predicts-the-future-be-influence-the-past/](https://bigthink.com/hard-science/a-new-quantum-theory-predicts-that-the-future-could-be-influencing-the-past/)
13. 没有回溯因果关系,量子理论的时间对称解释可能吗? —英国皇家学会出版社
14. [https://authors.library.caltech.edu/11095/1/WHErmp45.pdf](https://authors.library.caltech.edu/11095/1/WHErmp45.pdf)
15. [https://www . NBC news . com/mach/science/weirdest-idea-quantum-physics-catching-there-may-be-endless-worlds-ncna 1068706](https://www.nbcnews.com/mach/science/weirdest-idea-quantum-physics-catching-there-may-be-endless-worlds-ncna1068706)
16. [https://www . science alert . com/quantum-physics-theory-predicts-future-may-impact-the-past-retro 因果关系](https://www.sciencealert.com/quantum-physics-theory-predicts-future-might-influence-the-past-retrocausality)
17. [https://www . the guardian . com/news/2015/nov/04/相对论-量子力学-宇宙-物理学家](https://www.theguardian.com/news/2015/nov/04/relativity-quantum-mechanics-universe-physicists)
18. [https://en.m.wikipedia.org/wiki/Theory_of_everything](https://en.m.wikipedia.org/wiki/Theory_of_everything)
19. [https://plato.stanford.edu/entries/qm-retrocausality/](https://plato.stanford.edu/entries/qm-retrocausality/)
20. [https://thereader.mitpress.mit.edu/the-many-worlds-theory/](https://thereader.mitpress.mit.edu/the-many-worlds-theory/)
# quarto:Jupyter 笔记本渲染的游戏改变者
> 原文:<https://towardsdatascience.com/quarto-a-game-changer-for-rendering-jupyter-notebooks-826c885a531f>
## 借助 Quarto,您的 Jupyter 笔记本现在可以轻松发布为各种专业格式

由[卢卡斯·布拉塞克](https://unsplash.com/@goumbik?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)在 [Unsplash](https://unsplash.com/s/photos/data-documents?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 拍摄的照片
作为一个同时使用 R 和 Python 工作的人,我一直希望有一个更好的解决方案将我的 Jupyter 笔记本变成专业外观的文档。R 生态系统在 R Markdown 中有这样一个选项已经有一段时间了,我非常喜欢使用它,事实上我已经在 R 环境中编写了我的 Python 代码,这样我就可以利用 R Markdown 所提供的优势。
我一直对 Jupyter 笔记本的外观和缺乏将它变成更“完美”和更具视觉吸引力的东西的选项有疑问。当然,交互式编程是有帮助的,但是当涉及到为观众发布你的作品时,外观和感觉从来没有切过芥末。
随着 [Quarto](https://quarto.org) 的发布,这一切都改变了。Quarto 是 R Markdown 的一个发展,现在可以在 Jupyter 笔记本上使用。渲染过程是相同的,除了不再使用`knitr`来生成 markdown,Quarto 现在可以使用`knitr`和`jupyter`。
随着一些非常聪明的功能的引入,您现在可以像往常一样编写 Jupyter 笔记本,但添加特定的指令将允许它们以多种格式呈现(如 pdf、html、docx、revealjs、Powerpoint、Wikis、网站、书籍)。特殊注释允许您对文档中的代码执行和放置进行大量的控制,还可以对代码输出的格式和放置进行更多的控制。
此外,您可以在不从根本上改变现有编码工作流程的情况下完成所有这些工作,因为所有 Quarto 参数都可以以一种在您的正常 Jupyter lab 会话中被忽略的方式输入。
在这篇文章中,我将带你通过几个基本的例子,如何把你的 Jupyter 笔记本和渲染成各种格式。我将举例说明一些特性,但是为了使阅读简短,我将只涉及一些简单的特性,并且将在以后的文章中返回来举例说明其他特性。
## 为 Quarto 设置现有笔记本——一个简单的例子
首先你需要在你的系统上安装 Quarto 命令行界面——见[这里](https://quarto.org/docs/get-started/)。如果您在 VSCode 中工作,已经有一个可以安装的 Quarto 附加组件。我相信我们很快也会看到皮查姆的作品。
现在让我们在适当的 Python 环境中获取一个现有的`ipynb`文件——我们称之为`my_notebook.ipynb`。这里有一个简单的例子,我生成了一个很好的相关图。

我们的 Jupyter 笔记本起点
现在我们准备把它渲染成一个四开文档。我们需要的第一件事是一个 YAML 标头,它提供了所需输出类型的信息。**重要的是**,这个 YAML 标题必须作为顶部的*原始块*输入笔记本。不要把它作为代码输入,否则编译器不会理解它,并且会影响 Jupyter 会话的运行。作为原始输入,它在你的交互式编程中被安全地忽略,但是当它被 Quarto 渲染时就有意义了。

添加原始 YAML 页眉
现在你已经创建了你的第一个基本的四开文档——就这么简单!让我们来看看我们放在 YAML 报头中的一些基本参数:
* `title`是你的文档标题
* `format`指示输出的格式和任何特定于该格式的进一步参数。在这种情况下,我已经指定我想要一个 HTML 输出,其中的代码是折叠的,这样我只有在单击适当的部分时才能看到代码(当您的读者是技术人员和非技术人员时,这是一个很好的特性)
* `execute`给出了你希望代码何时在文档中执行的规范。设置`enable: true`意味着每次渲染时都会执行代码。否则,将保存代码输出,并且不会在后续渲染中重新执行该代码。如果您的代码计算量很大或者运行时间很长,这将非常有用。
* `jupyter`指示使用什么来运行代码。在这种情况下,它显然是 Python 3,但也可能有其他引擎,如配置正确的 Julia。
现在您需要做的就是在终端中键入`quarto render my_notebook.ipynb`,您将获得这个简单的 HTML 输出,其中包含折叠的代码(您只需单击代码部分将其展开并查看代码)。

带有代码折叠的基本 HTML 输出
## 可用的四开格式
这是一个非常简单的例子,我们马上会看到一个更有趣的例子,但是 Quarto 有很多格式。只要在 YAML 文件头中用`pdf`替换`html`就会生成一个整洁的 PDF 输出,在 MS Word 中用`docx`替换就会生成。也有许多幻灯片演示输出,网站和各种其他。完整列表可在[这里](https://quarto.org/docs/output-formats/all-formats.html)获得。下面是一个 PDF 格式的渲染示例,带有摘要和作者(输出被分解到另一个页面上)。

带有作者和摘要的 PDF 输出
## 使用代码块参数控制输出
Quarto 最令人印象深刻的特性之一是能够使用特殊的注释语法来控制代码和输出的显示。除了在 YAML 头中定义全局输出选项之外,您还可以在每个代码块中放置 YAML 参数,以给出关于如何处理该代码块的特定指令。这些特殊的注释参数将在您的 Jupyter 实验会话中被忽略,因为它们以 hash 开头,但它们在 Quarto 中有特定的含义。
在这个例子中,我将以`revealjs`格式呈现我的文档,这是一个简洁的幻灯片演示。在主幻灯片上,我希望代码和输出以列的形式并排显示。我也想给我的剧情一个参考。为此,我使用特殊的注释向代码块添加了各种参数,然后在我的文本中引用了`fig-cap`。

带有块参数和引用的 revealjs 表示
请注意,在`revealjs`中,默认情况下不回显代码,因此,如果特定代码块需要回显,则需要显式的`echo: true`参数。其呈现的输出如下:

幻灯片上基于列的输出
代码框有一些简洁有用的特性,比如滚动和复制图标。
在这篇文章中,我仅仅触及了如何使用 Quarto 的皮毛。有大量的格式和参数可以让您更好地控制数据科学文档的外观。我将在不久的将来发布一些后续文章来说明一些更高级的特性。
# 查询 SQL Server 系统表以对数据库进行反向工程
> 原文:<https://towardsdatascience.com/query-sql-server-system-tables-to-reverse-engineer-databases-fe26f98457d6>
## 数据工程、SQL 和 SQL Server
## 每个 Microsoft SQL Server 数据库中的系统表都包含有助于理解数据库的元数据

凯文·Ku 从[派克斯](https://www.pexels.com/photo/data-codes-through-eyeglasses-577585/?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels)拍摄的照片
在长期的信息技术职业生涯中,我担任过软件开发人员、数据库管理员和数据分析师等角色,需要对几个 SQL Server 数据库进行反向工程。通常,这是因为软件供应商不会为他们的应用程序数据库提供专有文档。作为一名数据分析师或工程师,在某些情况下,我需要编写 SQL 查询来为数据分析解决方案获取数据。但是当数据库文档丢失时,我求助于系统表和视图来理解数据库的结构及其包含的数据。
作为数据工程师、数据分析师或数据科学家,您知道对任何数据分析项目所用数据的良好理解是项目成功的关键。但是文档有时是不可用的。如果是这样,并且您的数据源是 SQL Server 数据库,那么应用本文中描述的查询和技术可以帮助您理解数据库的表、列、主键和外键关系,为您提供一个良好的开端。
# 本文的先决条件
要从本文中获得最大收益,您需要以下内容:
* Windows 机器
* Microsoft SQL Server 或 SQL Server Express 的任何最新版本
* Microsoft SQL Server Management Studio(SSMS)
* 对用 SQL 编写 SELECT 语句的基本理解
如果您没有 SQL Server 或 SSMS,下一节将提供这些工具的基本安装说明。
# 什么是系统表?
每个 Microsoft SQL Server 最终用户数据库都包含几十个系统表,这些表由数据库的元数据(关于数据的数据)组成。例如,它存储有关数据库中定义的所有表、列、键、视图、存储过程和视图的数据。虽然您可以在[SQL Server Management Studio](https://docs.microsoft.com/en-us/sql/ssms/download-sql-server-management-studio-ssms?view=sql-server-ver15)(SSMS)中查看其中的一些内容,但是您可以编写 Transact-SQL 来查询从系统表中检索元数据的视图。
以下是存储在系统表中的几种元数据类型:
* 桌子
* 列
* 主键
* 指数
* 表关系(外键)
* 视图
* 存储过程
有关系统表和视图的详细信息,请参见微软的[系统表页面](https://docs.microsoft.com/en-us/sql/relational-databases/system-tables/system-tables-transact-sql?view=sql-server-ver15)。此外,您可以滚动浏览可用的系统视图,如下面 AdventureWorks2019 数据库的 SSMS 屏幕截图所示。

访问系统表的系统视图可以在 SSMS 中浏览。Randy Runtsch 在 SSMS 拍摄的快照。
要了解系统视图的结构,在 SSMS 中,单击其名称左侧的“+”号。然后,点击“+”号进入“栏目”为每一列显示名称、数据类型以及是否允许空值。下面的示例显示了“all_columns”视图中的列。

all_columns 视图中的列。Randy Runtsch 截图。
# 查询系统表以了解最终用户表和列
逆向工程是关于学习事物是如何构造或工作的,不管文档是否可用。虽然每个 SQL Server 数据库都包含许多系统视图,但本教程将向您展示如何使用其中的几个视图来了解数据库中的最终用户表和列。我曾使用类似这里所示的查询对包含数百或数千行的数据库进行逆向工程。虽然这项工作需要几天或几周的时间,但这项技术使我能够使用数据库的数据来创建满足客户需求的数据分析解决方案。
对于本教程,我使用了 SQL Server Express、SSSMS 和 AdventureWorks2019 示例数据库。下面显示的查询将适用于任何最新版本的 SQL Server 和任何 SQL Server 数据库。但是,如果您需要安装 SQL Server 和示例数据库,请查看以下链接:
* [在 Windows 上安装 SQL Server Express 2019](https://www.microsoft.com/en-us/Download/details.aspx?id=101064)
* [安装 SQL Server Management Studio](https://docs.microsoft.com/en-us/sql/ssms/download-sql-server-management-studio-ssms?view=sql-server-ver15)(SSMS)
* 安装 SQL Server 或 SQL Server Express 和 SSMS 后,将 [AdventureWorks2019.bak 文件](https://docs.microsoft.com/en-us/sql/samples/adventureworks-install-configure?view=sql-server-ver15&tabs=ssms)恢复到 SQL Server(此链接中提供了数据库恢复说明)
## 查询最终用户表
下面的查询检索 SQL Server 数据库中定义的每个最终用户表的架构名、表名和行数。因为表是任何关系数据库的基本构建块,所以该查询返回的结果应该提供对数据库存储的数据类型的大致理解。
查询以获取 SQL Server 数据库中所有用户定义的表。兰迪·朗奇的代码。

get_table.sql 查询的示例结果。Randy Runtsch 截图。
## 查询最终用户列
下面的查询检索 SQL Server 数据库的最终用户表中所有列的元数据。它比上面的表查询更复杂,因为它将 sys.columns 视图与其他四个视图连接起来以获得所需的列。
检索 SQL Server 数据库中所有最终用户列的查询。Randy Runtsch 的 SQL 代码。
虽然上面的查询按模式、表和列对结果进行了排序,但有时先按列对数据进行排序是有用的,如下面的屏幕截图所示。这允许您查看列在整个数据库中的使用位置。

列名按列排序 Randy Runtsch 截图。
## 查询主键
另一种有助于理解关系数据库的元数据是每个表的主键。主键定义唯一标识表中某一行的列值或列值组合。下面的查询返回每个主键的名称,以及它所应用的表和列。列 ID 值指示列在多部分键的键中的顺序。
检索所有 SQL Server 最终用户表的主键的查询。兰迪·朗奇的代码。

get_pk.sql 查询的示例结果。Randy Runtsch 截图。
## 查询外键关系
从系统视图中查询表和列元数据可以很好地理解数据库中存储的数据和数据元素的类型。但是要获得关系数据库的知识,理解表之间的外键关系是必不可少的。以下查询返回定义父表和被引用表之间外键关系的列。
查询来检索所有外键关系。兰迪·朗奇的代码。

get_fk_relationships.sql 查询的示例结果。Randy Runtsch 截图。
# 后续步骤
虽然对 SQL Server 数据库进行反向工程是一项耗时的艰苦工作,但有时这是很好地了解数据库的唯一方法。运行上面的查询应该可以提供对数据库的基本理解,除非其创建者对表和列进行了隐晦的命名或者省略了列定义。
一旦您很好地理解了数据库,您就可以通过对最终用户表运行 SELECT 查询来查看和理解它们存储的数据,从而获得更多的知识。然后,您可以使用您的新知识来很好地记录数据库,以支持您或其他数据分析师执行的数据分析项目。
# 查询所有组和用户的 Tableau 工作簿权限
> 原文:<https://towardsdatascience.com/query-tableau-workbook-permissions-for-all-groups-and-users-4ae4e4cc404>
## TABLEAU REST API: TABLEAU-API-LIB 教程
## 使用 Python 和 Tableau REST API 来跟踪谁可以做什么

纳蕾塔·马丁在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的照片
在您的 Tableau 环境中,跟踪谁可以访问什么可能是一件困难的事情。数据治理总是伴随着挑战。一个好的经验法则是将内容组织到特定的项目中,为这些项目分配特定的组权限,并避免完全分配基于用户的权限。
即使您遵循了最佳实践,也有足够的理由维护一个更新的视图,以了解哪些组和用户对您环境中的任何给定内容拥有各种权限。手动编译这些信息很繁琐,而自动化可以消除很多麻烦。
让我们来看看实现自动化的一种方法。
本文中看到的所有代码在最后都以更方便的格式提供,并带有代码所在的 GitHub gist 的链接。
本教程介绍了如何使用 Python [tableau-api-lib](https://github.com/divinorum-webb/tableau-api-lib) 包,并且是如何像使用小桶一样使用 tableau 服务器的系列教程的一部分,让您能够控制 Tableau 服务器的 REST API。
这些教程假设您已经安装了 Python 3。如果你还没有 Python 3,这将帮助你入门:[安装 Python 的指南](https://wiki.python.org/moin/BeginnersGuide/Download)。
## 搭建舞台
有许多 Tableau REST API 端点,您可以在 [Tableau 的 REST API 参考](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm)中阅读所有相关内容。在本教程中,我们将放大查询[工作簿权限](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_permissions.htm#query_workbook_permissions)。
这里演示的概念旨在强调什么是可能的,并且可以扩展到其他权限模型,如视图和数据源。如何将这些例子应用到您自己的产品代码中是您自己的选择。
`tableau-api-lib`库中的所有功能都基于 Tableau REST API。如果你选择使用另一个库,比如 Tableau 的`tableau-server-client`(或者从头开始编写你自己的代码),请注意本教程依赖于`tableau-api-lib`中的帮助函数,这些函数旨在为我们做很多繁重的工作。
在高层次上,我们的目标是获得一个 Pandas 数据框架,它描述了每个工作簿的组及其各自用户的所有权限。以下是相关的里程碑:
1. 创建一个占位符 Pandas DataFrame 来存储所有权限
2. 获取描述我们所有工作簿的熊猫数据框架
3. 获得一个描述我们环境中所有群体的熊猫数据框架
4. 遍历每个工作簿,获取每个组和该组中每个用户的权限,并将数据追加到我们的占位符数据框架中
5. 获得一个描述我们环境中所有用户的熊猫数据框架
6. 遍历每个工作簿,获取单个用户的权限(用户级,而不是组级),并将数据追加到我们的占位符数据框架中
7. 将结果数据帧输出到一个 CSV 文件,该数据帧描述了我们工作簿的所有组和用户权限
8. 使用 Tableau Desktop 连接到 CSV 文件并与结果进行交互
## 步骤 1:确保你已经安装了 tableau-api-lib
即使你是这些教程的专家,帮自己一个忙,下载最新版本的库。它定期更新。
`pip install --upgrade tableau-api-lib`
不熟悉 Python 这个东西?别担心,你会很快明白的。跟随[这个入门教程](https://medium.com/snake-charmer-python-and-analytics/tableau-server-on-tap-getting-started-89bc5f0095fa)。该教程将引导您使用 tableau-api-lib 连接到 Tableau 服务器。
# 步骤 2:进入 Tableau 服务器环境
使用下面的代码作为连接到您的服务器的模板。在接下来的步骤中,我们将使用一次性代码行来构建这个 boiler 板。在本文的最后,您会发现一个合并的代码块,为了方便起见,您可以复制/粘贴它。
import pandas as pdfrom tableau_api_lib import TableauServerConnection
from tableau_api_lib.utils import querying, flatten_dict_column, flatten_dict_list_columntableau_server_config = {
'my_env': {
'server': 'https://YourTableauServer.com',
'api_version': '<YOUR_API_VERSION>',
'username': '<YOUR_USERNAME>',
'password': '<YOUR_PASSWORD>',
'site_name': '<YOUR_SITE_NAME>',
'site_url': '<YOUR_SITE_CONTENT_URL>'
}
}conn = TableauServerConnection(tableau_server_config, env='my_env')
conn.sign_in()
有趣的事实:你也可以使用个人访问令牌,假设你在 Tableau Server 2019.4 或更新版本上。如果你对访问令牌很感兴趣,可以看看我的文章,了解如何使用它们的细节。
## 步骤 3(可选):了解相关的 API 端点
本教程深入探讨了两个端点:
1. 【查询工作簿权限】的[端点](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_permissions.htm#query_workbook_permissions)
2. 【站点查询工作簿】[端点](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_workbooks_and_views.htm#query_workbooks_for_site)
3. 【查询组】的[端点](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_users_and_groups.htm#query_groups)
4. [端点](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_users_and_groups.htm#get_users_in_group)中的“获取用户组”
5. [端点](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_users_and_groups.htm#get_users_on_site)
上面的链接将带您进入 Tableau 的 REST API 文档,该文档提供了一些技术指导和对这些端点如何工作的解释。
对于这些端点中的每一个,tableau-api-lib 库都有一个以`snake_case`格式镜像名称的方法,这样“查询工作簿权限”就变成了“query_workbook_permissions()”。
## 步骤 4:创建一个占位符熊猫数据框架
这一步创建了一个空的数据帧,我们将向其追加数据:
workbook_permissions_df = pd.DataFrame()
## 步骤 5:获得一个描述所有工作簿的熊猫数据框架
当我们收到描述我们工作簿的数据时,一些关于相关项目的信息包含在名为`project`的嵌套列中。

“项目”列本身是一个字典,填充了键/值对。
我们使用`flatten_dict_column`函数来删除该列中的有用信息。
workbooks_df = querying.get_workbooks_dataframe(conn)[["project", "name", "id"]]
workbooks_df = flatten_dict_column(df=workbooks_df, keys=["id", "name"], col_name="project")
workbooks_df.rename(columns={"id": "workbook_id", "name": "workbook_name"}, inplace=True)
workbooks_df

“项目”栏已被展平,将“项目标识”和“项目名称”分开。
## 第六步:获得描述所有群体的熊猫数据框架
当我们查询工作簿的权限时,我们将收到与任何给定组相关的权限规则信息。这是由组的本地唯一标识符(LUID)唯一标识的,我们将需要关于每个组的一些附加信息。
groups_df = querying.get_groups_dataframe(conn)

现在,我们可以使用用户友好的组名了。
## 步骤 7:迭代我们的工作簿和查询权限
for _, workbook in workbooks_df.iterrows():
print(f"fetching group permissions for workbook '{workbook.workbook_name}...'")
response = conn.query_workbook_permissions(workbook_id=workbook.workbook_id)
permissions_df = pd.DataFrame(response.json()["permissions"]["granteeCapabilities"])
if "group" not in permissions_df.columns:
continue
permissions_df = permissions_df[permissions_df["group"].notnull()]
permissions_df = flatten_dict_column(df=permissions_df, keys=["id"], col_name="group")
permissions_df = flatten_dict_list_column(df=permissions_df, col_name="capabilities")
permissions_df = flatten_dict_column(df=permissions_df, keys=["name", "mode"], col_name="capability")
permissions_df["workbook_id"] = workbook.workbook_id
permissions_df["workbook_name"] = workbook.workbook_name
permissions_df["project_name"] = workbook.project_name
permissions_df["project_id"] = workbook.project_id
permissions_df["join_col"] = 1
all_group_users_df = pd.DataFrame()
group_ids = permissions_df.group_id.unique()
for index, group_id in enumerate(group_ids):
print(f"joining users from group '{group_id}'")
group_users_df = querying.get_group_users_dataframe(conn=conn, group_id=group_id)
group_users_df.rename(columns={"name": "user_name", "id": "user_id"}, inplace=True)
group_users_df["join_col"] = index
group_df = groups_df[groups_df["id"] == group_id].copy()[["id", "name"]]
group_df = group_df.add_prefix("group_")
group_df["join_col"] = index
all_group_users_df = pd.concat([all_group_users_df, pd.merge(group_users_df, group_df, how="inner", on="join_col")])
workbook_permissions_df = pd.concat(
[workbook_permissions_df, permissions_df.merge(all_group_users_df, on=["group_id"])]
)
上面的逻辑遍历每个工作簿。对于每个工作簿,获取并加入相关的组及其各自的用户。结果是,我们获得了一个数据框架,描述了为每个工作簿分配的所有组级权限。
这可能需要一些时间,取决于您的 Tableau 环境的大小。如果工作簿的数量非常大,您可能需要考虑将此逻辑隔离到工作簿的一个子集(按项目过滤等)。
## 步骤 8(可选):获得一个描述所有用户的熊猫数据帧
如果您定义了任何特定于用户的权限,那么您还会希望获取每个工作簿的权限,其中权限是特定于用户而不是组的。提醒一下,这通常是一种不好的做法。最好将用户分配到组中,然后将权限分配给这些组。
users_df = querying.get_users_dataframe(conn)
users_df.rename(columns={"id": "user_id", "name": "user_name"}, inplace=True)
## 步骤 9(可选):遍历我们的工作簿和查询权限
与查询组级权限非常相似,我们将遍历工作簿并查询用户级权限。
for _, workbook in workbooks_df.iterrows():
print(f"fetching user permissions for workbook '{workbook.workbook_name}...'")
response = conn.query_workbook_permissions(workbook_id=workbook.workbook_id)
permissions_df = pd.DataFrame(response.json()["permissions"]["granteeCapabilities"])
if "user" not in permissions_df.columns:
continue
permissions_df = permissions_df[permissions_df["user"].notnull()]
permissions_df = flatten_dict_column(df=permissions_df, keys=["id"], col_name="user")
permissions_df = flatten_dict_list_column(df=permissions_df, col_name="capabilities")
permissions_df = flatten_dict_column(df=permissions_df, keys=["name", "mode"], col_name="capability")
permissions_df["workbook_id"] = workbook.workbook_id
permissions_df["workbook_name"] = workbook.workbook_name
permissions_df["project_name"] = workbook.project_name
permissions_df["project_id"] = workbook.project_id
permissions_df["join_col"] = 1
workbook_permissions_df = pd.concat(
[workbook_permissions_df, permissions_df.merge(users_df, on=["user_id"])]
)
当我们查询组级别的权限时,我们最终得到了用户级别的数据,但是请注意不同之处:这些是属于特定组的用户。上面的查询是查询不与任何给定组相关联的用户权限(同样,用户级权限规则通常是一种不好的做法)。
## 步骤 10:将最终权限数据输出到 CSV
现在我们有了一个描述所有权限的 Pandas 数据框架,我们可以使用它了。对于本教程,我们希望将数据输出到一个文件,然后使用 Tableau 连接到它。在您自己的使用中,考虑将数据写入数据库或以其他方式存储它,但这对于您的工作流是有意义的。
workbook_permissions_df.to_csv("workbook_permissions.csv", sep=",", header=True, index=False)
## 步骤 11:连接 Tableau 桌面中的数据并进行交互
本教程假设熟悉 Tableau 桌面。如果您不知道如何在 Tableau 中连接到 CSV 文件,有许多免费文章、YouTube 视频和 Tableau 论坛可以提供帮助。
下面是一个与最终数据交互的例子。这是一个非常基本的 Tableau 工作表,但是请注意,我可以很容易地过滤到特定的用户、组、站点角色等。

我们现在可以利用权限数据了。
在上面的屏幕截图中,我们可以很容易地识别不同工作簿的组级和用户级权限。在这种情况下,`Null`组名表示权限不与任何组相关联,这意味着它们是用户级权限。
## 包装它
希望本教程有助于展示一种可行的方法,提取您需要了解的关于 Tableau 环境中内容权限的信息。
最后一点:我强烈建议您组织您的权限模型,始终在项目级别锁定权限,并且只在组级别而不是用户级别分配权限。
## 合并代码
使用 GitHub gist 作为模板,将本教程的内容集成到您自己的工作流程中。
# 使用 query()方法查询熊猫数据帧
> 原文:<https://towardsdatascience.com/querying-pandas-dataframes-using-the-query-method-c1b887b5c461>
## 了解如何使用类似 SQL 的语法查询您的熊猫数据帧

约翰·施诺布里奇在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上拍摄的照片
大多数 Pandas 用户都熟悉从 Pandas 数据帧中检索行和列的`iloc[]`和`loc[]`索引器方法。然而,随着检索数据的规则变得越来越复杂,这些方法变得非常笨拙。既然大多数开发人员都熟悉 SQL,为什么不使用类似于 SQL 的东西来查询您的数据框架呢?
事实证明,你可以用`query()`方法做到这一点。因此,在本文中,我将展示一些例子,说明如何使用`query()`方法在数据帧上执行查询。
# 获取数据
在这篇文章中,我将使用泰坦尼克号数据集。
> **数据来源**:本文数据来源于[*https://www.kaggle.com/datasets/tedllh/titanic-train*](https://www.kaggle.com/datasets/tedllh/titanic-train)*。*
>
> ***许可—*** *数据库内容许可(DbCL)v 1.0*[*https://opendatacommons.org/licenses/dbcl/1-0/*](https://opendatacommons.org/licenses/dbcl/1-0/)
让我们将 CSV 文件加载到熊猫数据帧中:
import pandas as pddf = pd.read_csv('titanic_train.csv')
df
数据帧有 891 行和 12 列:

作者图片
# 使用 query()方法
让我们找到所有从南安普顿登船的乘客。使用*方括号索引*,代码如下所示:
df[df['Embarked'] == 'S']
然而,如果你使用`query()`方法,它看起来更整洁:
df.query('Embarked == "S"')
> 如果您想与 SQL 进行比较,`query()`方法中的表达式就像 SQL 中的 WHERE 语句。
结果是所有乘客从*南安普敦*出发的数据帧:

作者图片
`query()`方法接受字符串形式的查询。因此,如果您正在查询一个字符串列,您需要确保您的字符串被正确地括起来。下图说明了这一点:

作者图片
通常,您可能希望将变量值传递到查询字符串中。您可以使用`@`字符来完成:
embarked = 'S'
df.query('Embarked == @embarked')
或者,您也可以使用 f 弦,如下所示:
df.query(f'Embarked == "{embarked}"')
> 就个人而言,我认为使用`@`字符比 f 弦方式更简单、更优雅。
如果列名中包含空格,您可以使用反斜杠(````)将列名换行:
df.query('Embarked On == @embarked')
# 就地执行查询
当您使用`query()`方法执行查询时,该方法将结果作为数据帧返回,而原始数据帧保持不变。如果您想更新数据帧,使用`inplace`参数,如下所示:
df.query('Embarked == "S"', inplace=True)
当`inplace`设置为`True`时,`query()`方法将不返回值。相反,原始数据帧被修改。
# 指定多个条件
您可以在查询中指定多个条件。例如,说我想得到从*南安普敦* ('S ')或*瑟堡* ('C ')登船的所有乘客。使用方括号索引,这种语法很快变得非常笨拙:
df[(df['Embarked'] == 'S') | (df['Embarked'] == 'C')]
注意,在您的条件中,您需要引用您的 dataframe ( `df`)两次。使用`query()`方法,这是在公园散步:
df.query('Embarked in ("S","C")')
上述语句产生以下输出:

作者图片
如果您想查找所有从*南安普敦* ('S ')或*瑟堡* ('C ')出发的**而非**的乘客,您可以在 Pandas 中使用否定运算符(~):
df[~((df['Embarked'] == 'S') | (df['Embarked'] == 'C'))]
使用`query()`方法,您只需要使用`not`操作符:
df.query('Embarked not in ("S","C")')
以下输出显示了从 Queenstown ('Q ')出发的乘客以及缺少值的乘客:

作者图片
谈到缺失值,如何查询缺失值?当应用于列名时,您可以使用`isnull()`方法查找缺失值:
df.query('Embarked.isnull()')
现在将显示在**开始的**列中缺少值的行:

作者图片
实际上,可以直接在列名上调用各种 Series 方法。以下是一些例子:
df.query('Name.str.len() < 20') # find passengers whose name is
# less than 20 charactersdf.query(f'Ticket.str.startswith("A")') # find all passengers whose
# ticket starts with A
# 比较数字列
您还可以轻松地比较数字列:
df.query('Fare > 50')
以下输出显示了票价大于 50 的所有行:

作者图片
# 比较多列
您还可以使用`and`、`or`和`not`操作符来比较多个列。以下语句检索所有费用大于 50 且年龄大于 30 的行:
df.query('Fare > 50 and Age > 30')

作者图片
# 查询索引
通常,当您想要基于索引值检索行时,您可以使用`loc[]`索引器,如下所示:
df.loc[[1],:] # get the row whose index is 1; return as a dataframe
使用`query()`方法,事情更加直观:
df.query('index==1')

作者图片
如果希望检索索引值小于 5 的所有行:
df.query('index<5')
上面的语句显示了结果:

作者图片
您也可以指定索引值的范围:
df.query('6 <= index < 20')

作者图片
# 比较多列
您还可以比较列之间的值。例如,以下语句检索其 **Parch** 值大于 **SibSp** 值的所有行:
df.query('Parch > SibSp')

作者图片
<https://weimenglee.medium.com/membership>
# 相关文章
<https://weimenglee.medium.com/performing-exploratory-data-analysis-eda-using-pivot-tables-in-pandas-3f402f95681b> </working-with-multi-index-pandas-dataframes-f64d2e2c3e02> </working-with-multi-index-pandas-dataframes-f64d2e2c3e02>
# 摘要
从上面的例子可以看出,`query()`方法使得搜索行的语法更加自然,更像英语。为了确保您真正理解 query 的工作原理,这里有一些挑战(在尝试这些问题之前不要看下面的答案):
* 显示票价高于平均票价的所有行
* 查找姓名中包含称呼博士的所有乘客
* 找出所有付了最高票价的乘客
* 找到所有有机舱号的乘客
* 找到所有机票号码以 STON 开头的乘客
## 答案
df.query(f'Fare < {df["Fare"].mean()}')
df.query('Name.str.contains(" Dr. ")')
df.query(f'Fare == {df["Fare"].max()}')
df.query('~Cabin.isnull()')
df.query('Ticket.str.contains("^STON")')
# 基于搜索和排序的教科书问答
> 原文:<https://towardsdatascience.com/question-answering-on-textbooks-by-searching-and-ranking-609f8b007aa6>
## 用数据做很酷的事情

照片由 nadi borodina 在 Unsplash 上拍摄
# 介绍
问答是自然语言处理的一个流行应用。在大数据集上训练的 Transformer 模型极大地改善了问答的最新结果。
问题回答任务可以用许多方式来表述。最常见的应用是在小范围内回答提取的问题。班数据集是一个流行的数据集,其中给定一段话和一个问题,模型选择代表答案的单词。这在下面的图 1 中示出。
然而,大多数问题回答的实际应用涉及非常长的文本,如完整的网站或数据库中的许多文档。使用 Google Home/Alexa 之类的语音助手回答问题需要在网络上搜索大量文档以获得正确答案。

小队数据集。图片来自[小队论文](https://arxiv.org/pdf/1606.05250.pdf)。
在这篇博客中,我们使用 [Haystack](https://github.com/deepset-ai/haystack) 构建了一个搜索和问答应用程序。这个应用程序搜索了几本与编程相关的 O'Reilly 开源书籍。代码在 Github [这里](https://github.com/priya-dwivedi/Deep-Learning/blob/master/Question_Answering_on_TextBooks_using_Search/Question_Answering_TextBooks.ipynb)公开。你也可以在这里使用[的 Colab 笔记本](https://colab.research.google.com/drive/1lxBoth4JPvsZewr1zpqeOhW0W9Hxms8q#scrollTo=CRFyiVsNSZYS)来测试这个模型。
这里的完整帖子也可以在我们的网站[这里](https://deeplearninganalytics.org/question-answering-on-textbooks-by-searching-and-ranking/)找到。
# 搜索和排名
那么,我们如何从庞大的数据库中检索答案呢?
我们将回答问题的过程分为两步:
1. 搜索和排序:从我们的数据库中检索可能有答案的段落
2. 阅读理解:在检索到的文章中寻找答案
搜索和排名过程通常包括将数据编入数据库,如弹性搜索数据库或 T2 FAISS 数据库。这些数据库已经实现了在数百万条记录中进行快速搜索的算法。可以使用 TFIDF 或 BM25 使用查询中的单词来完成搜索,或者可以通过嵌入文本来考虑文本的语义来完成搜索,或者也可以使用这些的组合。在我们的代码中,我们使用了来自[干草堆](https://github.com/deepset-ai/haystack)的基于 TFIDF 的检索器。整个数据库被上传到 ElasticSearch 数据库,搜索是使用 TFIDF 检索器完成的,该检索器根据分数对结果进行排序。要了解不同类型的搜索,请查看 Haystack 的博客。
来自检索器的前 k 个段落然后被发送到问题回答模型以获得可能的答案。在这篇博客中,我们尝试了两种模式——1。在班数据集和 2 上训练的 BERT 模型。在班数据集上训练的罗伯塔模型。《干草堆中的读者》帮助加载这些模型,并通过对从搜索中检索到的段落运行这些模型来获得前 K 个答案。
# 构建我们的问答应用程序
用于开发该问答模型的训练数据集是编程开源 O ' Reilly book-[Be 操作系统编程](https://www.oreilly.com/openbook/beosprog/book/)。书中的个别章节以 pdf 文件的形式从 [O'Reilly 网站](https://www.oreilly.com/openbook/)下载。这本书是在知识共享许可下提供的。我们通过合并章节创建了一个合并的 PDF,这些章节可以在 google drive [这里](https://drive.google.com/drive/u/2/folders/1-IAona91wwNKA0_Wm0ux5yCtH15m8Yv2)找到。如果使用 Colab,将此 PDF 上传到您的环境中。
我们使用 HayStack PDF 转换器将 PDF 读入一个文本文件
converter = PDFToTextConverter(remove_numeric_tables=True, valid_languages=["en"])doc_pdf = converter.convert(file_path="/content/converter_merged.pdf", meta=None)[0]
然后,我们使用 Haystack 中的预处理器类将文档分割成许多更小的文档。
preprocessor = PreProcessor(clean_empty_lines=True,clean_whitespace=True,clean_header_footer=False,split_by="word",split_length=100,split_respect_sentence_boundary=True)dict1 = preprocessor.process([doc_pdf])
每份文件约 100 字。预处理程序创建 1200 个文档。
接下来,我们将这些文档索引到一个 ElasticSearch 数据库,并初始化一个 TFIDF 检索器。
retriever = TfidfRetriever(document_store=document_store)document_store.write_documents(dict1)
检索器将对 ElasticSearch 数据库中的文档进行搜索,并对结果进行排序。
接下来,我们初始化问答管道。我们可以使用 FARM 或 Transformer 阅读器在 HuggingFace hub 上加载任何问答模型。
reader = TransformersReader(model_name_or_path="deepset/bert-large-uncased-whole-word-masking-squad2", tokenizer="bert-base-uncased", use_gpu=-1)
最后,我们将检索器和排序器合并到一个管道中,一起运行它们。
from haystack.pipelines import ExtractiveQAPipelinepipe = ExtractiveQAPipeline(reader, retriever)
仅此而已!我们现在准备测试我们的管道。
Giphy 免版税。链接:【https://giphy.com/gifs/cheer-cheering-11sBLVxNs7v6WA
# 测试管道
为了测试管道,我们通过它运行我们的问题,为来自检索器和阅读器的 top_k 结果设置我们的首选项。
prediction = pipe.run(query="What is Virtual Memory?", params={"Retriever": {"top_k": 10}, "Reader": {"top_k": 3}}
当我们要求管道解释虚拟内存时,最重要的结果是非常相关的。

问题 1 管道测试
接下来,我们询问 OpenGL 工具包,得到的答案正确地解释了这个库允许程序员向模型添加 3D 功能。
prediction = pipe.run(query="What is OpenGL kit?", params={"Retriever": {"top_k": 10}, "Reader": {"top_k": 3}}

问题 2 管道测试
我们已经在许多问题上测试了管道,它表现得相当好。阅读源 pdf 中的新主题并提出相关问题很有趣。通常,我发现我阅读的段落在前 3 个结果中。
我的实验表明,伯特和罗伯塔的结果基本相似,罗伯塔略胜一筹。
请使用 [Colab](https://colab.research.google.com/drive/1lxBoth4JPvsZewr1zpqeOhW0W9Hxms8q?usp=sharing) 或 [Github](https://github.com/priya-dwivedi/Deep-Learning/blob/master/Question_Answering_on_TextBooks_using_Search/Question_Answering_TextBooks.ipynb) 上的代码通过模型运行您自己的科学问题。
# 结论
这个博客展示了问答如何应用于许多实际应用,包括长文本,如 pdf 格式的数据库,或者包含许多文章的网站。我们在这里使用 Haystack 构建一个相对简单的应用程序。Haystack 有许多杠杆可以调整和微调,以进一步提高性能。我希望你下载代码,运行你自己的实验。请在下面的评论中分享你的经历。
在[深度学习分析](https://deeplearninganalytics.org/),我们非常热衷于使用机器学习来解决现实世界的问题。我们已经帮助许多企业部署了创新的基于人工智能的解决方案。如果您看到合作的机会,请通过我们的网站[这里](https://deeplearninganalytics.org/contact-us/)联系我们。
# 参考
* [罗伯塔模型](https://ai.facebook.com/blog/roberta-an-optimized-method-for-pretraining-self-supervised-nlp-systems/)
* [草堆文档](https://haystack.deepset.ai/overview/intro)
# 问题旋转——处理无法完成的人工智能任务
> 原文:<https://towardsdatascience.com/question-pivoting-handling-unachievable-ai-tasks-6e715cb12026>
## 无法训练监督模型时要考虑的 3 个支点

照片由[像素](https://www.pexels.com/photo/jumping-cute-playing-animals-4602/?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels)的[地形](https://www.pexels.com/@gratisography?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels)拍摄
T 他的是一个普通的剧本;数据科学团队被要求生成一个应该非常简单的洞察,但由于缺乏数据等原因,这是不可行的。一个常见的例子是请求预测客户的状态(对产品满意吗?愿意推荐 VS 策划[流失](https://blog.hubspot.com/service/what-is-customer-churn)等..)却发现不存在任何相关的标记数据(如客户反馈记录或客户流失请求)。下一步做什么?最简单的解决方案是推迟请求,直到收集到足够的数据。但是通常我们会建议一个折中的解决方案。这种开箱即用的选项将是采用 [**问题中枢**](https://knowledge.wharton.upenn.edu/article/pivot-entrepreneurship/)**——通过回答不同的问题来处理完全相同的需求。正如我们将看到的,有三个主要可能的支点**可供选择——直接**、**间接**和**手动直接**。每一个都有自己的优点和缺点,这将由你来决定哪一个最适合你的需要。******
# **直接问题支点**
最直观的选择。鉴于标记数据稀缺,尝试在不依赖标记数据的情况下满足需求;使用[非监督](https://en.wikipedia.org/wiki/Unsupervised_learning)方法,而不是我们首先想到的[监督](https://en.wikipedia.org/wiki/Supervised_learning)分类器(需要标记数据进行训练)。一种常见的无监督算法选择是[聚类](https://en.wikipedia.org/wiki/Cluster_analysis);使用现有的(未标记的)指标生成客户聚类,并根据生成的聚类平面得出结论。唯一的问题是,给定这样的聚类,如何识别其中满意度较低的客户?一种选择是通过使用 [**异常检测**](https://en.wikipedia.org/wiki/Anomaly_detection);搜索簇外/奇怪的簇,假设它指示值得观察的客户。另一种选择是**部分贴标签**;给定几个已知对产品不满意的客户,查看他们的集群邻居,假设低距离表示类似状态(也不满意)。上述方法的主要问题是假设**生成的集群结构与客户满意度**有关。但是聚类平面上的低距离取决于我们用来生成聚类的指标,因此这可能是由于其他原因,例如类似的使用模式。更普遍的问题是**如何衡量我们的 pivot 成功**?虽然有多种方法可以评估集群的好坏(如[集群内平方和](https://en.wikipedia.org/wiki/K-means_clustering)),但没有人能保证现成的集群会通过将具有相似满意度的客户放在一起而直接满足我们的需求。这是直接枢纽方法的固有问题,即非监督方法不能直接满足监督模型的需求(否则,我们可以省去监督方法对标记数据的需求,而只是通过使用非监督方法来依赖数据)。另一个问题是,在有监督的情况下衡量无监督任务的成功可能会很棘手。一种可能的解决方案是采用其他支点——间接支点;使用监督方法,但将问题重新表述为存在标记数据的问题。
# **间接疑问句枢纽**
假设我们没有标记数据来为我们的问题(客户的状态是什么)训练分类模型,并且假设直接透视(使用非监督方法)效果不好,直观的下一个透视将是相反的方向;继续使用监督方法,但要找到其他有足够标签数据的相关问题来训练它。以我们的例子为例,大多数公司拥有的一个常见的客户数据是操作日志;就像在使用外部任务管理系统时,比如吉拉的 T1 或 T2 的 Zendesk 的 T3,我们可以导出他们的任务执行日志,使我们能够找到我们想要的数据。使用它,我们可以将“我们的客户状态是什么”问题替换为“客户的请求会按时得到解决吗”这样的问题。这样一个问题支点是有意义的,假设客户的哪些请求被及时解决会更满意。现在,我们可以生成一个监督分类模型,并轻松测试它以验证成功。问题是相关性水平;假设我们预测客户请求不会按时解决,并且假设使用我们的帮助,支持团队将设法按时处理它,这与客户状态有什么关系?此外,一个满意的客户可能根本没有任何支持请求。固有的问题是假设可以从一个分类问题到另一个分类问题进行明显的简化(正如我们刚刚看到的,情况并不总是如此)。这就是为什么最终许多案例会以中间支点方法结束——保留目标分类问题(在我们的示例中为“客户状态是什么”),但找到一种中间方法来为其收集标记数据;手动直接旋转。
# **手动直接提问枢纽**
到目前为止,我们一直试图改变我们使用的方法(无人监督而不是有人监督)和我们回答的问题(其他有人监督的任务而不是我们被要求的任务)。两者都缺乏与感兴趣的原始任务的直接联系。直观的最终支点将是尝试回答原始需求,同时找到一种方法来生成中间标记数据以对其进行训练(直到收集到足够多的所需标记数据)。在我们的示例中,我们可以通过查看我们拥有的部分数据,使用启发式方法来估计客户的状态(所需的标签);衡量影响 it 的关键指标,如使用水平、停机次数或整体服务体验。生成定期客户满意度得分。挑战在于使其成为一个可靠的衡量标准。例如,停机时间与客户满意度有什么关系?对于面向应用程序的客户来说,这可能非常重要,但对于监控等后台应用程序来说,停机时间就不那么引人注目了(客户通常只在设置阶段进入应用程序,很可能根本不会意识到停机)。问题是我们试图生成一个固执己见的 KPI(客户满意度得分)来反映一个绝对的 KPI(客户状态)。虽然乍看起来微不足道,但在许多情况下,它最终会变得非常复杂(否则我们可以使用生成的试探法,而不是所需的分类模型)。依赖过于天真的启发式方法的风险可能会很高。那么选择什么支点呢?。
# **没有神奇的答案(除了透明)**
底线结论是,选择正确的支点并不是一个简单的决定。这里没有神奇的解决办法。从我的经验来看,每个支点选项**总是有好有坏**,这取决于我们所拥有的独特条件,以便决定我们应该接受哪一个。同时值得注意的是,这些案例的共同点是需要**的透明度**;尽早向客户清楚地反映我们的状态(有哪些选项、成本和价值),使我们能够更好地进行规划,然后选择最适合我们的解决方案。在某些情况下,结论是根本不要转向(而是[加速标记过程](/how-to-utilise-a-manual-labelling-workforce-b30db3ab1e8e),或者[优先处理其他任务](/pivoting-ml-apps-to-success-9bfcb032e0c6),因为数据丢失)。让相关各方充分理解状态是正确选择如何(中间)调整手头问题所需的秘方。使我们能够专注于我们能回答的问题。
# 帮助您练习数据科学面试的问题
> 原文:<https://towardsdatascience.com/questions-to-help-you-practice-for-your-data-science-interview-c619ab6db70b>
## *概述数据科学面试中的问题类型,以及使用我们的平台进行实践*

作者在 [Canva](https://canva.com/) 上创建的图片
2012 年,《哈佛商业评论》称数据科学家是 21 世纪最性感的工作,数据科学家职位空缺数量的增长趋势似乎证实了这一说法。随着数据的增长,当今大多数公司都在大量利用数据科学来做出明智的业务决策,并找出业务增长领域。数据科学家在其中发挥着关键作用。作为一名数据科学家,你需要具备很多技能;编码技能、统计分析、概率、解决问题、技术知识、商业敏锐度等。对于面试来说,可以从很多方面来评判候选人。
由于数据科学家角色的广泛性质,它对候选人来说变得相当压倒性。大多数候选人发现很难通过招聘程序。在本文中,我们将看到在数据科学面试中可以提出的问题类型。[数据科学面试问题](https://www.stratascratch.com/blog/40-data-science-interview-questions-from-top-companies/?utm_source=blog&utm_medium=click&utm_campaign=medium)可以分为两大类,也可以分为 8 个小类。
两个主要类别:
* 编码问题
* 非编码问题
非编码问题可以进一步分为不同的类别:
* 系统设计
* 可能性
* 统计数字
* 建模
* 技术的
* 产品
在继续提问之前,我们先来看看数据科学家在公司中的角色是什么:
# 数据科学家是做什么的?
数据科学家是组织中的分析专家,帮助企业做出明智的决策,并在公司中实现创新。数据科学家是公司中的关键人物,他们组织和分析大量结构化和非结构化数据,并从中获得见解。这些人是分析、机器学习、问题解决技能和解释洞察力以将其转化为可操作的商业决策的专家。他们设计数据建模流程,创建高级 ML 算法和预测模型,以提取业务所需的数据。
为了收集和分析数据,数据科学专业人员具有以下职责:
1. 从各种来源获取数据
2. 数据清理和处理
3. 根据业务需求组合相关的数据源
4. 存储数据
5. 探索性数据分析
6. 定义手头的问题并进行规划
7. 选择预测模型和算法
8. 测量和改进结果
9. 向利益相关者传达结果以采取行动
10. 重复该过程以解决另一个问题
下面是终极指南" [*数据科学家是做什么的?*](https://www.stratascratch.com/blog/what-does-a-data-scientist-do/?utm_source=blog&utm_medium=click&utm_campaign=medium) “这将引导你了解数据科学工作的各个方面。
# 编码
在分析了来自 80 家不同公司的数据科学面试问题后,编码问题似乎是最主要的问题。面试官会用这些问题来测试应聘者的编程能力。语言可以是任何东西;SQL、Python、R 或该特定工作所需的任何其他编程语言。编码是数据科学家最重要的[技能之一。](https://www.stratascratch.com/blog/what-skills-do-you-need-as-a-data-scientist/?utm_source=blog&utm_medium=click&utm_campaign=medium)
FAANG 公司非常关注编码问题。在 Glassdoor 的所有数据科学问题中,的确如此。,接近 50%的问题与编码相关。编码问题可以定义为需要编程语言或伪代码来解决特定问题的问题。编码题旨在了解考生解决问题的能力,了解其思维过程和对编程语言的熟悉程度,检查其创造力等。在[数据科学面试](https://www.stratascratch.com/blog/data-science-interview-guide-questions-from-80-different-companies/?utm_source=blog&utm_medium=click&utm_campaign=medium)中,编码问题的重要性怎么强调都不为过,因为绝大多数数据科学职位都定期涉及编码。
通常这些数据科学公司中的大多数会测试你两种主要的语言;Python 和 SQL。今天,我们将学习一些在面试中被问到的编码问题,并进行动手练习。
# 结构化查询语言

作者在 [Canva](https://canva.com/) 上创建的图像
## 数据科学面试问题#1:每月百分比差异
> 给定一个按日期排列的采购表,计算收入的逐月百分比变化。输出应该包括年月日(YYYY-MM)和百分比变化,四舍五入到小数点后第二位,从年初到年底排序。百分比变化列将从第二个月开始填充,并且可以计算为((本月收入-上月收入)/上月收入)*100。
在亚马逊的这个问题中,我们需要计算收入的月环比百分比变化。输出应包含 YYYY-MM 格式的数据,百分比变化应四舍五入到小数点后第二位,并从年初到年底排序。
提供了一个表: *sf_transactions*
**表:** sf_transactions

表格中有四个字段;ID、创建时间、价值和购买 ID。
**解决方法**
根据问题,第一步是计算月级别的收入,并将数据格式更改为 YYYY-MM。因此,我们将使用 DATE_FORMAT 函数来更改 created_at 字段的格式,然后使用 SUM()来计算总收入。
SELECT DATE_FORMAT(created_at,'%Y-%m') AS ym,
SUM(value) AS revenue
FROM sf_transactions
GROUP BY 1
ORDER BY 1
在代码的上面部分,我们对每个月的“值”求和,以计算该月的总收入,并按照问题中提到的那样更改数据格式。因为我们需要日期以升序排列,所以我们在最后使用 ORDER BY。
现在我们有了按升序排列的每个月的收入,让我们使用 LAG()函数来获得前一个月的收入,以便进行逐月计算。
SELECT DATE_FORMAT(created_at,'%Y-%m') AS ym,
SUM(value) AS revenue,
LAG(SUM(value)) OVER() AS prev_revenue
FROM sf_transactions
GROUP BY 1
ORDER BY 1
使用 lag 函数,您可以获得前一个月的收入。现在,要计算每月的百分比变化,我们可以使用公式:((当月收入-上月收入)/(上月收入))* 100,然后对结果使用 ROUND()函数,以获得最多 2 个小数点的百分比差异。
**最终查询**
SELECT DATE_FORMAT(created_at,'%Y-%m') AS ym,
ROUND((SUM(value) - LAG(SUM(value)) OVER ())
/ LAG(SUM(value)) OVER ()
* 100, 2) AS revenue_diff_pct
FROM sf_transactions
GROUP BY ym
ORDER BY ym
**输出**

## 数据科学面试问题#2:高级与免费增值
> 按日期查找付费和非付费用户的下载总数。只包括非付费用户比付费用户下载更多的记录。输出应首先按最早日期排序,并包含 3 列日期,非付费下载,付费下载。
在这个问题中,有三个表,ms_user_dimension,ms_acc_dimension,ms_download_facts。
**表:** ms_user_dimension

**表格:** ms_acc_dimension

**表格:**ms _ 下载 _ 事实

**解决方法**
我们有三张桌子。第一步是将用户维度表与帐户表连接起来,以确定哪些用户是付费客户,哪些用户是非付费客户。让我们用 CTEs 来解决这个问题。
WITH user_account_mapping as (SELECT u.user_id,
u.acc_id,
a.paying_customer
FROM ms_user_dimension u
JOIN ms_acc_dimension a
ON u.acc_id = a.acc_id)
在上面的步骤中,我们从用户维度中选择用户 ID 和帐户 ID,将该表与帐户 ID 上的帐户维度连接起来,并从帐户维度中提取付款客户列。该表的输出将给出映射到帐户 id 的用户 id,并带有付费/不付费标志,如下所示:

现在,下一步是将这个表与下载事实表连接起来,以获得每个用户的下载次数。让我们看看下面代码。
final_table as
(
SELECT d.date,
d.user_id,
ua.paying_customer,
SUM(d.downloads) AS downloads
FROM ms_download_facts d
JOIN user_account_mapping ua
ON d.user_id = ua.user_id
GROUP BY 1,2,3
)
现在,上表的输出将给出每个付费和非付费客户在所有日期的下载次数,如下所示。我们将上面的查询命名为 final_table,让我们用它来计算剩余部分。

从预期的输出中,我们需要一些非付费下载和付费下载作为单独的列。因此,我们将在 SUM()中使用 CASE WHEN 来实现这一点。下面是它的代码。
SELECT date,
SUM(CASE WHEN paying_customer = 'no' THEN downloads END) non_paying,
sum(CASE WHEN paying_customer = 'yes' THEN downloads END) paying
FROM final_table
GROUP BY 1
现在,上面的查询将给出日期、非付费用户的下载量和付费用户的下载量。对于预期的输出,我们需要按日期对数据进行排序,并只显示那些非付费下载量大于付费下载量的行。下面是它使用 WHERE 条件过滤数据的代码。
SELECT *
FROM (
SELECT date,
SUM(CASE WHEN paying_customer = 'no' THEN downloads END) non_paying,
SUM(CASE WHEN paying_customer = 'yes' THEN downloads END) paying
FROM final_table
GROUP BY 1
)b
WHERE non_paying > paying
ORDER BY date
**最终查询:**
WITH user_account_mapping as (SELECT u.user_id,
u.acc_id,
a.paying_customer
FROM ms_user_dimension u
JOIN ms_acc_dimension a
ON u.acc_id = a.acc_id),final_table AS (
SELECT d.date,
d.user_id,
ua.paying_customer,
SUM(d.downloads) AS downloads
FROM ms_download_facts d
JOIN user_account_mapping ua
ON d.user_id = ua.user_id
GROUP BY 1,2,3
)SELECT *
FROM (
SELECT date,
SUM(CASE WHEN paying_customer = 'no' THEN downloads END) non_paying,
SUM(CASE WHEN paying_customer = 'yes' THEN downloads END) paying
FROM final_table
GROUP BY 1
)b
WHERE non_paying > paying
ORDER BY date
**输出**

## 数据科学面试问题#3:营销活动的成功
> 你有一个用户应用内购买的表格。首次进行应用内购买的用户被置于营销活动中,他们会看到更多应用内购买的行动号召。找出由于营销活动的成功而进行额外应用内购买的用户数量。
> 营销活动在首次应用内购买后一天才开始,所以在第一天只购买一次或多次的用户不算在内,我们也不计算那些只购买第一天购买的产品的用户。
在这个问题中,提供了一个表格。这张表是关于营销活动的。一些背景,第一次购买的用户被放置在这个表中,在那里他们看到更多购买的行动要求。我们需要找到由于营销活动的成功而进行额外购买的用户数量。
**表:**营销 _ 活动

**解决方法**
第一步是找出每个用户对数据集中任何商品的第一次订购日期。为此,我们将使用 MIN()函数和 PARTITION BY()子句。找到下面代码的第一部分:
SELECT
user_id,
-- Date when user first orders any item
MIN(created_at) OVER(PARTITION BY user_id ) AS m1
FROM marketing_campaign
我们还需要找到每个用户对每个产品的首次订购日期。为了进行计算,我们将使用与上面类似的代码,但是使用 partition 子句中的 product ID 字段以及用户 ID。这将为我们提供每个用户的每个产品的第一个订单日期。
SELECT
user_id,
-- Date when user first orders any item
MIN(created_at) OVER(PARTITION BY user_id ) AS m1,
-- Date when each item was first ordered by user
MIN(created_at) OVER(PARTITION BY user_id,product_id ) AS m2
FROM marketing_campaign
现在是问题的最后一部分,我们需要找到由于活动成功而进行额外购买的用户数量,这意味着我们需要计算不同用户 id 的数量,其中每个用户的第一个订单日期小于额外产品的第一个订单日期,即 m1 < m2.
**最终查询**
SELECT
COUNT(DISTINCT user_id) AS users
FROM (
SELECT
user_id,
-- Date when user first orders any item
MIN(created_at) OVER(PARTITION BY user_id ) AS m1,
-- Date when each item was first ordered by user
MIN(created_at) OVER(PARTITION BY user_id,product_id ) AS m2
FROM marketing_campaign
)c WHERE m1< m2
**输出**

## 数据科学面试问题#4:葡萄酒总收入
> 你有一个葡萄酒的数据集。找出每个至少有 90 分的酿酒厂和品种的总收入。酒厂中的每种葡萄酒,品种对应该至少是 90 分,以便在计算中考虑该对。
> 输出酒厂和品种以及相应的总收入。酿酒厂的订单记录按升序排列,总收入按降序排列。
在问题中,我们有一张表;winemag_p1。找到下表的输出示例:
**表格:** winemag_p1

我们需要找到每个至少有 90 分的酒厂和品种的总收入。
**解决方法**
所以对于这个问题,第一步是计算每个酒厂和品种的总收入。为此,我们将做 SUM(price)来计算总收入,然后按酒厂和品种分组。下面是它的代码。
SELECT winery,
variety,
SUM(price) AS revenue
FROM winemag_p1
GROUP BY 1
这将计算所有葡萄酒厂的总收入,而不考虑总积分。问题还要求我们只有在积分大于 90 的情况下才计算收益。因此,为了合并这一部分,我们将在上述查询的末尾使用 HAVING()子句来解决这个问题。
**最终查询:**
SELECT winery,
Variety,
SUM(price)
FROM winemag_p1
GROUP BY 1,2
HAVING SUM(points<90) = 0
**输出**

## 数据科学面试问题#5:课堂表现
> 给你一张表格,上面有一个班级学生的作业分数。编写一个查询,确定所有作业总分的最大差异。仅输出最高分学生和最低分学生之间的总分(所有 3 项作业的总和)之差。
在这个问题中,我们需要找出不同学生之间所有三个作业中的范围(最大值和最小值之间的差异)。为了解决这个问题,我们有一个表:box_scores。
**表格:**方框 _ 分数

**解决方法**
第一步是计算每个学生所有三次作业的总分。让我们用 CTE 来回答这个问题,并将结果存储在 t1 中。
with t1 as (
SELECT DISTINCT student,
assignment1 + assignment2 + assignment3 AS total_score
FROM box_scores
)
上述查询的输出如下所示,其中包含不同的学生以及所有三项作业的总分。现在我们需要找出最高分和最低分的区别。

**最终查询**
WITH t1 as (
SELECT DISTINCT student,
assignment1 + assignment2 + assignment3 AS total_score
FROM box_scores
)
SELECT MAX(total_score) - MIN(total_score) AS diff
FROM t1
**输出**

# 计算机编程语言

作者在 [Canva](https://canva.com/) 上创建的图片
## 数据科学面试问题#6:工资中位数
> 求各部门员工工资中位数。输出部门名称以及四舍五入到最接近的整数美元的相应工资。
**解决方法**
在这个问题中,我们需要找到每个部门的工资中位数。我们有一张表格,如下所示:
**表:**员工

该表有许多不需要的列。因此,我们首先将这个数据帧缩减为解决这个问题所需的两列;部门和工资。
# Import your libraries
import pandas as pd# Start writing code
employee = employee[['department','salary']]
得到的 Dataframe 将只有两列;部门和工资。现在,我们可以按部门进行分组,并使用函数 group by()和 median()计算工资中位数,如下所示:
**最终查询:**
# Import your libraries
import pandas as pd# Start writing code
employee = employee[['department','salary']]
result = employee.groupby(['department'])['salary'].median().reset_index()
result
**输出**

## 数据科学面试问题#7:平均工资
> 将每个员工的工资与相应部门的平均工资进行比较。输出雇员的部门、名字和工资以及该部门的平均工资。
**解决方法:**
下面提供了一个表格来解决这个问题:
**表:**员工

在这个问题中,我们需要计算每个部门的平均工资。因此,这可以通过在 python 中使用 groupby()函数和 average 函数来实现。因此,第一步是在单独的列中计算平均值,如下所示:
# Import your libraries
import pandas as pd# Start writing code
employee['avg_salary'] = employee.groupby(['department'])['salary'].transform('mean')
employee.head()
一旦我们计算了平均值,我们现在可以选择列;问题中提到的部门,名字,工资,平均工资。最后一个查询如下:
**最终查询:**
# Import your libraries
import pandas as pd# Start writing code
employee['avg_salary'] = employee.groupby(['department'])['salary'].transform('mean')result = employee[['department','first_name','salary','avg_salary']]
**输出**

## 数据科学面试问题#8:有奖金的员工
> 查找奖金低于 150 美元的员工。输出名字和相应的奖金。
**解决方法**
在这个问题中,首先需要检查有奖金的表中有哪些行< 150\. There is one table provided to solve this question:
**表:**员工

从上表中,我们可以看到解决该问题所需的“奖金”和“名字”字段。第一步是筛选奖金低于 150 的行的数据。请在下面找到它的示例代码:
# Import your libraries
import pandas as pd# Start writing code
employee = employee[employee['bonus']<150]
完成后,我们选择最终查询问题中提到的 first_name 和 bonus 字段。
**最终查询:**
# Import your libraries
import pandas as pd# Start writing code
employee = employee[employee['bonus']<150]employee[['first_name','bonus']]
**输出:**

这些是 Python 中常见的编码问题。如果你想练习更多,查看数据科学的[熊猫面试问题](https://www.stratascratch.com/blog/python-pandas-interview-questions-for-data-science/?utm_source=blog&utm_medium=click&utm_campaign=medium)了解更多信息。
# 非编码
在这一部分,我们将讨论数据科学面试中可能会问到的非编码面试问题。这部分问题对于[数据科学面试准备](https://www.stratascratch.com/blog/5-tips-to-prepare-for-a-data-science-interview/?utm_source=blog&utm_medium=click&utm_campaign=medium)至关重要,因为面试通常有广泛的主题。非编码问题可以是系统设计主题、概率、商业案例研究、统计、建模、技术或产品。这些问题也可以作为您在编码过程中编写的代码的后续问题。现在,让我们看看在数据科学面试中可以提出的不同类型的非编码问题。
# 系统设计

作者在 [Canva](https://canva.com/) 上创建的图片
这类问题测试你解决设计问题和从头创建系统的能力。这些是典型的带有一些计算的理论问题。现在我们来看几个例子。
## 数据科学面试问题#9:餐厅推荐
> 你会如何在新闻提要上建立一个“你可能喜欢的餐馆”推荐系统?
这个问题要求您在新闻提要上建立一个“您可能喜欢的餐馆”推荐系统。
**解决方案:**
要解决这些问题,你需要对推荐系统的工作原理有一个基本的了解,以及对于给定的问题,我们如何最好地利用脸书的这些系统。你可以从网飞的推荐系统中得到启发。
“您可能喜欢的餐厅”推荐系统采用两种方法:
**基于内容的过滤(CBF):**
定义:它根据用户过去的行为来模拟用户的口味,但不受益于其他用户的数据。
**该方法的局限性:**
* 推荐结果的新颖性,因为它只查看用户的历史,从不跳转到用户可能喜欢但以前没有互动过的其他领域。
* 如果内容的质量不包含足够的信息来准确地辨别项目,CBF 将表现不佳。
**协同过滤(CF):**
定义:CF 查看用户/项目的交互(访问),并试图在用户或项目之间找到相似性,以完成推荐工作。
**限制:**
* 用户和项目之间的稀疏交互矩阵:许多用户很少甚至没有交互。
* 冷启动问题:寻找合适的餐厅是一个巨大的问题,尤其是对特定地区的新人。
为了解决这两种方法的局限性,可以使用一种混合方法,利用用户、项目和元数据之间的协作信息来完成推荐工作。
## 数据科学面试问题#10:存储数据的 Python 字典
> 什么时候我会使用 Python 字典来存储数据,而不是另一种数据结构?
**解决方案**
当代码可读性和获取数据的速度很重要时,我会使用 Python 字典来存储数据。当数据的顺序不重要时,我也会使用它。
Python 中的字典是用于保存键值对的结构。
它应该在以下情况下使用:
* 我们希望快速访问一些数据点(值),假设我们可以唯一地将该数据与一些标识符(键)相关联;
* 数据点的顺序并不重要。
这个键应该是 *hashable* ,也就是说,它可以被传递给一个哈希函数。哈希函数接受任意大小的输入,并将其映射到相对较小的固定大小的输出(哈希值),该输出可用于表查找和比较。
对字典值的类型没有限制。这是 Python 中字典相对于集合的一个重要优势,因为集合要求所有值都是可散列的。
## 数据科学面试问题#11:建立推荐系统
> 你能告诉我们你将如何建立一个推荐系统吗?
**解决方案:**
应该建立的推荐系统(RS)的类型取决于应用程序的当前用户(和产品/项目)基数的大小。
RSs 有一个“冷启动”的问题,也就是说,如果我们的数据量很少,那么它们根本没有效率。但是,一旦我们收集了足够的数据点,我们就可以利用它们向我们的用户提供建议。
因此,为了解决冷启动问题,我建议使用基于**流行度的**算法。基于流行度的算法通过某种度量(例如,购买计数、查看次数等)对所有项目进行排序。)并推荐排名靠前的项目。很明显,这种方法过度适合最受欢迎的商品,并且总是推荐它们,但是当我们的商品数量很少时,这是有意义的。
一旦用户群增长,并且我们已经收集了一些数据,我们就可以应用更高级的算法来推荐商品。两种最流行的方法是:
1. **基于商品的过滤**:我们向每个用户推荐与其购买历史最相似的商品
2. **基于用户的过滤**:我们向每个用户推荐与他们最相似的用户最常购买的商品
在这两种情况下,相似性的度量需要被定义并且是特定于应用的。在实践中,这两种方法经常结合起来提供一个混合的 RS。
构建 RS 的另一种方法是通过**分类模型**。分类模型将输入用户和商品相关的特征,并输出每个商品的概率/标签,该概率/标签将表示用户购买该商品的可能性。模型可以是*任何*分类模型:逻辑回归、KNN、神经网络等。我认为这是大公司(如亚马逊、谷歌、网飞)提供个性化推荐的方法。
# 可能性

作者在 [Canva](https://canva.com/) 上创建的图像
## 数据科学面试问题#12:电梯里的四个人
> 电梯里有四个人,他们将在大楼的四个不同楼层停留四次。每个人在不同楼层下车的概率是多少?
**解决方案:**
4 个人分配 4 个楼层的总可能性:
4 * 4 * 4 * 4 = 256
因为有 4 个人,并且每个人可以在 4 个楼层中的任何一个楼层下车,所以从 4 个人分配 4 个楼层的总可能性没有重复:
4 * 3 * 2 * 1 = 4!= 24 种可能性
这意味着第一个人选择楼层有 4 个选项,第二个人有 3 个选项,第三个人有 2 个选项,最后一个人只有 1 个选项。
因此,每个人在不同楼层下车的概率将是:

## 数据科学面试问题#13:选择两个女王
> 从一副牌中选出 2 张皇后的概率?
**解决方案:**
由于一副牌中总共有 52 张牌和 4 张皇后,那么我们在第一次尝试中得到一张皇后的概率是:

如果我们在第一次试验中得到一个皇后,那么在 51 张牌中还有 3 个皇后,因此我们在第二次试验中得到一个皇后的概率是:

我们得到 2 个皇后的概率是在第一轮和第二轮中得到一个皇后,或者:

# 统计数字

作者在 [Canva](https://canva.com/) 上创建的图像
## 数据科学面试问题#14:中心极限定理
> 解释中心极限定理。
**解决方案:**
**中心极限定理**可以分多个部分解释:
* 无论样本大小或总体分布如何,样本均值等于总体均值
* 样本均值的标准差等于总体的标准差除以样本大小的平方根
* 如果总体呈正态分布,则无论样本大小如何,样本均值的抽样分布都将呈正态分布
* 如果人口分布不是正态的,那么 30 或更多的样本量通常被认为足以满足 CLT
## 数据科学面试问题#15:评估多重共线性
> 评估多重共线性的不同技术有哪些?
**解决方案:**
要处理多重共线性,有以下几种常见的方法:
1.相关矩阵
相关矩阵显示两个独立变量之间的皮尔逊相关性。如果两个自变量之间的相关系数大于 0.75,那么我们认为这些变量具有高度的共线性。
2.差异通货膨胀系数(VIF)
VIF 测量模型中只有相应自变量的给定系数的方差与模型中所有自变量的给定系数的方差之比。
第 I 个自变量的 VIF 定义为:

其中 R(i)是当只有第 *i* 个变量起作用时的方差的量度,相对于当所有变量都包括在内时的方差。VIF 值越接近 1,变量与所有其他预测值的相关性越小。因此,要评估数据集中的多重共线性,我们可以计算所有预测变量的 VIF,并将临界值(通常在 5 到 10 之间)指定为阈值。
## 数据科学面试问题#16:精确度和召回率
> 提供精确和召回的公式。
**解决方案:**
精确度和召回率都是分类任务中常用的评估指标。
**精度**
Precision 试图回答以下用例:
从我们的 ML 模型中得到的正面预测有多大比例是正确的?

**回忆**
回忆试图回答以下使用案例:
我们的模型正确预测的实际阳性比例有多大?

# 建模

作者在 [Canva](https://canva.com/) 上创建的图像
## 数据科学面试问题#17:标记的实例很少
> 使用大数据集(其中只有一小部分实例被标记)可以使用什么技术来训练分类器?
**解决方案:**
因此,问题是“使用大数据集(其中只有一小部分实例被标记)可以使用什么技术来训练分类器?”
如果我们有一个大数据集,其中只有一小部分实例被标记,那么我们可以使用半监督机器学习技术。
在实践中,半监督机器学习算法包括监督算法和非监督算法的组合。例如,将 k-均值聚类和诸如神经网络、SVM、随机森林等分类算法之一相结合的算法。
举个例子,假设我们想对大量的 0-9 之间的手写数字进行分类。标记所有的手写数字既费时又费钱。我们能做的是:
* 首先,使用 K-means 聚类对所有手写数字进行聚类,假设我们初始化了 100 个聚类。
* 接下来,我们在每个聚类中选取最接近每个聚类质心的数据点,这意味着我们有 100 个手写数字,而不是整个数据集。
* 接下来,我们可以标记这 100 个手写数字中的每一个,并使用它们作为我们分类算法的输入,以获得数据的最终预测。
## 数据科学面试问题#18:功能相关性
> 如果两个特征在线性回归中相关,会发生什么?
**解决方案:**
如果两个要素相互关联,那么它们将引入所谓的多重共线性问题。
**什么是多重共线性?**
当两个或多个独立变量(要素)相互关联时,线性回归中会出现多重共线性。这不应该发生在线性回归中,因为顾名思义,一个独立变量应该独立于其他独立变量。
**多重共线性有什么问题?**
简单的线性回归模型具有以下等式:

其中:
y^:预测值
*θ(0):截距*
*θ(1):第一个特征的权重*
*x(1):第一个特征的值*
*当我们的线性回归模型存在多重共线性问题时,那么:*
* *每个特征的权重将对模型中的微小变化高度敏感。例如,如果我们从模型中添加或删除一个特征,每个特征的权重将大幅波动。结果,解释一个特征对我们的线性回归模型的性能的影响变得困难。*
* *多重共线性会增大每个要素权重的误差和标准差。这就成了一个问题,因为当我们想要进行特征选择(向模型添加或移除特征)时,我们不能相信从每个特征的模型得出的统计结果(p 值)*
*如果您想更深入地了解多重共线性以及我们如何解决这个问题,请查看此资源。*
# 技术的

作者在 [Canva](https://canva.com/) 上创建的图像
## 数据科学面试问题#19:数据库规范化
> 列出并简要解释数据库规范化过程的步骤。
**解决方案:**
数据库规范化是将复杂的用户视图和数据存储转换为一组更小的稳定数据结构。除了更简单和更稳定之外,规范化数据结构比其他数据结构更容易维护。
**步骤**
1. 第一范式(1 NF):该过程的第一阶段包括移除所有重复组并识别主键。为此,需要将关系分解为两个或更多的关系。在这一点上,关系可能已经是第三范式,但是很可能需要更多的步骤来将关系转换成第三范式。
2. 第二范式(2 NF):第二步确保所有非键属性完全依赖于主键。所有部分从属关系都被移除并放置在另一个关系中。
3. 第三范式(3 NF):第三步移除任何传递依赖。传递依赖是指非键属性依赖于其他非键属性。
在 3NF 之后,其余的规范化是可选的,设计选择取决于数据集的性质。
4.Boyce-Codd 范式(BCNF):它是 3NF 的更高版本。这个表单处理 3NF 不能处理的某种异常。没有多个重叠候选关键字的 3NF 表被称为在 BCNF。对于 BCNF 中的表,该表必须是第三范式,并且对于每个函数依赖(X -> y),X 应该是一个超级键。
5.第四范式(4NF):对于满足第四范式的表,它应该是 Boyce-Codd 范式,并且该表不应该有任何多值依赖。
## 数据科学面试问题#20: N-Gram
> 什么是 n-gram?
**解决方案:**
N-gram 是 N 个单词或字母的序列,通常用于自然语言处理领域以实现各种事情:例如,作为模型训练的输入以执行句子自动完成任务、拼写检查任务或语法检查任务。
* 1-gram 表示一个序列只有一个词,如 ***【星期六】【是】【我的】【最爱】【日】***
* 2-gram 表示有两个记录的序列,如 ***“星期六是”、“是我的”、“我最喜欢的”、“最喜欢的一天”***
以此类推,3、4、5 克。正如你注意到的,N-gram 序列越大,它提供的上下文就越多。
# 产品

作者在 [Canva](https://canva.com/) 上创建的图像
## 数据科学面试问题#21:客户参与和脱离
> 您如何衡量客户参与度和脱离度?
**解决方案:**
客户参与是指用户在整个用户旅程中反复遇到你的产品或品牌。
有四种方法来衡量客户参与度:
1. **按月、周或日:**找出每天、每周或每月有多少忠实用户使用你的产品。这完全取决于你期望你的用户如何使用这个应用。这里一个重要的 KPI 就是粘性:日活跃用户和月活跃用户的比例。
2. **按渠道:**找到带来最赚钱客户的获取渠道。
3. **通过功能使用:**为你的用户识别无价的功能。
4. **根据客户健康评分:**根据参与度预测从客户处获得选定结果的可能性。
## 数据科学面试问题#22:点击搜索结果
> 您注意到点击脸书活动搜索结果的用户数量每周增加 10%。你会采取什么步骤来调查变化背后的原因?你如何决定变革对企业的影响是好是坏?
**解决方案:**
为了回答这个问题,下面有一个非常详细的解决方案:
**你会采取什么措施来调查变化背后的原因**
1.澄清指标
* 搜索结果的过程是怎样的
* facebook 事件到底是什么
2.暂时的
* 增长变化有多快
* 这是一个月之后的一个渐进的变化吗
* 一周内是否有异常值导致 10%的变化
* 查看历史周环比百分比,确定它是否是正常变化
**如果这些情况都不存在,则继续**
3.内部产品变更
* 是产品的某种变化导致了这种度量的变化吗
* 前任。改变搜索结果的顺序(事件弹出更多),改变产品以增加搜索量,导致更多点击
4.外部变化
* 突然出现的引起更多点击的重大事件
* 增加事件数量
5.度量分解
* “周复一周”指标包括整体搜索还是仅包括事件的点击量
* 检查为什么总搜索量可能会增加
**您如何决定变革对业务的影响是好是坏**
1.阐明企业的目标是什么
* meta 通过事件的更多点击获得了什么
* 这些活动页面有人们可以看到的广告吗
* meta 会从活动页面的更多点击中获得收入吗
* 或者产品的目标仅仅是与 facebook 平台更多的互动?
2.一旦定义了产品的目标,我们就可以巩固指标
* 我们还可以看哪些其他指标
* 页面上的广告点击率是多少
* 收入增加了多少
* 点击率增加会导致 facebook 会话时间变长吗
3.用所有的信息做一个推荐
* 前任。如果增加导致会话时间变短,那么我们可能希望诊断问题并恢复所做的任何更改
# 结论
在准备数据科学工作面试时,你应该准备好各种各样的问题。编码和非编码数据科学面试问题是两大类。
即使编码相关的问题是最常见的,你仍然需要掌握其他技能。因此,非编码问题是必不可少的。它们是展示你的技术熟练程度以及产品、建模和系统设计知识的一种方式。
本指南描述了您在知名公司的数据科学面试中会遇到的问题。在这些公司找工作并不简单。浏览每个问题只是一个开始。剩下的编码和非编码面试问题现在轮到你来考察了。
# 你应该问基于人工智能的药物研发公司的问题
> 原文:<https://towardsdatascience.com/questions-you-should-ask-ai-based-drug-discovery-companies-78c5a011c915>
## 基于人工智能的药物发现公司正在处理的问题
当我第一次听说有人在努力通过机器学习方法来发现药物时,我对人工智能可以提出新的药物化合物的想法非常感兴趣,我立即戴上了数据科学家的帽子,准备在这个领域应用一些很酷的深度学习方法(特别是甘,我承认:-)。尽管我的精神很好,但很快,我意识到(在我的医学算法职业生涯中,一次又一次地)在计算机视觉应用中工作良好的方法不一定在药物发现中工作。我被激励去揭开这个差距的原因,结果就在这里。我提出了 9 个问题,我认为这是造成这种情况的原因,我将在下面详细阐述。如果你从事药物研发领域,无论是在求职期间还是作为企业家,我建议你进一步研究或讨论这些问题。为了帮助你进行这些互动,我在每个主题上添加了一个问题示例,如果你试图建立一个问题示例,你可以问公司或你自己。

药品代理公司正在考虑是否在 tinder 中直接攻击 GAN。[派克斯](https://medium.com/u/5c7773c5a002?source=post_page-----78c5a011c915--------------------------------)
1. **数据缺乏,数据集不平衡。**用于药物靶标相互作用(DTI)预测的最常用基准数据集包含不到 1000 个药物分子。另一个数据集包含 1:41 的活性与非活性药物反应比。迁移学习(TL)可以从大的标记数据点学习,并将其知识转移到有限数据点的下游任务。然而,TL 通常仍然需要合理数量的平衡训练数据点。*您使用哪个数据集?你训练了多少药物分子?*
2. **非同步结构。**对于生成模型来说,探索超出其训练范围的分子结构是期望的,甚至是“健康的”,但是单个功能基团的变化可以导致独特的合成途径;因此,他们可能会提出荒谬的结构,不合理的药物目的。在一个案例中,即使在对深度学习模型生成的 30,000 个结构的初始列表进行过滤后,根据合成可及性,从 40 个候选结构中仅选择了 6 个合理的分子[6]。在其他情况下,提出的分子是如此荒谬,以至于人们立即意识到为什么仅仅根据它们的目标函数值来衡量这些方法是不够的。*你的生成式方法得到一堆结构后,你的过滤/评分/重新学习过程是怎样的?*
3. **配送班次。**当训练和部署数据遵循相同的数据分布时,ML 模型工作良好。然而,在现实世界的使用中,特别是在基因组学中,许多问题会经历分布变化,其中部署数据与训练数据不同。例如,用脑组织的基因表达进行训练,同时预测骨组织。另一个例子是动物模型转录组的训练和预测人类的表型。一般来说,如果感兴趣的组织类型以前从未被研究过,将没有现有的数据集可用于训练监督方法,如[元学习](https://par.nsf.gov/servlets/purl/10219017)。然而,当训练数据可用时,监督方法的精确度、速度和可重复性的提高很容易胜过非监督方法。*你会将你的数据推广到不同种类的组织吗?*
4. **表现能力。** CNN 模型常用来表示 DNA 序列。然而,大多数成功的应用仅适用于从预定义的预处理步骤产生的短 DNA 序列,而不是整个基因组序列的大部分,这可以允许模型利用长程基因调控依赖性的关键信息。RNN 和变形金刚也只能接受中等长度的输入,相比之下,每个基因组有超过一百万个 SNP(核苷酸)。这也意味着输入特征的数量可以比数据点的数量大几个数量级,这是一个众所周知的 ML 挑战,称为维度的诅咒(我在我的博客[中写道,弥合遗传和神经网络之间的差距](/bridging-the-gap-between-genetics-and-neural-networks-4fdb91032f4b?source=your_stories_page----------------------------------------))。此外,一般的 ML 模型通常是为没有生物动机的图像和文本数据开发的。因此,需要一个领域驱动的模型来捕捉极远距离高维特征之间的相互作用,以模拟人类基因组和基因之间的复杂调控。*你的基因组代表是什么?嵌入层是怎么学的?*
5. 多模态。众所周知,相同的小分子药物在不同的基因组谱中可以具有不同的反应水平。例如,一种抗癌药物对组织的反应可能不同[4]。因此,生成给定药物的准确反应谱和相应的基因组学谱对是至关重要的。然而,当前的 ML 方法集中于为单个数据模态开发方法,而为了完全捕获个体周围的完整数据类型,应该使用多模态。如上所述,集成受限的原因之一是缺少连接这些异构数据类型的数据访问。此外,尚不清楚该模型是否可以推广到新的背景,如新的细胞类型和结构多样的药物,样本有限。然而,下面的架构试图通过连接两个模态来解决这个问题:基因表达和一个分子(下图)。如上所述,RNN 代表一个分子,CNN 代表一个基因图谱。通过连接这些模型的输出,生成了包含顺序数据和卷积数据的组合表示。*如何连接顺序层和卷积层?*
6. **维度。**字符串和指纹等分子表示将小分子描述为二维字符串(例如 SMILE 数据集)。提醒一下:有不止一个分子阶段,比如代表三维的三级结构,代表四维的四级结构,等等。然而,需要通过考虑三维结构来改进这种表示,因为同一小分子的构象变化可以改变受体-配体相互作用。你训练高于二维的结构吗?
7. **模范守信。**“打开盒子”模型可以理清事情,并呈现最终预测内部知识的真实画面(我在博客[中写了这一点,而不仅仅是性能](/not-just-performance-insights-about-the-validation-process-of-sequences-of-life-models-eddd2c365343?source=your_stories_page----------------------------------------))。由于 ML 模型不是无误差的,当用户不自信时,提醒用户避免做出预测是至关重要的。*如何评价你的模型预测质量?* *你是否使用 e*[*xplainable models*](https://arxiv.org/pdf/2006.14779.pdf)*之类的方法来量化你预测的不确定性?*
8. **针对某些人群的不平衡数据。**由于不同人群中等位基因频率的差异,在一个人群中表现良好的 ML 模型可能在其他人群中预测不佳。这些算法偏差可能对少数民族产生重大的[医学问题后果](http://ziadobermeyer.com/wp-content/uploads/2021/01/Algorithmic-approach-to-pain.pdf)。79%的基因组数据来自欧洲血统的患者,尽管他们只占世界人口的 16%[2]。由于迄今为止大多数发现是在欧洲血统人群中进行的,预测模型忽略了非洲和西班牙血统人群。同样,大多数研究集中于常见疾病,而罕见疾病往往是有限的。*你的数据集涉及不同的人群吗?你如何概括人口基因组?*(我写了一篇关于这个的博客[这里](/bridging-the-gap-between-genetics-and-neural-networks-4fdb91032f4b?source=your_stories_page----------------------------------------))
9. **基因组数据隐私。**每天都会产生大量的基因组学数据和注释。这些数据和注释的集合可以极大地有益于 ML 模型。但是,这些通常被视为个人的私有资产,包含敏感的个人信息,因此不能直接共享。联合学习技术允许在不共享数据的情况下对聚合数据进行机器学习模型训练。*您使用任何技术来匿名化您的数据吗?*

**多模态神经网络**。给定一对药物化合物的分子结构和细胞系的基因表达谱,预测药物反应。药物编码器和细胞系编码器分别产生药物和细胞系的嵌入,然后将它们送入预测器以估计药物反应。(我画这个图)
## 关闭
在这里,我们概述了药物发现中的一些主要问题,希望尽快解决这些问题,并让您对这个主题的局限性有一个深刻的理解。我个人认为,就目前而言,我们远远没有解决这些问题,主要是因为最新的可用数据。一句话:如果你想在这个领域有所贡献,就把深度学习算法放在一边,自己创业去收集药物分子的下一个大标杆;这将比其他任何事情都更有贡献。同时,下一次你有关于这个话题的谈话,不要犹豫,看着他们的眼睛,自信地问这些问题…祝你好运。
## **注意事项**
1. 我写了另一个关于药物发现模型的博客,更像是一个测验。如果你想测试自己(无耻地),阅读[这篇](/pl-ai-play-models-quiz-yourself-9ca35acd5192)博客。
2. 我在博客上投入了很多心血。请给我写一封电子邮件(miritrope@gmail)并通过 [LinkedIn](https://www.linkedin.com/in/miri-trope-916a194a/) 联系,告诉我你喜欢我的作品。
## 参考
1. 人工智能在药物发现中的应用与技术。 *arXiv 预印本 arXiv:2106.05386* (2021)。
2. 黄,可新,等,〈机器学习在基因组学数据治疗任务中的应用〉。arXiv 预印本 arXiv:2105.01171 (2021)。
3. 李、、、徐、。"卷积回归神经网络和转移学习用于有机药物和材料发现中的 QSAR 建模."*分子* 26.23 (2021): 7257。
4. 金,叶筋等,〈利用迁移学习预测未充分研究的组织中的抗癌药物协同作用〉*美国医学信息学协会杂志*28.1(2021):42–51。
5. 高,,柯里。"由生成模型提出的分子的可合成性."《化学信息与 2 建模杂志》*60.12(2020):5714–5723。*
6. 使用深度学习的最新药物发现的综合调查。《国际分子科学杂志》第 22 卷,18 9983。2021 年 9 月 15 日,doi:10.3390/ijms22189983
7. 卞、与项。"生成化学:具有深度学习生成模型的药物发现."*分子模拟杂志*27.3(2021):1–18。
# Python 面向对象编程的快速简单介绍
> 原文:<https://towardsdatascience.com/quick-and-easy-introduction-to-object-oriented-programming-with-python-8d6167c4c8eb>

妮可·沃尔夫在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的照片
## 使用 Python 从对象到面向对象的行为
如何管理一堆数据要操作,一堆事情要做的复杂程序?

作者图片
对于如何构建一个程序,有很多可能性,如果我们将这些可能性结合在一组结构化的规则和属性中,我们就得到所谓的编程范例。
面向对象编程(OOP)只不过是一种通用的编程范式,也就是说,一种设计软件的方法。它被认为有助于大型软件项目的设计,在学术界和工业界得到了广泛的应用。
***在这篇文章中,我想用 Python 来讨论 OOP 的基础知识。***
# 从复杂的组件到直观的对象
OOP 应该是一种让软件开发过程感觉更直观的编程范式。为什么?因为我们可以从对象及其关系的角度来思考复杂的组件。
一个物体是一个实体,或者“东西”,通常是一个名词,比如一个人或者一辆摩托车。
每个对象都有属性,比如一个人有名字、年龄和(可能是地址),一辆摩托车有颜色、型号和牌照。

图片由作者提供,改编自 [Lukaz Adam 的免费插图](https://lukaszadam.com/illustrations)
除了属性,单个对象也有行为,比如一个人走路和说话,而一辆摩托车,可以启动和关闭,以给定的速度运行,等等。

图片由作者提供,改编自 [Lukaz Adam 的免费插图](https://lukaszadam.com/illustrations)
还有其他的复杂性我们在这里没有提到,比如,成为一个对象需要什么?这似乎有点主观,对吗?还有,一旦某物是对象,那么某物成为属性或行为的规则是什么?
我们将跳过那些没有明确答案的问题,坚持把事物看作具有属性和行为的对象。

作者图片
# Python 类
为了进入对象、属性和行为的主题,让我们使用 [Python 类](https://www.w3schools.com/python/python_classes.asp)作为我们的指南。在讨论 Python 类时,有一些事情需要考虑:
* *类是我们在 Python 中用来创建对象的东西*
* 我们可以从一个类中创建尽可能多的独特对象
* *类在 Python* 中定义了一个叫做 `*type*` *的东西*
* *实例化是从 Python 中的类创建对象的过程的名称*
让我们用一个简单的例子来解开所有这些断言:
name = 'Lucas'
在这里,我简单地创建了一个名为`name`的变量,其值为`Lucas`,这个变量是一个对象的引用,在这里,对象的`type`是一个`str`,因为为了创建它,我们实例化了 Python 内置的`str`类。
现在,让我们创建一个名为`Person()`的类,和另一个名为`Motorcyle()`的类,它们将分别具有属性(attribute):`name`和`model`。
class Person:
def init(self, name):
self.name = name
class Motorcycle:
def init(self, model):
self.model = model
p = Person("Lucas")
print(p.name)
m = Motorcycle("Scooter X3000")
print(m.model)Lucas
X3000
在本例中,我们创建了`Person()`和`Motorcycle()`类,它们分别有自己的名称和模型属性,然后我们实例化了它们,这意味着我们从这些类(或类型)中创建了唯一的对象,在本例中名称为`"Lucas"`,模型为`"Scooter X3000"`。
我们使用了被称为构造函数的方法:`__init__`,Python 中用来初始化数据的方法,这些是我们实例化类时调用的方法。
在 Python 类的上下文中,`self.name = name`行是 Python 中的*实例属性*的一个例子,它指的是该类的唯一实例化的特定属性。
我们可以创建尽可能多的这些类的实例:
instantiate 3 people and 3 motorcycles
p1 = Person("Lucas")
p2 = Person("Beatriz")
p3 = Person("Sara")
m1 = Motorcycle("Scooter X3000")
m2 = Motorcycle("Scooter X4000")
m3 = Motorcycle("Scooter X5000")
and so on and so forth
考虑这个问题的一种方法是,类就是模板,每个实例化都是模板的一个真实应用实例。

图片由作者提供,改编自 [Lukaz Adam 的免费插画](https://lukaszadam.com/illustrations)
考虑到这种类比可能会与抽象和一种特定类型的 Python 类(称为抽象类)相混淆,所以要有所保留。
## 行为呢?
我们可以通过添加允许它们做事情的方法来为我们的 Python 类添加功能,例如,我们的`Person()`类可以有一个`walk()`方法,我们的`Motorcyle()`类可以有一个`turn_on()`方法。
class Person:
def init(self, name):
self.name = name
def walk(self):
print(f"{self.name} is walking")
class Motorcycle:
def init(self, model):
self.model = model
def turn_on(self):
print("Motor is on")
p = Person("Lucas")
p.walk()
m = Motorcycle("Scooter X3000")
m.turn_on()Lucas is walking
Motor is on
但是,班级之间如何交流呢?
比方说,我想让我的`Person()`类能够以某种方式与`Motorcycle()`类相关联,我们从`Motorcycle()`类写一个对`turn_on()`方法的修改怎么样,其中需要创建一个`Person()`类的实例?
class Person:
def init(self, name):
self.name = name
def walk(self):
print(f"{self.name} is walking")
class Motorcycle:
def init(self, model):
self.model = model
def turn_on(self, owner):
p = Person(owner)
print(f"{p.name} turned the motor on")
mt = Motorcycle("Scooter X3000")
mt.turn_on("Lucas")Lucas turned the motor on
现在,我们开始看到属性、行为与类交互结合在一起。这是 OOP 世界的开始,因为对象开始相互交互来创建复杂的动态。

图片由作者提供,改编自 [Lukaz Adam 的免费插画](https://lukaszadam.com/illustrations)
# 面向对象编程是一种技能
OOP 是一个迷人的主题。我们如何将对象聚集在一起以创建有趣的软件动态?鉴于问题的复杂性,答案可能不是很清楚。
但是,当我们作为 Python 开发人员发展时,考虑它是有意义的。将软件组件视为我们可以创建并组合在一起以生成丰富的交互并最终产生有趣功能的对象,这是一项终生的技能,在学术界和工业界编写良好的软件中深受赞赏。
如果你喜欢这个帖子,[加入 Medium](https://lucas-soares.medium.com/membership) ,[关注](https://lucas-soares.medium.com/),[订阅我的简讯](https://lucas-soares.medium.com/subscribe)。还有,订阅我的 [Youtube 频道](https://www.youtube.com/channel/UCu8WF59Scx9f3H1N_FgZUwQ)在 [Tiktok](https://www.tiktok.com/@enkrateialucca?lang=en) 、[推特](https://twitter.com/LucasEnkrateia)、 [LinkedIn](https://www.linkedin.com/in/lucas-soares-969044167/) 、 [Instagram](https://www.instagram.com/theaugmentedself/) 上和我联系!谢谢,下次再见!:)
如果你想进入面向对象编程领域,可以看看这本书:
* [编程 Python:强大的面向对象编程](https://amzn.to/3EAJhfr)
> 这是一个附属链接,如果你购买的产品,我得到一小笔佣金,干杯!:)
# 参考
* [https://real python . com/lessons/what-object-oriented-programming-OOP/](https://realpython.com/lessons/what-object-oriented-programming-oop/)
* [https://www.w3schools.com/python/python_classes.asp](https://www.w3schools.com/python/python_classes.asp)
# 调试 SageMaker 管道的快速简便方法
> 原文:<https://towardsdatascience.com/quick-and-easy-way-to-debug-sagemaker-pipelines-473b9c56f015>
## AWS SageMaker 本地模式简介

照片由[EJ·斯特拉特](https://unsplash.com/@xoforoct?utm_source=medium&utm_medium=referral)在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上拍摄
## 介绍
您可能已经体验过,在云环境中调试管道非常耗时。因为我们的程序与云托管的服务交互,所以与本地环境相比,我们有许多需要考虑的地方。例如,假设您的管道由于管道最后一步的版本问题而失败。可能它已经运行了 2 个小时才出现故障。在这种情况下,你在特定云资源上的钱就浪费了。这就是数据科学家/ML 工程师应该知道的调试和测试管道的经济有效的方法。在本文中,我将指导您如何使用本地模式特性调试 AWS SageMaker 管道。
本文涵盖的主题。
* 什么是 SageMaker 管道
* 什么是 SageMaker 本地模式
* 使用 SageMaker 本地模式的好处
* 如何使用 SageMaker 本地模式进行测试和调试?
我们开始吧。
## 什么是 Sagamaker 管道?

示例管道-插图-作者提供的图片
简而言之,SageMaker 管道是一个有向无环图(DAG ),它让我们能够根据自己的需求定义指令集。DAG 中的每个气泡对应于用户指定的指令。由于这些气泡是相互关联的,我们可以映射它们之间的依赖关系,这有助于我们将复杂的指令分成单个部分。此外,它为这些管道提供了更多的可维护性。
<https://docs.aws.amazon.com/sagemaker/latest/dg/pipelines-sdk.html>
## 什么是 SageMaker 本地模式,我们为什么要关心它
在解释本地模式之前,我们应该知道 SageMaker 管道是如何工作的。
> ***当我们执行 SageMaker 管道时会发生什么?***
为了解释这一点,让我以下面的步骤为例。
* 数据摄取
* 预处理
* 模型结构
* 推理
* 后加工
当我们执行上述管道时,SageMaker 将自动为每个步骤旋转一个 EC2 实例,并运行用户定义的脚本。事先,整个管道将使用 5 个 EC2 实例。记住,云中的所有资源都是有成本的。那就是成本因素了。时间因素怎么样?更惨。除了脚本执行时间,我们必须等待几分钟来旋转 EC2 实例。假设您正在构建一个管道或调试一个管道,这意味着您不得不在 EC2 实例上浪费不必要的金钱和时间。如果我们可以避免旋转 EC2 实例,并在本地运行整个管道作为试运行,会怎么样?这就是 SageMaker 本地模式派上用场的地方。
> 启用 SageMaker 本地模式后,我们可以在本地机器上尝试我们的管道。这意味着当我们调试或测试管道时,不会在 EC2 实例上浪费更多的时间和金钱。
让我们用预处理和训练步骤构建一个简单的管道。这里我将使用一个自定义容器。但是本地模式既适用于内置容器,也适用于自定义容器。你可以在下面看到我的文件夹结构。

我的文件夹结构—按作者分类的图像
## 构建预处理步骤
在这一步中,我将使用数据目录中的训练数据集,并将它们上传到预处理容器中。然后执行位于预处理目录中的 python 脚本。处理作业完成后,预处理文件应保存到配置的 S3 位置。
首先,我们需要设置下面的会话配置。只有当您与本地文件系统而不是 S3 进行交互时,才有必要这样做。
现在是定义处理步骤的时候了。
## 建立培训步骤
此步骤将执行以下操作。
* 获取预处理步骤输出,并用它们训练模型。
* 将模型工件保存到名为`model`的本地目录中
所以问题是如何将估算器`output_path`设置为本地目录。我们可以通过给本地路径添加一个`file://`前缀来设置本地路径。
## 建造管道
让我们测试我们的管道。
当您成功执行管道时,您会注意到在模型目录中创建的 model.tar.gz 文件。您可以使用它进行推理和模型诊断等。
## 最后的话
如果使用 SageMaker 进行开发,可能会遇到不同的 bug 和错误。这就是 SageMaker 本地模式可以成为你的救命稻草的地方。这会让你把更多的时间集中在你的问题上。在本地成功运行之后,您可以将模型管道投入生产。将本地模式转换为 AWS 托管管道时,必须用 SageMaker 会话替换本地会话,并使用具有必要权限的 SageMaker IAM 角色。
完整的代码可以在我的 [GitHub repo](https://github.com/Ransaka/Sagemaker-Local-Mode-Example) 中找到。
快乐学习!!
> *参考文献:*
* AWS 回购样本—[https://github.com/aws-samples/amazon-sagemaker-local-mode](https://github.com/aws-samples/amazon-sagemaker-local-mode)
* Sagemaker 文档—【https://sagemaker.readthedocs.io/en/stable/overview.html? highlight = local % 20mode #本地模式
# 多模态 ML 快速入门指南
> 原文:<https://towardsdatascience.com/quick-fire-guide-to-multi-modal-ml-with-openais-clip-2dad7e398ac0>
## 了解如何使用剪辑和矢量嵌入在文本和图像之间进行转换

作者修改,背景照片由 [Maximalfocus](https://unsplash.com/@maximalfocus?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 在 [Unsplash](https://unsplash.com/s/photos/future?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上拍摄
经过短短几年的生活,儿童可以理解简单单词背后的概念,并将它们与相关图像联系起来。他们能够识别物理世界的形状和纹理与书面语言的抽象符号之间的联系。
这是我们认为理所当然的事情。世界上很少有人(如果有的话)会记得这些“基本”技能超出他们能力的时候。
电脑不一样。他们可以计算出火箭穿越太阳系所需的参数。但是如果你让一台电脑找到一张“公园里的一只狗”的图像,你最好向美国国家航空航天局索要一张去空间站的免费机票。
至少,直到最近还是如此。
在这篇文章中,我们将看看 OpenAI 的剪辑。能够理解文本和图像之间的关系和概念的“多模态”模型。正如我们将会看到的,剪辑不仅仅是一个花哨的客厅把戏。它能力惊人。
# 对比学习?
**C**ontrastive**L**language-**I**mage**P**再训练(CLIP)由两个模型并行训练而成。用于图像嵌入的视觉变换器(ViT) *或* ResNet 模型和用于语言嵌入的变换器模型。
在训练期间,*(图像,文本)*对被馈送到相应的模型中,并且都输出表示矢量空间中相应图像/文本的 512 维矢量嵌入。
*对比*组件采用这两个向量嵌入,并计算模型损失作为两个向量之间的差异(例如,对比度)。然后优化两个模型以最小化这种差异,并因此学习如何将相似的*(图像,文本)*对嵌入到相似的向量空间中。
在这个对比预训练过程之后,我们剩下 CLIP,一个能够通过共享向量空间理解语言和图像的多模态模型。
# 使用夹子
OpenAI 开发并发布了`clip`库,可以在 GitHub 这里找到。然而,Hugging Face 的`transformers`库托管了 CLIP 的另一个实现(也是由 OpenAI 构建的),这个实现更常用。
拥抱脸实现*不*使用 ResNet 进行图像编码。它使用与文本转换器配对的 ViT 模型的替代设置。我们将通过把一个简单的文本-图像搜索脚本串在一起来学习如何使用这个实现,这个脚本可以适用于图像-图像、文本-文本和图像-文本模态。
## 加载数据和剪辑
首先,我们将安装演示所需的库,下载数据集,并初始化 CLIP。
pip install -U torch datasets transformers
我们将使用*“imagenette”*数据集,这是由拥抱脸托管的~10K 图像的集合。
这给了我们从收音机到狗的各种图像。所有这些图像都作为 PIL 图像对象存储在`'image'`特征中。
现在我们通过`transformers`库初始化 CLIP,如下所示:
这里发生了一些事情:
* 整个`device`部分是设置我们的实例,以使用我们可用的最快的硬件(M1 芯片上的 MPS,否则 CUDA)。
* 我们设定了`model_id`。这是在这里找到的剪辑型号[的名称](https://huggingface.co/openai/clip-vit-base-patch32)。
* 然后我们初始化一个用于预处理文本的`tokenizer`,一个用于预处理图像的`processor`,以及一个用于生成矢量嵌入的剪辑`model`。
现在我们准备开始创建文本和图像嵌入。
## 创建文本嵌入
文本转换器模型将我们的文本编码成有意义的矢量嵌入。为此,我们首先对文本进行标记化,将其从人类可读的文本转换为转换器可读的标记。
然后使用`get_text_features`方法将这些令牌输入模型。
这里我们有一个 512 维的向量来表示短语“雪中的狗”的语义含义。这是我们文本图像搜索的一半。
## 创建图像嵌入
下一步是创建图像嵌入。同样,这非常简单。我们将`tokenizer`换成`processor`,这将给我们一个叫做`pixel_values`的调整后的图像张量。
我们仍然可以看到处理过的图像。它已被调整大小,像素“激活”值不再在 Matplotlib 可以读取的 0–255 的典型 RGB 范围内,因此颜色无法正确显示。尽管如此,我们可以看到这是我们之前看到的同一台索尼收音机。
接下来,我们用 CLIP 处理这些输入,这次使用的是`get_image_features`方法。
在此基础上,我们用剪辑为文本和图像构建了矢量嵌入。有了这些嵌入,我们可以使用像欧几里德距离、余弦相似性或点积相似性这样的度量来比较它们的相似性。
然而,我们不能只拿一个例子来比较,所以让我们继续,在更大的图像样本上测试。
我们将从`imagenette`数据中随机抽取 *100 张*图像。为此,我们首先随机选择 100 个索引位置,并使用它们来构建图像列表。
现在我们遍历这 100 张图片,用 CLIP 创建图片嵌入。我们将它们全部添加到一个名为`image_arr`的 Numpy 数组中。
在底部的单元格中,我们可以看到我们的图像嵌入中的最小值和最大值分别是`-7.99`和`+3.15`。我们将使用*点积相似度*来比较我们的向量。如果我们想准确地将它们与点积进行比较,我们需要将它们归一化。我们这样做:
现在我们准备比较和搜索我们的向量。
## 文本图像搜索
如前所述,我们将使用点积来比较矢量。文本嵌入将充当“查询”,我们将使用它来搜索最相似的图像嵌入。
我们首先计算查询和图像之间的点积相似度:
这给了我们 100 分,例如,每个文本嵌入到图像嵌入对的一对多得分。我们所知道的是按照降序对这些分数进行排序,并返回各自得分最高的图像。
在 1 号位置,我们有一只狗在雪地里,这是一个很好的结果!这是*很可能是*在我们的 100 张图片样本中*唯一的*一张狗在雪中的图片。这就是我们如何使用 CLIP 执行文本图像搜索。
CLIP 是一个惊人的模型,可以以任何顺序或组合应用于语言图像领域。我们可以使用相同的方法执行文本-文本、图像-图像和图像-文本搜索。
事实上,我们可以通过简单地将图像和文本向量添加到一个存储中,然后使用图像或文本进行查询,来同时完成所有这些工作。
如果你坚持使用较少的搜索项目,我们的方法是很好的。然而,当我们开始搜索更多的记录时,这是缓慢的,甚至是不可能的。为此,我们需要一个[向量数据库](https://www.pinecone.io/learn/vector-database/)。允许我们将它扩展到数百万甚至数十亿条记录。
如果你有兴趣了解更多关于多模态模型、自然语言处理或矢量搜索的知识,可以查看我的 [YouTube 频道](https://www.youtube.com/c/jamesbriggs),联系 [Discord](https://discord.gg/c5QtDB9RAP) ,或者跟随我的一门免费课程(下面的链接)。
感谢阅读!
<https://www.pinecone.io/learn/nlp/> <https://www.pinecone.io/learn/image-search/>
# 将数据从 AWS RDS 移动到 S3 暂存区的快速入门
> 原文:<https://towardsdatascience.com/quick-start-for-moving-data-from-aws-rds-to-an-s3-staging-bucket-c60267ccf7a3>
## *利用 Python 和 Boto3 作为构建数据湖的第一步*

考特尼·摩尔在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上拍摄的照片
数据仓库架构中的一个常见元素是数据湖。数据湖是所有结构化和非结构化数据源的中央存储库。最近,我一直在使用雪花,一个流行的基于云的数据存储解决方案,作为一个数据湖和数据仓库。
获取源数据是构建数据湖的第一步。在本文中,我们将建立到 AWS RDS Postgres 实例的连接,并在将数据复制到 Snowflake 之前将数据传输到 S3 分段存储桶。本文利用 Python 和 Boto3 库来访问 AWS 资源。
## **要求**
1. 计算机编程语言
2. Boto3 库
3. Psycopg2 库
4. IO 库
5. AWS 帐户访问
6. AWS 帐户访问 ID 和秘密访问密钥
7. RDS Postgres 数据库和用户访问/凭证
8. S3 桶访问
# 入门指南
[Boto3](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html) 是 Python 的 AWS SDK。它提供了面向对象的 API 和对 AWS 服务的底层访问。为了建立到 RDS 的连接,我们可以利用`Boto3 Session`对象来生成一个`db_authentication_token`,我们将在稍后使用`psycopg2`连接到 Postgres 时使用它。
有了这个令牌(密码),一旦我们有了连接对象,就可以用`psycopg2`连接并查询我们的 RDS 实例。
有了这个连接对象,我们可以向 RDS 数据库发送查询来执行必要的提取。举个例子,
import pandas as pd
sql = f'''
select table_name
from information_schema.tables
where table_schema like 'public'
and table_type like 'base table'
'''
results = pd.read_sql(sql, conn)
df = pd.DataFrame(results)
使用同样的逻辑,我们可以遍历表名的结果,并从每个表中提取行上传到雪花。
# S3 暂存桶
一旦我们有了包含源数据的可用数据帧,我们就可以开始设置另一个 python 会话资源来用于 S3 分段存储桶。一旦分配了资源,我们就可以利用`io.StringIO`库将数据帧作为 csv 文件上传到 S3 存储桶。
[StringIO](https://docs.python.org/3/library/io.html?highlight=stringio#io.StringIO) 是一个文本流对象,当我们将 csv 写入 S3 存储桶时,它使用内存中的文本缓冲区来保存数据帧的内容。
`StringIO`的好处是它可以很好地与 Apache Airflow 这样的 orchestrator 一起工作,在上传文件之前,我们不需要担心将查询的输出编写为本地 csv 文件(这是另一种选择)。然而,我们也需要考虑这种选择的局限性。随着表大小的增加,有一个明显的内存限制,这将需要增量提取或多部分上传。这可以很容易地用 pandas 数据帧批处理来处理,从而减少内存负载。
这些数据现在可以在 S3 以 csv 格式获得!将数据放入雪花数据湖的下一步是利用 [S3 存储桶作为中转站点](https://docs.snowflake.com/en/user-guide/data-load-s3-create-stage.html),这样数据就可以直接加载到雪花中。期待在我的下一篇文章中更深入地探究这个过程。
概括地说,在本文中,我们探索了 Boto3 库,用 Python 建立了到 AWS 资源的会话和连接,并从 RDS 中提取数据,以`.csv`格式上传到 S3 桶。在我作为数据工程师的工作中,这是一种常见的做法。请伸出手来,与我联系,谈论更多关于这个或任何问题。
# 将 MongoDB 集合引入 Pandas 数据框架的快速入门
> 原文:<https://towardsdatascience.com/quick-start-from-mongodb-to-pandas-3f777dcbfb6e>
## 通过将 MongoDB 数据导入 Pandas,让您快速进行数据分析和模型构建

在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上 [Vince Gx](https://unsplash.com/@vincegx?utm_source=medium&utm_medium=referral) 拍摄的照片
**MongoDB** 属于一类被称为 [**NoSQL**](https://en.wikipedia.org/wiki/NoSQL) 数据库的数据库。NoSQL 数据库设计用于存储和检索数据,而不需要预定义的模式,这与关系数据库相反,关系数据库需要在存储数据之前定义一个模式。MongoDB 是一个 [**文档存储库**](https://en.wikipedia.org/wiki/Document-oriented_database) ,这意味着它在**类 JSON 文档**中存储数据。
我最近遇到了一个项目,我需要连接到 MongoDB 并执行数据分析。一种选择是请熟悉数据库的人将数据提取为 CSV 之类的格式,这种格式很容易放入 Python。然而,这不是获得最新数据的有效方法。我也知道 MongoDB 的持久性是类似 JSON 的格式,作为一名数据科学家,我更喜欢一个 **Pandas DataFrame** 。正如大多数数据科学从业者所知,一旦我们的数据处于数据帧中,我们就可以对该数据执行任何标准操作,从 [EDA](/exploratory-data-analysis-with-python-1b8ae98a61c5) ,到[特征工程](/6-tips-to-power-feature-engineering-in-your-next-machine-learning-project-d61530eee11b),到[模型选择](/demystify-machine-learning-model-selection-e3f913bab7e7)和[评估](/evaluating-ml-models-with-a-confusion-matrix-3fd9c3ab07dd)。
**注意:**更好的方法是通过像 [Fivetran](https://www.fivetran.com/connectors/mongodb) 这样的工具将数据提取到像[雪花](https://www.snowflake.com/en/)这样的数据仓库中。然而,这在项目的这一点上是不实际的;这是下一个最好的选择!
# 创建您的免费 Mongo 数据库帐户
例如,我们将使用一个免费的 MongoDB Atlas 帐户。Atlas 是 MongoDB 的云托管版本,它包括一个免费层,这使得它非常适合在开始一个更大的项目之前进行学习,或者给你一个实验的平台。让我们来完成创建免费帐户的步骤。
从 [MongoDB 主页](https://www.mongodb.com/),点击**免费试用**按钮,注册一个新账号。选择免费的**共享的**选项,这会让您从一个基本的托管数据库开始。

作者图片
接下来,继续使用**共享**选项,并选择您所在的地区和云托管提供商。我通常使用 AWS,但是对于这个例子,任何提供者都可以。

作者图片
最后,使用**集群层**设置,选择 **M0 沙箱**选项;免费选项将为您提供一个具有 512MB 存储空间的单节点数据库,对于我们的示例来说已经足够了。

作者图片
接下来,我们将进行配置。
# 配置访问
接下来,我们需要基本的配置细节,如用户、密码和 IP 地址信息。让我们从**用户名和密码**选项开始。创建一个用户,让 Mongo 为您指定 PW。它将使用一个不需要 URL 编码的(如果您愿意,我们将稍后处理)。

作者图片
接下来,我们应该只允许通过我们信任的 IP 地址连接到此。你可以通过网址 [WhatsMyIPAddress](https://whatismyipaddress.com) 找到你的 IP 地址。理想情况下,你应该有一个静态的 IP 地址,但首先,当你访问这个网站时,使用列出的 IP4 地址。如果你愿意,你可以随时添加更多的 IP 地址,甚至向全世界开放(不要这样做)。

作者图片
我们准备好获取一些样本数据了!
# 安装示例数据
MongoDB 提供了一种非常简单的方法来获取样本数据,允许我们探索数据库并学习如何与它交互。点击`...`按钮,选择**加载样本数据集**。

作者图片
你可以通过**连接**按钮了解更多关于连接 Mongo 的信息;但是,接下来我将带您浏览一下。一个很酷的探索是用于浏览服务器的 **VS Code** 扩展。写完这篇文章后,看看吧。

作者图片
# 安装 MongoDB Python 客户端
要使用 MongoDB Atlas,您需要使用`dnspython` python 安装 Python 驱动程序,您可以使用以下命令安装驱动程序:
python -m pip install 'pymongo[srv]'
**注意:**如果你之前已经安装了`pymongo`,试着先卸载它,然后运行上面的命令。
# Python 导入
像往常一样,让我们导入所有需要的库。我们有自己的标准`numpy`和`pandas`以及`os`来获取环境变量。我们还从`urllib`导入了`quote_plus`,这将有助于用户名和密码的编码。最后,我们有`pymongo`,MongoDB Python 驱动。
import pandas as pd
import numpy as np
from pymongo import MongoClient
import os
import pandas as pd
from urllib.parse import quote_plus
from pprint import pprint
# 连接到您的集群
我们首先要为我们的`host`、`username`和`password`将环境变量加载到内存中。处理敏感信息时,利用环境变量是最佳实践。搜索关于如何在您的操作系统中保存和持久化它们的教程。Mac、Linux 和 Windows 有不同的方法。但是,下面的代码可以让您轻松地将它们加载到内存中。
host = os.environ.get("MONGO_TEST_HOST")
username = os.environ.get("MONGO_TEST_USER")
password = os.environ.get("MONGO_TEST_PW")
接下来,根据 MongoDB 文档,我们需要确保连接字符串中的任何信息都经过正确的 [URL 编码](https://www.mongodb.com/docs/atlas/troubleshoot-connection/#special-characters-in-connection-string-password),比如用户名和密码,这可以通过`urllib`中的`quote_plus`函数轻松实现。让我们快速看一下一个示例密码以及它是如何编码的。
fake_pw = "p@ssw0rd'9'!"
print(quote_plus(fake_pw))
p%40ssw0rd%279%27%21
接下来,我们用下面的格式构造我们的**连接字符串**,并创建一个名为`client`的新的`MongoClient`对象。
uri = "mongodb+srv://%s:%s@%s" % (
quote_plus(username), quote_plus(password), host)
client = MongoClient(uri)
就是这样!让我们探索一下我们服务器上的**数据库**!
# 获取数据库列表
假设我们没有访问 MongoDB 控制台的权限(如果这不是我们的示例数据,我们可能没有),我们可以用下面的代码查询可用数据库列表:
client.list_database_names()
['sample_airbnb',
'sample_analytics',
'sample_geospatial',
'sample_guides',
'sample_mflix',
'sample_restaurants',
'sample_supplies',
'sample_training',
'sample_weatherdata',
'admin',
'local']
我们可以看到我们添加的所有不同的示例数据库。我们将在文章的其余部分使用`sample_analytics`。
# 获取数据库中的收藏列表
首先,我们需要如上所述指定我们想要连接的数据库或`sample_analytics`,然后我们利用之前创建的`client`对象连接到特定的`db`。我们可以通过简单打印`list_collection_names()`方法来查看数据库中的所有集合。
db = "sample_analytics"
mydb = client[db]
pprint(mydb.list_collection_names())
['accounts', 'transactions', 'customers']
如果我们希望看到集合中的单个记录,我们可以使用下面的代码来实现:
pprint(mycol.find_one())
{'_id': ObjectId('5ca4bbc7a2dd94ee5816238c'),
'account_id': 371138,
'limit': 9000,
'products': ['Derivatives', 'InvestmentStock']}
就是这样!我们现在连接到我们的 MongoDB Atlas 服务器,并可以开始查询数据。
# 将收藏保存到熊猫数据框
现在,到了你们期待已久的时候了,把你们的收藏放进**熊猫数据框**!很容易。我们将使用`.find`方法查询集合,使用`pd.DataFrame`方法将其转换为 DataFrame。
mycol = mydb["accounts"]
df_accounts = pd.DataFrame(list(mycol.find()))
现在我们有了一个数据框架,让我们取一个行的样本,看看它返回什么,以及一些基本的探索性的数据。
df_accounts.sample(5)
_id account_id limit
672 5ca4bbc7a2dd94ee5816262f 120270 10000
1371 5ca4bbc7a2dd94ee581628ea 680724 10000
1087 5ca4bbc7a2dd94ee581627ce 639934 10000
1360 5ca4bbc7a2dd94ee581628df 486521 10000
1217 5ca4bbc7a2dd94ee58162850 212579 10000
products
672 [InvestmentFund, Derivatives, InvestmentStock]
1371 [Brokerage, InvestmentStock]
1087 [CurrencyService, Derivatives, Commodity, Inve...
1360 [InvestmentStock, Commodity, CurrencyService]
1217 [InvestmentStock, CurrencyService, InvestmentF...
df_accounts.shape
(1746, 4)
df_accounts.limit.describe()
count 1746.000000
mean 9955.899198
std 354.750195
min 3000.000000
25% 10000.000000
50% 10000.000000
75% 10000.000000
max 10000.000000
Name: limit, dtype: float64
就是这样!现在我们有了一个数据框架,我们可以用数据做的事情是无限的。
和往常一样,本文中使用的代码可以在 [GitHub](https://github.com/broepke/MongoDB) 上获得。
# 结论
就是这样!我们创建了我们的免费层 MongoDB Atlas 集群,并将其配置为可以访问我们的环境。然后,我们向您展示了如何对您的服务器进行身份验证,以及如何查询服务器上的数据库。最后,我们向您展示了如何将收藏保存到熊猫数据帧中。通过这些基本步骤,您可以利用 Pandas 和 Python 的能力来分析数据,甚至构建任意数量的机器学习模型。虽然我们只是触及了 MongoDB 的皮毛,但是通过这些简单的步骤,您可以使用 MongoDB Atlas 快速启动您的数据科学项目。
如果你喜欢阅读这样的故事,并想支持我成为一名作家,考虑注册成为一名媒体成员。一个月 5 美元,让你可以无限制地访问成千上万篇文章。如果你使用 [*我的链接*](https://medium.com/@broepke/membership) *注册,我会在没有额外费用的情况下赚取一小笔佣金。*
# r 程序设计——初学者学习指南
> 原文:<https://towardsdatascience.com/r-programming-a-study-guide-for-beginners-732de9542aa8>
## 使用本指南来规划您的 R 编程学习之旅,并了解如何准备构建生产级 R 代码

照片由[弗雷迪婚姻](https://unsplash.com/@fredmarriage) @Unsplash.com 拍摄
D 在过去的几年里,我已经培训了数千名学生学习 R 编程,并与许多试图将 R 作为他们第一编程语言的人进行了交流。
作为一种开源语言,R 有如此多的特性,以至于在围绕它制定学习计划时会迷失方向。你从哪里开始?学习对象?学习模型?处理数据框架?这些概念之间的纠缠让一切变得更加复杂。
对于初学者来说,R 有优势。可以说,它比 Python 简单一点,因为大多数人学习这门语言是为了做以下三件事之一:
* 数据分析
* 数据科学
* 数据可视化
有了 Python,你可以拿起这门语言去做其他事情(更多地与软件工程联系在一起),比如后端开发或一些自动化项目——这使得这门语言比 r 更复杂一些。
这篇文章应该可以帮助你围绕 R 计划你的学习之旅。我在对我的绝对初学者课程的 [R 进行了大量迭代并结合了我的学生的大量反馈后,设计了这个学习流程(向他们致敬!).](https://www.udemy.com/course/r-for-absolute-beginners/?couponCode=IVOSTUDENTSJULY)
这是我推荐你学习的六大主题,依次是:
* r 基本对象
* r 数据帧
* 系统模型化
* 功能
* 图书馆
* 测绘
让我们更详细地了解一下吧!
# r 基本对象
R 对象是理解整个 R 语言的基础。基本的是:
* 向量
* 列表
* 数组
* 矩阵
在学习它们的过程中,你会接触到两个重要的特征,它们将决定你如何与它们互动,即:
* 单一类型与多类型对象
* 一维对象与多维对象。
为什么不立即跳到主 R 对象,即数据帧?因为它们是重要的操作,只有通过控制其他对象才可能实现——两个例子:
* 向量可以和`%in%`命令一起使用,在一个过滤器中子集化多个实例。
* 列表是 R 中唯一允许嵌套对象的对象。
R 的基本编程逻辑是围绕这些基本对象的。你应该在处理其他事情之前先研究它们,以增加成为一名优秀 R 开发人员的可能性。你可以在这里查看更多关于对象[的信息,或者直接进入我的](/5-r-objects-you-should-learn-to-master-r-programming-685341ce6661) [R 编程课程](https://www.udemy.com/course/r-for-absolute-beginners/?couponCode=IVOSTUDENTSJULY)的第一部分。
# 钉住数据框对象
如果您从事数据分析或科学工作,操作数据框将是最重要的技能。
**如果你习惯于用其他二维格式工作,比如 SQL 表,这个对象将是一个范式转变。**要掌握数据框架,您需要深入了解:
* 在 R 中索引行和列;
* 排序对象;
* 通过特定的键聚集;
* 过滤;
这些操作在为分析或建模构建数据时非常常见。如果您真正了解如何来回处理数据框,您将能够加快代码开发。
W3 学校包含一个关于他们的[不错的指南](https://www.w3schools.com/r/r_data_frames.asp)!
# 功能
**函数让你的代码变得可重用和干净。它们是正确的 R 脚本的支柱,没有它们,我们就不能在不同的对象上调用几个方法。**
**你知道你一启动 R 就和函数交互吗?**例如,当你使用`c()`创建一个 vector 时,你正在与一个名为`c`的函数交互,这个函数将你输入的对象组合成参数。不相信我?就在你的 R 控制台上呼叫`help(c)`!
没有功能,每个人都将编写枯燥重复的代码,无法维护和调试。
第一次构建自己的函数时,您可能会有点困惑,因为我们大多数人都习惯于编写脚本(特别是,如果您不是软件工程背景)。学习如何编写它们将会提高你的编码技能。使您准备好处理其他编程语言和编码范例。
你可以在我的 [R 编程课程](https://www.udemy.com/course/r-for-absolute-beginners/?couponCode=IVOSTUDENTSJULY)上了解更多,或者通过查看这个[博客上的一些最佳实践。](/writing-better-r-functions-best-practices-and-tips-d48ef0691c24)
# 走进图书馆
只有当你与别人的代码打交道时,你才是真正的 R 开发人员。怎么做呢?使用库!
库(或包)是使用 R 的主要优势(与其他非开源语言相比)。学习如何安装、加载和调试软件包的代码将让你接触到社区开发的数百万行代码。
你可以开始调查哪些图书馆?以下是一些建议:
* 训练决策树的内置`[rpart](https://cran.r-project.org/web/packages/rpart/rpart.pdf)`库。
* 一个非常酷的数据辩论库。
* `[ggplot2](https://ggplot2.tidyverse.org/)`r 国最著名的绘图库。你应该把它留到以后学习的时候。
当你觉得合适的时候,不妨去看看这篇[博文](/top-r-libraries-for-data-scientists-16f064151c04),看看一些你可以探索的图书馆的推荐。
# 系统模型化
当人们一头扎进 R 时,他们犯的主要错误是直接进入模特行业。
如果您在没有理解基本对象和功能的情况下从这里开始,您可能会有令人沮丧的经历。**为什么?**
**首先,您将无法很好地操作您的模型的输出**,因为多个模型需要不同的对象,甚至可能输出不同的格式。
其次,你将很难理解建模函数的参数是如何工作的。你不希望只在基础 R 中与建模捆绑在一起——你希望能够使用`caret`、`h2o`或其他独立库如`ranger`来训练你自己的高级模型。所有这些图书馆都有自己的琐碎和特色。它们都需要不同类型的参数、对象和细节。
**最后,每个模型本身都是一个函数,有自己的一组自变量和参数。**此外,有三件重要的事情你必须知道,以便与他们无缝合作:
* 如何操作函数。
* 参数期望什么类型的对象。
* 如何通过使用具有更快或更准确模型的外部库来改进您的培训过程。
当你准备好解决建模问题时,请随意进入我的 [R 数据科学训练营](https://www.udemy.com/course/r-for-data-science-first-step-data-scientist/?couponCode=IVOSTUDENTSJULY),在那里你将学习用 R 构建机器学习模型的理论和实践
# 测绘
这个列表的最后一个元素是绘图。有了`ggplot2`、`plotly`或`altair`,当谈到可视化库时,你有很多选择。它们都适合构建非常有趣的图表,可以讲述关于您的数据的故事。
成为数据可视化专家绝非易事。我提到的库有数百个参数和设置,人们可以通过调整来改进。我建议您从建立以下地块的基线开始:
* [一个简单的散点图。](https://r-graph-gallery.com/272-basic-scatterplot-with-ggplot2.html)
* [历史数据折线图。](http://www.sthda.com/english/wiki/ggplot2-line-plot-quick-start-guide-r-software-and-data-visualization)
* [一个方框图。](https://plotly.com/python/box-plots/)
* 饼图。
你可以在我上面详述的每个库中做其中的一个图。
理解它们的主要差异和复杂性将使您在围绕数据构建自己的故事时更加灵活。另一个重要的细节——我建议您跳过 base R plotting,因为与上面提到的任何包相比,它都非常有限。
就是这样!我希望你喜欢这篇文章,你可以更好地规划你的学习之旅。
做这样的学习流程帮助我培训了世界上成千上万想要学习 R 的人——当人们跟随这个旅程时,我看到了他们编码能力的重大突破。
**当然,这并不意味着你要在完全掌握了之前的概念之后,才可以跳到下一个概念。**首先,很好地掌握基础知识,巩固基础知识,在做了一些实践练习和构建了一些代码之后,您就可以开始下一个组件了。
重要的是,在进行下一项技能之前,你对每一项技能都感到满意。
如果你想去我的 R 课程,请随时加入这里( [R 编程绝对初学者](https://www.udemy.com/course/r-for-absolute-beginners/?couponCode=IVOSTUDENTSJULY))或这里([数据科学训练营](https://www.udemy.com/course/r-for-data-science-first-step-data-scientist/?couponCode=IVOSTUDENTSJULY))。我的课程大致遵循这种结构,我希望有你在身边!

[数据科学训练营:你成为数据科学家的第一步](https://www.udemy.com/course/r-for-data-science-first-step-data-scientist/?couponCode=IVOSTUDENTSJULY) —图片由作者提供
<https://ivopbernardo.medium.com/membership>
# r 闪亮的小玩意
> 原文:<https://towardsdatascience.com/r-shiny-gadgets-f9094ce21a47>
## 如何帮助您的 RStudio 使用交互式应用

作者图片
[Shiny](https://shiny.rstudio.com/) 是一个非常著名且广泛使用的 R 包,它让你可以用 R 开发交互式 web 应用,而不需要 HTML、CSS 或 JavaScript 编码。我最近自己发现,Shiny 有一个鲜为人知的功能,即所谓的闪亮小工具。这些小工具基本上是迷你应用程序,你可以直接从 RStudio 运行,例如在你的查看器面板中。
有趣的是,小玩意似乎在很大程度上被遗忘了。它们是在 5 年多前问世的,正如我们所知,这在数据科学领域是一个很大的进步。有一些 2016 年和 2017 年的教程,几个关于堆栈溢出的问题,就这些。
反正我开始在日常工作中使用一个闪亮的小玩意,我觉得值得分享。在这篇文章中,我们将学习什么是闪亮的小工具,然后仔细看看我创造的一个。如果你只是需要代码,你可以在[我的 GitHub](https://github.com/MatePocs/rshiny_apps/blob/main/gadgets/my_plot_gadget.R) 上找到。我假设读者通常熟悉闪亮的应用程序,如果你不熟悉,有大量的好教程,我不久前也写了一个一般的[闪亮教程](/how-to-build-a-data-analysis-app-in-r-shiny-143bee9338f7)。
# 什么是闪亮的小玩意?
闪亮的小工具是包装在一个函数中的迷你闪亮应用程序,可能有返回值。一旦 gadget 功能在您的环境中,您就可以像运行任何其他功能一样运行它。应用程序会弹出,你与它交互,当你关闭它时,你会得到返回值,你可以在你常规的 R 工作中使用。
小工具的构建与常规应用非常相似:它们有主要的`ui`和`server`功能,并使用相同的反应逻辑在前端和后端之间进行通信。
## 闪亮应用和小工具的区别
* 从技术上讲,小工具是一种特殊的应用程序;每个小工具都是应用,但不是每个应用都是小工具。也就是说,本文中的“应用”仅指非小工具应用。
* 小工具是编程过程的一部分,由 R 用户运行。另一方面,应用程序的主要目的是将结果或想法传达给更广泛的受众,他们不需要任何编程经验来与应用程序交互。
* gadget 在本地 R 会话中运行,从函数调用,而应用程序部署在 web 服务器上或嵌入 R Markdown 文档中。
## 小玩意的潜在用途
你可能会说,这很好,但是实际上在哪里可以使用这样的工具呢?毕竟,如果我们内联运行它,使用代码肯定会更快?一般来说,我会说是的,在绝大多数情况下,使用代码比在 UI 中点击更快,这可能是这个工具没有更受欢迎的主要原因之一。
> 然而,有一个我认为小工具真正闪耀的利基领域(双关语):利用图来过滤你的数据。
我不认为这是巧合,[小工具教程网站](https://shiny.rstudio.com/articles/gadgets.html)上的主要例子展示了这样一个应用程序。
为了完整起见,我还发现了其他一些可能有效的潜在用途:一个用于可视化的交互式颜色选择器,一个密码处理器(你输入你的密码,这样你就不必在代码中硬编码/从文件中导入),以及一个正则表达式测试器。也有一堆其他小工具的想法被抛出,如交互式模型生成器,但我个人并不认为这是一个非常有吸引力的解决方案。
# 我的小工具
现在让我们来看看我创建的小工具,实际上我发现它很有用。我的起点是[官方文档](https://shiny.rstudio.com/articles/gadgets.html),它显示了一个小工具,让你从一个矩形的图表中选择点,并返回你选择的点的列表。我采纳了这个想法,并根据需要添加了一些额外的功能。
## 描述
小工具功能:
* 将一个`data.frame`作为输入
* 创建散点图
* 用户可以选择 X 和 Y 变量,图形会动态更新
* 当用户选择绘图中的一个区域时(用闪亮的术语来说:刷它),区域中的点根据当前从下拉列表中选择的内容改变颜色,并且可以多次重复这个选择
* 一旦用户关闭应用程序,返回值是一个带有颜色代码的向量
## 我认为这个小工具有用的地方
* 标记异常值
* 将数据分成最多三组
* 检查数据点如何随着图上 X 和 Y 变量的变化而移动
## 限制
* 这个小工具只适用于足够小的数据,可以在散点图上可视化,最多几百行
# 代码
我的 [GitHub](https://github.com/MatePocs/rshiny_apps/blob/main/gadgets/my_plot_gadget.R) 上也有这个小工具,下面是全部内容:
# 如何运行小工具
要在 RStudio 中运行小工具:
* 在您的环境中导入函数
* 像这样运行函数:
`results <- my_plot_gadget(iris, “Petal.Length”, “Sepal.Width”)`
* 在查看器面板中与应用程序互动
* 点击完成按钮
* `results`矢量将包含你分配给不同点的颜色
# 守则的注释
以下是我在使用这个小工具时发现的一些有趣的事情。
## miniUI
正如你可能注意到的,我们装载了`miniUI`包,还有`shiny`。这个包帮助你创建一个在小屏幕空间工作的用户界面,例如,在 RStudio 的查看器面板中。详见 [UI 设计指南](https://shiny.rstudio.com/articles/gadget-ui.html)。
有趣的是,你可以在常规应用中使用`miniUI`元素,在小工具中使用常规闪亮的 UI 元素(如`fluidPage`、`sidebarLayout`等)。请注意,从查看器面板运行小工具并不是唯一的选择,您还可以做一个弹出窗口,在这里使用常规 UI 元素是完全可行的。
## 菲尔罗和菲尔科尔
除了使用像`miniContentPanel`这样的`miniUI`元素,设计指南还推荐使用大而闪亮的封装中的两个函数:`[fillRow](https://shiny.rstudio.com/reference/shiny/0.14/fillRow.html)`和`[fillCol](https://shiny.rstudio.com/reference/shiny/0.14/fillRow.html)`。做了相当多的实验才把这些做对。
以下代码:
fillRow(flex = c(NA, 1),…
意味着第一列将使用它所需要的空间,第二列将获得行中剩余的可用空间。但是,我还必须单独将第一列的宽度定义为 150 像素。您可能会认为,如果您手动定义宽度,您可以省略 flex 部分,但这不起作用。
更奇怪的是,我发现为了让右边的按钮对齐,我不得不插入以下内容:
fillCol(flex = c(NA, NA, NA),…
所以有 3 个元素,它们都应该使用尽可能多的空间。这不是默认设置,如果省略 flex 部分,按钮将分布在整个列中。
也有一些违反直觉的缺省值(至少对我来说是违反直觉的),比如必须为绘图定义高度= 100%。
## stopApp
这个函数停止应用程序的运行,它的参数定义了整个 gadget 函数的返回值。在 RStudio 中包含关闭小工具并返回到日常工作的路线非常重要。
从技术上来说,你也可以把这个放在一个普通的闪亮的应用程序中。然而,我们倾向于将闪亮的应用程序视为网页,我们不期望它们有一个关闭一切的按钮。`[gadgetTitleBar](https://rdrr.io/cran/miniUI/man/miniTitleBar.html)`函数为你创建了两个默认按钮,`cancel`和`done`,在`observeEvent`中编辑它们的行为很重要。如果你不把这个位放在代码里:
observeEvent(input$cancel, {
stopApp(NULL)
})
每次你点击`cancel`,你都会得到一个错误信息,这很快就变得很烦人。
## 电抗值与电抗
当我第一次使用 Shiny 时,我犯了一个错误,认为`[reactiveValues](https://shiny.rstudio.com/reference/shiny/0.11/reactiveValues.html)`只是一个`reactive`对象的列表,使用`reactiveValues`来跟踪一个对象是没有意义的。事实并非如此!
关键的区别在于您可以从多个来源改变`reactiveValues`中的对象。如果您有一个反应性元素,其行为就像一个跟踪某个状态的变量,那么使用`reactiveValues`可能是有意义的。更多详情参见本[论坛交流](https://stackoverflow.com/questions/39436713/r-shiny-reactivevalues-vs-reactive)和本[关于反应性的文档](https://shiny.rstudio.com/articles/reactivity-overview.html)。
在我的小工具中,我定义了一个跟踪颜色状态的向量,它被初始化为:
results <- reactiveValues(
colourcodes = rep(“none”, nrow(data))
)
稍后,当`main_plot_brush`事件发生时(因此用户改变了绘图上的矩形选择),它会相应地更新。我可以简单地在一个`observeEvent`中给`results$colourcodes`赋值。如果我定义了一个`reactive`对象并试图在其他地方更新它,同样的情况不会发生:
results_colourcodes <- reactive(rep(“none”, nrow(data)))
自然,使用一个常规的、非反应性的变量也不行,因为这样它就不能在`observeEvent`中更新。
## 清理刷子
一个有趣的用户体验决定。我最初把`resetBrush`函数放在`observeEvent`中,它监视画笔的更新。当画笔事件发生时,我更新了`results$colourcodes`状态向量,然后清除了画笔。然而,因为这都是在一个`observeEvent`中完成的,所以绘图更新直到完成后才开始,这导致了用户体验的轻微偏离:用户选择了矩形,它立即消失,然后在一个小但明显的延迟之后,绘图中的点得到了它们的颜色。
我最终创建了一个独立的`observeEvent`来监控`main_plot`的更新。这让流动感觉更加自然。
## 最后一个值
如果您忘记将函数结果分配给一个对象,您的结果将简单地打印在控制台上,就像其他函数一样。请记住,您可以使用`.Last.Value`来访问您在 R 中运行的最后一行的结果,并将其保存在一个对象中。在使用小工具的时候,我确实经历了很多。
<https://matepocs.medium.com/membership>
# 来源
<https://shiny.rstudio.com/articles/gadgets.html> <https://www.rstudio.com/resources/webinars/introducing-shiny-gadgets-interactive-tools/>
# R Shiny 要来 Python 了
> 原文:<https://towardsdatascience.com/r-shiny-is-coming-to-python-1653bbe231ac>

斯蒂芬·道森在 Unsplash[拍摄的照片](https://unsplash.com/s/photos/dashboard?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)
## Shiny 正在加入 Streamlit 和 Dash 等网络应用工具的行列
最近,RStudio 宣布该公司将更名为 Posit(意思是:提出一个想法或理论),以反映该公司将重点扩展到 r 以外的计划。他们的努力现在也将涵盖 Python 用户和与 Visual Studio 代码的集成。然而,R 用户放心了,公司不会从 R 转向 Python,RStudio IDE 也不会被重命名。该公司正在扩大其数据科学生态系统。
与此同时,Posit 宣布发布 Shiny for Python。我很确定 R Shiny 不需要介绍。但是万一它真的出现了,它是一个包/框架,允许数据科学家直接从 R 开始构建交互式 web 应用,而不需要任何 web 开发知识。
自 10 年前推出以来,R Shiny 在社区中被广泛采用。现在,我们也可以使用 Python 中的框架。Python 的[*Shiny*](https://shiny.rstudio.com/py/)目前处于 alpha 阶段,未来几个月可能会有很多变化。但是我们已经可以试一试,看看它是如何工作的。
在本文中,我们将看到如何用 Python 创建简单闪亮的应用程序。我们不会详细讨论 Shiny 实际上是如何工作的,因为在 Medium 上已经有很多关于这个主题的优秀教程了。让我们开始吧!
# 蟒蛇皮闪亮
和往常一样,我们需要从安装库开始。我们可以使用 pip 来实现:
pip install shiny
然后,我们需要创建一个目录,例如:
mkdir python_shiny_example
cd python_shiny_example
当我们在目录中时,我们运行以下命令:
shiny create .
它生成一个包含简单演示应用程序的样本文件`app.py`。以下代码片段包含该应用程序的代码:
要运行该应用程序,我们可以在终端中使用以下命令:
shiny run --reload app.py
幸运的是,Posit 通过提供一个 [VS 代码扩展](https://marketplace.visualstudio.com/items?itemName=rstudio.pyshiny),使得开发闪亮的应用程序变得更加简单。通过扩展,我们可以专注于开发应用程序,而无需离开 IDE。安装后,我们可以使用*播放*(或运行)按钮在 VS 代码中运行应用程序。下图展示了在 VS 代码中执行演示应用程序时的样子。

# 创建简单的应用程序
我们已经看到了演示应用程序,现在是时候建立自己的东西。我们将构建一个非常简单的股票价格跟踪器。我选择使用股票价格作为输入数据,因为我们可以很容易地即时下载数据(你可以在这里[了解更多信息](/a-comprehensive-guide-to-downloading-stock-prices-in-python-2cd93ff821d4))。在这样做的同时,我们可以展示我们的应用程序的不同类型的输入。
以下代码片段包含我们的应用程序代码:
简单评论一下与演示应用相比,实际上发生了哪些变化。首先,我们报道美国的变化。对于这个应用程序,我们使用一个页面布局——一个带有侧边栏的面板。在侧边栏中,我们要求用户输入两条信息:
* 感兴趣的股票的股票代号—我们使用一个下拉菜单,其中有两个可能的值,
* 我们要下载价格的日期范围。
在主面板中,我们创建了一个简单的收盘价随时间变化的图表。在这一部分,我们还可以打印下载的带有原始价格数据的数据帧等。
在服务器函数中,我们定义了一个助手函数,使用侧边栏中的输入下载所需的股票价格。然后,该函数使用`pandas`数据帧的`plot`方法创建一个简单的时间序列图。
结果如下所示:

自然,我们可以用 Shiny 构建无限复杂的应用程序。然而,通过这个例子,我想展示在几分钟内从头开始构建东西是多么简单。
# 思想
我真的很想看看 Shiny for Python 的下一步发展以及它的采用。目前,Python 最流行的 web 应用框架是 Streamlit 和 Dash。Shiny 能抓住部分用户群吗?时间会证明一切。
Posit 还提供了与他们的 Connect 平台的平滑集成,这使得部署闪亮的应用程序变得容易。这可能是一个很大的好处,将天平向用户广泛采用倾斜。
就我个人而言,我对它充满热情,因为我已经花了相当多的时间在 R 中构建闪亮的应用程序。我也看到 R Shiny 被推到了极限,以构建一个成熟的数据门户,用于非技术利益相关者的各种数据相关任务。构建这样一个工具需要相当多的定制,我不认为在 Streamlit 中构建如此复杂的东西是简单/可能的。
**趣闻**:在 *Shiny for Python* 的网站的[示例部分,我们可以找到一款流行浏览器游戏 Wordle 的克隆版。推荐你](https://shinylive.io/py/examples/)[去看看](https://shinylive.io/py/examples/#wordle),就为了看看 Shiny 能有多百搭。
# 外卖食品
* Posit(前 RStudio)最近发布了其闪亮包的 Python 版本,
* 使用 *Shiny for Python* ,数据从业者可以在没有 web 开发知识的情况下创建复杂的交互式 web 应用,
* 我们还可以使用 Shiny 的[在线环境](https://shinylive.io/py/examples/)来尝试构建简单的应用程序,而无需在本地安装任何东西,
* 这个库目前处于 alpha 阶段,所以在我们可以用它来构建生产就绪的应用程序之前,还需要一段时间。
您可以在我的 [GitHub](https://github.com/erykml/medium_articles/tree/master/python_shiny_example) 上找到本文使用的代码。很好奇听听大家对 Python 用户扩展 Shiny 有什么想法!请在评论中或者在 [Twitter](https://twitter.com/erykml1?source=post_page---------------------------) 上让我知道。
*喜欢这篇文章?成为一个媒介成员,通过无限制的阅读继续学习。如果您使用* [*这个链接*](https://eryk-lewinson.medium.com/membership) *成为会员,您将支持我,无需您额外付费。提前感谢,再见!*
您可能还会对以下内容感兴趣:
</pyscript-unleash-the-power-of-python-in-your-browser-6e0123c6dc3f> </three-approaches-to-feature-engineering-for-time-series-2123069567be> <https://medium.com/geekculture/investigating-the-effects-of-resampling-imbalanced-datasets-with-data-validation-techniques-f4ca3c8b2b94>
# 参考
* [https://shiny.rstudio.com/py/](https://shiny.rstudio.com/py/)
* [https://shinylive.io/py/examples/](https://shinylive.io/py/examples/)
* 【https://github.com/rstudio/py-shiny】
所有图片,除非特别注明,均为作者所有。
# 最常用的熊猫函数的 r 版本
> 原文:<https://towardsdatascience.com/r-versions-of-the-most-frequently-used-pandas-functions-f658cbcabaf7>
## 实用指南

[张杰](https://unsplash.com/@jay_zhang?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)在 [Unsplash](https://unsplash.com/s/photos/computer-keyboard?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上的照片
我使用熊猫已经 3 年了,最近我发表了一篇关于我使用最多的 [8 熊猫功能](https://medium.com/p/4e54f4db5656)的文章。我也经常使用 R,它是数据科学生态系统中的另一个关键角色。
r 是数据科学的主要语言之一,在数据分析、操作和建模方面非常高效。因此,我决定编写我最常用的熊猫函数的 R 版本。在某些情况下,就单个功能而言,不存在精确的匹配。
我们开始吧。
我准备了一个销售数据集样本,你可以从我的 GitHub 页面下载。我们将使用名为“sample-sales-data.csv”的文件。
## 1.从文件中读
fread 函数用于读取 csv 文件。就好比熊猫的 read_csv 功能。
library(data.table)sales <- fread("sample-sales-data.csv")head(sales)

销售(作者图片)
head 函数显示前 6 行。
就像 read_csv 函数一样,fread 函数有几个参数,我们可以使用这些参数使它更有用、功能更强。
例如,我们可以使用 select 参数选择只读取 csv 文件中的某些列。
sales <- fread("sample-sales-data.csv",
select = c("store","product","sales_qty"))head(sales)

销售(作者图片)
nrows 参数用于读取文件的前 n 行。熊猫的 read_csv 函数有一模一样的参数。
sales <- fread("sample-sales-data.csv", nrows=500)
dim(sales)# output
500 5--------------------------------------------------sales <- fread("sample-sales-data.csv")
dim(sales)# output
224229 5
dim 函数可用于检查表格的大小。它返回行数和列数。
## 2.普通
Pandas 具有 value_counts 函数,该函数返回一列中的唯一值以及它们出现的次数。数据表库没有针对此任务的特定功能,但是执行起来非常简单。
N 用于计数。我们可以使用它来复制 value_counts 函数,如下所示:
sales[, .N, store]# output
store N
1: 1 53971
2: 2 47918
3: 3 39156
4: 4 28091
5: 5 39898
6: 6 15195
我们所做的是按存储列对行进行分组,并计算每个不同存储值的行数,这基本上就是 value_counts 函数所做的事情。
## 3.更改数据类型
Pandas 的 astype 函数用于改变列的数据类型。我们给它一个字符串形式的新数据类型,然后它进行转换。
数据表库允许对不同的数据类型分别进行这种转换。例如,我们可以将 sales_date 列的数据类型转换为 date,如下所示:
sales[, sales_date := as.Date(sales_date)]
我们可以使用“as.double”函数将整数列转换为小数类型。
sales[, sales_qty := as.double(sales_qty)]
## 4.查找缺失值
缺失值现在是,将来也永远是数据科学家需要处理的问题的一部分。这是一个分两步走的任务。第一个是找到丢失的值,第二个是删除或替换它们。
isna 函数用于检测缺失值。这个函数的名字在熊猫中是一样的,但是我们使用它的方式是不同的。
我创建了一个非常简单的带有一些缺失值的雇员数据集。
employee <- fread("employee.csv")

客户(图片由作者提供)
它只有 5 行,因此我们可以直观地检查用“NA”表示的缺失值。然而,当我们处理大型数据集时,这种控制是不可能可视化的。我们需要一个更实际的方法。
我们能做的是计算每一列或每一行中缺失值的数量。根据缺失值的数量和数据集的特征,我们可以决定如何处理缺失值。
在用 isna 函数检测到丢失的值之后,colSums 和 rowSums 函数帮助我们对它们进行计数。
colSums(is.na(employee)) # number of missing values in each column# output
id name salary bonus continue
0 0 2 1 1----------------------------------------------rowSums(is.na(employee)) # number of missing values in each row# output
[1] 0 0 1 0 3
## 5.删除丢失的值
一旦我们找到丢失的值,我们需要决定如何处理它们。一种方法是丢弃丢失的值。
在数据表中,“complete.cases”函数允许删除缺少值的行。
employee[complete.cases(employee)]# output
id name salary bonus continue
1: 11001 John 75000 12000 1
2: 11102 Jane 72000 12000 0
3: 11107 Ashley 76000 8000 1
如果我们想要删除在特定列或一组列中缺少值的行,我们只需要指定列名。例如,我们可以删除 bonus 列中缺少值的行,如下所示:
employee[complete.cases(employee[, c("bonus")])]# output
id name salary bonus continue
1: 11001 John 75000 12000 1
2: 11102 Jane 72000 12000 0
3: 11005 Matt NA 10000 0
4: 11107 Ashley 76000 8000 1
我们还可以为缺失值的数量设置一个阈值。例如,我们可能希望删除至少有 3 个缺失值的行。
employee[!rowSums(is.na(employee))>=3]# output
id name salary bonus continue
1: 11001 John 75000 12000 1
2: 11102 Jane 72000 12000 0
3: 11005 Matt NA 10000 0
4: 11107 Ashley 76000 8000 1
“rowSums(is.na(employee))”部分计算每行中缺失值的数量。“!”符号意味着我们希望保留不满足给定条件的行。因此,我们不会保留(即删除)有 3 个或更多缺失值的行。
## 6.替换丢失的值
删除并不总是处理缺失值的最佳选择。考虑这样一种情况,我们有几列,其中只有一列缺少一些值。在这种情况下,我们不是删除一行,而是用适当的值替换缺失的值。
假设我们想用 5000 替换 bonus 列中缺少的值。我们是这样做的:
employee[is.na(bonus), bonus := 5000]
逗号之前的部分过滤 bonus 列中缺少值的行。逗号后面的部分将该值更新为 5000。
在某些情况下,最好的方法是用上一个或下一个值替换丢失的值。当我们有顺序数据时,它特别有用。我们可以通过使用 nafill 函数来实现。
下面的代码片段显示了如何用上一个和下一个值填充 salary 列中缺少的值。
fill with previous value
employee[, salary := nafill(salary, "locf")]--------------------------------------------
fill with next value
employee[, salary := nafill(salary, "nocb")]
## 7.分组依据
Pandas 中的 groupby 函数对于数据分析来说非常方便。它允许根据列中的不同值对行进行分组。然后,我们可以为每个组计算一个大范围的聚合。
数据表库没有 groupby 这样的特定功能,但它为执行此类任务提供了简单而直观的语法。
例如,在我们的销售数据集中,我们可以计算每个商店的总销售量,如下所示:
sales[, .(total_sales = sum(sales_qty)), by=store]# output
store total_sales
1: 1 140436
2: 2 89710
3: 3 71309
4: 4 45068
5: 5 69614
6: 6 23598
by 参数用于选择用于分组的列。数据表库可以用逗号分隔不同类型的操作。例如,如果我们需要传递一个条件来过滤行,我们把它放在方括号内的第一个逗号之前。
我们还可以在一个操作中执行多个聚合:
sales[, .(total_sales = sum(sales_qty),
avg_revenue = mean(sales_rev)),
by=store]# output
store total_sales avg_revenue
1: 1 140436 60.46818
2: 2 89710 34.31836
3: 3 71309 35.00804
4: 4 45068 26.56241
5: 5 69614 31.97003
6: 6 23598 27.22416
## 8.独一无二的
这两个功能非常相似,所以我想一起解释一下。
* unique 返回不同的值
* uniqueN 返回不同值的数量
unique(sales\(store)**# output**
[1] 1 2 3 4 5 6-------------------
uniqueN(sales\)store)# output
6
就像熊猫一样,数据表库有更多的功能来简化和加快数据科学家和分析师的工作。在本文中,我们已经介绍了最常用的 Pandas 函数的数据表版本。
*你可以成为* [*媒介会员*](https://sonery.medium.com/membership) *解锁我的全部写作权限,外加其余媒介。如果你已经是了,别忘了订阅**如果你想在我发表新文章时收到电子邮件。***
*<https://sonery.medium.com/membership>
感谢您的阅读。如果您有任何反馈,请告诉我。*
# 彩虹 DQN——最好的强化学习能提供什么?
> 原文:<https://towardsdatascience.com/rainbow-dqn-the-best-reinforcement-learning-has-to-offer-166cb8ed2f86>
## 如果深度 Q 学习中最成功的技术被组合到一个算法中,会发生什么?

彩虹 DQN 性能与其他 DQN 技术相比。这些结果好到不真实吗?【来源: [DeepMind 论文](https://arxiv.org/pdf/1710.02298.pdf)
自经典的[表格 Q 学习](https://medium.com/towards-data-science/walking-off-the-cliff-with-off-policy-reinforcement-learning-7fdbcdfe31ff)时代以来,强化学习已经走过了漫长的道路。**深度 Q 学习网络(DQN)** 推动了该领域的一场革命,实现了对状态的强大概括。
通过用强大的神经网络(理论上能够将任何输入状态转换为每个动作的输出值)取代 Q 表(存储所有状态-动作对的值),我们可以**处理大规模状态空间的问题**,这标志着该领域的突破。
然而,使用神经网络也带来了无数的问题。作为回应,大量的技术被用来提高 DQN 算法的性能。常见的技术包括[重放缓冲区、目标网络和熵奖励](/how-to-model-experience-replay-batch-learning-and-target-networks-c1350db93172)。通常,结合这些技术可以确保显著的性能改进。
> 如果一种技术可以如此显著地提高性能,为什么不使用所有的技术呢?
这正是 DeepMind 团队(他们 2017 年的论文有 10 位作者,所以我就称之为“团队”)肯定想知道的。如果你将所有这些单独的技术结合成一个单一的算法,你会得到一个梦之队还是科学怪人?
# 彩虹 DQN 中使用的技术
我们将暂时搁置性能问题,首先深入研究在彩虹 DQN 中部署和组合的六种技术。

[Harry Quan](https://unsplash.com/@mango_quan?utm_source=medium&utm_medium=referral) 在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的照片
## 一.双 Q 学习
Q-learning 以最大化状态-动作对的方式选择动作。然而,这带来了一个根本性的缺陷。因为 Q 值仅仅是近似值,所以该算法倾向于 Q 值被夸大的动作。这意味着我们系统地高估了 Q 值!自然,当试图找到最佳决策政策时,这种偏见没有帮助。更糟糕的是,Q-learning 是一种自举方法,即使在多次迭代后,高估效应可能仍然存在。
双 Q-learning 通过部署一个网络来选择动作,另一个网络来更新值,从而克服了这一缺陷。因此,即使我们使用一个膨胀的 Q 值来选择动作,我们也用一个(可能)不膨胀的 Q 值来更新。每次,我们随机选择一个网络进行操作选择,另一个网络进行更新。因此,我们将选择机制与评估机制分离开来。网络 A 的更新过程如下所示(显然,网络 B 的部署正好相反):

深度 Q 学习的更新机制。用一个网络选择最佳已知动作,用另一个网络确定其 Q 值。
从经验上看,双 Q 架构成功地减少了偏差,并导致更好的决策政策。
## 二。优先体验重放
收集观察数据通常成本很高。q-learning——作为一种非策略方法——允许将观察结果存储在重放缓冲区中,随后我们可以从该缓冲区中**采样经验来更新我们的策略**。此外,它打破了状态之间的相关性——由于后续状态通常表现出很强的相关性,神经网络往往会局部过度拟合。
然而,并不是每一次过去的经历都同样值得重温。为了最大化学习,我们可能希望对过去产生**高(绝对)值函数误差**的元组进行采样,我们希望再次评估这些经验并减少误差。

优先级采样方案,其中 p_i 表示体验的优先级(例如,其等级或绝对 TD 误差+一些噪声),指数α确定优先化的强度。
简单地选择误差最大的经验会引入抽样偏差。为了补救这一点,使用一种形式的**加权重要性采样**来执行权重更新:

加权重要性抽样方案,其中 N 是批量,β是比例参数。
优先体验重放被证明始终优于统一重放抽样。
## 三。决斗网络
深度 Q 网络通常返回 Q 值 *Q(s,a)* ,作为对应于给定状态-动作对 *(s,a)* 的值函数的近似。在最初的贝尔曼方程中,这非常有意义,因为计算最优价值函数会产生最优策略。在 Q-learning 中,状态值和动作值的**串联可能是有问题的**,因为可能很难区分一个值是主要源于状态、动作还是两者。
Q 值可以分解如下: *Q(s,a)=V(s)+A(s,a)* 。假设我们在最佳已知策略下操作(即,该策略根据其估计返回最佳行动),则**预期优势为 0** ,Q 值等于状态值。
特别是,对于一些州来说,动作选择并不那么重要,例如,考虑一辆无人驾驶汽车处于无障碍状态,优势值可以忽略不计。或者,再举一个例子,一个**状态可能是差的,不管我们在其中采取什么动作**。对于这样的状态,我们不希望通过探索所有的动作来浪费计算的努力。
决斗架构允许更**快速地确定好的动作**,因为差的动作可以更容易地被丢弃,可比较的动作可以更快地被识别。通过将优势价值从状态价值中分离出来,我们获得了关于行动价值的更细粒度的视角。

传统深度 Q 网络(上)和决斗深度 Q 网络(下)架构之间的比较。后者有单独的流来输出状态值和优势值,相加后返回 Q 值。[图改编自[王等,2016](https://arxiv.org/pdf/1511.06581v3.pdf) ]
该网络具有由 *θ* 参数化的层的联合集合、由 *α* 参数化的流以确定优势函数,以及由 *β* 参数化的流以确定价值函数。**两个流相加,返回 Q 值**。
为了确保价值函数和优势函数确实依赖于**单独参数化的流**,从所选动作的优势中减去所有动作的平均优势:

在决斗网络中,一个流(由β参数化)返回状态相关值函数,另一个流(由α参数化)返回优势函数。
实验结果表明,所提出的体系结构可以快速识别好的动作,因为识别动作值已经成为学习问题的显式部分。
## 四。多步学习
一项行动的影响并不总是直接可见的。许多个人行为甚至可能不会立即产生回报。例如,赛车比赛中的一个糟糕的转弯可能会导致稍后失去杆位。典型的 Q-learning 更新——或者更一般地说,TD(0)更新——只考虑直接跟随行动的回报,引导决策的下游值。
另一方面,蒙特卡罗/ TD(1)学习在更新 Q 值之前首先观察所有回报,但这通常导致高方差和慢收敛。多步学习—也称为*n*—Q 学习— **在偏差和方差**之间取得平衡,在执行更新之前观察多个时间步。例如,如果 *n=3* ,更新将是三个观察到的奖励和剩余时间范围的自举价值函数的组合。
在一般形式下,多步奖励可计算如下:

在多步学习中,我们跟踪 n 个时间步的回报,并使用一个 Q 值来估计之后的效果。这种方法经常在时间差异和蒙特卡罗学习之间找到平衡。
相应的更新方案定义如下:

多步 Q 学习的权重更新方案。
对于许多问题设置,多步学习比 TD(0)和 TD(1)学习产生了实质性的改进,**减轻了偏差和方差影响,并承认了决策的延迟影响**。主要的挑战是针对手头的问题适当地调整 *n* 。
## 动词 (verb 的缩写)分布式强化学习
大多数强化学习算法使用预期的返回值。相反,我们可以尝试学习收益的**分布**(Q 值代表期望值)。虽然传统上主要用于获得洞察力(例如,回报的风险),但这种学习方式似乎实际上也能提高绩效。
学习*分布*而不是*期望值*的主要用例是在随机环境中的应用。即使奖励是嘈杂的,并不直接有助于收敛期望值,我们仍然可以利用它们来更好地把握**潜在的奖励产生分布**。
鉴于典型的 Q 值由 *Q(s,a)=r(s,a)+γQ(s’,a’)*定义,分布变量定义如下:

奖励分配的定义,取代 Q 值。在分布 RL 中,我们的目标是学习整个分布,其期望值等于 Q 值。
这里, *Z* 表示收益的分布,而不是预期(Q-)值。另外, *R* 、*S’*和*A’*都是随机变量。
我们的目标不是减少 TD 误差,而是**最小化目标分布的采样** [**Kullback-Leibner 散度**](https://medium.com/towards-data-science/natural-policy-gradients-in-reinforcement-learning-explained-2265864cf43c) 。我们最大限度减少的损失如下:

分布 RL 的损失函数。我们构建了一个样本奖励分布,我们用它来更新预测的奖励分布。
这里,𝒯是收缩映射,*φ*是分布投影, *bar θ* 是表示目标网络的冻结参数副本。恐怕你真的需要整篇论文才能理解它。
也许更直观的是:网络输出一个离散化的概率质量函数,其中**每个输出(‘atom’)代表一个小回报区间**的概率。随着每次更新,我们试图使预测的奖励分布更接近观察到的。
分布式 RL 产生了很好的结果,尤其是在奖励稀少且嘈杂的问题环境中。
## 不及物动词嘈杂的勘探层
传统的 q 学习使用ε-贪婪学习,随机选择具有概率ϵ(通常为 0.05 或 0.10)的动作,而不是具有预期最大值的动作。这种机制允许通过确保探索来避免局部最优。熵奖励是另一种奖励偏离行为的常用技术。
在嘈杂的环境中,我们可能需要更多的探索。我们可以通过向我们的神经网络添加噪声探索层来实现这一点。我们用由确定性流和噪声流组成的**层替换标准线性层——以规范形式定义为 *y=Wx* ,其中 *x* 是输入, *W* 是权重集,而 *y* 是输出:**

神经网络中的噪声线性层。输出 y 取决于确定性流和噪声流。对应于两个流的权重随时间更新。
正如您所想象的,图层变换产生的噪声会影响最终的 Q 值,网络产生的最高 Q 值可能会有所不同,从而推动探索行为。随着时间的推移,算法可以**学会忽略噪声流**,因为*b _ noise*和*W _ noise*是可学习的参数。然而,信号和噪声之间的比率可以根据状态而变化,从而鼓励在必要时进行探索,并在适当时收敛到确定性动作。
噪声神经网络似乎是比例如ε贪婪或熵奖励更健壮的探索机制。与这里提到的其他技术一样,经验表明它可以提高许多基准问题的性能。
# 那么…有用吗?
DeepMind 团队将他们的彩虹算法(结合了上述六种技术)与 DQN 算法进行了测试,每次只使用一种技术。下图代表了 57 款 Atari 游戏的平均性能,100%表示人类水平的性能。

经验表现,平均超过 57 雅达利游戏。彩虹 DQN 在学习速度和最终表现方面都远远超过了单个 DQN 技术[来源: [DeepMind 论文](https://arxiv.org/pdf/1710.02298.pdf)
如图所示, **Rainbow DQN 明显优于所有使用单一组件 DQN 的基准测试**。同样有趣的是,它获得良好的性能比其他的要快得多。在大约 15m 帧后,它可以媲美类似人类的性能;有四个基准根本没有达到那个点。在 44/200 米帧之后,彩虹 DQN 已经处于持续超越所有竞争对手的水平,无论运行时间如何。在 2 亿帧之后,它是这场拍卖中无可争议的赢家。
DeepMind 团队还通过省略彩虹中的个别技术,检查了所有组件是否都是必要的。**有些技术比其他技术贡献更大**,但是——稍微简化一下论文的结论——每种技术都在性能方面给算法增加了一些东西。见下面的对比。

虽然不是每种技术都对彩虹 DQN 的性能有同等的贡献,但实验结果表明,每种技术都增加了价值【来源: [DeepMind 论文](https://arxiv.org/pdf/1710.02298.pdf)
总的来说,实验结果看起来绝对令人震惊,那么我们为什么不一直使用彩虹 DQN 呢?
# 弊端?
首先,由于部署了如此多的精细技术,**彩虹 DQN 速度很慢**。每种技术都需要一系列额外的计算,这些自然会累积起来。对于每一帧,该算法可以学习很多,但是处理一帧的时间要长得多。因此,与其他算法进行基于时间的比较不太有利。
彩虹 DQN 的第二个缺点是大量的调谐。多步骤过程中要采取的步骤数量、噪声层中的噪声量、目标网络的更新频率——一切都必须进行适当的调整。知道网格搜索爆炸的速度有多快,**在这样一个高度参数化的算法**中进行调整并不容易,尤其是考虑到算法的低速度。
最后,部分由于前面的几点,彩虹 DQN 可能不稳定并且**难以调试**。一项技术中的一个编码错误就可能毁掉整个算法——祝你好运找到错误。行为不符合预期?它可以是任何东西,甚至是各种技术之间的某种特殊的相互作用。
考虑到算法的困难,彩虹 DQN 目前**还没有包含在流行的 RL 库中**,因此不能轻易的使用。从某种意义上说,彩虹确实是 RL 技术的梦之队,但似乎胜利并不是一切。
# 结束语
正如我们所见, **Rainbow DQN 公司报告了其基准测试的令人印象深刻的结果**。然而,应该注意的是,原始论文仅将其与其他 DQN 技术进行比较。特别是,整个基于政策的[类](https://medium.com/towards-data-science/the-four-policy-classes-of-reinforcement-learning-38185daa6c8a)方法——包括流行的算法如 [TRPO](https://medium.com/towards-data-science/trust-region-policy-optimization-trpo-explained-4b56bd206fc2) 和[PPO](/proximal-policy-optimization-ppo-explained-abed1952457b)——都被排除在比较之外。
在 OpenAI 的一项研究中(虽然不是针对相同的问题),性能与 PPO 进行了比较,PPO 是当前连续控制中的 go-to 算法。通过标准实现,彩虹 DQN 轻而易举地击败了 PPO。然而,当部署联合训练机制时——本质上是转移学习,同时保持算法本身相同——**联合 PPO 以微弱优势击败联合彩虹 DQN** 。鉴于 PPO 比彩虹 DQN 更简单快捷,它更受欢迎也就不足为奇了。
最终,性能非常重要,但它不是 RL 算法的唯一相关方面。我们应该能够使用它,修改它,调试它,最重要的是,理解它。彩虹 DQN 证明了该领域的进步可以相互加强,该算法产生了**最先进的结果**,但最终没有说服人类用户全心全意地接受它。

照片由 [Paola Franco](https://unsplash.com/@peeeola?utm_source=medium&utm_medium=referral) 在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上拍摄
想更多地了解 DQN 吗?查看以下文章:
</deep-q-learning-for-the-cliff-walking-problem-b54835409046> </how-to-model-experience-replay-batch-learning-and-target-networks-c1350db93172> </a-minimal-working-example-for-deep-q-learning-in-tensorflow-2-0-e0ca8a944d5e>
# 参考
## 彩虹 DQN
**彩虹 DQN 论文:** Hessel,m .,Modayil,j .,Van Hasselt,h .,Schaul,t .,Ostrovski,g .,Dabney,w .,… & Silver,D. (2018,4 月)。[彩虹:结合深度强化学习的改进。](https://arxiv.org/pdf/1710.02298.pdf)在*第三十二届 AAAI 人工智能大会*。
**比较彩虹 DQN 和 PPO:** 尼科尔,a .,普法乌,v .,黑塞,c .,克里莫夫,o .,&舒尔曼,J. (2018)。[快速学习:学习力泛化的新基准。](https://s3-us-west-2.amazonaws.com/openai-assets/research-covers/retro-contest/gotta_learn_fast_report.pdf) *arXiv 预印本 arXiv:1804.03720* 。
## DQN 技术
**一、双 Q 学习:** Hasselt,H. (2010)。[双 Q-学习。](https://proceedings.neurips.cc/paper/2010/file/091d584fced301b442654dd8c23b3fc9-Paper.pdf) *神经信息处理系统的进展*、 *23* 。
**二。优先经验回放:** Schaul,t .、Quan,j .、Antonoglou,I .、& Silver,D. (2015)。[优先体验回放。](https://arxiv.org/pdf/1511.05952v4.pdf)arXiv 预印本 arXiv:1511.05952 。
**三。决斗网络:**王,z .,绍尔,t .,赫塞尔,m .,哈瑟尔特,h .,兰托特,m .,&弗雷塔斯,N. (2016,6 月)。[深度强化学习的决斗网络架构](https://arxiv.org/pdf/1511.06581v3.pdf)。在*机器学习国际会议*(第 1995–2003 页)。PMLR。
**四。多步学习:**萨顿,R. S .,&巴尔托,A. G. (2018)。 [*强化学习:入门*](http://www.incompleteideas.net/book/RLbook2020.pdf)*(7.1 节,n 步 TD 预测)*。麻省理工出版社。
**五、分布式学习:**贝勒马尔,M. G .,达布尼,w .,&穆诺斯,R. (2017,7 月)。[强化学习的分布式视角。](https://arxiv.org/pdf/1710.02298.pdf)在*机器学习国际会议上*(第 449–458 页)。PMLR。
六。嘈杂网络: Fortunato,m .,Azar,M. G .,Piot,b .,Menick,j .,Osband,I .,Graves,a .,… & Legg,S. (2017)。[探索嘈杂的网络。](https://arxiv.org/pdf/1706.10295.pdf?source=post_page---------------------------) *arXiv 预印本 arXiv:1706.10295* 。
# “提高”是沃尔多最好的开始词
> 原文:<https://towardsdatascience.com/raise-is-the-best-starting-word-for-wordle-e8f24c786427>
## 单词数据科学
## 基于解决方案的最短路径
我不经常玩 Wordle,但我对获得正确答案的科学背后很感兴趣。猜六次,看起来你应该能到达那里,但是要多快呢?我见过一些人根据单词和其他单词有多少相同的字母来分析单词,以找到最好的一个。我认为这些方法没有理解游戏规则。
所以我看着这个词,尽量减少使用规则找到答案的路径。

所有图片由作者提供
# 沃尔多规则
1. 这个单词必须是五个字母,并且来自一个列表。这个列表大约有 2315 个单词。
2. 你的猜测必须来自那个列表,所以你不能随意猜测。
3. 你有六次猜测的机会。
每次猜中后,字母都用**颜色编码**:
1. **灰色**:不在答案里
2. **黄色**:在回答但不正确的位置
3. **绿色**:正确的位置和字母
每个猜测消除单词:
1. 灰色:任何包含灰色字母的单词都被剔除
2. **黄色**:没有黄色字母的单词不存在
3. **绿色**:任何没有那个位置包含那个字母的单词都不存在
# 算法
我列出了每个单词,并假设它是所有其他单词的第一个猜测。然后我问“你的第二次猜测还剩下多少个单词的可能性?”
作为一个例子,我看了一个更短的列表,开始的单词是“back”这种情况下的答案是“abase”,使用三个标准,我可以出于各种原因抛出单词来减少列表。事实上,从 back 开始,整个列表只剩下两个选择来降低或减弱。

然后我做了同样的过程,将每个单词作为起始单词,与每个单词作为答案进行比较。因此,对于 2315 个起始单词,我重复了这个过程,每个单词有 2315 个解(2315 x 2315)。
一次迭代后,我到了第二关。假设你用了这个词,然后用了下一个词可能性,有多少种可能的第三种猜测?这两者都为下面表示的所有起始单词产生了所有的可能性。深蓝色意味着较少可能的第二个答案,而亮色意味着较多可能的第二个答案。

我对各列进行了求和,以便能够按照导致最少可能的第二次和第三次猜测的单词对单词进行排序。

第一猜和第二关之间没什么太大动静,就没再深究。单词“加注”是最好的第一个猜测,因为平均来说,第二个和第三个猜测的可能性最小。
我们甚至可以查看所有答案的可能性数量,大多数情况下,从“提高”开始会将搜索空间缩小到原始列表的 10%(小于 200 个可能的答案)。

我看了几个对其他人来说很常见的起始词,看看他们是如何基于最小化通往答案的路径这一目标进行排名的。有几个词不在这个单词列表中,比如 cones 和 adieu,但以下是这些词的排名:

我想知道人们什么时候会开始结合使用单词列表和以前使用的单词答案的查找。我也想知道一个单词格式的基于句子或短语的游戏会有多有趣。我猜沃尔多是五个字母的幸运之轮。
如果你愿意,可以在推特、 [YouTube](https://m.youtube.com/channel/UClgcmAtBMTmVVGANjtntXTw?source=post_page---------------------------) 和 [Instagram](https://www.instagram.com/espressofun/) 上关注我,我会在那里发布不同机器上的浓缩咖啡照片和浓缩咖啡相关的视频。你也可以在 [LinkedIn](https://www.linkedin.com/in/dr-robert-mckeon-aloe-01581595) 上找到我。也可以关注我在[中](https://towardsdatascience.com/@rmckeon/follow)和[订阅](https://rmckeon.medium.com/subscribe)。
# [我的进一步阅读](https://rmckeon.medium.com/story-collection-splash-page-e15025710347):
[浓缩咖啡系列文章](https://rmckeon.medium.com/a-collection-of-espresso-articles-de8a3abf9917?postPublishedType=repub)
[工作和学校故事集](https://rmckeon.medium.com/a-collection-of-work-and-school-stories-6b7ca5a58318?source=your_stories_page-------------------------------------)
[个人故事和关注点](https://rmckeon.medium.com/personal-stories-and-concerns-51bd8b3e63e6?source=your_stories_page-------------------------------------)
# rajini++超级编程语言
> 原文:<https://towardsdatascience.com/rajini-the-superstar-programming-language-db5187f2cc71>

介绍超级编程语言 rajini++!([来源](https://github.com/aadhithya/rajiniPP))
介绍 rajini++这是一种深奥的编程语言,基于超级巨星拉吉尼坎塔的对话。rajini++中使用的语法和关键字是基于超级巨星拉吉尼坎塔的对话。
rajini++中的 hello world 程序
# 谁是拉吉尼坎塔?
在我们开始之前,你可能想知道谁是超级巨星拉吉尼坎塔。嗯,首先,他是印度最大和最受欢迎的演员之一,在电影界已经超过 45 年了!要了解有关图例的更多信息,请点击以下链接:
* [拉吉尼坎塔](https://www.youtube.com/watch?v=YDUQZwMHMoo)是谁?
* [https://en.wikipedia.org/wiki/Rajinikanth](https://en.wikipedia.org/wiki/Rajinikanth)
# rajini++的起源
嗯,rajini++的起源要追溯到大约 6 年前,当时我第一次遇到这个叫做 ArnoldC 的 [esolang](https://en.wikipedia.org/wiki/Esoteric_programming_language) 。从那一刻起,我就想为这位超级巨星创造一个这样的 esolang。经过 6 年的学习,我们终于有了一个可用的 rajini++语言版本。
# 获取 rajini++文件
`rajinipp`包解释并执行 rajini++程序。它完全是用 python 编写的,可以使用 pip 和以下命令轻松安装:
* `pip install rajinipp`
**注意:**rajinipp 解释器要求 python ≥ 3.8。你可以在这里获得 python [。安装 rajinipp 包也需要 pip。你可以在这里得到 pip](https://www.python.org/downloads/)。
* 测试 rajinipp 安装:`rajinipp version`
# rajini++语言
rajini++基于拉吉尼坎塔最经典的对话。rajini++中使用的语法和关键字是基于超级巨星拉吉尼坎塔的对话。
rajini++的特性相当丰富,支持以下特性:
* 数学运算(SUM,SUB,MUL,DIV,MOD)
* 一元运算(SUM,SUB)
* 印刷
* 变量声明
* 可变访问
* 变量操作和赋值
* 数据类型(布尔值、字符串、数字)
* 逻辑运算(≥,>,≤,
* If statement
* If-Else statement
* For loop
* While loop
* Functions
* Functions with return
# Example Programs
1. **If-Else 程序**
示例 1: If-Else 程序
输出:
Example: If-Else Statement
x ( 5.0 ) is less than 10!
**2。FizzBuzz 程序**
示例 2: FizzBuzz 程序
输出:
1.0
2.0
Fizz
4.0
Buzz
Fizz
7.0
8.0
Fizz
Buzz
11.0
Fizz
13.0
14.0
FizzBuzz
**3。返回的函数**
示例 3:带有 return 语句的函数。
输出:
Hi from main!
Hello from myfunc_one!
returning ix = 100.0 to main
Value returned from myfunc_one: 100.0
# 运行 rajini++程序
rajini++程序存储在`.rpp`文件中。要运行 rajini++程序,只需运行以下命令:
`rajinipp run path/to/my_program.rpp`
**注意:**注意在 windows 中路径使用反斜杠!
# `rajinipp`互动外壳
除了能够运行 rajinip ++程序,rajinipp python 包还提供了一个交互式 shell( *experimental* )来测试 rajini++命令。可以使用以下命令进入 rajinipp shell:
`rajinipp shell`

拉吉尼普贝壳。
使用`ctrl+D`退出外壳。
**注意:**目前阶段的 rajinipp shell 仅支持 rajini++语言的有限功能子集。不支持控制语句(循环,if-else)和函数。
# **在 python 脚本中嵌入 rajini++代码**
因为 rajinipp 解释器完全是用 python 写的,所以可以在 python 脚本内部运行 rajini++代码(*实验*)!
示例 4:在 python 脚本中运行 rajini++代码!
输出:
Hello, World from python!
Hello world from rajini++!
Executing 5+5 in rajini++
5 + 5 = 10.0
**注意:**`rajinipp.runner.RppRunner.eval()`方法在当前阶段只支持 rajini++语言的有限的一部分特性。不支持控制语句(循环,if-else)和函数。
# 了解更多关于 rajini++的信息
* **学习 rajini++语言:**带有语法和示例的 rajini++语言文档可以在 [rajiniPP Wiki](https://github.com/aadhithya/rajiniPP/wiki/) 找到。
* **示例程序**可以在这里找到:[示例程序](https://github.com/aadhithya/rajiniPP/tree/master/examples)。
* **rajini ++语言规范:**rajini ++命令及其在 python3 中的等价物可以在这里找到[。语言规范还没有完成,是一项正在进行的工作。](https://github.com/aadhithya/rajiniPP/wiki/1.-rajiniPP:-Language-Specification)
* **rajinipp 解释器文档:**rajinipp 解释器的文档可以在[这里](https://github.com/aadhithya/rajiniPP/wiki/2.-rajinipp:-The-interpreter)找到。
**注意:**随着新特性不断添加到 rajinipp 存储库中,语言语法和文档也会发生变化。请参考[官方维基](https://github.com/aadhithya/rajiniPP/wiki/)了解最新变化。
# 审核代码,贡献给 rajini++吧
如果该项目引起了您的兴趣,并且如果您想对 github repo 的项目负责人进行审计或做出贡献,请:
<https://github.com/aadhithya/rajiniPP>
# PyPI 上的 rajinipp
rajinipp 在 PyPI 上,可以在这里找到:
<https://pypi.org/project/rajinipp/>
请继续关注下一篇文章,我会写关于如何在几分钟内完成 [rajiniPP 回购](https://github.com/aadhithya/rajiniPP)并创建你自己的 esolang!
# 一些标志性的拉吉尼坎塔对话
一些标志性的 rajinikanth 对话!
保持清醒!凯瑟姆凯瑟姆。
# 从零开始的随机森林和决策树—无需编码
> 原文:<https://towardsdatascience.com/random-forest-and-decision-trees-by-hand-no-coding-a209f2bbb1c9>

一棵粉红色的樱花树
# 介绍
在本文中,我们将讨论决策树和随机森林,这是机器学习中用于分类和回归任务的两种算法。
我将展示如何使用纸笔从头开始构建决策树,以及如何对其进行归纳并构建一个随机森林模型。
## 资料组
让我们用一个简单数据集来看看这在实践中是如何工作的。

数据集(作者提供的图片)
在这里,我们试图预测一个人是否有大学学位。我们有两个数字变量,那个人的年龄和工资(以千美元计)。
## 关于数据集的初始假设
当你查看一个新的数据集时,我总是建议你想出关于它的**假设**,看看它们是否是真的。
我个人在这里有两个假设:
* **高薪人士更有可能拥有大学文凭。我这么说是基于这样一个事实:很多高薪职业(医学、法律、科技、咨询、金融等)都需要大学学位。**
* 年轻人比他们的长辈更有可能上大学。我是基于皮尤研究中心的研究和个人经验(我经常听我周围的老年人说,他们觉得我这一代人比他们过去上大学多得多——现在,我知道这是真的)。
我快速计算了一些平均值,发现数据集中上过大学的人的平均年龄是 34 岁,而没有上过大学的人是 43 岁。关于工资,上过大学的人平均工资为 63K,相比之下,没上过大学的人平均工资为 37K。因此,我们的两个假设都是正确的。
这样做不是强制性的,但总是有用的,可以让您更好地理解您正在处理的数据集。
# 决策图表
现在,让我们看看如何在这个数据集上训练决策树并进行预测。

根节点(作者图片)
一开始,我们有 **6 是**和 **4 否**。这将成为我们的根节点。
在任何时候,我们**总是**能够访问真正的标签(也就是说,我们总是知道整个决策树中的一个例子是“是”还是“否”)。记住这一点,这对接下来的事情很重要。
在决策树的每个节点,我们可以预测其中一个类别。这里,我们既可以预测**是**也可以预测**否**。
* 如果我们预测**是**,我们将正确分类 10 个数据点中的 6 个数据点,错误分类 4 个数据点。因此,我们将有 4/10 = **0.4 的错误率**。
* 如果我们预测**没有**,我们将正确分类 10 个数据点中的 4 个数据点,错误分类 6 个数据点。因此,我们将有 6/10 = **0.6 的错误率**。
因此在这里,我们应该预测**是**以获得最低的错误率。
现在,让我们看看如何改进这一点。请记住,我们在这里仍然没有使用任何功能,只有数据集的真实标签。
## 分裂
现在的问题是,我们如何利用我们的特征来改善我们的预测,并做得比 0.4 的错误率更好。我们将为此使用一个阈值:如果某人的工资高于 X,那么我们预测是。如果他们的工资低于 X,我们预测是否定的。让我们看一个例子:

工资分割(图片由作者提供)
在这里,我们仍然有我们的根节点,但我们没有直接预测,而是首先以 32.5K 的拆分值拆分薪水(剧透:这是这里的最佳拆分),然后进行预测—同样,我们有真正的标签。我们的错误率现在是 0.2(我们只对右边方框中的两个黄色点进行了错误分类,它显示为 6 | 2 -我们预测这些点是,而我们应该预测不是)。
所以,有两个问题:为什么我们决定先按**薪水**分(而不是按**年龄**或其他特征,如果我们有更多的话)以及为什么我们按 **32.5K** 分而不是按另一个值分?
## 如何找到最佳阈值?

薪水从最小到最大排序,绿色表示是,黄色表示否(图片由作者提供)
在这里,我将工资从最小到最大排序。寻找给定特征的最佳阈值的想法如下:
* 对特性的值进行排序(如上)
* 取两点之间的中间值
* 像前面一样,将此作为阈值,并计算损耗
* 对所有中间值(即所有分割)进行此操作
* 为给定特征选择最佳分割
让我们看一个例子。

以 4.2 万英镑的薪水分割(图片由作者提供)
这里我们取 42 作为阈值,是 40 到 44 之间的中间值。这将我们的数字列表一分为二。
对于每一部分,我们可以预测是或不是。
* 如果我们在左边预测是,在右边预测不是(与我们在图片上所做的相反),我们将有 6 个错误= 0.6 错误率。
* 如果我们反过来做,如图所示,我们会得到 4 个错误= 0.4 个错误率。
所以我们这里能达到的最低错误率是 0.4(图片上是什么)。
现在的想法是计算所有可能的分割值的损失。让我们看看这是什么样子。

工资的所有可能分割(图片由作者提供)
在这里,我们有各种可能的方法来平分薪水。我们看到 32.5K 是分割的最佳值,因为它的错误率最低。
请记住,你是在 42 还是 42.005 上拆分并不重要,只要你在两个值之间选择一个阈值。我的意思是:

分割区域(作者图片)
在这里,您可以在红色区域中选取任何值,只要它大于 40 并且小于 44(因此 40.1 有效,43.9 也有效)。原因是这些值仍然会导致相同的错误率。因此,你选择哪一个并不重要。我们只是选择中点作为惯例。
## 决定分割哪个变量
现在,我们知道,在我们可以分割薪水的所有方式中,32.5K 的门槛是最好的分割方式。
现在,我们如何决定我们是应该在年龄和薪水上平分呢?简单。计算年龄的误差率,就像我们计算工资的误差率一样,从两者中选择最低的误差率,并将其作为我们的分割值。就这么办吧。

所有可能的根分裂的总结(图片由作者提供)
我们用**年龄**能达到的最低错误率是 **0.3** ,而用**工资**能达到的最低错误率是 **0.2** 。因此,我们在 **32.5K** 上对**工资**进行分割,并选择此作为我们的第一次分割。
现在,我们递归:只要我们在一个给定的节点中没有 0 错误或者没有特征,我们就继续分裂。这是我们的树目前的样子:

当前树(作者图片)
* 在**左侧**,您可以看到我们无法做得更好(我们已经将这 2 点完美分类)。因此,我们不需要进一步分裂。
* 在**右侧**,我们仍然可以在年龄上分裂,并有可能做得更好。
让我们计算一下年龄的所有可能的分裂。这里,我们有 8 个数据点,而不是 10 个,因为我们只处理树右侧的数据点。
我们像前面一样,首先对数据点进行排序。

对剩下的 8 个数据点进行排序(图片由作者提供)
现在,我们得到下面的分裂列表。

年龄差异(作者图片)
因此,我们在 **52.5** 的**年龄**上分开,如果年龄低于 52.5 则预测是,如果高于 52.5 则预测否。这给了我们下面的树:

该数据集的最终树(图片由作者提供)
我们得到最终树的错误率为 0.1,因为它只将 1 个点错误分类为是而不是否。我们不能进一步分割,因为我们没有可分割的特征。
## 分类特征
如果你有**分类**而不是**数值**特征,过程同上。您只需对现有的值进行拆分。
例如,如果你有一个**分类特征**来表示某人是住在农村还是住在城市,你就可以分开来看它的表现如何。然后,您将与其他功能进行比较。基本相同的过程。这就是为什么我首先讨论了数字特征,因为分类特征基本上是相同的,但是你已经有了分裂。
# 什么时候停止种树?
早些时候,我们没有其他可以拆分的功能(我们有两个功能,年龄和工资,并且拆分了两次)。因此,这个问题没有提出来。
**在实践中**,你会有更多的功能,你将能够分裂更多,让你的树长得更大,
正如我在以前的[文章](/lasso-and-ridge-regression-an-intuitive-comparison-3ee415487d18)中提到的,这里你将有一个**偏差-方差**的权衡。树越深,它就越适合数据集,但是过度适合的风险就越高。你的树将开始捕捉数据的噪音。因此,何时停止生长一棵树的问题出现了。
有两个主要的技巧:提前停止和修剪。
在**提前停止**,中,你基于给定的条件停止在树的给定节点上的分裂。以下是早期拆分条件的示例:
* 设置叶节点所需的最小样本数
* 设置树的最大深度
在**修剪**中,你只需像我们之前做的那样构建树,然后构建一个比你之前停止时更复杂的树。然后,根据给定的条件删除一些节点。
两者各有利弊,都在实践中使用。为此,我建议查看一下 [Scikit-learn](https://scikit-learn.org/stable/modules/tree.html) 文档。我所涉及的参数称为*修剪*、*最大深度*和*最小样本叶。*还有很多,我建议尽可能多地研究这些文档。
# 随机森林
现在,如何构建一个随机森林分类器?简单。
首先,创建一定数量的决策树。然后,从数据集中统一采样(**替换**),采样次数与数据集中的样本数相同。因此,如果您的数据集中有 100 个示例,您将从中抽取 100 个点作为样本。替换意味着一旦你采样了一个给定点,你就不能把它从数据集中去掉(**你基本上可以对同一个点采样两次**)。
这为我们提供了构建随机森林分类器的以下过程:
* 数据集中替换的样本
* 在这个子数据集上训练一个决策树
* 重复所需的树数
一旦你有了自己的决策树,你只需要进行多数投票。在我们之前的例子中,假设您已经训练了 100 棵决策树。对于一个给定的例子,如果 64 棵树预测是,36 棵树预测否,那么你会预测是。
# 更进一步
在这篇文章中,我停留在相对较高的层次,没有涉及一些事情,比如可以用于分割的不同度量,以及树如何解决回归问题。在 Scikit-learn 文档中还可以找到更多的参数。
然而,对于以前没有使用过这种算法的人来说,这应该是一个坚实的基础。我建议在一张纸上重新计算,看看你是否得到同样的结果。这确实有助于理解算法。
我最近写了一篇关于 [Ridge 和 Lasso](/lasso-and-ridge-regression-an-intuitive-comparison-3ee415487d18) 的文章,这是回归问题的两种正则化算法。虽然它们不能用于分类,但本文将帮助您理解偏差-方差权衡。这在决策树的上下文中很有用,可以理解为什么你应该在训练你的树或者修剪它们的时候早点停止。
*我希望你喜欢这个教程!让我知道你对它的看法。*
*随时连接上*[*LinkedIn*](https://www.linkedin.com/in/thomas-le-menestrel/)*和*[*GitHub*](https://github.com/tlemenestrel)*谈论更多关于数据科学和机器学习的话题并关注我上* [*中*](https://tlemenestrel.medium.com/) *!*
# 随机森林分类
> 原文:<https://towardsdatascience.com/random-forest-classification-678e551462f5>
## 7 分钟的背景信息和示例使用案例

图片由来自 Unsplash 的 David Kovalenko 提供
机器学习模型通常分为监督和非监督学习算法。当我们定义(标记)参数时,监督模型被创建,包括相关和独立参数。相反,当我们没有已定义(未标记)的参数时,使用无监督方法。在这篇文章中,我们将关注一个特定的监督模型,称为随机森林,并将展示一个关于泰坦尼克号幸存者数据的基本用例。
在进入随机森林模型的细节之前,重要的是定义决策树、集合模型和引导,它们对于理解随机森林模型是必不可少的。
**决策树**用于回归和分类问题。它们在视觉上像树一样流动,因此而得名,在分类的情况下,它们从树根开始,然后根据可变的结果进行二进制分割,直到到达一个叶节点,并给出最终的二进制结果。下面是决策树的一个示例:

作者图片
在这里,我们看到决策树从变量 1 开始,并根据特定标准进行拆分。当“是”时,决策树分类为真(真-假可以被视为任何二进制值,如 1-0,是-否)。当“否”时,决策树向下到下一个节点,并且重复该过程,直到决策树到达叶节点,并且决定结果。
**集成学习**是使用多个模型的过程,对相同的数据进行训练,平均每个模型的结果,最终找到更强大的预测/分类结果。
**Bootstrapping** 是在给定迭代次数和给定变量数的情况下随机采样数据集子集的过程。然后将这些结果平均在一起,以获得更有效的结果。自举是应用集合模型的一个例子。
bootstrapping**Random Forest**算法将集成学习方法与决策树框架相结合,从数据中创建多个随机绘制的决策树,对结果进行平均以输出一个结果,该结果通常会导致强预测/分类。
在这篇文章中,我将展示一个随机森林模型,它是由 Syed Hamza Ali 发布到 Kaggle 上的泰坦尼克号幸存者数据创建的,位于[这里](https://www.kaggle.com/hesh97/titanicdataset-traincsv),这些数据是 CC0 许可的——公共领域。该数据集提供了乘客的信息,如年龄、机票等级、性别,以及乘客是否幸存的二元变量。这些数据也可以用于 Kaggle Titanic ML 竞赛,因此本着保持竞赛公平的精神,我不会展示我进行 EDA &数据争论的所有步骤,也不会直接发布代码。相反,我将提到一些一般的概念和技巧,然后重点介绍随机森林模型。
**EDA &数据争论:** 进行 EDA 面临的挑战之一是数据缺失。当我们处理缺失数据值时,我们有几个选项,我们可以用固定值填充缺失值,如平均值、最小值、最大值。我们可以使用样本平均值、标准偏差和分布类型来生成值,以提供每个缺失值的估计值。第三种选择是删除丢失数据的行(我一般不推荐这种方法)。下面是其中一些选项的示例:
import pandas as pd
fill with mean
df.fillna(np.mean('column_name')# create normal distribution
np.random.normal(mean, standard_deviation, size= size_of_sample)
此外,即使数据类型是整数,也要如此对待分类变量,这一点很重要。这样做的一种常见方法是一键编码,下面是它的一个例子。
import pandas as pd
pd.get_dummies(df, columns=['list_of_column_names'])
最后,重要的是要考虑到一些变量在模型中可能没有用。决定这些变量可以通过一些方法来完成,比如通过你的经验和直觉进行规范化或判断。出于直觉删除变量时要小心,因为您可能会错误地删除对模型实际上很重要的变量。
**Train/Test Split:** 我们将使用 sklearn 模块进行大量分析,具体来说,在此阶段,我们将使用此包的 train_test_split 函数来创建单独的数据训练和测试集。对于一个完整的数据科学项目,我们还会执行交叉验证,并选择具有最佳结果的选项。然而,为了简单起见,我没有在本文中讨论交叉验证,将在后面的文章中讨论交叉验证和网格搜索。运行 train_test_split 的代码如下:
x_train, x_test, y_train, y_test = train_test_split(X, y, test_size = .25, random_state = 18)
传递给我们的 train_test_split 函数的参数是“X ”,它包含我们的数据集变量,而不是我们的结果变量,“y”是 X 中每个观察值的数组或结果变量。test_size 参数决定测试数据集将保存的数据部分。在这种情况下,我选择了 0.25 或 25%。random_state 参数只是确定数据的具体分割,以便您稍后可以复制您的结果。使用这个函数后,我们现在有了训练和测试数据集,可以用于模型训练和测试。
**随机森林模型:** 我们将继续使用 sklearn 模块来训练我们的随机森林模型,特别是 RandomForestClassifier 函数。RandomForestClassifier 文档显示了我们可以为模型选择的许多不同的参数。下面重点介绍了一些重要参数:
* n_estimators —您将在模型中运行的决策树的数量
* max _ depth 设置每棵树的最大可能深度
* max _ features 确定分割时模型将考虑的最大特征数
* bootstrapping —默认值为 True,这意味着模型遵循 bootstrapping 原则(前面已定义)。
* max _ samples 此参数假设 bootstrapping 设置为 True,否则,此参数不适用。在 True 的情况下,该值设置每棵树的每个样本的最大大小。
* 其他重要的参数是 criterion、min_samples_split、min_samples_leaf、class_weights、n_jobs 和其他可以在 sklearn 的 RandomForestClassifier 文档中读取的参数[这里](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html)。
出于本文的目的,我将选择这些参数的基本值,而不进行任何重大的微调,以观察该算法的总体性能。使用的培训代码如下:
clf = RandomForestClassifier(n_estimators = 500, max_depth = 4, max_features = 3, bootstrap = True, random_state = 18).fit(x_train, y_train)
我选择的参数值是 n_estimators = 500,意味着为这个模型运行 500 棵树;max_depth = 4,因此每棵树的最大可能深度被设置为 4;max_features = 3,因此每棵树最多只能选择 3 个特征;bootstrap = True 同样,这是默认设置,但我想包含它来重申 bootstrap 如何应用于随机森林模型;最后,随机状态= 18。
我想再次强调,这些值是经过最小的微调和优化而选择的。本文的目标是演示随机森林分类模型,而不是获得最佳结果(尽管该模型的性能相对较好,我们很快就会看到)。在以后的文章中,我将深入研究优化方法,并通过网格搜索找到一个更优的解决方案。
为了测试训练好的模型,我们可以使用内部的。predict '函数,将我们的测试数据集作为参数传递。我们也可以使用下面的度量来看看我们的测试效果如何。
Create our predictions
prediction = clf.predict(x_test) # Create confusion matrix
from sklearn.metrics import confusion_matrix, f1_score, accuracy_scoreconfusion_matrix(y_test, prediction)# Display accuracy score
accuracy_score(y_test, prediction)# Display F1 score
f1_score(y_test,prediction)

作者图片
我们的模型提供了 86.1%的精确度和 80.25%的 F1 分数。
准确度是以(TP + TN)/(所有情况)的总数来衡量的,而 F1 分数是以 2*(精度*召回)/(精度+召回)来计算的,精度= TP/(TP+FP),召回= TP/(TP+FN)。
通常,准确性不是我们用来判断分类模型性能的指标,原因是数据中可能存在的不平衡会导致高准确性,因为对一个类别的预测不平衡。然而,为了简单起见,我把它包括在上面。我还加入了 F1 分数,它衡量了精确度和召回率之间的调和平均值。F1 得分度量能够惩罚精度之间的巨大差异。一般来说,我们更喜欢通过分类的精确度、召回率或 F1 分数来确定分类的性能。
**结论:** 本文的目的是介绍随机森林模型,描述 sklearn 的一些文档,并提供一个关于实际数据的模型示例。使用随机森林分类,我们得到了 86.1%的准确率和 80.25%的 F1 值。这些测试是使用正常的训练/测试分割进行的,没有太多的参数调整。在以后的测试中,我们将在训练阶段考虑交叉验证和网格搜索,以找到一个性能更好的模型。
感谢您花时间阅读这篇文章!我希望你喜欢阅读,并了解了更多关于随机森林分类的知识。如前所述,我将继续撰写文章更新这里部署的方法,以及其他方法和数据科学相关的主题。
# 用于测井数据岩性分类的随机森林
> 原文:<https://towardsdatascience.com/random-forest-for-lithology-classification-from-well-log-data-4a1380ef025b>
## 机器学习|岩石物理学
## 用机器学习对地下进行分类

奈杰科希尔的照片:[https://www.pexels.com/photo/green-leafed-tree-338936/](https://www.pexels.com/photo/green-leafed-tree-338936/)
随机森林是一种非常流行的机器学习算法,可用于分类(我们试图预测类别或类)和回归(我们试图预测连续变量)。
在岩石物理学和地球科学中,我们可以使用这种算法根据测井测量预测岩性,或者根据岩心测量预测连续测井曲线,例如连续渗透率。
在本文中,我们将重点关注随机森林在测井测量岩性预测中的应用。但首先,让我们看看随机森林是如何工作的。
# 随机森林分类简介
在很高的层次上,随机森林本质上是决策树的集合。这些都是非常简单和直观的理解,我们经常在日常生活中使用它们,即使我们可能没有意识到。
在这个简单的例子中,我们在散点图上有一系列双色(红色和橙色)的数据点。每个彩色系列代表一个不同的类别。

一个简单的数据集被分成两类:橙色和蓝色。图片由作者提供。
我们首先决定如何最好地分割我们的数据,并尝试分离类。这里我们选择 x ≤ 3 的值。

我们决策树中的第一个决策。散点图上的灰线代表我们的决策值 3。然后,我们对此应用一个条件,并检查点数是否小于或等于 3。图片由作者提供。
我们可以看到,当我们这样做了,我们结束了两个橙色的点在左边,蓝色和橙色的混合点在右边。
我们可以根据许多其他条件继续分割数据,直到我们成功地分离出不同的类。

经过多次决策,我们终于有了一个完整的决策树,可以用来进行预测。图片由作者提供。
如上所示,橙色点已经成功地从蓝色点中分离出来,我们有了最终的决策树。这现在形成了新数据的蓝图,新数据将遵循正确的路径,直到到达最终节点。
现在,这个例子非常简单,我们只使用了二维空间,我们可以很容易地分离出类。然而,真实世界的数据可能是杂乱的,我们可能不会以纯粹的叶节点结束。我们可能有包含这两个类的节点,为了确定该节点的最终结果,我们可以使用多数投票的过程。这将在以后的文章中讨论。
我们所做的是创建一个决策树。为了建立我们的森林,我们需要组合多个决策树。顾名思义,我们需要随机化它的一些部分。
我们首先获取数据,并对其进行随机采样,这为我们提供了完整训练数据集的子集。这被称为**引导**我们的数据。
我们还可以为每棵树随机选择不同的输入特征。
这样会形成不同形状的树。

将多个决策树聚合在一起,以进行更可靠的预测。图片由作者提供。
然后,我们可以将新数据传递给这些树,这些数据将经过每个决策,并在最终节点结束,然后给出一个结果。
我们可能不会从所有的树中得到相同的结果,所以我们可以采取多数投票,换句话说,什么类被预测得最多,然后使用它作为我们的最终预测。
组合多棵树的结果是一种强大的技术,可以为我们提供比单棵树更好的答案。这个过程被称为**装袋**。
既然我们对随机森林的作用有了基本的直觉,现在我们可以看看如何将它应用于一些测井数据进行岩性预测。
# 本教程中使用的数据
我们在本教程中使用的数据集是一个训练数据集的子集,该数据集用作 Xeek 和 FORCE 2020 *(Bormann et al .,2020)* 举办的机器学习竞赛的一部分。它是在挪威政府的 NOLD 2.0 许可下发布的,详细信息可以在这里找到:[挪威开放政府数据许可(NLOD) 2.0](https://data.norge.no/nlod/en/2.0/) 。
完整的数据集可通过以下链接获得:[https://doi.org/10.5281/zenodo.4351155](https://doi.org/10.5281/zenodo.4351155)。
竞赛的目的是利用测井测量从现有的标记数据预测岩性。完整的数据集包括来自挪威海的 118 口井。
# 本教程的视频版本
我还在我的 YouTube 频道上发布了这个教程的视频版本,它用另一个数据例子进行了更详细的描述。
# 导入库和数据
我们项目的第一步是导入我们将要使用的库。在本例中,我们将结合使用 [pandas](http://pandas.pydata.org) 来加载和存储我们的数据,结合使用 [matplotlib](https://matplotlib.org) 和 [seaborn](https://seaborn.pydata.org) 来可视化我们的数据,并结合使用 [missingno](https://github.com/ResidentMario/missingno) 来分析哪里可能有缺失值。
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import missingno as mno
接下来,我们将使用`pd.read_csv()`从 CSV 文件导入数据。
df = pd.read_csv('Data/Xeek_train_subset_clean.csv')
一旦数据被加载,我们可以检查我们的数据,看看我们有什么功能。
使用`describe()`方法将允许我们查看数据集中数字特征的统计数据。我们很快会谈到分类特征。
df.describe()

Xeek 测井子集的内容。图片由作者提供。
我们得到的摘要为我们提供了数据的一般统计信息,包括最小值、最大值、百分位数等等。
我们还可以使用`.info()`方法来检查数据集中有多少非空值。
df.info()

对测井数据集使用 df.info()的结果。图片由作者提供。
## 快速查看岩性变量
在本教程中,我们将使用名为`LITH`的特征,这是我们的地质岩性,用于训练和预测。
让我们首先检查一下在`LITH`列中有多少唯一值。
df['LITH'].nunique()
它返回:
11
接下来,我们将检查每个唯一值是什么:
df['LITH'].unique()
它返回以下岩性列表:
array(['Shale', 'Sandstone', 'Sandstone/Shale', 'Limestone', 'Tuff',
'Marl', 'Anhydrite', 'Dolomite', 'Chalk', 'Coal', 'Halite'],
dtype=object)
最后,我们可以使用`value_counts()`方法收集每个岩性的数量。
df['LITH'].value_counts()

数据中的岩性计数。图片由作者提供。
我们可以看到,我们的数据集是不平衡的,在页岩、石灰岩和砂岩岩性中有更多的点。这是在进行预测时需要考虑的事情,但不在本文讨论范围之内。
## 可视化按岩性划分的数据
由于我们的数据集中只有少量岩性,我们可以使用 Seaborn 的 FacetGrid 图来可视化两个变量的值分布:`NPHI`和`RHOB`。这两个特性通常用于岩石物理学,但所有其他列都可以替代使用。
g = sns.FacetGrid(df, col='LITH', col_wrap=4)
g.map(sns.scatterplot, 'NPHI', 'RHOB', alpha=0.5)
g.set(xlim=(-0.15, 1))
g.set(ylim=(3, 1))

FacetGrid 显示了密度(RHOB) —中子孔隙度(NPHI)数据中的岩性分布。
从上面可以看出,一些岩性具有较大的分布,如页岩数据,而硬石膏和石盐具有更明显的分布。
# 处理缺失值
在探索了我们的数据之后,下一步是处理我们缺失的值。可视化我们丢失的值的一个很好的方法是使用 [missingno](https://github.com/ResidentMario/missingno) 库。
在本教程中,我们将只使用条形图,但理想情况下,您应该查看所有图表,尝试并了解哪里以及为什么会有缺失值。
我们可以像这样调用条形图。
mno.bar(df)
这将返回下面的图表。我们可以看到,与其他列(例如 NPHI)相比,一些列的值的数量(嗯,DEPTH_MD)更大。

缺失没有显示数据集中缺失值总数的条形图。图片由作者提供。
为了简单起见,我们将简单地使用列表删除来删除任何列中缺少值的行。还有更高级的方法,但是这个适合这个例子。
我们可以使用`df.dropna()`删除包含缺失值的行,如果我们设置参数`inplace`等于`True`,那么我们将删除原始数据帧中的行。
df.dropna(inplace=True)
mno.bar(df)

缺失使用 pandas 的 dropna()方法删除缺失值后没有条形图。图片由作者提供。
如上所示,我们已经将总价值计数从 133,196 减少到 81,586,这是一个显著的减少。
# 构建随机森林模型
既然我们已经研究了数据并移除了缺失值,现在我们可以继续构建我们的随机森林分类模型。为此,我们需要从 [Scikit-learn](https://scikit-learn.org/stable/) 库中导入一些模块。
from sklearn.model_selection import train_test_split
from sklearn import metrics
from sklearn.metrics import classification_report, confusion_matrix
from sklearn.ensemble import RandomForestClassifier
接下来,我们需要将我们的数据分成哪些变量将用于训练模型(X)和我们的目标变量`LITH` (y)。
X = df[['RDEP', 'RHOB', 'GR', 'NPHI', 'PEF', 'DTC']]
y = df['LITH']
与任何机器学习模型一样,我们需要一个用来训练模型的数据集和一个用来测试和验证我们的模型是否有效的数据集。这可以通过使用`train_test_split()`功能轻松实现。
我们传入 X 和 y 变量,以及表示我们想要多大的测试数据集的参数。这是作为十进制值输入的,范围在 0 和 1 之间。在这种情况下,我们使用 0.3,这意味着我们的测试数据集将是原始数据的 30%,我们的训练数据集将是原始数据的 70%。
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
设置我们的分类器非常简单,我们创建一个名为`clf`的变量,并将其赋给`RandomForestClassifier`。我们也可以为我们的分类器传入参数,但是在这个例子中,我们将保持简单并使用默认值。
你可以在这里找到更多相关信息。
clf=RandomForestClassifier()
一旦模型被初始化,我们就可以通过调用`fit`方法并传入`X_train`和`y_train`将它应用于我们的训练数据。
clf.fit(X_train,y_train)
# 利用随机森林分类器预测岩性
在模型建立之后,我们可以对我们的测试数据进行预测,并评估它的表现如何。这是通过调用`predict`方法并传入我们的测试数据集特性`X_test`来完成的。
y_pred=clf.predict(X_test)
一旦模型被用于预测,我们就可以开始查看一些关键指标。第一个是准确性,即每类的正确预测数除以预测总数。
accuracy=metrics.accuracy_score(y_test, y_pred)
accuracy
它返回:
0.91203628043798
这是一个很好的值,但是,它不应该完全单独使用,因为它可能会对模型的实际性能产生错误的感觉。
为了获得更好的见解,我们可以生成一个分类报告,如下所示。
clf_rpt = classification_report(y_test, y_pred)
print(clf_rpt)

scikit-learn 的分类报告。图片由作者提供。
**precision:** 提供了该类中有多少值被正确预测的指示。值介于 0.0 和 1.0 之间,1 表示最好,0 表示最差。
**recall:** 提供了分类器能够找到该类的所有阳性案例的程度的度量。
**f1-score:** 精度和召回率的加权调和平均值,生成 1.0(好)和 0.0(差)之间的值。
**support:** 这是数据集中该类的实例总数。
## 混淆矩阵
另一种观察我们的模型表现如何的方法是使用混淆矩阵。这用于根据提供的标签的实际值绘制每个特征的预测结果(`LITH`)。
cf_matrix = confusion_matrix(y_test, y_pred)
cf_matrix

scikit-learn 生成的混淆矩阵的原始示例。图片由作者提供。
我们可以使用 seaborn 和热图绘制一个漂亮且易于理解的图表,而不是使用数字来可视化我们的混淆矩阵。首先,我们必须设置希望出现在 x 轴和 y 轴上的标签。
labels = ['Shale', 'Sandstone', 'Sandstone/Shale', 'Limestone', 'Tuff',
'Marl', 'Anhydrite', 'Dolomite', 'Chalk', 'Coal', 'Halite']
labels.sort()
然后我们可以创建我们的图形,并添加一些标题和轴标签。
fig = plt.figure(figsize=(10,10))
ax = sns.heatmap(cf_matrix, annot=True, cmap='Reds', fmt='.0f',
xticklabels=labels,
yticklabels = labels)ax.set_title('Seaborn Confusion Matrix with labels\n\n');
ax.set_xlabel('\nPredicted Values')
ax.set_ylabel('Actual Values ');

我们的分类结果的混淆矩阵是使用 Seaborn 热图生成的。图片由作者提供。
使用我们混乱矩阵的可视化比看简单的数字要好得多。我们可以看到,数值越高,红色越深。因此,我们可以立即看到,我们的大多数数据点都属于页岩类别,这也表明这些值预测正确。
# 摘要
随机森林是一种功能强大、易于理解、易于实现的机器学习方法。如上所述,它在岩性预测的第一次通过中提供了非常好的结果,但是,我们仍然需要在看不见的数据上验证我们的模型并调整参数。
*感谢阅读。在你走之前,你一定要订阅我的内容,把我的文章放到你的收件箱里。* [***你可以在这里做!***](https://andymcdonaldgeo.medium.com/subscribe)**或者,您也可以* [***注册我的简讯***](https://fabulous-founder-2965.ck.page/2ca286e572) *免费将更多内容直接发送到您的收件箱。**
*其次,通过注册会员,你可以获得完整的媒介体验,并支持我和其他成千上万的作家。它每个月只花你 5 美元,你可以完全接触到所有令人惊叹的媒体文章,也有机会用你的写作赚钱。如果你用 [***我的链接***](https://andymcdonaldgeo.medium.com/membership)**报名,你直接用你的一部分费用支持我,不会多花你多少钱。如果你这样做了,非常感谢你的支持!***
# **参考**
**博尔曼,彼得,奥桑德,彼得,迪里布,法哈德,曼拉尔,投降,&迪辛顿,彼得。(2020).机器学习竞赛 FORCE 2020 井测井和岩相数据集[数据集]。芝诺多。[http://doi.org/10.5281/zenodo.4351156](http://doi.org/10.5281/zenodo.4351156)**
# 随机森林还是 XGBoost?是时候探索 LCE 了
> 原文:<https://towardsdatascience.com/random-forest-or-xgboost-it-is-time-to-explore-lce-2fed913eafb8>
## LCE:本地级联合奏

在 [Unsplash](https://unsplash.com/) 上由 [David Bruyndonckx](https://unsplash.com/@david_bxl?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 拍摄的照片。
【O】在过去的几年里,Random Forest [Breiman,2001]和 XGBoost [Chen and Guestrin,2016]已经成为在解决分类和回归的许多挑战方面表现最好的机器学习方法。从业者面临着一个反复出现的问题:对于给定的数据集,我们应该选择哪一个?**局部级联集成(LCE)**【Fauvel et al .,2022】是一种新的机器学习方法,它提出来回答这个问题。它结合了两者的优势,采用互补的多样化方法来获得更好的泛化预测器。因此, **LCE 进一步增强了**随机森林**和 **XGBoost 的预测性能。****
本文介绍了 LCE 和相应的 [Python 包](https://pypi.org/project/lcensemble/),并附有一些代码示例。LCE 包**与 scikit 兼容-学习**;它通过[检查估计器](https://scikit-learn.org/stable/modules/generated/sklearn.utils.estimator_checks.check_estimator.html#sklearn.utils.estimator_checks.check_estimator)。因此,它可以与 scikit-learn 管道和模型选择工具进行交互。
# LCE 演示
集合方法的构建包括将准确和多样的单个预测因子结合起来。有两种互补的方式来生成不同的预测器: *(i)* 通过改变训练数据分布,以及 *(ii)* 通过学习训练数据的不同部分。
我们在发展 LCE 时采用了这两种多样化的方法。首先, *(i)* LCE 结合了两种众所周知的方法,这两种方法通过对偏差-方差权衡的互补效应来修改原始训练数据的分布:bagging [Breiman,1996](方差减少)和 boosting [Schapire,1990](偏差减少)。然后, *(ii)* LCE 学习训练数据的不同部分,以捕捉基于分治策略(决策树)无法在全局发现的新关系。在详细介绍 LCE 如何结合这些方法之前,我们先介绍一下在解释 LCE 时用到的关键概念。
偏差-方差权衡定义了学习算法在训练集之外进行概括的能力。*偏差*是由学习算法的系统误差产生的预测误差的分量。高偏差意味着学习算法不能捕获训练集的底层结构(欠拟合)。*方差*衡量学习算法对训练集变化的敏感度。高方差意味着算法对训练集的学习过于紧密(过度拟合)。目标是最小化偏差和方差。*套袋*对方差减少有主效应;这是一种生成预测值的多个版本(引导复制)并使用这些版本获得聚合预测值的方法。目前最先进的采用装袋的方法是随机森林。而*升压*对偏置降低有主要影响;it 是一种迭代学习弱预测器并将它们相加以创建最终强预测器的方法。在添加弱学习者之后,数据权重被重新调整,允许未来的弱学习者更多地关注先前弱学习者预测错误的例子。目前最先进的使用 boosting 的方法是 XGBoost。图 1 说明了装袋和增压方法之间的区别。

图一。植物病害数据集上的套袋与加强。n——估计量的数量。图片由作者提供。
因此,我们的新集成方法 LCE 结合了 boosting-bagging 方法来处理机器学习模型面临的偏差-方差权衡;此外,它采用分而治之的方法来个性化训练数据不同部分的预测器误差。LCE 如图 2 所示。

图二。植物病害数据集上的局部级联系综,参考图 1,以军校生蓝装袋,以红色增强。n —树的数量,XGB — XGBoost。图片由作者提供。
具体来说,LCE 基于级联概化:它按顺序使用一组预测器,并在每个阶段向输入数据集添加新属性。新属性是从被称为基础学习器的预测器给出的输出(例如,分类器的类概率)中导出的。LCE 遵循分而治之的策略(决策树)在局部应用级联泛化,并通过使用基于提升的预测器作为基础学习器来减少决策树中的偏差。采用当前性能最好的最新 boosting 算法作为基本学习器(XGBoost,例如,图 2 中的 XGB ⁰、XGB)。当生长树时,通过将每个决策节点处的基础学习器的输出作为新属性添加到数据集,增强沿着树向下传播(例如,图 2 中的 XGB ⁰( *D* )。预测输出表明基础学习者正确预测样本的能力。在下一个树级别,基础学习者利用添加到数据集的输出作为加权方案,以更多地关注先前预测错误的样本。然后,通过使用装袋来减轻由增强决策树产生的过拟合。Bagging 通过用原始数据集的替换从随机抽样中创建多个预测值来提供方差减少(例如,图 2 中的 *D* 、 *D* )。最后,通过简单多数投票来聚合树。为了被用作预测器,LCE 在每个节点中存储由基础学习器生成的模型。
## 缺失数据
我们选择**本地处理丢失的数据。**与 XGBoost 类似,LCE 排除了分割的缺失值,并使用块传播。在节点分割期间,块传播将所有丢失数据的样本发送到错误较少的决策节点端。
## 超参数
LCE 的超参数是基于树的学习中的经典参数(例如,`max_depth`、`max_features`、`n_estimators`)。此外,LCE 在树的每个节点学习特定的 XGBoost 模型,并且它只需要指定 XGBoost 超参数的范围。然后,每个 XGBoost 模型的**超参数由 Hyperopt** [Bergstra 等人,2011]自动设置,Hyperopt 是一种使用 Parzen 估计器算法树的基于序列模型的优化。Hyperopt 从先前的选择和基于树的优化算法中选择下一个超参数。Parzen 树估计器满足或超过超参数设置的网格搜索和随机搜索性能。LCE 超参数的完整列表可在其[文档](https://lce.readthedocs.io/en/latest/api.html)中找到。
## 公布的结果
我们最初在[Fauvel 等人,2019]中为特定应用设计了 LCE,然后在[Fauvel 等人,2022]中的公共 UCI 数据集[Dua 和 Graff,2017]上对其进行了评估。结果表明,LCE 平均获得了比包括随机森林和 XGBoost 在内的最新分类器更好的预测性能。
# Python 包和代码示例

图片由作者提供。
## 装置
LCE 有一个 [Python 包](https://pypi.org/project/lcensemble/) (Python ≥ 3.7)。可以使用`pip`进行安装:
pip install lcensemble
或者`conda`:
conda install -c conda-forge lcensemble
## 代码示例
LCE 包**兼容 scikit-learn**;它通过[检查估计器](https://scikit-learn.org/stable/modules/generated/sklearn.utils.estimator_checks.check_estimator.html#sklearn.utils.estimator_checks.check_estimator)。因此,它可以与 scikit-learn 管道和模型选择工具进行交互。以下示例说明了 LCE 在公共数据集上用于分类和回归任务的情况。还显示了包含缺失值的数据集上的 LCE 的示例。
* 在[虹膜数据集](https://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html)上使用 LCE 进行分类
Iris 数据集上的这个示例说明了如何训练 LCE 模型并将其用作预测器。它还通过使用`cross_val_score`演示了 LCE 与 scikit-learn 模型选择工具的兼容性。
* 缺失值虹膜数据集上的 LCE 分类
这个例子说明了 LCE 对缺失值的稳健性。Iris 训练集被修改为每个变量有 20%的缺失值。
* 对[糖尿病数据集](https://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_diabetes.html)进行 LCE 回归
最后,这个例子展示了如何在回归任务中使用 LCE。
# 结论
本文介绍了 LCE,一种用于一般分类和回归任务的新集成方法,以及相应的 Python 包。有关 LCE 的更多信息,请参考发表在*数据挖掘和知识发现*杂志上的相关[论文](https://hal.inria.fr/hal-03599214/document)。
# 参考
J.伯格斯特拉、巴登内、本吉奥和凯格尔。超参数优化算法。2011 年第 24 届国际神经信息处理系统会议论文集。
长度布莱曼。打包预测值。机器学习,24(2):123–140,1996 年。
长度布莱曼。随机森林。机器学习,45(1):5–32,2001。
T.陈和 C. Guestrin。XGBoost:一个可扩展的树提升系统。2016 年第 22 届 ACM SIGKDD 知识发现和数据挖掘国际会议论文集。
D.杜瓦和 c .格拉夫。UCI 机器学习知识库,2017 年。
K.Fauvel,V. Masson,E. Fromont,P. Faverdin 和 A. Termier。走向可持续的奶牛管理——发情检测的机器学习增强方法。2019 年第 25 届 ACM SIGKDD 知识发现和数据挖掘国际会议论文集。
K.Fauvel、E. Fromont、V. Masson、P. Faverdin 和 A. Termier。XEM:一种用于多元时间序列分类的可解释的设计集成方法。数据挖掘和知识发现,36(3):917–957,2022。
R.沙皮雷。弱可学性的力量。机器学习,5(2):197–227,1990 年。
# 随机森林回归
> 原文:<https://towardsdatascience.com/random-forest-regression-5f605132d19d>
## 7 分钟的基本解释和使用案例

塞斯·芬克在 [Unsplash](https://unsplash.com/s/photos/random-forest?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上的照片
几周前,我写了一篇文章演示了[随机森林分类模型](/random-forest-classification-678e551462f5)。在本文中,我们将使用 sklearn 的 RandomForrestRegressor()模型演示随机森林的回归情况。
与我的上一篇文章类似,我将通过强调一些与随机森林机器学习相关的定义和术语来开始这篇文章。本文的目标是描述随机森林模型,并演示如何使用 sklearn 包来应用它。我们的目标不是解决最佳解决方案,因为这只是一个基本指南。
**定义:
决策树**用于回归和分类问题。它们在视觉上像树一样流动,因此而得名,在回归的情况下,它们从树根开始,并根据可变结果进行分割,直到到达一个叶节点并给出结果。下面是决策树的一个示例:

作者图片
这里我们可以看到一个基本的决策树图,它从 Var_1 开始,根据特定的标准进行拆分。当“是”时,决策树沿着表示的路径前进,当“否”时,决策树沿着另一条路径前进。这个过程重复进行,直到决策树到达叶节点,并决定结果。对于上面的例子,a、b、c 或 d 的值可以代表任何数值或分类值。
**集成学习**是使用多个模型的过程,对相同的数据进行训练,平均每个模型的结果,最终找到更强大的预测/分类结果。我们对集成学习的希望和要求是,每个模型(在这种情况下是决策树)的误差是独立的,并且在不同的树之间是不同的。
**Bootstrapping** 是在给定迭代次数和给定变量数的情况下随机采样数据集子集的过程。然后将这些结果平均在一起,以获得更有效的结果。自举是应用集合模型的一个例子。
bootstrapping**Random Forest**算法将集成学习方法与决策树框架相结合,从数据中创建多个随机绘制的决策树,对结果进行平均以输出新的结果,这通常会导致强预测/分类。
在这篇文章中,我将展示一个随机的森林模型,这个模型是由 Austin Reese 发布到 Kaggle 上的美国住房数据创建的,这个数据被授权为 CC0——公共领域。该数据集提供了有关待售房屋的信息和详细信息。该数据集由大约 380,000 个观察值和 20 多个变量组成。我进行了大量的 ed a,但为了让本文更多地介绍实际的随机森林模型,我不会包括所有的步骤。
**随机森林回归模型:** 我们将使用 sklearn 模块来训练我们的随机森林回归模型,特别是 RandomForestRegressor 函数。RandomForestRegressor 文档显示了我们可以为模型选择的许多不同的参数。下面重点介绍了一些重要参数:
* **n_estimators** —您将在模型中运行的决策树的数量
* **标准** —该变量允许您选择用于确定模型结果的标准(损失函数)。我们可以从均方误差(MSE)和平均绝对误差(MAE)等损失函数中进行选择。默认值为 MSE。
* **max_depth** —设置每棵树的最大可能深度
* **max _ features**-确定分割时模型将考虑的最大特征数
* **bootstrap** —默认值为 True,这意味着模型遵循 bootstrap 原则(前面已定义)
* **max_samples** —该参数假设 bootstrapping 设置为 True,否则该参数不适用。在 True 的情况下,该值设置每棵树的每个样本的最大大小。
* 其他重要参数有 **min_samples_split、min_samples_leaf、n_jobs** 以及其他可以在 sklearn 的 RandomForestRegressor 文档[中读取的参数,此处](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestRegressor.html)。
出于本文的目的,我们将首先展示输入到随机森林回归模型中的一些基本值,然后我们将使用网格搜索和交叉验证来找到一组更优的参数。
rf = RandomForestRegressor(n_estimators = 300, max_features = 'sqrt', max_depth = 5, random_state = 18).fit(x_train, y_train)
看看上面的基本模型,我们使用了 300 棵树;max_features per tree 等于训练数据集中参数数量的平方根。每棵树的最大深度设置为 5。最后,random_state 被设置为 18 只是为了保持一切标准。
正如我在以前的随机森林分类文章中所讨论的,当我们解决分类问题时,我们可以使用准确性、精确度、召回率等指标来查看我们的性能。当查看回归模型的性能指标时,我们可以使用诸如均方误差、均方根误差、R、调整后的 R 等因素。在这篇文章中,我将重点介绍均方误差和均方根误差。
简单来说,均方误差(MSE)是实际输出值和预测输出值之间的平方差总和的平均值。我们的目标是尽可能降低 MSE。例如,如果我们有一个(3,5,7,9)的实际输出数组和一个(4,5,7,7)的预测输出数组,那么我们可以将均方误差计算为:
((3-4)+(5–5)+(7–7)+(9–7))/4 =(1+0+0+4)/4 = 5/4 = 1.25
均方根误差(RMSE)就是 MSE 的平方根,因此本例中 RMSE = 1.25^.5 = 1.12。
使用这些性能指标,我们可以运行以下代码来计算模型的 MSE 和 RMSE:
prediction = rf.predict(x_test)mse = mean_squared_error(y_test, prediction)
rmse = mse**.5print(mse)
print(rmse)

作者图片
我们从这个基本的随机森林模型中得到的结果总体上并不是很好。鉴于我们数据集的大多数值都在 1000–2000 之间,RMSE 值 515 相当高。展望未来,我们将看到调整是否有助于创建一个性能更好的模型。
在大型数据集上运行随机森林模型时,需要考虑的一个问题是潜在的长训练时间。例如,运行第一个基本模型所需的时间大约是 30 秒,这还不算太糟,但是正如我稍后将演示的,这个时间需求可能会迅速增加。
既然我们已经完成了基本的随机森林回归,我们将寻找一个性能更好的参数选择,并将利用 GridSearchCV sklearn 方法来实现这一点。
Define Grid
grid = {
'n_estimators': [200,300,400,500],
'max_features': ['sqrt','log2'],
'max_depth' : [3,4,5,6,7],
'random_state' : [18]
}## show start time
print(datetime.now())## Grid Search function
CV_rfr = GridSearchCV(estimator=RandomForestRegressor(), param_grid=grid1, cv= 5)
CV_frf.fit(x_train, y_train)## show end time
print(datetime.now())

作者图片
正如您可能已经注意到的,在上面的代码中,我包含了两个显示当前日期时间的打印语句,这样我们就可以跟踪函数的开始和结束时间来测量运行时间。正如我们在上面的图像中看到的,这个函数花了 2 个多小时来训练/调整,这是一个不小的时间量,也是一个明显更大规模的版本,我们在早期的基本模型中看到了 30 秒。
为了进一步扩展,我们的数据集有大约 380,000 个观察值,这仍然相对较小,特别是与那些用于专业应用或学术研究的观察值相比,这些观察值可能有数百万或数十亿个。在考虑使用哪种模型以及权衡性能与时间时,需要考虑这些时间限制。
通过网格搜索找到的最佳参数在下面的代码部分。使用这些参数,并对相同的数据进行测试,我们发现了以下结果。
{'max_depth': 7,
'max_features': 'sqrt',
'n_estimators': 300,
'random_state': 18}# Create and train model
rf = RandomForestRegressor(n_estimators = 300, max_features = 'sqrt', max_depth = 7, random_state = 18)
rf.fit(x_train, y_train)# Predict on test data
prediction = rf.predict(x_test)# Compute mean squared error
mse = mean_squared_error(y_test, prediction)# Print results
print(mse)
print(mse^.5)

作者图片
这个均方误差结果低于我们的基本模型,这很好,但总的来说,我仍然认为这种性能是不够的。均方根误差为 504 意味着每次估算的平均误差比实际租赁价格低 504 美元。这种糟糕的性能可能有几个原因:
* 不使用某些变量和/或使用不必要的变量
* 糟糕的 EDA 和数据争论
* 未能正确说明分类变量或文本变量
在我看来,结果不佳的主要原因可以归结为上面提到的第一点。也就是说,本文的目标不是产生最佳结果,而是演示如何使用 sklearn 将随机森林回归模型和一些背景信息应用到随机森林模型的操作中。就本文的目的而言,我认为我们能够实现我们的目标。
**结论:**
在本文中,我们展示了随机森林模型背后的一些基础知识,更具体地说,是如何应用 sklearn 的随机森林回归算法。我们指出了随机森林模型的一些好处,以及一些潜在的缺点。
感谢您花时间阅读这篇文章!我希望您喜欢阅读,并了解了更多关于随机森林回归的知识。我将继续撰写文章,更新这里部署的方法,以及其他方法和数据科学相关的主题。
# 连续测井预测的随机森林回归
> 原文:<https://towardsdatascience.com/random-forest-regression-for-continuous-well-log-prediction-61d3ec1c683a>
## 机器学习|岩石物理学
## 应用于岩石物理学的流行机器学习算法综述

HoliHo 的照片:[https://www . pexels . com/photo/pathway-in-trees-between-daytime-1112186/](https://www.pexels.com/photo/pathway-in-between-trees-at-daytime-1112186/)
随机森林是一种非常流行,也相对容易理解的机器学习算法。[在我之前关于随机森林的文章中,我们试图从一系列测井测量中预测分类数据(岩性)。](https://medium.com/p/4a1380ef025b)在本文中,我们将重点关注连续测量的预测。
# 回归随机森林简介
在很高的层次上,随机森林本质上是决策树的集合(系综)。决策树理解起来非常简单和直观。我们经常在日常生活中使用它们来做决定,尽管我们可能没有意识到。
如果你想知道随机森林是如何应用于分类问题的,可以看看下面的文章。
</random-forest-for-lithology-classification-from-well-log-data-4a1380ef025b>
在分类的情况下,我们试图使用决策将我们的数据分开,并将它们放入类中。当它们应用于回归时,我们试图做类似的事情,但我们预测的不是一个类,而是一个值。
在树的顶端(根节点),我们首先做出一个决定,拆分我们的数据。这一过程沿树向下继续,直到不能再进行分割,这通常定义为每个末端节点(叶节点)的最小样本数或树的最大深度。

随机森林中的单个决策树示例。图片由作者提供。
随机森林算法由以随机方式构造的多个决策树形成。这包括随机抽样我们的数据,并从我们的数据集中为每棵树随机选择变量/特征。

使用测井/岩石物理数据的随机森林回归示例。图片由作者提供。
一旦森林建成,我们将所有预测的平均值作为最终的输出值。与单个决策树相比,这种平均允许我们提高预测的准确性,并且还减少了过度拟合的机会。
现在,我们已经对随机森林回归有了很高的理解,我们可以继续我们的 Python 示例,看看如何实现从测井测量预测声波压缩慢度。
即使您不熟悉测井测量或岩石物理学,所使用的大部分代码也可以应用于任何数据集。
# 本教程中使用的数据
本教程中使用的数据是 Equinor 在 2018 年发布的 Volve 数据集的子集。数据集的全部细节,包括许可证可以在下面的链接中找到。
<https://www.equinor.com/energy/volve-data-sharing>
Volve 数据许可证基于 CC BY 4.0 许可证。许可协议的全部细节可以在这里找到:
[https://cdn . sanity . io/files/h 61 q 9 gi 9/global/de 6532 f 6134 b 9 a 953 f 6 c 41 BAC 47 a 0 c 055 a 3712d 3 . pdf?equinor-hrs-条款和条件-许可-数据-volve.pdf](https://cdn.sanity.io/files/h61q9gi9/global/de6532f6134b9a953f6c41bac47a0c055a3712d3.pdf?equinor-hrs-terms-and-conditions-for-licence-to-data-volve.pdf)
# 导入库和数据
本教程的第一步是导入我们将要使用的库。在这个例子中,我们将结合使用 [pandas](http://pandas.pydata.org/) 来加载和存储我们的数据,以及 [matplotlib](https://matplotlib.org/) 来可视化我们的数据。
import pandas as pd
import matplotlib.pyplot as plt
接下来,我们将使用`pd.read_csv()`从 csv 文件导入数据。在这个函数中,我们将传入我们的文件路径以及我们将使用的列。只要 csv 文件包含每一列的标题,我们就可以使用`usecols`参数。
df = pd.read_csv('Data/Volve/volve_wells.csv', usecols=['WELL', 'DEPTH', 'RHOB', 'GR', 'NPHI', 'PEF', 'DT'])
# 检查我们有哪些井
数据成功加载后,我们可以检查一下,看看我们有哪些井。最简单的方法是在包含井名(数据源)的井列上调用`.unique()`。
df['WELL'].unique()
它返回:
array(['15/9-F-11 B', '15/9-F-11 A', '15/9-F-1 B', '15/9-F-1 A'],
dtype=object)
我们可以看到 Volve 油田有四口井。
# 创建培训、测试和验证数据集
由于我们使用多口井的测量数据,将我们的数据分成有效子集进行训练、验证和测试的一种方法是留出一口井(盲测试井),用于观察我们的模型在未知数据上的表现,然后使用剩余的井进行训练。
需要注意的一点是,测试和验证数据集的术语可能因文章、网站和视频而异。这里使用的定义说明如下:

将数据分为训练、验证和测试的示例。图片由作者提供,来自麦当劳,2021。
**训练数据集:**用于训练模型的数据
**验证数据集:**用于验证模型和调整参数的数据。
**测试数据集:**留出的数据,用于在看不见的数据上测试最终模型。
Training Wells
training_wells = ['15/9-F-11 B', '15/9-F-11 A', '15/9-F-1 A']# Test Well
test_well = ['15/9-F-1 B']train_val_df = df[df['WELL'].isin(training_wells)].copy()
test_df = df[df['WELL'].isin(test_well)].copy()train_val_df.describe()
这将返回我们的训练数据集:

我们的训练数据集的数据帧统计。图片由作者提供。
我们还可以对测试数据集进行同样的操作,以确认数据帧已经创建。
test_df.describe()
这将返回下面的数据帧,其中包含我们的测试数据集所需的曲线。

我们的测试数据集的数据帧统计。图片由作者提供。
# 从数据帧中删除缺失值
我们可以从前面的图像中看到,每列的计数行中有不同的数字。这表明我们缺少值。
从数据帧中移除缺失值是处理它们的一种方法,但是,这样做会减少可用的训练数据量。
**从数据集中移除值时应小心谨慎,应进行完整的 EDA 以了解值缺失的原因。可以使用其他更复杂的方法来填充具有更可能和更合理的值的 nan。**
train_val_df.dropna(inplace=True)
test_df.dropna(inplace=True)
train_val_df.describe()

# 实现随机森林回归模型
既然我们已经准备好了数据并移除了缺失值,现在我们可以继续构建随机森林回归模型了。为此,我们需要从 [Scikit-learn](https://scikit-learn.org/stable/) 库中导入一些模块。
from sklearn.model_selection import train_test_split
from sklearn import metrics
from sklearn.ensemble import RandomForestRegressor
接下来,我们需要将我们的数据分成哪些变量将用于训练模型(X)和我们的目标变量,`DT` (y)。
X = train_val_df[['RHOB', 'GR', 'NPHI', 'PEF']]
y = train_val_df['DT']
我们接下来调用`train_test_split()`函数将我们的数据分成训练和验证数据集。
我们传入 X 和 y 变量,以及表示我们想要多大的测试数据集的参数。这是作为十进制值输入的,范围在 0 和 1 之间。在这种情况下,我们使用 0.2,这意味着我们的测试数据集将是原始数据的 20%,我们的训练数据集将是原始数据的 80%。
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2)
## 构建随机森林回归模型
为了建立我们的回归模型,我们首先需要创建一个名为`regr`的变量。这可以是你想要的任何东西,只要有意义。
一旦我们初始化了模型,我们就可以根据数据进行拟合。这本质上是训练过程。如果需要,我们可以更改许多参数,但在本例中,我们将保留默认值。
regr = RandomForestRegressor()
regr.fit(X_train, y_train)
在模型被训练之后,我们可以通过调用`.predict()`对我们的验证数据进行预测。
y_pred = regr.predict(X_val)
## 检查预测结果
现在,我们的模型已经被训练并应用于验证数据,我们需要检查我们的模型表现如何。
我们将用到的第一个指标是平均绝对误差,计算方法如下:

平均绝对误差公式。图片由作者提供。
metrics.mean_absolute_error(y_val, y_pred)
这将返回值 1.6841。这将告知我们实际测量值(`y_val`)和预测测量值(`y_pred`)之间的平均绝对误差,其测量单位与目标特征相同。在这种情况下,美国/英尺。
我们可以利用的另一个指标是均方根误差(RMSE)。这可以通过以下方式计算:

均方根误差(RMSE)公式。图片由作者提供。
要使用 scikit-learn 计算 RMSE,我们首先需要计算均方误差,然后求它的平方根,这可以通过将 MSE 提高到 0.5 的幂来实现。
mse = metrics.mean_squared_error(y_val, y_pred)
rmse = mse**0.5
这将返回值 3.0664。RMSE 方程为我们提供了预测误差大小的概念。
## 超越度量标准
像上面这样的简单指标是查看模型表现的好方法,但是您应该总是检查实际数据。
一种方法是使用散点图,x 轴表示验证数据,y 轴表示预测数据。为了帮助观想,我们可以添加一条一对一的关系线。
完成这项工作的代码如下。
plt.scatter(y_val, y_pred)
plt.xlim(40, 140)
plt.ylim(40, 140)
plt.ylabel('Predicted DT')
plt.xlabel('Actual DT')
plt.plot([40,140], [40,140], 'black') #1 to 1 line

实际测量值与预测测量值的散点图。图片由作者提供。
我们得到的是上面的图,这表明我们有一个相当好的预测。有几个点在 60 到 80 us/ft 之间,预测值高于实际值。
理想情况下,我们应该回到我们的数据或模型,看看我们是否可以改善这种预测。这可能涉及改变输入、检查异常值、处理异常值以及收集更多数据。
## 测试数据预测(盲测试井)
一旦我们最终确定了我们的模型,我们就可以用我们为盲测留出的数据来测试它了。
首先,我们将创建用于应用模型的特征。
test_well_x = test_df[['RHOB', 'GR', 'NPHI', 'PEF']]
接下来,我们将为预测数据的数据框架分配一个新列。
test_df['TEST_DT'] = regr.predict(test_well_x)
预测完成后,我们可以查看与上面相同的散点图。
plt.scatter(test_df['DT'], test_df['TEST_DT'])
plt.xlim(40, 140)
plt.ylim(40, 140)
plt.ylabel('Predicted DT')
plt.xlabel('Actual DT')
plt.plot([40,140], [40,140], 'black') #1 to 1 line

实际测量值与预测测量值的散点图。图片由作者提供。
为了更好地了解预测相对于实际测量的效果,我们可以查看一个简单的数据对深度的线图。
plt.figure(figsize=(15, 5))
plt.plot(test_df['DEPTH'], test_df['DT'], label='Actual DT')
plt.plot(test_df['DEPTH'], test_df['TEST_DT'], label='Predicted DT')
plt.xlabel('Depth (m)')
plt.ylabel('DT')
plt.ylim(40, 140)
plt.legend()
plt.grid()

预测测量值与实际测量值的线图(对数图)。图片由作者提供。
如上所述,这个简单的模型表现得相当好。然而,有几个地区的结果预测不足(在 3170 米和 3200 米之间),也有一些地区我们预测过高(在 3370 米和 3450 米之间)。
# 摘要
随机森林是一种强大的机器学习算法,可以同样应用于基于分类和回归的问题。不需要对模型进行重大调整,我们就可以得到一个好的结果,尤其是在盲测数据上。随着模型的进一步调整和新数据训练的引入,我们可能能够提高模型的泛化能力。但是,请记住,随着数据集变得越来越大,训练时间也会增加。
在达到最终模型之前,比较几个机器学习模型的结果也是值得的。
*感谢阅读。在你走之前,你一定要订阅我的内容,把我的文章放到你的收件箱里。* [***你可以在这里做!***](https://andymcdonaldgeo.medium.com/subscribe)**或者,您也可以* [***注册我的简讯***](https://fabulous-founder-2965.ck.page/2ca286e572) *免费将更多内容直接发送到您的收件箱。**
*其次,通过注册会员,你可以获得完整的媒介体验,并支持我和其他成千上万的作家。它每个月只花你 5 美元,你可以完全接触到所有令人惊叹的媒体文章,也有机会用你的写作赚钱。如果你用 [***我的链接***](https://andymcdonaldgeo.medium.com/membership)**报名,你直接用你的一部分费用支持我,不会多花你多少钱。如果你这样做了,非常感谢你的支持!***
# 随机森林漫游——群体的智慧以及为什么它们比决策树更好
> 原文:<https://towardsdatascience.com/random-forests-walkthrough-why-are-they-better-than-decision-trees-22e02a28c6bd>
## 随机森林总是被称为“基于树的”模型的更强大和更稳定的版本。在本帖中,我们将证明为什么将群体的智慧应用到决策树是一个好主意。

照片由[@ skamenar](https://unsplash.com/@skamenar)-[Unsplash](https://medium.com/u/2053395ac335?source=post_page-----22e02a28c6bd--------------------------------)拍摄。com
决策树是非常强大的算法。当你进入数据科学和机器学习领域时,它们可能是你可能学习的第一批非线性算法之一。
决策树可以处理非线性模式,并理解目标和特征之间的一些最疯狂的关系。从这个意义上说,它们比线性模型如线性或逻辑回归有巨大的优势。
虽然它们具有捕捉更复杂的特征和目标关系的巨大能力,但它们也很容易过度拟合。它们是倾向于搜索完美叶节点的贪婪算法的子集。特别是当我们处理高维度(行或特征)时,这可能导致树最终用小样本进行概括,从而使您的算法对您的训练数据来说是完美的。
[他们的内部工作方式](/classification-decision-trees-easily-explained-f1064dde175e?sk=ccd305c31950f2e8c843377186e5e75f)很难找到纯节点,并且很难建立一个在偏差和方差之间达到最佳平衡的决策树。如何避免这种情况?
大多数情况下,研究人员已经找到了两种解决这个问题的方法:
* boosting——将多个弱学习者组合并迭代到一个算法中——XGBoost 是最著名的实现之一。
* 打包——将多个模型聚合成一个整体——例如随机森林(RF)。
RFs 使用多个决策树的输出,将它们合并到一个投票系统中,这有助于缓解他们的一些紧迫问题。打个比方,如果决策树是一个独裁政府,只使用一个“头脑”的意见来做决定,随机森林就是一个民主政府,依靠群众的智慧来执行预测。
这篇文章会让你很好地理解随机森林背后的直觉。通过一些实验,我们将比较它们与单一决策树的性能,并理解为什么它们通常比单一决策树模型有更好的结果。
# 拟合多个决策树
为了真正理解 RF 的工作原理,让我们做一个实验,在一个数据集上拟合几个决策树。为了便于每个人复制,我们将使用 Kaggle 中可用的 [*泰坦尼克号*](https://www.kaggle.com/c/titanic/data?select=train.csv) 数据框,这是大多数数据科学家都知道的数据集。
对于这个特定的问题,我们希望根据一组特征(如乘客年龄、机票等级、性别等)来预测哪些乘客在泰坦尼克号失事中幸存。— **这是一个二元分类问题,有两个互斥的结果:**
* 乘客生还;
* 乘客没有生还;
让我们首先拟合 3 个不同的决策树。我们控制和创建完全不同的树的最常见的方法之一是给它们自己的一组[超参数](/5-decision-tree-hyperparameters-to-enhance-your-tree-algorithms-aee2cebe92c8?sk=5124b0f5996634d4f0d214499906f1d5)。这意味着产生的不同决策树会因用于预测结果的不同分割和变量而有所不同。
**除了不同的超参数,我们还将为每棵树选择 80%的原始训练样本,从而在构成的每棵树中引入更多的随机性来源。**
为了评估我们不同的决策树,我们将使用 30%的维持率作为纯测试集:
Reading the titanic train dataset
titanic <- read.csv('./train.csv')# Obtaining the number of rows for training (70%)
size <- ceiling(0.7*nrow(titanic))# Use an indexer to perform train and test split
set.seed(999)
train_index <- sample(
seq_len(nrow(titanic)), size = size
)train_df <- titanic[train_index, ]
test_df <- titanic[-train_index, ]
*train_df* 数据帧包含 624 名乘客,而 *test_df* 包含 267 名乘客。
我们可以构建三个不同的决策树——由于它们对超参数和训练数据中的微小变化非常敏感,我们可以轻松地构建三个彼此完全不同的决策树,只需对代码进行非常小的更改。
举个例子,让我来拟合 1 号决策树,我将它命名为 *oak_tree* (用不同的名字来称呼不同的树将有助于我们更好地形象化我们的练习)。下面是我训练它的代码:
library(rpart)set.seed(9990)
oak_tree <- rpart(Survived ~ Fare + Age + Sex + Pclass,
data = sample_n(train_df, 600),
method = 'class',
control = list(maxdepth = 2,
minsplit=30))
*(不要忘记在运行 set.seed 的同时运行训练代码,这样您就可以复制这些结果)*
请注意,我们只是使用 4 个特征来预测是否有人在泰坦尼克号失事中幸存——乘客票价、年龄、性别和机票等级。是什么让我的*橡树*与众不同?我的 *oak_tree* 是一棵深度只有 2 的超级小树,允许在每个节点上进行至少 30 个例子的分割。如果这个超参数术语让你感到困惑,[看看这个](/5-decision-tree-hyperparameters-to-enhance-your-tree-algorithms-aee2cebe92c8?sk=5124b0f5996634d4f0d214499906f1d5)。
正如我以前说过的,我也给我的*橡树增加了更多的辣度—* 我只在大约 600 名随机乘客的数据上训练算法,这将模拟不同树之间更多的随机性。
所以我们的*橡树*看起来像下面的 *:*

决策树 1 —作者图片
在这个树中,用来决定结果的第一个变量(也是唯一的一个)是性别。这一变量似乎具有很高的判别能力,因为如果乘客是男性,他不幸存的可能性更高——约为 82%,而如果乘客不是男性,则为 26%。关于此图如何工作的一个小插图:

决策树图表说明—图片由作者提供
请记住,这种特殊的树是以这种方式构建的,因为我们定义了 600 个客户的特定子样本和一组超参数。
现在让我们训练一个完全不同的树,这次我叫它 *pine_tree。*
set.seed(9991)
pine_tree <- rpart(Survived ~ Fare + Age + Sex + Pclass,
data = sample_n(train_df, 600),
method = ‘class’,
control = list(maxdepth = 3,
minsplit=3,
minbucket=4,
cp=0.01))
我们的*松树*可以比我们的*橡树更深一点。*此外,我们允许我们的树用更少的例子构建节点——无论是在进行分割时还是在构建末端叶节点时。
让我们检查一下结果——注意这个树形图和*橡树图*的区别:

决策树 2 —作者图片
注意一些很酷的事情——新的变量开始发挥作用。虽然性别仍然被用作第一分割点,但从那时起,票价、阶级和年龄就占据了舞台,并决定了我们的大部分概率。
有趣的流程如下所示:
* 支付超过 16 美元且不到 2 岁的儿童有 86%的存活概率。
* 上流社会的女性生存的可能性最高(95%)。
我们现在已经建立了两个不同的树!请注意,它们显示了完全不同的结果,尽管我们的*松树*似乎是我们的*橡树*的自然延伸。也预期*松树*比*橡树*有更多的过度拟合,仅仅是因为超参数的设置。
最后再来拟合另一棵树, *elm_tree。*
*Elm_tree* 与 *pine_tree* 相似,但有一点不同——我们只允许树的最大深度为 2。
set.seed(9992)
elm_tree <- rpart(Survived ~ Fare + Age + Sex + Pclass,
data = sample_n(train_df, 600),
method = 'class',
control = list(maxdepth = 2,
minsplit=2,
minbucket=4,
cp=0.01))
*榆树 _ 树*看起来如下:

决策树 3 —作者图片
请注意,性别仍然是定义第一次拆分的变量。接下来就是上课了。
这三棵树看起来很相似,只是深度不同。当然,我们现在需要评估它们!它们在性能上有多大的不同?
# 评估每棵树的性能
为了快速进行性能评估,并且不受限于任何阈值,让我们使用 AUC 作为评估模型的指标。
更高的 [AUC](/understanding-auc-roc-curve-68b2303cc9c5) 意味着我们的模型更善于区分 0 类和 1 类,使我们的模型在预测中更有效和正确。
我正在使用 *ROCR* 库来更快地获得 AUC:
library(ROCR)obtainauc <- function(model) {
predictions <- predict(model, test_df)[,2]
pred <- prediction(predictions, test_df$Survived)
perf <- performance(pred, measure = 'auc')
return (perf@y.values[[1]])
}
我们的采油树表现如下:

树与树之间 AUC 的比较—图片由作者提供
从上面的图来看:
* 橡树的 AUC 为 0.778
* *松树*的 AUC 为 0.832
* *榆树*的 AUC 为 0.807
*松树*是表现最好的树。这是意料之中的,因为这也是包含最佳细节的树。每当我们提高最大深度时,我们可能会在训练集上有更好的性能——当我们在测试集上评估时,可能会有所不同。
因此,随机森林集中在一个核心问题中:**“即使我的*松树*是最好的,我是否应该完全放弃*橡树*和*榆树*的观点?”**
让我们建立另一个实验——我们将做一个多数投票选项——平均所有树的结果,并将这个意见“集合”与每棵树单独进行比较。由于我们正在处理概率结果,我们将只是平均 3 个不同树之间的概率,并认为这是我们的组合投票。
# 平均结果
为了更容易理解“平均结果”的真正含义,我们来做一点角色扮演。
假设我询问橡树、松树和榆树对下面这个人的看法:
* 一名男性花了不到 16 美元登上泰坦尼克号,他会幸免于难吗?
橡树说这位乘客有 18%的可能性会活下来。松树说只有 10%的机会活下来。*榆树*也说这个人有 18%的几率活下来。
如果我们对每棵树的推荐进行平均,我们将得到 *(18%+18%+10%)/3* ,这将产生 *15.3%* — **使用投票平均值,这应该是我们最终的乘客存活概率。**
让我们看看另一位乘客:
* 乘坐头等舱或二等舱的女乘客。
橡树说这位乘客有 74%的可能性会活下来。松树和榆树都显示有 95%的可能性。
如果我们平均这些建议,我们得到 88%的生存概率。
**主要问题是——你认为对测试集中的所有乘客使用这种基本原理会提高我们的性能吗?还是由于他们更糟糕的表现,我们的*橡树*和*榆树*的意见只会拖累我们的成绩?**
让我们看看!
# 群众的智慧
这里有一小段代码,我基于*橡树、松树*和*榆树*构建了这个集合模型。
ensemble <- (
predict(oak_tree, test_df)[,2]
+
predict(pine_tree, test_df)[,2]
+
predict(elm_tree, test_df)[,2]
)/3
这个简单的系综模型表现如何?让我们再次检查使用 ROCR 图书馆:
Ensemble Performance
prediction <- prediction(ensemble, test_df$Survived)
perf <- performance(prediction, measure = 'auc')
performance_ensemble <- perf@y.values[[1]]
我们有以下 AUC 结果:

树木和系综的 AUC 比较——作者提供的图像
哇!尽管我们的*橡树*和*榆树*比我们的*松树*更糟糕,但在做预测时考虑他们的意见是有价值的。**请注意,我们的集合模型的 AUC 略好于最佳个体树。**结论是,即使通过将该树与“较弱”的树相结合,我们也能够提升性能。
这正是随机森林背后的基本原理!每次你适应一个随机的森林,你就适应了无数的树,当与一个单独的决策树比较时,即使是较弱的树也会有利于你的整体性能。
当你增加被训练的树的数量时——有时增加到几千棵树——这种行为就更加强大了。当然,当你增加训练的树的数量时,你提高了你的“集合”比组成相同“集合”的单个决策树更好的可能性。
就是这样!感谢你花时间阅读这篇文章,希望你喜欢。我的目标是解释为什么随机森林通常比单一决策树更好,以及为什么它们是您的机器学习项目中值得考虑的优秀算法。这个例子应该能让你很好地理解 RF 背后的“群体智慧”假设。
*[***我在 Udemy 上开设了一门关于学习数据科学的课程***](https://www.udemy.com/course/r-for-data-science-first-step-data-scientist/?referralCode=MEDIUMREADERS)***——这门课程是为初学者设计的,包含 50 多个练习,我希望你能在我身边!您将学习基于树的模型、回归以及如何从头到尾构建数据科学项目。****
*这里有一个小的*要点*和贯穿这篇文章的代码:*
**数据集许可:本文中使用的数据集可以在*[https://www.openml.org/d/40945](https://www.openml.org/d/40945)公开使用*
*<https://ivopbernardo.medium.com/membership> *
# Python 中的随机投影
> 原文:<https://towardsdatascience.com/random-projection-in-python-705883a19e48>
## 降维
# Python 中的随机投影
## 在这篇文章中,我简要介绍了降维工具——随机投影的概念,以及它在 Python 中的实现。

Gabriella Clare Marino 在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上拍摄的照片
D 维度缩减通常是处理大数据时必须做的预处理。最广泛使用的方法之一是**主成分分析** ( ***PCA*** ),但是 PCA 的主要缺点是对于高维数据来说计算量非常大。一种替代方法是**随机投影**,这是一种计算成本较低的降维工具。
在这篇文章中,我将简要描述一下*随机投影*的思想及其在 Python 中的实现。
## TLDR
> 1.随机投影适用于高维数据处理。
>
> 2.随机投影的核心思想在 Johnson-Lindenstrauss 引理中给出。它基本上说明了高维空间中的数据可以被投影到低得多的维空间中,而几乎没有距离失真。
>
> 3.随机投影中的投影矩阵可以使用高斯分布生成,这被称为高斯随机投影;或者稀疏矩阵,称为稀疏随机投影。
>
> 4.随机预测可用作管道中的早期步骤之一,以更好地理解数据。
>
> 5.X_new = `sklearn.random_projection`。GaussianRandomProjection(n _ components = ' auto ',eps = 0.05)。拟合 _ 转换(X)
## 随机投影的概念
随机投影是一种降维工具。“*投影*表示该技术将数据从高维空间投影到低维空间,“*随机*表示投影矩阵是随机生成的。直截了当,对吧?
那么,投影是怎么做的?通过投影矩阵的线性变换。
具体来说,假设我们有一个带有 *d* 行(特征)和 *N* 列(样本)的原始数据集 *X* ,我们希望将特征维数从 *d* 减少到*k*(*d>>k*)。*随机投影*使用随机生成的具有 *k* 行和 *d* 列的投影矩阵 *R* 来获得变换后的新数据集 *X_new* 。

从 k 维到 d 维的投影(图片由作者提供)
随机投影不会改变样本之间的成对相似性(或距离),这得到了**约翰逊-林登斯特劳斯引理**的支持。它基本上说明了高维空间中的数据可以被投影到低得多的维空间中,而几乎没有距离失真。
如果我们用数学语言来描述这个引理,它是这样的,
给定 *0 < ε < 1* ,一个特征中 *d* 维的数据集 *X* 和 *N* 个数据点*k*>*8ln*(*N*)/*ε*,从 *d* 有一个线性映射(投影) *f*

约翰逊-林登施特劳斯引理方程(图片由作者提供)
其中 *u* 和 *v* 均来自原始特征空间, *f(u)* 和 *f(v)* 来自变换后的特征空间。
基于上面的描述,我们可以看到两件重要的事情。首先,投影并没有完全保持从尺寸 *d* 到尺寸 *k* 的成对距离,而是带有一个误差参数 *ε* 。第二,对于任何固定的 *ε* 和样本大小 *N* ,对于成对距离失真的“可接受”水平,存在最小的最终变换尺寸 *k* 。如果我们仍然想更努力地减少尺寸 *k* ,我们可能需要通过接受更大的 *ε* 来失去失真的容差。
因此,在样本大小固定的情况下,在成对距离的失真 *ε* 和最终特征空间的最小维度 *k* 之间存在权衡。
## 生成投影矩阵的两种方法
生成投影矩阵 *R* 的一种方法是让 *{r_ij}* 遵循正态分布。而另一种方式是使用稀疏随机矩阵作为 *R* 。“稀疏”是指 *{r_ij}* 的大部分为零。典型使用的分布是

稀疏投影矩阵的分布。
稀疏随机投影比高斯随机投影的计算量小,主要是因为两个原因。第一,上面的公式只涉及整数运算;第二,稀疏投影矩阵几乎没有非零值(更稀疏)。
## 实现随机投影的 Python 代码
随机投影作为一种降维工具,可以作为数据分析的前期步骤之一。基于*约翰逊-林登斯特劳斯引理*,我们可以研究数据集的结构,并在一个低得多的维度上可视化数据。
下面是 **Python** 中实现*高斯*和*稀疏随机投影*的代码,
Gaussian Random Projection
from sklearn.random_projection import GaussianRandomProjection
projector = GaussianRandomProjection(n_components='auto',eps=0.05)
X_new = projector.fit_transform(X)
其中 *X* 是我的原始数据, *n_components* 是目标空间的维数( *auto* 表示根据参数自动调整, *ε* ), *eps* 是 *Johnson-Lindenstrauss 引理*中的 *ε* ,*X _ 1*
稀疏随机投影也是如此,
Sparse Random Projection
from sklearn.random_projection import SparseRandomProjection
projector = SparseRandomProjection(n_components='auto',density = 'auto', eps=0.05)
X_new = projector.fit_transform(X)
其中*密度*是随机投影矩阵中的非零分量密度。
就是这样!希望文章有帮助。
如果你喜欢读这篇文章,这里有一些其他的片段,
[https://towards data science . com/Gaussian-mixture-models-with-python-36 dabed 6212 a](/gaussian-mixture-models-with-python-36dabed6212a)
[https://towards data science . com/fuzzy-c-means-clustering-with-python-f 4908 c 714081](/fuzzy-c-means-clustering-with-python-f4908c714081)
[https://towards data science . com/regression-splines-in-r-and-python-cfba 3 e 628 BCD](/regression-splines-in-r-and-python-cfba3e628bcd)
## 参考资料:
<https://en.wikipedia.org/wiki/Random_projection> <https://scikit-learn.org/stable/modules/generated/sklearn.random_projection.GaussianRandomProjection.html> <https://scikit-learn.org/stable/modules/generated/sklearn.random_projection.SparseRandomProjection.html#sklearn.random_projection.SparseRandomProjection>
# 用 SciPy 和 NumPy 进行随机抽样:第一部分
> 原文:<https://towardsdatascience.com/random-sampling-using-scipy-and-numpy-part-i-f3ce8c78812e>

由 [Unsplash](https://unsplash.com/s/photos/statistics?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上的 [Edge2Edge 媒体](https://unsplash.com/@edge2edgemedia?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)拍摄
## 介绍采样、编写我们自己的程序、速度测试
能够从你选择的分布中随机抽取样本是非常有用的。它是任何一种随机过程模拟的基础,无论是粒子扩散、股票价格运动,还是模拟任何显示某种时间随机性的现象。
因此,获得准确高效的采样过程非常重要。一旦我们接触到像正态分布这样到处都在使用的分布,这种重要性只会增加。
下面是对 SciPy 和 NumPy 如何为我们打包以使大规模采样非常快速和易于使用的“深入探究”。任何有一点使用 SciPy 历史的人都会告诉你原因如下:
* 它是由一些非常聪明的人写的
* 这是非常优化的代码
* 它使用 C 语言编写的底层数值例程
这都是真的。这里的目的是更深入地了解这是如何发生的,以及为什么聪明人可以用一些聪明的算法让事情变得更快。
## 我为什么要关心“引擎盖下”发生了什么?
因为 SciPy 只能让我们到此为止,尽管[它提供的发行范围相当令人难以置信](https://docs.scipy.org/doc/scipy/reference/stats.html)。当我们想从“自定义分布”中取样时,问题就出现了。如果不是从给定的参数化正态或指数分布中采样,而是从我们自己的分布中开始采样,会怎么样?也许是因为这种分布更好地代表了我们试图拟合的数据,我们希望利用蒙特卡罗过程进行一些测试?
在这些情况下,正如我们将在下面看到的,理解它的工作原理是有好处的,因为:
* 编写自己天真的采样机制可能会慢得令人难以置信
* 理解它的工作原理可以让我们在 SciPy 框架中编写自己的定制发行版
## 采样是如何工作的?
换句话说:给定一个密度函数(pdf),我如何使用它来绘制随机样本,如果我绘制它们,它们将形成与 pdf 相同形状的直方图?为了给这个陈述增加一点视觉效果,让我们用一个正态分布的例子。使用 SciPy,让我们绘制 pdf,然后生成一系列随机样本,然后再深入了解:
* 它是如何产生这些样本的
* 它怎么做得这么快

作者图片
因此,蓝色线显示我们绘制的 pdf,橙色直方图显示我们从同一分布中提取的`1,000,000`样本的直方图。**这是采样——给定一条指定的蓝线(无论它可能采取什么形状),我们如何定义一个过程(最好是快速准确的),可以生成形成与蓝线一致的直方图的数字。**
要回答最初的问题*我们如何*做到这一点,答案是:视情况而定。有许多方法可以做到这一点,每种方法都有优点和缺点。我们会发现其中一些方法比其他方法快得多。首先,我们将关注一种特定的方法,这种方法是通用的,我们将使用它作为比较 SciPy 速度的基线。如果我们没有可以与之比较的东西,我们就不能称之为 SciPy fast。
## 逆变换采样(ITS)
这里有一些想法,在编写一些基本代码来说明和形成我们的速度基准之前,我们将尝试将其浓缩成几个简短的段落。首先,我们将解决以下问题— **生成随机数需要某种随机数生成器**。
不管我们想从哪个分布中得到它们,我们都需要某种潜在的随机过程。在计算机的情况下,这个过程不可能是真正随机的,因为它需要能够被编程到机器中,但是它们可以是“伪”随机的。
换句话说,如果我们不知道生成这些数字的基本过程,那么它们对我们来说可能是随机的,即使它们对生成过程来说不是随机的。借用纳西姆·塔勒布的话,圣诞节那天对火鸡随机的东西对农民来说并不随机——这完全取决于你的信息集。这种过程被称为伪随机数发生器(PRNG ),有许多竞争对手提供。
让我们想当然地认为,我们有这样一个 PRNG,它产生这些随机数,这些随机数来自均匀分布。如果你对这些数字究竟是如何产生的感到好奇,那么我已经在这里写了一个解释器,但是对于这篇文章来说,说这样一个过程存在就足够了。
</where-does-python-get-its-random-numbers-from-81dece23b712> 。事实证明,如果我们:
* 从连续的概率分布中抽取一批数字
* 获取所有这些样本的 cdf 值
**…这些 cdf 值的分布将是均匀分布的。**这里有一个很棒的 gif [图](https://gfycat.com/unfitflatflounder)展示了标准正态分布的过程。但是对我们来说更有用的是反过来——或者说*反*。如果我们从一堆均匀分布的随机数开始(我们的 PRNG 会给我们),那么我们可以在“逆”cdf 上发射它们,并获得一堆遵循我们想要的分布的数。这就是逆变换采样。
来自[https://gfycat.com/unfitflatflounder](https://gfycat.com/unfitflatflounder)的 GIF
## 我们能把这个编码吗?
绝对的。写下来并创建我们自己的正态分布随机抽样器有两个目的:
* 提供一个模拟上述理论解释的代码
* 为 SciPy 实现创建一个纯 python 比较来检查速度
首先让我们定义我们的 pdf。我们不会给 SciPy 太多的优势,所以为了保持合理的速度,我们将:
* 利用 NumPy 进行矢量化计算
* 将`sqrt(2 * pi)`编码为浮点数,以保存乘法和 sqrt 操作
define std normal pdf
def norm_p(x):
return np.exp(-0.5 * x**2) / 2.5066282746310002
接下来,我们需要创建 cdf,这样我们就可以反转它。为此,我们将:
* 定义一个数值范围,并计算每个数值的 pdf
* 归一化 pdf 值,这样我们就有了一个密度函数,即 pdf 下的面积为`1`
* 使用“累积分布函数”中的“累积”来创建 cdf:我们只需对这些 pdf 值应用累积和来创建我们的 cdf
下面的代码实现了这一点,为了更好地衡量,我们将绘制创建的 pdf 和 cdf 以供检查。

作者图片
所以看起来在意料之中。现在我们需要获取生成的 CDF——此时它只是一组 x 值的累积概率值,并将其转换为一个函数。特别是我们想把它变成反函数。幸运的是,我们可以依靠 SciPy 并使用插值函数`interp1d`:
generate the inverse cdf
func_ppf = interp1d(my_cdf, xs, fill_value='extrapolate')
我们称之为“PPF”——百分点函数,因为这与 SciPy 术语一致,但这正是我们想要实现的——反向 cdf 函数。现在我们有了这个函数,我们可以用它来:
* 首先:向它发射均匀分布的随机数,从标准正态分布中产生样本
* 第二:比较它与内置的 SciPy 采样相比有多快
## 分配检查
首先,让我们仔细检查一下,以确保我们是根据正确的分布来生成数字的——换句话说,我没有将一个错误集中到上面几行代码中。如前所述,现在我们有了反向 cdf,我们只需要向它发射随机均匀分布的数。我们如何获得那些均匀分布的随机数?
让我们利用 NumPy 中的随机数生成器:

作者图片
因此,我们可以确信我们创建的函数确实从标准正态分布中抽取了随机样本——我们的橙色直方图与 SciPy 生成的代表 pdf 的蓝色线条对齐。
## 速度
现在进入主要问题——我们生成的函数与 SciPy 相比如何?
%timeit func_ppf(np.random.uniform(size=n))2.32 s ± 264 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)%timeit snorm.rvs(size=n)56.3 ms ± 1.08 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
因此,即使我们尽了最大努力来创建一个正态分布采样器的有效实现,我们仍然比做同样事情的 SciPy 慢。这就引出了下一个问题:为什么?
## 概述
在深入研究 SciPy 和 NumPy 代码库以弄清楚为什么我们在速度方面仍然落后之前,让我们简单回顾一下我们已经建立的东西:
* 抽样是抽取随机数的过程,这些随机数作为一个集合遵守给定的 pdf
* 有许多方法可以实现这种采样,其中一种方法称为逆变换采样
* 它依赖于在插入均匀分布的随机数之前对给定分布的 cdf 求逆
* 即使有相当有效的自我实现,我们也比 SciPy 慢大约`40x`
记住这一点,[让我们继续第二部分](/random-sampling-with-scipy-and-numpy-part-ii-234c2385828a),开始挖掘 SciPy 和 NumPy 代码库。
</random-sampling-with-scipy-and-numpy-part-ii-234c2385828a> [## 用 SciPy 和 NumPy 随机抽样第二部分
towardsdatascience.com](/random-sampling-with-scipy-and-numpy-part-ii-234c2385828a)
# 使用 SciPy 和 NumPy 的随机抽样:第二部分
> 原文:<https://towardsdatascience.com/random-sampling-with-scipy-and-numpy-part-ii-234c2385828a>

照片由[андрейсизов](https://unsplash.com/@alpridephoto?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)在 [Unsplash](https://unsplash.com/s/photos/algorithm?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上拍摄
## 奇特的算法,源代码演练和潜在的改进
[在第一部分](/random-sampling-using-scipy-and-numpy-part-i-f3ce8c78812e)中,我们讲述了逆变换采样(ITS)的基础知识,并创建了我们自己的 ITS 纯 python 实现来从标准正态分布中采样数字。然后,我们比较了我们稍微优化的功能和内置的 SciPy 功能的速度,发现我们有些欠缺——慢了`40x`倍。
在这一部分中,我们的目的是通过挖掘 SciPy 和 NumPy 代码库的相关部分来解释为什么会出现这种情况,看看这些速度改进在哪里表现出来。总的来说,我们会发现它由以下几个部分组成:
* 由于是用 Cython 或直接用 C 编写的,所以函数速度更快
* 与我们屡试不爽的逆变换采样相比,更新的采样算法速度更快
## 我们如何在 SciPy 中生成正态分布的随机样本?
下面是从标准正态分布生成随机数`1,000,000`的代码。
43.5 ms ± 1.2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
所以函数`rvs`在刚刚超过`40ms`的时间内生成`1,000,000`个样本。为了进行比较,我们使用基于逆变换采样原理的算法在`2.3s`中平均实现了这一点。为了理解速度差异,我们将不得不深入到那个`rvs`方法中。
值得注意的是,*(一般而言)*使用 SciPy,逻辑的核心包含在下划线方法中——所以当我们想要查看`rvs`时,我们真的想要查看`_rvs`的代码。非下划线方法通常在传递给下划线方法之前执行一些参数类型检查或默认设置。
在我们开始之前,让我们先简要概述一下 SciPy 在库中组织分发功能的方式。
## RV _ 一般和 RV _ 连续
SciPy 发行版是从一个简洁的继承结构中创建的,它具有:
* `rv_generic`作为顶层类,提供`get_support`和`mean`等方法
* `rv_continuous`和`rv_discrete`用更具体的方法继承它
所以在上面的例子中,我们将我们的正态分布类`snorm`初始化为`stats.norm()`,这实际上是创建了一个`rv_continuous`的实例,它继承了`rv_generic`的很多功能。更具体地说,我们实际上创建了一个`rv_frozen`实例,它是`rv_continuous`的一个版本,但是分布的参数是固定的(例如,平均值和方差)。记住这一点,现在让我们看看`rvs`方法的内部。
## 房车
当我们在`snorm.dist._rvs`上运行`??`魔术时,我们看到下面的代码片段:
?? snorm.dist._rvs
def _rvs(self, size=None, random_state=None):
return random_state.standard_normal(size)
因此,似乎在我们创建的 distribution 类中的某个地方,我们已经在某个地方分配了一个`random_state`对象,该`random_state`对象包含一个方法,该方法可以返回根据标准正态分布分布的数字。
**原来,吐出这些随机数的** `**random_state**` **物体其实来自 NumPy。**我们通过查看 [rv_generic](https://github.com/scipy/scipy/blob/b5d8bab88af61d61de09641243848df63380a67f/scipy/stats/_distn_infrastructure.py#L627) 的源代码来了解这一点,它在其`__init__`方法中包含对一个名为 [check_random_state](https://github.com/scipy/scipy/blob/e3cd846ef353b10cc66972a5c7718e80948362ac/scipy/_lib/_util.py#L209) 的 SciPy util 方法的调用,如果还没有传递种子,该方法将把`random_state`设置为`np.random.RandomState`的实例。下面是这段代码:
## 交给 NumPy
因此,似乎提供如此高速采样的“魔法”实际上存在于 NumPy 而非 SciPy 中。这不应该太令人震惊,因为 SciPy 是故意构建在 NumPy 之上的,以防止两个库可能提供相同特性的重复和不一致。这在 SciPy 简介文档的第一行[中有明确说明,这里是](https://docs.scipy.org/doc/scipy/tutorial/general.html):
SciPy 是建立在 Python 的 NumPy 扩展之上的数学算法和便利函数的集合
要了解这是怎么回事,我们可以看看这里的`np.random.RandomState`类。从使用中我们可以看出:
* 用`cdef`代替`def`进行函数声明
* 一个`.pyx`文件扩展名代替。巴拉圭
这两者都表明该函数是使用 [Cython](https://cython.readthedocs.io/en/latest/index.html) 编写的——这是一种非常类似于 python 的语言,允许以几乎 Python 的语法编写函数,然后编译成优化的 C/C++代码以提高效率。正如他们自己在文档中所说的[:](https://cython.readthedocs.io/en/latest/src/quickstart/overview.html)
*“源代码被翻译成优化的 C/C++代码,并被编译成 Python 扩展模块。这允许非常快速的程序执行和与外部 C 库的紧密集成,同时保持 Python 语言众所周知的高程序员生产率。”*
在本课程中,我们需要了解两件事来理解采样过程:
* 它是如何生成均匀分布的随机数(PRNG)的
* 它使用什么算法将这些均匀分布的数字转换成正态分布的数字
## PRNG
正如在第一部分中提到的,生成随机样本需要某种形式的随机性。几乎总是这不是真正的随机,而是由“伪随机数发生器”(PRNG)产生的一系列数字。正如采样算法一样,有多种 PRNGs 可用,这里使用的具体实现在`np.random.RandomState`的 `[__init__](https://github.com/numpy/numpy/blob/b991d0992a56272531e18613cc26b0ba085459ef/numpy/random/mtrand.pyx#L180)` [方法](https://github.com/numpy/numpy/blob/b991d0992a56272531e18613cc26b0ba085459ef/numpy/random/mtrand.pyx#L180)中详细介绍:
如上所示,当该类被初始化时,默认的 PRNG 被设置为[梅森扭结](https://en.wikipedia.org/wiki/Mersenne_Twister)算法的一个实现——如此命名是因为它的周期长度为[梅森素数](https://en.wikipedia.org/wiki/Mersenne_prime)(它在开始重复自身之前可以生成的随机数的数量)。
## 取样过程
沿着类`np.random.RandomState`的代码往下,我们看到`[standard_normal](https://github.com/numpy/numpy/blob/b991d0992a56272531e18613cc26b0ba085459ef/numpy/random/mtrand.pyx#L1344)`的定义调用了一个叫做`legacy_gauss`的东西。`legacy_gauss`函数的 C 代码在这里是,为了便于查看,我们将在这里显示它:
正如在 Wiki 上的[实现部分](https://en.wikipedia.org/wiki/Marsaglia_polar_method#Implementation)中所看到的,这正是 [Marsaglia 极坐标方法](https://en.wikipedia.org/wiki/Marsaglia_polar_method#Implementation)的 C 实现,用于在给定一串均匀分布的输入数的情况下,从正态分布中生成随机样本。
## 概述
我们已经经历了很多,所以有必要回顾一下,确保一切都非常清楚。我们已经从:
* 一个用 python 写的名为`_rvs`的函数启动
* 一个 NumPy 类`np.random.RandomState`,用 Cython 写的,它
* 使用 Mersenne Twister 算法生成均匀分布的数字,然后
* 将这些数字输入用 C 编写的函数`legacy_gauss`,该函数使用 Marsaglia 极坐标法生成正态分布的样本
以上强调了构建 SciPy 和 NumPy 的聪明人为了生成高效代码所付出的努力。在基础设施的更深层尽可能接近 C 语言(为了速度)之前,我们有一个可以被用户(比如你和我)调用的顶层,它是用 python 编写的(为了 python 的“程序员生产力”)。
## 为什么 SciPy 调用 NumPy 函数被视为“遗留”?
因为采样是数学/计算机科学的一个分支,仍然在向前发展。与其他领域不同,在这些领域中,某些原则在几个世纪前就已达成一致,并且从那以后没有发生变化,有效地对各种分布进行采样仍然有新的发展。随着新的开发得到测试,我们希望更新我们的默认流程,以纳入这些进步。
这正是 2019 年 7 月 NumPy 1.17.0 所发生的事情,当时[他们引入了 2 个影响采样的新功能](https://numpy.org/devdocs/release/1.17.0-notes.html):
* 一种新的默认伪随机数发生器(PRNG)的实现:[麦丽莎·奥尼尔的 PCG 算法家族](https://www.pcg-random.org/index.html)
* 一个新的采样过程的实现:金字形神塔算法
然而,由于对 prng 向后兼容性的期望,他们没有创建突破性的改变,而是引入了一种新的方式来启动 prng,并将旧的方式切换到引用“遗留”代码。
这里提到的向后兼容性是指在给定相同种子的情况下,希望 PRNG 函数生成相同的随机数字符串。两种不同的算法不会产生相同的随机数,即使它们被给予相同的种子。这种再现性对于测试尤其重要。
看来 SciPy 还没有升级来利用这些新的发展。
## 我们能打败西皮吗?
鉴于我们现在所知道的关于在 SciPy 中如何实现正态分布抽样的知识,我们能战胜它吗?
答案是肯定的——通过利用 NumPy 为我们实现的最新采样技术。下面是采样的一个实现,其中我们:
* 使用最新的 PRNG
* 使用新的金字形神塔算法将这些数字转换成正态分布的样本
test scipy speed
%timeit snorm.rvs(size=n)51 ms ± 5.08 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)# test numpy speed
%timeit nnorm.normal(size=n)24.3 ms ± 1.84 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
所以看起来我们现在已经和 SciPy 一样快了 NumPy 在他们的发布中强调了这是预计的 2-10 倍。
## 结论:这个有多大用处?
说到实现定制分布抽样:非常有用。我们现在完全理解了追求 SciPy 式采样速度的决定,并且可以适当地实现定制分布采样。我们可以:
* 坚持使用第一部分中的纯 python 逆采样转换实现(毕竟,在大多数上下文中,`2s`对于`1,000,000`的示例来说并不坏)
* 编写我们自己的采样程序——最好用 C 或 Cython 编写这个采样程序——这不是一个小问题
在下一部分中,我们将研究如何做到这一点——在 SciPy 基础结构中实现一个高效的定制分布采样函数。这给了我们两全其美的东西——既可以灵活地实现我们选择的精确分布,又可以利用我们从`rv_generic`和`rv_continuous` SciPy 类继承的高效且编写良好的方法。
# 使用 SciPy 和 NumPy 的随机抽样:第三部分
> 原文:<https://towardsdatascience.com/random-sampling-with-scipy-and-numpy-part-iii-8daa212ce554>

由[米克·豪普特](https://unsplash.com/@rocinante_11?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)在 [Unsplash](https://unsplash.com/s/photos/numbers?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上拍摄
## 在 SciPy 中实现自定义分布采样
在前两部分([第一部分](/random-sampling-using-scipy-and-numpy-part-i-f3ce8c78812e)、[第二部分](/random-sampling-with-scipy-and-numpy-part-ii-234c2385828a))中,我们快速介绍了采样需要什么,然后深入研究了 NumPy 和 SciPy 的源代码,以准确理解这在现代 python 库中是如何实现的。
当我们通常不关心底层过程时,所有这些看起来都是多余的——我们很高兴智能体操由巧妙构建的导入库来处理,并让我们专注于利用输出来完成更有洞察力的任务(如蒙特卡洛模拟)。
然而,有时我们会遇到标准库不太适合的用例。下面是一个这样的例子,提供了一个理解这两者的例子:
* 采样(尤其是逆变换采样)背后的理论思想
* 这种采样过程的程序实现
可以在一个不可行的缓慢实现和一个几乎可以与 NumPy 和 SciPy 背后的聪明人的优化工作相媲美的实现之间做出区别。
## 为什么我需要从自定义分布中取样?
在很多场合。而在正常情况下(请原谅这个双关语),默认的做法是:
* 收集数据并绘制直方图
* 得出数据大致正常的结论(即呈钟形)
* 拟合一个合适的正态分布并完成它
在有些情况下,这并不能完全解决问题。下面是我的特例,但可以推广到任何一个预先打包的发行版,不管参数化得多好,都不够好。
## “保方差尾肥”
这听起来比实际上要美好得多。[几何布朗运动(GBM)](https://en.wikipedia.org/wiki/Geometric_Brownian_motion) 由以下方程定义:

作者图片
简而言之,在某个时间增量`dt`内,我们的过程值`S`的变化由两部分组成:
* 一些常数确定性漂移项
* 一些随机项
重要的是,GBM 的特点是随机元素呈正态分布——`dW`元素被称为[维纳过程](https://en.wikipedia.org/wiki/Wiener_process)或更常见的“随机游走”。我的问题如下——我想模拟这些“随机路径”的负载,但我不想从正态分布中取样我的随机变化。
相反,我想创建自己的“定制”分布——特别是我想要一个方差保持变换(即方差仍然是标准正态的`1`),但该分布有着丰富的尾部(过度峰度)。那么问题就变成了:
## 我如何在 SciPy/NumPy 框架中做到这一点?
在开始之前,让我们先快速了解一下以下 pdf 之间的区别:
* 标准正态分布
* 我希望从中取样的分布
为了创建我想要的厚尾分布,我将利用[詹森不等式](https://en.wikipedia.org/wiki/Jensen's_inequality)。具体来说,我将利用这样一个事实,即如果我们取两个平均方差为`1`的正态分布的平均密度,那么我们将得到一个“身材更高”和“尾部更胖”的分布,即峰度更大,但仍保持像标准正态分布一样的方差`1`。
更清楚地说,我们的厚尾分布将是通过平均得到的 pdf:
* 有方差的正态分布`1 + e`
* 方差为正态分布`1 - e`
其中`e`被限制在`[0,1]`之间。让我们画出这个图,这样我们就可以看到当我们改变`e`与标准正态分布相比时,这些分布是什么样子。

作者图片
如上所述,我们的平均分布显示了更高的身体和更胖的尾巴。特别是,我们看到所有的定制分布在几乎相同的点上与标准正态分布交叉——肩部大约为+/-0.66,尾部大约为+/-2.13。事实上,我们可以用这个特性来定义当我们谈论“尾部”时,我们实际上指的是概率分布的哪一部分,而不仅仅是一个抽象的概念。
更专业地说,尾部是由凸方差概率分布的密度定义的。简单来说,一旦我们改变方差(而不是一个常数`1`),在上面的分布中+/-2.13 之外的密度增加了。
这与通常流传的关于厚尾分布如何与峰度相关的观点非常吻合——因为峰度只是四阶矩,或者换句话说,是方差的方差。改变方差(通过我们上面的分布平均),我们开始得到厚尾的 pdf。
现在我们知道了我们要实现的发行版,让我们来实现它。
## 尝试 1:简单,幼稚但缓慢(真的很慢)
尽管这种方法太慢了,根本不能成为真正的解决方案,但 SciPy 内置了所有这些功能,这还是很了不起的。方法是这样的:
* 创建一个从`rv_continuous` SciPy 类继承的定制分发类
* 只需定义反映我们所追求的定制发行版 pdf 的`_pdf`方法
* SciPy 做了所有其他事情来允许我们从这个定制发行版中进行采样
要快速回顾一下什么是`rv_continuous`类或者 SciPy 的分布结构,那么最好[跳回第二部分](/random-sampling-with-scipy-and-numpy-part-ii-234c2385828a)快速回顾一下`rv_continuous`是如何继承`rv_generic`的。否则,让我们继续写我们天真的实现,`naive_cust_dist`:
现在我们已经定义了它,让我们做两件事:
* 从中抽取样本,检查我们从这些样本中生成的直方图是否与我们定义的 pdf 一致
* 快速测试我们抽取这些样本并与 SciPy 的嵌入式正态分布采样进行比较需要多长时间

作者图片
上面显示了从我们的自定义类`naive_cust_dist`中采样`1,000`数字得到的直方图,该自定义类实现了我们想要从中采样的 pdf。正如我们所见,直方图很大程度上与理论 pdf(绿线)一致,这与标绘的标准正态 pdf 明显不同。
所以理论上就是这样。我们已经实现了自定义采样,只定义了`_pdf`方法,其余的由 SciPy 完成。
## “其余的”是什么?
正如在第一部分中提到的,有许多不同的方法从一个分布中抽取样本。然而,它们都没有单独将 pdf 作为输入,并使用它来生成随机数,如果我们将它们绘制成直方图(如上),我们将恢复相同的 pdf。相反,我们需要:
* 巧妙的算法(例如,像金字形神算法这样的拒绝采样算法)
* 利用逆变换采样的逆累积分布函数(cdf)
算法可以是特定于发行版的,当实现定制发行版时,SciPy 使用逆变换采样,这需要后者:逆 cdf。因此,“其余”包括:
* 根据给定的`_pdf`计算`_cdf`
* 从 cdf 计算【SciPy 给逆向 cdf 起的名字——“百分点函数”)
* 使用该`_ppf`函数通过使用 PRNG 生成均匀分布的随机数并使用逆变换采样来产生样本
问题如下:
## 做“其余的”是极其缓慢的
下面显示了速度有多慢——从我们的定制发行版中计算一个仅仅是`1,000`的样本就需要花费超过`30s`的时间。
%timeit naive_dist.rvs(size=n)32.6 s ± 4.57 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
我们可以将其与使用 SciPy 从正态分布中采样的`1,000`数字进行比较。
%timeit stats.norm.rvs(size=n)168 µs ± 29.1 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
所以要明确的是,我们几乎比内置的 SciPy 采样方法慢`200,000x`。然而,有问题的不仅仅是速度有多慢的相对性质——从绝对意义上来说,仅用`30s`来采样`1,000`数字是不可行的,特别是如果我们想要做一些类似于采样`10,000`路径的事情,其中每条路径都由`1,000`步骤组成。我们不能每次想运行模拟都等`3.5`天。
## 为什么这么慢?
在使用我们从第一部分和第二部分中学到的知识来创建一个更好的解决方案之前,让我们快速找出为什么内置的 SciPy 功能如此之慢。要回答这个问题,我们需要深入研究 SciPy 源代码,找到`_ppf`方法——特别是它调用的`_ppf_single`方法(像 NumPy 这样命名 SciPy 是为了便于快速计算数字集合)。下面是代码片段,但是如果你喜欢自己阅读 Github 源代码,你可以点击这里:
换句话说,以上是逆变换采样的实现,其利用求根算法( [Brent 的方法](https://en.wikipedia.org/wiki/Brent's_method))来将给定随机数`q`从均匀分布转换成来自我们选择的定制分布的数。即使不深入算法的本质,这个过程也需要:
* 两个`while`循环
* 数值积分函数
应该表明这种功能的速度可能不足。相反,我们想避开这个过程,实现我们自己的`_ppf`。换句话说,让我们把在第一部分中创建的逆向 cdf 放到 SciPy 发行框架中。
## 尝试 2:可行的解决方案
所以我们将返回到创建我们自己的分发类,它继承了 SciPy 中的通用`rv_continuous`类。然而,除了定义一个`_pdf`方法,我们还要定义一个`_ppf`方法。
怎么会?我们将实现第一部分中的反变换采样方法。这意味着当我们实例化我们的类时,我们需要做更多的工作。更具体地说,我们需要通过以下方式创建`ppf`:
* 首先通过对`pdf`进行累积求和来创建`cdf`
* 使用 SciPy 的内置`interp1d`函数来反转它,最后得到我们的反转`cdf`
一旦我们有了它,我们就可以将它设置为我们的`_ppf`方法,以防止 SciPy 从方法 1 开始进行数学体操,这使得它慢得不可行。
现在我们已经定义了它,让我们试一试。就像之前一样,让我们检查它是否生成了与我们定义的`pdf`一致的样本,然后我们可以检查我们是否得到了速度方面的可行解决方案。

作者图片
因此,我们似乎又一次实现了期望的采样分布。现在让我们来看看额外的代码和复杂性在加速方面是否值得。
%timeit my_dist.rvs(size=1000)1.84 ms ± 512 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)%timeit stats.norm.rvs(size=1000)116 µs ± 5.21 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)%timeit my_dist.rvs(size=10000000)12.8 s ± 1.62 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
因此,我们仍然比 SciPy 慢得多(大约在`15x`),但这比我们以前的`200,00x`方法有了巨大的改进。我们现在有了一个解决方案,可以在`15s`多一点的时间内产生`10,000,000`个随机数——与我们之前实现的`3.5`天相比,这个时间要合理得多。
## 结论
SciPy 提供了广泛的打包发行版,可以快速采样。这种采样是特定于发行版的,是为了利用 C 语言的速度、优化的 python 代码和最有效的采样过程而编写的。虽然这是一个无价的资源,在大多数情况下将提供一个充分的解决方案,但确实有需要定制分布采样的时候。
更深入地了解 SciPy 的分发体系结构,特别是它如何依赖于 NumPy,如上所示,可以决定一个精确但不可行的缓慢解决方案和一个与 SciPy 的超高速实现相差不远的解决方案。
# 随机种子和再现性
> 原文:<https://towardsdatascience.com/random-seeds-and-reproducibility-933da79446e3>
## 在 Python、Numpy 和 PyTorch 中设置您的实验

克里斯蒂安·朱德雷在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上拍摄的照片
# 动机
> “程序员最可怕的噩梦是什么?”
就我而言,我可以有把握地说,作为一名程序员,我最糟糕的噩梦是一段**代码,它的行为就好像是随机的**,每次运行它都会产生**不同的结果,即使我给它**完全相同的输入**!**
实际上有一个著名的关于精神错乱的定义:
> **“疯狂就是一遍又一遍地做同样的事情,却期待不同的结果。”**
尽管人们通常认为这是阿尔伯特·爱因斯坦的功劳,但研究表明事实并非如此。但是,撇开引用的作者不谈,事实仍然是:一遍又一遍地给一段代码输入相同的输入,每次都得到不同的结果,会让你发疯的:-)
> 本帖包含部分转载自我的书内容:**[**深度学习用 PyTorch 循序渐进:初学者指南**](https://pytorchstepbystep.com) **。****
# **(伪)随机数**
> **"一个人怎么可能调试和修复这样的东西?"**
**幸运的是,对于我们程序员来说,我们不必处理真正的随机性,而是处理伪随机性。**
> **“什么意思?”**
**嗯,你知道,随机数并不完全是随机的……它们实际上是伪随机的,也就是说,一个数字生成器抛出一系列数字,看起来像是随机的。但是是**不是**,真的。**
**这种行为的好处是我们可以**告诉生成器启动一个特定的伪随机数序列**。在某种程度上,它的工作原理就好像我们告诉生成器:“*请生成序列#42,*”,它就会溢出一个数字序列。**
**这个数字 42 的作用类似于序列的**索引,被称为**种子**。每次我们给它**相同的种子**,它都会生成**相同的数字**。****
> **"同样的老种子,同样的老号码。"**
**这意味着我们有两个世界最好的**:一方面,我们做**产生**一个数字序列,对于所有意图和目的来说,**被认为是随机的**;另一方面,我们有能力复制任何给定的序列。我确信您能够理解这对于调试目的和避免精神错乱是多么方便:-)。****
****此外,你可以保证其他人能够复制你的结果。想象一下,运行从博客帖子或书籍中获得的代码,每次都得到不同的输出,不得不怀疑它是否有问题,这是多么令人讨厌的事情。****
****在学习一个新主题时,您最不需要的就是失去平衡,因为每次运行一些代码时,您都会得到不同的结果(除了有一个种子集之外,代码很可能完全正确)。但是,通过正确设置随机种子,您和我,以及运行代码的每个人,都可以获得完全相同的输出,即使这涉及到生成随机数据!****
# ****生成随机数****
****虽然种子叫做*随机*,但是它的*选择*肯定不是!通常,你会看到选择的随机种子是 [42](https://bit.ly/2XZXjnk) ,这是一个人可能选择的所有随机种子中[(第二个)最不随机的](https://bit.ly/3fjCSHR)。****
****因此,我们也在本帖中向将种子设定为 42 的悠久传统致敬。在纯 Python 中,您使用`[**random.seed()**](https://docs.python.org/3/library/random.html#random.seed)`来设置种子,然后您可以使用`[**random.randint()**](https://docs.python.org/3/library/random.html#random.randint)`来绘制一个随机整数,例如:****
********
****设定种子后抽取四个随机整数。图片作者。****
****看到了吗?*完全决定论*!一旦你将随机种子设置为 42(显然!),生成的前四个整数依次是 10、1、0 和 4,无论您是一个接一个地生成它们,还是在一个列表中理解它们。****
> ****如果你对生成本身感到好奇,Python 的 random 模块使用了 **Mersenne Twister 随机数生成器**,这是一个**完全确定性算法**。这意味着该算法对于解决再现性问题来说很棒,但是完全不适合用于加密目的。****
****数字**发生器**有一个**内部状态**,它跟踪从特定序列中提取的最后一个元素(每个序列由其对应的种子标识),因此它知道从哪里挑选下一个元素。****
********
****种子#42 的状态序列和相应的生成号。图片作者。****
****如果您愿意,可以使用`[**random.getstate()**](https://docs.python.org/3/library/random.html#random.getstate)`和`[**random.setstate()**](https://docs.python.org/3/library/random.html#random.setstate)`来检索(和设置)该状态:****
********
****让生成器“忘记”抽取了一个数字!图片作者。****
****不出所料,第一个数字还是 10(因为我们使用了同一个种子)。此时,发生器的内部状态记录了从序列中只抽取了一个数字。我们将这个州命名为`**first_state**`。****
****所以我们又画了一个,我们得到了,不出所料,数字 1。内部状态会相应地更新,但我们会将其设置回绘制第二个数字之前的状态。****
****现在,如果我们绘制另一个数字,我们将再次得到数字 1,因为我们通过更新它的内部状态,迫使生成器“*忘记*”最后绘制的数字。****
****这些数字看起来不再是随机的了,嗯?****
> ****"是的,但是我必须问…那个州有什么?"****
****很高兴你问了。它只是一个元组!第一个元素是 version (3),第二个元素是 625 个整数的长列表(内部状态),最后一个元素通常是`**None**`(你可以暂时放心地忽略它)。****
********
****生成器的内部状态(仅显示第一行和最后一行)。图片作者。****
****看到最后那个“1”了吗?那是列表的第*625 个元素,它作为其他元素*的索引*—**实际内部状态由前 624 个元素**表示。记住这一点,我们将很快回到它!*****
> ****"好吧,那么我们很好,现在一切都完全可以复制了?"****
****我们还没有到那一步…如果你查看 Python 的“ [*关于再现性的注释*](https://docs.python.org/3/library/random.html#notes-on-reproducibility) ”,你会看到:****
> ****“通过重用种子值,只要多个线程没有运行,相同的序列就应该可以在不同的运行**之间重现。”******
****所以,如果你是多线程的,再现性就拜拜了!从好的一面来看,Python 的(伪)随机数生成器(从现在开始姑且称之为 RNG)有两个保证(从“*注释*”转录而来):****
* ****如果添加了新的播种方法,那么将提供向后兼容的播种机。****
* ****当兼容的播种机被给予相同的种子时,生成器的`random()`方法将继续产生相同的序列。****
> ****“好了,现在我们好了吗?”****
****抱歉,但是不行!Python 自己的 RNG 是**而不是唯一一个**你可能需要设置种子的。****
## ****Numpy****
****如果你也使用 Numpy,你需要为它自己的 RNG 设置一个种子。为此,您可以使用`[**np.random.seed()**](https://numpy.org/doc/stable/reference/random/generated/numpy.random.seed.html)`:****
********
****Numpy 中的随机数(传统)。图片作者。****
****从上面的例子可以看出,Numpy 的 RNG 的行为方式与 Python 的 RNG 相同:一旦设置了种子,生成器就输出完全相同的数字序列,6、3、7 和 4。****
****虽然上面的代码是最常见的“T7”,而且许多人一直这样使用它(包括我自己,被指控有罪),但它已经被认为是**遗留的**代码。****
****从 1.17 开始,更近的 Numpy 版本使用不同的方式生成(伪)随机数:**首先创建一个生成器**,然后**从中抽取数字**。用户可以使用`[**np.random.default_rng()**](https://numpy.org/doc/stable/reference/random/generator.html#numpy.random.default_rng)`创建默认生成器:****
********
****Numpy 中的随机数(最新)。图片作者。****
> ****“等等,现在数字不一样了吗?”****
****是的,它们**不同**,尽管我们使用的是**相同的种子**,42。****
> ****“那是为什么?”****
****数字不同是因为**发生器与**不同,也就是说,它使用了**不同的算法**。Numpy 的遗留代码使用 *Mersenne Twister (MT)* 算法,就像 Python 的 random 模块一样,而 **Numpy 的新默认生成器**使用 *Permute 同余生成器(PCG)* 算法。****
****但是,事实证明,即使 Numpy 的遗留代码和 Python 的随机模块使用相同的算法,并且我们在它们两者中使用相同的种子,生成的数字仍然*不同*!****
> ****“你一定是在和我开玩笑!为什么?!"****
****我理解你可能会感到沮丧,这种差异归结为*Python 的随机模块和 Numpy 处理生成器内部状态*中讨厌的“索引”的方式。如果你对这方面的更多细节感兴趣,请查看下面的旁白——否则,请随意跳过它。****
## ****匹配内部状态****
****如果我们使用相同的 624 个数字的列表来更新两个生成器的状态,同时将“索引”设置为 624(就像 Numpy 默认设置的那样),这就是我们得到的结果:匹配序列!****
********
****匹配 Python 的 random 和 Numpy 的内部状态。图片作者。****
*****正如您在上面的代码中看到的,还可以分别使用* `[***set_state()***](https://numpy.org/doc/stable/reference/random/generated/numpy.random.set_state.html)` *和* `[***get_state()***](https://numpy.org/doc/stable/reference/random/generated/numpy.random.get_state.html)` *来检索或设置 Numpy 的生成器的内部状态,并且状态本身在其元组中有更多的元素(‘mt 19937’代表 Mersenne Twister (MT)及其范围(顺便说一下,2 ⁹⁹ ⁷-1)),但是我们不会对此进行任何深入的研究。毕竟,在 Numpy 中,您不太可能需要修改生成器的内部状态…*****
****还有一点需要指出,转自 Numpy 的`[**Generator**](https://numpy.org/doc/stable/reference/random/generator.html#numpy.random.Generator)`文档,一个标题为“ ***无兼容性保证*** ”的章节:****
> ****`Generator`不提供版本兼容性保证。特别是,随着更好的算法的发展,比特流可能会改变。****
****谁说确保再现性很容易?不是我!****
> ****请记住:为了真正的再现性,您需要使用相同的随机种子、相同的模块/包和相同的版本!****
****是时候换一个不同的包了!****
## ****PyTorch****
****就像 Numpy 一样,PyTorch 也有自己设置种子的方法,`[**torch.manual_seed()**](https://bit.ly/3hOwklL)`,为所有设备(CPU 和 GPU/CUDA)设置一个种子:****
********
****PyTorch 中的随机数(CPU)。图片作者。****
****正如您可能已经预料到的,生成的序列又是不同的。**新包装,新顺序**。****
****但还有更多!如果你在一个**不同的设备**中生成一个序列,比如你的 GPU ( `**'cuda'**`),你会得到**又一个序列**!****
********
****PyTorch 中的随机数(CUDA/GPU)。图片作者。****
****在这一点上,这不应该是一个惊喜,对不对?此外,PyTorch 关于[再现性](https://pytorch.org/docs/stable/notes/randomness.html)的文档非常简单明了:****
> ****“不能保证 PyTorch 版本、单个提交或不同平台的结果完全可重复。此外,即使使用相同的种子,CPU 和 GPU 执行之间的结果也可能不可重复。”****
****因此,我相应地更新了上一节的建议:****
> ****请记住:为了真正的再现性,您需要使用相同的随机种子、相同的模块/包、相同的版本、相同的平台、相同的设备,甚至可能是相同的驱动程序(例如,您的 GPU 的 CUDA 版本)!****
****也许你注意到了上面输出中的一个`**Generator**` …不出所料,PyTorch 也使用生成器,就像 Numpy 一样,并且那个生成器是 **PyTorch 的默认生成器**。我们可以使用`[**torch.default_generator**](https://pytorch.org/docs/stable/torch.html#torch.torch.default_generator)`检索它,并使用`[**manual_seed()**](https://pytorch.org/docs/stable/generated/torch.Generator.html#torch.Generator.manual_seed)`方法设置它的种子:****
********
****PyTorch 的默认生成器。图片作者。****
****您也可以创建另一个生成器,并将其用作其他函数或对象的参数:****
********
****在 PyTorch 中创建和使用生成器。图片作者。****
****有一种情况下使用自己的生成器特别有用:数据加载器中的采样。****
## ****数据加载器****
****在为训练集创建数据加载器时,我们通常将其参数`**shuffle**`设置为`**True**`(因为在大多数情况下,混合数据点可以提高梯度下降的性能)。这是一种非常方便的**混洗数据**的方式,它是使用引擎盖下的`[**RandomSampler**](https://pytorch.org/docs/stable/data.html#torch.utils.data.RandomSampler)`实现的。每次请求一个新的小批量时,它会随机采样一些索引,并返回对应于这些索引的数据点。****
****即使不涉及混洗,也要使用`[**SequentialSampler**](https://pytorch.org/docs/stable/data.html#torch.utils.data.SequentialSampler)`,这在用于验证集的数据加载器中是很典型的。在这种情况下,每当请求新的小批量时,该采样器简单地按顺序返回一系列索引,并且返回对应于那些索引的数据点。****
****从 PyTorch 1.7 开始,为了保证再现性,**我们需要给** `[**DataLoader**](https://pytorch.org/docs/stable/data.html#torch.utils.data.DataLoader)`分配一个生成器,所以在相应的采样器中使用它(当然前提是它使用生成器)。****
********
****将生成器分配给数据加载器。图片作者。****
****实际上,我们可以从加载器中检索采样器,检查其初始种子,并根据需要手动设置不同的种子:****
********
****手动设置装载机发电机的种子。图片作者。****
****我们将在几个部分中这样做,同时编写一个函数来使用"*一个种子来统治所有的* " :-)****
****为数据加载器分配一个生成器将会覆盖您,但是只有当您在主进程 ( `**num_workers=0**`,默认)中加载数据时**。如果您想要使用**多重处理来加载数据**,也就是说,指定更大数量的工人,您还需要**为您的数据加载器**分配一个** `**worker_init_fn()**` **,以避免您的所有工人绘制完全相同的数字序列。让我们看看为什么会出现这种情况!******
**PyTorch 实际上可以在上面的情况下照顾自己——[它用不同的编号](https://pytorch.org/docs/stable/data.html#randomness-in-multi-process-data-loading)播种每个 worker,也就是`**base_seed + worker_id**`,但是它不能照顾其他的包(比如 Numpy 或者 Python 的 random 模块)。**
**我们可以使用作为参数传递给数据加载器的`**seed_worker()**`函数中的一些打印语句来看看发生了什么:**
****
**看一眼每个工人使用的种子。图片作者。**
**有两个工人,(0)和(1),每次调用一个工人执行任务时,`**seed_worker()**`函数打印 PyTorch、Numpy 和 Python 的 random 模块使用的种子。**
**你可以看到 PyTorch 使用的种子刚刚好——第一个工人使用一个以 55 结尾的数字;第二个工人的,一个以 56 结尾的号码,不出所料。**
> **但是 Numpy 和 Python 的 random 模块使用的**种子**是跨 worker 的**相同,这也是我们要**避免**的。不过,不同模块之间的种子可以相同。****
**幸运的是,有一个简单的解决方法:我们**在** `**seed_worker()**` **函数**中包含一些种子设置语句,使用 PyTorch 的初始种子(并将其调整为 32 位整数),而不是打印语句:**
****
**适当地播种工人。图片作者。**
**现在每个工人将为 PyTorch、Numpy 和 Python 的 random 模块使用不同的种子。**
> **“好的,我明白,但是如果我只使用 PyTorch,为什么我需要播种其他的包呢?”**
## **播种 PyTorch 是不够的!**
**您可能认为,如果您没有在代码中显式使用 Numpy 或 Python 的 random 模块,您就不需要关心为它们设置种子,对吗?**
**也许你没有,但是最好**稳妥一点,为一切设置种子:PyTorch,Numpy,甚至 Python 的 random 模块**,这就是我们在上一节中所做的。**
> **“那是为什么?”**
**原来,PyTorch *可能*用的是不属于自己的发电机!老实说,当我发现这件事的时候,我也很惊讶!听起来可能很奇怪,在 0.8 之前的 **Torchvision 版本中,仍然有一些**代码依赖于 Python 的随机模块,而不是 PyTorch 自己的随机生成器**。当使用一些用于数据扩充的随机转换时,问题就出现了,比如`**RandomRotation()**`、`**RandomAffine()**`等等。****
## **库达**
**手动设置 PyTorch 的种子对 CPU 和 CUDA/GPU 都有效,正如我们在前面几节中看到的那样。但是由 **CUDA 卷积运算**使用的 cuDNN 库仍然可能是**非确定性**行为的来源。**
**事实证明,根据所提供的参数以及底层硬件和环境,库试图使用最快的算法。但是我们可以通过禁用这个所谓的基准特性,将`**torch.backends.cudnn.benchmark**` 设置为`**False**.`,来强制**确定性地选择一个算法****
**虽然使用上述配置可以使算法的 ***选择*具有确定性**,但是**算法本身*可能不是*的**!**
> **“哦,来吧!”**
**我听到了。为了解决这个问题,我们还需要做另一个配置:将`**torch.backends.cudnn.deterministic**` 设置为`**True**.`**
****
**使 CUDA 卷积运算具有确定性。图片作者。**
> **使用 CUDA 的可再现性还有其他含义:由于 CUDA 版本 10.2 中引入的变化,RNN 和 LSTM 层也可能表现出不确定性行为(详细信息参见[文档](https://docs.nvidia.com/deeplearning/cudnn/release-notes/rel_8.html#rel-840__section_nbs_qgh_fsb))。**
>
> **PyTorch 的文档建议将环境变量`**CUBLAS_WORKSPACE_CONFIG**` 设置为`**:16:8**` 或`**:4096:2**`来实施确定性行为。**
# **合唱:老麦克托奇有一个模型**
> **“老麦克托奇有一个模型,咿呀咿呀**
>
> **在它的模型上,有一些种子,咿呀咿呀**
>
> **这里有一粒种子,那里有一粒种子**
>
> **这里一粒种子,那里一粒种子,到处都是一粒种子**
>
> **老麦克托奇有一个模型,咿呀咿呀哟"**
**你觉得上面这首歌怎么样,来自“*程序员童谣*”?对了,我开玩笑,*那不是真书*,我编的!也许我应该写这样一本书…但是我跑题了!**
**回到我们的主要话题,可能感觉和那首歌一模一样——种子和更多的种子——到处都是种子!**
**要是有就好了…**
> **“一粒种子统治他们所有人!”**
**没有这种事,但是我们可以试试退而求其次:**我们自己的函数设置尽可能多的种子**!下面的代码为 PyTorch、Numpy、Python 的 random 模块、采样器的生成器设置种子;除了配置 PyTorch 的后端使 CUDA 卷积操作具有确定性之外。**
****
**一粒种子统治所有人。图片作者。**
> **“这样够吗?”**
**不一定,不。一些操作*可能*仍然具有不确定性,从而使您的结果不完全可再现。虽然有可能**强制 PyTorch 只使用确定性算法**设置`[**torch.use_deterministic_algorithms(True)**](https://pytorch.org/docs/stable/generated/torch.use_deterministic_algorithms.html#torch.use_deterministic_algorithms)`,但是有一个问题…**
> **“我就知道!”**
**可能你正在执行的一些操作只有**非确定性**算法可用,然后你的代码在被调用时会抛出一个`**RuntimeError**`。出于这个原因,我没有将它包含在上面的 set_seed 函数中——我们没有破坏代码,以确保它的可再现性。**
> **此外,如果您使用的是 CUDA(10.2 或更高版本),除了设置`**torch.use_deterministic_algorithms(True)**`,您还需要设置环境变量`**CUBLAS_WORKSPACE_CONFIG**`,如前一节所述。**
**那些**非确定性算法**可能来自**最意想不到的地方**。例如,在 PyTorch 的文档中,有一条关于在图像中使用**填充**时可能出现再现性问题的警告:**
> **“使用 CUDA 后端时,此操作可能会在其反向传递中引发不确定性行为,这种行为不容易被关闭。请参阅关于再现性的注释以了解背景信息。”**
**让我觉得有点奇怪的是,这么简单的操作会危及可重复性。真不敢相信**
# **随机种子调谐**
> **"(正确的)随机种子是你所需要的!"**
**看起来像个笑话,但是随机种子的选择可能会对模型训练产生影响。一些种子比其他种子“更幸运”,因为它们允许模型训练得更快,或者实现更低的损失。当然,没有办法事先说清楚,也没有,42 不是“*什么是正确的随机种子*”问题的答案:-)**
**如果你对这个话题很好奇,可以查看大卫·皮卡德的论文:“ [*Torch.manual_seed(3407)是你所需要的全部:论深度学习架构中随机种子对计算机视觉*](https://arxiv.org/abs/2109.08203) 的影响”。摘要如下:**
> **“在本文中,我研究了在将流行的深度学习架构用于计算机视觉时,随机种子选择对准确性的影响。我在 CIFAR 10 上扫描了大量种子(多达 104 个),在 Imagenet 上也扫描了较少的种子,使用预先训练的模型来调查大规模数据集。结论是,即使方差不是很大,也很容易找到表现比平均值好得多或差得多的异常值。”**
# **最后的想法**
****再现性很难!****
**我们甚至没有谈论更基本的问题,例如确保你正确使用数据,以避免多年后当别人试图复制你发表的结果时的尴尬(见[莱因哈特和罗格夫的 Excel 大错](https://www.businessinsider.com/reinhart-and-rogoff-admit-excel-blunder-2013-4),也被称为“[如何不擅长经济学](https://theconversation.com/the-reinhart-rogoff-error-or-how-not-to-excel-at-economics-13646)”)!**
**我们只关注(伪)随机数生成器,即使如此,我们也需要考虑 ***许多不同来源的(伪)随机性*** 以确保可再现性。那是大量的工作,但是它值得麻烦。**
> ****确保总是在代码的最开始初始化你的随机种子,以确保(或者尝试!)您的结果的可重复性。****
> **愿你未来的实验完全可复制!**
***如果您有任何想法、意见或问题,请在下方留下评论或通过我的* [*简历链接*](https://bio.link/dvgodoy) *页面联系。***
***如果你喜欢我的帖子,请考虑使用我的推荐页面通过* [*注册一个中级会员*](https://dvgodoy.medium.com/membership) *来直接支持我的工作。对于每一个新用户,我从中获得一小笔佣金:-)***
# R 中的范围:如何使用 Range 函数找到最小值和最大值
> 原文:<https://towardsdatascience.com/range-in-r-how-to-find-min-and-max-values-using-the-range-function-5c3a1f5dffad>
## 通过这 6 个实际例子掌握 R 中的范围函数

照片由 [Vipul Jha](https://unsplash.com/@lordarcadius?utm_source=medium&utm_medium=referral) 在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上拍摄
R 中的 Range 返回一个向量,该向量包含给定参数的最小值和最大值—在统计学中称为 *range* 。您可以将范围视为向量中最低值和最高值之间的间隔。向量可以是任何东西,从数字列表到数据帧列——R 真的不在乎。
实际上,范围是向量中最大值和最小值之间的差,但是计算 R 中的范围只报告区间极值。你可以用一个减去另一个来得到实际的范围。
今天,您将通过大量实际例子了解 R 中的`range()`函数是如何工作的。您将使用常规和字符向量、数据帧和数据帧列,还将看到如何处理缺失值和无穷大值。
# R 中的范围—定义和用法
你想学习如何在 R 中找到一个范围内的值?你来对地方了。在这一节中,我们将检查语法和返回值,它们将作为以后动手操作示例的基础。
## 句法
range(x, na.rm = FALSE)
其中:
* `x` -一个字符或一个数字向量。
* `na.rm` -可选参数,指定在计算范围前是否应移除 NA 值。
## 返回值
R 中的`range()`函数返回一个双精度值或一个字符,这取决于作为参数传递的是数字向量还是字符向量。
# R 范围—示例
本节我们将讨论六个例子,涵盖从基本数字向量到整个数据帧的用法。
## 如何用 R 中的值域求最小值和最大值
下面的代码显示了如何计算数值向量的范围:
arr <- c(-10, -15, 5, 19, 27, 0)
range(arr)
输出:
-15 27
有意义,因为 27 是最大值,而-15 是最小值。
## 在字符向量上使用范围函数
你不会总是和数字数据打交道。您可以计算字符串的范围,该函数将返回按字母顺序排序的第一个和最后一个值:
arr_char <- c("Bob", "Mike", "Kelly", "Sue")
range(arr_char)
输出:
"Bob" "Sue"
## 求缺失值向量的值域
真实世界的数据通常是混乱的,充满了缺失值。如果处理不当,它们会打乱你的计算。这里有一个例子:
arr <- c(-10, -15, NA, 19, 27, 0)
range(arr)
输出:
NA NA
指定`na.rm = TRUE`以避免此问题:
range(arr, na.rm = TRUE)
输出:
-15 27
## 求一个无穷(Inf)值向量的范围
计算错误有时会产生正值或负值的`Inf`值。它们打乱了范围计算,因为它们要么是向量的最小值,要么是向量的最大值:
arr <- c(-10, -15, Inf, 19, 27, 0)
range(arr)
输出:
-15 Inf
指定`finite = TRUE`从范围计算中排除`Inf`:
range(arr, finite = TRUE)
输出:
-15 27
## 在 DataFrame 列上使用 Range 函数
让我们将虹膜数据集存储到变量`df`中:
df <- iris head(iris)

图片 1 —虹膜数据集的头部(图片由作者提供)
您可以通过以下方式计算特定 dataframe 列的范围:
range(df$Sepal.Length)
输出:
4.3 7.9
## 在整个数据帧上使用范围函数
但是如果您需要整个数据集的范围呢?你只能对这个数据集中的数字列进行计算:
df_numeric <- df[c('Sepal.Length', 'Sepal.Width', 'Petal.Length', 'Petal.Width')]
range(df_numeric)
输出:
0.1 7.9
这并不能告诉您太多—您不知道最小值和最大值在哪些列中。使用`sapply()`函数获取各个列的范围:
sapply(df_numeric, function(df_numeric) range(df_numeric))

图片 2-计算整个数据集的范围(图片由作者提供)
# 结论
今天,您已经通过大量的实际例子了解了 R 中的值域是如何工作的。这是一个简单的功能理解,但绝对需要有你的工具带。您现在已经准备好在您的数据分析项目中使用它,因为没有您不能处理的数据类型或用例。
请继续关注后续文章,因为我们将探索其他有用的内置 R 函数。
## 保持联系
* 雇用我作为一个技术作家
* 订阅 [YouTube](https://www.youtube.com/c/BetterDataScience)
* 在 LinkedIn 上连接
*喜欢这篇文章吗?成为* [*中等会员*](https://medium.com/@radecicdario/membership) *继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。*
<https://medium.com/@radecicdario/membership>
*原载于 2022 年 2 月 9 日 https://betterdatascience.com**的* [*。*](https://betterdatascience.com/range-in-r/)
# 在 GCP 使用 Terraform、GitHub Action、Docker 和 Streamlit 进行快速原型制作
> 原文:<https://towardsdatascience.com/rapid-prototyping-using-terraform-github-action-docker-and-streamlit-in-gcp-e623ae3fdd54>
## 使用 CI/CD 工具加速见解共享

马修·布罗德在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的照片
# 介绍
数据科学家和分析师是许多组织的宝贵资源。他们将大部分时间用于理解业务需求,并通过数据收集、争论、清理和建模来寻找见解。支持这些数据专家的工具非常丰富,例如,像 Jupyter 和 Colab 这样的笔记本、Python 或 R 库以及其他数据技术。
另一方面,当涉及到与利益相关者分享重要的发现和有价值的见解时,选择是有限的。对于非技术人员来说,笔记本可能不是最佳选择;在像 Word 和 PowerPoint 这样的办公文档中分享你的发现通常不能描述你的发现的动态和交互性质。对于数据专家来说,找到以合适的格式快速有效地向决策者展示调查结果的最佳方式通常是一件痛苦的事情。
在本文中,我们将展示一种使用 CI/CD(持续集成和持续交付)工具(如 Terraform、GitHub Actions 和 Docker with Streamlit applications)与利益相关方共享数据科学发现的方法,这些工具可以加速原型开发。我们将代码部署到谷歌云平台(GCP)的虚拟机上,但是你也可以将这些技术应用到其他云服务提供商。
## 工作流程概述
本文将指导您创建以下 CI/CD 管道。
1.在本地写 Terraform 文件,推送到 GitHub。
2。GitHub Action 使用 Terraform 实现 GCP 供应自动化。
3。在本地编写 Streamlit app 代码和 Dockerfile,并将代码推送到 GitHub。
4。通过 GitHub Action 将代码部署到 GCP 的虚拟机实例中。

图一。工作流程概述(作者图片)
## 先决条件
实施这些解决方案有四个要求。
● GitHub 账号
● Terraform 安装
● GCP 服务账号
● Python 库
## 开源代码库
GitHub 是一个使用 Git 的基于 web 的版本控制服务。它还提供一项名为 GitHub Actions 的服务。您不需要安装第三方应用程序来自动化测试、发布和将代码部署到生产中的工作流。您可以在 [GitHub 网站](https://github.com/)上创建一个帐户。
## 将(行星)地球化(以适合人类居住)
Terraform 是一个开源的 CI/CD 工具,它将基础设施作为代码(IaC)进行管理,由 HashiCorp 提供。IaC 使用代码来管理和配置基础设施,消除了通过云供应商提供的 UI 手动配置物理硬件的需要。
使用 Terraform 进行原型开发有一些优点。例如:
1. 避免手动调配基础架构。
2. 数据科学家和软件工程师可以自助。
3. 对当前设置的改变变得简单明了。
4. 易于传递知识和保持版本。
不需要在本地安装 Terraform,就可以通过 GitHub Action 实现流程自动化,因为 GitHub 在云中执行工作流。然而,在测试新资源或刚刚开始使用 Terraform 时,在本地安装软件可能是个好主意。在这种情况下,您需要向 Terraform 用户提供访问云资源的凭据,这在一些组织中可能是一个安全问题。你可以在 [Terraform 网站](https://www.terraform.io/downloads)上找到安装说明。
## GCP
服务账户(SA)代表谷歌云服务身份,例如,在计算引擎上运行的代码、在云存储中创建桶等。可以从 *IAM 和管理*控制台创建 sa。出于原型制作目的,请为帐户选择编辑者角色,以查看、创建、更新和删除资源。你可以参考谷歌云的[官方指南](https://cloud.google.com/iam/docs/creating-managing-service-accounts)。本文使用了计算引擎、云存储和 VPC 网络。
## 计算机编程语言
我们只用了三个库:Pandas、Yfinance 和 Streamlit。Pandas 是一个开源的数据操作工具。yfinance 是一个 python API,用于从 Yahoo!金融。Streamlit 是一个基于 Python 的 web 应用框架;与 Flask 或 Django 等其他框架相比,您可以用更少的代码开发 web 应用程序。
# 1.地形文件
Terraform 是一个 IaC 工具。您可以通过编写代码来管理云基础架构。Terraform 可以通过各种云提供商提供服务和资源。HashiCorp 和 Terraform 社区编写和维护代码。你可以在 Terraform Registry 网站上找到你可以使用的提供商列表,包括 AWS、Azure 和 GCP。
Terraform 的工作流程由三个步骤组成:定义、计划和应用。在定义步骤中,您将编写配置文件来声明提供者、服务和资源。配置文件可以由 Terraform 使用 *terraform fmt* 和 *terraform validate* 命令进行格式化和验证。在计划步骤中,Terraform 根据配置文件和现有基础设施的状态创建一个执行计划。它可以创建新的服务或更新/删除现有的服务。最后,应用步骤执行计划的操作,并记录 Terraform 提供的基础设施的状态。
我们将创建三个 Terraform 文件:main.tf、variables.tf 和 terraform.tfvars。
## main.tf
main.tf 是一个包含主要配置集的文件。它定义了所需的提供者、提供者变量和资源。该文件的内容如下:
首先,定义提供者。在本文中,我们将使用 google。
定义了提供者的变量。var。*将从单独的文件 variables.tf 中导入值。
credentials_file 应该是从 GCP 下载的 json 中服务帐户密钥的路径。
接下来的脚本创建了一个 VPC 网络。
然后,我们配置一个虚拟机实例。
我们指定虚拟机实例名称、机器类型、磁盘映像和网络接口。我们还添加标签并定义启动脚本。标记允许您将防火墙规则应用于 VM 实例。我们创建了三个标记,分别对应于下面定义的三个防火墙规则。metadata_startup_script 在启动时运行 shell 脚本。在我们的例子中,我们将运行脚本在虚拟机中预安装 Docker 引擎。
下一个特定的 http 访问防火墙规则。
您可以通过 CIDAR 格式的 source_ranges 来限制访问。
我们可能希望通过 SSH 访问 VM 实例。
第三个防火墙规则是针对 Streamlit web 应用程序的。端口 8501 将被打开。
最终的资源定义是针对云存储的。它使用从 variables.tf 文件中读取的变量创建新的 bucket。
## 变量. tf
var 的值。*由 variables.tf 文件提供。
每个变量都可以有一个默认值。在我们的例子中,一些变量没有默认值。因为它们是必需的参数,所以 Terraform 需要提供它们。提供变量的方法之一是通过*。tfvar 文件,减少了一些敏感信息的暴露。
## terraform.tfvars
我们在 terraform.tfvars 文件中有三行。请用您的值替换“***”。
## GCP 机器类型和图像列表
在 main.tf 中,我们指定了机器类型和磁盘映像。接受的值可能不总是与您在 GCP 控制台上看到的描述相同。您可以使用以下 gcloud 命令检索机器类型列表和计算引擎映像。(必须安装谷歌云 SDK。如果没有,请参考[官方指南](https://cloud.google.com/sdk/docs/install)
g 云计算映像列表
# Docker 安装脚本
我们将 docket 安装到虚拟机实例中。在 main.tf 文件所在的目录中找到安装脚本 install_docker.sh。Ubuntu 的安装脚本复制自官方 [Docker Doc 网站](https://docs.docker.com/engine/install/ubuntu/)。
# 2.GitHub 行动——GCP
GitHub 操作允许您构建自动化的 CI/CD 管道。公共存储库免费,私人存储库包含 2000 分钟[【1】](https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions)。
GitHub 动作读取了[下保存的 YAML](https://en.wikipedia.org/wiki/YAML) 文件。github/workflows/repository 中的目录并执行文件中定义的工作流。一个工作流可以有多个作业,每个作业都有一个或多个步骤。一个步骤可以有动作,但不是所有的步骤都可以执行一个动作。
文件内容如下:
首先,我们定义工作流名称和触发器,即 *push* 到主分支。接下来,我们添加关于作业的信息
上面的作业在服务器端的 ubuntu 机器上运行。每个作业在一个新的实例上运行;因此,每个作业下定义的环境变量不能用于另一个作业。
接下来,我们指定步骤和动作。
第二步,我们将使用秘密。GCP 萨基。这些秘密存储在存储库的动作秘密中。GitHub 支持 base64 编码的秘密。您可以使用 Python 将 GCP 服务帐户密钥编码为 base64 格式的 JSON:
Terraform init 步骤通过下载提供者插件启动工作目录的准备。我们还在后端配置中指定了一个 GCS bucket 名称。
如果您将目前准备好的文件从本地驱动器提交到主驱动器,GitHub 工作流会自动运行并提供服务。你可以在 GitHub Action 页面查看执行日志。

图二。GitHub 动作工作流程日志(图片由作者提供)
# 3.Streamlit 和 Dockerfile
在前面的章节中,我们使用 Terraform 和 GitHub 操作准备了基础设施。我们现在用 Streamlit 和 Docker 准备运行在 GCP 虚拟机实例上的 web 应用程序代码。
作为一个简单的例子,我们将为 Yahoo Finance 的时间序列数据创建一个仪表板,显示每日回报率,以便用户可以比较不同的指数或外汇汇率。仪表板用户也可以修改日期范围。
我们将要创建的仪表板图像如下所示:

图 3。Streamlit Web 应用程序用户界面(图片由作者提供)
## Python 脚本
上面显示的应用程序可以使用 Streamlit 创建。
首先,我们导入三个库。如果没有安装,请使用 pip(或 pip3) install 命令安装它们。
我们在 st.title('text ')中指定 web 应用程序的名称。
然后,我们准备选择题。st.multiselect()创建一个下拉选择。
Streamlit 提供了 date_input 选项,可以从日历中选择日期。这里我们创建两个变量:from_date 和 to_date。
最后,我们用计算出的返回值设计图表。
Streamlit 动态读取选择器变量中的值,然后从 yfinance API 中检索收盘价数据。增长率是使用 pandas pct_change()计算的。最后一步是使用 st.line_chart()函数在折线图中表示数据框。
我们将脚本保存在 app.py 文件中。
## Dockerfile 文件
dockerfile 是一个文本文件,包含创建图像文件的所有命令。您可以使用 docker build 构建映像文件,并通过 docker run 命令将映像作为容器运行。
在上面的 docker 文件中,我们在最新的官方 Python docker 发行版上构建了自己的映像。因为我们需要三个库来运行 Streamlit 应用程序,所以我们在文本文件中指定依赖关系,然后运行 pip install 命令。Streamlit 的默认端口是 8501。然后,我们复制容器的/app 目录中的 app/py 文件,并运行 Streamlit web 应用程序。
# 4.GitHub Action — Web 应用程序
在前面的小节中,我们为 GitHub 存储库中的基础设施供应准备了一个 YAML 文件。我们还必须创建一个存储库并定义一个工作流来部署 docker 容器,该容器在 GCP 的已调配虚拟机实例上运行 Streamlit web 应用程序。
首先,我们为 web 应用程序代码创建一个新的私有 GitHub 存储库,然后重复相同的步骤,在 GitHub Action secrets 中添加 GCP 服务帐户凭证。
接下来,我们在 GitHub 中准备一个私有访问令牌。该令牌用于从 GCP 的 VM 实例克隆 GitHub 中的这个存储库。
在你的 GitHub 页面,进入*设置*->-*开发者设置*->-*个人访问令牌*,然后点击*生成新令牌*。在*新增个人访问令牌*页面:*回购*和*工作流*必须打勾。*工作流程*选项允许您更新 GitHub 动作工作流程。您只会看到一次生成的令牌,所以请将您的令牌复制到编辑器中。

图 4。新的个人访问令牌(图片由作者提供)
我们需要在存储库 URL 中插入您的用户名和个人访问令牌。比如[*【https://github.com/your-user-name/your-repository.git】*](https://github.com/your-user-name/your-repository.git)会是[*https://****your-user-name:your-access-token @****github.com/your-user-name*](https://your-user-name:your-access-token@github.com/your-user-name)*/your-repository . git*。将完整的 URL 保存在 GitHub Action secrets 中,这样我们就可以在工作流 YAML 文件中调用它。
在下面。github/workflows 目录,我们创建 deploy_dokcer.yaml 文件。该工作流从 Dockerfile 构建 docker 映像,并将其部署在 GCP 虚拟机实例中。
在 jobs 部分,我们设置了一些变量和权限。
在“步骤”部分,我们定义了操作。为了通过 ssh 在 VM 实例中运行 bash,我们设置了 gcloud CLI。在命令中,我们克隆 GitHub 存储库并创建 docker 映像,然后从映像运行容器。
当您将文件推送到 GitHub repo 时,工作流运行并将容器部署到 VM。您可以从 GitHub Actions UI 查看工作流日志。

图 5。GitHub 动作工作流程日志(图片由作者提供)
现在,您可以修改 app.py,将修改后的代码推送到存储库,并确认更改已应用到虚拟机。
# 结论
本文解决了数据专家在分享来自数据分析阶段的有价值的见解方面的难题,并提出了一种使用 Streamlit web 应用程序呈现发现的方法。为了部署应用程序,CI/CD 工具和服务(如 Terraform 和 GitHub Actions)通过自动化工作流来帮助数据专家加速原型开发。
我们使用的例子是一个简单的用例;然而,Streamlit 可以做得更多。我们建议您访问 [Streamlit 网站](https://streamlit.io/)了解它能提供什么。类似地, [Terraform Registry](https://registry.terraform.io/) 也有很多有用的资源,并且由 HashiCorp 和提供商积极更新。值得查看您感兴趣的提供商的文档,以找到工作流自动化的其他机会。最后,GitHub 动作允许你设计更复杂的工作流程。如果你想在原型之外使用 GitHub 动作,强烈推荐阅读官方文档。
# 参考
[1]“关于 GitHub 操作的计费”,GitHub 文档。[https://docs . github . com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions](https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions)【访问时间:2022 年 8 月 7 日】。
# 用 Python 快速构建和部署 Web 应用程序
> 原文:<https://towardsdatascience.com/rapidly-building-and-deploying-a-web-app-in-python-10e0021596a5>

在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上拍摄的 [ThisisEngineering RAEng](https://unsplash.com/@thisisengineering?utm_source=medium&utm_medium=referral)
我最近用 Python 写了几个 [web 开发框架,今天我将介绍使用 Streamlit 构建和部署一个非常简单的新冠肺炎仪表板。Streamlit 旨在通过围绕您的 Python 代码构建一个流畅的 UI 来轻松创建以数据为中心的应用程序,不需要任何 web 开发经验(尽管它确实有所帮助)。](https://python.plainenglish.io/a-very-brief-overview-ofweb-development-with-python-8898dd00801c)
在开始之前,我想解释一下为什么我要写这篇文章。我和一位技术高超的数据科学家同事通了电话。他在寻找利用他的 Python 技能来构建可共享的应用程序的方法。我向他介绍了 Streamlit,他很快就开发出了一个很棒的应用。然而,当要分享他的应用程序时,他完全不知道该怎么做。作为一名数据分析师,我也经历过他的情况,如果不能与他人分享自己辛苦编写的代码,会非常沮丧。我希望通过这篇文章来弥补这一差距,解释让你在适当的时候轻松分享应用程序所需的所有步骤。
我们将首先为我们的项目建立一个虚拟环境。然后,我们将编写一些 Python 代码,作为应用程序的引擎。第三,我们将使用 Streamlit 库来帮助为我们的 Python 代码创建 UI,最后我们将部署我们的应用程序。我希望你能更好地理解 Python 中 web 开发的端到端过程。
## 1.设置虚拟环境
当您开始一个项目时,设置虚拟环境是您应该采取的第一步。你可以把虚拟环境想象成你的应用程序所在的地方,它与外界完全隔离。它不知道您可能已经编写的任何其他程序或您可能已经安装的 Python 库。这一点很重要,因为已经安装在计算机上的 Python 库可能会给应用程序带来问题。例如,一个新版本的包可能会通过取消某些功能来破坏您的代码。虚拟环境通过允许您控制库的版本并将它们与已经安装在您机器上的库分开来缓解这个问题,有助于防止任何依赖关系被覆盖或彼此冲突。此外,它有助于保持整洁有序。您的应用程序可能不会使用您机器上安装的所有 Python 库。如果你要在虚拟环境之外构建一个应用程序,那么每个库都将被包含在所谓的需求文件中(稍后会详细介绍),这是部署你的应用程序的必要元素。这只会导致额外的膨胀。
要实际设置虚拟环境,请打开终端或命令提示符。如果你在 Mac 上,通过键入 cd desktop 导航到你的桌面位置——在 windows 上是 CD C:\ Users \ YOUR PC USERNAME \ Desktop。接下来键入 mkdir streamlit _ project & & CD streamlit _ project。在您的项目目录中,通过键入 python3 -m venv 创建一个虚拟环境,后跟您决定的虚拟环境名称。惯例通常称之为虚拟环境。但是我要把我的名字叫做 virt_env。他就是那个样子:
python3 -m venv virt_env # Mac
python -m venv virt_env # Windows
接下来,我们将激活虚拟环境,并开始安装项目所需的软件包。要激活虚拟环境,请根据您的操作系统键入以下内容之一:
source virt_env/bin/activate # Mac
virt_env\Scripts\activate # WindowsOnce activated you should see
(virt_env) ...
## 创建我们的应用程序
现在让我们安装 streamlit 和 Python 应用程序中需要的其他包:
python3 -m pip install streamlit # Mac
python -m pip install streamlit # Windows python3 -m pip install pandas # Mac
python -m pip install pandas # Windowspython3 -m pip install matplotlib # Mac
python -m pip install matplotlib # Windows
在我们安装完这些包之后,请从我的 [Github repo](https://github.com/cbecks1212/streamlit_covid/blob/main/covid_dashboard.py) 中复制并粘贴代码,并将其作为 covid_dashboard.py 保存在您的虚拟环境的目录中。这是我们的 python 文件,包含我们的 streamlit 应用程序,它主要做两件事:1)计算四个指标,2)按状态绘制新案例的时间序列。我将更详细地解释每一部分。
@st.cache(allow_output_mutation=True)def load_dataset(): try: df = pd.read_csv("https://raw.githubusercontent.com/nytimes/covid-19-data/master/us-counties.csv", sep=",") states = list(df['state'].unique()) except: Exception return df, states
load_dataset()函数从 NYT Github 帐户中提取所有的 Covid 数据,并创建一个美国各州的列表进行过滤。在这个函数上面使用 st.cache 很重要,因为它允许将整个数据集存储在内存中,这样程序就不必在每次应用不同的过滤器时重新加载整个数据集。
def pct_change(data, state, today):
....
pct_change 函数有三个参数:covid 数据集、我们在下拉列表中选择的州(我们稍后会讲到)和今天的日期。该函数返回一个包含四个指标的字典(新病例、14 天病例变化、新死亡和 14 天死亡变化)。
def chart_data(data, state):
...
def chart_data 从下拉列表中获取我们的 covid 数据集和所选州,并返回我们所选州的新病例的时间序列数据帧。
移动到代码的最后一部分(这是我们的函数被调用和数据被返回并显示在我们的应用程序中的地方)。调用 load_dataset()并生成数据框和美国各州列表。然后,我们用 st.selectbox 实例化一个下拉列表,并向其中添加一个标签和我们的状态列表。然后,当下拉菜单被选中时,我们添加逻辑。最后,streamlit 提供了一种方便的方式来添加列和图形到您的网页,创建一个更干净的整体演示。所有这一切的美妙之处在于能够用最少的代码用纯 Python 创建流畅且响应迅速的 HTML 组件。
df, states = load_dataset() state_dropdown = st.selectbox('Select a State', states)if state_dropdown:data_dict = pct_change(df, state_dropdown, today)col1, col2, col3, col4 = st.columns(4)col1.markdown(f"New Cases: {data_dict['Cases']}")col2.markdown(f"""
14-Day Change: {data_dict['Case Change']}%
""", unsafe_allow_html=True)col3.markdown(f"New Deaths: {data_dict['Deaths']}")col4.markdown(f"""14-Day Change: {data_dict['Death Change']}%
""", unsafe_allow_html=True)chart_data = chart_data(df, state_dropdown)st.line_chart(chart_data)
## 部署应用程序:初始步骤
我们已经创建了一个虚拟环境和我们的 streamlit 应用程序,但它只存在于我们的本地机器上。为了与全世界分享它,我们需要确保安装了 Git 和 Heroku。要安装 Git,打开一个新的命令行窗口,在 Mac 上键入
git --version
应该会出现一个弹出窗口,显示进一步的说明,允许您继续安装。对于 Windows 用户,使用以下链接下载 Git:[https://git-scm.com/downloads](https://git-scm.com/downloads)
要在 Mac 上安装 Heroku,首先安装[家酿](https://brew.sh/),如果你还没有的话,然后在新的终端窗口中键入:
brew tap heroku/brew && brew install heroku
如果你有一个 M1 芯片,你可能会得到一个错误,但这将有助于解决问题:【https://support.apple.com/en-us/HT4
对于 Windows 用户,使用以下链接下载 Heroku CLI:[https://dev center . Heroku . com/articles/Heroku-CLI #下载并安装](https://devcenter.heroku.com/articles/heroku-cli#download-and-install)
一旦安装了这些依赖项,就可以将代码推送到 Github 了。首先创建一个. gitignore 文件,将虚拟环境文件从 Github repo 中排除,然后通过键入 virt_env/(在做出更改后保存文件)来修改它:
(virt_env) > touch .gitignore # Mac
(virt_env) > echo.>.gitignore # Windows
现在,在命令行窗口中初始化 git,通过 Git 激活您的虚拟环境
git init
git add -A
git commit -m "initial commit"
登录您的 Github 帐户,创建一个新的存储库,然后选择“从命令行推送现有的存储库”选项将该选项中的代码片段复制并粘贴到运行虚拟环境的命令行中。
为了结束这个阶段,通过 pip freeze 创建一个需求文件。这告诉 web 服务器成功运行应用程序需要哪些依赖项
python -m pip freeze > requirements.txt
在名为 setup.sh 的虚拟环境目录中创建新文件,并将以下内容保存到其中:
mkdir -p ~/.streamlit/echo "[server]\n\headless = true\n\port = $PORT\n\enableCORS = false\n\n" > ~/.streamlit/config.toml
然后在虚拟环境目录中创建一个 Procfile(就叫它 Procfile)并将以下内容保存到其中:
web: sh setup.sh && streamlit run covid_dashboard.py
现在是时候将 requirements.txt、Procfile 和 setup.sh 文件添加到 Github repo:
git add -A
git commit -m "files for Heroku deployment"
git push -u origin main
## 将应用程序部署到 Heroku:
要将应用程序部署到生产环境,请在运行虚拟环境的命令行中键入以下内容:
(virt_env) > heroku login
(virt_env) > heroku create
这将创建一个随机的 url 名称并部署您的应用程序。这将需要一两分钟的时间来完成,但是您应该会在命令行中看到一个类似于此处的 url。你可以把它复制到你的网络浏览器里。
## 总结:
读完这篇文章后,我希望代码困在您的计算机上的日子已经成为过去,您的组织中的人们开始从您的创新中受益。
# 快速浏览 Jupyter 笔记本电脑(就在您的终端中)
> 原文:<https://towardsdatascience.com/rapidly-explore-jupyter-notebooks-right-in-your-terminal-67598d2265c2>
## 优化笔记本搜索
作者:[阿玛尔哈斯尼](https://medium.com/u/d38873cbc5aa?source=post_page-----40d1ab7243c2--------------------------------) & [迪亚赫米拉](https://medium.com/u/7f47bdb8b8c0?source=post_page-----40d1ab7243c2--------------------------------)

作者截图
想象一下,你有一个代码样本,埋藏在一个旧的 Jupyter 代码中,却不记得是哪一个了。您可以使用两种简单的解决方案:
1.旋转 Jupyter 实例并手动搜索所有笔记本
2。在文本编辑器中打开笔记本,搜索特定文本
这两种方式都不理想,因为:
1. 在 Jupyter 中加载每个笔记本并进行搜索需要时间
2.JSON 格式的大笔记本可读性不强
如果有第三种方法可以解决这两个问题,并且你可以在不离开终端的情况下使用它,那会怎么样?
认识一下 `nbmanips`,这是一个 python *包,我们专门为处理、探索和转换笔记本而创建。*
💡声明:这是我和 Dhia Hmila 创建的一个包。
Table Of Contents:
. 1 — How to Install
. 2 — nbmanips in Python
∘ 2.1 — Reading a notebook
∘ 2.2 — Filtering/Selecting Notebook Cells
∘ 2.3 — Handling Multiple Notebooks
. 3 — nbmanips in the Terminal
# 1 -如何安装
如果使用 pip,安装 `nbmanips`非常简单:
pip install nbmanips
通过该软件包,即使您的 Jupyter 笔记本内容包含图像,也可以在您的终端上显示。是的,你没看错!如果您安装以下可选要求,它将呈现包含的图像:
pip install nbmanips[images]
如果你愿意,你可以用你自己的笔记本文件测试下面的库。但是如果你需要测试笔记本,这里有一个很棒的 [Git 资源库](https://github.com/Nyandwi/machine_learning_complete.git),里面有超过 30 个机器学习相关的笔记本。
# Python 中的 2 - nbmanips
## 2.1 -阅读笔记本
阅读 Jupyter 笔记本非常简单:
一旦你读完了笔记本,你可以用 `show`方法把它打印到屏幕上:
nb.show()

您可以选择不同的展示风格:
Possible values: ['single', 'double', 'grid', 'separated', 'rounded', 'dots', 'simple', 'copy']
nb.show(style='double')
💡**提示:** `copy`风格非常适合从终端复制/粘贴代码😉
📓其他有趣的参数有:
* `width`:单元格的宽度
* `exclude_output`:如果想要隐藏单元格的输出,则为 True。

## 2.2 -过滤/选择笔记本单元格
> 现在,我们有了一种不用离开终端就能以可读的方式可视化我们的笔记本的方法
然而,Jupyter 笔记本可以有大量的电池,我们不一定想在屏幕上看到所有的电池。
这就是 `Selectors`发挥作用的地方。它们用于指定我们将在哪些单元格上应用给定的操作(如显示或删除)。在我们的示例中,操作只是显示单元格。
要选择要应用前面操作的单元格,可以使用:
**1。使用索引过滤**
**2。使用预定义选择器**进行过滤
`nbmanips`中可用的预定义选择器如下:
* `code_cells` / `markdown_cells` / `raw_cells`:选择给定类型的单元格
* `contains`:选择包含某个文本/关键字的单元格
* `is_empty` / `empty`:选择空单元格
* `has_output`:检查电池是否有输出
* `has_output_type`:选择具有给定输出类型的单元格
* `has_byte_size`:选择字节大小在给定值范围内的单元格。这对于过滤掉包含大图像的单元格特别有用。
* `has_html_tag`:选择具有特定 HTML 标签的减价单元格
* `has_tag`:选择具有某个单元格标签的单元格。对于那些不知道它的人来说,可以给 Jupyter 笔记本单元格添加一个标记:

作者截图
* `has_slide_type`:选择具有给定幻灯片类型的单元格
* `is_new_slide`:选择新幻灯片/子幻灯片开始的单元格:

作者截图
**3。使用用户自定义函数过滤**
该函数采用一个 `Cell`对象,如果单元格应该被选中,则返回 True:
**4。组合过滤器**
* 组合选择器的第一种方法是简单地使用一个列表。
* 通过链接 select 语句来组合选择器:
nb.select('markdown_cells').select('is_empty').show()
* 使用二元运算符组合选择器:
## 2.3 —处理多台笔记本电脑
现在我们知道了如何在每个笔记本中显示特定的单元格,让我们将它应用于给定文件夹中的所有笔记本。
# 终端中的 3 - nbmanips
`nbmanips`还附带了一个 CLI,因此我们之前看到的所有内容都可以在几行代码中完成。
例如,要预览一个笔记本,您可以运行 `show`子命令:
nb show nb.ipynb
如果你想看到可用的参数,你可以通过 `--help`选项:
您可以使用过滤器,通过以下方式进行管道连接:
您也可以使用 `--help`选项获取更多信息:
# 最后的想法
Jupyter 笔记本对于交互式探索数据或快速尝试新想法非常有帮助。然而,一旦你完成了它们,你经常会留下一个快速和肮脏的代码。
`nbmanips`试图提供一种快速探索代码的方法,同时也能轻松地重组单元格和改造笔记本。
请继续关注我们的下一篇文章,了解如何拆分和合并不同的笔记本。
你可以在这个 [GitHub 库](https://github.com/hmiladhia/nbmanips-playground)中找到所有的 Python 脚本。如果您有任何问题,请不要犹豫,在回复部分留言,我们将非常乐意回答。
感谢您坚持到现在,注意安全,我们将在下一篇文章中再见!😊
# 更多文章阅读
</how-to-easily-merge-multiple-jupyter-notebooks-into-one-e464a22d2dc4> </equivalents-between-pandas-and-pyspark-c8b5ba57dc1d> </8-tips-to-write-cleaner-code-376f7232652c> </this-decorator-will-make-python-30-times-faster-715ca5a66d5f> 
人工智能生成的图像(稳定扩散)


浙公网安备 33010602011771号