机器学习实战——第十章-聚类

import numpy

def loadDataSet(fileName):  # general function to parse tab -delimited floats
    dataMat = []  # assume last column is target value
    fr = open(fileName)#打开文件
    for line in fr.readlines():#读取每一行数据
        curLine = line.strip().split('\t')#去掉首位的空格,并且以‘\t’分割数据
        fltLine = list(map(float, curLine))  # map all elements to float()使用map把函数转化为float类型
        dataMat.append(fltLine)
    return dataMat


def distEclud(vecA, vecB):#计算向量A和向量B之间的距离
    return numpy.sqrt(numpy.sum(numpy.power(vecA - vecB, 2)))  # la.norm(vecA-vecB)

#随机生成中心
def randCent(dataSet, k):
    n = numpy.shape(dataSet)[1]#得到数据列的数量,即数据的维度
    centroids = numpy.mat(numpy.zeros((k, n)))  # create centroid mat创建一个由k个质心组成的零矩阵
    for j in range(n):  # create random cluster centers, within bounds of each dimension
        minJ = min(dataSet[:, j])#得到第j个维度的最小值
        rangeJ = float(max(dataSet[:, j]) - minJ)#得到第j个维度的取值范围
        centroids[:, j] = numpy.mat(minJ + rangeJ * numpy.random.rand(k, 1))#生成k*1的随机数(在数据该维度的取值范围内)
    return centroids


def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):#输入变量有4个,数据集,聚类中心的个数,计算距离函数和随机生成聚类中心函数
    m = numpy.shape(dataSet)[0]#得到数据的个数
    clusterAssment = numpy.mat(numpy.zeros((m, 2)))  # create mat to assign data points生成m*2的零矩阵
    # to a centroid, also holds SE of each point
    centroids = createCent(dataSet, k)#随机创建k个中心
    clusterChanged = True#中心是否改变的标志
    while clusterChanged:
        clusterChanged = False
        for i in range(m):  # for each data point assign it to the closest centroid循环每个数据点
            minDist = numpy.inf
            minIndex = -1
            for j in range(k):#循环每个聚类中心
                distJI = distMeas(centroids[j, :], dataSet[i, :])#计算每个数据点到聚类中心的距离
                if distJI < minDist:#选择距离最小的聚类中心
                    minDist = distJI#赋值
                    minIndex = j#最小的聚类中心的标志
            if clusterAssment[i, 0] != minIndex: clusterChanged = True#该数据的最近的聚类中心不会发生变化,当所有的数据最近的聚类中心不会变化时,停止迭代
            clusterAssment[i, :] = minIndex, minDist ** 2#给clusterAssment每行赋值,第一个值是那个聚类中心距离该数据点距离最小,第二个值是最小距离的平方是多少
        print (centroids)#输出聚类中心
        for cent in range(k):  # recalculate centroids重新计算聚类中心
            ptsInClust = dataSet[numpy.nonzero(clusterAssment[:, 0].A == cent)[0]]  # get all the point in this cluster得到属于该聚类中心的所有点
            centroids[cent, :] = numpy.mean(ptsInClust, axis=0)  # assign centroid to mean计算平均值
    return centroids, clusterAssment#返回聚类中心和数据属于哪个聚类中心的矩阵


