数据可视化的艺术——使用 Matplotlib 和 Ggplot2 实现天气数据可视化
数据可视化与其说是科学,不如说是一门艺术。为了产生良好的可视化效果,您需要将几段代码放在一起,以获得出色的最终结果。本教程演示了如何通过分析天气数据产生良好的数据可视化。
在第一节的中,我们使用 matplotlib 包制作了一个可视化。在第二节中,我们尝试使用 ggplot2 再现可视化。
在深入研究数据可视化艺术之前,让我们首先讨论一个好的数据可视化的基本组件。
良好的数据可视化的组成部分
一个好的数据可视化由几个组件组成,这些组件必须组合在一起才能产生最终产品:
a) 数据组件:决定如何可视化数据的第一个重要步骤是了解数据的类型,例如分类数据、离散数据、连续数据、时间序列数据等。
b) **几何组件:**您可以在这里决定哪种可视化适合您的数据,例如散点图、线形图、条形图、直方图、QQ 图、平滑密度、箱线图、对线图、热图等。
c) **映射组件:**这里你需要决定用什么变量作为你的 x 变量,用什么变量作为你的 y 变量。这一点非常重要,尤其是当数据集是包含多个要素的多维数据集时。
d) **秤组件:**在这里您可以决定使用哪种秤,例如线性秤、对数秤等。
e) **标签组件:**这包括轴标签、标题、图例、使用的字体大小等。
伦理成分:在这里,你要确保你的视觉化图像讲述的是真实的故事。在清理、总结、操作和制作数据可视化时,你需要意识到你的行为,并确保你没有利用你的可视化来误导或操纵你的观众。
本文将比较 Python 的 Matplotlib 和 R 的 ggplot2 包在分析和可视化天气数据方面的优势。
该代码将执行以下数据可视化任务:
- 它返回了 2005 年至 2014 年期间一年中每天的最高纪录和最低纪录的折线图。一年中每一天的最高温度和最低温度之间的区域用阴影表示。
- 覆盖 2015 年打破十年记录(2005-2014 年)最高或最低记录的任何点(最高和最低)的 2015 年数据散点。
数据集:本项目使用的 NOAA 数据集存储在文件 weather_data.csv 中。这些数据来自国家环境信息中心(NCEI)每日全球历史气候网络(GHCN-Daily)的一个子集。GHCN-Daily 由来自全球数千个地面站的每日气候记录组成。这些数据是从美国密歇根州安阿伯市附近的数据站收集的。
本文的完整代码可以从这个资源库下载:https://github.com/bot13956/weather_pattern。
I .使用 Python 的 Matplotlib 包进行气象数据分析和可视化
1.导入必要的库和数据集
**import** **matplotlib.pyplot** **as** **plt**
**import** **pandas** **as** **pd**
**import** **numpy** **as** **np**
df=pd.read_csv('weather_data.csv')
df.head()
2.数据准备和分析
#convert temperature from tenths of degree C to degree C
df['Data_Value']=0.1*df.Data_Valuedays=list(map(**lambda** x: x.split('-')[-2]+'-'+x.split('-')[-1], df.Date))years=list(map(**lambda** x: x.split('-')[0], df.Date))df['Days']=days
df['Years']=yearsdf_2005_to_2014=df[(df.Days!='02-29')&(df.Years!='2015')]
df_2015=df[(df.Days!='02-29')&(df.Years=='2015')]df_max=df_2005_to_2014.groupby(['Element','Days']).max()
df_min = df_2005_to_2014.groupby(['Element','Days']).min()
df_2015_max=df_2015.groupby(['Element','Days']).max()
df_2015_min = df_2015.groupby(['Element','Days']).min() record_max=df_max.loc['TMAX'].Data_Value
record_min=df_min.loc['TMIN'].Data_Value
record_2015_max=df_2015_max.loc['TMAX'].Data_Value
record_2015_min=df_2015_min.loc['TMIN'].Data_Value
3.生成数据可视化
plt.figure(figsize=(10,7))plt.plot(np.arange(len(record_max)),record_max, '--k', label="record high")plt.plot(np.arange(len(record_max)),record_min, '-k',label="record low")plt.scatter(np.where(record_2015_min < record_min.values),
record_2015_min[record_2015_min <
record_min].values,c='b',label='2015 break low')plt.scatter(np.where(record_2015_max > record_max.values),
record_2015_max[record_2015_max >
record_max].values,c='r',label='2015 break high')plt.xlabel('month',size=14)plt.ylabel('temperature($^\circ C$ )',size=14)plt.xticks(np.arange(0,365,31),
['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug',
'Sep','Oct','Nov','Dec'])ax=plt.gca()ax.axis([0,365,-40,40])plt.gca().fill_between(np.arange(0,365),
record_min, record_max,
facecolor='blue',
alpha=0.25)plt.title('Record temperatures for different months between 2005-2014',size=14)plt.legend(loc=0)
plt.show()
Weather data visualization using matplotlib.
二。使用 R 的 Ggplot2 软件包进行天气数据分析和可视化
我在想,我能不能用 R 的 ggplot2 包制作一个类似的上图?
因此,我决定进行类似的分析,看看我能与 ggplot2 接近到什么程度。
1.导入必要的库和数据集
library(tidyverse)
library(readr)
df<-read.csv("weather_data.csv")
2.数据准备和分析
**#convert temperature from tenths of degree C to degree C**df$Data_Value = 0.1*df$Data_Value **#functions to split date** split_function<-function(x)unlist(strsplit(x,'-'))year_function<-function(x)split_function(x)[1]
day_function<-function(x)paste(split_function(x[2],
split_function(x)[3],sep='-') **#create Day and Year columns** day<-sapply(as.vector(df$Date),day_function)
year<-sapply(as.vector(df$Date),year_function)
df<-df%>%mutate(Day=day,Year=year ) **#filter leap year and select 10 year observation period: 2005-2014** df_2005_to_2014<-df%>%filter((df$Day!='02-29') (df$Year!='2015'))df_2015<-df%>%filter((df$Day!='02-29')&(df$Year=='2015')) **#record min and max for each day of the year for the 2005-2014 period** record_max<-df_2005_to_2014%>%group_by(Day)%>%summarize(Max = max(Data_Value),Min=min(Data_Value))%>%.$Maxrecord_min<-df_2005_to_2014%>%group_by(Day)%>%summarize(Max = max(Data_Value),Min=min(Data_Value))%>%.$Min **#record min and max for each day of the year for 2015** record_2015_max<-df_2015%>%group_by(Day)%>%summarize(Max = max(Data_Value),Min=min(Data_Value))%>%.$Maxrecord_2015_min<-df_2015%>%group_by(Day)%>%summarize(Max = max(Data_Value),Min=min(Data_Value))%>%.$Min
3.为可视化准备数据
**#data frame for the 2005-2014 temperatures** y<-c(seq(1,1,length=365),seq(2,2,length=365))
y<-replace(replace(y, seq(1,365),'max'),seq(366,730),'min')
values<-data.frame(day=c(seq(1,365), seq(1,365)),element=sort(y),Temp=c(record_max,record_min))
q<-values%>%mutate(element=factor(element))**#data frame for the 2015 temperatures** max_index<-which(record_2015_max>record_max)
min_index<-which(record_2015_min < record_min)dat15_max<data.frame(max_index=max_index,
Tmax=record_2015_max[max_index])dat15_min<-data.frame(min_ndex=min_index,
Tmin=record_2015_min[min_index])
4.生成可视化
q%>%ggplot(aes(day,Temp,color=element))+
geom_line(size=1,show.legend = TRUE)+
geom_point(data=dat15_max,
aes(max_index,Tmax,color='max_index'),
size=2,show.legend = TRUE)+
geom_point(data=dat15_min,
aes(min_index,Tmin,color='min_index'),
size=2,show.legend = TRUE)+
scale_colour_manual(labels=c("record high","2015 break
high","record low","2015 break low"),
values=c('red','purple','blue','green'))+
xlab('Month')+
ylab('Temperature (C)')+
scale_x_continuous(breaks = as.integer(seq(1,335,length=12)),
labels=c('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug',
'Sep','Oct','Nov','Dec'))+
ggtitle("Record temperatures between 2005-2014")+
theme(
plot.title = element_text(color="black", size=12, hjust=0.5,
face="bold"),
axis.title.x = element_text(color="black", size=12,
face="bold"),
axis.title.y = element_text(color="black", size=12,
face="bold"),
legend.title = element_blank())
以下是使用 R 的 ggplot2 的输出:
观察
我尽力用 R 的 ggplot2 包制作了一个可视化效果,它与用 Python 的 Matplotlib 制作的非常相似。
在我看来,对于这个特定的项目,与 R 的 ggplot2 包相比,Matplotlib 产生了更好、更漂亮的可视化效果。与 ggplot2 相比,使用 matplotlib 生成可视化效果所需的工作量(代码行)更少。
如果您有兴趣下载 Matplotlib 和 ggplot2 执行的源代码,请访问下面的 Github 资源库:https://github.com/bot13956/weather_pattern。
请留下关于如何改进 ggplot2 可视化的建议的反馈意见。我很肯定质量可以提高。
预测的艺术
Photo by Emmanuel Holveck-Lafay from Pexels
当我们试图预测未来时,到底发生了什么
数据科学是许多学科的混合体——统计推断、分析、可视化、分类和预测。人们普遍认为最令人着迷的是预测。
预测未来听起来像是魔术,无论是提前发现潜在客户购买你产品的意图,还是计算出股票价格的走向。如果我们能够可靠地预测某事的未来,那么我们就拥有巨大的优势。
机器学习只是放大了这种神奇和神秘。随着预测模型复杂性的增加(以失去可解释性为代价),它们能够发现以前未被注意到的相关性,并利用以前难以使用的数据。
但是从本质上来说,预测其实很简单。今天我们将介绍一个思考预测的心智模型,希望它能揭开这门艺术的神秘面纱,并鼓励你将预测添加到你的定量工具包中。
线性回归预测
我们在统计学中学习的第一个模型是线性回归。它是多用途的,可解释的,并且惊人的强大。这是一个很好的工具来说明预测是如何工作的。
对线性回归如何工作的非数学描述是,它试图通过代表数据的点尽可能最佳地拟合一条线。这条线试图捕捉你的数据的趋势。下图显示了这条最佳拟合线(蓝色)的样子。让我们来解开到底发生了什么:
- 每个点代表一个观察。它的垂直位置是由点表示的观察的目标变量(我们试图预测的)的值。而点的水平位置就是我们的特征值(我们的自变量)。
- 目标变量和我们的特征之间显然存在正相关关系——请注意圆点如何向右上方移动。
- 最佳拟合线是沿着趋势方向,穿过尽可能多的点的线。这条线概括了我们的目标和我们的特征之间的关系。
The Line of Best Fit (In Blue)
那么这和预测未来有什么关系呢?让我们想象一下,我们正试图预测美国的 GDP(经济)增长。我们假设 GDP 增长和企业利润率(企业盈利能力如何)之间存在关系。我们将它们绘制成图,并看到以下关系(所有数据都是我编造的,仅用于说明目的):
GDP Growth vs. Profit Margins (Fake Data)
强正相关!那么企业利润率高是否预示着 GDP 的高增长?别急,这是你脑中的声音应该在低语的地方:
“相关性并不意味着因果关系.”
—你脑海中的声音
诚然,经济扩张时期通常伴随着丰厚的企业利润(和丰厚的利润率)。但我们正试图了解利润率是否能预测经济增长。为此,我们需要修改我们的分析:
- **错:**当期利润率对比当期 GDP 增速。
- 正确:当前利润率对比明年的 GDP 增长。
这种区别是关键。我们并不试图寻找与 GDP 增长同时相关的特征。相反,我们希望找到预测未来 GDP 增长的特征。也就是说,我们希望找到与下一期 GDP 增长相关的特征(我们的目标是下一年 GDP 增长的变化)。考虑到这一点,让我们重做散点图:
Future GDP Growth vs. Current Profit Margins
我们的正相关去哪了?预测未来很难。当我们将一个变量切换到它的未来状态(并测量预测相关性)时,变量之间的强并发相关性通常会消失。
这并不意味着所有的希望都破灭了。通常,如果我们足够努力地寻找并创造性地思考,我们仍然可以找到一些真正具有预测性的关系。但不要指望这些关系会很强——根据我的经验(试图预测金融市场的未来),0.20-0.30 的预测相关性已经很好了。
理解预测的心理模型
假设我们继续努力测试未来 GDP 增长的特征,并最终发现以下关系:
Future GDP Growth vs. Current Growth in Working Young
我们发现了未来 GDP 增长和目前观察到的年轻劳动者(25-35 岁)增长之间的关系。它不像我们之前看到的并发关系(GDP 增长和利润率)那样强有力,但这是一种预测关系,我们可以用它来建立预测模型(再次强调,这是我为了说明预测如何工作而编造的所有数据)。
那么我们如何建立模型呢?实际上已经完成了— 最佳匹配的蓝线是我们的型号。我们通过简单地观察工作的年轻人口的当前变化来做出我们的预测(假设它是由金色虚线表示的值)。然后,我们沿着 y 轴(纵轴)查看黄金虚线与最佳拟合蓝线(绿色虚线)相交的值,该值就是我们的预测值。
Making Our Prediction
好的,酷,这很简单。对于更多的功能,甚至是更复杂的预测算法,这个过程都是一样的。
但在更基本的层面上,我们的预测到底意味着什么?每一个预测,不管产生它的模型有多复杂,都可以被认为是一个条件预期。条件期望只不过是期望值的一种类型,也称为平均值。
那么,我是不是在告诉你,你那奇特的 XGBoost 回归器所产生的值只不过是一个平均值,确切地说,是你在初级统计课上学到的第一个概念?
差不多(有一些警告)。不相信我?让我们做一个思维练习。如果我让你预测明天的天气(不查互联网),你会怎么做?你可以这样做,“昨天很热,现在是八月,我们在圣何塞,所以我预测明天会是晴朗炎热的。”
你为什么得出那个结论?这是因为,也许你没有意识到,你对你记忆中在圣何塞的所有日子做了一个心理盘点,那是夏天,前几天天气很热。在你脑海中相关的每一天的平均天气都是晴朗炎热的。
所以你通过过滤相关的观察数据(圣荷西的夏天,之前天气炎热)**并取这些观察数据的平均值来做出你的预测。**这就是我们作为人通常是如何直觉地做出预测的——那么我们为什么要期待模型和机器会有什么不同呢?答案是他们没有什么不同,除了他们可以处理更多的数据,并且以一种更少偏见的方式这样做。
**因此,为了理解预测未来是如何工作的,我的建议是,把每个预测看作是从最相关的先前观察到的观察值的平均值中得出的。**对未来的预测只是在回答以下问题:
当我看到类似的事情在过去发生时,接下来通常会发生什么?
可视化条件平均值(也就是我们的预测)
让我们用前面的例子来形象地说明我们所说的预测是条件平均值是什么意思。下图显示了我如何将线性回归可视化,以及更一般地,预测模型的输出。每个金色矩形是样本(我们的观察)的一部分,条件是特征变量在某些值之间。例如,左边的第一个黄金矩形可能只是那些对 GDP 增长(我们的目标,在 Y 轴上)的观察,其中年轻工作人口的增长率(我们的特征,在 X 轴上)在 0%和 0.1%之间。
A Regression is Like a Bunch of Snapshots
注意蓝色预测线(最佳拟合线)是如何落在每个黄金矩形的中间的?这是设计使然——每个矩形的中间是该特定切片的平均值(回想一下,每个矩形代表一个切片)。随着我们获取越来越小的切片(随着我们增加矩形的数量,同时减少每个矩形的宽度),条件平均值系列(由每个矩形的中间表示)收敛到回归线。
“但是等一下!”你在思考。其中一些矩形的中间显然不是该切片内观察值的平均值。例如,第一个黄金矩形只包括一个观察值,它在我们的预测线之上。所以我怎么能说预测线是条件平均值,也就是该切片内的平均值呢?
对于简单的线性回归,条件平均值与每个切片中观察值的平均值不匹配,因为回归算法在目标和要素之间建立了线性关系。但是并没有失去一切!我们的心理模型仍然有效。
OLS ( 普通最小二乘法),线性回归用来寻找最佳拟合线的方法,就像取一组条件平均值,但有一点扭曲。我们可以用投票类比来理解我的意思:
- 想象每个黄金矩形是一个独立的选区。
- 每个区域都希望将区域内蓝色预测线的值(与每个黄金矩形的垂直中点相同)设置为尽可能接近本地区域平均值(区域中所有黑点的 Y 轴平均值)。
- 当单个分区移动预测线时,该移动会影响所有分区中预测线的值(因为它必须保持为直线)。
- 每个分区影响预测线的能力与该分区内的观测数量成正比(观测越多,影响越大)。
最终结果是,每个选区都尽最大努力将这条线拉向自己的平均值。与此同时,每个选区都感受到并受到其他选区的每一次推拉,尤其是那些有大量观察数据的选区。最终,预测线以如下方式确定:
- 每个选区都很满意,向自己选区的平均值移动了线,即使只是一点点。
- 考虑了群体的整体平均值(因为每个人都试图将预测线移向自己的个人平均值)。
因此,虽然线性回归做出的预测在严格意义上不是条件平均值,但由于每个选区将预测线拉向自己的平均值的方式,它与条件平均值相似。
那又怎样?
条件平均值如何帮助我们更好地理解预测?嗯,它们是每个预测的基础支柱。不管有多复杂,每种算法在其最基本的层面上都试图识别本质上与它试图预测的观测相似的观测。并且它做出的预测是目标在那些最相似的观察值中的平均值的导数。
甚至有某些算法,如k-最近邻或仅使用非有序分类特征的线性回归,可以通过条件平均值进行显式预测。
因此,预测没有什么神奇的——无论是算法还是人类都无法将当前数据转化为对未来的精确预测。相反,我们都只是把过去发生的事情作为未来可能发生的事情的粗略指南。
关键要点
在构建预测算法时,我们应该记住两件事。首先,因为我们用过去来预测未来,所以背景很关键。如果我们所处的环境与训练我们模型的可用历史有很大不同,那么我们就不能期望我们的预测表现良好。
第二,这个心智模型对于思考过度拟合是有用的。我们可以通过遵循以下两个原则来构建一个能够合理概括的模型:
- 确保每个条件平均值包含足够多的观察值(少量观察值的平均值比大量观察值的平均值更有可能出错)。我们可以通过不包含太多的特征来确保这一点——太多的特征(相对于观测值的数量)将导致非常精细的切片,每个切片只有非常少的观测值。
- **小心那些允许单个分区(切片)对其自己的分区内预测产生太大影响的算法。**包含过多非线性的算法(如多项式回归或深度神经网络)将允许每个分区将分区内预测拉至等于其分区平均值。这可能导致不合理的不稳定预测,不能很好地概括。
希望这是有帮助的!干杯!
更多数据科学与分析相关帖子由我:
火花中加入的艺术
加速火花连接的实用技巧
几个月前,我遇到了阿帕奇火花,这是一见钟情。我的第一个想法是:“这么强大的东西怎么会这么容易使用,真是不可思议,我只需要写一堆 SQL 查询!”。事实上,从 Spark 开始非常简单:它有多种语言的非常好的 API(例如 Scala、Python、Java),几乎可以只使用 SQL 来释放它的所有能力,它有一个广泛的社区和大量的文档。我的起点一直是一本书, Spark:权威指南 ( < -附属链接 美国 , 英国 ) ,我相信这是一个很好的工具介绍:它的作者是比尔·钱伯斯(data bricks 的产品经理)和马泰·扎哈里亚
很快我意识到事情并不像我过去认为的那么简单。同样的发现可能是你阅读这篇文章的原因。例如,我敢打赌,你会发现下面的图像非常熟悉:
正如您可能已经知道的,上面是连接操作期间数据偏斜的典型表现:一个任务将永远无法完成,因为所有连接工作都放在单个执行程序进程上。
过度简化 Spark 连接表的方式
看看我们通常用 Spark 连接哪些表,我们可以确定两种情况:我们可能将一个大表和一个小表连接在一起,或者将一个大表和另一个大表连接在一起。当然,在 Spark 开发过程中,我们会面临介于这两个极端之间的所有灰色阴影!
坚持上面提到的用例,Spark 将以两种不同的方式执行(或被我们强制执行)连接:如果我们要连接两个大表,要么使用排序合并连接,如果涉及的至少一个数据集足够小,可以存储在单个 all 执行器的内存中,则使用广播连接。请注意,还有其他类型的连接(例如混洗散列连接),但是前面提到的那些是最常见的,尤其是 Spark 2.3 中的那些。
排序合并连接
当 Spark 将执行计划中的操作转换为排序合并连接时,它会在节点之间启用所有对所有的通信策略:驱动程序节点将编排执行器,每个执行器将持有一组特定的连接键。在运行实际操作之前,先对分区进行排序(这个操作本身明显很重)。可以想象这种策略会很昂贵:节点需要使用网络来共享数据;注意,排序合并连接倾向于最大限度地减少集群中的数据移动,尤其是与混排散列连接相比。
In a Sort Merge Join partitions are sorted on the join key prior to the join operation.
广播加入
当 Spark 决定向所有执行器节点发送一个表的副本时,就会发生广播连接。这里的直觉是,如果我们广播其中一个数据集,Spark 就不再需要一个全对全的通信策略,每个执行器都可以独立地将每个节点中的大数据集记录与小(广播的)表连接起来。我们会看到这个简单的想法通常会提高性能。
In a Broadcast Join a copy of the small table is sent to all the Executors. Each executor will then perform the join without the need of network communication
消灭我们的敌人
在连接操作中,我们可能面临的一些最大的威胁是:
- 数据偏斜度:我们执行连接的键在集群中分布不均匀,导致其中一个分区非常大,不允许 Spark 并行执行操作和/或拥塞网络。请注意偏斜是一个影响许多并行计算系统的问题:这里的关键词是“并行”,只有当我们能够同时执行多个操作时,我们才能利用这些工具,因此任何发现自己处于某种偏斜操作的数据处理系统都会遇到类似的问题(例如,它也会发生在运行混合整数线性规划优化的分支定界算法时)。
- 全对全的沟通策略
- 有限的遗嘱执行人记忆
在深入研究一些优化连接的想法之前,有一点很重要:有时我会用执行时间来比较不同的连接策略。实际上,较低的绝对执行时间并不意味着一种方法绝对优于另一种方法。性能还取决于 Spark 会话配置、集群上的负载以及配置和实际代码之间的协同作用。因此,阅读下面的内容,收集一些你可能需要根据具体情况定制的想法!
播还是不播
首先,让我们看看如果我们决定在一个连接期间广播一个表会发生什么。请注意,Spark 执行计划可以自动转换成广播(不需要我们强制),尽管这可能因 Spark 版本和配置方式而异。
我们将连接两个表:事实表和维度表。首先,让我们看看它们有多大:
fact_table.count // #rows 3,301,889,672
dimension_table.count // #rows 3,922,556
在这种情况下,数据没有倾斜,分区也没有问题— 你必须相信我的话。注意, dimension_table 并不“小”(虽然大小不是我们仅通过观察行数就能推断出来的信息,但我们更愿意看看 HDFS 上的文件大小)。
顺便说一句,让我们尝试在不广播的情况下连接这些表,看看需要多长时间:
Output: Elapsed time: 215.115751969s
现在,如果我们广播维度表会发生什么?通过对 join 操作的简单添加,即将变量 dimension_table 替换为broadcast(dimension _ table),,我们可以强制 Spark 使用 broadcast 来处理我们的表:
Output: Elapsed time: 61.135962017s
广播使代码运行速度提高了 71%!再次,阅读这个结果时要记住我之前写的关于绝对执行时间的内容。
播音总是对表演有好处吗?完全没有!如果您尝试执行上面的代码片段,为集群提供更多的资源(尤其是更多的执行器),非广播版本将比广播版本运行得更快!出现这种情况的一个原因是因为广播操作本身是相当昂贵的(这意味着所有的节点都需要接收表的副本),所以毫不奇怪如果我们增加需要接收表的执行者的数量,我们增加了广播成本,这可能突然变得高于加入成本本身。
重要的是要记住,当我们广播时,我们会碰到每个 Executor 节点上可用的内存(这里有一篇关于 Spark 内存的简短文章)。这很容易导致内存不足异常或者让你的代码不稳定:想象一下广播一个中等大小的表。你运行代码,一切都很好,超级快。几个月后,你突然发现你的代码坏了。经过几个小时的调试后,您可能会发现,您为使代码快速运行而传播的中等大小的表不再是“中等”的了。外卖,如果你播的是中等大小的桌子,你需要确定它以后还是中等大小!
扭曲它!这太耗时间了。
当您想要连接两个表时,偏斜是一个常见的问题。当连接键在数据集中不是均匀分布时,我们说连接是偏斜的。在偏斜连接期间,Spark 不能并行执行操作,,因为连接的负载会不均匀地分布在执行器上。
让我们用旧的事实表和一个新的维度:
fact_table.count // #rows 3,301,889,672
dimension_table2.count // #rows 52
太好了,我们的 dimension_table2 非常小,我们可以直接广播它!让我们一起来看看会发生什么:
Output: Elapsed time: 329.991336182s
现在,在 SparkUI 上观察任务在执行过程中发生了什么:
如上图所示,其中一项任务比其他任务花费了更多的时间。这显然表明数据中存在偏斜度——通过查看 fact_table 中连接键的分布,可以很容易地验证这一猜想。
为了让事情顺利进行,我们需要找到一种方法来重新分配工作负载,以提高我们的 join 的性能。我想提出两个想法:
- 选项 1 :我们可以尝试重新划分我们的事实表,以便在节点中分配工作
- 选项 2 :我们可以人为创建一个重新分区密钥(密钥加盐)
选项 1:重新划分表格
我们可以选择一个均匀分布的列,并相应地对表进行重新分区;如果我们将此与广播结合起来,我们应该已经实现了重新分配工作量的目标:
Output: Elapsed time: 106.708180448s
请注意,我们希望选择一个也考虑基数的列(例如,我不会选择基数“太高”或“太低”的键,我让您量化这些术语)。
重要注意事项:如果您无法广播维度表,但仍想使用此策略,需要使用同一个分割器对连接的左侧和右侧进行重新分区!让我们看看如果我们不这样做会发生什么。
考虑下面的代码片段,让我们看看 Spark UI 上的 DAG
If we don’t specify a partitioner, Spark may decide to perform a default repartitioning before the join
正如您所看到的,在这种情况下,我的重新分区基本上被忽略:在执行之后,spark 仍然决定使用默认配置重新交换数据。让我们看看,如果使用相同的分区器,DAG 会如何变化:
Using the same partitioner allows Spark to actually perform the join using our custom options
选项 2:密钥加盐
另一个策略是伪造一个新的连接密钥!
我们还是想逼 spark 做一个大桌子的统一重新划分;在这种情况下,我们还可以将密钥加盐与广播结合起来,因为维度表非常小。
左表的连接键存储在字段 dimension_2_key 中,非均匀分布。第一步是让这个场更“均匀”。一种简单的方法是在连接键上随机附加一个 0 到 N 之间的数字,例如:
正如您所看到的,我们修改了 dimension_2_key ,它现在是“均匀”分布的,我们正朝着集群上更好的工作负载前进。我们已经修改了连接键,所以我们需要在维度表上做同样的操作。为此,我们为事实表中的每个“新”键值在维度中创建一个对应的值:对于维度表中的每个 id 值,我们生成 N 个值**,其中我们将[0,N]区间中的数字附加到旧 id 上。**让我们用下面的图片更清楚地说明这一点:
此时,我们可以使用“新的”salted 键连接两个数据集。
这个简单的技巧将提高 DAG 执行的并行度。当然,我们增加了维度表的行数(在本例中 N=4)。较高的 N(例如 100 或 1000)将导致键在事实中更均匀的分布,但是维度表的行数更多!
让我们编码这个想法。
首先,我们需要将 salt 添加到事实表的键中。这是一项极具挑战性的任务,或者更好地说,这是一个决策点:
- 我们可以使用 UDF :简单,但是可能会很慢,因为 Catalyst 对 UDF 不是很满意!
- 我们可以使用randSQL 操作符
- 我们可以使用单调递增 id 函数
只是为了好玩,让我们采用第三种选择(它似乎也更快一点)
现在我们需要用新的键“分解”维度表。我发现最快的方法是创建一个包含 0 到 N(在本例中为 0 到 1000)之间的数字的虚拟数据集,并用这个“虚拟”数据集交叉连接维度表:
最后,我们可以使用 salted key 连接表,看看会发生什么!
Output: Elapsed time: 182.160146932s
同样,执行时间并不是理解我们改进的一个很好的指标,所以让我们看看事件时间表:
如您所见,我们大大提高了并行度。
在这种情况下,简单的重新分区和广播比制作新的密钥更有效。请注意,这种差异不是由于连接,而是由于事实表提升过程中的随机数生成。
外卖食品
- 连接可能很难调优,因为性能与代码和 Spark 配置(执行器数量、内存等)都有关系。)
- 一些最常见的连接问题是节点之间的所有对所有通信和数据偏斜
- 如果集群中有足够的内存,我们可以使用小型表或中型表的广播来避免所有对所有的通信
- 广播并不总是有益于性能:我们需要关注 Spark 配置
- 如果广播表随时间增长,广播会使代码不稳定
- 偏斜会导致集群上的工作负载不均衡,从而导致很小一部分任务花费的时间比平均时间长得多
- 对抗偏态有多种方法,一种是重新划分。
- 我们可以创建自己的重新分区密钥,例如使用密钥加盐技术
看看这些其他的文章!
一些具有挑战性的 Spark SQL 问题,易于在许多现实世界的问题上提升和转移(带解决方案)
towardsdatascience.com](/six-spark-exercises-to-rule-them-all-242445b24565) [## 聚类波洛克
杰森·布拉克绘画的聚类分析——如何利用 k-means 进行色彩分组
towardsdatascience.com](/clustering-pollock-1ec24c9cf447)
上行投票的艺术:使用 NLP 根据标题预测上行投票
我最喜欢的项目是那些试图理解人类行为和行动的项目。无论是试图预测自行车租赁最受欢迎的工作日,还是教育水平与个人是否喜欢原版《星球大战》三部曲有任何关系。
在线互动,尤其是在论坛中,非常有趣,因为它们是如此的被动和无形。一个简单的“喜欢”或“赞成”可以是有意的,也可以是无意义的。与脸书和 Instagram 不同,论坛大多是匿名的。这个事实使得像 Reddit 和 Hacker News 这样的地方特别有趣。
目标
我在这个项目中的目标是根据标题预测黑客新闻文章获得的投票数。由于 upvotes 是受欢迎程度的一个指标,我想发现哪种类型的文章最受用户欢迎。
数据
该项目使用的数据是 2006 年至 2015 年用户向《黑客新闻》提交的资料。这个数据是开发者 Arnaud Drizard 利用黑客新闻 API 刮出来的,可以在这里找到。
我从数据中随机抽取了 300 行,并删除了除提交时间、upvotes、url 和 headline 之外的无关列。
现在是有趣的部分,NLP!😎
我选择使用单词包模型,它描述了单词在文档中的出现,或者在我们的例子中是标题。这包括以下步骤:
- 标记每个标题
- 创建独特单词的集合或“词汇表”
- 创建单词袋矩阵
- 给标题中的每个单词打分
- 管理词汇
标记化
第一步是标记每个标题。每个标题都表示为一个字符串。标记化包括将这些单个字符串拆分成标题中的几个单个单词(标记)的字符串。这对于稍后创建词汇和单词包矩阵是必要的。
Example of tokenization
创建词汇和词汇包向量
每个独特的单词都成为词汇的一部分。我选择将词汇表转换成熊猫数据框,如下所示。这允许我在下一步填充数据框,并将其用作单词袋矩阵。
data frame of BoW vocabulary
数据框中的每一行代表数据集中每个索引处的标题。此时,矩阵中充满了 0,因为我还没有对标题中的单词进行评分。
得分单词
最简单的计分方法是计算每个单词的出现次数。我用这种方法来填充矩阵。
bag of words matrix
管理词汇
此时,我有一个相对较大的数据框架。减少它的一个简单的方法是删除停用词。停用词是这样的词:a,and,the,it,is 等。那不代表任何真正的意义。这样做应该会减少下一节中计算的误差量。
训练模型和进行预测
回到我的目标,我想训练一个模型来预测一个标题将获得的支持票数。在转移到更复杂的模型之前,最好先看看一个简单的模型是否能完成工作。
线性回归
我将使用 sci kit-learntrain _ test _ split函数将我的数据分成训练集和测试集。我将 80%用于训练,20%用于测试,并使用均方差作为我的误差度量。
X: bag of words matrix; y: upvotes
我最终得到的 MSE 是 2361.02,这导致了 48.59 的 RMSE。这意味着平均误差与真实值相差 48.59 票。考虑到我的数据的平均投票数是 10,这是一个很大的错误。我将使用随机森林模型再次尝试,看看更复杂的模型是否能帮助我减少错误。
随机森林
使用随机森林只是稍微增加了我的模型的准确性,从误差 48.59 到 45.41。这仍然是一个很大的误差。标题可能不是预测上行投票的最佳特征,因为它不一定指示帖子活动。评论数量和提交时间可能是更好的功能。
也许在第二部里?
A/B 测试的 A 和 B
数据新闻
实验初学者指南
Photo by Bùi Thanh Tâm on Unsplash
我最近完成了 Udacity 关于 A/B 测试的课程。在深入实验过程中每个阶段的细节之前,它提供了对典型的 A/B 测试所包含的内容的高级理解。不用说,这是一次很棒的学习经历!在这篇文章中,我将总结我从该课程中学到的关键知识,并解释它如何使许多专注于改善用户体验的公司受益。
所以,让我们开始吧!
那么,告诉我什么是 A/B 测试?
A/B 测试是一种实验方法,旨在了解用户体验如何随着他们与网站、移动应用程序等交互方式的变化而变化。这是一种非常流行的方法,用于了解现有客户体验的水平,并根据某些相关指标来改进它。
公司可能带来的变化是改变网站的布局,改变特定网页上某些按钮的外观/位置,改变网站上选项的排名等。A/B 测试有助于公司评估这些变化在发布后是否会成功。这是通过在一段时间内在一部分观众身上测试这些变化来实现的。
好的,明白了!但是我将如何评价这些测试呢?
Image from http://www.quickmeme.com/
评估基于选择的指标进行。
- 健全性度量:这些被称为不变度量。在实验过程中,这些指标不应在对照组和实验组之间发生变化。如果它们改变了,那么在实验设置中就有一些根本性的错误。
- **评估指标:**可以根据业务需求选择这些指标。这些可以是下面列出的任何一种:
a.求和计数。访问该网站的用户数量
b.是指中位数和百分位数。例如,创建帐户后注册在线课程的平均用户数。
c.概率。点击通过概率
d.比率。例如(创收点击次数)/总点击次数
此外,值得一提的是,这些指标应该在固定的时间间隔内进行评估,比如一周、一个月或一年。由于 A/B 测试通常是有时间限制的练习,所以提前确定观察实验结果的时间表是至关重要的。
如果在某些情况下无法确定具体的指标,该怎么办?
Image from http://www.quickmeme.com/
您如何衡量再次注册第二个在线课程的用户比率,如何衡量在线购物门户网站购物者的平均快乐程度,或者如何衡量通过搜索引擎搜索找到信息的概率?在这种情况下,提出代理指标是一个好主意!它们可以是以下类型。
- UER(用户体验研究):跟踪一个具体的用户体验,从体验的角度了解一个典型的用户历程。
- 外部数据:查看类似网站/移动应用的相关数据。
- 人工评估:聘请人工评估员对结果进行评估。
- 回顾性分析
- *焦点小组研究:*采访小组成员以获得反馈。
- *调查:*让人们填写问卷。
同样重要的是,所选择的指标对您希望看到的变化敏感,但对您不希望看到的变化稳健。换句话说,度量标准应该对实验过程中可能发生的微小变化具有鲁棒性,并且能够捕获所有最重要的变化。
我如何决定测试的样本量?
- 测试对象通常称为“分流单位”。这可以是基于事件的(如页面浏览量)、基于匿名 id 的(如 cookies)或基于用户 id 的。你什么时候选择取决于你试图解决的问题。对于用户可见的更改,使用 cookies 或用户标识。如果你测量延迟变化,比如网站加载时间,那么通常使用基于事件的转移单位。
- 人群取决于哪些受试者与所考虑的研究相关。特定人口统计、区域/国家的人口是否相关?据此选择。
为了减少实验的规模,通常建议提高显著性水平(α)或降低功效(1-β),以便减少需要测试的样本数量。
其他需要牢记的重要事情:
- 实验将进行多长时间,实际运行的最佳时间是什么时候?根据实验在一年中的什么时间进行,比如说圣诞节、黑色星期五、网络星期一或者返校期间,实验的结果会发生变化。
- 多大比例的流量会暴露在实验中?可取的做法是在工作日、周末、假日等时段,抽取一小部分流量进行多次测试。
- 学习效果是另一个需要注意的因素。通常,一旦引入了变化,用户需要一段时间来适应它。一旦他们适应了,用户行为就变得稳定,并且变得更适合衡量任何变化的影响。
我如何决定哪个变化是重要的?
在 A/B 测试期间,显著性水平固定为 0.50,功效(1-β)设置为 0.80。虽然这些都是重要的值,但重要的是要理解什么构成了统计上的显著变化,而不是实际上的显著变化!必须确定业务需求,并且必须计算 ROI,以确定是否必须实际推出变更。推广值得努力吗?如果我们决定启动变革,业务影响会更大吗?当我们做出这些关键决策时,如果我们继续进行变更,考虑可能涉及的机会成本、工程和推广成本可能是值得的。
到目前为止一切顺利!现在,告诉我如何分析结果?
场景 1:一个单一指标
如果你使用一个单一的指标,但它产生了模糊的结果,那么把实验分成不同的部分。在不同平台(网络和移动)上查看结果。在不同的时间段(几天、几周、几个月、几年等等)检查它。)
如果没有什么可以解释奇怪的结果,那么可能是你的实验设置不正确,或者你的实验正遭受辛普森悖论的折磨。这是一种趋势出现在不同的数据组中,但当不同的组被组合起来并从整体上看时就会消失的现象。
场景二:多指标
当涉及到许多指标时,通常重要的结果可能是偶然出现的!解决这个问题的方法有:
i. Bootstrap :将测试样本分成更多的样本,并对每个这样的细分样本进行实验。重要的结果会消失,如果它最初是偶然出现的。
二。 Bonferroni 校正:将显著性水平 0.50 除以测试运行次数。然后对照调整后的显著性水平,检查测试的统计显著性。
三。控制家族误差率(FWER) :调整任何指标显示误报的概率。
四。控制误发现率(FDR) :修正实验中任何指标产生的可接受的误报率。假设您决定 0.05 作为 FDR,那么这意味着在 100 个样本中,您准备好接受每个测试的 5 个假阳性。
但是等等!如果我看到指标如预期的那样朝着不同的方向移动,该怎么办?
Image from http://www.quickmeme.com/
潜得更深!通常情况下,指标可能并不总是如您所期望的那样表现或产生结果。在这种情况下,拥有一个总体评估标准(OEC) 是很有用的,它可以平衡所有指标的结果,而不会忽略业务需求。人们很容易陷入单个指标性能的混乱中,因此防止这一点很重要!
嗯…现在一切都说得通了!所以,最重要的问题是我该不该发起变革?
如果到目前为止你的实验一切顺利,你得到了梦寐以求的重要结果,那么你需要反思以下几件事:
- 这种变化对用户和业务涉众意味着什么?
- 这种改变真的会给企业带来价值吗?
- 发布后用户会有什么感觉?不同的客户群会有不同的意见吗?你冒着失去一些顾客的风险吗?
我再怎么强调从商业角度发起变革的实际意义也不为过!它到底值不值得?
吸取的教训
- A/B 测试就是首先决定你的最终目标。什么变化,为什么变化,变化后你想看到什么?
- 决定参加实验的控制组和实验组
- 多次验证实验设置—健全性检查!
- 如果你计划推出一个大的变化,尝试多次,以避免粗鲁的冲击你的客户。实验和更多的实验,直到你确信它的发射!
感谢阅读和快乐的 A/B 测试!😃
创造力的自动化
这是我发表在 女王商业评论 上的那篇完整版。原载于我的媒体 此处 。
人工智能。这个术语在这个数字时代已经变得无处不在。高度复杂,不透明,但著名。我们感谢它通过零售、音乐和流媒体平台上的推荐引擎带来的便利。然而,铺天盖地的选择让我们放弃了购买行为和交易数据,转而使用一种算法,这种算法只会强化我们对过去的品味。
什么是“味道?”
世界上最有价值的公司怎么可能建立一个帝国,重塑一个古老的消费电子行业的外观,因为他们的“永恒的设计”而受到同样多的赞扬和批评
透过哲学的镜头,自然的趣味是"不是一种理论知识;这是对我们甚至不知道的规则的快速而精致的应用。某样东西对我们来说是否有品味,这种不确定性无法用单一的算法来衡量或捕捉。
像脸书和亚马逊这样的公司主导着我们的数字身份——把我们分成基于行为的群体。即使聘用了顶尖的博士,这些公司的算法也严重依赖于跟踪我们过去的行为,将我们的视野局限于后视镜的延伸,而不是我们未来可能会发现的有品位的有机创新内容。在网飞决定我们的下一个节目和亚马逊建议我们下一次购买之前,我们必须自己发现并做出这些选择。决策之间的摩擦导致了对各种选项的考虑,通过这些,我们聚合了我们独特的个人品味。
是时候认识到人工智能等现代先进技术背后的含义了,它们可能会让我们倒退,而不是推动我们前进。在喧闹的流行语庆祝活动背后,算法正在悄悄地管理和推广能够彻底改变我们个人品味的通用内容。
也许最伟大的例子之一是一个号称拥有 8300 万活跃用户的平台,试图定义和改造艺术品味。2017 年, Spotify 的听众在该应用上花了一半的时间专门听播放列表。该服务每周提供一个“发现”播放列表,据报道,该播放列表根据每个听众的“口味配置”进行微调播放列表由机器严格管理,旨在为用户找到满足他们特定音乐需求的歌曲。然而,在狂热的 Spotify 用户 Adam Pasick 在 Quartz 的一篇帖子中,他概述了当他听到咖啡店里播放的与他的 Discover 播放列表完全相同的曲目时的震惊。那一周,咖啡师兼乐队成员 Homero 收到了与 Adam 几乎一模一样的混音带。现实是,即使有 20 亿个播放列表通过一种据称先进的算法过滤,仍然可以为两个不同的听众产生相同的结果,这证明了人工智能促进所有人的通用品味和内容的威胁。
2015 年,EDM 艺术家 Tiesto 在 Spotify 上发布了“Burn ”,这是一首由音乐流媒体服务直接委托制作的作品,迎合了他们的锻炼播放列表。Burn 专为适应任何听众的跑步速度而设计。不幸的是,音乐行业的艺术创作意图已经朝着这个方向急剧发展。制作为游戏算法设计并被 Spotify 播放列表成功收录的音乐的做法不可避免地创造了一个“轻松音乐”的时代。Spotify 前内容主管乔治·埃尔加图迪斯(George Ergatoudis)承认,该行业正朝着以理解流媒体算法为中心的创作方向发展。这甚至在一年前都没有发生。“当艺术家们注意到病毒式传播和受欢迎程度是普通音乐人的优先选择时,他们开始认为自己独特的音乐风格是劣等的。由于人工智能驱动的同质化需求的受害者,为了纯粹的艺术表达和视觉而创造未来音乐的意图正在减弱。
此外,具有挑战性、创新性和突破性的音乐的潜力已经受到威胁。像 Spotify 这样的算法有利于听众人数和病毒式内容的激增。研究表明在所有播放列表中持续播放安全和“轻松的音乐”从根本上改变了听众的集体偏好和品味,他们认为这是未来的“好歌”。Spotify 引发的“后仰聆听”现象催生了一种新的音乐创作类型。标题为“Ambient Chill”、“Chill Covers”和拥有超过 240 万追随者的特别受欢迎的“Chill Tracks”的算法优化播放列表将轻松和麻醉的曲目放在听众的指尖。一位唱片公司老板直言不讳地评论道,“发布的产品越普通,对 Spotify 就越有利。如果是挑战音乐?那鸿“他接着说,由于 Spotify 算法的不利曝光,他们的许多相对实验性和攻击性的音乐在平台上听不到。它把艺术家们抛在了后面。如果 Spotify 只是向每个人提供简单的音乐,那么这种艺术形式将何去何从?还有人能够突破界限,突破广大受众吗?”
培养一种充斥着主流流行音乐的短暂文化,从长远来看,只会将我们的品味塑造成一致的线性,从而损害真正的创造力。正如查尔斯顿学院的艺术教授 Marian Mazzone 雄辩地指出的那样,“最终,我们可能会选择改变我们对艺术的定义,以便适应人工智能的[所谓]创造力。“曾经是释放沮丧、激情、饥饿、愤怒和个人身份的一种无限的艺术形式,现在已经萎缩为满足自动化的音乐。
这一趋势与其他行业相似。Echo Look——亚马逊的电脑化时尚顾问——旨在为每个用户推荐理想的时尚、风格和品味,尽管它就像货架上一台毫无生气的黑匣子相机。
对网飞最近的热门电影《我以前爱过的所有男孩》的导演苏珊·约翰逊的采访显示,这部电影完全是基于网飞的数字分析而要求的。“[网飞]到处都有团队,寻找人们正在寻找的东西。所以他们意识到人们在寻找浪漫喜剧。显然,投资这部小说的电影版并不是网飞投资创意的结果,而是为他们的商业算法加油。约翰逊甚至称这个过程为“小老大哥”,,由网飞控制我们消费什么以及如何消费。此外,网飞表示,他们的推荐算法“影响了该服务上 80%的流媒体时间的选择。因此,网飞自己的内容无疑将受益,不管每个观众的真实口味偏好,都被强有力地宣传。最终,不仅是《我之前爱过的所有男孩》获得了巨大成功,而且它还帮助这项服务影响了超过 8000 万订户在夏季剩余时间观看浪漫喜剧类型的内容。谈概括口味。
事实上,人工智能日益增长的作用并没有让我们的时尚感或音乐品味变得更加独特——而是将我们禁锢在一个同质化的盒子里,计算我们在“平均”身份中的位置。事实上,这些平均身份都有细微差别(发现播放列表上的一两首不同的歌曲),这仅仅意味着它们远非独一无二。
毫无疑问,人工智能每时每刻都在塑造个人选择和更广泛的社会结构。单个来看,为了方便和效率,牺牲隐私和数据是值得的。目前,从规模上来说,我们的偏好是由那些被我们掌控的大型科技公司来决定的。在这种情况下,这个光鲜的人工智能时代的突出权衡不是隐私,而是我们的个人品味和身份。
Spotify 的算法对整个行业的操纵已经成为音乐爱好者和艺术家的运营动机和预期标准。遵从算法游戏会扼杀因品味和想法冲突而产生的创新。不用说,当受限于未来独特的品味和平淡的灵感时,在线条之外着色几乎是不可能的。用算法取代品味判断的微妙而有害的结果将不可避免地剥夺我们的人性。
总之,我们应该谨慎对待对前沿技术的依赖,保持警惕其压制真正新颖性的威胁能力,并培养对创造力的一致看法。
平台文化的深度
我最近参加了一个研讨会,听一名学生对迷因进行解剖。当我困惑地看着时,一层又一层的参考文献被剥掉了。结论是,从像素中删除了大约 15 个对其他先前迷因的引用,可以追溯到几个月或几年前。一系列内幕细节在我眼前展开。我已经知道迷因经常在有趣的参考点和无休止的自我参考引用上工作,但是它的复杂性和数量吸引了我。
被剖析的迷因代表了一些东西。不知何故,它捕捉到了按需或基于平台的文化令人困惑的深度、深不可测的混乱和不断变化的混乱。文化一直是一个复杂和无定形的东西,但随着它进入这些大型技术平台,注入用户内容的能量,以及数据主导的消费和预测方法的强烈冲动,一个新的混乱和混乱水平已经出现。平台文化就更难把握了。
试图理解正在发生的事情是困难的,甚至是不可能的。高深莫测是平台文化的核心属性之一。在这个被剖析的迷因的例子中,它甚至被有意地编码到图像中,以在知情人和不知情人之间建立一条线。这种方法只会增加文化分析的难度。
尽管有这些障碍,我们还是需要总体的想法来找到看到正在发生的事情的方法。面对这样一个不断扭曲的万花筒,很难看出这是如何运作的。过去用来理解文化的许多概念将需要彻底改革,以把握现在,特别是如果它们不想以丢失甚至误导而告终的话。也需要新的概念来看待这个复杂的领域。替代监督的方法是尝试使用显微镜。至少,从近处看,这些东西是可以消化的。然而,专注于文化的一个小方面可能会排除其高度网络化和不断变化的背景的重要性。
或者,焦点的某种组合是尝试获得某种视角的一种方式。为了理解正在发生的事情,我们不仅仅需要机械的细节——超然的远景会错过深度。我们需要的是一些印象主义,关于这些细节如何融入更广阔的场景。这就是受乔治·齐美尔启发的社会学家大卫·弗里斯比所说的“社会学印象主义”。这是一种用笔触暗示细节的方法,同时给人一种背景的印象。
平台文化充满了奇怪和不寻常的东西。这些有时会在小新闻或社交媒体帖子中被顺便评论,但通常会被孤立对待。仅举几个上个月的例子来说明这一点:一本学术书籍是由机器写的;优步寻求超过 900 亿美元的上市价值,却从未公布过利润;在 Whatsapp 中发现了一个弱点,这意味着你的手机摄像头和麦克风可能会被第三方控制;本质上是零售商的亚马逊投资了一款食品配送应用 Deliveroo。我们几乎已经习惯了这样的怪癖,现在我们错过了它们,它们被评论,但它们要么似乎无关紧要,要么只提供了短暂的娱乐或分心。但是如果我们看看这些和其他的例子,它们通常能告诉我们一些更广泛的趋势和力量。如果我们能找到一个怪癖,我们就能描绘出一幅图画。
在我即将出版的新书 数字文化的怪癖 中,我确定了一系列这些不同寻常和揭示性的东西,并试图用它们来创造对支撑和塑造我们文化景观的运动、力量和不断变化的议程的印象。我报道的一些事情,比如电视游戏节目或者音乐回归,看起来都很无聊。其他的,如通过家庭设备或社交媒体操纵的声波监控,显然更加严重。数字文化的各种怪癖可以让我们对文化转移到这些技术平台上会发生什么有一点了解。这不是一个全面或全景的视图,但它可能有助于建立一个印象主义的画面,有助于理解我们面临的沸腾的混乱。我的建议是,我们留意这些稍微奇怪或不寻常的事件,当我们发现它们时,我们可以把它们放在手中,看看它们还能揭示什么。
基础知识:决策树分类器
从头开始的数据科学
对决策树如何工作和构建的直觉
决策树在概念上是一种简单明了的模型风格,尽管技术上的实现确实涉及到一些值得理解的计算。尽管如此,决策树背后的直觉应该很容易理解。事实上,决策树在某种程度上非常类似于人们在现实世界中做出选择的方式。
什么
当面临选择时,真实的人可能会在一系列级联决策中思考。如果我需要选择早上穿什么,我不会从衣橱里随意挑选一套衣服,我会首先分解我的选择。首先,我可能已经把厚重的冬装和轻薄的夏装分开了。然后,我可能会查看天气,进一步缩小服装的范围——比如说,如果下雨,我可能会穿靴子。最后,我可能会考虑我今天有什么计划,以及我是否因为任何原因需要打扮。你可以把我的决策过程想象成从我衣柜里的所有选项开始,然后逐渐缩小我的选择范围,直到我从一个更小的选项集中挑选。
也许我们可以教计算机以类似的方式做出决定或预测。一个方面是,决策过程是基于规则的,这使得它很容易在计算机上实现,假设我们知道规则是什么。因此,任务是找出规则是什么。
考虑一个简单的分类问题。以下是分散在三个类别中的要点:
A simple three category classification problem
如果在网格的某个地方出现一个新点,你会如何预测它属于哪一类?你可能会注意到,比如说,网格上部的所有点都是橙色的。所以,让我们从简单地在这个‘橙色领地’的底部画一条水平线开始。如果我们试图预测的新点落在这条线之上,我们会猜测它属于橙色类别:
A line now separates the orange territory
如果点低于那条线会发生什么?好了,现在我们注意到蓝点倾向于在右边,绿点倾向于在左边,所以让我们把这个较低的区域分成蓝色和绿色两部分:
Further segmentation
如果新点在这条线的左边,我们就猜它是蓝色的,如果在右边,我们就猜绿色的。常见的是决策树也被形象化为流程图。该流程图描述了该示例数据集的决策过程:
我们首先询问新点的 y 值是大于还是小于 6.138。如果小于,我们向下移动到左边,然后通过询问 x 值是大于还是小于-2.337 来跟进。
请注意,当我们将图表分割成更小的部分时,新的部分比之前的整体图表更加同质。我们从一个正方形图开始,它有相同数量的三个类别。然后,我们将图形一分为二,顶部几乎完全同质(全部为橙色),底部现在只分为两种颜色,而不是三种。在下一轮中,我们将底部一分为二,得到两个几乎同质的部分。不管我们的数据集有多复杂,有更多的特征或类别,决策树的目标是找到分割数据的方法,使子段尽可能“纯净”。
决策树的优势之一是它能很好地处理非线性。考虑一个稍微复杂一点的例子:
Another classification problem
这张图只有两个类别,但是它们没有被很好的分割。我们不能说“上面的点更可能是橙色的”,因为橙色和蓝色有点混在一起了。我们仍然可以像以前一样采取类似的策略,一点一点地分割数据集,直到我们得到相对同质的子集。决策算法将进行以下拆分。首先:
A first split
其次是:
A second split
然后,再经过两步:
Now we’re getting somewhere
该算法已经成功地识别了这些分组的各个中心。
这里有一个更具体/不那么抽象的设置,决策树算法可以很好地识别这些非线性。数据科学家在培训中常用的一个数据集有泰坦尼克号乘客的信息,包括他们是否在船沉没时死亡。如果你制作一个模型来预测某个乘客是否会死亡,你可能会注意到男性的死亡率比女性高。在逻辑回归模型中,您会注意到变量 is_man 的系数为负。有一个例外,那就是被确认为男性的儿童乘客有更高的存活率。为了捕捉逻辑模型中的非线性,你必须做一些特征工程,以某种方式交互你的性别和年龄变量。然而,通过巧妙地选择使用哪些特征来确定其分裂,决策树算法可以隐式地发现这些类型的关系。
算法正在做什么
到目前为止,我只是简单地说决策树算法决定在哪里分割数据集,而没有说如何分割。为了了解算法是如何工作的,为什么它会工作,为什么它不总是给出完美的结果,有必要稍微离题一下,谈谈信息论。
信息论基本上是由克劳德·香农创立的,尤其是在 1948 年发表了他的论文《交流的数学理论》。香农是贝尔实验室的一名研究员,也是他工作的部分动机,信息论的许多早期应用都与贝尔电话公司正在处理的通信问题有关:如何通过频繁嘈杂的电话线发送可解码的信息。其中一个重要的方面是确定什么是信息,以及如何衡量它。香农将对信息的直觉形式化为一个具体的公式,将整个问题从本质上变成了一个应用统计问题。
Claude Shannon, father of information theory
关键的见解是,字符串中包含的信息量,无论是书面英语的字母还是二进制数字,都与字符串的长度、有多少可用字符以及每个字符的使用概率有关。信息量应该与字符串的长度成比例,这可能很直观——毕竟,在一个段落中可以说的比在一个句子中说的更多——但其他两点值得一提。
首先,可用字符的数量应该与每个字符包含的信息量相关。想想写一个单词需要多少个字符。一个英语单词的平均字母数不到 5 个。(这个统计数据和许多其他有趣的英语语言统计数据将在这里讨论。)毕竟,英语字母表只有 26 个字母,并且有许多不同的可能单词,所以自然地,表达任何给定的单词都需要使用不同字母的组合。另一方面,书面汉语有成千上万的可用字符。大多数常用词可以表示为单个字符或两个字符的组合。当你有这么多的字符时,任何给定的字符都可以更具体,代表一个完整的语义单位。反之亦然;用二进制表示一个单词,只有两个可用的字符,0 和 1,比用英语表示需要更多的字符。
但是,并不是每个角色都生来平等。可预测的字符模式并不真正携带新的信息;一本只有一句话被反复重复的书,一旦你过了第一句话,明白了发生了什么,就不会告诉你任何新的东西。可预测的模式会产生信息冗余。
威滕英语似乎充满了冗余。考虑一下将“are”或“you”缩短为单个字母“r”或“u”的文本写作练习。一个经常被引用的例子是在纽约地铁广告秘书工作中发现的一则旧广告,上面写着:“f u cn rd ths,u cn BCM a sec > a GD JB w hi pa”。即使去掉一半以上的字母,大多数人也能正确解析这个句子:‘如果你能读这个……’所以看起来并不是每个字母都像其他字母一样携带那么多信息。事实证明,一个给定的字母到底携带了多少信息,与你在那个位置看到它的可能性有关。
英语中的字母使用频率不同,但更重要的是,它们之间以及它们在单词中的位置也有不同的频率。英语中一个常见的例子是,字母“q”后面几乎总是跟着字母“u”(例外情况往往是来自其他语言的外来词)。另一个可能是“e”是最常见的字母,但不经常作为一个单词的第一个字母出现。字母“I”相对常见,但很少作为单词的最后一个字母出现。某些字母经常出现在每个字母的旁边(“th”、“he”等)。),其他的不是(有多少单词含有‘KD’或者‘BP’?).因此,存在可预测的模式。
Shannon 举例说明了这一点,他试着一次猜一个单词中的下一个字母是什么。香农会从书架上取下一本书,随机选择一个句子,他的妻子会试着依次猜测每个字母。想象一下自己尝试这样做。我记住了一句话,你认为第一个字母是什么?嗯,什么都有可能!你必须猜测,而且很有可能你会弄错。但是,你不必从所有 26 个字母中随机猜出任何一个,因为有些字母在英语中比其他字母更常见。当然,可能是“z”、“q”或“j ”,但这些字母实际上并不经常出现,所以你可能不应该猜其中之一。字母“e”很常见,但正如我们已经提到的,它并不经常作为单词的开头,所以还是不要猜为妙。
知道这是一个句子的第一个字母而不仅仅是一个单词的第一个字母也会有所帮助。视上下文而定,某些词在句首很常见:“the”、“then”、“I”、“a”、“to”、“that”等。你仍然要猜,但是有一个小得多的字母池可以猜。你猜一个你认为最有可能的字母。在这种情况下,假设我确认句子的第一个字母是 t。现在猜第二个字母比第一个容易多了。首先,虽然以前有些字母我们认为不常见,但现在有些字母我们可以完全排除。英语中有以字母“tg”开头的单词吗?还是‘tk’?一旦你发现下一个字母是 h,猜第三个字母就变得更容易了。首先,你几乎可以排除所有其他辅音,这意味着在最坏的情况下,你的猜测将是五分之一,而不是 26 分之一。当你读到第三个字母时,你甚至可以一次猜出剩下的所有字母。有多少单词是以字母“tho”开头的,并且可能是一个句子的开头?可能是‘虽然’,也可能是‘成千上万’,但不可能有十几个可能的选项。
你可以把每个字母携带的信息量想象成与该槽中可能出现的合理选项的数量相关。在这个例子中,单词的第一个字母携带了大量信息——它几乎可以是任何字母——而第二个字母携带的信息稍少,单词中后面的字母几乎不携带任何信息。事实上,发短信的人在匆忙中可以将单词“tho”缩写成“tho ”,而不会失去任何意义,尽管这可能会困扰拼写纯粹主义者。though 这个词的最后三个字母基本上是多余的。
香农将所有这些形式化为一种他称为熵的度量,它本质上描述了任何给定消息可以携带的信息量。一个称为 H 的字符的熵的正式表达式是所有不同可能字符的总和:
其中 pi 是该字符出现的概率。当这个概率变为零时,和中表达式的值也变为零——如果字母“q”从未真正出现,那么它作为一个选项对你没有多大好处。至关重要的是,当概率变为零时,表达式的值也变为零(1 的对数为零):如果你提前知道下一个字符将会是什么,那么当该字符出现时,你没有学到任何新信息。
因此,最大化总熵的方法是每个可能的字符以相等的概率出现,此时文本中没有模式,字符看起来基本上是随机的。人类的语言看起来不像那样是有实际原因的。特别是,人类语言中的冗余有助于避免错误——即使有拼写错误或遗漏了一些字母,你仍然可以看出一个单词应该是什么——但这种鲁棒性的代价是添加额外的字母,使文本相应地变长。
这是决策树算法使用的公式。它查看数据点的总体集合,并计算该熵的值。在第一个例子中,我们有三个不同的类别,每个类别的数量相等——最大可能熵。
Maximum Entropy
然后,该算法考虑它可以进行的每个分割,并单独计算每个子部分的熵值。然后它搜索寻找它能产生最低可能组合熵的分裂。请记住,在本例中进行的第一次拆分实际上是将图表的橙色上半部分与蓝色和绿色的下半部分分开:
Below Maximum Entropy
在这个新的配置中,顶部的熵接近于零,因为它基本上只包含橙色点。底部的熵也减少了,因为即使它仍然在颜色之间平均分配,也只有两种颜色选项,而不是三种。决策树的实际实现可能使用不同的特定度量——GINI 杂质是一种类似但略有不同的群体同质性度量——但功能目标是相同的。
如何
当建立决策树模型时,有许多参数可以调整,特别是为了避免过度拟合。
**每叶最小样本数/每分割最小样本数:**这两个值都是为了避免过度拟合。一个是算法将在树的最终节点之一中接受的最小数据点数,另一个是在算法决定进行另一次分裂之前需要在子组中的点数。如果每片叶子没有最小数量的样本,算法可能会简单地继续进行越来越小的切割,直到组中只有一个样本。这些算法看起来是同质的和低熵的,但是有严重过度拟合的风险。考虑我们之前的一个例子:
Is this a satisfactory final model?
这些地区中的一些仍然不是同质的!如果我们让算法继续下去呢?
Extreme overfit
现在,奇怪的异常值,也就是大部分橙色区域中的蓝色,被赋予了自己的小雕刻,可能只包含一个或两个点的超薄区域。该模型现在过拟合。
最大深度:类似地,你可以通过给树设置一个最大深度来避免过度拟合,允许算法只做这么多的分割。您希望模型的深度可能取决于数据的性质——您有多少个特征,样本有多大,等等。
优势和劣势
决策树很容易建立;它们可以用特征工程来改进,但不一定需要。它们也不真正需要缩放或标准化特征。它们很好地处理了非线性,而且不像 KNN 模型,它们是可以解释的。用户可以查看决策树的阈值,找出给定预测是如何得出的。它们还隐式地执行自己的特征选择。本身不是特别有用的特征可能不会被利用,因为算法将发现沿着该特征分割样本不起作用。
不幸的是,决策树也很脆弱。大多数实现用来设计树的算法试图在任何给定点进行尽可能好的分割,这就是所谓的“贪婪算法”,它不一定产生尽可能好的结果,尽管它通常足够好。一个不幸的后果是,对训练数据的微小改变会导致算法找到一个完全不同的树,也许早期的分裂之一将在不同的地方进行,从而改变随后的所有决策。虽然决策树通常是可解释的,但是根据具有许多特征的数据训练的深度树将是复杂的,因此不像线性回归中的系数那样容易解释。
最后要指出的一点是,由树做出的决策的性质意味着某些形状的决策边界对树来说更容易或更难映射。当相似数据点的组彼此充分分离或者组之间的边界与特征对齐时,决策树将能够相当好地跟踪它们。当许多点靠近与特征不对齐的边界时,树会变得更难。考虑下面的例子:
Yet another classification problem
您可以直观地看出这两个区域之间的边界实际上是线性的。如果将 x 和 y 坐标都考虑在内,则只需一次分割就可以准确地分离蓝色和橙色区域,如下所示:
A sensible way of splitting up the territory
然而,决策树算法很难定义与一个特征不一致的决策边界。对于这个例子,你需要让树至少有四到五层深,你会得到一个更加混乱的边界:
What our decision tree algorithm actually produces
假设您对数据有很好的了解,较小的特征工程将允许您克服这一点,但是这些种类的边界可能用其他工具更好地建模。
基础知识:分类和回归的 KNN
从头开始的数据科学
对 KNN 模型如何工作建立直觉
数据科学或应用统计课程通常从线性模型开始,但就其方式而言,K 近邻可能是概念上最简单的广泛使用的模型。KNN 模型实际上只是一种普遍直觉的技术实现,即具有相似特征的事物往往是相似的。这算不上深刻的见解,但这些实际的实现可能非常强大,而且,对于接近未知数据集的人来说至关重要的是,可以在没有任何复杂的数据工程或模型设置的情况下处理非线性。
什么
作为说明性的例子,让我们考虑使用 KNN 模型作为分类器的最简单的情况。假设您有属于三类之一的数据点。二维示例可能如下所示:
Three categories
您可能会非常清楚地看到不同的类别被分组在一起——图表的左上角似乎属于橙色类别,右边/中间部分属于蓝色类别。如果给你一个新点在图上某处的坐标,并问它可能属于哪一类,大多数情况下答案会非常清楚。图表左上角的任何一点都有可能是橙色的,等等。
然而,在两个类之间,任务变得不那么确定,我们需要确定一个决策边界。考虑下面用红色添加的新点:
Which of the original categories does the new point belong to?
这个新点应该归为橙色还是蓝色?这一点似乎落在两个集群之间。您的第一直觉可能是选择离新点更近的集群。这将是“最近邻”方法,尽管在概念上很简单,但它经常产生相当合理的预测。哪个先前识别的点是最接近的新点?仅仅目测图表,答案可能并不明显,但计算机很容易浏览这些点,并给我们一个答案:
With the nearest point now circled in red
看起来最近的点在蓝色类别中,所以我们的新点可能也是。这就是最近邻法。
此时,您可能想知道 k-nearest-neighbors 中的“k”是什么意思。k 是模型在评估新点时将查看的邻近点的数量。在我们最简单的最近邻例子中,k 的值就是 1——我们查看最近邻,仅此而已。然而,你可以选择看最近的 2 或 3 个点。为什么这很重要,为什么有人会把 k 设得更高?一方面,类之间的边界可能会彼此相邻,使得最近的点给我们正确的分类不太明显。考虑我们例子中的蓝色和绿色区域。事实上,让我们把它们放大:
Zooming in on the the boundary between the blue and green regions
请注意,虽然整体区域看起来足够不同,但它们的边界似乎有点相互交织。这是带有一点噪声的数据集的共同特征。在这种情况下,对边界区域的事物进行分类变得更加困难。考虑这个新观点:
A new point along the border between blue and green regions
一方面,从视觉上看,最接近的先前确定的点肯定是蓝色的,我们的计算机可以很容易地为我们确认这一点:
The new point is closes to a blue point
另一方面,最近的蓝点看起来有点像异常值,远离蓝色区域的中心,有点被绿点包围。这个新点甚至在那个蓝点的外面!如果我们观察离新红点最近的三个点会怎么样?
Looking at three nearby points
或者甚至是离新点最近的五个点?
Looking at five
现在看来,我们的新点是在一个绿色的街区!它碰巧有一个附近的蓝点,但优势或附近的点是绿色的。在这种情况下,为 k 设置一个更高的值,查看一些附近的点,并让它们以某种方式对新点的预测进行投票,这可能是有意义的。
正在说明的问题是过度拟合。当 k 设置为 1 时,算法识别为蓝色和绿色的区域之间的边界是凹凸不平的,它随着每个单独的点向前弯曲。红点看起来可能在蓝色区域:
The same graph with the implicit decision boundaries highlighted
然而,当不同的邻近点投票时,将 k 提高到 5 可以平滑决策边界。红点现在似乎牢牢地位于绿色区域:
Decisions now made based on 5 neighbors
k 值较高的代价是决策边界中粒度的损失。将 k 设置得很高会使边界变得平滑,但是您试图模拟的真实世界的边界可能并不完全平滑。
实际上,我们可以使用相同类型的最近邻方法进行回归,其中我们需要一个单独的值而不是一个分类。考虑下面的回归:
A simple regression example
数据是随机生成的,但生成的数据是线性的,因此线性回归模型自然会很好地拟合该数据。不过,我想指出的是,您可以使用 K-最近邻方法以概念上更简单的方式来近似线性方法的结果。在这种情况下,我们的“回归”不会像 OLS 模型那样是一个单一的公式,而是任何给定输入的最佳预测输出值。考虑 x 轴上的值-.75,我用垂直线标记了它:
无需求解任何方程,我们只需考虑附近的点,就可以得到输出的合理近似值:
Three points near a chosen x-value
预测值应该在这些点附近,而不是更低或更高,这是有意义的。也许一个好的预测是这些点的平均值:
An average of their outputs
您可以想象对所有可能的输入值都这样做,并到处得出预测:
将所有这些预测用一条线连接起来,我们得到了回归结果:
在这种情况下,结果并不是一条清晰的直线,但它们确实很好地描绘了数据的上升趋势。这可能看起来不太令人印象深刻,但这种实现的简单性的一个好处是它可以很好地处理非线性。考虑这个新的点集合:
A non-linear example
这些点是通过简单地对上一个示例中的值求平方而生成的,但是假设您在野外遇到了这样的数据集。显然,它在本质上不是线性的,虽然 OLS 风格的模型可以轻松处理这种数据,但它需要使用非线性或交互术语,这意味着数据科学家必须就执行何种数据工程做出一些决定。KNN 方法不需要进一步的决策——我在线性示例中使用的相同代码可以完全重新用于新数据,以产生一组可行的预测:
与分类器示例一样,设置较高的 k 值有助于我们避免过度拟合,尽管您可能会开始失去对边缘的预测能力,尤其是在数据集的边缘周围。考虑第一个示例数据集,其预测 k 设置为 1,这是最近邻方法:
KNN reggressor with K set to 1
当模型从数据集中的一点跳到下一点时,我们的预测会不稳定地跳跃。相比之下,将 k 设置为 10,这样总共 10 个点被平均在一起用于预测会产生更平滑的行驶:
KNN regressor with K set to 10
一般来说,这看起来更好,但是您可以在数据的边缘看到一些问题。因为我们的模型为任何给定的预测考虑了如此多的点,当我们接近样本的一个边缘时,我们的预测开始变得更差。我们可以通过将我们的预测加权到更近的点来解决这个问题,尽管这有其自身的权衡。
如何
当建立 KNN 模型时,只有少数参数需要选择/调整以提高性能。
K:邻居的数量:如前所述,增加 K 将会平滑决策边界,避免以牺牲某些分辨率为代价的过度拟合。不存在适用于每个数据集的单一 k 值。对于分类模型,特别是如果只有两个类别,通常为 k 选择一个奇数。这是为了使算法永远不会遇到“平局”:例如,它查看最近的四个点,发现其中两个在蓝色类别中,两个在红色类别中。
距离度量:事实证明,有不同的方法来衡量两点之间的“接近”程度,并且这些方法之间的差异在更高维度中会变得显著。最常用的是欧几里得距离,这是你在中学可能已经学过的使用勾股定理的标准排序。另一个度量标准是所谓的“曼哈顿距离”,它测量每个主要方向上的距离,而不是沿着对角线的距离(就好像你从曼哈顿的一个十字路口走到另一个十字路口,不得不沿着街道网格走,而不是选择最短的“直线”路线)。更一般地说,这些实际上是所谓的“闵可夫斯基距离”的两种形式,其公式为:
当 p 设置为 1 时,该公式与曼哈顿距离相同,当设置为 2 时,为欧几里德距离。
权重:当算法对一个类进行投票时,有一种方法可以解决可能出现的“平局”问题,也有一种方法可以解决我们的回归预测越接近数据集边缘越糟糕的问题,这就是引入权重。对于权重,近点比远点更重要。该算法仍然会查看所有 k 个最近的邻居,但是最近的邻居比较远的邻居拥有更多的投票权。这不是一个完美的解决方案,并带来了再次过度拟合的可能性。考虑我们的回归示例,这次使用权重:
KNN regressor, but not with weighted voting
我们的预测现在直接到了数据集的边缘,但是你可以看到我们的预测现在摆得更靠近单个的点。当您在点之间时,加权方法工作得相当好,但是当您越来越接近任何特定点时,该点的值对算法的预测有越来越大的影响。如果你足够接近一个点,这几乎就像把 k 设为 1,因为那个点有如此大的影响。
缩放/标准化:最后一点,也是非常重要的一点是,如果不同的特征变量具有非常不同的缩放比例,KNN 模型可能会被抛弃。假设有一个模型试图根据卧室数量和房屋总面积等特征来预测市场上房屋的销售价格。一所房子的平方英尺数比卧室数的差异更大。通常,房子只有少数几个卧室,即使是最大的豪宅也不会有几十或几百个卧室。另一方面,平方英尺相对较小,所以房子的面积可以从小的接近 1000 平方英尺到大的上万平方英尺不等。
考虑两个卧室的 2000 平方英尺的房子和两个卧室的 2010 平方英尺的房子之间的比较。脚几乎没什么区别。相比之下,一栋 2000 平方英尺、有三间卧室的房子就大不相同了,它代表了一种非常不同的、可能更拥挤的布局。然而,一台天真的计算机没有上下文来理解这一点。它会说三居室离二居室只有“一”个单元,而 2010 平方英尺的房子离 2000 平方英尺的房子是“十”个单元。为避免这种情况,应在实施 KNN 模型之前对要素数据进行缩放。
优势和劣势
KNN 模型易于实现,并能很好地处理非线性。拟合模型也往往很快:毕竟,计算机不需要计算任何特定的参数或数值。这里的权衡是,虽然模型建立起来很快,但预测起来较慢,因为为了预测新值的结果,它需要搜索训练集中的所有点,以找到最近的点。因此,对于大型数据集,与其他可能需要更长时间才能拟合的回归方法相比,KNN 可能是一种相对较慢的方法,但随后可以通过相对简单的计算进行预测。
KNN 模型的另一个问题是它缺乏可解释性。OLS 线性回归将有清晰的可解释系数,这些系数本身可以给出给定特征的“效应大小”的一些指示(尽管,在指定因果关系时必须小心)。然而,对于 KNN 模型来说,询问哪些特征影响最大并没有什么意义。部分由于这个原因,KNN 模型也不能真正用于特征选择,就像增加了成本函数项的线性回归(如 ridge 或 lasso)一样,或者决策树隐式选择哪些特征似乎最有价值。
基础知识:线性回归
从头开始的数据科学
对线性模型如何工作建立直觉
线性回归模型是第一批预测模型。虽然在概念上很简单,但它们有一些关键的特性,使它们灵活、强大且易于理解。虽然更新的和概念上更复杂的模型可能优于线性回归,但线性模型继续得到广泛使用,特别是在社会科学和政策领域,在这些领域,数据收集可能很昂贵,而高度可解释的模型有很大的价值。线性回归的扩展,如 Ridge 和 Lasso,可以帮助避免在特征丰富的模型中过度拟合,甚至执行特征选择。逻辑回归使线性框架适合分类问题。首先,让我们看看平面-普通线性回归是如何工作的。
什么
线性回归将输出变量建模为输入要素的线性组合。这到底是什么意思?让我们从最简单的情况开始,了解一下模型是如何工作的,然后考虑如何将其扩展到具有更多功能的更复杂的情况。
线性模型试图找到特征变量和输出之间尽可能简单的关系。这通常被称为“拟合直线”。你可能记得在代数课上,任何给定的线都可以表示为某种形式的方程:
其中 y 是因变量/输出,m 是斜率,x 是输入/自变量。x 每增加一个单位,y 就增加 m 个单位(如果 m 为负,y 就减少)。b 项是一个截距项,它在不改变斜率的情况下向上或向下移动你的直线。线性回归试图找到输入要素和因变量之间的相似关系,并最终创建一个相似的公式:
在一个变量中,它看起来就像一条线,除了我们把系数 m 重命名为希腊字母 beta。让我们想象一个简单的例子。以下是一些数据点:
Some points to consider
这就像每个真实世界的数据集一样,有点嘈杂,但显然有一个趋势:随着 x 的增加,y 也增加。也许这种关系用一条线就能很好的估计出来。你如何选择哪一行呢?考虑以下选项:
Two possible lines: which describes the points best?
哪条线似乎最能抓住趋势?不一定清楚。橙色的线看起来最接近左边的点,但是一旦你接近分布的中心,就不清楚了。在右手边,也许橙色线超出了标记,太高了。
线性回归模型通过最小化直线和各个点之间的垂直距离来选择直线。只考虑红线,以及点和线之间的垂直距离,现在显示为紫色线段:
Just the red line, with the vertical distances to the line visualized
您的回归线代表您对任何给定 x 值的预测。这些紫色线代表每个点的预测误差。当然,你画的直线不会完全没有误差,但是尝试最小化这个误差似乎是一个合理的目标。实际上还有一个小问题,那就是线性回归模型通常不会简单地找到最小化预测误差的直线,而是找到最小化预测误差平方值的直线。对于更倾向于数学的人来说,这被称为误差平方和或 SSE,用如下公式表示:
我们最小化平方误差的事实就是这种方法的名字:普通最小二乘法或 OLS。让我们在我们的可视化中包括平方误差的值。红线的系数为 40,截距值为 10:
Spelling out the coefficient, intercept and SSE
调整截距可以上下移动线条。如果我们把截距改为 30,线会上移。新线条是更好还是更差的预测器?它变得更靠近一些点,但是离已经在线下的点更远。然而,从误差平方和来看,这条新线总体上是一个更差的预测指标:
The new intercept increases our SSE
当然,我们也可以调整系数,改变直线的斜率。事实证明,我们的线太陡了,将系数降低到 30 可以改善整体预测:
Our best fitting line yet
通过使用一点微积分,我们可以找到最小化 SSE 的系数和截距的值,并给出最佳拟合线(或者更准确地说,您的计算机可以使用微积分并为您找到这些值)。
虽然这些示例都很简单,只有一个输入要素,但这种方法很容易推广到多维。对于任何给定的特征集,多元 OLS 模型将找到相应的系数集。这些多元模型有点难以可视化,但在概念上是相同的。我们将有多个 beta 系数,而不是一个 beta 系数,每个 beta 系数对应一个输入要素:
从这些线性模型公式的描述中,你可能已经看到,线性模型的一个好处是很容易解释它们是如何通过系数工作的。如果 X1 的值增加 1,模型对 y 的估计值就会增加β1 的值。很容易看出是什么影响了您的预测值,也很容易知道如果您假设改变其中一个特征,您的预测将会如何变化。
假设和警告
线性回归模型隐含地对特征变量以及它们与因变量的关系做出某些假设。线性回归有用的部分原因是,在满足这些假设的情况下,它具有可预测的特性。例如,您可以为任何给定的预测创建准确的误差幅度。如果不满足基本假设,线性回归模型将不会那么准确,尽管它们在提供可用预测的意义上可能仍然有用。这些假设是什么?
1.输出变量是特征变量的线性组合——线性
简单地说,输出变量可以用一组系数乘以相应的特征变量来精确描述。我们将每个输入特征变量乘以其 beta 系数的公式在技术上称为“线性组合”。需要注意的是,虽然这被称为线性,但并不意味着线性回归都是简单的直线,因为输入特征变量不需要是线性的 。 考虑这样一个例子:
A non-linear example.
这些点显然不是线性的——我可以确认它们是方形的,通过生成一组线性点,然后简单地平方 y 值来创建。当然,没有一条直线能很好地捕捉曲线!然而,线性回归仍然可以处理这样的数据集,因为输入要素可以是二次的。以前,我们的例子显然是线性的,所以我们的公式也是线性的:
在这种情况下,看起来有一些二次的东西在进行,但我们仍然可以使用一个线性框架和一点点功能工程。让我们添加一个新的特征,这将是我们原来的特征平方。现在我们有了新的公式:
该公式是两个特征 X1 和 X1 的平方的线性组合。它还有线性!我们之前的所有框架基本上是一样的,我们可以用同样的方式建模这些点并可视化这些错误:
But linear regression can still handle it!
2.恒定方差—同方差
这是一种奇特的说法,即当你移动输入变量时,输出变量的方差保持不变。例如,这组点看起来是同质的,是线性回归的主要候选点:
Another regression example
如果我们对此拟合一条线,我们会注意到,虽然这些点可能不总是正好落在这条线上,但预测线和这些点之间的平均距离似乎不会随着您的前进而增长或收缩:
另一方面,这种分布看不到恒定的方差,技术术语是异方差的:
A heteroscedastic example
像这样的分布看起来不太适合线性回归。当然,我们可以用一条线来拟合它,但是这条线提供的预测似乎越来越不准确:
Our linear regression looks less impressive…
注意到异方差会让你对使用线性模型有所犹豫,但这并不意味着 OLS 线性模型完全无效。在真实世界的数据中,异方差通常是一种迹象,表明某些具有解释价值的特征(某些解释变量,尤其是交互项)尚未纳入模型。为了说明我这样说的意思,让我们想象一个真实世界的场景,它可能会给我们提供类似上面例子的数据。
假设你是一名经济研究员,正在进行一项关于收入分配的研究。您希望创建一个基于一些变量来预测收入的模型:收入似乎会根据您的所在地、个人受教育程度等因素而有所不同。要包括的一个变量是这个人已经工作了多长时间。除非经济不景气,否则随着职业生涯的进展,人们往往会挣得更多。你问你的回答者他们工作了多久,然后把结果标在 x 轴上,收入水平标在 y 轴上。你会发现你有一个像上面这样的图表,收入不断扩大。有人告诉你要警惕异方差,这是否意味着你应该放弃这个变量或者尝试不同类型的模型?也许不是。
在我们做任何激烈的事情之前,让我们试着想清楚我们每个变量之间的关系。我们看到收入随着工作年限的增加而增加,但这种情况的发生率似乎并不是对每个人都一样。让我们试着用另一个变量来划分我们的数据。让我们以教育水平为例,根据受访者是否拥有高中学历、大学学历或专业学位(如律师或医生),对他们进行分组。分别考虑这些群体,可能会给我们一个这样的图表,不同的颜色代表不同的教育水平:
Our heteroscedastic example, color coded
现在数据看起来更像三个同方差分布!每一个教育水平似乎都可以用一条线来很好地估计,但是这些线的斜率都有一点不同,这就是当我们只看工作年限和收入时,总体分布呈现出异方差的原因。实际上,我们可以在一个模型中说明这一点,而不用通过包含一个交互项将数据分成三组:我们将创建的一个新特征,类似于我们的教育水平乘以工作年限。我们的回归公式如下所示:
这种情况的挑战在于它需要人工输入——模型无法为您执行特征工程。在您拥有领域知识并能够合理猜测特征变量可能如何交互的领域,您仍然可以使用线性模型,并获得良好拟合和高度可解释的模型。
3.误差的独立性
误差,即数据中的实际值和模型的预测值之间的差异,应该彼此不相关。任何一组随机点的误差项本质上应该看起来像同分布的独立随机变量。如果情况并非如此,这意味着您的模型在某些领域的预测性会低于其他领域。同样,这可能是变量偏差缺失的迹象。
4.缺乏完美的多重共线性
虽然可以通过将其他要素平方或使多个要素相互作用来创建要素,但没有一个要素应该与另一个要素完全相关(例如,一个要素以年为单位测量年龄,另一个要素以月为单位测量年龄),或者是其他要素的线性组合。首先,这会产生可解释性问题,因为多个系数可以描述同一条线,例如 y = 24*年数= 12 *年数+1 *月数= 2 *月数。这也产生了一个问题,因为统计包通常使用线性代数求解系数,如果存在完美的多重共线性,这种方法就不起作用。(用专业术语来说,代表每个样本变量值的‘设计矩阵’必须是可逆的,多重共线性则不是这种情况。)
基础:逻辑回归和正则化
从头开始的数据科学
线性模型的扩展
普通最小二乘线性回归开箱即用,功能强大,用途广泛,但在某些情况下它会失败。首先,它是一个“回归”框架,这使得它很难作为一个分类器来应用。另一方面,与决策树不同,线性回归模型不执行自己的隐式特征选择,这意味着如果包含太多特征,它们容易过度拟合。幸运的是,线性模型有一些扩展,允许我们克服这些问题。逻辑回归将线性回归框架转变为分类器和各种类型的“正则化”,其中脊和套索方法是最常见的,有助于避免在特征丰富的情况下过度拟合。
逻辑回归
逻辑回归基本上采用了线性回归公式,使其能够充当分类器。为什么常规的 OLS 线性回归不能单独作为一个分类器?让我们考虑一个例子。下面,是一个简单的一维分类例子。x 变量是连续的,但 y 变量是分类的,可以是零或一:
A simple regression example
有一些重叠,但是我们可以直观地看到,当我们向右移动时,我们的分类选项卡变得更加突出。在右边,更多的点是 1 而不是 0,在某个 x 值之上,我们看到的所有的点的 y 值都是 1。这看起来是一个相当容易建模的关系,但是如果我们试图简单地用线性回归来拟合这个数据,结果就有点奇怪了:
Fitting an OLS line to our classification problem
一方面,这条线在某种程度上成功地捕捉到了两个变量之间的正关联,但是这条线的输出并没有太大的意义。如果我在 x 值为 0.25 时查阅这条线,我们发现这条线预测的值为 0.71。因为我们试图预测一个只取值 0 或 1 的变量,所以预测 0.71 有点奇怪;我们的二元变量实际上不能取那个值,那么这个预测意味着什么呢?
在这种情况下,解释小数值的一种方法是稍微重新组织一下我们的问题。我们可以不要求模型预测自变量的值,而是要求模型给出自变量的值为 1 的概率。随着模型输出的增加,我们可以说变量为 1 的几率也增加了。这个框架中的小数值更有意义。像 0.71 这样的值意味着什么?嗯,这个结果比模型给我们的数字低的可能性更大,比给我们的数字高的可能性更小!
然而,要把我们简单的线性回归变成这种模型,还有一点工作要做。首先,从直觉上看,我们的模型应该输出的概率是非线性的。就目前的情况而言,我们的线性回归有时会给出大于 1 的值或负值,这同样没有意义。在这种情况下,我们的解决方案是让我们的线性模型通过一个 sigmoid 函数。“Sigmoid”在这里的意思是“S 形”,我们可能会用到一些函数,但最常用的是逻辑函数。逻辑函数的一般形式如下:
这可能看起来有点吓人,但是值得稍微思考一下。分数底部的 e 上的指数看起来像我们之前的线性回归方程,除了整个东西都是负的。随着这个“回归方程”的输出变得非常大,指数相应地变负,e 的值的乘方变为零;因此,整个表达式的值更接近 1/(1+0),即 1。如果这个回归方程的输出非常负,那么 e 被提高到正值,分数的底部变得非常大;整个表达式的值越来越接近 0。
在我们之前的示例中,拟合的逻辑曲线如下所示:
Our simple classification problem with a fitted logistic curve
我们的曲线从来不会低于 0 或高于 1,所以我们可以明智地将其解释为二元变量为 1 的概率。拟合这条曲线的过程基本上与我们拟合正常的线性回归线是一样的。在这里,我们找到了截距和系数β1,使直线和点之间的垂直距离最小化,这里我们同样找到了截距和系数,但现在我们要使到这条曲线的距离最小化。
一旦你得到了最合适的逻辑曲线,将预测的概率转化为预测的结果就相对简单了。因为结果是二元的,所以你的预测也是二元的。最简单的是,如果你向大多数统计软件包询问预测结果,它们可能会做什么,只要你的逻辑回归给你 50%以上的概率,你就可以简单地预测该类。让我们将这些预测包含在我们的可视化中:
当然,由于该模型在任一点上对概率产生更细粒度的估计,因此您可以使用逻辑模型为进一步的模型产生输入,这些模型本身也接受概率。例如,预测选举结果的人可能有一套模型来预测每个州的选举结果,然后在一个模型中使用这些概率来预测整个国家所有州的结果范围。
逻辑回归以与简单线性回归非常相似的方式推广到多个变量,向回归公式添加了更多的特征和相应的系数:
逻辑版本中的系数比普通线性回归中的系数更难解释。理论上,您可以通过将它们与正在建模的结果的对数概率的变化相关联来直接解释它们,但这意味着什么有点不清楚,因为实际上,移动输入要素之一对概率的影响取决于您从哪里开始。然而,你可以简单地解释系数的方向。具有正系数的要素随着其增加而增加建模结果的概率,而具有负系数的要素随着其增加而降低概率。
规则化(脊和套索)
山脊和套索正则化也称为“收缩”方法,因为它们会减少或收缩结果回归中的系数。这减少了模型中的方差:当输入变量改变时,模型的预测变化比没有正则化时要小。为什么要减少模型的方差?以避免过度拟合。
过度拟合导致我们的模型无法推广。有时我们可能会发现,我们已经在一些数据集上训练了一个模型,它似乎在这些数据上工作得很好,但当我们在一些新的数据集上测试它时,性能会受到影响。出现这种情况有几个原因。例如,也许我们得出模型的样本在某些方面有偏差。另一个常见的问题是过度拟合,即模型过于符合训练集,因此错过了更具普遍性的趋势。
考虑创建一个模型来估计某个城市市场上新房子的售价。你有以前房屋销售的数据,并着手创建一个线性回归。显然,大房子往往比小房子更贵,所以你可能会自然地包括平方英尺或卧室数量等特征。你可能还会注意到,不同的社区可能会有不同的定价,比如说,一个社区的两居室可能比另一个社区的要贵。所以,你决定在你的模型中包括邻居,但是为什么要止步于此呢?也许在一个街区内也有变化,所以你可以考虑包括次街区大小的单元,也许是单独的街道甚至单独的街区。你可能能够想象为什么如此精细的位置标记实际上会对房子的最终价格产生影响——可能街区的一端过于靠近嘈杂、繁忙的十字路口,不太理想,或者街区的另一部分阳光更好——但当涉及到实际训练你的模型时,你会遇到几个挑战。
首先,在回归中包含的变量越多,就越有可能遇到特征之间的过度协方差(在添加交互或幂项时尤其可能)。另一方面,支持你估计任何给定系数的数据量可能很小。当按街区对房屋销售进行分组时,每个街区可能都有许多数据点,但是如果将数据一直拆分到街区级别,任何给定的街区都可能只有少数几个示例可用于训练模型。如果您要包括每个单个块的要素,任何块的系数都可能很容易被异常值扭曲-例如,如果您的训练数据中的一所房屋碰巧以异常高或异常低的价格出售。您的模型可以很好地代表您的训练数据,但不一定能很好地预测未来。
多少功能才算太多?哪些特性是最重要的?正规化会有所帮助。岭正则化和套索正则化都是通过向用于推导回归公式的成本函数添加一个新项来实现的。回想一下,OLS 回归通过最小化训练数据的预测误差平方来查找系数和截距,如下式所示:
Lasso 正则化将另一项添加到此成本函数中,表示模型中所有系数的大小之和:
在上面的公式中,第一项是我们知道并喜爱的残差平方和,第二项是一个惩罚,其大小取决于所有系数的总大小。该总和前面的项由希腊字母 lambda 表示,是一个调整参数,用于调整惩罚的大小。如果它被设置为 0,你会得到一个普通的 OLS 回归。岭回归遵循相同的模式,但罚项是系数平方之和:
包括额外的惩罚项实质上抑制了包括额外特征。一项新功能可能有助于通过减少残差来最小化成本函数中的第一项,但它会增加惩罚项。这里有一个最终的平衡动作,增加一个系数的值与模型的整体方差的相应增加相权衡。
降低模型的方差可以提高模型对未知数据的准确性。在我们的住房价格预测例子中,你可以想象包括那些逐块的变量,但是看到这些变量的系数非常低。一个离群的房子不再把那个地区的所有预测都抛出这么多。
不言而喻,脊线和套索也作为它们自己的特征选择;不驱动回归预测能力的特征看到它们的系数被下推,而更具预测性的特征看到更高的系数,尽管增加了惩罚。岭回归,因为它平方惩罚项中的系数,倾向于将不太有用的特征的系数降低到接近零,但不完全为零。另一方面,Lasso 将有助于将一些系数一直发送到零。
密码学基础
R 中的应用程序
Photo by Markus Spiske on Unsplash
你有没有想过公司如何安全地存储你的密码?或者在网上购物时,你的信用卡信息是如何保密的?
答案是密码学。现在,绝大多数互联网网站都使用某种形式的加密技术来确保用户的隐私。就连来自你 Gmail 账户的电子邮件等信息,在谷歌数据中心流动时也会被加密。
什么是密码学?
密码学是一门针对潜在的第三方对手安全传输信息的科学。
例如,想想“模仿游戏”对于那些看过这部电影的人来说,你知道这是一个关于艾伦·图灵如何创造第一台计算机来解密德国恩尼格玛机的故事,这是一台对所有德国通信进行编码的机器,以便纳粹可以通过无线电安全地传输信息。虽然这些信息很容易被盟军截获,但这些信息却无法被理解,因为它们都是用每天都在变化的外国密钥进行编码或加密的。
Cover of “The Imitation Game”
加密算法
- 对称密钥加密—在对称密钥算法中,有一个公共密钥,用于锁定和解锁加密“盒子”发送方和接收方拥有相同的密钥。对称密钥算法非常快,因为密钥不需要很长;然而,首先存在共享公共密钥的问题,因为它可能被截取,然后整个系统受到危害。
- 非对称密钥加密—在非对称密钥算法中,只有接收方持有密钥。接收者可以公开发出一个锁(或者锁定我们假设的盒子的方法),只有接收者持有这个锁的密钥。锁叫做公钥,钥匙叫做私钥。注意:每个私钥只有一个公钥。
非对称密钥加密:它是如何工作的?*
首先,接收器通过以下方式生成 2 个公钥 n 和 e ,以及一个私钥 d :
- 选择 2 个大素数 p & q ,使得 n = p*q
- 选择另一个素数 e,例如 3
- Calculating d such that d*e-1 = k(p-1)(q-1).
Next, you’re ready to encrypt:
- Next transform the plaintext that you want to send into a number m ,使用 ASCII 数字表示或其他方法。
- 通过找到密文 c= m^e mod n. 来加密数字 m
- 将 n、e 和 c 发送给接收者。
如果这很难理解,请查看我的 Github,上的示例代码,它使用 R 中的一个名为“openssl”的包,引导你完成这个过程的每一步。摘录见下文:
R code on Github to practice encrypting & decrypting messages
签名
加密的另一个重要方面是签署消息的能力。它允许您验证发送者,并避免将敏感信息发送给错误的用户和/或公钥。
如何签署消息
创建一个签名 M ,这样 *S = M^d mod n,*和你的消息一起发送 S 。记住 d 是你的私钥。
如何验证签名
如果 M= S^e 模 n. ,接收者可以快速确定签名有效
R code on Github to sign & verify a message
散列法
您会注意到,在上面的示例代码中,我使用函数 sha256()作为变量 m_hash。哈希是一种单向加密功能,允许您将信息不可逆地转换为称为哈希的字母和数字字符串。哈希不同于加密,因为哈希意味着不可能被解密,尽管许多人已经尝试过,有些人已经成功了。当您听到密码或其他安全漏洞时,通常指的是加密黑客攻击,黑客能够将哈希与原始文本进行匹配。
工作原理:
有各种各样的散列算法( MD 、 SHA1 、 SHA2 、&SHA3),但是我们将集中讨论 SHA256 算法,因为它是当今最常见的算法。
SHA256 算法首先将文本转换为由 0 和 1 组成的 256 位字符串(因此得名)。一个例子是:
1110001010111000101011100010101110001010111000101011100010101110001010111000101011100010101110001010111000101011100010101110001010111000101011100010101110001010111000101011100010101110001010111000101011100010101110001010111000101011100010101110001010000101
由于这些二进制散列非常长,无法显示,因此它们被转换为十六进制格式(值 0–9 & a-f 的 64 个字符的组合),每个 4 位部分代表一个字符。十六进制表示的一个例子是:
a 235810 CD 87 df 030d 78 e 890d 90 c 187 cc 04 a 09 ad 09 b 289 b 91 BBA e9 d 890 f 987 e
密码
哈希的一个主要用途是用于密码验证。对你的银行来说,保存一个密码数据库是非常不安全的,所以它保存了一个散列数据库,与你的实际密码相对应。当您在线登录银行时,系统会对您的密码进行哈希处理,然后根据您的档案中的哈希进行核对。
这个系统之所以有效,是因为哈希算法总是为相同的密码生成相同的哈希——哈希不是字符的随机组合。哈希也是拥有复杂且唯一的密码很重要的原因,因为如果我计算“密码 123”的哈希,并将其与对应于您的哈希进行匹配,那么我就知道您的密码是“密码 123”,并且我可以轻松地在网上侵入您的银行帐户。
加分内容!
彩虹桌
彩虹表是普通密码的散列数据库。
以 ATM 密码为例。使用数字 0-9 的 4 位 ATM pin 码有 10,000 种组合。彩虹表将为 10,000 个代码中的每一个提供散列,黑客可以使用该散列列表将散列映射回您的代码,从而从散列中解码您的 pin 号。
晚上怎么睡得着?
放松点。银行和大多数其他组织明白黑客想要获取敏感信息,因此他们通常通过一种叫做“盐”的东西来提供额外的安全层。
Salts 是添加到密码(或其他信息)中的额外字符串,使其更独特、更长、更难破解。
添加 salt 会将您的 pin 更改为类似于“0000B_of_A_salt”的内容,这将具有完全不同的散列,而不是 pin = "0000 "。组织可以创造性地使用盐来使黑客攻击变得极其困难。为了使用彩虹表来破解这样的算法,您需要为每种可能的盐准备一个彩虹表,这极大地增加了 pin 号码的可能组合的数量。
区块链
加密技术使区块链能够通过签名来验证网络中的发送者,并确保过去的交易和记录(称为“块”)不能被更改。
区块链还利用哈希算法为每个区块分配一个唯一的哈希,允许您区分不同的区块。
结论
既然你已经知道了散列和加密的所有知识,看看这个简短的视频关于艾伦·图灵是如何成功“黑掉”德国的 Enigma 机器的,如果你还没有看过的话,就看看这个电影吧!
*注意:出于本文的目的,我重点介绍了一种称为 RSA (Rivest、Shamir 和 Adleman)加密的非对称加密算法。
深度神经网络的基础
随着 Tensorflow 2.0、PyTorch 和 Fastai 等库的兴起,越来越多的人可以实现深度学习,这有助于理解深度神经网络背后的基础。希望这篇文章能够帮助那些正在学习深度神经网络的人。
当我第一次了解神经网络并实现我的第一个神经网络时,它们总是被表示为单独的人工神经元,本质上是具有单独加权输入、总输出和激活函数的节点。
当第一次回到学习深度神经网络时,这如何等同于矩阵乘法的概念并不明显。此外,与此相关的是为什么图形处理单元(GPU)及其衍生产品对深度学习结果的推动作用如此之大。
与矩阵乘法相比,这是一个非常简单的神经网络
让我们来看一个非常简单的网络,它有两个输入,一个隐藏层有两个神经元。每个神经元有两个权重,每个输入有一个单独的权重。每个权重乘以输入到神经元的每个输入,然后求和,并在通过激活函数馈送后形成来自神经元的输出。
A very simple network
这等同于应用两次矩阵乘法,然后是激活函数。
First matrix dot product multiplication. Generated using the excellent http://matrixmultiplication.xyz
Second matrix dot product multiplication. Generated using the excellent http://matrixmultiplication.xyz
GPU 带来的进步
我们离开上一个所谓的“人工智能冬天”的原因之一是使用 GPU 取得的计算进步,在这个冬天,人工智能和机器学习的研究和兴趣放缓并变得“冷淡”。
GPU 传统上用于计算机图形和计算机视频游戏领域。计算机图形学需要非常快的矩阵乘法。计算机视频游戏行业对 GPU 计算能力的需求导致了 GPU 设计者和制造商的巨大进步。
值得注意的是,最大的 GPU 公司之一 Nvidia 发布了 CUDA(一种用于在 Nvidia GPUs 上编程的语言/API)和 cuDNN(使用 CUDA 构建的深度神经网络库)。这使得训练神经网络中的计算更容易转移到 GPU,这些被打包为 PyTorch 的一部分,并相对容易地安装在 TensorFlow 中。只要计算可以并行进行,计算时间就会减少几个数量级。
矩阵乘法是线性函数
矩阵乘法是线性函数。许多线性函数层层叠加,仍然是一个线性函数,只是参数不同而已。因此,然而许多层叠在一起的矩阵乘法仍然只是线性函数。
线性函数是仿射函数的子集。仿射函数是值相乘并求和的函数。线性函数总是仿射的。
卷积神经网络(CNN)中使用的卷积是一种特殊的仿射函数,适用于自相关数据,通常是图像,但也适用于其他领域,如蛋白质相互作用、药物设计。这些卷积是逐元素的矩阵乘法,而不是点积乘法。逐元素矩阵乘法也是线性的。
非线性激活函数
激活函数是非线性函数的一种形式。传统上,在神经网络中,sigmoid 函数被用作非线性激活函数。在现代架构中,整流线性单元(ReLU)被用作激活功能或其变体之一。
一个 ReLU 听起来很复杂,它的维基百科页面试图让它变得复杂:https://en . m . Wikipedia . org/wiki/Rectifier _(neural _ networks)——一个 ReLU 说如果值小于零,就把值向上舍入为零。这是最复杂的了。
有一种称为泄漏 ReLU 的变体,它与 ReLU 相同,只是如果值小于零,它会乘以 0.1,而不是向上舍入到零。之所以这么叫,是因为它允许少量的值泄漏到激活中。
激活函数选择有时并不总是那么重要,只是它是非线性的。如果没有非线性激活函数,那么神经网络就不会被认为是深度的,因为它只是一个线性函数。线性函数永远无法执行复杂的任务,例如对电影评论进行分类或在卫星图像中识别地形类型。
这种任务可以用足够大的矩阵乘法来执行,每个矩阵乘法后面都有一个非线性。随着足够多的这些线性和非线性层夹在一起,网络变得非常深,可以产生任何任意形状或函数,可以近似任何东西。
这些计算的结果或输出被称为激活,通过训练网络来学习这些激活的原因,这些训练值是系数/参数,通常被称为权重和偏差。
参数/重量
这些参数的值是随机初始化的,或者从使用迁移学习的预训练模型中复制。
使用反向传播作为梯度计算技术,用随机梯度下降(SGD)或其他更快版本的 SGD 来训练这些参数。SGD 是最小化损失函数的优化。
随机梯度下降、损失函数和反向传播
损失函数评估训练期间预测输出与实际输出的差距。这可以是简单的数学计算,也可以是各种因素的复杂组合,根据正在进行的讨论使用不同的函数,如分类或回归。
损失函数用于评估训练期间的网络性能,损失变化的梯度是反向传播的,并用于更新权重以最小化损失。
每个节点的输出值被计算并缓存在通过网络的正向传递中。然后,通过网络的计算图反向计算损失函数相对于每个参数的误差的偏导数。
根据误差的导数,它显示了损失是增加还是减少,以及如何以什么速率增加(有多陡),然后这被用于少量更新参数以减少损失。学习率乘数用于缩小更新的幅度,以试图确保训练不会在损失空间中采取太大的步骤。这有助于优化权重的更新量。
这个过程就是 SGD。
分类
对于用于分类的训练网络,使用的损失函数是交叉熵。交叉熵基于预测正确的类和预测正确的置信度来提供损失。这是通过以下方式实现的:如果训练示例属于某个类(值为 1),则应用公式;如果训练示例不属于该类(值为 0),则应用不同的公式。
假设预测值在 0 和 1 之间,则:
- 训练示例实际值为 1,损失计算如下:log(预测值)
- 训练示例实际值为 0,损失计算如下:log(1 -预测值)
回归
如果网络用于回归,那么训练损失函数通常是零,通常均方误差(MSE)或均方根误差(RMSE)被用作损失函数中的度量。
最后一层激活功能
分类中的最后一个输出层表示类分数,它是任意的实数值,或者是回归类的实值目标。不同的激活功能用于这些任务。
二元分类
二元分类问题是将数据分类为属于某一类或不属于该类,例如“有肿瘤”或“没有肿瘤”。sigmoid 函数通常用作最后一层激活函数,因为它能很好地区分两类。
一个 sigmoid 函数的图形:f(x) = 1 / (1 + e^-x)
Graph for a sigmoid function: f(x) = 1 / (1 + e^-x)
sigmoid 函数渐近于 0,渐近于 1,输出 0 和 1 之间的值。高于或低于 0.5 的值表示数据将被分类到两个类别中的哪一个。
对于 sigmoid 函数的输出形状,当激活小于 0.5 时,sigmoid 函数将返回较低的概率值,如果激活大于 0.5,sigmoid 函数将返回较高的概率值。
这里可以使用下面描述的 Softmax 函数来代替 sigmoid 函数。如果有两个输出,每个输出表示两个类别中的一个。这里最好使用 sigmoid 函数,因为它更快。
多类单标签分类
在多类单标签分类中,数据可以分为许多类,但只能用这些类中的单个标签进行分类。例如,试图对狗的品种进行分类,代表狗的数据只能有一个品种。
Softmax 函数用于多类别单标签分类。给定类别 i 的输出被视为 e^i 除以 e^k 之和,其中 k 是每个类别。
The Softmax function, source: Finn Eggers’s activation function on YouTube.
Softmax 函数非常擅长挑选单个标签。它输出一个概率分布,其中输出总和为 1。每个类的输出范围在 0 到 1 之间。所有类别的概率之和为 1。
预测的类别是具有最高概率输出的类别。
多类、多标签分类
在多类别、多标签分类中,数据可以被分类为许多类别,并且数据可以用这些类别中的一个或多个标签来分类。
例如,卫星图像可以被标记为不同的天气和特征类别,例如云、森林、农业、水、湖泊和河流。一个图像可以很容易地属于许多这样的类。
使用 sigmoid 函数是因为对于给定的数据项可能有多个具有高概率的标签。
Softmax 不能有效地使用,因为概率的总和不等于 1,每个类的概率在 0 和 1 之间。即使是有经验的从业者,这也是一个常见的错误。
回归
根据问题的不同,回归的输出是一个数字或一组数字。如果有一个以上的输出,这被称为多元回归。
执行回归时的输出值通常是无界的。
通常对于回归,没有最后一层激活函数,尽管有时在一些应用中,sigmoid 函数可能是有益的,例如当使用回归进行协作过滤时。
将 Logstash 管道部署到 Kubernetes 的基础
Photo by Hin Bong Yeung on Unsplash
你脑子里有一长串你想学的东西的清单吗?
我有,直到我把这个清单写在一张纸上,并决定为此做些什么。在 2018 年底,我开始总结我一直在学习的东西,并决定为 2019 年的学习建立一些结构。
2018 年是有趣的一年,我已经换了 3 次工作,感觉我的学习无处不在。前一天我在学 Scala,第二天我在学 Hadoop。回想起来,我觉得我没有取得多大进展。
我决定在 2019 年做点什么。我在一张纸的一面写下了一年中的 12 个月,并在另一面写下了我想在那个月学习的内容,我的想法是,这一个月的学习将专注于特定的产品/堆栈/语言/事物。
之前被称为 ELK 的 Elastic stack 之所以排在这个列表的首位,有几个原因。
我以前用过 Elastic stack,更确切地说是 ElasticSearch 和 Kibana,但我觉得我可以从这两个产品中学到很多东西。我还想了解 Logstash,看看它能帮助我解决什么问题。
当我开始学习新的东西时,我会设定一些小的、可实现的目标。我写的其中一个目标是在 Kubernetes 中运行一个功能完整、运行正常的 Logstash 管道,从某个地方接收数据,对其执行一些操作,然后将其发送给 ElasticSearch。
我是 Kubernetes 的超级粉丝。在过去的几年里,容器化应用发生了巨大的转变,我完全接受这种转变。我对运行我在当地修建的这条管道没有兴趣,这是库本内特斯或半身像!
我查了一下谷歌,很难找到任何关于部署、最佳实践等方面的具体内容,因此我写了一篇关于如何在 Kubernetes 上运行基本 Filebeat、Logstash 和 ElasticSearch 管道的基本文章。
那么 Filebeat 是什么?它是一个作为代理运行的发送器,将日志数据转发给 ElasticSearch、Logstash 等。
假设您正在运行 Tomcat,Filebeat 将在同一台服务器上运行,读取 Tomcat 生成的日志,并将它们发送到一个目的地,这个目的地通常是 ElasticSearch 或 Logstash。FileBeat 还可以在 Kubernetes 上的 DaemonSet 中运行,将节点日志传送到 ElasticSearch 中,我认为这非常酷。Fluentd 也这样做,但这是另一天。
想了解更多关于 Filebeat 的信息,请点击这里。
对,在我们的场景中,我们让 Filebeat 读取某种类型的日志,并将其发送到 Logstash,但 was 是 Logstash 吗?
Logstash 是一个服务器端应用程序,它允许我们构建配置驱动的管道,可以同时从多个来源接收数据,对其进行转换,然后将其发送到您喜欢的目的地。
我们可以编写一个配置文件,其中包含从哪里获取数据、需要对数据执行什么操作(如过滤、grok、格式化)以及数据需要发送到哪里的指令。我们将这种配置与 Logstash 应用程序结合使用,这样我们就有了一个功能完整的管道。Logstash 的美妙之处在于,它可以使用各种资源,包括 RabbitMQ、Redis 和各种数据库,以及其他使用特殊插件的资源。然后我们可以把这些数据藏在 S3、HFDS 和更多地方!这一切都是由一个配置文件和一堆插件驱动的…很神奇吧!
想了解更多关于 Logstash 的信息,请点击这里。
让我们开始一些代码和令人兴奋的东西!
我在弹性网站上跟随 Logstash 教程,发现了我的管道的完美候选……做了一些小的修改。
我按照教程一步一步地操作,FileBeat 正在运行,它正在读取教程中提到的日志文件,一切都很好。我必须配置 FileBeat 配置文件filebeat.yml
以指向我所展示的 Kubernetes 节点端口,这将在稍后介绍,我还将提供的 FileBeat 日志移动到 Filebeat 应用程序文件夹中。
filebeat.inputs:- type: logenabled: truepaths:- logstash-tutorial.logoutput.logstash:hosts: ["localhost:30102"]
现在只需 Logstash 和 Kubernetes 进行配置。让我们看一下管道配置。
每一个配置文件被分成 3 个部分,输入,过滤和输出。它们是大多数 ETL 过程的 3 个阶段。
我们首先指定我们的数据来自哪里,在我们的例子中,我们使用 beats 插件并指定接收 Beats 的端口。
那么这个 Beats 插件是什么?它使 Logstash 能够从 Elastic Beats 框架中的应用程序接收事件。当我们运行 FileBeat 时,它在那个框架中,FileBeat 读取的日志行可以被我们的 Logstash 管道接收和读取。
接下来,我们指定过滤器。过滤器部分是可选的,如果你不想的话,你不必应用任何过滤器插件。如果是这种情况,数据将被发送到 Logstash,然后发送到目的地,没有格式化,过滤等。在我们的例子中,我们使用的是 Grok 插件。Grok 插件是比较酷的插件之一。它使您能够将非结构化的日志数据解析成结构化和可查询的数据。
Grok 在它接收的数据中寻找模式,所以我们必须配置它来识别我们感兴趣的模式。Grok 带有一些内置模式。在这种情况下,我们使用的模式是%{COMBINEDAPACHELOG}
,当 Logstash 从 Apache HTTP 接收日志数据时,可以使用这个模式。
最后,我们指定我们的输出。这是我们的数据过滤后的最终位置。如果您的数据需要发送到多个地方,您可以指定多个输出。在这个例子中,数据被输出到 ElasticSearch,但也打印到控制台,只是为了安全起见。在 ElasticSearch 块中,我们指定 ElasticSearch 集群 URL 和索引名称,索引名称是由元数据组成的模式组成的字符串。
现在,我们已经完成了管道的配置,我们可以转到 Kubernetes 了。
我们首先要做的是创建一个配置图。ConfigMap 允许我们存储可由 pod 访问的配置数据的键值对。因此,我们可以有一个配置映射,它可以存储一个目录,其中包含所有配置文件,也可以存储单个配置文件。
首先,我们创建一个配置图。我们将其命名为 apache-log-pipeline ,并引用前面的管道配置文件。
> kubectl create configmap apache-log-pipeline --from-file apache-log-es.conf
我们可以通过用 describe 运行 kubectl 来检查我们已经创建的 ConfigMap。
> kubectl describe cm/apache-log-pipeline
如果命令已经正确运行,您应该会看到apache-log-pipeline
的键和之前配置文件的值。如果是这样的话,你做得很好!
Name: apache-log-pipeline
Namespace: default
Labels: <none>
Annotations: <none>Data
====
apache-log-es.conf:
----
input {
beats {
port => "5044"
}
}
filter {
grok {
match => { "message" => "%{COMBINEDAPACHELOG}"}
}
}
output {
elasticsearch {
hosts => ["[http://elasticsearch:9200](http://elasticsearch:9200)"]
index => "%{[[@metadata](http://twitter.com/metadata)][beat]}-%{[[@metadata](http://twitter.com/metadata)][version]}-%{+YYYY.MM.dd}"
}
stdout {
codec => rubydebug
}
}Events: <none>
现在我们有了 ConfigMap,我们需要为我们的 Logstash 服务组装一个部署,并将apache-log-pipeline
引用为一个挂载的卷。
让我们看一下部署的一些部分。我们为容器指定了两个端口,5044 和 9600。端口号 5044 用于从 Elastic beats 框架接收 Beats,在我们的例子中是,端口号 9600 允许我们检索关于 Logstash 的运行时指标。更多关于那个的信息在这里。
我们还指定我们想要挂载config
卷,以及我们想要将它挂载到哪个路径/usr/share/logstash/pipeline
。我们将卷挂载到这个特定的目录中,因为默认情况下,Logstash 从这个目录中读取配置。这允许我们只运行命令logstash
,而不是指定配置文件所在位置的标志。
然后,我们有我们的卷,称为apache-log-pipeline-config
,它是一种类型的configMap
。有关卷的更多信息,请查看这里的。我们指定希望使用的配置图,apache-log-pipeline
是我们之前创建的配置图。因为我们的 ConfigMap 是由键值对组成的,所以我们添加了包含我们的管道配置的键,apache-log-es.conf
。
然后我们继续服务。这里唯一真正的讨论点是我们正在使用一个NodePort
。这有两个原因,FileBeat 需要与运行在 Kubernetes 中的 Logstash 对话,因此我们需要一个端口来完成这项工作,我已经将它指定为30102
,因为filebeat.yml
需要配置这个端口号,以便将 beats 发送到 Logstash。第二个原因,我想检查使用端口9600
的 Logstash 监控 API,如前所述。
因此,随着我们的部署和服务准备就绪,我们可以部署它。
> kubectl create -f apache-log-pipeline.yaml
如果 Pod 创建正确,您应该能够获得 Pod 并看到它运行。
> kubectl get pods================================================NAME READY STATUS RESTARTS AGE
apache-log-pipeline-5cbbc5b879-kbkmb 1/1 Running 0 56s
================================================
Pod 已正确创建,但它实际上已经启动并运行了吗?最快的方法是通过跟踪吊舱的日志。
> k logs -f pod/apache-log-pipeline-5cbbc5b879-kbkmb
如果管道运行正常,您应该看到的最后一行日志显示 Logstash API 已经成功创建。
[2019-01-20T11:12:03,409][INFO ][logstash.agent] Successfully started Logstash API endpoint {:port=>9600}
当我最初构建这个管道时,我遇到了两个错误。第一种情况是,由于格式原因,配置文件的格式不可读,在跟踪 Pod 日志时,错误被打印出来并可见。第二种情况是当 ConfigMap 没有正确安装时,管道将运行、停止然后重新启动,这再次被打印出来,并通过跟踪日志可见。
因此,我们在 Kubernetes 有一个功能齐全的 Logstash 管道。
但它实际上什么也没做。我们现在需要做的是运行 Filebeat。为了确保正确运行,我打开了两个终端窗口,一个跟踪 Pod 的日志,另一个跟踪我将要运行的 FileBeat 命令。
sudo ./filebeat -e -c filebeat.yml -d "publish" -strict.perms=false
当运行该命令时,Filebeat 将开始运行并读取在filebeat.yml
配置文件中指定的日志文件。本文开头提到的教程中讨论了其他标志。
在管道配置文件中,我们包含了stdout
插件,因此接收到的消息被打印到控制台。记住这一点,我们应该在终端窗口中看到输出的消息,跟踪 Pods 日志,并且您应该在运行 Filebeat 命令的窗口中看到类似的内容。
{
"[@timestamp](http://twitter.com/timestamp)" => 2019-01-20T11:35:36.042Z,
"request" => "/style2.css",
"prospector" => {
"type" => "log"
},
"response" => "200",
"httpversion" => "1.1",
"offset" => 18005,
"bytes" => "4877",
"tags" => [
[0] "beats_input_codec_plain_applied"
],
"timestamp" => "04/Jan/2015:05:24:57 +0000",
"clientip" => "81.220.24.207",
"referrer" => "\"[http://www.semicomplete.com/blog/geekery/ssl-latency.html\](http://www.semicomplete.com/blog/geekery/ssl-latency.html\)"",
"message" => "81.220.24.207 - - [04/Jan/2015:05:24:57 +0000] \"GET /style2.css HTTP/1.1\" 200 4877 \"[http://www.semicomplete.com/blog/geekery/ssl-latency.html\](http://www.semicomplete.com/blog/geekery/ssl-latency.html\)" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.73.11 (KHTML, like Gecko) Version/7.0.1 Safari/537.73.11\"",
"ident" => "-",
"agent" => "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.73.11 (KHTML, like Gecko) Version/7.0.1 Safari/537.73.11\"",
"beat" => {
"version" => "6.5.4",
"name" => "local",
"hostname" => "local"
},
"auth" => "-",
"[@version](http://twitter.com/version)" => "1",
"host" => {
"name" => "local"
},
"verb" => "GET",
"input" => {
"type" => "log"
},
"source" => "/filebeat-6.5.4-darwin-x86_64/logstash-tutorial.log"
}
如果我们看到了控制台中打印的消息,我们几乎可以保证消息已经被发送到 ElasticSearch 中。有两种方法可以检查这一点,用一些参数调用 ElasticSearch API 或者使用 Kibana。
我是基巴纳的超级粉丝,所以这是我们要走的路线。
启动基巴纳,前往探索区。
在我们的管道配置中,更具体地说是 ElasticSearch 输出,我们指定要创建的索引是一个由元数据组成的模式,其中包括 Filebeat 版本和日期。我们使用这个索引模式从 ElasticSearch 中检索数据。
在这个例子中,我定义的索引名为filebeat-6 . 5 . 4–2019 . 01 . 20,因为这是由 Logstash 创建的索引。
接下来,我们配置时间过滤器字段。当我们希望按时间过滤数据时,会用到该字段。有些日志会有多个时间字段,所以我们必须指定它。
一旦执行了这些步骤,我们应该能够查看日志。如果我们回到 Discover 部分,一旦我们定义了索引,日志应该是可见的。
如果可以看到日志,就拍拍自己的背吧!做得好,很努力!如果你想的话,可以轻拍一下庆祝一下:)
所以在我结束之前,我想说一些很酷的与基巴纳相关的事情。
我们可以从 Kibana 的监控部分监控我们的 Logstash 管道。我们可以深入了解事件发生率,例如发出和收到的事件。我们还可以获得节点信息,比如 CPU 利用率和 JVM 指标。
最后但同样重要的是 Kibana 的日志部分。我想这是我目前最喜欢的基巴纳区。它允许您近乎实时地查看流日志,并回顾历史日志。
要查看运行中的日志部分,进入 Filebeat 目录并运行sudo rm data/registry
,这将为我们的日志重置注册表。一旦完成了这些,我们可以再次开始 Filebeat up。
sudo ./filebeat -e -c filebeat.yml -d "publish" -strict.perms=false
如果你把运行 Filebeat 的终端放在装有 Kibana 的浏览器旁边,你会看到日志以近乎实时的方式流动,很酷吧?
如题,这是一篇基础文章。它指导你如何快速启动和工作,因此一定会有改进/改变,在所有方面都做得更好。
非常感谢一如既往地阅读我的文章,真的很感激。任何想法、评论或问题都可以给我发推特。
干杯👍🏻
手
https://twitter.com/danieljameskay
EDA 基础(带糖果)
image: Adobe Stock/biotin
探索性数据分析——什么是探索性数据分析,我该如何做?
目标是将数据转化为信息,将信息转化为洞察力
- 卡莉·菲奥莉娜,惠普公司前首席执行官
作为一名数据科学家,据说我们会把 80%左右的时间花在 EDA 上。因此,尽管看起来很乏味,但在开始有趣的事情之前掌握这个过程是个好主意。该过程包括清理、分类和检查特征相关性,以剔除噪声,并有希望获得一些关于哪些片段最有用的见解。
这个例子的数据集是来自科学创意季刊网站的 2017 年糖果等级。如果你想跟进,你可以在这里找到并下载 csv 。我在 Jupyter 笔记本里用的是 Python 3.7。
我在这里分享的一些步骤是个人喜好,你可以随意修改,但是,这些前几个步骤总是非常重要的。
导入:
- pandas——数据分析库,几乎所有数据操作都需要它
- numpy——对于线性代数函数来说,即使您并不期望需要它,也应该拥有它。
- Matplotlib —用于绘图和图形的可视化库
- Seaborn —实现更加可定制的数据可视化
读入您的数据:
使用 pandas pd.read_csv 函数,以字符串形式输入您的数据源,并将其保存为变量,该变量将成为您的数据帧的名称。很多人用 df ,我喜欢用一些更具描述性的东西,所以这次我会用 candy 。
接下来,您需要检查数据的前几行,以获得您将处理的数据的峰值。
candy.head( ) 是一个返回前 5 行数据的方法。如果你想要更多或更少,只需输入括号中的数字。例如,头(10)。
candy.shape 是一个属性,告诉你数据帧中的行数和列数。从上面可以看到, candy 有 2460 行 120 列。
在检查了数据字典后,我发现每一行都是被调查的个人。数据字典提供了关于数据集中提供什么信息的更多细节。如果可能的话,复习一下总是个好主意。
检查缺失值:
用 candy.isnull( ) 。这种方法有很多种。添加*。sum()* 到最后会给出每列的总 NaNs 和*。mean( )* 将告诉您每一列中缺失值的百分比。我喜欢补充。*sort _ values(ascending = False)*此外,按照从缺失最多到最少的顺序显示列。
此时,您需要检查数据字典,确定丢失的数据是否意味着什么?还是只是未知?决定是否可以用删除丢失的单元格。dropna( ) 或者是否应该用来代替它们。菲尔娜()。
下降列:
对于这个例子,我们可以看到[‘Unnamed: 113’]和[‘Click Coordinates (x,y)’]列没有信息,也没有用,所以我们可以使用。drop(columns =[’ insert column names '],inplace=True )。在此之前,我使用了*。value_counts(dropna=False)* 查看包括 nan 在内的那些列中包含的值,并确认其中没有重要的内容。
数据字典没有说明调查者可以跳过任何让他们不舒服的问题,所以我们可以假设一些缺失的数据就是因为这个原因。大多数问题都是要求糖果评级,从快乐到中性到绝望。如果你从来没有吃过糖果,就没有盒子可以选择。因此,我们可以做出的另一个假设是,这些列中的 nan 属于这一类。在这种情况下,它们可以用“未知”或您选择的字符串来填充。这真的没关系,因为为了让评级对衡量或预测某件事更有用,我们最终需要将它们转换成数字。
检查数据类型:
在填 NaNs 之前,我通常使用*。属性来查看每一列的数据类型,并寻找任何奇怪的东西。*
正如所料,大多数列都是对象类型,这意味着字符串。
Q3 是年龄,应为整数。
Q12 是唯一的浮点类型。它有四个答案,并被自动分成虚拟列,其中 1.0 是“是”,NaN 是“否”。因此我们知道可以用 0.0 填充这些答案。
填写 NaNs:
一旦我弄清楚了所有的类型,我喜欢创建一个 for 循环,它将同时处理我所有丢失的值。我用 0 填充任何整数或浮点数列,用字符串’ unknown '填充任何对象列。然后检查是否还有空值。
重命名列:
接下来,我想将列名修改为小写,并且不包含特殊字符。这通常使它们更容易阅读和理解。可以使用*。columns* 属性来检查列的当前名称。然后我用进行列表理解。lower( ) 和 .str.replace( ) 进行我想要的修改。这些名字需要更多的工作,但这只是让你知道如何开始。
如果有更少的列,并且您想完全重写名称,您可以使用 dictionary 方法。
最后,我开始使用*。value_counts( )* 来更深入地挖掘和探索它们的用处。在这一部分,我可能会将评级改为数值,并将性别和外出等特征二进制化,如果这些特征对我的问题陈述似乎很重要的话。
在上面的剪辑中,我创建了一个地图,其中所有欢乐的细胞将变为 3,所有 MEH 细胞将变为 2,绝望变为 1。然后我用了*。对 lambda 函数执行 applymap( )* 操作,该函数基本上循环遍历数据帧的单元格,如果它找到了映射中列出的值,它将使用指定的替换值替换它,否则如果它不在映射中,它将保持不变。
如果我只想看看 Q6,关于人们的糖果评级,可以设置这些行等于一个新的变量,如 candy_only 。
candy.describe( ) 将向我们显示描述性统计数据,如平均值、标准差和最小/最大值。
由于我们没有目标变量,也就是我们试图预测的东西,我将使用这些数据来计算出最受欢迎的 10 种糖果。
首先,我用找到了每一列的平均值。mean( ) ,然后我把值从大到小排序,只显示前 10 名。最后我用*。plot(kind= “bar”)* 绘制条形图。此外,任何排在第一位的全尺寸糖果,人们最喜欢 Kit Kats,Reeses 和 cash!
photo: amazon
博弈论的基础
常用术语和视觉布局介绍
Originally Published On https://www.setzeus.com/
现代博弈论,诺依曼&纳什建立的应用数学分支,是研究智能、理性、决策者之间冲突&合作的数学模型。一个广泛应用于从经济学、政治学到计算机科学的工业&领域的工具——博弈论的基础对普通高中生来说惊人地成立。这些概念并不 太过 高级,但是潜在的知识绝对会有回报。理解博弈论的基本原理对于任何处于群体决策地位的人(提示:每个人)来说都是值得努力的。
上一次我们讨论了早期的现代化,这一次我们将回顾常用的术语,游戏图解的基础。
常用术语
大多数数学分支,尤其是应用数学分支,都需要它们的词汇——博弈论在这里也不例外。要理解示例,熟悉适当的语言至关重要。下面我们会找到基本词汇;这些术语不是按照字母顺序排列的,而是按照与整个主题的相关性递减的主观顺序排列的:
游戏— 两个或更多玩家之间的任何互动,其中每个玩家的收益都受到他们的决策&其他人做出的决策的影响。
玩家— 游戏中相互依赖的主体,可能包括个人、政府、公司等
动作— 玩家必须选择的严格定义的行为,玩家*能做什么?*输入投标?结束罢工?赌掷硬币?
回报 — 在一个价值体系中,映射到玩家行为的“价值”的具体、精确、增加或减少。
价值体系— 抽象&上下文相关,这是所有可能值的范围;从坐牢,到市场份额,到土地占用,什么都有。
**零和——**一个玩家的收益等于另一个玩家的损失的情况;整个游戏的财富或收益的净变化为零。
非零和— 基于博弈**的结果,系统存在净 收益或损失 的情况;**所有玩家的赢款&损失和加起来不为零。
**同时——**玩家做决定&采取行动几乎同时;他们在做选择的时候不知道其他玩家的选择,比如石头剪子布。
顺序— 玩家做决定时&轮流采取行动,如大富翁或国际象棋。
非合作 —更常见的游戏类型,这是一种个体玩家之间严格竞争的游戏。
纳什均衡— 博弈的最优结果,其中没有玩家有动机偏离所选策略;假设其他参与者的策略保持不变,改变行动不会带来增量收益。
优势策略 — 不管其他玩家做什么,在所有策略中最有利的策略。
合作 —一种玩家可以结成联盟的游戏&合作应对外部可信威胁。
Shapely Value — 玩家的价值在所有可能的联盟中的平均边际贡献;用于合作/联盟游戏,它是每个玩家的平均预期边际贡献。
完整信息— 一种游戏,其中所有参与者都可以获得关于其他玩家的知识;玩家的收益函数、策略&“类型”是常识。
不完全信息— 一个游戏,玩家可能知道也可能不知道游戏类型、玩家行动、玩家类型、策略、收益的信息…
不完全信息— 玩家不知道其他玩家选择的动作的游戏;然而,其他的一切,玩家类型,策略,收益等等…都是常识。
菲尼托。上面的术语列表涵盖了我们可能会遇到的任何博弈论问题的基本要点。正如你将在下面学到的,除了这些文字描述,分析一款游戏的最好方法是通过可视化地描绘出来。
视觉布局
在博弈论中有两种主要的可视化游戏的方法:矩阵&树。总的来说,这两种模型都是合适的&应该会产生一些洞察力;然而,哪种模型最适合任何给定的场景取决于游戏类型及其规则(同时与连续,完全与不完全,等等)。
矩阵
博弈论最基本的工具是收益矩阵。通常,矩阵被用来描述双人同时进行的游戏。在下面的模板中可以看到,两个玩家的选择在我们矩阵的外部边界上相互垂直排列——一个横跨顶部(从左到右),&一个横跨左侧(从上到下)。里面是假设每个玩家采取了那个行动的预期收益。例如,对于一些非常简单的游戏,让我们假设有两个玩家(Alice & Bob)存在,每个玩家只能选择两个动作中的一个(因此总共有四个场景)。矩阵中的每个“场景”或方框包含两个数字,即特定场景的收益:
Originally Published On https://www.setzeus.com/
上面的例子是没有上下文的,但是仍然服务于演示博弈论矩阵的基本布局的目的。从粗体的外部标题可以明显看出,这个游戏是在爱丽丝和鲍勃之间进行的;此外,很明显他们每个人只有两个选择:“行动 1”或“行动 2”最后,矩阵中的有序对表示给定特定场景下两个玩家的收益(Alice 的收益是左/x 数字,Bob 的收益是右/Y 数字)。
让我们考虑一个更相关的游戏:石头剪刀布。一款全球知名的普通游戏,严格来说是一款双人同时非合作游戏。众所周知,玩家只有三种策略中的一种。此外,我们还从这三种策略中了解了收益&价值体系。我们可以用一个 3x3 的矩阵对这个游戏进行数学映射:
Originally Published On https://www.setzeus.com/
上面的例子应该更直观。沿着一行(Alice 的选择)和一列(Bob 的选择)走下去,两个人的收益就很清楚了。不过请注意,通过用矩阵表示游戏,我们只能用表示玩家同时行动的情况——没有时间的概念。
树
博弈树是一个带有节点&边的有向图。节点代表玩家的位置或从游戏中退出/收益。通常情况下,玩家被分配到决策节点;一组决策节点,当在同一水平 x 轴上时,代表单个玩家在单个时间点的阵列选择 。另一方面, 边代表移动或动作, 由玩家/节点采取 。一个游戏的完整游戏树是从初始位置&开始的游戏树,包含从每个位置 所有 可能的移动。
有了树,我们基本上给表增加了一个时间维度,允许随时间的顺序游戏的最佳表示。游戏树的拓扑在两种可能的设置中有所不同,这取决于游戏是同时进行的还是顺序进行的。为了从有意义的比较中获得洞察力,我们将通过继续我们先前的石头剪子布的例子来回顾树:
Originally Published On https://www.setzeus.com/
请注意,Alice 是随机选择的顶部节点/原点,但是,如果我们简单地交换她和 Bob 的位置,整个图将保持不变。和上面的矩阵例子一样,这个图提供了相同的收益选项。
对于每一棵博弈树,只有一种方法用矩阵来表示;但是对于每个矩阵,有多种方法可以用博弈树来表示。考虑到时间因素,除非一个游戏真的很简单,博弈树通常是分析的首选工具。
结论
我们走吧!随着通用语言和视觉映射现在在我们的腰带之下,除了它的历史,我们自学的游戏理论知识正在稳步增长。我们已经发展了对游戏的看法,这很令人兴奋,因为它让我们能够以完全不同的视角看待日常场景。
虽然这一次我们专注于构建我们的游戏,但下一次,我们将最终进入实质——学习博弈论的真正回报:分析&得出优势策略。
最初发表于:
Python 列表索引和切片的基础
初学者指南,由初学者编写
Photo by Igor Miske on Unsplash
几天前,我正准备向一群 Python 初学者演示索引和切片列表,我被一些看起来很基本的用例卡住了。所以,为了摆脱困境,我四处逛了逛,觉得值得分享一下我学到的东西。
访问列表中的条目(以及其他可条目中的条目,如元组和字符串)是 Python 程序员的一项基本技能,许多 Python 工具都遵循类似的索引和切片约定(例如numpy Arrays
和pandas DataFrames
)。所以熟悉一下来龙去脉还是值得的。
定义和舞台设置
“索引”是指通过元素在可迭代对象中的位置来引用可迭代对象的元素。“切片”意味着根据索引从 iterable 中获取元素的子集。
打个比方,我最近被召集去做陪审员,他们给每个潜在的陪审员分配了一个号码。你可能会说我的陪审员号是我的索引。当他们说,“42 号陪审员;请站起来,”我知道他们在和我说话。当他们说,“37 号到 48 号陪审员,请在下午 3:30 回来报到,”那一大群人中的片集体叹了口气,去吃午饭了。
让我们首先创建一个列表,我们可以使用列表理解来处理它:
my_list = [_ for _ in 'abcdefghi']
my_list['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
索引
为了检索列表中的元素,我们使用了索引操作符 ( []
):
my_list[0]'a'
列表是“零索引”的,所以[0]
返回列表中的第零个(即最左边的),而[1]
返回第一个(即第零个右边的一个)。由于我们的列表中有 9 个元素([0]
到[8]
),尝试访问my_list[9]
会抛出一个IndexError: list index out of range
,因为它实际上是在尝试获取第 10 个元素,而实际上并没有。
Python 还允许使用负数从列表的结尾开始索引,其中[-1]
返回最后一个元素。这非常有用,因为这意味着您不必通过编程来找出 iterable 的长度,以便处理它末尾的元素。my_list
的索引和反向索引如下:
0 1 2 3 4 5 6 7 8
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
-9 -8 -7 -6 -5 -4 -3 -2 -1
限幅
一个片是列表元素的子集。在列表的情况下,单个切片总是由连续的元素组成。切片符号采用以下形式
my_list[*start*:*stop*]
其中*start*
是要包含的第一个元素的索引,*stop*
是要在处停止但不包含在切片中的项目的索引。于是my_list[1:5]
返回['b', 'c', 'd', 'e']
:
0 ***1 2 3 4 5*** 6 7 8
× **↓ ↓ ↓ ↓ ** × × × ×
['a', ***'b', 'c', 'd', 'e'*,** 'f', 'g', 'h', 'i']
将任一切片边界留空意味着从列表的末尾开始(或转到列表的末尾)。例如:
my_list[5:]['f', 'g', 'h', 'i'] my_list[:4]['a', 'b', 'c', 'd']
使用负索引器将设置相对于它们从列表的端的位置的开始/停止界限,因此my_list[-5:-2]
返回['e', 'f', 'g']
:
['a', 'b', 'c', 'd', ***'e', 'f', 'g',*** 'h', 'i']
× × × × **↑ ↑ ↑** × ×
-9 -8 -7 -6 ***-5 -4 -3 -2*** -1
注意,如果你尝试my_list[-2:-5]
,你会得到一个空列表。这是我犯的一个错误,但是事情是这样的:为了包含在切片中,一个元素必须在*start*
边界的右边和*stop*
边界的左边。因为-2
已经在-5
的右侧,所以切片器在将任何值填充到切片中之前停止。
一个for
循环的工作方式完全相同;下面的第一个循环没有输出,但第二个有输出:
for i in range(-2,-5):
print(i)for i in range(-5,-2):
print(i)-5
-4
-3
步进
切片器可以接受可选的第三个参数,该参数设置切片中包含元素的间隔。所以my_list[::2]
返回['a', 'c', 'e', 'g', 'i']
:
***0 1 2 3 4 5 6 7 8*** **↓** ×¹ **↓**²×¹ **↓**² ×¹ **↓**² ×¹ **↓**²
[***'a',*** 'b', ***'c',*** 'd', ***'e',*** 'f', ***'g',*** 'h', ***'i'***]
并且my_list[1::2]
返回['b', 'd', 'f', 'h']
:
0 ***1 2 3 4 5 6 7 8*** × **↓** ×¹ **↓**²×¹ **↓**² ×¹ **↓**² ×¹
['a', ***'b'***, 'c', ***'d'***, 'e', ***'f'***, 'g', ***'h'***, 'i']
负步长值反转切片器遍历原始列表的方向:
my_list[::-1]['i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a']
列表元素的索引位置不会改变,但是元素返回的顺序会改变。*start*
和*stop*
边界的意义也是相反的,所以*start*
值应该在切片的最右边,而*stop*
值应该在它的左边。所以my_list[5:3:-1]
给了我们[‘f’, ‘e’]
:
<----<----<----<----<----<----<----<----<--
0 1 2 ***3 4 5*** 6 7 8
× × × × **↓ ↓** × × ×
['a', 'b', 'c', 'd', ***'e', 'f'***, 'g', 'h', 'i']
同样,my_list[-2:-5:-1]
给了我们['h', 'g', 'f']
:
['a', 'b', 'c', 'd', 'e', ***'f', 'g', 'h',*** 'i']
× × × × × **↑** **↑** ** ↑ ** ↑
-9 -8 -7 -6 ***-5 -4 -3 -2*** -1
<----<----<----<----<----<----<----<----<--
而my_list[-1:-8:-3]
给了我们['i', 'f', 'c']
:
['a', 'b', ***'c',*** 'd', 'e', ***'f',*** 'g', 'h', ***'i'***]
× ×¹ **↑**³ ײ ×¹ **↑**³ ײ ×¹ ↑
-9 ***-8 -7 -6 -5 -4 -3 -2 -1***
<----<----<----<----<----<----<----<----<--
我希望这能为您省去一些我在索引和切片工作中遇到的困惑,尤其是对于负步骤。
基础知识:支持向量机
从头开始的数据科学
将线性方法应用于非线性边界
支持向量机是一种用于分类的机器学习模型,自 20 世纪 90 年代广泛引入以来,已被证明非常受欢迎。有些令人困惑的是,“支持向量机”和“支持向量分类器”这两个名称经常互换使用,指的是一些不同实现的算法,但在本质上,这些方法都在做同一种事情,即找到根据类别将数据分成离散区域的方法。事实证明它们非常强大。支持向量机模型的技术实现可能相当复杂,但是一些直觉应该是清楚的,不要在技术细节上陷入太多。
最简单的版本:最大间隔分类器
考虑一个二维的简单分类问题:
A simple classification question with two classes
我们可以直观地看到,橙色和蓝色这两个类别似乎占据了图形中不同的区域,橙色在左下方,蓝色在右上方。明智的首要任务是使我们对橙色和蓝色阶级各自的“区域”的理解正式化。一旦我们在它们之间有了一个清晰的边界,我们就可以根据一个新点的位置来快速预测它将属于哪一类。让我们从最简单的边界开始,一条直线。同样,从视觉上看,我们可以用两个类之间的一条线来分割图形,但是我们应该选择哪条线呢?考虑两种可能的选择:
Two possible boundary lines
上面的红线和黑线都将两个点群分开,但它们代表不同的决策边界。如果每条线代表不同的模型,则位于线之间的一些点将根据所使用的模型进行不同的分类。
The green point will be classified differently by each of the models represented here
我们应该使用哪个决策边界?一种策略可能是寻找尽可能远离训练数据点的决策边界。这里的概念是,如果决策边界线非常接近训练点,则新的边际案例更有可能越过决策边界,并在错误的一侧结束。所以我们要寻找一条线,给训练点尽可能宽的泊位。一种测量方法是查看“最小余量”,即直线和所有训练点中最近的点之间的距离。这是我们的数据,其中一条线和最近的点/边缘突出显示:
One possible line and its margin
线条周围突出显示部分的宽度代表我们的最小边距。理想情况下,我们希望这条线尽可能的粗,以找到一条给我们的点簇最大可能的泊位的线。有一个清晰的代数任务(找到具有最大可能最小边距的线)和一个我们可以遵循的漂亮的视觉解释。例如,这是新的一行:
Another option
这一次似乎给了我们更大的利润空间。我们可以同时绘制它们,以便更清楚地看到这一点:
Both lines together
这些例子是二维的,以使它们易于可视化,但同样的策略可以在任何数量的维度上使用。例如,如果有三个维度,你会寻找一个平面来分隔空间,在点和平面之间有尽可能多的空间。在更多的维度中,你会寻找一个分离的“超平面”,我们可能无法想象,但概念上不会更复杂。
显而易见,尽管在某些情况下可能是有用的,但这种性质的“最大最小间隔”分类器具有严重的缺点。首先,如果我们的集群没有像这个可能有点做作的例子中那样清晰地分开,会怎么样呢?在现实世界中,数据通常是嘈杂的,即使聚类看起来很明显,它们也可能在边缘重叠。我们如何处理像这样的数据:
Slightly overlapping clusters
下一步:支持向量分类器
允许我们继续使用易于描述的线性决策边界的一个简单修复方法是,使边界有点模糊,引入一种算法,允许一些点在确定的边界内,甚至在分界线的另一侧。因为在这种实现中,边缘不再严格,所以支持向量分类器有时被称为“软边缘”分类器。允许模型对训练数据中的点进行错误分类的程度由调整参数控制。回到我们的第一个例子,将这个调整参数的各种级别应用到一个软边界分类器说明了正在发生的事情。当不考虑错误分类点时,得到的模型看起来与我们的最大最小利润模型非常相似:
The first example with a SVC
决策边界毕竟还是一条线。本例中的关键区别在于,模型识别了三个最近点,而不是一个。这三个点是定义边缘和边界的“支持向量”。它们被称为“support ”,因为改变其中一个必然会改变模型找到的边距和边界。如果您让模型对落在边界内的点更加宽容,那么它所识别的边界将会增长,并且必然会包含更多的支持向量:
A soft margin classifier with an even softer margin
这种软边际模型允许我们处理不干净和有一点重叠的数据簇。例如,在我们之前的稍微重叠的示例中,这是两个组之间的合理线性边界:
Slightly overlapping datasets handled!
这极大地扩展了我们的边缘分类器的效用,尽管仍有失败的例子。特别是,一些决策边界可能不是线性的,我们的模型是否会对这些边界做出合理的反应还不清楚。例如,考虑以下几点:
Uh oh, non-linear bondaries
您可能很快就能看出这个示例中发生了什么,但是我们的软边界分类器很混乱,因为边界不是线性的,并且会产生一些无用的东西,如果不是完全无意义的话:
This soft margin classifier is useless!
不知何故,我们需要发挥非线性边界的作用。
解决方案:支持向量机
支持向量机通过引入“核函数”将我们到目前为止一直在处理的软间隔分类器向前推进了一步。在这种情况下,核函数将某组输入变量映射到更高维度的空间。有时你可能会看到这种被称为“核技巧”的函数的使用——技巧是有时不能被线性边界分开的点可以通过核函数映射到更高维度,其中变换的点可以被线性边界分开!
例如,不是简单地考虑 x-y 平面上的点,我们可以将它们映射到三维空间中,添加一个 z 轴,并将任何给定点的 z 坐标定义为 x 的平方加上 y 的平方。你可能会注意到这种变换与圆的代数公式之间的相似之处——事实上,这种变换可以让我们轻松地将距离原点较近或聚集在原点周围的点与距离较远的点分开,一旦这些点从二维空间映射到三维空间,就会有一个单一的线性平面穿过这些点。在实践中,支持向量机做的事情稍微复杂一点,因为它对特征向量的所有组合(即所有点)执行这种转换,但概念是相同的。
有几种常见的内核可以应用。例如,径向基函数 内核采用刚才描述的类似方法,处理特征向量之间的平方欧几里德距离。使用此内核,我们可以创建决策边界,将彼此靠近的点组合在一起,即使一个类的点聚集在另一个类的点内,如我们前面的示例所示:
The rbf kernel saves the day
多项式内核将输入向量及其组合的特征进行比较,以达到给定的程度,允许我们创建决策边界,更好地拥抱点簇之间的曲线。考虑我们的第一个完全线性的软边界解决方案,解决点簇重叠的情况:
A boring, straight decision boundary
对于这个解决方案,使用 3 次多项式核的支持向量机:
An attractive, curved boundary
使用这些核技巧,支持向量机模型可以非常有效地分类复杂的非线性边界。
贝叶斯大脑假说
我们的大脑是如何进化来预见未来的
如果你能看透时间的种子,并说出哪些谷粒会生长,哪些不会…
麦克白,威廉·莎士比亚
人生充满了不确定性,谁也说不清未来。根据布莱士·帕斯卡的说法,我们在一个巨大的球体中航行,永远在不确定性中漂流,被从一端驱动到另一端。没有人知道死亡什么时候会来,什么时候生活会给我们带来苦难,什么时候生活会给我们回报。
虽然我们都必须在生命中的某个时刻学习这一沉闷的课程,但我们仍然令人钦佩地成功地在一个充满不确定性的宇宙中获胜。我们盖房子,我们把钱存入银行账户,我们为退休基金和我们的孙辈存钱。我们塑造稳定的关系,建造不朽的丰碑。我们对正在发生的事情有一种控制感,这是我们应得的。
对于像我们这样的人来说,这是非常了不起的,我们是从进化的随机和混乱的幻想中诞生的。我们是如何从不确定性定义的未来中发展出一种确定感的能力的?
贝叶斯大脑假说 认为我们的行为背后有一个深藏的结构,其根源可以追溯到生命的本质。它指出,在某种程度上,大脑除了预测未来和实现这种期望的未来之外,几乎没有什么别的作用,大脑与生命系统的法则一致,总是与大自然为它们准备的惊喜进行艰苦的斗争。
Photo by Ramón Salinero on Unsplash
体内平衡的必要性
体内平衡是所有生命背后的根本原则。它源自拉丁语 homeo (相等)和 stasis (静止不动),由沃尔特·布雷德福·坎农于 1926 年创造。体内平衡象征着维持生命系统内的物理和化学过程,使生命系统保持完整,并阻止它们消失(我在关于生命起源的文章中对此做了更详细的介绍)。这是自我组织的原则,可以抵御自然的无序趋势。
Antonio Damasio 在他的书《事物的奇怪顺序》 中指出,这个术语具有误导性,因为体内平衡不仅仅意味着静止。生命是一个自我实现的原则,它不仅仅满足于在当下维持它的功能。如果你有两个相互竞争的有机体,其中一个满足于现有的生活,而另一个经过优化可以在未来的岁月里过得很好,哪一个将有更大的机会存活数百万甚至数十亿年?我们在当今世界发现的生命总是 含蓄地将自己推向遥远的未来 ,因为在过去,它进化出的特性会激励它继续将自己推向未来。
保持车轮滚动,保持呼吸,向前推进。
Priestess of Delphi, by John Collier [Public domain]
预测未来
人们总是试图预测和改变未来。在古代,算命是牧师和巫师的一项复杂技艺。这方面最著名的例子可能是德尔斐神谕,数百年来,希腊政治家和罗马皇帝都参考这一神谕。古代政治是一个充满不确定性的时代,我们不应该对人们希望减少不确定性的愿望过于武断。
但是从更现代的科学角度来看,我们已经认识到,在恍惚状态下吸入有毒气体和说谜语不太可能给我们任何关于世界运作的真正见解。为了减少对未来的不确定性,我们(以及我们的大脑)需要采取一种更普通的方法,试图根据我们对世界的已知尽可能好地预测未来。根据我对当今世界的观察,我可以期待明天会发生什么,我应该在什么意义上指导我的行动,以获得对我的生存最有利的结果?
贝叶斯定理
18 世纪虔诚的托马斯·贝叶斯提出了一个简洁的小定理,这个定理在他生前没有发表,但后来证明在许多领域都非常有用。这真的很简单,但这并不妨碍贝叶斯的名字成为现代认知科学中最热门的理论之一。
这里我们可以看到它的蓝色发光版本:
Bayes’ theorem. Credit to mattbuck (category) [CC BY-SA 2.0]
贝叶斯定理指出,给定 B 的概率等于给定 A 的 B 的概率乘以 A 的概率,再除以 B 的概率。
当我们已经知道其他相关事情发生的概率时,它给出了某些事情发生的概率。
你可能已经猜到了为什么这在预测未来时会派上用场。
应用贝叶斯定理的一个流行的例子是看天气,这是不确定性和对自然残酷的沮丧的永恒来源。
Photo by Elliott Engelmann on Unsplash
比方说,你去散步,但由于某种原因在干旱和无尽的沙漠中迷路了。你打算在公园里散步,所以你只带了一小瓶水喝。迷路三天后,你自然会很渴。你在早晨扫描天空寻找一片云,你瞧,在地平线上你看到一小片云。
下雨的概率有多大,你不死于脱水的概率有多大?
我们正在寻找概率 P(雨|云),因此对于给定你观察到云的情况下下雨的条件概率。我们需要:
- P(云|雨):如果某一天下雨,这一天是以天空中的云开始的吗?假设沙漠中 80%的雨天一开始都是多云天气。这意味着很有可能一开始就有云,以防那天下雨。
- 在某一天,沙漠中出现云的几率非常小:只有 10%。
- 下雨的可能性就更小了。沙漠里每隔一百天才会下雨,所以概率在 1%。
假设你观察到一朵云,那么下雨的总概率为:
P(雨|云)=P(云|雨)P(雨)/P(云)=0.80.01/0.10=0.08
看到云之后,你可以说有大约 8%的可能性会下雨。小小的安慰,但总比没有好。
重要的是要注意,在计算你感兴趣的条件概率时,其他三个概率都是必不可少的。放弃一个会显著改变你想要的结果。
假阳性
贝叶斯定理帮助我们纠正假阳性,例如,当我们假设一个事件可以提供关于一个结果的信息,而这个结果本身是不太可能的。一个著名的例子是癌症测试(或任何其他罕见疾病)。**
假设只有 0.1%的人口患有某种癌症。你的医生告诉你一种新的、改进的癌症测试,如果病人真的患有癌症,这种测试可以在 90%的情况下检测出癌症。缺点是,当癌症并不真正存在时,它也在大约 9%的测量中检测到癌症。
你是一个天生焦虑的人,所以你想通过考试来缓解你的焦虑。你有一个积极的结果。你真的害怕了一秒钟,因为毕竟,你患癌症的几率是 90%,不是吗?
不,因为你可以快速应用贝叶斯定理计算出你患癌症的真实几率。请注意,在这种情况下,您需要划分出真阳性的概率和假阳性的几率:
***P(癌|阳性结果)=P(阳性结果|癌)p(癌)/(p(阳性结果)p(癌)+p(假阳性)p(未患癌))= 9.17%
所以,你不应该太担心(考虑到你一开始是出于焦虑而参加测试,这将是很困难的),因为患癌症的可能性很小,假阳性的几率比得到阳性结果并实际患有癌症的几率大约高十倍。****
关于独角兽的一个注记
This is very probably not a real unicorn. Photo by Andrea Tummons on Unsplash
因此,对于任何对预测未来感兴趣的人来说,很好地了解事件发生的先验概率是有帮助的。
为了判断一个事件(例如,看到云或阳性癌症测试)对预测另一个事件(例如,下雨或患癌症)的信息量有多大,当我们观察云或癌症测试时,我们需要对下雨或癌症的总体概率进行表示。
由于大脑在对感官收集的关于外部世界的信息进行分类时不断判断概率,你可能会开始猜测为什么它的运行方式可能有一些 贝叶斯 。
假设你看到一只四条腿的动物沿着地平线飞奔的模糊轮廓。看起来像有一个又长又尖的物体附着在动物的前额上。
你的大脑会自动跳到结论是独角兽吗?
**如果你没有疯,可能不会,因为在给定你正在观察的形状的情况下,观察到独角兽的概率 **P(独角兽|形状)**必须由观察到独角兽的先验概率 **P(独角兽)在我们的宇宙中很可能为零这一事实来加权。
世界的内部模型
如果大脑想要模拟世界的行为,特别是未来的行为,大脑需要有一个世界是什么样子的内部模型,以便了解世界可能会变成什么样子。
大脑需要能够在接收到关于世界状态的新信息(例如,通过接收新样本)后更新世界的内部模型。假设你经常在上班的路上看到独角兽。多久你会开始怀疑你关于没有独角兽的假设是否仍然成立?或者说,50 个癌症检测呈阳性的人中有 20 个实际上患有癌症。对于所有阳性结果中只有 9.17%意味着患者实际上患有癌症的估计,您有多大的信心?
以统计最优的方式基于新信息更新内部模型的概率分布称为 贝叶斯推理 。
我们经常观察到大脑在行为实验中进行这种推理,或者在将感官输入相互关联时进行这种推理:已经表明,在巴甫洛夫关联刺激实验中,不同刺激之间的相互信息得到了最佳解释(参见第 16 页此处的概述)。另一个很好的例子是布里顿等人 1992 年著名的视觉运动研究。al ,表明当试图根据对刺激的神经反应/放电率解码视觉运动的一致性时,猴脑设法接近贝叶斯最佳解码率。
原来,大脑以可预测的方式进行预测。
贝叶斯大脑假说
现在我们准备更深入地研究 贝叶斯大脑假说 实际需要什么。
贝叶斯大脑 存在于外部世界,并被赋予了这个外部世界的内在表征。这两者由所谓的 马尔科夫毯 相互隔开。
大脑试图根据世界的生成模型来推断其感觉的原因。为了成功地模拟外部世界,它必须能够以某种方式模拟外部世界正在发生的事情。用 卡尔·弗里斯顿 (此处摘自)的话说:
如果大脑正在对其感觉的原因进行推理,那么它必须有一个导致感觉输入的世界的
(隐藏的)状态之间的因果关系(联系)的模型。由此可见,
神经元连接编码(模拟)了共同产生感觉信息的因果连接。
这是理解 贝叶斯大脑假说 的第一个关键点。这是一个深刻的观点:大脑内部世界的内部模型表明,大脑中的 过程是物理世界中的 过程。为了成功预测未来,大脑需要在自己的硬件上运行对世界的模拟。这些过程需要遵循与外部世界相似的因果关系,在观察它的大脑中,它自己的世界变得活跃起来。
第二点涉及回贝叶斯推理:它需要大脑在某种意义上是最优的/优化的 ,就像我们对自然的期望一样。
正如我前面指出的,当对感知内容进行分类并在不确定性下做出决策时,贝叶斯大脑在近似贝叶斯最优水平上工作,这意味着它在推断世界的未来(隐藏)状态时,尽可能地考虑所有可用信息和所有概率约束。
你可以给被优化的量取几个名字,但是通常情况下,在深入统一的理论中,不同的观点优化不同的事物,最终得到相同的量。一种看待方式是作为 证据 ,这在信息论中等同于最大化感知数据和世界内部模型之间的。**
自由能
在我关于自由意志热力学的文章中,我更详细地研究了卡尔·弗里斯顿的主动推理理论。
自由能被最小化,以优化模型的证据或边际可能性,弗里斯顿将其等同于最小化被称为模型 的 惊喜的东西(因此最小化体验不符合你的世界模型的东西)。
该理论进一步在大脑 等生命系统的行为中融入了一个主动成分,让系统在世界中执行动作 。你不仅可以梦想未来,还可以积极地改变未来,改变世界,让你的期望成真。
主动推理被 AI 缩写并非巧合,因为 Karl Friston 在这里,他认为“在 5 到 10 年内,大多数机器学习将纳入自由能最小化”。
这就把我们带回了达玛西欧的 稳态批判:
生命系统不是一成不变的,它们在世界上的行为是为了最小化惊奇,并在不确定性塑造的未来中持续存在。
最小化自由能与熵相关,因为对惊喜的时间平均给了我们一个熵的度量。这具有深刻的物理后果,因为正如弗里斯顿所说:
这意味着试图最大化其
证据的贝叶斯大脑隐含地试图最小化其熵。换句话说,它
抵制热力学第二定律,并在面对
无序的自然趋势时,为自我组织提供了原则性的
解释。
因此,贝叶斯大脑假说 是一个理论的根本范畴。它将大脑的行为与 稳态命令 联系在一起,与生命在一个宁愿消散的世界中的生存斗争联系在一起。
我们如何观察贝叶斯大脑?
提出大范围理论是一回事。从大脑的行为方式中寻找证据是另一回事。如果大脑表现为贝叶斯大脑,我们需要进一步了解大脑实际上是如何实现贝叶斯推理的。
贝叶斯推理被认为发生在许多认知层面上,从运动控制到注意力和工作记忆。每一项认知任务都有自己的预测、内部模型和独特的时间尺度。新的研究表明,执行贝叶斯推理的结构可以在神经成像数据中观察到,并且“神经元活动的时间
序列与理论预测的计算顺序相匹配”。
实现它的一个有希望的尝试被称为 预测编码 ,它做的正是贝叶斯大脑应该做的事情:它的算法通过改变它们预测的参数来为未来做准备,以便在再次面临相同情况时将惊讶降至最低。这方面的证据已经在所谓的 N400 效应的单词预测实验中被发现(参见这里的这里的或这里的)
认知科学正在认识到,大脑不仅仅是一个被动接受世界信息并做出反应的探测器。它通过对世界现状和未来做出假设,以一种 自上而下 的方式不断塑造其对世界的愿景(这意味着高阶概念首先塑造了低阶感官数据的感知方式,如独角兽的例子中所述)。这使得研究人员采用了现实的奇妙概念,即 控制的幻觉 (更多信息见安尼尔·塞思的 Ted 演讲),例如,先证者在事先阅读了“kick”后,产生了听到“kick”而不是“pick”的幻觉,如本研究中的所示。
以可预测的方式幻想现实给了我们决定性的进化优势,这是我们希望在纷乱复杂的世界中找到结构时迫切需要的。
科学家们仍在激烈地争论该理论的有效性,以及大脑如何在功能层面上实际整合贝叶斯推理这一大问题。在我们做出任何明确的断言之前,还需要进一步的研究。但是我认为理论的美丽和我们目前所看到的证据可以让我们充满希望,我们正走在一条好的轨道上。
我们越来越接近于破解宇宙中最神秘的物体的秘密,这个物体允许我们观察世界并在其中导航,它使我们生老病死并希望有一个更好的未来(你可能已经有了我将如何结束这句话的内在表示):我们自己的贝叶斯大脑。
BEA 2019 共享任务:语法错误纠正的技术、调整和技巧
最近的 BEA 语法错误纠正共享任务共有 24 个团队参与,他们提出了许多有趣的解决问题的方法——其中许多都取得了令人印象深刻的结果!使用了各种各样的技术、调整和技巧,为了呈现一个更容易理解的概述,我列出了我的主要收获。
纵观提交的材料,有两个总体趋势很明显:
- 神经机器翻译方法占主导地位。以前基于规则、分类器和统计机器翻译的最先进的方法,现在已经远远落在后面了。
- 在大量人工生成的错误示例或弱监督数据(例如,来自维基百科修订历史)上训练系统已经成为标准做法。
在描述更详细的组件时,我将它们分为三个部分:
- 模型:模型的架构和解码技术
- 数据:用于训练模型的(人工)数据
- 训练:训练程序
模型
架构绝大多数提交的内容都基于神经机器翻译方法,其中三分之二使用 transformer 架构,而其余的则基于卷积序列到序列架构或两者的结合。比较两种架构时,袁等人发现变压器模型的性能增益非常大。
Choe 等人也利用了复制增强的变压器架构。这最初是由赵等人提出用于语法错误纠正的,他们通过引入一种允许复制输入标记的输出机制显示了改进。这是有意义的,因为每当纠错系统不纠正错误时,它只是简单地复制输入。
重新排序受限赛道的两个顶级系统也对 beam 搜索的输出句子进行重新排序。
Choe 等人注意到,他们的模型的许多修正是不自然或不正确的,他们通过使用预先训练的神经语言模型重新排序来改善这些修正。 Grundkiewicz 等人在重新排序时使用额外的从右到左神经语言模型作为特征——其动机是从右到左模型可以补充标准的从左到右解码。
其他方法使用错误检测模型进行重新排序:袁等人基于从错误检测系统导出的特征进行重新排序,而 Kaneko 等人在句子级错误检测任务上微调 BERT,并使用其预测作为重新排序的特征。随着系统被推向纠正更多错误,这两种方法的总体得分都有所提高,尤其是召回率。
过滤这种方法对于工业用例也很有趣,因为它可以显著减少处理时间,因为一般来说,大多数句子不包含错误。
迭代解码通过迭代解码,一个句子不断地通过翻译模型,直到模型输出不变的句子。这允许以增量而不是仅通过一遍来校正句子,从而使得模型能够生成更多的校正。 Náplava 等人看到了使用迭代解码的改进,但是它们以牺牲精度为代价提高了召回率。相反, Grundkiewicz 等人放弃迭代解码,因为他们认为只有在系统召回率低的情况下才有必要。
数据
一般来说,存在两种方法来生成错误的人工示例:
- 基于规则,使用从真实数据和混淆集收集的错误统计数据,混淆集由通常被错误混淆的单词组成
- 反向翻译,其中反向训练校正模型以将错误插入到正确的文本中
基于规则的生成受限领域的前两个贡献都使用了基于规则的方法。
Grundkiewicz 等人将他们的错误生成方法基于从拼写检查器 Aspell 中提取的混淆集,该拼写检查器基于词汇和语音相似性提出建议。在概率与开发集中的单词错误率相匹配的情况下,一个单词要么被删除,与其相邻单词交换,用其混淆集中的一个单词替换,要么插入一个随机单词。此外,为了处理拼写错误,通过在字符级别上使用上述相同的操作,将词汇噪声引入单词。
亚军 Choe 等人试图通过将真实错误中的 n-gram 模式插入到正确的文本中来制造真实的错误。他们还利用规则来创建特定的错误类型,如介词、名词数和动词错误。当将他们的错误生成方法与基于随机替换、删除、插入和重新排序的方法进行比较时,他们看到了明显的好处。然而,如果模型根据实际误差进行微调,差异就会变得均匀。
由于不能保证注入错误,只能简单转述句子,为了控制数据质量袁等过滤掉可能转述的人工句子对。这样,如果生成的句子不具有比从真实错误示例中学习到的阈值更高的降低的语言模型概率,则句子对被过滤。
培养
检查点平均 Náplava 等人通过使最终模型的权重为多个先前检查点的平均值,看到了性能的提高和方差的降低。对于他们的最终模型,他们最终平均了 8 个最新的检查点。
****加权最大似然估计由于语法纠错系统倾向于收敛到局部最优,其中模型通常简单地将输入不变地复制到输出, Grundkiewicz 等人和 Náplava 等人修改 MLE 损失函数,以给予应该改变的标记更高的权重。
****域适应提出了几种方法来处理不同域中错误类型和频率的差异。在一种方法中, Náplava 等人通过对共享测试集域的较小数据集进行过采样来生成他们的训练集。
作为过采样的替代方案, Choe 等人使用顺序迁移学习方法。这样,他们的模型在三个阶段的过程中被训练:1)去噪自动编码器 2)训练 3)微调。在每个阶段,模型都在一个逐渐变小的数据集上进行训练,该数据集更接近测试域。
****多任务学习他们的系统在错误检测方面表现非常好,表明辅助检测任务是有益的。
结论
BEA 2019 共享任务中系统令人印象深刻的表现表明,自上一个共享任务 CONLL14 以来的 5 年中,语法纠错领域取得了巨大的飞跃。特别是,通过将问题建模为低资源神经机器翻译任务,已经取得了很多成功。即使在这种相对狭窄的环境中,系统仍在朝着许多不同的方向发展,许多不同的技术正在被应用。展望未来,我列出的每一个组件都应该进一步探索,以开发一套最佳实践,作为工业系统和未来研究方向的基础。
原载于http://www . flachs . io。**