人工智能小白日记 语音情感分析探索之4 语音分离k-means实现

人工智能小白日记 语音情感分析探索之4 语音分离k-means实现

前言

前面已经了解了k-means算法,现在对其进行应用,用到要做的语音分离这里。

正文内容

1 k-means算法实现

在前面非监督学习调研中,已经描述了k-means算法原理,这里不再赘述。直接上代码,这是从网上找到的一篇,对其进行了重新注解,便于理解。

由于以下内容使用了tensorflow的计算图,在查看之前需要对tensorflow的低级api进行了解。参考 https://tensorflow.google.cn/guide/low_level_intro ,看完以下章节即可:
在这里插入图片描述

# -*- coding: utf-8 -*-
import numpy as np
from numpy.linalg import cholesky
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import tensorflow as tf
from random import choice, shuffle
from numpy import array
############Sachin Joglekar的基于tensorflow写的一个kmeans模板###############
def KMeansCluster(vectors, k):
    """
    K-Means Clustering using TensorFlow.
    `vertors`应该是一个m*n的NumPy的数组,其中m代表着n维向量的数目
    'k' 代表了待分的集群的数目,是一个整型值
    """

    #assert断言如果失败,程序直接报异常结束,保障聚类数k比样本数m要小
    k = int(k)
    assert k < len(vectors)
    #计算向量维度
    dim = len(vectors[0])
    
    #获取样本数组的下标,用于后面选取中心点
    vector_indices = list(range(len(vectors)))
    #随机化下标
    shuffle(vector_indices)
    '''
    计算图
    我们创建了一个默认的计算流的图用于整个算法中,这样就保证了当函数被多次调用时,
    默认的图并不会被从上一次调用时留下的未使用的OPS或者Variables挤满
    '''
    graph = tf.Graph()
    with graph.as_default():
        #计算的会话
        sess = tf.Session()
        ##构建基本的计算的元素
        ##首先我们需要保证每个中心点都会存在一个Variable矩阵
        ##从现有的点集合中抽取出一部分作为默认的中心点
        centroids = [tf.Variable((vectors[vector_indices[i]]))
                     for i in range(k)]
        ##创建一个placeholder用于存放各个中心点可能的分类的情况
        centroid_value = tf.placeholder("float64", [dim])
        cent_assigns = []
        for centroid in centroids:
            cent_assigns.append(tf.assign(centroid, centroid_value))
        ##对于每个独立向量的分属的类别设置为默认值0
        assignments = [tf.Variable(0) for i in range(len(vectors))]
        assignment_value = tf.placeholder("int32")
        cluster_assigns = []
        for assignment in assignments:
            cluster_assigns.append(tf.assign(assignment,
                                             assignment_value))
        ##下面创建用于计算平均值的操作节点
        #输入的placeholder
        mean_input = tf.placeholder("float", [None, dim])
        #节点/OP接受输入,并且计算0维度的平均值,譬如输入的向量列表
        mean_op = tf.reduce_mean(mean_input, 0)
        ##用于计算欧几里得距离的节点
        v1 = tf.placeholder("float", [dim])
        v2 = tf.placeholder("float", [dim])
        euclid_dist = tf.sqrt(tf.reduce_sum(tf.pow(tf.subtract(
            v1, v2), 2)))
        ##这个OP会决定应该将向量归属到哪个节点
        ##基于向量到中心点的欧几里得距离
        #Placeholder for input
        centroid_distances = tf.placeholder("float", [k])
        cluster_assignment = tf.argmin(centroid_distances, 0)
        ##初始化所有的状态值
         ##这会帮助初始化图中定义的所有Variables。Variable-initializer应该定
         ##义在所有的Variables被构造之后,这样所有的Variables才会被纳入初始化
        init_op = tf.global_variables_initializer()
        #初始化所有的变量
        sess.run(init_op)
        ##集群遍历
        #接下来在K-Means聚类迭代中使用最大期望算法。为了简单起见,只让它执行固
        #定的次数,而不设置一个终止条件
        noofiterations = 20
        for iteration_n in range(noofiterations):

            '''
                根据中心点对所有样本进行集群
            '''
            #首先遍历所有样本
            for vector_n in range(len(vectors)):
                vect = vectors[vector_n]
                #计算样本与k个中心节点之间的欧几里得距离
                distances = [sess.run(euclid_dist, feed_dict={
                    v1: vect, v2: sess.run(centroid)})
                             for centroid in centroids]
                #集群分配,将样本分配到distance最小的群
                assignment = sess.run(cluster_assignment, feed_dict = {
                    centroid_distances: distances})
                #更新分配结果到assignments
                sess.run(cluster_assigns[vector_n], feed_dict={
                    assignment_value: assignment})

            '''
                根据样本集群结果计算新的中心点,使集群内的平方和最小
            '''
            for cluster_n in range(k):
                #收集所有分配给该集群的向量
                assigned_vects = [vectors[i] for i in range(len(vectors))
                                  if sess.run(assignments[i]) == cluster_n]
                #计算新的集群中心点
                new_location = sess.run(mean_op, feed_dict={
                    mean_input: array(assigned_vects)})
                #更新中心点到centroids
                sess.run(cent_assigns[cluster_n], feed_dict={
                    centroid_value: new_location})

        #返回中心节点和分组
        centroids = sess.run(centroids)
        assignments = sess.run(assignments)
        return centroids, assignments

