65.9K
CodeProject 正在变化。 阅读更多。
Home

机器学习基本算法介绍(第四部分)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (10投票s)

2018年7月27日

CPOL

5分钟阅读

viewsIcon

21814

逻辑回归介绍

引言

在本篇技巧中,我将介绍一种优化算法:逻辑回归。在统计学中,逻辑模型通常用于解释二元因变量。在回归分析中,逻辑回归是指估计逻辑模型的参数(维基百科)。我还会介绍梯度上升及其变体——随机梯度上升。

背景

我从多个来源学习,您可以在“参考文献”部分找到它们。这是我对所学内容的总结。

逻辑回归

逻辑回归是一种分类算法,它通过学习一个函数来近似 P(Y |X)。它有一个核心假设,即 P(Y|X) 可以近似为应用于输入特征线性组合的 sigmoid 函数。这个假设通常以等价形式写为

其中

在逻辑回归中,θ (theta) 是一个长度为 m 的参数向量,我们将根据 n 个训练样本学习这些参数的值。参数的数量应等于每个数据点的特征数量(xi)。函数 σ(z) (sigma z) 称为逻辑函数(或 sigmoid 函数)。

对数似然

为了选择逻辑回归参数的值,我们使用最大似然估计 (MLE)。我们可以写出所有数据的似然

对数似然方程为

对数似然的梯度 

现在我们有了对数似然的函数,我们只需要选择最大化它的 theta 值。我们可以通过使用优化算法来找到最佳的 theta 值。我们使用的优化算法需要对数似然关于每个参数的偏导数

我们已经准备好进行优化算法了。为此,我们采用一种称为梯度上升的算法。梯度上升的思想是,梯度指向“上坡”方向。如果你不断地沿着梯度的方向迈出小步,你最终会到达一个局部最大值。每次小步更新的参数计算如下:

其中 η (eta) 是我们迈出步子的大小,也称为学习率。如果你不断使用上面的方程更新 θ,你最终会收敛到最佳的 θ 值。方括号中的表达式也称为实际类别和预测类别之间的误差

Using the Code

我们将重点关注二元分类问题,其中 y 只能取两个值:010 也称为负类1 称为正类,有时也用符号“-”和“+”表示。给定 x(i),相应的 y(i) 也称为训练样本的标签

假设我们有 10 个数据点,每个点有三个数值特征:X1、X2 和 X3。为了简化,我们将假设 X1 = 1。所以我们有

我们的数据点将存储在mySample.txt文件中,其内容可能如下所示:

稍后我们将通过 Python 代码将 X1 = 1 插入到每个数据点。下面的 loadDataSet() 函数将从 mySample.txt 加载数据。

def loadDataSet():
     # storing X<sub>i</sub>, where i = 1,2,3
     dataMat = [];
     # storing labels, 0 or 1
     labelMat = []
     fr = open('mySample.txt')
     for line in fr.readlines():
           lineArr = line.strip().split()
           # notes: we are inserting X1 = 1 to the dataMat
           dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])
           labelMat.append(int(lineArr[2]))
     return dataMat,labelMat

我们可以尝试使用以下代码行来测试 dataMatlableMat 的结果。

dataMat,labelMat=loadDataSet()

print(dataMat)

print(labelMat)

dataMat 看起来是这样的:

[[1.0, -3.0, -2.0], [1.0, -2.0, 3.0], [1.0, -1.0, -4.0], 
[1.0, 2.0, 3.0], [1.0, 3.0, 4.0], [1.0, -1.0, 9.0], 
[1.0, 2.0, 14.0], [1.0, 1.0, 17.0], [1.0, 3.0, 12.0], [1.0, 0.0, 8.0]]

labelMat

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

我们将尝试使用梯度上升来拟合逻辑回归模型的最优参数或权重(θi)到我们的数据。theta 的计算方法如下:

Python 代码行可能如下所示:

dataMat, labelMat = loadDataSet()
# convert the dataMat to a matrix object
dataMatrix = mat(dataMat)
# convert the dataMat to a matrix object and transpose this matrix
labelMatrix = mat(labelMat).transpose()
# get m, n from the dataMatrix
# m is the number of data points, n is the number of features (xi) of each data point
m,n = shape(dataMatrix)
eta = 0.001
thetas = ones((n,1))
# using the sigmoid function
sig = sigmoid(dataMatrix*thetas)
error = (labelMatrix - sig)
# calculate thetas
thetas = thetas + eta * dataMatrix.transpose()* error

学习过程必须重复很多次(例如 500 次),因此代码行可以重写为:

