Pomiń nawigację

Russian Open Speech To Text

Speech to Text Russian Open STT

Kolekcja próbek mowy z różnych źródeł dźwięku. Ten zestaw danych zawiera krótkie klipy audio w języku rosyjskim.

Prawdopodobnie największy jak dotąd publiczny zestaw danych do konwersji mowy na tekst dla języka rosyjskiego:

  • Około 16 milionów wypowiedzi
  • Około 20 000 godzin
  • 2,3 TB (nieskompresowane w formacie .wav int16), 356 GB w formacie .opus
  • W tej chwili wszystkie pliki zostały przekształcone do formatu opus z wyjątkiem zestawów danych do weryfikacji.

Głównym przeznaczeniem zestawu danych jest trenowanie modeli przekształcających mowę na tekst.

Budowa zestawu danych

Rozmiar zestawu danych jest podany dla plików .wav.

Zestaw danych Wypowiedzi Godziny GB Sekundy/znaki Komentarz Adnotacja Jakość/szum
radio_v4 (*) 7 603 192 10 430 1195 5 s / 68 Przycisk opcji Wyrównaj 95% / wyraźne
public_speech (*) 1 700 060 2 709 301 6 s / 79 Przemówienia publiczne Wyrównaj 95% / wyraźne
audiobook_2 1 149 404 1511 162 5 s / 56 Książki Wyrównaj 95% / wyraźne
radio_2 651 645 1 439 154 8 s / 110 Przycisk opcji Wyrównaj 95% / wyraźne
public_youtube1120 1 410 979 1104 237 3 s / 34 YouTube Napisy 95% / ~wyraźne
public_youtube700 759 483 701 75 3 s / 43 YouTube Napisy 95% / ~wyraźne
tts_russian_addresses 1 741 838 754 81 2 s / 20 Adresy Głosy TTS 4 100% / wyraźne
asr_public_phone_calls_2 603 797 601 66 4 s / 37 Rozmowy telefoniczne ASR 70% / zaszumione
public_youtube1120_hq 369 245 291 31 3 s / 37 YouTube HQ Napisy 95% / ~wyraźne
asr_public_phone_calls_1 233 868 / 2000 211 23 3 s / 29 Rozmowy telefoniczne ASR 70% / zaszumione
radio_v4_add (*) 92 679 157 18 6 s / 80 Przycisk opcji Wyrównaj 95% / wyraźne
asr_public_stories_2 78 186 78 9 4 s / 43 Książki ASR 80% / wyraźne
asr_public_stories_1 46 142 38 4 3 s / 30 Książki ASR 80% / wyraźne
public_series_1 20 243 17 2 3 s / 38 YouTube Napisy 95% / ~wyraźne
asr_calls_2_val 12 950 7,7 2 2 s / 34 Rozmowy telefoniczne Ręczne adnotacje 99% / wyraźne
public_lecture_1 6 803 6 1 3 s / 47 Wykłady Napisy 95% / wyraźne
buriy_audiobooks_2_val 7850 4,9 1 2 s / 31 Książki Ręczne adnotacje 99% / wyraźne
public_youtube700_val 7311 4,5 1 2 s / 35 YouTube Ręczne adnotacje 99% / wyraźne

(*) Za pomocą plików txt udostępniana jest tylko próbka danych.

Metodologia adnotacji

Zestaw danych został przygotowany za pomocą otwartych źródeł. Długie zdania są dzielone na fragmenty audio za pomocą funkcji wykrywania aktywności głosowej i wyrównywania. Niektóre typy audio są automatycznie opatrywane adnotacjami i weryfikowane statystycznie/z użyciem heurystyki.

Objętości danych i częstotliwość aktualizacji

Łączny rozmiar całego zestawu danych to 350 GB. Całkowity rozmiar zestawu danych z publicznie udostępnionymi etykietami to 130 GB.

Sam zestaw danych prawdopodobnie nie będzie aktualizowany w celu zachowania zgodności wstecz. Testów porównawczych i plików wykluczeń należy szukać w oryginalnym repozytorium.

W przyszłości mogą zostać dodane nowe domeny i języki.

