背景与目标###

Youzan 是一家SAAS公司,服务于数百万商家,帮助互联网时代的生意人私有化顾客资产、拓展互联网客群、提高经营效率。现在,该公司希望能够从商家的交易数据中,挖掘出有强烈续费倾向的商家,并提供更优质更有针对性的服务。

目标: 从商家交易数据中识别有强烈续费倾向的商家。

思路与建模###

kNN是一种思路简单清晰的有点近似蛮力的机器学习算法。它将待分类数据的特征值集与已分类数据集的每个样本的特征值集进行比较,计算出距离值,然后根据距离最小原则,选择k个距离最小的已分类实例,从这k个已分类实例中选择分类概率最大(出现次数最多)的那个。

从商家交易数据中识别有强烈续费倾向的商家,首先进行建模:

商家的强烈续费意向与哪些交易数据密切关联呢?这里,作出如下假设: 商家的强烈续费意向与“日均成交订单数”、“日均实际成交金额”、“日均累计粉丝数” 三个特征值关联。 下面,就说明运用kNN算法识别潜在续费商家的步骤。 完整源代码见附录。

本文的源代码来自《机器学习实战》 第二章。实现用到了矩阵计算,在代码讲解中会细说。

步骤与实现###

训练样本集####

首先需要准备一个训练样本集,里面含有每个商家的“日均成交订单数”、“日均实际成交金额”、“日均累计粉丝数”、“是否已续费”。为方便起见,可以准备一个原始的数据文件 origin.txt ,然后通过随机值增加的方式(见源代码中的 createMoreSamplesFrom(filename, n)) 生成更大的实际样本集 sample.txt。 这里数据都是伪造的。

12 789 11 0
1089 1200000 134 1
10 9800000 789 1
244 98700 256 0
1234 987653 900 0
40000 50000000 14000 1
30000 600000 120000 1
2500 4700000 9000 1
3 278 55 0
5890 2457788 130000 1
702 89032 890 0
12456 87200012 75000 1
125 90321133 45001 1
600 300020 120000 1
1456 90224441 900000 1
456 12456 2356 0
8 1204 236 0
129000 135700000 8000000 1

数据归一化####

可以看到,日均实际成交金额的值非常大,因此特征值“日均实际成交金额”的距离会起决定性的作用,弱化其他特征值的影响。可以采用数据归一化,使得特征值的绝对值影响弱化。 见函数 autoNorm(dataset)。

对于一个序列 x = [x1, x2, ..., xN] 来说, 归一化数值 xi = (xi - min(x)) / (max(x) - min(x)) 。

矩阵归一化的计算逻辑如下: 假设 x = [[16,4,9], [3,15,27]] ,

step1: 计算每列的最小值 min(x) = [3,4,9] 以及最大值 max(x) = [16,15,27]

step2: 计算最大值与最小值之间的距离:ranges = max(x) - min(x) = [13,11,18]

step3: 计算每行的归一化数值为 ([16,4,9] - [3,4,9]) / [13,11,18] = [1,0,0] , ( [3, 15, 27] - [3,4,9] ) / [13,11,18] = [0, 1, 1] ;这里都是逐个值对应计算。

step4: 得到原矩阵 x 的归一化数值为 [[1,0,0], [0,1,1]]

def autoNorm(dataset):
minVals = dataset.min(0)
maxVals = dataset.max(0)
ranges = maxVals - minVals
normDataset = zeros(shape(dataset))
rows = dataset.shape[0]
normDataset = dataset - tile(minVals, (rows,1))
normDataset = true_divide(normDataset, tile(ranges, (rows,1)))
return normDataset, ranges, minVals

可视化数据####

可以通过图形初步展示数据的形状, 见函数 draw(dataset)

计算距离向量####

得到归一化数据后,可以计算输入向量与每一个样本实例的距离,得到距离向量。见函数 computeDistance(inX, dataset) 。 这里 inX 是输入向量,dataset 是训练样本矩阵。

这里为了计算 inX 与每一个样本实例的距离值,需要将 inX 在行方向上平铺N次,N 是 dataset 的总行数,因此使用 tile(inX, (dataset.shape[0],1)); 然后进行矩阵减法和平方,再将每行的平方和进行求根,得到输入向量与每个样本实例的距离。

