美文网首页
LogisticRegression

LogisticRegression

作者: 乔一波一 | 来源:发表于2023-08-09 19:22 被阅读0次

    逻辑回归

    Logistic回归是众多分类算法中的一员。通常,Logistic回归用于二分类问题,例如预测明天是否会下雨。当然它也可以用于多分类问题,不过为了简单起见,本文暂先讨论二分类问题。首先,让我们来了解一下,什么是Logistic回归。
    参考

    1. 什么是回归?
      假设现在有一些数据点,我们利用一条直线对这些点进行拟合(该线称为最佳拟合直线),这个拟合过程就称作为回归
    2. 逻辑回归怎么分类?
      Logistic回归是分类方法,它利用的是Sigmoid函数值域在[0,1]这个特性。Logistic回归进行分类的主要思想是:根据现有数据拟合分类边界线然后划分分类阈值,以此实现分类。
      \mathrm{g}(\textbf{W})=\frac{1}{1+e^{-\textbf{W}^T\textbf{x}}}\\
      函数曲线如下图
      Sigmoid

    逻辑回归输出值的含义

    其实y=\textbf{W}^T\textbf{x}就是线性回归,使用Sigmoid将其输出激活到[0,1]表示样本\textbf{x}的类别为1的概率p(y=1|\textbf{x}):g(\textbf{W}),那么二分类中样本类别为0的概率p(y=0|\textbf{x}):1-g(\textbf{W})

    如何设计目标函数

    1. 目标函数的优化目标肯定是希望实际类别为1的预测也为1的概率大,而实际类别为0的预测为1的概率小;既是实际类别为1的样本g(\textbf{W})\rightarrow 1,实际类别为0的样本:g(\textbf{W})\rightarrow 0。这样逻辑回归分类器的判别准确率才高。
      目标函数如下:J(\textbf{W})=g(\textbf{W})^y(1-g(\textbf{W}))^{(1-y)}\\
      两边同时取对数
      \log{J(\textbf{W})} =y\log{g(\textbf{W})} +(1-y)\log{(1-g(\textbf{W}))}\\
      我们把优化的目标代入上式:当y=1时,J(\textbf{W})=g(\textbf{W})且希望J(\textbf{W})\rightarrow 1;当y=0J(\textbf{W})=1-g(\textbf{W})且希望J(\textbf{W})\rightarrow 0,两种情况下我们对g(\textbf{W})的希望是一致的,既g(\textbf{W})=\frac{1}{1+e^{-\textbf{W}^T\textbf{x}}} \rightarrow 1\\

      目标函数图像
    2. 这个目标函数,是对于一个样本而言的。给定一个样本,我们就可以通过这个目标函数求出样本所属类别的概率并计算目标函数值,而这个目标值越大越好,所以也就是求解这个目标函数的最大值。既然概率出来了,那么最大似然估计也该出场了。

    • 概率:是我们比较熟悉的,用于在已知某些参数的值的情况下预测某个事件被观测到的可能性。
    • 似然性:则是相关的过程,是已知观测到的结果,反过来推测什么样的参数才最有可能使当前观测到的结果发生。
    • 我们现在训练集中的带标签样本是已知的,需要根据这些已知的结果去反推参数\textbf{W},使得逻辑回归方程拟合训练集中的样本。而求解这个最有可能导致现象发生的过程就是极大似然估计。步骤如下:
      我们假定样本与样本之间相互独立,那么整个样本集生成的概率即为所有样本生成概率的乘积,此处省略了取对数的过程,便可得到如下公式:
      J(\textbf{W}) = \sum_{i=1}^n[y_i\log{g(\textbf{W})_i} +(1-y_i)\log{(1-g(\textbf{W})_i)}]\\
    1. 综上所述,满足J(\textbf{W})的最大的\textbf{W}值即是我们需要求解的模型。刚好梯度表示函数值增大最快的方向,那我们就沿着J(\textbf{W})梯度方向更新\textbf{W},也称为梯度上升法

    如何优化目标函数

    这里距离简单的一元二次曲线求极值;f(x)=-x^2+4x, 我们可以计算一阶导函数f'(x)=-2x+4,令其等于0解得x=2。但是当面对的是多元的复杂目标函数求极值问题时,可能通过通过直接求导函数计算驻点找极值的方式就比较困难。这时候可以使用梯度来优化目标函数的权重,使其逼近目标函数的最小值或极小值。我们使用梯度上升法模拟求解上式的最大值。

    代码和更新过程如下:

    learning_rate = 0.1
    
    def gradient_ascent_test():
        # Calculate the first derivative value
        def cal_derivative(x_old):
            return -2 * x_old + 4
    
        x_old = -1
        x_new = 0
        rec = [x_old, x_new]
        precision = 1e-8
        while x_new - x_old > precision:
            x_old = x_new
            # Update the unknown parameters along the positive direction of the gradient
            x_new = x_old + learning_rate * cal_derivative(x_old)
            rec.append(x_new)
        return x_new, rec
    
    def show_result(iter_records):
        x = np.linspace(start=-2, stop=4, num=100)
        y = -np.square(x) + 4 * x
        y_hat = [-np.square(s) + 4 * s for s in iter_records]
        plt.plot(x, y, color="blue", label="y=-x^2 + 4x")
        plt.scatter(iter_records, y_hat, color="red", label="iteration points")
        plt.xlabel("X")
        plt.ylabel("Y")
        plt.legend()
        plt.title(f"learning rate:{learning_rate}")
        plt.show()
    
    x_new, records = gradient_ascent_test()
    show_result(records)
    print(x_new)
    
    梯度上升法举例
    此时x= 1.9999999646630588, 已经十分逼近2了;

    计算J(\textbf{W})的偏导数

    首先这里代换了变量,方便计算
    h(\textbf{W})=\textbf{W}^T\textbf{x},~\mathrm{g}(h)=\frac{1}{1+e^{-h}}\\
    J(\textbf{W}) = \sum_{i=1}^n[y_i\log{g_i} +(1-y_i)\log{(1-g_i)}]\\
    链式求导法则:
    \frac{\partial{J}}{\partial{\textbf{W}_j}}=\sum_{i=1}^{n}\frac{\partial{J}}{\partial{g_i}}\frac{\partial{g_i}}{\partial{h_i}}\frac{\partial{h_i}}{\partial{\textbf{W}_j}}\\
    \frac{\partial{J}}{\partial{g_i}} = [\frac{y_i}{g_i}+\frac{(y_i-1)}{1-g_i}]\\
    \frac{\partial{g_i}}{\partial{h_i}} = \frac{e^{-h_i}}{(1+e^{-h_i})^2}=(\frac{1}{1+e^{-h_i}})(1-(\frac{1}{1+e^{-h_i}}))=g_i(1-g_i)\\
    \frac{\partial{h_i}}{\partial{\textbf{W}_j}} = \frac{\partial{(\textbf{W}_1*\textbf{x}_{i1}+\dots+\textbf{W}_j*\textbf{x}_{ij})}}{{\textbf{W}_j}}=\textbf{x}_{ij}\\
    综上所述:
    \frac{\partial{J}}{\partial{g_i}}\frac{\partial{g_i}}{\partial{h_i}}\frac{\partial{h_i}}{\partial{\textbf{W}_j}}= [\frac{y_i}{g_i}+\frac{(y_i-1)}{1-g_i}]*g_i(1-g_i)*\textbf{x}_{ij}=(y_i-g_i)*\textbf{x}_{ij}\\
    \frac{\partial{J}}{\partial{\textbf{W}_j}} = \sum_{i=1}^n(y_i-g_i)*\textbf{x}_{ij}\\
    因此,权重\textbf{W}_j的迭代公式
    \textbf{W}_j^{t+1}= \textbf{W}_j^{t}+\lambda*\sum_{i=1}^n(y_i-g_i)*\textbf{x}_{ij}\\
    \begin{bmatrix} \textbf{W}_1^{t+1} \\ \vdots \\ \textbf{W}_j^{t+1} \\ \end{bmatrix}= \begin{bmatrix} \textbf{W}_1^{t}+\lambda*\sum_{i=1}^n(y_i-g_i)*\textbf{x}_{ij} \\ \vdots \\ \textbf{W}_j^{t}+\lambda*\sum_{i=1}^n(y_i-g_i)*\textbf{x}_{ij} \\ \end{bmatrix}= \begin{bmatrix} \textbf{W}_1^t \\ \vdots \\ \textbf{W}_j^t \\ \end{bmatrix}+\lambda \begin{bmatrix} \textbf{x}_{\_1}\\ \vdots \\ \textbf{x}_{\_j}\\ \end{bmatrix} \begin{bmatrix} y_{1}-{\textbf{W}^t_1}^T\textbf{x}_1 \\ \vdots \\ y_{i}-{\textbf{W}^t_j}^T\textbf{x}_i \\ \end{bmatrix} \\
    最后权重\textbf{W}的更新公式记为:
    \textbf{W}^{t+1}=\textbf{W}^{t}+\lambda X^T(Y-X\textbf{W}^t)\\

    逻辑回归实战

    加载数据集,并可视化

    这里使用的数据集为二分类,每个样本包含两个特征

    def load_data(path):
        """
        load dataset from file
        """
        datas = []
        labels = []
        with open(path, mode="r") as f:
            for line in f.readlines():
                line_list = line.split()  # default split by wihtespace
                datas.append([1.0, float(line_list[0]), float(line_list[1])])
                labels.append(int(line_list[-1]))
        return np.array(datas), np.array(labels)
    
    def plot_dataset():
        """
        visualization distribution of dataset.
        """
        datas, labels = load_data("../dataset/testSet.txt")
        label_1_x, label_1_y = [], []
        label_0_x, label_0_y = [], []
        for i in range(len(labels)):
            if labels[i] == 1:
                label_1_x.append(datas[i, 1])
                label_1_y.append(datas[i, 2])
            else:
                label_0_x.append(datas[i, 1])
                label_0_y.append(datas[i, 2])
        plt.scatter(
            label_1_x, label_1_y, marker="s", color="red", alpha=0.5, label="label 1"
        )
        plt.scatter(
            label_0_x, label_0_y, marker="s", color="green", alpha=0.5, label="label 0"
        )
        plt.title("dataset distribution")
        plt.xlabel("feature 1")
        plt.ylabel("feature 2")
        plt.legend()
        plt.show()
    
    数据集分布

    训练更新权重\textbf{W}

    # sigmoid activation
    def sigmoid(x):
        return 1.0 / (1 + np.exp(-x))
    
    
    def grad_ascent_matric(datas, labels, iterations, lr):
        """
        Vectorized Gradient Ascent Algorithm
        """
        datas = np.array(datas) if not isinstance(datas, np.ndarray) else datas  # (m,n)
        labels = (
            np.expand_dims(np.array(labels), axis=1)
            if not isinstance(labels, np.ndarray)
            else np.expand_dims(labels, axis=1)
        )  # (m,1)
        m, n = datas.shape
        weights = np.ones((n, 1))
        for _ in range(iterations):
            h = sigmoid(np.dot(datas, weights))  # (m,1)
            error = labels - h  # (m,1)
            # update W according to the above formula.
            weights = weights + lr * np.dot(datas.transpose(), error)  # (n,1)
        return weights
    # load data
    datas, labels = load_data("../dataset/testSet.txt")
    # iterations:500, learning rate: 0.001
    weights = grad_ascent_matric(datas, labels, 500, 0.001)
    print(weights)
    [[ 4.12414349] 
     [ 0.48007329] 
     [-0.6168482 ]]
    
    # plot decision boundary of logistic regresion.
    
    def plot_decision_boundary(
        weights,
    ):
        """
        Visualize the training set and the fitted regression line
        """
        datas, labels = load_data("../dataset/testSet.txt")
        label_1_x, label_1_y = [], []
        label_0_x, label_0_y = [], []
        for i in range(len(labels)):
            if labels[i] == 1:
                label_1_x.append(datas[i, 1])
                label_1_y.append(datas[i, 2])
            else:
                label_0_x.append(datas[i, 1])
                label_0_y.append(datas[i, 2])
        plt.scatter(
            label_1_x, label_1_y, marker="s", color="red", alpha=0.5, label="label 1"
        )
        plt.scatter(
            label_0_x, label_0_y, marker="s", color="green", alpha=0.5, label="label 0"
        )
        x = np.arange(-3, 3, 0.1)
        y = (-weights[0] - weights[1] * x) / weights[2]
        plt.plot(x, y, label="LogisticRegression Line")
        plt.title("LogisticRegression")
        plt.xlabel("feature 1")
        plt.ylabel("feature 2")
        plt.legend()
        plt.show()
    
    逻辑回归模型的决策边界

    模拟生成噪声数据,验证模型的性能

    def add_noise():
        """
        Add random noise to the original training set and simulate to generate a test set.
        """
        datas, _ = load_data("../dataset/testSet.txt")
        noise_datas = np.random.normal(loc=0, scale=0.1, size=datas.shape)
        return noise_datas + datas
    
    # test the noise data on the trained logisticregression model.
    def test(weights):
        """
        Visualize the training and noise sets, and the regression line
        """
        noise_datas = add_noise()
        predicts = sigmoid(np.dot(noise_datas, weights))
        for i, o in enumerate(predicts):
            predicts[i] = 1 if o > 0.5 else 0
    
        n_label_1_x, n_label_1_y = [], []
        n_label_0_x, n_label_0_y = [], []
        for i in range(len(predicts)):
            if predicts[i] == 1:
                n_label_1_x.append(noise_datas[i, 1])
                n_label_1_y.append(noise_datas[i, 2])
            else:
                n_label_0_x.append(noise_datas[i, 1])
                n_label_0_y.append(noise_datas[i, 2])
    
        plt.scatter(
            n_label_1_x, n_label_1_y, marker="x", color="red", alpha=0.5, label="n label 1"
        )
        plt.scatter(
            n_label_0_x,
            n_label_0_y,
            marker="x",
            color="green",
            alpha=0.5,
            label="n label 0",
        )
    
        datas, labels = load_data("../dataset/testSet.txt")
        label_1_x, label_1_y = [], []
        label_0_x, label_0_y = [], []
        for i in range(len(labels)):
            if labels[i] == 1:
                label_1_x.append(datas[i, 1])
                label_1_y.append(datas[i, 2])
            else:
                label_0_x.append(datas[i, 1])
                label_0_y.append(datas[i, 2])
    
        plt.scatter(
            label_1_x, label_1_y, marker="s", color="red", alpha=0.5, label="label 1"
        )
        plt.scatter(
            label_0_x, label_0_y, marker="s", color="green", alpha=0.5, label="label 0"
        )
    
        x = np.arange(-3, 3, 0.1)
        y = (-weights[0] - weights[1] * x) / weights[2]
    
        plt.plot(x, y, label="LogisticRegression Line")
        plt.title("Text LogisticRegression")
        plt.xlabel("feature 1")
        plt.ylabel("feature 2")
        plt.legend()
        plt.show()
    

    'x'表示的是生成的模型数据,可以看到大部分都被正确分类。


    模拟数据验证逻辑回归模型

    使用模型预测

    output = \begin{cases} 1, & \text{if } g(h) \geq 0.5 \\ 0, & \text{if } g(h) < 0 \end{cases}\\

    相关文章

      网友评论

          本文标题:LogisticRegression

          本文链接:https://www.haomeiwen.com/subject/wuvopdtx.html