Normalizacja audio

Aby łatwiej i szybciej dodawać rozszerzenia w czasie wykonywania oraz przetwarzać dane, wszystkie pliki są znormalizowane w następujący sposób:

  • Przekonwertowane do formatu mono, jeśli to konieczne
  • Przekonwertowane do częstotliwości próbkowania 16 kHz, jeśli to konieczne
  • Zapisane jako 16-bitowe wartości całkowite
  • Przekonwertowane do formatu OPUS

Metodologia bazy danych na dysku

Każdy plik audio (wav, binarny) ma skrót. Skróty są używane do tworzenia hierarchii folderów w celu zapewnienia lepszego działania operacji systemu plików.

target_format = 'wav' wavb = wav.tobytes() f_hash = hashlib.sha1(wavb).hexdigest() store_path = Path(root_folder, f_hash[0], f_hash[1:3], f_hash[3:15] + '.' + target_format)
Pliki do pobrania

Zestaw danych jest dostarczany w 2 postaciach:

  • Archiwa dostępne za pośrednictwem magazynu obiektów blob platformy Azure i/lub linków bezpośrednich
  • Oryginalne pliki dostępne za pośrednictwem magazynu obiektów blob platformy Azure

Wszystko jest przechowywane pod adresem https://azureopendatastorage.blob.core.windows.net/openstt/

Struktura folderów:

└── ru_open_stt_opus <= archived folders │ │ │ ├── archives │ │ ├── asr_calls_2_val.tar.gz <= tar.gz archives with opus and wav files │ │ │ ... <= see the below table for enumeration │ │ └── tts_russian_addresses_rhvoice_4voices.tar.gz │ │ │ └── manifests │ ├── asr_calls_2_val.csv <= csv files with wav_path, text_path, duration (see notebooks) │ │ ... │ └── tts_russian_addresses_rhvoice_4voices.csv └── ru_open_stt_opus_unpacked <= a separate folder for each uploaded domain ├── public_youtube1120 │ ├── 0 <= see "On disk DB methodology" for details │ ├── 1 │ │ ├── 00 │ │ │ ... │ │ └── ff │ │ ├── *.opus <= actual files │ │ └── *.txt │ │ ... │ └── f ├── public_youtube1120_hq ├── public_youtube700_val ├── asr_calls_2_val ├── radio_2 ├── private_buriy_audiobooks_2 ├── asr_public_phone_calls_2 ├── asr_public_stories_2 ├── asr_public_stories_1 ├── public_lecture_1 ├── asr_public_phone_calls_1 ├── public_series_1 └── public_youtube700
Zestaw danych GB, wav GB, archiwum Archiwum Element źródłowy Manifest
Szkolenie
Przykłady z radia i przemówień publicznych - 11,4 opus i txt - manifest
audiobook_2 162 25,8 opus i txt Internet i wyrównanie manifest
radio_2 154 24,6 opus i txt Przycisk opcji manifest
public_youtube1120 237 19,0 opus i txt Wideo z usługi YouTube manifest
asr_public_phone_calls_2 66 9,4 opus i txt Internet i ASR manifest
public_youtube1120_hq 31 4,9 opus i txt Wideo z usługi YouTube manifest
asr_public_stories_2 9 1.4 opus i txt Internet i wyrównanie manifest
tts_russian_addresses_rhvoice_4voices 80,9 12,9 opus i txt TTS manifest
public_youtube700 75,0 12,2 opus i txt Wideo z usługi YouTube manifest
asr_public_phone_calls_1 22,7 3.2 opus i txt Internet i ASR manifest
asr_public_stories_1 4.1 0,7 opus i txt Historie publiczne manifest
public_series_1 1,9 0.3 opus i txt Serie publiczne manifest
public_lecture_1 0,7 0,1 opus i txt Internet i ręcznie manifest
Val
asr_calls_2_val 2 0,8 wav i txt Internet manifest
buriy_audiobooks_2_val 1 0,5 wav i txt Książki i ręcznie manifest
public_youtube700_val 2 0.13 wav i txt Wideo z usługi YouTube i ręcznie manifest
Instrukcje pobierania

