Skip to content

Latest commit

 

History

History
581 lines (357 loc) · 21.4 KB

File metadata and controls

581 lines (357 loc) · 21.4 KB

四、逻辑回归

在上一章中,我们已经看到了一种将现实的一部分建模为线性函数的方法,该函数具有独立变量,并且偏差最小化了误差函数。

除了某些非常明确定义的问题(预期结果是连续的变量和函数)之外,这种特殊的分析还不够。

但是,如果我们面对具有定性因变量的数据,将会发生什么? 例如,是否存在确定的特征; 受试者有金色的头发吗? 病人以前有病吗?

这些是我们将在本章中处理的问题。

问题描述

线性回归旨在解决的问题不是基于连续函数的值预测,这一次,我们想知道样本属于确定类别的可能性。

在本章中,我们将依靠线性模型的一般化来解决回归问题,但最终目标是解决分类问题,我们必须应用标签或将观察集中的所有元素分配给预定义的组。

Problem description

在上图中,我们可以看到如何对旧问题和新问题进行分类。 第一个(线性回归)可以想象为值不断增长的连续体。

另一个是基于x值的输出只能具有两个不同值的域。 在第二张图的特定情况下,我们可以看到对其中一个选项的特定偏向极端:在左侧,y值偏向 0,在右侧偏向 1。

鉴于即使在进行回归从而寻找连续值的情况下,这种项也可能有些棘手,实际上,最终目标是为具有离散变量的分类问题建立预测。

此处的关键是要了解我们将获得与类有关的项目的概率,而不是完全离散的值。

sigmoid 函数的前身 -- 对率(Logit)函数

在研究逻辑函数之前,我们将回顾该函数所基于的原始函数,并为其提供一些更一般的属性。

本质上,当我们谈论logit函数时,我们正在使用随机变量p的函数,更具体地说,是与伯努利分布相对应的函数。

伯努利分布

在解释理论细节之前,值得注意的是伯努利分布是一个随机变量,它具有:

  • 取值为 0 且失败概率为q = 1 - p

  • 取值为 1,成功概率为p

可以表示如下(对于具有伯努利分布的随机变量X):

Bernoulli distribution

这是一种概率分布,它将以二元选项的形式表示事件的发生概率,就像我们要表示自己的变量(特征的存在,事件的发生,现象的因果关系等)一样。

链接函数

在尝试建立广义线性模型时,我们要从线性函数开始,并从因变量开始,获取到概率分布的映射。

由于选项具有二元性质,因此通常选择的分布是最近提到的伯努利分布,而倾向于 logistic 函数的链接函数是logit函数。

Logit 函数

我们可以利用的可能变量之一是p等于 1 的几率的自然对数。 此函数称为logit函数:

Logit function

我们也可以将logit函数称为对数奇数函数,因为对于给定的概率p,我们正在计算赔率的对数(p/1-p)

Logit function

因此,正如我们可以直观地推断出的那样,用自变量的组合替换X,无论它们的值是什么,用从负无穷大到无穷大的任何出现替换X,我们将响应缩放到01

Logit 反函数的重要性

假设我们计算logit函数的逆。 这将使我们编写以下函数:

The importance of the logit inverse

此函数是sigmoid函数。

sigmoid 函数

逻辑函数将帮助我们在新的回归任务中表示二元类别。

在下图中,您将找到sigmoid函数的图形表示:

The logistic function

逻辑函数或 Sigmoid 的图形表示

Logistic 函数作为线性建模的泛化

逻辑函数δ(t)定义如下:

Logistic function as a linear modeling generalization

该方程式的正常解释是t代表一个简单的自变量。 但是,我们将改进此模型,并假定t是单个解释变量x的线性函数(对t是多个解释变量的线性组合的情况进行类似处理)。

然后,我们将t表示为:

Logistic function as a linear modeling generalization

最终估计的回归方程

因此,我们从以下等式开始:

Final estimated regression equation

使用所有这些元素,我们可以计算回归方程,这将为我们提供回归概率:

Final estimated regression equation

下图将显示如何将从任意范围的映射最终转换为范围[0, 1],该范围可以解释为表示事件发生的概率p

Final estimated regression equation

