使用亚马逊评论数据集的产品推荐器
现实世界中的 DS
有什么问题?为什么我们需要推荐系统?
在线商店的目录中有数百万种产品。由于这种“信息过载”,找到合适的产品变得很困难。用户会感到困惑,这使得用户在选择产品时认知超载。
什么是推荐系统?
推荐系统通过推荐可能的产品列表来帮助客户,他们可以很容易地从中选择合适的产品。它们通过提供可比较的成本、功能、交付时间等,使客户了解可供购买的新产品和/或类似产品。
推荐系统已经成为电子商务网站和其他业务(如社交网络、电影/音乐呈现网站)不可或缺的一部分。它们对这些企业的收入有着巨大的影响,也通过减少搜索和筛选过载数据的认知负荷而使用户受益。推荐系统通过理解用户对系统的使用并推荐他们认为有用的项目来个性化用户体验。
你可能遇到过亚马逊推荐系统的例子,如下所示:
Recommendation based on recently viewed items
推荐系统可以用两种不同的方法构建:基于内容的过滤和协同过滤。
基于内容的过滤
在基于内容的过滤中,不同产品之间的相似性是基于产品的属性来计算的。例如,在基于内容的书籍推荐系统中,书籍之间的相似性是基于流派、书籍的作者、书籍的出版商、书籍的标题等来计算的。
协同过滤
协同过滤通常用于推荐系统。这些技术旨在填充用户-项目关联矩阵的缺失条目。我们将使用协同过滤(CF)方法。
CF 基于这样一个想法,即最好的推荐来自有相似品味的人。换句话说,它使用志同道合者的历史项目评级来预测某人将如何评价某个项目。协同过滤有两个子类别,通常称为基于记忆和基于模型的方法。
基于内存的
有两种方法:第一种方法识别用户群,并利用一个特定用户的交互来预测其他相似用户的交互。第二种方法标识已经被用户 A 评级的项目的聚类,并利用它们来预测用户 A 与不同但相似的项目 b 的交互。这些方法通常遇到大型稀疏矩阵的主要问题,因为用户-项目交互的数量对于生成高质量的聚类来说可能太低。
基于模型的
这些方法基于机器学习和数据挖掘技术。目标是训练模型能够做出预测。例如,我们可以使用现有的用户-项目交互来训练一个模型,以预测用户可能最喜欢的前 5 个项目。这些方法的一个优点是,与基于记忆的方法等其他方法相比,它们能够向大量用户推荐大量项目。它们有很大的覆盖范围,甚至在处理大型稀疏矩阵时也是如此。[10]
矩阵分解
一种非常流行的协同过滤方法是矩阵分解。它有一组用户和一组项目,以及一个非常稀疏的矩阵,表示已知的用户对项目的评级。我们希望预测矩阵中缺失的值。为了做到这一点,我们将每个用户和每个项目表示为潜在特征的向量,使得这些向量的点积紧密匹配已知的用户对项目的评级。期望未知的用户对项目的评级也可以通过相应特征向量的点积来近似。我们希望最小化的目标函数的最简单形式是:
这里,r 是已知的用户对项目的评级,x 和 y 是我们试图找到的用户和项目特征向量。由于有许多自由参数,我们需要正则化部分来防止过拟合和数值问题,gamma 是正则化因子。经过一定次数的迭代后,特征向量的变化变得非常小,并且达到收敛。
交替最小二乘法
交替最小二乘法(ALS)是矩阵分解的方法之一。当有两个因变量(在我们的例子中,向量 x 和 y)时,它用于非线性回归模型。该算法固定其中一个参数(用户向量 x),同时通过最小化二次型来最优地求解另一个参数(项目向量 y)。该算法在固定用户向量和更新项目向量以及固定项目向量和更新用户向量之间交替,直到满足收敛标准。
业界有几种有效且可扩展的矩阵分解实现。其中一个突出的例子是由 Apache Spark 提供的,这是一个分布式数据处理引擎,可以通过 Elastic Mapreduce 集群在亚马逊网络服务上轻松运行。
Apache Spark 实现了一个分布式版本的交替最小二乘法(ALS)和权重正则化。
数据采集和选择
该数据集可在加州大学圣地亚哥分校网站上获得。感谢麦考利教授和他的团队提供了这个数据集。[1][2]该数据集包含亚马逊的产品评论和元数据,包括 1996 年 5 月至 2014 年 7 月期间的 1.428 亿条评论。[1]由于数据的巨大规模,处理所有这些数据是一个相当大的挑战。因此,推荐器是使用 5 个产品类别的数据集构建的,即庭院、草坪和花园、乐器、办公产品、汽车、即时视频。
数据清理
为了使数据具有一致的格式,采取的步骤有:
- 删除不必要的列
- 删除重复记录
- 检查无效数据
- 检查适用列的范围(如等级在 1 到 5 之间)
- 处理缺失值和异常值
探索性数据分析
了解要素及其数据类型并查看数据分布是一种很好的做法。绘制数据可以洞察数据遵循的模式。“庭院、草坪和花园”产品类别数据集用于绘制图表 1 至 6。
1)产品总体评分分布
许多用户给产品打 5 分,然后是 4 分和 3 分,而很少用户给 1 分或 2 分。
2)历年评分的平均值和中值
从下图中,我们可以推断出,从 2000 年到 2014 年,产品的平均评级有所下降。从 2000 年到 2014 年,除了 2010 年和 2011 年,产品评级的中位数保持在 5
3)跨年度评分数量趋势
用户对亚马逊上的产品给出的评级数量呈上升趋势,这表明越来越多的用户开始使用亚马逊电子商务网站进行在线购物,越来越多的用户开始对 2000 年至 2014 年购买的产品给出反馈。从 2012 年到 2014 年,用户给出的评级数量显著增加。
4)每个用户的评级分布
每个用户的收视率分布呈长尾正态分布。花园和庭院数据集中的用户总数是 1685。单个用户给出的最大评分数为 63,单个用户给出的最小评分数为 1。根据目前的数据(截至 2014 年),用户在亚马逊上平均给出 7.55 分。
5)按月评级分布
从下面的方框图中,我们可以说 6 月的收视率最高,其次是 5 月和 7 月。这表明在这三个月里,购买量很大。与此相反,二月显示了用户给出的最低数量的评级,因此可以推断最低销售额可能发生在二月。这是有道理的,因为在夏季,园艺产品的购买量较高。
6)具有特定评级的亚马逊产品的年度统计
下图显示,与评级为“4”和“5”的产品相比,评级为“1”、“2”或“3”的产品数量逐年显著减少。
7)按产品类别划分的亚马逊评分明细
我使用了来自亚马逊评论数据集的 5 个不同数据集来绘制下图,即“汽车”、“花园和庭院”、“乐器”、“办公室”和“即时视频”。总的来说,“办公类”产品和“即时视频”在这 5 个类别中似乎更受欢迎。在所有五个类别中,好的评级数量(4.5)多于坏的评级数量(1.2)。
履行
火花设置
我在一台搭载 32 GB 内存英特尔 i5 处理器的 Windows 10 机器上安装了 Spark。
火花 MLLib
Apache Spark MLlib 提供的 ALS 实现用于该推荐器。MLlib 是 Apache Spark 的可扩展机器学习库。Spark 擅长迭代计算,使 MLlib 能够快速运行。
Apache Spark 实现了一个分布式版本的交替最小二乘法(ALS)和权重正则化。Apache 的 spark.mllib 目前支持基于模型的协同过滤,其中用户和产品由一小组潜在因素描述,这些因素可用于预测缺失的条目。spark.mllib 使用交替最小二乘(ALS) 算法来学习这些潜在因素。[6] [12]
模型选择和超参数
为推荐引擎设置的主要超参数是秩(潜在因素的数量)、λ(正则化参数)和迭代。对于[0.6,0.2,0.2]拆分,等级设置为值[8,10,20],对于数据集中无拆分,等级设置为[ 2,5,10,20]。使用的λ范围从[0.001 到 50]。对于数据集中的无分割,迭代次数在 5 到 20 之间变化,对于数据集中的[0.6,0.2,0.2]分割,迭代次数设置为 20。
我使用了 spark.mllib 中的 ALS 实现,在那里可以设置和测试这些超参数。
模型评估
对于推荐引擎评估,使用不同的指标,如 RMSE、MAE、精确度和召回率。我用 RMSE 进行了模型评估。RMSE 越小,推荐引擎越好。
结果
1)数据集分为训练、测试和验证,训练占 60%,测试占 20%,验证占 20%。除了使用分割,我们还在 8-20 的范围内改变等级超参数。一些重要的观察结果如下:
- 我们一直看到这里评估的所有类别的 RMSE 值都很低。
- 令人惊讶的是,办公用品类别的 RMSE 为 0,表明预测是完美的(我们检查了数据中的错误或系统问题,如所有空值或相同值,但没有发现任何问题)。
我们看到度量对秩超参数非常小或没有敏感性。在更大的范围内搜索这个参数将是一个有趣的下一步。
2)在第二种方法中,我们再次对训练、验证和测试集使用 60–20–20 分割。除了使用分割,我们还在 0.001-50 的范围内改变正则化超参数。一些重要的观察结果如下:
a)对于除“办公产品”类别之外的所有产品类别,将正则化参数设置为 0.001 会给出最佳 RMSE。对于“办公产品”类别,0.5 及以上的正则化参数给出最佳 RMSE。
b)除了在办公产品类别的情况下,我们看到度量对正则化超参数非常小或没有敏感性。
结论
在本文中,我们讨论了使用 Apache PySpark MLLib 构建的亚马逊评论数据集的五个不同产品类别的产品推荐器,并讨论了作为数据科学项目构建推荐器的主要步骤。
后续步骤
我们希望在云中实现这个推荐器。下一步将是为亚马逊评论数据集中剩余的产品类别定制推荐器。这个推荐器利用用户给出的等级。进一步利用用户给出的评论文本会很有意思。
确认
我真诚地感谢我的数据科学导师 Ramakrishna Soma 博士对我完成这个项目的指导和鼓励。
参考
-
R. He,J. McAuley,沉浮:用一类协同过滤对流行趋势的视觉演变建模, WWW ,2016 pdf
-
J. McAuley,C. Targett,J. Shi,A. van den Hengel,基于图像的风格和替代品推荐,2015 年, pdf
3)好的、坏的和丑陋的:数据科学工作的 Apache Spark
https://thenewstack . io/the-good-bad-and-ugly-Apache-spark-for-data-science-work/
4)使用 NumPy 和 Pandas 清理 Pythonic 数据
https://realpython.com/python-data-cleaning-numpy-pandas/
5)使用 Python 和 Pandas 清理数据:检测缺失值
6)阿帕奇火花https://spark.apache.org/
7)阿帕奇 PySpark
https://spark . Apache . org/docs/0 . 9 . 1/python-programming-guide . html
8)博瓦迪利亚、奥尔特加、埃尔南多阿。Gutiérrez,基于知识的系统中的推荐系统调查,第 46 卷,2013 年 7 月,第 109–132 页
9)什么是协同过滤?
https://dzone . com/articles/building-sales-recommendation-engine-with-Apache-s
10)夏如华, 推荐者系统 ,一本综合的书
- Greg Linden,Brent Smith,Jeremy York,Amazon.com 建议:项目对项目协作过滤,IEEE 互联网计算,第 7 版第 1 号,第 76-80 页,2003 年 1 月
12)阿帕奇火花 ALS
https://spark . Apache . org/docs/2 . 2 . 0/ml-collaborative-filtering . html
13)使用 Pyspark 对 Movielens 数据集进行简单矩阵分解的示例
用 PySpark 在自动气象站电子病历上处理生产数据
使用 AWS CLI 在集群上提交 PySpark 应用程序,分步指南
带有 PySpark 和 AWS EMR 的数据管道是一个多部分系列。这是第二部分。如果你需要 AWS EMR 的入门知识,请查看第 1 部分。
- 在 AWS EMR 上开始使用 PySpark
- 用 PySpark 在 AWS EMR 上处理生产数据**(本文)**
动机
Apache Spark 在大规模数据处理和分析领域风靡一时,这是有充分理由的。借助 Spark,组织能够从不断增长的数据堆中提取大量价值。正因为如此,能够构建 Spark 应用的数据科学家和工程师受到企业的高度重视。本文将向您展示如何从命令行在 Amazon EMR 集群上运行 Spark 应用程序。
大多数 PySpark 教程都使用 Jupyter 笔记本来演示 Spark 的数据处理和机器学习功能。原因很简单。在集群上工作时,笔记本通过在 UI 中提供快速反馈和显示错误消息,使得测试语法和调试 Spark 应用程序变得更加容易。否则,您将不得不挖掘日志文件来找出哪里出错了——这对于学习来说并不理想。
一旦您确信您的代码可以工作,您可能想要将您的 Spark 应用程序集成到您的系统中。在这里,笔记本就没那么有用了。要按计划运行 PySpark,我们需要将代码从笔记本转移到 Python 脚本中,并将该脚本提交给集群。在本教程中,我将向您展示如何操作。
我们开始吧
一开始,从命令行向集群提交 Spark 应用程序可能会令人生畏。我的目标是揭开这个过程的神秘面纱。本指南将向您展示如何使用 AWS 命令行界面来:
- 创建一个能够处理比本地计算机容量大得多的数据集的集群。
- 向集群提交 Spark 应用程序,集群读取数据、处理数据并将结果存储在可访问的位置。
- 该步骤完成后自动终止集群,因此您只需在使用集群时付费。
Spark 开发工作流程
当开发 Spark 应用程序来处理数据或运行机器学习模型时,我的首选是从使用 Jupyter 笔记本开始,原因如上所述。这里有一个关于创建一个亚马逊 EMR 集群并用 Jupyter 笔记本连接到它的指南。
一旦我知道我的代码起作用了,我可能想把这个过程作为一个预定的工作来进行。我会把代码放在一个脚本中,这样我就可以用 Cron 或 Apache Airflow 把它放在一个时间表中。
生产火花应用
重要更新:本指南使用 AWS CLI 版本 1 —以下命令需要进行一些调整才能与版本 2 配合使用。
创建您的 AWS 帐户如果您还没有。安装和配置AWS 命令行界面。要配置 AWS CLI,您需要添加您的凭据。您可以按照这些说明创建凭证。您还需要指定您的默认区域。对于本教程,我们使用us-west-2
。您可以使用任何您想要的区域。只是要确保所有的资源都使用相同的区域。
定义 Spark 应用程序
对于这个例子,我们将从 S3 加载亚马逊书评数据,执行基本处理,并计算一些聚合。然后,我们将把聚合数据帧写回 S3。
这个例子很简单,但这是 Spark 的一个常见工作流。
- 从源(本例中为 S3)读取数据。
- 使用 Spark ML 处理数据或执行模型工作流。
- 将结果写在我们的系统可以访问的地方(在这个例子中是另一个 S3 桶)。
如果您还没有,现在就创建一个 S3 存储桶。 确保你创建桶的区域与你在本教程剩余部分使用的区域相同。我将使用地区“美国西部(俄勒冈州)”。 复制下面的文件。确保编辑main()
中的output_path
来使用你的 S3 桶。然后把pyspark_job.py
上传到你的桶里。
# pyspark_job.pyfrom pyspark.sql import SparkSession
from pyspark.sql import functions as Fdef create_spark_session():
"""Create spark session.Returns:
spark (SparkSession) - spark session connected to AWS EMR
cluster
"""
spark = SparkSession \
.builder \
.config("spark.jars.packages",
"org.apache.hadoop:hadoop-aws:2.7.0") \
.getOrCreate()
return sparkdef process_book_data(spark, input_path, output_path):
"""Process the book review data and write to S3.Arguments:
spark (SparkSession) - spark session connected to AWS EMR
cluster
input_path (str) - AWS S3 bucket path for source data
output_path (str) - AWS S3 bucket for writing processed data
"""
df = spark.read.parquet(input_path)
# Apply some basic filters and aggregate by product_title.
book_agg = df.filter(df.verified_purchase == 'Y') \
.groupBy('product_title') \
.agg({'star_rating': 'avg', 'review_id': 'count'}) \
.filter(F.col('count(review_id)') >= 500) \
.sort(F.desc('avg(star_rating)')) \
.select(F.col('product_title').alias('book_title'),
F.col('count(review_id)').alias('review_count'),
F.col('avg(star_rating)').alias('review_avg_stars'))
# Save the data to your S3 bucket as a .parquet file.
book_agg.write.mode('overwrite')\
.save(output_path)def main():
spark = create_spark_session()
input_path = ('s3://amazon-reviews-pds/parquet/' +
'product_category=Books/*.parquet')
output_path = 's3://spark-tutorial-bwl/book-aggregates'
process_book_data(spark, input_path, output_path)if __name__ == '__main__':
main()
使用 AWS 命令行界面
是时候创建集群并提交应用程序了。一旦我们的应用程序完成,我们将告诉集群终止。自动终止允许我们仅在需要时才支付资源费用。
根据我们的使用案例,我们可能不想在完成时终止集群。例如,如果您有一个依赖 Spark 来完成数据处理任务的 web 应用程序,那么您可能希望有一个一直运行的专用集群。
运行下面的命令。确保用你自己的文件替换 粗体斜体 部分。关于--ec2-attributes
和--bootstrap-actions
以及所有其他参数的细节包括在下面。
aws emr create-cluster --name "Spark cluster with step" \
--release-label emr-5.24.1 \
--applications Name=Spark \
--log-uri ***s3://your-bucket/logs/*** \
--ec2-attributes KeyName=***your-key-pair*** \
--instance-type m5.xlarge \
--instance-count 3 \
--bootstrap-actions Path=***s3://your-bucket/emr_bootstrap.sh*** \
--steps Type=Spark,Name="Spark job",ActionOnFailure=CONTINUE,Args=[--deploy-mode,cluster,--master,yarn,***s3://your-bucket/pyspark_job.py***] \
--use-default-roles \
--auto-terminate
**aws emr create-cluster**
重要论据:****
--steps
告诉您的集群在集群启动后要做什么。确保将--steps
参数中的s3://your-bucket/pyspark_job.py
替换为 Spark 应用程序的 S3 路径。您还可以将应用程序代码放在 S3 上,并传递一个 S3 路径。--bootstrap-actions
允许您指定要安装在所有集群节点上的软件包。只有当您的应用程序使用非内置 Python 包而不是pyspark
时,这一步才是必需的。要使用这样的包,使用下面的例子作为模板创建您的emr_bootstrap.sh
文件,并将其添加到您的 S3 桶中。在aws emr create-cluster
命令中包含--bootstrap-actions Path=s3://your-bucket/emr_bootstrap.sh
。
#!/bin/bash
sudo pip install -U \
matplotlib \
pandas \
spark-nlp
--ec2-attributes
允许您指定许多不同的 EC2 属性。使用以下语法设置您的密钥对--ec2-attributes KeyPair=your-key-pair
。 注意:这只是你的密钥对的名字,不是文件路径。 你可以在这里了解更多关于创建密钥对文件的信息。--log-uri
需要一个 S3 桶来存储你的日志文件。
其他 **aws emr create-cluster**
论据解释:
--name
给你正在创建的集群一个标识符。--release-label
指定使用哪个版本的 EMR。我推荐使用最新版本。--applications
告诉 EMR 您将在集群上使用哪种类型的应用程序。要创建火花簇,使用Name=Spark
。--instance-type
指定要为集群使用哪种类型的 EC2 实例。--instance-count
指定集群中需要多少个实例。--use-default-roles
告诉集群使用 EMR 的默认 IAM 角色。如果这是你第一次使用 EMR,你需要运行aws emr create-default-roles
才能使用这个命令。如果您已经在配置了 AWS CLI 的区域中的 EMR 上创建了一个集群,那么您应该已经准备好了。--auto-terminate
告诉集群在--steps
中指定的步骤完成后立即终止。如果您想让您的集群保持运行,请排除此命令—请注意,只要您保持集群运行,您就是在为它付费。
检查 Spark 应用程序的进度
在您执行了aws emr create-cluster
命令之后,您应该会得到一个响应:
{
"ClusterId": "j-xxxxxxxxxx"
}
登录到 AWS 控制台并导航到 EMR 仪表板。您的集群状态应该是“正在启动”。您的集群启动、引导和运行您的应用程序大约需要 10 分钟(如果您使用了我的示例代码)。一旦该步骤完成,您应该会在 S3 存储桶中看到输出数据。
就是这样!
最后的想法
您现在知道了如何创建 Amazon EMR 集群并向其提交 Spark 应用程序。该工作流是使用 Spark 构建生产数据处理应用程序的重要组成部分。我希望您现在对使用所有这些工具更有信心了。
一旦你的工作顺利进行,考虑在亚马逊上建立一个气流环境来安排和监控你的管道。
取得联系
感谢您的阅读!请让我知道你是否喜欢这篇文章,或者你是否有任何批评。如果你觉得这个指南有用,一定要关注我,这样你就不会错过我以后的文章。
如果你在一个数据项目上需要帮助或者想打个招呼,在LinkedIn上联系我。干杯!
使用 Flask 和 Google App Engine 生产您的机器学习模型
这个小教程将帮助你理解一个经过训练的机器学习模型是如何用于生产的。
如今,你可以找到很多学习数据科学和机器学习的教程、MOOCs 和视频。但它们都没有解释当你在 jupyter notebook 或其他 IDE 中的本地系统上训练和优化一个机器学习模型后,你的机器学习模型会发生什么。
在生产环境中,没有人坐在系统前面提供输入并检查您创建的模型的输出。
因此,在本教程中,我们将在 flask 中创建一个简单的 RESTful web 服务,以 API 响应的形式提供我们的机器学习模型输出,然后在谷歌云平台的应用引擎中部署该应用。
设置和要求:
- Python 3.6
- 文字编辑器
- 谷歌云平台账户
- Jupyter 笔记本
另外,请安装下面指定的库:
在 Flask 中创建 RESTful API 所需的库
训练一个样本机器学习模型:
我将在波士顿住房数据集上训练一个样本线性回归模型,这两个数据集都可以在 Scikit-learn 库中找到,最后我将使用 Joblib 将训练好的模型保存在一个文件中。
下面是示例 python 代码:
## Importing required Librariesimport pandas as pd
import numpy as np
## for sample dataset
from sklearn.datasets import load_boston
## for splitting data into train and test
from sklearn.model_selection import train_test_split
## LinearRegression model
from sklearn.linear_model import LinearRegression
## For saving trained model as a file
from sklearn.externals import joblib## Getting sample dataset
boston_dataset = load_boston()
boston = pd.DataFrame(boston_dataset.data, columns=boston_dataset.feature_names)
boston['MEDV'] = boston_dataset.target## Preparing variable
X = pd.DataFrame(np.c_[boston['LSTAT'], boston['RM']], columns = ['LSTAT','RM'])
Y = boston['MEDV']## splitting train and test data
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size = 0.2, random_state=5)
#print(X_train.shape)
#print(X_test.shape)
#print(Y_train.shape)
#print(Y_test.shape)## Training Model
lin_model = LinearRegression()
lin_model.fit(X_train, Y_train)## Saving trained model
joblib.dump(lin_model, 'test_model')
创建一个简单的烧瓶应用程序:
现在,我们将创建一个简单的 RESTful 应用程序,将我们的模型输出作为 API 响应。我们的应用程序将完成以下任务:
Task flow of our API
下面是 flask 应用程序的代码。你也可以从 GitHub 获得,使用这个链接。
from flask import Flask,request
from flask_restful import Resource,Api
from sklearn.externals import joblib
import pandas as pdapp=Flask(__name__)
api=Api(app)class Test_index(Resource):
def post(self):
loaded_model = joblib.load('./model/test_model')
test_data=request.get_json()
input_df=pd.DataFrame([test_data])
input_df.rename(columns={"input_lstat":'LSTAT',"input_rm":'RM'},inplace=True)
print(input_df)
y_train_predict = loaded_model.predict(input_df)
test_output=pd.DataFrame(y_train_predict,columns={'output'})
output=test_output.to_dict(orient="list")
return outputapi.add_resource(Test_index,"/test")
if __name__=='__main__':
app.run(debug=True)
测试我们的应用程序(本地或开发环境):
在编写完我们的应用程序后,我们将在本地或开发环境中进行测试,然后在 Google 云平台中进行生产。
我使用 Gunicorn,一个运行我们应用程序的 WSGI 应用服务器。
只需在终端中打开项目目录并运行以下命令:
$ gunicorn test_flask:app
我们将获得运行我们的应用程序的本地端口的详细信息:
App running in gunicorn server
让我们用 postman 应用程序测试 API 响应。我们将向应用程序端点发出一个 POST 请求,传递一个包含输入参数的 JSON 请求体,如果没有错误,我们将得到一个包含模型预测的 JSON 对象响应。
Testing app response in Postman
在 Google 云平台中将应用部署到生产:
我们测试的应用程序部署在本地系统中。为了使我们的系统可以全球访问,我们需要将它部署在一些服务器上,这些服务器有一个全球 URL 来评估我们的应用程序。
为此,我们将使用谷歌云平台的应用程序引擎。
注意:在接下来的步骤之前,请检查您是否拥有 Google Cloud Platform 帐户,以及您的系统中是否安装了 google cloud SDK。你可以在这里 找到设置 google SDK 的细节。
Google App Engine 需要一个名为“app.yaml”的部署描述符文件,用于在 google cloud 中部署我们的应用程序。
下面是部署描述符 app.yaml 文件的内容:
app.yaml 文件的内容
现在创建一个目录结构,如下所示:
Directory Structure for deploying app in production
最后,在终端中打开同一个目录文件夹,使用 below 命令在 Google cloud 中部署 app。
$ gcloud app deploy --version 1
Google Cloud SDK 将检查适当的权限,然后读取部署描述符文件“app.yaml”并要求确认。键入“Y”。
App deployment in Google app engine
得到确认后,Google cloud SDK 将复制应用程序引擎实例中所有需要的文件,如果没有错误,我们将获得应用程序的全局端点。
Successful deployment in google app engine
在我们的例子中,全局端点 URL 是:https://hadooptest223316.appspot.com
测试我们的应用程序(生产环境):
让我们再次用 postman 应用程序测试我们的 API 响应。我们将向我们的全局端点 URL 发出 POST 请求:
Testing the production app in GCP app engine
我们再次将所需模型的预测作为 API 响应。
结论:
在本文中,我们看到了机器学习模型是如何在生产中使用的。虽然这是一个非常基本的用例。但这将让你对机器学习模型如何在不同应用程序内的云服务器中投入生产有所了解。
我希望你喜欢我的文章,如果你想了解更多关于这个话题的信息,你可以在insta gram或LinkedIn上关注并留言给我。
用 Django API 生产机器学习模型
关于在 Django API 背后部署机器学习模型的教程。
之前,我写过一篇关于用烧瓶制作 ML 模型的教程。
Flask 的重量更轻,但是如果我们需要更多的功能,django 会附带它。
我构建了一个 API,通过 sklearn 模型进行预测。下面是我一步一步的代码。
本教程包含 3 个部分:
1。训练 ML 模型
2。构建 django 应用程序
3。测试 API
第 1 部分:训练 ML 模型
第一部分可以在笔记本上完成。
这不是关于机器学习的教程。所以我们将根据虚构的数据训练一个模型。也就是说,它将像你可以训练的任何其他 sklearn 模型一样工作。
我们的模型将根据动物发出的噪音来检测动物是否是狗。
创造虚构的数据!在每个内部列表中,第一个索引是动物的声音,第二个索引是指示动物是否是狗的布尔标签。
data = [
['woof', 1],
['bark', 1],
['ruff', 1],
['bowwow', 1],
['roar', 0],
['bah', 0],
['meow', 0],
['ribbit', 0],
['moo', 0],
['yip', 0],
['pika', 0]
]
将上述内容转换为功能和标签列表。
X = []
y = []for i in data:
X.append( i[0] )
y.append( i[1] )
安装矢量器并转换要素。
from sklearn.feature_extraction.text import CountVectorizervectorizer = CountVectorizer()
X_vectorized = vectorizer.fit_transform(X)
训练线性回归。
from sklearn.linear_model import LinearRegression
import numpy as npregressor = LinearRegression()
regressor.fit(X_vectorized, y)
现在用几个例子来测试一下。
test_feature = vectorizer.transform(['woof'])
prediction = regressor.predict(test_feature)
print(prediction)test_feature = vectorizer.transform(['ribbit'])
prediction = regressor.predict(test_feature)
print(prediction)test_feature = vectorizer.transform(['meoww'])
prediction = regressor.predict(test_feature)
print(prediction)#=> [1.]
#=> [0.]
#=> [0.36363636]
完美。
将我们的模型打包成字节流,这样我们就可以将它们存储在应用程序中。
import picklepickl = {
'vectorizer': vectorizer,
'regressor': regressor
}
pickle.dump( pickl, open( 'models' + ".p", "wb" ) )
第 2 部分:构建 django 应用程序
打开命令行,进入存储 django 项目的目录。为这个应用程序创建一个目录并cd
到其中。
mkdir DjangoMLAPI && cd DjangoMLAPI
创建虚拟环境并安装所需的软件包。
python3 -m venv env
source env/bin/activate
pip install django djangorestframework sklearn numpy
现在创建一个 django 项目,这个目录包含了我们正在处理的所有代码。如果我们需要的话,这还包括数据库配置和应用程序设置。
虽然“项目”实际上代表了我们正在构建的应用程序,但是 django 使用术语“app”来指代项目中的包。我们的主包将被称为api
。
django-admin startproject api
这生成了一堆支持我们的项目所需的样板代码。它看起来像左边的文件树。
外层的/api
只是一个包含我们所有项目代码的文件夹。
内部的/api
是我们项目的主要 python 包。
接下来,我们将在项目中生成一个“应用程序”。这将为我们 API 背后的机器学习提供动力。
我们把这个叫做预测器。
cd api
python manage.py startapp predictor
完整的目录现在看起来像左边的文件树。
我们在这里添加了一个名为/predictor
的文件夹,里面有很多文件。
我们的用例甚至不需要这些文件中的几个。在本教程的最后,我们将继续删除它们。
apps.py
是我们定义配置类的地方。这是只运行一次(而不是每次请求都运行)的代码,所以我们最终会将代码放在那里来加载我们的模型。
将包含在每个请求上运行的代码。所以我们把矢量化和回归逻辑放在那里。
现在让我们将这个应用程序添加到INSTALLED_APPS
中。打开/api/api/settings.py
,给INSTALLED_APPS
添加'predictor'
。它应该如下图所示。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'predictor'
]
现在在/predictor
中创建一个名为/models
的文件夹。把你训练好的腌渍模型移到这个目录。
请注意,在真实的生产环境中,我会将它从应用程序中分离出来(可能使用 S3),这样我们就不需要在每次更新模型时都重新部署应用程序。但在这里,我们只是将模型包含在应用程序中。
将这一行也添加到设置中。我们将使用它来加载我们的模型。
MODELS = os.path.join(BASE_DIR, 'predictor/models')
现在编写在应用程序启动时加载我们的模型的代码。在/api/predictor/apps.py
里面使用这个代码。
from django.apps import AppConfig
from django.conf import settings
import os
import pickleclass PredictorConfig(AppConfig): # create path to models
path = os.path.join(settings.MODELS, 'models.p')
# load models into separate variables
# these will be accessible via this class
with open(path, 'rb') as pickled:
data = pickle.load(pickled) regressor = data['regressor']
vectorizer = data['vectorizer']
现在创建一个支持我们回归逻辑的视图。打开/api/predictor/views.py
并用此代码更新。
from django.shortcuts import renderfrom .apps import PredictorConfigfrom django.http import JsonResponse
from rest_framework.views import APIViewclass call_model(APIView):
def get(self,request):
if request.method == 'GET': # get sound from request
sound = request.GET.get('sound')
# vectorize sound
vector = PredictorConfig.vectorizer.transform([sound]) # predict based on vector
prediction = PredictorConfig.regressor.predict(vector)[0] # build response
response = {'dog': prediction} # return response
return JsonResponse(response)
设置路由,将 URL 映射到/api/api/urls.py
中的视图。
from django.urls import path
from predictor import viewsurlpatterns = [
path('classify/', views.call_model.as_view())
]
您可以删除以下文件,因为我们不需要它们。
api/predictor/tests.py
api/predictor/models.py
api/predictor/admin.py
第 3 部分:测试 API
现在启动服务器。
python manage.py runserver
并发出几个 curl 请求来测试它。你也可以直接在浏览器中输入网址。
curl -X GET [http://127.0.0.1:8000/classify/?sound=meow](http://127.0.0.1:8000/classify/?sound=meow)
#=> {"dog": 0.0}curl -X GET [http://127.0.0.1:8000/classify/?sound=woof](http://127.0.0.1:8000/classify/?sound=woof)
#=> {"dog": 1.0}
放松点皮兹。起作用了!接近1
的数字表示是狗,接近0
的数字表示不是狗。
我们找到了。一个 django API,加载并运行一个经过训练的机器学习模型!
用 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。避免与“有毒”的人打交道
尽管软件开发人员可能是你的理想职业,但你的工作场所可能会有一些有毒的人。
你以为你在高中就已经把这类人抛在脑后了,但事实证明生活中充满了这类人。
这些人只是在寻找机会让别人失望,通常对生活中的一切都持消极态度。
你可以试着用你善意的影响去改变他们,但往往一个人很难改变(原因很多)。
如果你发现你没有任何积极的影响,和他们交往只会给你带来麻烦,那么你应该避免和他们打交道。
然而,你可能会遇到这样的情况,这个人是你的经理或者你办公室的同事。
在这些情况下,你的选择非常有限。你可以考虑换个部门,或者开始找新工作。
如果你别无选择,只能面对他们,提前做好心理准备,告诉自己不要让他们影响你的情绪,毁了你的一整天。
他们的态度不会让你感到惊讶。
尽量少和他们互动。
结论
软件开发人员的生活不仅仅是写代码。与人打交道是关键因素之一。
虽然他们没有被提及或强调太多,社交技能是那些看似不重要的技能中的一部分,但实际上可以加速你职业生涯的发展。
它们比在白板上倒置二叉树更容易理解和实践。
虽然我同意其中一些事情说起来容易做起来难,但在我们的生活中实践它们是很重要的。
在你离开之前,让我善意地提醒你一件事:不要期望总是能够一直应用这些东西。
我们是人,我们也有自己的困难时期。
然而,我们绝不能让这几个挫折和愤怒的时刻毁掉我们整个职业和非职业的生活。