Bezpośrednio

Zobacz tutaj — https://github.com/snakers4/open_stt#download-instructions

Poprzez instalację magazynu obiektów blob platformy Azure

Sprawdź notes zlokalizowany na karcie „Dostęp do danych”

Kontakty

W celu uzyskania pomocy lub odpowiedzi na pytanie skontaktuj się z autorem/autorami zestawu danych pod adresem aveysov@gmail.com

Licencja

Niniejsza licencja pozwala użytkownikom na dystrybucję, remiksowanie, adaptację i bazowanie na tym materiale na dowolnym nośniku lub formacie wyłącznie w celach niekomercyjnych i tylko pod warunkiem, że uznanie należy do twórcy. Obejmuje następujące elementy:
* BY – uznanie należy do twórcy
* NC – dozwolone jest tylko niekomercyjne wykorzystanie pracy

Licencja CC-BY-NC i użytek komercyjny jest możliwy po zawarciu umowy z autorami zestawu danych.

Dokumentacja / dalsza lektura

Oryginalny zestaw danych

  • https://github.com/snakers4/open_stt

Artykuły w języku angielskim

  • https://thegradient.pub/towards-an-imagenet-moment-for-speech-to-text/
  • https://thegradient.pub/a-speech-to-text-practitioners-criticisms-of-industry-and-academia/

Artykuły w języku chińskim

  • https://www.infoq.cn/article/4u58WcFCs0RdpoXev1E2

Artykuły w języku rosyjskim

  • https://habr.com/ru/post/494006/
  • https://habr.com/ru/post/474462/

Access

Available inWhen to use
Azure Notebooks

Quickly explore the dataset with Jupyter notebooks hosted on Azure or your local machine.

Select your preferred service:

Azure Notebooks

Azure Notebooks

Package: Language: Python

Helper functions / dependencies

Building libsndfile

The best efficient way to read opus files in python (the we know of) that does incur any significant overhead is to use pysoundfile (a python CFFI wrapper around libsoundfile).

When this solution was being researched the community had been waiting for a major libsoundfile release for some time.

Opus support has been implemented some time ago upstream, but it has not been properly released. Therefore we opted for a custom build + monkey patching.

At the time when you read / use this - probably there will be decent / proper builds of libsndfile.

Please replace with your faviourite tool if there is one.

Typically, you need to run this in your shell with sudo access:

apt-get update
apt-get install cmake autoconf autogen automake build-essential libasound2-dev \
libflac-dev libogg-dev libtool libvorbis-dev libopus-dev pkg-config -y

cd /usr/local/lib
git clone https://github.com/erikd/libsndfile.git
cd libsndfile
git reset --hard 49b7d61
mkdir -p build && cd build

cmake .. -DBUILD_SHARED_LIBS=ON
make && make install
cmake --build .

Helper functions / dependencies

Install the following libraries (versions do not matter much):

pandas
numpy
scipy
tqdm
soundfile
librosa

Depending on how this notebook is run, this sometimes can be as easy as (if, for example your miniconda is not installed under root):

In [ ]:
!pip install numpy
!pip install tqdm
!pip install scipy
!pip install pandas
!pip install soundfile
!pip install librosa
!pip install azure-storage-blob

Manifests are just csv files with the following columns:

  • Path to audio
  • Path to text file
  • Duration

They proved to be the most simple / helpful format of accessing data.

For ease of use all the manifests are already rerooted, i.e. all paths in them are relative and you just need to add a root folder.

In [1]:
# manifest utils
import os
import numpy as np
import pandas as pd
from tqdm import tqdm
from urllib.request import urlopen



def reroot_manifest(manifest_df,
                    source_path,
                    target_path):
    if source_path != '':
        manifest_df.wav_path = manifest_df.wav_path.apply(lambda x: x.replace(source_path,
                                                                              target_path))
        manifest_df.text_path = manifest_df.text_path.apply(lambda x: x.replace(source_path,
                                                                                target_path))
    else:
        manifest_df.wav_path = manifest_df.wav_path.apply(lambda x: os.path.join(target_path, x))
        manifest_df.text_path = manifest_df.text_path.apply(lambda x: os.path.join(target_path, x))    
    return manifest_df


