熊猫数据框简单指南
如何使用 Python 的 Pandas 库创建、存储和操作数据
埃米尔·佩龙在 Unsplash 上的照片
标准 Python 库pandas
是用于数据分析和操作的最流行的库之一。pandas
用于将数据转换为称为 DataFrame 的结构化格式,可用于各种操作和分析。数据帧有助于将数据格式化为清晰的表格,便于阅读和操作。
装置
pandas
是标准的 Python 库之一,应该已经安装了,但是如果没有,可以用下面的命令安装:
pip install pandas
入门指南
pandas
库通常与 Python 的numpy
库一起使用,因此为了本教程,我将导入并使用这两个库。(将来会写一个如何使用numpy
库的指南!)下面是一个从numpy
系列创建pandas
数据帧的简单例子:
import pandas as pd
import numpy as npvalues = np.random.randn(3, 2)
print(values)
print(type(values))df = pd.DataFrame(values)
print(type(df))
print(df)
下面是两个打印语句的输出:
<class 'numpy.ndarray'>
[[-1.01774631 1.0842383 ]
[-0.04752437 -0.19560713]
[-0.3643328 0.34562402]]<class 'pandas.core.frame.DataFrame'>
0 1
0 -1.017746 1.084238
1 -0.047524 -0.195607
2 -0.364333 0.345624
如您所见,第一个输出是一个 3 行 2 列的多维numpy
数组。np.random.randn
就是简单地在指定的输出形状中生成随机数。通过使用numpy
数组作为参数调用pd.DataFrame()
,可以将数组转换为pandas
数据帧。第二组打印语句显示了 DataFrame,它包含与numpy
数组相同的值,以及 3 行 2 列的相同形状。默认情况下,列和行的索引从 0 开始,但这些可以手动更改,也可以在创建数据帧时通过初始化来更改。
更新表格
更改列/行索引
如前所述,默认情况下,列和索引从 0 开始。但是,使用简单的整数作为索引并不总是有用的,因此建议根据您正在处理的数据将行和列更改为更合适的标签。您可以在初始化数据帧时或之后更改它们。假设您有想要用来标记列和行的字段名称。以下代码显示了如何使用名称初始化 DataFrame:
import pandas as pd
import numpy as npvalues = np.random.randn(3, 2)rows = ["A", "B", "C"]
features = ["foo", "bar"]df = pd.DataFrame(values, index=rows, columns=features)
print(df)
输出:
foo bar
A -2.702060 0.791385
B 1.696073 -0.971109
C -1.430298 -2.549262
如您所见,index
和columns
参数允许您在创建 DataFrame 时指定索引和列名。您也可以使用以下内容手动更改它们(建议使用关键字重命名,而不是位置):
df.rename(columns={"bar": "baz"}, inplace=True) foo baz
A 0.010732 0.420194
B -1.718910 -1.810119
C 0.409996 0.694083
在这里您可以看到,通过调用rename()
函数,列bar
被更改为baz
。注意inplace=True
被添加到参数的末尾。如果要提交对数据帧的更改,这是必要的。或者,由于 DataFrame 上的函数返回 DataFrame 对象,您可以执行以下操作,其效果与添加inplace=True
相同:
df = df.rename(columns={"bar": "baz"})
在这里打印df
会得到与上面相同的结果,只是列被重命名了。
此外,通过使用set_index()
函数,您可以选择其中一列作为索引,如下所示,使用 3x4 矩阵:
import pandas as pdvalues = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
rows = ["A", "B", "C"]
features = ["foo", "bar", "baz", "qux"]df = pd.DataFrame(values, index=rows, columns=features)df.set_index("foo", inplace=True)print(df)
输出:
bar baz qux
foo
1 2 3 4
5 6 7 8
9 10 11 12
这将获取foo
列并用这些值替换当前索引。当有一列用于date
或id
并且您想要将索引设置为这些唯一值时,这通常会很方便。
添加新列/行
如果您想在数据帧的底部追加一个新行,您必须确保它与现有数据帧的形状相同。例如,如果您有与上面相同的 4 列数据帧,则正在添加的新行也应该有相同的 4 列。下面是新的数据框架,由 3 行 4 列组成:
import pandas as pdvalues = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
rows = ["A", "B", "C"]
features = ["foo", "bar", "baz", "qux"]df = pd.DataFrame(values, index=rows, columns=features)
prrint(df)
输出:
foo bar baz qux
A 1 2 3 4
B 5 6 7 8
C 9 10 11 12
现在,如果您想添加一个新行,这 4 列必须对齐。至于索引,您可以指定它或者让pandas
自动生成一个新的(默认为下一个可用的整数):
new_data = [[13, 14, 15, 16]]
new_df = pd.DataFrame(new_data, index=["D"], columns=features)df = df.append(new_df)
print(df)
输出:
foo bar baz qux
A 1 2 3 4
B 5 6 7 8
C 9 10 11 12
D 13 14 15 16
注意new_data
不仅仅是一个列表,而是一个 1 行 4 列的矩阵。这是因为它必须遵循与附加它的初始数据帧相同的结构。然后,您可以将这个 1x4 转换为第二个 DataFrame,它具有指定的索引和相同的列。功能df.append()
允许您将这个由单行组成的新数据帧添加到原始数据帧中。
要向数据帧添加新列,只需用新列名和新数据引用数据帧。使用与上面相同的数据帧,它看起来像这样:
import pandas as pdvalues = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
rows = ["A", "B", "C"]
features = ["foo", "bar", "baz", "qux"]df = pd.DataFrame(values, index=rows, columns=features)
df["quux"] = [55, 56, 57]print(df)
输出:
foo bar baz qux quux
A 1 2 3 4 55
B 5 6 7 8 56
C 9 10 11 12 57
只要新列表与初始数据帧具有相同的行,就可以很容易地追加新列。
还有一个更巧妙的技巧——如果您想选择所有的列名,您可以调用df.columns
来返回['foo', 'bar', 'baz', 'qux', 'quux']
!
选择数据
有时,您可能需要访问特定的行或列,甚至是特定的数据单元格。与我们之前添加列的方式类似,您可以用同样的方式访问一个或多个列:
import pandas as pdvalues = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
rows = ["A", "B", "C"]
features = ["foo", "bar", "baz", "qux"]df = pd.DataFrame(values, index=rows, columns=features)
print(df[["foo", "baz"]])
输出:
foo baz
A 1 3
B 5 7
C 9 11
通过调用由列名列表索引的df
,您可以创建一个只包含指定列的新对象。现在,如果您只想访问某些行,您可以使用.loc
或.iloc
。这两者的区别只是通过名称或索引位置来访问行。以下示例显示了两种方法以及它们如何返回同一行:
import pandas as pdvalues = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
rows = ["A", "B", "C"]
features = ["foo", "bar", "baz", "qux"]df = pd.DataFrame(values, index=rows, columns=features)row_loc = df.loc["B"]
print(row_loc)row_iloc = df.iloc[1]
print(row_iloc)
输出:
foo 5
bar 6
baz 7
qux 8
Name: B, dtype: int64foo 5
bar 6
baz 7
qux 8
Name: B, dtype: int64
索引 1 处的行(第二行,因为我们从 0 开始计数!)和索引为B
的行是同一行,可以用任何一种方法访问。
如果您想访问单个单元格,您也可以使用.iloc
,如下图所示:
import pandas as pdvalues = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
rows = ["A", "B", "C"]
features = ["foo", "bar", "baz", "qux"]df = pd.DataFrame(values, index=rows, columns=features)
print(df)cell = df.iloc[1, 2]
print("cell: ", cell)
输出:
foo bar baz qux
A 1 2 3 4
B 5 6 7 8
C 9 10 11 12cell: 7
使用.iloc
,第一个值是行,第二个值是列——因此.iloc[1, 2]
访问第 2 行第 3 列的单元格。
操作
数学运算也可应用于pandas
数据帧中的数据。简单地说,可以将标量应用于数据帧的一列,如下所示:
import pandas as pdvalues = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
rows = ["A", "B", "C"]
features = ["foo", "bar", "baz", "qux"]df = pd.DataFrame(values, index=rows, columns=features)df["qux"] = df["qux"] * 10print(df)
输出:
foo bar baz qux
A 1 2 3 40
B 5 6 7 80
C 9 10 11 120
这里,我们将qux
列乘以标量 10,并用新的值替换该列中的值。您还可以在多列之间执行操作,例如将列相加或相乘。在下面的示例中,我们将两列相乘,并将它们存储到一个新列中:
import pandas as pdvalues = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
rows = ["A", "B", "C"]
features = ["foo", "bar", "baz", "qux"]df = pd.DataFrame(values, index=rows, columns=features)df["quux"] = df["bar"] * df["baz"]print(df)
输出:
foo bar baz qux quux
A 1 2 3 4 6
B 5 6 7 8 42
C 9 10 11 12 110
这里,新的quux
列的每一行都是bar
列和baz
列的每一行的乘积。
您可以在数据帧上执行的另一个重要操作是apply()
功能。这允许您对数据帧中的数据应用整个函数。以下是使用apply()
函数对数据应用独立函数的简单示例:
import pandas as pddef sum_function(row):
return row["foo"] + row["bar"] + row["baz"] + row["qux"]values = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
rows = ["A", "B", "C"]
features = ["foo", "bar", "baz", "qux"]df = pd.DataFrame(values, index=rows, columns=features)df["quux"] = df.apply(sum_function, axis=1)print(df)
输出:
foo bar baz qux quux
A 1 2 3 4 10
B 5 6 7 8 26
C 9 10 11 12 42
这里,我们有一个名为sum_function()
的函数,它简单地将一行中的 4 个元素相加。使用相同的 DataFrame,我们可以用这个函数作为参数调用df.apply()
。第二个参数axis=1
是必需的,因为默认情况下轴被设置为 0,而apply()
将作用于行而不是列。使用这个apply()
函数,我们可以看到quux
列包含所有先前字段的总和,这是由指定的函数应用的。
读取/写入数据
pandas
的另一个简洁的特性是你可以读写数据帧中的文件。例如,您可以读入 CSV 或 JSON 文件,处理数据,然后将其写回新文件。假设您在与 Python 脚本相同的目录中有一个名为data.csv
的文件,其中包含我们一直在处理的相同数据。您可以使用以下代码将它读入数据帧:
import pandas as pddf = pd.read_csv("data.csv")print(df)
其输出将与我们一直在处理的数据相同:
foo bar baz qux
A 1 2 3 4
B 5 6 7 8
C 9 10 11 12
然后,如果您想将数据写回一个新的 CSV,您可以使用以下命令将它写到您的工作目录:
output.csv
:
,foo,bar,baz,qux
A,1,2,3,4
B,5,6,7,8
C,9,10,11,12
这个output.csv
正是我们最初用read_csv()
函数读入数据帧的data.csv
所包含的内容!
外卖食品
pandas
库非常灵活,对于处理数据集和数据分析非常有用。使用pandas
对数据进行存储、操作和执行操作非常简单。此外,许多机器学习模型可以使用pandas
数据帧作为它们的输入。本指南的目标是提供该库的简单概述,并提供一些基本示例来帮助您更好地理解pandas
的一些关键特性。
感谢您的阅读,我希望您已经对pandas
有了足够的了解,可以开始在您的下一个 Python 项目中实现它了!
一个简单的黑客来提高任何全局优化算法
用准随机数生成器给你的优化器加点盐和胡椒
比尔·杰伦在 Unsplash 上的照片
许多计算机程序依靠随机数工作。
这就是全局优化算法的情况,其中属于应用数学和数值分析的一个分支,研究如何在给定的优化超空间上有效地找到函数 f(x) 的全局最小值/最大值。
在每个全局优化算法中,优化过程从生成维度为 D 的 N 个个体的随机抽样群体开始。
这些个体中的每一个都代表了要解决的优化问题的候选解决方案。
许多研究表明,第一代候选解的空间分布对优化算法的性能有重要影响。
安迪·马克里在 Unsplash 拍摄的照片
像混沌理论的蝴蝶效应一样,随机过程优化算法初始化中的小偏差会导致非常不同的结果。
在本文中,你会发现有一种特殊的“随机”数字发生器可以提高优化算法向全局最优的收敛速度。
当我第一次开始从事进化算法的开发工作并遇到这个相对简单的黑客时,我怀疑它是否会对性能产生任何明显的影响,但没过多久我就发现混沌理论著名的蝴蝶效应也适用于全局优化问题。
欢迎来到疯狂而美丽的随机世界。
克瑞丝·布朗从澳洲墨尔本拍摄的照片。维基共享。
电脑不玩骰子
我们生活在一个混沌的世界里,随机性时刻存在于我们的生活中。然而,在我们在计算机上运行的程序中准确模拟这种随机性并不容易。
一台普通的计算机,就像一台机器,在设计上是确定的,这意味着相同的输入总是产生相同的输出。
随机值通常用于统计分析、艺术,或者只是当我们不得不随机选择一些东西时,例如在抽奖中。
然而,当涉及到用标准计算机产生随机性时,我们归因于硬币或骰子等物理对象的完美随机性有点难以实现。
计算机试图以纯数学的方式生成随机数,这是一个问题,而不是一个优势——过了一段时间,看似随机的东西最终不再那么随机了。
虽然随机物理过程的组合可以用来生成纯随机数(从桌上的鼠标移动到放射性),但如果我们像计算机一样依赖纯数学,我们能得到的最好结果是伪随机数。
标准计算机中纯粹的随机性只是一个错误的幻觉,我们所能得到的最好的结果是“几乎随机”的不可预测性
伪随机数非常接近纯粹的概率,但在随机性方面也有一些限制。这就是前缀“*伪“*的原因。
无论如何,在计算机中生成一个好的伪随机数序列也不容易。
这个游戏的名字是寻找一个函数,它的结果在每次使用时都不同,尽管我们知道它的输出会在某个时间重复,因为从技术上讲,只能使用有限数量的数字(例如,手表表面的数字或存储在变量中的 264 位数字)。
为什么伪随机数可能不足以进行全局优化?
在任何基于群体的全局优化算法中,如 PSO 、 CMA-ES 、 GA 或 DE 等等,当我们使用伪随机数发生器从均匀概率分布 U(0,1) 中对初始代的前 N 个个体进行采样时,我们得到如下结果:
在 Matlab 中使用伪随机数发生器分配 1000 个样本。图片由作者提供。
你不认为 2D 空间中的个体分布在某些区域过于稀疏吗?
这是伪随机数的问题,它们不会以均匀的方式有效地填充空间。
用伪随机数发生器创建的样本并不是沿着采样超空间的每个维度完美地均匀分布的。正如您稍后将看到的,这可能会对优化性能产生很大的影响。
但是不要担心,这个问题有一个非常简单的方法,叫做准随机数。处理伪随机数缺乏同质性的一个非常好的替代方案。
准随机数与伪随机数有许多相似之处,但它们是基于低差异序列确定性选择的。
低差异序列?
好吧,我知道你现在可能想知道,什么是低差异序列?
在数学中,差异( DN) 为一个样本序列( s₁,…)关于区间[a,b]被定义为
根据这个定义,当 N (序列中元素的数量)趋于无穷大时,一个均匀分布序列具有趋于零的差异。
低差异序列的一个优点是它们以均匀分布的方式更加均匀地填充 N 维超空间,因此,使用这种类型的序列创建的初始群体将提供对优化超空间的更加有效的探索。
低差异序列也被称为准随机序列。
对于我们的情况,真正重要的是要注意,准随机数的低差异属性对于全局优化方法至关重要。
但是,最重要的低差异序列类型是什么?
哈尔顿序列和哈默斯利集
哈尔顿序列和哈默斯利集合定义了对 D 维空间采样的两种确定性的https://en.m.wikipedia.org/wiki/Deterministic_system方式,使得连续的点彼此尽可能远离。
本质上,Halton 序列和 Hammersley 集合都是针对多维情况的 Van der Corput 序列的推广。
Van der Corput 序列最初被提出来对一维区间[0,1]进行采样,使得序列的一个点和下一个点尽可能远。
假设我们想要生成基数为 b 的范德科尔特序列的 N 个样本(其中 b 是一个质数),那么第 n 个元素可以通过两个简单的步骤获得:
- 获取系数 ai 表示基数 b: 中整数 n 的二进制表达式
****
*2.然后,利用 aᵢ 系数,我们可以最终计算出序列的第 n 个元素*ϕ(n】如下:
例如,数字 n = 5 可以用基数 b = 2 表示,其中 a₀ = 1, a₁ = 0, a₂ = 1,因此范德科尔普特序列的第 5 个元素将等于 1/2 + 1/8 = 5/8。
下图将帮助您以升序(红点)显示基数为 b = 2 的范德科尔特序列的前十二个样本。
资料来源:从范德科尔普特到拟蒙特卡罗规则序列的现代构造。论文
我知道这里涉及的数学可能不简单。老实说,我一直很难想象数字在不同于 b = 10 的基数下的表示。
然而,您会惊讶地发现,在通用库 b. 中编写范德科尔特序列是多么简单
看看这段 C 代码片段。
是不是简单得令人难以置信?确实是!
到目前为止,我们已经看到了如何生成一个一维序列,所以下一步是将这些序列扩展到更高维度。
这就是哈尔顿的贡献发挥作用的时候了。
为了将范德科尔普序列推广到一个 D- 维超 - 空间,哈尔顿使用了一个非常简单的策略,即使用一个范德科尔普序列,每个轴具有不同的素数基。
这是,对于 D- 维的情况,哈尔顿序列的第一维是基数为 b = 2 的范德科尔普序列,第二维是基数为 b = 3 的范德科尔普序列,第 D-th 维是基数与第 D-th 素数一致的范德科尔普序列。
这是 1000 个哈尔顿序列样本的样子。
此图像显示了来自伪随机数发生器(顶部)的 1000 个点,与 Halton 低差异序列(底部)进行了比较。图片由作者提供。
哈默斯利使用了这个策略的一个小变体来产生另一个准随机序列,即哈默斯利集合。
在哈默斯利集合中,第一维的第 n 个样本不是基数为 2 的范德科尔普序列,而是简单的 n / N
此图像显示了伪随机数发生器(顶部)的 1000 个点,与 Hammersley 集(底部)进行了比较。随着样本以递增的顺序呈现,观察 Hammersley 集合的第一维如何是 n / N。
Halton 序列和 Hammersley 集都是多维中最基本的低差异序列生成器,它们可以被认为是其他低差异序列的构造块。
2.Sobol 序列
Sobol 序列是另一种广泛使用的准随机数生成器,它是由 Ilya M. Sobol 早在 1967 年发明的。
这个准随机数生成器使用基数 2 来生成区间[0,1]的均匀分区,然后对采样超空间的每个维度执行主序列的特殊重新排序。
Sobol 序列的数学实现稍微繁琐一些,所以我们将直接使用它获得结果,并将它们与使用伪随机数生成器获得的结果进行比较。
如果你想了解更多 Sobol 序列背后的理论,可以参考这个来源。
该图显示了 Sobol 序列中的 1000 个点(底部)与伪随机样本(顶部)的比较。Sobol 序列更均匀地覆盖空间。图片由作者提供。
对于 Python 、 Matlab 、 Julia 、 Fortran 和 C++ 编程语言来说,Sobol 序列有太多的实现。
🟣应用实例——助推粒子群优化算法
这里,我们将评估随机初始化对最简单但有效的优化算法之一粒子群优化(PSO)的优化性能的影响。
基于粒子群优化算法的种群进化动画。来源:维基共享。
为该比较分析选择的函数 f(x) 将是一组三个多维函数,它们通常用于全局优化算法的基准测试。
- f₁ :球体功能
- f₂ :超椭球函数
- f₃ :阿克利函数
我们将只考虑自变量 x 的值的上下限约束,而不考虑非线性约束。
将比较三种不同的粒子群优化算法的种群初始化方法:
- U-PSO :使用伪随机数发生器(
rand()
函数)的统一初始化。 - H-PSO :使用随机 Halton 序列初始化。
- S-PSO :使用随机化的 Sobol 序列进行初始化。
选择用来测量优化性能的品质因数将是找到基准函数的全局最小值(在这种情况下是已知的)所需的函数求值的平均数。越低越好。
为了分析维度诅咒的影响,我们还将考虑三种不同的情况,维度 D = 10, D = 20, D = 30。
你渴望看到优化基准测试的结果吗?
这是我在笔记本电脑上运行了几次优化后得到的结果。
D = 10 的优化基准测试结果,显示了寻找最优解的函数评估的平均值。图片由作者提供。
D = 20 的优化基准结果,显示了寻找最优解的函数评估的平均值。图片由作者提供。
D = 30 的优化基准测试结果,显示寻找最优的函数评估的平均值。图片由作者提供。
如结果所示,S-PSO 算法(用随机化的 Sobol 序列初始化的 PSO)优于其他两种算法(U-PSO 和 H-PSO),而不考虑成本函数和优化空间维度 D.
从这些结果得出的另一个结论是,尽管用随机化 Halton 序列初始化的 PSO 算法并不比使用 Sobol 序列的算法好,但它与使用标准伪随机数发生器的算法一样好(在某些情况下稍好)。
最后的想法
正如您所看到的,从伪随机初始化策略切换到准随机初始化策略可以提高优化算法的性能。
这个技巧非常简单,可以应用于任何全局优化器,您只需修改那些使用rand()
函数的代码行,作为您自己的低差异序列生成器。
但一如既往,有一个免责声明:
“低差异序列破解不是魔术”
有时,根据您正在使用的成本函数,您看不到任何改进,但是无论如何,这都不会损害优化器的性能。所以值得一试!
参考文献
[1]马阿拉宁,h .,米耶蒂宁,k .和马克勒,M. M. 2004 年。遗传算法的准随机初始种群。计算机与数学应用,第 47 卷:1885-1895。
[2] 莫罗科夫,W. J .和卡弗利什,R. E. 1994。准随机序列及其差异。暹罗 J. Sci。计算机。, 15(6):12511279
[3]马斯卡尼,m .和迟,H. 2004 年。在加扰的 Halton 序列上。蒙特卡洛方法应用,10(3):435–442
【范·德·科尔普特,J.G. 1935。Verteilungsfunktionen。一.米特。继续。阿卡德。潮湿。阿姆斯特丹。38: 813–821
[5] Pausinger,f .和 A. Topuzoglu,Van der Corput 序列和置换多项式,预印本。
OpenCV Python 上一个简单的 HDR 实现
了解如何使用 Python 和 OpenCV 创建高动态范围(HDR)图像
在 Unsplash 上由 Lerone Pieters 拍摄
HDR 图像包含了不同曝光的多幅图像的信息。在光源不均匀的场景中,单次拍摄可能会使图像的某些区域过度曝光,并且由于亮度增加,细节会丢失。相反,这张照片也可能出现曝光不足的区域,这也将导致信息丢失。
要创建 HDR 图像,您需要:
- 用不同的曝光量拍照。最少 2 个,通常 3 个,你可以使用 3 个以上的图像,但这将占用大量的 CPU 资源。
- 对齐图像。即使你使用三脚架,你也需要执行这一步(我们说的是像素级对齐)。没有正确对齐您的图像将导致您的 HDR 图像中的伪像和“幽灵”。
- 将对齐的图像合并为一个。
- 对合并的图像执行色调映射。在自然界中,最小的可能亮度是零,但最大值不限于 255,事实上没有限制,它可以是无穷大。为此,我们需要将第三步中获得的图像映射到(0,255)范围。这可以通过色调映射来实现。
例子
import cv2 as cvimport numpy as np# Loading exposure images into a listimg_fn = [r"C:\Users\felipe.cunha\Documents\venv\HDRTest\100.jpg", r"C:\Users\felipe.cunha\Documents\venv\HDRTest\250.jpg", r"C:\Users\felipe.cunha\Documents\venv\HDRTest\500.jpg"]img_list = [cv.imread(fn) for fn in img_fn]exposure_times = np.array([100, 250, 500], dtype=np.float32)# Merge exposures to HDR imagemerge_debevec = cv.createMergeDebevec()hdr_debevec = merge_debevec.process(img_list, times=exposure_times.copy())merge_robertson = cv.createMergeRobertson()hdr_robertson = merge_robertson.process(img_list, times=exposure_times.copy())# Tonemap HDR imagetonemap1 = cv.createTonemap(gamma=2.2)res_debevec = tonemap1.process(hdr_debevec.copy())# Exposure fusion using Mertensmerge_mertens = cv.createMergeMertens()res_mertens = merge_mertens.process(img_list)# Convert datatype to 8-bit and saveres_debevec_8bit = np.clip(res_debevec*255, 0, 255).astype('uint8')res_mertens_8bit = np.clip(res_mertens*255, 0, 255).astype('uint8')cv.imwrite(r"C:\Users\felipe.cunha\Documents\venv\HDRTest\ldr_debevec.jpg", res_debevec_8bit)cv.imwrite(r"C:\Users\felipe.cunha\Documents\venv\HDRTest\fusion_mertens.jpg", res_mertens_8bit)
德贝韦克方法。看看这些细节!
默滕斯方法。
结论
我们已经介绍了 HDR 的基本情况。当您需要在曝光不足和曝光过度的大视场中定位特征时,此技术特别有用。
希望这能对你的计算机视觉项目有所帮助。
如果你想要一篇关于这个话题的更深入的文章,请在评论中告诉我。
谢谢!
逻辑回归系数的简单解释
入门
赔率简单解释了一下。
图片由伊恩·杜利(来源:Unsplash)——感谢伊恩!
我一直对逻辑回归很着迷。这是一个相当简单但功能强大的机器学习模型,可以应用于各种用例。它已经被广泛地解释和应用,然而,我还没有看到许多对模型本身的正确和简单的解释。让我们现在破解它。
我不会深入研究什么是逻辑回归,它可以应用在哪里,如何测量模型误差等细节。已经有很多关于它的好文章了。这篇文章将以一种简单、直观的方式专门处理的系数的解释,,**,**而不引入不必要的术语。
步骤零:解释线性回归系数
让我们首先从一个线性回归模型开始,以确保我们完全理解它的系数。这将是稍后解释逻辑回归的基础。
这是一个线性回归模型,有两个预测变量和结果 Y :
Y = a+ bX₁ + cX₂ ( 等式 ** )*
我们来选一个随机系数,比如说, b 。让我们假设 b > 0 。解释 b 很简单:x₁增加 1 个单位将导致 Y 增加 b 个单位,,如果所有其他变量保持不变(了解这一条件很重要)。注意,如果 b < 0 ,那么 X₁ 增加 1 个单位将使 Y 减少 b 个单位。
作为一个例子,让我们考虑以下基于 2 个输入变量预测房价的模型:平方英尺和年龄。请注意,该模型是“假的”,即我编造的数字只是为了说明这个例子。
房价= a + 50,000平方英尺-20,000年龄
如果我们增加 1 平方英尺的面积,房价将增加 50,000 美元。如果我们将房龄增加 1 年,房价将减少 2 万美元。年龄每增加 1 岁,房价将继续下降 20,000 美元。
好吧,这很简单。现在让我们继续进行逻辑回归。
接下来:解释逻辑回归系数
逻辑回归模型是这样的:
logit§ = a+ bX₁ + cX₂(方程式**)
你会注意到它与线性模型略有不同。让我们澄清它的每一点。 logit§ 只是 log(p/1-p) 的一个快捷方式,其中 p = P{Y = 1} ,即“成功”的概率,或者说一个结果的存在。X₁和 X₂是预测变量, b 和 c 是它们对应的系数,每个系数决定了 X₁和 X₂对最终结果 Y (或 p )的侧重。最后, a 就是简单的截距。
我们仍然可以使用旧的逻辑,比如说, X₁ 增加 1 个单位将导致blogit§增加*。我在这里只是一个模仿者,应用线性模型解释。为什么不呢?方程和**其实形状一样!
但是现在我们不得不更深入地研究“X₁每增加 1 个单位将导致 logit§增加 b”这一说法。第一部分很清楚,但是我们真的感觉不到 logit§ 中 b 的增加。这到底意味着什么?
要理解这一点,我们先解开 logit§ 。前面说过, logit§ = log(p/1-p) ,其中 p 是 Y = 1 的概率。 Y 可以取两个值,0 或 1。P{Y=1}称为成功的概率。因此logit§= log(P { Y = 1 }/P { Y = 0 })。这被称为对数优势。
揭开对数概率的神秘面纱
我们得出了这个有趣的术语 log(P{Y=1}/P{Y=0}) 又名log-odds。现在回到系数解释:X₁每增加 1 个单位将导致成功几率的对数增加:失败。
好吧,这样更有道理。但是让我们充分阐明这个新术语。先从赔率说起,再扩展到对数赔率。
你可能以前听说过赔率,例如赢得赌场游戏的赔率。人们经常错误地认为几率和概率是一回事。他们不是。这里有一个例子:
掷出公平的 6 面骰子得到 4 的概率为 1/6 或~16.7%。另一方面,得到 4 的几率是 1:5,或者 20%。这等于 p/(1-p) = (1/6)/(5/6) = 20%。所以,几率代表了成功概率和失败概率的比率。从赔率到概率的转换相当简单,反之亦然。
现在,对数几率就是几率的对数。引入对数的原因很简单,因为对数函数将产生一个可爱的正态分布,同时缩小 P{Y=1}/P{Y=0} 的极大值。还有,对数函数是单调递增的,所以不会破坏原来数列的顺序。
也就是说, X₁ 的增加将导致 log-oddslog(p { y = 1 }/p { y = 0 })增加数量 b > 0 ,这将增加赔率本身(因为 log 是单调递增的函数),这意味着 P{Y=1} 获得 100%概率馅饼的更大比例。换句话说,如果我们增加 X₁ ,那么 Y=1 对 Y=0 的几率会增加,导致 Y=1 比增加前更有可能。
当 b < 0. I’ll leave it up to you to interpret this, to make sure you fully understand this game of numbers.
Phew, that was a lot!
But bear with me — let’s look at another “fake” example to ensure you grasped these concepts.
logit§ = 0.5 + 0.13 * study_hours + 0.97 * female
In the model above, b = 0.13,c = 0.97,和 p = P{Y=1} 是通过一次数学考试的概率时,解释是类似的。让我们挑选出学习时间,看看它如何影响通过考试的机会。学习时间增加 1 个单位(1 小时)将导致 logit§ 或 log(p/1-p) 增加 0.13。现在,如果log(p/1–p)增加 0.13,那就意味着 p/(1 — p) 会增加 exp(0.13) = 1.14 。这意味着通过考试的几率增加了 14%(假设变量女性保持不变)。
我们也来解读一下身为女性对通过考试的影响。我们知道 exp(0.97) = 2.64 。也就是说,女性通过考试的几率要高出 164%。
模型解释日益成为机器学习&数据科学的一个重要方面。理解模型做什么以及它如何做出预测在模型构建&评估过程中至关重要。现在您对逻辑回归的工作原理有了更好的理解,您将能够更好地理解您构建的模型!
如果您喜欢这篇文章,请关注我,以便在新内容发布时收到通知!❤你可能也会喜欢下面的内容,在这里我用简单的方式解释统计概念:
p 值的简单解释
简单解释了 p 值和冰淇淋消耗量。
Katie Smetherman 的照片(来源:Unsplash——感谢 Katie!❤)
p 值很有意思。它们非常有用,但又耐人寻味,神秘莫测,难以理解。这篇文章将试图在不使用太多技术术语的情况下解释它们。
让我们简单地从一个关于冰淇淋消费的有趣故事开始,用通俗易懂的方式来说明 p 值。
冰淇淋店问题🍦
为了便于说明,我将讲述一个虚构的冰淇淋店,我经常在那里停下来买我最喜欢的冰淇淋口味。这家店位于多伦多一条繁忙的街道上,每天都有数百人驻足品尝美味。
在一个阳光明媚的星期天,我路过这个地方,点了我最喜欢的草莓🍓冰淇淋。出于好奇,我问店主,她认为普通顾客一周会买多少冰淇淋。她摇摇头说:
“嗯,我想说,平均来说,一个顾客一周会买 3 个冰淇淋,可能多一个或少一个。”
我谢过她,走到附近的长椅上享用我的甜点。
当我坐在附近的长椅上时,我仔细观察了所有进出的人,注意到有些人甚至一天来多次。我想——嗯,他们可能会在本周再次回来。所以我开始怀疑——她告诉我真相了吗?我开始怀疑现实可能是平均每个顾客一周消费超过 3 个冰淇淋。考虑到冰淇淋的美味,这并不奇怪。
我非常注重数据,喜欢根据数据点做出结论,所以我决定做一个彻底的统计分析来验证我的怀疑。我决定进行假设检验(即 A/B 检验),并收集我做决定所需的数据。
首先,我使用所有者与我分享的数据点绘制了一个分布图:
我假设我的数据近似正态分布,均值= 3,标准差= 1。请记住,并不是所有的真实生活数据都是正态分布的![图片是我自己的]
太好了——现在我们对底层数据有了一个大致的概念。让我们继续并建立一个 A/B 测试。
设置 A/B 测试
在 A/B 测试中,你可能已经知道,你有一个控制组 A (也称为现状)与一个处理组 B竞争。在这种情况下,让我们这样设置它们:
答:平均每位顾客每周在观察到的冰淇淋店购买 3 份冰淇淋。
和…相对
B:平均每位顾客每周在观察到的冰淇淋店购买3 个以上的冰淇淋。**
如果我能找到足够的证据来否定 A 而支持 B,那就太好了,这也证实了我的怀疑。
那么,我如何收集足够的证据呢?我需要数据(耶!).我要做的是,我将收集关于 n = 50 顾客的数据点,观察他们的习惯,并获得他们在给定的一周内冰淇淋的平均数量。
好吧,假设我收集了这些数据,并观察到X̅ = 4,也就是说,我观察到平均来说,一位顾客每周从这家虚拟的冰淇淋店订购 4 份冰淇淋。下一步是使用一个测试统计,它将帮助我得出一个结论,即我怀疑店主对我撒谎是否正确。因为我们在这里测试样本均值,所以我将使用以下常用的测试统计:
Z = (X̅— μ)/ (σ / sqrt(n))
****边注:根据 A/B 测试的设置,有大量可用的测试统计数据。例如,如果您测试的是一个比例而不是一个平均值,或者您测试的是多个治疗组,那么测试统计看起来会有所不同。你可以点击了解更多信息。
注意上面的 Z 测试统计的公式。我们基本上期望从样本均值中减去假设(理论)均值 μ ,然后通过样本大小和理论标准偏差 σ 将这一差异归一化。这将有助于我们确定我们选择的样本平均值与理论平均值相差多少。另外,根据店主告诉我们的,注意 μ = 3,σ = 1 。X̅是 4,而 n = 50:
z =(4–3)/(1/sqrt(50))= 7.07。
好了,我们得到了这个所谓的 z 值 7.07,现在,我们该怎么做呢?我们将在图表上绘制这个数字,旁边是假设的差值 0(即,如果语句 A 是正确的,那么 Z = 0 )。
[图片是我自己的]
因此,您看到的是,A 与 B 的对比——我们看到现状为 0,即没有差异,这意味着所有者是正确的,B 意味着归一化差异为 7.07,即根据我的观察,我与现状相差 7.07 个单位(或者,如果我们将 z 分数非归一化并查看原始原始值,则相差 1 个冰淇淋)。
现在的问题是,这种差别太大了吗?这个观察到的差异足够大(显著)让我说“好吧,我有足够的证据拒绝 A,接受 B”*?*
融合 p 值以进行数据驱动的决策
看上面的图表,我们到达了需要做出决定的点,但是我们不知道如何做。救市是为了找到一个有助于我们做出最终决定的 p 值。什么是 p 值?
假设零假设(A)是正确的,p 值是获得至少与已经观察到的结果一样极端的结果的概率。
好吧,这是一个非常类似维基百科的定义,让很多人感到困惑(当我第一次了解 p 值时,我也感到困惑😌).通俗地说,p 值是下图中绿色的区域,所以本质上它是得到比我们观察到的更极端结果的概率(在我们的场景中 X̅ = 4)。****
p 值是曲线下的绿色区域。在我们的例子中,我们很快就会看到这个面积几乎为 0。显然,上图中的面积远不为零,我们想从概念上强调 p 值,所以不要让这个问题困扰你!😃【图片是我自己的】
这意味着,我们离平均值越远,这个区域(即 p 值)就越小,因此我们就越不可能观察到比我们已经看到的更极端的情况。这个区域越小,我们反对现状的证据就越多(假设 A)。这个区域越小,我们已经观察到的(X̅ = 4)就越有可能已经非常极端,并且如此极端,以至于观察到更极端的东西是如此不可能**。所以,我们有很多反对现状的证据。计算 p 值意味着计算钟形曲线下的面积,以查看这些极端事件发生的可能性有多大。**
让我们也正式计算我们的 p 值:
p = P{Z ≥ z} = P{Z≥ 7.07} ≈ 0。
****边注:我用这个 Z 分表得出 P{Z≥ 7.07} ≈ 0。这些表在推断统计中非常常用,所以请继续将链接加入书签以备将来使用!
现在你可能想知道——多小的 p 值才算足够小?多大的差异才算足够大?为此,我们需要一个预先确定的门槛。请注意,这些阈值是在实验之前确定的,以避免 p-hacking 提前调整阈值以获得所需的最终结果。
这个阈值在统计学上正式称为显著性水平**,或 alpha,通常为 5%或 0.05。在不同的情况下,你会看到不同的阈值,例如在医学等更敏感的领域,阈值可能会更低。**
拒绝规则是,如果 p 值小于α,我们拒绝 A 而支持 B 。在我们的例子中,这是真的,所以我们可以继续拒绝 A,并得出结论,我们有足够的证据来支持我们的假设 b。😃
End Note 1: 在设置 A/B 检验时考虑其他因素的细节我没有赘述,比如选择最佳样本量、检验的功效、效应大小等。这篇文章的目的是专注于解释 p 值,而不是转移到其他话题。
结尾注释 2: 作为一项作业,我会让你观察如果我们观察到 X̅ = 3.2 而不是 X̅ = 4 会发生什么。继续计算这种情况下的 p 值,您也可以绘制图表,给你一个更好的直觉。你还有足够的证据来否定你的零假设 A 吗?X̅ = 3.2 而不是 X̅ = 4 这一事实如何改变了你的直觉?请在评论中告诉我!👇
谢谢你陪我走完这段旅程!我希望你喜欢学习 p 值。请随意查看我在中以简单、务实的方式解释统计概念的几篇文章:
另外,请关注我的博客更新。干杯!❤
一个简单的数学自由 PyTorch 模型框架
喜欢数据科学和深度学习,但被所有的数学和公式搞糊涂了,只想要一个简单的解释和一组例子?我也是。所以现在我们来纠正一下,好吗?
本文的目标是尝试并制作一个简单的可解释的构建 PyTorch 深度学习模型的示例,以给出回归、分类(二元和多类)和多标签分类的示例。
我发现每一种都有几十个甚至几百个例子,但没有一个以相似的方式给出了每一种的例子,并且经常归结为大型的数学公式,所以我在不同的风格和方法之间来回转换,因此我一次又一次地困惑自己,所以我想要一些简单的东西可以重复使用。我还想尽可能简单地完成它们,使用可爱的、随处可得的库和模块,尽可能少地定制“我的解释”。
因此,我不想陷入 EDA、特征工程和所有那些非常重要的东西(我会说比模型更重要),并希望尽可能保持简单和“只是模型的框架”。
考虑到这一点,我们将使用来自 sklearn.datasets 的精彩的 make_regression 、 make_classification、和make _ multi Label _ classification,从而模拟一旦您为第一个基线模型做好所有 EDA 和功能工程准备,您的数据将处于的状态,这意味着我们将不会进行任何标签编码、解决不平衡等。
我也想完全远离数学。我会解释为什么我们在没有符号、公式和算法的情况下做我们正在做的事情。
这不仅仅是给你一些代码来剪切/粘贴,而是向你展示我在这个过程中遇到的一些错误,从而产生(我希望)一组有用的函数和信息。
我写这篇文章是为了帮助我拥有一个入门笔记本,我可以用它来做各种各样的事情,我希望这样做能帮助到其他人,所以我们开始吧。
准备笔记本
首先,加载相关模块。基本上就学、 torch 、 NumPy 、 matplotlib 。
from sklearn import metricsfrom sklearn.model_selection import train_test_split
from sklearn.datasets import make_regression, make_classification, make_multilabel_classification
from sklearn.preprocessing import LabelEncoder, StandardScaler, MinMaxScalerimport torch
from torch.utils.data import Dataset, DataLoader
import torch.optim as torch_optim
import torch.nn as nn
import torch.nn.functional as Fimport numpy as np
import matplotlib.pyplot as plt
创建可重复使用的 PyTorch 组件
好了,下一节是我们将在整篇文章中反复使用的函数。我将概述他们做什么,但是损失函数和精度函数的任何细节将随着我们的进展详细解释。
使用 PyTorch 数据集
作为其中的一部分,我们将熟悉并使用 PyTorch 数据集。为什么?嗯,它们提供了一个很好的方法来在我们的数据周围放置一个迭代器,并允许所有的好处,比如干净地批处理数据等等,所以为什么不使用它们呢。
这是建立一个数据库的最低要求,而且都是不言自明的。
我相信你知道,深度学习模型喜欢数字,所以当你准备好数据时,所有的分类特征都已经被编码了。如果它是一个整数,即所有特性的 np.float32 和目标的 np.int64,否则它也是 np.float32。
在这个过程中,它还把我们可爱的 numpy 数组变成了时髦的 PyTorch 张量。
class MyCustomDataset(Dataset):
def __init__(self, X, Y, scale=False):
self.X = torch.from_numpy(X.astype(np.float32))
y_dtype = np.int64 if Y.dtype == np.int64 else np.float32
if scale: self.y = torch.from_numpy(MinMaxScaler().fit_transform(Y.reshape(-1,1)).astype(y_dtype))
else: self.y = torch.from_numpy(Y.astype(y_dtype))
def __len__(self):
return len(self.y)
def __getitem__(self, idx):
return self.X[idx], self.y[idx]
创建一个简单的 PyTorch 模型
这里我们将创建一个相当简单的模型,因为这不是一篇关于特定问题类型的最佳模型类型的文章。这给你的是构建 PyTorch 模型的类的结构,你可以用你认为合适的任何东西改变/扩展/替换这些模型。
它以输入 X 形状 10(这将是我们在这些例子中使用的数据的大小)开始,并且可以有一个参数传递给它以形成最终的层(y 形状),默认为 1。
class BetterTabularModule(nn.Module):
def __init__(self, out_features=1):
super().__init__()
self.lin1 = nn.Linear(10, 200)
self.lin2 = nn.Linear(200, 70)
self.lin3 = nn.Linear(70, out_features)
self.bn1 = nn.BatchNorm1d(10)
self.bn2 = nn.BatchNorm1d(200)
self.bn3 = nn.BatchNorm1d(70)
self.drops = nn.Dropout(0.3)
def forward(self, x):
x = self.bn1(x)
x = F.relu(self.lin1(x))
x = self.drops(x)
x = self.bn2(x)
x = F.relu(self.lin2(x))
x = self.drops(x)
x = self.bn3(x)
x = self.lin3(x)
return x
设置一个简单的优化器
对于所有这些问题,我们将坚持使用 Adam 优化器。看起来很合适。
def get_optimizer(model, lr=0.001, wd=0.0):
parameters = filter(lambda p: p.requires_grad, model.parameters())
optim = torch_optim.Adam(parameters, lr=lr, weight_decay=wd)
return optim
现在一个简单的训练功能,训练循环和评估功能
这些是使用 PyTorch 的标准方法,所以让我们将它们放在函数中,并在整个过程中使用它们。唯一需要改变的是,是的,你已经猜到了,损失函数,它将作为一个参数传入,还有精度函数。
def train_model(model, optim, train_dl, loss_func):
# Ensure the model is in Training mode
model.train()
total = 0
sum_loss = 0 for x, y in train_dl:
batch = y.shape[0]
# Train the model for this batch worth of data
logits = model(x)
# Run the loss function. We will decide what this will be when we call our Training Loop
loss = loss_func(logits, y)
# The next 3 lines do all the PyTorch back propagation goodness
optim.zero_grad()
loss.backward()
optim.step()
# Keep a running check of our total number of samples in this epoch
total += batch
# And keep a running total of our loss
sum_loss += batch*(loss.item())
return sum_loss/totaldef train_loop(model, epochs, loss_func, lr=0.1, wd=0.001):
optim = get_optimizer(model, lr=lr, wd=wd) train_loss_list = []
val_loss_list = []
acc_list = [] for i in range(epochs):
loss = train_model(model, optim, train_dl, loss_func)
# After training this epoch, keep a list of progress of the loss of each epoch
train_loss_list.append(loss)
val, acc = val_loss(model, valid_dl, loss_func)
# Likewise for the validation loss and accuracy (if applicable)
val_loss_list.append(val)
acc_list.append(acc)
if acc > 0: print("training loss: %.5f valid loss: %.5f accuracy: %.5f" % (loss, val, acc))
else: print("training loss: %.5f valid loss: %.5f" % (loss, val))
return train_loss_list, val_loss_list, acc_listdef val_loss(model, valid_dl, loss_func):
# Put the model into evaluation mode, not training mode
model.eval()
total = 0
sum_loss = 0
correct = 0 for x, y in valid_dl:
current_batch_size = y.shape[0]
logits = model(x)
loss = loss_func(logits, y)
sum_loss += current_batch_size*(loss.item())
total += current_batch_size # All of the code above is the same, in essence, to Training, so see the comments there # However the functions to assess Accuracy change based on the type of problem we are doing.
# Therefore the following lines will make more sense as we progress through the article. # Accuracy for Binary and Multi-Class Classification
if loss_func == F.cross_entropy:
# Find out which of the returned predictions is the loudest of them all, and that's our prediction(s)
preds = logits.sigmoid().argmax(1)
# See if our predictions are right
correct += (preds == y).float().mean().item() # Accuracy for Multi-Label Classification
if loss_func == F.binary_cross_entropy_with_logits:
# Find out, of ALL the returned predictions, which ones are higher than our test threshold (50%), and they are our predictions
preds = logits
correct += ((preds>0.5) == y.bool()).float().mean().item() return sum_loss/total, correct/total
现在一个小函数来查看结果。
def view_results(train_loss_list, val_loss_list, acc_list):
plt.figure()
epochs = np.arange(0, len(train_loss_list))
plt.plot(epochs-0.5, train_loss_list) # offset by half an epoch as Training calculated mid epoch but val calculated at end of epoch
plt.plot(epochs, val_loss_list)
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'val', 'acc'], loc = 'upper left')
plt.show()
if acc_list[0]:
plt.figure()
plt.plot(acc_list)
plt.title('accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train','val', 'acc'], loc = 'upper left')
plt.show()
回归
好的,让我们从回归模型开始。具有 10 个特征的 1000 个样本,其中 8 个是信息性的(即,在模型/预测中实际有用的数量),让我们进行 80/20 训练测试分割。
X, y = make_regression(n_samples=1000, n_features=10, n_informative=8, random_state=1972)
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=1972
现在将它们加载到我们的 PyTorch 数据集,并在 PyTorch 数据加载器中将它们分成 256 个大小的批次。
train_ds = MyCustomDataset(X_train, y_train, scale=True)
valid_ds = MyCustomDataset(X_val, y_val, scale=True)batch_size = 256
train_dl = DataLoader(train_ds, batch_size=batch_size, shuffle=True)
valid_dl = DataLoader(valid_ds, batch_size=batch_size, shuffle=True)
如果我们愿意的话,我们可以看看这些,看看他们玩得好不好。
train_features, train_labels = next(iter(train_dl))
train_features.shape, train_labels.shape
(torch.Size([256, 10]), torch.Size([256, 1]))train_features[0], train_labels[0]
(tensor([ 0.8939, -1.0572, -2.1115, 0.9993, -0.4022, -0.7168, -0.1831, 0.3448, -0.6449, -0.4287]), tensor([0.5383]))
现在开始训练
回归是我们这里最简单的问题类型。
让我们建立我们的模型。对于输出目标的数量,我们可以坚持默认值 1。
我们这里的损失函数是 MSE 损失。(MSE 代表均方误差,基本上就是我们的预测与 y 目标相比有多“错误”)。这是回归问题的一个很好的起点,所以让我们用它来训练。
我们无法计算回归模型的准确性,因为回归模型的性能必须报告为回归预测中的错误。
model = BetterTabularModule()
train_loss_list, val_loss_list, acc_list = train_loop(model, epochs=10, loss_func=F.mse_loss)
view_results(train_loss_list, val_loss_list, acc_list)
很好,是吧?
分类—单一类别
好了,现在让我们来看一个分类模型。具有 10 个特征的 1000 个样本,其中 8 个是信息性的(也就是说,在模型/预测中实际有用的数量),让我们再次进行 80/20 训练测试分割。
X, y = make_classification(n_samples=1000, n_classes=2, n_features=10, n_informative=8, n_redundant=0, random_state=1972)
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=1972
现在将它们加载到我们的 PyTorch 数据集,并在 PyTorch 数据加载器中将它们分成 256 个大小的批次。
train_ds = MyCustomDataset(X_train, y_train)
valid_ds = MyCustomDataset(X_val, y_val)batch_size = 256
train_dl = DataLoader(train_ds, batch_size=batch_size, shuffle=True)
valid_dl = DataLoader(valid_ds, batch_size=batch_size, shuffle=True)
现在训练
二元分类(例如,是/否)比回归稍微复杂一点,但不会太复杂。
让我们建立我们的模型。我们需要为输出目标的数量传入 2。首先是答案是第一个选项的概率。第二个是它是第二个选项的概率。
我知道你在想什么。为什么不只有一个输出,并根据它接近 0 或 1 的程度给出我们的答案呢?是的,我们可以这样做,但我认为这种方式更好,原因将变得清楚。
我们这里的损失函数是交叉熵损失(F.cross_entropy ),它的基本功能是返回每个答案的概率。它通过在内部将 log_softmax(一条漂亮的 sigmoid 曲线中的所有值和整行值的总和为 1)和 nll_loss(根据适当的目标标签选择适当的损失)组合成一个函数来实现这一点。
对于分类,我们只关心哪个返回的预测是最响亮的,这就是我们的预测。我们可以使用 logits.argmax(1)来基本上说明 F.cross_entropy 的预测行中的哪一项是最大值。
然后,为了查看我们的预测是否正确,我们将所有预测与实际目标(y)进行比较,并返回正确的数字:(preds == y)。浮动()。平均值()。项目()。这是分类问题的一个很好的起点,所以让我们用损失函数进行训练,用准确度函数进行验证。
model = BetterTabularModule(2)train_loss_list, val_loss_list, acc_list = train_loop(model, epochs=100, loss_func=F.cross_entropy, lr=0.001, wd=0.001)view_results(train_loss_list, val_loss_list, acc_list)
一点都不差!!
分类—多类
好了,现在让我们来看一个多类分类模型。具有 10 个特征的 1000 个样本,其中 8 个是信息性的(即,在模型/预测中实际有用的数量),同样具有 80/20 训练测试分割。
X, y = make_classification(n_samples=1000, n_classes=3, n_features=10, n_informative=8, n_redundant=0, random_state=1972)
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=1972)
现在将它们加载到我们的 PyTorch 数据集,并在 PyTorch 数据加载器中将它们分成 256 个大小的批次。
train_ds = MyCustomDataset(X_train, y_train)
valid_ds = MyCustomDataset(X_val, y_val)batch_size = 256
train_dl = DataLoader(train_ds, batch_size=batch_size, shuffle=True)
valid_dl = DataLoader(valid_ds, batch_size=batch_size, shuffle=True)
现在列车
多类分类(比如狗/猫/马)比二元分类难多了。
笑话!!!一点也不,因为我们用二元分类法做的。想想看,我们建立的模型有两个输出目标。第一个是答案是第一个选项的概率。第二个是它是第二个选项的概率。
那么,我们真的需要现在就用更多的输出目标来构建模型,并让我们的损失函数和准确度与我们使用二元分类时完全相同吗?
没错。!
告诉过你这很容易。
model = BetterTabularModule(3) # Now we want 3 outputs
train_loss_list, val_loss_list, acc_list = train_loop(model, epochs=100, loss_func=F.cross_entropy, lr=0.001, wd=0.001)
view_results(train_loss_list, val_loss_list, acc_list)
分类—多标签
X, y = make_multilabel_classification(n_samples=1000, n_features=10, n_classes=3, n_labels=1, allow_unlabeled=False, random_state=1972)
# This returned mimicked one hot encoded data, but we need those as Floats, not Integers for our Accuracy Function
y = y.astype(np.float32)
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=1972)
现在将它们加载到我们的 PyTorch 数据集,并在 PyTorch 数据加载器中将它们分成 256 个大小的批次。
train_ds = MyCustomDataset(X_train, y_train)
valid_ds = MyCustomDataset(X_val, y_val)batch_size = 256
train_dl = DataLoader(train_ds, batch_size=batch_size, shuffle=True)
valid_dl = DataLoader(valid_ds, batch_size=batch_size, shuffle=True)
现在列车
我们这里的损失函数是 f . binary _ cross _ entropy _ with _ logits loss,它只是在 BCE 损失之前做一个 sigmoid,因此最终因为我们可以有一个以上最响亮的激活(因为它可以是多标签),所以我们希望让它们通过 sigmoid,并基于阈值来识别它们是否正确。即:正确+= ((preds>0.5) == y.bool())。浮动()。平均值()。项目()
model = BetterTabularModule(3)
train_loss_list, val_loss_list, acc_list = train_loop(model, epochs=50, loss_func=F.binary_cross_entropy_with_logits, lr=0.001, wd=0.001)
view_results(train_loss_list, val_loss_list, acc_list)
同样,我认为这在样本数据上训练和验证得非常好。
一路上的教训
在把这些放在一起的过程中,我遇到了各种各样的错误和问题,下面我整理了一些。其原因是,当你(希望)利用这一点创建自己的模型,改变损失函数或精度函数,甚至当然只是使用自己的数据时,你可能会遇到相同或类似的问题。
目标 2 出界。
- 这是因为当我创建我的模型时,我没有改变一个输出目标的默认值。所以一旦 a 尝试了一个分类模型,我就得到错误
通过将 2 传递给函数来建立我的模型,我正在纠正我的错误,并确保我的模型有两个可能的结果目标
我的训练看起来很平静,没有什么事情发生
- 我错误地没有重置我的模型,所以这个训练是在我已经做过的训练之上的,所以看起来根本没有什么不同
RuntimeError:张量 a (256)的大小必须与非单一维度 1 的张量 b (3)的大小相匹配
- 在多标签分类中,我使用 argmaxing 而不是 sigmoid,因此,对于所有目标,我没有获得介于 0 和 1 之间的良好概率,而是像我对标准分类所做的那样,再次选择最响亮的目标
结论
现在你知道了。一个简单的 PyTorch 用例的不同问题类型及其方法的例子,您可以根据您的问题需求对其进行扩展。
我希望这有所帮助。
Python 中数值积分的一种简单方法
在本文中,我们将介绍一种用 python 计算积分的简单方法。我们将首先导出积分公式,然后在 python 中的几个函数上实现它。这篇文章假设你对概率和积分有基本的了解,但是如果你没有,你可以直接跳到例子。尽情享受吧!
衍生物
我们必须首先说明连续随机变量的期望值的定义。
假设 X 是一个概率密度函数为 f(x)的随机变量。X 的期望值定义如下:
X 的期望值
接下来,我们使用期望公式来推导一个计算积分的简单方程。我们想估计以下积分:
函数 g(x)从 a 到 b 的积分
我们首先将积分改写如下:
然后,我们可以将函数 h(x)定义为:
这允许我们以熟悉的形式重写积分:
积分中的所有计算都被简化为一个期望值,我们知道如何找到一组数据的期望值。最终近似值变为:
Python 示例 1:积分初等函数
我们从简单的从 0 到 1 积分二次函数 f(x) = x 开始。
# Dependencies
import numpy as np
import scipy.integrate as integrate
import matplotlib.pyplot as plt# Our integral approximation function
def integral_approximation(f, a, b):
return (b-a)*np.mean(f)# Integrate f(x) = x^2
def f1(x):
return x**2# Define bounds of integral
a = 0
b = 1# Generate function values
x_range = np.arange(a,b+0.0001,.0001)
fx = f1(x_range)# Approximate integral
approx = integral_approximation(fx,a,b)
approx
我们的积分近似值为:
这是我们所期望的,因为积分的真实值是 1/3。然而,我们也可以将我们的结果与 Scipy 的“quad”函数进行比较。
# Scipy approximation
integrate.quad(f1,a,b)
结果值:
我确信这个例子让你坐立不安,但是让我们看看我们是否能集成一个更复杂的函数。
Python 示例 2:整合高斯函数
高斯函数因极难积分而臭名昭著。在这个例子中,我们将通过整合标准正态分布来测试我们的方法。
# Integrating a random function
np.random.seed(42)def gaussian(x, mu, sigma):
return np.exp((-1*(x - mu)**2) / (2 * sigma**2))# Define mu and sigma
mu = 0
sigma = 1# Define bounds of integral
a = -3
b = 3# Generate function values
x_range = np.linspace(-3,3,200)
fx = gaussian(x_range, mu, sigma)
结果函数如下所示:
我们的积分近似法的好处是,函数的复杂性确实会影响计算的难度。在任何情况下,我们需要的只是积分的边界和函数值。
# Our approximation
approx = integral_approximation(fx,a,b)
approx
与 Scipy 解决方案相比:
# Scipy approximation
integrate.quad(lambda x: np.exp((-1*(x - mu)**2) / (2 * sigma**2)),a,b)
我希望你觉得这篇文章简单易懂而且有趣!
谢谢大家!
喜欢我的文章吗?请我喝杯咖啡:https://www.buymeacoffee.com/HarrisonfhU
独立于模型的分数解释
如何为任何模型分数生成的案例级解释
T 机器学习(ML)应用的指数级增长以及模型在许多生产应用中的嵌入推动了对解释这些模型的可解释性和透明性的需求。在这篇文章中,我提出了一种简单的方法来提供对模型分数的案例级解释——即:哪些输入特征导致了特定案例的预测分数**、或记录的增加或减少?**
介绍
在数据科学中,我们处理统计和机器学习模型的广泛工具箱,从最简单的回归到最复杂的深度学习神经网络架构。此外,在当今数据丰富的世界中,具有数百个输入特征的模型并不罕见,这增加了通常产生单一输出信号的机器的整体复杂性,即指示某事是真还是假,或者预测连续测量。
在许多情况下,最终预测可以推动关键行动,或者为人类决策提供信息。例如,我在税务合规领域的 ML 应用领域进行了大量工作,使用模型自动对数百万份个人纳税申报单进行评分,以识别可疑的申报单,供进一步检查。在这种情况下,仅仅有一个模型级的特性重要性的解释是不够的(在许多开源包中都有);相反,我们需要一些案例层次的解释来解释模型预测。具体来说,如果给定输出类的概率很高(或很低),我们需要提供一个我们认为导致模型做出特定决定的要素列表。这可以帮助用户获得对预测的信心,并帮助推动进一步的决策。
ML 模型被使用的地方越多,对可解释性的需求就越大。例如,在大多数欺诈检测应用中,能够支持拒绝单个交易的决策是非常重要的。显然,这同样适用于大多数类型的信用评分,在这种情况下,重要的贷款决策是在整体模型评分的基础上做出的。通常,分数解释对于最终用户获得对模型的信心并推动采用是至关重要的。当 ML 模型被引入到历史上依赖于专家知识和直觉的现有过程中时,这是特别真实的。
不是每个 ML 模型都需要可解释性。当模型驱动高度自动化的决策时,如流程控制、直接营销、内容推荐等。在应用程序的调优和调试过程中,模型输出的案例级解释可能更有用,但对操作来说用处不大。
一般来说,当分数影响到个别案例时,解释是有用的。当模型分数只影响一个大规模的过程时,单独的解释只适用于在过程中具有启发性或历史性专业知识的人。
这些仅仅是多希-维勒兹和金在这篇论文中陈述的 ML 模型的可解释性的一般问题的几个例子,该论文提供了该问题的一般框架和定义。
可解释的模型?
在数据科学中可用的各种模型类型中,有些被认为比其他模型更适合“解释”——但是我们真的能这样说吗?比如分类,我能想到的最基本的框架大概就是 Logistic 回归模型。这里,除了最后的非线性单调变换,预测基本上是根据输入的线性函数计算的。
因此,可以用每个特征的系数乘以特征本身的值的乘积来量化每个特征对最终得分的贡献。够简单吗?事实上,在大多数情况下,这将是可行的,尤其是在某些假设为真的情况下,即特征是独立的(无共线性)和标准化的,因为偏差项可能会使模型的解释变得复杂。当一个模型有许多特征时,完全没有共线性是不可能的,因此,即使是最简单的可用模型的可解释性也成问题。
决策树,至少在其最简单的形式中,也被认为是“可解释的”,因为它们可以从数据中推断出定义良好的决策规则。同样,这在理论上是正确的,特别是如果我们控制树的深度,从而限制每个规则中的前提条件的数量。但是在实践中,为了使模型达到合理的准确性,决策树通常会产生大量的“深层”规则。除此之外,规则是人类可读形式的事实并不总是意味着这些规则也是可解释的。
虽然一般来说,更简单类型的模型和具有更少变量的模型可能更容易解释,但这种准确性/可解释性权衡在给定的应用程序中可能不容易协商,或者至少不需要一些努力。例如,我经常发现自己不得不“简化”复杂的模型,纯粹是为了可解释性。虽然执行模型缩减对于获得泛化和限制过度拟合很重要,但是缩减模型以使其更易解释并不总是容易的,或者是可能的。即使驯服一个“简单”的逻辑模型来减少变量,降低共线性,并使系数更容易解释,同时保持其准确性,也是一项艰巨的工作。
案例级模型解释技术
在我提出这个高度简化的方法来提供模型输出的案例级解释之前,让我们回顾一下文献中已经提出的一些更复杂的技术。
作为一般背景,我强烈推荐谷歌关于人工智能可解释性的白皮书,它很好地概述了这个主题,并讨论了在谷歌云中实现的解决方案(充分披露:我在谷歌工作,但这不是为了宣传我们的服务,我只是认为这是一篇很好的文章)。具体来说,本白皮书中用于描述在实例级别“解释”ML 模型分数的能力的术语是特征属性 (FA)。FA 是一个有符号的连续值,表示在分数的特定实例中,任何单个输入要素对模型输出的贡献程度。
谷歌用于 FA 的技术是基于由劳埃德·沙普利首先引入的博弈论概念,被称为沙普利值(SV)。由 Christopher Molna r 撰写的关于可解释机器学习的优秀在线书籍中有一节专门介绍了 Shapley Values ,该书对这种方法进行了出色的概述。SV 方法有几个优点:
- 它是独立于模型的:它可以应用于任何“黑盒”模型,因为它只需要对从数据集中抽取的例子进行评分的能力。
- 有一个清晰的解释:对于一个给定的实例,每个输入特征的 SV 表示实例得分与总体平均得分之间的差异部分,这可以通过该特征假设其特定值来解释。
- 完整性:保证所有特征的所有 SV 之和与分数的边际差与其基线相匹配。
这种方法的缺点是计算 Shapley 值所涉及的计算复杂性随着特征的数量呈指数增长,使得它不可能以其原始形式应用于现实世界的问题。然而,一些方法可以通过采样来近似计算 SV。
另一种流行的模型解释方法是 LIME ,最早出现在 2016 年。在高层次上,LIME 通过用可解释的模型(例如,回归)局部近似任何黑盒模型来工作。这是通过用从黑盒模型生成的数据训练近似模型来完成的,通过从被解释的实例的值扰动其输入来实现。LIME 也是一种独立于模型的方法,但是它对问题采取了与 SV 方法完全不同的态度。石灰和 SHAP (一种基于沙普利原理的方法)的比较见本帖。
请注意,上面提到的方法比这篇文章中提到的方法要复杂得多,理论上也更加合理。然而,我们的方法很容易实现,并且在实践中运行良好。对于负责根据分数采取行动的人来说,这也可能更容易理解。也就是说,我会确保强调那些可能不太好的情况。
一种简单的独立于模型的方法
给定一个模型 M ,让我们假设我们可以对数据集 X 进行评分,该数据集包含一个记录样本,该样本包含 M 所需的所有输入特征。该数据集可以是训练数据本身,模型 M 基于该数据集进行估计,或者是保留集,或者是完全不同的样本。唯一的要求是 X 提供一个合理的输入空间表示,在这个空间内 M 将在生产中运行。
接下来,我们将 M 应用于数据集 X ,产生分数 Y 的向量。注意,我们不需要目标的实际真值,也不需要知道关于 M 的任何细节——我们需要给定输入数据集 X 由 M 产生的分数。
对于每个输入特征 J ,我们做如下操作:
- 对于分类特征:计算 J 的每个不同值的 Y 的平均值。我们还可以计算标准偏差,因为它提供了额外的有用信息。
- 对于连续/数值特征:应用任何合理的宁滨方法(如等频宁滨)首先离散变量 J 。然后,计算与分类变量相同的分数统计。
然后我们计算一个比率, Zj ,即 Y 的平均值与JY的总体平均值之间的比率。该比率表示当输入 J 采用特定值 Ji 时,整个数据集中得分的平均变化。我们还可以计算出 J = Ji 时得分的标准差与 Y 跨总体的标准差之比。姑且称这个值为 Vj 。
上述过程的输出可以看作是一个简单的查找表。作为一个例子,这里我们将一个简单的逻辑回归模型(我们可以使用任何其他类型的模型)拟合到众所周知的 UCI 人口普查收入数据集。然后,我们为模型的每个输入计算了 Zj 和 Vj 值。结果示例如下所示,由于篇幅限制,我只显示了 11 个模型输入中 4 个的计算值。请注意,对于连续输入,如年龄和教育编号*,添加离散化仅用于此评估方法——这些输入不是为了创建模型而离散化的。事实上,这种方法完全独立于输入是如何在模型“内部”具体转换的,因为它只取决于模型在给定输入向量的情况下生成的分数。颜色编码有助于识别那些对增加(绿色)或减少(红色)模型分数贡献最大的输入/值组合。*
UCI 人口普查收入数据集模型的模型说明表示例(图片由作者制作)
给定一个实例 I ,我们可以根据每个输入 J 的对应值 检索*一组对应的 Zj 和 Vj 值。第一组数字, *Zj,代表输入 J 假设值 Ji 对分数的平均影响;高于 1.0 的值表示 J = Ji 增加分数(正贡献),低于 1.0 的值表示反而有助于降低分数(负贡献)。【Zj】值的相对比例为我们提供了对【J】**输入的贡献差异的指示,例如 I 。
因此,给定一个实例 I ,我们可以检索 Zj 分数,并使用它们根据它们的值对输入特征进行排序。同样,大于 1.0 的因子代表正贡献,而小于 1.0 的因子代表负贡献。接近 1.0 的 Zj 值表示中性贡献。下面的示例显示了所选数据集的特定记录的模型输入的分级效果,该模型为此产生了很高的分数。
示例 1:高分案例(作者制作的图片)
在第一个示例中,输入的关系排名最高,因为它对获得高于平均水平的分数贡献最大,职业和婚姻状况紧随其后。虽然输入 fnlwgt 和本国在列表的底部,但是请注意 Zj 分数接近 1.0,这意味着这些输入对该记录分数起着中性作用,而不是负的作用。**
下一个例子是模型产生相对较低的概率分数 0.18 的情况。请注意,在这种情况下,输入的排序完全改变了。相对于这个例子,贡献最大的特性是教育数量和每周小时数;然而,在这种情况下,最高的贡献因子仅略高于 1.0,因此接近于“中性”贡献。另一方面,该图解释了低得分的主要驱动因素,在这种情况下,是职业和工作类,它们的 Zj 得分在 0.6 左右,推低了整体模型得分。
示例 2:低分案例(作者制作的图片)
Vj 值为实例 I 提供了关于每个输入效果的附加信息。具体来说,它通知我们关于对分数的贡献的可变性,以及因此由 Zj 表示的信息的可靠性。 这也解决了这种方法的主要弱点和局限性——让我们假设,事实上,当 J = Ji 时,分数的分布 Y 实际上是双峰的。
双峰分布的一个例子(来源:Statology.org)
在这种情况下,得分***【Y】***的平均值将不能恰当地捕捉特征 J 假设值 Ji 对模型输出的典型影响。实际上,类似于这里描述的情况将意味着模型实际上正在捕捉输入特征 J 和一个或多个其他输入特征之间的一些高度非线性的关系。这肯定是可能的,尽管并不常见。所提出的方法将完全不能代表输入特征贡献是强负还是强正的事实,这取决于其他输入特征的值。相反,这是像 SV 或 LIME 中实现的局部估计这样的计算强度大得多的方法可以明确解决的问题。
然而,通过测量当 J 取值时得分 Y 的可变性,并将其与得分的总体可变性进行比较,我们至少可以意识到这样的情况。如果得分可变性相对较高,如在双峰分布的示例中, Vj 值将增加,这表明与具有可比较的 Zj 值但具有较低的 Vj 值的特征相比,效果的差异较大。因此,例如,我们可以使用**【Vj】**值基于 Zj 值对条形图进行颜色编码,以捕捉特征属性的方向、强度和一致性。
下图提供了模型输入对分数、一致性、可变性或贡献方向的影响的可视化表示示例。
颜色编码的影响图:条形的长度表示对更高分数的贡献强度,而相邻单元格的颜色表示这种贡献的可靠性或一致性(图片由作者制作)
结论
在数据科学中,我们学会处理的许多权衡之一是准确性与复杂性。正如在特征归属的已建立方法的简要概述中所讨论的,存在复杂的方法,其可以在个体实例水平上提供模型分数的更准确的“解释”,包括 Shapley 值和 LIME。虽然这些方法肯定会为更复杂和非线性的模型提供更精确的结果,但这里建议的独立于模型的方法实施起来非常简单,并且可以提供一种快速的解决方案,将“解释因素”添加到模型得分报告中。
我要感谢 Paul McQuesten 在这篇文章的准备过程中对我的支持,特别是在制作这些例子时的帮助。
一个简单的用于歧义消解的 NLP 应用
如何利用 expert.ai 自然语言处理技术解决同形词和多义词的歧义问题
马库斯·斯皮斯克在 Unsplash 上的照片
歧义是自然语言处理中最大的挑战之一。当我们试图理解一个词的意思时,我们会考虑几个不同的方面,例如使用它的上下文,我们自己对世界的了解,以及一个给定的词在社会中通常是如何使用的。词汇会随着时间的推移而改变意思,在某个领域可能是一个意思,在另一个领域可能是另一个意思。这种现象可以在同形词和多义词中观察到,同形词是指两个单词恰好以相同的方式书写,通常来自不同的词源,而多义词是指一个单词有不同的意思。
在本教程中,我们将看到如何使用 expert.ai 技术解决词性标注和语义标注中的歧义。
开始之前
请查看如何安装 expert.ai NL API python SDK,要么在这篇关于数据科学的文章上,要么在官方文档上,这里。
词性标注
语言是模糊的:不仅一个句子可以用不同的方式来表达相同的意思,就连词条——一个被认为不那么模糊的概念——也可以表达不同的意思。
例如,单词 play 可以指几种不同的事物。让我们来看看下面的例子:
我真的很喜欢这部戏。我在一个乐队里,我弹吉他。
不仅同一个词可以有不同的意思,而且它可以用在不同的角色中:在第一句中, play 是名词,而在第二句中它是动词。给每个单词分配正确的语法标签被称为词性标注,这并不容易。
让我们看看如何用 expert.ai 解决 PoS 歧义性—首先,让我们导入库并创建客户端:
我们将看到两个句子的词性标注——注意,在两个句子中,词条键是相同的,但其词性发生了变化:
为了分析每个句子,我们需要创建一个对 NL API 的请求:最重要的参数——也显示在下面的代码中——是要分析的文本、语言和我们请求的分析,由资源参数表示。
请注意,expert.ai NL API 目前支持五种语言(en、it、es、fr、de)。我们使用的资源是消歧,它作为 expert.ai NLP 管道的产品,执行多级标注。
事不宜迟,让我们创建第一个请求:
现在我们需要迭代文本的位置,并检查哪个被分配给了词条键:
Part of speech for "The key broke in the lock."
The POS: DET
key POS: NOUN
broke in POS: VERB
the POS: DET
lock POS: NOUN
. POS: PUNCT
上面打印的是跟随 UD 标签的 PoS 列表,其中名词表示词条关键字在这里用作名词。我们在第二个句子中看到的同形异义词不应该是这种情况,其中键用作形容词:
Part of speech for "The key problem was not one of quality but of quantity."
The POS: DET
key POS: ADJ
problem POS: NOUN
was POS: AUX
not POS: PART
one POS: NUM
of POS: ADP
quality POS: NOUN
but POS: CCONJ
of POS: ADP
quantity POS: NOUN
. POS: PUNCT
正如您在上面看到的,在这个句子中,词条键被正确地识别为一个形容词。
语义标注
一个单词也可以有相同的语法标签,也可以有不同的意思。这种现象被称为一词多义。能够推断每个单词的正确含义就是执行语义标记。
更常见的单词往往有更多的及时添加的含义。比如,引理纸可以有多种含义,这里看到:
我喜欢在纸上做笔记。每天早上,我丈夫都会阅读当地报纸上的新闻。
指出每个词条的正确含义是一项重要的任务,因为一个文档可能会基于此改变含义或焦点。要做到这一点,我们必须依靠发达和强大的技术,因为语义标记严重依赖于来自文本的许多信息。
对于语义标记,通常使用 ID:这些 ID 是概念的标识符,每个概念都有自己的 ID。对于同一个引理,比如论文,我们会用某个 id x 表示其作为材料的意义,用另一个 y 表示其作为报纸的意义。
这些 id 通常存储在知识图中,在知识图中,每个节点是一个概念,而拱形是遵循特定逻辑的概念之间的连接(例如,如果一个概念是另一个的下位词,则拱形可以链接两个概念)。现在让我们看看 expert.ai 是如何执行语义标记的。我们首先选择句子来比较两个引理解:
现在是对第一句话的请求—使用与上一个示例相同的参数:
语义信息可以在每个令牌的 syncon 属性中找到:syncon 是一个概念,存储在 expert.ai 的知识图中;每个概念由一个或多个引理构成,这些引理是同义词。
让我们看看信息是如何在文档对象中呈现的:
Semantic tagging for "Work out the solution in your head."
Work out CONCEPT_ID: 63784
the CONCEPT_ID: -1
solution CONCEPT_ID: 25789
in CONCEPT_ID: -1
your CONCEPT_ID: -1
head CONCEPT_ID: 104906
. CONCEPT_ID: -1
每个令牌都有自己的 syncon,而其中一些表示为-1 作为概念 id:这是分配给没有任何概念的令牌的默认 ID,比如标点符号或文章。
因此,如果对于前一个句子,我们获得了引理解的概念 id 25789,那么对于第二个句子,我们应该获得另一个概念 id,因为这两个引理在两个句子中具有不同的含义:
Semantic tagging for "Heat the chlorine solution to 75° Celsius."
Heat CONCEPT_ID: 64278
the CONCEPT_ID: -1
chlorine CONCEPT_ID: 59954
solution CONCEPT_ID: 59795
to CONCEPT_ID: -1
75 CONCEPT_ID: -1
° Celsius CONCEPT_ID: 56389
. CONCEPT_ID: -1
不出所料,引理解对应的是不同的概念 id,说明使用的引理与上一句的意思不同。
请在 GitHub 上找到这篇文章作为笔记本。
结论
NLP 很难,因为语言是模糊的:一个单词、一个短语或一个句子根据上下文可能有不同的意思。利用 expert.ai 等技术,我们可以解决歧义,并在处理词义时建立更准确的解决方案。
强化学习的简单概述
让我们来一次人工智能和控制相遇的旅行
“我们想要的是一台能够从经验中学习的机器。”艾伦·图灵,1947 年
你知道机器(或计算机)是如何在像国际象棋和围棋(DeepMind 的 AlphaGo、AlphaZero 和 MuZero)这样的复杂游戏中超越人类的表现,或者在没有人类干预的情况下驾驶汽车的吗?答案隐藏在它们的编程方式中。这种机器被编程为最大化某些目标,这些目标是由人类明确定义的。这种方法被称为强化学习,完全模仿人类和动物在世界中学习行为。
在这篇文章中,我的目的是解释什么是强化学习和基础知识,而不是太多的细节。
是什么让强化学习如此特别?
今天,机器学习由 3 个主要范例概括。除了有监督学习(SL)和无监督学习(UL),强化学习(RL)形成了第三种也是最后一种。虽然它是机器学习的一个子领域,但它有控制理论和博弈论的缺点。
我们可以把智能代理想象成一种功能,当输入(或观察)时,期望它给出有用的输出(或动作)。学习只是越来越接近给出一个正确的(或有用的)输出。
在二语习得中,智能体被输入和相应的正确输出所反馈,以使它学会给看不见的输入合理的输出。
UL 只给智能体输入信息,并让智能体学会给出一些输出信息,从而优化一个预定义的目标,如紧密度或熵之类的度量。
当代理需要通过在动态环境中反复试验来学习行为时,RL 的问题就出现了。这就是人类和动物如何学习他们的行为,如说话和运动。
学习类型的图解,照片由 IBM 拍摄
不像上面所说的,学习方法不需要严格区分。如果从更大的角度来看,它们都是基于某些目标的优化。近年来,许多成功的人工智能应用是这三种范式的混合,如自监督学习、逆强化学习等。最近在 ML 上的成功隐藏在定义适当的目标,而不是选择 3 种学习类型中的一种。
但是,RL 仍然是唯一的,因为它处理的是动态环境,数据不是事先给定的。因此,与 SL 和 UL 不同,RL 代理应该:
- 收集数据本身,
- 探索环境(学习如何收集有用的数据),
- 利用其知识(学习如何考虑未来状态,学习最佳策略),
- 辨别后果的原因(过去的哪些行为导致了当前的情况),
- 在探索过程中保持自身安全(对于机械应用)。
勘探开发困境
如上所述,代理应该利用它的知识来获得最优策略。然而,如果没有足够的环境知识,agent 可能会陷入局部最优解。因此,特工应该同时探索环境。这就出现了勘探-开发的困境,这是 RL 的重要组成部分。许多算法有各种技术来平衡它们。
代理人应该在哪里吃饭?,照片由加州大学伯克利分校 CS188 AI 课程
现在,让我们进入技术细节。
顺序决策
顺序决策或离散控制过程是在离散时间的每个时间步做出决策,考虑环境的动态性。
在时间 t,我们代理人在 sₜ,受到 rₜ奖励,得到 oₜ观察,并根据其政策 π 采取行动 aₜ。作为行动的结果,sₜ₊₁发生状态转变,新观察 oₜ₊₁以回报 rₜ₊₁.
期望观察值代表代理的状态。如果状态可以使用即时观察形成,观察直接用作状态(sₜ=oₜ),这被称为马尔可夫决策过程。如果不能,则称之为部分可观测马尔可夫决策过程,因为即时观测不能完全告知主体状态。然而,我们现在专注于 MDP。
马尔可夫决策过程(MDP)
MDP 由以下部分组成:
- 状态空间 S,作为所有可能状态的集合。
- 动作空间 A,作为所有可能动作的集合。
- 模型函数 T(s’|s,a),作为状态转移概率。
- 奖励函数 R(s),作为从状态、动作、下一个状态元组到奖励的奖励映射。
- 贴现因子γ ∈ [0,1],一个实数,决定未来奖励对控制目标的重要性。
马尔可夫决策过程,作者图片
RL 的建筑单元
- 策略函数π(a|s):取决于状态的行动的概率函数,指示在特定情况下如何行动。
- 回报 G:未来奖励在时间上的累积总和,按折现因子γ缩放。它被定义为:
返回值
- 价值函数 V(s|π):状态 s 下遵循策略π时的期望收益,定义为;
价值函数
- 动作值函数 Q(s,a|π):遵循策略π时的期望收益,除了状态 s 第一步的动作 a,定义为;
动作值函数,又名 Q 函数
贝尔曼方程
RL 的整体目标是最大化价值函数 V,以获得最优策略π*。为此,价值函数必须满足以下贝尔曼方程。贝尔曼方程告诉我们,最优策略必须以隐含的方式使所有可能状态的平均价值函数(未来的累积报酬)最大化。
贝尔曼方程
你可能想知道为什么我们需要 Q 函数。它与政策有着直接的关系。
Q 函数的策略推导
注意,两者都是相互依赖的。那么,为什么我们需要另一个定义呢?通过观察环境和行为的结果,代理可以学习当前策略的 Q 函数。然后,代理可以使用这个等式来改进它的策略。它允许代理在学习 Q 函数的同时,通过学习来改进策略。
无模型强化学习
无模型 RL 纯粹基于经验,没有模型和奖励函数。
蒙特卡罗方法
蒙特卡罗方法使用统计抽样来逼近 Q 函数。为了使用这种方法,必须等到模拟结束,因为每个状态都使用将来的累积奖励总和。
一旦 sₜ被访问,并采取行动 aₜ,回报 Gₜ是计算从其定义使用即时和未来奖励等待结束的一集。蒙特卡罗方法旨在最小化所有可能样本的 Q(sₜ,aₜ和目标值 Gₜ之间的差距。
利用预定的学习速率α,Q 函数被更新为:
蒙特卡洛 Q 更新
时间差分法
目标返回的时间差分(TD)方法 bootstrap Q 函数估计。这允许代理从每个奖励更新 Q 函数。
主要的 TD 方法是 SARSA 和 Q 学习。
- SARSA 最小化 Q(sₜ,aₜ和目标值 rₜ₊₁+γ Q(sₜ₊₁,aₜ₊₁之间的差距,目标值是收益的自举估计。因为还需要下一个动作,所以它被命名为 SARSA,表示状态、动作、奖励、状态、动作序列。学习率为α时,Q 更新为:
SARSA Q 更新
- q 学习最小化差距 Q(sₜ,aₜ)和目标值 rₜ₊₁+γ最大 Q(sₜ₊₁),这也是一个收益的自举估计。与 SARSA 不同,它不需要下一个动作,并假设在下一个状态采取最佳动作。学习率为α时,Q 更新为:
Q 学习 Q 更新
基于模型的强化学习
在基于模型的强化学习中,模型和奖励函数要么预先给定,要么通过 SL 方法学习。
基于模型的 RL 依赖于学习模型的质量。
动态规划
动态编程不需要采样。它是在给定模型和报酬函数的情况下解析求解最优策略。
动态编程 Q 更新
基于模拟的搜索
有时,状态和动作空间太大。这使得动态编程不适用。在这种情况下,会生成随机模拟路径。其余的是通常的无模型 RL,可以使用蒙特卡罗或时间差分方法。这种方法唯一优点是代理从它想要的任何状态开始模拟。
蒙特卡洛法、时间差分法和动态规划法之间的比较如下所示。
三种主要方法的备份图,照片由大卫·西尔弗的 RL 课程,第 4 讲拍摄
结论
在大多数应用中,RL 是通过称为深度强化学习的深度学习来结合的。最近 RL 的成功与神经网络有关。然而,由于样本学习效率低和安全限制,RL 难以适应真实世界的场景。RL 的未来取决于我们对人类学习方式的掌握。
总的来说,RL 不过是在动态环境中学习。有许多 RL 算法和方法,但我试图给出核心定义和算法,让初学者有一个关于它的想法,保持定义尽可能简单。我希望你喜欢!
作为临时演员,我把 OpenAI 的捉迷藏模拟留给 RL。
寻找商品碳强度的简单 Python 指南
使用 ResourceTradeEarth 和 ClimateTrace 数据计算碳强度
文件和数据
ClimateTrace
资源贸易地球:
此会话中使用的 GitHub 数据集
外卖食品
建立一个排放估算模型是非常具有挑战性的,即使应用于高质量的数据集,由于必须做出大量的假设。由于缺乏准确性、清晰度和对数据的理解,这种分析很难用于决策目的。
目前的方法是从交易的重量/价值,而不是生产/消费,得出碳强度。因此,这将导致对碳强度的更高估计。
它将主要作为将来如何使用 Python 对类似数据集进行分析的指南。
介绍
排放数据一直很难估计。迄今为止,大多数排放清单都是基于自我报告的。报告的排放数据往往有很大的时滞,估算的方法也往往相互不一致。Climate TRACE 的任务是使用人工智能(AI)和机器学习(ML)技术为卫星/传感器数据提供每个部门的最新排放数据。
据推测,碳排放可以通过关于每种商品的生产水平和每个不同国家在生产商品时的局部碳强度的足够信息来准确确定。然而,这两种数据都很难获得。
我的方法是,通过将发现的排放量与交易的商品进行对比,找到与商品相关的平均碳强度,从而逆向计算方程式。
数据分析
助手函数和库
import warnings
import numpy as np
import pandas as pd
import plotly as py
import seaborn as sns
import statistics as stat
from datetime import dateimport plotly.express as px
import plotly.graph_objs as go
warnings.filterwarnings("ignore")
pd.set_option('display.max_columns', None)import plotly.offline as pyo
pyo.init_notebook_mode()import matplotlib.lines as lines
import matplotlib.pyplot as plt%matplotlib inline
显示/隐藏按钮
import random
from IPython.display import HTMLdef hide_toggle(for_next=False):
this_cell = """$('div.cell.code_cell.rendered.selected')""" ; next_cell = this_cell + '.next()';
toggle_text = 'Code show/hide' # text shown on toggle link
target_cell = this_cell ; js_hide_current = ''if for_next:
target_cell = next_cell; toggle_text += ' next cell';
js_hide_current = this_cell + '.find("div.input").hide();'
js_f_name = 'code_toggle_{}'.format(str(random.randint(1,2**64)))html = """<script>
function {f_name}() {{{cell_selector}.find('div.input').toggle(); }}
{js_hide_current}
</script>
<a href="javascript:{f_name}()">{toggle_text}</a>
""".format(f_name=js_f_name,cell_selector=target_cell,js_hide_current=js_hide_current, toggle_text=toggle_text )
return HTML(html)hide_toggle()
ClimateTrace 的排放数据
file = 'climatetrace_emissions_by_subsector_timeseries_interval_year_since_2015_to_2020.csv'df = pd.read_csv(PATH + file)df[‘year’] = pd.DatetimeIndex(df[‘start’]).year
df[‘month’] = pd.DatetimeIndex(df[‘start’]).monthco2 = pd.DataFrame( df.groupby(['sector', 'subsector', 'year'])['Tonnes Co2e'].sum() ) \
.reset_index()
# wow magic linepd.set_option('display.max_rows', 250)
co2
作者图片
def make_bar_plot(data, xdata, ydata, cdata, title, legend=True, width=900, height=600):
import plotly.express as pxfig = px.bar(data,
x= xdata,
y= ydata,
color= cdata,
)fig.update_layout(
title= title,
xaxis_tickfont_size=14,
yaxis=dict(
title='',
titlefont_size=16,
tickfont_size=14,
),
legend=dict(
x=0.995,
y=0.98,
bgcolor='rgba(255, 255, 255, 0)',
bordercolor='rgba(255, 255, 255, 0)',
),
showlegend= legend,
height= height,
width= width,
barmode='group', #'stack',
bargap=0.25,
bargroupgap=0.1
)fig.show()
hide_toggle()
按国家
make_bar_plot(df[ df['country'].isin(['USA']) & df['year'].isin([2015, 2016, 2017, 2018, 2019]) ],
xdata='year',
ydata='Tonnes Co2e',
cdata='sector',
title='Co2 Emissions by USA')
作者图片
图表中的数字应该与 Climate TRACE live 网站上的数字完全一样。我们可以尝试更进一步,深入到每个部门和子部门。
按部门和分部门
make_bar_plot(co2[ co2['year'].isin([2015, 2016, 2017, 2018, 2019]) ],
xdata='year',
ydata='Tonnes Co2e',
cdata='sector',
title='Co2 Emissions by Sector')
作者图片
make_bar_plot(co2[ co2['sector'].isin(['agriculture']) & co2['year'].isin([2015, 2016, 2017, 2018, 2019]) ],
xdata='year',
ydata='Tonnes Co2e',
cdata='subsector',
title='Co2 Emissions by Subsector (Agriculture)')
作者图片
资源贸易地球的商品贸易数据
ResourceTradeEarth 按年份提供商品交易价值和重量的数据。该网站是由查塔姆研究所开发的,他们的数据是从国际商品贸易统计(IMTS)中获得的精确版本。
随后,IMTS 数据由国家海关当局收集,并由联合国统计司编入联合国商品贸易统计数据库。
现在我们已经有了来自 Climate TRACE 的排放数据,我们的目标是通过将商品部门从 Climate race 结果产生的排放量除以从 ResourceTradeEarth 获得的交易价值/重量结果来估计每种商品的排放强度。
export = pd.read_csv(PATH + SUBPATH + 'rte-exporters-2019.csv',
encoding= 'ISO 8859-1')
importe= pd.read_csv(PATH + SUBPATH + 'rte-importers-2019.csv',
encoding= 'ISO 8859-1')commodities = pd.read_csv(PATH + SUBPATH + 'rte-commodities-2019.csv',
encoding= 'ISO 8859-1')commodities.drop(['Exporter M.49', 'Exporter ISO3', 'Exporter region',
'Importer M.49', 'Importer ISO3', 'Importer region'], axis=1, inplace=True)trend = commodities.sort_values(by=['Resource', 'Year'], ascending=True)# Create differenced column
trend['dvalue_share'] = trend.groupby('Resource')['value share'].diff()
trend['dweight_share'] = trend.groupby('Resource')['weight share'].diff()
按重量绘图
make_bar_plot(commodities, 'Year', 'Weight (1000kg)', 'Resource', 'Weight (1000kg) Traded by Commodities')
作者图片
按价值/重量份额绘图
使用以下等式估算以下变量:
价值份额 _2015 =资源价值 _2015 /价值总和 _2015
权重份额 _2015 =资源权重 _2015 /权重总和 _2015
这个想法是不同的商品在生产和交易过程中有不同的碳强度。我们想知道我们的商品贸易中有多大比例是高碳密度的。
make_bar_plot(commodities, 'Year', 'value share', 'Resource', 'Value Share By Traded Commodities')
作者图片
make_bar_plot(commodities, 'Year', 'weight share', 'Resource', 'Weight Share By Traded Commodities')
作者图片
按商品和年份分列的价值/重量份额的变化
如果企业的目标是利润最大化,同时排放最少,一些人可能会对按交易价值计算的碳强度感兴趣。然而,这很容易被大宗商品价格扭曲。
如果一种商品的交易价值份额下降,而交易重量份额保持不变或上升,这意味着价格对该商品越来越不利。
trend
作者图片
def make_line_plot(data, xdata, ydata, cdata, title, legend=True, width=900, height=600):
import plotly.express as pxfig = px.line(data,
x= xdata,
y= ydata,
color= cdata,
symbol= cdata,
)fig.update_layout(
title= title,
xaxis_tickfont_size=14,
yaxis=dict(
title='',
titlefont_size=16,
tickfont_size=14,
),
legend=dict(
x=1.02,
y=0.98,
bgcolor='rgba(255, 255, 255, 0)',
bordercolor='rgba(255, 255, 255, 0)',
),
showlegend= legend,
height= height,
width= width,
)fig.show()hide_toggle()
价值份额变化
make_line_plot(trend,
'Year',
'dvalue_share',
'Resource',
'Value Share Change By Traded Commodities')
作者图片
make_line_plot(trend[ ~trend['Resource'].isin(['Pearls and gemstones']) ],
'Year',
'dweight_share',
'Resource',
'Weight Share Change By Traded Commodities')
作者图片
估算商品的碳强度
co2 = pd.DataFrame( df.groupby(['sector', 'year'])['Tonnes Co2e'].sum() ) \
.reset_index() # wow magic line# Agriculture
agri = trend[ trend['Resource'].isin(['Agricultural products']) ]
agri2 = co2[ co2['sector'].isin(['agriculture']) & ~co2['year'].isin([2020]) ]
有了排放和交易数据的信息,我们可以将它们结合到一个数据框架中。这里,我们假设:
Agricultural products
RTE 的商品与agriculture
部门的气候微量排放相关。Fossil fuels
商品与oil and gas
排放有关。Metals and minerals
商品与extraction
排放有关。
agriculture = pd.concat([agri.reset_index(drop=True),
agri2['Tonnes Co2e'].reset_index(drop=True)],
axis= 1)
agriculture['co2 vintensity'] = agriculture['Tonnes Co2e'] / agriculture['Value (1000USD)']
agriculture['co2 wintensity'] = agriculture['Tonnes Co2e'] / agriculture['Weight (1000kg)']# Oil and gas
oil = trend[ trend['Resource'].isin(['Fossil fuels']) ]
oil2 = co2[ co2['sector'].isin(['oil and gas']) & ~co2['year'].isin([2020]) ]fossilfuel = pd.concat([oil.reset_index(drop=True),
oil2['Tonnes Co2e'].reset_index(drop=True)],
axis= 1)
fossilfuel['co2 vintensity'] = fossilfuel['Tonnes Co2e'] / fossilfuel['Value (1000USD)']
fossilfuel['co2 wintensity'] = fossilfuel['Tonnes Co2e'] / fossilfuel['Weight (1000kg)']# Metals and Minerals
metal = trend[ trend['Resource'].isin(['Metals and minerals']) ]
metal2 = co2[ co2['sector'].isin(['extraction']) & ~co2['year'].isin([2020]) ]extraction = pd.concat([metal.reset_index(drop=True),
metal2['Tonnes Co2e'].reset_index(drop=True)],
axis= 1)extraction['co2 vintensity'] = extraction['Tonnes Co2e'] / extraction['Value (1000USD)']
extraction['co2 wintensity'] = extraction['Tonnes Co2e'] / extraction['Weight (1000kg)']# All commodities
allcomm = pd.concat([agriculture, fossilfuel, extraction], axis=0)
allcomm
作者图片
make_line_plot(allcomm,
'Year',
'co2 wintensity',
'Resource',
'Carbon Intensity by Weight, by Commodities')
作者图片
make_line_plot(allcomm,
'Year',
'co2 vintensity',
'Resource',
'Carbon Intensity by Value, by Commodities')
作者图片
结果不令人满意。以下是一些问题:
- 金属和矿物的碳强度数据非常低,你实际上从图表中看不出来。原因是来自气候跟踪的开采部门的 CO2 排放数据没有更新,特别是来自煤炭开采部门的数据。
- 化石燃料和金属的碳密度数据非常低。尚不清楚我们是否还应包括同样受到这些商品影响的其他部门的排放量。(例如:化石燃料的电力和运输,金属的制造)。
- 由于碳强度来源于交易的重量/价值,因此没有考虑国内消费的商品,导致对碳强度的估计要高得多。在我们的图表中,农产品碳强度的上升趋势可能意味着国内对农产品的消费将会增加。
- 同一种商品的碳强度在很大程度上取决于生产地。由于垂直农业技术,与欧洲生产的农产品相比,非洲农业单位产量的碳密度可能更高。
虽然我们最终没有做出任何有意义的结论,但我希望这能激励其他人采取更好的方法来处理这个问题。
如果您有任何错误或建议希望引起我的注意,请随时留下任何评论。
参考:
[1]查塔姆研究所(2021),’ resourcetrade.earth ',https://resourcetrade.earth/
[2]气候追踪(2021 年),https://www.climatetrace.org/
带有 AWS Lambda + Surprise 的简单无服务器协作过滤器
我们将回顾一个使用 Python 的 Surprise 库的内容推荐协作过滤器的简单部署。
图片由朱莉安娜·贝尔纳尔提供
介绍
在电子商务、社交媒体和其他应用中,需要将全部内容的子集与特定用户匹配,以最大化转化率、应用内时间或其他所需指标,这是一个常见要求。
确定向给定用户推荐什么内容的一种可能的方法是推荐被表现出类似内容评级倾向的对等体良好评级的项目。这种方法被称为协同过滤。有几种不同的方法来计算不同项目(视频、产品或其他要推荐的内容)的用户评级之间的相似性。然而,对于这个部署,我们将使用 Python 的 SciKit Surprise ,它带有内置算法。
协同过滤通常分 3 步进行:
- 数据的收集和预处理。
- 模型的训练。
- 生成预测。
在接下来的部分中,我们将讨论如何实现这些步骤。
架构和设置
现有的应用程序堆栈,增加了用于存储结果的 Lambda 和 S3。
先决条件
在我们深入 Lambda 的代码之前,我们需要设置 Lambda 的运行环境。关于要添加推荐算法的应用程序的设置的一些假设:
- 该应用程序运行在一个虚拟专用集群(VPC) 中。
- 训练模型所需的数据存储在可从 VPC 内部访问的 SQL 数据库中。
- 应用服务器可以访问(或者可以被允许访问)S3。
鉴于上述假设,我们可以设置我们的 Lambda,以便它从 SQL 数据库中读取数据,并将结果输出到 S3。这确保它从应用服务器异步运行,如果训练和预测是缓慢的过程,这可能是所期望的。
λ配置
我们将使用 SAM 来部署 Lambda 及其依赖资源。关于山姆的更深入的介绍,请看下面的文章。
https://medium.com/better-programming/private-serverless-rest-api-with-lambda-using-sam-2eb31864b243
先说文件夹结构。我们将有一个应用程序源文件目录,一个用于部署应用程序的脚本目录,以及 SAM 使用的顶层文件,如template.yaml
。
my-collaborative-filter/
├── cmd/
│ └── deploy.sh
├── src/
│ └── app.py
├── .gitignore
├── test-event.json
└── template.yaml
template.yaml
将如下图所示。
它定义了 3 种资源:
RecommendationFunction
是将执行协同过滤的实际 Lambda。- 如果您的数据库限制访问,可能需要一个
RecommendationFunctionSecurityGroup
。可以将数据库安全组配置为允许来自该特定安全组的访问。 OutputBucket
将是我们输出结果的 S3 桶。
注意RecommendationFunction
中的Layers
规格。由于 Surprise 和psycopg 2(PostgreSQL adapter)不是 Lambda 操作系统包含的 Python 库之一,我们需要在能够将其导入 Lambda 之前添加它。
在下面的文章中,我们将讨论创建这样一个层的可能过程。
https://medium.com/better-programming/creating-a-python-opencv-layer-for-aws-lambda-f2d5266d3e5d
如果您更喜欢在不使用 Docker 的情况下构建该层,您可以使用 EC2 实例在 Python 虚拟环境中安装 Suprise,并将文件复制到该层。在 EC2 实例上安装库的命令如下所示:
python3 -m venv lambda_layer_packages
source lambda_layer_packages/bin/activate
pip install psycopg2 numpy scikit-surprise
λ代码
将放入app.py
文件中的完整 Lambda 代码如下所示,带有内嵌注释。
部署
要部署 Lambda,只需运行下面的部署脚本:
sam deploy \
--template-file template.yaml \
--stack-name recommender-stack \
--parameter-overrides RdsPassword=YOUR_PASSWORD \
--guided
限制
由于该实现被设计为简单的,所以讨论它的一些限制以及如何改进它是很重要的:
最大λ时间限制
截至撰写本文时,Lambda 的最大执行时间为 15 分钟。在包含 5000 个项目和 5000 个用户的数据集上运行接近超时限制。
大部分时间花在预测步骤,因为代码需要为每个用户执行。一旦到达的用户数量足够大,使得 Lambda 超时,我们可以让上面描述的主 Lambda 只执行步骤 1 和 2,并将输出模型(通过pickle)保存到 S3 桶中。然后它会为每个用户排队(使用 SQS )一个作业,这样另一个 Lambdas 系列可以运行并做出预测。
最大磁盘空间
Lambda 被限制在 512MB 的/tmp
目录上的最大临时磁盘空间。如果作为 CSV 的数据集超过(或接近)这个数量,您可能需要为 Lambda 附加一个额外的卷,或者更改代码,以便数据集完全存储在内存中。
新用户预测
上述设置只会对至少有 1 个评分的用户进行预测。根据您的设置,有些用户可能没有评级。
如果需要为这些用户推荐,可以推荐全球最好的商品。这可以完全作为一个单独的服务来完成,或者(取决于为协作过滤选择的算法)您可以对不存在的user_id
进行预测(例如用户 0 ),并且每当没有对您当前正在寻找的用户的预测时,默认使用该服务。
下面的代码将这个功能添加到现有的 Lambda 中。
非实时
这种方法以一个固定的速率重新训练模型,这个速率至多需要 Lambda 运行的频率。因此,它不适用于必须能够向用户提供自适应推荐的系统。
结论
我们已经回顾了在 AWS 的 Lambda 上使用 Python 的 Suprise 库部署一个简单的推荐算法。该方法可用于测试算法的性能,而无需投入资源来实现全面的解决方案。
如果你有任何问题或只是想聊聊创业、创业、承包或工程,请给我发电子邮件:Paulo @https://avantsoft.com.br/。
避免内存不足的简单技巧
在不丢失数据的情况下减少熊猫内存使用的艺术
当您处理大型数据集时,会面临内存不足的风险。
内存不足的错误尤其令人沮丧,因为在你耐心地等待程序加载完你所有的数据后,你的程序突然崩溃了。
内存不足错误可能需要等待很长时间,然后才发现你的程序已经崩溃了。西格蒙德在 Unsplash 上拍照。
幸运的是,在使用 Python 和 Pandas 时,有大量的最佳实践来克服这个障碍,尤其是在 Itamar Turner-Trauring 的这篇优秀的参考文献中。
本文重点介绍一种简单而有效的技术,即改变 pandas 数据帧的数据类型,使其更有效地存储。
去哪里找
我们将很快讨论如何在 Python 中实现这一点,但是首先,让我们考虑一下幕后发生了什么。
最常见的情况是,三种主要的数据类型可以更有效地表示,因为 Pandas 过于谨慎,分配的内存类型常常容纳比实际需要更多的数据。
漂浮物
您的计算机可以表示特定精度级别的浮点数,这可能远远超出您的需要。如果内存是一个问题,问问你自己你的算法是否需要看到 52 位小数的数字。
NumPy 有各种各样的浮点数据类型,您可以在数据帧中指定自己喜欢的精度级别。
你要求的最低精确度是多少?照片由沃尔坎·奥尔梅斯在 Unsplash 上拍摄。
整数
假设你有人们鞋码的数据。在英国,它们通常介于 2 和 15 之间,所以即使考虑到偶尔的篮球运动员,拥有一个可以存储高达 9223372036854775807 的值的 NumPy 数据类型也是浪费内存。
如果您正在测量鞋号,您真的需要一个可以达到 9223372036854775807 的数据类型吗?Julian Hochgesang 在 Unsplash 上拍摄的照片。
如果您对数据所属的典型范围有很好的感觉,那么您可以选择一种只适应它需要的数据类型。另外,如果你不确定这些数字的范围,你可以使用有用的功能 iinfo 。
用线串
有时你有字符串(或熊猫称之为对象),它们只来自一个独特的可能的值集合。例如,您的数据可能会根据一个固定的列表[‘蓝色’,‘棕色’,‘绿色’,‘其他’]记录一个人的眼睛颜色。事实上,眼睛的颜色可能更微妙,但让我们用这个例子来说明。
将这些表示为字符串是很好的,但是当它们只能是四个值中的一个时,它需要占用内存来一次又一次地存储所有的字符。
简单来说,一个人眼睛的颜色大多属于一个固定的可能颜色列表。Alex Iby 在 Unsplash 上拍摄的照片。
幸运的是,Pandas 有一个分类数据类型,它将每个可能的值映射到一个整数,这样它就不需要每次存储字符串本身来浪费内存,例如{‘blue’: 0,’ brown’: 1,’ green’: 2,’ other’: 3}。
付诸实践
为了执行这些检查,我编写了一个名为pandas _ dtype _ efficiency的库,可以通过 pip 安装:
pip install pandas_dtype_efficiency
功能很简单:
- 取一个现有的数据帧
- 检查潜在的内存改进
- 将改进应用于现有的数据帧,或者将建议的数据类型存储为字典,以便在加载数据帧时使用。
这里是它的一个短暂停留之旅,但是你可以随意查看这个例子的更多细节这里。
熊猫 _ dtype _ 效率在行动。作者 GIF。
下次您处理一些大数据时,试试这个库;根据您的数据帧,它可以将它们减少到原始内存使用量的 10%左右。
当你在这里的时候
请随意查看我的其他文章:
获取股票基本面数据的简单方法
我如何使用 Python 和 API 调用来检索公司的财务报表和收益报告
获取或搜集股票数据有时说起来容易做起来难。找到包含适当的相关数据的正确资源或网站可能相当困难。在我的数据科学项目中,在过去几年中,我需要使用许多不同的数据集,其中相当一部分涉及股票数据和分析。
根据我的经验,股票的价格历史是最容易找到和检索的数据之一。通常,我会使用 Yahoo Finance 和附带的 Python 库来访问这些历史价格数据。然而,我发现对于一只股票的基本面数据来说,情况并非如此。
为了找到基本数据,比如财务报表和收益报告,你可能需要进行大量的谷歌搜索和烦人的网络搜索。在我之前的一个项目中,我幸运地发现了一个现已关闭的网站,名为 stockpup.com。这个网站包含了数千种股票的数千行基本面数据。它们都被整齐地组织成一个简单的 CSV 文件,供我下载并用于我的机器学习项目。
由于那个网站不再可用,我不得不在其他地方寻找数据…
财务数据 API
有许多金融数据 API 可以让你访问股票的基本数据。就我个人而言,我发现并使用了一个网站,它不仅能够提供基本数据,而且可以免费注册,这个网站叫做——【eodhistoricaldata.com】,也被称为 EOD 高清。披露:我从通过上面的链接购买的任何商品中赚取一小笔佣金。
使用这个财务数据 API,我能够从数千家公司检索基本数据。数据已经准备好并可用,但现在我需要将它组织并格式化成我能够使用的熊猫数据框架。为此,我利用 Python 做好了一切准备:
# Libraries
import pandas as pd
from eod import EodHistoricalData
from functools import reduce
from datetime import datetime, timedelta# Importing and assigning the api key
with open("../eodHistoricalData-API.txt", "r") as f:
api_key = f.read()
# EOD Historical Data client
client = EodHistoricalData(api_key)
有了我提供的 API 键和上面的代码,我就可以开始检索和格式化基础数据了。
获取基础数据
我使用的 API 能够为我提供季度基础数据,如资产负债表、损益表、现金流和收益报告,但它们都整齐地存储在 API 的 return 对象中各自的部分。我需要创建能够访问的功能,然后将所有这些数据整合到一个大的数据框架中。
所以我创建了一个函数,它能够将这些独立的数据转换成它们自己的数据帧,然后将它们合并成一个大的 DF:
在这个函数中,我能够从给定的股票报价机中检索基本数据。如您所见,每个财务报表都存储在我需要访问的特定属性中。一旦我将它们存储到各自的数据框架中,我就可以通过将它们合并在一起来整合它们。我还删除了任何多余的列,比如在我删除它们之前一直重复的“ Date 列,以及任何重复的列。
获取历史价格数据
我发现返回的基本面数据中缺少的一点是当时的股价。我认为这是一条重要的信息。API 也能够很容易地检索历史价格,所以这不成问题,但是我需要将这些价格与我上面创建的数据框架结合起来。
我创建了一个能够检索每日历史价格的函数,并将它们添加到更大的数据框架中:
当我最初将价格添加到数据框架中时,我发现了一个问题,即在较大的 DF 中,某些日期的一些价格数据丢失了。我假设这些日期有时是假日、周末或类似的日子。不管怎样,我只是想知道在财务报表报告日期前后的股票价格。它不需要很精确,事实上,它可能就在报告日期的前一天或后两天。
正如你在上面的函数中看到的,我可以填写周末、假期等。以之前的股价。之后,我将它们添加到最终返回的更大的数据帧中。
获取基本面和价格数据
因为我能够创建两个函数来检索基本面和价格数据,所以我决定将它们压缩成一个函数:
在这个函数中,我合并了前两个函数。我还添加了额外的数据清理选项,提供了在更大的 DF 中删除空值的选择。如果您需要在此数据上训练机器学习模型,并且无法使用数据集中的 n a 值,则此选项可能会很有用。
从多只股票中获取数据
前面的函数在从一个给定的股票行情自动收录器中检索基本数据时都很有用,但是如果我想从不止一只股票中检索数据呢?为此,我创建了以下函数,它允许从多只股票中检索基本面数据:
这是一个相对简单的函数,利用了上面的函数。我需要做的第一件事是交叉引用 API 中可用的代码。这样做是为了防止给定的 ticker 不存在或给得不正确。然后,如果给定的报价器是有效的,那么它将从所有给定的股票中检索基本面数据,并将它们组合成一个更大的 DF,其中包含多个股票的基本面数据。
结束语
通过使用 Python 和这个金融数据 API,我能够轻松地检索几乎任何股票的基本数据。除了对格式化过程进行编码,整个任务相当简单。
随着多只股票的基本面数据准备就绪,我现在可以将这个数据集应用于任何未来的数据科学项目。可以对该数据集使用分类或回归 ML 模型。或者使用 Python 的众多可视化库进行简单的分析。有各种各样的项目可以使用这样的数据集。
开源代码库
一种在 Google BigQuery 中查询表元数据的简单方法
轻松确定 BigQuery 数据集中的内容,以及哪些表对使用 INFORMATION_SCHEMA 和表进行分析有用
作者照片(使用 Canva.com 创建)
元数据!我打赌你以前可能听说过这个术语,并且可能问过自己它是什么,为什么它很重要。让我们用 Google BigQuery 探索一下这个概念。
在这篇文章中,你会发现:
- 什么是元数据?
- 为什么要在 Google BigQuery 中查询表元数据?
- 如何用 INFORMATION_SCHEMA 和表查询表元数据?
什么是元数据?
许多来源将元数据定义为“关于数据的数据”。但我个人觉得太模糊,难以理解。所以我尝试用通俗的语言来定义元数据。
作者照片(使用 Canva.com 创建)
为什么要在 Google BigQuery 中查询表元数据?
想象一下在 BigQuery 中给我们一个包含很多表的庞大数据集,我们应该查询哪个?我们如何识别哪些表是最新的?每个表是什么时候创建的?每个表中有哪些列?信不信由你,所有这些问题都可以用元数据来回答。
当涉及到查询表元数据时,有许多潜在的解决方案,从 Google Cloud Console 中的简单视图到更复杂的客户端库。但是这里有 3 个最简单和最不需要技术的解决方案。
第一个解决方案是查看数据目录,这是一个元数据集合,旨在帮助我们搜索所有可用的表,评估它们的质量和有用性,然后访问我们认为适合我们分析的任何表。这可能是确定我们应该查询哪些特定表的最简单的方法。但是,如果我们手边没有现成的数据目录,那该怎么办呢?怎么样,棕色的母牛?
作者照片
如上所示,第二个选项在 Google Cloud Console 中很容易找到。是的,我说的是与 BigQuery 下每个表相关的**“细节”和“模式”选项卡。然而,如果我们必须一个接一个地点击几十个(甚至几百个)表,这种解决方案将会引发严重的问题。一定有更好的方法,对吗?**
第三个解决方案是在这里扭转乾坤。你猜怎么着?我们可以使用 INFORMATION_SCHEMA 或 TABLES 元表轻松获得跨多个数据集的所有表元数据,所有这些都可以通过熟悉的 BigQuery 接口进行简单的 SQL 查询。这就是我们将在下一节讨论的内容。大伙儿,击鼓吧!让我们开始吧。
如何查询表元数据
关于所有表的概述
有两个选项可以获得数据集中所有表的概览。下面列出了这两个选项。
选项 1。信息 _ 模式视图
INFORMATION_SCHEMA 是一系列视图,提供对有关数据集、例程、表、视图、作业、预订和流数据的元数据的访问。
——来自谷歌云
在编写第一个查询之前,记住以下两点是至关重要的。
- 针对 INFORMATION_SCHEMA 视图的查询不会被缓存,而会产生数据处理费用(10MB)或消耗 BigQuery 槽 ,这取决于与您的项目相关的定价。
- INFORMATIONS_SCHEMA 查询必须用标准 SQL 语法编写。
列出所有表格
让我们开始探索 BigQuery 公共数据中的以太坊区块链数据集。我们将从 2 个简单的问题开始: 数据集中有多少个表?那些桌子是什么?
我们可以通过运行一个简单的查询来轻松获得答案。
# List all tables and their creation time from a single dataset#standardSQL
SELECT *
FROM `bigquery-public-data.ethereum_blockchain`.INFORMATION_SCHEMA.TABLES;
作者照片
查看查询结果,首先,这个数据集下有 14 个表。每个表格对应一行,以及以下各列。根据 GCP 文档,下面是每个列的含义的快速概述。
- 包含数据集的项目的项目 ID
- 包含表和/或视图的数据集的名称
- 属于指定数据集的所有表的名称
- 指示表是普通的 BigQuery 表(也称为基表)、视图、实体化视图还是引用外部数据源。
- 指示表是否支持 SQL INSERT 语句
- 该值始终为否
- 创建表的日期时间
使用 WHERE 子句列出数据集中选定的表
这次我们只对 获取包含令牌信息 的以太坊区块链数据集中 BigQuery 表的表名和创建时间感兴趣。好吧,让我们添加一个相关的 WHERE 子句来过滤我们想要的结果。Tada!下面是查询和结果。
# List metadata from selected tables with WHERE clause
#standardSQL
SELECT table_name, creation_time
FROM `bigquery-public-data.ethereum_blockchain`.INFORMATION_SCHEMA.TABLES
WHERE table_type = "BASE TABLE"
AND table_name LIKE "%token%";
作者照片
选项二。表元表
到目前为止还不错,但是很明显,仅仅 INFORMATION_SCHEMA 视图不足以帮助我们 根据大小识别最大的表或者根据“最后修改时间” 识别最新的表。别急,伙计们!因为在每个数据集中,都有一个隐藏的表,其中包含关于每个表的更多元数据。幸运的是,可以在<project name . dataset . name>访问这个隐藏的表。__ 表格 _ _
重要提示:您需要在“表格”的每一侧键入 2 条下划线。
让我向您介绍一个非常有用的查询,它利用这个**表元表来获取我们数据集中所有表的大小、行数和上次修改时间。**对我来说,从谷歌云培训的 Coursera 课程中学习这个查询绝对是一种福气。我们走吧,各位!
#standardSQL
SELECT dataset_id, table_id, # Convert size in bytes to GB
ROUND(size_bytes/POW(10,9),2) AS size_gb, # Convert creation_time and last_modified_time from UNIX EPOCH format to a timestamp
TIMESTAMP_MILLIS(creation_time) AS creation_time, TIMESTAMP_MILLIS(last_modified_time) AS last_modified_time,
row_count, # Convert table type from numerical value to description
CASE
WHEN type = 1 THEN 'table'
WHEN type = 2 THEN 'view'
ELSE NULL
END AS type
FROM `bigquery-public-data.ethereum_blockchain`.__TABLES__
ORDER BY size_gb DESC;
作者照片
但是,如果您想列出多个数据集中的所有表及其详细信息,该怎么办呢?您可以使用 UNION ALL 遍历每个数据集,类似于这个 GCP GitHub 中列出的查询。
放大列
让我们仔细看看以太坊区块链数据集中的所有数据列。我们将发现 每个表中有多少列,并识别分区列或聚集列 。
列出所有列
这个查询再简单不过了。
# List all columns related to all tables within a dataset
SELECT table_name, column_name, is_nullable, data_type, is_partitioning_column
FROM `bigquery-public-data.ethereum_blockchain`.INFORMATION_SCHEMA.COLUMNS;
作者照片
这里,我在 SELECT 子句中指定了 5 个属性,因为我只对获得列名、数据类型、为空性以及该列是否用于分区感兴趣。请随意让 SELECT *尝试查看包含数据列元数据的所有其他属性。
仅过滤用于分区的时间戳列
如果我们需要识别当前用于分区的所有时间戳列,该怎么办?让我们扭曲上面的查询来得到我们想要的。
#standardSQL
SELECT table_name, column_name, is_nullable, data_type, is_partitioning_column
FROM `bigquery-public-data.ethereum_blockchain`.INFORMATION_SCHEMA.COLUMNS
WHERE data_type = "TIMESTAMP"
AND is_partitioning_column = "YES";
作者照片
离别的思绪
我们处理大数据越多,我们决定哪些表值得我们研究,哪些表可以忽略的时间就越短。虽然本文仅仅触及了在探索 BigQuery 数据集的元数据时可以用表、元表和 INFORMATION_SCHEMA 视图做些什么的皮毛,但我希望它可以作为一个良好的起点。如果您热衷于使用 INFORMATION_SCHEMA 视图探索关于数据集、流、作业等的元数据,请不要忘记查看 GCP 文档。
感谢您的阅读。对我如何能做得更好有反馈,或者只是想聊天?请在评论中告诉我,或者在 LinkedIn 上找到我。祝大家这周过得愉快!
原载于 2021 年 2 月 1 日【http://thedigitalskye.com】。