各个模型解读专题(2) 逻辑回归

1. 回顾线性回归

h θ ( x ) = θ 0 + θ 1 x 1 + θ 2 x 2 + … θ n x n = θ T x h_{\theta}(x)=\theta_{0}+\theta_{1} x_{1}+\theta_{2} x_{2}+\ldots \theta_{n} x_{n}=\theta^{T} x hθ(x)=θ0+θ1x1+θ2x2+θnxn=θTx

x = ( 1 x 1 x 2 ⋮ x n ) , θ = ( θ 0 θ 1 ⋮ θ n ) x=\left(\begin{array}{c} 1 \\ x_{1} \\ x_{2} \\ \vdots \\ x_{n} \end{array}\right), \quad \theta=\left(\begin{array}{c} \theta_{0} \\ \theta_{1} \\ \vdots \\ \theta_{n} \end{array}\right) x=1x1x2xn,θ=θ0θ1θn

J ( θ ) = 1 2 m ∑ i = 1 m ( h θ ( x ( i ) ) − y ( i ) ) 2 J(\theta)=\frac{1}{2 m} \sum_{i=1}^{m}\left(h_{\theta}\left(x^{(i)}\right)-y^{(i)}\right)^{2} J(θ)=2m1i=1m(hθ(x(i))y(i))2

Y = X θ Y=X \theta Y=Xθ

2. 改进用于分类

  • sigmoid

g ( z ) = 1 1 + e − z g(z)=\frac{1}{1+e^{-z}} g(z)=1+ez1

g ′ ( z ) = g ( z ) ( 1 − g ( z ) ) g^{\prime}(z)=g(z)(1-g(z)) g(z)=g(z)(1g(z))

结合线性模型, z = x θ z = x\theta z=xθ

h ( x ) = 1 1 + e − x θ h(x)=\frac{1}{1+e^{-x \theta}} h(x)=1+exθ1

其中x为样本输入,h(x)为模型输出,可以理解为某一分类的概率大小。而θ为分类模型的要求出的模型参数。h(x)的值越小,而分类为0的概率越高,反之,值越大分类为1的概率越高。如果靠近临界点,则分类准确率会下降.

P ( y = 1 ∣ x , θ ) = h ( x ) P ( y = 0 ∣ x , θ ) = 1 − h ( x ) \begin{gathered} P(y=1 \mid x, \theta)=h(x) \\ P(y=0 \mid x, \theta)=1-h(x) \end{gathered} P(y=1x,θ)=h(x)P(y=0x,θ)=1h(x)

y的概率分布函数表达式用矩阵法表示,即为:

P ( Y ∣ X , θ ) = h ( x ) y ( 1 − h ( x ) ) 1 − y P(Y \mid X, \theta)=h(x)^{y}(1-h(x))^{1-y} P(YX,θ)=h(x)y(1h(x))1y

得到了y的概率分布函数表达式,就可以用似然函数最大化来求解需要的模型系数θ。为了方便求解,这里用对数似然函数最大化。其中

似然函数的代数表达式为

L ( θ ) = ∏ i = 1 m ( h ( x ( i ) ) ) y ( i ) ( 1 − h ( x ( i ) ) ) 1 − y ( i ) L(\theta)=\prod_{i=1}^{m}\left(h\left(x^{(i)}\right)\right)^{y^{(i)}}\left(1-h\left(x^{(i)}\right)\right)^{1-y^{(i)}} L(θ)=i=1m(h(x(i)))y(i)(1h(x(i)))1y(i)

l ( θ ) = log ⁡ L ( θ ) = ∑ i = 1 m y ( i ) log ⁡ h ( x ( i ) ) + ( 1 − y ( i ) ) log ⁡ ( 1 − h ( x ( i ) ) ) \begin{aligned} l(\theta) &=\log L(\theta) &=\sum_{i=1}^{m} y^{(i)} \log h\left(x^{(i)}\right)+\left(1-y^{(i)}\right) \log \left(1-h\left(x^{(i)}\right)\right) \end{aligned} l(θ)=logL(θ)=i=1my(i)logh(x(i))+(1y(i))log(1h(x(i)))

