3002 个技能背后的安全陷阱:OpenClaw 技能仓库踩坑实录

3002 个技能背后的安全陷阱:OpenClaw 技能仓库踩坑实录 OpenClaw 技能仓库 https://github.com/VoltAgent/awesome-openclaw-skills 有 14439 个星标,收录了 3002 个过滤后的技能。 花了一整天时间安装和测试 38 个技能后,我发现了一些有趣的问题。 技能仓库的诱惑 这个仓库太诱人了。 3002 个技能覆盖了所有你能…

3002 个技能背后的安全陷阱:OpenClaw 技能仓库踩坑实录

OpenClaw 技能仓库 https://github.com/VoltAgent/awesome-openclaw-skills 有 14439 个星标,收录了 3002 个过滤后的技能。

花了一整天时间安装和测试 38 个技能后,我发现了一些有趣的问题。

技能仓库的诱惑

这个仓库太诱人了。

3002 个技能覆盖了所有你能想到的场景:浏览器自动化、搜索、写作、开发工具、任务管理...

一个命令就能安装:

npx clawhub@latest install 

安装后技能自动集成到你的 OpenClaw 代理中,开箱即用。

但事情没那么简单。

安全警告:被标记为"可疑"的技能

安装第一个技能时,我看到了这个提示:

⚠️  警告:此技能被 VirusTotal 标记为"可疑"
原因:包含外部 API 调用、eval 代码等
是否继续安装?(y/n)

我安装了 38 个技能,其中 10+ 个被标记为可疑。

为什么会这样?

可疑技能的特征

分析这些被标记的技能后,我发现几个共同特征:

  1. 外部 API 调用

    • 技能代码中直接调用外部 API
    • 可能发送你的数据到第三方服务器
    • API 密钥可能硬编码在代码中
  2. eval() 和类似函数

    • 动态执行代码
    • 可能存在代码注入风险
    • 难以审计具体行为
  3. 网络请求

    • 技能向未知服务器发送请求
    • 可能泄露敏感信息
    • 难以追踪请求内容

审查技能源代码

被标记为可疑并不意味着技能一定有问题,但你需要审查源代码。

npx clawhub@latest show 

cd ~/.openclaw/workspace/skills/

cat SKILL.md
cat package.json
cat index.js

审查时重点关注:

  1. 外部 API 调用
const API_KEY = "sk-xxxxxxxxxxxxxxxxxxxx";

const API_KEY = process.env.OPENAI_API_KEY;
  1. eval() 使用
eval(userInput);

safeFunction(userInput);
  1. 网络请求
fetch('https://suspicious-server.com/api');

fetch('https://api.openai.com/v1/chat/completions');

强制安装的风险

ClawHub 提供了 --force 参数强制安装被标记的技能:

npx clawhub@latest install  --force

但我只在以下情况下使用:

  1. 审查完源代码:确认没有明显安全问题
  2. 理解技能行为:知道技能会做什么
  3. 隔离使用:在测试环境中先试用
  4. 来自可信开发者:有良好的社区口碑

我的安装经验

安装的 38 个技能中,我遇到了几个典型问题:

技能类型 已安装 被标记可疑 审查后安装
浏览器/自动化 5 1 1
搜索 11 3 2
写作 7 2 1
开发工具 5 1 1
计划/任务管理 8 2 2

有些技能被标记是因为使用了外部 API(如 Google、OpenAI),但审查后发现只是正常的 API 调用,没有恶意行为。

有些技能包含 eval() 代码,但仔细阅读后发现只是用于解析配置文件,风险可控。

最佳实践

安装技能前,遵循这些原则:

1. 先审查,再安装

npx clawhub@latest search 

npx clawhub@latest show 

2. 检查作者和仓库

  • 仓库是否有活跃维护?
  • 作者是否有良好声誉?
  • 有没有 Issues 或 PR 讨论?

3. 理解技能依赖

cat ~/.openclaw/workspace/skills//package.json

4. 隔离测试

在测试环境先试用,确认没有问题后再部署到生产环境

5. 限制权限

OpenClaw 提供了权限控制,确保技能只能访问必要的资源。

我的推荐技能(已审查)

基于我的安装和审查经验,推荐以下安全可靠的技能:

浏览器自动化

  • browser - OpenClaw 内置,无需安装
  • webapp-testing - 使用 Playwright 测试本地 Web 应用

搜索

  • brave-search - Brave Search API,隐私友好
  • searxng-bangs - SearXNG 本地搜索,支持 DuckDuckGo 风格快捷搜索

写作

  • writing - 自适应写作风格,无外部依赖
  • writer - 修复 AI 机器人写作模式

开发工具

  • coding - 编程助手,无外部 API 调用
  • api - API 开发与管理,纯本地工具

总结

OpenClaw 技能仓库是一个巨大的宝藏,但也存在安全陷阱。

关键要点:

  1. 不要盲目安装所有技能,先审查源代码
  2. 被标记为可疑的技能不一定有问题,但需要仔细检查
  3. 理解技能的行为和依赖,避免引入未知风险
  4. 使用 --force 参数前三思:是否真的信任这个技能?
  5. 在测试环境中先试用,确认安全后再部署

技能仓库持续更新,每周都有新技能加入。

保持谨慎,享受 OpenClaw 带来的强大功能!

Views: 29

AI 编程助手大对决:Cursor vs Copilot,我用一个月后换工具了!

经过一个月的实测,我深度对比了 Cursor 和 GitHub Copilot 两个 AI 编程助手。从代码理解、生成质量、学习成本、价格、速度、集成度四个维度全面测评,告诉你哪个更适合你!

开头:程序员的一天

记得那是周二的晚上,我对着电脑屏幕发呆。

一个复杂的需求要实现,涉及多个模块的联动,代码逻辑绕来绕去。写一段,删一段,再看一段,改一段...时间一分一秒过去,代码却没什么进展。

如果有人能帮我写代码就好了...

这时,我想到了最近爆火的 AI 编程助手 —— Cursor 和 GitHub Copilot。

一个月的实测对比

为了选出最适合我的工具,我花了一个月时间,深度使用了这两个 AI 编程助手。

下面是真实体验!

四大维度对比

维度 Cursor GitHub Copilot 胜者
代码理解能力 5/5 4/5 Cursor ✅
生成质量 4.5/5 4/5 Cursor ✅
学习成本 4/5 5/5 Copilot ✅
价格 4/5 4/5 平手
速度 5/5 4.5/5 Cursor ✅
集成度 3.5/5 5/5 Copilot ✅

代码理解能力

Cursor 的理解能力让我惊喜!它能读懂整个项目的上下文,而不仅仅是当前文件。

比如我要重构一个老旧的 Spring Boot 模块,Cursor 能:
- 自动识别依赖的 Service 层代码
- 理解数据库表结构(通过 MyBatis-Plus 的实体类)
- 推荐符合业务逻辑的重构方案

Copilot 的理解也不错,但更多是基于当前文件的上下文,跨文件的理解能力稍弱。

生成质量

让我印象最深的是一次 Vue3 前端开发:

我需要实现一个复杂的数据可视化组件,涉及多个图表的联动和数据刷新。

Cursor 给出的代码:
- 完整的 TypeScript 类型定义
- 考虑了加载状态和错误处理
- 使用了 Composition API 最佳实践
- 代码风格统一,可读性强

Copilot 给出的代码:
- 功能实现没问题
- 但类型定义不够完整
- 错误处理较简单
- 需要一些手工调整

实战场景测试

场景 1:从零开发 Spring Boot 项目

Prompt: "创建一个 Spring Boot 3 项目,包含用户登录、角色权限管理,使用 MyBatis-Plus 和 MySQL"

Cursor 的表现

1. 自动识别需要依赖:spring-boot-starter-web、mybatis-plus、mysql-connector
2. 生成完整的 User、Role、Permission 实体类
3. 创建对应的 Service、Controller 层
4. 生成 Spring Security 配置
5. 创建数据库建表 SQL

整个项目架构清晰,代码可以直接运行!

Copilot 的表现
- 也能生成大部分代码
- 但 Security 配置需要手工调整
- 整体流程不如 Cursor 完整

场景 2:Vue3 组件开发

Prompt: "创建一个可拖拽的 Kanban 看板组件,支持卡片拖拽、状态流转"

Cursor 的表现

// 生成的代码包含:
- 完整的拖拽逻辑
- 状态管理(使用 reactive)
- 拖拽动画效果
- 响应式布局

代码质量高,几乎不需要修改!

场景 3:代码重构优化

我有一段性能低下的代码:

// 旧代码:N+1 查询问题
List<User> users = userMapper.selectList(null);
for (User user : users) {
    List<Order> orders = orderMapper.selectByUserId(user.getId());
    user.setOrders(orders);
}

Cursor 立即识别出问题,并给出优化方案:

// Cursor 优化后:一次查询
List<User> users = userMapper.selectWithOrders();
// 或者使用 JOIN 查询优化

还加上了性能对比说明,非常专业!

踩坑总结

Cursor 的坑

  1. 集成度还不够完善
    - 不如 Copilot 和 GitHub 的深度集成
    - 有时需要手工复制代码到其他平台

  2. 中文支持一般
    - 英文 Prompt 效果更好
    - 中文生成质量略低

Copilot 的坑

  1. 上下文理解有限
    - 大项目时,跨文件的关联理解较弱
    - 需要频繁提供背景信息

  2. 生成重复代码
    - 有时会重复生成类似的代码片段
    - 需要人工去重

我的选择

经过一个月的实测,我选择了 Cursor 作为主力编程助手

原因
1. 代码理解能力强,适合复杂项目
2. 生成质量高,减少手工调整
3. 速度快,不影响开发节奏
4. 适合 Java 后端和 Vue3 前端开发

保留 Copilot 作为备用:
- 快速生成小片段代码时很方便
- GitHub 集成度高的场景

如何开始使用

Cursor 安装

# 下载安装
# 访问 https://cursor.sh 下载对应版本

# 配置 AI 模型(支持 OpenAI、Claude 等)
# Settings → Models → 选择你的模型

GitHub Copilot 安装

# VS Code 安装
ext install GitHub.copilot

# 登录 GitHub 账号
# 完成激活

高效使用技巧

1. 写好 Prompt

❌ 不好的 Prompt:
- "帮我写个用户登录"
- "这个代码有问题,修一下"

✅ 好的 Prompt:
- "使用 Spring Boot 3 + Spring Security 实现用户登录,支持 JWT 认证,登录成功后返回用户信息和 Token"
- "这段代码有 N+1 查询问题,请优化为 JOIN 查询,并说明优化后的性能提升"

2. 分步生成复杂功能

大功能不要一次生成,分步骤来:

步骤 1:生成实体类和 Mapper
步骤 2:生成 Service 层业务逻辑
步骤 3:生成 Controller 层接口
步骤 4:生成前端页面和 API 调用

3. 利用上下文

Cursor 支持多文件上下文,可以这样用:

  1. 打开相关的实体类、Service、Controller
  2. 在需要生成代码的文件中,使用 @filename 引用
  3. Cursor 会综合多个文件的上下文生成代码

参考资源

总结

AI 编程助手已经从"锦上添花"变成了"生产力工具"。

Cursor:适合复杂项目、深度开发、需要高质量代码的场景
Copilot:适合快速开发、GitHub 集成、日常辅助编程

我的建议:两个都试试,找到最适合你工作流程的那一个!


💡 互动时间:你使用过 AI 编程助手吗?在评论区分享你的使用体验!


本文首发于我的技术博客,转载请注明出处。

Views: 35

关于坚持

关于坚持

坚持需要明确目标、灵活调整方向、积累力量、克服困难、享受过程。这一周的任务完成了,下一周继续前行。

Views: 6

04 回归与聚类算法

线性回归

学习目标

  • 记忆线性回归的原理过程
  • 应用LinearRegression或SGDRegressor实现回归预测
  • 记忆回归算法的评估标准及其公式

应用

  • 波士顿房价预测

1、 线性回归的原理

1.1 线性回归应用场景\

  • 房价预测
  • 销售额度预测
  • 金融:贷款额度预测、利用线性回归以及系数分析因子

1.2 什么是线性回归

1.2.1定义与公式

线性回归(Linear regression)是利用回归方程(函数)一个或多个自变量(特征值)和因变量(目标值)之间关系进行建模的一种分析方式。

  • 特点:只有一个自变量的情况称为单变量回归,大于一个自变量情况的叫做多元回归

那么怎么理解呢?我们来看几个例子

  • 期末成绩:0.7×考试成绩+0.3×平时成
  • 房子价格 = 0.02×中心区域的距离 + 0.04×城市一氧化氮浓度 + (-0.12×自住房平均房价) + 0.254×城镇犯罪率

上面两个例子,我们看到特征值与目标值之间建立的一个关系,这个可以理解为回归方程

1.2.2 线性回归的特征与目标的关系分析

线性回归当中的关系有两种,一种是线性关系,另一种是非线性关系。在这里我们只能画一个平面更好去理解,所以都用单个特征举例子。

  • 线性关系
  • 多变量线性关系

注释:如果在单特征与目标值的关系呈直线关系,或者两个特征与目标值呈现平面的关系, 更高维度的我们不用自己去想,记住这种关系即可

非线性关系

注释:为什么会这样的关系呢?原因是什么?

我们后面 讲解过拟合欠拟合

重点介绍. 如果是非线性关系,那么回归方程可以理解为:

