Skip to main content

来写一个逻辑回归吧(二)

00:00/00:00

在上一篇文章里, 利用猫狗的图片, 实现了一个逻辑回归, 做了一些训练, 做出了一个了简单的模型, 这篇文章我们继续做下去, 解决上一次遇到的一些问题

计算速度

在上一篇文章里, 最多训练了1000张10*10的图片, 因为按照那种写法, 再多维度就会导致运算实际上不能完成

在吴恩达的课程中, 提到了使用矩阵化代替for函数可以显著提高运行速度, 我们先尝试一下看下效果, 再分析原因

按照之前的写法, 在aws的 x1.32xlarge(128 vcpu * Intel Xeon E7-8880 v3 (Haswell), 2T memory)机型上, 我们训练100张50*50的图片10次, 统计一下训练时间:

import cv2
import numpy as np
import time

m = 100 # 学习的图片张数目, 其中一半是狗, 一半是猫
height = 50 # 图片格式化的高度
width = 50 # 图片格式化的宽度
n = width*height*3 # 图片维度

trains = np.ndarray((m, n, 1))
labels = np.ndarray((m, 1))

for i in range(m/2):
    dog = cv2.imread("data/train/dog.%d.jpg" % i)
    cat = cv2.imread("data/train/cat.%d.jpg" % i)
    trains[2*i] = cv2.resize(dog, (width, height)).reshape((n, 1)) / (255.0 * n) # 归一化一下维度值
    labels[2*i][0] = 1
    trains[2*i+1] = cv2.resize(cat, (width, height)).reshape((n, 1)) / (255.0 * n)
    labels[2*i+1][0] = 0

w = np.zeros((1, n))
b = 0

loop = 10
a = 1

jFunctions = []
correctRate = []

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

startTime = time.time()
for l in range(loop):
    dw = np.zeros((n, 1))
    db = 0
    for j in range(n):
        for k in range(m):
            dw[j][0] = dw[j][0] + trains[k][j][0] * (sigmoid(np.dot(w, trains[k])[0][0] + b) - labels[k][0])
        dw[j][0] = dw[j][0] / m
    for k in range(m):
        db = db + (sigmoid(np.dot(w, trains[k])[0][0] + b) - labels[k][0])
    db = db / m

    for i in range(n):
        w[0][i] = w[0][i] - a * dw[i][0]
    b = b - a * db

endTime = time.time()
print "w[0][0] is %s, b is %s" % (w[0][0], b)
print "train cost time %s s" % (endTime-startTime)
w[0][0] is 1.63399128095e-05, b is 8.82553914705e-07
train cost time 73.1285889149 s

运行结果为:

w[0][0] is 1.63399128095e-05, b is 8.82553914705e-07
train cost time 73.1285889149 s

期间我们看一下系统top, cpu占用率最高100%, 一个核心

接下来, 我们将代码矩阵化, 再次运行同样的逻辑

import cv2
import numpy as np
import time

m = 100 # 学习的图片张数目, 其中一半是狗, 一半是猫
height = 50 # 图片格式化的高度
width = 50 # 图片格式化的宽度
n = width*height*3 # 图片维度

trains = np.ndarray((m, n))
labels = np.ndarray((m))

for i in range(m/2):
    dog = cv2.imread("data/train/dog.%d.jpg" % i)
    cat = cv2.imread("data/train/cat.%d.jpg" % i)
    trains[2*i] = cv2.resize(dog, (width, height)).reshape((n)) / (255.0 * n) # 归一化一下维度值
    labels[2*i] = 1
    trains[2*i+1] = cv2.resize(cat, (width, height)).reshape((n)) / (255.0 * n)
    labels[2*i+1] = 0

w = np.zeros((1, n))
b = 0

loop = 10
a = 1

jFunctions = []
correctRate = []

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

startTime = time.time()
for _ in range(loop):    
    dw = np.zeros((1, n))
    db = 0
    dw = (dw + np.dot((sigmoid(np.dot(w, trains.T) + b) - labels), trains))/m
    db = (db + np.sum((sigmoid(np.dot(w, trains.T) + b) - labels)))/m

    w = w - a * dw
    b = b - a * db

endTime = time.time()
print "w[0][0] is %s, b is %s" % (w[0][0], b)
print "train cost time %s s" % (endTime-startTime)
w[0][0] is 1.63399128095e-05, b is 8.82553914702e-07
train cost time 0.00550103187561 s

运行结果为:

w[0][0] is 1.63399128095e-05, b is 8.82553914702e-07
train cost time 0.00550103187561 s

(输出w和b的目的是对比实验结果, 保证运算结果一致)

运行速度比之前提高了20000倍, 四个数量级

运行速度有点快, 不好截图, 在增大一些参数后, 再次运行

期间我们查看系统top, 发现cpu占用率可以达到百分之几千

由此可以得出, 矩阵化加速的主要原因在于numpy库通过自身实现, 绕过了python中GIL的限制, 实现了多核运算, 所以, 矩阵化对于运算时间的节省非常重要, 能够矩阵化的代码一定要避免自己写for循环