∂ ∂ θ j l ( θ ) = ( y 1 g ( θ T x ) − ( 1 − y ) 1 1 − g ( θ T x ) ) ∂ ∂ θ j g ( θ T x ) = ( y 1 g ( θ T x ) − ( 1 − y ) 1 1 − g ( θ T x ) ) g ( θ T x ) ( 1 − g ( θ T x ) ) ∂ ∂ θ j θ T x = ( y ( 1 − g ( θ T x ) − ( 1 − y ) g ( θ T x ) ) x j = ( y − h ( x ) ) x j \begin{aligned} \frac{\partial}{\partial \theta_{j}} l(\theta) &=\left(y \frac{1}{g\left(\theta^{T} x\right)}-(1-y) \frac{1}{1-g\left(\theta^{T} x\right)}\right) \frac{\partial}{\partial \theta_{j}} g\left(\theta^{T} x\right) \\ &=\left(y \frac{1}{g\left(\theta^{T} x\right)}-(1-y) \frac{1}{1-g\left(\theta^{T} x\right)}\right) g\left(\theta^{T} x\right)\left(1-g\left(\theta^{T} x\right)\right) \frac{\partial}{\partial \theta_{j}} \theta^{T} x \\ &=\left(y\left(1-g\left(\theta^{T} x\right)-(1-y) g\left(\theta^{T} x\right)\right) x_{j}\right.\\ &=(y-h(x)) x_{j} \end{aligned} θjl(θ)=(yg(θTx)1(1y)1g(θTx)1)θjg(θTx)=(yg(θTx)1(1y)1g(θTx)1)g(θTx)(1g(θTx))θjθTx=(y(1g(θTx)(1y)g(θTx))xj=(yh(x))xj

根据上面的推导设立恰当的损失函数来方便逻辑回归的算法求解:

J ( θ ) = 1 m l ( θ ) J(\theta)=\frac{1}{m} l(\theta) J(θ)=m1l(θ)

此时训练时的梯度下降方向即为:

θ j : = θ j − α ∂ ∂ θ j J ( θ ) = θ j + α 1 m ∑ i = 1 m ( y − h ( x ( i ) ) ) x j ( i ) \theta_{j}:=\theta_{j}-\alpha \frac{\partial}{\partial \theta_{j}} J(\theta)=\theta_{j}+\alpha \frac{1}{m} \sum_{i=1}^{m}\left(y-h\left(x^{(i)}\right)\right) x_{j}^{(i)} θj:=θjαθjJ(θ)=θj+αm1i=1m(yh(x(i)))xj(i)

以上是不加正则项的推导,现实情况往往是需要加正则项的,具体加什么样的正则项需要根据具体任务判断。

3. 实践

3.1. 自定义


# %%
import numpy as np
import math


class Sigmoid:
    def function(self, x):
        return 1 / (1 + np.exp(-x))

    def derivative(self, x):
        return self.function(x) * (1 - self.function(x))


class LogisticRegression(object):
    def __init__(self, learning_rate=0.1):
        self.w = None
        self.sigmoid = Sigmoid()
        self.lr = learning_rate

    def fit(self, X, y, iterations=10000):
        lr = self.lr
        n_samples, n_features = np.shape(X)
        self.w = np.random.uniform(
            -1 / math.sqrt(n_features), 1 / math.sqrt(n_features), (n_features, )
        )
        print(f"w's shape : {self.w.shape}")
        for i in range(iterations):
            y_pred = self.sigmoid.function(X.dot(self.w))

            delta = y - y_pred
            grad = self.sigmoid.function(X.dot(self.w)) * (
                1 - self.sigmoid.function(X.dot(self.w))
            )
            # print(f"delta.shape:{delta.shape},gard.shape:{grad.shape} || gard:{grad}")
            self.w -= (lr * X.T.dot(-delta * grad))

    def predict(self, X):
        pred = self.sigmoid.function(X.dot(self.w))
        y_prediction = np.round(pred).astype("int")

        return y_prediction


# %%
from sklearn.datasets import load_iris

iris = load_iris()

X = iris.data
y = iris.target
# y = y.reshape(-1,1)
names = iris.feature_names


from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
    X[0:100], y[0:100], test_size=0.2, random_state=2021
)


# %%
model = LogisticRegression()
model.fit(X_train, y_train)


# %%

from sklearn.metrics import accuracy_score

y_pred = model.predict(X_test)

accuracy_score(y_test,y_pred)

3.2. 调用sklearn库

from sklearn.linear_model import LogisticRegression

lgr = LogisticRegression()
lgr.fit(X_train,y_train)
accuracy_score(y_test,lgr.predict(X_test))

其中的重要参数如下:

  • penalty

默认是L2的正则化,也可以选择L1正则化。如果在实际项目中发现过拟合情况严重或者由于模型特征过多,从而想让模型系数稀疏化可以选择L1正则化。

  • solver

优化算法,一共有下面4种选择:

1)linlinear:即坐标轴下降法;
2)lbfgs:拟牛顿法的一种;
3)newton-cg:牛顿法的一种;
4)sag:随机平均梯度下降,该算法每次仅使用了部分样本计算梯度迭代,适合样本数据量很大的情况,收敛速度优于SGD和其他3种算法。

