随笔Dome将GAN与Kmeans进行随机结合

98 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第24天,点击查看活动详情

前言

无意中翻到一篇以前的博文,觉得这个很有意思,所以刚好完善一下。 这边做的一个dome就是说,使用GAN网络去学习Kmeans的一种分类规则,同样分为两个阶段一个是预训练阶段,还有一个是运行阶段。当然这个也是Dome嘛,这里的话主要展示训练阶段。

KMeans算法

我们在开始之前先聊一下这个Kmeans算法。这个算法很简单,就几步。

KMeans 就分几步嘛。

  1. 随机选择中心点
  2. 计算每一个点离中心点的位置,选择最近的一个点(如果这个点理两个以上的中心的距离一样,那么无法分类)
  3. 根据每一个分类的点,重新计算中心,然后再次让所有的点去计算距离(中心点移动了,点要重新划分)
  4. 等到中心点不在移动后,我们停止算法

距离公式

由于咱们是使用距离的,所以这里我们有好几个距离公式可以选择(我这里复现是直接使用欧式距离的)

在这里插入图片描述 在这里插入图片描述

在这里插入图片描述 dist = |x1−x2|+|y1−y2|

这几个距离公式无所谓,到时候其实把代码改一下就好了。

代码

ok,现在我们直接来看看我们的代码。

我们的复现是做一个四分类的,四个颜色对应不同分类,还有一个分类是说那个点无法被那四个类分出来。 因为我们我们是分类属于那个类别是看你这个点距离那个中心点的距离来的,离得最近的中心的假设是A,那么此时这个点就是属于A的,但是如果最近的点有两个A,B的话,那么这个点是无法分类的。

import numpy as np
import  math
import matplotlib.pyplot as plt
import random
#我们设计一个四分类的玩意儿,是一个二维的,因为二维的图像好画
K = 4
ECPHO = 300
Classfiy = ['red','blue','green','black']
OverClass =['cyan']
NUMBERS = 200
DIM = 2
SIZEPoint=(NUMBERS+int(NUMBERS),DIM)
MinRange = 1
MaxRange = 10
FRESH = 10
FRESHTIME = 1/FRESH
BUFFERTIME = 1

plt.ion()

def CreateData(k):
    #返回初始化的点和随机选取的中心点
    dataset = np.array([random.sample(range(MinRange,MaxRange),2) for _ in range(NUMBERS)])
    # dataset = np.random.rand(400).reshape(200,2)*10
    centers_init = np.random.choice(np.arange(len(dataset)),k,replace=False)
    return dataset,dataset[centers_init]

def CompareDist(point,centers)->int:
    #距离计算
    #center:[[x1,y1],[x2,y2]]
    #返回当前点理哪一个中心点最近
    x,y = point[0],point[1]
    dist = []
    for center in centers:
        dis = math.sqrt(math.pow(x-center[0],2)+math.pow(y-center[1],2))

        dist.append(dis)
    if(dist.count(min(dist)))>1:
        return K
    else:
        return dist.index(min(dist))

def CompareClassfiy(dataset,centers):
    #这个函数的作用很简单,计算每一个点到中心的距离,然后分类
    #分完类之后,我们再重新计算新的centers
    #并且返回当前新的中心和原来的中心的差值,以及当前分好类的点,以及无法分类的玩意
    ClassPoints = {'red':[],'blue':[],'green':[],'black':[]}
    OverPoints = {'cyan':[]}

    for point in dataset:
        classfiy = CompareDist(point,centers)
        if(classfiy<K):
            ClassPoints[Classfiy[classfiy]].append(point)
        else:
            OverPoints[OverClass[0]].append(point)



    newCenters = []
    for key in ClassPoints.keys():
        temp = np.array(ClassPoints.get(key))
        newcenter = np.average(temp,axis=0).tolist()
        newCenters.append(newcenter)
    newCenters = np.array(newCenters)
    changed = newCenters - centers
    return newCenters,changed,ClassPoints,OverPoints


def show(ClassPoints:dict,Centers,OverPoints):

    for index in range(len(Centers)):
        #绘制中心点
        plt.scatter(Centers[index][0],Centers[index][1],marker='o',color=Classfiy[index],s=150)
        #绘制所属类别的点
        Points = ClassPoints.get(Classfiy[index])
        Points=np.array(Points)
        sca = plt.plot(Points[:,0],Points[:,1],'--',color =Classfiy[index] )
    Over = OverPoints.get(OverClass[0])

    if(len(Over)>0):
        Over = np.array(Over)
        sca = plt.plot(Over[:, 0], Over[:, 1],'*',color=OverClass[0],marker="X")
    plt.show()



