從音頻到Token:構建原神角色語音識別模型的完整實踐

本文將帶你從零實現一個基于音頻Token化的角色語音識別系統,完整復現原神角色語音分類任務,包含數據處理、模型訓練和推理全流程。

音頻波形通過滑動窗口轉換為數值Token序列的過程

一、為什么需要音頻Token化?

傳統音頻處理通常依賴MFCC、頻譜圖等特征,但這些方法:

  • 丟失原始波形細節
  • 難以與現代Transformer架構無縫集成
  • 特征工程復雜度高

創新思路:將原始音頻波形直接轉換為離散Token序列,使音頻能像文本一樣被語言模型處理。這種方法:

  • 保留完整波形信息
  • 兼容預訓練語言模型架構
  • 簡化特征工程流程

二、核心數據處理:wav_to_token函數詳解

def wav_to_token(path):# 1. 音頻標準化:零均值單位方差sr, audio = wavfile.read(path)left = (audio - np.mean(audio)) / np.std(audio)# 2. 生成參考信號(關鍵創新點)right = np.linspace(0.1, sr*np.pi, left.size)# 3. 滑動窗口特征提取sim_list = []slope_list = []for i in range(0, len(left) - 400, 400):x, y = left[i:i + 1200], right[i:i + 1200]# 確保窗口長度一致if len(x) > len(y): x = x[:len(y)]# 線性回歸擬合波形趨勢slope, intercept = linear_regression(x, y)# 計算擬合質量(余弦相似度)sim_list.append(cosine_similarity(slope * x + intercept, y))slope_list.append(slope)# 4. Token量化sim_list = min_max_normalize(sim_list) * 2**6  # 64級量化slope_list = min_max_normalize(slope_list) * 2**7  # 128級量化sim_list = sim_list.astype(np.int16)slope_list = slope_list.astype(np.int16)# 5. 生成最終Tokenres = sim_list * slope_list  # 特征融合return res[res != 0]  # 去除零值
關鍵設計解析:
  1. 雙特征融合機制

    • sim_list:衡量原始波形與線性趨勢的匹配度(0-63)
    • slope_list:表征波形局部斜率變化(0-127)
    • 通過乘法融合創建64×128=8192種獨特Token
  2. 參考信號設計

    right = np.linspace(0.1, sr*np.pi, left.size)
    

    生成線性增長的參考信號,使回歸斜率能反映波形變化速率,避免絕對數值干擾

  3. 滑動窗口參數

    • 窗口大小:1200樣本(400步長)
    • 采樣率影響:44.1kHz下窗口≈27ms(語音處理常用幀長)
可視化Token生成過程
# 在wav_to_token函數中添加
plt.figure(figsize=(12, 8))
plt.subplot(2,1,1)
plt.plot(left[:2400], 'b', label='原始波形')
plt.plot(right[:2400], 'r--', label='參考信號')
plt.legend()plt.subplot(2,1,2)
plt.plot(sim_list, 'g', label='相似度特征')
plt.plot(slope_list, 'm', label='斜率特征')
plt.legend()
plt.savefig('token_generation.png', dpi=150)

三、詞匯表構建:音頻Token與文本標記的融合

# 特殊標記
voc = ["<|pad|>", "<|im_start|>", "<|im_end|>", "<|wav|>"]# 添加角色名稱(從目錄名提取)
voc += [i.split("\\")[-1] for i in dirs]  # 如"胡桃", "鐘離"# 添加音頻Token空間(0-8197)
voc += [str(i) for i in range(8198)]# 創建ID映射
voc_x2id = {v: i for i, v in enumerate(voc)}
voc_id2x = {i: v for i, v in enumerate(voc)}
詞匯表示例:
Token類型示例ID用途
特殊標記`<wav>`
角色名稱胡桃12分類目標
音頻Token124520波形特征

四、數據集構建:序列化音頻樣本

