如何优雅地将二项逻辑斯蒂回归模型推广为多项逻辑斯蒂回归模型?

《统计学习方法》中的第六章提到了多项逻辑斯蒂回归,只是说有二项逻辑斯蒂回归推广而来,那具体是怎么推广而来的呢?
关注者
20
被浏览
9,961
登录后你可以
不限量看优质回答私信答主深度交流精彩内容一键收藏

逻辑斯蒂回归本身只能用于二分类问题,如果实际情况是多分类的,那么就需要对模型进行一些改动,以下是三种比较常用的将逻辑斯蒂回归用于多分类的方法:

One vs One

OvO 的方法就是将多个类别中抽出来两个类别,然后将对应的样本输入到一个逻辑斯蒂回归的模型中,学到一个对这两个类别的分类器,然后重复以上的步骤,直到所有类别两两之间都存在一个分类器。

假设存在四个类别,那么分类器的数量为6个,表格如下:

0123
00 vs 10 vs 20 vs 3
11 vs 21 vs 3
22 vs 3
3

分类器的数量直接使用 C^k_2 就可以了,k 代表类别的数量。

在预测时,需要运行每一个模型,然后记录每个分类器的预测结果,也就是每个分类器都进行一次投票,取获得票数最多的那个类别就是最终的多分类的结果。

比如在以上的例子中,6个分类器有3个投票给了类别3,1个投票给了类别2,1个投票给类别1,最后一个投票给类别0,那么久取类别3为最终预测结果。

OvO 的方法中,当需要预测的类别变得很多的时候,那么我们需要进行训练的分类器也变得很多了,这一方面提高了训练开销,但在另一方面,每一个训练器中,因为只需要输入两个类别对应的训练样本即可,这样就又减少了开销。

从预测的角度考虑,这种方式需要运行的分类器非常多,而无法降低每个分类器的预测时间复杂度,因此预测的开销较大。

以下是代码实现:

from sklearn.linear_model import LogisticRegression
import numpy as np
import pandas as pd
import warnings
warnings.filterwarnings('ignore')

def load_data():
    train = pd.read_csv('D:/mnist/mnist_train.csv')
    test = pd.read_csv('D:/mnist/mnist_test.csv')

    columns = list(set(train.columns) - {'label'})
    train[columns] = train[columns] / 255
    test[columns] = test[columns] / 255
    return train, test

def train_models(train):
    models = {}
    columns = list(set(train.columns) - {'label'})
    
    # train models
    for i in range(10):
        for j in range(i + 1, 10):

            cond = train['label'].isin([i, j])  # get train_data with labels in (i,j)
            train_data = train[cond]

            train_data_ij = train_data[columns]  # train_data
            train_label_ij = train_data[['label']]  # train_label

            lr = LogisticRegression(random_state=np.random.randint(0, 100))  # train model
            lr.fit(train_data_ij,train_label_ij)
            models['{}_{}'.format(i,j)] = lr  # save models
            
    return models

def predict_test(models, test):
    result = []
    
    # predict test data
    for i in range(10):
        for j in range(i + 1, 10):
            model_name = '{}_{}'.format(i, j)  # get model
            predict = models[model_name].predict_proba(test[columns])  # predict
            result.append(np.where(predict[:,0] > predict[:,1], i, j))  # save result
            
    return result

def check_result(result):
    
    result = np.vstack(result)
    cnt = 0
    
    # check each test data
    for i in range(result.shape[1]):
        p = Counter(result[:,i]).most_common(1)[0][0]  # get the predict result with the highest number of vote
        if p == test['label'].iloc[i]:
            cnt += 1

    acc = cnt / len(test)  # calculate accuracy
    return acc

train, test = load_data()
models = train_models(train)
result = predict_test(models, test)
acc = check_result(result)
print(acc)
# 0.9446

One vs All

OvA 的方法就是从所有类别中依次选择一个类别作为1,其他所有类别作为0,来训练分类器,因此分类器的数量要比 OvO 的数量少得多。

作为1的类别作为0的类别
01;2;3
10;2;3
20;1;3
30;1;2

通过以上例子可以看到,分类器的数量实际上就是类别的数量,也就是k。

虽然分类器的数量下降了,但是对于每一个分类器来说,训练时需要将所有的训练数据全部输入进去进行训练,因此每一个分类器的训练时间复杂度是高于 OvO 的。

从预测的方面来说,因为分类器的数量较少,而每个分类器的预测时间复杂度不变,因此总体的预测时间复杂度小于 OvA。

预测结果的确定,是根据每个分类器对其对应的类别1的概率进行排序,选择概率最高的那个类别作为最终的预测类别。

以下是代码实现:

