TDD 举例说明
建立一个 ASP.NET 维基来学习 TDD,这是一种使用小测试用例来构建更好软件的技术
我将尝试解释什么是 TDD,以及它如何在开发过程中提供帮助。有很多资源和书籍是这样做的,但我将尝试用一个简单的实际例子来介绍它。这更像是一个“哲学”概述,而不是你能在书中读到的严格定义。反正我也没想走纯理论的路子,而是更实践的方式,让你明白我们日常生活中真正需要的是什么。这种方法的纯粹主义支持者可能会发现这个解释有点不完整(抱歉…),但我认为这足以开始学习和理解基础知识。
也许你不需要再读一本关于 TDD 的书,只要用清晰简单的文字理解它是什么就行了
这对初学者来说很好,可以激发兴趣,进行深入的探索,然后终生拥抱它。
What is TDD? Test-driven development is a technique to build software using small test cases
什么是 TDD
从维基百科的定义开始:
测试驱动开发*(TDD)是一种* 软件开发过程,它依赖于一个非常短的开发周期的重复 :需求被转化为非常具体的 测试用例 ,然后软件被改进以通过新的测试,只。这与允许添加未被证明满足需求的软件的软件开发相反。
清楚了吗?TDD 的主要目的是创建一种策略,在这种策略中,测试将驱动开发过程,以便使编码更加高效、多产,减少回归。
先决条件是将一个大任务分解成更小的步骤,并使用单元测试进行开发。这允许您处理一小段代码,使它们工作,然后将许多工作部分集成在一起。
TDD 的好处
将 TDD 引入您的编码体验将会达到一个转折点。以下是一些最重要的优势:
- 关注真正重要的点:你会被要求分解问题,这将有助于你把注意力集中在最重要的事情上。
- 处理更简单的任务:每次处理一个更小的任务可以简化故障排除,加快开发速度。你不会陷入这样的情况:你会写所有的代码,然后有些东西不工作,你不知道为什么。
- 简化集成:当多个工作特性完成时,将所有特性组合在一起将是一件愉快而轻松的任务。在回归的情况下,你会提前知道代码的哪一部分是坏的。
- 免费测试:一旦全部任务完成,大量的单元测试仍然存在,可以作为集成\单元测试来验证代码,避免回归。
TDD 不是什么
TDD 是一种很好的方法,但不是:
- 测试的替换(单元测试、验收测试、UI 测试)
- 一天就能学会的东西
- 为你写代码的东西
- 一个从代码中驱除 bug 圣人
TDD 生命周期
TDD 主要由三个步骤组成:
- 编写单元测试(红色)。
- 让它工作(绿色)。
- 重构。
在示例中,您可以编写单元测试,使用其中的代码来实现该功能,直到它工作为止,然后在需要的地方重构这段代码。
步骤 1,2:让测试工作
public class StripTest
{
[Fact]
public static void StripHTml()
{
string test="<h1>test</h1>";
string expected="test";
string result=StripHTML(test);
Assert.Equal(expected,result);
}
public static string StripHTML(string input)
{
return Regex.Replace(input, "<.*?>", String.Empty);
}
}
步骤 3:重构
public class StripTest
{
[Fact]
public static void StripHTml()
{
string test="<h1>test</h1>";
string expected="test";
string result=HtmlHelper.StripHTML(test);
Assert.Equal(expected,result);
}
}
*//somewhere else*
public static class HtmlHelper
{
public static string StripHTML(string input)
{
return Regex.Replace(input, "<.*?>", String.Empty);
}
}
限制
在许多情况下,很难编写涵盖真实代码使用的单元测试。对于完全符合逻辑的过程来说,这很容易,但是当我们要涉及数据库或 UI 时,编写工作将会增加,并且在许多情况下,可能会超过好处。有一些最佳实践和框架对此有所帮助,但一般来说,并不是应用程序的所有部分都容易使用简单的单元测试来测试。
什么是 BDD?
BDD 是 TDD 的增强,它考虑了单元测试受限的情况。这个扩展将开发者作为一个单元测试,保持 BDD 背后的哲学。您仍然可以将复杂的任务分解成更小的任务,使用用户行为进行测试,并在纯后端任务上利用 TDD 的优势。
TDD 先决条件
在团队中工作时,除了掌握所有相关技术的知识之外,所有的队友都必须了解并接受这一理念。
首先,您的代码必须得到强大的单元测试系统的支持:
- 。网,。NET Core:内置 Visual Studio 或者 Xunit(第二个是我个人,首选)
- Java: JUnit 工作得非常好,我不需要寻找另一种解决方案
- PHP: PHP 单元在所有情况下都为我工作
然后,重要且强制的是:拥有一个允许在测试期间模仿或重现正确行为的架构。我说的是一个 ORM,它可以在测试期间在内存或本地数据库上工作,但也可以使用服务或存储库模式。使用阿迪框架(内置。NET core,Autofac 或其他什么…)也有帮助。
最后但同样重要的是:一个做得好的构建过程,集成到一个持续的集成流程中,除了正确的配置之外,还要定义在集成期间在其上运行哪些单元测试是有意义的,以及哪些单元测试只是在本地运行。
这个例子
让我们试着在一个真实的例子中把我们学到的关于 TDD 的东西付诸实践。我想用这种方法创建一个维基。我指的是一个简单的 wiki,用户可以在这里登录、编写减价页面并发布。听起来很复杂?
那非常容易。多亏了 TDD 和管理小任务,我很快完成了所有的微特征,最后我把已经工作的部分组装起来。
首先,我会将“长期”任务分解成更小的后续活动。每个子部分将使用小单元测试来开发。我会把重点放在维基页面上。
步骤 1:实体到 DTO 的映射
从这里开始听起来不太好。实体到 DTO 的映射是一件非常原始的事情,很难抑制我们想要从最酷的部分开始的编码本能。无论如何,这是第一个,自动一致的功能。映射两个类只需要这两个类的定义,仅此而已。无论数据库连接,网络错误等等。我们只需要创建两个类(d to 和实体),然后进行映射。最后,测试将是一段代码,它将检查实体中的字段是否被复制到 d to。轻松点。
让我们总结一下步骤:
- 写实体。
- 写维基页面 DTO。
- 编写将实体映射到 DTO 的代码。
*// Database entity*
public class WikiPageEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public int Version { get; set; }
public string Slug { get; set; }
public string Body { get; set; }
public string Title { get; set; }
}
*// DTO model in BLL*
namespace WikiCore.Lib.DTO
{
public class WikiPageDTO
{
public string Title { get; set; }
public string BodyMarkDown { get; set; }
public string BodyHtml { get; set; }
public int Version { get; set; }
public string Slug { get; set; }
}
}
*// From unit test, code omitted for brevity*
public void EntityToDTO()
{
WikiPageEntity source = new WikiPageEntity()
{
Title = "title",
Slug = "titleslug",
Version =1
};
var result = Mapper.Map<wikipagedto>(source);
Assert.Equal("title", result.Title);
Assert.Equal(1, result.Version);
}
*// From Mapping configuration, code omitted for brevity*
public MappingProfile()
{
CreateMap<wikipageentity, wikipagedto="">().ReverseMap();
}
步骤 2:降价到 HTML 转换
第二步是制作一个将markdown
转换成 HTML 的方法。这将是一个非常简单的方法,它将接受一个 markdown 字符串,并检查它的转换是否与预期的 HTML 匹配。
*//Before refactoring public class MarkdownTest*
{
[Fact]
public void ConvertMarkDown()
{
var options = new MarkdownOptions
{
AutoHyperlink = true,
AutoNewLines = true,
LinkEmails = true,
QuoteSingleLine = true,
StrictBoldItalic = true
};
Markdown mark = new Markdown(options);
var testo = mark.Transform("#testo");
Assert.Equal("<h1>testo</h1>", testo);
}
*// after refactoring ( method moved to helper )*
[Fact]
public void ConvertMarkDownHelper()
{
Assert.Equal("<h1>testo</h1>", MarkdownHelper.ConvertToHtml("#testo"));
}
*// From markdown helper*
public static class MarkdownHelper
{
static MarkdownOptions options;
static Markdown converter;
static MarkdownHelper()
{
options = new MarkdownOptions
{
AutoHyperlink = true,
AutoNewLines = true,
LinkEmails = true,
QuoteSingleLine = true,
StrictBoldItalic = true
};
converter = new Markdown(options);
}
public static string ConvertToHtml(string input)
{
Markdown mark = new Markdown(options);
return mark.Transform(input);
}
}
步骤 3:用降价增强映射
干得好!我们有从 markdown 生成 HTML 的方法和将实体翻译成 DTP 的映射器。下一步?把所有的放在一起!
下一段代码包含 HTML 字段计算的映射:
*// mapped profile changed*
public class MappingProfile : Profile
{
public MappingProfile()
{
SlugHelper helper = new SlugHelper();
CreateMap<wikipageentity, wikipagedto="">()
.ForMember(dest => dest.BodyMarkDown, (expr) => expr.MapFrom<string>(x => x.Body))
.ForMember(dest => dest.BodyHtml,
(expr) => expr.MapFrom<string>(x => MarkdownHelper.ConvertToHtml(x.Body)))
.ReverseMap();
CreateMap<wikipagebo,wikipageentity>()
.ForMember(dest => dest.Body, (expr) => expr.MapFrom<string>(x => x.BodyMarkDown))
.ForMember(dest => dest.Slug,
(expr) => expr.MapFrom<string>(x => helper.GenerateSlug(x.Title)));
}
}
*// From unit test, code omitted for brevity*
public void EntityToDTO()
{
WikiPageEntity source = new WikiPageEntity()
{
Body = "# prova h1",
Title = "title",
Slug = "titleslug",
Version =1
};
var result = Mapper.Map<wikipagedto>(source);
Assert.Equal("title", result.Title);
Assert.Equal(1, result.Version);
Assert.Equal("<h1>prova h1</h1>", result.BodyHtml);
}
步骤 4:设置数据库迁移
另一步是整合数据库。要记住的一件重要的事情是,我们只需要测试一件事情…而数据库访问是一件复杂的事情。对数据库的第一个要求是结构。所以,检查的第一步是确保这个思想实体框架的迁移。
要执行的步骤:
- 运行
Add-Migration
脚本。 - 创建一个在内存中工作的单元测试来测试它。
[Fact]
public void MigrateInMemory()
{
var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>();
optionsBuilder.UseInMemoryDatabase();
using (var db = new DatabaseContext(optionsBuilder.Options))
{
db.Database.Migrate();
}
*// No error assert migration was OK*
}
步骤 5:实体积垢
在我们设置好迁移之后,我们可以假设数据结构一切正常。让我们从证明 CRUD 特性的单元测试开始。
步骤:
- 编写一个 CRUD 测试。
- 测试一下。
[Fact]
public void CrudInMemory()
{
var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>();
optionsBuilder.UseInMemoryDatabase();
using (var db = new DatabaseContext(optionsBuilder.Options))
{
db.Database.Migrate();
db.WikiPages.Add(new Lib.DAL.Model.WikiPageEntity()
{
Title = "title",
Body = "#h1",
Slug = "slug"
});
db.SaveChanges();
var count=db.WikiPages.Where(x => x.Slug == "slug").Count();
Assert.Equal(1, count);
*// update, delete steps omitted for brevity*
}
}
步骤 6:测试服务
在我们的架构中,服务层将提供业务逻辑的抽象。在这个简单的例子中,我们的服务将包装插入或更新特性,在保存后返回一个 DTO。
这个单元测试的步骤:
- 用业务逻辑创建服务。
- 测试一下
[Fact]
public void TestSave()
{
var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>();
optionsBuilder.UseInMemoryDatabase();
using (var db = new DatabaseContext(optionsBuilder.Options))
{
db.Database.Migrate();
db.SaveChanges();
*//this recreate same behaviour of asp.net MVC usage*
DatabaseWikiPageService service = new DatabaseWikiPageService(db, Mapper.Instance);
service.Save(new Lib.BLL.BO.WikiPageBO()
{
BodyMarkDown="#h1",
Title="prova prova"
});
var item = service.GetPage("prova-prova");
Assert.NotNull(item);
}
}
步骤 7:在用户界面上继续
一旦使用单元测试测试 UI 变得复杂,我就从纯粹的 TDD 方法切换到一个更有弹性的测试版本,并参与到这个过程中。这有助于将所有工作分成多个步骤来完成 UI。因此,我没有编写所有代码然后测试它,而是将问题分解成多个子活动,然后逐一测试:
编辑
- 准备表单,并测试它。
- 准备模型,测试从表单提交的内容填充后端模型。
- 集成服务以保存数据,测试数据。
视图
- 准备模型,传递给视图,测试它。
- 将模型与服务集成,以获得真实数据。测试一下。
列表
- 准备视图模型,传递假数据到 UI,测试它。
- 集成服务,测试它。
每个微特征都可以快速实现并易于测试。这将促进完整的实现。
结论
TDD 是一种驱动测试支持的开发过程的方法。
这有助于在许多方面编码,但要求所有的队友都有一些基础知识。一旦完成了这一步,您将处理一个更简单的任务和许多可以重用的测试。
这个过程将有助于避免回归,并更快地达到目标,如果在开发的同时努力编写单元测试也是如此。
此外,如果您的应用程序由于复杂性而难以测试,您可以保持同样的理念执行一些手动步骤。
觉得这篇文章有用?在 Medium 上关注我(丹尼尔·丰塔尼),看看我下面最受欢迎的文章!请👏这篇文章分享一下吧!
- Docker 到底是什么?
- 【Kubernetes 到底是什么?
- 如何使用 Kubernetes 部署 web 应用程序
资源
- 完整的 git 源代码https://github.com/zeppaman/wiki.asp.net.core
- 原文发表于 2018 年 11 月 17 日www.codeproject.com。
- 关于 TDDhttps://en.wikipedia.org/wiki/Test-driven_development维基百科告诉了什么
教一个变分自动编码器(VAE)画 MNIST 字符
These characters have not been written by a human — we taught a neural network how to do this!
要查看完整的 VAE 代码,请参考我的 github 。
自动编码器是一种神经网络,可用于学习输入数据的有效编码。给定一些输入,网络首先应用一系列变换,将输入数据映射到低维空间。网络的这一部分被称为编码器。
然后,网络使用编码数据尝试重新创建输入。网络的这部分就是解码器。使用编码器,我们可以压缩网络能够理解的数据类型。然而,自动编码器很少用于此目的,因为通常存在更有效的手工算法(如 jpg -compression)。
相反,自动编码器被反复应用于执行去噪任务。编码器接收被噪声篡改的图片,并学习如何重建原始图像。
什么是变分自动编码器?
然而,自动编码器还有更有趣的应用。
一个这样的应用叫做变分自动编码器。使用可变自动编码器,不仅可以压缩数据,还可以生成自动编码器以前见过的新对象类型。
使用一个通用的自动编码器,我们对网络产生的编码一无所知。我们可以比较不同的编码对象,但是我们不太可能理解发生了什么。这意味着我们将不能使用我们的解码器来创建新的对象。我们只是不知道输入应该是什么样子。
使用变分自动编码器,我们采取相反的方法。我们不会去猜测潜在向量的分布。我们只是告诉我们的网络我们希望这个分布看起来像什么。
通常,我们将约束网络产生具有遵循单位正态分布的条目的潜在向量。然后,当试图生成数据时,我们可以简单地从该分布中采样一些值,将它们提供给解码器,解码器将返回给我们全新的对象,这些对象看起来就像我们的网络已经被训练过的对象。
让我们看看如何使用 Python 和 Tensorflow 来实现这一点。我们将教我们的网络如何画出 MNIST 人物。
第一步—加载培训数据
首先,我们执行一些基本的导入。Tensorflow 有一个方便的功能,让我们可以轻松地访问 MNIST 数据集。
定义我们的输入和输出数据
MNIST 图像的尺寸为 28 * 28 像素,具有一个颜色通道。我们的输入X_in
将是一批 MNIST 字符。网络将学习重新构建它们,并在占位符Y
中输出它们,占位符具有相同的尺寸。
Y_flat
将在以后计算损失时使用。keep_prob
将在申请退学作为正规化手段时使用。在训练期间,它的值将为 0.8。当生成新数据时,我们不会应用 dropout,因此值将为 1。
函数lrelu
正在被定义,因为 Tensorflow 不幸没有提出预定义的泄漏 ReLU。
定义编码器
因为我们的输入是图像,所以对它们应用一些卷积变换是最合理的。最值得注意的是,我们在编码器中创建了两个向量,因为编码器应该创建遵循高斯分布的对象:
- 均值向量
- 标准差的向量
稍后,您将看到我们如何“强制”编码器确保它真正创建遵循正态分布的值。将被馈送到解码器的返回值是 z 值。在计算损失时,我们将需要分布的平均值和标准差。
定义解码器
解码器不关心输入值是否是从我们定义的特定分布中采样的。它将简单地尝试重建输入图像。为此,我们使用一系列转置卷积。
现在,我们将两部分连接在一起:
计算损失并实施高斯潜在分布
为了计算图像重建损失,我们简单地使用平方差(这可能导致图像有时看起来有点模糊)。这种损失与 Kullback-Leibler 散度相结合,确保我们的潜在值将从正态分布中取样。关于这个话题的更多信息,请看看 Jaan Altosaar 关于 VAEs 的文章。
训练网络
现在,我们终于可以训练我们的 VAE 了!
每走 200 步,我们就会看一看当前的重建是什么样子。在处理了大约 2000 个批次后,大多数重建看起来是合理的。
生成新数据
最棒的部分是我们现在能够创造新的角色。为此,我们简单地从一个单位正态分布中抽取值,并将它们馈送给我们的解码器。大多数创造出来的角色看起来就像是人类写的一样。
Some of the automatically created characters.
结论
这是 VAEs 应用的一个相对简单的例子。但是想想什么是可能的!神经网络可以学习作曲。他们可以自动为书籍、游戏等制作插图。凭借一点创造力,VAEs 将为一些令人敬畏的项目开辟空间。
[## Felix mohr/使用 Python 进行深度学习
在 GitHub 上创建一个帐户,为深度学习 Python 开发做贡献。
github.com](https://github.com/FelixMohr/Deep-learning-with-Python/blob/master/VAE.ipynb)
教汽车驾驶——高速公路路径规划
Car Driving On Highway
这是 Udacity 自动驾驶汽车工程师纳米学位 第三学期的第一个项目。你可以在github上找到与这个项目相关的所有代码。你也可以阅读我以前项目的帖子:
- 项目一—项目一: 利用计算机视觉检测车道线
- 术语 1 —项目 2: 利用深度学习的交通标志分类
- 期限 1 —项目 3: 利用深度学习进行转向角度预测
- 术语 1 —项目 4: 使用计算机视觉的高级车道检测
- 术语 1 —项目 5: 利用机器学习和计算机视觉进行车辆检测
规划一条既安全又高效的路径是自主车辆开发中最困难的问题之一。事实上,这一步被称为路径规划,仍然是一个活跃的研究领域。路径规划之所以是一项如此复杂的任务,是因为它涉及到自动驾驶车辆的所有组件,从低级执行器、融合以创建世界“快照”的传感器,以及定位和预测模块,以准确了解我们在哪里,以及我们世界中的不同实体(其他车辆、人类、动物等)在接下来的几秒钟内更有可能采取什么行动。另一个显而易见的组件是轨迹生成器,它可以计算规划者要评估的候选轨迹。
在这篇文章中,我们将重点描述我们如何实现一个 C++高速公路路径规划器,它能够使用加加速度最小化技术在模拟器中生成安全有效的轨迹。该项目的制约因素如下:
- 任何时候都不与其他车辆发生碰撞
- 最高时速 50 英里(约 80 KMH)
- 最大加速度 10 米/秒
- 最大加加速度为 10 米/秒
- 车辆不能处于车道之间超过 3 秒钟
- 车辆不能出高速公路三车道
- 车辆不能在高速公路上逆向行驶
我们在完成这个项目时遇到了很多麻烦,下面的 gif 显示了我们早期的一些约束违反
Rough Start
首先,让我们仔细看看路径规划中涉及的不同层次。
自动驾驶汽车中的功能层
Layers involved in path planning — from Udacity
如前所述,路径规划需要自动驾驶汽车不同层的合作。上图概述了在给定的自动驾驶系统中,这些组件可能如何分层:
- 运动控制: 负责移动小车并尽可能接近地跟随参考轨迹。该层以最快的时间尺度运行
- 传感器融合: 负责融合传感器输出(如雷达+激光雷达)。
- 定位: 负责尽可能精确地了解我们的车辆在地图上的位置,以及其他实体(如其他汽车)相对于我们的车辆的位置
- 预测: 负责识别用传感器检测到的实体的性质(又名感知)以及基于汽车当前轨迹、其他车辆的轨迹和场景中的其他元素(如交通灯)预测场景中近期的变化。这一层的一个重要任务是预测碰撞。
- 行为: 协调层,接受来自较低层的所有信息,并决定未来状态以及要采用的轨迹
- 轨迹: 负责计算给定一组约束条件下的轨迹(如速度、距离、车道、加加速度等)。)
轨迹生成有许多方法,在这个项目中,我们选择在 Frenet 坐标系中计算轨迹。
理解传感器融合数据
模拟器中的车辆配备了一系列传感器,这些传感器的输出经过融合,可以产生更精确的测量结果。大多数从事第 4 级自动驾驶的公司都在他们的传感器套件中使用了雷达、激光雷达和摄像头。拥有多种不同类型的传感器至关重要,因为每种传感器都有各自的优势和劣势。此外,拥有同一传感器的多个实例对于减少给定传感器中的硬件故障也很重要。
在我们的例子中,模拟器从其传感器融合模块提供以下信息:
- 我们车辆的位置、速度和方向
- 传感器范围内其他车辆的位置和速度(我们可以用一点三角学计算方位——见反正切)
- 要执行的先前提交轨迹的剩余部分
有了这些信息,我们就能计算出本车与所有其他车辆的距离。我们进一步采取这许多步骤,并试图根据我们车辆的轨迹和其他车辆的推断轨迹来预测碰撞。我们在稍后定义的成本函数中利用这些信息。
轨迹生成
弗雷内坐标系
我们经常使用传统的笛卡尔坐标系来表示平面上的给定点 (x,y) ,这实际上是模拟器中用于识别道路上的汽车的默认系统。然而,在现实世界中,道路并不总是笔直的,因此人类执行的“简单”操作(如识别汽车在哪个车道上)很难用笛卡尔系统复制给计算机。下图说明了我们在传统的 (X,Y) 坐标系中面临的问题:
Curvy road in a Cartesian system (taken from Udacity lesson)
如果我们的坐标系支持道路的曲率,那么在这个新的坐标系中,我们的汽车前进并停留在车道内的轨迹将显示为一条直线,如下所示,这不是更容易吗?
Trajectories in Frenet (left) and normal Cartesian (right) coordinate systems
这正是 Frenet 坐标系所提供的:在这样一个系统中,我们将我们的平面分成纵向的轴和横向的轴,分别表示为 S 和 D. 如何获得这样一个系统背后的数学是相当复杂的,所以我们不会在本文中全部展示出来。但是你可以想象穿过道路中心的曲线决定了 S 轴,并指示我们在道路上走了多远。 D 轴映射到汽车的横向位移。下图显示了该系统在弯道上的表现:
Curvy Road Captured In Frenet Coordinates — from Udacity
冲击最小化
加加速度定义为加速度随时间的变化率。同时,加速度被定义为速度随时间的变化率。基本上加加速度和加速度分别是加速度和速度的导数。作为乘客,车辆中加速度的突然变化导致高的颠簸,最终使乘坐不舒适。因此,在规划轨迹时,最大限度地降低加加速度至关重要。
事实证明,通过扩展运动学方程来计算给定我们的当前位置 *s_0、*当前速度 s_0_v、和当前加速度 s_0_a. 的轨迹,对于给定的时间帧 T (例如 1 秒)、,计算一维的加加速度最小轨迹是相对容易的,我们可以表示为:
- 期望的最终位置(在时间 t ) s_f
- 期望最终速度 s_f_v
- 期望最终加速度 s_f_a
使用达到加加速度二阶导数的五次多项式(即 5 次)。关于起始值的等式如下:
Jerk Minimizing Trajectory equations — from Udacity
等于符号左侧的值是位置、速度和加速度在时间 t ≤ T 的预计值。在我们的例子中, t 被设置为控制器的更新速率,即 20 毫秒(0.02 秒)。我们可以通过设置适当的边界条件将所有这些插入多项式解算器。在我们的例子中,我们将期望的加速度设置为 0,因为我们希望降低加加速度。不幸的是,这仅设置了 t=T 的最终加速度,当T≦T 时,我们无法控制车辆的加速度。因此,我们需要试验不同的 T 值,以确定选择哪个时间范围来生成平滑的加加速度最小化轨迹。在我们的例子中,我们选择 T = 1.7 秒。另一个问题是,我们有一个完美的控制器,它可以将车辆移动到轨迹中的任何下一点,而不管物理定律如何(例如,可以在 20 毫秒内再移动 1 公里),因此我们需要对我们提交的轨迹保持高度警惕。
由于我们使用 Frenet 坐标,我们可以分别在 s 和 d 维度中生成一维加加速度最小轨迹。Werling 和 Kammel 的这篇论文是一篇很好的阅读材料,可以让你更熟悉这个话题。
由于模拟器不接受以 Frenet 坐标表示的轨迹,我们从 Frenet 坐标转换回真实世界坐标,以计算映射到给定的 (s,d) Frenet 点的 (x,y) 点。
改进本地化
创造更平滑的轨迹
我们假设赛道已经预先绘制好,并提供有个路点*,这些路点沿着中间的黄线延伸,黄线将公路两侧分开。这有助于我们根据最近的路点确定我们的位置。*
不幸的是,我们得到的地图航路点非常稀疏,当我们试图从 Frenet 转换回现实世界坐标时,可能会产生非常“有角度”的轨迹。这反过来导致加速度和冲击的突然峰值。由于函数 toRealWorld (s,d)——>(x,y)使用两个路点之间的基本插值来寻找 x 和 y 的最佳近似值,我们总是冒着生成不平滑轨迹的风险。
Lane Change With Coarse Waypoints
我们能做些什么来改善这一点呢?从以前的一些项目中,我们已经看到从多项式中导出的直线趋向于产生非常平滑的轨迹。因此,我们应该采用这种技术,而不是目前使用的基本插值。我们求助于使用通过采用 Frenet 坐标中的位置 s 创建的样条来获得真实世界的坐标 x 、 y ,以及偏移量 dx 和 dy 。然后,我们插入这个公式以获得最接近的真实世界坐标
*x = spline_s_x(s) + d * spline_s_dx(s)
y = spline_s_y(s) + d * spline_s_dy(s)*
现在我们可以看到我们的轨迹变得多么的平滑。
Smooth Driving From Better Waypoints
二维状态机
思考驾驶的最直观的方式之一是,我们人类根据我们的驾驶风格、我们捕捉的外部信息以及我们心目中的目的地,将我们的车辆转换到不同的状态。事实证明,我们可以为机器编写状态,并根据当前状态和我们的自动驾驶汽车堆栈的其他层,指示它们可以移动到哪些状态。
在我们的例子中,我们的有限状态机非常简单,如下所示:
Final State Machine For Path Planning — from Udacity
最常见的状态将是保持车道*,但每当我们希望改变车道时,汽车将首先转换到准备左/右变道状态,并在确保车道开关安全后,将移动到实际变道状态。我们在变换车道前进入的中间状态类似于车辆在变换车道前打开左/右信号灯(当然,司机也要确保变道是安全的)。*
关于我们的状态机的实现,我们从 Frenet 坐标中得到灵感来设计这个方法。我们决定将给定的状态拆分成其纵向和横向组件。这样做的原因是,我们认为它简化了我们在可能变道的高速公路上驾驶的想法。
基本上,横向状态决定了我们可能发现的下一个潜在状态,而代价函数可能选择一个纵向状态而不是另一个。状态机的实现可以在下面的要点中找到:
成本函数
考虑到我们通常会返回多个候选的下一个状态以及它们的轨迹,我们必须找到一种方法来选择要采取的“最佳”行动。这就是成本函数有用的地方。成本函数是必要的,以“教导”车辆我们想要鼓励哪些行为,以及我们通过不同的权重对哪些行为进行较轻或较重的惩罚。
我们所有的成本函数都遵循我们在项目存储库中的文件 cost_functions.h 中定义的接口:
*typedef function<double (const Vehicle&, const vector<Vehicle>&, const Trajectory&, const State&, const double&)> CostFunction;*
这使得添加成本函数变得非常简单(权重是最后一个参数)。我们定义了以下成本函数,其中权重完全可调:
-
【speedCostFunction】:如果我们的车辆行驶缓慢,该功能将对其进行处罚
-
laneChangeCostFunction:该功能总是惩罚变道,因为变道通常比在同一车道上行驶更危险**
-
averageLaneSpeedDiffCostFunction:根据本车道前方车辆的平均速度,对车辆想要进入的车道进行处罚的功能
我们尝试了许多不同的重量配置,但最终决定,由于我们不希望在安全性上妥协,我们将碰撞时间功能的最高重量指定为 10000。其他成本函数的权重变化很大,但我们给了 speedCostFunction 很小的权重,因为高速固然很好,但远不如无碰撞重要。**
决赛成绩
当前路径规划器执行得相当好,使车辆能够围绕轨道行驶多次。然而,它可以通过更多地调整权重和改进一些成本函数来改进。此外,我们相信,通过将一些机器学习纳入我们的预测层,我们可以消除一些可能导致碰撞的边缘情况。我们的规划器的一个有趣的行为是,它能够快速多次改变车道。我们最初认为这是我们最终状态机设计的一个错误,但结果是一个有趣的副作用!**
Smooth Double Lane Change (second time because of red car ahead)
你也可以观看上传到 YouTube 的全程视频:
Video Of Full Lap In Simulator
丰富
当前的路径规划器相对保守,没有针对最高速度进行优化。这意味着虽然速度有时可以高达 48 英里/小时(~77.2 公里/小时),但它通常会低于这个速度,几乎永远不会达到 50 英里/小时(~80.4 公里/小时)的法定速度限制。这是一个折衷方案,我们现在很乐意接受,但还需要努力让汽车以接近 50 英里/小时的速度行驶。
此外,规划者仅考虑车辆的相邻车道,因此从未“看到”非相邻车道何时会是更好的选择(例如,汽车在车道 1 上,规划者仅评估车道 1 和 2,而最右侧的车道 3 可能是自由的,因此是移动的良好候选车道)。这将需要更复杂的路径评估方法,其中规划者评估所有车道,并最终决定移动到哪个相邻车道,这是基于这样的事实,即一旦车辆到达相邻车道,与相邻车道相邻的车道将成为移动到的可行候选车道。我们对这种方法的另一个担心与安全性有关,因为执行这样一个大胆的动作更加危险和棘手,因为必须覆盖的横向距离以及预测道路上其他车辆行为的难度。
我们应该研究的另一个改进是采用统计技术来更好地预测其他车辆的行为,特别是预测他们何时并入我们的车道,因为这增加了致命碰撞的风险。我们可以从使用朴素贝叶斯开始,但没有足够的时间来专门测试,然后为我们的预测器选择最具区分性的特征。
最后,我们当前的规划器只为给定的可能的下一个状态生成单个轨迹,这意味着我们可能会忽略相同未来状态的更好的轨迹(例如,在给定车道的更远/更后面,或者更左或更右)。我们有这样的每状态多轨迹方案的实现,其假设所有最终的 Frenet 位置 s 和 d (通过计算加加速度最小轨迹获得)遵循高斯分布,对于 G(s)和 G(d)分布分别具有给定的平均值和标准偏差(mean_s,std_s)和(mean_d,std_d)。然而,我们必须为标准偏差选择适当的值,同时平均值将保持固定在最初期望的终点位置 end_s 和 end_d 。
承认
毫无疑问,这是迄今为止我作为无人驾驶汽车 nanodegree 的一部分承担的最困难的项目,涵盖所有三个项目。我不确定我能不能完成它,甚至不知道从哪里开始!
大卫·西尔弗和亚伦·布朗的小视频帮助我开始。因为我选择了加加速度最小化方法来生成轨迹,所以我没有找到太多选择这种技术的学生的博客帖子或参考资料,因为大多数文章都是关于在 David 和 Aaron 的教程中使用样条技术的。但是 Werling 和 Kammel 关于在 Frenet 框架下动态街道场景的最优轨迹生成的论文真的帮助提高了我对这项技术的直觉(尽管我并没有完全理解所有的东西)。
此外,我特别喜欢米蒂在这个项目上的文章,以及其他学生的帖子,并从中受到了启发。最后,我要感谢 Udacity 的所有团队和他们在戴姆勒的合作伙伴,是他们将如此伟大的内容和具有挑战性的项目整合在一起!
感谢你阅读这篇文章。希望你觉得有用。我现在正在建立一个新的创业公司,叫做 EnVsion !在 EnVsion,我们正在为 UX 的研究人员和产品团队创建一个中央存储库,以从他们的用户采访视频中挖掘见解。当然我们用人工智能来做这个。).
如果你是一名 UX 研究员或产品经理,正忙于从你与用户和客户的视频通话中提取真知灼见,那么 EnVsion 就是你的选择!
使用深度学习教授汽车驾驶-转向角度预测
The car of the past… or the future?
这是 Udacity 自动驾驶汽车工程师纳米学位 第一学期的项目 3。你可以在github上找到与这个项目相关的所有代码。你也可以阅读我以前项目的帖子:
- 项目 1: 利用计算机视觉检测车道线
- 项目二: 交通标志分类使用深度学习
近年来,尤其是自从十年前 Darpa 大挑战大赛取得成功以来,全自动驾驶汽车的发展速度大大加快。许多组件组成了一辆自动驾驶汽车,其中一些最关键的组件是传感器和为其提供动力的人工智能软件。此外,随着计算能力的增加,我们现在能够训练复杂和深度的神经网络,这些网络能够学习视觉和视觉以外的关键细节,并成为汽车的大脑,了解车辆的环境并决定接下来要采取的决定。
在这篇文章中,我们将讲述如何训练深度学习模型来预测方向盘角度,并帮助虚拟汽车在模拟器中自动驾驶。该模型使用 Keras 创建,依靠 Tensorflow 作为后端。
项目设置
作为这个项目的一部分,我们提供了一个用 Unity 编写的模拟器,它有两种模式:
- *训练模式:*我们手动驾驶车辆并收集数据
- *自主模式:*车辆根据从收集的数据训练的模型自动驾驶
数据记录保存在 csv 文件中,包含图像路径以及方向盘角度、油门和速度。我们只关心这个项目的方向盘角度和图像。
如下图所示,模拟器包含两条轨道。右侧的赛道(赛道 2)比赛道 1 更难,因为它包含斜坡和急转弯。
The Udacity Simulator
这个项目实际上是受 NVIDIA 研究人员的论文“自动驾驶汽车的端到端学习”的启发,NVIDIA 研究人员通过训练卷积神经网络来预测方向盘角度,根据安装在汽车前面的三个摄像头(左、中、右)捕捉的转向角度数据和图像,成功让汽车自动驾驶。经过训练的模型能够仅使用中央摄像头准确驾驶汽车。下图显示了创建这种高效模型的过程。
NVIDIA process for training their CNN based on 3 cameras and steering wheel angle data
与 NVIDIA 不同,他们在现实世界中进行自动驾驶,我们将在模拟器中教会我们的汽车驾驶。然而,同样的原则也应该适用。由于最近报道了模拟在为 Waymo 等公司开发自动驾驶技术中如何发挥关键作用,我们进一步支持了这一说法。
数据集
我们最终使用了 4 个数据集:
- 轨道 1 上的 Udacity 数据集
- 轨道 1 上手动创建的数据集(我们将其命名为标准数据集)
- 另一个手动创建的数据集在赛道 1 上,我们开车接近界限,恢复以教导模型如何避免出界——在现实世界中,这将被称为鲁莽驾驶或酒后驾驶
- 轨道 2 上手动创建的数据集
请注意,在我们所有手动创建的数据集中,我们在两个方向上驱动,以帮助我们的模型一般化。
数据集探索
然而,在分析我们的数据集捕捉到的转向角度时,我们很快意识到我们有一个问题:数据非常不平衡,绝大多数方向盘数据是中性的(即 0)。这意味着,除非我们采取纠正措施,否则我们的模型将偏向直线行驶。
Distribution of steering wheel angles across datasets
然而,请注意,轨道 2 上的数据显示了更多的可变性,有许多急转弯,正如我们对这种轨道的预期。即使在这种情况下,人们仍然强烈倾向于直行。
数据集分割
最后,我们决定创建一个集合训练数据集,由 Udacity 数据集、我们的恢复数据集和来自 track 2 的数据集组成。我们决定使用来自路线 1 的标准数据集作为验证集。
frames = [recovery_csv, udacity_csv, track2_csv]
ensemble_csv = pd.concat(frames)
validation_csv = standard_csv
这帮助我们从近 55K 训练图像和潜在的 44K 验证图像开始。
数据扩充
我们有大量的数据点,但遗憾的是,大多数数据显示汽车以中性方向盘角度行驶,我们的汽车倾向于直线行驶。下面的示例显示了我们的第一个模型,其中没有对训练数据集进行平衡:
first stab at getting the car to drive autonomously…
此外,轨道上也有阴影,可能会使模型陷入混乱。该模型还需要学会正确驾驶,无论汽车是在道路的左侧还是右侧。因此,我们必须找到一种方法来人为地增加和改变我们的图像和转向角度。为此,我们求助于数据增强技术。
摄像机和转向角度校准
首先,我们将转向角校准偏移添加到由左或右摄像机捕获的图像:
- 对于左边的摄像机,我们希望汽车转向右边(正偏移)
- 对于右边的摄像机,我们希望汽车转向左边(负偏移)
st_angle_names = ["Center", "Left", "Right"]
st_angle_calibrations = [0, 0.25, -0.25]
以上值是根据经验选择的。
图像水平翻转
因为我们希望我们的汽车无论在路上的位置如何都能够自动转向,所以我们对一部分图像应用了水平翻转,并自然地反转了原始转向角度:
def fliph_image(img):
"""
Returns a horizontally flipped image
"""
return cv2.flip(img, 1)
Original vs flipped image
变暗图像
由于阴影或其他原因,我们的轨道的某些部分变得更暗,我们还通过将所有 RGB 颜色通道乘以从一个范围内随机选取的标量来使图像的一部分变暗:
def change_image_brightness_rgb(img, s_low=0.2, s_high=0.75):
"""
Changes the image brightness by multiplying all RGB values by the same scalacar in [s_low, s_high).
Returns the brightness adjusted image in RGB format.
"""
img = img.astype(np.float32)
s = np.random.uniform(s_low, s_high)
img[:,:,:] *= s
np.clip(img, 0, 255)
return img.astype(np.uint8)
Original vs darkened image
随机阴影
由于我们有时会有被阴影覆盖的轨迹,我们还必须训练我们的模型来识别它们,并且不被它们吓到。
def add_random_shadow(img, w_low=0.6, w_high=0.85):
"""
Overlays supplied image with a random shadow polygon
The weight range (i.e. darkness) of the shadow can be configured via the interval [w_low, w_high)
"""
cols, rows = (img.shape[0], img.shape[1])
top_y = np.random.random_sample() * rows
bottom_y = np.random.random_sample() * rows
bottom_y_right = bottom_y + np.random.random_sample() * (rows - bottom_y)
top_y_right = top_y + np.random.random_sample() * (rows - top_y)
if np.random.random_sample() <= 0.5:
bottom_y_right = bottom_y - np.random.random_sample() * (bottom_y)
top_y_right = top_y - np.random.random_sample() * (top_y) poly = np.asarray([[ [top_y,0], [bottom_y, cols], [bottom_y_right, cols], [top_y_right,0]]], dtype=np.int32)
mask_weight = np.random.uniform(w_low, w_high)
origin_weight = 1 - mask_weight
mask = np.copy(img).astype(np.int32)
cv2.fillPoly(mask, poly, (0, 0, 0))
#masked_image = cv2.bitwise_and(img, mask)
return cv2.addWeighted(img.astype(np.int32), origin_weight, mask, mask_weight, 0).astype(np.uint8)
Original vs shadowed image
向左/向右/向上/向下移动图像
为了应对大量的中性角,并为数据集提供更多的变化,我们对图像应用随机偏移,并为横向偏移的每个像素的转向角添加给定的偏移。在我们的例子中,我们根据经验决定对每个向左或向右移动的像素加(或减)0.0035。向上/向下移动图像应该使模型相信它是在向上/向下的斜坡上。从实验中,我们相信这些横向移动可能是让汽车正常行驶所需的最重要的增强。
# Read more about it here: http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_imgproc/py_geometric_transformations/py_geometric_transformations.html
def translate_image(img, st_angle, low_x_range, high_x_range, low_y_range, high_y_range, delta_st_angle_per_px):
"""
Shifts the image right, left, up or down.
When performing a lateral shift, a delta proportional to the pixel shifts is added to the current steering angle
"""
rows, cols = (img.shape[0], img.shape[1])
translation_x = np.random.randint(low_x_range, high_x_range)
translation_y = np.random.randint(low_y_range, high_y_range)
st_angle += translation_x * delta_st_angle_per_px translation_matrix = np.float32([[1, 0, translation_x],[0, 1, translation_y]])
img = cv2.warpAffine(img, translation_matrix, (cols, rows))
return img, st_angle
Original vs shifted image
图像增强管道
我们的图像增强功能很简单:每张提供的图像都经过一系列的增强,每一次增强都以 0 到 1 之间的概率 p 发生。增强图像的所有代码都被委托给上述适当的增强器函数。
def augment_image(img, st_angle, p=1.0):
"""
Augment a given image, by applying a series of transformations, with a probability p.
The steering angle may also be modified.
Returns the tuple (augmented_image, new_steering_angle)
"""
aug_img = img
if np.random.random_sample() <= p:
aug_img = fliph_image(aug_img)
st_angle = -st_angle
if np.random.random_sample() <= p:
aug_img = change_image_brightness_rgb(aug_img)
if np.random.random_sample() <= p:
aug_img = add_random_shadow(aug_img, w_low=0.45)
if np.random.random_sample() <= p:
aug_img, st_angle = translate_image(aug_img, st_angle, -60, 61, -20, 21, 0.35/100.0)
return aug_img, st_angle
Keras 图像生成器
由于我们在训练模型时会动态生成新的和增强的图像*,因此我们创建了一个 Keras 生成器来生成每批的新图像:*
*def generate_images(df, target_dimensions, img_types, st_column, st_angle_calibrations, batch_size=100, shuffle=True,
data_aug_pct=0.8, aug_likelihood=0.5, st_angle_threshold=0.05, neutral_drop_pct=0.25):
"""
Generates images whose paths and steering angle are stored in the supplied dataframe object df
Returns the tuple (batch,steering_angles)
"""
# e.g. 160x320x3 for target_dimensions
batch = np.zeros((batch_size, target_dimensions[0], target_dimensions[1], target_dimensions[2]), dtype=np.float32)
steering_angles = np.zeros(batch_size)
df_len = len(df)
while True:
k = 0
while k < batch_size:
idx = np.random.randint(0, df_len) for img_t, st_calib in zip(img_types, st_angle_calibrations):
if k >= batch_size:
break
row = df.iloc[idx]
st_angle = row[st_column]
# Drop neutral-ish steering angle images with some probability
if abs(st_angle) < st_angle_threshold and np.random.random_sample() <= neutral_drop_pct :
continue
st_angle += st_calib
img_type_path = row[img_t]
img = read_img(img_type_path)
# Resize image
img, st_angle = augment_image(img, st_angle, p=aug_likelihood) if np.random.random_sample() <= data_aug_pct else (img, st_angle)
batch[k] = img
steering_angles[k] = st_angle
k += 1
yield batch, np.clip(steering_angles, -1, 1)*
请注意,我们能够在每批中减少一定比例的中性角度,并保持(即不增加)一定比例的图像。
下图显示了一批图像中的一小部分增强图像:
Some augmented images from a batch
此外,那些增强图像的转向角的伴随直方图显示了更多的平衡:
Distribution of steering angles over batch
模型
我们最初尝试了 VGG 架构的一个变体,层次更少,没有迁移学习,但努力获得令人满意的结果。最终,我们选定了 NVIDIA 白皮书中使用的架构,因为它为我们提供了最佳结果:
NVIDIA CNN Architecture
模型调整
然而,我们对模型做了一些细微的调整:
- 我们裁剪图像的顶部以排除地平线(它在立即确定转向角度时不起作用)
- 我们在模型中将图像的大小调整为 66x200 ,作为早期图层之一,以利用 GPU 的优势
- 我们在每个激活函数后应用批处理标准化以加快收敛
- 第二密集层的输出大小为 200,而不是 100
模型架构
该模型的完整架构如下:
- 输入图像为 160x320(高 x 宽格式)
- 通过移除一半高度(80 像素),图像在顶部被垂直裁剪,从而生成 80x320 的图像
- 裁剪后的图像被归一化,以确保我们的像素分布的平均值为 0
- 使用 Tensorflow 的TF . image . resize _ images将裁剪后的图像调整为 66x200**
- 我们采用一系列 3×5×5 的卷积层,步长为 2×2。每个卷积层之后都有一个批处理标准化操作,以改善收敛性。随着我们深入网络,每层的深度分别为 24、36 和 48
- 我们应用 2 个连续的 3×3 卷积层,深度为 64。每个卷积层之后紧接着是批处理标准化操作
- 我们在这一阶段拉平输入,进入完全连接阶段
- 我们应用了一系列完全连接的层,尺寸逐渐减小:1164、200、50 和 10
- 输出层的大小显然是 1,因为我们只预测一个变量,方向盘角度。
激活和正规化
除了最后一层,所有层使用的激活函数是 ReLU 。我们也尝试了 ELU ,但是用 ReLU + BatchNormalization 得到了更好的结果。我们对输出层使用均方误差激活,因为这是一个回归问题,而不是分类问题。
如前一节所述,我们使用批处理规范化来加速收敛。我们确实尝试了某种程度的辍学,但没有发现任何明显的差异。我们相信,我们在每一批生成新图像并丢弃一些中性角图像有助于减少过度拟合。此外,我们没有对我们的 NVIDIA 网络应用任何 MaxPool 操作(尽管我们尝试了 VGG 启发的操作),因为这需要对架构进行重大改变,因为我们会更早地降低维度。此外,我们没有时间尝试 L2 的正规化,但计划在未来尝试。
培训和结果
我们使用亚当作为优化器来训练模型,学习率为 0.001。经过大量的参数调整和多种模型的实验,我们最终找到了一种能够让我们的虚拟汽车在两条赛道上自动驾驶的模型。
Self-driving around track 1
我们可以看到车辆如何有效地在赛道 2 上驶下陡坡。
Self-driving around track 2
我们还展示了在赛道 2 上自动驾驶时前置摄像头看到的内容。我们可以看到汽车如何试图坚持在车道上行驶,而不是在中间行驶,因为我们自己在数据收集阶段努力只在道路的一侧行驶。这表明该模型确实学会了保持在自己的车道内。
What the car sees on track 2
录像
最重要的是,我甚至为你创建了一个视频蒙太奇,使用 Tron Legacy 的 The Grid 作为背景音乐。尽情享受吧!
Video of behavioural cloning
结论
我们已经表明,使用深度神经网络和大量的数据增强技术来创建可靠地预测车辆方向盘角度的模型是可能的。虽然我们已经取得了令人鼓舞的成果,但我们希望在未来探索以下方面:
- 在模型中考虑速度和油门
- 让汽车以 15-20 英里/小时的速度行驶
- 通过迁移学习实验基于模型的 VGG/ResNets/Inception
- 使用来自使用 Udacity 数据集的人的递归神经网络,就像这篇论文中那样
- 通过 comma.ai 阅读 学习驾驶模拟器 论文并尝试实现他们的模型
- 强化学习实验
*可以看出,我们可以探索许多领域来进一步推动这个项目,并获得更有说服力的结果。从这个项目中最重要的一点是**数据为王:*如果没有所有这些图像和转向角度,以及它们潜在的无限扩展,我们就无法建立一个足够强大的模型。
从个人角度来看,我非常喜欢这个项目,这是迄今为止最难的项目,因为它使我能够获得更多关于超参数调整、数据扩充和数据集平衡以及其他重要概念的实践经验。我觉得我对神经网络架构的直觉也加深了。
承认
我还要感谢我的 Udacity 导师 Dylan,感谢他的支持和合理的建议,也感谢我的同学之前的 Udacity 学生,他们通过博客帖子解释了他们是如何着手这个项目的。我从阅读他们的帖子中受到启发:他们无疑帮助我更好地理解了成功完成这个项目所需的概念。
感谢你阅读这篇文章。希望你觉得有用。我现在正在建立一个新的创业公司,叫做 EnVsion !在 EnVsion,我们正在为 UX 的研究人员和产品团队创建一个中央存储库,以从他们的用户采访视频中挖掘见解。当然我们用人工智能来做这个。).
如果你是 UX 的研究人员或产品经理,对与用户和客户的视频通话感到不知所措,那么 EnVsion 就是为你准备的!
你也可以关注我的 推特 。
教汽车看东西——使用计算机视觉进行高级车道检测
Lane With Motion Blur (from Pexels)
这是 Udacity 自动驾驶汽车工程师纳米学位 第一学期的项目 4。你可以在github上找到与这个项目相关的所有代码。你也可以阅读我以前项目的帖子:
- 项目 1: 利用计算机视觉检测车道线
- 项目二: 交通标志分类使用深度学习
- 项目三: 转向角度预测利用深度学习
识别道路上的车道是所有人类驾驶员执行的共同任务,以确保他们的车辆在行驶时处于车道限制内,从而确保交通顺畅,并将与附近车道上的其他车辆发生碰撞的可能性降至最低。同样,这也是自动驾驶汽车要完成的一项关键任务。事实证明,使用众所周知的计算机视觉技术来识别道路上的车道标志是可能的。我们将介绍如何使用各种技术来识别和绘制车道内侧,计算车道曲率,甚至估计车辆相对于车道中心的位置。
为了检测并绘制一个呈现汽车当前所在车道形状的多边形,我们构建了一个由以下步骤组成的管道:
- 从一组棋盘图像计算摄像机标定矩阵和畸变系数
- 图像失真消除
- 应用颜色和梯度阈值来聚焦车道线
- 通过透视变换生成鸟瞰图图像
- 使用滑动窗口寻找热点车道线像素
- 拟合二次多项式以识别构成车道的左线和右线
- 车道曲率和偏离车道中心的计算
- 在图像上扭曲和绘制车道边界以及车道曲率信息
我相信一张图胜过千言万语,所以在这里:
Diagram of our Lane Detection Pipeline
相机校准和图像失真消除
我们将采取的第一步是找到校准矩阵,以及用于拍摄道路照片的摄像机的失真系数。这是必要的,因为相机镜头的凸面形状使光线在进入针孔时发生弯曲,从而导致真实图像失真。因此,现实世界中的直线可能不会再出现在我们的照片上。
为了计算相机的变换矩阵和失真系数,我们使用同一台相机拍摄的一个棋盘在一个平面上的多张照片。OpenCV 有一个方便的方法叫做findchesboardcorners,它将识别黑白方块相交的点,并以这种方式逆向工程扭曲矩阵。下图显示了在样本图像上跟踪的已识别棋盘角:
9x6 corners of chessboard found
我们可以看到角点非常清晰。接下来,我们在从不同角度拍摄的多个棋盘图像上运行我们的棋盘寻找算法,以识别图像和对象点来校准相机。前者指的是我们的 2D 映射中的坐标,而后者表示这些图像点在 3D 空间中的真实坐标(对于我们的棋盘图像,z 轴或深度= 0)。这些映射使我们能够找出如何正确地消除从同一台相机拍摄的图像上的失真。你可以在下图中看到它的有效性:
original vs undistorted chessboard images
我们现在可以扭转所有图像的失真,如下图所示:
sample of original vs undistorted images
阈值处理
我们在这一部分应用颜色和边缘阈值来更好地检测线条,并使其更容易找到最好地描述我们的左右车道的多项式。
我们首先探索我们应该采用哪些颜色空间来增加我们检测车道的机会并促进梯度阈值步骤的任务。
颜色阈值
我们对不同的色彩空间进行了实验,以了解我们应该使用哪种色彩空间和通道来最有效地分离车道线:
from top to bottom: RGB, HLS, HSV, LAB color spaces split in their 3 channels
在 RGB 分量上,我们看到蓝色通道在识别黄线方面最差,而红色通道似乎给出了最好的结果。
对于 HLS 和 HSV,色调通道产生极其嘈杂的输出,而 HLS 的饱和通道似乎给出了强有力的结果;比 HSV 的饱和通道要好。相反,HSV 的值通道给出了非常清晰的灰度图像,特别是在黄线上,比 HLS 的亮度通道好得多。
最后,实验室的 A 通道表现不佳,而 B 通道擅长识别黄线。但是,在识别黄线和白线时发光的是亮度通道(没有双关的意思)。
在这个阶段,我们面临着各种有利有弊的选择。我们的目标是在给定的颜色通道上找到合适的阈值来突出车道的黄线和白线。实际上,有许多方法可以实现这一结果,但我们选择使用 HLS,因为我们已经知道如何从项目 1:简单车道检测中设置黄色和白色车道线的阈值。
下面的代码显示了我们如何对 HLS 上的白色和黄色(我们的车道颜色)进行阈值处理,并生成二进制图像:
def compute_hls_white_yellow_binary(rgb_img):
"""
Returns a binary thresholded image produced retaining only white and yellow elements on the picture
The provided image should be in RGB format
"""
hls_img = to_hls(rgb_img)
# Compute a binary thresholded image where yellow is isolated from HLS components
img_hls_yellow_bin = np.zeros_like(hls_img[:,:,0])
img_hls_yellow_bin[((hls_img[:,:,0] >= 15) & (hls_img[:,:,0] <= 35))
& ((hls_img[:,:,1] >= 30) & (hls_img[:,:,1] <= 204))
& ((hls_img[:,:,2] >= 115) & (hls_img[:,:,2] <= 255))
] = 1
# Compute a binary thresholded image where white is isolated from HLS components
img_hls_white_bin = np.zeros_like(hls_img[:,:,0])
img_hls_white_bin[((hls_img[:,:,0] >= 0) & (hls_img[:,:,0] <= 255))
& ((hls_img[:,:,1] >= 200) & (hls_img[:,:,1] <= 255))
& ((hls_img[:,:,2] >= 0) & (hls_img[:,:,2] <= 255))
] = 1
# Now combine both
img_hls_white_yellow_bin = np.zeros_like(hls_img[:,:,0])
img_hls_white_yellow_bin[(img_hls_yellow_bin == 1) | (img_hls_white_bin == 1)] = 1
return img_hls_white_yellow_bin
结果如下所示:
undistorted vs HLS binary color image (filtering for yellow and white)
正如你在上面看到的,我们的 HLS 颜色阈值在图像上取得了很好的效果。阈值处理与前面黄线上的树的阴影有点冲突。我们认为梯度阈值在这种情况下会有所帮助。
梯度阈值
我们使用 Sobel 算子来识别梯度,即图像中颜色强度的变化。较高的值表示强烈的梯度,因此颜色变化剧烈。
我们决定使用实验室的 L 通道作为单通道图像,作为以下 sobel 函数的输入。
我们试验了许多参数和不同的 Sobel 操作(所有这些都可以在这个 Jupyter 笔记本上看到),得出了这个最终结果:
Results of multiple sobel operations
我们从底部选择第二个图像作为我们的最佳结果。请注意,在我们选择的图像上,我们应用了 15x15 像素的内核,从而有效地平滑了像素,产生了更清晰的二进制图像。
结合两者
我们自然地将彩色和 Sobel 阈值化的二值图像结合起来,并得到以下结果:
combined color and gradient thresholded images in ®GB and binary formats
在左边的图像中,所有的绿色像素被我们的 Sobel 阈值化保留,而蓝色像素被我们的 HLS 颜色阈值化识别。结果非常令人鼓舞,似乎我们已经找到了正确的参数,以稳健的方式检测车道。接下来,我们对我们的图像进行透视变换,并生成车道的鸟瞰图。
透视变换
我们现在需要在 2D 图像中定义一个梯形区域,该区域将通过透视变换转换为鸟瞰图,如下所示:
trapezoid of lane on road
然后,我们定义 4 个额外的点,它们形成一个矩形,映射到我们的源梯形中的像素:
dst_pts = np.array([[200, bottom_px], [200, 0], [1000, 0], [1000, bottom_px]], np.float32)
透视变换产生以下类型的图像:
bird’s eye view on both straight and curved lanes
把所有的放在一起
我们可以看到,我们的透视变换保持直线笔直,这是一个必需的健全性检查。然而,曲线在上面的例子中并不完美,但是它们也不会给我们的算法带来无法克服的问题。
现在,我们可以将阈值处理应用于鸟瞰图图像:
test images with perspective transform, color and gradient thresholds applied
柱状图
然后,我们在图像的下半部分计算 y 方向上的二进制阈值图像的直方图,以识别像素强度最高的 x 位置:
Left: birds eye view binary representation of lane. Right: histogram of pixel intensities of the image
寻找线条并绘制车道区域
推拉窗
因为我们现在知道了最有可能产生车道线的像素的起始 x 位置(从图像的底部开始),我们运行了一个滑动窗口搜索,试图“捕获”我们车道线的像素坐标。
从那时起,我们通过 numpy 的 polyfit 简单地计算一个二次多项式,找到最适合左右车道线的曲线系数。
我们改进算法的一种方法是保存先前为帧 t-1 计算的系数,并尝试从这些系数中找到我们的车道像素。然而,当我们没有找到足够的车道线像素(少于总非零像素的 85%)时,我们恢复到滑动窗口搜索,以帮助提高我们在车道周围拟合更好曲线的机会。
车道曲率
我们还通过计算与车道线相切的最小圆的半径来计算车道曲率,在直线车道上,半径会很大。我们必须通过定义适当的像素高度与车道长度以及像素宽度与车道宽度的比率,将像素空间转换为米(也称为真实世界单位):
# Height ratio: 32 meters / 720 px
self.ym_per_px = self.real_world_lane_size_meters[0] / self.img_dimensions[0]# Width ratio: 3.7 meters / 800 px
self.xm_per_px = self.real_world_lane_size_meters[1] / self.lane_width_px
我试图通过参考来自这个资源的数据,在我的鸟瞰图像上手动估计道路的长度:每次汽车行驶都超过 40 英尺(约 12.2 米)。在我的样本图像上,鸟瞰图似乎覆盖了大约 32 米。宽度保持在 3.7 米,符合美国高速公路标准。你可以通过下面的链接找到更多关于曲率半径的数学基础的信息。
我们还通过偏移车道左右线的起始(即底部)坐标的平均值,减去中点作为偏移量,并乘以车道的像素与真实世界宽度的比率,来计算汽车与车道中心的距离。
取消绘制车道区域
最后,我们用绿色绘制车道的内侧,并取消扭曲图像,从而从鸟瞰图移动到原始的未失真图像。此外,我们用我们的车道检测算法的小图像覆盖这个大图像,以便一帧一帧地更好地感受正在发生的事情*。我们还添加了关于车道曲率和车辆中心位置的文本信息:*
Sample result of the output of detected lanes
决赛成绩
下面的 gif 显示了我们为项目视频构建了一个强大的车道检测管道:
Lane detection gif
此外,我在 Youtube 上上传了一个视频,我在项目视频上画了车道,并添加了额外的信息,如车道曲率近似值。背景音乐是弗林的儿子*(显然来自Tron:Legacy😎).尽情享受吧!*
Video of lanes detected on project video
结论
这是一个令人兴奋但困难的项目,感觉与我们之前的两个深度学习项目非常不同。我们已经介绍了如何执行相机校准、颜色和梯度阈值,以及透视变换和滑动窗口来识别车道线!滑动窗口代码最初特别难理解,但是经过长时间的调试和评论(都在我的笔记本上),我终于理解了每一行!
我们认为该项目可以进行许多改进,例如:
- 尝试 LAB 和 YUV 色彩空间,以确定我们是否可以产生更好的色彩阈值
- 使用卷积而不是滑动窗口来识别热点像素
- 产生先前帧的行系数的指数移动平均值,并在我们的像素检测失败时使用它
- 更好地检测“捕获”的像素中的异常(例如,一些完全离线的非零像素)并剔除它们
- 应用本项目未涵盖的其他相关计算机视觉技术
此外,我们需要建立一个更强大的渠道来成功完成这个项目中的两个挑战视频。
像往常一样,我要感谢我的导师迪伦的建议和支持,以及我在 Udacity 的同事,以前和现在的同事,他们整理了很多优秀的文章,激励了我。
感谢你阅读这篇文章。希望你觉得有用。我现在正在创建一个名为env sion的新公司!在 EnVsion,我们正在为 UX 的研究人员和产品团队创建一个中央存储库,以从他们的用户采访视频中挖掘见解。当然我们用人工智能来做这个。).
如果你是一名 UX 的研究人员或产品经理,对与用户和客户的视频通话感到不知所措,那么 EnVsion 就是为你准备的!
你也可以关注我的 推特 。
教汽车看东西——使用机器学习和计算机视觉进行车辆检测
这是 Udacity 自动驾驶汽车工程师纳米学位 第一学期的期末项目。你可以在github上找到与这个项目相关的所有代码。你也可以阅读我以前项目的帖子:
- 项目 1: 利用计算机视觉检测车道线
- 项目二: 交通标志分类使用深度学习
- 项目三: 转向角度预测利用深度学习
- 项目 4: 利用计算机视觉进行高级车道检测
当我们开车时,我们不断关注我们的环境,因为这关系到我们和其他许多人的安全。我们特别注意潜在障碍物的位置,无论是其他汽车、行人还是路上的物体。同样,随着我们开发为自动驾驶汽车提供动力所必需的智能和传感器,这种汽车也能检测障碍物是至关重要的,因为这可以加强汽车对环境的理解。最重要的一种 ostacles 是检测道路上的其他车辆,因为它们很可能是我们车道或邻近车道上最大的物体,因此构成了潜在的危险。
从传统的计算机视觉技术到深度学习技术,在整个文献中已经开发了许多障碍检测技术。在本练习中,我们通过采用一种称为梯度方向直方图(HOG) 的传统计算机视觉技术,结合一种称为支持向量机(SVM) 的机器学习算法,来构建一个车辆检测器。
资料组
Udacity 慷慨地提供了一个具有以下特征的平衡数据集:
- 约 9K 的车辆图像
- 非车辆的~ 9K 图像
- 所有图像都是 64x64
数据集来自 GTI 车辆图像数据库、 KITTI Vision 基准套件,以及从项目视频本身提取的例子。后者要大得多,没有用于这个项目。然而,这在未来将是一个很好的补充,特别是当我们计划使用深度学习建立一个分类器时。您可以从下面的数据集中看到一个图像示例:
Sample of vehicles and non-vehicles from dataset
我们可以清楚地看到车辆和非车辆图像。非车辆图像往往是道路的其他元素,如沥青、路标或路面。区别非常明显。大多数图像也将车辆显示在中央,但方向不同,这很好。此外,还有各种各样的汽车类型和颜色,以及照明条件。
探索特性
方向梯度直方图(HOG)
Navneet Dalal 和 Bill Triggs 在他们的论文《人类检测的方向梯度直方图中展示了令人印象深刻的结果后,使用 HOG 进行检测受到了欢迎。Satya Mallick 在这篇文章中很好地解释了这个算法,对于那些想要更好地掌握 HOG 的人来说。
我们首先在 RGB 图像上探索了 HOG 算法中以下值的不同配置:
- 方位数量(用 o 表示)
- 每个单元格的像素(用 px/c 表示)
每个块的单元最初固定为 2(用 c/bk 表示)。下图显示了在 RGB 格式的样本车辆图像上获得的结果:
Results of different configurations for HOG
从纯粹的观察来看,它看起来像一个猪配置:
- 11 个方向
- 每个单元格 14 个像素
- 每个区块 2 个单元
产生车辆最独特的坡度。我们还没有对每个模块的不同单元进行实验,所以现在让我们来尝试一下。
HOG results with different cells per block
对于人眼来说,我们在视觉上没有注意到明显的差异。理想情况下,我们希望减少特征空间以加快计算速度。我们现在决定每块 3 个细胞。
色彩空间
我们现在必须为我们的配置探索最合适的颜色空间,因为看起来我们在 3 个 RGB 通道上的 HOG 特征太相似了,因此感觉我们没有生成具有足够变化的特征。
我们在众多色彩空间中生成以下输出:
HOG image across all channels in different color spaces
对于某些颜色通道,很难解释 HOG 的结果。有趣的是,YUV、YCrCb 和 LAB 中的第一个颜色通道似乎足以捕捉我们正在寻找的渐变。在 HSV 和 HLS 中,HOG 分别在值和亮度通道上捕捉车辆的最重要特征。
为了证实我们的假设,让我们尝试一个不同的车辆图像:
Same HOG settings but on a different image
休斯顿,我们这里有一个问题 …在如上图这样的暗图像上,我们可以观察到携带最多光信息的信道上的 HOG 产生了不好的结果。**因此,我们必须考虑所有的颜色通道,以捕捉最多的特征。**最后,我们的配置如下:
- ycr CB 色彩空间的所有通道
- 11的拱起方向
- 每 14 个单元的 HOG 像素
- 每块猪细胞 2 个
我们还将添加颜色信息来增强我们的功能集。为此,我们只需使用 32 个箱生成所有颜色通道的直方图,如下所示:
def color_histogram(img, nbins=32, bins_range=(0, 256)):
"""
Returns the histograms of the color image across all channels, as a concatenanted feature vector
"""
# Compute the histogram of the color channels separately
channel1_hist = np.histogram(img[:,:,0], bins=nbins, range=bins_range)
channel2_hist = np.histogram(img[:,:,1], bins=nbins, range=bins_range)
channel3_hist = np.histogram(img[:,:,2], bins=nbins, range=bins_range) # Concatenate the histograms into a single feature vector and return it
return np.concatenate((channel1_hist[0], channel2_hist[0], channel3_hist[0]))
分类者
分类器负责将我们提交的图像分为车辆或非车辆类别。为此,我们必须采取以下步骤:
- 从数据集中加载我们的图像
- 提取我们想要的特征
- 使这些特征正常化
- 分割用于训练和测试的数据集
- 使用适当的参数构建分类器
- 在训练数据上训练分类器
正如上一节所讨论的,我们决定只保留一个特征:在 YCrCb 图像的 Y 通道上计算的 HOG 特征向量。
我们随机分割数据集,留下 20%用于测试。此外,我们通过使用一个sk learn . preprocessing . standard scalernormalizer 来缩放数据。
我们没有足够的时间对许多分类器进行实验,所以选择使用支持向量机 (SVMs),因为它们通常与 HOG 结合用于对象检测问题。此外,我们使用了带有内核的 SVC,因为它提供了最好的精度,但是比线性 SVC 慢。我们接受了这种折衷,因为当我们在一系列图像上测试时,使用 rbf 核的 SVC 的检测更强。
使用 GridSearchCV 函数获得了核类型(线性或 rbf )、 C ( 1,100,1000,1000 )和伽马 ( 自动,0.01,0.1,1 )中的理想参数。最佳配置实现了超过 99%的准确度,并具有以下参数:
- 内核 = rbf
- C = 100
- 伽玛 =自动
推拉窗
我们创建了多个维度的滑动窗口,范围从 64x64 到 256x256 像素,以针对分类器测试图像的部分,并仅保留正面预测。我们通常从屏幕底部滑动较大的窗口,因为这将对应于车辆出现最大的位置。较小的窗口会在屏幕上滑得更高。此外,我们能够配置小区重叠,并且当前已经将它设置为 1 以获得最大覆盖(即,每 14 像素*比例重叠,其中比例 1 的最小窗口是 64x64)。我们停止尝试检测 y 方向上任何低于 350 像素的车辆(即屏幕上图像的较高部分)。下图显示了单元格重叠设置为 4 的重叠滑动窗口示例:
Sliding windows of different sizes with cell overlap = 4
热图和阈值
分类器有时会误分类图像中实际上不是车辆的部分。为了避免突出显示视频中的车辆,我们利用我们通过多尺寸滑动窗口创建的冗余,并计算我们的分类器在图像的给定部分的所有窗口中预测车辆的次数。我们首先使用scipy . ndimage . measurements’label函数来标记具有重叠窗口的对象。然后,我们通过确定我们检测到的对象能够适合的最小边界框来提取每个标签的位置。我们只保留图像中检测到的阈值被设置为特定值的部分。通过实验,我们发现阈值 4 足以在项目视频上获得稳定的结果。下图说明了热图和阈值处理的工作原理:
Detected vehicles with heatmaps and thresholding
第一个迷你热图代表来自分类器的原始原始检测,而第二个显示阈值区域,其中红色的强度随着重叠窗口数量的增加而增加。右边最后一个小图像显示了我们的分类器预测到车辆的所有窗口。在这个例子中,我们实际上使用了线性 SVC ,它比 rbf SVC 更容易预测错误。
帧聚合
为了进一步加强我们的管道,我们决定每隔 n 帧平滑所有检测到的窗口。为此,我们累积帧( n-1)f+1 到 nf 之间的所有检测到的窗口,其中 n 是表示我们所在的帧的组的正标量。我们已经创建了以下封装检测到的对象的类:
class DetectedObject:
"""
The DetectedObject class encapsulates information about an object identified by our detector
"""
def __init__(self, bounding_box, img_patch, frame_nb):
self.bounding_box = bounding_box
self.img_patch = img_patch
self.frame_nb = frame_nb
self.centroid = (int((bounding_box[0][0] + bounding_box[1][0]) / 2), int((bounding_box[0][1] + bounding_box[1][1]) / 2))
self.similar_objects = []
...
每当我们在组中的当前帧或下一帧上检测到新的对象时,我们检查我们过去是否检测到类似的对象,如果是,我们附加类似的对象,从而增加该对象在多个帧上的计数。在帧 nf* 处,我们仅保留具有超过 m 个检测计数的检测到的对象(及其相关联的边界框),从而在流水线中实现某种类型的双重过滤(第一次过滤是关于重叠边界框的数量的阈值)。
在下面的 gif 上,你可以看到当我们有一个单一的边界框覆盖两辆汽车时和当每辆汽车都有自己的边界框时之间有一瞬间:帧聚合逻辑必须等到两个窗口出现足够的次数后才显示它们:
gif of vehicle detection with frame sampling
决赛成绩
下面的视频链接显示了对车辆的成功检测。由于这是第一学期的最后一个项目,我刚刚让使用 Tron Legacy 的 OST 的音轨片尾标题作为背景音乐——没有什么比这更合适的了😎。像往常一样享受吧!
Video montage of vehicle detection
丰富
这是一个棘手的项目,特别是对于那些选择更传统的计算机视觉和机器学习方法而不是深度学习的人来说。以下步骤相当耗时:
- 确定最合适的特征(HOG、图像颜色直方图等)
- 探索 HOG 参数+色彩空间的组合
- 应用网格搜索寻找最合适的分类器
此外,在我们的渠道中,我们还面临以下问题:
- 确定滑动窗口和重叠的正确位置
- 为重叠检测识别合适的阈值
- 采用合适的帧采样率
- 在多个帧中找到足够好的最小检测计数
- 聚集重叠检测的组合窗口维度
对于不是车辆但被分类器检测到的对象,流水线将失败,并且在足够多的重叠窗口上出现这种错误检测以突破所配置的阈值,并且在每组的最小数量的帧上始终如此。绘制的边界框并不总是完全适合车辆,并且每隔 n 帧重画一次,因此造成缺乏平滑度的印象。此外,与每 n 个帧进行批量聚合相反,可以通过使用 n 个帧的滚动窗口来改进帧聚合。
最后一个问题是我们的流水线太慢。我们不应该在整个屏幕上滑动窗口,只需要查看屏幕的一部分:例如,将来我们可以使用决策树来识别感兴趣的区域。我们还可以考虑减少滑动窗口的数量,以及采用像 LinearSVC 这样的更快的分类器来加速检测(但准确性也会显著下降)。尽管如此,这种车辆检测管道不太可能实时工作。
在未来,将采用深度学习方法,例如使用更快的 R-CNN 或 YOLO 架构,因为这些架构现在是检测问题的最先进技术,并且可以实时运行。然而,这是一个有价值的练习,可以更好地理解传统的机器学习技术,并在特征选择上建立直觉。此外,像 HOG 这样的技术的美丽和简单给我留下了深刻的印象,它仍然能够产生可靠的结果。
感谢
我要再次感谢我的导师迪伦,感谢他在这一学期对我的支持和建议。我也非常感谢 Udacity 设立了这样一个令人兴奋和具有挑战性的纳米学位,有着伟大的项目和优秀的材料。
我们站在巨人的肩膀上,因此我感谢人工智能、计算机视觉等领域的所有研究人员和爱好者,感谢他们通过论文和代码所做的工作和分享。没有这些资源,我就无法“借用”他们的想法和技术,并成功完成这个项目。
第一学期已经结束了🎉🎉。我将于 2018 年 1 月开始第二学期,与此同时,我将致力于深化我刚刚起步的人工智能技能,并恢复兼职项目的工作。感谢阅读,敬请关注!
感谢你阅读这篇文章。希望你觉得有用。我现在正在建立一个新的创业公司,叫做 EnVsion !在 EnVsion,我们正在为 UX 的研究人员和产品团队创建一个中央存储库,以从他们的用户采访视频中挖掘见解。当然我们用人工智能来做这个。).
如果你是一名 UX 研究员或产品经理,对与用户和客户的视频通话感到不知所措,那么 EnVsion 就是为你准备的!
你也可以关注我的 推特 。
在大会上讲授数据科学:第 1 周
本周,我开始向学生传授将他们转变为数据科学家的技能和技术,这些数据科学家是初创公司和大公司的宝贵团队成员,正在从根本上改变世界对数据的看法。
在旧金山,General Assembly (GA)将于 2017 年 1 月 30 日开始他们的第五期 数据科学沉浸式 课程。我将成为两位数据科学讲师之一,将我们的专业知识和行业经验带入课堂和课程。
我的行业经验包括为 Wine.com 和 NASA 构建可扩展的数据产品。对于 Wine.com,我使用 Spark 的机器学习库构建了一个推荐系统。这个系统根据每个用户对葡萄酒的喜好为他们提供个性化的推荐。在山景城的 NASA 中心,我为地理空间图像建立了一个可扩展的特征提取 ETL 管道。这个项目在他们内部的超级计算机昴宿星上运行。
这些项目和其他项目可以在我的 GitHub 欢迎页面找到。
我们的学生是工作专业人士,他们渴望改变和新的挑战,并强烈希望利用数据科学工具包来改善企业和人们的生活。他们的学术背景从数学、工程到商业都有。他们每个人都带来了独特的优势、工作经验和观点,这将交叉授粉的想法,并导致丰富的学习环境。
我的一些学生的个人兴趣包括萨尔萨和摇摆舞,以及交叉健身和踢拳。我的生活方式还包括各种形式的体育锻炼,比如跳探戈和奥运会举重。我几乎还不了解我的学生,但我们已经建立了关系,这将使接下来的 3 个月对每个人来说都是有趣而难忘的经历。
An inclusive culture for an inclusive community.
GA 不仅投资于学生的成果,也投资于教师。我对教师在培训和反馈方面获得的支持程度印象深刻。GA 培养主动学习和成长的教育思维方式,这种方式将继续为学生和教师带来成功的结果。
通过整合数据科学家的行业经验,GA 确保学生尽可能接受最新和行业相关的培训。在过去的两周,我一直在通过 GA 的讲师培训电路(ITC)工作,并帮助建立该课程的数据科学课程。
在那段时间里,我开始了解 GA 的学生——不仅仅是通过 DSI 项目,还有其他项目。当学生从事他们的项目,创造数据驱动的产品和解决方案时,有一种明显的兴奋感、协作和创新思维。
如果您对 GA 的 DSI 计划或我在数据科学领域的工作有任何疑问,请随时联系我。
此外,我们正在寻找客座演讲人走进教室,与学生分享他们所学的数据科学技能和技术如何解决现实世界的问题。如果你感兴趣,请告诉我!
在大会上讲授数据科学:第 4 周
砂砾。这就是我如何在每个工作日的早上 5:30 醒来,走进举重房,摇着水壶的铃铛,看着奥林匹克解除我眼中的睡意。
勇气是在为期 12 周的课程中学习数据科学基础知识所需要的,我的学生很快就发现了这一点。
第 2–3 周
我和我的教学团队教授了计算机科学、线性代数、统计学、概率等几门大学课程,并在两周半的时间里将它们的核心概念提炼出来。日复一日地频繁转移话题会让学生们有点迷失方向——如果你愿意的话,可以说是教育上的鞭打。当学生学习提到的低水平基础知识时,可能会有点迷失方向,因为他们正在学习看似孤立的技能和技术。对于他们来说,在课程中看到并应用这些技能和技术,利用机器学习和大数据综合创建令人惊叹的数据产品并优化业务运营还为时过早。
耐心点,年轻的杰迪们,基于系统的解决方案将会及时出现,因为现在你们将在光剑训练期间被盲目折叠,同时躲避盘旋在机器人周围的冲击波。
Those Web Development kids and their graffiti (shakes fist furiously in air).
第四周
第 4 周和第 3 周的末尾向学生介绍了机器学习的奇妙世界。是的,ML,计算机科学中的一个令人敬畏的领域,让你告诉你的朋友,你在与人工智能打交道——尽管是人工智能的一个狭窄子集。对于那些在斯坦福大学或加州大学伯克利分校等传统大学上过机器学习入门课程的人,或者在 GA 等训练营学习过 ML 的人,你会知道 ML 的教学首先是介绍线性回归和逻辑回归。你在传统大学通常得不到的是一个小班,有多名教师,致力于通过个性化的关注和定期的反馈来帮助学生取得成功。
我就是从那里进来的!是的,我很高兴通过机器学习及其所有许多方面来指导 GA 的下一批行业就绪数据科学家。下周—推进机器学习!
行业发言人
教学团队和我接触行业数据科学家,目的是让他们向学生介绍他们的工作。在第 3 周,我们有一位前 GA 学生讲述了他如何使用机器学习和 cleaver 合并开源数据集为他的太阳能公司节省了数万美元。
下周,MyFitnessPal 的一位数据科学家将讲述他围绕用户行为的 A/B 测试所做的工作。这些讲座帮助学生弥合他们在课堂上学到的知识与如何利用这些技能和技术解决现实世界的问题并为他们未来的创业创造巨大影响之间的差距。
我向我的学生脱帽致敬,他们不仅挺过了课程的难关,还迅速提高了技能和信心。看到他们在短短 4 周内取得如此大的进步,真是令人惊讶。行业就绪型数据科学家即将到来!
关于作者
Alexander 毕业于加州大学伯克利分校,获得物理学学士学位,毕业于 T2 大学,获得数据科学硕士学位。他热衷于应用数据科学来解决医疗保健、清洁能源和环境领域的挑战性问题。
在大会上讲授数据科学:第 5 周
A healthy mind is complemented by a healthy body.
本周,学生们开始思考他们想要为他们的顶点项目构建什么样的数据产品。GA 还为社区举办了一次晚宴,并邀请难民参加。这提醒了我为什么要成为一名数据科学家。
难民晚餐
We are a community of problem solvers and innovators with a vision for social progress. We are the counter-culture to our county’s current shift toward isolationism.
GA 为学生、教师和难民举办了一次内部晚宴。我遇到了几个有着惊人故事的人。战前,逃离叙利亚美好生活的人们。人们从一个国家跳到另一个国家——中东、亚洲、欧洲——寻求庇护,却遭到拒绝。二十多岁的年轻男女,要养家糊口的老男人——像你我这样的人。他们勇敢地在一个宣扬包容但实际上需要家庭工作经验的行业找工作。这提醒了我,出生在加利福尼亚的美国人是多么幸运。这也提醒了我为什么决定成为一名数据科学家。
2014 年春天从加州大学伯克利分校毕业后不久,我了解了数据科学和机器学习。ML 和大数据是所有技术创新背后的生命血液——这是行动所在,也是我关注的焦点。我读到了关于网飞的推荐系统、优步的优化司机服务以及其他令人难以置信的数据驱动产品的故事,这些产品让人类的生活变得更加便利。尽管数据科学的技术和技能满足了我的求知欲,但这个角色似乎没有多少人情味。 **我看到的大多是对数据科学作为另一种企业工具的颂扬。**我认为数据科学可能不适合我。
气候变化给人类带来了生存威胁。30 年前的今天,也就是昨天,我们需要转向清洁、可持续的能源。数据科学与太阳能等替代能源相结合,向世界展示了切断石油供应的市场化解决方案。正是教育、清洁能源、医疗保健以及建设一个环境可持续发展的社会方面的挑战,激励着我去解决难题。正是解决这些难题促使我成为一名数据科学家。
我不需要成为逃离人间地狱的难民,也能体会到我出生在这片叫做加利福尼亚的土地上是多么幸运。但与他们交谈提醒我,伴随着巨大的特权而来的是巨大的责任。
顶点项目
我的学生多种多样,他们的专业兴趣也是如此。他们对构建用于教育、清洁能源、招聘、金融等领域的智能工具感兴趣。
一名学生希望围绕分解能耗读数并确定哪些家用电器正在使用能源及其数量的想法开发一款产品。通过自动化这一过程,该工具可以识别各个来源的能源使用和浪费。这将帮助家庭、工厂和企业通过简单地将机器学习智能注入他们的能源网格来节省大量的资金。
另一个学生想为在线教育开发工具。在线课程已经大规模兴起,他希望帮助捕捉有助于学习成果和改善学习体验的趋势和隐藏模式。像这样的工具将有助于获得更多的在线学习者,降低辍学率,增加用户保留率。
这是一个为期 12 周的课程,我们现在才进行到第 5 周。学生们仍在集思广益,研究想法。观察这些想法如何演变并具体化为数据产品将是一件有趣的事情。
Meanwhile at the water cooler — an inspiration and reminder of what can be achieved with grit, vision, and a sense of purpose.
关于作者
Alexander 毕业于加州大学伯克利分校,获得物理学学士学位,毕业于大学,获得数据科学硕士学位。他目前是 General Assembly 的数据科学讲师。他热衷于应用数据科学来解决医疗保健、清洁能源和环境领域的挑战性问题。
教学数据科学坏了
我们教授数据科学的方式被打破了。事实上,即使是这句话也让我觉得很慷慨。在大多数情况下,我不确定我们是否尝试过。
我说的数据科学这个术语并没有什么过于浮夸的意思。在这种情况下,我使用“数据科学”来表示获取大量数据(超过 excel 可以轻松打开的数量)、创建图表、生成摘要,以及对数据所要表达的内容得出某种有意义的结论的能力。
在我看来,人们在学习数据科学时大致分为两种情况。一,应该懂数据科学而不懂的人。二是想学习数据科学但不知道如何学习的人。
案例一:应该了解数据科学的人
我应该懂数据科学。我在威斯康星主修统计学。但是我离开时对 R 几乎一无所知,尽管我学了很多应用课程。在很大程度上,我的应用课程包括解释其他人从统计程序中得出的结果。教授会给我们看一个截图,圈一个数字,我们必须告诉他,这是解释的方差百分比或类似的东西。
有时我们会使用 R,但我们会使用别人写的代码,最多改变几个数字或变量名。或者我们会在一堂课上看着教授输入 R。但是从来没有人让我坐下来解释什么是向量,如何写一个循环,或者如何使用 lapply 来代替循环。
很容易得出这样的结论:我接受了统计学教育,但没有学习某个特定的软件,所以现在我要做的就是自学 R,我会很好。但我觉得没那么简单。前几天我实现了一个算法。为了独立完成这个任务,我必须知道如何将数据读入 R,以一种有用的方式格式化它,将它汇总到一个表中,实现并使用文档来确切地知道我得到了什么输出。
弄清楚如何完成这项深刻而复杂的任务需要建立如此多的联系,以至于很难将解释输出截图这一反事实的任务描述为学习。
当我们教授统计学而没有以有意义的方式教授数据科学时,我们错过了一个机会。如果我们同时教授这两个科目,所有的统计概念和数据科学工具将以一种允许学生认真学习的方式连接起来。他们可以获得细致入微的理解,这将转化为独立工作的最终目标。此外,利用数据科学解决统计问题的能力在就业市场上很有价值。
我有时认为学生应该在六年级开始学习统计学之前学习 R(或者 Python 或者任何你想学的语言)。对于这个建议,我大多都有疯狂的想法,但我真的相信它。那么他们的统计学教育就可以建立在教授均值的基础上?向学生展示如何模拟数字向量。问他们如何总结这些数字。温和地教导他们中庸的直觉。或者给他们反复展示不同 x 的平均值。他们能解决这个难题吗?我们为什么要采取中庸之道?我们能不能模拟一堆不同的 x 向量,看看均值是怎么变化的?
想象一下,通过这种教育,学生将对统计学和数据科学有多么深刻的理解。
案例二:想学数据科学的人
我正在教授 Stata 入门课程,所以我一直收到想学习数据科学但不知道如何学习的人的电子邮件。例如,他们可能主修生物学,现在在一家医院担任研究员,并希望在未来申请计算生物学研究生课程。
第一个障碍是弄清楚学什么。他们应该学 R 还是 Python 还是 Stata 还是 SPSS?还不清楚。不同的人会有不同的答案。对于那些忙碌而不积极的人来说,他们需要学习数据科学,他们的旅程可能会在这里结束。
即使有人确切地知道他们想学什么,也没有很好的资源。有些人会推荐一两本书,这不是一个可怕的想法,但不可避免的是,典型的学生将会在知识上达到一些他们无法逾越的小差距。由于没有人寻求帮助,他们会无意中发现 stackoverflow,这将使它看起来不可思议的复杂,他们会在一段时间内停止转动,变得沮丧。在某个时候,他们脑海中的那个小小的声音会说“你不适合这个…”然后他们会告诉自己,他们真的不需要学习数据科学,因为如果他们知道生物学,他们总是可以与其他知道数据科学的人一起工作。这很可悲,因为事情本不应该是这样的。
也没有很棒的建议。很多人会建议学生通过尝试一个项目来学习。我觉得有点空虚。如果你什么都不知道,开始一个项目是一项不可能的艰巨任务。我很理解这个建议的来源——教育者知道学习需要学生在过程中积极主动,而项目是积极主动的最好方式。
问题是,学习实际上归结为学生既需要积极主动,又需要充分的指导。大多数解决方案都在这些方向上犯了错误。告诉一个新手开始一个项目,这是不够的。在一个大学课堂上,你看到有人在输入代码,但却不够活跃。找到完美的平衡非常困难。我无法想象世界上有多少地方能做到平衡。上个季度,我有幸参加了斯坦福大学的数据挑战实验室,它每个季度都在迭代,变得越来越好。比尔·贝尔曼带头冲锋,哈雷·威克姆远程协助教学。这些课程是令人难以置信的学习环境,但不幸的是,它们少之又少。
我现在看到了这个问题:在斯坦福大学,到处都有研究生在努力拼凑自己的数据科学教育。我不知道该怎么办。作为开始,我在上个季度写了一篇关于研究生数据科学教育问题的粗略的单页文章。下面我就分享一下。
我也觉得学生只是需要入门。大部分资源走得太深,太快,很难上手。Youtube 可能是一个强大的工具,但我认为大多数 R 视频对于典型的新手来说太快了。
作为对此的回应,我开始了一个兼职项目,在那里我制作非常基本的 R/Rstudio/Tidyverse 视频,提供在 R 中进行数据科学的基本工具。我将在 www.teachingr.com发布我制作的视频。
关于研究生院数据科学的一页纸
对于许多领域的研究生来说,数据科学技能对于有效地进行高质量的研究至关重要。
不幸的是,大多数学生没有获得这些技能的正式途径。许多学生在整个研究生院期间效率低下,从未真正成为一名熟练的数据科学家,其他人完全凭自己的意愿变得熟练,而其他人可能因为缺乏数据科学技能而全部失败。
这是一个已知的问题,有许多努力来解决它。然而,在我看来,由于各种原因,大多数这些努力都是无效的:
基于演讲的教学。学生需要做什么来学习
不要提供学生可以自己使用的资源
提供代码块,不要强迫学生打字…学生不理解单独的代码块
不要教授实际的编程逻辑
关注太多的语言
使数据科学和入门看起来比实际更难,而且对大多数学生来说,难以接近
不要有对每个人都有价值的明确目标
课程缺乏结构,过于简短,效果不佳
教师没有做好充分的准备,也没有使用高质量的教学方法。相反,他们会漫谈想到什么
知识的诅咒:导师对不知道是什么感觉没有同理心和理解
教员对如何做事没有足够的说明
讲师对与会者的回应过于积极——就一个人的问题离题 30 分钟
但是也没那么难啊!我怀疑,在一个设计良好的环境中,一个学生既活跃又有足够的指导,他可以在 2 到 3 周内取得巨大的进步。这里有一些原则:
向学生宣传深度参与的重要性。承诺实际结果。
选择一种语言(我建议 R ),并专注于此
从编码逻辑开始,以获得坚实的基础
提供入门书籍之类的资源
以小组形式开展基于活动的学习
要求学生在课外阅读/接触新材料
给学生高质量的练习,从非常容易的脚手架到专家
我怀疑,至少在教育研究生院,研究生的生产力与基线数据科学技能高度相关。我怀疑,为研究生和博士后提供基线数据科学方面的高质量培训可以将单个学生的研究生产率提高 2 倍,这在像教育研究生院这样数据科学技能稀缺的地方尤其如此。证明数据科学技能和生产力之间的这种相关性将是有趣的,并且可能促使在研究生培训中更优先考虑早期数据科学教育
教机器说话和即兴演奏布鲁斯爵士乐
对于本周的文章,我想重点关注长短期记忆 (LSTM)模型的例子,因为它们是我在深度学习中遇到的最优雅的概念之一。他们还为谷歌翻译提供动力,帮助优步预测极端事件期间的需求,让亚马逊回声听起来像真人,教机器人外科医生打结,甚至谱写新的布鲁斯爵士乐。
挑战:我的狗有四只…
假设我让你猜句子*“我的狗有四条腿……”*中的下一个作品,你可能会说“腿”。在这种情况下,最后一个单词的上下文在紧邻的前一个单词内。除了知道句子的主语是“狗”,你不需要任何上下文。递归神经网络 (RNNs) 特别擅长做这类预测,当上下文和预测之间的差距很小时。
但是如果我说的不是上面的,而是:
“我有一只叫查理的狗。查理喜欢在我出去的时候追棍子,追猫,吃我的鞋子。查理有四个…”
你也会猜“腿”。但那只是因为你记得的相关上下文,即“查理”是我的“狗”。相关的上下文并不是紧接在前面的单词的一部分,而是在故事的开头。对于这类问题——当上下文和预测之间的差距很大时——rnn 很快就会崩溃。这就是长短期记忆模型的用武之地。
你的记忆是做什么的?
想想你自己的记忆。它有效地做了三件事:
- 记录新信息(输入门)—“我回到家,把钥匙放在烤箱旁边”
- 忘记一些信息(忘记门)–忘记钥匙在烤箱旁边
- 向前传递剩余信息(输出门)–我回到家,把钥匙放在某个地方
LSTMs 使用上述三个函数来为它试图预测的事物提供上下文。然后,它每次接受一小组单词(例如,“我有一只狗……”),以(a)预测下一个单词(“叫查理”)和(b)记住句子的上下文(“狗”)。然后,当它需要预测句子后半部分的下一个单词(“查理有四个……”)时,它依靠记忆通知它我们在这里谈论的是一只狗,因此可能的答案是“腿”。
LSTMs 已经被证明在长时间内保持相关的上下文信息是非常有效的。
A memory cell. It takes as an input 1) new information + 2) outputted memory from an earlier cell. It then forgets some of its information. Finally, it outputs 1) a prediction + 2) the input into the next memory cell. Source: deeplearning.net
给机器人类语言
如果你在 Mac / iOS 上阅读这篇文章,试试这个:突出显示这一段,然后进入编辑- >语音- >开始朗读 (OSX) 或点击朗读 (iOS)。
或者,这里有一个你会听到的例子:
A basic text-to-speech (not using LSTMs). Notice how it sounds monotone and not like a human. Source
虽然你能理解字面意思,但它听起来显然不像人类。它是单调的,声音不像人类那样掌握同样的语调。在高层次上,您可以将人类语言视为以下内容的组合:
- 你说的话
- 你使用的音高
- 你发音的节奏
(1)很容易做到,因为通常不是上下文相关的。警告是一个异义词(两个单词拼写相同,但发音和含义不同,如“我们必须擦亮波兰家具”或“请关上你靠近的门”)。
但是(2)和(3)(音高/节奏)是高度语境化的,基于你试图传达的内容(想象一下如果小马丁·路德·金的“我有一个梦想”演讲被苹果单调的 Safari 阅读器阅读)。高级语言语音系统,如百度的或亚马逊的Polly(Alexa 背后的声音)通过对人类声音的音高和节奏进行编码来解决这一问题,并应用 LSTMs 不仅预测下一个单词,还预测下一个单词的音高和节奏。
这里有一个使用不同的 TTS 系统阅读的两个英语句子的例子。你会注意到第三个——谷歌的 WaveNet ,它使用 lst ms——听起来更像人类。
用 LSTMs 创作蓝调爵士乐
Eck 和 Schmidhüber 将 LSTMs 应用于音乐创作,通过随机选择符合布鲁斯风格音乐形式的旋律来训练他们的模型。具体来说,他们训练他们的 LSTM 学习如何生成新的旋律结构,以“适应”和弦结构。这是第一次使用神经网络来捕捉音乐中的全局音乐结构(即歌曲早期部分的上下文记忆),而不是局部音乐结构(就像 RNNs 能够做的那样)。
这是一个示例输出。虽然它不是 B. B. King,但它非常好,并且显示了我们离用 LSTMs 生成高级爵士乐有多近。
教授数据科学过程
The cyclical process of data science (source).
教授机器学习的课程已经存在了几十年,甚至更近的技术学科(深度学习或大数据架构)都有几乎标准的课程大纲和线性化的故事情节。另一方面,对数据科学过程的教学支持一直难以捉摸,尽管过程的大纲自 90 年代就已经存在。理解这一过程不仅需要机器学习的广泛技术背景,还需要企业管理的基本概念。在之前的一篇文章中,我已经详细阐述了由这些复杂性引发的数据科学转型的组织困难;在这里,我将分享我教授数据科学过程的经验。
The data science ecosystem. Data scientists “B” is in key position in formalizing the business problem and designing the data science workflow.
围绕工作流构建
最近,我有机会在大约 100 名来自巴黎综合理工学院的顶尖工科学生身上尝试一些实验性的教学技巧。课程的中心概念是数据科学工作流程。
- 设计工作流,其元素,要优化的分数,将工作流连接到业务数据科学家端。
- 优化工作流程,将其连接到技术数据科学家端。
这两者都不能在基于幻灯片的讲座中使用线性化叙述来教授。我用我们的平台围绕我们的坡道概念建造了这个球场。为了学习工作流优化,学生们参与了五个斜坡,旨在挑战他们不同的科学工作流和不同的数据科学问题。为了学习工作流设计,我讲述了几个数据驱动的业务案例,给学生提供了一个需要回答具体问题的线性指南,并要求他们在小组项目中构建业务案例和数据科学工作流。我使用 RAMP starting 工具包作为样本:限制无限的设计空间有助于学生构建项目。
使用坡道作为教学支持
RAMP 最初是为一个协作原型工具设计的,该工具可以有效利用数据科学家的时间来解决领域科学或业务问题的数据分析部分。然后,我们很快意识到,这对培训数据科学家新手同样有价值。我们需要改变的主要设计特征是完全的开放性。为了能够根据个人表现给学生打分,我们需要关闭排行榜。在封闭阶段,学生们看到彼此的分数,但看不到彼此的代码。我们使用分数的上限线性函数来给他们评分。这个通常持续 1-2 周的封闭阶段之后是一个“经典”的开放阶段,在这个阶段中,我们根据学生的活动和他们创造多样性的能力以及提高他们自己的封闭阶段分数来给他们打分。
学生们的集体表演堪称壮观。在所有五个斜坡中,他们不仅超过了基线,还超过了我们组织的测试工作流的单日 hackaton 分数,通常有 30-50 名顶级数据科学家和领域科学家参与。
Score vs submission timestamp of the first classroom RAMP. Blue and red circles represent submissions in the closed and open phases, respectively. The pink curve is the current best score and the green curve is the performance of the best model blend. The top 10% of the students outperformed both the data science researchers (single day hackaton) and the best deep neural nets, even in the closed phase. They then outperformed state-of-the-art automatic model blending when combining each other’s solutions in the open phase.
我也很高兴地看到,在开放阶段新手/普通学生通过学习和重用封闭阶段来自前 10-20%学生的解决方案赶上了前 11 名。另一个惊喜是直接盲目抄袭非常罕见**:学生们真诚地试图改进彼此的代码。**
Score distributions in classroom RAMPs. The blue and red histograms represent submissions in the closed and open phases, respectively (the darker histogram is the overlap). The histograms indicate that novice/average students catch up to the top 10% in the open phase by profiting from the open code.
我们将分析这些丰富的结果,并在领域科学(第一个例子见本文)数据科学和管理科学中撰写论文。这份技术报告包含了更多的细节,这里有我的幻灯片来自最近关于数据科学过程的 DALI 研讨会。
使用业务案例进行工作流设计教学
正如我在之前的文章中解释的那样,非 IT 公司启动数据科学项目的主要障碍不是缺乏准备充分的数据,不是基础设施,甚至不是缺乏训练有素的数据科学家,而是缺乏定义良好的数据驱动的业务案例。更糟糕的是:这个问题通常是在对数据湖、Hadoop 服务器和数据科学团队进行初始投资之后才发现的。一个准备充分的数据(流程)科学家,如果能够尽早进入这一过渡阶段并彻底改变项目,甚至可以为一家中型公司节省数百万美元。
为了培养学生胜任这一角色,我在课程开始时对模型预测性维护案例进行了深入讨论。每个人在他们的项目中需要回答的标准化问题帮助学生从广泛描述的业务案例走向明确定义的预测分数、误差测量和数据收集策略。
- 我们想要预测什么,我们如何衡量预测的质量?
- 更好的预测将如何改进选定的 KPI?
- 你想有决策支持,一个完全自动化的系统,还是只想知道哪些因素是重要的?代理将如何使用该系统?
- 定量预测应该是什么?
- 我们如何(使用什么分数)衡量成功?(可能不对称的)预测误差如何转化为成本或降低的 KPI?
- 我们需要什么数据来开发一个预测器?
- 我们需要做哪些工作来收集这些数据?
- 给定数据源和预测目标,工作流和工作流元素是什么?
- 该模型需要多长时间重新培训一次?
我进一步组织了他们的项目,让他们按照他们遇到的五个斜坡制作一个启动工具包。每个起始套件包含
- 一个数据集,
- 填充设计工作流的示例工作流元素,
- 实现工作流的单元测试,可用于测试工作流元素,以及
- Jupyter 笔记本,描述科学或商业问题(回答上述问题),读取、操作、探索和可视化数据,解释数据分析工作流,并提供和解释每个工作流元素的初始工作解决方案。
该课程包含大量问答,讨论其他业务案例(包括成功和失败的案例),并解释各种可能的工作流和工作流元素。
A time series forecasting workflow for predicting El Nino.
A multi-criteria workflow for classifying and quantifying chemotherapy drugs for noninvasive quality control.
由于学生可以自由选择任何可用的数据集,数据收集基本上不成问题。工作流相对简单,所以几乎所有的团队都交付了工作启动工具包。另一方面,很多时候,学生们陷入了试图为“好的”数据集寻找商业案例的陷阱。大约一半的团队至少试图设计一个有意义的商业案例。前 3 名团队(共 22 个)交付了顶级产品:
- 一个制造过程控制产品,使用精确校准的维护成本、生产成本、满意度成本和利润,计算出误报和漏报的不对称成本。团队在几个基线上显示了改进(不检查、检查所有、随机检查)。
- 一款卖给大型多人在线游戏的产品。目标是预测玩家是人类还是机器人。当机器人与他们自己的线下业务竞争时,这些游戏会赔钱,这些业务通过自动收集游戏中的角色和功能并在黑市上出售来出售真钱。该团队通过考虑不对称分类错误,制定了商业案例。
- 一种可以卖给出租车公司或优步的产品,预测每小时和曼哈顿地区的出租车需求。该团队通过估计可用乘坐次数乘以每次乘坐的利润,将预测转化为价值。
如果你喜欢你读的东西,在中、 LinkedIn 、& Twitter 上关注我。
Python Pandas 金融数据集技术分析库
[Image[1] (Image courtesy: https://unsplash.com)]
在过去的几个月里,我一直在研究一些金融时间序列,如预测比特币价格或号人物、两个适马投资或 G-Research 提出的不同挑战。也就是说,我们已经决定基于 Pandas 库用 python 开发一个技术分析库。您可以在以下位置找到该库:
ta——Python 中的技术分析库
github.com](https://github.com/bukosabino/ta)
这个新的库面向从典型的金融数据集进行“特征工程”,这些数据集通常包括诸如“时间戳”、“开盘”、“高”、“低”、“收盘”和“成交量”等列。该库将由希望使用 Python 数据科学技术堆栈(Pandas、Scikit-Learn、XGBoost、LightGBM、Keras、TensorFlow 等)解决机器学习问题的数据科学人员使用。
目前,这些工具在预测几乎任何事情上都取得了很好的效果,但是当它们被用来面对金融问题时,就不能正常工作了。它们不能正常工作,因为数据集中的行仅包含关于特定时间段(例如 6 小时或一天)的信息,这不足以使用当前模型生成良好的预测。为了改进预测,我们需要向数据集提供更多信息(要素),因为当提供更多信息时,当前模型会获得更好的结果。
技术分析的重点是提供过去的新信息来预测价格的走向。通过增加不同变量(“数量”、“波动性”、“趋势”、“势头”等)的不同指标产生的信息,我们可以提高原始数据集的质量。
现在,我们将详细解释两个例子:
布林线用于分析特定时期内资产价格的波动性。有 3 个波段,中间波段(MB)是最近 n 个周期的价格平均值,上波段(UB)和下波段(LB)等于中间波段,但加上和减去 x 倍标准差。正在使用的正常参数是 n = 20 个周期,x = 2。所以:
MB =总和(n 个最后收盘值)/ n
UB = MB + (X *标准偏差)
LB = MB — (X *标准偏差)
Bollinger Bands example [Image[2] (Own image generated with Matplotlib)]
在库中,收盘价变量被转换为 5 个新特性。除了 3 个布林线,我们还生成了另外 2 个指标,当收盘值高于上布林线或低于下布林线时,这两个指标会显示出来。因此,这两个特征将为 0,除非收盘值超出这些范围,否则将为 1。
如果我们看一下图 2,当收盘波(蓝色)超过上波段或下波段时,价格会发生突然变化,通常在高于上波段时卖出,在低于下波段时买入是一个好主意。
均线收敛背离是一个关注指数移动平均线(EMA)的交易指标。为了计算它,我们使用:
MACD =均线(n1,收盘)—均线(n2,收盘)
MACD 信号=均线(n3,MACD)
MACD _ 差异= MACD—MACD _ 信号
变量的典型值是 n1=12,n2=26,n3=9,但是根据你的交易风格和目标,库中也可以有其他的值。
MACD example [Image[3] (Own image generated with Matplotlib)]
理论告诉我们,当 MACD 曲线(蓝色)小于 MACD 信号(橙色)时,或者当 MACD 差(绿色曲线代表 MACD 信号和 MACD 曲线之间的差)的值小于 0 时,价格趋势将是熊市。反之,则表示涨价。
这时,图书馆已经实施了 32 项指标:
卷
- 积累/分配指数
- 平衡量(OBV)
- 平衡体积平均值(OBV 平均值)
- 柴金资金流(CMF)
- 力指数
- 便于移动(EMV 选举观察团)
- 量价趋势(VPT)
- 负体积指数
波动性
- 平均真实距离
- 布林线(BB)
- 凯尔特纳海峡
- 唐奇安海峡(DC)
趋势
- 移动平均收敛发散(MACD)
- 平均定向运动指数
- 涡流指示器(六)
- 特里克斯(Trix)
- 质量指数
- 商品频道指数(CCI)
- 去趋势价格振荡器(DPO)
- KST 振荡器(KST)
- 一目岛
动力
- 货币流通指数
- 相对强度指数
- 真实强度指数
- 终极振荡器(UO)
- 随机振荡器
- 威廉姆斯%R (WR)
- 超棒振荡器(AO)
其他人
- 每日退货
- 累积回报
这些指标产生了 58 个特征。开发人员可以设置许多输入参数,如窗口的大小,不同的常数或智能自动填充方法中生成的 NaN 值。
我们已经将该库的第一个稳定版本上传到 GitHub,可以使用“pip”安装。该库正在继续开发,所以我们将包括更多的指标,功能,文档等。请让我们知道任何评论,贡献或反馈。
ta——Python 中的技术分析库
github.com](https://github.com/bukosabino/ta)
此外,我是一名软件自由职业者,专注于数据科学,使用 Python 工具,如 Pandas、Scikit-Learn、Zipline 或 Catalyst。如果你需要与本库相关的东西,技术分析,Algo 交易,机器学习等,请随时联系我。**
机器学习中的技术债务
或者如何用火箭筒打自己的脚。
我们许多人都不喜欢技术债务,但一般来说,这并不是一件坏事。技术债务是一种工具,当我们需要满足一些发布截止日期或解除同事的封锁时,它是合理的。然而,技术债务的问题与金融债务的问题是一样的——当偿还债务的时候,我们归还的比开始时多。这是因为技术债务具有复合效应。
有经验的团队知道什么时候应该支持堆积如山的债务,但是机器学习中的技术债务堆积得非常快。 你可以在一个工作日内创造几个月的债务,即使是最有经验的团队也会错过债务如此巨大的时刻,以至于他们推迟半年,这通常足以扼杀一个快节奏的项目。
这里有三篇探讨这个问题的精彩论文:
机器学习:技术债的高息信用卡 NIPS’14
机器学习系统中隐藏的技术债 NIPS’15
你的 ML 测试分数是多少?NIPS’16
这些论文分类并展示了几十种机器学习反模式,它们会慢慢潜入您的基础设施,成为一颗定时炸弹。在这里,我只讨论三种让我在夜里吓出一身冷汗的反模式,其余的留给读者。
反馈回路
当 ML 模型的输出被间接反馈到它自己的输入中时,反馈循环就发生了。听起来像是很容易避免的事情,但实际上并不可行。反馈回路有多种变化,NIPS 的论文给出了一个很好的例子,但我将给出一个更真实的例子。
例子
假设你的公司有一个购物网站。一个后端团队提出了一个推荐系统,它根据客户的个人资料和过去的购买历史来决定是否显示一个带有报价的弹出通知。很自然,你想根据之前点击或忽略的弹出通知来训练你的推荐系统,这还不是一个反馈循环。当点击通知的比例一周比一周慢慢增加时,您启动了这个功能并欣喜不已。你用人工智能改善其过去性能的能力来解释这种增长:)但你不知道的是,前端团队实现了一个固定的阈值,如果推荐报价的可信度低于 50%,它会隐藏弹出通知,因为显然,他们不想向客户显示潜在的坏报价。随着时间的推移,以前在 50–60%置信度范围内的建议现在用< 50%置信度进行推断,只留下 50–100%范围内最有效的建议。这是一个反馈循环——您的度量在增长,但是系统的质量没有提高。士气:你不仅要 利用ML 系统,还要允许它 探索——摆脱固定的门槛。
在小公司中,控制反馈循环相对容易,但是在大公司中,几十个团队在几十个复杂的系统上工作,这些系统通过管道相互连接,一些反馈循环很可能会被遗漏。
如果您注意到一些指标随着时间慢慢上升,甚至在没有启动时,也能感觉到反馈循环。找到并修复循环是一个非常困难的问题,因为它涉及到跨团队的努力。
校正级联
当 ML 模型没有学习到您希望它学习的东西,并且您最终在 ML 模型的输出上应用了一个修补程序时,就会发生校正级联。随着修补程序的堆积,你最终会在 ML 模型的顶部有一层厚厚的试探法,这被称为校正级联。即使在没有时间压力的情况下,修正级联也是非常诱人的。很容易对 ML 系统的输出应用过滤器,以便处理 ML 不想学习的一些罕见的特殊情况。
校正级联将您的 ML 模型在从整个系统的整体指标进行训练时试图优化的指标去相关。随着这一层变得越来越厚,你不再知道 ML 模型的什么变化会改进你向你的老板展示的最终度量,并且你最终不能交付新的改进。
垃圾特征
垃圾特性是指在你的 ML 系统中没有任何用处的特性,你无法摆脱它们。有三种类型的垃圾特征:
捆绑特性 有时,当我们有一组新特性时,你会一起评估它们,如果发现有益,就提交整个捆绑包。不幸的是,捆绑包中只有一些功能是有用的,而其他功能正在拖累它。
ε-特性 有时添加一个特性是很诱人的,即使质量增加很少。然而,如果基础数据有一点漂移,这些特征可能会在一周内变得中性或负面。
随着时间的推移,我们向项目中添加新的特性,并且不再重新评估它们。几个月后,其中一些功能可能会变得完全无用或被新功能取代。
在复杂的 ML 系统中,有效清除垃圾特征的唯一方法是尝试一次修剪一个。也就是说,您一次删除一个特性,训练 ML 系统,并使用您的度量标准评估它。如果系统需要 1 天来训练,我们一次最多可以运行 5 次训练,我们有 500 个功能,那么修剪所有功能将需要 100 天。不幸的是,特征可能会相互作用,这意味着您必须尝试修剪所有可能的特征子集,这成为一个指数级的难题。
凭借我们的力量
在你的机器学习基础设施中拥有这三种反模式可能会毁掉整个项目。
有了反馈循环,你的度量将不能反映系统的真实质量,你的 ML 模型将学习利用这些反馈循环,而不是学习有用的东西。此外,随着时间的推移,您的模型可能会被工程团队无意中塑造成更多地利用这些循环。
校正级联将放松在 ML 模型上直接测量的指标和作为整体的系统之间的相关性。您将最终处于这样一种情况,对 ML 模型的积极改进会对整个系统的度量产生随机的影响。
有了垃圾特征,你甚至不知道你的数百个特征中的哪一个实际上携带了有用的信息,并且删除它们的代价太大了。每天,您通常监控的指标会随机上升或下降,因为一些垃圾特性会随机出现。不,正规化只会有一点点帮助。
您最终得到的项目中,度量标准随机上下跳动,不能反映实际的质量,并且您不能改进它们。唯一的出路就是从头开始重写整个项目。这时你就知道了——你用火箭筒打中了自己的脚。
基于电子邮件网络的见解的技术概述
unsplash.com
组织网络分析和沟通内容分析系列文章
这是第 1 部分文章的后续,其中我们提到了一些我们想要解决的关键难点。
在这里,我们深入探讨两个最有趣的观点:
- 内部继任计划
- 如果员工离职怎么办
内部继任规划
内部候选人被提拔为接班人是常有的事。内部晋升往往基于与离职领导和周围影响群体的绩效和社会关系,而没有数据驱动的分析。今天的系统在继任规划方面做得并不理想,因为它们有偏见,没有考虑到数据驱动的网络和关系(ONA)。
在我们的方法中,除了领导者-继任者沟通,我们还关注间接涉及其他因素和混杂因素的整体社会状态。我们借用了脸书提出的社会分散的概念。除其他外,该条指出:
一个人的网络邻居——与他或她有联系的一群人——已经被证明在广泛的环境中具有重要的影响,包括社会支持和职业机会。
“包括社会支持和职业机会”这句话是探讨继任规划方面的重要基础,此外还有共同的朋友交流范式。这种方法的缺点是不容易对外部候选人进行这种分析,因为离职的领导者在另一个通信领域,因此几乎不可能进行基于电子邮件网络的分析。
我们的实施可以向即将离任的领导者推荐几个潜在的内部继任者。
这是通过了解继任者的网络是否也在与领导者的网络通信来实现的。可以推断,高度分散是成功的内部继任规划的良好基础。这意味着继任者的网络与领导者的网络高度互联。显然,即将离任的领导人在确定继任者时会考虑其他因素,也许不是很数据驱动,而是基于直觉。我们的方法实际上是一个推荐器,而不是绝对匹配。
然而,如果继任者得到提升,她很可能会在组织中留下一个角色空缺。因此,我们更进了一步,我们提供了更进一步的接班人。下图更详细地解释了这个概念。
为了实现这一点,我们使用了基于电子邮件网络的“发件人”和“收件人”字段,不涉及任何内容(在稍后阶段,我们将利用时间戳来解决关系衰退)。该数据集包含 8000 名员工和 25 万封电子邮件。在技术和算法方面,我们使用了 Python 包,如: NetworkX 、熊猫、 NumPy 。以下代码片段是内部继任计划算法实施的关键部分:
df = pd.DataFrame(data, columns = ['Sender','Recipient'])
G = nx.from_pandas_edgelist(df, source = 'Sender', target = 'Recipient', create_using = nx.DiGraph())
dis = nx.dispersion(G, normalized = True)
对于我们的观众,我们实现了一个 3D 网络可视化,如下所示。但是,产品实现是基于 API 即服务的。
(注意姓名不代表我们的员工)
如果员工离职怎么办
一旦员工不再担任相同的角色,了解沟通风险是企业运营的关键。这一特性对直线经理特别有用,他们需要在整个变革过程中保持正常的沟通和劳动力。高中间中心性意味着更高的员工替换成本。如果一个员工有很高的关系网,并作为沟通的桥梁,介绍一个新的候选人担任这样的角色需要花费时间和金钱。
中间中心性被定义为节点“I”需要节点“k”(其中心性正在被测量)以通过最短路径到达节点“j”的时间份额。我们实现的研究基础来自研究论文中心性和网络流。
该算法的核心实现片段如下所示:
def betweenness_centrality_chunks(l, n):
l_c = iter(l)
while 1:
x = tuple(itertools.islice(l_c, n))
if not x:
return
yield x
def _betweenness_centrality_map(G_normalized_weight_sources_tuple):
return nx.betweenness_centrality_source(*G_normalized_weight_sources_tuple)
def betweenness_centrality_parallel(G, processes=None):
p = Pool(processes=processes)
node_divisor = len(p._pool)
node_chunks = list(betweenness_centrality_chunks(G.nodes(), int(G.order() / node_divisor)))
num_chunks = len(node_chunks)
bt_sc = p.map(_betweenness_centrality_map,
zip([G] * num_chunks,
[True] * num_chunks,
[None] * num_chunks,
node_chunks))
bt_c = bt_sc[0]
for bt in bt_sc[1:]:
for n in bt:
bt_c[n] += bt[n]
return bt_c
btwn_cent = betweenness_centrality_parallel(G)
同样,对于展示案例,我们已经实现了 3D 网络可视化。
有许多与员工离职概率相关的实现,如风险逃离、员工流失等。这些方法大多基于机器学习和对类似情况的分析,例如 k-means 聚类。我们的方法与众不同,因为它使用 ONA 指标来得出与实际员工更相关的结论,而不是从其他案例中学习。更好的方法是将 ONA 和机器学习的指标进行加权组合,我们正在研究这种方法。
大多数公司通过书面的官方流程来解决这些问题。这不会让这些公司为未来做好准备,因为流程是静态的,很难更新,不容易获得,也不能动态地反映不断变化的事件。我们想通过引入动态方法来改变这种情况。这种方法不依赖于书面文件,而是依赖于当前的信息流、背景、趋势、沟通和实际的员工数据——基本上是一种 VUCA 反应。
我们的进一步发展进入机器学习、异常检测和时间序列分析的方向。
本文是 ONA @豪夫系列文章的一部分
- 如何使用企业电子邮件分析来揭示隐藏的明星并确保机会均等(第一部分
- 电子邮件网络见解技术概述( 第二部分 )
- 深入探讨基于电子邮件网络的推荐(第 3 部分)
- 如何利用趋势发现隐藏的明星,并致力于一个完美的项目?人物分析会让你成为明星
- 如何实现基于电子邮件内容的分析(第 5 部分)
技术工作流程:为可达性分析构建交通场景
逐步文档
在的前一篇文章中,我记录了我使用一系列工具来衡量各种交通方式下的工作可达性。我最终使用了传送带的分析工具。然而,即使有了这个专门的软件,我的分析也需要大量的数据争论来构建未来的场景。
问题是
即使是最好的软件工具似乎也是为大城市设计的——像纽约和旧金山这样的地方,那里的道路或多或少都是固定的,所以规划增长通常意味着关注城市交通系统的变化。几个可达性分析工具使得用户可以很容易地开发和比较交通场景。能够在其他环境中利用这些工具是一个巨大的优势。但较小的城市有额外的需求,这些工具不容易解决。
在像本德这样的地方,增长规划主要集中在新的基础设施上:建设新的道路、自行车和行人设施。我考虑过的辅助工具都没有内置的添加或修改道路的功能;用户只能在当前道路网络*上运行场景。此外,(在我看来)没有基于道路交通压力的水平来评估自行车网络的功能,以便了解骑自行车的平均人的连通性。为了将可访问性分析整合到我们的过程中,我们需要能够处理这些类型的场景。
*注意:这不适用于更技术性的、自己动手的工具(即任何在 Github 上以代码形式存在的、不能通过用户友好的 GUI 访问的东西)。这些可能很棒,但是对于大多数在城市工作的城市规划者来说是不可用的(更不用说小城市了)。
解决方案
使用多种工具的组合,可以准备用于评估的自定义道路和自行车网络。这篇文章记录了我为了将分析应用于我们的特定需求而使用的工作流程。这种方法本质上是关于使用 OpenStreetMap (OSM)数据构建场景,因此它也适用于使用 OSM 数据作为输入的其他规划工具(这相当常见,尤其是对于开源工具)。
这篇文章记录了 Bend 的工作流程——部分供我们内部参考,也可能对其他人有用。
更广泛的要点
除了技术工作流程本身,该流程还强调了几点:
- 好的地图合并工具将为这样的项目带来真正的好处。手动操作不同格式的数据集需要花费大量的时间和精力。
- 如果情景规划工具能够让用户模拟未来状态的道路变化,那么它们对较小的城市更有用。输送机的分析工具允许用户输入 OSM 数据的自定义 PBF 文件,这就是为什么我们能够使用我们的目的的工具。不过,这仍然很难,并且需要大量关于处理 OSM 数据的技术知识。如果有一个使用户能够修改道路网络的功能,小城市可能会更好地利用分析等工具——类似于对场景进行本地更改的 iD 编辑器界面。
- 在 OSM 拥有一个 LTS 标签将为当前状态的 LTS 数据提供一个栖身之所,这将实现更好的分析,并可以避免像我这样的人在未来不得不经历复杂的手动数据过程(或者,该过程可能必须使用一次来获取数据,但随后将可供其他人使用)。
事不宜迟…以下是我使用的工作流程:
1.准确的 OSM 基线数据
许多此类工具使用 OpenStreetMap (OSM)作为本地道路网的输入数据。我想检查 OSM 对于我所在地区的准确性,并绘制出任何缺失的道路。
了解 OSM 以及如何编辑它的一个很好的方式是 learnosm.org 的。对于那些刚到 OSM 的人,我不建议对你所在城市的道路网进行大的改变(比如删除或移动现有的要素)——它可能已经足够接近现状了……而且很容易损坏东西。不过,添加缺失的道路可能会有所帮助。
我使用热门任务管理器将网格划分成合理的块(“任务”)。对于每个任务,我使用 JOSM (带有 shapefile 插件)将城市的 GIS 数据与 OSM 和卫星图像进行比较,并添加任何缺失的路径、道路等。我把它标记为“完成”,然后继续下一个任务。虽然我肯定有一个更技术性的、GIS 式的方法来做这种比较,但这个城市足够小,我可以很快完成。
The HOT Tasking Manager makes it easy to systematically map, validate, or update an area of interest
Comparing county roads data (fuschia, yellow) to OSM data (grey)
OSM 对大部分弯道都非常准确;我只找到了少数尚未绘制的新道路,其中包含了详细的小巷和便道。不过,我加了很多人行道。这些对于像当地社区学院这样的地区来说尤其重要,它坐落在一大片土地上,那里有许多纵横交错的小径(又名“社交小径”)。虽然不是官方路线,但这是许多人进入大学的现实途径。如果不追踪这些进入 OSM 的路径,行人可及性将会非常低(尤其是如果您使用的工具使用基于质心的方法来定位宗地、街区或区域内的工作)。
Adding footpaths (dashed lime green lines) in JOSM, especially for really large parcels
系统地检查整个城市意味着我对当地 OSM 数据的准确性非常有信心。但是花了很多时间(会议期间 8 小时以上的多任务处理)却没有多少改变。如果我再做一次,我仍然会手动查看数据,但我将只关注检查 OSM 最近开发的区域和拥有大片土地的区域。
我保存了我的更改并上传到 OSM。输送机的分析工具要求 OSM 数据以 PBF 格式提供,这只是 OSM 数据的压缩版本。有几种方法可以达到这个目的:
- 保存在 JOSM 的更改,并转换为 PBF 格式(更多信息如下。未来的场景需要这种方法,所以您最好熟悉它。)
- 等待更改到达服务器,并通过传输分析用户界面下载
- 等待更改到达服务器,并从热导出工具下载 PBF 格式
2.“基线”场景中还有什么?
这是添加其他项目或链接的时候了,这些项目或链接目前还不存在,但是会成为基线分析的一部分。
对本德来说,这意味着在大片未开发的土地上进行新的开发。我在模拟一个 20 年的场景,其中有相当大的人口增长和向新区域的扩张,这些区域刚刚被添加到本德的城市增长边界中。新的家园和工作岗位将位于这些地区,当地的街道将被修建。规划这些街道的确切走向将作为局部区域规划过程的一部分。与此同时,我需要在扩展区域中添加一些本地街道,以便允许模型中的“人”从扩展区域连接到城市,反之亦然。为了做到这一点,我使用 JOSM 绘制了扩展区域的道路,并将它们标记为highway=residential
。这些道路并不是特别真实或精确,我也不会在地图上将它们显示为“潜在道路”,但它们确实可以作为数据输入。
Adding placeholder local roads for currently-unbuilt areas that are projected to develop housing
在 JOSM,我把这些保存为一个单独的文件baseline_roads.osm
。我复制了几份用于场景。这个数据不应该上传到 OSM ,因为“虚拟道路”实际上并不存在。
3。转换成 PBF 格式
传送带公司的分析要求 OSM 数据采用一种叫做 PBF 的压缩格式。为此,我安装了 osmium 作为命令行工具,导航到带有.osm
文件的目录,并使用
osmium cat <baseline_roads.osm> -o <baseline_roads.pbf>
进行转换
这个 PBF 文件现在可以用于可访问性分析了。
4.准备其他数据输入,测试工具
其他输入包括公开可用的 GTFS 数据,人口和就业的土地使用数据,以及分析区域的边界。
**对于土地使用数据:**由于该建模侧重于 2040 年的情景,我没有使用可通过 Conveyal 接口访问的矿脉普查数据。相反,我上传了一个包含未来各州人口和就业预测的地块 shapefile。在 GIS 中,我对这些数据进行了分解,使其仅包含人口和就业字段,并确保其在 WGS84 中进行了投影。
我在这里遇到了一个小障碍,因为数据包含一些非常非常小的宗地(小于 0.00001 英亩),必须先从 shapefile 中删除这些宗地,然后工具才会接受它们作为输入。
Some of these teeny parcels needed to be deleted.
使用这些输入和基线 OSM 数据,我建立了一个超出我的兴趣范围的分析区域。我运行了分析工具,以确保这些数据是可行的,并且该工具按预期工作。
Inspecting point-based accessibility for cars and pedestrians
**分析区域:**然后,我通过上传本德市城市范围的 shapefile,加上扩展区域和预计未来人口增长和就业的其他区域,对结果进行约束。我检查了结果,以确保它们有意义,然后转向场景。
Inspecting regional results for cars. The gist: the average person in a car can reach all jobs in half an hour (or less). This is what I would expect to see.
Inspecting regional results for bikes. The gist: the average person on a bike could access about 60% of future jobs in half an hour. This doesn’t account for the level of traffic stress of the roads. This result seems pretty reasonable based on local conditions; seems like everything is working properly.
5.构建道路场景
这部分不是很复杂,但是很快。根据我拥有的数据格式,构建新道路场景的最简单方法是:
- 将基线 OSM 文件的副本保存为
scenarioA_roads.osm
,并在 JOSM 打开它。 - 使用我为潜在的新道路准备的 Shapefile,确保它在 WGS84 投影中,将它作为一个单独的图层加载到 JOSM(使用 shapefile 插件)
- 交替查看两个不同的图层,并根据它们的位置和我的描述追踪新的道路(这会影响道路是否应该标记为
highway=residential
或highway=secondary
等)。我还标记了潜在的项目(例如scenario=A
)来跟踪它们——数据将保留在我的本地机器上,所以我不担心用非标准的标记弄乱数据。 - 将文件保存在 JOSM 本地。不要将更改上传到 OSM。
- 使用第 3 节中的过程转换到 PBF(见上一节)
- 对任何其他场景重复此过程
然后,道路场景就可以载入分析并运行了。
Example: Tracing a new road into JOSM.
6.自行车网络:局限于低压力路线(很多兔子洞和麻烦)
对于骑自行车的普通人来说,并不是所有的道路都是安全舒适的。为了模拟普通人的可达性,根据 ODOT 的分类系统,我将分析仅限于模拟 LTS 1 号或 LTS 2 号公路。我用这个低压力网络为自行车开发了不同的场景。
我们已经有了 Bend 道路交通压力水平的内部 GIS 数据。这一信息还有另一个来源; PeopleForBikes 使用 OpenStreetMap 数据生成美国 500 多个城市道路的 LTS 估计值。这些数据可以从他们的网站上免费下载(旁注:如果 LTS 能作为 OSM 的一个标签被添加进去,那就太好了,这样这些信息就更容易获得了)。这不如基于当地详细信息的数据准确,质量取决于当地 OSM 数据的完整性;此外,它可能不遵循构成新 LTS 协议的特定分类标准,等等(在我们的案例中,我们使用了 ODOT 的指导方针)。也就是说,这些数据是一个非常好的开始,其背后的方法在网站上有非常好的记录,所以你可以告诉为什么事情会以这种方式分类。我将本德市的 LTS 数据与 PeopleForBikes 的 LTS 数据进行了比较,结果基本相当接近——见下文。在某些情况下,城市数据更准确或更新。在其他情况下(比如高速公路入口和出口),PeopleforBikes 的方法会产生更真实的(高压力)结果。
This map shows both LTS datasets on the same map. Zoom in to see the different datasets side-by-side. High-stress roads are shown in shades of red; low-stress roads are shown in shades of blue. City data are represented by darker and bolder lines than PeopleforBikes data. Matching data looks like two parallel lines of the same-ish colour; mismatching data will have a red line parallel to a blue line.
这是我遇到障碍的地方。我至少有两种不同格式的不同数据集。我的城市 GIS 数据不可选择路线,也没有在之前步骤中创建的“虚拟”道路。我的 OSM 数据没有 LTS 属性。
Why is this so hard?
首先,我试图变得老练
合并来自不同机构/来源、不同格式的数据,对世界各地的城市来说都是一个挑战。为了避免手动过程,我尝试了几个地图合并工具:hootenny和 SharedStreets 。SharedStreets 并没有不符合我当时的需求(但我对未来寄予厚望)。Hootenanny 目前正在被 NGA 使用,看起来是为 OSM 和 GIS 合并而设计的,所以我充满希望。我设置了一个本地版本的 Hootenanny,并尝试加载我的数据集。我在这里遇到了很多障碍,并认为这可能只是一个巨大的兔子洞(我的截止日期很紧),所以我现在放弃了这一努力。
我还尝试在 GIS 中混合我的数据集,转换成 OSM 格式,并加载到 JOSM。这涉及到重复的功能。我在 JOSM 检查了这些,看到了不应该存在的路径之间杂乱无章的连接。我将结果作为输入数据进行分析,只是为了看看会发生什么,结果真的很奇怪。这个过程是一个“不”。
实际效果:
最后,我不得不使用手动且相当繁琐的方法:
- 在 GIS 中,仅制作高应力道路的 shape file(LTS 3 或 4)
- 创建基线 OSM 数据(
baseline_bike.osm
)的副本,并在 JOSM 打开 - 将高应力形状文件作为图层加载到 JOSM 中(使用 JOSM 形状文件插件)
- 交替查看这两层。找出任何高应力的路段(路线),并从
baseline_bike.osm
文件中删除。几个小技巧可能会让这变得更容易——尝试过滤你的视图,只考虑高速公路和主干道highway=primary
、highway=secondary
等。其中大部分可能是高压力的,可以删除。接下来,过滤显示所有的道路和路径(highway=*
,但没有其他功能。 - 将 JOSM 文件保存在本地。不要将更改上传到 OSM。
- 转换为 PBF 使用锇命令行工具(见第 3 节)
现在可以输入到分析中了。
为了创建新的场景,我使用了baseline_bike.osm
文件作为基础,并在场景项目列表中包含的任何新的或修改的道路和自行车设施中进行跟踪——这与第 5 节的过程相同。我将这些标记为highway=residential
,以确保它们被认为是可供自行车使用的街道。
在分析中,我把这些设定为新的区域。由于道路网络仅包括可供普通骑自行车者使用的道路,因此只能从自行车的角度进行分析。我没有使用这些数据对其他模式进行分析。
7.自行车网络:开发书立
我还分析了如果整个网络(所有合理的设施)是低压力的,自行车的可达性。这篇文章解释了为什么,以及它如何有助于围绕城市的自行车网络形成对话。
为此,我使用汽车场景输入(baseline_roads.pbf
、scenarioA_roads.pbf
等)来分析自行车的就业可达性。这产生的结果比只有低压力的自行车场景的结果高两倍。这表明自行车的壁垒将就业机会减少了一半。它还有助于隔离场景项目的潜在影响——在一个联系更紧密、压力更小的网络中,新的联系会产生更大的影响。
TED 演讲分析—面向初学者的 EDA
Google — TED Talks
Ted 演讲是一个了不起的创举。很多人,你通过他们的工作和成就了解他们,但从来不知道他们的奋斗,他们对生活的看法,但通过 Ted 演讲,你可以了解所有这些。很多杂志也刊登采访,但是听他们的故事和他们自己的观点是完全不同的。很多时候,人们建议我应该听听 Ted 演讲,就像有一次我的一个朋友建议我听听马尔科姆·格拉德威尔。
我一直相信人们的选择,相信我认识的人,坦率地说,我甚至不喜欢一些 ted 演讲。这让我想到,我想成为一名数据科学家,也许我应该更多地依赖数据,从更多的人口中获得观点。
介绍
为了实现分析数据的愿望,我从 Kaggle 下载了 Ted 演讲数据。在本帖中,我们将使用这个数据集,并尝试找出 ted 演讲中的顶级演讲者,以及关于什么时候 Ted 演讲进行得最多的一些想法。
在我们开始代码和探索部分之前,作为初学者,理解 EDA 是什么以及它为什么重要是非常重要的。
EDA(探索性数据分析)-
维基百科定义
在统计学中,探索性数据分析 ( EDA )是一种分析数据集以总结其主要特征的方法,通常采用可视化方法。探索性数据分析是由约翰·图基倡导的,目的是鼓励统计学家探索数据,并可能提出可能导致新的数据收集和实验的假设。
EDA 有助于鸟瞰数据,我们可以尝试理解它。在数据分析中,它是第一步,是在我们应用任何统计技术之前实现的。人们使用一些特定的统计技术,如直方图或箱线图,但 EDA 不是一套技术或程序,也不限于一两种图或技术。EDA 是关于理解数据的,在开始对数据建模之前获得关于数据的见解是很重要的。
让我们举一个非常简单的现实生活中的例子,当你去购物时,在购买任何东西之前,你会在商店里闲逛,探索哪些东西最适合你或者哪些更便宜。价格、品牌、布料质量、尺寸等等,这些都是特点或特征,如果你不去探索这些特征,你可能最终会买一些你不喜欢的东西。
希望这有意义。让我们开始探索我们的数据。该数据集包含截至 2017 年 9 月 21 日上传到 TED.com 官方网站的所有 TED 演讲音频视频记录的信息。所有谈话,包括观点数量,评论数量,描述,发言者和标题。
ted_data = pd.read_csv("ted_main.csv")
ted_data.head()
Ted Dataset Snapshot
从快照中我们可以看到,日期是 Unix 时间戳格式,name 列包含 name + title。看起来是时候清理数据了。
数据准备:清理和格式化
在数据项目管道中,这一步占用了数据科学家的大部分时间。因此,在开始我们的数据分析和探索之前,让我们试着找出我们的数据集是否需要一些清理和格式化。
从寻找任何丢失的值开始-
# Let's have a look how many values are missing.
ted_data.isnull().sum()
Missing value analysis
我们很幸运,没有丢失值(可以忽略扬声器职业,只有 6 个丢失)。让我们看看数据集结构的内部
#identify Object/Categorical values and Continuous values
ted_data.dtypes
Structure of dataset
这里的 name 列不包含任何数据分析所需的值。所以,我们将放弃这个专栏—
#Drop the name column
ted_data = ted_data.drop(['name'], axis = 1)
ted_data.columns
Ted Talk Column Data
我们还需要转换日期列值,以便在进一步的分析中更好地理解数据。日期列包含 Unix 时间戳格式的值,我们将把它转换成日期时间格式。
from datetime import datetime
def convert(x):
return pd.to_datetime(x,unit='s')ted_data['film_date'] = ted_data['film_date'].apply(convert)
ted_data['published_date'] = ted_data['published_date'].apply(convert)
ted_data.head()
Formatted Data
一些列包含字典和列表格式的数据。我们还不会查看这些列。但是我们还不会查看这些专栏,因为我们的目标是找出最受欢迎的演讲者,以及什么时候演讲发表得最多。
数据探索
1。热门演讲人
数据集中有一个持续时间列。让我们找出谁比其他演讲者讲的时间更长。
#Lets see who talked a lot - top 20
import seaborn as sns
ax = sns.barplot(x="duration", y="main_speaker", data=ted_data.sort_values('duration', ascending=False)[:20])
Speaker vs duration — who talked the most
以上情节表明,道格拉斯·亚当斯谈了很长时间。很多人不喜欢看较长的视频,除非它非常有趣。让我们看看这个演讲者有多少观点。
#Let's see which video got the most views
ax = sns.barplot(x="views", y="main_speaker", data=ted_data.sort_values('views', ascending=False)[:20])
Speaker vs views
在上面的图中,我们可以看到道格拉斯·亚当不在这里,但是很多名字都在这里。视图和时长有关系吗?有可能,我们来看看。
首先,我们将检查数据的分布,然后查看视图和持续时间是否相关/相关。
#let's see the distribution of views
sns.distplot(ted_data[ted_data['views'] < 0.4e7]['views'])#let's see the distribution of duration
sns.distplot(ted_data[ted_data['duration'] < 0.4e7]['duration'])
Views data distribution
Duration data distribution
我们来看看它们有没有关联?
ax = sns.jointplot(x='views', y='duration', data=ted_data)
View v/s duration
看来我们错了。与长短和持续时间没有关系。看看 p 值,相当低。
有评论栏,最受欢迎的视频可能会得到更多的讨论。但是在找到热门演讲人和他/她的视频之前,我们先看看评论和观点有没有关系?
sns.jointplot(x='views', y='comments', data=ted_data)
Views vs Comments
看上面的情节和 p 值,说明观点和评论有很强的关系。流行的视频往往有更多的讨论。
现在我们知道评论和观点使得 ted 演讲上的任何视频都很受欢迎。所以,让我们来看看谁是最受欢迎的演讲者。
ted_data[['title', 'main_speaker','views', 'comments', 'duration']].sort_values('comments', ascending=False).head(10)
Top 10 speaker(According to discussion on their talk)
看来我要去看理查德·道金斯的无神论演讲了。
2。月—脱口秀主持人最多的**
现在我们想知道哪个月举办的 Ted 演讲最多。我们有 film_date 专栏,但我们没有每月组织一次讲座。我们可以很容易地获得这些信息,只需计算电影日期月份的条目数。
talk_month = pd.DataFrame(ted_data['film_date'].map(lambda x: x.month).value_counts()).reset_index()
talk_month.columns = ['month', 'talks']
talk_month.head()
Number of talks organized by months
不是很好,是吗?让我们将数据可视化。
sns.barplot(x='month', y='talks', data=talk_month)
Number of Ted talks in each month
看起来二月是举办 ted 演讲的最佳月份。所以,明年二月,我会准备好倾听人们的心声。虽然,我们可以根据年份进一步划分数据,但在这篇文章中我们不会涉及太多细节。
总结
正如我们从这篇文章中看到的,数据科学家甚至可以在应用复杂的机器学习算法之前对数据做些什么。EDA 是数据科学项目管道的重要组成部分,也是我认为最复杂和耗时的部分。在对数据做出决定之前,应该花时间去理解和探索数据。很多公司有时会问一些关于数据的问题,他们问的不是结果或最终产品,而是洞察力,或者我应该说是数据的故事。