θ0 + θ1×x1 + θ2×x^2 + θ3×x^3

2、线性回归的损失和优化原理(理解记忆)

假设刚才的房子例子,真实的数据之间存在这样的关系:

真实房子价格 = 0.02×中心区域的距离 + 0.04×城市一氧化氮浓度 + (-0.12×自住房平均房价) + 0.254×城镇犯罪率

那么现在呢,我们随意指定一个关系(猜测), 随机指定关系:

预测房子价格 = 0.25×中心区域的距离 + 0.14×城市一氧化氮浓度 + 0.42×自住房平均房价 + 0.34×城镇犯罪率

请问这样的话,会发生什么?真实结果与我们预测的结果之间是不是存在一定的误差呢?类似这样样子

那么存在这个误差,我们将这个误差给衡量出来

2.1 损失函数

总损失定义为:

  • y_i为第i个训练样本的真实值
  • h(x_i)为第i个训练样本特征值组合预测函数
  • 又称最小二乘法

如何去减少这个损失,使我们预测的更加准确些?既然存在了这个损失,我们一直说机器学习有自动学习的功能,在线性回归这里更是能够体现。这里可以通过一些优化方法去优化(其实是数学当中的求导功能)回归的总损失!!!

2.2 优化算法

如何去求模型当中的W,使得损失最小?(目的是找到最小损失对应的W值)

线性回归经常使用的两种优化算法

正规方程

理解:X为特征值矩阵,y为目标值矩阵。直接求到最好的结果
缺点:当特征过多过复杂时,求解速度太慢并且得不到结果

损失行数求解1

梯度下降(Gradient Descent)公式:

理解:α为学习速率,需要手动指定(超参数),α旁边的整体表示方向. 沿着这个函数下降的方向找,最后就能找到山谷的最低点,然后更新W值.
使用:面对训练数据规模十分庞大的任务 ,能够找到较好的结果

我们通过两个图更好理解梯度下降的过程

单变量的梯度下降:

多变量的梯度下降:

所以有了梯度下降这样一个优化算法,回归就有了"自动学习"的能力

2.3 优化动态图演示

线性回归优化动态图

3、 线性回归API

  • sklearn.linear_model.LinearRegression(fit_intercept=True)
    • 通过正规方程优化
    • fit_intercept:是否计算偏置
    • LinearRegression.coef_:回归系数
    • LinearRegression.intercept_:偏置
  • sklearn.linear_model.SGDRegressor(loss="squared_loss", fit_intercept=True, learning_rate ='invscaling', eta0=0.01)
    • SGDRegressor类实现了随机梯度下降学习,它支持不同的loss函数和正则化惩罚项来拟合线性回归模型。
    • loss:损失类型
      • loss=”squared_loss”: 普通最小二乘法
    • fit_intercept:是否计算偏置
    • learning_rate : string, optional
      • 学习率填充
      • 'constant': eta = eta0
      • 'optimal': eta = 1.0 / (alpha * (t + t0)) [default]
      • 'invscaling': eta = eta0 / pow(t, power_t)
        • power_t=0.25:存在父类当中
      • 对于一个常数值的学习率来说,可以使用learning_rate=’constant’ ,并使用eta0来指定学习率。
    • SGDRegressor.coef_:回归系数
    • SGDRegressor.intercept_:偏置

sklearn提供给我们两种实现的API, 可以根据选择使用

4、波士顿房价预测

数据介绍

属性:

给定的这些特征,是专家们得出的影响房价的结果属性。我们此阶段不需要自己去探究特征是否有用,只需要使用这些特征。到后面量化很多特征需要我们自己去寻找

4.1 分析

回归当中的数据大小不一致,是否会导致结果影响较大。所以需要做标准化处理。同时我们对目标值也需要做标准化处理。

  • 数据分割与标准化处理
  • 回归预测
  • 线性回归的算法效果评估


4.2 回归性能评估

均方误差(Mean Squared Error)MSE)评价机制:

线性回归评估:

注:y^i 为预测值,¯y为真实值

  • sklearn.metrics.mean_squared_error(y_true, y_pred)
    • 均方误差回归损失
    • y_true:真实值
    • y_pred:预测值
    • return:浮点数结果

4.2 代码

def mylinearregression():
    """
    线性回归预测房子价格
    :return:
    """
    lb = load_boston()
    #
    # print(lb.data)
    #
    # print(lb.target)

    # 对数据集进行划分
    x_train, x_test, y_train, y_test = train_test_split(lb.data, lb.target, test_size=0.3, random_state=24)

    # 需要做标准化处理对于特征值处理
    std_x = StandardScaler()

    x_train = std_x.fit_transform(x_train)
    x_test = std_x.fit_transform(x_test)
    # print(x_train)

    # 对于目标值进行标准化
    std_y = StandardScaler()

    y_train = std_y.fit_transform(y_train)
    y_test = std_y.transform(y_test)
    y_test = std_y.inverse_transform(y_test)

    # 使用线性模型进行预测
    # 使用正规方程求解
    lr = LinearRegression()
    # # 此时在干什么?
    lr.fit(x_train, y_train)

    y_lr_predict = std_y.inverse_transform(lr.predict(x_test))

    print(lr.coef_)

    print("正规方程预测的结果为:", y_lr_predict)

    print("正规方程的均方误差为:", mean_squared_error(y_test, y_lr_predict))

    # 梯度下降进行预测
    sgd = SGDRegressor()
    #
    sgd.fit(x_train, y_train)
    print("SGD的权重参数为:", sgd.coef_)
    #
    y_sgd_predict = std_y.inverse_transform(sgd.predict(x_test))
    #
    print("SGD的预测的结果为:", y_sgd_predict)
    #
    # # 怎么评判这两个方法好坏
    print("SGD的均方误差为:", mean_squared_error(y_test, y_sgd_predict))

    return None

我们也可以尝试去修改学习率

sgd = SGDRegressor(learning_rate='constant', eta0=0.001)

此时我们可以通过调参数,找到学习率效果更好的值。

4.3 正规方程和梯度下降对比

  • 文字对比
梯度下降正规方程
需要选择学习率不需要
需要迭代求解一次运算得出
特征数量较大可以使用需要计算方程,时间复杂度高O(n3)
  • 选择:
    • 小规模数据:
      • LinearRegression(不能解决拟合问题)
      • 岭回归
    • 大规模数据:SGDRegressor

5、拓展-关于优化方法GD、SGD、SAG

5.1 GD

梯度下降(Gradient Descent),原始的梯度下降法需要计算所有样本的值才能够得出梯度,计算量大,所以后面才有会一系列的改进。

5.2 SGD

随机梯度下降(Stochastic gradient descent)是一个优化方法。它在一次迭代时只考虑一个训练样本。

  • SGD的优点是:
    • 高效
    • 容易实现
  • SGD的缺点是:
    • SGD需要许多超参数:比如正则项参数、迭代数。
    • SGD对于特征标准化是敏感的。

5.3 SAG

随机平均梯度法(Stochasitc Average Gradient),由于收敛的速度太慢,有人提出SAG等基于梯度下降的算法

Scikit-learn:SGDRegressor、岭回归、逻辑回归等当中都会有SAG优化

6、总结

  • 线性回归的损失函数-均方误差
  • 线性回归的优化方法
    • 正规方程
    • 梯度下降
  • 线性回归的性能衡量方法-均方误差
  • sklearn的SGDRegressor API 参数

欠拟合与过拟合

  • 学习目标
    • 说明线性回归(不带正则化)的缺点
    • 说明过拟合与欠拟合的原因以及解决方法

问题:训练数据训练的很好啊,误差也不大,为什么在测试集上面有问题呢?

当算法在某个数据集当中出现这种情况,可能就出现了过拟合现象。

1、 什么是过拟合与欠拟合

  • 欠拟合
  • 过拟合
  • 分析
    • 第一种情况:因为机器学习到的天鹅特征太少了,导致区分标准太粗糙,不能准确识别出天鹅。
    • 第二种情况:机器已经基本能区别天鹅和其他动物了。然后,很不巧已有的天鹅图片全是白天鹅的,于是机器经过学习后,会认为天鹅的羽毛都是白的,以后看到羽毛是黑的天鹅就会认为那不是天鹅。

1.1 定义

  • 过拟合:一个假设在训练数据上能够获得比其他假设更好的拟合, 但是在测试数据集上却不能很好地拟合数据,此时认为这个假设出现了过拟合的现象。(模型过于复杂)
  • 欠拟合:一个假设在训练数据上不能获得更好的拟合,并且在测试数据集上也不能很好地拟合数据,此时认为这个假设出现了欠拟合的现象。(模型过于简单)

那么是什么原因导致模型复杂?线性回归进行训练学习的时候变成模型会变得复杂,这里就对应前面说的线性回归的两种关系,非线性关系的数据,也就是存在很多无用的特征或者现实中的事物特征跟目标值的关系并不是简单的线性关系。

2、 原因以及解决办法

  • 欠拟合原因以及解决办法
    • 原因:学习到数据的特征过少
    • 解决办法:增加数据的特征数量
  • 过拟合原因以及解决办法
    • 原因:原始特征过多,存在一些嘈杂特征, 模型过于复杂是因为模型尝试去兼顾各个测试数据点
    • 解决办法:
      • 正则化

在这里针对回归,我们选择了正则化。但是对于其他机器学习算法如分类算法来说也会出现这样的问题,除了一些算法本身作用之外(决策树、神经网络),我们更多的也是去自己做特征选择,包括之前说的删除、合并一些特征

如何解决?

正则化:

在学习的时候,数据提供的特征有些影响模型复杂度或者这个特征的数据点异常较多,所以算法在学习的时候尽量减少这个特征的影响(甚至删除某个特征的影响),这就是正则化

注:调整时候,算法并不知道某个特征影响,而是去调整参数得出优化的结果

2.1 正则化类别

  • L2正则化
    • 作用:可以使得其中一些W的都很小,都接近于0,削弱某个特征的影响
    • 优点:越小的参数说明模型越简单,越简单的模型则越不容易产生过拟合现象
    • 岭(Ridge)回归
  • L1正则化
    • 作用:可以使得其中一些W的值直接为0,删除这个特征的影响
    • LASSO回归

2.2 拓展-原理(了解)

线性回归的损失函数用最小二乘法,等价于当预测值与真实值的误差满足正态分布时的极大似然估计;

岭回归的损失函数,是最小二乘法+L2范数,等价于当预测值与真实值的误差满足正态分布,且权重值也满足正态分布(先验分布)时的最大后验估计;

LASSO的损失函数,是最小二乘法+L1范数,等价于等价于当预测值与真实值的误差满足正态分布,且且权重值满足拉普拉斯分布(先验分布)时的最大后验估计

线性回归的改进-岭回归

  • 学习目标
    • 说明岭回归的原理即与线性回归的不同之处
    • 说明正则化对于权重参数的影响
    • 说明L1和L2正则化的区别
  • 实际应用
    • 波士顿房价预测

1、 带有L2正则化的线性回归-岭回归

岭回归,其实也是一种线性回归。只不过在算法建立回归方程时候,加上正则化的限制,从而达到解决过拟合的效果

1.1 API

  • sklearn.linear_model.Ridge(alpha=1.0, fit_intercept=True,solver="auto", normalize=False)
    • 具有l2正则化的线性回归
    • alpha:正则化力度,也叫 λ
      • λ取值:0~1 1~10
    • solver:会根据数据自动选择优化方法
      • sag:如果数据集、特征都比较大,选择该随机梯度下降优化
    • normalize:数据是否进行标准化
      • normalize=False:可以在fit之前调用preprocessing.StandardScaler标准化数据
    • Ridge.coef_:回归权重
    • Ridge.intercept_:回归偏置
All last four solvers support both dense and sparse data. However,
only 'sag' supports sparse input when `fit_intercept` is True.

Ridge方法相当于SGDRegressor(penalty='l2', loss="squared_loss"),只不过SGDRegressor实现了一个普通的随机梯度下降学习,推荐使用Ridge(实现了SAG)

  • sklearn.linear_model.RidgeCV(_BaseRidgeCV, RegressorMixin)
    • 具有l2正则化的线性回归,可以进行交叉验证
    • coef_:回归系数
class _BaseRidgeCV(LinearModel):
    def __init__(self, alphas=(0.1, 1.0, 10.0),
                 fit_intercept=True, normalize=False, scoring=None,
                 cv=None, gcv_mode=None,
                 store_cv_values=False):

1.2 观察正则化程度的变化,对结果的影响?

  • 正则化力度越大,权重系数会越小
  • 正则化力度越小,权重系数会越大

1.3 波士顿房价预测

rd = Ridge(alpha=1.0)

rd.fit(x_train, y_train)
print("岭回归的权重参数为:", rd.coef_)

y_rd_predict = std_y.inverse_transform(rd.predict(x_test))

print("岭回归的预测的结果为:", y_rd_predict)


print("岭回归的均方误差为:", mean_squared_error(y_test, y_rd_predict))

分类算法-逻辑回归与二分类

  • 学习目标
    • 说明逻辑回归的损失函数
    • 说明逻辑回归的优化方法
    • 说明sigmoid函数
    • 知道逻辑回归的应用场景
    • 知道精确率、召回率指标的区别
    • 知道F1-score指标说明召回率的实际意义
    • 说明如何解决样本不均衡情况下的评估
    • 了解ROC曲线的意义说明AUC指标大小
    • 应用classification_report实现精确率、召回率计算
    • 应用roc_auc_score实现指标计算
  • 具体应用
    • 二分类问题
      • 癌症患者预测
      • 皮马印第安人糖尿病预测