什么影响会改变线性函数的参数? 它们是将更改sigmoid函数的中心斜率和从零开始的位移的值,从而使其可以更精确地减小回归值与实际数据点之间的误差。

Logistic 函数的属性

函数空间中的每条曲线都可以通过可能适用的目标来描述。 对于 logistic 函数,它们是:

  • 根据一个或多个独立变量对事件的概率p进行建模。 例如,鉴于先前的资格,被授予奖品的可能性。
  • 对确定的观测值进行估计(这是回归部分)p,与事件未发生的可能性有关。
  • 预测自变量变化对二元响应的影响。
  • 通过计算某项属于确定类别的概率对观察进行分类。

损失函数

在上一节中,我们看到了近似的p^函数,该函数将对样本属于特定类别的概率进行建模。 为了衡量我们对解的近似程度,我们将寻找精心选择的损失函数。

该损失函数表示为:

Loss function

该损失函数的主要特性是它不会以类似的方式惩罚误差,当误差增加到远远超过 0.5 时,误差惩罚因子会渐近增长。

多类应用 -- softmax 回归

到目前为止,我们仅针对两个类的情况进行分类,或者以概率语言对事件发生概率p进行分类。

在要决定两个以上类别的情况下,有两种主要方法: 一对一,一对剩余。

  • 第一种技术包括计算许多模型,这些模型代表每个类别相对于所有其他类别的概率。
  • 第二个由一组概率组成,其中我们代表一个类别相对于所有其他类别的概率。
  • 第二种方法是softmax回归的输出格式,它是 n 个类的逻辑回归的概括。

因此,为了训练样本,我们将使用句柄y(i)ε{1,...,K},将二元标签( y(i)ε{0,1})更改为向量标签,其中K是类别数,标签Y可以采用K不同的值, 而不是只有两个。

因此,对于这种特定技术,给定测试输入X,我们想要针对k=1,...,K的每个值估计Py=k|x)的概率。 softmax回归将输出K维向量(其元素总和为 1),从而为我们提供了K估计的概率。

在下图中,我们表示在单类和多类逻辑回归的概率映射上发生的映射:

Multiclass application - softmax regression

成本函数

softmax函数的成本函数是自适应的交叉熵函数,该函数不是线性的,因此对大阶函数差异的惩罚要比对小阶函数的惩罚更大。

Cost function

在这里,c是类别编号,I是各个训练样本索引,yc对于期望的类别为 1,对于其余类别为 0。

扩展这个方程,我们得到以下结果:

Cost function

迭代方法的数据标准化

正如我们将在以下各节中看到的那样,对于逻辑回归,我们将使用gradient descent方法来最小化成本函数。

Data normalization for iterative methods

此方法对特征数据的形式和分布非常敏感。

因此,我们将进行一些预处理,以便获得更好,更快的收敛结果。

我们将把这种方法的理论原因留给其他书籍,但我们将总结其原因,即通过归一化可以平滑误差表面,使迭代gradient descent更快地达到最小误差。

输出的单热表示

为了将softmax函数用作回归函数,我们必须使用一种称为单热编码的编码。 这种编码形式只是将变量的数字整数值转换为数组,其中将值列表转换为数组列表,每个数组的长度与该列表的最大值相同,并且每个数组的表示方式是在值的索引上添加 1,其余元素保持为 0。

例如,这将是单热编码形式的列表[1, 3, 2, 4]的表示形式:

[[0 1 0 0 0] 
[0 0 0 1 0] 
[0 0 1 0 0]
[0 0 0 0 1]]

示例 1 -- 单变量 logistic 回归

在第一个示例中,我们将使用单变量 logistic 回归(患者年龄)来估计心脏病的概率。

有用的库和方法

从 0.8 版开始,TensorFlow 提供了一种生成热点的方法。 用于此生成的函数是tf.one_hot,其形式如下:

tf.one_hot(indices, depth, on_value=1, off_value=0, axis=None, dtype=tf.float32, name=None)

此函数生成通用的单热编码数据结构,该结构可以指定值,生成轴,数据类型等。

在生成的张量中,索引的指示值将采用on_value(默认值为1),其他值将具有off_value(默认0)。

Dtype是生成的张量的数据类型; 默认值为float32

