如何在一小时内开发、部署和扩展一个数据科学算法!
这样你就有了一张数据表。您希望使用所有其他列中的值来预测一列中的值。您需要训练、测试、部署和生产(即,使其在 API 背后可用)一种算法,该算法将根据输入特征准确预测输出特征。我们开始吧。
在我们开始之前,你需要一个 AWS 账户。不要问问题,直接报名,现收现付,很便宜。其次,您还需要访问 Linux 终端。我用的是云 9 ,它太棒了,而且有一个免费选项。最后,用你的 AWS 证书在你的 Linux 终端上安装并设置无服务器框架,这是开源的好日子。
第一步:去 S3 上传你的 CSV 格式的数据。
第二步:在 AWS 上进入亚马逊机器学习,点击‘新建…’然后是“数据源和 ML 模型”。遵循要求的步骤(步骤不多,应该很简单,只需使用默认选项)。一旦完成,亚马逊机器学习将构建、训练和测试算法。
第三步:每个人都知道好的模型需要好的数据,“垃圾进垃圾出”等等。现在请评估您的模型,如这里的所解释的。
步骤 4:现在让我们部署模型。按照这些步骤为您的模型创建一个实时端点(阅读文档并自己按照这些步骤来做确实是一个很好的实践)。
第五步:接下来是有趣的部分。我们将使用 AWS Lambda 和 Amazon API Gateway 来处理对算法的请求。最棒的是,你甚至不会知道你正在使用它们!我将更详细地介绍这些步骤:
步骤 5a:在您的 Linux 终端中,键入:
serverless create --template aws-nodejs --path my-first-algorithm
步骤 5b:在新创建的文件夹中,将其粘贴到“serverless.yml”文件中:
service: my-first-algorithmprovider:
name: aws
runtime: nodejs4.3iamRoleStatements:
- Effect: Allow
Action:
- machinelearning:Predict
Resource: "*"functions:
model:
handler: handler.model
events:
- http:
path: model
method: post
cors: true
步骤 5b:将下面的代码粘贴到您的“handler.js”文件中,插入您在上面的步骤 4 中找到的正确的“MLModelId”和“PredictEndpoint”(我的示例只有一个输入特征,如果您的特定模型需要,请重命名并添加额外的特征):
// load aws sdk
const AWS = require('aws-sdk');// load AWS MachineLearning
var machinelearning = new AWS.MachineLearning({apiVersion: '2014-12-12'});module.exports.model = (event, context, callback) => { var data = JSON.parse(event.body);
var inputFeature1Data = data.inputFeature1; var params = {
MLModelId: **INSERT-THIS-YOURSELF**,
PredictEndpoint: **INSERT-THIS-YOURSELF**,
Record: {
inputFeature1: inputFeature1Data
}
}; machinelearning.predict(params, function(err, data) {
if (err) {
const response = {
headers: {'Access-Control-Allow-Origin': '*'},
statusCode: err.statusCode,
body: JSON.stringify(err)
};
callback(null, response);
} else {
const response = {
headers: {'Access-Control-Allow-Origin': '*'},
statusCode: 200,
body: JSON.stringify(data)
};
callback(null, response);
}
});};
步骤 5c:确保您仍然在 Linux 终端中新创建的文件夹中,并键入serverless deploy
,等待服务部署,一旦您看到“端点”url,您就可以开始了。
您可以通过将下面的代码粘贴到您的 Linux 终端来测试该模型的工作情况(将“endpoint-url”替换为步骤 5c 中的 url 端点) :
curl -X POST endpoint-url --data '{"inputFeature1":7}'
现在,您已经有了一个在生产中完全部署和可用的算法。它可以扩展到处理所有请求,并且完全由 AWS 管理,您不需要做任何维护,太棒了!这个工作流程很神奇,尤其是如果你想建立一个概念验证算法的话。添加 API 密钥或 JSON web 令牌验证将提供必要的安全性,只允许特定用户访问您的算法。
我希望您对此感兴趣,我确实认为这是数据科学工作流程中的一个范式转变。
附:这是我第一次在网上发帖,请评论但不要太苛刻:)
如何在数据科学面试中脱颖而出
注意:这篇文章的底部是一些行业数据科学家关于如何最好地获得你的第一个数据科学职位的回答。
获得一个新的数据科学职位需要采取一系列步骤来展示你处理数据的能力,以及你是否适合一家公司的文化。在这篇文章中,我将解决这个过程中最重要的部分之一(也是最可怕的部分):技术面试。
与我交谈过的大多数人都花了大量时间练习编码挑战,并温习像 SQL 查询这样的技能。虽然这些都是准备面试的重要方面,但它们只强调你能多好地使用键盘。
在我自己找工作的过程中,我花了很多时间思考如何利用技术面试作为一种手段,将自己从其他申请中区分出来。我们大多数人都能编写一个函数来表示一个数是否是质数(提示:模是你的朋友)。经常被忽视的是,你的每一个答案都是一个机会,为你解决问题的特定形式提供背景。最佳情境取决于你被问到的特定问题、面试官和你申请的职位。然而,从我的经验来看,在面试过程中,有三种主要类型的语境是有效的。
苦心经营
虽然编码问题通常可以简化为使用您偏好的编程语言的本地组件,但是它们表明您除了知道何时调用特定方法之外的解决问题的能力。无论是口头回答还是在白板上回答,您的解决方案的每一步都应该清楚地说明您为什么选择特定的方法。这允许您讨论可能不如您选择的解决方案执行得好的替代解决方案,并表明您能够思考您的代码如何影响产品的可伸缩性。
这里有一个很好的提示,就是知道如何测试不同方法的计算时间。因此,即使您不熟悉哪种规模更好,您也可以指出该问题有 n 种解决方案,并且测试每种解决方案的计算时间会很有趣。因此,即使你可能没有一个直接的答案,你仍然表明你在考虑性能,如果你坐在电脑前,你会知道如何测试不同的方法。
精化的另一个重要方面是讨论一个特定的编码解决方案如何被用于公司中某人可能正在处理的商业问题的上下文中。虽然这并不适用于每一个技术问题,但你应该抓住任何机会在这种背景下讨论解决方案。一个例子是处理预测客户流失的数据科学角色,你可以根据该问题解释基本的技术问题(例如,深度神经网络如何工作)。你可以谈论什么类型的数据将被输入到神经网络,以及该网络将如何使用与客户流失相关的特征来优化预测。
讲故事
讲故事类似于阐述,但涉及使用具体的过去事件来突出你的技能。在解决问题时,你可以指出你过去是如何实现类似的解决方案的。重要的是要强调项目的相关细节,你解决问题的步骤,以及你努力的具体结果。
我在项目 Y 的特征工程阶段采取了行动 X,这导致了模型准确性和 AUC 分数的 Z 增加。
讲故事的一个重要用途是展示传达公司价值观的行为。许多科技公司在他们的网页上列出了他们的核心价值观,有时被称为使命。其他公司,如银行和咨询机构,将寻找领导和解决问题的技能。例如,亚马逊将评估领导原则的问题与面向数据的问题交织在一起。行为故事的一个好框架是星型模式。
星形格式要求您用四个步骤陈述您的答案:
1.情况
2.工作
3.行动
4.反应
你可以在这里找到更多关于如何实现星星方法的信息。
后续
这个策略要求你就公司目前面临的某些问题引出细节。这里的想法是双重的:证明你是以解决方案为导向的心态来处理这个角色的,并且你能够实际上实施他们问题的解决方案。
具体做法是,当有机会向面试官提问时,你可以问一些你在这个角色中会遇到的问题的具体例子,或者团队目前正在解决的重要问题。面试结束后,你回到家,花一些时间概述问题和实施解决方案的计划。这不需要非常专业,只是一个概述,表明你理解手头的问题,并能提出自己要实施的解决方案。然后,你与面试官跟进,重申你在面试中的积极经历,并将文件发给他们,内容大致如下:“顺便说一下,我考虑了你目前面临的问题,提出了三个解决方案,我可以在进入贵公司的第一个月内实施。”这有效地给了面试官一个让你在公司起步的路线图,如果被录用,这可能会成为你的第一个项目。
一个很好的例子是拉米特·塞西的公文包技术。
关于差异化的进一步思考
在准备面试时,我询问了在数据科学岗位工作的朋友,听听他们进入该行业的经历。请注意,我和我的这些朋友从博士项目转到了数据科学职位,所以如果你来自不同的环境,他们的回答应该从这个角度来理解。
答案是半匿名的,来自脸书、优步和亚马逊等公司的数据科学家。希望它们对你思考如何在面试过程中进行阐述、讲故事和跟进有所帮助。
问题 1:除了良好的投资组合和技术技能,你认为对贵公司的数据/研究/nlp 科学申请人来说,什么是重要的?
回应 1: 产品感,强调产品指标。这是专门针对数据科学产品分析角色(FB 的绝大多数角色)的。此外,还有一个核心数据科学小组,他们基本上是软件工程师,也知道统计数据并构建其他 DS 使用的工具。还有基础设施数据科学,它们对我来说有些神秘,但在我们的数据仓库中工作。我们也有研究科学家的角色,但这些都是非常具体的领域。
回应 2: 商业意识/洞察力——能够直观地了解公司试图用数据科学解决什么问题,他们如何衡量成功,以及数据科学如何才能最好地满足这些需求。了解如何采纳 DS 见解并将其转化为可行的建议。此外,创造力、效率、独立性、协作/利他主义。
回应 3: 你能展示出你工作的影响力,并强调这是你工作中的一个考虑因素,这一点绝对重要。其中一部分是能够优先处理最重要的工作。在脸书,你总有一百万件事情可以做,知道哪些是最重要的、影响最大的是关键。
回答 4: 理解数据集的能力,快速学习这里的东西的能力,独立理解和学习概念的能力(有一大堆维基,你必须能够自己阅读和理解一些东西)。
问题 2:在找工作时,你关注的哪个领域(如投资组合、领域知识、特定技能、博客/外联)对被录用没有太大影响?
回应 1: 真的没有。我参加了 Insight Data Science fellowship,他们为通过 DS 面试做了很好的准备。(注意:如果你能进入数据科学训练营,你就处于有利地位。尤其是如果是洞察奖学金。)
**回应二:**我没有博客,但有个人网站和 github。我不确定我被聘用时是否考虑过他们,但我们现在肯定会考虑他们作为候选人。出版记录当然并不重要(除非它证明了完成项目的能力),但这是我所期望的。
回应 3: 这个问题真的很好。事实上,我发现在我进入科技行业的过程中,最有影响力的是人际关系。事实上,我仅仅写简历并没有得到任何工作。我所有的工作都是通过了解我或我的工作的人得到的。
回应 4: 我的投资组合真的不重要我觉得哈哈。
问题 3:如果你重新找工作,你会花更多时间在哪个方面?
回应 1: 我在这里没有很好的答案,但那可能是因为我目前的角色相当独特。如果我今天更广泛地看 DS 的空缺,我可能会想加强机器学习方法。
回应 2: 与上述类似。网络网络网络网络。我在演讲方面取得了巨大的成功。一次谈话后,基本上有 3-4 家公司有兴趣雇佣我。不过,这对于学术界来说可能有点困难。我肯定会建议寻找机会在会议上做志愿者。我也会优先去参加更多的聚会。根据你在哪里找工作,在 linkedin 上找到在那里工作的人并邀请他们见面(即使是通过视频会议)也是有意义的。对于那些会议,确保你已经研究过这个人,并且有具体的问题要问。另一个选择也可能是寻找指导项目(我是 chic geek 的导师)。基本上任何建立真实生活关系的事情,而不仅仅是发送简历。
回应 3: 本体建模(我还不完全知道它是什么)和机器学习的知识。
问题 4:你认为在学术界和工业界工作有什么大的不同?
回应 1: 快速运输远比完美运输重要。
项目所有权——我习惯于 100%拥有我所做的任何东西,但是在工业中有更多的相互依赖。
沟通是完全不同的,因为我几乎从来没有和其他数据科学家一起工作过。我正在与工程师/项目经理/等沟通,必须做出相应的调整(例如,没有人关心或理解方法,他们只想知道结果)。
回应 2: 速度——在行业中,快速获得可行的答案是最基本的要求,而完美是好的敌人。就速度/准确性的权衡而言,本能(和你的经理)会告诉你什么时候你已经得到了足够正确的答案,可以继续前进了。
跨功能性——许多数据科学家在 xf 团队中工作,需要能够与来自完全不同背景的人(如商业、设计、工程等)很好地沟通。
**回应三:**最大的肯定是时间线和现实生活约束的考虑。在学术界,目标通常是用最完美的方法找到最好、最合理的答案。你所花的时间往往是因为这个或那个考虑的结果。在行业中,时间表决定一切。你必须善于权衡,在你拥有的时间和资源内,你能做的最好的事情是什么,同时还要平衡其他三个项目。评论也不是很严谨,很少有像文学评论这样的努力(如果你有时间做得很好,但绝对不是一个要求)。
事实上,我喜欢工业的快节奏,但这也是学术界人士有时会纠结的问题。
还有,写报道。你必须善于将你的发现形象化,并让它为广泛的人群所接受。这包括能够撰写易于理解的执行摘要。
此外,您肯定需要能够从您所做的工作中定义可行的建议。有一个有趣的发现很好,但更重要的是你必须展示“那又怎样?”。采取什么步骤来修复您发现的任何问题,或者以对业务有影响的方式采取行动。因此,你还需要真正意识到你正在推动的业务成果是什么。
问题 5:对于在科技公司获得第一份工作,有什么建议吗?
回应 1: 我在这里写了一篇关于我从学术界到 DS 的转变的博文。
回应 2: 利用推荐,这至少会让你得到招聘人员或经理的进一步关注。为你申请的每一个职位定制你的简历(不超过一页)。如果你不能亲自证明你知道一项技能,就不要说你知道它。在面试前调查公司,如果可能的话尝试他们的产品。搜索 HackerNews 对了解硅谷公司很有帮助,可能对你也有用。
应对 3: 更新你的 Linkedin。确保你有一个总结,并记下以上所有的经验(说明你的工作的影响,表明你可以在紧张的时间表下工作,表明你可以向广泛的受众传达你的工作,等等。).无论谁考虑雇佣你,他都可能会看你的 Linkedin。如果你也有一个令人惊叹的网站。
也不要漏掉人的成分。展示你感兴趣的东西,让你与众不同的东西,志愿服务或为社会公益工作。据我所知,科技公司(至少是脸书)经常寻找持有某种观点的人。还要确保你回顾了你申请的公司的价值观,并在简历中反映出你了解这些价值观。(脸书的价值观包括:开放、注重影响力等。)
额外问题:你对亚马逊的面试过程有什么见解吗,或者你希望你已经为面试做了更具体的准备?
回应 1: 面试方面,大部分问题会是行为问题。他们会评估你在某种情况下的表现,而不是你的实际技能(尽管你可能会有一两次技术性面试——你申请了什么职位?)他们更喜欢你所有的答案都是“星形”格式——情境、任务、行动和结果<——如果你准备了一些这种格式的故事,很好(你会做得很好)。许多问题是愚蠢的和开放式的,比如“告诉我你不得不与你的主管(或顾问或研究伙伴)意见相左的一次经历”。它是什么?你是如何解决的?最终结果是什么。”如果你能将问题与这些所谓的领导原则联系起来,他们也会非常喜欢(你不必明确地说,比如,我想展示所有权或者我想深入探讨这个问题,但你在回答中展示某些领导原则的能力将在汇报中讨论。
我希望这能帮助每个人踏上数据科学之旅。如果您有任何其他提示或建议,请给我留下评论。
如何使用 Apache Livy 在 AWS EMR 中运行 spark 批处理作业
在本文中,我们将讨论在 Apache Livy 的帮助下,使用 rest 接口在 AWS EMR 上运行 spark 作业。
我们将完成以下步骤:
- 创建一个简单的批处理作业,从 Cassandra 读取数据并将结果写入 S3 的 parquet
- 建造罐子并把它存放在 S3
- 提交作业并等待它通过 livy 完成
什么是阿帕奇李维?
Apache Livy 是一个通过 REST 接口与 Spark 集群轻松交互的服务。它支持 Spark 作业或 Spark 代码片段的轻松提交、同步或异步结果检索,以及 Spark 上下文管理,所有这些都通过一个简单的 REST 接口或 RPC 客户端库实现。Apache Livy 还简化了 Spark 和应用服务器之间的交互,从而支持将 Spark 用于交互式 web/移动应用程序。
你为什么会使用它?
Apache livy 让我们的生活更轻松。我们不需要使用 EMR 步骤或 ssh 进入集群并运行 spark submit。我们只是使用了一个很好的 REST 接口。
让我们看看它的实际效果。
步骤 1:构建简单的批处理作业
批处理作业将是一个 scala 应用程序。
在我们的应用程序中,我们首先构建 spark 会话,并确保我们可以连接到 Cassandra 集群:
val sparkConf = new SparkConf()sparkConf.set("spark.cassandra.connection.host", "YOUR_CASSANDRA_HOST")
sparkConf.set("spark.cassandra.connection.port", "YOUR_CASSANDRA_PORT")val spark: SparkSession = SparkSession.builder()
.appName("RunBatchJob")
.config(sparkConf)
.getOrCreate()
我们还需要确保能够访问我们要写入数据的 S3 存储桶:
spark.sparkContext.hadoopConfiguration.set("fs.s3a.acl.default", "BucketOwnerFullControl")
我们可以从数据库中读取输入数据:
val input = spark.read
.format("org.apache.spark.sql.cassandra")
.options(Map("table" -> "my_table", "keyspace" -> "my_schema"))
.load()
Cassandra 桌子的结构非常简单:
CREATE TABLE my_schema.my_table (
id1 text PRIMARY KEY,
id2 text,
id3 text,
id4 text
);
最后,我们只需将数据帧写入 S3 的一个拼花文件,同时删除重复的行:
input
.dropDuplicates
.write
.mode("append")
.parquet("s3a://your_bucket/your_preffix/")
步骤 2:将 jar 部署到 S3
好,现在我们有了一个应用程序。我们需要把它装进一个大罐子里,然后复制到 S3。这样,远程访问 jar(从 livy)就更容易了。
使用 gradle 管理我们的应用程序的依赖关系。在教程的最后,我会发布 github repo 的链接,在那里你可以找到 gradle 文件的完整代码和细节。我不会在这里讨论细节,因为这超出了本教程的范围。
要创建 fat jar,我们需要在应用程序的根目录下运行:
gradle shadowJar
生成后,我们可以使用 aws cli 将其复制到 S3:
aws s3 cp build/libs/spark_batch_job-1.0-SNAPSHOT-shadow.jar s3://your_bucket/your_prefix/
一旦这样做了,我们终于可以从使用 livy 开始我们的 spark 工作了。
步骤 3:通过 Livy 提交作业
我们将使用一个简单的 python 脚本来运行我们的命令。主要功能非常简单:
def run_spark_job(master_dns):
response = spark_submit(master_dns)
track_statement_progress(master_dns, response)
它将首先提交作业,然后等待它完成。 track_statement_progress 步骤对于检测我们的作业是否成功运行非常有用。 master_dns 是 EMR 集群的地址。让我们更深入地研究我们各自的方法。
spark_submit 功能:
host = '[http://'](/') + master_dns + ':8999'data = {'className': "com.app.RunBatchJob", "conf":{"spark.hadoop.fs.s3a.impl":"org.apache.hadoop.fs.s3a.S3AFileSystem"}, 'file': "s3a://your_bucket/spark_batch_job-1.0-SNAPSHOT-shadow.jar"}headers = {'Content-Type': 'application/json'}response = requests.post(host + '/batches', data=json.dumps(data), headers=headers)
这只是使用请求库完成的一个 post 。在 EMR 上,livy 服务器运行在端口 **8999 上。**我们请求中的数据本质上是我们将提供给 spark-submit 命令的参数。我们需要使用 /batches 端点。这将告诉 livy 我们将提交一个批处理作业。
以及跟踪声明进度函数。这将每 10 秒轮询一次 livy 服务器,并检查应用程序的状态是否为*成功。*如果是,则作业已成功完成:
statement_status = ''
host = '[http://'](/') + master_dns + ':8999'
session_url = host + response_headers['location'].split('/statements', 1)[0]while statement_status != 'success':
statement_url = host + response_headers['location']
statement_response = requests.get(statement_url, headers={'Content-Type': 'application/json'})
statement_status = statement_response.json()['state']
logging.info('Statement status: ' + statement_status) lines = requests.get(session_url + '/log', headers={'Content-Type': 'application/json'}).json()['log']
for line in lines:
logging.info(line) if statement_status == 'dead':
raise ValueError('Exception in the app caused it to be dead: ' + statement_status) if 'progress' in statement_response.json():
logging.info('Progress: ' + str(statement_response.json()['progress']))
time.sleep(10)
这是一个较长的函数,所以我将尝试一步一步地解释它。对于运行时间较长的作业,livy 会更改 url,因此我们需要存储最新的一个:
statement_url = host + response_headers['location']
然后,我们获取集群的当前状态并记录下来:
statement_response = requests.get(statement_url, headers={'Content-Type': 'application/json'})
statement_status = statement_response.json()['state']
logging.info('Statement status: ' + statement_status)
我们也正在获取应用程序的日志并显示它们。这对调试错误很有用:
lines = requests.get(session_url + '/log', headers={'Content-Type': 'application/json'}).json()['log']
for line in lines:
logging.info(line)
如果我们有一个严重的异常,那么应用程序将会死亡。我们需要标记它并抛出一个异常:
if statement_status == 'dead':
raise ValueError('Exception in the app caused it to be dead: ' + statement_status)
最后一步,我们记录进度并等待 10 秒钟,直到我们再次检查应用程序的状态:
if 'progress' in statement_response.json():
logging.info('Progress: ' + str(statement_response.json()['progress']))
time.sleep(10)
这就是全部。代码在 github 上,可以从这里访问。
如何在你的公司做数据科学才能发挥最大作用?
建立数据科学团队,营造健康的数据文化
年轻的公司从一开始就吸收了精益开发、以客户为中心和数据驱动的设计。然而,充分利用数据的潜力仍然非常困难。这一步对于传统企业来说更是难上加难。配备了多个创业公司的资源,数据的使用似乎要困难得多。由于公司文化和技术领域的必要变化,数据计划没有取得成功(根据 Brynjolfsson 2011 年的研究,即使简单的数据驱动决策在几年前就已经显示出可以将股票价值提高 5-6%,但情况仍然如此)。
最大的线索是,我们被大量的数据和可用的使用选项所淹没,而管理或业务部门对短期内的切实成果缺乏兴趣(Veeramachaneni 2016)。
在一家高速增长的公司建立数据科学部门,我将分享一些我个人的见解,如何克服这些困难,以及如何从贵公司的数据和数据科学中获得最大收益。
我最初是一名数据科学家,只是通过查询数据库来回答一些业务问题。我还开发了普通的 Excel 模型来模拟商业行为。只要有可能,我都支持使用数据驱动的方法和统计方法来支持决策。与此同时,与许多了不起的人一起,我们已经将几个复杂的机器学习模型投入生产,我正在组建一个团队,该团队将与产品经理、开发人员和我们的管理层合作,以更多地利用数据。
接下来,我将发布几个故事,揭示我在这段旅程中的许多收获。首先,我会建议一个做数据科学的潜在结构和有利条件(组件)。在下一个故事中,我将展示一些数据科学项目的结果和验收驱动的项目模式(流程)。最后,我将介绍一些数据科学的总体知识和成功因素(油脂)。为了简化,因为大多数发现适用于这两种团队焦点,我将使用术语数据单位/团队,而不是数据科学单位/团队。
太棒了,我们开始吧!
成功的数据驱动型组织是什么样的?
首先,我们遵循这个简单的模式从数据驱动的组织开始:我们将数据集成到我们的战略核心愿景中,并将其视为竞争优势。在公司层面,我们决定在任何有意义的时候用数据挑战直觉。这样,我们可以专注于上下文、案例和手头的客户,避免机械地使用过去的经验和其他领域的经验而产生的偏见。听起来不错!
然后,我们在公司设立了一个新的数据部门,作为另一个业务部门来执行我们自己制定的计划。会出什么问题呢?
Figure 1: Top-down structure for a data unit with the goal to provide additional business value.
显然,会出现很多问题。首先,由于新成立的部门是一个孤岛,与其他业务部门没有紧密联系,任何分析、建议的方法或结果的接受度都将非常低,因为它不是一个合资企业。第二,因为数据部门不参与价值创造链,所以数据部门对业务的了解不足以成为业务部门的积极合作伙伴。因此,利用数据集可能得到的解决方案并没有启动。最后,由于业务单位将数据单位视为他们需要协调的另一个单位(这甚至适用于更多的 todos),他们会将潜在的数据解决方案推到只需要日常工作所需的报告或分析的边缘。
那听起来不令人满意。有更好的方法吗?根据我的个人经验,从结构的角度来看,对于一个成功的数据驱动型组织来说,有几个要素是必不可少的。组成部分是文化、融合、赋能。在下文中,我将强调所有三个方面的关键因素,以使数据驱动的计划取得更好的结果。
Figure 2: More informal network structure that integrates a data unit with potential true business impact.
先说文化。对于一个数据驱动的企业来说,文化显然必须是数据驱动的(见图 2)。公司中的大多数人至少应该接受和重视数据支持的见解,即使他们不能在自己的影响范围内使用它们。然而,如果对数据的渴望根植于企业文化中,事情就会简单得多。这意味着人们天生希望更好地了解市场、客户和基于数据的衡量标准。公司应该高度重视数据,即使不把数据视为塑造业务的一种强大优势,因为它们反映了客户的信念、态度和行为。
问题来了,“一种文化是如何形成的?文化是人们从生活价值观和行为中汲取的东西,它不能直接形成”。然而,雕刻文化有一个众所周知的秘密。通过以身作则,可以做出改变。在我们的例子中,这意味着公司内有影响力的人和高管正在展示对数据和数据产品潜力的认真关注。为了克服一个主要的惯性,一个可见的和实质性的团体需要将数据置于直觉之上,寻求数据解决方案,并分别从所有的度量中寻求可测量的结果。
第二个也可能是最重要的组成部分是公司中数据团队或数据部门的集成(同样,我包含了数据科学)。有效的数据单元不是简单地添加到现有的结构中(参见图 1 和图 2)。如果将数据单元放在一个孤岛中,则存在与其他单元和业务目标不平衡和不一致的高风险。相反,如果数据单元与业务单元(最重要的是顶层决策)紧密联系和集成,新的见解和中断就有可能揭示隐藏在公司数据中的真正价值。简而言之:数据成功的真正秘诀是在组织的其他地方感知数据,而不是在数据团队本身(Newman 2015)。
只有决策者和产品经理了解数据模型的全部潜力,重视从争论的数据和数据解决方案中产生的洞察力,任何计划才会成功。最大的危险是,数据团队只被视为一个被动的团队,提供按需报告或分析。一个关注面如此狭窄的团队,效果将非常有限。此外,数据团队必须能够利用业务知识来开发可行的解决方案。如果数据团队一方面能够利用所有数据科学方法解决更大的问题,另一方面能够充分理解给定的业务环境,就可以找到新的有效解决方案。通过与利益相关者紧密合作,创建协作解决方案并促进数据和业务知识的交流,输出将成为在内部、面向市场或面向客户的产品中可见的结果。有了这样的端到端结果,数据可以产生真正的业务影响。
毕竟,成功的数据计划的最后一个要素是授权。这包括培养正确的技能并相应地定义角色。先说技巧。鉴于上述观点,最重要的技能是数据人员能够(也确实)承担责任,表达和沟通复杂的问题,并且是自我组织的。由于拥有丰富数据技能的人往往在细节层次上变得有些数学化和复杂化,这显然不是一个容易实现的需求。对于喜欢细枝末节的人来说,另一项重要技能变得清晰起来。能够走捷径并在数据争论的某一点停止的能力足以刺激业务。这需要高度的结果导向。如果你足够幸运,能为你的团队找到具备这些技能的球员,你就可以大显身手了。如果没有,最重要的任务之一就是每天练习这些技能。
这就引出了最后一点。为了从非凡的成果中获益,数据处理人员的角色需要以这样一种方式定义,即他们能够从一端到另一端自主工作。这意味着发现和解决任务或问题的责任必须完全委托给负责人,包括他或她会做正确事情的信心。此外,必须确保所有必要和有用的资源可用(例如,所有必要的数据和上下文信息)。只有消除障碍(即知识、结果和数据可以容易地获得),才能取得优异的成果。
顺便说一句,我认为这些成功要素中的大部分以类似的方式适用于许多其他类型的团队和任务!
暂时就这样了。敬请期待第二部。在第二个故事中,我将阐述使数据驱动的计划成功的程序因素(即项目流程):
阅读第二部分:
如何在你的公司做数据科学才能发挥最大作用?第二部分。
定义数据项目的流程并理解它们的双峰性质
在一家快速发展的公司中建立数据科学团队,我将分享一些我个人对如何克服困难并从贵公司的数据中获得最大价值的见解。在上一个故事中,我提出了进行数据科学的潜在结构和有利条件(组件):
在这个故事中,我将为数据科学项目呈现一些面向结果的项目模式( Flow )。在下一个故事中,我将总结数据科学的知识和成功因素(油脂)。所有这三个故事都传达了在实现数据计划成果最大化的道路上取得的经验。
但是让我们现在就开始。下面我将概述我们如何管理和领导数据项目。这不是一项容易的任务,因为数据项目的性质可能非常不同:有些简明的问题需要快速清晰地回答,有些非常抽象的问题需要更广泛的理解和更全面的解决方案。为了描述数据项目的这种双峰性质,我将区分两种类型的项目:数据分析项目和新颖的数据解决方案。
数据分析项目
什么是数据分析项目?公司的努力应该以其对公司绩效 KPI 的影响来衡量。这对于学习采取什么行动是很重要的。根据我的观察,有前瞻性和回顾性思维的人:前者首先想了解一个主题,然后启动精心选择的措施,后者想快速跟随直觉,稍后才确认结果。两种观点有相似的要求:需要对一些 KPI 的一些问题进行分析,以了解范围、变化、绩效或差异,从而支持决策并消除偏见。但是,需要分析数据的时间点和问题类型不同。
数据分析项目有一定的特点:需求可以提前设定,有明确的预期结果,必要的努力在一定程度上可以提前预估。这是一个重要的特征,因为结果与决策者非常相关,并且是及时需要的。由于数据分析项目的要求有些固定,即使有时很复杂,也可以应用预定义的工作流来管理它们。这降低了风险,为请求者提供了透明性,并支持快速迭代。
Pipeline Flow of a Data Analysis Project
我们如何处理数据分析项目?
为了能够有效地处理分析数据项目,我们试图以面向过程的方式来管理它们。虽然我们通过不同的联系点和不同的团队收到请求,但我们会很好地协调它们。分析请求得到细化、优先化和规划。首先,我们试图理解哪个数据集市可以回答这个问题,需要什么样的分析,然后我们决定哪个团队处理这个分析。然后我们给请求者第一次反馈。当任务被执行时,我们获得数据,分析数据,与请求者一起审查结果,根据需要迭代,并快速及时地交流结果。对于大多数任务,我们使用标准流程。这不同于需要不同过程的新颖的数据解决方案,我将在下面进行描述。
新颖的数据解决方案
什么是新颖的数据解决方案?当我谈到这些 moonshot 项目之一时,我指的是新颖的数据解决方案,例如,试图预测具有特定属性和价格的产品是否会被销售,或者产品类型是否可以通过其图像来识别。与数据分析项目不同,人们对如何完成项目知之甚少。关注公司的模糊、复杂和一般性问题(例如,“我们想了解我们的产品”,“客户想要什么?”).我们称之为大问题。通过回答这些问题,可以开发出对业务和客户价值有重大影响的新产品和解决方案。
尽管新颖的数据解决方案为公司提供了很高的价值,但它们有着非常不同的性质:时间表通常不明确,难以估计。然而,需要付出巨大的努力才能获得初步的成果(有时是正常努力的 10 倍)。获得结果的方式是不明确的和探索性的,结果是非常不确定的。总之,这些类型的项目构成了高风险高收益的特征:它们可能被证明是根本不可行的,例如,因为所需的信息不包括在数据中,但是它们也可能对业务产生强烈的影响。这意味着他们甚至有可能扰乱和改变整个行业。结果,这些项目感觉像是“实验”,有些不寻常和独立,但是有许多依赖关系。
Explorative and Complex Flow of a Novel Data Solution
我们如何处理新奇的数据解决方案?
新颖的数据解决方案由于其非常复杂的性质和高度依赖性,只能被引导而不能被管理。提前计划并设定最后期限似乎会适得其反。然而,从我们最初的试点项目中获得一些经验后,我们开始通过一些深思熟虑的优化来管理风险。首先,我们从非常有选择性的优先级开始,只允许一些精心挑选的项目开始。这有助于保持注意力高度集中。其次,我们已经开始与的时间盒和的初始快速迭代(比如一个两周的原型)一起工作,以便在我们忙于它之前获得一些经验和对问题的理解。最后,我们已经成为了早期失败的忠实粉丝。这就是为什么我们从一开始就开始从端到端地切入项目的初始版本,以查看陷阱在哪里。
从完整的新数据解决方案项目组合的角度来看,我们在早期阶段就开始收集和酝酿广泛的主题,与来自产品管理、开发、营销、客户关系和管理的所有关键利益相关方进行讨论。这体现在我们定期修订的一系列大问题中。这个列表帮助我们首先对一个主题进行初步的思考和假设,包括专家的观点和市场势头,直到我们决定下一个要关注的项目。我们已经学会了一步一步地“发展”新的数据解决方案,而不是计划太难。但是我们总是与正在运行的项目仔细协调,看看我们站在哪里,并且能够控制努力和风险。我们由此采用一种播种、育种、剔除或**收获的方法。**分享这一点很重要:**不要固定长期路线图!**由于这类项目的本质,只需计划下一步并定期(至少每季度一次)修改现状,然后再计划下一个项目。然而,我们在我们同时运行的精选 moonshots 的明确投资组合上分散风险,因为在某些情况下,结果可能是零,但在其他情况下,结果可能是一切。
在整个过程中与所有利益相关者进行良好的沟通是非常重要的。开始时,主要目标是将商业知识和背景带入项目。随后,我们希望将风险透明化,找到潜在障碍的解决方案,并明确目标。在原型开发接近尾声时,可能的结果必须作为公司产品的一部分进行整合或测试,概念和数据模型必须投入生产。然而,由于新数据解决方案项目的模糊性,预先规划是非常困难的,因此与所有利益相关者的良好沟通必须对此进行补偿。
如何连接两部分
显然,这两种类型的项目很难组织在一起,因为它们会将紧急和重要之间的冲突推向极端。因此,优先考虑和保护所需数量的开放和更不确定的新数据解决方案非常重要。这可以通过“强门”来实现,即通过限制目前运行的项目总数和严格限制接受的特别问题的数量。这有助于“大问题”以集中的方式得到解决,并且正在进行的项目不会被更紧急和更实际的事情所阻碍。
How to Organize a Bimodal Data Project Pipeline
从我们的第一个数据项目中,我们开发了以下程序(见上图):
首先,评估来自业务部门或管理层的请求。在这一步,我们了解任务是需要统计模型支持还是需要以更线性的方式进行分析,哪个团队能最好地完成任务,任务有多复杂,任务的优先级如何,以及需要什么数据源。之后,我们可以将任务转给合适的人/团队(绕过季度路线图),或者如果是更复杂的任务,将任务安排在下一季度(通过季度路线图),或者如果是不需要立即解决的较难问题,我们可以将任务放在大问题列表中。
我们定期审查和更新包含“可以用数据回答的基本业务问题”的大问题列表。该列表包含我们多年来从利益相关者、集思广益、最佳实践和许多其他来源收集的潜在项目。该列表包含许多可能提供真正潜力的项目,但同时解决起来非常复杂(例如,了解用户、预测市场等等)。这些大题都是在迭代中逐步复习和提炼的。
其次,根据新兴的业务目标,我们每个季度都从列表中获得最有希望的大问题(“backlog”),并在我们的季度路线图中计划其中的一些问题(尽管我们知道有些问题可能需要一个季度以上的时间来解决,但这是限制首先投入的时间量的一个很好的方法,只有在审查后才继续)。季度路线图的剩余部分将由其他计划填充。
路线图中的主题和已经通过特别旁路的任务然后或者(1)在分析的情况下通过看板在数据团队中简单地执行,或者(2)为开发和机器学习团队的流程安排(如果任务需要更多的开发或机器学习工作),或者(3)为主题的第一个原型创建时间盒(如果它非常复杂并且需要首先开发更好的理解)。这个时间盒直接分配给个人或项目团队,其他任务的日程为分配的时间盒留出了空间。
为了让事情变得透明,backlog 和 boards 是开放的。此外,正在进行的和计划中的下一次迭代的项目会在公司内部定期交流。此外,如果需要保持信息流,直接受影响的产品经理或开发人员可以通过站立协调。
到目前为止,这些是我对如何组织数据项目的发现。在下一部分中,我将总结使数据驱动计划成功的一般见解和成功因素。敬请期待!
如何用图卷积网络在图上做深度学习
第 1 部分:图卷积网络的高级介绍
图的机器学习是一项困难的任务,因为图的结构非常复杂,但也能提供丰富的信息。本文是关于如何使用图形卷积网络(GCNs)对图形进行深度学习的系列文章中的第一篇,图形卷积网络是一种强大的神经网络,旨在直接对图形进行处理并利用其结构信息。系列文章包括:
- 图卷积网络的高级介绍(this)
- 利用谱图卷积的半监督学习
在本帖中,我将介绍 GCNs,并使用编码示例说明信息如何通过 GCN 的隐藏层传播。我们将了解 GCN 如何聚合来自之前图层的信息,以及这种机制如何在图表中生成有用的节点要素表示。
什么是图卷积网络?
GCNs 是一种非常强大的神经网络架构,用于在图上进行机器学习。事实上,它们是如此强大,甚至一个随机启动的 2 层 GCN 也能产生网络中节点的有用特征表示。下图显示了由这种 GCN 产生的网络中每个节点的二维表示。注意,即使没有任何训练,网络中节点的相对接近度也保留在二维表示中。
更正式地说,图卷积网络(GCN) 是一种对图进行操作的神经网络。给定一个图 G = (V,E) ,一个 GCN 取为输入
- 输入特征矩阵 N × F⁰ 特征矩阵, X, 其中 N 是节点的数量, F⁰ 是每个节点的输入特征的数量,并且
- 图结构的一个 N × N 矩阵表示如 g[1]的邻接矩阵 A
GCN 中的一个隐藏层因此可以写成**= f(h【ⁱ⁻】,a)其中h⁰=x和 f 为每层对应一个n×fⁱ特征矩阵,其中每一行都是一个节点的特征表示。在每一层,使用传播规则 f 聚集这些特征以形成下一层的特征。通过这种方式,每个连续层的特征变得越来越抽象。在这个框架中,GCN 的变体仅仅在传播规则的选择上有所不同。**
一个简单的传播规则
一个最简单的可能传播规则是[1]:
f(hⁱ,a)=σ(ahⁱwⁱ)
其中 Wⁱ 是层 i 的权重矩阵,而 σ 是非线性激活函数,例如 ReLU 函数。权重矩阵有维度fⁱ×fⁱ⁺;换句话说,权重矩阵的第二维的大小决定了下一层的特征数量。如果你熟悉卷积神经网络,这个操作类似于过滤操作,因为这些权重在图中的节点间共享。**
简单化
让我们从最简单的层面来研究传播规则。让
- i = 1 ,s.t. f 是输入特征矩阵的函数,
- σ 是恒等函数,并且
- 选择权重 s . t .ah⁰w⁰=axw⁰=ax。
**换句话说,f(X,A)=AX。这个传播规则可能有点太简单了,但是我们稍后会添加缺少的部分。作为旁注, AX 现在相当于一个多层感知器的输入层。
一个简单的图表示例
作为一个简单的例子,我们将使用下图:
A simple directed graph.
而下面是它的numpy
邻接矩阵表示。
**A = np.matrix([
[0, 1, 0, 0],
[0, 0, 1, 1],
[0, 1, 0, 0],
[1, 0, 1, 0]],
dtype=float
)**
接下来,我们需要特性!我们基于其索引为每个节点生成 2 个整数特征。这使得稍后手动确认矩阵计算变得容易。
**In [3]: X = np.matrix([
[i, -i]
**for** i **in** range(A.shape[0])
], dtype=float)
XOut[3]: matrix([
[ 0., 0.],
[ 1., -1.],
[ 2., -2.],
[ 3., -3.]
])**
应用传播规则
好吧!我们现在有一个图,它的邻接矩阵A
和一组输入特征X
。让我们看看应用传播规则时会发生什么:
**In [6]: A * X
Out[6]: matrix([
[ 1., -1.],
[ 5., -5.],
[ 1., -1.],
[ 2., -2.]]**
发生了什么事?每个节点(每行)的表示现在是其相邻要素的总和!换句话说,图形卷积层将每个节点表示为其邻域的集合。我鼓励你自己检查计算。注意,在这种情况下,如果存在从 v 到 n 的边,则节点 n 是节点 v 的邻居。
啊哦!即将出现的问题!
您可能已经发现了问题:
- 一个节点的聚合表示不包括它自己的特性!该表示是相邻节点的特征的集合,因此只有具有自环的节点才会将它们自己的特征包括在集合中。[1]
- 度数较大的结点在其要素制图表达中将具有较大的值,而度数较小的结点将具有较小的值。这可能导致梯度消失或爆炸[1,2],但对于通常用于训练此类网络的随机梯度下降算法来说也是一个问题,该算法对每个输入要素的比例(或取值范围)非常敏感。
下面,我将分别讨论这些问题。
添加自循环
为了解决第一个问题,可以简单地给每个节点添加一个自环[1,2]。实际上,这是通过在应用传播规则之前将单位矩阵I
添加到邻接矩阵A
来完成的。
**In [4]: I = np.matrix(np.eye(A.shape[0]))
IOut[4]: matrix([
[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.]
])In [8]: A_hat = A + I
A_hat * X
Out[8]: matrix([
[ 1., -1.],
[ 6., -6.],
[ 3., -3.],
[ 5., -5.]])**
由于该节点现在是其自身的邻居,所以在对其邻居的特征求和时包括了该节点自身的特征!
标准化要素制图表达
通过将邻接矩阵A
乘以逆矩阵D
【1】来转换邻接矩阵A
,可以通过节点度来归一化特征表示。因此,我们简化的传播规则如下所示[1]:
f( X ,a)=d⁻*ax*****
让我们看看会发生什么。我们首先计算度矩阵。
**In [9]: D = np.array(np.sum(A, axis=0))[0]
D = np.matrix(np.diag(D))
D
Out[9]: matrix([
[1., 0., 0., 0.],
[0., 2., 0., 0.],
[0., 0., 2., 0.],
[0., 0., 0., 1.]
])**
在应用规则之前,让我们看看变换后邻接矩阵会发生什么变化。
****在之前
**A = np.matrix([
[0, 1, 0, 0],
[0, 0, 1, 1],
[0, 1, 0, 0],
[1, 0, 1, 0]],
dtype=float
)**
****在之后
**In [10]: D**-1 * A
Out[10]: matrix([
[0\. , 1\. , 0\. , 0\. ],
[0\. , 0\. , 0.5, 0.5],
[0\. , 0.5, 0\. , 0\. ],
[0.5, 0\. , 0.5, 0\. ]
])**
注意,邻接矩阵的每一行中的权重(值)已经除以了对应于该行的节点的度数。我们用转换后的邻接矩阵应用传播规则
**In [11]: D**-1 * A * X
Out[11]: matrix([
[ 1\. , -1\. ],
[ 2.5, -2.5],
[ 0.5, -0.5],
[ 2\. , -2\. ]
])**
并且获得对应于相邻节点的特征的平均值的节点表示。这是因为(经变换的)邻接矩阵中的权重对应于相邻节点特征的加权和中的权重。我再次鼓励你亲自验证这个观察结果。
把所有的放在一起
我们现在将自循环和规范化技巧结合起来。此外,我们将重新引入之前为了简化讨论而放弃的权重和激活函数。
加回重量
首先要做的是应用权重。注意,这里的D_hat
是A_hat = A + I
的度矩阵,即带有强制自循环的A
的度矩阵。
**In [45]: W = np.matrix([
[1, -1],
[-1, 1]
])
D_hat**-1 * A_hat * X * W
Out[45]: matrix([
[ 1., -1.],
[ 4., -4.],
[ 2., -2.],
[ 5., -5.]
])**
如果我们想要减少输出特征表示的维数,我们可以减少权重矩阵的大小W
:
**In [46]: W = np.matrix([
[1],
[-1]
])
D_hat**-1 * A_hat * X * W
Out[46]: matrix([[1.],
[4.],
[2.],
[5.]]
)**
添加激活功能
我们选择保留特征表示的维度,并应用 ReLU 激活函数。
**In [51]: W = np.matrix([
[1, -1],
[-1, 1]
])
relu(D_hat**-1 * A_hat * X * W)
Out[51]: matrix([[1., 0.],
[4., 0.],
[2., 0.],
[5., 0.]])**
瞧啊。一个完整的隐藏层,有邻接矩阵,输入特征,权重和激活函数!
回到现实
现在,最后,我们可以将一个图卷积网络应用到一个真实的图上。我将向您展示如何生成我们在本文前面看到的要素制图表达。
扎卡里空手道俱乐部
扎卡里的空手道俱乐部是一个常用的社交网络,其中节点代表空手道俱乐部的成员,边代表他们的相互关系。当扎卡里在学习空手道俱乐部时,管理员和教练之间发生了冲突,导致俱乐部一分为二。下图显示了网络的图形表示,节点根据俱乐部的位置进行标记。管理员和讲师分别标有“A”和“I”。
Zachary’s Karate Club
建造 GCN
现在让我们建立图形卷积网络。我们实际上不会训练网络,只是简单地随机初始化它,以生成我们在本文开头看到的要素制图表达。我们将使用networkx
,它有一个很容易得到的俱乐部的图形表示,并计算A_hat
和D_hat
矩阵。
**from networkx import karate_club_graph, to_numpy_matrixzkc = karate_club_graph()
order = sorted(list(zkc.nodes()))A = to_numpy_matrix(zkc, nodelist=order)
I = np.eye(zkc.number_of_nodes())A_hat = A + I
D_hat = np.array(np.sum(A_hat, axis=0))[0]
D_hat = np.matrix(np.diag(D_hat))**
接下来,我们将随机初始化权重。
**W_1 = np.random.normal(
loc=0, scale=1, size=(zkc.number_of_nodes(), 4))
W_2 = np.random.normal(
loc=0, size=(W_1.shape[1], 2))**
堆叠 GCN 层。我们这里只使用单位矩阵作为特征表示,也就是说,每个节点被表示为一个独热编码的分类变量。
**def gcn_layer(A_hat, D_hat, X, W):
return relu(D_hat**-1 * A_hat * X * W)H_1 = gcn_layer(A_hat, D_hat, I, W_1)
H_2 = gcn_layer(A_hat, D_hat, H_1, W_2)output = H_2**
我们提取特征表示。
**feature_representations = {
node: np.array(output)[node]
for node in zkc.nodes()}**
瞧啊。将扎卡里空手道俱乐部的社区很好地分开的特征表示。我们甚至还没有开始训练!
Feature Representations of the Nodes in Zachary’s Karate Club
我应该注意到,对于这个例子,随机初始化的权重很可能在 x 轴或 y 轴上给出 0 值,作为 ReLU 函数的结果,所以需要一些随机初始化来产生上面的图。
结论
在这篇文章中,我对图卷积网络进行了高度的介绍,并举例说明了 GCN 中每一层节点的特征表示是如何基于其邻域的聚合的。我们看到了如何使用 numpy 构建这些网络,以及它们有多么强大:即使是随机初始化的 gcn 也可以在 Zachary 的空手道俱乐部中分离社区。
在下一篇文章中,我将更深入地介绍技术细节,并展示如何使用半监督学习来实现和训练最近发布的 GCN。你在这里 找到系列 的下一篇帖子。
喜欢你读的书吗?考虑在Twitter上关注我,在那里,除了我自己的帖子之外,我还会分享与数据科学和机器学习的实践、理论和伦理相关的论文、视频和文章。
如需专业咨询,请在LinkedIn上联系我,或在Twitter上直接留言。
参考
[2]Thomas Kipf 和 Max Welling 的论文称为图卷积网络半监督分类。
如何在计算机视觉中做所有的事情
将深度学习魔法用于计算机视觉
Mask-RCNN doing object detection and instance segmentation
想获得灵感?快来加入我的 超级行情快讯 。😎
想做计算机视觉?深度学习是当今的趋势。大规模数据集加上深度卷积神经网络(CNN)的代表能力有助于建立超精确和稳健的模型。只剩下一个挑战:如何设计你的模型。
像计算机视觉这样广泛而复杂的领域,解决方案并不总是清晰的。计算机视觉中的许多标准任务都需要特别考虑:分类、检测、分割、姿态估计、增强和恢复以及动作识别。尽管用于它们中每一个的最先进的网络展示了共同的模式,但是它们仍然需要它们自己独特的设计。
那么,我们如何为所有这些不同的任务建立模型呢?
我来给你演示一下如何用深度学习做计算机视觉中的一切!
分类
其中最著名的!图像分类网络以固定大小的*输入开始。*输入图像可以有任意数量的通道,但对于 RGB 图像通常是 3 个。设计网络时,从技术上讲,分辨率可以是任何大小,只要它足够大,能够支持整个网络中的缩减采样量。例如,如果在网络内进行 4 次缩减采样,则输入的大小至少需要为 4 = 16 x 16 像素。
随着网络的深入,空间分辨率将会降低,因为我们试图压缩所有的信息,并得到一维向量表示。为了确保网络始终有能力传送它提取的所有信息,我们增加了与深度成比例的特征地图的数量,以适应空间分辨率的降低。也就是说,我们在下采样过程中丢失了空间信息,为了适应这种丢失,我们扩展了我们的特征图以增加我们的语义信息。
在您选择了一定数量的缩减像素采样后,要素地图将被矢量化并输入到一系列完全连接的图层中。最后一个图层的输出与数据集中的类一样多。
目标检测
物体探测器有两种类型:一级和二级。两者都是从“锚箱”开始的;这些是默认的边界框。我们的探测器将预测这些盒子和地面真相之间的差异,而不是直接预测盒子。
在两级检测器中,我们自然有两个网络:盒建议网络和分类网络。盒子提议网络在它认为物体存在的可能性很高的地方提议边界盒子的坐标;同样,这些是相对于锚盒的相对位置。然后,分类网络采用这些边界框中的每一个,并对位于其中的潜在对象进行分类。
在一级检测器中,建议和分类器网络融合成一个单级。网络直接预测边界框坐标和位于该框内的类。因为两级融合在一起,所以单级检测器往往比两级检测器更快。但是由于两个任务的分离,两级检测器具有更高的精度。
The Faster-RCNN two-stage object detection architecture
The SSD one-stage object detection architecture
分割
分割是计算机视觉中更独特的任务之一,因为网络需要学习低级和高级信息。低级信息用于按像素精确分割图像中的每个区域和对象,高级信息用于直接对这些像素进行分类。这导致网络被设计成将来自早期层和高分辨率(低级空间信息)的信息与更深层和低分辨率(高级语义信息)的信息相结合。
正如我们在下面看到的,我们首先通过一个标准的分类网络运行我们的图像。然后,我们从网络的每个阶段提取特征,从而使用从低到高范围的信息。在依次将它们组合在一起之前,每个信息级别都被独立处理。随着信息的组合,我们对特征图进行上采样,最终得到完整的图像分辨率。
要了解更多关于深度学习如何分割的细节,请查看这篇文章。
The GCN Segmentation architecture
姿态估计
姿态估计模型需要完成两项任务:(1)检测图像中每个身体部位的关键点(2)找出如何正确连接这些关键点。这分三个阶段完成:
(1)使用标准分类网络从图像中提取特征
(2)给定这些特征,训练一个子网络来预测一组 2D 热图。每个热图与特定的关键点相关联,并且包含每个图像像素关于关键点是否可能存在的置信度值
(3)再次给定来自分类网络的特征,我们训练子网络来预测一组 2D 矢量场,其中每个矢量场编码关键点之间的关联程度。然后,具有高关联性的关键点被称为是连通的。
用子网以这种方式训练模型将共同优化关键点的检测和它们的连接。
The OpenPose Pose Estimation architecture
增强和恢复
增强和恢复网络是他们自己独特的野兽。我们不会对这些图像进行任何下采样,因为我们真正关心的是高像素/空间精度。下采样将真正杀死这些信息,因为它将减少多少像素,我们有空间的准确性。相反,所有处理都是在全图像分辨率下完成的。
我们首先以全分辨率将我们想要增强/恢复的图像不加任何修改地传送到我们的网络。该网络简单地由许多卷积和激活函数的堆栈组成。这些块通常是受启发的,偶尔也是最初为图像分类开发的那些块的直接拷贝,例如剩余块、密集块、挤压激发块等。最后一层没有激活函数,甚至没有 sigmoid 或 softmax,因为我们想直接预测图像像素,不需要任何概率或分数。
这就是这些类型的网络的全部内容!在图像的全分辨率下进行大量处理,以实现高空间精度,使用已被证明适用于其他任务的相同卷积。
The EDSR Super-Resolution architecture
动作识别
动作识别是少数几个特别要求视频数据正常工作的应用之一。为了对一个动作进行分类,我们需要了解场景随时间发生的变化;这自然导致我们需要视频。我们的网络必须被训练以学习空间和时间信息,即空间和时间的变化。最完美的网络是 3D-CNN。
顾名思义,3D-CNN 是一个使用 3D 卷积的卷积网络!它们与常规 CNN 的不同之处在于,卷积是在三维空间中应用的:宽度、高度和时间 T21。因此,每个输出像素都是通过基于其周围的像素以及相同位置的前一帧和后一帧中的像素的计算来预测的!
Passing images in a large batch directly
视频帧可以通过几种方式传递:
(1)直接进行大批量,如第一图。因为我们传递的是一系列帧,所以空间和时间信息都是可用的
Single frame + optical flow (left). Video + optical flow (right)
(2)我们也可以在一个流中传递单个图像帧(数据的空间信息)和来自视频的其对应的光流表示(数据的时间信息)。我们将使用常规的 2D 有线电视新闻网从两者中提取特征,然后将它们组合起来传递给我们的 3D 有线电视新闻网,后者将两种类型的信息组合在一起
(3)将我们的帧序列传递给一个 3D CNN,并将视频的光流表示传递给另一个 3D CNN。两个数据流都具有可用的空间和时间信息。这可能是最慢的选择,但也可能是最准确的,因为我们正在对包含所有信息的两种不同的视频表示进行特定的处理。
所有这些网络输出视频的动作分类。
如何使用梯度下降进行线性回归
梯度下降是我从 Siraj Raval 的深度学习基础纳米学位中学到的第一个有趣的话题。这个练习的回购可以在这里找到
这个练习的目的是研究学生的考试成绩和学习时间之间的关系。为了实现这一目标,我们使用称为线性回归的策略来模拟因变量(学生的考试成绩)和解释变量(学习时间)之间的关系。我们还使用梯度下降来进一步优化我们的模型。梯度下降可能是机器学习和深度学习中最流行的方法。
我们的数据集包含 x-y 平面中的 x 和 y 数据点的集合,其中 x 是学生的考试分数,y 是学生学习的小时数。
我们使用 Python 以编程方式计算最佳拟合线,该线描述了学生学习的小时数和学生在测试中获得的分数之间的线性关系。我们使用 Python 是因为它被认为是最流行和最具语言性的机器学习框架。
我们将从一个非常标准的起始代码开始。
if __name__ = '__main__':
run()
然后,我们使用 numpy 在内存中提取并解析我们的数据集,以在其上运行算法。我们使用分隔符’,',意思是用来在。csv 文件
学习率是超级参数(即我们用作模型的调谐旋钮,或者模型学习的速度)
如果学习率太低,我们的模型将太慢而无法收敛,如果学习率太高,它将永远无法收敛。我们想要达到一个平衡,一个最佳的学习速度。在机器学习中,我们并不总是知道最佳学习速率会是多少,所以猜测和检查是获得该值的最佳方式
我们从初始 m 和 b 值= 0 (方程 y = mx+b) 开始。我们从 0 开始,因为我们将随着时间的推移学习这些值。迭代次数= 1000(即我们希望从每个训练步骤中学习多少次迭代)。我们选择 1000 步,因为我们数据集很小。随着数据集变大,考虑到 CPU 等因素,迭代次数应该是 10K 或 100K
我们通过使用gradient _ descent _ runner函数计算 b 和 m,函数的输入为“点”——x,y 点数组,初始 b 值为起始 b 值,初始 m 值为初始 m 值,学习速率和上面定义的迭代次数
from numpy import *def run(): points = genfromtext('data.csv', delimiter=',')
learning_rate = 0.0001 #y = mx + b (slope formula) initial_b = 0
initial_m = 0 # ideal slope, will start with 0 num_iterations = 1000
[b, m] = gradient_descent_runner(points, initial_b, initial_m, learning_rate, num_iterations)
print "After {0} iterations b = {1}, m = {2}, error = {3}".format(num_iterations, b, m, compute_error_for_line_given_points(b, m, points))
gradient_descent_runner 函数定义如下:
def gradient_descent_runner(points, starting_b, starting_m, learning_rate, num_iterations):
b = starting_b
m = starting_m for i in range(num_iterations):
b, m = step_gradient(b,m, array(points), learning_rate return [b, m]
这里,我们将 starting_b 和 starting_m 值赋给 b 和 m,对于每次迭代,我们将通过将 b 和 m 的先前值输入 step_gradient 函数来计算 b 和 m,该函数接受 b、m 的值、点 x、y 的数组和学习速率
然后我们返回这个最佳对 b 和 m。
在我们编写“step_gradient”函数之前,我们将编写另一个函数,该函数计算给定一组点的 b 和 m 的线性模型的误差平方和的平均值:
def compute_error_for_line_given_points(b, m, points):
totalError = 0
for i in range(0, len(points)):
x = points[i, 0]
y = points[i, 1] totalError += (y - (m * x + b)) ** 2 return totalError / float(len(points))
为了确定如何用给定的点集来最好地拟合我们的模型,我们希望最小化这些点到我们的线性模型之间的距离。计算总误差有助于我们确定我们的模型有多差,这样我们就可以每一步都更新它。“y”是真实数据,mx+b 是我们的模型用来预测“y”的数据,取这些值之间的差值,就得到我们模型的正(或负)误差值。我们迭代地对这些平方差求和,并除以点数,以便获得我们的线性模型的误差平方和。3D 下图显示了所有可能的 x 轴截距、y 轴截距和误差值。我们想找到误差最小的点,即曲线的底部(或局部最小值)
然后,我们继续为我们的模型编写阶跃梯度函数。梯度可以最好地理解为斜率、切线或移动方向(向上或向下),以便最小化误差。
为了计算梯度,我们计算 b 和 m 的偏导数。然后,我们通过从学习率和每个值的梯度值的乘积中减去 b 和 m 的当前值来计算新的 b 和 m 值
def step_gradient(b_current, m_current, points, learning_rate):
b_gradient = 0
m_gradient = 0
N = float(len(points)) for i in range(0, len(points)):
x = points[i, 0]
y = points[i, 1] b_gradient += -(2/N) * (y - ((m_current * x) + b_current))
m_gradient += -(2/N) * x * (y - ((m_current * x) + b_current)) new_b = b_current - (learning_rate * b_gradient)
new_m = m_current - (learning_rate * m_gradient) return [new_b, new_m]
这里的学习率允许我们的模型学习并收敛于 b 和 m 的新的优化值,然后用于根据给定的学习小时数预测测试分数
完整代码如下:
from numpy import *def compute_error_for_line_given_points(b, m, points):
totalError = 0
for i in range(0, len(points)):
x = points[i, 0]
y = points[i, 1] totalError += (y - (m * x + b)) ** 2 return totalError / float(len(points))def step_gradient(b_current, m_current, points, learning_rate):
b_gradient = 0
m_gradient = 0
N = float(len(points)) for i in range(0, len(points)):
x = points[i, 0]
y = points[i, 1] b_gradient += -(2/N) * (y - ((m_current * x) + b_current))
m_gradient += -(2/N) * x * (y - ((m_current * x) + b_current)) new_b = b_current - (learning_rate * b_gradient)
new_m = m_current - (learning_rate * m_gradient) return [new_b, new_m]def gradient_descent_runner(points, starting_b, starting_m, learning_rate, num_iterations):
b = starting_b
m = starting_m for i in range(num_iterations):
b, m = step_gradient(b,m, array(points), learning_rate return [b, m]def run(): points = genfromtext('data.csv', delimiter=',')
learning_rate = 0.0001 #y = mx + b (slope formula) initial_b = 0
initial_m = 0 # ideal slope, will start with 0 num_iterations = 1000
[b, m] = gradient_descent_runner(points, initial_b, initial_m, learning_rate, num_iterations)
print "After {0} iterations b = {1}, m = {2}, error = {3}".format(num_iterations, b, m, compute_error_for_line_given_points(b, m, points))if __name__ = '__main__':
run()
来源:
1/ Siraj Raval 的《如何用梯度下降法做线性回归:【https://www.youtube.com/watch?v=XdM6ER7zTLk
2/深度学习纳米度:
https://www . uda city . com/course/deep-learning-nano degree-foundation-nd 101
如何用 BigQuery ML 做在线预测
BigQuery ML 是一种在 Google Cloud 上的 Pb 级交互式数据仓库中直接进行机器学习的方法。你可以在几分钟内在数百万行上训练机器学习模型,而不必四处移动数据。
注意:BigQuery ML 现在支持将训练好的模型提取为 TensorFlow SavedModel。所以你可以简单的 导出模型,然后将它 部署到云 AI 平台预测。也就是说,这篇文章仍然是有用的,它提醒我们如何看待权重,并重申 BQML 是一个开放系统的观点。
不过,训练完模型后,你需要用它来预测。开箱即用的 BigQuery 支持批量预测——这适用于报告和仪表板应用程序。但是,BigQuery 查询通常有 1-2 秒的延迟,因此批量预测功能不能用于在线预测(例如从 web 或移动应用程序)。
Sometimes your predictions need to return immediately
在本文中,我将向您展示如何从训练输出表中提取必要的权重和缩放参数,并自己计算预测。这些代码可以包装在 web 应用程序框架中,或者您希望预测代码存在的任何地方。
本文的完整代码在 GitHub 上的。
创建模型
让我们首先创建一个简单的预测模型来预测飞机的到达延迟(更多详细信息,参见本文)。我将使用这个模型来说明这个过程。
CREATE OR REPLACE MODEL flights.arrdelay
OPTIONS
(model_type='linear_reg', input_label_cols=['arr_delay']) AS
SELECT
arr_delay,
carrier,
origin,
dest,
dep_delay,
taxi_out,
distance
FROM
`cloud-training-demos.flights.tzcorr`
WHERE
arr_delay IS NOT NULL
这花了我大约 6 分钟,在 600 万行和 267 MB 数据上训练,花费大约 1.25 美元。( BigQuery 的自由层可能会帮你解决这个问题;为了降低成本,使用较小的表)。
模型批量预测
一旦有了训练好的模型,就可以在 BigQuery 内部进行批量预测。例如,要查找从 DFW 到洛杉矶国际机场的航班在一系列出发延迟时间内的预计到达延迟时间,您可以运行以下查询:
SELECT * FROM ml.PREDICT(MODEL flights.arrdelay, (
SELECT
'AA' as carrier,
'DFW' as origin,
'LAX' as dest,
dep_delay,
18 as taxi_out,
1235 as distance
FROM
UNNEST(GENERATE_ARRAY(-3, 10)) as dep_delay
))
在上面的查询中,我硬编码了承运人、原产地等的输入值。并使用 GENERATE_ARRAY 函数生成-3 分钟到 10 分钟范围内的出发延迟。这产生了一个表,该表具有每个出发延迟的预测到达延迟:
Predicted arrival delay if a flight from Dallas to Los Angeles departs 3 minutes early (dep_delay=-3) to 10 minutes late (dep_delay=10)
批量预测成本低。上面的查询处理了 16 KB,花费了 0.000008 美分。
虽然这种预测机制适用于离线预测,但实际上您不能将其用于在线预测。如果预测是作为网站或移动应用程序中用户交互的结果来显示的,那么您无法承受与每个 BigQuery 调用相关的 1-2 秒的延迟。您通常希望延迟在几毫秒的数量级,因此您需要一个更快的推理解决方案。
重量和比例
幸运的是,BigQuery 公开了自己计算预测值所需的所有信息。您可以将这些代码直接嵌入到您的应用程序中。我用 Python 来说明这一点,但是你可以用任何你想用的语言来说明。
你需要获取 3 条信息:
- 使用此查询可以获得的每个数字列的权重:
SELECT
processed_input AS input,
model.weight AS input_weight
FROM
ml.WEIGHTS(MODEL flights.arrdelay) AS model
- 使用以下查询可以获得的每个数字列的缩放比例:
SELECT
input, min, max, mean, stddev
FROM
ml.FEATURE_INFO(MODEL flights.arrdelay) AS model
- 使用该查询可以获得的每个分类列的词汇和权重(如果您不熟悉 UNNEST,请参阅本文):
SELECT
processed_input AS input,
model.weight AS input_weight,
category.category AS category_name,
category.weight AS category_weight
FROM
ml.WEIGHTS(MODEL flights.arrdelay) AS model,
UNNEST(category_weights) AS category
假设您已经将所有这三个查询的结果读入三个独立的 Pandas 数据帧,下面是一个计算预测的函数:
我正在做的是:我遍历每个数字列,并找到与该列相关的权重。然后,我取出平均值和标准差,用它们来调整输入值。两者的乘积就是与本专栏相关的贡献。然后,我遍历分类列。对于每个分类列,该列采用的每个值都有一个单独的权重。因此,我找到了与输入值相关联的权重,这就是贡献。所有贡献的总和就是预测。
上面的代码假设您训练了一个回归模型。如果你训练了一个分类模型,你需要对预测应用一个逻辑函数来得到概率(为了避免溢出,在 GitHub 上处理 pred < -500 as zero):
prob = (1.0/(1 + np.exp(-pred)) if (-500 < pred) else 0)
Here is an example of predicting the arrival delay of a specific flight:
rowdict = {
'carrier' : 'AA',
'origin': 'DFW',
'dest': 'LAX',
'dep_delay': -3,
'taxi_out': 18,
'distance': 1235
}
predicted_arrival_delay = compute_prediction(
rowdict, numeric_weights, scaling_df, categorical_weights)
This yields the following columns and their contributions:
col=dep_delay wt=36.5569545237 scaled_value=-0.329782492822 contrib=-12.0558435928
col=taxi_out wt=8.15557957221 scaled_value=0.213461991601 contrib=1.74090625815
col=distance wt=-1.88324519311 scaled_value=0.672196648431 contrib=-1.26591110699
col=__INTERCEPT__ wt=1.09017737502 scaled_value=1.0 contrib=1.09017737502
col=carrier wt=-0.0548843604154 value=AA contrib=-0.0548843604154
col=origin wt=0.966535564037 value=DFW contrib=0.966535564037
col=dest wt=1.26816262538 value=LAX contrib=1.26816262538
The total of the contributions is -8.31 minutes, which matches the batch prediction value, confirming that the code is correct.
The full code for this article is 的概率)。尽情享受吧!
如何在 OpenShift 上使用 Flask、uWSGI、NGINX 和 Docker 进行快速原型制作
这篇文章将详细介绍将你的原型放入 Docker 容器的技术方面(带有参考代码), Docker 容器可以使用任意用户 id 在 OpenShift 上运行。
如果你像我一样,你喜欢一头扎进去,然后弄清楚它是如何工作的。为此,请随意查看 源代码 和 图像 然后回来理解它是如何工作的。
对于我收集的许多数据科学原型,它们通常属于基本的 Flask 应用程序。就我个人而言,比起 Django,我更喜欢 Flask,因为同样的原因,在设计乐高雕塑时,我更喜欢单独的乐高积木,而不是 BURP 和 LURP。如果你感兴趣的话,这篇关于 Flask vs Django 的博客很好地展示了 web 框架的差异。
LEGO Brick, which do you prefer using when building?
为了在一个我们可以从对原型输出感兴趣的涉众那里获得反馈的环境中建立并运行原型,我们需要一个 web 服务器。为此,我们需要在网络上的真实 web 服务器中运行 Flask 应用程序。输入 NGINX 和 uWSGI!其他帖子详细介绍了这三个组件如何协同工作来服务您的 webapp,我们将探索如何让它在 Docker 和 OpenShift 中运行。
A Guide to Scaling Machine Learning Models in Production
有很多 Docker 教程,但是没有几个遵循 Docker 最佳实践。要让 Docker 映像在 OpenShift 这样的 Kubernetes 环境中运行,可能需要遵循更严格的最佳实践。我最近遇到的主要最佳实践是,您的容器应该作为“非根”(或任意)用户启动!
现在,让我们来看看让您的原型在这种类型的环境中运行所需的最低要求。为此,我们将定义一组配置文件,然后将它们合并到一个 Docker 映像中。
项目布局
$ tree .
.
├── deploy.sh
├── deployment
│ ├── docker-entrypoint.sh
│ ├── Dockerfile
│ ├── nginx.conf
│ ├── supervisord.conf
│ └── uwsgi.ini
└── src
├── __init__.py
├── static
│ └── index.html
└── wsgi.py
上述结构将所有的容器信息推送到“ deployment 文件夹,而所有的 Python 代码和资产作为伪模块放入“ src 文件夹。想象您的数据科学代码位于 src 下,并在 wsgi.py 文件中定义端点。在以后的文章中,我将介绍如何用 cookiecutter 标准化项目布局。
在这个结构中,要在调试模式下运行 Flask,我们可以简单地从命令行执行以下命令
$ python ./src/wsgi.py
在尝试将 Flask 应用程序放入 uWSGI、NGINX 和 Docker 映像之前,您应该使用上述命令进行所有本地验证测试,以确保 Flask 应用程序可以按预期运行和操作。
uWSGI 配置
在这篇文章中,我们想在 OpenShift 上运行 NGINX 和 uWSGI 中的 Flask 应用程序。所以我们需要首先配置 uWSGI,让它能够找到 Flask 应用程序。这在中完成。/deployment/uwsgi.ini 配置文件。
[uwsgi]
chdir=/opt/repo/src
chdir2=/opt/repo/src
master = true
module=wsgi
callable=app
buffer-size=65535
lazy=true
socket = /run/uwsgi.sock
这里我们定义了标准的 uWSGI 选项。要注意的主要选项是模块和可调用。这两个参数指示 uWSGI 服务在哪里寻找要执行的 Python 代码。在我们的例子中,我们告诉它在“ /opt/repo/src ”文件夹中查找“ wsgi.py ”文件,以及该文件中的“ app ”变量(这是我们的主 Flask 可调用变量)。我们还为这个服务明确指定了套接字文件的位置,这需要与 NGINX 配置相匹配。
NGINX 配置
接下来,我们需要告诉 NGINX 它可以在哪里找到 uWSGI 套接字文件,这样当新的请求进入它正在监听的端口时,它知道如何相应地路由请求。这是在中完成的。/deployment/nginx.conf 文件。
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
client_body_temp_path /spool/nginx/client_temp 1 2;
fastcgi_temp_path /spool/nginx/fastcgi_temp 1 2;
proxy_temp_path /spool/nginx/proxy_temp 1 2;
scgi_temp_path /spool/nginx/scgi_temp 1 2;
uwsgi_temp_path /spool/nginx/uwsgi_temp 1 2;
server {
listen 8080;
server_name localhost;
access_log /var/log/nginx/access.log;
location / {
try_files $uri @app;
}
location @app {
include uwsgi_params;
uwsgi_pass unix:///run/uwsgi.sock;
}
location /static {
alias /opt/repo/src/static;
expires 1d;
}
}
}
在这里,我们明确定义了 NGINX 服务应该在哪里创建日志、临时文件,以及监听哪些端口。因为 OpenShift 将以任意用户的身份启动这个映像,所以我们不能使用任何低于 1024 的端口号(即特权端口,比如标准的 HTTP 80 或 HTTPS 443)。所以我们告诉服务监听端口 8080 。然后在位置路由中,我们告诉 NGINX 将所有请求路由到 uWSGI 套接字和 Python 应用程序(即“ @app ”),除了任何静态文件,它们应该直接进入磁盘上的那个文件夹。
超级用户配置
我们需要配置的最后一件事是让所有这些在单个映像中运行的方法。Docker 最佳实践强烈建议每个图像一个应用程序,但是在我们的例子中,我们需要 uWSGI 来运行我们的 Python 代码,NGINX 路由到 uWSGI。因此,我们将使用启动 Supervisord 的技巧来处理多个并发服务。这是在中完成的。/deployment/super visor . conf文件。
[unix_http_server]
file=/run/supervisor.sock
chmod=0770
[supervisord]
nodaemon=true
pidfile=/run/pid/supervisord.pid
logfile=/var/log/supervisor/supervisord.log
childlogdir=/var/log/supervisor
logfile_maxbytes=50MB
logfile_backups=1
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///run/supervisor.sock
[program:nginx]
command=/usr/sbin/nginx -g "daemon off;" -c /etc/nginx/nginx.conf
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:uwsgi]
command=/usr/local/bin/uwsgi --ini /etc/uwsgi/apps-enabled/uwsgi.ini
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
在这里,我们明确了要为每个“服务”执行哪些命令,以及我们前面详述的配置文件的位置。通常,您可以以 root 用户身份启动 supervisord 或 systemd,并切换用户以执行特定的服务。然而,对于 OpenShift 上的任意用户 id,我们需要允许这些服务作为“root”组中的任何用户启动。这就是为什么我们没有在配置文件中指定任何用户参数,而是将日志路由到 /dev/stdout (这将允许它们在映像运行时显示在 Docker 日志文件中)。
码头入口点
配置文件都设置好了,我们只需要告诉 Docker 运行映像时要执行什么。在一些 Python 应用程序中使用任意用户 id 给这些计划带来了麻烦。此问题在问题 10496 中有详细记录。
好消息是有一个简单的解决方法。每次运行 Docker 映像时,我们只需要添加一个检查来验证任意用户在 /etc/passwd 文件中有一个条目。这在中完成。/deployment/docker-entry point . sh文件。
#!/bin/bash
set -e
# if the running user is an Arbitrary User ID
if ! whoami &> /dev/null; then
# make sure we have read/write access to /etc/passwd
if [ -w /etc/passwd ]; then
# write a line in /etc/passwd for the Arbitrary User ID in the 'root' group
echo "${USER_NAME:-default}:x:$(id -u):0:${USER_NAME:-default} user:${HOME}:/sbin/nologin" >> /etc/passwd
fi
fiif [ "$1" = 'supervisord' ]; then
exec /usr/bin/supervisord
fi
exec "$@"
但是,为了使其正确工作,我们需要设置 Docker 映像,以允许所有用户对/etc/passwd 文件进行写访问。一旦准备就绪,我们指定第二个条件来捕捉何时执行“supervisord”应用程序(它将依次执行 uWSGI 和 NGINX)作为任意用户 id,并将所有日志传送到/dev/stdout。
Dockerfile 文件
部署这个原型的最后一步是告诉 Docker 如何用我们上面指定的配置文件构建和配置映像。最好的部分是,一旦我们这样做了一次,我们就可以在多个项目的未来迭代中重用这个结构/映像!这是在中完成的。/deployment/Dockerfile 文件。
# Use the standard Nginx image from Docker Hub
FROM nginx
ENV HOME=/opt/repo
# install python, uwsgi, and supervisord
RUN apt-get update && apt-get install -y supervisor uwsgi python python-pip procps vim && \
/usr/bin/pip install uwsgi==2.0.17 flask==1.0.2
# Source code file
COPY ./src ${HOME}/src
# Copy the configuration file from the current directory and paste
# it inside the container to use it as Nginx's default config.
COPY ./deployment/nginx.conf /etc/nginx/nginx.conf
# setup NGINX config
RUN mkdir -p /spool/nginx /run/pid && \
chmod -R 777 /var/log/nginx /var/cache/nginx /etc/nginx /var/run /run /run/pid /spool/nginx && \
chgrp -R 0 /var/log/nginx /var/cache/nginx /etc/nginx /var/run /run /run/pid /spool/nginx && \
chmod -R g+rwX /var/log/nginx /var/cache/nginx /etc/nginx /var/run /run /run/pid /spool/nginx && \
rm /etc/nginx/conf.d/default.conf
# Copy the base uWSGI ini file to enable default dynamic uwsgi process number
COPY ./deployment/uwsgi.ini /etc/uwsgi/apps-available/uwsgi.ini
RUN ln -s /etc/uwsgi/apps-available/uwsgi.ini /etc/uwsgi/apps-enabled/uwsgi.ini
COPY ./deployment/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
RUN touch /var/log/supervisor/supervisord.log
EXPOSE 8080:8080
# setup entrypoint
COPY ./deployment/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
# https://github.com/moby/moby/issues/31243#issuecomment-406879017
RUN ln -s /usr/local/bin/docker-entrypoint.sh / && \
chmod 777 /usr/local/bin/docker-entrypoint.sh && \
chgrp -R 0 /usr/local/bin/docker-entrypoint.sh && \
chown -R nginx:root /usr/local/bin/docker-entrypoint.sh
# https://docs.openshift.com/container-platform/3.3/creating_images/guidelines.html
RUN chgrp -R 0 /var/log /var/cache /run/pid /spool/nginx /var/run /run /tmp /etc/uwsgi /etc/nginx && \
chmod -R g+rwX /var/log /var/cache /run/pid /spool/nginx /var/run /run /tmp /etc/uwsgi /etc/nginx && \
chown -R nginx:root ${HOME} && \
chmod -R 777 ${HOME} /etc/passwd
# enter
WORKDIR ${HOME}
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["supervisord"]
这篇文章并不打算遍历 Dockerfile 文件的每一行。只需知道我们是从官方的 NGINX 构建(已经安装了 NGINX)开始,通过 PIP 添加几个 Python 包,并显式设置 NGINX、uWSGI 和 Supervisord 在执行期间需要接触的所有文件夹的权限,以便“root”组中的任意用户 id 拥有它需要的权限。最后,我们告诉映像查看默认运行的每个映像上的“ docker-entrypoint.sh ”文件以启动“ supervisord ”。
建立码头形象
要将上述所有构建块组合在一起,我们只需执行 Docker 映像的构建。这可以在您的本地计算机上通过以下方式完成:
$ cd {{project root directory}}
$ docker build -f ./deployment/Dockerfile -t prototype:latest .
在上面的例子中,我们需要从根目录执行构建,这样构建上下文就可以访问两个**。/部署和。/src** 文件夹。
测试 Docker 图像作为任意用户 id
成功构建 Docker 映像是一回事,让它以任意用户 id 在 OpenShift 中运行则完全是另一回事。好消息是,我们可以在本地机器上用用户标志测试这一点
$ docker run -p 8080:8080 -u 112233 prototype:latest
在上面的例子中,我们选择了“ 112233 的任意用户 id,但是使用什么号码并不重要。您应该能够将其更改为任何数值,并且您的图像应该仍然能够正确运行(因此在 OpenShift 的任意用户 id 中是“任意的”)。
此外,我们将本地机器上的端口 8080 路由到容器内的 NGINX 服务,这意味着我们应该能够在本地机器上打开 web 浏览器,并在这些端点查看我们的简单原型:
- http://localhost:8080/static/index . html:从 NGINX 加载静态 HTML 页面
- http://localhost:8080/ :从 Flask 加载通用 home 端点
- http://localhost:8080/Echo _ request:将请求头从 Flask 中回显给调用者
Docker 映像故障排除
如果上述方法不起作用,您需要在本地进行调试,以确定您的应用程序需要什么权限,并相应地修改 docker 文件。要调试映像,可以用以下内容覆盖 entrypoint 命令:
$ docker run -it -p 8080:8080 -u 0 prototype:latest /bin/bash
这将使您以 root 用户身份进入交互式 bash 命令提示符,以便您在本地挖掘映像内部的问题。您可能还需要切换-u 参数中使用的用户 id。使用 CTRL+D 或退出终止图像。
要测试 supervisord 的执行情况,可以在 bash 命令提示符下执行以下命令来查看日志,以确定问题可能是什么。
$ supervisorctl [start|stop|restart] nginx # NGINX service
$ supervisorctl [start|stop|restart] uwsgi # uWSGI service
有时,您的构建状态会因为一堆您不再需要的映像而变得“脏”,从而消耗 Docker 守护进程的磁盘空间。要清理这种情况,您可以运行:
$ docker system prune
部署到容器存储库
在我们修改了任意用户 id 几次,并且对单个图像原型的执行有信心之后,是时候将它推到容器存储库中进行部署了。
在 OpenShift 中有多种构建和部署映像的方法。在这篇文章中,我们不打算讨论部署管道。相反,我们需要做的只是将我们的映像推送到一个容器存储库(比如 Docker Hub),然后指示 OpenShift 提取并部署该映像。
首先,我们需要对我们的容器存储库进行认证。其中 yourhubusername 是你在容器库中的用户名,而youremail@company.com是你在容器库中指定的电子邮件地址。
$ docker login --username=yourhubusername --email=youremail@example.com
然后构建/标记/推送我们图像到容器存储库。其中 yourhubusername 是您在容器存储库上的用户名,您向其进行了身份验证。
$ docker build -f ./deployment/Dockerfile -t prototype:latest .
$ docker tag $(docker images | grep ^prototype |awk '{print $3}') yourhubusername/prototype:latest
$ docker push yourhubusername/prototype:latest
现在图像应该在您的容器存储库中了!如果您使用的是 public Docker Hub,您可以通过此 URL 导航到您的存储库(在鉴定之后),并查看您的新图像:
部署到 OpenShift
接下来,让我们告诉 OpenShift 从该映像进行部署。为此,我们将使用open shift CLI工具。首先,我们需要认证我们的 OpenShift 服务器。只需用您的 OpenShift 服务器替换 URL,用您的 OpenShift 令牌替换< MY_TOKEN >。
$ oc login https://my.openshift-servername.com --token=<MY_TOKEN>
为了从 CLI 创建部署,我们只需要告诉 OpenShift 在哪里定位我们的 Docker 映像。有关 OpenShift 配置的详细信息,请访问他们的部署如何工作页面。本质上,只需将它指向我们刚刚创建的容器存储库上的图像。
$ oc new-app yourhubusername/prototype
接下来,我们需要告诉 OpenShift 添加一条路线,这样我们的 web 流量就可以到达我们的图像。在下面,我们告诉它将流量路由到“prototype.example.com ”,以使用 TCP 8080 端口上的“prototype”服务。这有效地告诉 OpenShift 如何将流量路由到我们刚刚创建的 NGINX 映像。
$ oc create route edge --service=prototype --hostname=prototype.example.com --port=8080-tcp
现在,您应该能够导航到 hostname:port 组合,以任意用户身份查看在 OpenShift 上运行的应用程序!
神奇的迭代时间
现在我们已经完成了所有的配置和构建,我们可以快速迭代原型了!因为我们的 over 文件只是从复制了我们的 Python 代码。/src 文件夹中,要更新我们的映像,我们只需确保我们的新代码在中。/src 文件夹,并使用我们的 debug Flask 命令进行本地测试:
$ python ./src/wsgi.py
一旦我们对新功能感到满意,我们就可以用一个简单的来构建和推送图像。/deploy.sh 命令:
$ bash ./deploy.sh yourhubusername prototype latest
一旦该命令完成,您在 OpenShift 中的 URL 将对服务执行滚动更新,并在短短 5 分钟内在您的原型中展示您的新功能!
源代码回购
这篇文章中引用的代码和配置的一个例子在我的 GitHub 页面这里 。
如何使用 scikit 进行无服务器机器学习-在 Google Cloud ML 引擎上学习
在 Google 云平台上,Cloud ML Engine 提供无服务器的机器学习,用于训练、超参数优化和预测。直到最近,这还只是针对 TensorFlow 的。不过最近,该团队已经为 scikit-learn 实现了所有三种功能。在这篇文章中,我将带它兜一圈。
问题是在给定一些怀孕信息的情况下预测婴儿的体重。这是我用 TensorFlow 端到端解决的一个问题,在我为 GCP NEXT 2018 开发的训练营中。现在让我来谈谈 scikit-learn。跟着我一起看看 GitHub 上的这个 Jupyter 笔记本。
输入数据,从 BigQuery 到 Pandas
输入数据在 BigQuery 中,我将整个数据集的 1/1000 放入熊猫数据帧:
**def** query_to_dataframe(query):
**import** **pandas** **as** **pd**
**import** **pkgutil**
privatekey = pkgutil.get_data(KEYDIR, 'privatekey.json')
**print**(privatekey[:200])
**return** pd.read_gbq(query,
project_id=PROJECT,
dialect='standard',
private_key=privatekey)
**def** create_dataframes(frac):
*# small dataset to fit into memory*
**if** frac > 0 **and** frac < 1:
sample = " AND RAND() < {}".format(frac)
**else**:
sample = ""
train_query, eval_query = create_queries()
train_query = "{} {}".format(train_query, sample)
eval_query = "{} {}".format(eval_query, sample)
train_df = query_to_dataframe(train_query)
eval_df = query_to_dataframe(eval_query)
**return** train_df, eval_dftrain_df, eval_df = create_dataframes(0.001)
如果我使用较大的虚拟机,就有可能使用完整的数据集,我将在最后一步进行服务培训时这样做。然而,对于开发模型来说,拥有一个小的数据集是很有帮助的。
培训 sci kit-学习模型
为了训练模型,我首先编写了一个函数来获取用于训练的 x 和 y(我将它们称为特征和标签):
**def** input_fn(indf):
**import** **copy**
**import** **pandas** **as** **pd**
df = copy.deepcopy(indf)
*# one-hot encode the categorical columns*
df["plurality"] = df["plurality"].astype(pd.api.types.CategoricalDtype(
categories=["Single","Multiple","1","2","3","4","5"]))
df["is_male"] = df["is_male"].astype(pd.api.types.CategoricalDtype(
categories=["Unknown","false","true"]))
*# features, label*
label = df['label']
**del** df['label']
features = pd.get_dummies(df)
**return** features, label
然后,我创建了一个 RandomForestRegressor,并将 x 和 y 传递给它的 fit()方法:
**from** **sklearn.ensemble** **import** RandomForestRegressor
estimator = RandomForestRegressor(max_depth=5, n_estimators=100, random_state=0)
estimator.fit(train_x, train_y)
通过在评估数据帧上调用 predict()并计算 RMSE,可以评估模型的性能:
**import** **numpy** **as** **np**
eval_x, eval_y = input_fn(eval_df)
eval_pred = estimator.predict(eval_x)
**print**(eval_pred[1000:1005])
**print**(eval_y[1000:1005])
**print**(np.sqrt(np.mean((eval_pred-eval_y)*(eval_pred-eval_y))))
将教练打包成一个包
代码运行后,我将以下 Jupyter 魔术添加到笔记本的单元格中,将单元格写出到 Python 文件中:
*#%writefile -a babyweight/trainer/model.py*
我还将许多硬编码的数字(比如随机森林中的树的数量)作为命令行参数。通过让它们成为命令行参数,我可以对它们进行超参数调优。
我在本地测试了这个包,仍然是在 1/1000 的数据集上:
%bash
export PYTHONPATH=${PYTHONPATH}:${PWD}/babyweight
python -m trainer.task \
--bucket=${BUCKET} --frac=0.001 --job-dir=gs://${BUCKET}/babyweight/sklearn --projectId $PROJECT
无服务器培训
关于 Cloud ML Engine 的培训就像提交 Python 包一样简单:
RUNTIME_VERSION="1.8"
PYTHON_VERSION="2.7"
JOB_NAME=babyweight_skl_$(date +"%Y%m**%d**_%H%M%S")
JOB_DIR="gs://$BUCKET/babyweight/sklearn/${JOBNAME}"
gcloud ml-engine jobs submit training $JOB_NAME \
--job-dir $JOB_DIR \
--package-path $(pwd)/babyweight/trainer \
--module-name trainer.task \
--region us-central1 \
--runtime-version=$RUNTIME_VERSION \
--python-version=$PYTHON_VERSION \
-- \
--bucket=${BUCKET} --frac=0.1 --projectId $PROJECT
这一次,我在全部数据集的 1/10 上进行训练。为了在完整数据集上训练,我需要一台更大的机器。我可以通过更改为自定义层来做到这一点:
%writefile largemachine.yaml
trainingInput:
scaleTier: CUSTOM
masterType: large_model
并传入上面的配置文件:
RUNTIME_VERSION="1.8"
PYTHON_VERSION="2.7"
JOB_NAME=babyweight_skl_$(date +"%Y%m**%d**_%H%M%S")
JOB_DIR="gs://$BUCKET/babyweight/sklearn/${JOBNAME}"
gcloud ml-engine jobs submit training $JOB_NAME \
--job-dir $JOB_DIR \
--package-path $(pwd)/babyweight/trainer \
--module-name trainer.task \
--region us-central1 \
--runtime-version=$RUNTIME_VERSION \
--python-version=$PYTHON_VERSION \
--scale-tier=CUSTOM \
--config=largemachine.yaml \
-- \
--bucket=${BUCKET} --frac=1 --projectId $PROJECT --maxDepth 8 --numTrees 90
我如何获得最大深度和最大树数?为此,我做了超参数调整。
超参数调谐
对于 ML 引擎中的超参数调整,在评估后写出一个摘要度量。这是代码,假设您有一个名为 rmse 的变量,它保存最终的评估度量:
## this is for hyperparameter tuning
hpt = hypertune.HyperTune()
hpt.report_hyperparameter_tuning_metric(
hyperparameter_metric_tag=’rmse’,
metric_value=rmse,
global_step=0)
要提交超参数优化作业,请使用要优化的参数编写一个配置文件:
%writefile hyperparam.yaml
trainingInput:
hyperparameters:
goal: MINIMIZE
maxTrials: 100
maxParallelTrials: 5
hyperparameterMetricTag: rmse
params:
- parameterName: maxDepth
type: INTEGER
minValue: 2
maxValue: 8
scaleType: UNIT_LINEAR_SCALE
- parameterName: numTrees
type: INTEGER
minValue: 50
maxValue: 150
scaleType: UNIT_LINEAR_SCALE
然后,通过以下方式将其传递给 ML 引擎:
--config=hyperparam.yaml
试验的输出将开始填充,如果您查看 GCP web 控制台,将会列出具有最低 RMSE 的试验及其运行时参数。
部署模型
经过培训后,可以部署 scitkit 学习模型:
gcloud alpha ml-engine versions create ${MODEL_VERSION} --model ${MODEL_NAME} --origin ${MODEL_LOCATION} \
--framework SCIKIT_LEARN --runtime-version 1.8 --python-version=2.7
部署的模型有一个端点,您可以访问它来获得预测:
**from** **googleapiclient** **import** discovery
**from** **oauth2client.client** **import** GoogleCredentials
**import** **json**
credentials = GoogleCredentials.get_application_default()
api = discovery.build('ml', 'v1', credentials=credentials)
request_data = {'instances':
*# [u'mother_age', u'gestation_weeks', u'is_male_Unknown', u'is_male_0',*
*# u'is_male_1', u'plurality_Single', u'plurality_Multiple',*
*# u'plurality_1', u'plurality_2', u'plurality_3', u'plurality_4',*
*# u'plurality_5']*
[[24, 38, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[34, 39, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0]]
}
parent = 'projects/**%s**/models/**%s**/versions/**%s**' % (PROJECT, 'babyweight', 'skl')
response = api.projects().predict(body=request_data, name=parent).execute()
**print** "response={0}".format(response)
目前,请求必须是文本行的形式,并且必须是预处理的数据。自然,这就对 scikit-learn 模型的可操作性提出了一些警告。
再说一遍,我的完整代码在 GitHub 上。编码快乐!
如何用 CNN,TensorFlow,单词嵌入做文本分类
假设我给你一篇文章的标题“Twitter Bootstrap 的惊人扁平版本”,并问你这篇文章出现在哪个出版物上:纽约时报、TechCrunch 或 GitHub。你的猜测是什么?一篇题为“最高法院审理党派选区重大案件”的文章怎么样?
你猜到 GitHub 和纽约时报了吗?为什么?像 Twitter 和 Major 这样的词很可能出现在任何出版物中,但像 Twitter Bootstrap 和 Supreme Court 这样的词序列更有可能分别出现在 GitHub 和纽约时报中。我们能训练一个神经网络来学习这个吗?
注意:评估者现在已经进入核心张量流。 更新了使用 tf.estimator 而不是 tf.contrib.learn.estimator 的代码现已在 GitHub——以更新后的代码为起点。
创建数据集
机器学习就是从例子中学习。为了了解给定标题的文章的可能来源,我们需要大量文章标题及其来源的示例。尽管它存在严重的选择偏差(因为只包括 HN 书呆子成员感兴趣的文章),但黑客新闻文章的 BigQuery 公共数据集是这一信息的合理来源。
query="""
SELECT source, REGEXP_REPLACE(title, '[^a-zA-Z0-9 $.-]', ' ') AS title FROM
(SELECT
ARRAY_REVERSE(SPLIT(REGEXP_EXTRACT(url, '.*://(.[^/]+)/'), '.'))[OFFSET(1)] AS source,
title
FROM
`bigquery-public-data.hacker_news.stories`
WHERE
REGEXP_CONTAINS(REGEXP_EXTRACT(url, '.*://(.[^/]+)/'), '.com$')
AND LENGTH(title) > 10
)
WHERE (source = 'github' OR source = 'nytimes' OR source = 'techcrunch')
"""
traindf = bq.Query(query + " AND MOD(ABS(FARM_FINGERPRINT(title)),4) > 0").execute().result().to_dataframe()
evaldf = bq.Query(query + " AND MOD(ABS(FARM_FINGERPRINT(title)),4) = 0").execute().result().to_dataframe()
本质上,我从 BigQuery 中的黑客新闻故事数据集中提取 URL 和标题,并将其分离到一个训练和评估数据集中(完整代码请参见 Datalab 笔记本)。可能的标签是 github、纽约时报或 techcrunch。以下是生成的数据集的外观:
Training dataset
我将两只熊猫的数据帧写成 CSV 文件(总共有 72,000 个训练样本,大约平均分布在纽约时报、github 和 techcrunch 上)。
创造词汇
我的训练数据集由标签(“源”)和单个输入列(“标题”)组成。然而,标题不是数字,神经网络需要数字输入。因此,我们需要将文本输入列转换为数字。怎么会?
最简单的方法是对标题进行一次性编码。假设数据集中有 72,000 个唯一的标题,我们将得到 72,000 列。如果我们随后就此训练一个神经网络,这个神经网络基本上必须记住标题——没有进一步推广的可能。
为了让网络通用化,我们需要将标题转换成数字,使得相似的标题以相似的数字结束。一种方法是找到标题中的单个单词,并将这些单词映射到唯一的数字。然后,有相同单词的标题在这部分序列中会有相似的数字。训练数据集中的唯一单词集被称为词汇。
假设我们有四个标题:
lines = ['Some title',
'A longer title',
'An even longer title',
'This is longer than doc length']
因为这些标题都有不同的长度,所以我会用一个虚拟单词来填充短标题,并截断很长的标题。这样,我就可以处理长度相同的标题了。
我可以使用下面的代码创建词汇表(这并不理想,因为词汇表处理器将所有内容都存储在内存中;对于更大的数据集和更复杂的预处理,比如合并停用词和不区分大小写, tf.transform 是更好的解决方案——这是另一篇博文的主题):
**import** **tensorflow** **as** **tf**
**from** **tensorflow.contrib** **import** lookup
**from** **tensorflow.python.platform** **import** gfile
MAX_DOCUMENT_LENGTH = 5
PADWORD = 'ZYXW'
*# create vocabulary*
vocab_processor = tf.contrib.learn.preprocessing.VocabularyProcessor(MAX_DOCUMENT_LENGTH)
vocab_processor.fit(lines)
**with** gfile.Open('vocab.tsv', 'wb') **as** f:
f.write("{}**\n**".format(PADWORD))
**for** word, index **in** vocab_processor.vocabulary_._mapping.iteritems():
f.write("{}**\n**".format(word))
N_WORDS = len(vocab_processor.vocabulary_)
在上面的代码中,我会用一个填充词填充简短的标题,我希望它不会出现在实际的文本中。标题将被填充或截短至 5 个单词的长度。我传入训练数据集(上面示例中的“lines”),然后写出结果词汇。词汇变成了:
ZYXW
A
even
longer
title
This
doc
is
Some
An
length
than
<UNK>
注意,我添加了 padword,词汇处理器在这组行中找到了所有独特的单词。最后,在评估/预测期间遇到的不在训练数据集中的单词将被替换为,因此这也是词汇表的一部分。
根据上面的词汇表,我们可以将任何标题转换成一组数字:
table = lookup.index_table_from_file(
vocabulary_file='vocab.tsv', num_oov_buckets=1, vocab_size=None, default_value=-1)
numbers = table.lookup(tf.constant(**'Some title'**.split()))
**with** tf.Session() **as** sess:
tf.tables_initializer().run()
**print** "{} --> {}".format(lines[0], numbers.eval())
上面的代码将查找单词“Some”和“title ”,并根据词汇返回索引[8,4]。当然,在实际的训练/预测图中,我们还需要确保填充/截断。让我们看看接下来该怎么做。
文字处理
首先,我们从行(每行是一个标题)开始,将标题拆分成单词:
*# string operations*
titles = tf.constant(lines)
words = tf.string_split(titles)
这导致:
titles= ['Some title' 'A longer title' 'An even longer title'
'This is longer than doc length']
words= SparseTensorValue(indices=array([[0, 0],
[0, 1],
[1, 0],
[1, 1],
[1, 2],
[2, 0],
[2, 1],
[2, 2],
[2, 3],
[3, 0],
[3, 1],
[3, 2],
[3, 3],
[3, 4],
[3, 5]]), values=array(['Some', 'title', 'A', 'longer', 'title', 'An', 'even', 'longer',
'title', 'This', 'is', 'longer', 'than', 'doc', 'length'], dtype=object), dense_shape=array([4, 6]))
TensorFlow 的 string_split()函数最终创建了一个 SparseTensor。谈论一个过于有用的 API。但是我不希望自动创建映射,所以我将把稀疏张量转换成密集张量,然后从我自己的词汇表中查找索引:
*# string operations*
titles = tf.constant(lines)
words = tf.string_split(titles)
densewords = tf.sparse_tensor_to_dense(words, default_value=PADWORD)
numbers = table.lookup(densewords)
现在,densewords 和 numbers 与预期的一样(注意填充的 PADWORD:
dense= [['Some' 'title' 'ZYXW' 'ZYXW' 'ZYXW' 'ZYXW']
['A' 'longer' 'title' 'ZYXW' 'ZYXW' 'ZYXW']
['An' 'even' 'longer' 'title' 'ZYXW' 'ZYXW']
['This' 'is' 'longer' 'than' 'doc' 'length']]
numbers= [[ 8 4 0 0 0 0]
[ 1 3 4 0 0 0]
[ 9 2 3 4 0 0]
[ 5 7 3 11 6 10]]
还要注意,数字矩阵具有数据集中最长标题的宽度。因为这个宽度会随着处理的每一批而变化,所以它并不理想。为了保持一致,让我们将其填充到 MAX_DOCUMENT_LENGTH,然后将其截断:
padding = tf.constant([[0,0],[0,MAX_DOCUMENT_LENGTH]])
padded = tf.pad(numbers, padding)
sliced = tf.slice(padded, [0,0], [-1, MAX_DOCUMENT_LENGTH])
这会创建一个 batchsize x 5 矩阵,其中较短的标题用零填充:
padding= [[0 0]
[0 5]]
padded= [[ 8 4 0 0 0 0 0 0 0 0 0]
[ 1 3 4 0 0 0 0 0 0 0 0]
[ 9 2 3 4 0 0 0 0 0 0 0]
[ 5 7 3 11 6 10 0 0 0 0 0]]
sliced= [[ 8 4 0 0 0]
[ 1 3 4 0 0]
[ 9 2 3 4 0]
[ 5 7 3 11 6]]
在上面的例子中,我使用的 MAX_DOCUMENT_LENGTH 为 5,这样我可以向您展示正在发生的事情。在真实数据集中,标题长于 5 个单词。所以,在我会用
MAX_DOCUMENT_LENGTH = 20
切片矩阵的形状将是 batchsize x MAX_DOCUMENT_LENGTH,即 batchsize x 20。
把…嵌入
既然我们的单词已经被数字取代,我们可以简单地进行一次性编码,但这将导致非常广泛的输入——在标题数据集中有数千个独特的单词。一个更好的方法是减少输入的维度——这是通过嵌入层来实现的(参见完整代码):
EMBEDDING_SIZE = 10
embeds = tf.contrib.layers.embed_sequence(sliced,
vocab_size=N_WORDS, embed_dim=EMBEDDING_SIZE)
一旦有了嵌入,我们现在就有了标题中每个单词的表示。嵌入的结果是一个 batch SIZE x MAX_DOCUMENT_LENGTH x EMBEDDING_SIZE 张量,因为一个标题由 MAX _ DOCUMENT _ LENGTH 个单词组成,每个单词现在用 EMBEDDING _ SIZE 个数字表示。(养成在 TensorFlow 代码的每一步计算张量形状的习惯——这将帮助你理解代码在做什么,维度意味着什么)。
如果我们愿意,我们可以简单地将嵌入的单词连接到一个深度神经网络中,训练它,然后开始我们的快乐之路。但是仅仅使用单词本身并没有利用单词序列具有特定含义的事实。毕竟,“最高法院”可以出现在许多场合,但是“最高法院”有更具体的含义。我们如何学习单词序列?
盘旋
学习序列的一种方法是不仅嵌入独特的单词,还嵌入二元模型(单词对)、三元模型(单词三元组)等。然而,对于相对较小的数据集,这开始变得类似于对数据集中的每个唯一单词进行一次性编码。
更好的方法是增加一个卷积层。卷积只是一种将移动窗口应用于输入数据并让神经网络学习应用于相邻单词的权重的方法。虽然在处理图像数据时更常见,但这是帮助任何神经网络了解附近输入之间的相关性的便捷方式:
WINDOW_SIZE = EMBEDDING_SIZE
STRIDE = int(WINDOW_SIZE/2)
conv = tf.contrib.layers.conv2d(embeds, 1, WINDOW_SIZE,
stride=STRIDE, padding='SAME') # (?, 4, 1)
conv = tf.nn.relu(conv) # (?, 4, 1)
words = tf.squeeze(conv, [2]) # (?, 4)
回想一下,嵌入的结果是一个 20 x 10 的张量(我们暂且忽略 batchsize 这里的所有操作都是一次对一个标题进行的)。我现在将 10x10 窗口中的加权平均值应用于标题的嵌入表示,将窗口移动 5 个单词(步幅=5),然后再次应用它。所以,我会有 4 个这样的卷积结果。然后,我对卷积结果应用非线性变换(relu)。
我现在有四个结果。我可以简单地将它们通过密集层连接到输出层:
n_classes = len(TARGETS)
logits = tf.contrib.layers.fully_connected(words, n_classes,
activation_fn=None)
如果你习惯于图像模型,你可能会感到惊讶,我用了卷积层,但没有最大池层。使用 maxpool 图层的原因是为了增加网络的空间不变性-直观地说,您希望找到一只猫,而不管这只猫在图像中的位置。然而,标题中的空间位置非常重要。很有可能《纽约时报》文章的标题与 GitHub 文章的标题有所不同。因此,我没有在这个任务中使用 maxpool 层。
给定 logit,我们可以通过执行 TARGETS[max(logit)]来找出源。在 TensorFlow 中,这是使用 tf.gather 完成的:
predictions_dict = {
'source': tf.gather(TARGETS, tf.argmax(logits, 1)),
'class': tf.argmax(logits, 1),
'prob': tf.nn.softmax(logits)
}
为了完整起见,我还发送了实际的类索引和每个类的概率。
培训和部署
代码都写好了(见完整代码这里),我就可以在 Cloud ML 引擎上训练它了:
OUTDIR=gs://${BUCKET}/txtcls1/trained_model
JOBNAME=txtcls_$(date -u +%y%m%d_%H%M%S)
echo $OUTDIR $REGION $JOBNAME
gsutil -m rm -rf $OUTDIR
gsutil cp txtcls1/trainer/*.py $OUTDIR
gcloud ml-engine jobs submit training $JOBNAME \
--region=$REGION \
--module-name=trainer.task \
--package-path=$(pwd)/txtcls1/trainer \
--job-dir=$OUTDIR \
--staging-bucket=gs://$BUCKET \
--scale-tier=BASIC --runtime-version=1.2 \
-- \
--bucket=${BUCKET} \
--output_dir=${OUTDIR} \
--train_steps=36000
数据集非常小,所以训练不到五分钟就结束了,我在评估数据集上获得了 73%的准确率。
然后,我可以将该模型作为微服务部署到云 ML 引擎:
MODEL_NAME="txtcls"
MODEL_VERSION="v1"
MODEL_LOCATION=$(gsutil ls \
gs://${BUCKET}/txtcls1/trained_model/export/Servo/ | tail -1)
gcloud ml-engine models create ${MODEL_NAME} --regions $REGION
gcloud ml-engine versions create ${MODEL_VERSION} --model \
${MODEL_NAME} --origin ${MODEL_LOCATION}
预言;预测;预告
为了让模型进行预测,我们可以向它发送一个 JSON 请求:
**from** **googleapiclient** **import** discovery
**from** **oauth2client.client** **import** GoogleCredentials
**import** **json**
credentials = GoogleCredentials.get_application_default()
api = discovery.build('ml', 'v1beta1', credentials=credentials,
discoveryServiceUrl='https://storage.googleapis.com/cloud-ml/discovery/ml_v1beta1_discovery.json')
request_data = {'instances':
[
{
'title': 'Supreme Court to Hear Major Case on Partisan Districts'
},
{
'title': 'Furan -- build and push Docker images from GitHub to target'
},
{
'title': 'Time Warner will spend $100M on Snapchat original shows and ads'
},
]
}
parent = 'projects/**%s**/models/**%s**/versions/**%s**' % (PROJECT, 'txtcls', 'v1')
response = api.projects().predict(body=request_data, name=parent).execute()
**print** "response={0}".format(response)
这会产生一个 JSON 响应:
response={u'predictions': [{u'source': u'nytimes', u'prob': [**0.777**5614857673645, 5.86951500736177e-05, 0.22237983345985413], u'class': 0}, {u'source': u'github', u'prob': [0.1087314561009407, **0.890**9648656845093, 0.0003036781563423574], u'class': 1}, {u'source': u'techcrunch', u'prob': [0.0021869686897844076, 1.563105769264439e-07, **0.997**8128671646118], u'class': 2}]}
经过训练的模型预测,最高法院的文章有 78%的可能性来自《纽约时报》。根据该服务,Docker 的文章 89%可能来自 GitHub,而时代华纳的文章 100%可能来自 TechCrunch。那是 3/3。
**资源:**所有代码都在 GitHub 这里:https://GitHub . com/Google cloud platform/training-data-analyst/tree/master/blogs/text classification
如何将闪亮的应用程序分类——第 1 部分
Docker : Representational Image
在我之前的帖子中,我写了如何在 AWS cloud 上托管一个 R shiny 应用程序。成功托管应用后,您的终端用户可以轻松访问该应用。但是,如果您的最终用户希望在他们的系统中本地托管应用程序,并可能对应用程序本身进行更改,该怎么办呢?
虽然我在上一篇文章中展示了一个“Hello world”版本,但在真实的工作环境中,R shiny 应用程序可能会变得非常复杂。一些闪亮的应用程序可以在后端运行复杂的算法,例如客户流失预测、设备故障概率、天气预测等。所有这些用 R 写的算法都调用了很多包。这些软件包中的一些可能与接收者系统中安装的 R 版本不兼容。
当我在真实环境中托管一个闪亮的应用程序时,我也面临着许多障碍,包括缺少数据文件、缺少工作空间、缺少闪亮的应用程序组件(UI。R &服务器。r ),当然更不用说各种缺失或不是“最新”的库包了。我希望有一种方法,负责开发 R shiny 应用程序的人只需给我一个 zip 文件,去掉所有依赖项,我所要做的就是解压缩并运行代码来托管应用程序。有些人可能会说这是痴心妄想,但事实并非如此。进入‘码头工人’。
什么是码头工人?
尽管软件工程人员可能已经知道了 Docker,但我将尝试用通俗的语言为不熟悉它的人解释。所以它开始了
想象你在地球上某个偏远的小镇。你希望用乐高搭建一个玩具建筑,但是你所在的城镇既没有乐高,也不知道如何把积木拼起来搭建一个建筑。
Image source : http://www.thinkgeek.com/product/edab/
输入您的朋友“码头工人”。码头工人已经用乐高积木预建了这座建筑,并将其包装在一个礼品盒中。谈论理想的圣诞礼物😉。现在你要做的就是打开包装,把它放在你的桌子上。Tada!!!你的愿望实现了。
你刚刚避免了哪些麻烦
购买正确的乐高积木,帮助你建造玩具建筑
将乐高积木拼在一起
故意拖延时间
现在让我们扩展乐高类比,更深入地研究 Docker。
乐高积木类似于您的代码及其依赖项(包、库)和环境(操作系统、软件版本、IDE)。
礼品盒是安全地包含所有组件的“容器”。
你打开包装并安全地把它放在桌子上就是代码的部署。
进行上述操作时,您的优势是
您不必安装该语言的最新版本或其包/库
您不必配置您的环境
你可以直接部署代码,是的,你节省了很多时间
好了,现在我们对 Docker 有了一个直观的概念,让我们更进一步,从技术上探索它。
引擎盖下的 Docker:
Docker 基本上是一个容器。容器是图像的运行实例。
Image 是运行代码的环境的快照。它包含操作系统、软件和库包。
dockerfile 文件有助于定义图像。这是创建图像的一系列步骤。它将包含加载哪些库包、从哪里复制文件以及将文件复制到哪个位置的详细信息。
下图恰当地说明了这一过程
Picture adapted from Slideshare
杰克·赖特写的关于码头工人的优秀教程可以在下面的链接中看到。
所以伙计们,Docker 帮助托管一个 R shiny 应用程序,如下图所示。多亏了 Docker,系统 B 成功托管了闪亮的应用程序。
Representational image of R shiny app hosted successfully
希望你喜欢我的文章。这里是第二部
你可以联系我
如何将一个闪亮的应用程序分类——第二部分
大家好!!
首先,我想为第二部分的出版延迟道歉。延迟的原因是第 2 部分的一些博客材料(命令、快照)在我的笔记本电脑中被意外删除了。
由于时间不够,而且我的工作性质变得更像数据科学,而不是开发工作/数据工程,不允许我将这些碎片拼凑起来,使之成为一篇有价值的文章。
然而,自从我读完第一部分后,我已经收到了很多读者关于第二部分的询问。我最终决定写第二部分。
所以,读者们,感谢你们的耐心。让我们从我在第 1 部分留下的地方开始阅读这篇文章。
好吧,那么,快速复习一下 Docker 及其概念。
什么是 Docker、Container 和 Dockerfile?
Docker 基本上是一个容器。容器是图像的运行实例。
Image 是运行代码的环境的快照。它包含操作系统、软件和库包。
Dockerfile 文件有助于定义图像。这是创建图像的一系列步骤。它将包含加载哪些库包、从哪里复制文件以及将文件复制到哪个位置的详细信息。
下图恰当地说明了这一过程
Picture adapted from Slideshare
如何将一个 R 闪亮的 App Dockerize
因此,这里有一系列的步骤来解释如何做到这一点。
第一步:安装 Oracle VM 虚拟箱
第二步:安装 Centos,也可以使用 ubuntu。
给出用户名和密码
你会看到如下图片
第三步:获取 docker CE for centos(https://docs . docker . com/engine/installation/Linux/docker-CE/centos/)
第四步:启动 docker(见下图)
第五步:运行 hello world(见下图)
第六步:创建文件夹。例如图像
通过命令:须藤 mkdir 图片
步骤 7 :创建 docker 文件(如 dock)
命令: Vi dock
按 I 键开始进入文件。输入详细信息后
按 esc -> : wq
docker 文件如下所示。这个 dockerfile (摇滚/闪亮)我是从 dockerhub 本身拿的。你可以调整 docker 文件例如,你可以安装一个你选择的 R 版本。
请注意,需要在 docker 文件中设置代理。
接下来的步骤将告诉你如何将 R Shiny 应用程序的内容复制到“images”文件夹中
第八步:输入 ip add 命令,获取虚拟机的 Ip 地址
**第九步:**打开 winscp 输入 ip
单击登录
步骤 10 :现在将 R shiny app 内容 s 复制到文件夹中
步骤 11 :现在输入 docker 图片
步骤 12 :复制图像 ID 或标签(在我的例子中,图像 ID 是 144146d3d082)
步骤 13 :运行以下命令
docker run–RM-p 3838:3838 144146 d3d 082
第 14 步:使用 vm 后缀:3838 的 ip,然后按回车键,你闪亮的 app 托管成功,对接成功!!。
见下图
现在你可能会问,用户如何在他/她的系统中部署 R shiny 应用程序?要做到这一点,需要采取以下步骤。
第 15 步:加载图像并放入 tar 文件格式(使用下面的命令:用你的应用程序的名称替换‘App ’)
docker save-o ~/App . tar App
步骤 16 :把这个 tar 文件交给安装了 docker 的用户就可以了。
步骤 17 :用户只需加载图片,然后运行 app。
步骤 18 :闪亮的 app 成功托管在用户系统上!!
伙计们,就是这样。再次感谢你的耐心。有很多种方法可以将一个 R Shiny App docker ize。本文采用虚拟机路线,也有其他方法。一个很好的来源是这个博客。
希望你喜欢这篇文章。如果你做了,你可以给它一些掌声。
你可以联系我
如何下载您所有的原始 fitbit 数据
出于好奇,有些人会想挖掘自己所有的原始 fitbit 数据。在本文中,我将讨论如何实际检索原始 fitbit 数据,假设他们有兴趣并且知道如何这样做。
2019 年更新
FitBit 现在提供一次性下载你所有的数据!从网页浏览器进入你的 fitbit 账户设置,将一个大的 zip 文件放入队列。您将收到一封电子邮件,内容如下:
所需操作:确认您的 Fitbit 数据导出请求
按照那里的指示,你就完成了!下面的原始博客帖子仍然具有编程价值,FitBit 限制了您重新下载所有数据的 zip 文件的频率,因此仍然有理由使用我在这里描述的应用程序编程接口方法。感谢媒体用户侏儒在八月份对这个选项的评论。该功能至少从 2019 年 4 月开始出现。
附文
在这种情况下,短语“原始数据”不是指加速度计信号,如你慢跑时的手速,这些信号经过瞬时片内处理,以识别每个原子脚步或楼梯攀爬。蓝牙带宽和电池寿命方面的考虑限制了你的 fitbit 设备只能将聚合数据传输到你的手机、平板电脑或电脑上。
在这种情况下,原始数据是指可用的最细粒度形式的数据,即 fitbit 生成并上传到云中某处持续更新的数据库的所谓数据耗尽。目前,最精细的数据形式是你每分钟的步数、心率和睡眠时间序列。
FitBit(该公司,不同于 fitbit ,追踪器/应用)声明“你的数据属于你”:任何人都可以在任何时候访问他或她自己的数据。尽管如此,在实践中检索数据需要克服一些重大的技术障碍。在过去的两年里,我开发并应用了一个 Python 框架来查询我的 fitbit 数据并将其编译成易于使用的格式,以便进行交互式数据科学和个人生理学研究。这是我发现的。
以编程方式访问 fitbit 数据
查看博客顶部今年的更新,您现在可以一次性下载您所有的 fitbit 数据!
据我所知,没有办法一次性下载你所有的 fitbit 数据。—这篇博文,2018
FitBit 在其开发者网站上提供应用编程接口(API),其中门户网站包含关于如何获取数据的适度高级指令。登录到 fitbit 帐户后,您可以阅读文档以了解系统如何工作。你将不得不用 RESTful HTTP 请求编写查询。如果您以前没有这样做过,这需要一点时间来适应,但是有一个例子可以说明这一点。如果您想获得从 2018 年 9 月 21 日开始的所有“日内心率数据”,您应该编写这个长 HTTP 查询字符串:
[https://api.fitbit.com/1/user/-/activities/heart/date/2018-09-21/1d/1min.json](https://api.fitbit.com/1/user/-/activities/heart/date/2018-09-21/1d/1min.json)
…但是你还没有完成。你需要说明你想如何处理这些数据——获取、更改或删除——并且你需要证明你有权这么做。有史以来最受欢迎的 Python 包之一, requests ,如果您了解 Python,它允许您以编程方式执行这些命令。每一种现代计算语言都有一个等同于请求的语言,但是我将主要关注 Python。在 Python 中,您可以:
import requests
response = requests.get(query_str, headers=secret_header)
其中query_str
是上面的查询字符串,secret_header
包含关于您的授权的信息。
批准
只有你能看到你的“日内”数据 FitBit 采用这个名称来表示你的心率和步数的每分钟时间序列。FitBit 使用标准的加密安全认证程序来验证你的身份,它们使检索当天数据变得特别困难:你需要注册一个免费的个人应用程序来获得 API 客户端证书。这篇 2016 的博文描述了如何让你的secret_token
成为标题。一旦你有了令牌,你就可以写:
secret_header = {‘Authorization’: ‘Bearer {}’.format(secret_token)}
注意,这个秘密令牌不应该公开共享——毕竟,它是秘密的。
保存和检查数据
您得到的响应对象包含json
格式的数据,它有多层原始数据和元数据。出于备份目的,您可以将未删节的数据保存在本地(推荐):
import json
with open(path, ‘w’) as f:
json.dump(response.json(), f)
…其中,path
是一个描述性的文件名,您可以编造,例如HR_20180921_1d_1min.json
。当您准备好分析您的数据时,您可以将笨拙的 json 格式打开成更方便的pandas
DataFrame
:
import pandas as pd
with open(path, ‘r’) as f:
json_data = json.load(f)
df = pd.DataFrame(json_data[‘activities-heart-intraday’][‘dataset’])
现在,您可以开始检查、合并和转换数据,以制作一些令人惊叹的图表。根据我的经验,我不得不做一些标准的清理操作,比如将索引设置为日期和时间,并将“值”重命名为更具信息性的名称,比如“heart_rate”或简单的“HR”。数据清理、合并和分析的主题是另一篇文章的主题。
My intraday heart rate data for January 1, 2017, from midnight to midnight.
纵向扩展和横向扩展
此时,下一步是获取所有数据。你可能想写一个for
-循环所有可用的日期和所有可用的数据类型。你将遇到的主要问题是,FitBit 只允许你每小时请求 150 包数据。这种看似反常的速率限制可能令人沮丧,但在公共 API 中是一种常见的做法——我们将不得不绕过节流。在你的 for 循环中插入下面的小技巧,在计算机等待下一个小时的时候显示一个实时的进度条:
from tqdm import tqdm
import timeif response.status_code == 429:
wait = int(response.headers[‘Fitbit-Rate-Limit-Reset’])+30
for seconds in tqdm(range(wait)):
time.sleep(1)
如果你有一个启用心率的 fitbit(如 Charge 2 或 Alta HR ,那么你将可以访问至少三个当天数据流:1) 步数,2) 心率,以及 3) 睡眠。如果你有,比如说,2 年的这些数据,你最终会得到成千上万的文件 :
2 years * 365 days/year * 3 streams/day * 1 file/stream = 2190 files
…假设您将每天和每个流的完整 json 数据保存到自己的文件中。你一次只能下载 150 个文件,所以你要花 15 个小时才能得到所有 2 年的数据。FitBit 文档确实一次提供了更多天的查询,所以如果您不耐烦等待 15 个小时以上,您可以尝试这些替代方法。
辅助数据
你可能还想要其他类型的数据,比如健身活动日志、可选的 GPS 地图文件、体重日志(假设你有 fitbit Aria ,或者你手动记录你的体重),等等。我不会详细介绍如何下载这些文件,但是这些方法非常相似,并且在 FitBit Web API 上有很好的记录。
回顾和展望
普通 fitbit 用户能够完成所有这些步骤的现实程度如何?不太可能。然而,普通的 fitbit 用户可能会从非常好的 fitbit 应用程序和网络仪表盘中获得大多数可用的见解。原始的 fitbit 日内数据很吸引人,但从中收集见解需要一些相当先进的数据科学技能,或者至少需要大量的奉献。
如何让 Pandas 从服务帐户访问 BigQuery
服务账户是一种严格控制你的应用程序在谷歌云平台上做什么的方式。您通常希望应用程序对组织资源的访问受到严格限制,而不是使用您的用户帐户拥有的所有权限来运行应用程序。
How to use a private key in a service account to access BigQuery from Pandas
假设您希望 Pandas 针对 BigQuery 运行一个查询。您可以使用熊猫的 read_gbq(在 pandas-gbq 包中提供):
import pandas as pdquery = """
SELECT
year,
COUNT(1) as num_babies
FROM
publicdata.samples.natality
WHERE
year > 2000
GROUP BY
year
"""df = pd.read_gbq(query,
project_id='MY_PROJECT_ID',
dialect='standard') print(df.head())
如果您在本地运行它,这将会起作用(因为它使用您的身份,并且假设您有能力在您的项目中运行查询)。
但是,如果您尝试从一个基本帐户运行上面的代码,它将不起作用。您将被要求通过一个 OAuth2 工作流来专门授权该应用程序。为什么?因为您不希望一个任意的应用程序运行一个 BigQuery 账单(或者更糟,访问您的公司数据),所以您必须为它提供所需的权限。
让我们试一试。
1.创建 GCE 实例
要跟随我,使用 GCP web 控制台并创建一个具有默认访问权限的 Google 计算引擎实例(这通常只包括最基本的内容,不包括对 BigQuery 的访问权限):
Creating a Google Compute Engine instance with restricted access
2.创建一个准系统服务帐户
从 GCP web 控制台,创建一个新的服务帐户。这是您将为其生成私钥的帐户。为了额外的安全性和审计,我建议为每个应用程序创建一个全新的服务帐户,并且不要在应用程序之间重用服务帐户。
进入 IAM & Admin,选择“服务帐户”,然后点击+创建服务帐户。按如下方式填写表格:
Creating a barebones service account for Pandas
上述角色允许服务帐户运行查询,并将这些查询记入项目。但是,数据集所有者仍然需要允许服务帐户查看他们的数据集。
如果您使用非公共的 BigQuery 数据集,请通过转到 BigQuery 控制台并与服务帐户的电子邮件地址共享数据集,授予服务帐户对该数据集的适当(通常只是查看)访问权限。出于本教程的目的,我将使用一个公共的 BigQuery 数据集,所以我们可以跳过这一步。
3.将示例代码放在 GCE 实例上
SSH 到 GCE 实例,并在命令行中键入以下命令:
sudo apt-get install -y git python-pipgit clone [https://github.com/GoogleCloudPlatform/training-data-analyst](https://github.com/GoogleCloudPlatform/training-data-analyst)sudo pip install pandas-gbq==0.4.1
4.不使用默认凭据运行
更改 nokey_query.py 中的项目 id,然后运行它:
cd training-data-analyst/blogs/pandas-pvtkey# EDIT nokey_query.py to set the PROJECT IDpython nokey_query.py
它将要求您完成一个 OAuth2 工作流。按 Ctrl-C 退出。您不希望此应用程序使用您的凭据运行。
遇到这种错误时,您会听到一个常见的建议,那就是运行:
# BAD IDEA! DO NOT DO THIS
gcloud auth application-default login
并在启动应用程序之前在 shell 中完成交互式 OAuth 2 工作流。要小心这样做:(1)上面的命令是一个火箭筒,允许应用程序做你能做的任何事情。(2)不可脚本化。每次运行应用程序时,您都必须这样做。
最好的方法是创建计算引擎虚拟机,不是使用最基本的服务帐户,而是使用您已经授予 BigQuery 查看器权限的服务帐户。换句话说,交换步骤 1 和 2,当您创建 GCE 实例时,使用新创建的服务帐户。
但是,如果您不在 GCP 上(因此您的机器不是由服务帐户 auth 创建的)或者您正在使用云数据流或云 ML 引擎等托管服务(因此虚拟机是由该服务的服务帐户创建的),该怎么办呢?在这种情况下,更好的方法是按如下方式更改熊猫代码:
df = pd.read_gbq(query,
project_id='MY_PROJECT_ID',
** private_key='path_to/privatekey.json',**
dialect='standard',
verbose=False)
应用程序现在将使用与该私钥相关联的权限。
5.不使用私钥运行
在 query.py 中更改项目 id,然后运行它:
cd training-data-analyst/blogs/pandas-pvtkey# EDIT query.py to set the PROJECT IDpython query.py
它会失败,说找不到私钥
6.为服务帐户生成私钥
还记得在创建服务帐户时创建了一个 JSON 私有密钥吗?你需要把它上传到 GCE 虚拟机。(如果您没有创建密钥文件,请导航到 IAM >服务帐户,并为 pandas 服务帐户创建一个私钥。这将创建一个 JSON 文件,并将其下载到您的本地计算机。您也可以从这里撤销密钥。)
在 SSH 窗口的右上角,有一个上传文件的方法。将生成的 JSON 文件上传到 GCE 实例,并将其移动到位:
mv ~/*.json trainer/privatekey.json
7.用私钥运行
python query.py
现在它可以工作了,您将得到查询的结果。
8.将私钥放入 Python 包中
如果您没有使用 GCE,而是使用托管服务(云数据流、云 ML 引擎等),该怎么办?)反而?
在这种情况下,您将提交一个 Python 包。使用 package_data 属性在 setup.py 中将私钥标记为资源:
from setuptools import setupsetup(name='trainer',
version='1.0',
description='Showing how to use private key',
url='[http://github.com/GoogleCloudPlatform/training-data-analyst'](http://github.com/GoogleCloudPlatform/training-data-analyst'),
author='Google',
[author_email='nobody@google.com](mailto:author_email='nobody@google.com)',
license='Apache2',
packages=['trainer'],
**## WARNING! Do not upload this package to PyPI
## BECAUSE it contains a private key**
**package_data={'trainer': ['privatekey.json']},**
install_requires=[
'pandas-gbq==0.4.1',
'urllib3',
'google-cloud-bigquery'
],
zip_safe=False)
然后,执行以下操作,而不是硬编码 privatekey.json 文件的路径:
private_key = pkgutil.get_data('trainer', 'privatekey.json')
这里有一个更完整的例子。注意 Python 没有将包标记为私有的方法,所以您应该小心不要错误地将这个包发布在公共存储库中,比如 PyPI。
9.保护私钥
私钥实质上为提供它的任何人解锁了已经可用的资源。在这种情况下,任何提供私钥的人都可以进行 BigQuery 查询。没有第二种形式的身份验证(IP 地址、用户登录、硬件令牌等)。)是必需的。因此,要注意如何/在哪里存储私钥。我的建议是:
(1)确保 Python 包中有一个显式忽略私钥的. gitignore:
$cat trainer/.gitignore
privatekey.json
(2)使用 git 秘密来帮助防止密钥泄漏
(3)旋转你的钥匙。详见本博客。
(4)确保不要将 Python 包发布到任何 Python 包的存储库中,因为您的存储库中包含私钥。
摘要
- 使用 JSON private_key 属性来限制 Pandas 代码对 BigQuery 的访问。
- 创建具有准系统权限的服务帐户
- 与服务帐户共享特定的 BigQuery 数据集
- 为服务帐户生成私钥
- 将私钥上传到 GCE 实例或将私钥添加到可提交的 Python 包中
- 确保不要将私钥签入代码或包存储库中。使用密钥旋转器和 git 机密。
下面是 GitHub 中这篇文章的示例代码。编码快乐!
鸣谢:感谢我的同事 Tim Swast 和 Grace Mollison 的帮助和建议。文章中的任何错误都是我自己的。