我或多或少是一个 Python 新手,正在研究与此 evolutionary Mona Lisa 类似的音频实验。
下面的代码是为了:
- 将给定的 .wav 文件读入 NumPy 数组。
- 检测波形中的“过零”,即数组元素改变符号时。在这些点将数组拆分为嵌套的波形“ block ”列表。
- 将正 block 与负 block 分开,然后打乱这些 block 并将它们重新组合成一个 NumPy 数组,正负交替。我不能使用 random.shuffle(),因为该列表有超过 2000 个元素。
- 比较打乱后数组的“适应度” 与原始样本,定义为差异的平方 在打乱后的数组和原始样本之间。
最终,我将添加复制、变异和选择,但目前我的适应度函数存在问题。拆分、打乱和重组的数组与原始输入的维度不同,导致以下错误:
$ ValueError: operands could not be broadcast together with shapes (1273382) (1138213)
第二个数组的维度在我每次运行程序时都不同,但总是在 1138000-1145000 左右。在拆分、洗牌和重组步骤中,我似乎丢失了一些 block ,我怀疑我在第 3 步的某个地方错误地使用了列表理解,但我不太清楚在哪里或为什么。出了什么问题?
# 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)`
请您参考如下方法:
首先,让我们清理您的一些代码。
不要通过将
list和input用作变量名来覆盖 python 内置函数。 Python 并没有严格阻止它,但它会在后面引起意外。无需显式调用
z = numpy.add(x, y)之类的东西。z = x + y更加 pythonic 并且完全等价。 (假设x和y是 numpy 数组。)类似地,没有必要创建一个新的 1 数组只是为了给 numpy 数组中的每个项目加 1。如果您需要副本,只需调用x += 1或x = x + 1。与其将关于函数功能的注释放在定义上方,不如将其放在定义下方。这不仅仅是一种样式约定,因为 python 的内置帮助和文档工具只能利用这些“文档字符串”,如果它们是第一个注释(或多行字符串,更常见,因此三引号)在函数定义之下。
正如@talonmies 指出的那样,您的问题来自这样一个事实,即您假设您拥有相同数量的正 block 和负 block 。有多种解决方法,但一种简单的方法是只使用 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()