depth变量定义每个元素将具有多少列。 我们假设它在逻辑上应该为max(indices) + 1,但也可以将其切掉。

TensorFlow 的 softmax 实现

在 TensorFlow 中应用softmax回归的方法包括tf.nn.log_softmax, with the following form:

tf.nn.log_softmax(logits, name=None)

在这里,参数为:

  • logits:张量必须为以下类型之一:float32float64 形状为[batch_size, num_classes]的 2D
  • name:操作的名称(可选)

此函数返回具有与logits相同类型和形状的张量。

数据集说明和加载

我们将讨论的第一种情况是我们要拟合逻辑回归的方法,仅测量一个变量,并且只有两个可能的结果。

CHDAGE 数据集

对于第一个简单的示例,我们将使用一个非常简单且经过研究的数据集,该数据集以在书中出版而闻名。 应用逻辑回归第三版,David W. Hosmer Jr.,Stanley Lemeshow,Rodney X. Sturdivant,作者:Wiley。

列出age的年限(AGE),以及对心脏病风险因素进行假设性研究的 100 名受试者是否患有严重冠心病(CHD)的证据。 该表还包含一个标识符变量(ID)和一个年龄组变量(AGEGRP)。 结果变量是 CHD,它用0值编码以表示不存在 CHD,或用1编码以指示其存在于个体中。 通常,可以使用任何两个值,但是我们发现使用零和一最为方便。 我们将此数据集称为 CHDAGE 数据。

CHDAGE 数据集格式

CHDAGE 数据集是一个两列的 CSV 文件,我们将从外部仓库下载该文件。

在第 1 章(探索和转换数据)中,我们使用了本机 TensorFlow 方法来读取数据集。 在本章中,我们将使用一个互补且流行的库来获取数据。

进行此新添加的原因是,鉴于数据集只有 100 个元组,实际上只需要一行读取即可,而且pandas库提供了免费但简单但强大的分析方法 。

因此,在该项目的第一阶段,我们将开始加载 CHDAGE 数据集的实例,然后将打印有关数据的重要统计信息,然后进行预处理。

在对数据进行一些绘制之后,我们将构建一个由激活函数组成的模型,该激活函数将在特殊情况下成为softmax函数,在特殊情况下它将变为标准逻辑回归。 那就是只有两个类别(疾病的存在与否)。

数据集加载和预处理实现

首先,我们导入所需的库,并指示所有matplotlib程序都将内联(如果我们使用 Jupyter):

>>> import pandas as pd 
>>> import numpy as np 
>>> %matplotlib inline 
>>> import matplotlib.pyplot as plt 

然后,我们读取数据并要求pandas检查有关数据集的重要统计信息:

>>> df = pd.read_csv("data/CHD.csv", header=0) 
>>> print df.describe() 
    age        chd
    count  100.000000  100.00000
    mean    44.380000    0.43000
    std     11.721327    0.49757
    min     20.000000    0.00000
    25%     34.750000    0.00000
    50%     44.000000    0.00000
    75%     55.000000    1.00000
    max     69.000000    1.000000

然后,我们继续绘制数据以了解数据:

plt.figure() # Create a new figure 
plt.scatter(df['age'],df['chd']) #Plot a scatter draw of the random datapoints 

Dataset loading and preprocessing implementation

模型架构

在这里,我们从以下变量开始,描述将在其中构建模型元素的代码部分:

learning_rate = 0.8 #Learning speed 
batch_size = 100 #number of samples for the batch 
display_step = 2 #number of steps before showing progress

在这里,我们为图创建初始变量和占位符,即单变量xy浮点值:

x = tf.placeholder("float", [None, 1]) # Placeholder for the 1D data 
y = tf.placeholder("float", [None, 2]) # Placeholder for the classes (2)

现在,我们将创建线性模型变量,随着模型拟合的进行,将对其进行修改和更新:

W = tf.Variable(tf.zeros([1, 2])) 
b = tf.Variable(tf.zeros([2]))

最后,我们将对线性函数应用softmax操作来构建激活函数:

activation = tf.nn.softmax(tf.matmul(x, W) + b) 

损失函数描述和优化器循环