def KMeans(K):
    # 初始化参数
    dataSet,centers = CreateData(K)
    newCenters,changed,ClassPoints,OverPoints = CompareClassfiy(dataSet,centers)
    show(ClassPoints,centers,OverPoints)

    bufferTime = 0
    for i in range(ECPHO):
        centers = newCenters
        newCenters,changed,ClassPoints,OverPoints = CompareClassfiy(dataSet,centers)

        show(ClassPoints,centers,OverPoints)
        plt.pause(FRESHTIME)
        plt.cla()
        if(changed.all()==0.):
            bufferTime+=1
        if(bufferTime>=BUFFERTIME):
            break

    plt.ioff()
    show(ClassPoints,centers,OverPoints)

if __name__ == '__main__':
    KMeans(K)





运行效果

看代码还是比较简单的 我们来看看运行图 在这里插入图片描述 这里我为了好看出边界,所以我是使用虚线连接了点,因为接下来有个问题是我们要解决的。 (下图是用点表示的,边界不好看) 在这里插入图片描述

结合

那么之后的就是我们如何将GAN 与 Kmeans想结合。我们这样干保留一开始的欧氏距离Kmeans,再学习的过程当中去训练我的GAN网络。当发现我们的距离无法工作的时候,我们使用GAN。换一句话说,俺们这个其实是个复合算法。

那么问题来了,为什么用神经网络?其实分类问题,你发现其实它背后是有个”隐藏“逻辑的。我们一开始使用的是距离公式,也就是说我们期望找到一种距离的关系,然后去分类。但是有些点,可能受到距离和一种非距离的约束,也就是说和距离相关但是又有点其他特殊的东西约束,那么使用距离公式可能无法感知到,也就无法分类。但是使用神经网络,它最擅长的就是这个特殊的,隐含的,非线性的关系。所以,我们就可以进行一定的分类。

举一个例子。老师教学生,这个学生可能天赋异禀,虽然老师教的是按照规定的规则的,但是学生学的时候可能出来老师教的规则,还很有可能有自己的思考,然后去”创新“。所以这也是我为什么认为可以这样干的原因。

只不过性能开销是可能有的,而且适合关系复杂的分类,因为越复杂,它可能捕捉到的东西就越多,而且复杂,数据量大可以学得更久一点儿。

编码

这里我做了简单的优化。 在这里插入图片描述 从上到下

import numpy as np
import torch
import random
import matplotlib.pyplot as plt


class ENV(object):
    K = 4
    Classfiy = ['red', 'blue', 'green', 'black']
    NUMBERS = 100
    DIM = 2
    MinRange = 1
    MaxRange = 10

    def __init__(self):
        self.DataSet = torch.tensor([random.sample(range(self.MinRange, self.MaxRange), 2) for _ in range(self.NUMBERS)],dtype=torch.float)
        self.Clusters = np.random.choice(np.arange(len(self.DataSet)),self.K,replace=False)
        self.Clusters = self.DataSet[self.Clusters]
        self.plt = plt
        self.plt.ion()

    def Show(self,ClassPoints:dict,Clusters:np.array)->None:
        # The show must be the numpy data running
        for index in range(len(Clusters)):
            # Draw center point
            self.plt.scatter(Clusters[index][0], Clusters[index][1], marker='o', color=self.Classfiy[index], s=100)
            # Draws the points of the category
            Points = ClassPoints.get(self.Classfiy[index])
            Points = np.array(Points)

            sca = self.plt.plot(Points[:, 0], Points[:, 1], '--', color=self.Classfiy[index])

        self.plt.show()



import torch
from torch import nn
import numpy as np
import torch.nn.functional as F
from KMeansNN.GANKMeans.ENV import ENV
from KMeansNN.GANKMeans.Professor import Professor
"""
Let's default BatchSize = 1  
Now, what happens here is that we have a very bad classification network at the beginning, 
so we can't just go there at first  
We use neural networks, but we use experts first, and when the experts can't tell, 
or we can start, we use GAN  
In other words, GAN is a set of auxiliary systems  
"""
DIM = 2
HIDDEN = 32
K = 4
EPOCH = 100
FRESHTIME = 10
ROUNDNUMBER = 50
FRESH = 1 / FRESHTIME

class Generate(nn.Module):
    def __init__(self):
        super(Generate,self).__init__()
        self.fc1 = nn.Linear(DIM, HIDDEN)
        self.fc1.weight.data.normal_(0, 0.1)  # initialization
        self.fc2 = nn.Linear(HIDDEN,int(HIDDEN/2))
        self.fc2.weight.data.normal_(0, 0.1)  # initialization
        self.out = nn.Linear(int(HIDDEN/2),K)
        self.out.weight.data.normal_(0, 0.1)  # initialization

    def forward(self,point):
        out = self.fc1(point)
        out = F.relu(out)
        out = self.fc2(out)
        out = F.relu(out)
        classfiy = self.out(out)
        return classfiy

