<机器学习实战>笔记(一):无监督学习k-means,apriori,FP-grownth

351 阅读6分钟

k-means

  • 算法思想

    ​ 随机初始k个簇质心,通过计算每个点与每个质心的距离对每个点进行分配,一次分配完毕重新计算簇质心;对每个点重新进行分配,直至没有点修改质心分配结果。

  • 伪代码描述

    ​ 随机初始k个簇质心

    ​ while 任一点簇分配结果发生改变

    ​ 对数据集中的每个点

    ​ 分配给质心最近的簇

    ​ 重新计算簇质心

  • 优缺点

    • 优点:容易实现
    • 缺点:可能收敛到局部最小值,大规模数据集上收敛较慢
  • 算法指标

    误差平方和(SSE,sum of squared error):每个点到所在簇质心的距离平方值

后处理

  1. 将具有最大SSE值的簇划分为两个簇
  2. 将某两个簇合并
    • 合并质心距离最近的两个簇
    • 合并所有组合的两个簇后计算SSE,将合并后SSE增加最少的两个簇作为最佳的合并方案

二分k-means

  • 算法思想:所有点作为一个簇一分为二,依照最大程度降低SSE的原则选择其中一个簇进行划分(或者选择SSE最大的簇进行划分),直至划分为k个簇。

  • 伪代码

    ​ 所有点看成一个簇 簇个数n=1

    ​ while n < k:

    ​ 对每一个簇

    ​ 计算sse_1

    ​ 进行k-means(k=2)

    ​ 计算sse_2

    ​ 选择sse_1 - sse_2 最大的簇进行划分

python实现

大一的时候刚学k-means时整理过的笔记:juejin.cn/post/684490… 不重复了

apriori

  • 相关概念
    • 频繁项集:经常一起出现的物品的集合
    • 关联规则:两种物品之间的关系
    • 支持度:数据寄中包含该项集(集合)所占的比例
    • 可信度(/置信度):可信度({A}→{B}) = 支持度({A,B})/ 支持度({A})
  • apriori原理:某个项集是频繁的,那么它的子集也是频繁的;如果一个项集是非频繁的,那么它的超集也是非频繁的。

频繁项集生成

  • 算法思想:扫描单元素项集,去除不满足最小支持度的项集;合并生成包含两个元素的项集,扫描新合并的项集,去除不满足最小支持度的项集;重复直至合并到包含所有元素的项集。
  • 生成候选项集
    • 遍历数据集 生成元素个数为1的候选项集
    • while 项集个数k小于所有元素个数n:
      • 遍历候选集
        • 遍历数据集 统计候选集出现次数
      • 生成频繁项集(移除小于最小支持度的项集)
      • 合并(前k-1个元素相同的两个项集(已排序)进行并运算)生成候选项集

关联规则生成

  • 如果某条规则不满足最小可信度,那么该规则的子集也不满足最小可信度。

算法形式化描述

  • 从包含两个元素起,对每一个频繁项集
    • 生成包含单个元素集合的列表H
      • 对于包含多于两个元素的频繁项集
        • 合并,计算规则置信度;再合并直至元素个数等于频繁项集个数-1
      • 对于包含两个元素的频繁项集:计算其中一个到另一个的置信度

python实现

def loadDataSet():
    return [[1,3,4],[2,3,5],[1,2,3,5],[2,5]]

def creatC1(dataSet):
    c1 = []
    for line in dataSet:
        for item in line:
            if not [item] in c1:
                c1.append([item])
    c1.sort()
    '''
    python2.x中map()返回list列表
    python3.x中map(frozeset,c1)返回迭代器,内循环中使用迭代器出错
    改用for循环强制转换类型
    '''
    for i in range(len(c1)):
        c1[i] = frozenset(c1[i])
    return c1    #转为不可变类型

def scanD(D,Ck,minSupport=0.5):
    sDict = {}
    num = 0 #记录数
    for line in D:
        num += 1
        for c in Ck:
            #判断当前项集是否为该记录子集以统计出现次数进而计算支持度
            if c.issubset(line):
                if c not in sDict.keys():
                    sDict[c] = 1
                else:
                    sDict[c] += 1
    retList = []  #频繁项集
    supportData = {}   #所有项集支持度
    for key in sDict:
        support = sDict[key]/num
        if support >= minSupport:
            retList.append(key)
        supportData[key] = support
    return retList,supportData