def biKmeans(dataSet, k, distMeas=distEclud):
    m = numpy.shape(dataSet)[0]#得到数据的数量
    clusterAssment = numpy.mat(numpy.zeros((m, 2)))#簇分配结果矩阵
    centroid0 = numpy.mean(dataSet, axis=0).tolist()[0]#第一个聚类中心,计算所有数据的平均值
    centList = [centroid0]  # create a list with one centroid创建一个列表保存聚类中心
    for j in range(m):  # calc initial Error循环每个数据
        clusterAssment[j, 1] = distMeas(numpy.mat(centroid0), dataSet[j, :]) ** 2#计算每个数据和第一个聚类中心的误差
    while (len(centList) < k):#判断聚类中心的数量和自定义的数量
        lowestSSE = numpy.inf#初始化最小的SSE
        for i in range(len(centList)):#循环每个聚类中心,range(10)循环的是  0—9
            ptsInCurrCluster = dataSet[numpy.nonzero(clusterAssment[:, 0].A == i)[0],:]  # get the data points currently in cluster i找到属于聚类中心i的数据点
            centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas)#计算出两个聚类中心时的聚类中心和簇分配结果矩阵
            sseSplit = sum(splitClustAss[:, 1])  # compare the SSE to the currrent minimum计算第i个聚类中心数据点总的SSE之和
            sseNotSplit = sum(clusterAssment[numpy.nonzero(clusterAssment[:, 0].A != i)[0], 1])#计算不属于第i个聚类中心数据点的数据SSE之和
            print("sseSplit, and notSplit: ", sseSplit, sseNotSplit)
            if (sseSplit + sseNotSplit) < lowestSSE:#判断把第i个聚类中心分成两个后,总的误差是否减小
                bestCentToSplit = i#如果减小,进行一系列的赋值
                bestNewCents = centroidMat
                bestClustAss = splitClustAss.copy()
                lowestSSE = sseSplit + sseNotSplit
        #为什么这儿有两个类别,因为上面的步骤就是每次把第i个分为2个聚类中心
        bestClustAss[numpy.nonzero(bestClustAss[:, 0].A == 1)[0], 0] = len(centList)  # change 1 to 3,4, or whatever将类别为1的更改为聚类中心的长度
        bestClustAss[numpy.nonzero(bestClustAss[:, 0].A == 0)[0], 0] = bestCentToSplit#将类别为0的变为前面的第i个长度
        print('the bestCentToSplit is: ', bestCentToSplit)
        print('the len of bestClustAss is: ', len(bestClustAss))
        # ----3. 用最优分隔点来重构聚类中心----#
        # 覆盖: bestNewCents[0,:].tolist()[0]附加到原有聚类中心的bestCentToSplit位置
        # 增加: 聚类中心增加一个新的bestNewCents[1,:].tolist()[0]向量
        #例如:目前划分成了0,1两个簇,而要求划分成3个簇,则在算法进行时,假设对1进行划分得到的SSE最小,则将1划分成了2个簇,其返回值为0,1
        #两个簇,将返回为1的簇改成2,返回为0的簇改成1,因此现在就有0,1,2三个簇了。
        centList[bestCentToSplit] = bestNewCents[0, :].tolist()[0]  # replace a centroid with two best centroids
        centList.append(bestNewCents[1, :].tolist()[0])
        clusterAssment[numpy.nonzero(clusterAssment[:, 0].A == bestCentToSplit)[0],:] = bestClustAss  # 把原来的属于第i个聚类中心的数据的簇分配结果矩阵换为新的簇分配结果矩阵
        #新的矩阵变成了两类
        # reassign new clusters, and SSE更新新的对应于bestCentToSplit的聚类中心bestClustAss的值
    return numpy.mat(centList), clusterAssment
#导入urllib
import urllib
#导入json模块
import json
#利用地名,城市获取位置经纬度函数
def geoGrab(stAddress, city):
    # 获取经纬度网址
    apiStem = 'http://where.yahooapis.com/geocode?'  # create a dict and constants for the goecoder
    # 初始化一个字典,存储相关参数
    params = {}#定义一个空字典
    # 返回类型为json
    params['flags'] = 'J'  # JSON return type
    # 参数appid
    params['appid'] = 'aaa0VN6k'
    # 参数地址位置信息
    params['location'] = '%s %s' % (stAddress, city)
    # 利用urlencode函数将字典转为URL可以传递的字符串格式
    url_params = urllib.parse.urlencode(params) #字典转换为字符串。#
    '''这两条语句一直出问题,
     url_params = urllib.parse.urlencode(params)
      c = urllib.request.urlopen(yahooApi)
     还没有解决,如果解决会及时更新'''
    # 组成完整的URL地址api
    url_params = url_params
    yahooApi = apiStem + url_params  # print url_params
    # 组成完整的URL地址api
    print(yahooApi)
    # 打开URL,返回json格式的数据
    c = urllib.request.urlopen(yahooApi) #打开URL读取,URL传递字符串
    # 返回json解析后的数据字典
    return json.loads(c.read()) #将json格式解码为字典。

