vector的学习和模拟

vector是表示可变大小数组的序列容器。vector就像数组一样,采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素 进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。手册:https://cplusplus.com/reference/vector/vector/rbegin/

构造函数

#define _CRT_SECURE_NO_WARNINGS 
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

void test_vector1()
{
	vector<int> v1;  // 创建一个vector对象,v1;类型为vector<int>
	v1.push_back(1); // 尾插
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	vector<int> v2(v1);  // 拷贝构造
	vector<int> v3;
	v3 = v2;             // 赋值
	for (size_t i = 0;i < v1.size();i++) // 下标访问
	{
		cout << v1[i] << " ";
	}
	cout << endl; 

	for (size_t i = 0;i < v2.size();i++)
	{
		cout << v2[i] << " ";
	}
	cout << endl;
	for (size_t i = 0;i < v3.size();i++)
	{
		cout << v3[i] << " ";
	}
	cout << endl;
}

三种访问元素的方式

void test_vector2()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	
	// 三种遍历  1、operator[]
	for (size_t i = 0;i < v1.size();i++)
	{
		cout << v1[i] << " ";
	}
	cout << endl;
	// 修改内容
	for (size_t i = 0;i < v1.size();i++)
	{
		v1[i] *= 2;
	}
	// 2、迭代器
	vector<int>::iterator it = v1.begin();
	while (it != v1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	// 3、范围for
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}

迭代器:

1、获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator

2、获取最后一个数据位置的reverse_iterator/const_reverse_iterator,获取第一个数据前一个位置的 reverse_iterator/const_reverse_iterator

reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问 题。 resize在开空间的同时还会进行初始化,影响size。和string类类似。

void test_vector3()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	cout << v1.size() << endl;
	cout << v1.capacity() << endl;
	cout << v1.empty() << endl;

	v1.reserve(10);               // 将capacity增大至10,size不变
	cout << v1.size() << endl;
	cout << v1.capacity() << endl;

	v1.resize(15);               // 将size增大至15,新开的空间,用0初始化,capacity也会相应增大
	cout << v1.size() << endl;
	cout << v1.capacity() << endl;
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}

增删查改,和string不一样vector没有find接口,这个接口时algorithm库提供的。

void test_vector4()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);

	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	/*v1.pop_back();
	v1.pop_back();
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	v1.insert(v1.begin(), 0);  // 在第一个位置插入0
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;*/

	v1.insert(v1.begin()+v1.size(), 6); // 在末尾插入6
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	v1.erase(v1.begin(), v1.begin()+2); // 删除前2个数据
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	// find不是vector的成员函数,是algorithm的模板函数,需要包含头文件:algorithm
	vector<int>::iterator pos = find(v1.begin(), v1.end(),5); //在【fist,last)区间找,找到了返回该元素个迭代器
	if (pos != v1.end())   // 如果找不到会返回v1.end()
	{
		cout << *pos << endl;
		v1.erase(pos);  // 删除5
	}
	for (auto e : v1)
	{
		cout << e << " ";
	}
}

void test_vector5()
{
	vector<int> v1;
	v1.push_back(12);
	v1.push_back(2);
	v1.push_back(32);
	v1.push_back(24);
	v1.push_back(15);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	sort(v1.begin(), v1.end()); // 排序,不是vector的成员函数,是algorithm的模板函数,需要包含头文件:algorithm
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}

迭代器失效问题:

实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器, 程序可能会崩溃)。

会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、 push_back等。

#include <iostream>
using namespace std;
#include <vector>
 
int main()
{
 vector<int> v{1,2,3,4,5,6};
 
 auto it = v.begin();
 
 // 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
v.resize(100, 8);
while(it != v.end()) // 报错;解决办法是在遍历之前it = v.begin();
 {
 cout<< *it << " " ;
 ++it;
 }
 cout<<endl;
 return 0;
}

指定位置元素的删除操作--erase

void test_vector4()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);

	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	auto it = v1.begin();
	/*while (it != v1.end()) // 错误删除,迭代器失效,删除2之后数据往前挪了,迭代器变为 “悬空” 状态。
	{
		if (*it % 2 == 0)
			v1.erase(it); // erase返回值是删除元素的下一个元素的迭代器

		else
			it++;
	}*/
	//
	while (it != v1.end())  // 
	{
		if (*it % 2 == 0)
			it=v1.erase(it);

		else
			it++;
	}

	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}

与vector类似,string在插入+扩容操作+erase之后,迭代器也会失效;迭代器失效解决办法:在使用前,对迭代器重新赋值即可。

vector简单模拟实现

假如如上有一个数组,使用3个指针来描述,数组的起始地址和有效数据的下一位置和有效空间的下一个位置。

那么构造函数为:

vector()
    :_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
	{
	}

