背景与目标###

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. CF718C Sasha and Array 线段树+矩阵加速

    正解:线段树 解题报告: 传送门! 首先这种斐波拉契,又到了1e9的范围,又是求和什么的,自然而然要想到矩阵加速昂 然后这里主要是考虑修改操作,ai+=x如果放到矩阵加速中是什么意思呢QAQ? 那不就 ...

  2. fork多线程进程时的坑(转)

    add : 在fork多线程的进程时,创建的子进程只包含一个线程,该线程是调用fork函数的那个线程的副本.在man fork中,有The child process is created with ...

  3. rar压缩类

    using System; using System.Collections.Generic; using System.Text; using System.Diagnostics; namespa ...

  4. C# Asp.net 修改Ueditor编辑器上传图片保存路径

    默认情况下Ueditor图片上传的保存路径是在/ueditor/net/upload/目录下,(如:http://localhost/ueditor/net/upload/123.png), 但是有时 ...

  5. vue.cli项目中src目录每个文件夹和文件的用法

    assets文件夹是放静态资源:components是放组件:router是定义路由相关的配置:view视图:app.vue是一个应用主组件:main.js是入口文件:

  6. ubuntu上第一个hello程序

    1.终端中输入gedit  hello.c ,然后输入程序: 2.使用gcc编译器,编译出在PC上运行的hello可执行程序:gcc  ./hello.c  -o   hello-pc; 3.使用ar ...

  7. http协议下载文件

    通过在 URL 上调用 openConnection 方法创建连接对象.(HttpURLConnection conn = (HttpURLConnection)new URL("网址&qu ...

  8. 【LeetCode每天一题】Combination Sum(组合和)

    Given a set of candidate numbers (candidates) (without duplicates) and a target number (target), fin ...

  9. es6 数组..... ==和===的区别 es6的递归方式 es6find函数 timer setTimeout v-html的用法,-

    相当于push了  find函数来找到某个值 如果新建一个 setTimeout 的timer 首先得清除这个timer. v-html用法之一就是加载后台传过来的模板

  10. 21-Python3 模块

    fibo.py文件 def fib(n): a,b = 0,1 while b<n: print(b,end='') a,b = b,a+b print() def fib2(n): resul ...