# 樣本結構:[起始標記] + [音頻Token序列] + [角色ID] + [結束標記]
data_set = []
for path in paths:tokens = wav_to_token(path)token_idx = [voc_x2id[str(i)] for i in tokens]role_id = voc_x2id[path.split("\\")[-2]]  # 從路徑提取角色名# 完整序列sample = [voc_x2id["<|im_start|>"]] + token_idx + [role_id] + [voc_x2id["<|im_end|>"]]data_set.append(sample)
序列化示例:
[3, 1245, 78, 309, ..., 8192, 12, 4] ↑    ↑             ↑    ↑|    |             |    |
起始  音頻特征      胡桃  結束

未來展望

  • 擴展到語音識別任務(添加字符級Token)
  • 結合對比學習提升特征表示
  • 部署到移動端實現實時角色識別

技術啟示:當我們將音頻視為“可讀的文本”時,語音處理就變成了自然語言處理——這是通往統一多模態模型的重要一步。


附錄:關鍵函數完整實現

import numpy as np
import pandas as pd
from tqdm import tqdmfrom shua2 import wav_to_token
from glob import glob
from model3 import SamOut
# from system_model import SamOut
import torch
from torch import nn, optim
import timetorch.manual_seed(42)
np.random.seed(42)
dirs=glob("C:/Users/dfy918/Downloads/yuanshen_zip/yuanshen_zip/*")paths = np.hstack([glob(dir+"/wav/*.wav") for dir in dirs])
voc = set([str(i) for i in range(8198)])# voc = ["<|pad|>", "<|im_start|>", "<|im_end|>", "<|wav|>", "<|cat|>", "<|cow|>", "<|dog|>", "<|pig|>"]+[i.split("\\")[-1] for i  in dirs] + list(voc)
voc = ["<|pad|>","<|im_start|>","<|im_end|>", "<|wav|>"]+[i.split("\\")[-1] for i  in dirs] + list(voc)
voc_x2id = {v: i for i, v in enumerate(voc)}
voc_id2x = {i: v for i, v in enumerate(voc)}
voc_size = len(voc)
# data_set = pd.read_pickle("wav.pkl")
data_set = []
for path in tqdm(paths):tokens = wav_to_token(path)token_idx = []for i in tokens.astype("str").tolist():token_idx.append(voc_x2id[i])data_set.append([1] + token_idx + [voc_x2id[path.split("\\")[-2].split("/")[0]]]+[2])
pd.to_pickle(data_set, "wav.pkl")
np.random.shuffle(data_set)train_data_set = data_set[:int(len(data_set) * 0.8)]
val_data_set = data_set[int(len(data_set) * 0.8):]
num_layers = 2
hidden_size = 2 ** 6 * num_layers
num_heads = num_layers
learning_rate = 0.001
batch_size = 32
num_epochs = 1000model = SamOut(voc_size=voc_size, hidden_size=hidden_size, num_heads=num_heads, num_layers=num_layers)
params = 0
for i in model.parameters():if i.shape != torch.Size([]):params += i.numel()
print(f"Total parameters: {params}")criterion = nn.CrossEntropyLoss(ignore_index=0)
optimizer = optim.Adam(model.parameters(), lr=learning_rate)start_time = time.time()
for epoch in range(num_epochs):# 訓練階段np.random.shuffle(train_data_set)model.train()bar=tqdm(range(0, len(train_data_set), batch_size))for i in bar:batch_data = train_data_set[i:i + batch_size]# 填充批次使所有序列長度相同max_len = max(len(seq) for seq in batch_data)padded_batch = []for seq in batch_data:padded_seq = seq + [0] * (max_len - len(seq))  # 使用0(<|pad|>)進行填充padded_batch.append(padded_seq)data = torch.tensor(padded_batch, dtype=torch.long)input_tensor = data[:, :-1]target_tensor = data[:, 1:]output, _ = model(input_tensor)output = output.reshape(-1, voc_size)target_tensor = target_tensor.reshape(-1)loss = criterion(output, target_tensor)optimizer.zero_grad()loss.backward()optimizer.step()bar.set_description(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')# 驗證階段model.eval()val_loss = 0correct = []total = 0with torch.no_grad():for i in range(0, len(val_data_set), batch_size):batch_data = val_data_set[i:i + batch_size]max_len = max(len(seq) for seq in batch_data)padded_batch = []for seq in batch_data:padded_seq = seq + [0] * (max_len - len(seq))  # 使用0(<|pad|>)進行填充padded_batch.append(padded_seq)data = torch.tensor(padded_batch, dtype=torch.long)input_tensor = data[:, :-1]target_tensor = data[:, 1:]output, _ = model(input_tensor)acc=np.mean((torch.argmax(output,-1)==target_tensor).numpy())correct.append(acc)output = output.reshape(-1, voc_size)target_tensor_flat = target_tensor.reshape(-1)# 計算驗證損失val_loss += criterion(output, target_tensor_flat).item()# 計算準確率 - 只關注倒數第二個token(類別標記)# 獲取每個序列的倒數第二個位置avg_val_loss = val_loss / (len(val_data_set) / batch_size)acc = np.mean(correct)print(f'Epoch [{epoch + 1}/{num_epochs}], Val Loss: {avg_val_loss:.4f}, Val Acc: {acc:.4f}, Time: {time.time() - start_time:.2f}s')print("Training complete.")
import numpy as np
import matplotlib.pyplot as plt
from scipy.io import wavfile# 中文顯示支持
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = Falsedef cosine_similarity(a, b):"""計算余弦相似度"""return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))def linear_regression(x, y):"""最小二乘法線性回歸"""n = len(x)sum_x, sum_y = np.sum(x), np.sum(y)sum_xy = np.sum(x * y)sum_x2 = np.sum(x ** 2)slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x ** 2)intercept = (sum_y - slope * sum_x) / nreturn slope, interceptdef min_max_normalize(data):min_val = np.min(data)max_val = np.max(data)return (data - min_val) / (max_val - min_val + 10 ** -8)def wav_to_token(path):# 數據讀取sr, audio = wavfile.read(path)left = (audio - np.mean(audio)) / np.std(audio)right = np.linspace(0.1,sr*np.pi,left.size)# 滑動窗口分析sim_list = []slope_list = []for i in range(0, len(left) - 400, 400):x, y = left[i:i + 1200], right[i:i + 1200]if len(x) > len(y): x = x[:len(y)]slope, intercept = linear_regression(x, y)sim_list.append(cosine_similarity(slope * x + intercept, y))slope_list.append(slope)# 可視化sim_list = np.array(sim_list)slope_list = np.array(slope_list)# token 化sim_list = min_max_normalize(sim_list) * 2 ** 6slope_list = min_max_normalize(slope_list) * 2 ** 7sim_list = sim_list.astype(np.int16)slope_list = slope_list.astype(np.int16)res = sim_list * slope_listreturn res[res != 0]# 歸一化 的最大值最小值應該是  全局考慮而不是 一段考慮 最好是精度極限

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/96670.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/96670.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/96670.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

