NumPy数组“打乱”维度不匹配

2 投票
1 回答
907 浏览
提问于 2025-04-17 09:46

我算是个Python新手,正在做一个音频实验,灵感来自于这个进化的蒙娜丽莎

下面的代码主要是想做以下几件事:

  1. 把一个指定的.wav文件读入到一个NumPy数组中。
  2. 检测波形中的“零交叉”,也就是数组元素符号变化的地方。在这些地方把数组分割成一个嵌套列表,里面是波形的“块”。
  3. 把正的块和负的块分开,然后把这些块打乱顺序,再交替组合成一个新的NumPy数组。因为列表里有超过2000个元素,所以我不能用random.shuffle()。
  4. 比较打乱后的数组和原始样本的“适应度”,适应度的定义是打乱数组和原始样本之间差值的平方。

最终,我会加入复制、变异和选择的过程,但现在我的适应度函数有问题。分割、打乱和重新组合后的数组和原始输入的维度不一样,导致了以下错误:

$ ValueError: operands could not be broadcast together with shapes (1273382) (1138213) 

每次运行程序时,第二个数组的维度都不一样,但总是大约在1138000到1145000之间。我觉得在分割、打乱和重新组合的过程中丢失了一些块,我怀疑在第三步的列表推导式使用得不太对,但我就是搞不清楚哪里出了问题,为什么会这样。到底哪里出错了呢?

# Import scipy audio tools, numpy, and randomization tools
import scipy
from scipy.io import wavfile

import numpy

from random import shuffle, randint

# Read a wav file data array, detect zero crossings, split at zero crossings, and return a nested list.
def process_wav(input):

    # Assign the wavefile data array to a variable.
    wavdata = input[1]

    # Detect zero crossings, i.e. changes in sign in the waveform data. The line below returns an array of the indices of elements after which a zero crossing occurs.
    zerocrossings = numpy.where(numpy.diff(numpy.sign(wavdata)))[0]
    # Increment each element in the array by one. Otherwise, the indices are off.
    zerocrossings = numpy.add(numpy.ones(zerocrossings.size, zerocrossings.dtype), zerocrossings)

    wavdatalist = wavdata.tolist()
    zerocrossingslist = zerocrossings.tolist()

    # Split the list at zero crossings. The function below splits a list at the given indices.      
    def partition(alist, indices):
        return [alist[i:j] for i, j in zip([0]+indices, indices+[None])]

    return partition(wavdatalist, zerocrossingslist)


# Accept a list as input, separate into positive and negative chunks, shuffle, and return a shuffled nested list
def shuffle_wav(list):

    # Separate waveform chunks into positive and negative lists.
    positivechunks = []
    negativechunks = []

    for chunk in list:
        if chunk[0] < 0:
            negativechunks.append(chunk)
        elif chunk[0] > 0:
            positivechunks.append(chunk)
        elif chunk[0] == 0:
            positivechunks.append(chunk)

    # Shuffle the chunks and append them to a list, alternating positive with negative.
    shuffledchunks = []
    while len(positivechunks) >= 0 and len(negativechunks) > 0:
        currentpositivechunk = positivechunks.pop(randint(0, len(positivechunks)-1))
        shuffledchunks.append(currentpositivechunk)
        currentnegativechunk = negativechunks.pop(randint(0, len(negativechunks)-1))
        shuffledchunks.append(currentnegativechunk)

    return [chunk for sublist in shuffledchunks for chunk in sublist]

def get_fitness(array, target):
    return numpy.square(numpy.subtract(target, array))

# Read a sample wav file. The wavfile function returns a tuple of the file's sample rate and data as a numpy array, to be passed to the process_wav() function.
input = scipy.io.wavfile.read('sample.wav')     

wavchunks = process_wav(input)  
shuffledlist = shuffle_wav(wavchunks)   
output = numpy.array(shuffledlist, dtype='int16')
print get_fitness(output, input[1])

scipy.io.wavfile.write('output.wav', 44100, output)

编辑:这是完整的错误追踪信息:

Traceback (most recent call last):
  File "evowav.py", line 64, in <module>
    print get_fitness(output, input[1])
  File "evowav.py", line 56, in get_fitness
    return numpy.square(numpy.subtract(target, array))
ValueError: operands could not be broadcast together with shapes (1273382) (1136678)`

1 个回答

1

首先,我们来整理一下你的代码。

  1. 不要用像 listinput 这样的 Python 内置函数作为变量名。虽然 Python 不会严格禁止这样做,但这样会在后面造成一些意外的问题。

  2. 没有必要像 z = numpy.add(x, y) 这样明确调用函数。直接用 z = x + y 更符合 Python 的风格,而且效果是一样的。(假设 xy 是 numpy 数组。)同样,没必要为了给 numpy 数组里的每个元素加 1 而新建一个全是 1 的数组。你只需要用 x += 1 或者 x = x + 1(如果你想要一个副本)就可以了。

  3. 与其在函数定义上方写关于函数作用的注释,不如把它放在下面。这不仅仅是风格问题,因为 Python 的内置帮助和文档工具只能利用这些“文档字符串”,前提是它们是函数定义 下面 的第一个注释(或多行字符串,通常用三重引号表示)。

正如 @talonmies 所指出的,你的问题在于你假设正负块的数量是相同的。有几种方法可以解决这个问题,但一个简单的方法就是使用 itertools.izip_longest

现在,举个例子……

import random
import itertools
import numpy
import scipy.io.wavfile

def main():
    """Read a wav file and shuffle the negative and positive pieces."""
    # Use unpacking to your advantage, and avoid using "input" as a var name
    samplerate, data = scipy.io.wavfile.read('sample.wav')     

    # Note, my sample.wav is stereo, so I'm going to just work with one channel
    # If yours is mono, you'd want to just pass "data" directly in
    left, right = data.T

    wavchunks = process_wav(left)  
    output = shuffle_wav(wavchunks).astype(numpy.int16)
    print get_fitness(output, samplerate)

    scipy.io.wavfile.write('output.wav', 44100, output)

def process_wav(wavdata):
    """Read a wav file data array, detect zero crossings, 
    split at zero crossings, and return a list of numpy arrays"""

    # I prefer nonzero to where, but either works in this case...
    zerocrossings, = numpy.diff(numpy.sign(wavdata)).nonzero()
    zerocrossings += 1
    indicies = [0] + zerocrossings.tolist() + [None]

    # The key is that we don't need to convert everything to a list.
    # Just pass back a list of views into the array. This uses less memory.
    return [wavdata[i:j] for i, j in zip(indicies[:-1], indicies[1:])]

def shuffle_wav(partitions):
    """Accept a list as input, separate into positive and negative chunks, 
    shuffle, and return a shuffled nested list."""

    # Instead of iterating through each item, just use indexing 
    poschunks = partitions[::2]
    negchunks = partitions[1::2]
    if poschunks[0][0] < 0:
        # Reverse the variable names if the first chunk wasn't positive.
        negchunks, poschunks = poschunks, negchunks

    # Instead of popping a random index off, just shuffle the lists...
    random.shuffle(poschunks)
    random.shuffle(negchunks)

    # To avoid the error you were getting, use izip_longest
    chunks = itertools.izip_longest(poschunks, negchunks, fillvalue=[])

    return numpy.hstack(item for sublist in chunks for item in sublist)


def get_fitness(array, target):
    """Compares sum of square differences between the two arrays."""
    # I'm going to assume that you wanted a single sum returned here...
    # Your original code returned an array.
    return ((array - target)**2).sum()

main()

撰写回答