2024/07/14

話者ベクトル

話者ベクトルをhttps://github.com/speechbrain/speechbrain.gitで取得してみます。

話者ベクトルの計算にはEncoderClassifier.encode_batchを使います。サンプリング周波数16kHzの音声データをこの関数に入力すると話者ベクトルが得られます。

それ以外の部分は音声の読み込みとt-SNEによる2Dベクトル化、散布図描画のための色の選択と、散布図の作成をしているだけです。

35行目でEncoderClassifierを作成して、52行目で話者ベクトルを取得します。

audio-16k.listは1行ごとに.wavファイルへのパスが書かれていることを想定しています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import sys
from collections import defaultdict
import numpy as np
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
import torchaudio

sys.path.append("speechbrain")
from speechbrain.inference import EncoderClassifier

def make_colormap(label_list, min_count):
    color_map = defaultdict(int)
    for label in label_list:
        color_map[label] += 1

    color_map_sorted = []
    for k, v in color_map.items():
        color_map_sorted.append((v, k))
    color_map_sorted = reversed(sorted(color_map_sorted))

    colors = ["b", "g", "r", "c", "m", "y", "k", "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9"]
    cindex = 0
    for v, k in color_map_sorted:
        if v < min_count:
            color_map[k] = None
            continue
        color_map[k] = colors[cindex] if cindex < len(colors) else "grey"
        cindex += 1
    color_list = []
    for i in range(len(label_list)):
        color_list.append(color_map[label_list[i]])
    return color_list

def main(wav_list):
    ec = EncoderClassifier.from_hparams(
            source="speechbrain/spkrec-ecapa-voxceleb",
            savedir="cache",
            run_opts={"device": "cuda"},
            use_auth_token=False
         )
    ec.hparams.label_encoder.ignore_len()
    embed_list = []
    label_list = []
    with open(wav_list) as wavs:
        i = 0
        for line in wavs:
            i += 1
            line = line.strip()
            signal, fs = torchaudio.load(line)
            assert fs == 16000
            # Get a speaker vector
            embed_list.append(ec.encode_batch(signal, wav_lens=None).squeeze(dim=1).cpu().numpy())
            label_list.append(line.split("/")[1].split("_")[0]) # Get speaker name
            if i % 100 == 0:
                print(i)
    label_list = make_colormap(label_list, min_count=100)

    # Filter speaker vectors
    embed_list = [embed_list[i] for i in range(len(label_list)) if label_list[i] is not None]
    label_list = [label_list[i] for i in range(len(label_list)) if label_list[i] is not None]
    embed_list = np.concatenate(embed_list)

    tsne = TSNE(n_components=2, random_state=123)
    vec_2d = tsne.fit_transform(embed_list)

    plt.figure(figsize=(10, 8))
    plt.scatter(vec_2d[:,0], vec_2d[:,1], alpha=0.7, c=label_list)
    plt.title("t-SNE visualization of speaker vectors")
    plt.xlabel("t-SNE feature 1")
    plt.ylabel("t-SNE feature 2")
    plt.grid(True, linestyle="--", alpha=0.7)
    plt.savefig("spk_distribution.png", dpi=300, bbox_inches="tight")
    plt.close()

if __name__ == "__main__":
    main("audio-16k.list")
実行すると、以下のような散布図が得られます。少し混じっているところはあるものの、色ごとに、つまり話者ごとにクラスタが異なっていることが分かります。

2024/07/06

moreの挙動が変化した

Debianを11から12にアップグレードしたところ、moreコマンドのデフォルトの挙動が変化しました。

具体的には、表示するテキストの行が画面の行数よりも短いときは、単にcatで出力される状態であったのに、画面をクリアして画面の上部に表示されるようになりました。

lessでいうと、less -Fの挙動をしていたものが、less -cに似た挙動に変更されてしまいました。しかも、元の画面を削除して復旧しないという挙動です。

less -Fが手間なのでmoreを使っていたのに、これは困ったことになってしまいました。

Perplexityによると

Linuxのmoreコマンドの動作変更は、util-linux パッケージのバージョン2.38で導入されました。この変更により、表示する内容が端末の画面高さよりも少ない行数の場合でも、画面の上部から表示されるようになりました。 この変更は、より一貫性のある動作を提供し、ユーザーエクスペリエンスを向上させることを目的としています。以前のバージョンでは、内容が短い場合に画面の下部に表示されることがあり、これが一部のユーザーにとって混乱を招く原因となっていました。 util-linux 2.38は2022年4月にリリースされており、この版で上記の動作変更が実装されました。したがって、util-linux 2.37.2と2.38.1の間で発生したこの変更は、正確には2.38で導入されたものです。
とのこと。

moreコマンドが含まれているlinux-utilのバージョンは、Debian 11では2.36.1、Debian 12では2.38.1です。また、Ubuntu 22.04のmoreはDebian 11と挙動が同じで、そのバージョンは2.37.2です。

そこで、linux-utilのログを調べてみると、

2021-06-01	build-sys: release++ (v2.37)v2.37
2022-01-31	build-sys: release++ (v2.38-rc1)v2.38-rc1
2022-03-28	build-sys: release++ (v2.38)v2.38
でした。これらの間のどこかに変更が含まれているはずです。

調べてみると

commit df6b29d3b8e9a55d2bfa69c593e17da4d11a3fac
AuthorDate: Wed Sep 29 14:50:14 2021 +0200
CommitDate: Wed Sep 29 14:50:14 2021 +0200

    more: POSIX compliance patch preventing exit on EOF without -e
で-eオプションが導入されて、デフォルトの挙動が変わっていました。とても煩わしいです。

さらにその後のコミットをたどっていくと、なんと、

commit 28b391ce7e58f8327c092b3911c05f526d0ad586
AuthorDate: Wed Jun 15 10:03:44 2022 +0200
CommitDate: Wed Jun 15 10:03:44 2022 +0200

    more: restore exit-on-eof if POSIXLY_CORRECT is not set
    
    In version 2.38, exit-on-eof has been disabled by default. This change
    is annoying for users and forces many users to use 'alias more="more
    -e"'. It seems better to force POSIX lovers to use POSIXLY_CORRECT
    env. variable and stay backwardly compatible by default.
    
    Addresses: https://github.com/util-linux/util-linux/issues/1703
    Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=2088493
 
がコミットされていて、POSIXでないとやだ!っていう人は環境変数POSIXLY_CORRECTを使いなさいという形に戻っていました。

Debian 12のパッケージ更新タイミングの運のなさよ。とりあえず、alias more="more -e"で回避するしか無さそうです。