需要注意的是正则化项的选择会影响损失函数优化算法的选择。4种优化算法newton-cg、lbfgs、liblinear和sag都适用于L2正则化,但是如果选择L1正则化,则只能选择liblinear。这是由于L1正则化的损失函数不是连续可导的,而另外3种优化算法时要求损失函数的一阶可导或者二阶可导。

  • multi_class

分类方式,multi_class参数即指分类方式的选择,可以选择one-vs-rest(OvR)和many-vs-many(multinomial)。需要注意的是liblinear无法用于multinomial这种分类方式。

  • 权重类型 class_weight

class_weight参数是指分类模型中各种类型的权重。如果在class_weight选择balanced,则会根据训练样本量来计算权重。某种类型样本量越多,则权重越低,样本量越少,则权重越高。提高了某种分类的权重,相比不考虑权重,会有更多的样本分类划分到高权重的类别中,从而可以解决样本不平衡的问题。sample_weight也可以用于解决这类问题。

4. plus 逻辑回归模型如何用于多分类

多元逻辑回归常见的有one-vs-rest(OvR)和many-vs-many(MvM)两种,OvR指每次将一个类的样例作为正例,所有其他类的样例作为反例来训练N个分类器,在测试时若仅有一个分类器预测为正类,则对应的类别标记作为分类结果。MvM指每次将若干个类作为正类,若干个其他类作为反类。

比较简单的多分类算法是,假设是n元分类模型,把所有第N类样本作为正例,其余作为反例, 在此基础上进行二元回归,得到第N类的分类模型,以此类推。

P ( Y = n ∣ x ) = e ( θ n x ) 1 + ∑ n = 1 N − 1 e ( θ n x ) n = 1 , 2 , ⋯   , N − 1 P ( Y = N ∣ x ) = e ( θ n x ) 1 + ∑ n = 1 N − 1 e ( θ n x ) \begin{gathered} P(Y=n \mid x)=\frac{e^{\left(\theta_{n} x\right)}}{1+\sum_{n=1}^{N-1} e^{\left(\theta_{n} x\right)}} \quad n=1,2, \cdots, N-1 \\ P(Y=N \mid x)=\frac{e^{\left(\theta_{n} x\right)}}{1+\sum_{n=1}^{N-1} e^{\left(\theta_{n} x\right)}} \end{gathered} P(Y=nx)=1+n=1N1e(θnx)e(θnx)n=1,2,,N1P(Y=Nx)=1+n=1N1e(θnx)e(θnx)

5. 逻辑回归和线性回归的比较

逻辑回归的本质是线性回归,只不过套了一层壳用于分类。套的这层壳就是 s i g m o i d sigmoid sigmoid函数。

  • 优化函数

再者,线性回归的优化函数是MSE,最小二乘,而逻辑回归的优化目标是一个似然函数。

加粗样式


6. 项目实践

导入

import matplotlib.pyplot as plt
import numpy as np
from io import BytesIO

import torch
import torch.nn.functional as F

import pandas as pd

print(torch.__version__)

数据集准备阶段

df = pd.read_csv('http://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data')

print("dataset preview:\n{}".format(df.head()))
print(df.tail())
ds = np.lib.DataSource()
fp = ds.open('http://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data')

x = np.genfromtxt(BytesIO(fp.read().encode()), delimiter=',', usecols=range(2), max_rows=100)
y = np.zeros(100)
y[50:] = 1

np.random.seed(1)
idx = np.arange(y.shape[0])
np.random.shuffle(idx)
X_test, y_test = x[idx[:25]], y[idx[:25]]
X_train, y_train = x[idx[25:]], y[idx[25:]]
mu, std = np.mean(X_train, axis=0), np.std(X_train, axis=0)
X_train, X_test = (X_train - mu) / std, (X_test - mu) / std

fig, ax = plt.subplots(1, 2, figsize=(7, 2.5))
ax[0].scatter(X_train[y_train == 1, 0], X_train[y_train == 1, 1])
ax[0].scatter(X_train[y_train == 0, 0], X_train[y_train == 0, 1])
ax[1].scatter(X_test[y_test == 1, 0], X_test[y_test == 1, 1])
ax[1].scatter(X_test[y_test == 0, 0], X_test[y_test == 0, 1])
plt.show()

数据集准备的思路如下 :

  • 下载现有的数据

我们看一下数据的构成

image-20200919204052124

可以看到每个数据有四个特征, 一个分类结果. 这就是著名的鸢尾花数据集

  • 为了方便可视化展示,选取两个特征作为分类依据.