關于TCP和UDP兩種網絡協議的區別

1、tcp協議TCP (Transmission Control Protocol - 傳輸控制協議)TCP 的核心目標是為應用層提供一條可靠的、無差錯的、有序的字節流通道。主要特點&#xff1a;面向連接&#xff1a;在數據傳輸之前&#xff0c;必須通過“三次握手”建立穩定的連接&#xff0c;傳輸結束后通過“…

Alibaba Lens:阿里巴巴推出的 AI 圖像搜索瀏覽器擴展,助力B2B采購

本文轉載自&#xff1a;https://www.hello123.com/alibaba-lens ** 一、&#x1f31f; 一鍵截圖&#xff0c;輕松找貨&#xff1a;采購神器 Alibaba Lens 詳解 Alibaba Lens 是阿里巴巴集團專為全球 B2B 采購商打造的一款智能瀏覽器插件&#xff08;支持 Chrome 等主流瀏覽器…

WPF常見問題清單

1.Grid 內容自動換行及自適應行高 <DataGrid Grid.Row"1" FontSize"14" IsReadOnly"True" VerticalScrollBarVisibility"Auto" RowHeight"NaN" ItemsSource"{Binding List}" AutoGenerateColumns"False…

Linux驅動開發筆記(十)——中斷

視頻&#xff1a;第13.1講 Linux中斷實驗-Linux內核中斷框架簡介_嗶哩嗶哩_bilibili 文檔&#xff1a;《【正點原子】I.MX6U嵌入式Linux驅動開發指南V1.81.pdf》五十一章 1. 中斷API函數 每個中斷都有一個中斷號&#xff0c;通過中斷號即可區分不同的中斷。在Linux 內核中使用一…