from time import sleep
#具体文本数据批量地址经纬度获取函数
def massPlaceFind(fileName):
    #新建一个可写的文本文件,存储地址,城市,经纬度等信息
    fw=open('places.txt','wb+')
    #遍历文本的每一行
    for line in open(fileName).readlines():
        #去除首尾空格
        line =line.strip()
        #按tab键分隔开
        lineArr=line.split('\t')
        #利用获取经纬度函数获取该地址经纬度
        retDict=geoGrab(lineArr[1],lineArr[2])
        #如果错误编码为0,表示没有错误,获取到相应经纬度
        if retDict['ResultSet']['Error']==0:
            #从字典中获取经度
            lat=float(retDict['ResultSet']['Results'][0]['latitute'])
            #纬度
            lng=float(retDict['ResultSet']['Results'][0]['longitute'])
            #打印地名及对应的经纬度信息
            print("%s\t%f\t%f" % (lineArr[0], lat, lng))
            #将上面的信息存入新的文件中
            fw.write('%s\t%f\t%f\n' % (line, lat, lng))
        #如果错误编码不为0,打印提示信息
        else:
            print('error fetching')
        #为防止频繁调用API,造成请求被封,使函数调用延迟一秒
        sleep(1)
    #文本写入关闭
    fw.close()
def distSLC(vecA, vecB):  # Spherical Law of Cosines # 返回地球表面两点之间的距离
    a = numpy.sin(vecA[0, 1] * numpy.pi / 180) * numpy.sin(vecB[0, 1] * numpy.pi / 180)
    b = numpy.cos(vecA[0, 1] * numpy.pi / 180) * numpy.cos(vecB[0, 1] * numpy.pi / 180) * \
        numpy.cos(numpy.pi * (vecB[0, 0] - vecA[0, 0]) / 180)
    return numpy.arccos(a + b) * 6371.0  # pi is imported with numpy

 机器学习实战——第十章-聚类

 

import matplotlib
import matplotlib.pyplot as plt


def clusterClubs(numClust=5):#只有一个输入,所希望得到的簇的数目
    datList = []
    for line in open('places.txt').readlines():#打开文件,读取每一行数据
        lineArr = line.split('\t')#用制表符进行分割
        datList.append([float(lineArr[4]), float(lineArr[3])])#把第4,3个元素变为浮点数并且加到datList中
    datMat = numpy.mat(datList)#把数据集变为矩阵
    myCentroids, clustAssing = biKmeans(datMat, numClust, distMeas=distSLC)#计算出聚类的中心点和所形成簇分类的结果
    fig = plt.figure()# 可视化簇和簇质心。
    rect = [0.1, 0.1, 0.8, 0.8]#创建矩形。
    ####为了画出这幅图,首先创建一幅画,一个矩形
    scatterMarkers = ['s', 'o', '^', '8', 'p', \
                      'd', 'v', 'h', '>', '<'] #使用唯一标记来标识每个簇。
    axprops = dict(xticks=[], yticks=[])
    ax0 = fig.add_axes(rect, label='ax0', **axprops)#绘制一幅图,图0
    imgP = plt.imread('Portland.png')#调用 imread 函数,基于一幅图像,来创建矩阵。
    ax0.imshow(imgP) #调用imshow ,绘制(基于图像创建)矩阵的图
    ax1 = fig.add_axes(rect, label='ax1', frameon=False)#绘制一幅新图,图1。 作用:使用两套坐标系统(不做任何偏移或缩放)。
    ###遍历每个簇,把它画出来。
    for i in range(numClust):#循环每个簇
        ptsInCurrCluster = datMat[numpy.nonzero(clustAssing[:, 0].A == i)[0], :]#挑选出该簇所有点。
        markerStyle = scatterMarkers[i % len(scatterMarkers)] #从前面创建的标记列表中获得标记。使用索引i % len(scatterMarkers)选择标记形状。
        # 作用:更多的图可以使用这些标记形状。
        ax1.scatter(ptsInCurrCluster[:, 0].flatten().A[0], ptsInCurrCluster[:, 1].flatten().A[0], marker=markerStyle,
                    s=90)#flatten是numpy.ndarray.flatten的一个函数,即返回一个折叠成一维的数组。但是该函数只能适用于numpy对象,即array或者mat,
        #你的xMat折叠成一维数组,而且是按A的方式,进行折叠,然后[0]是取第一个元素
        # 每个簇的所有点ptsInCurrCluster,根据标记画出图形。
    ax1.scatter(myCentroids[:, 0].flatten().A[0], myCentroids[:, 1].flatten().A[0], marker='+', s=300) #使用 + 标记来表示簇中心,并在图中显示。
    plt.show()

 机器学习实战——第十章-聚类