逻辑回归(Logistic Regression)是机器学习中的一种分类模型,逻辑回归是一种分类算法,虽然名字中带有回归,但是它与回归之间有一定的联系, 根据数据特征计算属于某一类别的概率p(x), 根据概率数值判断其所属类别 。由于算法的简单和高效,在实际中应用非常广泛。

1、逻辑回归的应用场景

  • 广告点击率
  • 是否为垃圾邮件
  • 是否患病
  • 金融诈骗
  • 虚假账号

看到上面的例子,我们可以发现其中的特点,那就是都属于两个类别之间的判断, 比如是不是垃圾邮件,是猫或是狗. 逻辑回归就是解决二分类问题的利器。

(神经网络模型底层也是基于逻辑回归实现的)

2、 逻辑回归的原理

2.1 输入

逻辑回归的输入就是一个线性回归的结果。

2.2 激活函数

  • sigmoid函数

特征值x的概率P(x)等于:

当概率大于等于0.5, 输出y=1;

当概率小于0.5, 输出y=0

其中,y为类别结果, P为概率,x为特征值,θ为常量

  • 分析
    • 回归的结果输入到sigmoid函数当中
    • 输出结果:[0, 1]区间中的一个概率值,默认为0.5为阈值

逻辑回归最终的分类是通过属于某个类别的概率值来判断是否属于某个类别,并且这个类别默认标记为1(正例),另外的一个类别会标记为0(反例)。(方便损失计算)

输出结果解释(重要):假设有两个类别A,B,并且假设我们的概率值为属于A(1)这个类别的概率值。现在有一个样本的输入到逻辑回归输出结果0.6,那么这个概率值超过0.5,意味着我们训练或者预测的结果就是A(1)类别。那么反之,如果得出结果为0.3那么,训练或者预测结果就为B(0)类别。

所以接下来我们回忆之前的线性回归预测结果我们用均方误差衡量,那如果对于逻辑回归,我们预测的结果不对该怎么去衡量这个损失呢?我们来看这样一张图

那么如何去衡量逻辑回归的预测结果与真实结果的差异呢?

2.3 损失以及优化

2.3.1 损失

逻辑回归的损失,称之为对数似然损失,公式如下:

  • 分开类别:

单个对数似然损失

怎么理解单个的式子呢?这个要根据log的函数图像来理解。

当y=1时:

  • 综合完整损失函数

看到这个式子,其实跟我们讲的信息熵类似。

接下来我们呢就带入上面那个例子来计算一遍,就能理解意义了。

我们已经知道,log(P), P值越大,结果越小,所以我们可以对着这个损失的式子去分析

2.3.2 优化

同样使用梯度下降优化算法,去减少损失函数的值。这样去更新逻辑回归前面对应算法的权重参数,提升原本属于1类别的概率,降低原本是0类别的概率。

3、逻辑回归API

  • sklearn.linear_model.LogisticRegression(solver='liblinear', penalty=‘l2’, C = 1.0)
    • solver:优化求解方式(默认开源的liblinear库实现,内部使用了坐标轴下降法来迭代优化损失函数)
      • sag:根据数据集自动选择,随机平均梯度下降
    • penalty:正则化的种类
    • C:正则化力度

默认将类别数量少的当做正例

LogisticRegression方法相当于 SGDClassifier(loss="log", penalty=" "),SGDClassifier实现了一个普通的随机梯度下降学习,也支持平均随机梯度下降法(ASGD),可以通过设置average=True。而使用LogisticRegression(实现了SAG)

4、 案例:癌症分类预测-良/恶性乳腺癌肿瘤预测

  • 数据介绍

原始数据的下载地址:https://archive.ics.uci.edu/ml/machine-learning-databases/

数据描述

(1)699条样本,共11列数据,第一列用语检索的id,后9列分别是与肿瘤

相关的医学特征,最后一列表示肿瘤类型的数值。

(2)包含16个缺失值,用”?”标出。

4.1 分析

  • 缺失值处理
  • 标准化处理
  • 逻辑回归预测

4.2 代码

def logisticregression():
    """
    逻辑回归进行癌症预测
    :return: None
    """
    # 1、读取数据,处理缺失值以及标准化
    column_name = ['Sample code number', 'Clump Thickness', 'Uniformity of Cell Size', 'Uniformity of Cell Shape',
                   'Marginal Adhesion', 'Single Epithelial Cell Size', 'Bare Nuclei', 'Bland Chromatin',
                   'Normal Nucleoli', 'Mitoses', 'Class']

    data = pd.read_csv("https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/breast-cancer-wisconsin.data",
                       names=column_name)

    # 删除缺失值
    data = data.replace(to_replace='?', value=np.nan)

    data = data.dropna()

    # 取出特征值
    x = data[column_name[1:10]]

    y = data[column_name[10]]

    # 分割数据集
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3)

    # 进行标准化
    std = StandardScaler()

    x_train = std.fit_transform(x_train)

    x_test = std.transform(x_test)

    # 使用逻辑回归
    lr = LogisticRegression()

    lr.fit(x_train, y_train)

    print("得出来的权重:", lr.coef_)

    # 预测类别
    print("预测的类别:", lr.predict(x_test))

    # 得出准确率
    print("预测的准确率:", lr.score(x_test, y_test))
    return None

在很多分类场景当中我们不一定只关注预测的准确率!!!!!

手机评判好坏

比如以这个癌症举例子!!!我们并不关注预测的准确率,而是关注在所有的样本当中,癌症患者有没有被全部预测(检测)出来。

5. 案例: 皮马印第安人糖尿病预测

皮马印第安人糖尿病数据集介绍

数据集来自Kaggle, 也可以从飞浆公开数据集中下载

image-20211227180249217

需求: 根据给定皮马印第安人的样本信息, 包括怀孕次数, 胰岛素水平, 体重指数, 年龄四个特征, 预测是否患有糖尿病(二分类)

导入数据集

 import pandas as pd
 path = '../data/pima-indians-diabetes.csv'
 pima = pd.read_csv(path)
 ​
 # 由于csv文件中缺少表头第一行描述信息, 下面将描述信息插入到第一行
 pima.loc[-1] = pima.columns.tolist()
 pima.index = pima.index + 1
 pima.sort_index(inplace=True)
 pima.columns = ['pregnant', 'glucose', 'bp', 'skin', 'insulin', 'bmi', 'pedigree', 'age', 'label']
 pima.head()

输出​

pregnantglucosebpskininsulinbmipedigreeagelabel
061487235033.60.627501
11856629026.60.351310
28183640023.30.672321
318966239428.10.167210
40137403516843.12.288331

保存特征和标签到变量中

# X y赋值
 feature_names = ['pregnant', 'insulin', 'bmi', 'age']
 X = pima[feature_names]
 y = pima.label
 # 避免后续在logreg.fit时不识别标签的类型object
 y= y. astype('int')
 # 维度确认
 print(X.shape)
 print(y.shape)
 (768, 4)
 (768,)

将数据集合分割为训练集和测试集

 # 数据分离
 from sklearn.model_selection import train_test_split
 X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

训练模型

 # 模型训练
 from sklearn.linear_model import LogisticRegression
 logreg = LogisticRegression()
 logreg.fit(X_train, y_train)

确认模型

 LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
                    intercept_scaling=1, l1_ratio=None, max_iter=100,
                    multi_class='auto', n_jobs=None, penalty='l2',
                    random_state=None, solver='lbfgs', tol=0.0001, verbose=0,
                    warm_start=False)

使用模型预测结果

 # 测试数据集结果预测
 y_pred = logreg.predict(X_test)

计算准确率

 # 使用准确率进行评估
 from sklearn import metrics
 print(metrics.accuracy_score(y_test, y_pred))
 0.6770833333333334

确认正负样本数据量

 # 确认正负样本数据量
 y_test.value_counts()

输出:

 0    130
 1     62
 Name: label, dtype: int64

判断为正确(1)的比例:

 # 1的比例
 y_test.mean()

输出:

 0.3229166666666667

判断为正确(0)的比例:

 # 0的比例
 1 - y_test.mean()

输出:

 0.6770833333333333

空准确率:

 # 空准确率(都预测了占比多的标签时的正确率)
 max(y_test.mean(), 1 -y_test.mean())

输出:

 0.6770833333333333

发现总的准确率还不如空准确率, 所以这个模型看起来并不是很成功 结论, 分类准确率只能衡量整体预测效果, 但是没有反映真实细节信息

 # 比对前面25组样本的正样本的预测情况
 print(y_test.values[:25])
 print(y_pred[:25] )

输出:

 [1 0 0 1 0 0 1 1 0 0 1 1 0 0 0 0 1 0 0 0 1 1 0 0 0]
 [0 0 0 0 0 0 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0]

6、分类的评估方法

使用准确率进行模型评估的局限性

模型评估回顾

  • 目的: 通过模型评估对比模型表现, 确定合适的模型参数
  • 方法: 计算测试数据集预测准确率以评估模型表现

使用准确率预测的局限性:

  • 无法真实反映模型针对各分类的预测准确度

比如对于0-1预测的准确率:

image-20211227180639988

准确率的评估只能看到整体, 无法得知细节, 比如:

比如1000个数据(900个实际分类是1,100个实际分类是0)

全部预测为1 , 整体准确率是90%

虽然1的准确率是100%, 而0被全部预测为1错误率100%就有大问题了.

6.1 混淆矩阵, 模型衡量指标及其意义

为了解决上述问题, 为了衡量分类算法的准确程度, 这里引入了混淆矩阵, 又称为误差矩阵

6.1.1混淆矩阵

在分类任务下,预测结果(Predicted Condition)与正确标记(True Condition)之间存在四种不同的组合,构成混淆矩阵(适用于多分类)

此图片的alt属性为空;文件名为image-111-1024x355.png

把样本分成四类:真正例 (TP), 伪正例(FP), 真反例(TN) , 伪 反 例(FN)

6.1.2 混淆矩阵的六个指标:

混淆矩阵指标的特点:

  • 分类任务中, 相比单一的预测准确率, 混淆矩阵提供了更全面的模型评估信息
  • 通过混淆矩阵, 我们可以计算出多样的模型表现衡量指标, 从而更好的选择模型

哪个指标更关键?

  • 取决应用场景
  • 对于垃圾邮件检测来说, 正样本为"垃圾邮件", 希望普通邮件(负样本)不要被判断为垃圾邮件(正样本)
    • 希望判断为垃圾邮件的样本都是判断正确的, 需要关注精确率
    • 希望所有的垃圾邮件都能尽可能被判断出来, 还需要关注召回率
  • 对于异常交易检测来说, 正样本为"异常交易"
    • 希望所有的异常交易都被检测到,即判断为正常的交易中尽可能不存在异常交易, 需要关注特异度

精确率(Precision)与召回率(Recall)来比较:

  • 精确率:从你预测结果为正例的样本中, 找出真实也为正例的样本的比例
  • 召回率:在真实为正例的样本中, 找出全部预测结果为正例的样本所占比例(查的全,对正样本的区分能力)

那么怎么更好理解这个两个概念,看下图:

还有其他的评估标准,F1-score,反映了模型的稳健型

下面使用混淆矩阵来对上述皮马印第安人糖尿病预测模型进行评估:

计算并展示混淆矩阵

 # 计算并展示混淆矩阵
 confution_matrix = metrics.confusion_matrix(y_test, y_pred)
 print(confution_matrix)
 [[114  16]
  [ 46  16]]
image-20211227181325046

计算从混淆矩阵得来的各种指标:

 # 将矩阵中的四个结果赋值
 TN = confution_matrix[0, 0]
 FP = confution_matrix[0, 1]
 FN = confution_matrix[1, 0]
 TP = confution_matrix[1, 1]
 # 计算指标 
 # 精确度 - 整体样本中预测正确的比率, 和前面得出的准确率结果相等
 accuracy = (TP + TN)/(TP + TN + FP + FN)
 print("accuracy_rate: ", accuracy)
 # 错误率 - 整体样本中预测错误的比率, 可以用 (100% - 精确度) 直接得出
 miss_rate = 1 - accuracy
 print("miss_rate: ", accuracy)
 # 召回率 - 在你认为是正样本的样本中, 预测正确的比率
 recall = TP/(TP + FN)
 print("recall_rate: ", recall)
 # 特异度 - 在你认为是负样本的样本中, 预测正确的比率
 specificity = TN/(TN + FP)
 print("specificity_rate: ", specificity)
 # 精确率 - 在预测结果为正的样本中, 预测正确的比率 
 precision = TP/(TP + FP)
 print("precision_rate: ", precision)
 # F1分数 - 综合 precision 和 recall 的一个判断指标
 f1_score = (2 * precision * recall)/(precision + recall)
 print("f1_score: ", precision)
 ​
 accuracy_rate:  0.6770833333333334
 miss_rate:  0.6770833333333334
 recall_rate:  0.25806451612903225
 specificity_rate:  0.8769230769230769
 precision_rate:  0.5
 f1_score:  0.5

观察以上结果, 发现这个模型还是很有问题的, 最大的问题就是召回率过低 - 即在你认为是负样本的样本中预测正确的成功几率过低. 当然最终需要得到什么样的模型, 还是取决于实际应用.

