【项目实战】WaveNet 代码解析 —— audio_reader.py 【更新中】

WaveNet 代码解析 —— audio_reader.py

  简介

       本项目一个基于 WaveNet 生成神经网络体系结构的语音合成项目,它是使用 TensorFlow 实现的(项目地址)。
       
        WaveNet 神经网络体系结构能直接生成原始音频波形,在文本到语音和一般音频生成方面显示了出色的结果(详情请参阅 WaveNet 的详细介绍)。
       
       由于 WaveNet 项目较大,代码较多。为了方便学习与整理,将按照工程文件的结构依次介绍。
       
       本文将介绍项目中的 audio_reader.py 文件:音频读取脚本。

       

  代码解析

    全局变量解析

       以下变量主要作为 audio_reader.py 脚本的全局变量。

		FILE_PATTERN = r'p([0-9]+)_([0-9]+)\.wav'
		# r'...'是原字符串,\反斜线不会特殊对待,即没有转义
		# 匹配以下形式的字符串:"p" + 任意数字 + "_" + 任意数字 + ".wav" 

       

    函数解析

      find_files(directory, pattern=’*.wav’)

        下面这段代码的主要任务是:匹配查找所有后缀为 “.wav” 的文件。
       os.walk() 方法用于通过在目录树中游走输出在目录中的文件名,向上或者向下。

	def find_files(directory, pattern='*.wav'):
	    ''' 递归地查找所有与模式匹配的文件 '''
	    files = []
	    
	    # root: 当前正在遍历的文件夹地址
	    # dirnames: 该文件夹中所有的目录名组成的列表
	    # filenames: 该文件夹中所有的文件名组成的列表
	    for root, dirnames, filenames in os.walk(directory):
	        # 实现列表特殊字符的过滤或筛选,返回符合匹配模式的字符列表
	        for filename in fnmatch.filter(filenames, pattern):
	            # 拼接文件路径
	            files.append(os.path.join(root, filename))
	    
	    # 返回文件路径列表
	    return files

       

      get_category_cardinality(files)

        下面这段代码的主要任务是:遍历所给的所有文件,将找出最大和最小的说话人id。

	def get_category_cardinality(files):
	    # 匹配以下形式的字符串:"p" + 任意数字 + "_" + 任意数字 + ".wav"
	    id_reg_expression = re.compile(FILE_PATTERN)
	    
	    # 初始化最大最小id
	    min_id = None
	    max_id = None
	    
	    # 遍历给定的所有文件
	    for filename in files:
	        # 匹配文件名,取第一组结果
	        matches = id_reg_expression.findall(filename)[0]
	        # 取音频的编号,id为大编号:区分说话人,recording_id为小编号:区分说话内容
	        id, recording_id = [int(id_) for id_ in matches]
	        # 针对音频大编号,找出最大和最小的id
	        if min_id is None or id < min_id:
	            min_id = id
	        if max_id is None or id > max_id:
	            max_id = id
	
	    # 返回说话人的最大最小id
	    return min_id, max_id

       

      randomize_files(files)

        下面这段代码的主要任务是:在给出的文件里表中,生成一个返回随机文件的生成器。

	def randomize_files(files):
	    # 遍历所有文件
	    for file in files:
	        # 随机生成一个整数,作为文件索引
	        file_index = random.randint(0, (len(files) - 1))
	        # 得到生成器,准备返回对于位置的文件
	        yield files[file_index]

       

      load_generic_audio(directory, sample_rate)

        下面这段代码的主要任务是:在给出的文件里表中,生成一个返回随机文件的生成器。
        librosa.load() 用来读取音频文件,转化为了 numpy 的格式储存;返回音频信号值和采样率。

	def load_generic_audio(directory, sample_rate):
	    ''' 从目录中生成音频波形的生成器 '''
	    
	    # 查找以".wav"为后缀的音频文件
	    files = find_files(directory)
	    
	    # 设置匹配模板
	    id_reg_exp = re.compile(FILE_PATTERN)
	    
	    # 打印文件列表长度,随机取出文件
	    print("files length: {}".format(len(files)))
	    randomized_files = randomize_files(files)
	    
	    # 遍历取出的文件
	    for filename in randomized_files:
	        # 查找匹配的文件
	        ids = id_reg_exp.findall(filename)
	        if not ids:
	            # 文件名与包含id的模式不匹配,所以没有id。
	            category_id = None
	        else:
	            # 文件名与包含id的模式匹配.
	            category_id = int(ids[0][0])
	        
	        # 读取音频文件,输出音频的信号值
	        audio, _ = librosa.load(filename, sr=sample_rate, mono=True)
	        # 转换为一列
	        audio = audio.reshape(-1, 1)
	        
	        # 生成器返回音频信号值、文件名和文件id
	        yield audio, filename, category_id

       

      trim_silence(audio, threshold, frame_length=2048)

        下面这段代码的主要任务是:移除音频样本开始和结束时的沉默
        librosa.feature.rmse() 的作用 从音频样本 y 或声谱图 S 计算每帧的均方根( RMS )能量。计算来自音频样本的能量更快,因为它不需要 STFT 计算。然而,使用谱图可以更准确地表示能量随时间的变化,因为它的帧可以被加窗,因此如果它已经可用,更喜欢使用 S

	def trim_silence(audio, threshold, frame_length=2048):
	    ''' 移除样本开始和结束时的沉默 '''
	    if audio.size < frame_length:
	        frame_length = audio.size
	    
	    # 计算每帧的均方根
	    energy = librosa.feature.rmse(audio, frame_length=frame_length)
	    # 返回数组中大于阈值的元素的索引值数组,是二维数组
	    frames = np.nonzero(energy > threshold)
	    # 转换帧索引到音频样本索引,取索引列,第一列全为0
	    indices = librosa.core.frames_to_samples(frames)[1]
	
	    # 注意:如果整个音频是无声的,索引可以是一个空数组
	    return audio[indices[0]:indices[-1]] if indices.size else audio[0:0]

       

      not_all_have_id(files)

        下面这段代码的主要任务是:判断文件名是否符合定类别id所需的模式

	def not_all_have_id(files):
	    ''' 如果任何文件名不符合我们确定类别id所需的模式,则返回true '''
	    # 匹配模板
	    id_reg_exp = re.compile(FILE_PATTERN)
	    
	    # 遍历传入的所有文件
	    for file in files:
	        # 查找符合匹配模版的字符串,判断是否存在
	        ids = id_reg_exp.findall(file)
	        if not ids:
	            return True
	    return False

    AudioReader类解析

      AudioReader类成员变量解析

       以下变量主要作为AudioReader类的成员变量

		audio_dir					# 音频样本文件目录
		coord						# 线程协调器
		receptive_field				# 感受野
		sample_rate					# 采样率
		sample_size					# 采样大小
		silence_threshold			# 沉默阈值
		
		sample_placeholder			# 32位float型占位符
		queue						# 32位float型占位符队列,默认大小为32
		enqueue						# 32位float型占位符入队操作
		
		gc_enabled					# 全局条件标志
		id_placeholder				# 32位int型占位符
		gc_queue					# 32位int型占位符队列,默认大小为32
		gc_enqueue					# 32位int型占位符入队操作
		
		gc_category_cardinality		# 类别基数
		
		threads						# 线程列表

      AudioReader类成员函数解析

        __ init __(self, audio_dir, coord, sample_rate, gc_enabled, receptive_field, sample_size=None, silence_threshold=None, queue_size=32)

       AudioReader类的初始化,为各成员变量赋值

    def __init__(self, audio_dir, coord, sample_rate, gc_enabled,
                 receptive_field, sample_size=None, 
                 silence_threshold=None, queue_size=32):
        self.audio_dir = audio_dir				# 赋值音频样本文件路径
        self.sample_rate = sample_rate			# 赋值采样率
        self.coord = coord						# 赋值线程协调器
        self.sample_size = sample_size			# 赋值样本大小
        self.receptive_field = receptive_field	# 赋值感受野
        self.silence_threshold = silence_threshold	# 赋值沉默阈值
        self.gc_enabled = gc_enabled			# 赋值全局条件标志
        self.threads = []						# 创建线程列表
        
        # 创建32位float型占位符
        self.sample_placeholder = tf.placeholder(dtype=tf.float32, shape=None)
        # 为占位符创建可包含动态形状的队列
        self.queue = tf.PaddingFIFOQueue(queue_size,
                                         ['float32'],
                                         shapes=[(None, 1)])
        # 为创建的队列创造入队方法
        self.enqueue = self.queue.enqueue([self.sample_placeholder])

        # 若全局条件标志开启
        if self.gc_enabled:
            # 创建32位int型占位符
            self.id_placeholder = tf.placeholder(dtype=tf.int32, shape=())
            # 为占位符创建可包含动态形状的队列
            self.gc_queue = tf.PaddingFIFOQueue(queue_size, ['int32'],
                                                shapes=[()])
            # 为创建的队列创造入队方法
            self.gc_enqueue = self.gc_queue.enqueue([self.id_placeholder])

        # 在audireader的线程中进行检查使得很难终止脚本的执行,所以我们现在在构造函数中执行
        # 在指定目录中查找文件
        files = find_files(audio_dir)
        # 若没找到文件则报错
        if not files:
            raise ValueError("No audio files found in '{}'.".format(audio_dir))
        
        # 若全局条件标志开启,但没找到相应的文件则报错
        if self.gc_enabled and not_all_have_id(files):
            raise ValueError("Global conditioning is enabled, but file names "
                             "do not conform to pattern having id.")
        
        # 确定我们将在嵌入表中容纳的互斥类别的数量
        if self.gc_enabled:
            # 取查到的文件索引
            _, self.gc_category_cardinality = get_category_cardinality(files)
            # 在最大的索引中添加1以获得类别的编号
            self.gc_category_cardinality += 1
            print("Detected --gc_cardinality={}".format(
                  self.gc_category_cardinality))
        else:
            self.gc_category_cardinality = None

       

        dequeue(self, num_elements)

       下面这段代码作用是:音频样本批量出队操作

	    def dequeue(self, num_elements):
	        # 批量出队指定数量的处理过的音频样本
	        output = self.queue.dequeue_many(num_elements)
	        # 将出队的音频样本元组返回
	        return output

       

        dequeue_gc(self, num_elements)

       下面这段代码作用是:音频样本批量出队操作

	    def dequeue_gc(self, num_elements):
	        # 为队列提供批量出队的方法
	        return self.gc_queue.dequeue_many(num_elements)

       

        thread_main(self, sess)

       下面这段代码作用是:线程音频处理函数

	    def thread_main(self, sess):
	        stop = False
	        # 多次查看数据集
	        while not stop:
	            # 加载通用音频
	            iterator = load_generic_audio(self.audio_dir, self.sample_rate)
	            for audio, filename, category_id in iterator:
	                # 若线程需要停止,则停止循环
	                if self.coord.should_stop():
	                    stop = True
	                    break
	                
	                # 若沉默阈值存在
	                if self.silence_threshold is not None:
	                    # 裁剪沉默时间
	                    audio = trim_silence(audio[:, 0], self.silence_threshold)
	                    # 转换为一列
	                    audio = audio.reshape(-1, 1)
	                    # 若大小为  0则提示音频在该沉默阈值下失效
	                    if audio.size == 0:
	                        print("Warning: {} was ignored as it contains only "
	                              "silence. Consider decreasing trim_silence "
	                              "threshold, or adjust volume of the audio."
	                              .format(filename))
	
	                # 填充数组
	                audio = np.pad(audio, [[self.receptive_field, 0], [0, 0]],
	                               'constant')
	
	                if self.sample_size:
	                    # 将样本切成大小为receptive_field + sample_size且有receptive_field重叠的部分
	                    while len(audio) > self.receptive_field:
	                        # 裁剪音频样本
	                        piece = audio[:(self.receptive_field +
	                                        self.sample_size), :]
	                        # 执行入队操作
	                        sess.run(self.enqueue,
	                                 feed_dict={self.sample_placeholder: piece})
	                        
	                        # 裁剪音频
	                        audio = audio[self.sample_size:, :]
	                        
	                        # 若全局条件标志存在,则入队
	                        if self.gc_enabled:
	                            sess.run(self.gc_enqueue, feed_dict={
	                                self.id_placeholder: category_id})
	                else:
	                    # 若样本大小为0,则直接执行入队操作
	                    sess.run(self.enqueue,
	                             feed_dict={self.sample_placeholder: audio})
	                    # 若全局条件标志存在,则入队
	                    if self.gc_enabled:
	                        sess.run(self.gc_enqueue,
	                                 feed_dict={self.id_placeholder: category_id})

       

        start_threads(self, sess, n_threads=1)

       下面这段代码作用是:音频样本批量出队操作

	    def start_threads(self, sess, n_threads=1):
	        for _ in range(n_threads):
	            # 创建线程,方法为thread_main,赋予参数sess
	            thread = threading.Thread(target=self.thread_main, args=(sess,))
	            thread.daemon = True	# 线程将在父进程退出时关闭
	            thread.start()			# 启动线程
	            self.threads.append(thread)		# 将线程加入线程池
	        
	        # 返回线程池
	        return self.threads

       

       
       
       本文还在持续更新中!
       欢迎各位大佬交流讨论!

