Logistic回归

Logistic回归

​        我的微信公众号: s406205391; 欢迎大家一起学习,一起进步!!!

        假设现在有一些数据点,我们用一条直线对这些点进行拟合(该线称为最佳拟合直线),这个拟合过程就称作回归。利用Logistic回归进行分类的主要思想是:根据现有数据对分类界线建立回归公式,以此进行分类。训练分类器时的做法就是寻找最佳拟合参数,使用的是最优化算法

Logistic回归的一般过程:

  1. 收集数据:采用任意方法收集数据。
  2. 准备数据:由于需要进行距离计算,因此要求数据类型为数值型。另外,结构化数据格式则最佳。
  3. 分析数据:采用任意方法对数据进行分析。
  4. 训练算法:大部分时间将用于训练,训练的目的是为了找到最佳的分类回归系数。
  5. 测试算法:一旦训练完成,分类将会很快。
  6. 使用算法:首先,我们需要输入一些数据,并将其转换成对应的结构化数字;接着,基于训练好的回归系数就可以对这些数值进行简单的回归计算,判定他们属于哪个类别;在这之后,我们就可以在输出的类别上做一些其他分析工作。

基于Logistic回归和Sigmoid函数的分类

​        Logistic回归的优先在于计算代价不高,易于理解和实现。缺点在于容易欠拟合,分类精度可能不高。适用数据类型为数值型和标称型数据

        Logistic回归中,使用了Sigmoid阶跃函数,该函数的特点是:该函数在跳跃点上可以从0瞬间跳跃到1

image

        基于这个函数,我们可以将回归方程,带入这个函数。进而得到一个范围在0~1之间的数字。任何大于0.5的数据被分为1类,小于0.5即被归入0类。这样便可以很好的完成分类。公式如下:

        Sigmoid: h(x)=\frac{1}{1+e^{-z}} <font size=2, color='grey'>z表示回归方程</font>

        回归方程:z = w_0x_0 + w_1x_1 + w_2x_2 + ... + w_nx_n, 也可以写成z=w^Tx

        接下来,我们要做的事情,便是找到w的最佳值,一般有梯度下降法、梯度上升法、随机梯度下降法、随机梯度上升法等。其最终推导公式如下:

        \theta:=\theta + \alpha\cdot[\frac{1}{m}]\cdot x^T\cdot(g(x\cdot\theta)-y) <font size=2, color='grey'>g(x)即为Sigmoid函数,α为随机常数,</font>

        具体推到过程,可以参考这篇文章

梯度上升/下降法

        梯度上升和梯度下降其实差不多,只需要把上面的公式的加号变成减号就行了。

        本文所用的数据集(提取码:ogge)。下面是一个梯度上升算法的具体实现。load_data_set()函数,从文本中提取了一组数据,作为我们的训练的数据集。sigmoid()函数则是Sigmoid的计算。grad_ascent()函数则是梯度上升求参数最佳值的方法。其实主要就是运用了上面的推导公式。

import matplotlib.pyplot as plt
import numpy as np


def load_data_set():
    """读取测试数据集"""
    data_mat = []
    label_mat = []
    for line in open('data\\Ch05\\testSet.txt'):
        line = line.strip().split()
        data_mat.append([1.0, float(line[0]), float(line[1])])
        label_mat.append(int(line[2]))
    return data_mat, label_mat


def sigmoid(in_x):
    """sigmoid阶跃函数"""

    return 1 / (1 + np.exp(-in_x))


def grad_ascent(data_mat_in, class_labels):
    """使用梯度上升法,求得系数"""

    data_matrix = np.mat(data_mat_in)
    label_mat = np.mat(class_labels).transpose()  # 转置矩阵
    m, n = np.shape(data_matrix)
    alpha = 0.001
    max_cycles = 500
    weights = np.ones((n, 1))
    for k in range(max_cycles):
        h = sigmoid(data_matrix * weights)
        error = (label_mat - h)
        weights = weights + alpha * data_matrix.transpose() * error
    return weights


def plot_best_fit(weights):
    data_mat, label_mat = load_data_set()
    data_array = np.array(data_mat)
    n = np.shape(data_array)[0]
    xcord1 = []
    ycord1 = []
    xcord2 = []
    ycord2 = []
    for i in range(n):
        if int(label_mat[i]) == 1:
            xcord1.append(data_array[i, 1])
            ycord1.append(data_array[i, 2])
        else:
            xcord2.append(data_array[i, 1])
            ycord2.append(data_array[i, 2])
        fig = plt.figure()
        ax = fig.add_subplot(111)
        ax.scatter(xcord1, ycord1, s=30, c='red', marker='s')
        ax.scatter(xcord2, ycord2, s=30, c='green')
        x = np.arange(-3.0, 3.0, 0.1)
        y = (-weights[0] - weights[1] * x) / weights[2]
        ax.plot(x, y)
        plt.xlabel('x1')
        plt.ylabel('x2')
        plt.show()