比如 inX = [0.1,0.5,0.8] , dataset = [[1,0,0], [0,1,1]] , 距离向量为 D; D.rows(1) = ((-0.9)^2 + (0.5)^2 + (0.8) 2)(0.5) = 1.3038 ; D.rows(2) = (0.01 + 0.25 + 0.04)^(0.5) = 0.5477 ; D = [1.3038, 0.5477]

def computeDistance(inX, dataset):
datasetrows = dataset.shape[0]
diffMat = tile(inX, (datasetrows, 1)) - dataset
sqDiffMat = diffMat ** 2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances ** 0.5
return distances

分类算法####

得到距离向量后,就可以进行分类了。先对距离向量从小到大进行排序,然后取前k个样本实例的分类值 class[k],接着计算这k个分类值中哪个分类值的出现概率最大。 见 函数 classify(inX, dataset, labels, k) , 这里 labels 是样本集的分类标签向量,k 是算法的设置变量,取前k个距离最近的样本的分类标签。

    distances = computeDistance(inX, dataset)
sortedDistIndicies = distances.argsort()
classCount = {}
for i in range(k):
voteLabel = labels[sortedDistIndicies[i]]
classCount[voteLabel] = classCount.get(voteLabel, 0) + 1
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]

分类算法的错误率####

评价分类算法的错误率是非常重要的。见函数 computeErrorRatio。

将已含分类标签值的样本集实例TotalSample分为两类,一个是用于计算距离向量的样本集 Sample,一个用于测试分类错误率的输入向量集合 inMatrix。一般 inMatrix 占总TotalSample的 10%,可以作为算法设置 ratio 。 对于 inMatrix 的每个输入向量,得到其分类标签值,然后与其已有的分类值比较,得到正确或错误的分类结果。分类算法的错误率 = 分类错误的结果数 / inMatrix 的总数。

对未知数据分类####

假设这个分类算法的错误率在接受范围内,那么就可以使用这个分类算法对未知数据分类了。注意,未知数据也要进行归一化。 见函数 classifyInstance(dataMatrix,classLabelVector, ranges, minVals)。

从其他数据文件读取待分类的数据,每行一个, 转换成输入向量 inX ,然后使用 classify 函数进行分类即可得到其分类值。

至此,kNN 算法的基本步骤和实现就完成了。

优化方向###

可以从“可扩展性”和“性能”来优化kNN算法的代码实现。

可扩展性####

在模型中增加更多的因素(比如下单/付款转化率,服务满意度等),可以不改变代码而依然执行。由于整个计算都是基于转化后的矩阵,而输入和输出都是以整个元组为单位,没有耦合业务性的特征值或特征值的数目,因此具备可扩展性。

性能####

主要性能开销集中在计算距离 computeDistance 方法上。 通过将矩阵分块,使用并发或分布式算法,能够在多台机器上运行 kNN 算法,实现对大规模数据样本集的kNN计算。

下面的实现,现将样本数据集 dataset 按行分割为 N 块,分别计算每一块的子距离向量,然后将多个子距离向量合并成一个最终的距离向量。

def divideNParts(total, N):
'''
divide [0, total) into N parts:
return [(0, total/N), (total/N, 2M/N), ((N-1)*total/N, total)]
''' each = total / N
parts = []
for index in range(N):
begin = index*each
if index == N-1:
end = total
else:
end = begin + each
parts.append((begin, end))
return parts def fastComputeDistance(inX, dataset, n):
totalrows = dataset.shape[0]
parts = divideNParts(totalrows, n)
partsResult = getPartResults(inX, dataset, parts)
return array(flatten(partsResult)) def flatten(multiDimenList):
return [item for sublist in multiDimenList for item in sublist] def getPartResults(inX, dataset, parts):
# should use concurrent implementation
partsResult = []
for part in parts:
partsResult.append(computeDistance(inX, dataset[part[0]:part[1], :]))
return partsResult

完整源代码###

需要安装如下包: numpy, matplotlib, fontconfig, operator

