TowardsDataScience-博客中文翻译-2019-五十一-

TowardsDataScience 博客中文翻译 2019(五十一)

原文:TowardsDataScience Blog

协议:CC BY-NC-SA 4.0

成为数据科学家的实用指南

原文:https://towardsdatascience.com/practical-guide-to-become-a-data-scientist-2483a5f83770?source=collection_archive---------15-----------------------

现在是成为数据科学家的最佳时机。以下是方法。

数据科学家是目前市场上最热门的工作之一。对数据科学的需求是巨大的,而且只会增长,而且它的增长速度似乎比数据科学家的实际数量要快得多。因此,如果你想改变职业生涯,成为一名数据科学家,现在正是时候。

Become a Data Scientist

谁是数据科学家?

数据科学家是在数据、算法和数据可视化方面拥有深厚知识的专家。要成为一名数据科学家,你需要具备团队合作的能力,理解数据结构,分析数据,设计和创建图表,编写简洁的代码。

什么是数据科学家工资?

数据科学家是目前业内最抢手的工作之一,它有很多额外津贴。他们每年可以赚 12 万到 18 万美元。相比之下,软件开发人员的收入在 11 万至 13.5 万美元之间。

当然,数据科学家的工资取决于他们的具体角色,但他们通常在分析或机器学习领域工作,经常处理大型数据集。

他们需要有优秀的分析能力,编程或数据库经验,以及很强的写作能力。

数据科学家职位描述

数据科学家的工作是开发和分析数据,然后分析数据以创造洞察力。这些见解可以以多种方式使用,包括在各种业务决策中,并且通常用于提出建议或帮助为新产品或服务建立业务案例。数据科学家的工作涉及各种不同的数据科学主题,例如:

商业智能、网络分析、自然语言处理、社交媒体分析、预测分析、机器学习、数据挖掘等等。

你可以在 LinkedIn 或 AngelList 或其他各种网站上找到一些关键职位的列表以及如何申请这些职位,这些网站允许你浏览你所在地区的工作机会。

成为数据科学家需要哪些技能

数据科学家的需求量很大,所以如果你对这类工作感兴趣,你需要具备全面的技能。

你需要的一些技能包括:

-出色的分析技能,包括理解数据的能力

-良好的项目管理技能,包括计划和管理项目以及与他人沟通的能力

-良好的沟通技巧,包括清晰简洁的写作能力

-了解数据完整性和隐私问题的重要性,以及了解用户的重要性

-出色的量化技能,包括利用数据获得洞察力和清晰传达结果的能力

-理解复杂信息和解释结果的能力

-批判性思考和解决问题的能力,既包括理解全局,也包括就如何解决特定问题做出具体决策

数据科学家的技能

从更专业的角度来看,你还需要具备以下条件——尤其是如果你申请的是非初级职位的话:

-精通 Excel、SAS、R 或 Python (注:Excel 是一种编程语言)

-机器学习方面的经验

-数据库经验(如关系数据库或 NoSQL 数据库)

-数据可视化经验

-强大的技术背景和/或计算机科学背景

-快速学习的能力

此外,数据科学家通常在团队中工作,这意味着你会被要求快速学习很多东西。

这不全是关于数据,而是信息,所以如果你想发展在这个行业取得成功的必要技能,你需要成为一个积极的学习者。

成为数据科学家的 3 个步骤

现在为非常实际的事情:

  1. GitHub 上构建你的知识库,并开始一个开源项目。你可以从 Kaggle 获取一个数据集,然后围绕它构建一些东西。通常分类问题往往更容易。这将让你磨练自己的技能,并向潜在雇主展示你的参与度。
  2. 参加关于数据科学和机器学习的脸书 /LinkedIn 小组。尝试查找附近的聚会和会议,并参加它们以结识更多的人。有人指导总是好的。
  3. 多写代码!数据科学最终是一种实用技能。在你的社交媒体上分享它——更新你的 LinkedIn 个人资料,以便有更好的机会找到工作。

祝你好运!

[## 加入我的数据科学时事通讯

让我们保持联系,继续学习数据科学。](https://creative-producer-9423.ck.page/c3b56f080d)

Data Science Job

最后,如果你想了解成为一名数据科学家意味着什么,那么看看我的书数据科学工作:如何成为一名数据科学家,它将指导你完成这个过程。

如果您想了解更多,请阅读我关于成为数据科学家的其他文章:

机器学习实用指南

原文:https://towardsdatascience.com/practical-guide-to-machine-learning-b391e538afcb?source=collection_archive---------35-----------------------

现在就开始你的机器学习生涯吧!

我们大多数人都已经看到了机器学习工作的迅速崛起,但对这个特定领域来说仍然是新手。问题是大部分 ML 的工作岗位都不是“入门级”的,所以很多人都在努力训练自己。这里有一些有用的信息给你,关于 ML 的不同阶段包括什么。

Start your dream career in Machine Learning

开始:开始

在每一个新的领域,都有人从新手开始,增加他们的知识。如果你是一个必须学习基础知识的人,并且你感到停滞不前,看看下面的列表,看看你下一步需要做什么。

是的,你可能需要更多的投入。好消息是这些资源在网络上非常丰富。

通过流行的视频和书籍自学

书籍对于熟悉这个领域很有帮助。视频提供了更多的教学指导,通常是灵感的良好来源。确保你看了关于 ML 技术的教程,如果你犯了一个错误,你有能力改正它。视频(尤其是 Udemy 或 Coursera 上的课程)可能需要付费,但这两种格式的视频通常都是免费的。

这里有一些资源可以找到一本关于 ML 的好书。

向专家学习

如果你买不起书,但你知道你想上 ML 的课,找一个好的导师是很好的,最好是该领域的前专业人士。当你面对一个你不知道如何解决的问题,需要一个问题的答案时,一个好的导师会很有帮助。

在许多情况下,如果你没有足够的资金,参加在线课程也是有利的。通过参加本课程,您通常可以获得进一步的培训,从而从本课程中获得最大收益。

高级:学习掌握

进入这个领域的下一步是学习如何掌握你正在使用的工具(又名计算机)。

一些提示:

  • 学习如何编码。
  • 学习 Python。
  • 浏览不同教程中的机器学习代码。
  • 学习数据挖掘。
  • 擅长统计学。
  • 学习机器学习理论和实践。

这是许多人在这一点上已经完成所有训练的阶段,现在可以进行一些真正的机器学习。重要的是,你不要让这成为一个障碍,当你对 ML 越来越熟悉时,你要继续学习和练习。

这一阶段在你的职业生涯中的重要性可能显而易见,所以确保你仍然了解自己的最新发展。

进阶:将好的开始转化为最好的职业

这是职业生涯中最重要的阶段之一。在这个阶段,你将从一名学生转变为你所在领域的专家。你必须让你的老板相信你有资格晋升。

  • 向你的主管学习,找一个了解你正在做的具体项目的人。
  • 学习如何沟通和展示自己
  • 学习如何使用你获得的新技能来解决新问题

你是拥有所有这些东西的幸运儿之一吗?

太好了,这就是你成为机器学习大师的途径。

Data Science Job

最后,如果你想了解成为一名数据科学家意味着什么,那么看看我的书数据科学工作:如何成为一名数据科学家,它将指导你完成这个过程。

异常值检测方法实用指南

原文:https://towardsdatascience.com/practical-guide-to-outlier-detection-methods-6b9f947a161e?source=collection_archive---------13-----------------------

如何系统地检测单变量数据集中的异常值

Photo by Tom Paolini on Unsplash

我将讨论在 R Studio 中实现的四种异常检测方法的细节。我将提到异常值是什么,为什么它很重要,为什么会出现异常值。这些方法如下:

  1. 图基的方法
  2. 推特异常检测
  3. z 分数法
  4. 平均绝对偏差(MADe)法

简而言之,离群值是指与数据集中的其他值有很大差异(远远小于或大于)的数据点。异常值可能是因为随机变化,或者可能证明了一些科学上有趣的事情。无论如何,在仔细调查之前,我们不应该简单地排除无关的观察。理解它们以及它们在正确的学习环境中的出现对于能够处理它们是很重要的。

为什么会出现离群值?

离群值有几个原因:
1。样本中的一些观察结果是极端的;
2。数据被不恰当地缩放;
3。数据输入出错。

数据集和预处理

我的公共数据集显示了该公司从 2014 年到 2017 年的每周销售额,它提供了“年”、“周”和“销售额”列,如下所示。

Figure 1: Original Dataset

数据集的维度是 201 x 3,数据包括 NA 值。我查看了空白值的那一周,然后通过取与数据集中的空白值相同的那一周的观察值的平均值来填补空白。

dim(sales_data)
[1] 201   3sum(is.na(sales_data$Sales))
[1] 4

该曲线图指出了 2014 年至 2017 年土耳其的饮料销售额。

Figure 2: Time Series Analysis

现在,是时候深入研究异常检测方法了。

  1. 图基氏法(盒须)

Figure 3: Box and Whiskers

  1. 最小值:数据集中的最小值(Q1–1.5 * IQR)
  2. 第一个四分位数:低于该值的 25%的数据包含在内( Q1 )
  3. 中值:数字范围中的中间数( Q2 )
  4. 第三个四分位数:包含高于该值的 25%的数据( Q3 )
  5. 最大值:数据集中的最大值( Q3+1.5*IQR )
  6. 四分位范围(IQR): 数据集中的观察范围( Q3-Q1 )

这种方法对于指示分布是否偏斜以及数据集中是否存在潜在的异常观察非常有用。

Figure 4: Box plot of sales dataset

“gg plot”是 R 中功能强大的数据可视化软件包之一;于是我用【gg plot】包来画销售箱线图。如上所示,蓝点表示异常值,红点表示数据集的中值。 的数据是左倾的。

如左表所示,模型在数据集中检测到 8 个异常值 。我可以明确地说,这些数据点不同于数据集的其余部分。事实上,离群点的销售值在 11.5 万以上。

2。推特异常检测

AnomalyDetection 是一个开源的 R 包,用于检测异常情况,从统计的角度来看,在存在季节性和潜在趋势的情况下,它是健壮的。AnomalyDetection 包可用于多种环境,如新软件发布、用户参与帖子和金融工程问题。称为季节性混合 ESD 的基本算法建立在用于检测异常的通用 ESD 测试的基础上。它可用于发现全局和局部异常。

下面可以看到,我实现了 Twitter 异常,然后模型通过取 alpha 值为 0.05,与 Tukey 的方法比较,在数据集中发现了 6 个异常值 。异常点在表格中被标识为一个圆圈。

Figure 5: Twitter Anomalies

下表显示了哪些数据点被标记为异常值。该指数累计显示了销售的周数。这些异常值通常属于 2014 年和 2015 年。

3。z 值法

Z-score 查找平均值为 0 且标准差为 1 的数据分布。Z 得分法依靠数据的平均值和标准偏差来衡量集中趋势和分散程度。由于平均值和标准偏差受到异常值的严重影响,这在很多情况下是有问题的。

从统计学的角度来看,使用 1.96 的临界值将得到 p = 0.05 的等价 p 值。结果,该方法在 201 次观察中检测到 8 个极值点。Z-score 方法在这个数据集上符合盒须现象。这些点大多出现在 2014 年。

4。制作方法

在统计学中,中位数绝对偏差 ( MAD )是单变量数据可变性的稳健度量。此外,MAD 类似于标准偏差,但与标准偏差相比,它对数据中的极值不太敏感。

我首先计算了中值,然后对于每个数据点,我计算了该值和中值之间的距离。MAD 被定义为这些距离的中间值。然后,这个量(MAD)需要乘以 1.4826,以确保它接近实际标准偏差。

通过应用下面的公式,使用中位数和 MADe 来检测异常值。

作为该方法的结果,如上所示,发现了 9 个异常值。

期末笔记

我执行了四种异常值检测方法,每种方法可能在数据集上产生不同的结果。因此,你必须选择其中一个来观察异常值* 或者 可以将所有方法中最常见的点标注为极值点。在这种情况下,在我上面提到的方法中有 6 个共同的极端点。*

下一步,在预测建模之前,将通过考虑不同的方式来转换离群点。这将产生更准确的预测模型。

你可以在这里看到所有代码***下一个故事即将到来……*****

实用 JavaScript:数组与对象

原文:https://towardsdatascience.com/practical-javascript-arrays-vs-objects-3c1f895907bd?source=collection_archive---------7-----------------------

Photo by Meagan Carsience on Unsplash

今天有人问我:“你怎么知道什么时候使用对象,什么时候使用数组?”我在网上找不到能给出我想要的答案的资源,所以…我会成为我想看到的改变。

TL;博士简介

想想你的特定数据代表什么:如果它是一个具有命名属性的单一实体,你需要一个对象。如果是一组相同类型/形状的实体,或者顺序很重要,您可能需要一个数组。

如果还不清楚,想想你将如何处理数据:操纵单个属性?大概是反对。对整体数据进行操作,还是过滤和操作大块数据?我猜是一个数组。

此外,如果您正在处理现有数据,并且它已经是一个对象或数组,那么如果没有充分的理由,您可能不会将它转换为另一个对象或数组。

// A list of ordered strings is a good case for an array:
const sortedNames = ['Axl', 'Billie', 'Chuck'];// An item with named properties is a good case for an object:
const box = { height: 4, width: 3, color: 'blue' };

两种类型的集合

数组和对象是将数据收集到一个组中的两种方式。数据可以是原语(字符串、数字、布尔值):

const namesArr = ['Danny', 'Donny', 'Joey', 'Jordan', 'Jonathan'];
const userObj = { name: 'Jamie', age: 42 };

…或者它们可以由其他数组或对象组成:

const usersArr = [{ name: 'Jim', age: 4 }, { name: 'Al', age: 62 }];
const miscObj = { colors: ['orange', 'red'], numbers: [1, 2, 3] };

那么,你为什么要选择一个而不是另一个呢?冒着过于简化的风险,它归结为易用性性能

插入、删除、迭代、更新

我说的易用性是什么意思?当我们将数据分组在一起时,我们通常希望以某种方式使用它。具体来说,我们希望添加元素,移除元素,访问/更新元素,或者迭代元素。

边注:提问的人正在使用 React,所以不变性是一个问题,这对易用性/可读性有影响。像 *push(), pop(), splice()* 等可变方法会使事情变得更简单,但是在这些例子中,我会不变地思考。也有一些不同的方法来实现这些示例中的每一个(例如,spread vs. *concat* ),但我将只坚持一种方法。

插入

假设我们有这样一组名字:

const names = ['Bob', 'Cate'];

我们有一个新的名字,我们想添加一个到两端。轻松点。

const namesPlusEnd = [...names, 'Deb'];
// ['Bob', 'Cate', 'Deb'];const namesPlusStart = ['Axl', ...names];
// ['Axl', 'Bob', 'Cate'];

但是当我们想在数组中间插入一个名字的时候,我们需要知道索引。我们不能插入东西,除非我们知道它需要去哪里,所以如果我们没有索引,我们需要使用Array.findIndex找到它,这需要时间来遍历数组。

const namesPlusMiddle = [
  ...names.slice(0, 1),
  'Bud',
  ...names.slice(1)
];// ['Bob', 'Bud', 'Cate']

另一方面,对象不跟踪顺序,所以在任何地方添加属性都很简单,因为没有开始/中间/结束的概念,并且快速,因为我们不需要迭代:

const box = { width: 4, height: 3, color: 'blue' };If we care about immutability:
const newBox = { ...box, id: 42 };Or, if we don't:
box.id = 42;// box/newBox are both now:
// { width: 4, height: 3, color: 'blue', id: 42 };

删除

移除物品呢?还是那句话,看情况!易于从数组的开头或结尾删除:

const colors = ['red', 'green', 'blue'];const colorsWithoutFirst = colors.slice(1);
// ['green', 'blue']const colorsWithoutLast = colors.slice(0, -1);
// ['red', 'green']

从中间开始也很容易,但是同样,您需要知道想要删除的索引(在本例中是 index 1),或者迭代过滤出值:

const colorsMinusMid = [...colors.slice(0, 1), ...colors.slice(2)];
// ['red', 'blue']const colorsMinusGreen = colors.filter(color => color !== 'green');
// ['red', 'blue']

就像给对象添加属性一样,无论对象在哪里,删除对象属性都很简单(因为没有什么东西在对象中“哪里”的概念)。

Immutably:
const { color, ...colorlessBox } = box;With mutation:
delete box.color;colorlessBox/box are both now:
// { height: 4, width: 3, id: 42 }

更新

更新-不是一个真正的词。当我们想要更新数组中的元素时,我们可以通过索引来完成,或者如果我们不知道索引,我们可以迭代它,根据元素的值(或者元素的属性)来查找元素。通过迭代进行更新是很常见的,因为我们经常在不知道索引的情况下处理大型数据集,或者在索引可能发生变化的情况下处理动态数据。

const fruits = ['apple', 'banana', 'clementine'];const newFruits = [
  ...fruits.slice(0, 1),
  'watermelon',
   ...fruits.slice(1)
];This is a little simpler, and leaves the fruits array unchanged:
const fruitsCopy = fruits.slice();
fruitsCopy[1] = 'watermelon';Or, if we don't know the index:
const newFruits = fruits.map(fruit => {
  if (fruit === 'banana') return 'watermelon';
  return fruit;
});// ['apple', 'watermelon', 'clementine'];

同样,更新一个对象要简单得多:

const box = { height: 4, width: 3, color: 'blue' };Immutably:
const redBox = { ...box, color: 'red' };Mutably:
box.color = 'red';// box/newBox are both now:
// { height: 4, width: 3, color: 'red' }

访问元素

如果您只需要获得数组中某个元素的值(不需要更新它),如果您知道索引就很简单,如果您不知道索引就不会太难(但是您知道一些关于您要寻找的元素的信息):

const fruits = ['apple', 'banana', 'clementine'];const secondFruit = fruits[1];
// 'banana'const clementine = fruits.find(fruit => fruit === 'clementine');
// 'clementine'

访问对象属性也很容易:

const box = { width: 4, height: 3, color: 'blue' };const boxColor = box.color
// 'blue'

迭代和方法

到目前为止,与对象相比,数组是一种累赘。用单个数组元素做任何事情都需要知道索引,或者需要更多的代码。最后,随着迭代,是时候让数组发光了。当您想成批地对元素进行一些转换时,数组就是为此而设计的:

const fruits = ['apple', 'banana', 'clementine'];const capitalFruits = fruits.map(fruit => fruit.toUpperCase());
// ['APPLE', 'BANANA', 'CLEMENTINE']fruits.forEach(fruit => console.log(fruit));
// 'apple'
// 'banana'
// 'clementine'Iteration is common in React:
const FruitsList = props => (
  <ul>
    {props.fruits.map(fruit => <li>{fruit}</li>)}
  </ul>
);
// <ul>
//   <li>apple</li>
//   <li>banana</li>
//   <li>clementine</li>
// </ul>

要迭代一个对象,我们唯一真正的选择是一个for...in循环,但是(在我看来)通常更简单/更易读的方法是……将它转换成一个数组。Object.keys/values/entries遍历键、值或两者,并给我们一个数据数组:

const box = { height: 4, width: 3, color: 'blue' };const BoxProperties = ({ box }) => (
  <ul>
    Object.keys(box).map(prop => <li>{prop}: {box[prop]}</li>);
  </ul>
);
// <ul>
//   <li>height: 4</li>
//   <li>width: 3</li>
//   <li>color: blue</li>
// </ul>

数组还有其他方法允许您处理数据,而这些方法是对象所没有的:

const animalNames = ['ant', 'bird', 'centipede', 'demogorgon'];animalNames.reverse();
// ['demogorgon', 'centipede', 'bird', 'ant']const shortNames = animalNames.filter(name => name.length < 5);
// ['ant', 'bird'];const containsB = animalNames.some(name => name.includes('b'));
// trueconst allAreLong = animalNames.every(name => name.length > 3);
// falseconst totalLetters = animalNames.reduce((total, name) => {
  return total + name.length;
}, 0);
// 26

您可以用for...in很容易地实现其中的任何一个,但是数组有现成的。

表演

速度并不总是一个需要考虑的因素,但是当它是一个需要考虑的因素时,数组和对象之间会有很大的不同。互联网上有大量关于数组与对象性能的资源,但简单来说:当您不知道索引(线性时间或 O( n ))时,数组操作会更慢,因为您必须迭代每个元素,直到找到您想要使用的元素。如果您确实知道索引并且不变性不是问题,那么您不需要迭代,并且可以快速访问/更新该索引处的元素(常量时间,或者 O(1))。对象属性查找/更新/插入/删除发生得很快(也是常数时间),因为属性名称给了你一个参考,所以你不必去寻找你想要的元素。

结论

经验法则是:类似类型的数据组(您需要对其进行排序或者希望对其进行批处理操作)更适合于数组,而单个实体的分组属性更适合于对象。使用正确的数据类型并不总是一个明确的选择,但是你使用每种数据类型越多,在任何给定的情况下哪种数据类型更有意义就越明显。

用 C++和 GRT 实现实用的机器学习

原文:https://towardsdatascience.com/practical-machine-learning-with-c-and-grt-a54857972434?source=collection_archive---------14-----------------------

Photo by Franck V. on Unsplash

这将是从程序员的角度解释机器学习基础知识的系列教程中的第一篇。在第 1 部分中,我将展示如何使用 GRT 库将基本的机器学习整合到 C++项目中。

什么是机器学习?

机器学习是一种计算方法,它使程序能够基于给定的输入生成可预测的输出,而无需使用显式定义的逻辑。

例如,使用传统的基于逻辑的编程,我们可以编写一个对水果进行分类的函数,它以颜色和尺寸作为输入,输出水果的名称。大概是这样的:

string classifyFruit(Colour c, Dimensions d)
{
    if (c.similar({255, 165, 0}))   // green
    {
        if (d.similar({10, 9, 11})) // round-ish
        {
             return "Apple";
        }
        else
        {
             return "Pear";
        }
    }
    if (c.similar({255, 255, 0}))   // yellow
    {
         return "Banana";
    }
    if (c.similar({255, 165, 0}))   // orange
    {
         return "Orange";
    }

    return "Unknown";
}

可以看出,这种方法存在各种各样的问题。我们的函数只知道四种水果,所以如果我们想扩展它来对 Clementines 进行分类,我们需要额外的语句来区分它们和橙子。根据水果的确切形状和我们的similar()方法的定义,我们还会将梨和苹果混在一起。要创建一个函数来对各种各样的水果进行高精度分类,事情会变得非常复杂。

机器学习通过将输入和输出之间的关系表示为状态而不是通过逻辑规则来解决这个问题。这意味着我们可以使用given / then例子来表达我们的意图,而不是使用if / then / else语句来编写我们的分类器。所以决定我们函数行为的代码看起来更像这样:

ml.given({0,   255, 0, 10, 9,  11}) .then("Apple");
ml.given({0,   255, 0, 15, 7,  8})  .then("Pear");
ml.given({255, 255, 0, 20, 4,  4})  .then("Banana");
ml.given({255, 165, 0, 10, 10, 10}) .then("Orange");

这里,ml表示我们的机器学习对象,given()的列表参数表示不同类型水果的颜色和尺寸元组,then()的字符串参数表示给定输入时我们期望从分类器得到的输出。

如果我们程序的下一步是类似于x = ml.classify({255, 165, 0, 10, 10, 10})的东西,我们期望x的值是“橙色”。

这种方法的优点是,由于我们已经将状态逻辑中分离,指定输入/输出关系的过程可以自动化。

类似于:

auto rows = csv.read();for (auto& r : rows)
{
    ml.given(r.colour, r.dimension).then(r.name);
}

现在,我们可以添加尽可能多的不同类型的水果,或者在不修改代码的情况下给出同一类型水果的许多不同示例!我们唯一需要改变的是输入数据的大小和内容,在本例中是来自一个 CSV 文件。

敏锐的读者现在会想:我们不是刚刚创建了一个大的查找表吗?答案是“否”,因为这只会对与示例中的颜色和尺寸完全匹配的水果进行分类。相反,我们希望系统能够对新的水果项目进行分类,这些项目与我们的一个示例具有相同的名称,但是具有不同的尺寸和颜色。因此对于x = c.classify({245, 145, 0, 11, 9, 10}),我们期望输出为“橙色”,即使颜色和尺寸与我们在given / then语句中提供的任何示例都不完全匹配。

为了实现这一点,机器学习系统使用一组统计参数来定义基于现有输入的给定输出的可能性。每次提供新的示例时,这些参数都会更新,以使系统更加精确。这就是监督机器学习的本质。

因此,让我们回顾并建立一些术语:

  • 监督机器学习中,我们有一组定义系统输入和输出之间预期关系的例子。这被称为数据集
  • 数据集中的每一行都由确定输入的类别标签和确定其属性的特征向量组成(例如{245, 145, 0, 11, 9, 10}包含颜色和尺寸特征)。
  • 我们的目标是创建输入(特征向量)和输出(类别标签)之间的统计 关系的表示。这叫做型号
  • 为了实现这一点,我们使用一个系统来从训练数据集生成模型,并使用该模型执行分类。这叫做机器学习算法

因此,机器学习算法被称为从数据集“学习”,以便生成可用于对原始数据集中不存在的输入特征向量进行分类的模型。

有什么用?

机器学习有很多种。在这篇文章中,我主要关注监督分类。当我们想要建立一个可以自动分类许多不同类别的对象的系统时,监督分类是有用的。“独特的”这个词很重要,因为当物体的类别具有将它们彼此分开的独特特征(颜色、形状、大小、重量等)时,分类算法工作得最好。如果我们的对象非常相似,并且只能通过特征的微小变化来区分,那么分类的效果就不太好。因此,它可以很好地用于水果或人脸(不同的眼睛颜色、头发颜色、面部毛发、脸型等),但会很难根据气温和天空颜色来识别某人的位置。这种区分不同物体的能力被称为可分性,是机器学习中的一个重要概念。一个很好的经验法则是,如果人类难以区分不同类别的物体,机器也会如此。

我如何将机器学习整合到我的 C++项目中?

现在我们对机器学习有了基本的了解,我们如何将它融入到我们的 C++项目中呢?一个很好的库是手势识别工具包或 GRT。GRT 是为实时手势识别而开发的,适用于一系列机器学习任务。在麻省理工学院的许可下,它可以在 Windows、Mac 和 Linux 上使用,因此可以用于封闭或开源项目。GRT 的完整类文档可以在这里找到。

GRT 由许多 C++类组成,每个类都实现一个特定的机器学习算法。几乎所有 GRT 类都使用以下约定:

  • train(...)使用输入数据(...)来训练新的模型,该模型然后可以用于分类。
  • predict(...)使用输入向量(...)训练模型进行分类。
  • getPredictedClassLabel()返回从输入向量预测的类别标签
  • save(...)将模型或数据集保存到文件中。
  • load(...)从文件中加载预训练模型或数据集。
  • clear()清除 ML 对象,删除所有预训练模型

建筑 GRT

以下说明是针对 Mac 和 Linux 平台的,Windows 用户应该参考官方的构建指南

要构建 GRT,需要 CMake。CMake 可以从项目页面安装。在 macOS 上,我推荐使用自制软件来安装 CMake。

安装 CMake 后,从 git 存储库下载 GRT:

$ git clone [https://github.com/jamiebullock/grt](https://github.com/jamiebullock/grt)

然后:

$ cd grt/build
$ mkdir tmp && cd tmp
$ cmake .. -DBUILD_PYTHON_BINDING=OFF
$ make

如果发生构建错误,可以在项目问题跟踪器中报告。否则,可以按如下方式测试构建:

$ ./KNNExample ../../data/IrisData.grt

这应该会输出类似如下的内容:

[TRAINING KNN] Training set accuracy: 97.5TestSample: 0 ClassLabel: 3 PredictedClassLabel: 3TestSample: 1 ClassLabel: 2 PredictedClassLabel: 2...TestSample: 29 ClassLabel: 2 PredictedClassLabel: 2Test Accuracy: 93.3333%

这将执行在examples/ClassificationModulesExamples/KNNExample/KNNExample.cpp中找到的代码——基于来自虹膜数据集的数据的虹膜花分类器。更多解释见此处

机器学习 Hello World!

我们现在准备使用 GRT 构建一个简单的水果分类器!

首先我们需要创建一个新的源文件,(姑且称之为 fruit.cpp)并包含 GRT 头文件。这是使用 GRT 库中大部分功能所需的唯一头文件。

#include "GRT.h"
using namespace GRT;typedef std::vector<double> v_;int main (int argc, const char * argv[])
{
}

接下来,我们将从训练数据集中添加一些数据。为了这个例子的目的,我们将使用同样的“水果数据”。为此,我们使用 GRT ClassificationData 类来创建一个简单的数据集。代替字符串,在机器学习中使用数字标签,这里我们假设一个映射:1 =苹果,2 =梨,3 =香蕉,4 =橘子。所以我们增加了我们的main()函数:

ClassificationData dataset;
dataset.setNumDimensions(6);// Add 3 examples for each item to give our classifier enough data
for (int i = 0; i < 3; ++i)
{
    dataset.addSample(1, v_{0,   255, 0, 10, 9,  11});   // Apple
    dataset.addSample(2, v_{0,   255, 0, 15, 7,  8});    // Pear
    dataset.addSample(3, v_{255, 255, 0, 20, 4,  4});    // Banana
    dataset.addSample(4, v_{255, 165, 0, 10, 10, 10});   // Orange
}

在实际代码中,我们将添加更多不同的训练示例,以便我们的分类器可以很好地推广到各种输入。我们还将使用loadDatasetFromCSVFile()方法从文件中加载数据集,这样数据就可以从我们的代码中分离出来。这方面的文档可以在这里找到。

接下来,我们加载数据集并训练分类器。这里我们使用一个KNN分类器,它实现了 k-NN 算法,但是 GRT 中的任何其他分类器都可以工作。作为一个练习,鼓励读者尝试从 GRT 中替换出各种不同的分类器。

// The classification class. Try also SVM!
KNN classifier;// Train our classifier
classifier.train(dataset); 

就是这样!我们现在有了一个训练好的模型,使用基于我们提供的输入数据集的 k-NN 算法。现在测试分类器…

VectorFloat testVector = v_{0,   255, 0, 10, 9,  11};// Predict the output based on testVector
classifier.predict(testVector);// Get the label
auto classLabel = classifier.getPredictedClassLabel();std::cout << "Class label: " << classLabel << std::endl;

最后,为了证明我们的分类器可以推广到原始数据集中不存在的输入,我们将要求它对与我们的训练示例之一相似但不相同的特征向量进行分类。

VectorFloat differentVector = v_{10, 240, 40, 8, 10, 9};classifier.predict(differentVector);
auto otherLabel = classifier.getPredictedClassLabel();std::cout << "Other label: " << otherLabel << std::endl;

因为我们的differentVector与苹果的dataset向量最相似,所以我们希望程序输出Other label: 1。让我们看看它是否有效!

完整的代码列表

#include "GRT.h"
using namespace GRT;typedef std::vector<double> v_;int main (int argc, const char * argv[])
{
    ClassificationData dataset;
    dataset.setNumDimensions(6); // Add 3 examples each to give our classifier enough data
    for (int i = 0; i < 3; ++i)
    {
        // Apple
        dataset.addSample(1, v_{0,   255, 0, 10, 9,  11});
        // Pear
        dataset.addSample(2, v_{0,   255, 0, 15, 7,  8});
        // Banana
        dataset.addSample(3, v_{255, 255, 0, 20, 4,  4});
        // Orange
        dataset.addSample(4, v_{255, 165, 0, 10, 10, 10});   
    } // The classification class. Try also SVM
    KNN classifier; // Train our classifier
    classifier.train(dataset); // Create a test vector
    VectorFloat testVector = v_{0,   255, 0, 10, 9,  11}; // Predict the output based on testVector
    classifier.predict(testVector); // Get the label
    auto classLabel = classifier.getPredictedClassLabel(); std::cout << "Class label: " << classLabel << std::endl; // Try an input vector not in the original dataset
    VectorFloat differentVector = v_{10, 240, 40, 8, 10, 9}; classifier.predict(differentVector);
    auto otherLabel = classifier.getPredictedClassLabel(); std::cout << "Other label: " << otherLabel << std::endl; return 0;}

要在 macOS 或 Linux 上编译代码,键入以下内容(来自我们在本教程开始时创建的同一个tmp目录)。

g++ -std=c++14 fruit.cpp -ofruit -lgrt -L. -I../../GRT

这告诉编译器链接到当前目录中的libgrt.so,头文件在../../GRT中,如果将 GRT 移动到其他地方,参数-L-I将需要调整。

最后,我们运行我们的程序:

./fruit

输出应该是:

Class label: 1
Other label: 1

恭喜你!您刚刚用 26 行 C++代码编写了一个通用分类器🤩。如果我们从一个 CSV 文件加载数据集,它会少得多。

我希望这个教程是有用的。敬请关注未来更多内容!

基于 Keras 的实用机器学习

原文:https://towardsdatascience.com/practical-machine-learning-with-keras-19d0e5b2558?source=collection_archive---------20-----------------------

使用 Keras 快速构建实用的 ML 模型

Image by Comfreak from Pixabay

这篇文章是关于什么的?

在本文中,我尝试解释了什么是 keras,并总结了我在 keras 中构建模型的所有方法,解释了所有实用的技巧以及与之相关的所有细微差别。这篇文章将教你几种在建模中使用的增加复杂性的常用技术。

  • 序列模型
  • 功能 API(高级)
  • 模型子类化(高级)

我们将使用一个非常著名的叫做 MNIST 的手写数字数据集,用这三种不同的方法制作 CNN 模型。我还将介绍基本的加载和使用数据。

这篇文章不是什么?

本文不会教你机器学习背后的理论或数学。如果你是机器学习的新手或者刚刚开始学习,我会提供一些资源来帮助你。

好了,现在让我们开始学习。

Keras 是什么?

Keras 是一个高级神经网络 API,用 Python 编写,能够在 TensorFlowCNTKTheano 之上运行(我们将使用 Keras 和 TensorFlow 作为后端)。

跳过正式的定义,keras 是一个易于使用的机器学习库,可用于快速原型制作,以构建复杂的定制模型,用于研究和生产以及介于两者之间的任何事情。Keras 有一个非常模块化和可组合的 API,从这个意义上说,Keras 模型是通过将可配置的构建块连接在一起而制成的(您很快就会看到这有多简单)。

让我们把手弄脏吧

现在您对 keras 有所了解,让我们直接进入它的实际实现。

导入 tf.keras

[tf.keras](https://www.tensorflow.org/api_docs/python/tf/keras)是 TensorFlow 对 Keras API 规范的实现。这是一个高级 API,用于构建和训练模型,包括对 TensorFlow 特定功能的一流支持。

加载数据

Original shape:  (60000, 28, 28) (60000,) (10000, 28, 28) (10000,)
After reshape and normalize:  (60000, 28, 28, 1) (10000, 28, 28, 1) (60000,) (10000,)

画出一个训练例子

将标签转换为分类标签

(60000, 28, 28, 1) (10000, 28, 28, 1) (60000, 10) (10000, 10)

现在我们已经加载了数据,并准备好投入到机器学习模型中,我们可以编写我们的第一个模型了。我的朋友,你做得很好,给你一张猫的照片作为奖励。

cat motivation: let’s learn some more

顺序模型

在 keras 中,模型可以被分解成各层之间有不同连接的图形。最常见的类型是一堆层,因此得名顺序。

让我们看看添加激活、初始化器和正则化的不同方法:

  • 激活:该参数设置层的激活功能。默认为不激活。
  • kernel_initializerbias_initializer :初始化方案为层创建权重。默认是 Glorot 制服。
  • kernel _ regulatorbias _ regulator:对权重应用 L1 和 L2 正则化,控制模型的过拟合。默认情况下没有正则化。

为 MNIST 造一个

让我们建立一个模型来对 MNIST 数据进行分类。这里我们将展示如何定义卷积层和最大池层。我们将使用一个标准的结构来建立一个 CNN: Conv 层,然后是最大池层。然后,我们使用展平图层展平图层,并应用完全连接的密集图层,之后是最终分类图层,该图层将预测分类为 10[0–9]个输出类。当我们调用 model.summary()时,您会更清楚地看到这个结构。

Model: "sequential_1" _________________________________________________________________ Layer (type)                 Output Shape              Param #    ================================================================= conv2d (Conv2D)              (None, 26, 26, 32)        320        _________________________________________________________________ max_pooling2d (MaxPooling2D) (None, 13, 13, 32)        0          _________________________________________________________________ conv2d_1 (Conv2D)            (None, 11, 11, 64)        18496      _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 5, 5, 64)          0          _________________________________________________________________ conv2d_2 (Conv2D)            (None, 3, 3, 64)          36928      _________________________________________________________________ flatten (Flatten)            (None, 576)               0          _________________________________________________________________ dense_9 (Dense)              (None, 64)                36928      _________________________________________________________________ dense_10 (Dense)             (None, 10)                650        ================================================================= Total params: 93,322 Trainable params: 93,322 Non-trainable params: 0 _________________________________________________________________

编译模型

既然我们的模型已经构建好了,我们必须通过调用 compile 方法来配置模型。它需要三个重要参数:

  • 优化器:简单来说,就是决定你的损失函数应该如何减少的函数。
  • 损失:这是损失函数,计算你的预测与实际标签有多远。这是优化器最小化的损失。
  • 指标:用于监控培训过程。我们可以得到关于损失或精确度降低或增加的信息。

训练模型

我们已经为训练准备好了一切,让我们训练我们的第一个 keras 模型。我们通过在模型上调用 fit 方法来训练模型。除了训练数据和标签之外,该模型还采用了一些重要的参数:

  • 历元:一个历元是整个输入数据的一次迭代。迭代可以在较小的批量中完成。
  • batch_size :模型将数据分割成更小的数据批次,然后输入到训练步骤中。除了最后一批可能较小之外,每一批的大小都相同。
  • validation_data :这在很多机器学习文献中也被称为交叉验证集。这在每次训练迭代之后测量模型的性能。
Train on 60000 samples, validate on 10000 samples
Epoch 1/5
60000/60000 [==============================] - 9s 150us/sample - loss: 0.1473 - acc: 0.9552 - val_loss: 0.0674 - val_acc: 0.9783
Epoch 2/5
60000/60000 [==============================] - 7s 117us/sample - loss: 0.0483 - acc: 0.9850 - val_loss: 0.0668 - val_acc: 0.9772
Epoch 3/5
60000/60000 [==============================] - 7s 116us/sample - loss: 0.0330 - acc: 0.9899 - val_loss: 0.0397 - val_acc: 0.9882
Epoch 4/5
60000/60000 [==============================] - 7s 118us/sample - loss: 0.0266 - acc: 0.9913 - val_loss: 0.0292 - val_acc: 0.9910
Epoch 5/5
60000/60000 [==============================] - 7s 117us/sample - loss: 0.0204 - acc: 0.9936 - val_loss: 0.0284 - val_acc: 0.9920<tensorflow.python.keras.callbacks.History at 0x7f6020679828>

[ 注意:出于本教程的目的,我们已经将验证数据和测试数据视为相同,但是在实践中,您应该将一些模型看不到的数据保留为测试数据。]

测试性能

为了测试我们的模型的性能,我们可以通过分别对模型使用预测和评估函数来获得预测或直接评估模型并获得损失和准确性。

Prediction shape:  (10000, 10)Model evaluation: 
10000/10000 [==============================] - 1s 65us/sample - loss: 0.0334 - acc: 0.9891
[0.033432122451053876, 0.9891]

让我们检查一下我们对第一个测试示例的预测,并把它变成一个方便的方法,这样我们就可以用不同的指数为后面的模型调用它。

恭喜你,你已经制作了你的第一个 keras 模型,并对它进行了训练和评估。你应该得到另一张猫的照片作为激励,因为一些预先的东西就要来了。

cat motivation: let’s gooooooooo!

功能 API

在实践中经常使用序列模型,但在许多特殊情况下,您可能希望定义一些简单的层堆栈无法定义的任意模型。我们可以在复杂模型中求助于 Keras 功能 API,例如:

  • 多输入模型
  • 多输出模型
  • 具有共享层的模型,即同一层被多次调用
  • 具有非顺序数据流的模型(例如:ResNets 中的剩余连接)

使用函数式 API 构建模型遵循一组规则:

  1. 层实例是可调用的,并返回一个张量作为其输出。
  2. 要实际创建一个模型,我们必须将输入和输出张量传递给 tf.keras.Model 实例。
  3. 模型的训练类似于顺序模型的训练。

说够了,现在让我们建立一个。

模型的训练和评估保持不变,所以我在这里只给出代码。在这一次,我们只是没有使用验证集,而是使用测试集来评估训练后的未知数据的模型。

10000/10000 [==============================] - 1s 71us/sample - loss: 0.0368 - acc: 0.9914
[0.03678753161538142, 0.9914]

现在,您知道了如何根据自己的喜好在模型中建立奇怪而复杂的连接,但是如果您必须定义自己的前进路线,那该怎么办呢?我们有一个解决方案,但首先,你应该欣赏你的努力工作,这个猫的形象。

模型子类化

我们已经建立了我们的模型,定义了我们自己的数据流,但是我们仍然不能完全控制模型的向前传递。我们可以通过子类化 tf.keras.Model 实例来构建一个完全可定制的模型。

这涉及到几个步骤,请密切注意:

  1. 我们要创建一个 tf.keras.Model 的类实例,并在 init 方法中把所有的层定义为类的属性(基本可以比作不熟悉 python 类的人用的构造函数)。
  2. 然后我们必须在 调用 方法中定义我们的自定义向前传递。
  3. 如果我们想使用我们的子类模型作为函数式模型的一部分,我们也可以覆盖compute _ output _ shape方法(可选方法

让我们将理论付诸实践,为识别 MNIST 数据建立一个子类模型:

让我们训练、评估和测试我们的模型。

恭喜你,现在你知道了制作几乎所有类型模型的工具。在得出结论之前,让我们再学习一件非常方便的事情,就在猫休息之后。

when you code and your cat motivates you

自定义回调

回调是传递给模型的对象,用于在训练期间自定义和扩展其行为。您可以编写自己的自定义回调,或者使用内置的TF . keras . callbacks包括:

fit 方法的回调参数需要一个回调数组,我们将在那里传递回调。在本例中,我们将使用两种最常见的方法 LearningRateScheduler 和 EarlyStopping。

让我们像以前一样训练、评估和测试我们的模型。

结论

我希望你觉得这篇文章很有用,而且因为有猫而有点娱乐性。如果你事先知道一点机器学习,这可能是有用的,因为这篇文章完全是面向实践的,希望现在你应该出去做一些真正令人敬畏的项目。

下面是整个 代码 的链接:

如果你对这篇文章有任何疑问,或者如果我犯了什么错误,欢迎在评论中发表。这里有一个 chonky 猫阅读整篇文章。

你可以在推特这里找到我。
如果你更喜欢 LinkedIn,请点击这里找到我。或者如果 Github 是你的东西,我也在那里。

如果你对机器学习、Tensorflow 有任何问题,请随时联系我,或者如果这是你的事情,就用 cat gif 来联系我。我不是专家,但我会尽力帮助你。

❤再见

实用数字——通过函数理解 Python 库

原文:https://towardsdatascience.com/practical-numpy-understanding-python-library-through-its-functions-adf2e3841894?source=collection_archive---------8-----------------------

Photo by Mika Baumeister on Unsplash

在开始数据科学和机器学习的旅程之前,了解一些在数据科学领域无处不在的 python 库是非常重要的,比如 Numpy、Pandas 和 Matplotlib。Numpy 就是这样一个用于数组处理的强大的库,以及一个大型的高级数学函数集合来操作这些数组。这些函数分为线性代数、三角学、统计学、矩阵操作等类别。今天我们将看到几个如此重要的函数的例子。

得到 NumPy

要在本地机器上安装 NumPy,我建议从这里下载 anaconda 包发行版,它安装 python 和其他重要的 python 库,包括 NumPy、Pandas 和 Matplotlib,对机器学习很有用。Anaconda 支持 Windows、Mac 和 Linux。要快速开始使用 NumPy 而不在本地机器上安装任何东西,请查看 Google Colab 。它免费提供与你的 Google Drive 账户相关联的云端 Jupyter 笔记本,并且预装了所有重要的软件包。您也可以在 GPU 上运行您的代码,这有助于加快计算速度,尽管我们在本教程中不需要 GPU 计算。要快速开始使用 Google Colab,请查看同一网站上这篇令人惊叹的文章

熟悉基础知识

N umPy 的主要对象是一个同质多维数组。与 python 的 array 类只处理一维数组不同,NumPy 的ndarray 类可以处理多维数组,并提供更多功能。NumPy 的维度被称为轴。例如,下面的数组有 2 个维度或 2 个轴,即行和列。有时,维数也称为特定数组或矩阵的秩。

[[1, 4, 7],
 [2, 5, 8],
 [3, 6, 9]]

导入数字

使用以下命令导入 umPy。注意这里的np 是别名遵循的约定,这样我们就不需要每次都写numpy

import numpy as np

创建数组

用 NumPy 创建数组有很多方法,但最常用的方法是使用array 函数。一旦创建了一个数组,我们也可以使用ndim 方法检查它的尺寸。

#creating a one-dimensional array
a = np.array([1 ,2 ,3])
print (a)#ouptput
[1 2 3]a.ndim#output
1#creating a two-dimensional array
b = np.array([
             [1, 5 , 7], [2, 4, 6]
             ])
print (b)#output
[[1 5 7]
 [2 4 6]]b.ndim#output
2#creating a three-dimensional array
c = np.array([
             [[1,2,3], [3,4,5]], 
             [[5,6,7], [7,8,9]]
             ])
print (c)#output
[[[1,2,3]
 [3,4,5]] [[[5,6,7]
   [7,8,9]]]c.ndim#output
3

您还可以在创建数组时使用参数dtype 指定数组的数据类型,并使用它来检查数组的数据类型。

d = np.array([1 , 4 , 7], dtype=float)
print (d)#output
[1\. 4\. 7.]a.dtype#output
dtype('int64')

创建数组的一些特殊方法

umPy 提供了多种创建数组的方法。有像zerosones这样的特殊函数,它们分别创建只包含 0 和 1 的元素数组。您还可以将数组的长度(在一维情况下)或形状(在多维情况下)指定为参数。可以使用arange方法创建在给定间隔内具有均匀间隔值的数组。默认情况下,值之间的间距假定为 1 个单位,但是我们可以在参数中指定值之间的间距以及间隔的起始值和结束值。注意arange方法并不打印作为参数指定的间隔的最后一个值。

zeros_array = np.zeros(3, dtype=int)
print (zeros_array)#output
[0 0 0]zeros_array_nd = np.zeros((3,2), dtype=int)
print (zeros_array_nd)#output
[[0 0]
 [0 0]
 [0 0]]ones_array = np.ones(4, dtype=int)
print (ones_array)#output
[1 1 1 1]ones_array_nd = np.ones((2,3), dtype=int)
print (ones_array_nd)#output
[[1 1 1]
 [1 1 1]]range_array = np.arange(2,5)
print (range_array)#output
[2 3 4]range_array_space = np.arange(1,7,2)
print (range_array_space)#output
[1 3 5]

数组的形状

数组的 shape 属性返回一个描述其维度的元组。参考上面的例子,我们创建了数组 b ,这是一个二维数组,因此我们得到它的形状为(2,3 ),这意味着它有 2 行 3 列。这里注意数组 c 的 shape 属性返回(2,2,3),这是因为数组 c 是一个三维数组,它表示有两个各有 2 行 3 列的数组。NumPy 还提供了reshape方法来调整数组的大小。

b.shape#output
(2, 3)B = b.reshape(3,2)
print (B)#output 
[[1 5]
 [7 2]
 [4 6]]c.shape#output
(2, 2, 3)C = c.resize(2,3,2)
print (C)#output
[[[1 2]
  [3 3]
  [4 5]] [[5 6]
  [7 8]
  [8 9]]]

数组的索引

可以使用标准 python 语法x[obj]索引 umPy 数组,其中 x 是数组,obj 是选择。在 NumPy 数组中,就像 python 一样,所有的索引都是从零开始的。NumPy 中的切片与 Python 中的类似。当obj是由方括号内的start:stop:step符号构造的切片对象时,就会发生基本切片。切片期间,并不总是需要所有三个startstopstep都出现在方括号内。如果切片时出现负整数 j,则索引将被视为 n+j,其中 n 是数组中元素的数量。在多维数组的情况下,切片是以在方括号内传递元组的形式完成的,具有相同的符号约定。在高级索引中,以列表形式传递的第一个参数只是我们想要选择的特定行,第二个参数的列表指示我们想要从该行中选择的特定元素。

#Basic Slicing
x = np.array([9,8,7,6,5,4,3,2,1,0])
print(x[2:5])                      #output: [7 6 5]
print(x[5:])                       #output: [4 3 2 1 0]
print(x[:4])                       #output: [9 8 7 6]
print(x[1:7:3])                    #output: [8 5]
print(x[-5:10])                    #output: [4 3 2 1 0]#Boolean Indexing
print(x[x>4])                      #output: [9 8 7 6 5]#Indexing in multidimensional array
y = np.array([
             [1, 3],
             [4, 6],
             [7, 9]])#Advanced Indexing 
print(y[:2,1:2])                   #output: [[3]
                                             [6]]print(y[[0,1,2], [1,0,1]])         #output: [3, 4, 9]

向量、矩阵及其基本运算

在线性代数和机器学习领域,一维数组称为向量,二维数组称为矩阵。一个更高级的或 n 维的数组称为 n 维张量。作为各种机器学习和深度学习模型的输入的数据仅仅是矩阵和张量的形式,因此学习矩阵运算变得非常重要。

矩阵的转置

E 在本文的前面,我们看到了二维数组的形状和整形的概念,其中shape方法返回一个描述矩阵的行数和列数的元组。矩阵的转置是一个新的矩阵,它的行是原矩阵的列。这使得新矩阵的列成为原始矩阵的行。这是一个矩阵及其转置。让我们继续以矩阵“b”为例。

print (b)#output
[[1 5 7]
 [2 4 6]]b.shape#output
(2, 3)b_transpose = b.T
print (b_transpose)#output
[[1 2]
 [5 4]
 [7 6]]b_transpose.shape#output
(3, 2)

算术运算

M 个如果形状相同,可以相加或相减。一个矩阵中的一个元素对应于另一个矩阵中相同位置的元素被加或减。

Aij+Bij =Cij

也可以将标量值添加到矩阵中,这意味着将该值添加到矩阵的每个元素中。使用*运算符或multiply方法执行元素乘法。元素乘法不同于矩阵乘法,矩阵乘法我们将在线性代数一节中看到。类似地,我们可以按元素划分两个矩阵,并找到矩阵中每个元素的平方根和指数值,如下例所示。

A = np.array([[5, 6], 
              [7, 8]])B = np.array([[4, 3],
              [2, 1]])add = A + B                #np.add(A,B) can also be used
print (add)#output
[[9 9]
 [9 9]]sub = A - B                #np.subtract(A,B)can also be used
print (sub)#output
[[1 3]
 [5 7]]add_scalar = A + 5
print (add_scalar)#output
[[10 11]
 [12 13]]multiply = A * B            #np.multiply(A,B) can also be used
print (multiply)#output
[[20 18]
  [14 8]]divide = A / B              #np.divide(A,B) can also be used
print (divide)#output
[[1.25 2]
 [3.5  8]]square_root = np.sqrt(A)
print (square_root)#output
[[2.23606798 2.44948974]
 [2.64575131 2.82842712]]exponential = np.exp(A)
print (exponential)#output
[[ 148.4131591   403.42879349]
 [1096.63315843 2980.95798704]]

广播

B roadcasting 是机器学习中的一个重要概念。广播基本上意味着将两个不同形状的矩阵相加。当两个不同形状的矩阵相加时,较小的矩阵通过自身延伸呈现较大矩阵的形状。在上面的例子中,我们向矩阵 A 添加了一个标量,我们实际上使用了广播,其中标量采用了矩阵 A 的形状。让我们看看下面的例子。这里,矩阵 X 具有(3,3)的形状,矩阵 Y 具有(3,1)的形状,但是两个矩阵相加的结果是新的(3,3)矩阵,因为矩阵 Y 将其自身扩展为(3,3)矩阵。

X = np.array([
             [1,2,3], 
             [4,5,6], 
             [7,8,9]
                   ])Y = np.array([[2],[4],[6]])matrix_broad = X + Y
print (matrix_broad)#output
[[3 4 5]
 [8 9 10]
 [13 14 15]]

线性代数函数

T 点积(也称为内积)是行和列之间乘积的总和,如下图所示。NumPy 提供了dot方法来计算两个矩阵的点积。为了计算两个矩阵的点积,第一个矩阵的列数应该等于第二个矩阵的行数。如果不遵守此规则,NumPy 将抛出一个错误,指出形状没有对齐。

Matrix multiplication or Dot product

matrix_1 = np.array([
                    [2, 3],
                    [1, 4],
                    [4, 5]])matrix_2 = np.array([
                    [2, 3, 5],
                    [1, 6, 7]])dot_product = np.dot(matrix_1, matrix_2)
print (dot_product)#output
[[7 24 31]
 [6 27 33]
 [13 42 55]]

在上面的例子中, matrix_1 的形状为(3,2),而 matrix_2 的形状为(2,3),即 matrix_1 的列数等于 matrix_2 的行数,得到的矩阵的形状为(3,3)。

矩阵的行列式、逆矩阵和范数

线性代数的函数可以在模块linalg中找到。下面列出了一些功能。

#Determinant for 2 dimensional matrix
matrix_A = np.array([
                    [1, 2],
                    [3, 4]
                          ])
det_A = np.linalg.det(matrix_A)
print (det_A)#output 
-2.0#Determinant for 3 dimensional tensor (stack of matrices)
matrix_A_3d = np.arange(1,13).reshape(3,2,2)
det_A_3d = np.linalg.det(matrix_A_3d)
print (det_A_3d)#output 
[-2\. -2\. -2.]#Inverse of 2-D Matrix and 3-D Tensor
inv_A = np.linalg.inv(matrix_A)
inv_A_3d = np.linalg.inv(matrix_A_3d)print (inv_A)
print (inv_A_3d)#output
[[-2\.   1.]  
 [1.5 -0.5]][[[-2\.   1\. ]   [ 1.5 -0.5]]   
 [[-4\.   3\. ]   [ 3.5 -2.5]]   
 [[-6\.   5\. ]   [ 5.5 -4.5]]]Norm of 2-D Matrix and 3-D Tensor
norm_A = np.linalg.norm(matrix_A)
norm_A_3d = np.linalg.norm(matrix_A_3d)print (norm_A)
print (Norm_A_3d)#output
5.47722557505
25.49509756796

统计功能

N umPy 有一套丰富的函数来执行统计操作。NumPy 的random模块的randrandn方法用于分别生成所需维数的随机值和正态分布值的矩阵和张量。当我们想要为深度学习模型的第一次正向传播生成随机权重时,这些函数就很方便了。我们还可以计算输入数据的平均值、中值和标准差,如下所示。

#Create array of desired shape with random values
random_array = np.random.rand(3,2)
print (random_array)#output
[[0.42598214 0.49227853]  
 [0.06742446 0.46793263]  
 [0.23422854 0.80702256]]#Create array with values from a normal distribution
random_normal_array = np.random.randn(3,2)
print (random_normal_array)#output
[[ 1.99670851  0.40954136]  
 [ 0.5125924  -0.04957141]  
 [ 0.33359663  0.26610965]]P = np.array([
             [10, 12, 14],
             [8, 10, 12],
             [3, 5, 7]])#Calculate mean of the whole array, columns and rows respectively
P_mean = np.mean(P)                   #output: 9.0
P_mean_column = np.mean(P, axis=0)    #output: [7\. 9\. 11.]
P_mean_row = np.mean(P, axis=1)       #output: [12\. 10\. 5.] #Calculate median of the whole array, columns and rows respectively
print(np.median(P))                   #output: 10.0
print(np.median(P, axis=0))           #output: [8\. 10\. 12.]
print(np.median(P, axis=1))           #output: [12\. 10\. 5.]#Calculate standard deviation of the whole array, columns and rows #respectively
print(np.std(P))
print(np.std(P, axis=0))
print(np.std(P, axis=1))#output
3.366501646120693 
[2.94392029 2.94392029 2.94392029] 
[1.63299316 1.63299316 1.63299316]

结论

umPy 是 Python 中科学计算的基本库,本文展示了一些最常用的函数。理解 NumPy 是机器学习和深度学习之旅的第一个重大步骤。

希望这篇文章对你有用。请发表你的看法,评论和赞赏。

数据科学家实用心理学

原文:https://towardsdatascience.com/practical-psychology-for-data-scientists-74b4b031b777?source=collection_archive---------16-----------------------

8 种常见的认知偏差及其对你的意义

Image source

你并不像你想象的那样有逻辑。我们都不是。我们每天都容易受到认知偏差的影响。如果你有幸读过丹尼尔·卡内曼的《T2 思维》、《快与慢》和《T3》,那么你对这个现实就再熟悉不过了。我们是不完美的生物,世界是一个不完美的地方

鉴于数据科学的实验性和研究导向性,我们比大多数人更容易受到这些偏见的影响。我们代表着数据和洞察力之间的媒介。我们将字符串、整型和浮点转换成可操作的建议、模型和可视化。

这个角色伴随着巨大的责任。当我们的思维被认知偏见劫持时,坏事就会发生。做出不明智的决定,传播不正确的信息。在这篇文章中,我们将通过解决常见的偏见来避免这些情况,它们可能出现在哪里,以及如何识别它们。

确认偏差

可以说是认知偏差的典型代表,确认偏差是指我们倾向于确认我们现有信念的事物。这是有充分理由的。确认偏差无处不在。我们希望自己是对的。见鬼,错了真不舒服。你真的能责怪我们的思想时不时地试图欺骗我们吗?

这对数据科学家来说尤其危险。想想你最后一次执行某种分析的时候。如果你对这个项目感兴趣,那么你可能会带着一些假设、想法或你认为会发生的有利结果进入这个项目。这就是偏见发挥作用的地方。

你更有可能把你的发现解释为支持你先前信念的证据。我们不能完全避免这一点,但是如果你意识到了这一点,那么你就可以意识到什么时候可能会出现确认偏差,并相应地调整你的想法。

建议:在进行分析之前,写下任何假设、想法和信念。之后,重新阅读这份笔记并再次检查你的见解,确保它们是正确的。

叙事谬误

我们喜欢好故事。至少提利昂是这么认为的。数据科学和分析也不例外。叙事谬误是这样一种观点,即我们想把点点滴滴连接起来编织一个故事,即使没有故事可讲。我们迫切希望现实符合我们的世界模型,但它往往不符合。

让我们考虑一个例子。你是一家消费互联网公司的数据科学家,日活跃用户数量增加了,但花在产品上的平均时间却下降了。你怎么解释这个?

有人可能会用一个类似于“我们引入了对产品参与度较低的低意向用户”的故事来报告这一点。这似乎是合理的,但我们能肯定这一点吗?没有更多的研究来证实这一说法,我们只是以一种似乎最合理的方式将这些点联系起来。这并不总是一件坏事;故事是传达真知灼见的好工具,但应该明智而有目的地使用。

建议:你看到的信号是否足够强,足以说明它不是由噪音引起的?如果是这样,你可能已经有了一个因果故事。没关系,但是花一点时间根据你现有的数据列出其他可能的原因。意识到你的故事是许多可能解释中的一种

参照效应

简而言之,锚定效应表明你判断的第一件事会影响你对接下来事情的判断。这就是为什么当你买东西的时候,你会首先得到一个更贵的产品。你固定在那个昂贵的价格点上,所以当你看到更便宜的东西时,它看起来并不那么糟糕。

当我们必须进行比较时,你可以看到这将如何影响数据科学家。如果你正在考虑一些新的独立功能的影响,你如何解释第一个功能将会影响你如何看待第二个功能。

当你比较两个组或使用事物作为基准时,记住这一点很重要。

建议:提前下锚。如果你认为任何一个特征的有效升力大约是 2%,那么在你分析之前把它写下来。这允许你在你的判断被改变后回到你的无偏基准

可用性偏差

我们的想法很大程度上是基于我们过去的经验。这不是突破性的信息,但可用性偏差使它更进一步。可用性偏见说我们不仅仅依靠我们的经验来做决定,我们也更喜欢那些更容易想到的经验。

当我们发展假设时,这是显而易见的。如果你最近读了一篇关于深度学习技术的论文,在为你的下一个模型进行头脑风暴时,你会更倾向于走这条路。如果你在一个领域做了一个重要的项目,那么你将在这个背景下解释下一个项目。

你想到的第一件事会是最容易想到的。这并不一定意味着它是对的。

建议:在做决定和头脑风暴假设时,利用你的关系网。允许他人带来不同的视角。如果他们在不同的团队,致力于不同的问题,加分。

晕轮效应

光环效应描述了一种现象,即把事物的一小部分应用于整体。当谈到第一印象时,这种认知偏见经常在社会背景下被提及。我不必告诉你握手和介绍很重要。

作为数据科学家,我们经常与利益相关者合作项目,以便推动影响。尽管我们可能会有不同的想法,但影响力的游戏是真实存在的。做好工作是一回事,但有效地让别人支持你的工作是另一回事。这是一个棘手的问题,但光环效应是一个强有力的起点。以正确的方式开始你的项目,然后观察事情的发展。

建议:大多数项目都是从某种简报或会议开始的。这段时间,你要带头。作为一个内向的人,这可能很难做到,但通过乐观的视角解释你对项目的愿景对培养热情和组织支持大有帮助向前迈进

沉没成本谬论

我个人最喜欢的一个观点是,在管理时间表和项目时,沉没成本谬误会打击我们。当你非理性地抓住已经让你付出代价的东西不放时,它就会发生。

我们经常继续做一个项目,仅仅是因为我们已经投入了时间,即使它不是我们能做的最有影响力的事情。意识到这一点并不难。如果它正在发生,你可能已经有了直觉。棘手的部分是打电话。

退出一个项目并继续前进是一颗难以下咽的药丸。虽然这份清单上的其他偏见肯定更隐蔽,但沉没成本谬误可能是最难正面应对的。

建议:尽管很难,但要关注未来的结果。过去的就过去了。这是无法改变的,也不应该左右你的决定。就目前的项目状况而言,这是实现未来成果的最佳投资时机吗?如果答案是否定的,那就继续这段旅程吧。

知识的诅咒

交流并不容易。当知识的诅咒控制了我们,这就更不容易了。知识的诅咒是当你知道一些事情,并认为它对每个人都是显而易见的。对于数据科学家来说,这可能是一个严重的致命弱点。

我们天生就是技术型的。我们做了大量的分析。我们挖掘了许多假设。慢慢地,我们用数据建立起一幅幕后真实情况的画面。

当我们分享这些见解时,往往会出错。我们不记得其他人没有像我们一样尽职尽责。他们对这个问题的理解还不一致。他们还没有探究所有的假设。因此,当我们交流结果时,结果会显得难以解释或过于复杂。

建议:设身处地为你的利益相关者着想。对于对这个问题没有深入理解的人来说,这有意义吗?我的演示文稿或文档是否过于复杂?尽可能清晰简洁地交流。首先关注可行的见解,然后如果需要的话再深入

信息偏差

作为数据科学家,我们渴望信息。这是我们工作的核心。我们对细节的关注是我们最大的优点之一,也是最大的缺陷。如果你不熟悉,信息偏见是指在不影响行动的情况下继续寻找信息的倾向。

分析瘫痪是一种无法避免的瘟疫。在某种程度上,尽管探索很重要,但深入研究问题的收益减少不会直接影响项目的更大成果。在这种情况下,你的时间最好花在别的地方。

建议:从最小可行性分析开始。尽可能少地获取信息和理解的基线。向你的利益相关者展示并获得他们的反馈。令人惊讶的是,通常情况下,这就足够了。

摘要

意识到我们并不像自己想象的那样能够控制自己的决定和思维,这既令人不安又令人着迷。随着世界上可访问信息的深度,清晰和相对无偏见的思维已经成为一个经常被忽视的超级大国。

在这篇文章中,我列出了一些我认为有用的实践。这些可以大致分为以下几点:

  • 事先把事情写下来
  • 与不同观点的人交谈
  • 问自己一些尖锐的问题
  • 设身处地为利益相关者着想

这四样东西会带你走很长的路。事先把事情写下来会让你保持诚实。与他人交谈将帮助你检查你的认知盲点。难题会让你正面面对艰难的决定。对利益相关者的同情会帮助你产生更有意义的工作。

认知偏差是不可避免的,但这并不意味着我们必须在他们掌控方向盘时无所事事。有了正确的系统,我们可以采取措施驯服它们。勇往直前去征服。

感谢阅读!请随意查看下面我的一些类似文章,并订阅我的时事通讯中的以获得有趣的链接和新内容。

你可以在 Medium 上关注我更多类似的帖子,也可以在 Twitter 上找到我。想了解更多关于我和我在做什么,请查看我的网站。

用 Python 和 Plotly 实现实用统计和可视化

原文:https://towardsdatascience.com/practical-statistics-visualization-with-python-plotly-770e96e35067?source=collection_archive---------3-----------------------

Photo credit: Pixabay

如何使用 Python 和 Plotly 进行统计可视化、推理和建模

上周有一天,我在用 Python 谷歌“ 统计”,结果有些毫无收获。大部分文献、教程、文章都以 R 为统计重点,因为 R 是一种专门用于统计的语言,比 Python 有更多的统计分析特性。

在两本优秀的统计学书籍中,《面向数据科学家的 实用 统计学》、《统计学习入门》中,统计概念均在 R 中实现。**

数据科学融合了多个学科,包括统计学、计算机科学、信息技术和特定领域。我们每天使用强大的开源Python工具来操作、分析和可视化数据集。

我当然会建议任何对成为数据科学家或机器学习工程师感兴趣的人深入理解并不断实践统计学习理论。

这促使我为这个主题写一篇文章。我将使用一个数据集来回顾尽可能多的统计概念,让我们开始吧!

数据

数据是房价数据集,可以在这里找到。

*import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from plotly.offline import init_notebook_mode, iplot
import plotly.figure_factory as ff
import cufflinks
cufflinks.go_offline()
cufflinks.set_config_file(world_readable=True, theme='pearl')
import plotly.graph_objs as go
import plotly.plotly as py
import plotly
from plotly import tools
plotly.tools.set_credentials_file(username='XXX', api_key='XXX')
init_notebook_mode(connected=True)
pd.set_option('display.max_columns', 100)df = pd.read_csv('house_train.csv')
df.drop('Id', axis=1, inplace=True)
df.head()*

Table 1

单变量数据分析

单变量分析也许是最简单的统计分析形式,关键事实是只涉及一个变量

描述数据

数字数据的统计摘要包括数据的平均值、最小值和最大值,对于了解一些变量有多大以及哪些变量可能是最重要的非常有用。

*df.describe().T*

Table 2

分类或字符串变量的统计摘要将显示“计数”、“唯一”、“前几名”和“频率”。

*table_cat = ff.create_table(df.describe(include=['O']).T, index=True, index_title='Categorical columns')
iplot(table_cat)*

Table 3

柱状图

绘制数据中所有房屋销售价格的直方图。

*df['SalePrice'].iplot(
    kind='hist',
    bins=100,
    xTitle='price',
    linecolor='black',
    yTitle='count',
    title='Histogram of Sale Price')*

Figure 1

箱线图

绘制数据中所有房屋销售价格的箱线图。箱线图没有显示分布的形状,但它们可以让我们更好地了解分布的中心和分布,以及可能存在的任何潜在异常值。箱线图和直方图通常相互补充,帮助我们了解更多的数据。

*df['SalePrice'].iplot(kind='box', title='Box plot of SalePrice')*

Figure 2

按组划分的直方图和箱线图

通过分组绘图,我们可以看到一个变量如何响应另一个变量而变化。例如,如果有或没有中央空调的房屋销售价格不同。或者房屋销售价格是否根据车库的大小而变化,等等。

按有无空调分组的房屋销售价格箱线图和直方图

boxplot.aircon.py

Figure 3

histogram_aircon.py

Figure 4

*df.groupby('CentralAir')['SalePrice'].describe()*

Table 4

显然,没有空调的房子的平均和中间销售价格比有空调的房子低得多。

按车库大小分组的房屋销售价格箱线图和直方图

boxplot_garage.py

Figure 5

车库越大,房子的中间价格越高,这种情况一直持续到我们有 3 辆车的车库。显然,有 3 个车库的房子的中间价格最高,甚至高于有 4 个车库的房子。

无车库房屋销售价格柱状图

*df.loc[df['GarageCars'] == 0]['SalePrice'].iplot(
    kind='hist',
    bins=50,
    xTitle='price',
    linecolor='black',
    yTitle='count',
    title='Histogram of Sale Price of houses with no garage')*

Figure 6

单车库房屋销售价格柱状图

*df.loc[df['GarageCars'] == 1]['SalePrice'].iplot(
    kind='hist',
    bins=50,
    xTitle='price',
    linecolor='black',
    yTitle='count',
    title='Histogram of Sale Price of houses with 1-car garage')*

Figure 7

带两个车库的房屋销售价格直方图

*df.loc[df['GarageCars'] == 2]['SalePrice'].iplot(
    kind='hist',
    bins=100,
    xTitle='price',
    linecolor='black',
    yTitle='count',
    title='Histogram of Sale Price of houses with 2-car garage')*

Figure 8

带 3 个车库的房屋销售价格直方图

*df.loc[df['GarageCars'] == 3]['SalePrice'].iplot(
    kind='hist',
    bins=50,
    xTitle='price',
    linecolor='black',
    yTitle='count',
    title='Histogram of Sale Price of houses with 3-car garage')*

Figure 9

带 4 个车库的房屋销售价格直方图

*df.loc[df['GarageCars'] == 4]['SalePrice'].iplot(
    kind='hist',
    bins=10,
    xTitle='price',
    linecolor='black',
    yTitle='count',
    title='Histogram of Sale Price of houses with 4-car garage')*

Figure 10

频率表

频率告诉我们事情发生的频率。频率表给了我们数据的快照,让我们能够找到模式。

总体质量频率表

*x = df.OverallQual.value_counts()
x/x.sum()*

Table 5

车库大小频率表

*x = df.GarageCars.value_counts()
x/x.sum()*

Table 6

中央空调频率表

*x = df.CentralAir.value_counts()
x/x.sum()*

Table 7

数字摘要

获得定量变量的一组数字汇总的快速方法是使用 describe 方法。

*df.SalePrice.describe()*

Table 8

我们还可以计算 SalePrice 的个别汇总统计数据。

*print("The mean of sale price, - Pandas method: ", df.SalePrice.mean())
print("The mean of sale price, - Numpy function: ", np.mean(df.SalePrice))
print("The median sale price: ", df.SalePrice.median())
print("50th percentile, same as the median: ", np.percentile(df.SalePrice, 50))
print("75th percentile: ", np.percentile(df.SalePrice, 75))
print("Pandas method for quantiles, equivalent to 75th percentile: ", df.SalePrice.quantile(0.75))*

计算售价在第 25 百分位(129975)和第 75 百分位(214000)之间的房屋比例。

*print('The proportion of the houses with prices between 25th percentile and 75th percentile: ', np.mean((df.SalePrice >= 129975) & (df.SalePrice <= 214000)))*

计算地下室面积总平方英尺在第 25 百分位(795.75)和第 75 百分位(1298.25)之间的房屋比例。

*print('The proportion of house with total square feet of basement area between 25th percentile and 75th percentile: ', np.mean((df.TotalBsmtSF >= 795.75) & (df.TotalBsmtSF <= 1298.25)))*

最后,我们计算每种情况下的房屋比例。由于有些房子同时符合这两个标准,所以下面的比例小于上面计算的两个比例之和。

*a = (df.SalePrice >= 129975) & (df.SalePrice <= 214000)
b = (df.TotalBsmtSF >= 795.75) & (df.TotalBsmtSF <= 1298.25)
print(np.mean(a | b))*

计算没有空调的房子在 IQR 的销售价格。

*q75, q25 = np.percentile(df.loc[df['CentralAir']=='N']['SalePrice'], [75,25])
iqr = q75 - q25
print('Sale price IQR for houses with no air conditioning: ', iqr)*

计算带空调房屋 IQR 销售价格。

*q75, q25 = np.percentile(df.loc[df['CentralAir']=='Y']['SalePrice'], [75,25])
iqr = q75 - q25
print('Sale price IQR for houses with air conditioning: ', iqr)*

分层

从数据集中获取更多信息的另一种方法是将其分成更小、更统一的子集,并单独分析这些“层”。我们将创建一个新的 HouseAge 列,然后将数据划分到 HouseAge 层,并在每个层内构建并排的销售价格箱线图。

*df['HouseAge'] = 2019 - df['YearBuilt']
df["AgeGrp"] = pd.cut(df.HouseAge, [9, 20, 40, 60, 80, 100, 147]) # Create age strata based on these cut points
plt.figure(figsize=(12, 5)) 
sns.boxplot(x="AgeGrp", y="SalePrice", data=df);*

Figure 11

房子越老,价格中位数越低,也就是房价随着年龄的增长趋于降低,直到 100 岁。超过 100 年的房屋的中间价格高于 80 至 100 年的房屋的中间价格。

*plt.figure(figsize=(12, 5))
sns.boxplot(x="AgeGrp", y="SalePrice", hue="CentralAir", data=df)
plt.show();*

Figure 12

我们之前已经知道,有空调和没有空调的房价会有所不同。从上图中,我们还发现最近的房子(9-40 年)都装有空调。

*plt.figure(figsize=(12, 5))
sns.boxplot(x="CentralAir", y="SalePrice", hue="AgeGrp", data=df)
plt.show();*

Figure 13

我们现在先按空调分组,然后在空调内按年龄段分组。每种方法突出了数据的不同方面。

我们还可以通过房屋年龄和空调来共同分层,以探索建筑类型如何同时受这两个因素的影响而变化。

*df1 = df.groupby(["AgeGrp", "CentralAir"])["BldgType"]
df1 = df1.value_counts()
df1 = df1.unstack()
df1 = df1.apply(lambda x: x/x.sum(), axis=1)
print(df1.to_string(float_format="%.3f"))*

Table 9

对于所有住宅年龄组,数据中绝大多数住宅类型为 1Fam。房子越老,越有可能没有空调。但是,对于 100 年以上的 1Fam 房子来说,有空调的可能性比没有空调的可能性大一点。既没有很新也没有很旧的复式房屋类型。对于一个 40-60 岁的复式房子,它更有可能没有空调。

多变量分析

多元分析基于多元统计的统计原理,涉及一次对多个统计结果变量的观察和分析

散点图

散点图是一种非常常见且易于理解的定量双变量数据可视化。下面我们做一个销售价格与地面居住面积平方英尺的散点图。这显然是一种线性关系。

*df.iplot(
    x='GrLivArea',
    y='SalePrice',
    xTitle='Above ground living area square feet',
    yTitle='Sale price',
    mode='markers',
    title='Sale Price vs Above ground living area square feet')*

Figure 14

2D 密度联合图

以下两个地块边界分别显示了销售价格和地上居住面积的密度,而中间的地块共同显示了它们的密度。

price_GrLivArea.py

Figure 15

异质性和分层

我们继续探索销售价格和 GrLivArea 之间的关系,按建筑类型分层。

stratify.py

Figure 16

在几乎所有的建筑类型中,SalePrice 和 GrLivArea 呈现出正的线性关系。在下面的结果中,我们看到 1Fam 建筑类型的 SalepPrice 和 GrLivArea 之间的相关性最高,为 0.74,而复式建筑类型的相关性最低,为 0.49。

*print(df.loc[df.BldgType=="1Fam", ["GrLivArea", "SalePrice"]].corr())
print(df.loc[df.BldgType=="TwnhsE", ["GrLivArea", "SalePrice"]].corr())
print(df.loc[df.BldgType=='Duplex', ["GrLivArea", "SalePrice"]].corr())
print(df.loc[df.BldgType=="Twnhs", ["GrLivArea", "SalePrice"]].corr())
print(df.loc[df.BldgType=="2fmCon", ["GrLivArea", "SalePrice"]].corr())*

Table 10

分类双变量分析

我们创建一个列联表,计算由建筑类型和一般分区分类组合定义的每个单元中的房屋数量。

*x = pd.crosstab(df.MSZoning, df.BldgType)
x*

Table 11

下面我们在行内标准化。这就给出了每个分区分类中属于每个建筑类型变量的房屋比例。

*x.apply(lambda z: z/z.sum(), axis=1)*

Table 12

我们也可以在列内标准化。这给了我们属于每个分区分类的每种建筑类型中的房屋比例。

*x.apply(lambda z: z/z.sum(), axis=0)*

Table 13

更进一步,我们将针对空调和建筑类型变量的每个组合,查看每个分区类别中房屋的比例。

*df.groupby(["CentralAir", "BldgType", "MSZoning"]).size().unstack().fillna(0).apply(lambda x: x/x.sum(), axis=1)*

Table 14

数据中比例最高的房屋是分区 RL、带空调和 1Fam 建筑类型的房屋。由于没有空调,最高比例的房屋是分区 RL 和复式建筑类型的房屋。

混合分类和定量数据

为了更有趣,我们将绘制一个小提琴图来显示每个建筑类型类别中房屋的销售价格分布。

price_violin_plot.py

Figure 17

我们可以看到,1Fam 建筑类型的销售价格分布略微右偏,而对于其他建筑类型,销售价格分布接近正态分布。

这个帖子的 Jupyter 笔记本可以在 Github 上找到,还有一个 nbviewer 版本

参考:

* [## 使用 Python | Coursera 进行统计

这种专业化的目的是教学习者开始和中级概念的统计分析使用?…

www.coursera.or](https://www.coursera.org/specializations/statistics-with-python?)*

成功描述客户群的实用步骤和注意事项

原文:https://towardsdatascience.com/practical-steps-and-considerations-to-successfully-profile-your-customer-base-19f3f0991407?source=collection_archive---------10-----------------------

如何使用特征丰富的数据集,通过 K 均值聚类、主成分分析和 Bootstrap 聚类评估进行有效的统计分割

Photo by Toa Heftiba on Unsplash

概观

统计细分是我最喜欢的分析方法之一:我从自己的咨询经验中发现,它能很好地引起客户的共鸣,并且是一个相对简单的概念,可以向非技术受众解释。

今年早些时候,我使用了流行的 K-Means 聚类算法,根据客户对一系列营销活动的反应来划分客户。为了进行分析,我特意选择了一个基本数据集,以表明这不仅是一个相对容易进行的分析,而且有助于挖掘客户群中有趣的行为模式,即使使用很少的客户属性。

在这篇文章中,我使用一个复杂且功能丰富的数据集重新审视了客户细分,以展示在更现实的环境中运行这种类型的分析时,您需要采取的实际步骤和可能面临的典型决策。

注意为了简洁起见,我只包括了构成故事流程的一部分的代码,所以所有的图表都是“只有图片和数字”。你可以在我的网站 找到完整的客户档案分析

商业目标

选择合适的方法取决于你想要回答的问题的性质和你的企业所处的行业类型。在这篇文章中,我假设我正在与一个客户一起工作,这个客户希望更好地了解他们的客户基础,特别强调每个客户对企业底线的货币价值

一种非常适合这种分析的方法是流行的 RFM 细分,它考虑了 3 个主要属性:

  • Recency客户最近购买了什么?
  • Frequency他们多久购买一次?
  • Monetary Value他们花了多少钱?

这是一种受欢迎的方法,理由很充分:实现很容易(你只需要一个随时间变化的客户订单的交易数据库),并且基于每个客户贡献了多少显式地创建子组。

加载库

library(tidyverse)
library(lubridate)
library(readr)
library(skimr)
library(broom)
library(scales)
library(ggrepel)
library(fpc)

数据

我在这里使用的数据集附带了一个红皮书出版物,可以在附加资料部分免费下载。这些数据涵盖了样本户外公司3 & 1/2 年的销售额orders,这是一家虚构的 B2B 户外设备零售企业,并附有他们销售的products及其客户(在他们的案例中是retailers)的详细信息。

在这里,我只是加载已编译的数据集,但如果你想继续,我也写了一篇名为加载、合并和连接数据集的文章,其中我展示了我如何组装各种数据馈送,并整理出变量命名、新功能创建和一些常规内务任务等内容。

orders_tbl <- read_rds("orders_tbl.rds")

你可以在我的 Github 库上找到完整的代码。

数据探索

这是任何数据科学项目的关键阶段,因为它有助于理解数据集。在这里,你可以了解变量之间的关系,发现数据中有趣的模式,检测异常事件和异常值。这也是你制定假设的阶段,假设细分可能会发现哪些客户群。

首先,我需要创建一个分析数据集,我称之为customers_tbl ( tbl代表 tibble ,R modern 代表数据帧)。我将average order valuenumber of orders包括在内,因为我想看看 RFM 标准之外的几个变量,即近期、频率和货币价值。

customers_tbl <- 
   orders_tbl %>%  
                                 # cut-off date is 30-June-2007
   mutate(days_since = as.double( 
     ceiling(
       difftime(
          time1 = "2007-06-30",
          time2 = orders_tbl$order_date, 
          units = "days")))
          ) %>%
   filter(order_date <= "2007-06-30") %>% 
   group_by(retailer_code) %>%    # create analysis variables
   summarise(
      recency = min(days_since),  # recency
      frequency = n(),            # frequency
      avg_amount = mean(revenue), # average sales
      tot_amount = sum(revenue),  # total sales
      # number of orders
      order_count = length(unique(order_date))
      ) %>%                          
   mutate(avg_order_val =         # average order value
            tot_amount / order_count) %>% 
   ungroup()

根据经验,你最好在你的细分中包括一个良好的 2 到 3 年的交易历史(这里我用的是完整的 3 & 1/2 年)。这确保您的数据有足够的变化,以捕捉各种各样的客户类型和行为、不同的购买模式和异常值

异常值可能代表很少出现的客户,例如,在一段时间内只进行了一些零星的购买,或者只下了一两个非常大的订单就消失了。一些数据科学从业者更喜欢从细分分析中排除离群值,因为 k 均值聚类倾向于将它们放在自己的小组中,这可能没有什么描述能力。相反,我认为重要的是包括异常值,这样就可以研究它们,了解它们为什么会出现,如果这些客户再次出现,就用正确的激励措施瞄准他们(比如推荐他们可能购买的产品,多次购买折扣,或者让他们加入一个忠诚度计划)。

单变量探索

崭新

新近分布严重右偏,平均值约为 29,50%的观察值介于 9 和 15 之间。这意味着大部分顾客在过去 15 天内进行了最近一次购买。

**summary**(customers_tbl$frequency)##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##     2.0   245.0   565.5   803.1   886.2  8513.0

通常情况下,我希望订单在时间上分布得更均匀一些,尾部的第一部分不会有太多缺口。集中在过去 2 周活动中的大量销售告诉我,订单是“手动”添加到数据集的,以模拟订单激增。

频率

分布是右偏的,大多数客户购买了 250 次到不到 900 次,平均值被右偏拉到中值以上。

**summary**(customers_tbl$frequency)##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##     2.0   245.0   565.5   803.1   886.2  8513.0

在每个客户购买 4000 次以上时,可以发现少量异常值,其中一个极值点超过 8500 次。

总销售额和平均销售额

total salesaverage sales都是右偏的,总销售额在 5000 万美元和 7500 万美元标记处显示出一些极端的异常值,无论平均销售额是否有更连续的尾部。

它们都可以很好地捕捉细分的货币价值维度,但我个人更喜欢average sales,因为它缓和了极端值的影响。

订单数量

orders per customer的数量在左手边显示出一个双模态的暗示,一个峰值在 30 左右,另一个峰值在 90 左右。这表明了数据中不同亚组的潜力。

这种分布也有右偏,在截至 2007 年 6 月 30 日的 3 年中,大多数零售商的订单数量在 37 到 100 之间。有少量异常值,一个极端的例子是一家零售商在 3 年内下了 349 份订单

**summary**(customers_tbl$order_count)##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    1.00   37.00   72.00   78.64  103.00  349.00

平均订单价值

average order value刚刚超过 10.5 万美元,50%的订单价值在 6.5 万美元到 13 万美元之间,少数异常值超过 30 万美元。

**summary**(customers_tbl$avg_order_val)##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   20238   65758   90367  105978  129401  661734

我们还发现了少量超出每个订单 30 万美元的异常值。

多变量探索

将 2 或 3 个变量绘制在一起是理解它们之间存在的关系的一个很好的方法,并且可以感觉到你可能会找到多少个聚类。绘制尽可能多的组合总是好的,但这里我只展示最显著的组合。

让我们画出 RFM 三重奏(recencyfrequencyaverage sales)并使用频率对这些点进行颜色编码。

该图表不太容易阅读,因为大多数数据点聚集在左侧,鉴于我们在前一节中发现recency严重向右倾斜,这并不奇怪。您还可以注意到,大多数点都是淡蓝色,表示购买频率较低。

为了使图表更具可读性,通常方便的做法是用正偏斜对变量进行对数变换,以将观察值分布到整个绘图区。

即使是对数刻度对图表的可读性也没有太大帮助。鉴于recency中发现的极端右偏,我预计聚类算法可能会发现很难识别定义良好的组。

分析

为了描述客户,我使用了 K 均值聚类技术:它可以很好地处理大型数据集,并快速迭代到稳定的解决方案。

首先,我需要调整变量的比例,以便它们大小的相对差异不会影响计算。

clust_data <- 
   customers_tbl %>% 
   **select**(-retailer_code) %>% 
   **scale**() %>% 
   **as_tibble**()

然后,我构建一个函数来计算任意数量的中心的kmeans,并创建一个嵌套的 tibble 来容纳所有的模型输出。

kmeans_map <- **function**(centers = centers) {
   **set.seed**(1975)                   *# for reproducibility*
   clust_data[,1:3] %>%  
      **kmeans**(centers = centers, 
             nstart = 100, 
             iter.max = 50)
}kmeans_map_tbl <-                *# Create a nested tibble*
   **tibble**(centers = 1:10) %>%    *# create column with centres* 
   **mutate**(k_means = centers %>% 
          **map**(kmeans_map)) %>%   *# iterate `kmeans_map` row-wise* 
   **mutate**(glance = k_means %>%   *# apply `glance()` row-wise* **map**(glance))

最后,我可以构建一个scree plot并寻找“肘”,在这个点上,添加额外集群的增益tot.withinss开始变得平稳。看起来最佳的集群数是 4

评估集群

尽管该算法在数据中捕获了一些不同的组,但是在分类 1 和 2 以及分类 3 和 4 之间也有一些明显的重叠。

此外,集群之间的平衡不是特别好,第 4 组包含了所有retailers的近 80%,这对于分析您的客户来说用处有限。第 1 组、第 3 组和第 4 组具有非常相似的recency值,第 2 组捕获了一些(但不是全部)“最近”的买家。

rfm 4-cluster table

替代分析

正如预期的那样,该算法正在努力寻找基于recency的定义明确的群体。我对基于 RFM 的分析不是特别满意,我认为考虑不同的特征组合是明智的。

我已经研究了几个备选方案(为了简洁起见,这里没有包括),发现每个客户的average order valueorders per customeraverage sales 是有希望的候选方案。绘制它们揭示了足够好的特征分离,这是令人鼓舞的。

让我们用新的变量再进行一次客户细分。

*# function for a set number of centers*
kmeans_map_alt <- **function**(centers = centers) {
   **set.seed**(1975)                 *# for reproducibility*
   clust_data[,4:6] %>%           *# select relevant features*
      **kmeans**(centers = centers, 
             nstart = 100, 
             iter.max = 50)
}*# create nested tibble*
kmeans_map_tbl_alt <- 
   **tibble**(centers = 1:10) %>%     *# create column with centres* 
   **mutate**(k_means = centers %>% 
       **map**(kmeans_map_alt)) %>%   *# iterate row-wise* 
   **mutate**(glance = k_means %>%    *# apply `glance()` row-wise*
       **map**(glance)) 

同样,聚类的最佳数量应该是 4 ,但是斜率变为 5 的变化没有我们之前看到的那么明显,这可能意味着有意义的组的数量可能会更高。

评估集群

虽然仍然存在,但集群重叠不太明显,群体分离更加明显。

集群被更好地定义,不再像以前那样由一个群体主宰。虽然没有在模型中使用,但我已经在表格中添加了recency,表明即使是以前的“问题儿童”现在在各个组中也更加平衡了。

当我增加集群的数量时,组分离仍然很整齐,一些明显的重叠仅在 7 集群配置中再次出现。

主成分分析

绘制变量组合图是一个很好的探索性练习,但本质上是任意的,可能会导致错误和遗漏,尤其是当您需要考虑的变量不止一个时。

幸运的是,我们可以使用降维算法,如主成分分析,简称 PCA,来可视化客户群。

PCA 的一个主要优点是每个 PCs 都与最大化数据线性方差的方向正交。这意味着前几个 PC 可以捕获数据中的大部分方差,并且是比上面图的变量比较更可靠的聚类的二维可视化。

为了执行主成分分析,我使用了基数 r 的prcomp函数。

非常重要:不要忘记缩放和居中您的数据!出于某种原因,这不是默认的!

pca_obj <- 
   customers_tbl[,5:7] %>% 
   **prcomp**(center = TRUE, 
          scale. = TRUE)**summary**(pca_obj)
*## Importance of components:*
*##                          PC1   PC2  PC3*
*## Standard deviation     1.422 0.942 0.30*
*## Proportion of Variance 0.674 0.296 0.03*
*## Cumulative Proportion  0.674 0.970 1.00*

最好看一下每台电脑解释的差异。我需要的信息是方差比例

前两个组成部分解释了数据中 97%的变化,这意味着使用前两个 PC 将使我们对数据有很好的理解,随后的每个 PC 将增加很少的信息。当您有大量变量要在聚类中运行时,这显然更有意义。

4 个聚类的 PCA 可视化

首先,我从pca_obj中提取 PCA,并将元素x中包含的 PCs 坐标与原始customer_tbl集中的retailer信息连接起来。

pca_tbl <- 
   pca_obj$x %>%                 *# extract "x", which contains the PCs co-ordinates*
   **as_tibble**() %>%               *# change to a tibble*
   **bind_cols**(customers_tbl %>%   *# append retailer_code*
             **select**(retailer_code))

然后,我从kmeans_map_tbl_alt开始pluck第 4 个元素,将集群信息附加到它上面,并通过零售商代码连接left_join,这样我在一个 tibble 中就有了我需要的所有信息,为绘图做好准备。

km_pca_4_tbl <- 
   kmeans_map_tbl_alt %>% 
   **pull**(k_means) %>%
   **pluck**(4) %>%                  *# pluck element 4* 
   **augment**(customers_tbl) %>%    *# attach .cluster to the tibble* 
   **left_join**(pca_tbl,            *# left_join by retailer_code* 
             by = 'retailer_code')

该图证实了在 4 集群配置中,各个段被很好地分开。分段 1 和分段 3 在不同方向上显示出显著的可变性,并且分段 2 和分段 4 之间存在一定程度的重叠。

第 1 组包括下了少量高额订单的客户。尽管他们只占总销售额的 6%,鼓励他们下稍微多一点的订单也能大大增加你的底线。

第 2 组是“订单价值低”/“订单数量低”部分。然而,由于它几乎占客户群的 40%,我会鼓励他们增加订单价值或订单数量。

第 3 组相对较小(占总retailers的 11%),但已经下了非常多的中高价值订单。这些是你最有价值的客户,占总销售额的近 40%。我想让他们非常开心和投入。

第四组好机会可能出现的地方!就零售商数量(45%)和对总销售额的贡献(44%)而言,这是最大的群体。我会试着激励他们转移到第 1 组或第 3 组

6 个聚类的 PCA 可视化

重要提示:集群编号是随机生成的,因此群组名称与上一节中的名称不匹配。

现在,让我们看看添加额外的集群是否揭示了一些隐藏的动态,并帮助我们微调这个分析练习。这里我只展示 6 集群配置,这是最有前途的。

6-片段设置广泛地证实了在 4-分割解决方案中发现的组结构和分离,显示了良好的簇稳定性。之前的细分市场 1 和 3 进一步分裂,形成 2 个“中端”组,每个组都从之前的细分市场 2 和 4“借用”。

新的【中端】群体有其独特的特点:

  • 新组 1 的客户正在下中高价值高订单数量,并贡献了总销售额的约 18%。
  • 测试的策略:由于他们已经频繁下订单,我们可能会向他们提供激励措施以增加他们的订单价值
  • 另一方面,新的第 3 组客户购买的频率降低,具有类似的中高订单价值,约占总客户的 16%。
  • 测试的策略:在这种情况下,激励可以集中在提高订单数量

定义更好的集群代表更大的潜在机会:测试不同的策略、了解每个群体真正的共鸣以及使用正确的激励与他们联系起来变得更加容易。

集群启动评估

值得采取的最后一个步骤是通过验证它们是否捕获了数据中的非随机结构来验证您的集群有多“真实”。这对于 k 均值聚类尤其重要,因为分析师必须提前指定聚类的数量。

clusterboot 算法使用 bootstrap 重采样来评估给定集群对数据扰动的稳定程度。通过测量给定数量的重采样运行的集合之间的相似性来评估聚类的稳定性。

kmeans_boot100 <-
   **clusterboot**(
      clust_data[,4:6],
      B = 50,                    *# number of resampling runs*
      bootmethod = "boot",       *# nonparametric resampling* 
      clustermethod = kmeansCBI, *# clustering method: k-means* 
      k = 7,                     *# number of clusters* 
      seed = 1975)               *# for reproducibility*bootMean_df <-                   # saving results to a data.frame
   **data.frame**(cluster = 1:7, 
              bootMeans = kmeans_boot100$bootmean)

为了解释结果,我用一个简单的图表来可视化测试输出。

请记住:

  • 高于 0.8 (段 2、3 和 5)表示高度稳定的簇
  • 在 0.6 和 0.75 之间(段 1、4 和 6)表示可接受的稳定度
  • 低于 0.6 (段 7)的值应视为不稳定

因此,6 集群配置总体上相当稳定。

结束语

在这篇文章中,我使用了一个功能丰富的数据集来运行您需要采取的实际步骤,以及在运行客户概要分析时可能面临的考虑。我在一系列不同的客户属性上使用了 K-means 聚类技术,在客户群中寻找潜在的子群体,用主成分分析直观地检查了不同的群体,并用fpc包中的集群引导验证了集群的稳定性。

这种分析应该为与相关业务涉众的讨论提供一个坚实的基础。通常情况下,我会根据客户特征的不同组合向客户展示各种资料,并提出我自己的数据驱动型建议供讨论。然而,最终还是由他们来决定他们想要满足于多少个组,以及每个细分市场应该具有什么样的特征。

结论

统计细分非常容易实现,可以识别您的客户群中自然发生的行为模式。但是,它有一些限制,在商业环境中应该始终牢记。首先也是最重要的,它是一张及时的快照,就像一张照片一样,它只代表拍摄的那一瞬间。

因此,应该定期对进行重新评估,因为:

  • 它可以捕捉不一定适用于不同时间段的季节性影响
  • 新客户可以进入你的客户群,改变每个群体的构成
  • 客户的购买模式会随着时间的推移而演变,你的客户档案也应该如此

尽管如此,统计细分仍然是在消费者数据中寻找群体的一个强大而有用的探索性练习。我从自己的咨询经验中发现,这也能引起客户的共鸣,而且对于非技术受众来说,这是一个相对简单的概念。

代码库

完整的 R 代码可以在我的 GitHub 简介中找到

参考

原载于 2019 年 9 月 23 日https://diegousei . io

处理缺失值的实用策略

原文:https://towardsdatascience.com/practical-strategies-to-handle-missing-values-626f9c43870b?source=collection_archive---------10-----------------------

大多数数据科学项目中的主要挑战之一是找出一种获得干净数据的方法。总时间的 60%到 80%花在清理数据上,然后您才能对其进行有意义的理解。对于 BI 和预测分析项目来说都是如此。为了提高数据清理过程的有效性,当前的趋势是从手动数据清理迁移到更加智能的基于机器学习的过程。

确定我们正在处理的缺失值的类型

在我们深入研究如何处理缺失值之前,弄清楚缺失值的性质是至关重要的。根据缺少的数据与数据集中的其他数据之间是否存在关系,有三种可能的类型。

  1. 它们可能无法解释为什么一个列的数据与数据中的其他列一起丢失,并被称为完全随机丢失( MCAR )。这种情况的一个例子可能是由于某人不能赴约而导致调查数据丢失。或者管理员将他/她应该输入计算机的测试结果放错了地方。缺失值的原因与数据集中的数据无关。
  2. 有些情况下,一列中缺少的数据可以用其他列中的数据来解释。这就是所谓的随机失踪( MAR )。这方面的一个例子是,在一所学校里,超过分数线的学生通常会得到分数。这意味着如果一些学生的分数缺失,可以用分数低于分数线的那一栏来解释。缺失值的原因可以通过另一列中的数据来描述。
  3. 有些情况下,缺少的值与值本身有关。例如,高收入者可能不会公开他们的收入。缺失值与实际收入之间存在关联,并且不依赖于数据集中的其他变量。这被称为不是随机丢失( MNAR )

一般来说,删除缺失的行对 MCAR 来说是没问题的。输入数据适用于 MCAR 和马尔。如果是 MNAR,必须通过引入额外的列来建模。

处理缺失数据的策略

处理缺失值有几种策略。首先,可以使用像随机森林或 KNN 这样的算法,这些算法在处理缺失值时很健壮。第一种常见策略是删除缺少值的行。通常,任何单元格中缺少值的任何行都会被删除。有时,许多行将被删除的可能性很高。由于信息和数据的丢失,当没有足够的样本时,通常不使用这种方法。

可以用多种方式输入丢失的数据。它可能只基于缺少值的列中存在的信息。或者它也可以基于数据集中存在的其他列。最后,还可以使用分类或回归模型来预测缺失值。我们将通过下面的例子来讨论这些。

处理数字列中的缺失值

第一种方法是用下列策略之一替换丢失的值。

  1. 用常数值替换它。通常,这用于与领域专家讨论我们正在处理的数据。
  2. 用平均值或中间值代替它。这是一个不错的方法,特别是当数据很小时,但是它确实增加了偏差。
  3. 通过利用其他列中的信息,用值替换它。我们将通过下面的例子对此进行更多的讨论。

在上面的雇员数据集子集中,我们在三行中缺少薪水。我们也有他们居住的州和他们在数据集中的经验。

第一种方法是用列的平均值填充缺失值。这里,我们只使用缺少值的列中的信息。

在领域专家的帮助下,我们可以做得更好,并使用来自数据集中其他列的信息。

各州平均工资不同。用它来填写值。计算在德克萨斯州工作的人的平均工资,并用在德克萨斯州工作的人的平均工资替换缺少德克萨斯州工资的行。对其他州也这样做。

我们还能做得更好吗?除了国家专栏,我们还可以利用多年的经验。

计算在德克萨斯州工作的人员的平均入门级工资,并用德克萨斯州的平均入门级工资替换缺少德克萨斯州入门级人员工资的行。对其他州和其他级别进行同样的操作。

这为你提供了一套很好的估算数据的方法,尤其是当我们有一个领域专家来指导我们的时候。请注意,有一些边界条件需要处理。例如,对于一个居住在得克萨斯州的人,可能会有一行缺少工资和工作经验。有多种方法可以解决这个问题。一个直接的方法是用德克萨斯州的平均工资替换缺失值。

使用算法预测缺失值

另一种方法是创建一个简单的回归模型。这里要预测的列是使用数据集中其他列的薪金。如果输入列中有缺失值,我们必须在创建预测模型时处理这些情况。有许多方法可以管理这种情况,但一种简单的方法是选择没有缺失值的要素,或者在任何像元中选取没有缺失值的行。人们可以用不同的技术和不同的算法(KNN,老鼠等)进行实验。)并选择最准确的一个。这可能会在随后的帖子中涉及。

处理分类列中的缺失值

处理分类列中的缺失值比处理数字列中的缺失值要容易得多。

1.用常数值或最流行的类别替换它。这是一个很好的方法,尤其是当数据很小时,但是它确实增加了偏差。例如,假设我们有一个包含高中和大学学位值的教育专栏。如果数据集中有更多拥有大学学位的人,我们可以用大学学位替换缺失的值。

2.我们可以通过利用其他专栏中的信息对此进行更多的调整。如果数据集中有更多来自德克萨斯州的高中学历的人,我们可以替换缺失的值来反映德克萨斯州高中毕业的人。

3.也可以创建一个分类模型。我们要预测的列是教育,使用数据集中的其他列。

4.最常见和最流行的方法是将分类列中缺失的值建模为一个名为“未知”的新类别

摘要

我们讨论了处理缺失值的几种策略。根据数据的种类和问题的类型,所采用的方法会略有不同。

对于数值,最好利用数据中存在的模式。对于分类列,最好将其建模为一个新的级别。如果你有领域专家的帮助,在填写缺失值时,最好能采纳专家的建议。

需要注意的是,无论选择哪种输入方法,最好运行预测模型,从准确性的角度来看哪种方法效果最好。

训练音乐模型的实用技巧

原文:https://towardsdatascience.com/practical-tips-for-training-a-music-model-755c62560ec2?source=collection_archive---------6-----------------------

这是“构建人工智能音乐生成器”系列的第二部分。我们将更深入地构建在第一部分中介绍的音乐模型。

这里有一个简单的概述:

数据编码及如何处理:

  • 复调音乐
  • 音符音高/持续时间

培训最佳实践:

  • 数据扩充
  • 位置编码
  • 教师强迫
  • TransformerXL 架构

注意:这是一个更技术性的帖子,对于理解 第三部分 / 第四部分 并不重要。请随意跳过前面!

快速重复。

在前一篇文章中,我提到了训练音乐模型的两个基本步骤:

步骤一。将音乐文件转换成一系列标记:

第二步。建立并训练语言模型,预测下一个令牌:

这个职位将被分割在完全相同的步骤。只是这一次,我们不会掩饰细节。

第一步。将音乐转换为代币

从原始数据开始

我们将使用一个主要由 MIDI 文件组成的数据集。这是最流行的数字音乐格式之一,互联网上有大量这样的文件。更多的数据是深度学习最好的朋友。

原始 MIDI 以字节表示。即使转换成文本也不是很容易读懂。

我不会显示 MIDI,而是向您展示如下内容:

Even if you can’t read music, it makes more sense than this

标记化

事实证明,在将音乐文件编码为令牌时,需要记住几个问题。对于文本,这是一个非常简单的转换。

下面是如何将文本编码为一系列标记:

Vocabulary: { 
  'a': 1, 
  'is': 2, 
  'language': 3,
  'like': 4,
  'model': 5,
  'music': 6
}Text:      “a  music model is like a  language model”
Tokenized: [1, 6,    5,    2, 4,   1  3,       5]

这是一种直接的一对一映射——单词到单词。您可以进行其他类型的编码,如拆分缩写或字节对编码,但这仍然是一种顺序转换。

然而,音乐在 2D 最具代表性:

这是看待它的另一种方式:

这是频率随时间变化的曲线图(也称为钢琴声)。您会注意到这个图表的两点:

  1. 一个单个音符是一个值的集合(音高+持续时间)
  2. 多个音符可以在一个单个时间点演奏(复调)

用音乐训练变形金刚的诀窍是弄清楚如何将这些 2D 数据符号化为一维。

注释—一对多

  1. 一个单个音符代表一个集合的值:
    -音高(C,C #…A #,B)
    -时长(四分音符,全音符)

注:一个音符实际上有更多的属性,如乐器类型(鼓、小提琴)、力度(响度)和速度(定时)。这些对于生成流行旋律并不重要,但对于生成 演奏片段 却很有帮助。

最简单的方法是将一个单个音符编码成一个记号序列:

注意:另一种选择是 的值组合成一个单独的 token【C:QTR,D:QTR,E:HLF】,但这意味着更大的词汇量,对预测的控制更少。

复调——多对一

你怎么知道什么时候同时演奏一系列的音符,或者按顺序演奏?

另一个名为“ bachbot ”的音乐模型对此有一个巧妙的解决方案。如果音符由特殊的“ SEP ”符号分隔,则按顺序播放音符。如果没有,一次弹奏所有音符。

将所有这些放在一起

把这些放在一起,你就有了我们一开始展示的例子:

如果你喜欢 Python 胜过英语,试试这个 笔记本

第二步。培训最佳实践

现在,我们准备好对我们的标记化数据进行训练。这将是与第一部分相同的训练代码,但是增加了一些特性。

让我们一行一行地检查代码。

数据扩充

第 3 行:config['transpose _ range']=(0,12)

数据扩充是一个伟大的数据集倍增器。一首歌转化成 12 首不同调的歌!

*item.to_text() # Key of C* **Tokens:    xxbos xxpad n60 d4 n52 d8 n45 d8 xxsep d4 n62 d4***item.transpose(4).to_text() # Key of E* **Transpose: xxbos xxpad n64 d4 n56 d8 n49 d8 xxsep d4 n66 d4**

数据扩充似乎有助于概括关键音阶和节拍。然而,在训练的最后几个时期,我删除了增强,保留了 c 调中的所有内容。

无论是机器还是人类,预测和弹奏全白键都要容易得多。

位置拍编码

第 2 行:config['encode_position'] = True

位置节拍编码是我们提供给模型的额外元数据,以使它对音乐定时有更好的感觉。

正如我们在标记化步骤中看到的,将音符转换成标记并不是一对一的映射。这意味着令牌的位置与它在时间上的实际位置不一致。

*item.to_text()* **Token:  xxbos xxpad n60 d4 n52 d8 n45 d8 xxsep d4 n62 d4***list(range(len(item.data)))*
**Index:   0,    1,    2,  3, 4,  5, 6,  7, 8,    9, 10, 11***item.position//4*
**Beat:    0,    0,    1,  1, 1,  1, 1,  1, 1,    1, 2,  2**

索引 7 的代币实际上是在节拍 1 上打出的。

如果我们将“节拍”元数据和令牌一起发送给我们的模型,它将包含更多的上下文信息。它不再需要自己计算音乐的节奏。

老师逼问

第 4 行:config[' mask_steps '] = 4

当训练变形金刚时,你通常应用一个注意力面具来防止模型偷看它应该预测的下一个令牌。

*lm_mask(x_len=10, device=None)               # 10 = bptt*tensor([[[[0, 1, 1, 1, 1, 1, 1, 1, 1, 1],    # Can only see itself
          [0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
          [0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
          [0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
          [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
          [0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
          [0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
          [0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
          [0, 0, 0, 0, 0, 0, 0, 0, 0, 1],  
          [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]]]) # Can see everything**# 0 = Can see token** 
**# 1 = Can't see token**

每一行代表一个时间步长,它可以看到或看不到哪些标记。

除了屏蔽未来的标记,您还可以屏蔽之前的几个标记。这迫使模型提前预测几个步骤,并在理想情况下产生一个更一般化的模型。

*window_mask(10, None, size=(2,0))           # Window size of 2*tensor([[[[0, 1, 1, 1, 1, 1, 1, 1, 1, 1],   # Only sees itself
          [0, 1, 1, 1, 1, 1, 1, 1, 1, 1],   # Only sees the previou
          [0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
          [0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
          [0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
          [0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
          [0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
          [0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
          [0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
          [0, 0, 0, 0, 0, 0, 0, 0, 1, 1]]]])# Can't see final 2

把这个想成是一个反转的“老师逼”。

变压器架构

第 6 行:model = get _ language _ model(arch =MusicTransformerXL…)

TransformerXL 是一种特定风味的变压器型号。它具有相对位置编码和隐藏状态记忆功能。

变压器内存实现超快速推理。不必在每次预测时都重新评估整个序列,您只需要评估最后一个预测的标记。先前的令牌已经存储在存储器中

相对位置— 普通变压器仅使用绝对位置。对于音乐模型来说,知道每个记号相对于另一个的位置非常重要。这是对我们的位置节拍编码的补充

训练循环

learn.to_fp16(dynamic=True,clip = 0.5);
learn.fit_one_cycle(4)

fastai 库免费提供训练代码。我就不赘述了,但是混合精度,一个周期,多 GPU 训练会节省你很多时间。

结束了。

咻!现在你知道了我所知道的关于音乐变形金刚的一切。现在我们可以在这个概念的基础上创造一个更酷的音乐模型——多任务变压器。

第三部分。构建一个多任务音乐模型——一个增强的 MusicTransformer。它可以和声,产生旋律,并混音歌曲。**

第四部分。用一个音乐机器人重新混合烟雾弹——用音乐机器人重新混合艾伯顿的一个 EDM drop。纯娱乐目的而已。**

感谢阅读!

特别感谢耶鲁安·克斯滕杰瑞米·霍华德的指导,南方公园公地帕拉帕维克的支持。

免费在云上练习 R 和 Python

原文:https://towardsdatascience.com/practice-r-and-python-on-the-cloud-for-free-103e9d26902a?source=collection_archive---------21-----------------------

r 和 Python,数据科学的“动态二人组”,都是免费的开源编程语言。这意味着没有所谓的“供应商”,比如说,微软拥有 Excel。这使得开始使用这些程序变得有点棘手:有几种安装它们的方法,通常需要多个步骤,令人困惑,并且耗费大量资源。

对于一个全新的程序员来说,放弃那些甚至连安装都如此复杂的工具是很容易的——“如果来说很难,想象一下试图使用它们!”

幸运的是,免费的基于云的应用程序可以让你试验这些程序,不需要安装。这为您节省了磁盘空间和麻烦,并允许您深入代码——和可能性——而不是逻辑。

对于 R: RStudio 云

RStudio Cloud 来自 RStudio,它是主流的 RStudio 集成开发环境的供应商。(我在中使用 RStudio 来教授我的 R 课程。)

暗示创建一个 RStudio 帐户并开始使用。您可以创建一个新项目,并从浏览器运行 RStudio 会话。代码将在 RStudio 服务器上执行。

您的初始工作区将如下所示。这是 RStudio 界面的“虚拟”实例:

这是你第一次在 RStudio 工作,看看我下面的“RStudio 之旅”。

要继续涉猎 R,请查看我的帖子。您的 R 会话将像在您的计算机上一样运行,但这一次 RStudio 将负责软件。

对于 Python: Google 联合实验室

谷歌托管免费的协作服务,使用修改过的 Jupyter 笔记本运行 Python。Colab 的确切“外观和感觉”与使用像 PyCharm (我最喜欢的 Python 工作环境)这样的代码编辑器或者甚至是一个“普通”的 Jupyter 笔记本是不一样的,但是功能是存在的,而且你不必处理软件和软件包的维护。

要访问 Colab,请登录您的 Google 帐户,查看包含以下视频的 Google Colab starter 笔记本

谷歌 Colab 让你可以直接访问谷歌的超级计算机——你可以在这里做一些非常严肃的数据,正如来自 TensorFlow 的认可所表明的那样(这是谷歌开发人员为深度学习开发的一个受欢迎的包)。你甚至可以完全从云端执行你的 Google Drive 文件

我写的 Python 不如 r 多,如果你想对这种语言有个大概的了解,可以看看 DataCamp 的免费课程,Python 中的数据科学简介

结论:快速编码

有没有尝试过 RStudio Cloud 和 Google Colab?对你来说哪个看起来更人性化?学 R 和 Python 你更兴奋?

或者,您更喜欢使用不同的免费在线资源来练习 R 和/或 Python 吗?比如最近了解到的微软 Azure 笔记本,可以让你从 Jupyter 笔记本上免费练习 R 和 Python 两种语言。

在评论里说吧。

实践数据科学

原文:https://towardsdatascience.com/practicing-data-science-5487e9f88aad?source=collection_archive---------20-----------------------

在数据科学项目中询问方向

数据科学项目的倾斜有很多:有无标注数据;停止数据争论或涉及机器学习算法;预测类别或预测数字;不均匀分布的类,二进制类,甚至没有一个类的例子;结构化数据和非结构化数据;利用过去的样本或者仅仅停留在现在;要求实时或接近实时执行或具有可接受的较慢性能;在光鲜亮丽的报告中展示结果,或者在中立的 IT 架构背后隐藏事实;有大量预算或者根本没有预算。

CRISP-DM 循环够用吗?

The CRISP-DM cycle chart as from Wikipedia

虽然数据科学项目的一般开发是相对标准的,例如遵循 CRISP-DM 周期,但每个项目通常需要一些特殊的定制,即特殊的成分以适应特定的数据、目标、约束、领域知识,甚至项目的预算。

CRISP-DM 周期为任何数据科学项目推荐以下阶段:数据和业务理解、数据准备、模型训练、模型优化、模型评估,以及最后的模型部署。关于他们的实际实施的更多细节可以在“分析和超越!”

通过 CRISP-DM 循环提供的这些指南对于标准项目非常有用;也就是一个训练数据充足,类分布均匀,对速度或最终可视化没有特殊要求等等的项目。指南没有提供任何细节来处理数据、项目特征和最终用户请求的细节。

定制 CRISP-DM 周期以适应您的项目

即使我们清楚这个过程,如果没有合适的方向图,在许多机器学习算法和部署选项中进行选择可能会很困难。我们希望画出这样一张地图,以帮助理解根据手头的数据科学项目的特点应该朝哪个方向前进。

我们用一系列越来越复杂的问题来组织我们的定向之旅。我们从解决关于项目类型的基本疑问开始,然后我们转移到项目生产性实现的执行和部署选项。

我需要机器学习模型吗?

在我们碰巧工作的学科的命名混乱中,我们经常将数据科学、数据挖掘、人工智能、商业智能以及可能其他一些“数据”、“科学”和“智能”的排列放在同一个桶中。我不完全清楚每一个名字的细微差别和涵盖范围。

然而,我知道并不是所有与数据相关的项目都需要预测模型。通常,数据混合、清洗和转换;KPI 的计算;或者在仪表板或 BI 报告中组织成功的度量标准。在这种情况下,纯粹的数据转换和数学运算就足够了,不应该使用机器学习模型。

分类还是数量预测?

第二个问题指的是要预测的变量的性质。我们要预测一个类还是一个数?例如,我们是否希望预测客户将购买的下一个产品(类别)或明年的头发颜色(类别)趋势?还是我们要预测未来两周一台洗衣机的价格(数字)或者今晚伦敦金融城要消耗的千瓦(数字)?

请注意,有时数值预测问题可以简化为分类任务。例如,如果我想预测洗衣机价格的上涨或下跌(而不是以美元为单位的实际价格),我可以定义三个类别(“价格上涨”、“价格相同”、“价格下跌”),因此,这就变成了一个分类问题。

最常见和最容易的分类问题包括一个二元分类系统:“真”或“假”,“疾病”或“健康”,“低于”或“高于”某个值,等等。

一旦我们决定了我们是在处理分类还是数值预测问题,我们就可以从两个不同的机器学习算法库中进行选择。训练后,分类算法输出一组标称值(字符串),而数值算法输出数字(整数或双精度)。

数字预测和时间序列分析一样吗?

数值预测的算法不一定实现时间序列分析。顾名思义,时间序列分析需要一个额外的约束:时间。以随机顺序预测数字,就像线性回归一样,只是将数字输出与输入要素的向量相关联。另一方面,时间序列预测算法接受按特定时间顺序排列的输入向量。

尽管在时间序列分析中也经常使用相同的数值预测技术,但是已经设计了专用算法来考虑训练期间输入数据的时间顺序。在时间序列项目的情况下,记住数据划分为训练集和测试集不能是随机的,但它必须符合过去和未来的概念。

监督与非监督训练算法

现在,让我们进入数据集的细节。对于分类问题,很多机器学习算法需要一个带标签的数据集;也就是说,数据集提供了示例向量,并且每个示例都标记了正确的类别。这种算法被称为“有监督的”,当出现特定的输入向量时,它使用这种训练集的标记样本来定义内部规则,以输出正确的类。

然而,通常有标签的数据集是一种奢侈。除非标签以某种方式自动可用,否则贴标签过程通常在时间和金钱上是昂贵的。如果没有可用的标记数据集,我们可以解决聚类过程,其中模式根据距离和规则分组在一起。

如果我们仍然坚持一个有标签的数据集,可以使用主动学习程序。在这种情况下,我们从一个带有标签的数据集开始。在接下来的几次迭代中,临界点的数据子集被手动标记,并且它们的类别以“无监督”的方式自动扩展到相邻点。

分布不均匀、不常见和稀有类

当训练数据集充分覆盖各种类的所有不同类型和子类型时,监督分类算法工作得最好。在一个完美的项目中,数据均匀地分布在所有的类中。

然而,有时数据远非均匀分布。在这种情况下,每个类都需要足够数量的代表性数据示例才能出现在最终预测中。当类别分布不均匀时,我们可能希望模型输出反映原始的先验类别概率。为了迫使模型学习各种先验概率,我们可以划分原始数据集,对类列应用分层采样。分层采样生成与原始数据集具有相同类别分布的训练集。

有时一个类比数据集中的其他类更不常见。在这种情况下,不平衡的类别分布可能会使模型产生偏差,并遮蔽不经常出现的类别。这里,我们需要在训练期间用一个人为的更频繁的类来强制输入模型。这是通过对最频繁的类进行下采样(如果不太频繁的类的例子数量足够的话)或者对最不频繁的类进行过采样(引导)来获得的。

在极端情况下,原始数据集中完全缺少一个类。例如,物联网中的异常检测就是这种情况。这个场景需要稍微改变一下视角。在这种情况下,机器学习模型仅在可用类的数据上训练,计算预测值和真实值之间的距离,并且从距离值中扣除可能的异常。

在某些情况下,一个类可能是罕见的,但充满了后果,如投资中的金钱损失或医疗程序中的死亡。然后,只有在绝对确定的情况下,我们才应该强制模型决定支持这个类。这通常通过在模型部署期间应用权重或更改阈值来实现。

结构化数据与非结构化数据

传统上,数据的分析是指数值或标称值。在数据库领域,我们称这样的数据架构为结构化的。然而,在过去的几年里,数据分析领域注入了一些不太传统的数据。文字、网络图、推文、评论等。已经成为我们数据库和数据湖的一部分。

特殊类型的数据需要特殊类型的处理。例如,文本和网络图需要一套特殊的工具来进行解析、清理、矢量化,当然还有可视化。

图一。数据科学项目中的培训选项

闪亮的报告、休息和普通的后台执行

一旦模型被训练、测试和接受,它就进入部署。部署工作流将模型应用于新的真实数据,并生成相应的预测。预测然后被返回给最终用户。

在一些项目中,部署工作流需要为结果呈现提供一个漂亮的交互式图形仪表板。可以安排部署工作流的执行,以便定期生成设计的仪表板。

然而,有时结果只需要传递给 web 服务网络中的下一个应用程序。这里,部署工作流需要被部署为 REST 服务本身。

在某些情况下,输出结果只是保存在一个文件(或数据库)中,不需要其他可视化或 REST 接口。

执行选项:大数据、实时、流

现在是最后一个问题。我们需要大数据平台吗?我们需要流媒体吗?我们需要实时执行吗?

让我们从实时执行开始。我们认为的实时有时仅仅是人类感知的实时。先说清楚,等待几秒钟对于人类来说是完全可以接受的,但不一定是真正的时间。

当然,执行性能取决于平台。然而,并不是所有的项目都需要高性能的执行。在开始对分布式平台进行大规模投资之前,要考虑执行需求是什么。如果最终用户可以等待几秒钟,那么在快速机器上的经典执行是可以接受的。

如果数据量很大,等待时间变长,那么并行执行就变得很有必要。

流也可能是您的项目的一个特定需求,即使大多数时候刷新从文件中读取的数据足以保持结果最新。

图二。数据科学项目中的部署选项

作者注:

我们已经在电子书“实践数据科学”中收集了一系列用例及其实现的经验。案例研究集。”这本书是按应用领域组织的。它从最古老的数据科学领域(CRM 数据分析)开始,然后转移到零售商店的推荐引擎。接下来,它涵盖了金融行业、社交媒体和物联网时间序列分析中的项目,最后以几个网络安全项目结束。

电子书中报道的所有例子都引用了一个(或两个)工作流,这些工作流位于 KNIME EXAMPLES 服务器上,在每一节的开头都会如实报道。

我们希望这些数据科学经验将有助于培养下一代数据科学家的实用数据科学技能。如需免费下载,请访问https://www.knime.com/knimepress/practicing-data-science,并使用以下促销代码: MEDIUM-0519

— — — — -

为首次发表于data versity

布拉格步行无障碍。

原文:https://towardsdatascience.com/prague-walking-accessibility-8c0954e83bea?source=collection_archive---------43-----------------------

1.摘要

在这项研究中,我们试图分析 10 至 16 岁儿童的城市步行能力。我们使用一些数据来源和网络分析来构建所选城市的可达性地图。使用 K 最近邻技术,我们执行聚类以选择具有不同步行能力分数的区域。我们根据可步行性得分和 poi 密度分析这些杂乱数据,并确定在步行可达性方面需要改进的区域。

这项研究的笔记本可以在 GitHub 上免费获得:

数据采集和清理

探索性数据分析和可视化

建模和聚类

关键词

可达性聚类分析网络分析城市规划

2.介绍

步行是最可靠、最轻松、最健康的旅行方式。然而,今天步行上学的儿童数量正在下降。这对我们儿童的健康、安全和福祉造成了破坏性后果。今天,超过 70%的父母在他们还是孩子的时候步行上学,但是现在只有不到一半的孩子步行上学。与此同时,开车上学的儿童数量正在增加,对交通拥堵、健康和社区产生了越来越大的负面影响[1]。

步行是一种简单,自由和健康的方式,让孩子们去学校或其他活动,如图书馆,文化和爱好中心,操场等。步行为每个人提供了很多好处。

行人无障碍是什么意思?对我们来说,它意味着在合理的时间内到达特定的日常生活兴趣点(POI)的能力。我们所说的合理时间是指儿童行走 1300 米所需的时间。对于选定的年龄范围,这个距离是合理的。[2]10 至 16 岁儿童的常见兴趣点有:学校、业余爱好和休闲设施、图书馆、体育设施和户外游乐场。

因此,本研究的目的是:(1)根据人口密度和兴趣点密度分析当前城市的儿童基础设施。(2)构建可步行路线网络。(3)定义可步行性集群

通过对相关文献的讨论,本部分介绍了本文的研究目的和范围。研究区域和数据部分进入研究区域和数据源。方法在方法部分介绍。结果部分展示了结果,并说明了如何将结果应用于规划实践。结论和政策建议见讨论和结论部分。

报告全文可在 GitHib 上获得

3.研究区域和数据

布拉格是捷克共和国最大的城市,总面积 298 平方公里,人口 130 万,是欧盟第 14 大城市。它是著名的经济和文化中心,位于捷克共和国的中部地区。作为一个拥有超过 14 个世纪历史的城市,它拥有古典的中世纪城市中心,有许多历史建筑和小街道。市中心的周围更加现代化,外围则是小排屋和私人住宅。下图显示了布拉格市辖区的行政边界和每个区的总人口。

在我们的研究中,我们使用的数据类型包括所选 POI 的人口和地理数据。所有数据均来自公开数据来源:人口数据来自捷克统计局,教育设施数据来自捷克共和国教育、青年和体育部。其他数据集来自布拉格的开放数据门户和其他网络资源。

3.1 人口

作为人口数据集,我们使用捷克统计局的人口普查数据。捷克统计局的整个数据集包含捷克共和国公民的人口、性别和年龄数据。特征名称已编码。

因为我们的研究区域是布拉格,所以我们只对具有 uzcis = 44 的行政区的人口感兴趣。我们根据这个参数从原始数据集中过滤数据,删除不必要的列并执行重命名。

3.2 地理形状

IPR Praha 获取行政区边界数据集。这是 json 数据,包含一组地区属性,如名称、形状、面积等。

"type" : "FeatureCollection",
	"name" : "TMMESTSKECASTI_P",
	"features" : [
		{
			"type" : "Feature",
			"geometry" : {
				"type" : "Polygon",
				"coordinates" : [
					[
						[ 14.535485616000074, 50.01175081800005 ],
						...
						[ 14.535067366000021, 50.011918405000074 ]
				]
			},
			"properties" : {
				"OBJECTID" : 1,
				"DAT_VZNIK" : "20171204150607",
				"DAT_ZMENA" : "20171204154726",
				"PLOCHA" : 3703180.2199999997,
				"ID" : 34,
				"KOD_MC" : 539791,
				"NAZEV_MC" : "Praha-Újezd",
				"KOD_MO" : 43,
				"KOD_SO" : "116",
				"TID_TMMESTSKECASTI_P" : 34,
				"POSKYT" : "HMP-IPR",
				"ID_POSKYT" : 43,
				"STAV_ZMENA" : "U",
				"NAZEV_1" : "Újezd",
				"Shape_Length" : 0.11393431835892182,
				"Shape_Area" : 3703180.22185
			}
		},

从这些数据中,我们感兴趣的是面积和几何形状,我们还得到一个区的名称作为唯一键。

我们通过地区名称来比较人口数据集和地区边界数据集。我们在这两个数据集中获得了 57 个唯一的记录。我们通过 name this 来执行连接,以将集合合并成结果集合。结果数据集被保存到 IBM 云存储中,命名为布拉格 _ 区 _ 人口. csv 。此外,还可以在我们的 GitHub 库上访问它

3.3 兴趣点

在我们的研究中,我们对一些类型的点感兴趣,如:学校、教育和爱好中心、图书馆、体育设施和操场。我们决定只使用这 5 种类型,因为这些类型是 10-16 岁儿童最常见的类型。数据是从许多数据源获得的。我们不使用任何类型的地理 API,如 Google Places 或 Foursquare,因为我们需要全面覆盖我们的研究领域。所有知名 API 都没有为免费账号提供这样的设施。

学校和教育中心

我们从教育、青年和体育部获得学校和教育中心的数据。这个 xml 数据集包含布拉格所有州立教育机构的信息。在我们的研究中,我们只对 poi 类型、地区和地理坐标感兴趣。

从原始教育数据集中,我们提取了教育设施的地址和类型,总共有 2273 行。类型是编码的。由于我们没有找到这个数据集的任何未来描述,我们必须决定什么类型的教育设施适合我们的研究。我们从检索到的数据中获得独特的类型:

Types in XML file
A00 Mateřská škola
L11 Školní jídelna
L13 Školní jídelna - výdejna
B00 Základní škola
M60 Přípravný stupeň základní školy speciální
G21 Školní družina
G22 Školní klub
F10 Základní umělecká škola
C00 Střední škola
D00 Konzervatoř
M20 Školní knihovna
E00 Vyšší odborná škola
M79 Jiné účelové zařízení
H22 Domov mládeže
G11 Dům dětí a mládeže
G40 Zařízení pro další vzdělávání pedagogických pracovníků
M40 Středisko praktického vyučování
H21 Internát
K20 Speciálně pedagogické centrum
G12 Stanice zájmových činností
K10 Pedagogicko-psychologická poradna
F20 Jazyková škola s právem státní jazykové zkoušky
J12 Dětský domov se školou
J21 Středisko výchovné péče
J14 Diagnostický ústav
J11 Dětský domov
J13 Výchovný ústav
L12 Školní jídelna - vývařovna
H10 Škola v přírodě
A15 Mateřská škola (lesní mateřská škola)
Schools and educational centers count 560
Unique types ['school' 'educatioanal center']

我们可以看到我们感兴趣的类型有:

对于建模步骤,我们对实际的场地地址不感兴趣,而是对坐标感兴趣。我们使用 ArcGis api 检索纬度和经度。学校和教育中心的结果数据集包含 560 行

图书馆的数据从网站被废弃。作为数据清洗过程中,我们提取的地址和地区名称从原来的 html 页面。对于 html 解析,我们使用 BeautifulSoup 库。使用 ArcGis api 将地址转换为纬度和经度。结果数据集包含 41 个库。

体育中心、俱乐部和游乐场。

布拉格开放数据门户网站检索了体育设施和操场的数据。对于这些数据,我们执行与前面的数据集相同的步骤:提取地址和地区名称,将地址转换为纬度和经度。

我们将所有五个数据集合并成一个有 1623 行的数据集。数据集可在外部 GitHub 存储库上访问

从开放街道地图(OSM)获得的街道网络数据 OSM 数据包含布拉格道路网络和周围连接的道路,这些是以长度为特征的线几何。这些可以用来构建一个可路由的拓扑图(有向的、加权的和连通的),它由节点和边组成,因此任何众所周知的路由搜索算法都可以应用[3]。布拉格的步行网络包含 140 822 个节点和 204 575 条链路。预处理数据我们存储在 IBM 云对象存储和 GitHub

4.探索性数据分析

布拉格的总人口约为 130 万。最高值在南区,从 110K 到 130K。所有人口高于平均值的地区都位于历史中心区周围。这些地区的平均人口是 7 万人。这是意料之中的。在像布拉格这样的城市里,大多数人离开市中心很近,但不在市中心。北部两个人口从 9 万到 10 万的最大地区向我们展示了布拉格新的发展区域。

从另一个角度分析布拉格的人口,我们可以注意到每 1000 名成人中儿童的分布与之前的情况相关。在这张图中,我们可以清楚地看到主要受家庭欢迎的地区。中心区和郊区的儿童死亡率最低,而中心附近但离中心不远的地区的儿童死亡率平均为每 1000 名成人 160 至 180 名儿童。

儿童人口最多的前 10 个地区是以前图片中的前 10 个地区的重复。在布拉格,我们最多只有 8%。

名胜古迹也集中在历史中心周围。按地区的分布与地区人口相关。这是预期值。我们在人口前 10 名的所有地区都有大致相等数量的点,在南部有一个异常的高度值。

兴趣点的总数并不能全面反映每个地区对儿童的益处。对我们来说,更有趣的是研究兴趣点的数量与 1000 名儿童之间的关系。比如我们看学校/1000 的直方图。我们注意到最高值与儿童数量无关。我们可以看到布拉格 1 的值最高,但如果我们看一下以前的图表,我们可以看到布拉格的儿童百分比甚至没有进入前 10 名。另一方面,布拉格 4 区的人口数值最高,学校/1000 的平均值也最高。

我们可以在接下来的直方图中看到同样的图片。中心区的 POIs/1000 值最高,郊区最低。一方面,这是因为城市中心的儿童人口较少,另一方面,其他城市正从其历史中心发展起来,那里的学校和其他设施的密度通常较高。

5.方法学

5.1 网络分析

这项研究的空间框架需要使用基于点的无障碍措施。常用的空间单位是行政区划和格网单元。由于研究区域从一个城市到一个国家,甚至整个世界,空间单元的类型、形状和大小因研究目的不同而有所差异,并且没有达成共识。在我们的研究中,使用布拉格的边界框作为空间框架。我们不能在研究中使用更小的框架,如行政区划,因为我们必须处理边界条件。如果儿童离开自己的地区,而最近的 POI 位于另一个地区。在这种情况下,父母通常会决定去最近的 POI。

第一步,我们将 OSM 街道网络转换成图形对象。对于构建图形,我们使用 Pandana 框架。Pandana 的主要用例是沿着网络执行聚合,即缓冲区查询。该 api 旨在以多线程方式(使用底层 C 库)同时为网络中的所有节点执行聚合。大多数步行规模的可访问性查询可以在不到一秒钟的时间内执行,甚至对于成千上万的节点也是如此。[4]

在“清理”中,我们指的是移除不代表实际交点的点(因此不是图论意义上的节点)。

街道图[6]

因为第二步是从上一步到网络图定位兴趣点并计算可达性矩阵。在可达性矩阵中,我们指的是从在数据采集步骤中采集的预定义兴趣点阵列到前 3 个兴趣点的距离阵列。因为我们想研究可达性的平均值,所以我们计算了每种兴趣点的平均步行距离:学校、图书馆和其他儿童设施。并将其存储到数据集。这个数据集稍后将用于聚类。我们总共得到 140877 条边

我们将使用 K-Means 聚类算法。这是一种矢量量化的方法,最初来自信号处理,在数据挖掘的聚类分析中很流行。k-means 聚类旨在将 n 个观察值划分为 k 个聚类,其中每个观察值属于具有最近均值的聚类,作为该聚类的原型。这导致将数据空间划分成 Voronoi 单元。k-Means 最小化类内方差(平方欧几里得距离)。确定我们使用的最佳聚类数和肘方法。

肘方法是一种启发式方法,用于解释和验证聚类分析中的一致性,旨在帮助在数据集中找到适当数量的聚类。它通常是不明确的并且不是非常可靠,因此用于确定聚类数量的其他方法,例如剪影方法是优选的。

这种方法将方差的百分比解释为聚类数的函数:应该选择多个聚类,这样添加另一个聚类不会提供更好的数据建模。更准确地说,如果绘制由聚类相对于聚类数量解释的方差的百分比,第一个聚类将增加很多信息(解释很多方差),但是在某个点,边际增益将下降,在图中给出一个角度。在这一点上选择簇的数量,因此称为“肘形标准”。这个“肘”不能总是被明确地识别。[1]解释的方差百分比是组间方差与总方差的比率,也称为 f 检验。该方法的一个微小变化是绘制组内方差的曲率。[2]

5.2 可步行性集群

因此,我们有 4 个集群。我们计算每个集群的距离、可步行性核心和时间的平均值。我们所说的合理时间是指儿童行走 1300 米所需的时间。计算出每个聚类中每种类型的可步行性得分。分数=实际距离/1300。作为平均步行速度,我们得到 4 公里/小时[4]。

通过重新定义行走图上边的权重,我们可以在布拉格地图上可视化聚类。

此外,我们还为每种类型的兴趣点建立了可步行性热图。

5.结果

因此,我们在布拉格有 4 个步行性集群。聚类 2 具有最佳步行性得分 1。这意味着,从这个集群内的每一个点,儿童都可以在平均大约 15 分钟内到达所有必要的兴趣点。学校在这个集群内是最容易到达的,这是一个非常好的结果。另一方面,集群 2 主要覆盖中心历史区,我们知道该区的儿童人口低于周边地区。第二组是 0 号。它的可行走性得分约为 1.3。学校和体育设施不到 10 分钟就能到达。这是非常好的结果。

这两个集群中可访问性得分最低的是图书馆和业余爱好中心。平均大约需要 30 分钟的步行时间。

我们在第三组得到的最低值。该集群主要覆盖远离市中心的小郊区和地区,主要是小村庄。第三产业群内部的另一部分也坐落着许多新的开发项目

群组 1 我们决定将它算作噪声值,因为在这个群组中,我们主要有公园、墓地和工业区,换句话说就是非生活区。

最容易到达的兴趣点是学校,这是非常好的结果,因为根据不同的研究,到学校的步行距离在测量区域行人可达性方面具有最重要的价值。

结果也很好,我们有运动设施。除了第一组,其他组平均步行 10 分钟就能到达。

最低的可及性得分有业余爱好中心和图书馆,这是可预期的结果,因为我们像往常一样每个地区有一个这种类型的场所。

7.讨论和结论

在这项研究中,我们分析了布拉格的步行性。我们主要关注 10-16 岁儿童的步行无障碍性。我们确定了所选焦点组的共同兴趣点,并对所有所选兴趣点的最短距离进行了测量。利用无监督聚类算法,我们将研究区域划分为 4 类。用统计分析的方法检验了聚类的结果。

在未来的研究中,我们计划对每种类型进行更详细的分析。我们必须根据地势给街道图的边缘增加权重。此外,我们不仅从最短路线的角度,也从最安全的角度来看待行人可达性。

8.参考

  1. 活生生的街道(行人协会)一份活生生的街道报告
  2. 比利时大龄青少年主动上学的标准距离和相关因素。德尔菲恩·范·戴克,伊尔莎·德·布尔多胡伊,问候卡顿
  3. 瑙曼和科瓦廖夫(2017 年)。基于 OpenStreetMap 的行人路径搜索。在智能交通系统和出行行为(第 87-96 页)。湛:斯普林格。
  4. pandanahttps://udst . github . io/pandana/introduction . html #简介
  5. 儿童行走的力学https://phys oc . online library . Wiley . com/doi/pdf/10.1113/jphysiol . 1983 . sp 014895
  6. OS mnx:Python for Street Networkshttps://geoffboeing . com/2016/11/OS mnx-Python-Street-Networks/

9.版权信息

安东·艾尔明 2019

开架借阅

本文根据知识共享署名 4.0 国际许可证(http://creativecommons.org/licenses/by/4.0/)的条款分发,该许可证允许在任何媒体上不受限制地使用、分发和复制,前提是您适当注明原作者和来源,提供知识共享许可证的链接,并指出是否进行了更改。

降低神经网络复杂性的预定义稀疏度

原文:https://towardsdatascience.com/pre-defined-sparsity-for-reducing-complexity-in-neural-networks-55b0e85a1b54?source=collection_archive---------16-----------------------

预定义稀疏度

神经网络现在非常流行。它们使深度学习成为可能,这为语音识别和无人驾驶汽车等智能系统提供了动力。这些酷的最终结果并没有真正反映大多数现代神经网络的血淋淋的复杂性,这些网络有数百万个参数需要被训练以使系统变得智能。训练需要时间和大量的计算资源,而这些资源通常会转化为金钱。这在富人和穷人之间形成了一个障碍,富人是拥有大量计算能力的大型科技公司,穷人是像我这样的博士生,他们负担不起在几天内培训几十个网络的费用。嗯,研究通常是出于需要。

我的博士工作试图以最小的性能下降来降低神经网络的复杂性。这是通过使用预定义稀疏度来完成的,从一开始,即在训练之前,就以低复杂度网络开始。

本文将解释和描述预定义的稀疏性。也鼓励你参考我最近的论文了解更多细节:

基本概念

神经网络由节点(或神经元)组成,这些节点被分组到中,并使用加权边填充层之间的结点进行连接。本文的大部分内容将涉及多层感知器(MLPs),而我正在进行的工作是扩展到卷积神经网络(CNN)。如图所示,如果一个层中的每个节点都连接到其相邻层中的每个节点,则 MLP 是全连接的 (FC)

A fully connected MLP with neuronal configuration Nnet = (8,4,4) denoting the number of neurons in each layer. Each edge has an associated weight value, only a few of them are shown for clarity.

文献中的几种复杂性降低技术训练 FC 网络,然后删除它的一些权重,以获得用于测试阶段的更简单的网络。相比之下,我的预定义稀疏性工作中的“前”来自于这样一个事实:稀疏连接模式在训练之前是固定的。因此,对于训练和推断都获得了复杂度降低,并且可以通过查看稀疏网络相对于 FC 的权重密度来量化复杂度降低。例如,下面的网络在交叉点 1 的 ρ₁ = 25%密度,在交叉点 2 的 ρ₂ = 50%,在整个网络中 ρ net = 33% 总密度。这意味着 FC 网络将具有 48 个权重,而预定义的稀疏网络具有 16 个权重。

请注意,特定层中的每个节点都有相同数量的向右连接(出度 dout ) —第一个结点为 1,第二个结点为 2,并且两个结点都有相同数量的来自左侧的连接(入度 din ) — 2。这意味着稀疏连接模式是结构化的,而不是随机分配权重,冒着让一些节点完全断开的风险。事实上,我的结果表明结构化稀疏比随机稀疏表现更好。

有用吗?如果是,为什么?它有什么独特之处?

我用来测试我的工作的分类数据集是 MNIST 手写数字,路透社 RCV1 新闻文章, TIMIT 语音音素识别,以及 CIFAR -10,-100 图像识别。预定义的稀疏度给出了有希望的结果,如下所示。

旁边的结果是 MNIST(上)和路透社(下)。x 轴是总密度,因此最右边 100%的圆圈点是 FC 网络。y 轴是测试准确度,这是神经网络常用的性能指标。请注意,20%密度点的精度仅比 FC 低 1%左右。换句话说,权重的数量可以减少 5 倍,而性能下降很少,这正是我的研究旨在实现的目标。

现在让我们来解决一个更大的问题:为什么预定义的稀疏性有效。这不可能是一种魔法,它使我们能够从一开始就将 FC 网络的重量减少 80%,同时几乎没有性能损失(补充说明:深度学习中的许多东西看起来确实像魔法,但电视刚推出时也是如此)。为了理解这一点,我在对一个全连接网络进行训练直至其收敛后,绘制了该网络不同连接点的权值直方图。请注意大多数权重都接近 0 。这表明一个网络可能不需要太多的权重就能变好。

让我们暂时考虑一下正则化的标准技术。这对网络的权重施加了惩罚,使得它们在训练期间受到限制。预定义的稀疏度也施加约束,除了它对权重的数量施加约束,并且在训练之前施加约束。另一种流行的技术——辍学——也在训练期间删除连接。然而,它在不同的随机配置中这样做,并在组合它们进行测试之前训练所有这些候选网络。相比之下,预定义的稀疏度只在一个网络上训练和测试,这个网络恰好具有少得多的权重。

表征预定义的稀疏性

预定义的稀疏连接模式——哪些节点连接到哪些节点——是要在训练之前设置的超参数。我的研究发现了几个趋势,作为选择这个超参数的指南。

首先,预定义的稀疏性利用了数据集中的冗余。

首先,大多数机器学习数据集都有冗余。以经典的 MNIST 为例,每幅图像有 28×28 = 784 个像素。做一个主成分分析表明只有中心像素是有趣特征集中的地方,这并不奇怪。这意味着,通过考虑,比如说,每幅图像中心附近的 200 个承载最多信息的像素,可以获得修改的减少冗余的 MNIST 数据集。

下图比较了原始数据集(黑色)的预定义稀疏性与增加(红色)或减少(蓝色)冗余的相同数据集的修改版本的性能。请注意,稀疏网络对于减少冗余的情况并不那么有效,即蓝线在密度减少的情况下向左侧下降。

Tokens for Reuters and MFCCs for TIMIT serve as features. For CIFAR, the depth of the pre-processing CNN before the MLP serves as redundancy.

其次,后面的连接需要更高的密度。

这是我们之前在重量直方图中看到的。与 W 4 相比,更早的连接( W 1、 W 2、 W 3)在量值上具有更多接近 0 的权重,这表明稀疏连接模式应该尝试在更晚的连接中具有更多权重,在更早的连接中具有更少权重。实验结果证实了这一点,为了简洁起见,我没有在这里展示。

第三,“大而稀疏”的网络比具有相同数量参数的“小而密集”的网络表现更好。

之前,当我提到“一个网络可能不需要太多权重就能变好”时,您可能会想,如果我们从一个小型 FC 网络开始,即拥有较少节点,会发生什么情况?答案是,它的性能将不如预定义的具有更多节点和相同数量权重的稀疏网络。这表明,小网络不是解决方案,最好有大的传统网络,并消除重量。

下图显示了在 MNIST 进行的 4 结点网络培训。根据输入要素和输出标注的数量,输入和输出图层分别具有 784 个和 10 个结点。3 个隐藏层具有相同的节点数 x ,这是可变的。这些预定义稀疏网络的总密度被设置为使得对于不同的 x 值,不同的网络具有相同数量的权重。例如, x =14 的 FC 网络与 x =28 的 50%密集网络、 x =56 的 22%密集网络和 x =112 的 10%密集网络具有相同的权重数(大约 11000)。这些是蓝色椭圆内的点。请注意 x =112 的情况如何在这些情况中表现最佳。然而,这种趋势在密度非常低的情况下失败了(意味着密度为 4%且 x =224 的网络表现稍差),然而,一般来说,在参数数量相同的情况下,“大而稀疏”的网络比“小而密集”的网络表现更好。

Each line with a single color denotes a particular value of x, i.e. a particular network architecture. The shading denotes 90% confidence intervals, since test accuracies are not exactly the same across different runs. Each marker of a certain shape denotes a particular number of total weights (i.e. trainable parameters) in the network.

预定义稀疏性的应用和扩展

我们的一些前期工作详细介绍了一种硬件架构,该架构利用预定义稀疏性减少的存储和计算复杂性来训练和测试 FPGAs 上的网络。从机器学习的硬件加速的角度来看,这是有前途的。

我个人的努力已经转向模型搜索领域。模型搜索,包括神经结构搜索(NAS) 和超参数搜索,基本上试图自动发现一个给定问题的好的神经网络。虽然“好”通常指的是高精度,但这种网络通常非常复杂,有几十层。我目前的研究集中在低复杂度网络的模型搜索,它也表现良好。换句话说,这是一个优化问题,目标 f 为:

fp and fc are functions to measure the performance and complexity of a network. wc denotes the amount of importance given to minimizing complexity. Its effect is shown below.

这个领域还扩展了低复杂度的技术来考虑 CNN。例如,可以将滤波器核预定义为稀疏的,或者可以增加步长以减少训练时间。这些细节将在另一篇研究文章中介绍。

伙计们,现在就到这里吧!如果你对预定义稀疏度及其应用感兴趣,可以在这里找到代码。为了直观地了解我在神经网络方面的研究,请看这个视频

Sourya Dey 是南加州大学的博士生。他的研究涉及探索深度学习中的复杂性降低。你可以在他的网站上了解更多关于他的信息。

使用管道预处理数据,以防止交叉验证期间的数据泄漏。

原文:https://towardsdatascience.com/pre-process-data-with-pipeline-to-prevent-data-leakage-during-cross-validation-e3442cca7fdc?source=collection_archive---------7-----------------------

image source: http://www.securitymea.com/2018/04/25/data-leakage-has-gone-up-by-four-times/

在机器学习中, K 重交叉验证是一种常用的验证技术,用于评估统计模型的结果如何推广到一个未知的数据集。它通常用于估计模型的预测能力。今天我想讨论一下使用 StandardScaler 等数据预处理器进行交叉验证时的一个常见错误。

错误

让我们通过检查下面的代码来识别错误。我用两个红框突出了要讨论的部分。

在第一个红色框中,使用标准缩放器对训练集进行拟合和转换,对测试集进行转换(不拟合)。这似乎是正确的,因为训练集和测试集都是由标准定标器仅使用来自训练集的信息进行标准化的(测试集是不可见的)。

第二个红框突出显示了由 GridSearchCV 执行的 5 重交叉验证,以在拟合数据时基于标准化的训练集选择最佳模型。问题来了,但是让我先后退一步,简单回顾一下 GridSearchCV 是如何执行交叉验证的。 GridSearchCV 将训练集分为内部训练集和验证集。对于 5 重交叉验证,内部训练集与验证集的数据比为 4: 1。该过程重复四次,从而覆盖整个列车组(见下图)。

https://scikit-learn.org/stable/modules/cross_validation.html

现在让我分四步来讨论这个问题:

  1. 交叉验证的目的是估计预测能力,因此验证集应被视为临时的不可见测试集。
  2. 标准刻度不应与临时测试装置相匹配。然而,因为我们给 GridSearchCV 配备了一个预处理过的列车组,不幸的是,临时测试组配备了标准刻度,因此泄露了。
  3. 临时测试集的方差被人为地去除,因此得到的交叉验证分数是有偏差的。
  4. GridSearchCV 根据交叉验证分数决定模型参数,可能无法找到减轻过拟合的最佳模型。

我从我在大会上学习的数据科学沉浸式项目的顶点项目中注意到了这个问题的重要性。该项目旨在使用 Kaggle 提供的经典威斯康星大学乳腺癌细胞(诊断)数据集建立分类模型,对乳腺癌细胞进行分类。该项目的第一部分是复制 1994 年由威斯康星大学教授 Olvi L. Mangasarian、W. Nick Street 和 William H. Wolberg 出版的《通过线性规划进行乳腺癌诊断和预后》中讨论的乳腺癌诊断模型。因为数据集很小(即 569 个样本),所以模型的预测能力仅基于交叉验证分数进行估计(而不是分成训练集、开发集和测试集三部分)。

解决方案

幸运的是,有一个简单的解决方案。通过简单地将预处理器和估计器放入流水线,我们可以避免 GridSearchCV 的这种不希望的结果。参见下面的代码:

为了演示 Pipeline 如何正确地解决问题(fit _ transform only the inner train set withstandard scaler),我想引用 Shihab Shahriar(经他允许)对我在 Stackoverflow.com 上发布的问题的惊人回应。通过使用 cross_val_score 仅查看 GridSearchCV 的交叉验证部分,进一步简化了响应。该演示使用了乳腺癌数据集(569 个样本)。

1.子类 StandardScaler 打印发送给 fit_transform 方法的数据集的大小。

2.将类放到管道中,并通过 cross_val_score 进行 5 重交叉验证。

3.输出显示,只有 80%的数据集(569 * 80%)符合 cross_validation_score 并已转换。

我要特别感谢 Shihab,他的解决方案让读者受益匪浅。我在 Stackoverflows.com 上发布的最初的问题集中在交叉验证期间管道是否与预处理程序一起正常工作。Shihab 不仅给出了解决方案,还讨论了不同估计器对预处理器输出的敏感性,这也可能回答您的一些其他问题。如果你感兴趣,点击此处查看整个讨论。

结论

这篇博客文章的要点是,每当需要数据预处理程序如标准缩放器PCA (即主成分分析)来构建机器学习模型时,确保使用管道来进行模型调整的交叉验证。同样,我们的最终目标是建立一个机器学习模型,它将推广到一个看不见的数据集。因此,我认为在交叉验证过程中防止数据泄漏确实很重要。希望这个帖子有帮助~。

为 NLP 模型训练预处理 Wikipedia 转储—书面报告

原文:https://towardsdatascience.com/pre-processing-a-wikipedia-dump-for-nlp-model-training-a-write-up-3b9176fdf67?source=collection_archive---------18-----------------------

下载、提取、清理和预处理 NLP 模型的维基百科转储(例如,像 BERT、RoBERTa 等的变压器)。)培训

Wikipedia entry for Bert (from Sesame Street)

Wikipedia dumps在现代 NLP 研究中被频繁用于模型训练,尤其是与变形金刚如 BERT、RoBERTa、XLNet、XLM 等。因此,对于任何有志于掌握这些模型的 NLP 研究人员来说,这篇文章展示了下载、提取、清理和预处理维基百科转储所涉及的一切(和代码)。

📥下载维基百科转储

维基百科转储以多种语言的多种格式免费提供。对于英语维基百科,最新转储的所有可用格式的完整列表可以在这里找到。

因为我们主要对文本数据感兴趣,所以为了本文的目的,我们将使用下面的代码下载这样一个压缩 XML 格式的转储(只包含页面和文章):

Simple bash script to download the latest Wikipedia dump in the chosen language

例如,要下载最新的英语维基百科转储,只需在终端中运行以下命令:./download_wiki_dump.sh en

🗜️提取和清理维基百科转储

我们刚刚下载的维基百科转储还不能进行预处理(句子标记和每行一句)。首先,我们需要提取并清理转储,这可以通过使用下面的代码使用 WikiExtractor轻松完成:

Simple bash script to extract and clean a Wikipedia dump

例如,要提取并清理我们刚刚下载的 Wikipedia 转储,只需在您的终端中运行以下命令:./extract_and_clean_wiki_dump.shenwiki-latest-pages-articles.xml.bz2

⚙️预处理维基百科转储

既然我们已经成功地下载、提取和清理了维基百科转储,我们可以开始预处理它了。实际上,这意味着对文章进行句子标记,以及将它们每行一句地写到一个文本文件中,这可以使用微软速度惊人的bling fire tokenizer来完成,使用下面的代码:

例如,要预处理我们刚刚提取和清理的 Wikipedia 转储,只需在您的终端中运行以下命令:python3 preprocess_wiki_dump.pyenwiki-latest-pages-articles.txt

****就这样,大功告成!🙌现在,您可以使用自己新创建的维基百科语料库,亲自尝试 NLP 中最新最棒的内容。🤗

OCR 中的预处理!!!

原文:https://towardsdatascience.com/pre-processing-in-ocr-fc231c6035a7?source=collection_archive---------0-----------------------

OCR 系统最广泛使用的预处理技术的基本解释。

欢迎来到关于 OCR 系统工作的系列第二部分 。在 之前的文章 中,我们简要讨论了 OCR 系统的不同阶段。

在 OCR 的所有阶段中, 预处理分割 是最重要的阶段,因为 OCR 系统的准确性很大程度上取决于预处理分割的执行情况。因此,在这里我们将学习一些最基本和最常用的图像预处理技术。

我们走吧…

预处理阶段的主要目的是 使 OCR 系统尽可能容易地 从背景中辨别出字符/单词。

一些最基本和最重要的预处理 技术有

1)二值化
2) 倾斜校正
3) 去噪
4) 细化和骨架化

在讨论这些技术之前,让我们先了解一下 OCR 系统是如何理解图像的。对于 OCR 系统,图像是多维数组(如果图像是灰度(或)二进制,则为 2D 数组,如果图像是彩色的,则为 3D 数组)。矩阵中的每个单元称为一个像素,它可以存储 8 位整数,这意味着像素范围是 0-255。

Internal Representation of RGB image with Red, Green and Blue Channels. Source: left image from semantics scholar, right image from researchgate.

Internal Representation of Grayscale image. It has only one channel. Source: ekababisong.org

让我们逐一检查上面提到的每一种预处理技术

  1. 二值化: 通俗地说二值化就是将彩色图像转换成只由黑白像素组成的图像(黑色像素值=0,白色像素值=255)。作为一个基本规则,这可以通过固定一个阈值来实现(通常阈值=127,因为它正好是像素范围 0–255 的一半)。如果像素值大于阈值,则认为是白色像素,否则认为是黑色像素。

Binarization conditions. Source: Image by author

但是这种策略并不总是给我们想要的结果。在图像中光照条件不均匀的情况下,这种方法会失败。

Binarization using a threshold on the image captured under non-uniform lighting. Source: left image from this post and right image binarised by author.

所以,二值化的关键部分是确定 阈值 。这可以通过使用各种技术来完成。

局部最大最小值法:

Imax= Maximum pixel value in the image, Imin= Minimum pixel value in the image, E = Constant value Source: Reference [2]

C(i,j) 是图像中局部 定义尺寸阈值(如 10x10 尺寸的零件)。使用这种策略,我们将为图像的不同部分设置不同的阈值,这取决于周围的光照条件,但是过渡并不平滑。

Otsu 的二值化:该方法考虑到整个图像的各种特征(如光照条件、对比度、锐度等),为整个图像给出一个阈值,该阈值用于二值化图像。
这可以通过以下方式使用 OpenCV python 来实现:

ret, imgf = cv2.threshold(img, 0, 255,cv2.THRESH_BINARY,cv2.THRESH_OTSU) #imgf contains Binary image

-> 自适应阈值处理:这种方法根据图像的局部和邻居的特征,为图像的一小部分给出一个阈值,也就是说,对于整个图像没有单一的固定阈值,但是图像的每一小部分根据局部都有不同的阈值,并且还提供平滑过渡。

imgf = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,11,2) #imgf contains Binary image
  1. 歪斜校正: 扫描文件时,有时可能会出现轻微歪斜(图像与水平面成一定角度)。从扫描图像中提取信息时,检测&校正倾斜是至关重要的。
    多种技术用于倾斜校正。

→投影轮廓法
→霍夫变换法
→背线法
→扫描线法

然而,投影轮廓方法是确定文件倾斜的最简单、最容易和最广泛使用的方法。

在这个方法中,首先,我们将二进制图像,然后

  • 将其水平投影(取图像矩阵各行的像素总和)以获得图像高度的像素直方图,即每行的前景像素计数。
  • 现在,图像以各种角度旋转(以称为 Delta 的小角度间隔),并且将计算峰值之间的差异(方差也可以用作度量之一)。找到峰值之间的最大差(或方差)的角度,该对应角度将是图像的倾斜角度
  • 找到歪斜角后,我们可以通过在歪斜的 相反方向 旋转图像一个等于歪斜角的角度来校正歪斜。

Correcting skew using the Projection Profile method. Source: Reference[1]

import sys
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image as im
from scipy.ndimage import interpolation as interinput_file = sys.argv[1]img = im.open(input_file)# convert to binary
wd, ht = img.size
pix = np.array(img.convert('1').getdata(), np.uint8)
bin_img = 1 - (pix.reshape((ht, wd)) / 255.0)
plt.imshow(bin_img, cmap='gray')
plt.savefig('binary.png')def find_score(arr, angle):
    data = inter.rotate(arr, angle, reshape=False, order=0)
    hist = np.sum(data, axis=1)
    score = np.sum((hist[1:] - hist[:-1]) ** 2)
    return hist, scoredelta = 1
limit = 5
angles = np.arange(-limit, limit+delta, delta)
scores = []
for angle in angles:
    hist, score = find_score(bin_img, angle)
    scores.append(score)best_score = max(scores)
best_angle = angles[scores.index(best_score)]
print('Best angle: {}'.formate(best_angle))# correct skew
data = inter.rotate(bin_img, best_angle, reshape=False, order=0)
img = im.fromarray((255 * data).astype("uint8")).convert("RGB")
img.save('skew_corrected.png')

Skew Correction. Source: pyimagesearch.com by Adrian Rosebrock

  1. 噪声去除:噪声去除阶段的主要目的是通过去除比图像其余部分具有更高亮度的小点/小块来平滑图像。可以对彩色二进制图像进行噪声去除。
    使用 OpenCVfastNlMeansDenoisingColored函数进行去噪的一种方法。
import numpy as np 
import cv2 
from matplotlib import pyplot as plt 
# Reading image from folder where it is stored 
img = cv2.imread('bear.png') 
# denoising of image saving it into dst image 
dst = cv2.fastNlMeansDenoisingColored(img, None, 10, 10, 7, 15) 
# Plotting of source and destination image 
plt.subplot(121), plt.imshow(img) 
plt.subplot(122), plt.imshow(dst) 
plt.show()

Smoothening and Denoising of image. Source: Reference [4]

更多关于去噪&图像平滑的技巧可以在 这篇 的精彩文章中找到

  1. 细化和骨架化 :这是一个可选的预处理任务,取决于使用 OCR 的上下文。
    →如果我们对打印文本使用 OCR 系统,则无需执行此任务,因为打印文本始终具有统一的笔画宽度。
    →如果我们使用 OCR 系统处理手写文本,则必须执行此任务,因为不同的书写者有不同的书写风格,因此笔画宽度也不同。所以为了使笔画的宽度一致,我们必须执行细化和

这可以通过以下方式使用 OpenCV 来实现

import cv2
import numpy as npimg = cv2.imread('j.png',0)
kernel = np.ones((5,5),np.uint8)
erosion = cv2.erode(img,kernel,iterations = 1)

在上面的代码中,图像的细化取决于内核大小和迭代次数。

Before and After Thinning and Skeletonization. Source: datacamp

在本文中,我们看到了一些基本的和最广泛使用的 预处理 技术,这让我们对 OCR 系统内部发生的事情有了一个基本的了解。下图是 预处理 工作流程的一个例子。

Source: Reference [5]

我希望你已经了解了在 OCR 中 预处理 是如何执行的。

延伸阅读:

part-III中,我们将看到 OCR 系统所使用的分割技术。

快乐学习!!!!

欢迎任何疑问、建议和更正。😃

参考资料:

[1] Shafii,m .,Sid-Ahmed,m .基于轴平行包围盒的倾斜检测和校正。伊达尔* 18,59–71(2015)。https://doi.org/10.1007/s10032-014-0230-y*

[2] Jyotsna,S. Chauhan,E. Sharma 和 A. Doegar,“退化文档图像的二值化技术——综述”, 2016 年第五届可靠性、信息通信技术和优化国际会议(趋势和未来方向)(I rito),诺伊达,2016,第 163–166 页,doi:10.11109/I rito . 20136106

[3] A. Papandreou 和 B. Gatos,“一种基于垂直投影的新型倾斜检测技术”, 2011 年国际文档分析与识别会议,北京,2011,第 384–388 页,doi: 10.1109/ICDAR.2011.85

[4] K. Lin,T. H. Li,S. Liu 和 G. Li,“使用噪声域适应和注意力生成对抗网络的真实照片去噪”, 2019 年 IEEE/CVF 计算机视觉和模式识别研讨会会议(CVPRW) ,美国加利福尼亚州长滩,2019 年,第 1717-1721 页,doi: 10.1109/CVPRW.2019.00221

[5] Choudhary,Amit & Rishi,Rahul & Savita,Ahlawat。(2013).一种新的脱机手写草书字符分割方法。计算机科学。17.88–95.10.1016 年 5 月 13 日

预先训练的语言模型:简化

原文:https://towardsdatascience.com/pre-trained-language-models-simplified-b8ec80c62217?source=collection_archive---------18-----------------------

NLP 世界的芝麻街

什么是预训练语言模型?

预先训练的语言模型背后的直觉是创建一个黑盒,让理解该语言,然后可以要求它用该语言做任何特定的任务。这个想法是创造一个相当于“博学”的人的机器。

语言模型首先被输入大量未标注的数据(例如,完整的维基百科转储)。这让模型学习各种单词的用法以及语言的一般写法。该模型现在被转移到 NLP 任务,在那里它被馈送给另一个更小的特定于任务的数据集,该数据集用于微调和创建能够执行前述任务的最终模型。

为什么它们比以任务为中心的模型更好?

一句话:他们更好读!!仅在特定于任务的数据集上训练的模型需要使用相对较小的数据集来理解语言和任务。另一方面,语言模型已经理解该语言,因为它在预训练期间已经“读取”了大量的语言转储。因此,语言模型可以直接微调自身以匹配所需的任务,并且比现有的 SOTA 执行得更好。

嵌入与微调

NLP 中的每个单词都需要用数学表示,以便让机器做进一步的处理。你可以通过我之前关于分布式向量表示的博客获得更多的直觉…

[## 分布式矢量表示:简化

可以说是机器学习中最基本的特征表示方法

towardsdatascience.com](/distributed-vector-representation-simplified-55bd2965333e)

已经提出了许多不同的算法来创建这些嵌入,通过在单独的较大数据集上预先训练模型来捕捉语言的本质。例如,Word2Vec 嵌入获得了难以置信的流行,这些嵌入直接用于 NLP 中的许多任务。

然而,这些单词表征是在广义的上下文中学习的,并不代表特定任务的信息。这就是语言模型的微调部分要考虑的地方。直接使用预先训练的嵌入可以减小整个模型的大小,但是迫使我们只能使用一般化的单词表示。另一方面,语言模型微调允许用户通过在特定于任务的数据集上进行训练来微调这些单词嵌入/表示。

例如,在广义的上下文中,单词“当前”的表示可能与“新闻”和“电”都有良好的关系。然而,对于涉及电路的特定任务,允许模型微调单词表示,使得“电流”和“电”更好地匹配,可以帮助提高模型性能。

伯特

BERT(变形金刚的双向编码器表示)是由 Google 在去年提出的,它能够单枪匹马地在 11 个独立的 NLP 任务上实现 SOTA 性能!!从那以后,它成为了多种语言模型的来源。

该模型的成功主要归功于文中提出的训练方法。这两个训练协议,即“掩蔽 LM”和“NSP:下一句预测”(后来被改进为“SOP:句序预测”),帮助 BERT 从可用的庞大语言语料库中学习。

“屏蔽 LM”任务是通过在每个句子中随机屏蔽 15%的单词并训练模型来预测它们来实现的。“SOP”任务是具有两个句子输入的分类任务,并且该模型被期望识别这两个句子之间的原始顺序,这增加了它的文档级理解。这些训练任务产生的影响和 BERT 的内部工作需要更详细的分析,我现在不打算深入讨论。

下一步是什么?

虽然语言模型的不同变体开始主导各种 NLP 任务,但每个人都开始意识到两个重要的事实。第一,尽管最初的伯特模型有正确的想法,但它缺乏训练,因此具有难以置信的未开发的潜力。第二,像 BERT 这样的预训练语言模型的巨大规模是这些模型未来研究和部署的一大障碍。看来这两个主要问题需要在这个领域向前发展之前得到回答。

这个博客是努力创建机器学习领域简化介绍的一部分。点击此处查看完整系列

[## 机器学习:简化

在你一头扎进去之前就知道了

towardsdatascience.com](/machine-learning-simplified-1fe22fec0fac)

或者干脆阅读系列的下一篇博客

[## 卷积核的类型:简化

对迷人的 CNN 层的不同变化的直观介绍

towardsdatascience.com](/types-of-convolution-kernels-simplified-f040cb307c37)

参考

[1]瓦斯瓦尼、阿希什等,“你所需要的只是关注。”神经信息处理系统进展。2017.
【2】Devlin,Jacob 等《Bert:语言理解的深度双向变换器预训练》。arXiv 预印本 arXiv:1810.04805 (2018)。
[3]彼得斯、马修·e 等,“深度语境化的词语表征”arXiv 预印本 arXiv:1802.05365 (2018)。
[4] Mikolov,Tomas 等,“单词和短语的分布式表示及其组合性”神经信息处理系统进展。2013.
[5]刘,,等.“Roberta:一种鲁棒优化的 bert 预训练方法”arXiv 预印本 arXiv:1907.11692 (2019)。

预训练单词嵌入还是嵌入层?—进退两难

原文:https://towardsdatascience.com/pre-trained-word-embeddings-or-embedding-layer-a-dilemma-8406959fd76c?source=collection_archive---------5-----------------------

预训练单词嵌入和嵌入层对语义 NLP 任务绩效影响的比较

当单词嵌入在近十年前变得可用时,它们永远地改变了自然语言处理(NLP)。正如 Ronan Colobert 等人在他们 2008 年著名的 JMLR 论文中所说,他们“几乎从零开始”重新开发了 NLP。几年后,在 2013 年,随着 Mikolov 等人(2013)word2vec 库的发布,它们迅速成为文本数据矢量化的主导方法。基于传统矢量化方法(如 LSI 和 TF-IDF)已经得到充分研究的 NLP 模型正在接受单词嵌入的测试,在大多数情况下,单词嵌入处于领先地位。此后,开发了许多嵌入方法。此外,主要的深度学习(DL)包包含一个学习特定任务嵌入的嵌入层。

虽然我找到了一些比较不同类型的预训练单词嵌入的性能的研究,但我找不到任何将预训练单词嵌入的性能与嵌入层的性能进行比较的综合研究。然而,当我开始为 NLP 实现一个新的 DL 模型时,这是我问自己的第一个问题。为了回答这个问题,我进行了几个实验,以便比较预训练的单词嵌入和嵌入层对 DL 模型在两个语义任务(即情感分析和问题分类)上的性能的影响。但是首先,让我们回顾一下单词嵌入背后的基本思想,并简要回顾一下它们的历史。

根据一个人交的朋友来判断这个人

为什么单词嵌入比传统的单词向量更好,这超出了本文的范围。然而,它们是基于 20 世纪 50 年代发明的丰富的语言学理论,叫做分布假设。该理论通过观察上下文来定义一个词的语义。

“从一个人交的朋友,你就可以知道这个人是谁。”约翰·r·弗斯(20 世纪语言学的领军人物)

林(1998 )用一个很少见的专有名词 T ezgüino 来说明这个概念,很多人都不熟悉。然而,只要我们在上下文中看到它,就很容易推断出来:“桌子上有一瓶 T ezgüino 。Tezgü ino 让你醉了。换句话说,即使你不知道tez guino的意思,它的上下文也阐明了它的意思。

单词嵌入简史

传统上,提出了许多基于统计和规则的模型,试图对分布语义进行建模。主要地,为语料库中出现的所有单词创建共现矩阵,然后使用诸如 LSA 的方法将该矩阵投影到小得多的空间中。传统模型的详细讨论超出了本文的范围,但是,它可以为单词和短语的语义以及无监督单词向量的底层概念提供有价值的见解。相关工作见 Mitchell 和 Lapata (2008)Katrin Erk (2010 ),以及 Katz 和 Giesbrecht (2006 )。

直到 2003 年,Yoshua Bengio et al. (2003) 研究了学习半监督单词嵌入的想法。在他们的工作中,作者提出了一种神经网络,它学习单词序列的概率函数(语言模型的主要目标),同时学习每个单词的分布表示。他们将输入的权重作为单词嵌入引入神经网络的隐藏层。然而,主要由于 Softmax 输出层,他们的模型在计算上是昂贵的,并且远离实际。该模型的目标是学习一个函数,该函数在给定观察到的单词序列的情况下预测良好的条件概率分布:

网络的架构如下所示:

Figure from Bengio et al. 2003. The proposed architecture for learning word representations.

其中 C(i) 是词汇表中 i^th 单词的表示。输出层会计算每个单词在整个词汇表中的条件概率分布,这导致模型不切实际:

其中 N 是 Softmax 层之前的网络。这个模型的计算复杂性问题由 Collobert 等人(2008) 提出,他们提出了一个使用不同目标函数的卷积神经网络。与 Bengio 等人(2003) 不同,他们主要关心的不是语言建模,而是学习好的单词嵌入。因此,他们有更多的自由来达到这个目标。他们受益于正在学习的单词前后的上下文。尽管该模型产生了(第一个)无监督的单词嵌入,其结合了语义和句法信息,但是它仍然是计算昂贵的。由于之前模型的高计算成本,直到 2013 年 Mikolov 等人(2013) 提出了他们简单而有效的模型,普及了单词嵌入,并开始渗透到 NLP 中。

他们提出了两种计算单词嵌入的架构:连续词袋(CBOW)和跳格(SG)模型。下图说明了 CBOW 模型,其中使用上下文( w_{i-2},w_{i-1},w_{i+1},w_{i+2} )来预测目标单词( w_i ):

The architecture of CBOW word embedding model

另一方面,SG 模型试图从给定的单词中预测上下文。如上所述, Mikolov 等人(2013) 的 CBOW 模型与之前的工作相比具有更简单的架构,从而降低了该模型的计算成本。这个模型的实现( word2vec )促进了跨 NLP 不同领域的单词嵌入的广泛实验。显示使用单词嵌入的实验导致了大多数 NLP 任务的改善(巴隆尼等人(2014)米科洛夫等人(2013)亚兹达尼和波佩斯库-贝利(2013) )。在过去的几年中,提出了许多其他嵌入方法,如手套、ELMO 和多义词嵌入,以及许多其他模型。

那么…嵌入层还是预先训练的单词嵌入?

今天,我们可以通过高效的工具,如 fastText 立即创建特定于语料库的单词嵌入。我们也可以在我们的网络中使用一个嵌入层来训练关于手边问题的嵌入。

然而,每当我必须为一个特定的 NLP 任务建立一个新的模型时,我首先想到的问题之一是我是否应该使用预先训练的单词嵌入或嵌入层。

虽然类似于人工智能中的大多数问题,这个问题可能没有在每个场景中都适用的普遍正确的答案,但在这里,我试图根据经验回答这个问题。

我研究了上述两种嵌入对两种语义 NLP 任务绩效的影响。即情感分析问题分类并提供预训练单词嵌入和嵌入层之间的比较。

模型架构

我创建了一个具有两个卷积层的 CNN,并在下面的实验中使用了这个架构。对于预训练的嵌入实验,我用预训练的嵌入替换这一层的参数,维护索引并冻结这一层,防止它在梯度下降的过程中被更新。

超参数

为了能够仅控制嵌入类型,我修正了以下超参数,并使用完全相同的超参数进行实验:

数据

情感分析:IMDB 数据集
问题分类:TREC 数据集

预训练向量

  1. GloVe ( glove.42B.300d ):在 42B 令牌通用抓取语料库上训练的 300 维向量
  2. fast text WIKI(WIKI-news-300d-1M):在 16B 令牌维基百科 2017 转储上训练的 300 维向量

估价

我用(I)训练损失,(ii)混淆矩阵,(iii)精确度(宏观平均),回忆(宏观平均)和不同类型嵌入的 F1 分数来说明我的发现。

任务 1:情感分析

情感分析(也称为极性检测和观点挖掘),是指识别给定文本的极性或情感。情感通常用积极、消极和中性的标签来量化。我正在用来自 torchtext.datasets 的 IMDB 数据集进行实验,其中情感用 0 和 1 表示。下图显示了不同模型的训练损失和混淆矩阵结果。

嵌入层

Confusion Matrix and Training Loss for the Model with an Embedding Layer

预训练单词嵌入

Confusion Matrix and Training Loss for the Model with Pre-trained IMDB vectors

Confusion Matrix and Training Loss for the Model with Pre-trained Glove vectors

Confusion Matrix and Training Loss for the Model with Pre-trained WIKI vectors

如上所述,与基于嵌入层的模型相比,所有三个预训练的基于嵌入的模型的训练损失衰减得更快。此外,对于类别 1,预训练的基于嵌入的模型的精度始终较高。总精确度和召回率,以及 F1 分数如表 1 所示。如所见,预训练的基于嵌入的模型始终优于基于嵌入层的模型,尽管差距很小。

Table 1. The overall precision, recall, and F1 score for Sentiment Analysis task

任务 2:问题分类

问题分类的任务是给问题分配一个语义类别标签,以便更容易找到问题的答案。我使用了来自 torchtext.datasets 的 TREC 数据集,该数据集包括来自 6 个 TREC 类的问题,即缩写、实体、描述、位置和数值。

下图说明了不同模型的训练损失以及测试集在混淆矩阵方面的性能:

嵌入层

预训练单词嵌入

Confusion Matrix and Training Loss for the Model with Pre-trained TREC vectors

Confusion Matrix and Training Loss for the Model with Pre-trained Glove vectors

Confusion Matrix and Training Loss for the Model with Pre-trained WIKI vectors

基于嵌入层的模型和预训练的基于嵌入的模型的训练损失衰减相对较快并且没有太多波动。没有观察到训练损失之间的显著差异。另一方面,除了在 TREC 问题数据集上训练的嵌入之外,所有基于预训练嵌入的模型的总体精度、召回率和 F1 分数都有所提高。这是意料之中的,因为 TREC 是一个包含简短问题的小数据集,因此,在该数据集上训练的向量可能不会携带太多语义信息。对于其他预训练的基于嵌入的模型,即 Glove 4B 和 fastText WIKI,几个类的性能都有相当大的提高。例如,在 ABBR,正确分类实例的百分比从 82%增加到 92-93%。或 LOC,其中正确分类的实例的百分比从 84%增加到 90-96%。下表列出了不同模型的总体精度、召回率和 F1 值。

在家上课

查看 IMDB 情感分析任务的结果,似乎预先训练的单词嵌入导致更快的训练和更低的最终训练损失。可以解释为,与通过嵌入层从训练数据中提取的语义信号相比,该模型可以从预训练的嵌入中提取更多的语义信号。

我们从 TREC 问题分类任务的结果中看到,在小语料库上训练的 向量将比嵌入层具有更差的性能。然而,就精确度和召回率而言,在大型语料库上训练的向量以相当大的优势击败了嵌入层。

一致 对于两个任务,当我们使用预训练的单词嵌入(在足够大的语料库上训练)时, 精度和召回率提高。然而,对于情感分析任务,这种改善很小,而对于句子分类任务,这种改善要大得多。

这可能意味着对于解决 语义 NLP 任务,当手头的训练集足够大时(如情感分析实验中的情况),最好使用预训练的单词嵌入。尽管如此,出于任何原因,你仍然可以使用一个嵌入层,并期待可比较的结果。然而,训练集很小,上述实验强烈鼓励使用预先训练的单词嵌入。

最终注释

与可根据实数值直接获得并因此可由机器学习模型直接解释的图像数据不同,文本数据需要被转换成一种表示,使得每个单词所承载的广泛知识的至少一部分可被带入这种表示。只有到那时,我们才能期望拥有在某种程度上理解人类语言的智能模型。学习词语的语义表征有着相对悠久的历史,并且一直是语言学和自然语言处理的核心研究课题。单词嵌入提供了一种表示单词的有效方式,然而,它们当前的能力在捕获每个单词所承载的语义、句法和搭配信息方面是有限的。这意味着在我们能够开发出能够理解和生成文本级别的自然语言的智能系统之前,在表示文本数据方面仍有很大的改进空间。

用云 TPU 从头开始预训练伯特

原文:https://towardsdatascience.com/pre-training-bert-from-scratch-with-cloud-tpu-6e2f71028379?source=collection_archive---------1-----------------------

Google Cloud TPUv2

在这个实验中,我们将使用谷歌云基础设施对任意文本数据预先训练一个最先进的自然语言理解模型 BERT

本指南涵盖程序的所有阶段,包括:

  1. 设置培训环境
  2. 下载原始文本数据
  3. 预处理文本数据
  4. 学习新词汇
  5. 创建分片的预训练数据
  6. 为数据和模型设置 GCS 存储
  7. 在云 TPU 上训练模型

几个问题

这本指南有什么用?

有了这个指南,你将能够在任意文本数据上训练一个 BERT 模型。如果您的语言或用例的预训练模型在开源中不可用,这将非常有用。

这本指南是给谁的?

本指南面向那些对 BERT 技术感到兴奋,但对现有的
开源模型的性能不满意的 NLP 研究人员。

我如何开始?

对于训练数据和模型的持久存储,您将需要一个 Google 云存储桶。请按照谷歌云 TPU 快速入门创建一个 GCP 帐户和 GCS 桶。新的谷歌云用户可以获得 300 美元的免费积分来开始使用任何 GCP 产品。

出于演示目的,本教程的步骤 1-5 可以在没有 GCS 铲斗的情况下运行。但是,在这种情况下,您将无法训练该模型。

需要什么?

在 TPUv2 上对基于 BERT 的模型进行预训练需要大约 54 个小时。Google Colab 不是为执行这种长时间运行的作业而设计的,大约每 8 小时就会中断一次训练过程。对于不间断的训练,考虑使用付费的可抢占的 TPUv2 实例。

也就是说,在撰写本文时(2019 年 5 月 9 日),使用 Colab TPU,可以从零开始预先训练 BERT 模型,而将所述模型和数据存储在 GCS 中的成本可以忽略不计(约 1 美元)。

我如何跟随指南?

下面的代码是 Python 和 Bash 的结合。
它被设计为在 Colab Jupyter 环境中运行。
因此,在那里运行它是最方便的。

但是,除了实际的培训部分,本指南的所有步骤都可以在单独的机器上运行。如果您的数据集太大
(或太私有)而无法在 Colab 环境中进行预处理,这可能会很有用。

好吧,给我看看代码。

给你。

我需要修改代码吗?

您真正需要设置的唯一参数是您的 GCS BUCKET_NAME。其他一切都有默认值,这应该适用于大多数用例。

现在,让我们进入正题。

步骤 1:设置培训环境

首先,我们得到了训练模型所需的包。Jupyter 环境允许使用感叹号“!”直接从笔记本上执行 bash 命令,像这样:

!pip install sentencepiece
!git clone [https://github.com/google-research/bert](https://github.com/google-research/bert)

在整个实验中,我将利用这种方法来使用其他几个 bash 命令。
现在,让我们导入包并在 Google Cloud 中授权我们自己。

Setting up BERT training environment

步骤 2:获取数据

我们继续获取文本数据的语料库。在这个实验中,我们将使用open 字幕数据集,该数据集可用于 65 种语言这里是

与更常用的文本数据集(如维基百科)不同,它不需要任何复杂的预处理。它还预先格式化为每行一个句子,这是进一步处理步骤的要求。

通过设置相应的语言代码,您可以随意使用您的语言的数据集。

Download OPUS data

出于演示的目的,默认情况下,我们将只使用整个语料库的一小部分。

训练真实模型时,请确保取消选中 DEMO_MODE 复选框,以使用 100 倍大的数据集。

请放心,100 米的线完全足以训练一个相当好的 BERT-base 模型。

Truncate dataset

步骤 3:预处理文本

我们下载的原始文本数据包含标点符号、大写字母和非 UTF 符号,我们将在继续之前删除它们。在推理过程中,我们将对新数据应用同样的过程。

如果您的用例需要不同的预处理(例如,如果在推断过程中需要大写字母或标点符号),请随意修改下面的函数以满足您的需求。

Define preprocessing routine

现在让我们预处理整个数据集。

Apply preprocessing

第四步:积累词汇

下一步,我们将学习一个新的词汇来表示我们的数据集。

BERT 论文使用了一个 WordPiece tokenizer,这在 opensource 中是没有的。相反,我们将在单字模式下使用句子片断标记器。虽然它不能直接与 BERT 兼容,但只要稍加修改,我们就能让它工作。

SentencePiece 需要相当多的 RAM,所以在 Colab 的完整数据集上运行它会使内核崩溃。为了避免这种情况,我们将随机对数据集的一部分进行二次抽样,以构建词汇表。另一种选择是使用内存更大的机器来完成这一步——这取决于您。

另外,缺省情况下,SentencePiece 将 BOS 和 EOS 控制符号添加到词汇表中。我们通过将它们的索引设置为-1 来显式禁用它们。

VOC_SIZE 的典型值介于 32000 和 128000 之间。我们保留 NUM_PLACEHOLDERS 标记,以防在预训练阶段结束后,有人想要更新词汇表和微调模型。

Learn SentencePiece vocabulary

现在,让我们看看如何让句子片段为 BERT 模型工作。

下面是一个句子,使用了来自官方回购
预训英语伯特基础模型的词块词汇。

>>> wordpiece.tokenize("Colorless geothermal substations are generating furiously")

['color',
 '##less',
 'geo',
 '##thermal',
 'sub',
 '##station',
 '##s',
 'are',
 'generating',
 'furiously']

正如我们所看到的,单词块标记器将出现在单词中间的子单词添加到带有“##”的单词前面。出现在单词开头的子单词不变。如果子词同时出现在单词的开头和中间,则两个版本(带和不带' ## ')都被添加到词汇表中。

SentencePiece 创建了两个文件:tokenizer.model 和 tokenizer.vocab,我们来看看学过的词汇:

Read the learned SentencePiece vocabulary

这给出了:

Learnt vocab size: 31743 
Sample tokens: ['▁cafe', '▁slippery', 'xious', '▁resonate', '▁terrier', '▁feat', '▁frequencies', 'ainty', '▁punning', 'modern']

正如我们所观察到的,句子成分与单词成分正好相反。来自文档:

句子片断首先用元符号“▁”(u+2581)转义空格,如下所示:

Hello▁World

然后,该文本被分割成小块,例如:

[Hello] [▁Wor] [ld] [.]

出现在空格之后的子词(也是大多数单词开始的地方)被加上'▁',而其他的则保持不变。这不包括只出现在句首而不在其他地方的子词。然而,这些情况应该非常罕见。

因此,为了获得一个类似于 WordPiece 的词汇表,我们需要执行一个简单的转换,从包含“▁”的标记中删除它,并将“##”添加到不包含它的标记中。

我们还添加了一些特殊的控制符号,这些符号是 BERT 体系结构所需要的。按照惯例,我们把它们放在词汇表的开头。

此外,我们将一些占位符标记添加到词汇表中。
如果希望用新的
任务特定的令牌更新预训练的模型,这些是有用的。在这种情况下,占位符标记会被新的真实标记替换,预训练数据会重新生成,并且模型会根据新数据进行微调。

Convert the vocabulary to use for BERT

最后,我们将获得的词汇写入文件。

Dump vocabulary to file

现在让我们看看新词汇在实践中是如何工作的:

>>> testcase = "Colorless geothermal substations are generating furiously"
>>> bert_tokenizer = tokenization.FullTokenizer(VOC_FNAME)
>>> bert_tokenizer.tokenize(testcase)['color',  
 '##less',  
 'geo',  
 '##ther',  
 '##mal',  
 'sub',  
 '##station',  
 '##s',  
 'are',  
 'generat',  
 '##ing',  
 'furious',  
 '##ly']

看起来不错!

步骤 5:生成训练前数据

有了这些词汇,我们就可以为 BERT 模型生成预训练数据了。
由于我们的数据集可能相当大,我们将把它分成若干片段:

Split the dataset

现在,对于每个碎片,我们需要从 BERT repo 中调用create _ pre training _ data . py脚本。为此,我们将使用 xargs 命令。

在开始生成之前,我们需要设置一些参数传递给脚本。你可以在自述中找到更多关于它们的含义。

Define parameters for pre-training data

根据数据集的大小,运行此操作可能需要相当长的时间。

Create pre-training data

步骤 6:设置持久存储

为了保护我们来之不易的资产,我们将把它们保存到谷歌云存储中。假设您已经创建了 GCS bucket,这应该很容易。

我们将在 GCS 中创建两个目录,一个用于数据,一个用于模型。在模型目录中,我们将放入模型词汇表和配置文件。

在继续之前,请在此配置 BUCKET_NAME 变量,否则您将无法训练模型。

Configure GCS bucket name

下面是 BERT-base 的超参数配置示例。更改风险自担!

Configure BERT hyperparameters and save to disk

现在,我们准备将我们的资产推向 GCS

Upload assets to GCS

步骤 7:训练模型

我们几乎准备好开始训练我们的模型。

请注意,前面步骤中的一些参数在此处重复,以便方便地重新开始训练程序。

确保参数设置在整个实验中完全相同。

Configure training run

准备训练运行配置,构建估计器和
输入函数,启动低音加农炮。

Build estimator model and input function

执行!

Execute BERT training procedure

使用 100 万步
的默认参数训练模型需要大约 54 小时的运行时间。万一内核因为某种原因重启,你可以从最近的检查点继续训练。

这就结束了在云 TPU 上从头开始预训练 BERT 的指南。

后续步骤

好了,我们已经训练好了模型,现在做什么?

这是一个全新讨论的话题。你可以做几件事:

  1. 将预训练模型用作通用 NLU 模块
  2. 针对某些特定的分类任务对模型进行微调
  3. 使用 BERT 作为构建块创建另一个 DL 模型
  4. ???

真正有趣的事情还在后面,所以保持清醒。与此同时,查看令人敬畏的 bert-as-a-service 项目,并开始在生产中为您新培训的模型提供服务。

继续学习!

本系列中的其他指南

  1. 用云 TPU 从头开始预训练 BERT【你在这里】
  2. 用 BERT 和 Tensorflow 构建搜索引擎
  3. 用 Keras 和 tf 微调 BERT。模块
  4. 使用 BERT 和表征学习改进句子嵌入

精确度和召回率

原文:https://towardsdatascience.com/precision-and-recall-1af0a478fc7e?source=collection_archive---------20-----------------------

Photo by Hush Naidoo on Unsplash

假设我想建立一个系统来判断一个人是否患有肺结核。我们收集了包括 50,000 幅图像的数据集。这些图像中有 49.950 个是反面例子,其余的是正面例子。

在我们的数据集中,只有 50 人患有肺结核,而我们有 50,000 张图像。(50/50.000)** 100= 0.1%*都是肺结核。我们称之为不平衡数据集。即使没有构建深度学习算法,通过一个简单的语句,也可以达到大约%99 的准确率。也就是说,你希望你的算法预测某人是结核病,而我们不知道我们的算法是否能正确预测某人是否是结核病。我们只知道准确性,仅此而已!在这种情况下,您可以使用精度和召回评估指标。别担心,我也会提到 F1 的分数。

深入研究指标之前的一些关键词

正的;
实际:肺结核|预测:肺结核

TrueNnegative;
实际:非肺结核|预测:非肺结核

阳性:实际:非结核病,预测:非结核病

阴性:实际:不是肺结核,预测:不是肺结核

Confusion Matrix

精确

我们预测的肺结核患者中有多少是真正的肺结核?让我们看看混淆矩阵,看看我们的算法在哪里说某人是结核病(蓝色矩形),什么是实际的(红色矩形)。

Precision

我们可以像上面这样写下来。

回忆

我们正确预测了多少肺结核患者?让我们看看混淆矩阵,看看我们的算法在哪里正确地检测到患有结核病的人(橙色矩形)和所有实际患有结核病的人(蓝色矩形)。

Recall

F1 分数

要说某人是不是肺结核,我们要确保我们的算法做得非常好。判断算法是否有效的一个方法是,我们可以看看精度和召回率。在精确度和召回率之间有一个权衡。

你有 5 种算法,每一种都有不同精度和召回率。我们如何判断哪种算法更好?在这种情况下,F1 分数开始起作用。所以我们可以用 F1 的分数来决定,是哪个算法在做。

F1 score

感谢您的阅读!我希望你喜欢它。请关注即将推出的 Python 和深度学习部分。别忘了关注我的 GitHub

精确度和召回率—一个简化的视图

原文:https://towardsdatascience.com/precision-and-recall-a-simplified-view-bc25978d81e6?source=collection_archive---------8-----------------------

理解精确度和召回率对于完善任何机器学习模型都是至关重要的。这是微调模型以产生准确结果所需的技能。少数型号需要更高的精度,而少数型号可能需要更高的召回率。在文章的最后,我们还将讨论我们需要什么。这篇文章旨在减少苹果和橘子的精确度和召回率。我是认真的,所以让我们举一个苹果分类器的例子。其主要目的是对苹果和橘子进行分类。

假设有一个巨大的农场,里面种满了苹果树和橘子树。农场的主人想要建立一个能够正确预测苹果和橙子的分类器,这样他就可以对它们进行分类并出售。此外,与橙子相比,苹果的价格要高得多,因此他特别希望构建一个能够检测苹果的分类器。在这一过程中,他建造了一个能够对苹果和橘子进行分类的探测器,并随机抽取了 13 种水果样本进行分类。因为他更专注于预测苹果(因为它们很贵),该模型将苹果归类为阳性,将橙子归类为阴性。

他制作了如下图表来检查模型的表现:

confusion matrix

真正的肯定:这些是模型正确预测的苹果。

误报:有模型预测为苹果的橙子。

假阴性:有模型预测为橙子的苹果。

真正的否定:有模型正确预测的橙子。

从图表中我们可以得出以下推论:

  1. 模型将两个橙子归类为苹果
  2. 模型把 3 个苹果归类为橘子
  3. 模型正确地分类了 5 个苹果
  4. 模型正确地分类了三个橙子

让我们修改图表,以理解精确度并正确回忆

modified confusion matrix

图像的左侧包含实际苹果的预测结果。图像的右侧包含实际橙子的预测结果。

上图帮助我们从不同的角度了解模型的预测:

  1. 在它归类为苹果的 8 个价值中,只有 5 个是真正的苹果,3 个是橙子。
  2. 在它归类为橙子的 5 个值中,只有 2 个是真正的橙子,3 个是苹果。

现在,让我们深入精确地回忆一下。关于上图。

精确

precision

这是模型做出的正确预测的数量。简单来说,就是:

模型预测正确的苹果数量/模型预测正确的苹果和橙子数量

它不考虑模型所做的错误预测。

精度公式:

真阳性数/(真阳性数+假阳性数)

苹果预测值的精度:5/(5+2) = 5/7 = 0.714

回忆

recall

它是模型对当前正值总数做出的正确预测的数量。简单来说,就是:

模型正确预测的苹果数量/苹果总数

苹果总数是发送到系统的苹果数量,即 8 个。

它考虑了模型做出的错误预测。召回的公式:

真阳性数/(假阴性数+真阳性数)

对于上面的例子,它是:5/(5+3) = 5/8 = 0.625

所以,我们知道农场主人创建的模型精度高,但召回率低!

什么时候我们需要高精度或者高召回率?

当您需要对输出敏感的预测时,模型需要高召回率。例如,预测癌症或预测恐怖分子需要高召回率,换句话说,你也需要覆盖假阴性。如果非癌性肿瘤被标记为癌性,这是可以的,但是癌性肿瘤不应该被标记为非癌性的。

同样,在推荐引擎、垃圾邮件检测等地方,我们也需要高精度。你不在乎假阴性,而是更关注真阳性和假阳性。如果垃圾邮件进入收件箱文件夹是可以的,但是真正重要的邮件不应该进入垃圾邮件文件夹。

关于开头提到的例子,为了使农场主人的利润最大化,他不应该让苹果被误归类为橘子。所以,模型需要苹果的高召回率。

如何调优一个机器学习模型来调整到高精度或者召回?

如果它是一个神经网络,分配一个适当的损失函数,该函数对变化敏感,不会对值进行不必要的舍入。主要关键是您分配给神经网络最后一层的阈值。如果是二元分类的情况,阈值需要以最大化召回或精确度的方式设置,无论哪种需要。如果您想最大化召回率,请将阈值设置为低于 0.5,即大约 0.2。比如大于 0.3 是苹果,0.1 不是苹果。这样会增加系统的召回。为了精确起见,可以将阈值设置为更高的值,例如 0.6 或 0.7。这样你就可以调整神经网络的精确度和召回率。

如果是任何其他机器学习模型,您需要调整超参数和概率阈值,以实现更高的精度或召回率。

感谢您的阅读!☺

制造业中的精确度和召回率:案例研究

原文:https://towardsdatascience.com/precision-and-recall-in-manufacturing-a-case-study-4cbb4c352bcd?source=collection_archive---------19-----------------------

A Fully Automated Assembly Line

任何即将开始机器学习项目的组织都会很快遇到定义适当的评估指标的问题。不幸的是(或者幸运的是,取决于你的观点),有大量的度量标准;从常见的准确度、精确度、召回率、f1 分数到不太为人所知的马修斯相关系数。

选择正确的评估指标与选择正确的算法同样重要。

我参加过太多会议,在这些会议上,这种度量标准成为每个人困惑的来源。在本文中,我想讨论其中的两个:精确度和召回率,使用制造业的一个案例研究。

自动测试设备

除非你在制造业工作过,否则你很可能不熟悉自动测试设备。但即便如此,你也可能间接受益于它们。不相信我?如果你正在电子设备上阅读这篇文章,那就是了;该设备很可能已经在工厂中由这样的设备进行了自动测试,如下图所示。这台机器确保你的设备可以安全使用。

A sample circuit board tester machine

然而在现实中,并不是工厂生产线上的所有测试都是自动完成的。案例研究是这样的:假设我们正在开始一个新的概念验证实验,来自动化生产线上的一个测试系统。这种新的自动测试设备的工作是检查产品,通常被称为被测设备(DUT),并决定它是通过(质量良好)还是失败(有缺陷)。在这种情况下,我们假设自动测试设备已经装载了某种算法。

算法要求

让我们假设我们的算法有以下要求。

  1. 让不良 dut 进入下一个生产线的成本很高,例如,我们不能容忍逃亡者。我们希望避免任何受伤的 DUT 成为下游的退货授权。约束:最小化风险。
  2. 尽管如此,我们也不希望算法在每个 DUT 都失败。这是因为任何失败的 DUT 通常将由人类技术人员手动重新检查。为了使算法成功,它必须减少人工检查的次数。目标:储蓄最大化。

因此,我们收集了 1000 个样本 dut(900 个好的,100 个坏的)。然后,我们用设备的算法对所有 1000 个 dut 进行分类。为了衡量后者的性能,我们使用 python scikit-learn 的分类报告

**from** **sklearn.metrics** **import** classification_report# 'labels' is an array of 1000 groundtruth labels
# 'preds' is an array of 1000 outputs from the algorithm  
print(classification_report(labels, preds, target_names=["fail", "pass"]))

Sample Classification Report from Scikit-Learn

那么,我们该如何解读这份报告呢?要记住的关键是,召回率和精确度都衡量条件精确度。它们的区别在于衡量绩效的条件。这种说法现在可能没有意义,但希望在阅读完本文的其余部分后,您可以再次重温这一点。

回忆:以基础事实标签为条件

请记住,在实验的最开始,我们选择了 1000 个 dut,其中 900 个是好的(通过),100 个是差的(失败)。我们可以计算每个标签的召回,例如一次召回合格的和一次召回不合格的

召回通过回答了这个问题:在我测试过的 900 个良好/通过 dut 中,有多少被算法准确分类?在我们的报告示例中,我们看到 pass 的召回率是 0.67(或 67%)。这意味着 900 个通过测试的 dut 中有 67%也被正确分类为通过。

Visualizing Recall with a Tree

同样的逻辑可以通过遍历上面的决策树来解释。在根部,我们有一个装有 1000 个 dut 的盒子。然后,盒子被分成 2 个子节点(一个盒子用于 100 个坏的/失败的 dut,另一个盒子用于 900 个通过的 dut)。这种分裂对应于地面真相标签。我们可以进一步将 900 个通过的 dut 分成两个框,一个框用于算法标记为失败的 dut(300 个 dut),另一个框用于被认为通过的 dut(600 个 dut)。我们看到,对于通过测试的 dut 子集,该算法的精确度仅为 600 / 900 ~= 0.67 或 67%。

召回失败查看树的另一边,回答这个问题:在 100 个失败的 dut 中,算法设法捕获了多少个?在这种情况下,只有 80 / 100 = 0.8 或其中的 80%被正确分类。

我们可以使用召回来量化我们在多大程度上满足了前面列出的两个产品要求。

  1. 最小化逃脱者的风险相当于最大化失败召回
  2. 最大化节省等同于最大化通过的召回。

精度:以算法标签为条件

现在假设我们已经在生产中部署了该算法。最有可能的是,我们的客户( OEM / CM )会开始询问他们是否可以信任这个新系统。精确可以用来回答这样的问题。

Visualizing Precision with Recall

通过精度回答了这个问题:如果算法通过了 DUT,我们能在多大程度上信任这个决定?换句话说,从常客的角度来看,如果算法通过了 620 个 dut,其中有多少是真正好的?在我们的例子中,600/620 ~= 0.97 或 97%的时间判断是正确的。

同样,我们可以通过遍历树得到相同的数字。唯一的区别是,我们根据算法的判断而不是地面真实标签来分割根节点(1000 个 dut)。

通过使用相同的方法,我们得到了失败的精度为 80 / 380 ~= 21%。这意味着,当算法说 DUT 是坏的,只有 21%的时间是正确的。

这告诉我们,这种算法在识别好的 dut(通过)方面是有用的,但在识别坏的 dut(失败)方面不是有用的。

结论

总之,我们需要在以下方面改进算法:

  1. 改进召回故障以防止逃逸
  2. 提高对通行证的召回,以增加成本节约,从而缩短投资回收期(如果我们要向客户销售该产品,我们最好确保投资回报的计算是合理的)。
  3. 保持/提高通过的精度,这样我们可以确保模型在通过 DUT 时非常自信

我们关于精度和召回率的讨论到此结束。请记住,这 4 个数字不是独立的,即增加一个数字会减少其他数字(根据贝叶斯法则,查看附录以了解精确度与回忆的关系)。因此,简单地偏置算法的决策边界是行不通的。人们可以尝试摆弄算法的超参数,或者尝试一种完全不同的架构。这最好留给另一个讨论话题。

因此,我希望这个小案例研究能让我们从制造分析的角度更好地了解每个指标所传达的信息。

附录:用贝叶斯法则连接精确度和召回率

用条件概率定义召回率和精确度

应用贝叶斯规则找出特定类别(失败)的召回率和精确度之间的关系

替换案例研究中的值来证明等号。

精确度和召回率的权衡和多重假设检验

原文:https://towardsdatascience.com/precision-and-recall-trade-off-and-multiple-hypothesis-testing-family-wise-error-rate-vs-false-71a85057ca2b?source=collection_archive---------13-----------------------

现实世界中的数据科学

家族错误率(FWE)与错误发现率(FDR)

在我之前作为天体物理学家的工作中,我致力于探测释放出惊人能量的大恒星 (GRBs)的爆炸。我们的自动分析管道到处搜寻伽玛暴,因为无法预测何时何地会发生这样的爆炸。这提出了一个重要的挑战,与我们检测过程的统计性质有关:多重假设检验。

Left: a map of the sky in Gamma-Rays showing the position of the stellar explosions (GRBs) detected by the Fermi Space Telescope. The map is in Galactic coordinates, and the bright stripe going across the map from left to right corresponds to the cosmic dust contained in the Galactic plane of our Milky Way galaxy and glowing in gamma-rays. Right: an animation of our current picture of how a GRB might look like if seen up close. Credits: NASA (reproduced according to NASA Media Usage guidelines from https://nasa.tumblr.com/post/176492220069/gamma-ray-bursts-black-hole-birth-announcements).

在我目前作为数据科学家的工作中也可以发现同样的挑战,例如在多个数据集中搜索异常或执行许多 A/B 测试时。

让我们从基础开始,说明多重测试意味着什么。

注意:这篇文章在我的 GitHub 库中作为一个可运行的 Jupyter 笔记本提供。我在整篇文章中使用的函数和类可以在文章结尾的代码单元中找到。

在这篇文章中,我假设大家熟悉一些概念,特别是统计检验、零假设与替代假设、p 值、I 型错误和 II 型错误

粗略地说,统计检验能够拒绝或不拒绝具有给定的 I 型错误概率α(假阳性的概率)和 II 型错误概率β(假阴性的概率)的零假设。

我们的玩具问题

让我们考虑确定两个总体是否具有相同平均值的问题。零假设是平均值是相同的,另一个假设是平均值不相同。

请注意,您可以用任何其他可以通过统计测试回答的问题来代替这个问题,这里的所有讨论仍然成立(但是当然您需要重新编写代码)。

简单的例子:一个测试

最适合手头问题的测试是学生的 T-test 。让我们编写一个计算 p 值的函数和一个根据 p 值决定是否拒绝空值的函数:

# Let's write so that w1 and w2 can be lists of n
# datasets, from 1 to as much as needed

**def** apply_ttest(w1, w2):

    ts, pvalues = scipy.stats.ttest_ind(w1, w2, axis=1)

    # np.squeeze is necessary so we can use this
    # function both for single and for multiple tests

    **return** np.squeeze(pvalues)

**def** null_hyp_status(pvalue, alpha):

    # We write it using np.all so we can use the same function
    # for both single and multiple tests

    **return** np.all(pvalue > alpha)

我们现在生成一个合成数据集,将零假设设置为真,然后进行测试。我们将使用类型 I 错误概率α=0.05:

# Let's get a dataset with 1 group and
# the null hypothesis is true

w1, w2, ground_truth = generate_dataset(n_datasets=1, 
                             n_null_true=1)
# Let's now apply the test
alpha = 0.05
pvalue = apply_ttest(w1, w2)

**if** null_hyp_status(pvalue, alpha) **is** True:

    **print**("We do not reject the null hyp.")

**else**:

    **print**("We reject the null hyp.")

> We do **not** reject the null hyp.

测试如预期的那样工作,没有拒绝零假设(我们知道这是真的)。让我们验证测试的性能是名义上的,也就是说,通过重复相同实验的大量独立实现,我们偶然拒绝了名义 I 型错误概率为α的零假设:

# Let's perform 5000 independent simulations
type_I_error_p = measure_rejection_prob(5000, 
                                        apply_ttest, 
                                        null_hyp_status,
                                        alpha, 
                                        n_datasets=1,
                                        n_null_true=1)

**print**("\nMeasured chance probability of rejecting the "
      "null: %.3f (should be %.3f)" % (type_I_error_p, alpha))

> 5000 out of 5000 completed (fraction of rejections so far: 0.05)
> Measured chance probability of rejecting the null: 0.05 (should be 0.050)

好了,果然有效。当然,如果你运行它,你可能会得到一个稍微不同的值,因为生成过程是随机的,但是它应该接近 0.05。

多重测试

现在让我们假设我们有 m 对群体,我们想要找出是否一对或多对群体之间有显著差异。

这里的零假设是“在所有对中,两个群体具有相同的平均值”,另一个是“至少有一个对中两个群体的平均值不同。”

我们能不能只对每一对分别进行测试,看看是否至少有一对被拒绝?(剧透:答案是否定的!还有,我们忽略一个事实,就是还有其他针对这种情况设计的测试)。让我们看看:

# Generate m=50 pairs of populations, all with the same
# average between the populations (the null hypothesis is true)
w1, w2, _ = generate_dataset(n_datasets=50, n_null_true=50)
pvalues = apply_ttest(w1, w2)

**if** null_hyp_status(pvalues, alpha) **is** True:

    **print**("We do not reject the null hyp.")

**else**:

    **print**("We reject the null hyp.")

> We reject the null hyp.

起初,这个结果可能会令人惊讶。毕竟我们知道零假设是真的!

然而,如果你还记得类型 I 错误概率的定义,通过固定α=0.05,我们设置了测试,这样它将错误地以 5%的概率拒绝空值。因此,通过重复测试 50 次(每对一次),我们每次都有 5%的机会出现 I 型错误。因此,至少有一次拒绝的概率由二项式分布给出:

# probability of having one or more rejections in 50 trials
m = 50
binomial_distr = scipy.stats.binom(m, alpha)

# NOTE: the .sf method gives the probability of obtaining > 1,
# while we need >= 1, so we add the pmf at 1
prob = binomial_distr.sf(1) + binomial_distr.pmf(1)

**print**("The prob. of >= 1 false positives in %i "
      "trials is %.3f" % (m, prob))

> The prob. of >= 1 false positives **in** 50 trials **is** 0.923

在我们的设置中有超过 90%的机会得到至少一个假阳性。在同一个问题中多次测试一个假设被称为“多重测试”,需要更多的思考。

邦费罗尼/西达克校正

Bonferroni (1936)对这种情况引入了一个简单的修正。处方是用修正的 I 型错误概率代替复合试验中的每一个独立试验的α,该概率由西达克公式α′=1−(1−α)^(1/m给出(对于大的m通常近似为α′=α/m)

有时,在文献中,校正α′=α/m 被称为“邦费罗尼校正”,而校正α′=1−(1−α)^(1/m 被称为“西达克校正”在这里,我们将使用后一种表述方式,但是可以互换使用这个名称,因为在所有实际应用中,这种差异非常小

西达克公式的理由可以很容易地推导出来,它是我们刚刚在二项分布中发现的观察的直接结果。在概率为α’的 m 次试验中获得 1 次或多次成功的概率α由 1b(mp=α’,k=0)给出,其中 B( mp=α’,k=0)是由二项式分布给出的获得 0 次成功的概率。

我们有:

在这里,我们刚刚替换了 k = 0。通过求解α’,我们获得了需要在每个 m 测试中使用的 I 型误差概率,以获得α的全局 I 型误差概率,这就是 Bonferroni/Sidak 校正。

注意:我们假设独立测试。如果不同的测试之间存在相关性,这里介绍的方法可能适用,也可能不适用,您需要仔细查看相关的论文。

让我们看看这是否能解决我们的问题。我们只需要改变用于决定是否拒绝空值的标准。不需要改变 p 值的计算:

# Test if any of the pvalues is lower than alpha',
# if the answer yes, the null hyp. is deemed False

**def** null_hyp_status_bonferroni(pvalues, alpha):

    # Number of tests
    m = pvalues.shape[0]

    # Bonferroni/Sidak correction
    alpha_prime = 1 - (1-alpha)**(1.0/m)

    # Test whether *all* null hypothesis in the subtests are
    # true or not
    **return** np.all(pvalues > alpha_prime)

w1, w2, _ = generate_dataset(n_datasets=50, n_null_true=50)
pvalues = apply_ttest(w1, w2)

**if** null_hyp_status_bonferroni(pvalues, alpha) **is** True:

    **print**("We do not reject the null hyp.")

**else**:

    **print**("We reject the null hyp.")

> We do **not** reject the null hyp.

看起来好多了。为了确保这一点,让我们生成许多合成数据集,看看我们的 Bonferroni 校正测试是否提供了名义 I 型错误概率α=0.05。

# Let's do again 5000 realization of datasets with 50
# pairs where the null is true for all pairs,
# and study the performance of the new procedure

type_I_error_p = measure_rejection_prob(5000, 
                                        apply_ttest, 
                                        null_hyp_status_bonferroni,
                                        alpha, 
                                        n_datasets=50,
                                        n_null_true=50)

**print**("\nMeasured chance probability of rejecting the "
      "null: %.3f (should be %.3f)" % (type_I_error_p, alpha))

> 5000 out of 5000 completed (fraction of rejections so far: 0.05)
> Measured chance probability of rejecting the null: 0.047 (should be 0.050)

成功了。第一类错误概率确实非常接近名义上的 5%。

类 Bonferroni 修正的问题:全局与局部假说

到目前为止,我们一直在处理这个问题的“全局”零假设“是否存在两个群体的平均值不同的配对?”无效假设是所有对在群体之间具有相同的平均值,或者是至少有一个不具有相同的平均值。

然而,我们经常对另一个问题感兴趣:“找出平均值不同的所有配对”。或者,“找到所有恒星爆炸”,就像我的天体物理学问题一样。在第二种情况下,每一对都有自己的无效假设和替代假设,我们感兴趣的是有多少无效假设被拒绝。

很明显,Bonferroni 校正将仍然保证偶然拒绝一个或多个零值的全局αI 型误差概率,但是它为了这样做而惩罚所有测试,因为每个测试的α由 Sidak 公式和 1−(1−α)^(1/ m ) < α给定,用于 m > 1。

此外,随着 m 的增长,全局无效假设仍然以相同的 I 型错误概率进行测试,但是 m 无效假设中的每一个都得到越来越严格的测试,并且随着 m →∞我们有α′→0,因此很难找到与无效假设的任何偏差。换句话说,“看的越多,发现的越少。”

让我们通过考虑一个单一测试的类型 II 错误来说明这一点,即而不是在我们应该拒绝空值时拒绝空值的概率。首先,让我们生成并测试一个空值为假的对:

*# Let's get a dataset with 1 group and
# the null hypothesis is False

w1, w2, ground_truth = generate_dataset(n_datasets=1, n_null_true=0)

# Let's now apply the test
alpha = 0.05
pvalue = apply_ttest(w1, w2)

**if** null_hyp_status(pvalue, alpha) **is** True:

    **print**("We do not reject the null hyp.")

**else**:

    **print**("We reject the null hyp.")

> We reject the null hyp.*

我们已经正确地拒绝了无效假设。现在让我们看看,在多次重复相同的实验后,我们有多少次未能拒绝空值,即使它是假的(类型 II 错误概率):

*type_II_error_p = 1 - measure_rejection_prob(5000, 
                                             apply_ttest, 
                                             null_hyp_status,
                                             alpha, 
                                             n_datasets=1,
                                             n_null_true=0)
**print**("\nMeasured chance probability of *not* rejecting the "
      "null: %.3f" % (type_II_error_p))

> 5000 out of 5000 completed (fraction of rejections so far: 0.94)
> Measured chance probability of ***not*** rejecting the null: 0.062*

因此,对于一个测试,我们有大约 6% (β=0.06)的概率而不是拒绝空值,即使它是假的(当然,β取决于α,以及效果的大小——在这种情况下,是两个平均值之间的差异)。

现在,让我们看看如果对 50 对进行 Bonferroni 校正测试会发生什么,其中只有一对的零假设为假:

*type_II_error_p = 1 - measure_rejection_prob(5000, 
                                             apply_ttest, 
                                             null_hyp_status_bonferroni,
                                             alpha, 
                                             n_datasets=50,
                                             n_null_true=49)

**print**("\nMeasured chance probability of *not* rejecting the "
      "null: %.3f" % (type_II_error_p))

> 5000 out of 5000 completed (fraction of rejections so far: 0.59)
> Measured chance probability of ***not*** rejecting the null: 0.410*

现在我们有 41%的概率没有在我们应该拒绝 null 的时候拒绝它。很明显,我们已经失去了很多敏感性,因为一对样本中的差异被 50 对样本所掩盖。

从某种程度上来说,这是不可避免的,也是我们不知道具体去哪里寻找所付出的代价。

然而,当试图测试所有的局部无效假设而不是全局无效假设时,事情会很快失控。为了得到一个概念,让我们制作几个越来越大的数据集,每个数据集有 50 个假零假设,并看看类型 II 误差如何作为配对/测试数量 m 的函数而变化:

注意:从现在开始,我们将重复使用精度和召回的概念。前者描述了所有检测中正确“检测”(即拒绝零假设)的比例,即描述了我们程序的输出样品的纯度。后者描述了空 hyp 的分数。我们已经拒绝了我们应该拒绝的(例如,我们输出样本的完整性)。

*# Test the Bonferroni method with alpha=0.05
methods = [('bonferroni', 0.05)]

# Number of pairs per dataset
ms = np.array([70, 80, 90, 100, 120, 150, 175, 220, 280, 350, 500, 700, 1000])
**print**("Generating %s datasets" % len(ms))

# Pairs with a false null hypothesis for each dataset
n_false = 50

(selections, 
 false_positives, 
 false_negatives, 
 global_typeI) = characterize_methods(apply_ttest, 
                                      methods, 
                                      ms, 
                                      [n_false] * ms.shape[0], 
                                      niter=800,
                                      plot=True)

> Generating 13 datasets
> Method bonferroni **with** alpha 0.05.............completed*

我们可以看到,输出样本的纯度恒定为 1.0,但完整性很小,并且随着测试次数的增加,其下降速度也很快。换句话说,随着 m 的增加,我们检测到的异常越来越少,但是我们检测到的异常总是正确的。检测任何假阳性的 I 类错误概率总是低于声明的α水平,尽管对于小的 m 来说非常保守。我们能做得更好吗?

答案是,幸运的是,是的!

霍尔姆-西达克方法

对 vanilla Bonferroni/Sidak 方法提出了几个修正。你可以在这里找到他们描述的。不去探究它们中每一个的细节(参见维基百科页面),让我们来测试它们:

*# Test different Bonferroni-like methods with alpha=0.05

methods = [('bonferroni', 0.05), 
           ('holm', 0.05), 
           ('holm-sidak', 0.05),
           ('simes-hochberg', 0.05)]

# Number of pairs per dataset
ms = np.array([70, 80, 90, 100, 120, 150, 175, 220, 280, 350, 500, 700, 1000])
**print**("Generating %s datasets" % len(ms))

# Pairs with a false null hypothesis for each dataset
n_false = 50
(selections, 
 false_positives, 
 false_negatives, 
 global_typeI) = characterize_methods(apply_ttest, 
                                      methods, 
                                      ms, 
                                      [n_false] * ms.shape[0], 
                                      niter=800,
                                      plot=True)

> Generating 13 datasets
> Method bonferroni **with** alpha 0.05.............completed
> Method holm **with** alpha 0.05.............completed
> Method holm-sidak **with** alpha 0.05.............completed
> Method simes-hochberg **with** alpha 0.05.............completed*

新方法保持了 vanilla Bonferroni 的绝对纯度和低于或等于标称值的 I 型误差,但稍微提高了完整性。但是,我们可以做得比这好得多!让我们看看怎么做。

错误发现率与家庭错误率

到目前为止,我们对多重假设检验问题的解决方案一直试图将家族错误率(FWER)控制在可控范围内,即在整个 m 检验集中出现的 I 类错误率(在上面的图中我们称之为全局α)。

然而,在我们期望几个“检测”(即几个错误的零假设)的情况下,我们可以稍微牺牲我们对完全纯度的渴望,并且决定我们可以接受受控数量的假阳性,如果这有助于提高完整性的话。对于数据科学从业者来说,这是一个非常熟悉的想法:我们可以用一些精确度来换取更多的召回。换句话说,我们可以接受在检测的输出样本中有一定数量的“冒名顶替者”。这是罗斯福背后的理念。

Benjamini 和 Holdberg (1995) 提出了一种方法。这里,α不再代表 I 型误差概率,而是控制输出样本的纯度(即直接影响精度,而不是像我们以前的方法那样影响全局α)。(预期)精度保证为>1α。

如前所述,我们参考该文件了解详情。这里我想说明一下与我们以前的方法的不同之处。让我们使用和以前一样的程序。为了简单起见,根据前面的图(“holm-sidak”),让我们在类似 Bonferroni 的方法中只考虑我们问题的最佳方法:

*# Let's use two values of alpha per method to illustrate
# what they affect
methods = [('holm-sidak', 0.1), 
           ('holm-sidak', 0.05), 
           ('fdr_bh', 0.1), 
           ('fdr_bh', 0.05)]

# Number of tests
ms = np.array([70, 80, 90, 100, 120, 150, 175, 220, 280, 350, 500, 700, 1000])

# False null hypothesis that we are going to generate

n_false = 50
(selections, 
 false_positives, 
 false_negatives, 
 global_typeI) = characterize_methods(apply_ttest, 
                                      methods, 
                                      ms, 
                                      [n_false] * ms.shape[0], 
                                      niter=800,
                                      plot=True)

> Method holm-sidak **with** alpha 0.10.............completed
> Method holm-sidak **with** alpha 0.05.............completed
> Method fdr_bh **with** alpha 0.10.............completed
> Method fdr_bh **with** alpha 0.05.............completed*

我们可以立即看到,BH 方法通过牺牲一定数量的精确度(“纯度”)来提供更大的召回(“完整性”,第二个面板)。事实上,正如承诺的那样,精度大于 1α。在 BH 方法中,从α=0.01 到α=0.05 增加了预期的纯度,但降低了完整性。此外,BH 方法的全局α(下图)很大,接近 1,这意味着在任何实验中,获得一个或多个假阳性的概率都很高。这是增加完整性的代价,我们几乎获得了 2 倍的收益,特别是对于大量甚至非常大量的测试 m 关于类似 Bonferroni 的方法。

现在,我们了解了 FWER 控制方法和 FDR 控制方法之间的关键区别:前者对 FWER 设置了上限α(“全局”α,下图),而后者对精度设置了下限 1α(“纯度”,上图)。

到目前为止,我们已经研究了假零假设数不变而检验数增加的情况。当错误假设的数量随着测试次数的增加而增加时会发生什么?例如,当我们将搜索扩展到参数空间的先前未探索的部分时,当我们期望错误的零假设/异常(或恒星爆炸,如在我们的天文学例子中)具有与先前相同的密度时,这种情况就会发生。

*# This time we have 30% of false hypothesis for each m
(selections, 
 false_positives, 
 false_negatives, 
 global_typeI) = characterize_methods(apply_ttest, 
                                      methods, 
                                      ms, 
                                      np.ceil(0.3 * ms).astype(int), 
                                      niter=800)

> Method holm-sidak **with** alpha 0.10.............completed
> Method holm-sidak **with** alpha 0.05.............completed
> Method fdr_bh **with** alpha 0.10.............completed
> Method fdr_bh **with** alpha 0.05.............completed*

结果与之前相似,但是现在 BH 方法的完整性(回忆)基本上是恒定的(中间的图),独立于 m

结论

我们已经用多重假设检验和两种非常不同的处理方法说明了这个问题。

  • 控制 FWER 的方法(如 Bonferroni 校正)以牺牲召回率(“完整性”和灵敏度)为代价最大化了精确度(“纯度”)。当预期的假零假设、异常或检测的数量很少,并且为假阳性付出的代价很高时,它们是合适的,因此纯度比完整性更重要。例如,当第一次寻找新效应/新物理时,它们是非常合适的,因为假零假设的预期数量最多是一个,而全局零假设真的很重要。在这种情况下,即使是一个错误的主张,当然也是非常重要的。然而,使用 FWER 会引入显著的 Malmquist 偏差,在这种情况下只能看到非常强的影响。
  • 控制 FDR 的方法(如 Benjamini-hoch Berg)通过允许受控数量的假阳性混入(即以受控的方式牺牲精度)而相对于 FWER 控制方法显著提高了完整性(召回)。当我们预期会有几个错误的零假设、异常或检测,并且我们可以承受一些错误的声明时,它们是合适的。

注:在这个说明性的例子中,我们对所有错误的零假设使用了一个单一的效应大小。这种情况几乎从未出现过,因此效应大小的分布(在我们的 t 检验示例中,两个群体的平均值之间的差异大小,或者在我们的天体物理学示例中,恒星爆炸的亮度)将明显影响异常/探测的数量。然而,这里提出的一般思想仍然成立。使用 FDR 代替 FWER 允许检测更多和更小的效应大小(增加灵敏度/召回),代价是一些假阳性。

密码

***import** numpy **as** np
**import** pandas **as** pd
**import** scipy.stats
**import** matplotlib.pyplot **as** plt
**import** sys
**import** multiprocessing
**import** itertools
**import** functools
**from** statsmodels.stats.multitest **import** multipletests
#Let's set the random seed so the results of the notebook
# are always the same at every run
np.random.seed(0)
%matplotlib inline
**def** generate_dataset(n_datasets, n_null_true, n_samples=100, seed=0):

    # This is to make the results predictable
    np.random.seed(seed)

    n_null_false = n_datasets - n_null_true

    w1 = []
    w2 = []
    null_status = []

    **for** i **in** range(n_null_true):

        wn_1 = np.random.normal(loc=90, scale=10, size=n_samples)
        wn_2 = np.random.normal(loc=90, scale=10, size=n_samples)

        w1.append(wn_1)
        w2.append(wn_2)

        null_status.append(True)

    **for** i **in** range(n_null_false):

        wn_1 = np.random.normal(loc=95, scale=10, size=n_samples)
        wn_2 = np.random.normal(loc=90, scale=10, size=n_samples)

        w1.append(wn_1)
        w2.append(wn_2)
        null_status.append(False)

    **return** w1, w2, np.array(null_status)

**def** worker_function(i, generate_dataset_kw, test, null_hyp_status):

    generate_dataset_kw['seed'] = (i+1) * 1000
    w1, w2, _ = generate_dataset(**generate_dataset_kw)
    pvalue = test(w1, w2)
    **return** null_hyp_status(pvalue, alpha) 

**def** measure_rejection_prob(n_iter, test, null_hyp_status, 
                           alpha, **generate_dataset_kw):       

    n_rejected = 0

    worker = functools.partial(worker_function, generate_dataset_kw=generate_dataset_kw,
                              test=test, null_hyp_status=null_hyp_status)

    pool = multiprocessing.Pool()

    **try**:

        **for** i, res **in** enumerate(pool.imap(worker, range(n_iter), chunksize=100)):
            **if** **not** res:
                n_rejected += 1
            **if** (i+1) % 100 == 0:
                sys.stderr.write("\r%i out of %i completed (fraction of "
                                 "rejections so far: %.2f)" % (i+1, n_iter, 
                                                               n_rejected / float(i+1)))
        sys.stderr.write("\n")
        sys.stderr.flush()

    **except**:

        **raise**

    **finally**:

        pool.close()
        pool.join()

    **return** n_rejected / float(n_iter)

**def** worker_function2(i, generate_dataset_kw, test, method, alpha):

    generate_dataset_kw['seed'] = (i+1) * 1000
    w1, w2, null_hyp = generate_dataset(**generate_dataset_kw)
    pvalues = test(w1, w2)

    reject, _, _, _ = multipletests(pvalues, alpha, 
                                    method=method, 
                                    is_sorted=False, 
                                    returnsorted=False)

    # False positives: I rejected when I shouldn't have
    n_false_pos = np.sum((reject == True) & (null_hyp==True))

    # False negatives: I didn't reject when I should have
    n_false_neg = np.sum((reject == False) & (null_hyp==False))

    **return** np.sum(reject), n_false_pos, n_false_neg
**def** measure_detections(n_iter, test, method, 
                       alpha, **generate_dataset_kw):       

    n_false_pos = []
    n_false_neg = []
    n_selected = []

    worker = functools.partial(worker_function2, generate_dataset_kw=generate_dataset_kw,
                              test=test, method=method, alpha=alpha)

    pool = multiprocessing.Pool()

    **try**:
        **for** i, (s, fp, fn) **in** enumerate(pool.imap(worker, 
                                                  range(n_iter), 
                                                  chunksize=100)):

            n_selected.append(s)
            n_false_pos.append(fp)
            n_false_neg.append(fn)
    **except**:

        **raise**

    **finally**:

        pool.close()
        pool.join()

    global_typeI = np.sum(np.array(n_false_pos) > 0) / float(n_iter)

    **return** (np.average(n_selected), 
            np.average(n_false_pos), 
            np.average(n_false_neg), 
            global_typeI)
**def** characterize_methods(test, methods, ms, n_false, niter=800, plot=True):

    selections = {}
    false_positives = {}
    false_negatives = {}
    global_typeI = {}

    **for** method, alpha **in** methods:

        # Clear output
        sys.stderr.write("Method %s with alpha %.2f" % (method, alpha))
        s = np.zeros(len(ms), int)
        fp = np.zeros_like(s)
        fn = np.zeros_like(s)
        gtI = np.zeros(s.shape[0], float)
        **for** i, (m, nf) **in** enumerate(zip(ms, n_false)):
            s[i], fp[i], fn[i], gtI[i] = measure_detections(niter, 
                                                test, 
                                                method,
                                                alpha, 
                                                n_datasets=m,
                                                n_null_true=m - nf)
            sys.stderr.write(".")

        selections[(method, alpha)] = s
        false_positives[(method, alpha)] = fp
        false_negatives[(method, alpha)] = fn
        global_typeI[(method, alpha)] = gtI

        sys.stderr.write("completed\n")

    **if** plot:

        fig, subs = plt.subplots(3, 1, sharex=True, 
                             figsize=(4,10), 
                             gridspec_kw={'hspace': 0.0, 'top': 0.95})
        **for** key **in** methods:
            true_positives = selections[key] - false_positives[key]
            precision = true_positives.astype(float) / selections[key]
            recall = true_positives / (true_positives + false_negatives[key]).astype(float)

            label = r"%s ($\alpha$=%.2f)" % (key[0], key[1])

            _ = subs[0].plot(ms, precision, label=label)
            _ = subs[1].plot(ms, recall, label=label)
            _ = subs[2].plot(ms, global_typeI[key], label=label)

        subs[0].set_ylabel("Precision\n(purity)")
        subs[1].set_ylabel("Recall\n(completeness)")
        subs[2].set_ylabel(r"Global $\alpha$")
        subs[2].set_xlabel("Number of tests")
        subs[2].set_xscale("log")

        plt.axes(subs[0])
        plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)

    **return** selections, false_positives, false_negatives, global_typeI*

使用签名时间序列模型预测比特币价格

原文:https://towardsdatascience.com/predict-bitcoin-prices-by-using-signature-time-series-modelling-cf3100a882cc?source=collection_archive---------13-----------------------

Pixabay

操纵数据流的新方法

路径签名简介

首先,我想简单介绍一下签名方法。根据维基百科,粗糙路径是平滑路径概念的概括,允许构建由经典不规则信号驱动的受控微分方程的鲁棒解理论,例如,维纳过程。这个理论是由特里·莱昂斯在 20 世纪 90 年代提出的。数学的目的是有效地描述一条平滑但可能高度振荡的多维路径 X。

签名是从路的幺半群(在级联下)到自由张量代数的群状元素的同态。它提供了路径 x 的分级总结。这里是签名变换的正式数学定义,来自《机器学习中的签名方法入门》。

为了一条路

如下定义路径的签名

在哪里

长话短说,签名是将路径转换成封装路径摘要的序列。

这些路径的分级总结或特征是粗略路径定义的核心;在本地,它们消除了查看路径的精细结构的需要。泰勒定理解释了任何光滑函数如何局部地表示为某些特殊函数(基于该点的单项式)的线性组合。

Taylor expansion of a function f(x,y)

坐标迭代积分(签名项)形成了更微妙的特征代数,可以以类似的方式描述流或路径;它们允许定义粗糙路径,并为路径上的连续函数形成自然的线性“基础”。

Expansion to signature

使用签名作为路径函数的基础有很多好处。首先,签名特征对于粗糙路径更鲁棒。第二,虽然路径的签名是无限长的序列,但我们可以使用它的截断版本作为基础来近似连续函数,而不会丢失太多信息。

An approximation of a function f(X) with level 1 truncated signature features

此外,签名功能是可扩展的。举例来说,如果我们想将未来原油价格建模为历史原油价格的函数。

  1. 我们可以用过去的油价作为特征。我们使用的价格越多,关于其行为的信息就越多,模型就越精确,但计算成本也更高。也就是说,1000 个历史价格导致 1000 个特征,100 万个历史价格导致 100 万个特征。
  2. 或者,我们可以从过去的油价构建一条路径,并使用该路径的截断签名作为特征。即使我们对路径使用更多的过去价格,特征的数量也是一样的。例如,假设 X 是 2 维的路径,X 的 2 级截断签名只包括 7 个元素。也就是说,使用 1,000 个过去的价格或 1,000,000 个过去的价格将给我们 7 个特征。这将有助于计算时间(如果我们考虑到计算路径签名所花费的时间),并潜在地给我们关于数据的洞察力(一个签名特征可能是重要的)。然而,我们需要小心使用太低水平的截断,这可能导致拟合不足。

A level 2 truncated signature of a path X with dimension 2

如果你想了解更多关于签名背后的数学知识,我推荐你去阅读

以下是签名功能的应用示例

我们想在这里做什么

最近,我们看到了加密货币交易的显著增长,其中最受欢迎的货币比特币在 2017 年底达到了近 20,000 美元/BTC 的峰值,而在 2018 年 11 月则暴跌至约 3000 美元/BTC。数字货币在金融市场上相当新,我们可以说它的行为几乎是不可预测的。知道了签名可以捕获路径的有意义的属性,并且可以用作线性基来近似路径的连续函数,作者想要探索当我们使用它来预测比特币价格时,这种有前途的方法会如何表现。我们将该结果与当前最先进的机器学习算法 XGBoost 算法进行比较。

我们将使用每日比特币兑换来自 https://www.cryptodatadownload.com/的价格数据。我们将使用美国最大的加密货币交易平台之一 Gemini 的数据。我们的目标是使用 30 天的窗口来预测未来 10 天的平均价格。对于签名方法,我们将使用 30 天价格的截断签名作为特征+ Lasso 线性回归,而对于 XGBoost,我们将使用 30 天价格作为特征。

数据预处理

我们使用熊猫来探索数据集。

我们需要删除数据帧的第一行,反转数据帧并使用日期作为索引。

所以现在我们已经准备好处理数据帧了。让我们先画出收盘价来形象化价格。

#Plot to visualise data
import matplotlib.pyplot as pltax = BTC_price.plot(y= 'Close', figsize=(12,6), legend=True, grid=True, use_index=True)
plt.show()

我们可能会对几个有趣的时期感兴趣,2017 年 10 月的繁荣以及 2018 年 1 月和 2018 年 10 月的崩溃。首先,我们将使用 2017 年 1 月至 2017 年 11 月的数据,看看该模型是否可以预测繁荣期。

#select duration
initial_date = '2017-01-01'
finish_date = '2017-12-01'BTC_price_time = BTC_price[initial_date:finish_date]

从数据帧创建特征

接下来,我们将为我们的机器学习算法构建特征。首先,我们将编写一个函数,该函数产生一个大小为 h 的历史价格窗口和下一个未来 f 价格的平均值。

我们将用长度为 10 的收盘价序列来测试我们的函数

BTC_price_time['Close'].head(10)

GetWindow(BTC_price_time.loc[:,'Close'].head(10), h_window = 5, f_window =2)

我们得到一个包含大小为 5 的滚动窗口的数据帧,如下所示。

GetNextMean(BTC_price_time.loc[:,'Close'].head(10), h_window = 5, f_window =2)

GetNextMean 为我们提供了一个数据帧,其中包含从第 6 个价格开始的 2 个连续价格的平均值。比如 896.12 = (893.49+898.75)/2。

除了价格窗口,我们还为特性添加了时间列。

标志性特征

现在,让我们构建签名特征。如上所述,签名是连续路径的迭代积分形式,但我们只有离散的数据点。有多种方法可以将离散的数据点转换成连续的路径。举个例子,

  • 分段线性插值
  • 矩形线性插值

下面是从一本关于机器学习中签名方法的初级读本中得到的每个转换的插图。对于两个长度为 4 的一维序列,

然而,还有另一种有趣的变换,即超前-滞后变换,它将一维路径变换为二维路径。

这里,我们将在离散数据点上使用这种超前-滞后变换,并将该路径的特征用于我们的特征。

我们用一个序列(1,1),(2,4),(3,2),(4,6)进行测试。

接下来,是时候计算路径的截断签名了!我们足够幸运,不用写函数来计算迭代积分。显然,有一个包 ESig 将为我们做(肮脏的)工作,尽管它仍处于开发的活跃阶段。

pip install esig
import esig.tosig as ts

下面是 ts.stream2sig(…)的文档。
stream 2 SIG(array(no _ of _ ticks x signal _ dimension),signature_degree)读取一个 2 维 numpy 浮点数组“流空间中的数据”,并返回一个 numpy 向量,其中包含到给定 signature_degree 为止的向量系列的签名。

我们将使用这个函数来计算路径的签名(Time_lead,Price_lead,Price_lag)。

获取功能

现在,我们准备计算正常特征和签名特征。为简单起见,我们将只使用比特币的接近价格。以下代码所做的是获得带有时间列的正常窗口特征,获得预测目标,即未来价格的平均值,并计算签名特征。对于签名特征,我们使用 ESig 包来寻找路径的 2 级截断签名(time_lead,price_lead,price_lag)。

我们可以检查结果特征。

y.head()

pd.DataFrame(X_window).head()

pd.DataFrame(X_sig).head()

火车模型

我们将 X_sig,y 分成模型的训练集和测试集,我们将预测 10 个未来价格。

首先,我们将在具有签名特征的套索线性回归上训练我们的模型。我们使用时间序列分割测试交叉验证和 gridsearchCV 来调整超参数 alpha。

这是错误

时间序列预测不可或缺的一部分是将结果可视化。

PlotResult(y_train, y_test, y_train_predict, y_test_predict, test_len, 'Lasso + Signature features')

该模型可以预测未来将出现一次繁荣,其精确度在 15%左右。注意,这里我们只使用 2 级截断签名特征。从理论上讲,如果我们增加截断的级别,我们希望模型会更准确,因为我们有更多的信息

现在我们将尝试我们的 XGBoost 模型。我们还使用时间序列分割交叉验证和 GridsearchCV 来调整超参数。

我们观察到训练集的平均绝对误差非常低,这是过度拟合的标志,并且测试集的值与签名方法大致相同。让我们把图表形象化

PlotResult(y_train, y_test, y_train_predict, y_test_predict, test_len, 'XGBoost')

我们可以看到,该模型根本不能预测繁荣期,它猜测平均未来价格是稳定的。出现这种情况的一个可能原因是该模型以前没有经历过如此急剧的增长。

与他人进行一段时间的实验

我们将使用 2018 年 1 月至 2018 年 12 月的一段时间来测试该模型是否可以预测崩溃。我们测试了价格相当稳定的持续时间。

#select duration
initial_date = '2018-01-01'
finish_date = '2018-11-01'

以下是签名功能的结果。

我们收到了一个收敛警告,说模型不收敛,结果图形很奇怪。请注意,这里我们只使用截断的签名级别 2,它只有 12 个特征,因此它们可能无法捕获有关路径的重要信息。

我们用截断的签名级别 3 进行实验。

该模型在术语或误差方面表现较差,但是我们可以从图中看到,它具有比 2 级截断签名特征更真实的路径。

另一方面,这是 XGBoost 算法的结果。

该模型做得非常好,做出了误差高达 0.5%的预测。

现在,让我们挑战这些模型是否能够预测 2018 年 12 月的崩盘。

我们使用了 3 级截断签名功能,以下是结果。

与 XGBoost 模型相比

很明显,没有一个模型能提前预测到撞车。在这里,我们可能会达到只使用历史价格的极限。在现实世界中,有许多因素会影响加密货币的价格,如新闻、其他金融产品的吸引力。然而,像这样的技术分析可以让我们快速了解数据。

结论

我们已经学会了如何使用签名特征来建模时间序列,并将其与 XGBoost 算法进行比较,XGBoost 算法是目前最先进的算法。XBGoost 在价格稳定时期表现非常出色,但在繁荣或萧条时,它无法给我们提供太多信息。另一方面,签名方法在价格稳定时期表现良好,但可能会提供一些关于重大变化的信息。尽管如此,作者选择的数据集可能会有偏差,因为我们事先知道什么时候是繁荣期,什么时候是萧条期。总之,这种方法很新,仍然需要证明自己,我鼓励读者尝试一下。

注来自《走向数据科学》的编辑: 虽然我们允许独立作者根据我们的 规则和指导方针 发表文章,但我们不认可每个作者的贡献。你不应该在没有寻求专业建议的情况下依赖一个作者的作品。详见我们的 读者术语

用 30 行 Python 预测大学篮球成绩

原文:https://towardsdatascience.com/predict-college-basketball-scores-in-30-lines-of-python-148f6bd71894?source=collection_archive---------5-----------------------

深度分析

创建一个机器学习算法,用不到 30 行 Python 代码预测大学篮球成绩

Don’t worry, we’ve all been beaten by a Very Good Boy at least once. Photo by Jenny Marvin on Unsplash

又在你办公室的疯狂三月泳池里获得最后一名?有没有一只金毛或者你邻居家女儿的宠物石头选了一个比你更好的支架?创建一个获胜的阵营是困难的,甚至会绊倒大学篮球的专家分析师。与其让猜测工作听天由命,或者每个赛季看几千个小时的篮球比赛(我想我反正是这样做的,但那不是重点),为什么不训练一台计算机来为你做预测呢?

在 Python 和一些令人敬畏的库的帮助下,您可以构建自己的机器学习算法,用不到 30 行代码预测 NCAA 男子 I 级大学篮球比赛的最终得分。本教程旨在解释创建机器学习应用程序所需的所有步骤,包括设置、数据检索和处理、训练模型以及打印最终预测。

设置

先决条件

要学习本教程,强烈建议您对 Python 有一个基本的了解,尽管这不是必需的。了解导入模块、获取和设置变量、字典和实例化类的目的是一个很好的基础,而使用Pandassklearn的经验是一个巨大的优势。

开发要求

pip install pandas sklearn sportsreference
  • 活跃的网络连接:这对于大多数人来说可能不是问题,但是您最终使用的开发环境必须能够访问外部 web,以便从我们的代码中下载数据集。

构建应用程序

现在我们的开发环境已经设置好了,让我们开始构建实际的应用程序。

完整算法

Complete machine learning program to predict college basketball scores

对于那些喜欢直接跳到代码的人来说,上面的要点是我们将使用的最终程序。如果你已经熟悉了pandassklearn,你可以跳到本教程的底部,看看这个程序是如何运行的,以及如何扩展它以获得更高的精度、更快的运行时间和改进的可用性。对于其他想进一步了解这段代码的人,请继续阅读下面的内容,了解每一步的目的。

导入依赖关系

Importing all required dependencies

几乎每一个 Python 程序都以一个import部分开始,其中包含了模块后面要用到的必需依赖项。对于这个项目,我们需要导入我们之前安装的以下包:

  • pandas :一个流行的 Python 数据科学库,我们将使用它来存储和操作我们的数据集。
  • sportsreference :一个免费的 Python sports API,我们将使用它从 NCAAB 游戏中提取数据。更多信息可以在这篇博文中找到。
  • sk learn:Python 最大的机器学习库之一,包括几个预制的算法,比如我们将使用的RandomForestRegressor,以及一些有用的工具来帮助数据创建管道,比如自动创建训练和测试数据集的train_test_split

正在初始化数据集

Initializing our dataset using sportsreference

没有数据集,任何机器学习应用都是不完整的。为了帮助我们预测 NCAAB 比赛的最终得分,我们希望创建一个包含所有单个比赛统计数据(如投篮命中率、失误次数和盖帽次数、篮板百分比等)的数据集,然后我们可以用它来预测这些因素与最终得分的关系。

为了创建这个数据集,我们首先需要初始化一个空的Pandas DataFrame,我们将使用它来存储我们的最终数据。接下来,我们从sportsreference初始化Teams类,它包含当前或最近一个赛季的每个 NCAA 男子篮球队的信息,并允许我们轻松地获取每个队的统计数据。

在提取数据之前,我们需要通过运行for team in teams:来迭代每支球队,其中每次迭代对应于联盟中唯一的一支球队。sportsreference公开了每个团队的日程和 boxscore 信息,并使我们能够编写类似于team.schedule.dataframe_extended的代码,该代码收集该团队在当前赛季参加的每场比赛的统计信息。dataframe_extended属性返回一个pandas DataFrame,其中每个索引对应一个不同的游戏。

在收集了每个游戏的 boxscore 信息后,我们希望将它添加到我们的整体数据集中,这样我们就有了一个单一的数据源。这可以通过将我们现有的数据集与包含当前团队完整 boxscore 信息的本地DataFrame连接起来来实现。通过用得到的连接覆盖我们现有的数据集,我们确保数据集总是不仅包括最近的团队的信息,还包括以前查询过的所有团队的信息。

预处理数据集

Preprocess our dataset by dropping unused values, creating our X and y, and separating a training and testing dataset

在我们的数据集完成自我构建后,我们需要从我们的数据集中过滤出一些我们不想使用的类别(或在机器学习中经常被称为特征)——即那些属于string类型(或分类)的类别,如团队名称或日期和地点。有时,基于字符串的要素会很有用,例如,在预测房屋价值和确定列为“海滨”的房产比列为“内陆”的房产价值更高的情况下。尽管该功能对房价预测很有用,但大多数机器学习算法无法处理基于字符串的数据。替换这些类型的特征的一种方法称为一键编码,它自动用唯一特征列替换相似的分类值,其中落入该特征的每个索引的值为 1,否则为 a。通过将类别更改为 1 和 0,机器学习算法能够更有效地处理这些特征。

然而,出于我们的目的,我们将简单地删除这些特性,因为它们要么太多(即可能的比赛场地非常大),没有意义(在根据统计数据确定结果时,比赛是在 11 月 18 日还是 12 月 2 日并不重要),或者会引入偏见(我们希望算法根据球队的表现来确定最终得分——而不仅仅是因为他们的名字是“杜克”)。因此,我们将放弃所有这些类别。

在这一点上,有些人可能想知道为什么我将home_pointsaway_points包含在要删除的字段列表中。这两个字段是我们想要预测的最终输出(通常称为标签),因此我们不希望它们包含在我们的主要特征中,而是应该将它们专门保留给我们的输出标签。

通过上面的代码,我们首先从数据集中删除所有不需要的特征,并将修剪后的输出保存为X。删除未使用的特性后,我们接下来删除所有数据不完整的行。如果 sports-reference.com 上的数据没有正确填充,或者如果一支球队没有执行某个统计动作,例如没有盖帽或罚球,这种情况有时会发生。有两种方法可以处理这种不完整的数据,一种是用一组数字设置缺失值(例如类别的平均值或默认值为零),另一种是删除任何无效的行。因为对于我们的数据集来说,无效单元格的数量非常少,所以我们将删除所有数据不完整的行,因为这不会影响我们的最终结果。

因为探戈是两个人跳的(嗯,一场比赛有两个参赛队),所以当两个队的时间表被拉出来时,每场比赛都有一个副本(一个是主队的,一个是客场队的)。这只是污染了我们的数据集,并没有提供任何价值,因为行是完全相同的,所以我们想删除任何副本,只保留每个游戏的一个实例。为此,我们只需将drop_duplicates()添加到我们的数据集中,以确保每个索引都是唯一的。

接下来,我们需要创建输出标签,用于在训练时确定模型权重的准确性,并测试最终算法的准确性。我们可以通过创建一个只包含主客场点的两列向量来生成标签,并将结果设置为y

最后,通常的做法是将数据集分成训练和测试子集,以确保训练模型是准确的。理想情况下,我们希望将大约 75%的数据集用于训练,将剩余的 25%用于测试。这些子集应该随机选取,以防止模型偏向某一组特定的信息。使用定型数据集对模型进行定型后,应该对测试数据集运行该模型,以确定模型的预测性能,并查看它是否过度拟合。

幸运的是,sklearn有一个内置函数可以为我们创建这些子集。通过将我们的Xy帧输入到train_test_split中,我们能够检索具有预期分割的训练和测试子集。

创建和训练模型

Setting hyperparameters and training our model

既然我们的数据集已经被处理,现在是时候创建和训练我们的模型了。我决定在这个例子中使用一个 RandomForestRegressor ,因为该算法易于使用,相对准确,而且与标准决策树相比,它可以减少过拟合。随机森林算法创建了几个决策树,在特征权重中注入了一些随机性。这些决策树然后被组合以创建一个森林(因此是决策 ) 的随机森林,用于训练、验证或推断时的最终分析。该算法既支持分类也支持回归,使其非常灵活地适用于各种应用。

分类确定属于固定数量类别的输出标签,例如学生在测验中收到的字母等级(“A”、“B”、“C”、“D”或“F”)。只能有五个类别(或),因此模型将只尝试将输出放入这五个类别中的一个。另一方面,回归确定可以取值范围不确定的输出标签,例如房屋的价格。尽管有一个标准房价的范围,但是房子的售价是没有限制的,任何正数都是合理的。因为篮球比赛的最终比分从技术上讲可以是任何正数(或者零!),我们要用回归。

在我们构建和训练我们的模型之前,我们首先需要设置一些超参数。超参数是在训练之前输入到模型中的参数,影响模型的构建和优化。对于机器和深度学习领域的大多数初学者来说,这些参数往往是最大的障碍,因为这些设置通常没有一个“完美”的值,如果有什么的话,确定应该放什么会变得非常困难。

一般的经验法则是,最初坚持使用这些超参数的默认值,然后一旦模型被训练和完成,并且您能够测试它,就开始使用试错法来调整这些值,直到您对最终结果满意为止。对于我们的模型,我选择了六个不同的超参数,并找到了这组特定的值来提供性能和精度之间的最佳平衡。关于这些具体设置的更多细节可在官方 scikit-learn 文档中找到。

在选择了我们的超参数之后,终于到了创建模型的时候了。首先,我们需要实例化我们之前导入的RandomForestRegressor类,并包含我们的超参数。通过使用(**parameters),我们将字典的键-值对扩展为该类的命名参数,其功能与以下内容相同:

An example of how dictionary expansion works for function calls

既然我们的模型已经实例化了,剩下的就是训练它了。sklearn通过将fit方法与RandomForestRegressor结合在一起,使这变得非常容易,所以我们只需要用我们的输入特性和相应的输出标签来运行它。这个方法就地运行,所以我们的model变量现在将自动指向一个经过训练的模型,我们可以用它来进行预测!

打印结果

Print the final results

我们应用程序的最后一步是根据我们的测试子集运行预测,并将它们与我们的预期结果进行比较。这个 print 语句将预测结果和我们的实际预期结果输出为两个不同的两列向量。

运行应用程序

我们期待已久的时刻终于到来了!我们的应用程序现在已经完成,剩下的就是运行算法了。我将我的程序命名为ncaab-machine-learning-basic.py,所以我只需要运行下面的代码来初始化算法:

python ncaab-machine-learning-basic.py

请注意该程序可能需要很长时间 才能完成,因为大部分处理时间都花在了为 I 级大学篮球赛的所有 350+支球队构建数据集上。如果您只是希望看到一个工作的算法,您可以通过在数据连接行之后的第一个循环中添加一个break语句来提前停止数据创建。

一旦程序完成,它将输出类似如下的内容(为了节省空间,我减少了行数):

(array([[86, 86],
       [71, 71],
       [78, 77],
       [74, 72],
       [90, 81],
       ...
       [52, 66],
       [68, 65]]),
array([[ 83,  89],
       [ 71,  73],
       [ 80,  76],
       [ 77,  72],
       [ 92,  84],
       ...
       [ 46,  73],
       [ 66,  65]]))

该输出包含两个部分:预测输出和预期输出。从array([[86, 86][68, 65]])的所有内容都是预测输出,而array([[83, 89][66, 65]])是实际数据。如前所述,第一列是指主场队的预期得分,第二列是客场队的预计得分。

预测输出中的行也与预期输出中的行匹配,因此[86, 86][83, 89]相关,依此类推。如果我们比较下面的列表,我们会发现我们的预测并不太差!在大多数情况下,预计分数与实际结果只差几分。另一个有希望的迹象是,当实际分数与 70 分左右的典型结果不同时,我们的算法能够识别差异,并生成高于或低于正常分数的分数。

改进应用程序

如果这是你的第一个机器学习程序,恭喜你!希望本教程足以让您入门,并表明一个基本的机器学习应用程序不需要多年的教育或数千行代码。

虽然这个计划是一个很好的开始,但我们有很多方法可以扩展它,使它变得更好。下面是我将对应用程序进行的一些改进,以提高性能、准确性和可用性:

  • 将数据集保存到本地目录:如前所述,该计划需要很长时间才能完成,因为它为所有 350 多个团队从头开始构建数据集。目前,如果您想要再次运行该算法,您将需要重新构建数据集,即使它没有发生变化。在第一次构建数据集之后,可以通过将DataFrame的副本转换成 CSV 或 Pickle 文件保存到本地文件系统来简化这个过程。然后,下次运行该程序时,您可以选择测试 CSV 或 Pickle 文件是否存储在本地,如果是,则从该文件加载它并跳过构建数据集。这将大大减少首次保存数据集后运行程序所需的时间。
  • 特征工程:改进机器学习模型的一种常见做法被称为特征工程。这与创建或修改特征的过程有关,这些特征有助于模型找到各种类别之间的相关性。特征工程通常是一项困难的任务,就像超参数一样,没有一个定义好的方法可以用来持续提高性能。然而,一些经验法则是修改数字特征,使它们处于相同的数量级。例如,我们的数据集包含许多百分比和累计总数。百分比范围从 0 到 1,而总数可以是大于或等于 0 的任何数字(想想“分数”或“上场时间”)。修改这些特征使它们处于相同的数量级有助于模型的创建。特征工程的另一个例子是创建一个新的特征,比如著名的每个团队的四因素评分。我们可以生成这个新特征,并将其包含在我们的数据集中,以确定它是否会改进整个模型。
  • 显示特定球队的预测:虽然我们的程序很好地介绍了机器学习和预测篮球比分,但它并不能完全用于确定特定比赛或比赛的结果。一个很好的扩展是为特定的团队生成预测。这样,我们可以回答类似“印第安纳大学在普渡大学比赛”这样的问题。预测的分数是多少?”正如我们在构建数据集时所做的那样,我们可以利用sportsreference来生成特定于各个团队的数据,并在进行预测时将其用作我们的输入。
  • 为相似的代码块编写函数:为了更加 Pythonic 化,并使我们的应用程序模块化以适应未来的变化,函数应该用于所有具有特定目的的代码块,例如构建数据集、处理数据、构建和训练模型。这也提高了代码的可读性,以帮助将来可能使用它的其他人。

既然您已经有了一个可以工作的应用程序,请尝试实现这些建议来提高模型的准确性和性能。如果您生成了一个您满意的模型,您可以使用它来为 NCAA 锦标赛创建预测,或者可能参加比赛

虽然要打败金毛猎犬或莎莉的宠物岩石仍然很难,但这种算法可能会让你今年在公司的人才库中获得竞争优势。为什么不把这个三月变成你的并把吉姆从会计部赶下台呢?

使用 PySpark 机器学习预测客户流失

原文:https://towardsdatascience.com/predict-customer-churn-using-pyspark-machine-learning-519e866449b5?source=collection_archive---------16-----------------------

Source: Globallinker

预测客户流失是数据科学家目前面临的一个具有挑战性的常见问题。预测特定客户面临高风险的能力,同时还有时间做些什么,这对于每个面向客户的企业来说都是一个巨大的额外潜在收入来源。

在这篇文章中,我将指导你创建一个能够预测客户流失的机器学习解决方案。这个解决方案将通过阿帕奇火花实现。Apache Spark 是一个流行的分布式数据处理引擎,可以以多种方式部署,为 Java、Scala、Python 和 r 提供原生绑定。它提供了一系列库,包括 Spark SQL、Spark Streaming、用于机器学习的 MLlib 和用于图形处理的 GraphX。对于这个项目,我们将重点关注机器学习库 MLlib。我们将使用 Python API for Spark,称为 PySpark。

如果您阅读这篇文章,您将学会如何:

  • 将大型数据集加载到 Spark 中,并使用 Spark SQL 和 Spark Dataframes 操纵它们,以设计相关功能来预测客户流失,
  • 使用 Spark ML 中的机器学习 API 来构建和调整模型。

介绍

想象一下,你正在一个类似于 Spotify 的流行数字音乐服务的数据团队工作。姑且称之为 Sparkify 吧。用户每天播放他们喜欢的歌曲,要么使用在歌曲之间放置广告的免费层,要么使用付费订阅模式,他们免费播放音乐,但按月支付固定费用。用户可以随时升级、降级和取消服务。每当用户与该服务交互时,如播放歌曲、注销或竖起大拇指喜欢一首歌,它都会生成数据。所有这些数据都包含了让用户满意和帮助企业发展的关键见解。我们数据团队的工作是预测哪些用户面临取消账户的风险。如果我们能够在这些用户离开之前准确地识别他们,我们的企业就可以为他们提供折扣和激励,从而潜在地为我们的企业节省数百万美元的收入。

导入库并设置 Spark

我使用 IBM Watson Studio(默认 Spark Python 3.6 XS,一个驱动程序,两个执行程序,Spark 2.3 版)来完成这个项目。与 PySpark 数据帧的交互不如与 pandas 数据帧的交互方便。这就是为什么我建议安装并导入pixiedust:

!pip install --upgrade pixiedust
import pixiedust

pixiedust是一个开源的 Python 助手库,作为 Jupyter 笔记本的附加组件,极大地改善了我们与 PySpark 数据帧的交互方式。

import numpy as np
import pandas as pd
%matplotlib inline
import matplotlib.pyplot as plt
import datetime

from sklearn.metrics import f1_score, recall_score, precision_score
from pyspark.sql import SparkSession
import pyspark.sql.functions as F
from pyspark.sql.types import IntegerType, DoubleType, DateType, FloatType
from pyspark.ml.feature import VectorAssembler, MinMaxScaler
from pyspark.ml import Pipeline
from pyspark.ml.evaluation import BinaryClassificationEvaluator
from pyspark.ml.tuning import ParamGridBuilder, CrossValidator
from pyspark.ml.classification import LogisticRegression, DecisionTreeClassifier, GBTClassifier, LinearSVC 

创建 Spark 会话并读取 Sparkify 数据集:

# create a Spark session
spark = SparkSession \
    .builder \
    .appName("Sparkify") \
    .getOrCreate()# read in dataset
df = spark.read.json('medium-sparkify-event-data.json')

pixiedust现在派上了用场,我们可以显示数据帧的第一个条目。

display(df)

Figure 1

浏览一下模式:

df.printSchema()

Figure 2

该数据集包含有关用户如何与流媒体平台交互、他们听了哪些歌曲、他们访问了哪个页面、他们的帐户状态等信息。任何用户交互都存储有 UNIX 时间戳,这使得分析用户行为随时间的变化成为可能。我们将在以后的特征工程过程中利用这些信息。

探索性数据分析

接下来,我们将通过在 PySpark 中进行基本操作来执行 EDA。

为了了解用户如何与音乐服务交互,我们可能想看看他们查看最多的页面。

df.groupBy('page').count().sort(F.desc('count')).show()

Figure 3

我们可以清楚地看到,“NextSong”是最受欢迎的页面视图,这对于音乐服务来说非常有意义。然而,还有许多其他页面视图对于从该原始数据集中设计相关要素也很重要。我们使用“取消确认”页面,计算 99 次访问,来创建机器学习模型的标签。

flag_cancellation_event = F.udf(lambda x: 1 if x == 'Cancellation Confirmation' else 0, IntegerType())
df = df.withColumn('label', flag_cancellation_event('page'))

基于 UNIX 时间戳 ts ,我们可以按小时计算统计数据。

get_hour = F.udf(lambda x: datetime.datetime.fromtimestamp(x / 1000.0).hour, IntegerType())
df = df.withColumn('hour', get_hour(df.ts))

由于 matplotlib 不支持 PySpark 数据帧,我们将其转换回 pandas 数据帧,并按小时绘制用户活动。

*# Count the events per hour*
songs_by_hour = df.groupBy('hour').count().orderBy(df.hour)
songs_by_hour_pd = songs_by_hour.toPandas()
songs_by_hour_pd.hour = pd.to_numeric(songs_by_hour_pd.hour)*# Plot the events per hour aggregation*
plt.scatter(songs_by_hour_pd['hour'], songs_by_hour_pd['count'])
plt.xlim(-1, 24)
plt.ylim(0, 1.2 * max(songs_by_hour_pd['count']))
plt.xlabel('Hour')
plt.ylabel('Events');

Figure 4

特征工程

特征工程在大数据分析中发挥着关键作用。没有数据,机器学习和数据挖掘算法就无法工作。如果只有很少的特征来表示底层的数据对象,那么就很难实现什么,并且这些算法的结果的质量很大程度上取决于可用特征的质量。
因此,我们开始构建我们发现有希望训练模型的特征。

为此,我们从头开始创建一个新的 py spark data framefeature _ df,每行代表一个用户。我们将从数据帧 df 中创建特征,并将它们依次连接到数据帧 feature_df 中。

基于 df 中的标签列,我们可以将被搅动的用户与其他人分开。

churned_collect = df.where(df.label==1).select('userId').collect()
churned_users = set([int(row.userId) for row in churned_collect])all_collect = df.select('userId').collect()
all_users = set([int(row.userId) for row in all_collect])feature_df = spark.createDataFrame(all_users, IntegerType()).withColumnRenamed('value', 'userId')

编码标签

*# Create label column*
create_churn = F.udf(lambda x: 1 if x in churned_users else 0, IntegerType())
feature_df = feature_df.withColumn('label', create_churn('userId'))

将性别和帐户级别编码为特征

*# Create binary gender column*
convert_gender = F.udf(lambda x: 1 if x == 'M' else 0, IntegerType())
df = df.withColumn('GenderBinary', convert_gender('Gender'))*# Add gender as feature*
feature_df = feature_df.join(df.select(['userId', 'GenderBinary']), 'userId') \
    .dropDuplicates(subset=['userId']) \
    .sort('userId')convert_level = F.udf(lambda x: 1 if x == 'free' else 0, IntegerType())
df = df.withColumn('LevelBinary', convert_level('Level'))*# Add customer level as feature*
feature_df = feature_df.join(df.select(['userId', 'ts', 'LevelBinary']), 'userId') \
    .sort(F.desc('ts')) \
    .dropDuplicates(subset=['userId']) \
    .drop('ts')

将页面视图编码为功能

每次用户与平台交互时,它都会生成数据。这意味着我们确切地知道每个用户在数据提取期间经历了什么。我的方法是将页面分成几类:

  • 中性页面:“取消”、“主页”、“注销”、“保存设置”、“关于”、“设置”
  • 负面页面:“拇指朝下”、“滚动广告”、“帮助”、“错误”
  • 正面页面:“添加到播放列表”、“添加朋友”、“下一首歌”、“竖起大拇指”
  • 降级页面:“提交降级”、“降级”
  • 升级页面:“提交升级”、“升级”

这种方法背后的原因是,我们可以计算用户与正面页面进行交互的频率。我们可以为每个页面分别做这件事,但这会导致一个更高的特征空间。

让我们把它写成代码:

*# Create a dictonary which maps page views and PySpark dataframes* 
pages = {}
pages['neutralPages'] = df.filter((df.page == 'Cancel') | (df.page == 'Home') | (df.page == 'Logout') \
    | (df.page == 'Save Settings') | (df.page == 'About') | (df.page == 'Settings'))
pages['negativePages'] = df.filter((df.page == 'Thumbs Down') | (df.page == 'Roll Advert') | (df.page == 'Help') \
    | (df.page == 'Error'))
pages['positivePages'] = df.filter((df.page == 'Add to Playlist') | (df.page == 'Add Friend') | (df.page == 'NextSong') \
    | (df.page == 'Thumbs Up'))
pages['downgradePages'] = df.filter((df.page == 'Submit Downgrade') | (df.page == 'Downgrade'))
pages['upgradePages'] = df.filter((df.page == 'Upgrade') | (df.page == 'Submit Upgrade'))*# Loop through page views and aggregate the counts by user*
for key, value in pages.items():
    value_df = value.select('userId') \
        .groupBy('userId') \
        .agg({'userId':'count'}) \
        .withColumnRenamed('count(userId)', key)

    *# Add page view aggregations as features*
    feature_df = feature_df.join(value_df, 'userId', 'left').sort('userId') \
        .fillna({key:'0'})

接下来,我们将计算用户与平台互动的天数:

*# Create dataframe with users and date counts*
dateCount_df = df.select('userId', 'date') \
    .groupby('userId') \
    .agg(F.countDistinct('date')) \
    .withColumnRenamed('count(DISTINCT date)', 'dateCount')

*# Add date count as feature*
feature_df = feature_df.join(dateCount_df, 'userId', 'left').sort('userId') \
        .fillna({'dateCount':'1'})

这些页面视图特征是计算出现次数的绝对值。然而,如果一些用户在数据提取结束时注册,而另一些用户从一开始就使用该平台,这可能会导致误导性的结果。为此,我们将通过在特定于用户的时间窗口内划分汇总结果,获得计数/天,从而使汇总结果具有可比性。我在这里跳过代码,完整的代码可以在 GitHub 上找到。

将一段时间内的用户活动编码为特征

另一个有希望的特性是用户交互如何随时间变化。首先,我们计算每天的用户交互次数。其次,我们使用 numpy.polyfit 为每个用户拟合一个线性回归模型。我们将获取这些线的斜率,移除异常值,并将缩放后的斜率作为特征插入分类算法中。

Figure 5

*# Create dataframe with users and their activity per day*
activity_df = df.select('userId', 'date') \
    .groupby('userID', 'date') \
    .count()*# initialize slopes*
slopes = []
for user in all_users:
    *# Create pandas dataframe for slope calculation*
    activity_pandas = activity_df.filter(activity_df['userID'] == user).sort(F.asc('date')).toPandas()
    if activity_pandas.shape[0]==1:
        slopes.append(0)
        continue
    *# Fit a line through the user activity counts and retrieve its slope*
    slope = np.polyfit(activity_pandas.index, activity_pandas['count'], 1)[0]
    slopes.append(slope)

特征缩放,将列合并为一个特征向量

作为特征工程过程的最后一步,我们将迭代创建的特征,并使用 MinMaxScaler 缩放它们。然后,我们将这些特征放入一个向量中,这样我们就可以将它插入 pyspark.ml 算法中。

*# UDF for converting column type from vector to double type*
unlist = F.udf(lambda x: round(float(list(x)[0]),3), DoubleType())

*# Iterate over columns to be scaled*
for i in ['neutralPagesNormalized', 'negativePagesNormalized', 'positivePagesNormalized', 'downgradePagesNormalized', 'upgradePagesNormalized', 'dateCountNormalized', 'hourAvg', 'UserActiveTime', 'Slope']:
    *# VectorAssembler Transformation - Convert column to vector type*
    assembler = VectorAssembler(inputCols=[i],outputCol=i+"_Vect")

    *# MinMaxScaler Transformation*
    scaler = MinMaxScaler(inputCol=i+"_Vect", outputCol=i+"_Scaled")

    *# Pipeline of VectorAssembler and MinMaxScaler*
    pipeline = Pipeline(stages=[assembler, scaler])

    *# Fitting pipeline on dataframe*
    feature_df = pipeline.fit(feature_df).transform(feature_df) \
        .withColumn(i+"_Scaled", unlist(i+"_Scaled")).drop(i+"_Vect")

*# Merge columns to one feature vector*
assembler = VectorAssembler(inputCols=['neutralPagesNormalized_Scaled', 'negativePagesNormalized_Scaled', 'positivePagesNormalized_Scaled',                                   'downgradePagesNormalized_Scaled', 'upgradePagesNormalized_Scaled', 'dateCountNormalized_Scaled',                                   'hourAvg_Scaled', 'UserActiveTime_Scaled', 'Slope_Scaled', 'LevelBinary', 'GenderBinary'], outputCol='features')
feature_df = assembler.transform(feature_df)

遵循 feature_df 的模式:

Figure 6

特征列保存每个用户的组合特征向量:

Figure 7

机器学习建模

创建特征后,我们可以继续将整个数据集分为训练和测试两部分。我们将测试几种用于分类任务的常见机器学习方法。将评估模型的准确性,并相应地调整参数。根据 F1 分数、精确度和召回率,我们将确定获胜的型号。

将特性数据框架分为训练和测试,并检查类别不平衡。

train, test = feature_df.randomSplit([0.7, 0.3], seed = 42)plt.hist(feature_df.toPandas()['label'])
plt.show()

Figure 8

检查数据中潜在的阶级不平衡是至关重要的。这在实践中是非常普遍的,并且许多分类学习算法对于不常见的类具有低的预测准确度。

机器学习超参数调整和评估

Spark 的 MLlib 支持[CrossValidator](https://spark.apache.org/docs/latest/api/python/pyspark.ml.html#pyspark.ml.tuning.CrossValidator)等模型选择的工具。这需要满足以下条件:

  • 估计器:算法还是流水线
  • 参数集:要搜索的参数网格
  • Evaluator:度量模型在测试数据集上的表现的指标。

将为每个分类器专门设置估计器和参数。

为了评测,我们取支持“areaUnderROC”和“areaUnderPR”的[BinaryClassificationEvaluator](https://spark.apache.org/docs/latest/api/python/pyspark.ml.html#pyspark.ml.tuning.BinaryClassificationEvaluator) 。由于我们在数据中有一个等级不平衡,我们采用“areaUnderPR”作为我们的评估指标,因为 PR 曲线在这种情况下提供了更多信息(参见http://pages.cs.wisc.edu/~jdavis/davisgoadrichcamera2.pdf)。

由于 pyspark.ml.evaluation 中的类BinaryClassificationEvaluator只提供了“areaUnderPR”和“areaUnderROC”这两个指标,我们将使用sklearn来计算 F1 分数、精确度和召回率。

evaluator = BinaryClassificationEvaluator(metricName = 'areaUnderPR')

逻辑回归

逻辑回归基本上是一种线性回归模型,它解释了因变量与一个或多个名义变量、序数变量、区间变量或比率级自变量之间的关系,唯一的区别是,线性回归的输出是一个具有实际意义(标签)的数字,而逻辑回归的输出是一个代表事件发生概率(即客户删除其账户的概率)的数字。

在实例化逻辑回归对象之前,我们计算一个平衡比率来说明类的不平衡。我们使用weightCol参数根据预先计算的比率对训练实例进行过采样/欠采样。我们想要“欠采样”负类和“过采样”正类。逻辑损失目标函数应该用较低的权重处理负类(标签== 0)。

*# Calculate a balancing ratio to account for the class imbalance*
balancing_ratio = train.filter(train['label']==0).count()/train.count()
train=train.withColumn("classWeights", F.when(train.label == 1,balancing_ratio).otherwise(1-balancing_ratio))

*# Create a logistic regression object*
lr = LogisticRegression(featuresCol = 'features', labelCol = 'label', weightCol="classWeights")

对于逻辑回归,pyspark.ml 支持在训练集上从模型的中提取一个[trainingSummary](https://spark.apache.org/docs/2.1.0/api/python/pyspark.ml.html#pyspark.ml.classification.LogisticRegressionSummary) 。这不适用于拟合的CrossValidator对象,这就是为什么我们从没有参数调整的拟合模型中取出它。

lrModel = lr.fit(train)
trainingSummary = lrModel.summary

我们可以使用它来绘制阈值-召回曲线,阈值-精度曲线,召回-精度曲线和阈值-F-测量曲线,以检查我们的模型如何执行。此阈值是一个预测阈值,它根据模型输出的概率确定预测的类别。模型优化包括调整该阈值。

*# Plot the threshold-recall curve*
tr = trainingSummary.recallByThreshold.toPandas()
plt.plot(tr['threshold'], tr['recall'])
plt.xlabel('Threshold')
plt.ylabel('Recall')
plt.show()

Figure 9

*# Plot the threshold-precision curve*
tp = trainingSummary.precisionByThreshold.toPandas()
plt.plot(tp['threshold'], tp['precision'])
plt.xlabel('Threshold')
plt.ylabel('Precision')
plt.show()

Figure 10

*# Plot the recall-precision curve*
pr = trainingSummary.pr.toPandas()
plt.plot(pr['recall'], pr['precision'])
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.show()

Figure 11

*# Plot the threshold-F-Measure curve*
fm = trainingSummary.fMeasureByThreshold.toPandas()
plt.plot(fm['threshold'], fm['F-Measure'])
plt.xlabel('Threshold')
plt.ylabel('F-1 Score')
plt.show()

Figure 12

随着我们提高预测阈值,召回率开始下降,而精确度分数提高。常见的做法是将相互竞争的指标可视化。

让我们使用交叉验证来调整我们的逻辑回归模型:

*# Create ParamGrid for Cross Validation*
paramGrid = (ParamGridBuilder()
             .addGrid(lr.regParam, [0.01, 0.5, 2.0])
             .addGrid(lr.elasticNetParam, [0.0, 0.5, 1.0])
             .addGrid(lr.maxIter, [1, 5, 10])
             .build())

cv = CrossValidator(estimator=lr, estimatorParamMaps=paramGrid, evaluator=evaluator, numFolds=5)

*# Run cross validations*
cvModel = cv.fit(train)
predictions = cvModel.transform(test)
predictions_pandas = predictions.toPandas()
print('Test Area Under PR: ', evaluator.evaluate(predictions))*# Calculate and print f1, recall and precision scores*
f1 = f1_score(predictions_pandas.label, predictions_pandas.prediction)
recall = recall_score(predictions_pandas.label, predictions_pandas.prediction)
precision = precision_score(predictions_pandas.label, predictions_pandas.prediction)

print('F1-Score: {}, Recall: {}, Precision: {}'.format(f1, recall, precision))

参数调整后,逻辑回归显示以下性能:

  • f1-得分:0.66
  • 回忆:0.84
  • 精度:0.54

梯度增强树分类器

梯度推进树是一种使用决策树集成的流行分类方法。对于类不平衡的数据,提升算法通常是很好的选择。PySpark 的 MLlib 支持它,所以让我们在我们的数据集上尝试一下:

gbt = GBTClassifier()*# Create ParamGrid for Cross Validation*
paramGrid = (ParamGridBuilder()
             .addGrid(gbt.maxDepth, [2, 4, 6])
             .addGrid(gbt.maxBins, [20, 60])
             .addGrid(gbt.maxIter, [10, 20])
             .build())
cv = CrossValidator(estimator=gbt, estimatorParamMaps=paramGrid, evaluator=evaluator, numFolds=5)

*# Run cross validations*
cvModel = cv.fit(train)
predictions = cvModel.transform(test)
predictions_pandas = predictions.toPandas()
print('Test Area Under PR: ', evaluator.evaluate(predictions))*# Calculate and print f1, recall and precision scores*
f1 = f1_score(predictions_pandas.label, predictions_pandas.prediction)
recall = recall_score(predictions_pandas.label, predictions_pandas.prediction)
precision = precision_score(predictions_pandas.label, predictions_pandas.prediction)

print('F1-Score: {}, Recall: {}, Precision: {}'.format(f1, recall, precision))

参数调整后,梯度增强树分类器显示出以下性能:

  • f1-得分:0.58
  • 回忆:0.56
  • 精度:0.61

决策树分类器

决策树是一种流行的分类和回归方法。

dt = DecisionTreeClassifier(featuresCol = 'features', labelCol = 'label')*# Create ParamGrid for Cross Validation*
paramGrid = (ParamGridBuilder()
             .addGrid(dt.maxDepth, [2, 4, 6])
             .addGrid(dt.maxBins, [20, 60])
             .addGrid(dt.impurity, ['gini', 'entropy'])
             .build())
cv = CrossValidator(estimator=dt, estimatorParamMaps=paramGrid, evaluator=evaluator, numFolds=5)

*# Run cross validations*
cvModel = cv.fit(train)
predictions = cvModel.transform(test)
predictions_pandas = predictions.toPandas()
print('Test Area Under PR: ', evaluator.evaluate(predictions))*# Calculate and print f1, recall and precision scores*
f1 = f1_score(predictions_pandas.label, predictions_pandas.prediction)
recall = recall_score(predictions_pandas.label, predictions_pandas.prediction)
precision = precision_score(predictions_pandas.label, predictions_pandas.prediction)

print('F1-Score: {}, Recall: {}, Precision: {}'.format(f1, recall, precision))

参数调整后,决策树分类器表现出以下性能:

  • f1-得分:0.56
  • 回忆:0.60
  • 精度:0.52

结论

该项目的目标是利用 Apache Spark 的分析引擎进行大规模数据处理的能力,来检测即将停止使用 Sparkify 音乐流媒体服务的客户。

我们应用了数据科学流程的典型步骤,如了解数据、数据准备、建模和评估。

逻辑回归模型显示出最高的性能(F1-得分:0.66,回忆:0.84,精度:0.54)。我们能够召回 84%的流失客户,并为他们提供特别优惠,防止他们删除 Sparkify 账户。然而,我们需要考虑 54%的中等精度分数。这意味着,在所有将获得特别优惠的顾客中,46%的顾客实际上对服务感到满意,不需要任何特殊待遇。

这篇文章的源代码可以在 GitHub 上找到。我期待听到任何反馈或问题。

预测员工保留率

原文:https://towardsdatascience.com/predict-employee-retention-901bfb2c8db5?source=collection_archive---------14-----------------------

Photo by Mario Gogh on Unsplash

介绍

我们知道,大公司有超过 1000 名员工为他们工作,所以照顾每个员工的需求和满意度是一项具有挑战性的任务,它导致有价值和有才华的员工离开公司,而没有给出适当的理由。

如今,员工流失是许多公司面临的一个主要问题。优秀的人才是稀缺的,难以留住的,而且需求量很大。鉴于快乐的员工和快乐的客户之间众所周知的直接关系,了解员工不满的驱动因素变得至关重要。

这个职位强调预测一个员工在一个组织中的保留,例如该员工是否会离开公司或继续留在公司。它使用以前为该公司工作的员工的数据,通过找到一种模式,以是或否的形式预测保留率。

我们正在使用的参数,如工资、在公司的工作年限、晋升、工作时间、工伤事故、财务背景等。通过这篇论文,一个组织可以选择它的策略来阻止优秀的代表离开组织。数据有 14999 个例子(样本)。下面是每一种的功能和定义:

  • satisfaction_level:满意度{ 0–1 }。
  • last_evaluationTime:自上次绩效评估以来的时间(以年为单位)。
  • number_project:工作时完成的项目数。
  • average_montly_hours:工作场所的平均月小时数。
  • time_spend_company:在公司工作的年数。
  • Work_accident:员工是否发生了工作场所事故。
  • left:员工是否离开了工作场所{0,1}。
  • promotion_last_5years:员工在过去五年中是否获得晋升。
  • 销售:员工工作的部门。
  • 工资:工资的相对水平{低、中、高}。

创建这篇文章的源代码可以在这里找到。

Data Set

将数据加载到数据框中并分离结果列。

FinalCode.py hosted by GitHub

数据预处理

数据集将“salary”和“sales”列作为分类数据,因此我们必须执行 OneHotEncoding & LabelEncoding 来将此数据转换为数字形式,并创建虚拟特征。我们必须删除第一个特征,以避免某些学习算法可能会遇到的线性依赖性。

之后,我们将把数据分成训练和测试数据集。

Preprocessing

回归模型

因为我们想要“是”或“否”形式的结果,例如员工是否会离开公司,所以最合适的回归模型是该数据集的逻辑回归。逻辑回归是一种分类算法,用于将观察值分配给一组离散的类。分类问题的一些例子是电子邮件垃圾邮件或非垃圾邮件、在线交易欺诈或非欺诈、恶性肿瘤或良性肿瘤。

为了计算我们的模型生成的结果的准确性,我们将使用混淆矩阵作为评估参数。

Logistic Regression

分类器

决策树是一种类似流程图的树结构,其中内部节点代表特征(或属性),分支代表决策规则,每个叶节点代表结果。决策树中最顶端的节点称为根节点。它学习根据属性值进行分区。它以递归的方式对树进行分区,称为递归分区。

随机森林是一种监督学习算法。森林是由树木组成的。据说树越多,森林就越健壮。随机森林在随机选择的数据样本上创建决策树,从每棵树获得预测,并通过投票选择最佳解决方案。它还提供了一个很好的特性重要性指标。

在我们的数据集中,我们将使用这两个分类器以是和否的形式对结果进行分类。

Decision Tree and Random Forest Classifier

结论

Factors affecting the employment

在上面的图表中,x 轴上从 0 到 6 的数字代表较高的项目,较低的销售级别,较高的时间,促销,工作事故,或非工作事故,无事故。以上每一项都是影响就业的因素,用 HigherTime 表示,有四年以上工作经验但仍未得到任何晋升的员工是 1750 人,这是一个很大的数字,用 LowerSalary 表示即使他们的评估分数高于 3,工资水平也很低的员工是 750 人。

因此,在评估该数据集后,我们了解到较低的工资水平,即使员工工作超过 4 年也没有晋升是员工离开组织的两个主要原因。

从赛季表现预测花样滑冰世锦赛排名

原文:https://towardsdatascience.com/predict-figure-skating-world-championship-ranking-from-season-performances-7461dc5c0722?source=collection_archive---------38-----------------------

体育分析

第五部分:序贯多因素模型

背景

在项目的前几部分,我试图根据运动员在该赛季前几场比赛中获得的分数来预测一年一度的世界花样滑冰锦标赛的排名。主要策略是将滑手效应(每个滑手的内在能力)与事件效应(一个事件对滑手表现的影响)分开,以便建立更准确的排名。

First 4 columns: normalized scores in each factor. Last column: combined score from logistic regression model. Heatmap color: rank of a skater in each factor. Text color: rank of a skater in the world championship

具体来说,在项目的第三部分中,每个滑手都由多个潜在因素代表(见附图中的前 4 列)。然后,在第四部分的中,使用逻辑回归模型来组合每个选手的潜在分数,并根据这些组合分数对选手进行排名(图表中的最后一列)。该模型使用双重交叉验证进行评估:它在 5 个季节进行训练,并在另外 5 个季节进行评估。

问题

使用多因素模型来学习运动员的潜在得分,使用逻辑回归模型来预测运动员的排名,我们试图通过以下方式改进这两个模型:

  • 增加潜在因素的数量
  • 提前停止多因子模型的梯度下降算法
  • 提前停止逻辑回归模型的梯度上升算法

后两种提前停止策略旨在防止模型过度拟合赛季得分(对于多因素模型)或世界锦标赛排名(对于逻辑算法模型),以便他们可以很好地预测他们没有训练过的赛季。

然而,当我们检查这两个模型的哪个停止迭代对验证集中的肯德尔τ的改善最大时,得到了一个奇怪的结果(与基线模型相比):

从上表可以看出,对于大多数因素(除了 2),当逻辑回归运行 0 次迭代时,Kendall 的改善最大。这意味着使用逻辑回归比根本不使用它更糟糕!

潜在分数的问题

为什么逻辑回归在合并潜在得分方面做得如此糟糕?我的假设是,在 part 3 中学到的潜在分数本来就不好。更具体地说,它们完全是任意的,并没有真正抓住每个选手的潜在能力。

要了解原因,让我们重温一下多因素模型的公式(如第 3 部分所述):

  • ŷ_event-skater:给定运动员在给定项目中的预测得分
  • 存在于每个项目和每个运动员身上的潜在因素
  • θ_event,f:给定事件的潜在因素得分
  • θ_skater,f:给定选手的潜在因素得分

从上面的公式中,我们看到,学习潜在得分只是为了尽可能地逼近赛季得分,而不是其他。因此:

  • 如果两个因素互换,预测的分数——因此模型性能(RMSE)——将保持完全相同,但这些因素现在与它们之前完全相反。
  • 作为一个推论,没有一个因素会比其他因素更重要,他们的产品和相应的事件得分在每个赛季得分中的贡献大致相同。
  • 这似乎不符合我们对滑冰者的合理直觉:可能有一些因素比其他因素更重要,对滑冰者赢得的赛季分数贡献更大。

当绘制两个不同季节(2005 年和 2017 年)的 2 因素模型的潜在得分时,可以证明潜在因素模型的这些弱点。此外,对于每个季节,梯度下降开始时的潜在得分用两个不同的随机种子(42 和 24)初始化:

Heatmap color: Ranking of skater by each individual factor. Text color: rank of a skater in the world championship

从上图可以看出:

  • 即使只是在初始化梯度下降时改变种子,我们最终会在两个因素(白色部分)中的每一个上得到非常不同的潜在选手分数。事实上,潜在得分似乎在很大程度上互换了,因为当使用不同的种子时,每个单独因素产生的排名似乎交换了位置。
  • 这也可以在代表每个因素(以及基线得分)对赛季得分的平均贡献的饼图中看到:一个种子显示因素 1 的贡献大于因素 2,而另一个种子显示相反。然而,与之前的猜测一致,这两个因素的影响大致相同。
  • 更糟糕的是,即使是同一个因素,该因素在不同季节的得分和排名也几乎没有相似之处。因此,很难证明将不同季节的差异矩阵堆叠在一起来训练第 4 部分中的逻辑回归模型是合理的,因为这样做,我们假设相同的因子代表不同季节的相同事物。

因此,我们需要彻底检查潜在因素模型,使一些因素优先于其他因素,并确保同一因素在不同季节之间保持一致。

依次训练潜在因素

对当前潜在因素模型的一个明显的修正是只更新一个因素的潜在得分,直到 RMSE 达到稳定状态。然后,我们开始更新下一个因子,以此类推,希望 RMSE 进一步减小。

这可以通过修改多因子模型的梯度下降算法来完成,以便它一次更新一个因子。然而,实现这一点的更方便的方法是:

  • ****第一步:在赛季得分上训练单因素模型,得到第一因素的潜在事件和潜在得分(关于实现单因素模型的更多细节,参见第二部分)。
  • ****第二步:在第一步后的负残差上训练另一个单因素模型,得到第二个因素的潜在得分。使用负残差(而不仅仅是残差),因为在这个项目中,我们将残差定义为预测得分-真实得分。因此,它的负值:真实得分-预测得分,表示第一个因素没有考虑的赛季得分的剩余部分。
  • ****第三步:我们可以对任意多的因素重复第二步,并简单地将所有单个因素收集到潜在选手分数的矩阵中。换句话说,这些步骤的输出将与多因素模型相同,但在这种情况下,因素是按顺序学习的。

编码顺序多因素模型

鉴于我们已经在第 2 部分中浏览了单因子模型的梯度下降算法的代码,下面的代码块显示了如何简单地修改现有代码以按顺序训练多个因子。

**# Transform long score table to pivot form
season_pivot = pd.pivot_table(sample_season_scores[['name', 'event', 'score']], values='score', index='name', columns='event')# Convert pivot table to numpy array
true_scores = season_pivot.values# Store skater and event names to retrieve later
skater_names = list(season_pivot.index)
event_names = list(season_pivot.columns)# Create lists to store scores of individual  
multi_skater_scores = []
multi_event_scores = []
multi_baselines = []# Run gradient descent algorithm
alpha = 0.0005
n_factors = 2
init_seed = 42**for n in range(n_factors):**
    # 1\. Initialize baseline, event, and skater scores
    random_state = np.random.RandomState(seed=init_seed**+n**)
    baseline = random_state.random_sample()
    skater_scores = random_state.random_sample((len(skater_names), 1))
    event_scores = random_state.random_sample((1, len(event_names)))

    # Run gradient descent
    for i in range(1000):
        # 2a. Calculate gradients
        predicted_scores = skater_scores @ event_scores + baseline
        residuals = predicted_scores - true_scores baseline_gradient = np.nansum(residuals)
        event_gradients = np.nansum(residuals * skater_scores, axis=0, keepdims=True)
        skater_gradients = np.nansum(residuals * event_scores, axis=1, keepdims=True) ### 2b. Update latent scores using gradients
        baseline = baseline - alpha * baseline_gradient
        event_scores = event_scores - alpha * event_gradients
        skater_scores = skater_scores - alpha * skater_gradients

    # Store result for each factor 
    multi_skater_scores.append(skater_scores.ravel())
    multi_event_scores.append(event_scores.ravel())
    multi_baselines.append(baseline)

    # Reset true score matrix as negative residual
 **final_residuals = skater_scores @ event_scores + baseline - true_scores    
    true_scores = -final_residuals**# Create latent score matrices with previously-stored name
multi_skater_scores = pd.DataFrame(multi_skater_scores).T
multi_skater_scores.index = skater_namesmulti_event_scores = pd.DataFrame(multi_event_scores).T
multi_event_scores.index = event_names**

主要修改以粗体突出显示,包括:

  • 将梯度下降算法包含在一个for循环中——for n in range(n_factors)——以便它可以对多个因子重复。
  • 这伴随着每个循环/因子末尾附近的代码块,其中用于训练下一个因子的true_scores矩阵被设置为前一个因子的负残差矩阵。请注意,我们需要在每次梯度下降后计算一个final_residual矩阵:这将使用潜在得分的最新值更新残差,然后将其交给下一个因子。
  • 最后,在实现该算法时,我注意到,如果使用新的种子来初始化每个后续因子的梯度下降,模型的 RMSE 将下降得更多。也许一个新的种子阻止了下一个因子达到与前一个因子相同的局部最小值。在任何情况下,种子现在都与代表每个因子的数字n联系在一起,为了灵活起见,它被添加到用户指定的种子数字:random_state = np.random.RandomState(seed=init_seed+n)之上。

双因素示例的结果

让我们回顾一下前面两个不同季节的双因素例子,每个季节有两种不同的种子。不同的是,现在我们依次训练两个潜在因素,而不是像以前一样同时训练。

White text: latent score in each factor. Heatmap color: Ranking of skater by each individual factor. Pie chart: average contribution of each factor, along with the baseline score, in season scores

从上面显示的结果,我们可以看到:

  • 在初始化模型时改变种子的效果对最终的潜在分数的影响要小得多,因为分数即使在不同的种子数目开始时也保持相对稳定。
  • 第一个因素现在比第二个因素对赛季得分的贡献大得多。事实上,第二个因素的贡献是如此之小(<0.1%) that it did not even show up in the pie charts at the bottom of the diagram. The first factor also has much better predicted ranking than the second, as evidenced by its “smoother” rank colors. This indicates that there is a clear hierarchy in which some factors are more important than others.
  • Furthermore, this hierarchy consistently holds across different seasons, as evidenced by the relative contribution of the first factor in each pie chart between the 2005 and 2017 seasons. Within each factor, the ranking produced between the seasons also match quite closely (at least for the first factor). This then allows us to stack different seasons together to train the logistic regression model.

These results confirm that we should learn the latent factors sequentially. However, once these factors are learned, we can use logistic regression to combine them and rank the skaters exactly as before (see 第 4 部分关于如何做到这一点)。

结果

为了更清楚地显示顺序多因素模型是如何工作的,我们可以在梯度下降的迭代中监控其性能。每个因素训练 1000 次迭代,每 100 次迭代的结果显示在下面熟悉的动画仪表盘中(针对 2017 赛季的男滑手):

上面的仪表板清楚地显示了一个因素完成训练的时间,之后剩下的赛季分数用于训练下一个因素。RMSE 下降的明显“步骤”以及当一个新的因素开始训练时热图中残差的突然减少都证明了这一点。

改进序贯多因素模型

为了改进顺序多因素模型,使其能够更好地对运动员进行排名——当与逻辑回归模型结合时——我们对第 4 部分中的原始多因素模型使用相同的策略:增加因素的数量,并提前停止。

增加因子的数量

使用相同的 2 折交叉验证计划,我们在不同数量的因子(在α=0.005 和 1000 次迭代)下训练顺序多因子模型,然后在潜在得分上训练逻辑回归模型(也在α=0.005 和 1000 次迭代)。对于每个因素的数量,我们然后从基线模型获得肯德尔τ的平均训练和验证改进。

Bold lines: average improvement in Kendall’s tau across 10 years. Lighter lines: improvement in Kendall’s tau for each year

因子数增加 2 倍(从 2 一直到 128 个因子)的结果如上图所示。从这个图表中,我们可以看到:

  • 与来自原始多因素模型的结果相似,序列模型仍然可以在更高数量的因素下过度拟合训练数据,这由训练集中的高 Kendall tau 改进所证明,但与验证集中的基线模型相比,Kendall tau 差得多。
  • 然而,在验证集中,顺序多因子模型的表现略好于原始模型,尤其是在因子数量较少的情况下。这是有意义的,因为顺序模型首先训练最重要的因素,所以即使结合这些因素中的几个也可能提供一个体面的排名。当然,排名准确性仍然没有明显好于季节平均值的基线模型(水平线为零),这由验证集中 Kendall 的 tau 改进的置信区间所证明。
  • 然而,这些置信区间比原始多因素模型的置信区间相对更窄。这表明,按顺序训练潜在因素是一个很好的选择:这些因素的排名在不同季节之间不太不稳定,这可能是由于这些因素在不同季节之间的一致性,如前所述。

提前停止

与第 4 部分类似,我们可以提前停止序列多因素模型的梯度下降,以及随后的逻辑回归模型的梯度上升。通过阻止这些算法完全收敛,我们可能不会获得最佳的训练性能,但希望这些模型不会过度适应训练数据,并更好地进行概括。

因此,我们可以改变两个模型的 3 个超参数:

  1. 因素数量
  2. 多因素模型梯度下降的停止迭代
  3. logistic 回归模型梯度上升的停止迭代

对于这 3 个超参数的每个组合,我们记录其平均训练和肯德尔τ与基线模型相比的验证改进。这些结果绘制如下:

  • 多因子模型,停止迭代次数从 0 到 100(每个因子),间隔 5 次迭代
  • 停止迭代从 0 到 100,间隔 5 次迭代的逻辑回归模型
  • 因子数量以 2 的倍数增加,从 2 到 128 个因子

上述结果在很大程度上反映了原始多因素模型的结果,即:

  • 因子数量加倍的效果在提高训练集中的 Kendall tau 方面最为显著,但遗憾的是,也恶化了验证集中的 Kendall tau——这是反映早期观察的过度拟合的经典案例。
  • 对于涉及早期停止的其他两个超参数,我们看到,在两个模型的迭代次数都很低的情况下,虽然训练性能可以理解地乏善可陈,但验证性能是最不令人担忧的。这可以从验证集(底行)的每个方块左下角的亮淡色点看出。换句话说,很早就停止这两个模型可以极大地帮助减少模型过度拟合。
  • 与原始的多因素模型相比,对来自序列模型的潜在分数运行逻辑回归,即使只是几次迭代,也比不运行所有的要好得多。这可以通过底部行的每个方块中的垂直红线看出,这意味着如果我们不运行逻辑回归,即运行 0 次迭代,则肯德尔τ的有效性非常低。

对于每一个因子,我们注意到与基线模型相比,两个模型在验证集中的肯德尔τ值的改善最大的停止迭代时,也可以看出这一点:

从上表中,我们可以看到,在 4 个因素下,顺序多因素模型的每个因素迭代 35 次,之后的逻辑回归模型迭代 30 次,相对于其他超参数组合,预测排名在验证集中具有最高的平均肯德尔τ。然而,请注意,与季节平均值的基线模型相比,这仅仅是 0.009 的微小改进。

对女性滑冰运动员重复同样的分析,我们看到 3 个模型超参数的相互作用基本相同:

然而,对于女性速滑运动员,超参数的最佳组合是 2 个因子,顺序模型的每个因子 65 次迭代,逻辑回归模型的 5 次迭代。这导致肯德尔τ的平均改善比验证集中的基线模型稍高 0.015。

决赛成绩

使用上面这些微调的超参数,我们可以从 2017 赛季的男子滑冰运动员的逻辑回归中找到模型系数和综合得分:

First 4 columns: normalized scores in each factor. Last column: combined score from logistic regression model. Heatmap color: rank of a skater in each factor. Text color: rank of a skater in the world championship

  • 如前所述,序列模型的第一个因素是最重要的,不仅在于它对赛季得分的贡献,还在于预测世界冠军排名(见随附的热图)。因此,与其他因素相比,它的模型系数高得多(至少高 10 倍)也就不足为奇了。
  • 然而,这意味着结合潜在的分数可能不会比仅仅通过第一个因素对运动员进行排名产生更多的改进,这正是在第二部分中讨论的单因素模型的策略。
  • 事实上,在这个例子中,仅通过第一个因素对溜冰者进行排名,在 276 对中就产生了 241 对正确的对,对应于 0.746 的肯德尔τ。相比之下,根据运动员的综合得分对他们进行排名只会产生多一对正确的选手,肯德尔的τ值为 0.754。从随附热图的第一列和最后一列之间非常相似的等级颜色可以看出这一点。

因此,当汇总所有季节时,我们可以看到多因素模型仅略微改进了在第 2 部分中开发的提前停止单因素模型:

即便如此,多因素模型仍然无法超越季节平均值的基线模型,因为其肯德尔τ改进的 95%置信区间(在验证集上)仍然包含零水平线。此外,由于我们已经使用了所有 10 个赛季的世界冠军排名来调整模型的超参数,上述结果很可能过于乐观。

因此,在接下来的和项目的最后一部分,我会尝试将所有现有的模型合并成一个,看看最后的努力是否能提高预测的排名。然后,我将使用尚未分析的剩余 4 个赛季对项目开始以来开发的所有排名模型进行基准测试。

资源

正如在第三部分末尾所讨论的,寻找事件和运动员的潜在得分无非是将赛季得分的事件-运动员矩阵分解成运动员特定矩阵和事件特定矩阵,然后它们可以相乘以近似原始赛季得分矩阵。

事实上,用于训练多因素模型的梯度下降算法几乎与网飞挑战赛中用于向用户推荐电影的著名的 FunkSVD 矩阵分解算法相同(基于他们对每部电影留下的评级)。在这种情况下,用户=选手,电影=事件,评分=赛季得分。

然而,我一点也不知道原来的芬克先生也是一次训练一个人的潜在特征!事实上,在他的博客文章的结尾,他的模型的 RMSE 图与我的动画仪表板中的台阶状形状相同。我想我应该从一开始就更仔细地阅读他的文章。

RMSE plot from the original FunkSVD

从赛季表现预测花样滑冰世锦赛排名

原文:https://towardsdatascience.com/predict-figure-skating-world-championship-ranking-from-season-performances-8af099351e9c?source=collection_archive---------24-----------------------

体育分析

第 3 部分:多因素模型

背景

在项目的前几部分,我试图根据运动员在该赛季前几场比赛中获得的分数来预测一年一度的世界花样滑冰锦标赛的排名。主要策略是将滑手效应(每个滑手的内在能力)与事件效应(一个事件对滑手表现的影响)分开,以便建立更准确的排名。

针对这个排名问题提出了几个模型,其中一对运动员的赛季得分可以近似为:

  • 一个基线得分,它在所有赛季得分中保持不变
  • 潜在的事件分数,该分数在所有参加该事件的选手中是不变的
  • 一个潜在的滑冰者分数,该分数在该滑冰者参加的所有比赛中都是不变的

这些潜在得分可以相加在一起(加法模型),或者相乘在一起(乘法模型,或者两者的组合(混合模型):

  • 正如项目第一部分所讨论的,前两个模型可以重写为简单的线性模型,模型系数就是潜在得分本身。因此,通过求解线性回归的正规方程可以很容易地找到它们
  • 相比之下,没有封闭形式的解决方案来学习混合模型的潜在分数。然而,这些分数可以通过应用梯度下降来最小化预测分数和该赛季的真实分数之间的平方差来找到。项目的第二部分更详细地讨论了混合模型的梯度下降算法。
  • 最后,一旦学习了每个模型的潜在分数,模型的预测排名就只是潜在溜冰者分数的排名,从最高到最低。

问题

来自所有 3 个先前模型的预测排名与基于他们的赛季平均成绩的简单排名的基线模型相比较。用于评估这些模型的指标是肯德尔排名相关系数 —也称为肯德尔的τ—相对于实际的世界锦标赛排名:如果模型的预测排名具有更接近 1 的肯德尔τ,则模型更好,这意味着预测排名与当年世界锦标赛的实际排名更相关。你可以查看这个项目的第一部分来了解 Kendall 的 tau 是如何工作的,包括如何计算一个玩具的例子。

从以上关于在训练集中选择的 10 年(14 年中的 10 年)中的这三个模型的报告来看,所有模型都比赛季平均值的基线模型更好地逼近赛季得分,这由它们与基线模型相比平均更低的均方根误差 (RMSE)来证明。

然而,在更重要的 Kendallτ度量中,遗憾的是,与基线模型相比,这些模型没有提供该度量的任何显著增加,因为对于男性和女性溜冰者,它们与基线之间的 Kendallτ差异的 95%置信区间包含零。

为了改进这些模型,我尝试了几种策略来防止模型过度适应赛季分数,这可能会很吵,可能不会反映运动员的真实能力(也许他们在比赛当天早些时候食物中毒)。因此,这可能会更好地捕捉运动员的真实能力,并为世界锦标赛更准确地排名。

这些策略包括岭回归提前停止,这是减少机器学习中模型过度拟合的两种最流行的策略。然而,这两种策略都未能显著提高模型的肯德尔τ值。因此,如果我们想更好地预测运动员在世界锦标赛中的排名,就需要一种新的模型。

多因素模型

为了开发这个新模型,让我们重新回顾一下在项目的前一部分中开发的混合模型:

  • ŷ_event-skater:给定运动员在给定项目中的预测得分
  • θ_baseline:基线得分(整个赛季得分不变)
  • θ_event:该项目潜在得分(运动员之间不变)
  • θ_skater:溜冰者潜在得分(跨事件恒定)

在上面的公式中,我们可以看到,每个项目都用一个单独的潜在得分(θ_event)来表示,每个滑手也用一个单独的潜在得分(θ_skater)来表示。

但是,如果不是单一的潜在分数,而是每个滑手都用多个潜在因素来表示呢?例如,他有多喜欢在早上滑冰,或者她有多习惯在高海拔地区比赛。类似地,每个事件可以用相同的因素来表示:事件发生在早上多早,或者发生在什么高度。

因此,一个运动员和一个事件之间的每个对应因素可以相乘,然后将这些乘积的总和加到基线得分上,以预测该季节中该运动员-事件对的真实得分:

  • ŷ_event-skater:给定事件中给定选手的预测得分
  • f:存在于每个项目和每个滑手身上的潜在因素
  • θ_event,f:给定事件潜在因素得分
  • θ_skater,f:给定选手潜在因素得分

寻找潜在因素

与前面的模型类似,多因素模型中的潜在因素可以通过最小化赛季期间真实得分和预测得分之间的平方差之和来找到(从这里开始称为残差):

  • J:模型的目标函数。继续以 2017 赛季的男子滑冰运动员为例,这将是 1 个基线分数、9 个项目中每个项目的所有n潜在因素(以蓝色突出显示)以及 62 名滑冰运动员中每个项目的所有n潜在因素(以黄色突出显示)的函数。
  • ŷ_e,s:多因素模型预测的一对运动员的得分
  • y_e,s:在赛季中记录事件-溜冰者对的真实分数
  • θ_baseline:基线得分
  • θ_e,f:事件e的潜在因素f的得分
  • θ_s,f:滑手s潜在因素f的得分

梯度下降算法

尽管多因素模型中的潜在分数激增——最后的双倍总和看起来确实很可怕——让我们保持冷静,继续梯度下降。首先,让我们对潜在得分的目标函数进行微分,以获得它们的梯度。

寻找渐变

类似于混合模型,基线得分的梯度就是赛季中所有项目-运动员对的残差之和:****

接下来,我们来算出给定事件 **i** ( θ_ei,k)的某因子 **k** 梯度:****

  • 当取这个梯度时,所有与这个事件无关的残差平方都将消失。剩下的就是那些涉及到参加这个项目的滑手。因此,只有这些平方残差的导数才有效。
  • 当对参加这个项目的每个溜冰者s求导时,基本的链式法则起作用:外部求导返回这个项目中那个溜冰者的余数。然后,我们对残差本身进行内部求导,这将使基线、真实分数和其他所有潜在因素的分数(不是 T1)消失。剩下的是那个滑冰运动员(θ_s,k)的潜在因素k的单个分数,因为它之前被乘以了这个非常事件(θ_ei,k)的因素k的相应分数。
  • 因此,对于参加这个事件的每个溜冰者s,我们将把外部导数——那个溜冰者在事件i中的残差——乘以内部导数——他拥有的潜在因素k的分数。将所有参加本次活动的滑冰运动员的这些产品相加,最终将得出事件i的因子k的梯度。
  • 这个梯度公式的巧妙之处在于,外导数中的残差不依赖于我们感兴趣的因子k。因此,可以多次重复使用该残差,以找到该事件的所有其他因素的梯度(通过将其乘以不同的内部导数)。这将在我们编写梯度下降算法的时候用到。

同样,给定滑手 **j**的随机因子 **k** 梯度就是上面事件梯度的滑手类似物:****

与前一个公式相比,该公式的主要区别在于:

  1. 我们正在总结这位选手参加的所有项目
  2. 内部导数是每个事件(θ_e,k)的潜在因素k的相应分数

但是,请注意,外部导数中的残差仍然独立于k,并且可以被重新用于找到该溜冰者的所有其他因素的梯度(除了k)。

更新潜在得分

一旦计算出每个项目和每个运动员的所有因素的梯度,我们就可以更新这些因素的潜在得分:

  • 类似于混合模型的梯度下降,更新步骤中学习率α的存在将控制梯度下降的速度并帮助其更好地收敛(如果选择了合适的学习率)。
  • 请注意,每个因素的更新步骤是独立于所有其他因素完成的。这也将帮助我们实现多因子模型梯度下降的 Python 代码。

摘要

给定上述梯度公式和更新规则,多因子模型的梯度下降可总结为:

Gradient descent algorithm for multi-factor model

通过以上总结,可以看出:

  • 一旦计算了每个事件-滑冰运动员对的残差(步骤 2a),它可以用于独立于每个其他潜在因素单独计算每个潜在因素k的梯度(步骤 2c-i)。换句话说,这就好像每个项目和每个滑手只有一个单一的潜在得分,它们的梯度计算方法与单因素混合模型完全相同。之后,残差被重新用于计算下一个潜在因子的梯度(使用完全相同的方法)。
  • 此外,每个因子k的更新步骤也独立于所有其他因子(步骤 2c-ii)。因此,我们可以逐个更新每个潜在因素的分数,直到我们完成步骤 2 的一次迭代。

换句话说,多因素模型的梯度下降算法实际上与单因素混合模型的算法相同,只是一旦计算出每个项目-运动员对的残差,我们需要计算梯度并逐个更新每个潜在因素的分数(通过重用残差)。

多因素模型编码

让我们使用与混合模型相同的玩具示例,一个season_scores熊猫数据框架,包含 4 名滑冰运动员(MAJOROV,FERNANDEZ,GE,MURA)的 7 个赛季成绩和不同国家(CA,FR,RU)的 3 项赛事:

类似于混合模型,我们首先将该数据框从长格式转换为数据透视表格式,运动员作为行,事件作为列,条目是每个事件-运动员对的分数。注意,不是所有的滑手都参加所有的项目,所以数据透视表中有缺失值,用NaN(不是数字)表示。

**season_pivot = pd.pivot_table(season_scores[['name', 'event', 'score']], values='score', index='name', columns='event')**

然后,我们将熊猫数据透视表转换成一个 4×3 的 numpy 矩阵true_scores,这样我们可以更容易地操作它。这实际上将删除所有的行和列名称,所以让我们存储运动员名称(行)和事件名称(列),这样我们在运行梯度下降后仍然可以识别它们。

**skater_names = list(season_pivot.index) 
# ['Alexander, MAJOROV', 'Javier, FERNANDEZ', 'Misha, GE', 'Takahito, MURA']event_names = list(season_pivot.columns) 
# ['CA', 'FR', 'RU']true_scores = season_pivot.values
# array([[   nan,    nan, 192.14],
#        [   nan, 285.38, 292.98],
#        [226.07, 229.06,    nan],
#        [222.13, 248.42,    nan]])**

第一步:初始化基线、潜在项目和潜在选手得分

对于基线分数,类似于混合模型,我们可以只使用一个[RandomState](https://docs.scipy.org/doc/numpy-1.15.0/reference/generated/numpy.random.RandomState.html)对象的[random_sample](https://docs.scipy.org/doc/numpy-1.15.0/reference/generated/numpy.random.RandomState.random_sample.html#numpy.random.RandomState.random_sample)方法(带有特定的种子)来返回一个介于 0 和 1 之间的随机数。

**random_state = np.random.RandomState(seed=42)
baseline = random_state.random_sample() # 0.37**

对于这个玩具的例子,让我们假设对于每个事件和每个溜冰者,我们有 2 个潜在的因素。因此,我们可以将 4 个溜冰者的所有潜在分数初始化为 4×2 矩阵。类似地,我们可以将 3 个事件的所有潜在得分初始化为 2×3 矩阵。

**skater_scores = random_state.random_sample((4, 2))
# array([[0.95071431, 0.73199394],
#        [0.59865848, 0.15601864],
#        [0.15599452, 0.05808361],
#        [0.86617615, 0.60111501]])event_scores = random_state.random_sample((2, 3))
# array([[0.70807258, 0.02058449, 0.96990985],
#        [0.83244264, 0.21233911, 0.18182497]])**

步骤 2a:计算每对运动员的残差

以矩阵形式初始化这些潜在得分的原因是,通过将这些向量相乘(使用矩阵乘法运算符@)并在此基础上添加基线,可以一次性计算出每对运动员的预测得分:

**predicted_scores = skater_scores @ event_scores + baseline
# array([[1.65705782, 0.54954103, 1.42974207],
#        [0.92831034, 0.41999206, 0.98355296],
#        [0.53334684, 0.39008461, 0.53640179],
#        [1.48824946, 0.52001014, 1.32395061]])**

为了更清楚地说明这一点,下面的图表显示了两个潜在矩阵(大小分别为 4×2 和 2×3)与添加在顶部的基线相乘,以形成每对运动员的 4×3 预测得分矩阵。

Dotted line: implied indices of skaters and events, even though they don’t exist in numpy arrays

用红色突出显示的是如何通过此操作计算出一对运动员(CA-MAJOROV)的预测得分。请注意,两个潜在得分矩阵的乘法运算是如何自动对一对运动员的所有因素进行求和的,方法是在各自的潜在得分矩阵中取该运动员的相应行和该项目的相应列的点积。

一旦计算出每对运动员的预测得分,寻找残差就像从预测得分矩阵中减去真实得分的 numpy 数据透视表一样简单:

**residuals = predicted_scores - true_scores**

回想一下,我们的true_scores numpy 矩阵包含赛季中不存在的事件-运动员配对的NaN值。因此,当计算残差时,那些对的对应残差也是NaN

步骤 2b:计算基线得分的梯度并更新它

就基线分数而言,混合模型的梯度没有任何变化,它只是所有残差的总和,并使用该梯度更新分数。

因此,为了计算基线梯度(baseline_gradient),我们使用相同的[np.nansum](https://docs.scipy.org/doc/numpy/reference/generated/numpy.nansum.html)函数对原因中现有的事件-溜冰者对的残差求和,同时忽略不存在的对,它们是residuals矩阵中的NaN。最后,基线分数通过减去其梯度乘以学习率alpha来更新。

**alpha = 0.0005
baseline_gradient = np.nansum(residuals)
baseline = baseline - alpha * baseline_gradient**

步骤 2c:对于每个因子 k,计算其在每个项目和每个选手中的梯度,并通过相应的梯度更新每个分数

下面是整个步骤的代码:

**# For each factor k
for k in range(2):
    # Extract latent scores for that factor
    skater_scores_k = skater_scores[:, [k]]
    event_scores_k = event_scores[[k], :] # Step 2c-i: Find gradients of factor k for all skaters & events
    event_gradients_k = np.nansum(residuals * skater_scores_k, axis=0, keepdims=True)
    skater_gradients_k = np.nansum(residuals * event_scores_k, axis=1, keepdims=True) # Step 2c-ii: Update scores of factor k for all skaters & events
    event_scores[[k], :] = event_scores_k - alpha * event_gradients_k
    skater_scores[:, [k]] = skater_scores_k - alpha * skater_gradients_k**

正如前面在梯度下降推导中提到的,一旦计算了残差(步骤 2a),我们就将每个因素视为完全独立于所有其他因素。因此,我们可以计算梯度,并通过循环for k in range(2)(在我们的玩具示例中,因子数为 2)逐个更新每个潜在因子的分数:第一个因子将有k=0,而第二个有k=1

让我们先来看看第一个因素(k=0)的步骤:

  • 首先,我们提取该因素的潜在得分。这将是事件(event_scores[[0], :])的潜在矩阵中的第一行,以及溜冰者(skater_scores[:, [0]])的潜在矩阵中的第一列。使用这些有趣的方括号0的原因是,我们希望将第一个因素的潜在分数保存为跨事件的大小为(1,3)的行向量和跨溜冰者的大小为(4,1)的列向量。如果没有这些括号,numpy 只会将这些切片折叠成一维数组。

  • 对于所有事件(event_scores_k)和所有溜冰者(skater_scores_k)的第一因素的这些提取的分数,问题现在简化为混合模型的单因素问题。因此,计算该因子中所有分数的梯度与混合模型完全相同:
**event_gradients_k = np.nansum(residuals * skater_scores_k, axis=0, keepdims=True)
skater_gradients_k = np.nansum(residuals * event_scores_k, axis=1, keepdims=True)**

我不会对这一步做太多的详细说明,因为它在项目的第 2 部分中有完整的解释,包括np.nansum : axis的参数,它控制梯度的求和方向(跨运动员或跨事件),以及keepdims,它防止 numpy 将梯度折叠为 1-D,并为后面的更新步骤保持它们的行/列向量形式。简而言之,我将在下面的图表中总结这些操作,首先计算所有事件中的梯度(第一个潜在因素):

Highlighted in red: relevant values for an example event (RU). Dotted arrows: numpy broadcasting of latent skater scores (for the first factor)

然后,为了计算所有溜冰者的梯度(第一潜在因素):

Highlighted in red: relevant values for an example skater (Takahito, MURA). Dotted arrows: numpy broadcasting of latent event scores (for the first factor)

  • 一旦计算出第一个潜在因素的梯度,就可以用它们来更新所有运动员和项目中该因素的得分。
**event_scores[[0], :] = event_scores_k - alpha * event_gradients_k
skater_scores[:, [0]] = skater_scores_k - alpha * skater_gradients_k**

一旦第一个因素的潜在得分被更新,循环就移动到第二个因素(k=1)并重复同样的事情:计算所有事件和运动员中该因素的梯度(使用来自步骤 2a 的相同的residuals矩阵),并使用这些梯度更新潜在得分。以下是提取的单因素向量,将在k=1时处理:

最后,整个步骤 2 重复多次,在计算残差、寻找梯度和更新潜在得分之间移动,直到每个循环之后的 RMSEnp.sqrt(np.nanmean(residuals**2))已经稳定。经过 1000 次迭代后,这个玩具示例的多因子模型的 RMSE 达到了惊人的 0.004(相比之下,第 2 部分中的单因子混合模型为 4.30),迭代与迭代之间的差异为 1e-5。

事实上,当将潜在选手和事件矩阵相乘以获得赛季期间的最终预测分数时,模型的 RMSE 如此之低一点也不奇怪,因为预测分数(左)实际上与真实分数(右)相同:

Predicted season scores after 1000 iterations of gradient descent vs. true scores

最后,可以在 pandas DataFrame中检索所有选手的 2 个因素的潜在分数,将先前存储的选手姓名作为行索引添加回来,并将因素(01)作为列:

**pd.DataFrame(skater_scores, index=skater_names)
#                             0         1
# Alexander, MAJOROV  10.226857  3.122723
# Javier, FERNANDEZ   16.009246  4.594796
# Misha, GE           11.919684  7.280747
# Takahito, MURA      14.059611  3.020423**

算法管用!然而,当我将它应用于真实数据时,它需要相当长的时间才能运行,尤其是当涉及到许多因素时。当然,我也记录了一系列中间值,但是让我们看看如何使梯度下降更快,这样迭代模型就不那么痛苦了。

用数字广播优化梯度下降

如前所述,多因素模型的梯度下降与单因素模型的梯度下降相同,但逐个应用于每个因素。这是算法的“幼稚”实现,因为我们只是按照数学告诉我们的方式编码。

然而,我们可以计算梯度并同时更新所有潜在因素的分数,而不是通过步骤 2c 中昂贵的for循环逐一处理这些因素。通过再次利用 numpy 的广播,这是可能的,这一次有一个额外的因素维度(而不仅仅是运动员和事件)。步骤 2c 的修改版本的代码如下:

**# 2c-i: Calculate gradients for all factors
# Reshape matrices
reshaped_residuals = residuals[np.newaxis, :, :]
reshaped_event_scores = event_scores[:, np.newaxis, :]
reshaped_skater_scores = skater_scores.T[:, :, np.newaxis]# Calculate gradients
event_gradients = np.nansum(residuals * reshaped_skater_scores, axis=1)
skater_gradients = np.nansum(residuals * reshaped_event_scores, axis=2).T# 2c-ii: Update latent scores for all factors
event_scores = event_scores - alpha * event_gradients
skater_scores = skater_scores - alpha * skater_gradients**

np.newaxis的矩阵有哪些诡异的索引?为什么在幼稚的实现中是keepdims=False而不是True?请阅读下面的内容,看看它们是如何工作的。

重塑矩阵

回想一下,我们已经为所有事件和运动员初始化了潜在得分矩阵,它们分别是大小为(2,3)的 numpy 数组event_scores和大小为(4,3)的skater_scores。从步骤 2a 中,我们还得到大小为(4,3)的残差矩阵(residuals),其中每行代表一名运动员,每列代表一个事件。所有矩阵如下图所示:

在上面的潜在矩阵中,这两个因素粘在一起。然而,梯度下降算法规定它们应该被分别对待。因此,我们需要找到一种方法来分离每个潜在矩阵中的两个因素。具体来说,对于事件,我们需要 2 个大小为 1×3 的行向量,对于溜冰者,我们需要 2 个大小为 4×1 的列向量,每个向量代表一个因素。

这些潜在矩阵是如何被重塑的:

**reshaped_event_scores = event_scores[:, np.newaxis, :]**

回想一下事件的潜在矩阵(event_scores)是一个大小为(2,3)的二维 numpy 数组。因此,event_scores[:, np.newaxis, :]仍将保留原始尺寸——通过:——同时通过[np.newaxis](https://docs.scipy.org/doc/numpy/reference/constants.html?highlight=newaxis#numpy.newaxis)在原始轴之间添加一个新轴。这会产生一个大小为(2,1,3)的三维数组。您可以将此视为(1,3)数组的两个不同层,每层代表所有事件中的一个因素(下面以红色突出显示):

**reshaped_skater_scores = skater_scores.T[:, :, np.newaxis]**

我们可以对 skater 的潜在矩阵做同样的事情,它最初是一个大小为(4,2)的二维 numpy 数组。然而,我们想把它做成(4,1)数组的两个不同层,每层代表所有溜冰者的一个因素。为此,我们使用skater_scores.T[:, :, np.newaxis]:由于skater_scores.T的大小为(2,4),所以在末尾添加新轴后,三维数组的大小将为(2,4,1)。潜在选手分数的两个层次如下所示(红色):

**reshaped_residuals = residuals[np.newaxis, :, :]**

最后,残差矩阵最初是一个大小为(4,3)的二维数组,由于前面添加了新的轴,它被重新整形为residuals[np.newaxis, :, :],这是一个大小为(1,4,3)的三维数组。你可以认为残差矩阵本质上和以前一样,但是现在存在于三维平面层中。

步骤 2c-i:计算所有因子的梯度

一旦我们知道如何重塑各种矩阵,我们就可以开始计算梯度,不仅包括所有项目和运动员,还包括所有因素。

**event_gradients = np.nansum(reshaped_residuals * reshaped_skater_scores, axis=1)**
  • reshaped_residuals * reshaped_skater_scores:对于残差(1,4,3)和潜在选手得分(2,4,1)的整形三维数组,当我们将它们相乘时,两者都由 numpy 广播。先前平放在 3-D 平面中的剩余矩阵将被复制到另一层以形成(2,4,3)矩阵。同时,在重新整形的潜在运动员矩阵的每一层中的潜在运动员分数将被水平复制三次,以形成(2,4,3)矩阵:

Dotted arrows: numpy broadcasting of latent skater scores (for both factors)

  • 广播完成后,请注意,每一层现在又变成了寻找单一因素的梯度,而与其他因素无关。因此,所有因素的每个事件的梯度将只是熟悉的np.nansum(reshaped_residuals * reshaped_skater_scores, axis=1):axis=1参数是对参加每个事件的运动员进行求和,而keepdims=False参数将求和的结果折叠回所有事件梯度的大小为(2,3)的二维数组:

**skater_gradients = np.nansum(reshaped_residuals * reshaped_event_scores, axis=2).T**
  • 同样的事情也可以一次找到所有因素的潜在选手分数的梯度。然而,这一次,重塑的残差(1,4,3)和事件潜在矩阵(2,1,3)的 3d 阵列相乘在一起,并且两者都将被广播:reshaped_residuals矩阵将再次被复制到另一层以形成(2,4,3)矩阵。同时,在reshaped_event_scores矩阵的每一层中的潜在事件分数将被水平复制四次,也形成(2,4,3)矩阵:

Dotted arrows: numpy broadcasting of latent skater scores (for both factors)

  • 一旦这两个矩阵相乘,将对每个运动员参加的所有事件(axis=2)求和,以得到运动员梯度,而keepdims=False参数将运动员梯度折叠回大小为(2,4)的二维数组。然而,我们知道矩阵的潜在矩阵大小为(4,2),所以我们必须在使用它更新分数之前转置溜冰者梯度矩阵(.T):

步骤 2c-ii:更新所有因素的潜在得分

一旦针对所有因素一次性计算出梯度,就以学习率alpha从相同形状的事件和溜冰者潜在得分矩阵中减去这些二维梯度阵列。

**alpha = 0.0005
event_scores = event_scores - alpha * event_gradients
skater_scores = skater_scores - alpha * skater_gradients**

优化的梯度下降算法——使用 numpy 的广播来计算梯度并一次更新所有因素的潜在得分——在下面完整呈现(针对 4 名运动员、3 个项目和 2 个潜在因素):

**# Step 1: Initialize baseline score, and scores of all factors
random_state = np.random.RandomState(seed=42)
baseline = random_state.random_sample()
skater_scores = random_state.random_sample((4, 2))
event_scores = random_state.random_sample((2, 3))# Step 2: Repeat until convergence
for i in range(1000):
    # a. Calculate residual for every event-skater pair
    predicted_scores = skater_scores @ event_scores + baseline
    residuals = predicted_scores - true_scores

    # b. Calculate baseline gradient and update baseline score
    baseline_gradient = np.nansum(residuals)
    baseline = baseline - alpha * baseline_gradient

    # c. Calculate gradient and update latent scores for all factors
    # Reshape matrices
    reshaped_residuals = residuals[np.newaxis, :, :]
    reshaped_event_scores = event_scores[:, np.newaxis, :]
    reshaped_skater_scores = skater_scores.T[:, :, np.newaxis]

    # c-i: Calculate gradients for all factors
    event_gradients = np.nansum(residuals * reshaped_skater_scores, axis=1)
    skater_gradients = np.nansum(residuals * reshaped_event_scores, axis=2).T

    # c-ii: Update latent scores for all factors
    event_scores = event_scores - alpha * event_gradients
    skater_scores = skater_scores - alpha * skater_gradients**

用矩阵乘法优化梯度下降

上述使用 numpy 广播的实现是可行的。然而,它看起来很笨拙,特别是在计算梯度时要跟上所有的三维矩阵。

这里有一个简单得多的方法来同时计算两个因子的相同梯度,但只需乘以二维矩阵:

  • 为了找到所有因素的所有事件梯度,我们只需要将溜冰者潜在矩阵的(2,4)转置与(4,3)残差矩阵相乘。请注意矩阵的方向如何对齐,从而为事件梯度矩阵提供完美的(2,3)形状。

  • 类似地,为了找到跨所有因素的所有溜冰者梯度,我们将形状(4,3)的残差乘以事件潜在矩阵的(3,2)转置。这给出了溜冰者梯度矩阵的(4,2)的正确形状。

但是,请注意,为了实现这一点,numpy 的矩阵乘法运算需要忽略残差矩阵中的NaN值。遗憾的是,事实并非如此——没有所谓的np.nanmatmul。然而,我们总是可以使用[np.nan_to_num](https://docs.scipy.org/doc/numpy/reference/generated/numpy.nan_to_num.html)将 NaN 值替换为零,然后像往常一样继续矩阵乘法:

**residuals = np.nan_to_num(residuals)
event_gradients = skater_scores.T @ residuals
skater_gradients = residuals @ event_scores.T**

一旦计算出梯度,它们就被用于分别更新各自的潜在得分矩阵。

3 种实现之间的比较

让我们看看这些梯度下降算法的优化版本是否胜过使用for循环的逐因子天真实现。

时间复杂度

使用 Jupyter 笔记本的[%timeit](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit)魔法功能,我测量了在这个玩具示例(4 个溜冰者,3 个事件)上运行一次梯度下降迭代所需的时间。我还改变了因素的数量,并测量了两种实现的相应时间,以查看当因素的数量增加时,它们的时间效率如何。

结果可以在附图中看到:

  • 在只有一个潜在因素的情况下,所有 3 种实施方式运行一次梯度下降迭代需要大约相同的时间(100 μs 左右)。
  • 随着因子数量的增加,简单实现完成一次迭代所需的时间也线性增加。这是有意义的,因为步骤 2c 中的for循环本质上只是从一个因素移动到下一个因素。
  • 相比之下,当因子数量增加时,广播实现比原始实现快得多:在只有 10 个因子的情况下,它已经快了近 10 倍,而在因子数量较多的情况下,它始终快 100 倍—请注意上图中以 10 为基数的对数标度绘制了运行时间。
  • 更好的是用矩阵乘法实现,比广播的快两倍。这些优化版本快得多的原因可能源于它们高效的np.nansumnp.matmul操作,而不是简单实现中较慢的for循环。

空间复杂性

然而,这些优化方法的一个潜在缺点是增加了空间复杂度:例如,从广播到更大的 3-D 矩阵。我们可以通过绘制一个接一个地运行每个实现时消耗的内存量来验证这一点。我们仍然使用 4 个溜冰者和 3 个事件的玩具例子,但是因素的数量被提升到 100,000 个因素的最坏情况。

从上面使用的内存随时间变化的图表(使用 memory_profiler 包)中,我们可以看到:

  • 在两个实现运行的近 6.7 秒中,原始版本运行了前 6.5 秒,广播版本运行了接下来的 0.15 秒,而矩阵乘法版本只运行了最后的 0.02 秒。这与我们之前发现的时间基准是一致的。
  • 简单的实现在运行期间只消耗了 10MiB内存(1 MiB = 1.024 兆字节)。有趣的是,一个行分析器揭示了在 100,000 个因子上的for循环实际上不消耗任何内存!这可能是因为对于每个因素,我们最多只能用一个非常小的 4×3 的矩阵来计算它的梯度。
  • 相比之下,广播实现消耗了大约 30 MiB 的内存,考虑到其 3-D 矩阵,这并不像预期的那样糟糕。然而,矩阵乘法版本只消耗了一半,大约 15 兆字节。

简而言之,与其他两种方法相比,使用矩阵乘法的梯度下降的实现在可读性、速度和/或存储器使用方面提供了显著的改进。因此,最终选择寻找多因素模型的潜在分数。

多因素模型的结果

当我们将上面选择的梯度下降算法应用于 2017 赛季男性滑冰运动员的熟悉示例时,我们可以在算法的迭代中再次跟踪模型的残差和 RMSE。对于梯度下降的前 150 次迭代,它们显示在下面的动画仪表板中:

  • 随着梯度下降的迭代次数越来越多,每个项目-选手对的残差显著减少,正如残差热图末尾的灰色海洋所证明的那样(与项目第 2 部分中单因素模型的相比)。
  • 150 次迭代后 2.41 的非常低的模型 RMSE 证实了这一点。这比以前的模型(平均 8.84,乘法 8.88,混合 8.86)小三倍以上,比季节平均基线模型(10.3)小四倍以上。换句话说,多因素模型在估算赛季得分方面做得非常出色。
  • 事实上,如果我们让算法真正收敛,在 1000 次迭代之后,它实现了实际上不存在的 0.03 的 RMSE,迭代与迭代之间的差异为 1e-4。然而,这并不令人惊讶,因为这是一个比以前更复杂的模型,有更多的参数要调整,以最好地逼近赛季得分。

使用个人因素对滑冰运动员进行排名

尽管多因素模型可以很好地逼近赛季得分,但它有一个主要的弱点:不是用一个单一的得分来给运动员排名(从最高到最低),而是每个运动员现在有多个潜在得分,每个因素一个。这就引出了一个问题:我们应该根据哪个分数来给运动员排名?****

在 5 个潜在因素中,当我们分别使用每个因素的分数对运动员进行排名时,我们看到:

Predicted rankings from each individual factor. In parentheses: Kendall’s tau of each predicted ranking to the world championship ranking

  • 由这 5 个因素产生的排名似乎并没有以任何方式相互关联,因为连接两个相邻排名的线是极其混乱的:一个运动员可能在一个因素中的分数排名接近顶部,而在另一个因素中的分数排名接近底部。

  • 这也可以通过绘制 Kendall tau 来看出,Kendall tau 测量不同因素的任何两个排名之间的成对相关性(见随附的热图):任何两个因素的预测排名之间的最高 Kendall tau 相关性在因素 1 和 5 之间只有微不足道的 0.37。
  • 更糟糕的是,每个因素单独产生的排名都不能很好地预测世界冠军的最终排名,第一个因素的最高肯德尔τ只有 0.45。这甚至远远低于季节平均值的基线模型(0.695),更不用说我们以前建立的模型(加法和乘法模型为 0.732,混合模型为 0.724)。

因此,没有一个因素——单独使用——可以体面地给滑冰运动员排名世界冠军!相反,我们必须找到一种方法来结合不同因素的潜在得分,从而更准确地给运动员排名。在项目的下一部分的中,我将讨论我们如何做到这一点。

资源

在多因素模型中,滑手的潜在得分存储在大小滑手数×因素数的矩阵skater_scores中,而项目的潜在得分存储在大小因素数×项目数的矩阵event_skaters中。因此,多因素模型可以被视为将大小为运动员数量×事件数量的赛季得分矩阵分解为这两个潜在矩阵,使得它们在基线得分之上的乘积——skater_scores @ event_scores + baseline——很好地逼近赛季得分的原始矩阵。

事实上,项目的这一部分使用的梯度下降算法几乎与网飞挑战赛中用于向用户推荐电影的著名的 FunkSVD 矩阵分解算法相同:通过将评级矩阵分解为特定于用户和特定于电影的潜在矩阵,我们可以使用这些潜在得分的乘积来预测用户尚未看过的电影的排名。那么,多因素模型本质上是 FunkSVD,用户=溜冰者,电影=事件,评级=赛季得分。

但是,这两种情况的主要区别在于:

  • 对于这个问题,我们对预测一个运动员在过去一个赛季的比赛中可能会得多少分不感兴趣,而是预测他在未来的世界锦标赛中会排名多高。
  • 为了简单和小的训练数据,在这个项目中使用的梯度下降是批量梯度下降,其中整个训练数据用于在每次迭代中更新所有潜在得分。相比之下,FunkSVD 通常通过随机梯度下降来完成:每次迭代一次只选取一个评级,以更新与该评级相关的用户和电影的潜在得分。这可能比这里给出的优化实现还要快。
  • 与多因素模型不同,原始的 FunkSVD 公式不包括基线项。相比之下,该公式的其他变体不仅包括通用基线项,还包括特定项目和运动员的基线项。您可以查看这篇文章中关于 FunkSVD 和推荐系统中常见的其他矩阵分解方法的可读性很强的介绍。

从赛季表现预测花样滑冰世锦赛排名

原文:https://towardsdatascience.com/predict-figure-skating-world-championship-ranking-from-season-performances-a4771f2460d2?source=collection_archive---------29-----------------------

体育分析

第 4 部分:从多个因素对运动员进行排名

背景

在项目的前几部分,我试图根据运动员在该赛季前几场比赛中获得的分数来预测一年一度的世界花样滑冰锦标赛的排名。主要策略是将选手效应(每个选手的内在能力)与事件效应(一个事件对选手表现的影响)分开,以便建立更准确的排名。

具体来说,在项目的 part 3 中,每个项目和每个滑手都由多个潜在因素来表示。一个项目和一名运动员之间的这些潜在因素的乘积,当加到基线得分时,将接近该项目-运动员对在赛季中的得分:

  • ŷ_event-skater:给定运动员在给定项目中的预测得分
  • θ_event:基线得分(在所有项目和运动员中保持不变)
  • m:模型的潜在因素数
  • 存在于每个项目和每个运动员身上的潜在因素
  • θ_event,f:给定事件的潜在因素得分
  • θ_skater,f:给定选手的潜在因素得分

问题

使用梯度下降,我们可以找到所有项目和运动员的不同因素的潜在得分。然而,一旦找到了每个选手的潜在分数,我们就面临一个大难题:我们应该用什么分数给选手排名?

正如在第 3 部分中所讨论的,如果我们按照每个单独的因素对运动员进行排名,没有一个因素可以给出一个像样的排名:给定 5 个潜在因素,任何一个因素的最高肯德尔τ是 0.45(从第一个因素开始)。这甚至远远低于季节平均值的基线模型(0.695),更不用说我们以前建立的模型(加法和乘法模型为 0.732,混合模型为 0.724)。

Predicted rankings from each individual factor. In parentheses: Kendall’s tau of each predicted ranking to the world championship ranking

因此,我们必须找到一种方法,将不同因素之间的潜在得分结合起来,以便更准确地对滑冰运动员进行排名。

综合多种因素对运动员进行排名

对我来说,这是这个项目中最难的部分,因为最初我绞尽脑汁想知道我究竟如何将几个因素的潜在分数结合起来给运动员排名。

然而,“啊哈!”当我意识到我可以只使用每年的世界锦标赛排名来直接学习如何组合潜在的分数时,那一刻到来了。我也很幸运,因为 Kendall 的 tau 的简单排名度量允许我建立一个简单的逻辑回归模型来组合它们。

肯德尔的 tau 排序度量

让我们回顾一下肯德尔τ的等级度量公式,也称为肯德尔等级相关系数,在项目的第 1 部分中有解释。这是一个衡量两个排名彼此相似程度的指标,比如在世界锦标赛中的预测排名和实际排名之间。

使用前面部分的 4 名滑手的相同玩具示例,让我们假设在该赛季结束时的世界锦标赛中,这 4 名滑手的排名是这样的(从最高到最低;为简单起见,去掉了名字):

1\. FERNANDEZ
2\. MURA
3\. GE
4\. MAJOROV

从这个排名中,我们可以生成 4×(4–1)/2 = 6 个有序对,其中每对中第一个溜冰者的排名总是高于第二个。在本例中,6 个有序对是:

(FERNANDEZ, MURA)
(FERNANDEZ, GE)
(FERNANDEZ, MAJOROV)
(MURA, GE)
(MURA, MAJOROV)
(GE, MAJOROV)

与世界锦标赛完全匹配的预测排名也将产生它自己的 6 个有序对,与上面的 6 个有序对匹配。我们把在两种排序中都匹配的有序对称为和谐对,而那些只出现在一种排序中而不出现在另一种排序中的有序对称为不和谐对

那么,两个等级之间的肯德尔τ可以计算为:

n: number of skaters in the world championship

结果,该预测的排序将具有 6 个一致对和 6 个总有序对中的 0 个不一致对。这转化为(6–0)/6 = 1 的肯德尔τ,这也是两个等级之间的肯德尔τ的上限。相比之下,下限是-1,这是两个完全相反的排名之间的肯德尔τ。

上述公式中的一个重要观察结果是,Kendall 的 tau 相对于一致对的数量严格增加。因此,优化预测排名的肯德尔τ与试图从中获得最高数量的一致对是一样的。更简单地说,在世界锦标赛的有序对中,我们预测的排名需要尽可能多地正确猜测这些对。

逻辑回归模型

但是我们如何对这些有序对进行智能猜测呢?这就是来自多因素模型的潜在得分的来源。如前所述,在我们的玩具示例中,从 4 名溜冰者的世界锦标赛排名中,我们可以生成 4(4–1)/2 = 6 个有序对。

  • 每个有序对将作为我们逻辑回归问题的一个观察值
  • 对于每一个有序对,第一个滑手(姑且称他为滑手 A)的排名都高于第二个滑手(姑且称他为滑手 B)。这将作为我们逻辑回归的基本事实响应。更具体地说,每一对的响应将是一个二元变量,它指示溜冰者 A 是否将超过溜冰者 B: I(A>B)。在这种情况下,在每对选手中,选手 A 的名次都高于选手 B;因此,训练样本中所有对的响应都将是 1。
  • 对于两位选手(A & B),我们已经有了他们之前运行多因素模型的潜在分数。因此,我们可以从运动员 A 的潜在得分中减去运动员 B 的潜在得分,以获得两个运动员在每个因素上的差异。这些差异将被用作每次观察的预测值

根据上面的设置,很明显,我们可以使用逻辑回归来预测每对/观察值的二元响应。更具体地说,对于每一对选手,我们预测 A 选手在世界锦标赛中超过 B 选手的概率P(A>B)。因此,理想的情况是每一对的这个预测概率尽可能接近 1,假设每个观察的地面真实响应总是 1。

对于逻辑回归,运动员 A 将超过运动员 B 的预测概率将是一个 sigmoid 函数,应用于两个运动员在不同因素上的潜在得分差异的线性组合:

  • P(A > B):在有序配对中,第一个选手(A)的排名超过第二个选手(B)的预测概率
  • β_f:潜在因素得分差异的线性系数f
  • Δθ_f:两名选手在f因子上的得分差异
  • Δθ_A,f:选手 A 中f因素的潜在得分
  • Δθ_B,f:运动员 B 中f因素的潜在得分

因此,一旦我们知道了每个因素f的差异的线性系数β_f,我们就可以使用上面的公式来预测 A 选手在世界锦标赛中超过 B 选手的概率。然后,如果这个预测的概率大于 0.5,我们预测溜冰者 A 将排名溜冰者 b。最后,从这些成对的排名预测,我们可以预测从上到下所有溜冰者的最终排名。

学习模型系数

但是我们如何学习逻辑回归模型的线性系数呢?关键是从所有 n(n-1)/2 个有序对中找到最大化观察地面真实响应的联合概率的系数——溜冰者 A 确实比溜冰者 B 排名更高。

m: number of factors, n: number of skaters in the world championship

  • β̂_1、…、β̂_m:逻辑回归模型的学习线性系数(每个因子一个)
  • p:从世界锦标赛排名中提取的每个有序对/观察
  • P(A > B)_p:在双人世界锦标赛中,A 选手的排名超过 B 选手的预测概率p

从上面的公式中注意到,最大化联合概率与最大化其对数是一样的,我们通常称之为 对数似然 。取对数的原因是预测概率的乘积被转换成这些概率的对数之和,这是一个更容易最大化的目标函数:

m: number of factors, n: number of skaters in the world championship

因此,我们可以找到这个目标函数J相对于每个模型系数β_1β_n的梯度,并使用梯度上升相应地更新这些系数。梯度上升只不过是相反的梯度下降:对于模型参数的更新步骤,我们增加而不是减少梯度。换句话说,既然目标是最大化目标函数,我们就需要沿着梯度走,而不是逆着梯度走。

梯度上升算法

更具体地,对于因子k,目标函数相对于其模型系数β_k的梯度通过简单的链式法则找到:

m: number of factors, n: number of skaters. Terms highlighted for clarity.

从上面的梯度公式——用总和和向量符号表示——一旦我们有了每对的预测概率作为列向量,我们就可以从所有 1 的向量中减去它。然后,我们将结果向量与因子k的预测列(潜在得分差)进行点积。该点积的标量结果将是目标函数相对于因子k的模型系数的梯度。

一旦计算了所有因子的梯度,我们可以通过添加相应的梯度来更新每个因子的系数,以及学习速率α来控制梯度上升的收敛速率:

简而言之,梯度上升算法可以概括为以下步骤:

m: number of factors, n: number of skaters in the world championship, p: each ordered pair in the world championship

对于梯度上升,可以通过检查平均对数似然(对数似然除以训练样本中有序对的数量)来监控收敛,并查看它是否在迭代之间保持稳定。除以训练对的数量使得对数似然独立于数据大小,并且更容易监控。

编码梯度上升算法

准备预测矩阵

有了上面的梯度上升模型,让我们看看如何使用 Python 为我们的玩具示例编码。首先,在用 2 个因素训练多因素模型之后,我们现在有了一个大小为(4,2)的熊猫数据帧skater_scores,它代表了这 2 个因素在 4 个溜冰者中的潜在得分。

接下来,我们对这个数据帧中的运动员/行进行排序(使用[reindex](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.reindex.html)方法)以匹配世界冠军排名的顺序:费尔南德斯> MURA >葛>马约罗夫。我们还将这个数据帧转换成一个二维 numpy 数组,这样我们可以更容易地操作它:

world_ranking = ['Javier, FERNANDEZ', 'Takahito, MURA', 'Misha, GE', 'Alexander, MAJOROV']skater_scores = skater_scores.reindex(world_ranking)
skater_scores = skater_scores.values
# array([[16.00924627,  4.59479646],
#        [14.05961149,  3.02042308],
#        [11.91968361,  7.2807469 ],
#        [10.22685671,  3.12272308]])

接下来,对于每个因素,我们通过减去平均值并除以该因素的标准偏差来标准化其得分。这导致所有因子的均值为零,标准差为 1。这种标准化有两个目的:

  • 这将有助于梯度上升更好地收敛:通过保持预测值较小,sigmoid 函数中的指数在计算预测概率时不会内爆/爆炸。
  • 标准化因素意味着它们现在处于相同的尺度。因此,这些因素的模型系数可以帮助我们衡量每个因素在运动员排名中的有用程度。这将在后面的结果部分探讨。

这种标准化可以在 Python 中很容易地完成(见下文),通过axis=0取平均值和标准化,意思是跨行/滑手:

normed_skater_scores = (skater_scores - skater_scores.mean(axis=0)) / skater_scores.std(axis=0)

最后,我们可以使用来自itertools模块的[combinations](https://docs.python.org/2/library/itertools.html#itertools.combinations)函数在规范化矩阵中生成有序的行对,其中第一行总是在第二行之上。结果是,从第一行减去第二行,将计算出在世界锦标赛中排名高于另一名选手(选手 B)的一名选手(选手 A)之间的潜在分数差异。

X = np.array([row1 - row2 for row1, row2 in combinations(normed_skater_scores, 2)])

这将为我们提供用于逻辑回归的预测矩阵,其中 6 行代表 6 个有序对,2 列代表我们示例中两个因素的潜在得分差异(见下文)。

一旦预测矩阵准备好了,我们就可以开始编写逻辑回归的梯度上升公式了。

步骤 1:初始化每个因子模型系数

因为在这个例子中有 2 个因子,我们可以初始化两个系数β_1β_2,两者的值都是 0.5,并且包含在大小为(2)的 numpy 数组beta

beta = np.full(2, 0.5)

步骤 2:计算梯度并更新系数

对于学习速率alpha = 0.01,我们让梯度上升运行 100 次迭代。如下所示,算法的核心非常简单,所有重要的步骤只用了 3 行代码就完成了!

alpha = 0.01# Step 2: Repeat unitl convergence
for i in range(100):
    # a. Calculate predicted probabilities
    prob = 1 / (1 + np.exp(-X @ beta)) # b. Calculate gradients
    gradient = (1 - prob) @ X # c. Update factor coefficients
    beta = beta + alpha * gradient

我已经在之前的项目中详细解释了上面的代码块是如何工作的,该项目也使用了逻辑回归。该项目中的梯度上升与上面的代码之间的唯一区别是:

  • 这些系数现在被称为beta,而不是theta,因为符号θ已经被潜在因子占用
  • 当计算梯度(y - prob)时,不用算法中的响应 y,我们可以像梯度公式规定的那样使用1 - prob,因为我们知道我们的地面真实响应都是 1

下图概述了梯度上升算法第一次迭代的算法工作原理:

每次迭代后的平均对数似然性可以通过对所有概率的对数求和并除以训练样本中的对数(在本例中为 6)来轻松检查:np.log(prob).sum() / len(X)。经过 100 次迭代后,平均对数似然为-0.337,迭代与迭代之间的差异为 1e-4。

模型输出

对于我们的玩具示例,在梯度上升收敛之后,两个因子的模型系数对于第一个因子是 0.745,对于第二个因子是 0.367。

使用这些学习到的系数,我们可以最终预测在我们的训练数据中的每对选手中,第一名选手(选手 A)在世界锦标赛中排名第二名选手(选手 B)的概率。此外,如果这个概率高于 0.5,我们预测滑冰者 A 的排名将超过滑冰者 B;如果没有,我们的预测正好相反:

prob = 1 / (1 + np.exp(-X @ beta))
y_pred = prob > 0.5

以下是我们玩具示例的预测概率和成对排序的结果:

从上面的结果中,我们可以看到预测的排名在世界锦标赛排名的 6 对有序对中得到了 5 对正确。换句话说,对于 5 个一致对和 1 个不一致对,该预测排序的肯德尔τ为(5–1)/6 = 0.67。

重建预测排名

从每个预测的成对排序的结果(上表中最右边的一列)可以清楚地看出,唯一可以创建的预测排序是费尔南德斯>葛> MURA >马约罗夫。让我们看看如何使用代码得出这个结论:

方法 1:根据两两排名

首先,我们建立一个列表counter来统计每个选手的分数。都将从 0 分开始。

counter = [0] * 4
# [0, 0, 0, 0]

然后,我们生成对应于六个有序对中的每一个的有序索引对。指数对的第一个数字是第一个滑手的世锦赛排名(从 0 开始),第二个数字是第二个滑手的排名。使用combinations很容易做到这一点,从技术上讲,它将创建一个生成器,但是为了清楚起见,我将它放在一个列表中。

index_pairs = list(combinations(range(n_skaters), 2))
# [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]

接下来,使用zip,我们将每个指数对与预测的成对排名关联起来——在y_pred中的TrueFalse。如果预测的成对排名是True,我们在 counter 中第一个滑手的指标上加 1 分;如果预测排名是False,我们给第二滑手的指数加一分。

for y, (i, j) in zip(y_pred, index_pairs):
    if y == True:
        counter[i] += 1
    else:
        counter[j] += 1

下面是循环在迭代y_predindex_pairs的每个元素时如何工作的示意图:

True ranking: FERNANDEZ > MURA > GE > MAJOROV

最后,我们根据每位选手在counter中的分数重新排列世界排名,从最高到最低(因此有了reverse=True的说法)。这将从逻辑回归的成对排序输出中给出我们最终的预测排序。

world_ranking
predicted_ranking = [skater for rank, skater in sorted(zip(counter, world_ranking), reverse=True)]

方法二:从综合因素得分

给定beta中学习到的模型系数,我们可以将其乘以标准化潜在得分的normed_skater_scores矩阵,以获得每位选手在 2 个因素上的综合得分:

combined_scores = normed_skater_scores @ beta

因此,现在每个溜冰者都由一个单独的分数来表示,类似于所有以前的模型。通过将这些综合得分从最高到最低排序,我们可以得到最终的预测排名:

sorted_combined_scores = pd.Series(combined_scores, index=world_ranking).sort_values(ascending=False)
# Javier, FERNANDEZ     1.028974
# Misha, GE             0.205300
# Takahito, MURA        0.026682
# Alexander, MAJOROV   -1.260957predicted_ranking = list(sorted_combined_scores.index)
# ['Javier, FERNANDEZ', 'Misha, GE', 'Takahito, MURA', 'Alexander, MAJOROV']

两种方法给出相同排名的原因是,如果一个滑手的综合得分高于另一个滑手,那么这两个滑手的有序对的预测概率将大于 0.5,这意味着预测对将在世界锦标赛排名中成立:

换句话说,根据运动员的综合得分对他们进行排名等同于通过前面方法中所有预测的成对排名对他们进行排名。然而,很明显,这种方法不仅更简单,而且有利于将所有因素浓缩为每个滑冰运动员的单一组合分数。

结果

我们首先将梯度上升算法应用于 2017 赛季男性滑冰运动员的熟悉例子,每个例子有 5 个潜在因素。回想一下项目的第 3 部分, 5 个因素中没有一个可以单独产生一个像样的排名,所以让我们看看使用逻辑回归将它们结合起来是否可以使预测的排名更好。

首先,对于梯度上升的每次迭代,我们不仅可以跟踪平均可能性,还可以跟踪该时间点的预测排名(使用上面概述的方法),以及来自该排名的肯德尔τ。对于梯度上升的前 250 次迭代(学习率α = 0.001),这些可以在下面的动画仪表板中看到:

从上面的仪表板可以看出:

  • 随着梯度上升的进行,模型的平均对数似然性如预期的那样增加。250 次迭代后,对数似然收敛到-0.260,迭代与迭代之间的差异为 1e-5。肯德尔的 tau 也收敛到 0.768,即来自世界锦标赛的 276 对有序对中的 244 对正确。
  • 这个 Kendall 的 tau 高于以前的模型,以前的模型的最高性能是 239 个正确对(来自加法和乘法模型),以及季节平均值的基线模型,它有 234 个正确对。

可能性与准确性

然而,在梯度上升过程中会发生一件奇怪的事情:在第 30 次迭代附近,肯德尔的τ值达到峰值 0.783(或 276 对中的 246 对)。之后,在 250 次迭代后,它收敛到只有 244 个正确对。为什么肯德尔的τ值下降,尽管只是轻微下降,而平均对数似然继续增加?

  • 原因在于,虽然逻辑回归直接最大化了数据的联合(对数)似然性,但这通常会(但并不总是)提高模型的预测准确性。在这种情况下,准确性意味着预测排名的肯德尔τ:正确预测的成对排名越多(一致对),预测排名的肯德尔τ越高。
  • 例如,假设我们的训练数据中只有 2 对,它们的真实响应都是 1。在梯度上升的一些迭代中,这两对的预测概率是 0.51 和 0.52,这给出了 0.51×0.52=0.27 的联合似然性。然而,这两对都将被正确分类,因为它们的预测概率都大于 0.5。
  • 相反,当梯度上升收敛时,预测概率为 0.49 和 0.99。这给出了 0.49×0.99=0.49 的联合可能性。虽然这种联合可能性比前一个大得多,但只有一对(预测概率为 0.99 的一对)被正确分类。

简而言之,与我先前的想法相反,逻辑回归经常会,但并不总是,增加模型输出的准确性。我们将很快讨论如何提高精确度,即模型的肯德尔τ。

综合得分

一旦我们的梯度上升算法收敛,我们就获得每个因子的模型系数(存储在beta中,并显示在附图的底部)。然后,我们使用这些系数,按照前面介绍的方法,计算出每位选手的综合得分。

First 5 columns: normalized scores in each factor. Last column: combined score from logistic regression model. Heatmap color: rank of a skater in each factor. Text color: rank of a skater in the world championship

  • 在 5 个潜在因素中,第一个因素的β系数最高。这意味着,对于任何两名选手来说,他们在这一因素上的潜在得分差异是预测一名选手是否会在世界锦标赛中超越另一名选手的最重要因素。
  • 这与项目的第 3 部分结束时的结果非常吻合,当时根据每个单独的因素对选手进行排名:仅根据第一个因素进行排名的选手在所有 5 个因素中获得了最高的肯德尔τ。这也反映在附图中:第一个因素中的等级颜色是 5 个因素中“最平滑”的。
  • 然而,这与综合得分的预测排名相比就相形见绌了:一般来说,一个运动员的综合得分越高,他在世界锦标赛中的排名就越高(见随附的热图的最后一栏)。这解释了当对 5 个潜在因素应用逻辑回归时,如前所述的相对高的肯德尔τ(0.768,或 276 对中的 244 对)。然而,让我们看看这个令人鼓舞的结果是否可以进一步改善。

增加因子的数量

由于我们可以调整每个选手的潜在因素的数量,也许更多的因素将有助于我们更好地排列选手。当我们将因子的数量增加到 50 时,结果证明这是正确的:

Dashed lines: converged values (after 250 iterations) at 5 factors for comparison

  • 随着梯度上升算法的运行,其平均对数似然性迅速超过 5 因子模型,并在 250 次迭代后最终为-0.052,迭代与迭代之间的差异为 1e-4(见上图左图)。
  • 更重要的是,与 5 因子模型相比,它的 Kendall 的 tau 要优越得多:它在每次迭代后都不断攀升,最终达到 1(中间面板)的完美值。这一点从动画接近尾声时预测排名和世界排名之间的完全一致可以看出(右图)。

然而,如果某件事看起来好得不像是真的,它很可能就是真的。这是因为:

  • 通过 logistic 回归模型,我们已经使用了 2017 年世锦赛的实际成绩来训练模型系数。因此,尽管产生了一个完美的预测排名,这些系数无疑严重过度拟合了该赛季的世界锦标赛结果,并且不能很好地推广到其他赛季。
  • 此外,即使我们要使用这些系数来预测其他赛季的排名,很明显我们应该使用许多赛季来学习它们,而不仅仅是 2017 年。

模型验证计划

上述警告意味着我们需要将训练集中的 10 个赛季分成两个独立的组:

  • 训练组:logistic 回归模型被训练的季节
  • 验证组:剩余的赛季,在这些赛季中,一旦对训练组中的那些人进行了训练,就对肯德尔的τ进行评估

我们将使用以下步骤验证逻辑回归模型,包括对模型进行的任何改进:

Blue: train group, purple: validation group. After the switch in step 4, the colors are reverse — blue: validation group, purple: train group

第一步:对于 10 个赛季中的每一个赛季,我们对赛季得分运行多因素模型,以获得该赛季的潜在选手得分。然后,我们将这些因素标准化到相同的尺度,并考虑运动员之间所有因素的差异。

步骤 2: 对于训练组中的季节,我们将它们的差异矩阵垂直堆叠成单个预测矩阵,并使用它来训练逻辑回归模型。在梯度上升收敛之后,我们可以使用模型系数来对这些季节中的每一个进行排名,并评估该排名的肯德尔τ。从现在开始,我们将这些训练称为肯德尔的 taus ,因为用于产生排名的模型系数是直接从世界锦标赛结果中训练出来的。

第三步:类似地,对于验证组中的季节,我们也将它们的差矩阵堆叠成单个预测矩阵。然后,我们使用从步骤 2 中学习的模型系数对这些季节中的每一个进行排名,并评估该排名的肯德尔τ。从现在开始,我们将对这些肯德尔的 taus 进行验证,因为用于产生排名的模型系数没有经过训练,而只是在这些季节进行了验证。

步骤 4: 为了简单起见,我们将使用 2 重交叉验证来划分季节:对于 10 个季节,这意味着随机选择 5 个季节属于训练组,剩余的 5 个属于步骤 2 中的验证组。然而,在步骤 3 之后,每组中的季节被交换:那些在训练组中的人现在在验证组中,反之亦然。然后,我们重复第 2 步和第 3 步,以获得各自的训练和验证肯德尔的 tau。

由于这种转换,每个赛季都将有一个相应的训练肯德尔的 tau,这涉及到一个直接从该赛季的世界冠军(在其他 4 个冠军中)训练的逻辑回归模型。每个赛季也将有一个相应的验证肯德尔的 tau,这涉及到一个在其他 5 个赛季训练的逻辑回归模型。

步骤 5: 对于每个季节,我们可以用基线模型的肯德尔τ减去其训练或验证的肯德尔τ(使用季节平均值),以获得肯德尔τ的相应改善。最后,我们可以简单地对 10 个训练肯德尔的 tau 的改进进行平均,并将其与 10 个验证肯德尔的 tau 的平均改进进行比较。

因此,前一个平均值代表直接从世界锦标赛排名训练的逻辑回归模型的排名改进(从基线模型),而后者代表间接在其他赛季训练的逻辑回归模型的改进。后者也是对该模型的更现实的评估,因为该模型将用于预测未来赛季的世界锦标赛排名,而这样的排名当然首先还不可用。

结果

根据上面概述的模型验证计划,我们对不同数量的因子运行所有这些步骤(在α=0.005 和 1000 次迭代时),并从基线模型获得它们在肯德尔τ中的平均训练和验证改进:

Bold lines: average improvement in Kendall’s tau across 10 years. Lighter lines: improvement in Kendall’s tau for each year

因子数增加 2 倍(从 2 一直到 128 个因子)的结果如上图所示。从这个图表中,我们可以看到:

  • 当对直接训练的季节(蓝线)进行逻辑回归评估时,随着因子数量的增加,Kendall tau 从基线的平均改善稳步增加,并且从 16 个因子开始显著增加:训练 Kendall tau 改善的 95%置信区间在 16 个因子后飙升至水平线以上零。
  • 然而,当逻辑回归被评估到它没有被训练的季节(红线)时,肯德尔的 tau 的平均改善从 2 个因素向前增加。然而,在 16 个因素之后,平均验证肯德尔的 tau 改善显著下降。
  • 换句话说,很明显,更多的潜在因素并不总是更好:根据这些因素训练的逻辑回归模型可能在它被训练的季节表现得非常好,但在其他季节给出可怕的排名预测——这是模型过度拟合的典型标志。
  • 然而,即使在 8 个因素下,当模型在肯德尔τ中具有最高的平均验证改进时,它仍然是-0.025 的负改进。换句话说,多因素模型的表现甚至不如基线模型!诚然,这种排名预测的不良表现在统计上是微不足道的,因为其 95%的置信区间包含了零水平。

也就是说,我们如何减少模型过度拟合,如上图所示?对于关注该项目的前几部分的读者来说,答案应该是熟悉的:提前停止。

提前停止

与模型惩罚的比较

在项目的前面部分,用于减少模型过度拟合的策略包括模型惩罚和早期停止。

  • 对于项目的这一部分,模型惩罚意味着修改多因素模型的目标函数和梯度下降,以防止潜在因素变得过大,这可能会过度拟合它们所训练的赛季得分。这在项目的第 2 部分的中用单因素(混合)模型进行了演示,但是可以很容易地适应多个因素。
  • 此外,逻辑回归模型和用于训练它的梯度上升算法也可以被罚分。实际上,这将缩小每个潜在得分的模型系数,以便逻辑回归模型不会过于适合用于训练它的世界锦标赛排名,并且可以更好地推广到其他赛季。我已经解释了 L2 惩罚(也称为 L2 正则化)是如何实现的,以及它在我之前的项目中涉及到逻辑回归的影响。
  • 每个模型都有自己的惩罚参数,控制每个模型被惩罚的程度。然而,正如在项目的第 2 部分中提到的,模型惩罚有两个主要缺点:我们必须在每次改变惩罚参数时重新运行模型,并且我们需要寻找最佳的惩罚参数。在这一部分中,对两个模型进行惩罚会变得非常繁琐。

早期停止方法

因此,减少模型过度拟合的一个更方便的方法是提前停止多因子模型的梯度下降算法和其后的逻辑回归模型的梯度上升算法。通过阻止这些算法完全收敛,我们可能不会获得最佳的训练性能,但希望这些模型不会过度适应训练数据,并更好地进行概括。

换句话说,我们将看到,对于任一算法,在哪个停止迭代时,我们将具有最佳验证性能,即,与基线模型相比,肯德尔τ的平均验证改进最高。因此,我们的两个模型(多因素和逻辑回归)现在总共有 3 个超参数:

  1. 因素数量
  2. 多因素模型梯度下降的停止迭代
  3. logistic 回归模型梯度上升的停止迭代

结果

对于这 3 个超参数的每个组合,我们可以记录其在肯德尔τ中与基线模型相比的平均训练和验证改进。这些结果绘制如下:

  • 多因子模型,停止迭代次数从 0 到 1000,间隔 20 次迭代
  • 从 0 到 1000 停止迭代,间隔 20 次迭代的逻辑回归模型
  • 因子数量以 2 的倍数增加,从 2 到 128 个因子

Left to right: Average train and validation Kendall’s tau improvement from baseline as number of factors increase (2 to 128)

从上面的情节中,我们可以看出:

  • 除了 2 个因子,增加多因子模型的迭代次数对 Kendall 的 tau 改进几乎没有影响,在训练和验证中都是如此。这是有意义的,因为多模型仅旨在降低赛季得分的 RMSE,而不是直接降低预测排名的肯德尔τ。
  • 相比之下,增加逻辑回归的迭代次数可以改善训练期间的 Kendall,特别是在因子数较高的情况下:在顶行的大多数方块中,颜色从左到右变蓝。然而,这在验证期间恶化了肯德尔τ:在底部行的大多数方块中,颜色从左到右变得更红。这表明,我们让逻辑回归模型运行的时间越长,它在训练集中对季节的拟合就越多,在验证集中对新季节的表现就越差。
  • 然而,对肯德尔τ最显著的影响是增加因子的数量。随着因子数量的增加,在训练集中,Kendall 的 tau 的改善是显著的,这由靠近顶行末端的正方形变得更蓝来证明。不幸的是,这转化为验证集中 Kendall tau 的显著下降:接近底行末尾的方块变得更红。这与早期的观察一致,即增加因子的数量,特别是在 16 之后,将使训练季节过度拟合,并在验证集中给出新季节的可怕预测。

最后,对于每个因子数,我们记录多因子和逻辑回归模型的停止迭代,其在平均验证肯德尔τ中给出最高改进:

从上面的结果表中,一个有趣的结果立即浮现出来:

  • 除了 2 个因素外,验证集中肯德尔τ的最高平均改善出现在逻辑回归的第 0 次迭代。这意味着,尽管逻辑回归成功地提高了它所训练的赛季的预测排名,但对于它所应用的新赛季,不应用逻辑回归比应用它更好!

  • 更具体地说,回想一下,我们在梯度上升算法中将模型系数初始化为 0.5。因此,当不运行逻辑回归时,这些系数保持为 0.5。这意味着每位选手的综合得分仅仅是潜在得分相加的一半(见所附公式)。
  • 换句话说,学习如何通过逻辑回归将运动员的潜在得分结合起来,似乎并不比简单地将它们相加并根据这些总和对运动员进行排名更好!

在项目的下一部分中,我将解释为什么会出现这种情况,以及我们如何解决这个问题。

资源

你可以从 CS229 (我极力推荐的吴恩达教授讲授的机器学习课程)的相应讲义视频 讲座中找到 logistic 回归的推导及其梯度上升法。请注意,由于我们的训练数据只有一类预测值(全为 1),因此之前导出的公式比典型逻辑回归模型的公式更简单。

接下来,我们使用逻辑回归来学习如何根据运动员的潜在得分对他们进行排名。这项任务属于信息检索系统中经常使用的名副其实的“学习排序”策略。例如,在向每个用户显示排名靠前的文章之前,搜索引擎需要基于某些属性对文章进行排名。更具体地说,通过利用 Kendall 的 tau 的排序度量的成对性质,我们的方法属于学习排序策略的成对子集。

此外,一旦计算出每个潜在因素的成对差异,我们可以使用其他分类方法,而不仅仅是逻辑回归,来预测每个观察/有序对的成对排序。例如,这里有一篇论文使用 SVM 来预测成对排名(也称为 RankSVM),还有一篇可读性更强的博客文章解释了它是如何工作的。

最后,当我关于在 Stackoverflow 上逻辑回归收敛到更高可能性但更低准确性的特殊现象时,我得到的答案链接到几个有趣的讨论。例如,这个线程强调了准确性实际上是一个独立于统计的问题,在本例中是逻辑回归模型。此外,准确度不仅是预测概率的函数,也是用于分类样本的阈值的函数。因此,对于许多问题来说,仅仅基于准确性来评估模型可能不是一个好主意。

从赛季表现预测花样滑冰世锦赛排名

原文:https://towardsdatascience.com/predict-figure-skating-world-championship-ranking-from-season-performances-d97bfbd37807?source=collection_archive---------27-----------------------

体育分析

第 6 部分:组合排名模型和最终基准

背景

在项目的前几部分,我试图根据运动员在该赛季前几场比赛中获得的分数来预测一年一度的世界花样滑冰锦标赛中的排名。主要策略是将溜冰者效应(每个溜冰者的内在能力)与事件效应(一个事件对一个溜冰者表现的影响)分开,这样可以建立一个更准确的排名。

为此,在项目的前几部分使用了几种模型:

第一部分 :加性和乘性模型,可以表述为简单的线性模型,试图逼近所有的赛季得分。线性模型的回归系数包括每个溜冰者的潜在分数,然后根据这些潜在分数对溜冰者进行排名(从最高到最低)。

  • 给定事件中给定运动员的近似分数
  • θ_baseline:基线得分(整个赛季得分不变)
  • θ_event:该项目潜在得分(运动员之间不变)
  • θ_skater:溜冰者潜在得分(跨事件恒定)

Part 2 :混合模型,是 part 1 中加法和乘法模型的交叉。学习潜在分数没有封闭形式的解决方案,但是可以使用梯度下降增量学习。每个溜冰者的潜在分数可以再次被用来给他们排名。

第三部分 :多因素模型,它只是混合模型的一个扩展。不是每个溜冰者(和事件)有一个单一的潜在分数,每个溜冰者将有跨越不同因素的多个潜在分数,这可以再次使用梯度下降来学习。

  • f:存在于每个项目和每个选手身上的潜在因素
  • m:多因子模型中的因子总数
  • θ_event,f:给定事件的潜在因素得分
  • 给定选手的潜在因素的分数

第四部分 : logistic 回归模型,用于组合第三部分中跨因素的潜在得分。模型的预测因子是各因子中的分数差,模型的响应是世界锦标赛中每对有序选手的状态。换句话说,冠军排名本身被直接用于学习如何从多因素模型中组合潜在分数,然后这些组合分数被用于对滑手进行排名。

  • P(A > B):在世界锦标赛的一对有序选手中,第一名选手(A)超过第二名选手(B)的预测概率
  • β_f:潜在因素得分差异的线性系数f
  • Δθ_f:2 名选手在f因子上的得分差异
  • Δθ_A,f:A 滑手f因素的潜在得分
  • Δθ_B,f:运动员 B 中f因素的潜在得分

第五部分 :序贯多因素模型,是第三部分多因素模型的修改版:不是一次性学习所有潜在因素的分数,而是一次学习一个。事实证明,这只不过是训练第 2 部分中概述的单因素模型,然后在学习第一个因素后,在负残差上训练另一个单因素模型,以此类推。

Training the sequential multi-factor model

然后使用逻辑回归将得到的多因素潜在得分结合起来,与第 4 部分相同。最后,在第三部分中发现序列多因素模型比原始多因素模型更好地预测了运动员的排名。

问题

使用肯德尔的 tau 排名指标评估每种方法的预测排名,该指标衡量预测排名与实际世界锦标赛排名的相关程度。对于在训练集中选择的 10 个赛季中的每一个赛季,我们计算每个排名模型和赛季平均值的基线模型之间的肯德尔τ的差异,即简单地通过他们的赛季平均分数对运动员进行排名。

此外,对于每种方法,我们尝试了不同的策略来提高他们的预测排名,例如 L2 惩罚提前停止。这些策略旨在防止模型过度拟合赛季得分,以及逻辑回归模型过度拟合世界锦标赛排名,以便他们可以为未来赛季更好地排名运动员。

与基线模型相比,每种方法的肯德尔τ(超过 10 个赛季)的平均差异记录如下。注意,逻辑回归模型直接用世界冠军来学习如何组合潜在因素。因此,它不应该在训练的同一季节进行评估,而应该在新的季节进行评估。因此,使用了双重交叉验证:在 5 个季节训练模型,在另外 5 个季节评估模型。

从上表中,我们看到:

  • 对于简单的线性模型(加法和乘法),未惩罚的版本比惩罚的版本提供了更好的排名。相比之下,对于混合和多因素+逻辑回归模型,阻止模型完全收敛会给出更好的排名。
  • 从肯德尔τ与基线相比的平均差异(第四列)来看,随着我们从加法、乘法、混合,然后到与逻辑回归模型耦合的顺序多因子,预测排名的准确性增加。这对训练集中选择的 10 个赛季的男女选手都适用。
  • 然而,这些排名准确性的提高是相当有限的。事实上,没有一种方法提供的排名在统计上优于季节平均值的基线模型:Kendall 的 tau 到基线模型的差异的 95%置信区间包含所有模型的零(最后一列)。

因此,项目最后部分的两个主要目标是:

  1. 将不同的预测排名组合成一个最终排名,我们希望它比单独的单个排名更准确。
  2. 评估测试集中剩余 5 个赛季的所有排名方法,这些方法之前没有进行过分析。

首先,让我们尝试两种不同的方法来组合我们迄今为止开发的所有方法的排名:使用 Borda 计数的无监督方法和使用逻辑回归的监督方法。

无监督方法:Borda 计数

理论

由于每种方法都预测了不同的选手排名,我们可以认为每种方法都为所有选手投了不同排名的一票(比如,选手 B >选手 A >选手 C 代表 3 名选手 A、B 和 C)。从这个角度来看,有很多方法可以将这些投票组合成最终的选手排名。它们都属于等级投票方法的范畴,这种方法在许多国家和州的选举中使用。

一种常见的排名投票方法是博尔达计数,它最早发现于 1435 年!然而,这并不令人惊讶,因为它非常简单。下面的 5 个溜冰者的玩具示例将演示它是如何工作的:

Avg: season average. Add: additive. Mul: multiplicative. Hyb: hybrid. Multi: sequential multi-factor + logistic

  1. 我们从迄今为止为 5 名选手开发的所有方法的潜在分数开始。请注意,我们还包括季节平均基线模型,因为它与我们开发的其他 4 个模型一样有效,表现也一样好。
  2. 对于每个模型,我们给该模型中分数最低的选手 0 分,然后给分数第二低的选手 1 分,以此类推。结果,对于 5 个滑冰者,每个模型中得分最高的滑冰者将具有等级点 4。因此,等级点数简单地指示每个模型对溜冰者的等级有多高,从 0 开始向上。
  3. 对于每一个溜冰者,我们将所有模型的排名点相加,得到该溜冰者的 Borda 计数。最后,给选手排名不过是给他们的 Borda 分数排名,从最高到最低。
  4. 请注意,Borda 计数可以在多个溜冰者之间绑定,例如在上面玩具示例中的GEMAJOROV之间:两者的 Borda 计数都是 9。虽然存在许多打破平局的规则,但对于这个问题,我们将通过季节平均模型的排名点打破平局-该模型中得分较高的选手是平局获胜者。在本例中,GEMAJOROV平分秋色,因为对于季节平均模型,后者的等级点(3)比后者(2)高(参见等级点表的第一列)。
  • 选择此平局打破规则是因为通过 Borda 计数的预测排名将在最后与季节平均值的基线模型进行比较,因此使用季节平均值打破平局确保排名准确性的任何提高都是由于 Borda 计数方法本身,而不是平局打破规则。

从上面的玩具例子可以看出:

  • 尽管没有一个排名模型将HANYU排在第一,他仍然以最高的博尔达数 13 结束。这是因为大多数模特——5 个中的 4 个——将他排在第二位(排名点为 3)。
  • 相比之下,即使加性车型排名TANAKA第一,其余车型对他的排名都很低:两个车型倒数第一,另外两个车型倒数第三。结果,他以最低的博尔达数 8 结束。

Ranked voting ballot for the 2003 Heisman trophy. See ranking rule for the trophy here.

  • 因此,Borda count 经常被描述为基于共识的排名模型,对于要排名高的滑冰运动员,大多数模型必须将他排名高。这就是为什么它通常用于体育奖项的运动员排名,如最杰出的大学足球运动员的海斯曼杯(见附图)。
  • 然而,Borda count 模型仅基于它们之间的共识来组合现有的排名,希望这样的组合将准确地预测最终的世界冠军排名。在机器学习的说法中,这是一种 无监督的方法 ,因为预测的基本事实——世界冠军排名——根本没有用于训练模型。

编码

Borda 计数模型的编码相当简单:

  • 我们从熊猫数据帧skater_scores开始,它代表了之前所有 5 个模型的运动员分数。
  • 然后,我们通过以下方式计算每个模型的等级点数:
  1. 首先,使用[DataFrame.values](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.values.html)将 skater_scores 数据帧转换成一个 numpy 数组。
  2. 这允许我们调用数组的[argsort](https://docs.scipy.org/doc/numpy/reference/generated/numpy.argsort.html#numpy.argsort)方法两次,将分数转换成等级点数:
**rank_points = skater_scores.values.argsort(axis=0).argsort(axis=0)**

axis=0参数意味着排序是跨行/运动员执行的。下图突出显示了argsort方法是如何计算根据季节平均模型排名第一的选手的排名点(FERNANDEZ,排名分数为 4)与根据同一模型排名最后的选手(TANAKA,排名分数为 0)进行比较的。

Blue: skater ranked first by season average model (FERNANDEZ). Red: skater ranked last for season average model (TANAKA)

  • 一旦计算出所有模型的等级点,我们就可以对 rank_points 矩阵的每一行求和,以获得每个选手的最终 Borda 分数。axis=1参数意味着求和是跨列/模型进行的。
**borda_counts = rank_points.sum(axis=1)**

  • 一旦 Borda 计数被计算出来,我们就把它作为一个额外的列添加回原来的 skater_scores 数据帧中。这使得我们可以对运动员/行进行排序,首先根据 Borda 计数,然后根据平均模型的分数(打破任何平局),从最高到最低——因此有了ascending=False参数。
**skater_scores['Borda'] = borda_counts
sorted_skater_scores = skater_scores.sort_values(by=['Borda', 'Avg'], ascending=False)**

  • 最后,从排序的 Borda 计数中获得预测的排名只不过是从前面的步骤中提取出sorted_skater_scores数据帧的索引。
**borda_ranking = list(sorted_skater_scores.index)
# ['Yuzuru, HANYU',
#  'Javier, FERNANDEZ',
#  'Misha, GE',
#  'Alexander, MAJOROV',
#  'Keiji, TANAKA']**

在我们检查 Borda count 模型的结果之前,让我们看看另一种结合现有排名的方法,但这次使用世界锦标赛排名来直接指导这样的结合。

监督方法:逻辑回归

理论

  • 回到包含每个模型的运动员分数的矩阵,我们看到运动员在每个模型中的分数只不过是该运动员的另一个因素。
  • 因此,组合这些因素的方式与组合第四部分和第五部分中多因素模型的潜在得分完全相同,即使用简单的逻辑回归模型:
  1. 该模型的预测值将是一对有序的溜冰者中每个模型的分数差。这对选手是从世界锦标赛排名中产生的,这样这对选手中的第一个选手(选手 A)的排名总是高于第二个选手(选手 B)。为了一致性和易于收敛,在进行成对差异之前,每个模型的分数都朝着平均值 0 和标准偏差 1 标准化。
  2. 每对选手的回答将是一个二元变量,它表示在世界锦标赛中选手 A 的排名是否高于选手 B:I(A>B)。由于所选择的惯例规定溜冰者 A 总是在溜冰者 B 之上,因此训练样本中所有对的响应都是 1。

对于我们的玩具例子,假设世界冠军排名是HANYU > FERNANDEZ > TANAKA > GE > MAJOROV。因此,我们可以从这个排序中生成 10 个有序对;对于每对选手,我们计算 5 个分数差异的预测值,每个预测值对应于该对选手中两个选手之间的一个先前的模型。注意,由于世界锦标赛排名直接用于生成有序对并计算其相应的预测器,这是一个 监督的 机器学习任务。

上述问题的逻辑回归模型与第 4 部分中的模型完全相同,重复如下:

  • P(A > B)_p:在世界锦标赛中,运动员 A 在有序对p中的排名超过运动员 B 的预测概率(从世界锦标赛排名中提取)
  • β_model:每个先前模型的学习线性系数
  • Δθ_model,p:两人一组p中 2 名选手的模型得分差异

使用梯度下降,可以递增地学习 5 个先前模型中每一个的线性系数,直到模型的对数似然最大化。一旦学习了这些系数,它们可以用于将不同的模型分数组合成单个分数,可以根据该分数对溜冰者进行排名:

  • β_model:每个先前模型的学习线性系数
  • θ_s,model:溜冰者的每个模型的标准化分数s

由于世界锦标赛排名直接用于训练上述逻辑回归模型,我们再次使用双重验证来客观地评估它:我们在随机选择的 5 个赛季训练模型,并在剩余的 5 个赛季评估它。为了保持一致,为该部分选择的两个折叠与第 4 部分中选择的两个折叠相同。

提前停止

由于这一部分中逻辑回归的梯度下降与第 4 部分中的完全相同,所以我不再赘述。但是,类似于那部分,我们可以过早地停止梯度下降算法。

这样做,模型不适合世界冠军的排名,因为它应该是 5 个赛季的训练(训练折叠)。然而,它的预测排名可能会更好的 5 个新赛季,它是适用的(验证折叠)。然后,我们检查在验证集中的所有 10 个季节中,逻辑回归模型在哪个迭代中具有最高的肯德尔τ(相对于基线模型)平均改善。

对于男子滑冰运动员,在梯度下降的第一个 2000 次迭代中,绘制了训练集和验证集的肯德尔τ相对于基线的平均改善:

  • 验证集的肯德尔τ的改善在梯度下降的 650 次迭代时达到峰值。此后,训练集的 Kendall tau 改进总体上继续上升,而验证集的 Kendall tau 改进急剧下降。
  • 因此,很明显,对于男性滑冰运动员,我们应该在逻辑回归模型的 650 次迭代时停止梯度下降算法。这导致验证集的肯德尔τ值比基线模型平均提高了 0.01。
  • 然而,这种改善是高度可变的:与基线模型相比,一些年份的 Kendall tau 有非常高的改善,而一些年份的模型表现低于基线(有关更多详细信息,请参见下面的结果部分)。

训练集的结果

男子滑冰运动员

对于 2017 赛季男子滑冰运动员的熟悉示例,我们显示了之前 5 个模型中每个模型的归一化分数,然后是 Borda 分数,最后是上述逻辑回归模型的组合分数(在梯度下降的 650 次迭代处停止)。

First 4 columns: normalized scores in each factor. Last 2 columns: combined score from Borda count and logistic regression model. Heatmap color: rank of a skater in each model. Text color: rank of a skater in the world championship

从随附的所有 7 款车型的预测排名热图中,我们看到:

  • 每个模型的标准化分数在之前的 5 个模型中非常相似。因此,他们的排名也非常相似,正如热图颜色显示的每个模型的选手排名所示。
  • 因此,通过 Borda 计数的排名大部分保留了以前模型的排名,因为 Borda 计数只不过是这些模型中排名点的总和。
  • 类似地,对于逻辑回归模型,通过跨 5 个先前模型的标准化分数的线性组合对溜冰者进行排名将给出与那些原始排名非常相似的预测排名。
  • 有趣的是,逻辑回归模型(热图底部)的模型系数β在之前的 5 个模型中有很大不同:最大的(1.16)属于平均得分模型,最小的(-0.27)属于乘法模型。但是,请注意,这些分数在模型之间高度共线。因此,我们不应该试图解释模型系数,因为它们可能是高度可变的,正如在这种情况下明显证明的那样。

为了可视化预测排名之间的关系,我们可以为 2017 赛季并排绘制它们(下图中的底部面板),以及我们开发的 7 个排名模型中每个模型的肯德尔 tau 到基线模型的差异(顶部面板):

Each gray line in the top panel corresponds to each of the 10 seasons in the training set. Colors in bottom panel are based on the 2017 world championship ranking.

从上图中,我们看到:

  • Borda 计数确实起到了在之前的 5 个排名模型中一致认可的计数的作用。比如这 5 款中有 4 款把阿列克谢、拜琴科排在米凯尔、科里亚达之上。只有多因素模型将它们反向排列。然而,如上图中用红色突出显示的,Borda 计数将与大多数排名一致,而不是多因素模型的单独排名。
  • 与之前的 5 个模型相比,无监督方法(Borda 计数)和有监督方法(逻辑回归)都没有在排序准确性方面提供任何显著的改进。这可以从 2017 赛季男子滑冰运动员排名时这两个型号的一致对数量中看出(在 276 对有序对中,分别为 240 对和 242 对)。
  • 这也可以在相对于这两种方法的基线模型的平均 Kendallτ改善中看出:与季节平均值的基线模型相比,这两种方法都没有在 Kendallτ方面提供任何统计上显著的改善,正如它们在 0 处穿过水平线的 95%置信区间所证明的那样(在下表中重复)。换句话说,他们的表现和之前的 5 款车型一样平庸。

说明

Borda 计数和逻辑回归模型的平庸表现完全说得通。这是因为它们本身只是之前五个排名的组合,这五个排名彼此非常相似。因此,如果一名选手在之前的排名中排名靠后,他的排名在 Borda 计数或逻辑回归模型中就不可能上升。

相比之下,我们在上面的图表中看到,滑冰运动员在世界锦标赛中可以做出令人惊讶的跳跃和排名下降。这意味着排名模型的平庸结果很大程度上是由于世界锦标赛本身的不可预测性。

此外,在一些赛季中,大多数预测排名的表现优于赛季平均基线模型,而在其他赛季中,它们的表现一直不佳。换句话说,仅仅因为大多数排名模型在一个季节表现良好,并不意味着在下一个季节没有什么时髦的事情发生。

这种季节与季节之间的高度可变性是为什么尽管大多数模型平均来说对基线模型提供了微小的改进,但是这些改进每年变化如此之大,以至于它们最终根本不具有统计显著性。这可以从所有排名模型的 Kendall 的 tau 改进在零处穿过水平线的 95%置信区间中看出。

女子滑冰运动员

对于女性滑冰运动员,逻辑回归模型在 92 次迭代的验证集中对肯德尔τ的改善最大(见上图)。下表总结了无监督方法(Borda 计数)和有监督方法(逻辑回归)结合女性滑冰运动员先前排名的表现:

从上面的图和表中,我们看到逻辑回归模型(在 92 次迭代时停止)提供了非常乐观的结果,与基线模型相比,肯德尔τ的平均改善为 0.01。此外,这种改善在统计上几乎是显著的,因为其 95%的置信区间几乎高于水平线零。

但是,请注意,在优化排名模型时,我们已经在训练集中使用了所有 10 个赛季的世界冠军排名,例如,当选择理想的迭代来停止多因素模型或逻辑回归模型时。因此,上面发布的结果很可能在一定程度上过度适应了训练集,并且可能过于乐观。

因此,我们通过在测试集上对所有 7 个排名模型进行基准测试来结束项目,该测试集包括我们迄今尚未分析的 5 个赛季。这个测试集上的基准测试结果将为我们的模型提供更客观的评估。

测试集的结果

在我们评估测试集中 5 个赛季的 7 个排名模型之前,我们使用训练集中的所有 10 个赛季来重新训练它们。我们还在通过 2 重交叉验证选择的理想迭代处停止相关模型。我们还选择了 2019 年的最新一季,以展示测试集中一季的不同预测排名。

男子滑冰运动员

Each gray line in the top panel corresponds to each of the 5 seasons in the test set. Colors in bottom panel are based on the 2019 world championship ranking.

从上图中,我们可以看到:

  • 2019 年的世界锦标赛比 2017 年的锦标赛更不可预测,因为所有型号在前一季的协和对数量(约 220 对)远低于后一季(约 240 对)。
  • 对于 2019 赛季,所有车型的预测排名基本相同。此外,我们开发的所有模型都优于季节平均值的基线模型,该模型在 276 个有序对中仅预测了 216 个正确对。
  • 然而,当在测试集中对 5 个季节进行平均时,这种改善在很大程度上是不显著的,因为对于所有模型,Kendall 的 tau 改善的 95%置信区间在零处穿过水平线。这种情况的根本原因与训练集中的原因相同:有一些赛季,如 2019 年,排名模型的表现优于基线模型,但仍有一些赛季与基线相比,他们的表现一直不佳。这种较高的季节间可变性一直是排名模型的一个持续问题,甚至可以追溯到第一部分。

上述结果总结在下表中。请注意,与训练集相比,大多数模型在测试集中的性能较低,这证实了我们之前对训练集性能过于乐观的怀疑。

女子滑冰运动员

Each gray line in the top panel corresponds to each of the 5 seasons in the test set. Colors in bottom panel are based on the 2019 world championship ranking.

女性滑冰运动员的情况也好不到哪里去:大多数模特在 2019 赛季的表现都低于赛季平均水平的基线模型,这从她们相对较少的和谐对数量(在 231 对有序对中)可以看出。平均而言,除了可能的混合模型之外,他们也没有在基线模型上提供 Kendall 的显著改善,因为他们的 95%置信区间都在零处穿过水平线。这些结果总结在下表中:

结论

如果有一件事你应该从阅读我的项目中学到,那就是:预测体育运动很难!这一点在本项目开发的所有车型的不同季节表现的高度可变性中显而易见。即使是最有潜力的——多因素模型结合逻辑回归——仍然无法克服这种可变性,即使我们直接使用世界锦标赛排名来训练它。

Ice, number one nemesis of ranking models

因此,模型的未来改进包括:

  • 按顺序对待每个季节,而不是分别对待。这可以潜在地使用贝叶斯框架来完成:前一季的逻辑回归系数的后验被用作下一季的先验。或者,可以使用递归神经网络,其中运动员潜在得分用作内部状态,并且当遇到新的赛季得分时被顺序更新。
  • 使用非线性模型,例如具有非线性核的支持向量机,来组合潜在因素以及模型分数。这将捕捉到不同因素之间的相互作用,我们希望每个季节都保持一致。

最后,这个项目在构建一个新问题(花样滑冰运动员排名)方面教会了我很多,在大多数数据科学家熟悉的任务方面:线性回归、矩阵分解、逻辑回归,甚至像 Borda 计数这样简单的事情。许多人“啊哈!”我在项目中遇到的时刻是难以形容的!

资源

在项目的这一部分,我们使用非监督(Borda 计数)和监督方法(逻辑回归)结合了前 5 个模型的排名。这是众多排名聚合方法中的两种,这种方法在像投票理论一样古老的领域和像信息检索一样新的领域都会遇到——想想谷歌的 PageRank 算法。在这篇文章中可以找到对排名汇总方法的全面概述。

最后,感谢您通读了我的项目报告的 6 个长部分。我希望你能学到一些有用的技术来帮助你解决体育预测问题,或者简单地获得一些关于如何将普通数据科学技术应用于像这样的独特问题的见解。如果您有任何进一步的问题或反馈,请不要犹豫通过媒体与我联系!

用你自己的神经网络预测癌症肿瘤的恶性程度

原文:https://towardsdatascience.com/predict-malignancy-in-breast-cancer-tumors-with-your-own-neural-network-and-the-wisconsin-dataset-76271a05e941?source=collection_archive---------4-----------------------

在这个系列的最后一部分,我们使用我们从头编码的网络来预测乳腺癌肿瘤的恶性程度。

在本系列的第 1 部分中,我们深入了解了我们的神经网络的架构。 在第二部中,我们用 Python 构建了它。我们还深入了解了反向传播和梯度下降优化算法。

在****最终第 3 部分中,我们将使用威斯康星癌症数据集。我们将学会准备我们的数据,通过我们的网络运行它,并分析结果。****

是时候探索我们网络的损失情况了。

Navigating the Loss Landscape within deep learning training processes. Variations include: Std SGD, LR annealing, large LR or SGD+momentum. Loss values modified & scaled to facilitate visual contrast. Visuals by Javier Ideami@ideami.com

打开网络

为了打开我们的网络,我们需要一些燃料,我们需要数据

  • 我们将使用与乳腺癌肿瘤检测相关的真实数据集。
  • 数据来自于 威斯康辛癌症数据集
  • 这些数据是由麦迪逊的威斯康星大学医院和威廉·h·沃尔伯格博士收集的。
  • 应数据所有者的要求,我们提及与数据集相关的一项研究:O. L. Mangasarian 和 W. H. Wolberg:“通过线性规划进行癌症诊断”,《暹罗新闻》,第 23 卷,第 5 期,1990 年 9 月,第 1 和 18 页。
  • csv 格式的数据可以通过链接下载
  • 在这个 Github 链接 ,可以访问项目的所有代码和数据。

**** [## javismiles/深度学习预测乳腺癌肿瘤恶性肿瘤

用 Python 从头开始编码的 2 层神经网络预测癌症恶性程度。…

github.com](https://github.com/javismiles/Deep-Learning-predicting-breast-cancer-tumor-malignancy)

首先,我们将数据下载到我们的机器上。然后,我们使用 pandas 创建一个数据帧,并查看它的第一行。

**df = pd.read_csv('wisconsin-cancer-dataset.csv',header=None)**
df.head(5)

dataframe 是一种 python 数据结构,它允许我们非常容易地工作和可视化数据。

我们需要做的第一件事是理解数据的结构。我们在它的网站上找到了关于它的关键信息。

  • 共有 699 行,属于 699 名患者
  • 第一列是标识每个患者的 ID
  • 下面的 9 列是特征,表示与检测到的肿瘤相关的不同类型的信息。它们代表与以下相关的数据:团块厚度、细胞大小的均匀性、细胞形状的均匀性、边缘粘附、单个上皮细胞大小、裸露的细胞核、平淡的染色质、正常的核仁和有丝分裂。
  • 最后一列是肿瘤的类别,它有两个可能的值: 2 表示肿瘤被发现为良性4 表示发现为恶性
  • 我们还被告知有几行包含丢失的数据。缺失数据在数据集中用表示。性格。
  • 在数据集中的 699 名患者中,类别分布为:良性:458 名(65.5%)和恶性:241 名(34.5%)

这是有用的信息,可以让我们得出一些结论。

  • 我们的目标是训练我们的神经网络,根据数据提供的特征预测肿瘤是良性还是恶性,。
  • 网络的输入将由 9 个特征组成,9 列表示肿瘤的不同特征
  • 我们将不使用保存患者 ID 的第一列。
  • 我们将从数据集中删除任何包含丢失数据行。性格)。
  • 在二进制分类的情况下,从两个类中获得较大比例的数据是有益的。我们有一个 65%-35%的分布,这已经足够好了。
  • 良性和恶性用数字 2 和 4 标识。我们网络的最后一层通过它的 Sigmoid 函数输出 0 到 1 之间的值。此外,当数据设置在从 0 到 1 的范围内时,神经网络往往工作得更好。因此,我们将更改 class 列的值,对于良性情况,将值保持为 0 而不是 2,对于恶性情况,将值保持为 1 而不是 4。(我们也可以改为缩放 Sigmoid 的输出)。

我们开始做这些改变。首先,我们将类值(在第 10 列)从 2 更改为 0,从 4 更改为 1

df.iloc[:,10].replace(2, 0,inplace=True)
df.iloc[:,10].replace(4, 1,inplace=True)

然后,我们继续删除所有包含缺失值的行(由?character)位于第 6 列,我们已将其标识为包含它们的列。

df = df[~df[6].isin(['?'])]

那个“?”字符导致 Python 将第 6 列解释为由字符串组成。其他列由整数组成。我们将整个数据帧设置为由浮点数组成。这有助于我们的网络执行复杂的计算。

df = df.astype(float)

接下来,让我们处理数据中值的范围。请注意 9 个特征中的数据是如何由超出 0 到 1 范围的数字组成的。真实的数据集通常是杂乱的,并且它们的值具有很大的范围差异:负数、列内的巨大范围差异等等。

这就是为什么数据标准化是深度学习过程的特征工程阶段中关键的第一步。

数据规格化意味着以一种网络更容易消化的方式准备 it 。我们正在帮助网络更容易、更快地收敛到我们所寻求的最小值。通常,神经网络对 0 到 1 范围内的数值数据集反应良好,对平均值为 0、标准偏差为 1 的数据也反应良好。

特征工程和标准化不是本文的重点,但让我们快速提及特征工程过程的这个阶段中的一些方法:

  • 标准化方法的一个例子是通过对每个特征列应用最小-最大方法来重新调整我们的数据,使其符合 0 到 1 的范围。
    new _ x =(x-min _ x)/(max _ x-min _ x)
  • 我们还可以应用标准化,它将每个特性列的值居中,设置平均值为 0,标准偏差为 1。
    new_x = (x 均值)/std.dev

一些数据集和场景将从这些技术中获益更多。在我们的例子中,经过一些测试后,我们决定使用 sklearn 库应用最小-最大归一化:

names = df.columns[0:10]
scaler = MinMaxScaler() 
**scaled_df** = scaler.fit_transform(**df**.iloc[:,0:10]) 
**scaled_df** = pd.DataFrame(**scaled_df**, columns=names)

让我们来看看所有这些变化之后的相同的 15 行。

  • 更改后,我们有 683 行。16 个缺失数据已被删除。
  • 所有的列现在都由浮点数组成,它们的值在 0 和 1 之间被规范化。(当我们稍后构建训练集时,将忽略列 0,即 id)。
  • 最后一列 class 现在对良性肿瘤使用 0 值,对恶性肿瘤使用 1 值。
  • 请注意,我们没有对 class 列进行规范化,因为它已经保存了 0 到 1 范围内的值,并且它的值应该保持设置为 0 或 1。
  • 注意,最后一列,我们将用作目标的那一列,不需要是浮点型。它可以是整数,因为我们的输出只能是 1 或 0。(当我们训练网络时,我们将从原始的 df dataframe 中选取该列,该列被设置为 0 或 1)。
  • 因此,我们的 scaled_df dataframe 包含所有规范化的列,我们将从数据集的非规范化版本 df dataframe 中选择 class 列。

随着我们探索更多的数据,这一过程可能会继续下去。

  • 这 9 个特征都是必不可少的吗?我们要把他们都包括在培训过程中吗?
  • 我们有足够的高质量数据来产生好的训练、验证和测试集吗?(稍后详细介绍)
  • 研究这些数据,我们是否发现了任何有意义和有用的见解,可以帮助我们更有效地训练网络?

这些以及更多都是培训开始前进行的功能工程流程的一部分。

另一件有用的事情是构建图表,以不同的方式分析数据。myplotlib python 库帮助我们通过不同种类的图表来研究数据。

我们先把要研究的规格化列和 class 列结合起来,然后开始探索。

scaled_df[10]= df[10]scaled_df.iloc[0:13,1:11].plot.bar();
scaled_df.iloc[0:13,1:11].plot.hist(alpha=0.5)

点击此链接,探索 Panda 提供的所有可视化选项

为了加快文章的速度,在我们的例子中,我们认为 9 个特性是有用的。我们的目标是精确预测“类”列

那么,描述我们的 683 个样本和它们的输出之间的联系的函数会有多复杂呢?

9 个特征和输出之间的关系显然是多维的和非线性的。

让我们通过网络运行数据,看看会发生什么。

在此之前,我们需要考虑一个关键话题:

  • 如果我们使用 683 个样本,我们所有的样本,来创建我们的训练集,并获得良好的结果,我们将不得不面对一个关键问题。
  • 如果网络以完全匹配训练期间使用的样本的方式设置其权重,而未能推广到训练集之外的新样本,该怎么办?
  • 使用训练数据时,或者当使用从未见过的数据时,我们的最终目标是否达到很高的准确度?显然,第二种情况。

这就是深度学习实践者通常考虑三种数据集的原因:

  • 训练集:你用来训练网络的数据。它包含输入要素和目标标注。
  • 验证集:一个单独的、不同的数据批次,理想情况下应该来自与训练集相同的分布。您将使用它来验证培训的质量。验证集也有目标标签。
  • 测试集:另一批单独的数据,用于测试网络中的新相关数据,这些数据最好来自与验证集相同的分布。通常,测试集不带有目标标签。

不同集合相对于彼此的大小是另一个需要花些时间来描述的话题。出于我们的目的,考虑大部分数据形成了训练集,其中一小部分通常被提取(并从训练集中消除)成为验证集。

20% 是一个典型的数字,通常被选为构成我们验证集的数据的百分比。

要估计网络的训练质量,比较训练集和验证集的性能是很有用的:

  • 如果在验证集上获得的损失值提高,然后开始变得更差,则网络过度拟合,这意味着网络已经学习了非常适合训练数据的函数,然而没有将 足够好地推广到验证集
  • 过度拟合的反义词是欠拟合,当网络的训练性能不够好时,我们在训练集和验证集中获得的损失值都太高(例如,训练损失比验证损失更严重)。
  • 理想情况下,您希望在两个数据集中获得相似的性能。
  • 当我们有过拟合时,我们可以应用正则化。正则化是一种对优化算法进行更改以使网络更好地泛化的技术。正则化技术包括剔除、L1 和 L2 正则化、提前停止和数据增强技术。

总的来说,认识到验证集的成功是你真正的目标。如果网络不能很好地处理它以前没有见过的新数据,那么让网络在训练数据上表现得非常好也没有用。

因此,您真正的目标是达到一个良好的损失值,并通过验证集实现良好的准确性。

为了达到这一点,过度拟合是我们需要防止的最重要的问题之一,这就是为什么正规化如此重要。让我们快速简单地回顾一下 4 种广泛使用的正则化技术。

退出:在每一轮训练中,我们会随机关闭一些隐藏的网络单位。这可以防止网络过分强调任何特定的权重,并有助于网络更好地推广。这就好像我们通过不同的网络架构运行数据,然后平均它们的影响,这有助于防止过度拟合。

L1 和 L2 :我们在成本函数中增加了额外的项,当权重变得太大时,这些项会惩罚网络。这些技术鼓励网络在损失值和权重比例之间找到一个好的平衡。

过早停止:过度适应可能是训练时间过长的结果。如果我们监控我们的验证错误,当验证错误停止改善时,我们可以停止训练过程。

数据扩充:通常,更多的训练数据意味着更好的网络性能,但是获取更多的数据并不总是可能的。相反,我们可以通过人为创建数据的变体来扩充现有数据。例如,在图像的情况下,我们可以应用旋转、平移、裁剪和其他技术来产生它们的新变体。

回到我们的数据。是时候挑选我们的训练集和验证集了。我们将选择 683 行中的一部分作为训练集,选择数据集的另一部分作为我们的验证集。

培训结束后,我们将通过验证集再次运行流程来验证我们网络的质量

**x=scaled_df.iloc[0:500,1:10].values.transpose()
y=df.iloc[0:500,10:].values.transpose()****xval=scaled_df.iloc[501:683,1:10].values.transpose()
yval=df.iloc[501:683,10:].values.transpose()**

我们决定用 683 行中的 500 行来构建我们的训练集,并且我们从标准化的 scaled_df 数据帧中挑选它们。我们还确保删除第一列(id ),并且不包括网络的输入 x 中的最后一列(class)

我们使用对应于相同的 500 行的类列声明目标输出 y 。我们从原始的非规范化 df dataframe 中选择 class 列(因为 class 值应该保持为 0 或 1)。

然后,我们为验证集选择接下来的 183 行,并将它们存储在变量 xvalyval 中。

我们准备好了。我们将首先用我们的 x,y 训练集的 500 行来训练网络。之后我们将使用我们的 xval,yval 验证集的 183 行来测试训练好的网络,以查看网络对它以前从未见过的数据的概括能力如何。

nn = dlnet(x,y)
nn.lr=0.01
nn.dims = [9, 15, 1]nn.gd(x, y, iter = 15000)

我们声明我们的网络,设置一个学习率和每层的节点数(输入有 9 个节点,因为我们使用的是 9 个特征,不算网络的一层。第一隐藏层具有 15 个隐藏单元,第二和最后一层具有单个输出节点)。

然后,我们通过几千次迭代运行梯度下降算法。让我们用几秒钟的梯度下降来感受一下网络的训练效果。

每 x 次迭代,我们显示网络的损耗值。如果训练进展顺利,损失值将在每个周期后下降。

**Cost after iteration 0: 0.673967
Cost after iteration 500: 0.388928
Cost after iteration 1000: 0.231340
Cost after iteration 1500: 0.171447
Cost after iteration 2000: 0.146433
Cost after iteration 2500: 0.133993
Cost after iteration 3000: 0.126808
Cost after iteration 3500: 0.122107
Cost after iteration 12500: 0.101980
Cost after iteration 13000: 0.101604
Cost after iteration 14500: 0.100592**

经过多次迭代后,我们的损失开始稳定在一个较低的水平。我们绘制了一个图表,通过迭代跟踪网络的损耗。

我们的网络似乎训练得相当好,达到了低损耗值(我们的预测和目标输出之间的距离很小)。但是,有多好?最重要的是,它有多好,不仅仅是对整个训练集,更重要的是,对我们的验证集

为了找到答案,我们创建了一个新函数, pred() ,它通过网络运行一组输入,然后系统地将每个获得的输出与其对应的目标输出进行比较,以便产生一个平均精度值。

请注意下面函数是如何研究预测值是高于还是低于 0.5 的。我们正在进行二元分类,默认情况下,我们认为高于 0.5 的输出值意味着结果属于其中一个类,反之亦然。

在这种情况下,因为 1 是恶性肿瘤的类值,我们认为高于 0.5 的输出预测恶性结果,低于 0.5 则相反。我们稍后将讨论如何、何时以及为什么要更改这个 0.5 的阈值。

def pred(self,x, y):  
        self.X=x
        self.Y=y
        comp = np.zeros((1,x.shape[1]))
        pred, loss= self.forward()    

        for i in range(0, pred.shape[1]):
            if pred[0,i] > 0.5: comp[0,i] = 1
            else: comp[0,i] = 0

        print("Acc: " + str(np.sum((comp == y)/x.shape[1])))

        return comp

现在,我们通过调用两次 pred 函数,一次使用我们的训练集,另一次使用我们的验证集,来比较使用训练集和验证集时网络的准确性。

**pred_train = nn.pred(x, y)
pred_test = nn.pred(xval, yval)**

我们得到了这两个结果。

**Acc: 0.9620000000000003
Acc: 1.0**

该网络在训练集(前 500 行)上的准确率为 96%,在使用验证集(接下来的 183 行)时的准确率为 100%。

验证集上的准确率更高。这意味着网络没有过度拟合,并且泛化得足够好能够适应它以前从未见过的数据。

我们现在可以使用 nn.forward()函数直接比较与目标输出相关的验证集输出的前几个值:

nn.X,nn.Y=xval, yval 
yvalh, loss = nn.forward()
print("\ny",np.around(yval[:,0:50,], decimals=0).astype(np.int))       
print("\nyh",np.around(yvalh[:,0:50,], decimals=0).astype(np.int),"\n")

我们得到了

**y [[0 0 0 1 0 0 1 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0
  0 0 0 0 0 0 0 0 0 0 0 0 0 1]]****yh [[0 0 0 1 0 0 1 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0
  0 0 0 0 0 0 0 0 0 0 0 0 0 1]]**

两个完全匹配,因为我们已经在验证集上实现了 100%的准确性

因此,该函数很好地学习了以适应训练集和验证集。

分析准确性的一个很好的方法是绘制一个混淆矩阵。首先,我们声明一个自定义绘图函数。

def plotCf(a,b,t):
    cf =confusion_matrix(a,b)
    plt.imshow(cf,cmap=plt.cm.Blues,interpolation='nearest')
    plt.colorbar()
    plt.title(t)
    plt.xlabel('Predicted')
    plt.ylabel('Actual')
    tick_marks = np.arange(len(set(expected))) # length of classes
    class_labels = ['0','1']
    tick_marks
    plt.xticks(tick_marks,class_labels)
    plt.yticks(tick_marks,class_labels)
    # plotting text value inside cells
    thresh = cf.max() / 2.
    for i,j in itertools.product(range(cf.shape[0]),range(cf.shape[1])):
        plt.text(j,i,format(cf[i,j],'d'),horizontalalignment='center',color='white' if cf[i,j] >thresh else 'black')
    plt.show();

(这个自定义的混淆矩阵函数来自JP 创建的这个公共 Kaggle)

然后,我们再次运行 pred 函数两次,并为训练集和验证集绘制混淆矩阵。

**nn.X,nn.Y=x, y 
target=np.around(np.squeeze(y), decimals=0).astype(np.int)
predicted=np.around(np.squeeze(nn.pred(x,y)), decimals=0).astype(np.int)
plotCf(target,predicted,'Cf Training Set')****nn.X,nn.Y=xval, yval 
target=np.around(np.squeeze(yval), decimals=0).astype(np.int)
predicted=np.around(np.squeeze(nn.pred(xval,yval)), decimals=0).astype(np.int)
plotCf(target,predicted,'Cf Validation Set')**

我们可以更清楚地看到,我们的验证集在其 183 个样本上具有完美的准确性。至于训练集,500 个样本中有 19 个错误。

现在,在这一点上,你可能会说,在像诊断肿瘤这样微妙的主题中,如果乙状结肠输出给出高于 0.5 的值,则将我们的预测设置为 1 并不是很好。在给出恶性肿瘤的预测之前,网络应该非常有信心。

我完全同意,那是非常正确的。这些都是你需要根据挑战的性质和你要处理的主题做出的决定。

然后,让我们创建一个名为阈值的新变量。它将控制我们的置信阈值,在我们确定肿瘤是恶性肿瘤之前,网络的输出需要多接近 1。默认情况下,我们将其设置为 0.5

**self.threshold=0.5**

外部预测函数现在被更新以使用置信度阈值。

def pred(self,x, y):  
        self.X=x
        self.Y=y
        comp = np.zeros((1,x.shape[1]))
        pred, loss= self.forward()    

        for i in range(0, pred.shape[1]):
            **if pred[0,i] > self.threshold: comp[0,i] = 1**
            else: comp[0,i] = 0

        print("Acc: " + str(np.sum((comp == y)/x.shape[1])))

        return comp

现在,让我们随着置信度阈值的逐渐提高来比较我们的结果。

置信度阈值:0.5。输出值需要高于 0.5 才能被视为恶性输出。如前所述,验证准确率为 100%,训练准确率为 96%。

置信度阈值:0.7。输出值需要高于 0.7 才能被视为恶性输出。验证准确率保持在 100%,训练准确率下降到 95%。

置信度阈值:0.8。输出值需要高于 0.8 才能被视为恶性输出。第一次验证准确率非常非常轻微地下降到 99.45%。在混淆矩阵中,我们看到 183 个样本中有 1 个没有被正确识别。训练精度下降更多,直到 94.2%

置信度阈值:0.9。最后,在 0.9 的情况下,输出值需要高于 0.9 才能被视为恶性输出。我们正在寻找几乎完全的信心。验证准确度稍微下降,直到 98.9%。在混淆矩阵中,我们看到 183 个样本中有 2 个没有被正确识别。训练准确率进一步下降到 92.6%。

因此,通过控制置信度阈值,我们可以适应挑战的特定需求。

如果我们想要降低与我们的训练集相关的损失值(因为我们未能识别一小部分训练样本),我们可以尝试训练更长时间,并且还可以使用不同的学习率。

例如,如果我们设置学习率为 0.07,训练 65000 次迭代,我们得到:

**Cost after iteration 63500: 0.017076
Cost after iteration 64000: 0.016762
Cost after iteration 64500: 0.016443
Acc: 0.9980000000000003
Acc: 0.9945054945054945**

现在,将我们的置信阈值设置为 0.5,网络对两组中的每个样本都是准确的,除了每组中的一个样本。

如果我们将置信度阈值提高到 0.7,性能仍然很好,只有 1 个验证样本和 2 个训练样本没有被正确预测。

最后,如果我们真的要求很高,并且将置信度阈值设置为 0.9,则网络无法正确猜测 1 个验证样本和 10 个训练样本。

虽然我们做得很好,但考虑到我们使用的是没有正规化的基本网络,当您处理更复杂的数据时,事情通常会变得更加困难。

通常情况下,亏损局面会变得非常复杂,而且更容易陷入错误的局部最小值,或者无法收敛到足够好的亏损。

此外,根据网络的初始条件,我们可能会收敛到一个好的极小值,或者我们可能会在某处停滞不前,无法摆脱它。在这个阶段,再次描绘我们的初始动画是很有用的。

Navigating the Loss Landscape. Values have been modified and scaled up to facilitate visual contrast.

想象一下这样的风景,到处都是山丘和山谷,有些地方损耗很高,有些地方损耗很低。与复杂场景相关的损失函数的情况通常不一致(虽然可以使用不同的方法使其更加平滑,但这是一个完全不同的主题)。

到处都是深浅不一、角度各异的山丘和山谷运行梯度下降算法时,您可以通过改变网络的损失值来改变地形

并且你移动的速度由学习率控制:

  • 如果你移动得非常慢,不知何故到达了一个不够低的高原或山谷,你可能会被困在那里。
  • 如果你走得太快,你可能会到达一个足够低的山谷,但穿过它,并以同样快的速度离开它。

因此,有些非常微妙的问题会对您的网络性能产生巨大影响。

  • 初始条件:在流程开始时,你把球丢在景观的哪个部分?

  • 你移动球的速度,学习率。

最近在提高神经网络训练速度方面取得的许多进展都与不同的技术有关,这些技术动态地管理学习率,也与以更好的方式设置初始条件的新方法有关。

关于初始条件:

  • 记住,每一层计算前一层的权重和输入的组合(输入的加权和),并将该计算传递给该层的激活函数。
  • 这些激活函数的形状可以加速或停止神经元的动态变化,这取决于输入范围和它们对该范围的反应方式之间的组合。
  • 例如,如果 sigmoid 函数接收的值触发了接近其输出范围极值的结果,则激活函数在该范围部分的输出会变得非常平坦。如果它在一段时间内保持不变,导数,在那一点的变化率变为零或者非常小。
  • 回想一下是导数帮助我们决定下一步的走向。因此,如果导数没有给我们提供有意义的信息,网络将很难知道从该点开始下一步的方向。
  • 就好像你已经到达了风景中的一个高原,你真的不知道下一步该去哪里,你只是不停地绕着那个点转圈。
  • ReLU 也可能发生这种情况,尽管 ReLU 只有 1 个平面,而不是 2 个乙状结肠和 Tanh。 Leaky-ReLU 是 ReLU 的一个变种,它稍微修改了函数的那一面(平坦的那一面),试图防止渐变消失。

因此,以可能的最佳方式设置我们的权重的初始值是至关重要的,以便在训练过程开始时单元的计算产生落入我们的激活函数的最佳可能范围内的输出。

这可能会使从开始的整个区别成为一个真正高的损失或更低的损失。

管理学习率以防止训练过程太慢或太快,并使其值适应过程和每个参数的变化条件,是另一个复杂的挑战

谈论处理初始条件和学习率的许多方法需要几篇文章。我将简要描述其中的一些方法,让专家们了解一些应对这些挑战的方法。

  • Xavier 初始化:一种初始化我们的权重的方法,这样神经元就不会开始处于饱和状态(陷入输出范围的微妙部分,在那里导数无法为网络提供足够的信息来知道下一步去哪里)。
  • 学习率退火:高学习率会推动算法绕过并错过损失景观处的良好最小值。逐渐降低学习速度可以防止这种情况。有不同的方法来实现这种减少,包括:指数衰减、阶跃衰减和 1/t 衰减。
  • fast . ai Lr _ find():fast . ai 库的一种算法,为学习率寻找理想的取值范围。 Lr_find 通过几次迭代训练模型。它首先尝试使用一个非常低的学习速率,并在每个小批量中逐渐改变速率,直到它达到一个非常高的值。每次迭代都会记录损失,一个图表可以帮助我们将损失与学习速度进行对比。然后,我们可以决定以最有效的方式减少损失的学习率的最佳值。
  • 不同的学习率:在我们网络的不同部分使用不同的学习率。
  • SGDR,带重启的随机梯度下降:每 x 次迭代重置我们的学习率。如果我们陷入其中,这可以帮助我们走出不够低的高原或局部极小值。典型的过程是从高学习率开始。然后在每一个小批量中逐渐减少。经过 x 个周期后,您将其重置回初始高值,并再次重复相同的过程。这个概念是,从高速率逐渐移动到较低的速率是有意义的,因为我们首先从景观的高点(初始高损耗值)快速向下移动,然后缓慢移动以防止绕过景观的最小值(低损耗值区域)。但是,如果我们在某个不够低的高原或山谷中停滞不前,那么每 x 次迭代就将我们的速率重新设置为一个较高的值,这将有助于我们跳出这种情况,继续探索这一领域。
  • 1 周期策略:les lie n . Smith 提出的一种动态改变学习速率的方式,我们从一个较低的速率值开始,逐渐增加,直到达到最大值。然后,我们继续逐渐减少它,直到过程结束。最初的逐渐增加允许我们探索大面积的损失景观,增加我们到达不颠簸的低区域的机会;在循环的第二部分,我们在我们到达的低平地区安顿下来。
  • 动量:随机梯度下降的一种变化,有助于加速通过损失景观的路径,同时保持总体方向受控。回想一下,SGD 可能很吵。动量平均化路径中的变化,使路径变得平滑,并加速向目标的移动。
  • 自适应学习率:为网络的不同参数计算和使用不同学习率的方法。
  • AdaGrad ( 自适应梯度算法):
    结合上一点,AdaGrad 是 SGD 的变体,它不是对所有参数使用单一的学习速率,而是对每个参数使用不同的速率。
  • 均方根传播 (RMSProp):像 Adagrad 一样,RMSProp 对每个参数使用不同的学习速率,并根据它们变化的平均速度来调整这些速率(这在处理嘈杂的环境时很有帮助)。
  • 亚当:它结合了 RMSprop 和 SGDR 的一些方面与动力。像 RMSprop 一样,它使用平方梯度来缩放学习速率,并且它还使用梯度的平均值来利用动量。

如果你对这些名字都不熟悉,不要不知所措。在它们大多数的背后是非常相同的根:反向传播和梯度下降。

此外,在现代框架(如 fast.ai 库)中,许多这些方法都是自动为您选择的。理解它们是如何工作的是非常有用的,因为这样你就能更好地做出自己的决定,甚至研究和测试不同的变化和选择。****

理解意味着更多的选择

当我们理解了网络的核心,基本的反向传播算法和基本的梯度下降过程,每当我们面临艰难的挑战时,我们就有更多的选择去探索和实验。

因为我们了解这个过程,所以我们意识到,例如在深度学习中,我们在损失范围内的初始位置是关键。****

一些初始位置会很快推动球(训练过程)卡在景观的某个部分。其他人会很快把我们逼到一个很好的最小值。

当神秘函数变得更加复杂时,就是时候加入我前面提到的一些高级解决方案了。现在也是时候更深入地研究整个网络的架构,并更深入地研究不同的超参数。

浏览风景

我们的损失状况在很大程度上受到网络架构设计以及超参数的影响,如学习率、我们的批量大小、我们使用的优化算法等。

****

有关这些影响的讨论,请查看这篇论文:李浩、、加文·泰勒、克里斯托夫·斯图德、汤姆·戈尔茨坦的《可视化神经网络的损失景观》

最近的研究得出了一个非常有趣的观点,即神经网络中的跳过连接模型如何平滑我们的损失景观,并使它变得更加简单和凸,增加我们收敛到好结果的机会。

Navigating the Loss Landscape. Values have been modified and scaled up to facilitate visual contrast.

****跳过连接对训练非常深的网络帮助很大。基本上,跳过连接是链接不同层的节点的额外连接,跳过中间的一个或多个非线性层。

当我们用不同的架构和参数实验时,我们正在修改我们的损失场景,使其更加崎岖或平滑,增加或减少局部最优解的数量。当我们优化初始化网络参数的方式时,我们正在提高我们的起点。

让我们继续探索新的方法来应对世界上最迷人的挑战。

Navigating the Loss Landscape. Values have been modified and scaled up to facilitate visual contrast.

这篇文章涵盖了基础知识,从这里开始,前途无量

链接到本文的 3 个部分:
第 1 部分 | 第 2 部分||第 3 部分

Github 仓库里有这个项目的所有代码

** [## javismiles/深度学习预测乳腺癌肿瘤恶性肿瘤

用 Python 从头开始编码的 2 层神经网络预测癌症恶性程度。…

github.com](https://github.com/javismiles/Deep-Learning-predicting-breast-cancer-tumor-malignancy)**

用海报预测电影收入

原文:https://towardsdatascience.com/predict-movie-earnings-with-posters-786e9fd82bdc?source=collection_archive---------29-----------------------

用电影海报确定电影的类型和收益

如果你有一部夏季大片或一部短片,抓住观众注意力和兴趣的最佳方式是什么?两个最突出的方法是显而易见的:海报和预告片。

电影海报传达了电影的基本信息,如片名、主题、人物、演员以及电影制作人。电影海报可以告知观众他们正在观看的电影类型,因此,如果给定一张电影海报,机器学习模型可以识别电影的类型吗?

电影海报是促销的重要来源,优秀的海报设计有利于吸引尽可能多的观众。我们想知道,如果给我们一张电影海报,我们能否预测这部电影的票房是否会很好?

在本文中,我们将探讨数据准备和使用卷积神经网络来构建机器学习模型来回答这些问题。

资料组

我们从电影数据库(TMDb) 中收集了 45466 个电影元数据。我们可以从 TMDb 获得各种各样的属性,但是对于这个实验,我们只对以下字段感兴趣,1)标题,2)类型,3)海报,4)受欢迎程度,5)预算,6)收入。

由于一部电影可以分为多种类型,我们将只选择每部电影的第一种类型,因此每部电影只能有一种类型。在这个实验中,我们打算预测一部电影是否会在票房上取得好成绩,我们将使用收入/预算比率,定义为如果该值大于 1,则电影正在赚钱;否则,就不是。

以下是熊猫数据框中加载的样本数据集:

数据分析和过滤

我们不会马上下载所有 45466 张图片。相反,我们会做一些分析,筛选出那些有数据问题的,并选择电影海报列表进行下载。

首先,我们将删除那些缺少信息的内容:

  • 删除所有非字母数字字符后的空白标题
  • 没有流派
  • 没有海报网址
  • 没有预算
  • 没有收入

过滤掉不需要的数据后,有 40727 部电影。以下是各类型电影的数量分布:

对于我们的类型预测任务,我们想要预测 10 个类别。因此,我们将选择前 10 个流派,并删除其余的。

因此,我们根据受欢迎程度选择了每个类型中最受欢迎的 1000 部电影。这些是我们将要下载的电影海报,10 种类型的 10,000 张图片。

下载电影海报

从上面显示的数据框来看, poster_path 是文件的名称。为了获得玩具总动员海报的图片 URL,我们将http://image.tmdb.org/t/p/w185/追加到海报 URL 以获得:http://image . tmdb . org/t/p/w185//rhirbceo 9 lr 4 veexuwcc 2 wartg . jpg

我们可以用请求库下载所有的图像。我建议在每次图像下载之间增加 1 秒钟的延迟。这段代码用于将图像下载并保存到相应的流派文件夹中,以预测电影的流派:

图像处理

为了利用预先训练的模型,我们首先需要将我们的矩形海报转换成正方形。此外,为了减少计算成本,图像尺寸被调整为 224 乘 224。我们确定了 4 种图像处理方法来满足这些要求:

  • PIL 图书馆调整大小
  • 居中裁剪库调整大小
  • 填料
  • 随机裁剪和调整大小

方法 1: PIL 图书馆调整大小

使用 PIL 图书馆调整图像大小为 224x224。

from PIL import Imageimage = Image.open(PATHOFIMAGE)
image = image.resize((224, 224), Image.BILINEAR)
image.save(NEWPATH)

调整大小后处理过的图像变形如下:

方法 2:居中裁剪

我们将使用 PyTorch 的火炬视觉转换图像。

do_transforms = transforms.Compose([
        transforms.CenterCrop(input_size),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406],
                             [0.229, 0.224, 0.225])
])dataset = datasets.ImageFolder(PATH, transform=do_transforms)

处理后的图像导致图像的顶部和底部都被裁剪。

方法 3:填充

由于大多数电影海报都是纵向的,我们决定在左右两边添加黑色填充。这将避免原始海报图像的任何扭曲和裁剪。因为黑色填充在 RGB 中为零,所以它对我们的卷积神经网络的影响最小。

from skimage.transform import resizedef resize_image_to_square(img, side, pad_cval=0, dtype=np.float64):    
    h, w, ch = img.shape
    if h == w:
        padded = img.copy()
    elif h > w:
        padded = np.full((h, h, ch), pad_cval, dtype=dtype)
        l = int(h / 2 - w / 2)
        r = l + w
        padded[:, l:r, :] = img.copy()
    else:
        padded = np.full((w, w, ch), pad_cval, dtype=dtype)
        l = int(w / 2 - h / 2)
        r = l + h
        padded[l:r, :, :] = img.copy()resized_img = resize(padded, output_shape=(side, side))
    return resized_img

应用填充后的处理图像:

方法 4:随机裁剪和调整大小

我们将使用 PyTorch 的火炬视觉转换图像。

do_transforms = transforms.Compose([
        transforms.RandomCrop((280,280), padding=None, pad_if_needed=True, fill=0, padding_mode='constant'),
        transforms.Resize(input_size, interpolation=2),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406],
                             [0.229, 0.224, 0.225])
])dataset = datasets.ImageFolder(PATH, transform=do_transforms)

经过随机裁剪和调整后的处理图像。

图像处理结果

为了测量图像处理方法的准确性,我们使用预训练的 ResNet18 来执行分类。我们将分为喜剧和恐怖两种类型,因为它们的海报总体上明显不同。为了确保我们的比较是公平的,我们做了以下工作:

  • 用于训练的同一套电影和用于验证的同一套电影
  • 设置种子编号
  • 从 PyTorch 的火炬视觉载入预训练的 ResNet18

不同图像处理方法的模型精度如下:

  • PIL 图书馆的大小约为 80%
  • 中心裁剪库大小调整约为 80%
  • 填充率约为 85%
  • 随机裁剪和调整大小约为 85%

随机裁剪和调整大小方法在模型精度和处理速度方面表现最佳。在卷积神经网络中,图像中对象的位置无关紧要。

我们能通过海报来辨别电影的类型吗?

在我们的预处理步骤中,我们可以实现大约 85%的分类准确率,分为两类:喜剧恐怖。我们选择喜剧恐怖是因为这两种类型的海报截然不同。喜剧一般颜色较亮,而恐怖相比之下可能较暗。

以下是我们的一些测试案例,这些案例是模型看不到的:

有趣的是,该模型可以学习和区分这两种类型。这位模特很可能会拿起有骷髅头图案的海报,并将海报与恐怖电影联系起来。第四张图片显示并非所有白色背景的海报都是喜剧电影,并且模型预测是正确的。

然而,由于并非所有类型都遵循电影海报设计的一般要求,这些海报可能会导致模特误读设计。随后,模型可能会将这些电影错误地分类到相反的类型中。以下是一些电影海报的例子,它们偏离了与各自类型相关的一般设计。

第一个图像包含许多白色区域,通常看起来令人愉快,而第二个图像包含大面积的黑色区域,这导致海报看起来很暗,尽管有卡通设计和字体。这些布局误导了模型,从而导致了错误的预测。

10 种类型之间的模型识别

在我们的数据集中,我们有 10 个流派;每种类型包含 1000 张电影海报。执行 80/20 分割来训练和验证模型。我们使用了 8000 张图像进行训练,2000 张图像进行验证(不用于训练)。

我们利用预训练的 ResNet18 模型的权重来训练一个模型,以根据电影的海报对电影的类型进行分类。这些是训练过程中的精确度和损耗。

验证准确率约为 32%。我们的模型可以在训练集上学习和过拟合,但不能在验证数据集上推广。

前 3 名的准确率约为 65%。这让我们思考,是什么导致了所有的错误分类?我们如何进一步提高它的准确性?下面是一个热图,显示了前 1 名模型的所有错误分类:

我们意识到这个模型很难区分恐怖片和惊悚片《T21》的海报。如果你仔细想想,即使对我们人类来说也是如此,我们可能无法区分恐怖片和惊悚片。

同样的结果也可以在喜剧和浪漫中观察到,因为这两种类型的海报都采用了更轻松的基调,并且包含了人类和微笑的面孔。

我们可以通过海报来判断这部电影是否会在票房上赚钱吗?

由于海报是一部电影的营销工具,我们想知道一部电影海报是否能吸引更多的观众。一个模型能识别出一种特定类型的海报在票房上是否更好吗?

在我们的实验中,我们通过电影的收入与预算比率来定义电影的表现。预算较高的电影需要更高的收入才能收支平衡。比例越高,电影做的越好。

我们用收入与预算比率创建了两个类别,“做得好”和“做得不好”。比率为 1 及以上的电影“做得好”,否则归类为“没做好”。

预训练结果 18

是啊!我们预先训练的 ResNet18 模型可以正确识别一部电影是否有赚钱的潜力,准确率约为 68%。

我们能做得比这更好吗?我可以换一个更深入的实验,但不会很有趣,所以这里有一些我们尝试过的其他实验。

卷积神经网络用于图像分类的技巧包

Tong He et al. 的一篇论文建议通过在下采样块中接收更多信息来改善 ResNet 调整。

作者使用这些调整将 ResNet50 模型 top-1 在 ImageNet 上的准确率从 75.3%提高到 79.29%

Mish 激活函数

Mish 是一个上无界、下有界、光滑、非单调的激活函数。

Mish 激活函数的正范围与最流行的激活函数 ReLu 非常相似。被限制在下面导致了正则化效应。负范围保留了小的负输入,这改善了表现性和渐变流动。阅读更多关于米什的文章,作者是迪甘塔·米斯拉。

数据扩充

最近,模型精度的提高归功于通过数据扩充产生了更多的数据;这显著增加了可用于训练的数据的多样性。

from torchvision import transformsimage_transforms = {
    # Train uses data augmentation
    'train':
    transforms.Compose([
        transforms.RandomResizedCrop(size=256, scale=(0.8, 1.0)),
        transforms.RandomRotation(degrees=15),
        transforms.ColorJitter(),
        transforms.RandomHorizontalFlip(),
        transforms.CenterCrop(size=224),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406],
                             [0.229, 0.224, 0.225])
    ]),
    # Validation does not use augmentation
    'validate':
    transforms.Compose([
        transforms.Resize(size=256),
        transforms.CenterCrop(size=224),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406],
                             [0.229, 0.224, 0.225])
    ]),
}

自制深宽网

这是受推荐系统的宽&深模型的启发,通过将预训练的 ResNet 与预训练的宽 ResNet 相结合。

首先,我们加载预训练的 ResNet 和预训练的 wide-ResNet,并移除最后的完全连接层以进行 ImageNet 分类。然后,我们将来自输入的 3×3 卷积、批量归一化和 ReLu 附加到两个 ResNet。最后,我们连接来自两个 ResNet 的输出,然后添加另一个 3×3 卷积和用于分类的全连接层。

各种实验的分类结果

以下是我们的结果:

米什可以得到 3%的改善,因为正则化效果,从而更好地概括了看不见的。我会给这个激活更多的探索在未来。

数据增强也有 3%的改进,事实上,我有点惊讶数据增强会在这个问题上有所改进。

结论

仅凭一张电影海报,预测一部电影的收入和受欢迎程度可能是一项艰巨的任务。这个问题甚至对分销公司和投资者来说也是如此,他们有数百名专家和分析师为他们工作,以确保他们的投资不会白费,并获得丰厚的回报。我们模型的引入和发展可能会在未来帮助这些分析师和公司做出更详细和合理的预测。

根据进一步的实验,为了获得更准确的读数,深入研究更深的 ResNet 模型可能会提高性能。然而,在我们的实验中,我们应用了 Mish 激活和来自研究论文的各种调整;因此,返回的结果是有希望的,是一条值得进一步探索的道路。

训练 AI 模型是成功的一半;值得注意的是,真实世界的数据是“肮脏的”和“未经提炼的”;这意味着并不是所有的数据都是准确和真实的。为了让机器学习发挥作用,我们必须首先很好地理解我们的数据,并理解我们的模型要成功需要什么。

[## 数据科学家:21 世纪最肮脏的工作

40%的吸尘器,40%的看门人,20%的算命师。

towardsdatascience.com](/data-scientist-the-dirtiest-job-of-the-21st-century-7f0c8215e845) [## 用海报预测电影收入

用电影海报确定电影的类型和收益

towardsdatascience.com](/predict-movie-earnings-with-posters-786e9fd82bdc)

以 50%的准确率预测足球比赛

原文:https://towardsdatascience.com/predict-soccer-matches-with-50-accuracy-a24cc8078877?source=collection_archive---------12-----------------------

如何在 Python 中使用泊松分布进行足球预测

在过去的几年里,博彩和博彩游戏变得更加流行。你可以通过电脑或智能手机随时随地下注。因此,很多人试图预测体育赛事的结果并不奇怪。足球有一个巨大的市场,你可以在足球比赛中出现的几乎每一个事件上下注,比如进球、球员、卡片、角球等等。最常见的赌注叫做 1x2。你试着预测主队会赢(1),平局(x)还是客队会赢(2)。不信任章鱼保罗等动物的人正在寻找更复杂的统计方法。我想和你分享一个非常基本的方法。我们将更深入地了解泊松分布以及它在 Python 中的用法。关于基本的统计概念,请看这里的和这里的。在接下来的部分中,我将使用大卫·希恩在他的精彩博客中发表的部分代码。我将更深入地探讨细节,并在更广泛的样本上回顾该模型,以回答该模型有多成功的问题。一如既往,我不会包括我使用的任何代码,但你可以在 Github 上找到它。

获取一些数据

在我们创建模型、预测结果并检查其正确性之前,我们需要一些数据。那么让我们看看最后一个英超赛季:

20 支球队在 PL 中比赛,因此我们删除最后 10 行,并尝试预测最后一个比赛日的结果。

对于足球迷来说,这个结果并不奇怪。主队平均比客队进更多的球。

一支球队在单场比赛中的最高进球数是 6 个。基本上,我们现在已经有了泊松分布所需的所有参数。第一步,我们可以预测主队进 1 球的概率,例如:

主队进 1 球的几率是 32.6%。让我们想象一下 0 到 6 之间的目标值,以及主客场的目标值:

条形图显示了 2018/19 赛季的实际进球数量。这些线条显示了我们的泊松计算值,它们似乎非常符合实际分布。

预测一场英超比赛

现在我们已经学习了泊松分布的基本概念。但是我们如何预测一场比赛的进球数呢?答案是泊松回归:

我们将关注这种回归的主要方面。观察次数为 740 次(20 支球队 37 个比赛日)。在第一行中,我们看到一个名为“Intercept”的条目。这是基准比率,所有其他估计都是相对于它的。利物浦的团队系数是 0.1849,这意味着利物浦比一般球队进更多的球。系数越低,球队平均射门次数越少。切尔西的对手系数是-0.2537。切尔西的进球数比一般球队少。系数越低,球队平均进球越少。最后一排“主场 0.2526”是主场优势的表达:主场球队平均进球多。
为了计算利物浦的预计进球数,我们使用以下公式:

exp(Intercept+Coefficients)

因此,让我们填写 a)利物浦和 b)切尔西的公式:

请注意,我们只为利物浦添加了“主场”系数(0.2526)。预测将是 2:1,所以我们预计利物浦会赢。现在,让我们将我们的预测与上一个比赛日进行比较。

测试最后一个比赛日

好吧,我们预测了一半比赛日是正确的,一半比赛日是错误的。对于这样一个简单的模型,这并不坏,或者你认为呢?但我们只是预测了一场比赛,这是最后一个比赛日,一般来说不太容易预测。一些球队无法改变他们在积分榜上的位置,因此他们没有什么可失去的,这导致了他们的表现变弱。下一步,我们看看欧洲排名前五的联赛(关于欧足联系数)并挑选一个更大的样本。我们预测每个联赛的最后 10 个比赛日。

测试 5 个欧洲顶级联赛的最后 10 个比赛日

我们可以很容易地使用相同的代码和以前一样,并运行它为 5 而不是 1 联赛。正如我们之前所做的,我们在训练数据集中删除了最近 10 个比赛日:

在上图中,你看到了我们已经预料到的情况。最后两个比赛日的预测更糟糕。但总的来说,这条线大部分时间都在 5 点左右,这意味着特定比赛日的比赛有一半是预测正确的。总之,我们已经正确预测了 48.2%的比赛日。你可以看到标题是点击诱饵;).

结论

我们看到,通过使用简单的泊松回归,我们几乎可以正确预测 50%的匹配。它可以很容易地与 Python 一起使用,并允许有效的计算。它还可以快速扩展。您可以扩展代码来预测 a)其他联赛或 b)更多比赛的比赛。如果你想在这个模型上更进一步,你可以检查团队的形式,时间加权或者低估或高估结果的正确性。我希望你喜欢阅读!

如果您喜欢中级和高级数据科学,并且还没有注册,请随时使用我的推荐链接加入社区。

使用机器学习预测 2018-19 年 NBA 最有价值球员

原文:https://towardsdatascience.com/predicting-2018-19-nbas-most-valuable-player-using-machine-learning-512e577032e3?source=collection_archive---------18-----------------------

ML 模特对今年 NBA 最有价值球员有什么评价?

我们已经深入到季后赛了,但是投票结果还没有公布,这给了我一个想法去预测 MVP 的结果。

Photo by Kenny Eliason on Unsplash

收集数据

我在 basketball-reference 上找到了从 1968 年到 1969 年的每个赛季的数据,但我只使用了 1980 年到 1981 年的数据,这是媒体投票的第一个赛季,在此之前投票是由球员进行的。这是一个关于 2015-16 赛季数据的例子

这也是第一次,也是迄今为止唯一一次,一个球员被一致投票选为 MVP(每个投票者都给他第一名的选票)。

除了上表中的数据,我还搜集了一些不可用的玩家资料,例如,BPM,PER,TS%等数据。

刮刀的代码可以在这里找到。

但是我鼓励你避免再次抓取它,因为数据已经被获取,并且可以在我的 GitHub repoKaggle 中找到。

定义问题

有了这些数据,我将定义将要解决的问题。因为这是一个排序的任务,所以没有现成的算法可以解决这个问题。我将尝试通过回归问题来简化这个问题。

我们任务的目标值将是前面表格中的 Share 列。这个数字总是在 0 和 1 之间,代表每个玩家在投票中赢得的点数。

由于数据的结构方式,我们基本上是在试图模拟媒体将如何投票给本赛季的球员。当然,回归对于解决这样的问题是很棒的,但是这里的问题在于数据,因为它很不平衡。在奖励份额值的直方图上,您可以看到一半以上的示例位于 0.0 和 0.2 之间,这可能会给模型带来问题。

Image by Author

功能和功能选择

在我开始训练和验证模型之前,我将解释我将使用什么作为特征和一些简单的特征选择过程。

pts_per_g,ast_per_g,trb_per_g,stl_per_g,blk_per_g 是简单的 stats。场均得分,助攻,篮板,抢断,盖帽。 mp_per_g 代表每场比赛的分钟数, win_pct 代表球队的胜率(一个赛季有 82 场比赛,所以这个值代表 games_won 除以 games_total)。

fga, fg3afta 分别是投篮命中率,三分球命中率,罚球命中率, fg_pctfg3_pctft_pct 分别代表投篮命中率,三分球命中率,罚球命中率。

现在我们来看一些高级统计数据。

PER 代表玩家效率等级,基本上是所有正面和负面简单统计的计算。

BPM 代表框加减,是评估球员素质和对球队贡献的高级统计。根据,与相比,它需要更多的团队属性。

TS_PCT 代表真实投篮命中率,计算相当简单(实际上也包含在上面的统计中)。公式如下:

PTS / (2 * (FGA + 0.44 * FTA)) * 100

USG_PCT 是使用率百分比的缩写,是对球员在球场上使用的团队比赛的估计。计算可以在这里找到

而且最后 WSWS_per_48 代表赢股和每 48 分钟赢股。该统计试图将团队的成功划分到团队的个人成员身上。

特征选择

现在,即使在我对这些统计数据的简短描述中,您也可以看到它们是相似的,这就是为什么我继续进行几个简单的特征选择过程,以消除那些不能带来大量信息的特征。

第一个是使用互信息找出变量之间的依赖关系,这是使用 sklearn 的特征选择包完成的。

ws: 0.2884
per: 0.2811
ws_per_48: 0.2592
bpm: 0.2013
pts_per_g: 0.1482
usg_pct: 0.1053
win_pct: 0.0973
fta: 0.0948
ts_pct: 0.0872
fga: 0.0871
trb_per_g: 0.0695
mp_per_g: 0.0668
fg3a: 0.0355
ft_pct: 0.0311
ast_per_g: 0.0279
stl_per_g: 0.0139
fg_pct: 0.0089
blk_per_g: 0.0066
fg3_pct: 0.0000

然后,我使用随机森林回归,我拟合随机森林的实例,并找到该模型最重要的特征。

ws: 0.3911
win_pct: 0.1326
per: 0.0938
bpm: 0.0537
ws_per_48: 0.0428
fga: 0.0368
usg_pct: 0.0310
ft_pct: 0.0263
ast_per_g: 0.0253
mp_per_g: 0.0251
fg_pct: 0.0231
fta: 0.0213
pts_per_g: 0.0183
fg3_pct: 0.0159
ts_pct: 0.0155
trb_per_g: 0.0153
blk_per_g: 0.0118
stl_per_g: 0.0103
fg3a: 0.0101

最后,我找到了特征之间的关联矩阵。这对我帮助最大,因为我有一个很好的方法来可视化哪些特征非常相关,从而带来重复的信息,这在某些情况下对模型没有帮助。

Image by Author

在上一个图像中,您可以查看数据集中所有要素之间的相关性。之后,我去除了那些高度相关的或者基本上代表同一事物的特征,或者可以用一些标量乘法来表示的特征。

有了以上描述的几个步骤和我在上面描述的统计定义,我决定将特征简化为这些最终特征:

ts_pct
bpm
mp_per_g
pts_per_g
trb_per_g
ast_per_g
stl_per_g
blk_per_g
ws
win_pct

虽然它们之间仍然有一些关联,但我仍然决定使用这些功能,因为它们看起来最适合使用。我试着用所有的特性运行一个实验,结果导致了更慢的训练时间和更差的结果,这意味着这个特性选择是有意义的。

培训和验证

除了用于验证的一个季节之外,我使用所有季节的训练数据来进行交叉验证。我以这种方式训练每个模型,并平均每个赛季的结果。

我使用的指标是回归的均方误差,因为我想得到媒体投票的准确表示。但我也对结果进行了排序,并衡量了排名的准确性,我将此作为一个辅助指标。

对于回归,我使用了以下模型:

  • 线性回归
  • 里脊回归
  • 梯度推进回归器
  • 随机森林回归量
  • 连续的血管反应

我以上述方式对常规和多项式特征(多项式次数为 2 和 3)进行了实验。在训练期间,我对 0 和 1 之间的数据进行了缩放和不缩放的实验,结果没有显示出太大的差异。

通过梯度推进回归器获得最佳结果,该模型保持前 6 位(一些模型具有不同的参数或不同次数的多项式)。

完整的结果可以在我的 GitHub 库上看到,它们也放在三个文件中,分别叫做 reg_results_mse_sorted.txt,reg_results_sorted_top_1.txt,reg _ results _ sorted _ top _ 5 . txt

2018-19 赛季预测

最后,以下是对这一季的预测。首先,我将展示几个回归模型的结果,这些模型在所有验证分割中均方误差最好。

梯度推进回归器

该模型具有 50 个估计器和 0.1 的学习率,并且特征具有二次多项式特征。

Image by Author

梯度推进回归器的其他变化也有类似的结果,哈登排在第一位。此外,markdown 到 pdf 的转换器很笨拙,而且字母之间有间隔(我在 markdown 中创建了表格并转换为 pdf,然后将该表格的截图放在这里,我承认这是一个粗略的过程)。

随机森林回归量

具有 50 个估计量的模型,其中特征具有仅具有交互作用的三次多项式。

Image by Author

但是对于稍微不同的参数,例如 100 个估计量和仅具有交互作用的二次多项式特征,结果稍微不同。

Image by Author

山脉

岭是一种正则化的线性回归。值为 10 的 Alpha 和二次多项式特征显示为最佳结果。

Image by Author

线性回归

现在我也使用了正态线性回归,虽然这个模型的 MSE 非常好,但是结果却很有趣。

Image by Author

由于没有正则化,该模型可能在某些特征上过度拟合,并给出了这些结果。这是德怀特·鲍威尔和德安德鲁·乔丹唯一出现在前 5 名的地方,所以它们被排除在你将在下一节看到的图表之外。

支持向量回归

这是用于回归的支持向量机的变体。以下是模型的结果,其中 C=100伽马=0.001

Image by Author

此外,由于所有这些模型都是回归模型,它们当然可能会超过这些值,超过最大值 1.0。这并不理想,但由于我们对排名最感兴趣,我们可以接受。

平均 2018-19 年预测

在下图中,您可以看到根据按 MSE 指标排序的前 50 名模型,前 10 名玩家的平均得分。

Image by Author

哈登和詹尼斯之间非常接近,但哈登略有优势。在他们之后,Jokic 占据了第三的位置,尽管他没有进入最佳模特预测的前五名。而在那之后,其他所有分数都比较接近,实际上,会比那个小很多。

结论

在这篇文章的最后,我想祝贺詹姆斯·哈登,2018-19 赛季 NBA 最有价值球员!哈登被我训练和测试的大多数模型预测为 MVP(包括顶级模型,具有各种不同参数的梯度推进回归器),当一个人花一点时间看他的统计数据时,他应该是合乎逻辑的,然而詹尼斯也有一个惊人的赛季,在我看来,媒体可能会更偏向于他。

未来的工作

我认为这篇文章和我目前所做的所有工作只是触及了表面,我认为为了获得数据,可以进行更多的特征工程和搜集工作。

我认为,如果能够获得整个球队和整个赛季的平均数据,将会有很大收获。因为用它可以实现更好的数据标准化。此外,你可以查看谁在他们的团队中表现最突出,换句话说,谁携带他们最多(像今年的詹姆斯·哈登)。我也想这么做,但现在我不得不划清界限。

最后的话

我在我的 GitHub 资源库上传了完整的笔记本,它做了这篇文章中描述的所有事情,这里有一个到它的直接链接

这里还有一个直接链接到我的 GitHub 知识库,里面有很多关于篮球和数据科学的东西。

我也上传了数据集到 Kaggle,这里有一个链接到 Kaggle 数据集

用最大似然法预测房价。网

原文:https://towardsdatascience.com/predicting-a-house-price-using-ml-net-6555ff3caeb?source=collection_archive---------25-----------------------

Photo by todd kent on Unsplash

ML。Net 是一个开源的跨平台机器学习框架。NET 开发人员。Python(例程是用 C++编写的)通常用于开发许多 ML 库,例如 TensorFlow,当您需要在上紧密集成 ML 组件时,这会增加额外的步骤和障碍。Net 平台。ML.Net 提供了一套很棒的工具,让你可以使用。你可以找到更多关于 ML 的信息。网这里

针对 ML 1.4.0 更新

ML。NET 允许你开发一系列的 ML 系统

  • 预测/回归
  • 问题分类
  • 预测性维护
  • 图像分类
  • 情感分析
  • 推荐系统
  • 集群系统

ML。NET 为机器学习提供了一个开发者友好的 API,支持典型的 ML 工作流:

  • 加载不同类型的数据(测试、IEnumerable、二进制、拼花、文件集)
  • 转换数据特征选择、归一化、改变模式、类别编码、处理缺失数据
  • 为你的问题选择一个合适的算法(线性,提升树,SVM,K-Means)。
  • 培训(培训和评估模型)
  • 评估模型
  • 部署和运行(使用训练好的模型)

使用 ML.NET 框架的一个显著优势是,它允许用户快速试验不同的学习算法,更改特征集、训练和测试数据集的大小,以获得针对其问题的最佳结果。实验避免了一个常见的问题,即团队花费大量时间收集不必要的数据,并产生性能不佳的模型。

学习管道

ML。NET 将用于数据准备和模型定型的转换合并到一个管道中,然后将这些转换应用于定型数据和用于在模型中进行预测的输入数据。

讨论 ML 的时候。NET 中,重要的是要认识到使用:

  • 转换器—这些转换器转换和处理数据,并产生数据作为输出。
  • 评估者——获取数据并提供一个转换器或模型,例如在训练时
  • 预测-这使用单行或一组要素并预测结果。

我们将在简单的回归样本中看到这些是如何发挥作用的。

房价样本

了解功能如何适应数据准备的典型工作流,使用测试数据集和使用模型来训练模型和评估适合度。我研究了如何实现一个简单的回归应用程序,在给定大约 800 套房屋销售的一组简单特性的情况下,预测房屋的销售价格。在示例中,我们将看一看多元线性回归的监督学习问题。监督学习任务用于从一组特征中预测标签的值。在这种情况下,我们希望使用一个或多个特征来预测房屋的销售价格(标签)。

重点是让一个小样本启动并运行,然后可以用它来试验特性和训练算法的选择。你可以在这里找到这篇文章的代码

我们将使用一组销售数据来训练该模型,以便在给定一组超过 800 套房屋销售的特征的情况下预测房屋的销售价格。虽然样本数据具有各种各样的特征,但是开发有用系统的一个关键方面是理解所用特征的选择会影响模型。

开始之前

你可以找到一个 10 分钟教程,它将带你完成安装和先决条件——或者只使用以下步骤。

您需要安装 Visual Studio 16.6 或更高版本。安装了 NET Core 你就可以到达那里这里

开始建设。你只需要下载并安装就可以了。NET SDK

通过在命令提示符下运行以下命令来安装 ML.Net 软件包

dotnet add package Microsoft.ML --version 1.4.0

数据类

我们的第一项工作是定义一个数据类,在加载我们的。房屋数据的 csv 文件。需要注意的重要部分是[LoadColumn()]属性;这些允许我们将字段与输入中的不同列相关联。它为我们提供了一种简单的方法来适应我们可以处理的数据集的变化。当用户想要预测房子的价格时,他们使用数据类来给出特征。请注意,在定型模型时,我们不需要使用类中的所有字段。

培训和保存模型

CreateHousePriceModelUsingPipeline(…)方法在创建用于预测房价的模型时做了大部分有趣的工作。

代码片段显示了您可以如何:

  • 从. csv 文件中读取数据
  • 选择训练算法
  • 选择训练时要使用的功能
  • 处理字符串特征
  • 创建一个管道来处理数据和训练模型

在这些类型的问题中,您通常需要归一化入站数据,考虑特征房间和卧室,房间的值范围通常大于卧室,我们归一化它们以对拟合具有相同的影响,并加快(取决于教练)拟合时间。我们使用的训练器会自动对特性进行规范化,尽管如果你自己需要的话,框架会提供工具来支持规范化。

类似地,根据所用特征的数量(如果模型过度拟合),我们将正则化应用于训练器——这基本上保留了所有特征,但为每个特征参数添加了权重以减少影响。在示例中,培训师将处理规范化;或者,您可以在创建培训师时进行规范化调整。

估价

训练后,我们需要使用测试数据评估我们的模型,这将表明预测结果和实际结果之间的误差大小。减少误差将是对相对小的数据集进行迭代过程的一部分,以确定最佳的特征组合。ML 支持不同的方法。NET 我们使用交叉验证来估计从一次运行到另一次运行的模型质量的方差,这也消除了提取单独的测试集进行评估的需要。我们显示质量度量来评估和获得模型的准确性度量

我们将看看 R 平方指标:

  • R 平方 —提供拟合优度的度量,因为线性回归模型将在 0 - > 1 之间,越接近 1 越好。

您可以在这里找到可用的指标的描述

“按原样”运行代码会产生 0.77 的 R 平方,我们希望对此进行改进。第一步是开始试验特性选择,也许从减少特性列表开始之后,我们有了更清晰的理解,然后考虑获取更广泛的数据。

保存模型以备后用

存储用于预测房价的模型很简单

加载和预测房屋销售价格

一旦您调整了功能并评估了不同的培训,您就可以使用该模型来预测销售价格。我认为这是 ML.NET 框架的闪光点,因为我们可以在。Net 来支持使用该模型的不同方式。

对于这种类型的 ML 应用程序,典型的用途是创建一个简单的 REST 服务,在部署到 Windows Azure 的 docker 容器中运行。一个用 javascript 编写的 web 应用程序使用该服务,让人们快速了解房子应该卖多少钱。

使用。Net Core 我们可以在不同的硬件平台上运行后端,Visual Studio 2019、2017 使健壮服务的创建、部署和管理变得快速而简单。

在 GitHub 中找到代码:

GitHub

原载于 2019 年 2 月 21 日junwin . github . io

预测服务员的小费

原文:https://towardsdatascience.com/predicting-a-waiters-tips-1990342a0d02?source=collection_archive---------20-----------------------

fast.ai《程序员实用深度学习》第四课

在 fast.ai 的“程序员实用深度学习”第四课中,我们发现了如何使用深度学习和协同过滤来解决表格数据问题。正如我在 fast.ai 讲座上经常做的那样,我将讲座从头到尾看了一遍,然后在浏览笔记本时又看了一遍,必要时暂停一下。当我完成后,我想确保我可以用不同的数据集复制这个过程,我选择了乔·杨的 Kaggle 数据集“ A Waiter's Tips ”。有了这个数据集,我们想建立一个模型来预测餐馆服务员的小费金额。

我从下载数据集并解压它开始,检查丢失的值(没有!),然后将 tips.csv 文件上传到工作目录中的“data”文件夹。

from fastai.tabular import *之后,我需要编辑笔记本以使路径指向正确的位置,因为笔记本默认路径指向讲座中使用的数据集。我的数据在‘data’文件夹中,所以我相应地设置了路径,并告诉它将数据存储在熊猫DataFrame中:

path = Path(‘data’)
df = pd.read_csv(path/’tips.csv’)

然后,我设置列名、因变量和预处理函数。因为我想根据其他因素来预测小费金额,所以我将tip设置为因变量,并且因为可以从简短的可能性列表中选择一周的sexsmokerday,所以我将这些设置为“分类”变量。total_billsize只是数字,所以它们是“连续”变量。我必须思考一分钟关于一天的time,虽然:“时间”感觉像是一个连续的概念,但看着数据,我看到“时间”被定义为“午餐”或“晚餐”那就绝对的。

笔记本附带了预处理函数FillMissingCategorifyNormalize,我暂时保留它们。

dep_var = 'tip'
cat_names = ['sex', 'smoker', 'day', 'time']
cont_names = ['total_bill', 'size']
procs = [FillMissing, Categorify, Normalize]

(这位加州女孩惊讶地看到“吸烟者”被列为餐馆顾客的一个可能属性)

接下来,我需要为我的测试集选择一个大小。我们通常为测试集留出 20%的数据,因此由于我的数据集有 244 个元素,我将测试集设置为使用从 196 到 244 的索引范围。

test = TabularList.from_df(df.iloc[196:244].copy(), path=path, cat_names=cat_names, cont_names=cont_names)

然后是时候使用 fastai 库的 Datablock API 来创建我的databunch:

data = (TabularList.from_df(df, path=path, cat_names=cat_names,    cont_names=cont_names, procs=procs)
 .split_by_idx(list(range(196,244)))
 .label_from_df(cols=dep_var)
 .add_test(test)
 .databunch())

我检查出一批数据:

data.show_batch(rows=10)

total_billsize经常有负值?我不认为餐馆付钱给任何顾客在那里吃饭,所以这一定是调用Normalize预处理的结果。我查看了文档,没有发现Normalize实际上做了什么,所以我返回到详细的课堂笔记,发现了这个:

"Normalize:提前做一个归一化,就是取连续变量,减去它们的均值,除以它们的标准差。"好吧,那么这就和统计学中的 z 分数一样,所以低于平均值的值为负是有道理的。

我声明我的学习器,告诉 API 它将是一个表格学习器(例如,与卷积神经网络学习器相反),并保留讲座中使用的参数:

learn = tabular_learner(data, layers=[200,100], metrics=accuracy)

然后我运行learn.fit,得到一个错误:

看起来accuracy函数中的“参数#2”是targs,但是对我来说不清楚我需要如何修复它。我用谷歌搜索了这个错误,发现了一个关于它的帖子。所以没错,targs是“参数#2”,我需要自己做accuracy函数。当我创建一个学习者时,调用了accuracy函数,所以我在其上创建了一个新的单元格,并定义了一个新的精度函数,称为accuracy_long,它与原来的accuracy函数相同,除了第四行的.long()之外:

def accuracy_1ong(input:Tensor, targs:Tensor)->Rank0Tensor:
    n = targs.shape[0]
    input = input.argmax(dim=-1).view(n,-1)
    targs = targs.view(n,-1).long()
    return (input==targs).float().mean()

…并按顺序运行两个单元。如果我们没有得到另一个错误,那就没什么意思了,对吗?

我就像“我也定义了它!看到了吗,就在上面?”

几个小时的谷歌搜索和思考,我甚至不能定义一个函数,以后肯定会放弃编码 仔细看看我在上面输入的内容。accuracy_long中的“l”是一个“1”。酷,酷。

我冷冷地盯着电脑看了很久,然后继续重新创建我的学习者并运行learn.fit(1, 1e-2)。看看我的准确率!这与我希望自己的准确率有很大关系,因为它们是完全相反的。🙄

由于这个数据集是预先清理过的,上面有一颗樱桃,并且被设计用来准确预测我试图用它来预测的东西,看起来我的准确率应该很高,而不是非常糟糕。所以我在想,是不是不小心把某个地方的一些逻辑颠倒了,把代码梳理了一遍。不过,我什么也没找到。

嗯。我觉得不可能是这个,但是我会尽量不把数据归一化?不,没有改善。

与第 2 课笔记本不同,第 4 课笔记本并不通过拟合几个时期的一个周期来开始学习,也不运行学习率查找器或混淆矩阵。我自己把这些放进去。

我创建并运行五个新单元:learn.fit_one_cycle(4)learn.save(‘stage-1’)learn.unfreeze()learn.lr_find()learn.recorder.plot()。这是学习率图表:

从图表来看,改变学习率似乎对我没有帮助。

我已经没有主意了,所以我求助于 fast.ai 论坛。一名成员建议尝试第 6 课中使用的 RMSE(均方根误差)技术,所以我现在暂停一下,在第 6 课结束后再试一次。

查看 GitHub 上的代码!

关于此主题的其他帖子:

第一课:fast . ai 入门

第二课:对孕检结果进行分类

第二课(续):深度学习能比鸽子表现更好吗?

第三课:一万种行不通的方法

第五课:但是泡菜去哪里了?

第六课:每个人都想成为一只猫

我是加州大学东湾分校的数学讲师,也是一名有抱负的数据科学家。在 LinkedIn 上和我联系,或者在 Twitter 上和我打招呼。

Python 中的数据清理:Airbnb 数据清理示例

原文:https://towardsdatascience.com/predicting-airbnb-prices-with-deep-learning-part-1-how-to-clean-up-airbnb-data-a5d58e299f6c?source=collection_archive---------14-----------------------

Python 中如何处理杂乱的位置和文本数据

Source: airbnbeazy.com

项目背景

Airbnb 让我着迷。我之前在一家 Airbnb 物业管理公司工作了一年半,担任负责定价、收入和分析的团队主管。我发现特别有趣的一件事是如何计算出网站上的一个列表的价格。尽管“这是曼彻斯特的一套两居室”会让你走得相当远,但实际上有很多因素可以影响房源的价格。

作为利用深度学习预测 Airbnb 价格的更大项目的一部分,我发现自己被抛回了房地产数据的黑暗世界。地理空间数据可能非常复杂和混乱,用户输入的地理空间数据更是如此。这篇文章将解释我是如何为我的项目寻找和准备数据的,包括一些关于处理英国地理数据(它出奇的复杂)和从长文本字符串中提取相关信息的想法。

数据集

这个项目使用的数据集来自Insideairbnb.com,这是一个反 Airbnb 的游说团体,从全球多个城市搜集 Airbnb 房源、评论和日历数据。该数据集是在 2019 年 4 月 9 日抓取的,包含了当天该网站上所有伦敦 Airbnb 房源的信息(约 80,000)。

清理和准备数据

关于数据清理的全部令人兴奋的细节(披露:兴奋程度可能有所不同),请随时查看我的 GitHub repo 。为了简洁起见,我将讨论可能感兴趣的数据预处理的三个特定领域。

未包含的功能(但我希望包含)

原始数据集包含 106 个要素,包括许多不同描述字段的文本列,您可以在 Airbnb 列表中填写这些字段。由于时间限制,我没有在这个模型中做任何自然语言处理(NLP ),所以所有这些功能都被删除了。然而,该模型未来发展的一个有趣途径是用 NLP 来增强它——可能用于情感分析,或寻找关键词,或某种奇特的 Word2Vec 类型的情况,寻找相似的列表描述,并使用它来帮助根据相似的列表猜测价格。

未来工作的另一个潜在方向可能包括审查。Insideairbnb.com 还抓取评论,这些评论可以与带有列表 id 的列表匹配。虽然大多数客人倾向于给大多数列表高评级,但更多细微的评级可能来自评论本身。

处理伦敦的地理信息(TLDR:伦敦的地图没有考虑到数据科学家)

英国的邮政编码复杂而混乱。它们可以有不同的长度,由字母和数字以不同的顺序组成。邮政编码的前半部分称为输出码或邮政编码区,指的是如下所示的区域:

London postcode districts. Source: https://en.wikipedia.org/wiki/London_postal_district

让事情变得更复杂的是,伦敦的主要地理分区是 32 个区加上伦敦金融城(由于 12 世纪英国历史的一些怪癖,严格来说是一个公司而不是一个区),这与邮政编码区不一致(因为这太容易了,对吗?):

London postcode districts (red), layered over London boroughs (black lines). Source: https://en.wikipedia.org/wiki/London_postal_district

也没有什么简单的方法可以在更小的粒度上对伦敦地区进行分类。事实上,对于什么是“内伦敦”,人们甚至还没有达成一致:

What even is London? Source: https://en.wikipedia.org/wiki/Inner_London

更糟糕的是,Airbnb 允许主机在自由文本输入框中输入邮政编码,排除了邮政编码部分的任何简单分离,并允许主机写各种无意义的内容(我最喜欢的只是“不”这个词)。

最后,在抛弃了一堆关于邮政编码的正则表达式实验之后,我决定使用行政区作为地理单位。位置对于 Airbnb 房源来说非常重要,所以我对不得不使用 borough 并不十分满意。它不是在一个特别精细的层次上,也不总是能很好地表达一处房产是在伦敦市中心还是在偏远地区——这对价格有很大的影响。例如,著名的夏德摩天大楼在南华克,但杜尔威治也是如此,地铁甚至没有到达那里(免责声明:杜尔威治其实很可爱,但可能不太为伦敦的游客所知)。

Both in Southwark, but possibly with different Airbnb prices? Left: the Shard (source: https://www.visitbritainshop.com/world/the-view-from-the-shard/). Right: Dulwich high street (source: https://de.wikipedia.org/wiki/Dulwich_(London)).

为了获得更精细的结果,我也尝试使用纬度和经度而不是行政区——但正如未来的博客文章所显示的,这并不完全成功。

便利设施(如此多的便利设施)

在 Insiderairbnb.com 的数据集中,设施被存储为一大块文本,下面是一个例子:

为了弄清楚各种选项是什么以及哪些列表包含这些选项,我首先制作了一个包含所有便利设施值的巨大字符串,稍微整理了一下,用逗号分隔出各个便利设施,并创建了一组结果列表(幸运的是,数据集足够小,允许这样做,但我需要一种更有效的方法来处理更大的数据集):

这里列出了所有可能拥有的便利设施:

 '24-hour check-in',
 'Accessible-height bed',
 'Accessible-height toilet',
 'Air conditioning',
 'Air purifier',
 'Alfresco bathtub',
 'Amazon Echo',
 'Apple TV',
 'BBQ grill',
 'Baby bath',
 'Baby monitor',
 'Babysitter recommendations',
 'Balcony',
 'Bath towel',
 'Bathroom essentials',
 'Bathtub',
 'Bathtub with bath chair',
 'Beach essentials',
 'Beach view',
 'Beachfront',
 'Bed linens',
 'Bedroom comforts',
 'Bidet',
 'Body soap',
 'Breakfast',
 'Breakfast bar',
 'Breakfast table',
 'Building staff',
 'Buzzer/wireless intercom',
 'Cable TV',
 'Carbon monoxide detector',
 'Cat(s)',
 'Ceiling fan',
 'Ceiling hoist',
 'Central air conditioning',
 'Changing table',
 "Chef's kitchen",
 'Children’s books and toys',
 'Children’s dinnerware',
 'Cleaning before checkout',
 'Coffee maker',
 'Convection oven',
 'Cooking basics',
 'Crib',
 'DVD player',
 'Day bed',
 'Dining area',
 'Disabled parking spot',
 'Dishes and silverware',
 'Dishwasher',
 'Dog(s)',
 'Doorman',
 'Double oven',
 'Dryer',
 'EV charger',
 'Electric profiling bed',
 'Elevator',
 'En suite bathroom',
 'Espresso machine',
 'Essentials',
 'Ethernet connection',
 'Exercise equipment',
 'Extra pillows and blankets',
 'Family/kid friendly',
 'Fax machine',
 'Fire extinguisher',
 'Fire pit',
 'Fireplace guards',
 'Firm mattress',
 'First aid kit',
 'Fixed grab bars for shower',
 'Fixed grab bars for toilet',
 'Flat path to front door',
 'Formal dining area',
 'Free parking on premises',
 'Free street parking',
 'Full kitchen',
 'Game console',
 'Garden or backyard',
 'Gas oven',
 'Ground floor access',
 'Gym',
 'HBO GO',
 'Hair dryer',
 'Hammock',
 'Handheld shower head',
 'Hangers',
 'Heat lamps',
 'Heated floors',
 'Heated towel rack',
 'Heating',
 'High chair',
 'High-resolution computer monitor',
 'Host greets you',
 'Hot tub',
 'Hot water',
 'Hot water kettle',
 'Indoor fireplace',
 'Internet',
 'Iron',
 'Ironing Board',
 'Jetted tub',
 'Keypad',
 'Kitchen',
 'Kitchenette',
 'Lake access',
 'Laptop friendly workspace',
 'Lock on bedroom door',
 'Lockbox',
 'Long term stays allowed',
 'Luggage dropoff allowed',
 'Memory foam mattress',
 'Microwave',
 'Mini fridge',
 'Mobile hoist',
 'Mountain view',
 'Mudroom',
 'Murphy bed',
 'Netflix',
 'Office',
 'Other',
 'Other pet(s)',
 'Outdoor kitchen',
 'Outdoor parking',
 'Outdoor seating',
 'Outlet covers',
 'Oven',
 'Pack ’n Play/travel crib',
 'Paid parking off premises',
 'Paid parking on premises',
 'Patio or balcony',
 'Pets allowed',
 'Pets live on this property',
 'Pillow-top mattress',
 'Pocket wifi',
 'Pool',
 'Pool cover',
 'Pool with pool hoist',
 'Printer',
 'Private bathroom',
 'Private entrance',
 'Private gym',
 'Private hot tub',
 'Private living room',
 'Private pool',
 'Projector and screen',
 'Propane barbeque',
 'Rain shower',
 'Refrigerator',
 'Roll-in shower',
 'Room-darkening shades',
 'Safe',
 'Safety card',
 'Sauna',
 'Security system',
 'Self check-in',
 'Shampoo',
 'Shared gym',
 'Shared hot tub',
 'Shared pool',
 'Shower chair',
 'Single level home',
 'Ski-in/Ski-out',
 'Smart TV',
 'Smart lock',
 'Smoke detector',
 'Smoking allowed',
 'Soaking tub',
 'Sound system',
 'Stair gates',
 'Stand alone steam shower',
 'Standing valet',
 'Steam oven',
 'Step-free access',
 'Stove',
 'Suitable for events',
 'Sun loungers',
 'TV',
 'Table corner guards',
 'Tennis court',
 'Terrace',
 'Toilet paper',
 'Touchless faucets',
 'Walk-in shower',
 'Warming drawer',
 'Washer',
 'Washer / Dryer',
 'Waterfront',
 'Well-lit path to entrance',
 'Wheelchair accessible',
 'Wide clearance to bed',
 'Wide clearance to shower',
 'Wide doorway',
 'Wide entryway',
 'Wide hallway clearance',
 'Wifi',
 'Window guards',
 'Wine cooler',
 'toilet',

在上面的列表中,有些设施比其他设施更重要(例如,阳台比传真机更容易涨价),有些设施可能相当不常见(例如,“电动仿形床”)。基于之前的行业经验,以及对顾客认为哪些设施更重要的进一步研究,我们选择了一些更重要的设施。然后根据数据的稀疏程度从最终模型中选择这些数据。例如,如果事实证明几乎所有的房产都有/没有特定的舒适设施,那么这个特性在区分房源或解释价格差异时就没什么用了。

关于这一点的复杂代码可以在 GitHub 上找到,但这是最后一部分,我删除了超过 90%的列表中有或没有特定便利设施的列:

这些是我最终保留下来的便利设施:

  • 阳台
  • 被单和枕套
  • 早餐
  • 电视
  • 咖啡机
  • 基本烹饪设备
  • 白色家电(特别是洗衣机、烘干机和/或洗碗机)
  • 儿童友好型
  • 停车
  • 户外空间
  • 受到主人的欢迎
  • 互联网
  • 允许长期停留
  • 允许携带宠物
  • 私人入口
  • 安全或安保系统
  • 自助入住

摘要

在这些(和许多其他)清理和预处理步骤之后,Airbnb 处于合适的形式,可以开始探索和建模,你可以在我的下一篇关于数据探索的文章和我写的另一篇关于建立预测模型的文章中了解更多信息。

如果你觉得这篇文章有趣或有帮助,请通过鼓掌和/或评论的方式让我知道,你可以关注我,以便在未来的文章中得到通知。感谢阅读!

探索伦敦 Airbnb 价格:哪些因素影响价格?

原文:https://towardsdatascience.com/predicting-airbnb-prices-with-deep-learning-part-2-how-to-improve-your-nightly-price-50ea8bc2bd29?source=collection_archive---------11-----------------------

分析 Airbnb 数据有助于提高你的房源收入

Source: plumguide.com

项目目标和背景

Airbnb 是一个房屋共享平台,允许房主和租户(房东)将他们的房产(房源)放在网上,这样客人就可以付费入住。房东为他们的房源设定自己的价格,尽管 Airbnb 和其他网站提供一些一般性的指导,但目前没有免费和准确的服务来帮助房东使用广泛的数据点为他们的房产定价。

在 Airbnb 上获得正确的定价非常重要,尤其是在伦敦这样竞争激烈的大城市。我最近进行了一个项目,利用机器学习和深度学习来预测伦敦 Airbnb 房产的价格。我已经在之前的一篇文章中探索了 Airbnb 数据的准备和清理,在未来的一篇文章中,我将和 T2 一起探索建模。但这篇文章是关于探索列表数据,提取有趣和有用的见解,以帮助主机最大化他们的收入。

额外背景:我之前在一家 Airbnb 物业管理公司工作了一年半,担任负责定价、收入和分析的团队主管。因此,在这个项目过程中所做的决定是由这个行业领域的专业知识决定的。

数据集

这个项目使用的数据集来自 Insideairbnb.com,这是一个反 Airbnb 的游说团体,从全球多个城市搜集 Airbnb 房源、评论和日历数据。该数据集是在 2019 年 4 月 9 日抓取的,包含了当天该网站上所有伦敦 Airbnb 房源的信息(约 80,000)。伦敦行政区边界的 GeoJSON 文件也从同一网站下载。

这些数据相当混乱,而且有一些局限性。主要的一点是,它只包括广告价格(有时称为“标签”价格)。标价是向潜在客人宣传的每晚总价格,而不是之前客人每晚支付的实际平均金额。主机可以将广告价格设置为任意数量,并且对 Airbnb 不太有经验的主机通常将这些价格设置为非常低(例如 0)或非常高(例如 10,000)的数量。

尽管如此,这个数据集仍然可以用作概念验证。一个更准确的版本可以使用客人支付的实际平均每晚费用的数据来构建,例如从像 AirDNA 这样的网站收集和出售更高质量的 Airbnb 数据。

探索数据以获得洞察力

位置,位置,位置

问题:伦敦哪些地区的 Airbnb 房产最多,哪些最贵?

回答:威斯敏斯特(城市西部)的 Airbnb 房产最多,其次是 Tower Hamlets(城市东部)。伦敦内城区的房源明显比外城区多。

然而,价格模式略有不同。肯辛顿和切尔西(威斯敏斯特以西)是最昂贵的地区。这是一个众所周知的生活成本高的地区,有些地方的房价是世界上最高的。虽然内伦敦通常比外伦敦更贵,但也有一些更贵的房源沿着泰晤士河分散到城市的西部(那里有一些非常美丽的地区)。

伦敦市中心的房源很少(因为这里的住宅物业相对较少),但结果是价格非常昂贵。

我是怎么发现的?我在做这个项目的时候发现了一个有趣的叫做 GeoPandas 的库。它基本上是用于地理空间数据的 Pandas,允许您将区域边界添加为矢量(或多边形或 shapefiles ),作为数据帧中的新列。如果你想看的话,产生这个的代码在我的 GitHub repo 里。

评估竞争

问题:最常见的物业和房型有哪些?

回答:大约 70%的房产是公寓。其余的是房子或更不常见的财产类型(如“床和早餐”或“蒙古包”)。

大约 55%的房源是整栋房屋(也就是说,你自己租了整栋房子)。其余的大部分都是私人房间(也就是说,你租了一间卧室,可能还有一间浴室,但房子里还有其他人)。不到 1%是合住房间(即您与业主或其他客人合住一个房间)。

随着时间的变化

问题:房东在伦敦 Airbnb 上挂牌出售房产有多久了?

回答:目前 Airbnb 上最古老的伦敦房源是在 2008 年 8 月首次在网站上列出的。从 2011 年起,上市数量开始大幅增加。然而,自 2015 年以来,新主机(目前在该网站上市的主机)数量的增长一直在下降,当时英国政府出台了一项法律,规定每年出租短期住宅超过 90 晚为非法。

高度的季节性是显而易见的,夏季是明显的高峰,人们在网上出售房产,以利用暑假期间游客数量的增加。

下面两张图片显示了加入网站的(当前在线)主机数量的分解时间序列,以及获得首次审核的(当前在线)列表数量。这是使用 statsmodels 库中的seasonal_decompose 函数完成的。它将时间序列分解为总体趋势、季节性(这在旅游业中很重要)和剩余的无法解释的残差。后者可能是由多种因素造成的。例如,2015 年加入 Airbnb 的主机数量达到高峰,这可能是对政府立法的回应,因为现有主机可能已经创建了新帐户,以便重新列出他们的属性,并绕过 90 天的限制。

Decomposed time series

越多越热闹

问题:伦敦 Airbnb 房源的入住人数分布如何,这对价格有何影响?

回答:最常见的物业设置是两个人睡在一个卧室的一张床上,带一个卫生间。不足为奇的是,容纳更多人的酒店会获得更高的夜间房价,大约 10 人后收益递减。

正如我在这个项目的建模部分发现的那样,价格的最大预测因素是一处房产能容纳的人数。

所以,可能是时候买那个沙发床了。

宾客心 Airbnb

问题:评论的分布是什么样的?

回答:人们喜欢他们的航空站。对于每个审核类别(准确性、清洁度、入住、沟通、位置、价值和总体),大多数已审核的列表都获得了该类别的 10/10 评分(或总体 95-100/100)。所以,最好确定你属于这一类。8 分或 8 分以下的评分相对较少。客人似乎对沟通、入住和准确性最为肯定。

大约四分之一的名单还没有被审查。这些大多是尚未完成入住的酒店,其中许多可能是“不活跃”的酒店,即那些虽然列在网站上,但没有任何预订日期的酒店。

您的客人会喜欢的便利设施(以及您的酒店可能讨厌的设施)

问题:哪些便利设施是常见的,哪些会提高 Airbnb 房源的价格?

答:设施可以分为四大类:

不常见,但拥有它的房产具有更高的中值价格:

  • 阳台
  • 被单和枕套
  • 咖啡机
  • 基本烹饪设备
  • 电梯
  • 儿童友好型
  • 户外空间
  • 允许长期停留
  • 私人入口
  • 安全或安保系统
  • 自助入住

大部分房产都有,有了它的房产有更高的中位数价格:

  • 电视
  • 洗衣机、烘干机和/或洗碗机

大部分楼盘都有,有和没有的楼盘价格没有大的区别:

  • 互联网

不常见,拥有它的房产价格中位数较低:

  • 早餐(可能因为这些是住宿加早餐,因此是单人间而不是整个家庭)
  • 停车(大概是因为这些不太可能是中心物业)
  • 受到主持人的问候(出人意料!)
  • 允许携带宠物

由此得出的结论是,主办者可以做一些事情来尝试提高他们的列表价格。然而,并不总是存在简单的因果关系,例如,有停车位可能会提高你的价格,但有停车位的房源平均来说更便宜,因为它们不太可能位于伦敦市中心,而且位置是比停车位更重要的因素。

以下是一些重要提示:

  • 确保你拥有竞争对手也拥有的必需品,例如互联网、电视和白色家电(洗衣机、烘干机和/或洗碗机)。
  • 如果可以的话,增加一些额外的东西,让你的房产脱颖而出,获得更高的价格。例如,你可以买一台咖啡机,允许自助入住和长期入住,让你的酒店对儿童友好。
  • 不要养宠物,因为宠物会给你的酒店带来额外的损耗,这可能会损害酒店的财务表现(更不用说会吓跑那些可能喜欢无烟酒店的客人)。

不值得额外的努力

问题:当超级主持人值得吗?而且值得被验证吗?(经常出现的两个问题)

两个问题的答案都是:不尽然。

大约 15%的主机是超级主机。这需要满足四个标准:

  1. 每年至少接待 10 次住宿
  2. 快速回应客人,保持 90%或更高的回应率
  3. 至少有 80%的五星评价
  4. 不要取消已确认的预订

然而,这似乎并没有提高他们的 Airbnb 房源每晚的中值价格,因此它是否值得是有争议的(从纯粹的财务角度来看)。

大约 35%的主机经过验证,例如通过提供 ID 和验证您的电话号码和电子邮件地址。然而,有趣的是,尽管人们可能期望由验证过的主机所要求的额外信任会导致更高的夜间价格,但是在具有验证过的和未验证过的主机的列表的中间夜间价格之间没有可辨别的差异。

然而,除了经济原因之外,出于其他原因(比如善良的人),这些可能仍然是值得做的事情。

摘要

上面的探索性分析强调了一些有趣的趋势和模式,以及一些可以提高 Airbnb 房源价格的因素。在的另一篇文章中,我将详细介绍我如何使用这些数据建立一个模型来预测 Airbnb 的价格。我希望你至少发现了一些有用的(如果你是 Airbnb 的主人)和/或有趣的(如果你像我一样是个数据迷)!

如果你觉得这篇文章有趣或有帮助,请通过鼓掌和/或评论的方式让我知道,你可以关注我,以便在未来的文章中得到通知。感谢阅读!

用机器学习和深度学习预测 Airbnb 价格

原文:https://towardsdatascience.com/predicting-airbnb-prices-with-machine-learning-and-deep-learning-f46d44afb8a6?source=collection_archive---------11-----------------------

XGBoost 实验和调整神经网络

Source: plumguide.com

项目目标和背景

Airbnb 是一个房屋共享平台,允许房主和租户(房东)将他们的房产(房源)放在网上,这样客人就可以付费入住。主办方应该为他们的列表设定自己的价格。虽然 Airbnb 和其他网站提供了一些一般性的指导,但目前还没有免费和准确的服务来帮助房东利用广泛的数据点为他们的房产定价。

付费的第三方定价软件是可用的,但通常你需要输入你自己的预期平均每夜价格(“底价”),算法将根据一周中的某一天、季节性、日期的远近以及其他因素,围绕该底价改变每天的价格。

Airbnb 的定价很重要,尤其是在伦敦这样的大城市,那里竞争激烈,即使价格上的微小差异也会造成很大差异。这也是一件很难做到正确的事情——价格太高,没有人会预订。价格太低,你会错过很多潜在的收入。

这个项目旨在解决这个问题,通过使用机器学习和深度学习来预测伦敦房产的基础价格。我在之前的帖子中已经探索了 Airbnb 数据的准备和清理,并进行了一些探索性数据分析。这篇文章是关于创建模型来预测 Airbnb 价格的。

数据集

这个项目使用的数据集来自 Insideairbnb.com,这是一个反 Airbnb 的游说团体,从全球多个城市搜集 Airbnb 房源、评论和日历数据。该数据集是在 2019 年 4 月 9 日抓取的,包含了当天该网站上所有伦敦 Airbnb 房源的信息(约 80,000)。

这些数据相当混乱,而且有一些局限性。主要的一点是,它只包括广告价格(有时称为“标签”价格)。标价是向潜在客人宣传的每晚总价格,而不是之前客人每晚支付的实际平均金额。主人可以将广告价格设置为任意值。

尽管如此,这个数据集仍然可以用作概念验证。一个更准确的版本可以使用客人支付的实际平均每晚费用的数据来构建,例如从像 AirDNA 这样的网站收集和出售更高质量的 Airbnb 数据。

清理和删除共线列后,模型中的特征为:

  • 该物业容纳的人数
  • 浴室的数量
  • 物业类型(如公寓)和房间类型(如整个住宅)
  • 财产的位置(在自治市的层面上(在之前的帖子中进一步讨论),或者在纬度和经度的层面上的一个模型中——在下面进一步讨论)
  • 保证金、清洁费和额外人员费
  • 最短和最长住宿时间
  • 未来 90 天内可预订的天数
  • 评论总数
  • 审查每个类别的评级(准确性、清洁度、入住、沟通、位置、价值和总体)
  • 自第一次和最近一次审核以来的时间
  • 取消政策的类型
  • 酒店是否可即时预订
  • 是否有各种各样的便利设施(在之前的帖子中有更深入的讨论,但包括电视、咖啡机、阳台、互联网和停车场等项目,无论该物业是否适合儿童入住,是否允许自助入住或允许宠物入住,等等)
  • 主机响应时间和速率
  • 主机是否是超级主机(质量的标志,需要满足各种条件)或其身份是否经过验证(例如,通过验证政府 ID、电话号码和电子邮件地址)
  • 主持人总共负责多少个列表
  • 主持人在 Airbnb 上挂牌多少天了

构建机器学习模型

出于篇幅的考虑,我将在这里跳过数据准备阶段,但是如果你感兴趣,可以在我的 GitHub repo 中找到这个项目的所有代码。总之,在清理数据、检查多重共线性和移除共线特征后,除非另有说明,否则使用 sklearn 的StandardScaler()对数据进行标准化。使用pd.get_dummies()对分类特征进行一次性编码。以 0.2 的测试规模执行了训练测试分割。

虽然我热衷于实验深度学习模型进行价格预测,但我首先构建了一个香草(未调优)XGBoost 机器学习模型(具体来说就是xgb.XGBRegressor())。这是为了提供一个基线水平的准确性,也是为了衡量特征的重要性(众所周知,一旦你进入深度学习领域,这是很难的)。XGBoost 可能会使用机器学习模型提供最佳的可实现精度(除了超参数调整带来的可能的小精度增加),因为它具有卓越的性能和在 Kaggle 比赛中观察到的普遍出色性。

因为这是一项回归任务,所以选择的评估指标是均方误差(MSE)。我还对精确度感兴趣,所以我也查看了每个生产模型的 r 平方值。

下面是我用来拟合和评估模型的代码:

结果:

Training MSE: 0.1576
Validation MSE: 0.159

Training r2: 0.7321
Validation r2: 0.7274

对于一个未经调整的模型来说还不错。现在谈谈特性的重要性:

十大最重要的功能是:

  • 该房产可容纳多少人
  • 清洁费
  • 主机有多少其他列表(以及它们是否是多列表主机)
  • 未来 90 天中有多少天可以预订
  • 额外每人的费用
  • 评论的数量
  • 浴室的数量
  • 保证金
  • 如果房产在威斯敏斯特
  • 最短住宿时间

毫不奇怪,最重要的特征是酒店能容纳多少人,因为这是你首先用来搜索酒店的主要因素之一。与位置和评论相关的功能位列前十也不足为奇。

或许更令人惊讶的是第三个最重要的功能与主人在 Airbnb 上管理的其他房源数量有关,而不是房源本身。然而,这并不意味着管理更多物业的主机将导致房源获得更高的价格(尽管这确实是关系的方向)。首先,数据似乎被一些非常大的房地产管理公司扭曲了。其次,这种关系是与广告设定的价格,而不是实际达到的价格,这表明,如果有什么更有经验的主人往往会设定(而不一定达到)更高的价格。第三,我们不能必然地暗示因果关系——这可能是更有经验的多清单主机倾向于采取更昂贵的财产(这确实是一些情况,例如一个罚款停留)。

同样值得注意的是,其他三种费用类型——清洁费、保安费和额外人员费——都在十大费用清单中。很可能当主人为每晚住宿设定较高的价格时,他们也可能将其他价格设定得较高,反之亦然。

构建深度学习模型

接下来,我决定用神经网络(NN)进行实验,看看我是否能提高 XGBoost 模型的分数。我从一个具有密集连接层的相对较浅的三层 NN 开始,对隐藏层使用 relu 激活函数,对输出层使用线性激活函数(因为它正用于回归任务)。损失函数是均方误差(同样,因为这是用于回归)。

这是我的代码:

这是总结和想象:

为了在评估多种方法时节省时间,我构建了一个方便的函数来打印测试集和训练集的 MSE 和 r 平方结果,并为测试集和训练集生成每个时期的损失线图以及预测值与实际值的散点图:

结果如下:

Training MSE: 0.0331
Validation MSE: 0.2163

Training r2: 0.9438
Validation r2: 0.6292

与 XGBoost 模型相比,神经网络的性能更差。过度拟合似乎也是一个问题,从训练和测试 MSE 和 r 平方结果之间的差异,以及线图中训练和测试损失之间的差异,以及散点图中值更接近线的事实可以看出。

然后,我迭代了该模型的各种其他版本,以尝试消除过度拟合并提高准确性。尽管精确度有所不同,但其他版本都消除了过度拟合。我尝试的调整是:

  • 添加第四层和第五层——第四层提高了精确度,但第五层没有帮助。
  • 使用 L1 正则化——这被证明是对精确度的最大提升。
  • 在 30%和 50%的辍学率下使用辍学正规化——50%被证明是一个可怕的想法,并显著增加了 MSE。30%表现更好,但不如 L1 正则化。
  • 使用随机梯度下降(SGD)优化器而不是 Adam——这表现稍差。
  • 改变批量大小——这没有太大的区别。
  • 为更多的时期训练——这对一些模型有一点帮助,但大多数模型无论如何都相当快地最小化了损失函数。
  • 删除大部分评论评级列——一个类别中的高评论评级与其他类别高度相关,因此我尝试删除除总体评级之外的所有评论评级。然后,我使用这个新的截断数据集来训练之前性能最高的模型架构(使用 L1 正则化和 Adam 优化器)。其性能基本相同,但少了 18 列,因此在选择将哪个模型投入生产时,它将是首选模型,因为它需要的数据更少,计算成本也更低。
  • 使用纬度和经度而不是区——同样,这个调整后的数据集与我迄今为止提出的最佳模型架构一起使用。这个表现稍差。
  • MinMaxScaler()代替StandardScaler()。这也表现的略差。

最后,最好的神经网络是具有 L1 正则化和 Adam 优化器的四层模型,去掉了额外的审查列:

My best performing neural network architecture

结果:

Training MSE: 0.1708
Validation MSE: 0.1689

Training r2: 0.7096
Validation r2: 0.7105

I still haven’t gotten to the bottom of why the NN is unable to predict values for the log-transformed price lower than about 3.1, but I’m pretty sure it’s something to do with the use of regularization

然而,即使是这种模型架构的性能也不如 XGBoost 模型。总的来说,XGBoost 模型是首选模型,因为它的性能比最好的神经网络稍好,并且计算成本更低。通过超参数调整,它可能会得到进一步的改进。

结论

这是深度学习对于预测来说根本不必要的情况之一,机器学习模型表现得一样好。

然而,即使在表现最好的模型中,该模型也只能解释 73%的价格变化。剩下的 27%可能由数据中不存在的特征组成。这种无法解释的差异很大一部分可能是由于列表照片的变化造成的。Airbnb 上的房产照片在鼓励客人预订方面非常重要,因此预计也会对价格产生重大影响——更好的照片(主要是质量更好的房产和家具,但也包括质量更好的摄影)等于更高的价格。

未来工作的潜在方向

  • 找到将图像质量纳入模型的方法,例如通过使用卷积神经网络的输出来评估图像质量,作为定价模型的输入。
  • 使用质量更好/更准确的数据,包括每晚支付的实际平均价格。
  • 包括更广泛的地理区域,例如英国的其他地方或世界上的其他主要城市。
  • 用列表描述和/或评论的自然语言处理(NLP)来增强模型,例如用于情感分析或寻找关键词。
  • 除了预测基价之外,还可以创建一个序列模型,使用季节性和入住率数据来计算每日价格,这将允许创建实际定价软件。
  • 通过删除当时不为人知的功能,例如其他费用、可用性和评论,更具体地针对新房源定制模型,以帮助主办方设定新房产的价格。

我希望你觉得这篇文章有趣和/或有帮助。感谢阅读!

利用机器学习和位置数据预测 Airbnb 价格

原文:https://towardsdatascience.com/predicting-airbnb-prices-with-machine-learning-and-location-data-5c1e033d0a5a?source=collection_archive---------2-----------------------

使用苏格兰爱丁堡市数据的案例研究

关键词:Airbnb、爱丁堡、城市、数据科学、熊猫、geopandas、地理空间、foursquare、地图、matplotlib、建模、邻居、网络、numpy、foursquare API、规划、python、城市规划、数据可视化

Photo by Paul Fiedler on Unsplash

作为 IBM 数据科学专业证书 的一部分,我们可以尝试一下我们自己的数据科学顶点,在那里我们可以体验像数据科学家一样解决问题和回答问题的感觉。对于我的任务,我决定做另一个项目,研究 Airbnb 价格与其决定因素之间的关系。是的,有几个很酷的,像劳拉·刘易斯这里。如果没有阅读和理解她的(和她的代码),我就不可能完成我的工作,所以恭喜你!然而,由于我对交通研究很感兴趣,我添加了一点地理空间分析,通过研究位置特征作为可能的预测因素。这篇文章解释了一些项目背景、数据收集、清理和预处理、建模和快速总结。

  1. 项目背景及目标
  2. 数据
  3. 数据清理和探索
  4. 位置特征
  5. 建立模型
  6. 改进模型
  7. 结论和建议

对于完整笔记本全部代码,可以在我的 Github 上查看回购。

项目背景和目标

如果出于某种原因你还不知道,Airbnb 是一个短期房屋和公寓租赁的互联网市场。举例来说,它允许你在外出时出租(列出)你的房子一周,或者出租你的空卧室。Airbnb 房东面临的一个挑战是确定最佳的每夜租金价格。在许多地区,租房者(房东)都有很好的房源可供选择,他们可以根据价格、房间数量、房间类型等标准进行筛选。由于 Airbnb 是一个市场,主机可以收取的费用最终取决于市场价格。Airbnb 上的搜索体验是这样的:

Airbnb Edinburgh home page at https://www.airbnb.co.uk/s/Edinburgh--United-Kingdom/all?set_bev_on_new_domain=1571135536%2B1HjOBOK%2FivIgihM

虽然 Airbnb 为主机提供了一般的指导,但没有容易获得的方法来确定出租空间的最佳价格。有第三方软件可用,但价格昂贵(关于可用软件的示例,请点击此处)。

一种方法可以是找到一些与将要出租的地方相似的列表,平均列出的价格,并将我们的价格设置为计算出的平均价格。但是市场是动态的,所以我们希望经常更新价格,这种方法可能会变得繁琐。

另一个问题?我们可能会错过我们的列表相对于周围列表的竞争优势,比如它离杂货店、酒吧和其他额外服务有多近,或者甚至是我的照片相比有多棒。

因此,这个项目使用了几个列表功能来尝试和预测价格,并增加了一个基于空间的预测器:房产与某些场馆的距离。这使得该模型可以对诸如住在酒吧、酒吧或超市附近等事物进行隐性定价。

数据

Airbnb 不向公众发布任何数据,但 Airbnb 内部一个名为的独立小组 从 Airbnb 网站收集并汇编了许多城市房源的公开信息。为了这个项目,我使用了他们在 2019 年 7 月 21 日在苏格兰爱丁堡市收集的数据集。它包含了当天网站上所有爱丁堡 Airbnb 房源的信息(超过 14,000 个)。

数据有一定的局限性。最值得注意的一点是,它刮的是广告价格,而不是以前的客户支付的实际价格。更精确的数据可以在像 AirDNA 这样的网站上付费获得。

数据集中的每一行都是 Airbnb 网站中特定城市的可用租赁清单(观察)。这些列描述了每个列表的不同特征(特性)。

该项目将研究的一些更重要的功能如下:

  • 租赁可以容纳的客人数量
  • bedrooms:租金中包含的卧室数量
  • bathrooms:租金中包含的浴室数量
  • beds:租金中包含的床位数
  • price:每晚租金
  • minimum_nights:租赁期间客人可入住的最少天数
  • maximum_nights:租赁期间客人可入住的最大天数
  • number_of_reviews:之前客人留下的评论数量

为了对 Airbnb 租金价格和某些场馆附近的物业之间的空间关系进行建模,我使用了 Foursquare API 来访问该市的场馆,街道网络可通过 OpenStreepMap (OSM) 获得。

数据清理和探索

简而言之,原始数据集包含 14014 个 Airbnb 列表和 106 个功能,但我删除了一大堆功能。例如,其中一些是自由文本变量,如酒店的主机描述和所有书面评论。为了包含这些数据,我必须做一些自然语言处理,但不幸的是,我不能。所以这些功能都被删除了。此外,一些列只包含一个类别,所以它们也被删除了。大多数列表不提供体验功能,所以这也是不可能的。

一个完整的一步一步的这一节,我如何和为什么我这样做是在笔记本上的我的 github这份报告没有所有的代码。

还有一个有趣的探索性数据分析。“没有夏洛克”的发现之一是,进入市场的上市公司有明显的季节性。

Clear seasonality in the middle of the year

每年年中(夏季)左右都有一个主机加入的高峰,最低点是每年的年初和年末。8 月,在爱丁堡边缘艺术节期间,房间租金价格大幅上涨。这是一个非常受欢迎的活动,由于每年参加的人数众多,许多出租的房产将被占用。爱丁堡全年有许多其他活动,主要集中在 4 月和 12 月之间。这几个月有世界级的艺术家在表演。

在这一年的其余时间里,爱丁堡作为文化之都的地位是独一无二的,不仅仅是苏格兰,还有英国。对住宿和旅游有稳定的需求,因为爱丁堡是仅次于伦敦的商业之都。

就价格随时间的变化而言,爱丁堡 Airbnb 房源的每晚平均价格在过去 10 年中略有上升。但房地产价格的高端已经增加了很多,导致平均价格比中位数有更大的增长。2010 年的平均价格为 107.33 英镑,中位数为 115.0 英镑,而 2018 年(上一个完整数据年度)的平均价格为 104.55 英镑,中位数为 79.0 英镑。基本上,昂贵的住宿业主很快发现了使用 Airbnb 的好处(主要是更少的税收和更少的责任,但那是另一个故事了)。

Outliers have been increasing over time

位置特征

现在是酷的东西(对我来说)。寻找住宿时,靠近某些旅游区很重要。但是,知道你将有一个杂货店或超市在一个步行距离可以加分。很多租房者喜欢这样的事实,当你有了 Airbnb 而不是酒店房间时,你可以自己做饭(并节省一些)。

因此,靠近某些场所,如餐馆、咖啡馆和商店可以帮助我们预测价格。

Airbnb 的许多房源都集中在爱丁堡的老城区,这与游客的巨大吸引力相符,尤其是在一年一度的边缘节日期间。

Most Airbnb listings in Edinburgh are near the Old Town area

价格的分布也反映出最贵的房源位于老城区。

Price spatial distribution

为了获得特定场馆的信息,我使用爱丁堡街区的经纬度,通过 Foursquare 的 API 下载了每个街区的场馆列表。

纬度和经度来自 geojson 文件形式的公开的街区列表。我本来可以从原始 Airbnb 列表数据集中检索纬度和经度,但是我已经在使用这个文件,所以我决定只使用它。后来,我把它们按邻域合并。

首先,我必须加载地理数据:

map_df是一个地理数据框架。为了检索每个街区的场馆,我从地理系列中的点对象提取了纬度和经度或xy。以下代码将xy作为原始map_df中单独的 GeoDataFrame 列返回。

然后,我们创建 API 请求 url,发出 GET 请求,并提取我们附近的每个场馆的信息。下面这个非常有用的函数是在数据科学课程中提供给我们的。它遍历所有的街区来检索我们想要的数据:

现在,我们在每个邻域上运行上述函数,并创建一个名为edinburgh_venues的新数据帧。

一旦我们有了这个,我们就可以看看每个街区周围的不同场地。首先,有 182 个独特的场馆类别。不过,那里很乱。因此,我清理了检索到的数据,将这些行按邻域分组,并取每一类的平均值和出现频率。

这帮助我找到了每个街区最常见的 5 个场所:

并将其保存到熊猫数据帧中,以便查看并决定对哪些场馆感兴趣:

Five most common venues per neighbourhood (first five rows)

仔细查看数据,很明显最常见的场所是酒店酒吧杂货店/超市咖啡馆/咖啡店,其次是酒吧汽车站印度餐馆。餐馆被分成子类别。所以我把它们归为一个大的类别:餐馆。我认为附近有酒店不太可能影响价格,因为 Airbnb 的房源应该属于不同的短期租赁类别。与酒店相比,它们提供了完全不同的好处。所以我没有考虑那一类场馆。

有了这些数据,我开始测量我们一直在谈论的离场馆的距离。我们通过分析可达性来做到这一点。

可达性是到达目的地的容易程度。是运输中一个非常重要的概念,有大量的文献可用。无障碍环境塑造了人们获得机会和发挥其作为人的全部潜力的能力。这就是为什么可达性指标被大量用作空间和交通不平等的替代指标。

为了简单起见,对于这个项目,我通过测量访问场地或从 Foursquare 下载的兴趣点(POIs) 来测量可访问性。酒吧、餐馆、咖啡馆和超市/杂货店是我们的兴趣点(441 个兴趣点的数据集)。

为了帮助进行可访问性分析, Pandana 是 Python 库的首选。Pandana 是一个方便的图形库,允许将 Pandas 数据帧传递到一个网络图中,该网络图将图形级分析映射到底层 C 操作。所有这些都是说,它比传统的基于 Python 的图形要快得多。

为了开始分析,我们必须使用爱丁堡的位置数据从 OSM API 获取街道网络数据(边界框)。

运行代码后,从 OSM 下载一个有 51,553 个节点的网络。当我们将其保存到 HDF5 时,您会注意到某些节点被排除在外。这在优化网络以使其仅包含经验证的节点时非常有用。我使用了low_connectivity_nodes()方法来识别那些可能没有连接到更大网络的节点,并排除它们。

最后,我们计算 poi 的可访问性:

生成的数据框如下所示:

Network distance from the node to each of the 10 POIs

还有什么更好的方式来理解所有这些数字是关于什么的,而不是一些漂亮的可视化(没有它们,什么是地理空间分析?).首先,让我们看看到任何类型的最近 POI 的距离,因为我对场馆之间的可访问性差异不感兴趣:

我们可以看到,在一些区域,人们必须步行 500 米以上才能到达最近的便利设施,而爱丁堡的老城区平均步行距离不到 100 米。

该地图以米为单位显示了从每个网络节点到最近的餐馆、酒吧、咖啡馆、酒吧和杂货店的步行距离。但是,一个更好的无障碍指标可能是可以使用大量的便利设施。所以我没有画最近的,而是画了第五近的便利设施的可达性:

这一次更加引人注目的是,爱丁堡的老城区和市中心区域具有更好的可达性。

对于这个项目,我假设进入餐馆、商店、咖啡馆、酒吧和酒馆对 Airbnb 用户来说很重要。因此,我对所有兴趣点进行了同等加权,并使用到第五个最近场馆的距离作为可达性的复合度量。与只画出到最近地点的距离相比,这能更清楚地显示出哪些社区最适合步行。

以下代码将距离设置为第五个最近的便利设施,作为可达性的复合度量:

这就是了。已创建位置要素,然后将其添加到数据集进行建模。

构建模型

这里发生了很多数据准备,当然是在回购上。我不会涉及任何细节,但总的来说,我清理了数据,检查了多重共线性并处理了有问题的要素,对数据进行了对数转换,这有助于使price更正态分布,热编码分类要素,并使用StandardScaler()将其标准化。最后,我将数据分成训练测试。

使用了两种模型:

  • 一个空间特征价格模型 (OLS 回归),带有来自 Scikit-Learn 库的LinearRegression
  • 梯度增强方法,带有 XGBoost 库中的XGBRegressor

使用的评估指标是均方误差(损失)和 r 平方(准确性)。

我选择运行 Hedonic 模型回归,因为它们常用于房地产评估和房地产经济学。理想情况下,应进行拉格朗日乘数检验以验证因变量中是否存在空间滞后,因此空间滞后模型是估计空间 HPM 的首选模型(使用 Pysal 进行空间回归,参见本文)。然而,为了这个项目的目的,我只使用一个传统的 OLS 模型进行特征价格估计,它包括空间和位置特征,但没有考虑空间依赖性的空间滞后。

因此,第一个解释变量是列表特征(accommodatesbathrooms等),第二组基于空间和位置特征的解释变量是Score,这是我们通过 Pandana 计算的到第五个最近场馆的网络距离;和Neighbourhood归属,如果列表属于指定的邻域,则为1,否则为0

这是我为空间特征价格模型编写的代码:

和结果:

因此,这些特征解释了目标变量中大约 51%的方差。解释mean_squared_error值比 r 平方值更直观。作为方差的平方根, RMSE 可以解释为未解释方差的标准偏差,并且具有与响应变量相同单位的有用属性。但是在这种情况下,响应变量是经过对数转换的,所以解释不是以原始单位进行的,而更像是百分比偏差。RMSE 值越低表示拟合度越好。RMSE 可以很好地衡量该模型预测市场反应的准确程度。我们可以用散点图直观地看到这种关系:

Actual vs Predicted Price for Spatial Hedonic Regression Model

我还尝试使用山脊正则化来减少不太重要的特征的影响。岭正则化是缩小不太重要的特征的回归系数的过程。它采用参数α,该参数控制正则化的强度。

我通过循环几个不同的 alpha 值进行了实验,看看这如何改变结果。

这是我得到的:

是的……没发生什么。随着 alpha 值的增加,RMSE 值和 r 值几乎相同,这意味着使用岭回归模型时,模型预测并没有显著提高。

继续讨论梯度增强决策树模型。

XGBoost(eXtremeGradientBoosting)是一个针对速度和性能而设计的梯度提升决策树的实现。是一种非常流行的算法,最近一直主导着结构化数据的应用机器学习。

与 Hedonic 回归相比,该模型预计将提供最佳的可实现准确性和特征重要性的度量。确实如此。

以下是 XGBoost 模型的代码:

结果是:

使用此模型,这些特征解释了目标变量中大约 65%的方差,并且具有比 SHP 回归模型更小的 RMSE。

除了其卓越的性能,使用决策树方法(如梯度提升)的好处是,它们可以从训练好的预测模型中自动提供特征重要性的估计值。有关如何在提升决策树中计算特征重要性的更多详细信息,请查看 StackOverflow 中的答案。

XGBoost Model Feature importance

如您所见,在这个 XGBoost 回归模型中,许多特性的特性重要性为 0。

十大最重要的功能是:

  1. 是否出租整个公寓room_type_Entire home/apt
  2. 酒店可容纳多少人(accommodates)
  3. 财产的类型(property_type_Other)
  4. 浴室的数量(bathrooms
  5. 未来 90 天内有多少天可以预订(availability_90)
  6. 评论的数量(number_of_reviews)
  7. 取消政策是温和的(cancellation_policy_moderate)
  8. 主持人还有多少其他列表(host_listings_count)
  9. 最少住宿天数(minimum_nights)
  10. 最大住宿天数(maximum_nights)

预测价格的最重要的特征是租赁的是整个公寓。好像差了很多。有道理。如果报价是整个公寓/房子,要价会更高。第二是物业容纳多少人,也有道理。当你寻找一个地方时,这是你首先要找的东西。

有趣的是,定位功能并不在前十名。与前 3 个特征相比,属于某个街区和Score(可达性指标)的重要性相对较低。不过,Review_Scores_Location_9/10差点进了前十。相当有趣。

价格似乎受到先前租户对该位置的积极评价的影响,而不是该物业相对于主要兴趣点的实际可达性。

这也可能是因为爱丁堡是一个适合步行的小城市,交通便利。因此,在到达主要旅游景点和设施时,位置可能不是一个主要问题。

改进模型

XGBoost 模型生成的 feature importances 图显示,许多评论列的重要性相对较低。所以,我放弃了。然后我再次运行模型,只是没有那些列。

结果如下:

和岭回归的尝试一样,这里也没有发生什么。没有额外的回顾列,空间享乐回归和 XGBoost 的表现几乎完全相同。

结论和建议

如果我们要选择一个模型来预测价格,那么它应该是不带评论列的 XGBoost。它比两种空间享乐回归模型都表现得更好,与第一个 XGBoost 模型一样好,但计算成本更低。尽管如此,它只能预测 66%的价格变化。也就是说我们还有剩余的 34.44%无法解释。

一堆没有包括在内的不同变量可以解释剩余的方差。

例如,考虑到客户对列表的评论在确定价格中的重要性,更好地理解评论很可能会改进预测。人们可以使用情绪分析。可以为每个列表属性的每个评论分配介于-1(非常负面的情绪)和 1(非常正面的情绪)之间的分数。然后,对与该列表相关的所有评论进行平均,最终得分可以作为一个新特征包含在模型中(见此处的示例)。

我们也可以将图像质量作为一个特征。通过使用对 Airbnb 面板数据集进行深度学习和监督学习分析的差异差异,研究人员发现,拥有经过验证的照片(由 Airbnb 的摄影师拍摄)的单位平均每年会产生额外的收入(见此处)。

我仍然认为可访问性起着重要的作用,尽管这一次不是很清楚。我使用 OSM 步行网络进行可达性分析,但是使用多模态图网络会是理想的选择。这样,我就可以从整个交通网络的角度来衡量可达性,而不仅仅是从步行性的角度。也许特性的重要性在列表中会更高。

仅此而已。这是一个非常有趣的项目。

如果你设法看完了整本书,并且正在阅读这篇文章,感谢你的时间!非常感谢任何反馈。

预测飞机滑行时间

原文:https://towardsdatascience.com/predicting-aircraft-taxi-time-6c4eaa402d77?source=collection_archive---------24-----------------------

线性回归方法

Photo by Josue Isai Ramos Figueroa on Unsplash

如果你以前从一个繁忙的机场坐过飞机,很有可能你经历了一次滑行时间延迟。每个人都在飞机上,飞机甚至已经离开了登机口,然而起飞还需要相当长的时间。

背景

定义滑行时间的一个简单方法是飞机在机场表面运动的时间。对于这个项目的范围,我只关注出发滑行时间(或滑行时间),这是飞机离开航站楼登机口和实际从机场起飞之间的时间。典型地,整个过程由 ATC(空中交通管制)监督,飞行员被给予离开登机口、起飞等许可。

太好了!我们定义了什么是出租车时间,但为什么它真的很重要?

Fuel Consumption Statistics from International Civil Aviation Organization (ICAO)

快速浏览一下上面国际民航组织提供的一些信息就会发现,一架典型的波音 777 飞机仅滑行一项就要消耗 23.3 千克/分钟(或 6.15 加仑/分钟)的燃油。滑行时间平均为 15 分钟,仅仅滑行到起飞点就要消耗大约 92 加仑的燃料!

虽然不同类型的飞机会有不同的消耗率,但能够优化机场地面运动和减少滑行时间将使航空公司节省大量燃料成本,并减少二氧化碳排放,从而改善客户体验。

我的方法

因为许多航班数据是基于航空公司的内部或主观数据,所以我选择比较排名前四的航空公司在滑行时间速度方面的表现,并重点关注天气模式如何影响滑行时间。所以我的目标可以浓缩成以下几个问题:

  1. 不同航空公司的出租车时间不同吗?
  2. 机场的天气状况对滑行时间有显著影响吗?如果有,影响有多大?
  3. 是否有可能预测滑行时间以改进跑道排序?

数据

对于这个项目的范围,我选择在 2018 年的 4 个不同月份(1 月、4 月、6 月、10 月)只关注 10 个机场,以尝试解释季节性。我还将我的航空公司限制在前四名(西南航空、美国航空、达美航空、联合航空),因为它们在国内航班市场中占了很大一部分。我使用 Selenium 自动化了从运输局的航空页面下载航班统计数据的过程,并利用 BeautifulSoup 收集相应日期和机场的天气数据。

探索性数据分析

快速浏览上面的柱状图可以发现,像 JFK 机场和旧金山机场这样极其繁忙的机场往往有更长的滑行时间。这是意料之中的,因为通过这些机场的航空交通量比 STL 等机场高得多,但这些初始假设通过数据验证仍然很重要。

一个有趣的观察是,西南方往往有最快的滑行时间。这是相当令人惊讶的,因为这是一个明显的区别,从其他航空公司,尽管我的研究,这是很难确定的原因。

我只考虑了西南航空公司不飞往像 JFK 这样的机场,但它仍然是最快的航空公司。

浏览一些航空论坛发现,即使是空中交通管制员也知道西南航空公司的出租车比其他航空公司快,但不知道为什么。一个可能的原因是,西南航空公司只使用波音 737,与其他航空公司使用的一些较大的飞机相比,波音 737 较小,因此更容易驾驶。这可能是在以后的项目中值得研究的东西。

气压

一开始令我困惑的一个显著关系是平均气压和滑行时间之间的正相关关系。

尽管我进行了研究,但我无法为压力和出租车时间之间的这种正相关找到合理的解释。然而,数据中的气压计数图显示,不同的机场往往有不同的平均气压读数,如下图所示。因此,压力和滑行时间之间的这种相关性被证明是飞机从哪个机场起飞的代理。

造型

对于实际的建模,我选择坚持使用线性回归作为基线,同时在必要时使用 lasso 和 ridge 回归来提高模型性能。我使用平均绝对误差作为我的评估标准,因为我对我的预测与实际滑行时间的差距更感兴趣。结果如下:

如你所见,平均时间大约是 3.5 分钟。这意味着平均而言,模型的预测将与实际滑行时间相差约 3.5 分钟。此外,正则化没有产生巨大的影响,多项式特征的线性回归具有最佳性能,可能是因为这将允许航空公司和机场之间的交互。例如,一个多项式要素将是 JFK 德尔塔(机场航空公司),这可能是一个更好的预测德尔塔在 JFK 的出租车时间的要素,如果要素只是德尔塔。然而,即使这样也只减少了大约 10 秒的 MAE。

不过需要注意的一点是,这个模型打破了线性回归的一个假设——同方差。这可以从下面的残差图中看出:

如你所见,在上面的残差图中存在异方差。与剩余误差达到 15 分钟的较短时间相比,该模型在预测较长的滑行时间方面表现稍好。当我们考虑出租车时间的问题时,这实际上是有意义的。在短暂的滑行时间内,一个简单的差异,如飞行员驾驶的速度,可能会导致数据的差异,而我们的模型不具备处理这种情况的能力。而在更长的滑行时间里,可能有其他因素造成。比如这个数据中的一个特征就是NAS 中的延误(分钟)基本上是全国航空系统的延误。这种类型的延误会导致较长的滑行时间,因此,这一特征可能是模型预测较长滑行时间优于较短滑行时间的重要原因。

结论

从这个回归模型,我们可以得出结论,繁忙的机场和上午的航班往往有较长的滑行时间。虽然这种趋势是合理的,也是可以猜测的,但用数据来验证它是很重要的。此外,该模型能够更复杂地衡量每个机场,以确定飞机的滑行时间。

虽然西南航空公司在滑行时间指标上占主导地位的原因仍不清楚,但确定其他可能影响停机坪延误的因素是值得研究的。

感谢您的阅读!

项目资源库可以在这里找到

参考

[## 详细的统计数据

政府交通财务统计

www.transtats.bts.gov](https://www.transtats.bts.gov/ONTIME/Departures.aspx) [## 加州旧金山天气历史|地下天气

编辑描述

www.wunderground.com](https://www.wunderground.com/history/monthly/us/ca/san-francisco/KSFO/date/2019-1)

使用机器学习预测破产

原文:https://towardsdatascience.com/predicting-bankruptcy-f4611afe8d2c?source=collection_archive---------18-----------------------

维克拉姆·迪瓦塔&迪瓦希什·迪曼

2008 年的经济危机引发了一场关于市场可持续性以及可用于预测市场可持续性的工具的讨论。为了避免未来发生这样的灾难性事件,对更好的预测模型的需求变得显而易见。公司和企业的破产在多个方面影响金融市场,因此通过监控多个变量来预测公司破产的需求变得更加重要。更好地理解破产和预测破产的能力将会影响全球贷款机构的盈利能力

作为一项分类工作,数据分析师有太多的选择。著名统计学家乔治·博克斯(George Box)曾经说过,“所有的模型都是错的,但有些是有用的”(维基百科,自由百科,2019)。考虑到这一点,我们承担了构建不同的监督机器学习算法的任务,并对每个模型进行了比较分析,以确定哪些模型更适合预测经济破产

我们遵循的分析流程概述如下:

关于数据集

本次练习使用的数据集是波兰公司在 5 年研究期内的破产状况,可从这里下载:【https://goo.gl/e2Px2y。它包含 43405 个观察值,分布在 5 个子集(每年一个),每个观察值有 64 个财务比率。这里有一个 URL ,其中提供了属性的名称(感谢 Harsha Danda 找到了这个链接)。一些公司在研究期间破产(用“1”表示),而其他公司幸存下来(用“0”表示)。研究期间每年的总结如下。

性能指标

经过适当的输入和预处理后,数据以 70:30 的比例分成训练数据集和测试数据集。分类器建立在训练数据上,并且它们的性能使用训练和测试数据集的混淆矩阵来测量,如下所示:

其中 TP =真阳性的数量,
TN =真阴性的数量,
FP =假阳性的数量,
FN =假阴性的数量。

这些可用于计算以下指标:

观察次数,n = TN+FP+FN+TP
错误数= FP+FN
错误分类(错误率)= FP+FN / n
敏感度(真阳性)= TP / FN+TP
假阳性= FP / TN+FP
特异性(真阴性)= TN / TN+FP
精度= TP / FP+TP
患病率= FN+TP / n
准确度= TN+TP

由于我们感兴趣的是将公司分类为可能破产(或不破产),上述比率可以解释如下:

错误分类(错误率):分类器出错的频率有多高?
敏感性(真阳性):当一家公司实际上“破产”时,分类器正确预测结果的频率是多少?
误报:当一家公司实际上“没有 _ 破产”时,分类器出错的频率是多少?
特异性(真阴性):假设一家公司“没有破产”,那么分类器有多准确?
精度:当分类器预测“破产”时,它的正确率是多少?
普遍性:数据集中“破产”实际上发生的频率有多高?
准确性:总体而言,分类器正确的频率如何?

构建模型

使用 R 和 Python 为这个练习构建了不同的机器学习算法。可以在这里找到运行代码的视频:https://youtu.be/OWbuOza_Gao

逻辑回归
感知器作为分类器
深度神经网络分类器(具有不同的大小和深度)
Fischer 线性判别分析
K 最近邻分类器(具有不同的 K 值)
朴素贝叶斯分类器
决策树(具有不同的桶大小阈值)
袋装决策树
随机森林(具有不同的树大小)
梯度推进
支持向量机(具有不同的核)

有些算法是用不同的初始化构建的,为了找到最佳的初始化,记录了每种算法的性能。例如,对于 K-最近邻,尝试了从 1 到 19 的不同 K 值。下面以从红到绿的颜色编码显示了每个人的表现,红色表示“差”,绿色表示“好”:

我们看到,随着 k 的增加,错误率(以及准确性)降低,然后又开始增加。灵敏度随着 k 的增加而增加,而特异性随着 k 的增加而降低。

类似地,我们为随机森林尝试了不同数量的树,范围从 50–500,跳跃 50:

该模型在有 200 棵树的情况下表现最佳。

模型比较

我们制作了一个所有模型的对比图。基于所使用的超参数以及为测量性能而选择的度量标准,可以看出这些模型表现不同。下面给出了关于训练数据的度量的概述。

测试数据的指标概述如下:

一些发现

数据集有大量的“0”(没有破产的公司),很少的“1”(破产的公司)。因此,大多数模型给出了很高的精确度,但是在预测“1”时表现很差。由于任务是预测破产,我们认为我们必须把重点放在敏感性(真正的积极)作为比较模型的相关措施。

我们发现,集成模型如梯度推进和袋装决策树在训练和测试数据集上表现最好,甚至优于神经网络算法。他们的计算速度也很快,只需几分钟就能完成训练并得分。表现最差的模型是朴素贝叶斯模型,它导致了大量的错误和较差的敏感性得分。

后续步骤

合理的下一步是探索为什么朴素贝叶斯表现如此之差,以及不同的打包和提升模型以进一步提高模型的性能。可以考虑的另一种方法是仅对前 30 个主要成分运行上述模型,因为它们可以解释 97%以上的数据变化,如下所示。

如果您在电脑上试用了这些模型,请与我们分享您的结果!你可以在 LinkedIn 上找到我们。

Photo by Karol D from Pexels

参考

英国马卡姆(2014 年 3 月 25 日)。混淆矩阵术语简单指南。检索 2019 年 2 月 7 日,来自数据学校:https://www . Data School . io/simple-guide-to-focus-matrix-terminals/

Géron,A. (2019,1 1)。第一章。人工神经网络导论。检索 26 2019,来自神经网络与深度学习:https://www . oreilly . com/library/view/Neural-networks-and/9781492037354/ch01 . html

扁平图标。(2019, 1 1).每月一包。检索到 2019 年 2 月 7 日,来自平面图标:https://www.flaticon.com

未知。(-, — -).算法特点。检索 22019 年 6 月,来自 r studio PUBS:http://r studio-pubs-static . S3 . amazonaws . com/4239 _ fcb 292 ade 17648 b 097 a 9806 FBE 026 e 74 . html

郭,法律顾问(未注明)。基于 Yelp 数据集预测餐厅的评分和受欢迎程度。斯坦福大学经济系 CS 229 机器学习期末项目。

马切伊·zięba,S. K. (2016)。集成提升树与合成特征生成在破产预测中的应用。弗罗茨瓦夫科技大学,计算机科学系,运筹学系,计算机科学与管理学院,弗罗茨瓦夫。

维基百科,免费的百科全书。(2019, 2 5).所有型号都是错的。(T. F. Wikipedia,制作人)检索 2019 年 7 月 2 日,来自维基百科,免费百科:【https://en.wikipedia.org/wiki/All_models_are_wrongT2

用细胞神经网络预测电池寿命

原文:https://towardsdatascience.com/predicting-battery-lifetime-with-cnns-c5e1faeecc8f?source=collection_archive---------1-----------------------

使用 TensorFlow 2 分析序列数据

Photo from Fancycrave

本文由汉尼斯·克诺布洛赫、阿德姆·弗伦克和温迪·张撰写。
你可以在 GitHub 上找到这个项目的源代码:

[## DSR-18/电池寿命长

用 TensorFlow 2 预测总电池循环寿命。我们将发表一篇描述这个项目的博客文章…

github.com](https://github.com/dsr-18/long-live-the-battery)

锂离子电池为我们生活中的几乎所有电子设备供电,包括手机和笔记本电脑。它们是可再生能源和电动汽车的核心。多年来,公司一直试图预测电池在耗尽之前可以持续多少次充电。更好的预测将有助于更准确的质量评估和改善长期规划。

但这很难,因为每种电池的老化程度都不一样,这取决于电池的使用和制造过程中的条件。Kristen A. Severson 等人最近发表的一篇名为容量退化前电池循环寿命的数据驱动预测的论文,声称通过“综合实验数据和人工智能”找到了解决这一问题的关键。尽管他们的结果领先于传统方法,但该团队更关注他们在电气工程领域的知识,而不是机器学习部分。

好消息是:研究人员已经公开了同类最大的数据集!虽然数据集仅限于在实验室环境下对新电池的测量,但它仍然是同类中最全面的。我们使用了更复杂的机器学习方法来建立一个更通用、更准确的模型来预测电池寿命(在这些情况下)。在这里你可以看到它的行动:www.ion-age.org/example。要完全理解这个模型的作用,请继续阅读!

这篇文章从头到尾描述了我们的研究过程。此外,我们还描述了我们的工作流程和工具,为初级数据科学家的项目提供灵感。

我们试图预测什么?

该论文的作者专注于全新的电池,并预测了它们的寿命。这令人印象深刻,但如果你想诊断一个已经在使用的电池,你就不走运了。相反,我们希望预测任何电池的剩余循环寿命,不管是旧的还是新的。

另一个警告是预测所需的数据量。研究人员使用第一次和第一百次充电循环的数据进行预测。我们的目标是通过仅 20 个连续充电周期的测量获得准确的结果,使该模型更适用于现实世界。除此之外,了解电池的当前年龄也很有用。

这使我们得出以下问题定义:

在有限数量的充电周期内,给定测量值,一个电池单元经历了多少个周期,在它损坏之前将持续多少个周期?

让我们通过步骤建立一个模型,并预测这一点!

了解数据

原始论文的作者组装了 124 个锂离子电池来测量来自的数据。每个电池根据许多预定策略中的一个进行充电和放电,直到电池达到其原始容量的 80%(这意味着电池对于正常使用来说已经变得太不可靠,并且被认为是“坏的”)。达到这种状态之前的循环次数(完全充电,然后完全放电)称为电池循环寿命,这是我们的目标之一。在我们的数据集中,这个数字从 150 到 2300 不等。

Cycle life of the battery cells in the dataset. Most cells lasted around 500 charging cycles before they were considered broken.

每个单元的数据以嵌套结构的形式呈现,有些特征每个周期只测量一次,有些则测量多次。在一个完整的周期中,我们对容量、温度、电压和电流进行了一千多次测量,但对电池内阻或总周期时间等其他指标只有一次标量测量。

The data consists of 124 battery cells, each has gone through a variable number of charging cycles, and for each cycle we have measurements over time and scalar measurements.

由于大部分测量是在实验控制的充电策略(因电池而异)期间进行的,因此我们将数据裁剪到放电周期(所有电池都具有可比性)。这使我们进入下一步…

处理数据

原始测量数据可能非常嘈杂。测量之间的距离并不总是相等的,本应单调减少的数据会意外增加,有时硬件会关闭并在随机时间点继续测量。因此,在将数据输入模型之前,我们要特别注意数据是否干净,格式是否正确。

我们去除有时间间隙、小异常值或其他不一致的周期。我们发现平滑噪声的一个特别有用的东西是 savitzky golay 滤波器。这帮助我们恢复了一些在实验中有测量问题的数据样本。

数据中的另一个问题是时间。不同的充电政策意味着一些循环比其他循环完成得更快,充电和温度的时间测量值无法进行比较。相反,我们以与原始论文相似的方式对数据进行了重新采样:

  1. 以放电时的电压范围为基准,而不是时间!
    对于该电池型号,3.6V 和 2.0V 始终对应完全充电和放电。这个范围保持不变,即使时间不变。
  2. 在电压上插入电荷和温度。
  3. 以 1000 个等距电压步长对电荷和温度进行重新采样。

搞定了。现在所有的测量对于每个单元和周期都有相同的长度,但是我们仍然有一些 1000 步的特征,而其他的仅仅是标量。当数组特征和标量特征同时输入到我们的模型中时,我们如何避免形状不匹配?一种解决方案是在不同的入口点将数据输入到模型中,并在以后将所有的数据整合在一起。当我们详细讨论模型时,这一招会变得更加清晰。我们还需要做一件事。

为了能够检测趋势,我们将多个连续的充电周期作为输入。我们称这些周期组为窗口。整个窗口应该总是只有一个目标,但是每个周期都有一个“当前周期”和“剩余周期”值。这就是为什么我们将上一个周期的值定义为整个窗口的目标。

让我们最终进入(更)有趣的部分!

建立框架

在我们深入研究数据和创建酷模型之前,我们需要考虑我们的设置。我们想从头到尾使用 TensorFlow 2.0,以从集成的功能中受益,如 tensorboard数据集 API超参数调整

在选择了框架之后,我们决定了应该在什么平台上运行我们的培训工作。我们没有让自己的笔记本电脑过热,而是使用了谷歌云的人工智能平台。人工智能平台允许我们同时运行几个培训任务,轻松标记它们并监控过程。

这需要一些设置。这可能要花相当多的时间才能第一次做好,所以我们不会在本文中深入讨论所有的细节。这里有一个总结:

  1. 创建一个帐户并在你的机器上安装 google cloud sdk
  2. 将您的数据上传到谷歌云桶
  3. 编写一个运行作业的中央 python 脚本(加载数据、加载模型、训练模型、保存结果)
  4. 确保你的项目和文件夹结构是为 AI 平台正确设置的

现在,我们能够从命令行启动一项培训工作,并可以随时修改几乎所有内容。通过在命令后添加一个标志,我们可以调整诸如时期数、批量大小、重排、检查点保存,甚至可以轻松地在模型架构之间切换。这让我们能够快速迭代,测试不同的理论,并消耗大量(免费)积分。

开发模型

我们用 tf 建立了我们的模型。使用功能 API 的 Keras。我们在不同的入口点将数组和标量特征输入到模型中,因此在将它们组合在一起之前,我们可以对它们做不同的事情。

我们将每个窗口中的阵列特征沿着它们的短边连接起来,使它们成为具有形状(窗口大小、长度、特征数量)的 3D 矩阵。然后,我们可以使用 MaxPooling 将这个矩阵通过三个 Conv2D 层,从中提取相关信息,同时保持窗口的顺序性质。Conv2D 作用于“特征数量”维度,就好像这些是图像中颜色通道的数量。这是因为阵列特征都共享相同的电压范围,因此高度相关(就像图像中的 RGB 通道一样)。卷积后,我们将数据展平为 1D 数组。

The data entering the model is split into array features and scalar features. Both are processed separately before they’re concatenated and sent through a fully connected dense network to produce two outputs.

我们在窗口方向上以类似的方式连接标量特征,以产生具有形状(窗口大小、特征数量)的数组,然后通过具有一个 MaxPooling 的两个 Conv1D 层,最后将其展平。

我们现在有两个带有特性映射的平面阵列,我们可以很容易地将它们放在一起,并将其输入到一个完全连接的密集网络中,以产生我们的结果。

训练和调整模型

在我们建立了模型之后,是时候训练它了。我们编写了一个脚本,在一个简单的命令行界面中调用 GCP API,因此当您在项目的主目录中时,在云中启动培训工作变得非常容易:

./train.sh

例如,如果我们想要修改训练时期的数量和每个窗口的样本数量,我们的脚本将允许我们使用简单的标志来完成此操作:

./train.sh -e 70 -w 10

在训练期间,我们跟踪了训练(橙色)和验证(蓝色)集的三个指标:当前周期以及剩余周期的损失和平均绝对误差(MAE)。几分钟后,我们可以在 TensorBoard 中查看结果。让我们看看损失是什么样的:

Loss measured in Mean Squared Error for the first training run over 68 epochs. Orange is the train loss, blue the validation loss.

它正朝着正确的方向发展,但是我们对培训和确认损失之间的差距并不满意。为了缩小这个差距,辍学是一个流行的工具,所以我们把它添加到模型中。我们还需要调整我们的超参数,这就是为什么我们在不同的设置上使用 gridsearch。为了跟踪这些设置,我们使用了 hparams 模块,它来自 TensorFlow 2.0,可以在 tensorboard.plugins 中找到。现在我们可以比较不同的运行,并选择最佳的参数。

由于“当前”和“剩余周期”的正确预测应该总是大于零,所以我们尝试将 ReLU 作为输出层的激活函数,以在训练过程中减少模型的搜索空间。此外,我们的模型严重依赖 CNN,所以我们也尝试了不同的内核大小。最后,我们测试了两种不同的学习速率,并测量了设置的当前周期和剩余周期的 MAE。

Results of a gridsearch, displayed in TensorBoard. Each colored line represents one parameter setup.

使用我们从超参数调整中获得的最佳模型设置,并通过将训练历元数设置为 1000,我们最终得到了当前周期为 90 MAE、剩余周期为 115 MAE 的模型:

Loss measured in Mean Squared Error for the final training run over 1000 epochs, displayed with a smoothing factor of around 0.6.

Mean Absolute Error, Current Cycle for the final training run over 1000 epochs, displayed with a smoothing factor of around 0.6.

Mean Absolute Error, Remaining Cycles for the final training run over 1000 epochs, displayed with a smoothing factor of around 0.6.

它仍然不完美,但我们对我们心目中的应用程序的结果非常满意(我们终于有一些食物来庆祝了!).

做预测

当我们查看我们的最佳设置的训练曲线时,我们可以看到,最低的损失不是在训练结束时,而是在训练的四分之三左右。我们如何使用当时的模型进行预测?我们必须实现检查点,允许我们在训练期间的特定时间恢复保存的模型。

一旦我们有了一个模型,我们就可以用 TensorFlow 服务或 Flask 之类的 web 框架来服务它。当时,谷歌云平台不支持 TF2 服务,所以我们决定完全在 Flask 中构建应用程序,并将其托管在 AWS EC2 实例上。

你可以在这里再次看到结果:【www.ion-age.org/example】T2

加载随机样本文件后,您可以预览我们将要预测的数据,您会发现我们的两个线性特征和三个标量特征。点击预测按钮会生成一个图表,显示我们的两个目标:当前和剩余周期。

Screenshot from www.ion-age.org

就是这样!这就是给定适当的测量数据,可以准确预测任何锂离子电池的年龄和预期寿命的算法所需的全部内容。

如果您有任何建议或问题,或者您发现了一个 bug,请随时留下您的评论。我们很乐意回答问题!

我们在柏林为期三个月的数据科学训练营数据科学务虚会期间创建了这个项目。

预测美国城市的“骑行能力”

原文:https://towardsdatascience.com/predicting-bikeability-in-u-s-cities-67da4ff8376?source=collection_archive---------20-----------------------

根据联合国的统计,超过一半的世界人口居住在城市。随着全球贫困率持续下降,人们变得更加富裕,道路上的私人车辆数量激增。这两种现象意味着更严重的交通拥堵,这反过来又加剧了导致气候变化的温室气体排放。替代的“绿色”交通方式,即步行、踏板车和自行车,可以改善城市流动性并帮助城市实现减排目标,但并非所有城市都同样有利于这些方式。正是在这种背景下,我开发了一个数据科学项目,来预测美国各地城市的“可骑性”,并探索哪些城市特征决定了“可骑性”。

我使用 OLS 回归技术来预测目标值,城市自行车得分,范围从 0(糟糕的骑行条件)到 100(完美的骑行条件)。我的模型的特征包括一个城市的公共交通得分、人口、人口密度、商业密度、天气、人均 GDP 和当地税率。数据来源包括 Walk Score、美国气候数据、INRIX、城市实验室和税收基金会。

第一部分:数据争论

该项目开始于大多数数据科学项目:获取和清理数据!在从上述来源收集和清理数据之后,我需要将分散的信息连接起来。令人头疼的一个主要原因是同一个城市不同的命名习惯(例如,华盛顿特区和华盛顿 DC)。我对所有数据集运行了以下函数,以确保具有多种命名约定(或拼写错误)的城市在数据集之间是一致的。

def fix_cities(df):
    df.loc[df['city'] == 'Nashville', 'city'] = 'Nashville-Davidson'
    df.loc[df['city'] == 'Louisville', 'city'] = 'Louisville-Jefferson'
    df.loc[df['city'] == 'Lexington', 'city'] = 'Lexington-Fayette'    
    df.loc[df['city'] == 'OklahomaCity', 'city'] = 'Oklahoma City'
    df.loc[df['city'] == 'Salt LakeCity', 'city'] = 'Salt Lake City'
    df.loc[df['city'] == 'SanFrancisco', 'city'] = 'San Francisco'
    df.loc[df['city'] == 'VirginiaBeach', 'city'] = 'Virginia Beach'
    df.loc[df['city'] == 'Washington,D.C.', 'city'] = 'Washington, D.C.'
    df.loc[df['city'] == 'Washington Dc', 'city'] = 'Washington, D.C.'
    df.loc[df['city'] == 'Washington', 'city'] = 'Washington, D.C.'
    df.loc[df['city'] == 'New York', 'city'] = 'New York City'

接下来,由于在不同的州有一些同名的城市,我需要在城市和州的组合上连接数据帧。然而,一些数据集包括完整的州名,而其他数据集只包括两个字母的缩写。我创建了一个字典来将州名映射到州缩写。

states_abbrev = {'Alaska': 'AK', 'Alabama': 'AL', 'Arkansas': 'AR', 'Arizona': 'AZ','California': 'CA', 'Colorado': 'CO', 'Connecticut': 'CT','District of Columbia': 'DC','Delaware': 'DE','Florida': 'FL','Georgia': 'GA','Hawaii': 'HI','Iowa': 'IA', 'Idaho': 'ID', 'Illinois': 'IL', 'Indiana': 'IN','Kansas': 'KS', 'Kentucky': 'KY', 'Louisiana': 'LA', 'Massachusetts': 'MA','Maryland': 'MD', 'Maine': 'ME', 'Michigan': 'MI', 'Minnesota': 'MN', 'Missouri': 'MO', 'Mississippi': 'MS', 'Montana': 'MT', 'North Carolina': 'NC','North Dakota': 'ND', 'Nebraska': 'NE', 'New Hampshire': 'NH', 'New Jersey': 'NJ','New Mexico': 'NM', 'Nevada': 'NV', 'New York': 'NY', 'Ohio': 'OH',
'Oklahoma': 'OK', 'Oregon': 'OR', 'Pennsylvania': 'PA', 'Puerto Rico': 'PR','Rhode Island': 'RI', 'South Carolina': 'SC', 'South Dakota': 'SD', 'Tennessee': 'TN','Texas': 'TX', 'Utah': 'UT', 'Virginia': 'VA', 'Vermont': 'VT','Washington': 'WA', 'Wisconsin': 'WI', 'West Virginia': 'WV', 'Wyoming': 'WY'}

我之前已经“腌制”了每个单独的数据帧,所以我的下一步是读取每个腌制,确保城市名称和州是一致的,并按城市和州合并数据帧。当我准备开始探索性数据分析(EDA)和特性工程时,这给了我一个主数据集以备后用。

%pylab inline
%config InlineBackend.figure_formats = ['retina']import pandas as pd
import seaborn as snsdf = pd.read_pickle('city_traffic.pkl')
df['city'] = df['city'].str.lower()
df['state'] = df['state'].str.strip()
df['city'] = df['city'].str.strip()
fix_cities(df)files = ['city_busdensity.pkl','city_percip.pkl','city_poulations.pkl',
         'city_taxes.pkl','city_temp.pkl','city_walkscore.pkl']for file in files:
    df_pkl = pd.read_pickle(file)
    if 'city' in df_pkl.columns:
        df_pkl['city'] = df_pkl['city'].str.strip() 
        df_pkl['city'] = df_pkl['city'].str.lower() 
    if 'state_long' in df_pkl.columns:
        df_pkl['state'] = df_pkl['state_long'].map(states_abbrev) #Get state abreviation for these
    df_pkl['state'] = df_pkl['state'].str.strip()
    fix_cities(df)
    if 'state' in df_pkl.columns:
        df = pd.merge(df, df_pkl, on = ['city','state'], how = 'outer')
        print(file,'success')
    else:
        print(file, 'no state column')df.to_pickle('city_data.pkl')

第二部分:EDA 和特征工程

我加载回我的腌数据帧,并丢弃除了我的目标值(bike_score)和感兴趣的特性之外的所有内容。

df = pd.read_pickle('city_data.pkl')df = df[['bike_score','walk_score','transit_score',
         'congestion', 'bus_density','pop_density', 'population', 
         'gdp_per_cap',  'state_tax', 'city_tax', 'total_tax',
         'avg_percip',  'avg_temp']]

接下来,我创建了 pairplots 来查看我的变量的分布,以及我的数据框架内的二元关系。

sns.pairplot(df, height=1.2, aspect=1.5);

虽然大多数要素看起来呈正态分布,但人口、人口密度(“pop_density”)和商业密度(“bus_density”)是明显的例外,我想知道它们是否会受益于对数变换。

log_vars = ['population','pop_density','bus_density']
for v in log_vars:
    df['log_'+v] = log(df[v])

我绘制了这三个特征在对数变换前后的直方图。代码和图形如下所示。

f, axes = plt.subplots(3, 2)
f.subplots_adjust(hspace = 0.5)
sns.distplot(df.population, ax=axes[0][0], kde=False, bins='auto')
sns.distplot(df.log_population, ax=axes[0][1], kde=False, bins='auto')
sns.distplot(df.pop_density, ax=axes[1][0], kde=False, bins='auto')
sns.distplot(df.log_pop_density, ax=axes[1][1], kde=False, bins='auto')
sns.distplot(df.bus_density, ax=axes[2][0], kde=False, bins='auto')
sns.distplot(df.log_bus_density, ax=axes[2][1], kde=False, bins='auto')for i in range(0,3):
    for j in range(0,2):
        axes[i][j].set_xticks([])
        axes[i][j].set_yticks([])

Histograms of select features, before and after log transformation

从直方图中可以看出,在对数变换之后,总体分布看起来更为正态分布。另一方面,人口和商业密度似乎仍未呈正态分布。相反,这些要素似乎具有较大的异常值(例如,纽约市),导致它们向右倾斜。尽管如此,经过对数变换后,变换后的密度要素和目标值之间的关系似乎更加线性,因此我决定将它们保留下来,看看它们实际上是否显著。

Bike score as a function of urban population density and log urban population density

Bike score as a function of urban business density and log urban business density

接下来,我研究了要素之间的相关性,以解释任何共线性。

Heatmap of feature correlations

corrs = {}
cols = list(df.iloc[:,1:].columns)
for x in cols:
    cols = [w for w in cols if w != x]
    for w in cols:
        corrs[(x, w)] = df[x].corr(df[w])
results = [(x, v.round(2)) for x, v in corrs.items() if corrs[x] > 0.5]results = sorted(results, key=lambda x: x[1], reverse=True)
results

Correlations for pairs of features

前三个相关性是预期的(变量的对数应该与变量本身高度相关)。Total tax 就是城市和州的税收之和,所以我选择 total_taxes 作为包含在模型中的税收特性。相关性在 0.52 和 0.56 之间的底部四对特征是值得关注的,所以我为它们创建了交互项。

interact = [x[0] for x in results[4:]]
interacts = []
for i in range(len(interact)):    
    col1 = interact[i][0]
    col2 = interact[i][1]
    col_interact = col1+'_x_'+col2
    interacts.append(col_interact)
    df[col_interact] = df[col1]*df[col2]

接下来,我检查了各种要素组合的方差膨胀因子,以确保最终要素之间的任何共线性都很低。足够幸运的是,我所有的 VIF 都低于“神奇数字”3。

from statsmodels.stats.outliers_influence import variance_inflation_factordf['constant'] = 1
X = df[['congestion', 'population', 'gdp_per_cap', 'walk_score_x_transit_score','total_tax', 'avg_percip', 'avg_temp', 'log_population','bus_density_x_pop_density', 'constant']]
vif = pd.Series([variance_inflation_factor(X.values, i) for i in range(X.shape[1])],index=X.columns)
vif.sort_values(ascending = False)

Variance inflation factors of model features

在我的数据为分析做好准备之后,我在继续进行 hpyer 参数调整和模型选择之前,对这个版本的数据帧进行了处理。

df.to_pickle('regression_data.pkl')

第三部分:超参数调整和模型选择

最后,我准备创建我的模型,看看哪些特征决定了一个城市的“骑行能力”!

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression, Lasso, LassoCV, Ridge, RidgeCV
from sklearn.metrics import r2_score 
from sklearn.preprocessing import StandardScaler, PolynomialFeatures
from sklearn.pipeline import Pipeline
from sklearn.metrics import mean_absolute_error as mae
from sklearn.model_selection import cross_val_score, cross_val_predict
from sklearn.model_selection import GridSearchCVdf = pd.read_pickle('regression_data.pkl')

首先,我将“y”定义为我的目标变量(bike_score),将“X”定义为城市特征矩阵。

y = df['bike_score']
X = df[['constant','congestion', 'transit_score', 'gdp_per_cap','total_tax', 'avg_percip', 'avg_temp', 'log_population','bus_density_x_pop_density']]

接下来,我将数据分成训练集和测试集。我用 80%的数据训练我的模型,保留 20%用于测试。

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,random_state = 88)

a .线性回归

我首先在 5 组交叉验证数据上拟合了一个“普通”线性回归模型(这意味着我的五分之一的数据总是被保留下来评分)。我得到了所有验证折叠的平均绝对误差(MAE)接近 8。

lr=LinearRegression()scores = cross_val_score(lr, X_train, y_train, cv = 5, scoring='neg_mean_absolute_error')
print (np.mean(scores)*(-1))

b .岭正规化

知道我的要素之间存在大量的多重共线性后,我接下来拟合了一个岭回归,它在我的(标准化)要素上添加了多重共线性的惩罚。

std_scale = StandardScaler()
X_train_scaled = std_scale.fit_transform(X_train)
X_test_scaled = std_scale.transform(X_test)ridge = Ridge(random_state=88)

我使用 scikit learn 的 GridSearchCV 来寻找使五个交叉验证折叠的平均绝对误差最小化的 alpha 参数。

grid_r1 = {'alpha': np.array([100,75,50,25,10,1,0.1,0.01,0.001,0.0001,0])}
ridge_grid = GridSearchCV(ridge, grid_r1, cv=5, scoring='neg_mean_absolute_error')
ridge_grid.fit(X_train_scaled, y_train)print("tuned hpyerparameters :(best parameters)", ridge_grid.best_params_)
print("MAE score :",-1*(ridge_grid.best_score_))

最好的 alpha 是 100,这给出了训练集的五个折叠的平均 MAE 为 7.5——比“香草”线性回归略有改善。

c .拉索正规化

套索正则化会对多重共线性造成不利影响,并且仅保留系数非零的重要要素。考虑到这一点,我决定将“厨房水槽”特性加入到这个模型中。同样,我的第一步是使用 GridsearchCV 来确定最佳 alpha 参数。alpha 值为 1.0 时,MAE 值为 6.6,与岭模型相比,这已经是一个显著的改进。

y = df['bike_score']
XL = df.drop(['bike_score','constant'], axis=1)X_train, X_test, y_train, y_test = train_test_split(XL, y, test_size=0.2, random_state = 88)std_scale = StandardScaler()
X_train_scaled = std_scale.fit_transform(X_train)
X_test_scaled = std_scale.transform(X_test)grid_l1 = {'alpha':  np.array([100,75,50,25,10,1,0.1,0.01,0.001,0.0001,0])}
lasso = Lasso(random_state=88)
lasso_grid = GridSearchCV(lasso, grid_l1, cv=5, scoring='neg_mean_absolute_error')
lasso_grid.fit(X_train_scaled, y_train)print("tuned hpyerparameters :(best parameters) ",lasso_grid.best_params_)
print("MAE score :",-1*(lasso_grid.best_score_))

正如我上面提到的,lasso 正则化倾向于将几个特征系数取零,只保留一组精选的重要特征。事实上,只有步行得分、步行得分/交通得分相互作用、拥堵和平均降水量返回非零系数。我决定在最终模型中保留除 walk score 之外的所有内容(以考虑 walk_score 和 walk_score*transit_score 之间的多重共线性)。

coefficients = sorted(list(zip(X_test.columns, lm.coef_.round(2))), key=lambda x: x[1], reverse=True)
coefficients

Feature coefficients with lasso regularization

下面是我的最终模型的代码,它对训练数据中的三个重要特征(步行分数/交通分数交互、拥堵和平均降水量)进行了 alpha 1.0 的 Lasso 回归拟合。最后,我使用该模型来预测测试集中的自行车分数(还记得我放在一边的 20%的数据吗?).测试数据的平均误差(模型预测值和实际目标值之间的平均绝对误差)为 6.3,这意味着我的模型能够预测城市自行车得分,平均与实际得分相差 6.3 分。

y = df['bike_score']
XL2 = df[['walk_score_x_transit_score','congestion','avg_percip']]X_train, X_test, y_train, y_test = train_test_split(XL2, y, test_size=0.2, random_state = 88)std_scale = StandardScaler()
X_train_scaled = std_scale.fit_transform(X_train)
X_test_scaled = std_scale.transform(X_test)lasso = Lasso(random_state=88, alpha=1.0, fit_intercept=True)lm2 = lasso.fit(X_train_scaled, y_train)
y_pred = lm2.predict(X_test_scaled)
mae(y_pred, y_test)

结果

根据模型系数,更“适合骑自行车”的城市是那些有替代交通方式(公共交通和步行)先例、更拥堵(更有动力寻找替代私家车的方式)和更少降水(雨/雪)的城市。根据 lasso 模型,当地税率、商业密度和人口密度等特征对一个城市的“骑行能力”没有影响。

coefficients = sorted(list(zip(X_test.columns, lm2.coef_.round(2))), key=lambda x: x[1], reverse=True)
coefficients

Feature coefficients, final regression model with lasso regularization

我的模型的低 R 平方和调整后的 R 平方表明,我选择的特征不能解释一个城市骑行能力的大部分差异。下一步将收集可能解释骑行性的其他要素,例如与自行车相关的基础设施的质量和整体山地。

def r2_adj(X,Y,r_squared):
    adjusted_r_squared = 1 - (1-r_squared)*(len(y)-1)/(len(y)- X.shape[1]-1)
    return(adjusted_r_squared)r2 = lm2.score(X_test_scaled, y_test)
r2a = r2_adj(X_test_scaled, y_test, r2)
print('R-squared', r2.round(2), 'Adjusted r-squared', r2a.round(2))

一旦我能够完善模型,并通过额外的功能选择和功能工程进一步减少 MAE,未来的工作将是纳入 walkscore.com 目前不提供自行车分数的国际城市。随着全球城市化进程的加快和气候变化的威胁日益逼近,城市必须找到减少私家车拥堵和排放的方法。自行车和踏板车共享计划有助于这一努力,我希望这个项目将阐明那些决定实施这些计划的可行先决条件的因素。

我一直在努力改进我的工作,所以欢迎提出建议!请随时在评论中提供反馈。

我尝试了深度学习模型来预测比特币价格

原文:https://towardsdatascience.com/predicting-bitcoin-prices-with-deep-learning-438bc3cf9a6f?source=collection_archive---------2-----------------------

利用神经网络预测加密货币价格

Photo by André François McKenzie on Unsplash

点击下方查看之前的文章使用机器学习研究比特币价格:

[## 我试图用机器学习来预测比特币的价格

利用时间序列模型预测加密货币价格

towardsdatascience.com](/predicting-prices-of-bitcoin-with-machine-learning-3e83bb4dd35f)

不久前,我们深入研究了机器学习模型的使用,以预测比特币的未来价格。在那里,我们使用了两个时间序列模型来预测比特币价格在未来几天或几周内的走向。就训练和使模型适应比特币的历史价格数据而言,这相当简单。但是,如果除了机器学习之外还有其他方法来预测时间序列数据呢?

这个问题的答案是…

深度学习

深度学习是机器学习的一个子集,但两者之间的关键区别在于,深度学习使用神经网络,赋予机器自我训练的能力。机器学习需要用户或工程师设置参数的算法来训练机器。换句话说,机器学习比深度学习需要更多的动手调整和修复。深度学习能够进行所有的调整和自我修复,而不需要用户/工程师的任何手动干预。他们需要调整或创建的主要东西是神经网络本身,它有自己的一套障碍。

但是深度学习比机器学习好吗?

这完全取决于问题。在某些情况下,传统的机器学习模型要比复杂的神经网络好得多。我们发现的一个这样的例子是用于分类的表格数据的训练和拟合。在这种情况下,与传统分类算法的结果相比,神经网络的训练时间明显更长,但表现水平较低或相当。但是时间序列数据呢?

这正是我们今天要做的!我们将根据比特币的历史价格数据,创建并运行我们自己的、相对简单的神经网络。然后,我们将能够看到神经网络是否优于传统的 SARIMAX,甚至是我们之前实现的脸书先知时间序列模型。

当我们演练创建神经网络的过程时,我们将解释必要的概念以更好地理解代码。期望在最后看到一个 Github 链接,展示神经网络的全部源代码。

在这里注册一个中级会员,可以无限制地访问和支持像我这样的内容!在你的支持下,我赚了一小部分会费。谢谢!

我们开始吧

首先,我们需要安装必要的库,以便创建我们的神经网络(您可能需要 安装 Tensorflow Keras )。接下来我们加载包含比特币历史价格数据的 CSV 文件,可以从 雅虎财经 下载。价格数据的另一个选项是使用金融数据 API,如 EOD 历史数据 。注册是免费的,你可以获得大量的金融数据。披露:我通过上面的链接从任何购买中赚取一小笔佣金。

然后,我们将做一些数据预处理,以便我们观察到的价格是“收盘价格,并且指数采用日期时间格式。最后,为了提高性能,我们将使用 scikit-learn 的 MinMaxScaler()来缩放数据。

Note: we are only selecting the last 1,000 days of Bitcoin prices because those days are the most representative of the current Cryptocurrency market.

助手功能

对于下一部分,我们将需要创建一些辅助函数,这将使我们能够适当和有效地运行神经网络。

visualize_training_results()—绘制我们的神经网络的精确度和损失。这允许我们检查我们的神经网络的训练进度/结果。

split_sequence() —创建两个数组: Xy 。这些变量分别是我们的神经网络的输入输出变量。输入是回顾的周期数,而输出是未来的周期数。简单地说,输入周期用于计算出导致输出周期的模式和序列。它们都可以是任何数量,但是如果输入周期的数量大于输出周期的数量可能是有益的。

layer_maker() —为我们的神经网络创建指定数量的隐藏层(如有必要,还可删除层)。这项功能构成了我们网络的主体。这个功能的重要性将在后面解释。

准备/分割数据

我们的下一步是在我们的比特币价格数据上实现我们的split_sequence()功能。

的周期数可以是我们选择的任意数。但是为了这个例子,我们将选择回顾价格历史的 30 天来预测下一个 10 天。但是,这并不意味着我们只回顾最近的 30 天来计算接下来的 10 天,总共只有 40 天。这意味着对于我们选择的 1,000 天中的每一天,前 30 天用于确定任何导致下 10 天的模式或序列。这些值用于训练神经网络,以便我们可以预测或预测从今天起未来 10 天的比特币价格。

神经网络

既然我们已经准备好了数据,我们可以进入有趣的部分——构建神经网络。我们的神经网络将使用来自 Keras 库的简单 序列 模型来创建。从那里,我们将通过在我们的模型中实现 LSTM (长期短期记忆)层来使用一个称为递归神经网络的特定神经网络。

递归神经网络— LSTM

递归神经网络( RNN )是一种神经网络,长短期记忆( LSTM )网络是一种 RNN。我们使用 RNN,而不是普通的神经网络,如多层感知器( MLP )网络,因为 rnn 最适合用于序列数据,如我们的比特币历史价格数据。如果你想了解更多关于 RNNs 和 LSTMs 的信息,点击 这里

创造我们的循环神经网络

万事俱备,我们可以开始为比特币价格数据构建神经网络了:

首先,我们实例化了顺序模型和一个激活函数,该函数贯穿神经网络中的各层。经过大量实验,最佳激活函数被证明是“ softsign 或“ tanh”。“这可能是因为这两个函数的范围都是从-1 到 1,并且比范围从 0 到 1 的激活函数更好地处理来自输入的负值和正值。

输入层

我们网络中的第一层将是输入层,只需调用model.add()就可以添加到网络中。在.add()方法中,我们有:

  • LSTM 层创造了我们的 RNN。
  • 作为第一个参数的节点数(这里设置为 25 )。
  • activation是我们之前设置为变量的激活函数。
  • return_sequence被设置为,因此 LSTM 层之间的每个序列包含适当的尺寸。
  • input_shape被设定为输入和特性的数量。这样做是为了让网络知道预期的形状。

隐藏层

对于下一个项目,我们可以开始在神经网络中创建我们的隐藏层。为了避免多次输入model.add(),我们将使用我们的layer_maker()功能。这就创建了一个简单的“ for ”循环,它具有一个指定的范围,可以根据我们的需要生成任意多的层。它还可以选择添加漏失层,这对正则化很重要。如果你不熟悉正则化这个术语,只知道它是我们神经网络中防止过拟合的一种方法。过度拟合,简单来说,发生在一个模型在预测新的观察值时表现不佳,在我们的情况下,预测未来的比特币价格。

接下来,我们用一个没有设置为真的return_sequence的最终隐藏层来完成隐藏层。这样做是为了使最终隐藏层的尺寸可以顺利地传递到输出层,而不会出现错误。

输出图层和模型汇总

上面代码片段中的下两项包含最后一层和我们刚刚创建的神经网络的摘要。最后一层或输出层不是 LSTM 层,而是一个正常的、密集连接的神经网络层,如果我们使用标准的非递归神经网络,就会经常使用它。该层包含我们希望预测的周期/天数作为其节点数。模型摘要显示有关创建的神经网络的基本信息。

编译模型

在我们继续训练我们的神经网络之前,我们必须用首选的规范来编译它:

  • optimizer是我们训练神经网络所需的优化算法(点击此处查看不同优化算法的完整概述)。
  • loss是我们在模型中衡量误差的方式。通常,我们希望损耗尽可能接近零(参见此处了解不同损耗函数的更多信息)。
  • metrics是我们衡量模型/神经网络性能的方式(单击此处了解有关不同指标的更多信息)。

尝试不同的参数

当创建神经网络模型时,为了找到最佳的参数组合,大量的实验是必要的。当谈到模型最终将如何执行时,节点和层的数量尤其重要。没有神奇的数字,两者都需要自己的理想数字,必须通过反复试验才能发现。

几乎你为任何新数据集创建的每一个神经网络都必须由它们自己唯一的一组参数组成。由于这个实验过程,我们的结果可能会因所用数据集的不同而大相径庭。

拟合/训练神经网络

一切准备就绪,万事俱备,我们终于可以开始拟合/训练我们的神经网络了!我们可以通过编写一行代码来实现这一点:

res = model.fit(X, y, epochs=800, batch_size=32, validation_split=0.1)

一旦我们运行这个代码,我们的神经网络将开始对我们的比特币价格数据进行训练。根据我们拥有的资源,培训时间可能需要几个小时。那些硬件规格更好的公司可能会比我们获得更好的结果。同时,当 NN 在训练时,我们可以解释model.fit()方法中的参数:

  • X, y是我们分配的包含比特币历史价格数据的变量。
  • epochs是 NN 在整个数据集上训练的次数,包含批次数。
  • batch_size是模型在更新其自身参数之前将从中工作的训练集中的样本数。
  • validation_split是在一个时期结束时,为评估数据的损失和指标而搁置的数据集的百分比。

可视化我们模型的性能

一旦我们完成了模型的训练,我们就可以通过使用visualize_training_results()函数来可视化我们的训练结果。运行以下代码行将显示我们的模型在多个时期的性能。

visualize_training_results(res)

这将为我们提供以下可视化效果…

这些可视化显示了训练验证设置的损失准确度。为了查看我们的 NN 是否训练良好,我们将希望看到验证和训练集的损失和准确性随着历元数的增加而收敛。否则,如果我们两组的损失和精确度彼此偏离,那么我们的神经网络可能有过度拟合的迹象。这可以通过引入一些脱落层,减少历元数,改变层数等方法解决。我们的神经网络训练中的这一步可能需要额外的试验和调整,以便观察训练和验证集的收敛性。

验证我们的神经网络

一旦训练完成,我们对损失和准确性的融合感到满意。我们需要根据实际数据测试我们的模型,看看它的表现如何。我们可以通过简单地可视化神经网络预测的比特币价格,并用比特币的实际价值来绘制它们。

Here we observe the Actual Bitcoin prices from the last 10 days and our NN’s predicted values starting from 10 days ago.

说到准确预测价格,我们的模型有好几次都不尽如人意。虽然,趋势和潜在价格目标可能提供一些价值。例如:

在第 0 天,您从模型的预测中观察到价格将在第 10 天达到 7,700 美元。但是,实际上,价格比第六天的价格略高。

从这个例子中,该信息可以向比特币交易者提供潜在的投资时机和机会。

使用我们的 RNN 进行预测

当我们最终对验证我们预测的比特币价值感到满意时,我们就可以进入神经网络中最有用的部分——预测比特币的未来价格!为了预测未来 10 天的比特币价格,我们所要做的就是在我们的model.predict()方法中输入过去 30 天的价格。

使用下面的代码,我们可以打印出未来 10 天的价格,并为这些预测绘制图表,以获得更好的可解释性。

太棒了。我们现在有了未来 10 天的比特币价格。然而,这些数字是可靠的吗?绝对不行!就像我们以前的时间序列模型一样,这个神经网络也可以给我们未来价格的大致趋势。如果他们错了,那也是意料之中的事,因为没有人和机器能够正确预测未来。我们只能希望看到它可能去哪里,使用神经网络可能比盲目猜测更好。

结束语

这个新创建的基于比特币历史价格数据的神经网络能够成功地为我们提供未来 10 天的预测。你如何处理这些信息完全取决于你 但是你应该知道这仍然是一个相对简单的神经网络。可以做更多的工作来提高我们的神经网络的性能,但是为了时间和简单起见,我们决定只讨论创建 r NN 的基础知识。

现在,根据你花了多少时间来训练和调整神经网络,你的结果可能会有所不同。请随意试验代码并测试不同的方法来改进神经网络。一些想法可能是将技术指标作为另一组输入变量,或者尝试不同类型的 RNN。深度学习有太多的可能性和途径可以探索,每天都有新的前景出现。所以,请留意新的发现,希望你喜欢并学到了一两件关于建立神经网络的事情!

资源:

[## 马克桑 93/BTC-预报员

此时您不能执行该操作。您已使用另一个标签页或窗口登录。您已在另一个选项卡中注销,或者…

github.com](https://github.com/marcosan93/BTC-Forecaster) [## 我试图用机器学习来预测比特币的价格

利用时间序列模型预测加密货币价格

towardsdatascience.com](/predicting-prices-of-bitcoin-with-machine-learning-3e83bb4dd35f) [## 如何开发用于时间序列预测的 LSTM 模型

长短期记忆网络,简称 LSTMs,可用于时间序列预测。有很多种…

machinelearningmastery.com](https://machinelearningmastery.com/how-to-develop-lstm-models-for-time-series-forecasting/)

用 Python 中的逻辑回归预测癌症

原文:https://towardsdatascience.com/predicting-cancer-with-logistic-regression-in-python-7b203ace16bc?source=collection_archive---------18-----------------------

理解数据,逻辑回归,测试数据,混淆矩阵,ROC 曲线

Source

简介:

在我的第一个逻辑回归分析中,我们仅仅触及了表面。讨论的只是高级概念和双变量模型示例。在这一分析中,我们将着眼于更具挑战性的数据,并学习更先进的技术和解释。

目录:

1.数据背景

2.数据探索/清理

3.数据可视化

4.构建模型

5.测试模型

数据背景:

测量体内某些蛋白质的水平已经被证明可以预测癌症的发展。医生可以进行测试来检查这些蛋白质的水平。我们有 255 名患者的样本,希望获得关于 4 种蛋白质及其与癌症生长的潜在关系的信息。

我们知道:

  • 每位患者测得的每种蛋白质的浓度。
  • 每个患者是否已经被诊断患有癌症(0 =没有癌症;1=癌症)。

我们的目标是:

通过从我们样本中的蛋白质水平和癌症之间的关系中提取信息来预测未来的患者是否患有癌症。

我们将关注的 4 种蛋白质:

甲胎蛋白

癌胚抗原

癌抗原 125

癌抗原 50

我从 UAB 的 MBA 项目 @ 那里得到了这套用于教育目的的数据。

数据探索/清理

让我们通过引入数据和导入必要的模块来开始分析。

%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
import seaborn as sns
df = pd.read_excel(r"C:\Users\Andrew\Desktop\cea.xlsx")
df.head()

Figure 1

df.head()为我们提供了数据集的前 5 个特征。每行是一名患者,每列包含一个描述性属性。类别(Y)描述了患者是没有癌症(0)还是患有癌症(1)。接下来的 4 列是在病人血液中发现的蛋白质水平。

df.describe()

Figure 2

我们可以从描述方法中检索关于样本的一些基本信息。该数据集中有 255 行,具有不同的标准差和均值。值得一提的是,与平均值相比,这 4 种蛋白质都具有较低的 50%值。这意味着大多数蛋白质水平很低。这 4 种蛋白质还具有高范围,这意味着存在高值异常值。比如我们看 AFP,均值 4.58,中位数 3.86,最高值 82.26。

df.isnull().sum()

检查数据中是否有空值总是好的。这可以通过多种方式解决。幸运的是,这次我们不用担心空值。

数据可视化

让我们想象一下我们的每个变量,并根据我们所看到的假设可能会发生什么:

目标变量(Y)

yhist = plt.hist('class (Y)', data = df, color='g')
plt.xlabel('Diagnosis (1) vs no diagnosis (0)')
plt.ylabel('Quantity')
plt.title('Class (Y) Distribution')

Figure 3

这个样本中有更多的无癌患者。如图 2 所示,“类别(Y)”变量的平均值为 0.44。

菲律宾武装部队;法新社;甲胎蛋白;金融理财师证书

**#setting the axes**
axes = plt.axes()
axes.set_xlim([0,(df['AFP'].max())])**#making histogram with 20 bins**
plt.hist('AFP', data = df, bins = 20)
plt.xlabel('AFP Level')
plt.ylabel('Quantity')
plt.title('AFP Distribution')

Figure 4

如前所述,甲胎蛋白水平低的患者相对较多。

**#color palette**
pal = sns.color_palette("Set1")**#setting variable for max level of protein in dataset**
lim = df['AFP'].max()**#setting bin size to be 20**
bins = np.linspace(0,lim,(lim/(lim*(1/20))))**#creating new column in df with bin categories per feature**
df['AFP_binned'] = pd.cut(df['AFP'], bins)**#creating a crosstab stacked bar chart variable**
chart = pd.crosstab(df['AFP_binned'],df['class (Y)'])**#normalizing chart and plotting chart**
chart.div(chart.sum(1).astype(float), axis=0).plot(kind='bar', color = pal,stacked=True)
plt.xlabel('Bins')
plt.ylabel('Quantity')
plt.title('Normalized Stacked Bar Chart: AFP vs Class(Y)')

Figure 5

作为分布直方图的补充,上面的堆积条形图显示了随着 AFP 水平的增加,1 与 0 的比例。该图表与分布直方图一样,也分为 20 个区间。结合我们从上述目标变量的分布和比例中获得的知识,我们可以直观地确定从该蛋白质中可能没有获得太多的预测知识。

让我们一步一步来。大多数患者的 AFP 值低于 10,如图 4 中的前 2 条所示。因为大多数患者都在前两个柱中,所以图 5 中它们之间 Y 的变化比其他柱中 Y 的变化更重要。

从柱 1 到柱 2,癌症患者的比例略有增加。癌症患者的比例从第 2 栏下降到第 3 栏。bar 3 之后,剩下来分析的患者少之又少,对趋势影响不大。从这里我们可以看到,目标变量看起来基本上与 AFP 的变化无关。最显著的变化(条 1 到 2)非常轻微,之后的变化不在同一方向。

让我们看看其他蛋白质是什么样子的。

成本效益分析(Cost Effectiveness Analysis)

Figure 6

Figure 7

东航似乎有一个不同的故事。图 6 显示了与 AFP 相似的分布形状;然而,图 7 显示了癌症发病率的不同变化。就像 AFP(由于分布形状)一样,柱之间最显著的癌症变化在柱 1 和柱 2 之间。

从柱 1 到柱 2 的变化从大约 63%的非癌性变为 18%的非癌性(或者换句话说,37%的癌性变为 82%的癌性)。此外,从 bin 2 到 bin 3 的变化是相同的方向,更多的癌症。从具有 100%癌症的 bin 5 开始的异常值加强了较高 CEA 可能指示癌症的趋势。

CA125

Figure 8

Figure 9

CA125 有点棘手。柱 1-2 表明,像癌胚抗原一样,这种蛋白的高水平可能导致癌症。然而,这一次似乎有两种趋势。随着几乎所有的后一类人群变成非癌症人群,这一趋势发生逆转。我们稍后将更详细地研究这个变量。

Figure 10

Figure 11

CA50 看起来没什么前途。前 4 个条柱似乎表明了较高癌症发病率的趋势。然而,趋势看起来在第 7-9 条中发生了逆转。CA50 水平和癌症之间的关系可能很小或可以忽略。

构建模型

让我们把这个模型放在一起,看看回归能告诉我们什么。

**#importing module**
import statsmodels.api as sm**#setting up X and y**
cols= [‘AFP’,’CEA’,’CA125',’CA50']
X= df[cols]
y = df[‘class (Y)’]**#filling in the statsmodels Logit method**
logit_model = sm.Logit(y,X)
result = logit_model.fit()
print(result.summary())

Figure 12

突出显示的值是本报告中重要的内容:我们的 4 个独立变量及其 p 值。AFP 和 CA50 的 p 值较高。如果我们的α值为 0.05,那么 AFP 和 CA50 的值太高,无法拒绝我们的无效假设(我们的无效假设是蛋白质水平对癌症发病率没有影响)。然而,CEA 和 CA125 通过了测试,并且被确定为显著的。AFP 和 CA50 都是基于我们在堆积条形图上看到的假设而被忽略的,所以这是有道理的。让我们去掉这些变量,再次进行回归分析:

**#deleted p values above the 0.05 alpha threshold**
cols= ['CEA','CA125']
X= df[cols]
y = df['class (Y)']
logit_model = sm.Logit(y,X)
result = logit_model.fit()
print(result.summary())

Figure 13

有了我们的最终系数,我们对每个剩余蛋白质和癌症之间的关系有了更多的了解。CEA 的阳性关系比 CA125 的阴性关系强约 3 倍。随着癌胚抗原的增加,癌症的可能性增加。随着 CA125 的增加,癌症的可能性降低。

测试模型

我们将把样本数据分为训练和测试,以测试我们的回归结果。

from sklearn.linear_model import LogisticRegression
from sklearn import metrics**#shuffling df**
df = df.sample(frac=1).reset_index(drop=True)**#redefining columns** 
cols= ['CEA','CA125']
X= df[cols]
y = df['class (Y)']**#Dividing into training(70%) and testing(30%)**
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)**#Running new regression on training data**
logreg = LogisticRegression()
logreg.fit(X_train, y_train)**#Calculating the accuracy of the training model on the testing data**
accuracy = logreg.score(X_test, y_test)
print('The accuracy is: ' + str(accuracy *100) + '%')

形象化上面计算的精确度的一个好方法是使用混淆矩阵。下面是混淆矩阵的概念框架。

编辑:我和一个生物统计的朋友谈论我的分析,这个领域的惯例是这种疾病被认为是阳性的。我武断地将癌症设定为阴性,因为我当时并不知道这一点。

Figure 14

“迷惑”是很多人的关键词。试着一次看一行:第一行是一个好的起点。这一行告诉我们有多少实例被预测为良性的。如果我们查看这些列,我们可以看到该预测中实际值的拆分。请记住,行是预测值,列是实际值。

from sklearn.metrics import confusion_matrix
confusion_matrix = confusion_matrix(y_test, y_pred)
print(confusion_matrix)

将上面的矩阵与图 14 进行匹配,了解其含义:

  • 我们模型的 39 个猜测是真阳性:模型认为病人没有癌症,他们确实没有癌症。
  • 我们的模型的猜测中有 18 个是真阴性:模型认为病人患有癌症,他们确实患有癌症。
  • 14 我们的模型的猜测是假阴性:模型以为病人得了癌症,但实际上他们并没有得癌症
  • 6 我们的模型的猜测是假阳性:模型认为病人没有癌症,但他们实际上患了癌症。

我们总数据的 30%给了测试组,剩下 255(.3) = 77 个被测试的实例。矩阵的和是 77。将“真实”的数字除以总数,将得到我们模型的精确度:57/77 = 74.03%。

请记住,在执行这个测试之前,我们随机打乱了数据。我运行了几次回归,得到了 65%到 85%的准确率。

受试者工作特征曲线

最后,我们将执行接收器操作特性 (ROC)分析,作为测试我们模型的另一种方式。该测试的两个目的是

  1. 确定最佳“截止”点在哪里。
  2. 通过另一个称为“曲线下面积”(AUC)的指标来确定模型的分类效果。

我们将从头开始创建我们的 ROC 曲线。以下是用于格式化新数据框架以计算 ROC、截止点和 AUC 的所有代码。

**#Formatting y_test and y_predicted probabilities for ROC curve**
y_pred_prob = pd.DataFrame(y_pred_prob)
y_1_prob = y_pred_prob[1]
y_test_1 = y_test.reset_index()
y_test_1 = y_test_1['class (Y)']**#Forming new df for ROC Curve and Accuracy curve**
df = pd.DataFrame({ 'y_test': y_test_1, 'model_probability': y_1_prob})
df = df.sort_values('model_probability')**#Creating 'True Positive', 'False Positive', 'True Negative' and 'False Negative' columns** 
df['tp'] = (df['y_test'] == int(0)).cumsum()
df['fp'] = (df['y_test'] == int(1)).cumsum()
total_0s = df['y_test'].sum()
total_1s = abs(total_0s - len(df))
df['total_1s'] = total_1s
df['total_0s']= total_0s
df['total_instances'] = df['total_1s'] + df['total_0s']
df['tn'] = df['total_0s'] - df['fp']
df['fn'] = df['total_1s'] - df['tp']
df['fp_rate'] = df['fp'] / df['total_0s']
df['tp_rate'] = df['tp'] / df['total_1s']**#Calculating accuracy column**
df['accuracy'] = (df['tp'] + df['tn']) / (df['total_1s'] + df['total_0s'])**#Deleting unnecessary columns**
df.reset_index(inplace = True)
del df['total_1s']
del df['total_0s']
del df['total_instances']
del df['index']
df

为了理解下面的数据框中发生了什么,让我们一行一行地分析它。

  • 索引:这个数据帧是按照 model_probability 排序的,为了方便起见,我重新编制了索引。
  • CA125 和 CEA :蛋白质水平原始检测数据。
  • model_probability :该列来自我们的训练数据的逻辑模型,该模型基于输入的测试蛋白质水平输出其被分类为“1”(癌性)的概率预测。第一行是最不可能被归类为癌症的实例,其 CA125 水平高而 CEA 水平低。
  • y_test :我们用来检查模型性能的测试数据的实际分类。

其余的专栏仅仅基于“y_test”,而不是我们的模型的预测。把这些值想象成它们自己的混淆矩阵。这些将有助于我们确定以后的最佳分界点。

  • tp(真阳性):该列从 0 开始。如果 y_test 为‘0’(良性),则该值增加 1。它是所有潜在真阳性的累积追踪器。第一行就是一个例子。
  • fp(假阳性):该列从 0 开始。如果 y_test 为‘1’(癌变),则该值增加 1。它是所有潜在误报的累积跟踪器。第四行就是一个例子。
  • tn(真阴性):该列从 32 开始(测试集中 1 的总数)。如果 y_test 为‘1’(癌变),则该值减少 1。它是所有潜在真阴性的累积追踪器。第四行就是一个例子。
  • fn(假阴性):该列从 45 开始(测试集中 0 的总数)。如果 y_test 为‘0’(良性),则该值减少 1。它是所有潜在假阴性的累积跟踪器。第四行就是一个例子。
  • FP _ Rate(False Positive Rate):这是通过获取行的误报计数并除以总的阳性数(在我们的例子中是 45)计算出来的。它让我们知道我们可以通过在该行设置分界点来分类的假阳性的数量。我们希望尽可能降低成本。
  • tp_rate(真阳性率):也称为敏感度,计算方法是获取行的真阳性计数,然后除以阳性总数。它让我们知道我们可以通过在那一行设置分界点来分类的真阳性的数量。我们希望尽可能地保持高水平。
  • 准确性:真阳性和真阴性的总和除以总实例数(在我们的例子中是 77)。我们一行一行地计算潜在的精确度,基于我们混淆矩阵的可能性。

Figure 15

我粘贴了整个数据帧,因为它值得研究一段时间,并从所有移动的片段中找出意义。看完之后,试着找出最高的准确率。如果您可以找到它,您可以将其与相应的 model_probability 进行匹配,以发现我们数据的最佳分界点。

**#Plotting**
plt.plot(df['model_probability'],df['accuracy'], color = 'c')
plt.xlabel('Model Probability')
plt.ylabel('Accuracy')
plt.title('Optimal Cutoff')**#Arrow and Star**
plt.plot(0.535612, 0.753247, 'r*')
ax = plt.axes()
ax.arrow(0.41, 0.625, 0.1, 0.1, head_width=0.01, head_length=0.01, fc='k', ec='k')
plt.show()

Figure 16

模型概率为 54%,其中精确度最高为 75%。这可能看起来违背直觉,但这意味着如果我们在将患者归类为癌症患者时使用 54%而不是 50%,这实际上会更准确。如果我们想最大限度地提高准确性,我们应该将阈值设置为 54%,但是,由于癌症的极端性质,将阈值降低到 50%以下可能是明智的,以确保无论如何都可以检查出可能患有癌症的患者。换句话说,当涉及到癌症时,假阳性比假阴性更重要!

最后,让我们绘制 ROC 曲线并找出 AUC:

**#Calculating AUC**
AUC = 1-(np.trapz(df[‘fp_rate’], df[‘tp_rate’]))**#Plotting ROC/AUC graph**
plt.plot(df[‘fp_rate’], df[‘tp_rate’], color = ‘k’, label=’ROC Curve (AUC = %0.2f)’ % AUC)**#Plotting AUC=0.5 red line**
plt.plot([0, 1], [0, 1],’r — ‘)
plt.xlabel(‘False Positive Rate’)
plt.ylabel(‘True Positive Rate (Sensitivity)’)
plt.title(‘Receiver operating characteristic’)
plt.legend(loc=”lower right”)

Figure 17

黑色的 ROC 曲线显示了我们的测试数据的真阳性率和假阳性率之间的权衡。穿过图表中心的红色虚线是为了提供一种最坏可能模型看起来像 ROC 曲线的感觉。

ROC 线越靠近左上方,我们的模型越有预测性。它越像红色虚线,预测性就越低。

这就是曲线下面积(AUC)的由来。AUC,顾名思义,是 ROC 曲线下的空间面积。直观上,这个值越接近 1,我们的分类模型就越好。虚线的 AUC 是 0.5。完美模型的 AUC 应该是 1。我们的 AUC 为 0.82,相当不错。

如果您觉得这很有帮助,请订阅。

我的其他文章,如果你想了解更多:

点击了解多项式回归

点击了解有关 rsquared 的信息:

用逻辑回归、knn、梯度推进和分子指纹法预测环境致癌物

原文:https://towardsdatascience.com/predicting-carcinogens-with-logistic-regression-knn-gradient-boosting-and-molecular-e7952294a08c?source=collection_archive---------42-----------------------

平衡不平衡的数据,探索准确性度量,以及化学信息学导论

导言和背景

Tox 21 是什么?

T21 世纪毒理学计划(Tox21)是几个联邦机构之间的独特合作,旨在开发快速测试物质是否对人类健康产生不利影响的新方法。Tox21 中检测的物质包括各种产品,如:商业化学品、杀虫剂、食品添加剂/污染物和医用化合物。

为什么是 p53 蛋白?

p53 基因编码一种同名的蛋白质,被称为肿瘤抑制蛋白。当细胞经历 DNA 损伤时,p53 蛋白在细胞中表达,这可以将正常细胞转化为癌细胞。为了抵消这种影响,p53 可以导致生长停滞、修复 DNA 或开始细胞死亡过程。因此,当 DNA 损伤发生时,p53 表达显著增加。这种蛋白质表达的增加是不规则细胞健康的良好指标。Tox21 数据是通过测试在 p53 细胞机制控制下产生荧光报告基因产物的细胞系产生的。通过测量针对各种化合物的报告基因产物的水平,研究人员能够确定化合物是否是 p53 途径的激动剂(激活剂)。

使用分子指纹预测激动剂

指纹是一种将分子结构和性质表示为二进制位串(0 和 1)的方法。这种表示最初被开发并应用于搜索具有特定子结构的分子的数据库,但它也可以应用于机器学习。哈希函数是一个随机数生成器,应用于分子的每个特征,例如存在的键和分子的类型,这意味着它们充当函数的种子。

下图说明了如何通过生成分子指纹来评估两个分子的相似性(使用 Tanimoto 指数)。每个分子首先应用哈希函数,然后根据特征生成指纹。下图中的指纹生成器查看某个焊接距离半径以及该距离内的特征。

[1]

指纹的种类

这个项目将使用四种不同类型的指纹,并比较它们的预测能力。

  • 摩根圆形指纹— 该指纹属于扩展连接指纹(ECFPs)家族,使用摩根算法生成[2,3]。这些指纹通过圆形原子邻域(键半径)来表示分子结构和亚结构的存在。另一个重要的特征是它们能够确定分子功能性的存在与否,这可以在对分子进行分类时进一步帮助区分。

  • 类似日光的指纹 —该指纹生成器(使用 RDKit)生成的指纹与使用日光指纹算法生成的指纹相似。一个过于简单的解释:该算法沿着分子内的路径(拓扑路径)散列键[4][5]。下图显示了如何识别和描述焊接路径。

[6]

  • 原子对指纹 —顾名思义,原子对指纹是使用原子对(作为特征)及其拓扑距离构建的。通过首先识别重原子和它们之间的最短距离,生成如下所示的原子对指纹。一对原子中的单个原子的特征(如键的数量)被编码。然后,这些编码特征被转换成位串,并表示为一个数字。然后,这个串联的整数串被传递给哈希函数,哈希函数将其赋值为 1 或 0。

[7]

  • 拓扑扭转指纹— 是通过识别四个非氢原子的键路径而产生的 2D 结构指纹。然后这些四路碎片有了它们的特征,比如计算出的每个原子的 π 电子数、原子类型和非氢分支数。每个原子由 8 位来表征,这 4 个原子将它们的位存储在 32 位数组中。

准备数据

Tox21 数据已经标记了活动(1)和非活动(0)状态,因此除了加载、格式化列名和为每个分子生成 mol 文件(这些文件通常被归类为包含分子数据信息、原子、键、坐标和连接信息的数据文件)之外,在此步骤中实际上没有太多事情要做。

要继续操作,请编辑数据在机器上的路径。我们加载一个 sdf 文件——它是一个结构数据文件,包含化合物的相关数据。我们实际上不需要这些细节,因为它们不会增加 p53 激动剂的分类,因此我们放弃了它们。例如,“公式列”没有给我们任何有用的信息,因为它是经验公式,不包含 SMILES 字符串提供的有价值的结构信息。我们确实需要分子的摩尔表示,所以我们生成了它们。

我们还应该确保数据中没有任何重复。特别是这个数据集,总共有 8629 条记录,只有 6826 条唯一记录,这意味着大约 20%的数据是重复的,需要丢弃。

指纹生成

这是一个使用摩根算法生成指纹的例子。为“mol”列中的每种化合物生成指纹,半径为 2,位长为 2048。还为每个 mol 生成具有相同参数的列表。然后,for 循环遍历该列表,创建一个 numpy 数组,并在名称中添加一个由“np”表示的新列表。当运行分类器时,这些列表将成为我们的 x 变量。

设置 x 和 y 变量:

抽样:班级平衡吗?

当分类标签(在我们的例子中:活动= 1;inactive = 0)是成比例偏斜的,偏差被引入到模型中。例如,如果数据集包含 95%的活性标记,那么模型可能将非活性分子分类为活性分子,简单地说,准确度将反映类别分布。确定阶级不平衡是否是直接的——计算标签的分布。一旦我们确定了少数民族在我们的数据集中有多普遍,我们就可以继续平衡它。

上面的计数图显示了我们的数据中存在不成比例的类分布,比例为 15:1(非活动与活动),这意味着大约 93.84%的类是非活动的。

阶级平衡的方法

有两种方法可以平衡数据:过采样和欠采样。过采样通过使用多种算法之一生成合成数据点来实现平衡。我们今天要用的是 SMOTE(合成少数过采样技术)算法的一个变种。SMOTE 的工作原理是搜索给定点数据的 k-最近邻,然后沿着邻点之间的线生成数据点。由于这些点落在真实数据点之间的线上,所以存在一些相关性,这就是为什么我们将使用 ADASYN 算法(自适应合成)。ADASYN 的工作方式基本上与 SMOTE 相同,但是它向点添加随机生成的小值,以增加方差并模拟更真实的数据。

  • 注意 —我选择不使用欠采样,因为它通过从多数类中移除数据点来平衡类,因此有数据损失,可能影响分类器的区分能力。

[9]

应用 ADASYN 算法:

在这里,我们将 ADASYN 算法应用于每个 x 变量,这将最终导致每个指纹的不同少数类计数,但范围很窄,计数的变化很小,所以这不会造成问题。

我们可以绘制一个重新采样的分布图来直观地表示类别平衡:

创建培训、测试和验证测试集

为了训练、测试和再次测试,我们需要创建三个独立的数据分区。训练数据将用于训练我们的模型——本质上,模型将使用该数据来学习哪些指纹(2048 位模式)可能属于 p53 激动剂。测试集将用于评估我们模型的准确性,最后一个验证集将用作无偏数据集来测试我们模型的预测能力。

我们的数据将被分成 85/15 训练/测试集,然后训练数据将被进一步分成 85/15 训练/验证集。

逻辑回归

逻辑回归算法根据分类反应(结果)变量与预测特征的关系,将其分类为 1 到 0。相反,线性回归输出的响应变量是连续的,可以是任何实数。最重要的是,线性回归不输出概率,而是拟合最佳超平面。因此,逻辑回归是像我们这样的二元分类问题的自然选择。

交叉验证和模型拟合

交叉验证是一种统计技术,用于降低数据过度拟合的风险。当分类器记忆数据并拟合噪声和误差而不是变量之间的潜在关系时,发生过拟合。如果模型过于复杂,并且用于训练的数据有限,就会出现这种情况。我们的模型似乎没有这两个限制,但无论如何,执行交叉验证是一个好的实践。

上面的图片展示了一个模型是如何“记忆”和拟合数据的。下图显示了交叉验证是如何创建 10 个折叠的,每个折叠都被分成测试集和训练集以适应模型,然后对每个折叠的误差进行平均。

Over-fitting (source) | cross validation (source)

以下代码行显示了如何执行逻辑回归,获得每个折叠的交叉验证分数(只需对训练数据进行一次迭代,以使用选择的分类器估计分数)。然后,我们在执行 k-fold (10)交叉验证的同时,对 1000 次迭代的逻辑回归进行建模。

对于其余的指纹,我们可以简单地编写一个函数来模拟数据:

模型度量和预测

测试集预测和混淆矩阵

混淆矩阵是描述训练模型对测试数据的性能的表格。矩阵由四个象限组成:

  • 真阳性(TP): 这些是预测为阳性(活性 p53 激动剂)并且实际上是阳性的化合物。
  • 真阴性(TN): 这些化合物被预测是无活性的,实际上是无活性的。
  • 假阳性(FP): 预测化合物有活性但实际上没有活性的情况(I 型错误)。
  • 假阴性(FN): 预测化合物无活性但实际上有活性的情况(II 型错误)

我们可以绘制测试集和验证集预测的混淆矩阵以及准确性,准确性通过(TP+TN)/总案例来确定。左边的混淆矩阵是测试集预测的混淆矩阵,右边的是验证集的混淆矩阵。

k-最近邻

k-最近邻算法(knn)取一个数据点并查看 k 个最近的数据点(k =7 意味着 7 个最近的),然后根据 k 个最近点的多数标签将该数据点标记为 0 或 1。例如,如果给定一个 k=7 的数据点,并且最接近的点恰好是五个 0 和两个 1,那么该点将被分类为 0(无效)。

Knn 在处理较少的特征时表现最佳,从而降低了过度拟合的可能性。正常情况下,我们必须执行 PCA 或其他形式的降维,然而,由于我们使用的是单一的预测特征,我们不需要这样做。

knn

网格搜索和模型拟合

通过交叉验证执行网格搜索,为我们的数据确定最佳邻居数量。我们将测试 9 个候选数字(3、5、7、9、11、13、15、17、19),并执行 5 重交叉验证。然而,这可能需要一段时间,因为 9 个邻居 x 每个邻居的 5 倍验证等于 45 次拟合。k 倍可以减少,这样我们就可以保持一个体面的邻居范围来尝试。

注意:将“n_jobs”设置为-1——这允许在最大 CPU 使用率下进行并行处理

网格搜索建议使用的最佳邻居数量为 3。

为了测试 k 个邻居数的范围,我们可以用不同的 k 值来拟合不同的指纹数据。使用原子对训练数据,我测试了 k = 3,5,7,9,这按顺序产生了以下准确度分数:

0.846
0.846
0.815
0.785

看起来 3 和 5 有相同的分数,但是 3 可能是因为计算时间较少而被选中的。最后,用 k=3 训练模型,首先计算测试数据的准确度,然后计算验证数据的准确度

梯度增强

到目前为止,我们已经为每个分类器的训练数据拟合了单个模型。梯度增强将许多单独的模型结合起来,创建一个精确的模型,这就是所谓的集合。增强是用于创建合奏的方法。它通过将数据与初始模型拟合来工作,然后建立后续模型,该模型用于准确预测初始模型未正确分类的标签。本质上,每个后续模型都致力于最小化前一个模型的预测误差,这导致预测误差的总体降低。因此,最终模型做出的预测是所有先前模型计算的预测的加权和的结果。

By Alex Rogozhnikov (source)

网格搜索和模型训练,以及度量

像我们之前做的那样,执行网格搜索来调整参数:

检查测试和验证数据的准确性

AUC 值和 F1 分数

罗马纪元

曲线下面积(AUC)值是评估模型在二元分类任务上的性能的另一种度量,并且是最广泛使用的评估指标之一。计算 AUC 需要给定模型的两个属性——敏感性和特异性。敏感度是正确分类的阳性数据点与所有分类为阳性的数据点的比例。敏感度是被正确分类为阴性的数据点与被分类为阴性的所有数据点的比例。

  1. 一个模型的灵敏度:真阳性/(假阴性+真阳性)。
  2. 模型的特异性:真阴性/(假阳性+真阴性)

当真阳性率相对于假阳性率作图时,可以计算该曲线的 AUC,并从本质上对模型区分两类的能力进行评分。较高的 AUC 意味着归类为 p53 激动剂的分子确实更有可能是激动剂。

f1-分数

模型的 f1 分数是使用回忆(像敏感度一样计算)和精确度计算的,它是以下各项的比率:

  • 真阳性/(假阳性+真阳性)

F1 分数可通过以下方式获得:

F1 分数在模型召回率(较少的预测点被证明是假阴性)和精确度(较少的预测点被证明是假阳性)之间找到了平衡。

结果和结论

下表显示了使用四个指纹时每个分类器的预测能力。包括的指标有训练、测试和验证数据集的准确度分数、验证数据的 AUC 分数以及验证数据的 f1 分数。对于每个指纹和分类器,最高分以绿色突出显示。

哪个是最好的分类器?

就验证准确性而言,逻辑回归得分一直最高。接下来,当评估 AUC(可以说是最重要的指标)时,梯度增强产生了最高的分数,但是逻辑回归具有最高的平均分数。最后,最高的个体和平均 f1 分数属于逻辑回归,表明它产生的模型在精度和稳健性之间达到了最大的平衡。

赢家:逻辑回归

哪个指纹最好?

文献表明,尽管拓扑扭曲可能不如日光样指纹和摩根指纹那样受欢迎,但与其他指纹相比,它们表现出更高的性能[8]。

结果显示,日光样指纹在逻辑回归和 knn 中都具有最高的 AUC 得分——当使用梯度增强时,原子对指纹得分仅略微高一些。然而,当考虑梯度增强时,原子对指纹在验证准确性、AUC 和 F1 分数方面超过了日光样指纹。

获胜者:日光般的指纹

可能的问题

由于相似的分子亚结构,指纹中有时会发生图案之间的意外碰撞。当相同的位由多个模式设置时,会发生这种情况(将两个相似结构的特定特征散列为相同的位)。例如,拓扑扭转指纹产生了八个相同的指纹,这可以通过增加位长(从 2048 到 4096)来避免。

参考

[1] Lo,Y.-C .,Rensi,S. E .,Torng,w .,& Altman,R. B. (2018 年)。化学信息学和药物发现中的机器学习。今日药物发现,23(8),1538–1546。https://doi.org/10.1016/j.drudis.2018.05.010

[2] Morgan,H. L.《化学结构的独特机器描述的产生——化学文摘服务处开发的一种技术》。化学博士。医生。1965, 5: 107–112.

[3]罗杰斯,d;扩展连接指纹。化学博士。Inf。模型。2010, 50(5): 742–754.

[4]https://www.rdkit.org/docs/GettingStartedInPython.html

[5]https://www . daylight . com/day html/doc/theory/theory . finger . html

[6]https://docs . eyes open . com/toolkits/python/graphsimtk/fingerprint . html

[7] Jelínek,j .,koda,p .,& Hoksza,D. (2017 年)。利用氨基酸结构邻域知识库预测蛋白质相互作用位点。BMC 生物信息学,18(S15)。https://doi.org/10.1186/s12859-017-1921-4

[8]斯柯达公司和霍克斯扎公司(2015 年)。拓扑挠指纹探索。2015 年 IEEE 生物信息学和生物医学国际会议(BIBM)。doi:10.1109/bibm。58606.88868688666

[9] Lee,h .,Kim,j .,& Kim,S. (2017 年)。基于高斯的 SMOTE 算法求解偏斜类分布。国际模糊逻辑和智能系统杂志,17(4),229–234。https://doi.org/10.5391/ijfis.2017.17.4.229

预测网站的点击率

原文:https://towardsdatascience.com/predicting-click-through-rate-for-a-website-7cd2a892d26e?source=collection_archive---------4-----------------------

这是一个二元分类项目,根据给定的特征预测用户是否会在网站上申请。

介绍

想象一个用户访问一个网站,并执行工作搜索。从显示的结果集合中,用户点击他/她感兴趣的某些结果,并且在检查工作描述之后,她进一步点击其中的应用按钮以进入应用页面。申请率被定义为申请的分数(在访问职位描述页面后),目标是使用下一节中描述的数据集来预测此指标。

这篇文章将为任何机器学习新手提供完整的指南。我的目标是为您提供一个应用机器学习的端到端蓝图,同时尽可能保持它的可操作性和简洁。

蓝图

  1. 数据收集
  2. 探索性数据分析 —首先,“了解”数据。这一步应该快速、高效、果断。
  3. 数据清理——然后,清理你的数据以避免许多常见的陷阱。更好的数据胜过更好的算法。
  4. 功能工程 —接下来,通过创建新功能来帮助您的算法“专注于”重要的东西。
  5. 算法选择 —选择最佳、最合适的算法,不浪费您的时间
  6. 模型训练和调优 —最后,训练你的模型。一旦你完成了前 5 步,这一步就相当公式化了
  7. 洞察力 —最终将推动业务发展的最终结果

数据收集和问题描述

你可以从这里下载数据。

Dataset

数据集中的每一行都对应于一个职务列表的用户视图。它有 10 列,如下所述。
1。职位接近度 tf idf:衡量查询和职位的接近度。
2。描述接近度 tf idf:衡量查询和工作描述的接近度。
3。主查询 tf idf:与用户查询与职位名称和职位描述的接近程度相关的分数。
4。查询 jl 得分:衡量查询和职务列表对的流行程度。
5。查询职称得分:衡量查询和职称对的受欢迎程度。
6。城市匹配:指明职务列表是否与用户(或用户指定的)位置匹配。7。职务年龄天数:指明已发布职务列表的年龄。8。申请:指明用户是否已申请此职务列表。9。太平洋搜索日期:活动日期。10。类别 id:单击的职位的类别 ID。

这个问题有两个部分。

  1. 我们必须只关注前 7 列,并使用这些功能来预测有多少用户申请该网站。
  2. 我们必须考虑将最后一列添加到特征集(“类 id”),并检查分类性能是否提高。

探索性数据分析

探索性分析的目的是“了解”数据集。预先这样做将使项目的其余部分更加顺利,主要有三个方面:

  1. 您将获得关于数据清理的有价值的提示(这将决定您的模型的成败)。
  2. 你会想到特征工程的想法(可以让你的模型从好变得更好)。
  3. 您将获得对数据集的“感觉”,这将有助于您交流结果并产生更大的影响。

但是,机器学习的探索性分析应该是快速、高效、果断 …而不是冗长!

不要跳过这一步,但也不要在这一步上卡住。

你看,有无限可能的图、图表和表格,但是你只需要一把来“了解”足够好的数据来使用它。

在这一步中,我们将向您展示为您的投资带来最大收益的可视化效果。

从基础开始

首先,您需要回答一组关于数据集的基本问题:

  • 我有多少观察值?
  • 有多少功能?
  • 我的要素的数据类型是什么?它们是数字吗?绝对的?
  • 我有目标变量吗?

该数据集总共包含 1200890 个观察值,其中 789586 个观察值是在 2018 年 1 月 21 日至 2018 年 1 月 26 日之间收集的,而其余的观察值是在 2018 年 1 月 27 日收集的。总共有 9 个特性和 1 个目标变量(“应用”)。没有明确的特征。

Dataset info

缺少值

接下来,我们可以检查数据集中有多少缺失值。从上表可以清楚地看出,标题邻近 tfidf、描述邻近 tfidf 和城市匹配包含空值。

类别分布

由于该数据集是关于有多少访问网站的客户点击应用,它可能不是很多,我们可以假设这将是一个不平衡的数据。让我们看看我们是否正确。

Data Distribution (1 = Apply, 0 = Not Apply)

我们可以看到,这种分布确实是不平衡的。为了处理这种情况,有各种技术,如欠采样、过采样、SMOTE,使用 AUC 等指标。但这些将在以后讨论。

数据描述

Data Description

以下是该表中的观察结果:

  1. 大多数预测值的 75%瓦片和最大值之间存在显著差异。
  2. ' title_proximity_tfidf ',' description_proximity_tfidf ',' main_query_tfidf ',' query_jl_score ',' query_title_score ',' job_age_days '的中值低于平均值
  3. 因此,观察值 1 和 2 表明数据中存在大量异常值

相互关系

相关性允许您查看数字特征和其他数字特征之间的关系。

相关性是一个介于-1 和 1 之间的值,表示两个要素协调移动的程度。你不需要记住数学来计算它们。只要知道以下直觉:

  1. 相关意味着随着一个特征的增加,另一个特征也增加。例如孩子的年龄和身高。
  2. 相关性意味着一个特征增加,另一个特征减少。例如花在学习上的时间和参加的聚会。
  3. 接近-1 或 1 的相关性表示强关系
  4. 那些接近 0 的表示弱关系。
  5. 0 表示没有关系

Correlation between variables

大多数特征彼此之间并不高度相关。仅有的两个具有某种相关性的特征是主查询 tfidf 和标题接近度 tfidf(大约 0.8)。

极端值

Outliers

探索性数据分析中最重要的步骤之一是异常值检测和处理。机器学习算法对数据点的范围和分布非常敏感。数据异常值会欺骗训练过程,导致训练时间更长,模型更不准确。离群值被定义为与剩余数据显著不同的样本。这些点位于分布的整体模式之外。均值、方差和相关性等统计指标很容易受到异常值的影响。

异常值的性质:

由于以下原因之一,数据集中可能会出现异常值,

  1. 数据集中真正的极高值和极低值
  2. 由于人为或机械错误而引入
  3. 通过替换缺失值引入

异常值检测

  • 极值分析
  • z 分数法
  • k 表示基于聚类的方法
  • 可视化数据
  • 箱线图

异常值处理

  • 均值/中值或随机插补
  • 整理
  • 顶部、底部和零编码
  • [数]离散化

然而,在本文中,我将使用箱线图方法检测异常值。如果您想深入了解如何检测和处理异常值,请参考这篇文章。箱线图也称为晶须图,是一种图形方法,通常用四分位数和四分位数间距来描述,有助于定义上限和下限,超出上限和下限的任何数据都将被视为异常值。

简而言之,分位数是分布中与该分布中值的等级顺序相关的点。对于给定的样本,您可以通过对样本进行排序来找到任何分位数。排序样本的中间值是中间分位数或第 50 个百分位数(也称为样本的中位数)。

下面是我们数据集的箱线图。

如您所见,数据集中有许多异常值。删除它们可能会导致重要信息的丢失。

数据清理

数据清理是每个人都在做但没有人真正谈论的事情之一。当然,这不是机器学习中“最性感”的部分。不,没有隐藏的技巧和秘密要揭开。

然而,适当的数据清理可以成就或破坏您的项目。专业数据科学家通常会在这一步花费大量时间。

为什么?因为机器学习中一个简单的道理:

更好的数据胜过更好的算法。

换句话说……垃圾进来,垃圾出去。即使你忘记了本课程的其他内容,请记住这一点。

事实上,如果您有一个适当清理的数据集,即使简单的算法也可以从数据中获得令人印象深刻的见解!

显然,不同类型的数据需要不同类型的清理。然而,本文中介绍的系统化方法总是可以作为一个很好的起点。

移除不需要的观察

数据清理的第一步是从数据集中移除不需要的观测值。这包括重复的不相关的观察值。我们的数据集包含相当多的重复条目,这些条目将被删除。

处理缺失值

在应用机器学习中,缺失数据是一个看似棘手的问题。

首先,明确一点, 你不能简单地忽略数据集中的缺失值。你必须以某种方式处理它们,因为大多数算法不接受缺失值。

“常识”在这里是不理智的

以下是处理缺失数据的最常用方法:

  1. 删除有缺失值的观察值
  2. 输入基于其他观察的缺失值
  3. 插值和外推
  4. 使用 KNN
  5. 均值/中位数插补
  6. 回归插补
  7. 随机回归插补
  8. 热卡插补

如果你想更详细地了解他们,请参考这篇文章。在我们的例子中,两个特征(标题邻近 tfidf 和描述邻近 tfidf)主要包含 0,因此我将用 0 替换缺少的值。对于城市匹配特征,1 和 0 分布几乎相等。在这里,我可以选择删除包含 null 的值,或者使用 mean 替换。我已经删除了这种情况下的值。

特征工程

特征工程是关于从现有的输入特征中创建新的输入特征

一般来说,你可以把数据清理看成是一个减法的过程,把特征工程看成是一个加法的过程。

这通常是数据科学家为提高模型性能所能做的最有价值的任务之一,原因有三:

  1. 您可以隔离并突出显示关键信息,这有助于您的算法“专注于”重要的内容。
  2. 可以自带 领域专长
  3. 最重要的是,一旦你理解了特性工程的“词汇表”,你就可以引入其他人的领域专长!

以下是我们可以执行特征工程的一些方法,但是请注意,这并不是所有特征工程的详尽概要,因为这个步骤有无限的可能性。好消息是,随着你获得更多的经验,这项技能自然会提高。

  1. 注入领域知识
  2. 创建交互式功能
  3. 组合稀疏类
  4. 添加虚拟变量
  5. 移除未使用的功能

在我们的例子中,由于没有太多关于数据集的领域知识,我们在特征工程的应用中受到限制。我应用的唯一特征工程是将两个相关的特征(title_proximity_tfid 和 main_query_tfidf)相乘,以创建一个名为 main title tfidf 的新列。

算法选择

影响模型选择的一些因素有:

  1. 模型是否满足业务目标
  2. 模型需要多少预处理
  3. 模型有多精确
  4. 这个模型的解释力有多强
  5. 模型有多快:建立模型需要多长时间,模型做预测需要多长时间。
  6. 模型的可伸缩性如何

影响算法选择的一个重要标准是模型复杂度。一般来说,较复杂的模型是:

  1. 它依赖于更多的特征来学习和预测(例如,使用两个特征对十个特征来预测目标)
  2. 它依赖于更复杂的特征工程(例如,使用多项式、相互作用或主成分)
  3. 它有更多的计算开销(例如,单个决策树与 100 棵树的随机森林相比)。

除此之外,基于参数的数量或一些超参数的选择,相同的机器学习算法可以变得更加复杂。举个例子,

  1. 一个回归模型可以有更多的特征,或者多项式项和交互项。
  2. 决策树可以有或多或少的深度。

使相同的算法更复杂会增加过度拟合的机会。

用于分类的常用机器学习算法

逻辑回归

逻辑回归模型符合一条“直线”。实际上,他们很少表现良好。对于大多数机器学习问题,我们实际上建议跳过它们。

它们的主要优点是易于解释和理解。但是,我们的目标不是研究数据,写研究报告。我们的目标是建立一个可以做出准确预测的模型。

在这方面,逻辑回归有两个主要缺陷:

  1. 输入特征多了容易过拟合。
  2. 它不能轻易表达非线性关系。

正规化

如上所述,逻辑回归遭受过拟合和处理非线性关系的困难。正则化是一种通过人为惩罚模型系数来防止过度拟合的技术。

  • 它可以阻止大系数(通过抑制它们)。
  • 它还可以完全移除要素(通过将它们的系数设置为 0)。
  • 惩罚的“力度”是 可调 。(明天会有更多相关内容……)

正则化的类型有套索(L1)、脊(L2)和弹性网(脊和套索的折衷)

决策树

决策树将数据建模为分层分支的“树”。它们分支,直到到达代表预测的“叶子”。由于它们的分支结构,决策树可以很容易地模拟非线性关系。

不幸的是,决策树也有一个重大缺陷。如果你允许他们无限制地增长,他们可以完全“记住”训练数据,只是从创建越来越多的分支开始。因此,单个无约束决策树非常容易过度拟合。​

那么,我们如何利用决策树的灵活性,同时防止它们过度拟合训练数据呢?

树系综

集成是用于组合来自多个独立模型的预测的机器学习方法。有几种不同的组装方法,但最常用的两种是:

  1. 装袋:试图减少复杂模型过度拟合的机会

Bagging

  • 它并行培养了大量的“强”学习者。
  • 一个 强学习者 是一个相对无约束的模型。
  • Bagging 然后将所有强学习者结合在一起,以便“平滑”他们的预测。
  • 常用的技术是随机森林

2.助推:试图提高简单模型的预测灵活性

Boosting

  • 它按顺序训练出大量的“弱”学习者。
  • 一个 弱学习器 是一个约束模型(即你可以限制每个决策树的最大深度)。
  • 序列中的每一个都着重于从之前的错误中学习。
  • 然后,Boosting 将所有弱学习者组合成一个强学习者。
  • 常用的技术是 XGBoost 和 LightGBM
  1. LightGBM: Light GBM 是一个使用基于树的学习算法的梯度推进框架。轻型 GBM 垂直生长树而其他算法水平生长树意味着轻型 GBM 逐叶生长而其他算法逐级生长。它会选择 delta 损失最大的叶子来生长。当生长相同的叶子时,逐叶算法可以比逐层算法减少更多的损失。

How LightGBM works

How other boosting algorithm works

还有许多其他算法,如支持向量机、神经网络等。但我们不会在这里接受。

对于我们的例子,我将使用 XGBoost、Random Forest 和 LightGBM。

模型训练和调整

培训模式:2018 年 1 月 21 日至 2018 年 1 月 26 日

测试型号:2018 年 1 月 27 日

我们将使用的指标是 AUC。我们得到的初始 AUC 值

  1. XGBoost: 0.5803
  2. LightGBM: 0.5807
  3. 随机森林:0.5806

使用贝叶斯优化的超参数调整

在几乎所有实际情况下,都需要搜索机器学习模型的参数以获得最佳交叉验证性能,从而获得具有最佳泛化估计的模型。scikit-learn 中的一个标准方法是使用 GridSearchCV 类,它为每个要尝试的参数取一组值,并简单地枚举参数值的所有组合。随着新参数的增加,这种搜索的复杂性呈指数增长。一种更可扩展的方法是使用 RandomizedSearchCV,但是它没有利用搜索空间的结构。

Scikit-optimize 为 GridSearchCV 提供了一个替代方案,它利用贝叶斯优化,其中一个称为“代理”的预测模型用于对搜索空间建模,并用于尽快获得良好的参数值组合。

贝叶斯优化,一种基于模型的寻找函数最小值的方法,最近被应用于机器学习超参数调整,结果表明这种方法可以在测试集上实现更好的性能,同时比随机搜索需要更少的迭代。

在应用贝叶斯优化和交叉验证后,AUC 值:

  1. XGBoost: 0.5819
  2. LightGBM: 0.5819
  3. 随机森林:0.5810

尽管改进并不显著,但贝叶斯优化器能够以更快的速度执行调优操作。

堆垛

堆叠是一种集成学习技术,通过元分类器或元回归器组合多个分类或回归模型。基于完整的训练集来训练基础级模型,然后在基础级模型的输出上训练元模型作为特征。

基础层通常由不同的学习算法组成,因此堆叠集成通常是异构的。在我们的例子中,我将组合 XGBoost 和 LGBoost 输出,并将使用投票分类器,这是 Scikit-learn 中提供的一个包。

如下图所示,叠加两个输出后,AUC 得分从 0.5819 提高到 0.5848。

ROC Graph for XGB, LGB, and Stacking

Insights

  1. 如此低的 AUC 分数 0.5848 可能是由于我们在数据集中没有太多的特征,这使得算法很难正确地对目标变量进行分类。

待办事项:

  1. 包含最后一列(class_id)以改善结果

如果你对代码感兴趣,你可以在这里找到我的笔记本。

参考

[## 利用机器学习检测信用卡欺诈

用数据科学抓坏人

towardsdatascience.com](/detecting-credit-card-fraud-using-machine-learning-a3d83423d3b8) [## 第五章:机器学习中的算法选择-数据科学入门

在本指南中,我们将向您展示如何在众多选项中选择最有效的机器学习算法…

elitedatascience.com](https://elitedatascience.com/algorithm-selection) [## Python 中的自动机器学习超参数调整

Python 中使用贝叶斯优化进行自动超参数调优的完整演练

towardsdatascience.com](/automated-machine-learning-hyperparameter-tuning-in-python-dfda59b72f8a)

使用 PySpark 预测客户流失

原文:https://towardsdatascience.com/predicting-customer-churn-using-pyspark-6a78a78a8412?source=collection_archive---------25-----------------------

数据探索,特征生成、建模和调整——使用 Spark。

Photo by Kaboompics from Pexels

客户流失是当今商业世界的一个主要商业问题,数据科学家正在快速采用工具和技术来有效预测客户流失并实时避免客户流失。

Apache Spark,尤其是 PySpark,是执行探索性分析和机器学习来解决这一分类问题的最快工具之一。

在本文中,主要目标是探索用户活动数据集,并使用 Spark 建立一个预测用户流失的机器学习模型。

喜欢你读的书吗?关注我上LinkedIn

点击获取数据服务产品

数据

数据已经出来了。json 格式由别名为 Sparkify 的音乐公司的日志组成。这是用户与音乐平台交互时捕获的日志。

加载数据

path = “s3n://xxxxxxx-xxxx/sparkify/mini_sparkify_event_data.json”
df = spark.read.json(path)

数据架构:

df.printSchema()

探索性数据分析

在本节中,我们将执行一些数据预处理,以回答一些业务问题。

删除任何空行或缺少 userId 的行

#Drop null rows
df = df.dropna(how = “any”, subset = [“userId”, “sessionId”,”ts”])#Drop rows with missing userId
df = df.filter(df["userId"] != "")

删除任何重复的行

df.select(“userId”).dropDuplicates()

添加流失标签

churn_indicator = udf(lambda c: 1 if c == 'Cancellation Confirmation' else 0, IntegerType())
df = df.withColumn('churn_indication', churn_indicator('page'))#Add churn columns to indicate users who have churned
windowval = Window.partitionBy('userId')
df = df.withColumn('churn', max('churn_indication').over(windowval))

那么我们的客户流失数据框架将会是:

df_churn = df.filter('churn == 1')

下一步,我们将回答以下业务问题:

性别对流失有影响吗?

#Group by users and gender to aggregate count of usersdf_churn_by_gender = df.select(["userId", "gender","churn"]).groupby(["churn", "gender"]).count().sort("churn").toPandas()#Plot a barplotsb.barplot(x='churn', y='count', hue='gender', data=df_churn_by_gender)
plt.title('What is the churn comparison by gender', fontsize= 16);
plt.xlabel('Churn');
plt.ylabel('Number of Users');

从上图可以看出,男性比女性有更多的麻烦,因为女性是这项服务的主要用户。

用户流失多久?

为了回答这个问题,我们添加了一个以天为单位的总时间列,并计算每个用户总天数的最大值。

df = df.withColumn('total_time_days', (df.ts-df.registration)/1000/3600/24)total_time_df = df.select('UserId','churn','total_time_days').groupBy('userId','churn').agg(max('total_time_days').alias('total_time')).toPandas()sb.boxplot(data=total_time_df, x='churn', y='total_time', orient='v');
plt.ylabel('Total days');
plt.xlabel('Churned');
plt.title('After how long do Users churn?');

大多数用户在使用音乐平台的第二个月就会流失

哪一天和哪一小时的客户流失率高?

我们从添加小时和工作日列开始。为了对上述问题有所了解,我们将汇总每天每小时的用户数量,然后以工作日为中心,如下图所示。

#Create an hour and weekday column
calc_hour = udf(lambda t: dt.datetime.fromtimestamp(t / 1000.0).hour)
df_churn = df_churn.withColumn(“ts_hour”, calc_hour(df.ts))calc_weekday = udf(lambda t: dt.datetime.fromtimestamp(t / 1000.0).strftime(“%A”))
df_churn = df_churn.withColumn(“ts_weekday”, calc_weekday(df.ts))df_churn_by_time = df_churn.select(['userId', 'ts_weekday','ts_hour','churn']).groupby(["userId","ts_weekday","ts_hour"]).agg(count(df.userId).alias('count'))#.toPandas()df_churn_by_hr_week = df_churn_by_time.groupBy('ts_hour').pivot('ts_weekday').sum('count').sort('ts_hour')df_churn_by_hr_week = df_churn_by_hr_week.withColumn('ts_hour',df_churn_by_hr_week.ts_hour.cast('int'))
df_churn_by_hr_week = df_churn_by_hr_week.toPandas().set_index('ts_hour').sort_index(axis=0,ascending=True)#Plot a Heat Mapplt.figure(figsize=(16,10))
sb.heatmap(df_churn_by_hr_week, fmt='d',  cmap='viridis_r', annot_kws={"size": 12},  cbar_kws={'label': 'Number of Churns'})
plt.title("Which Hour and Day has High Customer Churn?", y=1, fontsize=18)
plt.xlabel('Day of the week', labelpad=8)
plt.ylabel('Hour (24hr)', labelpad=8)
plt.yticks(rotation=360);

特色工程

让我们开始构建一些有希望的特性,我们将使用它们来训练我们的模型

  1. 性别特征。男性或女性
gender_ft = df.select("userId", "gender").dropDuplicates().replace(['M', 'F'], ['0', '1'], 'gender').select('userId', col('gender').cast('int'))

2.订阅级别—付费或免费

level_ft = df.select("userId", "level").dropDuplicates().replace(['free', 'paid'], ['0', '1'], 'level').select('userId', col('level').cast('int'))

3.用户在平台上的总时间(生命周期)

total_time_ft = df.select(‘UserId’,’total_time_days’).groupBy(‘userId’).agg(max(‘total_time_days’).alias(‘total_time’))

4.用户听过的歌曲总数

total_songs_ft = df.select(‘UserId’,’song’).groupBy(‘userId’).agg(count(‘UserId’).alias(‘total_songs’))

5.平台上每个用户会话的歌曲数量

songs_per_session_ft = df.filter(df.page=="NextSong").groupBy('UserId','sessionId').count().groupBy('userId').agg(avg('count').alias('songs_per_session'))

6.艺术家总数

total_artists_ft = df.filter(df.page==”NextSong”).select(‘UserId’,’artist’).dropDuplicates().groupBy(‘userId’).agg(count(‘UserId’).alias(‘total_artists’))

7.总收听时间

total_listening_ft = df.select(‘UserId’,’length’).groupBy(‘userId’).sum().withColumnRenamed(‘sum(length)’, ‘total_listen_time’)

基于页面导航日志的更多功能

8.添加的好友数量

friends_added_ft = df.filter(df.page==”Add Friend”).groupBy(‘userId’).agg(count(‘UserId’).alias(‘friends_added’))

9.帮助页面访问

help_ft = df.filter(df.page==”Help”).groupBy(‘UserId’).agg(count(‘UserId’).alias(‘help_access’))

10.添加到播放列表的歌曲数量

songs_playlist_ft = df.filter(df.page==”Add to Playlist”).groupBy(‘UserId’).agg(count(‘UserId’).alias(‘songs_on_playlist’))

11.赞数—竖起大拇指页面

likes_ft = df.filter(df.page==”Thumbs Up”).groupBy(‘UserId’).agg(count(‘UserId’).alias(‘likes’))

12.不喜欢的数量—拇指向下页面

dislikes_ft = df.filter(df.page==”Thumbs Down”).groupBy(‘UserId’).agg(count(‘UserId’).alias(‘dislikes’))

最后,

13.流失标签

churn_label_ft = df.select(‘UserId’, col(‘churn’).alias(‘label’)).dropDuplicates()

最后,我们将使用外部连接将我们的特性连接到一个数据帧中,清除一些空值并删除不必要的 UserId 列

#combine all datasources into a single data frame
feature_df = gender_ft.join(level_ft,’userID’,’outer’) \
 .join(total_time_ft,’userID’,’outer’) \
 .join(total_songs_ft,’userID’,’outer’) \
 .join(total_artists_ft,’userID’,’outer’) \
 .join(songs_per_session_ft,’userID’,’outer’) \
 .join(total_listening_ft,’userID’,’outer’) \
 .join(friends_added_ft,’userID’,’outer’) \
 .join(help_ft,’userID’,’outer’) \
 .join(songs_playlist_ft,’userID’,’outer’) \
 .join(likes_ft,’userID’,’outer’) \
 .join(dislikes_ft,’userID’,’outer’) \
 .join(churn_label_ft,’userID’,’outer’)#Drop unnecessary userid column and fill null valuesfeature_df = feature_df.drop(‘userID’).fillna(0)feature_df.show(5)

建模

我们将首先使用 VestorAssembler(一种将多个列合并为一个向量列的要素转换器)和 StandardScaler(通过移除平均值并缩放至单位方差来标准化要素)对要素数据集进行矢量化和标准化

#Vectorize features
cols = [‘gender’, ‘level’, ‘total_time’, ‘total_songs’, ‘total_artists’, ‘songs_per_session’, ‘total_listen_time’, ‘friends_added’, ‘help_access’, ‘songs_on_playlist’, ‘likes’, ‘dislikes’]
assembler = VectorAssembler(inputCols=cols, outputCol=”vfeatures”)
data = assembler.transform(feature_df)# standardize features
scaler = StandardScaler(inputCol="vfeatures", outputCol="features", withStd=True)
scalerModel = scaler.fit(data)
data = scalerModel.transform(data)

将数据集分为训练、测试、验证和测试,如下所示:

# train test split
train, rest = data.randomSplit([0.6, 0.4], seed=42)
validation, test = rest.randomSplit([0.5, 0.5], seed=42)

下一步包括比较模型的性能并选择性能最佳的模型。我们将使用默认参数训练模型,并对最佳模型进行改进。最佳模型将用于扩展到更大的数据集。

  1. 逻辑回归
#estimator
lr = LogisticRegression(maxIter=10)
#evaluator
mce_f1_evaluator = MulticlassClassificationEvaluator(metricName=’f1')# build an empty paramGrid for now
paramGrid = ParamGridBuilder().build()lr_cv = CrossValidator(estimator=lr,
 evaluator=mce_f1_evaluator, 
 estimatorParamMaps=paramGrid,
 numFolds=3)#Train the model
lr_cv_model = lr_cv.fit(train)
#fit the model
svm_results = svm_model.transform(validation)#Evaluate using f1 score
evaluator = MulticlassClassificationEvaluator(predictionCol = "prediction")
print('F1 Score:{}'.format(evaluator.evaluate(svm_results, {evaluator.metricName: "f1"})))

2.支持向量机

#estimator
svm = LinearSVC(maxIter=10)
#evluator
mce_f1_evaluator = MulticlassClassificationEvaluator(metricName=’f1')
# build an empty paramGrid for now
paramGrid = ParamGridBuilder().build()svm_cv = CrossValidator(estimator=svm,
 estimatorParamMaps=paramGrid,
 evaluator=mce_f1_evaluator,
 numFolds=3)#train
svm_model = svm_cv.fit(train)
#fit
svm_results = svm_model.transform(validation)
#evaluate
evaluator = MulticlassClassificationEvaluator(predictionCol = "prediction")
print('F1 Score:{}'.format(evaluator.evaluate(svm_results, {evaluator.metricName: "f1"})))

3.随机森林分类器

#classifier
rf = RandomForestClassifier()#evaluator
mce_f1_evaluator = MulticlassClassificationEvaluator(metricName=’f1')# build an empty paramGrid for now
paramGrid = ParamGridBuilder().build()rf_cv = CrossValidator(estimator=rf,
 estimatorParamMaps=paramGrid,
 evaluator=mce_f1_evaluator,
 numFolds=3)#train
rf_model = rf_cv.fit(train)#validate
rf_results = rf_model.transform(validation)#evaluate
evaluator = MulticlassClassificationEvaluator(predictionCol = "prediction")
print('F1 Score:{}'.format(evaluator.evaluate(rf_results, {evaluator.metricName: "f1"})))

4.梯度推进树

#classifier
gbt = GBTClassifier(maxIter=10,seed=42)#evaluator
mce_f1_evaluator = MulticlassClassificationEvaluator(metricName=’f1')# build an empty paramGrid for now
paramGrid = ParamGridBuilder().build()gbt_cv = CrossValidator(estimator=gbt,
 estimatorParamMaps=paramGrid,
 evaluator=mce_f1_evaluator,
 numFolds=3)#train
gbt_model = gbt_cv.fit(train)#validate
gbt_results = gb.model.transform(validation)#evaluate
evaluator = MulticlassClassificationEvaluator(predictionCol = "prediction")
print('F1 Score:{}'.format(evaluator.evaluate(gbt_results, {evaluator.metricName: "f1"})))

4 个分类器的结果如下:

Classifier Results

梯度提升树是迄今为止最好的评分模型,F1 得分为 0.9,准确度得分为 0.9,其次是随机森林分类器。我们还可以观察其他指标,比如模型训练所需的时间。可以说,我们希望提供最好的结果,因此基于准确性和 f1 分数,我们可以选择我们在这种情况下的最佳模型。

超参数调谐

在这最后一部分,我们希望通过将超参数添加到梯度推进树和随机森林分类器模型来调整我们的 2 个最佳模型

渐变提升树

以下是默认参数

{'seed': 42, 'predictionCol': 'prediction', 'labelCol': 'label', 'featuresCol': 'features', 'maxDepth': 5, 'maxBins': 32, 'minInstancesPerNode': 1, 'minInfoGain': 0.0, 'maxMemoryInMB': 256, 'cacheNodeIds': False, 'checkpointInterval': 10, 'lossType': 'logistic', 'maxIter': 10, 'stepSize': 0.1, 'subsamplingRate': 1.0, 'featureSubsetStrategy': 'all'}

用参数建立模型

gbt = GBTClassifier(maxIter=10,seed=42)# build param Grid
paramGrid_gbt = ParamGridBuilder() \
    .addGrid(gbt.maxIter,[5, 10,15]) \
    .addGrid(gbt.maxDepth,[2,3,4,5,6,7,8]) \
    .build()
# set evaluator
mce_f1_evaluator = MulticlassClassificationEvaluator(metricName='f1')gbt_hpt_cv = CrossValidator(estimator=gbt,
                          estimatorParamMaps=paramGrid_gbt,
                          evaluator=mce_f1_evaluator,
                          numFolds=3)gbt_hpt_model = gbt_hpt_cv.fit(train)#Test the model using test data
gbt_model_results = gbt_hpt_model.transform(test)

随机森林分类器

#Default parameters
{'probabilityCol': 'probability', 'rawPredictionCol': 'rawPrediction', 'seed': -5387697053847413545, 'predictionCol': 'prediction', 'labelCol': 'label', 'featuresCol': 'features', 'maxDepth': 5, 'maxBins': 32, 'minInstancesPerNode': 1, 'minInfoGain': 0.0, 'maxMemoryInMB': 256, 'cacheNodeIds': False, 'checkpointInterval': 10, 'impurity': 'gini', 'numTrees': 20, 'featureSubsetStrategy': 'auto', 'subsamplingRate': 1.0}

用参数建立模型

#classifier
rf = RandomForestClassifier()#evaluator
mce_f1_evaluator = MulticlassClassificationEvaluator(metricName=’f1')# build an empty paramGrid for now
paramGrid = ParamGridBuilder() \
 .addGrid(rf.impurity,[‘entropy’, ‘gini’]) \
 .addGrid(rf.maxDepth,[2,3,4,5,6,7,8]) \
 .build()rf_cv = CrossValidator(estimator=rf,
 estimatorParamMaps=paramGrid,
 evaluator=mce_f1_evaluator,
 numFolds=3)
#train model
rf_model = rf_cv.fit(train)
#Test using test data set
rf_model_results = rf_model.transform(test)

在测试数据上测试两个调整的模型之后,随机森林分类器具有体面的 0.85 准确度分数0.84 f1 分数。

梯度提升以 0.78 的准确度分数和 0.78 的 f1 分数位居第二

与梯度相比,随机森林分类器在参数调整后的测试数据集上得分最高。当把时间作为一个衡量标准时,训练也需要更少的时间。因此,在 4 种算法中,随机森林是最健壮的

结论

我首先加载数据,删除丢失的值,删除重复的行,为数据探索创建额外的列,并使用可视化工具回答了一些业务问题。然后开始研究特征生成和建模。该项目的最有趣和最困难的部分是 spark ML 的实现,使用 PySpark 预处理和特征工程数据;考虑到这是第一次使用这个工具。能够在云上部署 ML 解决方案(在本例中是 AWS EMR 服务)是一个很好的技能补充。

改进

另一个具有挑战性的部分是特征生成。虽然我只能想出 12 个功能,但他们有可能创造更多。因此,有了更大的数据集和更多的特征,该模型在预测客户流失方面会更加准确和稳健

该模型也可以作为 API 部署,在业务环境中使用。

更多细节请查看我的 Github 库

Keras 中用神经网络预测客户流失

原文:https://towardsdatascience.com/predicting-customer-churn-with-neural-networks-in-keras-f904c7113fcd?source=collection_archive---------12-----------------------

为什么要预测客户流失?

这对于任何地方的组织都是一个大问题,也是我们看到机器学习高采用率的主要领域之一,这可能是因为我们正在预测客户行为

“流失”一词用于描述客户停止使用某个组织的服务。对于订阅服务来说,这是一个非常强大的 KPI,订阅服务的大部分收入来自重复支付(通常是每月支付)。

深度学习用例—网飞

网飞是订阅公司的一个很好的例子,他们也是一个接近技术前沿的组织。网飞使用深度学习技术来预测客户是否会在实际离开之前离开,这意味着他们可以采取预防措施来确保他们留下来。

他们是怎么做到的?

不用深入兔子洞,开始吧…

网飞收集了很多关于个人的数据,你看了什么,你什么时候看的,你喜欢和不喜欢的一切等等。他们可以将这些数据与深度学习分类技术结合使用,计算出他们认为客户何时会离开。简单的解释可能是这样的“如果一个客户 N 天没看任何东西,那么他们很快就会流失”。

使用神经网络分析所有收集到的数据将使组织能够根据他们现有的数据建立他们客户的档案。一旦一群用户被分类,网飞可以决定采取什么行动,如果一个客户是一个可疑的流失。例如,他们可以了解他们想要留住哪些客户,并为这些人提供折扣和促销,然后还可以确定哪些客户是“注定失败的”,可以让他们离开。

所以,这个实验…

IBM 电信数据集已经在互联网上流传了一年多,所以我想现在是尝试使用它来预测客户流失的好时机。

我需要从别处借用一些代码。我直接从这篇博文中摘录了数据准备代码。这让我可以花更多的时间调整模型,并(试图)获得更高的精确度。

导入必要的库

这是非常简单的,它几乎是一个直接的复制/粘贴到您自己的笔记本上的工作。在网上搜索时,我发现一大堆人在分发导入了错误库的代码。这里要注意的主要事情是我导入 Keras 模块的第二个块。

很多帖子(我假设都来自同一个帖子)混淆了导入 Keras 库和 TensorFlow 库。一个好的经验法则是确保你是:

  • 只进口 Keras 的东西:他们会从“Keras”开始。例如,从 keras.models 导入…
  • 仅导入 TensorFlow Keras 库,这些库以 TensorFlow.Keras 开头,例如来自 TensorFlow。Keras.models 导入…
import numpy as np 
import pandas as pd 
from matplotlib import pyplot as plt 
from sklearn.model_selection import train_test_split 
import keras 
from keras.models import Sequential 
from keras.layers import InputLayer 
from keras.layers import Dense 
from keras.layers import Dropout 
from keras.constraints import maxnorm

读取 pandas 数据框架中的测试数据(通过单击顶部的 IBM 数据集链接来获取 CSV)。

data = pd.read_csv('churn.csv')

数据预处理

一旦我们在 Pandas DF 中有了数据,我们就可以使用窃取的预处理代码将数据转换成针对神经网络优化的格式。本质上,我们正在做以下事情:

  • 将分类变量转换为数值变量(例如,是/否转换为 0/1)。
  • 正确设置列格式,使它们都是数字格式
  • 填充任何空值。
data.SeniorCitizen.replace([0, 1], ["No", "Yes"], inplace= True) data.TotalCharges.replace([" "], ["0"], inplace= True) data.TotalCharges = data.TotalCharges.astype(float) data.drop("customerID", axis= 1, inplace= True) data.Churn.replace(["Yes", "No"], [1, 0], inplace= True)

一旦我们有了一个干净的数据集,我们就可以使用 pandas get_dummies 功能将所有分类列替换为‘dummy’数字‘indicator’列。类似于上面的代码所做的,但是这将去掉整个数据集。

data = pd.get_dummies(data)

拆分数据

像任何模型一样,我们应该将数据分成训练和验证(或测试集)。

首先我们把数据集分成 X 和 y。

  • x 包含了我们用来做预测的所有变量。
  • y 只包含结果(无论客户是否翻炒)。
X = data.drop("Churn", axis= 1) y = data.Churn

接下来,我们使用标准的 train_test_split 将数据分成训练和测试(验证)集。

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size= 0.2, random_state= 1234)

现在我们已经准备好数据集,让我们建立一个模型。

构建模型

Keras 的一个伟大之处在于,我们可以非常简单地建立一个基于层的神经网络。看起来有点像这个图。

首先,我们告诉 Keras 我们想要使用什么类型的模型。大多数情况下,你会使用序列模型。

model = Sequential()

接下来让我们首先构建输入层。图中的红色层。这里有几点需要注意。

我们在整个神经网络中使用密集层,我不会深入不同层和神经网络之间的差异,但大多数时候你会使用密集层或 LSTM 层。点击此处了解更多信息。

第一个数字— 16 是节点(或上图中的圆圈)的数量。我们从 16 开始,以后我们可以改变它,看看它如何影响我们的精度。

设置 input_dim 参数很重要,它必须与 X_train 数据集中的列数相匹配。我们可以简单地计算数据集中的列数,然后像我下面所做的那样键入它——但实际上,您会希望使用 X_train.shape[1]来自动计算值。

激活函数——你真的应该在某个时候仔细阅读这些——我可能会专门就这些写另一篇文章,但目前只知道我们的层都使用“Relu ”,除了输出层(我们将在后面讨论)。作为一般的经验法则——“如果你不确定,使用 relu”

model.add(Dense(16, input_dim=46, activation='relu', kernel_constraint=maxnorm(3)))

我们增加了一个辍学层。dropout 层确保我们在每次迭代神经网络时删除设定百分比(在本例中为 0.2%或 20%)的数据。这是可选的,但值得包含在您的代码中,以防止它过度适应。你可以改变不同的辍学率来进行实验,以获得更高的精确度。

model.add(Dropout(rate=0.2))

现在,我们添加了所谓的隐藏层,我们可以拥有尽可能多的隐藏层,但值得考虑的是,使具有大量隐藏层的神经网络工作所需的额外计算能力。

注意到“内核约束”参数了吗?这涉及使“下降”层有效工作所需的权重的缩放。文档中还有更多的信息,但我想你需要知道的是,从本质上讲,神经网络会自动试错数据集中变量的所有不同权重(这就是为什么我们首先使用神经网络),kernel_constraint 参数增加了对这一过程的控制。

model.add(Dense(8, activation='relu', kernel_constraint=maxnorm(3)))

添加另一个脱落层,以避免过度拟合。

model.add(Dropout(rate=0.2))

最后,我们添加一个输出层:这定义了神经网络的最终输出。这里有几件事你需要记住:

  • 我们的第一个参数是数字 1。这是因为我们的 NN 的 out 是一个包含指示符的列,该指示符将指定我们的客户是否会流失。
  • 激活功能不同。对于单个(是/否)分类模型,我们使用“sigmoid ”,有许多不同的功能可用,例如,如果我们正在构建一个对多个结果进行分类的网络(例如将客户分组到特定的组),我们可以使用“softmax”激活功能。
model.add(Dense(1, activation='sigmoid'))

现在我已经描述了每一个单独的层,我将展示挤在一起的整体。

model = Sequential() 
model.add(Dense(16, input_dim=46, activation='relu', kernel_constraint=maxnorm(3)))
model.add(Dropout(rate=0.2)) 
model.add(Dense(8, activation='relu', kernel_constraint=maxnorm(3))) model.add(Dropout(rate=0.2)) model.add(Dense(1, activation='sigmoid'))

编译模型

接下来,我们编译我们的模型(把它们粘在一起,告诉它应该如何工作)。

我们使用编译方法来实现这一点。它需要三个参数:

  • 优化器。这可能是现有优化器的字符串标识符(我们使用“Adam”),可以使用这些标识符,看看哪个是最好的,您也可以将其拆分出来,并手动调整学习速率以受益于更高的准确性,但我们将使用默认值。参见:优化师
  • 损失函数。这是模型试图最小化的目标。由于这是一个单一的分类问题,我们将使用“二元交叉熵”。参见:损失
  • 指标列表。对于任何分类问题,我们都将其设置为“准确性”。
model.compile(loss = "binary_crossentropy", optimizer = 'adam', metrics=['accuracy'])

拟合模型

快速拟合模型,请注意,我们在这里将测试和验证数据集都拟合到了我们的模型中,这样 Keras 将立即告诉我们在这两个数据集上的表现。

这里我们需要注意几个论点。

  • Epochs =这是我们在神经网络中来回移动的次数——更多的 Epochs 意味着更高的准确性,但也意味着更多的处理时间,而且太多的 epochs 会导致过度拟合。您可以随时对此进行调整,所以尝试一些不同的值,看看结果会发生什么变化。
  • batch_size =这是一次通过神经网络的记录批数。批量越小,精确度越低,但速度越快。同样,您可以通过在代码中直接更改这个数字来进行试验。
history = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=40, batch_size=10)

模型评分

一旦我们运行上面的代码,我们将得到模型执行情况的指示——您将看到它在各个时期运行,并为训练(acc)和测试(val_acc)集提供准确性分数。

一旦它完成了它的任务,剩下要做的就是观想结果。

plt.plot(history.history['acc']) plt.plot(history.history['val_acc']) 
plt.title('model accuracy') plt.ylabel('accuracy') plt.xlabel('epoch') plt.legend(['train', 'test'], loc='upper left') plt.show()

我们需要做的最后一件事是保存模型,这样我们就可以在以后将它部署到生产环境中——AWS SageMaker/Azure/Google 都有不同的方法来做到这一点,但是通常你需要一个 JSON 和一个权重文件。

# serialize model to JSON 
model_json = model.to_json() with open("model.json", "w") as json_file: json_file.write(model_json) # serialize weights to HDF5 model.save_weights("model.h5") print("Saved model to disk")

所以我们有它。一个神经网络产生了大约 79%的客户流失准确率。当然,我们可以花更多的时间研究学习率、激活函数、节点数量、时期数量等,以使其更加准确,但希望这是开始研究的坚实基础。

接下来,我将研究如何将这些代码部署到生产环境中——这变得很棘手。

原载于 2019 年 5 月 27 日【http://drunkendatascience.com

PySpark & AWS |预测客户流失

原文:https://towardsdatascience.com/predicting-customer-churn-with-pyspark-95cd352d393?source=collection_archive---------27-----------------------

你怎么称呼一群分散的恐龙繁殖岛?

侏罗纪火花...Ba Dum Tss**

O 对于采用订阅式商业模式的公司来说,最重要的问题之一是客户流失。客户因为各种原因降级或停止服务,服务提供商通常直到客户离开才知道他们何时或为什么离开!

如果公司能够在客户离开之前预测到他们何时会流失,会怎么样?

这将是强大的!如果我们能够可靠地预测客户是否会流失,我们就有机会通过促销、宣传新功能等方式留住这些客户。这是一种主动的方法来留住客户,而不是一种被动的方法来挽回失去的客户。

Udacity 提供了两个类似格式的用户活动数据集(128MB 和 12GB),来自一家虚构的音乐流媒体公司 Sparkify,我使用这些数据来更好地了解 Sparkify 的客户,然后预测客户是否会流失,准确率超过 80%。

我无法在本地对我的机器上的 12GB 数据集进行分析和建模,所以我在 Jupyter 笔记本中使用 PySpark 的本地模式 在 128MB 数据集上探索并原型化了我的工作流,这让您 模拟在一台机器上的分布式集群上工作的

然后,我把工作流变成了一个 Python 脚本 ,旋出了一个 4 节点 AWS EC2 集群 ,在集群上通过 AWS EMR 执行脚本。

以下是我在这篇文章中将要涉及的内容:

  1. 特征工程&在本地探索数据
  2. 本地数据预处理
  3. 本地机器学习
  4. 在 AWS 分布式集群上运行步骤 1–3

这篇文章是写给谁的?

本文旨在向您介绍我所采取的步骤和一些有用的代码,这些步骤和代码带我从一个小型 Jupyter 笔记本分析到一个 4 节点 AWS EMR 集群上的 12GB 数据集分析。所以,我假设你对Python&机器学习* 。*

此外,在这篇文章中,我展示了许多我使用【py spark】的代码片段,这些代码片段与通过 PandasScikit-Learn 库处理数据的非常不同。如果你不熟悉 PySpark,那就略读一下吧!没什么大不了的。**

最后,如果你想对分析和机器学习如何通过我的代码进行更详细的分析和演练,请查看我的完整 Jupyter 笔记本(托管在我的网站)或 GitHub Repo

我希望你喜欢这个!

现在让我们开始吧…

一、特征工程&探索数据

首先,我将 128MB 的数据集从 JSON 格式转换成 Spark 数据帧。以下是模式,以便您了解数据的结构:

Original imported features.

日期特性工程| 经过一些简单的探索,我使用“ts”(时间戳)属性通过 PySpark UDF(用户定义函数)创建了一些日期特性。

我为新的日期特性创建了一个计数图,以便更好地理解用户在整个数据集期间的行为。

Note this smaller dataset (128MB) only contains data for about two months.

事件特性工程| 我使用了“页面”特性来标记用户访问的特定页面或执行的特定操作。这些标志特性将在以后帮助我在用户级别而不是事件级别聚合用户活动。

This is identical to one-hot encoding based on this feature. In hindsight, this would have been faster with PySpark’s native OneHotEncoder. Oh well! It works.

运行计数特征工程| 我还利用了数据的事务性质(例如,某个用户在某个时间点执行的每个事件对应一行)来创建一些窗口计算特征(例如,一个月内的运行收听计数等)。)这里有一个例子,说明这样的功能是如何创建的。

Creating a window for each user per month, counting rows whenever a user listens, and joining back to DF.

这将创建一个类似于图中第四列的列:

Last Column: Note the null values

由于这是一个连续计数,空值应该在前面填充。然而,鉴于 Spark 的弹性分布式数据集(rdd)的分布式本质,没有前置填充空值的原生函数。为了解释,如果一个数据集在云中的几个机器之间随机分区,那么机器如何知道前一个值何时是数据集中的直接前一个值?

我最好的解决方法是创建一个 running listens 列的 lag,然后用这个 lag 迭代地替换空值。连续的空值需要多次执行这种练习,因为我们实际上是用先前的值替换当前的空值(对于连续的空值,先前的值为空)。我是这样实现的:

Sharing this snippet in case anyone finds it useful. This is the only way I found to front-fill null values that was computationally cheaper than using a complicated cross-join.

上面的代码片段用其先前的值填充空值 6 次,然后用 0 填充剩余的值以节省计算时间。这可能不是前置填充的最佳方式,但如果要用相对较少的空值填充大型数据集,这可能会很有用。这是可行的,因为该操作需要 Spark 连接分区来计算 lag(即紧接在前面的值)。

定义流失| 最后,我将客户流失定义为每当用户访问“取消确认”或“降级”页面时。

This labels the “Churn” column of the DataFrame as 1 if a user visits the aforementioned pages.

二。数据预处理

现在让我们退一步,想想我们拥有什么,我们想要实现什么。

我们所拥有的: 一个“事件”数据集,指定用户在给定的时间点执行什么活动。

我们想要的: 预测客户流失。

凭直觉,我不认为在我们现有的数据集中预测变动是一个好主意。基于用户事件进行训练和预测将是非常昂贵的,这可能是非常非常昂贵的*因为你每小时可以有数千个事件(或者更多!)*****

因此,我的方法是简单地汇总每个用户的数据。具体来说:

我使用了 、事件级数据 来创建 一个基于 指标的用户级矩阵 ,它本质上是在一个矩阵中总结每个用户的活动,该矩阵的行数与唯一用户的行数一样多

这可能无法从文本中很好地翻译出来,所以让我给你看看我是怎么做的。下面是一个代码聚合示例,用于查看每个用户执行的全部活动:

这种类型的聚合帮助我创建一个整洁的数据框架和可视化,如下所示…

Truncated DF | One row per user with user-specific summary statistics.

Using the summary statistics to compare our churned users vs. our non-churned users.

有趣的是,看起来我们被激怒的用户比我们没有被激怒的用户更活跃。

现在我有了一个很好的、紧密的矩阵中的数据,我用它作为机器学习模型的基础。

三。机器学习

我的建模方法简单明了:尝试一些算法,选择一个看起来最有希望的,然后调整这个模型的超参数。

这里有一个用户矩阵的模式,这样您就知道正在建模什么了:

One user and several metrics/aggregations for this user per row.

PySpark ML 要求数据采用非常特殊的数据帧格式。它需要它的 特征 在一个 列的向量 中,其中向量的每个元素代表它的每个特征的值。它还要求其 标签 在其 自有列 中。

下面的代码将原始矩阵转换成这种 ML 友好的格式,并标准化这些值,使它们具有相同的比例。

现在我们已经准备好了数据,下面的代码显示了我最初是如何评估模型的。我任意选择了逻辑回归、随机森林和梯度提升树作为最终流失模型的候选。

在初始模型评估后,我发现 gbt 分类器(梯度增强树)表现最好,准确率为 79.1%,F-1 得分为 0.799。

因此,我使用 PySpark 的原生 CrossValidator 和 ParamGridBuilder 将该模型调优为网格搜索,网格搜索使用 K-Fold 验证选择最佳超参数。这里,由于计算时间昂贵,我使用了超参数的小网格和 3 重验证。

经过调优后,重新评估模型,准确率和 F-1 分别提高到 82.35%和 0.831!最佳模型的最大深度为 3,最大箱数为 16。如果在我的笔记本电脑上运行不需要这么长时间(大约需要 1 个小时),我会尝试更广泛的网格搜索。

PySpark 自动计算这些基于树的模型的特征重要性。

注意:出于好奇, 下面是 一篇有趣的文章,解释了特征重要性是如何计算的,以及 为什么它实际上不是那么准确 。这真的不在这个项目的范围内,所以我只是敷衍一下。

Feature Importances calculated for the tuned GBTClassifier.

看来前三个 预测流失最重要的特征 是:

  • 每次会话的平均歌曲播放次数
  • 总赞数
  • 最连续几天不播放歌曲

看起来,与错误和歌曲播放总量相关的指标与预测客户流失完全无关。

四。AWS 集群部署

到目前为止,我一直在描述我在笔记本电脑上使用 Spark 的本地模式对小型 128MB 数据集执行的分析。

为了对 12GB 的数据集进行同样的分析,我需要更多的处理能力。这就是 AWS 弹性地图简化(EMR)和弹性云计算(EC2)的用武之地。

注意:EC2 可以让你在云中的机器上工作。EMR 用于轻松管理已经安装了 Spark 的 EC2 集群

在设置好 EMR 并通过我的终端访问 AWS 之后,我将我的脚本上传到我的集群的 Hadoop 分布式文件系统(HDFS),从 Udacity 的 S3 存储桶加载包含 12GB 数据集的数据,并从我的终端在主节点上运行该脚本。

注意: 这里是我用来设置 EMR 的 这里是我用来设置我在终端上访问 AWS 的

Don’t even try to read this. Just FYI: this is what it looks like when you run a Spark app through EMR from your CLI/terminal.

这个脚本需要很长时间才能完成(我在 4 台机器上花了大约 5 个小时)。为了不浪费所有这些辛苦的工作,我在脚本的最后保存了模型和预处理过的用户矩阵。

如果您感到好奇,在完整的 12GB 数据集上运行此分析和工作流会产生非常高的准确性和 F-1 分数!

This was a HUGE improvement from the model trained on 128MB dataset.

动词 (verb 的缩写)总结

更多的功能可以从用户活动中设计出来,比如每天/每周/每月的竖起大拇指数,竖起大拇指与不竖起大拇指的比率,等等。特征工程可以比简单地优化一种算法更好地改善结果。

因此,可以做进一步的工作,从我们的交易用户数据中提取更多的特征来改进我们的预测!

一旦一个模型被创建,也许它可以被部署到生产中,并且每隔 x 天或 x 小时运行一次。一旦我们预测到用户可能会流失,我们就有机会进行干预!

为了评估这个假设部署的模型做得有多好,我们可以运行一些概念验证分析,并且在给定的测试期间不干预它的预测。如果它预测的用户将以比普通用户更高的速度流失,这可以表明我们的模型工作正常!

不及物动词总结想法

学习 PySpark 和 AWS 好像是一场噩梦(我从这次经历中知道)。然而,如果你已经熟悉 Python 和机器学习,你不需要知道太多的来开始使用 PySpark 和 AWS。

你知道如何处理数据。你知道机器学习是如何工作的。战斗的另一半是知道如何在 PySpark 和 AWS 集群上执行这些相同的任务,这可以通过 Google 搜索和教程找到!我建议慢慢浏览我的(或其他人的) Jupyter 笔记本以便你对 PySpark 有所了解,然后查找如何在 AWS EMR 上运行 Spark 应用程序。

这看起来工作量很大,但是我根本不可能用笔记本电脑处理 12GB 的数据集。每天都有令人难以置信的大量数据被创建,如果你想处理这么多数据,Spark 是一个很好的学习工具!

用 Spark 预测客户流失

原文:https://towardsdatascience.com/predicting-customer-churn-with-spark-4d093907b2dc?source=collection_archive---------16-----------------------

对于许多公司来说,客户流失是一个主要问题。一些人停止使用这项服务是很自然的,但如果这个比例变得太大,就会阻碍增长,不管收入来源如何(广告销售、订阅或两者兼而有之)。考虑到这一点,企业通过识别处于风险中的客户来预测客户流失的能力至关重要,因为这使他们能够采取某些行动,如个性化的优惠或折扣,以尝试和减少客户的流失。基于历史数据建立的机器学习模型可以让我们洞察客户流失的信号,并帮助我们在它发生之前预测它。

对于这个例子,我们使用一个虚构的音乐应用公司 Sparkify 的日志数据。这是一个玩具数据集,相对较小,因此可以由一台计算机处理。尽管如此,为了模拟真实世界,我们使用 Spark(在本地模式下)来处理数据和构建模型。Spark 是大数据处理和建模的领先解决方案之一,通过 DAG 的内存处理和计算延迟评估来提高速度。你可以点击这里了解更多。通过在这个项目中使用 Spark,尽管这不是绝对必要的,但我们建立了一个可扩展的框架来分析任何大小的数据的变动,因为代码和分析可以很容易地扩展,只要它们部署在能够处理所需计算的集群(如 AWS、IBM Cloud 或 GCP)上。

在这篇博文中,我将总结包含在这个 GitHub 库中的分析。在简要概述手头的数据后,我们将介绍该模型、其结果以及它对 Sparkify 的意义。

数据

在本地模式下启动 Spark 会话后,我们可以加载数据集。它包含 2018 年 10 月 1 日至 2018 年 12 月 3 日之间 226 个不同用户的信息。它是 JSON 格式的(关于 JSON 格式的更多信息在这里),可以很容易地用以下命令加载:

path = “mini_sparkify_event_data.json”df = spark.read.json(path)

这些数据捕捉了各种各样的行为,如听歌、竖起大拇指、点击主页、更改帐户设置或向播放列表添加歌曲。因此,尽管它是一个很小的用户子集,但数据集仍然包括 278,251 行。

在数据集中出现的 226 名用户中,有 52 名用户最终出现了混乱。为了正确地训练和评估我们的模型,我们解决了这种不均衡,以确保我们的预测可以准确地预测这两个类别,而不是过度倾向于预测不存在流失。我们通过一种称为上采样的技术来做到这一点,即从搅动的用户群体中进行替换采样,直到我们得到两个大小相当的群体。

型号

为了构建这个模型的功能,我让数据探索决定了我的方法,以及我在一家非常相似的(真实的)音乐应用公司工作时获得的领域知识。特别是,我在寻找那些在翻炒用户和不翻炒用户之间价值差异很大的特性。寻找这样的功能突出了帐户类型(免费与付费)以及其他帐户相关信息的重要性,如状态和注册日期,让我们对用户有所了解。

Users who churned are more likely to have a free account

另一组特性集中在人们在平台上的行为。这些元素,如会话长度、每次会话的歌曲数量以及拇指向上/拇指向下、添加到播放列表或添加朋友,为我们提供了额外的洞察力。建模之前的直观解释是,这些特征捕捉到了与用户参与度相关的潜在变量,较低的参与度与较高的搅动可能性相关联。例如,停止搅动的用户每月来平台 9 次,而留下来的用户每月来 14 次。我们将在后面看到这种前概念是否被数据所证实。

The distribution in terms of session length and number of items in session differs between both groups

那些对创建这些特性的技术细节感兴趣的人可以参考介绍中链接的代码,但是由于 Spark 的管道,我们可以高效地处理所有这些特性,并将其转换为适合分析的形式。您可以在下面看到数据集的前几行及其所有功能。

First few rows of features dataset

检查这些特性的分布,我们可以看到 itemInSession、thumbsUp、addFriend 和 addToPlaylist 非常分散地分布在平均值周围。这一点很重要,因为模型依赖于可变性来学习和做出预测。另一方面,每日会话或长度等特征的变化较小,因此我预计这些特征在预测中的权重较小。

完成后,我们测试三种不同的分类模型(随机森林、逻辑回归和梯度推进),并评估它们在测试集上的准确性和 F1 得分。考虑这两者是很重要的(不仅仅是准确性),因为后一个度量允许我们调整测试集中存在的类不平衡,并且通过扩展,在真实世界中也是如此。由于三个模型之间的结果没有显著差异,我们选择进一步调整逻辑回归模型,因为它具有更好的可解释性。我们通过交叉验证,利用网格搜索算法找到最佳的参数组合。特别是,我们测试了以下值:

  • minInfoGain(在树节点处被考虑的分割的最小信息增益):0,1
  • maxDepth(树的最大深度):5,10
  • numTrees(树的数量):20,50

我特别选择了这些参数,因为它们与防止过度拟合有关。

结果

优化后,最优超参数是 50 棵树,0 最小信息增益和最大深度 10。我们有一个模型,在测试集上达到 73%的准确率,F1 值为 0.72。这两个指标合在一起非常令人鼓舞:只有关于 191 个用户(在我们的训练集中)的数据,我们就能够有效地将用户分类到这两个类别中,而在预测一个或另一个方面的性能没有显著差异。有趣的是,网格搜索后我们模型的性能并没有提高,很可能是因为我们的数据集很小。我们甚至通过训练和预测不同的随机状态来评估模型的稳健性,并发现模型的准确性在它们之间非常一致。

看看特征重要性,我们之前的直觉得到了证实:静态变量(注册月份、地理位置)和行为(添加好友)在我们的预测中都有很大的分量。这应该鼓励 Sparkify 记录尽可能多的信息,因为在试图预测客户流失时,所有信号都很重要。

结论

Spark 为我们提供了一个预测客户流失的通用框架。它可以为任何公司处理大数据,只要它部署在能够处理所需计算的集群上。如果这种分析应用于具有更多可用计算能力的更大数据集,我认为甚至会达到更好的精确度/F1,因为我们将能够在更大的超参数空间中为更多用户进行搜索。我们甚至可以在一个非常大的超参数空间上组合随机搜索,以产生一个子集,网格搜索将在这个子集上寻找最佳组合,以便进一步加快计算速度和提高性能。最后,为了更深入地了解模型,我们可以利用 SHAP 值或排列重要性来了解各个特征如何影响模型预测。

根据一小部分客户的历史数据,我们建立了一个模型,能够以 73%的准确率识别出有流失风险的用户。它可以定期(每天/每周,取决于现有的计算基础设施)应用于用户群,并标记可能很快离开服务的用户。记住这些信息,Sparkify 可以采取缓解措施,例如发送个性化信息或提供每月折扣。所有这些都可以自动化,并将对收入和增长产生巨大影响。应该通过 A/B 测试来确定要采取的具体行动。

最后,随着缓解措施的实施以及用户群的增长和演变,必须定期对该模型进行重新训练,以使该模型适应不断变化的条件。

电子商务中无标签数据的顾客流失预测。

原文:https://towardsdatascience.com/predicting-customer-churns-without-any-labelling-data-in-e-commerce-e54cb70944fe?source=collection_archive---------19-----------------------

Beautiful Cali

当你花了很大的力气去吸引你的客户,却不知道谁会去吸引时,你会感到困惑甚至沮丧吗?当你打算花 1 万美元,却只能获得 5 万美元的收入时,一切都感觉崩溃了(可能会让情况变得更糟)。我们发现在帮助你限制营销成本方面非常有用的一件事是定义和预测你的流失客户。有了一个精确的模型,你将能够削减高达 80%的成本,并减少你的竞选战略的不确定性的一大部分。让我们直接进入今天的话题吧!

首先,我们想谈谈为什么很难预测搅动。但在此之前,我们需要了解一下两种不同类型的 2C(对客户)商业模式。№1 是人们订阅和预付费的地方,这称为订阅模式。№2 是人们来买东西时没有留下任何关于他们何时会回来下一个订单的暗示,这被称为交易模式。

订阅模式有哪些例子?我会暂停 30 秒让你思考至少 2 秒。我的答案是苹果音乐,Spotify,亚马逊 Prime,纽约时报,甚至你的电,煤气,垃圾,下水道,有线电视都算在订阅里。订阅模式与众不同的是一个明确的契约。这意味着双方都知道钱和时间。因此,在客户流失建模中,这是一个相对简单的案例。

事务模型的一些例子是什么?每一个销售有形产品而没有签约重复的品牌都是一种交易模式。您现在将立即理解为什么在这种情况下更难对搅动进行建模,因为搅动实际上是不可见和不透明的。

我们将在事务模型中讨论搅动,因为我们喜欢挑战。但是为了克服挑战,我们需要变得更聪明。我们知道在监督学习的框架下解决这个问题是不可能的。为什么?因为作为一名员工,99%的时候你会发现自己在为一个项目争取多一天的时间。当你有一个监督学习的问题,但你没有足够的标记数据时,通常正确的解决方案是找到另一种方法,而不是为自己自我标记 1000,2000 个数据。时间很重要。(参见我的另一篇帖子关于作为初级数据科学家你肯定会陷入的一些陷阱。)

那么我们如何解决这个问题呢?我们需要转向营销中另一个更广为人知和使用的问题,顾客终身价值(CLV)。

CLV 是做什么的?CLV 是一个相当简单的模型,它可以根据顾客的历史行为来估计你能从他/她那里得到多少。让它简单有效的是 CLV 分析中只有三个组成部分,频率、最近和货币价值。频率是到目前为止的订单数量。最近是第一个订单和最后一个订单之间的天数。几乎每个人都很难记住这个定义,因为 recency 与“最近”有关,后者本质上代表你最后一次做某事是什么时候?这与新近的定义相反。你需要翻转它来记住它。货币价值是所有订单的平均价格。在我们的客户流失模型中,我们不太在乎钱,所以我们暂时不考虑它。

在继续之前,我想请读者暂停 30 秒,思考一下为什么这种模式在营销中如此有用和受欢迎。我的答案是 a)它对每个人都有意义 b)因为它直觉上有意义,它一定抓住了人类行为的本质。

现在是时候真正了解这个模型了。我会把所有的数学都交给最初的论文,因为不是这里的每个人都同意这种复杂性。我们更关心可用性而不是理论。该模型的基本思想是这样的:我们通过某个时间单位(天)观察每个客户,每个客户在任何给定的一天都有一定的订购概率,每个客户在任何给定的一天都有一定的流失概率。我们假设这两个概率是独立的,并且随时间保持不变。然后,我们可以建立一些方程来描述交易行为,并使用数学来估计概率。使用 Python,我们可以充分利用这个

首先,您需要使用以下命令安装这个包:

pip install lifetimes

该产品包包括 4 个可用于我们客户流失模型的模型,如果您感兴趣,可以查看gamma gamma fitter以全面评估客户生命周期价值建模之旅。****

我个人建议在最初的尝试中使用 BetaGeoFitterModifiedBetaGeoFitter 。原因是它们相对更容易收敛,训练速度更快(这也是原作者开发它们而不是经典的paretonbfilter的重要原因之一)。我使用betageobitabinomfitter的经验告诉我,这个模型也比其他两个模型需要更多的时间来训练。

我们现在需要一些训练数据,您可以通过在 python 中键入以下命令来获得

from lifetimes.datasets import load_cdnow_summary
data = load_cdnow_summary(index_col=[0])

我在这篇文章中使用了一些其他数据源,但是您可以预期使用这个示例数据集生成类似的曲线图、图表和数字。

查看数据集,我们有以下结构:

print(data.head())
*"""*
 *frequency   recency      T*
*ID*
*1    2           30.43       38.86
"""*

频率是某个客户的历史订单数量。最近度是第一次发现和最后一次购买之间的天数。 T 是客户被发现的天数。如果你使用自己的数据集,你需要以同样的方式组织你的数据。**

我将在我的帖子中使用 ModifiedBetaGeoFitter

mbgf = ModifiedBetaGeoFitter(penalizer_coef=0.001)# penalizer_coef is helpful in converging the trainingmbgf.fit(summary_train['frequency'], summary_train['recency'], summary_train['T'])# display some parameters of the model
display(mbgf)# <lifetimes.ModifiedBetaGeoFitter: fitted with xxx subjects, a: 1.10, alpha: 3.27, b: 0.05, r: 0.85>

这个模型已经训练成功,我们希望得到它的一些视觉效果:

plot_probability_alive_matrix(mbgf)

The brighter, the more likely a customer has not churned

该图描述了新近度频率与流失可能性之间的关系。给定一定的频率,较高的新近度增加了不流失的可能性。这是有道理的,因为高新近性意味着客户最近的活动表明他们坚持使用该品牌。给定一定程度的新近性,更高的频率增加了流失的可能性。这可能看起来不直观,因为我们通常认为顾客买得越多,他们就越喜欢这个品牌,离开的可能性就越小。但是如果我解释给你听的话,它就不一样了:

假设你经营一家超市,你有一些忠实的顾客,你至少每隔一天就会见到他们。我们把其中一个叫做贝拉吧。她一年前就开始来了,她从来没有错过一天,所以你真的认为她很忠诚。有一天你没有见到她,第二天你又没有见到她,你开始自言自语“她一切都好吗?我会永远失去她吗?”。当你从一两天前开始不再见到一些忠实的客户时,你的担心就会立即产生。因为你非常关心你的敬业的人,所以你不会太关心一年只来一次的人。现在,根据你对他们是否会回来的担心程度,想想他们回来的可能性。一个忠诚的人不再拜访你,这比你 7 个月没见一个人,但你只希望一年见一次更强烈的信号。正是这种行为的改变引起了我们的担忧,我们真的感觉到了。

现在我们完全理解了这个模型能给我们什么样的预测,接下来我们真正的预测是什么:

summary_train['mbgf_lh'] = mbgf.conditional_probability_alive(summary_train['frequency'], summary_train['recency'], summary_train['T'])summary_train['mbgf_lh'].plot.hist()

Most customers have low probability to return, but you can manage it before it happens

这张图显示了未来返回可能性的分布。我们注意到,大多数人不太可能回来(在电子商务中,大多数人都是买了就走,再也不回来)。现在我遵守了我的诺言,带你走到了这段旅程的终点。

我想给你留一个作业,你可以在下面评论:你如何利用这个情节来管理你的电子商务业务?你能从中赚钱吗?如果有,如何实现?

一些有用的资源:

彼得·法德尔教授在沃顿商学院谈论顾客终身价值。

他的论文:

盘点你的客户:他们是谁,接下来会做什么?T3,

用 Python 中的“买到死”概率模型预测顾客终身价值

原文:https://towardsdatascience.com/predicting-customer-lifetime-value-with-buy-til-you-die-probabilistic-models-in-python-f5cac78758d9?source=collection_archive---------1-----------------------

Tokyo, Japan - photo credit: Pexels

客户的价值是什么?在搅动之前,客户还会购买多少次?他在未来 3 个月内流失的可能性有多大?最重要的是,我们应该期望客户“存活”多久?

虽然这些问题在营销、产品、风险投资和公司财务专业人士中很常见,但总是很难用准确的数字来正确回答。

的非契约性商业环境,顾客可以随时终止与零售商的关系,无需事先通知,这可能更加棘手。
亚马逊的图书(或任何其他没有订阅的产品类别),Zalando 的服装,Booking.com 的酒店都是非合同商业设置的例子。对于所有这三种电子商务,我们无法通过查看客户合同的结束日期来了解他是“活着”(将来会购买)还是“死了”(永远不会再购买)。我们只能依靠客户过去的购买和其他不太典型的事件(网站访问、评论等)。).
但在这种情况下,我们如何决定客户是会回来还是永远离开呢?

“买到死”概率模型通过评估客户未来交易的预期次数及其“活着”的概率,帮助我们量化客户的终身价值。

英国天然气公司/NBD 模型

为了理解“买直到你死”模型是如何工作的,我们把重点放在预测现实生活数据的最佳选择上:BG/NBD 模型。
贝塔几何/负二项分布模型于 2004 年由 P. Fader 的论文提出,是对 Schmittlein 等人于 1987 年开发的帕累托/NBD 模型(第一个 BTYD)的改进。

特别是,为了预测未来的交易,该模型将客户的购买行为视为掷硬币游戏。
每位顾客有 2 枚硬币:一枚购买硬币控制顾客购买的概率,一枚骰子硬币控制顾客退出且不再购买的概率。

让我们通过模型假设来理解一切是如何进行的。

假设 1: 活跃时,客户的交易笔数遵循一个 泊松过程 ,交易率λ(=一个时间间隔内的预期交易笔数)。

A customer’s purchasing behavior observed over a period of 12 months, where the number of transactions is distributed as a Poisson Process with unobserved transaction rate c

在特定时间间隔(12 个月)的每个子周期(1 个月),每个顾客投掷他的购买硬币,根据结果,他购买或不购买。
我们在周期内观察到的交易数量(头数)取决于每个客户在λ附近的概率分布。
让我们在客户的泊松概率分布下方绘图,以形象化我们刚才所说的内容。

Poisson Probability Mass Function of a customer with λ = 4.3

这里我们假设我们的随机客户的交易率λ = 4.3。
因此,他有 19%的概率在随机的 12 个月内购买 4 次,有 4%的概率购买 8 次,等等。

假设 2: 客户间交易率的异质性遵循一个 伽玛分布

这相当于说每个顾客都有自己的购买硬币(有自己的头尾概率)。 为了更好地理解这一假设,我们模拟了 100 个客户的泊松分布,其中每个λ都用伽马分布建模,参数为:形状=9,比例=0.5。

Simulation of 100 customers Poisson Probability distributions where each customer’s λ depends on a Gamma distribution with shape = 9 and scale = 0.5

如假设中所述,在给定的时间间隔内,每个顾客都有自己购买 x 次的概率。

假设 3: 在任何一笔交易之后, 一个客户以概率 p 变得不活跃 因此,客户“退出”的点按照一个 (移位)几何分布 跨交易分布。

每次交易结束后,每位顾客都会投掷第二枚硬币,即骰子硬币。 鉴于 p 是“死亡”的概率,那么我们可以定义 P(活着)= 1-p. 再一次,我们来绘制一个随机的客户概率分布,更好地把握这个假设的含义。

Shifted Geometric Probability Mass Function for a customer with p = 0.52

假设我们的客户变得不活跃的概率 p = 0.52,那么他在第二次交易后变得不活跃的概率是 25%,他在第三次交易后变得不活跃的概率是 12%。

正如你所看到的,顾客买的越多,他活着的概率就越高。

假设 4:p 中的异质性遵循一个 贝塔分布

至于购买硬币,每个顾客都有自己的硬币,在特定数量的交易后,它有自己的存活概率。
我们可以在下面看到如何寻找 10 个客户的模拟,其中 p 遵循 Beta 分布,其中 α = 2, β = 3。

Simulation of 10 random customers Geometric Probability distributions where p is follows a beta distribution with parameters α = 2 and β = 3

假设 5: t 交易率 λ 和退出概率 p 在客户之间独立变化。

模型输出

最后,通过对历史客户数据拟合前面提到的分布,我们能够推导出一个模型,该模型为每个客户提供:

  • P(X(t) = x | λ,p) - 在长度为 t 的时间段内观察到 X 笔交易的概率
  • E(X(t) | λ,p) - 长度为 t 的时间段内的预期交易数
  • P(τ > t) - 客户在 τ期间变得不活跃的概率

然后,拟合的分布参数被用于前瞻性的基于客户的分析中,以找到具有由 x,t,T — 定义的过去观察到的行为的个人在长度为 t 的未来时间段中的预期交易次数,其中 x =历史交易次数, t ₓ =最后购买时间, T =客户年龄。
下面是数学爱好者的最终公式(详细推导见 P. Fader 论文的附录):

The expected number of transactions in a future period of length t for an individual with past observed behavior (X = x, tₓ, T; where x = n. historical transactions, tₓ = time of last purchase and T = Age of a customer) given the fitted model parameters r, α, a, b

用 Python 实现 CLV 模型

既然我们已经了解了“买到死”模型是如何工作的,我们终于准备好从理论到实践,将 BG/NBD 模型应用于真实的客户数据。

在可用于实现模型的各种备选方案中,我强烈推荐 Python 中的生存期包(在此使用)和 r 中的 BTYD 库,这些包在将模型的方程包装成方便的函数方面做得非常好,使我们的生活变得非常容易。

数据的形状

如前所述,BG/NBD 模型将几种分布拟合到历史客户购买数据中。为此,我们需要建立一个数据集,为每个客户提供以下三个信息:

最近(来源于 t ₓ) :顾客最后一次购买时的年龄,等于顾客第一次购买和最后一次购买之间的时间。

频率(x) :客户重复购买的次数。

客户年龄(T) :客户在研究期间结束时的年龄,等于客户第一次购买和数据集中最后一天之间的持续时间。

下面是您的数据应该是什么样子的示例:

您是选择几天、几个月还是几年,很大程度上取决于您典型的客户购买周期。例如,送餐业务往往会在同一周内遇到重复的顾客,所以他们可能会去几天。在上面的例子中,我用了月,因为它更合适。

拟合模型

一旦我们创建了数据集,我们就可以将它传递给模型并打印出摘要。

如果您需要一些真实的客户数据,请随意使用 UCI ML 存储库提供的在线零售数据集(我将使用一个虚构的数据集,不提供任何有关我分析的公司的敏感信息)。

酷!这是什么?我们将假设的分布拟合到历史数据中,并推导出模型参数: alphar 用于伽马分布(假设 2),而 ab 用于贝塔分布(假设 4)。
在总结中,我们还为每个参数提供了一个置信区间,我们可以用它来计算每个客户的预期未来交易的置信区间。

评估模型拟合度

现在我们已经建立了一个模型,我们可以检查它是否真的有意义。第一种方法是根据拟合的模型参数人工生成具有预期购买行为的客户,并将其与真实数据进行比较。

就我们所见,人为的客户分布与真实数据非常相似。
在这一步,我还建议计算总体百分比误差(=预测交易/实际交易-1)和 在校准周期 中完成的每笔交易的百分比误差。
通过这种方式,您可以量化模型与现实的接近程度,以及它是否更适合某些客户。例如,该模型在 5、6 和 7 个校准交易桶中放置的客户可能比实际少,这可能最终导致整体严重低估。

可视化模型频率/新近矩阵

现在我们有了一个拟合的模型,我们可以查看它的频率/最近矩阵,以检查客户最近(上次购买时的年龄)、频率(重复交易的次数)和下一个时间段的预期交易次数之间的预期关系(基于我们的拟合模型参数)(下图左侧)。
我们还可以根据客户的近期情况和频率来想象客户存活的预期概率(下图)。

事实上,我们看到,如果一个客户购买了超过 25 次,并且他们最近一次购买是在他们超过 25 个月大的时候(右下角),那么他们是你最热门的客户,最有可能活着并购买。相反,你最冷的客户在右上角:他们很快买了很多,我们几个月没见了。

交互效度分析

一旦您验证了模型足够接近实际数据,我们就可以看到它在预测未来购买方面有多好。

得益于 Lifetimes 'calibration _ and _ holdout _ data()函数,我们可以将简单的事务数据集快速拆分为校准期和维持期。我们首先将模型拟合到 2 年的校准期,然后预测下一年的事务,最后比较预测的事务与维持的事务。

下面是 cal_hold 数据帧的样子:

通过比较下图中的平均实际购买量和预测购买量,我们可以注意到,对于在校准期内有 0 到 3 次重复交易的客户,预测购买量和实际购买量非常接近,而对于重复交易次数较多的客户,预测购买量和实际购买量会越来越大。

同样在这种情况下,由于图表本身可能会产生误导—对于有很多客户的时段,小的差异可能会导致大的误差—为了正确地评估预测,我们应该查看总体百分比误差(图表中的预测误差)、,在我的场景中它占-6.3%(模型预测的交易比实际少 6.3%)。
通过查看校准期内重复测试的误差百分比,我们注意到,我们低估了重复测试 3 次或更多次的客户(-7.3%至-30.3%),以及没有重复测试的客户(-12.2%)。我们还强烈高估了 1 个回头客(+19.7%)。

尽管我们在更细的层次上预测失误,但这是一个相当好的结果,因为我们感兴趣的是交易的总体预测量。尽管如此,仅仅在一个时期内的交叉验证并不能让我们理解未来会发生什么。未来预期的-6.3%的误差合理吗,或者这是一个难以置信的幸运机会?

为了更好地评估模型,我们将在几个期间进行交叉验证,然后检查每个期间的错误。为此,我们简单地构建一个 For 循环,在几个周期内迭代交叉验证。
具体来说,我们在具有 6 年交易的数据集上运行它,其中对于每次迭代,我们在选定的 2 年校准期内对客户子集进行采样,并预测未来 1 年的交易。因此,我们最终有 4 个交叉验证的期间。
然后我们标绘结果。

如下图所示,每年的预测误差始终在 4.1%到-7.9%之间,2018 年是预测最好的一年(-2.3%预测误差)。这是非常好的,尤其是与绝对预测误差大于 10%的普通队列模型相比。

客户预测和概率历史

一旦你建立了模型并验证了它的有效性,你就可以很容易地查看单个客户的预测以及他们存活的概率。这非常有价值,因为你可以将 CLV 预测用于营销活动、预测或更普遍的防止流失。

例如,在下面,我们绘制了客户存活的历史概率。

从图中我们可以观察到顾客每增加一次购买,他活着的概率就增加,然后又开始下降;但是速度较慢,因为每个新的交易增加了他的频率和他的新近度。

结论

总之,预测 CLV 总是一项棘手的任务,通常历史频率模型无法区分过去购买次数接近的客户。
购买直到你买概率模型来拯救我们,它允许我们仅使用 3 个客户的信息(客户的频率、最近和年龄)来建立相当准确的预测。

在本文中,我们有意没有提及一些与 CLV 有关的重要话题,但在让您了解之前,让我对其中的三个话题做一个简短的评论:

  • 我们预测了未来的交易,但是我们忽略了 CLV 等式中的“价值”部分。通常伽马-伽马子模型用于 BG/NBD 模型之上,以估计交易的货币价值。
  • 我们使用 BG/NBD 模型来预测未来交易总量,但如果您打算在用户层面采取行动,您应该正确衡量单个客户预测的准确性。
  • 如果模型不太适合您的客户(并且这些假设对您的业务来说是合理的),那么考虑让它适合客户群体(例如。按用户国家划分),和/或 将其与具有附加功能的线性模型(例如。客户的网站访问量、自上次访问以来的时间、产品评论、获取渠道等。).

感谢阅读,用数据不断改造世界!

建设性的反馈和激励性的谈话总是受欢迎的。随意连接在Linkedin上打招呼!

预测新加坡的登革热病例

原文:https://towardsdatascience.com/predicting-dengue-in-singapore-f9be0a761ce4?source=collection_archive---------19-----------------------

你正在公园里享受一个愉快的夜晚,野餐垫已经摆好,手里拿着一杯可口的冷饮。

“啪!”

你刚刚杀死了一只埃及伊蚊。你欣赏它手上独特的黑白条纹身体。你认为只是无害的咬一口。

接下来的一周,你躺在床上发高烧,肌肉疼痛,出皮疹。究竟发生了什么事?你从两周前孵化的伊蚊身上感染了登革热。

预警系统

登革热是一种可怕的疾病。如果没有及时或有效的干预,散发的登革热病例很容易演变成流行病。伊蚊通过吸食受感染的人类或其母亲感染登革热,后者在其两周的寿命内可以产下数百个卵。

如果流行病发生,早期预警系统可以帮助医疗保健提供者更好地管理登革热患者的增加。

登革热的潜在预测者

天气信息:雌性伊蚊喜欢炎热多雨的天气。它们在死水中产卵,温暖的温度促使它们的后代生长得更快。

谷歌趋势:包含“登革热”一词的搜索词已被成功用于预测多个国家的登革热病例。这可能是因为那些对病毒更敏感的人,也更有可能搜索它。

数据收集

从网络上收集了 2014 年至 2018 年新加坡特定的登革热病例数据、天气信息和搜索兴趣。

使用 SeleniumBeautifulSoup 从政府网站上搜集登革热和天气数据:

  1. 每周登革热病例数。报废时只有 2014 年到 2018 年的数据。此限制为所有其他数据源设置时间段。
  2. 每日天气信息。只有樟宜气象站的数据被废弃;这个车站是政府的主要历史参考点。

Google Trends search interest data for search term ‘dengue’ from 2014 to 2018.

以下词语的每周搜索兴趣数据来自 Google Trends:

  • 疼痛或疼痛
  • 登革热
  • 发热
  • 头痛
  • 呕吐
  • 恶心
  • 发疹
  • 目痛

数据清理和合并

数据集相对干净,除了重复和缺失的值,它们都被删除了。登革出血热病例也从登革热数据集中删除,因为这类病例很少。

主要挑战是创建一个跨数据集的公共时间要素来合并它们。使用流行病学周(e 周,例如 2018 年第 52 周)对登革热数据集进行了日期确定。 Epiweeks 软件包用于将数据集的日期特征转换为 e-weeks。

对于天气数据集,使用 groupby 获得每个 e 周的平均温度和降雨量。

在将数据拟合到模型之前,删除了所有日期时间功能。

时间滞后

时滞被用来解释感染者出现症状和伊蚊孵化所需的时间。

Assumed timeline of events

对于每个每周的登革热观察,其对应的搜索兴趣数据将来自一周前,而天气数据将来自两周前。原因如下:

  • 登革热的症状通常在被感染后的 4 到 7 天内出现。我认为在此期间,潜在的患者可能会在谷歌上搜索登革热及其症状。
  • 天气变化被认为会影响蚊子的繁殖速度。假设是较高的温度促使伊蚊产卵;伊蚊大约需要一周的时间孵化和生长。

列车测试分离

合并的数据框架被分成 20%的测试数据和 80%的训练验证数据(用于 k 倍交叉验证)。

特色工程&改造

通过取最高和最低温度之间的差值创建了温度范围特征。我预计较小的温度范围预示着登革热病例,尤其是在较温暖的几周。

对登革热病例数(目标)和对登革热的搜索兴趣(特征)进行对数转换,以校正偏差并提高其相关性。

在模型拟合之前,使用标准定标器对所有变量进行标准化。

特征选择

在模型构建过程的不同阶段进行特征选择。

  1. 在构建模型之前:为了避免多重共线性,高度相关的要素被移除。
  2. 交叉验证确定线性模型可能适用于数据后: Lasso 回归用于特征选择。使用 LassoCV 获得 alpha 值,并应用于模型。移除了具有零值系数的要素。
  3. 选择合适的线性模型后:使用向后逐步方法选择特征。具有最高 p 值的要素被移除,直到模型的校正 r 减小。

以下是这些变量及其被删除原因的摘要:

Table of variables, and their status (removed/included in model)

k 倍交叉验证

由于观察值数量较少(248,在去除了因对数据应用时间滞后而产生的缺失值后),因此模型选择使用了 5 k 倍交叉验证。

以下是所用线性模型的总结,以及它们在 k 倍范围内的 r 均值和标准差。对于脊和套索,alpha 值设置为默认值 1。

Simple linear model r^2: 0.755 +- 0.041 
Polynomial model r^2: 0.441 +- 0.153
Ridge model r^2: 0.755 +- 0.043
Lasso model r^2: -0.010 +- 0.012

为了在简单模型和脊模型之间进行选择,在使用 Lasso 回归进行特征选择并应用从 RidgeCV 获得的 alpha 值之后,我重新运行了交叉验证。

Simple linear model r^2:: 0.757 +- 0.043 
Polynomial model r^2: 0.665 +- 0.053
Ridge model r^2: 0.757 +- 0.046
Lasso model r^2: -0.010 +- 0.012

两个模型的 r 均值和标准差相似,因此选择了一个简单的线性回归

测试&评估

使用 StatsModel 对整个 80%的训练验证数据重新训练模型。为了进一步简化模型,进行了向后逐步特征选择。

以下是最终模型的结果:

StatsModel Output for Training-Validation Set

将测试数据拟合到模型后,得到的调整后的 r 为:0.760。这个模型似乎概括得很好。

检查假设

Left to Right: Scatterplot of y-predicted and y-residuals; Q-Q plot of y-residuals; Scatterplot of e-week and y-residuals.

散点图中没有可辨别的模式,y 残差似乎呈正态分布,除了尾部的一些偏差。

StatsModel 输出和上述图表判断,线性回归模型似乎适用于该数据。

结论

该模型可以解释新加坡每周登革热病例记录中 77%的差异,并具有良好的泛化能力。

在预测未来登革热病例时,对“登革热”的搜索兴趣似乎是最重要的。这一专题可能已经包含了一些关于蚊子繁殖和传播的信息,因为新加坡新闻媒体倾向于报道繁殖集群、登革热感染和相关死亡。

因此,医疗保健提供者可以使用登革热搜索兴趣 作为粗略的指标,而不是监控大范围的变量。

未来的工作

我对头痛和疼痛的显著负系数感到惊讶。新加坡对登革热的认识很高。我怀疑,当蚊子叮咬后出现这些症状或登革热病例增加时,意识到登革热的人可能会直接搜索“登革热”。因此,更好地理解搜索模式有助于改进模型。

*** GitHub ***

** * 这是一个 METIS 数据科学训练营项目。线性回归和网页抓取是项目需求***

用 XGBoost 预测地震

原文:https://towardsdatascience.com/predicting-earthquakes-with-xgboost-5feca64d28f6?source=collection_archive---------36-----------------------

我最近创建了一个 Plot.ly Dash web 应用程序,它处理地震的震级和深度,以预测它们在美国和美国领土内的位置。

处理我的数据

很多时候,当我开始一个新的机器学习项目,并开始查看我的数据时,我会先发制人地考虑模型和验证。尽管我喜欢筛选 NaNs 和脏数据,但我更喜欢拟合模型和预测结果。尽管如此,我倾向于最初选择一个最小可行的产品,然后向上滚动开始数据清理过程。数据清理和模型选择一样重要,这一点在这个项目中得到了很好的证明,在这个项目中,我从一个随机的森林分类器开始,得到了低得多的分数。最后,在最后一秒,我将模型切换到 XGBoost 分类器,这使我的分数提高了约 0.01%,但对复制的 gap-per 预测大幅下降。

数据探索

一开始,我很快意识到我的模型将围绕着根据地震的属性对州名进行分类。预期的结果将是明确的,并使用地震的属性进行预测。我从一个快速、极简的数据清理开始,为了避免一些严重的泄漏,我做了一些删减,包括经度和纬度列,这会使算法完全不相关。接下来,我开始了一些经典的数据探索。我决定用直方图开始我的分析,以确定州数据中某些结果的频率。这个直方图对于确定我的过程中的下一步,以及在开始拟合模型之前,数据下一步需要去哪里是必不可少的。

California is separated marginally from the rest of the data.

我对这一结果的最初反应是恐惧,这是理所当然的;如果加州比其他地方的数据要对更多的地震负责,那么真的有必要应用一个模型吗?这个问题很好地延续到任何数据科学家将对他们的数据做的下一件事。所以我用 iter-tools 写了一个函数,它能给我一个很好的主意,如果没有一个真实的模型,预测会有多准确,这当然也被称为多数类基线。这将让我知道,如果我们提供的任何数据(x)只猜测加利福尼亚,这个模型会有多准确。此外,您可能已经注意到直方图中有一个“CA ”,所以我很快将其替换为“California ”,以使命名更加一致,并且对该州有一个单独的预测。有趣的是,这是这个特定问题暴露出来的唯一地点。在将查询“CA”替换为“California”之后,我继续获取我的多数类基线。

def most_common(L):
  SL = sorted((x, i) for i, x in enumerate(L))
  groups = itertools.groupby(SL, key=operator.itemgetter(0))
  def _auxfun(g):
    item, iterable = g
    count = 0
    min_index = len(L)
    for _, where in iterable:
      count += 1
      min_index = min(min_index, where)
    return count, -min_index
  return max(groups, key=_auxfun)[0]
def baseliner(y,ypr):
  r = []
  m = most_common(y)
  for i in ypr:
    r.append(m)
  return(r)

我快速编写了两个函数,并使用测试集中的目标和特性运行该函数,产生了以下结果:

from sklearn.metrics import accuracy_score# Setting our majority class accuracy
acc = accuracy_score(ytest,y_pred)
# Printing out the results:
print("Baseline Accuracy:",acc)

结果是:

Baseline Accuracy: 0.5108583247156153

至少可以说,我非常震惊!令人高兴的是,这表明数据对于一点点机器学习仍然是可行的,生活中没有比这更令人兴奋的事情了。考虑到这一点,我用一个顺序编码器、迭代估算器和最后的 XGBoost 分类器设置并安装了我的管道。当内核计算结果时,我积极地预期结果,希望模型最初会获得很高的分数,因为仍然有很大的改进空间,使我能够获得非常高的训练准确度分数,这使我对模型的验证非常兴奋。

拟合模型

pipeline.fit(Xtrain,ytrain)

我安装了管道,设置了模型,并准备获得一些验证准确性,看看这个项目的未来会发生什么。这听起来好像我是在杞人忧天,但这真的是“数据科学家的事情”,你不会明白的。我在从我的 test-x 预测我的 test-y 时初始化了管道。

ypr = pipeline.predict(Xtest)
accuracy_rf = accuracy_score(ytest,ypr)

我终于得到了我的准确度分数,为了制造悬念,我不得不为我最初的准确度做一个夸张的展示:

print("===================================================================")
print("___Unscaled, continous encoded Xgb Classifier Accuracy___")
print("_________________________",accuracy_rf,"______________________")
print("===================================================================")

这产生了:

===================================================================
___Unscaled, continous encoded Random Forest Classifier Accuracy___
_________________________ 0.8749038445433443 ______________________
===================================================================

在这之后,我回去清理了数据——稍微深入一点,我最终回去删除了“时间”一栏,因为害怕泄露。此外,我优化了一些超参数,以小幅提高精度,最高可达:

===================================================================
_______________________Xgb Classifier Accuracy______________________
_________________________ 0.9118407445708376 ______________________
===================================================================

这表明,当数据猴子不断提醒你不断清理数据的重要性时,它们可能终究是正确的。

探索模型特征

在获得一个相当准确的模型后,我决定在将所有这些都转移到 Dash-app 之前,用 Plotly 对数据进行更多的探索,从一些幅度的依赖关系图开始。

虽然这肯定不是该项目的必要获得,也没有必要说明任何不可能假设的事情,但它确实显示了一些关于这个特性在实际预测中有多少作用的事情,并且看到它与其他特性相比的位置是非常令人惊讶的。接下来,a 用各州的震级标绘了一些点:

我认为这可能是一个更重要的可视化,因为现在我们可以看到为什么多数类不是一个非常准确的预测器,通常某一级别的地震在位置上是一致的,至少在某种程度上是如此。此外,值得注意的是,气泡的大小是地震的间隙,(这在悬停数据中更明显,在停滞的截图中不太明显)。在 3.0-4.0 的震级范围内,加利福尼亚州不太可能发生地震,被多米尼加共和国忽视了,该国也没有发生-1.0-2.5 的地震。

虽然这看起来像是旧闻,特别是对那些有领域知识的人来说,但了解一下为什么准确度可以如此容易地非常高是非常有趣的,尽管该模型需要的工作比清理数据要少。

当然,下一步是将这个模型放入 Dash 应用程序,提供一个用户友好的体验,使用这个模型预测地震的准确率达到 91%。但是我认为现在是提供一个笔记本链接的好时机,它也将出现在页脚,因为我们正在从笔记本转移到 flask/dash。

创建一个 Plotly-Dash 应用程序

对我来说,这是这样一个项目的另一部分,我觉得比清理数据更有趣。很多时候,整天埋头于笔记本会变得乏味,感觉有点做作。当然,我不得不从矢量图形开始,但是我是数据科学家,不是图形设计师,所以我不太热衷于提供更多关于 Adobe Illustrator 设计的信息。

为了创建 Dash 应用程序,我需要知道更多关于数据的事情。由于数据主要是振动扰动(seismec),它主要以 double 数据类型存储,所以我的范围主要由一些非常精确的小数组成。此外,我需要分类特征观测值中的所有唯一值。我决定快速创建一个新的笔记本,我可以用它来填充我的半个屏幕,以便快速简单地将最小值和最大值输入到我的 IDE 中。这让我真的希望有一种简单的方法在 Dash 中将列表添加到组合框中。

这里有一个笔记本的链接。我使用了 Numpy idx-min 和 max,以及 Numpy unique,这两个都是熊猫内置的,这里没有什么真正严重或复杂的事情发生。

至少对于 slider 的来说,更自动化的方法是可能的,通过在 DCC 中使用 min()和 max()作为 slider 构造函数中的 max 和 min 值。

由于这不是一个情节性的 Dash 教程,(也许有一天),我不会解释如何使用 Dash 进行机器学习。但如果你愿意,你可以看看这个应用程序,它将于今天下午(9 月 27 日)推出,这里。而至于来源,这里

我真诚地希望这次旅行至少能读起来有趣,或者甚至有一点教育意义。构建 Dash 应用程序是一种简单、快速、有趣且富有创造性的方式,可以展示您的数据科学实力,同时还可以产生一个伟大的项目,并在此过程中学习一些东西。将来我肯定会在空闲时间赚更多的钱。

预测洛杉矶的电力需求——超越政府

原文:https://towardsdatascience.com/predicting-electricity-demand-in-la-outperforming-the-government-a0921463fde8?source=collection_archive---------13-----------------------

介绍

这是一篇小博文,描述了我为 Springboard 的数据科学职业轨道所做的第一个顶点项目。我比较了一些根据天气特征预测电力需求的机器学习模型。我发现大多数模型对电力需求的预测比政府预测要准确 5%。查看我的 GitHub 库,获取该项目的代码、图表和数据集。

数据收集和分析

为了训练和测试我的模型,我使用了 2015 年 7 月至 2018 年 9 月洛杉矶约 27,000 个小时的电力需求测量值。该数据由洛杉矶水电部记录,并通过能源信息管理局的公共 API 发布。

对于天气数据,我从美国国家海洋和大气管理局(NOAA)网站检索每小时的当地气候数据(LCD) 。在 LCD 数据集中,我们有熟悉的测量方法,如压力和温度,以及不太直接相关的量,如风向。增加了一个额外的功能:每小时的冷却和加热程度。加热/冷却度天数分别是低于/高于 65 华氏度(负值设置为零)的度数,代表该温度所需的加热/冷却总量。我们特别预计 CDD 将成为洛杉矶电力需求的强劲代表,因为洛杉矶的主要能源消耗在夏季会降温。图 1 显示了 2015 年 7 月至 2018 年 9 月 LA 的电力需求时间流程。请注意夏季的需求高峰。

Figure 1. Electricity demand versus time in LA. Note the sinusoidal pattern, with the summer season experiencing more demand because of air conditioning.

经过一天的初步清理和准备,我进行了一些探索性的数据分析,以查看独立变量(天气)和非独立变量(电力)之间的初始关系。图 2 显示了各特性与电力需求之间的皮尔逊相关系数图 3r。我们发现,日冷却度天数与电力需求有很强的正相关关系——绝大多数电力使用是在洛杉矶的夏季用于冷却。

# Code to produce figure 2
import pandas# df is our main dataset with weather + electricity data
print('DEMAND CORRELATIONS (PEARSON) FOR LA')
print(df.corr()['demand'].sort_values(ascending=False)[1:])

Figure 2. Pearson correlation coefficients for all weather features with electricity demand. Cooling degrees is a strong predictor of electricity demand.

# Code to produce figure 3
import scipy
import pandas as pd# get r^2 values per column and print
demand_r = {}
for col in df.columns:
 if col != 'demand':
  slope, intercept, r_value, p_value, std_err = scipy.stats.stats.linregress(df['demand'], df[col])
  demand_r[col] = r_value**2print('DEMAND CORRELATIONS (r^2) FOR LA')
demand_r_df = pd.DataFrame({'col': demand_r.keys(), 'r^2': demand_r.values()})
print(demand_r_df.sort_values(by='r^2', ascending=False))

Figure 3. values for all weather features with electricity demand.

我们的一些天气特征有共线性;有三个压力特征(气象站、海平面、高度计)和另外三个温度特征(湿球、干球、露点)。共线性增加了预测建模的难度,因此我们删除了具有最低 r 值的两个温度和压力特征。

由于人们在白天和晚上的行为是不同的,不管那天有多热或多冷,我添加了一个“hourlytimeofday”列,代表白天或晚上(早上 6 点到下午 6 点之间为 0,其他时间为 1)。温度数据对于识别加热和冷却峰值很重要,但是回归可以开始依赖温度作为白天的代理。这个特征大大增加了我们的多元回归 r,表明了它的重要性。

在使用其他机器学习技术之前,我们对所有特征与电力需求进行多重普通最小二乘(OLS)回归,以隔离不重要的特征。图 4 显示了结果。高 p 值特征证实了我们的直觉——加热对洛杉矶并不重要。天空条件,即云覆盖和风速,直观上不是预测电力需求的重要特征。这些不太有用的要素由高 p 值表示,p 值大于 0.1 的任何要素都将从数据集中删除。

Figure 4. Multivariate OLS regression for all the features + constant. Shown are a list of coefficients with errors, t-statistics, confidence intervals.

机器学习建模

现在我们为机器学习建模准备数据集。我们的第一步是将时间序列数据转换成适合监督学习的形式。从这篇博文中大量提取,我转换了我们的数据集,这样我使用了前一时间步的所有特征(即电力需求加上所有天气特征)来预测当前时间步的电力需求由于前一小时的电力需求似乎是当前电力需求的相关代理,因此该过程似乎是直观的。在以这种格式重新构建我们的数据后,我们将数据分成训练和测试集,然后开始评估我们的机器学习模型。为了使分析与我们后来使用的递归神经网络和长短期记忆一致,我们将第一年的数据指定为训练集,其余数据指定为测试集(约 32%训练和约 68%测试)。这种分割避免了过度拟合,并且在训练和测试上更传统的 80/20 分割产生了几乎相同的模型性能。建模的一般过程是:1 .)在所有功能上使用最小-最大缩放器以使它们更具可比性,2。)适合训练集,以及 3 .)计算测试集上的相关度量,并在最后比较所有模型。在我们的分析中,我们没有对模型进行超参数调整(sklearn 的默认设置),但仍然取得了非常显著的结果。

我使用 sklearn 模块训练和测试了大量的标准机器学习模型,如上所述。建模的具体过程和结果可以在本 IPython 笔记本中找到。对于直接开箱即用的模型,我获得了令人印象深刻的准确性,在我尝试的一半以上的模型中, r 值超过 0.95。此外,训练和测试的 r 值相差不超过百分之几,这表明我没有过度适应。

除了开箱即用的模型,我们还使用了循环神经网络(RNN)和长短期记忆(LSTM)。本 IPython 笔记本中的详细介绍了 RNNs 和 LSTM 的基础知识以及建模过程。使用这个模型,我获得了与我之前测试的最好的机器学习模型相当的性能。图 5 显示了使用 LSTM 建模的训练集和测试集的损失(平均绝对误差)与历元的关系。测试损失仍然略高于训练损失,表明模型没有过度拟合。

*# Code to produce figure 5
from keras.models import Sequential
from keras.layers import Dense, LSTM
import matplotlib.pyplot as plt# design network
model = Sequential()
model.add(LSTM(50, input_shape=(train_X.shape[1], train_X.shape[2])))
model.add(Dense(1))
model.compile(loss='mae', optimizer='adam')
# fit network
history = model.fit(train_X, train_y, epochs=50, batch_size=72, validation_data=(test_X, test_y), verbose=2, shuffle=False)
# plot history
plt.plot(history.history['loss'], label='train')
plt.plot(history.history['val_loss'], label='test')
plt.xlabel('Epoch')
plt.ylabel('Loss (MAE)')
plt.legend()
plt.show()*

Figure 5. Loss measured in mean-absolute error (MAE) versus epoch for both training and testing of our LSTM network. Testing error remains slightly above training, indicating that we are not overfitting. The LSTM network was optimized using the Adam implementation of stochastic gradient descent with MAE loss.

EIA 还提供了一天前的电力需求预测,我们希望将其与我们的模型进行比较。EIA 用各种方法预测需求。天气预报很大程度上取决于天气预报和一周中的第。使用相同的测试集,我们发现 EIA 的前一天需求预测的准确性比我们的所有模型都差,只有一个模型除外,这表明了简单、开箱即用的机器学习模型在回归问题上的能力。

结果汇总在按 r 排序的表 1 中。总的来说,集成机器学习模型比其他模型表现得更好,尽管简单的线性回归本身表现得非常好。所有模型的训练和测试误差保持在彼此的百分比水平之内,这表明我们没有过度拟合数据。由于交叉验证方法在处理相关的时间流数据时变得更加棘手,我们在分析中省略了交叉验证,以保持所有模型的可比性和一致性。我们发现梯度增强模型具有最好的性能,尽管前五个模型的性能都相似(它们之间的性能差异约为 1%)。

Table 1. Table of results of machine learning models. The best performing model (gradient boosting) is highlighted.

选择了性能最佳的模型(梯度推进)后,我们现在想看看哪些特性在预测电力需求时最为重要。这显示在图 6 中。“特征重要性被计算为通过到达该节点的概率加权的节点杂质的减少。节点概率可以通过到达该节点的样本数除以样本总数来计算前一小时的需求是预测当前小时需求的最重要特征,但如我们所料,其他替代因素如降温天数也是一个强有力的预测因素。

Figure 6. Feature importance for each feature in the gradient boosting model. Demand at the previous time step is four times more important than cooling degree days. Reframing our supervised learning problem in this way greatly improved the predictive power of the models.

结论

我们表明,通过机器学习,超越 EIA 的日前电力需求预测实际上相当容易。值得注意的是,除了一个模型(k-NN)之外,我们的所有模型都比 EIA 的模型表现得更好。来自公共数据源的相对简单的数据采集、清理和机器学习建模导致比传统方法更精确的建模。我们报告说,与 EIA 的模型相比,总体性能提高了约 5%;能源公司可以利用这些模型更好地应对高需求电力高峰,并最终增加利润。既然已经确定了最佳模型,那么可以执行额外的超参数调整和交叉验证技术来提高性能,尽管这种提高很可能是微不足道的,并且由于我们的模型已经优于 EIA 的模型,所以我们在没有调整的情况下得出结论。请查看这个项目的 GitHub以获得更详细的报告、代码、情节和文档。

利用向量误差修正模型预测方股票价格

原文:https://towardsdatascience.com/predicting-fang-stock-prices-ecac4ddd27c1?source=collection_archive---------10-----------------------

Source: All images in this article were generated by the author in R using dygraphs

基于向量误差修正模型的协整时间序列数据建模

金融数据是时间序列数据最著名的资源。一个多世纪以来,股票价格的记录被一丝不苟地记录下来,提供了丰富的数据集。所谓的“方”公司,即脸书、亚马逊、网飞和谷歌/Alphabet 的股票因其令人印象深刻的回报率和这些高科技公司的“酷”因素而受到了很多关注。但是这些股票真的相互关联吗?或者这是华尔街营销一堆不相关商品的一个例子?还有一种可能是,投资者认为他们之间存在关联,这足以导致他们的股票一起波动。即使我们不知道它们为什么一起移动,如果它们一起移动,我们至少可以确定你是否可以预测一只股票和其他股票。

我从雅虎财经获得了方股票的收盘价数据。脸书的首次公开募股是在 2012 年,所以这是样本期的限制因素。我们的样本期从 2012 年 5 月 18 日到 2019 年 8 月 23 日。我截掉了 2019 年的所有交易日作为测试数据集,把剩下的观测值留给训练数据集。这意味着数据集缺乏这些公司的股票在衰退中如何表现的数据。不管怎样,我继续我的分析。我测试了整合度、格兰杰因果关系和约翰森整合。基于这些结果,我继续进行向量误差修正模型(VECM),这是一种同时估计至少有一个协整关系的多个时间序列的技术。

平稳性测试

平稳性是指时间序列的均值方差不依赖于时间。当用图表表示时,它看起来像白噪声。由于对非平稳时间序列建模所引起的几个问题,主要是自相关,你需要在建模之前检查一个时间序列是否是平稳的。如果它不是静止的,你需要通过差分得到它。

上图显示了 4 个绝对看起来不稳定的时间序列。但是,在继续之前,我们需要对它进行正式测试。

无差异股票的自相关函数(ACF)图表明,股票肯定不是平稳的。差异变量的 ACF 看起来可能是稳定的。这表明每只股票都是综合 I(1)。增强的 Dickey-Fuller (ADF)测试可以证实这一点。

脸书的自相关函数图

亚马逊的自相关函数图

网飞的自相关函数图

谷歌的自相关函数图

无差异和有差异的 ADF 测试的输出强烈支持股票是整合的 I(1)的假设。在建模之前,需要对这些股票价格进行一次区分。许多用于集成时间序列模型的时间序列包会自动完成这项工作。

针对脸书的增强迪基-富勒测验

Non-Differenced## Augmented Dickey-Fuller Test 
## alternative: stationary 
##  
## Type 1: no drift no trend 
##      lag   ADF p.value
## [1,]   0 0.405   0.760
## [2,]   1 0.441   0.771
## [3,]   2 0.483   0.783
## [4,]   3 0.508   0.790
## [5,]   4 0.542   0.800
## [6,]   5 0.608   0.819
## [7,]   6 0.639   0.828
## [8,]   7 0.692   0.843
## Type 2: with drift no trend 
##      lag   ADF p.value
## [1,]   0 -1.11   0.661
## [2,]   1 -1.16   0.644
## [3,]   2 -1.19   0.634
## [4,]   3 -1.17   0.642
## [5,]   4 -1.14   0.651
## [6,]   5 -1.14   0.651
## [7,]   6 -1.19   0.635
## [8,]   7 -1.19   0.635
## Type 3: with drift and trend 
##      lag   ADF p.value
## [1,]   0 -2.42   0.399
## [2,]   1 -2.25   0.469
## [3,]   2 -2.08   0.541
## [4,]   3 -2.01   0.572
## [5,]   4 -1.88   0.628
## [6,]   5 -1.60   0.745
## [7,]   6 -1.47   0.803
## [8,]   7 -1.28   0.881
## ---- 
## Note: in fact, p.value = 0.01 means p.value <= 0.01Differenced## Augmented Dickey-Fuller Test 
## alternative: stationary 
##  
## Type 1: no drift no trend 
##      lag   ADF p.value
## [1,]   0 -41.5    0.01
## [2,]   1 -29.8    0.01
## [3,]   2 -24.6    0.01
## [4,]   3 -21.7    0.01
## [5,]   4 -20.1    0.01
## [6,]   5 -18.3    0.01
## [7,]   6 -17.3    0.01
## [8,]   7 -16.4    0.01
## Type 2: with drift no trend 
##      lag   ADF p.value
## [1,]   0 -41.5    0.01
## [2,]   1 -29.8    0.01
## [3,]   2 -24.6    0.01
## [4,]   3 -21.7    0.01
## [5,]   4 -20.2    0.01
## [6,]   5 -18.4    0.01
## [7,]   6 -17.4    0.01
## [8,]   7 -16.5    0.01
## Type 3: with drift and trend 
##      lag   ADF p.value
## [1,]   0 -41.5    0.01
## [2,]   1 -29.8    0.01
## [3,]   2 -24.6    0.01
## [4,]   3 -21.7    0.01
## [5,]   4 -20.2    0.01
## [6,]   5 -18.4    0.01
## [7,]   6 -17.4    0.01
## [8,]   7 -16.5    0.01
## ---- 
## Note: in fact, p.value = 0.01 means p.value <= 0.01

亚马逊增强的迪基-富勒测试

Non-Differenced## Augmented Dickey-Fuller Test 
## alternative: stationary 
##  
## Type 1: no drift no trend 
##      lag  ADF p.value
## [1,]   0 1.54   0.969
## [2,]   1 1.60   0.974
## [3,]   2 1.60   0.974
## [4,]   3 1.67   0.977
## [5,]   4 1.76   0.980
## [6,]   5 1.62   0.975
## [7,]   6 1.63   0.976
## [8,]   7 1.75   0.980
## Type 2: with drift no trend 
##      lag      ADF p.value
## [1,]   0 -0.03873   0.953
## [2,]   1  0.00509   0.956
## [3,]   2  0.00206   0.956
## [4,]   3  0.04246   0.959
## [5,]   4  0.08628   0.963
## [6,]   5  0.01241   0.957
## [7,]   6  0.02187   0.957
## [8,]   7  0.07776   0.962
## Type 3: with drift and trend 
##      lag   ADF p.value
## [1,]   0 -1.93   0.605
## [2,]   1 -1.90   0.619
## [3,]   2 -1.89   0.622
## [4,]   3 -1.86   0.637
## [5,]   4 -1.80   0.663
## [6,]   5 -1.87   0.631
## [7,]   6 -1.87   0.634
## [8,]   7 -1.80   0.664
## ---- 
## Note: in fact, p.value = 0.01 means p.value <= 0.01Differenced## Augmented Dickey-Fuller Test 
## alternative: stationary 
##  
## Type 1: no drift no trend 
##      lag   ADF p.value
## [1,]   0 -41.9    0.01
## [2,]   1 -29.1    0.01
## [3,]   2 -24.3    0.01
## [4,]   3 -21.7    0.01
## [5,]   4 -17.8    0.01
## [6,]   5 -16.4    0.01
## [7,]   6 -15.9    0.01
## [8,]   7 -16.9    0.01
## Type 2: with drift no trend 
##      lag   ADF p.value
## [1,]   0 -41.9    0.01
## [2,]   1 -29.2    0.01
## [3,]   2 -24.4    0.01
## [4,]   3 -21.8    0.01
## [5,]   4 -17.9    0.01
## [6,]   5 -16.5    0.01
## [7,]   6 -16.0    0.01
## [8,]   7 -17.1    0.01
## Type 3: with drift and trend 
##      lag   ADF p.value
## [1,]   0 -41.9    0.01
## [2,]   1 -29.2    0.01
## [3,]   2 -24.5    0.01
## [4,]   3 -21.8    0.01
## [5,]   4 -18.0    0.01
## [6,]   5 -16.5    0.01
## [7,]   6 -16.1    0.01
## [8,]   7 -17.1    0.01
## ---- 
## Note: in fact, p.value = 0.01 means p.value <= 0.01

为网飞增加了迪基-富勒测验

Non-Differenced## Augmented Dickey-Fuller Test 
## alternative: stationary 
##  
## Type 1: no drift no trend 
##      lag   ADF p.value
## [1,]   0 0.837   0.885
## [2,]   1 0.833   0.884
## [3,]   2 0.799   0.874
## [4,]   3 0.701   0.846
## [5,]   4 0.670   0.837
## [6,]   5 0.880   0.897
## [7,]   6 0.890   0.900
## [8,]   7 0.946   0.907
## Type 2: with drift no trend 
##      lag    ADF p.value
## [1,]   0 -0.516   0.873
## [2,]   1 -0.517   0.873
## [3,]   2 -0.541   0.864
## [4,]   3 -0.598   0.844
## [5,]   4 -0.618   0.837
## [6,]   5 -0.492   0.882
## [7,]   6 -0.486   0.884
## [8,]   7 -0.455   0.895
## Type 3: with drift and trend 
##      lag   ADF p.value
## [1,]   0 -2.00   0.576
## [2,]   1 -2.01   0.574
## [3,]   2 -2.04   0.562
## [4,]   3 -2.14   0.517
## [5,]   4 -2.18   0.501
## [6,]   5 -1.95   0.597
## [7,]   6 -1.94   0.602
## [8,]   7 -1.89   0.623
## ---- 
## Note: in fact, p.value = 0.01 means p.value <= 0.01Differenced## Augmented Dickey-Fuller Test 
## alternative: stationary 
##  
## Type 1: no drift no trend 
##      lag   ADF p.value
## [1,]   0 -40.6    0.01
## [2,]   1 -28.2    0.01
## [3,]   2 -21.9    0.01
## [4,]   3 -18.8    0.01
## [5,]   4 -19.2    0.01
## [6,]   5 -17.5    0.01
## [7,]   6 -16.5    0.01
## [8,]   7 -16.7    0.01
## Type 2: with drift no trend 
##      lag   ADF p.value
## [1,]   0 -40.6    0.01
## [2,]   1 -28.2    0.01
## [3,]   2 -21.9    0.01
## [4,]   3 -18.8    0.01
## [5,]   4 -19.3    0.01
## [6,]   5 -17.6    0.01
## [7,]   6 -16.6    0.01
## [8,]   7 -16.9    0.01
## Type 3: with drift and trend 
##      lag   ADF p.value
## [1,]   0 -40.6    0.01
## [2,]   1 -28.2    0.01
## [3,]   2 -21.9    0.01
## [4,]   3 -18.8    0.01
## [5,]   4 -19.3    0.01
## [6,]   5 -17.6    0.01
## [7,]   6 -16.6    0.01
## [8,]   7 -16.9    0.01
## ---- 
## Note: in fact, p.value = 0.01 means p.value <= 0.01

谷歌的增强 Dickey-Fuller 测试

Non-Differenced## Augmented Dickey-Fuller Test 
## alternative: stationary 
##  
## Type 1: no drift no trend 
##      lag  ADF p.value
## [1,]   0 1.21   0.940
## [2,]   1 1.15   0.933
## [3,]   2 1.23   0.943
## [4,]   3 1.16   0.935
## [5,]   4 1.20   0.939
## [6,]   5 1.28   0.950
## [7,]   6 1.36   0.956
## [8,]   7 1.34   0.954
## Type 2: with drift no trend 
##      lag   ADF p.value
## [1,]   0 -1.07   0.677
## [2,]   1 -1.07   0.679
## [3,]   2 -1.06   0.680
## [4,]   3 -1.07   0.678
## [5,]   4 -1.07   0.676
## [6,]   5 -1.07   0.677
## [7,]   6 -1.04   0.687
## [8,]   7 -1.06   0.680
## Type 3: with drift and trend 
##      lag   ADF p.value
## [1,]   0 -3.52  0.0404
## [2,]   1 -3.64  0.0275
## [3,]   2 -3.49  0.0430
## [4,]   3 -3.63  0.0292
## [5,]   4 -3.56  0.0364
## [6,]   5 -3.36  0.0598
## [7,]   6 -3.22  0.0851
## [8,]   7 -3.26  0.0768
## ---- 
## Note: in fact, p.value = 0.01 means p.value <= 0.01Differenced## Augmented Dickey-Fuller Test 
## alternative: stationary 
##  
## Type 1: no drift no trend 
##      lag   ADF p.value
## [1,]   0 -39.6    0.01
## [2,]   1 -29.5    0.01
## [3,]   2 -23.1    0.01
## [4,]   3 -20.4    0.01
## [5,]   4 -19.1    0.01
## [6,]   5 -18.0    0.01
## [7,]   6 -16.3    0.01
## [8,]   7 -16.8    0.01
## Type 2: with drift no trend 
##      lag   ADF p.value
## [1,]   0 -39.6    0.01
## [2,]   1 -29.6    0.01
## [3,]   2 -23.2    0.01
## [4,]   3 -20.5    0.01
## [5,]   4 -19.2    0.01
## [6,]   5 -18.1    0.01
## [7,]   6 -16.4    0.01
## [8,]   7 -16.9    0.01
## Type 3: with drift and trend 
##      lag   ADF p.value
## [1,]   0 -39.6    0.01
## [2,]   1 -29.6    0.01
## [3,]   2 -23.2    0.01
## [4,]   3 -20.5    0.01
## [5,]   4 -19.2    0.01
## [6,]   5 -18.1    0.01
## [7,]   6 -16.4    0.01
## [8,]   7 -16.9    0.01
## ---- 
## Note: in fact, p.value = 0.01 means p.value <= 0.01

格兰杰因果关系

众所周知,在观察数据中建立因果关系非常困难。格兰杰因果关系是一个较低的酒吧。它只是说,如果 X 的以前值可以预测 y 的未来值,那么 X 格兰杰原因是 y。它是通过估计 X 对 y 的滞后值的回归并执行 f 检验来执行的。如果 p 值足够小,你拒绝零假设,即 X 的所有滞后值的系数都是 0。简单地说,小 p 值表示滞后的 x 对未来的 y 有预测能力,并有相应的置信度。

下面的输出简单地说明了 FANG 的每只股票都比其他股票具有预测能力。这意味着同步建模是一个好方法。

## Granger causality test
## 
## Model 1: j ~ Lags(j, 1:30) + Lags(i, 1:30)
## Model 2: j ~ Lags(j, 1:30)
##   Res.Df  Df      F    Pr(>F)    
## 1   1574                         
## 2   1604 -30 2.9968 1.226e-07 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## Granger causality test
## 
## Model 1: j ~ Lags(j, 1:30) + Lags(i, 1:30)
## Model 2: j ~ Lags(j, 1:30)
##   Res.Df  Df      F    Pr(>F)    
## 1   1574                         
## 2   1604 -30 2.4849 1.635e-05 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## Granger causality test
## 
## Model 1: j ~ Lags(j, 1:30) + Lags(i, 1:30)
## Model 2: j ~ Lags(j, 1:30)
##   Res.Df  Df      F   Pr(>F)    
## 1   1574                        
## 2   1604 -30 2.5479 9.15e-06 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## Granger causality test
## 
## Model 1: j ~ Lags(j, 1:30) + Lags(i, 1:30)
## Model 2: j ~ Lags(j, 1:30)
##   Res.Df  Df      F   Pr(>F)    
## 1   1574                        
## 2   1604 -30 2.5026 1.39e-05 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## Granger causality test
## 
## Model 1: j ~ Lags(j, 1:30) + Lags(i, 1:30)
## Model 2: j ~ Lags(j, 1:30)
##   Res.Df  Df     F    Pr(>F)    
## 1   1574                        
## 2   1604 -30 3.838 2.082e-11 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## Granger causality test
## 
## Model 1: j ~ Lags(j, 1:30) + Lags(i, 1:30)
## Model 2: j ~ Lags(j, 1:30)
##   Res.Df  Df      F    Pr(>F)    
## 1   1574                         
## 2   1604 -30 2.0986 0.0004837 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## Granger causality test
## 
## Model 1: j ~ Lags(j, 1:30) + Lags(i, 1:30)
## Model 2: j ~ Lags(j, 1:30)
##   Res.Df  Df      F   Pr(>F)    
## 1   1574                        
## 2   1604 -30 2.0299 0.000852 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## Granger causality test
## 
## Model 1: j ~ Lags(j, 1:30) + Lags(i, 1:30)
## Model 2: j ~ Lags(j, 1:30)
##   Res.Df  Df      F    Pr(>F)    
## 1   1574                         
## 2   1604 -30 3.3817 2.497e-09 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## Granger causality test
## 
## Model 1: j ~ Lags(j, 1:30) + Lags(i, 1:30)
## Model 2: j ~ Lags(j, 1:30)
##   Res.Df  Df      F    Pr(>F)    
## 1   1574                         
## 2   1604 -30 3.0723 5.784e-08 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## Granger causality test
## 
## Model 1: j ~ Lags(j, 1:30) + Lags(i, 1:30)
## Model 2: j ~ Lags(j, 1:30)
##   Res.Df  Df      F   Pr(>F)   
## 1   1574                       
## 2   1604 -30 1.7205 0.009195 **
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## Granger causality test
## 
## Model 1: j ~ Lags(j, 1:30) + Lags(i, 1:30)
## Model 2: j ~ Lags(j, 1:30)
##   Res.Df  Df     F   Pr(>F)   
## 1   1574                      
## 2   1604 -30 1.728 0.008712 **
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## Granger causality test
## 
## Model 1: j ~ Lags(j, 1:30) + Lags(i, 1:30)
## Model 2: j ~ Lags(j, 1:30)
##   Res.Df  Df      F    Pr(>F)    
## 1   1574                         
## 2   1604 -30 3.3607 3.101e-09 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

协整检验

协整是我反复提到的一个词。这仅仅意味着我们的股票中至少有两只之间存在长期关系。协整检验被称为 Johansen 检验,以开发它的统计学家/计量经济学家的名字命名。它可以用两种方式来表述,迹检验和最大特征值检验,它们有不同的假设。细节是相当技术性的,所以 TL;它的 dr 版本是,如果长期关系矩阵的秩 r 为 0 或等于被测试的时间序列的数量,则不存在协整,不同的建模技术是合适的。如果 r 大于 0 且小于时间序列数,则存在 r 个协整关系。

从下面的输出中,我们可以非常确定存在大于 0 的协整关系,但是我们不能在任何合理的统计显著性水平上确定存在大于 1 的协整关系。因此,我们在建模时将假设 FANG 股票之间存在一种协整关系。

###################### 
# Johansen-Procedure # 
######################Test type: trace statistic , with linear trend in cointegrationEigenvalues (lambda):
[1] 2.054935e-02 1.133947e-02 6.555429e-03 4.658323e-03 -2.362375e-18Values of teststatistic and critical values of test:test 10pct 5pct 1pct
r <= 3 | 7.63 10.49 12.25 16.26
r <= 2 | 18.39 22.76 25.32 30.45
r <= 1 | 37.03 39.06 42.44 48.45
r = 0 | 70.98 59.14 62.99 70.05Eigenvectors, normalised to first column:
(These are the cointegration relations)Close.fb.l30 Close.amzn.l30 Close.nflx.l30 Close.googl.l30 trend.l30
Close.fb.l30 1.000000000 1.0000000 1.0000000 1.000000000 1.0000000
Close.amzn.l30 -0.004276658 1.0364900 0.2719707 0.102530094 -0.1327168
Close.nflx.l30 0.159547714 -1.8623141 -1.2702538 -0.746632744 0.5949550
Close.googl.l30 -0.143075556 -1.9821424 0.4921765 -0.150661845 0.2315739
trend.l30 -0.054567826 0.2916345 -0.3575875 0.005284555 -0.3248181Weights W:
(This is the loading matrix)Close.fb.l30 Close.amzn.l30 Close.nflx.l30 Close.googl.l30 trend.l30
Close.fb.d -0.00219271 -0.0006776299 -0.003233474 0.00356431 1.732653e-17
Close.amzn.d 0.18630132 -0.0084137379 -0.008556667 0.01693689 -8.853648e-16
Close.nflx.d 0.02105269 -0.0006557141 0.001122418 0.01106052 -1.271123e-16
Close.googl.d 0.10670446 0.0031632593 -0.012012380 0.01474776 -9.437844e-16

选择模型的滞后数量

为了选择包含在最终模型中的滞后数量,我选取了我的训练数据的一个小子集;从 2018 年 5 月 9 日开始到年底。这个日期在小型测试数据集中留下了 163 个观察值,这与测试数据集中的观察值数量相等。然后,我用 1 到 30 个小训练数据估计模型,然后用该模型估计小测试数据集。我计算了每只股票的平均绝对百分比误差(MAPE ),并将它们相加。然后我选择了使每只股票的 MAPE 总和最小的滞后数。最小化该调谐数据集中的总 MAPE 的滞后数量是 5。

模型有多精确?

评估模型准确性时,进行基线比较会有所帮助。对于分类,它可能是总是预测模式的误分类率。对于横截面回归,它可以是因变量的平均值。对于时间序列,我希望我的预测是前一段时间的值。这意味着我的基准准确度等级是说明天的股价就是今天的股价的准确度。

这样,脸书的基准 MAPE 为 1.36%,亚马逊为 1.20%,网飞为 1.70%,谷歌为 1.15%。对于一种不依赖复杂统计模型的预测方法来说,这还不算太糟糕。VECM 能胜过这个吗?

## [1] "Close.fb"
## [1] 1.360928
## [1] "Close.amzn"
## [1] 1.203783
## [1] "Close.nflx"
## [1] 1.697295
## [1] "Close.googl"
## [1] 1.150686

现在我们可以估计 VECM。它将具有下面的简化形式,其中α的列向量是对长期调整的速度,β的行向量包含长期关系系数。

Formulation of the VECM with L lags

是的。VECM 得出脸书的 MAPE 为 0.30%,亚马逊为 0.28%,网飞为 0.40%,谷歌为 0.29%,这还不算太差。这些 MAPE 大约是基线错误率的四分之一。

## [1] "Close.fb"
## [1] 0.2972355
## [1] "Close.amzn"
## [1] 0.277129
## [1] "Close.nflx"
## [1] 0.3978918
## [1] "Close.googl"
## [1] 0.2943474

绘制每只股票的预测收盘价和实际收盘价表明,预测股价非常接近实际股价。由于股票价格比例的差异,每只股票都有自己的图表,以便更好地查看拟合值和预测值。

关闭思路

所以这个模型表现不错。有什么意义?该模型预测收盘股价。因此,如果交易价格远低于预测的收盘价,远低于预测的收盘价是置信区间和你的风险承受能力的函数,在收盘前买入并卖出股票是有意义的;前提是没有充足的理由让股价持续下跌。如果价格远高于预测的收盘价格,卖出它是有意义的()我不是在提供财务建议。只是陈述如果我愿意,我会如何使用这个模型。)。只有当你是日内交易者时,这些行为才有意义,但是这个模型的洞察力是有限的,因为它只预测前一天。

模型的准确性告诉我们模型是适合方股票的。从这里开始,重新构建模型来预测更远的时间,以决定一个期权是否被正确定价是一个合乎逻辑的地方。记住预测越来越远的未来是困难的,这一点很重要。随着人们对未来的预测越来越远,误差带变得越来越宽。在实施交易策略之前,严格的回溯测试和过去的表现不能保证未来的表现是关键。

我希望你对协整时间序列和 VEC 建模有所了解。还有很多工作没有在这里讨论,比如解释长期关系矩阵和脉冲响应函数。我希望这个博客能激发人们对此类数据的兴趣。

我的代码和数据可以在https://github.com/jkclem/FANG-stock-prediction获得。

从赛季表现预测花样滑冰世锦赛排名

原文:https://towardsdatascience.com/predicting-figure-skating-championship-ranking-from-season-performances-fc704fa7971a?source=collection_archive---------14-----------------------

体育分析

第 1 部分:线性模型

背景

几年前我开始关注花样滑冰,现在已经成为这项运动的忠实粉丝。与许多运动类似,花样滑冰比赛分为两大类:

  • 每年 10 月至次年 3 月的一系列小型赛事,包括:在 6 个不同国家(美、加、俄、法、中、日)举行的大奖赛系列赛,大奖赛系列赛 6 名最佳选手之间的大奖赛决赛比赛,欧洲运动员的欧洲锦标赛,以及其他大洲运动员的另一场比赛,适当的名称为四大洲。当然,每四年,人们熟悉的冬季奥运会会在二月举行。
  • 一年一度的世界锦标赛:这是本赛季的盛大赛事,通常在三月底举行,为本赛季画上圆满的句号。全世界大约有 24 名选手将参加每个项目(男、女)的比赛。

问题

每年,在世界锦标赛之前,互联网上都充斥着对谁将登上冠军领奖台的预测。做出预测的显而易见的来源是赛季早期比赛中运动员的分数,根据这些分数对运动员进行排名的显而易见的方法是比较每个运动员在他或她之前所有比赛中的平均分数。

然而,这种方法的一个潜在问题是分数是跨不同事件平均的,没有两个事件是相同的。首先,每个项目的评判小组大不相同。另一方面,可能有一些其他因素可以系统地提高或降低某个项目的运动员分数,例如冰的情况,项目发生的时间,甚至海拔高度(运动员可能不适应高海拔的项目)。正如下面 2017 赛季男子滑冰运动员的方框图所示,每个项目的得分中心和分布可能会明显不同:

Box plot of skater scores for 2017 season events

因此,一个排名模型可以梳理出运动员效应,每个运动员的内在能力,从事件效应,一个事件对运动员表现的影响,可能会对谁在世界锦标赛中表现更好或更差做出更好的预测。

在这个项目中,我们将探索几个这样的模型来实现这个目标。你正在阅读的第一部分涉及简单的线性模型,而项目的后续部分将涵盖更复杂的模型。

数据

数据抓取

好消息是:从 2005 年(现行评分系统实施的那一年)开始的所有比赛的分数都可以通过国际滑冰联盟的官方网站获得。分数包含在普通 HTML 页面的表格中,所以使用 Beautiful Soup 来抓取它们就足够了。

坏消息是:每个赛事都有自己的分数网页。正如十多年来不同活动组织者建立的网页所预期的那样,它们有轻微但非常令人沮丧的不同链接模板和 HTML 格式,从 2005 年的页面和 2018 年的页面可以看出。

Left: score page in 2005. Right: score page in 2018.

因此,我们非常小心地确保所有的分数都是准确的。例如,下面是几个链接模板,用于捕捉所有网页的事件分数:

**'http://www.isuresults.com/results/gp{0}{1}/CAT00{3}RS.HTM',
'http://www.isuresults.com/results/gp{0}20{1}/CAT00{3}RS.HTM',
'http://www.isuresults.com/results/gp{0}{1}{2}/CAT00{3}RS.HTM',
'http://www.isuresults.com/results/season{1}{2}/gp{0}{1}{2}/CAT00{3}RS.HTM',
'http://www.isuresults.com/results/season{1}{2}/gp{0}{1}{2}/data0{3}90.htm',
'http://www.isuresults.com/results/season{1}{2}/gp{0}20{1}/CAT00{3}RS.HTM',
'http://www.isuresults.com/results/season{1}{2}/gp{0}20{1}/data0{3}90.htm'**

链接样板中的占位符有助于识别特定事件,包括:

  • {0}:赛事类型(如'usa'代表美国大奖赛,'gpf'代表大奖赛决赛,以此类推)
  • {1}:季节的开始年份(如'06')
  • {2}:季节的结束年(如'07')
  • {3}:对应评分页面的选手性别('1'为男性,'2'为女性)

例如,如果{0}='gpf'{1}='06'{2}='07'{3}='1',那么相应的模板就变成了http://www.isuresults.com/results/gpf0607/CAT001RS.HTM,这是 2006-2007 赛季男子滑冰大奖赛总决赛的比分网页。如果一个模板不起作用,scraper 会沿着列表向下尝试另一个模板,直到找到一个有效的链接。

数据清理

一旦所有事件的分数被清除,它们将通过以下步骤被清除:

  1. ****删除所有多余的空格,包括不换行空格\xa0
  2. ****将溜冰者的名字从姓氏中分离出来。由于名称顺序的不一致,这是必要的:一些页面将名称存储为First Name LAST NAME,而一些存储为LAST NAME First Name。因此,将名和姓分开可以使运动员在他或她参加的所有比赛中被完全识别。值得庆幸的是,所有选手的姓氏都是大写的,只需要一些简单的正则表达式就可以进行拆分。例如,匹配运动员姓氏的正则表达式的一部分是[A-ZÄÖÜ-]+,它允许像带重音符号的PÖYKIÖ或带连字符的GILLERON-GORRY这样的姓氏被捕获。
  3. ****标准化备选名称拼写。由于运动员的数量足够少,通过他们的名字进行人工检查可以识别出运动员名字在多个项目中的不同拼写,例如名字的Min-JeongMin-Jung,或者姓氏的TUKTAMYSHEVATUKTAMISHEVA。从下面的列表中可以看出,可供选择的拼写大多是有多个名字(有时用连字符连接)的韩国滑冰运动员,夹杂着多音节的俄罗斯名字和充满元音的北欧名字。

Alternative spellings of skaters’ names (blue: male, pink: female)

数据分割

在 14 年的粗略分数中,随机 10 年的分数将用于训练和验证不同的排名模型,而剩余 4 年的分数将用于在最后对模型进行基准测试。这个项目的后面部分将专注于更复杂的选手排名模型,我将利用 4 年的测试时间在项目的最后部分对所有模型进行基准测试。

排名度量

在我们比较不同的模型对世界锦标赛选手的排名之前,让我们首先定义我们如何评价一个排名比另一个更好或更差。

我们知道,一个好的预测排名是一个与最终世界锦标赛结果非常接近的排名。可以衡量两个排名之间相似性的一个简单度量就是 肯德尔排名相关系数 (也叫肯德尔的τ系数)。让我们通过一个简单的例子来看看这个指标是如何工作的:

**Predicted ranking   Actual ranking  
  1\. AARON            1\. BRUCE                 
  2\. BRUCE            2\. AARON                   
  3\. CRAIG            3\. CRAIG**

上表显示了一些预测排名(左)和世界锦标赛后的实际排名(右),每个从最好到最差,为 3 个虚构的选手。比较这两个排名,我们可以看到预测的排名错误地将 Aaron 置于 Bruce 之上。然而,克雷格的排名是正确的:他在两个排名中都低于其他两名选手。

肯德尔τ系数(𝜏)可以用下面的公式量化上述描述:

n: number of ranked skaters

该公式的工作原理如下:

  • 根据AARON > BRUCE > CRAIG的预测排名,可以生成有序的选手对,其中选手对中第一个选手的排名高于第二个选手。对于 3 个溜冰者(n = 3)的排名,我们最终得到 3×(3–1)/2 = 3 个有序对。它们是:
**(AARON, BRUCE), (AARON, CRAIG), (BRUCE, CRAIG)**
  • 在这 3 对有序的组合中,第一对(AARON, BRUCE)不再保持BRUCE > AARON > CRAIG的冠军排名,而后面两对仍然保持。换句话说,一致对——在两种排序中都成立的有序对——的数量是 2,而不一致对——在一种排序中成立但在另一种排序中不成立的有序对——的数量是 1。
  • 因此,这两个排名的肯德尔τ系数为:

结果,在两个排序中存在的有序对越多,这两个排序之间的肯德尔τ系数就越高。两个相同的排名将有𝜏 =1,而两个完全相反的排名(想想:一个排名从最高到最低,而另一个从最低到最高)将有𝜏 = -1。因此,当与同赛季的世界冠军排名进行比较时,肯德尔的τ系数较高(更接近 1)的预测排名将优于系数较低的预测排名。

履行

在 Python 中,scipy库内置了[scipy.stats.kendalltau](https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.kendalltau.html)函数来计算系数。然而,“从零开始”实现这种计算非常容易:itertools模块中的[combinations](https://docs.python.org/2/library/itertools.html#itertools.combinations)函数将从每个排名中生成有序对,两个排名之间的一致对只不过是两组有序对的交集(通过&运算符):

**from itertools import combinations
ranking1 = ['AARON', 'BRUCE', 'CRAIG']
ranking2 = ['BRUCE', 'AARON', 'CRAIG']# Generate set of ordered pairs from each ranking
ordered_pairs1 = set(combinations(ranking1, 2))
# {('AARON', 'BRUCE'), ('AARON', 'CRAIG'), ('BRUCE', 'CRAIG')}
ordered_pairs2 = set(combinations(ranking2, 2))
# {('AARON', 'CRAIG'), ('BRUCE', 'AARON'), ('BRUCE', 'CRAIG')}# Calculate number of total, concordant & discordant pairs and tau
n_pairs = len(ordered_pairs1) # 3
n_concordant_pairs = len(ordered_pairs1 & ordered_pairs2) # 2
n_discordant_pairs = n_pairs - n_concordant_pairs # 1# Calculate Kendall's tau from pair counts
tau = (n_concordant_pairs - n_discordant_pairs) / n_pairs # 0.33**

季节平均模型(基线)

正如开始提到的,这是预测世界锦标赛上运动员排名的最简单和最明显的方法,可以通过以下方式实现:

  1. 平均每位选手在本赛季参加的所有比赛中的得分
  2. 根据这些赛季平均水平,从最高到最低对运动员进行排名,并使用该排名作为世界锦标赛的预测排名

以 2017 年男子滑冰运动员的成绩为例,我们可以看到,使用赛季平均值预测的排名与世界锦标赛的最终排名具有相对较强的相关性(见下面的排行榜对比)。和任何运动一样,都有很大的惊喜:Denis, TEN排名下降了 10 位,从第 6 位降至第 16 位,而Brendan, KERRY上升了 7 位,从第 22 位升至第 15 位。然而,快速浏览一下,赛季中顶级选手的表现往往比低级选手更稳定,这可以从排行榜顶部与底部相比相对较少的排名中看出。

Ranking by season average vs. world ranking (for male skaters in the 2017 season). Colors are based on the world championship ranking.

24 个滑手,2017 年世锦赛实际排名有 24×23/2 = 276 个可能的有序对。在这 276 对中,季节平均模型正确预测了 234 对(如Yuzuru, HANYU > Javier, FERNANDEZ),剩下 42 对预测错误(如Boyang, JIN > Nathan, CHEN))。这转化为肯德尔τ分数为(234 - 42)/276 = 0.695。对于基线模型来说还不算太差!

加性模型

理论

让我们从季节平均值的基线模型转移到其他模型,这些模型有望将事件影响与运动员影响分开。一个简单的这样的模型是假设在给定的事件中给定的溜冰者的分数是以下的:

  • 赛季中所有分数的基准分数
  • 潜在的事件分数对于该事件是唯一的,但是对于该事件的所有运动员是相同的
  • 一个潜在的滑冰者分数对于该滑冰者来说是唯一的,但是对于该滑冰者参加的所有项目来说是相同的
  • 零均值高斯噪声

这个加法模型可以用数学方法表示为:

  • y_event-skater:给定运动员在给定比赛项目中的得分
  • θ_baseline:基线得分(整个赛季得分不变)
  • θ_event:潜在事件分数(运动员之间不变)
  • θ_skater:潜在选手得分(跨赛事不变)
  • N(0, σ²):均值为零的高斯噪声

乍一看,似乎没有简单的方法来学习这些潜在分数,因为每对运动员都有不同的潜在分数组合。换句话说,不存在机器学习问题所预期的所有数据点的固定特征集。

但是,模型公式可以重写为以下形式:

这种形式的模型公式中的新术语是:

  • θ_e:事件的潜在得分e
  • I(e = event):如果事件e是给定事件,则为 1,否则为 0
  • θ_s:滑手潜在得分s
  • I(s = skater) : 1 如果滑冰者s是给定的滑冰者,否则为 0

以这种方式书写,很容易看出,对于给定的一对运动员,只有该运动员和该项目的潜在得分将计入该对运动员的实际得分,这与前面的公式完全相同。这是因为在上述公式中,那些潜在得分的系数I(e = event)I(s = skater)将为 1,而其他滑手和项目的潜在得分的系数将为 0。

同样,以这种方式书写,每个运动员的分数可以简单地认为是基线分数和零均值高斯噪声之上的二元变量I(s = skater)I(e = event)的线性组合。换句话说,问题就变成了一个简单的二元特征线性回归,其回归系数的确是所有项目和滑手的潜在得分——分别为θ_eθ_s——回归截距不过是基线得分θ_baseline

编码

我们通过一个小例子(使用 2017 赛季的男性得分)来看看这个线性回归模型是如何编码的:

  • 清理完数据后,我们以下面的数据框season_scores结束,它代表了本赛季所有赛事选手的分数(当然,除了世界锦标赛)。对于 2017 赛季,有来自 9 项赛事的 62 名选手的 120 个分数。

  • 接下来,我们使用 panda 的[get_dummies](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.get_dummies.html)函数将这个数据帧转换成包含虚拟特征的数据帧dummies,虚拟特征就是上面提到的二进制特征。
******dummies = pd.get_dummies(season_scores[['name', 'event']], prefix=['', ''], prefix_sep='', drop_first=True)******

请注意,我们必须使用函数的drop_first=True参数,因为包含所有溜冰者和事件的二元变量将导致二元特征完全共线,这对于线性回归是不允许的;这有时被称为虚拟变量陷阱。实际上,这种争论将会抛弃一个运动员和一个项目的二元特征。由于有 62 名溜冰者和 9 个事件,我们的dummies数据框将有 61 个溜冰者二进制列和 8 个事件二进制列,总共有 69 个二进制要素。可以通过下面的方法找到被丢弃的运动员和事件的身份:**

******# Get unique names of skaters and events from original data frame
unique_skaters = season_scores['name'].unique()
unique_events = season_scores['event'].unique()# Get unique names of skaters and events from dummies data frame 
dummies_skater_count = len(unique_skaters) - 1
dummies_skaters = dummies.columns[:dummies_skater_count]
dummies_events = dummies.columns[dummies_skater_count:]# Dropped skater is 'Adam, RIPPON'
dropped_skater = list(set(unique_skaters) - set(dummies_skaters))[0]# Dropped event is '4C'
dropped_event = list(set(unique_events) - set(dummies_events))[0]******

删除这两列后,得到的dummies数据表如下左图所示,其中 120 行对应于该赛季的 120 个唯一得分,69 列为二进制哑变量。所有 1 的列被附加到数据表的左侧,以对应基线得分,该基线得分出现在所有赛季得分中。因此,基线分数将只是回归系数中的一个,它现在有 70 个。

Left: feature matrix (predictors). Center: coefficient vector. Right: score vector (response). Highlighted in red is how one row of the feature matrix can make a dot product with the coefficient vector to approximate the season score of an event-skater pair (CA-Yuzuru, HANYU).

创建这个 120×70 数据表的原因是,它可以表示一个二进制特征矩阵X,当乘以表示所有 70 个回归系数(包括基线/截距)的向量θ时,它可以近似该赛季的所有得分——长度为 120 的向量y。换句话说,线性回归问题可以用矩阵形式表示为:

其中ε表示对每个赛季得分有贡献的高斯噪声。因此,回归系数的最小二乘估计可以通过求解线性回归的标准方程来轻松计算:

由于每个季节的数据都很小,如微小的 120×70 要素矩阵所示,因此在 Python 中可以很容易地求解法线方程,以返回回归系数的精确值(与梯度下降之类的东西相反)。这个的代码如下,用.T做矩阵转置,@做矩阵乘法,np.linalg.inv做矩阵求逆。

******# Create numpy feature matrix and response vector
X = dummies.values
y = season_scores['score'].values# Append column of all 1's to the left (0th-index) of feature matrix
X = np.insert(X, obj=0, values=1, axis=1)# Apply normal equation to find linear regression coefficients (including intercept)
coefs = np.linalg.inv(X.T @ X) @ (X.T @ y)******

从通过解正规方程找到的回归系数中,可以提取基线分数、溜冰者分数和项目分数。请注意,我们需要添加回运动员和事件的分数,这些分数在回归之前被删除;他们的分数为零,因为通过放弃他们,他们的分数已经被吸收到基线分数中。

******# Get baseline, skater & event scores from regression coefficients
baseline_score = coefs[0]
skater_scores = pd.Series(coefs[1:dummies_skater_count+1], index=dummies_skaters)
event_scores = pd.Series(coefs[dummies_skater_count+1:], index=dummies_events)# Add back scores of dropped skater and dropped event
skater_scores[dropped_skater] = 0
event_scores[dropped_event] = 0# Sort skater scores and event scores from highest to lowest
skater_scores.sort_values(ascending=False, inplace=True)
event_scores.sort_values(ascending=False, inplace=True)******

结果

一旦对潜在的溜冰者分数进行了排序,基于加法模型的预测排名就像将具有最高潜在分数的溜冰者排名为第一,将具有第二高潜在分数的溜冰者排名为第二一样简单,以此类推。

此外,这些潜在分数可以被解释为一个运动员与被淘汰的运动员相比好或差多少,一旦去除了事件影响(根据加法模型),参考分数为零。

另外,请注意,在已经计算出潜在分数的 62 名滑手中,只有 24 人获得了 2017 年世界锦标赛的参赛资格。因此,我们只根据那些滑手的预测排名来评估模型。

估价

首先,让我们看看加法模型在逼近赛季中的实际得分时表现如何,因为这是回归的主要目标。下面是一个图表,显示了本赛季每对赛事选手的预测分数,每个分数都按其潜在成分(基线分数+赛事分数+选手分数)与该对选手的真实分数进行了细分。

Each tick represents a skater. Each gray bar represents the discrepancy between predicted and true season scores.

上图显示,预测得分与赛季中的真实得分非常接近,与真实得分的最大偏差(也称为残差)不到 30 分。我们可以根据预测得分绘制这些残差,如果使用了季节平均模型,还可以绘制相同的残差。此外,我们可以绘制这些残差的直方图和 QQ 图,以确认线性回归模型的高斯噪声假设是否合理。

Top: residual vs. predicted score for season average and additive models. Bottom: histogram and QQ plot of additive models’s residuals

从上面的情节可以看出:

  • 来自加法模型的残差在整个预测分数范围内是相当随机的。这加强了随机噪声的模型假设,与不同分数范围的噪声不同(也称为异方差),其在残差与预测分数图中具有特征性的“扇形”形状。
  • 从残差的直方图和 QQ 图来看,残差的分布非常接近高斯分布。这证明了线性模型的零均值高斯噪声假设。
  • 然而,似乎有异常大量的零残差,从直方图的高峰以及 QQ 图中间的扭结可以看出。经过一些检查,这些零残差原来只属于在赛季中只参加一个项目的运动员。这是有意义的,例如,如果梯度下降被用来寻找潜在得分:这样的滑冰运动员的潜在得分只对单个项目的预测得分有贡献,而整个赛季的其他预测得分不变。因此,在梯度下降过程中,潜在的运动员分数可以自由地自我调整,使得该项目-运动员对的真实分数和预测分数之间的差,即残差变为零。
  • 最后,将加法模型的残差与平均模型的残差进行比较,结果显示它们在规模上非常相似。事实上,线性模型的均方根误差 (RMSE)为 8.84 点,比季节平均模型的 10.3 点要好一些。

其次,也是更重要的,让我们看看加法模型在预测世界锦标赛的最终排名方面做得如何(与运动员赛季平均水平的基线模型相比)。细心的读者会注意到,之前显示的加法模型得出的排名与赛季平均排名非常相似,这在将这两个排名相互比较时得到了证实,同时也与世界锦标赛排名进行了比较:

Left: predicted ranking from additive (linear) model. Center: predicted ranking from season average model. Right: actual ranking from 2017 world championship. Colors are based on the world championship ranking.

在比较这两种模型的预测排名时,有一些有趣的情况(以红色突出显示):

  • 还有一些滑手按季均排名更准确,比如Moris, KVITELASHVILI(加法排名第 16,季均排名第 12,世界排名第 13)。
  • 相比之下,也有一些从加性模型预测的排名更准确,比如Paul, FENTZ(加性第 20,季均第 17,世界第 20)。
  • 当然,由于体育运动的不可预测性,有些运动员的两个模型都没有提供好的预测:Boyang, JIN被预测会在两个排名中名列第七,但却出人意料地在世界锦标赛中获得了第三名。

总的来说,在世界锦标赛的 276 个有序对中,有 239 个是加法模型正确得到的,这转化为肯德尔的 tau 分数为 0.732。与具有 234 个正确对的季节平均模型(肯德尔的τ= 0.695)相比,相加模型与基线模型相比似乎提供很少的预测改进。因此,让我们转向下一款有望表现更好的车型。

乘法模型

理论

我们可以将这些潜在得分乘以,再乘以零均值高斯噪声的指数,从而得出赛季得分,而不是将赛季中的项目-运动员得分视为由一些潜在得分相加而成(基线+项目+运动员)。这种乘法关系可以用数学方法表示为:****

乘法模型相对于加法模型的一个潜在优势是,潜在分数可以在该模型中相互作用。例如,在附加模型中,一个给定的溜冰者的潜在效应保持不变,不管他或她参加什么项目。相比之下,乘法模型中的溜冰者分数充当基线和事件分数之上的“乘数”:如果你愿意,可以把这想象为溜冰者在“利用”每个事件方面有多好。

我们在公式末尾乘以高斯噪声的指数的原因是因为可以在整个公式中取对数,这导致:

换句话说,与加法模型一样,乘法模型只不过是一个具有二元特征的线性回归问题。然而,不同之处在于,响应现在是赛季得分的对数,回归系数现在是潜在得分的对数。

编码

由于模型核心的线性回归保持不变,乘法模型的编码很大程度上与加法模型相同,除了在最后解正规方程时:我们用X.T @ np.log(y)代替了X.T @ y

******coefs = np.linalg.inv(X.T @ X) @ (X.T @ np.log(y))******

另一个小的修改发生在回归系数被获得之后,我们需要取它们的指数来得到潜在的基线、事件和选手分数。关于如何从回归系数中提取baseline_scoreevent_scoresskater_scores,请参考加性模型。

******baseline_score = np.exp(baseline_score)
event_scores = np.exp(event_scores)
skater_scores = np.exp(skater_scores)******

结果

以下是乘法模型恢复的选手潜在得分:

最优秀的滑手,Yuzuru, HANYU“乘数”为 1.15;这意味着对于任何一个项目,在基线得分和项目得分相乘后,他的预测得分将是该数字的 1.15 倍。垫底的是排名最差的滑手Julian Zhi Jie, YEE,乘数为 0.77。

估价

尽管它们的形式不同,乘法模型为赛季中的每一对运动员提供了与加法模型非常相似的预测。当绘制两个模型的真实得分和预测得分之间的偏差以及赛季平均值的基线模型时,可以在下面看到这一点。因此,两个模型的 RMSE 实际上是不可区分的:加法模型为 8.84,乘法模型为 8.88(而季节平均模型为 10.3)。

Deviation from true score for each event-skater pair of the season for all 3 models (average, additive, multiplicative)

在预测世界锦标赛的最终排名方面,这两个模型再次几乎相同(参见它们与最终世界排名的比较):

Predicted rankings of multiplicative and additive models compared to the 2017 world ranking. Colors are based on the world championship ranking.

2017 赛季的两个排名只有两个不同,在上图中用红色突出显示:

  • 乘法排名预测Nathan, CHEN排名高于Javier, FERNANDEZ,而加法排名预测相反。事实证明,在最终的世界锦标赛中,加法排名是正确的。
  • 乘法排名预测Kevin, REYNOLDS排名高于Maxim, KOVTUN,而加法排名预测相反。然而,在这种情况下,乘法排名在最终的世界锦标赛中确实是正确的。

因此,乘法排序与世界排序具有完全相同的一致对数量(276 对中的 239 对)和与加法排序相同的肯德尔τ(0.732),这仍然高于季节平均值基线模型的肯德尔τ(0.695)。换句话说,至少在 2017 赛季,两个模型在预测世界冠军的最终排名方面似乎没有什么表现差异。

典型惩罚

假设模型选择(加法或乘法)似乎不会显著影响预测性能,让我们看看如何以另一种方式改进任一模型。

具有讽刺意味的是,改进这些模型的一个常用方法是惩罚它们(也称为规则化)。对于这个问题,罚分可以理解为人为地将潜在事件和滑手分数向零收缩。一方面,这种萎缩无疑会使模型在预测赛季得分方面不太准确。另一方面,它可以防止模型过度适应赛季分数,赛季分数可能会很嘈杂,可能不会反映溜冰者的真实能力(可能他们在那天早些时候食物中毒)。因此,这可能会更好地捕捉运动员的真实能力,并对他们进行更准确的排名。****

对于这个问题,我会选择 岭回归 来惩罚两个模型,原因有二:

  • 岭回归及其近亲(例如套索弹性网回归)通常用于正则化线性回归问题。它也非常容易实现并合并到目前使用的正规方程中,如下所示。
  • 但是,与类似的正则化类型(如 lasso)相比,岭回归的一个优点是回归系数永远不会完全收缩为零。换句话说,多个滑手之间永远不会有平手,都是零潜分,适合这个排名问题。

理论

让我们看看如何将岭回归纳入用于线性回归的正规方程。回想一下加法模型的正规方程的解:

  • θ:回归系数(70: 1 基线+ 8 项+ 61 名选手)
  • X:二元特征表(120×70)
  • y:响应向量(120)。对于乘法模型,这变成了log(y)

我们可以修改岭回归的这个解决方案,使回归系数向零收缩(下面公式的推导见最后的参考资料部分):

这个等式中的额外项是:

  • λ:惩罚参数(标量)
  • I:单位矩阵(70×70 矩阵,其条目除了对角线上的 1 之外都是 0,每个对角线条目对应一个回归系数)。注意,通常情况下,我们不会惩罚回归的截距。因此,单位矩阵I的第一个对角元素将是 0 而不是 1。

惩罚参数λ越高,模型将变得越惩罚,即潜在事件和运动员分数进一步向零收缩。相比之下,当λ = 0 时,模型成为原始的、未受惩罚的版本。

编码

给定上面的公式,为加性模型的岭回归编写正规方程的解再简单不过了,因为我们已经为同一个方程的正规版本编写了代码。

******# Create 70x70 identity matrix
I = np.identity(n=X.shape[1])# Set first diagonal entry of identity matrix to zero
I[0, 0] = 0# Solving normal equation for ridge regression
lambda_reg = 0.01
coefs = np.linalg.inv(X.T @ X + lambda_reg * I) @ (X.T @ y)******

结果

以 2017 赛季的加性模型为例,当λ增加 10 倍时,潜在项目和滑手分数一般会向零收缩。相比之下,基线分数相对不受惩罚参数的影响(这本来就不是它的本意):

Effect of λ (penalty parameter) on baseline, skater, and event scores of additive model in 2017 season

估价

当潜在事件和运动员分数向零收缩时,模型在逼近赛季分数时自然会变差。因此,模型 RMSE 将增加(见下图):在极端情况下,当λ = 10000 时,RMSE 为 35.6,这比仅仅取每个溜冰者的季节平均值(RMSE = 10.3)要差得多,更不用说原始的、未被惩罚的模型(RMSE = 8.8)。

Effect of λ (penalty parameter) on RMSE, Kendall’s tau, and predicted ranking of additive model in 2017 season. Colors are based on world championship ranking.

然而,这种极端的萎缩会导致更好地预测世界锦标赛的排名吗?唉,没有。从上图来看,在所有 276 个有序配对中,极度惩罚模型只正确获得了 231 对,低于未惩罚模型的 239 对,甚至低于季节平均模型的 234 对!通过绘制当λ增加时预测排名如何变化的图表(见上文),我们可以看到,直到λ = 0.1,惩罚模型的预测排名与原始模型相比没有太大变化,并且一致对的数量保持在 239。然而,在此之后,惩罚模型的排名开始有点混乱,这与和谐对数量的下降相一致(从λ = 0.1 时的 239 到λ = 1 时的 235)。

简而言之,惩罚加性模型造成了双重打击:它不仅使模型在逼近赛季得分方面变得更差,而且在预测世界冠军排名方面也变得更差。

然而,这只是单个赛季(2017 年)的结果。让我们在训练样本中对所有 10 年应用相同的模型(加法和乘法)。对于每个模型,我们从季节平均值的基线模型计算它们在 RMSE 和肯德尔τ中的差异。然后,我们使用 9 个自由度的 t 分布(因为 n=10 年)绘制这些差异的平均值,以及它们的 95%置信区间。这与执行配对 t-检验来比较每个模型与基线相比的 RMSE 和肯德尔τ的平均差异是一样的。

Effect of λ (penalty parameter) on differences (Δ) in RMSE and Kendall’s tau from baseline model, averaged over the 10-year training sample

从上面的图表中,我们可以看到:

  • 与季节平均值的基线模型相比,加法和乘法模型实际上以相同的量影响 RMSE,并且平均以相似的量影响肯德尔τ。
  • 就 RMSE(左图)而言,两个模型将 RMSE 从基线降低了λ < 0.1. However, this in itself is of little value since approximating season scores is not our goal for this project. Furthermore, the RMSE naturally increases as λ increases, especially for λ > 0.1 的统计显著量,而 RMSE 从季节平均值的基线模型显著上升。
  • 就肯德尔τ(右图)而言,模型受到的惩罚越多(λ越大),与基线相比,它们的肯德尔τ平均越差,不幸的是,这与我们之前从 2017 赛季得到的结果一致。更糟糕的是,从λ = 1 开始,这些模型开始从基线模型减少显著数量的肯德尔τ:注意肯德尔τ从基线的差异的 95%置信区间如何下降到水平线以下的零。因此,我们应该为这个项目选择非惩罚模型。

然而,即使使用未加惩罚的加法和乘法模型(λ = 0),上图显示,平均而言,与季节平均值的基线模型相比,这些模型实际上降低了肯德尔τ😱当然,肯德尔τ差异的 95%置信区间表明,这种与基线的差异在统计学上不显著,因为它们包含了 0 处的水平线。但是,让我们分别绘制这三个模型每年的表现,以便我们可以更详细地比较这三个模型:

Each gray line represents a year in the training set, with 2017 highlighted in red

  • 就 RMSE(左)而言,与训练样本中每年的季节平均模型相比,加法和乘法模型确实降低了 RMSE。然而,与 2017 赛季一样,这两个模型在 RMSE 方面没有太大差异,即它们同样接近赛季得分。
  • 然而,就肯德尔的τ(右)而言,画面就不那么清晰了。正如 2017 赛季早些时候所示,加法和乘法模型预测最终的世界冠军排名都优于赛季平均模型。然而,有相当多的年份,这两种模型给出的预测都比季节平均模型差。平均所有 10 年的训练集,这并不奇怪,他们的综合表现与基线相比是如此黯淡。
  • 请注意,到目前为止,所有的结果都来自男性选手。对于女滑手来说,情况基本保持不变,从下面的对比图可以看出。

Comparison between three models for female skaters

以下是关于两个模型(加法和乘法)的 RMSE 和肯德尔τ性能的最终报告,包括平均值、与季节平均值基线模型的平均差异,以及 10 年训练样本中这些差异的 95%置信区间:

对于女性滑冰运动员,我们可以看到肯德尔τ(与季节平均值的基线模型相比)差异的 95%置信区间包含加法和乘法模型的零。换句话说,在从基线预测世界冠军排名时,两个模型都没有提供任何统计上的显著改进。

结论

尽管 2017 赛季的结果最初很乐观,但事实证明,为了预测最终的世界冠军排名,该项目期间提出的两个模型(加法和乘法)并不比简单地根据赛季平均水平对滑手进行排名更好。因此,根据奥卡姆剃刀,没有令人信服的理由选择这些更复杂的模型而不是更简单的季节平均模型。

一个问题仍然存在:考虑到这些模型中潜在的事件效应和运动员效应之间看似合理的相互作用,为什么它们在预测世界锦标赛排名时没有显示出显著的改进?

一个答案是体育运动太不可预测了。当然,这些模型可以很好地预测过去发生的事件的分数,正如他们的小 RMSE 事件所证明的那样,但是很难说在未来的事件中,没有人会受伤,或者有时差反应,或者只是在冰上撞了一个怪胎。这种不可预测性可以通过所有三种模型每年高度可变的肯德尔τ来反映:对于男性滑冰运动员,这可以在 0.5 到 0.8 以上的范围内(或者女性为 0.4 到 0.7)。换句话说,仅仅因为某年的世界锦标赛恰好有利于某个模特,并不意味着下个赛季不会有任何时髦的事情发生。****

Ice, as they say, is slippery

资源

尽管与基线模型相比没有获得显著的改进,但这个项目让我知道,即使是最简单的模型,如线性回归,也可以以创造性的方式使用,如找到潜在的因素来解决这种情况下的排名问题。以下是一些有助于理解本文理论的资源:

  • 对于法方程解的推导(对于非惩罚和惩罚模型),麻省理工学院开放式课件上的机器学习课程的这个讲义非常详细。注释还解释了惩罚参数λ的重要性:在贝叶斯设置中,较高的λ意味着潜在得分的高斯先验更集中于零。因此,这些分数的估计值也更有可能接近于零,正如在这个项目中所看到的那样。
  • 对于乘法模型,似乎可以用在时序分析中。此外,从这个讲座幻灯片来看,它似乎也可以被称为对数线性模型。然而,这些模型似乎包括取预测值的对数,而不是取感兴趣系数的对数(这正是本项目所做的)。

从赛季表现预测花样滑冰世锦赛排名

原文:https://towardsdatascience.com/predicting-figure-skating-world-championship-ranking-from-season-performances-part-2-hybrid-7d296747b15?source=collection_archive---------32-----------------------

体育分析

第 2 部分:混合模型

背景

在项目的第一部分,我试图根据运动员在该赛季以前的比赛项目中获得的分数来预测年度花样滑冰世界锦标赛中的排名。主要策略是将滑手效应(每个滑手的内在能力)与事件效应(一个事件对滑手表现的影响)分开,以便建立更准确的排名。

针对这个排名问题提出了两个模型,其中一对运动员的赛季得分可以近似为:

  • 一个基线得分,它在所有赛季得分中保持不变
  • 潜在的事件分数,该分数在所有参加该事件的选手中是不变的
  • 一个潜在的滑冰者分数,该分数在该滑冰者参加的所有比赛中都是不变的

这些潜在得分可以相加在一起(加法模型),或者相乘(乘法模型)以接近赛季得分:

在任一情况下,通过重写上述公式,可以容易地找到潜在分数,使得它们成为简单的线性模型,其回归系数是潜在分数本身。因此,这些分数可以简单地通过求解线性回归的正规方程来得到。从那里,每个模型的预测排名只是潜在滑手分数的排名,从最高到最低。

问题

将这两个模型预测的排名与根据赛季平均水平简单排名的基线模型进行比较。用于评估这些模型的度量是相对于实际世界锦标赛排名的肯德尔排名相关系数(也称为肯德尔的τ):如果模型的预测排名具有更接近 1 的肯德尔τ,则模型更好,这意味着预测排名与当年世界锦标赛中的实际排名更相关。

Baseline: season average model. The 95%-confidence intervals are built using a t-distribution with 9 degrees of freedom (since n=10 years).

从以上关于这两个模型在训练集中选择的 10 年(共 14 年)的报告来看,这两个模型比赛季平均值的基线模型更好地逼近赛季得分,因为它们与基线模型之间的均方根误差 (RMSE)差异的 95%置信区间小于零。

然而,在更重要的 Kendallτ度量中,遗憾的是,与基线模型相比,这些模型没有提供该度量的任何显著增加,因为对于男性和女性溜冰者,它们与基线之间的 Kendallτ差异的 95%置信区间包含零。

混合模型

鉴于这两个模型在排名预测方面表现平平,我提出了一个新模型,它是这两个模型的奇怪混合:

  • 给定事件中给定选手的分数
  • θ_baseline:基线得分(整个赛季得分不变)
  • θ_event:该项目潜在得分(运动员之间不变)
  • θ_skater:溜冰者潜在得分(跨事件恒定)

根据模型公式,给定溜冰者在赛季期间的给定事件中获得的分数可以近似为该溜冰者的潜在分数乘以该事件的潜在分数,类似于乘法模型。但是,该产品随后会添加到基线得分中,类似于加法模型。

由于有些年份加法模型比乘法模型具有更高的肯德尔τ,而有些年份则相反,因此混合模型可能提供比单独的任一模型更好的预测排名。你可以在这个项目的 Github repo 里找到这个 Jupyter 笔记本里关于混动车型的分析。

寻找潜在分数

由于混合模型中加法和乘法的奇怪组合,我们既不能将其重新表述为线性模型,也不能取公式的对数将其转换为线性模型(类似于我们对乘法模型所做的)。

尽管如此,寻找该模型的最佳参数的策略仍然与前两个模型相同:最小化赛季中所有事件-滑冰运动员对的预测分数和真实分数之间的平方差之和:

  • J:模型的目标函数;继续以 2017 赛季的男选手为例,这将是 1 个基线分数、9 个潜在事件分数(以蓝色突出显示)和 62 个潜在选手分数(以黄色突出显示)的函数。
  • ŷ_e,s:来自混合模型的项目-滑冰运动员对的预测得分
  • y_e,s:在赛季中记录事件-溜冰者对的真实分数
  • θ_baseline:基线得分
  • θ_e:事件潜在得分e
  • θ_s:滑手潜在得分s

梯度下降算法

有了上面的目标函数,我们可以使用 good ol 'gradient descent来找到模型的最佳基线、事件和选手分数。让我们看看每个参数的梯度是什么样的,首先是基准分数(θ_baseline):

根据上面的公式,目标函数相对于基线得分的梯度简单地是赛季中所有得分的真实得分和预测得分之间的差的总和(从这里开始将被称为残差)。

因此,给定基线、项目和运动员分数的一些当前值,我们总是可以计算基线分数的梯度,并根据该梯度更新基线分数(因为我们希望最小化目标函数):

与梯度相乘的额外项α被称为学习速率,并控制梯度下降算法运行的速度。低α可能会使算法收敛太慢,而高α可能会使更新太大,参数不断从一个极端摆动到另一个极端,这在我这个项目中发生过几次。

给定事件的潜在得分可以类似地更新,以俄罗斯的事件(潜在得分θ_RU)为例:****

当将目标函数与该事件的潜在得分进行微分时,所有不涉及该事件的平方差都将消失,这就只剩下涉及来自该事件的参加滑手的项,包括哈维尔、费尔南德斯(θ_FERNANDEZ)和亚历山大、马约罗夫(θ_MAJOROV)。使用链式法则,我们可以看到,梯度只不过是参与该事件的每个溜冰者的残差乘以该溜冰者各自的潜在得分,然后将这些乘积加在一起。

使用这个梯度,这个俄罗斯事件的潜在得分可以使用梯度下降来更新:

最后,对于溜冰者的潜在得分,例如 MURA 的高仁(θ_MURA),目标函数相对于该潜在得分的梯度得分为:****

2017 赛季,他参加了 2 场比赛,一场在加拿大,一场在法国,分别有潜在分数θ_CAθ_FR。因此,梯度只不过是每个事件的残差乘以该事件的潜在得分,然后将这些乘积相加。因此,可以使用梯度下降来更新该溜冰者的潜在得分:

简而言之,寻找基线、项目和运动员分数的梯度下降算法可以概括为以下步骤:

在第二步中,可以通过检查模型的 RMSE(即平均残差平方的平方根)是否已经稳定到最小值来监控收敛。梯度下降收敛后得到的运动员分数将被用于世界锦标赛的运动员排名。

混合模型编码

给定上面的梯度下降算法,让我们看一个如何用 Python 编码的小例子。下面,我们有一个数据框(season_scores),包含 4 名滑冰运动员(马约罗夫、费尔南德斯、葛、MURA)和 3 项赛事(加拿大、法国、俄罗斯)的 7 个赛季成绩:

首先,我们使用 pandas 的pivot_table函数将这个长表格式转换成数据透视表格式,将运动员姓名作为行索引,将事件名称作为列。将赛季得分转换成这种数据透视表格式的原因很快就会明了。

season_pivot = pd.pivot_table(season_scores[['name', 'event', 'score']], values='score', index='name', columns='event')

下面是我们最终将得到的 4×3 的赛季得分数据透视表。注意不是所有的滑手都参加所有的项目,所以数据透视表中有缺失值,用NaN(不是数字)表示。

最后,在我们开始梯度下降之前,让我们把熊猫数据帧season_pivot转换成一个 4×3 的 numpy 矩阵true_scores,这样我们可以更容易地操作它。这实际上将删除所有的行和列名称,所以让我们存储运动员名称(行)和事件名称(列),这样我们在运行梯度下降后仍然可以识别它们。

skater_names = list(season_pivot.index) 
# ['Alexander, MAJOROV', 'Javier, FERNANDEZ', 'Misha, GE', 'Takahito, MURA']event_names = list(season_pivot.columns) 
# ['CA', 'FR', 'RU']true_scores = season_pivot.values
# array([[   nan,    nan, 192.14],
#        [   nan, 285.38, 292.98],
#        [226.07, 229.06,    nan],
#        [222.13, 248.42,    nan]])

第一步:初始化基线、潜在项目和潜在选手得分

首先,让我们使用 numpy 的带有特定种子的[RandomState](https://docs.scipy.org/doc/numpy-1.15.0/reference/generated/numpy.random.RandomState.html)对象初始化梯度下降的基线分数(这样每次运行代码时结果都是一致的),并调用该对象的[random_sample](https://docs.scipy.org/doc/numpy-1.15.0/reference/generated/numpy.random.RandomState.random_sample.html#numpy.random.RandomState.random_sample)方法返回一个介于 0 和 1 之间的随机数。初始值没有理由在这个范围内。我只是觉得这样会更好看。

random_state = np.random.RandomState(seed=42)
baseline = random_state.random_sample() # 0.866

现在,让我们初始化潜在事件和选手分数。我不再逐一初始化它们,而是将所有选手的分数初始化为大小为 4×1 ( skater_scores)的列向量,将所有事件的分数初始化为大小为 1×3 ( event_scores)的行向量,其条目也是 0 到 1 之间的随机数,再次使用random_sample方法。

skater_scores = random_state.random_sample(4, 1))
# array([[0.37454012],
#        [0.95071431],
#        [0.73199394],
#        [0.59865848]])event_scores = random_state.random_sample((1, 3))
# array([[0.15601864, 0.15599452, 0.05808361]])

以向量形式初始化这些潜在得分的原因是,通过将这些向量相乘(使用矩阵乘法运算符@)并在此基础上添加基线,可以一次性计算出每对运动员的预测得分:

predicted_scores = skater_scores @ event_scores + baseline
# array([[0.52284634, 0.42976104, 1.19802617],
#        [0.48872716, 0.41705697, 1.00857581],
#        [0.46792756, 0.40931237, 0.89308382],
#        [0.39887817, 0.38360225, 0.50967974]])

为了更清楚地说明这一点,下图显示了两个潜在向量(大小分别为 4×1 和 1×3)与添加在顶部的基线相乘,以形成每个项目-选手对的预测得分的 4×3 矩阵。用红色突出显示的是一对运动员(CA-MAJOROV ),这是通过乘以潜在向量中的相应条目计算出来的,添加的基线在顶部。

Dotted line: implied indices of skaters and events, even though they don’t exist in the numpy array

接下来,由于已经计算了每对运动员的真实分数(true_scores)和预测分数(predicted_scores),我们可以很容易地计算出赛季中每个分数的残差。

residuals = predicted_scores - true_scores

回想一下,我们的true_scores numpy 矩阵包含赛季中不存在的赛事-运动员配对的NaN值。因此,当计算残差时,这些对的相应残差也是NaN

步骤 2a:计算基线、项目和运动员分数的梯度

残差矩阵中NaN的存在使得计算梯度变得非常方便,因为我们不必跟踪哪个运动员在赛季中存在或不存在。例如,先前导出的基线梯度是该季节中所有现有事件-滑冰运动员对{e,s}的残差之和:

因此,基线梯度可以简单地实现为:

baseline_gradient = np.nansum(residuals) # -1692

[np.nansum](https://docs.scipy.org/doc/numpy/reference/generated/numpy.nansum.html)函数有效地跳过了不存在的NaN值(通过将它们视为零),这符合我们的目的,即只对赛季中现有的事件-选手对的残差求和。

为了计算所有潜在事件分数的梯度,np.nansum函数因其axis参数而再次大有帮助:****

event_gradients = np.nansum(residuals * skater_scores, axis=0, keepdims=True)

让我们分解这行代码,看看它是如何工作的:

  • residuals * skater_scores:通过使用*操作,我们简单地将residual矩阵与步骤 1 中初始化的skater_scores逐元素相乘。

然而,由于两个数组的大小不同:矩阵的大小为 4×3 (4 个运动员乘 3 个事件),而列向量的大小为 4×1(4 个运动员的潜在得分),numpy 的广播开始运行。实际上,numpy 将水平复制skater_scores向量 3 次,使其与residuals矩阵的维数相同(4×3),如下图所示:

Highlighted in red: relevant values for the Russian event (RU). **Dotted arrows:** numpy broadcasting of latent skater scores

注意,在广播之后,两个矩阵(现在大小相似)之间的逐元素乘法将在与residuals矩阵中的值相同的位置留下NaN值。这允许np.nansum来计算相关的事件梯度,但这次该函数有两个新参数:

  • axis=0:这允许在residuals * skater scores矩阵的行(代表运动员)之间计算总和。实际上,这将对参加每个项目的所有选手的剩余分数和潜在分数的乘积求和,从而得出该项目潜在分数的梯度(见上图)

Highlighted in red: relevant values for the Russian event (RU)

  • keepdims=True:对 4×3 residuals * skater scores矩阵的行求和将产生一个 1×3 维的行向量,它正好包含 3 个事件的梯度。从技术上讲,这是 numpy 中 shape (1,3)的一个二维数组,然而默认情况下,np.nansum将结果折叠成 shape (3,)的一个一维数组。因此,设置keepdims=True将使事件梯度保持在 2-D 中,因为稍后将从相同形状(1,3)的event_score数组中减去该梯度。

计算所有潜在溜冰者分数的梯度与计算事件梯度相同,除了我们将残差(residuals大小为 4×3)乘以事件分数(event_scores大小为 1×3,将垂直广播 4 次)。****

skater_gradients = np.nansum(residuals * event_scores, axis=1, keepdims=True)

Highlighted in red: relevant values for the skater Takahito, MURA. **Dotted arrows:** numpy broadcasting of latent skater scores

乘法的结果将通过使用带有参数axis=1np.nansum跨列(每个选手参加的项目)求和,以获得选手梯度:

skater_gradients = np.nansum(residuals * event_scores, axis=1, keepdims=True)

Highlighted in red: relevant values for the skater Takahito, MURA

步骤 2b:使用梯度更新基线、项目和运动员分数

一旦计算出梯度,使用它们来更新基线、项目和选手的潜在得分只涉及简单的减法(使用学习率alpha=0.0005):

alpha = 0.0005
baseline = baseline - alpha * baseline_gradient
event_scores = event_scores - alpha * event_gradients
skater_scores = skater_scores - alpha * skater_gradients

使用一个简单的for循环,我们可以多次重复步骤 2,在计算梯度和使用它们更新潜在得分之间交替进行:

alpha = 0.0005for i in range(1000):
    # 2a. Calculate gradients
    predicted_scores = skater_scores @ event_scores + baseline
    residuals = predicted_scores - true_scores

    baseline_gradient = np.nansum(residuals)
    event_gradients = np.nansum(residuals * skater_scores, axis=0, keepdims=True)
    skater_gradients = np.nansum(residuals * event_scores, axis=1, keepdims=True)

    # 2b. Update latent scores using gradients
    baseline = baseline - alpha * baseline_gradient
    event_scores = event_scores - alpha * event_gradients
    skater_scores = skater_scores - alpha * skater_gradients

梯度下降算法的收敛可以通过计算每个循环后的 RMSEnp.sqrt(np.nanmean(residuals**2))来监控,并检查它与前一个循环的不同程度。经过 1000 次迭代后,模型的 RMSE 为 4.30,迭代与迭代之间的差异为 10e-9。这表明该算法已经很好地真正收敛。

最后,梯度下降收敛后的潜在选手分数被转换成熊猫系列,这允许早先存储的选手名字被添加回来。请注意,在将二维skater_scores数组(4,1)转换成序列之前,需要用[ravel](https://docs.scipy.org/doc/numpy/reference/generated/numpy.ravel.html)将它折叠成一维数组(4,)。此外,调用sort_values(ascending=False)从最高到最低对运动员的潜在得分进行排序,因为最初的目标是通过这些潜在得分对运动员进行排名。

skater_scores = pd.Series(skater_scores.ravel(), index=skater_names).sort_values(ascending=False)
# Javier, FERNANDEZ     16.701678
# Takahito, MURA        14.101404
# Misha, GE             13.587320
# Alexander, MAJOROV    10.678634

结果

当在 2017 赛季期间将梯度下降算法应用于男性滑冰运动员时,我们可以通过每次迭代的残差和 RMSE 来监控算法的进展。此外,每次迭代中的潜在选手分数可用于建立临时排名,其与世界锦标赛排名的相关性由肯德尔的 tau 测量。在算法期间监控的所有指标(学习率α = 0.0005)显示在下面的动画仪表板中:

Left: heat map of residuals, along with baseline, event, and skater scores at each iteration. Skaters are ordered based on their predicted ranks after 150 iterations. Right (top to bottom): RMSE, Kendall’s tau, and comparison between predicted ranking and world ranking at each iteration (colors are based on world ranking).

从上面的仪表板中,我们可以看到:

  • 随着梯度下降算法的运行,残差(预测得分-真实得分)一般会减少,从它们的热图越来越亮就可以看出。我第一次看的时候,觉得很酷!
  • 因此,RMSE 也会随着迭代次数的增加而降低(见右上图)。事实上,RMSE 甚至在 20 次迭代后就显著降低,之后开始稳定。
  • 在预测排名方面,我们还注意到,随着梯度下降的进行,它开始越来越好地与世界冠军排名相关联(见右下图):连接两个排名的线开始变得越来越不纠结。
  • 然而,可以看出,在算法开始附近有一个时刻(大约 25 次迭代左右),两个等级之间的相关性“看起来”最好,这也对应于在同一次迭代中肯德尔τ的峰(中右图)。这意味着,混合模型不需要尽可能好地逼近赛季得分,以便获得运动员的良好预测排名;否则,它可能会过度拟合赛季得分,从而损害预测排名的准确性。
  • 这种过度拟合的潜在证据意味着我们应该惩罚混合模型(类似于我们在前一部分对线性模型所做的)。避免过度拟合的另一个策略是当预测等级的肯德尔τ是最高的,也就是驼峰所在的位置时,过早地停止梯度下降。在机器学习的说法中,这被称为提前停止,这两种减少模型过度拟合的策略将在稍后探讨。

以下是梯度下降收敛后的潜在滑手分数;只有获得 2017 年世界锦标赛资格的 24 名选手将被显示,从最高到最低的选手分数排列如下:

Latent skater scores from hybrid model for male skaters in 2017 season (with associated rank)

模型评估

在以学习速率alpha=0.0005和 10,000 次迭代对混合模型运行梯度下降后,模型的最终 RMSE 为 8.86,迭代与迭代之间的差异为 1.7e-8,对于收敛来说足够小。

将该 RMSE 与项目第一部分中的线性模型进行比较时——加法模型为 8.84,乘法模型为 8.88——我们可以看到,混合模型在逼近赛季得分的程度上正好位于之前的模型之间。这也优于季节平均值的基线模型,其 RMSE 为 10.3。

所有型号的每对运动员与真实分数的偏差如下所示。该图中所有 3 个模型之间预测得分的接近程度证实了它们之间相似的 RMSEs。

Deviation from true score for each event-skater pair of the season for all models

就预测排名而言,混合排名再次与早期排名几乎相同(见下图)。事实上,相比于乘法排名,混合排名只是颠倒了只有一对滑手的顺序:Kevin, REYNOLDSMaxim, KOVTUN,用红色突出显示。

不幸的是,这种逆转与最终的世界冠军结果不符:Kevin, REYNOLDS最终排名高于Maxim, KOVTUN。结果,混合模型在所有 276 个有序对中得到 238 对,比乘法模型少一对。这意味着混合模型的肯德尔τ值略低,为 0.724,而乘法模型和加法模型的肯德尔τ值均为 0.732。然而,这仍然略高于 2017 赛季男子滑冰运动员赛季平均基线模型的肯德尔τ值 0.695(或 276 对中的 234 对)。

在评估训练集中所有 10 年的混合模型之前,让我们首先看看我们如何能够像前面承诺的那样使它对赛季分数的过度拟合更少。

典型惩罚

理论

回想一下梯度下降试图最小化的混合模型的目标函数:

为了惩罚混合模型,我们需要找到一种方法来人为地使潜在的事件和运动员分数变小(同时保持基线分数不变,以便赛季分数仍然是相当接近的)。这样做的一种方法是这样修改目标函数:

The 1/2 factor for the squared terms will make later derivations easier

上述目标函数末端的附加平方项表明:除了最小化季节分数的残差平方,我们还希望最小化潜在事件和潜在选手分数的平方(分别为θ_eθ_s),以使这些潜在分数保持较小。潜在得分被惩罚的程度由惩罚参数λ控制:λ越高,潜在得分的平方越小,这些潜在得分就越小。

我们可以相对于这些潜在得分对修改的目标函数进行微分,以在梯度下降期间获得梯度公式(步骤 2a):

Notice that the gradient formula for baseline score stays the same

从上面的梯度公式可以看出,对原梯度下降公式唯一的修改是分别在项目和滑手梯度公式的末尾带有λ的项。

编码

由于这些简单的修改,将这些术语合并到梯度下降的 Python 实现中变得非常容易。下面是惩罚模型的梯度下降步骤 2 的代码(使用惩罚参数lambda_param=10),与原始模型的变化以粗体突出显示。请注意,对于我们的玩具示例,我们在收敛后获得的惩罚模型的潜在溜冰者分数小于原始模型的分数,这意味着惩罚确实有效。

alpha = 0.0005
**lambda_param = 10**for i in range(1000):
    # 2a. Calculate gradients
    predicted_scores = skater_scores @ event_scores + baseline
    residuals = predicted_scores - true_scores

    baseline_gradient = np.nansum(residuals)
    event_gradients = np.nansum(residuals * skater_scores, axis=0, keepdims=True) **+ lambda_param * event_scores**
    skater_gradients = np.nansum(residuals * event_scores, axis=1, keepdims=True) **+ lambda_param * skater_scores**

    ### 2b. Update latent scores using gradients
    baseline = baseline - alpha * baseline_gradient
    event_scores = event_scores - alpha * event_gradients
    skater_scores = skater_scores - alpha * skater_gradientsskater_scores = pd.Series(skater_scores.ravel(), index=skater_names).sort_values(ascending=False)
# Javier, FERNANDEZ     16.108775
# Takahito, MURA        13.429787
# Misha, GE             12.893933
# Alexander, MAJOROV     9.784296

估价

下面是当惩罚参数λ增加(增加 10 倍)时,混合模型的 RMSE、肯德尔τ和预测排名如何变化的图表:

Learning rate α = 0.0005, Iterations = 10,000

从上图可以看出:

  • 随着惩罚参数λ的增加,模型的 RMSE 如预期一样增加,特别是从λ=10 开始,RMSE 几乎是基线模型的两倍(RMSE=10.3)。
  • 然而,当我们试图惩罚模型时,预期的肯德尔τ值的增加并没有实现。λ越高,模型的预测排名就变得越差,这可以从肯德尔的τ的减少中得到证明,并且随着λ的增加,预测排名与世界冠军排名相比变得越来越“凌乱”。

当通过绘制不同λ值的基线模型(季节平均值)上的 RMSE 和肯德尔τ的平均差异来检查这种惩罚对训练集中所有 10 年的影响时,出现了相同的画面:

  • 如左图所示,当λ增加时,混合模型相对于基准模型的 RMSE 平均差异(10 年以上)通常会增加,尤其是在λ=10 之后。这意味着当惩罚越多,该模型越接近赛季得分。然而,这种行为是意料之中的,因为毕竟惩罚的主要目的是减少对赛季分数的过度拟合。
  • 然而,一般来说,混合模型相对于基线模型的肯德尔τ的平均差异随着λ的增加而下降。换句话说,模型给出的预测排名越差,惩罚就越多。这令人失望,但并不令人惊讶,因为惩罚(通过最小化潜在得分的平方)在项目的第一部分对线性模型有同样的影响。
  • 此外,与之前的模型类似,与基线模型相比,无惩罚混合模型(λ=0)给出了更差的平均排名预测。然而,这种差异微乎其微:平均而言,Kendall tau 仅下降 0.001,并且不具有统计学意义:当λ=0 时,与基线模型相比,Kendall tau 平均差异的 95%置信区间很容易包含零水平线。

有了这些黯淡的模型惩罚结果,让我们看看是否可以用另一种方式减少模型过度拟合,这有望为混合模型产生更好的预测排名。

提前停止

理论

如前所述,在这种情况下提前停止的想法非常简单:当预测排名的肯德尔τ值最高时,我们只是提前停止梯度下降。实际上,这将防止潜在分数变得太大,就好像我们在原始混合模型中让梯度下降一直收敛一样。

因此,早期停止将使模型在预测赛季得分(更高的 RMSE)方面不如收敛混合模型准确。然而,类似于模型惩罚,这可能会使模型不太适应训练数据,并更准确地对溜冰者进行排名(更高的肯德尔τ)。这在前面的动画仪表盘中可以看到,它监控梯度下降迭代中的这些指标,我在下面的静态版本中复制了这些指标。

RMSE and Kendall’s tau across iterations of gradient descent (for male skaters in 2017 season)

然而,与模型惩罚相比,提前停止提供了一些额外的优势:

  • 对于提前停止,模型仅运行—并且提前停止—一次,相比之下,对于模型惩罚,模型运行多次,每次使用不同的惩罚参数λ。
  • 对于模型惩罚,我们首先需要猜测惩罚参数λ的一些值来运行模型(例如当λ增加 10 倍时,如前面所示)。如果某个值λ提高了模型的肯德尔τ,我们需要测试这个λ的邻近值,看看是否有任何额外的提高。相比之下,对于早期停止,我们总是可以找到 Kendall 最高的单个迭代,并让该迭代成为停止点。换句话说,在早期停止中,没有“四处寻找”最佳模型超参数的忙乱。

编码

在我们的梯度下降的 Python 实现中,早期停止仅仅意味着将步骤 2 的循环次数限制到一个小的数目——例如 for i in range(10)。之后,可以计算 10 次迭代后的预测排名的肯德尔τ,我们继续尝试其他少量的迭代,并选择具有最高肯德尔τ的一个。

然而,执行早期停止的更实际的方法是运行大量迭代(比如 1000 次)的梯度下降,监视每次迭代的 Kendallτ(类似于我们之前对仪表板动画所做的),并追溯性地查明 Kendallτ最高的迭代。如果在 Kendall 的 tau 中有几个最大值,我们选择最早的迭代,因为我们希望在较少的模型过度拟合方面出错。

估价

Each dot represents the stopping point (highest Kendall’s tau) for each year. For reference, curves for 2017 are highlighted in red.

当监测每年梯度下降迭代中的 RMSE 和肯德尔τ值时(见附图),我们可以看到:

  • 在几乎所有的 10 年中,如果梯度下降很早就停止,甚至在第 40 次迭代之前,梯度下降就达到最大肯德尔τ(见下图)。相比之下,RMSE 通常在这些点之后继续下降(见上图),这是有道理的,因为梯度下降的收敛过早地停止了。
  • 10 年中只有一年在 40 次迭代后出现最大肯德尔τ,即 2014 年的第 60 次迭代。然而,这可能是侥幸,因为上图显示,在第 40 次迭代之前的几次迭代中,今年的肯德尔τ已经非常接近最大值。
  • 然而,尽管大多数年份的理想停止点低于 40 次迭代,但我们可以看到,在一些情况下,一年的理想停止点可能是另一年的灾难(尝试在每个购物点画一条垂直线,并在该迭代中查看其他年份的肯德尔τ)。鉴于肯德尔τ在梯度下降的早期迭代中高度不稳定,这一点尤其正确。

因此,我们必须通过在每次迭代中取平均值来平滑所有 10 年中的肯德尔τ,并找出哪个迭代具有最高的跨年度平均值。更准确地说,对于每次迭代,我们对混合模型的肯德尔τ与每年的基线模型的差异进行平均(超过 10 年)。这使我们能够直接比较早期停止对基线模型的影响,下图(棕色部分)突出显示了这一点:

Difference in Kendall’s tau of hybrid model over baseline model (season averages) at each iteration of gradient descent

从上图中,我们可以看出:

  • 在梯度下降的第 47 次迭代时,混合模型相对于基线模型的平均肯德尔τ差异(超过 10 年)达到最大值。
  • 然而,快速检查表明,对于大多数年来说,这个迭代已经过了他们的停止点,这意味着他们的肯德尔的 tau 已经走下坡路相当一段时间了。此外,虽然有些年份的肯德尔τ在第 47 次迭代时显示出与基线相比的显著提高(有些年份的肯德尔τ提高了近 0.1),但仍有一些年份的肯德尔τ无论是否提前停止,与基线模型相比始终表现不佳。
  • 因此,毫不奇怪,在 10 年的训练集中,即使是最好的早期停止点,肯德尔的 tau 也只比赛季平均值的基线模型提高了 0.005。更糟糕的是,这种改善在统计学上毫无意义,多年来高度可变的 Kendall tau 改善超过基线就是证明。这相当于一个非常宽的 95%置信区间,很容易包含零水平线(见上图)。

简而言之,即使提前停止似乎也无助于提高混动车型的预测排名。然而,在我们完全放弃混合模型之前,让我们看看我们是否可以以某种方式结合两种策略来减少模型过度拟合——模型惩罚和提前停止——作为改进该模型的最后努力。

结合模型惩罚和提前停止

将模型惩罚和早期停止相结合无非是在计算梯度(模型惩罚)的同时包括惩罚参数λ,同时将梯度下降的迭代次数限制为较小的数目(早期停止)。因此,除了限制梯度下降算法步骤 2 的迭代次数外,Python 代码对模型惩罚没有任何改变——例如,只有for i in range(10)

对于模型参数的每个组合(梯度下降的迭代次数以及惩罚参数λ),我们可以测量混合模型相对于基线模型的肯德尔τ的平均差异(超过 10 年),并将其绘制在下面的热图中:

Color intensity signifies how much higher on average the Kendall’s tau of hybrid model over the baseline model is

从上面的热图可以看出:

  • 即使模型被罚(λ>0),梯度下降的理想停止点仍在 40–50 次迭代附近徘徊,因为与基线相比,这是肯德尔τ平均最高的区域。
  • 然而,随着模型越来越多地被惩罚(增加λ),当与模型惩罚相结合时,早期停止似乎越来越无效。这在λ=10 及以上的贫瘠荒地上看得最清楚,除了一个小岛在λ=100 时迭代次数接近 80 次。
  • 早期停止迭代和λ之间的关系可以以不同的方式可视化,首先通过挑选每个λ的 Kendall(在基线模型上)具有最高平均差的迭代(该λ的理想停止点),并将其性能与如果没有执行早期停止(即如果允许模型完全收敛)的性能进行比较。

The small numbers on the graph show the ideal iteration for early stopping at each λ

以上是男性和女性溜冰者的对比图,显示了非常有趣的模式:

  • 对于女性滑冰运动员来说,与之前讨论的男性运动员类似,单独的惩罚往往会降低她们预测排名的准确性(右图中的蓝线):随着λ的增加,肯德尔τ与基线模型的平均差异越小。
  • 对于男性和女性来说,提前停止可以使肯德尔的 tau 值比基线模型有最大的提升:男性平均提升 0.005,而女性则提升了 0.009。诚然,这些改善在统计上并不显著,这可以从它们包含水平线为零的宽置信区间中看出。
  • 此外,这种通过提前停止的改进适用于每一级惩罚,尤其是当模型被高度惩罚(高λ)时。相比之下,即使早期停止也仍然无法挽救一个高度惩罚的模型,正如当λ增加时早期停止模型的 Kendall tau 改进普遍降低所证明的那样(两个图中的紫色线)。

结论

从上面的分析可以清楚地看出,对于这个问题,提前停止不仅比模型惩罚(如前所述)更容易实现,而且与后者相比,它还具有明显的性能优势。因此,对于两种性别,只选择早期停车来改进混合模式。以下是将其应用于 10 年训练集时的最终结果:

  • 请注意,即使早期停止模型与基线模型相比在肯德尔τ方面提供了边际改善,这些改善在很大程度上是不显著的。这可以从肯德尔τ与基线模型的平均差异中看出:这种差异的 95%置信区间对于两种性别来说仍然低于零。
  • 此外,由于世界锦标赛排名本身被用于选择最佳模型参数,即梯度下降应该停止时的迭代,模型本身很可能过度拟合 10 年训练集中的数据。因此,在项目的最后部分,我将评估这些提前停止的模型(以及其他模型)与测试集中剩余的 4 年作为最终基准。

资源

在项目的这一部分开发的混合模型最初是受推荐系统中使用的各种矩阵分解技术的启发。一般来说,这些技术会将许多用户对许多商品(例如,在亚马逊上)留下的评级矩阵“分解”成特定于用户的矩阵和特定于商品的矩阵。当这两个矩阵相乘时,它们可以近似原始评级矩阵。

同样的原理在我们的混合模型中成立:评级矩阵只不过是所有事件-溜冰者对的季节分数的数据透视表,而用户特定和项目特定的矩阵只不过是溜冰者和事件潜在分数向量。因此,当乘以skater_scores @ season_scores(加上顶部的基线分数)时,我们可以近似得出赛季的true_scores。您可以查看这篇非常可读的关于推荐系统矩阵分解的文章,其中也包括梯度下降算法的推导,该算法与本项目中使用的算法非常相似,通常被称为 FunkSVD

也就是说,我们目前的混合模型只涉及一个单一因素:每个运动员和每个项目只由一个潜在的分数来表示。相比之下,推荐系统中的矩阵分解往往涉及多个因素。在项目的下一部分中,我将描述多因子矩阵分解如何应用于我们的排序问题。

用前馈神经网络预测食物份量

原文:https://towardsdatascience.com/predicting-food-serving-sizes-with-a-feed-forward-neural-network-2d8d40d82f72?source=collection_archive---------30-----------------------

在我在大会的沉浸式项目中,我必须完成一个顶点项目,一个完全由我自己设计的项目,除了满足几个关键标准之外,没有任何关于做什么或如何去做的指示。所以我决定专注于我非常了解并且热爱的东西——食物!我找到了一个由美国农业部维护的数据集,其中包含了该国超过 260,000 种包装和品牌食品,被恰当地命名为美国农业部品牌食品数据库。他们对数据库有相当开放的 API 访问,但幸运的是,完整的 API 也可以作为 zip 文件下载,节省了我 30 多个小时来 ping 他们的服务器。它保存了大量信息,从制造商和成分的基本信息到更具技术性的信息,如用于确定每种营养价值的特定实验室方法。引起我注意的一件事是这些产品的食用量差异很大。

Spam! Credit: Lhe3460, CC BY-SA 4.0

问题

现在,对于那些不在食品科学游戏中的人来说,每份食物的份量实际上有食品和药物管理局的指导方针,食品公司必须遵守,称为习惯食用的参考量。这是一个灰色地带,因为它们不仅仅是一刀切的(老实说,我不能说这是一个双关语),但它们必须被用作公司的出发点。因此,开发团队仍有大量工作要做,以确定食品的最终食用量。这成了我项目的中心目标:建立一个模型来预测一种食品的份量。

清洁

第一步是合并所有的数据。因为这是一个关系数据库,所以我要找的信息被分散在多个表中。我将食品名称和它们的详细信息结合起来,比如品牌所有者、市场类别和成分。添加营养成分稍微有点棘手,因为我必须创建一个数据透视表,将它们与产品放在同一格式中。每种食物的能量含量有两个不同的单位:千卡和千焦,这有点小问题。千焦在欧洲更常见,而美国使用千卡,这是一个大写的 C 卡路里(是的,卡路里和卡路里之间有区别,不,我也不喜欢它)。我编码了一个快速转换,然后我就回到了正轨。最终的组合表如下所示:

然而,完整的营养表并没有被剔除。它必须被缩小到前 10 个最常见的。这主要是由于营养标签的一个重要事实:它们并不总是列出相同的东西。以外的常见营养素(脂肪、蛋白质、胆固醇等。),必输项很少,大部分维生素和矿物质在大多数情况下是可选的。所以,在 97 种不同的营养成分中,绝大多数超过 90%是零的情况下,我不得不坚持强制输入。

从那以后,我还得清理许多遗留的东西。一个例子是许多卡路里计数是空的。起初,这似乎是一件大事,因为估算不是一件轻而易举的事情,但如果你对营养学很感兴趣,你应该知道实际上有一种非常简单的方法来估算卡路里,通过使用食物的三种主要营养素——脂肪、蛋白质和碳水化合物。总的来说,这些是我们每天摄入的卡路里的提供者,每种类型都有特定的量。蛋白质和碳水化合物每克都给我们的身体提供 4 卡路里,而脂肪每克提供 9 卡路里。再一次,一些快速的数学和代码是有序的,我们丢失的卡路里已经被考虑。

当我开始深入研究这些数据时,我注意到一些有趣的事实。食用量的分布有一个长的右尾,大部分食物在 0-50 克之间。由于形状的原因,我决定最好将数据转换成对数形式,以便进行建模。查看市场类别的影响也揭示了最大份量的食物类型,液体、汤类和冷冻食品构成了前 10 项。这很有意义,因为与许多其他食物相比,液体相当稠密,像冷冻主菜这样的东西可能会取代其他几种食物,本质上是多份食物(如意大利面、蔬菜和肉类)。

建模

我最终使用的功能是全营养事实,和市场类别(这是 dummied)。我曾试图对配料表执行 NLP 以进一步扩展功能,但由于课程的时间限制,我无法有效地优化它们。现在我有了更多的时间,我想回到这个话题上来。

Basic Neural Network. Credit: Glosser.ca, CC BY-SA 3.0

使用并评估了几种模型,但最终我决定使用前馈神经网络。如果你需要复习一下神经网络,可以查看一下简单设置的图表。它们本质上是一系列复杂的逻辑回归(线条),由您决定有多少隐藏层,以及每层中有多少节点(圆圈)。有一些关于建立神经网络的指导方针,但是可能性确实是无穷无尽的。

带着为我的神经网络寻找最佳参数的艰巨任务,我决定创建一个工具来帮助我加快这个过程。在 SciKit-Learn 中,有一个非常有用的工具,GridSearchCV可以为各种各样的模型找到最佳参数。您设置您正在构建的模型,然后可以输入广泛的超参数,GridSearchCV将构建这些模型,并告诉您哪个组合是最佳的。对于 Keras 中的神经网络,没有与此功能等效的功能,所以我构建了自己的功能。它是三个不同功能的组合:一个用于接受参数字典并迭代出每个排列,一个用于基于这些参数排列构建每个不同的神经网络,另一个用于组合这些功能,执行评估并跟踪最佳模型。这是很多代码,所以我只在这里包括了最后一块,但如果你有兴趣看到完整的细节,请查看回购

**def** nn_grid_search(
    X,
    y,
    grid_params,
    random_state=42
):
    *### this will make a series of FFNN models* 
    *### and return the one with the best score as set below*
    *### currently set to test r2 score*

    *# list of all parameter combinations*
    all_params = permutate_params(grid_params)

    *# creating vars with to update each iter*
    best_model = **None**
    best_score = 0.0 
    best_params = **None**
    best_history = **None**

    *# train/test split the data*
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=random_state)

    *# scaling data*
    ss = StandardScaler()
    X_train_sc = ss.fit_transform(X_train)
    X_test_sc = ss.transform(X_test)

    *# looping through the unpacked parameter list*
    **for** i, params **in** enumerate(all_params):

        *# keeping track of which model we're running*
        print(f"Building model {i + 1} of {len(all_params)}")

        *# bulding the model*
        model, history = build_model(
            params_dict = params,
            X_train = X_train_sc, 
            X_test = X_test_sc, 
            y_train = y_train, 
            y_test = y_test
        )

        *# making preds and scoring*
        test_preds = model.predict(X_test_sc)
        score = metrics.r2_score(y_test, test_preds)

        *# checking if the score beats the current best*
        *# updates vars if true*
        **if** score > best_score:
            print("***Good R2 found: **{:.2%}*****".format(score))
            best_score = score
            best_model = model
            best_params = params
            best_history = history

    *# loop is done, return the best model*
    **return** {
        "best_model"   : best_model,
        "best_score"   : best_score,
        "best_params"  : best_params,
        "best_history" : best_history,
        "test_preds"   : test_preds
    }

我决定给我的模型设置 3 个隐藏层,试图在避免过度拟合的同时平衡深度学习,并着手确定我的其他参数。我给了每一层一个 ReLU 激活,这通常被认为是这些网络最好的激活功能。接下来,我需要在每个隐藏层中选择多少个节点。这通常是一个基数为 2 的数字,因此这已经有助于缩小选择范围。此外,让您的第一个图层具有与输入(即您的要素)数量相似的结点是一个好主意。总共不到 200 个特征,我选择了一系列合适的数字。由于拥有一个金字塔形状的网络(每个后续层的节点更少)也是有帮助的,所以我有一个清晰的方向来建立我的完整参数字典。

在运行搜索并获得最佳模型的最终结果后,我采用这些参数,并通过增加运行的时期和应用各种正则化技术(L2 惩罚、退出和提前停止)来继续微调模型。我的模型的最终参数集如下:

'best_params': {
 'first_layer_nodes': 256,
  'first_dropout_rate': 0,
  'second_layer_nodes': 128,
  'second_dropout_rate': 0,
  'third_layer_nodes': 64,
  'third_dropout_rate': 0,
  'reg': 0.001,
  'epochs': 75,
}

评价

我评估神经网络的第一步是可视化它运行的全部 75 个时期的损失曲线,用 Adam 优化器监控均方误差作为损失。

我发现曲线非常平滑,训练集和测试集的损失值非常一致,没有任何明显的差异。

然后,我使用两个关键指标来分析模型的表现:调整后的 R 和均方根误差(RMSE)。使用调整后的 R 非常有用,因为它不仅能告诉您模型覆盖了多少数据方差,还能说明您拥有多少要素,从而确定它们是否真的有助于您的模型。另一方面,RMSE 为您提供了一个可直接解释的值,用于查看模型的预测值通常与真实值之间的误差。

这些分数彼此非常接近,也比我构建的其他模型的分数好得多,我觉得我已经在这段时间内尽可能地充分优化了我的模型。不幸的是,当我们现实地看待这些结果时,预测的食用量通常与实际食用量相差 24 克以上可能会有些问题,因为平均食用量约为 64 克(相差 38%)。

包扎

最后,我不相信这是一个完全的成功,因为我的模型有很多错误,它可能不会比仅仅遵循 FDA 的指导方针更有帮助。由于我无法将这些指导方针整合到数据中,我无法确定我的模型如何直接与它们相抗衡,所以一切都应该持保留态度(这绝对是一语双关)。

然而,它确实显示出了希望,我认为我可以做很多事情来改进这个项目。我要做的第一件事就是花些时间完善 NLP 步骤,从而整合这些成分。此外,如果这是一个生产设置,我将实现 API 访问,以跟上对数据库或目标相关食品的特定组所做的任何更改。

这绝对是一个有趣的项目,我学到了很多关于完整的从前到后的数据科学工作流程,清理数据,尤其是构建神经网络。

如果你碰巧还在读这篇文章,那么我为你坚持和我一起探索食物世界而鼓掌!请回来查看更多帖子!

你可以在这里找到全项目回购。

你也可以在 LinkedIn 上找到我

用 XGBoost 预测足球运动员的商业价值

原文:https://towardsdatascience.com/predicting-football-players-commercial-value-with-xgboost-d0670d9e9d2e?source=collection_archive---------13-----------------------

Pic by Jannes Glas - Unsplash

商业价值真的只依赖于纯竞技场属性吗?还是另有隐情?

如果你和我一样,你会觉得有必要了解所有事物是如何工作的,如果你对数据科学感兴趣,你会觉得有冲动去预测所有可以预测的事情。在这种情况下,我会告诉你如何预测足球运动员的商业价值仅仅依靠他们的足球技术。我们将使用 XGBoost 来这样做,并在这样做的同时了解更多的库。

我使用 Jupyter 笔记本在 Python 中进行了分析。笔记本可以在我的 GitHub 页面上找到,我会不时地加入一些代码片段,让这个故事更有教育意义。我鼓励读者拿起笔记本,边阅读文章边阅读。

数据

该数据集包括超过 18000 个足球运动员的条目,按照价值排序,从最有价值到不太有价值。虽然数据集与 FIFA '19 视频游戏相关,但其玩家商业估值和玩家游戏技能评级非常准确,因此我们可以假设我们正在处理现实生活中的玩家数据。

导入

首先,让我们获取数据集。我最初是从 Kaggle 获得的,但是你也可以从我的 GitHub 页面获得。

安装 pandas 以便能够操作数据,然后直接从 URL 导入数据集:

import pandas as pdurl = '[https://raw.githubusercontent.com/estebanvillaturek/fifa19/master/fifa19_data.csv'](https://raw.githubusercontent.com/estebanvillaturek/fifa19/master/fifa19_data.csv')data = pd.read_csv(url)data.head()data.shape

正如我们所看到的,我们得到了一个包含超过 18k 行和 89 列的数据集,包括所有类型的数据格式。

探索

我们主要对“价值”一栏感兴趣,它包含了每个球员的商业价值。我们注意到一些事情:它的条目被格式化为字符串,前面有一个€符号,百万和千分别用大写的 M 和 K 表示。这意味着,我们将有一些争论和重新格式化要做,因为我们想得到所有的数值作为数值,以便建立和运行预测模型。

重新格式化“值”

我们可以使用一个简单的 lambda 函数来移除€符号。在这种情况下,我们求助于 lambda 函数(也称为匿名函数),因为我们只需要对整个列应用一次性函数,并且不用任何东西替换€符号。

# Remove €data.Value = data.Value.apply(lambda x: x.replace('€', ''))

接下来,由于值的范围从数百万到数千,我们需要找到一种方法,首先找到以“K”结尾的条目,然后删除它,将字符串转换为浮点数(数字数据类型),然后乘以 1000。然后,我们将对百万欧元的条目重复相同的过程。

最好的方法是使用 for 循环迭代该列。创建迭代器之前的最佳实践是使用 next()函数模拟迭代器,该函数返回第一次迭代,这样我们就可以使用结果来创建最终迭代器。注意,为了迭代 pandas 数据框列,即一个序列,我们调用。items()方法。

# preview iteration next(data.Value.items())

第一次迭代的结果是一个元组,由第一个元素索引和第二个元素值本身组成。有了这些信息,我们就可以构建第一个迭代器来重新格式化千欧元单元格。

# reformat thousands cellsfor i,j in data.Value.items():
    if 'K' in j:
        data.Value.iloc[i] = float(j.replace('K', '')) * 1000

现在,让我们对剩下的百万欧元细胞做同样的事情。

# Reformat millions cellsfor i,j in data.Value.items():
    if 'M' in j:
        data.Value.iloc[i] = float(j.replace('M', '')) * 1000000

将“值”转换为数值

既然“Value”列中的所有条目都可以表示为数字数据类型,我们就可以相应地转换它们,这样我们就可以获得回归模型的目标(没错,我们将预测一个连续的因变量,因此我们将编译一个回归模型,但稍后会详细介绍)。

# to_numericdata.Value = pd.to_numeric(data.Value, errors = 'coerce')

创建新的全数字数据框

现在我们知道我们将在练习中使用回归变量,我们将创建一个新的、更具体的数据框,该数据框将仅包含数字变量,并将由目标变量“值”和所有预测变量、每个玩家的游戏技能的排名分数(每个玩家总共 34 个排名能力)组成。

# first, the target variablescores = pd.DataFrame(data.Value) # then, slice the ranking scores for players' abilitiesdata1 = data.iloc[:, 54:88] #finally, join the the two subsetsscores = pd.concat([scores, data1], axis = 1)

描述分数&留意偏斜度

由于我们处理的是数值,我们必须检查数据是如何分布的。这是理解数据行为的基础,也是理解我们是否必须对数据进行规范化或标准化的基础。请记住,归一化将我们的数据转换为从 0 到 1 的范围,正则化将我们的数据转换为正态分布,平均值为 0,标准差为 1。

# The describe method returns an overview of the descriptive stats of all our columns.scores.describe()

我们可以从目测数据中看出,有轻微的偏态倾向,因为中位数和平均数总是分开的(在正态分布中,中位数和平均数是相同的)。在这种情况下,偏斜的一个特殊情况是“值”变量。它有相当多的离群值(梅西、罗纳尔多等),将平均值拉向右边。我们可以使用 Seaborn 直观地看到分布,但首先我们需要检查数据中是否有缺失值。

检查 NaNs

检查缺失值、nan 等的简单方法。,就是使用。isnull()方法,然后按列对所有缺失值求和。像这样,我们可以看到每列有多少个 nan,因此,决定如何处理它们,即是否删除它们,估算它们,等等。

# sum up all non-values scores.isnull().sum()

结果是惊人的:尽管我们的预测变量每列只有 48 个缺失值,但我们的目标变量“值”有将近 6k 个缺失值。这意味着,如果我们决定删除所有的 nan,我们将丢失几乎三分之一的数据集。).那是不行的。

估算中值以避免数据丢失

或者,我们将求助于数据插补,这是一种允许我们用另一个值替换所有缺失值的技术。对于此示例,我们将使用每列的中值来估算每列中的 NaNs。我们选择中位数,因为它根本不会影响每个变量的分布,也许除了“价值”之外。让我们检查一下。

# to not lose around 6k data points, let's explore missing data imputationscores.iloc[:, :].fillna(scores.iloc[:, :].median(), inplace = True)print(scores.isnull().sum())

现在,数据集中没有丢失的值,所以我们可以开始可视化地研究分布。

检查单个分布

这里我们也可以求助于 for 循环,我们可以要求机器绘制每个变量的分布。

# Explore the distribution of each variableprovisional = scoresfor i, j in provisional.items():
    sns.kdeplot(j, shade = True)
    plt.figure()

正如我们所看到的,“价值”仍然是高度倾斜的,我们的大多数变量倾向于正态分布(除了那些相对于守门员能力排名的变量),其中一些变量倾向于双峰。

正常化(如果需要)

首先,让我们把目标变量和预测变量分开,因为它们需要不同的处理方法。

# Separate examples and targetX = scores.iloc[:, 1:]y = scores.iloc[:, 0]

为了练习的缘故,我们将标准化和规范化(标度)我们的预测因子,以便能够评估治疗的差异。我们将使用 sklearn 库进行预处理任务。

# Now we’ll first normalize the data so that we can have it in a 0 to 1 range and then standardize itfrom sklearn import preprocessing#nrm_X = preprocessing.normalize(X)#std_X1 = preprocessing.scale(X)#std_X = preprocessing.scale(nrm_X)

现在,让我们直观地比较我们的原始“交叉”预测器及其缩放和标准化版本。

# Assess the difference visuallysns.kdeplot(std_X[:, 0])sns.kdeplot(std_X1[:, 0])

Original Crossing variable

The orange distribution is the scaled version w/o normalization. The blue distribution is the same scaled variable, only after being normalized first.

我们可以看到,虽然差别不大,但蓝色分布的方差要小一些。让我们继续这个标准化和缩放的预测数据。

对数转换目标变量

正如我们前面看到的,“价值”是相当扭曲的,因为有天文数字估计的商业价值的突出离群值:梅西,罗纳尔多,小内马尔,等等。这是有问题的,因为绝大多数球员的价值都远低于百万欧元。

要在数据中解决这个问题,我们必须对“值”变量进行对数转换。在处理随时间增长的经济或金融数据(工资、财富、GDP 等)时,对数变换非常有用。)因为它们往往是指数增长,而不是线性增长。但是要小心,不要对零进行对数转换,否则会得到 inf 值,这将使您无法绘制或继续分析您的数据!

# Now we'll have to rescale the target variable - we'll log-transform it - it's crazy skewed!plt.figure(figsize=(15,10))
sns.kdeplot(y, shade = True)

由于数据中有 252 个零,我们将再次估算中值,以便能够对变量进行对数转换。

y[y == 0].count() # we have 252 zeroes in 'Value' - we have to impute the with the mediany[y == 0] = np.median(scores.Value)y[y == 0].count() # ready, now we can log-transform the variable w/o getting any - inf values

现在,让我们对目标变量 y 进行对数变换,并对其值进行指数运算,以练习将它们恢复到原始比例。

# do the log-transformation - looks much better!y_log = np.log1p(y)plt.figure(figsize = (15,10))
sns.kdeplot(y_log, shade = True)

Log-transformed ‘Value’ variable

# now we can just do the exponential of any value of y to get back the original valuesnp.expm1(y_log[0])

构建 XGBoost 回归模型

首先,我们可以直观地评估随着每个游戏技能等级分数的增加,价值是如何增加的。为此,我们可以再次使用 For 循环,这样我们就可以创建 34 个散点图,每个散点图对应玩家的额定游戏能力。这里我们将使用未缩放的数据集,但是为了更好的可视化,我们将对图中的 x 变量进行对数变换。

# Visualize the relationships within the datasetx_col = "Value"
y_columns = scores.loc[:, scores.columns != 'Value']for y_col in y_columns:figure = plt.figure
    plt.figure(figsize = (15,10))
    ax = sns.regplot(x=x_col, y=y_col, data = scores, logx = True)
    ax.set_xlabel(x_col)
    ax.set_ylabel(y_col)
    ax.set_title("{} & {}".format(x_col, y_col))plt.show()

我们可以清楚地看到,随着每项技能的增加,价值也会增加,这是很自然的。我们还可以看到,在一大群更为普通的玩家中,具有更高商业价值的顶级玩家是如何成为显著的离群者的。

现在让我们来看看 XGBoost 模型。正如这里完美解释的,XGBoost 是 2016 年开发的一种基于决策树的卓越算法,它利用梯度下降和不同硬件相关计算技术的力量来提升模型,使其符合结构化集合的数据,这是其他算法无法做到的,至少在今天是如此。它非常适合构建分类或回归模型。

首先,我们将拆分数据,

# We will use a 20% of the data set as the test setfrom sklearn.model_selection import train_test_splitX_train, X_test, y_train, y_test = train_test_split(std_X, y_log ,test_size=0.2, random_state = 42)

然后我们将编译并拟合回归模型。

# Compile and fit the modelimport xgboostmodel = xgboost.XGBRegressor(objective='reg:squarederror')model.fit(X_train, y_train)

我们用我们的测试集来预测。

# Now we predict based on the fitted modelpreds = model.predict(X_test)

最后,我们基于适当的准确性指标来评估我们的模型。在这种情况下,我们将使用平均绝对误差(MAE ),它代表我们的预测和我们的真实测试目标之间的差异的绝对值。

# Accuracyfrom sklearn.metrics import mean_absolute_errormae = mean_absolute_error(y_test, preds)

请记住,由于我们的预测和测试目标是对数转换的,我们需要取 MAE 的指数来获得原始单位的度量。

np.expm1(mae)

这给了我们大约 0.75 €的平均相对误差,相当不错,对吧?

结论

我们刚刚看到了一种方法,只根据球员不同的足球能力评级来预测球员的商业价值,而不考虑其他因素。虽然预测本身相当精确,但我们在模型中的变量解释的方差并不太大(R2 上升到只有大约 35%),这强调了清楚你想通过模型实现什么的重要性:要么是预测准确性,要么是通过自变量解释因变量中的方差来彻底解释现象,这是机器学习和统计学之间的基本差异之一。

奖励曲目

  1. 想想职业足球历史上所有可以避免的转会失误!看看《经济学人》的这篇文章,感受一下成功(和失败)的球员在完成转会时的评估对球队表现的巨大影响。
  2. 如果你想知道在预测商业价值时哪些玩家能力是最关键的,你可以探索 XGBoost 提供的特性重要性指标。不过要小心:看看这篇文章,感受一下重要性度量告诉你什么!

让我知道你的想法,别忘了留下评论!

从零开始使用快速 AI API 通过 RNNs 预测未来的医疗诊断

原文:https://towardsdatascience.com/predicting-future-medical-diagnoses-with-rnns-using-fast-ai-api-from-scratch-ecf78aaf56a2?source=collection_archive---------6-----------------------

全面 pytorch 实现医生 AI 论文使用电子健康档案

在本教程的第一篇第一部分中,我们创建了 Edward Choi 等人的医生 AI:通过递归神经网络预测临床事件论文(2016) 的粗略模板。在本教程中,我们使用 Fast.ai 自下而上方法对其进行了进一步处理。该代码功能齐全,有关数据处理的详细信息可在第一部分中获取。

详细代码: Github

加载数据

关于数据集:

本研究将利用 MIMIC III 电子健康记录(EHR)数据集,该数据集包含 38,645 名成人和 7,875 名新生儿的 58,000 多份住院记录。该数据集是从 2001 年 6 月至 2012 年 10 月在贝斯以色列女执事医疗中心的去识别重症监护病房住院病人的集合。在第一部分中可以找到所使用的数据预处理步骤的详细演练。

数据预处理数据集将被加载,并按75%:15%:10%比率分成训练、测试和验证集。

Data loading function

填充序列:处理可变长度序列

使用在第一部分中创建的人工 EHR 数据,我们将序列填充到每个小批量中最长序列的长度。为了帮助更深入地解释这一点,让我们看一看在第一部分中创建的Artificial EHR data

使用人工生成的 EHR 数据进行详细解释

在这里,您可以看到我们有一个包含两个列表数组,每个列表代表一个独特的患者。现在,在每个列表中有一系列列表,每个列表代表一次独特的访问。最后,编码的数字代表每次就诊时分配的诊断代码。值得注意的是,鉴于每位患者病情的独特性,指定的就诊和诊断代码都有variable length序列。因为 EHR 数据本质上是纵向,我们通常对了解患者的风险或随时间的进展感兴趣。当使用表格数据处理时,这些嵌套的依赖于时间的variable length序列会很快变得复杂。回想第一部分的下图,详细描述了每次就诊日期与就诊期间指定的诊断代码之间的映射关系。

Patient Sequence Encodings

Python Pickled List of List containing patient visits and encoded diagnosis codes

那么我们到底用这个嵌套列表填充什么呢?

让我们分解填充函数:

  1. lenghts = np.array([len(seq) for seq in seqs]) - 1这里神秘地从长度中减去 1,在作者的笔记中,他提到visitlabel文件必须匹配,因为算法会考虑推理时间的时间延迟。

这是什么意思?考虑到数据的结构,每个患者记录中的最后一次就诊将被删除。如此处所示:

Removing the last visit for inference

旁白:在字符级 RNN 中处理可变长度序列

如果这是一个角色级别的问题,我们就说[ SparkleDorianDeepLearning。这些序列首先按长度降序排列,并用零(红色)填充,其中每个字母代表一个令牌。如下所示:

Variable length sequence padding

EHR 数据:

然而,对于这种形式的 EHR 数据,给出了我们当前的问题,而不是每个编码的诊断代码代表一个唯一的令牌。在这种情况下,每次访问代表一个令牌/序列。因此,使用与字符级 RNNs 相同的方法,我们首先按照患者就诊降序排列每个小批量。在这种情况下,患者 1 具有最长的就诊历史,共有两次就诊,而患者 2 的就诊将被填充到最大长度 2,因为它是最长的序列。如下所示:

Padding EHR data

现在,我们已经解决了可变长度问题,我们可以继续对我们的序列进行多一热编码。这将产生所需的 S x B x I 尺寸(序列长度、批量大小、输入尺寸/vocab)。

在这里,我们可以很容易地看到,序列将代表每个小批量中就诊历史最长的患者,而所有其他人将被填充到这个长度(红色)。根据所需的批次大小,批次大小将代表每个时间步输入多少患者序列。最后,内部列表将被编码为词汇表的长度,在本例中是整个数据集中唯一诊断代码的数量。

Multi-one hot encoded sequences

标签

以确保标签被移位一个序列,以便算法可以准确地预测下一个时间步长。作者通过确保训练数据排除每个患者历史中的最后一次就诊来解决这一问题,使用这种逻辑for xvec, subseq in zip(x[:, idx, :], seq[:-1]):,其中我们采用每个患者就诊记录seq[:-1]中除最后一次就诊之外的所有就诊。对于标签,这意味着序列将从患者的第二次就诊开始,或者按照 python 的索引风格,第一个索引for yvec, subseq in zip(y[:, idx, :], label[1:]),其中标签label[1:]移动一位。

Label time step lag

什么是掩蔽,它有什么作用?

屏蔽允许算法知道真正的序列在 one-hot 编码数据中的位置,简单地说就是忽略/过滤掉填充值,在我们的例子中填充值为零。这使我们能够轻松处理 RNNs 中的可变长度序列,它需要固定长度的输入。是怎么做到的?还记得lengths变量吗?该变量以降序存储每个患者序列的有效长度(回忆:在移除每个记录中的最后一个序列以进行推断后,例如,患者 1 有 3 次就诊,但长度将仅反映 2 次)。然后,代码mask[:lengths[idx], idx] = 1.中的逻辑用 1 沿着行填充我们的归零张量,以匹配从最大到最小的每个患者序列的长度。

lenghts_artificial → array([2, 1])

mask_artificial → tensor([[1., 1.], [1., 0.]])

数据加载器和采样器

Dataset类是一个抽象类,表示 x 和 y 对中的数据。

Sampler类随机打乱训练集的顺序(验证集不会被随机化)。此外,它保留创建完整批次所需的准确序列数量。

DataLoader类结合了数据集和数据采样器,后者遍历数据集并抓取批处理。

嵌入层

在将输入数据呈现给 GRU 之前,Custom_Embedding类用于将高维多热点编码向量投影到低维空间。在这一步中,作者使用了两种方法

  1. 随机初始化,然后在反向推进期间学习适当的 W(emb)W(emb)权重

2.使用 Skip-gram 算法初始化预训练嵌入,然后在 back-prop 期间优化权重

在本文的实现中,我们使用了第一种方法。因此,创建了Custom Embedding类来在嵌入层上应用 tanh 激活。

Custom embedding Layer

脱落层

在本文中,作者使用了由 Srivastava (2014) 首次引入的辍学的简单应用。虽然这种方法效果很好,但它影响了 RNNs 保持长期相关性的能力,因为我们没有在每个时间步长保持相同的掩码。为什么这很重要?很简单,如果我们在每个时间步随机采样一个新的掩码,它会干扰我们的 RNNs 连接,使网络难以确定哪些信息可能是长期相关的。在这种方法中,我测试了 Gal & Ghahramani (2016)提出并由 Merity (2017) 进一步开发的 LSTMs 技术。在这里,他们提出通过在 LSTMs 中的多个时间步长上使用相同的漏失掩码来克服上述与随机采样相关的问题。在这里,我将应用相同的方法在每层(两层)之间的 GRU 上。

Dropout Layer

艾医生:通过递归神经网络预测临床事件

尽管 LSTMs 很受欢迎,也很受青睐。本文使用了 GRU 架构,因为它简单并且能够获得与 LSTMs 相似的性能。本文中使用的数据集包含263, 706 patients,而我们的数据集(MIMIC III)总共包含7537 patients。然而,作者证明了在一个医院系统缺乏训练像 AI 博士这样的深度学习模型所需的大规模数据集的情况下,迁移学习可能是一个可行的选择。使用以下架构,我的兴趣在于对患者未来诊断代码的预测。然而,人们可以很容易地推断出该算法来预测诊断和就诊间隔时间。

Model Architecture

GRU 层:

这个类使用了EHR_GRU单元格类,并允许在期望的层数上迭代。

损失函数:

用于评估模型性能的损失函数包含交叉熵的组合。每个小批量的预测损失被标准化为序列长度。最后,L2 范数正则化应用于所有的权重矩阵。

模型参数:

这里使用的参数选自 AI 博士论文中使用的参数。这种方法和我在这里介绍的方法之间的主要区别是,我对 RNNs 使用了更新的 drop out 方法。

num class = 4894
input dimsize = 4894
embSize = 200
hiddenDimSize = 200
batch size = 100 num layers = 2

加载数据:

需要注意的是,您希望将序列和标签的同一个文件传递到load_data函数中,因为模型会在内部负责调整预测的时间步长。

训练和验证循环

我的实现与论文算法的比较:

我在论文的算法上运行了相同的序列,这是用 theano 和 python 2.7 编写的,这里可以看到 10 个时期后的最佳交叉熵分数约为 86.79,而 my 为 107。虽然,通过一些超参数调整和优化,我并没有表现得更好,但算法肯定会表现得更好。

Dr. Algorithm results for comparison

观察结果:

正如你所看到的,我们的训练和验证损失几乎是一样的,实际论文中使用的数据是如此之少。如果不过度拟合,可能很难获得更好的性能。然而,本教程的目的是提供一个如何使用 EHR 数据驱动洞察力的详细演练!

完整脚本

后续步骤:

  1. 使用 Fast 添加回调。人工智能的回调方法来跟踪训练数据
  2. 尝试不同的初始化方法

致谢:

  1. Fast.ai(雷切尔·托马斯、杰瑞米·霍华德和令人惊叹的 fast.ai 社区)
  2. 多里安·普勒里

使用随机森林模型预测鱼类样本的地理来源

原文:https://towardsdatascience.com/predicting-geographic-origin-for-fish-samples-using-random-forest-models-3a0f791d0f5c?source=collection_archive---------24-----------------------

机器学习概念如何支持渔业管理

问题

我试图展示一种分析的效用,这种分析根据鱼的耳骨形状,对来自特定物种的鱼样本进行分组。基本概念是,特定种类的鱼,比如鳕鱼,有独特的耳骨形状,可以用来识别它们来自哪个地理区域。我想展示我设计的特征能多好地预测每个样本的地理来源;本质上,这项技术有合适的预测能力吗?

数据收集

你可能会问,如何量化耳骨的形状?我用离散小波变换(类似于常用的傅立叶变换)用余弦波来描述每块骨头的轮廓形状(图 1)。这种转换给了我 64 个系数来描述每条鱼耳骨的形态差异,这是我们的特征。

Figure 1: Ear bone and the derived outline using a Wavelet transformation

回应:地理起源

特征:描述骨骼形状的小波系数

数据处理

我在不同的人群中进行了不平等的抽样调查;在不涉及生物学细节的情况下,我需要在没有人口统计学细节(如长度、年龄等)的情况下,分离出地理来源对骨骼形状的影响。)促成关系。我使用重复的 ANCOVAs 来消除人口统计变量随地理区域显著变化的特征,并应用 Bonferonni 调整来最小化重复分析中第一类错误的累积。

# function that will derive the p value for covariates
ancova.pvalues <- function(resp, pred, cov){
  return(unlist(summary(aov(resp~pred*cov)))['Pr(>F)3'])
}# apply to each feature (given predictor of region (pops) and covariate of length)
p.values<-0 # stores values
for (i in 1:length(colnames(Wavecoefs))){
  p.values[i]<-ancova.pvalues(unlist(Wavecoefs[i]), pops, length_cm)
}
which(p.values<0.05) # which features should we omit

有些特征是偏斜的,所以我对那些偏斜度大于 0.75 的特征应用了 box cox 变换。

很少功能包含 NAs 我用每个特性的平均值代替了 NAs。

我使用方便的方法“train_test_split”将数据集分为训练集和测试集,分别使用 80%和 20%的数据。

from sklearn.model_selection import train_test_split
train_X, test_X, train_y, test_y = train_test_split(df, resp, random_state = 0, test_size=.2)

我的响应变量——地理区域——是不均衡抽样的(图 2)。我想展示我设计的功能可以预测来自多个不同地区的鱼的原产地,并且我想最小化样本大小的变化对模型预测的影响。为此,我随机对最常见的类(海湾,蓝色)进行欠采样。

Figure 2: Sample size for each geographic region (1-Gulf of Mexico, 2-West Atlantic, 0-East Atlantic)

包不平衡学习为此提供了一些有用的方法;我使用“RandomUnderSampler”来创建一个更加平衡的训练数据集,以便在其上拟合我的模型(图 3)。

from imblearn.under_sampling import RandomUnderSamplerrus = RandomUnderSampler(return_indices=True)
X_rus, y_rus, id_rus = rus.fit_sample(train_X, train_y)

Figure 3: Sample size for each geographic region after undersampling

建模

鉴于描述骨骼形状的特征,我使用随机森林分类器来预测样本来自的区域。首先,我确定了最佳超参数值:

max_features :每次分割要考虑的最大特征数

max_depth :任意树的最大分割数

min_samples_split :分割一个节点所需的最小样本数

min_samples_leaf :每个叶节点所需的最小样本数

自举:数据集是自举还是整个数据集用于每棵树

标准:用于评估每次分割质量的函数

max_features = [‘auto’, ‘sqrt’, ‘log2’]
max_depth = [int(x) for x in np.linspace(10, 110, num = 11)]
max_depth.append(None)
min_samples_split = [2, 5, 10]
min_samples_leaf = [1, 2, 4]
bootstrap = [True, False]
criterion= ['gini', 'entropy']
grid_param = {'max_features': max_features,
               'max_depth': max_depth,
               'min_samples_split': min_samples_split,
               'min_samples_leaf': min_samples_leaf,
               'bootstrap': bootstrap,
               'criterion':criterion }

sci-kit 学习模块有一个简便方法“GridSearchCV ”,可以通过交叉验证找到最佳的超参数值。我用了 5 折的 k 折交叉验证。

from sklearn.model_selection import GridSearchCVgd_sr = GridSearchCV(estimator=RFC, param_grid=grid_param, scoring=’accuracy’, cv=5,n_jobs=-1)gd_sr.fit(X_rus, y_rus)  
print(gd_sr.best_params_)

在确定了最佳超参数之后,我用我的机器可以在相对较短的时间内计算的最大数量的树来拟合模型。

Best_RFC=RandomForestClassifier(n_estimators=8000,max_features=
’auto’, max_depth=20,min_samples_split=5, min_samples_leaf=1, bootstrap=True, criterion=’gini’)# fit best model to training dataset
Best_RFC.fit(X_rus, y_rus)

最后,我从测试集中预测了鱼样本的来源,并计算了模型的准确性:

# predict test Y values
ypred=Best_RFC.predict(test_X)from sklearn import metricsprint(“Accuracy:”,metrics.accuracy_score(test_y, ypred))

该模型以 89%的准确率预测了测试集中每个样本的地理来源。我的预测准确率高于对相似鱼类物种起源进行分类的研究。这项工作受到样本量小的限制;我研究的物种不常被捕获。

分类矩阵让我们深入了解模型预测如何与观察到的类别相关联。

随机森林模型预测来自墨西哥湾的鱼类样本比来自东大西洋和西大西洋的样本更准确。这表明墨西哥湾的骨骼形状比其他地区更独特。

这个练习展示了机器学习概念在渔业科学中的价值,以及它们使用我建议的技术预测鱼类样本来源的能力。鉴于样本量较小,我认为我设计的特征提供了很强的预测能力。

我感谢任何反馈和建设性的批评。与该分析相关的代码可在github.com/njermain上找到

使用随机森林预测 H-1B 状态

原文:https://towardsdatascience.com/predicting-h-1b-status-using-random-forest-dc199a6d254c?source=collection_archive---------20-----------------------

当我在做这个项目的时候,我很惊讶我数据科学课程的很多同学都不知道什么是 H-1B 签证。我的简单解释是——H-1B 签证项目允许外国工人在有限的时间内为美国的公司工作。为了让一些从未办理过签证的技术发烧友更感兴趣,我补充道,塞特亚·纳德拉、桑德尔·皮帅和埃隆·马斯克曾持 H-1B 签证移居美国。我希望这个项目能给美国人带来更多的意识,这只是签证的一种。

项目背后的故事

我经常想,是什么让我在其他数据科学家中成为一名优秀的数据科学家。我意识到我的优势在于我的多元化背景,我可以提出正确的问题,并对不同背景、性别和种族的人深表同情。这是我想带到现场的东西,这样数据可以代表每个人,对每个人都有益。

我做这个项目的主要原因是,三年前我持 H-1B 签证从哈萨克斯坦来到西雅图。这是一个极其不确定、漫长且昂贵的过程,我甚至不打算在这里解释所有的步骤。相反,我决定采取行动帮助其他 H-1B 申请人。我用机器学习(ML)预测了签证被接受的概率。利用创建的 ML 模型,我创建了一个应用程序,申请者可以输入信息并立即获得结果。

这不是你的模型,这是你的数据集

第一个数据集

这个项目最困难的部分是选择数据集。我使用的初始数据集存储在 Kaggle 中。

让我们回顾一下我是如何发现我的第一个数据集不起作用的几个步骤。

我的目标:确定申请是否被拒绝/接受。

我的第一个数据集包括以下特征:

  • 提交的完整日期(包括日/月/年)
  • 工资
  • 雇主信息
  • 行业信息
  • 依赖性(丈夫/妻子)
  • 违规历史

我收集了 2016 年至 2018 年的 H-1B 申请。

EDA

在开始预测建模之前,我做了一些有趣的 EDA,这与技术行业的数据科学家有关。

Graph 1

图 1 :数据科学的工作岗位主要有三种:数据科学家、数据分析师、数据工程师。根据这三个职位的 H-1B 申请人的工资分布,数据工程师的年收入最高。

Graph 2

图表 2:上面的柱状图代表了 2018 年雇佣数据科学家最多的十家公司。这是一个很好的求职数据科学家指南。

特征工程

我的数据集有许多分类变量,因此在将我的数据拟合到模型中之前,我总结了几个步骤:

  • 创建虚拟变量
  • 创建工资类别(非常低、低、中、高、非常高)
  • 至于工业,我划分了科技和非科技工业
  • 分开的年、日和月

分类建模

在探索了逻辑回归、XGBoost 分类和随机森林之后。我的首选是随机森林,原因如下:

  • 我的数据集有很多例子和特征
  • 与其他模型相比,随机森林通常会给出更准确的结果
  • 对于不平衡类学习有一个包-平衡随机森林分类,自动平衡类。
  • 我能够通过调整最大深度和估计器的数量来改进我的模型。

AUC = 0.76 for Random Forest

随机森林模型给我的 AUC 值是 0.76。 AUC 的取值范围从 0 到 1。AUC 值越高,模型越准确。

除了高 AUC 分数,即使在使用平衡随机森林分类器后,我对该数据集的精度和召回率也是不平衡的。

我试图改变接受和拒绝类的阈值,但它似乎不起作用。但这是我拿到的最高 f1 分。

在仔细评估我的模型后,我意识到问题出在我的特征上,我需要更多关于每个申请人的信息。

第二数据集

在对一个数据集做了更多的研究后,我发现在 https://www.foreignlaborcert.doleta.gov/performancedata.cfm上,OFLC(外国劳工认证办公室)提供了 H-1B 申请人的完整披露数据。

我添加了以下功能:

  • 职称
  • 教育专业
  • 教育水平
  • 国籍
  • 多年的经验

我使用与第一个数据集中相同的参数运行了平衡随机森林分类器,结果显著改善:

AUC 评分也有所改善:

汇总

使用第二个数据集,我能够提高我的分数,但是被接受的类的精度和 F1 分数仍然没有被接受的类高。我在 Flask 应用程序中实现了该模型,并将其部署到 Heroku 中。我有一个演示版本,我还在努力使它对人们更有用。你可以通过输入你的信息来尝试一下。

赫尔库 App:https://afternoon-sea-49094.herokuapp.com/

GitHub 库:https://github.com/AisOmar/H1B_predict

预测心脏病死亡率

原文:https://towardsdatascience.com/predicting-heart-disease-mortality-2320fa759f81?source=collection_archive---------29-----------------------

构建可以识别 2019 年高危状态的机器学习模型。

根据疾病控制中心的数据,“美国每年大约有 610,000 人死于心脏病,也就是说每 4 个人中就有一个。”阅读这篇文章的人不可能没有受到这种疾病的影响。今年早些时候,年仅 57 岁的我失去了一位家人。其原因已被很好地记录和理解,但它仍然是美国的主要死亡原因。在这方面,公共政策的改变有可能有助于拯救生命吗?

通过建立一个机器学习模型来预测各州的心脏病死亡率,我们应该能够确定哪些州已经有效地降低了这些死亡率。如果是这样的话,我们就有希望将这些政策原则推广到其他国家。

史料

自 1999 年以来,疾病控制和预防中心每年都会公布心脏死亡率和其他 T2 主要死因的数据。通过跟踪每个州相对于人口的死亡人数,我们可以看到死亡率在过去的 20 年中一直呈下降趋势。

正如我们所见,这一趋势似乎在 2011 年趋于平稳,可能是由于人口老龄化。

心脏病的主要原因

在进行了一些一般性研究后,确定了对心脏病有重大影响的四个主要因素:

通过对每个预测变量与目标进行简单的线性回归,我们可以开始了解哪些因素具有最显著的影响。

从这里,我们可以看到人口统计学似乎对心脏病有最大的影响,75-79 岁年龄段是主要的预测因素。也许更令人惊讶的是,随着男性在人口中所占比例的增加,心脏病死亡率下降了。这是否意味着男性不易患心脏病?不一定,但它似乎表明女性更有可能死于它。疾病预防控制中心认为意识是这方面的首要因素,

尽管在过去几十年中人们的意识有所提高,但只有大约一半(56%)的女性认识到心脏病是她们的头号杀手。

此外,女性往往会经历更广泛的症状,这可能会导致她们意识不到自己患有心脏病。

另一个令人惊讶的结果是,饮酒似乎与心脏病死亡率没有预期的那么大关联。事实上,对数据的初步审查似乎表明,葡萄酒消费量的增加与死亡率的降低相关。然而,这里有一个警告,葡萄酒消费也被发现与吸烟率呈负相关。由于吸烟确实与心脏病有很强的正相关性,因此有理由认为不是酒,而是较低的吸烟率导致了较低的死亡率。

定义变量

自然,对心脏病的预测需要建立在前期数据的基础上,并有充分的准备时间。换句话说,如果我们想预测 2019 年的结果,需要基于 2018 年及之前的数据。为了分析的目的,使用了 3-5 年的时滞。换句话说,如果预测 2016 年,我们使用 2011-2013 年的数据。

风险水平是基于历史心脏病发病率的总和。如果预测的比率在所有观察到的比率的 33%以下,我们将其标记为低风险。反之,如果是在 33%以上,就是高风险。

构建机器学习模型

有了这些信息,我们现在可以开始构建模型了。总的来说,六种模型架构的 1413 个变体应用于使用 5 重交叉验证的数据的 80%子集。将每个模型应用到测试数据中,我们可以测量准确性并比较不同架构的有效性。

这里,我们可以看到支持向量机模型能够实现 91.7%的 f1 得分。下面是一张显示 2019 年预测的地图,其中九个州被确定为高风险。

最引人注目的是该国中部高风险州的聚集,尽管不能确定这是否是巧合。

值得注意的是,该模型预测,从 2016 年到 2019 年,六个州的风险水平将发生变化,如下图所示。

正如我们所看到的,俄克拉荷马州、密歇根州和宾夕法尼亚州不再被预测为高风险州。然而,田纳西州却反其道而行之,从中等风险到高风险。但是有一种状态特别有趣。

俄克拉何马州

2016 年,俄克拉荷马州观察到的心脏病死亡率将其归入高风险类别,但我们的模型预测它在 2019 年是低风险的。当我第一次看到这个的时候,我认为这是一个错误。一个国家如何能在如此短的时间内如此大幅度地降低其风险水平?事实证明,有强有力的证据支持这一预测。

经过更仔细的检查,很明显这一变化的原因是吸烟率从 2010 年的 25.5%下降到 2015 年的 19%左右,创历史新低。

该州卫生与公众服务部部长特里·克莱恩将这一下降归因于该州的禁烟令。结合认证健康俄克拉荷马计划,激励商业地产做同样的事情,结果成为焦点。

推荐

这项研究的发现为想要降低心脏病死亡率的州指出了两个简单的策略。

  • 效仿俄克拉荷马州,在公共场所实施禁烟令。
  • 开展针对女性和退休人员的心脏病宣传活动。

城市中的提高认识运动可能最有效,因为妇女往往占人口的较高比例。虽然还有更多的事情可以做,但我希望这一分析能够为今后的一些初步步骤提供一些思路。

资源

这篇文章改编自我几个月前写的一篇关于这个话题的技术文章。如果你想知道更多关于准则和策略的细节,请参考它。

该分析是使用 Jupyter 笔记本和相关库进行的。包含所有代码和数据的库可以在 GitHub 上找到。

从零开始利用深度学习和 Keras 预测医院再入院

原文:https://towardsdatascience.com/predicting-hospital-readmission-with-deep-learning-from-scratch-and-with-keras-309efc0f75fc?source=collection_archive---------27-----------------------

让我们使用深度学习来识别有再次入院风险的患者!

介绍

最近,我看了我的朋友 Eric Ma 关于深度学习基础的视频。为了教授深度学习,他将其分为 3 个关键部分:模型、损失函数和优化例程。在整个教程中,他使用了一个自动微分工具箱。然而,我发现自己做导数是非常令人满意的(至少对于简单的情况)。今天,我想按照 Eric 的方法从头开始构建一个 2 层神经网络,但使用代数导数(来自吴恩达的 Coursera class ),然后使用 Keras(一个深度学习框架)再次实现它。

数据集

对于这个项目,我们将使用与我上一篇关于预测医院入院的文章相同的数据集,该数据集来自 UCI 的糖尿病医院数据集(https://archive . ics . UCI . edu/ml/datasets/diabetes+130-us+hospitals+for+years+1999-2008)。关于这个项目和功能工程的回顾,请参见我之前的帖子https://towards data science . com/predicting-hospital-re-admission-for-patients-with-diabetes-using-scikit-learn-a 2e 359 b 15 f 0

项目定义

预测糖尿病患者是否会在 30 天内再次入院。

特征工程

我们将开始这篇文章,就好像我们已经完成了我上一篇文章的特性工程部分,其中包括创建数字、分类(一键编码)和顺序特性。这些功能方便地保存在以前的笔记本中,并包含在我的 github repo 中。

对于深度学习来说,重要的是填充缺失值并对数据进行归一化。我们将从 scikit-learn 中使用 SimpleImputer 和 StandardScaler 来做这件事。

从头做起

这里,我们将使用 Eric Ma 介绍的结构从头开始构建一个简单的两层神经网络:

  • 模型
  • 损失函数
  • 优化程序

我会尽量坚持吴恩达在他的 Coursera specialization(https://www.coursera.org/specializations/deep-learning)中介绍的符号。

模型

我们将使用的模型是一个双层神经网络,如下所示:

这里我们将有 n_x 个输入变量、n_1 个隐藏节点和一个包括 m 个样本的输出节点。对于这个模型,我们将对隐藏层节点和输出层中的激活函数使用逻辑回归。

这里,我们的激活函数将具有以下形式

为了更有效地计算,我们将使用矢量化记号。在这种表示法中,X 的第一列将是第一个样本的所有特征(注意,这与 Python 中此时加载的内容相反,因此我们需要转置 X 矩阵)。

我们将用于该模型的参数将具有以下维度

为了简单起见,让我们选择 n1 = 64 个节点来近似地将输入变量的数量减半。按照 Eric 的符号,让我们将所有这些参数保存在一个字典中。我们将随机初始化这些,因为设置为 0 将不起作用,因为节点与节点之间的权重都是相同的。

为了计算我们的m示例中 y_hat 的估计值,我们可以使用下面的等式通过模型向前馈送信息(注意维度在花括号中)。

我们可以用下面的前馈函数来编写代码。这里我们将隐藏层的激活函数作为函数的变量。

损失函数

现在我们有了一个给定一些参数计算 y_hat 的方法,我们需要找到“最佳”参数。为了定义“最佳”,我们需要一个成本函数来定义参数有多好。我们用于二元分类的损失函数是:

很明显这是从哪里来的,对吗?

我更愿意看看这是从哪里来的,所以让我们绕一小段路,推导出这个方程。如果我们把我们模型的输出看作给定 x 的 y 的概率,我们可以为一个例子写如下:

可以更巧妙的写成:

如果我们假设所有样本都是独立的,那么看到所有数据的可能性就是个体概率的乘积:

现在我们要做的就是找到最大化这种可能性的参数。这听起来很复杂,因为有产品术语。幸运的是,最大化似然函数的对数也最大化了似然函数(因为对数是单调增加的)。在我们这样做之前,让我们提醒自己关于日志的属性:

应用于我们的似然函数,我们得到:

这已经接近我们的成本函数了!唯一的区别是我们乘以-1,然后除以 m(样本数)。负乘法将它从最大化问题转换到最小化问题。

对于我们的优化程序,我们将需要这个成本函数J的导数。Eric 用 python 包 jax.grad 完成了这个任务

dlogistic_loss = grad(logistic_loss)

但是我想把它明确地写出来,以便更好地理解数学。

为了做这个导数,我们实际上在我们的神经网络中从右向左工作,这个过程被称为反向传播。我们可以用导数的基本原理做到这一点:链式法则!

在我们深入研究这个之前,让我们先来看一些我们将要用到的函数导数

和线性

由于我们的成本是对每个样本求和的,现在让我们从计算中去掉每个样本的符号(和 1/m 乘数)。这里我们将使用吴恩达的简写符号和定义

现在,我们可以对输出层中的参数求导(并适当考虑矩阵数学):

在网络中向后移动并考虑任何激活功能g^[layer](Z[layer])。这里*代表元素级乘法,因为我们对单个样本使用链规则,所以它开始起作用。

正如你在这里看到的,有一个清晰的模式,允许我们将其扩展到任意数量的隐藏层。

现在让我们编写反向传播函数,它将参数、激活函数的导数函数、前馈值和输出值作为输入。

这里d_logistic是逻辑函数相对于z的导数。

此时,最好验证我们的参数和 d_params 对于每个参数集具有相同的形状。

优化程序

我们将使用梯度下降来更新我们的参数。梯度下降基本上通过将梯度的相反方向移动学习量alpha来迭代更新参数。我们可以在 for 循环中运行这个函数,并记录损失。注意 tqdmn 允许我们用一个进度条来查看进度(有点整洁,谢谢 Eric!).

我们可以验证损耗随着迭代而减少:

然后,我们可以计算训练集和验证集的预测值:

使用 scikit-learn 指标,我们可以绘制 ROC 曲线:

这种临时模型的训练非常缓慢。让我们使用 Keras,它有额外的更有效的优化例程,如 Adam。Keras 也非常适合用几行代码构建更复杂的网络。

克拉斯

首先让我们导入一些包

我们需要稍微调整一下 Keras 的输出标签,使每个标签都有一列:

现在我们可以使用序列来构建我们的模型。在这里,我将使用 ReLu 激活函数,而不是逻辑函数,因为 ReLu 往往工作得更好。我还会添加辍学,这是一种正规化的形式,有助于减少过度拟合。

这里,最终的输出层有两个节点(每个标签一个)。使用 softmax 函数将这些输出标准化,以将分数转换为概率。

然后,我们用指定的损失函数和优化器来编译模型。这里‘分类交叉熵’是上面定义的损失函数的公式。这也可以扩展到包括任意数量的结果

现在我们用这个模型

这里有两个输入参数batch_sizeepochs包含在拟合中。批量大小表示每次迭代中使用的样本数量。在我们的临时实现中,我们在每次迭代中包含了所有的样本,这需要更多的时间来计算。如果您用较小的批处理运行计算,您就能够更快地迭代。Epoch 被定义为在将整个数据集分成更小的批次后迭代整个数据集的次数。

与 scikit-learn 类似,我们使用 predict proba 获得预测,并只获取第二列:

我在隐藏层、丢弃率和附加层中试验了不同数量的节点。最终模型的验证 AUC = 0.66,如以下 ROC 所示:

不幸的是,这与我们在前一篇文章中训练的所有其他模型具有相同的性能!

结论

在这篇文章中,我们用 Keras 从头开始训练了一个 2 层神经网络。如果你有任何问题,请在下面评论。代码在我的 github 回购上:【https://github.com/andrewwlong/diabetes_readmission_deep

人工智能预测肾透析患者的住院情况

原文:https://towardsdatascience.com/predicting-hospitalisation-of-kidney-dialysis-patients-with-ai-88e57007193d?source=collection_archive---------34-----------------------

Photo by Robina Weermeijer on Unsplash

在 AI Singapore (AISG)的 100 个实验项目中,我和我的徒弟被分配到一家区域性肾透析公司工作,开发一个可以预测病人住院情况的人工智能模型。这是我们人工智能学徒计划(AIAP) 的一个关键组成部分,在那里我们着手解决现实世界的人工智能行业问题。我们的模型作为决策支持工具,帮助肾透析公司的医疗团队实现了 36%的更高精确度(即更少的假阳性)。它目前部署在他们的透析中心。

在这篇文章中,我将分享开发我的第一个医疗人工智能模型的关键挑战、过程和见解。

好的……你为什么对预测透析患者的住院率感兴趣?

接受透析的患者具有更高的发病率和住院的高风险。当他们住院时,他们的健康状况通常已经完全恶化,他们的死亡风险也会增加。预测住院风险的能力将允许早期医疗干预。

尽管对住院治疗的关键预测因素进行了研究,但目前的过程是模糊的,并且依赖于医务人员的经验。

The current process of hospitalisation prediction

由于从每个患者透析之前、期间和之后收集了大量数据,因此有可能使用这些数据来训练一个预测患者住院情况的人工智能模型。该模型的预测可用于医疗团队的决策支持。

Envisioned future of hospitalisation prediction, with AI-model as decision-making support

你只需要把那些医疗数据输入到模型中?

不。我们需要把原始的医学数据预处理成对模型有用的东西。

我们站在医学专业人士的角度,问自己:医生如何评估病人的住院风险?从这个思考过程中,我们了解到我们可以教授医学知识,并向模型提供病人的病史。

我们可以通过将医学研究整合到我们的数据中,将医学知识传授给我们的模型。

对于患者的病史,我们必须找到在不丢失过多信息的情况下聚合患者医疗参数的方法。如果病人的医疗参数正在恶化,通常预示着一个严峻的前景。这也是我们希望我们的模型知道的。

酷…你是怎么把医学知识教给模特的?

在给我们的原始数据集中,大多数医疗参数读数只是数字,没有任何数值意义。

大多数医疗参数都有健康范围指南。例如,患有高血压的个体的血压高于 140/90 mmHg。为了给原始血压数据赋予意义,我们将其转换为 0、1、2 或 3 类,分别代表低血压、健康血压、高血压前期血压和高血压。

Features Engineering: Categorising medical information to embed medical domain knowledge

我们通过将其他医学参数转换成已建立的类别来做同样的事情。

以及合并患者医疗信息的历史?

我们根据数据类型采取了不同的方法。

对于连续变量,如患者的血压,我们使用了 12 个周期的指数移动平均(EMA)。这是一个滑动窗口,取 12 个最近的读数并计算平均值,较新的数据权重较高。

Feature Engineering: Using exponential moving average to aggregate time-series data

为什么是 12?在透析治疗中,每位患者每周将接受 3 次治疗。周期 12 意味着取过去 1 个月透析数据的平均值。

对于离散变量,如过去的住院次数,我们创建了一个“累积计数”栏,记录患者住院的次数。患者每住院一次,我们就将累计计数增加 1。

Features Engineering: Calculating the cumulative sum of events to embed patient’s history

这是基于医学文献的发现,过去的住院次数是未来住院的一个强有力的预测因素(一个经常住院的患者意味着他的病情更严重,因此未来住院的可能性更大)。

你的项目有什么有趣的发现吗?

我们尝试使用 NLP(自然语言处理)从医生写的病人出院笔记中提取信息。我们认为向模型提供这些额外的信息会提高它的性能,但是我们错了,模型的性能并没有提高。

我们的假设是,患者出院记录中包含的任何信息都已经存在于患者的医疗参数中。例如,如果医生要在出院记录中注明“高血压”,他必须参考病人的血压读数。

此外,由于需要额外的数据处理,将 NLP 添加到模型中会显著降低模型性能。我们最终决定在最终的人工智能模型中排除病人的出院记录。

怎么知道自己的模型够不够好?

我们没有。每个人都想要一个完美的模型,尤其是医学专业人士。这是理所当然的。他们担心假阴性和假阳性结果。这些错误的结果将对患者的医疗结果产生负面影响。

你是如何说服医疗团队实施你的模型的?

我们很有创意。我们没有设定一个任意的基准,而是与医疗团队一起提出了一个模型验证练习。如果我们的模型可以让医疗团队做出更好的预测,那么部署我们的模型将有助于患者。

在一个月的时间里,医疗小组评估病人并预测哪个病人将住院。我们对我们的模型做了同样的事情。然后,我们将我们的预测与患者的实际住院情况进行了比较。

统计结果后,我们的人工智能模型的精确度提高了 36%。这意味着使用我们的模型作为决策支持工具将有助于肾透析公司的医疗团队做出更少的假阳性预测。

哦不!有什么不好的事情发生吗?

不。相反,在模型验证期间,一些积极的事情发生在病人身上。

与模型验证期之前的平均住院率相比,患者的住院率显著下降。这是即使医疗团队不知道我们的 AI 模型的预测。

在模型验证期之后,平均住院率会回升到原来的平均住院率。因此,该事件不太可能是由于随机性或其他混杂因素造成的。

你如何解释住院率的下降?

这种现象被称为社会促进,当与他人合作时,个人表现会有所改善。

有趣的是,似乎只要知道一个人工智能模型在住院预测方面与他们竞争,医疗团队的整体表现就会提高。

我们怀疑这可能是由于医疗团队在此期间对患者进行了更仔细的观察。一点良性竞争不会有坏处。

有趣…我应该开始告诉我的同事一个 AI 模型正在后台运行(即使没有)以提高他们的性能吗?

我将让你来决定。一个更好的选择是联系 AISG,让我们为贵公司开发一个人工智能模型。😊

本文首发于 AI 新加坡创客空间

在 Expedia 上预测酒店预订

原文:https://towardsdatascience.com/predicting-hotel-bookings-on-expedia-d93b0c7e1411?source=collection_archive---------23-----------------------

Photo: https://designanthologymag.com/story/st-regis-hong-kong

旅行者有许多不同的形状和形式。有了互联网,旅行不再局限于商人或特权阶层。因此,争夺消费者的竞争加剧。

Expedia 是一个在线预订平台,每月有 6 亿用户,在 200 多个国家提供住宿。为了留住客户和商家,Expedia 研究了消费者行为,提出了一项数据战略——个性化。个性化已成为关键策略之一,原因有二:一般用户在预订前会访问网站 4-9 次,并从搜索的第一页开始预订。因此,相似的酒店出现在每次搜索中是至关重要的。

使用 Expedia 在 Kaggle 上的数据集,我将尝试建立一个机器学习模型来预测客户将预订的酒店群。该数据集包含客户行为的日志,包括他们搜索了什么,是否进行了任何预订,以及搜索是否是旅行套餐。数据集中的所有要素都出于隐私目的进行了编码。

Expedia 使用内部算法来形成酒店集群,类似的酒店根据价格、星级、到市中心的距离等因素进行分组。对于这个项目,我使用了给定数据集的 1%,并且只使用了前十个聚类,这仍然留给我 290,000 行数据。

根据给定的特性,我产生了一些见解,试图构建一个具有更高准确性的模型。一些对模型很重要的特性是酒店相关和用户相关的混合。

使用三种机器学习模型,即 Keras 神经网络、决策树和 boosted 树来寻找最合适的一种。令人惊讶的是,神经网络的表现不如其他模型。

不同的模型使用了不同的评估方法。

  1. Keras 神经网络

在对模型进行多轮训练后,精确度和损失没有改善。准确度保持在 30%左右,这不是最理想的情况。

2。决策树

尽管决策树比神经网络具有更高的准确性,但它并不是理想的模型。不同酒店聚类的精度结果有显著差异,范围从 40–90%不等。

3。助推树

与其他模型相比,提升树具有最高的准确性。k 倍评估也显示没有变化,因此这是最好的模型。

结论

如果给我更多的时间,我想利用所有的数据来建立一个更高精度的模型。接下来,对于预订平台来说,为每个客户提供高度可定制性将非常有用。

我在 Metis 的沉浸式课程即将结束。这只是我数据科学之旅的开始。我将继续从事数据科学项目,并希望很快找到工作。

这个项目的代码可以在我的 Github 上找到。如果你愿意联系,可以通过 LinkedIn 联系我。

与 ARIMA 预测每周酒店取消

原文:https://towardsdatascience.com/predicting-hotel-cancellations-with-extratreesclassifier-and-logistic-regression-fb9229a95d1e?source=collection_archive---------20-----------------------

使用 ARIMA 进行时间序列预测,此模型用于预测每周酒店取消。

酒店取消预订会给业内许多企业带来问题。不仅会因为客户取消而损失收入,而且还会给协调预订和调整收入管理实践带来困难。

数据分析可以帮助解决这个问题,因为它可以识别最有可能取消预订的客户,从而让连锁酒店相应地调整营销策略。

以下示例基于 Antonio、Almeida 和 Nunes (2019)的酒店预订需求数据集

ARIMA 模型用于确定酒店取消预订是否也可以提前预测。这将首先使用阿尔加维酒店数据集(H1full.csv)来完成。由于我们现在寻求预测时间序列趋势,所有观测值现在都包含在该数据集中(取消和未取消,不管数据集整体是否不均匀)。

为此,每周对取消情况进行分析(即汇总给定一周的取消数量)。

首先,使用 pandas 执行数据操作程序,以汇总每周取消的数量并正确排序。

在配置 ARIMA 模型时,前 80 个观察值用作训练数据,随后 20 个用作验证数据

一旦模型配置完毕,最后 15 次观察结果将被用作测试数据来衡量模型对未知数据的准确性。

以下是输出的一个片段:

将时间序列可视化,并生成自相关和偏自相关图:

时间序列

自相关

偏自相关

#Dickey-Fuller Test
result = ts.adfuller(train)
result
print('ADF Statistic: %f' % result[0])
print('p-value: %f' % result[1])
print('Critical Values:')
for key, value in result[4].items():
    print('\t%s: %.3f' % (key, value))

当运行 Dickey-Fuller 检验时,会生成小于 0.05 的 p 值,表明非平稳性的零假设被拒绝(即数据是平稳的)。

ADF Statistic: -2.677149
p-value: 0.078077
Critical Values:
	1%: -3.519
	5%: -2.900
	10%: -2.587

然后使用金字塔库中的 auto_arima 运行 ARIMA 模型。这用于为 ARIMA 模型选择最佳(p,d,q)坐标。

from pyramid.arima import auto_arima
Arima_model=auto_arima(train, start_p=0, start_q=0, max_p=10, max_q=10, start_P=0, start_Q=0, max_P=10, max_Q=10, m=52, seasonal=True, trace=True, d=1, D=1, error_action='warn', suppress_warnings=True, random_state = 20, n_fits=30)

将生成以下输出:

Fit ARIMA: order=(0, 1, 0) seasonal_order=(0, 1, 0, 52); AIC=305.146, BIC=307.662, Fit time=0.139 seconds
Fit ARIMA: order=(1, 1, 0) seasonal_order=(1, 1, 0, 52); AIC=nan, BIC=nan, Fit time=nan seconds
Fit ARIMA: order=(0, 1, 1) seasonal_order=(0, 1, 1, 52); AIC=nan, BIC=nan, Fit time=nan seconds
Fit ARIMA: order=(0, 1, 0) seasonal_order=(1, 1, 0, 52); AIC=nan, BIC=nan, Fit time=nan seconds
Fit ARIMA: order=(0, 1, 0) seasonal_order=(0, 1, 1, 52); AIC=nan, BIC=nan, Fit time=nan seconds
Fit ARIMA: order=(0, 1, 0) seasonal_order=(1, 1, 1, 52); AIC=nan, BIC=nan, Fit time=nan seconds
Fit ARIMA: order=(1, 1, 0) seasonal_order=(0, 1, 0, 52); AIC=292.219, BIC=295.993, Fit time=0.590 seconds
Fit ARIMA: order=(1, 1, 1) seasonal_order=(0, 1, 0, 52); AIC=293.486, BIC=298.518, Fit time=0.587 seconds
Fit ARIMA: order=(2, 1, 1) seasonal_order=(0, 1, 0, 52); AIC=294.780, BIC=301.070, Fit time=1.319 seconds
Fit ARIMA: order=(1, 1, 0) seasonal_order=(0, 1, 1, 52); AIC=nan, BIC=nan, Fit time=nan seconds
Fit ARIMA: order=(1, 1, 0) seasonal_order=(1, 1, 1, 52); AIC=nan, BIC=nan, Fit time=nan seconds
Fit ARIMA: order=(2, 1, 0) seasonal_order=(0, 1, 0, 52); AIC=293.144, BIC=298.176, Fit time=0.896 seconds
Total fit time: 3.549 seconds

基于最低的 AIC, SARIMAX(1,1,0)x(0,1,0,52) 配置被确定为时间序列建模的最佳配置。

下面是模型的输出:

序列的 90% 作为训练数据建立 ARIMA 模型,剩余的 10% 现在用于测试模型的预测。以下是预测与实际数据的对比:

我们可以看到,虽然预测值低于实际测试值,但两个序列的方向似乎是相互跟随的。

从商业角度来看,酒店可能更感兴趣的是预测取消的程度在特定的一周内会增加/减少,而不是准确的取消数量,这无疑会更容易出错并受外部因素的影响。

在这方面,平均方向准确度用于确定模型准确预测每周取消频率方向变化的程度。

def mda(actual: np.ndarray, predicted: np.ndarray):
    """ Mean Directional Accuracy """
    return np.mean((np.sign(actual[1:] - actual[:-1]) == np.sign(predicted[1:] - predicted[:-1])).astype(int))

产生了 89%的 MDA:

>>> mda(val, predictions)0.8947368421052632

在这方面,ARIMA 模型在测试集上预测酒店取消的方向变化时显示了相当高的精确度。

还预测了 RMSE(均方根误差):

>>> import math
>>> from sklearn.metrics import mean_squared_error>>> mse = mean_squared_error(val, predictions)
>>> rmse = math.sqrt(mse)
>>> print('RMSE: %f' % rmse)RMSE: 77.047252

在这种情况下,RMSE 为 77。请注意,RMSE 的单位与响应变量相同,在本例中是酒店取消。验证数据中所有周的平均取消值为 94,RMSE 77 在技术上是未解释方差的标准偏差。在其他条件相同的情况下,该值越低越好。

针对看不见的数据进行测试

尽管 ARIMA 模型已经被训练过,并且通过验证数据验证了准确性,但是仍然不清楚该模型将如何针对看不见的数据(或测试数据)执行。

在这点上,使用 ARIMA 模型来生成 n=15 的预测,使用 test.index 来指定看不见的数据。

>>> test = np.array([[130,202,117,152,131,161,131,139,150,157,173,140,182,143,100]])

首先,对数组进行相应的整形:

>>> test=test.reshape(-1)
>>> testarray([130, 202, 117, 152, 131, 161, 131, 139, 150, 157, 173, 140, 182,
       143, 100])

现在,进行预测,并计算 RMSE(均方根误差)、MDA(平均方向精度)和平均预测误差:

>>> predictionnew=pd.DataFrame(Arima_model.predict(n_periods=15), index=test.index)
>>> predictionnew.columns = ['Unseen_Predicted_Cancellations']
>>> predictionsnew=predictionnew['Unseen_Predicted_Cancellations']>>> mse_new = mean_squared_error(test, predictionsnew)
>>> rmse_new = math.sqrt(mse_new)
>>> print('RMSE: %f' % rmse_new)RMSE: 57.955865>>> mda(test, predictionsnew)0.8666666666666667>>> forecast_error_new = (predictionsnew-test)
>>> forecast_error_new0     -39.903941
1    -128.986739
2     -47.325146
3     -76.683169
4     -14.237713
5      77.591519
6     -34.782635
7      59.277972
8       4.404317
9     -40.860982
10    -38.522419
11     49.074094
12    -44.497360
13     11.040560
14     73.507259
dtype: float64>>> mean_forecast_error_new = np.mean(forecast_error_new)
>>> mean_forecast_error_new-12.726958780163237

RMSE 略有改善(降至 57),而 MDA 降至 86%,平均预测误差为-12,这意味着该模型有轻微低估取消的趋势,因此预测偏差为负。

下面是预测取消与实际取消的对比图:

基于 H2 数据的 ARIMA 模型

应用了相同的过程——这次使用第二个数据集。

以下是使用 pyramid-arima 获得的 ARIMA 配置:

预测与验证

预测与实际

  • RMSE 上测试数据: 274
  • 平均定向精度: 0.8666
  • 平均预测误差: 156.329

结论

在本例中,ARIMA 模型用于预测每周的酒店取消率。在 H1 数据集上 RMSE 为 57 的测试集上,MDA 展示了 86%的准确性,并且在 RMSE 为 274 的 H2 数据集上再次产生了 86%的 MDA(测试集中 15 周的平均取消为 327)。

当然,这些发现的局限性在于,研究中的两家酒店都位于葡萄牙。在其他国家的酒店中测试该模型将有助于进一步验证该模型的准确性。

本例的数据集和笔记本可从 MGCodesandStats GitHub 库获得,以及对该主题的进一步研究。

你也可以在 michael-grogan.com 的找到更多我的数据科学内容。

免责声明:本文是在“原样”的基础上编写的,没有担保。本文旨在提供数据科学概念的概述,不应以任何方式解释为专业建议。

用机器学习预测酒店预订取消

原文:https://towardsdatascience.com/predicting-hotel-cancellations-with-machine-learning-fa669f93e794?source=collection_archive---------8-----------------------

Image by Alexas_Fotos from Pixabay

可想而知,在线预订行业的预订取消率相当高。一旦预订被取消,几乎没有什么可做的。这让许多机构感到不安,并产生了采取预防措施的愿望。因此,预测可以取消的预订并防止这些取消将为机构创造剩余价值。

在这篇文章中,我将尝试解释如何通过机器学习方法提前预测未来取消的预订。先说预处理!

预处理

首先,我应该说,您可以访问我的存储库中使用的数据,我将在我的文章结尾分享这些数据。我还想分享一下,这是一篇论文的主题。[1]

我们有两个独立的数据集,因为我们要对它们进行预处理,所以将它们组合起来是有意义的。但是在建模阶段,我们需要分别获得这两组数据。所以,为了区分这两者,我创建了id字段。

import pandas as pdh1 = pd.read_csv('data/H1.csv')
h2 = pd.read_csv('data/H2.csv')h1.loc[:, 'id'] = range(1, len(h1) + 1)

start = h1['id'].max() + 1
stop = start + len(h2)
h2.loc[:, 'id'] = range(start, stop)df = pd.concat([h1, h2], ignore_index=True, sort=False)

以下是该项目的预处理步骤:

  • 将字符串NULLUndefined 值转换为 np.nan
  • 从具有少量NULL值的列中删除缺失的观察值
  • 根据规则填充缺失值
  • 删除不正确的值
  • 离群点检测

步骤 1 —将 NULL 或未定义的值串到 **np.nan**

import numpy as npfor col in df.columns:
    if df[col].dtype == 'object' and col != 'country':
        df.loc[df[col].str.contains('NULL'), col] = np.nan
        df.loc[df[col].str.contains('Undefined', na=False), col] = np.nannull_series = df.isnull().sum()
print(null_series[null_series > 0])

使用上面的代码,我们将字符串NULLUndefined值转换为np.nan值。然后,我们打印每一列的NULL值的计数。这是结果的样子,

Null values

步骤 2-删除一些缺失的值

我们可以删除国家市场 _ 细分分销 _ 渠道中的NULL值,因为这些字段的NULL值很少。

subset = [
    'country',      
    'children',      
    'market_segment',      
    'distribution_channel'
] 
df = df.dropna(subset=subset)

步骤 3-通过规则集填充缺失值

为数据指定了许多规则。[2]例如,值为Undefined/SC意味着它们为no meal type.,因为我们之前已经用NULL替换了Undefined值,我们可以用SC填充字段中的NULL值。

aagent字段为NULL的事实意味着预订不是来自任何代理。因此,这些预订可以被认为是由顾客直接购买的,不需要任何中介组织,如代理等。这就是为什么我们没有删除NULL值,而是抛出一个像 999 这样的随机值。这同样适用于 ccompany字段。

更详细的信息可以在参考资料的第二个链接中找到。

df.loc[df.agent.isnull(), 'agent'] = 999 
df.loc[df.company.isnull(), 'company'] = 999 df.loc[df.meal.isnull(), 'meal'] = 'SC'

步骤 4-删除错误的值

ADR 字段指的是预订的每晚平均价格。因此,它取小于零的值是不正常的。你可以使用df.describe().T来查看这种情况。对于 ADR 字段,我们删除小于零的值。

df = df[df.adr > 0]

步骤 5—异常值检测

对于integerfloat字段,我们使用下面的代码来确定低点和高点。如果在低点和高点之间有等式,我们不做任何滤波。如果不相等,我们从数据集中移除大于上点的观测值和小于下点的观测值。

Outlier detection with IQR

田地的低点和高点似乎在下面,

IQR results

最后,我们将讨论多元异常值检测。[3]这是我们多做一点工作的特殊推论,并不适用于每一个企业。一晚 5 美元或 10 美元的费用可以正常支付,但 10 晚的费用就不正常了。因此,从数据集中删除这些被认为是相反的值将有助于我们的模型进行学习。所以我已经尝试了LocalOutlierFactorEllipticEnvelope,我只检查EllipticEnvelope是因为它产生了更好的结果,但是如果你想检查这两个,你可以看看我的资源库。

from sklearn.covariance import EllipticEnvelope
import matplotlib.pyplot as plt
import numpy as np# create new features: total price and total nights
cleaned.loc[:, 'total_nights'] = \
cleaned['stays_in_week_nights'] + cleaned['stays_in_weekend_nights']
cleaned.loc[:, 'price'] = cleaned['adr'] * cleaned['total_nights']# create numpy array
X = np.array(cleaned[['total_nights', 'price']])*# create model* 
ee = EllipticEnvelope(contamination=.01, random_state=0)*# predictions* 
y_pred_ee = ee.fit_predict(X)*# predictions (-1: outlier, 1: normal)*
anomalies = X[y_pred_ee == -1]*# plot data and outliers*
plt.figure(figsize=(15, 8))
plt.scatter(X[:, 0], X[:, 1], c='white', s=20, edgecolor='k')
plt.scatter(anomalies[:, 0], anomalies[:, 1], c='red');

图表如下。红点显示异常值。

EllipticEnvelope result

如您所见,在数据集之外保留小值是有意义的,尤其是在 6 个晚上之后。通过应用这个过程,我们可以保存数据集。

df_cleaned = cleaned[y_pred_ee != -1].copy()h1_cleaned = df_cleaned[df_cleaned.id.isin(h1.id.tolist())]
h2_cleaned = df_cleaned[df_cleaned.id.isin(h2.id.tolist())]h1_cleaned = h1_cleaned.drop('id', axis=1)
h2_cleaned = h2_cleaned.drop('id', axis=1)h1_cleaned.to_csv('data/H1_cleaned.csv', index=False)
h2_cleaned.to_csv('data/H2_cleaned.csv', index=False)

特征工程

在建立模型之前,另一个重要的问题是特征工程。添加或删除功能可能对我们的模型更有效。

步骤 1—相关性

首先,我将使用LabelEncoder将分类数据转换为integer,然后我将查看相关性。[4]下面的代码可以做到这一点,

from sklearn.preprocessing import LabelEncoder
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as snstrain = pd.read_csv('./data/H1_cleaned.csv')
test = pd.read_csv('./data/H2_cleaned.csv')df_le = train.copy()
le = LabelEncoder()

categoricals = [
    'arrival_date_month',
    'meal',
    'country',
    'market_segment',
    'distribution_channel',
    'reserved_room_type',
    'assigned_room_type',
    'deposit_type',
    'agent',
    'company',
    'customer_type',
    'reservation_status',
]

for col in categoricals:
    df_le[col] = le.fit_transform(df_le[col])plt.figure(figsize=(20, 15))
sns.heatmap(df_le.corr(), annot=True, fmt='.2f');

这段代码给了我们一个如下所示的相关矩阵,

Correlation matrix

在该矩阵中,在 reservation_statusis _ cancelled特征之间似乎存在负的高相关性。在 total_nightsstays_in_week_nightsstays _ in _ weekend _ nights字段之间也有很高的相关性。因此,我们从数据集中删除了 reservation_statustotal_nights 特性。由于reservation _ status _ datereservation_status 之间存在关联,我们将删除此功能。

columns = [
    'reservation_status_date',
    'total_nights',
    'reservation_status',
]

train = train.drop(columns, axis=1)
test = test.drop(columns, axis=1)
df_le = df_le.drop(columns, axis=1)

步骤 2—虚拟变量与标签编码器

机器学习模型需要数字数据来运行。因此,在我们可以建模之前,我们需要将分类变量转换为数字变量。我们可以使用两种方法来做到这一点:Dummy variablesLabelEncoder。通过下面你看到的代码,我们使用LabelEncoderdummy variables创建特征。

import pandas as pdnew_categoricals = [col for col in categoricals if col in train.columns]df_hot = pd.get_dummies(data=train, columns=new_categoricals)
test_hot = pd.get_dummies(data=test, columns=new_categoricals)X_hot = df_hot.drop('is_canceled', axis=1)
X_le = df_le.drop('is_canceled', axis=1)
y = train['is_canceled']

然后,我们用dummy variables构建一个logistic regression模型,并检查分类报告,作为对数据的初步观察。

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report
from sklearn.model_selection import train_test_splitX_train, X_test, y_train, y_test = train_test_split(X_hot, y, test_size=.2, random_state=42)

log = LogisticRegression().fit(X_train, y_train)
y_pred = log.predict(X_test)print(accuracy_score(y_test, y_pred))
print(classification_report(y_test, y_pred))

准确性分数看起来是 0.8584,但是当查看分类报告时,已经取消的预订的准确性非常低。因为我们的数据包含 23720 个成功案例和 8697 个取消案例。在这种情况下,优选的是稀释加权类或增加较少采样类的样本数量。我们将首先使用特征选择算法选择特征,然后使用稀释的数据比较虚拟变量和标签编码器。

First classification report

步骤 3—特征选择

特征选择是特征工程中最重要的问题之一。这里我们将使用SelectKBest,这是一种用于分类问题的流行特征选择算法。我们的评分函数将是 chi 。[5]

Feature selection

利用上述函数,我们为LabelEncoderdummy variables选择最佳特征。

selects_hot = select(X_hot)
selects_le = select(X_le)

然后我们用一种简单的方式来比较这些特征。

Dummy variables vs label encoder

比较结果如下:

Dummy variables vs label encoder classification reports

我们选择这些字段是因为我们用虚拟变量创建的特征能给出更好的结果。

from sklearn.model_selection import train_test_split
from sklearn.utils import resample
import pandas as pdlast = test_hot[selects_hot + ['is_canceled']]

X_last = last.drop('is_canceled', axis=1)
y_last = last['is_canceled']*# separate majority and minority classes*
major = selected[selected['is_canceled'] == 0]
minor = selected[selected['is_canceled'] == 1]

*# downsample majority class*
downsampled = resample(major, replace=False, n_samples=len(minor), random_state=123) 

*# combine minority class with downsampled majority class*
df_new = pd.concat([downsampled, minor])

*# display new class counts*
print(df_new['is_canceled'].value_counts())X = df_new.drop('is_canceled', axis=1)
y = df_new['is_canceled']X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.2, random_state=42)

使用上面的代码,我们将成功预订的数量和取消预订的数量平均化了 8697,并将数据集分为 train 和 test。然后,我们将通过创建以下类来度量模型的性能。

Report class

我们到最后一步,对比一下我们的车型!

系统模型化

这里尝试了很多模型,你可以在我的知识库里看到。但在这里,我将分享前 2 个模型的结果和一些代码,展示我们如何做hyperparameter tuning。事情是这样的,

from sklearn.model_selection import GridSearchCV
from xgboost import XGBClassifierreport = Report(X_test, y_test)
xgb = XGBClassifier().fit(X_train, y_train)xgb_params = {
    'n_estimators': [100, 500, 1000],     
    'max_depth': [3, 5, 10],     
    'min_samples_split': [2, 5, 10]
}params = {
    'estimator': xgb,
    'param_grid': xgb_params,
    'cv': 5,
    'refit': False,
    'n_jobs': -1,
    'verbose': 2,
    'scoring': 'recall',
}xgb_cv = GridSearchCV(**params)
_ = xgb_cv.fit(X_train, y_train)print(xgb_cv.best_params_)
xgb = XGBClassifier(**xgb_cv.best_params_).fit(X_train, y_train)report.metrics(xgb)
report.plot_roc_curve(xgb, save=True)

XGBoost 结果如下:

XGB results

如果我们使用上面的代码将 XGBoost 替换为 GBM,结果如下:

GBM results

结论

首先,我想在本文中强调预处理和特征选择步骤在模型构建过程中的重要性。创建成功模型的方法是获得干净的数据。

之后建立的模型的优化,尤其是分类问题不应该忽视召回值的重要性。分类的准确性是分类问题中最关键的问题之一。

希望这是一篇有用的文章!

感谢您的阅读!如果你想了解更多,想看看 H2 文件的结果,请访问我的知识库!

[## egemenzeytinci/取消-预测

这个项目的目的是预测将被取消的预订。它包括两个步骤:预处理和…

github.com](https://github.com/egemenzeytinci/cancellation-prediction)

参考

[1]努诺·安东尼奥、安娜·德·阿尔梅达和路易斯·努涅斯,预测酒店预订取消,以减少不确定性并增加收入 (2017)

[2]努诺·安东尼奥,安娜·德·阿尔梅达和路易斯·努内斯,酒店预订需求数据集 (2019)

[3]克里斯托弗·何塞,Python 中的异常检测技术 (2019)

[4] Vishal R,特征选择—相关性和 P 值 (2018)

[5] 使用选择测试进行特征选择(2018)

使用机器学习来预测每个站点每小时的共享单车检出量

原文:https://towardsdatascience.com/predicting-hourly-divvy-bike-sharing-checkouts-per-station-65b1d217d8a4?source=collection_archive---------20-----------------------

问题是

这个项目的目标是试图预测在一个给定的小时内有多少辆自行车将被检查出一个特定的车站。如果可以提前知道借出的自行车数量,那么 Divvy 将知道何时何地重新装载自行车,以便客户可以继续使用该站。这对 Divvy 很重要,因为它将为他们的客户创造一种能力和便利的文化。能够做出准确的预测对客户来说是未知的,但如果做得不好,可能会导致问题。如果忽视这一点,就会对业务造成不利影响。一个例子是,如果客户因为早上去车站查看自行车而没有自行车,导致他们经常感到不方便,他们就会停止使用这项服务。这将导致他们不得不步行到另一个车站,并希望这些车站有自行车。这可能是一个巨大的不便,而且由于步行到另一个车站的时间损失,客户可能会想为什么不乘坐 El(芝加哥的地铁系统),它每天在同一时间总是有相同的车站。

数据

Divvy 在其网站(https://www.divvybikes.com/system-data)上公开了他们的数据,供任何人使用。季度数据以 csv 格式提供,可追溯至 2013 年。Divvy bike 的每次旅行都记录了以下信息:

  • 行程开始日期和时间
  • 行程结束日期和时间
  • 行程起始站(id 和名称)
  • 行程终点站(id 和名称)
  • 骑手类型(会员、单人骑行和探索通行证)
  • 如果成员旅行,它还将包括成员的性别和出生年份

除了几个例外,数据非常清晰。每年至少有一个季度的列标题略有不同,例如“开始时间”与“开始时间”这是一个简单的列重命名修复。另一种情况是,在 2015 年,第二季度的数据被拆分为 4 月、5 月和 6 月的数据。这是一个小小的不便,但是各个文件只需要连接成一个文件。最后,年份的日期格式各不相同,但熊猫的。to_datetime()函数能够读取多种格式并将它们转换成相同的格式。

任何去过中西部,尤其是芝加哥的人都知道,天气一天之内变化很大。天气对某人是否想在芝加哥市中心骑自行车有很大的影响。随着 Weather Underground 在 2019 年 2 月停止提供免费 API,历史天气以批量格式从黑暗天空(【https://darksky.net/dev】)下载。有各种各样的功能可供选择,但本项目使用了以下功能:

-温度(开尔文,后转换为摄氏度)

-降雨量(每小时总量)

-雪(每小时总量)

-风速

探索性数据分析

该项目从一些探索性数据分析开始,分析仅在最近完整的数据年份 2018 年完成。全年完成了 3603082 次旅行,图 1 显示了这些旅行的每日分布情况。考虑到芝加哥的天气模式,夏季的出行次数远远高于冬季也就不足为奇了。在图 2 中,旅行次数是按一天中的某个小时来计算的。显而易见,通过 Divvy 上班的人有早上和下午的高峰。有趣的是,下午的通勤比早上的通勤多了不少。这里也有多种影响没有生效,首先,这是所有的短途旅行,不仅仅是订户,顾客可能不会在早上 8 点起床观光。此外,在下午 5 点左右,更多的人可能会去买食物或与他人见面共度欢乐时光,这在早上不会做得太多。

Figure 1: Number of trips by day (2018)

Figure 2: Number of trips broken down by hour of the day (2018)

有两种类型的乘客,订户和顾客。订阅者每年支付 99 美元,可以在 45 分钟内无限制乘坐;这些人经常通勤上班。顾客可以付费,他们可以花 15 美元买一张日卡,当天可以无限次乘车,或者花 3 美元,一次最多 30 分钟。对于游客来说,这些都是廉价游览这座城市的好选择。

Figure 3: Breakdown of rides by user type

图 3 显示,用户的出行次数远远多于客户。然而,由于旅行是匿名的,确切的订阅人数不得而知,只知道他们旅行的次数。图 4 显示了 2018 年每周每天的总乘车次数。订户和顾客对旅行的使用是反向相关的。订户在工作日(周一至周五)进行大部分旅行,而这是客户的低谷。如果订户使用 Divvy 上下班,这是有意义的。此外,客户最常在周末(周六和周日)乘车,而这对于订户来说是最不常见的。顾客只能在一天内得到他们的自行车,而周末是旅行者来参观的时候。最后,当比较用户和顾客时,他们旅行持续的时间有很大的差异。用户的平均行程只有 14.5 分钟,而客户的平均行程为 62.6 分钟。要查看工作日和周末的数据是否有偏差,请参考图 5,查看旅行持续时间与星期几的比较。

Figure 4: Number of trips by day of week

Figure 5: Duration of trips by day of week

还有更多关于 Divvy 用户的信息需要了解,当他们注册时,他们输入自己的出生年份和性别。在 2018 年的所有旅行中,男性占了近 75%,而女性只有 25%。回想一下,这并不一定意味着 75%的用户是男性,只是他们占了 75%的旅行。每一次旅行都是匿名的,没有办法识别个人用户。例如,2018 年有 261 个工作日,如果有人完美出勤并双向通勤,他们将自己计算 522 次旅行,但没有办法肯定地知道一个人,没有订户 id 列。尽管男性的出行次数是女性的 3 倍,但女性的出行次数往往略长。女性骑手的平均行程为 19 分钟(中位数为 11 分钟),而男性骑手的平均行程为 14.8 分钟(中位数为 9 分钟)。图 6 显示了使用自行车共享系统最多的用户的年龄。这个数据有一些问题,由于一些年龄超过 123 岁,所以不得不被剔除,这个数字在 80 岁被剔除,即使对用户来说似乎有点老,但这个直方图显示了大多数用户的年龄。从 24 岁到 34 岁,旅行次数至少为 11.7 万次,其中 29 岁的人在 2018 年的旅行次数接近 18 万次。

Figure 6: Distribution of rides by subscriber age

截至 2018 年,共有 621 个独特的 Divvy 站分布在芝加哥和北部郊区埃文斯顿。

Figure 7: Most used Divvy stations (sum of check ins and check outs)

图 7 显示了总使用量最受欢迎的车站,这些都是 2018 年总共超过 60,000 次入住和退房的车站。地图 1 显示了这些站点的位置以及 Divvy 自行车网络的覆盖范围。

Map 1: Distribution of Divvy Stations across Chicago (2018)

从地图上可以看出,车站非常集中在市中心,这里全天有大量的商业活动。地图 2 显示了每个车站的使用情况,它非常清楚地显示了城市中大多数乘坐的地方。市中心不仅是城市的商业中心,也是许多主要景点吸引游客的地方。这些景点包括海军码头、千禧公园、博物馆大道、艺术学院以及密歇根大道和州立大街,那里有很多零售市场

Map 2: Most used Divvy Stations (2018)

是。由于商业和景点的结合,订户和客户都可能经常光顾市中心的这些车站,这使得它们对于 Divvy 来说更加重要,以确保这些车站始终可用于取放自行车。第三个也是最后一个地图,即地图 3,用于发现哪些站点有更多的自行车托运到每个站点或从每个站点托运出去。这是决定哪些站点自行车过剩或短缺的一个重要因素。这将决定自行车可以从哪里搬迁到哪里

Map 3: Difference between check-ins and check-outs (2018)

需要去确保每个车站配备足够的自行车和开放的码头,以容纳他们的用户。

为了进一步说明这一点,图 8 进一步详细描述了登记人数最多的前 15 个车站和结账人数多于登记人数的后 15 个车站。前 15 个站是在该站有多余自行车的站,而底部的站是应该被监控以重新装载的站。

Figure 8: Stations based on the difference between check-ins and check-outs

模型加工

这个项目的目标是能够预测在任何给定的时间内有多少辆自行车将被检查出一个车站。由于计算能力和空间有限,用于创建该模型的数据是过去四年(2015 年至 2018 年)第二季度的数据。总共有 600 多个车站和 300 多万次旅行,只有前 26 个最常去的车站用于该模型。这仅占车站的 4.2%,但占出行的 39.5%。用于该模型的每个车站至少有 50,000 次检入和检出。

由于数据是单独的行程,数据必须按始发站和一天中的时间进行汇总。在创建了这个数据帧之后,必须根据日期和时间连接天气。用于此的天气是温度(以摄氏度计)、风速和降水(将雨和雪合并成一列)。尽管始发站标有 id 号,并在数据帧中标记为 int,但这是一个分类特征,因此必须将其转换为虚拟变量。年、月、日和小时也被转换成虚拟变量。这个模型使用的最终数据是 66 列 106,773 个实例。

在拆分数据和为模型定型之前,对要预测的列“checkout_count”进行了调查。以熊猫为原型。skew()函数,数据是右偏的,偏度为 3.61。对该列的第一个转换是平方根,这将偏斜降低到 1.62,但是对数转换将它进一步降低到 0.33。因此,预测的属性随后使用对数变换进行转换。

最后,数据被分成训练集和测试集。测试集不是随机选择日期进行预测,而是 2018 年 6 月的最后两周,即使用数据的最后两周。训练集是 100,831 行乘 65 列,而测试集是 5,942 个实例。

预测模型

有四种机器学习技术被用来预测一天中任何时间从车站借出的自行车数量。预测列是一个连续变量,因此我尝试的第一个模型是线性和套索回归。所有结果的结果如下:

Table 1: Results from multiple machine learning techniques to predict bike check-outs per hour by station.

很明显,回归技术的表现不是很好,因为它们具有高的均方误差和非常低的 R2 分数。由于绝大多数数据都是二进制格式,决策树和随机森林的表现要好得多。经过反复试验,决策树的最大深度是 31,而随机森林只使用了 15 个估值器。通过增加估计量,随机森林的结果稍微好一点,但如果使用太多估计量,就会有过度拟合和重复结果的风险。随机森林在使用的所有指标中表现最佳。包括解释模型方差的 R2 分数。随机森林解释了 72.98%的差异,这意味着我们拥有 72.98%的信息,我们需要这些信息来准确预测在任何给定的时间从任何车站检出的自行车数量。

Figure 9: Scatterplot comparing actual vs predicted values of the Random Forest

另一个有趣的结果是,我们的平均绝对误差大于我们的中值误差,这意味着要么是数据中有一些异常值,要么是所做的预测。根据数据训练的所有五个模型都是这种情况。当进一步研究预测时,预测最不准确的前六个实例都来自同一天,2018 年 6 月 23 日和 2018 年 6 月 24 日。当进一步研究这些日期时,这是芝加哥的一个大周末。那个周末的几个大型活动是芝加哥骄傲游行、乡村湖滨聚会(一场为期 3 天的乡村音乐会)、芝加哥食品卡车节和老圣帕特最大街区聚会。这些活动每年都会吸引成千上万的游客,在芝加哥旅游已经够困难的了,更不用说有更多的人试图做同样的事情了。这是 Divvy 的自行车共享计划非常方便的一个主要例子。

为了检查异常值如何影响结果,将它们从数据中移除,然后再次运行模型。异常值是通过查找 z 得分并移除任何大于 3 或小于-3 的 z 得分来确定的。z 得分代表数据点偏离平均值的标准偏差数。在训练集中删除了 2,242 个实例,在测试集中删除了 198 个实例。剔除异常值后,某些区域的结果会更好,而其他区域会更差。表 2 显示了运行相同技术的结果,但是没有异常值。均方差显著提高,尤其是回归技术,但 R2 分数仍然很低,这意味着它没有达到准确预测所需的百分比。然而,当查看决策树和随机森林时,所有的值都下降了,不幸的是包括 R2,这意味着它现在知道的预测值更少了。然而,除去异常值,随机森林的中位数绝对误差低于 2,这意味着 50%的预测彼此在 1.9953 自行车之内。

Table 2: Results of same machine learning techniques with the outliers removed

又做了一次尝试来改善我们模型的结果。在六月的最后两周,不是测试集,而是整个数据集的随机分割。使用所有相同的数据,随机选择 25%作为测试集。这导致 80,079 个训练集和 26,649 个测试集。创建了另一个数据集,再次从数据中移除了所有离群值,然后在没有任何离群值的情况下,将数据分成具有 25%数据的训练和测试集。这导致了 78,249 的训练规模和 26,084 个实例的测试集。由于随机森林的结果始终具有最佳结果,这是在这些数据集上运行的唯一模型。表 3 显示了所有训练和测试模型上所有随机森林运行的结果。“随机 25%分割”是一个重要的模型,因为异常值仍然存在于数据中。异常值在这里很重要,因为 Divvy 需要在这几天确保站点做好适当的准备。尽管剔除了异常值的 25%分割表现更好,但它错过了一些最重要的日期,并且仍然具有较低的 R2 分数。这意味着当异常值仍然包含在数据中时,可以对预测进行更多的解释。

Table 3: Random Forest results for all testing sets

最后,图 10 显示,即使数据中有异常值,也可以做出很好的预测。仍然有一些异常值,但在这个测试集上使用这个模型,预测在 56.5%的时间里在 2 辆自行车内,在 82.3%的时间里在 5 辆自行车内,在 93.5%的时间里在 10 辆自行车内。仍然有相当数量的异常值扭曲了数据,但即使有了它们,也能做出好的预测。

Figure 10: Actual vs predicted values on random test set including the outliers

这种差异仍然有很大的增加空间,并且有一些可能发生的方式。首先,运行的第一个模型的问题是,测试设置在 6 月底,由于周末发生的所有事件,周末是严重的异常值。改进方差和结果的一种方法是在数据中包含这些事件。这些可以是导致异常值的最流行事件的二进制列。例如,芝加哥的骄傲周末吸引了成千上万的游客,可以为每年创建一个列,如果那天/周末是骄傲周末,则值为 1,否则为 0。这使得数据知道正在发生的事情会增加当天的旅行次数。可能需要发生的一个小变化是将降水值从连续值改为二进制值。如果每小时下 3 英寸或 0.7 英寸的雨,人们很可能不想在雨中骑自行车。这可能只是引起了一个小小的变化,但变化仍然存在。

结论

在本文中,我们分析了芝加哥的人口和游客如何使用 Divvy 自行车共享系统。对探索性数据分析进行了广泛的研究,以了解用户构成及其与他们出行的关系。创建了五个模型来预测在 2018 年 6 月 17 日至 6 月 30 日的给定时间内,有多少辆自行车将从给定的车站检出。为了检查模型的有效性,在包含和不包含异常值的随机测试集组成的测试集上运行了其他预测。在这些模型中,随机森林模型获得了最好的预测。这些预测是基于直接来自 Divvy 网站的真实世界数据构建和预测的,以证明该模型的有效性。

在 GitHub 上查看我的代码:https://github.com/zkrumlinde/Predicting-Divvy-Checkouts

用线性回归预测房价|从零开始的机器学习(第二部分)

原文:https://towardsdatascience.com/predicting-house-prices-with-linear-regression-machine-learning-from-scratch-part-ii-47a0238aeac1?source=collection_archive---------1-----------------------

预测房子的销售价格,甚至是陌生人的。地下室是怎么回事?

TL;DR 使用测试驱动的方法从头开始使用 Python 构建线性回归模型。您将使用训练好的模型来预测房屋销售价格,并将其扩展到多元线性回归。

从零开始的机器学习系列:

  1. 逻辑回归智能折扣
  2. 用线性回归预测房价
  3. 用 Python 从头开始构建决策树
  4. 利用 K 均值聚类进行调色板提取
  5. 用朴素贝叶斯进行电影评论情感分析
  6. 使用随机梯度下降的音乐艺术家推荐系统
  7. 利用神经网络进行时尚产品图像分类
  8. 使用强化学习在后启示录世界中构建一个出租车驾驶代理

我知道你一直梦想统治房地产市场。直到现在,这还是不可能的。但是有了这个有限的优惠,你就可以……有点偏离主题了。

让我们开始用 Python 构建我们的模型,但这一次我们将在更真实的数据集上使用它。

完整源代码笔记本 (谷歌合作实验室):

[## 线性回归

colab.research.google.com](https://colab.research.google.com/drive/1DXkpo9PmH9_HiCSz9NQlZ9vGQtMIYqmF)

数据

我们的数据来自一个名为“房价:高级回归技术”的 Kaggle 竞赛。它包含了 1460 个训练数据点和 80 个可能帮助我们预测房屋售价的特征。

加载数据

让我们将 Kaggle 数据集加载到 Pandas 数据框中:

探索—感受我们的数据

我们将预测SalePrice列(USD),让我们从它开始:

count      1460.000000
mean     180921.195890 
std       79442.502883 
min       34900.000000 
25%      129975.000000 
50%      163000.000000 
75%      214000.000000 
max      755000.000000 
Name: SalePrice, dtype: float64

大部分密度介于 100k 和 250k 之间,但在价格较高的一侧似乎有许多异常值。

接下来,我们来看看更大的居住面积(平方英尺)与售价的对比:

你可能认为更大的居住面积意味着更高的价格。这张图表显示你大体上是正确的。但是那些提供巨大居住面积的 2-3“便宜”的房子是什么呢?

有一列你可能没想到要探索的是“TotalBsmtSF”——地下室面积的总平方英尺,但我们还是要做:

很有趣,不是吗?地下室区域似乎对我们的模型有很大的预测能力。

好了,最后一个。让我们看看“总体质量”——整体材料和表面质量。当然,这看起来更像是一个主观的功能,所以它可能会提供一个关于销售价格的不同观点。

对于这一个来说,一切似乎都很好,除了当你看向正确的东西时,事情开始变得更加微妙。这会“混淆”我们的模型吗?

让我们更全面地了解一下与售价相关的 8 大特性:

惊讶吗?到目前为止,我们讨论的所有特性似乎都存在。就好像我们从一开始就认识他们…

我们有缺失的数据吗?

我们还没有讨论“处理”丢失数据的方法,所以我们将像老板一样处理它们——只是不使用那些功能:

|              | Row count | Percentage |
|--------------|-----------|------------|
| PoolQC       | 1453      | 0.995205   |
| MiscFeature  | 1406      | 0.963014   |
| Alley        | 1369      | 0.937671   |
| Fence        | 1179      | 0.807534   |
| FireplaceQu  | 690       | 0.472603   |
| LotFrontage  | 259       | 0.177397   |
| GarageCond   | 81        | 0.055479   |
| GarageType   | 81        | 0.055479   |
| GarageYrBlt  | 81        | 0.055479   |
| GarageFinish | 81        | 0.055479   |
| GarageQual   | 81        | 0.055479   |
| BsmtExposure | 38        | 0.026027   |
| BsmtFinType2 | 38        | 0.026027   |
| BsmtFinType1 | 37        | 0.025342   |
| BsmtCond     | 37        | 0.025342   |

是的,我们不会使用任何一个。

预测销售价格

现在我们对正在处理的数据有了一些了解,我们可以开始我们的攻击计划了——如何预测给定房屋的销售价格?

使用线性回归

source: http://mybooksucks.com

线性回归模型假设因连续变量 Y 与一个或多个解释(自变量)变量 X 之间的关系是线性的(即直线)。它用于预测连续范围内的值(如销售额、价格),而不是试图将它们分类(如猫、狗)。线性回归模型可以分为两种主要类型:

简单线性回归

简单的线性回归使用传统的斜率截距形式,其中 ab 是我们试图“学习”并产生最准确预测的系数。 X 代表我们的输入数据, Y 是我们的预测。

source: https://spss-tutorials.com

多变量回归

一个更复杂的多变量线性方程可能看起来像这样,其中 w 代表系数或权重,我们的模型将尝试学习。

变量x_1, x_2, x_3代表我们对每个观察的属性或不同的信息。

损失函数

给定我们简单的线性回归方程:

我们可以使用以下成本函数来为我们的模型找到系数/参数:

均方差(MSE)成本函数

MSE 定义为:

在哪里

MSE 衡量平均模型预测与正确值的差异程度。当模型在我们的训练数据上表现“差”时,该数字会更高。

MSE 的一阶导数由下式给出:

半均方误差

我们将对 MSE 应用一个小的修改——乘以 1/2 ,这样当我们求导时, 2s 抵消了:

OHMSE 的一阶导数由下式给出:

让我们用 Python 来实现它(是的,我们要走 TDD 风格!)

现在我们已经准备好了测试,我们可以实现损失函数:

run_tests()

揭晓结果的时间到了:

..... 
-------------------------------------------------------------- 
Ran 5 tests in 0.007s OK

数据预处理

我们将使用以下公式(标准化)对数据进行一些预处理:

其中是总体均值,σ是标准差。

但是为什么呢?我们为什么要这么做?下面的图表也许能帮到你:

source: Andrew Ng

涂鸦告诉我们,当我们的训练数据被缩放时,我们的老朋友——梯度下降算法——可能会更快地收敛(找到好的参数)。我们走吧。

我们将只对我们的第一个模型使用greater living area特性。

实施线性回归

首先,我们的测试:

事不宜迟,简单的线性回归实现:

您可能会发现我们的线性回归实现比逻辑回归实现简单。注意,梯度下降算法的使用非常相似。很有趣,不是吗?一个算法可以建立两种不同类型的模型。我们可以用它做更多的事情吗?

run_tests()...... 
----------------------------------------------------------------- Ran 6 tests in 1.094s OK

用我们的第一个模型预测销售价格

让我们使用新创建的模型开始我们的住房市场统治:

训练进行得怎么样?

在最后一次迭代中,我们的成本值为:

1569921604.8332634

我们能做得更好吗?

多元线性回归

让我们使用更多的可用数据来建立一个多变量线性回归模型,看看这是否会改善我们的 OHMSE 误差。我们也不要忘记缩放:

执行多变量线性回归

这个空间是故意留白的

使用多变量线性回归

既然我们新模型的实现已经完成,我们就可以使用它了。搞定了。?

你看,年轻的学徒,软件开发的神奇世界有那些神话般的创造,叫做 抽象 。虽然把它们做对可能非常困难,但它们可能会大大降低你编写的代码的复杂性。

你只是使用了一个这样的抽象——叫做 矢量化 。本质上,这允许我们建立一个多变量线性回归模型,而不需要循环我们数据集中的所有特征。整洁,对不对?

我们的客户端界面也保持不变:

结果是:

822817042.8437098

最后一次迭代的损失几乎小了 2 倍。这是否意味着我们现在的模式更好?

您可以在以下位置找到完整的源代码并在浏览器中运行代码:

[## 线性回归

colab.research.google.com](https://colab.research.google.com/drive/1DXkpo9PmH9_HiCSz9NQlZ9vGQtMIYqmF)

结论

不错!你只是实现了一个线性回归模型,而不是简单/蹩脚的那种。

您可能想尝试的一件事是在 Kaggle 的测试数据集上预测房屋销售价格。这个模型好看吗?

在下一部分中,您将从头实现一个决策树模型!

从零开始的机器学习系列:

  1. 逻辑回归智能折扣
  2. 用线性回归预测房价
  3. 用 Python 从头开始构建决策树
  4. 利用 K 均值聚类进行调色板提取
  5. 用朴素贝叶斯进行电影评论情感分析
  6. 使用随机梯度下降的音乐艺术家推荐系统
  7. 利用神经网络进行时尚产品图像分类
  8. 使用强化学习在后启示录世界中构建一个出租车驾驶代理

喜欢你读的吗?你想了解更多关于机器学习的知识吗?提升你对 ML 的理解:

[## 从零开始实践机器学习

“我不能创造的东西,我不理解”——理查德·费曼这本书将引导你走向更深的…

leanpub.com](https://leanpub.com/hmls)

预测医疗保健提供商对政府来说有多贵

原文:https://towardsdatascience.com/predicting-how-expensive-a-healthcare-provider-is-for-the-government-9ef0cccf8a72?source=collection_archive---------23-----------------------

将线性回归应用于医疗保险数据

Medicare for All Rally by Molly Adams (Flickr)

作为 Metis 数据科学训练营的学生,我们的任务是为我们的第一个个人项目建立一个线性回归模型。够简单吧?通过数据画一条线就行了。

没那么快!

作为一名医疗保健专业人员,我很高兴使用这种算法来解决医疗领域的一个问题。但我很快发现,在任何建模之前,一个成功的机器学习项目都是从挑选正确的数据开始的。线性回归最适用于连续的数字数据,它排除了几个公开的医疗保健数据来源。然而,事实证明,医疗保险支付数据是一个完美的匹配。

但是为什么要关心医疗保险呢?医疗保险是一项政府资助的健康保险计划,目前覆盖 4400 万人,即美国八分之一的人口( 1 )。这给政府带来了巨大的公共健康和财政影响。一旦你年满 65 岁,你就有资格享受这一福利,只有少数例外,比如残疾的年轻人或患有晚期肾病的人。这个群体只会越来越大。事实上,人口普查局估计,到 2030 年,仅老年人口就将增加近一倍,达到 7800 万,相当于五分之一的美国人。

最重要的是,由于医疗保险在全国政治讨论中的流行,它在未来几年似乎会变得越来越重要。如果美国要采用单一付款人制度,比如拟议中的全民医保,我们就必须尽可能降低成本,而有效利用数据有助于实现这一目标。

做到这一点的一个方法是查看医疗保健提供者的成本。你可以在我的 GitHub 库上跟随我的代码;为了您的方便,我按照时间顺序整理了这篇文章。

在搜索医疗保险和医疗补助服务中心网站后,我能够获得最新的提供者支付数据,该数据有超过 100 万行,每一行对应一个医疗保健组织或个人提供者,以及 70 个特征。

数据清理

但不出所料,这个数据相当乱。因此,为了尽可能收集最可靠的信息,我决定将我测量的范围缩小到美国的单个供应商,不包括地区和军事区域。

然后,我用计数或百分比数据替换了列中所有缺失的值。如果一个提供者没有患有某种疾病的患者,那么这个字段很可能是空白的;这意味着在不损失太多保真度的情况下估算空值应该是相对安全的。

还有一些文档提供了每个列名的含义。我决定目标变量应为 total_medicare_payment_amt,这是扣除自付额和共同保险金额后,政府为每位患者支付的所有提供商服务的总金额。此外,我删除了所有不必要的列(如提供商名称)或可能导致数据泄漏的列(如其他基于价格的列)。

最后,我只剩下 38 个特征的 990,000 多行。我们开始吧!

初始模型

使用 Statsmodels python 库,我只是将所有数据放入普通的最小二乘(OLS)线性回归中,看看它在没有修改的情况下最初会如何执行。

Univariate Linear Regression Example

如前所述,线性回归试图找到自变量和因变量之间的线性关系。上面,你可以看到只有一个自变量或特征的最简单的单变量形式。它使用方程 y = mx + b 来寻找与数据的最佳拟合;m 是斜率,b 是 y 截距。

但是很明显有了 38 个特征,这个线性回归问题就复杂多了。在这种情况下,将有 38 个“mx”项加在一起,每个 m 项对应于特定变量对因变量的影响的大小和方向。在几何术语中,我们将把 38 维超平面拟合到 39 维空间(而不是直线)。如果你找到一种方法来想象这个,请告诉我!

好了,现在我们对模型有了一些直觉,但是我们如何确定模型做得有多好呢?

这里通常使用的度量标准称为决定系数或 R 平方。本质上,它是由特征预测的目标变量的方差的百分比。我们希望 R 平方接近 1,这表明该模型非常具有预测性。

The Inner Workings of R-squared

但让我们更深入地研究一下 R 平方的实际公式,因为它有助于我们理解我们是如何评估该模型的。我们可以用来预测医疗保险费用的最简单的方法就是猜测平均费用。这就是上图中的绿色ȳ(称为 y-bar)。这将是我们的基线。

但是我们可以通过使用线性回归或红色ŷ(称为 y-hat)做得更好。现在,我们只需找出这两个预测值与实际值的差距,并将它们相除(SSE/SST)。这将告诉我们模型不能解释的方差的百分比。但是我们真正想知道的是这个模型解释了多大百分比的方差。从 1 中减去这个值就可以得到结果。

1 - (Error Sum of Squares/Total Sum of Squares) **or** 1 - (SSE/SST)

运行初始模型后,R 平方为 0.619 。这意味着我们的模型只能解释大约 62%的数据变化。那不太好。

检查我们的假设

但是等等!线性回归有许多假设,检查我们的数据是否真的适用于这个模型是很重要的。

假设#1: 目标变量和特征之间存在线性关系吗?

Number of Services vs Total Medicare Cost Before Feature Engineering

出于说明的目的,如果我们使用医疗保险总成本的特征,它并不完全清楚。为了纠正这种情况,我们可以做一些功能工程。一种选择是对特征和目标都进行对数变换。

Number of Services vs Total Medicare Cost After Feature Engineering

哇!这是一个巨大的进步。任何人都可以通过它划清界限!如您所见,通常情况下,我们需要以特定的方式转换数据,以使其符合我们正在使用的模型的假设。

注意:为了将您的值返回到原始的上下文,请始终记住在之后撤消这个转换。因为毕竟,医疗保险费用的对数到底意味着什么?

假设#2: 目标和特征是否正态分布?

Total Medicare Cost Before and After Feature Engineering

在上图中,左图显示了使用对数变换之前的目标变量;正如你所看到的,它严重向右倾斜。另一方面,右边的图显示了应用这种变换如何产生显著的正态分布。

假设#3: 要素之间很少或没有多重共线性吗?

Correlation Coefficient Heat Map of All Variables

多重共线性是指要素之间高度相关。上图中,我们看到了一张热图,深色表示强正相关。理想情况下,除了中间的对角线之外,我们在其他地方只能看到浅色,因为很明显,一个变量将与自身完全相关。

但在现实中,我们看到深色到处出现,这表明我们违反了这一假设。这可能导致不精确的回归系数,或者更糟糕的是,不同样本中相同特征的符号发生变化,从而难以可靠地从这些系数中提取意义。

解决这一问题的方法是移除要素,直到不再存在任何共线性。正如稍后将讨论的那样,正则化技术通过将彼此共线的一些要素的系数置零来实现这一点。

假设#4: 残差与自身相关吗?

当特定要素的残差相互不独立时,就会发生自相关。这被认为是不好的,因为它表明模型没有从数据中提取所有可能的信息,因此,我们在残差中看到它。

这可以通过德宾-沃森测试来衡量。接近 2 的值表示没有自相关,而接近 0 或 4 的值表示强自相关。我们的初始模型的值为 1.998,表明该模型正在提取尽可能多的信息,并且已经满足了假设。

假设#5: 数据是同质的吗?

这里我们要避免的是异方差,一个有简单解释的大词。这是指残差的方差在要素的值范围内发生变化。

An Example of Heteroskedasticity (Source)

正如你在这个假设的例子中看到的,很明显,随着年龄的增长,方差变得更大。这并不好,因为这意味着我们的模型在预测年龄越大的人时会越差。我们真正想要的是在整个数值范围内一致的可预测性和方差,称为同伦方差。换句话说,两条红色虚线将相互平行。

Predicted vs Residuals Plot Before Feature Engineering

这里我们看到了医疗保险数据模型的预测值和残差。这看起来一点都不好。负残差中有一个严格的截止值(由于政府成本总是大于或等于 0 ),并且方差在值的范围内完全不一致。

Predicted vs Residuals Plot After Feature Engineering

但是在应用了我们之前所做的对数变换之后,这个图现在看起来是相对同伦的,我们已经满足了这个假设。嘣!

二次建模

因此,在检查了所有特性的假设之后,我决定对 3 个特性和目标变量应用对数转换。

现在,我将这个新转换的数据放回模型中,经过训练后,它产生了一个 R 平方值 0.92 。太棒了。这是一个可靠的结果,因为与基线模型相比,新模型可以解释 30%以上的数据差异。这表明转换数据以满足所选模型的假设是多么重要。

但这只是一个 OLS 模型。我们可以应用前面简单提到的正则化技术,这将进一步加强我们的模型。这些给成本函数增加了一个额外的项,使模型变得复杂。这是一个好主意,因为简单的模型通常比复杂的模型更好,因为它们不容易受到过度拟合的影响。

换句话说,复杂模型往往非常适合训练数据,但在看不见的数据上表现不佳。我切换到 scikit-learn 库来做这种规范化,同时通过测试序列分割和交叉验证来增加过程的严密性。

我用岭和套索回归进行了实验,并对决定正则化程度的阿尔法项进行了超参数调整。令人惊讶的是,优化 alphas 的两个模型的表现基本上与 OLS 模型完全相同,R 平方为 0.92,【ridge 明显优于 LASSO。这表明正则化对模型没有显著的帮助。

套索系数也支持这一发现。LASSO 通常会将任何多余的特征归零,只留下少数几个。相比之下,最好的套索模型只清除了 38 个特征中的 1 个。这是一个令人惊讶的结果,表明大多数特征都有助于模型的可预测性,因此更强的正则化只会损害模型的性能。

特征重要性

说到系数,我们可以通过查看系数的符号和大小来确定每个特征的重要性。这使我们能够为我们的利益相关者提供有价值的商业见解,在这种情况下是医疗保险和医疗补助服务中心。

Top 10 Features That Increase Medicare Costs (Positive Coefficients)

在前 10 个特征中,我觉得有趣的是第四个最重要的特征是一个提供者拥有的白人患者的数量。这是令人担忧的,因为模型实际上似乎以某种有意义的方式关心种族。

这可能暴露了系统的潜在缺陷,表明白人人口过多,因此与其他种族相比,白人在医疗保险费用中所占的比例更大。需要进行其他研究来确定根本原因,但很有可能得不到充分服务的人群缺乏获得服务的机会也是原因之一。

这是一个强有力的结果,也是数据科学对社会价值的一个例子。我惊讶地发现,我不仅可以用这种算法来提高利益相关者的底线,还可以揭示社会差距。这是我热爱数据科学的主要原因之一;它会对我们的社会产生巨大的影响。

Top 10 Most Expensive Specialties

看看最贵的专科,外科显然脱颖而出。这是有意义的。手术非常昂贵。这意味着政府应该尽最大努力降低手术成本,以便最大程度地影响他们的底线。

Top 10 Most Expensive Medical Conditions

就医疗条件而言,基本上可以预防的慢性病占据了上风。这一发现是一把双刃剑,与我们已经知道的一致。可悲的是,这意味着这些医疗保险患者中的大多数正在遭受疾病,如果他们有不同的生活方式选择,他们就不会遭受这些疾病。从金融和伦理的角度来看,这绝对是可怕的。

但从更积极的方面来看,这意味着政府可以节省大量的资金,同时通过开始关注预防性生活方式药物而不是被动治疗,如手术,来减少巨大的痛苦。

显然,我们不需要数据科学来告诉我们吃得更好,多运动。但是这进一步支持了我们都知道的对社会中每个人的幸福是必要的。

在这个过程中,我们建立了一个模型来准确预测医疗保健提供商对政府来说有多贵。省钱和拯救生命,我们还能要求什么?

从出院记录预测 ICU 再入院:重要术语

原文:https://towardsdatascience.com/predicting-icu-readmission-from-discharge-notes-significant-terms-270f6c6bb8f9?source=collection_archive---------32-----------------------

使用高频术语进行查询提高了召回率和稀有术语的精确度。重要术语平衡了两者,同时在检索到的文档可能属于的潜在类别中提供了一些辨别能力。此处研究 MIMIC-III 数据集的背景是根据出院记录预测患者再入院,弹性搜索驱动显著性测量…

了解哪些患者可能会重复入住重症监护室(CCU)是有用的。患者和支持团队可以格外警惕和规避风险,以尽量减少这种结果。任何 CCU 入院都会有大量数据,如患者的生命体征、诊断、提供的治疗、实验室结果等。除了预后和未来护理计划之外,这些数据通常都包含在出院记录中。我们能使用所有这些信息来帮助我们的预测任务吗?

各种传统的分类器(例如逻辑回归和新的基于 BERT 的模型(例如 ClinicalBERT )在过去已经被用于相同的任务,并取得了一些成功。因此,这里没有人声称这是开创性的工作——但我们确实以一种不同的方式来完成这项任务。当然,还要与其他产品进行比较,看看有什么好处。这是这篇文章的概要。

  • 检查重要的术语,看看它们为什么有用
  • 简要描述公开可用的 MIMIC-III 数据集,特别是我们感兴趣的 CCU 入院和出院记录
  • 建立一个弹性搜索索引,其中每个文档都是一个出院记录,带有一个标签,指示同一患者在出院后 30 天内是否再次入院( readmit = 1 )或是否再次入院( readmit = 0 )
  • 提取两个类别中的重要术语
  • 使用这些有意义的术语对一组出院记录进行评分,看看我们做得有多好。由于篇幅的原因,我们将在下一篇文章中讨论这个问题。

我们将在这里查看一些代码片段,用于将 MIMIC-III csv 数据转换为弹性搜索索引,但是 MIMIC-3 数据集本身需要单独获得。

1.重要术语

在任何文本分类任务中,知道哪些单词可能在一个类别中相对于所有其他类别更普遍是非常有用的。这些术语有可能被用作类的定义特征,从而为分类任务提供一个钩子。这些是重要术语。它们不必是该类别中最常用的术语,也不必是最罕见的术语。在大多数情况下,事实并非如此。就此类术语的存在程度而言,我们可以将它们定义为:

显示所有放电记录与特定类别放电记录之间测得的频率发生显著变化的术语

(转述自 elastic.co 的

通过罕见的术语进行搜索给出了很好的精确度,但是召回率会很低。高频词广撒网,提高了召回率,但精确度受到影响。重要术语试图在两者之间取得平衡。当从本质上相同的词汇表(例如,像病人出院记录)构建类时,考虑到精确度/召回率的折衷,重要的术语总体上可以更好地识别匹配的文档。

请参见杨和 Pedersen,“文本分类中特征选择的比较研究”从跨多个类别的文本语料库中提取这些重要术语的详细背景、分析和方法。此外,Elasticsearch 为索引文本提供了这些方法(以及其他一些方法)的现成实现。在它的默认实现中,我们可以按照为每个术语计算的所谓的 jlh 分数来排序这些重要的术语,如下所示。

Equation 1. The jlh score is a compromise between precision and recall

有了每个类别的这些术语,我们就有可能对类别概率的放电笔记进行评分,并确定该笔记应属于哪个类别。这是基本的想法。

1.MIMIC-III 数据集

论文的摘要指出:

MIMIC-III(“重症监护医疗信息集市”)是一个大型的单中心数据库,包含与大型三级护理医院重症监护病房收治的患者相关的信息。数据包括生命体征、药物治疗、实验室测量、护理提供者绘制的观察和记录、体液平衡、程序代码、诊断代码、成像报告、住院时间、存活数据等。

约翰逊等人。艾尔。2016

数据以各种 csv 文件的形式提供。就我们的目的而言,我们感兴趣的是“录取”。CSV "和" NOTEEVENTS。CSV”。入院文件允许我们判断某个特定的入院事件是否实际上是同一患者的再入院事件(当然,在某个时间段内,这里假定为 30 天)。“招生”的有用栏目。CSV”对于我们的目的是:

"ROW_ID", "SUBJECT_ID", "HADM_ID", "ADMITTIME","DISCHTIME", "DEATHTIME", "ADMISSION_TYPE",.. "HOSPITAL_EXPIRE_FLAG" ..

处理录取文件时需要注意的几件事是:

  • 死亡时间不为空或 HOSPITAL_EXPIRE_FLAG 等于 1 表示该住院以患者死亡结束。入院类型可以是[“紧急”、“选择性”、“紧急”、“新生儿”]之一
  • 如果入院事件最终导致患者死亡,那么该患者自然永远不会有再入院事件。因此,此类入院事件不应被视为不准再入院事件。
  • 如果入院事件是选择性的(即由患者发起),则即使该事件发生在上次出院后 30 天内,也不会被视为再次入院事件
  • 所有新生儿入院事件都被忽略,因为新生儿显然由于各种原因被例行地移入和移出 CCU

NOTEEVENTS 文件只有我们感兴趣的几列。

"ROW_ID","SUBJECT_ID","HADM_ID", ..., "CATEGORY", ... ,"TEXT"
  • 当该行的“类别”列显示为“出院小结”时,我们使用“文本”列。
  • SUBJECT_ID 和 HADM_ID 允许我们在招生中加入一个招生事件行。
  • 尽管入院中的每行在 NOTEEVENTS 中至少有一行,但该行可能没有“出院总结”类别。录取文件中的那些录取事件自然不会成为我们分析的一部分。
  • 当有出院总结时,通常每个入院事件有份。但是有些入院事件确实有两个出院小结,不管什么原因。我们在这里把它们连接起来。
  • 在少数情况下,出院记录显示患者已经过期,而同一事件的入院文件(SUBJECT_ID 和 HADM_ID 组合)却没有。不知道为什么,但为了安全起见,我们不考虑他们。

2.弹性搜索指数

我们将招生和 NOTEEVENTS 文件中的内容编入一个单一的弹性搜索索引。索引中的每个文档都将

  • 对应于入院事件(SUBJECT_ID 和 HADM_ID 组合)
  • 有相关的出院小结(如果不止一个,则串联)
  • 重新接收标签按照上面的讨论进行计算。
  • 再入院= 1 此入院是指患者在较早出院后的 30 天内再次入院
  • 重新接纳= 0 不是重新接纳事件

我们从这两个文件中选择我们需要的列。索引的数据模型很简单。

2.1 模型

数据模型本质上是一对一的,对于以编程方式连接的两个文件中的每一个,列到字段都有正确的类型关键字/文本/日期等。这些文件中的每一行都有 SUBJECT_ID 和 HADM_ID。它为我们提供了一个自然的连接条件,同时为每个准入强制执行一个出院总结,在我们的例子中,当 NOTEEVENTS 文件中有更多的出院总结时,将它们连接起来。SUBJECT_ID 和 HADM_ID 一起是我们索引的文档 ID。并且每个文档得到计算的标签 readmit — 0 或 1。这就很好地为我们想要对这些数据使用的任何分类器做好了准备。

录取文件上的日期是这样的:2196–04–09 12:26:00(未来之路!)因此,下面是日期字段的映射。日期值可能看起来很奇怪,但它们已经从实际值及时转移——PHI&HIPAA 在起作用。这是我们最终的索引映射。字段令牌和内容来自 NOTEEVENTS 文件。

在索引中携带标记数组的原因是我们希望密切控制标记化——而不是让 Elasticsearch 替我们做这件事。因此,我们用 NLTK 对 NOTEEVENTS 中的流量摘要进行标记,并使用可选的词汇化。内容只是标记的连接。它就在那里,以便我们可以看到和查询文本形式的出院记录。

根据 HIPAA 合规性,出院记录包含几个用于删除摘要中 PHI 的屏蔽词。我们在这里用不到它们,所以除了 NLTK 停用词之外,还要删除一些。

2.2 指数

在我的笔记本电脑上运行索引需要几分钟时间。我们需要做的一件事是增加 Elasticsearch 可以生成的聚合桶的数量。我们更新了聚类设置,因此这个数字大于我们拥有的患者数。

首先,当从文件中读取时,所有的许可(除了新生的)都被索引,并设置为 readmit = 0。然后,在处理 NOTEEVENTS 文件时,这些文档会用释放摘要(令牌和内容)进行更新。其中大多数都是通过以下流程作为批量索引操作来执行的。添加“time.sleep()”调用是为了确保在处理流程中可以通过搜索查询检索到索引文档。

上面的关键部分是计算重新提交标签。下面的代码片段查询索引以获得按 ADMITTIME 排序的每个患者的入院情况。一个选择性录取永远不会有重录取 = 1。但它可能会导致将来的重新入院事件,因此我们需要处理它。我们也排除了那些因院内呼气事件导致的入院。当前入院时间与前一次入院时间之差应小于 30 天,符合再次入院条件。

我们必须记住删除那些为重新接收标记为 0 的住院过期事件。下面的代码片段 removeDeaths() 更新它们,以便重新提交 = -1。它们会出现在索引中,但我们会在分析中将其过滤掉。

函数 removeExpired() 在出院记录中包含术语“Expired”的少数情况下,将 readmit 标签设置为-1,但由于某种原因,数据文件中的 HOSPITAL_EXPIRE_FLAG 没有正确设置为 1。

最后,函数 removeEmptyNotes()将所有那些没有出院小结的入院事件的重新入院标签设置为-1,因为它们属于不同的类别。我们在上一节中讨论了这一点。

我想我们可以将这三个移除函数合并成一个,这样可以节省一些编写工作…但是它们已经完成了工作,所以让我们继续。

2.3 数据

运行索引器输出票据上计算标签的分布进度。

Figure 1. Of the 51113 admission events 2892 of them resulted in readmission within 30 days.

这些班级很不平衡,在考虑的 43765 名入学者中,再入学者仅占 6.6%。在下一篇文章的分类练习中,我们会对更大的类进行二次抽样来处理这个问题。

3.重要术语

有了干净的标记和索引,我们就可以提取每个类中的重要术语了。首先,让我们通过实际计算一个学期的分数来说明我们在第 1 节中所说的关于 jlh 的分数。

3.1jlh分数

我们的背景设定是所有的放电笔记:40873 + 2892 = 42765。前景集是这两个类中任何一个的音符。所以我们得到了每个类的一组重要术语。查询很简单。对于重新提交 = 1 类,查询如下。前景注释集来自重新入院事件。background_filter 子句包括了 readmit = 0 和 readmit = 1(同时排除了索引中仍然存在的 readmit = -1!).正是我们想要的。

运行上面的代码会产生前 100 个重要术语,按照类 readmit = 1 的 jlh 分数排序。让我们看看最上面的一项,并确认分数与我们在等式 1 中的公式一致。

"aggregations" : {
    "driver_words" : {
      "doc_count" : 2892,
      "bg_count" : 43765,
      "buckets" : [
        {
          "key" : "failure",
          "doc_count" : 1549,
          "score" : 0.2392225894630799,
          "bg_count" : 16204
        },
        {
...

Figure 2. Computing the jlh score for a term

我们在这里计算的 jlh 分数与响应中的分数匹配。

3.2 频率与重要性

看看这两个类的前 10 个术语(重新接纳 = 0 & 1)是很有启发性的,包括频率和重要性。只是为了证明原始频率在描述一个类的特征方面并不是很有帮助…

Figure 3. There is no difference in the top 10 terms by Frequency in either class, except for a minor change in order. On the other hand, the significant terms for the two classes have zero overlap.

更好的是,为了鸟瞰,我们可以并排查看两个类中的频繁和重要术语的 wordcloud 图表。

Figure 4. (A) You cannot tell a potential future readmission discharge note from looking at the high frequency words. (B) Focusing on the significant terms we do see a good bit of difference between the two notes

3.3 稀有性与重要性

人们可能会认为,在识别类别时,稀有术语(按类别)可能比高频术语做得更好。不幸的是,在任何真实文本语料库中,这些罕见的术语都受到拼写错误的困扰,许多这样的词的计数为 1(长尾),因此没有产生任何价值。为了完整起见,这里是 MIMIC-III 数据集中按类别列出的最罕见的术语(带有计数频率)。

Figure 5. Rare terms are plagued by spelling errors, thereby yielding little value to the analysis. Plus we get whipped by the long tail of such words with the same low frequency

几乎所有罕见的术语似乎都是拼写错误的单词。使用最低阈值频率(> 1)作为一个罕见的词可能会有所改善,但你的里程可能会有所不同。

4.结论

在本帖中,我们有:

  • 描述了重要术语对信息检索的有用性
  • 描述了 MIMIC-III 数据集,并在 Elasticsearch 中对其进行了索引,目的是使用当前入院的出院记录预测潜在的未来 ICU 再入院事件(或不)
  • 使用 Elasticsearch 过滤每门课笔记中的重要术语
  • 我们发现,重要术语似乎能更好地区分这两个类别,而高频术语或罕见术语则不能

这为后续的文章打下了基础,在后续的文章中,我们将如何利用这些重要的术语来预测一组放电记录。

原载于 2019 年 11 月 30 日【xplordat.com】

预测 MLB 投手的伤病

原文:https://towardsdatascience.com/predicting-injuries-in-mlb-pitchers-c2e133deca39?source=collection_archive---------21-----------------------

Photo by Jose Francisco Morales on Unsplash

我已经完成了训练营的一半,完成了我的第三个也是最喜欢的项目!过去几周,我们学习了 SQL 数据库、分类模型(如逻辑回归和支持向量机)以及可视化工具(如 Tableau、Bokeh 和 Flask)。我把这些新技能用在我过去两周的项目中,对受伤的投手进行分类。这篇文章将概述我对这个项目的过程和分析。我所有的代码和项目演示幻灯片都可以在我的 Github 上找到,我这个项目的 Flask 应用程序可以在 mlb.kari.codes 上找到。

挑战:

在这个项目中,我的挑战是使用二元分类法预测 MLB 投手的受伤情况。为此,我从几个网站收集了数据,包括 Baseball-Reference.com 和 MLB.com 按赛季统计的投球数据,Spotrac.com 按赛季统计的残疾人名单数据,以及 ka ggle 2015-2018 年的逐场数据。我的目标是使用前几个赛季的汇总数据,预测下一个赛季是否会有投手受伤。该项目的要求是将我们的数据存储在 PostgreSQL 数据库中,利用分类模型,在 Flask 应用程序中可视化我们的数据,或者在 Tableau、Bokeh 或 Plotly 中创建图表。

数据探索:

我收集了 2013-2018 赛季超过 1500 名美国职业棒球大联盟投手的数据。为了对我的数据有一个感觉,我从最直观地预测受伤的特征开始,并在受伤和健康投手的子集中进行比较,如下所示:

我首先看了看年龄,虽然受伤和健康球员的平均年龄都在 27 岁左右,但数据在两组中都略有不同。受伤球员最常见的年龄是 29 岁,而健康球员的年龄要低得多,为 25 岁。同样,受伤球员的平均投球速度比健康球员高,这是意料之中的。我考虑的下一个功能是汤米约翰手术。这是投手中非常常见的手术,手臂的韧带撕裂,用从手臂或腿上提取的健康肌腱代替。我假设有手术史的投手更有可能再次受伤,数据证实了这个想法。30%的受伤投手曾经做过手术,而健康的投手只有 17%。

然后,我查看了两组的平均输赢记录,令人惊讶的是,这是我的数据集中与伤害相关性最高的特征。受伤投手的子集平均赢得了 43%的比赛,相比之下,健康球员的胜率为 36%。获胜次数多的投手会获得更多的上场时间,这是有道理的,这可能会导致更多的受伤,正如受伤球员每场比赛的平均投球局数更高所示。

在这个项目中,我最感兴趣探索的特性是投手的曲目,以及某些投球是否更能预示受伤。查看特征相关性,我发现伸卡球和切球的投球与受伤有最高的正相关性。我决定更深入地探索这些球种,并且观察每年由个别投手投出的伸卡球和切球组合球种的百分比。我注意到一种受伤的模式,发生在伸卡球/切球百分比最高的几年。以下是最近受伤的四位 MLB 顶尖投手的样图。图上的红点代表运动员受伤的年份。你可以看到他们通常对应于每一个投手的伸卡球/切球百分比都达到高峰的年份。

另一个趋势是,我从几个投手的这些图中注意到,受伤经常发生在同一年或在投手的剧目中首次引入伸卡球/切球的第二年。

建模:

我的下一步是将我所有的特征放入几个分类模型中。受伤的投手只占我的数据集的 28%,我首先要处理我的班级不平衡。我转向随机过采样来平衡输入到逻辑回归、KNN、线性 SVM 和随机森林模型中的类。我还用 Smote 和 Adasyn 测试了线性 SVM 模型中的合成过采样,结果稍差。我在 2015-2017 赛季训练了我的模型,并在这个训练数据的验证子集上比较了它们的分数。我的模型得分如下:

从上表可以看出,采用随机过采样的线性 SVM 得分最高。我使用 ROC 曲线下的面积作为我的模型评分标准,因为它提供了模型的真阳性与假阳性比率的可解释评估。下面是我测试的每个模型的 ROC 曲线。理想的 ROC 曲线会接触到图表的左上角,因此我们正在寻找最接近该角并且具有持续增加的真阳性率的模型。在这种情况下,代表线性 SVM 模型的蓝色粗线在 ROC 曲线的大多数阈值上为我的数据集取得了最好的成绩。

我选择了线性 SVM 模型,根据我的训练和验证数据对它进行了重新训练,并最终在我的 2018 赛季的 hold out 数据上对它进行了评分。我的模型在 ROC 曲线下获得了 0.7072 的面积,我对此非常满意!我的模型提供了对随机伤病猜测的重大改进,并给了我一些对可控特征(如伸卡球/切球投球百分比)的洞察力,教练可以想象这些特征来避免高风险投手的伤病。

烧瓶应用程序:

Main Search Page of App

为了展示我所有的上述发现,我创建了一个 Flask 应用程序,其中包括我数据库中所有投手的球员页面。在主页上,你可以输入你最喜欢的 MLB 投手(只要他们在 2013 年和 2018 年赛季之间打了 5 局以上),你就会被引导到他们的球员页面,其中包括投球统计数据、按赛季分列的投球百分比以及我的模型对 2019 年受伤概率的预测。

Sample Player Page in App

未来工作:

在这个训练营中,我真的希望我有机会参与一个与棒球相关的项目,我在构建这个模型和应用程序中获得了很多乐趣。在未来,我会喜欢在这个项目的基础上,把我的预测缩小到一个游戏一个游戏的水平,而不是一个赛季一个赛季的。我还希望扩展我的应用程序,使其更具交互性,并允许用户玩不同的投球百分比,以查看受伤概率如何变化。我很高兴有机会通过这些项目扩展我的知识。我也意识到在我当投手的这些年里,我个人努力投伸卡球,这可能是一件因祸得福的事。

在 Keras 中使用卷积神经网络(CNN)预测浸润性导管癌

原文:https://towardsdatascience.com/predicting-invasive-ductal-carcinoma-using-convolutional-neural-network-cnn-in-keras-debb429de9a6?source=collection_archive---------12-----------------------

使用 CNN 将组织病理学切片分类为恶性或良性

在这篇博客中,我们将学习如何在真实世界的组织病理学数据集中使用 CNN。真实世界的数据比 MNIST 等标准数据集需要更多的预处理,我们将经历使数据为分类做好准备的过程,然后使用 CNN 对图像进行分类。我还将讨论所使用的 CNN 架构以及在构建模型时调整的一些超参数。

这个博客的不同部分是:

  • 介绍
  • 了解数据集
  • 加载数据集
  • 数据预处理
  • 通过随机欠采样处理数据不平衡
  • 模型架构
  • 编译模型
  • 数据扩充
  • 训练模型
  • 做预测
  • 评估模型性能

我假设读者知道什么是卷积神经网络及其基本工作机制。

在这篇博客中,我们将只讨论在深度学习的背景下重要的代码,同时避免重复解释不止一次使用的代码。

这篇文章的 Github 库可以在这里找到。我建议你在阅读本教程的时候跟着 Jupyter 笔记本走。

介绍

在过去的几年里,使用深度学习进行医学图像分析的情况越来越多,并且越来越成功。医疗保健领域的深度学习用于识别模式、分类和分割医学图像。与大多数图像相关的任务一样,卷积神经网络用于完成这一任务。

这里解决的分类问题是将浸润性导管癌(IDC)的组织病理学切片分类为恶性或良性。

Histopathology slide of a malignant tumour. Image credits: Department of Pathology at Johns Hopkins University

IDC 是一种乳腺癌,其中癌症已经扩散到周围的乳腺组织。

癌症肿瘤可以分为两种类型:恶性和良性。良性肿瘤是一种不侵犯其周围组织的肿瘤,而恶性肿瘤是一种可以扩散到其周围组织或身体其他部位的肿瘤。

了解数据集

我们将使用的数据集可以从这里下载。向下滚动到页面的数据集描述部分,并下载 1.6 GB 的 zip 文件。

该数据集由 162 幅以 40 倍扫描的乳腺癌标本的完整载片图像组成。从中提取了 277,524 个大小为 50×50 的斑块,其中 198,738 个为 IDC 阴性(良性),78,786 个为 IDC 阳性(恶性)。

每个修补程序的文件名的格式如下:

u_xX_yY_classC.png →示例 10253 _ idx 5 _ x 1351 _ y 1101 _ class 0 . png

其中 u 是患者 ID (10253_idx5),X 是裁剪此补丁的 X 坐标,Y 是裁剪此补丁的 Y 坐标,C 表示类别,其中 0 表示非 IDC(良性),1 表示 IDC(恶性)

加载数据集:

imagePatches = glob('C:/Users/asus/Documents/Breast cancer classification/**/*.png', recursive=True)patternZero = '*class0.png'
patternOne = '*class1.png'#saves the image file location of all images with file name 'class0' classZero = fnmatch.filter(imagePatches, patternZero) #saves the image file location of all images with file name 'class1'
classOne = fnmatch.filter(imagePatches, patternOne)

数据集由 279 个文件夹组成,279 个文件夹中的每个文件夹都有子文件夹 0 和 1。我们首先创建两个变量 classZero 和 classOne,分别保存所有 class 0 和 class 1 图像的位置

def process_images(lowerIndex,upperIndex):
    """
    Returns two arrays: 
        x is an array of resized images
        y is an array of labels
    """ 
    height = 50
    width = 50
    channels = 3
    x = [] #list to store image data
    y = [] #list to store corresponding class
    for img in imagePatches[lowerIndex:upperIndex]:
        full_size_image = cv2.imread(img)
        image = (cv2.resize(full_size_image, (width,height), interpolation=cv2.INTER_CUBIC))
        x.append(image)
        if img in classZero:
            y.append(0)
        elif img in classOne:
            y.append(1)
        else:
            return
    return x,y

然后我们创建一个函数 process_images ,它将图像的起始和结束索引作为输入。这个函数首先使用 OpenCV 的 cv2.imread()读取图像,并调整图像的大小。因为数据集中的图像很少不是 50x50x3,所以完成了调整大小。该函数返回两个数组:X 是调整大小后的图像数据的数组,Y 是相应标签的数组。

X, Y = process_images(0,100000)

对于本教程,我们将只分析从索引 0 到 60,000 的图像。图像数据(像素值)现在存储在列表 X 中,它们相应的类存储在列表 y 中

数据预处理:

X = np.array(X)
X = X.astype(np.float32)
X /= 255.

列表 X 首先被转换为 numpy 数组,然后被转换为 float32 类型以节省空间。

图像首先通过除以 255 进行归一化。这确保了所有值都在 0 和 1 之间。这有助于我们更快地训练模型,也防止我们陷入消失和爆炸梯度的问题。

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X,Y,test_size=0.15)

数据集分为训练集和测试集,整个数据集的 15%保留用于测试。对于 60,000 的数据集,这意味着 51000 个图像被保留用于训练,9000 个用于测试。

通过随机欠采样处理数据不平衡

y_train.count(1)  #counting the number of 1
y_train.count(0)  #counting the number of 0

计算数组 Y 中 1 和 0 的数量,我们发现有 44478 个 0 类图像和 15522 个 1 类图像。

这个问题被称为数据不平衡,会导致我们的模型更偏向于某个特定的类,通常是拥有更多样本的类。特别是在医疗保健等领域,将少数群体(在这种情况下是恶性的)归类为多数群体(在这种情况下是良性的)可能非常危险。我们将通过随机欠采样多数类来处理数据不平衡,即移除多数类的样本以使多数类和少数类的样本数量相等。

y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

在此之前,我们需要对输出变量 y_train 和 y_test 进行一次热编码。

X_trainShape = X_train.shape[1]*X_train.shape[2]*X_train.shape[3]
X_testShape = X_test.shape[1]*X_test.shape[2]*X_test.shape[3]
X_trainFlat = X_train.reshape(X_train.shape[0], X_trainShape)
X_testFlat = X_test.reshape(X_test.shape[0], X_testShape)

我们还需要改变 X_train 和 X_test 的形状,以使用随机欠采样。

from imblearn.under_sampling import RandomUnderSampler
random_under_sampler = RandomUnderSampler(ratio='majority')
X_trainRos, Y_trainRos = random_under_sampler.fit_sample(X_trainFlat, y_train)
X_testRos, Y_testRos = random_under_sampler.fit_sample(X_testFlat, y_test)

参数“ratio=majority”表示随机欠采样到欠采样多数类。

在执行随机欠采样后,再次检查每个类别的样本数量,我们发现两个类别的样本数量相等。然后,图像数据被转换回其 50 x 50 x 3 的原始形状。

模型架构

我们使用一个与这篇论文中讨论的类似的架构。

batch_size = 256
num_classes = 2
epochs = 80model = Sequential()
model.add(Conv2D(32, kernel_size=(3,3),
                 activation='relu',
                 input_shape=(50,50,3)))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(64, (3,3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Conv2D(128, (3, 3), activation='relu'))
model.add(Conv2D(256, (3, 3), activation='relu'))
model.add(Flatten()) #this converts our 3D feature maps to 1D feature vectors for the dense layer below
model.add(Dropout(0.5))
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(128, activation='relu'))
model.add(Dense(num_classes, activation='sigmoid'))

该模型是连续的,允许我们逐层创建模型。该架构由卷积层、最大池层、丢弃层和全连接层组成。

第一层是卷积层,具有 32 个大小为 3×3 的滤波器。我们还需要在第一层中指定输入形状,在我们的例子中是 50 x 50 x 3。

我们将对除最终输出层之外的所有层使用整流线性单元(ReLU)激活函数。ReLU 是隐藏层中激活函数的最常见选择,并且已经显示出相当好的效果。

第二层是汇集层。汇集层用于减少维度。2x2 窗口的最大池仅考虑 2x2 窗口中的最大值。

第三层也是 64 个滤波器的卷积层,每个滤波器的大小为 3×3,后面是另一个 2×2 窗口的最大池层。通常,卷积层中的滤波器数量在每一层之后都会增长。具有较低数量滤波器的第一层学习图像的简单特征,而较深的层学习更复杂的特征。

接下来的两层也是卷积层,具有相同的滤波器大小,但是滤波器数量增加;128 和 256。

在添加完全连接的层之前,我们需要将从卷积层输出的 3D 特征图展平为 1D 特征向量。这就是扁平化层的用武之地。

下一层是辍学率为 0.5 的辍学层。脱落率为 0.5 的脱落层意味着 50%的神经元将被随机关闭。这有助于防止过度拟合,方法是让所有的神经元学习一些关于数据的知识,而不仅仅依赖于少数几个神经元。在训练过程中随机丢弃神经元意味着其他神经元将不得不做关闭的神经元的工作,从而更好地泛化并防止过度拟合。

值 0.5 取自 Hinton (2012) 的原始论文,经证明非常有效。这些脱落层被添加到输出之前的每个完全连接的层之后。辍学也减少了每个历元的训练时间。

下面的密集层(全连接层)有 128 个神经元。其后是另一个退出层,退出率为 0.5

下一层是另一个密集层,有 128 个神经元。

最终输出层是另一个密集层,其神经元的数量等于类别的数量。这一层中的激活函数是 sigmoid,因为手头的问题是二元分类问题。对于多类分类问题,激活函数应设置为 softmax。

编译模型

model.compile(loss=keras.losses.binary_crossentropy,
              optimizer=keras.optimizers.Adam(lr=0.00001),
              metrics=['accuracy'])

该模型采用二元交叉熵损失函数进行编译,并使用 Adam 优化器。“准确性”指标用于评估模型。

Adam 是一种优化算法,它以迭代的方式更新网络权重。

虽然可以设置 Adam 的初始学习速率(在我们的例子中,我们将其设置为 0.00001),但这是初始学习速率,每个参数的学习速率随着训练的开始而调整。这就是 Adam(自适应矩估计的缩写)与随机梯度下降的不同之处,随机梯度下降对所有权重更新保持单一的学习速率。亚当优化算法的详细解释可以在这里找到

学习率决定了我们将网络的权重向局部最小值调整的速度。过高的学习率会导致如此高的权重变化,以至于可能导致超过局部最小值。这导致训练或验证误差在连续的时期之间剧烈波动。学习率太低会导致花更长的时间来训练我们的网络。因此,学习率是建立模型时需要调整的最重要的超参数之一。

数据扩充

datagen = ImageDataGenerator(
    featurewise_center=True,
    featurewise_std_normalization=True,
    rotation_range=180,
    horizontal_flip=True,vertical_flip = True)

一般来说,我们拥有的数据越多,深度学习往往就越有效。Keras ImageDataGenerator 使用数据扩充在训练期间生成实时图像。转换是在运行中对小批量执行的。数据扩充通过减少网络过度拟合训练数据的能力来帮助概括模型。旋转、垂直和水平翻转是一些常用的数据扩充技术。

Keras ImageDataGenerator 提供了多种数据扩充技术。但是,我们只会使用其中的少数几个。

如果将标记为恶性的组织病理学载玻片旋转 20 度并垂直翻转,它仍然是恶性的。

训练模型

使用 GPU 训练模型可以加快训练过程。你将需要一个 NVIDIA 的 GPU 来做到这一点。我按照这个教程来启用 GPU 训练。

我们将历元的数量设置为一个较大的数字,在我们的例子中为 80,并使用一种称为早期停止的正则化方法

early_stopping_monitor = EarlyStopping(monitor='val_loss', patience=3, mode='min')

早期停止是一种用于避免过度拟合的方法,当要观察的参数集在一定数量的时期内没有改善时,通过停止训练过程来避免过度拟合。

在我们的例子中,我们告诉 EarlyStopping 监控 val_loss,如果连续 3 个时期没有改善,就停止训练过程。"

批量大小通常设置为 2 的幂,因为这样计算效率更高。我们将其设置为 256。

我们使用另一个称为 ModelCheckpoint 的 Keras 回调

model_checkpoint = ModelCheckpoint('best_model.h5', monitor='val_loss', mode='min', verbose=1, save_best_only=True)

模型检查点用于保存模型。monitor 参数允许我们设置一个我们想要关注指标。在我们的例子中,我们只在验证损失最小的时候保存模型。我们保存最佳模型,以便稍后用于进行预测,从而评估模型的性能。

结合这两个回调,保存最佳模型(其具有最小的验证损失),然后,如果验证损失在接下来的 3 个时期(由 EarlyStopping 设置)没有改善(减少),则模型训练停止。

training = model.fit_generator(datagen.flow(X_trainRosReshaped,Y_trainRosHot,batch_size=batch_size),steps_per_epoch=len(X_trainRosReshaped) / batch_size, epochs=epochs,validation_data=(X_testRosReshaped, Y_testRosHot), verbose=1, callbacks=[early_stopping_monitor, model_checkpoint])

因为我们正在动态使用 ImageDataGenerator,所以我们使用 model.fit_generator 来训练模型。我们将其设置为变量“training ”,因为我们稍后将绘制训练损失和验证损失。这有助于我们了解方差,即训练误差和验证集误差之间的差异。

为了验证,我们将使用 x_testRosReshaped 和 y_test shot,它们是在对 X _ test 和 Y _ test 集进行欠采样后获得的。

由于提前停止,训练在 37 个周期后停止。因此,保存的最佳模型是在时期 34 期间的模型,验证精度为 79.10%

val_loss stopped improving after Epoch 34

绘制训练集和验证集损失,我们发现方差非常低。该图确保我们的模型不会过度拟合。

Training and testing set loss

做预测

from keras.models import load_model
from sklearn import metricsmodel = load_model('best_model.h5')y_pred_one_hot = model.predict(X_testRosReshaped)y_pred_labels = np.argmax(y_pred_one_hot, axis = 1)
y_true_labels = np.argmax(Y_testRosHot,axis=1)

我们加载由 ModelCheckpoint 保存的最佳模型,并使用 predict 函数来预测 X_testRosReshaped 数组中图像的类别。预测现在存储在列表 y_pred_labels 中。

评估模型的性能

confusion_matrix = metrics.confusion_matrix(y_true=y_true_labels, y_pred=y_pred_labels)

我们使用混淆矩阵来评估模型的性能。二进制分类矩阵中的混淆矩阵有四个象限;假阳性,假阴性,真阳性和真阴性。

对于我们的情况,混淆矩阵的四个象限可以简化如下:

Confusion matrix with description of the 4 quadrants for our case

我们得到的混淆矩阵是:

Resultant confusion matrix

在这种情况下,较低的假阴性比较低的假阳性要好。这是因为将恶性肿瘤识别为良性比将良性肿瘤识别为恶性更危险,因为前者将导致患者因误诊而接受不同的治疗,而后者无论如何都可能要接受进一步的检查。

我们可以看到,我们的模型在测试集上表现良好,准确率为 79.10%。只有混淆矩阵也对我们有利,我们有一个低方差的模型。

应用深度学习是一个迭代的过程。您可以通过调整优化算法的学习速率、更改批量大小、更改卷积层的滤波器、添加更多层或使用更多数据等超参数来尝试和进一步改进该模型。

感谢阅读。如果你有任何问题,请在下面评论。我将定期撰写深度学习等主题的文章,所以请在 Medium 上关注我。我也可以上 LinkedIn !:)快乐编码。

用梯度增强决策树预测 Kickstarter 活动的成功:一个机器学习分类问题

原文:https://towardsdatascience.com/predicting-kickstarter-campaign-success-with-gradient-boosted-decision-trees-a-machine-learning-23077436c5f7?source=collection_archive---------9-----------------------

前几天,我在浏览 data.world 时,偶然发现了一个数据集,该数据集研究了 kickstarter 成功和失败的活动。

我想,“在预测 kickstarter 活动是否成功时,需要什么,即哪些功能是重要的?”于是,这个项目诞生了。在本文中,我将带您了解该项目的更多精彩亮点。完整代码见底部回购链接!

探索性数据分析对数据进行清洗并再次返回

我一头扎进去,看了看数据集的结构,想知道我能从中得到什么。在数据科学中,工作流的这一阶段被称为探索性数据分析 (EDA)。

EDA 通常发生在数据清理之后,但它不一定是一个线性过程。有时候,通过 EDA,你会意识到你需要以不同于以前的方式或者比以前更多的方式来清理某些东西。随着我们的进展,你会明白我的意思。

该数据集包含 67 个变量的 20,632 个观察值,也称为机器学习问题中的特征。在数据帧上调用.info()向我展示了每个特征的数据类型。

从这些信息中,我发现了一些无用的变量,并迅速删除了它们。在数据清理中,我们经常会处理丢失的值。如果你的数据集不是太大和多维的,你可以用 seaborn 库(sns)做一个巧妙的小把戏,用sns.heatmap(df.isnull())可视化你的数据帧是 df 的地方。通过这样做,只有空值才会以与其他值形成对比的颜色显示,如下所示。

正如我们所看到的,profile 变量是唯一剩下的包含缺失值的变量。接下来,我更详细地查看了该变量,发现由于它主要是元数据,每个元素在数据的其他地方都有自己的变量,所以我决定删除它。

在分类问题中,将你的目标变量(你试图使用机器学习算法预测的变量)可视化是必不可少的。对于此问题,0 =活动失败,1 =活动成功)。

我们可以看到,这大约是 60:40 的比例,失败多于成功。这在直觉上是有道理的。在你的整个工作流程中,重要的是要经常问自己一些事情直觉上是否有意义。

分类算法模型在数据子集上训练,并在不包含目标变量标签的数据上测试。通过测量有多少预测被正确标记,我们可以了解模型的性能。

在分类问题中,了解目标变量的分布很重要:在这个样本空间中存在偏差。看一下上面的图,可以训练的成功活动很少,所以对预测要有所保留(然后再多加一些盐,你稍后会看到)。

离群值

处理异常值也很重要。它们不仅会打乱你的绘图比例,还会弄脏你的数据,从而影响你的预测模型的性能。如果你的模型考虑了偏离平均值 5 个标准偏差的变量(假设正态分布为 T1),这是一个新的观察结果极不可能出现的值。您的模型将会被不代表可能人群的数据点扭曲/影响!

虽然我的变量很少是正态分布的,但我仍然觉得有必要采用四分位范围,并删除低于或高于平均值 3 个标准差的异常值。下面是该操作如何改变 backers_count 变量(一个活动拥有的支持者数量)。

Initial backers_count

在上面的方框图中,你甚至看不到方框。底部的线是第四个四分位数的边缘(数据的前 25%)。所以之后的一切都是极端的。这些点是单独的异常值。不仅有很多,而且一个活动就有 10 万支持者。而平均值大约是 12。这太不靠谱了。所以我应用了变换,得到了更合理的东西:

由于大多数值位于第三季度和第四季度,因此仍然存在一些右偏。但这是一个合理得多的方框图!我对其他变量也做了同样的处理,比如目标变量和承诺变量。还有 create_to_launch_days 变量,它是活动开始日期和将要开始日期之间的天数。

相关性、特征工程和特征选择

我接下来需要找到相关性。这些变量与目标变量的关联程度如何?这将为我们提供每个变量对目标的解释力/影响力的初步指示。

这很重要,原因有二:

  1. 可以说,我们将更多地了解哪些变量为模型提供了最强的解释力。
  2. 我们将查看是否有任何变量与目标变量有相似或相同的关联(也称为共线性,或者多重共线性,如果有两个以上)。

项目这一步的一个值得注意的发现是,如果一个活动受到关注,它与活动成功有 1.0 的完美正相关。这就是我们需要直觉/思维帽/严肃侦探面孔的地方。

统计学上没有什么是完美的…至少我有 95%的把握…

source: this funny site

我的猜测是,这个数据集仅仅是在聚光灯下的竞选活动中收集的。也许 Kickstarter 想分享他们最喜欢的活动的数据,而唯一成功的活动恰好在聚光灯下。因此,我们不能仅仅因为一个活动受到关注就认为它一定会成功,尽管我敢打赌它有助于获得曝光率!

此外,并非所有变量之间的关系都是线性的,因此处理这种情况并检查非线性关系相关性的一种技术是取变量的对数和平方根,并再次检查相关性。

这样做的时候,我发现活动的对数目标明显比其原始值更高度相关。对数认捐额也有所增加。

进入最终特征空间的亚军变量包括:

  • 启动至截止日期天数—启动和截止日期之间的天数)
  • staff _ pick—Kickstarter 员工是否选择了活动

拟合模型、评估性能、选择最终模型以及预测新的(完全真实的)活动

数据科学工作流程中的另一件常见事情是尝试多种模型。根据您想要完成的任务或者数据集是什么/问题是什么,有一些方法可以最大限度地减少这一阶段的工作量(例如,您不会尝试回归模型,因为这是一个分类问题)。

我们知道这是一个分类问题(活动是成功还是失败?)因为结果或目标变量采用二进制离散值(0 或 1,中间没有任何值)。

在我试过的模型上。

我使用了令人敬畏的机器学习库 sklearn 并定义了一个函数,该函数接受一个模型,将该模型拟合到特征和目标变量的训练数据集上,并返回该模型的分类报告混淆矩阵。如果你不知道这两种评估方法是什么,请随意查看这些链接,但我也会在下面简单解释一下。

Classification Report for K-Nearest-Neighbors Classifier (KNN) on Kickstarter Campaign dataset

让我们看看上面的 KNN 算法的结果。左边是类:0 和 1。顶部是精确度、回忆、f1 分数和支持。

精确度基本上意味着:对于所有被归类为积极的观察,正确的百分比是多少?这个号码。

回忆是:所有实际上是正面的观察的量度,正确分类的百分比。

F₁分数是精确度和召回率的加权调和平均值。这应该用于比较模型,而不是问题的全局准确性。

最后,支持度是数据集中每个类的实例数量。我们从 EDA 中知道失败多于成功,我们可以在这里看到这一点。

我们也有混淆矩阵,它利用了与上面相同的概念,但是显示了与百分比相对的单个实例。让我们看看下面 KNN 分类器的混淆矩阵。

Confusion Matrix for KNN Classifier

好,那么从左到右,从上到下我们有:

真正否定的次数:分类器正确猜测活动失败的次数。

误报的数量:当一个活动实际上是一个失败的活动时,分类器错误地猜测该活动成功的次数。

假阴性的数量:当一个活动实际上是成功的时候,分类器错误地猜测它是失败的次数。

真正肯定的次数:分类器正确猜测活动成功的次数。

如你所见,分类报告和混淆矩阵对于分类问题都是非常有用的评估工具。分类报告给你更多的信息,包括每一类的观察次数。但我也喜欢混淆矩阵的简单性。将它们都检查出来是很有用的,而且只有一行代码。

如题所示,我选择了梯度增强决策树分类器(它实际上有 100%的准确率,这是令人担忧的,因为在统计学和机器学习中,完美是值得警惕的)。

预测一场(完全真实的)运动的成功

好吧,这不是真的。我算出了这些数字,但这是:

gradient_boosted.predict_prob([[45, 0, 6, 1, 15000, 9.62, 6.91]])

这个调用的结果是什么?首先让我解释一下每个数字是什么。每个数字都对应于我们的功能列表,所以请仔细阅读下一段,了解它们各自的功能。出于 Pythonic 的原因,我将从第一个开始按顺序从[0]开始。

距离截止日期[0]还有 45 天的活动,不是工作人员挑选的[1],由 6 个支持者[2]支持,处于聚光灯下[3],目标为 15000 美元[4],log_goal 为 9.62 [5],log _ pledged 金额为 6.91(等于 1000 美元)[6],有 99.9%的成功机会!

结论

我希望你喜欢阅读!想看源代码?想帮我弄清楚为什么我的梯度增强分类器达到 100%吗?

查看回购!

如果你学到了新的东西,并希望将其传授给下一个学习者,可以考虑捐赠任何你觉得合适的金额,谢谢!

快乐编码,

奢侈的生活

用机器学习预测肺癌突变

原文:https://towardsdatascience.com/predicting-lung-cancer-mutations-with-machine-learning-fe2e348eaadb?source=collection_archive---------29-----------------------

最近看了一篇《自然医学》的文章,作者用机器学习预测肺癌基因突变有深度学习(链接)。他们是怎么做到的?

Photo by Ousa Chea on Unsplash

肺癌。肺癌有两个关键亚型:腺癌和鳞状细胞癌。能够区分这些亚型是极其重要的,因为每种亚型都有自己的治疗选择——针对腺癌和鳞状细胞癌的靶向治疗不同。特别是腺癌,需要分析基因突变;靶向的主要突变包括表皮生长因子受体(EGFR)、间叶淋巴瘤受体酪氨酸激酶(ALK)、肿瘤蛋白 53 (TP53)和 KRAS 突变。

[## 医疗保健的未来正在被一场巨大的技术入侵所塑造——数据驱动的投资者

过去十年,全球经济的所有部门都经历了大规模的数字颠覆,而卫生部门现在…

www.datadriveninvestor.com](https://www.datadriveninvestor.com/2018/11/02/the-future-of-healthcare-is-being-shaped-by-a-big-tech-invasion/)

识别这些突变是至关重要的,因为每个突变都有量身定制的治疗方法。例如,EGFR 和 ALK 突变已经有 FDA 批准的靶向治疗可用。当前分析肺癌组织样本的方法(组织样本的人工视觉检查)既详尽又有时不准确。此外,也很难区分腺癌和鳞状细胞癌。因此,能够准确分析肺癌组织的自动化机器学习模型将是极其有益的。

Number of whole-slide images for each class, where LUSC represents squamous cell carcinoma and LUAD represents adenocarcinoma. Image credits to Coudray et al., the original authors of the paper.

肺癌图像数据集。作者使用了来自 NCI 基因组数据共享空间的数据;他们检索了大约 1700 张全切片图像,其中 609 张为鳞状细胞癌阳性,567 张为腺癌阳性,459 张为正常。他们使用滑动窗口算法,从这些整张幻灯片图像中生成大约一百万个 512×512 像素的窗口。本质上,他们在整个组织样本上滑动一个想象的“窗口”(可以达到 100,000 像素乘 100,000 像素),并将这些窗口中的每一个用作单独的样本。然后,他们将得到的 100 万个窗口进行分割,其中 70%用作训练集,15%用于验证,15%用作测试集。

The data processing strategy used in the paper. Image credits to Coudray et al., the original authors of the paper.

用 Inception v3 进行机器学习。作者基于 Inception v3 架构 36 建立了他们的模型,该架构使用由不同内核大小的卷积和最大池层构成的 Inception 模块。你说的这个卷积是什么?我基本上说的是卷积神经网络(CNNs 这些神经网络特别擅长图像处理,而这恰好是论文试图做的事情!

迁移学习。本文还使用迁移学习进行腺癌和鳞状细胞癌的分类。但是什么是迁移学习呢?迁移学习基本上就是用别人的模式。神经网络在各层之间有权重,这些权重有助于模型的实际运行。所以如果你能得到那些精确的重量,你实际上是复制粘贴一个模型。这就是迁移学习——使用别人训练的重量,并根据自己的目的进行微调。先生偷哟模型。在这种情况下,作者使用了在 ImageNet 竞争中表现最好的权重,并在肺癌数据中对其进行了微调。当然,他们的模型还使用了其他一些超参数——损失函数(交叉熵)、学习率(0.1)、权重衰减(0.9)、动量(0.9)和优化器(RMSProp)。

Heatmap showing what the model is looking at. Image credits to Coudray et al., the original authors of the paper.

训练。因为他们有两个不同的任务(预测腺癌与鳞状细胞癌,以及预测腺癌载玻片的基因突变),他们训练了模型的多个变体。对于第一项任务,他们训练他们的模型来预测正常组织对腺癌对鳞状细胞癌。对于第二项任务,他们训练他们的模型以二元方式预测每个基因突变,而不是作为多类分类器。这意味着他们的实施允许肺癌组织的每个 512×512 斑块对一个以上的基因突变呈阳性。对于这两项任务,他们对模型进行了 50 万次迭代训练。

结果。使用了一些方法来验证他们的模型的有效性。首先,他们将他们的模型与病理学家进行比较。在独立的测试集上,被其模型错误分类的 50%的载玻片也被至少一个病理学家错误分类,并且被至少一个病理学家错误分类的 83%的载玻片被模型正确分类。这被视为该模型的表现与病理学家不相上下的证据。作者还计算了他们的模型对每个基因突变的准确性,发现该模型比猜测所有突变要好得多。

Area under Receiver Operating Characteristic scores for each mutation achieved by the model. Image credits to Coudray et al., the original authors of the paper.

这是什么意思?作者创建了一个机器学习模型,能够以合理的准确度对肺癌基因突变进行分类,并识别两种肺癌亚型之间的差异。这表明机器学习是多么强大,以及它有多么广泛的应用。该模型主要用于辅助病理学家进行诊断,从而使诊断过程保持半人工状态。这个模型还能做什么?在未来,作者将应用该模型尝试对不太常见的肺癌进行分类,包括大细胞肺癌和小细胞肺癌。他们的模型的引入还可能导致肺癌组织的高精度全自动分析,这将减少分析时间和潜在的人为错误。

也许在未来,我们将能够通过机器学习让电脑为我们诊断疾病……

我将在下面列出一些有趣的附加资源:

预测莱姆病,美国增长最快的传染病

原文:https://towardsdatascience.com/predicting-lyme-disease-the-fastest-growing-infectious-disease-in-the-u-s-4874ee1f5738?source=collection_archive---------21-----------------------

一个基于气候的分类模型来解决安静的流行病

(Source)

30 万。这是美国每年估计感染莱姆病的人数。

但更令人担忧的是,每年只有 10%的病例被报告给疾病控制和预防中心。这意味着每年可能有 270,000 人感染这种疾病,甚至可能不知道自己患有这种疾病。

Figure 1: Number of Reported Cases of Lyme Disease to the CDC from 2000–2017 (Data Source)

受影响的人数也在增加。如图 1 所示,在过去的 20 年里,莱姆病的年度病例增加了一倍多。大部分集中在东北部和中西部偏上地区,它继续每年向新的县蔓延

这都是由于黑腿蜱,也被称为鹿蜱。如果你被受感染的蜱叮咬,这种细菌伯氏疏螺旋体将进入你的血液,开始悄悄破坏你的免疫系统。

在大约一周内,70%到 80%的人会出现典型的“靶心”皮疹,称为游走性红斑。随后通常会出现典型的类似流感的症状,如发烧、头痛、发冷和全身疲劳。

这看起来没什么,但是如果不治疗,它会导致几乎每个身体系统的广泛炎症。一些症状包括使人虚弱的关节炎和关节肿胀、面瘫、心悸、气短、大脑和脊髓肿胀,导致严重的神经疼痛和记忆力丧失。

但不一定要这样。如果在早期确诊,莱姆病可以用某些抗生素完全治愈。这一切都归结于提高公众意识和教育高危地区的医疗服务提供者。

这就是机器学习的用武之地。我的想法是建立一个分类模型,可以预测美国哪些县的莱姆病发病率高。这样,疾控中心可以提前通知高危县,以便主动采取措施防止感染。

获取数据

但问题是。对莱姆病研究的资助比其他疾病低得多。因此,目前对莱姆病的研究和监测很少,公开可用的数据也极其有限。这意味着我必须完全从零开始构建我的数据集。

接受挑战。让数据争论开始吧。

如果您想了解我的代码,请查看我的 GitHub 库;为了您的方便,我按照时间顺序整理了这篇文章。

目标变量

首先,我需要设计我的目标变量。幸运的是,疾病预防控制中心有 2000 年至 2017 年每个县的报告病例数的数据。尽管如前所述,该疾病的报告严重不足,但这仍然很好地描述了该疾病的地理分布。

为了保持一致性,我决定将数据限制在 2010-2017 年,因为如图 1 所示,2009 年出现了异常大的峰值。此外,很可能许多年前的数据并不代表今天的数据,因此删除 2010 年之前的数据可能有利于模型的可预测性。

但是,这种形式的数据不能解决分类问题。一个问题是,病例的数量严重依赖于每个县有多少人居住。为了解决这个问题,我从人口普查局获得了县人口估计数据。然后,我将数据集合并在一起,并将每个县的病例数除以该县当时的估计人口数。

这导致了每人患莱姆病的比率。现在,我们有了一个指标,可以更合理地比较各个县之间的差异。

不过,还有一个步骤。对于监督学习算法,目标变量必须被标记。我发现疾控中心将高发县定义为每 10 万人中有 10 例以上确诊病例。这使我能够对数据进行分类,这样任何高于临界值的都被认为是高发县,任何低于临界值的都被认为是低发县。

特征

好的,但是我们如何获得这个模型的特征呢?我决定深入研究找出答案。

1998 年, Stafford 等人发现蜱类丰度与莱姆病感染呈强正相关。所以你会认为现在我们应该有一个完善的国家蜱类监测计划。

Figure 2: CDC Map Showing Geographic Distribution of the Black Legged Tick (Source)

嗯,不。如上所示,CDC 今天提供的只是一张定性地图,显示黑腿蜱可能在哪里。不是很有帮助。

在过去的几年里,一些州,如康涅狄格州的和北达科他州的、北达科他州的已经主动测量蜱的浓度,但是在有一个协调的国家监测计划之前,全国范围的蜱的数量数据将不会存在。

幸运的是,有一个很好的代理。 Khatchikian 等研究表明,极端冬季温度和夏冬降水等环境因素直接调节蜱类种群动态。这可能是气候对橡子影响的结果。如果气候最适合橡子生长,吃橡子的老鼠和鹿就会大量繁殖。以这些宿主动物为食的蜱也会因此而繁盛起来。

这是个好消息,因为美国国家海洋和大气管理局(NOAA)提供了大量的气候数据。

我很快发现这比预期的要难一些,因为每天有 10,000 个 API 请求的限制。假设我需要美国每个县的每个气象站 7 年的数据,那么我大概需要 56 天才能下载完所有数据。不理想。

不过,我还是解决了这个问题。幸运的是,我找到了一个允许访问每一个年度全球总结文件的页面,我把它们全部下载到了我的本地电脑上。当然,这意味着我现在拥有从 1900 年左右开始的世界上每个电台的数据,总计大约 78,000 个 CSV 文件,每个文件对应一个特定的电台。

显然,我只想要其中的一小部分。因此,我必须编写代码来手动解析每个 CSV 文件。

Figure 3: Example of NOAA Global Summary of the Year CSV Files

上面,您可以看到每个文件的一般格式(这里没有显示更多的列)。本质上,我从每个文件中获取纬度和经度,并将其用作反向地理编码器 python 库的输入,该库输出该位置的国家、州和县。如果它是一个美国站,那么我只取 2010 年到 2017 年的行,并将它们附加到一个全局数据帧中,并将它们关联的州和县作为新列。

经过 24 小时的解析,我得到了一堆乱七八糟的数据。首先,有 209 列有神秘的名字,比如“DX90”和“EMSD”。我使用提供的文档来确定每个特性的含义,然后通过仔细研究,小心翼翼地删除不相关的特性。我还重新标记了这些列,使它们有了更容易理解的名称。

Figure 4: The Percentage of Missing Values for Each of the 35 Selected Features

下一个问题是大量丢失的值,如图 4 所示。无论出于何种原因,许多台站在记录和/或报告其测量结果时并不一致。

我不想去掉列,因为那样我会丢失一些可以从气候数据中收集到的信息。因此,我选择估算所有缺失的值。

现在,这是一个我们必须非常小心的领域。我们需要放入一个能够合理代表实际值的占位符。

例如,假设缺少宾夕法尼亚州蒙哥马利县 2017 年的总降水量值。有理由相信宾夕法尼亚州所有县的平均总降水量将接近蒙哥马利县的实际降水量。这就是我对所有缺失值所做的。我在那个特定的列中找到了那个县的州的平均值,并估算了这个值。

夏威夷和华盛顿特区的一些列中没有数据,所以我必须完全删除它们。夏威夷在地理上是孤立的,没有莱姆病的发病率,所以清除它没有问题。华盛顿特区比任何一个州都要小得多,所以它对建模没有影响。

将目标与特征合并

好了,现在数据已经很好很干净了,我差不多准备好把特征和目标合并了。

由于我试图建立一个预测模型,我需要使用前一年的气候数据来预测今年的莱姆病发病率。

为此,我在包含下一年的特性数据中创建了一个新列。然后,我合并了该列上的目标数据,以创建一年的偏移。

建模准备

阶级不平衡

在任何建模之前,检查数据是否适合您将使用的算法是很重要的。在分类模型的情况下,类需要相对平衡。这意味着正面类和负面类应该有相同数量的实例。

Figure 5: Class Imbalance in Data

如上所述,该数据存在明显的阶层不平衡问题,85%的县莱姆病发病率低,只有 15%的县莱姆病发病率高。

但是为什么这是一个这样的问题呢?嗯,如果大多数县的莱姆病发病率较低,那么通过猜测所有县的发病率较低,模型将获得最佳准确性。在这种情况下,85%的时间模型是正确的。但是这对少数民族来说是以 0%的准确率为代价的。换句话说,我们的模型对于我们关心的有严重莱姆病问题的国家来说是完全无用的。

重采样技术

我尝试了许多重采样技术来抵消这一点。随机欠采样是指随机选择一小部分多数类,删除其余的数据点。这种去除导致多数阶级与少数阶级的平衡。但这也带来了丢失信息的明显负面影响,这可能会降低模型的可预测性。

然后是随机过采样,随机选择一部分不常见的类,并复制那些数据点。这也导致了平衡的班级;这里需要注意的是,拥有一堆完全相同的行可能会导致过度拟合,即模型只记住过度表示的合成数据,而对现实世界中看不见的数据失去了泛化能力。

这就是 合成少数过采样技术 ,也被称为击杀的用武之地。它不只是复制少数类的一部分,而是试图创建与原始数据相似但不完全相同的数据。在高层次上,它直接在不频繁类的数据点之间随机生成新点,从而产生完全唯一的合成数据。

最后,还有 自适应合成采样方法ADASYN ,它类似于 SMOTE,但为较难学习的少数类样本创建更多合成数据,为较容易学习的少数类样本创建较少合成数据。这将导致合成数据的分布,从而增强模型在决策边界区分类别的能力。

拆分数据

在我们看到这些技术如何执行之前,我们必须对数据进行分区。我将数据分成三部分:60%用于训练不同的模型,20%用于验证和优化最佳模型,20%作为测试集来展示最终模型的可推广性。

初始建模

现在我们到了有趣的部分。我使用不平衡学习 python 包结合 scikit-learn 的 GridSearchCV 构建了一个建模管道。这使我能够对超参数和数据转换(如缩放和重采样技术)的每种组合进行彻底的网格搜索,同时还进行 5 重交叉验证,以更可靠地测试每种组合的表现。

我在这条管道中运行的算法是:

我提供了有用文章的链接,以防您有兴趣了解每个模型的更多信息。

评估每个模型的性能

Figure 6: ROC Curves for Each Model

在分别优化了每个算法之后,我在验证集上测试了由此产生的五个模型。然后,我使用受试者工作特征曲线(也称为 ROC 曲线)绘制了它们之间的关系,如图 6 所示。ROC 曲线下面积(ROC AUC)用于比较模型。

简而言之,这一指标代表了模型在每个阈值的成本与收益,量化了模型区分不同类别的能力。最好的模型会有靠近左上角的曲线,并占据绘图中的大部分区域。

如图 6 所示,Random Forest(粉色)基本上在每个阈值上都优于其他模型,支持向量机(橙色)次之;这些模型的 ROC AUC 值分别为 0.9470.934 。1 分意味着它完美地预测了数据,所以这些是非常值得尊敬的结果。

有趣的是,随机过采样为这两种模型产生了最好的结果,这表明有时简单性甚至可以胜过最复杂的方法。

优化最佳模型

Figure 7: Distribution of the Best Random Forest Model’s Outputs

以上是最佳随机森林模型输出的预测概率分布。这些数字代表了某个县莱姆病高发的可能性。

输出向右倾斜,表明模型预测大多数县发生高发病率的概率较低。这正是我们想要看到的,尤其是在班级不平衡的情况下。

目前,模型的默认阈值是 0.5,这意味着任何概率高于 0.5 的县都将被归类为高发病率,任何低于 0.5 的县都将被归类为低发病率。但是如果这个阈值不是最优的呢?

这是我选择 ROC AUC 作为我的评估指标的主要原因,因为它独立于阈值。这意味着我可以调整阈值来优化与犯错相关的成本模型。为此,我专门留出了 20%的额外数据。

所以现在我们需要确定这些成本。这可以说是这个过程中最重要的一步,因为我们将把模型放在真实世界的背景中,而不仅仅是在数学的真空中。我必须自己估算成本,但理想情况下,与你合作的利益相关方应该能够给你这些信息。

正如我前面提到的,这种模式背后的想法是给疾病预防控制中心一个工具,以确定他们需要努力的领域。如果一个县被归类为莱姆病高发区,疾病预防控制中心将采取两项具体措施:

  1. 提高公众意识,以便从一开始就预防感染的发生
  2. 为医疗保健提供者提供教育资源,以增加早期诊断的数量。

假阳性成本

好吧,但是如果模型预测一个县将会有很高的莱姆病发病率,但实际上却没有呢?这被称为假阳性。这意味着人们会不必要地限制他们在户外的时间,甚至可能决定不去这些地方旅行。户外娱乐和旅游业的减少会损害当地经济。

为了估算这个数字,我使用了国家公园对当地社区的估计的总经济贡献,大约是每年202 亿美元。很明显,这并没有考虑到州立公园或其他任何会受到影响的服务,但目前来看这是可行的。

然后,我试图找到量化消费者销售在既定的流行病期间如何下降的研究,但没有太多可用的研究。Chou et al. 发现亚洲的 SARS 疫情给各国的消费相关产业造成了 0.2%到 1.56%的损失。SARS 是一种非常不同的疾病,但它几乎是目前唯一可以摆脱的疾病。我选择用 1% 来大致估算经济成本。

下面是我如何得出每个县的误报成本:

**Total Cost of a False Positive:**
(20.2 billion x .01) / [3,141 counties](https://www.usgs.gov/faqs/how-many-counties-are-there-united-states) = **$64,311 lost per county**

假负成本

那么假阴性的代价呢?这将是我们的模型预测,一个县不会有莱姆病的问题,当它真的发生了。

这显然是一个代价更大的错误。由于缺乏公众意识,更多的人会不必要地感染莱姆病。更多的人会患有慢性莱姆病,因为接受早期莱姆病诊断教育的医生越来越少。

张等人发现,早期莱姆病患者平均每年花费1310美元,而一个慢性莱姆病患者每年花费16199美元。那要贵 12 倍以上。这是在 2000 年完成的,所以我必须考虑到 1.41 的通货膨胀因素。

此外, Aucott 等人估计,每年 64% 的新增莱姆病病例为早期,而 36% 为慢性。如前所述,每年大约有 30 万 的这些新增病例。

此外,为了简化我的计算,我假设 CDC 的干预会完全有效,导致所有新的莱姆病病例都被及早发现。

要找出假阴性的成本,我只需要找出疾病控制中心不干预和干预时的成本差异。以下是我所有的计算:

**Number of Annual Early and Chronic Cases:**
.64 x 300,000 = 192,000 cases of early Lyme disease
.36 x 300,000 = 108,000 cases of chronic Lyme disease**Inflation-Adjusted Cost of Early vs Chronic:**
$1,310 x 1.49 = $1,952 per patient with early Lyme disease
$16,199 x 1.49 = $24,137 per patient with chronic Lyme disease**Average Cost for High Incidence County Without CDC Intervention:**
192,000 early cases / 3,141 counties = 61 early cases per county
61 early cases * $1,952 = $119,065108,000 chronic cases / 3,141 counties = 34 chronic cases per county
34 chronic cases * $24,137 = $820,641$119,065 + $820,641 = **$939,706****Average Cost for High Incidence County With CDC Intervention:** 300,000 early cases / 3,141 counties = 95 early cases per county
95 early cases * $1,952 = **$185,430**------------------------------**Total Cost of a False Negative:** $939,706 - $185,430 = **$754,276 lost per county**

寻找最佳阈值

最后,我计算了假阴性和阳性之间的成本比率:

**Ratio of False Negative vs False Positive:
$754,276 / $64,311 = 11.73**

据此,假阴性的代价几乎是假阳性的 12 倍。然后我将这个值插入我的代码,发现最佳阈值是 0.17 。这意味着任何高于 0.17 的县都将被列为高发病率,任何低于 0.17 的县都将是低发病率。回头参考图 7 来观察截止位置。

决赛成绩

为了证明我的最终模型的可推广性,我在之前使用的所有 80%的数据(60%的训练集和 20%的阈值优化集)上训练该模型,然后从一开始就用完全看不见的 20%测试集进行测试。

结果甚至比训练期间更好,ROC AUC 为 0.961

Figure 8: Confusion Matrix of Final Model’s Outputs

此外,该模型的召回率很高,约为 0.949 ,这意味着它对约 95%的高发县进行了正确分类。如图 8 所示,在 554 个高发县中,它只有 28 个假阴性。

这当然是以 0.503 的精度为代价的。这意味着,模型预测为高发的县,实际上只有 50%左右是高发县。如图 8 所示,假阳性和真阳性大致相同。

因为假阴性比假阳性代价高得多,这些结果完全有意义。为了正确分类尽可能多的高发县,模型必须不精确。

作为一名医疗保健专业人员,也作为一个认识四名受这种疾病影响的人的人,我受到了启发,机器学习可以成功地用于帮助抗击莱姆病。这是数据科学如何被用来让世界变得更美好的又一个例子,这也是我如此热爱它的原因。

当你在户外的时候,请检查你自己是否有虱子。帮助传播消息。

预测 TED 演讲中的男性和女性演讲者

原文:https://towardsdatascience.com/predicting-male-and-female-speakers-in-ted-talks-a2deb0ae84b?source=collection_archive---------22-----------------------

自然语言处理中使用朴素贝叶斯和处理类别不平衡

Photo by Samuel Pereira on Unsplash

TED 演讲已经成为非常受欢迎的会议。它们被视为思想的麦加,吸引了最高级的演讲者,如比尔·盖茨、阿尔·戈尔或斯蒂芬·霍金。截至 2015 年,TED 和它的姐妹 TEDx 已经在互联网上发布了超过 2000 篇长达 18 分钟的免费演讲。

如果你能建立一个模型来预测 TED 演讲的演讲者是男是女,会怎么样?有点像预测一本书的作者,有没有按照某个特征对文档进行分类的技术?

是啊!我告诉你怎么做。

使用一个包含 1060 场 TED 演讲记录的数据集,我应用了一个朴素贝叶斯分类器来预测演讲是由女性还是男性演讲者发表的。

准备

创建文档-特征-矩阵

在创建文档-特征-矩阵之前,我快速浏览了一下我感兴趣的主要变量的分布:性别。下面的代码显示数据集是有偏差的:大部分说话者是男性。为什么这很重要?当我们应用朴素贝叶斯分类器时,我们需要仔细考虑这一点,因为这可能会严重影响算法的性能!

class_distribution <- TedTalks %>% group_by(Gender) %>% summarize(class_count=n())
print(head(class_distribution))

但是我们暂时就这样吧。

创建文档特征矩阵的基本步骤是首先创建定义文本字段的语料库,然后应用基本的预处理技术,包括去除停用词和标记化。我还将行随机化,以确保 train、dev 和 test set 之间的划分是完全随机的。

#Randomizing rows
set.seed(1628)
TedTalks_imbalanced <- TedTalks[sample(row.names (TedTalks)), ]# Creating a corpus
TedTalks.c <- corpus(TedTalks, text_field = "transcript")# Pre-processing
stop_words = stopwords("english")
tok.all <- tokens(TedTalks.c, what="word",
              remove_symbols = TRUE,
              remove_punct = TRUE,
              remove_numbers = TRUE,
              remove_url= TRUE,
              remove_hyphens = FALSE,
              verbose = TRUE,
              remove_twitter = TRUE,
              include_docvars = TRUE)# Creating a document feature matrix
TedTalks_dfm <- dfm(tok.all,
                    tolower= TRUE,
                    remove=stop_words,
                    verbose=TRUE,
                    include_docvars = TRUE)

就绪

测试你的第一个模型

我现在可以从我的第一个模型开始。注意,我在这里主要依赖 r 中 quanteda 包中的命令。通常,行的随机化应该确保您在三个不同的数据集中保持大致相同的类别分布(即男性与女性)。但是一定要检查是否真的是这样。

# Dividing dfm into train, dev and test set.  
TedTalks_dfm_train <- TedTalks_dfm[1:635, ] #60% of total observations
TedTalks_dfm_dev <- TedTalks_dfm[636:847, ] #20% of total observations
TedTalks_dfm_test <- TedTalks_dfm[848:1060, ] #20% of total observationsTedTalks_train_labels <- TedTalks_imbalanced[1:635, ]$Gender
TedTalks_dev_labels <- TedTalks_imbalanced[636:847, ]$Gender
TedTalks_test_labels <- TedTalks_imbalanced[848:1060, ]$Gender

现在,我训练并运行我的朴素贝叶斯分类器。第一个模型做好了!

nb_model <- textmodel_nb(TedTalks_dfm_train, docvars(TedTalks_dfm_train, "Gender"))
summary(nb_model)dfmat_matched <- dfm_match(TedTalks_dfm_dev, features = featnames(TedTalks_dfm_train))actual_class <- docvars(dfmat_matched, "Gender")
predicted_class <- predict(nb_model, newdata = dfmat_matched)tab_class <- table(actual_class, predicted_class)
tab_class

评估

为了评估我的模型的性能,我运行了如下所示的混淆矩阵。考虑以下指标很重要:准确度、预见度、召回率和 F1。随着 0.6557 的精度水平,可以说我的模型表现良好。然而,在我有偏见的数据集的情况下(还记得数据集中男性说话者比女性说话者多吗?),将准确性水平作为模型性能的衡量标准可能会产生误导。为什么?因为您的模型将在大多数情况下预测主导类,并且仍然实现相对较高的整体准确性。

这就是为什么最好也看看其他指标。

0.083 的精度表明我的模型有大量的假阳性。同样,0.461 的召回也表明我的模型有一些假阴性。0.141 的 F1 的水平也很低,并且表明模型的精确度较低。因此,这些指标表明,我的模型实际上并不能很好地预测 TED 演讲者是男是女。是时候调整一下了!

confusionMatrix(tab_class, mode="everything")

一组

调整你的模型

如果我纠正我的数据集中的性别不平衡会发生什么?为了做到这一点,我依赖于一种常见的采样方法。基本上,我试图将不平衡的数据转换成平衡的分布,这样就有希望改进我的模型。通过改变原始数据集的大小并提供相同比例的平衡来进行修改。

在 R 中,ROSE 和 DMwR 等包帮助我们快速执行采样策略。在这里,我使用玫瑰包装。

作为第一步,我从过采样开始:我将女性数量与男性数量置于相同的水平(即各 795)。换句话说,我已经指示 R 对少数民族女性类进行过采样,直到它达到与男性类相同的水平,因此整个集合应该包括 1590 个观察值。

在第二步中,我执行欠采样:我降低我的多数类(男性)的观察数量,使其等于少数类(女性,即每个 265)。换句话说,我已经指示 R 对男性类进行欠采样,直到它达到 265,因此整个集合减少到 530 个观察值。

最后一步,我通过结合欠采样和过采样方法来实现平衡。这可以使用方法= both 来实现。在这种情况下,少数类在替换的情况下被过采样,而多数类在没有替换的情况下被欠采样。这个平衡的数据集被认为是最适合我的模型的,因为简单的过采样会导致大量的重复观察,而简单的欠采样会剥夺数据集的重要信息。

library(ROSE)# first step: oversampling
data_balanced_over <- ovun.sample(Gender ~ ., data = TedTalks, method = "over",N = 1590)$data
table(data_balanced_over$Gender)# second step: undersampling
data_balanced_under <- ovun.sample(Gender ~ ., data = TedTalks, method = "under",N = 530)$data
table(data_balanced_under$Gender)# third step: both
TedTalks_balanced <- ovun.sample(Gender ~ ., data = TedTalks, method = "both",p=0.5, N=1060, seed=1)$data
table(TedTalks_balanced$Gender)

因此,我知道我有一个新的平衡数据集(TedTalks_balanced ),我为朴素贝叶斯分类器准备的方式与我之前处理不平衡数据集的方式相同:我确保随机化行,创建语料库,运行基本的预处理技术,并随后创建新的 dfm。

与我的第一个模型相似,我也在训练集(60%的观察值)、开发测试(20%的观察值)和测试集(20%的观察值)之间划分了平衡数据集。

然后,我应用朴素贝叶斯分类器,如下面的代码所示。

nb_model2 <- textmodel_nb(TedTalks_dfm_train2, docvars(TedTalks_dfm_train2, "Gender"))
summary(nb_model2)dfmat_matched2 <- dfm_match(TedTalks_dfm_dev2, features = featnames(TedTalks_dfm_train2))actual_class2 <- docvars(dfmat_matched2, "Gender")
predicted_class2 <- predict(nb_model2, newdata = dfmat_matched2)tab_class2 <- table(actual_class2, predicted_class2)

评估

毫不奇怪,我新调整的模型在评估其性能时表现得更好。精度的水平更高,但最重要的是,我有高水平的精度召回F1 ,这表明误报或漏报的数量很少。因此,通过使用 ROSE 软件包平衡数据集,我能够显著提高算法的性能,预测 TED 演讲是由男性还是女性演讲者进行的。

confusionMatrix(tab_class2, mode="everything")

在您的测试集上运行它

最后,我在测试集上测试我的模型。混淆矩阵的结果表明,该模型在预测 TED Talk 演讲者的性别方面表现良好。我在开发测试中有非常相似的精确度精确度回忆F1 值。

dfmat_matched3 <- dfm_match(TedTalks_dfm_test2, features = featnames(TedTalks_dfm_train2))
actual_class3 <- docvars(dfmat_matched3, "Gender")
predicted_class3 <- predict(nb_model2, newdata = dfmat_matched3)tab_class3 <- table(actual_class3, predicted_class3)
tab_class3confusionMatrix(tab_class3, mode="everything")

就是这样!在自然语言处理中创建一个朴素贝叶斯分类器很容易,它允许你预测一些很酷的事情,比如一个 TED 演讲者的性别。

只需确保检查您感兴趣的变量的分布,因为偏差会显著影响您模型的性能。此外,在评估你的模型时,准确性不应该作为唯一的衡量标准,因为它有时会误导人。相反,应始终包括其他指标,如精度召回F1

我定期撰写关于数据科学和自然语言处理的文章——如果您想了解我的最新文章,请随时关注我的文章**

用神经网络和随机森林预测微量营养素(上)

原文:https://towardsdatascience.com/predicting-micronutrients-using-neural-networks-and-random-forest-part-1-83a1469766d7?source=collection_archive---------6-----------------------

联合国儿童基金会希望你利用机器学习的力量,帮助他们预测食物中的重要营养成分。

Photo by Dose Juice on Unsplash

欢迎大家!今天,我们将实施机器学习算法来解决世界上最困难的问题之一——世界饥饿。

我决定将本教程分成多个部分,以确保我们不会错过任何重要的细节。

在这个系列中,我们将解决一个回归问题,并根据其他微量营养素和常量营养素预测重要的微量营养素,微量营养素是健康大脑、骨骼和身体的基石。

我们将使用编程语言 Python 和机器学习中两个常用的依赖项来构建我们的模型,Keras 和 Scikit-Learn。

在第一部分中,我们将更多地讨论数据集和数据清理,而不是构建模型本身。

数据清理是该过程的一个关键部分,因为提供给我们的数据并不总是准备好输入到模型中。

事不宜迟,我们开始吧。

概述

这篇博文包括以下内容:

  • 动机
  • 先决条件
  • 介绍
  • 资料组
  • 数据清理

动机

在这篇博文中,我决定报道一些与任务稍有不同的内容。大多数时候,机器学习教程都涉及单变量回归,在预测过程中只有一个结果变量。

然而,这一次,我们将使用一些流行的机器学习算法来解决一个多元回归问题,其中结果变量不止一个。

我认为这很重要,因为许多现实世界的问题需要考虑多个输出变量之间的关系。

注意——深度学习的话题将在我们使用神经网络进行回归时出现。大多数博客帖子都会比较深度神经网络如何显示出比其他算法更好的准确性。

然而,在这个系列中,我将涵盖一个场景,其中深度神经网络实际上比其他机器学习架构表现更差。

此外,我将介绍一些有用的工具,您可以利用它们来构建机器学习算法。

先决条件

这篇博客文章假设读者对流行的机器学习算法(如线性回归和决策树)和技术术语(如过拟合、隐藏层等)有所熟悉。,).

如果你以前没有任何知识,或者需要一些知识来更新我上面提到的一些东西,这里有一些我推荐的资源:

介绍

联合国儿童基金会(UNICEF)有一个请求,希望您加入他们的数据科学团队,解决一个在对抗世界饥饿的斗争中起着至关重要作用的重要问题——微量营养素(也称为维生素和矿物质)。

Photo by Perry Grone on Unsplash

你的工作是创建一个机器学习模型,可以根据其他营养物质(蛋白质、碳水化合物等)对这些缺乏进行预测。,).

然而,关于这项工作,他们忘了说一件事,他们没有数据(这对于像联合国儿童基金会这样大的组织来说是极不可能的;但为了这篇博文,姑且说他们没有)。

因此,您需要找到获取数据的方法来构建模型。

但是怎么做呢?从哪里来的?

资料组

幸运的是,历史上最伟大的创造之一,互联网,充满了许多开放的数据集,我们可以下载并用来训练我们的模型。

在这种情况下,我们将使用来自美国农业部农业研究服务局的美国农业部国家营养数据库作为标准参考(SR)

在本教程中,我们将使用缩写的 excel 文件,您可以在这里下载。

该数据集包含美国食物成分数据的主要来源,有超过 8000 个数据点,每种食物中有超过 50 种微量营养素(如碳水化合物、蛋白质等)。,).

现在我们得到了数据,首先要做的是数据清洗。

数据清理

自然,数据并不总是以干净的 CSV 或文本文件的形式出现,可以随时输入到机器学习模型中。

我们从大自然中获取的大部分食物在我们从超市购买之前都要先进行清洗和加工。数据也是如此。

但首先,让我们看看我们的数据。由于数据是 Excel 文件的形式,我们将使用 Pandas read_excel函数打开文件。

import pandas as pdpath_to_data = "datasets/ABBREV.xlsx" # Path to the Excel file
dataset = pd.read_excel(path_to_data)
dataset.head()

If you run on the code on Jupyter Notebook, then the table should look like this (might look different if you run a python script on terminal)

dataset.head()显示数据集的前 5 行。通过这样做,我们可以对我们的数据有所了解。

正如我们所见,该表包含了每种食物的营养成分数据(如黄油、奶酪等。,).

各栏包含食物中每种微量营养素和常量营养素(蛋白质、碳水化合物等)的名称。,).

要查看所有变量的数据,可以运行下面的代码。

dataset.columns# Output
Index(['NDB_No', 'Shrt_Desc', 'Water_(g)', 'Energ_Kcal', 'Protein_(g)',
       'Lipid_Tot_(g)', 'Ash_(g)', 'Carbohydrt_(g)', 'Fiber_TD_(g)',
       'Sugar_Tot_(g)', 'Calcium_(mg)', 'Iron_(mg)', 'Magnesium_(mg)',
       'Phosphorus_(mg)', 'Potassium_(mg)', 'Sodium_(mg)', 'Zinc_(mg)',
       'Copper_mg)', 'Manganese_(mg)', 'Selenium_(µg)', 'Vit_C_(mg)',
       'Thiamin_(mg)', 'Riboflavin_(mg)', 'Niacin_(mg)', 'Panto_Acid_mg)',
       'Vit_B6_(mg)', 'Folate_Tot_(µg)', 'Folic_Acid_(µg)', 'Food_Folate_(µg)',
       'Folate_DFE_(µg)', 'Choline_Tot_ (mg)', 'Vit_B12_(µg)', 'Vit_A_IU',
       'Vit_A_RAE', 'Retinol_(µg)', 'Alpha_Carot_(µg)', 'Beta_Carot_(µg)',
       'Beta_Crypt_(µg)', 'Lycopene_(µg)', 'Lut+Zea_ (µg)', 'Vit_E_(mg)',
       'Vit_D_µg', 'Vit_D_IU', 'Vit_K_(µg)', 'FA_Sat_(g)', 'FA_Mono_(g)',
       'FA_Poly_(g)', 'Cholestrl_(mg)', 'GmWt_1', 'GmWt_Desc1', 'GmWt_2',
       'GmWt_Desc2', 'Refuse_Pct'],
      dtype='object')

在我们继续之前,我总是喜欢检查每一列的数据类型。这一点很重要,因为在训练中只使用 float 类型。

我不会详细说明所有这些变量的含义。但是如果您有兴趣了解,可以在下载数据集时查看 zip 文件中包含的文档。

幸运的是,Pandas 的DataFrame类有一个属性dtypes,它返回一个包含每一列数据类型的Series

dataset.dtypes#OutputNDB_No                 int64
Shrt_Desc             object
Water_(g)            float64
Energ_Kcal             int64
Protein_(g)          float64
Lipid_Tot_(g)        float64
Ash_(g)              float64
Carbohydrt_(g)       float64
Fiber_TD_(g)         float64
Sugar_Tot_(g)        float64
Calcium_(mg)         float64
Iron_(mg)            float64
Magnesium_(mg)       float64
Phosphorus_(mg)      float64
Potassium_(mg)       float64
Sodium_(mg)          float64
Zinc_(mg)            float64
Copper_mg)           float64
Manganese_(mg)       float64
Selenium_(µg)        float64
Vit_C_(mg)           float64
Thiamin_(mg)         float64
Riboflavin_(mg)      float64
Niacin_(mg)          float64
Panto_Acid_mg)       float64
Vit_B6_(mg)          float64
Folate_Tot_(µg)      float64
Folic_Acid_(µg)      float64
Food_Folate_(µg)     float64
Folate_DFE_(µg)      float64
Choline_Tot_ (mg)    float64
Vit_B12_(µg)         float64
Vit_A_IU             float64
Vit_A_RAE            float64
Retinol_(µg)         float64
Alpha_Carot_(µg)     float64
Beta_Carot_(µg)      float64
Beta_Crypt_(µg)      float64
Lycopene_(µg)        float64
Lut+Zea_ (µg)        float64
Vit_E_(mg)           float64
Vit_D_µg             float64
Vit_D_IU             float64
Vit_K_(µg)           float64
FA_Sat_(g)           float64
FA_Mono_(g)          float64
FA_Poly_(g)          float64
Cholestrl_(mg)       float64
GmWt_1               float64
GmWt_Desc1            object
GmWt_2               float64
GmWt_Desc2            object
Refuse_Pct           float64
dtype: object

啊哈,看那个。似乎除了NDB_NoShrt_DescEnergy_KcalGmWt_Desc1GmWt_Desc2之外,所有的列都是浮动的。

此外,您可以看到其中一些列的数据类型为object。这是因为它们包含每个行条目的文本。

出于这个原因,熊猫会认为它们是分类数据。我想使用术语假分类变量,因为它们实际上不是分类数据,只是包含文本。

为了使数据清理过程更加简单,让我们将这些错误分类变量与另一个数值变量分开。

由于熊猫不提供这样的功能,我们必须手动完成。

tot_cols = dataset.columns
num_cols = dataset._get_numeric_data().columns
cat_cols = list(set(tot_cols)-set(num_cols))# Output of cat_cols['GmWt_Desc1', 'GmWt_Desc2', 'Shrt_Desc']

太好了!现在我们可以开始第一步了。

首先注意,NBD_No包含每种食物的索引,Shrt_Desc包含食物本身的名称。在这种情况下,我们可以删除这些变量,因为它们不会用于构建模型。

drop_cols = ['NDB_No', 'Shrt_Desc']
dataset = dataset.drop(drop_cols, axis=1)

接下来,我们需要为我们的培训设置标签。因为我们正在处理一个多元回归问题,这意味着输出变量将不止一个。

换句话说,这意味着我们有多个标签。

正如我之前提到的,联合国儿童基金会希望你对某些食物中的重要微量营养素做出预测。但是正如我们在专栏中看到的,他们有很多。

我们怎么知道哪一个会成为我们的标签呢?

这个问题没有明确的答案,这取决于你要解决的问题。由于我们不太了解营养(我想),我们可以查看联合国儿童基金会的官方网站。

根据联合国儿童基金会的官方网站,微量营养素主要有七种缺乏:碘、维生素 A、铁、锌、钙、维生素 D 和叶酸。

除了碘,所有这些都在我们的数据集中提供。但是在与我们团队中的主管讨论之后,他说可以进行构建模型的第一次迭代。

Just to give a visualization of what we are trying to do.

现在,我们只是将结果变量的名称放在一个列表中。在 Python 中,我们可以通过执行以下操作来做到这一点。

labels = ['Calcium_(mg)', 'Iron_(mg)','Zinc_(mg)', 'Vit_A_IU', 'Vit_D_IU', 'Folate_Tot_(µg)']

好了,现在我们需要回到那些错误的分类变量。因为我们已经去掉了NDB_NoShrt_Desc,我们需要做的就是如何处理GmWt_Desc1GmWt_Desc2

现在你可以做两件事。

首先,你可以放弃它们,因为它们只是对GmWt_1GmWt_2变量的描述。第二种方法是清理文本,只包含每一行条目的编号。

我会推荐第一种选择,因为这些变量很可能不会对模型的性能产生任何重大影响。然而,为了这篇博文,我将把他们包括在内。

为什么?我会解释的。

互联网上的数据并不总是干净的。他们可以有多余的或喋喋不休的话。因此,以某种方式清除这些文本并用数字表示它们是一个好习惯。

计算机只能计算数字,不能计算文本类型的数据。

因此,我们需要删除GmWt_Desc1GmWt_Desc2中的文本。在这种情况下,它包括括号内的任何内容。

幸运的是,我已经写了一个函数来做这件事。

def clean_text(data):

    # If value is NaN we return the same value for cleansing later on
    if isinstance(data, str):

        # Remove any punctuations including words within parentheses
        clean = re.sub(r'\([^)]*\)', '', data)

        # Get numbers only
        clean = re.sub("\D","",clean)

        return clean

    else:
        return data

我们可以使用这个函数来删除括号中的标点符号和单词。此外,我们希望得到句子中的数字。

例如,如果行中的条目是“1 pat,(1" sq,1/3" high)”,那么我们只留下数字 1。

熊猫让我们简单地用属性apply来说明。这就是假分类变量列表派上用场的地方。

# Remove text in GmWt_Desc1
dataset[cat_cols[0]] = dataset[cat_cols[0]].apply(clean_text)# Remove text in GmWt_Desc2
dataset[cat_cols[1]] = dataset[cat_cols[1]].apply(clean_text)

太棒了。看来我们完了,对吧?不完全是。在准备好输入数据之前,我们需要做几件事情。

如果你回到clean_text函数,有一个注释说“如果值是 NaN,我们返回相同的值用于以后的清理。”

清理数据集的另一个常见步骤是处理缺失数据。这很常见,因为许多可以在互联网上找到的数据集并没有完全填满。

那些丢失的数据被替换为 NaN 值。与文本一样,计算机不能计算 NaN 值。

让我们看看数据集的每一列是否有任何缺失值。

dataset.isnull().sum()# OutputWater_(g)               1
Energ_Kcal              0
Protein_(g)             0
Lipid_Tot_(g)           0
Ash_(g)               325
Carbohydrt_(g)          0
Fiber_TD_(g)          594
Sugar_Tot_(g)        1832
Calcium_(mg)          348
Iron_(mg)             144
Magnesium_(mg)        739
Phosphorus_(mg)       579
Potassium_(mg)        426
Sodium_(mg)            83
Zinc_(mg)             706
Copper_mg)           1257
Manganese_(mg)       2160
Selenium_(µg)        1700
Vit_C_(mg)            818
Thiamin_(mg)          634
Riboflavin_(mg)       616
Niacin_(mg)           637
Panto_Acid_mg)       2242
Vit_B6_(mg)           905
Folate_Tot_(µg)      1261
Folic_Acid_(µg)      2039
Food_Folate_(µg)     1768
Folate_DFE_(µg)      2057
Choline_Tot_ (mg)    4016
Vit_B12_(µg)         1193
Vit_A_IU              711
Vit_A_RAE            1535
Retinol_(µg)         1806
Alpha_Carot_(µg)     3258
Beta_Carot_(µg)      3162
Beta_Crypt_(µg)      3270
Lycopene_(µg)        3292
Lut+Zea_ (µg)        3315
Vit_E_(mg)           2889
Vit_D_µg             3262
Vit_D_IU             3211
Vit_K_(µg)           3563
FA_Sat_(g)            349
FA_Mono_(g)           666
FA_Poly_(g)           665
Cholestrl_(mg)        410
GmWt_1                300
GmWt_Desc1            299
GmWt_2               3965
GmWt_Desc2           3965
Refuse_Pct             50
dtype: int64

哇,看来我们的数据集中有很多缺失的数据。

同样,这里我们可以做两件事。第一种方法是删除任何列中包含缺失数据的行。这意味着如果一个列有一个 NaN 值,我们会立即删除它们。

第二个选择是做输入,这意味着替换丢失的值。这个选项更可取,因为我们不会丢弃在模型训练期间可能很重要的其他数据。

问题是,我们用什么来代替它呢?

没有人能回答这个问题。这取决于你面临的问题和你使用的模型。一些模型对于缺失数据是稳健的,而另一些则不能处理它们。

例如,稍后,我们将使用一种流行的机器学习算法,称为随机森林。这个模型有自己处理缺失数据的方式。

但是,我们不会在本系列中讨论这个问题。但是如果你有兴趣了解的话,这个视频会很有帮助。

为了简单起见,我们将使用一种最流行的技术来替换丢失的数据。那就是通过使用中间值。

为什么?这是对另一篇博文的解释。与此同时,让我们继续前进。

为了获得每一列的中间值,我们可以使用由DataFrame类提供的属性median()。然后,我们可以使用fillna属性替换丢失的值。

get_median = dataset.median()
dataset = dataset.fillna(get_median)

另一种方法是使用 Scikit-learn 的[SimpleImputer](https://scikit-learn.org/stable/modules/generated/sklearn.impute.SimpleImputer.html)

现在我们需要检查数据集中是否没有丢失的数据值。

dataset.isna().values.any()# Output
False

看起来所有丢失的值都不见了。

现在,到了最后一步。由于我们正在处理一个监督学习任务,我们需要将数据集分成输入数据和标签。

但在此之前,我想快速提一下。

如果你注意我们的变量列表,对于维生素 A 和维生素 D,我们有两个变量分别代表它们。

维生素 A 有Vit_A_IUVit_A_RAE,维生素 D 有Vit_D_IUVit_D_µg

IU 代表国际标准。我不打算解释什么是 IU,但是如果你想知道更多,你可以在这里查看。

我决定选择Vit_A_IUVit_D_IU,因为我试图避免每个变量的单位差异有太大的变化。

现在我们已经解决了这个问题,让我们继续分离输入数据和标签。

还记得之前最初准备的标签名称列表吗?这就是它派上用场的地方。

# List of names of the columns that we will drop for the input data
drop_cols = ['Vit_D_µg', 'Vit_A_RAE'] + labels# Get the data for labels
y = dataset[labels].values# Get the data for inputs
X = dataset.drop(drop_cols, axis=1).values

属性将数据从一个对象转换成 Numpy 数组。

厉害!我们已经清理完数据了

作为数据科学团队的一员,我们正在取得进步。既然数据已经去掉了文本和缺失值,我们就可以开始构建模型了。

请加入本系列的第 2 部分,我们将通过使用 Scikit-learn 创建一个机器学习模型,最终在战胜世界饥饿的斗争中迈出又一步。

我希望这篇博文对你有所帮助,并且迫不及待地想在第 2 部分见到你。在那之前,干杯!

关于我:

一名印尼留学生,目前在韩国留学,对人工智能世界有着浓厚的兴趣。一家名为 88spares 的初创公司的后端开发人员。相信不断的学习。我想回馈在我追求知识的过程中贡献良多的人工智能。当我不写代码时,我喜欢阅读航天工业的最新趋势,并为我大学的美式足球队效力。

用神经网络和随机森林预测微量营养素(下)

原文:https://towardsdatascience.com/predicting-micronutrients-using-neural-networks-and-random-forest-part-2-1bd85497fdf1?source=collection_archive---------25-----------------------

联合国儿童基金会希望你利用机器学习的力量,帮助他们预测食物中的重要营养成分。

Photo by Julien R on Unsplash

欢迎回来!很高兴你能加入我的“使用神经网络和随机森林预测微量营养素”系列的第 2 部分。

在之前的博文中,我提到了联合国儿童基金会邀请我们加入他们的数据科学团队,通过使用机器学习的力量来预测某些食物中的微量营养素成分。

此外,我们还对来自美国农业部的数据集进行了数据清理。

现在,我们将进入几乎每个数据科学项目中最令人兴奋的部分之一,构建机器学习模型。

你可能从标题本身就能看出,我们将使用随机森林和神经网络来构建模型。但是对于第 2 部分,我们还没有进展到神经网络。

事实上,建议您在从事数据科学或机器学习项目时,从使用简单的模型开始,逐步转向更复杂的模型。

好的,我们在联合国儿童基金会数据科学团队的同事正在会议室等我们,所以让我们开始建立机器学习模型吧。

概观

这篇博文包含以下内容:

  • 介绍
  • 机器学习简介
  • 多元线性回归:线性回归和多元回归
  • 随机森林:决策树和引导

机器学习简介

在本系列的第一部分中,我提到了一些资料,这些资料是任何人能够更好地理解我在本系列中所谈论的内容的先决条件。

其中一个是关于“机器学习导论”然而,我决定我们应该做一个小的介绍来保持这篇博文的流畅。

但是如果你想获得更好的理解,我强烈推荐你阅读这篇由维哈尔·鞍马写的博客。

什么是机器学习?

我们将不得不对这个问题有更多的批判,因为已经有很多尝试来定义什么是机器学习,什么不是机器学习。这里有几个例子。

机器学习:在没有明确编程的情况下赋予计算机学习能力的研究领域。

阿瑟·塞缪尔(1959)

20 世纪 50 年代,亚瑟·塞缪尔(Arthur Samuel)编写了一个程序,让计算机通过与自己对弈来学习下跳棋。最后连 Samul 自己都在跳棋中输给了自己的程序。

第二个定义可能是机器学习社区中最流行和最知名的。

适定的学习问题:如果一个计算机程序在 T 上的表现,如 P 所测量的,随着经验 E 的增加而提高,那么就说它从关于某个任务 T 和某个表现测量 P 的经验 E 中学习。

汤姆·米切尔(1998)

让我们以目前的任务为例,预测某些食物中的微量营养素。

在这种情况下,E 是每种食物中营养成分数量的记录,T 是根据其他营养成分预测微量营养素的任务,P 是预测值与实际值的差距。

E: the records of the amount of nutrient composition within each food. T: the task of predicting the micronutrients based on other nutrients. P: how far are the predicted values from the ground truth.

在通常的实践中,当涉及到任务时,有三种不同类型的机器学习算法:

  • 监督学习。当我们有输入变量和输出变量进行训练时,这就是我们实际在做的事情。
  • 无监督学习。它与监督学习基本相同,但没有输出变量。
  • 强化学习。这种类型的机器学习处理没有输入或输出变量的任务。相反,计算机程序将与动态环境交互,通过试错来实现某个目标。

当谈到监督学习时,我们实际上试图学习一个将输入变量映射到输出变量的函数。

这个函数的形式是未知的,我们的工作是评估各种机器学习算法,并观察哪种算法在逼近底层函数方面更好。

我们认为函数的另一种方式就像一个永远无法打开的魔法盒子。但是这个盒子可以接受输入,并根据输入做出预测。

例如,如果你把一封来自可疑未知电子邮件放入邮箱,它可以 100%正确地告诉你这是一封垃圾邮件还是你的一个朋友刚刚创建了一封新邮件。

既然我们看不到盒子里面是什么,那么也许我们可以创造出类似于那个盒子的东西。这个东西的形式不一定是盒子。它可以是球形、棱柱形或任何其他形状。

(假设我们以一个球结束)

最重要的是我们的球可以做出和魔盒预测一样好的近似。这意味着它可以正确预测 99%而不是 100%。

多元线性回归

正如我之前提到的,当处理一个数据科学或机器学习项目时,建议我们从更简单的模型开始。

在这里,我们将介绍多元线性回归并使用 Scikit-learn 实现它。

线性回归

线性回归是预测建模中使用最广泛的机器学习算法之一。它也通常在监督学习场景中实现。

它被称为线性模型,因为它推断出我们的输入和输出变量之间的线性关系。基本上,它给了我们一个类似下图的线性方程

注意——当我们谈论线性回归时,输入和输出变量通常分别被称为自变量和因变量。我将交替使用这两个术语。这也适用于系数、权重和参数。

在上面的等式中,我们有一个因变量,y,和一个自变量,x。此外,我们还有两个β,它们是线性等式的系数

ε是随机采样产生的误差项

第一个系数(beta null)是截距项,而第二个系数(beta one)是分配给变量的权重。权重表示变量的重要性。

这意味着如果 beta one 是 2.5,那么我们的变量(x one)的单位也会增加 2.5。

请注意,上面的等式只有一个变量和一个系数。当我们有一个以上的变量时,假设有 n 个变量,那么我们需要在方程中包含 n 个系数。

当我们使用多变量线性回归建立预测模型时,它通常被称为多变量线性回归

Based on the preprocessing step that we’ve done in the previous blog post, we have 43 input variables for training. That means we will have 44 coefficients (43 for each variable plus the intercept term).

线性模型的目标是通过最小化残差平方和(RSS)来找到最佳学习数据的参数。

RSS 是真实值 y 和模型预测值ŷ(发音为 y-hat)之间的平方差总和,表示估计值。

在这篇博文中,我们不打算讨论 RSS 的细节。对详细的解释和推导感兴趣的可以查看这里

好吧,让我们在这里等一会儿。你能发现我们的讨论遗漏了什么吗?与我们提议的任务特别相关的东西?

如果你在想“但是我们有不止一个输出标签”或者“为什么线性方程中只有一个 y ?”那你应该升职了我的朋友(开个玩笑,也许一颗糖就够了?)

当我们必须预测一个输出变量的值时,这被称为单变量回归。事实上,这在你能在网上找到的大多数线性回归教程中都很常见。

但是当我们有不止一个输出变量时该怎么办呢?

多变量回归

在我们必须对多个输出变量进行预测的情况下,这被称为多元回归,我们将首先简要介绍一下。

还记得我们刚才谈到的线性方程吗?我们实际上可以进一步扩展这个公式,看起来像这样

基本上,我们现在有了更多的因变量、参数和误差项。我们的目标类似于单变量回归。只是这一次,我们必须找到最小化所有输出变量 RSS 的参数。

而且,这意味着我们现在有 264 个参数(44x6),而不是 44 个参数需要估计。

哇,真多,对吧?不完全是。这在大多数机器学习实践中很常见。尤其是当我们谈论深度学习时,我们可以拥有数千个参数。

我们不打算在多元回归的背景下讨论 RSS。然而,如果你想知道更多关于多元回归的细节,请参考这张幻灯片

对于不熟悉统计学的人来说,这张幻灯片可能太专业了。但是如果你感兴趣,试试吧!

接下来,我们将使用 Scikit-learn 实现多元线性回归。如果需要的话,别忘了休息一下。

是时候编码了!

好了,我们终于要开始编码了。确保您安装了 Scikit-learn。此外,我们将在这一部分进行数据可视化,因此请确保您还拥有 Matlotlib 和 Seaborn。

在我们构建模型之前,让我们使用一些可视化来更好地理解我们的数据。

让我们分析每两个变量之间线性关系的强度。这也就是每两个变量之间的 相关性

我们可以使用 pandas 提供的corr属性构建一个相关矩阵。

您将看到类似这样的输出。

啊,看看这些数字!我认为我们应该以这样一种方式让我们的团队成员能够更好地理解这种相关性。

让我们使用 Seaborn 来可视化相关矩阵。我们可以使用中的一小段代码来快速制作相关矩阵的热图。

通过一些小的调整,我们可以运行下面几行代码…

…应该会出现类似下面这样的内容。

好吧,这样看起来好多了。让我们来看看。

我们可以看到,其中一些营养素与其他营养素要么正相关,要么不相关,而只有一对是负相关的。

想知道正相关、负相关、无相关的区别,请看这里的。

虽然相关矩阵直观地显示了每两个变量之间的线性关系,但它并没有解释两个变量是否会导致彼此上升或下降。

事实上,很多时候你这里会有大量参与统计(包括机器学习)的人说类似“相关性不会导致因果关系”或者“相关性并不意味着因果关系”

出于这个原因,绘制我们的数据是一个好主意,以便更好地了解每个变量之间的关系。

让我们用 Matplotlib 来做这件事吧!

看起来碳水化合物和铁呈正相关。我们可以使用下面一行代码来绘制数据。

嗯,看起来尽管相关矩阵表明碳水化合物和铁是正相关的,但它们似乎缺乏 共线性

这就是为什么在建立模型之前绘制数据很重要。

好的,回到我们的数据,在图的底部看起来很混乱。不如我们用纤维和铁再试试。

看起来纤维和铁也是一样的。然而,在这种情况下,数据点都杂乱地堆在图的左下方。

另一个需要注意的是图上显示的 异常值 的高可变性。

显然,如果我们的最初目标是基于碳水化合物或纤维的量来预测铁的量,线性模型肯定会显示出较差的结果。

但是,我们在这里处理多个变量,让我们从 2D 进入三维。同样,Matplotlib 提供了一种绘制 3D 图的好方法。

好的,现在这个图明确显示了线性模型在这里不适用。我们的数据是高度非线性的,使用线性回归不会给我们带来任何好的表现。

不过为了好玩,还是试试吧。

首先,我们需要在一个训练和测试集中拆分输入数据和标签。Scikit-learn 的train_test_split函数使这个过程变得很方便。

我们将把 75%的数据分配给训练集,15%的数据分配给测试集。

在大多数实践中,强烈建议我们应该增加一个叫做验证集的集合。但是由于我们的数据集很小,我们不必在这里这样做。

现在,我们需要在编码模型之前标准化我们的数据。这一点很重要,因为有些营养素使用不同的单位。例如,维生素 A 使用 UI 单位,而碳水化合物使用克。

我们将使用 Scikit-learn 的RobustScaler来标准化我们的数据,这样所有的数据都可以抵抗单位的差异。

好了,看起来我们已经准备好使用数据来建立模型了。

我假设大多数读者已经熟悉 Scikit-learn 的[LinearRegression](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html)类。在很多在线教程中都是常用的。

但是,该类假定只提供了一个输出变量。

对于多个输出变量,我们需要添加一个类,这个类在大多数教程中通常不会提到,叫做MultiOutputRegressor

简单来说,就是 Scikit-learn 扩展 regressor 的一种方式,它本身不支持多输出回归。

我们将从简单地导入类开始。

接下来,我们可以开始拟合模型。

通常,在大多数关于线性回归的教程中,你很可能会看到这样的内容。

我们需要稍微修改一下,以包含MultiOutputRegressor类。

目前看起来不错。接下来,我们需要检查我们的模型在训练期间是否表现良好。Scikit-learn 的score属性应该可以做到这一点。

我们的模型显示了大约 72%的准确性。这是怎么回事?好吧,看起来仅仅用线性回归,我们就能得到比抛硬币更好的性能。

但是让我们继续每一个机器学习实践中非常重要的一步,在测试集上验证我们的训练性能。

我们这样做是为了避免在训练中过度适应。简而言之,当模型试图预测噪音过大的数据趋势时,就会发生过度拟合。

The black line is when the model was able to generalize well on the data. On the other hand, the blue line displays the line when the model has overfitted. (source: wikipedia.org)

因此,让我们检查模型在测试集上的表现。我们可以像训练集一样做一些小的改动。

看起来我们的模型能够在一定程度上避免过度拟合(训练和测试集精度之间的差异约为 7%)。

你现在可能会在脑海中嘀咕“那么我们如何避免过度适应和适应不足呢?”嗯,跟 偏差-方差权衡 有关系。

如果我们回到图中,我们的数据看起来杂乱无章。从视觉上看,这应该是高偏差的标志,这将导致拟合不足。然而,看起来我们的模型有更多的机会过度拟合。

虽然我们做了很好的工作来避免过度适应和适应不足,但这还不足以向我们在联合国儿童基金会的团队展示。

既然我们能够影响的人数很大(我指的是联合国儿童基金会),那么我们就应该提高我们模型的性能。

就像我之前说过的,数据集本身是高度非线性的,包含许多异常值。使用这样的数据集进行线性回归肯定不会产生最佳性能

作为一个有趣的练习,您可以尝试使用模型的预测值再次绘制数据。这将使我们对数据有更好的视觉理解(我们将在本系列的后面部分进行更多的可视化)。

因此,基本上,我们需要做的是使用一个不同的模型,可以处理非线性数据,并对异常值具有鲁棒性。

进入随机森林!

随机森林

如果我们试图找到一些最常用的机器学习模型来提高 Kaggle 的性能,它要么是随机森林,要么是梯度增强树。

然而,在这篇博文中,我们将讨论第一个问题。

但是在开始研究算法之前,我们需要先讨论一下决策树和自举。

决策树

想象一下,一边在公园散步,一边观察周围的环境。没有明显的原因,你决定看一棵树。它从根部开始,一路向上,直到最后一片叶子。

决策树也是一样,只是颠倒了。决策树算法也称为 CART、分类和回归树。

现在,我们将把注意力集中在回归树上,因为,当然,我们正在处理一个多元回归任务。

与线性回归不同,决策树算法不考虑数据中的线性假设。此外,它不需要任何重量。

相反,当使用决策树时,我们试图使用特征空间的满意度来进行预测。

根据《统计学习入门》一书的第 8 章,我们需要遵循两个步骤来构建决策树:

1.我们将预测空间(即 X1,X2,…,Xp 的可能值集)划分为 J 个不同且不重叠的区域,R1,R2,…,RJ。
2。对于落在区域 Rj 中的每个观察值,我们进行相同的预测,这只是 Rj 中训练观察值的响应值的平均值。

注意,作者使用 Xj 作为自变量的符号,而不是 xi。从现在开始,我们将做同样的事情。

现在,对于那些不熟悉决策树算法的人来说,可能会发现上面的陈述相当不清楚。

但简单地说,第一个陈述表明我们需要选择一个变量,并基于这些观察建立观察区域(数据点)。

而第二种说法是,在我们有了我们的区域之后,我们可以通过计算每个区域的观察值的平均值来进行预测。

有两种方法可以让我们通过视觉化直观地理解这个概念。

首先,我们可以绘制我们的数据点,并画一条线来分隔它的两个区域。让我们用纤维和碳水化合物来估计铁的价值。

这里我们看到一条线,将图分成两个区域。如果我们仔细观察,这条线在轴上代表碳水化合物的大约 41 个标记处开始分裂。

在回归方面,决策树算法通过平均每个区域的数据点来进行预测。

然后,算法将继续对变量进行分割,直到估计值与实际值相差不远。

我们可以形象化决策树的另一种方法是使用图表。

这里我们可以看到图表从顶部开始分裂。这些方框被称为节点,而指向/连接另一个节点的箭头被称为

如果你注意到了,第一个节点在 41.32 的值上做了一个分割,这和图中几乎一样。

与现实生活中的树不同,决策树通常通过向下的分支来可视化。

从顶层节点分支出来的两个节点被称为内部节点。上图中,每个内部节点内部都有字

这些值是算法通过对每个区域的观察值进行平均而得出的预测值。

然而,上面的图和图都只有一个深度。如果我们让树长到深度为 2,那么我们会得到这样的结果。

The plot and the graph with a depth of two.

在剧情上,我们可以看到现在另一条线对上一条线做了拆分。而该图具有更多的内部节点,这使得纤维分裂。

对于这个图,如果我们让树一直增长,直到它进行了最好的分裂,它将有更多的内部节点,最终可能不再是视觉上可理解的。

但问题是什么是最好的分割?好吧,最好的拆分是最小化预测值和真实值之间的误差的拆分。

如果我们仔细观察我们的图表,我们可以看到其中有单词 mse

这是指 均方误差 ,由以下公式给出

这类似于我们之前讨论过的 RSS,只是略有不同。

正如我们看到的,该公式具有 1/N 符号。这意味着在对所有平方误差求和后,我们用 N 个观察值对它们进行平均。

MSE 可能看起来像 RSS 加平均符号,但是在得出结论之前,这个讨论可能值得一查。

无论如何,理解决策树算法是使用随机森林算法的关键一步。

然而,这只是对决策树能力的简要说明,并推荐您查看《统计学习介绍》 第八章**书

该书对决策树给出了更好的高级数学解释。

在我们继续之前,我可能需要提到使用决策树时的一个更重要的方面。它被称为基尼指数。

简而言之,就是用来衡量某个变量在任意选择时被错误分类的概率。基尼指数基于熵的概念,熵是不确定性的程度。

这个概念通常在各种使用决策树相关算法进行分类任务的博客文章中很常见。

在分类任务中,我们想要测量信息增益,以便了解哪些特征给出了关于某个类别的最多信息。

然后,该信息用于解释每个变量的重要性,并利用它了解哪个变量对预测影响最大,或者最常被理解为变量重要性

就回归而言,正如我们之前讨论的,我们使用 mse 找到最佳分割。

我们可能会在本系列的一篇特色博文中讨论更多的可变重要性。

接下来,我们将介绍随机森林的另一个重要方面,引导。

拔靴带

使用机器学习项目的挑战之一是收集数据可能非常昂贵和困难。

虽然互联网上有各种开放数据集(如我们正在使用的数据集),但一些模型需要更多数据来提高训练期间的性能。

但是在实践中,收集数据有时既昂贵又困难。我们可以使用一种叫做自举的重采样方法

简而言之,bootstrapping 包括用替换随机地从我们已经可用的数据集中重新采样数据。“With replacement”基本上意味着我们可以在新数据集中两次使用相同的观察值。

我们重复这个过程一段时间,直到我们得到所需数量的引导数据集。每个引导数据集包含相同数量的原始数据集的观测值。

让我们以数据集为例,使用碳水化合物变量的前五个观察值。

前五个观察值分别是 0.06、2.87、00.0、2.34 和 2.79。

让我们将引导数据集表示为。如果我们有 4 个数据集,那么每个数据集将分别命名为 Z1、Z2、Z3、Z4

如果查看上面的图像,所有自举数据集的观测值都有重复值。这就是“用替换”的意思。

那么我们该如何处理这些自举数据集呢?当然,我们用它们来制作机器学习模型。

这听起来可能有点奇怪,因为我们已经有了用于训练的原始数据集。

但是,一旦我们进入这个主题令人兴奋的部分,编码随机森林,这一切就都有意义了!

编码随机森林

既然我们对决策树和自举有了一些直觉,我们可以继续讨论随机森林。

你看,这个算法被称为随机森林是有原因的。思考我们刚刚讨论的两个重要方面。

该算法使用 bootstrapping 使用原始数据集中的要素子集随机创建数据子集。然后使用决策树算法对每个子集建立模型。

然后,对每个模型的每个预测进行平均,以创建一个最终预测。

这对于多变量任务也是一样的。唯一的区别是每个决策树输出维生素 A、铁、锌、钙、维生素 D 和叶酸的值。

我们开始吧!与线性回归一样,我们需要导入RandomForestRegression类和MultiOutputRegressor类。

现在我们可以将数据放入模型中。

如我们所见,这里设置了一些参数。n_estimators为 100,因为 Scikit-learn 的下一个版本将把这个值作为默认值。

max_features仅仅是确保我们子集特征数量的平方根。这是我们需要为我们的模型调整的超参数之一。我们将在另一篇博文中讨论这个问题。

接下来,我们可以尝试检查我们的模型的准确性。

哇,看起来我们的模型在训练数据上表现得非常好。我是说,95%的准确率?听起来不错,对吧?

不完全是。我们仍然需要检查测试的准确性,因为我们想要的是模型在我们以前没有观察到的数据上表现良好。

看起来性能似乎相当不错。几乎比线性回归好 20%

与线性回归不同,我们的模型过度拟合,因为训练精度远大于测试精度(大约 15%的差异)。

我们可以用各种方法来改进这个模型。然而,让我们看看我们是否可以使用近年来获得高人气的算法来制作模型。

在本系列的下一部分,我们将构建一个神经网络算法来训练我们的数据。

当我们在数据集上使用神经网络时,您认为会发生什么?还会一样吗?好些了吗?或者更糟?

请在评论中告诉我你的想法。期待反馈和进一步的讨论。

第 3 部分见。干杯!

一名目前在韩国留学的印尼学生,对人工智能世界有着浓厚的兴趣。最近以 AI 研究实习生的身份跳槽到 IBM。相信不断的学习。我想回馈在我追求知识的过程中贡献良多的人工智能。当我不写代码时,我喜欢阅读航天工业的最新趋势,并为我大学的美式足球队效力。

用神经网络和随机森林预测微量营养素(三)

原文:https://towardsdatascience.com/predicting-micronutrients-using-neural-networks-and-random-forest-part-3-337ed6c5946a?source=collection_archive---------39-----------------------

联合国儿童基金会希望你利用机器学习的力量,帮助他们预测食物中的重要营养成分。

Photo by Zoltan Tasi on Unsplash

问候!欢迎来到“使用神经网络和随机森林预测微量营养素”博客系列的第 3 部分。

在之前的博客文章中,我们通过对数据进行一些可视化技术,稍微看了一下我们的数据是什么样子的。

然后,我们尝试使用两种最常见的监督机器学习算法:线性回归和随机森林。然而,似乎我们没有得到我们想要的结果。

如果你是深度学习的新手,请注意,虽然内容包含对深度学习的介绍,但这不是一篇关于深度学习如何漂亮地工作的博客文章。

相反,这篇博客文章是为了表明,当试图建立机器学习模型或解决数据问题时,深度学习是而不是的银弹。

如果你想看看深度学习何时起作用的例子,我会在这篇博文的结尾附上一些文章。

到目前为止,我们只有几天时间来准备我们向联合国儿童基金会数据科学团队提出的模型。当然,鉴于该组织的声誉,我们不想让他们失望,对吗?

太好了,现在我们站在同一边了,让我们开始吧。

概述 :

这篇博文包含以下内容:

  • 深度学习简介
  • 深度神经网络是如何“学习”的?
  • 使用 Keras 编码深度神经网络
  • 反射部分

深度学习简介

关于深度学习的所有宣传,我假设大多数正在阅读这篇文章的人对它的总体情况有所了解。

简而言之,深度学习是主要利用堆叠感知机从数据中学习的各种机器学习方法之一。

这些堆叠的感知器被称为神经网络,其灵感来自人脑复杂的生物结构。一般来说,一个神经网络有三个主要层 : 输入隐藏输出

隐藏层中的每个单元称为一个隐藏单元

A general structure of a neural network has an input, hidden, and output layer.

现在你可能会想,“好吧,但我还是不明白这到底是怎么回事。”嗯,你完全正确。然而,在大多数实践中,更多的隐藏层和隐藏单元堆叠在架构中。

一些复杂的网络使用了数十万甚至数百万个隐藏层和单元。随着隐藏层和单元数量的增长,大多数专家将该网络称为深度神经网络

A Deep Neural Network

另一个可能会突然出现在你脑海中的问题是“那么这个深度神经网络有什么特别之处?”深度学习的一个特殊之处就是它的“学习”部分。

我们已经讨论了“深”的部分,现在让我们谈谈第二部分。

深度神经网络是如何“学习”的?

各种有监督的机器学习算法的目标从根本上是相似的:最小化预测值和地面真实值之间的误差。

在深度学习流行之前,大多数机器学习过程都试图通过特征工程 的方式来做到这一点。

不要太深入细节,特征工程只是一种创建特征以使机器学习工作的方法。在这种情况下,特性意味着数据的变量。

然而,在深度学习算法中却不是这样。相反,一个 优化算法 被用来寻找最佳执行模型。其中最流行的一种叫做 渐变下降

基本上,梯度下降的主要目标是找到一个损失函数的局部最小值。

它能够通过称为 反向传播 的过程,相对于隐藏层中的所有权重取误差的梯度来做到这一点。

然后,你要向梯度方向迈出负的一步。我们将尝试使用类比来更好地理解这一点。

我们不打算在这里讨论数学,但从视觉上看,它大概是这样的。

What a gradient descent process looks like in three dimensions.

或者,我们可以用一个人蒙着眼睛下山的类比。向山下一大步,或者在山的某个安全的地方迈出一小步,会更好吗?

如果有人还想再活一天,那么后者当然会好得多。基本上,我们想做的是采取一个小的渐进步骤,将我们带到山的底部。

这些小步骤就是“向梯度负移一步”的意思,但这一次,它来自“误差函数”山。

optimization using gradient descent be like…

注意梯度下降有它的变体。选择使用哪种深度学习算法是训练深度神经网络艺术的一部分。

具体来说,我们使用的是最常见的变体之一,称为 Adam optimizer。同样,我们不打算在这里讨论,但可以随意参考原始论文

好了,现在是我们一直在等待的时刻,Keras 的编码部分!

使用 Keras 编码深度神经网络

Keras 是一个用 python 编写的开源神经网络库。它运行在各种现有的深度学习库之上,如 Tensorflow、MXNet 和微软认知工具包。

此外,我们将使用另一个名为权重和偏差的工具,用于跟踪可视化神经网络的训练进度。现在,我们将把注意力集中在跟踪训练精度上。

在我们开始编码之前,请注意我们已经准备好了数据集。在本博客系列的第一部分中,它已经被清理、扩展并分成了训练集和测试集。

数据准备好了,我们就可以开始编码神经网络了。我假设读者熟悉 Keras。如果没有,那么文档会非常有用。

让我们从 Keras 库中导入所有需要的东西。

这里,我们使用Sequential来堆叠层。我们将使用四种类型的层对象:ActivationDenseBatchNormalizationDropout

只是指出,Dense就是我们之前讲过的全连通层。根据名称,ActivationDenseBatchNormalization层非常简单。

如果你不熟悉最后两个,那么我强烈建议你读一点,因为它们在实践中经常被使用。

如前所述,Adam被选为优化器。此外,我们使用mean_squared_error作为损失函数,因为它是我们用于随机森林和线性回归的函数。

好了,我们从 Keras 得到了我们需要的东西,但是我们还需要一些东西来跟踪我们的模型在训练时的表现。这样,我们可以看到一段时间后精度是否停止提高。

我们可以使用很多开源工具,但我更喜欢的是一个叫做权重&偏差 (wandb)的工具。它为 Keras、Tensorflow 和 Pytorch 提供支持。

要使用它,我们需要在官网注册,下载 Python 的 SDK,做一点点设置。

wandb.init()初始化图表,将显示图表。config允许我们设置参数(批量、学习率等。)将被发送到图表轴的 wandb 服务器;例如像纪元。

现在我们需要将参数设置为 config。请随意修改参数。

太好了!让我们从神经网络架构开始。首先,让我们建立一个 2 个隐藏层的神经网络。第一层有 128 个隐藏单元,第二层有 64 个。

我们将在每一层之后使用一个 ReLU 激活。然后我们再做批量归一化,后面会有一个掉线。最后一层有 6 个输出单元。

我们得到了我们的架构,现在我们只需要编译我们的优化器和损失函数。

现在,是训练的时间了!

正如我们在这里看到的,我们有一个callbacks参数,它接受一个列表作为它的输入。我们可以有多个性能跟踪器,但是在这种情况下,我们将只在列表中使用WandbCallback

要查看模型的性能,我们可以直接查看 wandb 网站上的仪表板。

根据您的内存能力,可能需要一段时间来训练模型。然而,有了追踪器,如果我们的表现似乎一点也没有改善,我们可以随时停止。

让我们看看进展如何。

在整个训练过程中,我们的模型似乎只能达到大约 30%的准确率。具体来说,在第 20 个纪元前后,精确度停止提高。看起来我们必须稍微改变一下架构。

让我们试着在两层中使用较少的隐藏单元。注意,我们需要重新运行wandb.init()config = wandb.config来开始一个新的图表。

如果没有,那么 wandb 会认为我们在继续之前的培训课程,而实际上我们正在开始一个新的课程。

也就是说,让我们建立架构。

而且准确率还在 30%左右。嗯,我想知道这是怎么回事?但是在前 40 个纪元的训练中似乎有剧烈的变化。

这里有很多假设,但是减少隐藏单元的数量可能不是一个好主意。那么,如果我们添加比第一种架构更多的隐藏单元,会发生什么呢?

让我们找出答案。

准确率还是~30%?(混乱开始了)。

到目前为止,我们所做的只是改变隐藏单元的数量。也许是时候用图层来增加一些东西的味道了。让我们就这么做吧!我们要做 5 层,而不是 2 层。

好吧,现在我很确定我们没有朝着正确的方向前进。

在这种情况下,人们很容易得出这样的结论,即深度学习在这种情况下可能不是一个可行的解决方案,并提出一个解决方案。

然而,后退一步,花时间去理解为什么这不是一个可行的解决方案,这总是一个好的做法。

理解为什么某个解决方案对某个问题不起作用是每一次失败的学习部分。这样,当被问及为什么有些东西不起作用时,我们将能够解释为什么

那么让我们继续下一部分,反射部分。

反射部分

首先,让我们多想想我们的数据。我们的数据集大约有 8000 个数据点。

在深度学习实践者的眼中,这是相当小数量的数据点。通常,深度神经网络需要数百万,甚至数千万个数据点。

此外,我们在数据集中使用许多变量,这些变量可能充当噪声,而不是实际信号。也许我们不需要全部都用上。

也许如果我们做更多的可视化,我们可以更好地理解我们的数据,并使用更好的方法而不是深度学习。这正是我们在这个博客系列的第四部分也是最后一部分要做的。

尽管有深度学习的大肆宣传,但我们现在明白,深度学习并不总是答案;至少在这种情况下不会。还有其他方法可以解决数据问题。

顺便说一下,正如承诺的那样,这里有一些深度学习工作得非常好的文章(当然有 Keras):

上面的文章是很好的例子,当有足够数量的数据时,它们将向你展示深度神经网络的真正力量。

如果你一直读到最后,非常感谢你。如果你有进一步的问题或者想要给我一些反馈,请在评论中告诉我,或者在我的 Linkedin 上给我发消息。

希望下一部能见到你。到那时,干杯!

关于我:

一名目前在韩国留学的印尼学生,对 AI 世界有着浓厚的兴趣。大部分时间是后端和机器学习开发者。相信不断的学习。我想回馈在我追求知识的过程中贡献良多的人工智能。

基于比赛情况预测 MLB 投球概率

原文:https://towardsdatascience.com/predicting-mlb-pitch-probability-based-on-the-game-situation-1afc5a01cf3?source=collection_archive---------34-----------------------

Can we determine what this pitch will be before it is thrown?

对许多人来说,棒球是一项很棒的运动。这是第一个想到利用数据在各个层面做出决策的运动之一。经理使用数据做出游戏决策,总经理使用数据决定合同支出。

棒球也是最早将统计数据纳入电视转播内容的运动之一。实时投球速度,投球位置,得分概率和偷垒概率。这些只是观众在观看这项全国性娱乐节目时可以吸收的一些东西。随着最近人工智能的加入,更多的预测和实时内容可用。

Pitch location is just one of the many AI uses of real-time mediated content

作为一个游戏爱好者,我想深入到一个目前还不属于本内容的领域,但在未来将会是:根据游戏情况预测接下来会有什么样的投球。我对最初的结果感到非常兴奋。

第一,数据。Kaggle 提供了一个数据集,其中包含 2015-2018 赛季在https://www.kaggle.com/pschale/mlb-pitch-data-20152018的所有球场数据。数据来自几个方面。csv 文件包括投球,击球,比赛,弹射和球员姓名。为了达到我们的目标,只使用了击球和投球数据集。

以下是加载到笔记本中的包

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import RobustScaler, LabelEncoder
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import accuracy_score, f1_score, confusion_matrix, classification_report, precision_score
import xgboost as xgb
from keras.utils.np_utils import to_categorical
import random
from keras import models, layers, optimizers, regularizers
from keras.layers import Embedding, GRU, Dropout, GlobalMaxPool1D, Dense
from sklearn.model_selection import GridSearchCV

读入数据并将两个数据集合并在一起(在 ab_id 上)后,第一个障碍是要处理的数据量。从 2015 年到 2018 年,记录了超过 280 万次投球。出于计算费用和最相关数据的目的,数据集被缩减为仅反映 2018 赛季。

#isolating the data that only contains the 2018 season
data18 = data[data['year'] == '2018']

这仍然提供了超过 70 万次的观测。现在,有超过 40 列,其中许多包含俯仰速度,轨迹和位置信息。这些对我们预测下一次投球没有任何好处,因为在那个时候,投球已经投出了。我们关心的是所有可能有助于投球的信息,所以我们将只保留我们需要的信息:

  • pitch_type —音高的类型。这是我们的目标
  • b_score —击球手所在队的得分
  • b_count —当前计数中的球
  • s_count —当前计数中的打击数
  • 出局数——出局数(投球前)
  • 投球数——击球时的投球数
  • on_1b —如果第一个上有跑步者,则为 True,如果为空,则为 False
  • on_2b —如果二垒上有跑垒者,则为真;如果二垒上没有跑垒者,则为假
  • on_3b —如果三垒上有跑垒员,则为真,如果为空,则为假
  • 击球手 id —击球手的球员 id。由 MLB 提供,在 player_names.csv 中找到的球员姓名
  • 第一局——第几局
  • p_score —投手队的得分
  • p _ throws——投手用哪只手投球。单个字符,R 或 L
  • pitcher_id —投手的玩家 id。
  • 站立——击球手击中哪一方。单个字符,R 或 L
  • topTrue 如果是一局的开始,如果是结束,则为 False
#new dataframe with what is initially determined as situational + the target (pitch_type)pXs = pd.DataFrame(data18[['pitch_type', 'b_score', 's_count', 'outs', 'pitch_num', 'on_1b', 'on_2b', 'on_3b', 'batter_id', 'inning', 'p_score', 'p_throws','pitcher_id', 'stand', 'top', 'b_count']])

这些是我们将瞄准的推销类型:

  • CH —变速
  • CU —弧线球
  • FC —刀具
  • FF——四缝线快速球
  • FS —拆分器
  • 双缝线快速球
  • KC —关节曲线
  • KN——球
  • SI —沉降片
  • SL —滑块

数据集中还有其他类型的音高,但只占总观测值的不到 0.1%(投球、螺旋球或罕见的以弗所音高)。这些都从数据集中删除,布尔和二进制对象转换为整数和空删除(代表不到 1%的总数据)。

看一下数据本身,数据中的异常值代表了局数和得分。这是典型的加时赛,但不像高分比赛那样频繁。得分和局数确实与投球有关,所以删除它们可能会对结果产生负面影响,所以那些异常值被保留了下来。

Outliers for batters score, pitch number and on second base

outliers for inning and pitchers score

检查多重共线性,发现球场数与球数和击球数高度正相关。,因此将其从数据集中移除。

Multicollinearity of pitch_num

在这一点上,执行了几个测试模型和试验,但是在这一点上,超调 XGBoostClassifier 为我们的数据提供了最好的结果。

X_train, X_test, y_train, y_test = train_test_split(features2, target2, test_size=0.30)#create xgb instance with parameters
clf = xgb.XGBClassifier(learning_rate=0.1,max_depth=10,min_child_weight=15,n_estimators=250)#fit data
clf.fit(X_train, y_train)#make predictions
training_preds = clf.predict(X_train)
val_preds = clf.predict(X_test)#run classification report and confusion matrix
gbt_confusion_matrix = confusion_matrix(y_test, val_preds)
print(gbt_confusion_matrix)
gbt_classification_report = classification_report(y_test, val_preds)
print(gbt_classification_report)

Classification Report for hypertuned XGBoostClassifier

这是一个彻底的分类,因此在 11 种音高类型上获得 51%的总体准确率是很好的。然而,将这一点转化为实际的游戏,只在一半的时间里获得正确的音高对于现场消费来说并不是最佳的。在这一点上决定,也许预测每个投球的概率会更有益。幸运的是,XGBoost 的包中有一个概率选项,只需将目标切换到 multi:softprob。

le = LabelEncoder()
le.fit(target3)
target_num = le.transform(target3)# list(le.inverse_transform(target_num)) #If you wish to retrieve the original descriptive labels post productionX_train, X_test, y_train, y_test = train_test_split(features3, target_num, test_size=0.3, random_state=42)# use DMatrix for xgboost
dtrain = xgb.DMatrix(X_train, label=y_train)
dtest = xgb.DMatrix(X_test, label=y_test)
fit = xgb.XGBClassifier(objective = 'multi:softprob')
fit.fit(X_train,y_train)# set xgboost params
param = {
'max_depth': 10,  # the maximum depth of each tree
'learning_rate': 0.1,  # the training step for each iteration
'min_child_weight': 20,
'n_estimators': 300,
'objective': 'multi:softprob',  # error evaluation for multiclass training
'num_class': 10}  # the number of classes that exist in this datset
num_rounds=30#------------- numpy array ------------------#training and testing - numpy matrices
bst = xgb.train(param, dtrain,num_rounds)
preds = bst.predict(dtest)#extracting most confident predictions
best_preds = np.asarray([np.argmax(line) for line in preds])print ("Numpy array test precision:", precision_score(y_test, best_preds, average='macro'))

Probability predictions for each observation (example)

我们将 dataframe 转换为 numpy 数组进行计算,因此为了可视化结果,有必要将数据转换回 dataframe,并使用 matplotlib 重新标记列以创建一个视图。

#creating a dataframe for plotting purposes
plot = pd.DataFrame(data=preds[:,:], columns=['Changeup', 'Curveball', 'Cutter', 'Fastball_4S', 'Splitter', 'Fastball_2S', 'Knucklecurve', 'Knuckleball', 'Sinker', 'Slider'])#visualization function
    def pitch_pred(data):
    print(X_test.iloc[data])
    ax = plot.iloc[data].plot(kind='barh',title ="Pitch Prediction",     figsize=(15, 10), fontsize=20)
    ax.set_xlabel("Percent Chance", fontsize=24)
    ax.set_ylabel("Pitchtype", fontsize=24)
    plt.show()#visualize observation 6754
pitch_pred(6754)

Parameters for observation 6754

Visual for observation 6754

正如你所看到的,在彻底分类的基础上,展示下一个音高的可能性更有意义。未来的工作包括部署模型后的实时测试。也可能找到一些有用的附加数据,如体育场和天气。由于数据量巨大,笔记本可以在 Google Colab 和 Github 上找到,但我无法将数据文件保存在云中。数据集可以从 Kaggle 下载。

[## 谷歌联合实验室

棒球 _ 投球 _ 预测

colab.research.google.com](https://colab.research.google.com/drive/1VaHWXq2yYuH-S-6WL_WD8VFSugcgoiUz#scrollTo=eNI5nrnYrxPV) [## 杰森·M·理查兹/棒球投球预测

作为职业棒球的狂热观众,越来越明显的是,统计数据不仅有助于经理们…

github.com](https://github.com/Jason-M-Richards/Baseball-Pitch-Prediction)

使用 AdaBoost、XGBoost 和 LightGBM 预测电影收入

原文:https://towardsdatascience.com/predicting-movie-revenue-with-adaboost-xgboost-and-lightgbm-262eadee6daa?source=collection_archive---------20-----------------------

决定电影成功的因素是什么?

漫威的复仇者联盟 4:终局之战最近取代阿凡达 成为历史上票房最高的电影,尽管这部电影无疑会非常成功,但我想知道是什么让任何一部电影成功。

我要回答的问题是:

  1. 哪些变量特别能预测绝对收入数字?
  2. 将问题限制在二进制“失败”或“成功”的情况下,收入预测有多准确?

我正在使用通过 kaggle 提供的电影数据库中的数据。数据集被分成训练集和测试集,训练集包含 3,000 部电影,测试集包含 4,398 部电影。在训练集和测试集中有 22 个特性,包括预算流派、所属集合、运行时间、关键字和更多训练数据集还包含目标变量收入

List of variables in the train data set

探索性数据分析

难点:

  • 在进行任何探索性的数据分析之前,我必须将文本格式的看起来像 JSON 的数据转换成实际的数据类型,这实际上是字典列表。完成这个任务的一个非常有用的函数是ast包中的literal_eval()
  • 这个数据集中有很多分类特征,包括制作国家、制作公司、演员剧组。要包括他们所有的回报是不可能的,所以我决定对每种类型的特性的前 30 个回报进行任意限制。
  • 数据集中的流行度变量基于不同的指标,如每天的用户投票和浏览量。因为一旦一部电影上映,这一功能就会变得更加强大,所以这个变量可能不应该包含在一个旨在对正在上映的电影进行稳健的未来预测的模型中。
  • 有很多缺失的值,特别是在预算变量中,还有剧组剧组的性别子变量中。因为预算似乎是一个非常重要的指标,基于最初对收入数字的绘制,我决定删除任何预算为 0 的电影。由于数据不一致,我决定不在我的模型中包含性别比例。

见解:

前传和续集:也许不出所料,相关电影的前传或续集的平均收入高于独立电影。

Boxplots for movies that are part of collections (1) and those that are not (0)

预算:图表清楚地表明收入预算正相关。人们实际上希望大笔投资会产生更大的回报。

Budget variable plotted against revenue

train 数据集中的前六名演员似乎都是赚钱的人,其中一些人——比如塞缪尔·L·杰克逊——比其他人多。

Boxplots comparing revenue of movies including certain actors (1) and excluding certain actors (0)

上映日期:随着时间的推移,电影的数量和收入都在增加,然而,20 世纪 70 年代有一些电影产生了大量的收入,导致收入数字飙升。2017/2018 年也发生过类似事件。

Number of movies and revenue over time

数据预处理

我将演职人员、剧组、制作公司、制作国家关键词变量的前 30 个回报作为虚拟变量添加到数据集。我还创建了新的功能来计算每个样本的不同退货数量(生产公司、生产国家、口语)以及字母数量(标题、标语)。因为预算因通货膨胀而增加加班,所以我添加了一个可变的预算 _ 到 _ 年比率。

为了让数据进入统一的区间,我将对数函数应用于预算、人气收入变量,并借助 0 到 1 之间的MinMaxScaler()对所有数据进行缩放。我将发布日期转换成 4 个不同的变量:发布年份、发布月份、发布周发布工作日。我没有对概述、标题标语变量使用任何自然语言处理,并且忽略了单词内容。

在数据清理和工程之后,我的训练集中有 201 个特征。我把训练集拆分成 X_train,y_train,X_cross,y_cross。我的测试数据集不包含任何收入特征,我的预测仅包含在我的 Jupyter 笔记本中。

建模和评估

为了预测绝对收入数字,我决定使用三种不同的集成算法。我对它们每个都应用了参数调整技术。

AdaBoost

AdaBoost (自适应增强)是一种元估计器,它首先将一个回归变量拟合到数据中,然后添加额外的回归变量,旨在改善与回归变量偏差较大的情况。所有回归变量的结果是平均的,并导致最终的模型。

该算法的最重要的参数n_estimators (应该高些以获得更好的精度)和 learning_rate (应该小些以获得更好的精度)。我使用GridSearchCV来确定最优参数,对于 n_estimators 为 100,000,对于 learning_rate 为 0.001。

XGBoost

XGBoost (极限梯度提升)是一种基于梯度下降的提升算法,比 AdaBoost 算法更高效。它使用提升树和并行提升,使其快速准确。提升树的底层算法类似于 RandomForests。

我查看了许多不同的参数,并调整了以下内容:

Parameters tuned for the XGBoost model

LightGBM

我使用的最后一个模型是 LightGBM ,这也是一个基于树的梯度推进算法。它提高了 XGBoost 的速度,可以很快得到高精度的结果。关于 LightGBM 和 XGBoost 之间的差异的更多细节可以在这里找到

我向我的模型添加了以下调整后的参数:

Parameters tuned for the LightGBM model

均方根误差

我检查了训练和交叉验证数据集的预测,并决定将重点放在 XGBoost 模型上进行进一步的分析。它确实过拟合,但是,它仍然具有最佳比例对数均方根误差。

Scaled logarithmic root mean squared error for training set on the left and cross-validation set on the right

1。哪些特征特别能预测电影收入?

使用lightgbm 包中非常有用的方法plot_importance,预测收入时最重要的特性是流行度、预算、预算 _ 年 _ 比率、发行 _ 年、运行时间、标题 _ 长度、标语 _ 长度发行 _ 周。这些特性类似于 AdaBoost 模型和 LightGBM 模型的最重要特性。

Feature importance based on the XGBoost model

2.这个模型能多好地预测失败和成功的电影?

上述衡量尺度和对数均方根误差的指标可能难以理解和判断,因此我在我的数据集中添加了一个利润目标标签,其定义如下,先前由本研究论文介绍:

利润= (1/2) *收入-预算

计算 X_train 和 X_cross 数据集的实际利润和预测利润,得出训练数据的准确率为 78.9%,交叉验证数据的准确率为 68%。

43.2%的失败电影被错误地预测为成功电影,而 21.3%的成功电影被错误地预测为失败电影。这表明该模型有预测高于实际值的趋势。

结论

  1. 哪些特征特别能预测电影收入? 人气、预算、预算年比率、发行年、运行时间、标题长度、标语长度发行周
  2. 这个模型能多好地预测失败和成功的电影?将绝对值转换为二进制分类系统(翻牌或成功)只会导致交叉验证集中的准确率为 68%

限制

  • 相对较小的训练集规模。更大的训练集可以产生更精确的模型。
  • 特征选择。添加其他变量,包括用户评级、评论家评分、社交媒体内容,甚至来自网飞等推荐系统的数据,在试图提高准确性时,可能会被证明是有价值的。
  • 线性回归。根据这篇的研究论文,在应用的三个不同模型中,使用线性回归作为基本回归变量在预测低票房电影时表现良好,但在预测高票房电影时表现较差。
  • 将回归转换为分类输出。我只是简单地将我的回归输出转换成一个二进制类案例,并计算准确性。但是,我的模型只针对绝对收入数字进行了优化,而不是这两个类别。如果我们对失败与成功预测感兴趣,那么使用分类器和深度学习算法来获得高准确性会更好。

这个项目是我的 Udacity 的数据科学家纳米学位项目的顶点项目。在我的 Jupyter 笔记本上,还有很多关于电影数据集的有趣见解。详细代码请看我的 GitHub

你想在媒体上阅读更多高质量的故事吗?考虑注册一个支持我和其他媒体作者的会员。

[## 通过我的推荐链接加入 Medium-Julia Nikulski

作为一个媒体会员,你的会员费的一部分会给你阅读的作家,你可以完全接触到每一个故事…

medium.com](https://medium.com/@julia.nikulski/membership)

想知道 AdaBoost 和 XGBoost 算法实际上是如何工作的吗?查看我关于 bagging、boosting、AdaBoost、random forests 和 XGBoost 的深入指南。

[## AdaBoost、随机森林和 XGBoost 的终极指南

它们是如何工作的,有何不同,何时应该使用?

towardsdatascience.com](/the-ultimate-guide-to-adaboost-random-forests-and-xgboost-7f9327061c4f)

使用我的 Spotify 数据预测我的情绪

原文:https://towardsdatascience.com/predicting-my-mood-using-my-spotify-data-2e898add122a?source=collection_archive---------9-----------------------

上个月那是一个命运攸关的夜晚,我赶上了我最好的朋友。像往常一样,我们分享了我们最喜欢的新歌,并讨论了当前嘻哈游戏的氛围。有趣的是,我们的谈话最终引导我们讨论音乐流媒体服务(如 Apple Music 和 Spotify)可能有多少关于我们音乐行为和情绪状态的数据。

这段对话引发了我脑海中的一个问题:

给定听众的音乐数据,我们能推断出他们的情绪吗?

在本文中,我将概述我使用聚类和监督学习方法来回答这个问题的方法。

Photo by Fimpli on Unsplash

获取我的 Spotify 数据

第一步是通过 Spotify 的便捷 API 建立我的 Spotify 歌曲数据集!

我使用 Spotipy 库编写了一个 Python 脚本来连接到 Spotify API,并获取我曾经创建的每个播放列表以及其中的歌曲的列表。

对于每首给定的歌曲,Spotify API 都会提供其音频功能。

这些特征从一首歌的节奏到它的可跳舞性(是的,你没听错),衡量一首歌可跳舞的程度!

在这个项目中,我使用了 5 个音频特征来预测情绪:

  • acoustic ness:0.0 到 1.0 的音轨是否声学的置信度度量。1.0 表示音轨是声学的高置信度。
  • 可跳舞性:根据音乐元素的组合,包括速度、节奏稳定性、节拍强度和整体规律性,衡量一首曲目是否适合跳舞。值 0.0 最不适合跳舞,1.0 最适合跳舞。
  • 活跃度:检测录像中是否有观众。较高的活跃度值表示音轨被现场执行的概率增加。高于 0.8 的值表示该轨迹很有可能是实时的。
  • 响度:音轨的整体响度,单位为分贝(dB)。响度值是整个轨道的平均值,可用于比较轨道的相对响度。响度是声音的质量,是与体力(振幅)相关的主要心理因素。值的典型范围在-60 和 0 db 之间。
  • 语速:检测音轨中是否存在口语单词。高于 0.66 的值描述可能完全由口语单词组成的轨道。介于 0.33 和 0.66 之间的值描述可能包含音乐和语音的轨道,可以是分段的,也可以是分层的,包括说唱音乐。低于 0.33 的值很可能代表工具性的。

(显示的定义来自 Spotify API 文档。)

然后,我将每首歌曲的特征导出到 csv,总共包含 822 首歌曲。

first 5 songs in the data set

使音量正常化

在将数据输入任何最大似然算法之前,我必须确保特征被正确地缩放。典型地,当数值属性在非常不同的尺度上操作时,ML 算法表现不佳,因为它向它们的目标函数引入了偏差。

幸运的是,API 提供的大多数特性都在 1 到 0 之间。除了值在-60 和 0 db 之间的响度特性。

因此,我使用 sklearn MinMaxScaler 将响度特性标准化,使其值介于 0 和 1 之间。

from sklearn import preprocessingloudness = songs[['loudness']].values
min_max_scaler = preprocessing.MinMaxScaler()
loudness_scaled = min_max_scaler.fit_transform(loudness)

将歌曲分成不同的情绪

有了现在准备好的数据,下一步就是将我的歌曲分组,并确定每个分组所代表的情绪。

我决定使用 K-Means 聚类算法,它在发现数据中的潜在分布方面非常出色。它是一种无监督学习算法,只有一个目标:

将相似的数据点分成 k 组,并发现潜在的模式。为了实现这个目标,它寻找预定数量( k )的聚类。

那么,我们如何决定 k 的最优个数呢?

有许多方法可以做到这一点,其中最流行的方法是肘法。这包括对范围 k(例如1–15)运行 K 均值,并绘制到聚类中心的平均平方和距离与聚类数量的关系图以找到视觉“肘”,这是聚类的最佳数量。

from sklearn.cluster import KMeansSum_of_squared_distances = []
K = range(1,15)
for k in K:
    km = KMeans(n_clusters=k)
    km = km.fit(songs_features)
    Sum_of_squared_distances.append(km.inertia_)

现在,如果我们针对 k. 的范围绘制我们的距离,我们会得到以下图表:

正如你所看到的,当 k = 4 时,到聚类中心的平均平方距离开始显著下降。这就是期待已久的肘子,我们的优 k

使用主成分分析和 t-SNE 可视化聚类

确定了聚类的数量后,下一步就是探索由 K-均值构成的聚类,并识别它们可能代表的情绪!

由于数据集包含 5 个特征,我们正在处理高维数据,这意味着这很难想象(更不用说绘图了 lol)。

幸运的是,我们可以使用降维技术来降低数据的维度,使其更容易可视化,同时保留数据中的大部分信息(如方差)。

我使用了两种降维算法,主成分分析( PCA )和 t 分布随机邻居嵌入( t-SNE )来可视化下面看到的聚类。

2D Visualisation of song clusters using PCA

2D Visualisation of song clusters using t-SNE

从图中,我们可以看到集群形成良好,具有清晰的边界和最小的重叠。最大的重叠在簇 1 和簇 2 之间。

聚类 0 是最小的聚类,并且其歌曲离聚类 1 & 2 最远,这表明 0 中的歌曲与聚类 1 & 2 非常不同。

有趣的是,在 t-SNE 表示中,与更密集的 PCA 簇相比,簇的形状更加扩展和广泛分布。在 t-SNE 图像中,我们可以看到来自与簇 2 重叠的簇 0 的几个极值点,另一方面,在 PCA 表示中没有重叠。

下图显示了每个聚类中的歌曲数量。聚类 1 是最大的聚类,包含数据集中超过一半的歌曲!这可能表示我的播放列表中的一种主要情绪。其余分类的大小较小,分类 0 包含的数据集不到 10%!

Number of songs in each cluster

识别情绪

然后,我通过查看每个集群中的歌曲,并确定我与特定集群中大多数歌曲相关联的关键情绪,为每个集群分配一种情绪

所以让我们来看看其中的一些歌曲吧!

群集 0

4 个集群中最小的一个。我非常感兴趣地看到了聚集在这里的少数歌曲。

  1. 弗洛里汽车收音机(L o-fi 嘻哈
  2. 米勒·戴维斯-那又怎么样(爵士)
  3. 植松信夫-最终幻想 X(钢琴协奏曲):三。凯森(管弦乐,钢琴)
  4. Destati - Kairi 项目(管弦乐)
  5. 植松信夫-最终幻想 X(钢琴协奏曲):I .扎纳尔坎德(管弦乐,钢琴)

这 5 首歌我注意到的第一件事就是都是器乐曲目!例如,Flovry 的汽车收音机是一种高保真嘻哈乐器,我通常在放松时听。后 3 首是我学习时听的游戏原声(最终幻想&王国之心)。

Car Radio by Flovry

在进一步检查集群后,我开始看到更多的高保真和爵士乐乐器(包括更多的米勒·戴维斯,戴夫·布鲁贝克和艾拉·费兹杰拉)。

所有这些歌曲都有一个共同点。相对高的 声音度接近 1,表示使用真实乐器。因为大多数乐器的音轨都是声学的,所以它们共享这个属性是有意义的。下图显示了分类 0 的平均分类值。正如所料,声音值最高,语音值最低,表明在这些聚类的歌曲中很少或没有语音。

Average Features in Cluster 0

这一簇绝对代表了我“下雨时的舒适热巧克力”的心情。

因此,我将第 0 组标记为寒冷情绪。

群组 1

  1. 古驰马内-梅特加拉(壮举。偏移)(陷印)
  2. 肯德里克·拉马尔——大人物(与特拉维斯·斯科特)
  3. damso-θ。马卡雷纳(法国陷阱)
  4. 逻辑不锈钢(嘻哈)
  5. 一个\(AP 洛基-一个\)AP 永远(嘻哈)

Big Shot by Kendrick Lamar

从第一组的前五首歌来看,它们都是一贯响亮而乐观的嘻哈音乐。这些是我可能会在健身房或者和朋友一起听的歌曲!

从下面的平均群集特征中,我们可以看到该群集比之前的 chill 群集具有更高的可跳舞性和响度。此外,声音低得多,表明在这些集群歌曲中使用了更多的电子和合成声音。

Average Features in Cluster 1

这组歌曲向我传达了一个明确的信息,那就是能量!

因此,我将第一组标记为充满活力的情绪。

群组 2

  1. 萨巴-光合作用(嘻哈)
  2. 杰米 xx -我知道会有(美好时光)(舞蹈,电子)
  3. 坎耶·韦斯特-触摸天空(嘻哈)
  4. Epik High - 막을 올리며安可(k嘻哈
  5. Téo?- Orso (off top) ( 新美国嘻哈)

Photosynthesis by Saba

这绝对是最多样化的 4 个集群,包含了各种不同类型的音乐,从电子音乐到令人惊讶的年轻暴徒…

我发现第二组和第一组非常相似,因为有大量的嘻哈音乐,这解释了在可视化中看到的重叠。然而,我发现这个集群中的歌曲有更多的口语词(例如 A$AP Rocky 的 1Train 和创作者 Tyler 的 11 月)。

集群 2 的平均特征支持这一点,因为我们可以看到语音和活跃度(指示现场表演)高于集群 1。

Average Features in cluster 2

集群 2 中的歌曲让我想起我感到欢欣鼓舞和快乐的时候。

因此,我将第二组标记为愉快的心情。

第 3 组

  1. 玛丽娅-我希望我错过了我的前任( RnB,灵魂)
  2. 爱莉安娜·格兰德-陈哥( RnB )
  3. Joji -忘不了你(专长。蛤蜊赌场)( RnB 电子)
  4. gianni & kyle -你有没有想过我?( RnB,嘻哈)
  5. RAYE -自信( RnB,舞蹈)

I Wish I Missed My Ex by Mahalia

当我看到这个集群的时候,它已经在我的情绪中尖叫了。在这个集群中有明显的 RnB 歌曲趋势,我喜欢的很多歌手都聚集在这里(Syd,Jorja Smith 和 Daniel Caesar)。有趣的是,平均而言,这一组是最适合跳舞的,可能是因为 Rnb 和舞曲的自然反弹。

由于这个集群的“浪漫”本质,我决定将集群 3 标记为我的浪漫心情!

预测我的情绪标签

每首歌现在都被贴上了各自心情的标签。我想看看我们是否可以训练一个监督学习模型,然后准确地预测给定歌曲时我的情绪。

我将数据集分成训练集和测试集(70:30),并在训练集上训练 4 个不同的分类器模型。

  1. 随机森林
  2. KNN
  3. SVM
  4. MLP 分类器

每首歌曲的情绪标签被用作目标数据,我们的目标是正确预测(寒冷,充满活力,欢快,浪漫)。

混淆矩阵和 F1 分数

为了评估分类器的性能,我比较了它们的混淆矩阵。

混淆矩阵是查看哪个分类器误差最小的好方法,在理想世界中,一个完美的分类器只在对角线轴上有值(100%准确)。

我们可以在下面看到,大多数分类器都非常擅长识别充满活力的情绪歌曲。SVM 显示出最高的错误,例如,它把 11 首寒冷的歌曲归类为浪漫的歌曲。另一方面,随机森林显示出最少的错误,具有最少的假阳性。它能够预测所有充满活力的歌曲,除了 1!

然后,我计算 F1 分数,定量比较每个分类器的准确性。它是一个分类器的精度召回的调和平均值(这里解释得很好)。只有当分类器的精度和召回率都很高时,F1 分数才能很高。

F1 Scores for each classifier

不出所料,兰登森林得分最高,KNN 和 MLP 紧随其后,得分相同。或许,SVM 的性能可以通过调整内核等超级参数来提高。

厉害!我们现在有了一个强大的模型选择,开始根据看不见的歌曲预测听众的情绪。

特征重要性

当使用随机森林分类器时,我们可以输出每个特征相对于它们在构建森林时提供的信息量的重要性(减少熵)。

Feature Importance from Random Forest

声音是最重要的。这可能是因为数据集中原声/非原声歌曲之间的明显区别,以及大多数 Chill 集群是如何由高度原声的乐器音轨组成的!

另一方面,语言能力和舞蹈能力似乎是数据中最没有帮助的特征。这可能是因为我听的大多数歌曲都含有大量的话语,因为我喜欢说唱和嘻哈音乐。这也表明了我听的大多数歌曲都是适合跳舞的(所以至少我可以说我的音乐在大多数时候并不无聊👊).

结论

总之,这个项目是探索我的 Spotify 播放列表数据并了解更多关于我的音乐品味和行为的有趣方式。

和音乐评价一样,一首歌能创造出什么样的情感和感受,这是一个人的主观性问题。尽管如此,我认为在了解每个独特的听众的口味之后,我们可以慢慢地但肯定地开始在更大的范围内理解这些感受。

作为这个项目的后续,我们有可能预测听众的情绪。如果能建立一个推荐引擎,向听众推荐类似情绪的歌曲,那就太棒了!

感谢你阅读这篇文章,我希望你已经学到了一些新的东西(并且发现了一些新的音乐)。如果你想聊天或者有任何反馈,请在下面评论!

我在这里写的代码。

和平 Out✌️

预测 NBA Instagram 与机器学习的互动

原文:https://towardsdatascience.com/predicting-nba-instagram-engagements-with-machine-learning-f3a43d0aeef6?source=collection_archive---------42-----------------------

今年早些时候,我有幸参加了在新泽西 Secaucus 联盟办公室举行的 2019 NBA 黑客马拉松。作为申请过程的一部分,NBA 提出了两个技术筛选问题——一个涉及计算 2019 年季后赛每场比赛的高级球员指标,另一个侧重于预测 1000 个 Instagram 帖子被 NBA 雇佣的数量。

第二个筛选问题非常适合机器学习,我真的很享受在这个问题上全力以赴的时光。在这篇文章中,我将概述我的总体方法和最终解决方案。

问题陈述

手头的任务相当简单:我们有超过 7500 个 NBA Instagram 帖子的相关数据,根据这些数据,我们被要求预测 1000 个帖子的参与人数。

这两个数据集包括以下字段:

  • 发帖时的关注者(发帖时 Instagram 关注者的数量)
  • 创建日期(帖子的日期戳)
  • 文章类型(文章分类为照片、相册或视频)
  • 描述 (Instagram 帖子标题/描述)
  • 参与度 (Instagram 参与度——我们将对我们的抵制活动进行预测)

解决方案根据预测约定的平均绝对百分比误差进行分级,这可以通过找到 1000 个预测中每个预测的百分比误差的平均值来计算。

由于目标变量(参与度)是由 NBA 人为生成的,任何解决问题的方法都是公平的!让我们深入了解我是如何用机器学习来应对这一挑战的。

第一步:探索性数据分析

就像几乎所有的机器学习问题一样,我的第一步是通过探索性数据分析(EDA)在笔记本上熟悉数据。我们可以将训练数据集导入到熊猫数据框架中,并从查看数据开始。

接下来,我喜欢创建简单的可视化来更好地理解什么可能影响我们的目标变量。首先,我们可以从我们的数据集中绘制出参与次数与两个连续变量的关系:发帖时间和发帖时的关注者。

Engagement vs Post Date-Time

Engagement vs Followers at time of post

从这些想象中,我很快注意到两个观察结果。首先,NBA 休赛期(7 月至 10 月)的参与度似乎有所下降。此外,除了一些异常值,似乎有两个不同的“波段”的业务。

帖子参与之间的这种明显区别表明,我们可能正在查看两种不同类型的 Instagram 帖子,仔细看看我们的数据集,我们有一个分类变量在其他连续变量中脱颖而出: Type 。摘下我们的分析帽子,这具有定性意义——insta gram 照片可能会表现出与自动播放视频明显不同的参与度表现。通过根据帖子类型将我们的帖子映射到特定的颜色,我们可以更清楚地了解正在发生的事情。

Videos appear to be in a league of their own

看起来,当谈到一个给定帖子的参与次数时,我们实际上有两种类型:视频和照片/相册。为了更深入地了解这一点,我们可以通过叠加多个直方图来检查这些职位类型的聘用分布。

基于上述分析,很明显,为了有效地解决这个问题,我们可能需要建立多个机器学习模型。虽然有可能创建一个处理所有帖子类型的单一模型(使用“虚拟”分类变量),但这个模型很可能只会适度地推广。在我们开始构建我们的机器学习模型之前,我们先来看看这个问题中可能是我最喜欢的部分:特征工程。

步骤 2:特征工程

虽然我们的数据集只包括 4 个“开箱即用”的输入字段,但我们可以通过直觉和创造力创建更多的功能。两个字段包含丰富的基础数据,可用于生成额外的有价值的特征:创建日期描述。让我们看看如何从这些领域中梳理出有用的特性。

创建日期- 直觉告诉我们,休赛期的帖子可能比 NBA 赛季中期的帖子表现更差,同样,美国东部时间凌晨 1 点的帖子可能比美国东部时间晚上 7 点的帖子表现更差。从这个假设出发,我们有必要从我们独有的时间戳中提取一些常见的与日期相关的特征,比如:月、工作日和小时。

我们可以使用 Pandasdt系列访问器对象在我们的训练集数据框架中创建这些新特征,如下所示:

posts_df['month'] = posts_df.created_ts.dt.month
posts_df['weekday'] = posts_df.created_ts.dt.weekday
posts_df['hour'] = posts_df.created_ts.dt.hour

使用一些箱线图,我们可以更好地了解这些新功能是否指示帖子的参与度。

Engagements vs Instagram post Month

Engagements vs Instagram post Weekday

Engagements vs Instagram post Hour

描述- 我们的 Instagram 帖子描述了无尽的可能特征的来源。首先,我们可以检查一些可能影响帖子性能的连续和分类特征。以下是我能确定的一些最有影响力的特征:

  1. 长度 —我们可以很容易地计算出每个职位的描述长度,并检查与聘用的对应关系。对于所有类型的职位,描述长度和聘用之间存在负相关关系。下面是视频的散点图。

For Videos, there seems to be a negative correlation between Description Length and Engagements

  1. 表情符号— 虽然看起来很傻,但我很好奇表情符号是否会影响帖子的参与度。在我们的数据集中,表情符号显示为’??' string,所以我们可以在我们的描述中搜索这个子串,并创建一个数据透视表来理解结果。

🚀 Emojis! Continuous performance.

我们可以构建一个数据透视表,显示每个月的参与人数中位数,根据职位描述是否包含表情符号进行分组。值得注意的是,月复一月,描述中有表情符号的帖子表现优于没有表情符号的帖子。

  1. 标记账户——虽然我们无法分析图片本身,但我们可以通过对 NBA 球员的潜在标记,围绕 Instagram 帖子做出有根据的猜测(例如:“@ Stephen curry at the buzzer!”)。解决这个问题有点棘手,如果我们看看表现最好和最差的帖子,并确定是否有任何标记帐户与参与度高度相关,这可能是最容易的。

Top 20 performing posts — looks like the audience has some favorite players

看看排名前 20 的表演帖子,我们看到两名球员脱颖而出:勒布朗詹姆斯(@金詹姆斯)和凯里·欧文(@凯里欧文)。事实上,77 篇热门帖子中的每一篇都提到了这两个球员中的一个或两个。陪审团已经明确表态,他们有一些喜欢的球员。我们将确保为勒布朗和垂怜经创建分类变量(例如: contains_kingjames ),以表示给定的帖子描述是否包括对相关球员的提及。虽然这种方法可能不适用于预测未来 10 年的职位表现(外推),但由于我们正在解决一个插值问题(属于我们训练数据集的职位),这似乎完全有效。

Bottom 20 performing posts (worst at the top)

同样,我们可以在表现最差的职位中发现一些趋势。提到替代联赛或比赛的帖子(例如: NBA 非洲赛,WNBA,Jr NBA 等。)似乎都表现不佳。此外,包含大量标记球员的描述似乎也会出现在我们列表的底部。我们可以通过创建一个分类变量(contains _ alternate _ league),以及一个连续变量( player_mentions )并检查结果来测试这些假设。

Be warned if you post about an alternate league and mention a slew of players!

第三步:建模

创建了自定义功能后,我们可以开始测试不同的回归算法来预测雇佣关系。在高层次上,当选择算法时,我们将把数据分成两个子集:训练数据和测试数据,使我们的模型适合训练数据,并对我们的测试数据进行预测。然后,我们将计算我们的预测和测试数据集中的实际值之间的误差,并比较我们的各种模型。

虽然让我们的模型适应所提供的整个训练数据集(7500 个 Instagram 帖子的参与度值)很有吸引力,但我们会发现自己无法评估我们的模型对数据的预测有多好没有看到。我们将被迫对用于拟合模型的训练数据进行预测,这很可能导致过度拟合。相反,我们将留出一部分训练数据集,并将其视为测试数据集。

如前所述,我们需要创建一个模型来对视频进行预测,并创建另一个模型来对照片和相册进行预测。因此,我们首先将训练数据分成两个较小的数据集:视频数据和照片/相册数据。然后,我们可以使用 SciKitLearn 的 train_test_split 将每个数据集进一步分割成训练集和测试集。以下是用于预测视频参与度的各种初始模型的计算平均绝对百分比误差(我们提供的误差函数):

根据我们的初始模型,我们的线性回归模型对以前没有见过的数据做出预测,平均误差约为 3%。平均雇佣值约为 692,000 次雇佣,我们做出的预测平均错误 21,036 次雇佣(不错!)

在我们继续微调我们的线性回归模型之前,重要的是停下来考虑这样一个事实,即我们的视频数据集的分割有可能导致非常有利的训练数据。换句话说,我们只是运气好吗?我们是不是碰巧通过让他们在 Instagram 帖子上做出预测来测试我们的模型,与 K 近邻模型相比,线性回归模型更容易做出预测?有可能!为了更加确信我们选择了正确的算法,我们将利用交叉验证

交叉验证是一种技术,通过这种技术,我们可以评估模型归纳为独立数据集的能力,并更有把握地排除好运。最常见的交叉验证形式之一是 K-Folds 交叉验证,其中数据集被分成 K 个部分(或“折叠”),从一个折叠创建测试集,从其余折叠创建训练集。重复这个过程,直到每个折叠都被用作测试数据集,并且我们已经为每个独特的训练和测试计算了模型的误差。

source: K-folds cross-validation, Kaggle

在执行一轮 K 倍交叉验证(K 值为 5)后,我们可以通过对每次训练和测试的平均绝对百分比误差进行平均来再次比较我们的不同模型。毫不奇怪,我们的线性回归模型仍然表现最好(平均绝对百分误差为 3.0%)。

这个过程可以在我们的照片/相册中重复,在我的例子中,线性回归再次表现得最好。

随着我们最终模型的选定,我们可以通过对我们训练数据的整体训练新的线性回归模型来结束这个项目。值得注意的是,SciKit-Learn 的标准线性回归模型实际上是一个普通的最小二乘(OLS)回归。该模型优化了我们的特征系数,以最小化观察到的和预测的参与度值之间的差的平方和。在我们的具体应用中,我们的任务是针对平均绝对百分比误差(MAPE,一种不同的损失函数)进行优化。从高层次来看,OLS 是规模依赖型的,而 MAPE 是而不是。由于我们没有任何明显的异常值会扭曲我们的模型,我决定用 SciKit-Learn 的“普通”线性回归模型来训练我的线性回归模型。我在这个项目上的工作的一个改进将是创建一个定制的求解器来根据定制的损失函数优化特征系数——这是另一个有趣的项目!

结论

由于这个挑战是基于人工 Instagram 参与数据,不幸的是,我可能永远不会知道我最终预测的准确性。然而,我所知道的是,我的提交奖励了我一次去 NBA 总部的旅行,与体育分析世界中一些最聪明的头脑一起参加进一步的数据科学挑战。

有机会将我最大的两个爱好——数据科学和篮球——结合在一起无异于梦想成真,能够会见来自 NBA 各地的分析师是一次我将永远珍惜的经历。

Team 3!

posted @ 2024-10-14 11:49  绝不原创的飞龙  阅读(100)  评论(0)    收藏  举报