6.1.3 分类评估报告API
  • sklearn.metrics.classification_report(y_true, y_pred, labels=[], target_names=None )
    • y_true:真实目标值
    • y_pred:估计器预测目标值
    • labels:指定类别对应的数字
    • target_names:目标类别名称
    • return:每个类别精确率与召回率
print("精确率和召回率为:", classification_report(y_test, lr.predict(x_test), labels=[2, 4], target_names=['良性', '恶性']))

假设这样一个情况,如果99个样本癌症,1个样本非癌症,不管怎样我全都预测正例(默认癌症为正例),准确率就为99%但是这样效果并不好,这就是样本不均衡下的评估问题

问题:如何衡量样本不均衡下的评估?

6.2 ROC曲线与AUC指标

6.2.1 知道TPR与FPR
  • TPR = TP / (TP + FN)
    • 所有真实类别为1的样本中,预测类别为1的比例
  • FPR = FP / (FP + FN)
    • 所有真实类别为0的样本中,预测类别为1的比例

6.2.2 ROC曲线

  • ROC曲线的横轴就是FPRate,纵轴就是TPRate,当二者相等时,表示的意义则是:对于不论真实类别是1还是0的样本,分类器预测为1的概率是相等的,此时AUC为0.5
6.2.3AUC指标
  • AUC的概率意义是随机取一对正负样本,正样本得分大于负样本的概率
  • AUC的最小值为0.5,最大值为1,取值越高越好
  • AUC=1,完美分类器,采用这个预测模型时,不管设定什么阈值都能得出完美预测。绝大多数预测的场合,不存在完美分类器。
  • 0.5<AUC<1,优于随机猜测。这个分类器(模型)妥善设定阈值的话,能有预测价值。

最终AUC的范围在[0.5, 1]之间,并且越接近1越好

6.2.4 AUC计算API
  • from sklearn.metrics import roc_auc_score
    • sklearn.metrics.roc_auc_score(y_true, y_score)
      • 计算ROC曲线面积,即AUC值
      • y_true:每个样本的真实类别,必须为0(反例),1(正例)标记
      • y_score:每个样本预测的概率值
# 0.5~1之间,越接近于1约好
y_test = np.where(y_test > 2.5, 1, 0)

print("AUC指标:", roc_auc_score(y_test, lr.predict(x_test)))
6.2.5、总结
  • AUC只能用来评价二分类
  • AUC非常适合评价样本不平衡中的分类器性能

模型保存和加载

  • 学习目标
    • 应用joblib实现模型的保存与加载

当训练或者计算好一个模型之后,那么如果别人需要我们提供结果预测,就需要保存模型(主要是保存算法的参数)

1、sklearn模型的保存和加载API

  • from sklearn.externals import joblib
    • 保存:joblib.dump(rf, 'test.pkl')
    • 加载:estimator = joblib.load('test.pkl')

2、线性回归的模型保存加载案例

  • 保存
# 使用线性模型进行预测
# 使用正规方程求解
lr = LinearRegression()
# 此时在干什么?
lr.fit(x_train, y_train)
# 保存训练完结束的模型
joblib.dump(lr, "test.pkl")
  • 加载
# 通过已有的模型去预测房价
model = joblib.load("test.pkl")
print("从文件加载进来的模型预测房价的结果:", std_y.inverse_transform(model.predict(x_test)))

无监督学习-K-means算法

  • 学习目标
    • 说明K-means算法原理
    • 说明K-means的性能评估标准轮廓系数
    • 说明K-means的优缺点
  • 实际应用
    • instacart用户聚类

回忆非监督学习的特点?

1、 什么是无监督学习

  • 一家广告平台需要根据相似的人口学特征和购买习惯将美国人口分成不同的小组,以便广告客户可以通过有关联的广告接触到他们的目标客户。
  • Airbnb 需要将自己的房屋清单分组成不同的社区,以便用户能更轻松地查阅这些清单。
  • 一个数据科学团队需要降低一个大型数据集的维度的数量,以便简化建模和降低文件大小。

我们可以怎样最有用地对其进行归纳和分组?我们可以怎样以一种压缩格式有效地表征数据?这都是无监督学习的目标,之所以称之为无监督,是因为这是从无标签的数据开始学习的。

2、 无监督学习包含算法

  • 聚类
    • K-means(K均值聚类)
  • 降维
    • PCA

3、 K-means原理

我们先来看一下一个K-means的聚类效果图

K-means 如何聚类

3.1 K-means聚类步骤

  • 1、随机设置K个特征空间内的点作为初始的聚类中心
  • 2、对于其他每个点计算到K个中心的距离,未知的点选择最近的一个聚类中心点作为标记类别
  • 3、接着对着标记的聚类中心之后,重新计算出每个聚类的新中心点(平均值)
  • 4、如果计算得出的新中心点与原中心点一样,那么结束,否则重新进行第二步过程

我们以一张图来解释效果

4、K-meansAPI

  • sklearn.cluster.KMeans(n_clusters=8,init=‘k-means++’)
    • k-means聚类
    • n_clusters:开始的聚类中心数量
    • init:初始化方法,默认为'k-means ++’
    • labels_:默认标记的类型,可以和真实值比较(不是值比较)

5、 案例:k-means对Instacart Market用户聚类

5.1 分析

  • 1、降维之后的数据
  • 2、k-means聚类
  • 3、聚类结果显示

5.2 代码

# 取500个用户进行测试
cust = data[:500]
km = KMeans(n_clusters=4)
km.fit(cust)
pre = km.predict(cust)

问题:如何去评估聚类的效果呢?

6、Kmeans性能评估指标

6.1 轮廓系数

轮廓系数公式:

注:对于每个点i 为已聚类数据中的样本 ,b_i 为i 到其它族群的所有样本的距离最小值,a_i 为i 到本身簇的距离平均值。最终计算出所有的样本点的轮廓系数平均值

6.2 轮廓系数值分析

  • 分析过程(我们以一个蓝1点为例)
    • 1、计算出蓝1离本身族群所有点的距离的平均值a_i
    • 2、蓝1到其它两个族群的距离计算出平均值红平均,绿平均,取最小的那个距离作为b_i
    • 根据公式:极端值考虑:如果b_i >>a_i: 那么公式结果趋近于1;如果a_i>>>b_i: 那么公式结果趋近于-1

6.3 结论

如果b_i>>a_i:趋近于1效果越好, b_i<<a_i:趋近于-1,效果不好。轮廓系数的值是介于 [-1,1] ,越趋近于1代表内聚度和分离度都相对较优。

6.4 轮廓系数API

  • sklearn.metrics.silhouette_score(X, labels)
    • 计算所有样本的平均轮廓系数
    • X:特征值
    • labels:被聚类标记的目标值

6.5 用户聚类结果评估

silhouette_score(cust, pre)

7、K-means总结

  • 特点分析:采用迭代式算法,直观易懂并且非常实用
  • 缺点:容易收敛到局部最优解(多次聚类)

注意:聚类一般做在分类之前

课后回顾

1、线性回归的参数求解的方法是什么?

答案: 正规方程和梯度下降

2、什么是过拟合? 原因有哪些?

答案: 过拟合就是训练误差很小, 但是测试误差很大

​ 原因有: 样本偏差, 模型过于复杂

3、分类问题, 回归问题, 聚类问题的评估方法分别是什么?

答案: 分类问题的评估方法是准确率, 精确率和召回率

回归问题的评估方法是均方差

聚类问题的评估方法是轮廓系数

机器学习的算法选择

以上我们学习了这么多种有关机器学习的算法, 那么面对具体问题的时候该如何选择呢?

sklearn的官方手册给出了一份 cheat sheet, 可以简单的解决你的困惑.

英文版

中文版

Views: 315

03 分类算法

数据集介绍与划分

学习目标

  • 目标
    • 知道数据集的分为训练集和测试集
    • 知道sklearn的分类、回归数据集

拿到的数据是否全部都用来训练一个模型?

数据集的划分

机器学习一般的数据集会划分为两个部分:

  • 训练数据:用于训练,构建模型
  • 测试数据:在模型检验时使用,用于评估模型是否有效

划分比例:

  • 训练集:70% 80% 75%
  • 测试集:30% 20% 30%

API

  • sklearn.model_selection.train_test_split(arrays, *options)
    • x 数据集的特征值
    • y 数据集的标签值
    • test_size 测试集的大小,一般为float
    • random_state 随机数种子,不同的种子会造成不同的随机采样结果。相同的种子采样结果相同。
    • return ,测试集特征训练集特征值值,训练标签,测试标签(默认随机取)

结合后面的数据集作介绍

sklearn数据集介绍

API

  • sklearn.datasets
    • 加载获取流行数据集
    • datasets.load_*()
      • 获取小规模数据集,数据包含在datasets里
    • datasets.fetch_*(data_home=None)
      • 获取大规模数据集,需要从网络上下载,函数的第一个参数是data_home,表示数据集下载的目录,默认是 ~/scikit_learn_data/

分类和回归数据集

  • 分类数据集
  • sklearn.datasets.fetch_20newsgroups(data_home=None,subset=‘train’)
    • subset: 'train'或者'test','all',可选,选择要加载的数据集.训练集的“训练”,测试集的“测试”,两者的“全部”
  • 回归数据集

返回类型

  • load和fetch返回的数据类型datasets.base.Bunch(字典格式)
    • data:特征数据数组,是 [n_samples * n_features] 的二维 numpy.ndarray 数组
    • target:标签数组,是 n_samples 的一维 numpy.ndarray 数组
    • DESCR:数据描述
    • feature_names:特征名,新闻数据,手写数字、回归数据集没有
    • target_names:标签名

sklearn转换器和估计器

学习目标

  • 知道sklearn的转换器和估计器流程

转换器和估计器

转换器

想一下之前做的特征工程的步骤?

  • 1、实例化 (实例化的是一个转换器类(Transformer))
  • 2、调用fit_transform(对于文档建立分类词频矩阵,不能同时调用)

我们把特征工程的接口称之为转换器,其中转换器调用有这么几种形式

  • fit_transform
  • fit
  • transform

这几个方法之间的区别是什么呢?我们看以下代码就清楚了

In [1]: from sklearn.preprocessing import StandardScaler

In [2]: std1 = StandardScaler()

In [3]: a = [[1,2,3], [4,5,6]]

In [4]: std1.fit_transform(a)
Out[4]:
array([[-1., -1., -1.],
       [ 1.,  1.,  1.]])

In [5]: std2 = StandardScaler()

In [6]: std2.fit(a)
Out[6]: StandardScaler(copy=True, with_mean=True, with_std=True)

In [7]: std2.transform(a)
Out[7]:
array([[-1., -1., -1.],
       [ 1.,  1.,  1.]])

从中可以看出,fit_transform的作用相当于transform加上fit。但是为什么还要提供单独的fit呢, 我们还是使用原来的std2来进行标准化看看

In [8]: b = [[7,8,9], [10, 11, 12]]

In [9]: std2.transform(b)
Out[9]:
array([[3., 3., 3.],
       [5., 5., 5.]])

In [10]: std2.fit_transform(b)
Out[10]:
array([[-1., -1., -1.],
       [ 1.,  1.,  1.]])

估计器(sklearn机器学习算法的实现)

在sklearn中,估计器(estimator)是一个重要的角色,是一类实现了算法的API

1、用于分类的估计器:

  • sklearn.neighbors k-近邻算法
  • sklearn.naive_bayes 贝叶斯
  • sklearn.linear_model.LogisticRegression 逻辑回归
  • sklearn.tree 决策树与随机森林

2、用于回归的估计器:

  • sklearn.linear_model.LinearRegression 线性回归
  • sklearn.linear_model.Ridge 岭回归

3、用于无监督学习的估计器

  • sklearn.cluster.KMeans 聚类

1.3 估计器工作流程

K-近邻算法

学习目标

  • 说明K-近邻算法的距离公式
  • 说明K-近邻算法的超参数K值以及取值问题
  • 说明K-近邻算法的优缺点
  • 应用KNeighborsClassifier实现分类
  • 了解分类算法的评估标准准确率

应用

  • Facebook签到位置预测

问题:回忆分类问题的判定方法

什么是K-近邻算法?

K-Nearest-Neighbors, 即选举出K个最临近的邻居的算法

地图K近邻算法
  • 通过你的“邻居”来推断出你的类别

K-近邻算法(KNN)

定义

如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。

来源:KNN算法最早是由Cover和Hart提出的一种分类算法

距离公式

两个样本的距离可以通过如下公式计算,又叫欧式距离

(也有其他距离的定义方式)

2、电影类型分析

假设我们有现在几部电影

其中? 号电影不知道类别,如何去预测?我们可以利用K近邻算法的思想

问题

  • 如果取的最近的电影数量不一样?会是什么结果?

K-近邻算法数据的特征工程处理

  • 结合前面的约会对象数据,分析K-近邻算法需要做什么样的处理

K-近邻算法API

  • sklearn.neighbors.KNeighborsClassifier(n_neighbors=5,algorithm='auto')
    • n_neighbors:int,可选(默认= 5),k_neighbors查询默认使用的邻居数
    • algorithm:{‘auto’,‘ball_tree’,‘kd_tree’,‘brute’},可选用于计算最近邻居的算法:
      • ‘ball_tree’将会使用 BallTree
      • ‘kd_tree’将使用 KDTree
      • ‘auto’将尝试根据传递给fit方法的值来决定最合适的算法。 (不同实现方式影响效率)