# -*- coding: utf-8 -*-
# -------------------------------------------------------------------------------
# Name: potiential_users.py
# Purpose: recognise potiential renewal users using kNN algorithm
#
# Author: qin.shuq
#
# Created: 2018/03/10
#-------------------------------------------------------------------------------
#!/usr/bin/env python import random
import math
import matplotlib
import matplotlib.pyplot as plot
from numpy import *
import operator indicatorNumber = 3
k = 5 def createMoreSamplesFrom(filename, n):
datar = open(filename)
lines = datar.readlines()
datar.close() lineNum = len(lines)
totalLines = lineNum * n
dataw = open('sample.txt', 'w')
for i in range(totalLines):
data = map (lambda x: int(x), lines[random.randint(0,lineNum)].strip().split())
newdata = []
for j in range(indicatorNumber):
newdata.append(str(data[j] + random.randint(0, math.pow(8, j)*10)))
classifierResult = data[-1]
dataw.write('%s %d\n' % (' '.join(newdata), classifierResult))
dataw.close() def file2matrix(filename):
dataf = open(filename)
lines = dataf.readlines()
dataf.close() numOfLines = len(lines)
dataMatrix = zeros((numOfLines, indicatorNumber))
classLabelVector = []
index = 0
for line in lines:
line = line.strip()
listFromLine = line.split()
dataMatrix[index, :] = listFromLine[0:indicatorNumber]
classLabelVector.append(int(listFromLine[-1]))
index += 1
return (dataMatrix, classLabelVector) def autoNorm(dataset):
minVals = dataset.min(0)
maxVals = dataset.max(0)
ranges = maxVals - minVals
normDataset = zeros(shape(dataset))
rows = dataset.shape[0]
normDataset = dataset - tile(minVals, (rows,1))
normDataset = true_divide(normDataset, tile(ranges, (rows,1)))
return normDataset, ranges, minVals def computeDistance(inX, dataset):
datasetrows = dataset.shape[0]
diffMat = tile(inX, (datasetrows, 1)) - dataset
sqDiffMat = diffMat ** 2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances ** 0.5
return distances def divideNParts(total, N):
'''
divide [0, total) into N parts:
return [(0, total/N), (total/N, 2M/N), ((N-1)*total/N, total)]
''' each = total / N
parts = []
for index in range(N):
begin = index*each
if index == N-1:
end = total
else:
end = begin + each
parts.append((begin, end))
return parts def fastComputeDistance(inX, dataset, n):
totalrows = dataset.shape[0]
parts = divideNParts(totalrows, n)
partsResult = getPartResults(inX, dataset, parts)
return array(flatten(partsResult)) def flatten(multiDimenList):
return [item for sublist in multiDimenList for item in sublist] def getPartResults(inX, dataset, parts):
# should use concurrent implementation
partsResult = []
for part in parts:
partsResult.append(computeDistance(inX, dataset[part[0]:part[1], :]))
return partsResult def classify(inX, dataset, labels, k):
distances = fastComputeDistance(inX, dataset, 4)
sortedDistIndicies = distances.argsort()
classCount = {}
for i in range(k):
voteLabel = labels[sortedDistIndicies[i]]
classCount[voteLabel] = classCount.get(voteLabel, 0) + 1
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0] def draw(data):
fig = plot.figure()
ax = fig.add_subplot(111)
ax.scatter(data[:, 0], data[:,1])
#ax.scatter(data[:, 0], data[:,1], 20.0*array(classLabelVector), 20.0*array(classLabelVector))
plot.title('potiential renewal users figure')
plot.xlabel('daily order number')
plot.ylabel('daily actual deal')
#plot.show() def computeErrorRatio(dataMatrix, classLabelVector):
testRatio = 0.10
totalRows = dataMatrix.shape[0]
testRows = int(totalRows*testRatio)
errorCount = 0
for i in range(testRows):
classifierResult = classify(dataMatrix[i,:], dataMatrix[testRows:totalRows, :], classLabelVector[testRows:totalRows], k)
print 'classify result: %d, the real result is %d' % (classifierResult, classLabelVector[i])
if classifierResult != classLabelVector[i]:
errorCount += 1.0
print 'total error rate is %f' % (errorCount / float(testRows)) def classifyInstance(dataMatrix,classLabelVector, ranges, minVals):
dataf = open('data.txt')
for line in dataf:
line = line.strip()
# the first line is id of data, should not be used for distance computation
data = map(lambda x: int(x), line.split())
input = array(list(data[1:len(data)]))
classifierResult = classify((input-minVals)/ranges, dataMatrix, classLabelVector, k)
features = " ".join(map(lambda x: str(x), data[1:]))
print '%d (%s) is %s potiential renewal user' % (data[0], features, "not" if classifierResult != 1 else "" ) def test():
x = array([[16,4,9], [3,15,27], [12,1,18], [20, 7, 15], [7,9,15], [23,8,11], [5,17,26], [3,10,6]])
xNorm = autoNorm(x)
print 'x: ', x , 'norm(x): ', xNorm inX = array([0.1,0.5,0.8])
slowComputeDistances = computeDistance(inX, xNorm[0])
print 'slow distances: ', slowComputeDistances for npart in [1,2,3,4]:
fastComputeDistances = fastComputeDistance(inX, xNorm[0],npart)
print 'fast distances: ', fastComputeDistances for ind in range(slowComputeDistances.shape[0]):
slowComputeDistances[ind] == fastComputeDistances[ind] if __name__ == '__main__': test() createMoreSamplesFrom('origin.txt', 10)
(dataMatrix,classLabelVector) = file2matrix('sample.txt')
(dataMatrix,ranges, minVals) = autoNorm(dataMatrix) #print 'dataset: ', (dataMatrix,classLabelVector)
draw(dataMatrix) computeErrorRatio(dataMatrix, classLabelVector)
classifyInstance(dataMatrix,classLabelVector, ranges, minVals)