ubuntu18.04安裝PCL1.14

簡化版說明 1. 安裝依賴庫&#xff1a; (1) boost1.84 &#xff08;https://www.boost.org/releases/1.84.0/&#xff09; tar vxf boost_xxx.tar.gz ./bootstrap.sh --prefix/usr/local/ ./b2 sudo ./b2 install (2) vtk9.1.0 &#xff08;https://vtk.org/files/releas…

python將pdf轉txt,并切割ai

python將pdf轉txt&#xff0c;并切割ai step1:pdf轉換 from PIL import Image import pytesseract import os import tempfile from pdf2image import convert_from_path# 設置 Tesseract 路徑 pytesseract.pytesseract.tesseract_cmd rC:\Users\wangrusheng\AppData\Local\Pr…

Ubuntu22.04更換阿里鏡像源,ubuntu更換源

在 Ubuntu 22.04 上更換為阿里云鏡像源可以加速軟件包的下載和更新&#xff0c;大幅提升系統更新速度。以下是更換阿里云鏡像源的步驟&#xff1a;1. 備份現有源列表在更換鏡像源之前&#xff0c;建議先備份當前的源配置文件&#xff1a;bashsudo cp /etc/apt/sources.list /et…

Git版本控制工具+基礎命令

Git是什么&#xff1f;Git是目前世界上最先進的分布式版本控制系統代碼托管平臺&#xff1a;Gitlab/Github/Gitee&#xff08;碼云&#xff09;什么是版本控制系統&#xff1f;指對軟件開發過程中各種程序代碼、配置文件及說明文檔等文件變更的管理。版本控制最主要的功能就是追…

圖解設計模式【3】

本系列共分為三篇文章&#xff0c;其中包含的設計模式如下表&#xff1a; 名稱設計模式圖解設計模式【1】Iterator、Adapter、Template Method、Factory Method、Singleton、Prototype、 Builder、Abstract Factory、 Bridge、 Strategy圖解設計模式【2】Composite、 Decorato…

(純新手教學)計算機視覺(opencv)實戰十四——模板與多個對象匹配

圖片旋轉、圖片鏡像相關教學&#xff1a; &#xff08;純新手教學&#xff09;計算機視覺&#xff08;opencv&#xff09;實戰十三——圖片旋轉、圖片鏡像 的幾種常用方法-CSDN博客https://blog.csdn.net/2302_78022640/article/details/151356600?spm1011.2415.3001.5331 模板…

Java面試核心知識點總結:Redis與MySQL高可用、高并發解決方案

在分布式系統開發中&#xff0c;高并發場景下的數據一致性、系統可用性以及性能優化始終是核心挑戰。本文基于Java技術棧&#xff0c;結合Redis與MySQL的工程實踐&#xff0c;系統梳理分布式系統設計的關鍵技術要點。一、Redis集群架構演進與高可用實踐1.1 主從哨兵模式部署方案…

R 語言科研繪圖第 72 期 --- mantel檢驗圖