在这里,我们仅将互相关函数定义为loss函数,并定义optimizer操作,即gradient descent。 以下各章将对此进行解释; 现在,您可以看到它是一个黑框,它将改变变量,直到损失最小:

cost = tf.reduce_mean(-tf.reduce_sum(y*tf.log(activation), reduction_indices=1)) 
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost) 
#Iterate through all the epochs 
for epoch in range(training_epochs): 
        avg_cost = 0\. 
        total_batch = 400/batch_size 
# Loop over all batches 
        for i in range(total_batch): 
            # Transform the array into a one hot format 

        temp=tf.one_hot(indices = df['chd'].values, depth=2, on_value = 1, off_value = 0, axis = -1 , name = "a")       
        batch_xs, batch_ys =(np.transpose([df['age']])-44.38)/11.721327, temp 

        # Fit training using batch data 
        sess.run(optimizer, feed_dict={x: batch_xs.astype(float), y: batch_ys.eval()}) 

        # Compute average loss, suming the corrent cost divided by the batch total number 
        avg_cost += sess.run(cost, feed_dict={x: batch_xs.astype(float), y: batch_ys.eval()})/total_batch 

停止条件

一旦根据训练周期对数据进行了训练,该过程将简单地停止。

结果描述

这将是程序的输出:

Epoch: 0001 cost= 0.638730764
[ 0.04824295 -0.04824295]
[[-0.17459483  0.17459483]]
Epoch: 0002 cost= 0.589489654
[ 0.08091066 -0.08091066]
[[-0.29231569  0.29231566]]
Epoch: 0003 cost= 0.565953553
[ 0.10427245 -0.10427245]
[[-0.37499282  0.37499279]]
Epoch: 0004 cost= 0.553756475
[ 0.12176144 -0.12176143]
[[-0.43521613  0.4352161 ]]
Epoch: 0005 cost= 0.547019333
[ 0.13527818 -0.13527818]
[[-0.48031801  0.48031798]]

拟合函数的跨周期表示

在下图中,我们表示了拟合函数在不同周期之间的进展:

Fitting function representations across epochs

完整源代码

这是完整的源代码:

import pandas as pd 
import numpy as np 
get_ipython().magic(u'matplotlib inline') 
import matplotlib.pyplot as plt 
import tensorflow as tf 

df = pd.read_csv("data/CHD.csv", header=0) 
# Parameters 

learning_rate = 0.2 
training_epochs = 5 
batch_size = 100 
display_step = 1 
sess = tf.Session() 
b=np.zeros((100,2)) 

# tf Graph Input 

x = tf.placeholder("float", [None, 1]) 
y = tf.placeholder("float", [None, 2]) 

# Create model 
# Set model weights 
W = tf.Variable(tf.zeros([1, 2])) 
b = tf.Variable(tf.zeros([2])) 

# Construct model 
activation = tf.nn.softmax(tf.matmul(x, W) + b) 
# Minimize error using cross entropy 
cost = tf.reduce_mean(-tf.reduce_sum(y*tf.log(activation), reduction_indices=1)) # Cross entropy 
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost) # Gradient Descent 

# Initializing the variables 
init = tf.initialize_all_variables() 

# Launch the graph 