def save_manifest(manifest_df,
                  path,
                  domain=False):
    if domain:
        assert list(manifest_df.columns) == ['wav_path', 'text_path', 'duration', 'domain']
    else:
        assert list(manifest_df.columns) == ['wav_path', 'text_path', 'duration']

    manifest_df.reset_index(drop=True).sort_values(by='duration',
                                                   ascending=True).to_csv(path,
                                                                          sep=',',
                                                                          header=False,
                                                                          index=False)
    return True


def read_manifest(manifest_path,
                  domain=False):
    if domain:
        return pd.read_csv(manifest_path,
                        names=['wav_path',
                               'text_path',
                               'duration',
                               'domain'])
    else:
        return pd.read_csv(manifest_path,
                        names=['wav_path',
                               'text_path',
                               'duration'])


def check_files(manifest_df,
                domain=False):
    orig_len = len(manifest_df)
    if domain:
        assert list(manifest_df.columns) == ['wav_path', 'text_path', 'duration']
    else:
        assert list(manifest_df.columns) == ['wav_path', 'text_path', 'duration', 'domain']
    wav_paths = list(manifest_df.wav_path.values)
    text_path = list(manifest_df.text_path.values)

    omitted_wavs = []
    omitted_txts = []

    for wav_path, text_path in zip(wav_paths, text_path):
        if not os.path.exists(wav_path):
            print('Dropping {}'.format(wav_path))
            omitted_wavs.append(wav_path)
        if not os.path.exists(text_path):
            print('Dropping {}'.format(text_path))
            omitted_txts.append(text_path)

    manifest_df = manifest_df[~manifest_df.wav_path.isin(omitted_wavs)]
    manifest_df = manifest_df[~manifest_df.text_path.isin(omitted_txts)]
    final_len = len(manifest_df)

    if final_len != orig_len:
        print('Removed {} lines'.format(orig_len-final_len))
    return manifest_df


def plain_merge_manifests(manifest_paths,
                          MIN_DURATION=0.1,
                          MAX_DURATION=100):

    manifest_df = pd.concat([read_manifest(_)
                             for _ in manifest_paths])
    manifest_df = check_files(manifest_df)

    manifest_df_fit = manifest_df[(manifest_df.duration>=MIN_DURATION) &
                                  (manifest_df.duration<=MAX_DURATION)]

    manifest_df_non_fit = manifest_df[(manifest_df.duration<MIN_DURATION) |
                                      (manifest_df.duration>MAX_DURATION)]

    print(f'Good hours: {manifest_df_fit.duration.sum() / 3600:.2f}')
    print(f'Bad hours: {manifest_df_non_fit.duration.sum() / 3600:.2f}')

    return manifest_df_fit


def save_txt_file(wav_path, text):
    txt_path = wav_path.replace('.wav','.txt')
    with open(txt_path, "w") as text_file:
        print(text, file=text_file)
    return txt_path


def read_txt_file(text_path):
    #with open(text_path, 'r') as file:
    response = urlopen(text_path)
    file = response.readlines()
    for i in range(len(file)):
        file[i] = file[i].decode('utf8')
    return file 

def create_manifest_from_df(df, domain=False):
    if domain:
        columns = ['wav_path', 'text_path', 'duration', 'domain']
    else:
        columns = ['wav_path', 'text_path', 'duration']
    manifest = df[columns]
    return manifest


def create_txt_files(manifest_df):
    assert 'text' in manifest_df.columns
    assert 'wav_path' in manifest_df.columns
    wav_paths, texts = list(manifest_df['wav_path'].values), list(manifest_df['text'].values)
    # not using multiprocessing for simplicity
    txt_paths = [save_txt_file(*_) for _ in tqdm(zip(wav_paths, texts), total=len(wav_paths))]
    manifest_df['text_path'] = txt_paths
    return manifest_df