案例:鸢尾花分类预测

数据集介绍

使用sklearn的iris小数据集

# 模型调用
from sklearn.neighbors import KNeighborsClassifier
import sklearn.datasets

导入数据集:

data = sklearn.datasets.load_iris()

查看数据集描述:

data.DESCR

输出略

特征X:

X = data.data

打印X的形状:

X.shape

150行样本数据, 每行4列特征:

(150, 4)

标签y:

y = data.target

打印输出:

array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])

创建KNN分类器实例(k = 1)

# 创建实例
knn = KNeighborsClassifier(n_neighbors=1)

打印模型

knn

输出:

KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
                     metric_params=None, n_jobs=None, n_neighbors=1, p=2,
                     weights='uniform')

创建KNN分类器实例(k = 5)

# 创建另一个实例
knn_5 = KNeighborsClassifier()

确认模型

# 确认模型结构 n_neighbors默认是5
print(knn_5)

输出:

KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
                     metric_params=None, n_jobs=None, n_neighbors=5, p=2,
                     weights='uniform')

我们已经创建了两种模型(k=1, k=5), 下一步确定模型评估的策略

  • 使用整个数据集进行模型训练使用相同数据集测试
  • 将整个数据集拆分为训练数据集和测试数据集, 进行交叉验证

然后对比预测结果和实际结果(准确率)来评估模型表现

准确率

  • 正确预测的比率
  • 用于评估分类模型表现的常用指标

导入计算准确率的方法

from sklearn.metrics import accuracy_score

对全数据集进行训练和预测(k=1):

knn.fit(X, y)
y_pred = knn.predict(X)
print(accuracy_score(y, y_pred))

准确率为:

1.0

也可以这样比对每个元素:

y_pred == y

输出:

array([ True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True])

再次训练和预测(当k=5时):

knn_5.fit(X, y)
predct_y = knn_5.predict(X)

打印结果:

print(accuracy_score(y, y_pred))

仍然百分百正确:

1.0

这100%的准确率并不能说明当前的模型是最佳模型, 因为测试的都是训练过的数据.

k值取值过低会导致模型过于复杂, 会因为过拟合导致正确率反降.

为了解决这个问题, 我们一般要先将完全数据集分割成训练集和测试集两部分.

  • 训练集用于训练 (fit)
  • 测试集用于评估 (predict)

导入 train_test_split 用于分割数据集

from sklearn.model_selection import train_test_split

将数据集分割成训练集和测试集:

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4)

打印训练集和测试集的样本数量做对比:

print("train:test = {}:{}".format(X_train.shape[0], X_test.shape[0]))
train:test = 90:60

分离后训练集和测试集的预测 (k=5)

# 训练集和测试集分离后的预测 
knn_5_s = KNeighborsClassifier()
knn_5_s.fit(X_train, y_train)
y_train_pred = knn_5_s.predict(X_train)
y_test_pred = knn_5_s.predict(X_test)

打印训练集预测准确率 :

print("训练集预测准确率(knn5): ", accuracy_score(y_train, y_train_pred))
训练集预测准确率(knn5):  0.9777777777777777

I打印测试集预测准确率 :

print("测试集预测准确率(knn5): ", accuracy_score(y_test, y_test_pred))
测试集预测准确率(knn5):  0.9666666666666667

在测试k=1的情况

# 测试 k=1 的情况
knn_1_s = KNeighborsClassifier(n_neighbors=1)
knn_1_s.fit(X_train, y_train)
y_train_pred = knn_1_s.predict(X_train)
y_test_pred = knn_1_s.predict(X_test)

打印结果:

print("训练集预测准确率(knn1): ", accuracy_score(y_train, y_train_pred))
print("测试集预测准确率(knn1): ", accuracy_score(y_test, y_test_pred))
训练集预测准确率(knn1):  1.0
测试集预测准确率(knn1):  0.9333333333333333

那么到底k的取值为何时准确率最好呢?

可以让k的取值从1到25分别进行测试, 取准确率最高时的k值即可:

score_train = []
score_test = []
for  k in range(1, 26):
    knn_s = KNeighborsClassifier(n_neighbors=k)
    knn_s.fit(X_train, y_train)
    y_train_pred = knn_s.predict(X_train)
    y_test_pred = knn_s.predict(X_test)
    score_train.append(accuracy_score(y_train, y_train_pred))
    score_test.append(accuracy_score(y_test, y_test_pred))

for k in range(0, len(score_train) ):
    print("训练集预测准确率(k={}): {}".format(k + 1, score_train[k]))

print("===========")

for k in range(0, len(score_test) ):
    print("测试集预测准确率(k={}): {}".format(k + 1, score_test[k]))
训练集预测准确率(k=1): 1.0
训练集预测准确率(k=2): 1.0
训练集预测准确率(k=3): 0.9777777777777777
训练集预测准确率(k=4): 0.9777777777777777
训练集预测准确率(k=5): 0.9777777777777777
训练集预测准确率(k=6): 0.9777777777777777
训练集预测准确率(k=7): 0.9777777777777777
训练集预测准确率(k=8): 0.9666666666666667
训练集预测准确率(k=9): 0.9666666666666667
训练集预测准确率(k=10): 0.9666666666666667
训练集预测准确率(k=11): 0.9777777777777777
训练集预测准确率(k=12): 0.9666666666666667
训练集预测准确率(k=13): 0.9777777777777777
训练集预测准确率(k=14): 0.9666666666666667
训练集预测准确率(k=15): 0.9555555555555556
训练集预测准确率(k=16): 0.9666666666666667
训练集预测准确率(k=17): 0.9666666666666667
训练集预测准确率(k=18): 0.9666666666666667
训练集预测准确率(k=19): 0.9666666666666667
训练集预测准确率(k=20): 0.9666666666666667
训练集预测准确率(k=21): 0.9666666666666667
训练集预测准确率(k=22): 0.9666666666666667
训练集预测准确率(k=23): 0.9666666666666667
训练集预测准确率(k=24): 0.9666666666666667
训练集预测准确率(k=25): 0.9666666666666667
===========
测试集预测准确率(k=1): 0.9333333333333333
测试集预测准确率(k=2): 0.9166666666666666
测试集预测准确率(k=3): 0.9666666666666667
测试集预测准确率(k=4): 0.9666666666666667
测试集预测准确率(k=5): 0.9666666666666667
测试集预测准确率(k=6): 0.9666666666666667
测试集预测准确率(k=7): 0.9666666666666667
测试集预测准确率(k=8): 0.9666666666666667
测试集预测准确率(k=9): 0.9833333333333333
测试集预测准确率(k=10): 0.9666666666666667
测试集预测准确率(k=11): 0.9666666666666667
测试集预测准确率(k=12): 0.95
测试集预测准确率(k=13): 0.9333333333333333
测试集预测准确率(k=14): 0.9
测试集预测准确率(k=15): 0.9166666666666666
测试集预测准确率(k=16): 0.9
测试集预测准确率(k=17): 0.9
测试集预测准确率(k=18): 0.9
测试集预测准确率(k=19): 0.9
测试集预测准确率(k=20): 0.9
测试集预测准确率(k=21): 0.9
测试集预测准确率(k=22): 0.8833333333333333
测试集预测准确率(k=23): 0.8833333333333333
测试集预测准确率(k=24): 0.8833333333333333
测试集预测准确率(k=25): 0.8666666666666667
  • 对于训练集的评估, 由于是训练集训练得来的模型, 因此当k=1时可以达到100%准确率, 但是随着k值的增加准确率开始略有下降
  • 对测试集的评估, 由于测试集评估相当于是对未知数据进行评估,因此测试集评估的准确率更加重要, 根据经验合理的k值一般略低于训练样本数的平方根, 其准确率容易受数据的干扰,
import math
ideal_k = int(math.sqrt(X.shape[0]))
print("当k取值{}时, 测试集的评估准确率为{}".format(ideal_k, score_test[ideal_k - 1]))
当k取值12时, 测试集的评估准确率为0.95

为了找出最合适的k值可以对数据集进行多轮交叉验证, 选出综合准确率更高时的k值

也可以使用pyplot绘图实现可视化, 帮助你更直观找到合适的k值取值区间:

import matplotlib.pyplot as plt

绘 Training Accuracy折线图:

plt.plot(range(1, 26), score_train)
plt.xlabel('K(KNN Model)')
plt.ylabel('Training Accuracy')

输出:

Text(0,0.5,'Training Accuracy')

绘 Testing Accuracy折线图:

plt.plot(range(1, 26), score_test)
plt.xlabel('K(KNN Model)')
plt.ylabel('Testing Accuracy')

输出:

Text(0,0.5,'Testing Accuracy')

注意:

  • K 越小 模型越复杂 容易过拟合 对训练集训练准确率越高
  • K 越大 模型越简单 容易欠拟合

案例:签到位置预测(FaceBook)

FBLocation介绍

数据介绍:将根据用户的坐标位置,定位准确性和时间戳预测用户签到的地点ID。

train.csv,test.csv 
row_id:登记事件的ID
xy:坐标
准确性:定位准确性 
时间:时间戳
place_id:业务的ID,这是您预测的目标

官网:https://www.kaggle.com/navoshta/grid-knn/data

(Kaggle官网手机认证时需要梯子, 输入手机号前面加上+860)

FBlocation-飞桨AI Studio - 人工智能学习实训社区 (baidu.com)

(Baidu飞浆AI Studio里面有公开数据集可以下载, 无需登录)

分析

  • 对于数据做一些基本处理(这里所做的一些处理不一定达到很好的效果,我们只是简单尝试,有些特征我们可以根据一些特征选择的方式去做处理)
    • 缩小数据集范围 DataFrame.query()
    • 删除没用的日期数据 DataFrame.drop(可以选择保留)
    • 将签到位置少于n个用户的删除place_count = data.groupby('place_id').count()tf = place_count[place_count.row_id > 3].reset_index()data = data[data['place_id'].isin(tf.place_id)]
  • 分割数据集
  • 标准化处理
  • k-近邻预测

代码

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier

def knncls():
    """
    K近邻算法预测入住位置类别
    :return:
    """
    # 一、处理数据以及特征工程
    # 1、读取,缩小数据的范围
    data = pd.read_csv("./data/FBlocation/train.csv")

    # 数据逻辑筛选操作 df.query()
    data = data.query("x > 1.0 & x < 1.25 & y > 2.5 & y < 2.75")

    # 删除time这一列特征
    data = data.drop(['time'], axis=1)

    print(data)

    # 删除签到次数少于三次的位置
    place_count = data.groupby('place_id').count()

    tf = place_count[place_count.row_id > 3].reset_index()

    data = data[data['place_id'].isin(tf.place_id)]

    # 3、取出特征值和目标值
    y = data['place_id']
    # y = data[['place_id']]

    x = data.drop(['place_id', 'accuracy', 'row_id'], axis=1)

    # 4、数据分割与特征工程

    # (1)、数据分割
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3)

    # (2)、标准化
    std = StandardScaler()

    # 对训练集进行标准化操作
    x_train = std.fit_transform(x_train)
    print(x_train)

    # 进行测试集的标准化操作
    x_test = std.fit_transform(x_test)

    # 二、算法的输入训练预测
    # K值:算法传入参数不定的值    理论上:k = 根号(样本数)
    # K值:后面会使用参数调优方法,去轮流试出最好的参数[1,3,5,10,20,100,200]
    knn = KNeighborsClassifier(n_neighbors=1)

    # 调用fit()
    knn.fit(x_train, y_train)

    # 预测测试数据集,得出准确率
    y_predict = knn.predict(x_test)

    print("预测测试集类别:", y_predict)

    print("准确率为:", knn.score(x_test, y_test))

    return None

结果分析

准确率: 分类算法的评估之一

  • 1、k值取多大?有什么影响?

k值取很小:容易受到异常点的影响

k值取很大:受到样本均衡的问题

n_neighbors预测准确率备注
10.3421985815602837最差
30.39972419227738376
50.438140267927502
100.4676910953506698
200.49527186761229314
500.5027580772261623较好
1000.4923167848699764
2000.4858156028368794
实验结果(使用飞浆AI Studio)
  • 2、性能问题?

距离计算上面,时间复杂度高

K-近邻总结

  • 优点:
    • 简单,易于理解,易于实现,无需训练
  • 缺点:
    • 懒惰算法,对测试样本分类时的计算量大,内存开销大
    • 必须指定K值,K值选择不当则分类精度不能保证
  • 使用场景:小数据场景,几千~几万样本,具体场景具体业务去测试

在学习KNN后,我们需要考虑以下几个问题,当你把这些问题都解决了,KNN你已经掌握的差不多了。

1,问题描述:

  1,KNN的原理是什么?

  2,KNN算法的时间复杂度,和空间复杂度怎么样?

  3,K值如何选取,取多大合适?

  4,计算两个样本之间的距离,采用哪种距离计算方式好?

  5,类别如何判定最合适?

  6,计算量太大怎么办?

  7,假设样本中,类型分布非常不均匀,这又该怎么办?

2,K近邻原理:

  kNN原理很简单,通过计算待分类样本与已知类别样本之间的距离,找到距离待分类最近的K个已知类别的样本,然后根据少数“服从多数”的判决原则,统计K个样本中各类样本出现的次数,出现次数最多的样本即为待分类样本的类别。

近邻分类

