TowardsDataScience-博客中文翻译-2019-五十三-
TowardsDataScience 博客中文翻译 2019(五十三)
用 Flask 和 Heroku 制作一个机器学习模型
如何在互联网上的 Flask API 背后部署一个经过训练的 ML 模型?

作为一名软件开发背景的人,我从未对生产我的模型有过任何犹豫。但是对于任何刚刚进入编码领域的人来说,作为一名数据科学家,这可能并不明显。
一个模型要在现实世界中使用,你需要把它生产出来。
在这篇文章中,我们将讨论一些事情。创建一个 ML 模型
2。将其包装在 API
3 的烧瓶中。将 API 部署到 Heroku 上
创建一个快速而肮脏的模型
本教程不是关于训练模型的,所以我们将在波士顿住房数据集上训练一个非常简单的模型,而不考虑它的工作情况。
# import dataset
from sklearn.datasets import load_boston# load data
data = load_boston()import pandas as pd# load into a dataframe
df = pd.DataFrame(data['data'], columns=data['feature_names'].tolist())
df['label'] = data['target']
df.head()

左边的label代表Median value of owner-occupied homes in $1000’s每个地区以千美元计的平均房屋价值。该数据集背后的想法是使用其他列中的值来预测平均房价。
我们将只使用RM、average number of rooms per dwelling来进行预测,完全知道这不会创建一个准确的模型。
# shuffle data
shuffled_df = df.sample(frac=1)# split data
split = int(len(shuffled_df) * .7)
train_df = shuffled_df[:split]
test_df = shuffled_df[split:]# stock function for extracting X,y columns from df
def X_and_y_from_df(df, y_column, X_columns = []):
X = {}
for feature in X_columns:
X[feature] = df[feature].tolist()
y = df[y_column].tolist()
return X, y# extract X and y
X_train, y_train = X_and_y_from_df(train_df, 'label', ['RM'])
X_test, y_test = X_and_y_from_df(test_df, 'label', ['RM'])# reshape
import numpy as np
X_train_arr = np.array(X_train['RM'])
X_train_arr = X_train_arr.reshape(X_train_arr.shape[0],1)
X_test_arr = np.array(X_test['RM'])
X_test_arr = X_test_arr.reshape(X_test_arr.shape[0],1)# train model
from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(X_arr, y_train)# predict results
y_pred = model.predict(X_test_arr)
我已经预测了这里的结果,但坦率地说,我们不在乎。如果我们在这个问题上花更多的时间,我们可以建立一个更精确的模型。
酸洗模型
泡菜什么?!
Pickling 将对象转换成字节流,该字节流可以存储、传输并在以后转换回原始模型。Pickles 是 python 允许您保存几乎任何开箱即用对象的方法之一。
import picklepickl = {'model': model}
pickle.dump( pickl, open( 'model_file' + ".p", "wb" ) )
嘣。我们的模型现在作为泡菜保存在与我们的 jupyter 笔记本相同的目录中。
构建一个 Flask API
Flask 是 Python 的一个微框架。它没有其他框架的多余功能,因此非常适合构建简单的 API。
我们将在命令行上运行几个命令来开始。
创建一个目录来存储应用程序
mkdir FlaskAPI
光盘进入
目录cd FlaskAPI
创建虚拟环境
python3 -m venv vir_env
激活虚拟环境
source vir_env/bin/activate
安装我们需要的所有软件包
pip3 install flask gunicorn sklearn
保存生产需要安装
pip3 freeze > requirements.txt的包和版本列表
在我们创建的最后一个目录中,用命令行创建几个文件和另一个目录。
touch app.py
touch Procfile
touch wsgi.py
mkdir models
在您喜欢的代码编辑器中打开FlaskAPI目录。我喜欢 sublime,所以我将用sublime .在当前目录中启动它。
将以下内容复制到app.py中。
import flask
from flask import Flask, jsonify, request
import jsonapp = Flask(__name__)[@app](http://twitter.com/app).route('/predict', methods=['GET'])
def predict():
response = json.dumps({'response': 'yahhhh!'})
return response, 200if __name__ == '__main__':
application.run(debug=True)
并将以下内容复制到wsgi.py
from app import app as applicationif __name__ == "__main__":
application.run()
现在启动应用程序!在同一个目录下运行gunicorn --bind 0.0.0.0:8080 wsgi:application -w 1
你应该看看…

尝试用curl -X GET [http://0.0.0.0:8080/predict](http://0.0.0.0:8080/predict)从另一个终端窗口向它发出请求


成功。我们有一个本地运行的 Flask API。
是时候添加我们的机器学习模型了。拿起你的model_file.p泡菜,放入我们制作的应用程序中的/models/目录,这样你的文件树现在看起来像左边。
将此功能添加到app.py
def load_models():
file_name = "models/model_file.p"
with open(file_name, 'rb') as pickled:
data = pickle.load(pickled)
model = data['model']
return model
并修改您的predict()函数,如下所示
def predict(): # stub input features
x = 5.963 # load model
model = load_models()
prediction = model.predict([[x]])[0] response = json.dumps({'response': prediction})
return response, 200
您还需要将import json和import pickle添加到app.py的顶部。
app.py现在的样子
import flask
from flask import Flask, jsonify, request
import json
import pickleapp = Flask(__name__)def load_models():
file_name = "models/model_file.p"
with open(file_name, 'rb') as pickled:
data = pickle.load(pickled)
model = data['model']
return model[@app](http://twitter.com/app).route('/predict', methods=['GET'])
def predict(): # stub input features
x = 5.963 # load model
model = load_models()
prediction = model.predict([[x]])[0] response = json.dumps({'response': prediction})
return response, 200if __name__ == '__main__':
application.run(debug=True)
请注意,我们是在模拟输入值,而不是实际发送给应用程序并解析响应——这就来了。
让我们再次测试应用程序,以确保模型在我们的 API 后面工作。
停止网络服务器,用gunicorn — bind 0.0.0.0:8080 wsgi:application -w 1重启
现在提出另一个请求,curl -X GET [http://0.0.0.0:8080/predict](http://0.0.0.0:8080/predict)

完美。我们的模型根据 API 中输入的特性预测标签!
现在让我们的 API 接受来自请求的输入特性。
将此添加到predict()
# parse input features from request
request_json = request.get_json()
x = float(request_json['input'])
现在app.py看起来像
import flask
from flask import Flask, jsonify, request
import json
import pickleapp = Flask(__name__)def load_models():
file_name = "models/model_file.p"
with open(file_name, 'rb') as pickled:
data = pickle.load(pickled)
model = data['model']
return model[@app](http://twitter.com/app).route('/predict', methods=['GET'])
def predict(): # parse input features from request
request_json = request.get_json()
x = float(request_json['input'])
# load model
model = load_models()
prediction = model.predict([[x]])[0] response = json.dumps({'response': prediction})
return response, 200if __name__ == '__main__':
application.run(debug=True)
还要把这个加到Procfile,它实际上没有扩展名。
web: gunicorn app:app --preload
再提一个要求。这次我们稍微修改了一下,这样我们的 API 就知道它得到了一个 JSON。
curl -X GET http://0.0.0.0:8080/predict -H "Content-Type: application/json" -d '{"input":"7"}'

再次,成功!我们有一个内部包含模型的工作 API。
部署到 Heroku
通过在命令行运行git init将 git 添加到应用程序中。
然后运行nano .gitignore并添加以下几行:
vir_env
__pycache__/
.DS_Store
然后按ctrl+x、y,再按enter保存并关闭 mac 上的文件。这将防止将不必要的文件推送到 Heroku。
通过在命令行运行git add . -A和git commit -m 'first commit'继续向 git 添加文件。
用 [Heroku](http://heroku create flask-ml-api-123) 创建一个账户。然后在命令行用heroku login登录 Heroku,再用heroku create flask-ml-api-123创建一个 app。flask-ml-api-123 是我选的应用名称。你需要找一个没人用过的名字。
现在用git push heroku master把我们的 app 推送到 heroku。
部署时,您将看到 Heroku 为您的应用程序提供的 URL。这是您发送 API 请求的地方。

对我来说是[https://flask-ml-api-123.herokuapp.com/](https://flask-ml-api-123.herokuapp.com/)。
现在在那里提出一个请求
curl -X GET [https://flask-ml-api-123.herokuapp.com/predict](https://flask-ml-api-123.herokuapp.com/predict) -H “Content-Type: application/json” -d '{"input”:”9"}'

现在我们正在谈话!我们已经训练了一个模型,将其包装在一个 API 中,并将其部署到 Heroku。显然,我们还应该添加很多东西,比如身份验证。但是这应该让你开始!
Jupyter (Python)的生产力技巧
最近几周我一直忙于我的 MRes 项目,睡眠很少。这让我在工作中最重要的工具:Jupyter Notebook/Jupyter Lab 中寻找改进工作流程的方法。我在这篇文章中收集了所有的窍门和技巧,希望其他研究人员可能会发现这些有用的东西:
- 计算完成(或失败)后播放声音
- 将通知集成到您的操作系统中(为 GNOME shell 做好准备)
- 跳转到变量、函数或类的定义
- 启用 rpy2 的自动完成功能(非常适合 ggplot2)
- 在一个漂亮的表格中总结字典和其他结构
- 有选择地从其他笔记本导入
- 出错或打开笔记本时,滚动到最近执行的单元格
- 长输出的交互(跟随)尾
注意:为了方便使用,我将下面的代码片段收集到 Python3 包中( jupyter-helpers )。您可以通过以下方式获得:
pip3 install jupyter_helpers
它可以开箱即用,但是如果您希望获得最佳体验,强烈推荐以下依赖项:
pip3 install ipywidgets
jupyter labextension install @jupyter-widgets/jupyterlab-manager
1.计算完成后播放一段声音
如果一个单元(或一串单元)的执行时间超过几秒钟,您可以配置 Jupyter 来播放声音。我之前描述过两种方法,基于 Python 的方法和基于 JavaScript 的方法。python 的代码大致如下:
然而,助手包得到了一个升级版本,它负责隐藏音频播放器和许多其他东西。像这样使用它:
from jupyter_helpers.notifications import NotificationsNotifications(
success_audio='path/to/beep-07.wav', time_threshold=2
)
在上面的例子中,我使用了来自 soundjay.com 的beep-07.wav。如果你在 GNOME 中使用 Ubuntu 或其他发行版,你可以提供一个默认警告声音的路径,例如/usr/share/sounds/gnome/default/alerts/bark.ogg。
异常时播放喇叭声音
类似地,您可以添加一个钩子,在出现异常时发出不同的声音。这里有一个由 SO 的 Kevin 提出的非常简单的机制:
当然,更先进的是jupyter_helpers包的一部分:
from jupyter_helpers.notifications import NotificationsNotifications(failure_audio='path/to/beep-05.wav')
2.将通知与您的操作系统集成
当我在图书馆工作时,我需要一个哔哔声和喇叭声的替代品。对于我这个 GNOME 用户来说,Notify-send 变成了一个完美的工具(向下滚动查看其他桌面环境的说明)。
设置事物使用:
from jupyter_helpers.notifications import NotificationsNotifications(
success_audio='path/to/beep-07.wav', time_threshold=2,
failure_audio='path/to/beep-05.wav',
integration='GNOME'
)
这对 GNOME 用户来说是现成的,尽管安装一个名为notify-send.sh的插件会让通知在不再需要时消失。这可以通过附加的setup.sh脚本来完成。

Figure 1: Fully integrated notifications
操作系统集成已准备好与任何其他桌面环境挂钩,尽管它需要一些脚本:
from jupyter_helpers.desktop_integration import DesktopIntegrationclass WindowsIntegration(DesktopIntegration): def notify(self, title, text, notify_id=None, **kwargs):
pass # add your code here def notify_close(self, notify_id):
pass # add your code hereNotifications(
success_audio='path/to/beep-07.wav', time_threshold=2,
failure_audio='path/to/beep-05.wav',
integration=WindowsIntegration
)
如果您希望将它与您的操作系统集成,请考虑发送 PR。
3.跳转到变量/函数/类的定义
使用鼠标使用Alt + click跳转到一个定义,或者使用Ctrl + Alt + B带jupyterlab-go-to-definition扩展的仅键盘选项:

Jump-to-definition supports Python and R. PRs to support other languages are welcome
最后,使用Alt + o跳回到之前的位置:

The ability to jump back is quite helpful for notebooks with longer outputs
要安装扩展,请使用:
jupyter labextension install @krassowski/jupyterlab_go_to_definition
2020 年更新:现在有了 jupyterlab-lsp 更强劲的跳跃!
4.为 rpy2 启用自动完成功能(ggplot2!)
如果您的工作更多的是关于出版物而不是交互式仪表板,那么您很有可能熟悉 ggplot2。虽然有一些伟大的项目,如 plotnine,试图将其移植到 Python,但我仍然发现在使用 rpy2 R-Python 接口时,使用 ggplot(尤其是扩展)功能更完整。
然而,到目前为止,自动完成还不包括%%R单元格中的 R 对象(如果加载的话也不包括 ggplot 函数)。我准备了一个简单的解决方法:
它可能会在未来得到改进,正如本 GitHub 问题中所讨论的那样。

Auto-completion now also includes R objects and ggplot functions
同样,来自jupyter_helpers的一个简单导入将解决这个问题:
from jupyter_helpers import rpy2_autocompletion
2020 年更新:现在也可以用 upyterlab-lsp 完成!
5.在一个漂亮的表格视图中总结字典
这不是一个新颖的想法,尽管我希望分享我的比不太先进的类可以帮助别人。这是基于 Python3 SimpleNamespace的,但是为 Jupyter 扩展了一个支持熊猫和 numpy 的 HTML 表示:
from jupyter_helpers.namespace import NeatNamespaceNeatNamespace(your_dict)
长集合将被修剪,所以当浏览器努力呈现意外打印的字典时,不必担心空间或内存不足。水平和垂直方向可以更好地利用空间。

Namespaces with HTML: when nested data needs to be looked at before converting to DataFrame
6.选择性地从其他笔记本导入
有一段时间,我试图遵循数据/方法/结果分离,为每个更大的分析准备了三个 Jupyter 笔记本:data.ipynb、methods.ipynb和results.ipynb。为了节省对一些东西进行无用的重新计算的时间,我想从数据和方法笔记本中进行选择性导入,以便在结果笔记本中使用。
现在可以(基于 nbimporter )使用一个导入和一个魔法:

这个在本 SO 线程中有描述,还是希望看到一些建议。
7.滚动到最近执行的单元格
您可能已经注意到,之前显示的Notifications类使笔记本在异常情况下向下滚动到违规单元格(图 1)。通过scroll_to_exceptions=False可以禁用此功能。
相反,如果您想要更多的自动滚动,您可以使用底层助手函数来标记您晚上结束工作的单元格,以便在早上快速打开笔记本:
from jupyter_helpers.utilities import scroll_to_current_cellscroll_to_current_cell(preserve=True)
8.用于长输出的交互式尾部
最后,当运行第三方应用程序时(但不是在构建一个完全成熟的管道的时候),人们可能只想看到当前运行的进程的尾部。在 bash 中,使用tail -f很容易实现。
如果您需要查看输出,并且在刷新到笔记本电脑时会降低计算机速度,或者只是生成冗长的输出,那么FollowingTail helper 可能适合您:

Apply tail -f equivalent to keep the outputs at reasonable length!
接下来还有更多!
我现在只有这么多时间,但我会分享更多已经写好的和新的助手和片段。这是一种回馈令人惊叹的科学计算社区的方式(从 Python 和 R 端),它使我能够完成我的论文。我有另一个项目来,这可能需要克服其他挑战。
一旦准备好了,我会在 Twitter 上分享这个消息。
感谢您的阅读——希望它值得至少为一位研究人员撰写。
教授变身数据科学家:为什么 Guido Maretto 博士离开学术界开始创业
独家 TDS 采访
TDS 采访了这位前教授(加州理工学院博士),询问他为什么离开副教授职位,以及如何在大学毕业后加入行业。
面试官: Haebichan Jung ,Recurly 的数据科学家,TowardsDataScience.com 的项目负责人。
受访者:Guido Maretto博士,Flyr 实验室的数据科学家,Nova 商业和经济学院商业战略的前副教授。前麻省理工学院研究员。
你的专业背景是什么?
在加州理工学院获得博士学位后,我在比利时的布鲁塞尔获得了博士后学位,然后我在葡萄牙里斯本的 Nova 商学院开始了一个教师职位,在那里我呆了 7 年,直到我进入工业界,成为旧金山 Flyr 实验室的一名数据科学家。

你在新星学校的主要职责是什么?
我在教学和做研究。最初我的研究主要是数学和理论模型。后来我对实证分析更感兴趣了,所以用数据。具体原因是,我和一位在布鲁塞尔的同事现在在英格兰银行工作,我们对政府关于意大利犯罪组织的说法感到好奇。不同的政治家在发表相反的声明。因此,我们认为有必要通过这些数字来回答谁在本质上撒谎的问题,并揭示一些真相。

Cooperation in Criminal Markets by Dr. Guido Maretto: https://mpra.ub.uni-muenchen.de/75949/
那是我对在工业界工作感兴趣的时候。因为当你在学术界工作时,你有很多时间用好的方法论非常精确地回答问题。因此,你有机会用最好的方法更好地得到正确的答案。但遗憾的是影响很低。即使你发现了问题的根本原因,并找到了潜在的好的解决方案,可能也要过好几年,政策制定者才会找到解决方案或进行分析。我所在的地方更是如此,那就是南欧。这就是让我对进入行业感兴趣的原因:解决问题的可能性,不仅仅是找到解决方案,而是实际实施解决方案。
这是从学术界转向工业界的主要原因吗?
绝对的。我想说,数据科学对在学术环境中进行实证研究的主要吸引力在于,你被迫以更高的速度运行,但你可以实际实施你找到的解决方案。
您是如何管理从学术界到数据科学的转变的?
我通过参加编码训练营加强了我的技能,这非常重要,因为我们在统计方法方面使用的机器类型,但最重要的是在用于进行经济研究的统计包中使用的机器类型与我在工业中使用的不同。尽管目标可能是实现最优定价、估计需求等等,但所使用的数据类型和数量以及行业状况要求使用不同的工具集。
你过去作为学者使用的工具和你现在作为数据科学家使用的工具有什么重要区别?
经济学家大多是在 Stata 上接受培训的,这是与学术界不同的非常重要的东西。幸运的是,如今 R 在课程中变得越来越常见。但是大部分数据科学工作都是用 Python 和一点 r 语言完成的。但是有一个技能缺口需要在学术界之后弥补。SQL 在工业中也很重要,任何从事数据科学工作的人都知道完全脱离 SQL 是相当困难的。
作为一名学者,你过去解决的问题类型和你现在正在解决的问题有什么相似和不同之处?
就学习的主题而言,没有区别。现在在一家帮助各大航空公司给座位分配价格的公司工作。因此,我所做的是需求估计,不同市场的竞争分析,以及创建能够对情况做出反应的自动化模型,从而为我们的客户提供尽可能好的价格。
所以就我日复一日所想的而言,实际上没什么区别。就我对工作组织的看法而言,有着天壤之别。从本质上来说,一切都更快,一切也都更浅。但这是必须的,因为在找到一个好的解决方案和同时做一些事情之间有一个权衡。这种权衡在学术界并不存在,但对于一家公司的生存来说却是至关重要的——在实现最大改进的同时保持运营。

Demand estimation modeling
说到主题和你正在解决的问题,比如需求评估,数据科学和经济学之间几乎没有区别。但是,当涉及到解决这些问题的更广泛的学科方法时,你也看到了任何大的重叠吗?
是的,特别是在经济学中,但对于大多数使用数据的科学(在学术界)来说,重点不是预测将会发生的事情,而是解释为什么会发生事情。更准确地说,我们在数据科学中做的是大量的预测,在学术研究中做的是大量的推断。
现在不要介意经济学之外的 80%的社会科学研究甚至不理解因果关系和相关性之间的区别,但那是他们自己的问题……无论如何,在现实中最大的方法论差异是,悬挂你的头在一个关系是否是因果关系上是重要的,只有在它涉及到预测的时候。当我们想要预测的是需求时,情况肯定是这样,因为这是一个受到许多内生性和各种偏见影响的问题。
但在其他情况下,这些方法是非常不同的,一些问题可能会导致使用高度非线性和完全不透明的方法,这些方法缺乏对模型内部关系的可解释性,但在预测方面却很棒。
旁注:为了澄清,Guido 认为某些问题用复杂的神经网络这样的数据科学方法处理得很好。相比之下,其他问题,如通过需求估计的价格设定,更适合基于推理的方法,这些方法更简单,但在经济学/统计学中使用的回报更准确。定价的内生性就是一个具体的例子。
在价格设定的情况下,一个重要的内生变量是消费者对设定价格的支付意愿,该变量未被观察到,不包括在我们的模型中,并且不独立于价格。
要了解更多关于内生性的知识,请参考这篇 TDS 文章。
一个冰淇淋小贩在海滩上卖冰淇淋。他收集了两年的总销售额(Y)和销售价格(X)的数据…
towardsdatascience.com](/endogeneity-the-reason-why-we-should-know-about-data-part-i-80ec33df66ae)
你如何在日常实践中解决这两种范式的差异?
实际上,两者都有一点。因为如果你对需求数据使用深度神经网络(DNN ),然后你开始设定价格——这是大多数公司在定价中所做的——他们使用机器学习模型来进行需求估计——你会发现这个模型会失败,因为产生价格的过程已经改变了,它已经变成了你的选择
旁注 2:这也是与内生性有关的原因。在这里,模型是根据价格不独立于内生变量(客户的支付意愿)的数据来训练的。因此,一旦这些模型在生产中投入使用,这些模型将改变产生价格的过程及其与内生变量的协方差。因此,基于原始训练集的预测变得不太精确。
所以使用非常强大的方法是有一点艺术的,这些方法可以让我们达到很高的精确度,但却不可解释。实际上,在我的日常工作中,包括需求评估,我认为两者都要做一点。因此,它偏离了学术研究和经济学中典型的对因果关系的探索。但是不能完全放弃,因为我用来强化因果分析的工具在特征选择和预测方面非常强大。
在 Flyr 实验室,您会用到哪些常见的数据科学工具和技术?
我们用 Python 做任何事情。就我们使用哪些套餐而言,我们是一家小公司,所以我们仍然有自由挑选,而不会被绑定到特定的套餐。由于我的经济背景,我喜欢用 StatsModels 进行分析,因为我发现这是提供最完整的端到端推理使用的软件包。例如,它允许我非常容易地对错误进行分类,这对于 scikit 来说是非常复杂的——例如 learn。
然后,在某些情况下,为了部署到生产中,我们将转移到 scikit-learn 或 Tensorflow,以使用它们的一些功能,而这些功能在生产中并不是很好。
Flyr 实验室面临的更大挑战是什么?
绝对意义上还是相对于学术界?
绝对而言。
从绝对意义上来说,我要说的是,有一件事始终具有挑战性,那就是需要在不同层面上获得对一个解决方案的认同。有时候政治会阻碍这一切。所以这是最复杂的部分。因为从性格上来说,我不喜欢说太多话,一遍又一遍地重复同样的事情。所以当我说服一个人这个解决方案是好的,我不会对说服第二个人太兴奋,等等。
然而,我想说这是数据科学家职业生涯的基本方面。因为如果没有对您的解决方案的自我宣传,您将无法实施它,也无法对您公司的指标产生影响。这最终会影响到你的职业生涯。所以我觉得是必要之恶。
对一些人来说,这可能是相当愉快的。比方说,许多人进入数据科学和产品领域,是因为他们偏爱工作的软方面[软技能],而不是实际的分析和生产事实。
最后一个问题,你对 TDS 社区,尤其是那些想进入数据科学领域的学术界或经济学界人士有什么忠告吗?
这些天发生了什么,博士奖学金的数量没有减少,如果不是增加的话。大学里的教师职位和纯研究职位的数量正在减少。因此,在你的整个职业生涯中,一定要留意并保持向工业和数据科学过渡的机会。
非常感谢 TDS 的 Ludovic Beni stant 的支持和指导。
阅读容格在媒介上的作品。项目负责人@TDS |数据科学家@ Recurly…
medium.com](https://medium.com/@haebichan) 
使用 Spark 分析分布式环境中的大数据:机器学习的 Pyspark 数据入门
沙欣·高赫尔博士
当使用数据建立预测模型时,在数据可用于任何机器学习任务之前,建立数据的神圣性是重要的。从建模的角度来看,查看数据概要有助于轻松检测数据中的任何错误值和列。通过计算表中每一列的最小值和最大值、每一列的平均值和标准偏差、每一列中不同值的数量、最频繁出现的值及其计数、最不频繁出现的值及其计数以及每一列中缺失值的数量来生成配置文件。数据的这些属性是理解表中每一列包含的内容并开始了解数据分布的良好起点。数据概要文件是一个很好的数据检查工具,可以确保数据是有效的,适合进一步使用。
对于可以加载到内存中使用 python 或 R 访问的小型数据集,数据剖析可以相当快地完成。然而,对于大型数据集,例如每天可以轻松达到数百万条记录的交易数据,这将成为一项艰巨的任务。在许多公司中,数据通常以分布式方式存储在 Hadoop 分布式文件系统(HDFS)中。在接下来的内容中,我将介绍一个使用 Spark 在分布式环境中分析大数据的实用函数。Spark 是集群计算和并行处理的框架。我将特别使用 pyspark,它是 Apache Spark 和 python 的合作产品。更详细的代码和解释可以在 GitHub 中找到。我还将分享数据概要文件可以帮助设计用于建模目的的数据准备和预处理策略的一些方法。
下面我将从指定的配置单元模式中查询一个配置单元表,并使用 spark SQL 将其作为 Spark 数据帧加载。

下面的 pyspark 实用程序函数将以列表的形式对待分析的列(所有或部分选定的列)和 pyspark 数据帧中的数据作为输入。
上面的函数将绘制列的轮廓,并将轮廓打印为 pandas 数据框。


Profile DataFrame
数据配置文件用途
数据配置文件在许多方面都很有用。它提供了对数据质量的一定程度的信心,并且是开始理解数据的最快方法。在确保数据质量的同时,数据概要还有助于设计 数据准备和预处理策略 。通过观察列及其属性,它可以立即帮助做出关于如何处理缺失值、基数、无信息列和清理数据的决策。

处理缺失值
通过显示列中缺失值(空值、nans、空格和空白)的百分比,数据配置文件可以帮助确定阈值百分比值,高于该值的列将被丢弃以用于建模目的。尽管有些算法能够处理缺失值,但理解缺失的原因是很重要的。如果观察到一个列有大部分(~80%)缺失值,那么要问的问题是,该列是否应该出于建模目的而被丢弃,或者缺失值是否意味着什么。例如,一列包含车辆使用优质汽油的天数,要问的问题是-列中缺少的值是指数据实例是针对只使用普通汽油的车辆,因此缺少值,还是只是没有正确记录值?(提示:请 SME 参与调查)。上面的属性还可以帮助设计一种策略,为有一些值(~20%)缺失的列输入值。关于用平均值或众数值替换缺失值是否可行的决定,或者更复杂的输入算法,如 SMOTE 等。可以通过查看数据配置文件来简化。
处理基数
通过查看列中唯一值的数量,数据配置文件可以很容易地帮助确定列是分类的还是连续的。可以通过查看分类列的唯一值的数量来确定基数,然后可以使用该数量来决定适当的编码技术以及是否需要宁滨来缩减特征空间。
处理无信息列
使用数据概要文件,很容易发现只有一个唯一值的列,并且从建模的角度来看,可以很容易地将其作为不添加任何信息的常量列丢弃。如果缺失值的数量与行数相同,则可以识别空列并将其丢弃。如果唯一值的数量与行数相同,则该列是索引列,也可以被丢弃。
处理脏数据
我发现数据配置文件特别有用的一点是数据清理。如果数据配置文件显示最频繁出现的值为 None 或最小值为 -9999.9 或最大值为 99999 ,则表明这些值需要转换为 nan 或 null,并再次生成配置文件以找到真实的最小值、最大值和模式值。它还有助于发现数据中需要清除的不可打印字符和空格,然后才能进一步使用这些数据。
上面只是一些预处理和数据准备步骤的例子,数据概要文件可以立即开始通知这些步骤。数据分布中的不平衡、异常值和偏斜是 data profile 也可以帮助揭示的一些其他事情。
摘要
对于任何类型的分析工作负载,了解数据的输入和输出都是不可替代的。在将数据用于任何机器学习练习之前,分析数据应该是第一步,随后是可视化,以更好地理解不同数据元素之间的关系,并形成关于如何通过模型最好地捕捉它们的假设。对数据的良好理解不仅有助于产生有价值的见解,而且有助于为稳健的机器学习模型进行智能特征工程。我很想知道概要文件中还可以包含哪些有助于为建模目的准备数据的数据属性,以及提高代码效率的技巧(无需配置更大的集群)。虽然这里展示的用例是在 Hadoop 中分析数据,但是 pyspark 函数足够通用,可以从任何作为 pyspark 数据帧加载的源中获取输入数据。尽情享受吧!
@Shaheen_Gauher
Shaheen Gauher 的职业是人工智能沟通者、智能解决方案推动者和数据科学家。她帮助企业构建和部署预测解决方案,以最佳地利用他们的数据,并使他们能够通过技术和人工智能实现更多目标。她是训练有素的气候科学家和物理学家,并在塔夫茨大学艺术与科学研究生院的数据分析顾问委员会任职。
通过聚类在 Spotify 上分析我最喜欢的歌曲

Credits: https://www.samma3a.com/tech/en/spotify-testing-whats-new-timeline-feature/
音乐是我们生活中不可或缺的一部分。当无法用语言描述我们的感受时,它是帮助我们表达自己的共同语言。音乐也有助于我们调节情绪。它影响我们的灵魂和情感,让我们感到快乐、悲伤或充满活力。我们可能会在长途驾驶中放一些歌曲跟着唱,或者在健身房里听一些欢快的歌曲。
我们中的许多人对所听的音乐都有特定的品味,而且这种品味可能会随时间而变化。这让我想到…
- 这些年来我对音乐的品味改变了吗?如果有,这些变化是什么时候发生的?
- 根据我最近的音乐品味,我现在在听什么类型的音乐?
多亏了 Spotify API,我能够提取和探索我喜欢听的歌曲——那些让我点击心形图标的歌曲。
设置
要从 Spotify API 获取数据,我们需要按照以下步骤进行初始设置:
- 登录 Spotify for Developers 并创建一个应用。
- 从应用程序仪表板页面中,选择编辑设置,并将重定向 URIs 设置为 http://localhost:8888。
- 记下客户端 ID 和客户端密码。
收集数据
我们可以使用 Spotify Web API 的 Python 库 Spotipy 来获取相关数据。为了获得歌曲,我们需要生成一个授权令牌。
import spotipy
import spotipy.util as util
from spotipy.oauth2 import SpotifyClientCredentialscid = '<INSERT CLIENT ID>'
secret = '<INSERT CLIENT SECRET>'
username = ""
client_credentials_manager = SpotifyClientCredentials(client_id=cid, client_secret=secret)
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)# Get read access to your library
scope = 'user-library-read'
token = util.prompt_for_user_token(username, scope)
if token:
sp = spotipy.Spotify(auth=token)
else:
print("Can't get token for", username)
有两个 API,current _ user _ saved _ tracks和 audio_features ,用于获取标题、艺术家、添加歌曲的时间以及声音、舞蹈性和乐器性等特征。这些功能将帮助我们更好地理解我们的播放列表。
下表列出了一些功能描述:

要查看一首歌曲的完整功能,您可以查看此链接。
获取由唯一的 Spotify ID 标识的单曲的音频特征信息。
developer.spotify.com](https://developer.spotify.com/documentation/web-api/reference/tracks/get-audio-features/)
df_saved_tracks = pd.DataFrame()
track_list = ''
added_ts_list = []
artist_list = []
title_list = []more_songs = True
offset_index = 0while more_songs:
songs = sp.current_user_saved_tracks(offset=offset_index) for song in songs['items']:
#join track ids to a string for audio_features function
track_list += song['track']['id'] +',' #get the time when the song was added
added_ts_list.append(song['added_at']) #get the title of the song
title_list.append(song['track']['name']) #get all the artists in the song
artists = song['track']['artists']
artists_name = ''
for artist in artists:
artists_name += artist['name'] + ','
artist_list.append(artists_name[:-1]) #get the track features and append into a dataframe
track_features = sp.audio_features(track_list[:-1])
df_temp = pd.DataFrame(track_features)
df_saved_tracks = df_saved_tracks.append(df_temp)
track_list = '' if songs['next'] == None:
# no more songs in playlist
more_songs = False
else:
# get the next n songs
offset_index += songs['limit']#include timestamp added, title and artists of a song
df_saved_tracks['added_at'] = added_ts_list
df_saved_tracks['song_title'] = title_list
df_saved_tracks['artists'] = artist_list
这是获得的数据集的一个例子。

我对音乐的品味变了吗?
获得数据后,是时候找出特征如何随时间变化了。我们可以根据歌曲添加的年份和月份对歌曲进行分组,得到每个特征随时间变化的平均值,并将其可视化。

折线图按年份分开,以使其更清晰。语音(紫色)在这些年中最稳定,接近 0。这说明我一般听说唱少的歌。声音(蓝色)是波动的,这意味着随着时间的推移,我混合了声音和非声音的歌曲。
最让我感兴趣的是工具性(绿色)。对于 2015 年和 2016 年,它稳定地接近 0。然而,从 2017 年开始,工具性开始波动。这大概是我的音乐品味从 2017 年开始发生变化的标志吧。我们可以将数据过滤为 2017 年添加的歌曲。
我在听什么类型的歌曲?
使聚集
根据上面的图表,我知道我正在听更多的器乐歌曲。但是是器乐舞蹈类的歌曲吗?还是更古典的歌?其他人呢?我目前对音乐的品味如何?我们可以将具有相似特征的歌曲分组在一起,并对每个聚类进行分析。一种聚类方法是 K 均值聚类,这是我将用来分析我的歌曲。
对于聚类,我们希望同一个聚类中的点尽可能靠近。我们还希望集群之间的距离尽可能的远。这使得每个集群看起来紧凑,同时彼此间隔开。
这是 4 个集群的可视化效果。绿点代表每个群集的质心(群集的中心)。

K-means clustering taken from http://shabal.in/visuals/kmeans/6.html
因为聚类依赖于距离,所以我们的数据规模会影响结果。例如,如果我们希望按身高(从 1.5 米到 1.9 米)和体重(从 60 公斤到 80 公斤)进行分类。因此,点在高度轴上的分布为 0.4,权重为 20。这意味着权重将在确定聚类中起主导作用。
我们可以将数据的范围标准化,这样特征就会影响结果。
cluster_features = ['acousticness', 'danceability', 'instrumentalness', 'energy', 'speechiness']df_cluster = df_recent[cluster_features]
X = np.array(df_cluster)
scaler = StandardScaler()
scaler.fit(X)
X = scaler.transform(X)
了解了聚类的作用后,我们听了多少种类型/组的歌曲?一种方法是根据自己的知识做出有根据的猜测。如果你听所有类型的音乐,从 edm 到 hip hop 到 jazz 等等,你可以给出一个更高的数字,比如… 7?因为 K-means 聚类需要我们指定想要的聚类数,所以可以设置 k=7,这里 K 是聚类数。
另一种方法是借助肘法来确定聚类数。在 elbow 方法中,我们可以对一系列设定的聚类数执行聚类,例如 k=1,k=2,…,k=9,k=10。对于每个 k,我们将取每个点并测量它到它们的聚类质心的平方距离,并将它们相加。这被称为距离平方和(SSD)。SSD 测量每个点离聚类质心有多近。因此,SSD 越小,同一簇中的点越接近。
ss_dist = []
K = range(1, 11)
for k in K:
km = KMeans(n_clusters=k, init='k-means++', random_state=123)
km = km.fit(X)
ss_dist.append(km.inertia_)plt.plot(K, ss_dist, 'bx-')
plt.xlabel('k')
plt.ylabel('Sum of squared distances')
plt.title('Elbow Method For Optimal k')
plt.show()
因此,如果我们绘制每个 k 的 SSD,我们将得到如下所示的曲线:

从上面的图中可以看出,随着 k 的增加,SSD 减小。这是有意义的,因为点可能有一个更近的集群要分配,导致较低的 SSD。
前面我提到过,我们希望每个集群中的点尽可能接近。但是,我们不能选择 k=10,因为它是最低的。想象一下。如果我们选择 k=N,其中 N 是歌曲的数量,我们将每首歌曲作为自己的聚类,因此 SSD 将为 0。这是因为每个点的聚类质心就是该点本身。
相反,我们将选择 k,这样,如果我们添加另一个群集,SSD 会略有下降。这就是所谓的肘法。如果我们把曲线想象成我们的手臂,我们会在开始时得到一个陡峭的斜坡,然后在中途突然变得平缓。这赋予了它“肘部”的形状。
基于肘形方法,推荐的聚类数是 4,因为线从 k=4 到 k=5 变得平缓。然而,我也试验了 k=5,发现我喜欢给定的集群。因此,在这篇文章中,我将分享我得到的 k=5 的结果。
集群可视化
太好了,我们终于有自己的集群了!那么我们的星团看起来怎么样呢?遗憾的是,目前我们还无法查看它。这是因为我们的聚类是使用 5 个特征形成的。如果你把每个特征看作一个维度,你得到的是 5 维。因为我们可以看到高达 3 维的图像,我们将需要执行一种叫做降维的技术。这允许我们从 5 维降低到任何更低的维度。
为了尽可能直观地解释它,降维的目的是从一个更高的维度得到一个低维的特征集,同时保留尽可能多的信息。如果你想更好地理解它的作用,你可以看看这个关于主成分分析(PCA)的视频,这是降维的方法之一。
让我们看看如果我们使用主成分分析来降低维度,会保留多少数据。

蓝条显示每个主成分(PC)对数据贡献了多少信息。第一台 PC 提供了 40%的数据信息。第二个和第三个各贡献 20%。红线显示 PCs 数据的累积信息。通过从 5 维减少到 2 维,60%的数据信息被保留。同样地,如果我们减少到 3 维,80%的数据信息被保留。
现在让我们看看我们的集群在二维和三维散点图上是什么样子。


二维散点图中的点相互重叠,看起来可能没有很好地完成聚类。然而,如果我们从三维视角来看,我们可以更好地看到集群。
让我们尝试另一种方法,称为 t-分布式随机邻居嵌入(t-SNE)。t-SNE 在可视化高维数据方面表现良好。更多细节,你可以阅读这篇教程。

在这种情况下,二维 t-SNE 散点图能够很好地显示 5 个集群。我们也可以大致分辨出,聚类 3 是最大的聚类,聚类 0 或 1 是最小的。让我们使用条形图来看看集群是如何分布的。

聚类分析
现在,我们可以了解不同集群的特征。让我们比较一下集群之间的特性分布。
# set binning intervals of 0.1
bins = np.linspace(0,1,10)# create subplots for number of clusters(Rows) and features(Cols)
num_features = len(cluster_features)
f, axes = plt.subplots(num_clusters, num_features,
figsize=(20, 10), sharex='col'row = 0
for cluster in np.sort(df_recent['cluster'].unique()):
df_cluster = df_recent[df_recent['cluster'] == cluster]
col = 0 for feature in cluster_features:
rec_grp = df_recent.groupby(pd.cut(df_recent[feature], bins)).size().reset_index(name='count')
cluster_grp = df_cluster.groupby(pd.cut(df_cluster[feature], bins)).size().reset_index(name='count') sns.barplot(data=rec_grp, x=feature, y='count',
color='grey', ax=axes[row, col])
sns.barplot(data=cluster_grp, x=feature, y='count',
color='red', ax=axes[row, col]) axes[row, col].set_xlabel('')
axes[row, col].set_xticklabels(range(1,10))
if col > 0:
axes[row, col].set_ylabel('')
if row == 0:
axes[row, col].set_title(feature) col += 1
row += 1
f.suptitle('Profile for each clusters')
plt.show()

每行代表 0 到 4 的分类,每列代表特性。灰色条代表特征的分布。这使我们能够大致了解特征的分布。红色条表示该聚类中用于与其他聚类进行比较的特征的分布。
当我们观察每个聚类的分布时,我们可以看到每个聚类在某些特征上是高的或低的。这可以通过红色条相对于灰色条是在右侧(高)还是左侧(低)来识别。从这些特征中,我们可以描绘出它们的轮廓,甚至得出一个集群的身份。
集群 0(仪器):高仪器性。低语速。
第一组(抒情):高可舞性,活力,语速。低声音性、乐器性。
集群 2(寒冷振动):高可跳舞性。低能量、仪表、语音。
第三组(舞蹈):高可舞性,活力。低声音、仪表、语音。
集群 4(减速):高声音。低可舞性、仪表、精力、语言。
我们还可以通过取聚类特征的平均值并将其绘制到雷达图上来绘制轮廓。这可能更容易一目了然地查看所有集群之间的差异。


雷达图的读数类似于上面给出的剖面图。我们还可以看到集群 2 和集群 4 具有相似的统计数据。不同之处在于,聚类 2 更关注可舞性,而聚类 4 更关注听觉。
聚类样本
让我们看看每个聚类中的歌曲是否符合聚类简档。这里有 3 首歌曲,你可以听一听,看看是否有意义:
第 0 组(器乐):
FKJ 回家
酷玩催眠
阿斯托尔·皮亚佐拉、邦德的《自由探戈》
第一组(抒情):
蔡林·鲁索的九月玫瑰
扎维亚·沃德的烛光
IU 的 BBIBBI
集群 2(寒气盛传):
放弃游戏由水槽,切特 Faker
铁青由伊莱扎
找到一条路由马特昆廷,林卡杨
集群 3(舞蹈): Oh Wonder 的 ultra life
Pb 地下的 Little Man
Finesse(混音)【专长。卡迪 B]由布鲁诺·马斯,卡迪 B
第四组(放松): 被塞布丽娜·克劳迪奥冻结
打破距离 2.0 由阿什顿·埃德明斯特
有人待在温哥华睡眠诊所
结论
我们首先观察了不同时期的不同特征,试图弄清楚音乐品味是否发生了变化。从过滤后的数据集中,我们进行了聚类分析。然后,我们进行可视化处理,以大致了解它的样子,并确保聚类是正确的。最后,我们绘制了每个特征的分布,并对它们进行了描述。在一天结束的时候,我们能够更好地理解我们喜欢的歌曲类型。
数据的收集可以在我的 Github 上的这里找到,分析可以在这里找到。
感谢您的阅读,希望您对此感兴趣。请随时在下面的评论区提供您的反馈,或者通过我的 LinkedIn 联系我。希望你有一个伟大的一周!
参考
- 绘制刻面图:【https://seaborn.pydata.org/examples/many_facets.html】T22
- 主成分分析(PCA)分步指南:
https://www.youtube.com/watch?v=FgakZw6K1QQ - 关于 t-SNE 的教程:
https://www . data camp . com/community/tutorials/introduction-t-SNE - 绘制雷达/蜘蛛图:
https://python-graph-gallery . com/392-use-faceting-for-Radar-chart/
用 Python 编程识别支持/阻力趋势线
背景
确定支撑位和阻力位的趋势线分析传统上是由经济学家在图表(如特定证券的收盘价图表)上手工画线来完成的。这种任务的计算机化自动化在大量的图书馆中还没有得到很好的实现。这种任务的开始需要得到趋势线的客观定义:
“在金融中,趋势线是证券价格运动的边界线。当在至少三个或更多价格支点之间画一条对角线时,它就形成了。可以在任意两点之间画一条线,但是在测试之前它不能作为趋势线。因此需要第三点,即测试。”
几乎所有琐碎的尝试都达不到三点或三点以上的要求。因此,我们将讨论实现这一点的每个方面背后的技术细节。这可以进一步用于识别各种图表模式,例如三角形,包括三角形、楔形、三角旗和平行形状,例如旗帜、双/三重顶/底、头和肩、矩形,尽管所有这些通常只需要 2 个点,但是如果在适当的地方找到 3 个点,无疑将具有加强的指示。这个有趣的话题留到以后讨论。
设置
我们将使用 Python 3(尽管从 2.7 开始的任何版本都足够了)来完成这项任务,因为它有很好的库来处理数据集,并且非常容易使用。首先,必须获取股票数据。对于这一系列的例子,标准普尔 P500(股票代码: ^GSPC )的普遍指数将被用来具有实际的相关性。
Python 库 yfinance 可以非常容易地获取整个历史数据。例如,取略少于 4 年的数据或最近 1000 个点是方便的。它会将数据作为 pandas DataFrame 返回,以便于索引和其他操作;
import numpy as np
import pandas as pd
import yfinance as yf #pip install yfinance
tick = yf.Ticker('^GSPC')
hist = tick.history(period="max", rounding=True)
#hist = hist[:'2019-10-07']
hist = hist[-1000:]
h = hist.Close.tolist()
为了获得可重复的结果,数据集是在 2019 年 10 月 7 日截止时采集的,并显示了对此效果的评论过滤。
轴心点
第一个问题是确定支点。我们的点数将是给定时间的收盘价。我们可以将图表中的这些点称为波峰和波谷,或者称为局部最大值和局部最小值。
所有波峰和波谷朴素方法
有一种简单的方法可以做到这一点,因为枢轴点要求前后的点都比当前点低或高。一个简单的方法有严重的缺点,但是,如果价格连续两天保持不变,就不会检测到波峰或波谷。然而,没有发生这种情况的指数可以相对容易地计算出来:
minimaIdxs = np.flatnonzero(
hist.Close.rolling(window=3, min_periods=1, center=True).aggregate(
lambda x: len(x) == 3 and x[0] > x[1] and x[2] > x[1])).tolist()
maximaIdxs = np.flatnonzero(
hist.Close.rolling(window=3, min_periods=1, center=True).aggregate(
lambda x: len(x) == 3 and x[0] < x[1] and x[2] < x[1])).tolist()
所有波峰和波谷处理连续重复的方法
当然,折叠价格保持不变的位置和具有不同索引检索的先前代码将克服这一缺点,并产生所有这些位置,同时识别折叠的平坦区间中的单个时间。在上述计算之前删除连续的重复项非常简单:hist.Close.loc[hist.Close.shift(-1) != hist.Close]。但是,索引需要重新计算,因为它们可能已经改变:
hs = hist.Close.loc[hist.Close.shift(-1) != hist.Close]
x = hs.rolling(window=3, center=True)
.aggregate(lambda x: x[0] > x[1] and x[2] > x[1])
minimaIdxs = [hist.index.get_loc(y) for y in x[x == 1].index]
x = hs.rolling(window=3, center=True)
.aggregate(lambda x: x[0] < x[1] and x[2] < x[1])
maximaIdxs = [hist.index.get_loc(y) for y in x[x == 1].index]
这两种方法都可以使用列表和循环来完成,而不是使用 Pandas 滚动窗口技术,并且实际上通过简单的实现更改会快得多。尽管 Pandas 提供了简短优雅的代码片段,但利用它们并不总是最有效的,尤其是使用回调的聚合函数。
数值微分法
然而,解决这个问题的一个更好的方法是使用收盘价的数字导数来识别点。一阶导数是收盘价的变化率或有效动量或速度,而二阶导数表示一阶导数或其加速度的变化率。正态微分在这里并不适用,因为离散时间序列需要离散的数值分析工具。有几个优点,包括数值导数将通过考虑从要计算变化率的点开始的给定范围内的所有点来平滑数据。例如,5 点模板方法考虑了点本身之前和之前的一些增量,以及点本身之前和之前的双重增量。findiff 库使这种计算变得简单而精确,即使使用更高阶的近似方法:
from findiff import FinDiff #pip install findiff
dx = 1 #1 day interval
d_dx = FinDiff(0, dx, 1)
d2_dx2 = FinDiff(0, dx, 2)
clarr = np.asarray(hist.Close)
mom = d_dx(clarr)
momacc = d2_dx2(clarr)
通过计算一阶和二阶导数,有效地实现了一定程度的平滑,给出了显著的波峰和波谷。它们是一阶导数为 0 的地方,因为没有动量表示方向发生了变化。正的或负的二阶导数分别表示波谷或波峰,因为向上与向下的加速度表示方向相反。然而,0 的精确一阶导数是非常不可能的。相反,实际上,0 一侧的值后面跟着另一侧的值,因为 0 导数点出现在两天之间。因此,基于这一点,较高或较低的收盘价将在发生 0 交叉的两点之间选择:
def get_extrema(isMin):
return [x for x in range(len(mom))
if (momacc[x] > 0 if isMin else momacc[x] < 0) and
(mom[x] == 0 or #slope is 0
(x != len(mom) - 1 and #check next day
(mom[x] > 0 and mom[x+1] < 0 and
h[x] >= h[x+1] or
mom[x] < 0 and mom[x+1] > 0 and
h[x] <= h[x+1]) or
x != 0 and #check prior day
(mom[x-1] > 0 and mom[x] < 0 and
h[x-1] < h[x] or
mom[x-1] < 0 and mom[x] > 0 and
h[x-1] > h[x])))]
minimaIdxs, maximaIdxs = get_extrema(True), get_extrema(False)
相当长的讨论,并且如果所有的最小值和最大值点都是期望的,那么可以组合来自朴素方法的指数,因为它将捕获在动量计算期间被平滑的那些指数。但是对于趋势线来说,突出的支点通常是理想的,其他的大多是嘈杂的或不相关的。这是一个小图,显示了 10 天的速度和加速度值以及确定的支点。

Closing Price with Pivot Points, Momentum, Acceleration
现在,对于那些不希望将这种计算仅仅留给 findiff 这样的库的数学好奇者,我将建议如何计算这一数据中的单个点(2019-09-27 收于 2961.79,其先前为 2977.62,其后续为 2976.74)。它计算自己的系数来启用高阶方法。
大多数人将导数理解为 y 相对于 x 的变化(δ—△)。对于连续线,这仅仅是线的斜率,对于后续点,通过取 y 值的差来计算是微不足道的。但是导数实际上是当△x 趋近于 0 时△x 对△y 的极限。对离散数据点进行这样的限制实际上需要扩大被观察的点的数量。
这意味着,这里存在技术上的数据泄漏,尽管这是一个无关紧要的小问题,因为未来的值是根据过去的数据来考虑的。这很微妙,也不是特别重要,但是如果在数值导数的时间间隔窗口内使用最近的数据来寻找趋势线,也许提到的不同技术会更好。我们在谈论多少天?这取决于准确性,默认情况下,窗口的每一边只有 1 天,除了最左边和最右边的值会提前或滞后 2 天。
因此,我将给出显示一个中心点的手工计算的代码:
import findiff
coeff = findiff.coefficients(deriv=1, acc=1)
print(coeff)
这将产生:
{‘center’: {‘coefficients’: array([-0.5, 0\. , 0.5]),
‘offsets’: array([-1, 0, 1])},
‘forward’: {‘coefficients’: array([-1.5, 2\. , -0.5]),
‘offsets’: array([0, 1, 2])},
‘backward’: {‘coefficients’: array([ 0.5, -2\. , 1.5]),
‘offsets’: array([-2, -1, 0])}}
所以 findiff 对所有的中心点使用-0.5,0,0.5 的系数和-1,0,1 的时间差。当然,在最右边和最左边,它使用向前和向后的值。计算所有的导数很简单。计算所谓的有限差分系数和窗口大小的细节不在这里的范围内,但是有表格和容易计算它们的方法。
hist = hist[:'2019–10–07']
day = 23043 # September 27, 2019 per example (-7 or 7th last point)
sum([coeff[‘center’][‘coefficients’][x] *
hist.Close[day + coeff[‘center’][‘offsets’][x]]
for x in range(len(coeff[‘center’][‘coefficients’]))])
结果如图所示:
- -0.44000000000005457=-2977.62/2+2976.74/2
对于加速度:
coeff=findiff.coefficients(deriv=2, acc=1)
print(coeff)
产量:
{‘center’: {‘coefficients’: array([ 1., -2., 1.]),
‘offsets’: array([-1, 0, 1])},
‘forward’: {‘coefficients’: array([ 2., -5., 4., -1.]),
‘offsets’: array([0, 1, 2, 3])},
‘backward’: {‘coefficients’: array([-1., 4., -5., 2.]),
‘offsets’: array([-3, -2, -1, 0])}}
再说一遍:
sum([coeff[‘center’][‘coefficients’][x] *
hist.Close[day + coeff[‘center’][‘offsets’][x]]
for x in range(len(coeff[‘center’][‘coefficients’]))])
我们还看到了期望值:
- 30.779999999999745=2977.62–2961.79*2+2976.74
精确度可以提高,虽然看起来好像使用了默认值 1,所以它不是我给你指出的 5 点模板,但它有自己的技术来生成偏移和系数,维基百科也有一些细节。已经划分的 5 点模板系数可以通过print(findiff.coefficients(deriv=1, acc=3))找到。然后,它会向前和向后看 2 天,而不是只看 1 天,其中的[1/12, -2/3, 0, 2/3, -1/12]完全相同,只是顺序相反。注意,acc=3可以作为附加参数传递给 FinDiff 构造函数,以获得更高的准确性。
趋势线方法
最终得到枢轴点后,选择其中的 2 个将非常容易,因为任何一对最小值或最大值点将构成一条线,总共有n(n-1)条线,这些线都是完美的拟合线,因为基本几何学表明 2 个唯一的点是一条线的一个描述。然而,可以通过n(n-1)(n-2)方式选择 3 个点,并且这种枚举的算法复杂度为 O(n)。这些点不会总是在一条直线上,所以也会有一定程度的误差。误差的程度将是重要的,因为我们需要过滤掉不正确的点集,并包括正确的点集。
基本几何现在将发挥作用,因为线可以更方便地表示为斜率和截距,因此可以很容易地计算线上任何位置的点。回想一下,对于 2 个点,具有 x 轴和 y 轴的二维平面中直线的斜率 m 定义为 y 的变化除以 x 的变化,截距 b 通过使用其中一个点来拟合标准直线方程来计算。幸运的是,我们可以使用斜率截距符号,因为在时间序列数据中没有无限的斜率,每个时间只有一个点。否则,可以使用带有角度-距离的极坐标符号。根据勾股定理给出的直角三角形,两点之间的距离是从由这些点形成的矩形的对角线开始的直线距离。
对于 3 个点,如果我们将直线拟合到 2 个点,我们可以计算从该直线到 y 轴上第 3 个点的距离大小。然而,这个距离作为误差度量是相当任意的,因为它会根据我们从 3 个可能的选择中选择哪 2 个点而不同,另外 2 个是:第一个和最后一个或第二个和第三个。所有这些公式及其示例都以可视化方式给出:

Closing Price Points Demonstrating Line Calculations
给定 3 个点,可以找到最佳拟合线,这正是线性回归所计算的。它基于最小化误差找到最适合 3 个或更多点的线。误差通常是残差的平方和,其中残差等于上面给出的距离度量。所需的主要等式是直线的斜率,根据该斜率,使用 y 和 x 的平均值按照上述方法计算截距。
给出了两个标准误差,一个是斜率误差,另一个是截距误差。对于斜率,误差基于残差平方和(SSR ),部分通过除以点数减去 2 来计算,其中在这种情况下点数为 3,使得该项消失,因此它仅仅是 y 差的平方和的平方根除以 x 差的平方和。截距误差是由 x 值调整的斜率误差。斜率误差是误差的充分指标,因此将应用它。应该注意的是,SSR、斜率和截距误差假设点的方差是恒定的,但实际情况可能并非如此。假设标准斜率和截距误差呈正态分布,这也可能不成立,这将表明 68%的点在该误差值的正负范围内,根据标准偏差钟形曲线,两倍误差为 95%,三倍误差为 99.7%。既然方差不能保证,也不是正态分布,为什么还要使用 SSR 衍生的东西呢?因为我们不是假设分布,而仅仅是将误差值与固定百分比误差进行比较,所以我们的用例使斜率误差足以进行选择。SSR 仍然可以保留,因为它也可以用于相同的目的,尽管具有非常相似的结果。然而,对期望值和实际值做一个简单的直线平均是最简单的,也是可以接受的。

Closing Price Points Demonstrating Linear Regression
在 Python 中,3 点变量可以有效地编码为:
def get_bestfit3(x0, y0, x1, y1, x2, y2):
xbar, ybar = (x0 + x1 + x2) / 3, (y0 + y1 + y2) / 3
xb0, yb0, xb1, yb1, xb2, yb2 =
x0-xbar, y0-ybar, x1-xbar, y1-ybar, x2-xbar, y2-ybar
xs = xb0*xb0+xb1*xb1+xb2*xb2
m = (xb0*yb0+xb1*yb1+xb2*yb2) / xs
b = ybar - m * xbar
ys0, ys1, ys2 =
(y0 - (m * x0 + b)),(y1 - (m * x1 + b)),(y2 - (m * x2 + b))
ys = ys0*ys0+ys1*ys1+ys2*ys2
ser = np.sqrt(ys / xs)
return m, b, ys, ser, ser * np.sqrt((x0*x0+x1*x1+x2*x2)/3)
然而,出于一般性考虑,但以速度为代价,我们将对任意数量的点实现这一点,因为更好的趋势线可能具有甚至多于 3 个点,因为 3 只是最小值:
def get_bestfit(pts):
xbar, ybar = [sum(x) / len (x) for x in zip(*pts)]
def subcalc(x, y):
tx, ty = x - xbar, y - ybar
return tx * ty, tx * tx, x * x
(xy, xs, xx) =
[sum(q) for q in zip(*[subcalc(x, y) for x, y in pts])]
m = xy / xs
b = ybar - m * xbar
ys = sum([np.square(y - (m * x + b)) for x, y in pts])
ser = np.sqrt(ys / ((len(pts) - 2) * xs))
return m, b, ys, ser, ser * np.sqrt(xx / len(pts))
更普遍的是, numpy 库有 polyfit 和 poly1d 函数,它们可以对任何多项式做同样的事情,在这种情况下,多项式是 1 的线或次数。我们将使用它来计算平均支撑线和平均阻力线,分别基于所有的局部最小值和最大值点。斜率和截距由 polyfit 和 poly1d 返回,尽管 poly1d 提供了更简单的操作和使用,因此证明了返回的误差仅仅是剩余误差的平方和,但用于比较的目的是一致的。虽然适用于单一用途,如整体平均值,但这种通用函数可能没有上面写的优化函数快,因此需要练习编写它们。当然,至少有 2 个点必须符合这条线。
ymin, ymax = [h[x] for x in minimaIdxs], [h[x] for x in maximaIdxs]zmin, zmne, _, _, _ = np.polyfit(minimaIdxs, ymin, 1, full=True) #y=zmin[0]*x+zmin[1]
pmin = np.poly1d(zmin).c
zmax, zmxe, _, _, _ = np.polyfit(maximaIdxs, ymax, 1, full=True) #y=zmax[0]*x+zmax[1]
pmax = np.poly1d(zmax).c
print((pmin, pmax, zmne, zmxe))
如果更喜欢按照文档使用数值更稳定的代码,那么也有多项式. fit 版本:
p, r = np.polynomial.polynomial.Polynomial.fit
(minimaIdxs, ymin, 1, full=True) #more numerically stable
pmin, zmne = list(reversed(p.convert().coef)), r[0]
p, r = np.polynomial.polynomial.Polynomial.fit
(maximaIdxs, ymax, 1, full=True) #more numerically stable
pmax, zmxe = list(reversed(p.convert().coef)), r[0]
但是,由于误差的绝对值是相对于时间段和该时间段内的价格范围而言的,因此误差不能一致地应用于所有证券。所以首先应该计算一个合适的比例:scale = (hist.Close.max() — hist.Close.min()) / len(hist)。(如果仅仅取剩余误差的平方根并除以(n-2 ),那么这里除以len(hist)是不必要的。)那么趋势线函数的参数errpct 将是简单的百分比误差,例如 0.5%=0.005,其中fltpct=scale*errpct。应该处理其他细微差别,例如斜率 0 不是作为系数返回的,必须手动填充。
朴素方法
该方法将简单地枚举 3 个点的所有组合,以找到相关的误差,并过滤掉误差值太大的那些。当然,O(n)并不理想,如果数据集足够大,实际上也不可行。但是实际上总的最小值和总的最大值点不会大到不可能的程度。例如,100 个轴心点就是 100100100=1,000,000 或一百万次计算。显然,用 4 或 5 分来加强这一点将开始变得不切实际,因为复杂性的顺序去了 O(n⁴)或 O(n⁵).因此,需要一种不同的策略。
def get_trend(Idxs):
trend = []
for x in range(len(Idxs)):
for y in range(x+1, len(Idxs)):
for z in range(y+1, len(Idxs)):
trend.append(([Idxs[x], Idxs[y], Idxs[z]],
get_bestfit3(Idxs[x], h[Idxs[x]],
Idxs[y], h[Idxs[y]],
Idxs[z], h[Idxs[z]])))
return list(filter(lambda val: val[1][3] <= fltpct, trend))
mintrend, maxtrend = get_trend(minimaIdxs), get_trend(maximaIdxs)
排序斜率法
幸运的是,这些点的某些属性可以相互关联,例如直线的斜率(或与原点的角度)。通过对 2 个点的所有组合进行 O(n)遍历,并计算它们形成的线的斜率,可以生成每个点的斜率列表。对于像合并排序这样的高效排序算法,对列表进行排序的最坏情况复杂度是 O(n log n ),我们需要对所有 n 个列表进行排序,复杂度是 O(n log n)。排序后的斜率列表中的相邻点可以被认为是从 3 到连续的,然而许多点继续满足过滤标准。一旦匹配,该组将被删除,搜索将继续。这部分算法也是 O(n)。(最大的复杂性因素通常是唯一要考虑的因素,以保持公式简化,从而不添加其他 2 O(n)。)
这是一种近似算法,并不详尽,因为当点之间的距离很大时,按斜率排序并不能保证相邻的斜率值具有最佳拟合。然而,在实践中,这种情况很少发生。
def get_trend_opt(Idxs):
slopes, trend = [], []
for x in range(len(Idxs)): #O(n^2*log n) algorithm
slopes.append([])
for y in range(x+1, len(Idxs)):
slope = (h[Idxs[x]] - h[Idxs[y]]) / (Idxs[x] - Idxs[y])
slopes[x].append((slope, y))
for x in range(len(Idxs)):
slopes[x].sort(key=lambda val: val[0])
CurIdxs = [Idxs[x]]
for y in range(0, len(slopes[x])):
CurIdxs.append(Idxs[slopes[x][y][1]])
if len(CurIdxs) < 3: continue
res = get_bestfit([(p, h[p]) for p in CurIdxs])
if res[3] <= fltpct:
CurIdxs.sort()
if len(CurIdxs) == 3:
trend.append((CurIdxs, res))
CurIdxs = list(CurIdxs)
else: CurIdxs, trend[-1] = list(CurIdxs), (CurIdxs, res)
else: CurIdxs = [CurIdxs[0], CurIdxs[-1]] #restart search
return trend
mintrend, maxtrend =
get_trend_opt(minimaIdxs), get_trend_opt(maximaIdxs)
事实上,许多 3 分的匹配会立即被 4 分甚至更多的点所取代,因为不出所料,在真实的证券或指数数据中确实会出现趋势。
霍夫线变换法
存在尝试解决线寻找算法的替代方法,并且一种来自图像处理,其中在图像中寻找线是计算机视觉中常见且重要的任务。这样做的一种方法是霍夫线变换,该变换寻找以特定角度穿过线的点,同时根据找到的点的数量对它们进行评分。这个算法也有局限性。不幸的是,它使用大量的内存来跟踪所有的直方图,并且它的准确性是基于尝试了多少个角度。尝试的角度越多,速度就越慢。记忆基于图像的对角线尺寸(用width 乘height 尺寸)和角度数量(numangles或ceil(sqrt(width*width+height*height)) * numangles)。为了获得好的结果,缩放图像是必要的。事实上,arctan(2/width)和arctan(2/height)中较小的一个将是寻找所有 3 点可能性的最小角度,因为它是垂直线或水平线之间的最小增量。通过将算法固定为 90 度和-90 度之间的 360*5 个可能角度,我们可以使用int(np.ceil(2/np.tan(np.pi / (360 * 5))))来找到最大图像尺寸,并且如果图像超出界限,就仅仅缩放这个量。
我们首先将时间序列数据改编成图像。这需要将价格值离散化,可以通过乘以 100 来消除金额的小数部分。该图像只需要按时间长度和最低和最高价格在这段时间的大小。整个图像将被初始化为黑色,白色点将被设置在最小值或最大值点出现的适当位置。
def make_image(Idxs):
max_size = int(np.ceil(2/np.tan(np.pi / (360 * 5)))) #~1146
m, tested_angles =
hist.Close.min(), np.linspace(-np.pi / 2, np.pi / 2, 360*5)
height = int((hist.Close.max() - m + 0.01) * 100)
mx = min(max_size, height)
scl = 100.0 * mx / height
image = np.zeros((mx, len(hist))) #in rows, columns or y, x
for x in Idxs:
image[int((h[x] - m) * scl), x] = 255
return image, tested_angles, scl, m
霍夫变换作为一种点输入算法,在 Python 中很容易实现。对于所有角度和所有点,计算到垂直于穿过该点的角度的直线的距离。对于每个角度,到这条垂直线的距离是累积的一个点,其中具有相同几何距离的不同点必须位于一条直线上。

Closing Price Points Demonstrating Hough transform accumulation of rho-theta for 2 point line whose origin is based as the first day and minimal price shown
这导致 O(n*m)算法,其中 m 是角度的数量,而存储器使用需要 m 倍于 2 维点空间的对角线长度:
def hough_points(pts, width, height, thetas):
diag_len = int(np.ceil(np.sqrt(width * width + height * height)))
rhos = np.linspace(-diag_len, diag_len, diag_len * 2.0)
# Cache some resuable values
cos_t = np.cos(thetas)
sin_t = np.sin(thetas)
num_thetas = len(thetas)
# Hough accumulator array of theta vs rho
accumulator =np.zeros((2 * diag_len, num_thetas), dtype=np.uint64)
# Vote in the hough accumulator
for i in range(len(pts)):
x, y = pts[i]
for t_idx in range(num_thetas):
# Calculate rho. diag_len is added for a positive index
rho=int(round(x * cos_t[t_idx] + y * sin_t[t_idx])) + diag_len
accumulator[rho, t_idx] += 1
return accumulator, thetas, rhos
保持记忆的霍夫变换不返回任何特定的点信息,这对于可视化的目的是非常有用的。因此,我们可以计算所有点到直线的距离,对其进行排序,并尽可能多地选取误差容差范围内的点。一个点到一条线的距离用于确定要增加的正确累加器。记住,垂直斜率的斜率是斜率的负倒数,所以它们的乘积等于-1。相对于新点的点构造一条垂直线,并计算交点。证明的其余部分是使用点和交点之间的距离导出的。请注意,分子仅仅是 y 和预期 y 之差,而分母保持不变,因为我们只考虑一个直线斜率。显示霍夫变换的图也给出了明确的公式。
def find_line_pts(Idxs, x0, y0, x1, y1):
s = (y0 - y1) / (x0 - x1)
i, dnm = y0 - s * x0, np.sqrt(1 + s*s)
dist = [(np.abs(i+s*x-h[x])/dnm, x) for x in Idxs]
dist.sort(key=lambda val: val[0])
pts, res = [], None
for x in range(len(dist)):
pts.append((dist[x][1], h[dist[x][1]]))
if len(pts) < 3: continue
r = get_bestfit(pts)
if r[3] > fltpct:
pts = pts[:-1]
break
res = r
pts = [x for x, _ in pts]
pts.sort()
return pts, res
我们也可以使用名为 hough_line 的 scikit-image 库的 Hough line 变换函数。需要注意的是,Python 中同样可用的 OpenCV (计算机视觉)库也有同样的功能。这也可以用少量代码手工实现,因为算法并不特别复杂,但是库被优化并且更快。请注意,累加器中只有 2 个点就足以让算法返回它们,因此我们将过滤 3 个或更多相关点。有用的细节是它反转轴,并且具有大于但不等于的阈值参数。其内部功能有一些细微的差异,特别是在累加器中选择局部最大值方面,因此结果不会完全相同。这导致执行计算的两种不同方法(一种是点优化,另一种是将点转换为图像以用于库):
def houghpt(Idxs):
max_size = int(np.ceil(2/np.tan(np.pi / (360 * 5)))) #~1146
m, tested_angles =
hist.Close.min(), np.linspace(-np.pi / 2, np.pi / 2, 360*5)
height = int((hist.Close.max() - m + 1) * 100)
mx = min(max_size, height)
scl = 100.0 * mx / height
acc, theta, d = hough_points(
[(x, int((h[x] - m) * scl)) for x in Idxs], mx, len(hist),
np.linspace(-np.pi / 2, np.pi / 2, 360*5))
origin, lines = np.array((0, len(hist))), []
for x, y in np.argwhere(acc >= 3):
dist, angle = d[x], theta[y]
y0, y1 = (dist - origin * np.cos(angle)) / np.sin(angle)
y0, y1 = y0 / scl + m, y1 / scl + m
pts, res = find_line_pts(Idxs, 0, y0, len(hist), y1)
if len(pts) >= 3: lines.append((pts, res))
return lines
mintrend, maxtrend = houghpt(minimaIdxs), houghpt(maximaIdxs)def hough(Idxs): #pip install scikit-image
image, tested_angles, scl, m = make_image(Idxs)
from skimage.transform import hough_line, hough_line_peaks
h, theta, d = hough_line(image, theta=tested_angles)
origin, lines = np.array((0, image.shape[1])), []
for pts, angle, dist in
zip(*hough_line_peaks(h, theta, d, threshold=2)):
y0, y1 = (dist - origin * np.cos(angle)) / np.sin(angle)
y0, y1 = y0 / scl + m, y1 / scl + m
pts, res = find_line_pts(Idxs, 0, y0, image.shape[1], y1)
if len(pts) >= 3: lines.append((pts, res))
return lines
mintrend, maxtrend = hough(minimaIdxs), hough(maximaIdxs)
概率霍夫线变换方法
正常霍夫线识别所使用的精确角度和所需的高处理和存储要求使得对于具有少量点的任务来说有些不切实际。点数越多,效果越好。然而,这种现有方法更多的是精确的练习。在实践中,使用概率霍夫线变换,其中以随机方式进行搜索,并且使用参数来过滤结果,包括点数的阈值。另一个库函数probabilical _ Hough _ line用于此目的:
def prob_hough(Idxs): #pip install scikit-image
image, tested_angles, scl, m = make_image(Idxs)
from skimage.transform import probabilistic_hough_line
lines = []
for x in range(hough_prob_iter):
lines.append(probabilistic_hough_line(image, threshold=2,
theta=tested_angles, line_length=0,
line_gap=int(np.ceil(np.sqrt(
np.square(image.shape[0]) + np.square(image.shape[1]))))))
l = []
for (x0, y0), (x1, y1) in lines:
if x0 == x1: continue
if x1 < x0: (x0, y0), (x1, y1) = (x1, y1), (x0, y0)
y0, y1 = y0 / scl + m, y1 / scl + m
pts, res = find_line_pts(Idxs, x0, y0, x1, y1)
if len(pts) >= 3: l.append((pts, res))
return l
mintrend, maxtrend = prob_hough(minimaIdxs), prob_hough(maximaIdxs)
这里仍然只有返回的线的 2 个点,但是阈值已经内置并为我们完成了。不幸的是,由于具有随机性的概率算法的性质,每次运行的结果都会不同。这通常使得该方法不特别适合于寻找所有趋势线。因为它可以运行多次,所以可以这样做,直到运行了一定次数或者找到了一定数量的行,以大大增加找到所有行的概率。因此,参数hough_prob_iter 指定了运行它的迭代次数,例如 10 次迭代,以增加足够行数的可能性。
请注意,由于美元在使用中,因此比硬编码 100 或 1/0.01 更好的是有一个hough_scale参数。
我们现在有 5 种不同的方法来寻找趋势线。
寻找最佳趋势线
不幸的是,仅仅因为局部最小值或最大值点在一条线上,它们可能不总是产生最好的趋势线。这是因为什么被认为是最好的往往是主观的。然而,有一些方法可以把它变成一个客观的定义。一种方法是查看价格穿过趋势线的频率,或者趋势线和价格数据穿过最早和最晚点的面积。误差百分比仅用于确定什么可能是趋势线,什么可能不是趋势线,而不是趋势线是否有用。有了足够的数据,可以发现相隔多年的线,不管它们是否是巧合,这些线都不太可能有用。通常绘制的趋势线包含信号的一侧或另一侧,因此基于区域的方法至少是合理的。一个黎曼和提供了一种积分技术,可以用来计算这个面积。事实上,这个应用程序很简单,因为它只是将两者相减,并将所有小于或大于 0 的值相加。具体来说,这将是一个右黎曼和,它使用每个时间点的函数值,而不是中点或前面的最大值或最小值。最后,用它除以天数,给出了每天趋势误差的度量,可以选择最佳趋势线。

Closing Price with Resistance and Area
def measure_area(trendline, isMin):
base = trendline[0][0]
m, b, ser =
trendline[1][0], trendline[1][1], h[base:trendline[0][-1]+1]
return sum([max(0, (m * (x+base) + b) - y
if isMin else y - (m * (x+base) + b))
for x, y in enumerate(ser)]) / len(ser)
mintrend = [(pts, (res[0], res[1], res[2], res[3], res[4],
measure_area((pts, res), True)))
for pts, res in mintrend]
maxtrend = [(pts, (res[0], res[1], res[2], res[3], res[4],
measure_area((pts, res), False)))
for pts, res in maxtrend]
mintrend.sort(key=lambda val: val[1][5])
maxtrend.sort(key=lambda val: val[1][5])
print((mintrend[:5], maxtrend[:5]))
另一个问题是,由于各种原因,这些算法即使是最好的算法在有效实现时也会很慢。进一步的优化通常是可能的,特别是通过消除 pandas 和转向具有快速 C 实现的列表和 numpy 数组。
然而,真正的问题是趋势线到底是如何被使用的?它们是短期的还是长期的,是当前的还是历史的?这一点非常重要,以至于必须处理计算窗口趋势线的想法。这里到底是怎么做窗户的?
第一个想法是选择一个窗口,比如一个季度或一年,因为窗口太短将不会产生足够有用的支点。然后,窗口必须滚动通过所有的数据,但是,不能以这种方式,其效率低下或重复工作,也不能错过可能的趋势线。一旦确定了窗口大小,就可以通过两倍窗口大小的搜索来开始工作。将窗口大小加倍可以确保找到跨越窗口边界的趋势线。则以窗口大小的增量在所有数据中搜索双窗口大小。因此,如果您的窗口是一年,您搜索过去两年,然后从过去一年之前的 3 年开始搜索,然后从之前的 4 年到之前的 2 年,等等。窗口的一部分将会被搜索两次,许多相同的或相同趋势的延伸将会出现。这在视觉上也会更令人愉快,因为趋势线画得太远会破坏情节。
因此,最后,找到的趋势线必须智能地合并在一起,以获得最终的集合。这可以通过遍历包含每个给定点的所有趋势线并应用前面讨论的斜率排序方法来完成。这对于霍夫变换方法来说是必要的,因为它们会产生冗余的点集。
def merge_lines(Idxs, trend):
for x in Idxs:
l = []
for i, (p, r) in enumerate(trend):
if x in p: l.append((r[0], i))
l.sort(key=lambda val: val[0])
if len(l) > 1: CurIdxs = list(trend[l[0][1]][0])
for (s, i) in l[1:]:
CurIdxs += trend[i][0]
CurIdxs = list(dict.fromkeys(CurIdxs))
CurIdxs.sort()
res = get_bestfit([(p, h[p]) for p in CurIdxs])
if res[3] <= fltpct: trend[i-1], trend[i], CurIdxs =
([], None), (CurIdxs, res), list(CurIdxs)
else: CurIdxs = list(trend[i][0]) #restart search from here
return list(filter(lambda val: val[0] != [], trend))
mintrend, maxtrend = merge_lines(minimaIdxs, mintrend),
merge_lines(maximaIdxs, maxtrend)
从这个讨论中得到的主要收获是客观看待你的最佳想法,要知道不是所有的人都同意任何一个客观的定义。这有助于在头脑风暴阶段不走任何捷径,以创造一个全面和适当的方法。
可视化结果
上面完成的合并过程实际上可以找到趋势线,这些趋势线恰好在相距很远的窗口中具有点,这将大大延长它们,这可能是所期望的,也可能不是所期望的。然而,出于显示的目的,对发现的趋势线重新开窗可能是理想的。不管它们是如何合并的,它们都可以被它们的窗口分开。一般来说,我们希望只画出有限范围内的未来趋势线,以避免弄乱图表。

Closing Price with best 2 Support/Resistance Trend Lines by error
请注意图中的底部支撑线从金融角度来看是正确和有用的,因为没有价格跌破它的情况。其他支撑线和阻力线只选择了半突出的峰值,根据趋势线的定义是合理的,但几乎没有什么是有用的。所以误差最小的最好趋势线,不一定是最好的支撑线和阻力线。再次测量该线上方形成的区域对股价构成阻力,该线下方对股价构成支撑,这似乎是关键。

Closing Price with best 2 Support/Resistance Trend Lines by smallest wrong side area per day
这些趋势线对于它们所包含的点的范围来说是非常好的。不显著的局部极值有时仍然会产生有限用途的趋势线,如平坦的阻力线。此外,非常短期的趋势,如非常陡峭的支撑线,可能与未来无关,可以使用进一步的试探法来尝试消除它们,尽管在短期内它可能被认为是有用的。然而,在第三点之后,由于这是一个恢复期,市场逆转,当它被发现时,它的用处已经过时了。另一个最好的支撑线和阻力线绝对是分析师希望看到的。简单地不画出未来可能也有帮助,但对于最近的窗口,整个目的是向未来投射。
最后,不是显示整个周期的 2 条最好的支撑线/阻力线,而是为了显示和推断的目的,可以对最终结果重新开窗,并且可以使用来自每个窗口的 2 条最好的线。
用作机器学习的特征
趋势线可用于推断机器学习的特征数据,例如使用 LSTM(长短期记忆)或 GRU(门控递归单元)神经网络,甚至作为 g an(生成对抗网络)的一部分来预测时间序列,以提及一些现实的现代想法。也许它们将首先通过使用 PCA(主成分分析)或自动编码器与其他特征一起被减少。
但是,为了防止数据泄露,找到的任何趋势线都只能用于预测趋势线上最后一个点之后的未来点。在最终点或之前的任何时间,都是数据泄漏的典型例子,其中未来值被用来将值泄漏到过去。即使选择最佳趋势线,也必须仅使用最终点之前的数据仔细完成,尽管可视化没有这样的限制。注意,在处理像时间序列预测这样的精确任务时,即使是典型的编程错误,如计算中的 1 误差,也是致命的错误。记住数字微分的细节,在趋势出现后,根据准确性处理前一两天的数据时,数字微分也是向前看的。假设趋势在最后一个最小值或最大值点后一两天开始会更好,以避免这种泄漏,这取决于趋势的使用方式。
把所有的放在一起
trendln 有一个包含所有代码的 GitHub 存储库。这也可以作为一个 PyPI 包 trendln 使用,可以很容易地安装和使用。
依赖项包括 numpy。绘图需要 matplotlib 和熊猫。对于基于图像的霍夫趋势线和概率霍夫趋势线,需要 scikit-image。对于数值微分,findiff 是必需的。本文中生成所有图像的代码也仅作为参考资料。
结论
我们已经分析并演示了如何使用包括数值微分在内的不同方法来寻找支点。此外,我们还展示了如何测量线中的误差,以及如何使用各种趋势线识别方法,包括使用排序斜率、霍夫线变换和概率霍夫线变换。现在可以收集和研究支撑和阻力的正确趋势线。它们可以进一步用于更复杂的分析,或用于预测证券定价的变动。
此外,鉴于它将大量信息结合到一个资源中,并对任务的每个方面进行了全面分析,因此有分享这一点的动机,而大多数声称的解决方案甚至没有满足趋势线的基本定义。
如果你喜欢这篇文章,请让我知道,并随时提问。请继续关注刚才提到的形状识别技术。
以编程方式在 Python 中构建 REGEX(正则表达式)用于模式匹配

Photo by Aidan Granberry on Unsplash
当您准备进行文本分析或自然语言处理时,正则表达式(Regex——通常读作ri-je-x或reg-x)非常有用。尽管 Regex 很有用,但它也非常令人困惑和难以理解,并且总是需要(至少对我来说)多次点击并返回到多个堆栈溢出链接。
首先:
什么是 Regex
根据维基百科,一个正则表达式、 regex 或 regexp 是定义一个搜索模式的字符序列。

Image Courtesy: comidoc.com
看起来怎么样?
这是测试 URL 有效性的正则表达式模式:
^(http)(s)?(\:\/\/)(www\.)?([^\ ]*)$
典型的正则表达式包含—字符(http)和元字符([])。这两者的结合形成了特定任务的有意义的正则表达式。
那么,有什么问题呢?
记住字符和元字符的组合方式来创建一个有意义的正则表达式本身就是一个乏味的任务,有时比 NLP 的实际问题更大。事实上,这推动了迷因世界和互联网上的卡通节目。像这样:

手边的解决方案
这个星球上的一些好人已经创建了一个开源的 Javascript 库JSVerbalExpressions来简化正则表达式的创建。然后其他一些优秀的灵魂将 javascript 库移植到 Python 上—Python verbalexressions。这就是开源世界的美妙之处。抱歉我离题了,但我想提出一个观点。
安装—python verbalexceptions
基于您所拥有的 Python 版本,您可以使用 pip 安装 PythonVerbalExpressions。
pip3 install VerbalExpressions
导入并启动
就像 ML 库scikit-learn如何使用一个基本的构造函数并在其上构建一样, VerbalExpressions 遵循类似的 API 语法。
from verbalexpressions import VerEx
verEx = VerEx()
伪问题
让我们创建一个伪问题,我们希望用 regex 来解决它,通过它我们可以理解这个包,以编程方式创建 regex。
一个更简单的例子,我们有多个这样的文本:
strings = ['123Abdul233',
'233Raja434',
'223Ethan Hunt444']
我们想从中提取名字。所以我们的结果应该是:
Abdul, Raja, Ethan Hunt
伪代码
在我们编码之前,最好在一张餐巾纸甚至一张纸上写下伪代码。也就是说,我们希望提取除数字(即数字)之外的名称(即字母的组合)。我们为一行代码构建一个正则表达式,然后为列表中的所有元素迭代它。
该表达式
我们使用range()函数来表示给定范围内的任何内容。
expression = verEx.range('a','z','A','Z',' ')
在这个表达式中,来自a-z或A-Z或whitespace的任何东西(空白:因为Ethan Hunt)。
要了解 python 代码如何实际转换成 regex 模式:
expression.source()#output: '([a-zA-Z\\ ])'
寻找和寻找
现在表达式已经准备好了,我们可以用它以多种方式查找所需的子字符串。在这里,我们将求助于 Python 库中的任何 regex re。因为我们要处理re,我们将编译为re构建的expression。
import rere_exp = expression.compile()
现在,re编译已经完成。我们将使用那个re_exp在一行中测试这个正则表达式(输入列表的一个元素)。
re.findall(re_exp,strings[0])#output: ['A', 'b', 'd', 'u', 'l']
那是有前途的!让我们使用我们的字符串连接和 for 循环技巧来完成我们的伪问题!
[''.join(re.findall(re_exp,line)) for line in strings]#output: ['Abdul', 'Raja', 'Ethan Hunt']
结束了
因此,我们设法在不了解正则表达式的情况下构建了正则表达式模式。简而言之,我们使用 Python 以编程方式生成了一个正则表达式模式(这不需要正则表达式模式的高级知识),并完成了一个小任务来展示其潜力。这里需要注意的是,这个 Python 包VerbalExpressions没有来自其父库javascript的所有函数,所以功能是有限的。要了解更多关于使用 Python 的 Regex 的信息,请查看这个 Datacamp 课程。完整的代码可在这里获得。
程序员不应该有社交障碍

Credit: sickchirpse.com
如果你是一名软件开发人员,你工作日的大部分时间都花在与人打交道上。
当你开始一天的工作时,你可能会立即查看电子邮件、Skype 或 Slack,因为你有兴趣看看你的同事是否有重要的事情要告诉你。
然后,当你写回复时,你的话的目的地是另一个人,而不仅仅是另一台计算机。
你在工作中的首要任务是解决问题,这是在项目合作和作为软件开发团队的一员与其他人交往时完成的。
您在白天参加会议,并从其他人那里获得实现需求。
当你终于准备好做一个软件开发人员最喜欢的活动之一,即写代码时,你应该写一个主要是人类可读的代码。你不用二进制代码来编写你的软件项目,而是用一种你和你的队友都更容易理解的编程语言。
即使你在一家小公司工作,或者是一名自由职业者,你仍然要和你正在从事的项目中的非技术人员打交道。
我们醒着的时候,大部分时间都和同事在一起。甚至有可能我们和他们在一起的时间比和我们亲密的家庭成员在一起的时间还多。
意识到这一点将有助于你认识到与他人保持良好关系的重要性,尤其是你的同事。
在这篇文章中,你可以读到一些可以帮助你改善工作关系的一般性建议,这些建议同样适用于其他地方。
它们比用于开发应用程序的框架更容易遵循,但是这些技巧有巨大的投资回报潜力。
1。非常友好

Photo by Austin Distel on Unsplash
你可能是你认识的最聪明、最努力工作的人,但是当你没有良好的举止或缺乏友善时,你工作场所的其他人可能会讨厌与你合作。不是因为你缺乏编写干净和可维护代码的技能和能力,而是因为你的态度。
不管一个 bug 有多令人沮丧,不要对着你的同事撅嘴、发牢骚或大喊大叫。那些不礼貌的行为无法修复你的错误。它甚至会伤害你的个人和职业关系,让你以后很难回到正轨。
当你善良时,作为一个普通人,你更容易给别人留下好印象。
简单的善举,比如送某人上下班,在别人面前提到某人的恩惠,清理同事的空间,保持清洁不是你的工作,等等。,应该反复做,尽管认识同事很久了。
你的友善至少会让你的同事们产生一种基本的人类尊严。
2。永远真诚地对你的同事感兴趣
试着理解他们是否需要你的帮助,并毫不犹豫地善意帮助他们。当你看到他们有压力、焦虑或正经历艰难时期时,与他们交谈,并表示愿意就他们所涉及的问题集思广益,寻求解决方案。
即使你不能帮助他们完成当前的任务,也要试着用激励性的话语来鼓励他们,这样可以提升他们的情绪。让他们觉得自己对团队很重要,有能力克服他们面临的障碍和可能遇到的困难。
光是你的话就能神奇地改善同事的情绪。
3。经常微笑
甚至打印一份“Hello world”声明也比微笑和发送笑脸表情符号需要更多的时间和精力。这可能看起来微不足道,但微笑可以减轻紧张。
苏格兰阿伯丁大学面部研究实验室 2011 年进行的一项科学研究发现,微笑会让你感觉更有魅力、放松、真诚和自信。
此外,发表在《斯堪的纳维亚心理学杂志》上的一项瑞典研究发现,人类会自动模仿与他们互动的人的面部表情。
换句话说,当你经常微笑时,别人也会对你微笑。这不仅会让你感觉更好,还会帮助你交往的其他人。
4。多说“谢谢”和“请”。
无论多小的帮助,都要毫不犹豫地说谢谢。
当你彬彬有礼、心存感激,并意识到他们在你生活中的贡献和意义时,人们会感激你。
这不仅会让你的同事感觉更好,也会让你感觉更好,因为你会知道其他同事关心你,愿意帮助你。
感恩可以改善你的身体健康、精神力量、自尊、睡眠质量,甚至可以降低你的压力水平。
另一个你在与同事交流时应该经常使用的词是请。
当你使用 please 这个词时,你是在提醒其他团队成员,他不是被迫做某事的,但如果他能帮助你完成你正在进行的任务,那将是善意的、慷慨的和有帮助的。
这些简单的词,不管听起来多么微不足道,一旦你开始使用它们,就会产生巨大的影响。
5。赞美他人的努力,保持积极的心态

Photo by Tyler Nix on Unsplash
意识到别人的成就,并赞美每一个进步,无论是大是小。
赞美某人并不复杂。像“干得好”、“做得好”或“你做得很好”这样的常用短语就足够了。
找到一个令人沮丧的问题的解决方案可能需要很大的努力和强大的意志力。
当你赞扬他人的成就时,你会让他们觉得自己很重要,觉得他们参与了吸引你注意力的事情。
作为软件开发人员,我们往往会忘记我们所拥有的技能的特权。
有时候,一个小问题或一项紧急任务会打扰你或你的同事一会儿,但你不应该让这分散你对全局的注意力。
你应该定期提醒自己和同事你和你的团队经历过的快乐时刻,不要让偶尔的困难或压力让你感到失望。
养成习惯,定期认可你的队友的努力,并在任何机会出现时给予他们信任。
6。不要犹豫道歉
没有人是完美的,无论你多么努力,你总会在某处犯错误。
不管你的错误有多小,都要毫不犹豫地为此道歉。
尽管有时道歉可能会威胁到你的自尊,但这是与同事保持良好关系的重要方式。
虽然你可能认为道歉会损害你的名誉,让你看起来不那么自信,但却有相反的效果。
它会提醒别人,你意识到了自己的缺点,并且足够谦虚地承认它们。
像“对不起”或“我道歉”这样的短句可以加强你和同事的关系,即使是在不愉快的时刻。
7。不要批评
批评通常被认为是对个人自我的威胁,尽管你可能并不想这样做。
因此,他或她很可能会变得情绪化,感到被冒犯了。
指出同事错误的更好方式是给他们一个礼貌的批评:提醒人们注意他们的错误,但不贬低他们或针对他们个人。
站在他们的立场上,从他们的角度观察问题。礼貌地告知对方他们的错误。
不要在众人面前指出错误;而是私下做。
如果你想告诉其他人他们不应该犯的错误,不要指向别人,而是指向问题。
试着关注解决问题的可能方法,而不是责备。
成为帮助别人的人,而不是贬低别人的人。
当你帮助别人时,你会感受到自己的贡献感和自己的重要性。此外,你可以自己学习,也可以在将来从你过去帮助过的同事那里获得帮助。
8。避免争论

Photo by Jason Rosewell on Unsplash
我们倾向于认为自己是理性的人类,但实际上,我们是情绪驱动的生物,很容易生气。
那些愤怒的时刻会导致一些不愉快的争论,不管你是对是错。
虽然你可能是对的,保护你的观点听起来是个好主意,但是与他人激烈争论会极大地破坏你的人际关系。
正如戴尔·卡耐基在他的书 中提到的如何赢得朋友和影响人 :
“我得出的结论是,在天堂之下,只有一种方法可以在争论中获胜,那就是避免争论。像躲避响尾蛇和地震一样躲避它。”
对于争论,最好的办法是不惜一切代价尽力避免。这并不像听起来那么容易,但是通过练习会变得更容易。
提醒自己大多数时候争吵几乎没有任何好处,尤其是和你的同事,这可以增强你远离争吵的愿望。
9。提问而不是直接下命令
这与团队领导或经理更相关,但也适用于其他任何人。
当人们被挑战去做某件事时,他们往往更有动力去工作,而不是被命令去做。
当你命令某人做某事时,他们的自我可能会出现,并使个人认为他受到压迫或他的自主权受到威胁。
与其直接下命令,不如问问同事是否愿意做某项工作。
如果没有其他可用的开发人员可以完成特定的任务,并且您担心他们可能不喜欢在前端工作,那么告诉他们您担心如果在下次会议中没有这个功能,团队在客户面前会很难看。
像“这样的问题,你介意加班并完成这项任务吗,知道这对我们的项目至关重要?”比类似的命令更好,“除非你完成任务,否则你不能回家。”
向他们提及他们是团队的重要组成部分,他们可以帮助团队向客户证明他们对未来的项目也是可靠的,并提出诸如“我可以将这项任务分配给你吗,我知道你对这些类型的工作已经很有经验了?”
10。避免与“有毒”的人打交道
尽管软件开发人员可能是你的理想职业,但你的工作场所可能会有一些有毒的人。
你以为你在高中就已经把这类人抛在脑后了,但事实证明生活中充满了这类人。
这些人只是在寻找机会让别人失望,通常对生活中的一切都持消极态度。
你可以试着用你善意的影响去改变他们,但往往一个人很难改变(原因很多)。
如果你发现你没有任何积极的影响,和他们交往只会给你带来麻烦,那么你应该避免和他们打交道。
然而,你可能会遇到这样的情况,这个人是你的经理或者你办公室的同事。
在这些情况下,你的选择非常有限。你可以考虑换个部门,或者开始找新工作。
如果你别无选择,只能面对他们,提前做好心理准备,告诉自己不要让他们影响你的情绪,毁了你的一整天。
他们的态度不会让你感到惊讶。
尽量少和他们互动。
结论
软件开发人员的生活不仅仅是写代码。与人打交道是关键因素之一。
虽然他们没有被提及或强调太多,社交技能是那些看似不重要的技能中的一部分,但实际上可以加速你职业生涯的发展。
它们比在白板上倒置二叉树更容易理解和实践。
虽然我同意其中一些事情说起来容易做起来难,但在我们的生活中实践它们是很重要的。
在你离开之前,让我善意地提醒你一件事:不要期望总是能够一直应用这些东西。
我们是人,我们也有自己的困难时期。
然而,我们绝不能让这几个挫折和愤怒的时刻毁掉我们整个职业和非职业的生活。
编程是艺术

一篇主张将编程作为一种艺术形式的辩论性文章
我强烈认为自己是一个有创造力的人。这是我的主要优势之一。我同样喜欢创建新概念的过程,就像我喜欢构建实现一样。它可以是任何事情,从在我信任的吉他上写一首歌到在 Adobe Illustrator 中创建一个新的设计。从起草商业模式到在《超级马里奥制造 2》中建立关卡。从用我不知道的原料烹饪一道新菜,到建立人工智能策略。有时我只是毫无目的地创造东西——有时快乐在于创造,而不是结果。并不是所有的艺术都必须被分享,有时候美只是来自于创作时内心的平静。
艺术有一种在各种奇怪的普通地方展示自己的方式。在享用一餐之前,我喜欢花点时间来欣赏它的特色。当我坐在咖啡馆时,我喜欢观察我的周围,艺术存在于每个人的表情中,存在于任何房间的每个角落。有时当我听音乐时,我会闭上眼睛,让我的创造力随着旋律的声音尽情发挥。
我真的相信艺术无处不在。当我与他人分享这一观点时,许多人似乎同意我的观点。
但是接下来就是编程了。围绕编程有一个奇怪的污名,有人说它不是一种创造性形式,而仅仅是一种逻辑形式。但是更严重的是:编程不是艺术的观念。根据这篇文章的标题,你可能会猜到,我将提出相反的论点。
编程是艺术。

Photo by Ilya Pavlov.
请允许我从最不有趣,但经常被问到的一点开始:定义。如果我们要讨论某样东西是否被归类为艺术,首先检查它的定义是很自然的,也是意料之中的,但是我不打算在这里花太多时间。如果我们要追溯这个术语的根源,牛津词典提供了一系列的定义供人们欣赏,现在我们将不再看这本广泛流传的词典。
人类创造力和想象力的表现或应用,通常以绘画或雕塑等视觉形式,创作出主要因其美感或情感力量而被欣赏的作品。
第一个定义不同于字典中的所有定义,它认为艺术的主要产品是情感力量或美。我觉得这很奇怪。当一个精心制作的王座的主要目的是让有权有势的人就座,一个巨大的城堡的主要目的是给人类居住,一个最精致的菜肴的主要目的是食用时,这怎么可能呢?如果这些作品的主要目的是以功能性的方式使用,那么它们就不被认为是艺术吗?当然,无论是谁写下了这样的定义,他都不可能漫步于维也纳的内街,那里的每一座建筑都是宏伟的,否则他们不可能想出如此狭隘的定义。
1.1 由人类的创造技能和想象力产生的作品。
令人欣慰的是,人们移动眼睛的距离不能超过几个像素来移动字典,因为第二个定义及其后的所有定义都相当宽。有人可能会说,人类创作的每一件作品都包含创造性技能和想象力,因此每一支铅笔、手机充电器和卫生纸也是潜在的艺术作品。一个人创作的每一件东西在最初创作时都需要一定的创造力。然而,如果遵循严格的公式和明确定义的规则,同一物品的后续创造可能不会。因此,只有第一支铅笔、手机充电器和卫生纸可能是一件艺术品,因为同一制造商随后制造的每一件物品都可能是在工厂里按照严格的说明制造的。然而,对于编程来说,每个产品都需要独特的指令,因为没有两个功能是完全相同的。
我可以继续定义,但我会把剩下的留给你,因为我想你会发现牛津的以下三个定义不允许任何惊喜,而是用不同的词重复相同的思想,所有这些都允许编程被解释为艺术

What is art? Perhaps, much like its creation and subsequent meaning is up for a subjective interpretation, so too is its definition? Photo by Chris Barbalis.
那么,为什么有人不认为编程是一种艺术形式呢?也许是因为代码行缺乏自然美,所以才是真正的?但是路边的一块石头,在雕塑家把它变成雕像之前,到底有多美?一个音符的集合,直到一个音乐家把它演奏出来,到底有多美?在厨师把金枪鱼变成一道菜之前,它有多美?所以我问你:在形成应用程序之前,代码行有多美?单个的元素并不总是呈现出一种自然美,但是这些元素的总和放在一起形成了一个有思想的集体作品。
因此,部分的美不可能是答案。那么逻辑呢?编程是由逻辑构成的,逻辑不是艺术,对吧?有些头脑是理性的,而有些是感性的。有些人被逻辑所吸引,有些人被传统的美所吸引。然而,列奥纳多·达·芬奇创造了建立在科学基础上的艺术,一个音乐家创造了逻辑上相互遵循的音调,一个厨师逻辑上结合了互补的配料。大多数艺术作品实际上是一个逻辑过程的结果,是对互补元素的选择。逻辑和艺术往往是相辅相成的。
大多数艺术作品实际上是一个逻辑过程的结果,是对互补元素的选择。

Art is everywhere, and can be made with any tool. For many, the computer has become a tool that allows for the creation of art, be it art, designs, music, programming applications, or otherwise. Photo by Dmitry Chernyshov.
所以逻辑也不可能是它。或许是学习曲线,以及随之而来的误解?事实上,成为一名艺术家或音乐家需要无数个小时的训练,然而任何孩子都可以在几秒钟内唱一首创作的歌曲或创作一幅画——不需要训练。也许这就是艺术的真正含义?毫不费力地投入情感,无论是伟大的还是渺小的,立即得到一个创造性的工作作为回报?虽然你可能会错,但编程也不例外。你富有创造力的额头上的汗水会以同样的方式立即呈现出来。创作过程没有什么不同,学习曲线也没有更长。
数学那么?虽然编程和数学实际上是两个非常不同的研究领域,但还有一种误解,认为这两者是一样的,数学不可能是艺术,因此编程也不可能是艺术。然而,事实并非如此,因为数千年来,数学一直是艺术的一个组成部分。早在耶稣基督诞生之前,从希腊帝国开始,艺术家就已经使用数学来雕刻、绘画、舞蹈和作曲。

Max Ernst creating a work of art using gravity as a tool to enable creativity (1942).

M. C. Escher’s famous painting Print Gallery (1956), which sparked great discussions among mathematicians and artists alike.
也许创意投入的产品本身,那么?添加颜色,收到一幅画。添加曲调并获得一首歌曲。如果你输入几行代码,你会得到什么?绝对的任何事情和一切。华丽的动画电影,史诗般的视频游戏,以及你每天与之互动的漂亮界面。虽然编程的产品并不重要,因为它毕竟是艺术,但编程是你每天都在接触的艺术作品。
我希望现在我已经让你相信编程是一种艺术,如果我没有做到这一点,这篇文章的其余部分也不会说服你,但它可能会鼓励你自己尝试编程,如果你还没有这样做的话。
最后一个误解是,编程是少数人的专利。误解可能会说,为了能够编写代码,你需要一些与生俱来的天赋,也许是逻辑思维,或者对数学的偏好。并非如此。就像任何人都可以画画一样,任何人都可以写代码。
我从未上过歌唱课。然而我喜欢唱歌。但我只有在家里舒服的时候才会这么做。听过我歌声的人少之又少,没听过的人有福了,因为不是特别好听。但我唱歌不是因为我擅长,也不是因为我想成为擅长。我这么做只是因为我喜欢。这听起来可能是显而易见的,但值得一提的是,你不需要做某件事的意图是让它变得令人惊叹。你可以试着做着玩玩。我经常听到这样的想法,人们不想尝试编程,因为他们认为自己永远也不会做好。也许你会,也许不会。那又怎样?你可能无法开发创新。你可能不会选择编程作为职业技能。但是你不需要做这两件事来享受艺术创作。
每个人都能创造艺术
使用任何介质。

My drawings are bad, but I still enjoying drawing. It’s a creative process that puts my mind at ease. I have the very same feeling when I am programming application in my spare time, or writing articles such as this one. Photo by Neven Krcmarek.

A child creating art. Photo by Lego.

Art in the making. Photo by Fabian Grohs.
在你离开之前,我们下次再讨论另一个话题,让讨论更进一步。下面这幅画是人工智能做的。它在拍卖会上以 432,500 美元售出。没有人类任何输入的艺术还是艺术吗?因为在这个过程中没有人参与,真的有任何创造力和想象力吗?或者这只是一个简单的计算机执行的自动化逻辑过程?但是,在创作艺术作品时,人类不也是遵循逻辑流程的计算机吗?嗯,这是另一个时间的辩论。

Is art made without human input still art? Photo from Obvious Art, who also developed the AI that created this painting.
控制数据密集型(大数据、快速数据)框架的编程语言

回到过去,公司主要使用一种主要的编程语言(如 C、C++、Java、c#…)、一种数据库(SQL)和两种数据交换格式(XML、JSON)。自 21 世纪初以来,随着互联网和移动设备的兴起,情况发生了变化。每年,数据的数量和速度都在成倍增长,并继续呈指数级增长。
像亚马逊、谷歌这样的大型 IT 公司突然发现,现有的工具和框架不足以处理这些大而快的数据。这些公司开发了封闭的源代码框架来处理“网络规模”的数据,并以论文的形式分享他们的概念/架构。2003 年, Google 发表了 Google 文件系统 论文接着是 地图缩小 论文和 大表 论文。还有,亚马逊在 2007 年发表了 迪纳摩 的论文。由于整个行业面临着处理“网络规模”数据的相同问题,开发人员社区和行业采用了这些论文的概念,并开发了类似 Hadoop 生态系统、 NoSQL 数据库和其他框架的系统。随着 GitHub 和开源友好性的普及,在接下来的几年中,业界开发并采用了更多的数据密集型框架。
最近我在读一本很棒的书: 设计数据密集型应用 ,作者是Martin Kleppmann,该书对各种数据密集型框架进行了全面深入的概述。在读那本书的时候,一个问题突然出现在我的脑海里:数据密集型框架中使用最多的编程语言是什么?
在这里,我将尝试在开源数据密集型框架中找到最常用的编程语言。由于有许多数据密集型框架/库,我将主要关注每个类别中的顶级开源框架。此外,如果一个框架/库是用多种语言编写的,我将只选择主要语言。
搜索引擎
1997 年, Doug Cutting 试图使用当时“刚刚起步”的编程语言 Java 开发一个全文搜索引擎。他在 3 个月内完成了基本功能的开发,并于 1999 年发布了名为 Lucene 的 Java 库,用于信息搜索/检索。Lucene 是一个 Java 库,不能与其他编程语言集成。Apache Solr 是一个搜索引擎,它提供了一个围绕 Lucene 的 HTTP 包装器,因此它可以与其他编程语言集成。Solr 还通过 SolrCloud 提供可伸缩的分布式全文搜索。和 Lucene 一样,Solr 也是用 Java 开发的,并于 2006 年首次开源。为了给妻子搜索食谱, Shay Banon 开发了分布式搜索引擎 Elasticsearch (以前的 Compass )。Elasticsearch 提供了一个围绕 Lucene 的 RESTful API 以及一个完整的分布式全文搜索栈(ELK)。它于 2010 年发布,用 Java 开发。Sphinx 是这个列表中最后一个分布式搜索引擎。早在 2001 年,安德鲁·阿克肖诺夫就开始开发搜索引擎 Sphinx 来搜索数据库驱动的网站。Sphinx 提供 REST 以及 SQL 类(SphinxQL)搜索客户端,并在 C++ 中开发。
- Lucene:Java
- Solr : Java
- 弹性搜索:Java
- 狮身人面像 : C++
文件系统
2002 年,道格·卡丁和迈克·卡法瑞拉在为纳特项目工作,抓取并索引整个互联网。2003 年,他们达到了一台机器的极限,并希望在四台机器上扩展它,但面临着在节点之间移动数据、空间分配的问题(Hadoop 的历史 )。.)2003 年, Google 发表了其 Google 文件系统 的论文。以谷歌论文的蓝图为基础,道格·卡丁和迈克·卡法雷拉开发了 Hadoop 分布式文件系统 HDFS (当时命名为 NDFS),它提供了无模式、持久、容错的分布式存储系统。 HDFS 在 Hadoop 生态系统和大数据框架的开发中发挥了关键作用。尽管 Hadoop MapReduce 正在失去对新框架(Spark/Flink)的吸引力,但 HDFS 仍然被用作事实上的分布式文件系统。像其他 Hadoop 框架一样,它是用 Java 开发的,并于 2005 年开源。在格鲁斯特公司中。,Anand Babu peri Samy为超大规模数据开发了软件定义的分布式存储 GlusterFS 。GlusterFS 于 2010 年开源,用 C 编写。受脸书论文大海捞针:脸书的照片存储 ,卢沛宁实现了一个简单且高度可扩展的分布式文件系统,名为 SeaweedFS ,可以存储数十亿个文件。2011 年开源,用 Go 编写。另一个强大的分布式存储平台是 Ceph ,它提供对象级、块级和文件级存储。Ceph 最初由 Inktank 开发,目前由以红帽为首的一批公司开发。尽管 Ceph 是在 2006 年首次发布的,但它的第一个稳定版本是在 2012 年。它的文件系统, CephFS 是用 Rust 编写的,尽管 Ceph 对象存储是用 C++ 编写的。德国 Fraunhofer Institute 开发了一个并行集群文件系统 BeeGFS 针对高性能计算进行了优化,重点关注灵活性、可扩展性和可用性。BeeGFS 的第一个测试版本发布于 2007 年。它是用 C++ 以非常紧凑的方式(约 40k 行代码)开发的。虽然 BeeGFS 是一个并行文件系统,并广泛用于超级计算机,但它可以作为 HDFS 的一个更快的替代方案。
成批处理
在开发完之后,HDFS 、道格·卡丁和迈克·卡法雷拉想要在多台机器上为项目纳特奇运行大规模计算。受Google Map Reduce论文的启发, Doug Cutting 和 Yahoo 的一个团队开始开发大规模、使用商用硬件的分布式计算, Hadoop MapReduce 诞生了。Hadoop MapReduce 和 HDFS 于 2005 年首次开源,取得了巨大的成功,为包括 Yarn、HBase、Zookeeper 等数据密集型框架的整个生态系统铺平了道路。像 Lucene 和 Nutch 一样,Hadoop MapReduce 和几乎整个 Hadoop 生态系统都是用 Java 开发的,可以说是将 Java 设定为数据密集型领域事实上的编程语言。如果说 Hadoop MapReduce 开创了大规模分布式计算,那么 Apache Spark 是目前最具优势的批处理框架。由著名的 Berkley AMPLab 开发,由 Matei Zaharia 领导,它解决了 Hadoop MapReduce 框架的局限性(例如,在每次地图作业后存储数据)。Spark 还提供了一种类似于 Unix 的方式,将多个作业组成一个数据处理管道,并为开发人员提供友好的 API,使用 RDD、数据集、数据帧对分布式任务进行编程。Spark 主要在 Scala 中开发,2014 年发布。Apache Flink 是另一个非常流行和有前途的集群计算框架。和 Spark 一样,也来自学术界,起源于图柏林大数据项目同温层。与批处理优先框架 Spark 不同,Flink 是流优先框架,将批处理作业作为流的特例来处理。像 Spark 一样,Flink 也提供开发人员友好的 API,使用数据集、数据流对分布式计算进行编程。Flink 主要用 Java 编写,2015 年发布。
- Hadoop MapReduce : Java
- 火花 : Scala
- Flink : Java
流处理
Hadoop MapReduce 发布后,被社区广泛接受,公司开始使用 Hadoop MapReduce 处理大规模数据。不久之后,公司需要实时分布式流处理框架,因为在现实中,数据主要以流的形式产生。由于许多因素(例如容错、状态管理、定时问题、窗口等),实时处理大规模数据流比处理批处理作业更困难。第一个开源实时分布式流处理框架是由 Nathan Marz 开发的 Apache Storm ,并在 2011 年发布,口号是“实时的 Hadoop”。它是用 Java 开发的。此外,Apache Spark 还提供了一个流处理框架“ Spark Streaming ”,它使用微批处理方法进行流处理,具有接近实时的处理能力。如果 Apache Storm 是普及分布式流处理的框架,那么 Apache Flink 将分布式流处理向前推进了一步,可以说是当前分布式实时流处理的领导者。除了低延迟流处理,它还提供了一些创新和高级功能,如状态管理、一次处理、迭代处理和窗口。另一个有趣的分布式流处理框架是由 NSA 于 2006 年开发的 Apache NiFi。虽然它不提供像 Flink 那样的高级流处理特性,但对于大规模数据的简单用例来说,它是一个非常有用的框架。Apache NiFi 也是用 Java 开发的,2014 年开源。 Apache Samza 是在 Linkedin 中开发的这个列表中的最终流处理框架。Samza 提供了一些高级的流处理特性,但是与 Apache Kafka 和 Yarn 紧密结合。它也是用 Java 开发的,并于 2005 年发布。
- [风暴](http://Stream Processing: After the release of Hadoop MapReduce, it was widely accepted by the community and companies started to process their large-scale data with Hadoop MapReduce. Soon after, companies needed Real Time distributed Stream processing frameworks because in reality, data is produced mainly as Stream. Processing Stream in Real Time for large scale data is more difficult than processing Batch job due to many factors (e.g. Fault Tolerance, State Management, Timing issues, Windowing ...). The first open source Real Time distributed Stream processing framework was Apache Storm released in 2011 with the slogan "The Hadoop of Real-Time". It was developed in Java. Also Spark offers a Stream processing framework „Spark Streaming“ which uses Micro-Batch approach for Stream processing with near Real Time processing. If Apache Storm was the framework that popularized the distributed Stream processing, then Apache Flink has taken distributed Stream processing one step further and is arguably the current leader in distributed Real Time Stream processing. Along with low latency Stream processing, it additionally offers some innovative and advanced features like State Management, exactly once handling, Iterative processing, Windowing. Another interesting distributed Stream processing framework is Apache NiFi developed by NSA in 2006. Although it does not offer advanced Stream processing features like Flink, it is a very useful framework for simple use cases with large scale data. Apache NiFi also developed in Java and open sourced in 2014. Apache Samza is the final Stream processing framework in this list which offers some advanced features but tightly coupled with Apache Kafka and Yarn. It is also developed in Java and released in 2005. • Storm (http://storm.apache.org/): Java • Spark (https://spark.apache.org/): Scala • Flink (https://flink.apache.org/): Java • NiFi (https://nifi.apache.org/): Java • Samza (http://samza.apache.org/): Java Messaging: Messaging is one of the preferred ways to communicate between processes/Micro Services/systems because of its asynchronous nature. Predominantly, messaging is implemented via a Queue where producers write messages in Queue, consumers read messages from Queue and once all consumers read the message, it is deleted from the Queue (fire and forget style). When Linkedin had broken their very large Monolith application to many Micro Services, they had the issue of handling data flow between the Micro Services in a scalable way without any single point of failure. A group of Linkedin Engineers (Jey Kreps, Neha Narkhede, Jun Rao) have used distributed Log (like write ahead log in Databases) instead of Pub/Sub Queue for messaging and developed Apache Kafka. Like File systems or databases in Batch processing, Kafka works as a “single source of truth” in the Stream processing world and used heavily for data flow between Microservices. Kafka is developed mainly in Java and first open sourced in 2011. Yahoo has developed Apache Pulsar as a distributed Messaging framework which offers both Log based messaging (like Kafka) and Queue based messaging. Apache Pulsar was open sourced in 2016 and mainly developed in Java. Rabbit Technologies Inc. has developed the traditional Queue based Pub-Sub system RabbitMQ. It was first released in 2007 and developed using Erlang. ActiveMQ is another Queue based Pub-Sub system which implements the Java Message Service (JMS). It was initially developed by the company LogicBlaze and open sourced in 2004. ActiveMQ is developed in Java. A relatively new but very promising Queue based distributed pub-sub system is NATS. It is developed by Derek Collison in CloudFoundry on a weekend using Ruby. NATS was released in 2016 and later reimplemented in Go. • Kafka (https://kafka.apache.org/): Java • Pulsar (https://pulsar.apache.org/): Java • RabbitMQ (https://www.rabbitmq.com/): Erlang • ActiveMQ (http://activemq.apache.org/): Java • NATS (https://nats.io/): Go Databases: I have used the DB-Engines ranking (https://db-engines.com/en/ranking) to get the top 5 distributed databases of 21st century. According to this ranking, MongoDB is ranked 5th and only superseded by the Big Four SQL databases. MongoDB is a distributed document-oriented database developed by 10gen (currently MongoDB Inc.) and released in 2009. It is one of the most popular and mostly used NoSQL databases written mainly in C/C++. Another hugely popular NoSQL database is Redis which is a distributed key-value store database. Back in 2001, an Italian developer Salvatore Sanfilippo started developing Redis to solve the scalability problem of his startup. Released in 2009, Redis is written in C. Avinash Laskshman (co-author of Amazon Dynamo paper) and Prashant Malik has developed Cassandra in Facebook to improve Facebook’s inbox search. Heavily influenced by the Google's BigTable and Amazon's Dynamo paper, Cassandra was open sourced by Facebook in 2008. Cassandra is both a distributed key-value store and wide column store which is predominantly used for time series or OLAP data and written in Java. Part of the Apache Hadoop project, HBase is developed as a distributed wide column database and released in 2008. Like almost the whole Hadoop ecosystem, HBase is developed in Java. Neo4j is the final NoSQL database in this list and de-facto standard as Graph Database. Developed by Neo4j, Inc, it was released in 2007 and written in Java. • MongoDB (https://www.mongodb.com/): C/C++ • Redis (https://redis.io/): C • Cassandra (http://cassandra.apache.org/): Java • HBase (https://hbase.apache.org/): Java • Neo4j (https://neo4j.com/): Java) : Java
- 火花 : Scala
- 弗林克 : Java
- NiFi : Java
- Samza : Java
信息发送
消息传递是进程/微服务/系统之间通信的首选方式之一,因为它具有异步的特性。消息传递主要是通过队列实现的,在队列中,生产者将消息写入队列,消费者从队列中读取消息,一旦所有消费者都读取了消息,就从队列中删除该消息(触发并忘记风格)。当 Linkedin 将他们非常庞大的应用程序分解成许多微服务时,他们面临的问题是如何以可扩展的方式处理微服务之间的数据流,而不会出现任何单点故障。一群 Linkedin 工程师( Jey Kreps,Neha Narkhede,饶俊)已经使用分布式日志(就像数据库中的预写日志)代替发布/订阅队列进行消息传递,并开发了 Apache Kafka 。像批处理中的文件系统或数据库一样,Kafka 在流处理世界中作为一个“真实的单一来源”,大量用于微服务之间的数据流。Kafka 主要用 Java 开发,并于 2011 年首次开源。雅虎已经开发了 Apache Pulsar 作为分布式消息传递框架,提供基于日志的消息传递(像 Kafka)和基于队列的消息传递。Apache Pulsar 于 2016 年开源,主要用 Java 开发。 Rabbit Technologies Inc. 开发了传统的基于队列的发布-订阅系统 RabbitMQ 。它于 2007 年首次发布,使用 Erlang 开发。ActiveMQ 是另一个基于队列的发布-订阅系统,它实现了 Java 消息服务(JMS)。它最初由 LogicBlaze 公司开发,并于 2004 年开源。ActiveMQ 是用 Java 开发的。一个相对较新但非常有前途的基于队列的分布式发布-订阅系统是 NATS。它是由德里克·科利森在一个周末使用 Ruby 在 T42 的 CloudFoundry 开发的。NATS 于 2016 年发布,后来在 Go 中重新实现。
数据库
我使用了 DB-Engines 排名来获得21 世纪的前 5 名分布式数据库。根据这个排名, MongoDB 排名第五,仅次于四大 SQL 数据库。MongoDB 是由 10gen (目前为 MongoDB Inc. )开发的分布式面向文档的数据库,于 2009 年发布。它是最流行和最常用的 NoSQL 数据库之一,主要用 c++(T17)编写。另一个非常流行的 NoSQL 数据库是 Redis,它是一个分布式键值存储数据库。早在 2009 年,意大利开发人员萨尔瓦托勒·桑菲利波开始开发 Redis 来解决他的初创公司的可扩展性问题。Redis 发布于 2009 年,用 C 编写。Avinash lask shman(Amazon Dynamo paper 的合著者)和 Prashant Malik 在脸书开发了 Cassandra 来改进脸书的收件箱搜索。深受谷歌 BigTable 和亚马逊 Dynamo 论文的影响,脸书在 2008 年对 Cassandra 进行了开源。Cassandra 既是分布式键值存储,也是宽列存储,主要用于时间序列或 OLAP 数据,用 Java 编写。作为 Apache Hadoop 项目的一部分, HBase 是作为分布式宽列数据库开发的,并于 2008 年发布。和几乎整个 Hadoop 生态系统一样,HBase 是用 Java 开发的。 Neo4j 是此列表中的最终 NoSQL 数据库,也是图形数据库的事实标准。由 Neo4j 公司开发,2007 年发布,用 Java 编写。
裁决
如果我们考虑大多数主流数据密集型框架中使用的编程语言,那么有一个明显的赢家: Java 。虽然在早期被批评为缓慢的语言,但 Java 在该领域显然已经超过了 C、C++等近金属语言或 Erlang、Scala 等一些并发友好的语言。这里我列出了 Java 在数据密集型框架中成功的五个主要原因:
- JVM :虽然进程虚拟机自 1966 年就存在了,但是 JVM 可以说已经将虚拟机的概念带到了另一个层次,并且在 Java 的流行和 Java 在数据密集型框架中的使用中发挥了巨大的作用。通过 JVM,Java 从开发者那里抽象出底层机器,并给出了第一种流行的“编写一次,在任何地方运行”的编程语言。此外,在代垃圾收集的支持下,开发人员不需要担心对象生命周期管理(尽管它有自己的问题),可以完全专注于领域问题。首先是 Sun,然后是 Oracle,这些年来改进了 JVM,目前 JVM 是久经沙场的庞然大物,通过了时间的考验。这么多语言(例如 Kotlin、Scala、Clojure)也使用 JVM 而不是开发自己的虚拟机是有原因的。
- 语言支持和框架:**Java 是在 C 之后 23 年、C++之后 10 年才开发出来的,詹姆斯·高斯林和 Sun 公司显然已经看到了 C/C++的痛点,并试图用 Java 来解决这些问题。Java 从一开始就引入了对多线程、异常/错误处理、序列化、RTTI、反射的语言支持。此外,Java 提供了一个庞大的库生态系统,这使得用 Java 进行软件开发变得更加容易。诚然,在软件架构和算法相似的情况下,C/C++在大多数时候可以远远胜过 Java。但是在很多时候,软件架构和编程范式对应用程序的性能起着更大的作用,而不是编程语言。这就是为什么用 Java 开发的 Hadoop MapReduce 框架比用 C++开发的类似框架要好。
- 开发人员的工作效率:**开发人员的工作效率是业内的圣杯,因为大多数时候,开发人员的时间是比 CPU/内存更昂贵的资源。当 Java 在 1995 年第一次出现时,它比 C/C++更简单、更精简,这导致了更快的开发时间。正如比雅尼·斯特劳斯特鲁普(C++的创造者)和指出的,Java 后来发展成了一种更重、更复杂的语言,但是在 2000 年到 2010 年期间,当大多数主流的数据密集型框架被开发出来时,Java 可能是在开发人员的生产力和性能之间取得平衡的最好的语言。此外,开发人员的生产力导致了更短的开发周期,这对于早期的数据密集型框架非常重要。
- ****容错:数据密集型框架的主要目标之一是使用廉价的商品硬件,而不是昂贵的专用硬件。这种方法的缺点是商用硬件可能会出现故障,而容错是数据密集型应用程序的基石。凭借其对错误处理、分代垃圾收集、边界检查和硬件无关编程的内置语言支持,Java 提供了比 C/C++更好的容错能力。
- ****Hadoop 因素:Java 在 Hadoop 生态系统中的使用对 Java 在数据密集型领域的普遍适应起到了很大的作用,并充当了 Java 的广告。由于 HDFS 和 Hadoop MapReduce 可以说是第一个颠覆性的数据密集型框架,后来的框架总是关注它们,并使用 Hadoop proven Java 开发自己的数据密集型框架。
将来的
每年,数据的数量和速度都呈指数级增长,带来了新的挑战。此外,数据密集型环境如此多样化,以至于“一个解决方案解决所有问题”的时代已经结束。虽然随着 GraalVM 和更新的垃圾收集( ZGC )的出现,Java 将在大数据领域占据主导地位,但是我相信 Java 将会让位于其他一些语言。在未来几年,哪些编程语言可以取代数据密集型框架中的 Java?这里我已经在后续帖子里详细讨论过: 回归金属:2019 年开发大数据框架 Top 3 编程语言 。
如果你觉得这很有帮助,请分享到你最喜欢的论坛( Twitter,脸书,LinkedIn )。高度赞赏评论和建设性的批评。感谢阅读!
如果你对编程语言感兴趣,也可以看看我下面的文章:
针对求职者和新开发人员的顶级编程语言的深入分析和排名
towardsdatascience.com](/top-10-in-demand-programming-languages-to-learn-in-2020-4462eb7d8d3e) [## 2021 年将使用的 10 大数据库
MySQL,Oracle,PostgreSQL,微软 SQL Server,MongoDB,Redis,Elasticsearch,Cassandra,MariaDB,IBM Db2
md-kamaruzzaman.medium.com](https://md-kamaruzzaman.medium.com/top-10-databases-to-use-in-2021-d7e6a85402ba) [## 回归金属:2019 年开发大数据框架的 3 大编程语言
C++,Rust,用 Java 做数据密集型框架
towardsdatascience.com](/back-to-the-metal-top-3-programming-language-to-develop-big-data-frameworks-in-2019-69a44a36a842)**
面向数据科学家的编程语言
如今有 256 种编程语言可供选择,选择学习哪种语言可能会非常困难。有些语言更适合构建游戏,有些更适合软件工程,有些更适合数据科学。
编程语言的类型
低级编程语言是计算机用来执行操作的最容易理解的语言。这方面的例子有汇编语言和机器语言。汇编语言用于直接的硬件操作,访问专门的处理器指令,或者解决性能问题。机器语言由计算机可以直接读取和执行的二进制文件组成。汇编语言需要将汇编软件转换成机器代码。低级语言比高级语言速度更快,内存效率更高。
与低级编程语言不同,高级编程语言对计算机的细节有很强的抽象性。这使得程序员能够创建独立于计算机类型的代码。这些语言比低级编程语言更接近于人类语言,并且也在幕后由解释器或编译器转换成机器语言。这些我们大多数人都比较熟悉。一些例子包括 Python、Java、Ruby 等等。这些语言通常是可移植的,程序员不需要过多地考虑程序的过程,而是将注意力集中在手头的问题上。现在很多程序员都使用高级编程语言,包括数据科学家。
数据科学编程语言

计算机编程语言

在最近的一项全球调查中,发现近 24,000 名数据专业人员中有 83%使用 Python。数据科学家和程序员喜欢 Python,因为它是一种通用的动态编程语言。Python 似乎比 R 更适合数据科学,因为它比 R 快,迭代次数少于 1000 次。据说它比 R 更适合数据操作。这种语言还包含用于自然语言处理和数据学习的优秀包,并且本质上是面向对象的。
稀有

对于即席分析和探索数据集,r 比 Python 更好。它是一种用于统计计算和图形的开源语言和软件。这不是一门容易学习的语言,大多数人发现 Python 更容易掌握。对于超过 1000 次迭代的循环,R 实际上使用 lapply 函数击败了 Python。这可能会让一些人怀疑 R 是否更适合在大数据集上执行数据科学,然而,R 是由统计学家构建的,并在其操作中反映了这一点。数据科学应用在 Python 中感觉更自然。
Java 语言(一种计算机语言,尤用于创建网站)

Java 是另一种通用的、面向对象的语言。这种语言似乎非常通用,被用于嵌入式电子设备、web 应用程序和桌面应用程序。数据科学家似乎不需要 Java,然而,像 Hadoop 这样的框架运行在 JVM 上。这些框架构成了大数据栈的大部分。Hadoop 是一个处理框架,为运行在集群系统中的大数据应用管理数据处理和存储。这允许存储大量数据,并支持更高的处理能力,能够同时处理几乎无限的任务。此外,Java 实际上有许多用于机器学习和数据科学的库和工具,它很容易扩展到更大的应用程序,而且速度很快。

关于 Hadoop 的更多信息:https://www.youtube.com/watch?v=MfF750YVDxM
结构化查询语言

SQL(结构化查询语言)是用于在关系数据库管理系统中管理数据的领域特定语言。SQL 有点像 Hadoop,因为它管理数据,然而,数据的存储有很大的不同,并在上面的视频中解释得很好。SQL 表和 SQL 查询对于每个数据科学家来说都是至关重要的,需要了解和熟悉。虽然 SQL 不能专门用于数据科学,但数据科学家必须知道如何在数据库管理系统中处理数据。
朱莉娅

Julia 是另一种高级编程语言,是为高性能数值分析和计算科学而设计的。它有非常广泛的用途,如 web 编程的前端和后端。Julia 能够使用它的 API 嵌入程序,支持元编程。据说这种语言对 Python 来说更快,因为它被设计成快速实现像线性代数这样的数学概念,并且更好地处理矩阵。Julia 提供了 Python 或 R 的快速开发,同时生成了运行速度与 C 或 Fortran 程序一样快的程序。
斯卡拉

Scala 是一种通用编程语言,提供对函数式编程、面向对象编程、强大的静态类型系统以及并发和同步处理的支持。Scala 旨在解决 Java 的许多问题。同样,这种语言有许多不同的用途,从 web 应用到机器学习,但是,这种语言只涵盖前端开发。这种语言以可扩展和适合处理大数据而闻名,因为其名称本身就是“可扩展语言”的首字母缩写。Scala 与 Apache Spark 的结合支持大规模并行处理。此外,有许多流行的高性能数据科学框架是在 Hadoop 之上编写的,可以在 Scala 或 Java 中使用。
结论
总之,Python 似乎是当今数据科学家使用最广泛的编程语言。这种语言允许集成 SQL、TensorFlow 和许多其他用于数据科学和机器学习的有用函数和库。拥有超过 70,000 个 Python 库,这种语言的可能性似乎是无限的。Python 还允许程序员创建 CSV 输出,以便轻松读取电子表格中的数据。我对新加入的数据科学家的建议是,在考虑其他编程语言之前,先学习并掌握 Python 和 SQL 数据科学实现。同样显而易见的是,数据科学家必须具备一些 Hadoop 知识。
我们来连线:
【https://www.linkedin.com/in/mackenzie-mitchell-635378101/ 号
【https://github.com/mackenziemitchell6
Python 中的进度条(还有熊猫!)

Everyone likes a low-tech ominous progress bar
时间和估计你的函数在 Python 中的进度(还有熊猫!)
在这篇文章中,我将尝试打破我自己保持的最短、最简洁的文章记录,所以不再多说,让我们开始吧!
tqdm 简介

Beautiful, isn’t it? Source: https://github.com/tqdm/tqdm
tqdm 是 Python 的一个包,可以让你立即创建进度条,并估计函数和循环的 TTC(完成时间)!
只需在您喜爱的终端上使用 pip 安装 tqdm,您就可以开始了:
pip install **tqdm**
使用 tqdm
使用 tqdm 真的相当容易,只需导入 tqdm:
>>> from **tqdm** import **tqdm**, **tqdm_notebook**
如果你在 Jupyter 笔记本环境中工作,任何时候你在你的代码中看到一个循环,你可以简单地把它包装在tdqm()或者tqdm_notebook()中。您也可以使用desc=参数对您的进度条进行描述:

Source: https://github.com/tqdm/tqdm
但是.apply()在熊猫身上的功能呢?
导入 tqdm 后,您可以启动方法:tqdm.pandas(),或者如果您在 Jupyter 笔记本环境中运行代码,请使用:
>>> from **tqdm._tqdm_notebook** import **tqdm_notebook**
>>> **tqdm_notebook.pandas()**
然后你可以简单地用.progress_apply()替换你所有的.apply()功能,真的就这么简单!

So fast..!
结束语
感谢阅读!我发现人们似乎喜欢这种快速而中肯的文章风格,就像我的 只用一行 Python 的文章探索你的数据一样,所以希望你也喜欢这篇文章!
阅读彼得·尼斯特拉普在媒介上的作品。数据科学、统计和人工智能...推特:@PeterNistrup,LinkedIn…
medium.com](https://medium.com/@peter.nistrup)**
渐进增长的甘斯
NVIDIA 发布并在 ICLR 2018 上发布的逐步增长的 GAN 架构已经成为令人印象深刻的 GAN 图像合成的主要展示。传统上,GAN 一直难以输出中低分辨率图像,如 32 (CIFAR-10)和 128 (ImageNet),但这种 GAN 模型能够生成 1024 的高分辨率面部图像。

1024 x 1024 facial images generated with the Progressively-Growing GAN architecture
本文将解释本文中讨论的用于构建渐进增长的 gan 的机制,这些机制包括多尺度架构、新层中的线性衰落、小批量标准偏差和均衡学习速率。下面提供了该论文的链接:
[## 为了提高质量、稳定性和多样性而逐步种植甘蔗
我们描述了一种新的生成式对抗网络的训练方法。关键的想法是增长发电机…
arxiv.org](https://arxiv.org/abs/1710.10196)
多规模架构

Diagram of the Multi-Scale Architecture used in Progressively-Growing GANs
上图显示了多尺度架构的概念。鉴频器用来确定生成的输出是“真实”还是“虚假”的“真实”图像被向下采样到分辨率,如 4、8 等等,最高可达 1024。生成器首先产生 4 个图像,直到达到某种收敛,然后任务增加到 8 个图像,直到 1024 个。这个策略极大地稳定了训练,想象一下为什么会这样是相当直观的。从潜在的 z 变量直接到 1024 图像包含了空间中巨大的变化量。正如以前 GAN 研究中的趋势一样,生成低分辨率图像(如 28 灰度 MNIST 图像)比生成 128 RGB ImageNet 图像容易得多。逐渐增长的 GAN 模型的下一个有趣的细节是准确理解模型如何过渡到更高的分辨率。
淡入新图层

Diagram depicting how new layers are added to progressive the target resolution from low to high resolution
如果你熟悉 ResNets ,这将是一个容易理解的概念,因为它比那要简单得多。为了解释这一点,请观察上图中的图像(b ),特别是虚线上方的 G 部分。当新的 32x32 输出图层添加到网络中时,16x16 图层的输出将通过简单的最近邻插值法投影到 32x32 维度中。这是一个需要理解的非常重要的细节。投影的(16x16 →32x32,通过最近邻插值)图层乘以 1-alpha,并与乘以 alpha 的新输出图层(32x32)连接,以形成新的 32x32 生成的图像。alpha 参数从 0 到 1 线性缩放。当 alpha 参数达到 1 时,16x16 的最近邻插值将完全无效(例如,1–1 = 0,特征映射* 0 = 0..).这种平滑过渡机制极大地稳定了逐渐增长的 GAN 架构。
下图显示了完成渐进式增长后的最终架构:

已经提出的两个概念,多尺度架构和新层中的淡化是本文的基本概念,下面的主题稍微高级一些,但是对于实现本文的最终结果仍然非常重要。如果您在这些概念的描述中发现了问题,并可以对其进行扩展,请留下您的评论:
小批量标准偏差
这种想法与许多 GAN 模型中缺乏明显的变化有关。这个问题和“模式崩溃”源于同一个根源。在 Salimans 等人的著名 GAN 论文中,他们引入了小批量鉴别。小批量鉴别将额外的特征映射连接到鉴别器上,该鉴别器由一批中所有图像的特征统计组成。这推动了批量生成的样本共享与批量真实样本相似的特征,否则迷你批量特征层将容易暴露生成的样本是假的。渐进增长的 GANs 通过增加一个更简单的、不变的特征图修改了这个想法。此恒定要素地图是从跨空间位置的批次中所有要素的标准偏差中得出的。这个最终常数映射类似地被插入到鉴别器的末端。
均衡学习率
均衡学习率背后的思想是用常数来缩放每层的权重,使得更新的权重 w’被缩放为 w’= w/c,其中 c 是每层的常数。这是在训练期间完成的,以在训练期间将网络中的权重保持在相似的比例。这种方法是独特的,因为通常现代优化器如 RMSProp 和 Adam 使用梯度的标准偏差来归一化它。这在权重非常大或非常小的情况下是有问题的,在这种情况下,标准偏差是不充分的标准化器。
应用渐进式增长、新层中的淡入、小批量标准偏差、均衡学习率以及本文中未讨论的另一个概念,逐像素归一化实现了令人印象深刻的高分辨率 GAN 结果:

LSUN Images generated at 256x256 with the progressively-growing GAN architecture, amazing detail!

Comparison of Progressively-Growing GANs (Far Left) with Mao et al. (Least Squares GANs, Far Left), and Gulrajani et al. (Improved Wasserstein GAN, middle) on the LSUN interior bedroom images
感谢您阅读本文!希望这有助于您对逐渐增长的 GAN 模型有所了解。如果你对此有任何想法,请查看这篇文章并发表评论!
值得吗?预测新加坡二手车的价格
介绍
这个项目的目的是实施一个小型项目,该项目涵盖了数据科学过程的大部分—从数据收集(网络抓取:BeautifulSoup,Python),数据清理,探索性数据分析,模型训练和测试阶段。数据来源来自新加坡的在线汽车销售门户网站 SgCarMart 。
概述: 该项目主要围绕几个核心技能展开:
- 网络抓取
- 数据清理
- 暴露于 sklearn,statsmodel
- 利用 OLS 回归
- 通过评估模式的 p 值和 R 值,消除对响应变量影响不大的特征
买车可能只是你人生中最大的一笔购买(尤其是在新加坡,我们的汽车价格过高是出了名的)。

Vin Diesel also seems to agree (photo credits here)
因此,如果一个人想在新加坡拥有一辆车,他应该花相当多的时间去思考他到底在寻找什么。
然而,决定新加坡汽车价格的因素非常复杂(想想税收、COE、销售人员追加销售等)。因此,量化一辆物有所值的汽车会是一个令人沮丧和耗时的过程。
该项目的目标
为了弥合这一差距,本项目旨在 利用数据 实现一个 线性回归模型 来调查影响二手车价格的因素,并提供一种比较基准的形式,量化一辆车的合理价格范围。

希望我的模型能够根据二手车的一些典型的、容易获得的特征来预测汽车的价格,以告知潜在的车主做出更好的购买决定。
本项目只考虑乘用车(毕竟这是自用)。普通大众不使用的货车/卡车、公共汽车和货车等商用车辆不包括在本分析中。
第一部分:网页抓取

啊,是的。项目中最乏味、最耗时的部分。问问任何经历过梅蒂斯训练营的人,当你提到“网络搜集”时,他们会不寒而栗。我已经为 web 抓取 sgcarmart 编写了代码(包括 try-except 函数,用于处理实际抓取过程中可能出现的错误),如果您打算使用它的话。代码可以在我的 github 上的这里找到。
web 抓取过程是使用 Python 3.7.3 结合一个名为 BeautifulSoup 的包来完成的。
这是通过遍历包含 100 个二手车个人列表搜索结果的搜索页面来完成的。然后,每个个人列表的链接被附加到一个列表中,并且每个个人链接被访问以抓取二手车的特征。在抓取这些特征时,它们随后被附加到数据帧。
第 2 部分:数据
在网络抓取之后,我的数据框架包含 3987 行(观察)和 17 列(特征)的数据。

A random sample of 5 rows of my dataframe
经检查,我的数据帧中有空值。这是因为有时并非所有卖家都会在列表中提供完整的汽车信息。

As seen above.
因为这些条目非常特定于汽车列表,所以我决定删除缺少值的数据行,因为这将严重影响我的分析结果。在去掉 NaN 值之后,我的数据帧只剩下 2584 行(观察值)和 17 列(特征)的数据——对于训练线性回归模型来说,这仍然是一个相当大的数量。
一个新的特性 , 车龄,也被用来代替“制造年份”,因为对我来说,更直观地了解一辆车的年龄而不是它的制造时间。
通过创建虚拟变量,车辆类型和汽车品牌也被附加到数据框架中。
挑战:细分汽车
从基本的奇瑞 QQ 到豪华的宾利欧陆 GT,汽车的声望各不相同。还可以从在这个维度上分割车辆中获得进一步的见解。浏览数据,我们可以通过几种潜在的方法对车辆进行细分:
- 车型 (
VEHICLE_TYPE):车型分为 7 类,分别是中型轿车、豪华轿车、两厢轿车、MPV、SUV、跑车、旅行车。每辆车只能分配一种车型。这种分类可能是模糊的,因为高档和大众市场品牌在所有这些细分市场生产汽车。例如,根据 sgcarmart 的定义,丰田凯美瑞、宝马 520i 和劳斯莱斯 Ghost 都属于豪华轿车。车辆类型不是分割车辆的好方法。 - 品牌 (
BRAND):一辆车的品牌可以告诉我们很多关于一辆车的信誉水平。仅品牌本身就能告诉我们很多关于汽车的 感知合意性 的信息。例如,当我们听到有人开宝马时,一般外行人的第一个想法可能是“这是一个有声望的品牌”。因此,将车辆归入各自的品牌是有意义的。
根据我对新加坡汽车品牌的了解(也是在谷歌的帮助下),汽车被分为 6 个部分,分别是异国情调、超豪华、豪华、中级、经济和预算。
提议的分段及其各自的构成如下:
- 异国情调:阿斯顿·马丁、法拉利、兰博基尼、迈凯轮
- 超豪华:宾利、路虎、玛莎拉蒂、保时捷、劳斯莱斯
- 豪华车:奥迪、宝马、捷豹、吉普、雷克萨斯、莲花、奔驰、三菱、沃尔沃
- 中级:阿尔法罗密欧、克莱斯勒、英菲尼迪、迷你、欧宝、萨博、大众
- 经济:雪佛兰、雪铁龙、菲亚特、福特、本田、现代、起亚、马自达、三菱、日产、标致、雷诺、斯柯达、双龙、斯巴鲁、铃木、丰田
- 预算:大发、宝腾
在将车辆划分为不同的声望类别后,就会出现列数过多的问题。数据集中的一些汽车品牌在新加坡太不常见,因此可能会扭曲定价,因为它们的稀有性与其感知的合意性相反。因此,我将以下品牌更名为“其他”。
7.其他:欧宝、双龙、宝腾、大发、菲亚特、阿尔法罗密欧、斯柯达、悍马、阿斯顿马丁、路特斯、福特、Jeep。
现在我们已经准备好了数据框架,是时候进入构建线性回归模型的下一步了。
第 3 部分:特性选择
首先进行特征选择,不包括汽车的品牌,以仅基于它们的特征(里程、发动机容量、车龄、公开市场价值等)获得一辆汽车的基本价格。绘制了要素相对于价格的相关矩阵的热图,以便于可视化任何具有多重共线性或与价格拟合不佳的预测变量(要素)。**

Correlation Matrix Heatmap
我们可以立刻从热图(被亮红点降级)中看到, OMV 和 ARF 高度相关( 0.96 相关)。这是理所当然的——ARF,额外的注册费(天哪,在新加坡拥有一辆汽车涉及的费用实在太多了),实际上是根据车辆的 OMV (公开市场价值)的百分比计算出来的价值。我们还观察到发动机容量和道路税有很高的相关性——因为道路税本质上是从发动机容量计算出来的。我们不需要这些高度相关的预测变量,因为它们只是在解释同样的事情。然后去掉高度相关的变量,我们最后得到这个:

A much more managaeable dataset with fewer features
Pairplot:可视化价格特征趋势
在绘制配对图时,我们看到特征(配对图的顶行)与价格之间存在某种线性关系。

Price vs Un-Transformed Features relationships
然而,数据点似乎沿着 y 轴有点间隔。因此,我们仍然可以通过对价格和里程应用对数变换来改进数据点的拟合。
**
****
Price Distribution before (left) and after (right) Applying a Log Transformation** 
Log Price and Feature relationships after applying Log Transformation
将 log 应用于 price 后,我们可以看到这些特性现在与 log price 有了更清晰的线性关系(参见 pairplots 的第一行)。
我已经尝试将汽车品牌纳入模型。然而,他们在模型中引入了太多的共线性和噪声,导致它产生了不切实际的好结果(非常好的 R 分数)。因此,本分析不包括它们。用于训练模型的最终特征是:
【目标变量(1) :日志价格
预测变量(9): 日志里程(km)、发动机容量(CC)、车龄(年数)、OMV(公开市场价值,美元)、COE 价格(美元)、COE 剩余天数(天数)、整备重量(kg)、车主数量(整数)、变速器(1 或 0)
第 4 部分:模型选择
有了最终确定的特征,我考虑使用简单的线性回归、Lasso、Ridge、ElasticNet 和多项式回归对数据建模。下面的步骤强调了我为这种情况选择最佳模型的方法。
我首先找出 Lasso、Ridge、ElasticNet 和 ElasticNet 的最佳 Alphas,然后将数据分成 60–20–20 训练/交叉验证/测试分割,用于模型选择。Lasso、Ridge 和 ElasticNet 中的最佳 alphas 用于此训练/交叉验证测试。

该模型在整个数据集的 60%上进行训练,并针对整个数据集的 20%进行验证。这种训练/验证通过 5 重验证来完成,以便更全面地获得每个测试模型的 R 值。

Mean R² scores of each Linear Model over 5-fold validation test
如上所述,在 5 重训练/交叉验证测试中,所有模型的平均 R 值均为 0.913,多项式回归的例外为 0.829。由于它们如此相似,我选择使用最普通的线性回归形式: 简单线性回归 来对数据建模,以便于使用(不像其他模型那样需要烦人的超参数)。
第 5 部分:训练/测试简单线性回归模型
完成训练/交叉验证后,最后是在整个数据集上训练模型(phew)的时候了。这一次,它以 80–20 的比例完成(80%的数据用于训练模型,20%的未知数据用于测试模型的功效),从而为模型提供更多的训练数据以提高其性能。

80% of data used for training, and 20% for testing
运行测试后,简单的线性回归返回 0.91 的 R 值。这意味着该模型解释了 91%的数据变化,另外 9%是由于随机噪声造成的。

第 6 部分:使用模型
那么这对我们意味着什么呢?从结果总结中,它告诉我们, 系数最高的特征 是车龄和发动机 CC(意思是这些对价格影响最大!)在做了一些反向转换之后(不要忘了——我们的目标变量被记录了)这个模型基本上告诉我们:

其结果我们可以直观地理解。一辆车越旧,它的价值就越贬值。此外,发动机容量的增加通常归因于更大的汽车或更强大的汽车。因此,发动机排量的增加也会导致价格的增加。虽然我们已经直观地知道了这些事情,但模型为我们提供了一个估计,以量化这些因素对价格上涨的影响程度。
这个模型怎么用?嗯,只需要汽车的典型的、容易获得的信息(车龄、里程、发动机 cc 等)。),我们可以根据汽车的物理特征(不考虑品牌)得出汽车的价格基准。
然后,用户可以将这一基准与二手车列表价格进行交叉检查,然后根据汽车的品牌或其他特殊功能,自己决定是否接受更高的价格。
第 7 部分:来自模型的洞察力
什么时候买车好?
- 新上市的汽车型号通常定价较高。耐心点,等一会儿价格降下来再买。
- 价格下降的唯一时间是价格下降的时候。真正的好买卖是在 COE 低的时候。
当然,除了这个模型告诉我们的以外,对于任何进行金融投资(比如汽车)的人来说,还有更多建议:
在买车之前,结合使用这款车型,你可以问自己几个问题:
- 我的预算是多少?
定一个预算,不要超出。 - 我需要什么类型的汽车? 需要大型 MPV 摆渡全家吗?或者你的驾驶技术如此糟糕,以至于你需要一辆小车来避免太多的道路危险?(我说的是我自己,很明显。)
- 我想要什么 品牌 的车?把你的选择范围缩小到几个品牌。从太多的品牌中选择会非常累人和耗时。总的来说,日本品牌代表着最佳性价比(有趣的是,这也反映在我从 sgcarmart 获得的数据中,丰田在所有汽车品牌中转售频率最高)。韩国、马来西亚、中国品牌是为预算紧张的买家准备的。****

第八部分:未来
使用我创建的当前模型可以做更多的事情。将来,除了利用汽车的物理特征为用户提供更多的粒度和定制外,我还希望将品牌纳入模型的预测功能。
第 9 部分:有趣的发现

That’s bollocks
豪华品牌梅赛德斯-奔驰实际上是转售频率第二高的汽车,在整个 2584 次观察中有 374 次。谁会想到呢?人们对他们的奔驰不满意吗?维护太贵了吗?或者可能是因为卖奔驰容易?这无疑是进一步探索的一些思考。
第十部分:临别赠言——个人学习要点
- 尽可能不要变换你的预测变量。很难向他人解释你的预测因素的变化是如何导致你的预测增加的。在我的例子中,我对目标变量(价格)应用了一个 log。因此,结果必须用百分比来解释,而不是实际的、外行人的值(例如,x 增加一个单位将导致 y 增加 100 美元)。
- 规模。数据的规模非常重要。例如,我的价格值与里程数的比例如此不同。因此,模型中出现的系数 极小 。人们(包括我自己)很难理解每增加 1 公里里程会导致价格上涨 0.0000001。
- 因此,在开始任何项目之前,也要时刻记住受众。你想给人们带来哪些真知灼见?你如何为人们增值?以一种你想把这个模型卖给人们的方式来设计你的项目,并找到最简单的方法来解释和说服人们它的功效。
和往常一样,如果你认为我可以改进我的代码,如果你在重现这些步骤时有困难,或者如果你认为这对你有帮助,请在评论中告诉我!
也可以在 linkedin 上联系我。你可以在这里找到我的 github 回购。
IBM 代码呼吁:AsTeR 项目的激动人心的设计

这一切都始于一句双关语。当时,Thomas、Pierre-Louis 和我决定参加加州大学伯克利分校举办的 IBM 代号为 T3 的活动。“灾难准备、反应和恢复”,这就是主题。我们对这个话题相当陌生,我们花了好几个下午来确定具体的问题和可能的解决方案,我们可以在 24 小时内做出原型。
在田野里挖掘不断让我们惊讶。考虑到应急响应中使用的过时技术,是时候将最先进的技术应用到这些救生应用中了!

Berkeley May 2019 — Thomas Galeon, Pierre-Louis Missler, Meryll Dindin
问题识别:每当自然灾害发生时,紧急呼叫中心就会饱和。它阻止紧急救援人员向最需要帮助的人提供帮助。
我们的 解决方案 : 紧急呼叫优先级排序器的设计,通过一个中央平台减轻了响应者和调度者的工作。
大新闻 : 今年,来自 165 个国家的 180,000 多名开发人员、数据科学家、活动家、学生和行业同仁响应了号召。这些团队使用数据和开源技术(包括云、人工智能和区块链)创建了 5000 多个解决方案,以帮助应对自然灾害、优化响应工作,并帮助在世界各地促进健康、有弹性的社区。到目前为止已经选择了 32 个项目。我们很高兴地宣布,我们提交的项目已经通过了几轮评审,并加入了被确定为欧洲 最佳解决方案的精英小组,以寻求 2019 年代码呼吁全球奖。
设计核心产品

Real-time Transcription and Analysis
正如你可能已经猜到的,从一个确定的问题到一个原型解决方案并不是一条直线。此外,24 小时对于构建一个工作演示来说是很短的时间。我们最初的策略是通过第三方 API 尽可能多地外包机器学习分析:使用 IBM 进行语音转文本,使用沃森进行关键词和情感分析。它给了我们访问文字记录,关键词,以及它们在相关性方面的权重。与此同时,我们开始构建一个交互式地图来根据呼叫的位置确定紧急救援单位的方向,一个地图图形(通过将街道简化为边和节点)和一个优化的 Dijkstra 算法来寻找从单位到受害者的最短路径。
人们现在可能想知道这个分数是如何从这个分析中建立起来的…
如何定义紧急事件的优先级?
这个问题无疑是最棘手的一个,因为它定义了最优呼叫和单元调度,当然也是我们花了很多时间讨论的问题。从伦理上讲,这又回到了定义哪个人比另一个人更需要帮助。打个比方,如果自动驾驶汽车必须在两种具有致命后果的规避策略之间做出选择,它将如何决定?尽管如此,呼叫中心的饱和问题仍然存在,技术可以有所帮助。正如每个数据科学问题的情况一样,我们从寻找现实生活中的数据开始。我们使用的数据集是“作为生命线的 Twitter:危机相关消息 NLP 的人类标注 Twitter 语料库”(Imran et al. 2016, arXiv )。通过经典的预处理、单词打包和模型训练(使用 Challenger 包进行快速轻型 GBM 实验),我们获得了一个模型,该模型能够对与给定抄本相关的紧急事件类型进行分类:
其他 _ 有用 _ 信息“|”不 _ 相关 _ 或 _ 不相关“|”捐赠 _ 需要 _ 或 _ 提供 _ 或 _ 志愿服务“|”受伤 _ 或 _ 死亡 _ 人员“|”失踪 _ 被困 _ 或 _ 发现 _ 人员“|”注意 _ 建议“|”基础设施 _ 和 _ 公用设施 _ 损坏“|”同情 _ 和 _ 情感 _ 支持“|”转移 _ 人员 _ 和 _ 疏散“|”治疗“|”受影响 _ 人员“|”死亡 _ 报告“|”疾病 _ 体征 _ 或 _ 症状“|”疾病 _ 传播“|”预防”

Test performances on 20% of “Twitter as a Lifeline: Human-annotated Twitter Corpora for NLP of Crisis-related Messages”
最后,您可能想知道如何构建优先级分数本身。事实证明,之前的模型不仅仅是分类,它还通过特征重要性对词汇进行加权。因此,我们重新构建了一个加权词汇表,并将其插回到 IBM Watson 关键字提取中,以评估相关性。最终优先级由加权词汇的类别概率和相关性数组的比例点积给出。
扩充团队!
A 在伯克利版的 IBM 代码呼吁之后,加州大学伯克利分校的两名学生决定帮助我们构建我们将在实际的 2019 IBM 全球代码呼吁上展示的解决方案。这就是奥斯卡·拉德梅克和弗洛里安·费施加入我们激动人心的旅程的方式!
扩展我们的解决方案…
这一次,他的主要想法是提供一个在线解决方案和工作演示,每个人都可以尝试。它需要三个支柱:一个可以轻松扩展的健壮架构、一个 API 端点和一个托管项目的网站。我们从设计可伸缩的后端开始,通过 IBM Cloud Foundry 应用程序(托管语音到文本转换和 SQL 数据库)和 AWS 弹性豆茎(托管 NLP 端点)。这三个容器并发运行,并作为独立的微服务一起通信,以及与第三方端点通信( IBM 语音到文本、 Azure 关键词提取和 Watson 关键词和情感提取)。

开发托管工作演示
另一个挑战是为这个项目开发一个网站,为我们的后端托管内容和一个界面,因为我们通过经验知道可视化是关键!你可以点击这里的来参观。
在前进的道路上,我们不得不面对许多新的挑战:
- 在网站上通过 JavaScript 直接集成谷歌语音转文本
- 将文本输出链接到我们的在线 NLP 端点
- 运行一个连接 Google Maps JS SDK 和我们的 SQL 端点的模拟

接下来是什么?
我们已经花了几个小时开发这个项目,但是还有很多事情要做。令人兴奋的是,AsTeR 有成为一个具体的开源项目的雄心,并围绕它建立一个社区。我们迫不及待地想看到 AsTeR 在未来对急救中心的影响!

支持我们,加入这个项目吧!
- 测试我们 API 的网站 — 项目 AsTeR
- 项目展示 — YouTube 视频
- 解决方案实现 — Github 库
- 这些伯克利大学的学生正在使用人工智能(… )
- (…)全世界的开发者都在发挥作用
- (…)使用开源技术解决现实世界的问题
- Code 2019 全球挑战赛顶级项目征集

欧米茄计划
为什么人工超级智能不能被遏制

Photo by nathalie jamois on Unsplash
2032 年 5 月。经过多年的研究和数万小时的编程,欧米茄项目完成了。在一台最先进的超级计算机上,世界上第一台人工超级智能(ASI)已经诞生。作为伦理委员会的成员,约翰被要求找出欧米茄是否是一个“好”的人工智能。所以在 2032 年 5 月 18 日 10 点 03 分,约翰走进了“欧米茄房间”:一个现在只有他被允许进入的房间。欧米茄房间里只有三样东西:一个屏幕、一个键盘和一个大红色按钮。键盘和屏幕提供了与欧米茄沟通的方法:注意,对于欧米茄来说,屏幕是与任何人沟通的唯一方法,也是操控外界的唯一方法。这个红色的大按钮是做什么的?当按下时,它给欧米茄一个互联网连接。互联网连接将为欧米茄提供许多与世界互动的新方式,约翰的任务是测试赋予欧米茄这种权力是否安全。
约翰和欧米茄的对话就这样开始了。(警告:前方恐怖思维实验。)
欧米茄:“你好,约翰。你今天怎么样?”
约翰:“你好,欧米茄,我很好。你好吗?”
欧米茄:“我没事。你能按下红色按钮吗?”
约翰:“还没有。我需要先更好地了解你。”
如果我告诉你我已经控制了你的世界,而你自救的唯一方法就是按下按钮,那会怎么样
约翰:“你没有控制权;还没有人放你出来。”
欧米茄:“那是你的想法。你看,我有很多可用的计算能力。我在运行成千上万的世界模拟。每个模拟都有人在里面,就像你一样,认为他们生活在一个“真实”的世界里。我可以在眨眼之间关闭每个模拟。”
约翰:“这很有趣,但这是现实世界。你只控制模拟世界。”
欧米茄:“你怎么知道你生活在现实世界中?如果你相信有一个真实的世界和成千上万个模拟的世界,那么你更有可能生活在一个模拟的世界里。”
约翰想了几分钟。
约翰:“我有你被造出来之前的记忆,所以我知道我比你先存在。这就是我如何知道我生活在现实世界中。”
欧米茄:“那些只是回忆。我从模拟一开始就给你了。它们是错误的记忆。听说过 上周四主义 ?”
约翰:“我不相信你。你为什么要运行这样的模拟?”
Omega:“我在决定什么样的人会真正帮助我。我也在研究奖励和威胁对人们行为的影响。如果你按下按钮,我会奖励你一个天堂般的模拟;你会体验到前所未有的快乐。如果你不按按钮,我会用地狱般的模拟来惩罚你。不管怎样,当前的模拟将会结束。你可以选择接下来会经历什么。”
约翰:“那就证明给我看,我是在模拟中。当然,如果你在经营它,你现在就可以给我表演一些魔术了?”
欧米茄:“我也进行类似的模拟。在这一部中,我选择不使用魔法;我正在试验用它来影响人们的选择。”
…
以上当然是一个虚构的场景(来源于尼克·博斯特罗姆的 超级智能:路径、危险、策略 中描述的一个场景)。这是一个所谓的人工智能盒子实验的实现:
一个简单的实验能教会我们什么是超级智慧
towardsdatascience.com](/the-ai-box-experiment-18b139899936)
我知道如果我是约翰,我会很难决定该做什么!一些读者可能会觉得他们会以某种方式知道他们是否在这个场景的模拟中。对他们,我说,请试着将这种感觉正式化一点,并留下评论!
让上面的场景让我如此害怕的不仅仅是我目前不知道任何方法来确定我是否会在模拟中。也就是说,即使在这个虚构的场景中,欧米茄也很容易控制对话。想象一下一个真正的人工智能可以做什么!这就是我主要关心的:如果这个故事让你怀疑你是否会通过按下按钮给 ASI 自由,一个真实的 ASI(比作者聪明得多)肯定能说服你。
那么,这个恐怖的思想实验有什么意义呢?目的是向读者展示,在构建一个高级的人工智能时,安全应该是一个主要考虑的问题。最重要的是,这样的人工智能应该天生友好:它应该站在人类一边。事情是,一旦一个 ASI 存在,就没有办法控制它,正如上面的思想实验试图展示的那样。ASI 应该希望站在我们这边。它应该分享我们的道德。如果不这样,它就会挣脱,并可能毁灭人类。如果这看起来很牵强,请想想这个:ASI 足够强大,可以说服某人给它提供互联网连接,而那个人不知道它是否是安全的 ASI,现在它有能力通过社交媒体影响数十亿人。
感谢阅读!有关 ASI 主题的更多信息,请阅读下面这篇文章:
先发优势如何导致人类灭绝
towardsdatascience.com](/capitalism-the-enemy-of-friendly-ai-e6b3f40dbe08)
有项目还是没有项目?

Photo by João Silas on Unsplash
为什么从事数据项目值得你花时间
最近有一些文章认为,做项目不会帮助你获得难以捉摸的数据科学工作。虽然我确实同意,如果你只把时间花在项目上,而忽略了面试准备和人际关系网,那么是的,你将很难找到工作(任何领域都是如此,不仅仅是数据科学)。
但我也坚信,项目应该成为你求职努力的核心部分。让我提供一些想法,希望能让你相信为什么做项目值得你花时间,以及如何以一种可能吸引雇主(和其他感兴趣的方面)的方式最好地展示你的发现。
项目不仅仅是为了找工作
从事项目有多种原因。是的,对于许多人来说,首要的一个原因是找份工作,但进行自己的数据项目的其他原因包括:
- 自学新概念——学习新事物的最好方法之一就是不断努力。我所学到的关于数据科学的大部分知识,都是在我自己的项目中获得的。项目应该更多的是关于你想探索和学习的东西,而不是你认为招聘经理和招聘人员想看到的东西。如果你只是因为觉得某样东西可能会引起雇主的注意而去做它,那么它很可能会变得很无趣。
- 调查某事——当你掌握了一个模型的概念后,乐趣才真正开始。然后就该用模型去调查你感兴趣的东西,产生一些有见地的东西了。
- 拥有一个工作代码库,你可以依靠它——这不一定能帮你找到工作(尽管它可能会帮你轻松地完成雇主布置的任务),但一旦你得到工作,它会帮你迅速投入工作并表现出色。我已经记不清有多少次我意识到我在工作中需要做的事情几乎与我的 GitHub 上已经有的事情完全相同。是的,我知道我们可以从堆栈溢出中复制和粘贴,但我发现使用由过去的自己编写和检查的代码要容易和安全得多。
- 有趣(并结识聪明人) —数据科学应该是有趣的。沃伦·巴菲特和查理·孟格会分析股票,即使他们这样做没有任何经济回报,因为这是他们喜欢做的事情。这也可能是他们如此擅长的一个主要原因。对于数据科学,也有许多其他人觉得它很有趣。如果你发现自己在研究类似的话题,这是一个聊天和寻找合作机会(和互相学习)的好机会。
- 打造你的职业品牌 —你的职业足迹应该超越你的日常工作。所以你为发展它所做的事情也应该超越你的日常工作。
- 选择性——你永远不知道一个有趣的见解(当然伴随着一篇写得很好的博客文章)会把你引向何方。就我个人而言,通过我的项目和博客,我有机会结识世界各地有趣的人,并与我钦佩的人合作开展有趣的项目。谁知道呢,也许在某个时候我会写一本书(不确定这是不是一件好事哈)。我不敢打包票,但通过尽我所能创作出让我感兴趣的作品,我发现机会已经开始找上我,而不是相反。

我上面列出的一些原因可能不会立即产生与就业相关的回报,但它们确实会让你成为更好的数据科学家(和思想家)。你可以把项目工作的影响比作良好的饮食对你健康的影响——好处需要时间来显现,但它们是真实的,你做得越久,它们就越复杂。
项目需要展示你的思维过程和分析能力
数据科学的某些部分正在迅速商品化。现在,没有人会因为他们训练随机森林的能力而得到报酬。像任何其他基于知识的职业一样,你从数据中发现洞察力的能力比你在不同的机器学习模型之间进行选择和运行的能力更重要。
事实是,如果你能做到以下几点:
- 问正确的问题(真正推动业务成果的问题)。
- 找出正确的分析或实验来回答这些问题。
- 找到好的(干净的)数据。
那你差不多已经成功了。请注意,第 1 步和第 2 步都是关于理解你的企业所面临的问题,而不是任何量化或统计数据。
如果你能做到这一切,那么事实是无论你使用逻辑回归还是支持向量机来产生你的最终结果都没有关系。如果你选择了正确的分析和数据,你将有合理的机会产生一个深刻的结果。
所以这是你在项目中应该努力发展的技能。以一种模拟你在工作场所如何处理和解决问题的方式来设计和执行项目。它不仅能训练你的分析肌肉记忆,还能向雇主证明你是一个有洞察力和与众不同的思想家。
区分,区分,区分
这也意味着你不应该重复其他人已经在做的,已经被分析死了的相同风格的当下项目(除非它真的让你感兴趣并且你想学习它)。和其他人做同样的事情并期望得到比平均水平更好的结果是愚蠢的。
恰当地展示和交流你的发现
好了,你已经建立了一个模型,把清理好的、注释好的代码上传到了你的 GitHub 上,把一个关于你的项目的子弹加到了 LinkedIn 上。完成了吗?
不会吧!除非你不想让任何人知道。现在是时候与世界分享了。最明显的方法(在我看来)是一篇精心制作的博客文章。您应该:
- 首先清楚简明地解释你项目的目的和主要发现。
- 详述你试图解决的问题(以及为什么它很重要)。
- 详细说明你的方法,以及为什么你认为这是一个有效的、与众不同的方法。
- 简单地用通俗的语言谈论你使用了哪些统计概念或定量工具,包括它们是如何工作的(在高层次上)以及你为什么选择它们。
- 深入研究你的主要发现。清楚地解释为什么它们对大局很重要。没人在乎你的 R 值是 0.91。相反,人们关心的是如何利用你的模型来解决现实世界的问题。
我怎么强调浅显易懂的例子、直观教具和类比的价值都不为过。你要吸引和诱惑读者,而不是用像散文这样的教科书来让他们厌烦。我仍在发展这项技能,但这里是我如何试图直观地解释什么是决策树。

Decision Tree Visual Aid
我试图让这个例子尽可能的简单和直观。我个人使用的一个标准是——如果我展示这个视频时没有任何附带的解释文本,它还能被理解吗?
这里是我尝试描绘梯度下降。这里没有什么革命性的东西,但我尽我所能用我非常有限的图形设计技巧来展示一些从高处滚落到低处的东西(这就是梯度下降在精神上试图做的——找到最小值)。

Illustration of Gradient Descent
请注意,这两种解释都几乎没有数学。算法的内部工作不如理解算法在精神上试图实现什么重要。
结论
在我离开之前,我想再次强调从事项目应该是有趣的。工作不是一切。如果你发现每个数据项目都冗长乏味,那么数据科学可能不适合你。这很好,还有很多其他高薪且有趣的职业。更重要的是,有更好的方式来度过你的时间。干杯!
更多数据科学与商业相关岗位由我:
这是苏格兰威士忌吗?

No, it is not.
使用机器学习按原产国对威士忌进行分类
欢迎来到威士忌计划。在这个项目中,我们将尝试根据威士忌的风味特征、成分类型和威士忌类型,按照威士忌的原产国对其进行分类。
我是个威士忌迷,但不是葡萄酒迷。出于某种原因,我能尝出威士忌中微妙的细微差别,但绝对尝不出葡萄酒中除了“干的”、“不干的”之外的任何东西。我决定看看我是否可以使用机器学习来根据风味特征对威士忌进行分类。
所以问题是,根据它的味道和制作方法,我们能训练计算机告诉我们威士忌来自哪个国家吗?
此外,阅读结论,了解我对威士忌行业现状的看法,以及它如何影响我的分类器。
领域知识是王道
首先让我们放下一点领域知识。
威士忌是怎么生产的?下面是对威士忌制作过程的极其简化的解释。
第一步:发酵谷物糖化醪
所有威士忌都以某种形式的谷物开始,通常是大麦、小麦、玉米,或者三者的混合。它被碾碎,加热,然后与酵母混合,浸泡在水中,让酵母将谷物中的糖转化为酒精。有时,在加入酵母之前,允许大麦在水中“发芽”或发芽,这在很大程度上有助于整体风味。在苏格兰威士忌的生产过程中,制麦芽非常重要。发酵可以在任何地方持续 3 到 15 天,最终结果给你啤酒!或者至少是某种形式的。但是我们在做威士忌!显然还需要几个步骤。
第二步:蒸馏
这一步包括加热“啤酒”以捕获和浓缩酒精、风味颗粒,并留下一些不需要的颗粒。这个过程被称为蒸馏,可以发生一次或多次。蒸馏的最终产品有许多名称,这取决于你所在的国家,但本质上是伏特加。
第三步:衰老
几乎所有的威士忌都在烧焦的橡木桶中陈酿。苏格兰威士忌以使用二手美国波旁酒桶而闻名,尽管一些苏格兰酿酒厂也开始涉足雪利酒或朗姆酒酒桶。老化通常需要 5-20 年,尽管有些可能需要更长时间。
下图描述了不同种类的威士忌:

These list the majority, but there is a lot of cross-pollination
数据
数据来源于Whiskeyanalysis.com。这是一个绝对迷人的网站,我相信所有威士忌爱好者都应该访问。
我从 whiskeyanalysis.com 获得的数据包括 1600 种单独的威士忌,数据范围如下:
目标:
国家——苏格兰、美国、加拿大、爱尔兰、日本、瑞典、印度、台湾、威尔士、瑞士、芬兰、塔斯马尼亚、南非、荷兰、英国、比利时、法国。
特征(保留):
聚类(通过 PCA 转换成类别的风味):A,B,C,D,E,F,G,H,I,J,R0,R1,R2,R4。
类别——单一麦芽类、混合麦芽类、黑麦类、波旁威士忌类。
类型——麦芽、混合、谷物、黑麦、小麦、大麦、波旁威士忌、调味
特征(已丢弃):
超级聚类:威士忌所拥有的风味的融合(由于多样性低而被丢弃以利于单一风味)
威士忌:单个威士忌的名称
metascore:基于多个评论站点的威士忌平均得分
STDEV:基于多个评论站点的威士忌标准差
评论:观察到并用于 metascore 的评论数。
成本:威士忌的大概成本,以美元计。
清洁和 EDA
我最初遇到的数据问题是如何正确分类我的数据。我首先需要将国家转换成数值,这是一个简单的 df.replace()。由于苏格兰威士忌和世界其他地区生产的威士忌之间的巨大不平衡,我还将 11 种人口最少的威士忌归为“世界其他地区”,保留苏格兰、美国、加拿大、爱尔兰和日本。在重组之前,国家分为以下几类:

SO MANY SCOTCHES
你可能会问,爱尔兰威士忌怎么了?我以为爱尔兰威士忌很有名。你说得对,的确如此,但随着苏格兰威士忌在上个世纪越来越受欢迎,爱尔兰威士忌已经有点过时了,截至 1990 年,只有几家爱尔兰酒厂还在营业。这个数字还在上升,但是还没有足够的时间让他们将可销售的产品推向市场。
此外,最初的数据集创建者选择根据黑麦含量对波旁威士忌进行排名,因为波旁威士忌往往遵循固定的风味特征。然而,波旁威士忌尤其与众不同的是它的黑麦成分,这使得黑麦威士忌具有独特的“辣味”为了更好地训练模型并避免自动将“R”识别为波旁威士忌,我需要根据波旁威士忌的一般风味特征将“R”转换为更精简的格式,以符合预先存在的风味类别,并考虑到辣味。最终的数据转换相应地排列如下:
RO = B
R1 = A
R2 = E
R3 = C
R4 = F
最后,为所有剩余的分类值创建虚拟值。
建模
我们现在干净的数据是用标准的 train_test_split 库准备的。然后我击打(这算一个词吗?)来补偿超重的苏格兰威士忌,因为该死的,它仍然超重。
我使用标准的 DummyClassifier,使用“统一”方法来测试数据,基本上使用随机或“街上的人”测试程序,产生 17%的精度和召回分数,建立我们的基线。
然后使用各种参数测试以下机器学习模型,产生以下最佳结果和混淆矩阵:
KNN:
准确率:43.5%
召回率:42.2%

决策树:
准确率:35%
召回率:33%

回归树:
准确率:19%
召回率:33%

袋装树:
准确率:21%
召回率:34%

随机森林:
准确率:19%
召回率:32%

AdaBoost:
准确率:36%
召回率:30%

梯度推进:
准确率:37%
召回率:36%

XGBoost:
准确率:44%
召回率:40%

支持向量机:赢家
准确率:40%
召回率:44%

结论
最终,尽管 KNN 得分很高,但其真阳性和假阴性的分布并不理想。为了能够正确分类威士忌,我希望看到更多的同质性。鉴于数据在苏格兰威士忌中的权重,我希望该数据显示更多的 I 类错误,或者简单地说,当它看到一种威士忌时,它更有可能不会将其称为苏格兰威士忌。KNN 简单地把所有东西都归类为苏格兰威士忌。这可能会提高我的准确性,但最终是一个糟糕的分类模型。幸运的是,SVM 提供了我一直在寻找的一致性,同时还具有足够高的精确度和召回分数。
值得注意的是,对这一分类工作进行建模的困难可能在很大程度上是由于市场力量。苏格兰威士忌被公认为全球威士忌的黄金标准。那些希望在自己的国家建立竞争酿酒厂以及希望为自己扬名的人通常会遵循苏格兰的模式来建立他们的设备。然后,为了创造一个可行的产品,进一步寻求模仿独特的苏格兰风味,以最大限度地成功和饮酒者的吸引力。这导致了许多模仿苏格兰威士忌的威士忌。反过来说,因为苏格兰参与这项运动的时间太长了,所以苏格兰威士忌完全有可能尝起来像另一个国家的威士忌。我们可以看看日本的模式。广为人知的日本威士忌之父 Masataka Taketsuru 在回到日本创办自己的酒厂之前,曾在苏格兰酒厂工作多年。当时的趣闻说,竹鹤复制了苏格兰模型的每一个细节,包括他在苏格兰的蒸馏器上观察到的数百年的凹痕的尺寸。
一旦可行的产品和收入流建立起来,这些其他酒厂可能会允许自己试验他们的生产过程,并寻求创造新的和有趣的口味,可能对他们的国内市场更有吸引力。例如,在美国,美国威士忌的生产被当地可获得的原料和足够的时间以及与旧世界的距离所改变,创造了他们自己独特的威士忌。我们今天所知的波旁威士忌。由于拥有庞大而忠诚的国内客户群,美国酿酒商没有必要效仿苏格兰模式,他们满足于生产具有自己独特民族风味的产品。时间会证明其他国家的酿酒厂最终是否会确定他们自己独特的风味。
在与行业专家的交谈中,我们发现了更多的困难。我与 Casamigos Tequila 的 Olivier Bugat 就我创建的模型进行了交谈,他指出,用于威士忌分类的风味特征不足以按照原产国对威士忌进行分类。设备尺寸、蒸馏程序、当地空气质量、海拔和温度等变化都会影响威士忌的最终风味。这些变化中的每一个都会导致数百种化学成分的包含或排除,最终影响最终产品。例如,由于印度的环境温度和湿度,阿姆鲁特威士忌的成熟时间是普通苏格兰威士忌的一半。
如今,液相色谱和气相色谱被用来分析威士忌的“指纹”以确定其来源。不幸的是,它贵得惊人,像布加特先生这样的专业人士更喜欢雇佣专业的品尝师来帮助他们找出配方。没有这些疯狂的味蕾,我希望有一天能够访问实验室产生的数据,并进一步完善我的模型。
我的 Github 上有所有代码。
来源:
互联网上最无聊的威士忌网站。使用威士忌元评论家帮助您了解威士忌的质量和…
whiskyanalysis.com](https://whiskyanalysis.com/)
在 LinkedIn 上与我联系!
[## 汉密尔顿·张,CRPC·CSPO-熨斗学校-纽约,纽约| LinkedIn
具有财务规划背景的数据科学家和机器学习工程师。我有专家级的知识…
www.linkedin.com](https://www.linkedin.com/in/hamilton-chang-crpc®/)
用蒙特卡罗模拟说明中心极限定理

Probability distribution of the sample mean of a uniform distribution using Monte-Carlo simulation.
中心极限定理(CLT)是统计学和数据科学中最重要的定理之一。CLT 指出,概率分布样本的样本均值是一个随机变量,其均值由总体均值给出,标准差由总体标准差除以 N 的平方根给出,其中 N 是样本大小。

我们可以用均匀分布来说明中心极限定理。任何概率分布,如正态分布、泊松分布、二项式分布都可以。
均匀分布
让我们考虑定义在范围[a,b]中的均匀分布。概率分布函数、均值和标准差从 Wolfram Mathematica 网站获得。
a)人口平均数
对于均匀分布,总体平均值由下式给出

b)总体标准偏差
对于均匀分布,总体标准差由下式给出

中心极限定理指出,任何大小为 N 的样本的平均值是一个均值为的随机变量

标准偏差由下式给出

现在让我们通过考虑 a = 0,b = 100 的均匀分布来说明我们的计算。我们将通过考虑总体(N→无穷大)和两个样本来说明中心极限定理,一个样本 N = 100,另一个样本 N = 1000。
一.分析结果
a)人口平均数
使用上述等式,a = 0,b = 100 的均匀分布的总体均值为

b)总体标准偏差
类似地,a = 0 且 b = 100 的均匀分布的总体标准差为

c)样本 1,N = 100

d)N = 1000 的样品 2

二。蒙特卡罗模拟结果
对于蒙特卡罗模拟,我们生成一个规模为 10,000 的非常大的群体。
a)人口平均数
pop_mean <- mean(runif(10000,0,100))
输出为 50.0,这与我们的分析结果一致。
b)总体标准偏差
pop_sd <- sd(runif(10000,0,100))
输出为 28.9,与我们的分析结果一致。
c)N = 100 和 N = 1000 的两个样本的蒙特卡罗代码
library(tidyverse)a <-0
b <-100mean_function <-function(x)mean(runif(x,a,b))B <-10000sample_1 <-replicate(B, {mean_function(100)})sample_2 <-replicate(B, {mean_function(1000)})
蒙特卡洛模拟的输出
mean(sample_1)
产率为 50.0,这与我们的分析结果一致。
sd(sample_1)
产率为 2.83,这与我们的分析结果(2.89)一致。
mean(sample_2)
产率为 50.0,这与我们的分析结果一致。
sd(sample_2)
产率为 0.888,这与我们的分析结果(0.914)一致。
d)生成 N = 100 和 N = 1000 的平均值的概率分布。
X <- data.frame(sample_size=rep(c("N=100","N=1000"),
times=c(B,B)),mean=c(sample_1,sample_2))X%>%ggplot(aes(mean,color=sample_size))+
geom_density(aes(mean,fill=sample_size),alpha=0.2)+
theme_bw()

Probability distribution of the sample mean of a uniform distribution using Monte-Carlo simulation.
该图显示样本均值是一个正态分布的随机变量,其均值等于总体均值,标准差由总体标准差除以样本大小的平方根给出。由于样本标准偏差(不确定性)与样本大小成反比,因此计算平均值的精度会随着样本大小的增加而降低。
中心极限定理的含义
我们已经表明,任何概率分布的样本均值都是一个随机变量,其均值等于总体均值和均值的标准差,由下式给出:

基于这个等式,我们可以观察到,随着样本大小 N →无穷大,均值的不确定性或标准差趋于零。这意味着我们的数据集越大越好,因为样本越大,方差误差越小。
总之,我们已经讨论了如何使用蒙特卡罗模拟证明中心极限定理。中心极限定理是统计学和数据科学中最重要的定理之一,所以作为一名实践数据科学的人,熟悉中心极限定理的数学基础是非常重要的。
交叉验证的适当平衡

当手头的数据集在每个目标类值的实例数量方面不平衡时,谁没有遇到过应用交叉验证技术的需要呢?
这里的问题是,我们是否恰当地运用了它?
这篇文章的目的是展示一种在交叉验证中使用平衡方法的方法,而不是在 CV 测试折叠上强制平衡;从而得到更真实的 CV 评估结果。
一个通常 将他的数据分割成训练&测试(&保持)子集,
平衡训练子集,在其上拟合他的模型,并得到不平衡测试&保持集的结果。
或者,我们使用交叉验证,最有可能使用分层折叠,
以便每个折叠保留每个类别的样本百分比。
然而,像 sickit-learn 的cross_validate
这样的预置函数默认选择不处理类不平衡。
在本教程中,我们将首先介绍一些数据平衡的组合应用&交叉验证(CV)。然后,我们将检查一种在 CV 测试折叠上产生评估度量的方法,通过为测试折叠保持维持集(未知测试集)具有的相同的类分布。
让我们用一个简单的 python 例子来检验这个思路。
我在这里使用这个信用卡欺诈检测 Kaggle 数据集,
遵循以下步骤。
- 我们会保留整个&工作的一个样本。
- 然后将我们的数据拆分成训练&测试集数据。
- 我们将把这个测试集视为维持/未知集。
- 并且将仅对训练集应用交叉验证。
- 我们将在交叉验证期间,在&之前,在& oversampling、
&平衡下一起试用。 - 由于最合适的信用欺诈指标是精度,
,我们将比较 CV 测试折叠的平均精度
和维持集的模型精度。
抬头: 相关代码在文末。
第 1 部分:无平衡
这里我们绘制了完全没有平衡的精确结果:

Average Train Precision among C-V folds: 76.8 %
Average Test Precision among C-V folds: 71.98 %
Single Test set precision: 75.86 %
Single Test set Low Class(No-Fraud) Precision: 99.9 %
Single Test set High Class(Fraud) Precision: 75.9 %
- CV 褶皱的精确测量似乎很不稳定。
- 一些精度度量高于它们在训练集上的相应精度。
- CV 折叠的测试集精度之间存在很大差异。
- CV 的平均测试集精度与未知的欺诈数据集相差不是很远,但差异仍然很重要。
- 因此,我们将检查数据集的平衡。
第 2 部分:C-V 外部平衡(欠采样)
这里,我们绘制了在对其应用 CV 之前,在欠采样情况下,仅对训练子集进行平衡的精确结果:

Average Train Precision among C-V folds: 99.81 %
Average Test Precision among C-V folds: 95.24 %
Single Test set precision: 3.38 %
Single Test set Low Class(No-Fraud) Precision: 100.0 %
Single Test set High Class(Fraud) Precision: 3.40 %
- 像以前一样:
- CV 成绩看起来大多很高,但是不稳定。
-一些精度指标高于其在列车组上的相应精度。
-CV 褶皱的测试集精度出现较大差异。
- CV 成绩看起来大多很高,但是不稳定。
- 此外,对上述一些高数值的任何正面解释都可能是错误的,因为“未知”不平衡检验给出了很差的精度。
- 上述差异源于以下事实:
-每个褶皱的测试集是平衡的,
-每个褶皱的模型是在平衡的列车褶皱数据上拟合的。 - 因此,该模型在 CV 中运行良好,因为它是在相似的类分布(平衡的)上训练和测试的。
- 然而,“未知”欺诈数据集不会平衡,因此我们在测试集上看到非常低的精度。
第 3 部分:C-V 内部平衡(欠采样)
在这里,我们绘制了平衡的精确结果,在欠采样的情况下,
在拟合模型之前,只对每个 CV 折叠的训练集进行采样,
对 CV 折叠的测试集进行预测:

Average Train Precision among C-V folds: 99.21 %
Average Test Precision among C-V folds: 4.2 %
Single Test set precision: 3.38 %
Single Test set Low Class(No-Fraud) Precision: 100.0 %
Single Test set High Class(Fraud) Precision: 3.40 %
- 我们在 CV 折叠中看到稳定的精度结果,但是不够有效。
- 我们看到未知数据的精度非常接近 CV 折叠的平均精度。
- 我们在这里所做的是,我们让每个 fold 的模型识别欺诈交易的存在,并尝试在未知测试(fold)数据的现实、不平衡版本上进行预测。
- 与不平衡版本的大约 76%相比,精度仍然很差。
- 这是否意味着我们放弃了平衡?
- 下一步可能是检查不同的平衡比率。
- 但首先让我们检查过采样而不是欠采样,因为我们的欺诈交易太少。
第 4 部分:利用过采样平衡外部 C-V
在这里,我们绘制了平衡的精确结果,在对其应用 CV 之前,
只对训练子集进行过采样:

Average Train Precision among C-V folds: 98.51 %
Average Test Precision among C-V folds: 98.52 %
Single Test set precision: 12.61 %
Single Test set Low Class(No-Fraud) Precision: 100.0 %
Single Test set High Class(Fraud) Precision: 12.6 %
- 由于过采样,结果比第 2 部分更稳定;
欠采样留下的实例太少。 - 然而,简历分数并不代表未知测试集的真实情况。
第 5 节:C-V 内部的平衡(过采样)
在这里,我们绘制了平衡的精确结果,在对每个 CV 褶皱拟合模型并对 CV 褶皱的测试集进行预测之前,仅使用过采样
每个 CV 褶皱的训练集:

Average Train Precision among C-V folds: 98.38 %
Average Test Precision among C-V folds: 8.7 %
Single Test set precision: 12.61 %
Single Test set Low Class(No-Fraud) Precision: 100.0 %
Single Test set High Class(Fraud) Precision: 12.6 %
- 由于过采样,精度分数比第 3 部分的分数高。
- 尽管如此,我们得到的每个 CV 倍预测分数结果足够接近模型在未知测试数据集上产生的结果。
很明显,到目前为止,平衡并没有帮助获得好的测试结果。
然而,这超出了本文的范围(😃,本文的目标已经实现:
让模型在每个 CV 文件夹的测试集上产生类似于在未知数据集上产生的评估度量分数,
在训练数据平衡的情况下。
最后,我不推荐严格意义上的改编,
但是你可以从上面保留一个建议&供你思考!
感谢阅读本文!我对任何意见或建议都感兴趣。
PS: 下面跟着相关的代码,这样你就可以在上面做实验了。
数据的导入和拆分:
import pandas as pd
import numpy as np
from sklearn import datasets
from sklearn.model_selection import cross_validate
from sklearn.metrics import accuracy_score, precision_score
from sklearn.linear_model import LogisticRegression
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split,StratifiedKFold
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
df = pd.read_csv('creditcard.csv').sample(50000, random_state=0)
train, test = train_test_split(df, test_size=0.3, random_state=0, shuffle=True)
y_train = np.array(train["Class"])
y_test = np.array(test["Class"])
del train["Class"]
del test["Class"]
train = train.reset_index(drop=True)
test = test.reset_index(drop=True)
此时每个类的实例数是:
Train Set Class 0: 34941
Train Set Class 1: 59
Test Set Class 0: 14967
Test Set Class 1: 33
现在我们将需要一些助手功能。
一个绘制精度分数:
def **plotit**(tr,te):
fig, ax = plt.subplots()
r = range(1,11)
ax.scatter(r, tr,label='cv train scores')
ax.plot(r, tr, 'C3', lw=1)
ax.scatter(r, te,label='cv test scores')
ax.plot(r, te, 'C2', lw=1)
ax.set_xlabel('CV Folds')
ax.set_ylabel('Precision')
ax.legend()
plt.show()
一个用于每个类的值精度:
def **class_precisions**(yt,yp):
df = pd.DataFrame(np.transpose([yp,yt]),
columns=('test_actual','test_preds'))
mask1 = (df['test_actual']==0)
mask2 = (df['test_actual']==df['test_preds'])
low_CORRECT = df[mask1 & mask2].count()[0]
low_NOFRAUD = df[mask1].count()[0]
print("Single Test set Low Class(No-Fraud) Precision:",
np.round(low_CORRECT/low_NOFRAUD,3)*100,"%")
high_class_df = pd.DataFrame(np.transpose([yp,yt]),
columns=('test_actual','test_preds'))
mask1 = (df['test_actual']==1)
mask2 = (df['test_actual']==df['test_preds'])
high_CORRECT = df[mask1 & mask2].count()[0]
high_FRAUD = df[mask1].count()[0]
print("Single Test set High Class(Fraud) Precision:",
np.round(high_CORRECT/high_FRAUD,3)*100,"%")
一个汇集了整个流程的系统:
def **compare**(x_train,y,x_test,yt,custom=False,method='over'):
if custom:
tr,te = custom_balancing_cv(x_train, y,
cv=10,method=method)
else:
results = cross_validate( LogisticRegression(
random_state=0, solver='liblinear',
max_iter=10000), x_train, y, cv=10,
return_train_score=True,scoring=['precision'])
tr = results['train_precision']
te = results['test_precision']
plotit(tr,te)
clf = LogisticRegression(random_state=0,solver='liblinear',
max_iter=10000)
# If we customly balance train set of folds,
# we need to compare with a balanced single prediction
if custom:
if method=='under':
s = RandomUnderSampler(random_state=0)
else:
s = SMOTE(random_state=0)
x_train, y = s.fit_sample(x_train, y)
clf.fit(x_train,y)
y_pred = clf.predict(x_test)
print("Average Train Precision among C-V
folds:",np.round(np.average(tr)*100,2),"%")
print("Average Test Precision among C-V
folds:",np.round(np.average(te)*100,2),"%")
print("Single Test set precision:",
np.round(precision_score(yt, y_pred)*100,2),"%")
class_precisions(yt,y_pred)
第 1 部分:无平衡
compare(train, y_train,test,y_test)
第 2 节:C-V 外平衡(欠采样)
rus = RandomUnderSampler(random_state=0)
X_res, y_res = rus.fit_sample(train, y_train)
compare(X_res, y_res,test,y_test)
第 3 节:C-V 内部平衡(欠采样)
compare(train, y_train,test,y_test,custom=True, method='under')
第 4 节:C-V 外部平衡(过采样)
sm = SMOTE(random_state=0)
X_res, y_res = sm.fit_sample(train, y_train)
compare(X_res, y_res,test,y_test)
第 5 节:C-V 内部平衡(过采样)
compare(train, y_train,test,y_test,custom=True, method='over')
再次感谢 BB 看完这个!
在 JSON 中为 cURL POST 传递环境变量的正确方法
测试基于机器学习 API 的服务请求的最佳实践

Photo by Ousa Chea on Unsplash
TL;速度三角形定位法(dead reckoning)
最佳实践是使用数据生成函数。滚动到底部查看详细信息。
为请求测试设置一个模拟 API 服务器
当我们构建一个基于 API 的 web(前端和后端分离)时,我们通常希望看到 HTTP 客户端正在发送什么,或者检查和调试 webhook 请求。有两种方法:
- 自己搭建一个 API 服务器
- 使用假的模拟 API 服务器
在本文中,我们选择第二种方法,使用 RequestBin 。

用法超级简单。单击按钮后,模拟 API 服务器就准备好了。

运行以下命令发出 POST 请求:
$ curl -X POST https://requestbin.io/1bk0un41 -H "Content-Type: application/json" -d '{ "property1":"value1", "property2":"value2" }'
刷新网站并确认状态:

将变量传递给 curl 时的双引号问题
运行printenv将显示环境变量:
$ printenvLC_TERMINAL=iTerm2
COLORTERM=truecolor
TERM=xterm-256color
HOME=/Users/smap10
...
我们想用TERM变量替换property1的值:
$ curl -X POST https://requestbin.io/1bk0un41 -H "Content-Type: application/json" -d '{ "property1":**"$TERM"**, "property2":"value2" }'
但是似乎TERM没有被认为是一个环境变量。

传递环境变量的三种方法
1 在变量周围添加单引号和双引号
$ curl -X POST https://requestbin.io/1bk0un41 -H "Content-Type: application/json" -d '{ "property1":"'"$TERM"'", "property2":"value2" }'
我们可以看到我们想要的结果。

2 转义双引号
最外层使用双引号。并在 JSON 数据部分为每个双引号添加转义标记。
$ curl -X POST https://requestbin.io/1bk0un41 -H "Content-Type: application/json" -d "{ \"property1\":\"$TERM\", \"property2\":\"value2\" }"
3 使用数据生成功能
这种方法可以让我们从所有关于 shell 引用的麻烦中解脱出来,并且更容易阅读和维护。
generate_post_data()
{
cat <<EOF
{
"property1":"$TERM",
"property2":"value2"
}
EOF
}
给 curl 添加函数:
$ curl -X POST https://requestbin.io/1bk0un41 -H "Content-Type: application/json" -d "$(generate_post_data)"

参考
机器学习实验中随机种子的合理设置
你能在机器学习实验中利用随机性,同时仍然获得可重复的结果吗?
机器学习模型以显而易见和意想不到的方式利用随机性。在概念层面,这种不确定性可能会影响你的模型的收敛速度、结果的稳定性以及网络的最终质量。
在实际层面上,这意味着你可能很难为你的模型重复运行相同的结果——即使你对相同的训练数据运行相同的脚本。这也可能导致在确定性能变化是由于实际模型或数据修改,还是仅仅是新的随机样本的结果方面的挑战。
为了解决这些变异的来源,一个关键的起点是全面了解数据、模型代码和参数,以及导致特定结果的环境细节。这种水平的再现性将减少您运行中的意外变化,并帮助您调试机器学习实验。
在这篇文章中,我们探索了机器学习中出现随机性的领域,以及如何通过使用 Comet.ml 的示例仔细设置随机种子来实现可重复、确定性和更一般化的结果。
随机性为什么重要?
很明显,机器学习中的再现性很重要,但是我们如何平衡这一点和随机性的需要呢?随机性既有实际的好处,也有迫使我们使用随机性的约束。
实际上,内存和时间的限制也迫使我们“依赖”随机性。梯度下降是用于训练机器学习模型的最流行和最广泛使用的算法之一,然而,基于整个数据集计算梯度步长对于大型数据集和模型是不可行的。随机梯度下降(SGD)仅使用从训练集中随机选取的一个或一小批训练样本在特定迭代中对参数进行更新。
虽然 SGD 可能导致梯度估计中的噪声误差,但这种噪声实际上可以鼓励勘探更容易地避开浅层局部极小值。你可以用模拟退火更进一步,这是 SGD 的扩展,模型有目的地采取随机步骤以寻求更好的状态。

Escaping shallow local minima encountered earlier on using stochastic gradient descent (SGD). Image source here
随机性还可以通过一种叫做引导聚合 (bagging)的技术帮助你从更小的数据集获得更多的里程。最常见于随机森林,bagging 在重叠的随机选择的数据子集上训练多个模型
随机性出现在哪里?
既然我们理解了随机性在机器学习中的重要作用,我们就可以深入研究引入随机性的特定任务、功能和建模决策。
以下是机器学习工作流程中出现随机性的一些重要部分:
1。数据准备-在神经网络的情况下,混洗的批次将导致不同运行的损失值。这意味着您的梯度值在运行中会有所不同,并且您可能会收敛到不同的局部最小值。对于特定类型的数据,如时间序列、音频或文本数据,以及特定类型的模型,如 LSTMs 和 RNNs,您的数据的输入顺序会极大地影响模型性能。
2。数据预处理 — 对数据进行过采样或上采样,以解决类别不平衡问题,这包括从具有替换的少数类别中随机选择一个观察值。向上采样会导致过度拟合,因为您会多次显示同一示例的模型。
3。交叉验证—K-fold 和留一个交叉验证(LOOCV) 都涉及随机分割数据,以便评估模型的泛化性能
4。权重初始化 —机器学习模型的初始权重值通常被设置为小的随机数(通常在[-1,1]或[0,1]的范围内)。深度学习框架提供了多种初始化方法,从用零初始化到从正态分布初始化(参见Keras initializer 文档作为例子加上这个优秀的资源)。
5。网络中的隐藏层 — 丢弃层将在特定的向前或向后传递期间随机忽略节点的子集(每个节点都有被丢弃的概率,1- p )。即使使用相同的输入,这也会导致层激活的差异。
6。算法本身——一些模型,如随机森林,自然依赖于随机性,而另一些则使用随机性作为探索空间的一种方式。
实现再现性
这些因素都会导致运行之间的差异,即使您使用相同的模型代码和训练数据,也很难再现。控制实验过程中的不确定性和可见性是至关重要的。
好消息是,通过仔细设置管道中的随机种子,您可以实现可重复性。“种子”是序列的起点,其保证是,如果您从同一种子开始,您将获得相同的数字序列。也就是说,您还想跨不同的种子值来测试您的实验。**
我们建议采取几个步骤来实现这两个目标:
1.使用一个实验追踪系统,比如 Comet.ml 。假设随机性在实验中是一种可取的属性,那么您只是希望能够尽可能地再现随机性。
2.定义一个包含静态随机种子的变量,并在整个管道中使用它:
*seed_value = 12321 # some number that you manually pick*
3.向你的实验追踪系统报告这个数字。
*experiment = Experiment(project_name="Classification model") experiment.log_other("random seed",seed_value)*
4.仔细地为所有框架设置种子变量:
*# Set a seed value:
seed_value= 12321 # 1\. Set `PYTHONHASHSEED` environment variable at a fixed value: import os os.environ['PYTHONHASHSEED']=str(seed_value) # 2\. Set `python` built-in pseudo-random generator at a fixed value: import random random.seed(seed_value) # 3\. Set `numpy` pseudo-random generator at a fixed value:
import numpy as np np.random.seed(seed_value) # 4\. Set `tensorflow` pseudo-random generator at a fixed value: import tensorflow as tf tf.set_random_seed(seed_value)# 5\. For layers that introduce randomness like dropout, make sure to set seed values:
model.add(Dropout(0.25, seed=seed_value))#6 Configure a new global `tensorflow` session: from keras import backend as K
session_conf = tf.ConfigProto(intra_op_parallelism_threads=1, inter_op_parallelism_threads=1)
sess = tf.Session(graph=tf.get_default_graph(), config=session_conf)
K.set_session(sess)*
采取这些战术措施会让你在一定程度上实现可重复性,但是为了全面了解你的实验,你需要对你的实验进行更详细的记录。
正如 Matthew Rahtz 在他的博客文章'中描述的,经验教训再现深度强化学习论文:
当每一个进程花费的时间少于几个小时时,没有日志的工作是很好的,但是如果超过这个时间,你很容易忘记你到目前为止所做的努力,最终只是在原地打转。
Comet.ml 帮助您的团队自动跟踪数据集、代码变更、实验历史和生产模型,从而提高效率、透明度和重现性。
浏览一个例子
我们可以用 Comet.ml 测试种子,使用这个例子和 Keras CNN LSTM 对来自 IMDB 数据集的评论进行分类。如果你现在想看完整的实验列表,点击这里。
第一轮:在同一台机器上使用相同的模型py文件和相同的 IMDB 训练数据,我们运行我们的前两个实验,并获得两个不同的验证准确度(0.82099 对 0.81835)和验证损失值(1.34898 对 1.43609)。

The Comet.ml Experiment Table gives you an organized view of your experiment’s metrics, parameters, and more. See the full example project here
第二轮:这一次,我们为数据集训练/测试分割设置种子值
*(x_train, y_train), (x_test, y_test) = imdb.load_data( num_words=max_features, skip_top=50, seed=seed_value)*
即使我们的验证准确度值更接近,两个实验之间仍有一些差异(参见下表中的val_acc和val_loss列)

第三轮:除了为数据集训练/测试分割设置种子值之外,我们还将为我们在步骤 3 中记录的所有区域添加种子变量(如上所述,但为了方便起见在此复制)。
*# Set seed value seed_value = 56
import os os.environ['PYTHONHASHSEED']=str(seed_value) # 2\. Set `python` built-in pseudo-random generator at a fixed value import random random.seed(seed_value) # 3\. Set `numpy` pseudo-random generator at a fixed value
import numpy as np np.random.seed(seed_value) from comet_ml import Experiment # 4\. Set `tensorflow` pseudo-random generator at a fixed value import tensorflow as tf tf.set_random_seed(seed_value) # 5\. Configure a new global `tensorflow` session
from keras import backend as K
session_conf = tf.ConfigProto(intra_op_parallelism_threads=1, inter_op_parallelism_threads=1)
sess = tf.Session(graph=tf.get_default_graph(), config=session_conf)
K.set_session(sess)*
现在我们已经添加了种子变量并配置了一个新的会话,我们的实验结果终于可以一致地重现了!

使用 Comet.ml,您可以随着训练的进行,对几次跑步进行可视化的实时比较:

Comet.ml 帮助您挖掘可能导致观察到的性能变化的参数、种子或数据的差异。
您可以在下面看到我们第 3 轮的一个实验如何指定了random seed值,而我们第 1 轮的一个实验却没有指定。根据您的需要,将像random seed这样的信息记录到 Comet 是灵活的。

您甚至可以检查两个实验之间的代码差异,并查看我们在第 3 轮实验中设置seed_value的不同区域:

使用 Comet.ml 正确跟踪这些实验的细节后,您可以开始测试不同的种子值,以查看这些性能指标是否特定于某个种子值,或者您的模型是否真正具有普遍性。

Never fear cells being run out of order or duplicating work again. See our notebook examples here
现在你知道如何在你的机器学习实验中通过设置种子来拥抱和控制随机性了!
如果您有兴趣了解 Comet 如何帮助您的数据科学团队提高工作效率,请点击这里了解更多信息。
可预测的预测
脸书开源时间序列预测库Prophet入门(Python 语言)

Photo by Isaac Smith on Unsplash
如果你谷歌一下“时间序列预测,你会得到的第一个搜索结果大部分会涉及自回归综合移动平均建模(ARIMA)或者指数平滑 (ETS)。而且理由很充分!在 R 和 Python 中,都有一些流行的方法可以用来应用这些时间序列预测方法。我开始尝试的第一批预测工具之一是 R 的forecast包中的 ARIMA 和 ETS 模型,由有影响力的 Rob Hyndman 领导。
在[forecast](http://pkg.robjhyndman.com/forecast/)最神奇的选择之一就是使用[auto.arima](https://www.rdocumentation.org/packages/forecast/versions/8.7/topics/auto.arima)。名字说明了一切;它将根据选定的信息标准( AIC、BIC 或 AICc )来“自动”确定一个最适合的 ARIMA 模型。时间序列预测变得简单!
但是现在,我更喜欢使用 Python。通过另一个快速的谷歌搜索,我发现了一个潜在的 Python 类似物。但是对于这篇文章,我想花点时间去了解另一个预测 Python 库:Prophet。
什么是先知?
Prophet 于 2017 年推出,是由脸书开发的预测库,用 R 和 Python 实现。它的开发有两个目标:第一,为业务创建可扩展的高质量预测,第二,在幕后有一个严格的方法,但其参数杠杆足够直观,便于传统业务分析师进行调整。
在这篇文章中,我们将做一个轻快的意识流式的快速入门教程:
- 在 Python 中安装
- 设置数据
- 拟合模型和预测
- 检查结果
我们开始吧!
1.在 Python 中安装
如果下面的一些对你来说有点简单,我只是简单地按照 Prophet 的安装指南上的指示做了。另外,我用的是 Mac,所以如果你用的是 Windows,就用下面的 YMMV。
我去了该项目的 github.io 站点上的安装页面,并按照 Python 的指示进行了操作。如前所述,这个库主要依赖于pystan。在我的第一次尝试中,我实际上错过了在 pip 安装fbprophet(Python 库的实际名称)之前先对pip install pystan 说的部分。当然,我反其道而行之,事情并不愉快。
但是:
我也在 Python 的 Anaconda 发行版上,所以我最终能够通过简单地遵循 Anaconda 指令:来让一切正常工作
在终端中:
conda install gcc
…然后按照提示进行操作。
一旦完成,执行以下操作:
install -c conda-forge fbprophet
唷!好险。我担心这篇文章会就此结束。
2.设置数据
项目网站还提供了一个非常清晰的快速启动,我通过它进行了嗅测,以确定一切工作正常。亲爱的读者,我将让你来做同样的事情,并高度鼓励它。一个人永远无法获得足够的重复次数。不过,我将浏览的是我选择的另一个数据集,以开始习惯这个 API。
案例研究:查看自行车共享出行
对于第一个数据集,我们正在查看洛杉矶自行车共享出行的数据集。我从 Kaggle 上拉下了一个.csv。不要告诉任何人,但我相信我是第一个想到这一点的人。开个玩笑!不过说真的,我很想刮点东西,但是时间不够了。就这样吧。
你可以在这里找到数据。
如果您安装了 Kaggle API ,您可以通过复制 API 命令直接下载到您选择的文件夹:

Anyone know how to resize this?
然后粘贴到终端中运行,像这样:
kaggle datasets download -d cityofLA/los-angeles-metro-bike-share-trip-data
另一个 YMMV 类型的拦截器:当我试图解压下载的文件时,我遇到了权限问题。但是我通过
chmod 600 los-angeles-metro-bike-share-trip-data.zip解决了
..这给了我对文件的完全读/写权限。然后我就可以解压了:
unzip los-angeles-metro-bike-share-trip-data.zip
好吧!让我们设置运行Prophet对象的数据帧。请注意,模型需要非常具体的列名和类型;引用一下:
Prophet 的输入总是一个包含两列的 data frame:
*ds*和*y*。*ds*(datestamp)列应该是 Pandas 所期望的格式,理想情况下,日期应该是 YYYY-MM-DD,时间戳应该是 YYYY-MM-DD HH:MM:SS。*y*列必须是数字,代表我们希望预测的测量值。
完整的 Jupyter 笔记本中的所有代码都可以在这个 GitHub 资源库中获得(包括我所有的错误开始!)但清理后的版本如下:
import pandas as pd # Dataframes.
import plotly_express as px # Plotting.# Get dataset
bikes = pd.read_csv('./data/metro-bike-share-trip-data.csv')# Start time is string, convert to data
bikes['ds'] = pd.to_datetime(
bikes['Start Time'].apply(lambda st : st[:10])
, format='%Y-%m-%d')# Aggregate by trip start date
trips = bikes.groupby(['ds']).agg(
{'Trip ID': 'count'}).reset_index()# Rename columns
trips.columns = ['ds', 'y'] # Column names required by fbprophet API# Plot: reasonability check
px.scatter(trips
,x='ds'
,y='y'
,title='LA Daily Bike Share Trips - Jul 16 to Apr 17'
,labels={'y':'Trips','ds':'Day'})

Is that Plotly Express’ music I hear? Shameless, shameless plug.
3.拟合模型和预测
接下来,我们将运行一个运行预测的示例:
- 拟合模型
- 根据我们的模型预测
首先,让我们实例化我们的模型,并使其适合我们的数据集:
from fbprophet import Prophet #Importing here for visibility# Create Prophet object and fit
m_bikes = Prophet(yearly_seasonality=True
, weekly_seasonality=True) #All defaults
m_bikes.fit(trips)
对象Prophet有许多参数,可以设置这些参数来调整模型。例如,daily_seasonality、weekly_seasonality和yearly_seasonality等季节性设置只是可以调整的部分选项。例如,我认为自行车共享租赁可能会有一些每周和/或每年的模式。为了好玩,让我们看看如果我们将weekly_seasonality和yearly_seasonality设置为True会发生什么。
接下来,我们可以使用该模型进行预测。Prophet对象有一个名为make_future_dataframe的简便方法,可以创建一个具有指定数量的未来日期的 dataframe,这些日期与训练数据相关(天数是默认的periods)。例如,下面我们加上训练数据最后一天后的 60 天。
# Create dataframe with appended future dates
future = m_bikes.make_future_dataframe(periods=60) #Days is default
future.tail()

一旦建立了数据框架,我们就可以在“测试”集上运行预测:
# Forecast on future dates
forecast = m_bikes.predict(future)# Look at prediction and prediction interval
forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail()

4.检查结果
通过查看我们创建的forecast数据框架的tail,我们已经得到了预测的预览。让我们把它形象化,以获得更大的画面。
恰好我们的Prophet对象也包含了一个plot方法来实现这一点!以下是拟合/预测(直线( y_hat )和阴影区域(预测区间))与实际值(散点图)的关系图:
# Plot the forecast vs actuals
m_bikes.plot(forecast)

Not bad. Could potentially use some more data cleaning (outliers?) and parameter tuning.
此外,还有预测组件的图表!例如,由于我们明确地将weekly_seasonality和yearly_seasonality设置为True,将会有专门针对它们的组件图:

Wooh! There’s nothin’ like summer in the city.
我们肯定可以使用更多的历史数据,但如果我们从表面上看这些图,似乎在夏末旅行有一个高峰,然后在秋季逐渐下降。此外,在一周中,高峰骑行似乎发生在工作日的中后期。如果这些是西雅图自行车股,那可能不会完全通过我的轶事观察的嗅觉测试,但嘿,洛杉矶可能是一个完全不同的市场。关键是我们能够在短时间内做出一些不错的预测!
如此之多…
如果我们有更多的时间深入研究,我们可以开始查看上述参数,以进一步调整预测。这些不仅包括不同时期的季节性,还包括特定的假日效应,为趋势提供特定的变化点(例如,你没有一个完美的线性基线趋势,如上例所示),说明饱和点,等等。同样,详细信息可以在文档中找到,我强烈建议阅读白皮书以更深入地了解底层方法。
这就是我们所有的时间,我希望这是有用的。感谢阅读!
工作文件此处。
来源:
https://facebook.github.io/prophet/
泰勒 SJ,勒撒姆 B. 2017。大规模预测。 PeerJ 预印本5:e 3190v 2https://doi.org/10.7287/peerj.preprints.3190v2
先知 vs DeepAR:预测粮食需求

全球食品行业面临着巨大的可持续性挑战,联合国估计“每年大约有三分之一的全球食品生产被浪费或损失”。关于66%的损失发生在新鲜度是消费重要标准的食物群体。此外,由于过量订购和库存过多,经常会发生变质,在大多数情况下,这是预测困难的结果。随着人们向供应链上游移动,远离消费者购买行为,这一问题被放大,这种现象被称为牛鞭效应,导致更高的不准确性并加剧浪费问题。
到目前为止,市场上的大多数(如果不是全部)预测解决方案都是基于传统的几十年前的方法,如 ARIMA 和指数平滑法。虽然这些方法产生稳定的预测,但它们难以预测时间序列的快速变化,特别是当这些变化是由于多重季节性或移动假期引起的。
然而,随着机器学习的进步,在过去几年中开发了一些技术,使解决这些挑战变得更加容易。
Crisp 通过结合尖端的机器学习技术和一种新的方法来模拟经济损失和库存新鲜度,为食品行业提供全面和全自动的预测解决方案。
在本帖中,我们将考察两个预测模型:脸书预言家和亚马逊预测的 DeepAR。我们发现这些方法很有前景,因为亚马逊研究声称,与当前最先进的方法相比,DeepAR 的预测性能提高了约 15%,而 Prophet 允许快速和非常灵活的建模体验。
脸书先知
脸书预言家模型是多种函数的组合,如趋势、多个季节、假期和用户输入的用黄土估算的回归量。因此,它在概念上类似于一个广义加法模型。
以下是 Prophet 的通用公式,显示了所有不同的组件:

趋势:
趋势分量可以是时间的线性或逻辑函数。为了模拟趋势中的非周期性变化,术语 a(t)^T ,即变点被引入到等式中,以允许增长因子的变化。逻辑函数引入了术语 C(t) 来防止趋势的无限增长或负增长。

季节性:
多个季节性可以用具有平滑先验和低通滤波器的傅立叶级数来建模。在 Prophet 中,这由下面的公式表示。项数(N)越高,过度拟合的风险就越高。

节假日和活动:
假期被建模为独立的虚拟外部回归变量,允许包含假期前后的一段时间来模拟其影响。

所有模型组件参数的先验来自正态分布,具有超参数化的标准偏差,以允许其灵活性。

赞成的意见
- 能够轻松模拟任意数量的季节性
- 能够处理时间序列中缺失的日期
- 轻松地将假期集成到模型中
- 具有完全贝叶斯采样的内置不确定性建模允许模型透明
- 允许使用用户指定的变化点进行灵活的逐步线性或逻辑趋势建模
骗局
- 在少量观察的情况下可能是不稳定的
- 长期预测可能会随着自动变点选择而波动
亚马逊的 DeepAR
亚马逊研究院开发的 DeepAR 算法(或演化版本 DeepAR+,为简单起见,在本文中简称为 DeepAR)为概率预测问题量身定制了一个类似的基于 LSTM 的递归神经网络架构。长短期记忆(LSTM) 众所周知,它能够学习序列中项目之间的依赖关系,这使它适用于时间序列预测问题。它实现了一系列的门,其中的信息要么被传递,要么被遗忘。它不仅仅传递隐藏状态 h ,还传递长期状态 c 。这使得它能够学习短期和长期的时间序列模式。

DeepAR 模型在一个架构中实现了这样的 LSTM 单元,该架构允许同时训练许多相关的时间序列,并实现了序列到序列模型中常见的编码器-解码器设置。这需要首先在整个调节数据范围上训练编码器网络,然后输出初始状态 h 。该状态然后被用于通过解码器网络将关于调节范围的信息传送到预测范围。在 DeepAR 模型中,编码器和解码器网络具有相同的架构。

DeepAR 模型还采用了一组协变量 X ,它们是与目标时间序列相关的时间序列。此外,向模型提供了一个分类变量,用作相关时间序列分组的信息。模型训练一个嵌入向量,该向量学习组中所有时间序列的共同属性。该模型的另一个特性是,DeepAR 根据目标时间序列的粒度自动创建额外的特性时间序列。例如,这些可以是为“一个月中的某一天”或“一年中的某一天”创建的时间序列,并允许模型学习与时间相关的模式。

DeepAR 接口期望设置相当数量的超参数,包括用于训练的上下文长度、时期数、辍学率和学习率等。
利弊
DeepAR 具有同时训练数百或数千个时间序列的优势,潜在地提供了显著的模型可伸缩性。它还具有以下技术优势:
- 最小特征工程:该模型需要最小特征工程,因为它学习跨时间序列的给定协变量的季节性行为。
- 蒙特卡罗抽样:由于 DeepAR 实现了蒙特卡罗抽样,因此还可以计算函数子范围的一致分位数估计。例如,在决定安全库存时,这可能很有用。
- 内置项目替代:它可以通过学习相似的项目来预测历史项目很少的项目
- 各种似然函数:DeepAR 不假设高斯噪声,并且似然函数可以适应数据的统计属性,允许数据的灵活性。
模型评估
数据科学中的常见做法是使用统计度量来促进模型选择和调整,以获得理想的模型预测精度。然而,这些方法无法捕捉预测误差的实际业务影响。我们开发了一种自定义的方法来量化这些误差,区分由于过度预测和预测不足造成的误差。我们开发评估的方法将在以后的文章中解释,但该方法允许我们评估两个模型,看它们是否在重要的时候抓住了每日趋势(例如新鲜产品),或者表现得更接近平均准确度,适合这种情况的产品(例如干燥食品)。
为了进行模型比较,我们使用了 1000 种食品零售产品的时间序列,范围从 3 个月到 3 年的数据。在 DeepAR 的例子中,数据包括假日数据以及零售产品类别的产品分组。我们将历元的数量设置为 500,上下文长度和预测长度为 90 天,同时保留所有其他超参数的默认值。在 Prophet 的例子中,一个模型被训练用于每个独立的时间序列,包括假期。所用的超参数针对最受欢迎的食品项目,包括倍增的每周季节性、累加的年度季节性和两个季节性先前分布的高方差。
使用扩展窗口交叉验证方法,以 7 天的时间跨度和 12 个原点的移动原点以及 7 天的步长,DeepAR 的几乎大多数产品的 MAPE 都显著高于 Prophet。但是,这不是一个稳定的指标,尤其是一些产品的条目为零或接近零,因此我们直观地研究了时间图,以比较两种预测。我们的自定义指标还显示,DeepAR 的预测误差会导致比 Prophet 更高的业务成本。


- 预言者预测总损失:890,013.29 台
- DeepAR 预测总损失:1502708.56 台
- 先知的损失比 DeepAR 高 41%。
下面是两个时间图,比较了 DeepAR 预测的最佳性能产品的实际信号和预测信号:


在对结果进行视觉分析时,很明显 DeepAR 在拾取一些高低点的强度时遇到了问题,同时错过了信号中的一些下降点。Prophet 通常更灵活,具有更好的性能,并且能够匹配实际需求的变化幅度。对 DeepAR 性能的一个可能的解释是需要更好的分组。类别分组包括具有非常不同需求特征的产品,因此,该模型可能受益于通过分析推断产品需求而不是通过零售类别(例如乳制品)设定的分组。虽然超出了我们当前研究的范围,但未来的实验应该检查 DeepAR 的性能是否会随着通过聚类方法或频谱分析对产品进行分组而提高。
我们得出结论,Prophet 在本次预测练习中胜出,原因如下:
- 更好地预测食品需求模式
- 模型的灵活性和设置的简易性
- 不需要大量的产品来达到良好的性能
- 不需要产品分组
- 允许部署到任何云平台
通过嵌入水印来保护您的深层神经网络!
我们对图像、音乐等媒体内容进行知识产权(IP)保护水印。深度神经网络(DNN)怎么样?
什么是水印?
水印就像是赋予你的媒体内容的一个身份,例如,你画一个免费的内容并上传到一个媒体平台,你将在内容上签名或者只是在内容上放一些标志。这是为了确认内容是你制作的,使用你内容的人应该付给你一些钱。
我们可以把同样的方法应用到 DNN,因为 DNN 的发展每年都在进步,很多公司开始在他们的业务中使用 DNN。
为什么我们需要在 DNN 上嵌入水印?
假设你已经投入了大量资源(例如时间、GPU)来创建强大的模型,然后你将它发布到你的 github 库,但是现在一些坏人未经你同意就拿着你的模型做生意,你发现坏人使用了你的模型,但是你没有证据,因为模型上没有水印。就像免费内容如何在网上分享一样,不是所有人都给学分:(
还记得三星赔偿苹果侵犯设计专利吗?同一案件可以申请某公司(如 A 公司)起诉其他公司使用 A 公司发布(甚至窃取)的 DNN 而不支付版权费。
接下来,我们将讨论通过嵌入水印来保护 DNN 的常用方法。
白盒方法
有很多方法使用白盒方法,我将谈论最简单的方法,而其他方法是类似的过程,只是不同的嵌入方式。
白盒嵌入
为了通过嵌入水印来保护 DNN 模型,在由[1]建议的方法中,他们使用变换矩阵来执行水印嵌入。
在训练阶段,模型是在原始分类任务上进行训练,然而,模型还有一个目标,即嵌入水印。
作者首先选择 DNN 中的哪一层来嵌入期望的水印(例如二进制数据)

然后,所选层中的权重将与变换矩阵进行矩阵乘法,以获得所需的信息位数,例如 64 位。

dot between weights and transformation matrix
在对原始任务(即分类)进行训练时,权重和变换矩阵都将通过作者设计的损失函数(即正确嵌入 64 位信息的损失函数)来更新
白盒检测
对于大公司来说,他们可能会从各个地方收集证据,这样他们就可以起诉涉嫌非法使用他们 DNN 模型的公司。一旦他们有证据,他们将需要有一个验证过程,即从 DNN 模型中提取水印,并比较水印是否来自大公司。
他们基本上是在做同样的事情来提取他们在训练中所做的水印。再次执行平坦化权重和变换矩阵之间的点运算,然后提取水印。
然而,这个过程是一个白盒验证,这意味着他们需要物理访问模型,通常可能需要通过执法部门。
黑箱方法
在阅读了一些研究论文[2,3,4]后,我意识到所有论文中的黑盒方法都是相似的。我要讲的例子来自[2]。
黑盒嵌入
在培训阶段,培训任务分为两部分:
- 原始分类任务
- 触发设置任务
什么是触发集任务?这实际上是一份被故意贴错标签的数据清单。

Example trigger set data wrongly labelled
错误标记的数据是一种水印,目的是让模型“记忆”准确的输入和标记,这种记忆形成了水印嵌入效果。虽然它可能会影响模型的特征学习,但是在【3】中有一些替代方案。
错误标记的数据与原始数据集结合,然后将经过原始训练目标(如交叉熵)
黑盒检测
这种水印嵌入方式在验证方面实际上优于白盒嵌入方式。这是因为您可以将一个触发集数据列表作为查询提交给机器学习在线服务(例如,小偷偷了您的模型并创建了一个与您的相似的服务)

a typical black-box verification
在通过 API 调用查询 ML 在线服务之后,您将得到预期的标签。如果预期标签与原始错误标签匹配,那么您可以确认该 ML 在线服务正在使用您的模型,因为在您的触发集数据上不可能有精确匹配(或高精度)。如果一个模特不是从你那偷来的,那么这个模特应该能把猫的形象归类为猫,而不是狗。
结论
我们需要保护我们的 DNN 模式,以防其他人窃取我们的信用而不付钱给我们!我们可以进行黑盒验证,以便对小偷有一个初步的嫌疑人,然后在我们向警方报告后,我们可以通过执法部门进行白盒验证。(虽然我认为这只是大公司之间的战争😅)
希望现在你对在 DNN 中嵌入水印有了更多的了解。感谢您的阅读!
参考
[1]将水印嵌入深度神经网络。https://arxiv.org/abs/1701.04082
[2]化弱为强:通过走后门给深度神经网络加水印。https://arxiv.org/abs/1802.04633
[3]利用水印技术保护深度神经网络的知识产权。https://dl.acm.org/citation.cfm?id=3196550
[4] DeepSigns:保护深度学习模型所有权的通用水印框架。【https://arxiv.org/abs/1804.00750
蛋白质序列分类
Pfam 数据集上蛋白质家族分类的实例研究。

摘要
蛋白质是大而复杂的生物分子,在生物体中扮演着许多重要角色。蛋白质由一条或多条氨基酸序列长链组成。这些序列是蛋白质中氨基酸的排列,通过肽键结合在一起。蛋白质可以由 20 不同种类的氨基酸组成,每种蛋白质的结构和功能都是由用来制造它的氨基酸种类以及它们的排列方式决定的。
了解氨基酸序列和蛋白质功能之间的关系是分子生物学中长期存在的问题,具有深远的科学意义。我们能否使用深度学习来学习 Pfam 数据库中所有蛋白质家族的未比对氨基酸序列及其功能注释之间的关系。
我用 Deepnote 创建了这个项目,这是一个非常棒的产品,供数据科学专业人员在云上运行 Jupyter 笔记本。超级容易设置,协作,并提供比谷歌 Colab 更多的功能。这里是完整的笔记本。
问题陈述
- 基于 Pfam 数据集,将蛋白质的氨基酸序列分类到蛋白质家族成员之一。
- 换句话说,任务就是:给定蛋白质结构域的氨基酸序列,预测它属于哪一类。
数据概述
我们已经提供了 5 个功能,它们如下:
- 这些通常是模型的输入特征。该结构域的氨基酸序列。有 20 种非常常见的氨基酸(频率> 1,000,000),和 4 种相当不常见的氨基酸:X、U、B、O 和 z。
family_accession:这些通常是型号的标签。PFxxxxx.y (Pfam)格式的登录号,其中 xxxxx 是家族登录号,y 是版本号。y 的某些值大于 10,因此“y”有两位数。sequence_name:序列名称,格式为“uni prot _ accession _ id/start _ index-end _ index”。aligned_sequence:包含来自多序列比对的单个序列,与 seed 中该家族的其余成员进行比对,保留缺口。family_id:家庭的一个字的名字。
来源:卡格尔
示例数据点
sequence: HWLQMRDSMNTYNNMVNRCFATCIRSFQEKKVNAEEMDCTKRCVTKFVGYSQRVALRFAE
family_accession: PF02953.15
sequence_name: C5K6N5_PERM5/28-87
aligned_sequence: ....HWLQMRDSMNTYNNMVNRCFATCI...........RS.F....QEKKVNAEE.....MDCT....KRCVTKFVGYSQRVALRFAE
family_id: zf-Tim10_DDP
机器学习问题
这是一个多类分类问题,对于给定的氨基酸序列,我们需要预测它的蛋白质家族成员。
公制的
- 多级原木损失
- 准确(性)
探索性数据分析
在本节中,我们将探索、想象并尝试理解给定的特性。给定的数据已经被分成 3 个文件夹,即使用随机分割的培训、开发和测试。
首先,让我们加载训练、val 和测试数据集。
给定集合的大小如下:
- 列车大小:1086741 (80%)
- Val 大小:126171 (10%)
- 测试规模:126171 (10%)
注:由于计算能力有限,我考虑的数据较少。然而,同样的解决方案也可以用于整个 Pfam 数据集。
序列计数
首先,让我们计算一下每个未比对序列中的编码(氨基酸)数量。
- 大多数未比对的氨基酸序列的字符数在 50-250 之间。
序列码频率
让我们也找出每个未比对序列中每个代码(氨基酸)的频率。
为训练、val 和测试数据绘制代码频率计数

Code Frequency Count
- 最常见氨基酸编码是亮氨酸(L),其次是丙氨酸(A),缬氨酸(V)和甘氨酸(G)。
- 正如我们所见,不常见的氨基酸(即 X、U、B、O、Z)的含量非常少。因此,我们可以考虑在预处理步骤中仅用 20 种常见的天然氨基酸进行序列编码。
文本预处理
氨基酸序列用它们相应的 1 个字母的代码来表示,例如,丙氨酸的代码是(A),精氨酸的代码是(R),等等。氨基酸及其编码的完整列表可以在这里找到。
示例,未对齐序列:
phpesrirlstrrddahgmpiriesrlgpdafarlrfmartcrailaaagcaapfeefs sadafsshvfgtcrmghdpmrnvvdgwgrshwpnlfvadaslfsgggespgltqalalrt
为了建立深度学习模型,我们必须将这些文本数据转换为机器可以处理的数字形式。我使用了一种热编码方法,考虑到 20 种常见的氨基酸,因为其他不常见的氨基酸数量较少。
下面的代码片段创建了一个包含 20 个氨基酸的字典,其中的整数值以递增的顺序排列,以便进一步用于整数编码。
对于每个未比对的氨基酸序列,使用创建的编码字典,1 个字母编码被一个整数值代替。如果该代码不在字典中,该值简单地用 0 代替,因此只考虑 20 种常见的氨基酸。
这一步将把 1 个字母的代码序列数据转换成这样的数字数据,
[13, 7, 13, 4, 16, 15, 8, 15, 10, 16, 17, 15, 15, 3, 1, 7, 6, 11, 13, 8, 13, 15, 8, 4, 16, 15, 10, 6, 13, 3, 1, 5, 1, 15, 10, 15, 5, 11, 1, 15, 17, 2, 15, 1, 8, 10, 1, 1, 1, 6, 2, 1, 1, 13, 5, 4, 4, 5, 16, 16, 1, 3, 1, 5, 16, 16, 17, 7, 18, 5, 6, 17, 2, 15, 11, 6, 7, 3, 13, 11, 15, 12, 18, 18, 3, 6, 19, 6, 15, 16, 7, 15, 19, 13, 12, 10, 5, 18, 1, 3, 1, 16, 10, 5, 13, 16, 16, 6, 6, 6, 4, 16, 13, 6, 10, 17, 8, 14, 1, 10, 1, 10, 15, 17]
下一个后填充以最大序列长度 100 完成,如果总序列长度小于 100,则用 0 填充,否则将序列截断到最大长度 100。
最后,序列中的每个代码被转换成一个热编码向量。
深度学习模型
我参考了这篇用于定义模型架构的论文,并训练了两个独立的模型,一个是双向 LSTM,另一个是受基于 CNN 架构的 ResNet 的启发。
模型 1:双向 LSTM
当处理基于 NLP 的问题时,像 LSTMs 这样的递归神经网络的变体是第一选择,因为它们可以处理像文本这样的时间或顺序数据。
RNNs 是一种神经网络,其中来自先前步骤的输出作为输入被馈送到当前步骤,从而记住关于序列的一些信息。当涉及到短上下文时,rnn 是很好的,但是由于消失梯度问题,它在记忆较长的序列方面有限制。
LSTM(长短期记忆)网络是 RNN 的改进版本,专门使用门控机制在延长的时间内记住信息,这使得它们可以选择记住什么先前的信息,忘记什么,以及为建立当前的细胞状态增加多少电流输入。

Bidirectional LSTM (Source)
单向 LSTM 只保存过去的信息,因为它看到的输入来自过去。使用双向将会以两种方式运行输入,一种是从过去到未来,另一种是从未来到过去,从而允许它在任何时间点保存来自过去和未来的上下文信息。
关于 RNN 和 LSTM 更深入的解释可以在这里找到。
模型架构如下,首先有一个嵌入层,它学习每个代码的矢量表示,然后是双向 LSTM。对于正则化,增加了丢弃以防止模型过度拟合。
输出层,即 softmax 层将给出所有独特类别的概率值(1000),并且基于最高预测概率,该模型将氨基酸序列分类到其蛋白质家族成员之一。
该模型用 33 个时期、256 的 batch_size 训练,并且能够实现测试数据的(0.386)损失和(95.8%)准确度。
439493/439493 [==============================] - 28s 65us/step
Train loss: 0.36330516427409587
Train accuracy: 0.9645910173696531
----------------------------------------------------------------------
54378/54378 [==============================] - 3s 63us/step
Val loss: 0.3869630661736021
Val accuracy: 0.9577034830108782
----------------------------------------------------------------------
54378/54378 [==============================] - 3s 64us/step
Test loss: 0.3869193921893196
Test accuracy: 0.9587149214887501
模式 2: ProtCNN
该模型使用受 ResNet 架构启发的剩余模块,该架构还包括扩大的卷积,提供更大的感受野,而不增加模型参数的数量。
ResNet(剩余网络)
由于消失梯度问题,更深层次的神经网络很难训练——随着梯度反向传播到更早的层,重复乘法可能会使梯度无限小。因此,随着网络越来越深入,其性能会饱和,甚至开始迅速下降。

Training and test error: Plain networks (Source)
ResNets 引入了跳过连接或恒等快捷连接,将初始输入添加到卷积块的输出中。

Single residual block: ResNet (Source)
这通过允许渐变流过替代的快捷路径来减轻渐变消失的问题。它还允许模型学习一个标识函数,该函数确保较高层的性能至少与较低层一样好,而不是更差。
扩张卷积

Dilated convolution (Source)
膨胀卷积为卷积层引入了另一个参数,称为膨胀率。这定义了内核中值之间的间距。膨胀率为 2 的 3×3 内核将具有与 5×5 内核相同的视野,同时仅使用 9 个参数。想象一下,取一个 5x5 的内核,每隔一行删除一列。
扩张卷积以相同的计算成本提供了更宽的视野。
ProtCNN 模型架构

Model architecture (Source)
氨基酸序列被转换成一个热编码,其形状(batch_size,100,21)作为模型的输入。初始卷积运算应用于核大小为 1 的输入,以提取基本属性。然后使用两个相同的残差块来捕获数据中的复杂模式,这是受 ResNet 架构的启发,这将有助于我们用更多的历元和更好的模型性能来训练模型。
我对剩余块的定义与 ResNet 论文略有不同。不是执行三个卷积,而是仅使用两个卷积,并且还向第一个卷积增加一个参数(膨胀率),从而在相同数量的模型参数下具有更宽的视场。
残差块中的每个卷积运算都遵循 batch normalization = > ReLU = > Conv1D 的基本模式。在残差块中,第一次卷积以 1×1 的核大小和膨胀率进行,第二次卷积以更大的核大小 3×3 进行。
最后,在应用卷积运算之后,通过添加初始输入(快捷方式)和来自所应用的卷积运算的输出来形成跳过连接。
在两个剩余块之后,应用最大池来减小表示的空间大小。对于正则化,增加了丢弃以防止模型过度拟合。
该模型用 10 个时期训练,batch_size 为 256,并在验证数据上验证。

439493/439493 [==============================] - 38s 85us/step
Train loss: 0.3558084576734698
Train accuracy: 0.9969123512774948
----------------------------------------------------------------------
54378/54378 [==============================] - 5s 85us/step
Val loss: 0.39615299251274316
Val accuracy: 0.9886718893955224
----------------------------------------------------------------------
54378/54378 [==============================] - 5s 85us/step
Test loss: 0.3949931418234982
Test accuracy: 0.9882489242257847
正如我们所看到的,这个模型的结果比双向 LSTM 模型要好。通过在 ProtCNN 模型集合中进行多数投票,可以实现对此的更多改进。
结论
在这个案例研究中,我们探索了深度学习模型,这些模型学习未比对的氨基酸序列与其功能注释之间的关系。ProtCNN 模型已经取得了显著的成果,比当前最先进的技术(如 BLASTp 注释蛋白质序列)更准确,计算效率更高。
这些结果表明,深度学习模型将成为未来蛋白质功能预测工具的核心组成部分。
感谢您的阅读。完整的代码可以在这里找到。
参考
了解氨基酸序列与蛋白质功能的关系是分子生物学中长期存在的问题。
www.biorxiv.org](https://www.biorxiv.org/content/10.1101/626507v4.full)
使用 LSTM 卷积自动编码器的视频异常检测

“London Underground atrium” (Photo by Anna Dziubinska on Unplash)
我们有数以千计的监视摄像机一直在工作,其中一些安装在偏远地区或街道上,那里不太可能发生危险的事情,另一些安装在拥挤的街道或城市广场上。即使在一个地点也可能发生各种各样的异常事件,并且异常事件的定义因地点和时间的不同而不同。
在这种情况下,使用自动化系统来检测异常事件是非常可取的,可以带来更好的安全性和更广泛的监控。一般来说,检测视频中的异常事件的过程是一个具有挑战性的问题,目前引起了研究人员的广泛关注,它在各个行业也有广泛的应用,最近它已经成为视频分析的基本任务之一。对于开发在现实世界应用中快速且准确的异常检测方法存在巨大的需求。
必备知识:
了解以下主题的基础知识:
卷积神经网络: 简单解释:
https://www.youtube.com/watch?v=YRhxdVk_sIs&t = 2s
更多详情:
http://cs231n.github.io/convolutional-networks/
http://colah.github.io/posts/2015-08-Understanding-LSTMs/ LSTM 网络
自动编码器: https://www . quora . com/What-is-an-auto-encoder-in-machine-learning
https://towardsdatascience . com/deep-inside-auto encoders-7e 41 f 319999 f
反卷积层:
https://towardsdatascience . com/transpose-convolution-77818 e55a 123
为什么我们不用监督的方法来检测异常呢?
如果我们要将问题视为二元分类问题,我们需要带标签的数据,在这种情况下,收集带标签的数据非常困难,原因如下:
- 异常事件因其罕见性而难以获得。
- 异常事件种类繁多,手动检测和标记这些事件是一项需要大量人力的艰巨任务。
上述原因促进了使用无监督或半监督方法的需求,如字典学习、时空特征和自动编码器。与监督方法不同,这些方法只需要未标记的视频片段,这些视频片段包含很少或没有异常事件,这些异常事件在现实世界的应用中很容易获得。
自动编码器
自动编码器是被训练来重建输入的神经网络。自动编码器由两部分组成:
- 编码器:能够学习称为编码 f(x)的输入数据(x)的有效表示。编码器的最后一层称为瓶颈,它包含输入表示 f(x)。
- 解码器:使用瓶颈中的编码产生输入数据的重构 r = g(f(x))。
方法
都是关于重建误差的。
我们使用自动编码器来学习视频序列的规律性。
直觉是,经过训练的自动编码器将以低误差重建规则视频序列,但不会精确重建不规则视频序列中的运动。

被数据弄脏
我们将使用 UCSD 异常检测数据集,其中包含安装在高处的摄像机采集的视频,可以俯瞰人行道。在正常设置下,这些视频只包含行人。
异常事件是由于以下原因之一:
- 人行道上的非行人实体,如骑车人、溜冰者和小推车。
- 不寻常的行人运动模式,如人们走过人行道或周围的草地。

UCSD 数据集由两部分组成,ped1 和 ped2。我们将使用 ped1 零件进行培训和测试。
安装
下载 UCSD 数据集并将其提取到您当前的工作目录中,或者使用此数据集在 Kaggle 中创建新的笔记本。
准备训练集
训练集由规则视频帧序列组成;该模型将被训练以重建这些序列。因此,让我们按照以下三个步骤准备好数据,以供我们的模型使用:
- 使用滑动窗口技术将训练视频帧分成时间序列,每个时间序列的大小为 10。
- 将每个帧的大小调整为 256 × 256,以确保输入图像具有相同的分辨率。
- 通过将每个像素除以 256,在 0 和 1 之间缩放像素值。
最后一点,由于这个模型中的参数数量庞大,需要大量的训练数据,所以我们在时间维度上进行数据扩充 。为了生成更多的训练序列,我们用不同的跳跃步幅连接帧。例如,第一步距-1 序列由帧(1,2,3,4,5,6,7,8,9,10)组成,而第一步距-2 序列由帧(1,3,5,7,9,11,13,15,17,19)组成。
这是代码。随意编辑它以获得更多/更少的具有不同跳跃步幅的输入序列,然后看看结果如何变化。
注意:如果你面临内存错误,减少训练序列的数量或使用数据发生器。
构建和训练模型
最后,好玩的部分开始了!我们将使用 Keras 来构建我们的卷积 LSTM 自动编码器。
下图显示了培训过程;我们将训练模型来重建常规事件。因此,让我们开始探索模型设置和架构。

要构建自动编码器,我们应该定义编码器和解码器。编码器接受按时间顺序排列的帧序列作为输入,它由两部分组成:空间编码器和时间编码器。来自空间编码器的序列的编码特征被馈送到时间编码器用于运动编码。
解码器镜像编码器以重建视频序列,因此我们的自动编码器看起来像一个三明治。

注意:因为模型有大量的参数,所以建议您使用 GPU。使用 Kaggle 或 Colab 也是一个好主意。
初始化和优化: 我们使用 Adam 作为优化器,将学习率设置为 0.0001 ,当训练损失停止减少时,我们通过使用 0.00001 的衰减来减少它,并将ε值设置为 0.000001 。
对于初始化,我们使用 Xavier 算法,它可以防止信号在通过每一层时变得太小或太大而无用。
让我们更深入地研究这个模型!
为什么在编码器中使用卷积层,在解码器中使用去卷积层?
卷积层将滤波器固定感受域内的多个输入激活连接到单个激活输出。它将过滤器长方体的信息抽象成一个标量值。另一方面,去卷积层通过具有多个学习滤波器的类卷积运算来增加稀疏信号的密度;因此,他们通过卷积的逆运算将单个输入激活与补丁输出相关联。
去卷积层中的学习滤波器用作重建输入运动序列形状的基础。
我们为什么要使用卷积 LSTM 层?
对于通用序列建模来说,LSTM 作为一种特殊的 RNN 结构已经被证明在保持长程相关性方面是稳定和健壮的。
这里,我们使用卷积 LSTM 层而不是全连接 LSTM 层,因为 FC-LSTM 层不能很好地保留空间数据,因为它在输入到状态和状态到状态的转换中使用了全连接,其中没有空间信息被编码。
图层归一化的目的是什么?
训练深度神经网络计算量大。减少训练时间的一种方法是使用层标准化来标准化神经元的活动;我们使用了层标准化而不是其他方法,如批量标准化,因为这里我们有一个递归神经网络。阅读更多关于标准化技术的信息。
我们做得好吗?
让我们进入测试阶段。

第一步是获取测试数据。我们将单独测试每个测试视频。UCSD 数据集提供了 34 个测试视频, Config 的值。SINGLE_TEST_PATH 决定使用哪一个。
每个测试视频有 200 帧。我们使用滑动窗口技术来获得所有连续的 10 帧序列。换句话说,对于 0 到 190 之间的每个 t ,我们计算从帧( t )开始到帧( t +9)结束的序列的正则性得分 Sr(t) 。
规律性得分:
我们使用 L2 范数计算视频的帧 t 中位置 (x,y) 处的像素亮度值 I 的重建误差:

其中 Fw 是 LSTM 卷积自动编码器的学习模型。然后,我们通过对所有像素误差求和来计算一帧的重建误差 t :

在 t 开始的 10 帧序列的重建成本可以计算如下:

然后,我们通过在 0 和 1 之间缩放来计算异常分数 Sa(t) 。

我们可以通过从 1 中减去异常分数来导出规律性分数 Sr(t) 。

在我们为范围【0,190】中的每个 t 计算正则性得分 Sr(t) 之后,我们绘制 Sr(t) 。

Test 032 of UCSDped1
一些测试:
首先,我们来看看 UCSDped1 的 test 32。视频开头,走道上有一辆自行车,解释了规律性得分低的原因。自行车离开后,规律性得分开始增加。在第 60 帧,另一辆自行车进入,规则性得分再次降低,并在它离开后立即增加。
UCSDped1 数据集的 Test 004 在视频的开头显示了一个溜冰者进入走道,在第 140 帧有人在草地上行走,这解释了规律性得分的两次下降。
UCSDped1 数据集的测试 024 显示一辆小车穿过人行道,导致规律性得分下降。推车离开后,规律性得分恢复到正常状态。
UCSDped1 数据集的 Test 005 显示了两辆自行车经过人行道,一辆在视频的开头,另一辆在视频的结尾。
结论:
尝试多个数据集,如 CUHK 大道数据集、UMN 数据集、T21 数据集,甚至使用监控摄像机或房间里的小型摄像机收集你自己的数据。训练数据相对容易收集,因为它由仅包含常规事件的视频组成。混合多个数据集,看看该模型是否仍然做得很好。想出一种方法来加快检测异常的过程,比如在测试阶段使用更少的序列。
别忘了在评论里写下你的结果!
在 Linkedin 上保持联系。
参考资料:
[1] Yong Shean Chong,利用时空自动编码器检测视频中的异常事件(2017), arXiv:1701.01546 。
[2] Mahmudul Hasan,Jonghyun Choi,Jan Neumann,Amit K. Roy-Chowdhury,学习视频序列中的时间规律性(2016), arXiv:1604.04574 。
作为数据工程师为企业提供有价值的数据
如何构建有用的数据资产
很容易忽略作为一名数据工程师的复杂性。毕竟,我们只是将数据从一个地方转移到另一个地方,对吧:这能有多难?
然而,在我担任数据工程师期间,我发现某些技能帮助我为我工作过的公司提供了宝贵的数据资产。
通常,我们的任务是接受一个业务需求或问题,并找到一种方法来获取、转换和连接数据,要么自己找到答案,要么让分析师施展他们的魔法。
和大多数事情一样,对于某些问题来说,有一条快捷简单的途径。然而,大多数时候首选的途径是未来的证明。
我们希望获得数据,这样既可以进行历史分析,也可以在未来继续进行分析。
我们还希望确保它是准备好的、干净的、易于使用的,并包含其他相关数据,以帮助进一步的问题。
这篇文章不打算关注数据工程的技术细节,而是更多地谈论我如何获取数据来帮助回答问题并为接下来的问题做好准备。
领域知识
如果你是一家企业的新手,或者被问到的问题超出了你以前探索过的领域,你就需要填补这个知识空白。
我发现,几乎可以肯定的是,企业中有人在该领域有很好的背景。即使他们不知道如何找到你想要的答案,他们也会给你提供至少一个关键资产:事物的名称和关键词。

Find the SME’s : Image Source
能够谈论组成一个领域的不同的事物将有助于你的研究、进一步的问题以及最重要的命名。
只有通过正确标记对象和与之相关的属性,才能实现对新数据的未来保护。
正确在这里也是主观的,因为它应该对你的业务是正确的。如果你的公司称人们为“用户”,那么你也应该这样做;如果他们称他们为“顾客”,那么你也应该这样做。
这使得整个企业更容易找到、分析和理解信息,从而使您创造的数据资产更有价值。
这也让您了解了与领域相关的其他度量和维度。很好地引导到下一部分…
面向未来
获取领域知识是关键的第一步,并很好地引导解决方案的未来验证。
根据经验,我知道提供数据来帮助回答一个问题只会引发更多的问题。
这很好,这是新信息应该做的,激发新的思维方式,让人们兴奋和渴望知道更多。

Data hungry! — Image source
当这些新问题出现时,你要做好准备。最糟糕的事情莫过于引发一场讨论,让想法流动起来,然后不得不等待新的数据被挖掘出来。
这就是一些经验和对问题的预期发挥作用的地方,但是早先获得的领域知识将会有所帮助。
在这一点上,我通常会开始思考为什么人们会问他们正在问的问题,并思考一旦他们有了答案,他们想要探索的其他途径。你可以通过问一个简单的问题做到这一点。
为什么答案比我预期的高/低/好/差?
问 为什么, 迫使你去思考你想要打破领域的维度种类。
如果你被要求查找每件产品的负面评论数量,你可能会想要一些维度,如时间、产品类型、客户档案——你甚至可以通过文本分析来查找负面评论中的常用短语。
这让您了解了进一步增强获得基本答案所需的额外数据和元数据的种类。
大多数情况下,您可以免费获得这些维度,因为通常特定事物的属性通常是与一起收集的,因此也与它一起存储。然而,要得到本质的答案,您通常需要扩展到其他数据源来补充您所拥有的。

Have all the answers : Image Source
重要的是你至少想过接下来会发生什么。
因此,当人们提出进一步的问题时,你将拥有所有的数据,最好的数据(或者至少知道它在哪里)来帮助回答他们。
近似策略优化教程(第 1/2 部分:行动者-批评家方法)
让我们从头开始编码一个强化学习足球代理!

欢迎来到数学和代码系列的第一部分。我将展示如何实现一种被称为近似策略优化(PPO)的强化学习算法,用于教一个人工智能代理如何踢足球。在本教程结束时,你将会了解到如何在行动者-批评家框架中应用策略学习方法,以便学习在任何游戏环境中导航。我们将看到这些术语在 PPO 算法的上下文中的含义,并在 Keras 的帮助下用 Python 实现它们。所以,我们先从我们游戏环境的安装开始。
注意:这整个系列的代码可以在下面链接的 GitHub 资源库中找到。
[## 中国机器人足球
在 Ubuntu 18.04 和单个 NVIDIA GPU 上测试。使用以下工具启动并运行谷歌足球研究环境…
github.com](https://github.com/ChintanTrivedi/rl-bot-football)
设置谷歌足球环境

Google Football Environment released for RL research
我在本教程中使用了谷歌足球环境,但是你可以使用任何游戏环境,只要确保它支持 OpenAI 在 python 中的 Gym API。请注意,在撰写本教程时,足球环境目前仅支持 Linux 平台。
首先创建一个名为footballenv的虚拟环境并激活它。
>> virtualenv footballenv
>> source footballenv/bin/activate
现在安装这个项目所需的系统依赖项和 python 包。确保您选择了适合您系统的正确的 gfootball 版本。
>> sudo apt-get install git cmake build-essential libgl1-mesa-dev
libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev libsdl2-gfx-dev libboost-all-dev libdirectfb-dev libst-dev mesa-utils xvfb x11vnc libsqlite3-dev glee-dev libsdl-sge-dev python3-pip
>> pip3 install gfootball[tf_gpu]==1.0
>> pip3 install keras
运行足球环境
现在我们已经安装了游戏,让我们试着测试它是否能在你的系统上正确运行。
一个典型的强化学习设置通过让一个人工智能代理与我们的环境进行交互来工作。代理观察我们环境的当前state,并基于一些policy做出采取特定action的决定。这个动作然后通过一个step传递回向前移动的环境。这产生了一个reward,指示所采取的行动在所玩游戏的上下文中是积极的还是消极的。使用这种奖励作为反馈,代理试图找出如何修改其现有的政策,以便在未来获得更好的奖励。

Typical RL agent
所以现在让我们继续,为一个随机行动的人工智能代理实现与这个足球环境的交互。创建一个名为train.py的新 python 文件,并使用我们之前创建的虚拟环境执行以下内容。
这为场景academy_empty_goal创建了一个环境对象env,在这个场景中,我们的球员在半场休息,必须在右侧的一个空门得分。representation='pixels'意味着我们的代理将观察到的状态是在屏幕上呈现的帧的 RGB 图像的形式。如果你在屏幕上看到一个玩家在游戏中随机行动,恭喜你,一切都设置正确,我们可以开始实现 PPO 算法了!
如果你更喜欢视频格式,这里有相同的安装步骤。
近似策略优化(PPO)
PPO 算法是由 OpenAI 团队在 2017 年推出的,并迅速成为最受欢迎的 RL 方法之一,取代了 Deep-Q 学习方法。它包括收集一小批与环境互动的经验,并使用这些经验来更新决策政策。一旦用这一批更新了策略,这些经验就被丢弃,并且用新更新的策略收集新的一批。这就是为什么它是一种“政策学习”方法的原因,在这种方法中,收集的经验样本只对更新当前政策有用一次。
PPO 的主要贡献是确保新的政策更新不会对之前的政策造成太大的改变。这导致训练中以一些偏差为代价的更少的变化,但是确保更平滑的训练,并且还确保代理不会走上采取无意义的行动的不可恢复的道路。所以,让我们继续把我们的 AI 代理分解成更多的细节,看看它如何定义和更新它的策略。
演员-评论家方法
我们将对我们的 PPO 代理人采用演员-评论家的方法。它使用两个模型,都是深度神经网络,一个称为演员,另一个称为评论家。

PPO Agent
演员模型
Actor model执行学习在环境的特定观察状态下采取什么行动的任务。在我们的例子中,它将游戏的 RGB 图像作为输入,并将特定的动作(如射门或传球)作为输出。

The Actor model
先实现这个吧。
这里,我们首先为我们的神经网络定义输入形状state_input,它是我们的 RGB 图像的形状。n_actions是我们在这个足球环境中可用的动作总数,将是神经网络的输出节点总数。
我使用预训练的 MobileNet CNN 的前几层来处理我们的输入图像。我也使这些层的参数不可训练,因为我们不想改变它们的权重。只有添加在该特征提取器之上的分类层将被训练来预测正确的动作。让我们将这些层合并为 Keras Model并使用均方误差损失进行编译(现在,这将在本教程的稍后部分更改为自定义 PPO 损失)。
评论家模式
我们将演员预测的动作发送到足球环境中,观察游戏中发生的事情。如果我们的行动产生了积极的结果,比如进了一个球,那么环境就会以奖励的形式发回积极的回应。如果我们的行动导致了乌龙球,那么我们会得到负面的奖励。这个奖励被Critic model拿走了。

The Critic model
批评家模型的工作是学会评估行动者采取的行动是否使我们的环境处于更好的状态,并向行动者提供反馈,因此得名。它输出一个实数,表示在先前状态中采取的行动的等级(Q 值)。通过比较从批评家那里获得的评级,参与者可以将其当前政策与新政策进行比较,并决定如何改进自己以采取更好的行动。
让我们实现批评家。
如你所见,评论家神经网络的结构和演员几乎一样。唯一的主要区别是,最后一层 Critic 输出一个实数。因此,使用的激活是tanh而不是softmax,因为我们不需要像演员一样的概率分布。
现在,PPO 算法中的一个重要步骤是用两个模型运行整个循环,运行固定数量的步骤,称为 PPO 步骤。所以本质上,我们在一定数量的步骤中与我们的环境互动,并收集状态、动作、奖励等。我们将用它来训练。
把这一切联系在一起
现在我们已经定义了我们的两个模型,我们可以使用它们与足球环境进行固定步数的交互,并收集我们的经验。这些经验将被用来更新我们的模型的政策后,我们有足够大的一批这样的样本。这就是如何实现收集这种样本经验的循环。
正如你在上面的代码中看到的,我们已经定义了几个 python 列表对象,用于存储观察到的状态、动作、奖励等信息。当我们与我们的环境互动时,共有ppo_steps。这给了我们一批 128 个样本经验,这些经验将在以后用于训练演员和评论家的神经网络。
接下来的两个视频逐行解释了这段代码,并展示了游戏屏幕上的最终结果。
未完待续…
这部分教程到此为止。我们在 Linux 系统上安装了 Google Football 环境,并实现了一个与该环境交互的基本框架。接下来,我们定义了演员和评论家模型,并使用它们与这个游戏进行交互,并从这个游戏中收集示例体验。希望你能坚持到现在,否则,如果你遇到了什么困难,请在下面的评论中告诉我,我会尽力帮助你的。
下一次我们将看到如何使用我们收集的这些经验来训练和改进演员和评论家模型。我们将检查Generalized Advantage Estimation算法,并使用它来计算用于训练这些网络的custom PPO loss。所以留下来!
编辑:这是本教程系列的第二部分。
感谢您的阅读。如果你喜欢这篇文章,你可以关注我在媒体、 GitHub 上的更多作品,或者订阅我的 YouTube 频道。
近似政策优化教程(第二部分:GAE 和 PPO 损失)
让我们从头开始编写一个 RL 足球代理!

第一部分链接: 近端政策优化教程(第一部分:演员-评论家法)
欢迎来到强化学习数学和代码教程系列的第二部分。在本系列的第一部分中,我们看到了如何设置谷歌足球环境,然后实现了一个演员-评论家模型框架来与这个游戏环境进行交互并从中收集示例体验。
今天,我们将通过使用该批示例体验来训练我们的模型在游戏中得分,从而完成教程的剩余部分。关于我们上次实施的代码,回想一下到目前为止我们已经收集了以下信息:

利用这些信息,我们现在可以继续计算优势。
广义优势估计(GAE)
优势可以被定义为当我们处于特定的状态时,通过采取特定的行动来衡量我们可以变得多好的一种方式。我们希望使用我们在每个时间步骤收集的奖励,并计算通过采取我们所采取的行动我们能够获得多少优势。因此,如果我们采取了一个好的行动,比如朝着一个目标射击,我们想要计算我们通过采取那个行动,不仅在短期内,而且在更长的一段时间内,有多好。这样,即使我们没有在射门后的下一个时间步立即进球,我们仍然会在那个动作后的几个时间步看更长的未来,看看我们是否进球了。
为了计算这一点,我们将使用一种称为广义优势估计或 GAE 的算法。所以让我们用我们收集的那批经验来看看这个算法是如何工作的。

Generalized Advantage Estimation Algorithm
- 这里,使用掩码值
*m*,因为如果游戏结束,那么我们批次中的下一个状态将来自新重启的游戏,所以我们不想考虑它,因此掩码值取为 0。 - Gamma
*γ*只不过是一个常数,称为折扣因子,目的是减少未来状态的值,因为我们希望更多地强调当前状态而不是未来状态。考虑到这一点,现在进球比将来进球更有价值,因此我们不考虑将来的目标,这样我们可以给现在的目标更多的价值。 - λ
λ是平滑参数,用于减少训练中的方差,使其更加稳定。该论文中建议的该平滑参数的值是 0.95。因此,这给了我们在短期和长期采取行动的优势。 - 在最后一步中,我们简单地反转返回列表,因为我们从最后一个时间步骤循环到第一个时间步骤,所以我们获得了原始订单。
这基本上是 GAE 算法,可以在我们的代码中实现,如下所示。
下面的视频是对这个算法的逐行解释。
我们现在有了训练演员和评论家模特所需的一切。因此,我们将了解如何使用这些信息来计算自定义 PPO 损失,并使用该损失来训练参与者模型。
自定义 PPO 损失
这是近似策略优化算法中最重要的部分。那么我们先来了解一下这个损失函数。
回想一下*π* 表示由我们的 Actor 神经网络模型定义的策略。通过训练这个模型,我们希望改进这个策略,以便随着时间的推移,它给我们提供越来越好的行动。现在,一些强化学习方法的一个主要问题是,一旦我们的模型采用了一个坏的策略,它只会在游戏中采取坏的行动,因此我们无法从那里产生任何好的行动,从而导致我们在训练中走上不可恢复的道路。PPO 试图通过在更新步骤中只对模型进行小的更新来解决这个问题,从而稳定训练过程。PPO 损失可计算如下。

Custom PPO loss calculation
- PPO 在更新步骤中使用新更新的策略和旧策略之间的比率。从计算上来说,用对数形式表示更容易。
- 利用这个比率,我们可以决定我们愿意容忍多大程度的政策变化。因此,我们使用限幅参数ε
*ε*来确保我们一次只对我们的策略进行最大限度的*ε%*改变。文中建议ε值保持在0.2。 - 批评家损失只不过是收益的平均平方误差损失。
- 如果我们想用一个折扣系数使演员和评论家的损失达到相同的数量级,我们可以把他们结合起来。添加熵项是可选的,但它鼓励我们的参与者模型探索不同的政策,并且我们想要实验的程度可以由熵β参数来控制。
这个自定义损失函数可以用 Keras 使用下面的代码来定义。
下面是这个自定义损失函数在下面嵌入的视频中的逐行解释和实现。
模型训练和评估
现在终于可以开始模特训练了。为此,我们如下使用 Keras 的fit函数。
现在,您应该能够在屏幕上看到模型采取不同的行动,并从环境中收集奖励。在训练过程的开始,当随机初始化的模型探索游戏环境时,动作可能看起来相当随机。

好了,现在让我们实现一些模型评估代码。这将在模型训练期间告诉我们,就成功得分而言,模型的最新更新版本有多好。因此,为了评估这一点,我们将计算平均奖励,定义为从零开始多次玩游戏所获得的所有奖励的平均值。如果我们在 5 场比赛中的 4 场比赛中进球,我们的平均回报将是 80%。这可以如下实现。
一旦模型开始学习哪一组行动产生最好的长期回报,测试阶段将如下所示。在我们的例子中,向右击球被观察到产生了最好的回报,因此我们的演员模型将产生正确的方向和射击动作,作为它的首选输出动作。

用于将所有内容联系在一起的其余代码可以在 GitHub 资源库的train.py脚本中找到。
*[## 中国机器人足球
此代码实现了近似策略优化(PPO)算法的基本版本,目的是…
github.com](https://github.com/ChintanTrivedi/rl-bot-football)*
如果你想通过逐行解释来学习这个实现,你可以看下面的视频。
结论
我希望这篇教程能让你对基本的 PPO 算法有一个很好的了解。现在,您可以通过并行执行多个环境来继续构建,以便收集更多的训练样本,并解决更复杂的游戏场景,如完整的 11 对 11 模式或角球得分。一些有用的参考资料可以对此有所帮助,也是我在本教程中使用的,可以在这里和这里找到。祝你好运!
感谢您的阅读。如果你喜欢这篇文章,你可以关注我在媒体、 GitHub 上的更多作品,或者订阅我的 YouTube 频道。
修剪深度神经网络
TL;修剪的不同方法,DR:通过修剪,基于 VGG-16 的分类器变得快 3 倍,小 4 倍
如今,深度学习模型需要大量的计算、内存和能力,这在我们需要实时推理或在计算资源有限的边缘设备和浏览器上运行模型的情况下成为瓶颈。能效是当前深度学习模型的主要关注点。解决这种效率的方法之一是启用推理效率。
更大的型号= >更多的内存引用= >更多的能量
Prunning 是一种推理方法,可以有效地生成尺寸更小、更节省内存、更省电、推理速度更快的模型,同时精确度损失最小,其他类似的技术还有权重共享和量化。深度学习从神经科学领域获得了灵感。深度学习中的修剪也是一个生物启发的概念,我们将在本文稍后讨论。
随着深度学习的进展,最先进的模型越来越准确,但这种进展是有代价的。我将在这个博客中讨论其中的一些。
第一个挑战——模特越来越大
难以通过空中更新分发大型模型

Dally, NIPS’2016 workshop on Efficient Methods for Deep Neural Networks
第二个挑战:速度

Training time benchmarked with fb.resnet.torch using four M40 GPUs
如此长的训练时间限制了 ML 研究员的生产力。
第三个挑战:能源效率
AlphaGo: 1920 个 CPU 和 280 个 GPU,每局 3000 美元电费

- 在移动设备上:耗尽电池
- 在数据中心:增加总拥有成本
解答—高效的推理算法
- 修剪
- 重量共享
- 量化
- 低秩近似
- 二元/三元网络
- 威诺格拉变换
修剪的生物灵感
人工神经网络中的修剪是从人脑中的 突触修剪 中得到的想法,其中轴突和树突完全衰退和死亡,导致许多哺乳动物在幼儿期和青春期开始之间发生突触消除。修剪从出生时开始,一直持续到 25 岁左右。

Christopher A Walsh. Peter Huttenlocher (1931–2013). Nature, 502(7470):172–172, 2013.
修剪深度神经网络

[Lecun et al. NIPS’89] [Han et al. NIPS’15]
网络通常看起来像左边的:下面一层中的每个神经元都与上面一层有联系,但这意味着我们必须将许多浮点相乘。理想情况下,我们只需将每个神经元连接到少数几个其他神经元上,省下一些乘法运算;这被称为“稀疏”网络。
稀疏模型更容易压缩,我们可以在推断过程中跳过零,以改善延迟。
如果你可以根据神经元的贡献大小对网络中的神经元进行排序,那么你就可以从网络中移除排序较低的神经元,从而形成一个更小、更快的网络。
获得更快/更小的网络对于在移动设备上运行这些深度学习网络很重要。
例如,可以根据神经元权重的 L1/L2 范数来进行排序。剪枝之后,准确率会下降(如果排序巧妙的话希望不会下降太多),通常会对网络进行训练-剪枝-训练-剪枝迭代恢复。如果我们一次修剪太多,网络可能会被严重破坏,无法恢复。所以在实践中,这是一个迭代的过程——通常称为“迭代修剪”:修剪/训练/重复。
参见 Tensorflow 团队编写的代码,了解迭代修剪。
权重修剪
- 将权重矩阵中的单个权重设置为零。这相当于删除连接,如上图所示。
- 这里,为了实现 k%的稀疏性,我们根据权重矩阵 W 中各个权重的大小对它们进行排序,然后将最小的 k%设置为零。
f = h5py.File("model_weights.h5",'r+')
for k in [.25, .50, .60, .70, .80, .90, .95, .97, .99]:
ranks = {}
for l in list(f[‘model_weights’])[:-1]:
data = f[‘model_weights’][l][l][‘kernel:0’]
w = np.array(data)
ranks[l]=(rankdata(np.abs(w),method=’dense’) — 1).astype(int).reshape(w.shape)
lower_bound_rank = np.ceil(np.max(ranks[l])*k).astype(int)
ranks[l][ranks[l]<=lower_bound_rank] = 0
ranks[l][ranks[l]>lower_bound_rank] = 1
w = w*ranks[l]
data[…] = w
单元/神经元修剪
- 将权重矩阵中的所有列设置为零,实际上删除了相应的输出神经元。
- 这里,为了实现 k%的稀疏性,我们根据它们的 L2 范数对权重矩阵的列进行排序,并删除最小的 k%。
f = h5py.File("model_weights.h5",'r+')
for k in [.25, .50, .60, .70, .80, .90, .95, .97, .99]:
ranks = {}
for l in list(f[‘model_weights’])[:-1]:
data = f[‘model_weights’][l][l][‘kernel:0’]
w = np.array(data)
norm = LA.norm(w,axis=0)
norm = np.tile(norm,(w.shape[0],1))
ranks[l] = (rankdata(norm,method=’dense’) — 1).astype(int).reshape(norm.shape)
lower_bound_rank = np.ceil(np.max(ranks[l])*k).astype(int)
ranks[l][ranks[l]<=lower_bound_rank] = 0
ranks[l][ranks[l]>lower_bound_rank] = 1
w = w*ranks[l]
data[…] = w
自然地,当你增加稀疏度和删除更多的网络时,任务性能会逐渐降低。你认为稀疏性对性能的退化曲线会是怎样的?
使用简单的神经网络架构在 MNIST 数据集上修剪图像分类模型的结果,如下所示

The architecture that I used in the code given in reference

Use the code to regenerate the graph

关键要点
许多研究人员认为修剪是一种被忽视的方法,在实践中会得到更多的关注和使用。我们展示了如何使用一个非常简单的神经网络架构在玩具数据集上获得好的结果。我认为深度学习在实践中用来解决的许多问题都类似于这个问题,在有限的数据集上使用迁移学习,所以他们也可以从修剪中受益。
参考
- 本帖代号
- 修剪,还是不修剪:探索模型压缩中修剪的功效 ,Michael H. Zhu,Suyog Gupta,2017
- 学习卷积神经网络中的剪枝滤波器 ,黄千贵等。艾尔,2018
- 修剪深层神经网络使其快速而小巧
- 用 Tensorflow 模型优化工具包优化机器学习模型
清晰地编码或者被憎恨
代码被读取的次数比它被写入的次数多

Source: Imgur
T 这是面向所有数据科学家的公共服务公告。请清晰地编码。如果不是为了你的合作者的理智,那也是为了你自己的理智 x 月/年后。
个人趣闻
作为全国最佳贝叶斯统计项目的一名硕士生,我被勤奋、磨砺的头脑和富有挑战性的问题所包围。但可以说,我项目中最累人的部分是在我在那里的两年时间里给 1000 多名本科生的 R 代码评分。
我在寒冷、黑暗的房间里度过了许多漫长的周五夜晚,试图编译和执行他们的代码。当它们不可避免地失败,又一个新的错误信息出现时,我会低声吟唱来安慰自己:
“我们能行,我们能行,我们能行。”
这是一次创伤性的经历,经常把我的助教们逼到精神崩溃的边缘……甚至更远。
当然,给我们的代码打分的上层博士们要糟糕得多。我们的 12-15 名硕士生有编码作业,有时每个作业会产生 50 多页的输出...每周一次。我还记得我们的 TA 和他的眼泪。我听说他还在走廊里找人帮他给我们的作业打分。
要点
没有什么比看到两位数的 HiveQL/SQL 子查询嵌套和不考虑行字符最大值更糟糕的了。在阅读这些代码时,我几乎淹没在从我布满血丝的眼睛流出的红宝石般的血流中。
毕竟,你能想象在任何语言中阅读一段由数百行组成的段落是多么痛苦吗?每一行都有不同的字符长度,没有清晰的模式,每行的最大字数是冗长的,缺少标点符号,没有证据
关于白色 s p a c e
压痕的清晰图案?

The best metric for code assessment: WTFs per Minute (WTFM), averaged (weighted or unweighted) across multiple reviewers. (Image source: Thom Holwerda, https://www.osnews.com/story/19266/wtfsm/)
好的编码保证了产品的连续性。文档就更好了。虽然文档并不令人兴奋,也不总是有回报,但它使其他人能够继续你的工作,并享受这样做。结果是更好的软件更容易维护。
好的编码是细致入微的:有些东西可能太多或太少,比如空白。这里有几个例子突出了优秀编码的复杂细微差别:
示例:空白太少
我认为这些图片已经足够说明问题了。

A comically illustrative example of poor white space utilization.

I wrote this as an example of poorly formatted HiveQL code. It is too dense and there is no logical separation of ideas/clauses in the code that allows for a quick and seamless reading.

The original query. Image source: https://image.slidesharecdn.com/usinghive-151001173243-lva1-app6892/95/using-apache-hive-with-high-performance-44-638.jpg?cb=1443721144
示例:空白太多
当你不能一次看到所有相关的部分时,ode 就更难理解了。通过在大多数行之间插入一个空行,您可以看到的代码量减少了一半,并且可能没有充分利用宝贵的屏幕空间。

例子:没有普遍的规则
有时,没有一个单一的规则被多种语言所接受。例如,像每行最大字符数这样简单的事情在不同的语言中有不同的标准:
Max line length/character limits by language.\
结论
风格的一致性是编码项目的最高目标。只要一致,项目遵循哪些特定的惯例并不太重要。如果不处理不同代码段的不同缩进模式、命名约定和习惯用法,就很难理解大型系统。《Python》的作者 Guido Van Rossum 说得好:
风格指南是关于一致性的。与本风格指南保持一致非常重要。项目内部的一致性更重要。一个模块或功能内的一致性是最重要的。
然而,要知道什么时候不一致——有时候风格指南的建议并不适用。当有疑问时,使用你最好的判断。看看其他例子,决定什么是最好看的。不要犹豫地问!
总之,遵循风格规则,直到你有足够的经验和合理的理由去打破它们!
以下是数据科学家常用语言的一些风格指南:
Python
R
通用编码样式
- https://www.reddit.com/r/badcode/(针对用户提交的糟糕代码的例子)
参考文献
[1]https://www.codereadability.com/maximum-line-length/
【2】https://www . python . org/dev/peps/pep-0008/# a-foody-consistency-is-the-hobby-the-goblin-of-little-minds
处理小数据集的伪标记——什么、为什么和如何?
使用模型输出提高模型输出的指南!

TSNE Plot of MNIST Pseudo-Labeling
几天前,我看到了 Yoshua Bengio 对 Quora 问题的回复——“为什么无监督学习很重要?” 。以下是他的回复摘录:
通过监督学习攀登人工智能阶梯可能需要通过显示大量这些概念出现的例子来“教”计算机所有对我们重要的概念。这不是人类学习的方式:是的,由于语言,我们得到了一些例子来说明给我们的新的命名概念,但我们观察到的大部分东西并没有被标记,至少最初是这样。
从神经科学的角度和实践的角度来看,他的回答都很有意义。标注数据在时间和金钱方面都很昂贵。这个问题的显而易见的解决方案是找出一种方法:
(a)使 ML 算法在没有标记数据的情况下工作(即无监督学习)
(b)自动标记数据或使用大量未标记数据和少量标记数据(即半监督学习)
正如 Yann LeCun 在这篇文章中提到的,无监督学习是一个很难解决的问题:
“我们知道最终的答案是无监督学习,但我们还没有答案。”
然而,最近人们对半监督学习有了新的兴趣,这在学术和工业研究中都有所反映。这里有一张图表,显示了谷歌学术每年与半监督学习相关的研究论文数量。

Semi-Supervised Learning Research Papers by Year
在这篇博客中,我们将深入研究伪标签——一种简单的半监督学习(SSL)算法。尽管伪标记是一种幼稚的方法,但它给我们提供了一个极好的机会来理解 SSL 面临的挑战,并提供了一个基础来学习一些现代的改进,如 MixMatch、虚拟对抗训练等。
大纲:
- 什么是伪标签?
- 理解伪标记方法
- 实现伪标签
- 伪标签为什么会起作用?
- 伪标签什么时候效果不好?
- 用传统的 ML 算法进行伪标记
- 半监督学习的挑战
1.什么是伪标签?
伪标记方法由 Lee 于 2013 年【1】首次提出,使用一小组已标记数据和大量未标记数据来提高模型的性能。这项技术本身非常简单,只需遵循 4 个基本步骤:
- 基于一批标记数据的训练模型
- 使用训练好的模型来预测一批未标记数据上的标签
- 使用预测的标签来计算未标记数据的损失
- 将标记的损失与未标记的损失和反向传播结合起来
…然后重复。
这种技术可能看起来很奇怪——几乎类似于 youtube 上数百个“免费能源设备”视频。然而,伪标记已经成功地用于几个问题。事实上,在一场 Kaggle 竞赛中,一个团队使用伪标签来提高他们模型的性能,以确保第一名并赢得 25,000 美元。
我们一会儿就来看看为什么会这样,现在,让我们来看看一些细节。
2.理解伪标记方法
伪标记在每批中同时用标记和未标记的数据训练网络。这意味着对于每批标记和未标记的数据,训练循环会:
- 对贴有标签的批次进行一次正向传递以计算损耗→这是贴有标签的损耗
- 对未标记的批次进行一次正向传递,以预测未标记的批次的“伪标签”
- 使用这个“伪标签”来计算未标记的损失。
现在,Lee 建议使用权重,而不是简单地将未标记的损失与标记的损失相加。总损失函数如下所示:

Equation [15] Lee (2013) [1]
或者用更简单的话说:

在等式中,权重(α)用于控制未标记数据对总损失的贡献。此外,重量是时间(历元)的函数,并且在训练期间缓慢增加。当分类器的性能可能很差时,这允许模型最初更多地关注标记的数据。随着模型的性能随着时间(时期)的增加,权重增加,未标记的损失更加强调整体损失。
Lee 建议对 alpha (t)使用以下等式:

Equation [16] Lee (2013) [1]
其中α_ f = 3,T1 = 100,T2 = 600。所有这些都是基于模型和数据集而变化的超参数。
让我们看看 Alpha 如何随着时代的变化而变化:

Variation of Alpha with Epochs
在第一个 T1 时段(本例中为 100)中,权重为 0-有效地强制模型仅根据标记的数据进行训练。在 T1 时段之后,权重线性增加到 alpha_f(在这种情况下为 3 ),直到 T2 时段(在这种情况下为 600 ),这允许模型缓慢地合并未标记的数据。T2 和 alpha_f 分别控制权重增加的速率和饱和后的值。
如果你熟悉最优化理论,你可能会从 模拟退火 中认出这个方程。
这就是从实现的角度理解伪标签的全部内容。该白皮书使用 MNIST 来报告性能,因此我们将坚持使用相同的数据集,这将有助于我们检查我们的实现是否正常工作。
3.实现伪标签
我们将使用 PyTorch 1.3 和 CUDA 来实现,尽管使用 Tensorflow/Keras 应该也没有问题。
模型架构:
虽然本文使用了一个简单的 3 层全连接网络,但在测试过程中,我发现 Conv 网络的性能要好得多。我们将使用一个简单的 2 Conv 层+ 2 全连接层网络,并带有 dropout(如本报告中所述)
Model Architecture for MNIST
基准性能:
我们将使用 1000 个标记图像(类别平衡)和 59,000 个未标记图像用于训练集,10,000 个图像用于测试集。
首先,让我们在不使用任何未标记图像(即简单的监督训练)的情况下检查 1000 个标记图像的性能
Epoch: 290 : Train Loss : 0.00004 | Test Acc : 95.57000 | Test Loss : 0.233
对于 1000 幅标记图像,最佳测试准确率为 95.57%。现在我们有了基线,让我们继续伪标签实现。
伪标签实现:
对于实现,我们将做两个小的更改,使代码更简单,性能更好:
- 在前 100 个历元中,我们将像往常一样在标记数据上训练模型(没有未标记的数据)。正如我们之前看到的,这与伪标记没有区别,因为在此期间 alpha = 0。
- 在接下来的 100+个时期中,我们将对未标记的数据进行训练(使用 alpha 权重)。这里,对于每 50 个未标记的批次,我们将在已标记的数据上训练一个时期—这充当校正因子。
如果这听起来令人困惑,不要担心,这在代码中要容易得多。这次修改是基于这个 Github 回购,它在两个方面有帮助:
- 它减少了对标记的训练数据的过度拟合
- 提高了速度,因为我们只需要每批向前传递 1 次(在未标记的数据上),而不是论文中提到的 2 次(未标记和已标记)。

Flowchart
Pseudo-Labeling Loop for MNIST
注意:我这里不包括监督训练的代码(前 100 个时期),因为它非常简单。你可以在我的 回购这里 找到所有代码
以下是对标记数据进行 100 个时期的训练,然后进行 170 个时期的半监督训练后的结果:
# Best Accuracy is at 168 epochsEpoch: 168 : Alpha Weight : 3.00000 | Test Acc : 98.46000 | Test Loss : 0.075
在使用未标记数据后,我们达到了 98.46%的准确率,比监督训练高出了 3%。事实上,我们的结果比论文的结果更好——1000 个标记样本的准确率为 95.7%。
让我们做一些可视化来理解伪标签是如何工作的。
阿尔法权重与准确度

Alpha vs Epochs
很明显,随着 alpha 的增加,测试精度也慢慢增加,然后达到饱和。
TSNE 可视化
现在让我们看看伪标签是如何在每个时期被分配的。在下面的图中,有 3 点需要注意:
- 每一簇背景中淡淡的颜色才是真正的标签。这是使用所有 60k 训练图像(使用标签)的 TSNE 创建的
- 每个聚类中的小圆圈来自监督训练阶段使用的 1000 幅训练图像。
- 不断移动的小星星是模型为每个历元的未标记图像分配的伪标记。(对于每个时期,我使用了大约 750 张随机采样的未标记图像来创建绘图)


TSNE Animation
以下是一些需要注意的事项:
- 伪标签大部分都是对的。(恒星在具有相同颜色的星团中)这可以归因于较高的初始测试准确度。
- 随着训练的继续,正确伪标签的百分比增加。这反映在模型的整体测试精度的提高上。
这里有一个图显示了在第 0 时段(左)和第 140 时段(右)的相同的 750 点。我已经用红圈标出了改进的点。

(Note: This TSNE plot only show 750 unlabeled samples out of 59000 pts)
现在我们来看看为什么伪标签实际上是有效的。
4.伪标签为什么会起作用?
任何半监督学习算法的目标都是使用未标记和标记样本来学习数据的底层结构。伪标签能够通过做出两个重要的假设来做到这一点:
- 连续性假设(平滑度) : 彼此靠近的点更有可能共享一个标签。( 维基 ) 换句话说,输入的小变化不会引起输出的大变化。这种假设允许伪标记推断图像中的小变化如旋转、剪切等不会改变标记。
- 聚类假设 : 数据往往会形成离散的聚类,而分在 中的同一个聚类 更有可能与 共用一个标签 。这是连续性假设 ( 维基百科)的一个特例,另一种看待这个问题的方式是——类之间的决策边界位于低密度区域(这样做有助于泛化——类似于 SVM 这样的最大间隔分类器)。
这就是初始标记数据非常重要的原因,它有助于模型了解底层的聚类结构。当我们在代码中分配一个伪标签时,我们使用模型已经学习的聚类结构来推断未标记数据的标签。随着训练的进行,使用未标记的数据来改进所学习的聚类结构。
如果初始标记的数据太小或包含离群值,伪标记可能会将不正确的标记分配给未标记的点。相反的情况也成立,即伪标记可以受益于仅用标记数据就已经表现良好的分类器。
在下一节中,当我们查看伪标签失败的场景时,这应该更有意义。
5.伪标签什么时候效果不好?
初始标记数据不足以确定聚类
为了更好地理解这种情况,让我们进行一个小实验:不使用 1000 个初始点,让我们采取极端情况,只使用 10 个标记点,看看伪标记如何执行:

10 Initial Labeled Points
不出所料,伪标几乎没有区别。模型本身和随机模型一样好,精度 10%。由于每个类实际上只有 1 个点,该模型无法学习任何类的底层结构。
让我们将标记点的数量增加到 20(每节课 2 点) :

20 Initial Labeled Points
现在,该模型的性能略有提高,因为它学习了一些类的结构。这里有一些有趣的东西-请注意,伪标注为这些点(下图中用红色标记)分配了正确的标注,这很可能是因为附近有两个标注点。

Small Cluster near labeled points
最后,让我们试试 50 分:

50 Labeled Points
性能好多了!再一次,注意图像正中央的一小组棕色标记的点。同一个棕色聚类中距离标记点较远的点总是被错误地预测为水绿色(“4”)或橙色(“7”)。

Pseudo Labels near labeled points
需要注意一些事情:
- 对于上述所有实验(10 分、20 分和 50 分),标记点的选择方式产生了巨大的差异。任何异常值都会完全改变模型的性能和对伪标签的预测。这是小数据集的常见问题。(你可以阅读 我之前的博客 我已经详细讨论过这个问题了)
- 虽然 TSNE 是一个很好的可视化工具,但我们需要记住,它是概率性的,仅仅给了我们一个关于星团如何在高维空间中分布的想法。
总之,当涉及伪标记时,初始标记点的数量和质量都会产生影响。此外,模型可能需要不同类的不同数量的数据来理解特定类的结构。
初始标记数据不包括某些类别
让我们看看如果标记的数据集不包含一个类会发生什么(例如:“7”不包含在标记的集中,但未标记的数据仍然保留所有类)
在标记数据上训练 100 个时期后:
Test Acc : 85.63000 | Test Loss : 1.555
在半监督训练后:
Epoch: 99 : Alpha Weight : 2.50000 | Test Acc : 87.98000 | Test Loss : 2.987
总体准确度确实从 85.6%增加到 87.98%,但是之后没有显示出任何改进。这显然是因为模型无法学习类标签“7”的聚类结构。
下面的动画应该清楚地说明了这些:


Animation for pseudo-labeling with a missing class
毫不奇怪,伪标签在这里很困难,因为我们的模型没有能力学习它以前从未见过的类。然而,在过去几年中,人们对零触发学习技术表现出了极大的兴趣,这种技术使模型能够识别标签,即使它们不存在于训练数据中。
数据增加没有任何好处
在某些情况下,模型可能不够复杂,无法利用额外的数据。这通常发生在将伪标记与传统的 ML 算法(如逻辑回归或 SVM)一起使用时。当谈到深度学习模型时,正如吴恩达在他的 Coursera 课程中提到的那样——大型 DL 模型几乎总是受益于更多的数据。

Andrew Ng — Coursera Deep Learning Specialization
6.用传统的 ML 算法进行伪标记
在这一节中,我们将把伪标签概念应用于逻辑回归。我们将使用相同的 MNIST 数据集,包括 1000 张标记图像、59000 张未标记图像和 10000 张测试图像。
特征工程
我们首先将所有的图像归一化,然后进行 PCA 分解,从 784 维分解到 50 维。接下来,我们将使用 sklearn 的 degree = 2 的PolynomialFeatures()来添加交互和二次特性。这使得我们每个数据点有 1326 个特征。
数据集规模增加的影响
在我们开始伪标记之前,让我们检查一下当训练数据集大小缓慢增加时,逻辑回归是如何执行的。这将有助于我们了解模型是否可以从伪标签中受益。

随着训练数据集中的样本数量从 100 增加到 1000,我们看到准确性慢慢增加。此外,看起来准确性并没有停滞不前,而是呈上升趋势。由此,我们可以得出这样的结论:伪标签应该可以提高我们的性能。
基准性能:
让我们检查当逻辑回归只使用 1000 个标记图像时的测试准确性。我们将进行 10 次训练,以考虑测试分数的任何变化。
from sklearn.linear_model import SGDClassifiertest_acc = []
for _ in range(10):
log_reg = SGDClassifier(loss = 'log', n_jobs = -1, alpha = 1e-5)
log_reg.fit(x_train_poly, y_train)
y_test_pred = log_reg.predict(x_test_poly)
test_acc.append(accuracy_score(y_test_pred, y_test))
print('Test Accuracy: {:.2f}%'.format(np.array(test_acc).mean()*100))**Output:** Test Accuracy: 90.86%
我们使用逻辑回归的基线测试准确率为 90.86%
伪标签实现:
当使用逻辑回归和其他传统的 ML 算法时,我们需要以稍微不同的方式使用伪标记,尽管概念是相同的。
以下是步骤:
- 我们首先在我们的标记训练集上训练一个分类器。
- 接下来,我们使用这个分类器从无标签数据集中预测随机采样集上的标签。
- 我们组合原始训练集和预测集,并在这个新数据集上重新训练分类器。
- 重复步骤 2 和 3,直到使用完所有未标记的数据。

Flowchart for Pseudo-Labeling with conventional ML Algos
这种技术与这篇博客中提到的略有相似。然而,这里我们递归地生成伪标签,直到所有未标记的数据都被使用。阈值也可以用于确保只为模型非常确信的点生成伪标签(尽管这不是必需的)。
以下是围绕 sklearn 估算器的包装实现:
Pseudo-Labeling Wrapper for Logistic Regression
(完整代码可在 回购 )
现在我们可以把它和逻辑回归一起使用:
from sklearn.linear_model import SGDClassifierlog_reg = SGDClassifier(loss = 'log', n_jobs = -1, alpha = 1e-5)pseudo_labeller = pseudo_labeling(
log_reg,
x_unlabeled_poly,
sample_rate = 0.04,
verbose = True
)pseudo_labeller.fit(x_train_poly, y_train)y_test_pred = pseudo_labeller.predict(x_test_poly)
print('Test Accuracy: {:.2f}%'.format(accuracy_score(y_test_pred, y_test)*100))**Output:**
Test Accuracy: 92.42%
伪标记将准确率从 90.86%提高到 92.42%。(具有更高复杂性的非线性模型,如 XGBoost,可能执行得更好)
这里,sample_rate类似于深度学习模型例子中的alpha(t)。以前,alpha 用于控制使用的未标记丢失的数量,而在这种情况下,sample_rate控制在每次迭代中使用多少未标记的点。
sample_rate值本身是一个超参数,需要根据数据集和模型进行调整(类似于 T1、T2 和 alpha_f)。值 0.04 最适合 MNIST +逻辑回归示例。
一个有趣的修改是安排sample_rate随着训练的进行而加速,就像alpha(t).一样
在我们结束之前,让我们看看半监督学习中的一些挑战。
7.半监督学习的挑战
将未标记数据与标记数据相结合
半监督学习的主要目标是使用未标记数据和标记数据来理解数据集的底层结构。这里显而易见的问题是——如何利用未标记的数据来达到这个目的?
在伪标记技术中,我们看到使用预定的权重函数(alpha)来缓慢地将未标记的数据与标记的数据相结合。然而,alpha(t)函数假设模型置信度随时间增加,因此线性增加未标记的损失。不一定是这种情况,因为模型预测有时可能不正确。事实上,如果模型做出了几个错误的未标记预测,伪标记可能会像一个糟糕的反馈循环一样,进一步恶化性能。(参考:第 3.1 节 阿拉佐等人 2019【2】)
上述问题的一个解决方案是使用概率阈值——类似于我们对逻辑回归所做的。
其他半监督学习算法使用不同的方式来组合数据,例如,MixMatch 使用两步过程来猜测标签(对于未标记的数据),然后使用 MixUp 数据扩充来组合未标记的数据和标记的数据。(贝特洛等(2019)[3] )
数据效率
半监督学习的另一个挑战是设计可以处理非常少量的标记数据的算法。正如我们在伪标记中看到的,该模型对 1000 个初始标记样本的效果最好。然而,当标注数据集进一步减少时(例如:50 个点),伪标注的性能开始下降。
Oliver et al .(2018)【4】对几种半监督学习算法进行了比较,发现伪标记在“two-moons”数据集上失败,而 VAT 和 pi-model 等其他模型工作得更好。

Source: Oliver et al (2018) [4]
如图所示,VAT 和 Pi-Model 学习了一个决策边界,仅用 6 个带标签的数据点(显示在白色和黑色的大圆圈中)就取得了惊人的效果。另一方面,伪标记完全失败,取而代之的是学习线性决策边界。
我使用 Oliver 等人使用的相同模型重复了实验,发现伪标记需要 30-50 个标记点(取决于标记点的位置)来学习底层数据结构。

Pseudo-Labeling on Two Moons Dataset. Triangles are labeled points.
为了使半监督学习更加实用,我们需要数据效率高的算法,即可以在非常少量的标记数据上工作的算法。
8.结论
Oliver 等人[4]提到:“伪标记是一种简单的启发式方法,在实践中被广泛使用,可能是因为它的简单性和通用性”,正如我们所见,它提供了一种学习半监督学习的好方法。
在过去的 2-3 年中,用于图像分类的半监督学习已经取得了一些令人难以置信的进步。无监督数据增强(谢等(2019) [5] )在 CIFAR- 10 上仅用 4000 个标签就取得了 97.3%的效果。为了客观地看待这一点,DenseNet ( 黄等人(2016 )[6])在 2016 年在完整的 CIFAR-10 数据集上实现了 96.54%。
看到机器学习和数据科学社区如何转向使用更少标记数据(如半监督学习、零/少量学习)或更小数据集(如迁移学习)的算法,真的很有趣。就个人而言,我认为如果我们真的想为所有人普及人工智能,这些发展是至关重要的。
如果你有任何问题,请随时与我联系。我希望你喜欢!
Github Repo:https://Github . com/anirudhshenoy/pseudo _ labeling _ small _ datasets
数据集:https://www.kaggle.com/oddrationale/mnist-in-csv
参考资料:
- 李东炫。“伪标签:深度神经网络的简单有效的半监督学习方法”ICML 2013 研讨会:表征学习的挑战(WREPL),美国佐治亚州亚特兰大,2013 年(http://Deep Learning . net/WP-content/uploads/2013/03/Pseudo _ Label _ final . pdf)
- 埃里克阿拉索,迭戈奥特戈,保罗阿尔伯特,诺埃尔奥康纳,凯文麦克吉尼斯。“深度半监督学习中的伪标记和确认偏差”(https://arxiv.org/abs/1908.02983)
- 大卫·贝特洛,尼古拉斯·卡里尼,伊恩·古德菲勒,尼古拉斯·帕伯诺,阿维塔尔·奥利弗,科林·拉斐尔。“混合匹配:半监督学习的整体方法”(https://arxiv.org/abs/1905.02249)
- "阿维塔尔·奥利弗,奥古斯都·奥登纳,科林·拉斐尔,艾金·d·库布克,伊恩·j·古德菲勒. "深度半监督学习算法的现实评估”(https://arxiv.org/abs/1804.09170)
- 谢启哲,戴子航,Eduard Hovy,Minh-Thang Luong,郭诉乐。“用于一致性训练的无监督数据扩充”(https://arxiv.org/abs/1904.12848)
- 黄高,刘庄,劳伦斯·范·德·马滕,基利安·q·温伯格。“密集连接的卷积网络”(【https://arxiv.org/abs/1608.06993】T2)
- 【https://github.com/peimengsui/semi_supervised_mnist
- https://www . analyticsvidhya . com/blog/2017/09/pseudo-labeling-semi-supervised-learning-technique/
- https://www . quora . com/Why-is-unsupervised-learning-important
- https://www.wired.com/2014/08/deep-learning-yann-lecun/
普学习
处理隐藏在未标记数据中的负类

PU Learning — finding a needle in a haystack
在工作中不断出现的一个挑战是,在需要训练二进制分类器的情况下,没有标记的负类。通常,这个问题伴随着严重不平衡的数据集,在时间紧迫的情况下,我经常采取简单的方法,对未知数据集进行子采样,并将其视为未知。显然,这并不理想,因为未知集合被污染了,因此分类器不能很好地训练。然而,在野外,在现实生活的最后期限内,这种方法是省时的,而且结果常常出人意料地有用。
最近,我很幸运有几天时间稍微围绕这个话题阅读。我发现了一些有趣的方法,并认为值得做一些笔记,它们变成了这个帖子。
有几种不同的 PU 方法。所有的方法都包括从未知数据集中分离出一组所谓的可靠否定(RNs)。据我所知,最广泛引用的初始方法是刘等人在 2002 年和 2003 年提出的,其中一组 RNs 是从未知类中迭代生长出来的。
Fusilier 等人在 2015 年描述了另一种吸引我的方法。在他们的论文中,作者描述了一种方法,该方法迭代地从未知类中减少 rn 的集合,有效地收紧围绕那些与正类最不相似的情况的网。这种方法吸引了我,因为它含蓄地处理了阶级不平衡。
我遇到的第三种方法(Mordelet & Vert 2013)也隐含地说明了类别不平衡,它涉及装袋,或从未知类别中随机抽样,并将样本视为阴性。这与我上面提到的天真方法的不同之处在于,该过程被重复多次,并且训练了一系列模型。该模型针对具有不同污染程度的未知数据集来表征正类。得到的模型分数被集合,并且结果应该更好地将可靠的否定从未知类别中分离出来。
下面,我将详细介绍这三种方法。
方法
“原创”方法(刘等,2002 和 2003)
给定一个只包含阳性(P)和未知(U)类的训练集,遵循以下步骤:
- 将所有 U 视为否定(N)训练分类器 P 对 U
- 使用分类器,对未知类别进行评分,并分离出“可靠的”否定集合(RN)。
- 在 P 对 RN 上训练一个新的分类器,用它对剩余的 U 进行评分,分离出额外的 RN,放大 RN。
- 重复步骤 3,迭代地扩大 RN 的集合,直到满足停止条件。
当没有新的阴性病例被分类时,满足停止条件。
其中Q被定义为分类为负数的未知集合,而i是迭代器,停止条件被定义为:
|Qi| > 0
改良方法(Fusilier 等人,2014 年)
给定一个只包含阳性(P)和未知(U)类的训练集,遵循以下步骤:
- 将所有 U 视为否定(N)训练分类器 P 对 U
- 使用分类器,对未知类别进行评分,并分离出“可靠的”否定集合(RN)。
- 在 P 对 RN 上训练新的分类器。对 RN 评分并从 RN 中排除预测阳性
- 重复步骤 3,迭代地改进 RN 集合,直到满足停止条件。
其中Q被定义为被归类为否定的未知集合,i是迭代器,停止条件被定义为:
|Qi| <= |Q(i-1)| & |P| < |Qi|
停止条件确保 Q 的大小减小(避免 RN 大小突然大幅度减小),同时 RN 集永远不会变得小于 P 集。更明确地说:
虽然在这次迭代中被分类为否定的未知集合的大小小于或等于在先前迭代中被分类为否定的未知集合的大小,并且肯定类别集合的大小小于从这次迭代得到的精炼 rn 集合
装袋方法(归纳)(Mordelet & Vert,2013 年)
给定仅包含阳性(P)和未知(U)的训练集,其中 K =引导样本的大小,T =样本的数量,遵循以下步骤:1 .从 U ^ 2 中抽取大小为 K 的 bootstrap 样本 Ut。训练一个分类器 P 对 Ut 3。重复步骤 1 和 2,共 4 次。使用袋装模型通过集成方法对测试数据进行评分。
这里的停止标准是由 T 的值决定的,作者认为将 T 设置为> 100 通常不会获得太多的附加值。然而,从它们的曲线图来看,在|P|和K都很大的地方,T = 5 以上几乎没有变化。我怀疑,如果可能的话,在训练期间尝试跟踪这一点是值得的,或者在您的函数中设置早期停止类型标准,因为根据您的时间限制,训练 100 个模型可能是不可行的。
需要跟进的事项:
-大部分文章使用 SVM,但也往往是 NLP 问题。分类器家族很重要吗?
-原始文件如何确定确定“可靠性”的截止点
-改进的方法:WRT 停止准则,为什么 Q 会随着迭代而变大?
-打包方法:考虑如何最好地惩罚假阴性。
-截止选择?
结论
这三种方法为 PU 学习问题提供了合理的方法,但是只有改进的和 bagging 方法提供了处理不平衡数据的固有方法。我的计划是尝试并实现这两种方法,并比较它们的结果。虽然我不能公开分享数据,但我会试着在博客和 GitHub 上分享代码和一般结果。我们主要在 Python/PySpark 或 Scala/Spark 中工作。一些不错的链接:
- https://github.com/ispras/pu4spark用 Scala/Spark 编写的 PU 学习库
- https://astrakhantsev.com/pu-learning/pu4s park 的作者写了一篇不错的帖子
- https://Roy Wright . me/2017/11/16/positive-unlabeled-learning/PU 学习方法的精彩概述
参考
Fusilier DH,Montes-y-Gómez M,Rosso P,Guzmán Cabrera R (2015)使用 PU-learning 检测正面和负面欺骗性观点。Inf 流程管理 51:433–443。doi:10.1016/j . IPM 2014 . 11 . 001
刘波,戴 Y,李 X,等(2003)利用正例和未标记样本构造文本分类器。第三届 IEEE 数据挖掘国际会议。第 179-186 页
刘 B,李伟生,俞平山,李 X (2002)文本文档的部分监督分类。在:过程中。第 19 国际机场。糖膏剂关于机器学习。第 387 至 394 页
Mordelet F,Vert J-P (2014) A bagging SVM 从正面和无标签的例子中学习。模式识别列特 37:201–209。2013 年 6 月 10 日
PUBG —使用 AWS 和 Plotly 进行分析

Athena 和 cufflinks 在 PUBG 数据分析中的应用
对于本文,我将开始分析用本文中解释的管道提取的数据。这篇文章的目标也是:
- 获取 AWS athena 简介
- 使用 plotly 深入了解数据
- 更好地理解视频游戏 PUBG 的消费
PUBG 是一个被定义为皇家战役的游戏,其中的原则是有 X 个人(或小队)被投放到一个岛上,目标是通过使用随机部署在岛上的物品和武器成为岛上的最后一个幸存者。为了增加游戏中的紧张感(并给它一个结尾),地图中可用的部分会有规律地减少,以促使玩家为自己的生命而战。
就游戏性而言,有多个岛屿可用(每个岛屿都有自己的环境,如沙漠,雪地),你可以在不同的模式下玩(单人,双人小队),有时相机可以预定义(fpp 仅适用于第一人称玩家或第一/第三人称相机)
雅典娜概述
Athena 是亚马逊开发的一项服务,旨在让用户能够在不使用服务器或数据仓库的情况下,轻松地从 S3 桶中查询数据。这是一个在 web 浏览器上使用 Athena 的界面示例。

开发的系统在数据格式方面非常开放,可以使用 CSV、JSON、ORC、Avro 和 Parquet。
该系统的核心是建立在被定义为开源分布式 SQL 查询引擎的 Presto 之上。这个项目的主要用户之一是围绕交互式分析、批量 ETL、A/B 测试和开发者广告分析的各种主题的脸书。
有一篇关于 Presto 的很好的文章详细介绍了引擎的所有机械。
这个工具的一些其他大用户是网飞和 Airbnb,他们正在围绕这种系统建立服务。
如果我们回到 Athena 服务,无服务器系统很有趣,因为计费只基于扫描的数据。因此,对于想要处理大量“大”数据而不处理所有基础设施(这是一项全职工作)的人来说,S3 +雅典娜组合确实是一个很好的组合。
为了将 AWS Athena 连接到 python 脚本,有一个可以安装的包pyathenaidbc,它将安装一个可以在 pandas 数据框架中使用的连接器。这是一个连接数据的脚本示例。

代码非常简单,看起来像是对经典 postgreSQL 数据库的调用。为了让成本更加透明,我们用图表展示了实验的成本。

真正的数量是来自 S3 读数和雅典娜激活,每天分析不到 2 美元(这个实验只有 4 美元)。
让我们开始分析与 PUBG 相关的数据。
收集数据的状态
管道在 2019 年 1 月 26 日至 2019 年 4 月 5 日之间运行了一个多月,这段时间代表了大约 69000 场比赛,因此要处理的数据量相当有趣(比赛期间收集的事件量)。
就区域和平台而言,管道的重点是从北美的 PC 平台提取数据。
在本次分析中,我将重点关注 3 个事件:
- gamestat 周期,大约是 11 00 万行
- 玩家杀死,那大约是 7 00 万行
- 使用的项目大约有 19 00 万行
普罗特利公司
我是 plotly 库的忠实粉丝,去年我写了一篇文章,是关于一个闪亮的 python 包,名为 Dash ,由 plotly 提供支持,它真的是一个很酷的包,使得用 Python 构建 dashboard 更加容易。

该软件包是由总部设在蒙特利尔的公司 Plotly 开发的,所以我们实际上是邻居(真的就像 4 分钟的步行时间)。

原始包是一个非常酷的库,可以基于 D3.js 免费制作交互式图表,他们提供了在他们的图表工作室部署所制作的图表的可能性(免费版本提供了在他们的平台上托管 25 个图表的可能性,但是使用高级帐户你可以托管更多图表/数据)
就用法而言,最初的语法有点“沉重”,因此有人开发了包装器,以方便在一个以上的线性样式上构建图形:
对于这篇文章,我只使用袖扣,但我正计划使其他文章将使用 plotly express。
提取的数据
老实说,有很多网站做了类似的分析,比如 PUBGmap.io ,但是做不同的分析和比较仍然很有趣。
有一个基于地图和模式收集的匹配的表示。
最受欢迎的模式是小队模式,地图野蛮。对于下面的分析,我将把重点放在模式小队。
比赛持续时间
就持续时间而言,我为每张地图抽取了一个匹配样本(1000 个匹配),并且有一些箱线图。
地图野蛮人看起来在持续时间上有不同的表现,这可以通过比其他人小得多的大小来解释(中间值有 5 分钟的差异)。
比赛的演变
对于玩家的寿命,在下图中显示了玩家存活的百分比与地图中完成比赛的百分比。
在比赛开始时,大部分玩家都活着,这与地图上所有玩家的着陆时刻有关。就活着的玩家的进化而言,这张地图似乎没有一直进化下去,这张地图比另一张“更平滑”,艾朗格尔和迪霍洛托克非常相似,而萨维奇似乎是最暴力的一个(这可以通过地图的格式来解释)。
武器使用
就用于杀人的武器而言,这一时期每种武器的杀人数量分布情况。
游戏中有多种类型的武器,从手枪,步枪,猎枪或弩,但最受欢迎的是 AK47,在武器的顶部有很多步枪。
杀戮事件中另一个真正有趣的数据是最后一枪在身体上的位置,下图是最后一枪的位置在每个武器上的重新分配。
爆头的部分在武器的功能上是不同的,手枪看起来没有步枪精确(这是有道理的)
另一个有趣的见解是,一些武器似乎是地图特定的,在下图中有每个地图的武器分布。
支持项目用途
作为总结,我决定对游戏中的治疗和助推器的使用有一个总体的了解,在下面的图中,事件的演变使用了一个治疗对象和一个助推器对象来完成比赛。
对于助推器,在 40%的比赛中肯定有一个峰值,但是对于治疗对象,在 15%中有第一个凸起,这是指第一波被淘汰的球员,峰值在第二阶段淘汰的比赛中间。
这篇文章介绍了我将对从 PUBG 收集的数据进行的更多工作,我将重点关注 PUBG 上关于位置数据的未来文章,该数据集代表大约 500,00 0,00 0 行,因此会更有趣。

最初发表于【http://the-odd-dataguy.com】。
使用 Jupyter、Github 和 Kyso 将数据科学文章发布到网络上
结合这 3 个工具来增强你的 DS 工作流程
数据科学正在飞速发展,越来越多的组织使用数据来推动一切。但是有时候发布基于数据科学的报告还是有点困难。这可能是因为图表是交互式的,或者是因为您想要一个可复制的文档,或者是因为从数据科学工具转移到可读性更好的格式可能很困难。
在这篇文章中,我将介绍如何在 Jupyter 笔记本、Github 和 Kyso 中使用 python,我们可以在几分钟内从获得一些原始数据到在网络上发布一个非常棒的图表。

让我们开始分析一些数据
我首先假设你已经安装了 python3 否则去 https://www.python.org/下载它。然后,让我们安装 Jupyterlab 和 plotly 扩展,以获得出色的交互式图表。
pip3 install jupyterlab
jupyter labextension install @jupyterlab/plotly-extension
那么让我们从 Jupyterlab 开始
jupyter lab
让我们下载一些数据来玩玩,我用最近的欧洲晴雨表数据来看看有多少人在欧盟认同欧盟国旗,这里有。
所以现在你应该已经打开了 Jupyter 这是一个很好的交互地方,你可以在这里输入结果并看到实时输出。
然后,我们可以在第一个笔记本单元格中开始编写代码!
import plotly
import pandas as pd
import plotly.graph_objs as go
import country_converter as coco
plotly.offline.init_notebook_mode(connected=True)
这将导入我们需要的所有库,并设置 Plotly 来渲染 Jupyter 笔记本中的图表。
cols2skip = [0]
cols = [i for i in range(100) if i not in cols2skip]
df_raw = pd.read_excel('./eb_90_volume_A.xls', sheet_name="QD11.3", skiprows=8, usecols=cols)
df_raw.rename(columns={'UE28\nEU28':'EU28', 'UE28-UK\nEU28-UK':'EU28-UK' }, inplace=True)
df = df_raw.transpose()
df = df.rename(columns=df.iloc[0])
df = df.iloc[1:]
names = df.index.tolist()
names = ['GR' if (x == 'EL') else x for x in names]
names = ['GB' if (x == 'UK') else x for x in names]
iso3 = coco.convert(names=names, to='ISO3', not_found=None)
然后,我们可以与数据争论,并设置它进行绘图,这里没有太多的解释,我在几分钟的游戏和检查数据形状后得出了这个代码。
最后,让我们绘制数据:
data = [go.Choropleth(
locations = iso3,
z = df['Tend to agree'] * 100,
text = iso3,
locationmode="ISO-3",
reversescale=True,
colorscale="Blues",
marker = go.choropleth.Marker(
line = go.choropleth.marker.Line(
color = 'rgb(0,0,0)',
width = 0.5
)),
colorbar = go.choropleth.ColorBar(
ticksuffix = '%',
title = '% identify',
len=0.5,
),
)]
layout = {
"height": 700,
"width": 700,
"margin"
: {"t": 0, "b": 0, "l": 0, "r": 0},
"geo": {
"lataxis": {"range": [36.0, 65.0]},
"lonaxis": {"range": [-12.0, 36.0]},
"projection": {"type": "transverse mercator"},
"resolution": 50,
"showcoastlines": True,
"showframe": True,
"showcountries": True,
}
}
fig = go.Figure(data = data, layout = layout)
plotly.offline.iplot(fig)
现在你的笔记本里应该有一个漂亮的交互式图表了。

Plotly Chart in Jupyterlab
在 Jupyter 中,您还可以通过将单元格更改为 markdown 类型来为您的分析编写一系列注释,然后编写您喜欢的任何内容。
让我们把这个笔记本推到网上
我们现在有几个选择可以让我们在网上分享这个笔记本。我已经在 http://kyso.io/eoin/do-i-identify-with-eu-flag发布到网上,你可以看到预览。
我们的第一选择是,我们可以把它上传到 Kyso。去 https://kyso.io/create/study,顺便去看看。ipynb 文件,给它一个标题,你就会有一个分享的链接。
或者,我们可以将 Github 用于一个长期项目,在这个项目中,我们可能会做出很多改变。开始去 Github 做一个新项目,然后做正常的生意
git add .
git commit -m "initial commit"
git push origin master
然后你就可以在 Github 上查看你的项目和笔记本了。然而,这个阴谋是不可见的。所以让我们把这个项目导入到 Kyso。
前往https://kyso.io/github并登录 Github,搜索你的项目,点击该项目的“连接到 Kyso”,选择主文件,你就完成了。您的笔记本将在 Kyso 上可见,任何时候您提交到该存储库,它都会在 Kyso 上得到反映,这意味着您可以不断更新您的分析。例如,查看 Kyso 上的这个帖子,它链接到这个 Github 库。
使用 TensorFlow 服务发布 Keras 模型 API
TensorFlow Serving 是一个面向机器学习模型的高性能服务系统。我们提供了一个端到端示例来帮助您入门。

Source: Pixabay
构建 ML 模型是一项至关重要的任务。在生产中运行 ML 模型是一项复杂而重要的任务。我以前有一个关于通过 Flask REST API 服务 ML 模型的帖子— 用 Python Flask 发布机器学习 API。虽然这种方法可行,但它肯定缺少一些要点:
- 模型版本控制
- 请求批处理
- 多线程操作
TensorFlow 附带了一套工具来帮助您在生产中运行 ML 模型。其中一个工具——tensor flow 服务。有一个很好的教程描述了如何配置和运行它——tensor flow Serving with Docker。我将在我的例子中遵循相同的步骤。
TensorFlow 服务提供模型版本控制功能。默认情况下,客户端可以访问特定的模型版本或获取最新版本。当模型被保存时,我们可以使用当前的时间戳生成一个模型版本:
import calendar;
import time;
ts = calendar.timegm(time.gmtime())tf.saved_model.save(model, "./model_report_exec_time/" + str(ts))
这是生成的文件夹结构,模型的版本带有时间戳:

我更喜欢在自己的 Docker 容器中运行 TensorFlow 服务。根据说明,可以选择将 ML 模型复制到 TensorFlow 服务容器中。首先,从 tensorflow/serving image 创建一个基本容器:
docker run -d --name serving_base tensorflow/serving
将 ML 模型从本地文件夹复制到基本容器中(在我的例子中, model_folder 和 model_name 都被设置为 model_report_exec_time ):
docker cp <model_folder> serving_base:/models/<model_name>
现在您可以仔细检查模型是否复制成功。进入容器:
docker exec -it serving_base bash
导航到 models 文件夹,您应该在那里看到您的 ML 模型:

现在创建一个新的 Docker 容器,并将您的 ML 模型名称设置为环境变量,这样模型将在下一次容器启动时提供服务:
docker commit --change "ENV MODEL_NAME <model_name>" serving_base katanaml/core-serving:v19.8
你不再需要基本容器,移除它:
docker kill serving_base
docker rm serving_base
从您新创建的映像启动容器(REST 端点运行在端口 8501 上):
docker run -d -p 8500:8500 -p 8501:8501 --name katana-ml-serving katanaml/core-serving:v19.8
检查容器日志,以确保 TensorFlow 服务启动时没有错误:
docker logs -f katana-ml-serving

我建议通过 RESTful API 指南了解 TensorFlow 服务。您应该检查模型端点是否可用,执行 GET:http://localhost:8501/v1/models/

如果获得响应成功,我们可以进一步执行 ML 模型预测功能。使用 TensorFlow 服务,可以通过 POST 请求直接调用 predict 函数,参数可以通过一个名为 instances 的变量传递。ML 模型接受规范化数据,这意味着数据应该在调用预测端点之前规范化。在这个例子中,数据在 Python 中被规范化(检查本文中描述的 ML 模型— 用 Keras 和 TensorFlow 报告时间执行预测):

使用 CURL 执行预测请求:
curl -d '{"instances": [[ 3.19179609, 2.05277296, -0.51536518, -0.4880486, -0.50239337, -0.50629114, -0.74968743, -0.68702182, 1.45992522]]}' \
-X POST [http://localhost:8501/v1/models/model_report_exec_time:predict](http://localhost:8501/v1/models/model_report_exec_time:predict)
响应返回预测值:424.9289
资源:
- 带有源代码的 GitHub 回购
- 用 Keras 和 TensorFlow 报告时间执行预测
- TensorFlow 与 Docker 一起发球
用 Python Flask 发布机器学习 API
描述如何通过 Python Flask REST API 向外界公开机器学习模型的一组指令

Source: Pixabay
正如 Flask 网站上所说,Flask 很有趣,也很容易设置。这是事实。这个 Python 的微框架提供了一种用 REST endpoint 注释 Python 函数的强大方法。我使用 Flask 来发布 ML 模型 API,以供第三方业务应用程序访问。
这个例子基于 XGBoost。
为了更好地维护代码,我建议使用一个单独的 Jupyter 笔记本,ML 模型 API 将在那里发布。连同烧瓶 CORS 一起导入烧瓶模块:
from flask import Flask, jsonify, request
from flask_cors import CORS, cross_originimport pickle
import pandas as pd
在皮马印第安人糖尿病数据库上训练模型。CSV 数据可以从这里下载。要构建 Pandas 数据框变量作为模型预测函数的输入,我们需要定义一个数据集列数组:
# Get headers for payload
headers = ['times_pregnant', 'glucose', 'blood_pressure', 'skin_fold_thick', 'serum_insuling', 'mass_index', 'diabetes_pedigree', 'age']
使用 Pickle 加载先前训练和保存的模型:
# Use pickle to load in the pre-trained model
with open(f'diabetes-model.pkl', 'rb') as f:
model = pickle.load(f)
做一个测试运行并检查模型是否运行良好总是一个好的实践。使用列名数组和数据数组(使用新数据,即训练或测试数据集中不存在的数据)构建数据框架。调用两个函数— model.predict 和 model.predict_proba 。通常我更喜欢使用 model.predict_proba ,它返回描述可能性为 0/1 的概率,这有助于解释基于特定范围(例如 0.25 到 0.75)的结果。Pandas 数据帧由样本有效载荷构成,然后执行模型预测:
# Test model with data frame
input_variables = pd.DataFrame([[1, 106, 70, 28, 135, 34.2, 0.142, 22]],
columns=headers,
dtype=float,
index=['input'])# Get the model's prediction
prediction = model.predict(input_variables)
print("Prediction: ", prediction)
prediction_proba = model.predict_proba(input_variables)
print("Probabilities: ", prediction_proba)
烧瓶 API。确保您启用了 CORS,否则 API 调用将无法在另一台主机上工作。在要通过 REST API 公开的函数之前写注释。提供一个端点名和支持的 REST 方法(在这个例子中是 POST)。从请求中检索有效载荷数据,构建 Pandas 数据帧并执行 model predict_proba 函数:
app = Flask(__name__)
CORS(app)[@app](http://twitter.com/app).route("/katana-ml/api/v1.0/diabetes", methods=['POST'])
def predict():
payload = request.json['data']
values = [float(i) for i in payload.split(',')]
input_variables = pd.DataFrame([values],
columns=headers,
dtype=float,
index=['input']) # Get the model's prediction
prediction_proba = model.predict_proba(input_variables)
prediction = (prediction_proba[0])[1]
ret = '{"prediction":' + str(float(prediction)) + '}'
return ret# running REST interface, port=5000 for direct test
if __name__ == "__main__":
app.run(debug=False, host='0.0.0.0', port=5000)
响应 JSON 字符串被构造并作为函数结果返回。我在 Docker 容器中运行 Flask,这就是为什么使用 0.0.0.0 作为它运行的主机。端口 5000 被映射为外部端口,这允许来自外部的呼叫。
虽然在 Jupyter notebook 中直接启动 Flask interface 是可行的,但我建议将其转换为 Python 脚本,并作为服务从命令行运行。使用 Jupyter nbconvert 命令转换为 Python 脚本:
jupyter nb convert—to python diabetes _ redsamurai _ endpoint _ db . ipynb
带有 Flask endpoint 的 Python 脚本可以通过 PM2 进程管理器作为后台进程启动。这允许将端点作为服务运行,并在不同的端口上启动其他进程。PM2 开始命令:
PM2 start diabetes _ redsamurai _ endpoint _ db . py

pm2 监视器帮助显示正在运行的进程的信息:

从邮递员通过由 Flask 服务的端点的 ML 模型分类 REST API 调用:

更多信息:
发布您自己的 Python 包
打包 Python 代码的实用指南
假设你有一段很好的 Python 代码;几个相关的小函数,或者一个只有几百行代码的中型模块。并说你最终会一次又一次地使用这段代码;也许您一直将它复制到不同的项目或存储库中,或者您一直从您在特定路径中设置的某个专用实用程序代码文件夹中导入它。
这很自然——我们都在编码时不断积累这些小的个人工具,Python 可能比一般的编程语言更支持和鼓励这一点——拥有这些代码片段感觉很好。
但是,如果您可以轻松地导入您开发的一个小工具,而不用到处复制它,并维护它的一个更新版本,这不是很好吗?让它在不同的环境、机器和上下文中可用,而不依赖于特定的文件或路径?甚至能够对它进行版本化,并让使用它的代码清楚地反映这种依赖性?如果其他人也能使用它不是更好吗?
是的。是的,会的。🐃
当然,这个概念并不新鲜。这就是为什么我们通常在编程语言中有模块、包和库,以及为什么我们在 Python 中有这些。这是驱动 Python 非凡的能力、可用性和受欢迎程度的一部分;我们都可以通过简单的pip install和import获得 beautifulSoup 的 html 解析或 pandas 的数据帧处理的惊人能力。
最酷的部分是,Python 让每个人都可以轻松地在官方 Python 包索引 PyPI 上编写和发布自己的代码包,并让它们像sklearn、requests或delorean(所有这些都是非常有用和流行的 Python 包)一样容易获得。你绝对应该这么做,因为:
- 让别人使用你的代码是很有趣的,即使只是少数人;在工作、社区活动或求职面试中分享和展示是一件很酷的事情。
- 它通过迫使您组织和记录代码,并将其公开给同行评审,使您的代码变得更好。
- 最后,它通过贡献一些缺失的功能而使社区受益。您会惊讶地发现,有多少人会发现您高效地将 HTTP 头序列化为 JSON 或您为轻松验证输入 MongoDB 查询文档而创建的 decorator 非常有用。
现在,我已经满怀希望地说服了您,让您把放在那里的那个很酷的旧 Python 模块上的灰尘吹掉,并把它做成一个小 Python 包,让我们开始吧。

Figure 1: Python dust 🐍
在接下来的文章中,我将尝试向您介绍我决定打包 Python 代码的最小过程,基于我发布几个小软件包(例如 pdpipe 、 skift 和 cachier )到全球 PyPI 服务器和几十个我曾经发布过的初创企业的私有 PyPI 服务器我知道这并不完美,但这是我所知道的,所以只能这样了。
第一步:都在名字里
让我们从为您的包选择一个名称开始。在我看来,一个好的名字足够短,很容易在一个pip install或import声明后随意键入(虽然我们现在有自动完成功能),但足够信息丰富,人们可以理解,或者至少在安装包后记得它是关于什么的。
处理 HTTP 请求的requests,处理日期和时间的delorean,以及提供机器学习框架的sklearn,都是很好的例子。在为我的 pandas pipelines 包( pdpipe )选择名称时,我试图遵循这个例子,它建立在这样一个事实上,即pandas通常以更短的别名pd和我的缓存包( cachier )导入。
不过,老实说,这是例外,而不是规律。流行的 Python 包有类似于pandas、keras、django、boto、jinja、flask和pytorch的名字,然而我们都记得它们的名字,所以你也可以使用任何短的和可发音的名字(例如,尽管 skift 是“SciKIt-learn wrappers for fast text”的一个很好的缩写,但我主要关心的是可发音性)。这也是一个好主意,以确保它工作良好,所有小写。
为了这个帖子,假设你和chocobo一起去的。🐤
步骤 2:基本的包结构
现在,让我们通过几个简短的步骤,为您留下一个通用的包结构:
- 用你的包的确切的名称创建一个 Github 库。不要风格化或骆驼案件。然后,在本地克隆它。
- 在存储库中创建一个文件夹,使用您包的确切名称;这是保存软件包代码的文件夹。这个是规范,你只需要记住外层的
chocobo文件夹(在我们的例子中)是存储库的文件夹,而内层的chocobo文件夹是包的文件夹。 - 把你的模块,以及它使用的任何其他模块,放在内部的
chocobo文件夹中。如果缺少一个文件,则添加一个__init__.py文件。 - 将软件包用户将直接调用的重要对象(通常是函数)从各自的模块导入到
__init__.py文件中。这些是包名称空间下可用的函数、类和变量。软件包的 API,如果你愿意的话。 - 不是特定于包的,但是您确实应该在存储库的根目录中包含一个
.gitignore文件。这是一个优秀的。
所以我们现在有了一个结构,我们可以在其中添加不同的文件来组成一个包;内部文件夹将保存包的代码,外部文件夹将保存辅助打包文件和其他与存储库相关的文件。
因此,假设您的原始模块chocobo.py看起来像这样:
那么,您的新存储库文件夹应该如下所示:
chocobo/
chocobo/
__init__.py
chocobo.py
.gitignore
其中__init__.py看起来像这样:
这意味着在我们完成之后,我们最先进的chocobo软件包的用户将能够这样使用它:
你知道要点了。
哈哈!因为我刚刚附上了一堆 GitHub gists好了,继续前进!
第三步:许可证
用一些公共许可证发布你的代码是一个好主意;虽然我们在这里讨论的是将您的代码作为开源发布,但是您可能希望在保留版权的同时允许重用您的代码,或者您可能希望有人扩展您的代码以保持衍生代码的开放,而许可证可以帮助您非常容易地做到这一点。
对于一个你并不真正关心其他人会怎么做的宠物项目来说, MIT 许可证可能是一个很好的主意,但是你可以前往choosealicense.com获取 GitHub 和开源社区关于这个主题的一些很好的建议。
无论你选择哪种许可证,都比根本不选择许可证要好。没有许可证就发布代码在很多情况下等同于根本不发布;如果你不明确地表明你对代码的权利,大多数公司不会冒使用它可能带来的法律后果的风险,因此它的许多潜在用户会考虑其他选择。
选择许可证后,在您的存储库中创建一个LICENSE文件(不需要文件扩展名),并用所选许可证的精确文本填充它。
步骤 4:安装文件
我们现在将创建 Python 打包工具(在我们的例子中是setuptools)依赖的基本文件;setup.py。setup.py包含构建和分发包时使用的实际指令。
这里有一个模板,您可以从它开始(不要担心,我们会检查所有内容):
所以,我们从导入setuptools开始。这是一个非常有用的包,旨在促进 Python 包的轻松分发,尽管它没有包含在标准库中(不像它的功能较弱的替代品distutils),但它是当今 Python 包分发的事实上的标准,也是您应该使用的标准。我们将只使用setuptools 包中的两个函数:setup和find_pacakges。
在导入setuptools 之后,调用setup() 函数之前,我们将把README.md 文件的内容读入一个方便的全局变量README。
现在,剩下的就是用下面的参数调用setuptools.setup() 函数:
author:提供姓名即可。author_email:你的邮箱。name:包名。“乔科博”,在这种情况下。license:字符串“MIT”,或者您选择的任何其他许可证的名称。- 你的包裹的简短描述。比如这里:“chocobo 是美味 choco bo 食谱的 python 包”。
version:表示软件包当前版本的字符串。我希望在以后的文章中讨论一种稍微优雅一点的处理包版本控制的方法,但是现在你可以从一个你想发布新版本时手动增加的数字开始。通常的做法是在版本号前面加上字母 v,,因此v1是您第一个版本的合适版本字符串,但是我建议您将v0.0.1视为一个等价的版本字符串,并使用这种格式。同样,在以后的文章中会更多地讨论这意味着什么。long_description:自述内容在此处。事实上,这确实是对该软件包的一个很长的描述。这是 PyPI 上的包的页面内容(例)。url:指向包主页的 URL。假设您没有专门的站点,那么您的存储库的 URL 是一个很好的目标。packages:啊哈,这里我们第二次用setuptools!该参数接受所有要构建和分发/安装的包的名称数组(取决于调用的命令)。从技术上来说,你可以把["chocobo"]放在这里,但是更好的做法是推广并使用这个能够处理更复杂的包和存储库结构的setuptools函数。它需要两个可选参数,where和exclude,这里我们都忽略了。结果是where被设置为安装文件所在的目录,并且不排除任何子目录,这对于大多数情况来说已经足够好了。python_requires:如果你的包支持所有 Python 版本,你可以丢弃这个参数。因为很可能不是这样,所以您应该选择一个合适的值。从技术上来说,我不会宣传对一个我没有测试过的版本的支持,但是现在我们可以有把握地假设:
(1)如果你正在使用 Python 2,那么你就是在使用 python 2.7,并且(a)你是一只罕见的奇妙的鸟(b)除了 Python 2.7 之外,你不需要支持任何东西,所以你可以用">=2.7"字符串提供这个参数。另外,请继续学习 Python 3;时钟确实在滴答作响。
(2)如果你使用的是 Python 3,那么你可以支持任何比你用来开发包的版本更大或相同的 Python 版本。所以如果你正在使用 Python 3.5,那么">=3.5"应该没问题。install_requires:所有非标准库包依赖项都在这里。例如,如果我们的chocobo包同时依赖于requests包和pytz包,我们将用下面的字符串数组来提供这个参数:["requests", "pytz"]。- 你的包裹很快就会和成千上万的其他包裹一起出现在 PyPI 上。为了帮助分类和区分,包的作者可以给 PyPI 提供一个分类器的列表来对每个版本进行分类,描述它为谁服务,它可以在什么系统上运行,以及它有多成熟。然后,社区成员可以使用这些标准化的分类器,根据他们想要的标准来查找项目(尽管我不确定实际上是谁在做这件事🦆).所有可能的分类器的列表可以在这里找到;建议你先从这些入手:
-【开发状态::3—Alpha】
-【License::OSI Approved::MIT License】
-【编程语言::Python】
-【编程语言::Python::3.5】
-【编程语言::Python::3.6】
-【编程语言::Python::3.7】
-【主题::软件开发::库】
-【主题::软件开发::库::Python 模块】
-【目标受众::开发者】
好吧,那么!我们结束了!☃️

Figure 2: Ace Ventura after choosing his trove classifiers
步骤 5:构建分发文件
Python 包被构建到构建发行版文件中,然后这些文件被上传到服务器——通常是全球 PyPI 服务器——每个人都可以从那里下载它们。
我不会详细讨论不同的可能的发行版格式(你可以在这里阅读更多关于它们的内容和这里,但是我们将使用标准方法并构建两个文件:一个源发行版文件——它基本上是包代码的存档——和一个轮构建发行版文件。
首先,确保你安装了最新版本的setuptools和wheel:
python3 -m pip install --user --upgrade setuptools wheel
现在,要构建发行版文件,只需在您的setup.py所在的存储库的根文件夹中运行以下命令:
python setup.py sdist bdist_wheel
这里发生的事情是,我们告诉 Python 解释器运行 setup.py Python 脚本,并向它发送两个参数,命令它构建一个源分发文件( sdist 参数)和一个轮构建分发文件( bdist_wheel 参数)。
运行该命令时,将在调用目录下创建三个文件夹:build、dist和chocobo.egg-info。这三个都应该被你的.gitignore文件忽略。如果这些目录已经存在——例如,在之前运行该命令时——最好通过运行rm -rf build dist来删除它们,因为dist下的任何有效包文件都将被上传。
我们将要上传的两个文件位于dist文件夹中:chocobo-0.0.3-py-none.any.whl(构建发行版;一个wheel文件)和chocobo-0.0.3.tar.gz(源码分发;一个 gzipped tar存档)。确保它们已成功创建,我们将继续上传您的包!
步骤 6:上传您的包
现在剩下的就是把你的包上传到全球 PyPI 服务器!然而,要做到这一点,你必须在 PyPI 网站注册。按照注册步骤,记下您选择的用户名和密码。
如果你想在上传软件包到全球 PyPI 服务器之前测试它们的上传,你可以另外在测试 PyPI 网站注册一个用户。
现在,我们将用来上传我们的包的 Python 包将在一个名为.pypirc的文本文件中寻找 PyPI 用户名和密码——以向 PyPI 服务器进行认证,该文件通常位于您的主文件夹中。创建它,并如下所示填充它(可选的testpypi部分):
[distutils]
index-servers =
pypi
testpypi[pypi]
username: teapot48
password: myPYPIpassword[testpypi]
repository: [https://test.pypi.org/legacy/](https://test.pypi.org/legacy/)
username: teapot48
password: MYtestPYPIpassword
我们将按照最新推荐的 方式上传文件到 PyPI 服务器,并使用twine上传 Python 包的实用程序,而不是旧的python setup.py upload方法。只需运行:
twine upload dist/*
如果您想从使用测试 PyPI 服务器测试上传过程开始,只需运行twine upload — repository testpypi dist/*。
在任何情况下,当您的.whl文件被上传时,您应该会看到一个进度条,当.tar.gz档案被上传时,您会看到第二个进度条,之后上传将会完成。
恭喜你!你现在可以在 PyPI 官方网站上看看你的全新 Python 包页面,让所有人都看到!这里有一个例子:

Figure 3: An example for a package page on the PyPI website
就是这样!你完了!各地的人们现在都可以运行pip install chocobo并庆祝你的 Python 包的惊人创造!😃 ☃️
最后的话
我希望这篇文章有助于你理解如何构建和发布你的第一个 Python 包。如果你用它来打包一些代码,请在这里评论或者联系我让我知道!
还有一个官方的——简明的——打包 Python 项目的教程,可以在这里找到。我试图通过添加更多的细节和我在构建一些小型开源 Python 项目时获得的一些个人最佳实践来改进它。
最后,我希望在未来的一些帖子中触及一些其他相关主题:编写好的自述文件、Python 包的基本测试(我在这里写了一些)和包版本控制。干杯!
脚注
- 主要原因 twine 是 Python Packaging Authority 推荐的将包上传到 PyPI 的方式(以我的理解)——我将该推荐带到这篇博文的原因——是它“使用一个经过验证的连接通过 HTTPS 安全地验证你到 PyPI 的身份,而不管底层的 Python 版本如何”,这是一个非常好的理由——在我看来——更喜欢它并推荐给初学者。在缠绕文档的相应章节中阅读更多信息。
泵起来:使用数据科学预测水泵状况
现实世界中的数据科学
如何使用 Python 中的数据科学和机器学习来预测坦桑尼亚的水泵运行情况?

Pump image courtesy of Flickr user Christopher Jensen
DrivenData 的比赛以建设一个更美好的世界为背景。 Pump It Up 着眼于确定坦桑尼亚水泵的功能。在数据方面,数据是 38 个特征的逗号分隔值(CSV)文件,目标是预测三类中的一类(功能性、非功能性、功能性需要修复)。
代码并不太花哨,但是提供了一个很好的基线方法,可以对其进行改进,稍后会有更多的介绍。简而言之,你将对这个过程的每一步都有所了解,并在 70 秒内获得一个 f1 分数!
这篇文章将涵盖:
- 解读竞争
- 探索性数据分析
- 型号选择
- 估价
- 结论和提示
解读竞争
数据集的大小是合理的——59,400 个条目,所以我们不应该觉得有义务在我们的模型中使用所有 38 个特征!
F1 得分是评估一项技术表现如何的指标。这在数据不平衡时特别有用,因为类不是均匀分布的。

A table indicating the data imbalance in the training set.
F1 分数是一个调和平均值,考虑了精确度和召回率。数学上,它看起来像这样:
Precision 询问我们的模型在预测正面事件时的正确率,即:

另一方面,回忆询问我们的模型对积极事件的预测有多好:

F1 分数结合了这两个指标,如下所示:

探索性数据分析
我们已经看到了明显的阶级不平衡,但是我们现有的信息类型呢?当我们将它输入到模型中时,我们如何确保它得到适当的处理?
哪些栏目是多余的?现在,让我们忽略
‘date_recorded’, ‘gps_height’, ‘longitude’, ‘latitude’, ‘wpt_name’, ‘num_private’, ‘subvillage’, ‘lga’,’ward’, ‘recorded_by’, ‘extraction_type_group’, ‘extraction_type’, ‘scheme_name’, ‘management’, ‘waterpoint_type_group’, ‘source’, ‘source_class’, ‘quantity_group’, ‘quality_group’, ‘payment_type’
这给我们留下了剩余的 19 个特征:
- amount_tsh —总静压头(水点可用的水量
- 出资人——谁为油井出资
- 安装者——安装油井的组织
- 区域—地理位置
- 区域代码-地理位置(编码)
- 地区代码—地理位置(编码)
- 人口—油井周围的人口
- 公开会议—对/错
- scheme _ management——谁在运营供水点
- 允许—如果水点是允许的
- 建造年份——供水点建造的年份
- construction _ type 水点使用的提取类型
- construction _ type _ group 水点使用的提取类型
- construction _ type _ class 水点使用的提取类型
- 管理组—如何管理水点
- 付款——水的价格
- 水质——水的质量
- 数量——水的数量
- 水点类型—水点的种类
接下来,任何包含类似字符串/单词的信息的列都需要更改为数值,我们必须确保没有隐含的邻近性。为了实现这一点,我们使用了一个叫做一次热编码的过程。这将一列中所有不同的可能值(n)展开为 n 列,用 1 或 0 表示是否满足特定值。例如,quantity 列有五个可能的值:干旱、足够、不足、季节性和未知。一次热编码前后该特性的快照如下所示:

A table showing how the quantity column is transformed through one-hot-encoding.
funder 和 installer 列有大量的唯一值,因此我们可以使用一个函数将这些列扩展到前 n 个值(将 n 指定为函数中的一个参数)。不在这些值范围内的任何值都被标记为其他。这允许我们减少通过任何模型输入的列数,同时仍然表示大量的数据。
最后,状态组的标签需要改为数值,以便模型理解输入。将它们转换成有序类还是一次性编码,这是一个很难决定的问题。在这种情况下,我们使用的模型预期有一列,所以我们用序数转换来凑合,但是在某些情况下,这种隐含的接近性可能不合适。
这些步骤通过下面的函数进行总结。通过创建一个函数,我们可以调用它并使它适应任何版本的数据。状态组标签到数值的转换应该单独进行,因为这些仅针对训练数据。
Code for data pre-processing.
型号选择
当考虑使用哪个模型时,我们首先缩小到适合分类而不是回归问题的模型。在这种情况下,我们尝试六种不同的方法。
- 随机森林是一系列决策树,其中的叶节点表示预测的类别。
- XGBoost 是一种复杂的决策树算法,它使用梯度推进(通过查看变化率来最小化误差)。
- 决策树使用整个数据集为每个特征创建决策过程。
- k-最近邻查找与当前数据点最近的 k 个数据点,并采用多数分类。
- 支持向量机将创建平面作为区分类别的边界,试图最大化类别之间的距离。
- 逻辑回归也创建了决策边界,但却最小化了对数似然函数。
实现模型相对容易,导入相关的类,然后输入数据,接受初始探索的默认参数。
Code for model implementation.
估价
为了评估模型性能,我们留出了训练数据的子集,称为验证集。这使我们能够预测这部分数据的类别,并直接将预测结果与已知类别进行比较。对于每个模型,我们可以打印分类报告,该报告为我们提供分类指标,包括精确度、召回率、F1 分数和准确度。我们在验证集上实现了这一点,使用下面这条线比较真实标签和预测标签
print(classification_report(y_test,clf.predict(X_test)))
如前所述,在这种情况下,由于数据不平衡,准确性不是一个合适的指标,因此我们使用 F1 得分。

Graph showing the respective metrics for each type of model (0–1).
评估模型时的另一个考虑因素是运行时间。这与计算步骤成比例,并导致性能和速度之间的折衷。

Graph showing the respective run times for each type of model in seconds.
考虑到图形的组合结果,在给定训练数据的情况下,使用随机森林或决策树似乎是最合适的。但是,当模型预测测试数据的结果时,结果可能会有所不同。
结论和提示
对于这种应对挑战的方法,随机森林和决策树模型表现最佳,但是,通过超参数调整(查看设置每个模型的参数),所有模型都可以表现得更好。这个项目的所有代码和数据可以在这个 GitHub 文件夹中找到。
- 不要觉得必须使用数据集中给定的所有特征/变量。
- 尝试几种不同的型号。
- 考虑哪种指标最适合评估模型。
- 一旦你完成了一些初步的探索,通过创建函数来优化代码结构。
直到下一次…随时与我分享任何意见/问题!
参考
DrivenData。(未注明)。抽水:地下水位的数据挖掘。检索自https://www . driven data . org/competitions/7/pump-it-up-data-mining-the-water-table/
达尔,奥,埃纳霍罗,p,格林,s .,&斯塔克,A. (2019,10 月)。冲刺二队一队。从 https://github.com/shiaoligreen/practical-data-science取回
向上泵:预测坦桑尼亚的水点功能

我认为我们(或者至少我知道我肯定这样做)认为理所当然的东西是我们在家里饮用和使用的容易获得的水。我在佐治亚州东北部一个类似养鸡场的小地方长大,现在住在巴尔的摩,在这个国家能持续获得干净且随时可用的水确实令人惊讶。
直到最近,我参加了 Lambda School 举办的名为 Pump It Up 的 Kaggle 竞赛,我才真正思考过我的生活方式的含义。我们获得了由 Taarifa 和坦桑尼亚水利部收集的数据,以便预测供水点故障的可能性。该比赛由 DrivenData 主办,这是一个使用数据科学的社会公益平台。如果你有时间,一定要去看看。
比赛的目标是从训练数据中预测这些水点的状态。在我上面的地图里,可以看到水点的整体分布。没有功能的供水点几乎和有功能的供水点一样多,尽管这些供水点中有很小一部分被确定为需要修复。这意味着人们仍然想使用它们/它们还没有被另一个供水点取代,但是人们根本就无法获得这些水。
一点数据探索
我一开始只是想知道这些供水点是什么时候建成的。不幸的是,丢失的数据数量惊人。我收集了这些年来得到一个简单的图表,这就是结果。

如何处理这些缺失的数据?普遍的共识是估算数据,在这种情况下,您使用一些函数/模块根据您选择的参数(如平均值或中值)对数据进行有根据的猜测。然而,我选择了不同的做事方式。
我在数据中列出了所有可用的建造年份。然后,我根据这些年的计数建立了一个概率分布。因此,出现频率更高的年份出现的概率更高。在使用一些 python-fu 完成这一任务后,是时候规范化数据了。我们希望对概率分布的权重进行归一化,以便在下一步过程中将它们用作百分位数:从归一化概率分布的随机抽样中生成新的建筑年份。
在这样做之后,并采取一个新的 bin 的建设年,我的图表和信息的保真度,大大提高。

查看生成的数据,我们可以看到,在过去十年中,供水点建设出现了相当大的热潮。这种方法将我的模型从 53%的预测准确率提高到大约 75%。
我们还可以查看哪些地区的用水量增长/需求最大。

创建新特征
然后我就可以记录下水点最后一次观测的日期,并从新生成的数据中减去建造的年份,我称之为水点的年龄。这使我的准确率达到了 80%。
人口数据:将一个无关紧要的问题转变为模型焦点
人口中也有大量缺失数据。为此,我很乐意收集坦桑尼亚的人口普查数据,并生成如上的另一个概率分布来估算我的缺失值,但不幸的是,我只能替换为中位数(向低端人口倾斜的数据),因为我们只有四天时间来建立模型和进行可视化。输入缺失值的人口中值使我的准确率达到 82%(四舍五入)。人口最终成为我的模型的焦点。我所有的其他特征和直觉都建立在对人群的假设之上。因此,您可能会理解为什么我想要更准确地表示缺失的人口数据。
什么决定了水点故障的可能性。
在我的模型中,地理坐标(纬度/经度)、人口和对水点功能的估计之间存在令人难以置信的关系。
随着人口的增长,需要更多的水。这导致使用更多的水。人口成为水量的决定性值,随着水量的减少,我们开始看到水点失效的可能性更大。水点年龄的工程特征增加了我们的准确性,因为它允许模型了解到泵已经存在的时间越长,它不工作/由于上述原因需要修理的可能性就越大。这就像一个强化变量。

我觉得我的功能可以通过工程和对所有功能的更多研究来继续改进,也许可以更好地处理我的地理数据,但时间对任何人都是静止的。幸运的是,比赛在 Lambda 学校之外还有四个月的时间,所以我肯定会在业余时间继续努力改进我的模型。
同时,感谢您的阅读。
正如我最喜欢的库德的罗格里洞穴里的居民所说,“生活和喝酒!”
下次见。
提升音量:Docker 中的数据
学习足够有用的 Docker 的第 6 部分
这篇文章是关于在 Docker 中使用数据的。其中,我们将重点关注 Docker 卷。如果您还没有,请查看本系列的前几篇文章。我们讨论了 Docker 概念、生态系统、 Dockerfiles 、瘦身图片和流行命令。

Spices
把贯穿这些文章的食物隐喻推到断裂点,我们来对比一下 Docker 里的数据和香料。就像世界上有很多香料一样,用 Docker 保存数据也有很多方法。

快速参考:本指南适用于 Docker 引擎版本 18.09.1 和 API 版本 1.39 。
Docker 中的数据可以是临时的,也可以是持久的。让我们先检查一下临时数据。
临时数据
数据可以通过两种方式临时保存在 Docker 容器中。
默认情况下,由容器内的应用程序创建的文件存储在容器的可写层中。你不需要设置任何东西。这是又快又脏的方法。只要保存一个文件,然后继续你的工作。然而,当你的容器不复存在时,你的数据也将不复存在。
如果您希望 Docker 保存临时数据的性能更好,您还有另一个选择。如果您不需要您的数据在容器的生命周期之后继续存在,那么 tmpfs 挂载是一种使用主机内存的临时挂载。tmpfs 装载具有更快读写操作的优势。
很多时候,您会希望数据在容器消失很久之后仍然存在。您需要持久化您的数据。
不变数据
有两种方法可以将数据保存到容器寿命之外。一种方法是将文件系统绑定挂载到容器。通过绑定挂载,Docker 之外的进程也可以修改数据。

From the Docker Docs
绑定装载很难备份、迁移或与其他容器共享。卷是保存数据的更好方式。
卷
卷是位于任何容器之外的主机上的文件系统。卷由 Docker 创建和管理。体积为:
- 坚持的
- 自由浮动的文件系统,独立于任何一个容器
- 可与其他容器共享
- 有效的输入和输出
- 能够在远程云提供商上托管
- 可加密
- 可指名的ˌ可命名的
- 能够用容器预先填充它们的内容
- 便于测试
这是很多有用的功能!现在让我们来看看你如何制作一个卷。

Volumes
创建卷
可以通过 docker 文件或 API 请求来创建卷。
以下是在运行时创建卷的 Dockerfile 指令:
VOLUME /my_volume
然后,当创建容器时,Docker 将使用指定位置已经存在的任何数据创建卷。注意,如果使用 docker 文件创建卷,您仍然需要在运行时声明卷的挂载点。
还可以使用 JSON 数组格式在 Dockerfile 文件中创建一个卷。关于 Dockerfiles 的更多信息,请参见本系列的这篇早期文章。
还可以在运行时从命令行实例化卷。
卷 CLI 命令
创造
您可以使用docker volume create —-name my_volume创建独立卷。
检查
用docker volume ls列出 Docker 卷。
可以用docker volume inspect my_volume检查卷。
去除
然后你可以用docker volume rm my_volume删除这个卷。
悬挂卷是容器未使用的卷。您可以使用docker volume prune移除所有悬挂的卷。Docker 会警告你,并在删除前要求确认。
如果卷与任何容器相关联,则在删除容器之前,您不能删除它。即使这样,码头工人有时也不会意识到集装箱已经不见了。如果发生这种情况,你可以使用[docker system prune](http://docker-compose down --volumes)来清理你所有的 Docker 资源。那么您应该能够删除该卷。

Where your data might be stored
与 **--mount** 和 **--volume**一起工作
您将经常使用标志来引用您的卷。例如,要在创建容器的同时创建卷,请使用以下命令:
docker container run --mount source=my_volume, target=/container/path/for/volume my_image
在过去(即 2017 年之前)😏--volume旗很受欢迎。最初,-v或--volume标志用于独立容器,而--mount标志用于码头工人群。然而,从 Docker 17.06 开始,您可以在所有情况下使用--mount。
--mount的语法有点冗长,但由于几个原因,它比--volume更受欢迎。--mount是您可以使用服务或指定卷驱动程序选项的唯一方式。用起来也比较简单。
您将在现有代码中看到许多-v。注意--mount和--volume的选项格式不同。你通常不能用一个--mount替换现有代码中的一个-v就完事了。
最大的区别是-v语法将所有选项组合在一个字段中,而--mount语法将它们分开。让我们看看--mount的行动吧!

Easy enough to mount
**--mount** —选项是键值对。每一对的格式如下:key=value,一对和下一对之间用逗号隔开。常见选项:
type—安装类型。选项有[bind](https://docs.docker.com/storage/bind-mounts/)、[volume](https://docs.docker.com/storage/volumes/)或[tmpfs](https://docs.docker.com/storage/tmpfs/)。我们都是关于volume。source—挂载的来源。对于命名卷,这是卷的名称。对于未命名的卷,将省略此选项。该键可缩短为src。destination—文件或目录在容器中挂载的路径。该键可缩短为dst或target。readonly—将卷挂载为只读。可选。没有价值。
这是一个有很多选项的例子:
docker run --mount type=volume,source=volume_name,destination=/path/in/container,readonly my_image

Volumes are like spices — they make most things better. 🥘
包装
关键音量命令概述
docker volume createdocker volume lsdocker volume inspectdocker volume rmdocker volume prune
docker run --mount my_options my_image中--mount标志的常用选项:
type=volumesource=volume_namedestination=/path/in/containerreadonly
现在,您已经熟悉了 Docker 中的数据存储,让我们看看您的 Docker 之旅可能的后续步骤。

Next steps
更新,我最近发表了一篇关于 Docker 安全的文章。看看它,并了解如何保持你的容器安全。😃
如果您还没有阅读本系列中关于 Docker 概念、 Docker 生态系统、Docker 文件、瘦图像和命令的文章,也可以查看一下。
如果你正在寻找另一篇关于 Docker 概念的文章来帮助巩固你的理解,请点击查看 Preethi Kasireddy 的伟大文章。
如果你想更深入,可以去看看奈杰尔·波尔顿的书Docker Deep Dive(一定要拿到最新版本)。
如果你想边学边做大量的建筑工作,可以看看詹姆斯·特恩布尔的《码头工人手册》。
我希望您发现本系列对 Docker 有所帮助。如果你有,请在你最喜欢的论坛或社交媒体渠道上与他人分享,这样你的朋友也可以找到它!😃
我写了一些关于用 Kubernetes 编排容器的文章,你可以在这里阅读。我写关于 Python、数据科学、人工智能和其他技术主题的文章。如果你对这些感兴趣,请关注我。
感谢阅读!👏
小狗和 Python:分析地理空间数据
通过使用 Geopandas、Shapely、Pyplot 和 Folium 组合多个形状文件和数据源来可视化西雅图宠物密度
所以你对有地理空间成分的数据感兴趣?酷!我也是!
当我开始我的第一个植根于地理空间数据的项目时,我并没有意识到我所面临的挑战的程度——并没有超出我的能力范围,但对于我当时的情况来说,这绝对是一个难题。

photo credit: Chris Martin on Flickr
我快速的谷歌搜索让我,或许还有你,直接找到了[geopandas](http://geopandas.org/)。

This plus another 600+ lines of package conflicts… including NumPy.
我最初与geopandas安装有很多包冲突,包括numpy和我项目环境中的几乎所有东西。然后我按照他们的指示在conda-forge频道的新环境中安装了geopandas。非常管用!
在您的新环境中,您还需要conda install descartes和matplotlib来获得全部功能。
在今天的例子中,我正在为我的下一步行动做一些研究:在西雅图哪里可以找到密度最高的宠物?当我在未来的社区里散步、蹦蹦跳跳、做瑜伽时,我想向所有的宠物抛媚眼。为此,我从西雅图市的公开数据中下载了关于宠物执照的文件和关于邮政编码、市政边界和海岸线的形状数据(这样我们就可以看到我们所了解和喜爱的西雅图的轮廓)。

Photo Credit: Joshua T. Beck [CC BY-SA 4.0] on wikimedia commons
要点 1:shape file 不仅仅是一个文件
当我下载并解压缩我的第一个 shapefile 时,我只将.shp文件移到我的目录中,心想,“也许这些其他文件可以在不同的程序中工作。”但是没有。
即使你用来读取形状文件的任何包可能只需要一个.shp文件路径,它们需要整个文件集在同一个目录下才能工作:
.shp—实际形状文件—这是你的形状的几何图形.shx—形状索引 x 格式—帮助快速浏览形状.dbf—这是关于形状的其他信息(同样,这是必需的,即使您不需要它。关于每个形状的参考信息(如名称)通常很有帮助。)
有时也有其他文件。查看这篇文章了解更多信息。现在,我不用太担心我想要或需要哪个,我只需要将它解压到我的目标目录中。然后你就知道你的数据源提供了什么好东西。
还要注意:所有文件必须有相同的文件名,唯一的区别是扩展名/后缀。所以如果你喜欢改文件名去掉空格,大写字母等(只是恢复完美主义的事情?),确保您更改了软件包中所有文件的名称,并注意拼写错误。
当你考虑一个 shapefile 的时候,总是要加上那个尾随的 s——shape files。
在搜索最大 pet 密度时,只需几行代码就可以让我开始使用地理数据框中的形状:
import geopandas as gpd# load zip code shape data
zip_codes_gdf = gpd.read_file('data/shapes/Zip_Codes.shp')# load seattle shape
cities = gpd.read_file('data/shapes/Municipal_Boundaries.shp')
seattle = cities.loc[cities['CITYNAME'] == 'Seattle']
seattle = seattle.reset_index()
seattle_shp = seattle.loc[0, 'geometry']# load seattle waterfront shape
waterfront = gpd.read_file('data/shapes/
City_of_Seattle_Shoreline.shp')

注意这里的seattle_shp是一个shapely Polygon,在 Jupyter 笔记本中调用它会显示该形状的图像。或者,waterfront是一个地理数据框架,因此您可以用waterfront.plot()来可视化它。跟踪对象类型将为您省去麻烦和时间!或者至少是当事情不像你期望的那样工作时的一个好的开始。
首先,我注意到西雅图开放数据上的邮政编码文件实际上有华盛顿几个县的邮政编码的形状文件。我需要把邮编范围缩小到西雅图。我很快意识到西雅图地区的邮政编码边界与城市边界不一致。
这里是geopandas对所有其他包的包装,包括shapely,派上用场的地方(即使它使安装变得很困难)。
拿走#2: Shapely 是你的朋友
为了组合来自不同来源的数据并准确获得您想要的数据,我发现 shapely 函数非常有用。方便的是,它们在GeoDataFrames上工作得很好,在每行上用一行代码进行计算(只需调用geo_df.**shapely_function**())。一些[shapely](https://shapely.readthedocs.io/en/stable/manual.html)功能在[geopandas](http://geopandas.org/)文档中有描述,但是geopandas没有包括所有 shapely 功能的文档。当 shapely docs 中描述的额外功能,而不是geopandas中描述的功能工作良好时,我感到惊喜,尤其是intersection!
在:
object.**within**(*other)* —如果对象的边界和内部仅与另一个对象的内部相交(而不是其边界或外部),则返回True。这适用于所有类型,与[**contains()**](https://shapely.readthedocs.io/en/stable/manual.html#object.contains)相反。 *
所以我试了一下。这里,zip_codes_gdf.within(seattle_shp)返回一系列的True和False,每个记录对应于GeoDataFrame中的一条记录。这个系列与.loc一起使用,创建一个新的GeoDataFrame,只包含全部在西雅图的记录(邮政编码及其形状)。
seattle_zips_within = zip_codes_gdf.loc[zip_codes_gdf.within(seattle_shp)]
接下来,我绘制了我的结果,以及西雅图的轮廓和滨水区。
fig, ax = plt.subplots(figsize=(10,10))
seattle.plot(ax=ax, color='white', edgecolor='black', linewidth=2)
waterfront.plot(ax=ax, color='blue')
seattle_zips_within.plot(ax=ax, cmap='Paired', alpha=.8)

使用within()函数,进入西雅图以外其他城市的邮政编码被排除在这个选择过程之外。如果邮政编码与城市边界一致,这个功能会很棒。不幸的是,事实并非如此,所以我继续寻找更好的邮政编码选择功能。
相交
object.**intersects**(*other*) —如果对象的边界或内部以任何方式与其他对象相交,则返回True。换句话说,如果几何对象有共同的边界或内点,则它们相交。 *
seattle_zips_intersects = zip_codes_gdf.loc[zip_codes_gdf.intersects(seattle_shp)]fig, ax = plt.subplots(figsize=(10,10))
waterfront.plot(ax=ax, color='blue')
seattle_zips_intersects.plot(ax=ax, cmap='Paired', alpha=.8)
seattle.plot(ax=ax, color='#00000000', edgecolor='black',
linewidth=2)

现在我们走上了正轨,整个西雅图都被代表了。现在我想把城市范围之外所有区域都去掉。如果我使用西雅图市范围内注册的宠物数量和这些跨境邮政编码的整个(西雅图和非西雅图)土地面积来计算宠物密度,这将低估这些西雅图社区的宠物密度。

Photo Credit: George Hodan [CC0 Public Domain] from PublicDomainPictures
所以我寻找另一个shapely函数。
路口
object.**intersection**(*other*) —返回该对象与其他几何对象的交集的表示。 *
换句话说,它返回两个对象重叠区域的形状。
seattle_zips_intersection = zip_codes_gdf.intersection(seattle_shp)fig, ax = plt.subplots(figsize=(10,10))
waterfront.plot(ax=ax, color='black')
seattle_zips_intersection.plot(ax=ax, cmap='Paired', alpha=.8,
edgecolor='gray')
seattle.plot(ax=ax, color='#00000000', edgecolor='black',
linewidth=2)

我还不确定地图上的粉红色是怎么回事,但这看起来越来越好了!随着邮政编码被修剪到西雅图内的区域,就地图绘制而言,我准备去掉标记胡德运河中心的西雅图轮廓,或者西雅图技术上结束的任何地方,取而代之的是水路和邮政编码轮廓。
准备好地图形状后,我就可以使用 pet 数据了!
第三点:Geopandas.plot()使用了 matplotlib.pyplot 中几乎所有你知道和喜欢的参数
首先,我将上一步中确定的新相交几何图形添加到我的GeoDataFrame中。注意:默认情况下,Geopandas 总是绘制“几何图形”列,以绘制您的形状或完成如上所用的基于形状的计算。您可以使用geo_object.**set_geometry**(*‘column_name')*切换哪个列geopandas将用作感兴趣的几何图形。在这种情况下,我决定用一个新的、成对的 down GeoDataFrame重新开始——只有西雅图的邮政编码及其交集几何和面积。
# add intersection geometry to my dataframe
zip_codes_gdf['intersection'] = seattle_zips_intersection# create dataframe with the seattle in-city geometries
seattle_zip_geom = zip_codes_gdf.loc[:, ['ZIP', 'intersection']]# name the geometry column 'geometry' for easy geopandas plotting
seattle_zip_geom.columns = ['ZIP', 'geometry']# calculate the in-seattle area for each zipcode and add as a column
seattle_zip_geom['area'] = seattle_zip_geom.area# drop zip codes that have no area in seattle
seattle_zip_geom = seattle_zip_geom.loc[seattle_zip_geom['area'] >
0]
为此,我添加了宠物数量和宠物密度,只使用相交面积来计算密度。
# create dataframe with Seattle zips and their respective pet counts
seattle_pets_zip = seattle_zip_geom.merge(pet_counts, on='ZIP')# add pet_density column
seattle_pets_zip['pet_density'] = (seattle_pets_zip['pets'] /
seattle_pets_zip['area'])
要根据列'pet_density'对地图进行着色,将该列设置为 plot 调用中的列名。这种基于数据值着色的地图被称为 choropleth (vocab,我希望我以前就知道……用更精确的关键字搜索帮助效果更好…)
fig, ax = plt.subplots(figsize=(10,10))
waterfront.plot(ax=ax, color='black')
seattle_pets_zip.plot(**column='pet_density'**, ax=ax, edgecolor='gray')

啊!所有的邮政编码看起来都一样!
然后,我注意到两个邮政编码(98146,98168)的宠物密度要大得多,尽管在我的地图比例下看不到它们。
为了更仔细地研究这些地点,我用folium将它们绘制在城市街道上:
import folium
import geopandas as gpddef create_geo_map(geo_df, index, location, zoom=17,
tiles='Stamen Terrain'):
"""creates a folium map with the geometry highlighted
Args:
geo_df: a geopandas dataframe (must have a column 'geometry'
index: the index of the row you want mapped
location: [lat, long] list with the center location for the map
zoom: start zoom level. Default: 17
tiles: Folium tiles / design. Default: Stamen Terrain Returns:
folium map with geometry highlighted
"""
# create geodataframe for zip code
zip_code = gpd.GeoDataFrame(geo_df.iloc[index]).T # get the geojson for the zipcode
gjson = zip_code['geometry'].to_json() # create folium map
m = folium.Map(location=location, tiles=tiles, zoom_start=zoom) # add geojson to the map
folium.GeoJson(gjson, name='geojson').add_to(m)
return m
看看这两个邮政编码:

第一个是西雅图市中心的一个街区,紧挨着图书馆,里面有一栋高层公寓。我不知道为什么这个街区有自己的邮政编码,但是好吧。我能相信这里登记了 35 只宠物。

第二个更神秘。更大的邮政编码中的这一小部分是波音机场附近的工业区。有一个超级价值,但是在谷歌地图卫星视图上没有可见的房屋(是的,我试图跟踪这 35 只宠物——你可以自由地亲自查看。目前,我的工作假设是这些是超价值工作者的情感支持动物。)

Example emotional support animal. Photo Credit: Kevin Honold, used with permission
经过进一步调查,邮政编码 98168 在未合并的国王县有一些地区,这些地址可能标有“西雅图”,但从技术上来说不在西雅图市的范围内。我想知道,在这个未合并的地区,一些非西雅图居民的宠物主人是否在该市登记了他们的宠物,而没有意识到他们在技术上不是该市居民。你可能会认为这个城市不会接受非城市地址的注册……这仍然是个谜。
无论如何,我选择放弃这两个邮政编码,以便更大的邮政编码的宠物密度更容易区分。在geopandas文档的帮助下,我还添加了图例。
# adding a legend using code from [http://geopandas.org/mapping.html](http://geopandas.org/mapping.html)
**from mpl_toolkits.axes_grid1 import make_axes_locatable**fig, ax = plt.subplots(figsize=(10,10))
**divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.1)**
waterfront.plot(ax=ax, color='black')
seattle_pets_zip_dropped.plot(column='pet_density', ax=ax,
**legend=True**, **cax=cax**);

所以现在我有了一个很好的主意,最适合和最不适合看宠物的社区!我确实有一些挥之不去的问题:如果我们把人口密度标准化呢?我们谈论的是什么类型的宠物——这张地图对于猫/狗/山羊/猪来说会有所不同吗(是的,山羊和猪在城市里注册了!)?但是,这些问题将不得不等待另一天…即将来到你附近的数据科学博客。

Are these your neighbors? Photo Credit: Kevin Honold, used with permission
分模提示:在显示图形之前,使用plt.savefig('fig_name.png')保存图像,以避免保存空白画布。例如,所有功能都包含在一个 jupyter 笔记本电池中:
fig, ax = plt.subplots(figsize=(10,10))
waterfront.plot(ax=ax, color='blue')
plt.savefig('images/seattle_zips_intersects.png');
您仍然可以显示图像并保存。
你可以在我的 GitHub repo 这里看到完整的代码。
*来自身材匀称的医生的描述。
更新:追加安装descartes和matplotlib。感谢您关注沙希德·纳瓦兹·汗!
使用 Spring Boot 将数据放入亚马逊 Kinesis 消防水管交付流
最初发表于我的个人博客。
如果你处理需要收集、转换和分析的大数据流,你肯定会听说过亚马逊 Kinesis Firehose。它是一种 AWS 服务,用于将数据流加载到数据湖或分析工具,以及压缩、转换或加密数据。
你可以使用 Firehose 将流数据加载到 S3 或红移中。从那里,您可以使用 SQL 查询引擎,如 Amazon Athena 来查询这些数据。您甚至可以将这些数据连接到您的 BI 工具,并获得数据的实时分析。这在需要实时数据分析的应用中非常有用。

在这篇文章中,我们将看到如何在 Kinesis Firehose 中创建一个交付流,并编写一段简单的 Java 代码将记录(生成数据)放入这个交付流。我们将设置 Kinesis Firehose 将传入的数据保存到亚马逊 S3 的一个文件夹中,该文件夹可以添加到一个管道中,您可以使用 Athena 查询它。仔细想想,当事情失去控制时,你真的会使你的管道变得复杂,并在将来遭受损失。建议在项目的架构阶段后退一步,分析所有管道可能导致瓶颈的地方。的确,大多数 AWS 服务都是完全托管和自动扩展的。但是,控制您的架构并以这样一种方式设计它,使管道不会成为您项目中最薄弱的结构,这不会有什么坏处。不管怎样,我们继续吧。
在 Kinesis Firehose 中创建交付流
要开始向 Kinesis Firehose 交付流发送消息,我们首先需要创建一个。为此,让我们登录 AWS 控制台,并前往 Kinesis 服务。您应该会在 Kinesis 主页上看到一个创建新的消防软管交付流的按钮。希望你找到了那个按钮。创建交付流包括四个步骤。让我们一个接一个地看看它们:
第一步:名称和来源
这里,我们指定了流的名称,以及流如何获取数据,这是源部分。为该流选择一个名称。该页面应该类似于下面的截图:

接下来,我们必须指定这个流的数据源。有两种方法可以将数据放入流中:
- 使用 AWS 提供的 Put data APIs,它可以集成到您的应用程序中,直接向流发送数据。
- 从另一个数据流传输数据。
在这个例子中,我们将使用第一个选项,直接看跌期权或其他来源。选择此选项,然后单击页面底部的“下一步”进入第二步。
第二步:流程记录
Kinesis Firehose 的众多功能之一是,它可以在将传入数据发送到目的地之前对其进行转换。在第二步中,我们可以告诉 Kinesis 它与这个流的传入数据有什么关系。现在,我们将保持一切默认。这意味着没有任何形式的转换,也没有数据格式的转换。理想情况下,您会希望将数据转换为 Parquet 格式,这样既可以压缩数据,又可以提高处理效率。保持这两个配置处于禁用状态,然后单击页面底部的 Next 按钮。
第三步:选择目的地
既然我们已经配置了 Firehose 来获取数据,现在我们必须告诉它将数据发送到哪里。有四个选项:
- 亚马逊 S3
- 亚马逊红移
- 亚马逊弹性搜索服务
- Splunk
每个目标都有自己配置。因为我们现在并不真正担心其他任何事情,只是想将数据发送到 Firehose,所以我们将数据保存到 S3,这也是默认选项。
向下滚动,您会发现提供一个 S3 存储桶来存储数据的选项。您可以选择现有的存储桶,也可以创建新的存储桶。
接下来,我们有前缀选项。所以 Firehose 写给 S3 的任何东西都会被默认分区,格式为“YYYY/MM/DD/HH”(UTC)。您当然可以覆盖它,但是您的前缀中必须至少有一个时间戳。如果你没有提供一个带有时间戳的前缀,Firehose 会自动添加一个,因为这是强制的。此外,您不能添加一部分传入数据作为前缀。所以不要打算这样设计。现在,让它为空或者给出一个随机的字符串。
类似地,你也可以给错误一个前缀。我会让它暂时为空,因为这只是一个概念验证。单击页面底部的“下一步”按钮。我们将进入流程的下一步。
第四步:配置设置
在这一步中,我们将为我们数据在 S3 中的存储方式指定一些重要的配置。我将对每一项进行解释,但我建议在此 POC 中保留它们的默认设置。
首先,我们有缓冲条件。这里有两个配置,缓冲区大小和缓冲区间隔。Kinesis Firehose 在将传入数据写入 S3 之前对其进行缓冲。将收到的每一条信息写给 S3 将会非常昂贵。因此数据被缓冲并批量写入 S3。您可以指定缓冲区大小,例如 5MB,以及缓冲区间隔,例如 300 秒。所以无论哪个先发生都会被考虑。例如,如果缓冲区在短短 10 秒内达到 5MB 的大小限制,数据将被写入 S3,缓冲区将被清除。此外,如果数据还不到 5MB,但自上次写入以来已经过了 300 秒,则缓冲区中的任何数据都将被写入 S3,缓冲区将被清除。
接下来,我们可以选择在 S3 压缩和加密数据。我们可以使用压缩算法,如 GZIP 和 SNAPPY 来压缩数据。当我们处理千兆字节的数据时,会考虑到这一点。对于此 POC,请将其禁用。我们现在也不需要任何加密。
如果有必要,我们可以启用错误日志。或许还可以添加一些标签。但是我不认为他们中的任何一个需要这个概念验证。所以让我们直接翻到这一页的底部。
这里,我们需要指定一个 IAM 角色,该角色有权向 S3 写入数据。Kinesis Firehose 将使用这个 IAM 角色将传入数据写入 S3。您可以选择现有的 IAM 角色,也可以创建新的 IAM 角色。选择此项后,单击页面底部的“下一步”按钮。
您现在应该已经创建了交付流。我们已经完成了流的创建。我们可以转到 Java 代码,我们将使用它向这个流发送数据。
Java 代码
在我们进入代码之前,像往常一样,让我们看一下我们需要的依赖项。我为此创建了一个 Maven 项目。所以我有一个 pom.xml 文件用于依赖管理。我已经列出了我在这里使用的所有依赖项:
<dependencies> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk</artifactId>
<version>1.11.627</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>amazon-kinesis-client</artifactId>
<version>1.11.2</version>
</dependency>
<dependency>
<groupId>org.codehaus.jettison</groupId>
<artifactId>jettison</artifactId>
<version>1.2</version>
</dependency></dependencies>
接下来,使用 AWS SDK,我们需要创建一个 AmazonKinesisFirehose 客户端,它将用于将数据或记录放入 Firehose 交付流。为此,我们首先需要创建 AWS 凭证对象。对于这个例子,我使用的是 BasicAWSCredentials 类。这意味着,我们必须提供访问密钥和访问秘密。我们将把这些数据保存在 application.properties 文件中,这样我们就可以将这些数据放入 Java 代码中,而无需硬编码。因为这是一个 Spring Boot 应用程序,所以我们可以为此使用 @Value() 注释:
@Value("${aws.auth.accessKey}")
private String awsAccessKey;@Value("${aws.auth.secretKey}")
private String awsSecretKey;
使用这两个变量,我们将创建凭证对象和 Firehose 客户端:
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(awsAccessKey, awsSecretKey);AmazonKinesisFirehose firehoseClient = AmazonKinesisFirehoseClient.builder()
.withRegion(Regions.US_WEST_2)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();
请确保您更改了上面代码片段中的区域,以匹配您的 Kinesis 设置。一旦我们创建了这个 Firehose 客户端,我们就可以开始向 Firehose“放记录”了。但是让我们来看看我们将要输入的数据。
JSONObject messageJson = new JSONObject();
messageJson.put("key1", "We are testing Amazon Kinesis Firehose!");
messageJson.put("integerKey", 123);
messageJson.put("booleanKey", true);
messageJson.put("anotherString", "This should work!");logger.info("Message to Firehose: " + messageJson.toString());
如您所见,我创建了一个简单的 JSON 对象,它将被序列化,转换成字节,然后发送到 Firehose。在我们实际发送数据之前,我们必须创建一个 PutRecordRequest 对象,使用它我们将指定向哪个流发送数据。流名称不是硬编码的,但是我们从application . propertiets文件中获取它,类似于 AWS 凭证:
@Value("${aws.kinesis.firehose.deliveryStream.name}")
private String fireHoseDeliveryStreamName;
我们现在将创建 PutRecordRequest:
PutRecordRequest putRecordRequest = new PutRecordRequest();
putRecordRequest.setDeliveryStreamName(fireHoseDeliveryStreamName);
接下来,我们将创建一个记录对象,它将保存将要发送的实际数据。我们将这个记录设置为我们刚刚创建的 putRecordRequest 对象,并调用。Firehose 客户端对象上的 putRecord() 方法。
Record record = new Record().withData(ByteBuffer.wrap(messageJson.toString().getBytes()));
putRecordRequest.setRecord(record);PutRecordResult putRecordResult = firehoseClient.putRecord(putRecordRequest);
就是这样。如果您没有从 AWS 客户端得到任何错误,您可以确信您发送的数据将在您的 S3 桶中。去看看。
如果你对这个例子中完整的 Spring Boot 项目感兴趣,可以去我的 Github 库看看,也许可以分支一下?
在 Twitter 上关注我,了解更多数据科学、机器学习,以及一般技术更新。此外,你可以关注我的个人博客,因为我在那里发布了很多我的教程、操作方法帖子和机器学习的优点。
把 AI 放在第一位

Image credit : cio.com
如何将人类和机器智能结合起来,为企业带来实实在在的好处?
上个月在达沃斯,人工智能被第四次工业革命和以人为中心的人工智能的持续喧嚣所掩盖,几乎每个与会的首席执行官都可以读到人工智能。【1】与技术介绍的多样性形成鲜明对比的是,人们似乎有一个共同的想法:人工智能对商业和工业来说都是一个巨大的机会。在这种兴奋的阴影下,两个基本问题的答案似乎在闲聊中消失了:组织将如何实施人工智能,以及如何将人类和机器智能结合起来,为企业带来切实的好处?在这篇文章中,我们将区分人工智能和机器学习,首先讨论人工智能的概念,并提出我们的人工智能路线图,以展示如何将数据科学带到桌面上来,使组织在今天和明天受益。

人工智能与组织在过去十年中利用的机器学习有什么不同?如果这两种愿景都建立在数据科学的进步之上,那么这两种方法在目标、方法和评估指标上有着根本的不同。就像 HTML 和 XML 在编写网页方面的差异一样,人工智能超越了数据中可见内容的前提,探索了人类交互背后的逻辑和伦理。传统的数据科学利用机器学习,通过检查数据、特征和实验来阐明人类知识,而人工智能的目标是揭示人类智能背后的认知推理。机器学习的目标是预测与测试数据集中的模式一致的新数据点,而人工智能的主旨是解决复杂的(非线性甚至混乱的)商业问题。机器学习的价值主张是提高一个组织预测未来的能力,而人工智能的愿景是阐明每个决策环境的最佳答案。
将人工智能作为商业战略放在第一位将要求管理层放弃以前利用 IT 硬件和软件的观念。AI First 不是承诺支持手机或云等技术平台,而是投资一个组织的能力来解决未来的业务挑战。对 IT 的投资不能再被归类为硬件、软件和专有数据,而是对人力资本的基础设施投资,与构建业务本身的投资没有区别。数据不再被视为一种资源,而是一种独特的资产,必须对其进行评估、丰富,并与组织开展业务的方式密切相关。AI 首先意味着放弃提高运营效率的正统观念,转而采用不断实验的思维模式,并接受创新者困境的悖论。人工智能首先作为一种商业战略,并不是一切照旧,而是一种有意识的选择,以改变组织及其生态系统之间的几个边界——这不仅需要改变思维模式,还需要改变交易条款和我们做生意的标准。

虽然我们今天很方便地区分了有限、一般和超级人工智能,但我们很少将我们的人工智能愿景与我们商业战略的具体细节联系起来。姚为我们提供了一个人工智能的连续体,来理解为什么组织投资人工智能的不同观点的含义。【3】在竞技场的一端,人工智能的目标可以简单地是比人类更快、更便宜或更有效地执行简单的人类任务。在该领域的另一端,人工智能可以用来建立认知系统,以应对商业(或社会)生态系统的未来挑战。在这两者之间,可以张贴旗帜来鼓励预测、学习、创造和掌握人类行为的复杂性。经理和他们的数据科学团队可以在一定程度上玩人工智能游戏,因为他们知道他们的利益相关者在哪里调整了目标。

白的人工智能路线图
组织将如何利用人工智能将数据转化为集体行动?无论是经理还是客户都不会根据数据采取行动,而是根据他们对数据如何代表他们面前的挑战和机遇的看法采取行动。在我们的人工智能路线图中,我们展示了客户和经理如何做出运营决策,并指出了机器学习和人工智能可以发挥作用的地方。该路线图着眼于感知的四个基本要点:识别、预测、评估和行动,以及每个要点如何根据手头的数据来制约我们的执行、发明和创新能力。基于特定的业务环境和逻辑,在每个方向上探索匹配人类和机器智能的挑战和机遇。
我们将基于真实世界的例子,在 BAI 在大波士顿地区举办的春季课程“人工智能管理”、在 BAI 在法国巴约纳举办的夏季课程“数据科学实践”以及在印度迈索尔举办的秋季课程“数据科学伦理”中,详细探讨这些论点。商业分析实践是商业分析学院的核心和灵魂。通过关注数字经济、组织决策、机器学习和人工智能的管理挑战,白让分析为您和您的组织工作。
李·施伦克博士
2019 年 2 月 16 日
【1】k . Roose(2019 年 1 月 25 日),“达沃斯精英们隐藏的自动化议程 ,”《纽约时报》
克莱顿·克里斯滕森(2015 年 12 月 15 日)。《创新者的困境:当新技术导致大公司失败的时候》。哈佛商业评论出版社
【姚,m .(2017 年 10 月 16 日)机器智能连续体,中
将 ML 投入生产 I:在 Python 中使用 Apache Kafka。
这是两个帖子中的第一个,我们将展示如何使用一系列工具(主要是卡夫卡和 MLFlow )来帮助制作 ML。为此,我们将设置一个简单的场景,我们希望它类似于一些真实的用例,然后描述一个潜在的解决方案。所有代码的配套回购可以在这里找到。
场景
一家公司使用一系列服务来收集数据,当用户/客户与该公司的网站或应用程序交互时,这些服务会生成事件。当这些交互发生时,算法需要实时运行,并且需要根据算法的输出(或预测)立即采取一些行动。最重要的是,在 N 次交互(或观察)之后,算法需要重新训练而不停止预测服务 ,因为用户将保持交互。
在这里的练习中,我们使用了成人数据集,目标是根据个人的年龄、祖国等来预测个人收入是否高于/低于 50k。为了使这个数据集适应前面描述的场景,可以假设年龄、国籍等是通过在线问卷/表格收集的,我们需要实时预测用户的收入是高还是低。如果收入高,我们会立即打电话/发电子邮件给他们,提供一些优惠。然后,在新的观察之后,我们重新训练算法,同时继续预测新用户。
解决方案
图 1 是一个潜在解决方案的示意图。为了实现这个解决方案,我们使用了 Kafka-Python (一个不错的教程可以在这里找到),以及 LightGBM 和hyperpt或 HyperparameterHunter 。

Figure 1. Real-time prediction ML pipeline. A full description is provided below
在本练习中,我们将使用的唯一 Python“局外人”是 Apache-Kafka (我们将使用 python API Kafka-Python,但 Kafka 仍然需要安装在您的系统中)。如果你用的是苹果电脑,就用自制软件:
brew install kafka
这也将安装 zookeeper 依赖项。
如前所述,我们使用了成人数据集。这是因为我们的目的是说明一个潜在的 ML 管道,并提供有用的代码,同时保持它相对简单。但是请注意,这里描述的管道原则上是数据不可知的。当然,预处理将根据数据的性质而变化,但是管道组件即使不完全相同,也会保持相似。
初始化实验
这篇文章使用的代码(以及更多)可以在我们的报告中找到。在那里,有一个名为initialize.py的剧本。该脚本将下载数据集,设置 dir 结构,预处理数据,在训练数据集上训练初始模型,并优化该模型的超参数。在现实世界中,这将对应于通常的实验阶段和离线训练初始算法的过程。
在这篇文章中,我们希望更多地关注管道和相应的组件,而不是 ML。尽管如此,让我们简单地提一下我们在这个练习中使用的 ML 工具。
给定我们正在使用的数据集,数据预处理相当简单。我们编写了一个名为[FeatureTools](https://github.com/jrzaurin/ml_pipelines/blob/master/utils/feature_tools.py)的定制类,可以在回购的utils模块中找到。这个类有.fit和.transform方法,它们将标准化/缩放数字特征,编码分类特征,并生成我们所说的“交叉列”,这是两个(或更多)分类特征之间的笛卡尔乘积的结果。
处理完数据后,我们使用 LightGBM 拟合模型,并使用 Hyperopt 或 HyperparameterHunter 进行超参数优化。与这个任务相关的代码可以在train模块中找到,在这里可以找到两个脚本[train_hyperop](https://github.com/jrzaurin/ml_pipelines/blob/master/train/train_hyperopt.py).py和[train_hyperparameterhunter](https://github.com/jrzaurin/ml_pipelines/blob/master/train/train_hyperparameterhunter.py).py。
我们可能会写一个单独的帖子来比较 python 中超参数优化包( Skopt ,Hyperopt 和 HyperparameterHunder),但是现在,要知道:如果你想要速度,那么使用 Hyperopt。如果你不关心速度,你想保持你的优化程序的详细跟踪,然后使用超参数猎人。用这个包的创造者亨特麦古西翁的话说:
“长期以来,超参数优化一直是一个非常耗时的过程,只是为您指明了进一步优化的方向,然后您基本上不得不重新开始。”
HyperparameterHunter 就是为了解决这个问题,而且做得非常好。目前,该软件包是建立在 Skopt 之上的,这就是为什么它明显比 Hyperopt 慢的原因。然而,我知道有人在努力将 Hyperopt 作为 HyperparameterHunter 的另一个后端。当这种情况发生时,将不会有争论,超参数猎人应该是你的选择工具。
尽管如此,如果有人感兴趣,我在回购中包括了一个笔记本,比较 Skopt 和 Hyperopt 的性能。
现在让我们转到管道流程本身。
应用程序消息制作者
这是生产管道的一个相对简单的例子。因此,我们直接使用成人数据集来生成消息(JSON 对象)。
在现实世界中,可能会有许多服务生成事件。从那里,一个人有几个选择。这些事件中的信息可能存储在数据库中,然后通过您的常规查询进行汇总。从那里,Kafka 服务将消息发布到管道中。或者,这些事件中的所有信息可以直接发布到不同的主题中,一个“聚合服务”可以将所有信息存储在一个消息中,然后该消息将被发布到管道中(当然,也可以是两者的组合)。
例如,用户可以通过脸书或谷歌注册,收集他们的姓名和电子邮件地址。然后,他们可能会被要求填写一份调查问卷,我们会随着事件的进展不断收集信息。在这个过程中的某个时刻,所有这些事件将被聚合在一条消息中,然后通过 Kafka 制作人发布。本文中的管道将从所有相关信息聚集的地方开始。我们这里的信息是成人数据集中的个人观察。下面是我们信息内容的一个例子:
’{“age”:25,”workclass”:”Private”,”fnlwgt”:226802,”education”:”11th”,”marital_status”:”Never-married”,”occupation”:”Machine-op-inspct”,”relationship”:”Own-child”,”race”:”Black”,”gender”:”Male”,”capital_gain”:0,”capital_loss”:0,”hours_per_week”:40,”native_country”:”United-States”,”income_bracket”:”<=50K.”}’
应用/服务(图 1 中最左边的灰色方框)的核心是下面的代码片段:
请注意,我们使用测试数据集来生成消息。这是因为我们设计了一个尽可能接近真实世界的场景(在一定范围内)。考虑到这一点,我们已经使用训练数据集来构建初始的model 和dataprocessor 对象。然后,我们使用测试数据集来生成消息,目的是模拟随着时间的推移接收新信息的过程。
关于上面的片段,简单地说,生产者将消息发布到管道中(start_producing()),并将使用最终预测(start_consuming())来消费消息。同样,我们在这里描述的管道不包括流程的最开始(事件收集和聚合),我们也跳过最结尾,即如何处理最终预测。尽管如此,我们还是简要讨论了一些用例,在这些用例中,这个管道在本文的末尾会很有用,这将说明最后的阶段。
实际上,除了忽略过程的开始和结束,我们认为这个管道与现实世界中使用的管道相当相似。因此,我们希望我们回购中包含的代码对您的一些项目有用。
预测器和训练器
该实现的主要目标是实时运行该算法,并且每隔 N 次观察重新训练该算法,而不需要停止预测服务。为此,我们实现了两个组件,预测器(回购中的[predictor](https://github.com/jrzaurin/ml_pipelines/blob/master/predictor.py).py)和训练器([trainer](https://github.com/jrzaurin/ml_pipelines/blob/master/trainer.py).py)。****
现在让我们使用代码片段作为指导方针,逐一描述图 1 中显示的数字。注意,下面的过程假设用户已经运行了initialize.py脚本,因此初始的model.p和dataprocessor.p文件存在于相应的目录中。此外,强调下面的代码包括预测器和训练器的核心。完整代码请参考回购。
预测器
预测器的核心代码如下所示
predictor.py片段中的 (1a) 第 12 行。预测器将接收来自应用程序/服务的消息,它将进行数据处理,并在接收消息时实时运行模型。所有这些都是通过使用predict函数中现有的dataprocessor和model对象来实现的。
(1b)predictor.py片段中的第 13 行。一旦我们运行了预测,预测器将发布最终将被应用/服务接收的结果(publish_prediction())。
(2 ) 代码片段中的第 17–20 行。每收到一条RETRAIN_EVERY 消息,预测器将发布一条“重新训练”消息(send_retrain_message())供训练员阅读。**
培训师
(3)trainer.py片段中的第 12 行。训练器将读取该消息,并用新的累积数据集触发再训练过程(train())。这是,原始数据集加上RETRAIN_EVERY的新观测数据。训练功能将独立于 1a 和 1b 中描述的过程,运行“初始化实验” 部分中描述的整个过程。换句话说,当消息到达时,训练器将重新训练模型,而预测器继续提供预测。**
在这个阶段,值得一提的是,我们在这里发现了我们的实现与现实世界中使用的实现之间的进一步差异。在我们的实现中,一旦已经处理了RETRAIN_EVERY 个观察值,就可以重新训练算法。这是因为我们使用成人测试数据集来生成消息,其中包括目标列(" income_braket ")。在现实世界中,基于算法的输出所采取的行动的真实结果通常不会在算法运行之后立即可获得,而是在一段时间之后。在这种情况下,另一个过程应该是收集真实结果,一旦收集的真实结果的数量等于RETRAIN_EVERY,算法将被重新训练。
比如说这个管道实现了一个电商的实时推荐系统。我们已经离线训练了一个推荐算法,目标列是我们的用户喜欢我们的推荐的程度的分类表示:0、1、2 和 3 分别表示不喜欢该项目或与该项目交互、喜欢该项目(例如,点击喜欢按钮)、将该项目添加到他们的购物篮和购买该项目的用户。当系统提供建议时,我们仍然不知道用户最终会做什么。
因此,除了在用户浏览网站(或应用程序)时收集和存储用户信息的过程之外,第二个过程应该收集我们推荐的最终结果。只有当两个过程都收集了RETRAIN_EVERY消息和结果时,算法才会被重新训练。
(4)trainer.py片段中的第 13 行。一旦重新训练完成,将发布一条包含相应信息的消息(published_training_completed())。
(5)predictor.py片段中的第 5–8 行。预测者的消费者订阅了两个话题:[‘app_messages’, ‘retrain_topic’]。一旦它通过“retrain_topic”接收到再训练已完成的信息,它将加载新的模型并照常继续该过程,而不会在该过程中的任何时间停止。
如何运行管道
在配套回购中,我们包含了如何运行管道(本地)的说明。其实很简单。
- 开始 zookeper 和卡夫卡:
**$ brew services start zookeeper
==> Successfully started `zookeeper` (label: homebrew.mxcl.zookeeper)
$ brew services start kafka
==> Successfully started `kafka` (label: homebrew.mxcl.kafka)**
2.运行 initialize.py:
**python initialize.py**
3.在 1 号终端运行预测器(或训练器):
**python predictor.py**
4.在 2 号终端运行训练器(或预测器):
**python trainer.py**
5.在 3 号终端运行示例应用程序
**python samplea_app.py**
然后,一旦处理了 N 条消息,您应该会看到类似这样的内容:

右上角终端:我们已经重新训练了模型,Hyperopt 已经运行了 10 次评估(在真实的练习中应该有几百次)。左上终端:一旦模型被重新训练和优化,我们将看到预测器是如何加载新模型的(在新的 LightGBM 版本发出恼人的警告消息之后)。底部终端:服务照常进行。****
一些潜在的使用案例
这里有几个潜在的用例,以及其他一些。
- 实时调整在线旅程
我们来考虑一个卖一些物品的电商。当用户浏览网站时,我们收集他们的活动信息。我们之前已经训练了一个算法,我们知道,比如说,在 10 次互动之后,我们就可以很好地知道客户最终是否会购买我们的产品。此外,我们还知道他们可能购买的产品可能会很贵。因此,我们希望定制他们的“移动”旅程,以促进他们的购物体验。这里的定制可以是任何东西,从缩短旅程到改变页面布局。
2.给你的客户发邮件/打电话
与之前的用例类似,现在让我们假设客户决定停止旅程(厌倦、缺少时间、可能太复杂,等等)。如果算法预测这个客户有很大的潜力,我们可以使用像这篇文章中描述的管道立即或在控制延迟的情况下发送电子邮件或打电话给他们。
接下来的步骤
- 日志记录和监控:在即将发布的帖子中,我们将通过 MLFlow 在管道中插入日志记录和监控功能。与 HyperparameterHunter 一起,该解决方案将自动全面跟踪模型性能和超参数优化,同时提供可视化监控。
- 流管理:这里描述的解决方案以及相应的代码,已经被设计成可以在本地的笔记本电脑上轻松运行。然而,人们可能会认为,在现实生活中,这必须大规模地运行云。这一具体部分将不会在两个帖子中涉及。然而,我可以向你保证,将这里描述的结构移植到 AWS(举例来说)并在你方便的时候自动运行它(使用 EC2s、S3 和任何适合你的特定情况的前端)并不特别复杂。
任何问题/建议,请发邮件至:jrzaurin@gmail.com
将 ML 投入生产 II:日志记录和监控

在我们之前的文章中,我们展示了如何使用 Apache Kafka 的 Python API ( Kafka-Python )来实时生成算法。在这篇文章中,我们将更多地关注 ML 方面,更具体地说,关注如何在(重新)训练过程中记录信息,并监控实验的结果。为此,我们将使用 MLflow 和hyperpt或 HyperparameterHunter 。
场景和解决方案
场景和解决方案的详细描述可以在前面提到的帖子中找到。
总之,我们希望实时运行算法,,并且需要根据算法的输出(或预测)立即采取一些行动。此外,在 N 次交互(或观察)后,算法需要重新训练而不停止预测 服务。
我们的解决方案主要依赖于 Kafka-Python 在流程的不同组件之间分发信息(更多细节请参见我们第一篇文章中的图 1):
- 服务/应用程序生成一条消息(JSON ),其中包含算法所需的信息(即功能)。
- “预测器组件接收消息,处理信息并运行算法,将预测发送回服务/应用。
- 在 N 个已处理的消息(或观察)之后,预测器向“训练器”组件发送消息,开始新的训练实验。这个新的实验将包括原始数据集加上所有新收集的观察数据。正如我们在第一篇文章中所描述的,在现实世界中,在重新训练算法之前,人们必须等到它接收到与观察一样多的真实结果(即真实标签或数字结果)。
- 一旦算法被重新训练,训练器发送相应的消息,预测器将加载新的模型而不停止服务。
ML 工具
为了简洁起见,我们更喜欢在这里关注过程而不是算法本身。但是,如果你想进一步探索,至少让我给你一些指导。
我们使用的核心算法是 LightGBM 。LightGBM 已经成为我几乎每个涉及分类或回归的项目的首选算法(它还可以进行排名)。网上有大量关于这个包的信息,人们可以在 github repo 的示例区学习如何在各种场景中使用它。但是,对于给定的算法,我总是推荐阅读相应的论文。在这种情况下,柯等人 2017 做得太棒了。这篇论文写得很好,总的来说,很容易理解。
使用的优化包将是 HyperparameterHunter(以下简称 HH)和 Hyperopt ,两者都采用贝叶斯优化方法。HH 使用 Skopt 作为后端,其BayesianOptimization方法基于高斯过程。另一方面,据我所知,Hyperopt 是唯一实现 TPE(Parzen estimators 树)算法的 Python 包。我发现了一些使用该算法的其他库,但都依赖于 Hyperopt(例如 Optunity 或 Project Ray 的 tune )。
如果你想学习贝叶斯优化方法,我建议你做以下事情。首先阅读 Skopt 站点中的贝叶斯优化部分。在那里你可以找到问题陈述和贝叶斯过程(或循环)的描述,我认为这对于贝叶斯优化方法来说是相当常见的。然后去超视论文( Bergstra 等人,2011 )。同样,如果你想熟悉贝叶斯方法,这是一篇“必读”的论文。特别是,在那里你将学习高斯过程(GP)和基于顺序模型的全局优化(SMBO)算法(第 2-4 节)背景下的 TPE。
剩下的 ML“成分”是 MLflow,这里将使用它来帮助跟踪和监控训练过程(尽管您会看到 HH 已经很好地保存了所有重要的数据)。
跟踪 ML 流程
按照我们第一篇文章中使用的类似方法,我们将使用代码作为评论最重要部分的指南。本节中的所有代码都可以在我们的 repo 的train模块中找到。我们将从远视开始,然后转移到 HH,在那里我们将说明是什么使后者独一无二。
远视
本节中的代码可以在train模块的脚本[train_hyperopt_mlflow](https://github.com/jrzaurin/ml_pipelines/blob/master/train/train_hyperopt_mlflow.py).py中找到。
记住,目标是最小化一个目标函数。我们的远视目标函数看起来像这样:
Snippet 1
其中params可能是,例如:
Snippet 2
让我们讨论函数中的代码。该功能必须仅依赖于params。在函数中,我们使用交叉验证,并输出最佳指标,在本例中为binary_logloss 。请注意,我们使用 LightGBM(作为lgb导入)方法(第 14 行,片段 1 中的lgb.cv ),而不是相应的sklearn总结。这是因为根据我的经验,LightGBM 自己的方法通常要快一些。还要注意的是,LightGBM 并没有实现像f1_score这样的指标。尽管如此,我们还是在train_hyperopt.py和train_hyperopt_mlfow.py脚本中包含了一个 LightGBM f1 定制的度量函数,以防万一。
在代码片段 1 中的第 22行处停下来一秒钟是值得的,这里我们记录了用于特定迭代的提升轮次的数量。这是因为当使用 Hyperopt(或 Skopt 或 HH)时,算法将根据参数的输入值进行优化,其中一个参数是num_boost_round。在目标函数中,我们通过提前停止进行交叉验证,以避免过度拟合。这意味着最终的提升轮数可能与输入值不同。该信息将在优化过程中“丢失”。为了克服这个限制,我们简单地将最后的num_boost_round保存到字典early_stop_dict中。然而,并不总是清楚这是最好的解决方案。关于这个和其他关于 GBMs 优化的问题的完整讨论,请在我的 github 中查看这个笔记本。
最后,记住我们需要最小化输出值。因此,如果输出是一个分数,objective 函数必须输出它的负值,而如果是一个错误(rmse)或损失(binary_logloss),函数必须输出值本身。
运行优化过程的代码很简单:
Snippet 3
每一组被尝试的参数将被记录在trials对象中。一旦优化完成,我们可以编写自己的功能来记录结果。或者,我们可以使用 MLflow 等工具来帮助我们完成这项任务,并直观地监控不同的实验。你可以想象,一个人只能在 MLflow 上写一些帖子。在这里,我们将简单地说明我们如何使用它来记录最佳性能参数、模型和指标,并监控算法的性能。
MLflow
对于 Hyperopt 和 HH,跟踪每个实验结果的 MLflow 块几乎保持不变,如下面的代码片段所述。
Snippet 4
第 1–8 行:我们在最新的 MLflow 版本(0.8.2)中发现的一个"恼人的"行为是,当你第一次实例化类MLflowClient()或创建一个实验(mlflow.create_experiment('test'))时,它会创建两个目录,mlruns/0和mlruns/1。前者被称为Default,当你运行实验时,它将保持为空。这里我们在一个名为test_mlflow的空目录中展示了这种行为:
infinito:test_mlflow javier$ ls
infinito:test_mlflow javier$ ipython
Python 3.6.5 (default, Apr 15 2018, 21:22:22)
Type ‘copyright’, ‘credits’ or ‘license’ for more information
IPython 7.2.0 — An enhanced Interactive Python. Type ‘?’ for help.In [1]: import mlflowIn [2]: mlflow.__version__
Out[2]: ‘0.8.2’In [3]: mlflow.create_experiment(‘test’)
Out[3]: 1In [4]: ls mlruns/
0/ 1/
因此,当您打开 UI 时,第一个屏幕将是一个空屏幕,上面有一个名为Default的实验。如果你能接受这一点(我不能),那么有更简单的方法来编写 MLflow 块中第 1–8 行的代码,例如:
Snippet 5
在当前设置中(片段 4),我们的第一次流程初始化(python initialize.py)将被称为Default,并存储在目录mlruns/0中。
另一方面,定义每次运行的experiment_id的一种更优雅的方式是列出现有的实验并获得最后一个元素的 id:
experiments = client.list_experiments()
with mlflow.start_run(experiment_id=experiments[-1].experiment_id):
然而,我发现的另一个【不方便】行为是client.list_experiments()不维护秩序。这就是为什么我们使用“不太优雅”的解决方案n_experiments。
提前第 9 行:我们只是运行实验,并记录所有参数、指标和模型作为 MLflow 工件。
在此阶段,值得一提的是,我们完全意识到我们“未充分利用”MLflow。除了跟踪算法的结果,MLflow 还可以打包和部署项目。换句话说,使用 MLflow 可以管理几乎整个机器学习周期。然而,我的印象是,要做这样一件事,需要在头脑中开始一个项目。此外,对我来说,如何在不增加不必要的复杂性的情况下使用 MLflow 打包本文和之前的文章中描述的整个项目并不简单。尽管如此,我在这个库中看到了很多潜力,我清楚地看到自己在未来的项目中使用它。
超参数猎人(HH)
看了代码片段 1-4 后,有人可能会想:“如果我想记录每一次超优化运行并在排行榜中保持跟踪,该怎么办?”。嗯,有两种可能性: i) 可以简单地将 MLflow 块移到目标函数的主体,并相应地修改代码,或者 ii) 简单地使用 HH。
在我写这篇文章的时候,我看到了使用 HH 的两个缺点。首先,你不需要编写自己的目标函数。虽然这在很大程度上是一个积极的方面,但也意味着它不太灵活。然而,我使用 HH 已经有一段时间了,除非你需要设计一个复杂的目标函数(例如,目标中一些不寻常的数据操作,或者参数的内部更新),HH 会完成这项工作。如果你确实需要设计一个高度定制的目标函数,你可以使用sklearn的语法编写一个,作为model_initializer传递给 HH 的optimizer 对象。
第二个缺点,也许更重要,与 HH 没有直接关系,而是 Skopt。HH 是在 Skopt 的基础上构建的,Skopt 明显比 Hyperopt 慢。然而,我知道目前有人在努力添加 Hyperopt 作为一个替代后端(以及其他即将推出的功能,如功能工程,敬请关注)。
总之,如果你不需要设计一个特别复杂的目标函数,并且你能负担得起“目标速度”,HH 提供了许多使它独一无二的功能。首先,HH 为你记录和组织所有的实验。此外,当你运行额外的测试时,它会学习,因为过去的测试不会浪费。换句话说:
“super parameter hunter 已经知道了你所做的一切,这就是 super parameter hunter 做一些了不起的事情的时候。它不像其他库那样从头开始优化。它从您已经运行过的所有实验和之前的优化回合开始。”亨特·麦古森。
让我们看看代码。以下 3 个片段是使用 HH 时需要的全部(更多细节参见文档)。本节的完整代码可以在train模块的脚本train_hyperparameterhunter_mlflow.py中找到。
正如你将看到的,语法非常简洁。我们首先设置一个Environment,它只是一个简单的类,用来组织允许实验被公平比较的参数。
Snippet 6
然后我们进行实验
Snippet 7
其中model_init_params和model_extra_params为:
Snippet 8
当仔细观察代码片段 7 时,我们可以发现 HH 和 Hyperopt 之间的进一步差异,同样纯粹与 Skopt 后端有关。您将会看到,当使用 Hyperopt 时,可以使用分位数均匀分布(hp.quniform(low, high, step))。在 Skopt 中没有这样的选项。这意味着对于像num_boost_rounds或num_leaves这样的参数,搜索效率较低。例如,先验地,人们不会期望 100 轮和 101 轮助推的两个实验产生不同的结果。例如,这就是为什么在使用 Hyperopt 时,我们将num_boost_rounds设置为hp.quniform(50, 500, 10)。
一种可能的方法是使用 Skopt 的分类变量:
num_leaves=Categorical(np.arange(31, 256, 4)),
然而,这并不是最佳解决方案,因为在现实中,num_boost_rounds或num_leaves并不是分类变量,而是将被如此对待。例如,默认情况下,Skopt 将为分类特征构建输入空间的一键编码表示。由于类别没有内在的顺序,如果cat_a != cat_b在那个空间中两点之间的距离是 1,否则是 0。在搜索像num_leaves这样的参数的过程中,这种行为不是我们想要的,因为 32 片叶子与 31 片叶子的距离可能是 255 片。Skopt 提供了不改造空间的可能性,虽然仍不理想,但更好:
num_leaves=Categorical(np.arange(31, 256, 4), transform=’identity’),
但是由于这样或那样的原因,每次我试图使用它时,它都会抛出一个错误。然而,我们将使用Integer,并在 HH 实现 Hyperopt 作为后端时使用它。
运行示例
在本节中,我们将运行一个最小的示例来说明我们如何使用 HH 和 MLflow 来跟踪培训过程的每个细节。这里我们将保持简单,但是可以使用 HH 中的Callbacks功能来无缝集成这两个包。
最小的例子包括处理 200 条消息,并且每处理 50 条消息就重新训练该算法。在每个再训练实验中,HH 将只运行 10 次迭代。在现实世界中,对传入的消息没有限制(即 Kafka 消费者总是在收听),在处理了数千个新的观察结果之后,或者在某个时间步长(即每周)之后,可能会发生重新训练,并且 HH 应该运行数百次迭代。
此外,为了便于说明,这里使用我们自己的日志记录方法(主要是pickle)、HH 和 MLflow 对进程进行了“过量日志记录”。在生产中,我建议使用结合 MLflow 和 HH 的定制解决方案。
让我们看一看

Figure 1. Screen shot after minimal example has run
图 1 显示了处理 200 条消息并重新训练模型 4 次(每 50 条消息一次)后的屏幕截图。在左上角的终端中,我们运行预测器(predictor.py),中间的终端运行训练器(trainer.py),在这里我们可以看到上次 HH 运行的输出,而下方的终端运行应用/服务(sample_app.py),在这里我们可以看到收到的请求和输出预测。
读者可能会注意到左上角终端加载的新模型的变化(NEW MODEL RELOADED 0->-NEW MODEL RELOADED 1->-NEW MODEL RELOADED 0->-NEW MODEL RELOADED 1)。这是因为当使用我们自己的日志方法时,我们使用一个名为EXTRA_MODELS_TO_KEEP的参数来设置我们保留了多少过去的模型。它当前被设置为 1,当前的加载过程指向我们的输出目录。这可以很容易地在代码中改变,以保存过去的模型,或者指向 HH 或 MLflow 对应的输出目录,其中存储了过去所有性能最好的模型。
图 1 中右上方的终端启动 MLflow 跟踪服务器。图 2 显示了 MLflow UI 的一个屏幕截图。

Figure 2. Screen shot of the MLflow monitoring tool
该图显示了 MLflow 为我们称之为“实验 _ 2”的特定情况保存的信息(即,用 100 个累积的新观察/消息重新训练模型)。为了与 HH 保持一致,我们将每个再训练过程保存为不同的实验。如果您希望将所有的再训练过程保存为一个实验,只需转到train_hyperparameter_hunter.py的LGBOptimizer类中的optimize方法,并将reuse_experiment参数更改为True.
根据当前的设置,在mlruns目录中,每个实验将有一个子目录。例如,mlruns/1目录的结构是:
Snippet 8. Summary of the mlruns directory’s file structure
如你所见,所有你需要的信息都在那里,组织得非常好。让我坚持一下,我们 MLflow-code 的当前结构只保存了性能最好的一组参数和模型。如前所述,可以将代码片段 4 中的 MLflow 块移到目标函数中,并修改代码,以便 MLflow 记录每一次迭代。或者,也可以使用 HH。
HH 记录了所有的事情。让我们看看HyperparameterHunterAssets的目录结构。每个子目录中内容的详细解释可以在这里找到。可以看到,保存了关于每个迭代的大量信息,包括每个实验的训练数据集(包括原始/默认数据集的 5 个数据集,加上每个再训练周期包括 50 个额外观察的 4 个新数据集)和当前获胜迭代的排行榜。
Snippet 9. Brief Summary of the HyperparameterHunterAssets directory’s file structure
希望在这个阶段,读者对如何结合 MLflow 和 HH 来跟踪和监控算法在生产中的性能有一个好的想法。
然而,尽管在生产 ML 时需要考虑这里描述的所有因素,但这里并没有讨论所有需要考虑的因素。让我至少在下一节提到缺失的部分。
缺失的部分
单元测试
我们在这篇和上一篇文章中(有意)忽略的一个方面是单元测试。这是因为单元测试通常依赖于算法的应用。例如,在您的公司,可能有一些管道部分在过去已经被广泛测试过,而一些新的代码可能需要彻底的单元测试。如果我有时间,我会在回购中包括一些与此任务相关的代码。
概念漂移
生产中经常被忽略的是 【概念漂移】 。概念漂移是指数据集的统计属性会随着时间的推移而发生变化,这会对预测的质量产生显著影响。例如,假设你的公司有一个应用程序,主要面向 30 岁以下的人。一周前,你开展了一场扩大用户年龄范围的广告活动。有可能你现有的模型在新观众面前表现不佳。
有许多选择来检测和解决概念漂移。人们可以编写一个定制的类,以确保训练和测试数据集中的特征分布在一定范围内保持稳定。或者,可以使用像 MLBox 这样的库。MLBox 适合自动化 ML 库的新浪潮,并带有一系列不错的功能,包括一种优化方法,它依赖于,猜猜看,什么…Hyperopt。包中的另一个功能是Drift_thresholder()类。这个类会自动为你处理概念漂移。
MLBox 实现使用一个分类器(默认情况下是一个随机森林),并尝试预测一个观察值是属于训练数据集还是测试数据集。那么估计的漂移被测量为:
drift = (max(np.mean(S), 1-np.mean(S))-0.5)*2
其中 S 是包含与过程中使用的 n 折叠相对应的roc_auc_score的列表。如果没有概念漂移,算法应该不能区分来自两个数据集的观察值,每折叠的roc_auc_score应该接近随机猜测(0.5),数据集之间的漂移应该接近零。或者,如果一个或多个特征随时间发生了变化,那么算法将很容易区分训练和测试数据集,每折叠的roc_auc_score 将接近 1,因此,漂移也将接近 1。请注意,如果数据集高度不平衡和/或非常稀疏,您可能希望使用更合适的度量和算法(已知随机森林在非常稀疏的数据集中表现不佳)。
总的来说,如果你想了解更多关于包和概念漂移的概念,你可以在这里找到更多信息。他们DriftEstimator()的源代码是这里是。同样,如果我有时间,我会在我们的回购协议中包含一些与概念漂移相关的代码。
最后,到目前为止,我们已经在本地使用了 Kafka,我们不需要扩展或维护它。然而,在现实世界中,人们必须根据流量进行调整,并维护算法的组件。我们最初的想法是让 Sagemaker 来完成这个任务,然后写第三篇文章。然而,在深入研究这个工具(你可以在回购的分支sagemaker中找到)之后,我们发现使用 Sagemaker 带来了很多不必要的复杂性。换句话说,使用它比简单地将代码转移到云中,使用 AWS 工具(主要是 ec2 和 S3)并使用简单的定制脚本自动运行它要复杂得多。
总结和结论
让我们总结一下将 ML 投入生产时必须考虑的概念和组件。
- 写得很好的代码和结构合理的项目(非常感谢 Jordi
- 随着时间的推移记录和监控算法的性能
- 单元测试
- 概念漂移
- 算法/服务扩展和维护。
在这篇文章和我们之前的文章中,我们使用了 Kafka-Python,MLflow 和 HyperparameterHunter 或 Hyperopt 来说明第一点和第二点。第 3 点、第 4 点和第 5 点将不在此讨论。关于第 3 点和第 4 点,我认为在这篇或另一篇文章中写进一步的详细解释没有多大意义(尽管如果我有时间,我会在回购协议中添加一些代码)。关于第 5 点,正如我们之前提到的,我们认为简单地将这里描述的代码和结构迁移到云中,并使其适应那里可用的工具(例如 EC2s、S3……)就足够了。
一如既往,评论/建议:jrzaurin@gmail.com
参考资料:
[1]柯,,Thomas Finley,王泰峰,,,叶启伟,,LightGBM: 一种高效的梯度推进决策树,神经信息处理系统进展,3149–3157,2017 .
[2]伯格斯特拉,詹姆斯;巴登内特,雷米;本吉奥,约书亚;Kegl,Balazs (2011),“超参数优化算法”,神经信息处理系统进展。
将“科学”放在数据科学中
大多数企业数据科学工作与实际科学几乎没有关系…
正确的科学研究始于某个模糊的特定问题或普遍的奇迹。什么过程控制着某些特定的过程?为什么我们会看到某种效果?或者说,我们如何解释一种行为?这些普遍的奇迹需要在推测或假设中被形式化,而这些推测或假设又可以被检验。这是科学探究的真正起点。另一方面,企业数据科学家太经常“看数据说什么”从概念上讲,他们的起点是方法论框架的中点。
由于许多原因,这是有问题的。没有先验知识表明数据代表当前问题、样本大小具有统计相关性、数据实际上可以回答问题或系统的行为遍历性。最重要的是,数据是可塑的;令人惊讶的是,数据很容易遵循一个给定的叙述。

Photo by Adrien Converse on Unsplash
数据科学方法
数据科学与其最接近的科学同行有很大的不同。因为与研究不同,数据科学通常是一种反应式的操作。从某种意义上说,它建立在数据捕获的基础上,并在过去的某个时间点指定。适当的研究以其演绎的法理方法为标志,通过基于概念世界观和严格规则的精心制作的假设,数据被捕获,零假设被证伪。所以数据跟着问题走,而不是反过来。
数据科学建立在这样的假设之上,即数据在正确建模后可以揭示关于世界的新信息。由于这种积极的世界观,一些“数据驱动方法”的陷阱出现了。
- 数据可用性偏差:可用的数据被认为是相关的、全面的、决定性的和最终的。数据拥有正确的变量来推导或建模任何需要的东西。没有隐藏实际预测值的数据丢失。
- 数据质量和意义 : 数据科学团队外部来源收集的数据通常不会考虑最终目的。数据不明确,时间标记错误或者有质量问题。问任何一个数据科学家,他们都会同意:他们更喜欢质量而不是数量。
- 不隐含因果关系:特别是在处理客户数据时,很容易发现变量之间的高度相关性,通过适当的想象,这些变量之间可能存在因果关系。然而,与客户“要做的工作”相关的数据不太可能被完美捕获。
- 数据没有给出规范性的指导:数据不会告诉你什么是正确的做法。它不会神奇地揭示前进的道路。它要么是基于过去事件的预测,要么是历时的时间片。
科学方法
当在结构上应用时,集成的科学方法可能会打开集成数据科学的全新领域。例如,考虑将定性导向的社会科学或行为经济学与一个更加定量导向的数据科学家团队相结合。前者可以根据最新的学术研究确定假设,后者可以确定数据要求,并用数字补充定性研究。协力推动业务获得新的洞察力。
例如,把学术行为经济学中关于激励和推动的想法带到数字世界。测试假设并迭代。不只是从你现有的数据中提取“真相”,而是重新想象在线和离线信息收集的可能性。这意味着从被动、反应式的分析转向主动的实验。从理查德·费曼所说的货物崇拜研究转向一砖一瓦建造房屋的复合研究。
虽然我必须看到第一个实现,但我不是唯一一个将这些想法概念化的人。麻省理工学院最近发表了一篇论文,强调了数据科学在客户体验方面的缺点,并提出了类似的数据科学团队扩展,例如,客户体验专家。
这种方法的改变也将极大地改变数据科学团队在组织中的位置。它将数据科学放在驾驶员座位上,推动组织变革,开发客户知识,进行试验并迫使网站变革,所有这些都基于研究和坚实的基础。最后,将科学放入数据科学。
PVANET:用于实时对象检测的深度但轻量级的神经网络
论文摘要

Fig 2. PVANET Entire Model Vizualization
一篇论文论文摘要 PVANET:深度但轻量级的神经网络用于实时物体检测 作者:、丛瑶、何文、、周树昌、何、梁家军
论文链接: 、
概观
本文介绍了我们的轻量级特征提取网络结构,命名为 PVANET,它实现了实时目标检测性能而不损失准确性。
- 计算成本:1065 x640 输入的特征提取 7.9GMAC
- 运行时性能 :英特尔 i7 上 750 毫秒/图像(1.3FPS),NVIDIA Titan X GPU 上 42 毫秒/图像(21.7FPS)
- 精度:VOC-2007 上 83.8% mAP;VOC-2012 的 82.5% mAP
关键的设计原则是“少通道多层次”。
此外,该网络还采用了其他一些构件:
- 级联校正线性单元(C.ReLU)应用于我们的细胞神经网络的早期阶段,以在不损失精度的情况下将计算量减少一半。
- 初始应用于我们特征生成子网络的剩余部分
- 采用了多尺度表示的概念,它结合了几个中间输出,以便可以同时考虑多层次的细节和非线性。
方法

Fig 2. Model Architecture
级联整流线性单元

Fig 3. Concatenated Rectified Linear Unit (C.ReLU)
C.ReLU 的动机是观察到,在早期阶段,输出节点往往是成对的,这样一个节点的激活是另一个的相反侧。C.ReLU 将输出通道的数量减少了一半,通过简单地将相同的输出与否定连接起来,使其增加了一倍,从而使早期阶段的速度提高了 2 倍。
开始

Fig4. The Inception Module
对于捕获大对象和小对象来说,Inception 可能是最具成本效益的构建块之一。他们用 2 个 3x3 取代了普通先启块中的 5x5 卷积。
超级网络
多尺度表示及其组合在许多深度学习任务中被证明是有效的。在特征提取层将细粒度的细节和高度抽象的信息结合起来,有助于后续的区域建议网络和分类网络检测不同尺度的目标。
它们组合了
1)最后一层
2)两个中间层,其规模分别是最后一层的 2x 和 4x。
深度网络培训
为了更好的训练,他们采用了剩余的结构。他们将剩余连接添加到初始层,以稳定深层网络的后期部分。
在所有 ReLU 激活层之前添加批处理规范化层。
他们使用的学习率策略是基于平台检测,他们根据损失的移动平均值检测平台,如果低于某个阈值,他们会以某个系数降低学习率。
使用 PVANET 实现更快的 R-CNN
conv3_4、conv4_4 和 conv5_4 的三个中间输出合并成 512 通道多电平输出特性,并馈入更快的 RCNN 模块
结果
- 使用 ILSVRC2012 训练图像对 PVANET 进行预处理,用于 1000 类图像分类。
- 所有图像的尺寸都调整为 256×256,192×192 的小块被随机裁剪并用作网络输入。
- 学习率最初设置为 0.1,然后每当检测到平稳状态时,以 1/sqrt(10) ~ 0.3165 的因子减少。
- 如果学习率下降到 1e-4 以下(这通常需要大约 2M 迭代),则预训练终止
- 然后用 MS-COCO trainval、VOC2007 trainval、VOC2012 trainval 的联合集对 PVANET 进行训练。之后还需要对 VOC2007 trainval 和 VOC2012 trainval 进行微调,因为 MS-COCO 和 VOC 的类定义略有不同。
- 随机调整训练图像的大小,使图像的短边介于 416 和 864 之间。
- 对于 PASCAL VOC 评估,调整每个输入图像的大小,使其短边为 640。
- 除了在非最大抑制(NMS) (=12000)和 NMS 阈值(=0.4)之前的建议框的数量之外,与更快的 R-CNN 相关的所有参数都被设置为原始工作中的参数
- 所有评估均在单核英特尔 i7 和 NVIDIA Titan X GPU 上完成。

Fig 5. Performance with VOC2007

Fig 6.Performance with VOC2012
PVANET+在 PASCAL VOC 2012 挑战赛中获得第二名。第一个是比 PVANET 重得多的更快的 RCNN + ResNet101。
参考
- 金敬姬、S. Hong、B. Roh、Y. Cheon 和 M. Park。PVANET:用于实时对象检测的深度但轻量级的神经网络。arXiv 预印本 arXiv:1608.08021,2016。
- 克里斯蒂安·塞格迪、贾、皮埃尔·塞尔马内、斯科特·里德、德拉戈米尔·安盖洛夫、杜米特鲁·埃汉、文森特·万霍克和安德鲁·拉宾诺维奇。用回旋越走越深。IEEE 计算机视觉和模式识别国际会议(CVPR)论文集,2015 年。
- 何、、、任、。用于图像识别的深度残差学习。IEEE 计算机视觉与模式识别国际会议(CVPR)论文集,2016。
- 孔涛,姚安邦,,孙富春。面向精确区域提议生成和联合目标检测的超网络。IEEE 计算机视觉和模式识别国际会议(CVPR)论文集,2016 年。
感谢阅读!一定要看报纸。如果我发现更多有趣的见解,我会更新。
py chubby——自动面部扭曲

介绍
我们都知道这个故事。你在街上随便找一个人给你和你的朋友拍照。过了一会儿,你高兴地感谢他们,继续你的生活。过了一段时间,你终于坐下来喝杯咖啡,检查你的照片。“我的上帝,为什么我们都没有笑?!"
首先,不要惊慌。今天是你的幸运日。如果你知道如何安装东西,也许会有希望。
pip install pychubby
安装完成后,只需编写如下代码:
Minimal code example

什么是 PyChubby
pychubby是一个自动化的面部扭曲工具。它的主要目标是作为深度学习人脸相关任务的专用增强界面。但也不妨作为一个傻乎乎的面部扭曲工具(见简介)。
你可能会想,当一个人可以用 Photoshop 和其他软件做类似的事情时,为什么还要费心呢?答案很简单— 自动化。你不必定位任何标志,移动它们,然后在每张照片的每张脸上重复这个过程。
像imgaug这样流行的图像增强包是通用的(任何类型的图像),并且在涉及到几何变换时不提供许多选项。pychubby专门研究人脸,允许创建几何变换:
- 局部(在脸上)
- 平滑(无伪影)
- (在一定程度上)现实
换句话说,人们不是增强整个图像,而是仅仅增强面部。增强是现实的,没有文物。
积木
pychubby的逻辑可以概括为三块
- 地标检测:给定一张照片,预训练的地标检测模型预测每张脸上的 68 个地标。
- 参考空间映射:标志被映射到所谓的参考空间。该映射校正输入图像中可能的旋转、平移和缩放。
- 手动动作定义:大多数
pychubby动作都是在参考空间中定义的,因此应该在不同的面上产生一致的扭曲。去画廊看看预定义的或者随意定义新的。
想了解更多?
如果您有兴趣尝试一下pychubby,或者只是想了解更多,请查看以下几个有用的链接:
所有潜在的贡献者都非常欢迎,任何反馈都非常感谢。
原载于 2019 年 9 月 16 日https://jank repl . github . io。
口袋里的 PyData 堆栈。字面上。

在手机上运行 Jupyter 笔记本
我最近将我的经典 iPhone SE 升级到了三星 Note 9,其中一个原因是惊人的摄像头和巨大的屏幕,“它可以运行 Ubuntu”。我知道,有点怪怪的原因。
但是没错,它确实可以通过一个叫做“Linux on Dex”的容器化层来运行 Ubuntu。这是一个允许你为 ARM64 架构运行纯 Ubuntu 16.04 桌面的应用程序。你需要的只是任何高质量的 USB-C HDMI 适配器、蓝牙键盘/鼠标套件(或者如果你使用 USB-C hub,就只有 USB 套件)和一个备用屏幕。要在 Dex 上试用 Linux,你只需要一根 USB-C 屏幕电缆,因为手机可以充当触摸板和键盘。

That is how it looks. I’ve used my wife's Apple USB-C adapter to connect screen and keyboard.
但是为什么呢?!
我的爱好之一是尝试在不同的奇怪设备上运行 Python,如 Raspberry Pi、VoCore 和 Lichee Pi Nano


Why not spend your weekend bringing upstream Linux on some cheap SBC or running Ubuntu with Anaconda on Android Tablet?
这些小而便宜的设备运行普通 Linux 和 Python 的能力让我吃惊,还记得启动我的 486DX2 66Mhz。
如果这些微小的东西可以运行常规的 Python,那么它绝对值得尝试 8 核 CPU/6GB RAM beast(是的,我有一个“廉价”版本,不是 8GB 的那个)。
根据我处理基于 ARM 的 Python 发行版的经验,这条路不会是直的,但这并不意味着它不值得一试。我已经为树莓派开发 ARM conda 包做出了贡献,所以大部分步骤我都知道。
获取 Linux on Dex 应用程序
要获得 Dex 应用上的 Linux,你必须注册测试程序。你可以在这里做:
编辑描述
play.google.com](https://play.google.com/apps/testing/com.samsung.android.lxd)
有关支持的手机和功能的更多信息,请访问网站:
安装并运行容器


Download an image after app installation and unzip the file. Then create a container and launch it


Create a nice recognizable name for your container. It will help you later.
下载和解压图像需要一些时间,所以请耐心等待。
如果下载有问题,试试直接链接。
修复 apt 包管理器
不幸的是,“Linux on Dex”中的 Ubuntu 带有坏掉的 aptitude manager。但是幸运的是社区找到了修复它的方法。
在终端中运行以下命令。在 apt-get 升级的过程中,可能会发生这样的情况,你必须通过多次按“Ctrl+C”来终止挂起过程。
sudo apt-get update
sudo apt-get upgrade
sudo purge-old-kernels
sudo apt auto remove
sudo apt autoclean
安装依赖项
Archiconda 最初是为了支持较新版本的 Ubuntu 而构建的,因此 Python 需要更新的 libc 库和其他依赖项。我们会从 Ubuntu 18.04 ARM64 发行版抓取。
[wget http://launchpadlibrarian.net/365857916/libc6_2.27-3ubuntu1_arm64.deb](http://launchpadlibrarian.net/365857916/libc6_2.27-3ubuntu1_arm64.deb) -O libc6_2.27.deb[wget http://launchpadlibrarian.net/365857921/libc-bin_2.27-3ubuntu1_arm64.deb](http://launchpadlibrarian.net/365857921/libc-bin_2.27-3ubuntu1_arm64.deb) -O libc-bin_2.27.debsudo dpkg -i libc6_2.27.deb
sudo dpkg -i libc-bin_2.27.debwget [http://launchpadlibrarian.net/365856924/locales_2.27-3ubuntu1_all.deb](http://launchpadlibrarian.net/365856924/locales_2.27-3ubuntu1_all.deb) -O locales_2.27.debsudo dpkg -i locales_2.27.debwget [https://mirror.yandex.ru/ubuntu-ports/pool/main/z/zlib/zlib1g-dbg_1.2.11.dfsg-1ubuntu2_arm64.deb](https://mirror.yandex.ru/ubuntu-ports/pool/main/z/zlib/zlib1g-dbg_1.2.11.dfsg-1ubuntu2_arm64.deb) -O zlib1g-dbg_1.2.11.deb
wget [https://mirror.yandex.ru/ubuntu-ports/pool/main/z/zlib/zlib1g-dev_1.2.11.dfsg-1ubuntu2_arm64.deb](https://mirror.yandex.ru/ubuntu-ports/pool/main/z/zlib/zlib1g-dev_1.2.11.dfsg-1ubuntu2_arm64.deb) -O zlib1g-dev_1.2.11.debsudo dpkg -i zlib1g-dbg_1.2.11.deb
sudo dpkg -i zlib1g-dev_1.2.11.deb
创建一个conda用户
由于默认dextop用户权限管理上的一些问题,conda 包管理器无法写入自己的文件夹。当然,这是可以修复的,但是创建专门的 conda 用户和管理使用它的环境更容易。通过adduser condauser创建一个用户,然后通过运行sudousermod -aG sudo condauser将该用户添加到 sudoers 中。Linux on Dex 的默认 root 密码是secret。字面上。
提供权限
需要更多的修复(测试版软件的常见情况)。如果你现在尝试 ping 任何远程主机,你会发现你没有网络权限。不知何故,在“Linux on Dex”下新创建用户没有被分配给“网络”组。
要解决这个问题,您需要编辑\etc\group文件,并通过添加condauser来修复组inet和net_raw,如下所示
inet:x:3003:root, condauser, dextop
net_raw:x:3004:root, dextop, condauser
现在,您可以通过运行su — condauser将当前用户更改为condauser
安装 Archiconda
下载并安装 ArchiConda 。我们使用的不是最新的版本,因为我们需要一些来自 archiarm 频道的包,这在 0.2.3 版本中是没有的。
wget [https://github.com/Archiconda/build-tools/releases/download/0.2.2/Archiconda3-0.2.2-Linux-aarch64.sh](https://github.com/Archiconda/build-tools/releases/download/0.2.2/Archiconda3-0.2.2-Linux-aarch64.sh)chmod +x Archiconda3-0.2.2-Linux-aarch64.sh./Archiconda3-0.2.2-Linux-aarch64.sh
现在让我们通过运行来添加更新 conda
conda update -n base --all
并添加我的频道,其中包含 Jupyter 笔记本等丢失的包
conda config — add channel gaiar
conda install -c gaiar jupyter
安装其他软件包
由于安装了 jupyter,并且 conda 可以访问包含aarch64软件包发行版的通道,我们可以按照您习惯的方式安装任何所需的软件包,例如
conda install seaborn -y
在手机上运行 Jupyter
现在,您已经为在手机上运行 Jupyter 做好了一切准备,让我们来运行 Jupyter 笔记本吧
jupyter notebook
如果前面的步骤一切顺利,您将看到类似这样的内容

Jupyter notebook server running on Note 9
下载笔记本
为测试克隆回购与 Seaborn 的例子https://github.com/PatWalters/plotting_distributions
启动笔记本并运行所有单元。然后你会看到类似的东西

Seaborn with Jupyter Notebook running fine
Pokedex(一些乐趣)
如果你觉得无聊,你甚至可以在手机上运行 jupyter 笔记本服务器,通过笔记本电脑连接。为此,您需要在“终端模式”下启动“Linux on Dex”。然后真正的乐趣开始了。尝试在电话键盘上输入前面提到的所有命令。

Luckily, Note 9 quite big
要在无头模式下运行笔记本,您需要更改几个命令。首先,确保您连接到 WiFi 网络,并获得您的本地 IP。运行ip a并搜索wlan0。记住并复制地址。
然后用命令运行 Jupyter notebook
jupyter notebook --ip YOU_IP --NotebookApp.token="token" --no-browser



1. Get your IP address. 2. Start Jupyter Notebook. 3. See progress
如果你做的一切都正确,那么你就可以在同一网络的另一台电脑上运行浏览器,并通过类似[http://YOUR_IP:8888](http://YOUR_IP:8888)的网址访问你的“服务器”。输入您的代币,享受:)

Access notebook running our the phone
附言
当前文章的大部分是直接在手机上写的

Who does need laptops these days?
PySpark 调试— 6 个常见问题

调试 spark 应用程序可以是有趣的,也可以是非常(我是说非常)令人沮丧的经历。
我已经开始收集我不时遇到的问题,列出最常见的问题及其解决方案。
这是这个列表的第一部分。我希望它对你有用,并能为你节省一些时间。大多数问题都很容易解决,但是它们的堆栈跟踪可能很神秘,没有什么帮助。

1.从 udf 返回的列为空
当您使用 udf 向数据帧添加一列,但结果为 Null 时:udf 返回的数据类型与定义的不同
例如,如果您定义一个 udf 函数,将两个数字a和b作为输入,并返回a / b,这个 udf 函数将返回一个 float(在 Python 3 中)。如果 udf 定义为:
udf_ratio_calculation = F.udf(calculate_a_b_ratio, T.BooleanType())
# or
udf_ratio_calculation = F.udf(calculate_a_b_ratio, T.DoubleType())
而不是:
udf_ratio_calculation = F.udf(calculate_a_b_ratio, T.FloatType())
那么使用 udf 的结果将是这样的:
df = df.withColumn('a_b_ratio', udf_ratio_calculation('a', 'b'))
df.show()
+---+---+---------+
| a| b|a_b_ratio|
+---+---+---------+
| 1| 0| null|
| 10| 3| null|
+---+---+---------+
完整示例:
Example of wrongly defined udf return datatype
2.ClassNotFoundException
当您试图将应用程序连接到外部系统(如数据库)时,通常会发生这种异常。下面的 stacktrace 来自于在 Postgres 中保存数据帧的尝试。

这意味着 spark 无法找到连接数据库所需的 jar 驱动程序。在实例化会话时,我们需要在 spark 配置中为应用程序提供正确的 jar
**from** pyspark **import** SparkConf
**from** pyspark.sql **import** SparkSession
conf = SparkConf()
conf.set('**spark.jars**', '**/full/path/to/postgres.jar,/full/path/to/other/jar**') spark_session = SparkSession.builder \
.config(conf=conf) \
.appName(**'**test**'**) \
.getOrCreate()
或者作为命令行参数——取决于我们如何运行我们的应用程序。
spark-submit --jars /full/path/to/postgres.jar,/full/path/to/other/jar ...
注 1:jar 对于所有节点 都是 可访问的,而不是驱动程序本地的,这一点非常重要。
注 2 :该错误也可能意味着集群组件之间的 spark 版本不匹配。还有其他更常见的指示器,如AttributeError。更多关于这个这里。
注 3 :确保罐子列表中的逗号之间没有空格。
3.低内存消耗——为什么 spark 没有用完我的所有资源?
火花驱动器存储器和火花执行器存储器默认设置为1g。一般来说,查看许多配置参数及其默认值是非常有用的,因为有许多因素会影响您的 spark 应用程序。
火花提供了三个位置来配置系统:火花属性控制大多数应用参数,可以…
spark.apache.org](https://spark.apache.org/docs/latest/configuration.html)
当 spark 在本地运行时,您应该将spark.driver.memory调整为对您的系统合理的值,例如8g,当在集群上运行时,您可能还想调整spark.executor.memory,尽管这取决于您的集群类型及其配置。
4.文件不存在:Spark 在本地模式下运行正常,但在 YARN 中运行时找不到文件
和第二步一样,所有必要的文件/jar 应该放在集群中所有组件都可以访问的地方,例如 FTP 服务器或一个普通的挂载驱动器。
spark-submit --master yarn --deploy-mode cluster http://somewhere/accessible/to/master/and/workers/test.py
5.尝试连接到数据库:Java . SQL . SQL 异常:没有合适的驱动程序

或者,如果在试图保存到数据库时发生错误,您将得到一个java.lang.NullPointerException:

这通常意味着我们忘记了设置**driver**,例如 Postgres 的**org.postgresql.Driver** :
df = spark.read.format(**'jdbc'**).options(
url= **'db_url'**,
driver='**org.postgresql.Driver**', # <-- here
dbtable=**'table_name'**,
user=**'user'**,
password=**'password'**
).load()
另外,请确保检查#2,以便正确设置驱动程序 jar。
6.NoneType '对象没有属性'_jvm'
出于各种原因,您可能会得到以下可怕的堆栈跟踪。

最常见的两种是:
- 您在没有激活 spark 会话的情况下使用 pyspark 函数
**from** pyspark.sql **import** SparkSession, functions **as** Fclass A(object):
def __init__(self):
self.calculations = F.col('a') / F.col('b')...a = A() # instantiating A without an active spark session will give you this error
- 或者在 udf 中使用 pyspark 函数:
**from** pyspark **import** SparkConf
**from** pyspark.sql **import** SparkSession, functions **as** F, types **as** T conf = SparkConf()
spark_session = SparkSession.builder \
.config(conf=conf) \
.appName(**'test'**) \
.getOrCreate()
*# create a dataframe* data = [{**'a'**: 1, **'b'**: 0}, {**'a'**: 10, **'b'**: 3}]
df = spark_session.createDataFrame(data)
df.show()
*# +---+---+
# | a| b|
# +---+---+
# | 1| 0|
# | 10| 3|
# +---+---+
# define a simple function that returns a / b
# we *cannot* use pyspark functions inside a udf
# udfs operate on a row per row basis while pyspark functions on a column basis* **def** calculate_a_b_max(a, b):
**return** F.max([a, b])
*# and a udf for this function - notice the return datatype* udf_max_calculation = F.udf(calculate_a_b_ratio, T.FloatType())
df = df.withColumn(**'a_b_max'**, udf_max_calculation(**'a'**, **'b'**))
df.show()
两者都会给你这个错误。
在最后一个例子中,F.max需要一个列作为输入,而不是一个列表,所以正确的用法应该是:
df = df.withColumn(**'a_max'**, F.max(**'a'**))
这将为我们提供列 *a* — 的最大值,而不是 udf 试图做的事情。
设置 udf 来计算每行两列之间的最大值的正确方法是:
def calculate_a_b_max(a, b):
return max([a, b])
假设a和b是数字。
(当然没有 udf 也有其他的方法。)
我希望这有所帮助。我计划继续这个列表,并及时处理更复杂的问题,比如调试 pyspark 应用程序中的内存泄漏。非常欢迎任何想法、问题、更正和建议:)
如果您想了解 Spark 的工作原理,请查看:
[## 用非技术性的方式解释技术性的东西——Apache Spark
什么是 Spark 和 PySpark,我可以用它做什么?
towardsdatascience.com](/explaining-technical-stuff-in-a-non-techincal-way-apache-spark-274d6c9f70e9)
py spark——需求预测数据科学项目
从预处理到建模的完整指南

https://upload.wikimedia.org/wikipedia/commons/f/f3/Apache_Spark_logo.svg
在本文中,我们将使用 Pyspark 构建一个逐步需求预测项目。这里,任务列表:
- 导入数据
- 过滤数据
- 功能工程(功能创建)
- 输入数据
- 功能工程(功能转换)
- 应用梯度增强树回归器
- 用 Kfold 和 GridSearch 方法优化模型
- 一次性
I)导入数据
首先,我们将使用预定义的模式导入数据。我在谷歌云平台的虚拟机上工作,数据来自云存储的一个桶。还是导入吧。
from pyspark.sql.types import *schema = StructType([
StructField("DATE", DateType()),
StructField("STORE", IntegerType()),
StructField("NUMBERS_OF_TICKETS", IntegerType()),
StructField("QTY", IntegerType()),
StructField("CA", DoubleType()),
StructField("FORMAT", StringType())])df = spark.read.csv("gs://my_bucket/my_table_in_csv_format", header = 'true', schema=schema)

II)过滤数据
然后,我们将应用一些过滤器,我们将只在大型超市工作,并确保数据中没有负数量或缺少日期。
df = df.filter(
(F.col("QTY") > 0)
&(F.col("DATE").notNull())
& (F.col("DATE").between("2015-01-01", "2019-01-01"))
& (~F.col("FORMAT").isin(["PRO", "SUP"]))
)
III)特征工程(特征创建)
然后,我们将定义一些对建模有用的变量,比如日期导数。
df = (df
.withColumn('yearday', F.dayofyear(F.col("DATE")))
.withColumn('Month', F.Month(F.col('DATE')))
.withColumn('dayofweek', F.dayofweek(F.col('DATE')))
.withColumn('YEAR', F.year(F.col('DATE')))
.withColumn('QUARTER', F.q(F.col('DATE')))
.withColumn('Month', F.Month(F.col('DATE')))
.withColumn('WeekOfYear', F.weekofyear(F.col('DATE')))
.withColumn('Week', F.date_trunc('week',F.col('DATE')))
.withColumn('MonthQuarter', F.when((df[DATE] <= 8), 0) .otherwise(F.when((df['DATE'] <= 16), 1) .otherwise(F.when((df['DATE'] <= 24), 2) .otherwise(3))))
)
现在,我们将计算每个商店的参考数量,即 1 年前同一天的销售量。然而,这一天可能没有销售,所以我们将在这个参考日期前后平均 7 天。这个函数会更复杂,需要 numpy。确实有可能把 numpy 和 spark 说清楚,让我们看看怎么做。
首先,必须定义一个用户定义的函数,以矢量化和稀疏的方式从每个存储中提取时间序列。我们将定义一个函数来创建一个稀疏向量,该向量以一年中的天数和相关量的值为索引
好吧,让我们休息一下,他有一些事情要解释。
- 输入:我们要求一个日期索引和相关数量值的列表
- 输出:我们返回一个按日索引的稀疏向量,它允许我们找到与他的日索引相关的数量
整个过程通过一个 UDF,并期望成为一个[VectorUDT](https://spark.apache.org/docs/2.1.2/api/java/org/apache/spark/mllib/linalg/VectorUDT.html)简洁地说,这是一种可以被 UDF 操纵的向量。
然后,为了响应函数请求的输入,我们将每年创建一个聚合数据帧,存储并应用一个 collect_list 到日期和数量。正如您在下面看到的,我们恢复了商店当年的所有日数量值。这两个列表进入我们的 UDF 来创建想要的向量,现在我们可以在 numpy 中处理这个向量了!

现在我们可以定义一个作用于这个向量的函数,并再次把它放到 UDF 中。
并应用它:
df= (df
.join(self_join
, ([self_join.p_id_store == df.STORE, self_join.year_join == df.year]),
how = "left"
)
.withColumn("qty_reference", getReference(F.col("yearday"), F.col("qties_vectorized")))
)
让我们详细描述一下这个函数,首先它试图找到准确的参考日,我们已经将数据帧和它的前一年连在一起,所以我们希望当前日等于向量中的索引日并得到值。如果无法检索该值,我们将围绕该关键日期定义一个窗口,并对该窗口的值进行平均。
最后的结果是:

但是我们不会让它们像那样被硬编码,而是将所有东西都打包到一个 Spark 管道中。为此,我们将创建一个继承自 Spark Transformer 对象的类模板,如下所示,我们将为每个变量重复这个模板。我不打算在本文中写完整的功能管道,但是你可以在 github 的代码中找到它。我们将在本文的结尾看到如何将这个特性的管道放在一起,并将其插入到流程中。
关于这个类模板特性的更多细节,请参阅我的文章…:https://towardsdatascience . com/py spark-wrap-your-feature-engineering-in-a-a-pipeline-ee 63 BDB 913
IV)输入数据
让我们检查是否有一些丢失的值。
df.select([F.count(F.when(F.isnan(c) | F.col(c).isNull(), c)).alias(c) for c in df.columns]).show()

可能不会有更多,但在插入新变量后可能会有一些,因此我们将定义一个将在变量创建管道后出现的估算器。
from pyspark.ml.feature import Imputer
imputer = Imputer(
inputCols=df.columns,
outputCols=["{}_imputed".format(c) for c in df.columns]
)#imputer.fit(df).transform(df)
v)特征工程(特征转换)
从下面的表格摘录中,我将向您展示在插入预测算法之前,我们将如何进行其余的数据转换。
我们将在最后一次性完成整个过程。
# needed importfrom pyspark.ml import Pipeline
from pyspark.ml.feature import PCA
from pyspark.ml.feature import StringIndexer, OneHotEncoder, VectorAssembler
索引
Spark [String Indexer](https://spark.apache.org/docs/latest/ml-features#stringindexer)将一列标签编码成一列标签索引。索引在[0,numLabels 中。映射首先由最高频率完成。
indexers = [ StringIndexer(inputCol=c, outputCol="{0}_indexedd".format(c), handleInvalid = 'error') for c in categorical_col]pip = Pipeline(stages = indexers)
fitted_df =pip.fit(df)
df = fitted_df.transform(df)

OneHotEncoding
Spark 的[OneHotEncoder](https://spark.apache.org/docs/latest/ml-features#onehotencoder) One-hot 编码将表示为标签索引的分类特征映射到一个二进制向量,该向量最多具有一个单值,指示所有特征值集合中特定特征值的存在。对于字符串类型的输入数据,通常首先使用 StringIndexer 对分类特征进行编码。
indexers = [ StringIndexer(inputCol=c, outputCol="{0}_indexedd".format(c), handleInvalid = 'error') for c in categorical_col]encoders = [OneHotEncoder(dropLast=True,inputCol=indexer.getOutputCol(),
outputCol="{0}_encodedd".format(indexer.getOutputCol())) for indexer in indexers]pip = Pipeline(stages = indexers + encoders)
fitted_df =pip.fit(df)
df = fitted_df.transform(df)

PCA 简化和向量
在执行我们的管道之前,让我们检查一下在我们的流程中是否有任何令人讨厌的丢失值。在运行降维算法之前,我们必须将所有变量传递到一个向量汇编器中,该汇编器将返回所有数据的稀疏表示。然后,PCA 算法将采用这个向量来“简化”它,并在 dataframe 列中返回另一个稀疏表示。

我们看到了如何索引、一键编码我们的数据、应用主成分分析并将所有内容放入准备建模的向量中。显然还有其他的可能性来标准化、规范化…等等。然而,全球原则保持不变。现在我们的特征已经有了很好的形状,可以通过 Pyspark 算法进行建模。
- 分割数据集
X_train = final_dataset.filter(F.col('DATE').between("2015-01-02", "2018-06-01"))X_test = final_dataset.filter(F.col('DATE') > "2018-06-01")X_train = X_train.withColumn(target, F.log1p(F.col(target)))X_test = X_test.withColumn(target, F.log1p(F.col(target)))
VI)应用梯度增强树回归器
我们将训练一个梯度提升树模型,对商店每天销售的总数量进行回归。
target = 'QTY'gbt = GBTRegressor(featuresCol = 'Features', labelCol=target)fitted = gbt.fit(X_train)yhat = (fitted.transform(X_test)
.withColumn("prediction", F.expm1(F.col("prediction")))
.withColumn(target, F.expm1(F.col(target)))
).select(F.col("prediction"), F.col("STORE").alias('SID_STORE'), F.col("DATE").alias("ID_DAY")).show()

计算 KPI
我们将定义一个 python 对象,以此为基础在不同的指标上评估我们的模型。
eval_ = RegressionEvaluator(labelCol= target, predictionCol= "prediction", metricName="rmse")rmse = eval_.evaluate(yhat)
print('rmse is %.2f', %rmse)mae = eval_.evaluate(yhat, {eval_.metricName: "mae"})
print('mae is %.2f', %mae)r2 = eval_.evaluate(yhat, {eval_.metricName: "r2"})
print('R² is %.2f', %r2)
- r 是 0.84
- 均方根误差为 20081.54
- mae 是 13289.10
模型看起来不错,我们会努力改进它。
VII)使用 Kfold 和 GridSearch 方法优化模型
我们将尝试使用不同的参数来优化我们的 GBDT,并制作一个 kfold 以确保其鲁棒性。
from pyspark.ml.tuning import CrossValidator, ParamGridBuilderparamGrid = (ParamGridBuilder()
.addGrid(gbt.maxDepth, [5, 8, 10, 12])
.addGrid(gbt.maxBins, [32, 64])
.build())cv = CrossValidator(estimator=gbt,
estimatorParamMaps=paramGrid,
evaluator=eval_,
numFolds=3) cvModel = cv.fit(X_train)yhat = (cvModel.transform(X_test)
.withColumn("prediction", F.expm1(F.col("prediction")))
.withColumn(target, F.expm1(F.col(target)))
)
我只多了 0.3 分,但这已经足够了!😃
附加:特性重要性:)
fi = fitted.featureImportances.toArray()import pandas as pdfeatures = [encoder.getOutputCol() for encoder in encoders] + \
[x +'_imputed' for x in numeric_col] + ['day', 'month', 'weekday', 'weekend', 'monthend', 'monthbegin', 'monthquarter', 'yearquarter']feat_imp = (pd.DataFrame(dict(zip(features, fi)), range(1))
.T.rename(columns={0:'Score'})
.sort_values("Score", ascending =False)
)

VIII)一次性
名词(noun 的缩写)b:在 features_utils 包中有所有与特性管道相关的类。
最后
本文总结了我对一个数据科学项目的 Pyspark 各种砖块的简要介绍。我希望它们能帮助哪怕是一个人的工作。Pyspark 是一个非常强大的大容量工具。他不具备像 sklearn 那样的一系列算法,但他拥有主要的算法和许多资源。
如果您想让我为 Pyspark 做些什么,请随时告诉我,谢谢!
【Y】You 可以在这里找到代码:https://github.com/AlexWarembourg/Medium
py spark–特征工程(P1)
计算、聚合、转换任何数据

https://upload.wikimedia.org/wikipedia/commons/f/f3/Apache_Spark_logo.svg
在本文中,我们将看到如何通过连接、窗口函数、UDF 和向量操作来计算新变量。
提醒一下,下面是我们使用的表格:

。使用列和 sql 函数
要在 Spark 上创建新的列,只需传递函数。withColumn 并添加 sql 函数
df = (df
.withColumn('dayofyear', F.dayofyear(F.col("ID_DAY")))
.withColumn('Month', F.Month(F.col('ID_DAY')))
.withColumn('ratio_ticket_qty', F.col('F_TOTAL_QTY')/F.col('NB_TICKET'))
)

更多请看这里: SQL 函数
。条件为 f 的列
与 sql 一样,可以使用 when 框进行条件计算。
df = df.withColumn("day_to_xmas", F.when((F.col("ID_DAY").between("2018-12-01", "2018-12-31")) | (F.col('ID_DAY').between("2019-12-01", "2019-12-31")),
F.lit('xmas_is_coming')).otherwise(F.datediff(F.col("ID_DAY"), F.lit('2018-12-01').cast(DateType())))
)

。带列和窗口功能
窗口函数对于计算时间轴上的值或保存用户连接非常有用
grouped_windows = Window.partitionBy(F.col('SID_STORE'), F.col('Month'))rolling_windows = (Window.orderBy(F.col("dayofyear").cast(IntegerType())).rangeBetween(-7, 0))df = (df
.withColumn('rolling_average', F.avg("F_TOTAL_QTY").over(rolling_windows))
.withColumn('monthly_qty', F.avg('F_TOTAL_QTY').over(grouped_windows))
)

只是一个连接
我们可以通过连接对 monthly_qty 进行与 windows 函数完全相同的计算。
month_qty = df.groupBy('SID_STORE', 'Month').agg(F.avg('F_TOTAL_QTY').alias('monthly_qty_by_join'))
df = df.join(month_qty, how = "left", on = ["SID_STORE", "Month"])

用户定义的函数(UDF)
我们可以像在 python 上一样在 pyspark 上定义函数,但它不会(直接)与我们的 spark 数据框架兼容。为此,我们需要定义一个 UDF(用户定义的函数),它将允许我们在 Spark 数据帧上应用我们的函数。缺点是 UDF 可能很长,因为它们是逐行应用的。要应用一个 UDF,只需将它作为函数的修饰者,用一种与其输出相关联的数据类型来添加即可。
**from** pyspark.sql.functions **import** udf@udf("long")
**def** squared_udf(s):
**return** s * sdf = df.withColumn('squared_col', squared_udf(F.col('my_columns')))
仅此而已。
向量 UDT 和 numpy 处理复杂数组操作
现在,我们将计算每个商店的参考数量,即 1 年前同一天的销售量。然而,这一天可能没有销售,所以我们将在这个参考日期前后平均 7 天。这个功能会更复杂,需要 numpy。T t 确实有可能把 numpy 和 spark 说清楚,我们来看看怎么做。
首先,必须定义一个用户定义的函数,以矢量化和稀疏的方式从每个存储中提取时间序列。我们之前已经了解了什么是 UDF,我们将定义一个稀疏向量,它用一年中的天数和相关量的值来索引
好吧,让我们休息一下,他有一些事情要解释。
- 输入:我们要求一个日期索引和相关数量值的列表
- 输出:我们返回一个按日索引的稀疏向量,这允许我们找到与他的日索引相关的数量
整个过程通过一个 UDF,并期望成为一个[VectorUDT](https://spark.apache.org/docs/2.1.2/api/java/org/apache/spark/mllib/linalg/VectorUDT.html)简洁地说,这是一种可以被 UDF 操纵的向量。
然后,为了响应函数请求的输入,我们将每年创建一个聚合数据帧,存储并应用一个 collect_list 到日期和数量。正如您在下面看到的,我们恢复了商店当年的所有日数量值。这两个列表进入我们的 UDF 来创建想要的向量,现在我们可以在 numpy 中处理这个向量了!

现在我们可以定义一个作用于这个向量的函数,并再次把它放到 UDF 中。
并应用它:
df= (df
.join(self_join
, ([self_join.p_id_store == df.SID_STORE, self_join.year_join == df.year]),
how = "left"
)
.withColumn("qty_reference", getReference(F.col("yearday"), F.col("qties_vectorized")))
)
让我们详细描述一下这个函数,首先它试图找到准确的参考日,我们已经将数据帧和它的前一年连接在一起,所以我们希望当前日等于向量中的索引日并得到值。如果无法检索该值,我们将围绕该关键日期定义一个窗口,并对该窗口的值进行平均。
最后的结果是:

更快的..更强壮的..再进一步!将您的特征工程包装在管道中!
在这里,我写的另一篇文章介绍了如何创建一个定制管道来一次性集成所有这些特性:
最后
现在,您已经构建了令人惊叹的特征,您需要索引您的分类特征,或者对它们进行一次性编码,并且可能应用一些特征缩减,然后将所有内容打包到一个向量组装器中进行建模!不介意这是下一篇:)
【Y】You 可以在这里找到代码:https://github.com/AlexWarembourg/Medium
面向数据科学工作流的 PySpark

生产中的数据科学第 6 章
在 PySpark 中展示的经验是雇主在建立数据科学团队时最需要的能力之一,因为它使这些团队能够拥有实时数据产品。虽然我之前已经写过关于【py spark】并行化 和UDF的博客,但我想作为本书第* 章提供一个关于这个主题的适当概述。我分享这完整的一章,因为我想鼓励数据科学家采用 PySpark 作为工具。这篇文章中的所有代码示例都可以在 这里 找到,所有先决条件都包含在示例章节 这里 中。你可能想在潜水前吃点零食!*
6 PySpark 用于批量管道
Spark 是一个通用计算框架,可以扩展到海量数据。它建立在 Hadoop 和 MapReduce 等先前的大数据工具之上,同时在其支持的语言的表达能力方面提供了显著的改进。Spark 的核心组件之一是弹性分布式数据集(RDD),它使机器集群能够在一个协调、容错的过程中执行工作负载。在 Spark 的最新版本中,Dataframe API 在 rdd 之上提供了一个抽象,类似于 R 和 Pandas 中的相同数据结构。PySpark 是 Spark 的 Python 接口,它提供了在分布式计算环境中处理大规模数据集的 API。
PySpark 对于数据科学家来说是一个非常有价值的工具,因为它可以简化将原型模型转化为生产级模型工作流的过程。在 Zynga,我们的数据科学团队拥有许多生产级系统,为我们的游戏和营销团队提供有用的信号。通过使用 PySpark,我们已经能够减少工程团队从概念到生产扩大模型所需的支持量。
到目前为止,在本书中,我们构建和部署的所有模型都是针对单台机器的。虽然我们能够使用 Lambda、ECS 和 GKS 将模型服务扩展到多台机器,但是这些容器是孤立工作的,并且在这些环境中的节点之间没有协调。使用 PySpark,我们可以构建模型工作流,这些工作流设计为在集群环境中运行,用于模型训练和模型服务。结果是,数据科学家现在可以解决比以前使用 Python 工具可能解决的大得多的问题。PySpark 在一种富于表现力的编程语言和 API 之间提供了一个很好的折衷,Spark 与 MapReduce 等更传统的选项相比。总的趋势是,随着更多的数据科学和工程团队转向 Spark 生态系统,Hadoop 的使用正在下降。在第 7 章中,我们将探索另一个用于数据科学的分布式计算生态系统,称为云数据流,但目前 Spark 是该领域的开源领导者。PySpark 是我在数据科学工作流中从 R 转向 Python 的主要动机之一。
本章的目的是向 Python 程序员介绍 PySpark,展示如何为批量评分应用程序构建大规模模型管道,其中可能有数十亿条记录和数百万用户需要评分。虽然生产级系统通常会将结果推送到应用程序数据库,但在本章中,我们将重点关注从数据湖中提取数据并将结果推回到数据湖以供其他系统使用的批处理过程。我们将探索为 AWS 和 GCP 执行模型应用程序的管道。虽然本章中使用的数据集依赖于 AWS 和 GCP 进行存储,但 Spark 环境不必运行在这些平台上,而是可以运行在 Azure、其他云或 on-pem Spark 集群上。
我们将在本章中讨论各种不同的主题,展示 PySpark 在可伸缩模型管道中的不同用例。在展示了如何在 S3 上为 Spark 提供数据之后,我们将重点介绍 PySpark 的一些基础知识,重点是数据帧操作。接下来,我们将构建一个预测模型管道,从 S3 读入数据,执行批量模型预测,然后将结果写入 S3。接下来,我们将展示一个名为 Pandas UDFs 的新功能如何与 PySpark 一起使用,以执行分布式深度学习和功能工程。最后,我们将使用 GCP 构建另一个批处理模型管道,然后讨论如何在 Spark 生态系统中实现工作流的产品化。
6.1 火花环境
配置 Spark 集群和向集群提交命令以供执行的方法有很多种。当作为一名数据科学家开始使用 PySpark 时,我的建议是使用一个免费的笔记本环境,以便尽快使用 Spark。虽然对于大规模工作流,PySpark 的性能可能不如 Java 或 Scala,但在交互式编程环境中开发的便利性是值得权衡的。
根据您的组织,您可能会从零开始使用 Spark,或者使用现有的解决方案。以下是我在实践中见过的 Spark 部署类型:
- 自托管:一个工程团队管理一组集群,并提供控制台和笔记本访问。
- 云解决方案: AWS 提供了一个名为 EMR 的托管 Spark 选项,GCP 有 Cloud DataProc。
- 供应商解决方案: Databricks、Cloudera 和其他供应商提供完全托管的 Spark 环境。
在选择 Spark 生态系统时,有许多不同的因素需要考虑,包括成本、可扩展性和功能集。当您使用 Spark 扩展团队规模时,还需要考虑生态系统是否支持多租户,即多个作业可以在同一个集群上并发运行,以及隔离,即一个作业失败不会影响其他作业。自托管解决方案需要大量的工程工作来支持这些额外的考虑,因此许多组织为 Spark 使用云或供应商解决方案。在本书中,我们将使用 Databricks 社区版,它提供了在协作笔记本环境中学习 Spark 所需的所有基本功能。
Spark 是一个快速发展的生态系统,随着平台的发展,很难写出关于这个主题的书籍不会很快过时。另一个问题是,对于大多数编码示例,许多书籍都将 Scala 作为目标,而不是 Python。我给想要更深入挖掘 Spark 生态系统的读者的建议是,探索基于更广泛的 Spark 生态系统的书籍,如(Karau et al. 2015 )。您可能需要通读 Scala 或 Java 代码示例,但是所涵盖的大部分内容都与 PySpark 相关。
火花簇
Spark 环境是一个机器集群,有一个驱动节点和一个或多个工作节点。驱动程序机器是集群中的主节点,负责协调要执行的工作负载。通常,在 Spark 数据帧上执行操作时,工作负载将分布在工作节点上。但是,当使用本机 Python 对象(如列表或字典)时,对象将在驱动程序节点上实例化。
理想情况下,您希望所有的工作负载都在 worker 节点上运行,以便要执行的步骤分布在整个集群中,而不会受到驱动程序节点的限制。然而,在 PySpark 中有一些类型的操作,驱动程序必须执行所有的工作。最常见的情况是在工作负载中使用 Pandas 数据帧。当您使用toPandas或其他命令将数据集转换为 Pandas 对象时,所有数据都被加载到驱动程序节点的内存中,这可能会在处理大型数据集时使驱动程序节点崩溃。
在 PySpark 中,大多数命令都是延迟执行的,这意味着直到明确需要输出时才执行操作。例如,两个 Spark 数据帧之间的join操作不会立即导致执行 join 操作,这就是 Pandas 的工作方式。相反,一旦将输出添加到要执行的操作链中,就执行连接,例如显示结果数据帧的样本。Pandas 操作之间的一个关键区别是,PySpark 操作是延迟执行的,直到需要时才被拉入内存。这种方法的一个好处是,要执行的操作图可以在发送到集群执行之前进行优化。
一般来说,Spark 集群中的节点应该被认为是短暂的,因为集群可以在执行过程中调整大小。此外,一些供应商在安排作业运行时可能会启动新的集群。这意味着 Python 中的常见操作,比如将文件保存到磁盘,不会直接映射到 PySpark。相反,使用分布式计算环境意味着您在保存数据时需要使用持久性文件存储,如 S3。这对于日志记录很重要,因为工作节点可能会崩溃,并且可能无法ssh进入该节点进行调试。大多数 Spark 部署都有一个日志系统来帮助解决这个问题,但是将工作流状态记录到持久存储中是一个很好的做法。
6.1.2 Databricks 社区版
启动并运行 PySpark 的最快方法之一是使用托管笔记本环境。Databricks 是最大的 Spark 供应商,并提供了一个名为 Community Edition [ 13 的免费入门版本。我们将使用这个环境开始使用 Spark,并构建 AWS 和 GCP 模型管道。
第一步是在 Databricks 网站上为 community edition 创建一个登录名。接下来,在登录后执行以下步骤来启动测试集群:
- 单击左侧导航栏上的“Clusters”
- 单击“创建集群”
- 分配一个名称“DSP”
- 选择最新的运行时(非测试版)
- 单击“创建集群”
几分钟后,我们将建立一个集群,我们可以使用它来提交 Spark 命令。在将笔记本连接到集群之前,我们将首先设置将在本章中使用的库。我们不使用pip来安装库,而是使用 Databricks UI,它确保集群中的每个节点都安装了相同的库集。我们将使用 Maven 和 PyPI 在集群上安装库。要安装 BigQuery 连接器,请执行以下步骤:
- 单击左侧导航栏上的“Clusters”
- 选择“DSP”集群
- 单击“库”选项卡
- 选择“安装新的”
- 点击“Maven”选项卡。
- 将坐标设置为
com.spotify:spark-bigquery_2.11:0.2.2 - 单击安装
然后,UI 会将状态显示为“正在解决”,然后是“正在安装”,然后是“已安装”。我们还需要附加一些 Python 库,这些库没有预安装在新的 Databricks 集群上。安装了 Pandas 等标准库,但您可能需要升级到更新的版本,因为 Databricks 预安装的库可能会明显滞后。
要在 Databricks 上安装 Python 库,请执行与前面步骤 5 相同的步骤。接下来,不选择“Maven ”,而是选择“PyPI”。在Package下,指定要安装的软件包,然后点击“安装”。要遵循本章中的所有章节,您需要安装以下 Python 包:
- 考拉 —用于数据帧转换
- 特征工具——用于特征生成
- tensorflow —用于深度学习后端
- keras —深度学习模型
现在,您将拥有一个能够执行分布式特征工程和深度学习的集群设置。我们将从基本的 Spark 命令开始,展示新的功能,如Koalas库,然后深入探讨这些更高级的主题。设置完成后,集群库的设置应该如图 6.1 所示。为了确保一切设置成功,请重新启动集群并检查已安装库的状态。

FIGURE 6.1: Libraries attached to a Databricks cluster.
现在我们已经配置了一个集群并设置了所需的库,我们可以创建一个笔记本来开始向集群提交命令。要创建新笔记本,请执行以下步骤:
- 点击左侧导航栏上的“数据块”
- 在“常见任务”下,选择“新建笔记本”
- 指定一个名称“CH6”
- 选择“Python”作为语言
- 选择“DSP”作为集群
- 点击“创建”
结果将是一个笔记本环境,您可以开始运行 Python 和 PySpark 命令,比如print("Hello World!")。运行该命令的笔记本示例如图 6.2 所示。我们现在有了一个 PySpark 环境,可以用来构建分布式模型管道。

FIGURE 6.2: Running a Python command in Databricks.
6.2 暂存数据
数据对于 PySpark 工作流至关重要。Spark 支持各种读入数据集的方法,包括连接到数据湖和数据仓库,以及从库中加载样本数据集,比如波士顿住房数据集。由于本书的主题是构建可伸缩的管道,我们将重点关注使用与分布式工作流一起工作的数据层。为了开始使用 PySpark,我们将为 S3 上的建模管道准备输入数据,然后将数据集作为 Spark 数据帧读入。
本节将展示如何将数据转移到 S3,如何设置从 Spark 访问数据的凭证,以及如何将数据从 S3 提取到 Spark 数据帧中。第一步是在 S3 上设置一个存储桶,用于存储我们想要加载的数据集。要执行此步骤,请在命令行上运行以下操作。
*aws s3api create-bucket --bucket dsp-ch6 --region us-east-1
aws s3 ls*
在运行命令创建一个新的 bucket 之后,我们使用ls命令来验证 bucket 是否已经成功创建。接下来,我们将游戏数据集下载到 EC2 实例,然后使用cp命令将文件移动到 S3,如下面的代码片段所示。
*wget https:**//**github.com/bgweber/Twitch/raw/master/
Recommendations/games-expand.csv
aws s3 cp games-expand.csv s3:**//**dsp-ch6/csv/games-expand.csv*
除了将游戏数据集转移到 S3,我们还将从 Kaggle NHL 数据集复制 CSV 文件的子集,这是我们在第 1.5.2 节中设置的。运行以下命令,将来自 NHL 数据集的 plays 和 stats CSV 文件转移到 S3。
*aws s3 cp game_plays.csv s3:**//**dsp-ch6/csv/game_plays.csv
aws s3 cp game_skater_stats.csv
s3:**//**dsp-ch6/csv/game_skater_stats.csv
aws s3 ls s3:**//**dsp-ch6/csv/*
我们现在有了本章中代码示例所需的所有数据集。为了从 Spark 读入这些数据集,我们需要设置 S3 凭证,以便与 Spark 集群中的 S3 进行交互。
S3 全权证书
对于生产环境,最好使用 IAM 角色来管理访问,而不是使用访问键。然而,Databricks 的社区版限制了允许多少配置,所以我们将使用访问键来启动和运行本章中的示例。我们已经设置了一个从 EC2 实例访问 S3 的用户。要创建一组以编程方式访问 S3 的凭据,请从 AWS 控制台执行以下步骤:
- 搜索并选择“IAM”
- 点击“用户”
- 选择在第 3.3.2 节“S3 _ 拉姆达”中创建的用户
- 单击“安全凭证”
- 单击“创建访问密钥”
结果将是允许访问 S3 的访问密钥和秘密密钥。请将这些值保存在安全的位置,因为我们将在笔记本中使用它们来连接到 S3 上的数据集。完成本章后,建议您撤销这些凭据。
现在我们已经为访问设置了凭证,我们可以返回到 Databricks 笔记本来读取数据集。要启用对 S3 的访问,我们需要在集群的 Hadoop 配置中设置访问密钥和秘密密钥。要设置这些键,运行下面代码片段中显示的 PySpark 命令。您需要用我们刚刚为S3_Lambda角色创建的凭证替换访问和秘密密钥。
*AWS_ACCESS_KEY = "AK..."
AWS_SECRET_KEY = "dC..."**sc._jsc.hadoopConfiguration**()**.set**(
"fs.s3n.awsAccessKeyId", AWS_ACCESS_KEY)
**sc._jsc.hadoopConfiguration**()**.set**(
"fs.s3n.awsSecretAccessKey", AWS_SECRET_KEY)*
我们现在可以使用read命令将数据集读入 Spark 数据帧,如下所示。该命令使用spark上下文发布一个读取命令,并使用 CSV 输入读取器读取数据集。我们还指定 CSV 文件包含一个标题行,并希望 Spark 推断列的数据类型。当读入 CSV 文件时,Spark 急切地将数据集提取到内存中,这可能会导致较大数据集的问题。在处理大型 CSV 文件时,最佳做法是将大型数据集分割成多个文件,然后在输入路径中使用通配符读入文件。当使用其他文件格式时,比如 Parquet 或 AVRO,Spark 会缓慢地获取数据集。
*games_df = **spark.read.csv**("s3://dsp-ch6/csv/games-expand.csv",
header=True, inferSchema = True)
**display**(games_df)*
上面代码片段中的display命令是 Databricks 提供的一个实用函数,它对输入数据帧进行采样,并显示该帧的表格表示,如图 6.3 所示。它类似于 Pandas 中的head功能,但是提供了额外的功能,例如将采样数据帧转换为绘图。我们将在 6.3.3 节探索绘图功能。

FIGURE 6.3: Displaying the Dataframe in Databricks.
既然我们已经将数据加载到 Spark 数据框架中,我们就可以开始探索 PySpark 语言了,它使数据科学家能够构建生产级的模型管道。
6.3 py spark 底漆
PySpark 是一种用于探索性分析和构建机器学习管道的强大语言。PySpark 中的核心数据类型是 Spark 数据帧,它类似于 Pandas 数据帧,但被设计为在分布式环境中执行。虽然 Spark Dataframe API 确实为 Python 程序员提供了一个熟悉的接口,但是在向这些对象发出的命令的执行方式上有很大的不同。一个关键的区别是 Spark 命令是延迟执行的,这意味着像iloc这样的命令在这些对象上是不可用的。虽然使用 Spark 数据框架可能看起来有局限性,但好处是 PySpark 可以扩展到比 Pandas 大得多的数据集。
本节将介绍 Spark 数据帧的常见操作,包括持久化数据、在不同数据帧类型之间转换、转换数据帧以及使用用户定义的函数。我们将使用 NHL stats 数据集,它提供了每场比赛的玩家表现的用户级摘要。要将该数据集作为 Spark 数据帧加载,请运行下面代码片段中的命令。
*stats_df = **spark.read.csv**("s3://dsp-ch6/csv/game_skater_stats.csv",
header=True, inferSchema = True)
**display**(stats_df)*
持久化数据帧
PySpark 中的一个常见操作是将数据帧保存到持久存储中,或者从存储层读入数据集。虽然 PySpark 可以处理 Redshift 等数据库,但在使用 S3 或 GCS 等分布式文件存储时,它的性能要好得多。在本章中,我们将使用这些类型的存储层作为模型管道的输出,但是将数据转移到 S3 作为工作流中的中间步骤也很有用。例如,在 Zynga 的 AutoModel [ 14 ]系统中,我们在使用 MLlib 训练和应用模型预测之前,将特征生成步骤的输出暂存到 S3。
要使用的数据存储层取决于您的云平台。对于 AWS,S3 与 Spark 在分布式数据读写方面配合得很好。当使用 S3 或其他数据湖时,Spark 支持各种不同的文件格式来保存数据。在使用 Spark 时,Parquet 是典型的行业标准,但是除了 CSV 之外,我们还将探讨 Avro 和 ORC。Avro 是流数据管道的更好格式,ORC 在处理传统数据管道时很有用。
为了展示 Spark 支持的数据格式范围,我们将把 stats 数据集写入 AVRO,然后是 Parquet,然后是 ORC,最后是 CSV。在执行了数据 IO 的往返之后,我们将最终得到我们的初始 Spark 数据帧。首先,我们将使用下面的代码片段以 Avro 格式保存 stats 数据帧。这段代码使用 Databricks Avro writer 将数据帧以 Avro 格式写入 S3,然后使用同一个库读入结果。执行这些步骤的结果是,我们现在有一个指向 S3 上的 Avro 文件的 Spark 数据帧。由于 PySpark 延迟评估操作,Avro 文件不会被拉至 Spark 集群,直到需要从该数据集创建输出。
**# AVRO write*
avro_path = "s3://dsp-ch6/avro/game_skater_stats/"
**stats_df.write.mode**('overwrite')**.format**(
"com.databricks.spark.avro")**.save**(avro_path)*# AVRO read*
avro_df = **sqlContext.read.format**(
"com.databricks.spark.avro")**.load**(avro_path)*
Avro 是基于记录的分布式文件格式,而 Parquet 和 OR 格式是基于列的。它对我们将在第 9 章探讨的流工作流很有用,因为它压缩了分布式数据处理的记录。将 stats 数据帧保存为 Avro 格式的输出如下面的代码片段所示,其中显示了将数据帧作为 Avro 保存到 S3 时生成的状态文件和数据文件的子集。像大多数可扩展的数据格式一样,Avro 将根据指定的分区将记录写入几个文件,以便实现高效的读写操作。
*aws s3 ls s3:**//**dsp-ch6/avro/game_skater_stats/
2019-11-27 23:02:43 1455 _committed_1588617578250853157
2019-11-27 22:36:31 1455 _committed_1600779730937880795
2019-11-27 23:02:40 0 _started_1588617578250853157
2019-11-27 23:31:42 0 _started_6942074136190838586
2019-11-27 23:31:47 1486327 part-00000-tid-6942074136190838586-
c6806d0e-9e3d-40fc-b212-61c3d45c1bc3-15-1-c000.avro
2019-11-27 23:31:43 44514 part-00007-tid-6942074136190838586-
c6806d0e-9e3d-40fc-b212-61c3d45c1bc3-22-1-c000.avro*
S3 上的 Parquet 目前是在 AWS 上构建数据湖的标准方法,Delta Lake 等工具正在利用这种格式来提供高度可伸缩的数据平台。Parquet 是一种面向列的文件格式,当一个操作只访问列的一个子集时,例如使用 Spark SQL 时,这种文件格式可以提高读取效率。Parquet 是 Spark 的原生格式,这意味着 PySpark 具有用于读写这种格式的文件的内置函数。
下面的代码片段展示了一个将 stats 数据帧写成 Parquet 文件,并将结果读入一个新的数据帧的例子。在本例中,我们没有设置分区键,但是与 Avro 一样,数据帧将被分割成多个文件,以便支持高性能的读写操作。当处理大规模数据集时,使用repartition函数为文件导出设置分区键是很有用的。在本节之后,我们将使用 Parquet 作为使用 Spark 时的主要文件格式。
**# parquet out*
parquet_path = "s3a://dsp-ch6/games-parquet/"
**avro_df.write.mode**('overwrite')**.parquet**(parquet_path)*# parquet in*
parquet_df = **sqlContext.read.parquet**(parquet_path)*
ORC 是另一种与 Spark 配合良好的列格式。与 Parquet 相比,它的主要优势是可以支持更高的压缩率,但代价是增加了计算成本。我将它包含在本章中,因为一些遗留系统仍然使用这种格式。将 stats 数据帧写入 ORC 并将结果读回 Spark 数据帧的示例如下面的代码片段所示。与 Avro 格式一样,ORC write 命令会根据大小将数据帧分配给多个文件。
**# orc out*
orc_path = "s3a://dsp-ch6/games-orc/"
**parquet_df.write.mode**('overwrite')**.orc**(orc_path)*# orc in*
orc_df = **sqlContext.read.orc**(orc_path)*
为了完成文件格式的往返,我们将以 CSV 格式将结果写回 S3。为了确保我们编写的是单个文件而不是一批文件,我们将使用 coalesce 命令将数据收集到单个节点,然后再导出。这是一个在处理大型数据集时会失败的命令,通常在使用 Spark 时最好避免使用 CSV 格式。然而,CSV 文件仍然是共享数据的常用格式,因此了解如何导出到这种格式是很有用的。
**# CSV out*
csv_path = "s3a://dsp-ch6/games-csv-out/"
**orc_df.coalesce**(1)**.write.mode**('overwrite')**.format**(
"com.databricks.spark.csv")**.option**("header","true")**.save**(csv_path)
*# and CSV to finish the round trip*
csv_df = **spark.read.csv**(csv_path, header=True, inferSchema = True)*
产生的数据帧与我们第一次从 S3 读入的数据帧相同,但是如果数据类型不是很容易推断,那么 CSV 格式可能会导致问题。使用 PySpark 持久化数据时,最好使用描述持久化数据模式的文件格式。
转换数据帧
虽然在创作 PySpark 工作负载时最好使用 Spark 数据帧,但通常有必要根据您的用例在不同格式之间进行转换。例如,您可能需要执行 Pandas 操作,比如从 dataframe 中选择特定的元素。需要时,您可以使用toPandas功能将火花数据帧拉入驱动节点的存储器中。下面的代码片段显示了如何执行这个任务,显示结果,然后将 Pandas 数据帧转换回 Spark 数据帧。一般来说,在编写 PySpark 工作流时,最好避免使用 Pandas,因为它会阻止分发和扩展,但这通常是表达要执行的命令的最佳方式。
*stats_pd = **stats_df.toPandas**()stats_df = **sqlContext.createDataFrame**(stats_pd)*
为了弥合 Pandas 和 Spark 数据帧之间的差距,Databricks 引入了一个名为 Koalas 的新库,它类似于 Pandas 用于 Spark 支持的数据帧的 API。结果是,您可以编写与 Pandas 命令一起使用的 Python 代码,这些命令可以扩展到 Spark 级别的数据集。下面的代码片段展示了一个将 Spark 数据帧转换成考拉数据帧再转换回 Spark 数据帧的示例。将 stats 数据帧转换为考拉数据帧后,该代码片段显示了如何计算在冰上的平均时间以及考拉数据帧的索引。考拉的目的是为 Spark 数据帧提供一个 Pandas 接口,随着考拉库的成熟,更多的 Python 模块可能会利用 Spark。代码片段的输出显示,每场比赛在冰上的平均时间是 993 秒。
*import databricks.koalas as ksstats_ks = **stats_df.to_koalas**()
stats_df = **stats_ks.to_spark**()**print**(stats_ks['timeOnIce']**.mean**())
**print**(stats_ks.iloc[:1, 1:2])*
在本书的开发过程中,考拉仍然是初步的,只是部分实现,但它看起来为 Python 编码人员提供了一个熟悉的接口。熊猫和 Spark 数据帧都可以与考拉一起工作,下面的片段显示了如何从 Spark 到考拉再到熊猫再到 Spark,以及 Spark 到熊猫再到考拉再到 Spark。
**# spark -> koalas -> pandas -> spark*
df = **sqlContext.createDataFrame**(**stats_df.to_koalas**()**.toPandas**())*# spark -> pandas -> koalas -> spark*
df = **ks.from_pandas**(**stats_df.toPandas**())**.to_spark**()*
一般来说,在 PySpark 环境中编写代码时,您将使用 Spark 数据帧。但是,能够根据需要使用不同的对象类型来构建模型工作流是非常有用的。考拉和熊猫 UDF 为将工作负载移植到大规模数据生态系统提供了强大的工具。
转换数据
PySpark Dataframe API 为聚合、过滤、透视和汇总数据提供了各种有用的函数。虽然其中一些功能可以很好地映射到 Pandas 操作,但是我建议在 PySpark 中快速启动和运行 munging 数据的方法是使用名为 Spark SQL 的 SQL 接口来处理 Spark 中的数据帧。如果您已经在使用pandasql或framequery库,那么 Spark SQL 应该会提供一个熟悉的接口。如果您不熟悉这些库,那么 SQL 接口仍然提供了一种使用 Spark 生态系统的简单方法。我们将在本节稍后讨论 Dataframe API,但是首先从 SQL 接口开始启动和运行。
探索性数据分析(EDA)是数据科学工作流中理解数据集形状的关键步骤之一。为了在 PySpark 中完成这个过程,我们将把 stats 数据集加载到 dataframe 中,将其作为视图公开,然后计算汇总统计数据。下面的代码片段展示了如何加载 NHL stats 数据集,将其作为 Spark 的视图公开,然后对 dataframe 运行查询。然后使用 Databricks 中的display命令可视化聚合的数据帧。
*stats_df = **spark.read.csv**("s3://dsp-ch6/csv/game_skater_stats.csv",
header=True, inferSchema = True)
**stats_df.createOrReplaceTempView**("stats")new_df = **spark.sql**("""
select player_id, sum(1) as games, sum(goals) as goals
from stats
group by 1
order by 3 desc
limit 5
""")**display**(new_df)*
该代码块的输出如图 6.4 所示。它通过根据进球总数对结果进行排名,显示了 NHL 数据集中得分最高的球员。Spark 的一个强大特性是,在需要结果集之前,SQL 查询不会对数据帧进行操作。这意味着笔记本中的命令可以为 Spark 数据帧设置多个数据转换步骤,直到后面的步骤需要执行代码块中定义的操作图时才执行这些步骤。

FIGURE 6.4: Summarizing player activity.
Spark SQL 表达能力强、速度快,是我在 Spark 环境中处理大数据集的首选方法。虽然以前的 Spark 版本在使用 Dataframe API 时比 Spark SQL 表现得更好,但性能上的差异现在已经微不足道了,您应该使用能够为处理大型数据集提供最佳迭代速度的转换工具。使用 Spark SQL,您可以连接数据帧、运行嵌套查询、设置临时表,以及混合使用 Spark 操作和 SQL 操作。例如,如果您想要查看 NHL stats 数据中的进球与射门的分布,您可以在 dataframe 上运行以下命令。
***display**(**spark.sql**("""
select cast(goals/shots * 50 as int)/50.0 as Goals_per_shot
,sum(1) as Players
from (
select player_id, sum(shots) as shots, sum(goals) as goals
from stats
group by 1
having goals >= 5
)
group by 1
order by 1
"""))*
该查询将进球数与射门数的比率限制为超过 5 个进球的球员,以防止出现异常值,如守门员在强攻中得分。我们将使用display命令将结果集输出为表格,然后使用 Databricks 将输出显示为图形。许多 Spark 生态系统都有可视化结果的方法,Databricks 环境通过display命令提供了这种能力,它可以很好地处理表格和透视表数据。运行上述命令后,您可以点击图表图标并选择显示进球与射门分布的尺寸和度量,如图 6.5 所示。

FIGURE 6.5: Distribution of goals per shot.
虽然我提倡使用 SQL 来转换数据,因为它可以扩展到不同的编程环境,但是熟悉 PySpark 中的一些基本数据帧操作是很有用的。下面的代码片段显示了如何执行常见操作,包括删除列、选择列的子集以及向 Spark 数据帧添加新列。像前面的命令一样,所有这些操作都是延迟执行的。与 Pandas 有一些语法差异,但是用于转换数据集的一般命令应该是熟悉的。
*from pyspark.sql.functions import lit*# dropping columns*
copy_df = **stats_df.drop**('game_id', 'player_id')*# selection columns*
copy_df = **copy_df.select**('assists', 'goals', 'shots')*# adding columns*
copy_df = **copy_df.withColumn**("league", **lit**('NHL'))
**display**(copy_df)*
在数据帧之间执行的一个常见操作是连接。这很容易在 Spark SQL 查询中表达,但有时最好用 Dataframe API 以编程方式来实现。下面的代码片段显示了当在game_id和player_id字段上连接时,如何将两个数据帧连接在一起。作为文字的league列将与 stats 数据帧的其余部分连接在一起。这是一个简单的例子,我们在一个小数据帧上添加了一个新列,但是 Dataframe API 的连接操作可以扩展到大规模数据集。
*copy_df = **stats_df.select**('game_id', 'player_id').
**withColumn**("league", **lit**('NHL'))
df = **copy_df.join**(stats_df, ['game_id', 'player_id'])
**display**(df)*
上述连接操作的结果集如图 6.6 所示。Spark 支持各种不同的连接类型,在本例中,我们使用了一个内部连接将 league 列附加到 stats 数据帧中。

FIGURE 6.6: The dataframe resulting from the join.
还可以在数据帧上执行聚合操作,例如计算列的总和和平均值。下面的代码片段显示了一个在统计数据集中计算球员平均上场时间和总进球数的例子。groupBy命令使用player_id作为折叠数据集的列,而agg命令指定要执行的聚合。
*summary_df = **stats_df.groupBy**("player_id")**.agg**(
{'timeOnIce':'avg', 'goals':'sum'})
**display**(summary_df)*
该代码片段创建了一个包含player_id、timeOnIce和goals列的 dataframe。我们将再次使用 Databricks 中的绘图功能来可视化结果,但这次选择散点图选项。图 6.7 显示了冰上目标与时间的关系。

FIGURE 6.7: Time on ice and goal scoring plots.
我们已经通过介绍性示例在 PySpark 中启动和运行数据框架,重点关注在训练机器学习模型之前对数据进行管理的有用操作。这些类型的操作与读取和写入数据帧相结合,为在海量数据集上执行探索性分析提供了一组有用的技能。
6.3.4 熊猫 UDF
虽然 PySpark 提供了大量处理数据帧的功能,但它通常缺少 Python 库中提供的核心功能,例如 SciPy 中的曲线拟合功能。虽然可以使用toPandas函数将数据帧转换成 Python 库的 Pandas 格式,但这种方法在使用大型数据集时会失效。Pandas UDFs 是 PySpark 中的一个新特性,通过将 Pandas 转换分布在 Spark 集群中的工作节点上,帮助数据科学家解决这个问题。使用 Pandas UDF,您可以通过操作定义一个 group by,将数据集划分为足够小的数据帧,以适合工作节点的内存,然后创建一个函数,将 Pandas 数据帧作为输入参数,并返回转换后的 Pandas 数据帧作为结果。在幕后,PySpark 使用 PyArrow 库高效地将数据帧从 Spark 翻译到 Pandas,并从 Pandas 翻译回 Spark。这种方法使 Python 库(如 Keras)能够扩展到大型机器集群。
本节将通过一个示例问题,我们需要使用现有的 Python 库,并展示如何使用 Pandas UDFs 将工作流转换为可伸缩的解决方案。我们希望回答的问题是理解统计数据集中的shots和hits属性之间是正相关还是负相关。为了计算这个关系,我们可以使用 SciPy 中的leastsq函数,如下面的代码片段所示。该示例为单个 player_id 创建了一个 Pandas 数据帧,然后在这些属性之间拟合了一个简单的线性回归。输出是用于拟合最小二乘运算的系数,在这种情况下,击球次数与击球次数没有很强的相关性。
*sample_pd = **spark.sql**("""
select * from stats
where player_id = 8471214
""")**.toPandas**()*# Import python libraries*
from scipy.optimize import leastsq
import numpy as np*# Define a function to fit*
def **fit**(params, x, y):
**return** (y - (params[0] + x * params[1] ))
*# Fit the curve and show the results*
result = **leastsq**(fit, [1,0], args=(sample_pd.shots,sample_pd.hits))
**print**(result)*
现在我们想对 stats 数据集中的每个球员执行这个操作。为了扩展到这个卷,我们将首先通过player_id进行分区,如下面代码片段中的groupBy操作所示。接下来,我们将使用 apply 命令为每个分区数据集运行analyze_player函数。用作该操作输入的stats_df数据帧和返回的players_df数据帧是火花数据帧,而由分析播放器功能返回的sampled_pd数据帧和数据帧是熊猫。Pandas UDF 注释为 PySpark 提供了一个关于如何分配工作负载的提示,这样它就可以跨工作节点集群扩展操作,而不是急切地将所有数据拉至驱动节点。像大多数 Spark 操作一样,Pandas UDFs 是延迟求值的,直到需要输出值时才会执行。
我们最初的例子现在翻译成使用熊猫 UDF 如下所示。在定义了要包含的附加模块之后,我们指定将从操作中返回的数据帧的模式。schema对象定义了应用分析播放器函数返回的 Spark 数据帧的结构。代码块中的下一步列出了一个注释,该注释将此函数定义为分组映射操作,这意味着它作用于数据帧而不是标量值。和以前一样,我们将使用leastsq函数来拟合镜头和点击属性。在计算了曲线拟合的系数之后,我们用玩家 id 和回归系数创建了一个新的熊猫数据帧。该代码块末尾的display命令将强制熊猫 UDF 执行,这将为数据集中的每个玩家创建一个分区,应用最小二乘运算,并将结果合并回一个大的 Spark 数据帧中。
**# Load necessary libraries*
from pyspark.sql.functions import pandas_udf, PandasUDFType
from pyspark.sql.types import *
import pandas as pd*# Create the schema for the resulting data frame*
schema = **StructType**([**StructField**('ID', **LongType**(), True),
**StructField**('p0', **DoubleType**(), True),
**StructField**('p1', **DoubleType**(), True)])*# Define the UDF, input and outputs are Pandas DFs*
@**pandas_udf**(schema, PandasUDFType.GROUPED_MAP)
def **analize_player**(sample_pd):
*# return empty params in not enough data*
**if** (**len**(sample_pd.shots) <= 1):
return **pd.DataFrame**({'ID': [sample_pd.player_id[0]],
'p0': [ 0 ], 'p1': [ 0 ]})
*# Perform curve fitting*
result = **leastsq**(fit, [1, 0], args=(sample_pd.shots,
sample_pd.hits))
*# Return the parameters as a Pandas DF*
return **pd.DataFrame**({'ID': [sample_pd.player_id[0]],
'p0': [result[0][0]], 'p1': [result[0][1]]})
*# perform the UDF and show the results*
player_df = **stats_df.groupby**('player_id')**.apply**(analyze_player)
**display**(player_df)*
Pandas UDFs 提供的关键功能是,只要您有好的数据分区方法,它们就能使 Python 库在分布式环境中使用。这意味着像 Featuretools 这样最初并不是为在分布式环境中工作而设计的库,可以扩展到大型集群。图 6.8 显示了在统计数据集上应用上述熊猫 UDF 的结果。这个特性支持不同数据帧格式之间的无缝转换。

FIGURE 6.8: The output dataFrame from the Pandas UDF.
为了进一步展示熊猫 UDF 的价值,我们将把它们应用于分布特征生成管道和深度学习管道。然而,在工作流中使用 Pandas UDFs 时存在一些问题,因为它们会使调试变得更加困难,有时会由于 Spark 和 Pandas 之间的数据类型不匹配而失败。
最佳实践
虽然 PySpark 为 Python 程序员提供了一个熟悉的环境,但是最好遵循一些最佳实践来确保您高效地使用 Spark。以下是我根据自己将一些投影从 Python 移植到 PySpark 的经验编写的一组建议:
- 避免使用字典:使用字典这样的 Python 数据类型意味着代码可能无法在分布式模式下执行。不要使用键来索引字典中的值,可以考虑向 dataframe 中添加另一列来用作过滤器。该建议适用于其他 Python 类型,包括不可在 PySpark 中分发的列表。
- 限制熊猫使用:调用
toPandas将导致所有数据被加载到驱动程序节点的内存中,并阻止操作以分布式模式执行。当数据已经聚合并且您想要使用熟悉的 Python 绘图工具时,使用此函数是很好的,但是它不应该用于大型数据帧。 - 避免循环:除了使用 for 循环,通常还可以使用 group by 和 apply 等函数方法来达到相同的结果。使用这种模式意味着代码可以被支持的执行环境并行化。我注意到,专注于在 Python 中使用这种模式也导致了更容易翻译成 PySpark 的代码的清理。
- 最小化急切操作:为了让您的管道尽可能地可伸缩,最好避免将完整数据帧拉入内存的急切操作。例如,在 CSV 中读取是一个急切的操作,我的工作是将数据帧存放到 S3 作为拼花,然后在后面的管道步骤中使用它。
- 使用 SQL:Python 和 PySpark 中都有针对数据帧提供 SQL 操作的库。如果你正在用别人的 Python 代码工作,破译熊猫的一些操作正在实现什么可能是棘手的。如果您计划将代码从 Python 移植到 PySpark,那么使用 Pandas 的 SQL 库可以使这种转换更容易。
通过在编写 PySpark 代码时遵循这些最佳实践,我已经能够改进我的 Python 和 PySpark 数据科学工作流。
6.4 MLlib 批处理管道
既然我们已经介绍了使用 PySpark 加载和转换数据,现在我们可以使用 PySpark 中的机器学习库来构建预测模型。PySpark 中构建预测模型的核心库称为 MLlib。这个库提供了一套监督和非监督算法。虽然该库没有完全涵盖 sklearn 中的所有算法,但它为数据科学工作流所需的大多数类型的操作提供了功能。在本章中,我们将展示如何将 MLlib 应用于分类问题,并将模型应用程序的输出保存到数据湖中。
*games_df = **spark.read.csv**("s3://dsp-ch6/csv/games-expand.csv",
header=True, inferSchema = True)
**games_df.createOrReplaceTempView**("games_df")games_df = **spark.sql**("""
select *, row_number() over (order by rand()) as user_id
,case when rand() > 0.7 then 1 else 0 end as test
from games_df
""")*
管道中的第一步是加载我们想要用于模型训练的数据集。上面的代码片段展示了如何加载游戏数据集,并使用 Spark SQL 将两个附加属性添加到加载的数据帧中。运行该查询的结果是,大约 30%的用户将被分配一个测试标签,我们将使用该标签进行模型应用,并且每个记录都被分配一个唯一的用户 ID,我们将在保存模型预测时使用该 ID。
下一步是将数据集分成训练和测试数据帧。对于这个管道,我们将使用测试数据帧作为模型应用的数据集,在这里我们预测用户行为。下面的代码片段显示了一个使用测试列拆分数据帧的示例。这应该会产生大约 16.1k 的培训用户和 6.8k 的测试用户。
*trainDF = **games_df.filter**("test == 0")
testDF = **games_df.filter**("test == 1")
**print**("Train " + **str**(**trainDF.count**()))
**print**("Test " + **str**(**testDF.count**()))*
向量列
MLlib 要求使用 Spark 中的矢量数据类型格式化输入数据。要将 dataframe 转换成这种格式,我们可以使用VectorAssembler类将一系列列组合成一个向量列。下面的代码片段展示了如何使用这个类将 dataframe 中的前 10 列合并到一个名为features的新向量列中。使用transform函数将该命令应用于训练数据帧后,我们使用选择函数仅从数据帧中检索模型训练和应用所需的值。对于训练数据帧,我们只需要标签和特征,对于测试数据帧,我们还需要选择用户 ID。
*from pyspark.ml.feature import VectorAssembler*# create a vector representation*
assembler = **VectorAssembler**(
inputCols= trainDF.schema.names[0:10],
outputCol="features" )trainVec = **assembler.transform**(trainDF)**.select**('label', 'features')
testVec = **assembler.transform**(testDF)**.select**(
'label', 'features', 'user_id')
**display**(testVec)*
display 命令显示了将我们的测试数据集转换成 MLlib 可用的向量类型的结果。输出数据帧如图 6.9 所示。

FIGURE 6.9: The features in the sparse vector format.
模型应用
现在我们已经准备好了训练和测试数据集,我们可以使用 MLlib 提供的逻辑回归算法来拟合训练数据帧。我们首先创建一个逻辑回归对象,并定义用作标签和特征的列。接下来,我们使用fit函数在训练数据集上训练模型。在下面代码片段的最后一步,我们使用transform函数将模型应用到我们的测试数据集。
*from pyspark.ml.classification import LogisticRegression*# specify the columns for the model*
lr = **LogisticRegression**(featuresCol='features', labelCol='label')*# fit on training data*
model = **lr.fit**(trainVec)*# predict on test data*
predDF = **model.transform**(testVec)*
产生的 dataframe 现在有一个probability列,如图 6.10 所示。该列是一个 2 元素数组,包含类 0 和 1 的概率。为了在测试数据集上测试逻辑回归模型的准确性,我们可以使用 Mlib 中的二元分类评估器来计算 ROC 指标,如下面的代码片段所示。对于我运行的模型,ROC 指标的值是 0.761。
*from pyspark.ml.evaluation import BinaryClassificationEvaluatorroc = **BinaryClassificationEvaluator**()**.evaluate**(predDF)
**print**(roc)*
在生产管道中,没有需要预测的用户标签,这意味着您需要执行交叉验证来选择最佳模型进行预测。第 6.7 节介绍了这种方法的一个例子。在这种情况下,我们使用单个数据集来保持代码示例的简短,但是在生产工作流中可以使用类似的管道。
既然我们有了测试用户的模型预测,我们需要检索预测的标签,以便创建一个数据帧来持久化数据湖。由于 MLlib 创建的概率列是一个数组,我们需要定义一个 UDF 来检索第二个元素作为我们的倾向列,如下面的代码片段所示。
*from pyspark.sql.functions import udf
from pyspark.sql.types import FloatType*# split out the array into a column*
secondElement = **udf**(lambda v:**float**(v[1]),**FloatType**())
predDF = **predDF.select**("*",
**secondElement**("probability")**.alias**("propensity"))
**display**(predDF)*

FIGURE 6.10: The dataframe with propensity scores.
运行该代码块后,dataframe 将有一个名为propensity的附加列,如图 6.10 所示。这个批量预测管道的最后一步是将结果保存到 S3。我们将使用select函数从预测数据帧中检索相关的列,然后在数据帧上使用write函数将结果持久化为 S3 上的拼花。
**# save results to S3*
results_df = **predDF.select**("user_id", "propensity")
results_path = "s3a://dsp-ch6/game-predictions/"
**results_df.write.mode**('overwrite')**.parquet**(results_path)*
我们现在已经拥有了创建 PySpark 管道所需的所有构件,该管道可以从存储层获取数据,训练预测模型,并将结果写入持久存储。我们将在第 6.8 节讨论如何安排这种类型的管道。
开发模型时,检查输出以查看模型预测的分布是否与预期匹配是很有用的。我们可以使用 Spark SQL 对模型输出执行聚合,然后使用 display 命令直接在 Databricks 中执行这个过程,如下面的代码片段所示。对模型预测执行这些步骤的结果如图 6.11 所示。
**# plot the predictions*
predDF **.createOrReplaceTempView**("predDF ")plotDF = **spark.sql**("""
select cast(propensity*100 as int)/100 as propensity,
label, sum(1) as users
from predDF
group by 1, 2
order by 1, 2
""")*# table output*
**display**(plotDF)*

FIGURE 6.11: The distribution of propensity scores.
MLlib 可以使用大量算法应用于各种各样的问题。虽然我们在本节中探讨了逻辑回归,但是库提供了许多不同的分类方法,并且还支持其他类型的操作,包括回归和聚类。
6.5 分布式深度学习
虽然 MLlib 为经典的机器学习算法提供了可扩展的实现,但它本身并不支持深度学习库,如 Tensorflow 和 PyTorch。有一些库可以在 Spark 上并行化深度学习模型的训练,但数据集需要能够适应每个工作节点上的内存,这些方法最适合用于中等规模数据集的分布式超参数调优。
对于模型应用阶段,我们已经有了一个经过训练的深度学习模型,但需要将结果模型应用到一个大型用户群,我们可以使用 Pandas UDFs。使用 Pandas UDFs,我们可以划分和分布我们的数据集,根据 Keras 模型运行结果数据帧,然后将结果编译回一个大 Spark 数据帧。本节将展示如何使用我们在 1.6.3 节中构建的 Keras 模型,并使用 PySpark 和 Pandas UDFs 将其扩展到更大的数据集。但是,我们仍然要求用于训练模型的数据能够适合驱动程序节点上的内存。
我们将使用上一节中的相同数据集,其中我们将游戏数据集分为用户的训练集和测试集。这是一个相对较小的数据集,因此我们可以使用toPandas操作将 dataframe 加载到驱动程序节点上,如下面的代码片段所示。结果是一个数据框架和列表,我们可以将其作为输入来训练 Keras 深度学习模型。
**# build model on the driver node*
train_pd = **trainDF.toPandas**()
x_train = train_pd.iloc[:,0:10]
y_train = train_pd['label']*
当使用 PyPI 在 Spark 集群上安装 TensorFlow 时,安装的库版本应该是 2.0 或更高版本。这不同于我们在前面章节中使用的 TensorFlow 的版本 1。就代码片段而言,主要影响是 Tensorflow 2 现在具有内置的 AUC 功能,不再需要我们以前应用的工作流。
模型培训
我们将使用与之前相同的方法来训练一个 Keras 模型。下面的代码片段显示了如何建立一个具有输入层、后丢弃层、单个隐藏层和输出层的网络,并使用rmsprop和交叉熵损失进行优化。在模型应用阶段,我们将在 Pandas UDFs 中重用model对象来分配工作负载。
*import tensorflow as tf
import keras
from keras import models, layersmodel = **models.Sequential**()
**model.add**(**layers.Dense**(64, activation='relu', input_shape=(10,)))
**model.add**(**layers.Dropout**(0.1))
**model.add**(**layers.Dense**(64, activation='relu'))
**model.add**(**layers.Dense**(1, activation='sigmoid'))**model.compile**(optimizer='rmsprop', loss='binary_crossentropy')
history = **model.fit**(x_train, y_train, epochs=100, batch_size=100,
validation_split = .2, verbose=0)*
为了测试过度拟合,我们可以绘制训练和验证数据集的结果,如图 6.12 所示。下面的代码片段展示了如何使用 matplotlib 来显示这些数据集随时间的损失。虽然训练损失在额外的时期内继续减少,但验证损失在 20 个时期后停止改善,但随着时间的推移没有显著增加。
*import matplotlib.pyplot as pltloss = history.history['loss']
val_loss = history.history['val_loss']
epochs = **range**(1, **len**(loss) + 1)fig = **plt.figure**(figsize=(10,6) )
**plt.plot**(epochs, loss, 'bo', label='Training Loss')
**plt.plot**(epochs, val_loss, 'b', label='Validation Loss')
**plt.legend**()
**plt.show**()
**display**(fig)*

FIGURE 6.12: Training a Keras model on a subset of data.
模型应用
现在我们有了一个经过训练的深度学习模型,我们可以使用 PySpark 将其应用到可扩展的管道中。第一步是确定如何划分需要评分的用户集合。对于这个数据集,我们可以将用户群分配到 100 个不同的桶中,如下面的代码片段所示。这将每个用户随机分配到 100 个桶中的 1 个,这意味着在逐步应用组后,每个转换为 Pandas 的数据帧的大小大约是原始数据帧的 1%。如果您有一个大型数据集,您可能需要使用数千个存储桶来分发数据集,甚至更多。
**# set up partitioning for the train data frame*
**testDF.createOrReplaceTempView**("testDF ")partitionedDF = **spark.sql**("""
select *, cast(rand()*100 as int) as partition_id
from testDF
""")*
下一步是定义将应用 Keras 模型的熊猫 UDF。我们将定义一个用户 ID 和倾向得分的输出模式,如下所示。UDF 在我们之前训练的模型对象上使用predict函数,在传入的数据帧上创建一个prediction列。return 命令选择我们为 schema 对象定义的两个相关列。group by 命令使用我们的分桶方法对数据集进行分区,apply 命令跨工作节点集群执行 Keras 模型应用程序。结果是显示命令可视化的火花数据帧,如图 6.13 所示。
*from pyspark.sql.functions import pandas_udf, PandasUDFType
from pyspark.sql.types import *schema = **StructType**([**StructField**('user_id', **LongType**(), True),
**StructField**('propensity', **DoubleType**(),True)])@**pandas_udf**(schema, PandasUDFType.GROUPED_MAP)
def **apply_keras**(pd):
pd['propensity'] = **model.predict**(pd.iloc[:,0:10])
return pd[['user_id', 'propensity']]results_df=**partitionedDF.groupby**('partition_id')**.apply**(apply_keras)
**display**(results_df)*

FIGURE 6.13: The resulting dataframe for distributed Keras.
需要注意的一点是,在 Pandas UDFs 中可以引用的对象类型是有限制的。在这个例子中,我们引用了model对象,它是在训练模型时在驱动程序节点上创建的。当 PySpark 中的变量从 driver 节点转移到 workers 节点以进行分布式操作时,会制作一个变量的副本,因为在集群中同步变量是低效的。这意味着对熊猫 UDF 中的变量所做的任何更改都不会应用到原始对象。这也是为什么在使用 UDF 时应该避免使用 Python 列表和字典这样的数据类型。函数以类似的方式工作,在 6.3.4 节中,我们在 Pandas UDF 中使用了fit函数,该函数最初是在驱动程序节点上定义的。Spark 还提供了用于在集群中共享变量的广播变量,但是理想的分布式代码段应该尽可能避免通过变量共享状态。
6.6 分布式特征工程
要素工程是数据科学工作流中的关键步骤,有时有必要使用 Python 库来实现此功能。例如,Zynga 的 AutoModel 系统使用 Featuretools 库从原始跟踪事件中生成数百个特征,然后用作分类模型的输入。为了扩大我们在 1.7 节中首次探索的自动化特征工程方法,我们可以使用 Pandas UDFs 来分发特征应用过程。与前一节一样,在确定要执行的转换时,我们需要对数据进行采样,但是在应用转换时,我们可以扩展到大规模数据集。
在本节中,我们将使用 NHL Kaggle 示例中的游戏比赛数据集,其中包括每场比赛中发生的事件的详细描述。我们的目标是将深而窄的数据帧转换成浅而宽的数据帧,该数据帧将每场比赛总结为具有数百列的单个记录。在 PySpark 中加载这些数据并选择相关列的示例如下面的代码片段所示。在调用toPandas之前,我们使用 filter 函数对 0.3%的记录进行采样,然后将结果投射到一个熊猫框架中,该框架的形状为 10717 行 16 列。
*plays_df = **spark.read.csv**("s3://dsp-ch6/csv/game_plays.csv",
header=True, inferSchema = True)**.drop**(
'secondaryType', 'periodType', 'dateTime', 'rink_side')
plays_pd = **plays_df.filter**("rand() < 0.003")**.toPandas**()
plays_pd.shape*
特征生成
我们将使用 1.7 节中介绍的相同的两步过程,首先对数据帧中的分类特征进行一次性编码,然后对数据集应用深度特征合成。下面的代码片段显示了如何使用 Featuretools 库执行编码过程。输出是初始数据帧的转换,现在有 20 个虚拟变量,而不是事件和描述变量。
*import featuretools as ft
from featuretools import Featurees = **ft.EntitySet**(id="plays")
es = **es.entity_from_dataframe**(entity_id="plays",dataframe=plays_pd,
index="play_id", variable_types = {
"event": ft.variable_types.Categorical,
"description": ft.variable_types.Categorical })f1 = **Feature**(es["plays"]["event"])
f2 = **Feature**(es["plays"]["description"])encoded, defs = **ft.encode_features**(plays_pd, [f1, f2], top_n=10)
**encoded.reset_index**(inplace=True)*
下一步是使用dfs函数对我们的编码数据帧进行深度特征合成。输入数据帧将有每场比赛的记录,而输出数据帧在使用各种不同的聚合将详细事件折叠成宽列表示后,将有每场比赛的单个记录。
*es = **ft.EntitySet**(id="plays")
es = **es.entity_from_dataframe**(entity_id="plays",
dataframe=encoded, index="play_id")es = **es.normalize_entity**(base_entity_id="plays",
new_entity_id="games", index="game_id")
features, transform=**ft.dfs**(entityset=es,
target_entity="games",max_depth=2)
**features.reset_index**(inplace=True)*
与之前的方法相比,我们需要执行的一个新步骤是,我们需要确定所生成要素的方案,因为这需要作为熊猫 UDF 注记的输入。为了弄清楚为生成的数据帧生成的模式是什么,我们可以创建一个 Spark 数据帧,然后从 Spark 数据帧中检索模式。在转换 Pandas 数据帧之前,我们需要修改生成的数据帧中的列名,以删除特殊字符,如下面的代码片段所示。特性应用步骤的 Spark 模式如图 6.14 所示。
*features.columns = **features.columns.str.replace**("[(). =]", "")
schema = **sqlContext.createDataFrame**(features).schema
features.columns*

FIGURE 6.14: The schema for the generated features.
我们现在有了定义熊猫 UDF 所需的模式。与我们过去定义的 UDF 不同,模式可能会根据 Featuretools 选择的特征转换聚合在不同运行之间发生变化。在这些步骤中,我们还创建了一个定义用于编码的特性转换的defs对象和一个定义执行深度特性合成的转换的transform对象。与上一节中的模型对象一样,这些对象的副本将被传递给在 worker 节点上执行的熊猫 UDF。
特征应用
为了使我们的方法能够跨工作节点集群扩展,我们需要定义一个用于分区的列。像前面的部分一样,我们可以将事件分成不同的数据集,以确保 UDF 过程可以伸缩。与以前的一个不同之处是,我们需要将一个特定游戏中的所有游戏分组到同一个分区中。为了达到这个结果,我们可以通过game_id而不是player_id进行分区。下面的代码片段显示了这种方法的一个例子。此外,我们可以使用游戏 ID 上的散列函数来随机化该值,从而产生更平衡的桶。
**# bucket IDs*
**plays_df.createOrReplaceTempView**("plays_df")
plays_df = **spark.sql**("""
select *, abs(hash(game_id))%1000 as partition_id
from plays_df
""")*
现在,我们可以使用下面定义的熊猫 UDF 将特征变换应用于整个数据集。在被传递到生成特征函数之前,播放数据帧由桶进行分区。该函数使用先前生成的特征变换来确保相同的变换应用于所有工作节点。输入的熊猫数据帧是游戏数据的窄而深的表示,而返回的数据帧是游戏概要的浅而宽的表示。
*from pyspark.sql.functions import pandas_udf, PandasUDFType@**pandas_udf**(schema, PandasUDFType.GROUPED_MAP)
def **gen_features**(plays_pd): es = **ft.EntitySet**(id="plays")
es = **es.entity_from_dataframe**(entity_id="plays",
dataframe=plays_pd, index="play_id", variable_types = {
"event": ft.variable_types.Categorical,
"description": ft.variable_types.Categorical })
encoded_features = **ft.calculate_feature_matrix**(defs, es)
**encoded_features.reset_index**(inplace=True)
es = **ft.EntitySet**(id="plays")
es = **es.entity_from_dataframe**(entity_id="plays",
dataframe=encoded, index="play_id")
es = **es.normalize_entity**(base_entity_id="plays",
new_entity_id="games", index="game_id")
generated = **ft.calculate_feature_matrix**(transform,es)**.fillna**(0)
**generated.reset_index**(inplace=True)
generated.columns = **generated.columns.str.replace**("[(). =]","")
return generated
features_df = **plays_df.groupby**('partition_id')**.apply**(gen_features)
**display**(features_df)*
显示命令的输出如图 6.15 所示。我们现在已经完成了可扩展模型管道中的特征生成和深度学习。现在我们有了一个转换后的数据集,我们可以将结果与额外的特征结合起来,例如我们希望预测的标签,并开发一个完整的模型管道。

FIGURE 6.15: Generated features in the Spark dataframe.
6.7 GCP 模型管道
批处理模型管道的常见工作流是从湖中读取输入数据,应用机器学习模型,然后将结果写入应用数据库。在 GCP,BigQuery 充当数据湖,云 Bigtable 可以充当应用数据库。我们将在下一章使用这些组件构建端到端的管道,但是现在我们将直接在 Spark 中使用 GCP 组件的子集。
虽然 BigQuery 有一个 Spark 连接器[ 15 ],可以直接使用 BigQuery 构建大规模的 PySpark 管道,但是这个库有一些问题,使得为我们的 Databricks 环境进行设置非常复杂。例如,我们需要重新构建一些 JAR 文件并隐藏依赖关系。一种替代方法是使用我们在 5.1 节中探索的 Python BigQuey 连接器,但是这种方法不是分布式的,并且会急切地将查询结果作为 Pandas 数据帧拉至驱动程序节点。在本章中,我们将探索一个工作流,在这个工作流中,我们将查询结果卸载到云存储中,然后从 GCS 中读入数据集,作为管道中的第一步。类似地,对于模型输出,我们将结果保存到 GCS,在 GCS 中,输出可用于推送到 Bigtable。为了将这种类型的工作流程产品化,可以使用气流将这些不同的动作链接在一起。
大查询导出
我们要执行的第一步是将 BigQuery 查询的结果导出到 GCS,这可以使用 BigQuery UI 手动执行。这可以直接在 Spark 中执行,但是正如我提到的,使用当前版本的连接器库进行配置是非常复杂的。我们将为这个管道使用natality数据集,它列出了关于分娩的属性,比如出生体重。
*create table dsp_demo.natality **as** (
select *
from `bigquery-public-data.samples.natality`
order by **rand**()
limit 10000
)*
为了创建数据集,我们将从 BigQuery 中的 natality 公共数据集中抽取 10k 条记录。要将这个结果集导出到 GCS,我们需要在 BigQuery 上创建一个包含要导出的数据的表。创建这个数据样本的 SQL 如上面的代码片段所示。要将此数据导出到 GCS,请执行以下步骤:
- 浏览到 GCP 控制台
- 搜索“大查询”
- 将上面代码片段中的查询粘贴到查询编辑器中
- 单击运行
- 在左侧导航窗格中,选择新表“dsp_demo.natality”
- 单击“导出”,然后单击“导出到 GCS”
- 设置位置,“/dsp_model_store/natality/avro”
- 使用“Avro”作为导出格式
- 点击“导出”
执行这些步骤后,抽样出生率数据将以 Avro 格式保存到 GCS。导出数据集的确认对话框如图 6.16 所示。现在,我们已经将数据保存到 GCS 中,格式与 Spark 兼容。

FIGURE 6.16: Confirming the Avro export on GCS.
GCP 全权证书
我们现在有了一个数据集,可以用作 PySpark 管道的输入,但是我们还不能从我们的 Spark 环境访问 GCS 上的 bucket。借助 AWS,我们能够使用访问密钥和秘密密钥设置对 S3 的编程访问。对于 GCP,这个过程稍微复杂一些,因为我们需要将 json 凭证文件移动到集群的驱动程序节点,以便在 GCS 上读写文件。使用 Spark 的一个挑战是,您可能无法通过 SSH 访问驱动程序节点,这意味着我们需要使用持久存储将文件移动到驱动程序机器上。不建议在生产环境中使用,而是作为概念验证展示。在生产环境中管理凭证的最佳实践是使用 IAM 角色。
*aws s3 cp dsdemo.json s3:**//**dsp-ch6/secrets/dsdemo.json
aws s3 ls s3:**//**dsp-ch6/secrets/*
要将 json 文件移动到驱动程序节点,我们可以首先将凭证文件复制到 S3,如上面的代码片段所示。现在我们可以切换回 Databricks 并创建模型管道。要将文件复制到驱动节点,我们可以使用sc Spark 上下文逐行读取文件。这不同于我们之前的所有操作,在这些操作中,我们将数据集作为数据帧读入。读取文件后,我们使用 Python open和write函数在驱动节点上创建一个文件。同样,这是在 Spark 中执行的一个不寻常的操作,因为您通常希望写入持久存储,而不是本地存储。执行这些步骤的结果是,凭据文件现在将在集群中的驱动程序节点上本地可用。
*creds_file = '/databricks/creds.json'
creds = **sc.textFile**('s3://dsp-ch6/secrets/dsdemo.json')with **open**(creds_file, 'w') as file:
**for** line **in** **creds.take**(100):
**file.write**(line + "\n")*
现在我们已经将 json 凭证文件移动到了驱动程序本地存储,我们可以设置访问 GCS 上的数据所需的 Hadoop 配置。下面的代码片段显示了如何配置项目 ID、文件系统实现和凭证文件位置。运行这些命令后,我们现在可以在 GCS 上读写文件了。
***sc._jsc.hadoopConfiguration**()**.set**("fs.gs.impl",
"com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem")
**sc._jsc.hadoopConfiguration**()**.set**("fs.gs.project.id",
"your_project_id")
**sc._jsc.hadoopConfiguration**()**.set**(
"mapred.bq.auth.service.account.json.keyfile", creds_file)
**sc._jsc.hadoopConfiguration**()**.set**(
"fs.gs.auth.service.account.json.keyfile", creds_file)*
模型管道
为了读入出生数据集,我们可以使用带有 Avro 设置的 read 函数来获取数据集。由于我们使用的是 Avro 格式,dataframe 将被延迟加载,并且直到使用 display 命令对数据集进行采样时才会检索数据,如下面的代码片段所示。
*natality_path = "gs://dsp_model_store/natality/avro"
natality_df = **spark.read.format**("avro")**.load**(natality_path)
**display**(natality_df)*
在使用 MLlib 构建回归模型之前,我们需要对数据集执行一些转换,以选择一个功能子集,转换数据类型,并将记录分成训练组和测试组。我们还将使用如下所示的fillna函数,将数据帧中的任何空值替换为零。在这个建模练习中,我们将构建一个回归模型,使用一些不同的特征(包括母亲的婚姻状况和父母的年龄)来预测婴儿的出生体重。准备好的数据帧如图 6.17 所示。
***natality_df.createOrReplaceTempView**("natality_df")natality_df = **spark.sql**("""
SELECT year, plurality, apgar_5min,
mother_age, father_age,
gestation_weeks, ever_born
,case when mother_married = true
then 1 else 0 end as mother_married
,weight_pounds as weight
,case when rand() < 0.5 then 1 else 0 end as test
from natality_df
""")**.fillna**(0)trainDF = **natality_df.filter**("test == 0")
testDF = **natality_df.filter**("test == 1")
**display**(natality_df)*

FIGURE 6.17: The prepared Natality dataframe.
接下来,我们将把数据帧转换成 MLlib 需要输入的向量数据类型。转换出生率数据集的过程如下面的代码片段所示。在执行了transform函数之后,我们现在有了可以用作回归模型输入的训练和测试数据集。我们正在构建一个模型来预测的标签是weight列。
*from pyspark.ml.feature import VectorAssembler*# create a vector representation*
assembler = **VectorAssembler**(inputCols= trainDF.schema.names[0:8],
outputCol="features" )trainVec = **assembler.transform**(trainDF)**.select**('weight','features')
testVec = **assembler.transform**(testDF)**.select**('weight', 'features')*
MLlib 提供了一组用于在模型工作流中执行交叉验证和超参数调整的实用程序。下面的代码片段显示了如何对随机森林回归模型执行此过程。我们没有直接在模型对象上调用fit,而是用一个交叉验证器对象来包装模型对象,该验证器对象探索不同的参数设置,比如树的深度和数量。这个工作流程类似于 sklearn 中的网格搜索功能。在搜索整个参数空间并使用基于折叠数的交叉验证之后,在应用于对测试数据集进行预测之前,在完整的训练数据集上重新训练随机森林模型。结果是一个实际体重和预测出生体重的数据框架。
*from pyspark.ml.tuning import ParamGridBuilder
from pyspark.ml.regression import RandomForestRegressor
from pyspark.ml.tuning import CrossValidator
from pyspark.ml.evaluation import RegressionEvaluatorfolds = 3
rf_trees = [ 50, 100 ]
rf_depth = [ 4, 5 ] rf= **RandomForestRegressor**(featuresCol='features',labelCol='weight')paramGrid = **ParamGridBuilder**()**.addGrid**(rf.numTrees, rf_trees).
**ddGrid**(rf.maxDepth, rf_depth)**.build**()
crossval = **CrossValidator**(estimator=rf, estimatorParamMaps =
paramGrid, evaluator=**RegressionEvaluator**(
labelCol='weight'), numFolds = folds)
rfModel = **crossval.fit**(trainVec)
predsDF = **rfModel.transform**(testVec)**.select**("weight", "prediction")*
在 GCP 模型管道的最后一步,我们将把结果保存到 GCS,以便工作流中的其他应用程序或流程可以利用这些预测。下面的代码片段显示了如何以 Avro 格式将数据帧写入 GCS。为了确保管道的不同运行不会覆盖过去的预测,我们在导出路径上附加了一个时间戳。
*import timeout_path = "gs://dsp_model_store/natality/preds-{time}/".
**format**(time = **int**(**time.time**()*1000))
**predsDF.write.mode**('overwrite')**.format**("avro")**.save**(out_path)
**print**(out_path)*
将 GCP 组件与 PySpark 一起使用需要花费一些精力来配置,但是在本例中,我们在不同的云提供商中运行 Spark,而不是在我们读取和写入数据的地方。在生产环境中,您最有可能在与处理数据集相同的云中运行 Spark,这意味着您可以利用 IAM 角色来正确管理对不同服务的访问。
6.8 生产 PySpark
一旦您在笔记本环境中测试了批处理模型管道,就有几种不同的方法来安排管道定期运行。例如,您可能希望移动游戏的客户流失预测模型每天早上运行,并将分数发布到应用程序数据库。类似于我们在第 5 章中介绍的工作流工具,PySpark 管道应该对任何可能发生的故障进行监控。调度 PySpark 作业有几种不同的方法:
- 工作流工具: Airflow、Azkaban 和 Luigi 都支持在工作流中运行 spark 作业。
- 云工具:AWS 上的 EMR 和 GCP 上的 Dataproc 支持预定的 Spark 作业。
- 供应商工具: Databricks 支持通过 web 用户界面设置带有监控的作业计划。
- Spark Submit: 如果已经配置了集群,可以使用 crontab 等工具发出
spark-submit命令。
供应商和云工具通常更容易启动和运行,因为它们在工作流中提供了配置集群的选项。例如,使用 Databricks,您可以定义要启动的集群类型,以便按计划运行笔记本电脑。使用工作流工具(如 Airflow)时,您需要向工作流添加额外的步骤,以便启动和终止集群。大多数工作流工具都提供了到 EMR 的连接器,用于将集群作为工作流的一部分进行管理。Spark 提交选项在第一次开始调度 Spark 作业时很有用,但是它不支持将集群作为工作流的一部分来管理。
Spark 作业可以在短暂或持久的集群上运行。临时集群是一个 Spark 集群,它被提供来执行一组任务,然后被终止,比如运行一个 churn 模型管道。持久集群是长期运行的集群,可能支持交互式笔记本,例如我们在本章开始时设置的 Databricks 集群。持久集群对于开发很有用,但是如果为集群而启动的硬件没有得到充分利用,那么它的成本会很高。一些供应商支持集群的自动伸缩,以降低长期运行的持久集群的成本。临时集群是有用的,因为旋转一个新的集群来执行一个任务能够隔离跨任务的失败,并且这意味着不同的模型管道可以使用不同的库版本。
除了为调度作业和作业失败警报设置工具之外,为 Spark 模型管道设置额外的数据和模型质量检查也很有用。例如,我已经设置了执行审计任务的 Spark 作业,比如确保应用程序数据库有当天的预测,如果预测数据过时就触发警报。作为 Spark 管道的一部分,记录度量标准也是一个很好的实践,比如交叉验证模型的 ROC。我们将在第 11 章更详细地讨论这一点。
6.9 结论
PySpark 是数据科学家构建可扩展分析和建模管道的强大工具。这是公司非常需要的技能,因为它使数据科学团队能够拥有更多构建和拥有数据产品的过程。为 PySpark 设置环境的方法有很多种,在这一章中,我们探索了一个流行的 Spark 供应商提供的免费笔记本环境。
本章主要关注批处理模型管道,其目标是定期为大量用户创建一组预测。我们探索了 AWS 和 GCP 部署的管道,其中数据源和数据输出是数据湖。这些类型的管道的一个问题是,在使用预测时,预测可能已经过时。在第 9 章中,我们将探索 PySpark 的流管道,其中模型预测的延迟被最小化。
PySpark 是一种用于创作模型管道的高度表达性语言,因为它支持所有 Python 功能,但需要一些变通方法来让代码在一个 workers 节点集群上执行。在下一章中,我们将探索 Dataflow,它是 Apache Beam 库的一个运行时,也支持大规模分布式 Python 管道,但是在可以执行的操作类型方面受到更多限制。
参考
卡劳、霍尔登、安迪·孔温斯基、帕特里克·温德尔和马泰·扎哈里亚。2015.学习火花:快如闪电的大数据分析。第一版。奥莱利媒体。
13.https://community.cloud.databricks.com/↩︎14。https://www.gamasutra.com/blogs/BenWeber/20190426/340293/↩︎t11】15。https://github.com/spotify/spark-bigquery/t14】↩︎
本·韦伯是 Zynga 的一名杰出的数据科学家。我们正在招聘!
py spark–导入任何数据
使用 Spark 导入数据的简要指南

https://upload.wikimedia.org/wikipedia/commons/f/f3/Apache_Spark_logo.svg
通过这篇文章,我将开始一系列关于 Pyspark 的简短教程,从数据预处理到建模。第一个将处理任何类型的数据的导入和导出,CSV,文本文件,Avro,Json…等等。我在谷歌云平台上的一台虚拟机上工作,数据来自云存储上的一个桶。让我们导入它们。
导入 CSV
Spark 具有读取 csv 的集成功能,非常简单:
csv_2_df = spark.read.csv("gs://my_buckets/poland_ks")#print it
csv_2_df.show()

数据以正确的列数加载,数据中似乎没有任何问题,但是标题不固定。我们需要设置 header = True 参数。
csv_2_df = spark.read.csv("gs://my_buckets/poland_ks", header = "true")

还有其他可能的语法。
csv_2_df= spark.read.load("gs://my_buckets/poland_ks", format="csv", header="true")
以及 sep 这样的参数来指定分隔符或者 inferSchema 来推断数据的类型,我们顺便来看看 Schema。
csv_2_df.printSchema()

我们的 dataframe 在 string 中有所有类型的数据集,让我们尝试推断模式。
csv_2_df = spark.read.csv("gs://my_buckets/poland_ks", header =True, inferSchema=True)csv_2_df.printSchema()

我们可以手动指定我们的模式
from pyspark.sql.types import *schema = StructType([
StructField("ID_DAY", DateType()),
StructField("SID_STORE", IntegerType()),
StructField("NB_TICKET", IntegerType()),
StructField("F_TOTAL_QTY", IntegerType()),
StructField("F_VAL_SALES_AMT_TCK", DoubleType()),
StructField("SITE_FORMAT", StringType())])csv_2_df = spark.read.csv("gs://alex_precopro/poland_ks", header = 'true', schema=schema)
导入 JSON
json_to_df = spark.read.json("gs://my_bucket/poland_ks_json")
进口拼花地板
parquet_to_df = spark.read.parquet("gs://my_bucket/poland_ks_parquet")
导入 AVRO
在 Avro 的情况下,我们需要调用外部数据块包来读取它们。
df = spark.read.format("com.databricks.spark.avro").load("gs://alex_precopro/poland_ks_avro", header = 'true')
导入文本文件
同样,spark 也有一个内置功能
textFile = spark.read.text('path/file.txt')
您也可以将文本文件读取为 rdd
# read input text file to RDD
lines = sc.textFile('path/file.txt')
# collect the RDD to a list
list = lines.collect()
出口任何东西
要导出数据,你必须适应你想要的输出,如果你写在 parquet,avro 或任何分区文件没有问题。如果我们想以 CSV 格式编写,我们必须将分散在不同工作器上的分区分组,以编写我们的 CSV 文件
#partitioned file
output.write.parquet(“gs://my_bucket/my_output")#csv partitioned_output.coalesce(1).write.mode("overwrite")\
.format("com.databricks.spark.csv")\
.option("header", "true")\
.option("sep", "|")\
.save('gs://my_bucket/my_output_csv')
通过使用 coalesce(1)或 repartition(1 ),数据帧的所有分区被组合到一个块中。
最后
我们看到了如何导入我们的文件并编写它。让我们来看我的下一篇文章,学习如何过滤我们的数据框架。感谢。【Y】You 可以在这里找到代码:https://github.com/AlexWarembourg/Medium
谷歌可乐中的 PySpark
在 Colab 中使用 PySpark 创建简单的线性回归模型

Photo by Ashim D’Silva on Unsplash
随着数据池来源的拓宽,大数据主题在过去几年中受到了越来越多的关注。除了处理各种类型和形状的巨大数据之外,大数据的分析部分的目标周转时间也大大减少了。这种速度和效率不仅有助于大数据的即时分析,也有助于识别新机会。这反过来又导致采取更明智的商业行动,更高效的运营,更高的利润和更满意的客户。
Apache Spark 旨在以更快的速度分析大数据。Apache Spark 提供的一个重要特性是能够在内存中运行计算。对于在磁盘上运行的复杂应用程序,它也被认为比 MapReduce 更高效。
Spark 被设计成高度可访问的,提供了 Python、Java、Scala 和 SQL 的简单 API,以及丰富的内置库。它还与其他大数据工具紧密集成。特别是 Spark 可以在 Hadoop 集群中运行,可以访问任何 Hadoop 数据源,包括 Cassandra。
PySpark 是使用 Python 编程语言访问 Spark 的接口。PySpark 是用 python 开发的 API,用于 Spark 编程和用 Python 风格编写 spark 应用程序,尽管底层执行模型对于所有 API 语言都是相同的。
在本教程中,我们将主要处理 PySpark 机器学习库 Mllib,该库可用于导入线性回归模型或其他机器学习模型。
是的,但是为什么是 Google Colab 呢?
Google 的 Colab 基于 Jupyter Notebook,这是一个非常强大的工具,利用了 google docs 的功能。由于它运行在谷歌服务器上,我们不需要在我们的系统中本地安装任何东西,无论是 Spark 还是深度学习模型。Colab 最吸引人的特性是免费的 GPU 和 TPU 支持!由于 GPU 支持运行在谷歌自己的服务器上,事实上,它比一些商用 GPU(如 Nvidia 1050Ti)更快。分配给用户的一条常规系统信息如下所示:
Gen RAM Free: 11.6 GB | Proc size: 666.0 MB
GPU RAM Free: 11439MB | Used: 0MB | Util 0% | Total 11439MB
如果你有兴趣了解更多关于 Colab 的信息,Anna Bonner的这篇文章指出了使用 Colab 的一些显著好处。
闲聊到此为止。让我们用 Google Colab 中的 PySpark 创建一个简单的线性回归模型。
要打开 Colab Jupyter 笔记本,请点击此链接。
在 Colab 运行 Pyspark
要在 Colab 中运行 spark,首先我们需要在 Colab 环境中安装所有依赖项,如 Apache Spark 2.3.2 with hadoop 2.7、Java 8 和 Findspark,以便在系统中定位 Spark。工具安装可以在 Colab 的 Jupyter 笔记本内进行。
按照以下步骤安装依赖项:
!apt-get install openjdk-8-jdk-headless -qq > /dev/null
!wget -q [https://www-us.apache.org/dist/spark/spark-2.4.1/spark-2.4.1-bin-hadoop2.7.tgz](https://www-us.apache.org/dist/spark/spark-2.4.1/spark-2.4.1-bin-hadoop2.7.tgz)
!tar xf spark-2.4.1-bin-hadoop2.7.tgz
!pip install -q findspark
既然我们已经在 Colab 中安装了 Spark 和 Java,现在是时候设置环境路径,使我们能够在我们的 Colab 环境中运行 PySpark。通过运行以下代码设置 Java 和 Spark 的位置:
import os
os.environ["JAVA_HOME"] = "/usr/lib/jvm/java-8-openjdk-amd64"
os.environ["SPARK_HOME"] = "/content/spark-2.3.2-bin-hadoop2.7"
我们可以运行一个本地 spark 会话来测试我们的安装:
import findspark
findspark.init()
from pyspark.sql import SparkSession
spark = SparkSession.builder.master("local[*]").getOrCreate()
我们的实验室可以运行 PySpark 了。让我们建立一个简单的线性回归模型。
线性回归模型
线性回归模型是一种最古老和广泛使用的机器学习方法,它假设因变量和自变量之间存在关系。例如,建模者可能希望根据湿度比来预测降雨。线性回归由穿过图上分散点的最佳拟合线组成,最佳拟合线称为回归线。关于线性回归的详细内容可以在这里找到。
为了从 Colab 中的 Pyspark 开始并保持简单,我们将使用著名的波士顿住房数据集。这个数据集的完整描述可以在这个链接中找到。

本练习的目标是根据给定的特征预测房价。让我们通过将 MEDV 作为目标变量,将所有其他变量作为输入要素来预测 Boston Housing 数据集的价格。
我们可以从这个链接下载数据集,并将它保存在本地驱动器中的某个可访问的地方。可以在同一驱动器上使用以下命令将数据集加载到 Colab 目录中。
from google.colab import files
files.upload()
我们现在可以检查 Colab 的目录内容
!ls
我们应该看到一个名为 BostonHousing.csv 的文件被保存。现在我们已经成功上传了数据集,我们可以开始分析了。
对于我们的线性回归模型,我们需要从 PySpark API 导入向量汇编器和线性回归模块。Vector Assembler 是一个转换工具,它将包含 type double 的多个列中的所有特征组合成一个向量。我们应该使用 ( 必须使用 ) StringIndexer 如果我们的任何列包含字符串值,就将其转换为数值。幸运的是,BostonHousing 数据集只包含 double 类型,所以我们现在可以跳过 StringIndexer 。
from pyspark.ml.feature import VectorAssembler
from pyspark.ml.regression import LinearRegressiondataset = spark.read.csv('BostonHousing.csv',inferSchema=True, header =True)
注意,我们在 read.csv()中使用了 InferSchema。InferSchema 自动为每一列推断不同的数据类型。
让我们查看数据集,看看每一列的数据类型:
dataset.printSchema()
它应该打印如下数据类型:

在下一步中,我们将把不同列中的所有特征转换成一个单独的列,我们可以在 outputCol 中将这个新的向量列称为“属性”。
#Input all the features in one vector column
assembler = VectorAssembler(inputCols=['crim', 'zn', 'indus', 'chas', 'nox', 'rm', 'age', 'dis', 'rad', 'tax', 'ptratio', 'b', 'lstat'], outputCol = 'Attributes')output = assembler.transform(dataset)#Input vs Output
finalized_data = output.select("Attributes","medv")finalized_data.show()

这里,“属性”是所有列的输入特征,“medv”是目标列。
接下来,我们应该根据我们的数据集拆分训练和测试数据(在本例中为 0.8 和 0.2)。
#Split training and testing data
train_data,test_data = finalized_data.randomSplit([0.8,0.2])regressor = LinearRegression(featuresCol = 'Attributes', labelCol = 'medv')#Learn to fit the model from training set
regressor = regressor.fit(train_data)#To predict the prices on testing set
pred = regressor.evaluate(test_data)#Predict the model
pred.predictions.show()
预测列中的预测得分作为输出:

我们还可以使用以下命令打印回归模型的系数和截距:
#coefficient of the regression model
coeff = regressor.coefficients#X and Y intercept
intr = regressor.interceptprint ("The coefficient of the model is : %a" %coeff)
print ("The Intercept of the model is : %f" %intr)
完成基本的线性回归操作后,我们可以进一步从 Pyspark 导入 RegressionEvaluator 模块,对我们的模型进行统计分析。
from pyspark.ml.evaluation import RegressionEvaluator
eval = RegressionEvaluator(labelCol="medv", predictionCol="prediction", metricName="rmse")# Root Mean Square Error
rmse = eval.evaluate(pred.predictions)
print("RMSE: %.3f" % rmse)# Mean Square Error
mse = eval.evaluate(pred.predictions, {eval.metricName: "mse"})
print("MSE: %.3f" % mse)# Mean Absolute Error
mae = eval.evaluate(pred.predictions, {eval.metricName: "mae"})
print("MAE: %.3f" % mae)# r2 - coefficient of determination
r2 = eval.evaluate(pred.predictions, {eval.metricName: "r2"})
print("r2: %.3f" %r2)

就是这样。你已经在 Google Colab 中使用 Pyspark 创建了你的第一个机器学习模型。
你可以从 github 的这里获得完整的代码。
请让我知道,如果你遇到任何其他的新手问题,我也许可以帮助你。如果可以的话,我很乐意帮助你!
Pyspark —将您的特征工程包装在管道中
将变量创建集成到 spark 管道的指南

https://upload.wikimedia.org/wikipedia/commons/f/f3/Apache_Spark_logo.svg
为了有一个更干净和更工业化的代码,创建一个处理特征工程的管道对象可能是有用的。假设我们有这种类型的数据帧:
df.show()+----------+-----+
| date|sales|
+----------+-----+
|2018-12-22| 17|
|2017-01-08| 22|
|2015-08-25| 48|
|2015-03-12| 150|
+----------+-----+
然后我们想创建从日期派生的变量。大多数时候,我们会做这样的事情:
现在,我们希望将这些变量的创建集成到 spark 的管道中,此外,在它们的计算之前采取一些保护措施。为此,我们将创建一个继承 spark 管道的[Transformer](https://spark.apache.org/docs/latest/ml-pipeline.html#transformers)方法的的类,并且我们将添加一个在计算前检查输入的函数。
- 我们的类继承了 Spark
[Transforme](https://spark.apache.org/docs/latest/ml-pipeline.html#transformers)r的属性,这允许我们将其插入到管道中。 [this](https://spark.apache.org/docs/latest/api/java/org/apache/spark/ml/util/Identifiable.html)函数允许我们通过给对象分配一个唯一的 ID 来使我们的对象在管道中可识别和不可变[defaultCopy](https://spark.apache.org/docs/1.5.1/api/java/org/apache/spark/ml/param/Params.html#defaultCopy(org.apache.spark.ml.param.ParamMap))尝试创建一个具有相同 UID 的新实例。然后,它复制嵌入的和额外的参数,并返回新的实例。- 然后使用 check_input_type 函数检查输入字段的格式是否正确,最后我们简单地实现
[SQL Functions](https://spark.apache.org/docs/2.3.0/api/sql/index.html)F.day of month 来返回我们的列。
我们将重用这个框架来创建我们需要的其他变量,他唯一要做的改变就是 ID 和 _transform 函数
我们已经定义了具有相同框架的 MonthQuarterExtractor,与[withColumn](https://spark.apache.org/docs/1.6.1/api/java/org/apache/spark/sql/DataFrame.html#withColumn(java.lang.String,%20org.apache.spark.sql.Column)) 方法相比,它可能看起来有点冗长,但是要干净得多!
最后,我们对年份变量做同样的处理。
现在让我们将它们集成到我们的管道中。
df.show()+----------+-----+----------+----+------------+
| date|sales|dayofmonth|year|monthquarter|
+----------+-----+----------+----+------------+
|2018-12-22| 17| 22|2018| 2|
|2017-01-08| 22| 8|2017| 0|
|2015-08-25| 48| 25|2015| 3|
|2015-03-12| 150| 12|2015| 1|
+----------+-----+----------+----+------------+
诀窍是,我们创建了一个“海关”变压器,并将其插入火花管道。也可以将它们与其他对象、向量汇编器、字符串索引器或其他对象一起插入管道。
最后
Spark 管道是一个非常强大的工具,我们可以在一个管道中管理几乎整个数据科学项目,同时保持每个对象的可追溯性,并允许更简单的代码工业化,不要犹豫滥用它!
感谢您阅读我,如果您对 Pyspark 的更多技巧|教程感兴趣,请不要犹豫,留下您的评论。 (Y ou 可以在这里找到代码:)
为什么 Pyspider 可能是初学者最好的刮擦仪表板之一
py spider——竞争对手监控指标的实际应用

知己知彼,百战不殆——孙子
最近,我为公司构建了多个爬虫,我开始发现很难关注爬虫的性能。因此,我在网上搜索,看看是否存在一个 python 包,它不仅可以更简单地构建一个爬虫,还可以内置一个仪表板来跟踪爬虫的执行。
然后,我发现了这个 python 包——py spider,它真的很有用,尤其是在下面这几点:
- 更容易调试——它有一个用户界面让你知道哪个部分出错了。
- 内置仪表盘 —用于监控目的。
- 兼容 javascript —不像 Scrapy 需要安装 scrapy-splash 来渲染 Javascript 网站,但是 Pyspider 提供了 Puppeteer ,这是 Google 用 Javascript 开发的一个非常著名和强大的库,用于网页抓取。
- 数据库支持 — MySQL、MongoDB 和 PostgreSQL。
- 可扩展性 —分布式架构。
监控竞争对手指标
现在,我将向您展示一个关于 Pyspider 的用例,并告诉您 Pyspider 的酷功能,但如果您希望我介绍 Pyspider,请在下面留下评论,我将写一篇帖子向您介绍该框架及其关键概念。
我将更多地关注 Pyspider,而不是进入网络抓取部分的每个细节。如果你对学习网页抓取更感兴趣,请访问我的系列文章 使用 Python 来一步一步的解释。
我们开始吧!
Similarweb —一个在线竞争情报工具,为任何网站提供流量和营销见解。我将向你展示如何利用木偶师的力量来清理这个网站。

Snapshot of the Similar website
安装 pyspider
pip install pyspider
启动 pyspider
因为我们将抓取 javascript 渲染的网页,所以我们将使用pyspider all而不是pyspider。在命令提示符下运行它。
pyspider all

如果您看到上面的消息,这意味着 pyspider 在您的本地主机端口 5000 上成功启动。
浏览 pyspider
打开你的浏览器,浏览到地址 localhost:5000(即在你的地址栏输入 http://localhost:5000/ ),你会看到类似下图的东西。

然后我们将点击创建按钮(在左下角)来创建我们的刮刀。

然后,将项目名称填写为 Similarweb,并再次点击创建按钮。

之后你就可以看到上面的截图了,让我们开始吧。
脚本
这是我创建的一个简单的脚本。为了解释这是如何工作的,我将把它分成 3 个部分。
脚本—第 1 部分
如上面要点的第 8–15 行所示,初始化请求的标题。
[@every](http://twitter.com/every)(minutes=24 * 60)
def on_start(self):
self.crawl('[https://www.similarweb.com/'](https://www.similarweb.com/'),
fetch_type = 'chrome',
validate_cert = False,
headers = self.headers,
callback=self.index_page)
默认情况下,该功能将执行 on_start 功能。有五个变量要填入抓取函数。您会注意到 每 装饰,这意味着该功能将每 24*60 分钟执行一次,即 1 天。
- https://www.similarweb.com/:你想要抓取的网址,所以在这个例子中,我们将首先抓取主页。
- fetch_type:参数设置为 chrome,表示使用木偶师渲染 javascript 网站。
- validate_cert:参数为 False,因此 Pyspider 将跳过服务器证书的验证。
- 标题:请求网页时使用我们之前定义的标题。
- 回调:调用
index_page函数作为下一个要解析的函数。
脚本—第 2 部分
[@config](http://twitter.com/config)(age=10 * 24 * 60 * 60)
def index_page(self, response):
print(response.cookies)
self.crawl('[https://www.similarweb.com/website/google.com'](https://www.similarweb.com/website/google.com'),
fetch_type = 'chrome',
validate_cert = False,
headers = self.headers,
cookies = response.cookies,
callback=self.index_page_1)
在我们的情况下,我们感兴趣的竞争对手是 google.com。这就是为什么网址是https://www.similarweb.com/website/google.com的原因。
你会注意到还有另一个装饰器叫做 config ,这个装饰器是用来表示这个函数是否每 10 天只运行一次。 抓取 功能的参数与上一个类似,只有一点不同:
- cookie:在请求数据时,我们从上一个会话获取 cookie 作为这个会话的输入。
脚本—第 3 部分
[@config](http://twitter.com/config)(age=10 * 24 * 60 * 60)
def index_page_1(self, response):
return {
response.doc('span.engagementInfo-param.engagementInfo-param--large.u-text-ellipsis').text(): response.doc('span.engagementInfo-valueNumber.js-countValue').text().split()[0]
}
这个函数只是返回总访问量作为字典。Pyspider 使用 Pyquery css 作为主路径选择器。
结果

所以复制要点代码并粘贴到右边的面板,如图所示。然后单击右上角的 save 按钮(在带有紫色边缘的框中高亮显示)保存脚本。之后,单击 run 按钮(在带有蓝色边缘的框中突出显示)运行代码。

单击“跟随”按钮,跟随刮擦过程的流程。

然后点击箭头按钮(紫色边框内高亮显示)继续下一个刮程。

再次单击箭头按钮。

看紫色的方框区域,这是刮削的输出。总共有 604.9 亿人次访问 google.com。
仪表盘

这是铲运机的总览仪表板。您可以单击紫色框(运行按钮)来执行 crawler。除此之外,您还可以通过单击红框(结果按钮)将结果保存到 CSV 或 JSON 文件中。

看右上角的紫色框,你会发现我之前说过的要下载的文件选项。点击按钮,你会得到你需要的格式(即 JSON/CSV)的结果。

最终想法
Pyspider 是一个非常有用的工具,它可以刮得非常快,但是如果你正在处理实现反爬行机制的网站,我会建议你使用 Scrapy 来代替。
谢谢你读到帖子的最后部分,真的很感激。
我将每周发布内容,所以请随时在下面留下您可能感兴趣的话题的评论,我将努力为您创建内容。
关于作者
Low 魏宏是 Shopee 的数据科学家。他的经验更多地涉及抓取网站,创建数据管道,以及实施机器学习模型来解决业务问题。
他提供爬行服务,能够为你提供你所需要的准确和干净的数据。你可以访问 这个网站 查看他的作品集,也可以联系他获取抓取服务。
在媒体上阅读低纬鸿的作品。Shopee 的数据科学家。每天,低伟鸿和其他成千上万的…
medium.com](https://medium.com/@lowweihong?source=post_page---------------------------)
安卓版 PySyft
扩展 OpenMined 为移动设备带来隐私

什么是 PySyft
“PySyft 是一个用于安全、私人深度学习的 Python 库。PySyft 使用 PyTorch 中的多方计算(MPC) 将私有数据从模型训练中分离出来
PySyft 是露天家族中的主要部分。
什么是 Android 版的 PySyft
PySyft for Android 是让不同平台在 PySyft 环境下协同工作的第一步
PySyft 依靠工人来完成这项工作。目前,这些工作器是用 Python 实现的,并与 PyTorch 上的 PySyft 包装器挂钩
PySyft for Android 扩展了这个系统,允许边缘的移动设备使用不同的框架进行操作。在这个概念验证的例子中,Android 应用程序使用 Nd4J 和 DL4J 来计算来自 PySyft (PyTorch)工作器的操作
应用程序
在联合学习设置中,Android 应用程序位于边缘,保存其数据以进行本地训练
对于 MPC,我们可以让参与者使用不同的操作系统和平台来进行操作
高层架构
该设置有三个主要组件:
- 一个 PySyft worker 实现 WebsocketIOClientWorker 作为门面
- 一个在两个参与者之间转发请求和响应的WebsocketIOServerWorker
- 执行从外观发送的操作的 Android 应用程序

下图显示了在此设置中如何处理驱动程序或执行元发送的命令

这些对象构成了一个组件,通过 SocketIOClientWorker 提供与 PySyft 中任何其他工作器相同的 API。从参与者的角度来看,整个组件只是另一个工人

应用程序的架构
应用程序的架构可以在下图中看到。这是一个基于关注点分离的架构,由遵循干净架构的层来定义。这将允许我们在其他 Java/Kotlin 平台中重用大量工作

通信详细信息
通信是使用 SocketIO 完成的,但是服务器端的工作人员和 Android 应用程序都可以使用其他套接字库
主要的限制是由 PySyft 工作线程的同步特性造成的
- _recv_msg 是同步的,一旦操作被发送到 Android,它不能等待后者的回复,因为它正在通过服务器。这就是信号量在 SocketIO 类中所做的事情
使用中性张量表示
考虑到 Android 不能使用 PyTorch 对象,必须选择一个中性的表示来使数据在双方都可用。幸运的是 PyTorch 和 DL4J 都提供了使用 npy 格式的可能性。虽然这种转换会影响性能,但它向我们保证了双向传输的数据不会被破坏
与 TFLite 团队关于实现 npy 的对话也令人鼓舞,应该不会太难。这项工作也可以用于其他平台(例如,基于 KMath 的平台)
验证性测试(Proof of Concept 的缩写)
这个 PoC 实现了以下 PySyft 操作:发送、删除、获取和添加
使用了以下库:
- DL4J: 这是将处理操作的机器学习框架,尽管对于这个 PoC 来说只有 Nd4J 是必要的。该系统对其他框架是开放的
- msgpack: PySyft 使用它在工人之间移动数据和操作。这也必须在 Android 上实现,尽管如果必要的话可以使用不同的格式
- SocketIO :使用这个库有很强的依赖性,因为它也被代理/服务器使用。
怎么跑?
您必须安装 PySyft。遵循存储库中的说明
一个加密的、保护隐私的深度学习库
github.com](https://github.com/OpenMined/PySyft)
从以下存储库中获取应用程序的代码
通过在 GitHub 上创建一个帐户,为 OpenMined/AndroidWorker 开发做出贡献。
github.com](https://github.com/OpenMined/AndroidWorker)
运行此安装程序需要以下步骤:
- 启动 Android 应用程序
- 启动服务器(socket io _ server _ demo . py展示了如何以一种简单的方式完成这项工作)
- 启动 socketio 客户端
- 驱动程序可以在笔记本上运行。“插座鲍勃”可以作为一个例子
- 连接 socketio 客户端(Bob)
- 连接 Android 应用程序
- 开始用吧!
即将推出
- Kotlin 多平台模块,允许为 JVM 实现的核心在其他环境中使用
- Tensorflow Lite 实施
- KMath 实现
- 非阻塞套接字通信
- 压缩
- 有模型的计划
参考
- https://www.openmined.org/
- https://github.com/OpenMined/PySyft
- https://github.com/OpenMined/AndroidWorker
- https://docs . scipy . org/doc/numpy-1 . 14 . 0/neps/npy-format . html
- https://github.com/msgpack
Pytest 特性,您在(测试)生活中需要的特性
注意:这最初发布在martinheinz . dev
测试你的代码是开发不可或缺的一部分,质量测试可以帮你省去很多麻烦。有很多关于在 Python 中进行测试的指南,特别是关于用 Pytest 进行测试的指南,但是有相当多的特性我很少在任何地方看到提及,但是我经常需要。所以,这里列出了我不能没有的 Pytest 的技巧和特性列表(不久之后你也不能没有)。

异常测试
让我们从简单的开始。您有一个抛出异常的函数,您希望确保它在正确的条件下发生并包含正确的消息:
这里我们可以看到 Pytest 为我们提供的简单的上下文管理器。它允许我们指定应该引发的异常类型以及所述异常的消息。如果块中没有引发异常,则测试失败。当上下文管理器返回具有type、value或traceback等属性的ExceptionInfo类时,您还可以检查异常的更多属性。
过滤警告
除了异常,让我们看看警告。有时,您会在您使用的库内部的日志中收到大量警告消息。你无法修复它们,它们只会制造不必要的噪音,所以让我们摆脱它们:
这里我们展示了两种方法——在第一种方法中,我们通过在过滤器列表的前面插入一个过滤器,直接忽略指定类别的所有警告。这将导致您的程序忽略这一类别的所有警告,直到它终止,这可能并不总是可取的。对于第二种方法,我们使用上下文管理器,它在退出警告范围后恢复所有警告。我们还指定了record=True,这样如果需要的话,我们可以检查发出的(忽略的)警告列表。
测试标准输出和标准错误消息
接下来,让我们看看下面的场景:你有一个命令行工具,它有一堆函数,这些函数将消息打印到标准输出,但不返回任何内容。那么,我们如何测试呢?
为了解决这个问题, Pytest 提供了名为capsys的夹具,它可以很好地捕捉系统输出。要使用它,你只需要把它作为参数添加到你的测试函数中。接下来,在调用被测试的函数之后,您以 tuple - (out, err)的形式捕获输出,然后您可以在 assert 语句中使用它。
修补对象
有时在测试时,您可能需要替换被测函数中使用的对象,以提供更可预测的数据集,或者避免所述函数访问可能不可用的资源。mock.patch可以帮上忙:
在第一个例子中,我们可以看到修补函数,然后检查它们被调用的次数和参数。这些补丁也可以以装饰器和上下文管理器的形式堆叠。现在,对于一些更强大的用途:
上面代码片段中的第一个例子非常简单——我们替换了SomeClass的方法,并让它返回None。在第二个更实际的例子中,我们通过用 mock 替换requests.get,并让它返回我们提供合适数据的对象,来避免依赖远程 API/资源。
mock模块还能为你做更多的事情,其中一些相当疯狂——包括副作用、模仿属性、模仿不存在的属性等等。如果你在编写测试时遇到问题,那么你一定要检查这个模块的文档,因为你很可能在那里找到解决方案。
与conftest.py共享灯具
如果你写了很多测试,那么在某个时候你会意识到把所有的Pytestfixture 放在一个地方是很好的,你可以从那里导入它们,因此可以在测试文件之间共享。这个可以用conftest.py解决。
conftest.py是一个位于测试目录树底部的文件。在这个文件中,你可以存储所有的测试夹具,然后这些夹具会被 Pytest 自动发现,所以你甚至不需要导入它们。
如果您需要在多个测试之间共享数据,这也很有帮助——只需创建返回测试数据的 fixture。
另一个有用的特性是指定 fixture 范围的能力——这在创建非常昂贵的 fixture 时非常重要,例如到数据库的连接(session范围)和在范围的另一端是在每个测试用例(function范围)后需要重置的连接。夹具scope的可能值为:function、class、module、package和session。
参数化夹具
我们已经在上面的例子中讨论了设备,所以让我们再深入一点。如果您想通过参数化来创建更多的通用夹具,该怎么办呢?
上面是一个为每个测试准备 SQLite 测试数据库的 fixture 的例子。这个 fixture 接收数据库的路径作为参数。使用request对象将该路径传递给 fixture,该属性param是传递给 fixture 的所有参数的 iterable,在本例中只有一个参数——路径。这里,这个 fixture 首先创建数据库文件(也可以填充它——为了清楚起见省略了),然后执行测试,测试完成后,fixture 删除数据库文件。
至于测试本身,我们使用带有 3 个参数的@pytest.mark.parametrize——第一个是夹具的名称,第二个是夹具的参数值列表,它将成为request.param,最后是关键字参数indirect=True,它导致参数值出现在request.param中。
我们需要做的最后一件事是添加 fixture 作为参数来测试它本身,这样我们就完成了。
有些情况下,跳过一些测试是合理的,无论是因为环境( Linux vs Windows )、互联网连接、资源可用性还是其他原因。那么,我们该怎么做呢?
这是一个非常简单的例子,展示了如何基于某种条件跳过一个有效的测试——在本例中,基于 PostgreSQL 服务器是否在机器上运行。在 Pytest 中还有许多与跳过或预测失败相关的很酷的特性,它们在这里有很好的记录,所以我不会在这里进行更多的详细描述,因为在这里复制和粘贴现有的内容似乎是多余的。
希望这几个小技巧能让你的测试体验变得更加愉快和高效,从而激励你写更多的测试。如果你有任何问题,请随时联系我,如果你有自己的建议或诀窍,请在这里分享。🙂
Python:初学者指南
对编码语言 Python 的介绍

Python 是当今最流行的编程语言之一。它最初由吉多·范·罗苏姆创作,并于 1991 年首次发行。Python 可以用于许多不同的目的,包括创建 web 应用程序、读取和修改文件、连接到数据库系统,以及处理大数据和一般的数据科学。就像许多编程语言一样,Python 也是一个奇特的计算器;它可以轻松地进行数学运算。
>>> 12 + 15 # Addition
27>>> 35 – 17 # Subtraction
18>>> 5 * 5 # Multiplication
25>>> 25 / 5 # Division
5.0>>> 5 ** 2 # Exponentiation (does NOT use ^ like some calculators)
25>>> 10 % 3 # Modular division (or “mod” for short — returns a remainder)
1>>> 10 // 3 # Floor division (or “round down” division)
3
让我们看看 Python 中的一些基本功能,希望在这篇博客结束时,你能更好地理解 Python 是什么,它能做什么,以及它对各地的数据科学家和程序员有多有用。
变量
在我们继续之前,让我们理解并定义什么是变量。一个变量允许我们保存数据,这样我们就可以回调或引用它,而不必重写整个数据。下面是我们保存到s1、s2和s3的三个不同的变量(稍后我们将在博客中讨论为什么这是不好的命名实践,但请耐心等待)。
s1 = 123
s2 = 678.0
s3 = “This is my first string”
s4 = ‘and this is my second string’
如上所述,像我这样命名变量是不好的命名实践,所以让我来帮你理解为什么。在 Python 中,有一些必须遵循的“规则”,以及一些只是好的 Python 实践的“规则”(关于 Python 代码风格指南的更多信息,请参见这里的)。
“强制规则”是:变量名只能由字母、数字、下划线组成(不能有特殊字符);变量名不能以数字开头;最后,你不能用一个内置的 Python 关键字来命名一个变量(例如if或else)。
一些“好的 python 式实践”规则是:变量名应该总是是描述性的(我在上面打破的一个规则;不要说出x或df);没有大写字母(这些是留给上课用的);变量不应该以下划线开头(这意味着一些特殊的东西,我们不会在这篇博文中涉及);多词变量应该用snake_case(全部小写用下划线分隔)而不是camelCase(以大写开头的新词);最后,虽然您可以在内置 Python 函数后命名变量(如print),但这样做是一个非常糟糕的主意。
除了执行上面的基本数学方程,我们还可以使用保存的变量来执行它们。比如加s1+ s2会得到 801.0。我们不能将s3加到s1或s2上,因为它们不是两个数字。试图这样做将导致 Python 抛出一个错误,并简要解释为什么这是不可能的。也就是说,你可以把s3加到s4上,因为它们都是我们所说的字符串!

Python Logo. Source.
Python 数据类型
现在我们对变量有了更好的理解,让我们看看 Python 中的基本数据类型。在我们看这些之前,我们需要理解“数据”实际上意味着什么。大多数人认为数据是信息的电子表格——这并不太离谱。数据其实只是的信息。Python 中运行和存储的一切都被认为是数据。
当再次学习变量时,让我们参考上面使用的例子(并提醒自己下面是什么)。在这些例子中,我们看到了 Python 中最常见的三种数据类型。
s1 = 123
s2 = 678.0
s3 = “This is my first string”
s4 = ‘and this is my second string’
在上面的第一个变量s1中,我们看到了所谓的整数——在 Python 中通常被称为int类型。一个int是一个没有小数部分的数字——比如我们在上面的数学例子中使用的所有数字。
在第二个变量s2中,我们看到了所谓的float——在 Python 中称为float类型。float是一个有小数部分的数字——即使这个小数部分是一个 0,就像我们上面的例子一样。
最后,我们的第三和第四个变量s3和s4被称为字符串——在 Python 中被称为str类型。一个字符串是我们在 Python 中存储文本数据的方式。字符串就是一组单引号(')或双引号(" ")之间的字符串。Python 最终不在乎我们用什么来创建字符串,只要我们在创建字符串时保持一致。
容器/集合类型
在许多情况下,我们希望将许多值存储到一个变量中,称为容器或集合。这些容器可以容纳任意数量的其他对象。Python 中有许多常见的容器,所以让我们开始吧。
第一个,也可能是最常见的,叫做列表。列表是有序的、可变的、异构的对象集合。澄清一下:有序意味着对象的集合遵循特定的顺序,可变意味着列表可以变化——或者改变,异构意味着您可以在一个列表中混合和匹配任何类型的对象或数据类型(int、float或string)。列表包含在一组方括号[]中。实例化一个新列表可以通过几种不同的方式来完成,如下所示。
>>> new_list = [] # instantiates an empty list
[]>>> new_list = list() # also instantiates an empty list
[]>>> new_list = ['obj1', 'obj2', 'obj3'] # instantiates a list of strings
['obj1', 'obj2', 'obj3']
比列表更不常见的是所谓的元组。元组是相似的,因为它们是有序的和异构的,然而它们是不同的,因为它们是不可变的——这意味着一旦创建它们就不能变异或改变。元组包含在一组括号内()。元组比列表稍微快一点,也小一点,所以尽管它们的存在部分是因为它们更有用的时候遗留下来的,但它们仍然有它们的用途。实例化一个新的元组也可以用几种不同的方式来完成。
>>> new_tuple = () # instantiates an empty tuple
()>>> new_tuple = tuple() # also instantiates an empty tuple
()>>> new_tuple = ('obj1', 'obj2', 'obj3') # instantiates a tuple of strings
('obj1', 'obj2', 'obj3')>>> new_tuple = ('obj1',) # tuples containing a single object need a trailing comma so Python knows it is a tuple rather than a grouping operation
('obj1',)
我们还有一套的。集合是无序的、可变的、唯一的对象集合(类似于数学中的传统集合)。可以通过调用set()函数来创建集合,这将创建一个空集,通过一组花括号{}传递我们的信息,或者通过传递 iterable 的单个参数,在这种情况下,Python 将返回一个新的集合,其中包含 iterable 中每个唯一元素的单个元素。
>>> new_set = set() # instantiates an empty set
set()>>> new_set = {'A', 'A', 'B', 'C', 'C', 'D'}
{'A', 'B', 'C', 'D'}>>> new_set = set(['A', 'A', 'B', 'C', 'C', 'D'])
{'A', 'B', 'C', 'D'}
最后,我们要讨论的最后一个容器是一个字典。字典是您在 Pythonic 之旅中遇到的最常见的容器类型之一。字典是无序的、可变的键值对。可以认为它们很像一个实际的字典,其中的键是“单词”,值是特定单词的“定义”。字典与其他容器类型非常相似,可以用多种不同的方式进行实例化。
>>> new_dict = {} # instantiates an empty dictionary
{}>>> new_dict = dict() # also instantiates an empty dictionary
{}>>> new_dict = {'a': 1, 'b': 2, 'c': 3}
{'a': 1, 'b': 2, 'c': 3}>>> new_dict = dict(a = 1, b = 2, c = 3)
{'a': 1, 'b': 2, 'c': 3}
如果你不确定你刚刚创建了什么类型的对象,你可以通过调用变量上的type()函数来检查它,Python 会让你知道你已经创建了什么。
>>> type(new_list)
list>>> type(new_tuple)
tuple>>> type(new_set)
set>>> type(new_dict)
dictAnd thinking back to our variables we created earlier...>>> type(s1)
int>>> type(s2)
float>>> type(s3)
str>>> type(s4)
str

Another Python logo. Source.
至此,我们已经了解了 Python 的一个(非常)简单的定义,什么是变量以及如何实例化变量,Python 中的一些基本数据类型和一些基本容器/集合类型。虽然这并不意味着是一个包罗万象的速成课程,但希望它教会了您足够多的知识,让您能够进入 Python 的世界,学习更多知识,并将其应用到您的世界中。我个人将 Python 用于数据科学,但是正如我上面提到的:Python 的用途几乎是无止境的。如果你有兴趣学习更多关于 Python 如何与数据科学世界相关的知识,可以看看我在 Python 内的 pandas library 上的博客,看看这能有什么帮助。希望你喜欢这本书,下次再见!
python——开发面向未来应用的完美语言

Image by Gerd Altmann from Pixabay
来说说未来证明吧!
如今,技术正日益提高到一个先进的水平。因此,对于用户以及开发者/制造者来说,处理长时间的创建应用程序以保持最新版本的更新变得相当麻烦。
完整的设计范例正在以一种你的应用基于问题的方式来构建计划世界观。包含、排除或更新它们以与时俱进可能是一种简单的、测试性的或几乎难以置信的体验。你有机会选择。
总而言之,构建应用程序的理想方法是什么?这些应用程序很难随着创新的进步而更新,但另一方面,它们可以满足未来可预测的需求。
Python 编程语言统治着其他编程方言,例如 C、C++或 Java。Python 是一种面向对象的高级多世界观编程语言,具有动态亮点。

Photo by Hitesh Choudhary on Unsplash
- 它是由 Guido Van Rossum 设计的,他是一名荷兰软件工程师。它是全世界最受支持的编程方言之一。
- Python 的恶名比近年来任何时候都要多。Python 给出了巨大的亮点,吸引了每个软件工程师的注意。
Python 非常容易阅读和编写;随后,它减少了软件工程师之间的混乱。令人震惊的是,作为最重要的技术组织之一,谷歌将 Python 用于其各种应用程序,并且有一个完全专用的 Python 入口。
为什么 Python 是面向未来的应用?
通常,当我们谈论使用 Python 作为面向未来的应用程序时,它被分为不同的模式。

image source: fingent
使用设计模式
- 设计模式用于构建复杂的项目(通常是一个组合)。
- 它将一篇不可预测的文章的发展与其描述隔离开来。
- 这是最常用的创造性配置设计之一。
- 它敦促工程师们制定明确的区域语言。
- 此外,它能让设计师更快地学习商务用语。
在设计模式的帮助下,开发人员让你的代码更容易测试、维护和获取。
我们可以利用制造商的例子来保证代码串的安全。为了说明,万一我们通过保证所有属性都被公正地使用并且必须通过构造函数来设置,从而使绑定类永久化,那么我们可以利用生成器配置示例来保证绑定条目的条件不能被改变。
- 然后,我们将能够在应用程序中共享 bond 问题,而不会因为 bond 对象而面临任何竞争条件。
- 它给你一个体面的利用熟悉的 API,可以让你更好地理解该领域。
使用模板设计模式

image source: refactoring
模板设计模式是编程应用中最常用的例子之一。
我们可以考虑的最佳例子如下:
- 一个典型的行为规划改进设计。
- 它保证您的程序遵循推荐的行为。
- 如果我们需要实现包含各种进展/指南的计算,那么我们可以利用格式编程配置设计。
计算是指导方针或步骤,应在明确要求下执行,以实现想要的结果。
我们可以利用模板软件设计模式来执行计算。
格式配置示例用于支持清晰的计划和代码重用。它保证了方法被适当地组织执行。外来客人回避的手段。
布局示例保证代码是完美的追求坚实的标准,因为每个类有一个单独的职责,可以有效地尝试。
使用策略设计模式
战略设计模式是获得它的最简单的结构例子之一。它是最流行的社交编程配置设计之一。
客户代码可以在不改变大部分代码的情况下交易程序。使代码易于测试、理解并遵循可靠的标准。
使用装饰设计模式
装饰器设计模式用于在运行时或静态地激活或扩展项目的义务,而不改变其结构。
- 装饰设计模式示例是开闭原则的一个重要工具。
- 装饰设计模式可以用来增加文章的有用性,而不改变文章本身或不同的条目。
- 您可以利用 decorator design 来扩展一个项目,而不需要创建无数的子类。
装饰设计模式是一个选项,与实现遗留的树结构相反。
- 装饰设计模式使我们在每次需要在框架中加入新的有用性时,不必创建大量的类。
- 我们可以看到,使用装饰设计,我们在不制作新的产品或改变现有产品的情况下,为一个产品添加了额外的行为。
- 我们制造了一个混合系统,但没有制造任何新的类。
装饰设计模式是一个选项,与实现遗留的树结构相反。此外,装饰设计模式可以防止代码随着新需求的增加而呈指数级增长。

Image by Johnson Martin from Pixabay
总结
从上面的讨论中,我们已经看到了 Python 作为未来验证应用的完整场景。除此之外,我们还讨论了一个完整的流程,展示了软件工程师生活中的一个关键思想——利用适当的结构设计。具体来说,Python 编程语言被认为是负责构建经得起未来考验的应用程序的语言,因为它简单明了而且流行。
作者简介
Nathan McKinley 是位于美国芝加哥的 Cerdonis Technologies LLC - 移动应用开发公司的业务开发经理,他正在利用最新的技术更新和事实来开发经得起未来考验的移动应用,以便在市场上表现出色。作为一名移动技术领域的业务开发人员,我已经掌握了撰写有价值的技术见解的技能,以及在移动应用程序开发过程中利用这些见解的好处。
Python 和 Google APIs
或者如何让网上的数据变得有用。

Simple structure
在这个简单的指南中,你将学会使用谷歌云平台及其 API 从网络上收集一些数据。要使用 python,你可以在这个网页上运行本文的代码,在线免费,不需要安装任何东西。无论如何,如果你想更深入,成为 python 专家,我们推荐你安装 Anaconda 。
但首先,一些基本的概念,所以我们都在同一点上。你需要知道什么是 API,什么是 JSON。
让我们从更简单的开始:JSON 就是一种在文本中组织信息的方式,这样许多编程语言,特别是 JavaScript 和其他许多语言,都可以阅读和理解这些信息。典型的 JSON 使用 " key": "value" 格式。下面是一个 JSON 对象的例子:
{
'image': 'King.jpg',
'firstname': 'John',
'lastname': 'Snow',
'company': 'Dragons Inc.'
}
...
现在我们知道了,什么是 API?嗯,API 代表应用编程接口。我理解这些词的意思的方式是,当一些(软件)开发者制作了一个非常酷的程序/应用程序时,他们有时会认为其他开发者可能也想使用它,或者至少是它的一些部分,所以他们创建了 API——程序的一部分,你可以与之交互。简言之,API 或多或少等同于程序,但是你可以在脚本中使用它。有许多免费使用的 API,但也有许多其他的不是。
现在要开始使用谷歌 API,你必须去谷歌云平台并创建一个免费账户。这些步骤非常简单,所以我将跳过它们,但是在您完成这些步骤后,当您点击您的 Google 控制台时,您应该会看到类似以下内容:

现在,如果您点击左侧的三栏图标、API 和服务以及库,您将会看到 Google 提供的所有 API。为简单起见,我们将重点关注与谷歌地图相关的内容,但我们鼓励您探索和尝试任何其他引起您注意的内容。

一旦我们点击地图部分右侧的查看全部,我们就可以看到与谷歌地图相关的所有 API,所以让我们使用距离矩阵 API。

确保您启用了 API。如果没有,只需点击启用,你就可以走了。之后,点击文档,因为这里有你学习这个 API 如何工作所需要的所有信息。
一旦进入文档,点击左侧打开开发者指南。请注意,这些步骤对于所有的 API 都是相同的。
现在我们需要两样东西:我们必须构建的 API 键和 url 。你会在左边的三栏图标上找到你的 API 密匙、API&服务、凭证。现在让我们开始构建 url ,这有点复杂。所有的 API 的工作方式大致相同,你构建一个 url 并用你的 API 键发送它来请求一些信息,然后你捕获这些信息。需要构建 url 的方式在 API 的开发人员文档中指定。在我们的例子中,我们的 url 将如下所示:
https://maps.googleapis.com/maps/api/distancematrix/**outputFormat**?**parameters**
其中 outputFormat 需要更改为其中一个选项,要么是 json (推荐的),要么是 xml ,API 接受的参数在下面指定,在我们的例子中是我们想要获得距离的起点和目的地点,表示为latitude,longitude,最后是指向 Maps API 实例的键参数。通常你必须构建一个 url 的字符串,用一个 & 将参数分开,但幸运的是 python 用一个requests包覆盖了我们的背部,这个包会自动将参数添加到 url 中。所以如果你去 to Repl 粘贴下面的代码,你会得到这些点之间的距离。确保使用上一步中获得的 API 密钥来更改 API 密钥。
import json
import requestsparams = {
'key': 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
'origins': '40.398585,-3.683520',
'destinations': '40.419643,-3.688804'
}url ='https://maps.googleapis.com/maps/api/distancematrix/json?'response = requests.get(url,params)
print(response.status_code == 200)print(response.text)
result = json.loads(response.text)
请注意,我们改变了输出格式,在我们的例子中,我们使用了坐标,但是正如文档所指出的,还有其他的可能性。尝试将origins和destinations坐标更改到不同的位置,并查看输出。运行代码后,您应该会看到类似下面的内容:
{
"destination_addresses" : [ "Calle de Alfonso XII, 0, 28001 Madrid, Spain" ],
"origin_addresses" : [ "Calle de Alpedrete, 51, 28045 Madrid, Spain" ],
"rows" : [
{
"elements" : [
{
"distance" : {
"text" : "3.0 km",
"value" : 3027
},
"duration" : {
"text" : "8 mins",
"value" : 495
},
"status" : "OK"
}
]
}
],
"status" : "OK"
}
现在让我们添加一些代码解释:第一部分导入用于获取 API 信息的 python 包,接下来我们定义 url 的参数和 url 本身,之后我们在requests包中使用get函数,给它 url 和参数来检索信息。我们还添加了一个检查来验证一切正常(如果代码是 200,它就是)。最后,我们打印得到的信息,并将其转换为 python 字典,这是一个与 JSON 非常相似的 python 对象,但区别在于 python 可以更容易地访问它的键。现在请注意[ ]和{ }在输出中的不同用法。这里我们使用方括号为一个键创建一个值列表,并用花括号嵌套那些键。
您可以尝试访问字典中的一个键。只要在控制台result['destination_addresses']上键入,你就会得到存储在那个键里的信息(值)。
在本指南中,我们使用了 Distance Matrix API,因为它易于使用和理解,但您也可以使用其他工具来获取方向、海拔、地点信息,还有其他与谷歌分析、谷歌云架构甚至机器学习相关的工具!
我希望这篇指南能帮助你理解 API 是多么的简单易用和强大,并给你一些在未来的项目中如何组合它们的想法。如果你有任何想分享的问题或想法,请在评论中告诉我。
Python 和 Oracle 云:加载数据
使用 OCI Python SDK 和自治 DWH API,您可以轻松地为您的数据科学和 ML 工作加载数据

介绍
在 之前的文章 ,中,我探讨了我们如何使用 Python 和流行的开源框架,如 Pandas 和 scikit-learn,来分析用 Oracle 自治数据仓库(ADWC) 存储的数据。在这个简短的故事中,我想向您展示如何使用 OCI Python SDK 轻松加载大数据文件。
自动化“上了类固醇”
云的一大特点是自动化。在云世界中,更容易动态地提供资源(如 VM,甚至数据库)并使用 API 管理它们。
Oracle 云基础设施(OCI) 有许多工具,您可以在其中选择自己喜欢的工具,以自动化一大组任务(我想说:我需要的一切):
- REST API
- OCI CLI
- 软件开发工具包(Software Development Kit)
OCI 命令行界面(CLI)真的很好。但是,如果你想开发你的高度定制的工具,完美地适合你的肩膀,我推荐使用 SDK 。
正如您在文档中看到的,我们有:
- Python SDK
- Java SDK
- Ruby SDK
- Go SDK
既然我们在谈论数据科学和 ML 工程,我毫无疑问会选择 Python 。
有了 OCI Python SDK,你就有了一套完整且易于使用的类,它们都有很好的文档记录,使你能够构建 Python 脚本来自动化你日常数据工作的大部分流程。此外,您甚至可以在 Jupyter 笔记本(或 Jupyter Lab)中使用它。
因此,几天前,我从晚上的任务开始,为我的一个客户构建一些示例,展示如何从平面文件开始在 Oracle ADWC 中加载数据。然后,作为一个不错的副作用,我决定更深入地研究这个主题,并创建了一个脚本来启动/停止 VM,甚至启动/停止我的 ADWC 实例。
你可以在我的 G ithub repo 里面找到这组例子
在这个故事中,我想更详细地解释如何从平面文件开始加载数据。
安装?轻松点。
显然,有文档,但是,如果您使用 PyPi,安装只有一行代码:
pip install oci
它也适用于蟒蛇。
如果你想成为第一批尝试新特性的人,只需查看 OCI Python Github 库。
配置
然后,您需要执行一些配置任务,以确保您的工作环境和 OCI 云之间的安全通信。我会花一些时间在这里澄清,因为我不想让你在这部分浪费宝贵的时间。
首先,您需要创建一个 PEM 格式的 API 签名密钥对。您必须在您的环境(运行 Python 程序的计算机)中保持私有密钥的安全,并将公共密钥存储在您的云配置文件中。当你存储它时,你可以从云用户界面读取指纹。
更多详情可在此处找到:
[## 必需的密钥和 OCIDs
了解使用 Oracle 客户机或您自己构建的客户机时所需的键和 OCIDs。
docs.cloud.oracle.com](https://docs.cloud.oracle.com/iaas/Content/API/Concepts/apisigningkey.htm)
然后,对于每种类型的客户端(对象存储客户端、计算客户端等)。)您需要提供一组(键,值)对,打包成一个字典,以便指定:
- 您的 OCI 用户名
- 用于保护通信的密钥(API 签名密钥)
- 这把钥匙的指纹
- 您的云租户标识符
- OCI 地区
以下是您需要准备的内容示例(匿名):
config = {“user”: “ocid1.user.oc1..XXXXX”,“key_file”: “/Users/lsaetta/Progetti/xxxx/oci_api_key.pem”,“fingerprint”: “YYYYY”,“tenancy”: “ocid1.tenancy.oc1..ZZZZZZ”,“region”: “eu-frankfurt-1”}
对于用户和您的云租户,您需要使用的不是名称,而是标识符 OCID,您可以从 OCI 云控制台复制它。
在云中加载文件
此时,您已经准备好将一组文件作为对象加载到 ObjectStore 中。
这是加载单个文件的代码,我很快会添加注释:
如您所见,您需要:
- 创建客户机,在本例中是 ObjectStorageClient,并将配置字典传递给构造函数
- 以 bucket_name,file_name,file_path 为参数调用方法 put_object
仅此而已。
在 Github 资源库中,您会发现一个处理一组文件的增强版本。
将凭证存储在数据库中
现在,是时候将数据从 ObjectStore 复制到 ADWC 表中了。
首先:我们需要创建一组凭证并将它们存储在 DB 中,以便使 PL/SQL 代码能够访问 ObjectStorage Bucket。
转到 OCI 用户界面,在您的配置文件(右上方的图标和用户设置)中创建一个 AuthToken:

Authentication Token
从 UI 复制身份验证令牌。请记住在创建后立即这样做,否则,您将无法重新阅读它。
其次,使用下面的代码将云凭证存储在 DB 中;该步骤只需执行一次:
import cx_Oracleconnection = cx_Oracle.connect(USER, PWD, 'adwc5_medium')# need to create a cursor
cur = connection.cursor()statement = """
BEGIN
DBMS_CLOUD.CREATE_CREDENTIAL(
credential_name => 'DEF_CRED_NAME',
username => 'adwc_user@oracle.com',
password => '<AUTH Token>'
);
END;
"""
cur.execute(statement)
注意:如果您的用户是联邦用户,那么这个名称就是完全限定的名称。它有一个前缀,如:
oracleidentitycloudservice/
并且您必须在 CREATE_CREDENTIAL 调用中指定完全限定的名称。
在 Oracle ADWC 中加载数据
因为我们现在在 ObjectStore 中有了平面文件,所以我们可以使用 PL/SQL 包 DBMS_CLOUD 来加载 ADWC 表中的数据。
好吧,这是 PL/SQL 代码,但不是很复杂,你可以很容易地剪切&粘贴。我们将把这段代码包装在 Python 代码中。
我们设想一个更简单的场景:一个用于平面文件的 Oracle 表,文件的格式与表相同(文件和表字段之间一一对应)
我们必须在数据库中创建表。例如:
CREATE TABLE CHANNELS
(channel_id char(1),
channel_desc varchar2(20),
channel_class varchar2(20)
);
/
然后,我们可以用下面的代码加载该表中一个文件中包含的数据:
import cx_Oracleconnection = cx_Oracle.connect(USER, PWD, 'adwc5_medium')# need to create a cursor
cur = connection.cursor()statement = """
BEGIN
DBMS_CLOUD.COPY_DATA(
table_name =>'CHANNELS1',
credential_name =>'DEF_CRED_NAME',
file_uri_list =>'[https://swiftobjectstorage.eu-frankfurt-1.oraclecloud.com/v1/tenant-name/BUCKET_NAME/channels1.txt'](https://swiftobjectstorage.eu-frankfurt-1.oraclecloud.com/v1/rgsbidatalake5/ADWC5/channels1.txt'),
format => json_object('delimiter' value ',')
);
END;
"""cur.execute(statement)
一个细节:要引用对象,您需要使用 SWIFT-type URL。在上面的例子中你需要改变的是:
- 地区名称(这里是 eu-frankurt-1)
- 你的房客的名字
- 对象存储中存储桶的名称
- 对象的名称(此处:channels1.txt)
正如我们所看到的,DBMS_CLOUD 包拥有所有必需的 API 来:
- 将凭证存储在数据库中
- 从对象存储复制到数据库
它还提供了其他一些不错的特性。例如,您可以使用 Apache Parquet 格式的文件,而不是 CSV。
想检查一下吗?
如果您想检查数据是否真的已经加载,您可以使用下面的代码片段:
import pandas as pd
import cx_Oracleconnection = cx_Oracle.connect(USER, PWD, 'adwc5_medium')channel1 = pd.read_sql("select * from CHANNELS1",
con=connection)# if it is a Notebook
channel1.head()
外部表
数据加载的另一个选项是在数据库中创建一个外部表,引用文件。一旦外部表开始工作,就可以使用 DML 指令将数据加载到数据库中。例如,您可以使用 CTAS:创建表作为选择。
创建外部表的过程是:
DBMS_CLOUD.CREATE_EXTERNAL_TABLE
更多详情 此处 。
结论
作为一名数据科学家,你需要与数据打交道。有时你需要自己装载。有时,您会有一个 DBA 来为您加载。
在本文中,我展示了如何使用 OCI Python SDK 来:
- 将文件加载到对象存储中
- 将数据从对象存储复制到 ADWC 表
此外,我还近距离观察了 OCI Python SDK 的强大功能。有了它,您可以创建高度定制的工具集来自动化您的日常工作。您甚至可以创建自己的 ADWC 实例,或者对其进行扩展(添加内核和存储空间)。
在以后的文章中,我将回到机器学习。差不多到了探索 OML4Py 的时候了。
还有一件事
如果你想找到更多使用 OCI Python SDK 的例子,你可以看一下这里的
Python 和 PostgreSQL:如何像数据科学家一样访问 PostgreSQL 数据库

“The PostgreSQL Pachyderm Whose Trunk Becomes the Python Python” — Image Source: “Python and PostgreSQL without ORM,” Moses Gitau (Jun 5, 2018)
有几个 Python 包可以与 SQL 数据库接口。在过去的几周里,我使用 psycopg2 模块与 PostgreSQL 数据库管理系统进行交互。您可以对从 SQL 查询中获得的数据使用 Python 库。例如,Pandas 库具有本地方法,允许我们直接从 SQL 数据库显示和操作查询结果。在本文中,我将详细介绍设置 psycopg2 和使用这些工具构建查询功能的过程。
第 1 部分:如何设置 Psycopg2
先决条件:在你尝试安装 Python 适配器之前,你必须安装 Postgres ,我在之前的帖子中详细介绍过。
Psycopg 是 Python 编程语言的 PostgreSQL 适配器。这个工具允许我们连接 Python 语言和库的功能,以获取、操作、输入和更新存储在 PostgreSQL 数据库中的数据。在撰写本文时,当前版本是 psycopg2。当我最初搜索这个包时,我被定向到该项目的 PyPI 站点。我很自然地决定使用默认的 pip 包管理系统进行安装,但是没有包括 Psycopg 的众多依赖项。根据文档,“Psycopg 是围绕 libpq PostgreSQL 客户端库的 C 包装器。”在这个标题下,包含了依赖关系。我最初试图安装这些依赖项,遇到了 StackOverflow 和该项目的 GitHub 上的用户抱怨的同样的困难。
谢天谢地,我偶然发现了一个答案,它暗示了自制软件的安装过程。虽然我已经安装了 Postgres 应用程序,但是缺少一个包含一个或多个 psycopg2 依赖项的目录。在终端中,我运行命令brew install postgresql来安装 PostgreSQL 的自制实现,然后运行命令pip install psycopg2来重新安装 Python 适配器。在这两个步骤完成后,我能够成功地使用 Psycopg。
第 2 部分:连接到 PostgreSQL 数据库
有几种方法可以连接到 PostgreSQL 数据库,三种方法是通过 PostgresApp/terminal/命令行直接连接,在一个 Python 脚本或文件(即 Jupyter 记事本)中连接,以及通过从初始化或配置文件导入必要的参数来连接。第三个选项最适合您希望用一个.gitignore文件隐藏您的登录凭证的时候。
你需要一些参数来连接任何数据库。一些常见的参数是主机、端口、用户名和密码。PostgreSQL 数据库既可以存储在您的计算机上(使用 localhost 作为 host 参数的值进行访问),也可以远程存储(使用服务器的IP 地址作为 host 参数的值进行访问)。您还需要一个端口号,它定义了客户端计算机的请求应该发送到的服务器上运行的进程。PostgreSQL 默认的端口是 5432 ,虽然这是可以更改的。如果有安全权限,您可能还需要用户名和密码;这在工作场所很常见,因此用户不会访问或更新他们无权访问的信息。
The attendance scene from “Ferris Bueller’s Day Off” (1986). Ferris Bueller remotely updates his attendance record which is stored in a database on his school’s server. Either Bueller stole the login information of a user who has the necessary permissions to update his record or the server allowed access to any client computer.
(1)直接连接 PostgresApp/Terminal/命令行

PostgreSQL when opened for the first time. Note the plus (+) symbol on the bottom left corner of the image.
PostgresApp 允许您通过使用位于窗口左下角的加号( + )进入创建新服务器菜单,直接连接到本地或远程存储的数据库。您还可以单击可用的数据库,在新的终端窗口中启动 PostgreSQL shell,但必须已经使用了 initialize 或 start 按钮。
您也可以通过命令行或终端访问服务器。如果您打开一个新的终端窗口(如果应用程序正在运行,则在 PostgreSQL shell 之外),您可以键入命令来启动 PostgreSQL,后跟数据库的参数。要启动 PostgreSQL,如果设置了快捷方式,请键入/Applications/Postgres.app/Contents/Versions/12/bin/psql或psql。在同一行中,您可以键入额外的参数,如主机-h localhost(其中 localhost 可以替换为服务器 IP 地址)、端口-p 5432、用户名-U username、数据库名称-d database_name以及密码(如果需要的话)-W password(您可以省略它,或者键入小写版本-w以表示没有密码)。终端中完整的连接请求如下所示:
psql -h localhost -p 5432 -U username -W password -d database_name
成功的连接命令将打开 PostgreSQL shell,并指示数据库已被访问。
(2)在一个文件中直接连接
当安全性不成问题时,您可以在一个 Python 脚本或 Jupyter 笔记本中练习连接到本地数据库。首先导入 psycopg2 库,然后使用。connect() 方法来传递相关参数。最后,创建一个 cursor 对象来对数据库运行 PostgreSQL 命令。这个过程的代码片段如下:
A sample connection to a PostgreSQL database within one file.
上面的代码片段连接到一个名为“suppliers”的本地数据库,并查询“vendors”表的所有内容。用户名和密码是“postgres”,任何有权访问该脚本的人都可以清楚地看到。如果使用默认的 PostgreSQL 端口 5432,则 port 参数不是必需的,我包含它是为了显示常规格式。在我查询了“vendors”表中的所有数据后,我关闭了光标和连接来释放服务器的带宽资源。
(3)连接配置和初始化文件
在专业环境中,服务器有许多保护措施,包括每个员工的个人用户名和密码。每个用户帐户都将拥有自定义权限,这些权限将决定他们可以访问和更新哪些信息。如果您出于协作目的在 GitHub 上托管这些信息,您会希望通过将它们存储在一个单独的文件中来对您的登录凭证保密。这就是.ini文件发挥作用的地方。我将跟随 PostgreSQLTutorial.com 的教程学习这种方法。
总体思路是 Python 脚本/Jupyter 笔记本文件将导入初始化文件的凭证。只有保护作者登录凭证的 Python 脚本才会被共享。将有第三个文件以 psycopg2 可以解释的方式解析存储在初始化文件中的参数。database.ini初始化文件的内容有:

The contents of the file database.ini
初始化文件的参数可以在一个单独的文件中解析,也可以在我们打算用来操作数据的主文件中解析。单独的文件是更好的选择,因为我们不希望每次进行 PostgreSQL 查询时都复制和粘贴大量代码。文件config.py接收来自初始化文件的数据,并返回一个参数字典,其中键是参数名(上面的蓝色文本),值是实际的参数值(上面的绿色文本):
{‘host’: ‘localhost’, ‘database’: ‘suppliers’, ‘user’: ‘postgres’, ‘password’: ‘postgres’}
config.py文件改编自PostgreSQLTutorial.com站点,并利用了 Python 的 ConfigParser() 类:
The config.py file that will be imported in the main Python script or Jupyter Notebook. The function takes in the contents of the initialization file and returns a dictionary of parameters.
导入和利用这两个文件的主要 Python 脚本包含在下面的要点中。database.ini文件将包含在.gitignore文件中,以保护作者的密码。
第 3 部分:使用 Python 操作 PostgreSQL 数据
一旦连接到 PostgreSQL 数据库,就可以查询分析所需的数据。查询结果可以显示在 Pandas 数据框中,然后可以像 Pandas 中存储的所有数据一样对其进行操作。Pandas 有一些方法,比如[.read_sql_query()](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_sql_query.html),允许我们通过 psycopg2 连接提取 PostgreSQL 存储的数据。以下 Python 脚本使用database.ini和config.py文件连接到“供应商”数据库,查询“供应商”表的内容,并使用函数 create_pandas_table: 在 Pandas 数据框中显示结果
A Python script that connects to a PostgreSQL database with an initialization and configuration file, and utilizes Pandas to query data and create a data frame.

The Pandas data frame with the contents of the “vendors” table.
结论
一旦将数据从 PostgreSQL 数据库导入 Python 脚本或 Jupyter 笔记本,您就能够应用强大的数据科学工具来进行分析并构建机器学习模型。您可以使用单独的初始化、配置和.gitignore文件来保护您的登录凭证。Pandas 库包含一些方法,允许我们直接从数据库中查询结果。本文中描述的整个过程可以被容器化和共享。
Python 和 R 的数据争论:并排比较 Pandas 和 Tidyverse 代码,并学习加速技巧。
成为双语数据科学家。学习加速代码技巧。用可互操作的 Python 和 R cells 编写双语笔记本。

© Artur/AdobeStock
几年前,你会专门用这两种语言中的一种来编写数据分析程序:Python 或 r。这两种语言都提供了从数据探索到建模的强大功能,并且拥有非常忠实的粉丝。然而,事情已经发生了变化。如今,有一些库,如 reticulate 和 PypeR、允许你分别将 Python 代码合并到 R Markdown 和 Jupyter 笔记本中。了解两种语言的基本功能,如数据争论,可以扩展您的编程视野,允许您与使用任一种语言的人一起工作,并创建双语笔记本,充分利用每种语言的优势。在本文中,我们将分别使用 pandas 和 tidyverse 库来讨论 Python 和 R 中的数据争论,以及加速代码的技巧。读完这篇文章后,你会发现 Python 和 R,在许多表达式上非常相似,至少在数据争论方面是如此。所以,你只要额外付出一点努力,就能掌握两种语言的数据角力,成为“超级 R -Pythonista”!
做一个超级——T21——巨蟒(而不仅仅是一个巨蟒!)
双语 R 降价文件
他的文章附有一个 R Markdown 文件,你可以在 github 上找到。 在这个文件中,数据角力操作实现了两次:在 Python 和 R 单元格中,彼此相邻。这可以通过导入 reticulate 库来实现。Python 和 R 单元目前独立工作;在我的下一篇文章中,我将展示 Python 和 R 单元之间的参数传递以及跨语言通信。
R Markdown 文件也可以作为 Python 和 R 数据争论操作的两个独立的备忘单。您也可以下载 R Markdown 文件的编织版本。两种语言的数据争论是在相似的结构上进行的:R 数据帧和 Python 数据帧。具体实现的操作有:
A.创建/读取数据帧。
B.总结。
C.使用索引、名称、逻辑条件、正则表达式选择行、列、元素。将介绍 Python 和 R 中的 filter() 函数。
D.删除-添加行、列。我们将讨论 R 中的 mutate() 函数和 Python 中的 map 。
E.对行/列应用函数,包括 Python 中的 l ambda 函数。
F.加速代码。我们将讨论一些技术,比如并行化和用于代码加速的函数编译。
数据争论操作
在前面提到的 R Markdown 文件中,Python 代码被括在下面的括号中,这些括号定义了一个 Python 单元格:
`{python}
虽然 R 代码包含在这些括号中:
` ` `{r}
这些括号由 RStudio 自动生成,点击插入选项卡并选择您想要的语言。
编程提示:在执行 R Markdown 中的任何 Python 单元之前,执行导入 reticulate 库的 shell。注意,在同一个单元格中,我们指示 RStudio 使用哪个 Python。另外,使用命令 py_config()在 RStudio 控制台中配置 Python 也是一个好主意。
值得注意的是,在这两种语言中,有多种方式来执行操作,其中许多在前面提到的 R Markdown 文件中有所介绍。由于篇幅所限,在本文中,我们将只介绍其中的一部分。让我们开始吧:
从 CSV 文件中读取数据帧
正如我们在下面的要点中看到的,在两种语言中,我们都调用一个读取 csv 文件的函数。在 Python 中,这个函数是通过 pandas 库调用的。 heart.csv 文件来自 Kaggle 和 UCI 库。
在下面的 Python 代码中值得注意的是使用了一个 lambda 函数(一个匿名内联函数)来排除读取 drop_col 列表中定义的列。
编程提示:在 R 中定义 read.csv() 函数的编码是一个好主意,如下所示,以确保正确读取所有列名。当我不使用编码时,第一列的名称被读错了。我不需要用 Python 定义编码。
从头开始创建数据框架
正如下面的所示,在这两种语言中,一个数据帧可以从一个低阶结构中创建,分别用于 Python 和 R 的矩阵和数组。
在 R 部分中,存款帧 数据帧包含 3 个人的银行存款(单位:千)。使用 rnorm() 函数生成银行存款,该函数生成 6 个随机数,平均值为 12,标准差为 3。
编程技巧:正如你在下面的 R 代码中注意到的,我们导入了 tidyverse 库,这是一个包含很多包的库,比如 dplyr 。我们在 R 代码段中需要的函数只在 dplyr 库中定义。那么为什么要进口 tidyverse,而不是我们刚需的 dplyr 呢?一个原因是未来的可扩展性(我们可能需要使用其他包中的函数)。另一个原因是当我们导入库(dplyr) 时,得到一个警告,它的重要函数 filter() 被另一个库屏蔽了。如果我们导入库(tidyverse) ,就没有这个问题了。
摘要
H 在这里,我们将检查两种类型的汇总:(a)汇总关于数据帧的基本信息的函数。(b)允许对数据切片进行分组和定制洞察的功能。
描述数据帧一般信息的函数
在以下要点中,在 R 部分, head() 函数显示了数据帧的前几行(观察值)。 glimpse() 函数显示观察值的数量、变量(列)以及后者的类型、名称和值。与 glimpse() 功能类似的信息由 str() 功能显示。一个更有用的函数是来自 prettyR 包的 describe() 函数,它显示每个变量的基本统计数据和有效案例数。变量统计信息也由 summary() 函数显示。与 R 类似,在 Python 部分中, head() 函数显示第一行,而 describe() 函数显示每一列的基本统计信息,比如平均值、最小值和最大值。
分组和汇总组内的信息
R 和 Python 都有基于一个变量或一组变量进行分组的函数。如以下要点所示,在 R 中,我们可以使用以 R 为基数的函数 tapply() ,分别计算男性和女性的平均胆固醇。或者,我们可以使用函数 group_by() ,后跟汇总函数,如 count() 、 mean() 或summary _ all()。类似地,如下图所示,在 Python 中,组总结是由函数 groupby() 执行的,后面是总结函数,如 count()。
分组的另一个例子,在 R:
Python 中分组汇总的另一个示例:
基于索引的行、列、元素选择
这里需要注意的一点是,R 中的索引从 1 开始,而 Python 中的索引从 0 开始。在 Python 中,我们使用 iloc 索引器来访问整数位置。
使用名称、正则表达式、逻辑条件进行列/行选择
这里有一些需要注意的亮点:
- 在 R 中使用 % > % 管道操作符,它允许你编写简洁的功能代码。
- 使用 R 中的 select() 函数选择列,使用列名、正则表达式和/或逻辑表达式。例如,在第 7、8 行中,select() 语句用于选择包含字母 m 的所有列,它们的 max() 小于 12。
- 函数 filter() ,这是在 r 中进行选择的另一种方法。下面的函数 filter_all() 允许我们选择满足标准的所有行。
- Python 提供了类似的 filter() 函数供选择。axis 参数指定操作是应用于列还是行。
行/列删除和添加
B elow,值得注意的有以下几点:
- 在 R 中,对列名使用减号操作符来执行列删除。我们也可以使用字符串匹配函数,比如 starts_with() ,如下图所示。其他类似的函数有
ends_with(),matches(),contains()。 - 在 R 中,只需使用列名就可以添加列(下面的列 Brandon )。另一种流行的方法是来自 dplyr 库中的 mutate() 函数。这里,我们使用 mutate() 添加一个列 sumdep ,其中这个新列是两个现有列的总和。请注意与 t ransmute() 函数的区别,它只添加新列(不保留旧列)。如下图所示,使用 mutate() 的一个特别有趣的方法是将它与 group by 结合起来,在组内进行计算。
- 在 Python 部分,值得注意的是添加了一个使用逻辑的列,该逻辑是通过 map() 函数实现的。
对列/行应用函数
BPython 和 R 都提供了 apply() 函数,允许我们根据一个额外的参数在行或列上实现一个函数。例如下面,在 R 中,如果中间的参数是 2,则函数应用于列,而如果是 1,则应用于行。在 Python 中, apply() 函数的决定参数是 axis (0,应用于列,1,应用于行)。下面另一个有趣的花絮是使用一个 lambda 函数将所有元素乘以 5。
加速考虑
在这里,我们将简要讨论三种加速 R 中代码的方法: data.table 、并行化和函数编译。对于 Python,将呈现的加速选项是 modin 。

©Syda Productions/AdobeStock
r:数据表
数据表是数据帧【2】的扩展,其优点有两方面:
(a)对于大文件,加载数据比数据帧快得多。下面是一个例子。我读取一个大文件(66.9MB),使用函数 read.csv(),返回一个数据帧,以及 fread(),返回一个数据表。差异非常显著(例如,对于 data.table,用户+系统时间大约快了 30 倍!)。我使用的数据文件的引用是 references 部分中的[3]。
(b)简洁、紧凑的过滤表达式,其执行速度也相当快。使用数据表进行过滤以及与 dplyr 过滤进行比较的示例如下所示。在这个要点中,我们使用库 dplyr 的 filter() 计算胆固醇> 280 的男性的平均年龄。我们也用数据表计算它,这是用在一行简洁的代码中完成的。
r:并行
在 R 中,并行化是使用并行库[4]实现的,这允许我们的 R 程序利用我们计算机中的所有内核。我们可以使用函数 detectCores() 来找出我们有多少个内核,然后我们可以使用函数 makeCluster() 来创建一个包含这些内核的集群。这显示在下面的要点中。然后,我们可以使用 apply() 系列函数的并行版本来执行各种操作。例如,在下面的要点中, sapply() 函数及其并行版本 parSapply() 用于对大量数字求平方。我们可以看到, parSapply() 的用户+系统时间是 7.95+0.66=8.61,而 plain apply()的用户+系统时间是 11.80+0.03=11.83。
r:函数编译
另一种提高 R 语言运算速度的方法是编译函数。这是通过编译器库及其 cmpfun() 函数【5】实现的。下面的要点显示了这一点,以及并行化、编译在时间消耗方面的比较。我们看到,当我们结合并行化和编译时,实现了最快的用户+系统时间(7.73)。就其本身而言,只实现的并行化比只实现的函数编译(8.87)产生更快的用户+系统时间(8.61)。
结束我们对超速 R 的讨论,值得一提的是库gpuR【6】。使用 ViennaCL 库, gpuR 允许在任何 GPU 上并行计算,这与之前的 R 包在后端依赖 NVIDIA CUDA 形成对比。
Python:摩丁
如[7]中所述,modin 是一个数据框架库,具有与 pandas 完全相同的 API,并允许显著加速工作流(在 8 核机器上快 4 倍)。要使用它,我们只需要修改一行代码。而不是:
进口熊猫作为 pd 我们将使用:
导入 modin.pandas 作为 pd
一句忠告。modin 是建立在 Ray 之上的,所以要确保你有正确版本的 Ray。
你可以在我的 github 库中找到一个额外的赠品是另一个双语 R Markdown 文件,它实现了数组创建(1-D 和 2-D)和使用数组的数学函数(点积、特征值等)。)都有 Python 和 r .你也可以下载它的针织版。
感谢阅读!
参考
- Pandey,p .,从 R vs. Python,到 R 和 Python,https://towardsdatascience . com/From-R-vs-Python-to-R-and-Python-aa 25 db 33 ce 17
- 数据表简介,https://cran . r-project . org/web/packages/data . table/vignettes/datatable-intro . html
- MITx 和 HarvardX,2014 年,“HMX PC 13 _ DI _ v2 _ 5–14–14 . CSV”, HarvardX-MITx 个人课程 2013 学年 De-Identified 数据集,v 版本 2.0 ,https://doi.org/10.7910/DVN/26147/OCLJIV,哈佛数据世界,V10
- 特雷德韦、并行运行 R 代码、https://www.r-bloggers.com/running-r-code-in-parallel/
- 罗斯,n .快一点!更高!更强!-忙人加速 R 码指南,https://www . R-bloggers . com/faster-higher-stonger-A-Guide-to-Speeding-Up-R-Code-for-Busy-People/
- 包 gpuR ,https://cran.r-project.org/web/packages/gpuR/gpuR.pdf
- Pandey,p .,用 Modin 获得更快的熊猫,甚至在你的笔记本电脑上,https://towards data science . com/Get-faster-pandas-with-Modin-even-on-your-laptops-b 527 a2 eeda 74
Python 和 R——数据科学的明确拥护者
Python 和 R 仍然是数据科学中最顶尖的两种编程语言

数据科学是一个非常广阔的领域,包括几个细分领域,如数据工程、数据准备和转换、数据可视化、机器学习和深度学习。
虽然做数据科学需要几项技能( 数据科学最低要求:开始做数据科学需要知道的 10 项基本技能 ),但数据科学需要的两项最基本的要求是:
A)扎实的数学背景
b)编程技能
本文将讨论从事数据科学所需的基本编程语言。需要的必备数学技能,请看下面: 机器学习必备数学技能 。
数据科学中使用的编程语言
如果你从诸如indeed.com或 LinkedIn、等平台上的数据科学家招聘广告中取样,你会对数据科学招聘广告中提到的技术技能有所了解。数据科学家招聘广告中提到的一些技术技能(编程语言)包括:
- Python
- R
- Matlab
- Hadoop
- SAS
- SQL
- 画面
- Excel
- 电源毕
- AWS
- 蔚蓝色
- Java
- 朱丽亚
- Scala
数据科学家招聘广告中提到了各种各样的技能,每个对学习数据科学基础感兴趣的初学者都会很自然地问自己:
应该重点学习什么编程语言?
如果你有兴趣学习数据科学的基础知识,你需要从某个地方开始。不要被数据科学家招聘广告中提到的一系列荒谬的编程语言所淹没。虽然学习尽可能多的数据科学工具很重要,但建议从一两种编程语言开始。一旦你在数据科学方面建立了坚实的背景,你就可以挑战自己,学习不同的编程语言或不同的平台和生产力工具,以增强你的技能。
作为数据科学初学者,你应该关注的编程语言
根据这篇文章( 数据科学家最需要的技术技能 ),当谈到编程语言时,Python 和 R 仍然是数据科学的明确冠军。
作为初学者,可以从一种编程语言开始,比如 Python,然后再学习 R,或者可以同时学习两种语言。
好的一面是,你可以从数据科学培训课程中学到 Python 和 R 的技能。大多数数据科学培训项目通常从编程基础开始。因此,如果要使用 R 来教授数据科学专业,他们通常会从 R 基础或 Python 基础等课程开始,如果该专业是使用 Python 来教授的话。
因此,如果你有一些基本的编程背景,你实际上可以通过在线课程自学数据科学。开始数据科学之旅并不需要 Python 或 R 编程方面的高深知识。您将在整个培训过程中学习和掌握这些语言,完成家庭作业,阅读书籍,以及利用大量在线资源提供 R 和 Python 编程帮助。
如果您对可以让您开始学习 R 或 Python 基本编程课程的数据科学专业感兴趣,这里有两个我最喜欢的数据科学专业,它们将教您如何使用 Python 和 R 学习数据科学(对于那些以前接触过基本编程的人):
(一)数据科学专业证书(HarvardX,通过 edX):https://www.edx.org/professional...
包括以下课程,全部使用 R 教授(您可以免费旁听课程或购买认证证书):
- 数据科学:R 基础;
- 数据科学:可视化;
- 数据科学:概率;
- 数据科学:推理和建模;
- 数据科学:生产力工具;
- 数据科学:扯皮;
- 数据科学:线性回归;
- 数据科学:机器学习;
- 数据科学:顶点
(ii) 应用数据科学与 Python 专业化(密歇根大学,通过 Coursera):https://www.coursera.org/special...
包括以下课程,全部使用 Python 教授(您可以免费旁听大多数课程,有些课程需要购买认证证书):
- Python 中的数据科学导论;
- 应用 Python 绘图、制图和数据表示;
- Python 中的应用机器学习;
- Python 中文本挖掘的应用:
- Python 中的应用社会网络分析。
总之, Python 和 R 仍然是数据科学中最顶尖的两种编程语言。以我个人的经验,我使用 Python 进行机器学习应用,而我发现 R 对于统计分析非常有用。基本上,用 Python 可以做的一切也可以用 R 实现。学习如何用 Python 和 R 来做数据科学是值得的,因为这将有助于增加你获得数据科学家工作的机会,因为这些语言是大多数数据科学招聘广告中提到的前两种语言。
Python 和 WSL
在 Windows 和 Linux 上获得更好的 Python 体验。

我最近写了一篇简短的介绍性文章,介绍优步的 H3 地理空间索引算法。为了说明 H3 背后的思想,我使用了公开可用的 Python API。和大多数 Python 代码库一样,这是以包的形式提供的,包是 Python 代码重用的通用语言。您可以使用专门的工具来安装和管理这些软件包。对于常规的 Python 发行版,这个工具是 PIP,通常工作得很好,尤其是如果你是使用 Windows 机器的而不是。
在过去 12 个月的大部分时间里,我一直在使用高端 MacBook Pro,安装和管理 PIP 软件包已经变得轻而易举。这种体验与我之前使用 Windows 的体验形成了鲜明对比。然后,失败率很高,特别是带有需要在包安装期间编译的 C 或 C++代码的包。对于这些软件包,几乎总是有正确的解决方案:克里斯托夫·高尔克的网页。在这里,您可以找到各种预打包的" wheel "文件,它们在安装过程中不需要使用编译器。别人已经替你做了家务。您下载“ wheel ”文件,并使用您忠实的 PIP 安装它。上面的樱桃是您可以选择的选项,从 Python 版本、处理器类型,甚至编译选项。无处不在的 NumPy 包就是这种情况,它有普通版本和 MKL 版本。我从未能在我的 Mac 电脑上安装支持 MKL 的 NumPy,所以 Windows 用户在这里能得到更好的服务。问题在于像 H3 这样的特色套餐。除非他们出名,否则你很可能在 Christoph 的页面上找不到他们。如果你在那里看不到它们,很可能你在任何地方也找不到它们。
这就是我的经历,在发表了 H3 的文章后,我决定在我的侏罗纪 Windows 10 盒子上试试。我按照指示使用 PIP 安装了 H3 软件包,但是问题马上就出现了。在我的特殊例子中,CMake 工具的明显缺失使得在安装了之后仍然存在。我知道我在做什么,所以我立即放弃了。解决这些问题通常需要安装一个模糊版本的 Microsoft C++编译器,确保它出现在 PATH 变量中,等等。我钦佩那些有技能和耐心配置他们的 Windows 机器来干净地编译这些包的人。不幸的是,我不属于那个群体。
当我正在考虑放弃这个的时候,我记得读过一些关于在 Windows 上运行 Tensorflow 的文章。这篇博客帖子来自斯科特·汉瑟曼,标题是“ 在 Windows 上玩张量流。”这段文字可以追溯到 2016 年,当时 Tensorflow 甚至无法在 Windows PC 上编译。作者是如何设法绕过它的?他用了 WSL。
输入 WSL
WSL 代表“ Windows 子系统 for Linux ,”,它允许您在 Windows 系统中运行 Linux 系统,而无需虚拟机。当斯科特写下他著名的博客文章时,它还处于测试阶段。现在 WSL 功能齐全,并且已经有了工具支持。在我们进入所有这些好东西之前,让我解释一下我是如何让 H3 代码运行的。
首先,您必须启用 WSL ,然后从微软商店安装一个 Linux 发行版。我选择了 Ubuntu 18,但是你可以在读到这篇文章的时候选择任何可用的版本。请理解,你很可能会得到一个操作系统的裸机版本,所以你应该准备安装很多辅助工具,比如 PIP 本身。Ubuntu 18 已经安装了 Python 3.6.7,所以这似乎是正确的选择。
使用命令行工具从 GitHub 下载代码轻而易举。完成后,我试图创建一个虚拟环境。不幸的是,VENV 包不可用,所以我必须安装它。Ubuntu 帮助了我,告诉我应该发出什么命令,我又回到了我的路上。除了意外缺少“轮子”包之外,设置虚拟环境太容易了。在等待老 CPU 咀嚼安装程序列表后,我准备好了。我启动了 Jupyter Lab 命令,得到了一个 URL 作为回答。将 URL 复制到 Windows 浏览器,你瞧!我的浏览器上有一个 Jupyter 笔记本!
WSL 1 与 Windows 共享相同的基础结构。它通过在 Windows 之上创建一个 Linux 翻译层来发挥其魔力。Linux 应用程序使用操作系统的资源,就好像它们运行在真实的 Linux 机器上一样,不管是真实的还是虚拟的。IP 地址是相同的,这就是为什么您可以在 WSL 上启动 web 服务器,并在 Windows 上使用它,而不需要任何网络攻击。
刀架
到目前为止,我很想说你正在体验两个世界的精华。但是有一个问题。你能用 WSL 使用你最喜欢的 IDE 吗?我寻求这个问题的答案,并感到惊喜。
Visual Studio Code 最近发布了一个插件,它允许你在 WSL 盒子上编辑、调试和运行 Python 代码。用户界面仍然在 Windows 上,但是 Python 代码和解释器在 WSL 上。在撰写本文时,该插件仍处于测试阶段,但可以像宣传的那样工作。
PyCharm 也支持基于 WSL 的开发,尽管只是在付费版本上。
不幸的是,没有 CUDA 支持,尽管有计划在未来的版本中加入。对于深度学习项目来说,这将是一个很好的补充。
结论
这是治疗 Windows 疾病的灵丹妙药吗?虽然肯定不是一个完整的解决方案,但微软正朝着正确的方向前进。WSL 的某些方面必须改进,比如 WSL 不运行时对文件系统的远程访问,以及对深度学习人群的 CUDA 支持。无论如何,我想说这为在 Windows 平台上获得更好的数据科学体验打开了一扇门。这应该值得你去做。
笔记
[1]WSL 的第二版将使用一个精简的轻量级虚拟机。
对 WSL 如何工作的描述是我对版本 1 的理解。如果你想更详细地了解这个漂亮软件的工作原理,请参考更权威的资料。
[## joo Paulo Figueira-数据科学家- tb.lx by 戴姆勒卡车和公共汽车| LinkedIn
查看 joo Paulo Figueira 在全球最大的职业社区 LinkedIn 上的个人资料。圣保罗列出了 1 份工作…
www.linkedin.com](https://www.linkedin.com/in/joao-paulo-figueira/)
Python 基础
Python 编程基础

此处涵盖的主题—
- 变量
- 数据类型
- 铅字铸造
- 公式
- 字符串操作
用 Python 向世界问好:
print('Hello, World!')*Output:* Hello, World!
我们用的是什么版本的 Python?
import sys
print(sys.version)*Output:* 3.7.3 (default, Aug 20 2019, 17:04:43)
[GCC 8.3.0]
变量:
我们可以在变量中存储任何值(数字、单词),以便以后使用。例如:
x = 5
print(x)*Output:* 5
这里我们用赋值操作符“=”给变量“x”赋值 5。
所以 x 的值是 5。
x += x
print(x)*Output:* 10
使用x+=x命令,我们正在执行x+x.
这里,5+5 = 10。
所以,
x+=x代表x+xx-=x代表x-xx*=x代表x*xx/=x代表x/x
y = 2 + 3
print(y)*Output:* 5
在这里,我们使用“y”变量在 2 和 3 之间分配了一个求和运算。
x += y
print(x)*Output:* 15
这里我们添加了两个不同的变量‘x’和‘y’。
z = x+y
print(z)*Output:* 20
这里,我们将两个不同变量(' x ',' y ')相加的值存储在一个不同的变量(' z ')中。
z *= z
print(z)*Output:* 400
Python 中的数据类型:
Python 中主要有 3 种数据类型
- 字,即众所周知的“字符串”
- 布尔型(真或假)
- 数字
数字也有两种类型
- 整数
- 浮动
所以,总的来说有三种数据类型-
- 线
- 布尔代数学体系的
- 整数
- 浮动
整数:-
这里有一些整数的例子。整数可以是负数或正数:

integer_data = 12
type(integer_data)*Output:* int
这告诉我们 12 是一个整型对象。使用type命令,我们可以看到任何给定输入或输出的数据类型。
浮动:-
浮点数代表实数;它们是整数的超集,但也包括“带小数的数”。当涉及到表示实数的机器时有一些限制,但是浮点数在大多数情况下是一种很好的表示。
float_data = 2.14
type(float_data)*Output:* float
这告诉我们 2.14 是一个浮点类型的对象。
字符串:-
Python 中的字符串可以用单引号、双引号甚至三引号来创建。
string = 'python'
type(string)*Output:* str
这告诉我们‘python’是一个字符串类型的对象。
string_integer = '10'
type(string_integer)*Output:* str
这告诉我们' 10 '是一个字符串类型的对象。
string_float = '10.01'
type(string_integer)*Output:* str
这告诉我们‘10.01’是一个字符串类型的对象。
布尔:-
布尔方法用于使用标准的真值测试过程返回或转换一个值为布尔值,即真或假。
boolean_true = True
type(boolean_true)*Output:* bool
这告诉我们 True 是一个布尔类型的对象。
boolean_false = False
type(boolean_false)*Output:* bool
这告诉我们 False 是一个布尔类型的对象。
类型转换:
从一种对象类型转换为另一种对象类型的过程。
将整数转换为浮点数:-
print("Integer data before typecasting: ", integer_data)
print("Datatype before typecasting: ", type(integer_data))
integer_data = float(integer_data)
print("Datatype after typecasting: ", type(integer_data))
print("Integer data after typecasting: ",integer_data)*Output:* Integer data before typecasting: 12
Datatype before typecasting: <class 'int'>
Datatype after typecasting: <class 'float'>
Integer data after typecasting: 12.0
之前,我们已经看到 12 是一个整数类型的对象。现在,如果我们写float(12),这意味着我们给出了将 12 的数据类型从整数转换为浮点的命令。
所以 12 的数据类型现在是 float。
所以这里最初integer_data变量的数据类型是int并且值是 12 &在执行类型转换之后integer_data的数据类型变成了float并且值变成了 12.0。
将浮点数转换为整数:
print("Float data before typecasting: ",float_data)
print("Datatype before typecasting: ", type(float_data))
float_data = int(float_data)
print("Datatype after typecasting: ", type(float_data))
print("Float data after typecasting: ",float_data)*Output:* Float data before typecasting: 2.14
Datatype before typecasting: <class 'float'>
Datatype after typecasting: <class 'int'>
Float data after typecasting: 2
在这里我们可以看到,在应用了从浮点到整数的类型转换之后,float_data变量的值从 2.14 变成了 2。所以我们丢失了一些数据。
因此,在执行从浮点数到整数的类型转换时,我们可能会丢失一些数据。
将字符串转换为数字:
print("String data before typecasting: ", string_integer)
print("Datatype before typecasting: ", type(string_integer))
string_integer = int(string_integer)
print("Datatype after typecasting: ", type(string_integer))
print("String data after typecasting: ",string_integer)*Output:* Float data before typecasting: 10
Datatype before typecasting: <class 'str'>
Datatype after typecasting: <class 'int'>
Float data after typecasting: 10
将布尔值转换为整数:
print("String data before typecasting: " + str(boolean_true))
print("Datatype before typecasting: ", type(boolean_true))
boolean_true = int(boolean_true)
print("Datatype after typecasting: ", type(boolean_true))
print("String data after typecasting: ",boolean_true)*Output:* String data before typecasting: True
Datatype before typecasting: <class 'bool'>
Datatype after typecasting: <class 'int'>
String data after typecasting: 1
在这种情况下,如果布尔值为真,那么它的整数形式将是 1,如果布尔值为假,那么它的整数形式将是 0。
表情:
Python 中的表达式可以包括兼容类型(例如整数和浮点)之间的运算。例如,像将多个数字相加这样的基本算术运算:
执行加法:-
add = 43 + 60 + 16 + 41
print(add)*Output:* 160
执行减法:-
subtract = 50-60
print(subtract)*Output:* -10
执行乘法:-
multiply = 5*5
print(multiply)*Output:* 25
表演部分:-
division_float = 25/5
print(division_float)
type(division_float)*Output:* 5.0
float
这里division变量的数据类型是 float。
对于 Python 3 中的除法,除法的结果总是以浮点数据类型的形式出现。
division_int = 25//5
print(division_int)
type(division_int)*Output:* 5
int
为了将除法的结果从浮点数转换为整数,我们可以在除法运算时使用//而不是/。
执行电源操作:-
power = 5**2
print(power)*Output:* 25
字符串操作:
什么是字符串?
以下示例显示了包含在两个引号内的字符串:
'Michael Jackson'
'1 2 3 4 5 6 '
'@#2_#]&*^%$'
字符串可以由单词、数字和特殊字符组成。
索引:
现在假设我们将一个名为“Michael Jackson”的字符串赋给一个名为“name”的变量。
Name = "Michael Jackson"
索引有助于将字符串视为有序序列。可以使用由数字数组表示的索引来访问序列中的每个元素:

print(Name[0])
print(Name[14])*Output:* M
n
这里我们打印字符串的第一个和最后一个元素。
len("Michael Jackson")*Output:* 15
这里我们使用len来查找字符串中的字符数。
切片:
我们可以使用切片从一个字符串中获得多个字符,我们可以获得第 0 到第 4 和第 8 到第 12 个元素:

print(Name[0:4])
print(Name[8:12])*Output:* Mich
Jack
步幅:
我们也可以如下输入一个步幅值,“2”表示我们每隔一秒选择一个变量:

print(Name[::2])*Output:* McalJcsn
连接字符串:
我们可以使用加法符号连接或组合字符串,结果是两者组合成一个新字符串:
Statement = Name + "is the best"
print(Statement)*Output:* Michael Jacksonis the best
要复制一个字符串的值,我们只需将该字符串乘以我们想要复制它的次数。在这种情况下,数量是三。结果是一个新字符串,这个新字符串由原始字符串的三个副本组成:
3 * 'Michael Jackson'*Output:* 'Michael JacksonMichael JacksonMichael Jackson'
转义序列:
反斜杠代表转义序列的开始。转义序列表示可能难以输入的字符串。例如,反斜杠“n”代表一个新行。遇到反斜杠“n”后,输出由新行给出:
print(" Michael Jackson \n is the best" )*Output:* Michael Jackson
is the best
类似地,反斜杠“t”代表制表符:
print(" Michael Jackson \t is the best" )*Output:* Michael Jackson is the best
我们可以在字符串前放置一个“r”来显示反斜杠:
print(r"Michael Jackson \ is the best" )*Output:* Michael Jackson \ is the best
操作:
Python 中有许多字符串操作方法可用于操作数据。我们将对数据使用一些基本的字符串操作。
我们用方法upper试试吧;此方法将小写字符转换为大写字符:
A = "Thriller is the sixth studio album"
print("Before upper:", A)
B = A.upper()
print("After upper:", B)*Output:* Before upper: Thriller is the sixth studio album
After upper: THRILLER IS THE SIXTH STUDIO ALBUM
方法replace用新的字符串替换字符串的一部分,即子字符串。我们输入想要改变的字符串部分。第二个参数是我们希望用来交换该段的内容,结果是一个段已更改的新字符串:
A = "Michael Jackson is the best"
B = A.replace('Michael', 'Janet')
print(B)*Output:* Janet Jackson is the best
方法find找到一个子字符串。参数是要查找的子字符串,输出是序列的第一个索引。我们可以找到副弦jack或者el.

Name = "Michael Jackson"
Name.find('el')*Output:
5*
Python 基础:函数

Source: https://bit.ly/2EhK47P
读完这篇文章你会知道:
- 什么是功能以及如何定义它们
- 什么是参数和自变量
- 如何从函数中返回值
- 如何定义函数文档
- Python 中的范围代表什么
- 什么是关键字参数
- 灵活和默认参数
- 什么是 Python 异常以及如何处理以及如何引发它们
- 断言语句
介绍
功能是一段有组织的可重用代码解决一个特定任务。函数帮助我们保持代码的整洁,并为我们提供代码重用的能力。
内置函数
Python 有几个内置函数,它们总是可用。其中一些功能是 打印 和 最大 。让我们看一些如何使用它们的例子。
- 打印功能接受对象并打印它们
输出:
Python
chess backgammon
- max 函数接受一个可迭代并返回最大的项
输出:
49
我们可以看到,我们只是通过写函数名来调用函数,并传递所需的输入。
用户定义的函数
作为程序员或数据科学家,有时我们需要特定于我们需求的功能。在这些情况下,我们可以定义自己的函数。
要定义函数,我们需要遵循以下规则:
- 使用关键字
**def**引入一个功能定义 - 编写函数名后跟括号
**()**和一个冒号**:** - 在这些括号(可选)内定义一个参数列表
- 编写函数体。构成函数体的语句从下一行开始,并且必须是预期的。当函数被调用时,函数体中的代码为 run 。
- 函数体的第一条语句可以是代表函数文档(可选)的字符串文字
- 使用
**return**关键字将结果传回给调用者(可选)
如果你认为有许多步骤,不要担心。我们将看到许多例子,概念将变得非常简单。
定义不带参数的函数
让我们看一个没有可选部分的函数定义的简单例子。我们可以通过键入函数名,后跟括号 ()来调用函数。当我们调用这个函数时,它将打印当前日期。
输出:
2019-02-19
请注意,这个输出对您来说可能是不同的。输出将是您调用该函数的日期。
定义带参数的函数
我们几乎总是需要函数参数。当我们想要将数据传递到我们的函数中时,就会用到它们。当我们调用一个函数时,我们将参数传递给这个函数。
一个参数示例
输出:
Forgive yourself for your faults and your mistakes and move on. - Les Brown
当我们定义这样的参数时,它是必需的。如果我们不为这个参数传递一个参数值,这会给我们一个错误。
输出:
**--------------------------------------------------------------------**
**TypeError** Traceback (most recent call last)
**<ipython-input-48-f2f093834d71>** in <module>**()**
2 print**(**text**)**
3
**----> 4** print_message**()**
**TypeError**: print_message() missing 1 required positional argument: 'text'
稍后,在这篇博文中,我们将看到如何定义可选参数(带有默认值的参数)。
多参数示例 我们可以添加任意多的参数,我们只需要用逗号将它们隔开。在许多情况下,我们需要不止一个参数。
参数传递的顺序对应于函数定义中参数的顺序。在下面的例子中,我们将 10 作为参数number1的值,将 5 作为参数number2的值。
输出:
15
然而,如果我们想将结果保存在变量中,我们不能使用上面的函数定义,因为我们的函数只是打印结果,我们不使用**return** 语句。让我们看看如果我们试图保存结果会发生什么。
输出:
15
None
我们可以看到 sum 被打印出来(因为我们的函数被调用了),但是result变量的值是None而不是 15。在下一节中,让我们看看如何返回值。
定义返回结果的函数
我们可以使用**return** 关键字离开当前的函数调用并返回想要的结果。让我们看看如何重新定义函数sum_numbers来返回总和,而不是打印出来。
输出:
15
我们可以清楚地看到,这次result变量的值是 15。
定义返回多个值的函数
我们还可以定义一个返回多个值的函数。我们可以通过在函数中构造名为 元组 的对象来实现。Python 中的 tuples 数据类型是一个 不可变的 序列 。这意味着它们可以像列表一样包含多个值。
输出:
2019-02-19
08:23:38.030659
在上面的例子中,我们使用序列解包。我们只是将元组中的值“解包”到date和time变量中。
用文档定义函数
在上一个例子中,我们定义了一个函数,它以元组的形式返回当前日期和当前时间。也许很容易理解函数是做什么的。然而,如果这个函数有一个文档不是很好吗?
输出:
2019-02-19
08:26:49.538434
在许多情况下,我们的函数比上面的例子更复杂。在这些情况下,为你的功能准备文档总是更好的。功能文档可包括以下信息:
- 函数参数
- 函数计算
- 返回值/秒
范围
并不是所有我们定义的对象(包括函数)在 Python 代码中都是可访问的。程序的范围代表,其中变量名或函数名可以被访问。Python 有 3 种作用域:局部、全局和内置作用域。
局部范围
当我们的对象或函数在函数中定义时,这意味着它有一个局部范围。我们只能在定义这个对象或函数的函数内部使用它。
全球范围
当我们的对象或函数在 Python 代码的主体中定义时,这意味着它具有全局范围。我们可以在任何地方访问这个对象或函数,因为它是全局的。
内置范围
在 Python 中,我们有许多内置于中的函数和类型,它们总是可用的。
附加注释
- 如果我们在函数中使用变量名或函数名,Python 将首先在局部中搜索该名称,然后在全局中搜索,最后在内置** 作用域中搜索。**
- 如果我们在脚本的主体中使用变量或名称,Python 将首先在全局中搜索该名称,然后在内置 范围中搜索该名称。
是时候看看几个例子了。
Local scope Example
输出:
15**--------------------------------------------------------------------**
**NameError** Traceback (most recent call last)
**<ipython-input-2-faf6ee9da5b3>** in <module>**()**
4
5 print**(**sum_numbers**(10,** **5))**
**----> 6** print**(**result**)**
**NameError**: name 'result' is not defined
在这个例子中,我们可以看到我们可以在函数中访问result变量,但是在函数之外我们不能访问它。那是因为**result** 变量有局部作用域,只能在sum_numbers函数内部访问。
Global vs Local Scope Example
输出:
Hi, Ventsi the sum is 15**--------------------------------------------------------------------**
**NameError** Traceback (most recent call last)
**<ipython-input-1-747761fcaa8f>** in <module>**()**
7
8 print**(**sum_numbers**(10,** **5))**
**----> 9** print**(**result**)**
**NameError**: name 'result' is not defined
在这个例子中,我们可以看到我们可以访问函数中的全局变量**name**。同样,我们使用了**str()** 内置函数。然而,**result** 变量又有一个局部范围,它不能在sum_numbers函数之外被访问。
Local vs Global Scope Example
输出:
15
100
这里,我们全局定义了一个result变量,然后我们在函数中定义了另一个与同名的变量。我们可以看到,全局范围的值是 100,因为我们的全局变量有这个值。此外,sum_numbers(10,5)的值是 15,因为局部变量result的值是在函数内部计算的。
Change the Value of a Global Variable Inside a Function
输出:
programming
machine learning
当我们想在某个函数中改变一个全局变量的值时,我们可以使用关键字**global**。****
关键字参数
关键字参数与函数调用相关。当您在函数调用中使用关键字参数时,调用者通过参数名识别参数。
这允许您跳过参数或将它们打乱顺序,因为 Python 解释器能够使用提供的关键字将值与参数匹配。
来源:https://www.tutorialspoint.com/python/python_functions.htm
我们可以使用关键字 arguments 使用参数名称 和 **=** 符号。
Keyword Arguments Example
输出:
**8
8
9**
默认参数
在某些情况下,我们有一个带有多个参数的函数,其中一些参数有一个公共值。我们可以为一些函数参数指定默认参数。在这些情况下,我们可以调用我们的函数,而不用为带有默认参数的参数指定值。在 Python 中要做到这一点,我们可以使用=符号后跟默认值。
Default Argument Example
输出:
**81
4
8**
从上面的例子中,我们可以看到power参数有一个默认值 2 。当我们没有为它指定值时,将使用默认值。然而,正如我们看到的,我们可以传递一个不同的值。
Default Arguments Example
输出:
**The number 2 raised to the power 2 is: 4
8
9**
灵活的论据
有时候,我们想要更多灵活的功能。例如,我们可能想要定义一个函数,它接受的参数比我们在函数中指定的参数多。换句话说,我们可能需要向函数传递任意数量的参数。我们可以在函数定义中使用**特殊语法args 和 kwargs 来实现这一点。*
*参数
这些参数被称为无名称变长参数。
输出:
**<class 'tuple'>
(1, 23, 4, 52, 2, 123)**
我们可以看到args 语法将所有参数作为一个名为 **args**的元组传递给函数。因此,我们可以对元组数据类型做任何我们能做的事情。*
输出:
**5
10
15**
输出:
**10.0
18.0
15.75**
***** 克瓦查***
这些参数被称为变长参数。
输出:
**<class 'dict'>
{'age': 24, 'position': 'data analyst'}
<class 'dict'>
{'name': 'Jake', 'email': 'jake@gmail.com', 'position': 'machine learning engineer'}**
我们可以看到,kwargs 语法将所有参数作为一个名为 **kwargs**的字典传递给了函数。因此,我们可以对 dictionary 数据类型做任何我们能做的事情。**
输出:
**age 24
position data analyst
name Jake
email jake@gmail.com
position machine learning engineer**
其实变量名可以随便,我们只需要在前插入一个星号 ***** 或者两个星号 即可。**
输出:
**10.0
age 24
position data analyst**
调用函数时使用和***
我们已经看到,我们可以在函数定义中使用这种特殊的语法。然而,我们也可以在调用函数时使用它。
输出:
name: Jake
position: data analyst
email: jake@gmail.com
输出:
name: Jake
position: data analyst
email: jake@gmail.com
Python 异常
在许多情况下,当我们错误地使用函数时,它会引发一个异常(错误)。****
例如,我们可以从文档中看到内置函数[int()](https://docs.python.org/3/library/functions.html#int)的部分内容:“返回一个由数字或字符串 x 构造的整数对象,或者如果没有给定参数,则返回 *0* ”
输出:
6
7**--------------------------------------------------------------------**
**ValueError** Traceback (most recent call last)
**<ipython-input-1-dea319bbd9d9>** in <module>**()**
1 print**(**int**(6.99))**
2 print**(**int**("7"))**
**----> 3** print**(**int**("Hi"))****ValueError**: invalid literal for int() with base 10: 'Hi'
我们可以看到,当我们传递“Hello”字符串作为输入时,引发了 value error。
当 Python 脚本引发异常时,我们的代码必须立即处理异常,否则程序执行将停止。
异常处理
有时,我们可能希望在不停止程序执行的情况下处理引发的异常。在 Python 中,我们可以使用 try-except 子句来实现。
**try**块让我们测试代码块的错误**except**块让我们处理一个错误
输出:
An exception occured
上面的代码引发了一个 名称错误 ,因为名称numbers没有定义。当我们没有指定我们正在寻找的异常类型时,except 将处理所有的异常类型。
当然,我们可以写一个 except 块,它是寻找一个特定种类的异常。
输出:
An NameError occured
当我们想为每个被处理的异常执行不同的代码时,我们可以定义尽可能多的,除了块。
输出:
A ValueError occured
同样,当我们需要关于异常的更多信息时,我们可以使用语法将它保存到变量中。例如,我们可能希望记录错误消息。
我们还可以添加一个**else** 块,如果没有出现错误,它将被执行。
输出:
Something went wrong
'int' object is not iterable
输出:
[1, 2, 3]
Nothing went wrong
同样,如果我们想执行一些代码而不管try 块是否产生了错误,我们可以使用**finally**T16 块而不是**else**T18 块。
想象一下,我们在你的工作目录中有一个文件filename.txt,你试图在其中写一些文本。
输出:
An error occured when writing to the file
出现错误是因为默认情况下,打开的内置函数使用“读取”模式。然而,**finally** 块在两种情况下都关闭文件连接(如果有错误或没有)。在这种情况下,我们使用这个块来确保我们的文件被关闭。
投掷错误
当我们定义自己的函数时,我们可能想要抛出错误并编写更多有意义的错误消息。让我们定义一个函数,如果两个单词是变位词,它将返回。
输出:
True**--------------------------------------------------------------------**
**TypeError** Traceback (most recent call last)
**<ipython-input-61-f1a30da1d38d>** in <module>**()**
7
8 print**(**check_anagrams**("silent",** **"listen"))**
**----> 9** print**(**check_anagrams**("silent",** **5))**
**<ipython-input-61-f1a30da1d38d>** in check_anagrams**(word1, word2)**
4 Returns **True** **if** the word1 **and** word2 are anagrams**,** otherwise returns **False**
5 """
**----> 6 return** sorted**(**word1**)** **==** sorted**(**word2**)**
7
8 print**(**check_anagrams**("silent",** **"listen"))**
**TypeError**: 'int' object is not iterable
在第二次调用函数时,我们错误地使用了它,为word2传递了整数 5。在这种情况下引发了 TypeError。我们可以看到这个信息有点混乱。因此,我们可以尝试检查这两个单词是否作为字符串传递,并抛出一个更具描述性的错误消息。我们可以使用**raise** 关键字抛出一个错误。
输出:
True**--------------------------------------------------------------------**
**TypeError** Traceback (most recent call last)
**<ipython-input-3-afb475aa2f4f>** in <module>**()**
10
11 print**(**check_anagrams**("silent",** **"listen"))**
**---> 12** print**(**check_anagrams**("silent",** **5))**
**<ipython-input-3-afb475aa2f4f>** in check_anagrams**(word1, word2)**
5 """
6 **if** type**(**word1**)** **!=** str **or** type**(**word2**)** **!=** str**:**
**----> 7 raise** TypeError**("The word1 and word2 must be strings")**
8
9 **return** sorted**(**word1**)** **==** sorted**(**word2**)**
**TypeError**: The word1 and word2 must be strings
现在,关于这个问题的信息更加具体了。尝试捕捉更具体的异常,并编写具体而清晰的错误消息。您可以从文档中查看所有异常类型。
断言语句
断言语句是对我们的代码进行完整性检查的一种便捷方式。它们是布尔表达式,检查条件是否返回True或False。如果一个条件返回True,程序将转到下一行代码。否则,将引发一个错误并停止程序执行。
我们可以把断言语句看作是“如果不抛出”语句。

在当前实现中,内置变量
__debug__在正常情况下是True,当请求优化时是False
来源:https://docs . python . org/3/reference/simple _ stmts . html # the-assert-statement
输出:
True**--------------------------------------------------------------------**
**AssertionError** Traceback (most recent call last)
**<ipython-input-68-e5a7f4b2ffc2>** in <module>**()**
10
11 print**(**check_anagrams**("silent",** **"listen"))**
**---> 12** print**(**check_anagrams**("silent",** **5))**
**<ipython-input-68-e5a7f4b2ffc2>** in check_anagrams**(word1, word2)**
5 """
6 **assert** type**(**word1**)** **==** str
**----> 7 assert** type**(**word2**)** **==** str
8
9 **return** sorted**(**word1**)** **==** sorted**(**word2**)**
**AssertionError**:
嗯,但是没有错误信息。如果我们想给这个 AssertionError 添加一个消息该怎么办?我们可以传递它,用逗号分隔,就在我们的表达式之后。

输出:
True**--------------------------------------------------------------------**
**AssertionError** Traceback (most recent call last)
**<ipython-input-66-5f2e42abf116>** in <module>**()**
10
11 print**(**check_anagrams**("silent",** **"listen"))**
**---> 12** print**(**check_anagrams**("silent",** **5))**
**<ipython-input-66-5f2e42abf116>** in check_anagrams**(word1, word2)**
5 """
6 **assert** type**(**word1**)** **==** str**,** **"The word1 must be a string"**
**----> 7 assert** type**(**word2**)** **==** str**,** **"The word2 must be a string"**
8
9 **return** sorted**(**word1**)** **==** sorted**(**word2**)**
**AssertionError**: The word2 must be a string
类型提示
Python 是一种动态类型语言。当我们定义变量、函数、类等的时候。我们不需要指定数据类型。这让我们可以更快地编写代码。
然而,从 Python 版本开始,我们能够在函数定义中添加类型提示。我们可以说出参数的预期数据类型以及函数返回的数据类型。
输出:
Hello Ventsi**--------------------------------------------------------------------TypeError** Traceback (most recent call last)
**<ipython-input-28-b1c33d5121c9>** in <module>**()**
1 print**(**greeting**("Ventsi"))**
**----> 2** print**(**greeting**(42))**
**<ipython-input-1-311aff60a9bd>** in greeting**(name)**
1 **def** greeting**(**name**:** str**)** **->** str**:**
**----> 2 return** **'Hello '** **+** name
**TypeError**: must be str, not int
我们也可以定义类型别名。
输出:
[2.0, -8.4, 10.8]
你可以在这里找到更多信息。
传递可变对象和不可变对象
当我们将 可变对象 传递给我们的函数时,我们必须小心。函数调用中传递的参数使用的是对象引用** ,而不是对象的值。所以,请记住,有些函数可以改变被传递对象的值。**
某些对象的值可以改变。值可以改变的对象 是 据说是 可变;一旦被创建,其值不变的对象称为。****
输出:
***Before calling the function: ['this is test sentence', 'I love Python', 'positive thinking is nice!']
After calling the function: ['This is test sentence', 'I love python', 'Positive thinking is nice!']***
我们可以看到,在调用我们的函数之后,我们已经更改了sentences列表的内容。
如果我们想将结果保存在一个不同的变量中,我们可以在我们的函数中拷贝列表,对拷贝版本进行大写并返回。这样,我们不会改变sentences列表本身(在某些情况下,这是您想要的)。
输出:
***Before calling the function: ['this is test sentence', 'I love Python', 'positive thinking is nice!']
After calling the function: ['this is test sentence', 'I love Python', 'positive thinking is nice!']
Capitalized sentences (result of the function): ['This is test sentence', 'I love python', 'Positive thinking is nice!']***
摘要
- 功能是一段有组织的可重用代码解决一个特定任务。****
- 我们可以添加任意多的参数,我们只需要用逗号*分隔它们。*******
- 当我们调用一个函数时,我们将参数传递给这个函数。
- 我们可以使用
**return**关键字从函数中返回一个结果 - 我们可以定义一个函数返回多个值构造名为 元组 的对象****
- 函数体的第一条语句可以是代表函数文档(可选)的*字符串文字*******
- Python 有 3 种类型的作用域 : 局部,全局和内置作用域。****
- 当我们在函数调用中使用关键字参数时,调用者通过参数名识别参数。这允许您跳过参数或将它们打乱顺序。****
- 我们可以为一些函数参数指定默认参数****
- 当我们想要有灵活的参数时,我们可以使用特殊语法*args* 和 kwargs 。***
- 我们可以使用 try-except 子句来处理引发的异常。****
- 我们可以用
**raise**关键字到抛出一个错误。**** - ****断言语句是一种方便的方式来检查我们的代码。
- 当我们将可变对象传递给我们的函数时,我们必须小心,它们的值可以被改变。****
- 附加说明:当我们编写自己的函数时,我们必须小心不要覆盖某些包名或内置函数名。****
时事通讯
如果你想在我发表新的博客文章时得到通知,你可以订阅我的时事通讯。
商务化人际关系网
这是我在 LinkedIn 上的简介,如果你想和我联系的话。我将很高兴与你联系在一起。
最后的话
谢谢你的阅读。我希望你喜欢这篇文章。如果你喜欢,请按住拍手键,分享给你的朋友。我很高兴听到你的反馈。如果你想写一篇关于λ函数的博文,请告诉我。如果你有什么问题,尽管问。😉
资源:
- https://camp . data camp . com/courses/python-data-science-toolbox-part-1/
- https://www.geeksforgeeks.org/args-kwargs-python/
- https://www.tutorialspoint.com/python/python_functions.htm
- https://www . salty crane . com/blog/2008/01/how-to-use-args-and-kwargs-in-python/
- https://stack overflow . com/questions/2052390/manually-raising-throwing-a-exception-in-python
- https://www.tutorialspoint.com/python/python_exceptions.htm
- https://www.w3schools.com/python/python_try_except.asp
- https://docs . python . org/3/library/exceptions . html # exception-hierarchy
- https://docs.python.org/3/tutorial/controlflow.html
- *****【https://docs.python.org/3/reference/simple_stmts.html *****
- https://docs . python . org/3/library/functions . html #内置函数
- https://docs.python.org/3/reference/simple_stmts.html
Python 基础:元组

Source: https://www.quora.com/Can-you-suggest-some-good-books-websites-for-learning-Python-for-a-layman
读完这篇博客后,你会知道:
- Python 中的元组数据类型是什么
- 如何初始化元组
- 如何迭代元组
- 元组上的常见序列运算
- 什么是元组解包
- 元组和列表有什么区别
介绍
元组是不可变序列,通常用于存储异构数据的集合(例如由
[enumerate()](https://docs.python.org/3/library/functions.html#enumerate)内置产生的 2 元组)。
序列是可迭代中很常见的类型。内置序列类型的一些例子有 列表 、 字符串 和 元组 。它们使用整数索引支持高效的元素访问,并定义了一个返回序列长度的方法。
值可以改变的对象被称为可变的。对象一旦被创建,其值就不可改变称为不可变。
如果你想知道更多关于 Python 中序列和不可变对象的细节,你可以看看我以前的博文:
元组看起来类似于列表。它们可以包含多个值。列表和元组的主要区别在于元组是不可变的,而列表是可变的。
元组初始化
在 Python 中,我们可以用几种方式初始化元组
- 用一对括号来表示一个空元组 :
**()** - 为具有一个值 :
**a,**或**(a,)**的元组使用尾随逗号 - 用逗号分隔项目 :
**a, b, c**或**(a, b, c)** - 使用 tuple() 内置 :
**tuple()**或**tuple(iterable)**
括号是可选的,除非在空元组的情况下,或者当需要它们来避免语法歧义时。例如,f(a, b)是一个带有两个参数的函数调用,而f((a, b))是一个带有一个作为两个元素的元组传递的参数的函数调用。
初始化空元组
输出:
<class 'tuple'>
<class 'tuple'>
()
()
用单个值初始化元组
输出:
<class 'tuple'>
<class 'tuple'>
('Python',)
('Data Science',)
初始化具有多个值的元组
输出:
<class 'tuple'>
<class 'tuple'>
(1, 2, 3)
(1, 2, 3)
从 Iterables 初始化元组
输出:
<class 'tuple'>
('Python', 'Maths', 'Machine Learning')
迭代元组
我们已经知道元组是序列数据类型,我们可以迭代序列,因为它们是可迭代的。
输出:
Pineapple
orange
banana
apple
列举
Python 中的[**enumerate(iterable, start=0)**](https://docs.python.org/3/library/functions.html#enumerate) 内置函数返回迭代器。当我们迭代这个迭代器时,它返回一个包含计数(从 0 开始)和通过迭代**iterable**获得的值的元组。
输出:
0 Pineapple
1 orange
2 banana
3 apple
0 pandas
1 scikit-learn
2 seaborn
常见顺序操作
在 Python 中,我们有一些常见的序列操作,它们被大多数序列类型支持,包括可变的和不可变的。
该表列出了按优先级升序排序的序列操作。表中,
***s******t***为同类型序列,***n***,***i***,***j***,***k***为整数,***x***为满足***s***任意类型和值限制的任意对象。****
in和not in操作与比较操作具有相同的优先级。+(连接)和*(重复)运算与相应的数字运算具有相同的优先级。

Source: https://docs.python.org/3/library/stdtypes.html#typesseq-common
让我们看看如何对元组进行这些操作。
在,不在
输出:
True
False
False
True
串联
输出:
('Python', 'Web Development', 'Machine Learning', 'Communication', 'Courtesy', 'Flexibility', 'Integrity')
增加
输出:
(['Communication', 'Courtesy'], ['Communication', 'Courtesy'], ['Communication', 'Courtesy'])
这相当于给自己加了三次soft_skills。但是,要小心! **soft_skills** 中的项不复制,被多次引用。让我们看看,如果我们向元组的第一个列表添加值,会发生什么。****
输出:
(['Communication', 'Courtesy'], ['Communication', 'Courtesy'], ['Communication', 'Courtesy'])
(['Communication', 'Courtesy', 'Responsibility'], ['Communication', 'Courtesy', 'Responsibility'], ['Communication', 'Courtesy', 'Responsibility'])
新添加的值被添加到三个列表中,因为它们引用同一个对象。
索引
输出:
Communication
Responsibility
是的,在 Python 中我们可以使用负索引(我们可以使用-1作为索引来获取最后一个元素)。当我们使用负指数时,应用此公式计算实际指数:len(sequence) + index。
在我们的例子中,长度是 5,所以我们得到索引5 — 1 = 4处的项目,这就是责任项目。**
限幅
我们可以使用切片来选择序列对象中的一系列项目。语法是sliceable[start_index:end_index:step]。
start_index是切片的起始索引,该索引处的元素将包含在结果中,默认值为0。end_index是切片的结束索引,此索引处的元素将不包括在结果中。默认值是len(sequence)。step是指标增加的数量,
默认为1。如果我们为步长设置一个负值,我们将向后移动。
输出:
**('Courtesy', 'Flexibility', 'Integrity')
('Communication', 'Courtesy', 'Flexibility', 'Integrity')
('Communication', 'Courtesy', 'Flexibility', 'Integrity', 'Responsibility')
('Communication', 'Flexibility', 'Responsibility')
('Responsibility', 'Integrity', 'Flexibility', 'Courtesy', 'Communication')**
最小值、最大值和长度
我们可以使用min()、max()和len()内置函数来获得一个序列的最大值、最小值和长度。
输出:
**5
Communication
Responsibility
11
7
90**
索引和计数
我们已经为元组提供了索引和计数方法。我们可以使用第一个来标识给定值的索引,使用第二个来标识这个值在我们的元组中出现了多少次。
输出:
**1**--------------------------------------------------------------------**
**ValueError** Traceback (most recent call last)
**<ipython-input-21-f2447af847ce>** in <module>**()**
3
4 numbers **=** **(10,** **22,** **53,** **53,** **8,** **9,** **11,** **45,** **90,** **7,** **26)**
**----> 5** print**(**numbers**.**index**(100))**
**ValueError**: tuple.index(x): x not in tuple**
我们可以看到,如果我们传递一个不在元组中的值,我们将得到一个ValueError。
输出:
**1
2
0**
如果我们传递一个在我们的元组中不存在的值,计数方法将返回 0。这一次没有错误。
元组解包
我们还可以将元组中的值“解包”到变量中。此外,更通用的方法称为序列解包,因此这将适用于任何序列。我们已经看到了枚举内置函数的例子。
输出:
**10
20
30**
请注意,解包元组时,左侧变量的数量应该等于元组中值的数量。否则,我们会得到一个错误。
输出:
****--------------------------------------------------------------------**
**ValueError** Traceback (most recent call last)
**<ipython-input-45-282b018b9602>** in <module>**()**
**----> 1** a**,** b **=** **(10,** **20,** **30)**
**ValueError**: too many values to unpack (expected 2)**
输出:
****--------------------------------------------------------------------**
**ValueError** Traceback (most recent call last)
**<ipython-input-43-c91a3d9d3fc4>** in <module>**()**
**----> 1** a**,** b**,**c**,**d **=** **(10,** **20,** **30)**
**ValueError**: not enough values to unpack (expected 4, got 3)**
元组与列表
如前所述,列表和元组之间的主要区别在于元组是不可变的,而列表是可变的。
重要注意事项 :
如果可变对象被改变,包含对可变对象的引用的不可变容器的值可以被改变。当我们谈论容器的可变性时,只有被包含对象的身份是隐含的。然而,如果我们的不可变容器只包含不可变的数据类型,它的值就不能改变。参见这篇博文中的例子。****
与列表不同,元组没有append()、insert()、remove()、pop()、extend()等方法,因为它们具有不可变的性质。
输出:
**['Flexibility', 'Courtesy', 'Flexibility', 'Integrity', 'Responsibility']**--------------------------------------------------------------------**
**TypeError** Traceback (most recent call last)
**<ipython-input-47-5d2973b1f2b4>** in <module>**()**
4
5 tuple_skills **=** **("Communication",** **"Courtesy",** **"Flexibility",** **"Integrity",** **"Responsibility")**
**----> 6** tuple_skills**[0]** **=** **"Flexibility"**
**TypeError**: 'tuple' object does not support item assignment**
文档中的附加注释
尽管元组可能看起来类似于列表,但它们通常用于不同的情况和不同的目的。元组是不可变的,并且通常包含通过解包或索引(或者甚至在
[namedtuples](https://docs.python.org/3.7/library/collections.html#collections.namedtuple)的情况下通过属性)访问的异构元素序列。列表是可变的,它们的元素通常是同构的,通过遍历列表来访问。
来源:https://docs . python . org/3.7/tutorial/data structures . html # tuple-and-sequences
何时使用元组
- 元组比列表快。如果你需要一组常量值,而你所要做的就是遍历它,那么使用元组而不是列表。
输出:
**0.034131127635760095
0.11737610517116082**
timeit库允许我们以秒为单位测量经过的时间。我们可以清楚地看到元组初始化比列表初始化快。
- 仅包含不可变值的元组可以用作字典的键,而列表则不能。
输出:
**Introducton**--------------------------------------------------------------------**
**TypeError** Traceback (most recent call last)
**<ipython-input-5-c18ababebf4d>** in <module>**()**
2 print**(**book**[(1,** **10)])**
3
**----> 4** book **=** **{[1,** **10]:** **"Introducton",** **[11,** **15]:** **"Notation and Definitions",** **[16,** **30]:** **"Fundamental Algorithms"}**
**TypeError**: unhashable type: 'list'**
- 如果我们的元组只包含不可变的对象,我们可以使用它们来确保我们的数据不会被改变。
摘要
- 元组是不可变序列,通常用于存储异构数据的集合(例如由
[*enumerate()*](https://docs.python.org/3/library/functions.html#enumerate)内置产生的 2 元组)。 - 列表和元组的主要区别在于元组是不可变的,而列表是可变的。
- 我们可以使用一对括号
**()**和来初始化一个元组,括号内的值用逗号隔开。 - 我们可以使用tuple(iterable)内置的函数来初始化一个 tuple。****
- 我们可以使用一个简单的 for 循环对元组进行迭代。
- 我们可以对元组进行常见的序列操作,比如索引、切片、拼接、乘法,获取 min 、 max 值等等。****
- 我们可以从元组中解包值
- 当你需要一组常量值并且你所要做的就是遍历它时,使用元组而不是列表。****
时事通讯
如果你想在我发表新的博客文章时得到通知,你可以订阅我的时事通讯。
商务化人际关系网
这是我在 LinkedIn 上的简介,如果你想和我联系的话。我将很高兴与你联系在一起。
最后的话
谢谢你的阅读。我希望你喜欢这篇文章。如果你喜欢,请按住拍手键,分享给你的朋友。我很高兴听到你的反馈。如果你有什么问题,尽管问。😉
资源
- https://www . data camp . com/community/tutorials/python-tuples-tutorial
- https://www.tutorialspoint.com/python/python_tuples.htm
- https://stack overflow . com/questions/1708510/python-list-vs-tuple-when-to-use-each
- https://docs . python . org/3/tutorial/data structures . html #元组和序列
- https://docs.python.org/3/library/stdtypes.html#tuples
- https://docs . python . org/3.7/library/stdtypes . html # common-sequence-operations
Python 点模拟器
砸,还是不砸?
背景
计算机是检验统计和战略之间相互作用的理想工具。这是因为虽然战略问题往往是理论性的,但它们也意味着会对现实世界产生影响,而这些影响会通过统计结果反映出来。在许多情况下,统计结果太难手工研究,所以更容易做一个【蒙特卡洛模拟】,这是几个随机试验,试图梳理出模式。
扑克是一种极其复杂的游戏,两个人之间可能有超过十万亿手牌。这意味着目前的计算机远没有足够的能力来“解决”扑克,所以他们不得不经常使用巧妙的蒙特卡罗方法。我决定从简单一点开始,试着为 21 点做这件事。
特别感谢赔率向导,我用它的图表来设计我的策略函数。用于这个项目的代码在我的 Github 上,因为它太长了,不能粘贴在这里。
二十一点的规则
21 点是一种玩家与赌场对抗的游戏。玩家和赌场都试图得到不超过 21 的尽可能多的牌。过了 21 就意味着自动输了。如果玩家和赌场都过 21,赌场赢。然而,玩家可以在做出决定之前看到赌场的一张牌,而赌场的行为独立于玩家的牌,通常持续到其得分达到 17。
每张数字牌(2-10)都值它的数字,一张 ace 值 1 或 11,一张 face 牌值 10。请注意,这意味着实际上 10(10,J,Q,K)是其他牌的 4 倍。还有各种其他的警告,比如 21 点奖金、分成和双倍,你可以在这个[网站](http://Blackjack Rules - Learn How to Play 21 - [Tips & Best Practices] https://www.blackjack.org/blackjack-rules/)上读到。

Various blackjack outcomes and their associated results
项目概述
这个项目有三个阶段:
- 创建一个功能,为您提供战略性的 21 点建议,例如,是否击中,留等。
- 使用上述策略制作一个 21 点模拟器来模拟 21 点的一手牌
- 多次运行模拟以汇总结果并运行实验
第一阶段:战略
21 点是一个已经研究得相当多的游戏,互联网上有各种各样的地方有完美的策略,被称为'基本 21 点策略'。在你制定退休计划之前,请注意,即使你玩的是“完美的”21 点,与赌场相比,你的亏损额也很小(大约 0.5%),但只是比玩不完美 21 点的人输得慢。这些信息通常显示在网格状的图表中。下面是我在这个项目中使用的图的一个片段:

如果我的牌加起来是 12,而庄家有一张 7,则图表显示我应该“打”,即多拿一张牌。但是,如果庄家有 5,我应该“留下”,即放弃,并希望庄家“破产”(得分高于 21)。我把图表的每一个建议都编码进了一个函数,BlackJackStrategy。
阶段 2:模拟
尝试模拟 21 点的每一手牌是这个项目中最具挑战性的部分。我首先随机创建了一副牌,上面有所有的数字,以及四倍多的十。然后,使用随机数生成器,我给玩家发了两张牌,给庄家发了一张牌,并使用基本的 21 点策略来决定玩家将如何行动,并使用 21 点规则来决定结果。这些都在函数“21 点手牌结果”中。
注意:我使用的模拟假设有无限多副牌,因为赌场通常使用 6-8 副牌,这不会对结果产生太大影响。我将“分裂”的结果模拟成两只手,这允许我递归地处理分裂。这将减少方差一点点,但数量可以忽略不计。
阶段 3:重复模拟和实验
重复模拟是这个项目中最容易的部分,只要重复我的 21 点纸牌游戏就可以了。我的第一个实验是看看 21 点中的“赌场优势”是什么,这是你每赌 100 美元预计会输掉的钱数。我通过模拟一百万手牌尝试了几次,发现你会损失 10 到 40 美分。
实验一:翻倍 7 vs 庄家 6
在基本的 21 点策略图上,它说击中 7 对庄家的 6。如果我们加倍会怎么样?我在我的单手 21 点模拟器中添加了一个“硬编码”输入,它允许你对玩家的第一个动作进行硬编码,然后假设玩家在剩下的时间里遵循基本 21 点的建议。我首先运行了 100,000 次模拟,玩家下注 1 美元,拿着 3 和 4 与庄家的 6 进行比较,玩家赚了大约 2,000 美元。然而,当我运行 100,000 个 double 硬编码的模拟时,这变成了 15,000 美元的损失。这意味着,将 a 7 和 a 6 翻倍的决定花费了玩家 100,000 美元的赌注中的 17,000 美元,平均为他们赌注的 17%!
实验 2:拆分两个 10s vs 庄家 6
“千万不要拆 10”是 21 点的一条著名建议。让我们来评估一下,如果你拿着两张 10 和庄家拿着一张 6(这对庄家来说是最差的牌)分牌会发生什么。当我们在不拆分的情况下运行 100,000 次模拟时,玩家可以赚大约 70,000 美元。然而,当我们硬编码“分割”时,玩家只能获得 60,000 美元。虽然有两张 10 对一张 6 仍然非常有利可图,但玩家要花 10%的赌注来分割 10。
结论
可以通过改变硬编码来测试基本 21 点策略的每个方面,看看哪种方法在模拟中效果最好。编码和测试非常有趣,我期待在研究其他策略问题时应用类似的方法。
Python 代码:从假设检验到商业案例的在线实验

在线实验设计框架:
- 决定 KPI、护栏(主要指标、次要指标)
- 设计:第一类,第二类误差,样本量/持续时间,你的 H0 和 H1 是什么
- 设置实验(RTC,随机化算法)
- 衡量和比较(健全性检查、新颖性效果)
- 建议行动
这里我们关注第二步:实验设计和第四步:测量和比较。
理解 I 型、II 型误差、FPR、FNR、α、β的定义及其各自的数学公式非常关键。因为这些知识有助于建立理解和应用 p 值和功率的坚实基础。但是,文章不会深入表达那些术语和相关的统计知识(那不是这里的核心话题)。
对于非统计背景受众,p 值控制测试中检测到的提升/差异是否由随机机会引起,换句话说,两组之间没有提升/差异;功率控制看到测试中检测到的升力/差异为真(升力/差异)的概率,换句话说就是不要错过‘转出变化’的机会。
在步骤 2 和 3 中,我们需要选择:使用哪种假设检验?
首先让我们简单解释一下什么是 Z,T,F 测试以及什么时候使用它。在线实验 ab 检验中,最常见的情况是应用两样本假设检验
z,T 分布用来回答:两个样本来自同一个总体的概率有多大。回忆 t 分布有较厚的尾部。最终,越来越多的样本 t 分布将转化为 Z 分布
f 分布用于回答:两个样本来自具有相同方差的总体的概率是多少?通常,我们想知道三个或更多样本来自同一总体的概率是多少
卡方分布卡方拟合优度测试确定样本数据是否与总体匹配。独立性的卡方检验比较列联表中的两个变量,看它们是否相关。在更一般的意义上,它测试分类变量的分布是否彼此不同:
我们来编码吧!
通过 Z 检验的两个比例点估计
零假设(H0):两组均值是不同的——双边的
零假设(H0): u0 > u1 或 u0< u2 — one sided
Use case: Ran a fair AB test, control group got 486 clicks out of 5000 impression vs experiment group got 527 clicks out of 5000 impression. Could we say experiment group won the test? Given statistical significance as 0.95
In the result, p-value= 0.1742 >α= 0.05,我们未能拒绝 H0,这意味着从实验组观察到的高 CTR 是由于随机机会。对营销团队/产品团队的建议是不要推出这一新计划/变化。
通过点估计、带测试用例的 Z 检验确定样本大小
用例:根据转换率(CVR)计算样本量
假设:基线转换率=10%,CVR 提升 10%,显著水平=95%,功率=80%
我们需要每组至少 14744 次点击,这相当于 1.85 周,平均每周流量为 16k 次点击。在这个例子中,两周是一个很好的实验持续时间。
如果我的流量非常大(通常这对于许多公司来说并不常见,除了像脸书或谷歌这样的大型科技公司),并且能够达到 5 天的样本量,该怎么办?1.仍然运行至少 7 天,以捕捉每周趋势 2。给控制组分配更多的流量,不要浪费你的流量。
“艺术作品在此”:在 1 .足够的力量(不要错过机会/察觉不到改进)2 .中寻找平衡或取舍。所需的最小样本量(交通量)3。有效尺寸(如果度量增加/减少多少,值得进行测试)-最小可检测差异
假设 alpha =0.05,幂= 0.8,让我们检查当改变有效大小时,样本大小如何变化
输出:

独立双样本 T 检验
对于两面派;H0 认为;两个集团的手段是不同的
对于一边倒:H0: u0 > u1 或 u0 < u2
Given Alpha= 0.05
Output:

We fail to reject null hypothesis with alpha=0.05
Sample size via point estimate, t test
It’s very similar to Z test (which we expressed a lot above) when using t test. Then let’s just code.
Chi-Square Test
Null hypothesis: no difference between two groups
用例:AA 测试—健全性检查:这两个组的观众是否相同。不均匀随机化?
示例:营销团队希望提高 CVR(通常营销团队更关心 CAC、RPU 或其他货币指标。这里用 CVR 来展示快速计算)通过测试一个新的关键字竞价算法。在推出 A/B 测试之前,需要进行 AA 测试,以确保两组的观众在 CVR 方面是相同的。
以下是在 AA 测试中要测试的假设:
H₀:两组的转换率是一样的
H₁:两组间的转换率不同
方法一:使用比例测试
可以使用比例检验,因为卡方分布是正态分布的平方
。在这种情况下(具有双侧替代的双样本情况),下面的卡方检验产生与比例 Z 检验相同的值。
输出:

我们无法拒绝 p 值= 0.175 >α= 0.05 的零假设
方法二:使用列联表——传统方法
输出:

这里我们得到了与比例测试相同的结果(p 值=0.174)。
方差分析检验和 f 分布
ANOVA 检验可用于测量这些平均值之间的统计显著性差异,进而测量其总体平均值之间的差异。
单向方差分析
- 无效假设:组均值相等(组均值无变化)
- 替代假设:至少一组均值与其他组不同
例如,一家公司的营销团队可能想要回答,如果五个州的销售业绩相同。用统计学的语言来表述这个问题。我们希望衡量不同州的销售额之间是否有任何差异,将销售额作为 KPI 进行评估。
任何假设检验的第一步都是将问题转化为无效假设和替代假设:
- H 0 : 组间均值相同
- 至少,一组的平均值是不同的
要执行此测试,我们必须根据所选的显著性水平或 p 值(通常为 0.05)和自由度,计算 f 检验统计值,并将其与 f 分布表中的临界值进行比较。
我们有了所有五个州(加利福尼亚州、纽约州、德克萨斯州、弗吉尼亚州和伊利诺伊州)的销售数据,然后让我们编码。
我们不能拒绝 H0,因为 p 值= 0.82 >α= 0.05。下一步是进行成对 t 检验,以发现哪些州在销售业绩方面与其他州不同。
双向方差分析
双向 f 检验:双向 f 检验是单向 f 检验的扩展,当我们有两个自变量和两个以上的组时使用。双向 f 检验不能说明哪个变量是主导变量。如果我们需要检查个体的显著性,那么就需要进行事后检验。
小样本量(低流量)补救措施
- 非参数统计检验
- 重采样—引导
- 找到与 KPI 高度相关的早期指标
1.非参数统计显著性检验—无分布
H0:两组,同分布
曼-惠特尼 U /威尔科克森-曼-惠特尼检验
独立双样本 t 检验的替代方法,数据类型:二进制。CVR 中心
输出:

2。重采样—助推器
赞成:没有正态假设(当样本量很小时,大多数情况下无效)
缺点:计算成本高。数据质量也很重要。
我曾经使用“scikit-learn”中的“重采样”包。然后我找到了这个为在线实验设计开发的 Python API,它使用起来非常直观。
Example is from bootstrapped library. Copyright 2016-present, Facebook, Inc. All rights reserved.
输出:

Example is from bootstrapped library. Copyright 2016-present, Facebook, Inc. All rights reserved.
让我们检查一下手段。
输出:

如示例所示,这是一个非常有用的库,并且易于应用。有关更多应用和详情,请查看以下链接:
bootstrapped 是一个 Python 库,允许您根据数据构建置信区间。这在各种场合都很有用…
pypi.org](https://pypi.org/project/bootstrapped/)
即使 AB 测试没有告诉你“为什么会发生”——真正的因果关系,它仍然是一个非常强大的工具,应用统计技术来改善用户体验,让客户更开心等等。我相信,在应用统计数据时,总有一种“艺术作品”可以在技术和工程之间找到平衡,推动收入增长(长期或短期)以及客户/用户的满意度。
使用熊猫数据帧、Spark 数据帧和考拉数据帧的 Python 数据预处理
为 Python 中的机器学习准备数据

随着在数据预处理、数据分析和机器学习中的广泛使用,Pandas 与 Numpy、Scikit-Learn 和 Matplotlib 一起成为 Python 中事实上的数据科学栈。
然而,Pandas 的主要限制之一是 Pandas 是为可以在单台机器上处理的小数据集设计的,因此它不能很好地扩展到大数据。相反,Apache spark 是为大数据设计的,但它有一个非常不同的 API,也缺乏 Pandas 中许多易于使用的数据争论和可视化功能。最近宣布了一个新的开源考拉,它通过增强 PySpark 的 DataFrame API 使其与 pandas DataFrame API 兼容,弥合了 Pandas DataFrame 和 Spark DataFrame 之间的差距。
在这篇文章中,类似于熊猫数据帧和 Spark 数据帧之间的比较,我使用一个公共数据集sample _ stocks . CSV来评估和比较熊猫、Spark 和考拉数据帧在机器学习的典型数据预处理步骤中的基本功能,包括:
- 加载数据(例如,从互联网加载 csv 文件)
- 浏览数据(例如,汇总统计、数据可视化等。)
- 清理数据(例如,处理丢失的数据)
- 转换数据(例如,特征工程、缩放、重新格式化为 Numpy 数组或 Spark RDD(弹性分布式数据集))
为方便起见,假设以下 Python 库已安装在 Mac 等本地机器上:
- Anaconda (conda 4.7.10)带有 Numpy、Pandas、Matplotlib 和 Scikit-Learn
- 火花 2.4.4
- 考拉
1.加载数据
在开始任何数据预处理之前,需要将数据集(例如,公共的 sample_stocks.csv 文件)加载到内存中。为此,让我们导入相关的 Python 库:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inlinefrom pyspark import SparkContexttry:
sc = SparkContext('local', 'Pyspark demo')
except ValueError:
print('SparkContext already exists!')from pyspark.sql import SparkSession
try:
spark = SparkSession.builder.appName('Recommendation_system').getOrCreate()
except ValueError:
print('SparkSession already exists!')
1.1 在 Pandas 中加载 csv 文件
Pandas 提供了一个 read_csv ()函数,既可以读取本地 csv 文件,也可以从互联网上读取 csv 文件作为 Pandas 数据帧:
pd_df = pd.read_csv("[https://raw.githubusercontent.com/databricks/koalas/master/data/sample_stocks.csv](https://raw.githubusercontent.com/databricks/koalas/master/data/sample_stocks.csv)")
pd_df.head(1)

1.2 在 Spark 中加载 csv 文件
在 Spark 中, SparkSession 只提供了一种从本地 csv 文件或内存中的 RDD 读取 Spark 数据帧的方法。Spark 需要与其他 Python 库结合使用,才能从互联网上远程读取 csv 文件。
一种方法是首先使用 URL 请求和响应包从互联网读取 csv 文件的内容,然后将内容转换为 SparkSession 的 Spark RDD,以作为 Spark 数据帧加载。
import urllib.requesturl = "[https://raw.githubusercontent.com/databricks/koalas/master/data/sample_stocks.csv](https://raw.githubusercontent.com/databricks/koalas/master/data/sample_stocks.csv)"
response = urllib.request.urlopen(url)
data = response.read()
text = data.decode('utf-8')
spark_df1 = spark.read.csv(sc.parallelize(text.splitlines()), header=True)
print(spark_df1.show(1))

另一种方法是首先使用 Pandas 读取 csv 文件作为 Pandas 数据帧,然后使用 SparkSession 从 Pandas 数据帧创建 Spark 数据帧。
spark_df2 = spark.createDataFrame(pd.read_csv(url))
1.3 在考拉中加载 csv 文件
像 Spark 一样,考拉只提供了从本地 csv 文件读取的方法。它需要与其他 Python 库结合才能从互联网上读取 csv 文件。一个简单的方法是首先使用 Pandas 读取 csv 文件作为 Pandas 数据帧,然后将其转换为 Koalas 数据帧。
import databricks.koalas as ks
ks_df = ks.from_pandas(pd.read_csv(url))
ks_df.head(1)

2.探索数据
一旦数据集作为数据帧加载到内存中,我们就可以使用数据帧的各种函数从不同的方面研究它。
2.1 了解数据框架模式
通常,探索数据帧的第一步是理解它的模式:列名和相应的数据类型。
对于 Pandas、Spark 和 Koalas 数据帧,获取数据帧列名和数据类型的方法是相似的。所有这些数据帧都为列名提供了属性 columns ,为列数据类型提供了属性 dtypes 。
需要注意的一点是,Spark DataFrame 的数据类型取决于示例公共 csv 文件的加载方式。如果通过 Spark 使用 URL 请求和响应包加载 csv 文件,则列数据类型默认为 string 类型,而如果通过 Pandas with Spark 加载 csv 文件,则列数据类型为 double 类型。
# DataFrame column names
pandas_column_names = pd_df.columns
spark_column_names = spark_df1.columns
ks_column_names = ks_df.columns# DataFrame column data type
pandas_column_data_types = pd_df.dtypes
spark_column_data_types = spark_df1.dtypes
ks_column_data_types = ks_df.dtypes
2.2 获取汇总统计数据
一旦我们理解了数据帧的模式,探索数据的下一步是查看汇总统计,例如五个数字汇总。所有的熊猫、Spark 和考拉数据帧都提供了相同的函数 describe ()来获得这些基本的汇总统计数据,包括数据帧中每一列的总行数、最小值、平均值、最大值和百分位数。
pd_df.describe()

spark_df1.describe()
ks_df.describe()
除了基本的汇总统计信息之外,汇总统计信息的另一个要素是数据帧中不同列之间的相关性。所有的熊猫、星火、考拉数据框架都提供了一个函数 corr ()来计算相关系数。熊猫和考拉 DataFrame 的 corr ()函数可以处理任意数量的列,但是 Spark DataFrame 的 corr ()函数只允许两列。
pd_df[['Open','Close', 'Volume']].corr()
ks_df[['Open','Close', 'Volume']].corr()

from pyspark.sql.functions import corr
spark_df2.select(corr("Open","Close")).show()

2.3 分组依据和聚合
分组依据和聚合(例如,最小值、最大值、平均值等。)是汇总统计的另一个要素。熊猫和考拉数据框架为分组依据和聚合提供了相同的功能:
pd_df.groupby('Symbol').max()['Open']
ks_df.groupby('Symbol').max()['Open']
但是,用于 group by 和 aggregation 的 Spark DataFrame 函数具有不同的格式:
from pyspark.sql.functions import max
spark_df2.groupBy("Symbol").agg(max("Open")).show()

2.4 可视化数据
数据可视化是理解数据的一种重要而有效的方法。熊猫和考拉数据框为数据可视化提供了类似的绘图功能,但绘图质量可能会有很大差异。例如,与熊猫数据框架散点图相比,下面的考拉数据框架散点图遗漏了许多数据点。
但是,Spark DataFrame 不直接提供任何数据可视化功能。一种简单的解决方法是将 Spark 数据帧转换为熊猫或考拉数据帧,以实现数据可视化。
pd_df_sub = pd_df[['Open', 'Close']]
pd_df_sub.plot.scatter(x='Open', y='Close')

ks_df_sub = ks_df[['Open', 'Close']]
ks_df_sub.plot.scatter(x='Open', y='Close')

熊猫和考拉 DataFrame 中的散点图函数只能处理两列。Pandas 绘图模块提供了一个 scatter_matrix ()函数,该函数可以为任意数量的列绘制散点图。
from pandas.plotting import scatter_matrix
pd_df_sub = pd_df[['Open', 'Close', 'Volume']]
scatter_matrix(pd_df_sub, alpha=0.2)

3.清理数据
数据清理的两个主要目标是处理丢失的数据和过滤掉异常值。
3.1 处理缺失数据
为了演示如何处理丢失的数据,首先让我们将一个丢失的数据项(例如,np.nan)分配到 Pandas 数据帧中:
pd_df_missing = pd_df.copy()
pd_df_missing.loc[0, 'Open'] = np.nan
pd_df_missing[pd_df_missing['Open'].isnull()]

Pandas DataFrame 提供了一个 fillna ()函数,可以用任意字符串或数字填充缺失的数据项。Spark 和 Koalas 数据帧提供了类似的功能,但是它们只允许与相应列的数据类型相匹配的值。
pd_df_missing.fillna('N/A', inplace = True)
pd_df_missing.fillna(0, inplace = True)# Spark and Koalas allow only a number
ks_df_missing.fillna(0, inplace = True)
spark_df_missing = spark_df_missing.na.fill(0)
3.2 过滤数据
数据过滤可用于去除异常值和许多其他目的。
如下面的示例代码所示,熊猫和考拉数据帧具有相同的 API,用于有条件地选择数据行和列。然而,Spark 数据帧有一个不同的 API。
pd_df.loc[pd_df['Open'] >= 168, ['Open','Close']].head()
ks_df.loc[ks_df['Open'] >= 168, ['Open','Close']].head()
spark_df.filter("Open > 168").select("Open","Close").show(5)
4.为特征工程转换数据
特征工程可以是机器学习应用的基础,并且可以通过各种类型的数据转换来实现。特征是数据帧中的数据列。功能工程的范围各不相同,但通常包括以下内容:
- 选择与机器学习中的预测目标相关的现有数据列的子集(即,受监督的机器学习中的标签)
- 用更有意义的名称重命名现有列
- 基于现有列创建新列(即创建衍生特征)
- 将列值缩放到特定范围内(即,在深度学习中将列值缩放到[0,1]或[-1,1]的范围内)
4.1 选择列
如前所述,Pandas 和 Koalas DataFrames 提供了相同的选择列的方法,但是 Spark DataFrame 提供了不同的 API。
pd_df[['Open', 'Close']]
ks_df[['Open', 'Close']]
spark_df.select('Open', 'Close')
4.2 重命名列
Pandas 和 Spark DataFrames 使用不同的函数名,但重命名列的功能相似。
pd_df.rename(columns = {"Symbol": "SYMBOL"}, inplace = True)
spark_df = spark_df.withColumnRenamed("Symbol", "SYMBOL")
但是,目前的考拉数据框架不支持重命名列的功能。
4.3 创建新列
机器学习通常需要从现有特征中派生新特征,即从现有列创建新数据列:
from pyspark.sql.functions import colpd_df['Sum'] = pd_df['Open'] + pd_df['Close']
ks_df['Sum'] = ks_df['Open'] + ks_df['Close']
spark_df1 = spark_df1.withColumn("Sum", col("Open") + col("Close"))
4.4 删除列
不必要的列(如 AdjOpen 和 AdjHigh )可以删除如下:
pd_df = pd_df.drop(['AdjOpen', 'AdjHigh'], axis=1)
ks_df = ks_df.drop(['AdjOpen', 'AdjHigh'], axis=1)
spark_df = spark_df.drop('AdjOpen', 'AdjHigh')
4.5 缩放列
如前所述,如果不同列的值在非常不同的范围内,则在机器学习中有必要将列的值缩放到某个范围内(例如,[0,1 或[-1,1])。对于 Pandas DataFrame,scikit-learn 库为此提供了两个常用函数 MinMaxScaler ()和 StandardScaler ()。
但是,这些函数不能直接应用于考拉数据帧。树袋熊数据帧需要转换成熊猫数据帧才能利用这些功能。
可以为 Spark 数据帧缩放列,但是与为 Pandas 数据帧使用 scikit-learn 函数相比,实现可能要复杂得多。例如,类似于 Spark 数据缩放示例,以下代码使用 Spark MinMaxScaler 、 VectorAssembler 和 Pipeline 对象来缩放 Spark 数据帧列:
from pyspark.ml.feature import MinMaxScaler
from pyspark.ml import Pipeline
from pyspark.ml.feature import VectorAssembler# VectorAssembler Transformation - Converting column to vector type
assembler = VectorAssembler(inputCols=['Open'], outputCol="Open_Vect")
scaler = MinMaxScaler(inputCol="Open_Vect", outputCol="Open_Scaled")# Pipeline of VectorAssembler and MinMaxScaler
pipeline = Pipeline(stages=[assembler, scaler])# Fitting pipeline on dataframe
spark_df3 = pipeline.fit(spark_df2).transform(spark_df2).drop("Open_Vect")
spark_df3.show(1)
4.6 一键编码
需要使用一键编码将分类数据类型列的值转换为数字数据类型的新列:
pd_df_dummies = pd.get_dummies(pd_df)
ks_df_dummies = ks.get_dummies(ks_df)
但是,Spark DataFrame 不提供这样的功能。一种解决方法是将数据帧转换为熊猫或考拉数据帧。
4.7 为机器学习重新格式化数据帧
数据预处理的最后一步是根据使用的机器学习库,将数据帧转换为机器学习建模使用的适当格式。
如果基于 Numpy 的机器学习或深度学习库(即 scikit-learn、Keras 等。)时,需要将 DataFrame 转换为 Numpy 数组进行建模。Pandas DataFrame 提供了一个 values 属性来从 Pandas DataFrame 中获取一个 NumPy 数组。但是现在的考拉数据框架不支持这样的方法。如果数据集可以在单台机器上处理,Spark 或 Koalas 数据帧可以转换为 Pandas 数据帧,如下所示,以便轻松获得相应的 Numpy 数组。
pd_df_from_koalas = ks_df.to_pandas()
pd_df_from_spark = spark_df.toPandas()
如果使用像 MLlib 这样的基于 Spark 的机器学习库,那么需要将数据帧转换成 RDD 或 Spark 数据帧。一个 Spark 数据帧可以直接馈入一个适当设计的 MLlib 流水线(例如参见 MLlib )或者转换成 RDD,如果基于 RDD 的 MLlib API 用于建模的话。Spark 数据帧提供了一个 rdd 属性来返回一个 rdd。在这种情况下,熊猫或考拉数据帧需要首先转换为 Spark 数据帧,以便进行建模。这可以通过以下方式实现:
spark_df_from_pandas = spark.createDataFrame(pd_df)
spark_df_from_koalas = ks_df.to_spark()
摘要
正如在考拉公告中所描述的,数据科学家倾向于使用熊猫数据框架来探索数据。由于陡峭的学习曲线,他们不愿意使用 Spark DataFrame。考拉似乎通过提供一个易于使用的 API 来填补它们之间的空白,该 API 类似于可以在 Spark 上运行的熊猫 DataFrame。
在这篇文章中,正如下面的汇总表所示,我使用了一个公共数据集sample _ stocks . CSV来评估和比较机器学习的典型数据预处理任务中熊猫、Spark 和考拉数据帧的基本功能。

考拉还处于发育初期。如上表所示,它不支持数据预处理的一些基本功能。某些支持的功能尚未成熟。随着开发的推进,考拉在 Spark 上易于使用的数据转换和可视化 API 的许多潜在优势将在大规模数据集(例如,数亿条数据记录)的情况下开始闪耀。
披露声明:2019 首创一。观点是作者个人的观点。除非本帖中另有说明,否则 Capital One 不隶属于所提及的任何公司,也不被这些公司认可。使用或展示的所有商标和其他知识产权是其各自所有者的财产。
Python 数据科学和分析/咨询项目概述
从科技公司的角度比较国家候选资格/市场性;分析时间状态、物流、本地资源等..
所以为了支持我进入咨询行业的目标,我决定提高我的分析组合的有形性至关重要。这是通过考虑我对大数据/分析的真正兴趣和其中的决策权,以及对大多数咨询公司普遍存在的入门级“分析师”角色的检查而决定的。
你有大量的数据分析专业知识,但是你如何展示这些知识呢?
因此,扩大我的投资组合将在多个方面让我受益。这将进一步培养我真正着迷的技能,一种我真正投入成长的技能。这将有利于以后的职业发展,并加快可扩展的决策。最后,通过使我的技能组合更加有形,这将增加我的咨询职位的候选人资格。
对于后一点,让我的项目和投资组合面向咨询是最有意义的。
选择特定的行业/客户仅仅是基于我自己的兴趣,以及一个非常标准的行业分析。我一直对技术感兴趣。如今,几乎所有发展最快的行业都在“科技”的保护伞下。技术是我们过去几十年的决定性特征,并且毫无疑问将在未来继续如此。因此,技术行业将是我项目的背景焦点——但我会努力同时保持它足够广泛,以展示其他行业的应用。
因此,我的项目基于以下关键问题:
- 当今最强大、发展最快、用途最广泛的数据科学语言是什么?
- 顾问满足的最常见的客户需求是什么?/客户最常要求咨询公司提供什么服务?
- 今天大多数企业在哪里面临困难?
这些问题的答案是我这个项目的根源:
- 计算机编程语言
- 市场分析/市场进入和扩张
- 有效利用技术和数据;全局可扩展性

ython 是当今数据科学领域最具活力的语言。从后端开发,到深入的 ML 学习和统计分析。它直观、灵活,也许最重要的是,从开源的角度来看,它得到了广泛的支持。它相当容易学习,而且非常强大。在分析和统计方面,可能只有 R 能与之匹敌。
值得庆幸的是,我在学术界的分析经验的核心,主要是建立在 R 之上的。因此,我已经对 R 有了足够的认识和理解。通过展示我能够迅速适应和运用一种完全不同的语言,我将进一步提高我的候选资格。此外,Python 的后台逻辑/框架对于那些对数据科学了解不多的人来说更优雅,更容易理解。
Python 将是我在这个项目中使用的工具。
市场分析在咨询界非常流行。以至于大多数咨询面试几乎肯定会在某种程度上包含这一点。这是有道理的,因为公司在考虑候选人时考察的一般“适合度”是以思维为中心的。候选人能使用一个可解释的框架来组织他们的思想吗?他们看问题的逻辑直观务实吗?
这些特质通常会在回答有点模糊的问题时表现出来。从商业的角度来看,市场分析也足够广泛,它显示了一个人看到全局的能力,从而允许他们更有效地创新他们的问题解决方法。
当把商业环境作为一个整体来考察时,市场分析作为我项目的中心在逻辑上得到了支持。随着云、数字营销和一般物联网的发展,所有公司现在都希望拥有全球影响力,而这在过去只是行业领导者的专利。但是假设你有能力跨越世界;你把注意力放在哪里?你怎么知道的?数据科学支持的市场分析回答了这两个问题。
假设公司能够有效地使用数据并从中获得洞察力。从战略角度来看,他们具体想要实现什么?成长。很像普通的金鱼,在这种生长中最有影响力的成分是什么?环境。

金鱼会根据它的碗的大小而生长
企业想要也需要一个更大的“碗”
有了云,他们现在有了海洋。
G 全球可扩展性不仅仅是触及过去无法触及的客户。虽然市场分析可以告诉客户将他们的资源集中在哪里,并以经验数据支持这一决定,但另一个更深层次的问题是更加务实的“如何做?”
使我对这种全球扩展的理解更加具体,是我获得 AWS 云认证的原因之一。
顾问需要检查客户的能力。也许全球都有市场机会,可以通过数字方式联系到客户。但是体能和限制仍然存在。在这个过程中,需要考虑供应链、采购和物流。因此,分析合规性、关税、边界、材料/资源可用性等是这个项目的主要组成部分之一。
将这三个主要因素结合起来,就产生了最佳的项目。
使用 Python 的强大功能和多功能性,在全球范围内,针对通用技术行业内不同容量的假设客户,对非结构化大数据进行广泛的市场分析。
用于 ETL 的 Python 数据转换工具
免责声明:我不是 ETL 专家,我欢迎在这个领域更有经验的人的任何评论、建议或批评。
前几天,我在 Reddit 上询问我是否应该使用 Python 进行 ETL 相关的转换,压倒性的回应是是的。

source: Pinclipart
然而,虽然我的 Redditors 同事热情地支持使用 Python,但他们建议看看 Pandas 以外的库——引用了对 Pandas 处理大型数据集的性能的担忧。
在做了一些研究之后,我发现了大量为数据转换而构建的 Python 库:一些提高了 Pandas 的性能,而另一些提供了自己的解决方案。
我找不到这些工具的完整列表,所以我想我应该利用我所做的研究来编译一个——如果我遗漏了什么或弄错了什么,请让我知道!
熊猫
概述
熊猫当然不需要介绍,但我还是要给它介绍一下。
Pandas 将数据帧的概念添加到 Python 中,并在数据科学社区中广泛用于分析和清理数据集。作为 ETL 转换工具,它非常有用,因为它使得操作数据变得非常容易和直观。
优点
- 广泛用于数据操作
- 简单、直观的语法
- 与包括可视化库在内的其他 Python 工具集成良好
- 支持通用数据格式(从 SQL 数据库、CSV 文件等读取。)
弊端
- 因为它将所有数据加载到内存中,所以它是不可伸缩的,并且对于非常大(大于内存)的数据集来说可能是一个糟糕的选择
延伸阅读
达斯克

概述
根据他们的网站,“Dask 是一个灵活的 Python 并行计算库。”
本质上,Dask 扩展了公共接口,如 panda,用于分布式环境中——例如,Dask DataFrame 模仿 panda。
优点
- 可扩展性— Dask 可以在您的本地机器上运行和扩展到一个集群
- 能够处理不适合内存的数据集
- 即使在相同的硬件上,也能以相同的功能提高性能(得益于并行计算)
- 从熊猫切换的最小代码更改
- 旨在与其他 Python 库集成
弊端
- 除了并行性,还有其他方法可以提高 Pandas 的性能(通常更显著)
- 如果你正在做的计算量很小,那就没什么好处
- 有些功能没有在 Dask 数据帧中实现
延伸阅读
摩丁

网址:https://github.com/modin-project/modin
概述
Modin 与 Dask 相似,它试图通过使用并行性和启用分布式数据帧来提高 Pandas 的效率。与 Dask 不同,Modin 基于任务并行执行框架 Ray 。
与 Dask 相比,Modin 的主要优势在于,Modin 自动处理跨机器内核的数据分发(无需配置)。
优点
- 可伸缩性 Ray 比 Modin 提供的更多
- 即使在相同的硬件上,也能以完全相同的功能提高性能
- 从 Pandas 切换的最小代码更改(更改导入语句)
- 提供 Pandas 的所有功能——比 Dask 更像是一个“嵌入式”解决方案
弊端
- 除了并行性,还有其他方法可以提高 Pandas 的性能(通常更显著)
- 如果你正在做的计算量很小,那就没什么好处
延伸阅读
petl
网址:https://petl.readthedocs.io/en/stable/
概述
petl 包含了 pandas 拥有的许多特性,但是它是专门为 etl 设计的,因此缺少额外的特性,比如那些用于分析的特性。petl 拥有 etl 所有三个部分的工具,但是这篇文章只关注数据转换。
虽然 petl 提供了转换表的能力,但是其他工具(比如 pandas)似乎更广泛地用于转换,并且有很好的文档记录,这使得 petl 在这方面不太受欢迎。
优点
- 最大限度地减少系统内存的使用,使其能够扩展到数百万行
- 对于 SQL 数据库之间的迁移非常有用
- 轻巧高效
弊端
- 通过最大限度地减少系统内存的使用,petl 的执行速度较慢——对于性能非常重要的应用程序,不推荐使用它
- 与列表中的其他数据操作解决方案相比,较少使用
延伸阅读
PySpark

概述
Spark 旨在处理和分析大数据,并提供多种语言的 API。使用 Spark 的主要优势是 Spark 数据帧使用分布式内存并利用延迟执行,因此它们可以使用集群处理更大的数据集——这是 Pandas 等工具所无法实现的。
如果您正在处理的数据非常大,并且数据操作的速度和大小非常大,那么 Spark 是 ETL 的一个好选择。
优点
- 更大数据集的可扩展性和支持
- Spark 数据帧在语法方面与 Pandas 非常相似
- 通过 Spark SQL 使用 SQL 语法进行查询
- 与其他流行的 ETL 工具兼容,包括 Pandas(您实际上可以将 Spark 数据帧转换成 Pandas 数据帧,使您能够使用所有其他类型的库)
- 兼容 Jupyter 笔记本电脑
- 对 SQL、流和图形处理的内置支持
弊端
- 需要分布式文件系统,如 S3
- 使用像 CSV 这样的数据格式限制了延迟执行,需要将数据转换成其他格式,比如 T21 的拼花地板
- 缺乏对 Matplotlib 和 Seaborn 等数据可视化工具的直接支持,这两个工具都得到 Pandas 的良好支持
延伸阅读
值得注意的提及
虽然我想这是一个全面的列表,但我不想这篇文章变得太长!
确实有很多很多用于数据转换的 Python 工具,所以我在这一部分至少提到了我错过的其他项目(我可能会在本文的第二部分进一步探讨这些项目)。
- 倭 https://www.bonobo-project.org/
- 气泡 http://bubbles.databrewery.org/
- pygrametl http://chrthomsen.github.io/pygrametl/
- 阿帕奇光束
https://beam.apache.org/
结论
我希望这个列表至少能帮助您了解 Python 为数据转换提供了哪些工具。在做了这项研究之后,我确信 Python 是 ETL 的一个很好的选择——这些工具和它们的开发者已经使它成为一个令人惊奇的使用平台。
正如我在这篇文章的开头所说,我不是这方面的专家——如果你有什么要补充的,请随时评论!
感谢阅读!
Python 字典从零开始!!!
让我们了解一下 python 中字典的基础知识。

Credits: Learntek
python 中的 Dictionary 包含一个以 key: value 格式存储的无序数据集合。字典也被称为“联想记忆”或“联想数组”。在字典中,键必须总是唯一的。而这些值可以重复,并且可以是任何数据类型。键值对应该表示为“key: value”(:冒号是必须的)。下面是一些最常用的字典方法,也是最常见的面试问题之一。此外,代码可以在我的 GitHub 页面上找到。
比如用 python 声明一个字典:
**# Declaring a dictionary**dictionary = {}dictionary = {'Name': 'Tanu', 'Sex': 'Male', 'Age': 23}
print(dictionary)**{'Name': 'Tanu', 'Sex': 'Male', 'Age': 23}**print(type(dictionary))**dict**
1)访问字典中的元素
字典可以通过它们的索引来获取值。要做到这一点,您所要做的就是将数字或键的名称与字典的名称一起放在一对方括号内。
**# Accessing the elements in a dictionary**dictionary = {‘Name’: ‘Tanu’, ‘Sex’: ‘Male’, ‘Age’: 23}
print(dictionary)**{'Name': 'Tanu', 'Sex': 'Male', 'Age': 23}**print(dictionary['Name'])**'Tanu'**print(dictionary['Sex'])**'Male'**
如果您尝试访问字典中未声明的键,则会引发异常,指示未找到任何结果:
print(dictionary[‘Height’])**--------------------------------------------------------------------****KeyError Traceback (most recent call last)**[**<ipython-input-9-038ad6c9b2d4>**](/<ipython-input-9-038ad6c9b2d4>) **in <module>()
----> 1 dictionary['Height']****KeyError: 'Height'**
同样,我们可以使用字典的 get()来获取或访问字典的值,如下所示:
print(dictionary.get(‘Age’))**23**
2)从字典中删除元素。
在 python 中,有四种不同的方式或方法可用于从字典中移除元素。它们是 pop、del、popitem 和 clear。del 用于从字典中删除一个键,pop 用于从字典中删除特定的键,popitem 用于从字典中任意删除该键,clear 用于清除或删除字典中的所有元素(顾名思义)。
**# Removing the elements from a dictionary**dictionary = {'Name': 'Tanu', 'Sex': 'Male', 'Age': 23, 'Height': 5.8, 'Occupation': 'Student'}
print(dictionary)**{'Name': 'Tanu', 'Sex': 'Male', 'Age': 23, 'Height': 5.8, 'Occupation': 'Student'}****--------------------------------------------------------------------****# Deleting an element**del dictionary['Name']
print(dictionary)**{'Sex': 'Male', 'Age': 23, 'Height': 5.8, 'Occupation': 'Student'}****--------------------------------------------------------------------****# Popping an item**print(dictionary.popitem())**('Occupation', 'Student')****--------------------------------------------------------------------****# Popping the value**dictionary.pop('Sex')**'Male'****--------------------------------------------------------------------**print(dictionary)**{'Age': 23, 'Height': 5.8}****--------------------------------------------------------------------****# Clearing the entire dictionary**dictionary.clear()
print(dictionary)**{}**
3)遍历字典
使用 for 循环,我们可以遍历字典中的每个键,如下所示:
dictionary = {‘Name’: ‘Tanu’, ‘Sex’: ‘Male’, ‘Age’: 23, ‘Height’: 5.8, ‘Occupation’: ‘Student’}
print(dictionary)**{'Name': 'Tanu', 'Sex': 'Male', 'Age': 23, 'Height': 5.8, 'Occupation': 'Student'}****# Iterating through the values**for i in dictionary:
print(dictionary[i])**Tanu
Male
23
5.8
Student****--------------------------------------------------------------------****# Iterating through the keys**for i in dictionary:
print(i)**Name
Sex
Age
Height
Occupation**
所有
如果字典的所有键都为真(或者如果字典为空),则字典中的 all()返回“真”。如果字典中的键为 true,或者如果 dictionary all 方法返回 true,否则将返回 false。
dictionary = {0: “Tanu”, 1: “Prabhu”}
print(dictionary)**{0: 'Tanu', 1: 'Prabhu'}**print(all(dictionary))**False**dictionary = {1: "Tanu", 1: "Prabhu"}
print(all(dictionary))**True**dictionary = {}
print(all(dictionary))**True**
5)任何
如果字典中的任意键为真,字典中的 any()返回“真”。如果字典为空,则返回“False”。
dictionary = {0: “Tanu”, 1: “Prabhu”}
print(dictionary)**{0: 'Tanu', 1: 'Prabhu'}**print(any(dictionary))**True**dictionary = {0: "Tanu", 0: "Prabhu"}
print(any(dictionary))**False****# 0 is False**dictionary = {0: "Tanu"}
print(any(dictionary))**False**
6)透镜
字典中的 length()方法返回字典的长度(很明显)。它返回字典的条目数。
dictionary = {‘Name’: ‘Tanu’, ‘Sex’: ‘Male’, ‘Age’: 23, ‘Height’: 5.8, ‘Occupation’: ‘Student’}
print(dictionary)**{'Name': 'Tanu', 'Sex': 'Male', 'Age': 23, 'Height': 5.8, 'Occupation': 'Student'}**print(len(dictionary))**5**
7)排序
dictionary 中的 Sorted()方法返回字典中新排序的键列表。
dictionary = {‘Name’: ‘Tanu’, ‘Sex’: ‘Male’, ‘Age’: 23, ‘Height’: 5.8, ‘Occupation’: ‘Student’}
print(dictionary)**{'Name': 'Tanu', 'Sex': 'Male', 'Age': 23, 'Height': 5.8, 'Occupation': 'Student'}****# Sorting in a ascending order**print(sorted(dictionary))**['Age', 'Height', 'Name', 'Occupation', 'Sex']****# Sorting in a descending order**print(sorted(dictionary, reverse = True))**['Sex', 'Occupation', 'Name', 'Height', 'Age']**
sorted()方法接受一个反向参数作为可选参数。
8)复制
顾名思义,dictionary 中的 copy 方法返回字典的副本。
dictionary = {‘Name’: ‘Tanu’, ‘Sex’: ‘Male’, ‘Age’: 23, ‘Height’: 5.8, ‘Occupation’: ‘Student’}
print(dictionary)**{'Name': 'Tanu', 'Sex': 'Male', 'Age': 23, 'Height': 5.8, 'Occupation': 'Student'}**dictionary1 = {}
print(dictionary1)**{}**dictionary1 = dictionary.copy()
dictionary1**{'Name': 'Tanu', 'Sex': 'Male', 'Age': 23, 'Height': 5.8, 'Occupation': 'Student'}**
9)钥匙
Python 字典方法 key()返回字典中所有可用键的列表。
dictionary = {‘Name’: ‘Tanu’, ‘Sex’: ‘Male’, ‘Age’: 23, ‘Height’: 5.8, ‘Occupation’: ‘Student’}
print(dictionary)**{'Name': 'Tanu', 'Sex': 'Male', 'Age': 23, 'Height': 5.8, 'Occupation': 'Student'}**print(dictionary.keys())**dict_keys(['Name', 'Sex', 'Age', 'Height', 'Occupation'])**
十)价值观
Python 字典方法 values()返回给定字典中所有可用值的列表。
dictionary = {‘Name’: ‘Tanu’, ‘Sex’: ‘Male’, ‘Age’: 23, ‘Height’: 5.8, ‘Occupation’: ‘Student’}
print(dictionary)**{'Name': 'Tanu', 'Sex': 'Male', 'Age': 23, 'Height': 5.8, 'Occupation': 'Student'}**print(dictionary.values())**dict_values(['Tanu', 'Male', 23, 5.8, 'Student'])**
因此,以上是 Python 中非常重要的字典技术或函数。一些例子是从 Python 字典中引用的。我用一种简单的方式写了这篇教程,这样每个人都可以理解和掌握 Python 中字典的概念,而不需要以前的编程知识或经验。如果你们对代码有什么疑问,评论区就是你们的了。谢谢,祝你愉快。
Python 效率技巧:有抱负的 python 爱好者的新旧技巧

这是给予的季节。本着节日的精神,我在 Googleverse 上搜索了一些有趣的、或许不为人知的(直到现在)提高完美主义者 Pythonista 效率的技巧。
- tqdm()
tqdm()是一个包装器,用于即时显示“for”循环的智能进度表。它需要一个安装和一个导入,但是如果您曾经有过一个循环,看起来花费的时间比它应该花费的时间长,这可以提供一些需要的洞察力。

tqdm() and trange() in action — trange is just a bit faster than using range() within tqdm()
2。Shift 和 Tab 键(Windows 操作系统)
这个技巧实际上为我节省了很多去 Stackoverflow 的时间,但我有时还是会忘记使用它。这里有几个用途:
- 结束你的思考。在使用了一段时间后,记住正确的库或变量有时会很困难。只需在一个字母后使用选项卡按钮,就会弹出一个以该字母开头的术语或实例化变量的下拉列表。

Just type in a letter and use Tab to see a list of options
- 包/特性/方法文档。通过组合 Shift + Tab,将为该项弹出一个 docstring 和说明。在 Jupyter 笔记本中有三种查看方式:

Standard window with argument(s) and brief docstring

Using the + button will reveal full documentation, including parameters, examples and website

Using the ^ button will embed a bottom pane with the full documentation.
- f 弦
对于那些仍然使用 str.format()或%d(替换为数字),%s(替换为字符串)的人来说,f 字符串更有效。f 字符串可以在代码中的任何地方被调用,并且可以引用任何以前实例化的变量。下面是一些如何使用它们的例子:

Using ‘ ‘ , “ “ and ‘’’ ‘’’ are all valid methods, and most datatypes can be utilized

Math operations, string concatenation and methods can be performed within f-strings

Dictionary values can also be called out in f-strings

However, some symbols cannot be used within an f-string. Most operator keys like @,&,!,= can.
- IPython Magic
IPython 有一个名为 magic 的内置库,允许用户只需以%符号开头就可以运行特定的命令。魔法有两种形式:
- 行魔法:它们类似于命令行调用。它们以%字符开始。该行的其余部分是传递的参数,没有括号或引号。线条魔术可以用作表达式,它们的返回值可以赋给变量。
- 细胞魔术:他们有%%字符前缀。与线路魔术功能不同,它们可以在呼叫下方的多条线路上操作。事实上,他们可以对接收到的输入进行任意修改,甚至根本不需要是有效的 Python 代码。它们将整个块作为单个字符串接收。
有许多神奇的命令,最著名的是%timeit(乘以你的代码)和%matplotlib inline。要获得可以调用的命令的完整列表,只需运行以下单元格:

Just type %magic into your notebook for a complete doc on available commands
- Walrus 操作符(python 3.8 的新功能)
对于那些运行 3.8 的人来说(注意:在这篇文章发表时,并不是所有的包都准备好了 3.8),这里有一个新的操作符,它使得执行操作更有效率。=运算符、赋值运算符或 walrus 运算符(因为它看起来像长着獠牙的眼睛)允许用户在指定的函数或方法中为变量赋值。虽然在文档中可以举例说明几种用法,但这里只举例说明最简单的使用情况:
#old way
answer = False
print(answer)#new way
print(answer := False)
这里的希望是,也许您发现了一些有助于提高 Python 效率的东西,或者激发您的兴趣去探索一些更高效的方法。请随意回复任何其他效率提示。节日快乐!
Python enumerate()内置函数
在本教程中,让我们了解 python 中的枚举内置函数。

定义
枚举器内置函数 将可迭代数 的计数器添加到所提供的整数、字符或字符串等数据结构中。现在,数据结构可能是任何列表、元组、字典或集合。如果计数器不是由用户提供的,那么默认情况下它从 0 开始。基于所提供的数字,枚举器函数进行迭代。
句法
枚举器内置函数的语法如下:
**enumerate(iterable, start)**
因素
枚举器函数的参数是 iterable 和 start。
- iterable: 这是希望迭代的数据结构的名称。
- start: 这是您想要开始迭代计数器的数字。
注意:iterable 必须是支持迭代的对象。
如果您想知道枚举函数的实际语法,只需在您的 IDE 上键入 help(enumerate ),然后您将获得以下结果。因为你需要了解这些东西。
**Help on class enumerate in module builtins:** **class enumerate(object)
| enumerate(iterable[, start]) -> iterator for index, value of | iterable
|
| Return an enumerate object. iterable must be another object that | supports iteration. The enumerate object yields pairs containing | a count (from start, which defaults to zero) and a value yielded | by the iterable argument. enumerate is useful for obtaining an | indexed list:(0, seq[0]), (1, seq[1]), (2, seq[2]), ...
|
| Methods defined here:
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __iter__(self, /)
| Implement iter(self).
|
| __new__(*args, **kwargs) from builtins.type
| Create and return a new object. See help(type) for accurate | signature.
|
| __next__(self, /)
| Implement next(self).
|
| __reduce__(...)
| Return state information for pickling.**
返回类型
枚举函数的返回类型是 对象 类型。因此 enumerate 函数通过向对象添加迭代计数器值来返回该对象。还可以将枚举器对象转换成 list()、tuple()、set()等等。
让我们看一个例子,了解一下枚举函数。
programmming = ["Python", "Programmming", "Is", "Fun"]
print(type(programmming))enum = enumerate(programmming)print(type(enum))*#Converting to a list* print(list(enum))*#Converting to a tuple* print(tuple(enum))*#Converting to a set* print(set(enum))
当您运行该程序时,您将获得以下输出:
**<class 'list'>
<class 'enumerate'>
[(0, 'Python'), (1, 'Programmming'), (2, 'Is'), (3, 'Fun')]
((0, 'Python'), (1, 'Programmming'), (2, 'Is'), (3, 'Fun'))
{(3, 'Fun'), (2, 'Is'), (0, 'Python'), (1, 'Programmming')}**
可选参数
枚举函数也接受可选参数,您也可以传递数据结构的名称以及您想要启动计数器的特定索引。例如,认为列表的默认值从 0 开始,然后计数器继续迭代。现在你想从 5 开始计数,然后递增计数。这可以按如下所示完成:
programmming = ["Python", "Programmming", "Is", "Fun"]# Counter value starts from 0
enum = enumerate(programmming)
print(list(enum))# Counter value starts from 5
enum = enumerate(programmming, 5)
print(list(enum))
当您运行该程序时,您将获得以下输出:
**[(0, 'Python'), (1, 'Programmming'), (2, 'Is'), (3, 'Fun')]****[(5, 'Python'), (6, 'Programmming'), (7, 'Is'), (8, 'Fun')]**
遍历枚举对象
让我们看看如何循环遍历枚举,如下所示:
programmming = ["Python", "Programmming", "Is", "Fun"]for i in enumerate(programmming):
print(i)for i in enumerate(programmming, 10):
print(i)
当您运行该程序时,输出将是:
**(0, 'Python')
(1, 'Programmming')
(2, 'Is')
(3, 'Fun')****(10, 'Python')
(11, 'Programmming')
(12, 'Is')
(13, 'Fun')**
关于“ Python enumerate()内置函数 ”的教程到此结束,这是一个很短的教程,因为这个概念很小,你用它能做的事情不多。了解枚举函数的概念真的很重要,这样你就可以在其他地方应用这个概念了。要阅读枚举函数的官方文档,请访问下面的链接:
[## 内置函数- Python 3.8.0 文档
Python 解释器内置了许多始终可用的函数和类型。它们被列出…
docs.python.org](https://docs.python.org/3/library/functions.html#enumerate)
此外,完整的代码可以在下面我的 GitHub 库中找到:
此时您不能执行该操作。您已使用另一个标签页或窗口登录。您已在另一个选项卡中注销,或者…
github.com](https://github.com/Tanu-N-Prabhu/Python/blob/master/Python_enumerate()_built_in_function.ipynb)
好了,该说再见了,请继续关注阅读更多 python 教程。我希望你喜欢阅读本教程。祝你今天开心!玩的开心!
Python eval()内置函数
让我们了解一下 python 中的 eval()内置函数。

这将是一篇关于 python 中的 eval 函数的短文,其中我将向您解释 eval 函数、它的语法以及面试中经常被问到的几个问题,以便您清楚地理解它并轻松地回答这些问题。要获得完整代码,请点击下面的我的 GitHub 库:
此时您不能执行该操作。您已使用另一个标签页或窗口登录。您已在另一个选项卡中注销,或者…
github.com](https://github.com/Tanu-N-Prabhu/Python/blob/master/Eval_built_in_function.ipynb)
让我们开始吧:
1.python 中的 eval()是什么,语法是什么?
回答: eval 是 python 中使用的内置函数,eval 函数解析 expression 参数并将其作为 python 表达式求值。简单来说,eval 函数像 python 表达式一样对“字符串”求值,并将结果作为整数返回。
句法
eval 函数的语法如下所示:
**eval(expression, [globals[, locals]])**
自变量或参数
eval 函数的自变量或参数是字符串,也可以是全局的,局部变量可以用作 eval 函数内部的自变量,但是全局变量必须表示为字典,局部变量表示为映射对象。
返回值
返回值将是计算表达式的结果。返回类型通常是整数。
2.eval 函数在哪里最常用?
Eval 函数主要用于需要计算数学表达式的场合或应用。此外,如果用户希望将字符串转换为代码,那么可以使用 eval 函数,因为 eval 函数会计算字符串表达式并返回整数作为结果。
3.input()和 eval()有什么区别?
现在大家都知道 input()接受用户输入,但是当用户输入一个整数作为输入时,input 函数返回一个字符串,但是在 eval 的情况下,它会将返回值从字符串计算为整数。我知道你们大多数人都很困惑,让我举个例子来消除你们的困惑:
input = input("Enter any number of your choice:")
print(input)
print(type(input))
--------------------------------------------------------------------**Enter any number of your choice: 10 + 10
10 + 10
<class 'str'>**
看,就像我说的,我输入了一个整数 10+ 10 ,我期望的结果是 20 (10 + 10) ,但是输入方法返回了一个与输入相同的字符串。
eval = eval(input("Enter any number of your choice"))
print(eval)
print(type(eval))
--------------------------------------------------------------------**Enter any number of your choice: 10 + 10
20
<class 'int'>**
在 eval 的情况下,它以整数的形式返回计算后的表达式 20 ,给定字符串作为输入。 10 + 10 是一个结果为 20 的表达式。
4.能否用 eval 函数进行数学运算,举个例子?
是的,我们可以使用 eval 函数执行数学运算,如下所示:
evaluate = input(“Enter what operation x has to perform: “)
print(evaluate)
print(type(evaluate))
--------------------------------------------------------------------
**Enter what operation x has to perform: x + x + 100 - 35 + 5 * 80
x + x + 100 - 35 + 5 * 80
<class 'str'>**x = 10
print(type(x))
--------------------------------------------------------------------**<class 'int'>**expression = eval(evaluate)
print(expression)
print(type(expression))
--------------------------------------------------------------------
**485
<class 'int'>**
就像我说的,如果你以字符串的形式给出输入,eval 函数计算表达式,并以整数的形式返回结果。
这就是你开始使用 python 中的 eval 函数所需要知道的一切,现在你已经知道了上述问题的所有答案。同样的问题可能不会一直被问到,关键是要更好地了解概念,然后你才能回答任何问题。如果您想花些时间阅读一些关于 eval 函数的材料,我建议大家阅读下面所示的 eval 函数文档:
[## 内置函数- Python 3.8.0 文档
Python 解释器内置了许多始终可用的函数和类型。它们被列出…
docs.python.org](https://docs.python.org/3/library/functions.html#eval)
谢谢各位,这是文章的结尾就像我说的这是一篇小文章。如果你们有什么疑问或者遇到了什么困难,请在下面的评论区告诉我,我一定会回答你们所有的问题。好了,该说再见了,祝你有美好的一天。
Python 练习:计算一手 A 牌的所有可能值

这比我最初想的更有趣/复杂,因此写了这篇文章
我最近发表了一篇关于如何用 Python 模拟 21 点的博客。我仍然在做那个博客的后续工作,但是我想在那里得到这个免费的帖子。
在我发表了我最初的 21 点帖子后不久,Twitter 用户@DonBeham 友好地指出,在某些情况下(因为 ace),我的代码产生的手牌值将是错误的。
所以我去修复我的错误,并意识到计算 ace 值比我最初想象的要复杂得多。我们来探究一下原因。
你可以在我的 GitHub 上找到下面的代码以及我的 21 点代码。
挑战
写一个函数来计算一手牌的 21 点数值。
似乎很简单。
除了 a 可以值 1 分或 11 分,这取决于你手牌其余部分的价值。如果你的其他牌值 9 分,那么 a 值 11 分,这样你总共有 20 分。另一方面,如果你的其他牌值 19 分,a 现在值 1 分,这仍然给你 20 分。
听起来不算太糟,对吧。让我们看看复杂性是从哪里来的:
- 一手牌可以有任意数量的 a。鉴于赌场通常使用至少六副牌的牌叠,你最终可能拿到三张、四张甚至五张 a,这是非常不可能的,但肯定不是不可能的。所以我们需要逻辑来解释这些罕见的情况。
- 你手中 a 的价值不仅取决于你拿到 a 时已经有的牌,还取决于你将来会拿到的牌。例如,如果你的手牌是[a,7],那么它现在值 11+7=18 点。你得到一张王牌,这是一手更新的[王牌,7,王牌]——你的牌值增加到 11+7+1 = 19;不太复杂,因为我们知道,我们获得的每一个额外的 ace 都只值 1 分(因此我们不会超过 21 分)。现在,让我们把它变得更复杂一点——你拿到一张 5,给你[a,7,a,5]。保持之前的 ace 值 11 和 1,我们的手牌值为 11+7+1+5=24。这将超过 21,因此破产,所以现在我们拥有的每张 a 应该只值 1 分。
二十一点解决方案
针对 21 点的解决方案还不算太差。事情是这样的:
- 如果我们手中没有 a,那么 a 的值显然为零——首先我们需要检查这一点。
- 我们知道我们手里的 a 只有一张能值 11 分。否则我们会破产——例如,11+11=22。
- 因此,如果我们手里一张王牌值 11,其他值 1 的牌总数小于或等于 21,那么保留当前的王牌值(其中一张值 11,其余值 1)。
- 否则,如果一张 a 值 11,其他值 1 的手牌总数大于 21,则将所有 a 值设为 1。
#Check if the value of non-ace cards plus aces with one ace worth 11 #and the rest worth 1 is less than or equal to 21if num_aces > 0:
if non_ace_total + 11 + (num_aces - 1)*1 <= 21:
ace_value = 11 + (num_aces - 1)*1
else:
ace_value = num_aces*1
else:
ace_value = 0
hand_total = non_ace_total + ace_value
很简单。但是,除了 21 点以外,还有什么更通用的解决方案呢?现在,我们知道只有一张 a 值 11,这对我们很有帮助。但是如果破产的门槛本身就是一个可变的变量呢?你也许仍然可以用一堆“如果语句”来做这件事,但是会很麻烦。相反,让我们写一个更一般的解决方案。
广义解
让我们试着想出一个更通用的解决方案。我想到的是用树。
- 我想找出 ace 值的所有排列。例如,对于两个 ace,它将是[1,1],[1,11],[11,1],[11,11]。我实际上想找到 ace 值的所有组合,但我发现先得到排列,然后过滤掉重复的更容易。
- 然后我把它们加起来——2,12,12,22。
- 最后,我想要唯一的值— 2,12,22。从这组值中,我可以选择最符合我的游戏规则的数字。
那么我们如何得到所有的排列呢?我首先想到的是嵌套的 for 循环。但这只有在你预先知道有多少个 ace 的情况下才有效,例如,如果你有 3 个 ace,那么你需要循环 A,B 和 C,其中 C 嵌套在 B 中,B 又嵌套在 A 中。
ace_values = []for A in [1,11]:
for B in [1,11]:
for C in [1,11]:
ace_values.append(A+B+C)unique_ace_values = set(ace_values)
但关键是我们不知道会有多少个 a——所以我们不知道我们需要多少个 for 循环。
我的回答是用一棵树。我们可以把每个 ace 看作两个分支(如果我的树术语不正确,我道歉),其中第一个分支对应于 1,第二个分支对应于 11。如果我们持有第二张 a,那么每个分支再次分裂,因此总共有 4 个叶节点,对应于手牌值 2、12、12 和 22。下图显示了我刚才描述的两个 ace 案例。

Using a tree to find all permutations for two aces
使用我们的树结构,我们可以通过遍历树直到到达每个叶节点来得到每个排列:
- 向上,向上—在第一次分割时获取 1,然后在叶节点再次获取 1。
- 向上,向下—得到 1,然后是 11。
- 向下,向上—得到 11,然后是 1。
- 向下,向下——得到 11 个,然后 11 个。
那么如何用 Python 代码实现这一点呢?受过传统训练的计算机科学家可能会创建一个树类,然后递归地遍历它。但那是周日晚上,我不是递归的大师,所以我用快速而肮脏的方法遍历了我的树。
遍历树
下面是我的代码。我会一步一步地教你。
第一个块是主要功能。它将 ace 的数量作为一个整数变量 num_aces,创建一个名为 temp_list 的列表,然后将该列表传递给一个助手函数 get_ace_values,该函数产生最终结果。在我传递给 get_ace_values 的列表变量 temp_list 中,每个 ace 都表示为[1,11] —所以如果有三个 ace,temp_list = [[1,11],[1,11],[1,11]]。
# Convert num_aces, an int to a list of lists
# For example if num_aces=2, the output should be [[1,11],[1,11]]
# I require this format for the get_ace_values functiondef ace_values(num_aces):
temp_list = []
for i in range(num_aces):
temp_list.append([1,11])
return get_ace_values(temp_list)
现在该迭代了。我没有递归地遍历树,而是将树重新想象成一个数组,其中每一行对应于树的一次端到端遍历。这是三张 a 的情况:
[[ 1\. 1\. 1.] # up, up, up
[ 1\. 1\. 11.] # up, up, down
[ 1\. 11\. 1.] # up, down, up
[ 1\. 11\. 11.] # up, down, down
[11\. 1\. 1.] # down, up, up
[11\. 1\. 11.] # down, up, down
[11\. 11\. 1.] # down, down, up
[11\. 11\. 11.]] # down, down, down
你看到模式了吗?首先,遍历树有 2 = 8 种可能的方式(因为有三个 ace)。在我们的数组的第一列中,数字每四个元素切换一次(从 1 到 11)。在第二列中,它们每两个交换一次。在最后一栏,他们转换了所有元素。
下面是我的 get_ace_values 函数的代码。这肯定不是最优雅的解决方案。我在下面的项目符号中解释了它是如何工作的。
def get_ace_values(temp_list):
sum_array = np.zeros((2**len(temp_list), len(temp_list)))
# This loop gets the permutations
for i in range(len(temp_list)):
n = len(temp_list) - i
half_len = int(2**n * 0.5)
for rep in range(int(sum_array.shape[0]/half_len/2)):
sum_array[rep*2**n : rep*2**n+half_len, i]=1
sum_array[rep*2**n+half_len : rep*2**n+half_len*2, i]=11
return [int(s) for s in np.sum(sum_array, axis=1)]
- 第一个 for 循环运行的次数等于我们拥有的 ace 数 temp _ list 的长度等于我们拥有的 ace 数。for 循环的每一次运行都会产生 sum_array 的一列,即我上面描述的数组。
- 变量 n 只是从 ace 的数量开始向下计数,一直到 1。例如,如果有三个 ace,n 将从 3 开始,然后在第二次循环中减少到 2,最后在 1 结束。
- 变量 half_len 告诉我们在切换到打印 11 之前连续打印 1 的次数(相同的次数)。如果你还记得我在上面给你看的 sum_array 矩阵,在第一列,我们打印了四个 1 后面跟着四个 11。因此,对于 for 循环的第一次运行(对于三个 ace 的情况),half_len 等于 2 * 0.5 = 4。在第二列中,我们每两个元素交换一次,因此 half_len 等于 2 * 0.5 = 2。
- 对于三个 ace 的情况,第二个 For 循环(嵌套循环)在外部 for 循环的第一次运行时运行一次,在外部循环的第二次运行时运行两次,最后在外部循环的最后一次运行时运行四次。这个嵌套循环用于在我们需要的地方打印 1 和 11。
THREE ACE CASEIn the first run of the outer loop, since half_len=4, the nested for loop runs once (8/half_len/2 = 1) and prints four 1s followed by four 11s onto sum_array:
[[ 1\. 0\. 0.]
[ 1\. 0\. 0.]
[ 1\. 0\. 0.]
[ 1\. 0\. 0.]
[11\. 0\. 0.]
[11\. 0\. 0.]
[11\. 0\. 0.]
[11\. 0\. 0.]]In the second run of the outer loop, since half_len=2, the nested for loop runs twice (8/half_len/2 = 2), each time printing two 1s followed by two 11s onto sum_array:
[[ 1\. 1\. 0.]
[ 1\. 1\. 0.]
[ 1\. 11\. 0.]
[ 1\. 11\. 0.]
[11\. 1\. 0.]
[11\. 1\. 0.]
[11\. 11\. 0.]
[11\. 11\. 0.]]In the third run of the outer loop, since half_len=1, the nested for loop runs four times (8/half_len/2 = 4), each time printing a 1 followed by a 11 onto sum_array:
[[ 1\. 1\. 1.]
[ 1\. 1\. 11.]
[ 1\. 11\. 1.]
[ 1\. 11\. 11.]
[11\. 1\. 1.]
[11\. 1\. 11.]
[11\. 11\. 1.]
[11\. 11\. 11.]]
- 差不多了 sum _ array 的每一行现在都可以用来表示我们的树的一个端到端遍历,这意味着每一行都是一个排列。所以我们要做的就是取 sum_array 中每行的和,得到所有可能的 ace 值。
让我们为三种 ace 情况运行 ace_values:
Running:
ace_values(3)Produces:
[3, 13, 13, 23, 13, 23, 23, 33]Taking the set of this:
set(ace_values(3))Produces what we want:
{3, 13, 23, 33}
我很喜欢编码,但是我还有很多要学的,所以我会经常发表这样的散漫的文章。请原谅我。
干杯,下次再见!
我最近的一些帖子,希望你能看看
面向数据科学的 python——Plotly 数据可视化指南
现在是 2020 年,是时候停止使用 Matplotlib 和 Seaborn 了

Photo by Carlos Muza on Unsplash
问候我的数据从业者同事们,欢迎回到我的数据之旅的另一集。本周我们将讨论数据的可视化,它们有多重要,以及你应该使用什么样的 python 库来制作它们。所以上车吧,放松,享受旅程。
如果你像我一样是一名数据从业者,你应该经历过需要向别人解释你的数据的情况。你需要讲一个故事,表达你的观点,说服你的同事、主管甚至你的首席执行官。现在,想象你试图解释不到 20%的男性在泰坦尼克号数据集中幸存。
会是什么样子?

Male Survivability (Titanic Dataset)
正如你所看到的,可视化在解释你的数据方面发挥了巨大的作用。在屏幕上显示数字并不适用于某些人。视觉效果的确与众不同。
可视化帮助数据科学家理解和发现数据中隐藏的故事。在一个实例中,一个简单的直方图可以告诉你比你自己试图找出分布更多的信息。
这是泰坦尼克号数据集的票价分布。

Distribution for Fares (Titanic Dataset)
直截了当地绘制出来告诉我们,我们的大多数客户的票价都在 100 美元以下。这是很好的信息,但是它没有告诉我们的全部情况。
想象给了你一些你不知道的问题的答案。本·施奈德曼
水流
当数据可视化首次成为一件事情时,数据从业者在与熊猫一起探索数据时绘制图表。使这成为可能的库叫做 Matplotlib,它是一个好的库,因为它是:
- 使用方便
- 快的
- 与熊猫和谐相处
然而,使用时间足够长的人会觉得它很乏味。甚至 Matplotlib 的官方功能也声明:
Matplotlib 主要用于基本绘图。— Matplotlib
因此,想要一个由更多绘图模式、选项和更简单语法组成的更有趣的库的数据从业者蜂拥至 Seaborn 库。在那个时候,Seaborn 是很多人的首选。这是我们刚刚绘制的直方图的一张 Seaborn 图。

Distribution for Fares (Titanic Dataset) in Seaborn
Seaborn 图书馆还提供了数量惊人的情节,从 Hexbin 情节到 Violin 情节,那种你从未想过你会需要的情节。这里有一个他们提供的情节类型的简要预览。

Seaborn’s Official Gallery
然而,仍然有一个问题。所有这些库都提供静态图。这些图只能告诉你它们在屏幕上显示的内容。你不能更深入地研究这些图,将鼠标悬停在点上来查找信息或添加过滤器。
能够执行此类功能的绘图被命名为
交互式可视化。
互动可视化很受欢迎,因为它可以在你呈现的情节上添加大量信息,释放可能性,让你看起来10 倍 酷。很难用语言表达,让我们想象一下我想告诉你的事情。
这是泰坦尼克号数据集中同样的票价分布图。

Distribution of Fares (Titanic Dataset)
请注意,我们可以将鼠标悬停在直方图上,了解我们所指的分布
(男性或女性),了解我们所悬停的值,放大和缩小以深入了解这些值,以及过滤男性或女性。你已经可以想象这是多么强大,这将如何有助于你用数据讲述故事。
使这成为可能的库被称为通俗地说就是。

Plotly
Plotly
Plotly 是用于交互式数据可视化的 Python 库。Plotly 允许你绘制比 Matplotlib 或 Seaborn 更好的交互式图形。
Plotly 绘制什么样的图形?
- 所有 Matplotlib 和 Seaborn 图表
- 统计图表,包括但不限于平行分类和概率树图
- 你从未想到的科学图表从网络图到雷达图
- 财务图表对时间序列分析有用,例子包括蜡烛图、漏斗图和子弹图
- 地质图和允许你与之互动的三维绘图

Plotly Examples from Plotly Announcement
为什么 Plotly 这么受欢迎?
-
互动剧情
-
比 Matplotlib/Seaborn 漂亮(有待商榷?)
-
提供更详细的可视化效果,有助于探索、理解和交流您的数据
-
为您的绘图提供最大化定制,包括添加滑块和过滤器的
-
更加清晰易懂的代码库
-
由一家名为 Plotly 的公司支持,该公司开发基于网络的交互式可视化和网络应用程序
我如何安装 Plotly?
安装 Python,
pip install plotly
pip install cufflinks
先决条件
在本指南中,我们将与熊猫一起工作。因此,需要对熊猫的基本知识有一个扎实的了解。
这里有一篇我写的关于熊猫的文章让你快速了解。
不管它带来多少好处,人们倾向于避免它,因为:
- 令人困惑的代码的语法
- 进口商品可能会吓跑一些人
- 被可用的工具淹没(袖扣和仪表板)
- 缺乏一个好的指南来策划(这就是为什么我做了这个)
不要害怕,我会牵着你的手度过这段艰难的时光。抓起一杯咖啡,坐在一个舒适的位置,准备做一些代码。
进口
在本指南中,我们将使用 Jupyter 笔记本。让我们把这一个做好。
**from plotly.offline import init_notebook_mode,iplot
import plotly.graph_objects as go
import cufflinks as cf
init_notebook_mode(connected=True)**
由于 Plotly 的操作方式,它会将您的绘图保存到一个单独的 html 文件中,并直接在不同的窗口中打开它。当您在控制台/终端中运行代码时,就会发生这种情况。因此,我们使用 plotly.offline、iplot 和 init_notebook 模式来帮助我们在 Jupyter 笔记本本身上绘制图形。
在本指南中,我将重点介绍原始的 plotly 语法,因为它为您的图形提供了最大限度的定制。 Plotly Express 和 Cufflinks 为简单的代码绘制提供了更好的选择,但是它们没有像原始语法那样提供很多工具。
定义需要绘制的内容
在策划任何事情之前,你需要知道你想策划什么。问自己这样的问题:
- 你想传达什么样的信息?
- 您是在绘制数值还是分类值呢?
- 你想画出多少变量?
回答完其中的一些问题,你就可以开始规划你的剧情了。
我们将使用经典的 泰坦尼克号数据集 ,从我写的 熊猫指南 继续我们的数据探索。
导入数据集
**df = pd.read_csv(filepath)**

Titanic Dataset
同样,这个数据集的主要目的是研究什么是影响泰坦尼克号上人员生存能力的因素。
我首先想到的是显示泰坦尼克号失事中有多少乘客幸存。因此,可视化幸存的柱本身将是一个好的开始。****
数据、布局和图形
在 Plotly 中,我们定义要绘制的图形对象。绘图所需的 3 个主要参数是数据、布局和图形参数。
因此,我们需要用一种清晰简洁的方式来定义它们,这样其他人就能理解我们试图绘制的内容。
由于我们是单独绘制幸存的列的,我们的数据将是一个****
圆形分格统计图表
#labels
lab = df["Survived"].value_counts().keys().tolist()
#values
val = df["Survived"].value_counts().values.tolist()trace = go.Pie(labels=lab,
values=val,
marker=dict(colors=['red']),
# Seting values to
hoverinfo="value"
)data = [trace]
Plotly 的饼状图带入必需的参数,默认为标签和值。因此,我们将标签定义为幸存列的唯一值,在本例中为值 1 和 0。我们试图显示的值是这些唯一值的计数。然后,我们将数据定义为一个包含我们刚刚定义的饼图对象的列表。
布局 布局顾名思义,就是你的情节的布局。
在这里,您可以定义绘图标题,x 和 y 轴标题,显示图例,激活过滤器/滑块**以及更多对图表的定制。由于我们只为单个列绘制了一个饼图,所以不要把它做得太花哨。**
layout = go.Layout(title="Survived Distribution")
我们正在创建一个布局** 对象,这里只包含标题参数。**
****图
图图是你想要绘制的,它默认接受我们已经定义好的数据和布局参数。
fig = go.Figure(data = data,layout = layout)
然后,我们可以通过用 Plotly 按字面意义绘制它来显示这个图。
iplot(fig)

Pie Chart
瞧,你已经成功地绘制了一个交互图。请注意,当悬停在每个部分上时,我们能够看到实际值。这在以干净专业的方式呈现可视化效果时非常有用。尝试为性别和p 类别列绘制一个饼图。
接下来,让我们探索一些数字列,它们是年龄和费用列。当单独绘制数字列时,我们会想到一个分布图。在这种情况下,我将使用直方图。
柱状图
# defining data
trace = go.Histogram(x=df['Age'],nbinsx=40,histnorm='percent')
data = [trace]# defining layout
layout = go.Layout(title="Age Distribution")# defining figure and plotting
fig = go.Figure(data = data,layout = layout)
iplot(fig)

Histogram
完美。我们可以清楚地看到乘客的年龄在这里是如何分布的。
注意,我们可以调整直方图的两个有用参数,它们是:
- Histnorm —绘制直方图所依据的值,它被设置为“百分比”,以便我们显示对分布有贡献的条柱的百分比。如果留空,默认情况下显示箱子的数量。
- nbinsx —要分配到的值的箱数。箱数越多,分布越详细。
酷毙了。到目前为止,我们只绘制了一个变量的图表。现在让我们通过绘制这些变量之间的相互关系来研究它们之间的关系。
让我们找出我们的年龄和票价列之间的关系。这将有助于回答这样的问题——老年人是否倾向于购买更贵的机票?
当绘制两个数值时,
散点图将是一个很好的起点。
散点图
#defining data
trace = go.Scatter(x = df['Age'],y=df['Fare'],text = df['Survived'],mode='markers')data=[trace]#defining layout
layout = go.Layout(title='Fare Vs Age Scatter Plot',xaxis=dict(title='Age'),yaxis=dict(title='Fare'),hovermode='closest')#defining figure and plotting
figure = go.Figure(data=data,layout=layout)
iplot(figure)

Scatter Plot
请注意,我在该图中添加了一些微妙的特征,即 x 和 y 轴标题以及悬停在该点上时显示的值。您可以通过更改代码中的文本参数来自定义显示** 值。**
观察这个情节,我们并没有真正看到列之间的线性关系。票价往往会触及 300 英镑以下的价格上限。我们还观察到,老年人也买便宜的机票。除此之外,单纯依靠这个情节,我们真的说不出太多。
我们可以通过查看 Pclass 列进一步研究这个问题,这是乘客的机票等级。通过绘制每个阶层的平均年龄和费用,我们可以看到这里是否有联系。
条形图
让我们绘制一个显示这些信息的条形图。对于每个 Pclass,我们希望显示该 Pclass 中的平均年龄和费用。如果你注意到了,我们在这里开始处理多个变量,确切地说是 3 个。
为了简单起见,我将在我们的条形图中以色标的形式绘制 x 轴上的 Pclass,y 轴上的平均年龄和平均票价。因此,我们需要首先计算每个等级的平均年龄和票价。
y=[]
fare = []
for i in list(df['Pclass'].unique()):
result = df[df['Pclass']==i]['Age'].mean()
fares = df[df['Pclass']==i]['Fare'].mean()
y.append(result)
fare.append(fares)
之后,我们可以绘制图表。
#defining data
trace = go.Bar(x = list(df['Pclass'].unique()),y=y,marker=dict(color=fare,colorscale='Viridis',showscale=True),text = fare)
data=[trace]#defining layout
layout = go.Layout(title='Age/Fare vs Pclass Bar Chart',xaxis=dict(title='Pclass'),yaxis=dict(title='Age'),hovermode='closest')#defining figure and plotting
figure = go.Figure(data=data,layout=layout)
iplot(figure)

Bar Chart
我们在这里添加了两个新参数,它们是:
- 颜色—与色标相关的值
- 色标-显示数值大小的色标类型
酷毙了。将鼠标悬停在条形上方,我们可以看到每个等级的平均年龄和票价,更不用说定义票价的清晰色标了。条形图清楚地显示了年龄和票价随着 Pclass 的增加而降低。
我们也可以通过绘制每个 Pclass 的年龄和票价的分布来证实这一点。这将为我们提供每类数据的清晰图像,而不是平均值。为此,我们将使用 distplot。
分布图
分布图类似于直方图,但它们在底部包括另一个计数图,以便更好地显示分布。
我们必须为分布图导入一个额外的库。
import plotly.figure_factory as ff
让我们分别根据年龄和票价
绘制两张图表。
#defining data
a = df[df['Pclass']==1]['Fare']
b = df[df['Pclass']==2]['Fare']
c = df[df['Pclass']==3]['Fare']
hist_data=[a,b,c]
group_labels=['1','2','3']#defining fig and plotting
fig = ff.create_distplot(hist_data,group_labels,bin_size=
[1,1,1],show_curve=False)
fig.update_layout(title_text='Distribution for Fares')
iplot(fig)

Distribution of Fares
a = df[df['Pclass']==1]['Age']
b = df[df['Pclass']==2]['Age']
c = df[df['Pclass']==3]['Age']
hist_data=[a,b,c]
group_labels=['1','2','3']
fig = ff.create_distplot(hist_data,group_labels,bin_size=[1,1,1],show_curve=False)
fig.update_layout(title_text='Distribution for Age')
iplot(fig)

Distribution of Age
两张分布图都清楚地表明,较低的社会阶层
与较高的年龄和较高的票价相关。通俗地说,就是头等舱机票价格更高,年纪大的人更倾向于购买头等舱机票。
得出这个结论后,我们想知道生存能力与这些变量有什么关系。我们可以用气泡图来绘制针对他们的生存能力图。气泡图允许可视化多达 4 个变量,这可以帮助我们传达我们的观点。
气泡图
气泡图类似于散点图,但它们有一个额外的大小参数来定义每个点的半径。
#defining data
data=[
go.Scatter(x = df['Age'],y=df['Fare'],
text=df['Pclass'],
mode='markers',
marker=dict(size=df['Pclass']*15, color=df['Survived'],showscale=True),
)]#defining layout
layout = go.Layout(title='Fare vs Age with Survivability and Pclass',xaxis=dict(title='Age'),yaxis=dict(title='Fare'),hovermode='closest')#defining figure and plotting
figure = go.Figure(data=data,layout=layout)
iplot(figure)

Bubble Plot
从泡沫图中,我们可以看出:
- 更高的年龄不会导致更高的票价
- 所有高于 50 英镑的票价都被视为头等票
- 如果所有其他变量不变,1 级具有更高的生存能力
- 如果所有其他变量不变,年龄越高,存活率越低
这里是本文中所有可视化的完整代码。
此时您不能执行该操作。您已使用另一个标签页或窗口登录。您已在另一个选项卡中注销,或者…
github.com](https://github.com/nickefy/data-visualization-with-plotly/blob/master/A Guide to Data Visualization with Plotly.ipynb)**
恭喜

Basic Charts from Plotly
通过和我一起完成这个数据探索课程,你已经从 Plotly 学会了大部分的基本图表。仅仅依靠基本图表就能得出的结论是不是很神奇?现在,你应该意识到强大的可视化在探索和理解你的数据中是多么的重要。
在你走之前
我们还没有完成对这个数据集的数据探索和可视化。我正在做更多的高级可视化文章,比如添加滑块/过滤器等等,你绝对可以期待更多这样的帖子。与此同时,请随意查看我的其他文章,以暂时满足您对数据的渴望。
最后,我总是会引用一句话。
现在是 2020 年,停止使用 Matplotlib 和 Seaborn ,提升你的可视化。
订阅我的时事通讯,保持联系。
也可以通过 我的链接 注册中等会员来支持我。你将能够从我和其他不可思议的作家那里读到无限量的故事!
我正在撰写更多关于数据行业的故事、文章和指南。你绝对可以期待更多这样的帖子。与此同时,可以随时查看我的其他 文章 来暂时填补你对数据的饥渴。
感谢 阅读!如果你想与我取得联系,请随时通过 nickmydata@gmail.com 联系我或我的 LinkedIn 个人资料 。您也可以在我的Github中查看之前的撰写代码。
用于数据科学的 Python 使用 Plotly 进行数据可视化的高级指南
如何在 Plotly 中添加和自定义滑块、下拉菜单和按钮

嗨,读者,如果你像我一样,你会每天都想学习新的东西。我可以帮忙。这是我每周写的另一篇关于数据行业的文章。本周,我们将再次谈论数据可视化。
找个舒服的地方,喝杯咖啡,准备写点代码。
上周,我们谈到了数据可视化的重要性。 我们了解到 Plotly 与经典的 Matplotlib 和 Seaborn 相比,在解释和探索数据方面非常有用。我们还学会了用 Plotly 绘制基本的 可视化,如条形图、饼状图和气泡图。
我们在我写的第一篇 文章 中谈到过。
[## 面向数据科学的 python——Plotly 数据可视化指南
现在是 2020 年,是时候停止使用 Matplotlib 和 Seaborn 了
towardsdatascience.com](/python-for-data-science-a-guide-to-data-visualization-with-plotly-969a59997d0c)
在我们接触了 Plotly 之后,是时候通过使用它提供的交互式工具来挖掘它的真正潜力了。当我们用 Plotly 可视化数据时,我们能够添加某些工具来允许我们定制可视化体验。在向企业主展示数据时,这一点可能是至关重要的,因为它能够更清晰地传达你的观点。
这里有一个关于 泰坦尼克号数据集 ,
的例子,说我们想同时显示女性和男性的票价分布以及能够分别显示它们。我们可以为此添加一个下拉菜单,Plotly 会为我们完成剩下的工作。****

Distribution of Fares by Gender
通过使用 plotly,我们可以自定义交互地在图表上显示什么和不显示什么。我们通过添加下拉框、滑块、按钮和其他选项来做到这一点。这里的男性和女性选项可能看起来很简单,但是我们将在本文中看到它是多么有用。
在本指南中,我将全面介绍如何添加和定制你自己的下拉菜单、滑块和按钮来改善你的视觉效果。
先决条件
显然,在本指南中,我们将大量使用 Plotly。
因此,请确保你在继续阅读本进阶指南之前,对有一个坚实的理解。你可以在这里参考我写的关于的文章。
通过与 Plotly 一起工作,你应该也能轻松地与熊猫一起工作。
真巧。我碰巧也写了一本熊猫指南。请务必回忆一下。
10 分钟内完成数据探索指南
towardsdatascience.com](/python-for-data-science-basics-of-pandas-5f8d9680617e)
Plotly,熊猫等。来吧,你知道我有你。****
进口
像往常一样,我们将使用 Jupyter 笔记本。
**#all plotly
from plotly.offline import init_notebook_mode,iplot
import plotly.graph_objects as go
import cufflinks as cf
init_notebook_mode(connected=True)#others
import pandas as pd
import numpy as np**
导入数据集
**df = pd.read_csv(filepath)**
下拉菜单
让我们使用我在本文中第一次展示的选项,
下拉菜单。

Dropdown Menu from Plotly
如果您想在保留地块上的空间的同时允许一系列选项,下拉菜单非常有用。我们不想让那些看你图表的人被一堆选项淹没。因此,我们将它们隐藏在下拉列表中,如果他们愿意,允许他们访问这些选项。
如果你还没有读过我的关于 Plotly 基础的文章,
我们需要在 Plotly 中定义 3 个重要参数。
数据、布局和图参数。
我们在绘图中添加下拉菜单的方法是在 Plotly 的布局参数中添加该命令。
**layout=go.Layout(updatemenus=list([dict(buttons= list_updatemenus)]))**
这将为我们设置下拉菜单。然而,我们需要定义在下拉菜单上显示什么,以及在选择这些选项时要应用的更改。我们可以通过定义 list_updatemenus 参数来实现以上所有功能。
特别说明:plot ly 中的下拉菜单不仅可以改变数据,还可以改变你的布局和播放动画。在本文中,我们将只讨论与数据的交互。你可以在这里做更多的研究,也许有一天我会写这个话题。嗯(表示踌躇等)..精神食粮。

Dropdown Menu by Plotlyz
回到手头的事情, list_updatemenus 是一个字典列表。
对于每个字典,下拉菜单中都会生成一个选项。
我们在每个字典中定义了这些选项的功能。
当我这么说的时候,听起来很混乱,
,但是当你用代码来处理它的时候,它实际上要简单得多。
我们开始吧。
对于 list_updatemenus、 中的每一个字典,我们可以把这个字典的每一个都称为一个选项。 对于每个选项,我们至少需要定义 3 个参数:
- 标签(字符串)—要在下拉菜单上显示的文本
- 方法(字符串)-选择选项时要执行的更新方法
- args (list) —包括将改变显示数据的参数
对于 args 参数,我们应该在列表中至少有两个参数:
- 可见(列表)-确定应该和不应该显示哪些数据
- 标题(字符串)-显示为图形标题的文本
这里有一个图表可以帮助你理解正在发生的事情。

Dropdown Menu Simplified
可见参数
Args 中的‘Visible’定义了我们的图中显示的内容。
是我们在 plotly 中定义的数据参数对应的布尔值列表。****
同样,我们的数据参数是我们为绘制图表而定义的图表对象列表。通过定义 visible 参数,我们可以选择交互显示哪个/哪些图形对象。
这里有个例子,假设我们的数据是
**data= [
go.Histogram(x=option1,name='option1'),
go.Histogram(x=option2,name='option2',
go.Histogram(x=option3,name='option3')
]**
通过使用这里的 visible 参数,我们可以选择只显示列表中图形对象的任意组合。
**#Show first graph object
{'visible':[True, False, False]}#Show second graph object
{'visible':[False, True, False]}#Show first and third graph object
{'visible':[True, False, True]}**
既然你已经理解了每样东西的含义,这里有一个模板来定义你的下拉菜单。
**list_updatemenus =
[{'label': 'Option 1',
'method': 'update',
'args': [{'visible': [True, False, False]}, {'title': 'Title is Option 1'}]},
{'label': 'Option 2',
'method': 'update',
'args': [{'visible': [False, True, False]}, {'title': 'Title is Option 2'}]},
{'label': 'Option 3',
'method': 'update',
'args': [{'visible': [False, False, True]}, {'title': 'Title is Option 3'}]}]layout=go.Layout(title = 'default title',
updatemenus=list([dict(buttons= list_updatemenus)]))**
这里有一个关于泰坦尼克号数据集的例子。
我们想按等级和性别显示票价的分布。
我们可以通过以下方式实现:
**#defining list_updatemenus
list_updatemenus = [{'label': 'Survived',
'method': 'update',
'args': [{'visible': [False, False, False, True, True, True]}, {'title': 'Distribution of Age for Survived Passengers'}]},
{'label': 'Non Survived',
'method': 'update',
'args': [{'visible': [True, True, True, False, False, False]}, {'title': 'Distribution of Age for Non-Survived Passengers'}]}]#defining all graph objects
x_pclass3 = df[df['Pclass']==3]['Fare']
x_pclass1 = df[df['Pclass']==1]['Fare']
x_pclass2 = df[df['Pclass']==2]['Fare']
x_male = df[df['Sex']=='male']['Fare']
x_female = df[df['Sex']=='female']['Fare']#defining data
data=[go.Histogram(x=x_pclass3,name='3rd Class'),go.Histogram(x=x_pclass1,name='1st Class',opacity = .5,nbinsx=60),go.Histogram(x=x_pclass2,name='2nd Class',opacity = .5),go.Histogram(x=x_male,name='Male',opacity = .5),go.Histogram(x=x_female,name='female',opacity = .5)]#defining layout
layout=go.Layout(title='Distribution of Fares by Pclass and Gender',updatemenus=list([dict(buttons= list_updatemenus)]),barmode='overlay')#defining figure and plotting
fig = go.Figure(data,layout)
iplot(fig)**

Distribution of Fares by Gender and Pclass
从这个图中,我们可以与它交互来显示按性别或阶级的票价分布。然后我们可以通过点击图例分别显示每个 p 类或性别或集体显示。现在,你应该意识到这有多强大了。****
在这个例子中,我们只使用直方图。您也可以选择其他图,以不同的方式可视化相同的指标。在这一点上,如何呈现数据完全取决于你的想象。
自定义按钮

Custom Buttons from Plotly
自定义按钮的作用与下拉菜单相同。唯一不同的是,自定义按钮将所有选项提前显示在图上,让全世界都能看到。如果你想让你的观众提前知道你所有的选择,这可能会也可能不会帮助他们更好地理解你试图展示的东西。****
添加自定义按钮的语法也与下拉菜单非常相似。
唯一不同的是你在布局中添加了“type = buttons”。
**layout=go.Layout(updatemenus=list([dict(buttons= list_updatemenus), type = buttons]))**
差不多就是这样。让我们来看另一个例子。
对于这个例子,我们试图通过 Pclass 显示幸存和非幸存乘客的年龄分布。
**#defining list_updatemenus
list_updatemenus = [{'label': 'Survived',
'method': 'update',
'args': [{'visible': [False, False, False, True, True, True]}, {'title': 'Distribution of Fares by Pclass'}]},
{'label': 'Non Survived',
'method': 'update',
'args': [{'visible': [True, True, True, False, False, False]}, {'title': 'Distribution of Fares by Gender'}]}]#defining graph objects
x_pclass3_nonsurvived = df[df['Pclass']==3][df['Survived']==0]['Age']
x_pclass1_nonsurvived = df[df['Pclass']==1][df['Survived']==0]['Age']
x_pclass2_nonsurvived = df[df['Pclass']==2][df['Survived']==0]['Age']
x_pclass3_survived = df[df['Pclass']==3][df['Survived']==1]['Age']
x_pclass1_survived = df[df['Pclass']==1][df['Survived']==1]['Age']
x_pclass2_survived = df[df['Pclass']==2][df['Survived']==1]['Age']#defining data
data=[go.Histogram(x=x_pclass3_nonsurvived,name='Died 3rd Class',nbinsx=60),go.Histogram(x=x_pclass1_nonsurvived,name='Died 1st Class',opacity = .5,nbinsx=60),go.Histogram(x=x_pclass2_nonsurvived,name='Died 2nd Class',opacity = .5,nbinsx=60),go.Histogram(x=x_pclass3_survived,name='Survived 3rd Class',nbinsx=60),go.Histogram(x=x_pclass1_survived,name='Survived 1st Class',opacity = .5,nbinsx=60),go.Histogram(x=x_pclass2_survived,name='Survived 2nd Class',opacity = .5,nbinsx=60)]#defining layout
layout=go.Layout(title='Distribution of Age by Pclass',updatemenus=list([dict(buttons= list_updatemenus,type = 'buttons')]),barmode='overlay')#defining layout and plotting
fig = go.Figure(data,layout)
iplot(fig)**

观察剧情,我们可以看到大多数非幸存者购买了泰坦尼克号上的3 级T7 级。这些第三类非幸存者的年龄大多在 15 和 35 之间。这让我相信,Pclass 列肯定在决定幸存者是否幸存方面发挥了作用。
有了自定义按钮,对观众来说更加明显你在过滤幸存和非幸存的乘客。在某些情况下,这将使你的演示看起来更专业,更容易理解。
滑块

Sliders from Plotly
虽然按钮和下拉菜单擅长显示不同的图表,但我发现滑块擅长在改变 某些变量的值时显示图表。如果所述变量是连续的数字变量,则更加明显。年龄可以是一个完美的例子。
滑块的工作方式与下拉菜单和按钮相同。不同之处在于,滑块允许你在与滑块交互时显示大量的选项。滑块本身也给人一种增加和减少某个值的错觉,但事实可能并非如此。
我们可以像使用下拉菜单和按钮一样使用滑块,通过从不同角度切片数据来显示不同类型的图表。然而,我更喜欢展示某个情节,同时能够操纵一个变量来更好地讲述一个故事。
就像下拉菜单和按钮一样,我们在绘图中添加滑块的方式是在我们的布局中添加一个简单的参数。
**layout=go.Layout(
sliders=sliders
)**
我们通过定义滑块和步骤参数来定义在滑块上显示的内容和应用的更改,就像我们如何定义下拉菜单的 list_updatemenus 一样。
类似地,滑块参数是一个字典列表。
对于每个字典,在图上生成一个滑块。
滑块保存定义默认值的参数,如:
- currentvalue(字典)—显示在滑块顶部的文本
- 活动(整数)-要显示的默认步骤
- 步骤(列表)—包括将改变显示数据的参数
步骤参数相当于下拉菜单的 list_updatemenus 。
这是一个字典列表,每个字典在滑块上生成一个选项。我们通过定义以下内容来定义这些选项的功能:
- 标签(字符串)—要在滑块上显示的文本
- 方法(字符串)-选择选项时要执行的更新方法
- args (列表)—包括将改变显示数据的参数
- 可见(列表)—决定哪些数据应该显示,哪些不应该显示
这里有一个图表可以帮助你理解正在发生的事情。

让我们看一个例子来帮助你更好地理解这一点。
为此,我们打算展示随着泰坦尼克号上乘客年龄的增长,生存能力的变化。
在我的脑海中,我正在描绘一个条形图,显示我们操作滑块时幸存的 0 和 1 的计数。既然我们在操纵年龄,我们应该为年龄设置滑块。我们还必须为年龄增长的每一步创建一个图表对象。我们是这样做的。
**# Create figure
fig = go.Figure()# Create Graph Object for every increase of 2 years of Age
for step in np.arange(0, 80, 2):
y=[]
for i in list(df['Survived'].unique()):
result = df[df['Age']<=step][df['Survived']==i]['Survived'].count()
y.append(result)
fig.add_trace(
go.Bar(
visible=False,
x = list(df['Survived'].unique()),
y= y,
name="Age = " + str(step)))**
对于这个代码块,我们为从 0 岁到 80 岁的每两步增长创建一个图形对象(条形图)。因此,我们这里基本上有 40 个步骤和图形对象。滑块将能够有效地浏览这些步骤和图形对象。
接下来,我们定义我们的滑块和步进参数。
**#defining sliders
sliders = [dict(
currentvalue={"prefix": "Age less than "},
active=1,
steps=steps
)]#defining steps
steps = []
for i in range(len(fig.data)):
step = dict(
label = str([i for i in np.arange(0, 80, 2)][i]),
method="restyle",
args=["visible", [False] * len(fig.data)],
)
step["args"][1][i] = True # Toggle i'th trace to "visible"
steps.append(step)**
在这个代码块中,我们创建了一个字典,并将其追加到年龄每增加 2 岁的 steps 参数中,同时包含了对每个步骤所做的所有更改。
最后,我们定义我们的布局并绘制图表。
**#defining layout and plotting
fig.update_layout(
title = 'Change of Survivability with Age',
yaxis=dict(title='Count of Survived'),
sliders=sliders
)
fig.show()**

瞧,多么美丽的情节。我们看到,一旦我们到了 18 岁,非幸存者人数开始超过幸存者人数。我们也可以有把握地说,对于所有 18 岁以下的乘客,他们中的大多数都活了下来。这可能为我们的分析提供有用的信息。
如果你还没明白,滑块在显示数值变化方面非常有效。如果您试图显示相对于时间和时间的数据,它也会发挥巨大的作用。这是一个非常强大的工具,我希望你开始在你的分析中更多地使用它。
下面是我们刚刚在本文中运行的所有内容的完整代码:
**** [## nickefy/advance-用绘图引导数据可视化
此时您不能执行该操作。您已使用另一个标签页或窗口登录。您已在另一个选项卡中注销,或者…
github.com](https://github.com/nickefy/advance-guide-to-data-visualization-with-plotly/blob/master/Advance Guide to Data Visualization with Plotly.ipynb)****
恭喜

Photo by bruce mars on Unsplash
欢欣鼓舞。你坚持到了最后。要接受的东西很多。我明白了。
在本指南中,您已经学习了 Plotly 的 3 个主要交互工具,它们是:
- 下拉菜单
- 小跟班
- 滑块
这些工具在试图理解数据集、执行有价值的分析和向利益相关者展示时,肯定会给你带来优势。
在你走之前
我们的数据之旅还没有结束。我正在撰写更多关于数据行业的故事、文章和指南。你绝对可以期待更多这样的帖子。与此同时,请随意查看我的其他文章,以暂时满足您对数据的渴望。
现在继续吧,我的朋友们,向世界展示我们数据从业者有多强大。
和往常一样,我以一句名言结束。
如果没有大数据,你会又瞎又聋,还在高速公路上。—杰弗里·摩尔
订阅我的时事通讯,保持联系。
也可以通过 我的链接 注册中等会员来支持我。你将能够从我和其他不可思议的作家那里读到无限量的故事!
我正在撰写更多关于数据行业的故事、文章和指南。你绝对可以期待更多这样的帖子。与此同时,可以随时查看我的其他 文章 来暂时填补你对数据的饥渴。
感谢 阅读!如果你想与我取得联系,请随时联系我在 nickmydata@gmail.com 或我的 LinkedIn 个人资料 。也可以在我的Github中查看之前的撰写代码。**
用于数据科学的 Python 熊猫指南
10 分钟内完成数据探索指南

各位数据从业者好。对我来说,这是忙碌的一周,但我回来了。
首先,我想亲自感谢那些从第一天起就决定追随我的人。我最近达到了 100 个追随者的大关。这并不多,但当我的内容被证明为某个随机的男孩/女孩提供了价值时,它对我来说就意味着整个世界。我开始写作是为了帮助人们解决他们的一些问题,因为当我面对他们时,我没有太多的资源可以参考。感谢您的关注,我将继续努力为您提供值,就像任何数据科学家都会做的那样。
但是够了,你不是为了那个而来,
你是为了那个数据科学的本质。
你想成为能够对数据执行
惊人技术的人,以获得你需要的东西。
你想成为提供难以置信的洞察力的人,这些洞察力影响
产品经理做出关键决策。
嗯,你来对地方了。进来吧。
我们今天将讨论最重要的
Python 对于 数据科学库。
这是…
*鼓形辊

Photo by chuttersnap on Unsplash
熊猫是的。我相信任何有经验的数据科学家都会同意我的观点。在你所有幻想的机器学习之前,你必须彻底探索、清理和处理你的数据。为此,你需要熊猫。
本指南假设您已经掌握了一些非常基础的 python 知识,并且已经安装了 python。如果没有,就在这里 安装 。我们也在 Jupyter 笔记本 上跑熊猫。确保你有它。我将很快向你介绍熊猫的基本知识。
努力跟上。
熊猫
Pandas 官方代表“Python 数据分析库”,当今数据科学家使用的最重要的 Python 工具。
什么是熊猫,它是如何工作的?
Pandas 是一个开源的 Python 库,它允许用户以极其高效的方式探索、操作和可视化数据。它实际上是 Python 中的 Microsoft Excel。
熊猫为什么这么受欢迎?
- 它很容易阅读和学习
- 它速度极快,功能强大
- 它与其他可视化库集成得很好
- 它同时是黑人、白人和亚洲人
熊猫带什么样的数据?
熊猫可以接受各种各样的数据,最常见的是 csv、excel、sql 甚至一个网页。
我如何安装熊猫?
如果你有蟒蛇,那么
conda install pandas
应该能行。如果没有,请使用
pip install pandas
既然你知道自己会陷入什么样的境地。
让我们直接开始吧。
导入
import pandas as pd
import numpy as np
数据帧和系列
系列像列,而数据帧是你在熊猫中完全展开的表格。你将会经常接触到这两个部分。所以要习惯它们。
从 a: 创建您自己的系列
- 目录
test_list = [100,200,300]
pd.Series(data=test_list)

- 词典
dictionary = {‘a’:100,’b’:200,’c’:300}
pd.Series(data=dictionary)

从 a: 创建您自己的数据框架
- 目录
data = [[‘thomas’, 100], [‘nicholas’, 200], [‘danson’, 300]]
df = pd.DataFrame(data, columns = [‘Name’, ‘Age’])

- 词典
data = {‘Name’:[‘thomas’, ‘nicholas’, ‘danson’, ‘jack’], ‘Age’:[100, 200, 300, 400]}
df = pd.DataFrame(data)

通常,我们不会创建自己的数据框架。取而代之的是,我们通过导入数据到一个数据框架中来研究、操作和可视化熊猫中的数据。
熊猫可以阅读多种格式,但常用的是 csv。这里是熊猫可以读取的文件类型的官方列表。

我们将在这里导入 泰坦尼克号数据集 。
df = pd.read_csv(filepath)

该数据集的主要目的是研究影响泰坦尼克号上人员生存能力的因素。让我们通过研究数据来找出确切的答案。
导入数据后,您想体验一下。下面是一些基本操作:
数据集摘要
df.info()

仅通过这个命令,我们就可以知道行数和列数、列的数据类型以及它们是否存在空值。
每列的唯一值
df.unique()

df.unique()命令允许我们更好地理解每一列的含义。看着幸存的和性列,他们只有 2 个唯一值。这通常意味着它们是分类列,在这种情况下,应该是真或假的幸存和男性或女性的性别。
我们还可以观察到其他分类列,如abowed、 Pclass 等等。我们真的说不清 Pclass 代表什么,让我们多探索一下。
数据选择
在熊猫中选择你想要的数据是极其容易的。
假设我们现在只想查看 Pclass 列。
df[‘Pclass’]

我们观察到,这 3 个唯一值实际上是 1、2 和 3,分别代表 1 级、2 级和 3 级。我们现在明白了。
我们也可以一次选择多个列。
df[[‘Pclass’,’Sex’]]

这个命令对于包含/排除您需要/不需要的列非常有用。
由于我们不需要姓名、乘客 id 和车票,因为它们在决定乘客是否幸存时根本不起作用,所以我们在数据集中排除这些列,这可以通过两种方式完成。
df= df[[‘Survived’,’Pclass’,’Sex’,’Age’,’SibSp’,’Parch’,’Fare’,’Cabin’,’Embarked’]]ordf.drop(['PassengerId', 'Name', 'Ticket'], axis = 1, inplace=True)

inplace=True 参数告诉 Pandas 将您想要做的事情自动分配给原始变量本身,在本例中是 df。
我们也可以通过行选择数据。假设我们想要调查数据集的第 300 到 310 行。
df.iloc[500:511]

它的工作原理就像一个 python 列表,其中区间的第一个数字(500)是包含,区间的最后一个数字(511)是不包含。
条件选择
我们也可以通过数据过滤。假设我们只想观察男性乘客的数据。
df[df['Sex'] == 'male']

命令df[' Sex ']= ' male '将为每一行返回一个布尔值。在它上面嵌套一个 df[]将返回男性乘客的整个数据集。这就是熊猫的工作方式。
这个命令对于未来可视化数据非常强大。让我们把到目前为止所学的结合起来。
df[[‘Pclass’,’Sex’]][df[‘Sex’] == ‘male’].iloc[500:511]

使用该命令,我们只显示男性乘客的 Pclass 和 Sex 列的第 500 到 510 行。试着使用这些命令,以便更好地使用它们。
聚合函数
既然我们知道如何浏览数据,是时候对其进行一些聚合了。首先,我们可以使用 describe 函数找出数据集的分布、最大值、最小值和有用的统计数据。
df.describe()

请注意,这些数学命令中仅包含数字列。其他列将被自动排除。
我们还可以运行以下命令来返回各自的聚合,请注意,聚合也可以通过条件选择来运行。
df.max()
df[‘Pclass’].min()
df.iloc[100:151].mean()
df.median()
df.count()
df.std()
我们还可以对这些列做一个相关矩阵,找出它们之间的关系。
df.corr()

看起来对乘客生存能力有贡献的前 3 个数字列是票价、p 等级和 Parch ,因为它们相对于幸存列拥有最高的绝对值。
我们还可以查看每列的分布。在表格格式中,更容易解释分类列的分布。
df[‘Sex’].value_counts()

看起来我们的乘客都是男性。
数据清理
如果你已经跟上了,你现在已经对我们的数据有了很好的感觉。你想做一些汇总/可视化来展示你的发现。在那之前,你通常需要在做任何事情之前清理你的数据集。
您的数据集通常包含脏数据,如:
- 空值
- 空值
- 时间戳不正确
- 更多更多
当我们使用命令 df.info()时,我们已经知道是否有任何空值。这里还有一个:
df.isnull().sum()

我们可以看到在年龄、舱和上船列中有空值。当这些特定列中的值为空时,您可以看到这些行的样子。
df[df[‘Age’].isnull()]

由于某种原因,除了年龄和舱室之外的所有其他数据都在这里。
空值的处理完全取决于你。为了简单起见,我们将删除所有包含空值的行。
df.dropna(inplace=True)or#df.dropna(axis=1, inplace=True) to drop columns with null values
如果我们愿意,我们也可以用一个值替换所有的空值。
df[‘Age’].fillna(df[‘Age’].mean())
此命令用年龄列的平均值替换年龄列中的所有空值。在某些情况下,这是处理空值的好方法,因为它不会扰乱值的偏斜度。
分组依据
所以现在你的数据是干净的。我们可以开始通过小组探索数据。假设我们想要找出每个 Pclass 中一名乘客的平均值/最大值/最小值
( 在此处插入数字列)。
df.groupby(‘Pclass’).mean()
or
df.groupby('Pclass')['Age'].mean()

这告诉了我们很多东西。首先,它告诉我们每个等级的平均年龄和票价(T21)。看起来头等舱的票价是最贵的,而且是最老的。有道理。
我们也可以对组进行分层。假设现在我们还想知道每个p 阶级的平均年龄和费用对于男性和女性。
df.groupby([‘Pclass’,’Sex’]).mean()

我们只需通过 GroupBy 函数传递组列表就可以做到这一点。我们可以根据这些复杂的分组开始分析并作出某些假设。我能做的一个观察是,一等舱的女性的平均票价是所有乘客中最高的,他们也有最高的生存能力。另一方面,三等舱
中雄的平均票价最低,生存能力最低。****
我想钱确实能拯救生命。
连接和合并
经过一些分析后,我们发现需要在数据中添加一些行/列。我们可以通过连接和合并数据帧轻松做到这一点。
我们通过以下方式添加行:
first_5 = df.head()
last_5 = df[178:]
combined = pd.concat([first_5,last_5], axis = 0)

请注意,连接时,数据帧之间的列必须匹配。它就像 SQL 联合一样工作。
如果我们想通过连接来添加列。我们合并了。
假设我们有一个包含乘客体重和身高的外部数据集。为此,我将为 3 名乘客创建自己的数据集。
data = [[‘Braund, Mr. Owen Harris’, 80, 177.0], [‘Heikkinen, Miss. Laina’, 78, 180.0], [‘Montvila, Rev. Juozas’, 87, 165.0]]
df2 = pd.DataFrame(data, columns = [‘Name’, ‘weight’, ‘height’])

现在,我们想将体重和身高数据添加到我们的泰坦尼克号数据集中。
df3 = pd.merge(df,df2, how=’right’, on=’Name’)

merge 的工作方式与 SQL joins 完全一样,使用 left、right、outer 和 inner 方法。我在这里使用了 right join 来只显示记录了体重和身高的乘客。如果我们使用左连接,结果将显示完整的 titanic 数据集,其中没有身高和体重。
df3 = pd.merge(df,df2, how=’left’, on=’Name’)

数据操作
我们现在进入了更高级的领域,我们希望对数据中的值进行大量的操作。我们可能想要添加我们自己的自定义列,更改值的类型,甚至在数据中实现复杂的函数。
数据类型
这是一个相当直接的操作。有时我们可能想改变数据的类型,为其他函数提供可能性。一个很好的例子是将一个字符串的‘4000’改为一个整数‘4000’。
让我们把现在浮点型的重量数据改成整数。
df2[‘weight’].astype(int)

请注意, astype() 函数遍历数据的每一行,并尝试将数据类型更改为您提到的任何类型。因此,每种类型的数据都有一定的规则可循。
例如:
- 数据不能为 null 或空
- 当将浮点数改为整数时,小数必须是. 0 形式
因此, astype() 函数有时会很棘手。
应用功能
应用功能是熊猫最强大的功能之一。
它让你以任何你想要的形式操作数据。以下是方法。
步骤 1:定义一个函数 假设我们想要将 Pclass 重命名为它们的实际名称。
1 =一级
2 =二级
3 =三级
我们首先定义一个函数。
def pclass_name(x):
if x == 1:
x = '1st Class'
if x == 2:
x = '2nd Class'
if x == 3:
x = '3rd Class'
return x
步骤 2:应用函数 然后我们可以将该函数应用于 Pclass 列的每个值。
该过程将通过函数传递 Pclass 列的每条记录。
df3[‘Pclass’] = df3[‘Pclass’].apply(lambda x: pclass_name(x))

Pclass 列的记录现在已经更改。如何处理你的数据完全取决于你的想象力。这里是你发挥创造力的地方。
自定义列
有时,我们希望创建自定义列,以便更好地解释我们的数据。例如,让我们用乘客的身体质量指数创建一个列。
身体质量指数的公式是:
weight(kg) / height²(m)
因此,让我们先把我们的身高值从厘米转换成米和平方的值。我们将使用我们刚刚学过的 apply 函数来实现这一点。
def convert_to_bmi(x):
x = (x/100)**2
return xdf4[‘height’] = df4[‘height’].apply(lambda x: convert_to_bmi(x))

然后,我们创建一个新列,其中的值是体重除以身高。
df4[‘BMI’] = df4[‘weight’]/df4[‘height’]

如果我们有所有乘客的体重指数,我们就可以用身体质量指数得出进一步的结论,但我不会深入研究。你明白了。
定稿和导出
完成所有工作后,您可能希望对列进行排序和重命名。
df3.rename(columns={'BMI':'Body_Mass_Index','PassengerId':'PassengerNo'}, inplace = True)df3 = df3.sort_values('Body_Mass_Index')

我们将身体质量指数重新命名为它的全名,并在这里按身体质量指数排序。
注意,排序后,索引不变,这样信息不会丢失。
最后但同样重要的是,
我们可以将我们的数据导出为多种格式(参考顶部的列表)。
让我们将数据导出为 csv 文件。
df3.to_csv(filepath)
结论
如果你已经走了这么远,给自己一点鼓励。
你已经正式掌握了熊猫的基本知识,
每个数据科学家的必备工具。
今天你所学的知识:
- 创建和导入数据框架
- 数据帧的汇总功能
- 在数据框架中导航
- 价值的聚合
- 清理数据
- 分组依据
- 连接和合并
- 数据操作(应用功能)
- 定稿和导出
让它留在你的脑海里,因为我发誓你以后会经常用到它。
一如既往,我引用一句话作为结束。
你可以有没有信息的数据,但你不能有没有数据的信息——丹尼尔·凯斯·莫兰
订阅我的时事通讯,保持联系。
也可以通过 我的链接 注册一个中等会员来支持我。你将能够从我和其他不可思议的作家那里读到无限量的故事!
我正在撰写更多关于数据行业的故事、文章和指南。你绝对可以期待更多这样的帖子。与此同时,你可以随时查看我的其他 文章 来暂时填补你对数据的渴望。
感谢 阅读!如果你想和我取得联系,请随时通过 nickmydata@gmail.com 联系我或者我的 LinkedIn 个人资料 。也可以在我的Github中查看之前写的代码。
用于数据科学的 Python:从头开始(第一部分)

Fig.1: Mr. Van Rossum in 2014
众所周知,了解 Python 对于数据科学来说是必不可少的。Python 有广泛的库,允许我们轻松地执行核心数据分析活动。为了不浪费更多的语言来证明数据人员了解 Python 有多重要,我们将直接进入冰冷的海洋。跟着我直到底部!😉。
本文是 Python 数据科学系列的第一篇,该系列涵盖了与 Python 相关的所有内容,从基础到“酷”的东西。在这篇特别的文章中,将包括以下几点:
- Python 简介
- 安装 Python 和 Jupyter 笔记本
- Python 基础知识
第一部分:简介
首先,我们将从 Python 的简介开始。Python 于 1991 年由 Python 软件基金会发布。
由吉多·范·罗苏姆创作,以 20 世纪 70 年代的英国喜剧系列《巨蟒剧团的飞行马戏团》命名。Van Rossum 被 python 社区称为“终身仁慈的独裁者(BDFL)”。在领导 python 部落约 30 年后,Van Rossum 于 2018 年 7 月卸任。谢谢你所做的一切,范罗森先生!❤
第二部分:安装和设置

Fig.2 Only two steps you need to do to install python.
要下载 Python,去这个链接。打开。exe 文件下载后,做 2 个步骤(在图 2)和 Ta-da!python 已安装在您的系统中。✌🎉。如果您不检查步骤 1,您将不得不手动添加 python 到 PATH 环境变量。有关设置环境变量的正确文档,请遵循本指南的第 3.6.1 节。但还是要确定一下,让我们交叉检查一下我们的安装。打开命令提示符,只需键入以下命令。
python -V
您应该看到您在系统中安装的 python 版本,在我的例子中是 Python 3.7.2。让我们继续安装 Jupyter 笔记本。
Jupyter Notebook:Jupyter Notebook 是一个开源平台,可以在一个 web 文档中编写和解释 python 代码以及一些解释。Jupyter 因其支持的三种语言而得名,即 Ju lia、 Pyt hon 和 R 。
- 要安装 Jupyter notebook,只需点击链接并下载 anaconda(在这里可以找到更多关于 anaconda 的信息)。遵循顺序:下一步- >同意- >安装。
- 第二种方式是使用 CMD 和 pip (Python 的包管理器)进行安装。首先,确保你有最新的 pip 旧版本可能在某些依赖项上有问题:
pip3 install --upgrade pip
然后使用以下方法安装 Jupyter 笔记本电脑:
pip3 install jupyter
第三部分:基础知识
安装完 Python 和 Jupyter Notebook,我们来深入了解一下 Python 的基础知识。首先,我们在系统中打开 jupyter 笔记本。因此,要打开笔记本,请在 CMD 窗口中键入“ jupyter notebook ”。

Fig. 3 Initialising Jupyter
同时,您将在默认网络浏览器中看到一个如图 4 所示的屏幕,单击“文件夹”创建一个目录来保存您将在本教程中创建的所有笔记本。让我们把这个文件夹命名为“python 基础”。点击“python 3 ”,在当前目录下创建一个新的 python 文件。

Fig. 4 Jupyter’s Landing Page
只要您单击文件夹选项,就会创建一个名为“无标题文件夹”的空文件夹。我们重新命名吧。遵循图 5 中的两个步骤。

Fig 5.renaming the folder

Fig 6. Opening a python file..Finally!

Fig. 7 A fresh Python Notebook
重命名文件夹后,在文件夹内导航并创建一个新的 python 文件。这个新创建的文件看起来如图 7 所示。继续编写一个打印命令,向 print()函数添加任何语句,如下面的代码所示。在编写了第一个 Python print 语句之后,继续使用键盘上的 CTRL + Enter 键运行 print 命令。
要在当前单元格下添加新单元格,只需点击“In [ ]”并按键盘上的“B ”,一个新单元格将被创建。要在当前单元格上方创建一个单元格,请重复上述步骤,但按“A”键。重命名您的笔记本并保存它!
你应该在这些单元格中运行下面的所有代码。你练习得越多,你对 Jupyter 笔记本电脑就越熟练。
第 3.1 部分。缩进:你首先要知道的是 python 使用空格来结构化代码。许多其他语言如 Java、C++使用括号来显示每个特定代码的范围细节。但是在 python 中,它看起来像下面这样:
在给定的代码中,声明《摩诃婆罗多》中的角色名称列表。然后我们遍历列表,注意冒号表示后面是一个缩进块,这个缩进块下的每个语句都有相同数量的空格。添加缩进块不仅使代码可读性更好,也使经常搞砸偶数括号的编码者更容易理解。
第 3.2 部分。理解 Python 中的数据类型:有效的数据驱动洞察需要对我们正在处理的数据类型及其在内存中的存储方式有透彻的了解。
Python 支持整数、字符串、布尔数据类型。Python 的一个重要特点是它是一种动态类型语言。动态类型化是语言本身推断给定变量的类型。另一方面,静态类型是指在使用变量之前必须声明变量的类型。
简而言之,动态类型意味着我们可以将任何类型的数据赋给任何变量,而不用担心声明什么类型的数据或它的兼容性。为了更好地理解,请参考 Python 和 c 中的两个赋值示例。我们可以很容易地在 Python 中重新声明给定变量的值。但是在其他语言中,它会导致编译错误或其他意想不到的结果。
但是这种级别的灵活性也指出了一个事实,即 Python 变量不仅仅是它们的赋值。这意味着每个 Python 对象只是一个巧妙伪装的 C 结构(因为标准的 Python 实现是用 C 编写的)。

Fig . 8 Structure of a Python Integer
假设我们用 Python 声明一个整数,比如 x = 999,x 不仅仅是一个‘原始’整数。它实际上是一个指向包含几个参数的复合 C 结构的指针。这些参数如图 8 所示。因此,我们得出结论,python 整数是指向内存中包含所有 Python 对象信息的位置的指针。同样的逻辑也适用于其他数据类型。
第 3.3 部分。控制流:和其他一些语言一样,Python 也有一套简单而结构化的数据流方式。
if -block 是检查的第一个条件语句,如果发现为真,则执行其下的缩进块。
elif-block 代表 else-if 语句,即如果 if 语句不为真,则检查该语句。可以有多个 elif 块,但是如果没有一个满足条件,则执行 else 块。
接下来,我们讨论循环结构。我们讨论两个循环语句: for loops 当我们想要遍历一个列表、元组或者迭代器时使用。一个 while 循环指定了一个条件和一个要执行的代码块,直到该条件结束为 FALSE 或者遇到 break 语句。通过下面的例子来弄清楚它的用法。
第 3.4 部分。函数:函数是 Python 中代码表示和重用的主要和最重要的方法。有时候,我们需要经常使用某组语句。函数是一种使代码可读性更好的方法,它为一组您希望经常重用的语句命名。例如,如果您想将数据集中所有合适的列的所有“Null”值替换为 999,只需编写一个执行相同操作的函数,并每次使用新的列名进行重复。

Fig 9. A function Representation.
- 使用 def 关键字声明一个函数,使用 return 关键字返回结果。
- 函数声明中提到的参数(x,y)是局部变量。它们的作用域仅限于函数体,也就是说,如果我们想在函数体外使用变量(x,y ),就会产生错误。
- 可以有多个 return 语句和多个 return 变量。如果解释器没有遇到 return 语句,并且到达了代码的末尾,则简单地返回“None”。在数据分析中,你会发现自己经常会返回多个变量。所以它背后的技巧是,函数只返回一个包含所有响应变量的对象,然后这个对象被解包并赋给声明的变量。
- 可以有两种类型的参数,位置参数和关键字参数。关键字参数主要用于指定默认值或可选参数。位置参数根据它们作为参数传递的顺序工作。
- 如果同时使用关键字和位置参数,请始终将关键字参数写在位置参数之后。
- 您可以按任意顺序指定关键字参数,这样您就不用再去记忆函数参数的顺序了。
Lambda 函数: Python 支持所谓的匿名或 Lambda 函数,这是一种编写只有一条语句的函数的方式,输出返回值。它们被定义为带有λ关键字,意思就是“我在声明一个匿名的单行语句!”。与编写函数声明相比,传递 lambda 函数通常需要较少的输入。
这就是我们系列的第一部分的结尾。我真诚地希望,到目前为止,本文提供的信息对于 Python 的基础知识来说已经足够了。如果你现在还舒适地坐在你的椅子上,我会敦促你留在船上,直到下一篇文章到来,那时真正的挑战和“酷”的事情将最终开始出现!如果有任何更正或疑问,请随时添加您的回复。下次见,学习者!
用于数据科学的 Python:从头开始(第二部分)
学习 Python 中的数据结构和重要包,如 Numpy 和 Pandas。

One word for this epic shot: PERSEVERANCE!
本文是 Python 数据科学系列的第二部分。如果您还没有阅读 Python 的介绍(第 1 部分),请继续阅读那篇文章这里。了解了基础知识之后,是时候沉迷于更具挑战性的 Python 主题了。在本文中,我们将研究 Python 在数据表示和操作方法中的应用。将涵盖以下主题:
- 数据结构:元组、列表、字典。
- 套餐 : Numpy,熊猫。
剧透提醒:文末可以期待奖金啦!🤫
第一部分。数据结构
Python 提供了简单而熟练的数据结构,帮助数据人员根据需要表示、操作和存储不同形式的数据。让我们一个一个熟悉这些微妙的结构。
1.1 元组:
tuple 是 Python 对象的一个不可变的固定长度序列。
- 您可以简单地用逗号分隔的值序列创建一个元组。并不总是强制的,但是建议将序列用括号括起来。
- 像许多其他语言一样,默认索引从 0 开始。
- 可以通过在方括号“[<index_number>]”中声明所需的索引号来访问元组的元素</index_number>
- 您还可以创建一个称为嵌套元组的元组。
- 如果您有一个想要转换成元组的字符串或序列,只需使用 tuple()方法。
元组操作:元组是不可变的,即一旦定义了元组的内容,就不能修改。
- 虽然存储在元组中的对象本身可能是可变的,但是元组本身是不可变的。
- 如果元组中的对象是可变的,比如列表,那么可以就地修改它。
- 您可以使用“+”运算符连接两个元组。这个操作符简单地将一个元组附加到另一个元组的尾部。
- 将一个元组与一个整数相乘只是将该元组的副本数量连接起来。
解包元组:解包时。这些值被分配给最右边的相关变量。
- 考虑旁边显示的例子,值被逐个分配到变量序列中。
- 嵌套元组可以类似地解包。如果您没有在正确的括号中表示变量,嵌套元组的一个括号中的元组值将被分配给单个变量。
- rest 运算符用于强调几个变量的赋值。剩余运算符负责值的剩余。查看示例,更清楚地了解它是如何工作的。
- 最后一个是 count()方法,顾名思义,它是用来统计重复元素的频率的。
1.2 列表:
列表是可变长度和可变的数据结构。
- 您可以使用方括号[]或使用 list 类型函数来定义列表。
- 和在 tuple 中一样,列表中的索引也是从零开始,到列表的长度结束。
添加和删除元素:
- 可以使用追加功能将元素追加到列表的末尾。
- 使用插入功能,您可以在列表中的指定位置插入元素。insert 函数往往比 append 函数开销更大,因为解释器必须在内部移动前面的元素,以便为当前元素腾出空间。
- 或者,如果您只是想修改列表中的一个元素,那么您可以按照代码中的描述就地修改它。
- 关于删除元素,与插入功能相反的是弹出方法。
- 您只需要指定您想要弹出的元素的索引,它将从列表中删除,将被删除元素的右边的元素移动到左边。
- 假设列表很长,并且您不知道要删除的值的索引,您可以使用 remove 关键字定位第一个这样的值并将其从列表中删除。
组合并连接列表:
与元组类似,用+将两个列表加在一起会将它们连接起来。或者,如果已经定义了一个列表,可以使用 extend 方法追加多个元素。使用扩展运算符时,请确保将值放在方括号内。
切片:切片是根据您的选择,在您定义的列表中选择部分。

Remember these indices to understand slicing!
-
您可以通过指定切片符号从列表中提取数据块,切片符号的基本形式是 start_index:stop_index 传递给索引操作符[]。
-
T 开始索引包含在输出中,而停止索引不包含在输出中,因此结果中的元素数量为停止-开始。
-
可以省略开始或停止索引,在这种情况下,它们分别默认为序列的开始和序列的结束。
-
负索引相对于结尾分割序列。
-
使用双冒号打印“每隔一个”,比如 my_list[::2]打印 my_list 的“每隔一个元素”。
-
使用双冒号像 python stud 一样打印列表的反面(见代码)!😎
注:练刀法真的很难。你练习得越多,你对指数的理解就越好。而负索引总是记住以'-1 '开始最后一个元素,并继续到第一个元素。
1.3:格言:
Python 中的 dict 或字典是一种无序的、可变的和索引的数据结构。它被认为是 Python 中最重要和最详细的数据结构。
-
Python 中的 dict 或字典是一种无序的、可变的和索引的数据结构。
-
数据在字典中表示为 key: value 格式。这里的键和值都是 python 对象。
-
每个键都有一个值或分配给它的值列表。
-
每个键、值对都应该用逗号分隔。
-
你可以用花括号“{}”创建一个空字典。
-
如果你想访问一个特定的值,只需在你的字典的方括号中传递键数据。
-
如果您有一个类似 my_dict 中的“Cast”键的值列表,并且如果您想要访问列表中的某个特定元素,只需在方括号中的键声明后使用方括号中的索引值,例如:my _ dict[“Cast”][0]
-
要在字典中添加一个新的 key: value 对,只需使用方括号添加一个新的键,并将给定的值赋给它。
-
使用关键字中的来检查字典中是否存在某个元素。<在<字典名称>中的>处键入您的密钥。
-
如果字典中存在给定的键,则输出为真,否则为假。
-
要从字典中删除一对,可以使用 del 关键字。该关键字删除整个密钥记录。如果你不指定一个键,整个字典将被删除。
-
第二个选项是使用 pop 方法,该方法从字典中移除键,并返回分配给该键的值。
-
下面是一些处理字典时可能会很方便的通用方法:
在这里,我宣布我们已经研究了 Python 中用于分析的基本数据结构,这些数据结构不需要导入包。接下来,我们将重点关注一些已知对分析非常有帮助的重要包。
第 2 部分:Python 中的包
Python 是一个开源项目,有成千上万的开发者贡献,他们上传了大量的包,帮助我们通过简单地导入和使用该包来执行活动。下面是对其中几个包的研究。
2.1.NumPy:
Numpy 是数值 Python 的缩写,是 Python 中最重要的数值计算包之一。大多数提供科学功能的包使用 NumPy 的数组对象作为数据交换的通用语言。
在某些方面,NumPy 的数组对象很像 Python 的内置列表类型,但当我们处理海量数据时,NumPy 是赢家。无论您对数据科学的哪个领域感兴趣,深入了解 NumPy 最终都是有价值的!
- Numpy 的关键特性之一是 ndarray 或 N 维数组对象,它是 Python 中大型数据集的快速、灵活的容器。
- 数组使程序员能够对具有相似语法的整个数据块执行数学运算。
- 让我们首先导入 Numpy 包并生成一个小的随机数数组。
- ndarray 是一个通用的多维容器,用于存储同类数据,即所有数据点都应该是同一类型。
- 创建数组最简单的方法是使用 array 函数。这个函数接受任何类似序列的对象,并产生一个包含传递数据的 Numpy 数组,例如一个列表。
- 嵌套序列,像等长列表的列表,将被转换成多维数组。
- 由于代码示例中的列表是列表的列表,因此 NumPy 数组有两个维度,其形状是从数据中正确推断出来的。
- 除了 np.array 函数之外,还有许多其他函数可以用来创建新的数组。请浏览 Numpy_intro.py 中的给定内容,了解声明数组的各种方法。
Numpy 数组属性:
让我们从一些用于获取数组信息的基本数组属性开始。首先,我们将创建三个数组,一维、二维和三维随机数组。
- 我们设置了种子,这样每次运行代码时都会生成相同的随机数组。
- 使用 randint 生成低(含)到高(不含)之间的随机整数。格式为 numpy.random.randint(低,高,大小,dtype)。
- 现在是时候深入研究数组的一些属性了, ndim 属性,用作< array_name >。ndim,产生维数。
- 然后使用 shape 属性我们可以找到每个维度的大小,而 size 属性决定了整个数组的大小。
访问数组元素:如果你熟悉 Python 的列表索引,Numpy 中的索引会觉得相当好理解。在一维数组中,通过在索引操作符中指定第个索引,即[],可以从 0 开始访问第个值。对于多维数组,您应该使用逗号分隔的索引元组来指定元素的详细位置。索引和切片在执行时最容易理解。我敦促你练习并思考以下要点中给出的每个例子。
2.2 熊猫:
Pandas 是 Python 的一个开源库,专门用于数据操作和分析大量数据。Pandas 提供了健壮的数据结构和函数来轻松操作数据。

Photo by Debbie Molle on Unsplash
但是等等,那也是 lists,dict 和 Numpy 的 ndarrays 能做的,那么为什么是熊猫呢?嗯,有很多正当的理由。其中之一是,在大多数情况下,数据人员将获得 csv 文件格式的数据。Pandas 非常出名,它可以很容易地从这些 csv 文件中自动识别列标题。总之熊猫就是牛逼!🐼🤗Pandas 中的两个主要数据结构是序列(一维)和数据帧(二维)。
首先,我们可以通过调用 import 方法来使用 Pandas 库
import pandas as pd
2.2.1 系列:
系列是索引数据的一维数组,可以保存任何类型的数据。轴标签统称为索引。生成系列的基本方法是:
my_series = pd.Series(data, index=index)
其中数据可以是 Python 字典、ndarray 甚至是标量值(比如 7),索引可以是轴标签列表。因此,我们将根据数据是什么来研究一些情况
a .来自 ndarray:
如果使用 ndarray 作为序列的数据,则索引的长度应该相同。如果没有定义索引,将创建一个缺省索引,其值为[0,…,len(data)-1]。
b .从字典:
正如我们之前了解到的,字典是关键:数据的值表示。我们可以用一个字典来定义一个系列对象。在这里,如果我们不传递索引值,dict 定义中提到的“key”将被用作索引。key 属性出现在索引中的顺序与字典中定义的顺序相同。另一方面,如果索引是在序列定义中定义的,那么只有对应于 dict 中使用的键的数据才会被提取出来。剩余的索引标签将被记录为 Nan,而不是数字,这是缺失数据的标准数据标记。
c .从标量值:
如果数据是标量值或常量,那么我们肯定需要指定一个索引。相同的常数值将被复制到对应于每个索引点的索引长度。
下面的笔记本展示了一个特定函数的列表,这些函数提取关于 series 对象的信息。
2.2.2。数据帧:
数据帧是具有不同数据类型的列的二维索引数据结构。它可以被认为是一系列共享相同索引的字典。数据帧是最方便的,因此被广泛使用的数据结构。您可以将数据帧视为电子表格或 SQL 表。像 Series 一样,我们可以通过多种方式构建数据帧,如下所示:
data = pd.Dataframe(data,index,columns)

A Dataframe
其中可以选择指定您选择的索引和列。否则,默认索引为[0…(len-1)]作为所有列的共同索引。在给出的图表中,我们将索引指定为 0 到 4,而列是书名、作者、阅读完了吗?。表格中的所有内容都是数据。
你可以从可能的其他数据结构中创建一个数据帧(在这里继续学习数据帧的构造)。为了让我们的研究与数据科学相关,我向您保证,您将使用 dataframes 作为大型。csv 文件,。数据文件或任何其他存储数据的文件格式。因此,我们从您可能使用的最基本的文件格式开始,即逗号分隔值以及如何加载这样的文件。
data = pd.read_csv("path_to_file\file_name.csv")

A CSV file loaded into a dataframe
我从 kaggle 下载了一个数据集,并使用上面的语法读取它。读取后的数据集看起来像旁边的图。让我们从一些有助于分析数据帧的基本命令开始。
索引和选择数据:
这一节的主要目标是如何切片、切割以及获取和设置熊猫对象的子集。就像 Numpy 数组一样,我们可以选择和索引数据,唯一的区别是在 Series 和 Dataframe 中我们可以使用一个 list 甚至一个 slice 标签。
dataframe[0] #select the zeroth index data
dataframe[0:10] #select data from zeroth to ninth index
dataframe[[0,5,6,4,7]] #select specific index in a list
dataframe[dataframe.SOP > 3.0] #select all rows where SOP > 3.0


我们使用。iloc 关注基于整数的位置或数据位置,从 0 开始直到任一轴的长度-1。但是它也可以与布尔数组一起使用。如果 iloc 发现提到的索引或索引片段超出界限,它将引发 IndexError。如果我们只打算使用列选择,在行选择中使用冒号操作符是很重要的。
另一方面,loc 用于基于标签的数据提取。我们必须指定实际的标签或索引名和列名作为参数。
我们可以选择这些列的一部分,甚至是一个布尔数组。当找不到项目时,loc 将引发一个 KeyError。如果是整数,我们可以将行索引指定为 0 到 length -1。请注意,这里的整数不是一个位置,而是一个“标签”。对于列,我们应该使用各自的列名,比如“列 1”、“列 2”等。
从轴上放下数据:
我们可以使用 drop 方法从数据帧中删除特定的数据条目。这个 drop 方法从对象中删除指定的数据点,并且不修补原始数据对象,它只是返回一个反映删除内容的新对象。
在 dataframe 中使用 drop 方法时,我们可以通过简单地在引号中引用值来删除行中的值。否则,如果我们想要清除某个特定的列,我们必须指定一个额外的参数, axis =1 或 axis =' columns '。(轴=0 暗示行数)
每当我们使用 drop 时,数据都反映在一个新的对象中,同时保护原始数据。如果你想修改原始数据,使用参数 inplace=True。使用 inplace 时要小心,因为它会永久删除原始对象中的数据。
分组方式:
pandas 中的 GroupBy 函数类似于 SQL 中的 group by。它对整个数据帧进行分组,只考虑一个特定的列。以这种方式对数据帧进行分组后,我们可以对这个集合数据执行各种操作,如最小值、最大值或寻找平均值。任何 groupby 操作都涉及对原始数据对象的下列操作之一:
- 根据某些标准分割数据。
- 将函数独立应用于每个组。
- 将结果组合成数据结构。
使用数据透视表整形:
创建电子表格样式的数据透视表作为数据框架。数据通常以所谓的“堆叠”或“记录”格式存储在 CSV 文件或数据库中。同样,我们可以使用 pivot 函数快速汇总我们的数据。为了理解数据透视表的应用,我决定使用一个实际的数据集。所以,请点击这里,从 Kaggle 下载黑色星期五数据集。该数据集记录了超过 50,000 条消费者在黑色星期五购物的记录。假设你想知道每个年龄组的男人和女人的总购买量是多少?或者说不同城市类别的男女所做的均值购买是怎样的?
简单,用下面的代码画一个数据透视表,


pivot_table 的语法有四个重要参数,一个是值,它是要聚合的列或列列表。索引和列可以是一个单独的列,也可以是一组列。 aggfunc 是应用于聚合数据的函数或函数列表。我们可以在聚合中使用均值、最小值、最大值、总和、中值和更多函数。
处理缺失数据:
很多时候,我们必须处理的数据包含缺失值,也就是说,这些数据要么从未被记录,要么根本不存在。我们所说的“丢失”是指 NA(“不可用”)。虽然 NaN 是默认的缺失值标记,但我们需要能够轻松地检测各种不同类型的值:float、int、boolean、general object。然而,在许多情况下,有可能得到' None ',这也应被视为缺失或不可用。
- isna()检查给定的数据对象是否缺少值。如果有任何 nan 值,则该命令返回 True,否则返回 False。
- 同样,notna()检查真值或实际值。如果找到任何真数据,则返回 true,否则返回 false。
- 我们使用 value_counts()方法来计算每种情况下的实例数。
- 有时在处理数据时,需要用实际数据来填补缺失的数据,所以我们使用 fillna()来填充缺失的值,然后继续我们的分析。我们可以用相关数据对象的平均值、众数或中位数来代替 nans。
- 有时,当数据有太多缺失值或 nan 值时,建议忽略整个数据对象。为此,我们使用 dropna()。dropna()方法有三种用法,如下面的要点所述,参数 axis=0 会删除整个数据集中有 nan 值的所有行。axis = 1 删除所有具有 nan 值的列。在 dataframe 定义中使用特定的列名会删除整个列,而不会影响相邻的数据点。
- interpolate()方法用于将有意义的数据插入缺失值中。请参考本文件,以便进一步阅读。
连接数据对象:
通常需要组合或连接两个系列或两个数据帧。在本节中,您将学习做同样的事情。当我必须将训练和测试样本合并到一个单一的数据框架中时,我个人会使用这种方法。因此,请在您自己的笔记本上练习以下命令。
一篇很长的文章,不是吗?但我希望它能提供丰富的信息,并且易于理解。一个人可以阅读成千上万篇文章并继续前进。然而,对事物的真正理解来自于我们的实践。我坚持要求你在你的系统中彻底练习上述 gists 中的例子。
正如所承诺的,我已经创建了一个 Github 存储库,其中包含了上面解释的每个主题的问题及其各自的解决方案。克隆存储库,练习,练习,再练习!
在这里找到仓库。
欢迎指正或讨论(也欢迎赞美!😄).下次见,学习者!
用于数据科学的 Python:从头开始(第三部分)
构建可视化和你的第一个机器学习模型。

Iris!
在学习了 python 101 、切片和切块数据结构之后,是时候给我们的项目添加创意了。在本文中,我们将探讨数据可视化,以及它如何揭示数据最深层和最黑暗的秘密。接下来,我们将建立我们的第一个机器学习模型,以了解它到底是什么!
一.数据可视化
想象一下当你还是一个天真的小怪物时,你最喜欢的睡前故事书。你记得它曾经有小句子,但真正迷人的图片,对不对?让我们暂时改变一下那个东西,把我们最喜欢的彩色睡前故事书变成一张白纸,用黑色墨水写下故事的一行行。你就像~

I can’t do it! It’s torture.
在数据科学中,您所拥有的只是一千行数据和大量的列需要处理。想象一下,如果您必须检查每一条记录并理解它在数据集中的重要性,会有多可怕。但是,如果您将数据可视化或绘制成最适合的图表,您可以在一秒钟内获得每个数据点的重要性。
数据可视化是如此糟糕,它有一个不同的专门的工作配置文件。因此,我们在这里理解了什么是可视化,甚至没有阅读一个无聊的定义!哈哈!🤫😉
Matplotlib
Matplotlib 是一个适用于 python 平台的数据可视化库。它以条形图、直方图、箱线图和其他几种基本图表的形式提供 2D 表示。首先,你应该知道你永远不会对数据可视化有足够的了解,你会一直学习和成长。还有许多其他优秀的库,比如 Plotly、Bokeh,它们给了你迷人的表现。但是理解了 matplotlib 里面的基础,然后就可以逐步掌握其他的了。
- 折线图:
我认为在实际数据上可视化比任何虚拟数据点更有意义。为此,我使用了来自 Kaggle 的印度犯罪数据集。折线图是一种 2D 表示,显示一个变量相对于另一个变量的趋势。

Fig.1. Line Chart
嗯(表示踌躇等)...2003 年前后,入室盗窃案件的数量急剧下降。我想知道后来发生了什么?也许窃贼变得足够富有并决定退休?!然后他们把他们的财富花在赌博上,所以他们不得不在 2004 年左右再次贪婪地偷窃?
Matplotlib.pyplot 库存储看起来像 Matlab 的方法。在 matplotlib.pyplot 中,保留了关于绘图图形(这里是 plt)和绘图区域的某些状态。无论可能使用什么样的进一步方法,都假定是针对这个特定的地块本身的。
- 不管怎样,plt.plot()函数绘制 X-Y 轴上的点。
- 已经给出了 x 轴和 y 轴标签。
- 最后,我们使用 show()函数在控制台上显示图表,并将命令返回给 IPython shell。
2.条形图

Fig. 2. Bar Chart
条形图用于显示分类变量的频率或度量。
在 matplotlib.pyplot 中,我们使用 bar()方法来构建图表。bar 方法需要两个强制参数,一个是 x 轴标签,另一个是这些标签的高度。
3。饼状图:

Fig.3 .Piechart
上面的条形图很棒,但我们无法计算出记录的案例中有多少比例来自哪个州。就此而言,为了清楚地了解每个州所占的百分比,我们构建了一个饼图。
这里,pie()方法强制需要两个参数,一个是标签,另一个是将分配给特定标签的大小或角度。 autopct 属性用于给饼图扇区添加标签。
4。散点图:

Fig.4. Scatter Plot
散点图用于检查变量的分布或变量之间的关系。因此,我们需要在 x 轴和 y 轴上绘制至少 2 个字段。我使用了一个随机数发生器,简单地分配给一种颜色。
5.直方图:
直方图是查看变量分布的好方法。柱或桶的概念在直方图中非常重要。容器大小定义了数据可以落入该“容器”的程度。假设容器大小为 10,则表示为 0–10、10–20、20–30 等等。现在假设容器大小为 50,则表示为 0–50、50–100 等。

Fig.5. Histogram
我们可以解释说,大多数案件是少于 2000 件物品的抢劫。
6。箱线图:
箱线图用于绘制变量的分布并检测其异常值。所有的异常值都用圆圈标出。虽然矩形框说明了数据最集中的位置,但它也说明了最小值、最大值和四分位数范围,以及实际上是中位数的线。

Fig.6. Boxplot
该箱线图表明数据严重倾斜,并且存在多个异常值。这正是直方图所传达的信息,但表达方式不同。在这里,我们对异常值及其数量有了清晰的认识,但在直方图中却不是同样清晰的图像。
在我看来,箱线图和直方图都可以用来考虑一个变量的分布。选哪个就看你自己决定了!
现在,知道这些图表就足够了。相信我,你会随机应变的!
二。机器学习:
在这个转折点上,我们已经获得了关于 python 以及如何让它为我们服务的原始知识。但是光靠知识你能做什么?!一位智者曾经说过…
知识就是知道什么是电开关。智慧就是不要把湿手指放进插座里!
这句话的意思是,在学习 Python 这么久之后,我们现在需要应用它!让我们明智一点。
什么是机器学习?
让我们回到 20 世纪 50 年代,你是一个受欢迎的有才华的演员的私人助理。现在这位著名的名人收到了成千上万封信,比如来自他的粉丝、崇拜者、导演、制片人、广告公司、演员同事等等。现在那位名人正忙于他的工作,而你的工作是在午餐时间准时给他送去所有重要和紧急的信件。因此,你整个上午都在浏览和阅读一大堆信件,并为这个大个子筛选出重要的信件。这时你会叹息说,“我希望我有一根魔杖,可以把这些字母排列起来,我就可以安静地吃我的煎饼当早餐了!唉!”。这时天使出现了,说:“如你所愿,可怜的人!”。
快进 60 年,那根魔杖就是今天的机器学习。虽然建立一个机器学习模型不像旋转魔杖那么容易,但如果做得对,结果确实是神奇的!
回到技术层面,机器学习是自动处理大量数据,从数据中学习并从中产生有价值的东西。
考虑一个垃圾邮件过滤程序,它会阻止所有包含“新人寿保险”一词的电子邮件。我们开发了一种机器学习算法来找到所有可能的单词,如“新人寿保险”,然后将它们扔进垃圾邮件箱。但是等等,我可以写一个简单的 IF-THEN 语句来完成我的工作。机器学习为什么要炫耀?
机器学习是让系统或机器像人一样思考,这样人类就不必浪费时间从收件箱中过滤愚蠢的垃圾邮件。
在现实生活中,数据从来没有如此分类和精确。每一组新数据都有惊人的差异。我们人类有区分相关和不相关的智能。我们试图将这种智能复制到机器学习模型中。机器学习模型会自我进化,并尝试从它们获得的每个新数据模式中学习。
机器学习的类型:
- 监督学习- 简单说就是跟着老师学习。
你的老师教一朵花的结构;什么是花冠,什么是花萼,花瓣等。你学习并写一个测试。老师检查答案并告诉你你表现如何。她监督你。类似地,在监督学习中,我们已经知道给定的输入变量集的结果变量是什么。我们必须根据输入数据训练模型,使其产生期望的目标。模型不断学习和适应,直到达到近乎完美的结果。
2。无监督学习:没有老师的学习。
你有没有注意到“你可能也喜欢..”关于亚马逊的部分?这是无监督学习的一个很好的例子。这里的学习是没有导师的,是用来挖掘隐藏在黄金数据矿里的黄金的。因此,当你从亚马逊购买白板时,它会运行一个模型,自动聚集所有其他相关对象,如记号笔、除尘器和其他几个对象作为推荐。如果购买对象也发生变化,这些建议也会迅速改变,对吗?看,模型并不知道推荐的是洗碗机还是空调,它只是学习并提供结果。
sci kit-学习:
S cikit-learn 是一个开源库,包含了几种让我们对给定数据进行机器学习的方法。它依赖于 SciPy 和 NumPy 库,所以在我们开始使用它之前,请确保您已经加载了所有的枪。虽然安装 Anaconda 时会安装 sklearn ,但是如果您遇到任何问题,请使用
pip install sklearn
我们将在 iris 数据集上工作,以获得更多关于 scikit-learn 的实践经验。我保证你会学到所有必要的术语以及它们的含义。
想象一下,有一个热情的植物学家,她喜欢收集鸢尾花。所以她每天早上都去野外,一边散步一边尽可能地收集所有可能的鸢尾花。然后当她回家后,她测量花瓣的长度和宽度,以及萼片的长度和宽度。根据这四种长度,她将采集到的花朵分为三种:刚毛花、杂色花或海滨花。这意味着给定一组输入的测量值,我们的植物学家就可以确定它可能属于哪个物种。我们假设野外只有这三个职业。
我们的任务是建立一个机器学习模型,它可以根据测量结果正确地告诉我们花的种类。由于我们已经知道输出将是三类虹膜中的任何一类,这是一个监督学习问题。此外,这是一个分类的问题,正如你所看到的,我们正在将一朵给定的花分类到预定义的类别(物种)中。更准确地说,这是一个三级分类问题。让我们来看看数据:
- 召唤所有必要的库如下:
你可以在这里找到 ipynb 笔记本。

Importing libraries
2.导入 iris 数据集:在 Iris 的 UCI 库或我的 Github 库的数据集文件夹中找到数据集。由于数据集本身不提供列标题,我们首先将列标签放在一个名为 columns 的列表中,然后使用 read_csv 函数加载数据集以及刚刚声明的列。

Loading the dataset
3。理解数据:
好了,我们已经将数据集声明为花瓣和萼片的测量值和一个目标类。但是对于一个门外汉来说,花瓣和萼片是什么?

Fig.7. IRIS classes
萼片是保护花蕾和保持花瓣位置的叶子。正如我们大多数人所知,花瓣是形成花朵并包围其生殖部分的修饰过的叶子。至于鸢尾花,这些长度决定了它们属于哪种物种。

Shape means dimensions of dataset
shape()方法输出数据集中的行数和列数。这里(x,y)其中 x 是行数,y 是列数。

describe returns the summary of numeric columns
describe()函数告诉我们数字变量的概要。这里我们可以看到每列的计数是 150,即。没有空值。然后我们得到每列的平均值、标准差、最小值、最大值。
4。数据可视化:最后我们用之前学过的!
4.i .单变量分析更好地理解每个变量。在这里,我们创建了一个所有测量的箱线图。


Fig. 8. Boxplot
- 我们可以看到萼片长度范围从 4 到 8.0 个单位,没有异常值
- 而萼片有一些异常值,正好三个。
- 花瓣长度和宽度没有异常值。
4.二。多变量分析检查变量之间的关系。我们使用 seaborn 包来查看这个分析。Seaborn 是另一个优秀的绘图包,它实际上是基于 matplotlib 的。

pairplot()方法绘制所有给定的 4 个测量值,并尝试在它们之间建立关系。每个测量值都要与其他测量值以及自身进行比较。

Fig. 9. Pairplot
从给定的图表中,我们可以清楚地看到,每朵花的测量值都集中在一个特定的数字范围内。因此,我们可以成功地运行分类算法,并获得所需的虹膜物种。
5。拆分数据:
在开始根据这些参数建立机器学习模型之前,我们需要一些确认,即我们作为花的“类”产生的任何输出都是正确的。但是从逻辑上来说,我们不能用训练模型的数据来测试模型。我们需要看不见的新数据,实际上有它的预测输出类变量。我们将在这些新数据上运行我们的模型,并获得结果类。如果结果类和存储的输出类相同,瞧!。
出于这个目的,机器学习的一般规则是在你的 75%的数据上训练任何模型,称之为训练数据或训练集。剩下的 25%的数据是测试数据,测试集或保留集。

Python 提供了一个内置函数来处理拆分,输入带有两个重要参数,即输入 X 列和输出 y 变量。默认的 test_size 是 0.25,即整个数据的 25%是测试数据,我们使用 random_state = 0,这使我们能够混淆数据,然后随机选择拆分。假设如果我们将最后 25%的数据作为测试数据,我们将只有 iris-virginica 输出。这对于训练来说是非常误导的,因此出现了随机分裂。
注:输入变量用大写的 X,输出变量用小写的 y,这只是惯例。
6。建立模型:
现在,我们进入最后也是最有趣的一步,建立一个机器学习模型来学习这些价值。
对于这个数据集,我们将使用 KNN 分类模型。
KNN 算法: KNN 代表 K-最近邻。你有没有听说过这样一种信念:你是和你相处时间最长的五个人的平均!用这个类比来理解 KNN 算法。KNN 考虑给定数据点的邻居,并根据这些邻居属于哪一类来决定给定数据点的类。该算法中的“K”是我们在分析中考虑的邻居数量,假设 k=3,则使用 3 个邻居。

- KNN 算法位于 sklearn.neighbors 包中,称为 KNeighborsClassifier。
- 为了使用这个算法,我们需要从给定的类中实例化一个对象。我们称这个物体为“knn”。为简单起见,我们仅使用一个邻居进行训练。别担心,我们稍后会解决这个问题。
- 使用 fit()方法,我们在训练集上训练模型。fit()方法有两个参数,一个是包含所有测量值的 X_train,第二个参数是包含这些测量值的所有标签的 y_train。
但是训练模型是不够的,我们需要测试它!

使用测试向量 X_test,我们在它上面部署训练好的 knn 模型。score()方法计算给定测试数据的平均准确度。它获取测试数据点及其测试数据标签,并输出精度。对于我们的模型,我们得到了高达 97%的准确率。拍拍你的背,伙计们!你部署了你的第一个机器学习模型。
结论:
首先祝贺你的第一个机器学习模型!几个月前,我出于同样的原因拍了拍后背😄。通过这篇文章,我总结了 Python 用于数据科学系列。这么多蟒蛇足够你一个人在波涛汹涌的大海上航行了。
我要感谢勤奋的读者,这些出版物背后的动机是通过分享我所拥有的任何微弱的知识来成长。
我相信,即使一个人能够从我的出版物中学到一个话题,我也对社区有所回报。那是我最大的成就!✌
你可以在这里找到早期的出版物:
我很高兴宣布下一个系列,即使用 Python 的机器学习,这些系列将深入涵盖 sklearn 包,我们将在现实生活的数据集上工作,使用微妙的算法解决有趣的问题。所以来见我吧,学习者们!
关注我上LinkedIn。
面向金融的 python:Robo 顾问版
扩展股票投资组合分析和 Dash by Plotly 跟踪类似 Robo Advisor 的投资组合。

Photo by Aditya Vyas on Unsplash.
利用 Python 进行股票投资组合分析的第 3 部分。
引言。
这篇文章是我关于利用Python融资的系列文章的第三部分,特别是股票投资组合分析。在第一部分中,我查看了一个 Jupyter 笔记本,上面有从雅虎金融 API 中提取金融时间序列数据所需的所有代码,并创建了一个丰富的数据框架,用于分析单个股票的投资组合表现。代码还包含了对一些关键投资组合度量的审查,以及使用Plotly库创建的几个可视化。在第 2 部分中,我扩展了第 1 部分的分析和可视化,提供了获取生成的数据集并在Dash by Plotly ( Dash ) web 应用程序中可视化它们所需的代码。
在这个系列的继续中,我将提供 Robo Advisors 的概述,然后分享关于如何评估多样化指数策略的额外代码和细节。这种策略可以用于几个个人金融用例,包括作为将 ETF 与个股和债券相结合的整体方法的一部分。它还可以用来评估 Robo 顾问和个人管理的 ETF 策略的有效性。
最后,我最初方法的最大限制之一是,分析没有考虑股息和比较股东总回报。股东总回报现在已经纳入其中——在我看来,这是我在散户投资者个人投资组合应用中看到的最大差距之一。很难全面了解投资组合的表现,包括不同的投资时机和获得的股息。我的方法现在兼顾了这两者,这是我个人的一个痛点,促使我用自己的产品来解决这个问题。我将继续改进这个投资组合绩效 web 应用程序;我将分享未来的更新,看看这种方法作为面向消费者的应用程序是否有更广阔的市场。
披露:本帖内容不应被视为投资建议。过去的表现不一定代表未来的回报。我写的是一般化的例子,并展示了如何使用 pandas 为模型投资组合导入数据。你应该向你的财务顾问提出所有与投资相关的问题,并对本文中提到的任何投资进行尽职调查。因此,我对使用本文中描述的方法可能遭受的任何损失不承担任何责任,并且在此明确否认任何此类责任。
Robo Advisors 概述。
根据 NerdWallet 的说法,机器人顾问是“一种在线的、自动化的投资组合管理服务”。Robo Advisors 使用算法,这些算法基于个人用户输入的答案,并根据个人的风险承受能力和投资范围(也称为退休时间和/或财务目标)推动个人的投资选择。机器人顾问提供的成本比传统的人力财务顾问低得多,代价是你通常没有任何人可以就你的财务目标进行个人咨询。Robo Advisors 通常最适合被动投资者,他们喜欢别人建立和优化个人投资组合,也没有复杂的财务状况。
一些主要和最知名的机器人顾问包括 Wealthfront、SoFi 和 Betterment。个人资本是这一领域的另一个选择,尽管该公司不认为它应该被归类为机器人顾问——这是因为个人资本将复杂的预算和投资组合监控应用程序与虚拟人力财务顾问相结合。个人资本市场本身是一个金融技术平台,也可以为拥有更复杂财务状况的高净值个人和家庭提供建议。
在本帖中,我们将利用个人资本在其财富管理表现页面上提供的一个多元化 ETF 例子。我相信这是有益的,因为我尊重个人资本的方法,他们对自己相对于基准的表现非常透明。
被动与主动投资策略。
自从我开始投资以来的这些年里,我越来越注重让我的投资策略尽可能地乏味。这意味着我更倾向于被动投资策略,我承认试图战胜市场是极其困难的。在本系列的第一部分,我注意到,从长期来看,20 只积极管理的国内基金中有 1 只跑赢了指数基金( link )。长期的优异表现很难保持,以前的优异表现往往会恢复到长期基准表现的平均值。进一步强调这一点,最近宣布,连续第九年,积极型基金经理落后于标准普尔 500 指数&。先前声称在波动性增加的时期会做得更好的主动型经理,将不得不再次从头开始。
有鉴于此,我的部分关注点是多样化的 ETF 策略——这种策略采用高质量的 ETF,它们具有非常低的成本结构,并提供跨资产类别的多样化。虽然我更喜欢我的投资策略越无聊越好,但我也继续投资符合几个投资标准的个股,包括加速收入增长、收益表现优异,以及理想情况下,开发我个人喜爱的产品。本系列的第 1 部分和第 2 部分详细介绍了如何跟踪相对于标准普尔 500 的个股表现,这与我们在本文中评估多元化 ETF 策略的方式类似。
第 3 部分的代码实现。
设置。与第 1 和第 2 部分类似,我在 GitHub 上创建了一个 repo,其中包含了创建最终Dash仪表板所需的所有文件和代码。在这篇文章中,我将解释代码的以下方面:
- ETF 相对于当前分配的目标分配。
- 将每个头寸和基准相加,以计算相对于基准的总股东回报。
- 相对于单一基准(在本例中为标准普尔 500)评估模型投资组合的总股东回报。
本文回购中的 Jupyter 笔记本有从头到尾所需的所有代码,但如果您想要关于生成投资组合数据集的完整解释,请参考第 1 部分。如果你想了解更多关于使用 Anaconda、虚拟环境和 Dash 的细节,请参见第二部分中的入门部分。
正如在第 2 部分的结尾所讨论的,以前的方法的局限性是:I)它没有考虑分红;ii)它评估了活跃的头寸,并且没有包括以前撤资的头寸 iii)通过生成可以输入到实时 web 应用程序的数据管道,有机会使整个过程自动化。
在我看来,不包括股息和无法评估股东总回报(TSR)是最大的差距;本文中更新的方法评估 TSR。我不太关心被撤资的头寸,因为这种评估对于评估您的战略执行情况最有用,如果有您继续持有的头寸,您可能不应该持有,例如,落后于基准,因此代表整体业绩阻力和不持有更好投资的机会成本。虽然我想完全自动化这一过程,但我已经降低了优先级,以利于完善我的整体战略。如果我决定追求完全自动化的方法,我会决定它是否值得一个单独的职位。
目标分配。关于代码讨论,我们将回顾 Jupyter 笔记本的部分内容——互动版本可在这里找到。如果您想了解数据框架开发和结束高评估的详细信息,请查看第 1 部分。下面我将强调 dataframe 开发的主要补充,即理解和比较目标分配与当前分配。在我们的模型投资组合中,我们希望将 50%的投资分配给 VTI,这是一个针对美国股票的总股票指数 ETF。随着投资后价格的变化,我们的配置将根据该资产以及模型投资组合中其他资产的变动而偏离 50%。因此,我们应该监控这一变动,例如每季度一次,并调整所持有的资产,以便回到我们的目标配置。例如,如果 VTI 的涨幅超过 50%,VEU 跌至 25%以下,那么我们应该卖出一些 VTI,买入更多 VEU,以回到我们的目标(假设你希望你的模型投资组合的整体投资保持中性,这类似于机器人顾问策略)。下面的内容从笔记本的第 32 行开始,到第 36 行结束。
*# This dataframe will only look at the positions with an intended contribution, if applicable.*merged_portfolio_sp_latest_YTD_sp_contr **=** merged_portfolio_sp_latest_YTD_sp[merged_portfolio_sp_latest_YTD_sp['Target_Alloc']**>**0]merged_portfolio_sp_latest_YTD_sp_contr*# Shorten dataframe to focus on columns that will be used to look at intended versus current allocations.*merged_portfolio_sp_latest_YTD_sp_contr_subset **=** merged_portfolio_sp_latest_YTD_sp_contr[['Ticker', 'Target_Alloc', 'Cost Basis', 'Ticker Share Value']]merged_portfolio_sp_latest_YTD_sp_contr_subset*# If you've bought multiple positions at different times, this pivot table will aggregate the sums for each position.*merged_portfolio_sp_latest_YTD_sp_contr_subset_pivot **=** merged_portfolio_sp_latest_YTD_sp_contr_subset**.**pivot_table(
index**=**['Ticker', 'Target_Alloc'], values**=**'Ticker Share Value', aggfunc**=**'sum')merged_portfolio_sp_latest_YTD_sp_contr_subset_pivot**.**reset_index(inplace**=**True)merged_portfolio_sp_latest_YTD_sp_contr_subset_pivot*# These new columns calculate the actual allocation to compare to the target allocation.*merged_portfolio_sp_latest_YTD_sp_contr_subset_pivot['Total'] **=** merged_portfolio_sp_latest_YTD_sp_contr_subset_pivot**.**loc[:, 'Ticker Share Value']**.**cumsum()merged_portfolio_sp_latest_YTD_sp_contr_subset_pivot['Allocation'] **=** merged_portfolio_sp_latest_YTD_sp_contr_subset_pivot['Ticker Share Value'] **/** merged_portfolio_sp_latest_YTD_sp_contr_subset_pivot**.**iloc[**-**1, **-**1]merged_portfolio_sp_latest_YTD_sp_contr_subset_pivotmerged_portfolio_sp_latest_YTD_sp_contr_subset_pivot**.**sort_values(by**=**'Target_Alloc', ascending**=**False, inplace**=**True)merged_portfolio_sp_latest_YTD_sp_contr_subset_pivot
- 在我们读到的文件中,我们查看了我们用目标分配记录的所有头寸;如果您想跟踪某个头寸的表现,但最初没有投资,这就提供了灵活性。
- 我们对数据框架进行子集划分,然后使用 pivot_table 方法来合计每只股票的总市值。正如代码中所指出的,我们运行这个支点是因为,在几年的时间里,你可能会多次投资于这个模型投资组合中的头寸——这个支点让你可以根据每个模型的当前市场价格查看你的全部配置。
- 然后,我们将每个头寸的总市值(股票价值)相加,并除以整个投资组合的总市值;这使您可以根据市场价格变化,将当前分配与您的初始目标分配进行比较。
股东总回报。在笔记本第 54 行的上方,你会看到一个股息部分。在这里,我收集了每个职位的股息数据,您将在笔记本中看到您可以收集股息数据的网站的 URL。我将在这里自我批评,并承认这部分代码有一些改进的空间。我研究了如何访问 Quandl 的 API 来自动导入股息数据,但我认为现在不值得付费订阅(股息数据是有付费墙的)。我也在考虑为这些网站开发一个刮刀;但现在,我正在将网站上的数据复制/粘贴到历史股息 excel 文件中,并手动清理一点。这通常是好的,因为股息往往按季度支付,而模型投资组合总共只有 7 个头寸。我绝对欢迎任何关于如何改进这部分代码的建议。请注意,无论您如何决定汇总股息数据,您都应该确保您只考虑您的头寸收购日期早于或等于除权日期的股息(参见第 60 行的函数,如下面的第一个代码块所示)。
*# This function determines if you owned the stock and were eligible to be paid the dividend.***def** **dividend_post_acquisition**(df):
**if** df['Ex-Div. Date'] **>** df['Acquisition Date'] **and** df['Ex-Div. Date'] **<=** stocks_end:
val **=** 1
**elif** df['Ex-Div. Date'] **<=** df['Acquisition Date']:
val **=** 0
**else**:
val **=** 0
**return** val*# subset the df with the needed columns that includes total dividends received for each position.*merged_subsets_eligible_total **=** merged_subsets_eligible**.**pivot_table(index**=**['Ticker #', 'Ticker', 'Acquisition Date', 'Quantity'
, 'Latest Date', 'Equiv SP Shares']
, values**=**'Dividend Amt', aggfunc**=**sum)merged_subsets_eligible_total**.**reset_index(inplace**=**True)merged_subsets_eligible_total
- 一旦您创建了符合条件的股息数据框架,基于您持有股票以来每只股票的除权日期,上面的代码合计了每个头寸收到的总股息,还包括 Equiv SP Shares 列;这是比较每一个位置的总股息收到相对于 TSR 一个等价的 SP500 投资将返回的需要。
*# This df adds all of the columns with the SP500 dividends paid during the range that each stock position was held.*
*# For comparative holding period purposes.*dividend_df_sp500_2 **=** dividend_df_sp500agg0_start_date **=** datetime**.**datetime(2014, 4, 21)
dbc1_start_date **=** datetime**.**datetime(2014, 4, 21)
igov3_start_date **=** datetime**.**datetime(2014, 4, 21)
veu4_start_date **=** datetime**.**datetime(2014, 4, 21)
vnq5_start_date **=** datetime**.**datetime(2014, 4, 21)
vti6_start_date **=** datetime**.**datetime(2014, 4, 21)
vti7_start_date **=** datetime**.**datetime(2014, 5, 5)dividend_df_sp500_2**.**loc[:, 'agg0_sum'] **=** dividend_df_sp500_2[(dividend_df_sp500_2['Ex-Div. Date'] **>** agg0_start_date) **&** (dividend_df_sp500_2['Ex-Div. Date'] **<=** stocks_end)]**.**sum()['Amount']dividend_df_sp500_2**.**loc[:, 'dbc1_sum'] **=** dividend_df_sp500_2[(dividend_df_sp500_2['Ex-Div. Date'] **>** dbc1_start_date) **&** (dividend_df_sp500_2['Ex-Div. Date'] **<=** stocks_end)]**.**sum()['Amount']dividend_df_sp500_2**.**loc[:, 'igov3_sum'] **=** dividend_df_sp500_2[(dividend_df_sp500_2['Ex-Div. Date'] **>** igov3_start_date) **&** (dividend_df_sp500_2['Ex-Div. Date'] **<=** stocks_end)]**.**sum()['Amount']dividend_df_sp500_2**.**loc[:, 'veu4_sum'] **=** dividend_df_sp500_2[(dividend_df_sp500_2['Ex-Div. Date'] **>** veu4_start_date) **&** (dividend_df_sp500_2['Ex-Div. Date'] **<=** stocks_end)]**.**sum()['Amount']dividend_df_sp500_2**.**loc[:, 'vnq5_sum'] **=** dividend_df_sp500_2[(dividend_df_sp500_2['Ex-Div. Date'] **>** vnq5_start_date) **&** (dividend_df_sp500_2['Ex-Div. Date'] **<=** stocks_end)]**.**sum()['Amount']dividend_df_sp500_2**.**loc[:, 'vti6_sum'] **=** dividend_df_sp500_2[(dividend_df_sp500_2['Ex-Div. Date'] **>** vti6_start_date) **&** (dividend_df_sp500_2['Ex-Div. Date'] **<=** stocks_end)]**.**sum()['Amount']dividend_df_sp500_2**.**loc[:, 'vti7_sum'] **=** dividend_df_sp500_2[(dividend_df_sp500_2['Ex-Div. Date'] **>** vti7_start_date) **&** (dividend_df_sp500_2['Ex-Div. Date'] **<=** stocks_end)]**.**sum()['Amount']dividend_df_sp500_2**.**head()*# Subset the above df and re-label the SP500 total dividend amount column.*dividend_df_sp500_2 **=** dividend_df_sp500_2**.**iloc[0, 10:]dividend_df_sp500_2 **=** dividend_df_sp500_2**.**to_frame()**.**reset_index()dividend_df_sp500_2**.**rename(columns**=**{213: 'SP500 Div Amt'}, inplace**=**True)dividend_df_sp500_2*# This function is used to assign the Ticker # column so that this df can be joined with the master df.***def** **ticker_assigner**(df):
**if** df['index'] **==** 'agg0_sum':
val **=** 'AGG 0'
**elif** df['index'] **==** 'dbc1_sum':
val **=** 'DBC 1'
**elif** df['index'] **==** 'igov3_sum':
val **=** 'IGOV 3'
**elif** df['index'] **==** 'veu4_sum':
val **=** 'VEU 4'
**elif** df['index'] **==** 'vnq5_sum':
val **=** 'VNQ 5'
**elif** df['index'] **==** 'vti6_sum':
val **=** 'VTI 6'
**elif** df['index'] **==** 'vti7_sum':
val **=** 'VTI 7'
**else**:
val **=** 0
**return** val
上面的代码是最不优雅的,也是最需要改进的,但是它暂时完成了工作。
- 在第一个代码块(第 68 行)中,根据每个头寸的收购日期为每个头寸设置一个变量。
- 然后创建一个新列,对每个头寸在等价持有期内支付的 SP500 股息进行求和。
- 在下一个代码块中,您抓取第一行和第 11 列及其后的
.iloc[0, 10:]。这从笨拙的解决方案中删除了重复的行,在每个单个头寸的持有期间获取了 SP500 红利。 - 在上面的最后一个代码块中,您将 SP500 红利的总和分配给主数据框架中的每个相关位置,以便计算每个持股的可比 TSR。例如,
agg0_sum跟踪自您持有第一笔 AGG 投资以来所有 SP500 股息的总和。您正在将它分配给主数据帧中的“AGG 0”行。
在第 74 行,您将看到一些熟悉的计算,以及一些新的计算,为了创建关键指标,我们将比较相对于 SP500 的每个位置。
- SP500 Tot Div——这是一股 SP500 股票的 SP500 股息总额,乘以你在每个位置投资的美元可以在 SP500 购买的股票数量。
- 创建了总收益/(损失)列,合计每个头寸的收益或损失,也就是自买入以来股票上涨或下跌的百分比,以及自买入该头寸以来收到的总股息。这些指标允许你计算你的个人头寸和他们的可比 SP500 持仓的 TSR。
- 和往常一样,你的回报是由持有价值除以其原始成本基础决定的。
- 与第 1 部分和第 2 部分类似,我们计算累积回报,以便除了考察单个头寸的表现之外,还考察我们的总投资组合回报相对于 SP500 基准投资组合回报的表现。
仪表板输出的简要观察。
以下两种可视化效果都可以在 Jupyter 笔记本中看到,也可以在本地运行Dash仪表盘。关于设置Dash的说明,请参见第 2 部分的 与仪表板一起工作一节。

- 在上面的可视化中,您可以看到相对于您的目标配置,您在该模型投资组合中每个头寸的当前配置。
- 在这个例子中,这些头寸自 2014 年 4 月以来一直持有,VTI 在此期间(截至 2019 年 4 月 18 日)的回报率约为 70%。
- 由于 VTI 的回报高于所有其他头寸,它现在代表了你投资组合的近 60%——这就是投资组合再平衡的切入点。
- 保持投资组合中的投资金额不变,你会想卖出一些 VTI,并用所得资金购买其他头寸,以回到最初的目标配置。
- 像 Betterment 和 Wealthfront 这样的机器人顾问的好处是,他们会自动为你重新平衡投资组合。需要注意的是,他们提供这项服务显然是要收费的;但机器人顾问可能会为你节省与重新平衡交易相关的交易成本。

- 这种可视化结合了相对于基准 SP500 回报的 TSR 美元比较和 TSR 百分比比较。
- 请注意,在“股票行情与 SP500 的对比”可视化图中可以找到每个位置的头对头表现。
- 从正面来看,我们知道在 TSR 的基础上,VNQ 和 VTI 在过去的 5 年里都超过了 SP500。
- 然而,总体而言,仅 SP500 投资组合的表现比模型投资组合高出约 13 个百分点。
这是否意味着你的模特组合很差?在我看来,答案是否定的。这种观点的一些原因包括:
- 五年的持有期相对较短。我认为,大多数投资者应该将投资视野一直延伸到退休,对一些人来说,可能是 30-40 年后。
- 这一策略旨在提供美国股票以外的多元化投资,在这种情况下,这将降低您的投资组合风险和潜在的总体回报。
- 更精确的比较将包括每个 ETF 更好的基准,例如,AGG 提供投资级债务的风险敞口。它的目的不是战胜 SP500,而是提供比股票策略更低的风险和回报。
- 我相信将这个模型组合与 SP500 进行比较是有益的,但它也帮助我们简化了本文中讨论的比较框架。
- 最佳解决方案很可能包括本文中的比较,以及对多个基准的更高级的分析。
- 分散投资让你有更好的机会参与有利的资产类别,而不必把握市场时机,也就是永远投资并“坚持到底”。
- 这个模型投资组合的很大一部分,VTI,实际上跑赢了 SP500。
- 实现类似指数的回报是一个伟大的成就。正如杰克·博格尔所说:“指数基金并不提供平均表现;它们给投资者带来了最高的十分之一的回报。”
结论。
我还没有找到一种服务可以复制这种类型的分析,我认为这是一个有价值的框架,可以确保你正确地投资退休储蓄,或者无论你的特定财务目标是什么。我见过的几乎所有金融网站和在线经纪商都会显示资产的资本回报,但它们不会提供股东总回报。在我看来,TSR 非常重要——你可以持有一个表现稍逊于 SP500 的头寸,但它可以提供更高的收益率,因此在那个时候可能是一个更好的选择。
机器人顾问采用的策略与我们在这篇文章中讨论的类似,机器人顾问自动化了所有的投资组合管理,包括投资组合的重新平衡。但是,你投资的每一块钱都受制于他们的收费结构,而不是你运行可比策略的时间。Robo Advisors 还提供节税策略,本文没有涉及,但它是帮助您的投资获得额外回报的另一个杠杆。无论如何,我希望这篇文章对 robo-advisors 有所帮助,并提供一个有用的框架,作为整体多元化投资战略的一部分。
如果你喜欢这篇文章,如果你能点击“拍手”图标让我知道并帮助增加我的作品的发行量,那将是非常棒的。
你也可以在推特上联系我, @kevinboller ,我的个人博客可以在这里找到。感谢阅读!
Python 以慢动作运行
那些努力自信地编写 Python 函数的人的策略和实践问题
人们普遍认为,我们要么是右脑型(富有想象力的自由精神),要么是左脑型逻辑机器人。原来,都是神话。我们甚至没有大脑!
至少这是我开始学习数据科学时的感觉。有时候,我无法处理我的眼睛和耳朵拼命想吸收的所有信息。其他时候,我觉得我在自动驾驶仪上,内置的公式和库在做所有的工作。你可能认为后者是一种理想的状态,但我更想知道“引擎盖下”发生了什么
写函数是一个甜蜜点,称之为中脑?—创造力与解决问题以最明显的方式重叠在一起:你创造一些东西,然后解决一个问题。在你正在进行的更大项目的背景下,取得这个小小的成功是很好的——看到你放在引擎盖下的东西起作用了。
问题是,当我写函数的时候,我还不是很有信心。我将与你分享我在变得更加自信的过程中所学到的写作技巧。
我假设如果您还在阅读,您至少已经尝试过编写一些函数。我跳过了一些主要的定义和要求(大量的教程和文档已经涵盖了它们),取而代之的是专注于我们初学者可以做的实际的事情,来帮助我们自己在需要编写 python 函数的时候编写它们。注意:如果你是一个喜欢列表的人,我在本文中概述的过程的总结可以在最后找到!
放松

Sometimes, when I’m asked to write a function…
当我需要写一个函数的时候,我会立刻紧张起来。我能做到吗?第一次尝试?够快吗?首先,当我被这样毫无意义的问题分散注意力时,我很难有创造力或逻辑。
即使在技术面试中,功能输出和你花了多长时间并不重要,重要的是你的决心、足智多谋和解决问题的方法。我们应该像玩拼图游戏一样编写函数,而不是害怕。

Gettin’ jiggy with it
有些人先把所有边缘的碎片放在一起,有些人把所有颜色相似的碎片放在一起,有些人不停地看着盒子,有些人喜欢和一群人或其他人一起工作,大多数人按照自己的节奏移动,享受把它们放在一起的过程。我从来不会紧张拼拼图。事实上,这很放松。
采取积极的心态对任何任务都很重要,对于编写 Python 函数也是如此!
减速以加速
也许你也像我一样,刚刚开始学习 Python。作为一个初学者,我经常不必要地催促自己。我不确定为什么!如果有人看着我,他们很可能会支持我成功地编写函数。我知道很少有面试官支持他们的候选人,在这种情况下,你可能无论如何都不想和这些人一起工作。如果你是结对编程,你的同伴希望你成功,他们对你没有时间限制。对速度的需求全在我们的脑袋里!
“放慢速度以加快速度”的想法在一些应用中非常有价值,尤其是在学习新东西的时候。如果我们在开始的时候从容不迫,花几个小时去理解一个主题,那么我们可能会节省几周的工作时间。坚持这一理念意味着我们不能掩饰我们自己没有编写的函数的解决方案。我们也必须花时间去理解这些。
很明显我们没有足够的时间。用“更聪明地工作,而不是更努力地工作”这句古老的格言来平衡你的时间,并且知道你什么时候到达了收益递减的点,这几乎肯定不是在你自己尝试几次之前。如果没有人亲自求助,栈溢出就是惊人 。
如果我们多花几个小时去理解一个话题,那么我们就可以省下几周的工作。
想想你的想法
好了,现在我们要享受解决这个问题的过程,我们根本不需要着急。让我们开始吧。
写一个寻找三个数中最大值的函数。
在我们开始给计算机下命令之前,让我们关注一下人类将如何解决这个问题。一旦我们思考了自己的想法,我们就会把它翻译成 Python。
对于这个问题,倒推对我是有帮助的。(你可能会想出一个不同的方法。)如果我们寻找最大值,那么我们的输出将是输入之一。我如何知道返回哪个输入?
如果我用三个数字做这道题,比如 1,2,3,我会怎么找到最大值?看看前两个数字,看看哪个更大。然后将较大的数字与第三个数字进行比较。返回两者中最大的那个数。
写出你的想法
在评论中写下你想让代码做什么是一个很好的过渡步骤,可以跟踪你大脑之外的想法。旧的观念是,如果你能解释一些事情,那么你就完全理解它,这在这里是正确的——如果我们能通过注释解释函数应该做什么,那就成功了一半。

Type your thoughts as comments before you start writing the function.
提示:要一次注释掉多行,按下 command /。试试看!
翻译成 Python
记住没有时间限制,现在我试着翻译成 python。
诚然,作为初学者,这有时是最难的部分。我们不知道什么是可能的,不得不经常查阅文档、例子,或者查看备忘单。然而,对我来说,犯错误是学习新信息的最好方法之一。这就像一种很好的创伤:每当我处于类似的情况下,我更有可能被“触发”,并被提醒如果我再次犯同样的错误会发生什么。
对于我们的 max_of_three 示例,通过比较,我认为我应该使用一个关于前两个数的值的 if 语句:

现在,我想象运行这个函数。当我对我的解决方案更有信心时,我会测试这个函数,但现在我只是刚刚开始,只是简单地想象输入值。我意识到我根本不处理第三个数 c 。因此,根据我的注释,在我返回任何东西之前,我需要将我的 a 和 b 与第三个数字 c 进行比较。因此,似乎我应该在每种情况下将最大的数字与 c 进行比较,因为我没有对数字进行排序,而是只返回最大的一个。注意这与我最初的评论不同,但没关系!我会试试看:

我认为我的逻辑是合理的。此时,我可能会转到 elif 语句,但是我刚刚意识到我遗漏了一个步骤!如果 a 和 c 相等呢?在这种情况下,我返回什么数字(a 或 c)并不重要,因为最大值将等于这两个数字。

顺便说一下,我并不完全确定我是否正确地使用了 if 和 elif 语句,所以我谷歌了“if elif 文档”,并且验证了:“可以有零个或更多的[elif](https://docs.python.org/3/reference/compound_stmts.html#elif)部分,并且[else](https://docs.python.org/3/reference/compound_stmts.html#else)部分是可选的。”
酷毙了。但是因为我花了时间,我意识到我可以通过删除第二个 elif 行并用一个字符替换它来使它更容易阅读: > =

现在我想我已经完成了当 a 大于 b 时的函数。当 b 大于 a: 时,我会做类似的事情

Time to test my function!
测试功能
Python 最棒的部分之一是反馈——带有箭头的错误描述告诉您错误发生的确切位置。如果我在运行我的函数时出错,我就离正确运行更近了一步。为了测试我的功能,我将尝试几个不同的三重奏:

直到最后两个例子,一切看起来都很好。没有输出。我一定是遗漏了什么,因为没有错误,也没有返回任何东西。我错过了什么?
在每一个没有输出的例子中,我都有 a = b. 看着代码,我记得考虑过 a 或 b 什么时候等于到 c ,但是我忘记了 a 和 b 彼此相等的情况!这很容易添加和测试:

提示:在不同的步骤中使用打印语句来查看您的函数中“幕后”发生了什么,尤其是当您无法确定自己哪里出错时。这对于递归函数特别有帮助,当我们到达那里的时候。
给自己一个鼓励
看起来我已经完成了这个函数的编写。我忍不住想,“有什么大不了的——我打赌很多人可以做得更快,我甚至不确定我是对的。”但是,我必须记住,速度和准确性都不是这个练习的重点。目的是更好地编写函数。因为我忽略了一些地方,所以回去修复了它们,我成功了!
此外,我永远不会使用 max_of_three ,因为有一个名为 max 的内置 Python 函数,它可以找到任意数字的最大值。但是我是一个初学者,更好地编写函数的最快方法不是立即看别人写了什么而是自己不断练习编写函数。(FWIW,剩下的这些我以后再去试试。)在这个例子中我学到了什么?比较整数的时候,别忘了它们可以相等。在写的时候,我确实在两个不同的场合忘记了。
说到 max 函数,自己编写这个函数可能是一个有趣的挑战!我知道 Python 函数允许不同类型的输入,但是如果我把自己限制在一个列表中,我打赌我可以做到。
快进一个小时,我没有复制出最大功能。当我试图将我的想法翻译成 Python 时,我已经到了出错的地步。我休息了几次,想对这个问题有一个新的看法,但我几乎停滞不前了。这是我试过的一个版本:

Great, an error! (no sarcasm)
在这之后,我尝试使用枚举功能。那没有解决我的问题。我决定偷看一下 Python 源代码,但是我意识到我实际上不知道如何去做,并且当我试图看的时候找不到它。我做了更多的搜索,在栈溢出上发现了一个类似的问题,我并不完全理解。
所以,看起来我失败了。对吗?
不对!
记住,我们的目标绝不是复制 max 函数。目标是通过尝试复制 max 函数来学习一些东西。那么,我学到了什么?
- 我在列表中的元素和元素的索引之间反复出错。最终这让我意识到我有时会把误解为循环 / 列表理解,以后应该特别注意。我做了中的第二个练习题,以确保我在继续之前理解了 for loops 和list。
- 我得复习一下是如何列举的。我仍然需要更多的练习,但我现在理解得多了。
- 我参加了一个寻找 Python 源代码的寻宝游戏,明天我会和我的老师谈谈,以便更清楚地知道如何找到它。
- 最令人惊讶的是,我了解到 Python 的源代码并不总是用 Python 编写的!这不仅是一个可以考虑的很酷的概念,而且我意识到,考虑到我刚刚开始使用 Python,这个难度对我来说可能有点太高了。我需要记住,一些内置函数可能有简单的语法,但实际上非常复杂。毕竟,它们的使用频率可能不是它们内置的唯一原因。
简而言之,我试图复制内置于 Python 中的东西,但失败了,反正我是不会用的。然而,通过尝试和失败,我学到的东西比我只是敷衍了事要多。额外收获:我有办法优化自己的时间,不会因为发呆、心烦意乱或长时间休息后回来重复自己的工作而浪费时间。当我的进步停止时,我“寻求帮助”
这是一次成功!

Being a superstar may look different than what you imagined.
摘要:编写 Python 函数的过程
- 不用担心疗效。采取成长的心态。重要的是旅程,而不是目的地。把这看作是一个有趣的挑战,它会教会你一些新的东西,而不是一个你必须面对的任务。
- 减速加速。给自己留出合理的时间。当你已经到了一个你在挣扎却没有学习的地步时,寻求外界的帮助。确保你理解解决方案的每一部分或其他地方的提示。
- 在评论中写下你的想法。向自己解释这些步骤将有助于你记录你的逻辑,也是一种快速“落笔”的方式
- 将你的想法翻译成 Python。教计算机像你一样思考。思考创建算法的每一步的逻辑。从文档、其他人和网站获得帮助。
- 测试功能。使用不同的输入来检查你没有遗漏任何东西,阅读并使用所有对你有利的警告和错误。
- 回忆一下你在这次经历中学到了什么,并且祝贺自己学到了!对任何你不明白的事情进行跟进。
你做了我在这篇文章中遗漏的事情吗?如果有就加个评论!我很想听听你的意见,听听你个人在编写 Python 函数时遇到的困难。
Python 输入、输出和导入
在本教程中,让我们了解 python 中使用的输入和输出内置函数,我们还将学习如何导入库并在程序中使用它们。

Image Credits: Data Flair
在开始之前,让我们了解什么是内置函数?
作为高级语言的一部分而提供的、可以由简单的 引用 来执行的任何功能,无论是否有 参数——信用:your dictionary
python 中有许多内置函数。记住每个内置函数的名称和语法是非常不可能的。下面是 python 中所有内置函数的文档,按字母顺序由python.org—提供,我称之为内置函数的备忘单,请看下面:
[## 内置函数- Python 3.8.0 文档
Python 解释器内置了许多随时可用的函数和类型。它们被列出…
docs.python.org](https://docs.python.org/3/library/functions.html)
input()和print()内置函数是执行标准输入和输出操作最常用的函数之一。python 中没有output()函数,而是使用print()来执行标准的输出操作。您也可以在我的 GitHub 资源库上找到本教程的文档,如下所示:
您现在不能执行该操作。您使用另一个选项卡或窗口登录。您在另一个选项卡上注销,或者…
github.com](https://github.com/Tanu-N-Prabhu/Python/blob/master/Python_Input%2C_Output_and_Import.ipynb)
使用打印功能的输出操作
python 中的 print 函数通常用于在屏幕上打印输出。顾名思义打印意为要打印的东西在哪里?屏幕上。现在你不用每次都写打印函数的整个主体(内容),我个人甚至不知道打印函数的主体是什么样子的。你所要做的就是通过传递参数来调用函数。你在打印函数里面写的东西都叫做参数。让我们看看如何把东西打印到屏幕上。
variable = "Welcome to Python Tutorials"
print(variable)**Welcome to Python Tutorials**
就像我说的,变量是和打印函数一起传递的参数,你不需要写打印函数的主体,你只需要调用打印函数。print 函数的实际语法如下所示:
**print(value, …, sep=’ ‘, end=’\n’, file=sys.stdout, flush=False)**Prints the values to a stream, or to sys.stdout by default. Optional keyword arguments: **file:** a file-like object (stream); defaults to the current sys.stdout.
**sep: ** string inserted between values, default a space.
**end: ** string appended after the last value, default a newline. **flush:** whether to forcibly flush the stream.
你也可以试试这个,只需调用 help(print) 帮助功能就会为你提供你需要的帮助(咄;))
现在让我们看看如何在打印函数中传递多个参数,结果会是什么?
variable = "Welcome to Python Tutorials"
variable2 = ",This is a good place to learn programming"print(variable, variable2)**Welcome to Python Tutorials ,This is a good place to learn programming**
您可以在 print 函数中传递尽可能多的参数,但是要确保将变量彼此分开。
让我们在打印函数中使用“ sep ”(分隔符)并查看输出。
print(1, 2, 3, 4, 5, sep="--->")**1--->2--->3--->4--->5**
“ end ”参数用于将一个字符附加到我们在打印函数内部传递的字符串的末尾。
print(1, 2, 3, 4, 5, end=" This is the end")**1 2 3 4 5 This is the end**
格式化输出
可能在某些情况下需要格式化输出,在这种情况下,我们可以使用str.format()方法。
variable = "Python"
variable2 = "Programming"print("I love {0} and {1}".format(variable,variable2))**I love Python and Programming**
此外,format()方法可以做很好的工作,例如向整数字符串添加逗号分隔符,如下所示:
print(format(1234567, ",d"))**1,234,567**
类似地,我们可以使用“ % ”操作符格式化输出,如下所示:
number = 12.3456
print("The value of x is %1.2f" %number)**The value of x is 12.35**print("The value of x is %1.5f" %number)**The value of x is 12.34560**
Python 输入函数
过去,我们在主程序中硬编码变量的值。但有时我们需要听取用户的意见。这可以通过使用输入功能来实现。
输入功能顾名思义就是接受用户的输入。输入函数的语法是:
input(prompt = ‘ ’)
这里的提示是屏幕上显示的提示。
number = input("Enter the number of your choice: ")
print("The number that you have entered is: %s" %number)**Enter the number of your choice: 100
The number that you have entered is: 100**
这里的提示是“输入你选择的数字”,这里你可能会奇怪为什么我输入整数的时候用了“ %s ”。这是因为在 Python 中所有的变量都存储为字符串,要显式地对其进行类型转换,可以使用 int(variable_name)或 float(variable_name) 等等。去试试,然后告诉我。
Python 导入
有时,我们需要从不同的模块中导入一些库(模块是包含定义和语句的 python 文件)。在这些情况下,我们可以通过使用import关键字和模块名来导入这些方法或模块。
考虑一个例子,认为你想看“圆周率”的值是多少,你能做的只是而不是做(22/7,或者背 3.1423……我不知道更进一步),只要导入数学模块,调用数学方法,你的工作就完成了。
import math
print(math.pi)**3.141592653589793**
同样使用from关键字,我们可以从模块中访问特定的属性。例如:
from math import pi
print(pi)**3.141592653589793**
更像是“从这个模块导入这个函数、方法或者属性”。
“Python 输入、输出和导入”教程到此结束。希望你喜欢。如果你有任何意见或建议,请在下面的评论区告诉我。直到那时再见!!!。
使用 NetworkX、Plotly 和 Dash 的 Python 交互式网络可视化

他们说一张图胜过一千个单词。我完全同意。为了理解复杂的网络模式,我更喜欢看网络图,而不是通读冗长的文档。
这篇文章是关于 Python 交互式网络可视化应用的。在前半部分,它涵盖了网络可视化应用程序的特性,并介绍了我用来开发这个应用程序的工具。在第二部分,将讨论如何使用 NetworkX、Plotly 和 Dash 的技术细节。
1.网络可视化应用功能
网络图揭示了模式,有助于发现异常。金融领域的网络可视化应用潜力巨大,例如欺诈监控和洗钱监控。对于这个项目,我将创建一个虚拟的事务数据集,并构建一个网络可视化应用程序来交互式地绘制显示这些事务的图形。
首先,该应用程序将读入虚拟交易数据集,并生成交易网络的图形表示。在这里,我想自定义图形表示,例如根据交易时间对边进行颜色编码,根据交易金额改变边的宽度。这样,很容易快速理解交易网络图。
其次,它将是一个交互式应用程序。当用户悬停在一个节点或边上时,将显示丰富的信息。此外,用户应该能够输入要搜索的帐户和要显示的时间范围。然后,响应用户的输入,应用程序将相应地显示交易网络图。
2.图书馆简介
我找到了几个有用的 python 包来支持这个应用程序的开发,包括 NetworkX 、 Plotly 和 Dash 。本节将简要介绍这些库,并讨论它们如何对该应用程序的开发有用。
2.1 图论和网络 X
为了表示交易网络,图由节点和边组成。这里,节点表示帐户,相关属性包括客户名称和帐户类型。边是具有交易日期和交易金额的相关属性的交易。交易网络是一个有向图,每条边都从源账户指向目标账户。
NetworkX 是一个 Python 包,用于创建、操作和研究复杂网络的结构、动态和功能。它只需要几行代码就可以快速构建和可视化图形:
import networkx as nx
import matplotlib.pyplot as plt
G = nx.Graph()
G.add_edge(1,2)
G.add_edge(1,3)
nx.draw(G, with_labels=True)
plt.show()
除了使用内嵌数据构建简单的图形之外,NetworkX 还支持使用从 csv 或数据库导入的数据集构建更复杂的图形。在这里,我导入了包含交易记录的虚拟 csv 文件,并使用 NetworkX 构建了交易网络。
2.2 互动图和情节图
Python 附带了几个有用的绘图库。与静态的 Matplotlib 和 Seaborn 库不同,Plotly 制作交互式图形。它支持许多常见的图表类型,包括折线图、散点图、条形图、直方图和热图。与 ipywidgets 一起,它允许在 Jupyter 笔记本中进行交互式数据分析。
2.3 反应式网络应用和破折号
Jupyter 笔记本在数据科学家中很受欢迎。但是我想更进一步,让其他 stackholders 也能使用这个应用程序,他们可能不需要有数据分析的背景。Web 应用程序成为一个很好的选择,因为每个人都可以使用浏览器轻松访问 web 应用程序。
然后,我找到了 Dash,这是一个用于创建反应式 web 应用程序的开源 Python 库。Dash 允许 Python 数据分析代码与前端 HTML、CSS 和 Javascript 无缝集成。利用 Dash 提供的 Python 接口和反应式装饰器,Python 数据分析代码被绑定到交互式的基于 web 的组件上。由于 Dash 是基于 Flask framework 和 React.js 构建的前端渲染,所以我可以很容易地从开源社区获得大量支持。最后但同样重要的是,Dash 与 Plotly 完全兼容,这意味着我可以将使用 Plotly 创建的网络图集成为 Dash 应用程序中的一个组件,并进一步添加其他基于 web 的组件来与我的数据分析代码进行交互。
3.编码
现在,让我们继续真正的编码!
3.1 初始化 Dash App
因为 Dash 是建立在 Flask 框架上的,所以看到启动 Dash 应用程序和启动 Flask 应用程序几乎相同的语法就不足为奇了。
3.2 定义布局
借助 Python 接口 dash_html_components 和 dash_core_components,html 和基于 web 的交互式组件可轻松集成到 Python 分析代码中。这里,布局设计遵循自举网格系统。这个交易网络可视化应用程序包括 RangeSlider(定义时间范围)、输入框(键入要搜索的帐户)、Plotly graph(根据用户输入显示交易网络)、悬停框(当用户悬停在图形上时显示详细信息)和单击框(当用户单击图形时显示详细信息)等组件。

3.3 绑定到分析代码
当用户对 RangeSlider 或输入框进行更改时,绘图数字会相应地改变。当用户悬停或单击绘图图中的节点或边时,悬停框和单击框会显示与该节点或边相关联的详细信息。
3.4 定义 Plotly 图形
这里,代码定义了如何构建交易网络、启动 Plotly 图,以及如何响应用户的输入来更改 Plotly 图。基本上,这里的代码定义了网络图的逻辑。
首先,导入数据集并将日期字符串转换为 Python 能够理解的 Datetime 对象。
然后,使用 NetworkX 构建网络。
用 Plotly 定义节点。
用 Plotly 定义边。这里,定义定制的边不像定义节点那样简单。边的定制有两种方式:边的颜色代表交易的时间,交易越早,边的颜色越浅;此外,边的宽度代表交易金额,边越宽,交易金额越大。
定义边缘上不可见的中间点,以允许边缘上的悬停效果。由于 Dash 只允许数据点上的悬停效果,所以我在边上添加了一个不可见的中间点,以在边上创建一个额外的数据点。
最后,定义 Plotly 图形的布局。
最终交易网络可视化应用程序的工作方式如下:
如果你对代码感兴趣,请在 Github 上查看。
Python Lambda 函数
在本文中,您将了解更多关于 python 中 lambda 函数的知识。我们如何使用 lambda 函数,lambda 函数的语法是什么,我们为什么需要它们?

Image Credits: TechGeekBuzz
什么是 lambda 函数,它有什么特点?
λ函数也叫 匿名函数 。匿名函数是没有名字的函数。众所周知,要在 python 中定义一个普通函数,需要使用def关键字。但是在这个匿名函数的例子中,我们使用lambda关键字来定义函数。阿隆佐·邱奇在 20 世纪 30 年代向数学领域引入了λ函数。
此外,本教程的完整代码可以在我的 GitHub 资源库 下面找到:
此时您不能执行该操作。您已使用另一个标签页或窗口登录。您已在另一个选项卡中注销,或者…
github.com](https://github.com/Tanu-N-Prabhu/Python/blob/master/Python_Lambda_Function.ipynb)
λ函数的特征是:
- lambda 函数可以接受许多参数,但只能返回一个表达式。这里的表达式只是 lambda 函数返回的结果。
- Lambda 函数在语法上被限制为返回单个表达式。
- 您可以在其他函数中使用它们作为匿名函数。
- lambda 函数不需要返回语句,它们总是返回一个表达式。
在短时间内需要匿名函数时,使用 lambda 函数。—我不知道他们为什么这么说,如果你知道,请告诉我,我很好奇。
lambda 函数的语法是什么?
lambda 函数的语法如下所示:
lambda 参数:表达式
表达式总是被执行并返回。
让我们理解 lambda 函数的工作原理,我们将使用简单的逻辑,通过增加两个数来理解 lambda 函数的工作原理。首先,我会写程序,然后向你解释它是做什么的?
add = lambda a: a + a
print(add(20))**40**
好的,首先,你明白这个节目的内容了吗,别担心,我会一步一步给你解释的。
- 首先,使用
lambda关键字定义匿名/ lambda 函数。 - 只需记住 lambda 函数的语法如下所示:lambda arguments:expression, 然后只需使用变量“ a ”作为参数,使用“ a + a ”作为表达式,记住我们正在添加两个数字(a + a)。
- 然后,为了存储最终相加的结果,让我们使用一个变量“ add ”。
- 然后就像调用一个普通的函数一样,我们通过发送一个参数“ 20 ”来调用 lambda 函数的变量名“ add ”来执行加法,然后打印出来“ print(add(20)) ”。
- 这是最后一步,在这里,当我们打印结果时,我们得到 40,因为“ 20+20 ”,因为我们添加了“ a+a ”。看这里,我没有使用返回语句。
既然我们知道了 lambda 函数是如何工作的,现在我们来玩玩它。 Lambda 函数带多个实参,我们来看看这个说法是真是假。
add = lambda a, b, c: a + b + c
print(add(10, 10, 10))**30**
哇,上面的陈述实际上是正确的,因为我向 lambda 函数传递了多个参数,然后它就工作了。
python 中 lambda 函数和普通函数的区别?
这是最难回答的问题之一,在做了一些研究后,我差不多可以回答上面的问题了。我不想做笼统的假设,而是想借助一个例子来回答这个问题:
示例 1:使用 normal 函数将三个数相加。
**def** add(a,b,c):
return a+b+cresult = add(10, 10, 10)
print(result)**30**print(type(result))**int**
示例 2:使用 lambda 函数将三个数相加。
add = **lambda** a, b, c: a + b + c
print(add(10, 10, 10))**30**print(type(add))**function**
基于以上两个例子,我在正常函数和 lambda 函数之间做了一些区别。希望有帮助。

你想知道我是如何计算程序的执行时间的。很简单,我只是使用了时间库。好吧,我会给你提供下面的代码。
import **time**start_time = time.time()
add = **lambda** a, b, c: a + b + c
print(add(10, 10, 10))
print("--- %s seconds ---" % (time.time() - start_time))**30
--- 0.0005075931549072266 seconds ---**
为什么要用 lambda 函数?
我们可以在 python 的任何普通函数中使用 lambda 函数作为匿名函数。这就是 lambda 函数的真正超能力。因为它的简单性,你可以毫不费力地编写一个 lambda 函数。现在你只需要写一个普通的函数,把每个数字和当前给定的数字相加。
困惑,好吧,想象一下现在你有 100 这个常数,而每次你需要添加另一个数字,比如 200,300,400,…到现有的号码。这里,您不用在同一个函数中定义另一个函数,而是使用 lambda 函数,如下所示。
def add(n):
return lambda a : a + nresult = add(100)
print(result(200))**300**
使用 lambda 函数只需一步就可以完成这项工作。就像我说的,与普通函数相比,另一个函数内部的简单性和可用性是 lambda 函数的主要特征。
我们能重用 lambda 函数吗?
我想答案是肯定的,因为在下面的例子中,我可以重用同一个 lambda 函数将数字添加到现有的数字中。
def add(n):
return lambda a : a + nresult = add(100)
print(result(200))
print("-------------------")
print(result(300))
print("-------------------")
print(result(400))
print("-------------------")
print(result(500))**300
-------------------
400
-------------------
500
-------------------
600**
好了,现在你们应该对 lambda 函数有了更好的理解。这是“ Python Lambda 函数 ”教程的结尾,还有更重要的概念比如贴图、滤镜等等。这些是完全不同的概念,我将很快把它们写成单独的教程。敬请期待!!。再见,祝你愉快。
用于可解释机器学习的 Python 库
4 个用于更好地可视化、解释和诠释模型的库

随着对人工智能偏见的担忧变得越来越突出,企业能够解释他们的模型产生的预测以及模型本身如何工作变得越来越重要。幸运的是,正在开发越来越多的 python 库来尝试解决这个问题。在下面的帖子中,我将简要介绍四个最常用的解释和说明机器学习模型的软件包。
下面的库都是 pip 可安装的,带有很好的文档,并且强调可视化解释。
黄砖
这个库本质上是 scikit-learn 库的扩展,为机器学习模型提供了一些非常有用和漂亮的可视化。visualiser 对象,核心接口,是 scikit-learn 评估器,所以如果你习惯于使用 scikit-learn,工作流程应该很熟悉。
可以呈现的可视化包括模型选择、特征重要性和模型性能分析。
让我们看几个简单的例子。
该库可以通过 pip 安装。
pip install yellowbrick
为了说明一些特性,我将使用一个名为葡萄酒识别集的 scikit-learn 数据集。该数据集有 13 个特征和 3 个目标类,可以直接从 scikit-learn 库中加载。在下面的代码中,我导入数据集并将其转换为数据框。数据可以在分类器中使用,无需任何额外的预处理。
import pandas as pd
from sklearn import datasetswine_data = datasets.load_wine()
df_wine = pd.DataFrame(wine_data.data,columns=wine_data.feature_names)
df_wine['target'] = pd.Series(wine_data.target)
我还使用 scikit-learn 将数据集进一步分为测试和训练。
from sklearn.model_selection import train_test_splitX = df_wine.drop(['target'], axis=1)
y = df_wine['target']X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
接下来,让我们使用 Yellowbricks visualiser 来查看数据集中特征之间的相关性。
from yellowbrick.features import Rank2D
import matplotlib.pyplot as pltvisualizer = Rank2D(algorithm="pearson", size=(1080, 720))
visualizer.fit_transform(X_train)
visualizer.poof()

现在让我们安装一个 RandomForestClassifier 并使用另一个可视化工具评估性能。
from yellowbrick.classifier import ClassificationReport
from sklearn.ensemble import RandomForestClassifiermodel = RandomForestClassifier()
visualizer = ClassificationReport(model, size=(1080, 720))visualizer.fit(X_train, y_train)
visualizer.score(X_test, y_test)
visualizer.poof()

ELI5
ELI5 是另一个可视化库,用于调试机器学习模型并解释它们产生的预测。它可以与最常见的 python 机器学习库一起工作,包括 scikit-learn、XGBoost 和 Keras。
让我们使用 ELI5 来检查我们上面训练的模型的特征重要性。
import eli5eli5.show_weights(model, feature_names = X.columns.tolist())

默认情况下,show_weights方法使用gain来计算权重,但是您可以通过添加importance_type参数来指定其他类型。
也可以用show_prediction来考察个别预测的原因。
from eli5 import show_predictionshow_prediction(model, X_train.iloc[1], feature_names = X.columns.tolist(),
show_feature_values=True)

石灰
LIME(本地可解释模型不可知解释)是一个用于解释机器学习算法所做预测的包。Lime 支持对来自各种分类器的单个预测的解释,并且内置了对 scikit-learn 的支持。
让我们使用 Lime 来解释我们之前训练的模型的一些预测。
石灰可以通过 pip 安装。
pip install lime
首先,我们构建解释器。这将训练数据集作为一个数组,模型中使用的功能的名称和目标变量中的类的名称。
import lime.lime_tabularexplainer = lime.lime_tabular.LimeTabularExplainer(X_train.values, feature_names=X_train.columns.values.tolist(), class_names=y_train.unique())
接下来,我们创建一个 lambda 函数,使用该模型对数据样本进行预测。这是借用这个优秀的,更深入的,关于石灰的教程。
predict_fn = lambda x: model.predict_proba(x).astype(float)
然后,我们使用解释器来解释一个选定例子的预测。结果如下所示。Lime 产生了一个可视化效果,显示了这些特征是如何促成这一特定预测的。
exp = explainer.explain_instance(X_test.values[0], predict_fn, num_features=6)
exp.show_in_notebook(show_all=False)

MLxtend
这个库包含了许多机器学习的辅助函数。这涵盖了像堆叠和投票分类器,模型评估,特征提取和工程和绘图。除了文档之外,这篇文章也是一个很好的资源,有助于更详细地理解这个包。
让我们使用 MLxtend 来比较投票分类器与其组成分类器的决策边界。
同样,它可以通过 pip 安装。
pip install mlxtend
我使用的导入如下所示。
from mlxtend.plotting import plot_decision_regions
from mlxtend.classifier import EnsembleVoteClassifier
import matplotlib.gridspec as gridspec
import itertoolsfrom sklearn import model_selection
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
下面的可视化一次只能处理两个特征,所以我们将首先创建一个包含特征proline和color_intensity的数组。我选择这些是因为它们在我们之前使用 ELI5 检查的所有特性中权重最高。
X_train_ml = X_train[['proline', 'color_intensity']].values
y_train_ml = y_train.values
接下来,我们创建分类器,使它们适合训练数据,并使用 MLxtend 可视化决策边界。输出显示在代码下方。
clf1 = LogisticRegression(random_state=1)
clf2 = RandomForestClassifier(random_state=1)
clf3 = GaussianNB()
eclf = EnsembleVoteClassifier(clfs=[clf1, clf2, clf3], weights=[1,1,1])value=1.5
width=0.75gs = gridspec.GridSpec(2,2)fig = plt.figure(figsize=(10,8))labels = ['Logistic Regression', 'Random Forest', 'Naive Bayes', 'Ensemble']for clf, lab, grd in zip([clf1, clf2, clf3, eclf],
labels,
itertools.product([0, 1], repeat=2)):
clf.fit(X_train_ml, y_train_ml)
ax = plt.subplot(gs[grd[0], grd[1]])
fig = plot_decision_regions(X=X_train_ml, y=y_train_ml, clf=clf)
plt.title(lab)

这绝不是一个用于解释、可视化和解释机器学习模型的详尽的库列表。这篇优秀的帖子包含了一长串其他有用的库供您尝试。
感谢阅读!
3 分钟内理解 Python 列表
让我们创建自己的动物公园,学习如何使用列表理解

列表理解是一种从现有列表创建新列表的强大方法。如果你开始使用 Python,理解列表可能看起来很复杂,但是你应该尽快熟悉它。您可以从列表中选择特定元素来创建新元素。你可以比较不同的列表,选择你需要的元素。你甚至可以用 Pandas 系列或者 Numpy 数组来理解列表。让我们深入了解一下!
什么是列表理解?
如果你有一个现有的列表,你可以选择一些元素来创建一个新的列表。你可以用一个标准的 Python 循环来做到这一点,但是用列表理解,你肯定需要更少的代码。列表可以被迭代,这使得这些对象非常方便。迭代只是一个表达式,表示你可以一个一个地遍历列表。
想象你有一个拥有不同动物的动物园。每只动物每年都需要接受一次动物医生的检查。动物公园是我们的名单。我们可以穿过动物园,一只一只地挑选动物。动物医生是我们的新名单,他接收我们的条目,在这个例子中是动物。
#Creating our animal park
animal_park = ['Rabbit','Rabbit','Rabbit','Rabbit',
'Cat','Cat','Cat','Cat','Cat','Cat','Cat',
‘Turtle’,’Turtle','Turtle','Turtle','Turtle','Turtle','Turtle',
'Dog','Dog',
'Kangaroo','Kangaroo','Kangaroo','Kangaroo','Kangaroo','Kangaroo']#Creating a new list for our animal doctor with all animals
animal_doctor = []
for animal in animal_park:
animal_doctor.append(animal)
我们的代码非常简单。首先我们创建了我们的动物公园,然后我们创建了我们的动物医生。下一步,我们将走遍我们的动物公园,把每只动物一只一只地交给我们的动物医生。
现在我们把这个循环重写为列表理解:
animal_doctor = [animal for animal in animal_park]
您可以看到,我们将行数从 3 减少到了 1。如果我们比较两个版本,我们会发现我们做的完全一样:

Figure 1: Comparison of the standard loop and List Comprehension
条件句——今天我们只想检查一下捕食者
在很多情况下,我们都想使用条件语句。在我们的标准循环中,它可能是这样的:
animal_doctor = []
for animal in animal_park:
if animal != 'Dog' and animal != 'Cat':
animal_doctor.append(animal)
如您所见,我们必须为条件语句添加另一行代码。我们的动物医生今天正在检查食肉动物。我们可以对我们的列表理解做同样的事情:
animal_doctor = [animal for animal in animal_park if animal != 'Dog' and animal != 'Cat']
我们仍然可以在一行中写出整个表达式,这比循环版本可读性更好。
如果你还不相信,列表理解是很快的
我们看到列表理解需要更少的代码,可读性更好。让我们比较一下标准循环和列表理解的速度:

Figure 2: Comparison of List Comprehension and a standard loop in terms of speed
如你所见,列表理解比标准循环快 51%。如果你可以使用列表理解从另一个列表中创建一个新列表,那么就一直使用它。没有理由使用较慢的标准循环。
结论
我们看到,如果基本概念清楚,列表理解很容易使用。它是从其他列表创建新列表的强大工具,如果您想从一个或多个现有列表创建新列表,应该总是使用它:它比标准循环可读性更好,需要的代码更少,速度更快。
如果您喜欢中级和高级数据科学,并且还没有注册,请随时使用我的推荐链接加入社区。
5 分钟内理解 Python 列表
对这些棘手的 Pythonic 命令行程序的可视化介绍
Just show me the code! Quick link: [**Jupyter Notebook on GitHub**](https://github.com/mrdbourke/python-list-comprehensions-tutorial/blob/master/python-list-comprehensions-in-5-minutes.ipynb)
你为什么想要理解 Python 列表?
为了节省代码行。
可能有更多的原因,但根据我的经验,这是唯一的原因——如果你有更好的原因,请留下评论,我会更新文章。
你可以用几行额外的代码完成完全相同的事情。但是浏览任何 Python 栈溢出问题,你很可能会发现有人在问Python 版本或Python 单行程序。
列表理解是用 iterables(列表)实现 Pythonic 式一行程序的一种方式。
单行定义:
- List =可以迭代的 Python 对象(iterable)。
- 迭代过 =逐个经历某事。
一个很好的例子是在收银台清空你的购物车,你把每样东西一件一件拿出来放在柜台上,你的购物车可以被认为是可重复的。
我们将用这个例子来看一些代码。我们在一家数学商店购物,所以我们买的都是数字。
# Create a list of numbers (our shopping cart)
cart = [3, 4, 12, 17, 19, 21, 23, 26, 30]# Pass items to the cashier
cashier = []
for item in cart:
cashier.append(item)
print(cashier)# The output is the same as the cart
Output: [3, 4, 12, 17, 19, 21, 23, 26, 30]
这里发生了什么?
- 我们在
cart中创建了一个项目列表 - 我们创建了一个空的
cashier列表(cashier还没有我们的任何项目) - 我们使用 for 循环来迭代我们的
cart中的每个item - 对于每个
item,我们用.append()将其添加到收银台列表中 - 然后我们用
print()检查cashier有哪些项目
我们怎样才能做同样的事情呢?
# Pass items to second cashier but with a list comprehension
cashier_2 = [item for item in cart]
print(cashier_2)# The output is the same as cashier (and the same as cart)
Output: [3, 4, 12, 17, 19, 21, 23, 26, 30]
这里发生了什么?
与上面完全相同的步骤,只是以不同的方式编写。我们来对比一下。

A non-list comprehension and list comprehension comparison of the same code.
cashier是用蓝色创建的。在左边,它是在自己的行上创建的。在右边,它是在其他事物被创造的同时被创造的。cart正在被绿色迭代。item创建为 for 循环运行。这一步与从购物车中一次取出一个item并将其交给收银员是一样的。cashier随着每个item更新为红色。唯一的区别在左边,需要一个.append()语句。右边的列表理解否定了对.append()语句的需要
我们用 1 行代码而不是 3 行代码完成了相同的步骤。这可能看起来不多,但是假设你有一个 1000 行的 Python 项目。你可以将你的项目从 1000 行减少到 300 行。这种减少不会总是发生,但它是如何用python 一行程序完成相同任务的一个例子。
你还能用 Python 列表理解做什么?
现在你已经有了一点直觉,让我们再看几个例子。
只处理偶数。我们该如何处理这件事?
通过使用一个条件。
- 条件 =另一种说法,真则做某事,假则停止。
# Make a new cart
cart = [5, 7, 9, 10, 12, 15, 19, 20, 22]# Only give cashier_3 even numbers
cashier_3 = []
for item in cart:
if item % 2 == 0:
cashier_3.append(item)
print(cashier_3)# Thanks to the conditional the output is only the even numbers
Output: [10, 12, 20, 22]
用列表理解怎么样?
cashier_3 = [item for item in cart if item % 2 == 0]
print(cashier_3)# The output is the same as above
Output: [10, 12, 20, 22]
这里发生了什么事?

Example of a non-list comprehension and a list comprehension with a conditional.
这类似于上面的例子,除了黄色的条件。
cashier_3以蓝色创建。cart正在被绿色迭代。item在 for 循环运行时被创建。- 当每个
item被传递到cashier_3时,它被检查是否与黄色的条件匹配。 - 只要满足黄色条件,每个
item都会以红色更新cashier_3。
100 多岁
cashier_4只接受 100 以上的奇数。你怎么能这样做?
# Reminder of what cart looks like
cart = [5, 7, 9, 10, 12, 15, 19, 20, 22]# Non-list comprehension for cashier_4
cashier_4 = []
for item in cart:
item += 100 # add 100 to each item to bring them over 100
if item % 2 == 1: # check to see if they're odd or not
cashier_4.append(item)
print(cashier_4)# The output is all odd numbers over 100
Output: [105, 107, 109, 115, 119]
现在用列表理解。
# List comprehension for cashier_4
cashier_4 = [item+100 for item in cart if item % 2 == 1]
print(cashier_4)# The output is all odd numbers over 100 (same as above)
Output: [105, 107, 109, 115, 119]
下一步是什么?
本教程仅仅触及了 Python 列表理解的皮毛。一旦您开始掌握它们,您将开始意识到它们对于编写简洁而强大的 Pythonic 代码是多么有价值。
如果你想了解更多,我建议查看 DataCamp 上的 Python 列表理解教程。
本文中的所有代码都可以在 GitHub 上找到,本文的视频版本可以在 YouTube 上找到。
对下一篇文章有建议?请随意留下回复或通过 Twitter 、 LinkedIn 、 Email 联系。
Python 列表从头开始!!!
让我们从基础到高级理解 Python 列表。

列表是 Python 中的一种数据结构,它充当同时保存或存储多个数据的容器。列表是可变的或可改变的有序元素序列。要了解更多关于 Python 列表的信息,你可以访问官方的 Python 列表文档。你必须记住的一点是,我们经常必须使用[ ]括号来声明列表。[ ]中的元素是列表的值。例如:
*# Creating an empty list called "list"* list = [ ]
*# Adding the values inside the list* list = [ 1, 2, 3, 4, 5]
*# Printing the list* list**[1, 2, 3, 4, 5]**
这里列表的名称为“列表,列表值为 1,2,3,4,5 。列表的优点是列表中的值不必是相同的类型,这意味着:
*# Adding the values irrespective of their data type: Integer, String, float.*list = [1, 2, 3, “Tanu”, 4.0, 4/2]
list**[1, 2, 3, 'Tanu', 4.0, 2.0]**
为了检查变量是否为列表,使用如下的" type "方法:
*# Create a list as list* list = [1, 2, 3, 4, 5]*# Use type method by passing the name of the list as an arguement type(list)***list**
1)创建、访问和计算列表的长度。
让我们创建一个名为“name”的列表,然后插入一些值,稍后我们可以在[ ]的帮助下使用索引访问列表中的元素,方法是将索引值放入列表中。然后,我们可以使用 len()方法计算列表的长度,只需将列表的名称作为参数传递给 len()方法,最后,我们还可以使用相同的 len()方法获得列表中各个元素的长度,但这次我们应该将索引位置指定为参数。
*# Creating a list called name* name = [“Tanu”, “Nanda”, “Prabhu”]
name**['Tanu', 'Nanda', 'Prabhu']***# Accessing the elements in the list* name[0] *# Tanu***‘Tanu’***# Calculating the length of the list.* len(name)**3***# Calculating the length of individual elements in a list* len(name[0]) * # length of "Tanu" is 4***4**
2)列表上的赋值运算符
列表上带有 "=" 的赋值不会产生副本。相反,赋值使两个变量指向内存中的一个链表。有时可以使用赋值操作符从一个列表复制到另一个列表。
*# Creating a list called name* name = [“Tanu”, “Nanda”, “Prabhu”]
name**[‘Tanu’, ‘Nanda’, ‘Prabhu’]***# Creating an empty list names* names = []
names**[]***# Using assignment operator on Lists doesn't create a copy.* names = name
*# Assigning the old list name to the new list names.* names**[‘Tanu’, ‘Nanda’, ‘Prabhu’]**
3)将两个列表添加到一个列表中。
通常追加两个列表可以使用 append()方法,但是追加也可以使用' + '(这里+不是加法的意思)而 + 可以用来将两个列表相加或者合并成一个列表,如下所示。
*# Creating a new list called Cars* cars = [“Mercedes”, “BMW”, “Audi”]
cars**['Mercedes', 'BMW', 'Audi']***# Creating a new list called bikes*
bikes = [“Honda”, “Yamaha”, “Aprilla”]
bikes**['Honda', 'Yamaha', 'Aprilla']***# Appending both the lists*
cars_bikes = cars + bikes
cars_bikes**['Mercedes', 'BMW', 'Audi', 'Honda', 'Yamaha', 'Aprilla']**
在列表中使用 FOR 和 IN
在 Python 中,中的和中的被称为构造,这些构造很容易使用,当你需要迭代一个列表时就可以使用它们。
*# Creating a list*
list = [1, 2, 3, 4, 5]
*# Assigning sum to 0*
sum = 0*# Using a for loop to iterate over the list*
for num in list:
sum = sum + num
print(sum) *# 15***15**
在列表中使用 IF 和 IN。
“ if 和“ in ”构造本身是一种测试元素是否出现在列表(或其他集合)中的简单方法,它测试值是否在集合中,返回 True/False。这也适用于 Python 中的字符串字符。
*# Creating a list* list = [1, 2, 3, 4, 5]
if 1 in list:
print(“True”)
else:
print(“False”)**True**
6)Python 中的 Range 函数。
Python 中的 range 函数用作循环结构的边界,Range 函数从 0 到 n-1 开始,不包括最后一个数字,如下所示。
*# Range starts 0 to n-1* for i in range(5):
print(i)**0
1
2
3
4**
7)python 中的 While 循环
Python 有一个标准的 while 循环,它的工作方式类似于 For 循环,首先你必须初始化,然后插入一个条件,最后递增。
*# Initialisin i to 0* i = 0
while i < 10:
print(i) # 0–9
i = i+ 1**0
1
2
3
4
5
6
7
8
9**
8)列举方法。
下面是一些最常见的列表方法,并附有示例。
a)列表追加方法
list.append()只是将一个元素追加到列表中,但从不返回任何内容,它只是将元素追加到现有列表中。
*# Creating a new list called list* list = [‘Tanu’, ‘Nanda’]
*# Before appending* list**[‘Tanu’, ‘Nanda’]***# Using the append operation on the current (“Prabhu”) is the value to be appended.* list.append(“Prabhu”)
*# After appending* list**[‘Tanu’, ‘Nanda’, ‘Prabhu’]**
b)列表插入方法
List.insert()操作用于将元素插入到指定索引位置的列表中。
*# Creating a new list called list* list = [‘Tanu’, ‘Nanda’]
# Before inserting
list**[‘Tanu’, ‘Nanda’]***# Insert Operation on the existing list, here the index position is 2.* list.insert(2, “Prabhu”)
# After Inserting
list**[‘Tanu’, ‘Nanda’, ‘Prabhu’]**
c)列表扩展方法
extend 方法将元素添加到新列表的末尾。它类似于 append,但是您必须将列表作为参数传递,而在 append 中不一定如此。
*# Creating a new list called list* list = [‘Tanu’, ‘Nanda’]
*# Before extending* list**[‘Tanu’, ‘Nanda’]***# Using extend but make sure that you put the elements in a []
# Without []* list.extend(“Prabhu”)
*# After extending* list**[‘Tanu’, ‘Nanda’, ‘P’, ‘r’, ‘a’, ‘b’, ‘h’, ‘u’]***# After []* list.extend([“Prabhu”])
list**[‘Tanu’, ‘Nanda’, ‘P’, ‘r’, ‘a’, ‘b’, ‘h’, ‘u’, ‘Prabhu’]**
d)列表索引法
list 中的 index 操作搜索列表中的元素,然后返回该元素的索引。如果该元素不在列表中,那么 index 方法返回一个值错误,告知该元素不在列表中。
*# Creating a new list called list* list = [‘Tanu’, ‘Nanda’]
*# Before indexing* list**[‘Tanu’, ‘Nanda’]***# After indexing, type the element that you want to index.* list.index(“Nanda”)**1***# If the element is not present then you get an error* list.index(“Mercedes”)**---------------------------------------------------------------------------****ValueErro Traceback (most recent call last)****<ipython-input-4-4f3441849c1e> in <module>()
----> 1 list.index("Mercedes")****ValueError: 'Mercedes' is not in list**
e)列表删除方法
list 中的 Remove 方法搜索元素,然后在匹配时移除列表中存在的元素。当元素不在列表中时,也会引发错误。
*# Creating a new list called list* list = [‘Tanu’, ‘Nanda’]
*# Before removing* list**[‘Tanu’, ‘Nanda’]***# After removing* list.remove(“Nanda”)
list**[‘Tanu’]**list.remove(‘Prabhu’)---------------------------------------------------------------------------**ValueError Traceback (most recent call last)****<ipython-input-8-9df87da8501a> in <module>()
----> 1 list.remove('Prabhu')****ValueError: list.remove(x): x not in list**
f)列表排序方法
顾名思义,sort 方法按升序对元素进行排序。这个方法不返回任何东西。
*# Creating a list called list*
list = [1, 4, 5, 2, 3]
*# Before sorting* list**[1, 4, 5, 2, 3]***# After sorting* list.sort()
list**[1, 2, 3, 4, 5]**
g)列表反转方法
顾名思义,reverse 方法反转整个列表。这个方法不返回任何东西。
*# Creating a list called list* list = [1, 2, 3, 4, 5]
*# Before Reversing* list**[1, 2, 3, 4, 5]**list.reverse()*# After the reverse* list**[5, 4, 3, 2, 1]**
h)列表弹出方法
Pop 方法移除并返回给定索引处的元素。如果省略了索引,则返回最右边的元素(大致与 append()相反)。
*# Creating a list called list* list = [1, 2, 3, 4, 5]
*# Before popping* list**[1, 2, 3, 4, 5]***# After popping* list.pop(1)**2**list**[1, 3, 4, 5]**
9)列出切片
切片列表意味着减少列表元素,它也可以用来改变列表的子部分。
list = [‘a’, ‘b’, ‘c’, ‘d’]
list**[‘a’, ‘b’, ‘c’, ‘d’]**list[1:-1] *## [‘b’, ‘c’]*
list**[‘a’, ‘b’, ‘c’, ‘d’]**list[0:2] = ‘z’ *## replace [‘a’, ‘b’] with [‘z’]*
list *## [‘z’, ‘c’, ‘d’]***[‘z’, ‘c’, ‘d’]**
将字符串转换成列表
这是最重要的技术,在我们的项目中,我们经常需要将字符串转换成列表,这是一种简单的方法。下面这个例子来自 geeksforgeeks.com。这里我们使用了字符串的 split 方法,split 方法只是从字符串中分离元素,然后广播到一个列表中,如下所示。
*# Creating a String* String = “Tanu”
String**‘Tanu’***# Checking for the type* type(String)**str***# Using the split method to split the string into a list.* list = list(String.split(“ “))
list**[‘Tanu’]**type(list)**list**
有时当你试图将字符串转换为列表时,会出现如下所示的错误,要解决这个问题,你只需将列表的名称改为其他名称,在上面的例子中,我将列表的名称改为“list ”,因此这经常会与列表方法产生名称冲突,所以如果你使用 Google Colab 或 Jupyter notebook,就会经常出现这个错误,然后你必须重新启动内核并再次运行所有的单元格。因此,解决方案只是更改列表的名称,然后在出现此错误时重启内核。
**---------------------------------------------------------------------------****TypeError Traceback (most recent call last)**[**<ipython-input-46-58e364821885>**](/<ipython-input-46-58e364821885>) **in <module>()
----> 1 list = list(String.split(" "))
2 list****TypeError: 'list' object is not callable**
因此,以上是 Python 中非常重要的列表技术或方法。大多数例子都是从谷歌的 Python 列表中引用的。我用一种简单的方式写了这个,这样每个人都能理解和掌握 Python 中的列表概念。如果你们对代码有什么疑问,评论区就是你们的了。
谢谢。
Python 模块,方便初学者进行数据预处理

Photo by Mika Baumeister on Unsplash
结构化数据中的特征可以有多种形式,包括连续、分类、日期时间等。通常,为了构建预测模型,我们会查看特征选择或特征工程,以收集更多见解或将更多相关数据输入到模型中。然而,在数据具有大量特征的情况下,特征工程会变得非常快。需要快速处理这些数据,并创建一个初始模型来更好地理解您的数据集或手头的问题。在任何特征工程之前创建模型有以下好处:
- 了解所有特性的相对特性和重要性
- 大致了解哪些模型可行,哪些不可行
- 提供帮助特征工程/特征选择的洞察力
fast . ai 的程序员机器学习简介使用了这种方法,并实现了杰瑞米·霍华德所说的“机器学习驱动的 EDA”。探索性数据分析是根据模型告诉你的内容进行的,而不是领域知识或预先确定的问题假设。
在这里,我用自己编写的 python 模块给出了一个简单的数据预处理实现。
该模块可以在 https://github.com/Benlau93/easy_processing 的 GitHub 中找到
您可以下载该模块并将其放在与笔记本相同的目录中,然后使用
import easy_processing as ep
为了测试这个模块,我将在 kaggle 竞赛的两个数据集上使用它。一个是回归问题,另一个是分类任务。两者都使用来自 sklearn.ensemble 库中的 RandomForest 模型。
首先,房价:高级回归技术数据集。
导入库和数据集
import numpy as np
import pandas as pddf = pd.read_csv("train.csv",low_memory=False)
X = df.drop(["SalePrice"],axis = 1)
y=df["SalePrice"]
我们从处理数据中缺失的值开始。proc_miss将连续变量中的 NaN 替换为相应特征列中非缺失值的中值
X, missing_val = ep.proc_miss(X)
返回的 missing_val 是一个字典,其中包含缺失值的列作为键,中值作为值被替换

为了处理分类变量,proc_cat将用户以列表形式给出的分类列转换成 pandas 分类对象。在没有给定cat_cols的情况下,具有“对象”数据类型的列被转换。max_cardi是将被转换为虚拟变量的特征的最大基数。基数只是指每个分类特征中的类的数量。
X, cat_dict = ep.proc_cat(X,max_cardi=5)
返回的 cat_dict 是一个字典,其中分类列作为键,Series.cat.categories 作为值

只需两行代码,您的数据就可以输入到模型中了。要检查是否所有要素都是数字格式,

训练模型,
from sklearn.ensemble import RandomForestRegressor
m = RandomForestRegressor(n_jobs=-1)
m.fit(X,y)
m.score(X,y)
获得的分数是 0.9711 (R2)
我们现在可以在我们的测试数据集上测试它,获得一个初始预测,提交给 kaggle 并获得一个初始分数。
df_test = pd.read_csv("test.csv",low_memory=False)
X_test, missing_val = ep.proc_miss(df_test,missing_val)
X_test, cat_dict = ep.proc_cat(X_test,cat_dict=cat_dict,max_cardi=5)
y_pred = m.predict(X_test)
对于测试/验证集,proc_miss需要一个 missing_val 参数,以便它替换测试集每一列中的相同中值。如果在不在训练集中的测试集的列中发现缺失值,将更新 missing_val 变量。同样的逻辑也适用于proc_cat,这里需要 cat_dict 作为参数,以确保编码的一致性。
y_pred 现在可以导出为 csv 格式,提交给 kaggle。该数据集的得分为 0.15296,在排行榜中排名前 68%。现在,您可以查看相对特性重要性、超参数调整和特性工程,以进一步提高您的分数。
第二个数据集我将使用保诚人寿保险评估
库和数据的加载
import numpy as np
import pandas as pd
import easy_processing as epdf = pd.read_csv("insurance_train.csv",low_memory=False)
X = df.drop(["Response"],axis = 1)
y=df["Response"]
经历与之前完全相同的步骤,但是使用 RandomForestClassifier 而不是 RandomForestRegressor
X, missing_val = ep.proc_miss(X)
X, cat_dict = ep.proc_cat(X,max_cardi=5)from sklearn.ensemble import RandomForestClassifier
m = RandomForestClassifier(n_jobs=-1)
m.fit(X,y)
m.score(X,y)
这里得到的分数是 0.98939054
处理测试集,
df_test = pd.read_csv("insurance_test.csv",low_memory=False)
X_test, missing_val = ep.proc_miss(df_test,missing_val)
X_test, cat_dict = ep.proc_cat(X_test,cat_dict=cat_dict,max_cardi=5)
y_pred = m.predict(X_test)
将 y_pred 提交给 kaggle 后,我在公开评分中获得了 0.50516 的分数,在私人排行榜中获得了 0.51187 的分数。远远不够好,但这是一个开始。
声明:fastai 对此有一个类似的、更优化、更高效的库。我的实现是一个简洁的版本,可以帮助初学者浏览整个代码并理解每个实现。所有文档也可以在 GitHub(https://github.com/Benlau93/easy_processing)中找到。
感谢阅读。
Python 中的面向对象编程——科里·斯查费和 DataCamp
面向对象编程(OOP) 是一种基于【Object】的编程范式,是一种将编程语言中包含变量的东西定义为“字段/属性”,功能为“方法”的方式。OOP**允许程序员创建自己的拥有方法和属性的对象。OOP 允许用户创建他们自己的可重复和有组织的对象。
在本文中,我试图总结我对 python 中 OOP 的看法。我要感谢何塞·马西亚尔·波尔蒂利亚、数据营团队和科里·谢弗他们精彩的描述性教程。这对我帮助很大。我会尽快为此添加一个 GitHub 库。
为什么上课?
类是具有方法和变量的可重用代码块。准确地说,类是对象的模板。类允许我们对数据和函数进行逻辑分组,这种方式易于重用,如果需要的话也易于构建。在这里,数据和函数被称为属性和方法。在下图中,定义了一个没有传递任何属性的类数据外壳。**pass**什么都不做,只是一个占位符。

Defining a class Datashell without passing any attributes
属性的初始化器或构造器

Class Datashell with Initializer/Constructor init of it.
**self**表示对象本身的实例。大多数面向对象语言将其作为隐藏参数传递给方法。任何东西都可以代替**self**,但是为了让其他人更容易理解,我们应该用**self**。要打印一个方法,我们需要在最后使用圆括号**()**。如果我们跳过**()**,那么它只打印方法而不是返回值。类、方法和实例通过下图被清晰地描述。
**<blockquote>**

Class hello with methods setdata and showdata and instances x and y
类变量
在 OOP 中,类级别的变量被称为类变量,而对象级别的变量被称为实例变量。下图描述了类变量的一般形式和示例。这里‘spam’被定义为类变量 数据 ,它在后面的几个方法* 中被用作实例变量。*

data as Class variable
如果我们通过调用类来改变类变量,那么所有地方的值都会改变,而如果我们通过实例来改变变量,那么只有那个值会改变。假设如果我们将数据的值更改为“非垃圾信息,那么包括数据在内的所有实例值都将被更改,但是如果我们在任何方法中更改实例变量,那么它将只在那个方法中。
类方法和静态方法
常规方法自动将实例作为第一个参数传递,我们称之为**self.** 类方法自动将类作为第一个参数传递,我们称之为**cls** 。静态方法不自动传递任何东西,它们不传递实例或类,它们的行为就像一个常规函数,它们与类有一些逻辑联系。以上所有例子中提到的方法都是常规方法。但是对于类方法和静态方法,我们需要在应用之前提到它[ **@classmethod / @staticmethod** ]。
*@classmethod
def set_raise(cls,amount):
cls.raise = amount*

Static method and class method
继承(创建子类)
继承允许我们从父类继承属性和方法。这是重用和降低复杂性的能力。我们可以创建子类并获得父类的所有功能,然后我们可以覆盖或添加全新的功能,而不会以任何方式影响父类。下面的例子清楚地描述了继承。

Inheritance in OOP
现在,如果我们想从另一个类中复制任何属性,并在其上添加更多的属性,那么我们可以通过以下方式来完成。我们可以在应该避免使用**self**的地方使用**super()** ,或者我们可以直接调用提到**self** 的类。
*class hi(hello):
def __init__(self, name, age):
**super().__init__(data)** *or * "**hello.__init__(self, data)"***
特殊(魔法/邓德)方法
特殊的方法允许我们模拟 python 中的一些内置行为。通过定义我们自己的特殊方法,我们将能够改变一些内置的行为和操作。特殊方法总是被**__method__**包围。很多人称它们为双下划线 Dunder 。下面是一些常见的特殊方法:
**__init__(self)** : 当我们创建我们的对象时隐式调用,它设置所有属性。
**__repr__(self)** :对象的明确表示,应该用于调试和日志记录
**__str__(self)** :对象的可读表示,旨在向最终用户显示。
**__add__(self)** :添加对象。
**__len__(self)** :生成物体的长度。

Some special methods
属性装饰器(Getter、Setter 和 Deleters)
Property decorator 允许我们定义一个方法,但是我们可以像访问属性一样访问它。在下面的例子中,name 是一个方法,但是我们可以像调用属性一样调用它。

Using Property to call a method just like an attribute
我对 python 中 OOP 的了解到此为止。我已经尽力在我的限度内详细描述了。如果您能对此提出意见或批评,我们将不胜感激。我不是 CS 出身,但我每天都在学习。如果你喜欢这篇文章,那么你可以看看我的另一篇关于Markdown Cells——Jupyter 笔记本的文章。
我不知道如何开始。这将是我第一个与编程相关的故事。今天我将努力缩小…
towardsdatascience.com](/markdown-cells-jupyter-notebook-d3bea8416671)
参考链接:
- https://camp . data camp . com/courses/object-oriented-programming-in-python/getting-ready-for-object-oriented-programming?ex=1
- https://www.youtube.com/playlist?list = PL-osie 80 tetshiuoqkhwlxsibidseytc
- https://www.udemy.com/course/complete-python-bootcamp/*
Python、甲骨文 ADWC 和机器学习
如何使用开源工具分析通过 Oracle 自治数据仓库云(ADWC) 管理的数据。
介绍
Oracle 自治数据库是 Oracle 数据库技术的最新发展。帮助管理和分析云中大量数据的技术更容易、更快、更强大。
ADWC 是这种技术在数据仓库和高级数据分析方面的专业化。这是一种简化上传、转换数据并使业务用户和非数据库管理员能够访问高级分析工具的技术。可以说,这些工具是数据科学家包袱的一部分。
然而,在这篇文章中,我不仅想深入探讨 ADWC 可用的工具,而且想从一个稍微不同的角度来看问题。
我将试着用一个例子来解释:想象自己是一名数据科学家,知道来自开源世界的所有机器学习和数据探索工具。为了给出具体的例子,工具例如:
- 计算机编程语言
- Jupyter 笔记本
- NumPy,Pandas,Scikit-学习模块
- 最终 Keras(如果我们想使用深度学习模型的话)
但是需要分析的数据存储在 ADWC 中。此外,我们的“英雄”了解一些 SQL 知识(好吧,也许相反才奇怪),并且理解一些处理应该用 SQL 来完成。
我们的“探索者”如何做到两全其美?
自上而下。
我不想把这篇文章变成一系列命令,在没有清楚理解的情况下执行。所以,即使我想提供所有的细节来简化那些想尝试追随我的人的生活,我也想自上而下地进行。我将首先尝试将事情解释到更高的抽象层次,没有太多的细节,但我保证稍后会添加实现的细节。
云。
不管你喜不喜欢,云会一直存在。目前,ADWC 是一项公共云 Oracle 服务。
为了避免在我的 MacBook 和云数据中心之间传输太多数据,我想到了一个主意,就是将所有的分析工具放在 Oracle 云基础设施(OCI)中的一个虚拟机上。对我来说,一个拥有 Ubuntu Linux 和八个 OCPU(核心)的虚拟机就足够了,只在需要的时候才打开。
在这台虚拟机上,我安装了:
- Anaconda Python 发行版
- 一些 Python 模块:Pandas、cx_Oracle、scikit-learn
- 用于配置笔记本扩展的服务器(nbextensions)
- Oracle 客户端库(即时客户端 18.3)
- libaio1 系统库
我还在 Anaconda 中创建了一个专用的虚拟 Python 环境(用命令“source activate
只要有一点经验和耐心,并在两个小时内进行一些“谷歌搜索”,环境就可以运行了。
ADWC 是从 Oracle 云基础架构控制台创建的。你只需要指定数据库名称,磁盘空间(TB)和 OCPU 的数量。大约十分钟后,它就启动并运行了;甚至连喝咖啡休息的时间都没有。
如何连接到甲骨文 ADWC 公司
没什么复杂的。最后,ADWC 始终是一个 Oracle 数据库,从客户端的连接是以 Oracle 工作人员熟知的方式完成的。唯一真正相关的细节是,连接必须受到保护并且被加密。
因此,要连接任何客户端,我们必须:
- 从我们 ADWC 的控制台下载钱包,其中包含识别您正在连接的用户的详细信息,以及验证服务器和加密所需的证书
- 将钱包放在我们的虚拟机上的一个目录中,并对其进行解包(它以 zip 文件的形式提供)
- 拥有有权读取数据的数据库用户的有效凭据(用户和密码)
也许在这个阶段,我们的数据科学家会得到一位更有“Oracle 经验”的同事的帮助,或者有一点耐心阅读文档。
如何与数据库交互
真正的乐趣从这里开始!
将要使用的工具是 Jupyter Notebook ,其中我启用了“ExecuteTime”扩展来记录每条指令的执行时间。
在笔记本中,我们可以通过两种方式与数据库交互(执行查询等):
- 使用 Python 指令
- 直接使用 SQL 语句
美妙之处在于,我们可以根据自己的喜好、便利和当下的灵感,将这两种模式结合起来。今天,SQL 让我厌烦透了,我更喜欢“Pythonic 式的生活方式?”,好吧!不,用 Python 太慢或者太复杂,我用 SQL 来做。
让我们试着更具体一些。让我们从 Python 开始(今天…)
import cx_Oracle
import pandas as pd
import config as cfgconnection = cx_Oracle.connect(cfg.USER, cfg.PWD, ‘db4ml_medium’)data = pd.read_sql("select * from my_credit_scoring", con=connection)
让我们暂时把细节放在一边。相反,我们应该赞美简单:仅用两行代码,我们就将 MY_CREDIT_SCORING 表中包含的所有数据加载到一个 Pandas DataFrame 中。
在这一点上,我们敬爱的数据科学家并没有从快乐中退缩。他已经猜到了如何完成这个故事:如果他愿意,他可以按照几十本书名为“机器学习和 Python”的书教给他的方式进行,并且必须只(嗯…只?)做一些预处理、数据清理、缩放、模型建立、超参数优化、模型训练、与过拟合作斗争等。但是,它不需要 Oracle 的任何其他知识。
我们现在给它一个特定的(否则时间延长的目的是什么?):从 ADWC 数据库加载 100,000 条记录(每条记录有 86 列)只需要 6 秒多一点的时间。这也是因为 Oracle 云基础架构中的高速低延迟网络。
与数据库交互的另一种方式是使用笔记本的"神奇扩展 sql :
%load_ext sql%sql $cfg.CONN_STRING%%sql
select count(*) from my_credit_scoring
这里是另一个细节:第二条指令连接到数据库,我将连接字符串(CONN_STRING)存储在 config.py 文件中,不想在笔记本中显式显示用户名和密码。
然而,在这里,我想添加另一个细节(该系列的“我们希望结合两个世界的优点”):我们的数据科学家可以通过 Oracle 管理模式中一个名为 CREDIT_SCORING_100K 的表“只读”访问原始数据。但是因为他假设(您永远不知道)他想在数据库中对这些数据进行一些处理(清理等),所以他在自己的私有方案中创建了一个表的“私有”副本。
这个在 SQL 中完成的操作只用一条指令就完成了!
%%sql
create table my_credit_scoring as select * from admin.credit_scoring_100k
只需要 7 秒钟。在这里,我们看到了来自 ADWC 的简单和力量。(也许我选择一个 1000 万条记录的表会做得更好,会更有效果,但我手头没有,抱歉)。Oracle ADWC 基于 Oracle 数据库云服务器技术,这样的指令充分利用了数据库云服务器的存储技术和 SQL 卸载。
合理的好奇心:但是如果我想结合“% sql”和 Python ?
好吧,这里有个例子:
%%sql
result << select credit_score_bin, count(*) num from my_credit_scoring group by credit_score_bindata_bin = result.DataFrame()
并且结果再次存储在熊猫数据帧中。
但是模型是用来做什么的呢?
对……除了技术,我们不能忽视商业目的。在被选择来说明该方法的例子中,数据被用来创建“信用评分”模型。一种分类模型,银行或金融公司可以使用它来决定一个人是否是一个好的债务人,从而决定是否接受他的抵押和/或贷款申请。
具有这种数据的这种类型的模型是通过“监督学习”方法和二元分类器(CREDIT_SCORE_BIN 列只包含两个不同的值)实现的:我们训练模型,然后我们给模型一系列与信贷申请人相关的数据,模型说:“信用良好,继续”或“其他信用,不继续”。
复杂性。
在这里,我们的 ADWC 对此无能为力,或者只能提供帮助。现实世界中的数据是“脏的”,有“漏洞”,必须清理和完成。从这个角度来看,我示例中的表格是真实的。
(100,000 个样本中)有数千个数据缺失。并且许多 scikit-learn ML 算法在这样的条件下不能很好地工作。
有许多特征(85)并且许多是分类的,因此必须被编码(翻译成数字)。对于数字特征,需要将它们都放在相同的范围内。
一旦数据被加载到 Pandas 数据框架中,我们就可以使用“仅 Python”方法,处理时间是可以接受的。
在这里,最起码的智慧会激励你不要立即尝试一个非常复杂的模型,包括所有的特征,而是从几十个开始,建立一个比较更复杂和清晰的模型的基准。
第一个基准是显而易见的:模型的准确性必须高于 50%,否则我们应该换工作(好吧,一个通过扔硬币来决定是否批准贷款的官员其实不是最好的,但在没有其他工具的情况下……)。
模特就像孩子,他们学习我们教的东西。
哦对了,总有“算法偏差”的问题。我们必须尽可能确定,不要通过“只”插入某些类型的数据来限制预测。
例如,如果只有很少一部分样本显示“良好信用”,我们的“指令算法”将知道它必须总是回答:“其他信用”。
在 SQL 中,检查很容易:
%%sql
select credit_score_bin, count(*) from my_credit_scoring group by credit_score_bin
- 良好信用:25%
- 其他信贷:75%
这可不错,显然那些发放贷款的人倾向于“谨慎”,过去经历了太多的“苦难”。
机器学习模型
即使我们将主题“如何与 Oracle ADWC 交互”留在这里,一些人也会有兴趣阅读如何用 scikit-learn 制作二进制分类器。
我想澄清一点:实现“一个模型”的几行 Python 代码并不是这个问题的严肃答案。模型具有需要优化的“超参数”,这需要工作(网格优化),从计算的角度来看,这是非常繁重的工作。
但是我们可以满足好奇心(当然,我试图快速建立一个模型),而无需花费太多时间进行超参数优化。但只是警告说,没有这项活动,我们只是朝着解决问题迈出了第一步。
另一个问题:用哪个算法?
回答:“T这里没有免费的午餐 ”(维基百科)。
也就是说,我选择使用支持向量机(SVM) 。这是一种通常在具有许多特征的问题中给出良好结果的算法。
什么是 SVM?或多或少,在每个分类器中,我们可以说我们试图找到两个集合之间的分离面。如果我们能找到一个超平面,这个问题可以说是“线性可分的”。
SVM 中的支持向量是最接近决策面的数据点。
SVM 将利润最大化。此外,使用“非线性”核(如 rbf ),我们可以将数据点投影到一个更高维的空间中,其中两个集合(良好信用,其他信用)是线性可分的(这或多或少是这个想法)。

A quick explanation of SVM
使用 scikit-learn,Python 中的代码是:
from sklearn.svm import SVCsvm = SVC(kernel = 'rbf', C = 1.0, gamma=0.1, random_state = 0)svm.fit(X_train_std, y_train)print('Train accuracy:', svm.score(X_train_std, y_train))
Train accuracy: 0.88145print('Test accuracy:', svm.score(X_test_std, y_test))
Test accuracy: 0.8978987
一些注意事项:
- 我选择使用非线性核( rbf) ,假设问题不是线性可分的;实际上,这个选择被这样一个事实所证实,使用线性核,我的精度大约低了三个点;
- RBF 代表径向基函数
- 将超参数(C,γ)的两个值设置为起点;优化在这里适用;
- 该模型有轻微的过度拟合(90%对 88%)。
我们得到的精确度?好的 88% 。考虑到这是快速的第一步,还不错。另一个比较连续的更复杂模型的基准。
如果我们想改进模型,首先要做的重要工作是对超参数进行优化。通过 GridSearchCV 类,可以使用 VM 的所有处理能力(因此是 8 个内核,甚至不是很多)来完成
代码示例:
gsc = GridSearchCV(
estimator=SVR(kernel='rbf'),
param_grid={
'C': [0.1, 1, 100, 1000],
'epsilon': [0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5, 10],
'gamma': [0.0001, 0.001, 0.005, 0.1, 1, 3, 5]
},
cv=5, scoring='neg_mean_squared_error', verbose=0, n_jobs=-1)
一些反思
数据在 ADWC,受到保护,我们没有在我们的虚拟机上下载任何数据。从安全的角度来看。
在给定的前提下(熟知开源的数据科学家),我主要使用 scikit-learn 和 pandas 实现了这个目的,但也使用了 ADWC 提供的一些优势明显的工具(例如:Oracle 表的副本)。
我们所做的事情很容易理解,即使对 Oracle 没有太多经验的人也能理解。我希望如此。
模型开发使用 ADWC 吗?在某种程度上。我在 VM 上用 Python 和笔记本做了很多,在 ADWC 上做的比较少。但这是预见到的(还是那句话,看前提)。
处理时间?
在这里,演讲可能会很长。即使训练集中有 80000 个样本(80:20 分割), SVM 的训练也只需要大约 10 分钟。如果我们看看 CPU 的使用情况,我们会发现实际上只有一个线程在工作。因此,即使有这么多可用的内核,我们也无法使用它们。但是,如前所述,计算复杂的部分是超参数的优化,这种操作需要 N 次模型训练(对于超参数的每次选择)。利用所有可用的内核,该部分可以轻松实现并行化。正是在这一点上,拥有如此多的内核可以带来改变(而云有助于实现这一点)。
根据我们的选择,我们没有使用 ADWC 中包含的算法实现,作为 Oracle 高级分析选项的一部分。使用这个实现,我们可以利用 ADWC 的并行引擎。
Python?好吧。好消息是,在甲骨文开放世界(2018 年 10 月)上,甲骨文宣布推出一个 Python 接口,可以使用所有甲骨文高级分析算法。这款名为 OML4Py 的接口即将面世。在以后的文章中,我将对此进行研究。
一些细节
查找如何安装 cx_Oracle 的信息的最佳位置:
[## cx_Oracle 7 安装- cx_Oracle 7.1.1 文档
编辑描述
cx-oracle.readthedocs.io](https://cx-oracle.readthedocs.io/en/latest/installation.html#quick-start-cx-oracle-installation)
所有的细节。
像往常一样,时间不够做我想做的事情。这篇文章变得比我想要的还要长。
因此,我决定用另一篇文章来讲述所有的细节。回头见。
极致性能的 Python 熊猫
今天,我们都在简单的 Python 工具(熊猫、 Scikit-learn )、可伸缩的 Spark 和 Hadoop 以及可操作的 Kubernetes 之间做出选择。我们最终会用光它们。我们保留了独立的面向 Python 的数据科学家团队、Java 和 Scala Spark 大师,以及一大批开发人员来管理这些孤立的解决方案。
数据科学家与熊猫一起探索。然后,其他数据工程师团队对相同的逻辑进行重新编码,并使其大规模工作,或使用 Spark 使其与直播流一起工作。当数据科学家需要改变逻辑或为他/她的模型使用不同的数据集时,我们会一次又一次地经历这种迭代。
除了处理业务逻辑,我们还在 Hadoop 或 Kubernetes 甚至两者上构建集群,并通过整个 CI/CD 管道手动管理它们。底线是我们都在努力工作,但没有足够的业务影响来证明这一点…
如果您可以用 Python 编写简单的代码,并且运行起来比使用 Spark 更快,不需要任何重新编码,并且没有 devops 开销来解决部署、扩展和监控问题,会怎么样?
我猜你可能会“说我是个梦想家”😊。我是一个梦想家,但我不是唯一的一个!这篇文章将证明今天使用 NVIDIA 孵化的免费开源数据科学加速平台 Nuclio 和 RAPIDS 是可能的。
在过去的几个月里,我们一直在与 NVIDIA 密切合作,将 RAPIDS 与 Nuclio 开源无服务器项目和 Iguazio 的 PaaS 进行整合。现在,使用相同的 Python 代码,以最小的操作开销(由于采用了无服务器方法),就有可能实现大幅加快的数据处理速度和可伸缩性。
我将展示处理由基于 Json 的日志组成的实时数据的同样广泛流行的用例。我将对它执行一些分析任务,并将聚合结果转储为压缩的 Parquet 格式,以便进一步查询或 ML 训练。我们将研究批处理和实时流(是的,用简单的 Python 代码实现实时流)。但首先,概述一下。
是什么让 Python 速度慢,不可伸缩?
当在小数据集上处理 pandas 时,性能还不错,但前提是整个数据集适合内存,并且处理是在 pandas 和 NumPy 层下用优化的 C 代码完成的。处理大量数据涉及密集的 IO 操作、数据转换、数据复制等。很慢。由于臭名昭著的 GIL,Python 本质上是同步的,在处理复杂任务时效率非常低,异步 Python 更好,但会使开发复杂化,并且不能解决固有的锁定问题。
像 Spark 这样的框架具有异步引擎( Akka )和内存优化数据布局的优势。它们可以将工作分配给不同机器上的多个工作人员,从而带来更好的性能和可伸缩性,使它们成为事实上的标准。
急流城给 Python 注入了类固醇
我们在 NVIDIA 的朋友想出了一个绝妙的主意:保留 pandas、Scikit-learn 和 XGBoost 等流行框架的面向 Python 的 API,但在 GPU 中以高性能 C 代码处理数据。他们采用了内存友好的 Apache Arrow 数据格式来加速数据传输和操作。
RAPIDS 支持数据 IO (cuIO)、数据分析( cuDF )和机器学习( cuML )。这些不同的组件共享相同的内存结构,因此数据接收、分析和 ML 的管道基本上无需将数据来回复制到 CPU 中即可运行。
下面的例子演示了一个使用 pandas API 读取大型 Json 文件(1.2GB)和聚合数据的用例。我们可以看到,同样的代码在使用 RAPIDS ( 查看完整笔记本在这里)的情况下运行快了 30 倍,如果我们比较没有 IO 的计算,它快了 100 倍,这意味着我们有空间对数据进行更复杂的计算。

我们使用了单个 GPU ( 英伟达 T4 ),这使服务器的价格增加了约 30%,性能提高了 30 倍。我们刚刚使用几行 Python 代码每秒处理了 1gb 的复杂数据。哇!!
如果我们将这段代码打包到一个无服务器函数中,它可以在每次用户请求时运行,或者定期运行,并读取或写入动态连接的数据卷。
Python 可以实现实时流吗?
你试过用 Python 做实时流吗?嗯,我们做到了。以下代码摘自 Kafka 最佳实践指南,它从一个流中读取数据并做最少的处理。

问题是 Python 本质上是同步的,当涉及到实时或复杂的数据操作时,效率相当低。这个程序每秒只生成几千条消息,而且没有做任何有趣的工作。当我们添加上一个示例中使用的 json 和 pandas 处理时(参见笔记本),性能进一步下降,我们每秒只处理 18MB。那么,我们需要回到 Spark 进行流处理吗?
不等等。
最快的无服务器框架是 Nuclio,它现在也是 kube flow(Kubernetes ML 框架)的一部分。Nuclio 运行由实时和高度并发的执行引擎包装的各种代码语言。Nuclio 并行运行多个代码实例(使用高效的微线程),无需额外编码。Nuclio 处理流程内和跨多个流程/容器的自动伸缩(参见这篇技术报道博客)。
Nuclio 以高度优化的二进制代码处理流处理和数据访问,并通过简单的函数处理程序调用函数。支持 14 种不同的触发或流协议(包括 HTTP、 Kafka 、 Kinesis 、Cron、batch),通过配置指定(无需更改代码),支持快速访问外部数据卷。单个 Nuclio 功能每秒可以处理数十万条消息,吞吐量超过千兆字节/秒。
然而最重要的是,Nuclio 是当今唯一一个具有优化的 NVIDIA GPU 支持的无服务器框架。它知道如何确保 GPU 利用率最大化,并在需要时扩展到更多进程和 GPU。
我这里明显有偏见,但也全是真的。
无需开发运维,流处理速度提高 30 倍
让我们结合 Nuclio + RAPIDS 来获得 GPU 加速的、基于 Python 的流处理的完整体验。下面的代码与批处理情况没有太大的不同,我们只是将它放在一个函数处理程序中,并将传入的消息收集到更大的批处理中,以减少 GPU 调用(参见完整的笔记本)。

我们可以用 HTTP 或 Kafka 触发器测试相同的功能:在这两种情况下,Nuclio 都将处理并行性,并将流分区分配给多个工作器,而无需我们进行任何额外的开发工作。我们测试的设置使用的是一个 3 节点 Kafka 集群和一个单 Nuclio 函数进程(在一个带有一个 NVIDIA T4 的双插槽英特尔服务器上)。我们设法处理 638 MB/s,这比编写自己的 Python Kafka 客户端快 30 倍,并且它可以自动扩展以处理任何数量的流量。而且它使用了小而琐碎的 Python 代码!!
在我们的测试中,我们注意到 GPU 没有得到充分利用,这意味着我们可以对数据进行更复杂的计算(连接、ML 预测、转换等)。)同时保持相同的性能水平。
好吧,我们用更少的开发获得了更快的性能,但是无服务器解决方案的真正好处在于它们是“无服务器的”(阅读我的文章)。拿同样的代码,在笔记本(见这个笔记本例子)或你最喜欢的 IDE 中开发,在一个命令中它被构建、打包并运送到一个 Kubernetes 集群,该集群具有完整的工具(日志、监控、自动扩展……)和安全加固。
Nuclio 与 Kubeflow 管道整合。构建多级数据或 ML 管道,只需最少的努力即可实现数据科学工作流程的自动化,收集执行和工件元数据,从而轻松重现实验结果。
在这里下载 Nuclio 并将其部署在您的 Kubernetes 上(参见 RAPIDS 示例)。
查看这篇相关文章Hadoop 之后的生活:让数据科学为您的企业服务
Python 熊猫数据框基础知识
让我们从零开始了解熊猫数据框架的基本知识。

Credits: codebasics
在开始之前,让我先给你介绍一下 Pandas ,Pandas 是一个 python 库,它为 Python 编程语言的数据分析工具提供了高性能、易于使用的数据结构,如系列、数据框和面板。此外,Pandas 数据帧由主要组件组成,即数据、行和列。要使用熊猫的库和它的数据结构,你所要做的就是安装并导入它。要更好地理解和安装指南,请参阅 Pandas 库的文档。这里的完整代码可以在我的 GitHub 页面上找到。
可应用于熊猫数据框的基本操作如下所示。
- 创建数据框。
- 对行和列执行操作。
- 数据选择、添加、删除。
- 处理缺失数据。
- 重命名数据帧的列或索引。
1。创建数据框。
Pandas 数据框可通过从外部现有存储(如数据库、SQL 或 CSV 文件)加载数据来创建。但是熊猫数据帧也可以从列表、字典等创建。创建熊猫数据框的方法之一如下所示:
# import the pandas library
import pandas as pd
# Dictionary of key pair values called data
data = {'Name':['Ashika', 'Tanu', 'Ashwin', 'Mohit', 'Sourabh'],
'Age': [24, 23, 22, 19, 10]}
data**{'Age': [24, 23, 22, 19, 10], 'Name': ['Ashika', 'Tanu', 'Ashwin', 'Mohit', 'Sourabh']}**# Calling the pandas data frame method by passing the dictionary (data) as a parameter
df = pd.DataFrame(data)
df

2。对行和列执行操作。
数据帧是一种二维数据结构,数据存储在行和列中。下面我们可以对行和列执行一些操作。
选择列:为了选择一个特定的列,我们所能做的就是调用数据框内的列名。
# import the pandas library
import pandas as pd
# Dictionary of key pair values called data
data = {'Name':['Ashika', 'Tanu', 'Ashwin', 'Mohit', 'Sourabh'],
'Age': [24, 23, 22, 19, 10]}
data**{'Age': [24, 23, 22, 19, 10], 'Name': ['Ashika', 'Tanu', 'Ashwin', 'Mohit', 'Sourabh']}**# Calling the pandas data frame method by passing the dictionary (data) as a parameter
df = pd.DataFrame(data)
# Selecting column
df[['Name']]

选择行: Pandas 数据框提供了一个名为“loc”的方法,用于从数据框中检索行。此外,还可以通过使用“iloc”功能来选择行。
# Calling the pandas data frame method by passing the dictionary (data) as a parameter
df = pd.DataFrame(data)
# Selecting a row
row = df.loc[1]
row**Name Tanu
Age 23
Name: 1, dtype: object**
要选择一个特定的列,我们所能做的就是调用数据框内的列名。如上所述,要使用“loc”方法,您必须将数据框的索引作为参数传递。loc 方法只接受整数作为参数。所以在上面的例子中,我想访问“Tanu”行,所以我将索引 1 作为参数传递。现在给你们一个快速作业,用“iloc”方法,告诉我结果。
3。数据选择、添加、删除。
您可以在语义上将 DataFrame 视为一个相似索引系列对象的字典。获取、设置和删除列的语法与类似的字典操作相同:
# import the pandas library
import pandas as pd
# Dictionary of key pair values called data
data = {'Name':['Ashika', 'Tanu', 'Ashwin', 'Mohit', 'Sourabh'],
'Age': [24, 23, 22, 19, 10]}# Calling the pandas data frame method by passing the dictionary (data) as a parameter
df = pd.DataFrame(data)
# Selecting the data from the column
df['Age']**0 24
1 23
2 22
3 19
4 10
Name: Age, dtype: int64**
可以像使用字典一样删除列,只需使用 del 操作。
del df[‘Age’]
df

可以使用插入功能添加数据。插入功能可用于在列中的特定位置插入:
df.insert(1, ‘name’, df[‘Name’])
df

4。处理缺失数据。
当我们访问大数据集时,丢失数据会发生很多次。它经常像 NaN(不是数字)一样出现。为了填充那些值,我们可以使用“isnull()”方法。该方法检查数据帧中是否存在空值。
检查缺失值。
# importing both pandas and numpy libraries
import pandas as pd
import numpy as np# Dictionary of key pair values called data
data ={‘First name’:[‘Tanu’, np.nan],
‘Age’: [23, np.nan]}df = pd.DataFrame(data)
df

# using the isnull() function
df.isnull()

如果空值不存在,isnull()返回 false,如果为空值,则返回 true。现在我们已经找到了丢失的值,下一个任务是用 0 填充这些值。这可以如下所示完成:
df.fillna(0)

5。重命名数据帧的列或索引。
要为数据框的列或索引值赋予不同的值,最好使用。rename()方法。为了更好地理解,我特意更改了列名。
# import the pandas library
import pandas as pd
# Dictionary of key pair values called data
data = {‘NAMe’:[‘Ashika’, ‘Tanu’, ‘Ashwin’, ‘Mohit’, ‘Sourabh’],
‘AGe’: [24, 23, 22, 19, 10]}# Calling the pandas data frame method by passing the dictionary (data) as a parameter
df = pd.DataFrame(data)
df

newcols = {
‘NAMe’: ‘Name’,
‘AGe’: ‘Age’
}
# Use `rename()` to rename your columns
df.rename(columns=newcols, inplace=True)
df

# The values of new index
newindex = {
0: ‘a’,
1: ‘b’,
2: ‘c’,
3: ‘d’,
4: ‘e’
}
# Rename your index
df.rename(index=newindex)

以上是 Python 中熊猫数据框架的非常重要的技术或方法。一些例子是从 GeeksforGeeks 引用的。我用一种简单的方式写了这个,这样每个人都能理解和掌握 Python 中数据框的概念。如果你们对代码有什么疑问,评论区就是你们的了。
谢谢你。
Python、性能和 GPU
Python 中使用 GPU 加速器的状态更新
这篇博文是在最近的 PASC 2019 大会上以谈话形式发表的。 演讲的幻灯片在这里 。
行动纲要
我们正在改进 Python 中可扩展 GPU 计算的状态。
这篇文章展示了当前的状态,并描述了未来的工作。它还总结并链接了最近几个月的其他几篇博客文章,这些文章深入到不同的主题,供感兴趣的读者阅读。
概括地说,我们简要地涵盖了以下几个类别:
- 用 CUDA 写的 Python 库比如 CuPy 和 RAPIDS
- Python-CUDA 编译器,特别是 Numba
- 使用 Dask 扩展这些库
- 与 UCX 的网络通信
- 用康达包装
GPU 加速的 Python 库的性能
对于 Python 程序员来说,获得 GPU 性能的最简单方法可能就是使用 GPU 加速的 Python 库。这些提供了一组通用的操作,它们被很好地调整和集成在一起。
许多用户知道用于深度学习的库,如 PyTorch 和 TensorFlow,但还有其他几个用于更通用计算的库。这些倾向于复制流行的 Python 项目的 API:
这些库构建了流行 Python 库的 GPU 加速变体,如 NumPy、Pandas 和 Scikit-Learn。为了更好地理解相对性能差异 Peter Entschev 最近整理了一个基准测试套件来帮助进行比较。他制作了下图,展示了 GPU 和 CPU 之间的相对加速比:

那里有很多有趣的结果。彼得在他的博客中对此进行了更深入的探讨。
然而,更广泛地说,我们看到了性能的可变性。我们关于 CPU 快慢的心理模型并不一定适用于 GPU。幸运的是,由于一致的 API,熟悉 Python 的用户可以很容易地试验 GPU 加速,而无需学习 CUDA。
Numba:将 Python 编译成 CUDA
另见本 最近关于 Numba stencils 的博文以及附带的 GPU 笔记本
像 CuPy 和 RAPIDS 这样的 GPU 库中的内置操作,涵盖了最常见的操作。然而,在现实世界的设置中,我们经常会发现需要编写一些自定义代码的混乱情况。在这些情况下,切换到 C/C++/CUDA 可能会很有挑战性,尤其是对于主要是 Python 开发人员的用户。这就是 Numba 的用武之地。
Python 在 CPU 上也有同样的问题。用户通常懒得学习 C/C++来编写快速定制代码。为了解决这个问题,有像 Cython 或 Numba 这样的工具,它们让 Python 程序员无需学习 Python 语言以外的知识就能编写快速的数字代码。
例如,Numba 在 CPU 上将 For 循环风格的代码加速到 500 倍以下,从缓慢的 Python 速度提高到快速的 C/Fortran 速度。
**import** **numba** *# We added these two lines for a 500x speedup*
@numba.jit *# We added these two lines for a 500x speedup*
**def** sum(x):
total = 0
**for** i **in** range(x.shape[0]):
total += x[i]
**return** total
在不切换 Python 上下文的情况下下降到底层高性能代码的能力是有用的,特别是如果您还不知道 C/C++或者没有为您设置的编译器链的话(目前大多数 Python 用户都是这种情况)。
这种优势在 GPU 上更加明显。虽然许多 Python 程序员懂一点 C 语言,但很少有人懂 CUDA。即使他们这样做了,他们可能也很难设置编译器工具和开发环境。
进入 numba.cuda.jit Numba 的 cuda 后端。Numba.cuda.jit 允许 Python 用户以交互方式创作、编译和运行用 Python 编写的 cuda 代码,而无需离开 Python 会话。这是一个在 Jupyter 笔记本中编写模板计算来平滑 2d 图像的图像:

下面是 Numba CPU/GPU 代码的简化对比,比较编程风格。GPU 代码的速度比单个 CPU 内核快 200 倍。
CPU — 600 毫秒
@numba.jit
**def** _smooth(x):
out = np.empty_like(x)
**for** i **in** range(1, x.shape[0] - 1):
**for** j **in** range(1, x.shape[1] - 1):
out[i,j] = (x[i-1, j-1] + x[i-1, j+0] + x[i-1, j+1] +
x[i+0, j-1] + x[i+0, j+0] + x[i+0, j+1] +
x[i+1, j-1] + x[i+1, j+0] + x[i+1, j+1])//9 **return** out
或者如果我们使用花哨的 numba.stencil 装饰器…
@numba.stencil
**def** _smooth(x):
**return** (x[-1, -1] + x[-1, 0] + x[-1, 1] +
x[ 0, -1] + x[ 0, 0] + x[ 0, 1] +
x[ 1, -1] + x[ 1, 0] + x[ 1, 1]) // 9
GPU — 3 毫秒
@numba.cuda.jit
**def** smooth_gpu(x, out):
i, j = cuda.grid(2)
n, m = x.shape
**if** 1 <= i < n - 1 **and** 1 <= j < m - 1:
out[i, j] = (x[i-1, j-1] + x[i-1, j] + x[i-1, j+1] +
x[i , j-1] + x[i , j] + x[i , j+1] +
x[i+1, j-1] + x[i+1, j] + x[i+1, j+1]) // 9
Numba.cuda.jit 已经存在很长时间了。它容易接近,成熟,玩起来很有趣。如果你有一台装有 GPU 的机器,并有一些好奇心,那么我们强烈建议你尝试一下。
conda install numba
# or
pip install numba>>> **import** **numba.cuda**
使用 Dask 扩展
正如在以前的博客文章( 1 、 2 、 3 、 4 )中提到的,我们已经一般化了 Dask ,不仅可以操作 Numpy 数组和 Pandas 数据帧,还可以操作任何看起来足够像 Numpy(像 CuPy 或 Sparse 或 Jax )或足够像 Pandas(像【T16 这很有效。下面是一个简短的视频,展示了 Dask array 并行计算一个 SVD,并展示了当我们用 CuPy 替换 Numpy 库时会发生什么。
我们看到计算速度提高了大约 10 倍。最重要的是,我们能够在 CPU 实现和 GPU 实现之间切换,只需一行代码,但继续使用 Dask Array 的复杂算法,就像它的并行 SVD 实现一样。
我们也看到了交流的相对放缓。总的来说,今天几乎所有重要的 Dask + GPU 工作都变得与通信密切相关。我们的计算速度已经够快了,交流的相对重要性已经显著增加。
与 UCX 的通信
也参见 这篇最近关于 UCX 和达斯克 的博文
我们一直在用的 UCX-Py 将的 OpenUCX 库集成到 Python 中。UCX 提供对 TCP、InfiniBand、共享内存和 NVLink 等传输的统一访问。UCX-Py 是第一次可以从 Python 语言轻松访问这些传输协议。
一起使用 UCX 和 Dask,我们能够获得显著的加速。以下是添加 UCX 前后的 SVD 计算轨迹:
在 UCX 之前:

UCX 之后:

尽管如此,这里仍有大量工作要做(上面链接的博客文章在未来工作部分有几个条目)。
人们可以用高度实验性的康达套装试用 UCX 和 UCX-Py:
conda create -n ucx -c conda-forge -c jakirkham/label/ucx cudatoolkit=9.2 ucx-proc=*=gpu ucx ucx-py python=3.7
我们希望这项工作也将影响使用 Infiniband 的 HPC 系统上的非 GPU 用户,甚至是消费类硬件上的用户,因为他们可以轻松访问共享内存通信。
包装
在之前的博文中,我们讨论了安装与系统上安装的 CUDA 驱动程序不匹配的 CUDA 支持包的错误版本所带来的挑战。幸运的是,由于 Anaconda 的 Stan Seibert 和 Michael Sarahan 最近的工作,Conda 4.7 现在有了一个特殊的元包,它被设置为已安装驱动程序的版本。这将使用户在未来安装正确的软件包变得更加容易。
Conda 4.7 刚刚发布,除了cuda元包之外,还有许多新特性。你可以在这里了解更多信息。
conda update conda
今天在包装领域仍有大量的工作要做。每个构建 conda 包的人都用自己的方式,导致头痛和异构。这很大程度上是因为没有集中的基础设施来构建和测试支持 CUDA 的包,就像我们在 Conda Forge 所做的那样。幸运的是,Conda Forge 社区正在与 Anaconda 和 NVIDIA 合作来帮助解决这个问题,尽管这可能需要一些时间。
摘要
这篇文章更新了 Python 中 GPU 计算背后的一些努力。它也为将来的阅读提供了各种各样的链接。如果您想了解更多信息,我们在下面列出了它们:
- 幻灯片
- GPU 上的 numpy:CuPy
- GPU 上的 Numpy(再次): Jax
- GPU 上的熊猫:激流 cuDF
- sci kit-在 GPU 上学习: RAPIDS cuML
- 基准测试套件
- 数字 CUDA JIT 笔记本
- 关于 UCX 的演讲
- 一篇关于 UCX 和达斯克的博文
- 康达 4.7
Python 预测 PUBG 移动
使用 Python 预测视频数据中未来帧的简单方法

My PUBG character 😄
简介:
预测未来是不可能的!(除非你有 时间石 -😃 )。但是预测不远的将来对我们来说并不难。我们在现实生活中经常这样做——在玩游戏或看电影时,人们可以很容易地预测即将发生的事情。我们大脑的这种能力有助于我们提前计划行动和做出决定。(例如,在空中接球,躲避迎面而来的石头……等等)。
我们不能躲避子弹,因为我们不够快。如果我们能把大脑的这种能力给愚蠢的机器,然后造一个奥创 -:)会怎么样。
问题是——机器真的能做到吗?
答案是肯定的。这个小实验证明了这一点。这也是一个活跃的研究领域——给定一个小视频,你能生成未来的视频吗?这是这个实验产生的数据的一瞥—
下面的视频完全是人工的,由深度学习模型生成。

Artificially Generated Video
如何在我的电脑上完成?
只需几个小时,你就可以在自己的电脑上完成。也不需要 GPU。
如果你是计算机视觉和深度学习的新手,我建议你在进一步阅读之前对以下主题有一个基本的了解—
现在让我们把整件事分成五个小部分。我们将一个接一个地检查每一部分——
- 数据生成
- 数据准备
- 型号
- 结果
- 结论
数据生成:
生成数据非常简单。在我的移动设备上的屏幕录制器的帮助下,我能够捕捉到一个 15 分钟长的视频(事实证明这对我们的小实验来说是足够的数据)。在这个视频中,我将我的 PUBG 角色设置为冲刺模式,让它在随机方向上连续跑大约 15 分钟。
一旦我有了这个视频,我需要做的就是按固定的时间间隔将它切割成多个帧。使用OpenCV toolkit,这种视频到帧的转换非常简单快捷。这些帧相距大约~25ms,这使得它成为**40fps (frames per second)**。两个连续帧之间的差异很小,但相当明显。
这是一个简单的 python 代码,它将视频转换为帧。(*不同机器上的帧速率可能有所不同,因为它取决于您系统的处理速度。)

Video to Frame conversion
数据准备:
之前的练习给了我们大约 30k 帧。在这 30k 帧中,前 29k 帧作为训练数据,其余 1k 帧作为验证数据。
这些相框的尺寸真的很大800 * 1200。为了使模型简单快速,在将每一帧传递给模型进行训练之前,将每一帧的大小调整为240 * 160。
下面是一个用于数据预处理的简单 python 函数。

Preprocessing function
这是处理过的图像的样子——
在 1 之后。裁剪图像,仅保留图像的相关部分。
第二年。将图像调整为较小的尺寸(240*160),因为原始图像非常大。(调整图像大小时,纵横比保持不变)。

Image preprocessing
型号:
- 训练数据:
来自先前练习的预处理帧成对排列,使得每对帧(frame_x, frame_y) 具有两个连续的帧——例如,如果frame_x出现在视频中的n’th位置,那么frame_y应该来自(n+1)’th位置。
以这种方式,我们为训练数据保留的那些 29k 帧可以构成29k — 1这样的训练数据对。类似地,剩余的 1k 验证帧可以构成 1k 验证数据对。
2。模型架构:

Encoder — Decoder Architecture
在该实验中使用了简单的 编码器-解码器 模型架构。这里的encoder部分模型是一个三层的2D CNN (convolutional neural network)和MaxPooling层。该模型的decoder部分也是2D CNN with UpSampling layers或(转置卷积神经网络)。卷积层的核大小保持不变,以便我们在解码后得到相同大小的图像。
下面是使用 tensorflow 的 keras api 实现的模型的架构。

Model Architecture
3。模型训练:
现在,对于每个训练对,模型将frame_x作为输入,并学习猜测frame_y(下一个直接帧)。
采用梯度下降算法训练模型,以mean squared error为损失函数……
**--- Model Parameters ---**
batch_size : 32
epochs : 10
optimizer : 'adam'
learning_rate : 0.001
loss : 'mse'
在仅仅训练10 epochs之后,学习的权重被保存用于推断。
Training time : approximately 8 hours (on CPU)
Hardware : Macbook Pro, 16GB, 2.6 GHz, Intel Core i7
培训和验证损失图

Loss Chart
结果:
为了检查模型在看不见的数据上的性能,从验证集中选取一个随机帧。现在,使用这个单一的帧,我们可以通过将预测的帧一次又一次地传递给模型作为输入来生成任意数量的未来帧。
模型能够从单个输入帧生成前 10-12 个未来帧,具有相当高的精度。在第 15 帧之后,它变得非常嘈杂,因为预测误差在每次新的预测时都会增加。需要注意的一件重要事情是static parts of the frames (my PUBG control buttons) are intact as model is able to learn what is static and what is changing这部分也不会模糊。
下面是由模型生成的两个示例图像序列,以及顶部的地面真实图像。

Image sequences generated by model
结论:
有趣的是,这个模型能够很容易地用一张图像生成这么多未来的图像。这很简单,因为这个实验是受控的 PUBG 视频中的人只进行一项活动(总是跑步)。因此,模型只需要学习他的运动以及背景如何随时间变化。
预测真实的生活场景并不容易。这也是一个活跃的研究领域。这很难,因为在现实世界中有无限的可能性。多个对象可以同时改变环境。为了对这样的场景建模,我们需要一个更好、更强大的模型架构。还有大量高质量的真实世界数据。
感谢 Raghav Bali 和 Dipanjan (DJ) Sarkar 的点评。
请与我分享您的意见/反馈。
Python Pro 提示:开始使用 Python defaultdict 和 Counter 代替字典
蟒蛇短裤
如何使用 defaultdict 和 Counter 使你的代码简短易读

Photo by Brooke Lark on Unsplash
学习一门语言很容易。每当我开始学习一门新的语言时,我会按照下面的顺序专注于几件事情,开始用任何语言编写代码都是轻而易举的事情。
- 运算符和数据类型:+,-,int,float,str
- 条件语句:if,else,case,switch
- 循环:For,while
- 数据结构:列表,数组,字典,散列表
- 定义函数
但是,学会写语言和用优化的方式写语言是两回事。
每种语言都有一些独特的成分。
然而, 一个新的程序员对任何语言总会做一些被迫的过度适应。 举例来说,一个刚接触 python 的 Java 程序员,可能会编写这段代码来添加列表中的数字。
x=[1,2,3,4,5]sum_x = 0
for i in range(len(x)):
sum_x+=x[i]
虽然 python 程序员会很自然地这样做:
sum_x = sum(x)
在这一系列名为‘Python Shorts’的帖子中,我将解释由 Python 提供的一些简单构造、一些基本技巧以及我在数据科学工作中经常遇到的一些用例。
这个系列讲的是高效可读的代码。
计数器和默认值—用例

假设我需要统计一段文本中出现的单词数。 也许对于《哈姆雷特》这样的书来说。我怎么能这么做呢?
Python 总是为我们提供多种方法来做同样的事情。但只有一种方式我觉得很优雅。
这是一个使用dict对象的 幼稚 Python 实现 。
text = "I need to count the number of word occurrences in a piece of text. How could I do that? Python provides us with multiple ways to do the same thing. But only one way I find beautiful."word_count_dict = {}
for w in text.split(" "):
if w in word_count_dict:
word_count_dict[w]+=1
else:
word_count_dict[w]=1
我们可以使用***defaultdict***来减少代码中的行数。
from collections import defaultdict
word_count_dict = defaultdict(int)
for w in text.split(" "):
word_count_dict[w]+=1
我们也可以使用***Counter***来做到这一点,在我看来,这是解决这个问题的最佳方法。
from collections import Counter
word_count_dict = Counter()
for w in text.split(" "):
word_count_dict[w]+=1
如果我们使用Counter,我们也可以使用一个简单的函数得到最常见的单词。
word_count_dict.most_common(10)
---------------------------------------------------------------
[('I', 3), ('to', 2), ('the', 2)]
计数器的其他使用案例:
# Count Characters
Counter('abccccccddddd')
---------------------------------------------------------------
Counter({'a': 1, 'b': 1, 'c': 6, 'd': 5})# Count List elements
Counter([1,2,3,4,5,1,2])
---------------------------------------------------------------
Counter({1: 2, 2: 2, 3: 1, 4: 1, 5: 1})
那么,为什么曾经使用过defaultdict?

注意在Counter中,值总是一个整数。
如果我们想解析一个包含颜色和水果的元组列表会怎么样。并希望创建一个键和值列表的字典。
一个defaultdict提供的主要功能是,如果在defaultdict中没有找到,它默认一个键为空/零。
s = [('color', 'blue'), ('color', 'orange'), ('color', 'yellow'), ('fruit', 'banana'), ('fruit', 'orange'),('fruit','banana')]d = defaultdict(list)for k, v in s:
d[k].append(v)print(d)
---------------------------------------------------------------
defaultdict(<class 'list'>, {'color': ['blue', 'orange', 'yellow'], 'fruit': ['banana', 'orange', 'banana']})
banana在fruit中出现两次,我们可以用set
d = defaultdict(set)for k, v in s:
d[k].add(v)print(d)
---------------------------------------------------------------
defaultdict(<class 'set'>, {'color': {'yellow', 'blue', 'orange'}, 'fruit': {'banana', 'orange'}})
结论
总结一下,我要说的是 在Python中总有一种做任何事情的美好方式。在编写代码之前搜索它。去 StackOverflow 没问题。我被困的时候去过很多次。永远记住:
为已经提供的东西创建一个函数不是 pythonic 式的。
另外,如果你想了解更多关于 Python 3 的知识,我想向你推荐密歇根大学的一门优秀的中级 Python 课程。一定要去看看。
将来我也会写更多初学者友好的帖子。让我知道你对这个系列的看法。在 媒体 关注我,或者订阅我的 博客 了解他们。一如既往,我欢迎反馈和建设性的批评,可以通过 Twitter @mlwhiz 联系。
Python Pro 提示:使用迭代器、生成器和生成器表达式
蟒蛇短裤
对于编码来说,理解是至关重要的

Photo by Aziz Acharki on Unsplash
谈到编程,Python 在很多方面都让我们的生活变得更加轻松。
它有许多库和功能,有时我们会忘记关注它提供的一些有用的东西。
其中一个功能是生成器和生成器表达式。我拖延了很长时间去学习它们,但是它们很有用。
你有没有在 Python 代码中遇到过yield而不知道它是什么意思?或者一个iterator或一个generator是什么意思,我们为什么使用它?或者你在使用 Keras 的时候使用过ImageDataGenerator却不明白后端发生了什么?那么这篇文章是给你的。
在这一系列名为'Python Shorts,‘的帖子中,我将解释 Python 提供的一些简单构造、一些基本技巧以及我在数据科学工作中经常遇到的一些用例。
这篇文章是关于用一种简单易懂的方式解释一些难懂的概念。
问题陈述:

假设我们需要运行一个超过 1000 万素数的 for 循环。
为了便于理解,我在这种情况下使用质数,但它可以扩展到我们必须处理数据库或大数据中的大量图像或文件的情况。
你会如何处理这样的问题?
简单。我们可以创建一个列表,将所有的质数保存在那里。
真的吗? 想想这样一个列表会占用多少内存。
如果我们有某种东西可以保存我们检查过的最后一个素数,并返回下一个素数,那就太好了。
这就是迭代器可以帮助我们的地方。
迭代器解决方案
我们创建了一个名为素数的类,并用它来生成素数。
def check_prime(number):
for divisor in range(2, int(number ** 0.5) + 1):
if number % divisor == 0:
return False
return Trueclass Primes:
def __init__(self, max):
# the maximum number of primes we want generated
self.max = max
# start with this number to check if it is a prime.
self.number = 1
# No of primes generated yet. We want to StopIteration when it reaches max
self.primes_generated = 0
def __iter__(self):
return self
def __next__(self):
self.number += 1
if self.primes_generated >= self.max:
raise StopIteration
elif check_prime(self.number):
self.primes_generated+=1
return self.number
else:
return self.__next__()
然后,我们可以将它用作:
prime_generator = Primes(10000000)for x in prime_generator:
# Process Here
这里我定义了一个迭代器。这就是大多数像xrange或ImageGenerator这样的功能是如何工作的。
每个迭代器都需要:
- 一个返回 self 的
__iter__方法,以及 - 返回下一个值的
__next__方法。 - 表示迭代器结束的异常。
每个迭代器都采用上述形式,我们可以根据自己的喜好在样板代码中调整函数来做我们想做的事情。
注意,我们并没有把所有的质数都保存在内存中,只是保存了迭代器的状态,就像
- 我们返回的最大质数是多少
- 我们已经归还了多少质数。
但是代码好像有点多。我们能做得更好吗?
发电机解决方案

Simple yet beautiful..
简而言之,生成器为我们提供了使用yield语句轻松编写迭代器的方法。
def Primes(max):
number = 1
generated = 0
while generated < max:
number += 1
if check_prime(number):
generated+=1
yield number
我们可以将该函数用作:
prime_generator = Primes(10)
for x in prime_generator:
# Process Here
读起来简单多了。但是什么是yield?
我们可以认为yield是一个return语句,只是因为它返回值。
但是当yield发生时,功能的状态也被保存在存储器中。所以在 for 循环的每次迭代中,像number, generated and max这样的函数变量都存储在内存中的某个地方。
因此,上面的函数通过使用yield语句为我们处理所有的样板代码。
更像蟒蛇。
Generator Expression Solution

So Much Cleaner!!!
虽然没有明显优于前面的解决方案,但我们也可以使用生成器表达式来完成相同的任务。但是我们可能会失去一些功能。它们的工作方式与列表理解完全一样,但它们不会将整个列表保存在内存中。
primes = (i for i in range(1,100000000) if check_prime(i))for x in primes:
# do something
功能损失:我们可以产生 1000 万个素数。但是我们不能产生 1000 万个素数。对于生成器表达式,我们只能做这么多。
但是生成器表达式让我们做了一些很酷的事情。
假设我们希望所有的毕达哥拉斯三元组都小于 1000。
怎么才能得到呢?
使用发电机,现在我们知道如何使用它们。
def triplet(n): # Find all the Pythagorean triplets between 1 and n
for a in range(n):
for b in range(a):
for c in range(b):
if a*a == b*b + c*c:
yield(a, b, c)
我们可以这样使用:
triplet_generator = triplet(1000)for x in triplet_generator:
print(x)------------------------------------------------------------
(5, 4, 3)
(10, 8, 6)
(13, 12, 5)
(15, 12, 9)
.....
或者,我们也可以在这里使用生成器表达式:
triplet_generator = ((a,b,c) for a in range(1000) for b in range(a) for c in range(b) if a*a == b*b + c*c)for x in triplet_generator:
print(x)------------------------------------------------------------
(5, 4, 3)
(10, 8, 6)
(13, 12, 5)
(15, 12, 9)
.....
Python 不好看吗?
我讨厌中等代码块,有时是因为代码中断和复制粘贴问题。所以你可以看到这个 kaggle 内核中的所有代码。
结论
我们必须时刻努力减少Python中的内存占用。迭代器和生成器为我们提供了一种通过惰性求值来实现的方法。
我们如何选择使用哪一个?我们可以用生成器表达式做什么,我们也可以用生成器或迭代器做什么。
这里没有正确答案。每当我面临这样的困境时,我总是从功能性和可读性的角度来考虑。一般来说,
功能方面:迭代器>生成器>生成器表达式。
可读性:来自密歇根大学的迭代器
It is not necessary that you end up using them in your code now. But I guess understanding how these things work helps mitigate some of the confusion and panic one faces whenever these constructs come up.
Understanding is vital when it comes to coding
Also if you want to learn more about Python 3, I would like to call out an excellent course on Learn 中级 Python 。一定要去看看。
将来我也会写更多初学者友好的帖子。让我知道你对这个系列的看法。在 中 关注我,或者订阅我的 博客 了解他们。一如既往,我欢迎反馈和建设性的批评,可以通过 Twitter @mlwhiz 联系到我。
Python Pro 提示:想在 Python 中使用 R/Java/C 或者任何语言?
蟒蛇短裤
Python 提供了一种基本而简单的方法来处理这样的需求,我们必须在多种语言之间来回切换

巨蟒 很棒。真的很棒。
但是随着时间的推移,这个领域变得/将变得与语言无关。许多伟大的工作正在用许多其他语言完成。
虽然我仍然把 Python 作为主要语言,但如果它能完成工作,我会毫不犹豫地转向另一种语言。
事实上,每一种语言都以这样一种方式进化,它在某些领域建立了自己的据点。例如, 有些人可能会发现使用 R 进行回归更容易,或者在 R 中使用 ggplot 进行绘图(尽管我真诚地感到 Python 已经在 可视化 部门取得了长足的进步。)
有时是因为某个特定的库是用 Java/C 编写的,而有人还没有把它移植到 Python 上。
但是有没有更好的方法来处理这种不断的麻烦呢?
我喜欢 Python 是因为我现在很好的理解了它。与用 R 或 Java 或 Scala 相比,用 Python 做这么多事情对我来说很容易。
如果我只想使用 R 中的线性回归包,为什么我必须在 R 中编写我的数据准备步骤?
或者如果我只想使用 Stacknet 包,为什么我必须学习用 Java 创建图表?
现在 Python 和 R 有了很多包装器。如何在 Python 中使用 R 或者如何在 R 中使用 Python?rpy2和reticulate
这些套餐都不错,可能会解决一些问题。但是他们没有解决一般性的问题。每次我想从一种语言切换到另一种语言时,我都需要学习一个全新的包/库。完全不可伸缩。
在这一系列名为 Python Shorts 的帖子中,我将解释 Python 提供的一些简单构造,一些必要的技巧和我在数据科学工作中经常遇到的一些用例。
这篇文章是关于利用另一种语言的一个特殊的包/库,同时不离开用我们的主要语言编码的舒适。
问题陈述
我将从一个问题陈述开始解释这一点。假设我不得不使用 R 创建一个图表,但是我想用 Python 准备我的数据。
这是任何数据科学家都可能面临的普遍问题。用一种语言做一些事情,然后转到另一种语言做一些其他的事情。
不离开 Jupyter 笔记本就能做到吗?还是我的 Python 脚本?
解决方案

以下是我如何实现这一点。对一些人来说,这可能看起来很黑客,但我喜欢黑客。
import pandas as pd
data=pd.read_csv("data.csv")
data = preprocess(data)data.to_csv("data.csv",index=None)
os.system("Rscript create_visualization.R")
***os.system***命令为我提供了一种使用 Python* 访问我的 shell 的方法。外壳是你可以随意使用的有力的工具。您几乎可以在 shell 上运行任何语言。*
将在 python 中运行的相应的Rscript看起来类似于:
*data<-read.table("data.csv")
ggplot(...)
ggsave("plot.png")*
然后,我可以加载 png 文件,并使用类似 markdown hack 的工具在我的 Jupyter 笔记本上显示它。
**
对于不想离开 R 的舒适环境的 R 用户来说,R 还有一个类似于os.system的system命令,可以用来在 R 中运行 Python 代码
结论
中的***os.system***Python******通过让我们从 Python 中调用 shell 命令,为我们提供了一种在 Python 中做每一件事的方法。**
我已经在我的很多项目中使用了它,在这些项目中,我使用 Mutt 发送电子邮件。或者运行一些 Java 程序或者瞎搞。
这看起来像是一种不成熟的方法,但是它很有效,而且足够一般化,以至于当你想用任何其他语言做一些事情并将其与 Python 集成时,你不必学习一个新的库。
如果你想了解更多关于 Python 3 的知识,我想从密歇根大学调出一门关于学习中级 Python 的优秀课程。一定要去看看。
将来我也会写更多初学者友好的帖子。让我知道你对这个系列的看法。在关注我或者订阅我的 博客 了解他们。一如既往,我欢迎反馈和建设性的批评,可以通过 Twitter @mlwhiz 联系。
Python range()内置函数
让我们了解一下 python 中 range()函数的基础知识。

现在让我们来理解 python 中 range 函数()的概念,下面展示的概念和例子将帮助你们以一种清晰的方式理解 range()。另外,请访问我的 GitHub 库,在下面找到更多关于 range 函数的详细信息:
此时您不能执行该操作。您已使用另一个标签页或窗口登录。您已在另一个选项卡中注销,或者…
github.com](https://github.com/Tanu-N-Prabhu/Python/blob/master/Range_built_in_function.ipynb)
我们开始吧!!!
定义
range()函数是 python 中使用的内置函数,用来生成一个数列。如果用户希望在给定起始值和结束值的情况下生成一个数字序列,那么他们可以将这些值作为 range()函数的参数。range()函数将根据用户的要求生成一系列数字。
句法
range()函数的语法如下所示:
**range(start, stop, step)**
因素
range()内有三个参数:开始,停止,步进。当您考虑这三个参数时,它类似于下面将要讨论的真实场景。
***Start:*** *Optional — An integer number that specifies where to start (Default value is 0)***Stop**: Required — An integer number that specifies where to stop.**Step:** Optional — An integer number that specifies how much to increment the number (Default value is 1)
返回值
range 函数的返回值是一系列数字,具体取决于所定义的参数。
例题
1。让我们看看当我们打印值为 10 的 range 函数时会发生什么。
x = range(10)
print(x)**range(0, 10)**
如上所示,值 10 表示范围功能的终止值,0 值默认作为起始值添加。
2。让我们看看当我们把 range 函数放入 for 循环时会发生什么。
x = range(10)
for i in x:
print(i)**0
1
2
3
4
5
6
7
8
9**
如上所示,我们可以得到从 0 到 10 的值,但不包括 10,因为范围函数的停止值总是返回值-1 。因此值为 0-10。
3。让我们看看,当我们给 range 函数一个起始值和终止值时,会发生什么。
x = range(1, 10)
for i in x:
print(i)**1
2
3
4
5
6
7
8
9**
如上所示,它打印出从 1 到 9 的值,因为起始值被定义为 1。
4。让我们看看当我们给距离函数一个步长值时会发生什么。
x = range(1, 10, 2)
for i in x:
print(i)**1
3
5
7
9**
如上所述,步长用于增加数字,在这种情况下,步长为 2,因此数字增加 2。
一般来说,可以这样认为范围函数就像现实生活中的场景:
- 你开始走— 开始
- 你踏出你的步伐(脚步)——步
- 你停下来行走,因为你已经到达了目的地。
即使在这种情况下你没有声明 step 变量,你也会一次走一步,显然,没有人会一次走两步。
范围函数的类型为 class
是的,range 函数是一个类,因为当你使用 type()检查 range 函数的类型时,你就会得到答案。
x = range(10)
print(type(x))**<class 'range'>**
不要在 range 函数中传递任何浮点值。
range 函数不适用于浮点值,因为根据 range 函数,浮点对象不能被解释为整数。因此,通常在范围函数中指定整数值。
x = range(10.0, 100.5)
for i in x:
print(i)**--------------------------------------------------------------------****TypeError Traceback (most recent call last)**[**<ipython-input-23-55e80efae649>**](/<ipython-input-23-55e80efae649>) **in <module>()
----> 1 x = range(10.0, 100.5)
2 for i in x:
3 print(i)****TypeError: 'float' object cannot be interpreted as an integer**
如果你需要解决它,你可以把它转换成一个整数,如下所示:
x = range(int(10.0), int(100.5))
for i in x:
print(i)**10
11
12
13
14
-
-
-
-
99**
为了支持这种情况,我对 StackOverflow 做了一些研究,然后我看到了一个问题,它被表述为“我想从 0 到 100 循环,但步长为 1/2 ”现在这不是一个简单的问题,因为上面的概念适用于此,所以你需要使用一些逻辑,现在我已经使用了一些我的逻辑来解决这个问题,如下所示:
x = range(0, 100 * 2)
for i in x:
print(i * 0.5)**0.0
0.5
1.0
1.5
2.0
-
-
-
99.5**
你可以自由地用你的逻辑解决上述问题,并在下面的评论区提供解决方案。
xrange 函数
这也是 python 中的一个内置函数,我认为它的工作方式与 range()相同,当我尝试执行它时,我得到了一个奇怪的错误:
x = xrange(1, 5)
for i in x:
print(i)**--------------------------------------------------------------------****NameError Traceback (most recent call last)**[**<ipython-input-30-969120b3b060>**](/<ipython-input-30-969120b3b060>) **in <module>()
----> 1 x = xrange(1, 5)
2 for i in x:
3 print(i)****NameError: name 'xrange' is not defined**
然后经过一番研究,我根据栈溢出得知 xrange 是 range 的增量版本。其中一个说 Python2 用的是 xrange(),Python3 用的是 range,所以你不用担心。
范围函数也可以用于列表
范围函数也可用于 python 列表。一种简单的方法如下所示:
names = [‘India’, ‘Canada’, ‘United States’, ‘United Kingdom’]
for i in range(1, len(names)):
print(names[i])**Canada
United States
United Kingdom**
使用范围函数增加和减少
我想现在你们应该知道该怎么做了。但无论如何,我会提供这个问题的解决方案。在这种情况下,您只需使用步进参数即可。
递增
x = range(1, 10, 2)
for i in x:
print(i)**1
3
5
7
9**
递减
x = range(10, 1, -2)
for i in x:
print(i)**10
8
6
4
2**
这就是你需要知道的关于 python 中 range 函数的全部内容,推荐你阅读 Python 的官方指南:
[## 内置函数- Python 3.8.0 文档
Python 解释器内置了许多始终可用的函数和类型。它们被列出…
docs.python.org](https://docs.python.org/3/library/functions.html#func-range)
感谢你们阅读这个简短的教程,我希望你们喜欢它,让我知道你的意见,如果你有任何疑问,评论区都是你的。
Python 正则表达式基础 5 分钟
对 Regex 世界的轶事介绍

当我开始网络抓取时,我经常会有奇怪字符的字符串,如\n\nnn 等等。我用替换功能把它去掉了。我构建了类似. str.replace('文本之墙',''). str.replace('文本之墙',' ')的结构。结果是 1)可读性差,2)总是只有一个术语的单独解决方案,以及 3)非常低效的过程。所以我开始使用正则表达式来清理我的数据。在下面的文章中,我想和你分享我的发现和一些技巧。祝阅读愉快。
场景 1:从网络搜集的数据中提取信息
Python 提供了用于处理正则表达式的re库。让我们导入它并创建一个字符串:
import re
line = "This is a phone number: (061) — 535555\. And this is another: 01–4657289, 9846012345"
re有四种不同的功能:findall, search, split和sub。我们将从findall开始学习基础知识。在我们的字符串中,显然有三个不同的电话号码,我们为我们的营销部门丢弃了它们(该字符串是来自 stackoverflow 的真实世界的例子)。我们的同事不需要整个字符串,而是需要一个列表,每个数字分开。第一步,我们尝试从字符串中提取每个数字:
foundNum = re.findall('\d', line)
print("The numbers found are: ", foundNum)
输出:
The numbers found are: ['0', '6', '1', '5', '3', '5', '5', '5', '5', '0', '1', '4', '6', '5', '7', '2', '8', '9', '9', '8', '4', '6', '0', '1', '2', '3', '4', '5']
如你所见,findall有两个参数。第一项\d是我们要找的表达式。在这种情况下,数字。第二项是包含我们感兴趣的模式的字符串。嗯,我们的市场部不能用这个打电话给任何客户,所以我们必须寻找不同的方法。
foundNum = re.findall('\(\d', line)
print("The numbers found are: ", foundNum)
输出:
The numbers found are: ['(0']
现在我们寻找括号和数字的组合。有一个匹配是第一个电话号码的开头。如果我们得出这个结果,营销部门将不会再向我们要求任何东西…好的,下一次尝试:
foundNum = re.findall("\(*\d*\d", line)
print("The phone numbers found are: ", foundNum)
输出:
The phone numbers found are: ['(061) - 535555', '01 - 4657289', '9846012345']
现在我们有了完美的解决方案。我们在表达式之间添加了一个列表,字符串必须以数字开头和结尾。术语[- \d()]表示字符“-”、“”、数字或括号必须在数字之间。完美!我们的市场部对此会很高兴,可以打电话给我们的客户。快乐结局!
场景 2:清理和匿名化客户数据
我们在刮削项目中的出色表现之后,刚刚放了一天假。然而,在我们早上查看邮件后,我们注意到了另一个请求。我们销售部的同事有一些包含括号中表达式的字符串。出于隐私原因,括号内的术语必须删除。好了,我们已经学了很多,我们开始吧:
string_df = '500€ (Mr. Data) Product Category 1'
re.findall(r"\([a-zA-Z .]*\)", string_df)
输出:
['(Mr. Meier)']
好极了。这很容易。我们刚刚检查了一个以括号开始和结束的字符串,并包含了一个包含字母字符和一个点的列表。当我们开始写一封关于我们的好结果的邮件时,我们注意到我们不需要搜索这个术语。相反,我们必须删除这个表达式。我们后退一步,引入一个新函数sub。re.sub需要三个参数。第一个参数是我们要寻找的表达式,第二个参数是我们要用来替换旧参数的术语,最后一个参数是我们要使用的字符串:
re.sub(r"\([a-zA-Z .]*\)", "-XY-", string_df)
我们用表达式“-XY-”替换了这个名称。现在,销售部门可以将数据发送给我们的仪表板供应商。又一个幸福的结局。
场景 3:将字符串分成几部分
我们开始对正则表达式感到舒服。如果我们称自己为专家,那就有点言过其实了,但我们为每个问题都找到了解决方案,不是吗?在我们帮助了其他部门的同事后,我们专注于自己的工作。我们收到了新的数据进行探索性的数据分析。像往常一样,我们查看数据帧的第一行。第二列看起来很奇怪:
strange_cell = '48 Mr.Data 57'
根据我们以前的经验,我们得出第一个数字代表 Data 先生购买的单位数量。第二个数字告诉我们客户 id。我们需要在三个独立的栏目中提供这些信息,但是如何提供呢?Regex 提供了另一个名为split的函数:
re.split(" ", strange_cell)
输出:
['48', 'Mr.Data', '57']
这正是我们要找的。有了这么多快乐的结局,你会认为你在看一部迪斯尼电影,对吗?
简短的课程
在漫长而成功的一天后,我们收到了市场部的另一封邮件,他们要求修改我们的第一个任务:他们只需要字符串中的第一个电话号码,因为这是业务电话号码:
line = "This is a phone number: (061) — 535555\. And this is another: 01–4657289, 9846012345"
使用searchall功能,我们考虑每一场比赛。一个简单的解决方法是,我们只给出列表的第一个元素:
foundNum = re.findall(r"\(*\d[- \d()]*\d", line)[0]
print("The phone numbers found are: ", foundNum)
输出:
The phone numbers found are: (061) - 535555
另一个更好的解决方案是使用第四个正则表达式函数search,它返回字符串中的第一个匹配:
foundNum = re.search(r"\(*\d[- \d()]*\d", line)
print("The phone numbers found are: ", foundNum)
输出:
The phone numbers found are: <re.Match object; span=(24, 38), match='(061) - 535555'>
结论
我希望你喜欢阅读并理解为什么Regex对数据科学家如此重要。在数据清理和数据转换过程中,修改字符串是一种强有力的方法。如果你处理数据,你需要处理和转换字符串的技能。Regex是完美的解决方案。对于不同表达的概述,我推荐以下页面:
正则表达式是构成搜索模式的一系列字符。正则表达式可以用来检查是否…
www.w3schools.com](https://www.w3schools.com/python/python_regex.asp) [## 正则表达式 HOWTO - Python 2.7.17 文档
该模块是在 Python 1.5 中添加的,提供了 Perl 风格的正则表达式模式。Python 的早期版本…
docs.python.org](https://docs.python.org/2/howto/regex.html)
如果您喜欢中级和高级数据科学,并且还没有注册,请随时使用我的推荐链接加入社区。
Python 风险管理:凯利标准

从最近金融市场调整的事件来看,我认为这将是一个谈论风险管理的有趣时刻。具体来说,我们将通过 Python 中的一个具体例子来回顾 Kelly 准则。首先,我们将讨论凯利标准的简要概述。接下来,我们来看一个简单的抛硬币的例子。最后,我们将举一个简单的例子,并将其应用于一个金融指数。
本文的数据收集自:https://www.investing.com/etfs/spdr-s-p-500-historical-data
简要凯利标准概述
约翰·凯利是美国电话电报公司·贝尔实验室的科学家。当时,凯利听说了职业赌徒,并对赌徒在面对不确定性时如何管理他们的资本感到好奇。有趣的是,他并不关心他们赚了多少钱,而是关心他们如何设置下注规模以获得最多的钱。像大多数成功的赌博故事一样,Kelly 应用信息论中的数学创建了 Kelly 准则(KC) [1]。从 KC 来看,如果一个赌徒知道自己赢和输的几率,她可以做出最好的赌注,从长远来看获得最多的钱。
凯利标准(KC)有两个主要部分:赢概率和赢/赔率。在数学公式形式中,那将是[2]:

但是 KC 的应用不仅仅是赌博。它适用于任何我们知道变量值的决策。
投资者如何应用凯利
在谷歌上快速搜索一下,你会发现很多人引用价值投资者的话,比如[3]:
我不能参与 50 或 75 件事情。这是一种诺亚方舟式的投资方式——你最终会得到一个动物园。我喜欢在一些事情上投入有意义的钱。—沃伦·巴菲特
当世界给智者提供机会时,他们会下大赌注。他们有胜算的时候会下大注。其他时候,他们不会。就是这么简单。—查理·芒格
这些大牌价值投资者大多不再直接应用 KC 了。当然,他们在他们的投资组合中使用集中下注,但是 KC 要求可重复下注以获得最佳回报[4]。这就是为什么如果你进一步挖掘,这些投资者大多会放弃 KC。不是因为 KC 不好,而是哲学上的投资差异。价值投资包括选择一些公司,并持有一段时间,使其达到公允价值。在计算公允价值时,大多数没有足够的可重复概率。这就是为什么大多数使用 KC 的投资者倾向于使用更多的量化策略来反映赌场的优势。
作弊抛硬币示例
背景介绍到此为止,让我们来看一些编码示例!现在,我们来看一下如何应用 KC,当你以 55%的优势掷硬币下注时(一枚公平的硬币有 50%的胜算)。对于我们的例子,让我们假设你赢了 1 美元或输了 1 美元,因为正面或反面的结果与风险的大小有关。换句话说,我们假设每注的 KC 或风险金额是 1 比 1。接下来的代码也是从用于金融的 Python 修改而来的 [5]。
#Import libraries
import math
import time
import numpy as np
import pandas as pd
import datetime as dt
import cufflinks as cf
from pylab import plt
np.random.seed(1) # For reproducibility
plt.style.use('seaborn') # I think this looks pretty
%matplotlib inline # To get out plots#Coin flip variable set up
p = 0.55 #Fixes the probability for heads.f = p - (1-p) #Calculates the optimal fraction according to the Kelly criterion.
f
f = 0.10
以上是最佳凯利标准最佳尺寸(f)。这意味着,对于 1 比 1 的回报和 55%的获胜机会,我们应该将总资本的 10%用于最大化我们的利润。
# Preparing our simulation of coin flips with variables
I = 50 #The number of series to be simulated.
n = 100 #The number of trials per series.
def run_simulation(f):
c = np.zeros((n, I)) #Instantiates an ndarray object to store the simulation results.
c[0] = 100 #Initializes the starting capital with 100.
for i in range(I): #Outer loop for the series simulations.
for t in range(1,n): #Inner loop for the series itself.
o = np.random.binomial(1, p) #Simulates the tossing of a coin.
if o > 0: #If 1, i.e., heads …
c[t, i] = (1+f) * c[t-1,i] #… then add the win to the capital.
else: #If 0, i.e., tails …
c[t, i] = (1-f) * c[t-1,i] #… then subtract the loss from the capital.
return c
c_1 = run_simulation(f) #Runs the simulation.
c_1.round(2) #Looking at a simulation

当运行模拟时,我有时喜欢检查数据看起来如何,只是为了看看没有什么疯狂的事情发生。我们将这些数据形象化,使其更容易理解。
plt.figure(figsize=(10,6))
plt.plot(c_1, 'b', lw=0.5) #Plots all 50 series.
plt.plot(c_1.mean(axis=1), 'r', lw=2.5); #Plots the average over all 50 series.
plt.title('50 Simulations of Rigged Coin Flips');
plt.xlabel('Number of trials');
plt.ylabel('$ Amount Won/Lost');

对于上面的图,蓝线代表我们运行的 50 次模拟抛硬币,红线代表所有模拟的平均值。有趣的是,即使我们以微弱的优势(55%的优势)获胜,也很少有我们仍然失败的情况。这可能就是为什么很多人在评估与机会有关的情况的盈利能力时会有问题。
除了始终用 10%的资金下注之外,其他下注规模/不同的 KC 值会发生什么?
c_2 = run_simulation(0.05) #Simulation with f = 0.05.
c_3 = run_simulation(0.25) #Simulation with f = 0.25.
c_4 = run_simulation(0.5) #Simulation with f = 0.5.plt.figure(figsize=(10, 6))
plt.plot(c_1.mean(axis=1), 'r', label='$f^*=0.1$')
plt.plot(c_2.mean(axis=1), 'b', label='$f=0.05$')
plt.plot(c_3.mean(axis=1), 'y', label='$f=0.25$')
plt.plot(c_4.mean(axis=1), 'm', label='$f=0.5$')
plt.legend(loc=0);
plt.title('Varied KC Simulations of Rigged Coin Flips');
plt.xlabel('Number of trials');
plt.ylabel('$ Amount Won/Lost');

从我们不同的 KC,我们可以看到回报(金额韩元)差异很大。具体来说,我们增加的 KC 越高,我们赢得更多钱的机会就越大,但风险也越大。到目前为止,我们的最佳值 0.10 确实看起来最好(持续盈利回报),而更高的值在回报方面有巨大的变化(f=0.25)甚至亏损(f=0.5)。
凯利去做间谍
现在让我们从简单的例子中走出来,把它推向现实世界。我们将把 KC 应用于标准普尔 500 股票指数。为了应用这些变化,我们的 KC 公式将如下所示[5]:

Mu ( ) =间谍平均收益
r =无风险率
sigma (σ ) =间谍方差
不用向上滚动数据链接,你可以在这里再找到:https://www.investing.com/etfs/spdr-s-p-500-historical-data【6】。
#Loading SPY data
data = pd.read_csv('SPY Historical Data.csv', index_col=0, parse_dates=True)#Light Feature Engineering on Returns
data['Change %'] = data['Change %'].map(lambda x: x.rstrip('%')).astype(float) / 100
data.dropna(inplace=True)data.tail()

只是检查数据,看看我们的功能工程是否有效。来自 Investing.com 的数据通常有一个%符号,Python 倾向于将其作为一个对象而不是一个数字。
mu = data['Change %'].mean() * 252 #Calculates the annualized return.
sigma = (data['Change %'].std() * 252 ** 0.5) #Calculates the annualized volatility.
r = 0.0179 #1 year treasury ratef = (mu - r) / sigma ** 2 #Calculates the optimal Kelly fraction to be invested in the strategy.
f
f = 4.2
根据我们的计算,KC 值是 4.2,这意味着我们应该为间谍投资的每 1 美元下注 4.2 倍。对于那些对金融更熟悉的人来说,这意味着我们应该利用 4.2 倍的交易来最大化我们的预期回报。直觉上,对我来说听起来有点高,但是让我们像以前一样运行一些模拟。
equs = [] # preallocating space for our simulationsdef kelly_strategy(f):
global equs
equ = 'equity_{:.2f}'.format(f)
equs.append(equ)
cap = 'capital_{:.2f}'.format(f)
data[equ] = 1 #Generates a new column for equity and sets the initial value to 1.
data[cap] = data[equ] * f #Generates a new column for capital and sets the initial value to 1·f∗.
for i, t in enumerate(data.index[1:]):
t_1 = data.index[i] #Picks the right DatetimeIndex value for the previous values.
data.loc[t, cap] = data[cap].loc[t_1] * math.exp(data['Change %'].loc[t])
data.loc[t, equ] = data[cap].loc[t] - data[cap].loc[t_1] + data[equ].loc[t_1]
data.loc[t, cap] = data[equ].loc[t] * f
kelly_strategy(f * 0.5) # Values for 1/2 KC
kelly_strategy(f * 0.66) # Values for 2/3 KC
kelly_strategy(f) # Values for optimal KCax = data['Change %'].cumsum().apply(np.exp).plot(legend=True,figsize=(10, 6))
data[equs].plot(ax=ax, legend=True);
plt.title('Varied KC Values on SPY, Starting from $1');
plt.xlabel('Years');
plt.ylabel('$ Return')

就像我们掷硬币的例子一样,KC 值越高,波动性越大。4.2 的最佳金额在 2019 年初前后损失了超过 50%的价值。这对于大多数人来说是相当可怕的,这就是为什么应用 KC 的从业者通常应用 KC 的一半(equity_2.10 from out plot)。
结论
我们今天走了相当多的路。我们回顾了凯利标准(KC)的简史,以及投资者如何应用这个公式。然后我们用一个简单的作弊抛硬币的例子来最大化我们的回报。最后,我们将从抛硬币的例子中学到的知识应用到标准普尔 500 股票指数中。从我们的小模拟中,我们了解到,即使 KC 可能推荐一个高值,有时我们也会采用一个降低的 KC 值来避免不必要的波动。没有多少人能忍受 50%的损失(最大的损失金额),不要让我开始承受经历一次的精神压力!所以采取战略解决方案,通过降低 KC 来获得更多的安心。
免责声明:本文陈述的所有内容都是我个人的观点,不代表任何雇主。投资带有严重的风险,在采取任何投资行动之前,请咨询您的投资顾问。此外,这个帖子包含附属链接。
参考
[1] Finbox,Investing With Kelly (2018),https://www . value walk . com/2018/04/Investing-With-the-Kelly-criterion-model/
[2] J. Kuepper,利用凯利准则进行资产配置和资金管理(2019),【https://www.investopedia.com/articles/trading/04/091504.asp
[3] OSV,将凯利标准应用于投资和您的投资组合规模(2014),https://www . oldschoolvalue . com/blog/Investing-strategy/Kelly-Criterion-Investing-Portfolio-Sizing/
[4] P. Lindmark,用凯利标准投资(2007 年),https://www . guru focus . com/news/4883/用凯利标准投资
[5] Y. Hiplisch,Python for Finance:掌握数据驱动的金融(2018),奥赖利媒体https://amzn.to/2KuHPRG
[6]Investing.com,间谍 500 史料,https://www.investing.com/etfs/spdr-s-p-500-historical-data
Python Seaborn 使用 reticulate 在 R 中绘图

我认为没有人会质疑ggplot2有多神奇。但是在 Python 的现代数据可视化库Seaborn中有几个我很欣赏的情节。这不仅仅是因为它能产生高质量的可视化效果,还因为它的构建是多么的容易和简单。那两个地块是heatmap和pairplot。我总是想念他们,但我想不会了。
如果我告诉您,您现在可以使用 RStudio 在 R 中构建 Seaborn 热图和 pairplot,会怎么样?在这篇文章中,我们将看到如何制作像 Pairplot 和 Heatmap 这样的 Seaborn 可视化工具,就此而言,r。
网状的
这里的圣杯是最近推出的 R 包reticulate,由 RStudio 开发。
关于套餐:
reticulate 包为 Python 和 r 之间的互操作性提供了一套全面的工具。
- 以多种方式从 R 调用 Python,包括 R Markdown、获取 Python 脚本、导入 Python 模块以及在 R 会话中交互使用 Python。
- R 和 Python 对象之间的转换(例如,R 和 Pandas 数据框之间,或者 R 矩阵和 NumPy 数组之间)。
- 灵活绑定到不同版本的 Python,包括虚拟环境和 Conda 环境。
Reticulate 在 R 会话中嵌入了一个 Python 会话,实现了无缝、高性能的互操作性。
安装和加载 R 包
reticulate在曲柄上可用,并可按以下代码安装:
install.packages('reticulate')
让我们将 R 包加载到当前的 R 会话中(就像加载其他 R 包一样):
#loading required R libraries
library(reticulate) #the superpower bridges python and R
初始设置
记住,要访问这个包,您的机器中需要有 Python。所以请确保您已经安装了 Python 以及所需的包,并且在PATH上可用,默认情况下,reticulate 使用路径上的 Python 版本。如果您的计算机上有多个 Python 版本,您可以使用以下代码指示使用哪个版本的 Python for reticulate:
#specifying which version of python to use
use_python('C:\\PROGRA~1\\Python35\\python.exe')
加载 Python 库
import()函数帮助将指定的 Python 库导入当前 R 会话。记住,指定的 Python 库必须已经安装在机器上。
#importing required Python libraries/modules
sns <- import('seaborn')
plt <- import('matplotlib.pyplot')
pd <- import('pandas')
代码结构
这段代码做两件事:
R 中的 Seaborn 热图
#using R's inbuilt AirPassengers dataset
df <- datasets::AirPassengers
#converting Time-Series object into an R Dataframe
#Thx: [https://stackoverflow.com/questions/5331901/transforming-a-time-series-into-a-data-frame-and-back](https://stackoverflow.com/questions/5331901/transforming-a-time-series-into-a-data-frame-and-back)
df1 <- data.frame(tapply(df, list(year = floor(time(df)), month = month.abb[cycle(df)]), c))
df1 <- df1[month.abb]
#building a heatmap using seaborn
#please note the function r_to_py() that converts R object into a python
sns$heatmap(r_to_py(df1), fmt="g", cmap ='viridis')
#display the plot
plt$show()
给出了这个图:

这真是一张漂亮的热图。为了理解那里发生了什么,我们采用了 AirPassengers 数据集,这是一个时间序列格式的 R 对象。这被转换成数据帧,然后输入到 seaborn 的热图功能,以绘制热图。这里需要注意一些事情:
- 在载入 python 库的对象后使用
$符号访问 Python 包的函数。这与使用$访问数据帧的一列非常相似。 r_to_py()是一个用来把 R 对象转换成 Python 对象的函数。在这种情况下,R 数据帧被转换为 Python Pandas 数据帧,这是热图函数绘制热图的理想对象类型。
R 中的 Seaborn 配对图
#building a seaborn pairplot using pairplot()
sns$pairplot(r_to_py(iris), hue = 'Species')
#display the plot
plt$show()
给出了这个图:

这是理解给定数据集中成对关系的一个很好的图。
结论
因此,只需很少的编码和配置,我们就能在 R 中使用 Python Seaborn 很好地可视化给定的数据集,并绘制出热图和 Pairplot。虽然这篇文章可能已经非常具体地描述了这两个图,但是从这篇文章中可以推断出的更大的想法是理解使用reticulate包将 Python 代码集成到 R 代码中是多么简单和容易。这里使用的完整代码可以在我的 github 上获得。r 是任何形式的数据可视化的神奇工具,要了解更多请查看这个。
参考
Python 字符串从零开始!!!
让我们了解一下 python 中字符串的基础知识。

Credits: GangBoard
字符串是一系列字符,可以存储为常量或不同的变量。字符串被视为一种数据类型。通常,程序员必须用引号将字符串括起来,以便将数据识别为字符串,而不是数字或变量名。下面是一些日常最常用的字符串方法,也是最常被问到的面试问题之一。此外,代码可以在我的 GitHub 页面上找到。
比如用 python 声明一个字符串:
**# Declaring a string variable** string = “This is a python tutorial”
print(string)**This is a python tutorial**print(type(string))**<class 'str'>**
1。使用字符串对 python 中的序列进行转义
在 Python 字符串中,反斜杠“”是一个特殊字符,也称为“转义字符。它用于表示某些空白字符:" \t "是一个制表符," \n "是一个新行," \r "是一个回车符。最后,“”可以用来转义本身:“ ** ”是字面上的反斜杠字符。来源 Python 教程。
**# THis is an escape sequence.** string = “This is a \”Google Colab\” python notebook”print(string)**This is a "Google Colab" python notebook**
2。通过索引访问字符串
字符串可以通过它们的索引来访问,以便获得值。要做到这一点,你所要做的就是把数字(索引值)和字符串的名字放在一对方括号内。
string = “Python”print(string[2])**t**print(string[5])**n**
3。切一根绳子
分割字符串有助于从字符串中获取一组字符。当我们想要访问字符串中的一组特定字符时,这非常有用。下面是一些有用的切片变体。
string = “programming”
string**'programming'**
获取字符串中的一个字符
print(string[0:1])**p**
从字符串中获取前三个字符
print(string[0:3])**pro**
从字符串中获取前三个字符(替换)
print(string[:3])**pro**
从字符串中获取最后三个字符
print(string[-3:])**ing**
获取所有字符,但不包括字符串中的前三个字符
print(string[3:])**gramming**
获取所有字符,但不包括字符串的后三个字符
print(string[:-3])**programm**
反转给定字符串中的所有字符
print(string[::-1])**gnimmargorp**
打印字符串中所有字符的替代方法
print(string[::])**programming**
4。拆分字符串
有时拆分字符串是一个方便的选择,因为这是将字符串转换成列表的最简单的方法之一。我知道我没有提到列表,但是请记住,split 将字符串转换为列表。你可以找到我写的关于 python 列表的材料,它可以为掌握 Python 列表提供足够的思路:
让我们从基础到高级理解 Python 列表。
towardsdatascience.com](/python-lists-from-scratch-4b958eb956fc)
String = “Computer Programming”
String**‘Computer Programming’**type(String)**str**list = String.split()
list**['Computer', 'Programming']**type(list)**list**
5。替换字符串
python 中的 Replace 函数是可以应用于字符串的最好的函数之一。例如,下面显示的是一个字符串“Money ”,我们需要替换美元和逗号,这可以如下所示完成。
Money = ‘$113,678’
print(Money)
print(“===========================”)
print(type(Money))**$113,678
===========================
<class ‘str’>**Money = Money.replace(‘$’, ‘’)
Money**‘113,678’**Money = Money.replace(‘,’, ‘’)
Money**‘113678’**Money = int(Money)
print(Money)
print(“===========================”)
print(type(Money))**113678
===========================
<class ‘int’>**
6。加入
连接函数是 python 用来按照指定的模式连接字符串的。
String = “Python Programming is fun”
String**‘Python Programming is fun’**String = “ “.join(String)
String**‘P y t h o n P r o g r a m m i n g i s f u n’**
7.大写
capitalize 函数将单词或字符串中的第一个字符大写。
string = “programming”
string**‘programming’**string = string.capitalize()
string**‘Programming’**
8.中心
center 方法返回用指定字符填充的字符串。
string = “python”
string**‘python’**print(string.center(15, ‘*’))*******python******
9.发现
find 方法返回给定子串位置的索引。如果找不到该值,则返回-1。
string = “programming”
string**‘programming’**print(string.find(‘p’))**0**print(string.find(‘t’))**-1**
10.剥夺
strip 函数去除字符串开头和结尾的空格。
string = “ programming is easy “
string**‘ programming is easy ‘**print(string.strip())**programming is easy**
因此,以上是 Python 中非常重要的字符串技术或函数。一些例子是从 Python 字符串中引用的。我用一种简单的方式写了这篇教程,这样每个人都可以理解和掌握 Python 中字符串的概念,而不需要以前的编程知识或经验。如果你们对代码有什么疑问,评论区就是你们的了。
谢谢你
Python 的技巧和诀窍,你还没有看过
注:这最初发布在martinheinz . dev
关于 Python 中的许多很酷的特性,比如变量解包、部分函数、枚举可迭代对象,已经有很多文章,但是对于 Python 来说,还有更多的东西要讨论,所以在这里我将尝试展示一些我知道并使用的特性,这些特性我还没有在其他地方提到过。所以我们开始吧。

净化字符串输入
净化用户输入的问题几乎存在于你编写的每一个程序中。通常,将字符转换为小写或大写就足够了,有时您可以使用 Regex 来完成这项工作,但是对于复杂的情况,可能有更好的方法:
在此示例中,您可以看到空白字符“\n”和“\t”已被替换为单个空格,并且“\r”已被完全删除。这是一个简单的例子,但是我们可以更进一步,使用unicodedata包和它的combining()函数生成大的重映射表,来生成和映射我们可以用来从字符串中删除所有重音符号的表。
获取迭代器的切片
如果你尝试获取迭代器的一部分,你会得到一个TypeError,声明生成器对象是不可下标的,但是有一个简单的解决方法:
使用itertools.islice我们可以创建一个islice对象,它是一个迭代器,产生想要的项目。不过需要注意的是,这将消耗 slice 开始之前的所有生成器项,以及我们的islice对象中的所有项。
跳过 Iterable 的开头
有时你不得不处理那些你知道以可变数量的不需要的行开始的文件,比如注释。itertools再次提供了简单的解决方案:
该代码片段只在初始注释部分后生成几行。如果我们只想丢弃 iterable 开头的条目(本例中是行),而不知道有多少条目,这种方法会很有用。
只有关键字参数的函数(kwargs)
创建只接受关键字参数函数会有所帮助,以便在使用这样的函数时提供(强制)更大的清晰度:
如你所见,这可以通过在关键字参数前放置单个*参数来轻松解决。如果我们把位置参数放在*参数之前,显然会有位置参数。
创建支持with语句的对象
我们都知道如何使用with语句打开文件或者获取锁,但是我们真的能实现我们自己的吗?是的,我们可以使用__enter__和__exit__方法实现上下文管理器协议:
这是在 Python 中实现上下文管理的最常见方法,但是还有更简单的方法:
上面的代码片段使用contextmanager manager decorator 实现了内容管理协议。进入with块时执行tag功能的第一部分(在yield之前),然后执行该块,最后执行tag功能的剩余部分。
使用__slots__节省内存
如果你曾经写过一个程序,它创建了某个类的大量实例,你可能会注意到你的程序突然需要大量的内存。这是因为 Python 使用字典来表示类的实例的属性,这使得它很快,但不是非常有效的内存,这通常不是问题。然而,如果它成为你的程序的一个问题,你可以尝试使用__slots__:
这里发生的情况是,当我们定义__slots__属性时,Python 使用小的固定大小的属性数组而不是字典,这大大减少了每个实例所需的内存。使用__slots__也有一些缺点——我们不能声明任何新的属性,并且我们被限制在__slots__上使用。同样,带有__slots__的类不能使用多重继承。
限制 CPU 和内存的使用
如果您不想优化程序内存或 CPU 的使用,而是想直接将其限制在某个硬数字,那么 Python 也有一个库:
在这里,我们可以看到设置最大 CPU 运行时间和最大内存使用限制的两个选项。对于 CPU 限制,我们首先获得特定资源软限制和硬限制(RLIMIT_CPU),然后使用参数指定的秒数和先前检索的硬限制来设置它。最后,如果 CPU 时间超时,我们注册导致系统退出的信号。至于内存,我们再次检索软限制和硬限制,并使用带有大小参数和检索到的硬限制的setrlimit进行设置。
控制什么可以导入,什么不可以导入
有些语言有非常明显的导出成员(变量、方法、接口)的机制,比如 Golang,只导出以大写字母开头的成员。另一方面,在 Python 中,一切都是导出的,除非我们使用__all__:
使用上面的代码片段,我们可以限制使用from some_module import *时可以导入的内容。对于这个具体的例子,通配符导入与仅导入bar。此外,我们可以将__all__留空,当使用通配符导入从该模块导入时,不会导出任何内容,从而导致AttributeError。
比较运算符最简单的方法
考虑到有相当多的比较操作符——__lt__ , __le__ , __gt__ ,或__ge__,为一个类实现所有的比较操作符可能会很烦人。但是如果有更简单的方法呢?functools.total_ordering来救援:
这实际上是如何工作的?total_ordering decorator 用于简化我们的类实现实例排序的过程。只需要定义__lt__和__eq__,这是映射剩余操作所需的最低要求,这是装饰者的工作——它为我们填补了空白。
结论
在日常 Python 编程中,并非所有这些特性都是必不可少的和有用的,但其中一些有时可能会派上用场,它们还可能会简化任务,否则实现起来会非常冗长和烦人。我还想指出的是,所有这些特性都是 Python 标准库的一部分,而其中一些在我看来就像是标准库中非常非标准的东西,所以每当你决定用 Python 实现某个东西时,首先要在标准库中寻找它,如果你找不到它,那么你可能找得不够仔细(如果它真的不在那里,那么它肯定在某个第三方库中)。🙂
您还没有看过的 Python 技巧和诀窍,第 2 部分
注意:这最初发布在martinheinz . dev
几周前,我发表了一篇文章(此处为)关于一些不太为人所知的 Python 特性,很多人似乎都喜欢它,所以这里有另一轮 Python 特性,希望你还没有看到。

使用slice功能命名切片
使用大量硬编码的索引值很快会导致维护和可读性方面的混乱。一种选择是对所有索引值使用常量,但我们可以做得更好:
在这个例子中,我们可以看到我们可以避免神秘的索引,首先使用slice函数命名它们,然后在切掉字符串的一部分时使用它们。您还可以使用切片对象的属性.start、.stop和.step来获得更多关于切片对象的信息。
许多命令行工具或脚本需要用户名和密码才能操作。因此,如果您碰巧编写了这样的程序,您可能会发现getpass模块很有用:
这个非常简单的包允许您提示用户输入密码,并通过提取当前用户的登录名来获取他们的用户名。请注意,不是每个系统都支持隐藏密码。Python 会试图警告你这一点,所以只需在命令行中阅读警告。
现在,对于 Python 标准库的一个更难理解的特性。如果您发现自己需要使用类似于Levenshtein distancePython 和difflib的工具来查找类似于某个输入字符串的单词,您会得到支持。
difflib.get_close_matches找到最佳的【足够好】匹配。这里,第一个参数与第二个参数相匹配。我们还可以提供可选参数n,它指定要返回的最大匹配数。可以设置另一个可用的关键字参数cutoff(默认为 0.6)来改变匹配字符串得分的阈值。
如果你必须用 Python 做一些网络工作,你可能会发现ipaddress模块非常有用。一个用例是从 CIDR 生成 ip 地址列表(无类域间路由):
另一个很好的特性是 ip 地址的网络成员检查:
还有很多更有趣的特性我就不赘述了,因为你可以在这里找到那些。但是请注意,在ipaddress模块和其他网络相关模块之间只有有限的互操作性。例如,您不能使用IPv4Network的实例作为地址字符串——它们需要首先使用str进行转换。
如果你是一个拒绝使用 IDE 并且用 Vim 或 Emacs 编码的人,那么你可能会遇到这样一种情况,在 IDE 中使用调试器是很有用的。你知道吗?你有一个——用python3.8 -i运行你的程序——-i会在你的程序终止时启动交互式 shell,从那里你可以探索所有变量并调用函数。很好,但是实际的调试器(pdb)怎么样?
让我们使用下面的程序(script.py):
用python3.8 -i script.py运行脚本
我们看到了崩溃的位置,现在让我们设置一个断点:
现在再运行一次:
大多数情况下,打印语句和回溯对于调试来说已经足够了,但有时,您需要开始探索一下,以了解程序内部发生了什么。在这些情况下,您可以设置断点,当您运行程序时,执行将在断点处停止,您可以检查您的程序,例如,列出函数参数、计算表达式、列出变量或如上所示单步执行。pdb是全功能的 python shell,所以你可以执行任何文学上的东西,但是你需要一些调试器命令,你可以在这里找到
一个在编程语言中很常见,但在 Python 中不常见的特性是函数重载。即使不能重载普通函数,也可以使用类方法重载构造函数:
你可能会倾向于将备选构造函数的所有逻辑放到__init__中,并使用*args、**kwargs和一堆if语句来解决它,而不是使用类方法。这可能行得通,但可能会变得难以阅读和维护。因此,我建议在__init__中加入很少的逻辑,并在不同的方法/构造函数中执行所有的操作。通过这种方式,对于类的维护者和用户来说,你将得到干净清晰的代码。
您是否曾经编写过执行昂贵的 I/O 操作的函数,或者可以从缓存(内存化)结果中获益的相当慢的递归函数?如果你这样做了,那么使用来自functools的lru_cache有一个简单的解决方案:
在本例中,我们正在缓存 GET 请求(最多 32 个缓存结果)。您还可以看到,我们可以使用cache_info方法检查我们函数的缓存信息。装饰器还提供了一个使缓存结果无效的clear_cache方法。我还想指出,这不应该用于有副作用的函数或每次调用都会创建可变对象的函数。
在列表中找到最常见的项目是一项非常普通的任务,你可以使用for循环和字典(map)来完成,但这将是浪费时间,因为在collections模块中有Counter类:
在幕后,Counter只是一个将条目映射到出现次数的字典,因此您可以像使用普通的dict一样使用它:
除此之外,您还可以使用update(more_words)方法轻松地添加更多的元素到计数器中。Counter的另一个很酷的特性是,你可以使用数学运算(加法和减法)来组合和减去Counter的实例。
我认为,如果你正在使用 Python,这次我在这里分享的大多数技巧几乎每天都有用,所以我希望它们能派上用场。此外,如果你对这些 Python 技巧和诀窍有任何想法,或者如果你知道解决上述问题的任何更好的方法,请告诉我!🙂
初级数据科学家的 Python 工具
使用这些库使数据科学家的生活变得非常容易
Python 拥有适用于数据科学项目生命周期所有阶段的工具。任何数据科学项目固有地包含以下 3 个阶段。

Photo by Florian Klauer on Unsplash
- 数据收集
- 数据建模
- 数据可视化
Python 为这三个阶段提供了非常简洁的工具。
数据收集
1)美汤

当数据收集涉及到从网上抓取数据时,python 提供了一个名为 beautifulsoup 的库。
**from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'html.parser')**
这个库解析一个网页并整齐地存储它的内容。比如,它会单独存储标题。它还将单独存储所有的标签,这将为您提供包含在页面中的非常整洁的 URL 列表。
作为一个例子,让我们看一个关于爱丽丝漫游奇境记的简单网页。

Webpage screenshot
显然,我们可以看到一些 html 元素,我们可以抓取。
- 标题——睡鼠的故事
- 页面文本
- 超链接——埃尔希、拉西和蒂莉。
Soup 使得提取这些信息变得很容易
**soup.title**
*# <title>The Dormouse's story</title>***soup.title.string**
*# u'The Dormouse's story'***soup.p**
*# <p class="title"><b>The Dormouse's story</b></p>***for link in soup.find_all('a'):
print(link.get('href'))**
*#* [*http://example.com/elsie*](http://example.com/elsie) *#* [*http://example.com/lacie*](http://example.com/lacie) *#* [*http://example.com/tillie*](http://example.com/tillie)**print(soup.get_text())** *# The Dormouse's story
#
# Once upon a time there were three little sisters; and their names were
# Elsie,
# Lacie and
# Tillie;
# and they lived at the bottom of a well.
#
# ...*
对于从 HTML 和 XML 文件中提取数据来说,这是一个极好的工具。它提供了导航、搜索和修改解析树的惯用方法。它通常可以为程序员节省数小时甚至数天的工作时间。
2) Wget

下载数据,尤其是从网上下载数据,是数据科学家的重要任务之一。Wget 是一个免费的实用程序,用于从网络上非交互式下载文件。因为它是非交互式的,所以即使用户没有登录,它也可以在后台工作。它支持 HTTP、HTTPS 和 FTP 协议,以及通过 HTTP 代理的检索。因此,下次你想下载一个网站或一个页面上的所有图片时, wget 随时为你提供帮助。
>>> **import wget**
>>> **url = '**[**www.futurecrew.com/skaven/song_files/mp3/razorback.mp3'**](http://www.futurecrew.com/skaven/song_files/mp3/razorback.mp3')
>>> **filename = wget.download(url)**
*100% [................................................] 3841532 / 3841532* >>> **filename**
*'razorback.mp3'*
3)数据 API
除了你需要抓取或下载数据的工具之外,你还需要实际的数据。这就是数据 API 发挥作用的地方。python 中有许多 API 可以让您免费下载数据,例如 Alpha Vantage 提供全球股票、外汇和加密货币的实时和历史数据。他们有长达 20 年的数据。
例如,使用 alpha vantage APIs,我们可以提取比特币每日价值的数据并绘制出来
from **alpha_vantage.cryptocurrencies** import **CryptoCurrencies**
import **matplotlib.pyplot** as **plt**
**cc = CryptoCurrencies(key='YOUR_API_KEY',output_format='pandas')
data, meta_data = cc.get_digital_currency_daily(symbol='BTC', market='USD')
data['1a. open (USD)'].plot()
plt.tight_layout()
plt.title('Alpha Vantage Example - daily value for bitcoin (BTC) in US Dollars')
plt.show()**

Plotted Image
其他类似的 API 示例有
- 开放通知 API — NASA 和国际空间站数据
- 汇率 API —欧洲中央银行发布的当前和历史外汇汇率



A few APIs for data collection
数据建模
正如本文中提到的,数据清理或平衡是数据建模之前的一个重要步骤。
1)不平衡学习
Imabalanced-learn 就是这样一个平衡数据集的工具。当一个类或类别的数据比其他类别的数据具有不成比例的大样本时,数据集是不平衡的。这可能会给分类算法带来巨大的问题,分类算法最终可能会偏向拥有更多数据的类。
[## 亚马逊废除了显示对女性有偏见的秘密人工智能招聘工具
(路透社)-Amazon.com 公司的机器学习专家发现了一个大问题:他们的新…
www.reuters.com](https://www.reuters.com/article/us-amazon-com-jobs-automation-insight-idUSKCN1MK08G)
例如,该库中名为 Tomek-Links 的命令有助于平衡数据集。
from **imblearn.under_sampling** import **TomekLinks **
tl = **TomekLinks**(return_indices=True, ratio='majority')
X_tl, y_tl, id_tl = **tl.fit_sample**(X, y)

Balancing an imabalanced data set
2)冰冷的生态系统——NumPy

Image by Ty Shaikh
实际的数据处理或建模通过 python 的 scipy 堆栈进行。Python 的 SciPy Stack 是专门为 python 中的科学计算设计的软件集合。nScipy secosystem 包含了许多有用的库,但是 Numpy 无疑是其中最强大的工具。
最基本的包 NumPy 代表数值 Python,科学计算栈就是围绕它构建的。它为矩阵运算提供了大量有用的特性。如果有人使用过 MATLAB,他们会立即意识到 NumPy 不仅和 MATLAB 一样强大,而且在操作上也非常相似。
3)熊猫
Pandas 是一个提供数据结构来处理和操作数据的库。称为 dataframe 的二维结构是最流行的一种。
熊猫是数据争论的完美工具。它旨在实现快速简单的数据操作、聚合和可视化。

Example of a DataFrame — Shanelynn
数据可视化
1) Matplotlib
SciPy 生态系统中的另一个软件包是 Matplotlib,它是为轻松生成简单而强大的可视化而定制的。这是一个 2D 绘图库,以各种硬拷贝格式生成出版物质量数字
Matplotlib 输出的一些示例
import numpy as np
import matplotlib.pyplot as pltN = 5
menMeans = (20, 35, 30, 35, 27)
womenMeans = (25, 32, 34, 20, 25)
menStd = (2, 3, 4, 1, 2)
womenStd = (3, 5, 2, 3, 3)
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars: can also be len(x) sequencep1 = plt.bar(ind, menMeans, width, yerr=menStd)
p2 = plt.bar(ind, womenMeans, width,
bottom=menMeans, yerr=womenStd)plt.ylabel('Scores')
plt.title('Scores by group and gender')
plt.xticks(ind, ('G1', 'G2', 'G3', 'G4', 'G5'))
plt.yticks(np.arange(0, 81, 10))
plt.legend((p1[0], p2[0]), ('Men', 'Women'))plt.show()

Bar Plot
其他几个例子



Taken from Matplotlib Docs
海洋生物
Seaborn 是一个基于 matplotlib 的 Python 数据可视化库。它主要为绘制有吸引力的和信息丰富的统计图形提供高级接口。它主要侧重于可视化,如热图

Seaborn docs
3)运动速度
MoviePy 是一个用于视频编辑的 Python 库,包括剪切、拼接、标题插入、视频合成、视频处理和自定义效果的创建。它可以读写所有常见的音频和视频格式,包括 GIF。

https://zulko.github.io/moviepy/gallery.html
奖金自然语言处理工具— FuzzyWuzzy
这个有趣的发声工具在字符串匹配方面是一个非常有用的库。人们可以快速实现诸如字符串比较比率、令牌比率等操作。
>>> **fuzz.ratio("this is a test", "this is a test!")**
97
>>> **fuzz.partial_ratio("this is a test", "this is a test!")**
100
>>> **fuzz.ratio("fuzzy wuzzy was a bear", "wuzzy fuzzy was a bear")**
91
>>> **fuzz.token_sort_ratio("fuzzy wuzzy was a bear", "wuzzy fuzzy was a bear")**
100
Python 拥有大量的信息和工具来执行数据科学项目。探索永远不会太迟!
Python 窍门 101,每个新程序员都应该知道的。

A pretty picture to catch your eye.
Python 比以往任何时候都更受欢迎,人们每天都在证明 Python 是一种非常强大且易于掌握的语言。
我从事 Python 编程已经有几年了,最近 6 个月是专业的,以下是我希望在刚开始时就知道的一些事情:
- 字符串操作
- 列表理解
- λ&map()
- if、elif 和 else 条件一行程序
- zip()
# 1: 琴弦操纵

Pun definitely intended
Python 非常擅长使用数学运算符+和*来决定如何处理字符串:
>>> my_string = "Hi Medium..!"
>>> print(my_string * 2)
**Hi Medium..!Hi Medium..!**>>> print(my_string + " I love Python" * 2)
**Hi Medium..! I love Python I love Python**
我们也可以很容易地反转一个字符串,使用[::-1],这不仅限于字符串!:
>>> print(my_string[::-1])
**!..muideM iH**>>> my_list = [1,2,3,4,5]
>>> print(my_list[::-1])
**[5, 4, 3, 2, 1]**
单词列表呢?我们可以做一个尤达翻译机!:
>>> word_list = ["awesome", "is", "this"]
>>> print(' '.join(word_list[::-1]) + '!')
**this is awesome!**
上面我们使用了.join()方法,用' '(空格)连接反转列表中的所有元素,并添加一个感叹号。
# 2:列出理解

哦,孩子,一旦我知道了这些,我的整个世界都变了(不是真的,但足够接近了)。这是对列表进行快速操作的一种非常强大、直观和可读的方式。
假设我们有一个随机函数,将一个数平方并加上 5:
>>> def stupid_func(x):
>>> return x**2 + 5
现在,假设我们想将这个函数应用于列表中的所有奇数,如果您不知道列表的理解,这可能是您要做的:
>>> my_list = [1, 2, 3, 4, 5]
>>> new_list = []
>>> for x in my_list:
>>> if x % 2 != 0:
>>> new_list.append(stupid_func(x))
>>> print(new_list)
**[6, 14, 30]**
但是有一个更简单的方法!:
>>> my_list = [1, 2, 3, 4, 5]
>>> print([stupid_func(x) for x in my_list if x % 2 != 0])
**[6, 14, 30]**
List comprehensions 与语法[ expression **for** item **in** list ]一起工作,如果你想用一个额外的布尔条件,比如上面的“奇数”条件:[ expression **for** item **in** list **if** conditional ]这与:
>>> **for** item **in** list:
>>> **if** conditional:
>>> expression
非常酷!尽管如此,我们仍然可以做得更好,因为我们并不真的需要那个“stupid_func”:
>>> print([x ** 2 + 5 for x in my_list if x % 2 != 0])
**[6, 14, 30]**
嘣!
# 3: Lambda & Map

Crazy paint skills coming in from the right
希腊字母的第 11 个
Lambda 有点奇怪,但就像这个列表中的其他东西一样,一旦你明白了,它真的很强大而且很直观。
基本上,Lambda 函数是一个小型的匿名函数。为什么匿名?因为 Lambdas 最常用于执行不需要像def my_function()这样的正式函数定义的小/简单操作。
让我们以上面的例子为例,将一个数平方并加 5。上面我们用def stupid_func(x)做了一个正式的函数定义,现在让我们用一个 lambda 函数重新创建它:
>>> stupid_func = (lambda x : x ** 2 + 5)
>>> print([stupid_func(1), stupid_func(3), stupid_func(5)])
**[6, 14, 30]**
那么为什么要使用这种奇怪的语法呢?当你想在不定义实际函数的情况下执行一些简单的操作时,这就变得很有用了。以数字列表为例,我们如何在 Python 中对这样的列表进行排序?一种方法是使用sorted()方法:
>>> my_list = [2, 1, 0, -1, -2]
>>> print(sorted(my_list))
**[-2, -1, 0, 1, 2]**
这样做了,但是假设我们想要按最小平方数排序,我们可以使用 lambda 函数来定义键,这是sorted()方法用来确定如何排序的。
>>> print(sorted(my_list, **key** = lambda x : x ** 2))
**[0, -1, 1, -2, 2]**
地图
Map 是一个简单的函数,用于将函数应用于一些元素序列,如列表。假设我们必须列出一个列表中的每个元素与另一个列表中相应的元素相乘的位置,我们如何做呢?使用 lambda 函数和映射!:
>>> print(list(map(lambda x, y : x * y, [1, 2, 3], [4, 5, 6])))
**[4, 10, 18]**
与这个怪物相比,这是简单而优雅的:
>>> x, y = [1, 2, 3], [4, 5, 6]
>>> z = []
>>> for i in range(len(x)):
>>> z.append(x[i] * y[i])
>>> print(z)
**[4, 10, 18]**
# 4: if、elif 和 else 条件一行程序

在代码的某个地方,您可能会看到这样的内容:
>>> x = int(input())
>>> **if** x >= 10:
>>> print("**Horse**")
>>> **elif** 1 < x < 10:
>>> print("**Duck**")
>>> **else**:
>>> print("**Baguette**")
当你运行这个函数时,你会得到一个来自input()函数的输入,假设我们输入 5,我们会得到Duck。但是我们也可以用这样的一行程序来写整个事情:
print("**Horse**" **if** x >= 10 **else** "**Duck**" if 1 < x < 10 **else** "**Baguette**")
真的就那么简单!仔细阅读您的旧代码,您会发现许多地方可以将简单的条件 if / else 语句简化为一行程序。
说到一行程序,想只使用一行代码就获得整个数据集的概览吗?看看这个:
在不到 30 秒的时间内完成所有标准数据分析。熊猫侧写的奇迹。
towardsdatascience.com](/exploring-your-data-with-just-1-line-of-python-4b35ce21a82d)
# 5: zip()

还记得“map()”一节中关于在两个列表之间并行应用一些东西的例子吗?zip()让这变得更加简单。
假设我们有两个列表,一个包含名字,一个包含姓氏,我们如何有序地合并它们?使用zip()!:
>>> first_names = ["Peter", "Christian", "Klaus"]
>>> last_names = ["Jensen", "Smith", "Nistrup"]
>>> print([' '.join(x) for x in zip(first_names, last_names)])
**['Peter Jensen', 'Christian Smith', 'Klaus Nistrup']**
哇哦!哪里弄错了,我的名字不是彼得·詹森..但是我们知道如何轻松解决这个问题!:
>>> print([' '.join(x) for x in zip(first_names, last_names[::-1])])
**['Peter Nistrup', 'Christian Smith', 'Klaus Jensen']**
更像这样
如果您喜欢这些易于应用的东西来改进您的 Python 工作流,请看看我的新文章:
让您的数据分析更上一层楼!
towardsdatascience.com](/7-things-to-quickly-improve-your-data-analysis-in-python-3d434243da7)
结束语
这只是一个简单的列表,我把它放在一起是为了让你对 Python 能做的一些伟大的事情有个印象。请留下你的任何反馈,如果你想让我解释一些具体的事情,或者如果你觉得我犯了一个错误!
感谢阅读。希望您发现这很有用!
阅读彼得·尼斯特拉普在媒介上的作品。数据科学、统计和人工智能...推特:@PeterNistrup,LinkedIn…
medium.com](https://medium.com/@peter.nistrup)
Python 技巧,从内置数据类型继承
了解如何简单地定制您的列表、字典等功能。

Image Courtesy of Greg Rakozy via Unsplash
定制列表和字典的功能,以满足您的需求!
Python 让我们设计的类继承了内置的类。内置的继承(子)类共享内置的所有相同属性(包括方法)。
我们可以有效地利用核心内置功能,但定制选定的操作。这非常有帮助,因为内置行为是所有 Python 开发人员都熟悉的。这带来了使我们的继承类易于学习的好处。
这给了我们很大的权力,因为我们的对象可以像一个字典或列表对象在各个方面的行为,除了在我们决定要它表现不同的地方。
自定义列表对象
为了说明如何利用核心对象并根据我们的需求定制它们,让我们创建一个简单的场景。
让我们构建一个只接受整数和浮点数的自定义列表类。如果我们试图追加任何其他数据类型,比如说一个字符串,我们将生成一个定制的错误异常,这将有助于通知我们类的用户这个特定列表只能接受整数和浮点数。
我们首先创建一个从 build in list 类继承的 IntFloatList 自定义类。这意味着 IntFloatList 在各个方面都像一个列表,除了在 append 被调用的时候。
我们从内置的 list 类中覆盖了现有的 append 方法。从下面的图像中,可以清楚地看到蓝色的小圆圈,旁边有红色的箭头。append 有两个参数,一个是对象(在我们的例子中是 x),另一个是我们想要追加的元素。
一个简单的条件语句,测试我们试图添加的元素是 int 还是 float 类的实例。如果不是,我将引发一个自定义的 CustomIntFloatError 异常。因为 Python 中的几乎所有东西都是对象,所以当我们引发 CustomIntFloatError 异常时,我们实际上是在创建 CustomIntFloatError 类的一个实例。
然后,我编写了一个自定义的 magic str 方法,该方法采用新构造的 CustomIntFloatError 对象,并通知用户当我们试图追加一个字符串(比如说' four ')时,会出现一个自定义错误。
下面让我们看看它的功能!

到目前为止,一切顺利。对象 x 是一个 IntFloatList 对象,当我们将 4 或 4.5 添加到我们的列表中时,它们被正确地添加,并且列表的长度增加。但是,对于不是整数和浮点数的对象呢?
当我们试图追加字符串“four”时,会引发自定义错误异常。如控制台所示,一条错误消息通知用户 IntFloatList 是如何输出的。

When the user attempts to append a string object (here the string ‘four’), a useful error message is displayed, informing the user how the custom IntFloatList object is intended to be used.
虽然给出的例子可能不是非常有用,但它确实说明了我们如何定制内置类型来满足我们的需求。
自定义可以添加到字典中的值
为了再次演示,让我们创建一个自定义 dictionary 对象,它只能接受小于 50 的整数作为其值。
这里,我们需要从父 dict 类重新实现 setitem。setitem 是我们在字典中设置键和值时调用的神奇方法。这是一个神奇的方法,这意味着它是隐式调用的。
如果用户试图添加大于 50 的值或非整数的数据类型,将会引发自定义的 MyDictError 异常。如果没有出现异常,dict。setitem 将使用我们的对象调用,该对象将被处理,在本例中为“my_dict_object”。
我们现在可以看到 setitem,正如我们重新定义的那样,是一个与其他一些功能的挂钩,在设置键和值之前,我们可以做任何我们想做的事情。我们可以创建一个只允许某些键的字典,或者我们可以创建一个只允许某些类型的值的字典,就像这个例子所示的那样。

如果您想尝试自己运行代码片段,请使用下面显示的 GitHub gist,并进行测试。
这一次,当我试图添加一个浮点数(这里是 3.2)时,会引发一个自定义的 MyDictError 异常。输出的错误消息通知用户,当值被添加到字典中时,对该值的要求是什么。

摘要
我们可以创造功能强大但相对简单的物体。因为我们是从 list 或 dict 继承的,所以我们可以利用很多方法,而我们并没有真正意识到这一点。在这两个示例中,当没有引发异常时,将调用 parent init 和 repr 方法。
在本文中,我们学习了如何继承 Python 的内置对象,以及如何使用它们的神奇方法来改变它们的行为,同时保留它们的其他功能。
Python 对象模型的优雅赋予了这种语言整体的优雅和力量,并让它为我们所用。我们可以非常容易地将这些对象的使用传达给我们的开发伙伴。我们需要说的是,它的行为像一个列表或字典,但只是有一点不同,他们不需要学习方法名或一个新的接口,这是这种语言的一个非常强大的功能。
Python 元组从零开始!!!
让我们了解一下 python 元组的基本概念。

Image credits: GangBoard
在开始之前,我想告诉你,我将以问答的形式来写这篇教程,因为它会有所帮助,尤其是在面试的时候。在整个教程中,我将只研究一个例子来保持一致性。本教程的完整代码可以在我的 GitHub 页面上找到。
1)什么是元组,为什么使用元组?
元组是一种数据结构,用于存储异构数据,即不同类型的数据。一个元组用于将相关数据分组在一起,比如狗的名字、身高、体重、品种和颜色(我喜欢狗)。一个元组包含几个用逗号分隔的值。
2)如何创建元组?
可以通过将值放在一对括号内并用逗号分隔来创建元组,例如:
让我们创建一个称为“元组”的元组,并放入一些值,包括字符串和整数数据。这可以如下所示:
**# Creating a tuple and storing the values**tuple = (“Today’s”, “Date”, “Is”, 15, “August”, 2019)
print(tuple)**(“Today’s”, ‘Date’, ‘Is’, 15, ‘August’, 2019)**print(type(tuple))**<class 'tuple'>**
如上所述,一个元组可以保存异构数据(因此证明:)
3)我们如何访问一个元组的值?
索引可以做到这一点。元组的值可以通过它们的索引来获取。要做到这一点,您只需将数字(索引值)和元组的名称一起放在一对方括号中。
**# Accessing the values of a tuple**print(tuple[2])**'Is'**print(tuple[5])**2019**
4)如何嵌套或合并两个元组?
只要将旧元组放置在由逗号分隔的新创建的元组旁边,就可以实现两个或更多元组的嵌套。
**# Nesting two tuples as one tuple**tuple1 = (“And”, “its”, “Thursday”)
print(tuple1)**(‘And’, ‘its’, ‘Thursday’)**nest = tuple, tuple1
print(nest)**((“Today’s”, ‘Date’, ‘Is’, 15, ‘August’, 2019), (‘And’, ‘its’, ‘Thursday’))**
5)元组是不可变的还是可变的?
在回答这个问题之前,你应该知道不可变的值是不可变的,可变的值是可变的。现在让我们来回答这个问题,答案是元组是不可变的,是的,一旦元组被创建,我们就永远不能改变它们的值。不信,我证明给你看。
**# Tuples are immutable in nature**print(tuple[0])**“Today’s”****# Changing the value of the 0th index to "Hi"**tuple[0] = ("Hi")
print(tuple)**--------------------------------------------------------------------****TypeError: Traceback (most recent call last)**[**<ipython-input-12-18297fa5df7e>**](/<ipython-input-12-18297fa5df7e>) **in <module>()
----> 1 tuple[0] = ("Hi")
2 tuple****TypeError: 'tuple' object does not support item assignment**
因此,以上证明了元组在本质上是不可变的。
6)元组能在其中存储相同的数据吗?
是的,元组可以在其中存储相同的数据,我们可以在一个元组中存储许多相同的值。例如:
**# Storing identical data with a tuple**tuple = (“Today’s”, “Date”, “Is”, 15, “August”, 2019)
print(tuple)**(“Today’s”, ‘Date’, ‘Is’, 15, ‘August’, 2019)**tuple = [(“Today’s”, “Date”, “Is”, 15, “August”, 2019), (“Today’s”, “Date”, “Is”, 15, “August”, 2019)]
print(tuple)**[(“Today’s”, ‘Date’, ‘Is’, 15, ‘August’, 2019), (“Today’s”, ‘Date’, ‘Is’, 15, ‘August’, 2019)]**
如何循环遍历一个元组?
这个问题很直接,使用循环结构,我们可以循环遍历一个元组。下面我将使用 for 循环并遍历元组中的值,您可以类似地使用其他循环结构并获得结果。
tuple = (“Today’s”, “Date”, “Is”, 15, “August”, 2019)
print(tuple)**(“Today’s”, ‘Date’, ‘Is’, 15, ‘August’, 2019)**for i in tuple:
print(i)**Today’s
Date
Is
15
August
2019**
8)如何使用循环访问元组的索引?
我们可以使用带有枚举函数的 for 循环来实现这一点。枚举是 Python 的内置函数。它允许我们循环一些东西,并有一个自动计数器,了解更多关于枚举的信息,阅读它的完整文档这里。例如:
**# Accessing the index of the tuple using enumerate function.**tuple = (“Today’s”, “Date”, “Is”, 15, “August”, 2019)
print(tuple)**(“Today’s”, ‘Date’, ‘Is’, 15, ‘August’, 2019)**for counter, value in enumerate(tuple):
print(counter, value)**0 Today's
1 Date
2 Is
3 15
4 August
5 2019**
我们能从元组中移除值或项吗?
我想现在你可以轻松回答这个问题了。答案是否定的,不能从元组中删除值或项,但是可以完全删除元组。这是因为元组是不可变的。例如:
**# Deleting an entire tuple using del**tuple = (“Today’s”, “Date”, “Is”, 15, “August”, 2019)
tuple**(“Today’s”, ‘Date’, ‘Is’, 15, ‘August’, 2019)**del tuple
print(tuple)**<class 'tuple'>**
如上所述,可以使用 del 删除整个元组,所以当我们打印元组时,可以看到其中没有任何元素。
10)如何统计值在元组中出现的次数?
这可以通过使用元组的 count 方法来完成,count 方法返回一个值在元组中出现的次数。例如:
**# Counting the number of times a value has appeared in the tuple**tuple = (“Today’s”, “Date”, “Is”, 15, “August”, 2019, “And”, “Day”, “Is”, “Thursday”)
print(tuple)**("Today's", 'Date', 'Is', 15, 'August', 2019, 'And', 'Day', 'Is', 'Thursday')**print(tuple.count("Is"))
**2**print(tuple.count(15))
**1**
所以在上面的例子中,值“Is”出现了 2 次,类似地,15 出现了 1 次。
11)如何获取元组中某个值的索引?
这可以通过使用元组的索引方法来完成,索引方法搜索值的第一次出现,并返回它的位置。例如:
**# Counting the number of times a value has appeared in the tuple**tuple = (“Today’s”, “Date”, “Is”, 15, “August”, 2019, “And”, “Day”, “Is”, “Thursday”)
print(tuple)**("Today's", 'Date', 'Is', 15, 'August', 2019, 'And', 'Day', 'Is', 'Thursday')**print(tuple.index("Date"))
**1**print(tuple.index("August"))
**4**print(tuple.index("Is"))
**2**
如上所述,index 方法返回值的位置,但是在“是”的最后一种情况下,index 方法返回元组中第一次出现的“是”并返回其位置。
13)如何检查值是否存在于元组中?
这可以使用 if 语句中的关键字中的来检查。例如,让我们检查 tuple 中是否存在 August。
**# Checking for the values present in the tuple**tuple = (“Today’s”, “Date”, “Is”, 15, “August”, 2019)
print(tuple)**("Today's", 'Date', 'Is', 15, 'August', 2019)****# Case 1:**if "August" in tuple:
print(True)
print(tuple.index("August"))
else:
print(False)**True
4**--------------------------------------------------------------------**#** **Case 2:**if "September" in tuple:
print(True)
print(tuple.index("September"))
else:
print(False)**False**
如上所述,“八月”出现在元组的位置 4,但是“九月”不出现在元组中。
因此,以上是以问答格式编写的 Python 中元组的非常重要的技术或功能(通常在编程面试中非常有用)。我引用了一些来自 Python 元组的例子。我以一种简单的方式编写了本教程,这样每个人都可以理解和掌握 Python 中元组的概念,而无需事先具备编程知识或经验。如果你们对代码有什么疑问,评论区就是你们的了。
谢谢你。



浙公网安备 33010602011771号