if __name__ == '__main__':
    d, l = load_data_set()
    w = grad_ascent(d, l)
    plot_best_fit(w)

        下图是梯度上升法,迭代500次的分类结果。从结果上看,分类的还是比较好的。


image

随机梯度上升法

        梯度上升算法在每次更新回归系数时都需要遍历整个数据集,该方法在处理100个左右的数据集时尚可,但是如果有数十亿样本和成千上万个特征,那么该方法计算复杂度就太高了。一种改进的方法是一次仅用一个样本点来更新回归系数,该方法称为随机梯度上升算法。由于可以在新的样本到来时对分类器进行增量式更新,因而随机梯度上升算法是一个在线学习算法。下面是随机梯度算法的代码实现。

def sto_grad_ascent(data_matrix, class_labels):
    """随机梯度上升法"""

    m, n = np.shape(data_matrix)
    alpha = 0.001
    weights = np.ones(n)
    for i in range(m):
        h = sigmoid(data_matrix[i] * weights)
        error = (class_labels[i] - h)
        weights = weights + alpha * data_matrix[i] * error
    return weights

if __name__ == '__main__':
    d, l = load_data_set()
    w = sto_grad_ascent(np.array(d), l)
    plot_best_fit(w)

​        下图是随机梯度上升法,拟合的结果。不如梯度上升法完美,但是也需要考虑,后者是迭代了500次才得到的。一个判断优化算法优劣的可靠方法是看它是否收敛,也就是说参数是否达到了稳定值,是否还会不断地变化

image

改进的随机梯度上升算法

​        对随机梯度算法进行一些改进,得到下面的代码。其改进之处在于。一方面,alpha在每次迭代的时候都会调整,这会缓解随机梯度上升算法的数据波动。另外,虽然alpha会随着迭代次数不断减小,但永远不会减小到0,这是因为公式中存在常数项,必须保证在多次迭代后,该常数项仍然具有一定的影响。另一个改进之处在于,这里通过随机选取样本来更新回归系数。这种方法将减少周期性波动。

def stoc_grad_ascent(data_matrix, class_labels, num_iter=500):
    """改进的随机梯度上升算法"""

    m, n = np.shape(data_matrix)
    weights = np.ones(n)
    for j in range(num_iter):
        data_index = list(range(m))
        for i in range(m):
            alpha = 4 / (1 + j + i) + 0.01
            rand_index = int(np.random.uniform(0, len(data_index)))
            h = sigmoid(sum(data_matrix[rand_index] * weights))
            error = class_labels[rand_index] - h
            weights = weights + alpha * error * data_matrix[rand_index]
            del data_index[rand_index]
    return weights

        改进之后的拟合结果,要明显优于改进之前得结果。

在这里插入图片描述

示例:从疝气病症预测病马病死率

        接下来将使用Logistic回归来预测患有疝病的马的存活问题。这里的数据包含368个样本和28个特征。 我们在日常数据处理中,常会遇到数据集包含缺失值的情况,那么在Logistic回归算法中,缺失值是否可以用0来代替呢

​        答案是可以的。我们观察回归系数的更新公式:

        weights = weights + alpha * error * dataMatrix[randIndex]

        如果dataMatrix的某个特征对应值为0,那么该特征的系数将不做更新,即:

        weights = weights

        另外,由于sigmoid(0)=0.5, 即它对结果的预测不具有任何倾向性。基于上述原因,将缺失值用0代替既可以保留现有数据,也不需要对优化的算法进行修改。

        下面的代码中,classify_vector()构建了一个Logistic分类器,只需要将训练你的到的回归系数带入进去,就可以对样本进行分类了。colic_test()函数则是示例的具体代码

def classify_vector(in_x, weithts):
    prob = sigmoid(sum(in_x * weithts))
    if prob > 0.5:
        return 1.0
    else:
        return 0.0


def colic_test():
    fr_train = open('data\\Ch05\\horseColicTraining.txt')
    fr_test = open('data\\Ch05\\horseColicTest.txt')

    training_set = []
    training_labels = []
    for line in fr_train.readlines():
        line = line.strip().split('\t')
        line_arr = list(map(float, line[0: 21]))
        training_set.append(line_arr)
        training_labels.append(float(line[21]))

    train_weights = stoc_grad_ascent(np.array(training_set), training_labels, 500)
    error_count = 0
    num_test_vec = 0
    for line in fr_test.readlines():
        num_test_vec += 1
        line = line.strip().split('\t')
        line_arr = list(map(float, line[0: 21]))
        if int(classify_vector(np.array(line_arr), train_weights)) != int(line[21]):
            error_count += 1

    error_rate = error_count / num_test_vec
    print(f"the error rate of this test is: {error_rate}")
    return error_rate

if __name__ == '__main__':
    colic_test()

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容