回顾
Logistic回归(一)中最后提到的Logistic回归核心代码如下:
def gradAscent(dataMatIn,classLabels):
dataMatrix = mat(dataMatIn)
labelMat = mat(classLabels).transpose()
m,n = shape(dataMatrix)
alpha = 0.001
maxCycles = 500
weights = ones((n,1))
for k in range(maxCycles):
h = sigmoid(dataMatrix*weights)
error = labelMat - h
weights = weights + alpha * dataMatrix.transpose() * error
return weights
优化
思路
从中可以发现,每一次迭代都会对所有样本进行计算,也就是说每一次运算量 = 特征数样本数。当样本数或特征较多的时候,计算量会十分巨大,这时候就会尝试去减少每一次参与运算的样本数,肯定不能减少特征。这就是作者想要说的随机梯度上升算法。
这里作者提到了两个概念
在线学习算法:在新样本到来的时候对分类器进行增量式更新,随机梯度上升算法就属于这一类
批处理算法:一次性处理所有数据
第一次优化
def stocGradAscent0(dataMatrix,classLabels):
m,n = shape(dataMatrix)
alpha = 0.01
weights = ones(n)
for i in range(m):
h = sigmoid(sum(dataMatrix[i] * weights))
error = classLabels[i] - h
weights = weights + alpha * error * dataMatrix[i]
return weights
分析
这个算法比较简单明了,仅仅是按顺序将每一个样本带入计算了一次。由于梯度下降需要大量样本不断重复强化某一个特征的属性,该特征的参数才会逐渐收敛,所以很明显这样结果不会很理想的。
第二次优化
书中程序:
def stocGradAscent1(dataMatrix,classLabels,numIter = 150):
m,n = shape(dataMatrix)
weights = ones(n)
for j in range(numIter):
dataIndex = range(m)
for i in range(m):
alpha = 0.1/(1.0 + j + i)
randIndex = int(random.uniform(0,len(dataIndex)))
# randIndex = random.choice(dataIndex)
h = sigmoid(sum(dataMatrix[randIndex] * weights))
error = classLabels[randIndex] - h
weights = weights + alpha * error * dataMatrix[randIndex]
del(dataIndex[randIndex])
return weights
由于作者是用的是python2,而我用的是python3,所以其中会有一些不兼容的地方,之后会做出改动。
分析
比较启发人的一点是:步长不是一个定值,而是一个不断减小的值,我们都知道,随着代价函数的优化,当距离最优解比较近的时候,原来的步长会限制进一步靠近最优解,这时候只能靠限制迭代次数来结束程序,但是这样也有可能在后面的迭代过程中并没有得到优化,而步长是一个变值便解决了这一问题。
但是在这个里面,我不太懂:
for j in range(numIter):
按这个来说,反映的是迭代的变化,
for i in range(m):
那呢?意思是说每一次迭代运算次?
先不说python3中无法使用的问题,每次是要把样本全部遍历一次的!这个不会把样本全部删除吗?这个随机生成便没有意义了呀?每一次迭代必然会把所有样本遍历一次,这样的话和未优化版本的区别只在于那个是矩阵运算,这个才分开来,这样效率反而会更低吧?
我实在搞不懂这个地方,再加上版本不兼容问题,自己改进了一下,企图改进这个问题,程序如下:
def stocGradAscent1(dataMatrix,classLabels,numIter = 50):
m,n = shape(dataMatrix)
weights = ones(n)
dataIndex = list(range(m))
for j in range(numIter):
alpha = 0.1/(1.0 + j )
rand = int(random.uniform(0,len(dataIndex)))
randIndex = dataIndex.pop(rand)
#测试代码
# if dataIndex:
# randIndex = dataIndex.pop(rand)
# else:
# print("The list is empty {}".format(j))
# randIndex = random.choice(dataIndex)
h = sigmoid(sum(dataMatrix[randIndex] * weights))
error = classLabels[randIndex] - h
weights = weights + alpha * error * dataMatrix[randIndex]
return weights
思路
我把转换成List进行运算,每次随机出一个随机数,这样也会满足功能需要。除此之外,改为单个循环结构进行循环,没有什么可说的,仅仅是按我的理解写的这个随机梯度上升(下降)而已。
至于作者最后写的实际的例子,便先不做分析,因为只是利用python在进行数据处理,主要是看编程的熟练度了