...
numIter = 500
for k in range(numIter):
     # using the sigmoid function
     sig = sigmoid(dataMatrix*thetas)
     error = (labelMatrix - sig)
     # calculate thetas
     thetas = thetas + eta * dataMatrix.transpose()* error

以上所有代码都可以放入 gradAscent() 函数中。

def gradAscent(dataMat, labelMat, numIter):
     # convert the dataMat to a matrix object
     dataMatrix = mat(dataMat)
     # convert the dataMat to a matrix object and transpose this matrix
     labelMatrix = mat(labelMat).transpose()
     # get m, n from the dataMatrix
     # m is the number of data points, n is the number of features (xi) of each data point
     m,n = shape(dataMatrix)
     eta = 0.001
     thetas = ones((n,1))
     for k in range(numIter):
           # using the sigmoid function
           sig = sigmoid(dataMatrix*thetas)
           error = (labelMatrix - sig)
           # calculate thetas
           thetas = thetas + eta * dataMatrix.transpose()* error
     return thetas

我们正在求解一组权重(或参数),用于绘制一条分隔不同数据类别(0 和 1)的直线,我们可以使用 matplotlib 来绘制这条直线。以下代码行将绘制两个类别(0 和 1)数据点的散点图。

dataMat,labelMat=loadDataSet()
thetas = gradAscent(dataMat,labelMat)
thetaArr = np.array(thetas)
dataArr = np.array(dataMat)
# get the number of data points
m = shape(dataArr)[0]
xcord1 = []; ycord1 = []
xcord2 = []; ycord2 = []
# classifying data points in two classes (0 and 1)
for i in range(n):
    if int(labelMat[i])== 1:
        xcord1.append(dataArr[i,1]); ycord1.append(dataArr[i,2])
    else:
        xcord2.append(dataArr[i,1]); ycord2.append(dataArr[i,2])
# making a scatter plot
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(xcord1, ycord1, s=30, c='red', marker='s')
for j in range(0,len(xcord1)):
      ax.annotate("1", (xcord1[j],ycord1[j]))
ax.scatter(xcord2, ycord2, s=30, c='green')
for k in range(0,len(xcord2)):
     ax.annotate("0", (xcord2[k],ycord2[k]))

接下来,为了绘制一条分隔不同数据类别(0 和 1)的直线,我们必须通过求解以下方程来计算 xi (i = 1,2,3):

其中 x1 = 1(我们的假设),x2 是给定的,所以 x3

我们的代码行将是这样的:

# X2 is given
X2 = arange(-3.0, 3.0, 0.1)
# calculating X3
X3 = (-thetaArr[0]-thetaArr[1]*X2)/thetaArr[2]
ax.plot(X2, X3)
plt.xlabel('X2'); plt.ylabel('X3');
plt.show()

结果

正如你所见,最佳拟合线并不是一个好的数据分隔器。我们将通过使用随机梯度上升来改进这一点。随机梯度上升是在线学习算法的一个例子。之所以称为在线,是因为我们可以随着新数据的到来而增量更新分类器,而不是一次性全部更新。

我们将使用以下说明来修改 gradAscent() 函数。

  • 一次只使用一个实例来更新 thetas。
  • 变量 sigerror 现在是单个值而不是向量。
  • eta 在每次迭代时都会改变。
  • 随机选择每个实例来更新 thetas。

修改后的 gradAscent() 函数版本,称为 stocGradAscent(),可能如下所示:

 def stocGradAscent(dataMat, labelMat, numIter):
     dataMatrix = array(dataMat)
     m,n = shape(dataMatrix)
     thetas= ones(n)
     for j in range(numIter):
           dataIndex = list(range(m))
           for i in range(m):
                eta = 4/(1.0+j+i)+0.01
                randIndex = int(random.uniform(0,len(dataIndex)))
                sig = sigmoid(sum(dataMatrix[randIndex]*thetas))
                error = labelMat[randIndex] - sig
                thetas = thetas + eta * error * dataMatrix[randIndex]
                del(dataIndex[randIndex])
     return thetas

到目前为止,结果将是:

关注点

在本篇技巧中,我介绍了逻辑回归——一种寻找非线性函数(称为 sigmoid)的最佳拟合参数的算法。我还介绍了梯度上升及其变体随机梯度上升。您可以在这里查看本篇技巧中的所有源代码。

参考文献

我的系列

历史

  • 2018年7月26日:初始版本
  • 2018年8月21日:第二个版本
  • 2018年8月26日:第三个版本
© . All rights reserved.