同时, 矩阵化之后, 代码简洁了非常多, 看起来很干净

接下来, 我们使用矩阵化的代码进行一些分析

效果分析

在评判二分类模型效果时, 常用的方式有准确率, logloss, roc曲线, auc

准确率最好理解, 就是将结果判断正确的概率, 准确率的问题有两个, 一个是逻辑回归输出的值为区间在(0,1)之间的数字, 为了将数字对应为类别, 还需要一个阈值, 阈值是模型之外的参数, 第二个是准确率与测试集的正负例分布还有关系, 一个一直输出1的模型在正例比较多时候会有比较好的准确率, 但是难以说明模型本身比较好

logloss也比较好理解, 就是我们用来做梯度训练的代价函数的平均值的二分之一

roc值被定义为在给定区分阈值时, (伪阳性率, 真阳性率)所代表的点, 在选取不同的区分阈值时, 这些点可以构成一个曲线, 这个曲线为roc曲线, auc值被定义为roc曲线的面积

auc值的一个意义是, 在给定一个正样本, 一个负样本时, 分类器给出的正样本输出大于负样本输出的概率, 因此auc越接近1效果越好

在这个例子中, 我们可以设定同样的正负样本比例, 使用阈值0.5, 使用准确率来评判模型效果

在上一章中, 我们使用的是训练准确率, 这次我们使用测试准确率来评判

我们将1w个训练样本分为8000*22000*2两份, 8000用来做训练参数用, 2000用来验证, 使用300*300像素训练50轮次, 得到的效果图如下所示:

import cv2
import numpy as np
import time
import pylab as pl

m = 8000 * 2 # 学习的图片张数目, 其中一半是狗, 一半是猫
mForTests = 2000 * 2
height = 300 # 图片格式化的高度
width = 300 # 图片格式化的宽度
n = width*height*3 # 图片维度

trains = np.ndarray((m, n))
labels = np.ndarray((m))

tests = np.ndarray((mForTests, n))
labelsForTests = np.ndarray((mForTests))

for i in range(m/2):
    dog = cv2.imread("data/train/dog.%d.jpg" % i)
    cat = cv2.imread("data/train/cat.%d.jpg" % i)
    trains[2*i] = cv2.resize(dog, (width, height)).reshape((n)) / (255.0 * n) # 归一化一下维度值
    labels[2*i] = 1
    trains[2*i+1] = cv2.resize(cat, (width, height)).reshape((n)) / (255.0 * n)
    labels[2*i+1] = 0

for i in range(mForTests/2):
    dog = cv2.imread("data/train/dog.%d.jpg" % (m/2+i))
    cat = cv2.imread("data/train/cat.%d.jpg" % (m/2+i))
    tests[2*i] = cv2.resize(dog, (width, height)).reshape((n)) / (255.0 * n) # 归一化一下维度值
    labelsForTests[2*i] = 1
    tests[2*i+1] = cv2.resize(cat, (width, height)).reshape((n)) / (255.0 * n)
    labelsForTests[2*i+1] = 0


w = np.zeros((1, n))
b = 0

loop = 50
a = 1

correctRate = []

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

startTime = time.time()
for _ in range(loop):    
    dw = np.zeros((1, n))
    db = 0
    dw = (dw + np.dot((sigmoid(np.dot(w, trains.T) + b) - labels), trains))/m
    db = (db + np.sum((sigmoid(np.dot(w, trains.T) + b) - labels)))/m

    w = w - a * dw
    b = b - a * db

    s = 0
    for i in range(mForTests):
        r = sigmoid(np.dot(w, tests[i].T) + b)
        if r <= 0.5 and labelsForTests[i] == 0:
            s = s + 1
        if r > 0.5 and labelsForTests[i] == 1:
            s = s + 1
    correctRate.append(s*100.0/mForTests)

endTime = time.time()
print "w[0][0] is %s, b is %s" % (w[0][0], b)
print "train cost time %s s" % (endTime-startTime)

pl.grid()
x = range(loop)
y = correctRate
pl.xlabel("loop(times)")
pl.ylabel("correctRate(%)")
pl.plot(x, y)
pl.show()
w[0][0] is 1.24502944552e-06, b is -1.6534441578e-07
train cost time 193.686454058 s

准确率如图所示, 最高达到56.5%, 之后开始下降, 说明我们的训练在给定的训练集和测试集上出现了过拟合现象

模型应用

在这种简单的逻辑回归模型下, 上线所经历的过程为:
1. 模型加载
2. 输入预处理
3. 打分
4. 根据阈值判断正负

具体的代码就不写了, 价值不是很大

小结

在这篇笔记里, 我们尝试了矩阵化加速代码运行速度, 调参优化效果等操作, 得到的结论有:

  1. 矩阵化能够大幅度提高代码运行速度
  2. 训练轮次增多可以提升准确率
  3. 训练轮次过多存在过拟合

在接下来的笔记中, 会试图通过更多的方法来优化分类效果

打赏
微信扫一扫支付
微信logo微信扫一扫, 打赏作者吧~