矩阵函数####

  • array([[...]]): 将Python多维数组转化为相应的N维矩阵从而可以进行计算;
  • shape: 得到该矩阵的维度属性,是一个元组; m.shape[0],行数 ; m.shape[1] , 列数 ;
  • min(n): 得到该矩阵指定维度的最小值: m.min(0),每列的最小值 ; m.min(1), 每行的最小值;n 不大于 shape 的长度;
  • max(n): 得到该矩阵指定维度的最大值: m.max(0), 每列的最大值;m.max(0),每行的最大值 n 不大于 shape 的长度;
  • tile: 矩阵平铺,从指定的行方向/列方向将原矩阵进行平铺得到新的矩阵;
  • ravel: 多维数组平铺成低维数组
  • argsort: 将向量从小到大排序,排序后的每个元素在原向量中的索引值形成一个新的索引向量。
>>> import numpy as np
>>> x = np.array([[16,4,9], [3,15,27]])
>>> x
array([[16, 4, 9],
[ 3, 15, 27]])
>>> x.shape # 得到 x 的维度信息
(2, 3)
>>> x.shape[0] # 总行数
2
>>> x.shape[1] # 总列数
3
>>> x.min(0) # 列方向的最小值
array([3, 4, 9])
>>> x.min(1) # 行方向的最小值
array([4, 3])
>>> x.max(0) # 列方向的最大值
array([16, 15, 27])
>>> x.max(1) # 行方向的最大值
array([16, 27])
>>>
>>>
>>> np.tile(x, (2,1)) # 将 x 在列方向平铺2次
array([[16, 4, 9],
[ 3, 15, 27],
[16, 4, 9],
[ 3, 15, 27]]) >>> np.tile(x,(1,2)) # 将 x 在行方向平铺2次
array([[16, 4, 9, 16, 4, 9],
[ 3, 15, 27, 3, 15, 27]]) >>>
>>> np.tile(x, (2,2)) # 将 x 在行和列方向分别平铺2次
array([[16, 4, 9, 16, 4, 9],
[ 3, 15, 27, 3, 15, 27],
[16, 4, 9, 16, 4, 9],
[ 3, 15, 27, 3, 15, 27]]) >>> x = np.array([[1,2,3], [1,4,5]])
>>> np.ravel(x)
array([1, 2, 3, 1, 4, 5]) >>> x=np.array([1,4,3,-1,6,9])
>>> x.argsort()
array([3, 0, 2, 1, 4, 5]) # -1 是最小值,在原向量中 index = 3

小结###

本文讲解了如何使用kNN算法来实现识别潜在续费商家。kNN算法依赖于模型的正确性。如果分类标签值与样本特征值有非常密切的关联,使用简单的kNN算法即可得到有效的结果,而且不限于特定的应用领域。只要能够将领域问题转化为样本特征值矩阵,就能使用 kNN 算法来进行求解。

这也是我学习的第一个机器学习算法