x = np.genfromtxt(BytesIO(fp.read().encode()), delimiter=',', usecols=range(2), max_rows=100)
y = np.zeros(100)
y[50:] = 1

该数据集一共100项,前50项为 Iris-setosa花朵的数据, 后面50项为Iris-setosa的数据.

利用numpy从中读取数据,usecols = range(2)就是只取前两个特征作为X的值.

读取完成之后,X的效果如下:

在这里插入图片描述

对于y,我们将前50设置为0,后50设置为1就好.

在这里插入图片描述

之后,将数据进行乱序处理:

np.random.seed(1) # 将这个乱序记录下来,之后再次训练这个代码的时候,还是按照这个顺序排列
idx = np.arange(y.shape[0]) # 计数器
np.random.shuffle(idx) # 将100个数字乱序形成数组 idx 
X_test, y_test = x[idx[:25]], y[idx[:25]]  # 取乱序数组的前25个值作为索引,索引X的值作为测试集合
X_train, y_train = x[idx[25:]], y[idx[25:]] # 同理作为训练集
  • 将数据正则化

为了让计算量变小,让不同的衡量指标在同一个数据维度内可以比较 ,需要将数据正则化.

(例如: 在金融预测中, 对于金融数据 几百万到几百亿, 和哪一天这种个位数需要一起处理,那么需要将数据都限制到足够小才行.一般采用的方案就是正则化)

mu, std = np.mean(X_train, axis=0), np.std(X_train, axis=0)
X_train, X_test = (X_train - mu) / std, (X_test - mu) / std

X − μ σ \frac{X-\mu}{\sigma} σXμ 就是将数据正则化的常用公式.((概率论常识))

  • 画图表示数据

    fig, ax = plt.subplots(1, 2, figsize=(7, 2.5))
    ax[0].scatter(X_train[y_train == 1, 0], X_train[y_train == 1, 1]) # 在第一幅图中画散点图,将训练集中所有的第一类数据,f1和f2分别作为横轴和纵轴,画出来.
    ax[0].scatter(X_train[y_train == 0, 0], X_train[y_train == 0, 1])
    ax[1].scatter(X_test[y_test == 1, 0], X_test[y_test == 1, 1])
    ax[1].scatter(X_test[y_test == 0, 0], X_test[y_test == 0, 1])
    plt.show()
    

image.png

模型构建阶段

选择设备

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

是利用GPU还是CPU训练, 一般来说, 尤其对于大型模型来说,GPU是CPU训练速度的几十倍.所以,有钱真好.

设立模型

def custom_where(cond, x_1, x_2):
    return (cond * x_1) + ((1-cond) * x_2)

class LogisticRegression1():
    def __init__(self, num_features):
        self.num_features = num_features
        self.weights = torch.zeros(num_features, 1,
                                   dtype=torch.float32, device=device)
        self.bias = torch.zeros(1, dtype=torch.float32, device=device)

    def forward(self, x):
        linear = torch.add(torch.mm(x, self.weights), self.bias)
        probas = self._sigmoid(linear)
        return probas

    def backward(self, probas, y):
        errors = y - probas.view(-1) # X.view(-1)中的-1本意是根据另外一个数来自动调整维度。
        return errors

    def predict_labels(self, x):
        probas = self.forward(x)
        labels = custom_where(probas >= .5, 1, 0)
        return labels

    def evaluate(self, x, y):
        labels = self.predict_labels(x).float()
        accuracy = torch.sum(labels.view(-1) == y) / y.size()[0]
        return accuracy

    def _sigmoid(self, z):
        return 1. / (1. + torch.exp(-z))

    def _logit_cost(self, y, proba):
        tmp1 = torch.mm(-y.view(1, -1), torch.log(proba))
        tmp2 = torch.mm((1 - y).view(1, -1), torch.log(1 - proba))
        return tmp1 - tmp2

    def train(self, x, y, num_epochs, learning_rate=0.01):
        for e in range(num_epochs):
            #### Compute outputs ####
            probas = self.forward(x)

            #### Compute gradients ####
            errors = self.backward(probas, y)
            
            neg_grad = torch.mm(x.transpose(0, 1), errors.view(-1, 1))

            #### Update weights ####
            self.weights += learning_rate * neg_grad
            self.bias += learning_rate * torch.sum(errors)

            #### Logging ####
            print('Epoch: %03d' % (e + 1), end="")
            print(' | Train ACC: %.3f' % self.evaluate(x, y), end="")
            print(' | Cost: %.3f' % self._logit_cost(y, self.forward(x)))