~vector() // 析构
{
     if(_star)
      {
          delete[] _start;
          _start = _finish = _endofstorage = nullptr;
      }
			
}

拷贝构造

        // 拷贝构造1
		//vector(const vector<T>& v)
		//{
		//	_start = new T[v.capacity()];
		//	_finish = _start;
		//	_endofstorage = _start + v.capacity();

		//	for (size_t i = 0;i < v.size();i++)
		//	{
		//		*_finish = v[i];
		//		++_finish;
		//	}
		//}
		// 拷贝构造2--简洁版
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(v.capacity());
			for (const auto& e : v)
			{
				push_back(e);
			}
		}

赋值operator=

        // v1=v3;--1
		vector<T>& operator=(const vector<T>& v)
		{
			if (this != &v)
			{
				delete[] _start;
				_start = new T[v.capacity];
				memcpy(_start, v._start, sizeof(T) * v.size());
			}
			return *this;
		}
		//// v1=v3;--2--这里会有三次赋值的时候有三次深拷贝,消耗很大(在algorithm的swap内部有三次深拷贝)
		//vector<T>& operator=( vector<T> v)
		//{
		//	swap(_start, v._start);
		//	swap(_finish, v._finish);
		//	swap(_endofstorage, v._endofstorage);
		//	return *this;
		//}
		//// v1=v3;--3
		//vector<T>& operator=(vector<T> v)  // v是v3的拷贝构造的临时容器拿去和v2交换,函数调用结束时调用v的析构函数释放v
		//{
		//	Swap(v);
		//	return *this;
		//}
		//void Swpa(vector<T>& v)
		//{
		//	swap(_start, v._start);
		//	swap(_start, v._finish);
		//	swap(_start, v._endofstorage);
		//}

迭代器

		// 迭代器
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		const_iterator begin() const
		{
			return _start;
		}
		const_iterator end() const
		{
			return _finish;
		}

扩容

void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();  // 获得旧空间的size,否则释放_start之后无法得到size
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * sz);  // 按字节拷贝,将_start的元素拷贝到tmp,如果对象是vector<string>的时候会报错,因为是浅拷贝
					for (size_t i = 0;i < sz;i++)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = tmp + sz;
				_endofstorage = tmp + n;
			}
		}

尾插/尾删和随机插入

		void push_back(const T& x)
		{
			if (_finish == _endofstorage)
			{
				size_t newcapacity = capacity() == 0 ? 2 : 2 * capacity(); // 如果容器为空那么开辟2个空间否则2倍开空间
				reserve(newcapacity);
			}
			*_finish = x;
			++_finish;
		}
		void pop_back()
		{
			assert(_start < _finish);
			--_finish;
		}
		void insert(iterator pos, const T& x)
		{
			assert(pos <= _finish);
			if (_finish == _endofstorage)
			{
				size_t n = pos - _start;   // 获得pos的下标,reserve之后,原本的_start的内存被释放了,pos成为野指针
				size_t newcapacity = capacity() == 0 ? 2 : 2 * capacity();
				reserve(newcapacity);
				pos = _start + n;  // 在新空间中,找到pos位置
			}
			// 挪动数据
			iterator end = _finish - 1;// 最后一个数据的指针
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			*pos = x;
			_finish++;
		}

resize/

iterator erase(iterator pos)
		{
			assert(pos < _finish);

			iterator it = pos;
			while (it < _finish) // 将pos之后的数据往前挪一位,覆盖pos位置原数据
			{
				*it = *(it + 1);
				it++;
			}
			--_finish;

			return pos;
		}
		void resize(size_t n, const T& val = T())// 传的参数是未知的,可能是内置类型,也可能是自定义类型,所以用T()做缺省值
		{
			if (n < size())  // 调整_finish指向新的末尾即可
			{
				_finish = _start - n;
			}
			else
			{
				if (n > capacity()) // 增容
				{
					reserve(n);
				}
				while (_finish < _start + n)  // 从finish位置开始将数据初始化为val
				{
					*_finish = val;
					++_finish;
				}
			}
		}

		size_t size()
		{
			return _finish - _start;
		}
		size_t capacity()
		{
			return _endofstorage - _start;
		}
		T& operator[](size_t i)
		{
			assert(i < size());
			return _start[i];
		}
		const T& operator[](size_t i) const
		{
			assert(i < size());
			return _start[i];
		}

	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	};