运用kNN算法识别潜在续费商家的更多相关文章

  1. 机器学习--kNN算法识别手写字母

    本文主要是用kNN算法对字母图片进行特征提取,分类识别.内容如下: kNN算法及相关Python模块介绍 对字母图片进行特征提取 kNN算法实现 kNN算法分析 一.kNN算法介绍 K近邻(kNN,k ...

  2. KNN算法识别手写数字

    需求: 利用一个手写数字“先验数据”集,使用knn算法来实现对手写数字的自动识别: 先验数据(训练数据)集: ♦数据维度比较大,样本数比较多. ♦ 数据集包括数字0-9的手写体. ♦每个数字大约有20 ...

  3. Python 手写数字识别-knn算法应用

    在上一篇博文中,我们对KNN算法思想及流程有了初步的了解,KNN是采用测量不同特征值之间的距离方法进行分类,也就是说对于每个样本数据,需要和训练集中的所有数据进行欧氏距离计算.这里简述KNN算法的特点 ...

  4. KNN 算法-实战篇-如何识别手写数字

    公号:码农充电站pro 主页:https://codeshellme.github.io 上篇文章介绍了KNN 算法的原理,今天来介绍如何使用KNN 算法识别手写数字? 1,手写数字数据集 手写数字数 ...

  5. 【Machine Learning】KNN算法虹膜图片识别

    K-近邻算法虹膜图片识别实战 作者:白宁超 2017年1月3日18:26:33 摘要:随着机器学习和深度学习的热潮,各种图书层出不穷.然而多数是基础理论知识介绍,缺乏实现的深入理解.本系列文章是作者结 ...

  6. kNN算法python实现和简单数字识别

    kNN算法 算法优缺点: 优点:精度高.对异常值不敏感.无输入数据假定 缺点:时间复杂度和空间复杂度都很高 适用数据范围:数值型和标称型 算法的思路: KNN算法(全称K最近邻算法),算法的思想很简单 ...

  7. 使用KNN算法手写体识别

    #!/usr/bin/python #coding:utf-8 import numpy as np import operator import matplotlib import matplotl ...

  8. knn算法手写字识别案例

    import pandas as pd import numpy as np import matplotlib.pyplot as plt import os from sklearn.neighb ...

  9. 基于OpenCV的KNN算法实现手写数字识别

    基于OpenCV的KNN算法实现手写数字识别 一.数据预处理 # 导入所需模块 import cv2 import numpy as np import matplotlib.pyplot as pl ...

随机推荐

  1. Matlab中添加路径与去除路径

    今天在使用Matlab调用内部的PCA函数的时候,报错: 错误使用 pca输入参数太多. 如下图所示: 网上查找原因之后发现是因为我之前下载过开源的工具包toolbox,并且将它的路径add到了Mat ...

  2. 同步fifo的Verilog实现

    FIFO是一种先进先出的数据缓存器,他与普通存储器相比: 优点:没有外部读写地址线,这样使用起来非常简单: 缺点:只能顺序写入数据,顺序的读出数据, 其数据地址由内部读写指针自动加1完成,不能像普通存 ...

  3. Linux中程序开机自启

    Linux中程序开机自启 https://www.itbulu.com/debian-ubuntu-rclocal.html

  4. dedecms怎样调用指定id文章?

    前面我们聊了帝国cms如何调用指定id的文章到首页,作为同行的织梦cms应该也是可以实现的吧?那么,dedecms怎样调用指定id文章呢?使用idlist直接调用指定的ID这样的方法是比较好的.官方给 ...

  5. 【环境配置】配置jdk

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/manoel/article/details/37736569 1. 下载jdk的安装文件 http: ...

  6. 实现web消息推送的技术和采用长轮询corundumstudio介绍

    实时消息的推送,PC端的推送技术可以使用socket建立一个长连接来实现.传统的web服务都是客户端发出请求,服务端给出响应.但是现在直观的要求是允许特定时间内在没有客户端发起请求的情况下服务端主动推 ...

  7. 关于获取路径path

    String webPath = request.getServletPath(); log.info(webPath); 输出: /zjdlbb/zjdlbb/zjdlbb/test.ht log. ...

  8. javascript封装animate动画

    面向对象式: Element.prototype.animate=animate; Element.prototype.getStyle=getStyle; function animate(json ...

  9. Jmeter知识点

    聚合报告说明 https://www.cnblogs.com/duanxz/p/5464993.html JMeter之Ramp-up Period(in seconds)说明(可同时并发) http ...

  10. SpringBoot 项目健康检查与监控(转)

    前言 You build it,You run it, 当我们编写的项目上线后,为了能第一时间知晓该项目是否出现问题,常常对项目进行健康检查及一些指标进行监控.Spring Boot-Actuator ...