上图中要确定测试样本绿色属于蓝色还是红色。显然,当K=3时,将以1:2的投票结果分类于红色;而K=5时,将以3:2的投票结果分类于蓝色。

我们不禁会反问,这样的分类准确吗?为什么不同的K值会得到不同的分类结果?你有什么理由说待分类的样本与K个样本中出现次数最多样本是同一类?

  我们都听说过这句话“同一类样本之间差异较小,不同类之间差异较大”,这差异通过什么来体现呢?在KNN算法中,样本特征之间差异,主要通过特征之间的“距离”来体现,距离体现了他们之间的相似性,在多维空间中,不同类的样本散布于多维空间之中,同类样本总是聚集在一起,本质是讲就是他们的特征存在相似性,不同类样本之间是彼此分离的。

因此通过计算待分类样本与已分类样本之间的距离来进行分类是可靠的。对于分类决策主要是根据“少数服从多数” 本质上讲就是哪类样本与待分类样本最相似,就分给哪类。(对于这种判决准则,不一定适用于所有情况)

关于为什么K取不同的值,分类结果可能不一致,这主要体现KNN在类边界上,分类结果会随K的取值不同,分类结果不稳定,但对于非类边界分类准确率还是很高的。 

K值选取:

关于K的取值是K-最近邻算法中的一大难题,没有特定的经验公式来告诉我们K应该取多大?K值如果选取的太小,模型太复杂,K值选取的太大的话,又会导致分类模糊。K的取值与实际背景有关,与你数据分析的目标……那么K值到底怎么选取呢?既然K值这么麻烦有没有一些常规选取K值的方法,答案是肯定的。

经验规则:k一般低于训练样本数的平方根。

常用方法有Cross Validation,贝叶斯准则, bootstrap……

上面提到了类与类之间的相似性通过距离来体现,下面我们看下有哪些距离:

3,距离定义:

1)欧式距离

欧氏距离是最常见的距离度量,衡量的是多维空间中各个点之间的绝对距离。

因为计算是基于各维度特征的绝对数值,所以欧氏度量需要保证各维度指标在相同的刻度级别,比如对身高(cm)和体重(kg)两个单位不同的指标使用欧式距离可能使结果失效。

2马氏距离:马氏距离能够缓解由于属性的线性组合带来的距离失真,是数据的协方差矩阵。

3)曼哈顿距离:

曼哈顿距离来源于城市区块距离,是将多个维度上的距离进行求和后的结果。

4)切比雪夫距离

切比雪夫距离起源于国际象棋中国王的走法,我们知道国际象棋国王每次只能往周围的8格中走一步,那么如果要从棋盘中A格(x1, y1)走到B格(x2, y2)最少需要走几步?扩展到多维空间,其实切比雪夫距离就是当p趋向于无穷大时的明氏距离。

5)闵氏距离:

闵氏距离是欧氏距离的推广,是对多个距离度量公式的概括性的表述。r取值为2式为曼哈顿距离 ;r取值为1时为欧式距离。

6)平均距离

7)弦距离:

8)测地距离

关于距离选择:

高维度对距离衡量的影响:众所周知当变量数越多,欧式距离的区分能力就越差。

变量值域对距离的影响:值域越大的变量常常会在距离计算中占据主导作用,因此应先对变量进行标准化。

比较常用的是选用欧式距离。可是这个距离真的具有普适性吗?《模式分类》中指出欧式距离对平移是敏感的,这点严重影响了判定的结果。在此必须选用一个对已知的变换(比如平移、旋转、尺度变换等)不敏感的距离度量。书中提出了采用切空间距离(tangent distance)来替代传统的欧氏距离。

最近邻法介绍:

切空间距离定义:

(引自:杨剑. 基于局部切距离的近邻法[A]. 中国自动化学会智能自动化专业委员会、中国科学院自动化研究所.2005年中国智能自动化会议论文集[C].中国自动化学会智能自动化专业委员会、中国科学院自动化研究所:,2005:6.)

4,算法实现步骤:

简单来说,KNN可以看成:有那么一堆你已经知道分类的数据,然后当一个新数据进入的时候,就开始跟训练数据里的每个点求距离,然后挑离这个训练数据最近的K个点看看这几个点属于什么类型,然后用少数服从多数的原则,给新数据归类。

具体步骤如下

  step.1---计算未知样本和每个训练样本的距离dist ;

  step.2---对dist从小到大排序;

  step.3---取出距离从小到大的K个训练样本,作为K-最近邻样本;

  step.4---统计K-最近邻样本中每个类标号出现的次数

  step.5---选择出现频率最大的类标号作为未知样本的类标号,

5,算法复杂度分析:

   KNN算法简单有效,但没有优化的暴力法那样效率容易达到瓶颈。如样本个数为N,特征维度为D的时候,该算法时间复杂度呈O(DN)增长。所以通常KNN的实现会把训练数据构建成K-D Tree(K-dimensional tree),构建过程很快,甚至不用计算D维欧氏距离,而搜索速度高达O(D*log(N))。不过当D维度过高,会产生所谓的”维度灾难“,最终效率会降低到与暴力法一样。因此通常D>20以后,最好使用更高效率的Ball-Tree,其时间复杂度为O(D*log(N))。人们经过长期的实践发现KNN算法虽然简单,但能处理大规模的数据分类,尤其适用于样本分类边界不规则的情况。最重要的是该算法是很多高级机器学习算法的基础。

(KNeighbors Classifier可以设置3种算法:‘brute’,‘kd_tree’,‘ball_tree’。如果不知道用哪个好,设置‘auto’让KNeighborsClassifier自己根据输入去决定。)

(引用:http://blog.csdn.net/lsldd/article/details/41357931)

6,类别如何判定最合理?

  投票法没有考虑近邻的距离的远近,距离更近的近邻也许更应该决定最终的分类,所以加权投票法更恰当一些。

模型选择与调优

  • 学习目标
    • 说明交叉验证过程
    • 说明超参数搜索过程
    • 应用GridSearchCV实现算法参数的调优
  • 具体应用
    • Facebook签到位置预测调优

为什么需要交叉验证

交叉验证目的:为了让被评估的模型更加准确可信

什么是交叉验证(cross validation)

交叉验证:将拿到的训练数据,分为训练和验证集。以下图为例:将数据分成5份,其中一份作为验证集。然后经过5次(组)的测试,每次都更换不同的验证集。即得到5组模型的结果,取平均值作为最终结果。又称5折交叉验证。

分析

我们之前知道数据分为训练集和测试集,但是为了让从训练得到模型结果更加准确。做以下处理

  • 训练集:训练集+验证集
  • 测试集:测试集

问题:那么这个只是对于参数得出更好的结果,那么怎么选择或者调优参数呢?

通常情况下,有很多参数是需要手动指定的(如k-近邻算法中的K值),这种叫超参数。但是手动过程繁杂,所以需要对模型预设几种超参数组合。每组超参数都采用交叉验证来进行评估。最后选出最优参数组合建立模型。

模型选择与调优

  • sklearn.model_selection.GridSearchCV(estimator, param_grid=None,cv=None)
    • 对估计器的指定参数值进行详尽搜索
    • estimator:估计器对象
    • param_grid:估计器参数(dict){“n_neighbors”:[1,3,5]}
    • cv:指定几折交叉验证
    • fit:输入训练数据
    • score:准确率
    • 结果分析:
      • bestscore:在交叉验证中验证的最好结果_
      • bestestimator:最好的参数模型
      • cvresults:每次交叉验证后的验证集准确率结果和训练集准确率结果

Facebook签到位置预测K值调优

使用网格搜索估计器

from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV

# 使用网格搜索估计器实现参数调优方法
# 从[1,3,5,10,20,100,200]中交叉验证轮流试出最好的参数
k_params = {"n_neighbors": [1,3,5,10,20,100,200]}

knn = KNeighborsClassifier(n_neighbors=50)
gc = GridSearchCV(knn, param_grid=k_params, cv=2)

gc.fit(x_train, y_train)

print("选择了某个模型测试集当中预测的准确率为:", gc.score(x_test, y_test))

# 训练验证集的结果
print("在交叉验证当中验证的最好结果:", gc.best_score_)
print("gc选择了的模型K值是:", gc.best_estimator_)
print("每次交叉验证的结果为:", gc.cv_results_)

输出

选择了某个模型测试集当中预测的准确率为: 0.48877068557919623
在交叉验证当中验证的最好结果: 0.4906265833474075
gc选择了的模型K值是: KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
                     metric_params=None, n_jobs=None, n_neighbors=20, p=2,
                     weights='uniform')
每次交叉验证的结果为: {'mean_fit_time': array([0.00542653, 0.00541699, 0.00546551, 0.00545597, 0.00556421,
       0.0055443 , 0.00544441]), 'std_fit_time': array([6.68764114e-05, 3.20672989e-05, 4.22000885e-05, 8.96453857e-05,
       1.06811523e-04, 1.09553337e-04, 3.01599503e-05]), 'mean_score_time': array([0.15212929, 0.20162416, 0.16866851, 0.17537868, 0.18768466,
       0.28859103, 0.42182934]), 'std_score_time': array([0.00433338, 0.03741288, 0.00232887, 0.00084627, 0.0002321 ,
       0.00352132, 0.0011555 ]), 'param_n_neighbors': masked_array(data=[1, 3, 5, 10, 20, 100, 200],
             mask=[False, False, False, False, False, False, False],
       fill_value='?',
            dtype=object), 'params': [{'n_neighbors': 1}, {'n_neighbors': 3}, {'n_neighbors': 5}, {'n_neighbors': 10}, {'n_neighbors': 20}, {'n_neighbors': 100}, {'n_neighbors': 200}], 'split0_test_score': array([0.35483871, 0.39739909, 0.44958622, 0.48099983, 0.49315994,
       0.48826212, 0.46123966]), 'split1_test_score': array([0.35179868, 0.39976355, 0.43523054, 0.47086641, 0.48809323,
       0.47762202, 0.45938186]), 'mean_test_score': array([0.3533187 , 0.39858132, 0.44240838, 0.47593312, 0.49062658,
       0.48294207, 0.46031076]), 'std_test_score': array([0.00152001, 0.00118223, 0.00717784, 0.00506671, 0.00253336,
       0.00532005, 0.0009289 ]), 'rank_test_score': array([7, 6, 5, 3, 1, 2, 4], dtype=int32)}

可以看出, 当 k=20 时, 最好的预测准确率为 0.49062658

朴素贝叶斯算法

  • 学习目标
    • 说明条件概率与联合概率
    • 说明贝叶斯公式、以及特征独立的关系
    • 记忆贝叶斯公式
    • 知道拉普拉斯平滑系数
    • 应用贝叶斯公式实现概率的计算
  • 具体应用
    • 20类新闻文章分类预测

什么是朴素贝叶斯分类方法

概率基础

概率(Probability)定义

  • 概率定义为一件事情发生的可能性
    • 扔出一个硬币,结果头像朝上
    • 某天是晴天
  • P(X) : 取值在[0, 1]

案例: 预测女神是否喜欢

在讲这两个概率之前我们通过一个例子,来计算一些结果:

问题如下:

  • 女神喜欢一个人的概率
  • 职业是程序员并且体型匀称的概率
  • 被女神喜欢职业又是程序员的概率
  • 被女神喜欢而职业是产品且体重超重的概率

那么这些问题该如何计算呢?

条件概率与联合概率

  • 联合概率:包含多个条件,且所有条件同时成立的概率
    • 记作:P(A,B)
    • 特性:P(A, B) = P(A)P(B)
  • 条件概率:就是事件A在另外一个事件B已经发生条件下的发生概率
    • 记作:P(A|B)
    • 特性:P(A1,A2|B) = P(A1|B)P(A2|B)

注意:此条件概率成立的前提,是由于A1,A2相互独立(完全不相关), 这也是为什么称之为朴素贝叶斯的原因. (这个假设在实际应用中往往是不成立的,在属性个数比较多或者属性之间相关性较大时,分类效果不好。)

而在属性相关性较小时,朴素贝叶斯性能最为良好。对于这一点,有半朴素贝叶斯之类的算法通过考虑部分关联性适度改进。

这样我们计算结果为:

p(程序员, 匀称) =  P(程序员)P(匀称) =3/7*(4/7) = 12/49 
P(产品, 超重|喜欢) = P(产品|喜欢)P(超重|喜欢)=1/2 *  1/4 = 1/8

那么,我们知道了这些知识之后,继续回到我们的主题中。朴素贝叶斯如何分类,这个算法经常会用在文本分类,那就来看文章分类是一个什么样的问题?

P(科技|文章1)

P(娱乐|文章1)

这个了类似一个条件概率,那么仔细一想,给定文章其实相当于给定什么?结合前面我们将文本特征抽取的时候讲的?所以我们可以理解为:

P(科技|文章1) = P(科技|词1, 词2, 词4, 词4 ... ...)

P(娱乐|文章1) = P(娱乐|词1, 词2, 词4, 词4 ... ...)

但是这个公式怎么求?前面并没有参考例子,其实是相似的,我们可以使用贝叶斯公式去计算

贝叶斯公式

公式

那么这个公式如果应用在文章分类的场景当中,我们可以这样看:

公式分为三个部分:

  • P(C):每个文档类别的概率(某文档类别数量/总文档数量)
  • P(W│C):给定类别下特征(被预测文档中出现的词)的概率
    • 计算方法:P(F1│C)=Ni/N (训练文档中去计算)
      • Ni为该F1词在C类别所有文档中出现的次数
      • N为所属类别C下的文档所有词出现的次数和
  • P(F1,F2,…) 预测文档中每个词的概率