<think>好的,我现在需要详细解释i-vectorx-vector,并且确保符合之前用户提供的格式要求。首先,我需要回忆这两种技术的核心概念、发展背景、结构差异以及应用场景。 首先,i-vector是说话人识别中的一种方法,全称为“身份向量”(Identity Vector),主要用于将高维度的语音特征映射到低维的子空间。它是在GMM-UBM之后发展起来的,解决了GMM-UBM在高维数据上的不足。而x-vector则是更近期的技术,基于深度神经网络(DNN),在结构上不同于传统的因子分析方法。 接下来,我需要分步骤详细阐述两者的定义、核心思想、训练流程、关键技术细节、应用场景以及对比。同时,要确保数学公式用正确的LaTeX格式,行内公式用$...$,独立公式用$$...$$,并且结构清晰。 首先,i-vector部分: 1. **基本定义**:i-vector是一个低维的固定长度向量,能够捕捉语音中的说话人信道信息。它基于因子分析模型,将语音会话表示为全局差异空间(Total Variability Space)中的一个点。 2. **核心思想**:传统方法如JFA(联合因子分析)分别处理说话人信道因子,而i-vector将其合并为一个总变差空间,简化模型并提高效果。 3. **训练流程**: - 训练UBM,提取Baum-Welch统计量。 - 估计总变差矩阵T,将高维的Baum-Welch统计量投影到低维i-vector。 4. **数学公式**:需要包括i-vector的生成公式,如M = m + Tw,其中M是GMM均值超向量,m是UBM均值超向量,T是总变差矩阵,w是i-vector。 5. **关键技术细节**:可能需要提到后处理,如PLDA(概率线性判别分析)用于信道补偿。 然后是x-vector部分: 1. **基本定义**:x-vector是基于深度神经网络的嵌入向量,通过DNN从语音中直接提取说话人特征,通常在时延神经网络(TDNN)结构上实现。 2. **核心思想**:利用深度学习的强大表示能力,直接从帧级别的特征聚合到说话人级别的特征,减少对手工设计特征的依赖。 3. **训练流程**: - 输入是帧级别的特征(如MFCCs)。 - 经过多个时间延迟层处理,然后统计池化层(如平均标准差)生成固定长度的向量。 - 全连接层进一步处理,最后通过softmax分类。 4. **数学公式**:可能不需要复杂的公式,但需要描述网络结构,比如TDNN层的时延操作,池化层的聚合方法。 5. **关键技术细节**:数据增强(如加噪声、混响)、端到端训练、损失函数(如交叉熵、AM-Softmax)。 接下来,对比部分需要列出两者的优缺点应用场景,比如i-vector对数据量需求大,x-vector在小数据下表现更好,但需要更多计算资源。 还要注意用户提到的系统级指令,比如所有行内数学表达式用$...$,独立公式单独成段,正确使用LaTeX语法,以及结构清晰的中文回答。 可能需要检查是否有遗漏的关键点,比如i-vector中的总变差空间与JFA的区别,x-vector中的结构细节如TDNN层的作用,以及实际应用中的性能比较。 在组织内容时,使用标题子标题来分块,确保每个部分都有明确的重点,比如定义、核心思想、训练流程、数学公式、关键技术、应用对比。 最后,总结两者的优缺点,帮助用户理解在不同场景下的选择依据。</think>--- ### **i-vector 与 x-vector 详解** #### **1. i-vector(身份向量)** ##### **1.1 基本定义** - **i-vector** 是一种固定长度的低维向量(通常$100$-$600$维),用于表示语音中的**说话人身份****信道信息**。它基于**总变差空间(Total Variability Space)**建模,将语音会话映射到该空间中的一个点。 ##### **1.2 核心思想** - 传统方法(如JFA)分别建模**说话人因子****信道因子**,而i-vector将其合并为一个统一的低维空间,简化模型。 - 公式表示: $$\boldsymbol{M} = \boldsymbol{m} + \boldsymbol{T} \boldsymbol{w}$$ 其中: - $\boldsymbol{M}$:自适应后的GMM均值超向量 - $\boldsymbol{m}$:UBM的均值超向量 - $\boldsymbol{T}$:总变差矩阵(低维投影矩阵) - $\boldsymbol{w}$:i-vector(低维身份向量) ##### **1.3 训练流程** **步骤1:UBM与Baum-Welch统计量** - 训练UBM(通用背景模型),提取每个语音段的**Baum-Welch统计量**: - 零阶统计量:$N_k = \sum_{t=1}^T \gamma_t(k)$ - 一阶统计量:$\boldsymbol{F}_k = \sum_{t=1}^T \gamma_t(k) \boldsymbol{x}_t$ 其中$\gamma_t(k)$为第$k$个高斯分量对帧$\boldsymbol{x}_t$的后验概率。 **步骤2:估计总变差矩阵$\boldsymbol{T}$** - 通过EM算法迭代优化$\boldsymbol{T}$,最大化所有语音数据的似然。 **步骤3:提取i-vector** - 对每条语音,计算其$\boldsymbol{w}$: $$\boldsymbol{w} = (\boldsymbol{I} + \boldsymbol{T}^T \boldsymbol{\Sigma}^{-1} \boldsymbol{N} \boldsymbol{T})^{-1} \boldsymbol{T}^T \boldsymbol{\Sigma}^{-1} \boldsymbol{F}$$ 其中$\boldsymbol{N}$为对角矩阵,$\boldsymbol{\Sigma}$为UBM协方差矩阵。 ##### **1.4 关键技术细节** - **后处理**:使用**PLDA(概率线性判别分析)**去除信道影响,公式: $$\boldsymbol{w} = \boldsymbol{U} \boldsymbol{y} + \boldsymbol{\epsilon}$$ 其中$\boldsymbol{U}$为信道空间,$\boldsymbol{y}$为纯净说话人因子,$\boldsymbol{\epsilon}$为噪声。 - **长度归一化**:对i-vector进行L2归一化,提升余弦相似度计算的鲁棒性。 ##### **1.5 应用场景** - 说话人验证(如电话身份认证) - 语音聚类(如会议记录分割) --- #### **2. x-vector(深度嵌入向量)** ##### **2.1 基本定义** - **x-vector** 是基于深度神经网络(DNN)提取的固定维度的说话人嵌入向量(通常$512$维),直接通过端到端训练从语音中学习身份特征。 ##### **2.2 核心思想** - 利用**时延神经网络(TDNN)**处理帧级特征,通过**统计池化层**聚合时序信息,生成说话人级别的向量。 - 网络结构示例: ```plaintext 输入帧特征 → TDNN层(时延卷积) → 统计池化(均值/标准差) → 全连接层 → softmax分类 ``` ##### **2.3 训练流程** **步骤1:帧级特征处理** - 输入:帧级MFCC或FBank特征(如$40$维)。 - 经过多个TDNN层,捕获局部上下文信息(如前后$5$帧)。 **步骤2:统计池化** - 对TDNN输出计算**均值****标准差**,拼接成固定长度向量: $$\boldsymbol{h}_{\text{pool}} = [\mu_1, \sigma_1, \mu_2, \sigma_2, \dots]$$ **步骤3:全连接层与分类** - 池化后的向量通过全连接层映射到$x$-vector: $$\boldsymbol{x} = \text{FC}(\boldsymbol{h}_{\text{pool}})$$ - 使用softmax分类器优化说话人ID损失函数。 ##### **2.4 关键技术细节** - **数据增强**:添加噪声、混响等模拟真实环境。 - **损失函数**:改进的损失函数(如**AM-Softmax**、**AAM-Softmax**)增强类内紧致性。 - **端到端优化**:直接最小化说话人分类误差,无需手工设计特征。 ##### **2.5 应用场景** - 短语音说话人验证(如智能音箱唤醒) - 跨语言说话人识别 --- #### **3. i-vector 与 x-vector 对比** | 特性 | i-vector | x-vector | |---------------------|-----------------------------------|-----------------------------------| | **理论基础** | 因子分析(概率生成模型) | 深度学习(判别模型) | | **特征提取** | 依赖UBM手工统计量 | 端到端自动学习 | | **数据需求** | 需大量无标签数据训练UBM | 需标注说话人数据 | | **计算复杂度** | 中等(EM迭代耗时) | 高(GPU训练,但推理快) | | **鲁棒性** | 对信道敏感,需PLDA后处理 | 对噪声信道变化更鲁棒 | | **典型维度** | 100-600 | 512-1024 | --- #### **4. 数学示例** **i-vector的生成公式**: $$\boldsymbol{w} = \arg \max_{\boldsymbol{w}} \sum_{t=1}^T \sum_{k=1}^K \gamma_t(k) \log \mathcal{N}(\boldsymbol{x}_t | \boldsymbol{\mu}_k + \boldsymbol{T}_k \boldsymbol{w}, \boldsymbol{\Sigma}_k)$$ **x-vector的池化操作**: $$\mu_i = \frac{1}{T} \sum_{t=1}^T h_{i,t}, \quad \sigma_i = \sqrt{\frac{1}{T} \sum_{t=1}^T (h_{i,t} - \mu_i)^2}$$ --- #### **5. 优缺点总结** - **i-vector**: - 优点:理论清晰,适合无监督场景。 - 缺点:依赖UBM,对短语音敏感。 - **x-vector**: - 优点:端到端优化,长尾数据表现好。 - 缺点:需标注数据,训练成本高。 --- #### **6. 实际应用案例** - **i-vector**:银行电话客服的说话人验证系统。 - **x-vector**:智能家居设备的声纹解锁功能。 --- 以上为i-vector与x-vector的核心内容,如需进一步探讨实现细节或最新进展(如ECAPA-TDNN),请随时提问!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值