def replace_encoded(text):
    text = text.lower()
    if '2' in text:
        text = list(text)
        _text = []
        for i,char in enumerate(text):
            if char=='2':
                try:
                    _text.extend([_text[-1]])
                except:
                    print(''.join(text))
            else:
                _text.extend([char])
        text = ''.join(_text)
    return text
In [2]:
# reading opus files
import os
import soundfile as sf



# Fx for soundfile read/write functions
def fx_seek(self, frames, whence=os.SEEK_SET):
    self._check_if_closed()
    position = sf._snd.sf_seek(self._file, frames, whence)
    return position


def fx_get_format_from_filename(file, mode):
    format = ''
    file = getattr(file, 'name', file)
    try:
        format = os.path.splitext(file)[-1][1:]
        format = format.decode('utf-8', 'replace')
    except Exception:
        pass
    if format == 'opus':
        return 'OGG'
    if format.upper() not in sf._formats and 'r' not in mode:
        raise TypeError("No format specified and unable to get format from "
                        "file extension: {0!r}".format(file))
    return format


#sf._snd = sf._ffi.dlopen('/usr/local/lib/libsndfile/build/libsndfile.so.1.0.29')
sf._subtypes['OPUS'] = 0x0064
sf.SoundFile.seek = fx_seek
sf._get_format_from_filename = fx_get_format_from_filename


def read(file, **kwargs):
    return sf.read(file, **kwargs)


def write(file, data, samplerate, **kwargs):
    return sf.write(file, data, samplerate, **kwargs)
In [3]:
# display utils
import gc
from IPython.display import HTML, Audio, display_html
pd.set_option('display.max_colwidth', 3000)
#Prepend_path is set to read directly from Azure. To read from local replace below string with path to the downloaded dataset files
prepend_path = 'https://azureopendatastorage.blob.core.windows.net/openstt/ru_open_stt_opus_unpacked/'


def audio_player(audio_path):
    return '<audio preload="none" controls="controls"><source src="{}" type="audio/wav"></audio>'.format(audio_path)

def display_manifest(manifest_df):
    display_df = manifest_df
    display_df['wav'] = [audio_player(prepend_path+path) for path in display_df.wav_path]
    display_df['txt'] = [read_txt_file(prepend_path+path) for path in tqdm(display_df.text_path)]
    audio_style = '<style>audio {height:44px;border:0;padding:0 20px 0px;margin:-10px -20px -20px;}</style>'
    display_df = display_df[['wav','txt', 'duration']]
    display(HTML(audio_style + display_df.to_html(escape=False)))
    del display_df
    gc.collect()

Play with a dataset

Play a sample of files

On most platforms browsers usually support native audio playback.

So we can leverage HTML5 audio players to view our data.

In [4]:
manifest_df = read_manifest(prepend_path +'/manifests/public_series_1.csv')
#manifest_df = reroot_manifest(manifest_df,
                              #source_path='',
                              #target_path='../../../../../nvme/stt/data/ru_open_stt/')
In [5]:
sample = manifest_df.sample(n=20)
display_manifest(sample)
100%|██████████| 20/20 [00:07<00:00,  2.66it/s]
wav txt duration
5963 [пожалуйста прости всё в порядке\n] 2.48
19972 [хотелось бы хотя бы разок глазком на неё посмотреть раз такое дело\n] 5.68
15555 [они с егерем на след напали до инспектора не дозвониться\n] 3.84
430 [что то случилось\n] 1.36
4090 [так давай опаздываем\n] 2.16
18590 [да саид слушаю тебя троих нашли а в полётном листе\n] 4.60
17734 [надо сначала самому серьёзным человеком стать понимаешь\n] 4.32
978 [вот что случилось\n] 1.56
13269 [да паш юль пожалуйста не делай глупостей\n] 3.48
4957 [полусладкое или сухое\n] 2.32
1913 [ищи другую машину\n] 1.80
10522 [гражданин финн не зная что я полицейский\n] 3.08
9214 [ты чего трубку не берёшь я же переживаю\n] 2.88
10014 [я не окажу сопротивления я без оружия\n] 3.00
8351 [звони партнёру пусть он напишет\n] 2.80
3818 [ну что пойдём обсудим\n] 2.12
11097 [вы простите понимаете все об этом знают\n] 3.16
2989 [какие уж разводки\n] 2.00
12229 [я получается какой то диспетчер а не напарник\n] 3.28
5348 [я же тебе сказала никакой карелии\n] 2.40