class Discriminator(nn.Module):

    def __init__(self):
        super(Discriminator,self).__init__()
        self.fc1 = nn.Linear(1,HIDDEN*2)
        self.out = nn.Linear(HIDDEN*2,1)

    def forward(self,classfiy):
        score = self.fc1(classfiy)
        score = F.relu(score)

        score = self.out(score)
        score = torch.sigmoid(score)
        return score


class GANKMeans(object):
    def __init__(self):
        self.Env = ENV()
        self.Professor = Professor()
        self.D = Discriminator()
        self.G = Generate()
        self.LR_G = 0.0001
        self.LR_D = 0.0001
        #See if it doesn't work well with SGD
        self.opt_D = torch.optim.Adam(self.D.parameters(), lr=self.LR_D)
        self.opt_G = torch.optim.Adam(self.G.parameters(), lr=self.LR_G)

    def Learn(self,pred_G,point:torch.tensor,Clusters:torch.tensor):
        #EPOCH * NUMBERS
        #Tips:there epoch is actually epoch because of we will stop running if have arrived
        #changed limited
        #Remember that you have a tensor input here
        pred_P = self.Professor.CalculateDist(point.numpy(),Clusters.numpy())
        pred_P = torch.tensor(pred_P,dtype=torch.float)
        pred_P = pred_P.reshape((1,1))
        pred_G = pred_G.float()
        pred_G = pred_G.reshape((1,1))

        if(pred_P==-1):
            #is indicates that experts will not, so self-study is the only way out
            # so we will classfiy by G

            pred_P = pred_G.detach()

        prob_G = self.D(pred_G)
        G_loss = torch.mean(torch.log(1. - prob_G))
        self.opt_G.zero_grad()
        G_loss.backward()
        self.opt_G.step()

        prob_0 = self.D(pred_P)
        prob_1 = self.D(pred_G.detach())
        D_loss = - torch.mean(torch.log(prob_0) + torch.log(1. - prob_1))
        self.opt_D.zero_grad()
        D_loss.backward(retain_graph=True)
        self.opt_D.step()

        return pred_P

    def Divide(self,DataSet:torch.tensor,Clusters:torch.tensor):
        #The divided Clusters are fed back to the current center and
        # return the change from the previous comparison
        ClassPoints = {}
        for key in self.Env.Classfiy:
            ClassPoints[key]=[]

        for point in DataSet:
            pred_G = self.G(point)
            pred_G = torch.argmax(pred_G)

            pred_P = self.Learn(pred_G,point,Clusters)

            ClassPoints[self.Env.Classfiy[int(pred_P.item())]].append(point.numpy())
            # Learning update network

        #Computing the new ClusterCenter
        newClusters = []
        for key in ClassPoints.keys():
            temp = np.array(ClassPoints.get(key))
            newcluster = np.average(temp, axis=0).tolist()
            newClusters.append(newcluster)

        newClusters = torch.tensor(newClusters)
        Changed = Clusters-newClusters
        return ClassPoints,newClusters,Changed


    def KMeans(self):
        """
        we will stop run if epoch have arrived EPOCH limit or
        we have got stable Clusters. we will plot Clusters points
        after divided in every epoch
        :return:
        """
        dataSet,Clusters = self.Env.DataSet,self.Env.Clusters
        ClassPoints,newClusters,Changed = self.Divide(dataSet,Clusters)
        self.Env.Show(ClassPoints,Clusters)
        Roundnumber = 0
        for epoch in range(EPOCH):
            Clusters = newClusters
            ClassPoints,newClusters,Changed = self.Divide(dataSet,Clusters)

            self.Env.Show(ClassPoints,Clusters)
            self.Env.plt.pause(FRESH)
            self.Env.plt.cla()

            if(Changed.all()==0.):
                Roundnumber+=1
            if(Roundnumber>=ROUNDNUMBER):
                break


        self.Env.plt.ioff()

        self.Env.Show(ClassPoints,Clusters)


if __name__ == '__main__':
    ganKmeans = GANKMeans()
    ganKmeans.KMeans()


"""
Experts do one simple thing: provide data,RunTimeDataProcess
"""

import math
import numpy as np
class Professor(object):

    def CalculateDist(self,point:np.array,Clusters:np.array):
        x,y = point
        dists = []
        for Cluster in Clusters:
            x1,y1 = Cluster
            dist = math.sqrt(math.pow((x-x1),2)+math.pow((y-y1),2))
            dists.append(dist)

        if(dists.count(min(dists))>1):
            return -1
        else:
            return dists.index(min(dists))



效果

在这里插入图片描述 这个肯定是不用说的。 不过其实后面我还没有测试,例如,当训练到足够的轮数的时候,我是否可以拜托老师,独立分类?等等,这些都要去实验才知道,后面如果有时间我可以看看,把这玩意改一下用在图片分类上面怎么样?

总结

最近也是真忙,天天写接口,做项目,写论文,还要备研。不过这也导致,根本木有创作上限!最后祝各位1024快乐!!!