from sklearn.linear_model import LogisticRegression
import numpy as np
import pandas as pd
import warnings
warnings.filterwarnings('ignore')

def load_data():
    train = pd.read_csv('D:/mnist/mnist_train.csv')
    test = pd.read_csv('D:/mnist/mnist_test.csv')

    columns = list(set(train.columns) - {'label'})
    train[columns] = train[columns] / 255
    test[columns] = test[columns] / 255
    return train, test

def train_models(train):
    models = {}
    columns = set(train.columns) - {'label'}
    
    # train models
    for i in range(10):
        
        # set the label == i to 1 and the others to 0
        train_data = train.copy()
        cond = train_data['label'] == i
        train_data.loc[cond, 'label'] = 1
        train_data.loc[~cond, 'label'] = 0

        train_data_i = train_data[columns]  # train_data
        train_label_i = train_data[['label']]  # train_label

        lr = LogisticRegression(random_state=np.random.randint(0, 100))  # train model
        lr.fit(train_data_i,train_label_i)
        models[i] = lr  # save models
            
    return models

def predict_test(models, test):
    result = []
    columns = set(train.columns) - {'label'}
    
    # predict test data
    for i in range(10):
        predict = models[i].predict_proba(test[columns])  # predict
        result.append(predict[:,1])  # only save the result of class 1 is enough
            
    return result

def check_result(result):
    
    result = np.vstack(result)
    cnt = 0
    
    # check each test data
    for i in range(result.shape[1]):
        p = result[:,i]
        p = np.where(p == p.max())[0][0]  # select the predict class with the highest probability
        if p == test['label'].iloc[i]:
            cnt += 1

    acc = cnt / len(test)  # calculate accuracy
    return acc

train, test = load_data()
models = train_models(train)
result = predict_test(models, test)
acc = check_result(result)
print(acc)
# 0.9201

从sigmoid函数到softmax函数的推导

第三种方式,我们可以直接从数学上使用 softmax 函数来得到最终的结果,而 softmax 函数与 sigmoid 函数有着密不可分的关系,它是 sigmoid 函数的更一般化的表示,而 sigmoid 函数是 softmax 函数的一个特殊情况。

我们知道 logit 函数代表的是某件事发生的几率,其形式为:

logit(P) = log\frac{P}{1-P} = wx\\

分子代表的是一件事发生的概率,分母代表这件事以外的事发生的概率,两者的和为1。

当我们面对的情况是多个分类时,可以让 k-1 个类别分别对剩下的那个类别做回归,即得到 k-1 个 logit 公式:

log\frac{P(Y=c_i|x)}{P(Y=c_k|x)} = w_ix (i=1,2,...,k-1)\\

然后对这些公式稍微变个型,可得:

P(Y=c_i|x) = P(Y=c_k|x)exp(w_ix) (i=1,2,...,k-1)\\

由于我们知道所有类别的可能性相加为1,因此可以得到:

P(Y=c_k|x) = 1-\sum_{i=1}^{k-1}P(Y=c_i|x)\\ = 1-\sum_{i=1}^{k-1}P(Y=c_k|x)exp(w_ix)\\

通过解上面的方程,可以得到关于某个样本被分类到类别 c_k 的概率:

P(Y=c_k|x) = 1-\sum_{i=1}^{k-1}P(Y=c_k|x)exp(w_ix)\\ P(Y=c_k|x)+\sum_{i=1}^{k-1}P(Y=c_k|x)exp(w_ix)=1\\ P(Y=c_k|x)+P(Y=c_k|x)\sum_{i=1}^{k-1}exp(w_ix)=1\\ P(Y=c_k|x)=\frac{1}{1+\sum_{i=1}^{k-1}exp(w_ix)}

这就是我们所了解的 softmax 函数了。


其他面试题解答:

面试题解答1:为什么线性回归要求假设因变量符合正态分布 - 知乎 (zhihu.com)

面试题解答2:各种回归模型与广义线性模型的关系 - 知乎 (zhihu.com)

面试题解答3:如何用方差膨胀因子判断多重共线性 - 知乎 (zhihu.com)

面试题解答4:逻辑斯蒂回归是否可以使用其他的函数替代 sigmoid 函数 - 知乎 (zhihu.com)

面试题解答5:特征存在多重共线性,有哪些解决方法? - 知乎 (zhihu.com)

面试题解答6:逻辑斯蒂回归为什么使用交叉熵而不是MSE - 知乎 (zhihu.com)


数据分析,机器学习社群正式启动~

需要学习资料,想要加入社群均可私信~

在这里我会不定期分享各种数据分析相关资源,技能学习技巧和经验等等~

详情私信,一起进步吧!


写于上海 2021-01-16