精华内容
下载资源
问答
  • cmake 常用变量、常用环境变量、常用语法总结

    万次阅读 多人点赞 2016-02-04 14:27:11
    一,cmake 变量引用的方式: 前面我们已经提到了,使用${}进行变量的引用。在 IF 等语句中,是直接使用变量名而不通过${}取值 二,cmake 自定义变量的方式: 主要有隐式定义和显式定义两种,前面举了一个隐式定义的例子,...

    一,cmake 变量引用的方式:

    前面我们已经提到了,使用${}进行变量的引用。在 IF 等语句中,是直接使用变量名而不通过${}取值

    二,cmake 自定义变量的方式:

    主要有隐式定义和显式定义两种。

    隐式定义的例子: PROJECT 指令,会隐式的定义<projectname>_BINARY_DIR 和<projectname>_SOURCE_DIR 两个变量。

    显式定义的例子:使用 SET 指令,就可以构建一个自定义变量了。
    比如:
    SET(HELLO_SRC main.SOURCE_PATHc),就 PROJECT_BINARY_DIR 可以通过${HELLO_SRC}来引用这个自定义变量了.


    三,cmake 常用变量:

    1,CMAKE_BINARY_DIR

      PROJECT_BINARY_DIR
     <projectname>_BINARY_DIR
    这三个变量指代的内容是一致的,如果是 in source 编译,指的就是工程顶层目录,如果是 out-of-source 编译,指的是工程编译发生的目录。PROJECT_BINARY_DIR 跟其他指令稍有区别,现在,你可以理解为他们是一致的。

    2,CMAKE_SOURCE_DIR

       PROJECT_SOURCE_DIR
       <projectname>_SOURCE_DIR
    这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。
    也就是在 in source 编译时,他跟 CMAKE_BINARY_DIR 等变量一致。
    PROJECT_SOURCE_DIR 跟其他指令稍有区别,现在,你可以理解为他们是一致的。

    3,CMAKE_CURRENT_SOURCE_DIR

    指的是当前处理的 CMakeLists.txt 所在的路径,比如上面我们提到的 src 子目录。

    4,CMAKE_CURRRENT_BINARY_DIR

    如果是 in-source 编译,它跟 CMAKE_CURRENT_SOURCE_DIR 一致,如果是 out-of-source 编译,他指的是 target 编译目录。
    使用我们上面提到的 ADD_SUBDIRECTORY(src bin)可以更改这个变量的值。
    使用 SET(EXECUTABLE_OUTPUT_PATH <新路径>)并不会对这个变量造成影响,它仅仅修改了最终目标文件存放的路径。

    5,CMAKE_CURRENT_LIST_FILE

    输出调用这个变量的 CMakeLists.txt 的完整路径

     

    6,CMAKE_CURRENT_LIST_LINE

    输出这个变量所在的行

     

    7,CMAKE_MODULE_PATH

    这个变量用来定义自己的 cmake 模块所在的路径。如果你的工程比较复杂,有可能会自己编写一些 cmake 模块,这些 cmake 模块是随你的工程发布的,为了让 cmake 在处理CMakeLists.txt 时找到这些模块,你需要通过 SET 指令,将自己的 cmake 模块路径设置一下。
    比如
    SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
    这时候你就可以通过 INCLUDE 指令来调用自己的模块了。

    8,EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH

    分别用来重新定义最终结果的存放目录,前面我们已经提到了这两个变量。

    9,PROJECT_NAME

    返回通过 PROJECT 指令定义的项目名称。

    CMAKE_BUILD_TYPE

    生成 debug 版和 release 版的程序。

    可以的取值是 Debug Release RelWithDebInfo 和 MinSizeRel。当这个变量值为 Debug 的时候,CMake 会使用变量CMAKE_CXX_FLAGS_DEBUG 和 CMAKE_C_FLAGS_DEBUG 中的字符串作为编译选项生成 Makefile,当这个变量值为 Release 的时候,工程会使用变量CMAKE_CXX_FLAGS_RELEASE 和 CMAKE_C_FLAGS_RELEASE 选项生成 Makefile。

    现假设项目中只有一个文件 main.cpp ,下面是一个可以选择生成 debug 版和 release 版的程序的 CMakeList.txt :

    1 PROJECT(main)
    2 CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
    3 SET(CMAKE_SOURCE_DIR .)
    4
    5 SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
    6 SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
    7
    8 AUX_SOURCE_DIRECTORY(. DIR_SRCS)
    9 ADD_EXECUTABLE(main ${DIR_SRCS})

    第 5 和 6 行设置了两个变量 CMAKE_CXX_FLAGS_DEBUG 和 

    CMAKE_CXX_FLAGS_RELEASE, 这两个变量是分别用于 debug 和 release 的编译选项。 

    编辑 CMakeList.txt 后需要执行 ccmake 命令生成 Makefile 。在进入项目的根目录,输入

     "ccmake ." 进入一个图形化界面,如下图所示:

    图 5. ccmake 的界面


    按照界面中的提示进行操作,按 "c" 进行 configure ,这时界面中显示出了配置变量 

    CMAKE_BUILD_TYPE 的条目。如下图所示:

    图 6. 执行了 configure 以后 ccmake 的界面


    下面我们首先生成 Debug 版的 Makefile :将变量 CMAKE_BUILD_TYPE 设置为 

    Debug ,按 "c" 进行 configure ,按 "g" 生成 Makefile 并退出。这时执行命令

     find * | xargs grep "O0" 后结果如下:

    清单 8 find * | xargs grep "O0"的执行结果
    CMakeFiles/main.dir/flags.make:CXX_FLAGS = -O0 -Wall -g -ggdb 
    CMakeFiles/main.dir/link.txt:/usr/bin/c++ -O0 -Wall -g -ggdb 
    CMakeFiles/main.dir/main.cpp.o -o main -rdynamic 
    CMakeLists.txt:SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")

    这个结果说明生成的 Makefile 中使用了变量 CMAKE_CXX_FLAGS_DEBUG 作为编译时的

    参数。

    下面我们将生成 Release 版的 Makefile :再次执行命令 "ccmake ." 将变量

    CMAKE_BUILD_TYPE 设置为 Release ,生成 Makefile 并退出。执行命令

    find * | xargs grep "O0" 后结果如下:

    清单 9 find * | xargs grep "O0"的执行结果
    CMakeLists.txt:SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")

    而执行命令 find * | xargs grep "O3" 后结果如下:

    清单 10. find * | xargs grep "O3"的执行结果
    CMakeCache.txt:CMAKE_CXX_FLAGS_RELEASE:STRING=-O3 -DNDEBUG
    CMakeCache.txt:CMAKE_C_FLAGS_RELEASE:STRING=-O3 -DNDEBUG
    CMakeFiles/main.dir/flags.make:CXX_FLAGS = -O3 -Wall 
    CMakeFiles/main.dir/link.txt:/usr/bin/c++ -O3 -Wall 
    CMakeFiles/main.dir/main.cpp.o -o main -rdynamic 
    CMakeLists.txt:SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")

    这两个结果说明生成的 Makefile 中使用了变量 CMAKE_CXX_FLAGS_RELEASE 作为编译

    时的参数。

    CMAKE_C_COMPILER

    指定C编译器,通常,CMake运行时能够自动检测C语言编译器。进行嵌入式系统开发时,通常需要设置此变量,指定交叉编译器。

    CMAKE_CXX_COMPILER

    指定C++编译器

    CMAKE_C_FLAGS

    指定编译C文件时编译选项,比如-g指定产生调试信息。也可以通过add_definitions命令添加编译选项。

    EXECUTABLE_OUTPUT_PATH

    指定可执行文件存放的路径。

    LIBRARY_OUTPUT_PATH

    指定库文件放置的路径



    四,cmake 调用环境变量的方式

    使用$ENV{NAME}指令就可以调用系统的环境变量了。
    比如:
    MESSAGE(STATUS “HOME dir: $ENV{HOME}”)
    设置环境变量的方式是:
    SET(ENV{变量名} 值)
    1,CMAKE_INCLUDE_CURRENT_DIR
    自动添加 CMAKE_CURRENT_BINARY_DIR 和 CMAKE_CURRENT_SOURCE_DIR 到当前处理
    的 CMakeLists.txt。相当于在每个 CMakeLists.txt 加入:
    INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}
    ${CMAKE_CURRENT_SOURCE_DIR})

    2,CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE
    将工程提供的头文件目录始终至于系统头文件目录的前面,当你定义的头文件确实跟系统发生冲突时可以提供一些帮助。

    3,CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH 我们在上一节已经提及。

    五,系统信息

    1,CMAKE_MAJOR_VERSION,CMAKE 主版本号,比如 2.4.6 中的 2

    2,CMAKE_MINOR_VERSION,CMAKE 次版本号,比如 2.4.6 中的 4
    3,CMAKE_PATCH_VERSION,CMAKE 补丁等级,比如 2.4.6 中的 6
    4,CMAKE_SYSTEM,系统名称,比如 Linux-2.6.22
    5,CMAKE_SYSTEM_NAME,不包含版本的系统名,比如 Linux
    6,CMAKE_SYSTEM_VERSION,系统版本,比如 2.6.22
    7,CMAKE_SYSTEM_PROCESSOR,处理器名称,比如 i686.
    8,UNIX,在所有的类 UNIX 平台为 TRUE,包括 OS X 和 cygwin

    9,WIN32,在所有的 win32 平台为 TRUE,包括 cygwin

    六,主要的开关选项:

    1,CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS

    用来控制 IF ELSE 语句的书写方式,在下一节语法部分会讲到。

    2,BUILD_SHARED_LIBS
    这个开关用来控制默认的库编译方式,如果不进行设置,使用 ADD_LIBRARY 并没有指定库
    类型的情况下,默认编译生成的库都是静态库。
    如果 SET(BUILD_SHARED_LIBS ON)后,默认生成的为动态库。
    3,CMAKE_C_FLAGS
    设置 C 编译选项,也可以通过指令 ADD_DEFINITIONS()添加。
    4,CMAKE_CXX_FLAGS
    设置 C++编译选项,也可以通过指令 ADD_DEFINITIONS()添加。
    小结:
    本章介绍了一些较常用的 cmake 变量,这些变量仅仅是所有 cmake 变量的很少一部分,
    前 cmake 的英文文档也是比较缺乏的,如果需要了解更多的 cmake 变量,更好的方式是阅

    读一些成功项目的 cmake 工程文件,比如 KDE4 的代码。

    八,cmake 常用指令

    前面我们讲到了 cmake 常用的变量,相信“cmake 即编程”的感觉会越来越明显,无论如何,我们仍然可以看到 cmake 比 autotools 要简单很多。接下来我们就要集中的看一看cmake 所提供的常用指令。在前面的章节我们已经讨论了很多指令的用法,如:
    PROJECT, ADD_EXECUTABLE, INSTALL, ADD_SUBDIRECTORY, SUBDIRS,  INCLUDE_DIRECTORIES, LINK_DIRECTORIES, TARGET_LINK_LIBRARIES, SET 等。
    本节会引入更多的 cmake 指令,为了编写的方便,我们将按照 cmake man page 的顺序来介绍各种指令,不再推荐使用的指令将不再介绍,INSTALL 系列指令在安装部分已经做了非常详细的说明,本节也不在提及。(你可以将本章理解成选择性翻译,但是会加入更多的个人理解)

    一,基本指令

    1,ADD_DEFINITIONS
    向 C/C++编译器添加-D 定义,比如:
    ADD_DEFINITIONS(-DENABLE_DEBUG -DABC),参数之间用空格分割。

    如果你的代码中定义了#ifdef ENABLE_DEBUG #endif,这个代码块就会生效。
    如果要添加其他的编译器开关,可以通过 CMAKE_C_FLAGS 变量和 CMAKE_CXX_FLAGS 变量设置。


    2,ADD_DEPENDENCIES
    定义 target 依赖的其他 target,确保在编译本 target 之前,其他的 target 已经被构建。
    ADD_DEPENDENCIES(target-name depend-target1
                     depend-target2 ...)

    让一个顶层目标依赖于其他的顶层目标。一个顶层目标是由命令ADD_EXECUTABLE,ADD_LIBRARY,或者ADD_CUSTOM_TARGET产生的目标。为这些命令的输出引入依赖性可以保证某个目标在其他的目标之前被构建。查看ADD_CUSTOM_TARGET和ADD_CUSTOM_COMMAND命令的DEPENDS选项,可以了解如何根据自定义规则引入文件级的依赖性。查看SET_SOURCE_FILES_PROPERTIES命令的OBJECT_DEPENDS选项,可以了解如何为目标文件引入文件级的依赖性。


    3,ADD_EXECUTABLE、ADD_LIBRARY、ADD_SUBDIRECTORY

    ADD_EXECUTABLE(可执行文件名  生成该可执行文件的源文件)

    说明源文件需要编译出的可执行文件名

    例:

     ADD_EXECUTABLE(hello ${SRC_LIST})

    说明SRC_LIST变量中的源文件需要编译出名为hello可执行文件


    ADD_LIBRARY(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2 ... sourceN)

    生成动态静态库

    例:

    ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})


    ADD_SUBDIRECTORY(src_dir [binary_dir] [EXCLUDE_FROM_ALL])
    向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制的存放位置
    EXCLUDE_FROM_ALL含义:将这个目录从编译过程中排除



    4,ADD_TEST 与 ENABLE_TESTING 指令。
    ENABLE_TESTING 指令用来控制 Makefile 是否构建 test 目标,涉及工程所有目录。语法很简单,没有任何参数,ENABLE_TESTING(),一般情况这个指令放在工程的主CMakeLists.txt 中.


    ADD_TEST 指令的语法是:
    ADD_TEST(testname Exename arg1 arg2 ...)
    testname 是自定义的 test 名称,Exename 可以是构建的目标文件也可以是外部脚本等等。后面连接传递给可执行文件的参数。如果没有在同一个 CMakeLists.txt 中打开ENABLE_TESTING()指令,任何 ADD_TEST 都是无效的。
    比如我们前面的 Helloworld 例子,可以在工程主 CMakeLists.txt 中添加
    ADD_TEST(mytest ${PROJECT_BINARY_DIR}/bin/main)
    ENABLE_TESTING()
    生成 Makefile 后,就可以运行 make test 来执行测试了。


    5,AUX_SOURCE_DIRECTORY
    基本语法是:
    AUX_SOURCE_DIRECTORY(dir VARIABLE)
    作用是发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表。因为目前 cmake 还不能自动发现新添加的源文件。
    比如
    AUX_SOURCE_DIRECTORY(. SRC_LIST)
    ADD_EXECUTABLE(main ${SRC_LIST})
    你也可以通过后面提到的 FOREACH 指令来处理这个 LIST


    6,CMAKE_MINIMUM_REQUIRED
    其语法为 CMAKE_MINIMUM_REQUIRED(VERSION versionNumber [FATAL_ERROR])
    比如 CMAKE_MINIMUM_REQUIRED(VERSION 2.5 FATAL_ERROR)
    如果 cmake 版本小与 2.5,则出现严重错误,整个过程中止。


    7,EXEC_PROGRAM
    在 CMakeLists.txt 处理过程中执行命令,并不会在生成的 Makefile 中执行。

    具体语法为:
    EXEC_PROGRAM(Executable [directory in which to run]
                    [ARGS <arguments to executable>]
                    [OUTPUT_VARIABLE <var>]
                    [RETURN_VALUE <var>])
    用于在指定的目录运行某个程序,通过 ARGS 添加参数,如果要获取输出和返回值,可通过OUTPUT_VARIABLE 和 RETURN_VALUE 分别定义两个变量.


    这个指令可以帮助你在 CMakeLists.txt 处理过程中支持任何命令,比如根据系统情况去修改代码文件等等。
    举个简单的例子,我们要在 src 目录执行 ls 命令,并把结果和返回值存下来。
    可以直接在 src/CMakeLists.txt 中添加:
    EXEC_PROGRAM(ls ARGS "*.c" OUTPUT_VARIABLE LS_OUTPUT RETURN_VALUE
    LS_RVALUE)
    IF(not LS_RVALUE)
    MESSAGE(STATUS "ls result: " ${LS_OUTPUT})
    ENDIF(not LS_RVALUE)


    在 cmake 生成 Makefile 的过程中,就会执行 ls 命令,如果返回 0,则说明成功执行,那么就输出 ls *.c 的结果。关于 IF 语句,后面的控制指令会提到。


    8,FILE 指令

    文件操作指令,基本语法为:
    file(WRITE filename "message to write"... )
      file(APPEND filename "message to write"... )
      file(READ filename variable [LIMIT numBytes] [OFFSET offset] [HEX])
      file(STRINGS filename variable [LIMIT_COUNT num]
           [LIMIT_INPUT numBytes] [LIMIT_OUTPUT numBytes]
           [LENGTH_MINIMUM numBytes] [LENGTH_MAXIMUM numBytes]
           [NEWLINE_CONSUME] [REGEX regex]
           [NO_HEX_CONVERSION])
      file(GLOB variable [RELATIVE path] [globbing expressions]...)
      file(GLOB_RECURSE variable [RELATIVE path] 
           [FOLLOW_SYMLINKS] [globbing expressions]...)
      file(RENAME <oldname> <newname>)
      file(REMOVE [file1 ...])
      file(REMOVE_RECURSE [file1 ...])
      file(MAKE_DIRECTORY [directory1 directory2 ...])
      file(RELATIVE_PATH variable directory file)
      file(TO_CMAKE_PATH path result)
      file(TO_NATIVE_PATH path result)
      file(DOWNLOAD url file [TIMEOUT timeout] [STATUS status] [LOG log]
           [EXPECTED_MD5 sum] [SHOW_PROGRESS])

      WRITE选项将会写一条消息到名为filename的文件中。如果文件已经存在,该命令会覆盖已有的文件;如果文件不存在,它将创建该文件。

      APPEND选项和WRITE选项一样,将会写一条消息到名为filename的文件中,只是该消息会附加到文件末尾。

      READ选项将会读一个文件中的内容并将其存储在变量里。读文件的位置从offset开始,最多读numBytes个字节。如果指定了HEX参数,二进制代码将会转换为十六进制表达方式,并存储在变量里。

      STRINGS将会从一个文件中将一个ASCII字符串的list解析出来,然后存储在variable变量中。文件中的二进制数据会被忽略。回车换行符会被忽略。它也可以用在Intel的Hex和Motorola的S-记录文件;读取它们时,它们会被自动转换为二进制格式。可以使用NO_HEX_CONVERSION选项禁止这项功能。LIMIT_COUNT选项设定了返回的字符串的最大数量。LIMIT_INPUT设置了从输入文件中读取的最大字节数。LIMIT_OUTPUT设置了在输出变量中存储的最大字节数。LENGTH_MINIMUM设置了要返回的字符串的最小长度;小于该长度的字符串会被忽略。LENGTH_MAXIMUM设置了返回字符串的最大长度;更长的字符串会被分割成不长于最大长度的字符串。NEWLINE_CONSUME选项允许新行被包含到字符串中,而不是终止它们。REGEX选项指定了一个待返回的字符串必须满足的正则表达式。典型的使用方式是:

      file(STRINGS myfile.txt myfile)

    该命令在变量myfile中存储了一个list,该list中每个项是输入文件中的一行文本。
      GLOB选项将会为所有匹配查询表达式的文件生成一个文件list,并将该list存储进变量variable里。文件名查询表达式与正则表达式类似,只不过更加简单。如果为一个表达式指定了RELATIVE标志,返回的结果将会是相对于给定路径的相对路径。文件名查询表达式的例子有:

       *.cxx      - 匹配所有扩展名为cxx的文件。
       *.vt?      - 匹配所有扩展名是vta,...,vtz的文件。
       f[3-5].txt - 匹配文件f3.txt, f4.txt, f5.txt。

      GLOB_RECURSE选项将会生成一个类似于通常的GLOB选项的list,只是它会寻访所有那些匹配目录的子路径并同时匹配查询表达式的文件。作为符号链接的子路径只有在给定FOLLOW_SYMLINKS选项或者cmake策略CMP0009被设置为NEW时,才会被寻访到。参见cmake --help-policy CMP0009 查询跟多有用的信息。

    使用递归查询的例子有:

     /dir/*.py  - 匹配所有在/dir及其子目录下的python文件。

      MAKE_DIRECTORY选项将会创建指定的目录,如果它们的父目录不存在时,同样也会创建。(类似于mkdir命令——译注)

      RENAME选项对同一个文件系统下的一个文件或目录重命名。(类似于mv命令——译注)

      REMOVE选项将会删除指定的文件,包括在子路径下的文件。(类似于rm命令——译注)

      REMOVE_RECURSE选项会删除给定的文件以及目录,包括非空目录。(类似于rm -r 命令——译注)

      RELATIVE_PATH选项会确定从direcroty参数到指定文件的相对路径。

      TO_CMAKE_PATH选项会把path转换为一个以unix的 / 开头的cmake风格的路径。输入可以是一个单一的路径,也可以是一个系统路径,比如"$ENV{PATH}"。注意,在调用TO_CMAKE_PATH的ENV周围的双引号只能有一个参数(Note the double quotes around the ENV call TO_CMAKE_PATH only takes one argument. 原文如此。quotes和后面的takes让人后纠结,这句话翻译可能有误。欢迎指正——译注)。

      TO_NATIVE_PATH选项与TO_CMAKE_PATH选项很相似,但是它会把cmake风格的路径转换为本地路径风格:windows下用\,而unix下用/。

      DOWNLOAD 将给定的URL下载到指定的文件中。如果指定了LOG var选项,下载日志将会被输出到var中。如果指定了STATUS var选项,下载操作的状态会被输出到var中。该状态返回值是一个长度为2的list。list的第一个元素是操作的数字返回值,第二个返回值是错误的字符串值。错误信息如果是数字0,操作中没有发生错误。如果指定了TIMEOUT time选项,在time秒之后,操作会超时退出;time应该是整数。如果指定了EXPECTED_MD5 sum选项,下载操作会认证下载的文件的实际MD5和是否与期望值匹配。如果不匹配,操作将返回一个错误。如果指定了SHOW_PROGRESS选项,进度信息会以状态信息的形式被打印出来,直到操作完成。

      file命令还提供了COPY和INSTALL两种格式:

      file(<COPY|INSTALL> files... DESTINATION <dir>
           [FILE_PERMISSIONS permissions...]
           [DIRECTORY_PERMISSIONS permissions...]
           [NO_SOURCE_PERMISSIONS] [USE_SOURCE_PERMISSIONS]
           [FILES_MATCHING]
           [[PATTERN <pattern> | REGEX <regex>]
            [EXCLUDE] [PERMISSIONS permissions...]] [...])

      COPY版本把文件、目录以及符号连接拷贝到一个目标文件夹。相对输入路径的评估是基于当前的源代码目录进行的,相对目标路径的评估是基于当前的构建目录进行的。复制过程将保留输入文件的时间戳;并且如果目标路径处存在同名同时间戳的文件,复制命令会把它优化掉。赋值过程将保留输入文件的访问权限,除非显式指定权限或指定NO_SOURCE_PERMISSIONS选项(默认是USE_SOURCE_PERMISSIONS)。参见install(DIRECTORY)命令中关于权限(permissions),PATTERN,REGEX和EXCLUDE选项的文档。

      INSTALL版本与COPY版本只有十分微小的差别:它会打印状态信息,并且默认使用NO_SOURCE_PERMISSIONS选项。install命令生成的安装脚本使用这个版本(它会使用一些没有在文档中涉及的内部使用的选项。)

         


    9,INCLUDE 指令,用来载入 CMakeLists.txt 文件,也用于载入预定义的 cmake 模块.
           INCLUDE(file1 [OPTIONAL])
           INCLUDE(module [OPTIONAL])
    OPTIONAL 参数的作用是文件不存在也不会产生错误。
    你可以指定载入一个文件,如果定义的是一个模块,那么将在 CMAKE_MODULE_PATH 中搜索这个模块并载入。
    载入的内容将在处理到 INCLUDE 语句时直接执行。

    https://cmake.org/cmake/help/v3.0/command/include.html

    二,INSTALL 指令

    INSTALL 系列指令已经在前面的章节有非常详细的说明,这里不在赘述,可参考前面的安装部分。

    三,FIND_系列指令

    FIND_系列指令主要包含一下指令:
    FIND_FILE(<VAR> name1 path1 path2 ...)
    VAR 变量代表找到的文件全路径,包含文件名


    FIND_PATH(<VAR> name1 path1 path2 ...)
    • 指明头文件查找的路径,原型如下
    • 该命令在参数 path* 指示的目录中查找文件 name1 并将查找到的路径保存在变量 VAR 中。
    FIND_LIBRARY(<VAR> name1 path1 path2 ...)

    用法同FIND_PATH,表明库查找路径。



    FIND_PROGRAM(<VAR> name1 path1 path2 ...)

    VAR 变量代表包含这个程序的全路径。


    FIND_PACKAGE( <name> [major.minor] [QUIET] [NO_MODULE] [ [ REQUIRED | COMPONENTS ] [ componets... ] ] )

    用来调用预定义在 CMAKE_MODULE_PATH 下的 Find<name>.cmake 模块,你也可以自己定义 Find<name>模块,通过 SET(CMAKE_MODULE_PATH dir)将其放入工程的某个目录中供工程使用。

    这条命令执行后,CMake 会到变量 CMAKE_MODULE_PATH 指示的目录中查找文件 Findname.cmake 并执行。

    QUIET 参数:

    对应于Find<name>.cmake模块中的 NAME_FIND_QUIETLY,如果不指定这个参数,就会执行:
    MESSAGE(STATUS "Found NAME: ${NAME_LIBRARY}")

    指定了QUIET就不输出指令。

    REQUIRED 参数

    其含义是指是否是工程必须的,如果使用了这个参数,说明是必须的,如果找不到,则工程不能编译。对应于Find<name>.cmake模块中的 NAME_FIND_REQUIRED 变量。

    示例:

    FIND_PACKAGE( libdb_cxx REQUIRED)

    这条命令执行后,CMake 会到变量 CMAKE_MODULE_PATH 指示的目录中查找文件 Findlibdb_cxx.cmake 并执行。


     FIND_PACKAGE 的使用方法和 Find 模块的编写参见下面:编写属于自己的 FindHello 模块。


    FIND_LIBRARY 示例:
    FIND_LIBRARY(libX X11 /usr/lib)
    IF(NOT libX)
    MESSAGE(FATAL_ERROR “libX not found”)
    ENDIF(NOT libX)

    四,控制指令:

    1,IF 指令,基本语法为:

           IF(expression_r_r)
             # THEN section.
             COMMAND1(ARGS ...)
             COMMAND2(ARGS ...)
             ...
           ELSE(expression_r_r)
             # ELSE section.
             COMMAND1(ARGS ...)
             COMMAND2(ARGS ...)
             ...
           ENDIF(expression_r_r)
    另外一个指令是 ELSEIF,总体把握一个原则,凡是出现 IF 的地方一定要有对应的ENDIF.出现 ELSEIF 的地方,ENDIF 是可选的。
    表达式的使用方法如下:
    IF(var),如果变量不是:空,0,N, NO, OFF, FALSE, NOTFOUND 或<var>_NOTFOUND 时,表达式为真。
    IF(NOT var ),与上述条件相反。
    IF(var1 AND var2),当两个变量都为真是为真。
    IF(var1 OR var2),当两个变量其中一个为真时为真。
    IF(COMMAND cmd),当给定的 cmd 确实是命令并可以调用是为真。
    IF(EXISTS dir)或者 IF(EXISTS file),当目录名或者文件名存在时为真。
    IF(file1 IS_NEWER_THAN file2),当 file1 比 file2 新,或者 file1/file2 其中有一个不存在时为真,文件名请使用完整路径。
    IF(IS_DIRECTORY dirname),当 dirname 是目录时,为真。
    IF(variable MATCHES regex)
    IF(string MATCHES regex)
    当给定的变量或者字符串能够匹配正则表达式 regex 时为真。比如:
    IF("hello" MATCHES "ell")
    MESSAGE("true")
    ENDIF("hello" MATCHES "ell")
    IF(variable LESS number)
    IF(string LESS number)
    IF(variable GREATER number)
    IF(string GREATER number)
    IF(variable EQUAL number)
    IF(string EQUAL number)
    数字比较表达式
    IF(variable STRLESS string)
    IF(string STRLESS string)
    IF(variable STRGREATER string)
    IF(string STRGREATER string)
    IF(variable STREQUAL string)
    IF(string STREQUAL string)
    按照字母序的排列进行比较.
    IF(DEFINED variable),如果变量被定义,为真。
    一个小例子,用来判断平台差异:
    IF(WIN32)
        MESSAGE(STATUS “This is windows.”)
        #作一些 Windows 相关的操作
    ELSE(WIN32)
        MESSAGE(STATUS “This is not windows”)
        #作一些非 Windows 相关的操作
    ENDIF(WIN32)
    上述代码用来控制在不同的平台进行不同的控制,但是,阅读起来却并不是那么舒服,
    ELSE(WIN32)之类的语句很容易引起歧义。
    这就用到了我们在“常用变量”一节提到的 CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS 开关。
    可以 SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON)
    这时候就可以写成:
    IF(WIN32)
    ELSE()
    ENDIF()
    如果配合 ELSEIF 使用,可能的写法是这样:
    IF(WIN32)
    #do something related to WIN32
    ELSEIF(UNIX)
    #do something related to UNIX
    ELSEIF(APPLE)
    #do something related to APPLE
    ENDIF(WIN32)

    2,WHILE

    WHILE 指令的语法是:
            WHILE(condition)
              COMMAND1(ARGS ...)
              COMMAND2(ARGS ...)
              ...
            ENDWHILE(condition)
    其真假判断条件可以参考 IF 指令。

    3,FOREACH

    FOREACH 指令的使用方法有三种形式:
    1,列表
            FOREACH(loop_var arg1 arg2 ...)
              COMMAND1(ARGS ...)
              COMMAND2(ARGS ...)
              ...
            ENDFOREACH(loop_var)
    像我们前面使用的 AUX_SOURCE_DIRECTORY 的例子
    AUX_SOURCE_DIRECTORY(. SRC_LIST)
    FOREACH(F ${SRC_LIST})
        MESSAGE(${F})
    ENDFOREACH(F)
    2,范围
    FOREACH(loop_var RANGE total)
    ENDFOREACH(loop_var)
    从 0 到 total 以1为步进
    举例如下:
    FOREACH(VAR RANGE 10)
    MESSAGE(${VAR})
    ENDFOREACH(VAR)
    最终得到的输出是:
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    3,范围和步进
    FOREACH(loop_var RANGE start stop [step])
    ENDFOREACH(loop_var)
    从 start 开始到 stop 结束,以 step 为步进,
    举例如下
    FOREACH(A RANGE 5 15 3)
    MESSAGE(${A})
    ENDFOREACH(A)
    最终得到的结果是:
    5
    8
    11
    14
    这个指令需要注意的是,知道遇到 ENDFOREACH 指令,整个语句块才会得到真正的执行。
    小结:
    本小节基本涵盖了常用的 cmake 指令,包括基本指令、查找指令、安装指令以及控制语句等,特别需要注意的是,在控制语句条件中使用变量,不能用${}引用,而是直接应用变量名。

    掌握了以上的各种控制指令,你应该完全可以通过 cmake 管理复杂的程序了,下一节,我
    们将介绍一个比较复杂的例子,通过他来演示本章的一些指令,并介绍模块的概念。


    九,复杂的例子:模块的使用和自定义模块

    你现在还会觉得 cmake 简单吗?


    本章我们将着重介绍系统预定义的 Find 模块的使用以及自己编写 Find 模块,系统中提供了其他各种模块,一般情况需要使用 INCLUDE 指令显式的调用,FIND_PACKAGE 指令是一个特例,可以直接调用预定义的模块.

    其实使用纯粹依靠 cmake 本身提供的基本指令来管理工程是一件非常复杂的事情,所以,cmake 设计成了可扩展的架构,可以通过编写一些通用的模块来扩展 cmake.


    在本章,我们准备首先介绍一下 cmake 提供的 FindCURL 模块的使用。然后,基于我们前面的 libhello 共享库,编写一个 FindHello.cmake 模块.

    一,使用 FindCURL 模块

    在/backup/cmake 目录建立 t5 目录,用于存放我们的 CURL 的例子。
    建立 src 目录,并建立 src/main.c,内容如下:
    #include <curl/curl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    FILE *fp;
    int write_data(void *ptr, size_t size, size_t nmemb, void *stream)
    {
    int written = fwrite(ptr, size, nmemb, (FILE *)fp);
    return written;
    }
    int main()
    {
    const char * path = “/tmp/curl-test”;
    const char * mode = “w”;
    fp = fopen(path,mode);
    curl_global_init(CURL_GLOBAL_ALL);
    CURLcode res;
    CURL *curl = curl_easy_init();
    curl_easy_setopt(curl, CURLOPT_URL, “http://www.linux-ren.org”);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
    res = curl_easy_perform(curl);
    curl_easy_cleanup(curl);
    }
    这段代码的作用是通过 curl 取回 www.linux-ren.org 的首页并写入/tmp/curl-test文件中。
    建立主工程文件 CMakeLists.txt
    PROJECT(CURLTEST)
    ADD_SUBDIRECTORY(src)
    建立 src/CMakeLists.txt
    ADD_EXECUTABLE(curltest main.c)
    现在自然是没办法编译的,我们需要添加 curl 的头文件路径和库文件。
    方法 1:
    直接通过 INCLUDE_DIRECTORIES 和 TARGET_LINK_LIBRARIES 指令添加:
    我们可以直接在 src/CMakeLists.txt 中添加:
    INCLUDE_DIRECTORIES(/usr/include)
    TARGET_LINK_LIBRARIES(curltest curl)
    然后建立 build 目录进行外部构建即可。
    现在我们要探讨的是使用 cmake 提供的 FindCURL 模块。
    方法 2,使用 FindCURL 模块。
    向src/CMakeLists.txt 中添加:
    FIND_PACKAGE(CURL)
    IF(CURL_FOUND)
      INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR})
      TARGET_LINK_LIBRARIES(curltest ${CURL_LIBRARY})
    ELSE(CURL_FOUND)
        MESSAGE(FATAL_ERROR ”CURL library not found”)
    ENDIF(CURL_FOUND)
    对于系统预定义的 Find<name>.cmake 模块,使用方法一般如上例所示:
    每一个模块都会定义以下几个变量
        <name>_FOUND
      •
        <name>_INCLUDE_DIR or <name>_INCLUDES
      •
        <name>_LIBRARY or <name>_LIBRARIES
      •
    你可以通过<name>_FOUND 来判断模块是否被找到,如果没有找到,按照工程的需要关闭某些特性、给出提醒或者中止编译,上面的例子就是报出致命错误并终止构建。


    如果<name>_FOUND 为真,则将<name>_INCLUDE_DIR 加入 INCLUDE_DIRECTORIES,
    将<name>_LIBRARY 加入 TARGET_LINK_LIBRARIES 中。
    我们再来看一个复杂的例子,通过<name>_FOUND 来控制工程特性:
    SET(mySources viewer.c)
    SET(optionalSources)
    SET(optionalLibs)
    FIND_PACKAGE(JPEG)
    IF(JPEG_FOUND)
      SET(optionalSources ${optionalSources} jpegview.c)
      INCLUDE_DIRECTORIES( ${JPEG_INCLUDE_DIR} )
      SET(optionalLibs ${optionalLibs} ${JPEG_LIBRARIES} )
      ADD_DEFINITIONS(-DENABLE_JPEG_SUPPORT)
    ENDIF(JPEG_FOUND)
    IF(PNG_FOUND)
      SET(optionalSources ${optionalSources} pngview.c)
      INCLUDE_DIRECTORIES( ${PNG_INCLUDE_DIR} )
      SET(optionalLibs ${optionalLibs} ${PNG_LIBRARIES} )
      ADD_DEFINITIONS(-DENABLE_PNG_SUPPORT)
    ENDIF(PNG_FOUND)
    ADD_EXECUTABLE(viewer ${mySources} ${optionalSources} )
    TARGET_LINK_LIBRARIES(viewer ${optionalLibs}
    通过判断系统是否提供了 JPEG 库来决定程序是否支持 JPEG 功能。


    二,编写属于自己的 FindHello 模块。

    我们在此前的 t3 实例中,演示了构建动态库、静态库的过程并进行了安装。

    接下来,我们在 t6 示例中演示如何自定义 FindHELLO 模块并使用这个模块构建工程:

    请在建立/backup/cmake/中建立 t6 目录,并在其中建立 cmake 目录用于存放我们自己定义的 FindHELLO.cmake 模块,同时建立 src 目录,用于存放我们的源文件。


    1,定义 cmake/FindHELLO.cmake 模块

    FIND_PATH(HELLO_INCLUDE_DIR hello.h /usr/include/hello  /usr/local/include/hello)
    FIND_LIBRARY(HELLO_LIBRARY NAMES hello PATH /usr/lib  /usr/local/lib)
    IF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
      SET(HELLO_FOUND TRUE)
    ENDIF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
    IF (HELLO_FOUND)
      IF (NOT HELLO_FIND_QUIETLY)
         MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}")
      ENDIF (NOT HELLO_FIND_QUIETLY)
    ELSE (HELLO_FOUND)
      IF (HELLO_FIND_REQUIRED)
         MESSAGE(FATAL_ERROR "Could not find hello library")
      ENDIF (HELLO_FIND_REQUIRED)
    ENDIF (HELLO_FOUND)
    针对上面的模块让我们再来回顾一下 FIND_PACKAGE 指令:
           FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE]
                     [[REQUIRED|COMPONENTS] [componets...]])
    前面的 CURL 例子中我们使用了最简单的 FIND_PACKAGE 指令,其实他可以使用多种参数,
    QUIET 参数,对应与我们编写的 FindHELLO 中的 HELLO_FIND_QUIETLY,如果不指定这个参数,就会执行:
    MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}")


    REQUIRED 参数,其含义是指这个共享库是否是工程必须的,如果使用了这个参数,说明这个链接库是必备库,如果找不到这个链接库,则工程不能编译。对应于
    FindHELLO.cmake 模块中的 HELLO_FIND_REQUIRED 变量。
    同样,我们在上面的模块中定义了 HELLO_FOUND,
    HELLO_INCLUDE_DIR,HELLO_LIBRARY 变量供开发者在 FIND_PACKAGE 指令中使用。
    OK,下面建立 src/main.c,内容为:
    #include <hello.h>
    int main()
    {
        HelloFunc();
        return 0;
    }
    建立 src/CMakeLists.txt 文件,内容如下:
    FIND_PACKAGE(HELLO)
    IF(HELLO_FOUND)
       ADD_EXECUTABLE(hello main.c)
       INCLUDE_DIRECTORIES(${HELLO_INCLUDE_DIR})
       TARGET_LINK_LIBRARIES(hello ${HELLO_LIBRARY})
    ENDIF(HELLO_FOUND)
    为了能够让工程找到 FindHELLO.cmake 模块(存放在工程中的 cmake 目录)我们在主工程文件 CMakeLists.txt 中加入:
    SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)


    三,使用自定义的 FindHELLO 模块构建工程

    仍然采用外部编译的方式,建立 build 目录,进入目录运行:
    cmake ..
    我们可以从输出中看到:
    Found Hello: /usr/lib/libhello.so
    如果我们把上面的 FIND_PACKAGE(HELLO)修改为 FIND_PACKAGE(HELLO QUIET),则不会看到上面的输出。
    接下来就可以使用 make 命令构建工程,运行:
    ./src/hello 可以得到输出
    Hello World。
    说明工程成功构建。
    四,如果没有找到 hello library 呢?
    我们可以尝试将/usr/lib/libhello.x 移动到/tmp 目录,这样,按照 FindHELLO 模块的定义,就找不到 hello library 了,我们再来看一下构建结果:
    cmake ..
    仍然可以成功进行构建,但是这时候是没有办法编译的。
    修改 FIND_PACKAGE(HELLO)为 FIND_PACKAGE(HELLO REQUIRED),将 hello library 定义为工程必须的共享库。


    这时候再次运行 cmake ..
    我们得到如下输出:
    CMake Error: Could not find hello library.
    因为找不到 libhello.x,所以,整个 Makefile 生成过程被出错中止。
    小结:
    在本节中,我们学习了如何使用系统提供的 Find<NAME>模块并学习了自己编写
    Find<NAME>模块以及如何在工程中使用这些模块。
    后面的章节,我们会逐渐学习更多的 cmake 模块使用方法以及用 cmake 来管理 GTK 和 QT4工程。


    http://blog.csdn.net/gubenpeiyuan/article/details/8667279


    展开全文
  • 变量间关系及分析方法: a.函数关系(确定性关系)——数学表达式(数学模型) b.相关关系(非确定性的关系)——b1.平行关系(相关分析);b2依存关系(回归分析) b1.平行关系(相关分析)——b11.一元相关分析...

    变量间的关系分析:

    变量间的关系有两类:存在明确的关系-函数关系;不存在完全确定性。

    相关变量关系有两种:平行关系——相互影响;依存关系——变量a收到变量b的影响。

    变量间关系及分析方法:

    a.函数关系(确定性关系)——数学表达式(数学模型)

    b.相关关系(非确定性的关系)——b1.平行关系(相关分析);b2依存关系(回归分析)

    b1.平行关系(相关分析)——b11.一元相关分析——线相关分析;b12多元相关分析——复相关分析;典型相关分析;

    b2.依存关系(回归分析)——b21.一元回归分析:线性回归分析,非线性回归分析;b22.多元线性回归分析;多元非线性回归。

    相关分析以现象之间是否相关、相关(正负)、密切程度为主要内容,不区分自变量与因变量,也不关心各个变量的构成形式。主要分析法:绘制相关图、计算相关系数和检验相关系数。

    相关系数rou: rou=cov(x,y)/sqrt(var(x)*var(y))

    Pearson相关系数:r=s_{xy}/sqrt(s_x^2+s_y^2)=sum(x-mean(x))(y-mean(y))/sqrt((x-mean(x))^2*(y-mean(y))^2)


    回归变量的选择方法

    在多元回归分析中,并不是变量越多越好。变量多会增加模型复杂度、计算量增大、估计与预测精度下降、模型应用费用增加。

    变量选择常用准则:

    1.平均残差平方和最小准则:RMS_p=RSS_p/(n-p)

    2.误差均方根MSE最小准则:MSE_p=sqrt(RMS_p)

    3.校正复相关系数平方(Adjusted R^2)准则

    adjR^2=1-(1-R^2)(n-1)/(n-p);

    4.C_p准则

    C_p=RSS_p/s^2-(n-2p)=(n-p)RMS_p/MSE-(n-2p)

    5.AIC和BIC准则:AIC=n*ln(RSS_p/n)+2p;BIC=n*ln(RSS_p/n)+p*ln(n);




    展开全文
  • 目录 特征选择 (feature_selection) Filter 1. 移除低方差的特征 (Removing ... 2. 单变量特征选择 (Univariate feature selection) Wrapper 3. 递归特征消除 (Recursive Feature Eliminat...

    目录

    特征选择 (feature_selection)

    Filter

    1. 移除低方差的特征 (Removing features with low variance)

    2. 单变量特征选择 (Univariate feature selection)

    Wrapper

    3. 递归特征消除 (Recursive Feature Elimination)

    Embedded

    4. 使用SelectFromModel选择特征 (Feature selection using SelectFromModel)

    5. 将特征选择过程融入pipeline (Feature selection as part of a pipeline)


     

    scikit-learn(工程中用的相对较多的模型介绍):1.13. Feature selection

    原创 2015年08月07日 09:03:28

    参考:http://scikit-learn.org/stable/modules/feature_selection.html

    The classes in the sklearn.feature_selection module can be used for feature selection/dimensionality reduction on sample sets, either to improve estimators’ accuracy scores or to boost their performance on very high-dimensional datasets.

    1removing features with low variance

    VarianceThreshold 是特征选择的简单baseline方法,他删除方差达不到阈值的特征。默认情况下,删除all zero-variance features, i.e. features that have the same value in all samples.
    假设我们想要删除  超过80%的样本数都是0或都是1(假设是boolean features  的所有特征,由于boolean featuresbernoulli随机变量,所以方差为Var[X] = p(1-p),所以我们可以使用阈值0.8*1-0.8):

    >>> X = [[0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 1, 1], [0, 1, 0], [0, 1, 1]]
    >>> sel = VarianceThreshold(threshold=(.8 * (1 - .8)))
    >>> sel.fit_transform(X)
    array([[0, 1],
           [1, 0],
           [0, 0],
           [1, 1],
           [1, 0],
           [1, 1]])

    删除了第一列,因为p=5/6 > 0.8。

    2Univariate feature selection(单变量特征选择)(我用这个非常多)

    Univariate feature selection基于univariate statistical tests(单变量统计检验),分为:
    SelectKBest removes all but the  k highest scoring features

    • SelectPercentile removes all but a user-specified highest scoring percentage of features
    • using common univariate statistical tests for each feature: false positive rate SelectFpr, false discovery rate SelectFdr, or family wise error SelectFwe.
    • GenericUnivariateSelect allows to perform univariate feature selection with a configurable strategy. This allows to select the best univariate selection strategy with hyper-parameter search estimator.

    例如,我们可以对样本集使用卡方检测,进而选择最好的两个features

    >>> from sklearn.datasets import load_iris
    >>> from sklearn.feature_selection import SelectKBest
    >>> from sklearn.feature_selection import chi2
    >>> iris = load_iris()
    >>> X, y = iris.data, iris.target
    >>> X.shape
    (150, 4)
    >>> X_new = SelectKBest(chi2, k=2).fit_transform(X, y)
    >>> X_new.shape
    (150, 2)


    几个注意点:

    1These objects take as input a scoring function that returns univariate p-valuesFor regression: f_regressionFor classification: chi2 or f_classif 

    Beware not to use a regression scoring function with a classification problem, you will get useless results.

    2)Feature selection with sparse data:If you use sparse data (i.e. data represented as sparse matrices), only chi2 will deal with the data without making it dense.例子:Univariate Feature Selection

    3recursive feature elimination(递归特征消除)

    Given an external estimator that assigns weights to features (e.g., the coefficients of a linear model), recursive feature elimination (RFE) is to select features by recursively considering smaller and smaller sets of features.(从最初的所有特征集到逐步删除一个feature< features whose absolute weights are the smallest are pruned from the current set features>,最后达到满足条件的features个数)。

    RFECV performs RFE in a cross-validation loop to find the optimal number of features.
    Recursive feature elimination: A recursive feature elimination example showing the relevance of pixels in a digit classification task.

    4L1-based feature selection

    L1的sparse作用就不说了:

    >>> from sklearn.svm import LinearSVC
    >>> from sklearn.datasets import load_iris
    >>> iris = load_iris()
    >>> X, y = iris.data, iris.target
    >>> X.shape
    (150, 4)
    >>> X_new = LinearSVC(C=0.01, penalty="l1", dual=False).fit_transform(X, y)
    >>> X_new.shape
    (150, 3)

    With SVMs and logistic-regression, the parameter C controls the sparsity: the smaller C the fewer features selected. With Lasso, the higher the alpha parameter, the fewer features selected.

    Examples:

    5Tree-based features selection(这个也用的比价多)

    Tree-based estimators (see the sklearn.tree module and forest of trees in the sklearn.ensemble module) can be used to compute feature importances, which in turn can be used to discard irrelevant features:

    >>> from sklearn.ensemble import ExtraTreesClassifier
    >>> from sklearn.datasets import load_iris
    >>> iris = load_iris()
    >>> X, y = iris.data, iris.target
    >>> X.shape
    (150, 4)
    >>> clf = ExtraTreesClassifier()
    >>> X_new = clf.fit(X, y).transform(X)
    >>> clf.feature_importances_ 
    array([ 0.04...,  0.05...,  0.4...,  0.4...])
    >>> X_new.shape              
    (150, 2)

     

    6Feature selection as part of a pipeline

    Feature selection is usually used as a pre-processing step before doing the actual learning. The recommended way to do this in scikit-learn is to use a sklearn.pipeline.Pipeline:

    clf = Pipeline([
      ('feature_selection', LinearSVC(penalty="l1")),
      ('classification', RandomForestClassifier())
    ])
    clf.fit(X, y)

    In this snippet we make use of a sklearn.svm.LinearSVC to evaluate feature importances and select the most relevant features. Then, a sklearn.ensemble.RandomForestClassifier is trained on the transformed output, i.e. using only relevant features. You can perform similar operations with the other feature selection methods and also classifiers that provide a way to evaluate feature importances of course. See the sklearn.pipeline.Pipeline examples for more details.

     

     

    特征选择 (feature_selection)

    特征选择 (feature_selection)

    [toc]

    本文主要参考sklearn(0.18版为主,部分0.17)1.13节的官方文档,以及一些工程实践整理而成。

      当数据预处理完成后,我们需要选择有意义的特征输入机器学习的算法和模型进行训练。通常来说,从两个方面考虑来选择特征:

    • 特征是否发散:如果一个特征不发散,例如方差接近于0,也就是说样本在这个特征上基本上没有差异,这个特征对于样本的区分并没有什么用。
    • 特征与目标的相关性:这点比较显见,与目标相关性高的特征,应当优选选择。除移除低方差法外,本文介绍的其他方法均从相关性考虑。

    根据特征选择的形式又可以将特征选择方法分为3种:

    • Filter:过滤法,按照发散性或者相关性对各个特征进行评分,设定阈值或者待选择阈值的个数,选择特征。
    • Wrapper:包装法,根据目标函数(通常是预测效果评分),每次选择若干特征,或者排除若干特征。
    • Embedded:嵌入法,先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据系数从大到小选择特征。类似于Filter方法,但是是通过训练来确定特征的优劣。

    特征选择主要有两个目的:

    • 减少特征数量、降维,使模型泛化能力更强,减少过拟合;
    • 增强对特征和特征值之间的理解。

      拿到数据集,一个特征选择方法,往往很难同时完成这两个目的。通常情况下,选择一种自己最熟悉或者最方便的特征选择方法(往往目的是降维,而忽略了对特征和数据理解的目的)。本文将结合 Scikit-learn提供的例子 介绍几种常用的特征选择方法,它们各自的优缺点和问题。

    Filter:

    1. 移除低方差的特征 (Removing features with low variance)

      假设某特征的特征值只有01,并且在所有输入样本中,95%的实例的该特征取值都是1,那就可以认为这个特征作用不大。如果100%都是1,那这个特征就没意义了。当特征值都是离散型变量的时候这种方法才能用,如果是连续型变量,就需要将连续变量离散化之后才能用。而且实际当中,一般不太会有95%以上都取某个值的特征存在,所以这种方法虽然简单但是不太好用。可以把它作为特征选择的预处理,先去掉那些取值变化小的特征,然后再从接下来提到的的特征选择方法中选择合适的进行进一步的特征选择。

    >>> from sklearn.feature_selection import VarianceThreshold
    >>> X=[[0,0,1],[0,1,0], [1,0,0], [0,1,1], [0,1,0], [0,1,1]]
    >>> sel=VarianceThreshold(threshold=(.8*(1-.8)))
    >>> sel.fit_transform(X)
    array([[0,1],[1,0],[0,0],[1,1],[1,0],[1,1]])

    果然, VarianceThreshold 移除了第一列特征,第一列中特征值为0的概率达到了5/6.

    2. 单变量特征选择 (Univariate feature selection)

      单变量特征选择的原理是分别单独的计算每个变量的某个统计指标,根据该指标来判断哪些指标重要,剔除那些不重要的指标。

      对于分类问题(y离散),可采用:
        卡方检验f_classifmutual_info_classif互信息
      对于回归问题(y连续),可采用:
        皮尔森相关系数f_regressionmutual_info_regression最大信息系数

      这种方法比较简单,易于运行,易于理解,通常对于理解数据有较好的效果(但对特征优化、提高泛化能力来说不一定有效)。这种方法有许多改进的版本、变种。

      单变量特征选择基于单变量的统计测试来选择最佳特征。它可以看作预测模型的一项预处理。==Scikit-learn将特征选择程序用包含 transform 函数的对象来展现==

    • SelectKBest 移除得分前 k 名以外的所有特征(top k)
    • SelectPercentile 移除得分在用户指定百分比以后的特征(top k%)
    • 对每个特征使用通用的单变量统计检验: 假正率(false positive rate) SelectFpr, 伪发现率(false discovery rate) SelectFdr, 或族系误差率 SelectFwe.
    • GenericUnivariateSelect 可以设置不同的策略来进行单变量特征选择。同时不同的选择策略也能够使用超参数寻优,从而让我们找到最佳的单变量特征选择策略。

      将特征输入到评分函数,返回一个单变量的f_score(F检验的值)p-values(P值,假设检验中的一个标准,P-value用来和显著性水平作比较),注意SelectKBest SelectPercentile只有得分,没有p-value

    • For classification: chi2, f_classif, mutual_info_classif
    • For regression: f_regression, mutual_info_regression

    Notice:
      The methods based on F-test estimate the degree of linear dependency between two random variables. (F检验用于评估两个随机变量的线性相关性)On the other hand, mutual information methods can capture any kind of statistical dependency, but being nonparametric, they require more samples for accurate estimation.(另一方面,互信息的方法可以捕获任何类型的统计依赖关系,但是作为一个非参数方法,估计准确需要更多的样本)

    Feature selection with sparse data:
      If you use sparse data (i.e. data represented as sparse matrices), chi2, mutual_info_regression, mutual_info_classif will deal with the data without making it dense.(如果你使用稀疏数据(比如,使用稀疏矩阵表示的数据), 卡方检验(chi2)、互信息回归(mutual_info_regression)、互信息分类(mutual_info_classif)在处理数据时可保持其稀疏性.)

    Examples:
    Univariate Feature Selection
    Comparison of F-test and mutual information

    2.1 卡方(Chi2)检验

      经典的卡方检验是检验定性自变量对定性因变量的相关性。比如,我们可以对样本进行一次[Math Processing Error]chi2 测试来选择最佳的两项特征:

    >>> from sklearn.datasets import load_iris
    >>> from sklearn.feature_selection import SelectKBest
    >>> from sklearn.feature_selection import chi2
    >>> iris = load_iris()
    >>> X, y=iris.data, iris.target
    >>> X.shape
    (150,4)
    >>> X_new=SelectKBest(chi2, k=2).fit_transform(X, y)
    >>> X_new.shape
    (150,2)

     
    2.2 Pearson相关系数 (Pearson Correlation)

      皮尔森相关系数是一种最简单的,能帮助理解特征和响应变量之间关系的方法,该方法衡量的是变量之间的线性相关性,结果的取值区间为[-11]-1表示完全的负相关,+1表示完全的正相关,0表示没有线性相关。  Pearson Correlation速度快、易于计算,经常在拿到数据(经过清洗和特征提取之后的)之后第一时间就执行。Scipy pearsonr 方法能够同时计算 相关系数 和p-value.

     

    import numpy as np
    from scipy.stats import pearsonr
    np.random.seed(0)
    size=300
    x=np.random.normal(0,1, size)
    # pearsonr(x, y)的输入为特征矩阵和目标向量
    print("Lower noise", pearsonr(x, x+np.random.normal(0,1, size)))
    print("Higher noise", pearsonr(x, x+np.random.normal(0,10, size)))
    >>>

    # 输出为二元组(sorce, p-value)的数组
    Lower noise (0.71824836862138386,7.3240173129992273e-49)
    Higher noise (0.057964292079338148,0.31700993885324746)

     

    这个例子中,我们比较了变量在加入噪音之前和之后的差异。当噪音比较小的时候,相关性很强,p-value很低。

      Scikit-learn提供的 f_regrssion 方法能够批量计算特征的f_scorep-value,非常方便,参考sklearn pipeline

      Pearson相关系数的一个明显缺陷是,作为特征排序机制,他只对线性关系敏感。如果关系是非线性的,即便两个变量具有一一对应的关系,Pearson相关性也可能会接近0例如:

    x = np.random.uniform(-1, 1, 100000)
    print pearsonr(x, x**2)[0]


    -0.00230804707612

      更多类似的例子参考 sample plots 。另外,如果仅仅根据相关系数这个值来判断的话,有时候会具有很强的误导性,如 Anscombe’s quartet ,最好把数据可视化出来,以免得出错误的结论。

    2.3 互信息和最大信息系数 (Mutual information and maximal information coefficient (MIC)

      经典的互信息(互信息为随机变量XY之间的互信息[Math Processing Error]I(X;Y)为单个事件之间互信息的数学期望)也是评价定性自变量对定性因变量的相关性的,互信息计算公式如下:

     

      互信息直接用于特征选择其实不是太方便:1、它不属于度量方式,也没有办法归一化,在不同数据及上的结果无法做比较;2、对于连续变量的计算不是很方便(XY都是集合,xy都是离散的取值),通常变量需要先离散化,而互信息的结果对离散化的方式很敏感。

      最大信息系数克服了这两个问题。它首先寻找一种最优的离散化方式,然后把互信息取值转换成一种度量方式,取值区间在[01] minepy 提供了MIC功能。

    反过头来看y=x2这个例子,MIC算出来的互信息值为1(最大的取值)

    from minepy import MINE
    m=MINE()
    x =np.random.uniform(-1,1,10000)
    m.compute_score(x, x**2)
    print(m.mic())
    >>>1.0
     MIC的统计能力遭到了 一些质疑 ,当零假设不成立时,MIC的统计就会受到影响。在有的数据集上不存在这个问题,但有的数据集上就存在这个问题。

    2.4 距离相关系数 (Distance Correlation)

      距离相关系数是为了克服Pearson相关系数的弱点而生的。在xx2这个例子中,即便Pearson相关系数是0,我们也不能断定这两个变量是独立的(有可能是非线性相关);但如果距离相关系数是0,那么我们就可以说这两个变量是独立的。

      R energy 包里提供了距离相关系数的实现,另外这是 Python gist 的实现。

    >x = runif(1000, -1,1)
    >dcor(x, x**2)[1]
    0.4943864

      尽管有 MIC 距离相关系数 在了,但当变量之间的关系接近线性相关的时候,Pearson相关系数仍然是不可替代的。
      第一,Pearson相关系数计算速度快,这在处理大规模数据的时候很重要。
      第二,Pearson相关系数的取值区间是[-11],而MIC和距离相关系数都是[01]。这个特点使得Pearson相关系数能够表征更丰富的关系,符号表示关系的正负,绝对值能够表示强度。当然,Pearson相关性有效的前提是两个变量的变化关系是单调的。

    2.5 基于模型的特征排序 (Model based ranking)

      这种方法的思路是直接使用你要用的机器学习算法,针对 每个单独的特征 响应变量建立预测模型。假如 特征 响应变量 之间的关系是非线性的,可以用基于树的方法(决策树、随机森林)、或者 扩展的线性模型 等。基于树的方法比较易于使用,因为他们对非线性关系的建模比较好,并且不需要太多的调试。但要注意过拟合问题,因此树的深度最好不要太大,再就是运用交叉验证

      在 波士顿房价数据集 上使用sklearn 随机森林回归 给出一个_单变量选择_的例子(这里使用了交叉验证):

    from sklearn.cross_validation import cross_val_score, ShuffleSplit
    from sklearn.datasets import load_boston
    from sklearn.ensemble import RandomForestRegressor
    import numpy as np

    # Load boston housing dataset as an example
    boston = load_boston()
    X = boston["data"]
    Y = boston["target"]
    names = boston["feature_names"]

    rf = RandomForestRegressor(n_estimators=20, max_depth=4)
    scores = []
    # 单独采用每个特征进行建模,并进行交叉验证
    for i in range(X.shape[1]):
        score = cross_val_score(rf, X[:, i:i+1], Y, scoring="r2",  # 注意X[:, i]和X[:, i:i+1]的区别
                                cv=ShuffleSplit(len(X), 3, .3))
        scores.append((format(np.mean(score), '.3f'), names[i]))
    print(sorted(scores, reverse=True))

    [('0.620', 'LSTAT'), ('0.591', 'RM'), ('0.467', 'NOX'), ('0.342', 'INDUS'), ('0.305', 'TAX'), ('0.240', 'PTRATIO'), ('0.206', 'CRIM'), ('0.187', 'RAD'), ('0.184', 'ZN'), ('0.135', 'B'), ('0.082', 'DIS'), ('0.020', 'CHAS'), ('0.002', 'AGE')]


    Wrapper

    3. 递归特征消除 (Recursive Feature Elimination)

      递归消除特征法使用一个基模型来进行多轮训练,每轮训练后,移除若干权值系数的特征,再基于新的特征集进行下一轮训练

      sklearn官方解释:对特征含有权重的预测模型(例如,线性模型对应参数coefficients)RFE通过递归减少考察的特征集规模来选择特征。首先,预测模型在原始特征上训练,每个特征指定一个权重。之后,那些拥有最小绝对值权重的特征被踢出特征集。如此往复递归,直至剩余的特征数量达到所需的特征数量。

      RFECV 通过交叉验证的方式执行RFE,以此来选择最佳数量的特征:对于一个数量为dfeature的集合,他的所有的子集的个数是2d次方减1(包含空集)。指定一个外部的学习算法,比如SVM之类的。通过该算法计算所有子集的validation error。选择error最小的那个子集作为所挑选的特征。

    from sklearn.feature_selection import RFE
    from sklearn.linear_model import LogisticRegression

    #递归特征消除法,返回特征选择后的数据
    #参数estimator为基模型
    #参数n_features_to_select为选择的特征个数
    RFE(estimator=LogisticRegression(), n_features_to_select=2).fit_transform(iris.data, iris.target)

    示例:
    Recursive feature elimination: 一个递归特征消除的示例,展示了在数字分类任务中,像素之间的相关性。
    Recursive feature elimination with cross-validation: 一个递归特征消除示例,通过交叉验证的方式自动调整所选特征的数量。

    示例:
    Recursive feature elimination: 一个递归特征消除的示例,展示了在数字分类任务中,像素之间的相关性。
    Recursive feature elimination with cross-validation: 一个递归特征消除示例,通过交叉验证的方式自动调整所选特征的数量。

    Embedded

    4. 使用SelectFromModel选择特征 (Feature selection using SelectFromModel)

      单变量特征选择方法独立的衡量每个特征与响应变量之间的关系,另一种主流的特征选择方法是基于机器学习模型的方法。有些机器学习方法本身就具有对特征进行打分的机制,或者很容易将其运用到特征选择任务中,例如回归模型,SVM,决策树,随机森林等等。其实Pearson相关系数等价于线性回归里的标准化回归系数。

      SelectFromModel 作为meta-transformer,能够用于拟合后任何拥有coef_feature_importances_ 属性的预测模型。 如果特征对应的coef_  feature_importances_ 值低于设定的阈值threshold,那么这些特征将被移除。除了手动设置阈值,也可通过字符串参数调用内置的启发式算法(heuristics)来设置阈值,包括:平均值(“mean”), 中位数(“median”)以及他们与浮点数的乘积,如”0.1*mean”

    Examples
    Feature selection using SelectFromModel and LassoCV: 在阈值未知的前提下,选择了Boston dataset中两项最重要的特征。

    4.1 基于L1的特征选择 (L1-based feature selection)

      使用L1范数作为惩罚项的线性模型(Linear models)会得到稀疏解:大部分特征对应的系数为0。当你希望减少特征的维度以用于其它分类器时,可以通过 feature_selection.SelectFromModel 来选择不为0的系数。特别指出,常用于此目的的稀疏预测模型有 linear_model.Lasso(回归), linear_model.LogisticRegression svm.LinearSVC(分类):

    >>> from sklearn.svm import LinearSVC
    >>> from sklearn.datasets import load_iris
    >>> from sklearn.feature_selection import SelectFromModel
    >>> iris = load_iris()
    >>> X, y = iris.data, iris.target
    >>> X.shape
    (150, 4)
    >>> lsvc = LinearSVC(C=0.01, penalty="l1", dual=False).fit(X, y)
    >>> model = SelectFromModel(lsvc, prefit=True)
    >>> X_new = model.transform(X)
    >>> X_new.shape
    (150, 3)

     

      使用feature_selection库的SelectFromModel类结合带L1以及L2惩罚项的逻辑回归模型:


    from sklearn.feature_selection import SelectFromModel
    #带L1和L2惩罚项的逻辑回归作为基模型的特征选择
    #参数threshold为权值系数之差的阈值
    SelectFromModel(LR(threshold=0.5, C=0.1)).fit_transform(iris.data, iris.target)
      对于SVM和逻辑回归,参数C控制稀疏性:C越小,被选中的特征越少。对于Lasso,参数alpha越大,被选中的特征越少。
    

    示例:
    Classification of text documents using sparse features: 不同算法使用基于L1的特征选择进行文档分类的对比。

    Note:

    L1恢复和压缩感知 (L1-recovery and compressive sensing)
      对于一个好的alpha值,在满足特定条件下, Lasso 仅使用少量观测值就能够完全恢复出非零的系数。特别地,样本的数量需要足够大,否则L1模型的表现会充满随机性,所谓足够大取决于非零系数的数量,特征数量的对数,噪声的数量,非零系数的最小绝对值以及设计矩阵X的结构。此外,设计矩阵必须拥有特定的属性,比如不能太过相关(correlated) 对于非零系数的恢复,还没有一个选择alpha值的通用规则 alpha值可以通过交叉验证来设置(LassoCV or LassoLarsCV),尽管这也许会导致模型欠惩罚(under-penalized):引入少量非相关变量不会影响分数预测。相反BIC (LassoLarsIC) 更倾向于设置较大的alpha值。
    Reference Richard G. Baraniuk “Compressive Sensing”, IEEE Signal Processing Magazine [120] July 2007

    4.2 随机稀疏模型 (Randomized sparse models)

      基于L1的稀疏模型的局限在于,当面对一组互相关的特征时,它们只会选择其中一项特征。为了减轻该问题的影响可以使用随机化技术,通过_多次重新估计稀疏模型来扰乱设计矩阵_,或通过_多次下采样数据来统计一个给定的回归量被选中的次数_——==稳定性选择 (Stability Selection)==

      RandomizedLasso 实现了使用这项策略的LassoRandomizedLogisticRegression 使用逻辑回归,适用于分类任务。要得到整个迭代过程的稳定分数,你可以使用 lasso_stability_path

      注意到对于非零特征的检测,要使随机稀疏模型比标准F统计量更有效, 那么模型的参考标准需要是稀疏的,换句话说,非零特征应当只占一小部分。

    示例:
    Sparse recovery: feature selection for sparse linear models: 比较了不同的特征选择方法,并讨论了它们各自适用的场合。

    参考文献:
    N. Meinshausen, P. Buhlmann, “Stability selection”, Journal of the Royal Statistical Society, 72 (2010)
    F. Bach, “Model-Consistent Sparse Estimation through the Bootstrap”

    4.3 基于树的特征选择 (Tree-based feature selection)

      基于树的预测模型(见 sklearn.tree 模块,森林见 sklearn.ensemble 模块)能够用来计算特征的重要程度,因此能用来去除不相关的特征(结合 sklearn.feature_selection.SelectFromModel:

    >>> from sklearn.ensemble import ExtraTreesClassifier
    >>> from sklearn.datasets import load_iris
    >>> from sklearn.feature_selection import SelectFromModel
    >>> iris = load_iris()
    >>> X, y = iris.data, iris.target
    >>> X.shape
    (150, 4)
    >>> clf = ExtraTreesClassifier()
    >>> clf = clf.fit(X, y)
    >>> clf.feature_importances_  
    array([ 0.04...,  0.05...,  0.4...,  0.4...])
    >>> model = SelectFromModel(clf, prefit=True)
    >>> X_new = model.transform(X)
    >>> X_new.shape               
    (150, 2)
    示例:
    Feature importances with forests of trees: 从模拟数据中恢复有意义的特征。
    Pixel importances with a parallel forest of trees: 用于人脸识别数据的示例。

    5. 将特征选择过程融入pipeline (Feature selection as part of a pipeline)

      特征选择常常被当作学习之前的一项预处理。在scikit-learn中推荐使用
    sklearn.pipeline.Pipeline:

    clf = Pipeline([
      ('feature_selection', SelectFromModel(LinearSVC(penalty="l1"))),
      ('classification', RandomForestClassifier())
    ])
    clf.fit(X, y)

      在此代码片段中,将 sklearn.svm.LinearSVC sklearn.feature_selection.SelectFromModel 结合来评估特征的重要性,并选择最相关的特征。之后 sklearn.ensemble.RandomForestClassifier 模型使用转换后的输出训练,即只使用被选出的相关特征。你可以选择其它特征选择方法,或是其它提供特征重要性评估的分类器。更多详情见 sklearn.pipeline.Pipeline 相关示例。
      
    关于更多,参见另一个文档:
    《基于模型的特征选择详解 (Embedded & Wrapper)


    小结:

    所属方式

    说明

    VarianceThreshold

    Filter

    方差选择法(移除低方差的特征)

    SelectKBest

    Filter

    可选关联系数、卡方校验、最大信息系数作为得分计算的方法

    RFE

    Wrapper

    递归地训练基模型,将权值系数较小的特征从特征集合中消除

    SelectFromModel

    Embedded

    训练基模型,选择权值系数较高的特征


    参考:
    [1] [1.13. Feature selection](http://scikit-learn.org/stable/modules/feature_selection.html#feature-selection)
    [2] [1.13
    特征选择](http://sklearn.lzjqsdd.com/modules/feature_selection.html#feature-selection)
    [3] [
    干货:结合Scikit-learn介绍几种常用的特征选择方法](http://www.tuicool.com/articles/ieUvaq)
    [4] [
    使用sklearn做单机特征工程](http://www.cnblogs.com/jasonfreak/p/5448385.html#3601031)
    [5] [
    使用sklearn优雅地进行数据挖掘](http://www.cnblogs.com/jasonfreak/p/5448462.html)
    [6] [
    谁动了我的特征?——sklearn特征转换行为全记录](http://www.cnblogs.com/jasonfreak/p/5619260.html)

    注:
      文档[4]实际上是用sklearn实现整个数据挖掘流程,特别是在提高效率上sklearn的并行处理,流水线处理,自动化调参,持久化是使用sklearn优雅地进行数据挖掘的核心。这里是一个简单的总结,具体可查看该文档:

    类或方法

    说明

    sklearn.pipeline

    Pipeline

    流水线处理

    sklearn.pipeline

    FeatureUnion

    并行处理

    sklearn.grid_search

    GridSearchCV

    网格搜索自动化调参

    externals.joblib

    dump

    数据持久化

    externals.joblib

    load

    从文件系统中加载数据至内存

     

     

     

     

    展开全文
  • 几种常用的特征选择方法

    万次阅读 多人点赞 2016-12-14 16:33:38
    几种常用的特征选择方法

    结合Scikit-learn介绍几种常用的特征选择方法

    特征选择(排序)对于数据科学家、机器学习从业者来说非常重要。好的特征选择能够提升模型的性能,更能帮助我们理解数据的特点、底层结构,这对进一步改善模型、算法都有着重要作用。

    特征选择主要有两个功能:

    1. 减少特征数量、降维,使模型泛化能力更强,减少过拟合
    2. 增强对特征和特征值之间的理解

    拿到数据集,一个特征选择方法,往往很难同时完成这两个目的。通常情况下,我们经常不管三七二十一,选择一种自己最熟悉或者最方便的特征选择方法(往往目的是降维,而忽略了对特征和数据理解的目的)。

    在许多机器学习相关的书里,很难找到关于特征选择的内容,因为特征选择要解决的问题往往被视为机器学习的一种副作用,一般不会单独拿出来讨论。

    本文将结合 Scikit-learn提供的例子 介绍几种常用的特征选择方法,它们各自的优缺点和问题。

    1 去掉取值变化小的特征 Removing features with low variance

    这应该是最简单的特征选择方法了:假设某特征的特征值只有0和1,并且在所有输入样本中,95%的实例的该特征取值都是1,那就可以认为这个特征作用不大。如果100%都是1,那这个特征就没意义了。当特征值都是离散型变量的时候这种方法才能用,如果是连续型变量,就需要将连续变量离散化之后才能用,而且实际当中,一般不太会有95%以上都取某个值的特征存在,所以这种方法虽然简单但是不太好用。可以把它作为特征选择的预处理,先去掉那些取值变化小的特征,然后再从接下来提到的的特征选择方法中选择合适的进行进一步的特征选择。

    2 单变量特征选择 Univariate feature selection

    单变量特征选择能够对每一个特征进行测试,衡量该特征和响应变量之间的关系,根据得分扔掉不好的特征。对于回归和分类问题可以采用卡方检验等方式对特征进行测试。

    这种方法比较简单,易于运行,易于理解,通常对于理解数据有较好的效果(但对特征优化、提高泛化能力来说不一定有效);这种方法有许多改进的版本、变种。

    2.1 Pearson相关系数 Pearson Correlation

    皮尔森相关系数是一种最简单的,能帮助理解特征和响应变量之间关系的方法,该方法衡量的是变量之间的线性相关性,结果的取值区间为[-1,1],-1表示完全的负相关(这个变量下降,那个就会上升),+1表示完全的正相关,0表示没有线性相关。

    Pearson Correlation速度快、易于计算,经常在拿到数据(经过清洗和特征提取之后的)之后第一时间就执行。Scipy的 pearsonr 方法能够同时计算相关系数和p-value,

    import numpy as np
    from scipy.stats import pearsonr
    np.random.seed(0)
    size = 300
    x = np.random.normal(0, 1, size)
    print "Lower noise", pearsonr(x, x + np.random.normal(0, 1, size))
    print "Higher noise", pearsonr(x, x + np.random.normal(0, 10, size))

    Lower noise (0.71824836862138386, 7.3240173129992273e-49)Higher noise (0.057964292079338148, 0.31700993885324746)

    这个例子中,我们比较了变量在加入噪音之前和之后的差异。当噪音比较小的时候,相关性很强,p-value很低。

    Scikit-learn提供的 f_regrssion 方法能够批量计算特征的p-value,非常方便,参考sklearn的 pipeline

    Pearson相关系数的一个明显缺陷是,作为特征排序机制,他只对线性关系敏感。如果关系是非线性的,即便两个变量具有一一对应的关系,Pearson相关性也可能会接近0。

    x = np.random.uniform(-1, 1, 100000)
    print pearsonr(x, x**2)[0]

    -0.00230804707612

    更多类似的例子参考 sample plots 。另外,如果仅仅根据相关系数这个值来判断的话,有时候会具有很强的误导性,如 Anscombe’s quartet ,最好把数据可视化出来,以免得出错误的结论。

    2.2 互信息和最大信息系数 Mutual information and maximal information coefficient (MIC)

    以上就是经典的互信息公式了。想把互信息直接用于特征选择其实不是太方便:1、它不属于度量方式,也没有办法归一化,在不同数据及上的结果无法做比较;2、对于连续变量的计算不是很方便(X和Y都是集合,x,y都是离散的取值),通常变量需要先离散化,而互信息的结果对离散化的方式很敏感。

    最大信息系数克服了这两个问题。它首先寻找一种最优的离散化方式,然后把互信息取值转换成一种度量方式,取值区间在[0,1]。 minepy 提供了MIC功能。

    反过头来看y=x^2这个例子,MIC算出来的互信息值为1(最大的取值)。

    from minepy import MINE
    m = MINE()
    x = np.random.uniform(-1, 1, 10000)
    m.compute_score(x, x**2)
    print m.mic()

    1.0

    MIC的统计能力遭到了 一些质疑 ,当零假设不成立时,MIC的统计就会受到影响。在有的数据集上不存在这个问题,但有的数据集上就存在这个问题。

    2.3 距离相关系数 (Distance correlation)

    距离相关系数是为了克服Pearson相关系数的弱点而生的。在x和x^2这个例子中,即便Pearson相关系数是0,我们也不能断定这两个变量是独立的(有可能是非线性相关);但如果距离相关系数是0,那么我们就可以说这两个变量是独立的。

    R的 energy 包里提供了距离相关系数的实现,另外这是 Python gist 的实现。

    #R-code
    > x = runif (1000, -1, 1)
    > dcor(x, x**2)
    [1] 0.4943864

    尽管有MIC和距离相关系数在了,但当变量之间的关系接近线性相关的时候,Pearson相关系数仍然是不可替代的。第一、Pearson相关系数计算速度快,这在处理大规模数据的时候很重要。第二、Pearson相关系数的取值区间是[-1,1],而MIC和距离相关系数都是[0,1]。这个特点使得Pearson相关系数能够表征更丰富的关系,符号表示关系的正负,绝对值能够表示强度。当然,Pearson相关性有效的前提是两个变量的变化关系是单调的。

    2.4 基于学习模型的特征排序 (Model based ranking)

    这种方法的思路是直接使用你要用的机器学习算法,针对每个单独的特征和响应变量建立预测模型。其实Pearson相关系数等价于线性回归里的标准化回归系数。假如某个特征和响应变量之间的关系是非线性的,可以用基于树的方法(决策树、随机森林)、或者扩展的线性模型等。基于树的方法比较易于使用,因为他们对非线性关系的建模比较好,并且不需要太多的调试。但要注意过拟合问题,因此树的深度最好不要太大,再就是运用交叉验证。

    在 波士顿房价数据集 上使用sklearn的 随机森林回归 给出一个单变量选择的例子:

    from sklearn.cross_validation import cross_val_score, ShuffleSplit
    from sklearn.datasets import load_boston
    from sklearn.ensemble import RandomForestRegressor
    
    #Load boston housing dataset as an example
    boston = load_boston()
    X = boston["data"]
    Y = boston["target"]
    names = boston["feature_names"]
    
    rf = RandomForestRegressor(n_estimators=20, max_depth=4)
    scores = []
    for i in range(X.shape[1]):
         score = cross_val_score(rf, X[:, i:i+1], Y, scoring="r2",
                                  cv=ShuffleSplit(len(X), 3, .3))
         scores.append((round(np.mean(score), 3), names[i]))
    print sorted(scores, reverse=True)

    [(0.636, ‘LSTAT’), (0.59, ‘RM’), (0.472, ‘NOX’), (0.369, ‘INDUS’), (0.311, ‘PTRATIO’), (0.24, ‘TAX’), (0.24, ‘CRIM’), (0.185, ‘RAD’), (0.16, ‘ZN’), (0.087, ‘B’), (0.062, ‘DIS’), (0.036, ‘CHAS’), (0.027, ‘AGE’)]

    3 线性模型和正则化

    单变量特征选择方法独立的衡量每个特征与响应变量之间的关系,另一种主流的特征选择方法是基于机器学习模型的方法。有些机器学习方法本身就具有对特征进行打分的机制,或者很容易将其运用到特征选择任务中,例如回归模型,SVM,决策树,随机森林等等。说句题外话,这种方法好像在一些地方叫做wrapper类型,大概意思是说,特征排序模型和机器学习模型是耦盒在一起的,对应的非wrapper类型的特征选择方法叫做filter类型。

    下面将介绍如何用回归模型的系数来选择特征。越是重要的特征在模型中对应的系数就会越大,而跟输出变量越是无关的特征对应的系数就会越接近于0。在噪音不多的数据上,或者是数据量远远大于特征数的数据上,如果特征之间相对来说是比较独立的,那么即便是运用最简单的线性回归模型也一样能取得非常好的效果。

    from sklearn.linear_model import LinearRegression
    import numpy as np
    np.random.seed(0)
    size = 5000
    #A dataset with 3 features
    X = np.random.normal(0, 1, (size, 3))
    #Y = X0 + 2*X1 + noise
    Y = X[:,0] + 2*X[:,1] + np.random.normal(0, 2, size)
    lr = LinearRegression()
    lr.fit(X, Y)
    #A helper method for pretty-printing linear models
    def pretty_print_linear(coefs, names = None, sort = False):
    	if names == None:
    		names = ["X%s" % x for x in range(len(coefs))]
    	lst = zip(coefs, names)
    	if sort:
    		lst = sorted(lst,  key = lambda x:-np.abs(x[0]))
    	return " + ".join("%s * %s" % (round(coef, 3), name)
    								   for coef, name in lst)
    print "Linear model:", pretty_print_linear(lr.coef_)
    

    Linear model: 0.984 * X0 + 1.995 * X1 + -0.041 * X2

    在这个例子当中,尽管数据中存在一些噪音,但这种特征选择模型仍然能够很好的体现出数据的底层结构。当然这也是因为例子中的这个问题非常适合用线性模型来解:特征和响应变量之间全都是线性关系,并且特征之间均是独立的。

    在很多实际的数据当中,往往存在多个互相关联的特征,这时候模型就会变得不稳定,数据中细微的变化就可能导致模型的巨大变化(模型的变化本质上是系数,或者叫参数,可以理解成W),这会让模型的预测变得困难,这种现象也称为多重共线性。例如,假设我们有个数据集,它的真实模型应该是Y=X1+X2,当我们观察的时候,发现Y’=X1+X2+e,e是噪音。如果X1和X2之间存在线性关系,例如X1约等于X2,这个时候由于噪音e的存在,我们学到的模型可能就不是Y=X1+X2了,有可能是Y=2X1,或者Y=-X1+3X2。

    下边这个例子当中,在同一个数据上加入了一些噪音,用随机森林算法进行特征选择。

    from sklearn.linear_model import LinearRegression
    
    size = 100
    np.random.seed(seed=5)
    
    X_seed = np.random.normal(0, 1, size)
    X1 = X_seed + np.random.normal(0, .1, size)
    X2 = X_seed + np.random.normal(0, .1, size)
    X3 = X_seed + np.random.normal(0, .1, size)
    
    Y = X1 + X2 + X3 + np.random.normal(0,1, size)
    X = np.array([X1, X2, X3]).T
    
    lr = LinearRegression()
    lr.fit(X,Y)
    print "Linear model:", pretty_print_linear(lr.coef_)

    Linear model: -1.291 * X0 + 1.591 * X1 + 2.747 * X2

    系数之和接近3,基本上和上上个例子的结果一致,应该说学到的模型对于预测来说还是不错的。但是,如果从系数的字面意思上去解释特征的重要性的话,X3对于输出变量来说具有很强的正面影响,而X1具有负面影响,而实际上所有特征与输出变量之间的影响是均等的。

    同样的方法和套路可以用到类似的线性模型上,比如逻辑回归。

    3.1 正则化模型

    正则化就是把额外的约束或者惩罚项加到已有模型(损失函数)上,以防止过拟合并提高泛化能力。损失函数由原来的E(X,Y)变为E(X,Y)+alpha||w||,w是模型系数组成的向量(有些地方也叫参数parameter,coefficients),||·||一般是L1或者L2范数,alpha是一个可调的参数,控制着正则化的强度。当用在线性模型上时,L1正则化和L2正则化也称为Lasso和Ridge。

    3.2 L1正则化/Lasso

    L1正则化将系数w的l1范数作为惩罚项加到损失函数上,由于正则项非零,这就迫使那些弱的特征所对应的系数变成0。因此L1正则化往往会使学到的模型很稀疏(系数w经常为0),这个特性使得L1正则化成为一种很好的特征选择方法。

    Scikit-learn为线性回归提供了Lasso,为分类提供了L1逻辑回归。

    下面的例子在波士顿房价数据上运行了Lasso,其中参数alpha是通过grid search进行优化的。

    from sklearn.linear_model import Lasso
    from sklearn.preprocessing import StandardScaler
    from sklearn.datasets import load_boston
    
    boston = load_boston()
    scaler = StandardScaler()
    X = scaler.fit_transform(boston["data"])
    Y = boston["target"]
    names = boston["feature_names"]
    
    lasso = Lasso(alpha=.3)
    lasso.fit(X, Y)
    
    print "Lasso model: ", pretty_print_linear(lasso.coef_, names, sort = True)

    Lasso model: -3.707 * LSTAT + 2.992 * RM + -1.757 * PTRATIO + -1.081 * DIS + -0.7 * NOX + 0.631 * B + 0.54 * CHAS + -0.236 * CRIM + 0.081 * ZN + -0.0 * INDUS + -0.0 * AGE + 0.0 * RAD + -0.0 * TAX

    可以看到,很多特征的系数都是0。如果继续增加alpha的值,得到的模型就会越来越稀疏,即越来越多的特征系数会变成0。

    然而,L1正则化像非正则化线性模型一样也是不稳定的,如果特征集合中具有相关联的特征,当数据发生细微变化时也有可能导致很大的模型差异。

    3.3 L2正则化/Ridge regression

    L2正则化将系数向量的L2范数添加到了损失函数中。由于L2惩罚项中系数是二次方的,这使得L2和L1有着诸多差异,最明显的一点就是,L2正则化会让系数的取值变得平均。对于关联特征,这意味着他们能够获得更相近的对应系数。还是以Y=X1+X2为例,假设X1和X2具有很强的关联,如果用L1正则化,不论学到的模型是Y=X1+X2还是Y=2X1,惩罚都是一样的,都是2 alpha。但是对于L2来说,第一个模型的惩罚项是2 alpha,但第二个模型的是4*alpha。可以看出,系数之和为常数时,各系数相等时惩罚是最小的,所以才有了L2会让各个系数趋于相同的特点。

    可以看出,L2正则化对于特征选择来说一种稳定的模型,不像L1正则化那样,系数会因为细微的数据变化而波动。所以L2正则化和L1正则化提供的价值是不同的,L2正则化对于特征理解来说更加有用:表示能力强的特征对应的系数是非零。

    回过头来看看3个互相关联的特征的例子,分别以10个不同的种子随机初始化运行10次,来观察L1和L2正则化的稳定性。

    from sklearn.linear_model import Ridge
    from sklearn.metrics import r2_score
    size = 100
    #We run the method 10 times with different random seeds
    for i in range(10):
    	print "Random seed %s" % i
    	np.random.seed(seed=i)
    	X_seed = np.random.normal(0, 1, size)
    	X1 = X_seed + np.random.normal(0, .1, size)
    	X2 = X_seed + np.random.normal(0, .1, size)
    	X3 = X_seed + np.random.normal(0, .1, size)
    	Y = X1 + X2 + X3 + np.random.normal(0, 1, size)
    	X = np.array([X1, X2, X3]).T
    	lr = LinearRegression()
    	lr.fit(X,Y)
    	print "Linear model:", pretty_print_linear(lr.coef_)
    	ridge = Ridge(alpha=10)
    	ridge.fit(X,Y)
    	print "Ridge model:", pretty_print_linear(ridge.coef_)
    	print
    

    Random seed 0 Linear model: 0.728 * X0 + 2.309 * X1 + -0.082 * X2 Ridge model: 0.938 * X0 + 1.059 * X1 + 0.877 * X2

    Random seed 1 Linear model: 1.152 * X0 + 2.366 * X1 + -0.599 * X2 Ridge model: 0.984 * X0 + 1.068 * X1 + 0.759 * X2

    Random seed 2 Linear model: 0.697 * X0 + 0.322 * X1 + 2.086 * X2 Ridge model: 0.972 * X0 + 0.943 * X1 + 1.085 * X2

    Random seed 3 Linear model: 0.287 * X0 + 1.254 * X1 + 1.491 * X2 Ridge model: 0.919 * X0 + 1.005 * X1 + 1.033 * X2

    Random seed 4 Linear model: 0.187 * X0 + 0.772 * X1 + 2.189 * X2 Ridge model: 0.964 * X0 + 0.982 * X1 + 1.098 * X2

    Random seed 5 Linear model: -1.291 * X0 + 1.591 * X1 + 2.747 * X2 Ridge model: 0.758 * X0 + 1.011 * X1 + 1.139 * X2

    Random seed 6 Linear model: 1.199 * X0 + -0.031 * X1 + 1.915 * X2 Ridge model: 1.016 * X0 + 0.89 * X1 + 1.091 * X2

    Random seed 7 Linear model: 1.474 * X0 + 1.762 * X1 + -0.151 * X2 Ridge model: 1.018 * X0 + 1.039 * X1 + 0.901 * X2

    Random seed 8 Linear model: 0.084 * X0 + 1.88 * X1 + 1.107 * X2 Ridge model: 0.907 * X0 + 1.071 * X1 + 1.008 * X2

    Random seed 9 Linear model: 0.714 * X0 + 0.776 * X1 + 1.364 * X2 Ridge model: 0.896 * X0 + 0.903 * X1 + 0.98 * X2

    可以看出,不同的数据上线性回归得到的模型(系数)相差甚远,但对于L2正则化模型来说,结果中的系数非常的稳定,差别较小,都比较接近于1,能够反映出数据的内在结构。

    4 随机森林

    随机森林具有准确率高、鲁棒性好、易于使用等优点,这使得它成为了目前最流行的机器学习算法之一。随机森林提供了两种特征选择的方法:mean decrease impurity和mean decrease accuracy。

    4.1 平均不纯度减少 mean decrease impurity

    随机森林由多个决策树构成。决策树中的每一个节点都是关于某个特征的条件,为的是将数据集按照不同的响应变量一分为二。利用不纯度可以确定节点(最优条件),对于分类问题,通常采用 基尼不纯度 或者 信息增益 ,对于回归问题,通常采用的是 方差 或者最小二乘拟合。当训练决策树的时候,可以计算出每个特征减少了多少树的不纯度。对于一个决策树森林来说,可以算出每个特征平均减少了多少不纯度,并把它平均减少的不纯度作为特征选择的值。

    下边的例子是sklearn中基于随机森林的特征重要度度量方法:

    from sklearn.datasets import load_boston
    from sklearn.ensemble import RandomForestRegressor
    import numpy as np
    #Load boston housing dataset as an example
    boston = load_boston()
    X = boston["data"]
    Y = boston["target"]
    names = boston["feature_names"]
    rf = RandomForestRegressor()
    rf.fit(X, Y)
    print "Features sorted by their score:"
    print sorted(zip(map(lambda x: round(x, 4), rf.feature_importances_), names), 
                 reverse=True)

    Features sorted by their score: [(0.5298, ‘LSTAT’), (0.4116, ‘RM’), (0.0252, ‘DIS’), (0.0172, ‘CRIM’), (0.0065, ‘NOX’), (0.0035, ‘PTRATIO’), (0.0021, ‘TAX’), (0.0017, ‘AGE’), (0.0012, ‘B’), (0.0008, ‘INDUS’), (0.0004, ‘RAD’), (0.0001, ‘CHAS’), (0.0, ‘ZN’)]

    这里特征得分实际上采用的是 Gini Importance 。使用基于不纯度的方法的时候,要记住:1、这种方法存在 偏向 ,对具有更多类别的变量会更有利;2、对于存在关联的多个特征,其中任意一个都可以作为指示器(优秀的特征),并且一旦某个特征被选择之后,其他特征的重要度就会急剧下降,因为不纯度已经被选中的那个特征降下来了,其他的特征就很难再降低那么多不纯度了,这样一来,只有先被选中的那个特征重要度很高,其他的关联特征重要度往往较低。在理解数据时,这就会造成误解,导致错误的认为先被选中的特征是很重要的,而其余的特征是不重要的,但实际上这些特征对响应变量的作用确实非常接近的(这跟Lasso是很像的)。

    特征随机选择 方法稍微缓解了这个问题,但总的来说并没有完全解决。下面的例子中,X0、X1、X2是三个互相关联的变量,在没有噪音的情况下,输出变量是三者之和。

    size = 10000
    np.random.seed(seed=10)
    X_seed = np.random.normal(0, 1, size)
    X0 = X_seed + np.random.normal(0, .1, size)
    X1 = X_seed + np.random.normal(0, .1, size)
    X2 = X_seed + np.random.normal(0, .1, size)
    X = np.array([X0, X1, X2]).T
    Y = X0 + X1 + X2
    
    rf = RandomForestRegressor(n_estimators=20, max_features=2)
    rf.fit(X, Y);
    print "Scores for X0, X1, X2:", map(lambda x:round (x,3),
                                        rf.feature_importances_)

    Scores for X0, X1, X2: [0.278, 0.66, 0.062]

    当计算特征重要性时,可以看到X1的重要度比X2的重要度要高出10倍,但实际上他们真正的重要度是一样的。尽管数据量已经很大且没有噪音,且用了20棵树来做随机选择,但这个问题还是会存在。

    需要注意的一点是,关联特征的打分存在不稳定的现象,这不仅仅是随机森林特有的,大多数基于模型的特征选择方法都存在这个问题。

    4.2 平均精确率减少 Mean decrease accuracy

    另一种常用的特征选择方法就是直接度量每个特征对模型精确率的影响。主要思路是打乱每个特征的特征值顺序,并且度量顺序变动对模型的精确率的影响。很明显,对于不重要的变量来说,打乱顺序对模型的精确率影响不会太大,但是对于重要的变量来说,打乱顺序就会降低模型的精确率。

    这个方法sklearn中没有直接提供,但是很容易实现,下面继续在波士顿房价数据集上进行实现。

    from sklearn.cross_validation import ShuffleSplit
    from sklearn.metrics import r2_score
    from collections import defaultdict
    X = boston["data"]
    Y = boston["target"]
    rf = RandomForestRegressor()
    scores = defaultdict(list)
    #crossvalidate the scores on a number of different random splits of the data
    for train_idx, test_idx in ShuffleSplit(len(X), 100, .3):
    	X_train, X_test = X[train_idx], X[test_idx]
    	Y_train, Y_test = Y[train_idx], Y[test_idx]
    	r = rf.fit(X_train, Y_train)
    	acc = r2_score(Y_test, rf.predict(X_test))
    	for i in range(X.shape[1]):
    		X_t = X_test.copy()
    		np.random.shuffle(X_t[:, i])
    		shuff_acc = r2_score(Y_test, rf.predict(X_t))
    		scores[names[i]].append((acc-shuff_acc)/acc)
    print "Features sorted by their score:"
    print sorted([(round(np.mean(score), 4), feat) for
    			  feat, score in scores.items()], reverse=True)
    

    Features sorted by their score: [(0.7276, ‘LSTAT’), (0.5675, ‘RM’), (0.0867, ‘DIS’), (0.0407, ‘NOX’), (0.0351, ‘CRIM’), (0.0233, ‘PTRATIO’), (0.0168, ‘TAX’), (0.0122, ‘AGE’), (0.005, ‘B’), (0.0048, ‘INDUS’), (0.0043, ‘RAD’), (0.0004, ‘ZN’), (0.0001, ‘CHAS’)]

    在这个例子当中,LSTAT和RM这两个特征对模型的性能有着很大的影响,打乱这两个特征的特征值使得模型的性能下降了73%和57%。注意,尽管这些我们是在所有特征上进行了训练得到了模型,然后才得到了每个特征的重要性测试,这并不意味着我们扔掉某个或者某些重要特征后模型的性能就一定会下降很多,因为即便某个特征删掉之后,其关联特征一样可以发挥作用,让模型性能基本上不变。

    5 两种顶层特征选择算法

    之所以叫做顶层,是因为他们都是建立在基于模型的特征选择方法基础之上的,例如回归和SVM,在不同的子集上建立模型,然后汇总最终确定特征得分。

    5.1 稳定性选择 Stability selection

    稳定性选择是一种基于二次抽样和选择算法相结合较新的方法,选择算法可以是回归、SVM或其他类似的方法。它的主要思想是在不同的数据子集和特征子集上运行特征选择算法,不断的重复,最终汇总特征选择结果,比如可以统计某个特征被认为是重要特征的频率(被选为重要特征的次数除以它所在的子集被测试的次数)。理想情况下,重要特征的得分会接近100%。稍微弱一点的特征得分会是非0的数,而最无用的特征得分将会接近于0。

    sklearn在 随机lasso 和 随机逻辑回归 中有对稳定性选择的实现。

    from sklearn.linear_model import RandomizedLasso
    from sklearn.datasets import load_boston
    boston = load_boston()
    
    #using the Boston housing data. 
    #Data gets scaled automatically by sklearn's implementation
    X = boston["data"]
    Y = boston["target"]
    names = boston["feature_names"]
    
    rlasso = RandomizedLasso(alpha=0.025)
    rlasso.fit(X, Y)
    
    print "Features sorted by their score:"
    print sorted(zip(map(lambda x: round(x, 4), rlasso.scores_), 
                     names), reverse=True)

    Features sorted by their score: [(1.0, ‘RM’), (1.0, ‘PTRATIO’), (1.0, ‘LSTAT’), (0.62, ‘CHAS’), (0.595, ‘B’), (0.39, ‘TAX’), (0.385, ‘CRIM’), (0.25, ‘DIS’), (0.22, ‘NOX’), (0.125, ‘INDUS’), (0.045, ‘ZN’), (0.02, ‘RAD’), (0.015, ‘AGE’)]

    在上边这个例子当中,最高的3个特征得分是1.0,这表示他们总会被选作有用的特征(当然,得分会收到正则化参数alpha的影响,但是sklearn的随机lasso能够自动选择最优的alpha)。接下来的几个特征得分就开始下降,但是下降的不是特别急剧,这跟纯lasso的方法和随机森林的结果不一样。能够看出稳定性选择对于克服过拟合和对数据理解来说都是有帮助的:总的来说,好的特征不会因为有相似的特征、关联特征而得分为0,这跟Lasso是不同的。对于特征选择任务,在许多数据集和环境下,稳定性选择往往是性能最好的方法之一。

    5.2 递归特征消除 Recursive feature elimination (RFE)

    递归特征消除的主要思想是反复的构建模型(如SVM或者回归模型)然后选出最好的(或者最差的)的特征(可以根据系数来选),把选出来的特征放到一遍,然后在剩余的特征上重复这个过程,直到所有特征都遍历了。这个过程中特征被消除的次序就是特征的排序。因此,这是一种寻找最优特征子集的贪心算法。

    RFE的稳定性很大程度上取决于在迭代的时候底层用哪种模型。例如,假如RFE采用的普通的回归,没有经过正则化的回归是不稳定的,那么RFE就是不稳定的;假如采用的是Ridge,而用Ridge正则化的回归是稳定的,那么RFE就是稳定的。

    Sklearn提供了 RFE 包,可以用于特征消除,还提供了 RFECV ,可以通过交叉验证来对的特征进行排序。

    from sklearn.feature_selection import RFE
    from sklearn.linear_model import LinearRegression
    
    boston = load_boston()
    X = boston["data"]
    Y = boston["target"]
    names = boston["feature_names"]
    
    #use linear regression as the model
    lr = LinearRegression()
    #rank all features, i.e continue the elimination until the last one
    rfe = RFE(lr, n_features_to_select=1)
    rfe.fit(X,Y)
    
    print "Features sorted by their rank:"
    print sorted(zip(map(lambda x: round(x, 4), rfe.ranking_), names))

    Features sorted by their rank: [(1.0, ‘NOX’), (2.0, ‘RM’), (3.0, ‘CHAS’), (4.0, ‘PTRATIO’), (5.0, ‘DIS’), (6.0, ‘LSTAT’), (7.0, ‘RAD’), (8.0, ‘CRIM’), (9.0, ‘INDUS’), (10.0, ‘ZN’), (11.0, ‘TAX’), (12.0, ‘B’), (13.0, ‘AGE’)]

    6 一个完整的例子

    下面将本文所有提到的方法进行实验对比,数据集采用Friedman #1 回归数据( 这篇论文 中的数据)。数据是用这个公式产生的:

    X1到X5是由 单变量分布 生成的,e是 标准正态变量 N(0,1)。另外,原始的数据集中含有5个噪音变量 X5,…,X10,跟响应变量是独立的。我们增加了4个额外的变量X11,…X14,分别是X1,…,X4的关联变量,通过f(x)=x+N(0,0.01)生成,这将产生大于0.999的关联系数。这样生成的数据能够体现出不同的特征排序方法应对关联特征时的表现。

    接下来将会在上述数据上运行所有的特征选择方法,并且将每种方法给出的得分进行归一化,让取值都落在0-1之间。对于RFE来说,由于它给出的是顺序而不是得分,我们将最好的5个的得分定为1,其他的特征的得分均匀的分布在0-1之间。

    from sklearn.datasets import load_boston
    from sklearn.linear_model import (LinearRegression, Ridge, 
    								  Lasso, RandomizedLasso)
    from sklearn.feature_selection import RFE, f_regression
    from sklearn.preprocessing import MinMaxScaler
    from sklearn.ensemble import RandomForestRegressor
    import numpy as np
    from minepy import MINE
    np.random.seed(0)
    size = 750
    X = np.random.uniform(0, 1, (size, 14))
    #"Friedamn #1” regression problem
    Y = (10 * np.sin(np.pi*X[:,0]*X[:,1]) + 20*(X[:,2] - .5)**2 +
    	 10*X[:,3] + 5*X[:,4] + np.random.normal(0,1))
    #Add 3 additional correlated variables (correlated with X1-X3)
    X[:,10:] = X[:,:4] + np.random.normal(0, .025, (size,4))
    names = ["x%s" % i for i in range(1,15)]
    ranks = {}
    def rank_to_dict(ranks, names, order=1):
    	minmax = MinMaxScaler()
    	ranks = minmax.fit_transform(order*np.array([ranks]).T).T[0]
    	ranks = map(lambda x: round(x, 2), ranks)
    	return dict(zip(names, ranks ))
    lr = LinearRegression(normalize=True)
    lr.fit(X, Y)
    ranks["Linear reg"] = rank_to_dict(np.abs(lr.coef_), names)
    ridge = Ridge(alpha=7)
    ridge.fit(X, Y)
    ranks["Ridge"] = rank_to_dict(np.abs(ridge.coef_), names)
    lasso = Lasso(alpha=.05)
    lasso.fit(X, Y)
    ranks["Lasso"] = rank_to_dict(np.abs(lasso.coef_), names)
    rlasso = RandomizedLasso(alpha=0.04)
    rlasso.fit(X, Y)
    ranks["Stability"] = rank_to_dict(np.abs(rlasso.scores_), names)
    #stop the search when 5 features are left (they will get equal scores)
    rfe = RFE(lr, n_features_to_select=5)
    rfe.fit(X,Y)
    ranks["RFE"] = rank_to_dict(map(float, rfe.ranking_), names, order=-1)
    rf = RandomForestRegressor()
    rf.fit(X,Y)
    ranks["RF"] = rank_to_dict(rf.feature_importances_, names)
    f, pval  = f_regression(X, Y, center=True)
    ranks["Corr."] = rank_to_dict(f, names)
    mine = MINE()
    mic_scores = []
    for i in range(X.shape[1]):
    	mine.compute_score(X[:,i], Y)
    	m = mine.mic()
    	mic_scores.append(m)
    ranks["MIC"] = rank_to_dict(mic_scores, names)
    r = {}
    for name in names:
    	r[name] = round(np.mean([ranks[method][name] 
    							 for method in ranks.keys()]), 2)
    methods = sorted(ranks.keys())
    ranks["Mean"] = r
    methods.append("Mean")
    print "\t%s" % "\t".join(methods)
    for name in names:
    	print "%s\t%s" % (name, "\t".join(map(str, 
    						 [ranks[method][name] for method in methods])))
    

    从以上结果中可以找到一些有趣的发现:

    特征之间存在 线性关联 关系,每个特征都是独立评价的,因此X1,…X4的得分和X11,…X14的得分非常接近,而噪音特征X5,…,X10正如预期的那样和响应变量之间几乎没有关系。由于变量X3是二次的,因此X3和响应变量之间看不出有关系(除了MIC之外,其他方法都找不到关系)。这种方法能够衡量出特征和响应变量之间的线性关系,但若想选出优质特征来提升模型的泛化能力,这种方法就不是特别给力了,因为所有的优质特征都不可避免的会被挑出来两次。

    Lasso能够挑出一些优质特征,同时让其他特征的系数趋于0。当如需要减少特征数的时候它很有用,但是对于数据理解来说不是很好用。(例如在结果表中,X11,X12,X13的得分都是0,好像他们跟输出变量之间没有很强的联系,但实际上不是这样的)

    MIC对特征一视同仁,这一点上和关联系数有点像,另外,它能够找出X3和响应变量之间的非线性关系。

    随机森林基于不纯度的排序结果非常鲜明,在得分最高的几个特征之后的特征,得分急剧的下降。从表中可以看到,得分第三的特征比第一的小4倍。而其他的特征选择算法就没有下降的这么剧烈。

    Ridge将回归系数均匀的分摊到各个关联变量上,从表中可以看出,X11,…,X14和X1,…,X4的得分非常接近。

    稳定性选择常常是一种既能够有助于理解数据又能够挑出优质特征的这种选择,在结果表中就能很好的看出。像Lasso一样,它能找到那些性能比较好的特征(X1,X2,X4,X5),同时,与这些特征关联度很强的变量也得到了较高的得分。

    总结

    1. 对于理解数据、数据的结构、特点来说,单变量特征选择是个非常好的选择。尽管可以用它对特征进行排序来优化模型,但由于它不能发现冗余(例如假如一个特征子集,其中的特征之间具有很强的关联,那么从中选择最优的特征时就很难考虑到冗余的问题)。
    2. 正则化的线性模型对于特征理解和特征选择来说是非常强大的工具。L1正则化能够生成稀疏的模型,对于选择特征子集来说非常有用;相比起L1正则化,L2正则化的表现更加稳定,由于有用的特征往往对应系数非零,因此L2正则化对于数据的理解来说很合适。由于响应变量和特征之间往往是非线性关系,可以采用basis expansion的方式将特征转换到一个更加合适的空间当中,在此基础上再考虑运用简单的线性模型。
    3. 随机森林是一种非常流行的特征选择方法,它易于使用,一般不需要feature engineering、调参等繁琐的步骤,并且很多工具包都提供了平均不纯度下降方法。它的两个主要问题,1是重要的特征有可能得分很低(关联特征问题),2是这种方法对特征变量类别多的特征越有利(偏向问题)。尽管如此,这种方法仍然非常值得在你的应用中试一试。
    4. 特征选择在很多机器学习和数据挖掘场景中都是非常有用的。在使用的时候要弄清楚自己的目标是什么,然后找到哪种方法适用于自己的任务。当选择最优特征以提升模型性能的时候,可以采用交叉验证的方法来验证某种方法是否比其他方法要好。当用特征选择的方法来理解数据的时候要留心,特征选择模型的稳定性非常重要,稳定性差的模型很容易就会导致错误的结论。对数据进行二次采样然后在子集上运行特征选择算法能够有所帮助,如果在各个子集上的结果是一致的,那就可以说在这个数据集上得出来的结论是可信的,可以用这种特征选择模型的结果来理解数据。

    Tips

    什么是 卡方检验 ?用方差来衡量某个观测频率和理论频率之间差异性的方法

    什么是 皮尔森卡方检验 ?这是一种最常用的卡方检验方法,它有两个用途:1是计算某个变量对某种分布的拟合程度,2是根据两个观测变量的 Contingency table 来计算这两个变量是否是独立的。主要有三个步骤:第一步用方差和的方式来计算观测频率和理论频率之间卡方值;第二步算出卡方检验的自由度(行数-1乘以列数-1);第三步比较卡方值和对应自由度的卡方分布,判断显著性。

    什么是 p-value ?简单地说,p-value就是为了验证假设和实际之间一致性的统计学意义的值,即假设检验。有些地方叫右尾概率,根据卡方值和自由度可以算出一个固定的p-value,

    什么是 响应变量(response value) ?简单地说,模型的输入叫做explanatroy variables,模型的输出叫做response variables,其实就是要验证该特征对结果造成了什么样的影响

    什么是 统计能力(statistical power) ?

    什么是 度量(metric) ?

    什么是 零假设(null hypothesis) ?在相关性检验中,一般会取“两者之间无关联”作为零假设,而在独立性检验中,一般会取“两者之间是独立”作为零假设。与零假设相对的是备择假设(对立假设),即希望证明是正确的另一种可能。

    什么是 多重共线性 ?

    什么是 grid search ?

    That’s it

    References

    1. http://blog.datadive.net/selecting-good-features-part-i-univariate-selection/
    2. http://blog.datadive.net/selecting-good-features-part-ii-linear-models-and-regularization/
    3. http://scikit-learn.org/stable/modules/feature_selection.html#univariate-feature-selection
    4. http://www.quora.com/What-are-some-feature-selection-methods
    5. http://www.quora.com/What-are-some-feature-selection-algorithms
    6. http://www.quora.com/What-are-some-feature-selection-methods-for-SVMs
    7. http://www.quora.com/What-is-the-difference-between-principal-component-analysis-PCA-and-feature-selection-in-machine-learning-Is-PCA-a-means-of-feature-selection

    文章出处: http://chaoslog.com/te-zheng-xuan-ze.html

    展开全文
  • 一,cmake 变量引用的方式: 前面我们已经提到了,使用${}进行变量的引用。在 IF 等语句中,是直接使用变量名而不通过${}取值 二,cmake 自定义变量的方式: 主要有隐式定义和显式定义两种,前面举了一个隐式定义的例子,...
  • 常用的特征选择方法

    万次阅读 多人点赞 2018-09-20 17:54:04
    常用的特征选择方法 链接:https://www.zhihu.com/question/28641663/answer/110165221 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者:城东 链接:特征工程到底是什么? - 城东...
  • 下面列举一些最常用方法 1、根据阈值过滤掉方差小的变量。 2、通过计算变量与标签的相关系数,留下相关性高的特征。 3、根据决策树或者随机森林,选择重要程度高的特征。 4、利用PCA等算法,对数据进行变换,...
  • 结合Scikit-learn介绍几种常用的特征选择方法

    万次阅读 多人点赞 2016-06-07 22:51:32
    特征选择主要有两个功能:减少特征数量、降维,使模型泛化能力更强,减少过拟合增强对特征和特征值之间的理解拿到数据集,一个特征选择方法,往往很难同时完成这两个目的。通常情况下,我们经常不管三七二十一,选择...
  • Nginx系列之常用内置变量

    千次阅读 2017-02-13 21:26:25
    1、前言之前在大学里学PHP时,一直用的是LAMP/WAMP架构,对nginx不甚了解,大概一年前给新公司搭建后端环境时,选择了LNMP架构,...该文章主要总结了nginx常用的内置变量的含义,熟悉这些变量有助于熟练编写nginx配置文
  • 问题 在开发中碰到一个需要编译时拷贝文件到out指定目录...如果大家没有找到对应的变量名的值,可以尝试如下方法选择一个模块的android.mk文件,在里面添加: $(warning "TARGET_EXTRA_DATA_PATH is $(TARGET_EXTRA
  • 【机器学习】特征选择常用方法 整理

    万次阅读 多人点赞 2018-06-14 19:36:12
    特征选择 在实际工程中,对于特征变量的选取,往往是基于业务经验,也就是所谓你的先验知识。 现在数据的特征维度很多,而能作为训练集的样本量却往往远小于特征数量(如...变量排序就是一种典型的过滤式方法,...
  • 数据挖掘——变量选择

    千次阅读 2016-07-21 14:45:22
    大多数数据集需要我们对之进行“清洗”工作,包括数据转换、数据离散化、数据缺失替换和数据异常点处理 在实际的挖掘分析中,过多的变量对模型精度的提升十分有限,但是对于挖掘速度的影响...特征规约方法: 主成
  • 写在前面:前段时间正好用到特征选择的知识,有幸读到这篇文章,本文也主要参考这篇文章写成,但与原文章有不同之处:第一、纠正了原始文章中的代码错误,使其能用python3.5正常运行;第二、增加了一些新的特征选择...
  • /*声明变量选择结构*/ var a=1; switch (a){ case "1": alert("第一项选择"); break; case "2": alert("第二项选择"); break; default: alert("没有对应的选项"); ...
  • 数据分析的统计方法选择小结(变量之间的关联性分析) 一、两个变量之间的关联性分析 1.两个变量均为连续型变量 1)小样本并且两个变量服从双正态分布,则用Pearson相关系数做统计分析 2)大样本或两个变量不服从双...
  • kettle变量常用备注

    千次阅读 2015-09-28 23:10:29
    我以前使用Talend作为ETL工具,但总感觉非常麻烦,特别是在变量方面,而且网上也没多少资料,我的文章反倒被看了不少;书也没有。后来接触kettle,感觉上手非常快,但和talend比起来,有一个缺点,就是容易死,而且...
  • 倾向值分析(协变量选择

    千次阅读 2019-06-25 15:01:42
    Hirano 和 Imbens 基于预设的临界t值来设定预测变量方法 1.逻辑回归:逻辑回归虽然带有回归字样,但是逻辑回归属于分类算法。逻辑回归可以进行多分类操作,但由逻辑回归算法本身性质决定其更常用于二分类。 a.逻辑...
  • 二分类问题特征选择常用两个方法  2014-6-25   (1)互信息。值越大,相关性越强   w是特征,t是目标。反应的是特征出现和不出现对目标值的影响。     (2) 卡方检验   其中 A = N(w = 1,...
  • 前面提到,这里介绍的变量筛选的方法全部是基于《An Introduction to Statistical Learning with R》书中的方法,所以这里先开始介绍课本上的变量筛选方法,然后再进行延伸。 课本上数据降维方法 标准的...
  • 网上太多关于Android studio的快捷大全,但是有时候文章的...Ctrl + Alt + F:可将局部变量变为成员变量, 相当于eclipse的Ctrl + 1部分功能 Alt +回车:工程快速修复, 相当于eclipse的Alt + / 部分功能 Ctrl + Shift
  • 多数情况下,我们的被解释变量都是连续变量,但也有些情况下,我们会对分类变量感兴趣,比如,出门时选择何种交通工具?大学毕业时是否继续读研?等等。那么,此时,该用何种模型来分析比较合适呢? 分类变量可...
  • 在调试MFC程序时,我们经常需要查看特定位置变量的输出值。或者在某特定条件执行时,给出一个输出标识。 一般来说,有3种方法: 1)调用TRACE(LPCTSTR lpszFormat, ...)函数  在MFC中使用TRACE函数来打印输出结果...
  • ES6常用数组方法

    千次阅读 2018-02-01 17:19:47
    今天很高兴,连续收到 ...接来写几个我平时在项目使用的最常用的几个优化的方法~ 1- 数组去重 var arr = [1,2,3,4,3,4]; var arr2 = [...new Set(arr)]; 这个时候arr2就是去重后的数组~ 2- 交换两个变量
  • 前言:之前的文章(高维数据中特征筛选方法的思考总结——单变量分析筛选法)中,对单变量分析筛选变量进行了初步考量,本文将进一步总结多变量分析筛选法。由于本文多处摘录网上的博客,只是进行了归纳整理,因此...
  • 安卓逆向之----常用动态调试方法 一. 前言 逆向分析中常用的分析方法有:静态分析、动态调试、HOOK等。动态调试的好处是:1)可以在调试的过程中知道参数或者局部变量的值以及变化过程,2)可以快速履清代码运行的...
  • 方法区:在java的虚拟机中有一块专门用来存放已经加载的类信息、常量、静态变量以及方法代码的内存区域, 常量池:常量池是方法区的一部分,主要用来存放常量和类中的符号引用等信息。 堆区:用于存放类的对象...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 438,329
精华内容 175,331
关键字:

常用的变量选择方法