1)这部分定义了k-means的具体算法KMeansCluster,接受两个参数,把所有样本vectors,进行聚类集群,分成k类。
2)直接跳到核心部分
在这里插入图片描述
这里直接设置了算法的迭代次数,for循环内是核心部分:
1.根据上一次的k个中心点对样本重新分类集群
2.分好类后重新计算中心点位置
直到迭代次数用完。

3)这与之前看到的原理有所不同,直接设置了迭代次数,应该修改为循环结束条件,k个中心点位置不再发生变化时结束迭代。

4)修改循环:

while True:

            '''
                根据中心点对所有样本进行集群
            '''
            #首先遍历所有样本
            for vector_n in range(len(vectors)):
                vect = vectors[vector_n]
                #计算样本与k个中心节点之间的欧几里得距离
                distances = [sess.run(euclid_dist, feed_dict={
                    v1: vect, v2: sess.run(centroid)})
                             for centroid in centroids]
                #集群分配,将样本分配到distance最小的群
                assignment = sess.run(cluster_assignment, feed_dict = {
                    centroid_distances: distances})
                #更新分配结果到assignments
                sess.run(cluster_assigns[vector_n], feed_dict={
                    assignment_value: assignment})

            '''
                根据样本集群结果计算新的中心点,使集群内的平方和最小
            '''
            update = False    
            for cluster_n in range(k):
                #收集所有分配给该集群的向量
                assigned_vects = [vectors[i] for i in range(len(vectors))
                                  if sess.run(assignments[i]) == cluster_n]
                #计算新的集群中心点
                new_location = sess.run(mean_op, feed_dict={
                    mean_input: array(assigned_vects)})

                #中心点位置变化则更新
                print ('centroids['+str(cluster_n)+']',sess.run(centroids[cluster_n]),'new_location',new_location)
                if (sess.run(centroids[cluster_n]) != new_location).any():
                    update = True
                    #更新中心点到centroids
                    sess.run(cent_assigns[cluster_n], feed_dict={
                        centroid_value: new_location})

            if not update:
                break

这个修改很简单,用一个while循环替换for循环,当发现重新分配的中心点不再发生变化后,就break跳出循环。由于中心点数据是numpy数组,所以比较异同的时候要用any()或者all()才能得到正常结果。

5)进行调用,拿二维样本进行测试:

############生成测试数据###############
sampleNo = 100;    #指定样本数
# 二维正态分布
mu = np.array([[1, 5]])
Sigma = np.array([[1, 0.5], [1.5, 3]])
R = cholesky(Sigma)
srcdata= np.dot(np.random.randn(sampleNo, 2), R) + mu
plt.plot(srcdata[:,0],srcdata[:,1],'bo')
############kmeans算法计算###############
k=4               #指定集群数
center,result=KMeansCluster(srcdata,k)
print(center) 
############利用seaborn画图###############

res={"x":[],"y":[],"kmeans_res":[]}
for i in range(len(result)):
    res["x"].append(srcdata[i][0])
    res["y"].append(srcdata[i][1])
    res["kmeans_res"].append(result[i])
pd_res=pd.DataFrame(res)
sns.lmplot("x","y",data=pd_res,fit_reg=False,size=5,hue="kmeans_res")
plt.show()

运行结果:
在这里插入图片描述
可以看出,进行7次迭代后,中心点不再变化,同时结束了战斗。当然,这里进行了四舍五入,可以忽略。

在这里插入图片描述

  1. 如果是n维呢,测试以下3维样本,正态分布那些不管了
############生成测试数据###############
sampleNo = 100;    #指定样本数
n=5                #样本维度
srcdata=np.random.randn(sampleNo, n)
############kmeans算法计算###############
k=4               #指定集群数
center,result=KMeansCluster(srcdata,k)
print(center) 

经过些许步骤后,中心点手敛
在这里插入图片描述

2 k-means进行语音分离

核心算法有了,接下来做语音的分离,根据前面的语音分帧,一个多人对话的语音,可以被分为m个帧,每个帧有个n维特征向量,这样可以组成mxn的vectors,然后聚成k类即可。

这里直接调用上述方法即可:

#对其进行聚类,共2类,assignments为聚类结果
k=2 
centroids,assignments = kmean.KMeansCluster(extracted_features,k)
print('聚类结果',len(assignments),assignments)

在这里插入图片描述
其中extracted_features是用cnn提取的特征,这里将语音分帧后,40帧为一个样本,共104条样本,每条样本1024个维度,这里当然可以选择不同的特征,你喜欢就行。

聚类结果1为一类,0为一类。这样看不是很方便,可以简单的写个程序,看看音频在哪些时间点上被拆分了,方便和原音频做对比。我这里,一个样本是40帧,步幅是半帧,n个样本时长为 (n*40+1)*半帧时长32ms

比如前14位都是1,其时长计算为(14*40+1)*0.032 = 17.952。
在这里插入图片描述

3 实验结果说明

以上,只是其中的一个实验结果,k-means算法有个致命的问题在于,初始中心点的位置可能影响到最后聚类的结果,所以这次实验,得到了两个聚类结果。这对我的任务来说是不能接受的,分离效果也不是很好,可能问题出在几方面,样本包含帧数,特征的选取以及聚类的方式,要到达理想效果看来还得多尝试。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值