设立模型的思路如下:

  • 从上述数据中 可以看出,这是一个明显的线性分类任务

  • 设置线性模型

    y = X W + b y = XW+b y=XW+b

    W就是一个权重矩阵. 我们数据中选取的特征是2个,那么X的形状就是 X 75 × 2 X_{75 \times 2} X75×2 , 那么 W 2 × 1 W_{2\times 1} W2×1 . 关于权重参数的数量,一般是有几个特征 ,就 选取几个参数.

  • 模型结构:

  • 初始化

    • 特征数量
    • 初始权重值
    • 偏置
  • 前向传播

    • p r o b = s i g m o i d ( X W + b ) prob = sigmoid(XW+b) prob=sigmoid(XW+b)

      y = { 1 p r o b ≥ 0.5 0 p r o b < 0.5 y = \begin{cases} 1 & prob \geq 0.5 \\ 0 & prob < 0.5 \end{cases} y={10prob0.5prob<0.5

  • 向后传播

    后向传播就是更新权值而已

    • 计算梯度

      ∇ = X T ( y − p r o b ) = X T e r r o r s \nabla = X^T(y - prob) = X^T errors =XT(yprob)=XTerrors

      其实其含义就是:

      假如我们需要用以下数据做预测
      f 1 f 2 y x 1 , 1 x 1 , 2 1 x 2 , 1 x 2 , 2 1 x 3 , 1 x 3 , 2 0 \begin{matrix} f_1 & f_2 & y \\ x_{1,1}&x_{1,2}& 1 \\ x_{2,1}&x_{2,2}& 1 \\ x_{3,1}&x_{3,2}& 0 \end{matrix} f1x1,1x2,1x3,1f2x1,2x2,2x3,2y110

      我们对于第1项数据的预测值是0.7, 第二项数据的预测值是0.6, 第三项的预测值是0.8.那么

      e r r o r 1 = 1 − 0.7 = 0.3 error_1 = 1- 0.7 = 0.3 error1=10.7=0.3

      e r r o r 2 = 1 − 0.6 = 0.4 error_2 = 1-0.6 = 0.4 error2=10.6=0.4

      e r r o r 3 = 0 − 0.8 = − 0.8 error_3 = 0 - 0.8 = -0.8 error3=00.8=0.8

      我们计算的梯度就是如下公式
      ∇ = ( 0.3 0.4 − 0.8 ) \nabla = \left( \begin{matrix} 0.3 \\ 0.4 \\ -0.8 \end{matrix} \right) =0.30.40.8

      这就是梯度,也是下一步参数更新的方向.

    • 参数更新
      W = W + η ∇ b = b + η ( e r r o r 1 + e r r o r 2 + e r r o r 3 ) W = W + \eta \nabla \\ b = b + \eta (error_1+error_2+error_3) W=W+ηb=b+η(error1+error2+error3)

      偏置的更新与参数的更新不同,此处的偏置是一个常数, 更新的时候需要将所有的error值加起来用于更新.

      η \eta η是更新步长, 是一个可以自由设定的,控制更新步伐的一个超参数

训练评估阶段

训练,就是将前向传播和后向传播的操作组合起来就OK,评估就是计算分类正确率

X_train_tensor = torch.tensor(X_train, dtype=torch.float32, device=device)
y_train_tensor = torch.tensor(y_train, dtype=torch.float32, device=device)

logr = LogisticRegression1(num_features=2)
logr.train(X_train_tensor, y_train_tensor, num_epochs=10, learning_rate=0.1)

print('\n  Model parameters:')
print('  Weights: %s' % logr.weights)
print('  Bias: %s' % logr.bias)

结果可视化显示

image.png

image.png

w, b = logr.weights, logr.bias

x_min = -2
y_min = ((-(w[0] * x_min) - b[0]) / w[1])

x_max = 2
y_max = ((-(w[0] * x_max) - b[0]) / w[1])

fig, ax = plt.subplots(1, 2, sharex=True, figsize=(7, 3))
ax[0].plot([x_min, x_max], [y_min, y_max])
ax[1].plot([x_min, x_max], [y_min, y_max])

ax[0].scatter(X_train[y_train == 0, 0], X_train[y_train == 0, 1], label='class 0', marker='o')
ax[0].scatter(X_train[y_train == 1, 0], X_train[y_train == 1, 1], label='class 1', marker='s')
ax[1].scatter(X_test[y_test == 0, 0], X_test[y_test == 0, 1], label='class 0', marker='o')
ax[1].scatter(X_test[y_test == 1, 0], X_test[y_test == 1, 1], label='class 1', marker='s')

ax[1].legend(loc='upper left')
plt.show()
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

古承风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值