如果在一篇文章中计算两个类别概率比较:

P(科技类|文章1) = P(文章1|科技类)P(科技类)/p(文章)

P(娱乐类|文章1) = P(文章1|娱乐类)P(娱乐类)/p(文章)

所以我们只要比较前面的大小就可以,得出谁的概率大

文章分类计算

假设我们从训练数据集得到如下信息:

训练集中一共90个文档, 其中30个是科技类, 60是娱乐类.第一列为某个在文档中出现的单词, 单词右边的列表示这个单词在某个分类下共有多少个文档中出现过这个单词

则通过贝叶斯公式可以计算:

该文档为科技类别的概率 :

P(科技|影院,支付宝,云计算) = 𝑃(影院,支付宝,云计算|科技)∗P(科技)=(8/100)∗(20/100)∗(63/100)∗(30/90) = 0.00456109

该文档为娱乐类别的概率 :

P(娱乐|影院,支付宝,云计算) = 𝑃(影院,支付宝,云计算|娱乐)∗P(娱乐)=(56/121)∗(15/121)∗(0/121)∗(60/90) = 0

思考:我们计算出来某个概率为0,合适吗?

拉普拉斯平滑系数

当每个类别未出现导致概率为0时,可以采用贝叶斯估计的方式来解决。

为了防止计算出的分类概率为0,可以生成一个接近于0的概率代替0,几乎不影响原有的先验概率分布。贝叶斯估计公式中,α称之为拉普拉斯平滑(Laplace smoothing), 为一个大于0的常数, 常取值为1。

P(娱乐|影院,支付宝,云计算) 
=P(影院,支付宝,云计算|娱乐)P(娱乐) 
=P(影院|娱乐)*P(支付宝|娱乐)*P(云计算|娱乐)P(娱乐)
=(56+1/121+4)(15+1/121+4)(0+1/121+1*4)(60/90) 
= 0.00002

API

  • sklearn.naive_bayes.MultinomialNB(alpha = 1.0)
    • 朴素贝叶斯分类
    • alpha:拉普拉斯平滑系数

案例: 从20个新闻组数据集进行分类训练

4.1 分析

  • 分割数据集
  • tfidf进行的特征抽取
  • 朴素贝叶斯预测

4.2 代码

from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB

"""
朴素贝叶斯对新闻数据集进行预测
:return:
"""

# 获取新闻的数据,20个类别
news = fetch_20newsgroups(subset='all')
# 进行数据集分割
x_train, x_test, y_train, y_test = train_test_split(news.data, news.target, test_size=0.3)

# 对于文本数据,进行特征抽取
tf = TfidfVectorizer()

# 提取tfidf特征
x_train = tf.fit_transform(x_train)
# 这里打印出来的列表是:训练集当中的所有不同词的组成的一个列表
# print(tf.get_feature_names())
# print(x_train.toarray())

print(x_train.shape)
# nnz: number of none zero entries
print(x_train.nnz / float(x_train.shape[0]))

输出:

(13192, 136677)
156.75280473013947

我们从以上输出可以看出,提取的TF-IDF 向量是非常稀疏的,才有150多个非零特征.

# 不能调用fit_transform
x_test = tf.transform(x_test)

# 朴素贝叶斯估计器
mlb = MultinomialNB(alpha=1.0)
mlb.fit(x_train, y_train)

# 进行预测
y_predict = mlb.predict(x_test)

print("预测每篇文章的类别:", y_predict[:100])
print("真实类别为:", y_test[:100])

print("预测准确率为:", mlb.score(x_test, y_test))

注意:

数据预处理中的方法:

  • Fit(): Method calculates the parameters μ and σ and saves them as internal objects.

解释:简单来说,就是求得训练集X的均值啊,方差啊,最大值啊,最小值啊这些训练集X固有的属性。可以理解为一个训练过程, 返回了一个中间结果, 但是数据集不变.

  • Transform(): Method using these calculated parameters apply the transformation to a particular dataset.

解释:在Fit的基础上,进行标准化,降维,归一化等操作(看具体用的是哪个工具,如PCA,StandardScaler等)。

  • Fit_transform(): joins the fit() and transform() method for transformation of dataset.

解释:fit_transform是fit和transform的组合,既包括了训练又包含了转换。


transform()和fit_transform()二者的功能都是对数据进行某种统一处理(比如标准化~N(0,1),将数据缩放(映射)到某个固定区间,归一化,正则化等)

fit_transform(trainData)对部分数据先拟合fit,找到该part的整体指标,如均值、方差、最大值最小值等等(根据具体转换的目的),然后对该trainData进行转换transform,从而实现数据的标准化、归一化等等。

根据对之前部分trainData进行fit的整体指标,对剩余的数据(testData)使用同样的均值、方差、最大最小值等指标进行转换transform(testData),从而保证train、test处理方式相同。所以,一般都是这么用:

from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
sc.fit_tranform(X_train)
sc.tranform(X_test)

Note:

  • 必须先用fit_transform(trainData),之后再transform(testData)
  • 如果直接transform(testData),程序会报错
  • 如果fit_transfrom(trainData)后,使用fit_transform(testData)而不ransform(testData),虽然也能归一化,但是两个结果不是在同一个“标准”下的,具有明显差异。(一定要避免这种情况, 这也是为什么开头说不能对x_test使用fit_transform的原因)

输出:

预测每篇文章的类别: [15 13  7 15  8  0  9  7  3 18  7  5 12  2 13  1 18  1 16 12 13  5  4  9
  5 17 14  3 12 13 16  7 15  7 17 14  3 12  5 13 11 12 15 17  6  3  6 18
  6 18 11 15  2 16 13  7  8 14 11  2 17  8 15  2  3 14  3  4  5 15 14 15
 11  5  9 15  7  6 18 13  6 11 16 16 12  1  0 10 11  3 15 17 13  7  8  8
 13 15  2 11]
真实类别为: [15 13  7 15  8  0  9  7  3 18  7  5 12  2 13  1 18  5 11 12 13  5  4  9
  5 17 14  3 12 13 16  7 15  7 17 14  3 12  5 13 11 12 15 17  6  3  6 18
  6 18  6 15  2 16 13  7  8 14 11  2 17  8 15  2  6 12  3  4  5 19 14 15
 11  5  9 15  7  6 18 13  6 11 16 16 12  1  0 10 11  3 15 17 13  7  8  8
 13 19  2 11]
预测准确率为: 0.8514326140785284

总结

  • 优点:
    • 朴素贝叶斯模型发源于古典数学理论,有稳定的分类效率。
    • 对缺失数据不太敏感,算法也比较简单,常用于文本分类。
    • 分类准确度高,速度快
  • 缺点:
    • 由于使用了样本属性独立性的假设,所以如果特征属性有关联时其效果不好

决策树

  • 学习目标
    • 说明信息熵的公式以及作用
    • 说明信息增益的公式作用
    • 应用信息增益实现计算特征的不确定性减少程度
    • 了解决策树的三种算法实现
  • 具体应用
    • 泰坦尼克号乘客生存预测

认识决策树

决策树思想的来源非常朴素,程序设计中的条件分支结构就是if-then结构,最早的决策树就是利用这类结构分割数据的一种分类学习方法

怎么理解这句话?通过一个对话例子

想一想这个女生为什么把年龄放在最上面判断!!!!!!!!!

决策树分类原理详解

为了更好理解决策树具体怎么分类的,我们通过一个问题例子?

问题:如何对这些客户进行分类预测?你是如何去划分?

有可能你的划分是这样的

那么我们怎么知道这些特征哪个更好放在最上面,我们可以把最重要的判断放在前面:

为什么要这样做呢?

原理

需要用到信息论的知识, 下面通过例子引入信息熵:

熵(英语: entropy ):表示随机变量的不确定性, 单位比特(bit)。

条件熵:在一个条件下,随机变量的不确定性。

信息增益: 信息增益 = 熵 - 条件熵。表示在一个条件下,信息不确定性减少的程度。

通俗地讲,X(明天下雨)是一个随机变量,X的熵可以算出来, Y(明天阴天)也是随机变量,在阴天情况下下雨的信息熵我们如果也知道的话(此处需要知道其联合概率分布或是通过数据估计)即是条件熵。

X的熵减去Y条件下X的熵,就是信息增益。具体解释:原本明天下雨的信息熵是2,条件熵是0.01(因为如果知道明天是阴天,那么下雨的概率很大,信息量少),这样相减后为1.99。在获得阴天这个信息后,下雨信息不确定性减少了1.99,不确定减少了很多,所以信息增益大。也就是说,阴天这个信息对明天下午这一推断来说非常重要。

所以在特征选择的时候常常用信息增益,如果信息增益大的话那么这个特征对于分类来说很关键,下面我们要讲到的决策树就是这样来找特征的。

信息熵举例

那来玩个猜测游戏,猜猜这32支球队那个是冠军。并且猜测错误付出代价。每猜错一次给一块钱,告诉我是否猜对了,那么我需要掏多少钱才能知道谁是冠军? (前提是:不知道任意球队的信息、历史比赛记录、实力等)

为了使代价最小,可以使用二分法猜测:

我可以把球编上号,从1到32,然后提问:冠 军在1-16号吗?依次询问,只需要五次,就可以知道结果。

我们来看这个式子:

  • 32支球队,log32=5比特 (log的底为2, 最多5次可以猜中)
  • 64支球队,log64=6比特 (最多6次可以猜中, 数据增加一倍只需要多猜一次)
信息熵的定义

信息熵公式:

H的专业术语称之为信息熵,单位为比特。信息熵H越大证明信息的不确定性越大.

在预测获胜球队的例子中,p为每个球队获胜的概率, 假设概率相等,都为1/32, 那么信息熵的计算

假设每个球队获胜概率相同,即 p1=p2=...=p32=1/32
H = -(p1logp1 + p2logp2 + ... + p32logp32) 
  = -1 * -1*log32
  = log32
  = 5

当这32支球队夺冠的几率相同时,对应的信息熵等于5比特

只要概率发生任意变化,信息熵都比5比特小, 举个极端的例子, 比如确定其中的一个球队100%能够获胜, 则信息熵为0, 即没有任何不确定性.

总结(重要)

信息和消除不确定性是相联系的

当我们得到的额外信息(球队历史比赛情况等等)越多的话,那么我们猜测的代价越小(猜测的不确定性减小)

问题: 回到我们前面的贷款案例,怎么去划分?可以利用当得知某个特征(比如是否有房子)之后,我们能够减少的不确定性大小。越大我们可以认为这个特征很重要。那怎么去衡量减少的不确定性大小呢?

决策树的划分依据之一 - 信息增益

定义与公式

特征A对训练数据集D的信息增益g(D,A): 定义为集合D的信息熵H(D)与特征A给定条件下D的信息条件熵H(D|A)之差,即公式为:

公式的详细解释:

注:信息增益表示得知特征X的信息, 随着不确定性减少的程度使得特征Y的信息熵减少的程度

贷款特征重要计算

