机器学习基本算法介绍(第四部分)
逻辑回归介绍
引言
在本篇技巧中,我将介绍一种优化算法:逻辑回归。在统计学中,逻辑模型通常用于解释二元因变量。在回归分析中,逻辑回归是指估计逻辑模型的参数(维基百科)。我还会介绍梯度上升及其变体——随机梯度上升。
背景
我从多个来源学习,您可以在“参考文献”部分找到它们。这是我对所学内容的总结。
逻辑回归
逻辑回归是一种分类算法,它通过学习一个函数来近似 P(Y |X)。它有一个核心假设,即 P(Y|X) 可以近似为应用于输入特征线性组合的 sigmoid 函数。这个假设通常以等价形式写为
其中
在逻辑回归中,θ (theta) 是一个长度为 m 的参数向量,我们将根据 n 个训练样本学习这些参数的值。参数的数量应等于每个数据点的特征数量(xi)。函数 σ(z) (sigma z) 称为逻辑函数(或 sigmoid 函数)。
对数似然
为了选择逻辑回归参数的值,我们使用最大似然估计 (MLE)。我们可以写出所有数据的似然
对数似然方程为
对数似然的梯度
现在我们有了对数似然的函数,我们只需要选择最大化它的 theta 值。我们可以通过使用优化算法来找到最佳的 theta 值。我们使用的优化算法需要对数似然关于每个参数的偏导数
我们已经准备好进行优化算法了。为此,我们采用一种称为梯度上升的算法。梯度上升的思想是,梯度指向“上坡”方向。如果你不断地沿着梯度的方向迈出小步,你最终会到达一个局部最大值。每次小步更新的参数计算如下:
其中 η (eta) 是我们迈出步子的大小,也称为学习率。如果你不断使用上面的方程更新 θ,你最终会收敛到最佳的 θ 值。方括号中的表达式也称为实际类别和预测类别之间的误差。
Using the Code
我们将重点关注二元分类问题,其中 y 只能取两个值:0
和 1
。0
也称为负类,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
我们可以尝试使用以下代码行来测试 dataMat
和 lableMat
的结果。
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。
- 变量
sig
和error
现在是单个值而不是向量。 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)的最佳拟合参数的算法。我还介绍了梯度上升及其变体随机梯度上升。您可以在这里查看本篇技巧中的所有源代码。
参考文献
- https://support.sas.com/rnd/app/stat/papers/logistic.pdf
- https://web.stanford.edu/~jurafsky/slp3/7.pdf
- http://cs229.stanford.edu/notes/cs229-notes1.pdf
- https://math.oregonstate.edu/home/programs/undergrad/CalculusQuestStudyGuides/vcalc/grad/grad.html
- https://www.khanacademy.org/math/multivariable-calculus/multivariable-derivatives/partial-derivative-and-gradient-articles/a/the-gradient
- http://cs.wellesley.edu/~sravana/ml/logisticregression.pdf
- https://web.stanford.edu/class/archive/cs/cs109/cs109.1178/lectureHandouts/220-logistic-regression.pdf
- https://web.stanford.edu/class/archive/cs/cs109/cs109.1176/lectures/23-LogisticRegression.pdf
我的系列
历史
- 2018年7月26日:初始版本
- 2018年8月21日:第二个版本
- 2018年8月26日:第三个版本