李宏毅2022機器學習HW4 Speaker Identification上(Dataset &Self-Attention)

發表於2024-03-01

Homework4

Dataset介紹及處理

Dataset introduction

訓練資料集metadata.json包括speakers和n_mels,前者表示每個speaker所包含的多條語音資訊(每條資訊有一個路徑feature_path和改條資訊的長度mel_len或理解為frame數即可),後者表示濾波器數量,簡單理解為特徵數即可,由此可知每個.pt語言檔案可以表示為大小為mel_len \(\times\) n_mels的矩陣,其中所有檔案已規定n_mels為40,不同的是語言資訊的長度即mel_len。

測試資料集testdata.json包括n_mels和utterances,其中n_mels和意義前面一樣且固定為40,utterance表示一條語音資訊,不同的是這裡我們不知道這則資訊是誰說出來的,任務就是檢測這些資訊分別是誰說的。

對映檔案mapping.json包括兩項speaker2id和id2speaker,前者將600個speaker的編號隱射為0-599,後者則是相反的操作。

Dataset procession

正如上面說,儘管“古聖先賢”已經幫我們做好了絕大多數的資料處理操作,但是如果想要批次訓練資料那麼就需要對每個語音序列的mel_len進行固定,比如在sample code裡面固定為128個frame

class myDataset(Dataset):
	def __init__(self, data_dir, segment_len=128):

它的具體實現方式如下

def __getitem__(self, index):
		feat_path, speaker = self.data[index]
		# Load preprocessed mel-spectrogram.
		mel = torch.load(os.path.join(self.data_dir, feat_path))

		# Segmemt mel-spectrogram into "segment_len" frames.
		if len(mel) > self.segment_len:
			# Randomly get the starting point of the segment.
			start = random.randint(0, len(mel) - self.segment_len)
			# Get a segment with "segment_len" frames.
			mel = torch.FloatTensor(mel[start:start+self.segment_len])
		else:
			mel = torch.FloatTensor(mel)
		# Turn the speaker id into long for computing loss later.
		
		speaker = torch.FloatTensor([speaker]).long()
		return mel, speaker

即從每個語音序列總的frame中抽取連續的128個frame,但是這並不能夠保證所有的語音需要都為128個frame,可能某語音序列的frame數原本就小於128,因此還需要另外一道保險即padding。

def collate_batch(batch):
	# Process features within a batch.
	"""Collate a batch of data."""
	mel, speaker = zip(*batch)
	# Because we train the model batch by batch, we need to pad the features in the same batch to make their lengths the same.
	mel = pad_sequence(mel, batch_first=True, padding_value=-20)    # pad log 10^(-20) which is very small value.
	# mel: (batch size, length, 40)
	return mel, torch.FloatTensor(speaker).long()


def get_dataloader(data_dir, batch_size, n_workers):
	"""Generate dataloader"""
	dataset = myDataset(data_dir)
	speaker_num = dataset.get_speaker_number()
	# Split dataset into training dataset and validation dataset
	trainlen = int(0.9 * len(dataset))
	lengths = [trainlen, len(dataset) - trainlen]
	trainset, validset = random_split(dataset, lengths)

	train_loader = DataLoader(
		trainset,
		batch_size=batch_size,
		shuffle=True,
		drop_last=True,
		num_workers=n_workers,
		pin_memory=True,
		collate_fn=collate_batch,
	)
	valid_loader = DataLoader(
		validset,
		batch_size=batch_size,
		num_workers=n_workers,
		drop_last=True,
		pin_memory=True,
		collate_fn=collate_batch,
	)

	return train_loader, valid_loader, speaker_num

在data_loader讀取一個batch時,如果仍舊有不同大小的frame,那麼就可以透過指定collate_fn=collate_batch對資料進行填充,確保每個batch的frame相同進而進行批次(並行?)計算。
當然你也可以不預先設定frame數為128,僅使用collate_batch,那麼就會使這個batch內所有語音序列的frame數自動對齊到最長的frame,但往往會爆視訊記憶體。

Self-Attention簡單介紹

在不考慮Multi-Head Attention以及add &norm的情況下
\(X'=\)Self-Attention\((X, W^q, W^k, W^v, W^l)\) = \(\frac{XW^q(XW^k)^T}{\sqrt{d_k}}XW^vW^l\)
其中\(X, W^q, W^k, W^v, W^l\)維度分別為\(n\times d_{model}, d_{model}\times d_k, d_{model}\times d_k, d_{model}\times d_v, d_v\times d_{model}\),並稱\(Q=X\times W^q, K = X\times W^k, V = X\times W^v\)
可知,變化後的\(X'\)與原來\(X\)的維度相同,這一變化過程簡稱為編碼,那麼問題來了\(X'\)\(X\)究竟有何不同?
在變化過程中,我們對\(Q\)\(K\)做了內積運算,這表示一個查詢匹配過程,內積越大則相似度越高,那麼匹配所得的權重也就越大(表明越關注這個訊息),之後再和\(V\)運算相當於透過前面的權重及原始\(V\)對新\(X\)\(V\)做預測,影片給出了很生動的表述。

但是這裡我有一疑問,為什麼點積運算可以表示關注度?點積可以在一定程度表示兩向量的關聯性,這一點毫無疑問,但是憑什麼關聯性越高也意味著關注度(得分)越高呢?

相關文章