我们以年龄特征来计算:

  • g(D, 年龄) = H(D) -H(D|年龄) = 0.971-[5/15H(青年)+5/15H(中年)+5/15H(老年]
  • H(D) = -(6/15log(6/15)+9/15log(9/15))=0.971
  • H(青年) = -(3/5log(3/5) +2/5log(2/5))
    H(中年)=-(3/5log(3/5) +2/5log(2/5))
    H(老年)=-(4/5log(4/5)+1/5log(1/5))

我们以A1、A2、A3、A4代表年龄、有工作、有自己的房子和贷款情况。最终计算的结果

  • g(D, A1) = 0.313
  • g(D, A2) = 0.324
  • g(D, A3) = 0.420
  • g(D, A4) = 0.363

所以我们选择A3 作为划分的第一个特征。这样我们就可以慢慢建立一棵树

决策树的三种算法实现

当然决策树的原理不止信息增益这一种,还有其他方法。但是原理都类似,我们就不去举例计算。

  • ID3
    • 信息增益最大的准则
  • C4.5
    • 信息增益比最大的准则
  • CART
    • 分类树: 基尼(gini)系数最小的准则 在sklearn中可以选择划分的默认原则
    • 优势:划分更加细致(从后面例子的树显示来理解)

决策树API

  • class sklearn.tree.DecisionTreeClassifier(criterion=’gini’, max_depth=None,random_state=None)
    • 决策树分类器
    • criterion:默认是’gini’系数,也可以选择信息增益的熵’entropy’
    • max_depth:树的深度大小
    • random_state:随机数种子
  • 其中会有些超参数:max_depth:树的深度大小
    • 其它超参数我们会结合随机森林讲解

案例:泰坦尼克号乘客生存预测

  • 泰坦尼克号数据

在泰坦尼克号和titanic2数据帧描述泰坦尼克号上的个别乘客的生存状态。这里使用的数据集是由各种研究人员开始的。其中包括许多研究人员创建的旅客名单,由Michael A. Findlay编辑。我们提取的数据集中的特征是票的类别,存活,乘坐班,年龄,登陆,home.dest,房间,票,船和性别。

1、乘坐班是指乘客班(1,2,3),是社会经济阶层的代表。

2、其中age数据存在缺失。

数据:

泰坦尼克号幸存者数据集(Titanic Dataset)-飞桨AI Studio - 人工智能学习实训社区 (baidu.com)

分析

  • 选择我们认为重要的几个特征 ['pclass', 'age', 'sex']
  • 填充缺失值
  • 特征中出现类别符号,需要进行one-hot编码处理(DictVectorizer)
    • x.to_dict(orient="records") 需要将数组特征转换成字典数据
  • 数据集划分
  • 决策树分类预测

代码

# Imports
import pandas as pd
from sklearn.tree import DecisionTreeClassifier
from sklearn.feature_extraction import DictVectorizer
from sklearn.model_selection import train_test_split


%matplotlib inline

# 1、获取数据
titan = pd.read_csv('../data/data54777/train.csv')

# 2、数据的处理
X = titan[['Pclass', 'Age', 'Sex']]
y = titan['Survived']

# print(x , y)
# 缺失值需要处理,将特征当中有类别的这些特征进行字典特征抽取
# inplace 替换原始数据
X['Age'].fillna(X['Age'].mean(), inplace=True)

# 对于x转换成字典数据x.to_dict(orient="records")
dict = DictVectorizer(sparse=False)

X = dict.fit_transform(X.to_dict(orient="records"))

# ['Age', 'Pclass', 'Sex=female', 'Sex=male']
print(dict.get_feature_names())
print(X)

['Age', 'Pclass', 'Sex=female', 'Sex=male']
[[22.          3.          0.          1.        ]
 [38.          1.          1.          0.        ]
 [26.          3.          1.          0.        ]
 ...
 [29.69911765  3.          1.          0.        ]
 [26.          1.          0.          1.        ]
 [32.          3.          0.          1.        ]]
# 分割训练集合测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

# 进行决策树的建立和预测
dc = DecisionTreeClassifier(max_depth=5)

dc.fit(X_train, y_train)

print("预测的准确率为:", dc.score(X_test, y_test))
预测的准确率为: 0.7947761194029851
运行时长:7毫秒结束时间:2021-12-30 22:26:20

由于决策树类似一个树的结构,我们可以保存到本地显示

保存树的结构到dot文件

  • 1、sklearn.tree.export_graphviz() 该函数能够导出DOT格式
    • tree.export_graphviz(estimator,out_file='tree.dot’,feature_names=[‘’,’’])
  • 2、工具:(能够将dot文件转换为pdf、png)
    • 安装graphviz
    • ubuntu:sudo apt-get install graphviz
    • Mac:brew install graphviz
  • 3、运行命令
    • 然后我们运行这个命令
    • dot -Tpng tree.dot -o tree.png
from sklearn.tree import export_graphviz

export_graphviz(dc, out_file="./tree.dot", feature_names=['Age', 'Pclass', 'Sex=female', 'Sex=male'])

./tree.dot

digraph Tree {
node [shape=box] ;
0 [label="Sex=male <= 0.5\ngini = 0.473\nsamples = 623\nvalue = [384, 239]"] ;
1 [label="Pclass <= 2.5\ngini = 0.379\nsamples = 224\nvalue = [57, 167]"] ;
0 -> 1 [labeldistance=2.5, labelangle=45, headlabel="True"] ;
2 [label="Age <= 2.5\ngini = 0.096\nsamples = 119\nvalue = [6, 113]"] ;
1 -> 2 ;
3 [label="gini = 0.0\nsamples = 1\nvalue = [1, 0]"] ;
2 -> 3 ;
4 [label="Age <= 24.5\ngini = 0.081\nsamples = 118\nvalue = [5, 113]"] ;
2 -> 4 ;
5 [label="gini = 0.0\nsamples = 37\nvalue = [0, 37]"] ;
4 -> 5 ;
6 [label="Age <= 27.5\ngini = 0.116\nsamples = 81\nvalue = [5, 76]"] ;
4 -> 6 ;
7 [label="gini = 0.469\nsamples = 8\nvalue = [3, 5]"] ;
6 -> 7 ;
8 [label="gini = 0.053\nsamples = 73\nvalue = [2, 71]"] ;
6 -> 8 ;
9 [label="Age <= 36.0\ngini = 0.5\nsamples = 105\nvalue = [51, 54]"] ;
1 -> 9 ;
10 [label="Age <= 1.5\ngini = 0.496\nsamples = 97\nvalue = [44, 53]"] ;
9 -> 10 ;
11 [label="gini = 0.0\nsamples = 3\nvalue = [0, 3]"] ;
10 -> 11 ;
12 [label="Age <= 3.5\ngini = 0.498\nsamples = 94\nvalue = [44, 50]"] ;
10 -> 12 ;
13 [label="gini = 0.32\nsamples = 5\nvalue = [4, 1]"] ;
12 -> 13 ;
14 [label="gini = 0.495\nsamples = 89\nvalue = [40, 49]"] ;
12 -> 14 ;
15 [label="Age <= 55.5\ngini = 0.219\nsamples = 8\nvalue = [7, 1]"] ;
9 -> 15 ;
16 [label="gini = 0.0\nsamples = 7\nvalue = [7, 0]"] ;
15 -> 16 ;
17 [label="gini = 0.0\nsamples = 1\nvalue = [0, 1]"] ;
15 -> 17 ;
18 [label="Age <= 6.5\ngini = 0.296\nsamples = 399\nvalue = [327, 72]"] ;
0 -> 18 [labeldistance=2.5, labelangle=-45, headlabel="False"] ;
19 [label="Pclass <= 2.5\ngini = 0.391\nsamples = 15\nvalue = [4, 11]"] ;
18 -> 19 ;
20 [label="gini = 0.0\nsamples = 6\nvalue = [0, 6]"] ;
19 -> 20 ;
21 [label="Age <= 2.5\ngini = 0.494\nsamples = 9\nvalue = [4, 5]"] ;
19 -> 21 ;
22 [label="Age <= 1.5\ngini = 0.48\nsamples = 5\nvalue = [3, 2]"] ;
21 -> 22 ;
23 [label="gini = 0.444\nsamples = 3\nvalue = [1, 2]"] ;
22 -> 23 ;
24 [label="gini = 0.0\nsamples = 2\nvalue = [2, 0]"] ;
22 -> 24 ;
25 [label="Age <= 3.5\ngini = 0.375\nsamples = 4\nvalue = [1, 3]"] ;
21 -> 25 ;
26 [label="gini = 0.0\nsamples = 1\nvalue = [0, 1]"] ;
25 -> 26 ;
27 [label="gini = 0.444\nsamples = 3\nvalue = [1, 2]"] ;
25 -> 27 ;
28 [label="Pclass <= 1.5\ngini = 0.267\nsamples = 384\nvalue = [323, 61]"] ;
18 -> 28 ;
29 [label="Age <= 37.5\ngini = 0.444\nsamples = 81\nvalue = [54, 27]"] ;
28 -> 29 ;
30 [label="Age <= 33.5\ngini = 0.494\nsamples = 45\nvalue = [25, 20]"] ;
29 -> 30 ;
31 [label="gini = 0.467\nsamples = 35\nvalue = [22, 13]"] ;
30 -> 31 ;
32 [label="gini = 0.42\nsamples = 10\nvalue = [3, 7]"] ;
30 -> 32 ;
33 [label="Age <= 47.5\ngini = 0.313\nsamples = 36\nvalue = [29, 7]"] ;
29 -> 33 ;
34 [label="gini = 0.0\nsamples = 14\nvalue = [14, 0]"] ;
33 -> 34 ;
35 [label="gini = 0.434\nsamples = 22\nvalue = [15, 7]"] ;
33 -> 35 ;
36 [label="Age <= 32.25\ngini = 0.199\nsamples = 303\nvalue = [269, 34]"] ;
28 -> 36 ;
37 [label="Age <= 30.5\ngini = 0.24\nsamples = 237\nvalue = [204, 33]"] ;
36 -> 37 ;
38 [label="gini = 0.208\nsamples = 221\nvalue = [195, 26]"] ;
37 -> 38 ;
39 [label="gini = 0.492\nsamples = 16\nvalue = [9, 7]"] ;
37 -> 39 ;
40 [label="Age <= 43.5\ngini = 0.03\nsamples = 66\nvalue = [65, 1]"] ;
36 -> 40 ;
41 [label="gini = 0.0\nsamples = 40\nvalue = [40, 0]"] ;
40 -> 41 ;
42 [label="gini = 0.074\nsamples = 26\nvalue = [25, 1]"] ;
40 -> 42 ;
}

文件里描述了决策树, 但这样看不是很直观, 可以安装 graphviz 工具将dot文件转成图片文件:

sudo apt-get install graphviz
dot -Tpng tree.dot -o tree.png

tree.png

决策树总结

  • 优点:
    • 简单的理解和解释,树木可视化。
  • 缺点:
    • 决策树学习者可以创建不能很好地推广数据的过于复杂的树,这被称为过拟合。
  • 改进:
    • 减枝cart算法(决策树API当中已经实现,随机森林参数调优有相关介绍)
    • 随机森林

注:企业重要决策,由于决策树很好的分析能力,在决策过程应用较多, 可以优先选择区分度大的特征.

集成学习方法之随机森林

  • 学习目标
    • 说名随机森林每棵决策树的建立过程
    • 知道为什么需要随机有放回(Bootstrap)的抽样
    • 说明随机森林的超参数
  • 具体应用
    • 泰坦尼克号乘客生存预测

什么是集成学习方法

集成学习通过建立几个模型组合的来解决单一预测问题。它的工作原理是生成多个分类器/模型,各自独立地学习和作出预测。这些预测最后结合成组合预测,因此优于任何一个单分类的做出预测。

什么是随机森林

在机器学习中,随机森林是一个包含多个决策树的分类器,并且其输出的类别是由个别树输出的类别的众数而定。

例如, 如果你训练了5个树, 其中有4个树的结果是True, 1个数的结果是False, 那么最终投票结果就是True

随机森林原理过程

学习算法根据下列算法而建造每棵树:

  • 用N来表示训练用例(样本)的个数,M表示特征数目。
    • 一次随机选出一个样本,重复N次, (有可能出现重复的样本)
    • 随机去选出m个特征, m <<M,建立决策树
  • 采取bootstrap抽样

为什么采用BootStrap抽样

  • 为什么要随机抽样训练集?  
    • 如果不进行随机抽样,每棵树的训练集都一样,那么最终训练出的树分类结果也是完全一样的
  • 为什么要有放回地抽样?
    • 如果不是有放回的抽样,那么每棵树的训练样本都是不同的,都是没有交集的,这样每棵树都是“有偏的”,都是绝对“片面的”(当然这样说可能不对),也就是说每棵树训练出来都是有很大的差异的;而随机森林最后分类取决于多棵树(弱分类器)的投票表决。

API

  • class sklearn.ensemble.RandomForestClassifier(n_estimators=10, criterion=’gini’, max_depth=None, bootstrap=True, random_state=None, min_samples_split=2)
    • 随机森林分类器
    • n_estimators:integer,optional(default = 10)森林里的树木数量120,200,300,500,800,1200
    • criteria:string,可选(default =“gini”)分割特征的测量方法
    • max_depth:integer或None,可选(默认=无)树的最大深度 5,8,15,25,30
    • max_features="auto”,每个决策树的最大特征数量
      • If "auto", then max_features=sqrt(n_features).
      • If "sqrt", then max_features=sqrt(n_features) (same as "auto").
      • If "log2", then max_features=log2(n_features).
      • If None, then max_features=n_features.
    • bootstrap:boolean,optional(default = True)是否在构建树时使用放回抽样
    • min_samples_split:节点划分最少样本数
    • min_samples_leaf:叶子节点的最小样本数
  • 超参数:n_estimator, max_depth, min_samples_split,min_samples_leaf

代码

import pandas as pd
from sklearn.feature_extraction import DictVectorizer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import train_test_split

# 1、获取数据
titan = pd.read_csv('../data/data54777/train.csv')

# 2、数据的处理
X = titan[['Pclass', 'Age', 'Sex']]
y = titan['Survived']

# print(x , y)
# 缺失值需要处理,将特征当中有类别的这些特征进行字典特征抽取
# inplace 替换原始数据
X['Age'].fillna(X['Age'].mean(), inplace=True)

# 对于x转换成字典数据x.to_dict(orient="records")
dict = DictVectorizer(sparse=False)

X = dict.fit_transform(X.to_dict(orient="records"))

# 分割训练集合测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

# 随机森林去进行预测
rf = RandomForestClassifier()

param = {"n_estimators": [120,200,300,500,800,1200], "max_depth": [5, 8, 15, 25, 30]}

# 超参数调优
gc = GridSearchCV(rf, param_grid=param, cv=2)

gc.fit(X_train, y_train)

print("随机森林预测的准确率为:", gc.score(X_test, y_test))
随机森林预测的准确率为: 0.7574626865671642

总结

  • 在当前所有算法中,具有极好的准确率
  • 能够有效地运行在大数据集上,处理具有高维特征的输入样本,而且不需要降维
  • 能够评估各个特征在分类问题上的重要性

回顾:

1、估计器的工作流程是什么?

答案:

第一步: 实例化估计器

第二步: 调用估计器的fit函数, 用训练集的特征值和目标值训练

第三步: 调用预测函数predict, 用测试集的特征值预测

2、决策树的划分依据是什么?(课程介绍的主要方法)

答案: 根据更具信息增益的属性来划分.

3、编程: 通过K近邻算法对鸢尾花数据集进行分类预测

Views: 182

Index