NumPy数组“打乱”维度不匹配
我算是个Python新手,正在做一个音频实验,灵感来自于这个进化的蒙娜丽莎。
下面的代码主要是想做以下几件事:
- 把一个指定的.wav文件读入到一个NumPy数组中。
- 检测波形中的“零交叉”,也就是数组元素符号变化的地方。在这些地方把数组分割成一个嵌套列表,里面是波形的“块”。
- 把正的块和负的块分开,然后把这些块打乱顺序,再交替组合成一个新的NumPy数组。因为列表里有超过2000个元素,所以我不能用random.shuffle()。
- 比较打乱后的数组和原始样本的“适应度”,适应度的定义是打乱数组和原始样本之间差值的平方。
最终,我会加入复制、变异和选择的过程,但现在我的适应度函数有问题。分割、打乱和重新组合后的数组和原始输入的维度不一样,导致了以下错误:
$ 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
首先,我们来整理一下你的代码。
不要用像
list
和input
这样的 Python 内置函数作为变量名。虽然 Python 不会严格禁止这样做,但这样会在后面造成一些意外的问题。没有必要像
z = numpy.add(x, y)
这样明确调用函数。直接用z = x + y
更符合 Python 的风格,而且效果是一样的。(假设x
和y
是 numpy 数组。)同样,没必要为了给 numpy 数组里的每个元素加 1 而新建一个全是 1 的数组。你只需要用x += 1
或者x = x + 1
(如果你想要一个副本)就可以了。与其在函数定义上方写关于函数作用的注释,不如把它放在下面。这不仅仅是风格问题,因为 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()