Read a file

In [ ]:
!ls ru_open_stt_opus/manifests/*.csv

A couple of simplistic examples showing how to best read wav and opus files.

Scipy is the fastest for wav, pysoundfile is the best overall for opus.

In [6]:
%matplotlib inline

import librosa
from scipy.io import wavfile
from librosa import display as ldisplay
from matplotlib import pyplot as plt

Read a wav

In [7]:
manifest_df = read_manifest(prepend_path +'manifests/asr_calls_2_val.csv')
#manifest_df = reroot_manifest(manifest_df,
                              #source_path='',
                              #target_path='../../../../../nvme/stt/data/ru_open_stt/')
In [8]:
sample = manifest_df.sample(n=5)
display_manifest(sample)
100%|██████████| 5/5 [00:01<00:00,  2.61it/s]
wav txt duration
7802 [это же позитивные новости не негативные\n] 2.01
3590 [белый цветочек\n] 1.17
10594 [какое отношение имеет ваша пенсия к моему отделению\n] 3.14
4630 [есть есть видео\n] 1.35
468 [что ещё раз\n] 0.62
In [9]:
from io import BytesIO

wav_path = sample.iloc[0].wav_path
response = urlopen(prepend_path+wav_path)
data = response.read()
sr, wav = wavfile.read(BytesIO(data))
wav.astype('float32')
absmax = np.max(np.abs(wav))
wav =  wav / absmax
In [10]:
# shortest way to plot a spectrogram
D = librosa.amplitude_to_db(np.abs(librosa.stft(wav)), ref=np.max)
plt.figure(figsize=(12, 6))
ldisplay.specshow(D, y_axis='log')
plt.colorbar(format='%+2.0f dB')
plt.title('Log-frequency power spectrogram')
# shortest way to plot an envelope
plt.figure(figsize=(12, 6))
ldisplay.waveplot(wav, sr=sr, max_points=50000.0, x_axis='time', offset=0.0, max_sr=1000, ax=None)
Out[10]:
<matplotlib.collections.PolyCollection at 0x7fdf62f7e8d0>

Read opus

In [11]:
manifest_df = read_manifest(prepend_path +'manifests/asr_public_phone_calls_2.csv')
#manifest_df = reroot_manifest(manifest_df,
                              #source_path='',
                              #target_path='../../../../../nvme/stt/data/ru_open_stt/')
In [12]:
sample = manifest_df.sample(n=5)
display_manifest(sample)
100%|██████████| 5/5 [00:02<00:00,  2.24it/s]
wav txt duration
5018 [а вы кто\n] 0.96
143473 [пьеса дружбы нету\n] 1.86
272155 [не знаю где находится\n] 2.64
334225 [ты куда звонишь то куда ты звонишь ты знаешь\n] 3.12
143789 [помощник дежурного\n] 1.86
In [13]:
opus_path = sample.iloc[0].wav_path
response = urlopen(prepend_path+opus_path)
data = response.read()
wav, sr = sf.read(BytesIO(data))
wav.astype('float32')
absmax = np.max(np.abs(wav))
wav =  wav / absmax
In [14]:
# shortest way to plot a spectrogram
D = librosa.amplitude_to_db(np.abs(librosa.stft(wav)), ref=np.max)
plt.figure(figsize=(12, 6))
ldisplay.specshow(D, y_axis='log')
plt.colorbar(format='%+2.0f dB')
plt.title('Log-frequency power spectrogram')
# shortest way to plot an envelope
plt.figure(figsize=(12, 6))
ldisplay.waveplot(wav, sr=sr, max_points=50000.0, x_axis='time', offset=0.0, max_sr=1000, ax=None)
Out[14]:
<matplotlib.collections.PolyCollection at 0x7fdf62f8ee10>
In [ ]: