一、算法思想

1、特征选择

特征选择是去除无关紧要庸余的特征,仍然还保留其他原始特征,从而获得特征子集,从而以最小的性能损失更好地描述给出的问题。

特征选择方法可以分为三个系列:过滤式选择、包裹式选择和嵌入式选择的方法 。

本文介绍的卡方检验即为过滤式的特征选择算法。

关于过滤式的特征算法系列,可参考我的其他文章。
特征选择之互信息
特征选择之Fisher Score

2、卡方检验

卡方检验介绍

卡方是由英语"Chi_Square"音译而来,Chi是希腊字母X(读作kai),所以卡方又能写成 x 2 x^2 x2

卡方检验是一种常用的假设检验方法,它的无效假设 H 0 H_0 H0是:观察频数与期望频数没有差别。所谓无效假设又称零假设(null hypothesis),一般是有意推翻的、希望证明其错误的假设。
如,我们希望证明两个明显相关的变量具有相关性,我们可以做出无效假设 H 0 H_0 H0:假设二者无关。

举个例子,我们希望证明新型冠状病毒与最近的时事相关。我们可以做出无效假设,二者无关,即最近的时事没有新型冠状病毒的信息。然后我们通过我们的收集数据分析计算证明得出假设失效。这样就证明了这两者具有相关性。

检验过程

卡方检验通常用来检验两个变量之间有没有关系。
那么如何检验呢?

首先我们做出预测,得到期望频数。观察实际情况,得到观察频数。

检验过程如下:观察频数与期望频数通过如下公式可得到 x 2 x^2 x2值,它表示观察值与理论值之间的偏差程度:
X 2 = ∑ (  observed  −  expected  ) 2  expected  X^{2}=\sum \frac{(\text { observed }-\text { expected })^{2}}{\text { expected }} X2= expected ( observed  expected )2
observed为观察频数,expected为期望频数。

另外我们还引入另一个概念,自由度。自由度指的是不受限制的变量的个数。一般情况下,自由度=(行数-1)*(列数-1)。如下图的一个例子:

投骰子计算自由度的列子

最后由 x 2 x^2 x2的值和自由度可以查表得到p值(大学课本上就是这样)。当然也可以通过积分卡方函数得到精确结果,但是这比较复杂。

如果p值很小,观察值与理论值偏离程度太大。说简单点,p越大,观察值与理论值偏离程度越小。两个变量间越没有差别,它俩具有很强的“相关性”

如果还不太懂,可参考以下链接内的视频。↓
参考视频

特征选择中的卡方检验

由上文所述, 特征选择是去除无关紧要庸余的特征,仍然还保留其他原始特征,从而获得特征子集,从而以最小的性能损失更好地描述给出的问题。

这里我们将样本中的特征对应观察值,标签类对应理论值。如果某个特征对应属于某个类别,那么他与标签类“越没有差别”。或者反过来说,如果某个特征与某个标签类“越没有差别”,那么我们说这个特征对应属于这个标签类。

例如瓜的类别有好坏之分,其某个特征表面光滑,与“好“”这个类“越没有差别”,我们就说这个表面光滑这个特征属于好瓜这个标签。

这里说的“越没有差别”,即是上文说的p值越大,观察值与理论值偏离程度越小。两个变量间越没有差别,它俩具有很强的“相关性”

因此可以根据不同特征与标签类通过卡方检验得到的p值的大小来排名,排名越高,说明该特征更有可能属于标签类。

二、代码实现

1、python调用scipy包

python里面的scipy包里有卡方检验的函数:stats.chisquare()

输入参数为两个列表obs,exp。列表元素为其相应的频数。

from scipy import stats
obs=[8,7,7]
exp=[8,8,8]
print(stats.chisquare(obs,exp,))

结果:

Power_divergenceResult(statistic=0.25,pvalue=0.8824969025845955)

其中的statistics就是卡方值,可这样求出stats.chisquare(obs,exp,)[0]
pvalue即是p值,stats.chisquare(obs,exp,)[1]

2、自己实现python代码

话不多说,上代码。



import math
import numpy as np

# 自己实现chisquare,参数为两个列表obs,exp,返回为包含卡方值和p值的列表
#eg:obs=[8,7,7] ,exp=[8,8,8]
#return [0.25, 0.8824969025845955]
def my_chisquare(obs, exp):
    # 将列表转化为numpy.ndarray类型
    obs = np.atleast_1d(np.asanyarray(obs))
    exp = np.atleast_1d(np.asanyarray(exp))
    if obs.size != exp.size:
        print('The size of the obs and the exp  array is not equal')
        exit()

    # 得到ndarray类型,得到各项的理论与观察的相对偏离距离,相加即为卡方值
    terms = (obs - exp) ** 2 / exp
    # 求得卡方值,numpy.float64类型
    stat = terms.sum(axis=0)
    # 计算obs,exp的维度
    num_obs = terms.size
    # 调用自己写的求p值的函数,得到p值
    p = chisqr2pValue(num_obs - 1, stat)
    chisquare_list = []
    chisquare_list.append(stat)
    chisquare_list.append(p)
    return chisquare_list

# 用斯特林公式来近似求伽马函数(卡方检验)
def getApproxGamma(n):
    RECIP_E = 0.36787944117144232159552377016147
    TWOPI = 6.283185307179586476925286766559
    d = 1.0 / (10.0 * n)
    d = 1.0 / ((12 * n) - d)
    d = (d + n) * RECIP_E
    d = math.pow(d, n)
    d = d * math.sqrt(TWOPI / n)
    return d


# 不完全伽马函数中需要调用的函数(卡方检验)
def KM(s, z):
    _sum = 1.0
    log_nom = math.log(1.0)
    log_denom = math.log(1.0)
    log_s = math.log(s)
    log_z = math.log(z)
    for i in range(1000):
        log_nom += log_z
        s = s + 1
        log_s = math.log(s)
        log_denom += log_s
        log_sum = log_nom - log_denom
        log_sum = float(log_sum)
        _sum += math.exp(log_sum)

    return _sum


# 不完全伽马函数,采用计算其log值(卡方检验)
def log_igf(s, z):
    if z < 0.0:
        return 0.0
    sc = float((math.log(z) * s) - z - math.log(s))
    k = float(KM(s, z))
    return math.log(k) + sc

#卡方检验求p值
# dof是自由度,chi_squared为卡方值,该函数实现知道自由度和卡方值求p值
# 核心是用不完全伽马函数除以伽马函数,两者都采用近似函数求解
# 参见https://blog.csdn.net/idatamining/article/details/8565042
def chisqr2pValue(dof, chi_squared):
    dof = int(dof)
    chi_squared = float(chi_squared)
    if dof < 1 or chi_squared < 0:
        return 0.0
    k = float(dof) * 0.5
    v = chi_squared * 0.5
    # 自由度为2时
    if dof == 2:
        return math.exp(-1.0 * v)
    # 不完全伽马函数,采用计算其log值
    incompleteGamma = log_igf(k, v)
    # 如果过小或者无穷
    if math.exp(incompleteGamma) <= 1e-8 or math.exp(incompleteGamma) == float('inf'):
        return 1e-14

    # 完全伽马函数,用斯特林公式近似
    gamma = float(math.log(getApproxGamma(k)))
    incompleteGamma = incompleteGamma - gamma
    if math.exp(incompleteGamma) > 1:
        return 1e-14
    pvalue = float(1.0 - math.exp(incompleteGamma))
    return pvalue


obs1 = [8, 7, 7]
exp1 = [8, 8,8]
lst = my_chisquare(obs1, exp1)
print(lst)

主要是利用了近似函数来求p的值。主要公式如下:
F ( x ; k ) = γ ( k 2 , x 2 ) Γ ( k 2 ) = P ( k 2 , x 2 ) F(x ; k)=\frac{\gamma\left(\frac{k}{2}, \frac{x}{2}\right)}{\Gamma\left(\frac{k}{2}\right)}=P\left(\frac{k}{2}, \frac{x}{2}\right) F(x;k)=Γ(2k)γ(2k,2x)=P(2k,2x)

需要详细的数学公式可参考这个博客参考博客

最后注意,卡方检验中的期望值理论上不能为0,否则会使得卡方值为无穷大,无实际意义。读者输入的期望标签可以根据实际情况调整,避免出现0的情况。

希望本文对你有帮助!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