将数据科学作为职业(数学模型)
我写了一篇关于成为一名数据科学家所需的技能和知识的概述。在这篇文章中,我将写更多的细节,我的维恩图中的一个领域,那就是数学模型。
简介
如果你看看数据科学,我们实际上正在使用数学模型来建模(并希望通过模型来解释我们看到的一些事情)业务情况、环境等,通过这些模型,我们可以获得更多的见解,例如我们所做决策的结果,我们接下来应该做什么,或者我们应该如何做才能提高胜算。因此,数学模型非常重要,选择正确的模型来回答业务问题可以为组织带来巨大的价值。
1 —线性代数&微积分
是的,首先也是最重要的,就像你见过的大多数回归的功夫电影(主角被一个大坏老板打败,当他穷困潦倒时,找到一个聪明的老师教他功夫,学习功夫,然后打败大坏老板),老师总是从基础开始。
线性代数和微积分被认为是最基础的。鉴于我们所处的“深度学习”环境,这一点尤其正确。深度学习要求我们理解线性代数和微积分,理解它是如何工作的,例如向前传播、向后传播、参数设置等。有一个好的基础有助于我们理解这些模型是如何工作的,做了哪些假设,参数是如何得出的。早在我的大学时代,我学习线性代数和微积分,但直到现在才发现这与我的工作有关联。真希望我在这上面花了更多的时间。
那么,潜在的数据科学家应该学习什么呢?
对于线性代数,有矩阵运算(加、减、乘、除)、标量积、点积、特征向量和特征值。
对于微积分,数据科学家需要理解各种微分(对二阶导数)、积分、偏导数。在浏览一些材料时,他们确实会触及数学级数,如泰勒级数。如果你有兴趣了解更多关于数学级数的知识,维基百科相当全面。检查连杆。
当我们考虑设计机器学习/统计模型的损失函数、正则化和学习速率时,大量使用微积分和线性代数。
2 —统计数据
嗯,在做分析的时候,一个人怎么能逃避统计呢,这就不需要进一步介绍了。根据经验,当我们打算做实验和测试时,需要了解统计数据,例如在市场营销中,我们有 A/B 测试。我们通常希望了解两个样本之间是否有任何统计差异,或者在某些“处理”之后,它是否产生了统计上显著的效果。
因此,统计学的领域是简单的统计学,如中心性、分布和不同概率分布(威布尔、泊松等)的测量,贝耶定理(以后学习人工智能时会非常强调),假设检验等。
3 —机器学习/统计模型
我本科的时候学的是计量经济学,是最接近机器学习/统计模型的一门学科。在那项研究中,我遇到了线性回归和逻辑回归。该模块涵盖了非常多的回归模型假设,即异方差、自相关、E(e) = 0 和多重共线性。这些假设之所以重要,是因为在训练模型时,我们试图获得所谓的 BLUE(最佳线性无偏估计)参数,即包括截距在内的系数。
但是当我转到机器学习模型时,对于回归模型的课程,不再强调这些假设,而是非常强调建立损失函数,这是正则化、梯度下降和学习速率背后的基本原理。
回来,学习机器学习模型是任何数据科学家都必须要做的事情,因为他们需要提出可以帮助为组织提供见解的机器学习模型。数据科学家需要转换给定的业务目标,并将它们转化为机器学习模型,以获得答案和见解。
通常有两种类型的机器学习模型,监督和非监督学习模型。
监督学习模型
假设你有两组数据。集合 A 具有周期 1 中的行为数据和周期 2 中的结果。集合 B 只有周期 3(或 2)中的行为数据,但在周期 4(或 3)中没有任何结果。
使用集合 A,你将训练一个模型,仅仅通过观察行为,就能够“预测”(或给出概率)结果。训练出模型后,您将对行为数据进行“评分”,并尝试“预测”(或有一个概率)可能的结果。
可以使用的模型称为监督学习模型。它是受监督的,因为 Set 的结果“监督”了模型,以得出好的预测值。
无监督学习模型
所以你可能已经猜到,对于无监督学习模型,集合 A 数据上没有“结果”,它通常不与集合 B 数据一起使用。事实上,无监督学习模型只是试图找出集合 A 中的模式,这些模式是由模型的训练算法识别的。
从业务目标转移到建模目标
对监督和非监督模型有了很好的理解,数据科学家将需要知道在每个给定的业务目标中,使用哪些机器学习模型,如何使用它们,以什么顺序使用它们,以便实现给定的业务目标。我见过的许多培训项目通常使用单一模型来实现业务目标,例如,在创建电子邮件营销响应模型时,要么选择逻辑回归,要么选择决策树或支持向量机来构建。这就造成了一个盲点,即每个业务目标都有一个机器学习模型,而实际情况并非如此。
推荐使用哪些模型以及基于业务目标构建建模目标的能力来自于经验。因此,对于任何潜在的数据科学家来说,一定要开始研究它。
模特培训
在机器学习中,你遇到的每个机器学习模型都有许多“旋钮”和“开关”,供你在模型训练过程中调整或翻转。这些“旋钮”和“开关”被称为超参数。具有良好数学背景的数据科学家对于如何转动这些“旋钮”和“开关”以获得“最佳”模型有很高的理解能力。事实上,如果他们有良好的背景,他们可能会提出自己的损失函数并建立自己的随机梯度下降方法,这是训练不同机器学习模型(主要是监督的)的两个关键组成部分。
型号选择指标
大多数情况下,我们可以训练几个不同的模型(给定目标和超参数),然后我们需要了解模型选择指标是如何计算的,以及它们喜欢哪种模型。
选择最佳模型并不总是仅仅基于准确性,因为在现实生活中,错误预测正面的成本可能与错误预测负面的成本大不相同。例如,在一种流行病中,可以减少假阴性的测试比高度准确的测试更需要,因为它会得到很多真阴性。
4 —运筹学
我们都在一个需要不断做出决策的环境中工作。能够部署数学模型来帮助做出更好的决策是运营研究的目的。运筹学的一些例子是什么?分别是最优化、 博弈论、预测、排队论、 模拟、图论等。当然,运筹学还包括统计/机器学习模型,以帮助对商业环境进行建模,以便做出合理的决策。这完全是一个数学领域,需要大量非统计学的研究。
我相信数据科学家应该能够使用这些模型,使用数据支持的参数,以便做出“更好”的决策,帮助业务组织实现其业务目标。
结论
最终,数据科学家应该精通数学和统计学,为他/她建立数据科学职业生涯打下最好的基础。我认为,数据科学家最基本的技能是数学知识,能够将业务目标或挑战转化为数学模型,并使用这些模型作为做出最佳决策的基础。
还有人认为编程是一项基本技能,我不否认这一点,但我认为编程是必要的,因为我们现在使用计算机来处理我们拥有的大量数据。想象一下,如果没有数学知识来理解如何对环境建模,那么编程技能对数据科学家有多大用处?
我希望这能让任何考虑从事数据科学职业的人了解,为了成为一名数据科学家,他们需要知道哪些数学知识。在我的下一篇文章中,我将会谈到数据& IT 管理和领域知识。
**边注:**我想既然你们中的一些人正在阅读这篇文章,你们可能会有兴趣知道吴恩达认为在人工智能和机器学习中强大所需的数学是什么。这里是链接。
祝您的数据科学学习之旅愉快,请务必访问我的其他博客文章和 LinkedIn 个人资料。
**补充说明:**作为我 2020 年新年计划的一部分,我已经建立了自己的网站,最新消息请访问这里。感谢支持!
从 Python 迁移到 Go
过去两年我一直在使用 Python,它在很多方面都是一种非常方便的语言。它可以用于 web 开发(使用 Flask 或 Django)、机器学习(使用 nltk 或 scikit-learn)、数据可视化(使用 seaborn 或 matplotlib)和深度学习(使用 TensorFlow、keras)。Python 是一种非常适合初学者的简洁的书面语言。然而,它也有局限性——例如速度不够快、GIL、动态类型和非内置并发支持。最近,我被要求拿起 Go 进行一个查询理解项目(我已经在我的其他博客中写了这个)。
Moving from Python to Go
我对 Go 的第一印象是它有多罗嗦。铅字散落一地,没有课。已经掌握了一些 Python 语言,在 Go 中从头开始被证明是一个挑战。一些博客比较了 Python 和 Go,我将只关注语法部分。我听说过很多关于静态类型的东西,但我不知道我实际上是在做什么。事不宜迟,我们继续吧。
1.静态打字
令人惊讶的是 Python 对你隐藏了多少类型。你基本上是这样开始编写函数的:
def add_numbers(num1, num2):
return num1 + num2
现在你会期望这个函数的用户会给你发送两个整数,你可以把它们相加。但是,如果他们发送字符串呢?或者字典?对于字符串,这段代码仍然有效,但是“1”、“2”将返回“12”而不是 3。这可能会导致更复杂的程序出现许多令人痛苦的错误,这些程序可能很难调试 esp。因为 Python 只会在运行时告诉你这些。
在围棋中同样的程序是:
func add_numbers(num1 int, num2 int)int{
return num1 + num2
}
我们不能向该函数发送两个字符串或其他数据类型。如果 Go 需要两个整数,它将确保调用这个函数的每一行都发送两个整数。当 Go 的二进制文件被编译时,这些问题就可以被捕捉到,而不是等到生产中的运行时——因此节省了很多问题!欢迎来到静态打字的魔力!
2.结构和 JSON 解析
对于任何数据科学项目来说,JSON 解析都是非常重要的。围棋没有课。它的传播方式是使用结构以及接口和方法。对于像 Go 这样的静态类型语言来说,结构非常有用,因为结构预先明确了我们可以使用什么类型。让我们举一个例子:
{ "John" : {"id": 12345, "department": "Human Resources"},
"Sarah": {"id": 3, "department": "Software Engineering"},
"Omar": {"id": 145, "department": "Marketing"}
}
要从上面的 JSON 中获取所有雇员的姓名,Python 中的代码将是:
# Code in Python
import jsondef find_names():
with open("example.json", "r") as outfile:
data = json.load(outfile)
names = [name for name, details in data.items()]
return names
围棋中同样的代码是:
package mainimport (
"fmt"
"io/ioutil"
"json"
)func find_names()[]string{
type EmployeeDetails struct{
Id string `json:"id"`
Department string `json:"department"`
} var employee_data map[string]EmployeeDetails // Go's dictionary
data, _ := ioutil.ReadFile("example.json")
err := json.Unmarshal(data, &employee_data) // Use of pointer
if err!=nil{
fmt.Println("error:", err) // Easy handling of errors
} var names []string
for name, _ := range employee_data{
names = append(names, name)
}
return names
}
Python 和 Go 在解析 JSON 方面的一些关键区别:
- 在 Go 中,您需要知道您在 JSON 中解析的每个字段的数据类型。如果数据类型被混淆,Python 代码将保持不变,但是 Go 的代码将随着
interface{}
和类型断言等的使用发生相当大的变化。Go 是一种强类型语言,在设计 API 时最好记住这一点。 - Go 提供了开箱即用的错误处理。如果 json 中的
Unmarshal
函数有问题,就会创建上面的err
。在 Python 中,错误或异常处理更加谨慎,很容易被忽略。 - 围棋中没有列表理解。Python 在这方面很容易使用,但是在 Go 中,每个片段都需要显式分配数据类型,代码也更加冗长。
- 在 Go 中,指针允许用户明确控制是否要修改底层对象。在 Python 中,不清楚函数是就地修改传入的数据,还是使用数据的副本。在 Go 中,使用
&employee_data
可以清楚地表明我们想要修改底层变量。 - 在 Go 中,未使用的变量和未使用的导入是不行的——不像 Python。它们需要被替换为
_
(下划线)。我觉得这使得代码可读性更好。
对于混合数据类型,您需要接口。此外,如果您不想使用 structs 来自动让 JSON 的Unmarshal
遵循数据类型,您将需要几个类型断言,这两个我们将在接下来研究。
3.接口和类型断言
对于来自 Python 的我来说,接口是最棘手的部分。实际上,我更害怕指针,但是令人惊讶的是,到目前为止它们还可以,因为 Go 只是使用指针来给用户更多的变量控制。
虽然 Go 是一种强类型语言,但接口允许 Go 在数据类型上有一些模糊性。接口既可以是type
也可以是set of methods
。当你写一个以interface{}
作为参数的函数时,你可以给这个函数提供任何值——这很棘手,因为这并不意味着任何类型。interface{}
本身就是一个类型。阅读该堆栈溢出讨论以全面理解这一点。
func add_numbers(num1 interface{}, num2 interface{})interface{}{
// num1 and num2 are of interface{} type only (not any type)
// but any type can be converted to interface{} type
// This is really tricky, esp. coming from Python world // Need type assertion to ensure types are as expected
m := num1.(int)
n := num2.(int)
return m + n // returns an integer
}
上面是一个使用interface
作为类型的例子。Interface
也可以作为set of methods
为不同结构的变量提供方法。《以身作则》有一个很好的例子。简而言之,interface
可以用来收集与某些结构相关联的方法,比如针对rect
或circle
结构的area()
或perim()
。这就像 Python 中的类方法。
这些是我发现的 Python 和 Go 之间的一些主要区别。写 Go 代码让我重新思考如何写 Python,尤其是。我如何设计函数来接受类型和处理错误。
为了更好地学习围棋,很多人向我推荐了这本书围棋编程语言书:
我可能会在未来的博客文章中涉及机器学习、自然语言处理和围棋数据可视化等主题。
更新2019 年 2 月:在一个新的视频课程中,我在 Go 中涵盖了机器学习、NLP、决策树、推荐系统等所有上述主题![ Udemy Link , Packt Link ]请随意查看 Github 上的公共代码库,其中涵盖了 Golang 中的 tweet bot、文本分类、推荐引擎等主题。
附:看看我的新播客!我最近开始了一个名为“数据生活播客的新播客。如果你已经在工作中处理数据,或者想在机器学习、自然语言处理或信息检索方面做得更好,这就是适合你的播客。你可以在苹果播客、 Spotify 或 Anchor.fm 上听,或者在我最喜欢的播客应用之一:阴天上听。
My New Podcast: The Data Life Podcast
祝你写 Go 代码一切顺利!
MSE 和偏差-方差分解
当我翻阅一些伟大的机器学习书籍时,如 ISL 、 ESL 、 DL ,我对他们如何解释 MSE(均方误差)及其偏差-方差分解感到非常困惑。如果您想真正很好地掌握像过度拟合、欠拟合和模型容量这样的事情,偏差-方差分解是极其重要的。不幸的是,这些书要么放弃推导,要么在不同的上下文中给出,这令人困惑。在这里,我将给出两种最常见情况下的偏差-方差分解的完整推导:估计量的 MSE 和预测量的 MSE。
估计量的 MSE
估计器是数据样本上的任何函数,通常试图估计从中抽取样本的原始数据的一些有用质量。形式上,估计量是一个关于样本 S 的函数:
其中 x(i) 是从分布 D 中抽取的随机变量,即 x(i) ~ D 。
在统计学书籍中,通常可以方便地想象我们正在处理的数据是从某种分布中抽取的样本。想想股票市场,实际上我们只能每隔大约 10 毫秒监测一次股票价格,但实际上有一个隐藏的经济机器产生这些数据,由于其巨大的复杂性,我们无法观察到。这个机器描述了分布,我们观察到的数据是一个样本。
例子
我们想用这个样本来估计原始数据的一些有用的质量。例如,我们可能想知道 AAPL 股票的平均值,但由于我们无法掌握产生 AAPL 价格的整个经济机制,我们只能计算观察到的价格的平均值:
将是 AAPL 股票的估计值 均值和 AAPL 股票的真实均值。注意估计均值是依赖于样本的随机变量,样本也是随机变量,而**真实均值是标量。**
另一个例子是 AAPL 股票的估计方差:
其中 σ 是 APPL 股票的真实方差。
估计器属性
现在我们想知道我们的估计量有多好。有两个性质我们可以考虑: 估计量偏差 和 估计量方差 。
估计偏差 衡量我们的估计器在估计真实值时有多好。这是一个简单的区别:
估计量方差 衡量我们的估计量对采样的“跳动”程度,例如,如果我们每 100 毫秒而不是每 10 毫秒观察一次股票价格,估计量会有很大变化吗?
例子
如果我们假设 AAPL 股票价格的实际分布是一个高斯分布,那么估计量的偏差为零,意味着它是无偏的:
可惜 σ 的估计量的偏差不为零,是有偏差的:
顺便说一下,这就是为什么下面的无偏估计量在文献中更常用:
这些公式的证明见 DL 书中的第 5 章。
估计量的偏差-方差分解
偏差-方差分解只是将我们最喜欢的两个属性统一在一个公式中:
其中期望值是相对于 S 随机变量取的。
这就是证据:
这里我们使用了这样一个事实,即 θ 不是一个随机变量,因此它对于任何分布都等于它自己的期望。
预测值的 MSE
这个统计说够了,我们来谈谈机器学习吧!:)在上一节中,我们看到了如何使用估计器来估计数据的一些有用质量。在一个例子中,我们能够通过仅每 10ms 观察其值来估计 APPL 股票的均值和方差。
现在我们想赚点钱,在股票市场交易!我们需要建立一个模型,让从现有数据 x 中预测这只股票的未来价值 y 。这些可用的数据可以是销售数字、过去 5 天的股票价值、公告、产品发布等。所以我们建立了一个模型来描述我们的股票价格:
我们假设 f 是对我们隐藏的决定股票价格的真实模型, ϵ 是一些讨厌的观察噪声。我们想得出一个尽可能接近 f 的预测器*。预测器在训练数据的一些样本 S 上被训练,但是我们希望它在我们还没有观察到的数据上表现良好。因此,我们希望以下内容尽可能小:*
其中 (x,y) 是代表未观测数据的随机变量。 S 是我们训练预测器的数据, ϵ 是遵循某种分布 E 的噪声。注意,我们未观察到的(通常称为测试数据)与训练数据 S 中的点具有相同的分布。在 ML 中,让训练和测试数据来自同一个分布通常是非常重要的。
因为预测器的 MSE 也有偏差-方差分解。我们在这里推导一下。我们将使用以下公式:
下面所有的期望、方差和协方差都是通过 (x,y) 、 S 、和 ϵ 随机变量计算的。
这里我们假设我们的噪声是独立于s**【x,y】的随机变量。
现在我们将假设噪声 ϵ 具有零均值。如果平均值不是零,而是某个常数 c ,那么我们可以将该常数包含在我们的模型中的 f(x) 中,并认为该噪声具有零平均值。
第一项通常称为 方差 。它显示了真实模型和预测模型之间的差距是如何“跳动”的,这取决于训练数据和测试数据 (x,y) 。容量大的模型(如极多层的神经网络)方差高,容量小的模型(如想线性回归)方差*低。***
第二个名词是 噪音 。它显示了观测噪声的影响。它不依赖于任何东西,只依赖于噪声的基本分布。我们无法减少这种噪音,它是不可减少的。
第三项是平方 偏差 。它显示了我们的预测器是否很好地逼近真实模型。高容量机型 偏置 低容量机型 偏置 。
既然 偏差 和 方差 都对 MSE 有贡献,好的模型就尽量减少这两者。这被称为偏差-方差权衡。
这其实是一回事
正如你可能从公式中注意到的,估算器的 MSE 和预测器的 MSE 非常相似。估计量的 MSE 测量我们的 估计量 与期望量 θ 的接近程度。预测器的 MSE 测量我们的函数 预测器 在某些函数空间中与期望的函数 f 有多接近,其中我们测量两个函数之间的距离作为 L2 距离,这实际上是人们可以在函数空间中定义两个函数之间的距离的许多方法之一。
在预测器的 MSE部分,我们也引入了误差,但是在 MSE 中,估计器的 MSE部分也会有误差。在我们的股票例子中,这相当于我们对股票的观察被一些噪音扭曲了。
在 DL 书中找估计量被称为点估计*,因为 θ 是正则空间中的一个点。而寻找预测器被称为函数估计是因为 f 是函数空间中的函数。***
P.S .混淆的来源
对于试图理解偏差-方差分解的人来说,基本上有两个混淆的来源。
第一个是,书籍喜欢修正一些随机变量,只计算关于或 ϵ 的期望。它允许他们写出更短的公式。
第二个原因来自于这样一个事实,即从高层次的观点来看,MSE 估计量和预测量的 MSE 在技术上是一样的,在解释偏差-方差分解时,书籍喜欢将它们交替混合。
我实现了一个人脸检测模型。我是这样做的。
上周,我开始在 Augentix Inc .实习,旨在了解更多关于神经网络的知识。在那里,我遇到了一个面部检测模型,它在保持实时性能的同时实现了高精度(链接此处)。该模型使用多任务级联卷积网络(MTCNN),它本质上是几个卷积网络串在一起,给出几条信息。
从 github(链接这里)下载模型后,我打开并运行 example.py,它生成了这个图像:
Image 1: Output image from example.py // Source
如上图所示,神经网络检测各个面部,定位面部标志(即两只眼睛、鼻子和嘴的端点),并在面部周围绘制一个边界框。example.py 中的代码支持这一点。
首先,他们导入 OpenCV(打开、读取、写入和显示图像)和 MTCNN。正在检查。/mtcnn/mtcnn.py 显示了执行面部检测的 mtcnn 类。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-import cv2
from mtcnn.mtcnn import MTCNN
然后,创建一个 MTCNN 类的检测器,用 cv2.imread 读入图像。调用 MTCNN 类中的 detect_faces 函数,在我们传入的图像中“检测人脸”,并在“结果”中输出人脸。
detector = MTCNN()image = cv2.imread("ivan.jpg")
result = detector.detect_faces(image)
为了检查“结果”中有什么,我打印出来得到:
[{'box': [277, 90, 48, 63], 'confidence': 0.9985162615776062, 'keypoints': {'left_eye': (291, 117), 'right_eye': (314, 114), 'nose': (303, 131), 'mouth_left': (296, 143), 'mouth_right': (313, 141)}}]
结果似乎是一个字典,其中包括边界框和面部标志的坐标,以及网络将该区域分类为面部的信心。
我们现在可以分离坐标,将边界框坐标传递给 bounding_box,将面部标志坐标传递给关键点。
bounding_box = result[0]['box']
keypoints = result[0]['keypoints']
现在,我们通过传入坐标、颜色(RGB)和边框轮廓的厚度来绘制边框的矩形。这里,bounding_box[0]和 bounding_box[1]表示左上角的 x 和 y 坐标,bounding_box[2]和 bounding_box[3]分别表示框的宽度和高度。
类似地,我们可以通过传递坐标、圆的半径和线条的粗细来绘制面部标志的点。
cv2.rectangle(image,
(bounding_box[0], bounding_box[1]),
(bounding_box[0]+bounding_box[2], bounding_box[1] + bounding_box[3]),
(0,155,255),
2)cv2.circle(image,(keypoints['left_eye']), 2, (0,155,255), 2)
cv2.circle(image,(keypoints['right_eye']), 2, (0,155,255), 2)
cv2.circle(image,(keypoints['nose']), 2, (0,155,255), 2)
cv2.circle(image,(keypoints['mouth_left']), 2, (0,155,255), 2)
cv2.circle(image,(keypoints['mouth_right']), 2, (0,155,255), 2)
最后,我们创建另一个名为“ivan_drawn.jpg”的文件并显示图像。
cv2.imwrite("ivan_drawn.jpg", image)
cv2.namedWindow("image")
cv2.imshow("image",image)
cv2.waitKey(0)
这很好,但如果我们只能通过图像,面部检测是一个很大的用处。我想修改它,这样我就可以用我的网络摄像头。通过这样做,我还可以测试该报声称的能够实时定位人脸的说法。
我再次导入了 OpenCV 和 MTCNN,然后创建了一个检测器:
import cv2
from mtcnn.mtcnn import MTCNN
detector = MTCNN()
为了使用我的网络摄像头,我创建了一个视频捕捉对象。由于我只有 1 个摄像头,所以我传入了 0 个。
cap.read()返回一个布尔值(True/False ),该值表明帧是否被正确读入。如果一个错误发生并且一个帧没有被读取,它将返回 False 并且 while 循环将被中断。
然后,类似于 example.py,我在每一帧上调用 detect_faces。由于有时一张脸可能不在帧中(结果将是空的),我添加了“如果结果!= []"使程序即使在帧中没有人脸时也能继续运行。
此外,帧中可能有不止一个面。在这种情况下,result 将返回多组坐标,每个面一组。因此,我在 result 中运行了一个 for 循环来遍历每个单独的面。对于每一张脸,我画出边界框,并点上 5 个面部标志。
最后,我展示了每一个单独的画面。每一帧将为键“q”等待 1 毫秒,然后移动到下一帧。要关闭窗口,我只要按“q”就行了。一旦我这样做了,我就释放视频捕获并关闭窗口。
cap = cv2.VideoCapture(0)
while True:
#Capture frame-by-frame
__, frame = cap.read()
#Use MTCNN to detect faces
result = detector.detect_faces(frame)
if result != []:
for person in result:
bounding_box = person['box']
keypoints = person['keypoints']
cv2.rectangle(frame,
(bounding_box[0], bounding_box[1]),
(bounding_box[0]+bounding_box[2], bounding_box[1] + bounding_box[3]),
(0,155,255),
2)
cv2.circle(frame,(keypoints['left_eye']), 2, (0,155,255), 2)
cv2.circle(frame,(keypoints['right_eye']), 2, (0,155,255), 2)
cv2.circle(frame,(keypoints['nose']), 2, (0,155,255), 2)
cv2.circle(frame,(keypoints['mouth_left']), 2, (0,155,255), 2)
cv2.circle(frame,(keypoints['mouth_right']), 2, (0,155,255), 2)
#display resulting frame
cv2.imshow('frame',frame)
if cv2.waitKey(1) &0xFF == ord('q'):
break#When everything's done, release capture
cap.release()
cv2.destroyAllWindows()
运行这个新文件,我看到 MTCNN 网络确实可以实时运行。拳击我的脸,标出我的特征。如果我把脸移出相框,网络摄像头也会继续运行。
点击这里阅读 MTCNN 模型的结构!
点击这里阅读 MTCNN 如何做人脸检测!
点击此处下载 MTCNN 论文和资源:
- Github 下载:【https://github.com/ipazc/mtcnn
- 研究文章:http://arxiv.org/abs/1604.02878
基于 R 的文本多类分类
预测 Ted 演讲的收视率
Photo by Hermes Rivera on Unsplash
这个博客是我的 NLP 博客系列的延续。在之前的博客中,我讨论了 R 中的数据预处理步骤和识别 ted 演讲中的情绪。在这个博客中,我将预测观众对 ted 演讲的评价。这将需要多类分类和相当多的数据清理和预处理。我们将在下面详细讨论每个步骤。
所以,让我们开始吧!
A way to visualize text classification
数据清理和解析
ted 演讲数据集中的评级栏看起来像Figure 1
。
Figure 1
上面这个栏目的截图,Figure 1
表示有多少人认为一个特定的演讲是“鼓舞人心的”、“漂亮的”、“有创意的”、“有说服力的”等等。JSON 中的 count 键表示给予谈话的评分值。例如,这个演讲被 385 个人评为“鼓舞人心”,只有 2 个人认为这个演讲“有趣”。在这里,我们的目标是获得每次谈话的最高评分。
其中一个主要问题是解析 ratings 列。我已经使用了 gsub 函数来用双引号替换单引号。我在这里找到了关于gsub的细节和解释。
library(jsonlite)formatted_ted_ratings <- gsub(“‘“,’”’,ted_talks$ratings)
下一步是解析 JSON 以获得 id、评级名称和评级计数的列表。R 中的 jsonlite 库提供了流、验证和美化 JSON 数据的功能。 fromJSON 函数用于将 JSON 对象反序列化为 R 对象。最后, purrr::map 函数将一个函数(在我们的例子中是 fromJSON)应用于列表中的每个元素。文档和实现可以在这里阅读。
ted_ratings <- purrr::map(formatted_ted_ratings, jsonlite::fromJSON)
上面的代码块为我们提供了一个经过解析的评级列的简洁列表。看起来像Figure 2
。
Figure 2: Parsed rating column
在下一步中,我将创建一个新的列“highest_rating ”,在其中我将存储每次谈话的最高评分。发布后,我会将该列转换为因子,这将有效地给我们 13 个独特的因素(评级)来处理。我希望,到目前为止,您一定已经得到了一个提示,这个具有 13 个因子变量的最高评级列将用于多类分类。在这种意义上,二元分类问题有两个类别来分类数据点,例如真和假。然而,在这个问题中,我们必须将数据点分类到 13 个类别中的一个,因此,这是一个多类别分类问题。
for (i in (1:length(ted_ratings))) {
ted_ratings_df <- ted_ratings[[i]]
highest_rating_count <- ted_ratings_df[which(ted_ratings_df$count == max(ted_ratings_df$count)), ]
ted_talks$highest_rating[i] <- highest_rating_count$name
}ted_talks$highest_rating = as.factor(ted_talks$highest_rating)
完成以上步骤后,我们的数据集准备工作就完成了。
数据建模
我们现在将数据集分为训练和测试。我以 60:40 的比例划分了我的数据集。
trainObs <- sample(nrow(ted_talks), .6 * nrow(ted_talks), replace = FALSE)
testObs <- sample(nrow(ted_talks), .4 * nrow(ted_talks), replace = FALSE)train_dat <- ted_talks[trainObs,]
test_dat <- ted_talks[testObs,]
我现在将所有预处理步骤应用于我的训练和测试数据(分别)。不知何故,我处于双重心态:是将 DTM 分成训练和测试,还是分割数据集,然后分别准备他们的 DTM。不知何故,我选择了后者。你可以试试前一种选择,如果它对你合适,请告诉我。
我还处理了稀疏性,我在我的博客中详细讨论了这个问题。为了更直观,我还将目标变量重命名为“y ”,而不是 highest_rating。
train_corpus <- VCorpus(VectorSource(train_dat$transcript))##Removing Punctuation
train_corpus <- tm_map(train_corpus, content_transformer(removePunctuation))##Removing numbers
train_corpus <- tm_map(train_corpus, removeNumbers)##Converting to lower case
train_corpus <- tm_map(train_corpus, content_transformer(tolower))##Removing stop words
train_corpus <- tm_map(train_corpus, content_transformer(removeWords), stopwords(“english”))##Stemming
train_corpus <- tm_map(train_corpus, stemDocument)##Whitespace
train_corpus <- tm_map(train_corpus, stripWhitespace)# Create Document Term Matrix
dtm_train <- DocumentTermMatrix(train_corpus)train_corpus <- removeSparseTerms(dtm_train, 0.4)dtm_train_matrix <- as.matrix(train_corpus)
dtm_train_matrix <- cbind(dtm_train_matrix, train_dat$highest_rating)colnames(dtm_train_matrix)[ncol(dtm_train_matrix)] <- “y”training_set_ted_talk <- as.data.frame(dtm_train_matrix)
training_set_ted_talk$y <- as.factor(training_set_ted_talk$y)
现在我们已经准备好了训练数据集,我们可以训练我们的模型了。我在 caret 中使用了 caret 包和 svmLinear3 方法。 svmLinear3 为 SVM 的 L2 正则化提供线性核。同意,这是很多技术术语,我故意不在这里解释,因为这完全是另一个博客。同时,我会留下一些链接让你们了解 L2 正则化 ,以及 带线性核的 SVM。
library(caret)review_ted_model <- train(y ~., data = training_set_ted_talk, method = ‘svmLinear3’)Preparing our test data. It’s the same repetitive procedure.test_corpus <- VCorpus(VectorSource(test_dat$transcript))##Removing Punctuation
test_corpus <- tm_map(test_corpus, content_transformer(removePunctuation))##Removing numbers
test_corpus <- tm_map(test_corpus, removeNumbers)##Converting to lower case
test_corpus <- tm_map(test_corpus, content_transformer(tolower))##Removing stop words
test_corpus <- tm_map(test_corpus, content_transformer(removeWords), stopwords(“english”))##Stemming
test_corpus <- tm_map(test_corpus, stemDocument)##Whitespace
test_corpus <- tm_map(test_corpus, stripWhitespace)# Create Document Term Matrix
dtm_test <- DocumentTermMatrix(test_corpus)test_corpus <- removeSparseTerms(dtm_test, 0.4)dtm_test_matrix <- as.matrix(test_corpus)
模型准确性和其他指标
现在,我将根据测试数据检查我们的模型的准确性/性能。
#Build the prediction
model_ted_talk_result <- predict(review_ted_model, newdata = dtm_test_matrix)check_accuracy <- as.data.frame(cbind(prediction = model_ted_talk_result, rating = test_dat$highest_rating))library(dplyr)
check_accuracy <- check_accuracy %>% mutate(prediction = as.integer(prediction) — 1)check_accuracy$accuracy <- if_else(check_accuracy$prediction == check_accuracy$rating, 1, 0)
round(prop.table(table(check_accuracy$accuracy)), 3)library(performanceEstimation)
classificationMetrics(as.integer(test_dat$highest_rating), model_ted_talk_result)most_common_misclassified_ratings = check_accuracy %>% filter(check_accuracy$accuracy == 0) %>%
group_by(rating) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
head(3)##Most commong missclassified rating
levels(train_dat$highest_rating)[most_common_misclassified_ratings$rating]
模型指标包括:
Model metrics
前三个最常见的错误分类评级是:“鼓舞人心的”、“信息丰富的”、“引人入胜的”。你可以从这里和这里阅读更多关于微观和宏观 F1 分数。
结束语
在本文中,我们讨论了文本的多类分类。对我来说,解决这个数据问题相当具有挑战性,主要是因为:
- 我现在只处理二元分类问题
- 数据操作步骤导致每个单独的评级列的 13 个类。单引号破坏了看起来完美的 JSON 专栏。在没有任何直觉的情况下,找出单引号引起的问题是非常困难的。我最终设法用 gsub 解析了它。
最后,上面的模型分数严重依赖于我们在训练方法中使用的方法。我使用线性核 SVM 和 L2 正则化,这本身是计算量很大。您可以尝试其他方法,但是计算资源可能是一个问题。请让我知道你用的其他方法和你得到的分数(准确性和 F1)。
目前就这些。我将在下一篇博客中讨论一个与 NLP 相关的新任务。我也写过其他与软件工程相关的帖子。你可能想在这里查看它们。
多类文本分类模型的比较与选择
Photo credit: Pixabay
自然语言处理,word2vec,支持向量机,词袋,深度学习
当使用给定的数据集处理监督机器学习问题时,我们尝试不同的算法和技术来搜索模型以产生一般假设,然后对未来实例做出最准确的预测。相同的原理适用于文本(或文档)分类,其中有许多模型可用于训练文本分类器。问题“我应该用什么机器学习模型”的答案总是“视情况而定”即使是最有经验的数据科学家也无法在试验之前判断哪种算法的性能最佳。
这就是我们今天要做的:使用我们在以前的文章(以及更多)中介绍的关于文本分类的所有内容,并在我们训练的文本分类模型之间进行比较,以便为我们的问题选择最准确的模型。
数据
我们使用的是堆栈溢出问题和标签的相对较大的数据集。这些数据可以在的 Google BigQuery 中找到,也可以在这个云存储 URL 上公开获得:https://Storage . Google APIs . com/tensor flow-workshop-examples/stack-overflow-data . CSV。
探索数据
explore
Figure 1
10276752
我们的数据中有超过 1000 万个单词。
my_tags = ['java','html','asp.net','c#','ruby-on-rails','jquery','mysql','php','ios','javascript','python','c','css','android','iphone','sql','objective-c','c++','angularjs','.net']
plt.figure(figsize=(10,4))
df.tags.value_counts().plot(kind='bar');
Figure 2
班级非常平衡。
我们想看看一些帖子和标签对。
def print_plot(index):
example = df[df.index == index][['post', 'tags']].values[0]
if len(example) > 0:
print(example[0])
print('Tag:', example[1])print_plot(10)
Figure 3
print_plot(30)
Figure 4
如你所见,文本需要清理。
文本预处理
到目前为止,我们看到的文本清理技术在实践中非常有效。根据您可能遇到的文本类型,可能需要包括更复杂的文本清理步骤。但是请记住,我们添加的步骤越多,文本清理需要的时间就越长。
对于这个特定的数据集,我们的文本清理步骤包括 HTML 解码、删除停用词、将文本改为小写、删除标点符号、删除不良字符等等。
clean_text
现在我们可以看看一个干净的帖子:
Figure 5
好多了。
df['post'].apply(lambda x: len(x.split(' '))).sum()
3421180
在文本清理和删除停用词后,我们只有 300 多万个单词可以使用!
分割数据集后,接下来的步骤包括特征工程。我们将把文本文档转换成令牌计数矩阵(CountVectorizer),然后将计数矩阵转换成规范化的 tf-idf 表示(tf-idf transformer)。之后,我们从 Scikit-Learn 库中训练几个分类器。
X = df.post
y = df.tags
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state = 42)
多项式模型的朴素贝叶斯分类器
有了我们的特征之后,我们可以训练一个分类器来预测一篇文章的标签。我们将从一个朴素贝叶斯分类器开始,它为这个任务提供了一个很好的基线。scikit-learn
包括该分类器的几种变体;最适合文本的是多项式变量。
为了使矢量器= >转换器= >分类器更容易使用,我们将在 Scilkit-Learn 中使用Pipeline
类,它的行为类似于一个复合分类器。
nb
Figure 6
我们达到了 74%的准确率。
线性支持向量机
线性支持向量机被广泛认为是最好的文本分类算法之一。
svm
Figure 7
我们获得了 79%的更高准确率,比朴素贝叶斯提高了 5%。
逻辑回归
Logistic 回归是一种简单易懂的分类算法,可以很容易地推广到多个类。
logreg
Figure 8
我们达到了 78%的准确率,比朴素贝叶斯高 4%,比 SVM 低 1%。
正如你所看到的,遵循一些非常基本的步骤并使用一个简单的线性模型,我们能够在这个多类文本分类数据集上达到高达 79%的准确率。
使用相同的数据集,我们将尝试一些高级技术,如单词嵌入和神经网络。
现在,让我们尝试一些复杂的功能,而不仅仅是简单地计算单词。
Word2vec 和逻辑回归
Word2vec 和 doc2vec 一样,属于文本预处理阶段。特别是将文本转换成一行数字的部分。Word2vec 是一种映射类型,它允许具有相似含义的单词具有相似的向量表示。
Word2vec 背后的想法相当简单:我们希望使用周围的单词,用神经网络来表示目标单词,神经网络的隐藏层对单词表示进行编码。
首先,我们加载一个 word2vec 模型。它已经由谷歌在一个1000 亿字的谷歌新闻语料库上进行了预训练。
from gensim.models import Word2Vecwv = gensim.models.KeyedVectors.load_word2vec_format("GoogleNews-vectors-negative300.bin.gz", binary=True)
wv.init_sims(replace=True)
我们可能想要探索一些词汇。
from itertools import islice
list(islice(wv.vocab, 13030, 13050))
Figure 9
基于 BOW 的方法,包括平均、求和、加权加法。常见的方法是对两个字向量进行平均。因此,我们将遵循最常见的方式。
word_averaging
我们将对文本进行标记化,并将标记化应用于“post”列,并将单词向量平均应用于标记化的文本。
w2v_tokenize_text
是时候看看逻辑回归分类器在这些单词平均文档特征上的表现了。
from sklearn.linear_model import LogisticRegression
logreg = LogisticRegression(n_jobs=1, C=1e5)
logreg = logreg.fit(X_train_word_average, train['tags'])
y_pred = logreg.predict(X_test_word_average)
print('accuracy %s' % accuracy_score(y_pred, test.tags))
print(classification_report(test.tags, y_pred,target_names=my_tags))
Figure 10
这很令人失望,是我们迄今为止看到的最糟糕的一次。
Doc2vec 和 Logistic 回归
word2vec 的相同想法可以扩展到文档,我们不是学习单词的特征表示,而是学习句子或文档的特征表示。为了对一个 word2vec 有个大概的了解,可以把它想象成文档中所有单词的单词向量表示的数学平均值。 Doc2Vec 扩展了 word2vec 的概念,然而单词只能捕捉这么多,有时我们需要文档之间的关系,而不仅仅是单词。
为我们的堆栈溢出问题和标签数据训练 doc2vec 模型的方式与我们用 Doc2vec 和逻辑回归训练多类文本分类时非常相似。
首先,我们给句子贴上标签。 Gensim 的 Doc2Vec 实现要求每个文档/段落都有一个与之关联的标签。我们通过使用TaggedDocument
方法来做到这一点。格式为“TRAIN_i”或“TEST_i ”,其中“I”是帖子的虚拟索引。
label_sentences
根据 Gensim doc2vec 教程,它的 doc2vec 类是针对整个数据进行训练的,我们也会这样做。让我们看看带标签的文档是什么样子的:
all_data[:2]
Figure 11
当训练 doc2vec 时,我们将改变以下参数:
dm=0
,使用分布式单词包(DBOW)。vector_size=300
,300 个向量维特征向量。negative=5
,指定要抽取多少个“干扰词”。min_count=1
,忽略总频率低于此的所有单词。alpha=0.065
,初始学习率。
我们初始化模型并训练 30 个时期。
train_doc2vec
接下来,我们从训练好的 doc2vec 模型中获取向量。
get_vectors
最后,我们得到一个由 doc2vec 特征训练的逻辑回归模型。
logreg = LogisticRegression(n_jobs=1, C=1e5)
logreg.fit(train_vectors_dbow, y_train)
logreg = logreg.fit(train_vectors_dbow, y_train)
y_pred = logreg.predict(test_vectors_dbow)
print('accuracy %s' % accuracy_score(y_pred, y_test))
print(classification_report(y_test, y_pred,target_names=my_tags))
Figure 12
我们达到了 80%的准确率,比 SVM 高出 1%。
用 Keras 鞠躬
最后,我们将使用 Python 深度学习库 Keras 进行文本分类。
下面的代码很大程度上取自谷歌的一个研讨会。过程是这样的:
- 将数据分成训练集和测试集。
- 使用
tokenizer
方法来统计我们词汇中的独特单词,并给每个单词分配索引。 - 调用
fit_on_texts()
自动创建我们词汇表的单词索引查找。 - 我们通过向记号赋予器传递一个
num_words
参数来限制我们的词汇到顶部的单词。 - 有了我们的标记器,我们现在可以使用
texts_to_matrix
方法来创建训练数据,我们将通过我们的模型。 - 我们给我们的模型输入一个热点向量。
- 将我们的特征和标签转换成 Keras 可以读取的格式后,我们就可以构建我们的文本分类模型了。
- 当我们构建模型时,我们需要做的就是告诉 Keras 我们的输入数据、输出数据的形状,以及每一层的类型。keras 会照看剩下的。
- 在训练模型时,我们将调用
fit()
方法,向它传递我们的训练数据和标签、批量大小和时期。
keras_training
Figure 13
精确度为:
score = model.evaluate(x_test, y_test,
batch_size=batch_size, verbose=1)
print('Test accuracy:', score[1])
Figure 14
那么,哪个模型最适合这个特定的数据集呢?我将让你来决定。
Jupyter 笔记本可以在 Github 上找到。祝你有丰富的一天!
参考资料:
基于 Doc2Vec 和 Logistic 回归的多类文本分类
Photo credit: Pexels
目标是使用 Doc2Vec 和逻辑回归将消费者金融投诉分为 12 个预定义的类别
Doc2vec 是一个 NLP 工具,用于将文档表示为一个向量,并且是 word2vec 方法的推广。
为了理解 doc2vec,最好理解 word2vec 方法。然而,完整的数学细节超出了本文的范围。如果您不熟悉 word2vec 和 doc2vec,以下资源可以帮助您入门:
当我们使用 Scikit-Learn 进行多类文本分类时,使用相同的数据集,在本文中,我们将使用 Gensim 中的 doc2vec 技术按产品对投诉叙述进行分类。我们开始吧!
数据
目标是将消费者金融投诉分为 12 个预定义的类别。数据可以从 data.gov 下载。
import pandas as pd
import numpy as np
from tqdm import tqdm
tqdm.pandas(desc="progress-bar")
from gensim.models import Doc2Vec
from sklearn import utils
from sklearn.model_selection import train_test_split
import gensim
from sklearn.linear_model import LogisticRegression
from gensim.models.doc2vec import TaggedDocument
import re
import seaborn as sns
import matplotlib.pyplot as pltdf = pd.read_csv('Consumer_Complaints.csv')
df = df[['Consumer complaint narrative','Product']]
df = df[pd.notnull(df['Consumer complaint narrative'])]
df.rename(columns = {'Consumer complaint narrative':'narrative'}, inplace = True)
df.head(10)
Figure 1
在删除叙述列中的空值后,我们将需要重新索引数据框。
df.shape
(318718,2)
df.index = range(318718)df['narrative'].apply(lambda x: len(x.split(' '))).sum()
63420212
我们有超过 6300 万个单词,这是一个相对较大的数据集。
探索
cnt_pro = df['Product'].value_counts()plt.figure(figsize=(12,4))
sns.barplot(cnt_pro.index, cnt_pro.values, alpha=0.8)
plt.ylabel('Number of Occurrences', fontsize=12)
plt.xlabel('Product', fontsize=12)
plt.xticks(rotation=90)
plt.show();
Figure 2
这些分类是不平衡的,然而,一个简单的分类器预测所有的事情都是债务收集只会达到超过 20%的准确率。
让我们看几个投诉叙述及其相关产品的例子。
def print_complaint(index):
example = df[df.index == index][['narrative', 'Product']].values[0]
if len(example) > 0:
print(example[0])
print('Product:', example[1])print_complaint(12)
Figure 3
print_complaint(20)
Figure 4
文本预处理
下面我们定义一个函数来将文本转换成小写,并从单词中去掉标点/符号等等。
from bs4 import BeautifulSoup
def cleanText(text):
text = BeautifulSoup(text, "lxml").text
text = re.sub(r'\|\|\|', r' ', text)
text = re.sub(r'http\S+', r'<URL>', text)
text = text.lower()
text = text.replace('x', '')
return text
df['narrative'] = df['narrative'].apply(cleanText)
以下步骤包括 70/30 的训练/测试分割,删除停用词并使用 NLTK 标记器标记文本。在我们的第一次尝试中,我们将每个投诉叙述都贴上了产品标签。
train, test = train_test_split(df, test_size=0.3, random_state=42)import nltk
from nltk.corpus import stopwords
def tokenize_text(text):
tokens = []
for sent in nltk.sent_tokenize(text):
for word in nltk.word_tokenize(sent):
if len(word) < 2:
continue
tokens.append(word.lower())
return tokenstrain_tagged = train.apply(
lambda r: TaggedDocument(words=tokenize_text(r['narrative']), tags=[r.Product]), axis=1)
test_tagged = test.apply(
lambda r: TaggedDocument(words=tokenize_text(r['narrative']), tags=[r.Product]), axis=1)
这就是培训条目的样子——一个带有“信用报告”标签的投诉叙述示例。
train_tagged.values[30]
Figure 5
建立 Doc2Vec 培训和评估模型
首先,我们实例化一个 doc2vec 模型——分布式单词包(DBOW)。在 word2vec 架构中,两个算法名分别是“连续字包”(CBOW)和“skip-gram”(SG);在 doc2vec 架构中,对应的算法是“分布式内存”(DM)和“分布式单词包”(DBOW)。
分布式单词包(DBOW)
DBOW 是 doc2vec 模型,类似于 word2vec 中的 Skip-gram 模型。段落向量是通过训练神经网络来获得的,该神经网络的任务是在给定从段落中随机采样的单词的情况下,预测段落中单词的概率分布。
我们将改变以下参数:
- 如果
dm=0
,使用分布式单词包(PV-DBOW);如果dm=1
,则使用‘分布式内存’(PV-DM)。 - 300 维特征向量。
min_count=2
,忽略总频率低于此的所有单词。negative=5
,指定需要抽取多少个“干扰词”。hs=0
,且负数为非零,将使用负数采样。sample=0
,用于配置哪些高频词被随机下采样的阈值。- 使用这些工作线程来训练模型。
import multiprocessingcores = multiprocessing.cpu_count()
积累词汇
model_dbow = Doc2Vec(dm=0, vector_size=300, negative=5, hs=0, min_count=2, sample = 0, workers=cores)
model_dbow.build_vocab([x for x in tqdm(train_tagged.values)])
Figure 6
在 Gensim 中训练 doc2vec 模型相当简单,我们初始化模型并训练 30 个时期。
%%time
for epoch in range(30):
model_dbow.train(utils.shuffle([x for x in tqdm(train_tagged.values)]), total_examples=len(train_tagged.values), epochs=1)
model_dbow.alpha -= 0.002
model_dbow.min_alpha = model_dbow.alpha
Figure 7
为分类器构建最终矢量特征
def vec_for_learning(model, tagged_docs):
sents = tagged_docs.values
targets, regressors = zip(*[(doc.tags[0], model.infer_vector(doc.words, steps=20)) for doc in sents])
return targets, regressorsdef vec_for_learning(model, tagged_docs):
sents = tagged_docs.values
targets, regressors = zip(*[(doc.tags[0], model.infer_vector(doc.words, steps=20)) for doc in sents])
return targets, regressors
训练逻辑回归分类器。
y_train, X_train = vec_for_learning(model_dbow, train_tagged)
y_test, X_test = vec_for_learning(model_dbow, test_tagged)logreg = LogisticRegression(n_jobs=1, C=1e5)
logreg.fit(X_train, y_train)
y_pred = logreg.predict(X_test)from sklearn.metrics import accuracy_score, f1_scoreprint('Testing accuracy %s' % accuracy_score(y_test, y_pred))
print('Testing F1 score: {}'.format(f1_score(y_test, y_pred, average='weighted')))
检测精度 0.6683609437751004
测试 F1 分数:0.651646431211616
分布式内存(DM)
分布式记忆(DM)作为一种记忆,可以记住当前上下文中缺少的内容,或者段落的主题。虽然单词向量表示单词的概念,但是文档向量旨在表示文档的概念。我们再次实例化具有 300 个单词的向量大小的 Doc2Vec 模型,并且在训练语料库上迭代 30 次。
model_dmm = Doc2Vec(dm=1, dm_mean=1, vector_size=300, window=10, negative=5, min_count=1, workers=5, alpha=0.065, min_alpha=0.065)
model_dmm.build_vocab([x for x in tqdm(train_tagged.values)])
Figure 8
%%time
for epoch in range(30):
model_dmm.train(utils.shuffle([x for x in tqdm(train_tagged.values)]), total_examples=len(train_tagged.values), epochs=1)
model_dmm.alpha -= 0.002
model_dmm.min_alpha = model_dmm.alpha
Figure 9
训练逻辑回归分类器
y_train, X_train = vec_for_learning(model_dmm, train_tagged)
y_test, X_test = vec_for_learning(model_dmm, test_tagged)logreg.fit(X_train, y_train)
y_pred = logreg.predict(X_test)print('Testing accuracy %s' % accuracy_score(y_test, y_pred))
print('Testing F1 score: {}'.format(f1_score(y_test, y_pred, average='weighted')))
检测精度 0.47498326639892907
测试 F1 分数:0.4445833078167434
模型配对
根据关于 IMDB 情感数据集的 Gensim doc2vec 教程,将来自分布式单词包(DBOW)和分布式内存(DM)的段落向量相结合可以提高性能。我们将随后将模型配对在一起进行评估。
首先,我们删除临时训练数据来释放 RAM。
model_dbow.delete_temporary_training_data(keep_doctags_vectors=True, keep_inference=True)
model_dmm.delete_temporary_training_data(keep_doctags_vectors=True, keep_inference=True)
连接两个模型。
from gensim.test.test_doc2vec import ConcatenatedDoc2Vec
new_model = ConcatenatedDoc2Vec([model_dbow, model_dmm])
构建特征向量。
def get_vectors(model, tagged_docs):
sents = tagged_docs.values
targets, regressors = zip(*[(doc.tags[0], model.infer_vector(doc.words, steps=20)) for doc in sents])
return targets, regressors
训练逻辑回归
y_train, X_train = get_vectors(new_model, train_tagged)
y_test, X_test = get_vectors(new_model, test_tagged)logreg.fit(X_train, y_train)
y_pred = logreg.predict(X_test)print('Testing accuracy %s' % accuracy_score(y_test, y_pred))
print('Testing F1 score: {}'.format(f1_score(y_test, y_pred, average='weighted')))
检测精度 0.6778572623828648
测试 F1 分数:0.664561533967402
结果提高了 1%。
对于本文,我使用训练集来训练 doc2vec,然而,在 Gensim 的教程中,整个数据集用于训练,我尝试了这种方法,使用整个数据集来训练 doc2vec 分类器,用于我们的消费者投诉分类,我能够达到 70%的准确率。你可以在这里找到那个笔记本,它的做法有点不同。
用于上述分析的 Jupyter 笔记本可以在 Github 上找到。我期待听到任何问题。
基于 PySpark 的多类文本分类
Photo credit: Pixabay
Apache Spark 在头条新闻和现实世界中的采用率都迅速上升,这主要是因为它处理流媒体数据的能力。由于每天都有如此多的数据需要处理,因此对我们来说,实时传输和分析这些数据变得至关重要。此外,Apache Spark 足够快,无需采样就可以执行探索性查询。许多行业专家提供了所有的理由为什么你应该使用 Spark 进行机器学习?
所以,我们现在在这里,使用 Spark 机器学习库来解决一个多类文本分类问题,特别是 PySpark。
如果你想看看用 Scikit-Learn 的实现,请阅读以前的文章。
数据
我们的任务是将旧金山的犯罪描述分为 33 个预定义的类别。数据可以从 Kaggle 下载。
给定一个新的犯罪描述,我们希望将其分配到 33 个类别中的一个。分类器假设每个新的犯罪描述被分配到一个且仅一个类别。这是多类文本分类问题。
- 输入:描述
例如:“被盗汽车”
- 输出:类别
示例:车辆盗窃
为了解决这个问题,我们将在 Spark 中使用各种特征提取技术以及不同的监督机器学习算法。我们开始吧!
数据摄取和提取
使用 Spark csv 包加载 CSV 文件非常简单。
from pyspark.sql import SQLContext
from pyspark import SparkContext
sc =SparkContext()
sqlContext = SQLContext(sc)data = sqlContext.read.format('com.databricks.spark.csv').options(header='true', inferschema='true').load('train.csv')
就是这样!我们已经加载了数据集。我们开始探索吧。
删除我们不需要的列,看看前五行:
drop_list = ['Dates', 'DayOfWeek', 'PdDistrict', 'Resolution', 'Address', 'X', 'Y']data = data.select([column for column in data.columns if column not in drop_list])
data.show(5)
Figure 1
对数据应用 printSchema(),这将以树格式打印模式:
data.printSchema()
Figure 2
20 大犯罪类别:
from pyspark.sql.functions import coldata.groupBy("Category") \
.count() \
.orderBy(col("count").desc()) \
.show()
Figure 3
20 大犯罪描述:
data.groupBy("Descript") \
.count() \
.orderBy(col("count").desc()) \
.show()
Figure 4
模型管道
Spark 机器学习管道 API 类似于 Scikit-Learn 。我们的渠道包括三个步骤:
regexTokenizer
:标记化(使用正则表达式)stopwordsRemover
:删除停用词countVectors
:计数向量(“文档术语向量”)
from pyspark.ml.feature import RegexTokenizer, StopWordsRemover, CountVectorizer
from pyspark.ml.classification import LogisticRegression# regular expression tokenizer
regexTokenizer = RegexTokenizer(inputCol="Descript", outputCol="words", pattern="\\W")# stop words
add_stopwords = ["http","https","amp","rt","t","c","the"] stopwordsRemover = StopWordsRemover(inputCol="words", outputCol="filtered").setStopWords(add_stopwords)# bag of words count
countVectors = CountVectorizer(inputCol="filtered", outputCol="features", vocabSize=10000, minDF=5)
StringIndexer
StringIndexer
将一列标签编码成一列标签索引。索引在[0, numLabels)
中,按照标签频率排序,因此最频繁的标签得到索引0
。
在我们的例子中,标签列(类别)将被编码为标签索引,从 0 到 32;最频繁的标签(盗窃/偷窃)将被索引为 0。
from pyspark.ml import Pipeline
from pyspark.ml.feature import OneHotEncoder, StringIndexer, VectorAssembler
label_stringIdx = StringIndexer(inputCol = "Category", outputCol = "label")pipeline = Pipeline(stages=[regexTokenizer, stopwordsRemover, countVectors, label_stringIdx])# Fit the pipeline to training documents.
pipelineFit = pipeline.fit(data)
dataset = pipelineFit.transform(data)
dataset.show(5)
Figure 5
分区训练&测试集
# set seed for reproducibility
(trainingData, testData) = dataset.randomSplit([0.7, 0.3], seed = 100)
print("Training Dataset Count: " + str(trainingData.count()))
print("Test Dataset Count: " + str(testData.count()))
训练数据集计数:5185
测试数据集计数:2104
模型训练和评估
使用计数向量特征的逻辑回归
我们的模型将在测试集上进行预测和评分;然后,我们从最高概率的角度来看前 10 个预测。
lr = LogisticRegression(maxIter=20, regParam=0.3, elasticNetParam=0)
lrModel = lr.fit(trainingData)predictions = lrModel.transform(testData)predictions.filter(predictions['prediction'] == 0) \
.select("Descript","Category","probability","label","prediction") \
.orderBy("probability", ascending=False) \
.show(n = 10, truncate = 30)
Figure 6
from pyspark.ml.evaluation import MulticlassClassificationEvaluator
evaluator = MulticlassClassificationEvaluator(predictionCol="prediction")
evaluator.evaluate(predictions)
0.9610787444388802
准确度极好!
使用 TF-IDF 特征的逻辑回归
from pyspark.ml.feature import HashingTF, IDFhashingTF = HashingTF(inputCol="filtered", outputCol="rawFeatures", numFeatures=10000)
idf = IDF(inputCol="rawFeatures", outputCol="features", minDocFreq=5) #minDocFreq: remove sparse terms
pipeline = Pipeline(stages=[regexTokenizer, stopwordsRemover, hashingTF, idf, label_stringIdx])pipelineFit = pipeline.fit(data)
dataset = pipelineFit.transform(data)(trainingData, testData) = dataset.randomSplit([0.7, 0.3], seed = 100)
lr = LogisticRegression(maxIter=20, regParam=0.3, elasticNetParam=0)
lrModel = lr.fit(trainingData)predictions = lrModel.transform(testData)predictions.filter(predictions['prediction'] == 0) \
.select("Descript","Category","probability","label","prediction") \
.orderBy("probability", ascending=False) \
.show(n = 10, truncate = 30)
Figure 7
evaluator = MulticlassClassificationEvaluator(predictionCol="prediction")
evaluator.evaluate(predictions)
0.9616202660247297
结果是一样的。
交叉验证
现在让我们尝试交叉验证来调整我们的超参数,我们将只调整计数向量逻辑回归。
pipeline = Pipeline(stages=[regexTokenizer, stopwordsRemover, countVectors, label_stringIdx])pipelineFit = pipeline.fit(data)
dataset = pipelineFit.transform(data)
(trainingData, testData) = dataset.randomSplit([0.7, 0.3], seed = 100)lr = LogisticRegression(maxIter=20, regParam=0.3, elasticNetParam=0)from pyspark.ml.tuning import ParamGridBuilder, CrossValidator# Create ParamGrid for Cross Validation
paramGrid = (ParamGridBuilder()
.addGrid(lr.regParam, [0.1, 0.3, 0.5]) # regularization parameter
.addGrid(lr.elasticNetParam, [0.0, 0.1, 0.2]) # Elastic Net Parameter (Ridge = 0)
# .addGrid(model.maxIter, [10, 20, 50]) #Number of iterations
# .addGrid(idf.numFeatures, [10, 100, 1000]) # Number of features
.build())# Create 5-fold CrossValidator
cv = CrossValidator(estimator=lr, \
estimatorParamMaps=paramGrid, \
evaluator=evaluator, \
numFolds=5)cvModel = cv.fit(trainingData)
predictions = cvModel.transform(testData)
# Evaluate best model
evaluator = MulticlassClassificationEvaluator(predictionCol="prediction")
evaluator.evaluate(predictions)
0.9851796929217101
性能提高了。
朴素贝叶斯
from pyspark.ml.classification import NaiveBayesnb = NaiveBayes(smoothing=1)
model = nb.fit(trainingData)predictions = model.transform(testData)
predictions.filter(predictions['prediction'] == 0) \
.select("Descript","Category","probability","label","prediction") \
.orderBy("probability", ascending=False) \
.show(n = 10, truncate = 30)
Figure 8
evaluator = MulticlassClassificationEvaluator(predictionCol="prediction")
evaluator.evaluate(predictions)
0.962541462988848
随机森林
from pyspark.ml.classification import RandomForestClassifierrf = RandomForestClassifier(labelCol="label", \
featuresCol="features", \
numTrees = 100, \
maxDepth = 4, \
maxBins = 32)# Train model with Training Data
rfModel = rf.fit(trainingData)predictions = rfModel.transform(testData)predictions.filter(predictions['prediction'] == 0) \
.select("Descript","Category","probability","label","prediction") \
.orderBy("probability", ascending=False) \
.show(n = 10, truncate = 30)
Figure 9
evaluator = MulticlassClassificationEvaluator(predictionCol="prediction")
evaluator.evaluate(predictions)
0.6600326922344301
随机森林是一种非常好的、健壮的和通用的方法,但是对于高维稀疏数据来说,它不是最佳选择,这并不奇怪。
显然,逻辑回归将是我们在这个实验中的模型,具有交叉验证。
这就把我们带到了文章的结尾。创建这篇文章的源代码可以在 Github 上找到。我期待听到任何反馈或问题。
使用 Scikit-Learn 进行多类文本分类
Image credit: pexels
文本分类在商业领域有很多应用。例如,新闻故事通常是按主题组织的;内容或产品通常按类别进行标记;用户可以根据他们在网上谈论产品或品牌的方式进行分类…
但是网上绝大多数的文本分类文章和教程都是邮件垃圾过滤(spam vs. ham)、情感分析(正面 vs .负面)等二元文本分类。在大多数情况下,我们现实世界的问题要比这复杂得多。因此,这就是我们今天要做的事情:将消费金融投诉分为 12 个预定义的类别。数据可以从data.gov下载。
我们使用 Python 和 Jupyter Notebook 来开发我们的系统,依靠 Scikit-Learn 作为机器学习组件。如果您想在 PySpark 中看到实现,请阅读下一篇文章。
问题定式化
该问题是有监督的文本分类问题,我们的目标是调查哪些有监督的机器学习方法最适合解决它。
假设有新的投诉进来,我们希望将其分配到 12 个类别中的一个。分类器假设每个新投诉被分配到一个且仅一个类别。这是多类文本分类问题。我迫不及待地想看看我们能实现什么!
数据探索
在开始训练机器学习模型之前,我们应该先看看一些例子和每个类中的投诉数量:
import pandas as pd
df = pd.read_csv('Consumer_Complaints.csv')
df.head()
Figure 1
对于这个项目,我们只需要两列—“产品”和“消费者投诉叙述”。
- 输入:消费者 _ 投诉 _ 叙述
例如:“我的信用报告中有过时的信息,我以前质疑过这些信息,但现在还没有删除。这些信息已经存在了七年多,不符合信用报告要求”
- 输出:产品
例如:信用报告
我们将删除“消费者投诉叙述”列中缺少的值,并添加一个将产品编码为整数的列,因为分类变量通常用整数表示比用字符串表示更好。
我们还创建了几个字典供将来使用。
清理之后,这是我们将要处理的前五行数据:
from io import StringIOcol = ['Product', 'Consumer complaint narrative']
df = df[col]
df = df[pd.notnull(df['Consumer complaint narrative'])]df.columns = ['Product', 'Consumer_complaint_narrative']df['category_id'] = df['Product'].factorize()[0]
category_id_df = df[['Product', 'category_id']].drop_duplicates().sort_values('category_id')
category_to_id = dict(category_id_df.values)
id_to_category = dict(category_id_df[['category_id', 'Product']].values)
df.head()
Figure 2
不平衡的班级
我们看到每个产品的投诉数量不平衡。消费者的投诉更偏向于催债、征信、房贷。
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8,6))
df.groupby('Product').Consumer_complaint_narrative.count().plot.bar(ylim=0)
plt.show()
Figure 3
当我们遇到这样的问题时,我们必然很难用标准算法来解决它们。传统算法往往偏向于多数类,没有考虑数据分布。在最坏的情况下,少数类被视为离群值并被忽略。对于某些情况,如欺诈检测或癌症预测,我们需要仔细配置我们的模型或人工平衡数据集,例如通过对每个类进行欠采样或过采样。
然而,在我们学习不平衡数据的情况下,多数类可能是我们最感兴趣的。希望有一种对多数类给出高预测精度,同时对少数类保持合理精度的分类器。因此,我们将保持原样。
文本表示
分类器和学习算法不能直接处理原始形式的文本文档,因为它们中的大多数期望具有固定大小的数字特征向量,而不是具有可变长度的原始文本文档。因此,在预处理步骤中,文本被转换成更易于管理的表示。
从文本中提取特征的一种常见方法是使用单词袋模型:在该模型中,对于每个文档(在我们的情况下是投诉叙述),考虑单词的存在(通常是频率),但忽略它们出现的顺序。
具体来说,对于数据集中的每个术语,我们将计算一个称为术语频率的度量,即逆文档频率,缩写为 tf-idf。我们将使用sklearn.feature_extraction.text.TfidfVectorizer
来计算每个消费者投诉叙述的tf-idf
向量:
sublinear_df
设置为True
以使用对数形式的频率。min_df
是一个单词必须存在的最小文档数。norm
被设置为l2
,以确保我们所有的特征向量具有 1 的欧几里德范数。ngram_range
被设置为(1, 2)
,表示我们既要考虑单元组,也要考虑二元组。stop_words
设置为"english"
删除所有常用代词("a"
,"the"
,…)以减少噪声特征的数量。
from sklearn.feature_extraction.text import TfidfVectorizertfidf = TfidfVectorizer(sublinear_tf=True, min_df=5, norm='l2', encoding='latin-1', ngram_range=(1, 2), stop_words='english')features = tfidf.fit_transform(df.Consumer_complaint_narrative).toarray()
labels = df.category_id
features.shape
(4569, 12633)
现在,4569 个消费者投诉叙述中的每一个都由 12633 个特征表示,代表不同单字和双字的 tf-idf 得分。
我们可以使用sklearn.feature_selection.chi2
找到与每种产品最相关的术语:
from sklearn.feature_selection import chi2
import numpy as npN = 2
for Product, category_id in sorted(category_to_id.items()):
features_chi2 = chi2(features, labels == category_id)
indices = np.argsort(features_chi2[0])
feature_names = np.array(tfidf.get_feature_names())[indices]
unigrams = [v for v in feature_names if len(v.split(' ')) == 1]
bigrams = [v for v in feature_names if len(v.split(' ')) == 2]
print("# '{}':".format(Product))
print(" . Most correlated unigrams:\n. {}".format('\n. '.join(unigrams[-N:])))
print(" . Most correlated bigrams:\n. {}".format('\n. '.join(bigrams[-N:])))
’ 银行账户或服务 ':
。最相关的单字:
。银行
。透支
。最相关的二元模型:
。透支费
。支票账户
’ 消费贷款 ':
。最相关的单字:
。汽车
。车辆
。最相关的二元模型:
。车辆 xxxx
。丰田金融
’ 信用卡 ':
。最相关的单字:
。花旗
。卡片
。最相关的二元模型:
。年费
。信用卡
’ 征信 ':
。最相关的单字:
。experian
。equifax
。最相关的二元模型:
。跨联
。征信报告
#’讨债:
。最相关的单字:
。收藏
。债务
。最相关的二元模型:
。讨债
。代收机构
#’汇款’:
。最相关的单字:
。吴
。贝宝
。最相关的二元模型:
。西联
。转账
’ 抵押 ':
。最相关的单字:
。修改
。抵押
。最相关二元模型:
。抵押公司
。贷款修改
’ 其他金融服务 ':
。最相关的单字:
。牙科
。护照
。最相关二元模型:
。帮付
。声明支付
’ 发薪日贷款 ':
。最相关的单字:
。借来的
。发薪日
。最相关二元模型:
。大图
。发薪日贷款
’ 预付卡 ':
。最相关的单字:
。上菜
。预付
。最相关二元模型:
。存取款
。预付卡
’ 助学贷款 ':
。最相关的单字:
。学生
。导航
。最相关的二元模型:
。助学贷款
。助学贷款
#‘虚拟货币‘:
。最相关的单字:
。手柄
。https
。最相关二元模型:
。xxxx 供应商
。缺钱
他们都有道理,你不这样认为吗?
多类分类器:特征与设计
- 为了训练监督分类器,我们首先将“消费者投诉叙述”转换成数字向量。我们研究了向量表示,如 TF-IDF 加权向量。
- 有了这种文本的向量表示后,我们可以训练监督分类器来训练看不见的“消费者投诉叙述”,并预测它们所涉及的“产品”。
在所有上述数据转换之后,现在我们已经有了所有的特征和标签,是时候训练分类器了。对于这类问题,我们可以使用许多算法。
- 朴素贝叶斯分类器:最适合字数统计的一个是多项式变量:
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.naive_bayes import MultinomialNBX_train, X_test, y_train, y_test = train_test_split(df['Consumer_complaint_narrative'], df['Product'], random_state = 0)
count_vect = CountVectorizer()
X_train_counts = count_vect.fit_transform(X_train)
tfidf_transformer = TfidfTransformer()
X_train_tfidf = tfidf_transformer.fit_transform(X_train_counts)clf = MultinomialNB().fit(X_train_tfidf, y_train)
拟合完训练集之后,我们来做一些预测。
print(clf.predict(count_vect.transform(["This company refuses to provide me verification and validation of debt per my right under the FDCPA. I do not believe this debt is mine."])))
[‘讨债’]
df[df['Consumer_complaint_narrative'] == "This company refuses to provide me verification and validation of debt per my right under the FDCPA. I do not believe this debt is mine."]
Figure 4
print(clf.predict(count_vect.transform(["I am disputing the inaccurate information the Chex-Systems has on my credit report. I initially submitted a police report on XXXX/XXXX/16 and Chex Systems only deleted the items that I mentioned in the letter and not all the items that were actually listed on the police report. In other words they wanted me to say word for word to them what items were fraudulent. The total disregard of the police report and what accounts that it states that are fraudulent. If they just had paid a little closer attention to the police report I would not been in this position now and they would n't have to research once again. I would like the reported information to be removed : XXXX XXXX XXXX"])))
[‘信用报告’]
df[df['Consumer_complaint_narrative'] == "I am disputing the inaccurate information the Chex-Systems has on my credit report. I initially submitted a police report on XXXX/XXXX/16 and Chex Systems only deleted the items that I mentioned in the letter and not all the items that were actually listed on the police report. In other words they wanted me to say word for word to them what items were fraudulent. The total disregard of the police report and what accounts that it states that are fraudulent. If they just had paid a little closer attention to the police report I would not been in this position now and they would n't have to research once again. I would like the reported information to be removed : XXXX XXXX XXXX"]
Figure 5
不算太寒酸!
型号选择
我们现在准备用不同的机器学习模型进行实验,评估它们的准确性,并找到任何潜在问题的来源。
我们将对以下四个模型进行基准测试:
- 逻辑回归
- (多项式)朴素贝叶斯
- 线性支持向量机
- 随机森林
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import LinearSVCfrom sklearn.model_selection import cross_val_scoremodels = [
RandomForestClassifier(n_estimators=200, max_depth=3, random_state=0),
LinearSVC(),
MultinomialNB(),
LogisticRegression(random_state=0),
]
CV = 5
cv_df = pd.DataFrame(index=range(CV * len(models)))
entries = []
for model in models:
model_name = model.__class__.__name__
accuracies = cross_val_score(model, features, labels, scoring='accuracy', cv=CV)
for fold_idx, accuracy in enumerate(accuracies):
entries.append((model_name, fold_idx, accuracy))
cv_df = pd.DataFrame(entries, columns=['model_name', 'fold_idx', 'accuracy'])import seaborn as snssns.boxplot(x='model_name', y='accuracy', data=cv_df)
sns.stripplot(x='model_name', y='accuracy', data=cv_df,
size=8, jitter=True, edgecolor="gray", linewidth=2)
plt.show()
Figure 6
cv_df.groupby('model_name').accuracy.mean()
model _ Name
**linear SVC:**0.822890
**logistic regression:**0.792927
**MultinomialNB:**0.688519
**RandomForestClassifier:**0.443826
Name:accuracy,dtype: float64
LinearSVC 和逻辑回归比其他两个分类器表现得更好,LinearSVC 以大约 82%的中值精度略微占优。
模型评估
继续我们的最佳模型(LinearSVC),我们将查看混淆矩阵,并显示预测和实际标签之间的差异。
model = LinearSVC()X_train, X_test, y_train, y_test, indices_train, indices_test = train_test_split(features, labels, df.index, test_size=0.33, random_state=0)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)from sklearn.metrics import confusion_matrixconf_mat = confusion_matrix(y_test, y_pred)
fig, ax = plt.subplots(figsize=(10,10))
sns.heatmap(conf_mat, annot=True, fmt='d',
xticklabels=category_id_df.Product.values, yticklabels=category_id_df.Product.values)
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.show()
Figure 7
绝大多数预测最终出现在对角线上(预测标签=实际标签),这是我们希望它们出现的位置。但是,存在许多错误分类,看看这些错误分类是由什么引起的可能会很有意思:
from IPython.display import displayfor predicted in category_id_df.category_id:
for actual in category_id_df.category_id:
if predicted != actual and conf_mat[actual, predicted] >= 10:
print("'{}' predicted as '{}' : {} examples.".format(id_to_category[actual], id_to_category[predicted], conf_mat[actual, predicted]))
display(df.loc[indices_test[(y_test == actual) & (y_pred == predicted)]][['Product', 'Consumer_complaint_narrative']])
print('')
Figure 8
Figure 9
如您所见,一些错误分类的投诉涉及多个主题(例如,涉及信用卡和信用报告的投诉)。这种错误总是会发生。
同样,我们使用卡方检验来查找与每个类别最相关的术语:
model.fit(features, labels)N = 2
for Product, category_id in sorted(category_to_id.items()):
indices = np.argsort(model.coef_[category_id])
feature_names = np.array(tfidf.get_feature_names())[indices]
unigrams = [v for v in reversed(feature_names) if len(v.split(' ')) == 1][:N]
bigrams = [v for v in reversed(feature_names) if len(v.split(' ')) == 2][:N]
print("# '{}':".format(Product))
print(" . Top unigrams:\n . {}".format('\n . '.join(unigrams)))
print(" . Top bigrams:\n . {}".format('\n . '.join(bigrams)))
’ 银行账户或服务 ':
。Top unigrams:
。银行
。账户
。Top bigrams:
。借记卡
。透支费
’ 消费贷款 ':
。顶级单字:
。车辆
。汽车
。热门二元模型:
。个人贷款
。历史 xxxx
’ 信用卡 ':
。顶级单字:
。卡片
。发现
。顶级双元:
。信用卡
。发现卡
’ 征信 ':
。Top unigrams:
。equifax
。跨联
。Top bigrams:
。xxxx 账户
。跨联
’ 讨债 ':
。Top unigrams:
。债务
。收藏
。顶级名人:
。账户贷记
。提供时间
#’汇款’:
。Top unigrams:
。贝宝
。传送
。Top bigrams:
。汇款
。送钱
#‘抵押‘:
。Top unigrams:
。抵押
。托管
。Top bigrams:
。贷款修改
。抵押公司
’ 其他金融服务 ':
。Top unigrams:
。护照
。牙科
。Top bigrams:
。规定薪酬
。帮助支付
’ 发薪日贷款 ':
。Top unigrams:
。发薪日
。贷款
。Top bigrams:
。发薪日贷款
。发薪日
#‘预付卡‘:
。Top unigrams:
。预付
。上菜
。顶级双面人物:
。预付卡
。用卡
#‘助学贷款‘:
。Top unigrams:
。导航
。贷款
。Top bigrams:
。助学贷款
。莎莉美
’ 虚拟货币 ':
。Top unigrams:
。https
。tx
。顶级双元:
。钱要
。xxxx 提供商
它们符合我们的预期。
最后,我们打印出每个类别的分类报告:
from sklearn import metrics
print(metrics.classification_report(y_test, y_pred, target_names=df['Product'].unique()))
Figure 9
源代码可以在 Github 上找到。我期待听到任何反馈或问题。
用 python 中的 SKlearn 和 NLTK 实现多类文本分类|一个软件工程用例
photo credit: unsplash
最近,我从事一个软件工程研究项目。该项目的主要目标之一是了解开发团队的工作重点。当软件项目的规模变大时,管理工作流和开发过程变得更具挑战性。因此,对于管理团队和主要开发人员来说,理解软件开发人员执行的工作类型是非常重要的。
更简单地说,在项目的不同阶段,任何开发人员都可以随时编写代码来实现以下目标之一:
- 添加新功能
- 设计改进
- 错误修复
- 改进非功能性需求
现在有人可能会问,以上四个类别是开发人员完成的软件开发活动的唯一类型吗?答案是:不。这里的目标是提出一些包含大部分开发任务的通用工作类别。
了解开发团队的工作是如何根据上述四个类别进行分配的,可以帮助管理层在持续开发软件功能的同时,在管理软件增长方面做出更好的决策。例如,如果开发团队的大部分工作都是为了修复 bug,那么管理层可以采取必要的措施来防止软件中的错误和缺陷,并为主要开发人员提供指导方针,让他们更加关注质量。
问题是:
所以主要问题是:我们如何通过查看源代码来对开发人员的工作进行分类?
答案是:我们可以查看提交消息;开发人员提供一个提交消息以及他们对存储库的每一次提交。这些提交消息通常使用自然语言编写,并且通常传达一些关于它们所表示的提交的信息。我们可以使用一些标记的数据构建一个分类器,然后自动对未来的提交进行分类。
if I had a dollar for every commit … !!!
数据工程:
我们从 github 上的几个开源 Java 项目中收集了大约 4000 条提交消息。我们使用基于每个类别的特定关键字的高级搜索标准来挑选提交消息。然后,我们将每一类提交消息分配给有经验的开发人员,让他们逐一检查提交是否属于他们的类别。之后,我们通过在开发人员之间交换提交消息的类别来交叉检查流程,以减轻手动标记带来的主观性。最后,我们得到了将近 3400 条提交消息,包括 282 条不属于任何类别的提交;我们决定在分类任务中添加一个标签,这样以后我们就可以检测到类似的提交消息。
分类:
让我们来看看数据:
import pandas as pd
import re
import numpy as np
import matplotlib.pyplot as plt
from nltk.corpus import stopwords
from nltk.stem import PorterStemmer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report, confusion_matrix
from sklearn.model_selection import train_test_split
from sklearn.feature_selection import SelectKBest, chi2
from sqlite3 import Error
from sklearn.ensemble import RandomForestClassifier
import sqlite3
import pickle
%matplotlib inlinetry:
conn = sqlite3.connect("training_V2.db")
except Error as e:
print(e)#reading the data from the table that contains the labels
df = pd.read_sql_query('SELECT * FROM filtered', conn)
df.drop(['id'], 1, inplace=True)
df.head()
如你所见,我们导入了如此多的包,我们将使用它们来实现我们的目标。查看数据帧的前五行,我们可以看到它只有两列:文本(提交消息)和类(标签)。
- 1 表示用于修复错误的提交消息。
- 2 表示不属于任何类别的提交。
- 3 代表设计改进的提交消息。
- 4 表示用于添加新特征的提交消息。
- 5 表示用于改进非功能性需求的提交消息。
让我们看看它们的分布情况:
df.groupby('class').text.count().plot.bar(ylim=0)
plt.show()
数据集中有一点不平衡是很自然的,因为提交是从随机的开源项目中随机收集的。
现在让我们建立我们的分类器。但是等等……我们的数据是自然文本,但是需要格式化成列结构,以便作为分类算法的输入。
它是这样工作的:
文本预处理:
- 首先,我们从每个提交消息中删除标点、数字和停用词。
- 其次,所有的单词都被转换成小写,然后使用 NLTK 包中的斯特梅尔进行词干分析。词干提取的目标是减少提交中出现的单词的屈折形式的数量;它将通过将诸如“performance”和“performing”这样的词简化为它们的基本词“perform”来使它们在句法上相互匹配。这有助于减少词汇空间的大小,并提高语料库中特征空间的容量。
- 最后,使用 Python 的 SKlearn 包中的 tf-idf 矢量器将每个语料库转换为向量空间模型(VSM)来提取特征。
所有这些都是用几行代码完成的,这就是 python 的魅力所在。
stemmer = PorterStemmer()
words = stopwords.words("english")df['cleaned'] = df['text'].apply(lambda x: " ".join([stemmer.stem(i) for i in re.sub("[^a-zA-Z]", " ", x).split() if i not in words]).lower())
vectorizer = TfidfVectorizer(min_df= 3, stop_words="english", sublinear_tf=True, norm='l2', ngram_range=(1, 2))
final_features = vectorizer.fit_transform(df['cleaned']).toarray()
final_features.shape(3377, 2389)
因此,在将文本转换成向量后,我们最终得到了 2389 个特征。现在,我们继续构建我们的模型,并用我们的数据拟合模型。这里我们应该问的另一个重要问题是**我们真的需要使用所有的 2389 特性吗?**当处理非常长的向量时,有时选择你的最佳特征可能比使用所有特征更好。为此,我们使用 SKlearn.feature_selection 包中的 SelectKbest 方法。然后,我们使用 Chi2 分数来选择具有最高值的 n_features 特征,以进行卡方检验。 " **卡方检验测量随机变量之间的相关性,因此使用此函数“剔除”最有可能与类别无关的特征,因此与分类无关。**引自 sklearn 文档。
在构建模型时,我们可以从广泛的分类算法中进行选择。我们测试了四种不同的算法: KNN、多项式朴素贝叶斯、线性 SVC 和随机福里斯特。我研究了每个分类器,并使用分类性能的常用统计方法(精确度和召回率)来比较每个分类器,得出结论,Random Forrest 似乎比其他分类器表现得更好。根据我自己的经验,当你有一个多类问题时,Random Forrest 做得更好。您可以测试其他分类算法,如果您发现一些提供更好结果的算法,请告诉我。
overall workflow
现在让我们把所有东西都放到一个管道里。这里我们执行一系列转换:首先我们转换 tfidf 矢量器,然后我们选择 K 个最佳特征,最后我们使用分类器来拟合数据。所以我们使用一个管道同时完成所有这些任务:
#first we split our dataset into testing and training set:
# this block is to split the dataset into training and testing set
X = df['cleaned']
Y = df['class']
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.25)# instead of doing these steps one at a time, we can use a pipeline to complete them all at once
pipeline = Pipeline([('vect', vectorizer),
('chi', SelectKBest(chi2, k=1200)),
('clf', RandomForestClassifier())])# fitting our model and save it in a pickle for later use
model = pipeline.fit(X_train, y_train)
with open('RandomForest.pickle', 'wb') as f:
pickle.dump(model, f)ytest = np.array(y_test)# confusion matrix and classification report(precision, recall, F1-score)
print(classification_report(ytest, model.predict(X_test)))
print(confusion_matrix(ytest, model.predict(X_test))) precision recall f1-score support
1 0.99 0.98 0.99 271
2 0.91 0.91 0.91 47
3 0.98 1.00 0.99 116
4 0.99 0.99 0.99 143
5 0.98 0.99 0.98 268
avg / total 0.98 0.98 0.98 845Confusion Matrix: 1 2 3 4 5
1[[266 1 0 0 4]
2[ 2 43 0 1 1]
3[ 0 0 116 0 0]
4[ 0 2 0 141 0]
5[ 0 1 2 1 264]]
0.98 精度和召回…!!!嗯……这是我的第一反应。但是后来我查看了混淆矩阵,发现每个类都没有太多的错误分类,我认为我们的情况不错。我们甚至没有对分类算法进行任何参数调整就实现了高精度和高召回率!
现在我真的想看看这是否适用于另一个数据集。此外,我想使用其他指标对此进行评估,如:AUC 得分,并查看 ROC 曲线,因为数据有点不平衡。为了有一个更完整的理解,还有两件事要做:首先是使用 gridsearch 来调整参数,看看它是否真的有助于改善结果。第二,每个类别最好有 5 个最重要的关键词,这样以后我们可以用这些关键词来进行搜索。但是这篇文章已经够长了,所以我将在下一篇文章中回答这些问题。敬请期待!
非常感谢您的反馈和问题。
我的github上有这个项目的源代码、python 笔记本和数据集。
时尚 MNIST 的多标签分类和类别激活图
时尚-MNIST 是一个时尚产品图像数据集,用于对计算机视觉的机器学习算法进行基准测试。该数据集包括 60,000 幅 28x28 的训练图像和 10,000 幅 28x28 的测试图像,包括 10 类时尚产品。图 1 显示了所有的标签和一些图像在时尚-MNIST 。
Figure 1. Fashion-MNIST dataset. [github and arxiv]
有很多关于时尚的文章——MNIST[ref]。然而,这篇文章的目标是在多标签分类的背景下,而不是在多类分类的背景下,提出一项关于时尚 MNIST 的深度学习的研究。此外,这篇文章还研究了我们是否可以可视化卷积神经网络如何看待图像,并识别激活特定标签的区域。
1.可视化,可视化,可视化
在进入所有细节之前,让我们想象一下我们能达到的一些结果。
Figure 2. (a) A multi-label image, (b) The predicted probabilities over labels, © The class activation maps for the labels with higher probabilities.
让我们逐一分解图 2 中的图表。
- 第一个图显示了一个多标签图像,由来自时尚-MNIST 的四个时尚产品图像组成:一条裤子、一双运动鞋、一个包和一件衣服。与该多标签图像相关联的标签是 1 、7、8 和 3。请注意,实际上我们在这项研究中考虑的是标签的存在(例如 1/3/7/8)而不是标签的顺序(例如 1/7/8/3)。如果一个图像只包含两类时尚产品,那么只有两个标签会被分配给该图像。
- 给出了多标签图像,第二张图显示了由我们训练的卷积神经网络计算的所有标签的预测概率。请注意,标签 1 、标签 3 、标签 7 和标签 8 的概率都接近 1,这表明我们训练的卷积神经网络在给定图像上标记正确标签方面做得很好。
- 第三张图显示了与前四个预测标签相关的热图,即以较高概率激活标签的区域。突出显示热图(也称为类激活图)的目的是打开我们的卷积神经网络的黑盒,看看它是否真的精确定位了进行预测的关键区域。
再举两个例子:一个有 3 个标签(图 3),一个有 2 个标签(图 4)。
Figure 3. An image with 3 labels: 2, 3, and 8.
Figure 4. An image with 2 labels: 3 and 9.
图 5 给出了超过 9000 幅用于训练的图像和 1000 幅用于验证的图像的总体训练历史。在 1000 张测试图像上的准确率在 95% ~ 96%左右。该程序是在 Keras 中实现的,后台是 Theano。
Figure 5. Training history on accuracy and loss.
2.多标签时尚-MNIST
以下是我们用于多标签分类的新数据集的简介:
- 10000 幅 646×184 的训练图像和 1000 幅 646×184 的测试图像;
- 每张图片有四张从时尚-MNIST 中随机选取的时尚产品图片;
- 元数据文件保存了图像的有序标签,以及它的单主机编码方案。
图 6 显示了我们数据集中的一些图像,图 7 显示了元数据文件中的一些信息。
Figure 6. Sample images in Multi-Label Fashion-MNIST.
Figure 7. Sample meta-data in Multi-Label Fashion-MNIST.
3.神经网络体系结构
我们神经网络的重点是将执行多标签分类和生成类激活图的基本机制集成在一起。我们将遍历执行多标签分类的基本框架,然后检查生成类激活图所需的增强。
首先,让我们比较实现多类分类(其中一个图像仅属于一个类)和多标签分类(其中一个图像可以与多个标签相关联)的常见做法。)
- 用于图像分类的基本神经网络架构通常由用于特征提取的卷积层和最大池层的堆叠组成,随后是用于类别/标签预测的一些全连接层。
- 对于多类分类,模型中的最后一层使用 softmax 函数进行类预测,训练过程使用category _ cross entropy函数作为损失函数。
- 对于多标签分类,模型中的最后一层使用 sigmoid 函数进行标签预测,训练过程使用 binary_crossentropy 函数作为损失函数。
上述实践基于在深度学习中实现最后一层激活函数和损失函数的传统智慧,如图 8 所示。
Figure 8. Summary of last-layer activation function and loss function. [Deep Learning with Python, Ch4/Ch9**]**
现在,让我们考虑生成类激活映射的机制。有许多不同的方法来创建类激活映射。我们的想法和代码基于用于对象本地化的全局平均池层。这种方法有两个要点。首先,给定类的类激活图被视为最后一个卷积层的特征图的加权和。其次,全局平均池图层用于将要素地图转换为单个值,并作为计算相关权重的粘合剂。原始论文中的图 9 展示了完整的过程。
Figure 9. Class Activation Mapping. [ref]
图 10 显示了 Keras 中用于构建和训练深度神经网络的代码。图 11 显示了整个模型的概要。
Figure 10. The code for building and training the deep neural network.
Figure 11. The summary of the whole model.
图 12 显示了创建类激活映射的代码。
Figure 12. The code for creating a class activation map.
4.结论
这篇文章介绍了在多标签分类的背景下为时尚-MNIST 进行的深度学习研究。我们还试图打开我们的卷积神经网络的黑盒,以查看预测标签的类激活图。这项研究中最有趣但最具挑战性的问题是用全局平均池函数实现和验证类激活映射,特别是当一个映射看起来不正确时。最后,让我用两个数字来说明今后工作的方向。
Ref-1. DeepFashion
Ref-2. Grad-CAM
还有一件事…你喜欢 MNIST 吗?
Ref-3. DO YOU MNIST from anztee.com and teezily.com
基于初始网的多标签图像分类
Inception v3 architecture. (source)
**更新:**本文已更新,修复了特里萨·巴顿指出的精度计算可能存在的问题。 git 回购也已经更新,你可以在那里找到所有的变化。
Inception v3 是一个深度卷积神经网络,在 ImageNet 数据集上训练用于单标签图像分类。TensorFlow 团队已经准备了一个教程来重新训练它,根据我们自己的例子来区分一些类。我们将修改教程中的再训练脚本 retrain.py ,将网络改为多标签分类器。
如果你只是想跳转到结果代码,这里的是所有必要的文件和信息,需要让它工作。从现在开始,我将假设您已经克隆了提到的存储库并引用了它的文件。
那么需要做些什么呢?首先,我们必须以某种方式告诉网络哪一个是每个图像的正确标签。然后,我们必须修改正在重新训练的最后一层和评估生成的预测的方法,以便实际上能够针对每个图像的多个可能的正确类别来训练它。
要求
- 张量流 0.12.0-rc1
- JPEG 格式的训练图像
- 修改后的源代码与示例
数据预处理
准备训练图像
- 将所有训练图像放入图像目录下的一个文件夹中。
尽量去掉所有重复的图像,它们可能会人为地夸大测试和验证的准确性。文件夹的名称并不重要。我用的是多标。
为每个训练图像准备标签
我们需要为每个图像准备带有正确标签的文件。将文件命名为**<image _ file _ name . jpg>。txt** =如果你有一张图片【car.jpg.txt】,附带的文件应该命名为 car.jpg.txt 。
将每个标签放在文件中的一个新行上,不要做其他事情。
现在将所有创建的文件复制到项目根目录下的 image_labels_dir 目录中。您可以通过编辑 retrain.py 中的全局变量 IMAGE_LABELS_DIR 来更改该文件夹的路径。
创建包含所有标签的文件
最初的 Inception net 使用一个文件夹结构来导出类的列表。在我们的例子中,所有的训练图像都在一个文件夹中,因此我们需要在一个外部文件中列出这些类。
在项目根目录下创建文件 labels.txt ,并用所有可能的标签填充它。每个标签在一个新的行上,没有别的。就像所有可能的类中的图像的 image_label 文件一样。
修改总管
main()方法最初将包含每个标签的图像的目录结构加载到单独的文件夹中,并通过以下方式为每个类创建了验证、测试和训练集合:
image_lists = create_image_lists(FLAGS.image_dir, FLAGS.testing_percentage,cFLAGS.validation_percentage)
我们现在在一个目录中有所有的图像,因此 image_lists.keys() 只包含一个元素,那就是我们所有图像的文件夹(例如多标签)。所有的训练图像被分成验证、测试和训练组,通过该键可以进入。
现在我们已经正确地分割了数据,我们只需要加载标签列表并计算类计数:
**with** open(ALL_LABELS_FILE) **as** f:
labels = f.read().splitlines()
class_count = len(labels)
创建地面真实向量
- 添加get _ image _ labels _ path()方法,该方法只是稍微编辑了一下 get_image_path() 方法返回包含正确图像标签的文件的路径=例如image _ labels _ dir/car . jpg . txt对于car.jpg。
- 编辑**get _ random _ cached _ 瓶颈()**方法:
该方法创建包含每个返回图像的正确标签的 ground_truth 向量。最初它只是创建了一个零向量:
ground_truth = np.zeros(class_count, dtype=np.float32)
然后在正确的标签位置放一个 1.0,这是我们知道的,因为这是我们从中获取图像的文件夹的名称:
ground_truth[label_index] = 1.0
多标签分类没那么简单。我们需要从 image_label_file 中为给定的图像加载所有正确的标签。
获取带有正确标签的文件路径:
labels_file = get_image_labels_path(image_lists,label_name,image_index, IMAGE_LABELS_DIR, category)
从文件中读取所有行=标签,并保存到数组 true_labels :
**with** open(labels_file) **as** f:
true_labels = f.read().splitlines()
用零初始化 ground_truth 向量:
ground_truth = np.zeros(class_count, dtype=np.float32)
用 1.0 表示 ground_truth 向量中的正确标签:
idx = 0
**for** label **in** labels:
**if** label **in** true_labels:
ground_truth[idx] = 1.0
idx += 1
标签列表是添加到**get _ random _ cached _ bottoms()**方法的参数,包含所有可能的类的名称。
就是这样!我们可以通过缓存创建的地面真相来改进这个解决方案。这防止了每次我们为相同的图像请求时创建地面真实向量,如果我们为多个时期训练,这是必然发生的。这就是全局字典 CACHED_GROUND_TRUTH_VECTORS 的用途。
修改培训
add_final_training_ops() 方法最初添加了一个新的 softmax 和全连接层用于训练。我们只需要用一个不同的函数替换 softmax 函数。
为什么?
softmax 函数将向量的所有值压缩到[0,1]范围内,总和为 1。这正是我们在单一标签分类中想要的。但是对于我们的多标签情况,我们希望我们得到的类别概率能够表示汽车的图像属于类别汽车的概率为 90%,属于类别事故的概率为 30%,等等。
我们将通过使用例如 sigmoid 函数来实现这一点。
具体来说,我们将替换:
final_tensor = tf.nn.softmax(logits, name=final_tensor_name)
使用:
final_tensor = tf.nn.sigmoid(logits, name=final_tensor_name)
我们还必须更新交叉熵的计算方法,以正确训练我们的网络:
同样,只需将 softmax 替换为 sigmoid:
cross_entropy = tf.nn.sigmoid_cross_entropy_with_logits(logits,ground_truth_input)
修改评估
方法 add_evaluation_step() 插入我们需要评估预测标签准确性的操作。最初看起来是这样的:
correct_prediction = tf.equal(tf.argmax(result_tensor, 1), tf.argmax(ground_truth_tensor, 1))
好吧,这是怎么回事?
result_tensor 和 ground_truth_tensor 都可以想象成 2D 数组:
| | label1 | label2 | label3 |
| image1 | 0 | 1 | 0 |
| image2 | 1 | 0 | 0 |
因此这一行:
tf.argmax(result_tensor, 1)
返回每个行中最大值的索引。每一行因为(axis = 1 参数。
我们将获得具有最高值的索引并比较它们,同时知道因为只有一个标签是正确的,所以地面 _ 真相 _ 张量在每行中只包含一个 1 。
为了使这种方法适应我们的多标签情况,我们简单地用 round() 替换 argmax() ,这将概率变成 0 和 1。然后,我们将结果张量与已经只包含 0 和 1 的基础张量进行比较:
correct_prediction = tf.equal(tf.round(result_tensor), ground_truth_tensor)
这就是我们为正确分类带有多个标签的图像所需要做的所有更改。
运行再培训
只需从项目根目录运行以下命令:
python retrain.py \
--bottleneck_dir=bottlenecks \
--how_many_training_steps 500 \
--model_dir=model_dir \
--output_graph=retrained_graph.pb \
--output_labels=retrained_labels.txt \
--summaries_dir=retrain_logs \
--image_dir=images
我建议调整训练步骤的数量,以防止过度适应你的模型。
测试重新训练的模型
运行:
python label_image.py <image_name>
我稍微修改了一下 label_image.py ,将得到的类百分比写入 results.txt 。
可视化培训
重新训练完成后,您可以通过运行以下命令来查看日志:
tensorboard --logdir retrain_logs
并在浏览器中导航至 http://127.0.0.1:6006/ 。
结束了
我希望我尽可能清楚地解释了所有的变化及其背后的原因,希望你今天学到了一些新东西:)
如果你有进一步的问题,你可以在 linkedin 上找到我或者直接发邮件给我。
多标签意图分类
有很多应用需要文本分类,或者我们可以说意图分类。现在,所有的东西都需要分类,就像内容一样,产品也经常被分类。
但是网上绝大多数的文本分类文章和教程都是邮件垃圾过滤(spam vs. ham)、情感分析(正面 vs .负面)等二元文本分类。我们现实世界的问题要比这复杂得多。因此,这就是我要在这篇博客中解释的。将文本分类成多个类别。
问题陈述:
我为我的 GSoC 项目(Owasp SKF 聊天机器人)开发了这个分类器。问题陈述是针对不同漏洞的安全知识框架知识库。它提供了针对不同漏洞的描述、解决方案和代码示例。所以,我需要对用户的查询进行分类,无论他是要求描述、解决方案还是代码示例。
我使用了 Python 和 Jupyter Notebook 来开发我们的系统,依靠 Scikit-Learn 作为机器学习组件。
数据集的准备:
对于任何与分类或机器学习相关的问题,我们首先需要的是格式过于正确的数据。因此,首先我将解释我如何准备意图分类的数据集。
import json
import csv
with open("data.json",encoding='utf-8') as read_file:
data = json.load(read_file)
可以在这里查看 data.json 。我将准备 CSV 格式的数据集,因为它很容易训练模型。
#For parsing the Json
a=data['items']
#Declaration of liststitle=[]
ques=[]
sol=[]
code=[]#For accessing the title from Json and storing it in the list.
for d in a:
title.append((d['title']))
我的文本分类是针对问答类系统的。因此,我需要生成问题,因为我提取了列表中的所有标题。
for t in title:
ques.append("What is "+ t + " ?")
ques.append("What does "+ t + " mean ?")
ques.append("Tell me something about "+ t + " ?")
ques.append(t)
ques.append("Explain " + t +" ?")
ques.append("Elaborate " + t +" ?")
ques.append("Can you tell me about " + t + " ?")
ques.append("What do you know about " + t + " ?")
ques.append("What can you tell me about " + t + " ?")
ques.append("I want to know about XSS " + t )
ques.append("Do you have information about " + t + " ?")for t in title:
sol.append("How to solve "+ t + " ?")
sol.append("How to resolve "+ t + " ?")
sol.append("How to mitigate "+ t + " ?")
sol.append("Solution for "+ t)
sol.append("Provide me some Solution for "+ t)
sol.append("mitigation for "+ t)
sol.append("How to stop "+ t + " ?")
sol.append("How to defend "+ t + " ?")
sol.append("How to get secured against "+ t + " ?")
sol.append("Solution, "+t)
for t in title:
code.append("Give me some sample code of "+ t )
code.append("Code example of "+ t + " ?")
code.append("Code of "+ t )
因此,问题需要根据描述、解决方案和代码进行分类。所以,我做了三个列表,每个列表存储一个问题。
file=open("intent_data.csv","x")
file.write('class,question\n')
for x in ques:
x=x.replace(",","")
file.write('Description, '+x+"\n")
for y in sol:
y=y.replace(",","")
file.write('Solution, '+y+"\n")
for z in code:
z=z.replace(",","")
file.write('Code, '+z+"\n")
file.close()
所以,现在数据准备好了。你可以在这里查看完整数据。
为了更好地理解如何准备数据集,您也可以查看这个 jupyter 笔记本示例。
现在,我们已经准备好数据😄…是时候用它变变魔术了。让我们看看如何对意图进行分类。
让我们导入一些库:
import pandas as pd
from io import StringIO
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.naive_bayes import MultinomialNB
首先,我们将从 CSV 文件中提取数据,并将其存储在数据帧中。
def get_data():
df = pd.read_csv('datasets/intent_data.csv')
return df
在这一步中,我们将准备好数据以将其输入算法。首先,我们在**‘y’**中获得完整的数据,并使用
**y=[col]**
之后,使用’ pd.notnull’ 来检查问题列中的数据是否为空,如果为空,则将删除整行。
这一步对于获得高质量的干净数据非常重要。因为,如果我们有好的数据,我们就会有好的结果。😃
我们将创建一个新的列**‘category _ id’**,它将给出一个班级编号。类似于描述,它将是 0,解决方案 1 和代码 2。
删除重复后,我们将得到类似这样的东西
category_id_df = y[['classs', 'category_id']].**drop_duplicates()**.sort_values('category_id')
**print(category_id_df)****classs category_id**
0 Description 0
2 Solution 1
5081 Code 2
可以查看下面 data_prepare 的代码片段。
def data_prepare():
col = ['classs', 'question']
y=get_data()
y = y[col]
y = y[pd.notnull(y['question'])]
y.columns = ['classs', 'question']
y['category_id'] = y['classs'].factorize()[0]
category_id_df = y[['classs', 'category_id']].drop_duplicates().sort_values('category_id')
category_to_id = dict(category_id_df.values)
id_to_category = dict(category_id_df[['category_id', 'classs']].values) **#This will add the column in our dataframe**
return y
我使用了多项式朴素贝叶斯算法进行预测,因为我发现它易于实现并且具有很高的准确性。
OneVsRest 策略可用于多标签学习,例如,使用分类器来预测多个标签。朴素贝叶斯支持多类,但我们处于多标签场景中,因此,我们将朴素贝叶斯包装在 OneVsRestClassifier 中。
OneVsRest 多标签策略
多标签算法接受多个标签上的二进制掩码。每个预测的结果将是一个由 0 和 1 组成的数组,用于标记哪些类标签适用于每个行输入样本。
为了更好地理解下面的代码片段和多项式 Naive_bayes,请尝试这个。
一个简短的概述是:在这里,我将我的数据分为测试数据和训练数据,然后将这些数据输入到模型中。
def naive_algo():
tfidf = TfidfVectorizer(sublinear_tf=True, min_df=5, norm='l2', encoding='latin-1', ngram_range=(1, 2), stop_words='english')
df=data_prepare()
features = tfidf.fit_transform(df.question).toarray()
labels = df.category_id
features.shape
X_train, X_test, y_train, y_test = train_test_split(df['question'], df['classs'], random_state = 0)
count_vect = CountVectorizer()
X_train_counts = count_vect.fit_transform(X_train)
tfidf_transformer = TfidfTransformer()
X_train_tfidf = tfidf_transformer.fit_transform(X_train_counts)
clf = MultinomialNB().fit(X_train_tfidf, y_train)
return clf,count_vect
我还尝试了其他算法或模型,如线性 SVC、逻辑回归和随机森林。你可以看看这里的那个。
这将给我们最终的预测。
def predict(question):
clf,count_vect=naive_algo()
intent=clf.predict(count_vect.transform([question]))
intent=str(intent).strip("['']")
return intent
最后,我们输入问题,并将其传递给 predict 函数,然后等待变魔术。😝
ques=input("Enter your question ")
x=predict(ques)
漂亮,准确,不是吗?
您可以查看本笔记本以更好地了解意图分类。
感谢阅读!如果你喜欢它,请鼓掌,评论(评论或质疑)并分享它😄
更多内容,请点击下面的“给我买杯咖啡”图标支持我。