WaveNet 是一种基于深度学习的语音生成模型,用于生成高质量的自然语言语音。在 Python ,可以使用 WaveNet 的开源实现,例如 Google 的 Tacotron 和 DeepMind 的 WaveNet 项目。 如果你想使用 WaveNet 进行语音生成,可以按照以下步骤进行: 1. 安装依赖库 在使用 WaveNet 之前,需要安装几个必要的依赖库,例如 TensorFlow、NumPy、SciPy 等。可以使用 pip 命令进行安装,例如: ``` pip install tensorflow numpy scipy ``` 2. 下载 WaveNet 代码 可以从 GitHub 上下载 WaveNetPython 实现代码,例如: ``` git clone https://github.com/ibab/tensorflow-wavenet.git ``` 3. 准备语音数据 WaveNet 需要训练数据来学习语音特征。可以使用公开的语音数据集,例如 LJSpeech 数据集,也可以使用自己收集的语音数据。将语音数据转换为 WaveNet 可以接受的格式,例如 16kHz 采样率的 WAV 文件。 4. 训练 WaveNet 模型 使用准备好的语音数据,可以开始训练 WaveNet 模型。可以使用 WaveNet 项目提供的训练脚本,例如: ``` python train.py --data_dir=/path/to/data --log_dir=/path/to/log --checkpoint_dir=/path/to/checkpoint ``` 其,`data_dir` 是存放语音数据的目录,`log_dir` 是存放训练日志的目录,`checkpoint_dir` 是存放训练模型的目录。训练过程可能需要一些时间,具体时间取决于数据量和计算资源。 5. 使用 WaveNet 生成语音 训练好的 WaveNet 模型可以用来生成语音。可以使用 WaveNet 项目提供的生成脚本,例如: ``` python generate.py --model_dir=/path/to/model --wav_out_path=/path/to/output.wav ``` 其,`model_dir` 是存放训练模型的目录,`wav_out_path` 是生成语音的输出路径。运行脚本后,WaveNet 将会生成一段高质量的自然语音。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值