def aprioriGen(Lk,k):
    retList = []
    lenLk = len(Lk)
    for i in range(lenLk):
        for j in range(i+1,lenLk):
            L1 = list(Lk[i])[:k-2]
            L2 = list(Lk[j])[:k-2]
            L1.sort()
            L2.sort()
            if L1==L2:
                retList.append(set(Lk[i])|set(Lk[j]))
    for i in range(len(retList)):
        retList[i] = frozenset(retList[i])
    return retList

#生成频繁项集
def apriori(dataSet,minSupport=0.5):
    C1 = creatC1(dataSet)
    for i in range(len(dataSet)):
        dataSet[i] = set(dataSet[i])
    L1,supportData = scanD(dataSet,C1,minSupport)
    L = [L1]
    k = 2
    while (len(L[k-2]) > 0):            #L0,L1...
        Ck = aprioriGen(L[k-2],k)
        Lk,supK = scanD(dataSet,Ck,minSupport)
        supportData.update(supK)
        L.append(Lk)
        k += 1
    return L,supportData

#生成关联规则
def generateRules(L,supportData,minConf):
    bigRuleList = []
    for i in range(1,len(L)):
        for freSet in L[i]: #freSetw为频繁项集
            H1 = [frozenset([item]) for item in freSet]
            if i > 1:
                rulesFromConseq(freSet,H1,supportData,bigRuleList,minConf)
            else:
                calcConf(freSet,H1,supportData,bigRuleList,minConf)
    return bigRuleList

#计算置信度
def calcConf(freqSet,H,supportData,brl,minConf):
    preunedH = []
    for conseq in H:
        conf = supportData[freqSet] / supportData[freqSet-conseq]
        if conf >= minConf:
            brl.append((freqSet-conseq,conseq,conf))
            preunedH.append(conseq)
    return preunedH

def rulesFromConseq(freqSet,H,supportData,brl,minConf):
    m = len(H[0])
    if(len(freqSet)>(m+1)):
        hmpl = aprioriGen(H,m+1)    #合并
        hmpl = calcConf(freqSet,hmpl,supportData,brl,minConf)
        if (len(hmpl)>1):
            rulesFromConseq(freqSet,hmpl,supportData,brl,minConf)

if __name__ == '__main__':
    dataSet = loadDataSet()
    l,s = apriori(dataSet)
    b = generateRules(l,s,0.7)

FP-growth

  • 基于apriori
  • 用于发现频繁项集

FP-growth

  • 算法思想:将数据集存储在FP树,从FP树挖掘频繁项集
  • FP(frequent pattern):频繁模式,存储项集出现频率,通过链接(link)来连接相似元素。
  • 条件模式基(conditional pattern base):以所查元素项为结尾的路径集合
  • 前缀路径(prefix path):介于所查元素项与根节点之间的所有内容

构建FP树

  • 头指针表:指向给定类型的第一个实例
  • 构建FP树
    • 遍历数据集,统计所有元素项出现次数
    • 去掉不满足最小支持度的元素项
    • 遍历数据集
      • 对每个项集按频率进行排序
      • 将排序后的项集添加到一条已存在的路劲中
      • 不存在路径则创建新路径
        • 递归将首元素添加到FP树

挖掘频繁项集

  • 抽取条件模式基(获取前缀路径):利用头指针表,头指针表包含相同类型元素链接的起始指针;一旦到达每一个元素项,向上回溯到树的根节点。
  • 创建条件FP树:使用条件模式基作为输入数据,创建FP树。

python实现

class treeNode:
    def __init__(self,nameValue,numOccur,parentNode):
        self.name = nameValue   #节点名
        self.count = numOccur   #计数
        self.nodeLink = None    #链接相似项
        self.parent = parentNode    #父节点
        self.children = {}  #子结点

    def inc(self,numOccur):
        self.count += numOccur

    def disp(self,ind=1):
        print(' '*ind,self.name,' ',self.count)
        for child in self.children.values():
            child.disp(ind+1)