在發表科研論文的過程中&#xff0c;科研繪圖是必不可少的&#xff0c;一張好看的圖形會是文章很大的加分項。 為了便于使用&#xff0c;本系列文章介紹的所有繪圖都已收錄到了 sciRplot 項目中&#xff0c;獲取方式&#xff1a; R 語言科研繪圖模板 --- sciRplothttps://mp.…

4.2-中間件之MySQL

4.2.1MySQL的基本知識SQL語句用于存取數據以及查詢、更新和管理關系數據庫系統。包括&#xff1a;DQL&#xff08;select&#xff09;、DML&#xff08;insert,update,delete&#xff09;、DDL&#xff08;create,alter,drop&#xff09;、DCL&#xff08;grant,revoke&#xf…

LVS + Keepalived 高可用負載均衡集群

目錄 一、核心組件與作用 1. LVS&#xff08;Linux Virtual Server&#xff09; 2. Keepalived 二、DR 模式下的 LVS Keepalived 工作原理 1. 整體架構 2. 數據包流向&#xff08;DR 模式&#xff09; 三、部署步驟&#xff08;DR 模式&#xff09; 3.1 環境規劃 3.2…

知識沉淀過于碎片化如何形成體系化框架

要將過于碎片化的知識沉淀轉變為體系化的框架&#xff0c;必須采取一套自上而下設計與自下而上歸集相結合的系統性方法&#xff0c;其核心路徑在于首先進行戰略性診斷與頂層藍圖設計、其次構建統一且可擴展的知識架構&#xff08;分類與標簽體系&#xff09;、然后實施系統性的…

XLua教程之C#調用Lua

上一篇文章 XLua教程之入門篇-CSDN博客 在C#腳本中訪問lua全局數據&#xff0c;特別是table以及function&#xff0c;代價比較大&#xff0c;建議盡量少做相關操作。 LuaEnv.Global.Get 用于獲取一個全局變量&#xff0c;但是無法獲取局部變量(用local修飾) 全局基本類型變量…

C++ 標準庫中的哈希函數:從std::hash到自定義哈希器

C 標準庫中的哈希函數&#xff1a;從 std::hash 到自定義哈希器 1. 引言 在上一篇中&#xff0c;我們介紹了哈希表為什么能夠實現 O(1) 查找。 核心秘密在于&#xff1a;哈希函數。 在 C 標準庫中&#xff0c;哈希表容器&#xff08;如 unordered_map、unordered_set&#xff0…

在圖形 / 游戲開發中,為何 Pixels Per Unit(PPU)數值越小,物體在屏幕上顯示的尺寸越大?

1. 什么是 PPU&#xff1f; PPU&#xff08;Pixels Per Unit&#xff09;指的是 多少像素對應游戲世界中的一個單位&#xff08;Unit&#xff09;。 在 Unity 等游戲引擎中&#xff0c;1 Unit 通常被視為世界空間的基本長度&#xff0c;比如 1 米。2. PPU 與物體大小的關系PPU …

【ZYNQ開發篇】Petalinux和電腦端的靜態ip地址配置

使用Petalinux工具為ZYNQ板卡搭建嵌入式Linux操作系統&#xff0c;成功搭建后&#xff0c;用戶通常會使用客戶端軟件對ZYNQ板卡上的Linux系統進行訪問&#xff0c;軟件需要知道ZYNQ板卡的ip地址才能進行訪問&#xff0c;如果ip地址是動態變化的&#xff0c;軟件每次訪問都要重新…

AVL樹知識總結

AVL樹概念性質一顆AVL樹或是空樹&#xff0c;或者具有一下性質的二叉搜索樹&#xff1a;左右都是AVL樹&#xff0c;左右子樹高度差的絕對值不超過1AVL樹有n個結果&#xff0c;高度保持在O&#xff08;logN&#xff09; 搜索時間復雜度O(logN&#xff09;模擬實現插入定義&#…