十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
1、 数据类型转换:因”gender”实际代表男性、女性用户,故需要将“gender”变量转换成因子变量,且因子水平用“F”替换1,“M”替换2;将“fraudRisk”变量也转换成因子变量。
创新互联公司专注为客户提供全方位的互联网综合服务,包含不限于成都做网站、成都网站设计、重庆网络推广、成都小程序开发、重庆网络营销、重庆企业策划、重庆品牌公关、搜索引擎seo、人物专访、企业宣传片、企业代运营等,从售前售中售后,我们都将竭诚为您服务,您的肯定,是我们最大的嘉奖;创新互联公司为所有大学生创业者提供重庆建站搭建服务,24小时服务热线:18982081108,官方网址:www.cdcxhl.com
答:首先将数据导入到R中,并查看数据维度:
# 导入数据 ccFraud - read.csv("ccFraud.csv") # 查看数据维度 str(ccFraud)'data.frame': 10000000 obs. of 9 variables:$ custID : int 1 2 3 4 5 6 7 8 9 10 ...$ gender : int 1 2 2 1 1 2 1 1 2 1 ...$ state : int 35 2 2 15 46 44 3 10 32 23 ...$ cardholder : int 1 1 1 1 1 2 1 1 1 1 ...$ balance : int 3000 0 0 0 0 5546 2000 6016 2428 0 ...$ numTrans : int 4 9 27 12 11 21 41 20 4 18 ...$ numIntlTrans: int 14 0 9 0 16 0 0 3 10 56 ...$ creditLine : int 2 18 16 5 7 13 1 6 22 5 ...$ fraudRisk : int 0 0 0 0 0 0 0 0 0 0 ...
ccFraud数据集一共有一千万行9列,各列均为整型变量。按照题目要求先将“gender”变量转变为因子型,且因子水平用“F”替换1,“M”替换2。代码如下:
ccFraud$gender - factor(ifelse(ccFraud$gender==1,'F','M')) str(ccFraud)'data.frame': 10000000 obs. of 9 variables:$ custID : int 1 2 3 4 5 6 7 8 9 10 ...$ gender : Factor w/ 2 levels "F","M": 1 2 2 1 1 2 1 1 2 1 ...$ state : int 35 2 2 15 46 44 3 10 32 23 ...$ cardholder : int 1 1 1 1 1 2 1 1 1 1 ...$ balance : int 3000 0 0 0 0 5546 2000 6016 2428 0 ...$ numTrans : int 4 9 27 12 11 21 41 20 4 18 ...$ numIntlTrans: int 14 0 9 0 16 0 0 3 10 56 ...$ creditLine : int 2 18 16 5 7 13 1 6 22 5 ...$ fraudRisk : int 0 0 0 0 0 0 0 0 0 0 ...
将“fraudRisk”变量也转换成因子变量,代码如下:
ccFraud$fraudRisk - as.factor(ccFraud$fraudRisk)str(ccFraud)'data.frame': 10000000 obs. of 9 variables: $ custID : int 1 2 3 4 5 6 7 8 9 10 ... $ gender : Factor w/ 2 levels "F","M": 1 2 2 1 1 2 1 1 2 1... $ state : int 35 2 2 15 46 44 3 10 32 23... $ cardholder : int 1 1 1 1 1 2 1 1 1 1 ... $ balance : int 3000 0 0 0 0 5546 2000 60162428 0 ... $ numTrans : int 4 9 27 12 11 21 41 20 4 18... $ numIntlTrans: int 14 0 9 0 16 0 0 3 10 56 ... $ creditLine : int 2 18 16 5 7 13 1 6 22 5 ... $ fraudRisk : Factor w/ 2 levels"0","1": 1 1 1 1 1 1 1 1 1 1 ...
2、 数据探索:查看“fraudRisk”变量中0、1的频数及占比情况。
答:此题是送分题,通过table函数、prop.table函数即可实现。代码如下:
table(ccFraud$fraudRisk) 0 1 9403986 596014 prop.table(table(ccFraud$fraudRisk)) 0 1 0.9403986 0.0596014
3、 数据分区:需要按照变量fraudRisk来进行等比例抽样,其中80%作为训练集train数据,20%作为测试集test数据。
答:由于要根据fraudRisk变量进行等比例抽样,我们用caret包的createDataPartition函数实现。代码如下:
library(caret)载入需要的程辑包:lattice载入需要的程辑包:ggplot2 idx - createDataPartition(ccFraud$fraudRisk,p=0.8,list=F) train - ccFraud[idx,] test - ccFraud[-idx,] prop.table(table(train$fraudRisk)) 0 1 0.94039851 0.05960149 prop.table(table(test$fraudRisk)) 0 1 0.94039897 0.05960103
4、 建立模型:利用至少三种常见的分类算法(如KNN近邻算法、决策树算法、随机森林等)对数据建立预测模型。
答:由于数据量较大,学员反馈运行慢,这边利用MicrosoftML包来跑模型。关于MRS的快速入门请查阅之前文章:
# 模型一:利用MicrosoftML包的rxFastTrees()函数构建快速决策树模型 (a - Sys.time()) #模型运行前时间[1] "2017-09-03 23:32:04 CST" treeModel - rxFastTrees(fraudRisk ~ gender + cardholder + balance + numTrans+ + numIntlTrans + creditLine,data = train)Not adding a normalizer.Making per-feature arraysChanging data from row-wise to column-wiseBeginning processing data.Rows Read: 8000001, Read Time: 0, Transform Time: 0Beginning processing data.Processed 8000001 instancesBinning and forming Feature objectsReserved memory for tree learner: 79664 bytesStarting to train ...Not training a calibrator because it is not needed.Elapsed time: 00:01:04.6222538 (b - Sys.time()) #模型运行后时间[1] "2017-09-03 23:33:09 CST" b-a # 模型运行时长Time difference of 1.086313 mins # 模型二:利用MicrosoftML包的rxFastForest()函数构建快速随机森林模型 (a - Sys.time()) #模型运行前时间[1] "2017-09-03 23:33:31 CST" forestModel - rxFastForest(fraudRisk ~ gender + cardholder + balance + numTrans+ + numIntlTrans + creditLine,data = train)Not adding a normalizer.Making per-feature arraysChanging data from row-wise to column-wiseBeginning processing data.Rows Read: 8000001, Read Time: 0, Transform Time: 0Beginning processing data.Processed 8000001 instancesBinning and forming Feature objectsReserved memory for tree learner: 79664 bytesStarting to train ...Training calibrator.Beginning processing data.Rows Read: 8000001, Read Time: 0, Transform Time: 0Beginning processing data.Elapsed time: 00:01:25.4585776 (b - Sys.time()) #FastTrees模型运行后时间[1] "2017-09-03 23:34:57 CST" b-a # 模型运行时长Time difference of 1.433823 mins # 模型三:利用MicrosoftML包的rxLogisticRegression()函数构建快速逻辑回归模型 (a - Sys.time()) #模型运行前时间[1] "2017-09-03 23:34:57 CST" logitModel - rxLogisticRegression(fraudRisk ~ gender + cardholder + balance + numTrans+ + numIntlTrans + creditLine,data = train)Automatically adding a MinMax normalization transform, use 'norm=Warn' or 'norm=No' to turn this behavior off.Beginning processing data.Rows Read: 8000001, Read Time: 0, Transform Time: 0Beginning processing data.Beginning processing data.Rows Read: 8000001, Read Time: 0, Transform Time: 0Beginning processing data.Beginning processing data.Rows Read: 8000001, Read Time: 0, Transform Time: 0Beginning processing data.LBFGS multi-threading will attempt to load dataset into memory. In case of out-of-memory issues, turn off multi-threading by setting trainThreads to 1.Beginning optimizationnum vars: 8improvement criterion: Mean ImprovementL1 regularization selected 8 of 8 weights.Not training a calibrator because it is not needed.Elapsed time: 00:00:19.5887244Elapsed time: 00:00:00.0383181 (b - Sys.time()) #模型运行后时间[1] "2017-09-03 23:35:17 CST" b-a # 模型运行时长Time difference of 20.27396 secs
逻辑回归模型运行时间最短,消耗20.3秒,其次是决策树,消耗1.08分钟,时间最长的是随机森林,时长为1.4分钟。
5、 模型评估:利用上面建立的预测模型(至少第四点建立的三个模型),对训练集和测试集数据进行预测,评估模型效果,最终选择最优模型作为以后的业务预测模型。(提示:构建混淆矩阵)
答:针对上面的三种模型,我们分别对train、test数据集进行预测并评估。
# 利用决策树模型对数据进行预测,并计算误差率 treePred_tr - rxPredict(treeModel,data = train)Beginning processing data.Rows Read: 8000001, Read Time: 0, Transform Time: 0Beginning processing data.Elapsed time: 00:00:52.1015119Finished writing 8000001 rows.Writing completed. t - table(train$fraudRisk,treePred_tr$PredictedLabel) t 0 1 0 7446742 76447 1 253008 223804 (paste0(round((sum(t)-sum(diag(t)))/sum(t),3)*100,"%")) #计算决策树对train数据集的预测误差率[1] "4.1%" treePred_te - rxPredict(treeModel,data = test)Beginning processing data.Rows Read: 1999999, Read Time: 0, Transform Time: 0Beginning processing data.Elapsed time: 00:00:13.4980323Finished writing 1999999 rows.Writing completed. t1 - table(test$fraudRisk,treePred_te$PredictedLabel) t1 0 1 0 1861406 19391 1 63176 56026 (paste0(round((sum(t1)-sum(diag(t1)))/sum(t1),3)*100,"%")) #计算决策树对test数据集的预测误差率[1] "4.1%" # 利用随机森林模型对数据进行预测,并计算误差率 forestPred_tr - rxPredict(forestModel,data = train)Beginning processing data.Rows Read: 8000001, Read Time: 0.001, Transform Time: 0Beginning processing data.Elapsed time: 00:00:56.2862657Finished writing 8000001 rows.Writing completed. t - table(train$fraudRisk,forestPred_tr$PredictedLabel) t 0 1 0 7508808 14381 1 373777 103035 (paste0(round((sum(t)-sum(diag(t)))/sum(t),3)*100,"%")) #计算随机森林对train数据集的预测误差率[1] "4.9%" forestPred_te - rxPredict(forestModel,data = test)Beginning processing data.Rows Read: 1999999, Read Time: 0.001, Transform Time: 0Beginning processing data.Elapsed time: 00:00:14.0430130Finished writing 1999999 rows.Writing completed. t1 - table(test$fraudRisk,forestPred_te$PredictedLabel) t1 0 1 0 1877117 3680 1 93419 25783 (paste0(round((sum(t1)-sum(diag(t1)))/sum(t),3)*100,"%")) #计算随机森林对test数据集的预测误差率[1] "1.2%" # 利用逻辑回归模型对数据进行预测,并计算误差率 logitPred_tr - rxPredict(logitModel,data = train)Beginning processing data.Rows Read: 8000001, Read Time: 0.001, Transform Time: 0Beginning processing data.Elapsed time: 00:00:08.1674394Finished writing 8000001 rows.Writing completed. t - table(train$fraudRisk,logitPred_tr$PredictedLabel) t 0 1 0 7444156 79033 1 250679 226133 (paste0(round((sum(t)-sum(diag(t)))/sum(t),3)*100,"%")) #计算逻辑回归对train数据集的预测误差率[1] "4.1%" logitPred_te - rxPredict(logitModel,data = test)Beginning processing data.Rows Read: 1999999, Read Time: 0, Transform Time: 0Beginning processing data.Elapsed time: 00:00:02.0736547Finished writing 1999999 rows.Writing completed. t1 - table(test$fraudRisk,logitPred_te$PredictedLabel) t1 0 1 0 1860885 19912 1 62428 56774 (paste0(round((sum(t1)-sum(diag(t1)))/sum(t),3)*100,"%")) #计算逻辑回归对test数据集的预测误差率[1] "1%"
从训练集和测试集的预测误差率来看,对于此份数据,逻辑回归是最优的选择。
最近在学习 NLP 的相关项目,总觉得不够深入,决定通过写作的方式把自己学到的内容记录下来,分享给大家。并且通过一些通俗易懂的语言让大家最后能自己能动手实现一些NLP 的聊天机器人之类的应用。
先了解如何看懂一个模型评估的指标,譬如 F1-score , Confusion matrix .
在理解 F1-store 之前,首先定义几个概念:
TP(True Positive) : 预测答案正确
FP(False Positive) : 错将其他类预测为本类
FN(False Negative) : 本类标签预测为其他类标
F1分数(F1-score)是分类问题的一个衡量指标, 在 0~1 之间,公式如下:
通过第一步的统计值计算每个类别下的 precision 和 recall
精准度/查准率(precision):指被分类器判定正例中的正样本的比重
召回率/查全率(recall): 指的是被预测为正例的占总的正例的比重
每个类别下的f1-score,计算方式如下:
通过对第三步求得的各个类别下的F1-score求均值,得到最后的评测结果,计算方式如下:
混淆矩阵的每一列代表了预测类别,每一列的总数表示预测为该类别的数据的数目;每一行代表了数据的真实归属类别,每一行的数据总数表示该类别的数据实例的数目。每一列中的数值表示真实数据被预测为该类的数目。
如有150个样本数据,预测为1,2,3类各为50个。分类结束后得到的混淆矩阵为:
每一行之和表示该类别的真实样本数量,每一列之和表示被预测为该类别的样本数量.
巧妇难为无米之炊,语料库就是 NLP 项目中的 "米"。这里使用的是 awesome-chinese-nlp 中列出的中文wikipedia dump和百度百科语料。其中关于wikipedia dump的处理可以参考 这篇帖子 。
我们需要一个规模比较大的中文语料。最好的方法是用对应自己需求的语料,比如做金融的chatbot就多去爬取些财经新闻,做医疗的chatbot就多获取些医疗相关文章。
的执行速度慢上约20倍。无论什么都不能阻止Java语言进行编译。写作本书的时候,刚刚出现了一些准实时编译器,它们能显著加快速度。当然,我们完全有理由认为会出现适用于更多流行平台的纯固有编译器,但假若没有那些编译器,由于速度的限制,必须有些问题是Java不能解决的。
(2) 和C++一样,Java也提供了两种类型的注释。
(3) 所有东西都必须置入一个类。不存在全局函数或者全局数据。如果想获得与全局函数等价的功能,可考虑将static方法和static数据置入一个类里。注意没有象结构、枚举或者联合这一类的东西,一切只有“类”(Class)!
(4) 所有方法都是在类的主体定义的。所以用C++的眼光看,似乎所有函数都已嵌入,但实情并非如何(嵌入的问题在后面讲述)。
(5) 在Java中,类定义采取几乎和C++一样的形式。但没有标志结束的分号。没有class foo这种形式的类声明,只有类定义。
class aType()
void aMethod()
}
(6) Java中没有作用域范围运算符“::”。Java利用点号做所有的事情,但可以不用考虑它,因为只能在一个类里定义元素。即使那些方法定义,也必须在一个类的内部,所以根本没有必要指定作用域的范围。我们注意到的一项差异是对static方法的调用:使用ClassName.methodName()。除此以外,package(包)的名字是用点号建立的,并能用import关键字实现C++的“
import java.awt.*;
(
(7) 与C++类似,Java含有一系列“主类型”(Primitive type),以实现更有效率的访问。在Java中,这些类型包括boolean,char,byte,short,int,long,float以及double。所有主类型的大小都是固有的,且与具体的机器无关(考虑到移植的问题)。这肯定会对性能造成一定的影响,具体取决于不同的机器。对类型的检查和要求在Java里变得更苛刻。例如:
■条件表达式只能是boolean(布尔)类型,不可使用整数。
■必须使用象X+Y这样的一个表达式的结果;不能仅仅用“X+Y”来实现“副作用”。
(8) char(字符)类型使用国际通用的16位Unicode字符集,所以能自动表达大多数国家的字符。
(9) 静态引用的字串会自动转换成String对象。和C及C++不同,没有独立的静态字符数组字串可供使用。
(10) Java增添了三个右移位运算符“”,具有与“逻辑”右移位运算符类似的功用,可在最末尾插入零值。“”则会在移位的同时插入符号位(即“算术”移位)。
(11) 尽管表面上类似,但与C++相比,Java数组采用的是一个颇为不同的结构,并具有独特的行为。有一个只读的length成员,通过它可知道数组有多大。而且一旦超过数组边界,运行期检查会自动丢弃一个异常。所有数组都是在内存“堆”里创建的,我们可将一个数组分配给另一个(只是简单地复制数组句柄)。数组标识符属于第一级对象,它的所有方法通常都适用于其他所有对象。
(12) 对于所有不属于主类型的对象,都只能通过new命令创建。和C++不同,Java没有相应的命令可以“在堆栈上”创建不属于主类型的对象。所有主类型都只能在堆栈上创建,同时不使用new命令。所有主要的类都有自己的“封装(器)”类,所以能够通过new创建等价的、以内存“堆”为基础的对象(主类型数组是一个例外:它们可象C++那样通过集合初始化进行分配,或者使用new)。
(13) Java中不必进行提前声明。若想在定义前使用一个类或方法,只需直接使用它即可——编译器会保证使用恰当的定义。所以和在C++中不同,我们不会碰到任何涉及提前引用的问题。
(14) Java没有预处理机。若想使用另一个库里的类,只需使用import命令,并指定库名即可。不存在类似于预处理机的宏。
(15) Java用包代替了命名空间。由于将所有东西都置入一个类,而且由于采用了一种名为“封装”的机制,它能针对类名进行类似于命名空间分解的操作,所以命名的问题不再进入我们的考虑之列。数据包也会在单独一个库名下收集库的组件。我们只需简单地“import”(导入)一个包,剩下的工作会由编译器自动完成。
(16) 被定义成类成员的对象句柄会自动初始化成null。对基本类数据成员的初始化在Java里得到了可靠的保障。若不明确地进行初始化,它们就会得到一个默认值(零或等价的值)。可对它们进行明确的初始化(显式初始化):要么在类内定义它们,要么在构建器中定义。采用的语法比C++的语法更容易理解,而且对于static和非static成员来说都是固定不变的。我们不必从外部定义static成员的存储方式,这和C++是不同的。
(17) 在Java里,没有象C和C++那样的指针。用new创建一个对象的时候,会获得一个引用(本书一直将其称作“句柄”)。例如:
String s = new String("howdy");
然而,C++引用在创建时必须进行初始化,而且不可重定义到一个不同的位置。但Java引用并不一定局限于创建时的位置。它们可根据情况任意定义,这便消除了对指针的部分需求。在C和C++里大量采用指针的另一个原因是为了能指向任意一个内存位置(这同时会使它们变得不安全,也是Java不提供这一支持的原因)。指针通常被看作在基本变量数组中四处移动的一种有效手段。Java允许我们以更安全的形式达到相同的目标。解决指针问题的终极方法是“固有方法”(已在附录A讨论)。将指针传递给方法时,通常不会带来太大的问题,因为此时没有全局函数,只有类。而且我们可传递对对象的引用。Java语言最开始声称自己“完全不采用指针!”但随着许多程序员都质问没有指针如何工作?于是后来又声明“采用受到限制的指针”。大家可自行判断它是否“真”的是一个指针。但不管在何种情况下,都不存在指针“算术”。
(18) Java提供了与C++类似的“构建器”(Constructor)。如果不自己定义一个,就会获得一个默认构建器。而如果定义了一个非默认的构建器,就不会为我们自动定义默认构建器。这和C++是一样的。注意没有复制构建器,因为所有自变量都是按引用传递的。
(19) Java中没有“破坏器”(Destructor)。变量不存在“作用域”的问题。一个对象的“存在时间”是由对象的存在时间决定的,并非由垃圾收集器决定。有个finalize()方法是每一个类的成员,它在某种程度上类似于C++的“破坏器”。但finalize()是由垃圾收集器调用的,而且只负责释放“资源”(如打开的文件、套接字、端口、URL等等)。如需在一个特定的地点做某样事情,必须创建一个特殊的方法,并调用它,不能依赖finalize()。而在另一方面,C++中的所有对象都会(或者说“应该”)破坏,但并非Java中的所有对象都会被当作“垃圾”收集掉。由于Java不支持破坏器的概念,所以在必要的时候,必须谨慎地创建一个清除方法。而且针对类内的基础类以及成员对象,需要明确调用所有清除方法。
(20) Java具有方法“过载”机制,它的工作原理与C++函数的过载几乎是完全相同的。
(21) Java不支持默认自变量。
(22) Java中没有goto。它采取的无条件跳转机制是“break 标签”或者“continue 标准”,用于跳出当前的多重嵌套循环。
(23) Java采用了一种单根式的分级结构,因此所有对象都是从根类Object统一继承的。而在C++中,我们可在任何地方启动一个新的继承树,所以最后往往看到包含了大量树的“一片森林”。在Java中,我们无论如何都只有一个分级结构。尽管这表面上看似乎造成了限制,但由于我们知道每个对象肯定至少有一个Object接口,所以往往能获得更强大的能力。C++目前似乎是唯一没有强制单根结构的唯一一种OO语言。
(24) Java没有模板或者参数化类型的其他形式。它提供了一系列集合:Vector(向量),Stack(堆栈)以及Hashtable(散列表),用于容纳Object引用。利用这些集合,我们的一系列要求可得到满足。但这些集合并非是为实现象C++“标准模板库”(STL)那样的快速调用而设计的。Java 1.2中的新集合显得更加完整,但仍不具备正宗模板那样的高效率使用手段。
(25) “垃圾收集”意味着在Java中出现内存漏洞的情况会少得多,但也并非完全不可能(若调用一个用于分配存储空间的固有方法,垃圾收集器就不能对其进行跟踪监视)。然而,内存漏洞和资源漏洞多是由于编写不当的finalize()造成的,或是由于在已分配的一个块尾释放一种资源造成的(“破坏器”在此时显得特别方便)。垃圾收集器是在C++基础上的一种极大进步,使许多编程问题消弥于无形之中。但对少数几个垃圾收集器力有不逮的问题,它却是不大适合的。但垃圾收集器的大量优点也使这一处缺点显得微不足道。
(26) Java内建了对多线程的支持。利用一个特殊的Thread类,我们可通过继承创建一个新线程(放弃了run()方法)。若将synchronized(同步)关键字作为方法的一个类型限制符使用,相互排斥现象会在对象这一级发生。在任何给定的时间,只有一个线程能使用一个对象的synchronized方法。在另一方面,一个synchronized方法进入以后,它首先会“锁定”对象,防止其他任何synchronized方法再使用那个对象。只有退出了这个方法,才会将对象“解锁”。在线程之间,我们仍然要负责实现更复杂的同步机制,方法是创建自己的“监视器”类。递归的synchronized方法可以正常运作。若线程的优先等级相同,则时间的“分片”不能得到保证。
(27) 我们不是象C++那样控制声明代码块,而是将访问限定符(public,private和protected)置入每个类成员的定义里。若未规定一个“显式”(明确的)限定符,就会默认为“友好的”(friendly)。这意味着同一个包里的其他元素也可以访问它(相当于它们都成为C++的“friends”——朋友),但不可由包外的任何元素访问。类——以及类内的每个方法——都有一个访问限定符,决定它是否能在文件的外部“可见”。private关键字通常很少在Java中使用,因为与排斥同一个包内其他类的访问相比,“友好的”访问通常更加有用。然而,在多线程的环境中,对private的恰当运用是非常重要的。Java的protected关键字意味着“可由继承者访问,亦可由包内其他元素访问”。注意Java没有与C++的protected关键字等价的元素,后者意味着“只能由继承者访问”(以前可用“private protected”实现这个目的,但这一对关键字的组合已被取消了)。
(28) 嵌套的类。在C++中,对类进行嵌套有助于隐藏名称,并便于代码的组织(但C++的“命名空间”已使名称的隐藏显得多余)。Java的“封装”或“打包”概念等价于C++的命名空间,所以不再是一个问题。Java 1.1引入了“内部类”的概念,它秘密保持指向外部类的一个句柄——创建内部类对象的时候需要用到。这意味着内部类对象也许能访问外部类对象的成员,毋需任何条件——就好象那些成员直接隶属于内部类对象一样。这样便为回调问题提供了一个更优秀的方案——C++是用指向成员的指针解决的。
(29) 由于存在前面介绍的那种内部类,所以Java里没有指向成员的指针。
(30) Java不存在“嵌入”(inline)方法。Java编译器也许会自行决定嵌入一个方法,但我们对此没有更多的控制权力。在Java中,可为一个方法使用final关键字,从而“建议”进行嵌入操作。然而,嵌入函数对于C++的编译器来说也只是一种建议。
(31) Java中的继承具有与C++相同的效果,但采用的语法不同。Java用extends关键字标志从一个基础类的继承,并用super关键字指出准备在基础类中调用的方法,它与我们当前所在的方法具有相同的名字(然而,Java中的super关键字只允许我们访问父类的方法——亦即分级结构的上一级)。通过在C++中设定基础类的作用域,我们可访问位于分级结构较深处的方法。亦可用super关键字调用基础类构建器。正如早先指出的那样,所有类最终都会从Object里自动继承。和C++不同,不存在明确的构建器初始化列表。但编译器会强迫我们在构建器主体的开头进行全部的基础类初始化,而且不允许我们在主体的后面部分进行这一工作。通过组合运用自动初始化以及来自未初始化对象句柄的异常,成员的初始化可得到有效的保证。
1045页程序
(32) Java中的继承不会改变基础类成员的保护级别。我们不能在Java中指定public,private或者protected继承,这一点与C++是相同的。此外,在衍生类中的优先方法不能减少对基础类方法的访问。例如,假设一个成员在基础类中属于public,而我们用另一个方法代替了它,那么用于替换的方法也必须属于public(编译器会自动检查)。
(33) Java提供了一个interface关键字,它的作用是创建抽象基础类的一个等价物。在其中填充抽象方法,且没有数据成员。这样一来,对于仅仅设计成一个接口的东西,以及对于用extends关键字在现有功能基础上的扩展,两者之间便产生了一个明显的差异。不值得用abstract关键字产生一种类似的效果,因为我们不能创建属于那个类的一个对象。一个abstract(抽象)类可包含抽象方法(尽管并不要求在它里面包含什么东西),但它也能包含用于具体实现的代码。因此,它被限制成一个单一的继承。通过与接口联合使用,这一方案避免了对类似于C++虚拟基础类那样的一些机制的需要。
为创建可进行“例示”(即创建一个实例)的一个interface(接口)的版本,需使用implements关键字。它的语法类似于继承的语法,如下所示:
1046页程序
(34) Java中没有virtual关键字,因为所有非static方法都肯定会用到动态绑定。在Java中,程序员不必自行决定是否使用动态绑定。C++之所以采用了virtual,是由于我们对性能进行调整的时候,可通过将其省略,从而获得执行效率的少量提升(或者换句话说:“如果不用,就没必要为它付出代价”)。virtual经常会造成一定程度的混淆,而且获得令人不快的结果。final关键字为性能的调整规定了一些范围——它向编译器指出这种方法不能被取代,所以它的范围可能被静态约束(而且成为嵌入状态,所以使用C++非virtual调用的等价方式)。这些优化工作是由编译器完成的。
(35) Java不提供多重继承机制(MI),至少不象C++那样做。与protected类似,MI表面上是一个很不错的主意,但只有真正面对一个特定的设计问题时,才知道自己需要它。由于Java使用的是“单根”分级结构,所以只有在极少的场合才需要用到MI。interface关键字会帮助我们自动完成多个接口的合并工作。
(36) 运行期的类型标识功能与C++极为相似。例如,为获得与句柄X有关的信息,可使用下述代码:
X.getClass().getName();
为进行一个“类型安全”的紧缩造型,可使用:
derived d = (derived)base;
这与旧式风格的C造型是一样的。编译器会自动调用动态造型机制,不要求使用额外的语法。尽管它并不象C++的“new casts”那样具有易于定位造型的优点,但Java会检查使用情况,并丢弃那些“异常”,所以它不会象C++那样允许坏造型的存在。
(37) Java采取了不同的异常控制机制,因为此时已经不存在构建器。可添加一个finally从句,强制执行特定的语句,以便进行必要的清除工作。Java中的所有异常都是从基础类Throwable里继承而来的,所以可确保我们得到的是一个通用接口。
1047页程序
(38) Java的异常规范比C++的出色得多。丢弃一个错误的异常后,不是象C++那样在运行期间调用一个函数,Java异常规范是在编译期间检查并执行的。除此以外,被取代的方法必须遵守那一方法的基础类版本的异常规范:它们可丢弃指定的异常或者从那些异常衍生出来的其他异常。这样一来,我们最终得到的是更为“健壮”的异常控制代码。
(39) Java具有方法过载的能力,但不允许运算符过载。String类不能用+和+=运算符连接不同的字串,而且String表达式使用自动的类型转换,但那是一种特殊的内建情况。
(40) 通过事先的约定,C++中经常出现的const问题在Java里已得到了控制。我们只能传递指向对象的句柄,本地副本永远不会为我们自动生成。若希望使用类似C++按值传递那样的技术,可调用clone(),生成自变量的一个本地副本(尽管clone()的设计依然尚显粗糙——参见第12章)。根本不存在被自动调用的副本构建器。为创建一个编译期的常数值,可象下面这样编码:
static final int SIZE = 255
static final int BSIZE = 8 * SIZE
(41) 由于安全方面的原因,“应用程序”的编程与“程序片”的编程之间存在着显著的差异。一个最明显的问题是程序片不允许我们进行磁盘的写操作,因为这样做会造成从远程站点下载的、不明来历的程序可能胡乱改写我们的磁盘。随着Java 1.1对数字签名技术的引用,这一情况已有所改观。根据数字签名,我们可确切知道一个程序片的全部作者,并验证他们是否已获得授权。Java 1.2会进一步增强程序片的能力。
(42) 由于Java在某些场合可能显得限制太多,所以有时不愿用它执行象直接访问硬件这样的重要任务。Java解决这个问题的方案是“固有方法”,允许我们调用由其他语言写成的函数(目前只支持C和C++)。这样一来,我们就肯定能够解决与平台有关的问题(采用一种不可移植的形式,但那些代码随后会被隔离起来)。程序片不能调用固有方法,只有应用程序才可以。
(43) Java提供对注释文档的内建支持,所以源码文件也可以包含它们自己的文档。通过一个单独的程序,这些文档信息可以提取出来,并重新格式化成HTML。这无疑是文档管理及应用的极大进步。
(44) Java包含了一些标准库,用于完成特定的任务。C++则依靠一些非标准的、由其他厂商提供的库。这些任务包括(或不久就要包括):
■连网
■数据库连接(通过JDBC)
■多线程
■分布式对象(通过RMI和CORBA)
■压缩
■商贸
由于这些库简单易用,而且非常标准,所以能极大加快应用程序的开发速度。
(45) Java 1.1包含了Java Beans标准,后者可创建在可视编程环境中使用的组件。由于遵守同样的标准,所以可视组件能够在所有厂商的开发环境中使用。由于我们并不依赖一家厂商的方案进行可视组件的设计,所以组件的选择余地会加大,并可提高组件的效能。除此之外,Java Beans的设计非常简单,便于程序员理解;而那些由不同的厂商开发的专用组件框架则要求进行更深入的学习。
(46) 若访问Java句柄失败,就会丢弃一次异常。这种丢弃测试并不一定要正好在使用一个句柄之前进行。根据Java的设计规范,只是说异常必须以某种形式丢弃。许多C++运行期系统也能丢弃那些由于指针错误造成的异常。
(47) Java通常显得更为健壮,为此采取的手段如下:
■对象句柄初始化成null(一个关键字)
■句柄肯定会得到检查,并在出错时丢弃异常
■所有数组访问都会得到检查,及时发现边界违例情况
■自动垃圾收集,防止出现内存漏洞
■明确、“傻瓜式”的异常控制机制
■为多线程提供了简单的语言支持
■对网络程序片进行字节码校验
另外,团IDC网上有许多产品团购,便宜有口碑
参考:
package nlp;
/**
* @author Orisun
* date 2011-10-22
*/
import java.util.ArrayList;
public class BaumWelch {
int M; // 隐藏状态的种数
int N; // 输出活动的种数
double[] PI; // 初始状态概率矩阵
double[][] A; // 状态转移矩阵
double[][] B; // 混淆矩阵
ArrayListInteger observation = new ArrayListInteger(); // 观察到的集合
ArrayListInteger state = new ArrayListInteger(); // 中间状态集合
int[] out_seq = { 2, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1,
1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 2, 1 }; // 测试用的观察序列
int[] hidden_seq = { 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1,
1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1 }; // 测试用的隐藏状态序列
int T = 32; // 序列长度为32
double[][] alpha = new double[T][]; // 向前变量
double PO;
double[][] beta = new double[T][]; // 向后变量
double[][] gamma = new double[T][];
double[][][] xi = new double[T - 1][][];
// 初始化参数。Baum-Welch得到的是局部最优解,所以初始参数直接影响解的好坏
public void initParameters() {
M = 2;
N = 2;
PI = new double[M];
PI[0] = 0.5;
PI[1] = 0.5;
A = new double[M][];
B = new double[M][];
for (int i = 0; i M; i++) {
A[i] = new double[M];
B[i] = new double[N];
}
A[0][0] = 0.8125;
A[0][1] = 0.1875;
A[1][0] = 0.2;
A[1][1] = 0.8;
B[0][0] = 0.875;
B[0][1] = 0.125;
B[1][0] = 0.25;
B[1][1] = 0.75;
observation.add(1);
observation.add(2);
state.add(1);
state.add(2);
for (int t = 0; t T; t++) {
alpha[t] = new double[M];
beta[t] = new double[M];
gamma[t] = new double[M];
}
for (int t = 0; t T - 1; t++) {
xi[t] = new double[M][];
for (int i = 0; i M; i++)
xi[t][i] = new double[M];
}
}
// 更新向前变量
public void updateAlpha() {
for (int i = 0; i M; i++) {
alpha[0][i] = PI[i] * B[i][observation.indexOf(out_seq[0])];
}
for (int t = 1; t T; t++) {
for (int i = 0; i M; i++) {
alpha[t][i] = 0;
for (int j = 0; j M; j++) {
alpha[t][i] += alpha[t - 1][j] * A[j][i];
}
alpha[t][i] *= B[i][observation.indexOf(out_seq[t])];
}
}
}
// 更新观察序列出现的概率,它在一些公式中当分母
public void updatePO() {
for (int i = 0; i M; i++)
PO += alpha[T - 1][i];
}
// 更新向后变量
public void updateBeta() {
for (int i = 0; i M; i++) {
beta[T - 1][i] = 1;
}
for (int t = T - 2; t = 0; t--) {
for (int i = 0; i M; i++) {
for (int j = 0; j M; j++) {
beta[t][i] += A[i][j]
* B[j][observation.indexOf(out_seq[t + 1])]
* beta[t + 1][j];
}
}
}
}
// 更新xi
public void updateXi() {
for (int t = 0; t T - 1; t++) {
double frac = 0.0;
for (int i = 0; i M; i++) {
for (int j = 0; j M; j++) {
frac += alpha[t][i] * A[i][j]
* B[j][observation.indexOf(out_seq[t + 1])]
* beta[t + 1][j];
}
}
for (int i = 0; i M; i++) {
for (int j = 0; j M; j++) {
xi[t][i][j] = alpha[t][i] * A[i][j]
* B[j][observation.indexOf(out_seq[t + 1])]
* beta[t + 1][j] / frac;
}
}
}
}
// 更新gamma
public void updateGamma() {
for (int t = 0; t T - 1; t++) {
double frac = 0.0;
for (int i = 0; i M; i++) {
frac += alpha[t][i] * beta[t][i];
}
// double frac = PO;
for (int i = 0; i M; i++) {
gamma[t][i] = alpha[t][i] * beta[t][i] / frac;
}
// for(int i=0;iM;i++){
// gamma[t][i]=0;
// for(int j=0;jM;j++)
// gamma[t][i]+=xi[t][i][j];
// }
}
}
// 更新状态概率矩阵
public void updatePI() {
for (int i = 0; i M; i++)
PI[i] = gamma[0][i];
}
// 更新状态转移矩阵
public void updateA() {
for (int i = 0; i M; i++) {
double frac = 0.0;
for (int t = 0; t T - 1; t++) {
frac += gamma[t][i];
}
for (int j = 0; j M; j++) {
double dem = 0.0;
// for (int t = 0; t T - 1; t++) {
// dem += xi[t][i][j];
// for (int k = 0; k M; k++)
// frac += xi[t][i][k];
// }
for (int t = 0; t T - 1; t++) {
dem += xi[t][i][j];
}
A[i][j] = dem / frac;
}
}
}
// 更新混淆矩阵
public void updateB() {
for (int i = 0; i M; i++) {
double frac = 0.0;
for (int t = 0; t T; t++)
frac += gamma[t][i];
for (int j = 0; j N; j++) {
double dem = 0.0;
for (int t = 0; t T; t++) {
if (out_seq[t] == observation.get(j))
dem += gamma[t][i];
}
B[i][j] = dem / frac;
}
}
}
// 运行Baum-Welch算法
public void run() {
initParameters();
int iter = 22; // 迭代次数
while (iter-- 0) {
// E-Step
updateAlpha();
// updatePO();
updateBeta();
updateGamma();
updatePI();
updateXi();
// M-Step
updateA();
updateB();
}
}
public static void main(String[] args) {
BaumWelch bw = new BaumWelch();
bw.run();
System.out.println("训练后的初始状态概率矩阵:");
for (int i = 0; i bw.M; i++)
System.out.print(bw.PI[i] + "\t");
System.out.println();
System.out.println("训练后的状态转移矩阵:");
for (int i = 0; i bw.M; i++) {
for (int j = 0; j bw.M; j++) {
System.out.print(bw.A[i][j] + "\t");
}
System.out.println();
}
System.out.println("训练后的混淆矩阵:");
for (int i = 0; i bw.M; i++) {
for (int j = 0; j bw.N; j++) {
System.out.print(bw.B[i][j] + "\t");
}
System.out.println();
}
}
}
1.支持向量机(SVM)概述
(1)支持向量机(Support Vector Machines,SVM)是一种二元分类模型,它是一类模型的统称,其中包括:
①线性可分支持向量机;
②线性支持向量机;
③非线性支持向量机。
(2)核心思想:
训练阶段在特征空间中寻找一个超平面,它能(或尽量能)将训练样本中的正例和负例分离在它的两侧,预测时以该超平面作为决策边界判断输入实例的类别。寻找超平面的原则是,在可分离的情况下使超平面与数据集间隔最大化。
(3)支持向量机的分类示意图为:
简单来说,SVM的原理就是在平面内找到一条直线,使得这两类不同的样本点分开,并且保证能够尽可能远的远离这条直线。用向量表示两类样本点之间的分类间隔(Margin)为:
支持向量机的目的是使r最大,等价于使||w||/2最小。而几何向量使分类间隔最大问题可以转化为运筹学上的约束优化问题。因为涉及太多复杂公式,此处省略。
只要理解了SVM的原理,并且学会利用sklearn库调用SVM模块,就达到了数据分析的目的。
2.SVM算法实现
(1)重要参数说明:
①kernel :核函数,默认是rbf,可以是‘linear’, ‘poly’, ‘rbf’, ‘sigmoid’, ‘precomputed’ 。
·kernel='linear'时为线性核,C越大分类效果越好,但有可能会过拟合(defaul C=1);
·kernel='poly'时为多项式核函数;
·kernel='rbf'时(default)为高斯核,gamma值越小,分类界面越连续;gamma值越大,分类界面越“散”,分类效果越好,但有可能会过拟合。
②decision_function_shape:
·decision_function_shape='ovr'时,为one v rest,即一个类别与其他类别进行划分;
·decision_function_shape='ovo'时,为one v one,即将类别两两之间进行划分,用二分类的方法模拟多分类的结果。
(2)程序实现过程:
【注】
在分类型模型评判的指标中,常见的方法有如下三种:
①混淆矩阵(也称误差矩阵,Confusion Matrix)
混淆矩阵是评判模型结果的指标,属于模型评估的一部分。此外,混淆矩阵多用于判断分类器(Classifier)的优劣,适用于分类型的数据模型,如分类树(Classification Tree)、逻辑回归(Logistic Regression)、线性判别分析(Linear Discriminant Analysis)等方法。
混淆矩阵的一级指标:
通过混淆矩阵可以计算出评估模型的几个指标(二级指标):
三级指标:F1-score
其中,P代表Precision,R代表Recall。
F1-Score指标综合了Precision与Recall的产出的结果。F1-Score的取值范围从0到1的,1代表模型的输出最好,0代表模型的输出结果最差。
Ps:当分类结果多于两种时,需要将结果转化为上面的类型。
详细内容参考博文
②ROC曲线
③AUC面积
# -*- coding: UTF-8 -*-
"""绘制混淆矩阵图"""
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix
def confusion_matrix_plot_matplotlib(y_truth, y_predict, cmap=plt.cm.Blues):
"""Matplotlib绘制混淆矩阵图
parameters
----------
y_truth: 真实的y的值, 1d array
y_predict: 预测的y的值, 1d array
cmap: 画混淆矩阵图的配色风格, 使用cm.Blues,更多风格请参考官网
"""
cm = confusion_matrix(y_truth, y_predict)
plt.matshow(cm, cmap=cmap) # 混淆矩阵图
plt.colorbar() # 颜色标签
for x in range(len(cm)): # 数据标签
for y in range(len(cm)):
plt.annotate(cm[x, y], xy=(x, y), horizontalalignment='center', verticalalignment='center')
plt.ylabel('True label') # 坐标轴标签
plt.xlabel('Predicted label') # 坐标轴标签
plt.show() # 显示作图结果
if __name__ == '__main__':
y_truth = [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]
y_predict = [1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0]
confusion_matrix_plot_matplotlib(y_truth, y_predict)