成为数据科学家的实用指南
现在是成为数据科学家的最佳时机。以下是方法。
数据科学家是目前市场上最热门的工作之一。对数据科学的需求是巨大的,而且只会增长,而且它的增长速度似乎比数据科学家的实际数量要快得多。因此,如果你想改变职业生涯,成为一名数据科学家,现在正是时候。
Become a Data Scientist
谁是数据科学家?
数据科学家是在数据、算法和数据可视化方面拥有深厚知识的专家。要成为一名数据科学家,你需要具备团队合作的能力,理解数据结构,分析数据,设计和创建图表,编写简洁的代码。
什么是数据科学家工资?
数据科学家是目前业内最抢手的工作之一,它有很多额外津贴。他们每年可以赚 12 万到 18 万美元。相比之下,软件开发人员的收入在 11 万至 13.5 万美元之间。
当然,数据科学家的工资取决于他们的具体角色,但他们通常在分析或机器学习领域工作,经常处理大型数据集。
他们需要有优秀的分析能力,编程或数据库经验,以及很强的写作能力。
数据科学家职位描述
数据科学家的工作是开发和分析数据,然后分析数据以创造洞察力。这些见解可以以多种方式使用,包括在各种业务决策中,并且通常用于提出建议或帮助为新产品或服务建立业务案例。数据科学家的工作涉及各种不同的数据科学主题,例如:
商业智能、网络分析、自然语言处理、社交媒体分析、预测分析、机器学习、数据挖掘等等。
你可以在 LinkedIn 或 AngelList 或其他各种网站上找到一些关键职位的列表以及如何申请这些职位,这些网站允许你浏览你所在地区的工作机会。
成为数据科学家需要哪些技能
数据科学家的需求量很大,所以如果你对这类工作感兴趣,你需要具备全面的技能。
你需要的一些技能包括:
-出色的分析技能,包括理解数据的能力
-良好的项目管理技能,包括计划和管理项目以及与他人沟通的能力
-良好的沟通技巧,包括清晰简洁的写作能力
-了解数据完整性和隐私问题的重要性,以及了解用户的重要性
-出色的量化技能,包括利用数据获得洞察力和清晰传达结果的能力
-理解复杂信息和解释结果的能力
-批判性思考和解决问题的能力,既包括理解全局,也包括就如何解决特定问题做出具体决策
数据科学家的技能
从更专业的角度来看,你还需要具备以下条件——尤其是如果你申请的是非初级职位的话:
-精通 Excel、SAS、R 或 Python (注:Excel 是一种编程语言)
-机器学习方面的经验
-数据库经验(如关系数据库或 NoSQL 数据库)
-数据可视化经验
-强大的技术背景和/或计算机科学背景
-快速学习的能力
此外,数据科学家通常在团队中工作,这意味着你会被要求快速学习很多东西。
这不全是关于数据,而是信息,所以如果你想发展在这个行业取得成功的必要技能,你需要成为一个积极的学习者。
成为数据科学家的 3 个步骤
- 在 GitHub 上构建你的知识库,并开始一个开源项目。你可以从 Kaggle 获取一个数据集,然后围绕它构建一些东西。通常分类问题往往更容易。这将让你磨练自己的技能,并向潜在雇主展示你的参与度。
- 参加关于数据科学和机器学习的脸书 /LinkedIn 小组。尝试查找附近的聚会和会议,并参加它们以结识更多的人。有人指导总是好的。
- 多写代码!数据科学最终是一种实用技能。在你的社交媒体上分享它——更新你的 LinkedIn 个人资料,以便有更好的机会找到工作。
祝你好运!
让我们保持联系,继续学习数据科学。](https://creative-producer-9423.ck.page/c3b56f080d) 
Data Science Job
最后,如果你想了解成为一名数据科学家意味着什么,那么看看我的书数据科学工作:如何成为一名数据科学家,它将指导你完成这个过程。
如果您想了解更多,请阅读我关于成为数据科学家的其他文章:
机器学习实用指南
现在就开始你的机器学习生涯吧!
我们大多数人都已经看到了机器学习工作的迅速崛起,但对这个特定领域来说仍然是新手。问题是大部分 ML 的工作岗位都不是“入门级”的,所以很多人都在努力训练自己。这里有一些有用的信息给你,关于 ML 的不同阶段包括什么。
Start your dream career in Machine Learning
开始:开始
在每一个新的领域,都有人从新手开始,增加他们的知识。如果你是一个必须学习基础知识的人,并且你感到停滞不前,看看下面的列表,看看你下一步需要做什么。
是的,你可能需要更多的投入。好消息是这些资源在网络上非常丰富。
通过流行的视频和书籍自学
书籍对于熟悉这个领域很有帮助。视频提供了更多的教学指导,通常是灵感的良好来源。确保你看了关于 ML 技术的教程,如果你犯了一个错误,你有能力改正它。视频(尤其是 Udemy 或 Coursera 上的课程)可能需要付费,但这两种格式的视频通常都是免费的。
这里有一些资源可以找到一本关于 ML 的好书。
向专家学习
如果你买不起书,但你知道你想上 ML 的课,找一个好的导师是很好的,最好是该领域的前专业人士。当你面对一个你不知道如何解决的问题,需要一个问题的答案时,一个好的导师会很有帮助。
在许多情况下,如果你没有足够的资金,参加在线课程也是有利的。通过参加本课程,您通常可以获得进一步的培训,从而从本课程中获得最大收益。
高级:学习掌握
进入这个领域的下一步是学习如何掌握你正在使用的工具(又名计算机)。
一些提示:
- 学习如何编码。
- 学习 Python。
- 浏览不同教程中的机器学习代码。
- 学习数据挖掘。
- 擅长统计学。
- 学习机器学习理论和实践。
这是许多人在这一点上已经完成所有训练的阶段,现在可以进行一些真正的机器学习。重要的是,你不要让这成为一个障碍,当你对 ML 越来越熟悉时,你要继续学习和练习。
这一阶段在你的职业生涯中的重要性可能显而易见,所以确保你仍然了解自己的最新发展。
进阶:将好的开始转化为最好的职业
这是职业生涯中最重要的阶段之一。在这个阶段,你将从一名学生转变为你所在领域的专家。你必须让你的老板相信你有资格晋升。
- 向你的主管学习,找一个了解你正在做的具体项目的人。
- 学习如何沟通和展示自己
- 学习如何使用你获得的新技能来解决新问题
你是拥有所有这些东西的幸运儿之一吗?
太好了,这就是你成为机器学习大师的途径。
Data Science Job
最后,如果你想了解成为一名数据科学家意味着什么,那么看看我的书数据科学工作:如何成为一名数据科学家,它将指导你完成这个过程。
异常值检测方法实用指南
如何系统地检测单变量数据集中的异常值
Photo by Tom Paolini on Unsplash
我将讨论在 R Studio 中实现的四种异常检测方法的细节。我将提到异常值是什么,为什么它很重要,为什么会出现异常值。这些方法如下:
- 图基的方法
- 推特异常检测
- z 分数法
- 平均绝对偏差(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
现在,是时候深入研究异常检测方法了。
- 图基氏法(盒须)
Figure 3: Box and Whiskers
- **最小值:**数据集中的最小值(Q1–1.5 * IQR)
- **第一个四分位数:**低于该值的 25%的数据包含在内( Q1 )
- **中值:**数字范围中的中间数( Q2 )
- **第三个四分位数:**包含高于该值的 25%的数据( Q3 )
- **最大值:**数据集中的最大值( Q3+1.5*IQR )
- 四分位范围(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:数组与对象
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 实现实用的机器学习
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 的实用机器学习
使用 Keras 快速构建实用的 ML 模型
Image by Comfreak from Pixabay
这篇文章是关于什么的?
在本文中,我尝试解释了什么是 keras,并总结了我在 keras 中构建模型的所有方法,解释了所有实用的技巧以及与之相关的所有细微差别。这篇文章将教你几种在建模中使用的增加复杂性的常用技术。
- 序列模型
- 功能 API(高级)
- 模型子类化(高级)
我们将使用一个非常著名的叫做 MNIST 的手写数字数据集,用这三种不同的方法制作 CNN 模型。我还将介绍基本的加载和使用数据。
这篇文章不是什么?
本文不会教你机器学习背后的理论或数学。如果你是机器学习的新手或者刚刚开始学习,我会提供一些资源来帮助你。
- 吴恩达的《机器学习》:这是一门非常适合初学者的课程,怎么推荐都不为过。
【免费】https://www.coursera.org/learn/machine-learning - Deep Learning 的深度学习专业化:这也是吴恩达教的,这是一个完整的专业化,而且非常好。https://www.coursera.org/specializations/deep-learning
好了,现在让我们开始学习。
Keras 是什么?
Keras 是一个高级神经网络 API,用 Python 编写,能够在 TensorFlow 、 CNTK 或 Theano 之上运行(我们将使用 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_initializer 和 bias_initializer :初始化方案为层创建权重。默认是 Glorot 制服。
- kernel _ regulator和bias _ 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 构建模型遵循一组规则:
- 层实例是可调用的,并返回一个张量作为其输出。
- 要实际创建一个模型,我们必须将输入和输出张量传递给 tf.keras.Model 实例。
- 模型的训练类似于顺序模型的训练。
说够了,现在让我们建立一个。
模型的训练和评估保持不变,所以我在这里只给出代码。在这一次,我们只是没有使用验证集,而是使用测试集来评估训练后的未知数据的模型。
10000/10000 [==============================] - 1s 71us/sample - loss: 0.0368 - acc: 0.9914
[0.03678753161538142, 0.9914]
现在,您知道了如何根据自己的喜好在模型中建立奇怪而复杂的连接,但是如果您必须定义自己的前进路线,那该怎么办呢?我们有一个解决方案,但首先,你应该欣赏你的努力工作,这个猫的形象。
模型子类化
我们已经建立了我们的模型,定义了我们自己的数据流,但是我们仍然不能完全控制模型的向前传递。我们可以通过子类化 tf.keras.Model 实例来构建一个完全可定制的模型。
这涉及到几个步骤,请密切注意:
- 我们要创建一个 tf.keras.Model 的类实例,并在 init 方法中把所有的层定义为类的属性(基本可以比作不熟悉 python 类的人用的构造函数)。
- 然后我们必须在 调用 方法中定义我们的自定义向前传递。
- 如果我们想使用我们的子类模型作为函数式模型的一部分,我们也可以覆盖compute _ output _ shape方法(可选方法
让我们将理论付诸实践,为识别 MNIST 数据建立一个子类模型:
让我们训练、评估和测试我们的模型。
恭喜你,现在你知道了制作几乎所有类型模型的工具。在得出结论之前,让我们再学习一件非常方便的事情,就在猫休息之后。
when you code and your cat motivates you
自定义回调
回调是传递给模型的对象,用于在训练期间自定义和扩展其行为。您可以编写自己的自定义回调,或者使用内置的TF . keras . callbacks包括:
- TF . keras . callbacks . model check point:定期保存你的模型的检查点。
- TF . keras . callbacks . learning rate scheduler:训练时动态改变学习率。
- TF . keras . callbacks . early stopping:当验证性能停止提高时,中断/停止训练过程。
- TF . keras . callbacks . tensor board:使用 TensorBoard 监控模型的行为。
fit 方法的回调参数需要一个回调数组,我们将在那里传递回调。在本例中,我们将使用两种最常见的方法 LearningRateScheduler 和 EarlyStopping。
让我们像以前一样训练、评估和测试我们的模型。
结论
我希望你觉得这篇文章很有用,而且因为有猫而有点娱乐性。如果你事先知道一点机器学习,这可能是有用的,因为这篇文章完全是面向实践的,希望现在你应该出去做一些真正令人敬畏的项目。
下面是整个 代码 的链接:
如果你对这篇文章有任何疑问,或者如果我犯了什么错误,欢迎在评论中发表。这里有一个 chonky 猫阅读整篇文章。
你可以在推特这里找到我。
如果你更喜欢 LinkedIn,请点击这里找到我。或者如果 Github 是你的东西,我也在那里。
如果你对机器学习、Tensorflow 有任何问题,请随时联系我,或者如果这是你的事情,就用 cat gif 来联系我。我不是专家,但我会尽力帮助你。
❤再见
实用数字——通过函数理解 Python 库
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 提供了多种创建数组的方法。有像zeros
和ones
这样的特殊函数,它们分别创建只包含 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
符号构造的切片对象时,就会发生基本切片。切片期间,并不总是需要所有三个start
、stop
和step
都出现在方括号内。如果切片时出现负整数 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
模块的rand
和randn
方法用于分别生成所需维数的随机值和正态分布值的矩阵和张量。当我们想要为深度学习模型的第一次正向传播生成随机权重时,这些函数就很方便了。我们还可以计算输入数据的平均值、中值和标准差,如下所示。
#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 是机器学习和深度学习之旅的第一个重大步骤。
希望这篇文章对你有用。请发表你的看法,评论和赞赏。
数据科学家实用心理学
8 种常见的认知偏差及其对你的意义
你并不像你想象的那样有逻辑。我们都不是。我们每天都容易受到认知偏差的影响。如果你有幸读过丹尼尔·卡内曼的《T2 思维》、《快与慢》和《T3》,那么你对这个现实就再熟悉不过了。我们是不完美的生物,世界是一个不完美的地方。
鉴于数据科学的实验性和研究导向性,我们比大多数人更容易受到这些偏见的影响。我们代表着数据和洞察力之间的媒介。我们将字符串、整型和浮点转换成可操作的建议、模型和可视化。
这个角色伴随着巨大的责任。当我们的思维被认知偏见劫持时,坏事就会发生。做出不明智的决定,传播不正确的信息。在这篇文章中,我们将通过解决常见的偏见来避免这些情况,它们可能出现在哪里,以及如何识别它们。
确认偏差
可以说是认知偏差的典型代表,确认偏差是指我们倾向于确认我们现有信念的事物。这是有充分理由的。确认偏差无处不在。我们希望自己是对的。见鬼,错了真不舒服。你真的能责怪我们的思想时不时地试图欺骗我们吗?
这对数据科学家来说尤其危险。想想你最后一次执行某种分析的时候。如果你对这个项目感兴趣,那么你可能会带着一些假设、想法或你认为会发生的有利结果进入这个项目。这就是偏见发挥作用的地方。
你更有可能把你的发现解释为支持你先前信念的证据。我们不能完全避免这一点,但是如果你意识到了这一点,那么你就可以意识到什么时候可能会出现确认偏差,并相应地调整你的想法。
建议:在进行分析之前,写下任何假设、想法和信念。之后,重新阅读这份笔记并再次检查你的见解,确保它们是正确的。
叙事谬误
我们喜欢好故事。至少提利昂是这么认为的。数据科学和分析也不例外。叙事谬误是这样一种观点,即我们想把点点滴滴连接起来编织一个故事,即使没有故事可讲。我们迫切希望现实符合我们的世界模型,但它往往不符合。
让我们考虑一个例子。你是一家消费互联网公司的数据科学家,日活跃用户数量增加了,但花在产品上的平均时间却下降了。你怎么解释这个?
有人可能会用一个类似于“我们引入了对产品参与度较低的低意向用户”的故事来报告这一点。这似乎是合理的,但我们能肯定这一点吗?没有更多的研究来证实这一说法,我们只是以一种似乎最合理的方式将这些点联系起来。这并不总是一件坏事;故事是传达真知灼见的好工具,但应该明智而有目的地使用。
建议:你看到的信号是否足够强,足以说明它不是由噪音引起的?如果是这样,你可能已经有了一个因果故事。没关系,但是花一点时间根据你现有的数据列出其他可能的原因。意识到你的故事是许多可能解释中的一种。
参照效应
简而言之,锚定效应表明你判断的第一件事会影响你对接下来事情的判断。这就是为什么当你买东西的时候,你会首先得到一个更贵的产品。你固定在那个昂贵的价格点上,所以当你看到更便宜的东西时,它看起来并不那么糟糕。
当我们必须进行比较时,你可以看到这将如何影响数据科学家。如果你正在考虑一些新的独立功能的影响,你如何解释第一个功能将会影响你如何看待第二个功能。
当你比较两个组或使用事物作为基准时,记住这一点很重要。
建议:提前下锚。如果你认为任何一个特征的有效升力大约是 2%,那么在你分析之前把它写下来。这允许你在你的判断被改变后回到你的无偏基准。
可用性偏差
我们的想法很大程度上是基于我们过去的经验。这不是突破性的信息,但可用性偏差使它更进一步。可用性偏见说我们不仅仅依靠我们的经验来做决定,我们也更喜欢那些更容易想到的经验。
当我们发展假设时,这是显而易见的。如果你最近读了一篇关于深度学习技术的论文,在为你的下一个模型进行头脑风暴时,你会更倾向于走这条路。如果你在一个领域做了一个重要的项目,那么你将在这个背景下解释下一个项目。
你想到的第一件事会是最容易想到的。这并不一定意味着它是对的。
建议:在做决定和头脑风暴假设时,利用你的关系网。允许他人带来不同的视角。如果他们在不同的团队,致力于不同的问题,加分。
晕轮效应
光环效应描述了一种现象,即把事物的一小部分应用于整体。当谈到第一印象时,这种认知偏见经常在社会背景下被提及。我不必告诉你握手和介绍很重要。
作为数据科学家,我们经常与利益相关者合作项目,以便推动影响。尽管我们可能会有不同的想法,但影响力的游戏是真实存在的。做好工作是一回事,但有效地让别人支持你的工作是另一回事。这是一个棘手的问题,但光环效应是一个强有力的起点。以正确的方式开始你的项目,然后观察事情的发展。
建议:大多数项目都是从某种简报或会议开始的。这段时间,你要带头。作为一个内向的人,这可能很难做到,但通过乐观的视角解释你对项目的愿景对培养热情和组织支持大有帮助向前迈进。
沉没成本谬论
我个人最喜欢的一个观点是,在管理时间表和项目时,沉没成本谬误会打击我们。当你非理性地抓住已经让你付出代价的东西不放时,它就会发生。
我们经常继续做一个项目,仅仅是因为我们已经投入了时间,即使它不是我们能做的最有影响力的事情。意识到这一点并不难。如果它正在发生,你可能已经有了直觉。棘手的部分是打电话。
退出一个项目并继续前进是一颗难以下咽的药丸。虽然这份清单上的其他偏见肯定更隐蔽,但沉没成本谬误可能是最难正面应对的。
建议:尽管很难,但要关注未来的结果。过去的就过去了。这是无法改变的,也不应该左右你的决定。就目前的项目状况而言,这是实现未来成果的最佳投资时机吗?如果答案是否定的,那就继续这段旅程吧。
知识的诅咒
交流并不容易。当知识的诅咒控制了我们,这就更不容易了。知识的诅咒是当你知道一些事情,并认为它对每个人都是显而易见的。对于数据科学家来说,这可能是一个严重的致命弱点。
我们天生就是技术型的。我们做了大量的分析。我们挖掘了许多假设。慢慢地,我们用数据建立起一幅幕后真实情况的画面。
当我们分享这些见解时,往往会出错。我们不记得其他人没有像我们一样尽职尽责。他们对这个问题的理解还不一致。他们还没有探究所有的假设。因此,当我们交流结果时,结果会显得难以解释或过于复杂。
建议:设身处地为你的利益相关者着想。对于对这个问题没有深入理解的人来说,这有意义吗?我的演示文稿或文档是否过于复杂?尽可能清晰简洁地交流。首先关注可行的见解,然后如果需要的话再深入。
信息偏差
作为数据科学家,我们渴望信息。这是我们工作的核心。我们对细节的关注是我们最大的优点之一,也是最大的缺陷。如果你不熟悉,信息偏见是指在不影响行动的情况下继续寻找信息的倾向。
分析瘫痪是一种无法避免的瘟疫。在某种程度上,尽管探索很重要,但深入研究问题的收益减少不会直接影响项目的更大成果。在这种情况下,你的时间最好花在别的地方。
建议:从最小可行性分析开始。尽可能少地获取信息和理解的基线。向你的利益相关者展示并获得他们的反馈。令人惊讶的是,通常情况下,这就足够了。
摘要
意识到我们并不像自己想象的那样能够控制自己的决定和思维,这既令人不安又令人着迷。随着世界上可访问信息的深度,清晰和相对无偏见的思维已经成为一个经常被忽视的超级大国。
在这篇文章中,我列出了一些我认为有用的实践。这些可以大致分为以下几点:
- 事先把事情写下来
- 与不同观点的人交谈
- 问自己一些尖锐的问题
- 设身处地为利益相关者着想
这四样东西会带你走很长的路。事先把事情写下来会让你保持诚实。与他人交谈将帮助你检查你的认知盲点。难题会让你正面面对艰难的决定。对利益相关者的同情会帮助你产生更有意义的工作。
认知偏差是不可避免的,但这并不意味着我们必须在他们掌控方向盘时无所事事。有了正确的系统,我们可以采取措施驯服它们。勇往直前去征服。
感谢阅读!请随意查看下面我的一些类似文章,并订阅我的时事通讯中的以获得有趣的链接和新内容。
你可以在 Medium 上关注我更多类似的帖子,也可以在 Twitter 上找到我。想了解更多关于我和我在做什么,请查看我的网站。
用 Python 和 Plotly 实现实用统计和可视化
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?)*
成功描述客户群的实用步骤和注意事项
如何使用特征丰富的数据集,通过 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 value
和number 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 sales
和average 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 三重奏(recency
、frequency
和average 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 value
、orders per customer
和average 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 简介中找到
参考
- 关于客户细分优势的更广泛讨论
- 关于主成分分析的直观介绍
- 对于集群启动算法的应用
- 对于某些 k-means 缺点的批判
原载于 2019 年 9 月 23 日https://diegousei . io。
处理缺失值的实用策略
大多数数据科学项目中的主要挑战之一是找出一种获得干净数据的方法。总时间的 60%到 80%花在清理数据上,然后您才能对其进行有意义的理解。对于 BI 和预测分析项目来说都是如此。为了提高数据清理过程的有效性,当前的趋势是从手动数据清理迁移到更加智能的基于机器学习的过程。
确定我们正在处理的缺失值的类型
在我们深入研究如何处理缺失值之前,弄清楚缺失值的性质是至关重要的。根据缺少的数据与数据集中的其他数据之间是否存在关系,有三种可能的类型。
- 它们可能无法解释为什么一个列的数据与数据中的其他列一起丢失,并被称为完全随机丢失( MCAR )。这种情况的一个例子可能是由于某人不能赴约而导致调查数据丢失。或者管理员将他/她应该输入计算机的测试结果放错了地方。缺失值的原因与数据集中的数据无关。
- 有些情况下,一列中缺少的数据可以用其他列中的数据来解释。这就是所谓的随机失踪( MAR )。这方面的一个例子是,在一所学校里,超过分数线的学生通常会得到分数。这意味着如果一些学生的分数缺失,可以用分数低于分数线的那一栏来解释。缺失值的原因可以通过另一列中的数据来描述。
- 有些情况下,缺少的值与值本身有关。例如,高收入者可能不会公开他们的收入。缺失值与实际收入之间存在关联,并且不依赖于数据集中的其他变量。这被称为不是随机丢失( MNAR )
一般来说,删除缺失的行对 MCAR 来说是没问题的。输入数据适用于 MCAR 和马尔。如果是 MNAR,必须通过引入额外的列来建模。
处理缺失数据的策略
处理缺失值有几种策略。首先,可以使用像随机森林或 KNN 这样的算法,这些算法在处理缺失值时很健壮。第一种常见策略是删除缺少值的行。通常,任何单元格中缺少值的任何行都会被删除。有时,许多行将被删除的可能性很高。由于信息和数据的丢失,当没有足够的样本时,通常不使用这种方法。
可以用多种方式输入丢失的数据。它可能只基于缺少值的列中存在的信息。或者它也可以基于数据集中存在的其他列。最后,还可以使用分类或回归模型来预测缺失值。我们将通过下面的例子来讨论这些。
处理数字列中的缺失值
第一种方法是用下列策略之一替换丢失的值。
- 用常数值替换它。通常,这用于与领域专家讨论我们正在处理的数据。
- 用平均值或中间值代替它。这是一个不错的方法,特别是当数据很小时,但是它确实增加了偏差。
- 通过利用其他列中的信息,用值替换它。我们将通过下面的例子对此进行更多的讨论。
在上面的雇员数据集子集中,我们在三行中缺少薪水。我们也有他们居住的州和他们在数据集中的经验。
第一种方法是用列的平均值填充缺失值。这里,我们只使用缺少值的列中的信息。
在领域专家的帮助下,我们可以做得更好,并使用来自数据集中其他列的信息。
各州平均工资不同。用它来填写值。计算在德克萨斯州工作的人的平均工资,并用在德克萨斯州工作的人的平均工资替换缺少德克萨斯州工资的行。对其他州也这样做。
我们还能做得更好吗?除了国家专栏,我们还可以利用多年的经验。
计算在德克萨斯州工作的人员的平均入门级工资,并用德克萨斯州的平均入门级工资替换缺少德克萨斯州入门级人员工资的行。对其他州和其他级别进行同样的操作。
这为你提供了一套很好的估算数据的方法,尤其是当我们有一个领域专家来指导我们的时候。请注意,有一些边界条件需要处理。例如,对于一个居住在得克萨斯州的人,可能会有一行缺少工资和工作经验。有多种方法可以解决这个问题。一个直接的方法是用德克萨斯州的平均工资替换缺失值。
使用算法预测缺失值
另一种方法是创建一个简单的回归模型。这里要预测的列是使用数据集中其他列的薪金。如果输入列中有缺失值,我们必须在创建预测模型时处理这些情况。有许多方法可以管理这种情况,但一种简单的方法是选择没有缺失值的要素,或者在任何像元中选取没有缺失值的行。人们可以用不同的技术和不同的算法(KNN,老鼠等)进行实验。)并选择最准确的一个。这可能会在随后的帖子中涉及。
处理分类列中的缺失值
处理分类列中的缺失值比处理数字列中的缺失值要容易得多。
1.用常数值或最流行的类别替换它。这是一个很好的方法,尤其是当数据很小时,但是它确实增加了偏差。例如,假设我们有一个包含高中和大学学位值的教育专栏。如果数据集中有更多拥有大学学位的人,我们可以用大学学位替换缺失的值。
2.我们可以通过利用其他专栏中的信息对此进行更多的调整。如果数据集中有更多来自德克萨斯州的高中学历的人,我们可以替换缺失的值来反映德克萨斯州高中毕业的人。
3.也可以创建一个分类模型。我们要预测的列是教育,使用数据集中的其他列。
4.最常见和最流行的方法是将分类列中缺失的值建模为一个名为“未知”的新类别
摘要
我们讨论了处理缺失值的几种策略。根据数据的种类和问题的类型,所采用的方法会略有不同。
对于数值,最好利用数据中存在的模式。对于分类列,最好将其建模为一个新的级别。如果你有领域专家的帮助,在填写缺失值时,最好能采纳专家的建议。
需要注意的是,无论选择哪种输入方法,最好运行预测模型,从准确性的角度来看哪种方法效果最好。
训练音乐模型的实用技巧
这是“构建人工智能音乐生成器”系列的第二部分。我们将更深入地构建在第一部分中介绍的音乐模型。
这里有一个简单的概述:
数据编码及如何处理:
- 复调音乐
- 音符音高/持续时间
培训最佳实践:
- 数据扩充
- 位置编码
- 教师强迫
- 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 最具代表性:
这是看待它的另一种方式:
这是频率随时间变化的曲线图(也称为钢琴声)。您会注意到这个图表的两点:
- 一个单个音符是一个值的集合(音高+持续时间)
- 多个音符可以在一个单个时间点演奏(复调)
用音乐训练变形金刚的诀窍是弄清楚如何将这些 2D 数据符号化为一维。
注释—一对多
- 一个单个音符代表一个集合的值:
-音高(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。纯娱乐目的而已。