推荐一本好书(机器学习实战),别问我问什么,那么好的内容,为什么不一起入坑呢。欢迎fold 代码哦,python 代码:
https://github.com/unnunique/MachineLearning/tree/master/Ch02
https://github.com/unnunique/unnunique.github.io/tree/master/ScalaDemo/src/main/scala/com/sydney/dream/classdefine
需要书籍: 请留言,留下邮箱:
转载请注明出处哦,
大体上机器学习分为分类和回归,有监督学习和无监督学习。kNN 属于监督学习和分类算法模块。
在一大堆数据集合中,每一数据都存在自己的标签,即,每一数据都属于一个分类。当我们输入没有分类的新数据的时候,将新数据和数据集合中的每一数据进行比较,然后计算新数据离哪些数据最相近(把原始数据分布在坐标轴上,计算新数据和原始数据的距离)。距离计算好后,我们选取k个最相近的数据,这个就是kNN 中k 的来源。一般上k不大于20,。k个数据中出现最多的分类,即为kNN 算出来的新数据的分类。
我们以kiss 次数和打斗次数来区分是动作电影还是动作电影。
来接一波图和数据。
假如我们有一个位置的电影,问号脸?我们如何来区分他是爱情电影还是动作电影。
我们来计算他距离其他电影的距离。
例如:(18-98)**2 +(90-2)**2, 然后开方。
比如算的的距离最近的三个是Robo Slaye 3000, Kavin Longblode, Califonia Ma, 则起是动作片。(没有计算过哈哈。)
接下来我们用scala 以及python 来各自实现一波:
写代码嘛,先来一段注释,然后请佛祖开光,这个还是很皮很开心的
'''
reated on Mar 03, 2018
kNN: k Nearest Neighbors
Input: inX: vector to compare to existing dataset (1xN)
dataSet: size m data set of known vectors (NxM)
labels: data set labels (1xM vector)
k: number of neighbors to use for comparison (should be an odd number)
Output: the most popular class label
@author: lidiliang
'''
from numpy import *
import operator
## movie dataSets
def createMovieDataSets(): # 函数定义
dataSets = array([[3, 104], [2, 100], [1, 81], [101, 10], [99, 5], [98, 2]]) # python 自带的array 产生一个一维数组
lables = ['A', 'A', 'A', 'B', 'B', 'B'] # 以为数组
return group, lables # 数据返回
def classify0(inX, dataSet, lables, k): # 函数定义
dataSetSize = dataSet.shape[0] # dataSet 行数
diffMatV0 = tile(inX, (dataSetSize, 1)) - dataSet # 两个M*N 的数组相减
diffMatV1 = diffMat**2 # 平方
distancesV0 = diffMatV1.sum(axis=1) # 平方后相加
distancesV1 = distancesV0**0.5 # 开方
sortedDistancesIndexes = distancesV1.argsort() # 距离排序后,返回其索引值
classCount={} # 字典
for i in range(k):
voteLable = lables[sortedDistancesIndexes[i]] # 统计类别
classCount[voteLable] = classCount.get(voteLable, 0) + 1
sortedClassCount = sorted(classCount.iteritems(), key = operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
然后秀一波实验,感觉python 写起来完全像是在写脚本,然后,一个最简单的例子,就这么华丽丽的实现了。
[root@steve-lidiliang /user/MLiA_SourceCode/Ch02]# clear
[root@steve-lidiliang /user/MLiA_SourceCode/Ch02]# python
Python 2.7.5 (default, Nov 6 2016, 00:28:07)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import kNNMovie
>>> dataSets, labels = kNNMovie.createMovieDataSets()
>>> dataSets
array([[ 3, 104],
[ 2, 100],
[ 1, 81],
[101, 10],
[ 99, 5],
[ 98, 2]])
>>> labels
['A', 'A', 'A', 'B', 'B', 'B']
>>> kNNMovie.classify0([0,1000], dataSets, labels, 3)
'A'
>>>
直接上代码实现吧,具体流程和上面python 大致一样,写Scala 好烧脑,哎。
package com.sydney.dream.classdefine
import util.control.Breaks._
// 首先顶一个Movie 类来实现数据的封装
class Movie(var name : String,var action : Int,var kiss : Int,var typeMovie : Int ) {
def this(name : String, action : Int, kiss : Int) {
this(name, action, kiss, 0)
}
override def toString = s"movieName: ${name}, actinNum: ${action}, kissNum: ${kiss}, typeMovie: ${typeMovie}"
}
// 数据模型好了,那当然先再来一个类似java main 的东东了,no bb ,just do it
object Movie {
def main(args : Array[String]): Unit = {
// 准备数据
val movies = Array(new Movie("两小无猜", 1, 100, 0), new Movie("泰坦尼克号", 20, 100, 0), new Movie("初恋那件小事", 0, 500, 0),
new Movie("功夫足球", 100, 3, 1), new Movie("神棍", 300, 1, 1), new Movie("逃学威龙", 200, 3, 1))
// 计算new Movie("test", 50, 50) 到各个已知movie 的距离
val distances = getDistances(new Movie("test", 50, 50), movies)
println("排序前的数据")
for((k, v) <- distances) println(k, v)
// 对计算出来的距离进行排序
val sortedDistances = distances.toSeq.sortWith(_._2>_._2)
println
println("排序后的数据:")
for((k, v) <- sortedDistances) println(k, v)
// 返回分类标签
val lable = getClassLable(movies, sortedDistances, 3)
// 展示最终这个新电影属于哪个分类
println
showLable(lable)
}
def showLable(lable : Int) {
if (lable == 1) {
println("这个片子是动作片......")
} else {
println("这个片子是爱情片......")
}
}
// 返回分类标签
def getClassLable(movies : Array[Movie], sortedDistances : Seq[(Int, Double)], k : Int) : Int = {
var tmp : Map[Int, Int] = Map()
var count = 0
for((key, v) <- sortedDistances) {
if (count >= k) {
} else {
tmp += (movies(key).typeMovie -> (tmp.getOrElse(movies(key).typeMovie, 0) + 1))
}
count = count + 1
}
//for((key, v) <- tmp) {println(key, v)}
val seqTmp = tmp.toSeq.sortWith(_._2 > _._2)
println
println("最相邻的K个数据的分类统计...")
for((key, v) <- seqTmp) {println(key, v)}
seqTmp(0)._1
}
// 获取距离列表
def getDistances(movie : Movie, movies : Array[Movie]) : Map[Int, Double] = {
var distances : Map[Int, Double] = Map()
for (i <- 0 until movies.length) {
val distance2 = Math.pow((movies(i).action - movie.action), 2) + Math.pow((movies(i).kiss - movie.kiss), 2)
val distance = Math.sqrt(distance2)
distances += (i -> distance)
}
distances
}
}
先来简单过一下需求背景。
某都市杭州大龄单身妹纸小百合在某百合相亲网站上进行相亲,寻求高富帅。小百合发现自己相过的对象可以分为三类:
1,十分喜欢的人
2,一般般喜欢的人
3,不喜欢的屌丝
这三类人,可以用下面的特征数据进行衡量:
1,每年飞灰机的里程数。
2,玩游戏的次数。
3,每周吃掉的冰淇淋的公升数。
这些数据保存在txt 文件中类似如下:(数据可以在附件中获取哦,亲,)
32404 3.513921 0.9918541
27268 4.398172 0.9750242
5477 4.276823 1.174874 3
从左到右分别表示:
飞行的里程数, 玩游戏次数 冰淇淋公升数 喜欢程度(1,非常喜欢, 2, 一般般, 3,不喜欢)
来,让我们借鉴电影的例子,来实现相关的程序。
然后把读取到的数据转化为矩阵。
函数如下:(包含kNN 近邻算法的实现)
from numpy import *
import operator
# kNN suan fa
def classify0(inX, dataSet, lables, k):
dataSetSize = dataSet.shape[0]
diffMat = tile(inX, (dataSetSize, 1)) - dataSet
sqDiffMat = diffMat**2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances**0.5
sortedDistIndicies = distances.argssort()
calssCount = {}
for i in range(k):
vateIlable = lables[sortedDistIndicies[i]]
calssCount[vateIlable] = classCount.get(vateIlable, 1) + 1
sortedClassCount = sorted(classCount.iteritems, key = operator.itemgetter(1), reverse = True)
return sortedClassCount[0][0]
# Read data from files
def file2matrix(filename):
fr = open(filename)
arrayLines = fr.readlines()
numberOfLines = len(arrayLines)
returnMat = zeros((numberOfLines, 3))
classLableVector = []
index = 0
for line in arrayLines:
line = line.strip()
listFromLine = line.split('\t')
returnMat[index, :] = listFromLine[0:3]
classLableVector.append(int(listFromLine[-1]))
index += 1
return returnMat, classLableVector
接下来考虑一个问题,如下是4组数据:
我们来计算第三组和第四组的距离:
(0-67)**2 + (20000-32000)**2 + (0.1-1.1)**2,然后开方。
Oh my good(夸张脸), 有没有发现什么。 每年飞行常客里程数对最终结果的影响远远大于其他两个特征。所以计算出来的结果,可能只与每年 飞行常客的里程数有关。
为了解决这个问题:
我们使用一些技巧,把这些数据都转换为0到1之间的数据。
专业上叫做归一化数据:
我们利用如下的公式,把数据转换到0到1之间:
newValue = (oldValue - min)/(max - min)
在上一步,我们已经把数据从文件中读取,并且赋给了矩阵datingDataMat。接下来,我们就用上面这个公式,把这个矩阵中的数据进行归一化(皈依佛门)
# Shu zhi gui yi hua
def autoNorm(dataSet):
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = zeros(shape(dataSet))
m = dataSet.shape[0]
normDataSet = dataSet - tile(minVals, (m, 1))
normDataSet = normDataSet/tile(ranges, (m, 1))
return normDataSet, ranges, minVals
好了,到现在为止,我们改准备的都准备了,数据已有,数据已经皈依佛门,算法已有,接下来我们进行测试:
测试代码如下:
def datingClassTest():
testRate = 0.10
datingDataMat, datingLables = file2matrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
m = normMat.shape[0]
numTest = int(m*testRate)
errorCount = 0.0
for i in range(numTest):
classifierResult = classify0(normMat[i, :], normMat[numTest:m, :], datingLables[numTest:m], 3)
print "the classifier came back with: %d, the real answer is : %d" % (classifierResult, datingLables[i])
if (classifierResult != datingLables[i]): errorCount += 1.0
print "the total error rate is: %f" % (errorCount/float(numTest))
测试结果:
the classifier came back with: 3, the real answer is : 3
the classifier came back with: 2, the real answer is : 3
the classifier came back with: 1, the real answer is : 1
the classifier came back with: 2, the real answer is : 2
the classifier came back with: 1, the real answer is : 1
the classifier came back with: 3, the real answer is : 3
the classifier came back with: 3, the real answer is : 3
the classifier came back with: 2, the real answer is : 2
the classifier came back with: 1, the real answer is : 1
the classifier came back with: 3, the real answer is : 1
the total error rate is: 0.050000
错误率为百分之五,效果还行。
最后,来一段python 代码模拟约会网站优化:
交互式模拟相亲网站过滤过程:
def classifyPerson():
resultList = ['not at all', 'in small doses', 'in large doses']
gameTime = float(raw_input(\
"please input the time to playing video games: "))
flyMiles = float(raw_input(\
"please input the miles to flying: "))
icecream = float(raw_input(\
"please input the consumed icecream: "))
datingDataMat, datingLables = file2matrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = array([flyMiles, gameTime, icecream])
classifierResult = classify0((inArr-minVals)/ranges, normMat, datingLables, 3)
print "You will probably like this person: ", resultList[classifierResult -1]
我比较倾向于直接进行上代码,所以nobb 了。
package com.sydney.dream.classdefine
import scala.io.Source
import scala.collection.mutable.ArrayBuffer
// 用来封装约会数据
class DatingData(var fly : Float, var playGame : Float, var iceCream : Float, var typeLike : Int) {
// 重载构造函数
def this(fly : Float, playGame : Float, iceCream : Float) {
this(fly, playGame, iceCream, 0)
}
// 重写toString 方法
override def toString = s"fly: ${fly}, playGame: ${playGame}, iceCream: ${iceCream}, typeLike: ${typeLike} "
}
// dating 分类程序
/**
* 1, 加载数据
* 2,计算一个未知person 和其他person 的距离
* 3,把数据从大到小排序
* 4, 按照传入的K, 统计并且返回标签
*/
object Dating {
def main(args : Array[String]) : Unit = {
println("starting...")
// 参数验证:
if(args.length != 1) {
println("请传入datingDataSet2.txt 的路径")
}
// 数据处理
// 文件路径
val datingDataSetFilePath = args(0)
// 获取文件对象
val datingDataSetFile = Source.fromFile(datingDataSetFilePath)
// 数据存放在ArrayBuffer 中
var datingDataArrBuf = new ArrayBuffer[DatingData]()
// 封装DatingData, 并且添加到datingDataArrBuf 中
for(line <- datingDataSetFile.getLines) {
val arr = line.split("\t")
val datingData = new DatingData(arr(0).toFloat, arr(1).toFloat, arr(2).toFloat, arr(3).toInt)
datingDataArrBuf += datingData
}
println("datingDataSet 的总行数是:" + datingDataArrBuf.length)
// 计算距离
println("计算距离...")
val datingData = new DatingData(12345.0f, 15.09f , 1.23f)
val distances = getDistance(datingData, datingDataArrBuf)
// 对距离从大到小排序
// 距离排序
println("距离排序...")
val seqDistances = sortDistances(distances)
println("获取最终分类标签...")
val classLable = getClassLable(datingDataArrBuf, seqDistances, 3)
println("最终的分类标签是: " + classLable)
}
// 计算距离
def getDistance(datingData : DatingData, datingDataArrBuf : ArrayBuffer[DatingData]) : Map[Int, Double] = {
//用于结果封装返回
var distances : Map [Int, Double] = Map()
for(i <- 0 until datingDataArrBuf.length) {
// 求取欧式距离的平方
val distance2 = Math.pow((datingDataArrBuf(i).fly - datingData.fly), 2) +
Math.pow((datingDataArrBuf(i).playGame - datingData.playGame), 2) +
Math.pow((datingDataArrBuf(i).iceCream - datingData.iceCream), 2)
// 求取欧氏距离
val distance = Math.sqrt(distance2)
// 把距离添加到返回结果中
distances += (i -> distance)
}
distances
}
// 把计算出来的距离按照从大到小的顺序排序
def sortDistances(distances : Map[Int, Double]) : Seq[(Int, Double)] = {
distances.toSeq.sortWith(_._2 > _._2)
}
// 按照K,统计排序后的距离,返回分类标签
def getClassLable(datingDataArrBuf : ArrayBuffer[DatingData], sortedDistances : Seq[(Int, Double)], k : Int) : Int = {
var tmp : Map[Int, Int] = Map()
var count = 0
for((key, value) <- sortedDistances) {
if (count >= k) {} else {
tmp += (datingDataArrBuf(key).typeLike -> (tmp.getOrElse(datingDataArrBuf(key).typeLike, 0) +1))
}
count = count + 1
}
val seqTmp = tmp.toSeq.sortWith(_._2 > _._2)
seqTmp(0)._1
}
}
写得有些累,scala 和python 都是新学的语言。暂时更新到这里。后面会继续更新,不好意思了,各位大佬们。