def loadSimpDat():
    simpDat = [['r','a','h','j','p'],
               ['a','y','x','w','v','u','t','s'],
               ['a'],
               ['r','x','n','o','s'],
               ['y','r','x','a','q','t','p'],
               ['y','a','x','e','q','s','t','m']]
    return simpDat

def creatInitSet(dataSet):
    retDict = {}
    for line in dataSet:
        retDict[frozenset(line)] = 1
    return retDict

def createTree(dataSet,minSup=3):
    headerTable = {}
    #统计元素频率
    for line in dataSet:
        for item in line:
            headerTable[item] = headerTable.get(item,0) + dataSet[line]
    #移除不满足最小支持度的元素项
    delk = []
    for k in headerTable.keys():
        if headerTable[k] < minSup:
            delk.append(k)
    for k in delk:
        headerTable.pop(k)
    #没有元素满足最小支持度则退出
    freqItemSet = set(headerTable.keys())
    if len(freqItemSet) == 0:
            return None,None
    #头指针表的项的数据结构为{节点名:[出现次数,相同元素链表的起始指针]}
    for k in headerTable:
        headerTable[k] = [headerTable[k],None]
    retTree = treeNode('Null Set',1,None)
    for tranSet,count in dataSet.items():
        #对每条数据中的元素按频率进行排序
        localD = {}
        for item in tranSet:
            if item in freqItemSet:
                localD[item] = headerTable[item][0]
        if len(localD) > 0:
            orderedItems = [v[0] for v in sorted(localD.items(),key=lambda p:p[1],reverse=True)]
            updateTree(orderedItems,retTree,headerTable,count)  #使用排序后的数据项对FP树进行扩充
    return retTree,headerTable

def updateTree(item,inTree,headerTable,count):
    if item[0] in inTree.children:
        inTree.children[item[0]].inc(count)
    else:
        inTree.children[item[0]] = treeNode(item[0],count,inTree)   #不存在首个元素则根节点添加子结点
        #头指针表指向给定类型的第一个实例
        if headerTable[item[0]][1] == None:
            headerTable[item[0]][1] = inTree.children[item[0]]
        else:
            updateHeader(headerTable[item[0]][1],inTree.children[item[0]])    #链接节点,用于找到相似项
    if len(item) > 1:
        updateTree(item[1:],inTree.children[item[0]],headerTable,count)


def updateHeader(nodeToTest,targetNode):
    while(nodeToTest.nodeLink!=None):
        nodeToTest = nodeToTest.nodeLink
    nodeToTest.nodeLink = targetNode

#发现以给定元素项结尾的所有路径的函数
def ascendTree(leafNode,prefixPath):
    if leafNode.parent != None:
        prefixPath.append(leafNode.name)
        ascendTree(leafNode.parent,prefixPath)
def findPrefixPath(treeNode):
    conPats = {}
    while treeNode != None:
        prefixPath = []
        ascendTree(treeNode,prefixPath)
        conPats[frozenset(prefixPath[1:])] = treeNode.count
        treeNode = treeNode.nodeLink
    return conPats

def mineTree(inTree,hearderTable,minSup,preFix,freItemList):
    #注意sorted()对字典值排序的用法
    bigL = [v[0] for v in sorted(hearderTable.items(),key=lambda item:item[1][0])]
    for basePat in bigL:
        newFreqSet = preFix.copy()
        newFreqSet.add(basePat)
        freItemList.append(newFreqSet)
        condPattBases = findPrefixPath(hearderTable[basePat][1])
        myCondTree,myHead = createTree(condPattBases,minSup)
        if myHead != None:
            mineTree(myCondTree,myHead,minSup,newFreqSet,freItemList)

if __name__ == '__main__':
    dataSet = loadSimpDat()
    initdataSet = creatInitSet(dataSet)
    tree,table = createTree(initdataSet)
    freqItem = []
    mineTree(tree,table,3,set([]),freqItem)