with tf.Session() as sess: 
    tf.train.write_graph(sess.graph, './graphs','graph.pbtxt') 
    sess.run(init) 
    writer = tf.train.SummaryWriter('./graphs', sess.graph) 
    #Initialize the graph structure 

    graphnumber=321 

    #Generate a new graph 
    plt.figure(1) 

    #Iterate through all the epochs 
    for epoch in range(training_epochs): 
        avg_cost = 0\. 
        total_batch = 400/batch_size 
        # Loop over all batches 

        for i in range(total_batch): 
            # Transform the array into a one hot format 

            temp=tf.one_hot(indices = df['chd'].values, depth=2, on_value = 1, off_value = 0, axis = -1 , name = "a")       
            batch_xs, batch_ys = (np.transpose([df['age']])-44.38)/11.721327, temp 

            # Fit training using batch data 
            sess.run(optimizer, feed_dict={x: batch_xs.astype(float), y: batch_ys.eval()}) 

            # Compute average loss, suming the corrent cost divided by the batch total number 
            avg_cost += sess.run(cost, feed_dict={x: batch_xs.astype(float), y: batch_ys.eval()})/total_batch 
        # Display logs per epoch step 

        if epoch % display_step == 0: 
            print "Epoch:", '%05d' % (epoch+1), "cost=", "{:.8f}".format(avg_cost) 

            #Generate a new graph, and add it to the complete graph 

            trX = np.linspace(-30, 30, 100) 
            print (b.eval()) 
            print (W.eval()) 
            Wdos=2*W.eval()[0][0]/11.721327 
            bdos=2*b.eval()[0] 

            # Generate the probabiliy function 
            trY = np.exp(-(Wdos*trX)+bdos)/(1+np.exp(-(Wdos*trX)+bdos) ) 

            # Draw the samples and the probability function, whithout the normalization 
            plt.subplot(graphnumber) 
            graphnumber=graphnumber+1 

            #Plot a scatter draw of the random datapoints 
            plt.scatter((df['age']),df['chd']) 
            plt.plot(trX+44.38,trY) #Plot a scatter draw of the random datapoints 
            plt.grid(True) 

        #Plot the final graph 
        plt.savefig("test.svg")  

图形表示

使用 TensorBoard 工具,我们将看到操作链。 请注意,在一半的操作图中,我们定义了主要的全局操作(“小数点”)以及应用于其余项的梯度操作,这是进行loss函数最小化所必需的。 这是接下来几章要讨论的主题。

Graphical representation

示例 2 -- skflow 中的单变量 logistic 回归

在此示例中,我们将探索单变量示例域,但是这次我们将使用来自新库的帮助,该库为我们简化了模型构建,称为skflow

有用的库和方法

在机器学习库领域中,有很多选择。 最知名的之一是sklearn,我们在第 2 章聚类中讨论过。

在 TensorFlow 发布之后的很早,一个新的贡献库就出现了,叫做skflow,其主要目的是模拟sklearn的接口和工作流程,在这个 TensorFlow 会话环境中工作更简洁。

在下面的示例中,我们将使用skflow接口重复先前回归的分析。

在示例中,我们还将看到 skflow 如何为回归模型自动生成详细且组织良好的图,只需将日志目录设置为参数即可。

数据集说明

使用pandas库,数据集加载阶段与前面的示例相同:

import pandas as pd 

df = pd.read_csv("data/CHD.csv", header=0) 
print df.describe() 

模型架构

这是my_model的代码段:

 def my_model(X, y): 
    return skflow.models.logistic_regression(X, y) 

X1 =a.fit_transform(df['age'].astype(float)) 
y1 = df['chd'].values 
classifier = skflow.TensorFlowEstimator(model_fn=my_model, n_classes=2) 

在这里,我们可以使用softmax分类器查看逻辑回归阶段的详细视图:

Model architecture

Model architecture

结果描述

score = metrics.accuracy_score(df['chd'].astype(float), classifier.predict(X)) 
print("Accuracy: %f" % score) 

输出结果可观(为了简化模型)74% 的准确率:

Accuracy: 0.740000

完整源代码

这是完整的源代码:

import tensorflow.contrib.learn as skflow 
from sklearn import datasets, metrics, preprocessing 
import numpy as np 
import pandas as pd 

df = pd.read_csv("data/CHD.csv", header=0) 
print df.describe() 

def my_model(X, y): 
    return skflow.models.logistic_regression(X, y) 

a = preprocessing.StandardScaler() 

X1 =a.fit_transform(df['age'].astype(float)) 

y1 = df['chd'].values 

classifier = skflow.TensorFlowEstimator(model_fn=my_model, n_classes=2) 
classifier.fit(X1,y1 , logdir='/tmp/logistic') 

score = metrics.accuracy_score(df['chd'].astype(float), classifier.predict(X)) 
print("Accuracy: %f" % score) 

总结

在本章中,我们学习了一种新的建模技术,即逻辑函数,并从一种简单的分类任务入手。

我们还学习了一种通过pandas库读取基于文本的数据的新方法。

此外,我们还看到了与skflow库一起使用的经典工作流的一种补充方法。

在下一章中,我们将开始处理更复杂的架构,并进入 TensorFlow 库擅长的领域:训练,测试和最终实现神经网络以解决实际问题。