위의 예시와 같이 pyannote에서는 database라는 객체를 사용한다. 사실 뜯어보면 복잡한 객체는 아닌데, 처음 마주하면 낯설게 느껴져서 공식 readme 파일을 한국어로 해석하며 살펴봤다.
🔗출처 링크 : https://github.com/pyannote/pyannote-database
⚠️ 학부생이 간단하게 번역한 내용이므로 오역이 있을 수도 있습니다.
➕ 화자 분리에 커스텀 데이터 적용을 위해 번역했기 때문에, 그 외의 task들은 번역이 덜 되어있습니다😅
Definitions
pyanoote.database
용어에서, “resource”는 이미지, 오디오, 비디오 파일 또는 웹페이지와 같은 모든 멀티미디어 객체가 될 수 있다.- 가장 간단한 형태에서, 이는 entity를 식별하는
uri
키 (유니크한 resource 식별자)로pyannote.database.ProtocolFile
인스턴스로 모델링된다. ProtocolFile
에 키들을 추가함으로써 Metadata는 resource와 연결될 수 있다.- 예를 들어, 이미지 resource에다가 해당 이미지가 치와와인지 머핀인지를 설명하는 label 키를 추가할 수 있다.
- database는 같은 속성을 가진 resource들의 집합이다. (ex : 오디오 파일들의 모음)
- 이는
pyannote.database.Database
인스턴스로 모델링된다.
- 이는
- 실험 프로토콜 (
pyannote.database.Protocol
)은 보통 다음의 3개의 부분집합들을 정의한다.- train subset
- development subset
- test subset
Configuration file
- 실험 프로토콜은 YAML config 파일들을 통해 정의된다.
Protocols:
MyDatabase:
Protocol:
MyProtocol:
train:
uri: /path/to/train.lst
development:
uri: /path/to/development.lst
test:
uri: /path/to/test.lst
/path/to/train.lst
는 train subset의 파일들의 URI(unique reosource identifier) 리스트를 포함한다.
# /path/to/train.lst
filename1
filename2
- 버전 5.0부터 config 파일들은
registry
에 다음과 같이 로드되어야 한다.
from pyannote.database import registry
registry.load_database("/path/to/database.yml")
registry.load_database
는 선택적인mode
키워드 argument를 가지고 있다.- 다른 프로토콜에서 이미 사용됐던 이름을 가진 프로토콜을 로딩할 때 사용.
LoadingMode.OVERRIDE
: 기존의 프로토콜을 새 프로토콜로 덮어씀LoadingMode.KEEP
: 기존 프로토콜을 유지LoadingMode.ERROR
: 충돌이 발생했을 때RuntimeException
을 발생시킴.
- 4.x 버전과의 backward 호환성을 위해,
pyannote.database
를 불러올 다음의 파일들이 순서대로 자동으로 로드된다.~/.pyannote/database.yml
- 현재 작업중인 디렉토리의
database.yml
PYANNOTE_DATABASE_CONFIG
환경변수에 있는;
로 구분된 경로들의 리스트.- ex :
/absolute/path.yml;relative/path.yml
- ex :
- 한번 registry를 로드하면, 파이썬에서 프로토콜은 다음과 같이 사용 가능하다.
from pyannote.database import registry
registry.load_database("/path/to/database.yml")
protocol = registry.get_protocol('MyDatabase.Protocol.MyProtocol')
for resource in protocol.train():
print(resource["uri"])
filename1
filename2
- config 파일에 정의된 경로들은 절대 경로여도 되고, 상대 경로여도 된다.
- 예를 들어, 다음과 같은 파일 구조일 때
.
├── database.yml
└── lists
└── train.lst
database.yml
의 내용은 아래와 같을 것이다.
Protocols:
MyDatabase:
Protocol:
MyProtocol:
train:
uri: lists/train.lst
Data loaders
- 앞서 설명한
MyDatabase.Protocol.MyProtocol
프로토콜은 하나의uri
키를 갖고있는 resource들의 리스트를 순회하는 것만을 원할 때 그리 유용하지 않다. - Metadata는 각각의 resource에 아래와 같은 syntax를 지키며 추가될 수 있다.
Protocols:
MyDatabase:
Protocol:
MyProtocol:
train:
uri: lists/train.lst
speaker: rttms/train.rttm
transcription: ctms/{uri}.ctm
- 디렉토리 구조는 다음과 같다.
.
├── database.yml
├── lists
| └── train.lst
├── rttms
| └── train.rttm
└── ctms
├── filename1.ctm
└── filename2.ctm
- 이제, resource들은
speaker
와transcription
키들을 모두 갖고있다.
from pyannote.database import registry
protocol = registry.get_protocol('MyDatabase.Protocol.MyProtocol')
for resource in protocol.train():
assert "speaker" in resource
assert isinstance(resource["speaker"], pyannote.core.Annotation)
assert "transcription" in resource
assert isinstance(resource["transcription"], spacy.tokens.Doc)
- 이 작업을 통해 metadata 파일 suffix를 기반으로 Dataloader들이 자동으로 선택될 수 있다.
.rttm
파일 형태를 지닌speaker
entry를 위해서는pyannote.databse.loader.RTTMLoader
ctm
파일 형태를 지닌transcription
를 위해서는pyannote.database.loader.CTMLoader
- 이는
speaker
와transcription
키들을 채우기 위해 사용된다.
# instantiate loader registered with `.rttm` suffix
speaker = RTTMLoader('rttms/train.rttm')
# entries with {placeholders} serve as path templates
transcription_template = 'ctms/{uri}.ctm'
for resource in protocol.train():
# unique resource identifier
uri = resource['uri']
# only select parts of `rttms/train.rttm` that are relevant to current resource,
# convert it into a convenient data structure (here pyannote.core.Annotation),
# and assign it to `'speaker'` resource key
resource['speaker'] = speaker[uri]
# replace placeholders in `transcription` path template
ctm = transcription_template.format(uri=uri)
# instantiate loader registered with `.ctm` suffix
transcription = CTMLoader(ctm)
# only select parts of the `ctms/{uri}.ctm` that are relevant to current resource
# (here, most likely the whole file), convert it into a convenient data structure
# (here spacy.tokens.Doc), and assign it to `'transcription'` resource key
resource['transcription'] = transcription[uri]
pyannote.databse
는 제한된 파일 포맷에서 내장 data loader들을 제공한다.
Preprocessors
- 프로토콜 부분 집합을 순회할 때(
for resouce in protocol.train()
코드를 사용할 때), resource들은pyannote.database.ProtocolFile
의 인스턴스들로 제공된다. 이 때, resource들은 value들이 느리게 계산되는dict
인스턴스들로 기본적으로 제공된다. - 예를 들어, 위의 코드에서
resource['speaker']
를 통해 반환된 값은 처음 접근되었을 때만 계산되고, 그 다음 요청들부터는 캐싱된다. - 유사하게, resource들은
preprocessors
옵션들을 통하여 실시간으로 증강되거나 수정될 수 있다. 아래의 예시를 보면, 간단하게uri
string의 길이를 반환하는dummy
키가 추가되었다.
def compute_dummy(resource: ProtocolFile):
print(f"Computing 'dummy' key")
return len(resource["uri"])
from pyannote.database import registry
protocol = registry.get_protocol('Etape.SpeakerDiarization.TV',
preprocessors={"dummy": compute_dummy})
resource = next(protocol.train())
resource["dummy"]
Computing 'dummy' key
FileFinder
FileFinder
는 preprocessor의 특별한 케이스로,uri
로 연결된 미디어 파일을 자동으로 찾아준다.- 오디오 파일들이 다음과 같은 경로에 있다고 하자.
.
└── /path/to
└── audio
├── filename1.wav
├── filename2.mp3
├── filename3.wav
├── filename4.wav
└── filename5.mp3
FileFinder
전처리기는database.yml
에 추가되는Database:
부분에 영향을 받는다.
Databases:
MyDatabase:
- /path/to/audio/{uri}.wav
- /path/to/audio/{uri}.mp3
Protocols:
MyDatabase:
Protocol:
MyProtocol:
train:
uri: lists/train.lst
pathlib.Path.glob
이 지원하는 대부분의 패턴이 지원되지만, 가능한**
의 사용은 피하는 게 좋다. 왜냐하면 모든 경로들은database.yml
에 상대적일 수도 있고, 이는 곧 파일의 위치를 찾는데 런타임이 소비될 수 있기 때문이다.
from pyannote.database import registry
from pyannote.database import FileFinder
protocol = registry.get_protocol('MyDatabase.SpeakerDiarization.MyProtocol',
preprocessors={"audio": FileFinder()})
for resource in protocol.train():
print(resource["audio"])
/path/to/audio/filename1.wav
/path/to/audio/filename2.mp3
Tasks
Collections
- 파일의 raw collection은 Collection 작업을 사용하여 정의될 수 있다.
# ~/database.yml
Protocols:
MyDatabase:
Collection:
MyCollection:
uri: /path/to/collection.lst
any_other_key: ... # see custom loader documentation
path/to/collection.lst
는 collection의 파일들의 identifier들의 리스트를 포함하고 있다.
# /path/to/collection.lst
filename1
filename2
filename3
- Python에서는 다음과 같이 사용될 수 있다.
from pyannote.database import registry
collection = registry.get_protocol('MyDatabase.Collection.MyCollection')
for file in collection.files():
print(file["uri"])
filename1
filename2
filename3
Segmentation
- segmentation 프로토콜은
Segmentation
테스크를 정의하며 사용될 수 있다.
Protocols:
MyDatabase:
Segmentation:
MyProtocol:
classes:
- speech
- noise
- music
train:
uri: /path/to/train.lst
annotation: /path/to/train.rttm
annotated: /path/to/train.uem
path/to/train.lst
는 training set의 파일들의 identifier들 리시트이다.
# /path/to/train.lst
filename1
filename2
path/to/train.rttm
은 RTTM 포맷을 사용하는 reference segmentation을 포함한다.
# /path/to/reference.rttm
SPEAKER filename1 1 3.168 0.800 <NA> <NA> speech <NA> <NA>
SPEAKER filename1 1 5.463 0.640 <NA> <NA> speech <NA> <NA>
SPEAKER filename1 1 5.496 0.574 <NA> <NA> music <NA> <NA>
SPEAKER filename1 1 10.454 0.499 <NA> <NA> music <NA> <NA>
SPEAKER filename2 1 2.977 0.391 <NA> <NA> noise <NA> <NA>
SPEAKER filename2 1 18.705 0.964 <NA> <NA> noise <NA> <NA>
SPEAKER filename2 1 22.269 0.457 <NA> <NA> speech <NA> <NA>
SPEAKER filename2 1 28.474 1.526 <NA> <NA> speech <NA> <NA>
path/to/train.uem
은 UEM 포맷을 사용하는 anntated 구간을 묘사한다.
filename1 NA 0.000 30.000
filename2 NA 0.000 30.000
filename2 NA 40.000 70.000
- 전체 파일들을 커버하더라도
annotated
키를 제공하는 것이 추천된다.annotated
으로 제공되지 않은annotation
부분은 제거될 것이다. 이는pyannote.metrics
를 통하여 annotate되지 않은 구간을 평가를 통하여 삭제하고,pyannote.audio
가 빈 구간을 잘못 고려하는 것을 방지하기 위한 작업이다. - Python 파일로는 아래와 같다.
from pyannote.database import registry
protocol = registry.get_protocol('MyDatabase.Segmentation.MyProtocol')
for file in protocol.train():
print(file["uri"])
assert "annotation" in file
assert "annotated" in file
filename1
filename2
Speaker diarization
- 프로토콜 정의는 아래와 같다.
Protocols:
MyDatabase: # 데이터베이스이름
SpeakerDiarization:
MyProtocol: # 세부 프로토콜?
scope: file
train:
uri: /path/to/train.lst
annotation: /path/to/train.rttm
annotated: /path/to/train.uem
Protocols:
AMI-SDM:
SpeakerDiarization:
only_words:
train:
uri: ../lists/train.meetings.txt
annotation: ../only_words/rttms/train/{uri}.rttm
annotated: ../uems/train/{uri}.uem
development:
uri: ../lists/dev.meetings.txt
annotation: ../only_words/rttms/dev/{uri}.rttm
annotated: ../uems/dev/{uri}.uem
test:
uri: ../lists/test.meetings.txt
annotation: ../only_words/rttms/test/{uri}.rttm
annotated: ../uems/test/{uri}.uem
/path/to/train.lst
: training set의 파일 식별자들 리스트.
# /path/to/train.lst
filename1
filename2
path/to/train.rttm
: 발화자 정보
# /path/to/reference.rttm
SPEAKER filename1 1 3.168 0.800 <NA> <NA> speaker_A <NA> <NA>
SPEAKER filename1 1 5.463 0.640 <NA> <NA> speaker_A <NA> <NA>
SPEAKER filename1 1 5.496 0.574 <NA> <NA> speaker_B <NA> <NA>
SPEAKER filename1 1 10.454 0.499 <NA> <NA> speaker_B <NA> <NA>
SPEAKER filename2 1 2.977 0.391 <NA> <NA> speaker_C <NA> <NA>
SPEAKER filename2 1 18.705 0.964 <NA> <NA> speaker_C <NA> <NA>
SPEAKER filename2 1 22.269 0.457 <NA> <NA> speaker_A <NA> <NA>
SPEAKER filename2 1 28.474 1.526 <NA> <NA> speaker_A <NA> <NA>
/path/to/train.uem
: annotate 구간
filename1 NA 0.000 30.000
filename2 NA 0.000 30.000
filename2 NA 40.000 70.000
- segmentation과 마찬가지로
annotated
키를 사용하는 것을 추천. - Python 코드는 아래와 같다.
from pyannote.database import registry
protocol = registry.get_protocol('MyDatabase.SpeakerDiarization.MyProtocol')
for file in protocol.train():
print(file["uri"])
assert "annotation" in file
assert "annotated" in file
filename1
filename2
scope
파라미터는 speaker 라벨의 범위를 나타낸다.file
: 파일이 자신만의 speaker label set을 가지고 있음을 나타냄. 이 때,filename1
의speaker1
이filename2
의speaker1
과 같은 발화자라는 보장은 없음.database
: 데이터베이스의 모든 파일들이 동일한 speaker label set을 공유한다는 것을 나타냄.database1/filename1
의speaker1
은database1/filename2
의speaker1
과 동일한 발화자이다.global
: 모든 데이터베이스에서 speaker label set이 동일하다고 나타냄.database1
의speaker1
은database2
의speaker1
과 동일한 발화자이다.
scope
는file['scope']
로 접근 가능.
Speaker verification
- 프로토콜 정의는 아래와 같다.
Protocols:
MyDatabase:
SpeakerVerification:
MyProtocol:
train:
uri: /path/to/train.lst
duration: /path/to/duration.map
trial: /path/to/trial.txt
/path/to/train.lst
# /path/to/collection.lst
filename1
filename2
filename3
...
/path/to/duration.map
filename1 30.000
filename2 30.000
...
/path/to/trial.txt
1 filename1 filename2
0 filename1 filename3
...
1
은 target trial을 의미하고0
은 non-target trial을 의미한다. 예시에서 동일한 발화자가filename1
파일과filename2
파일에서 발화했고,filename1
과filename2
는 서로 다른 두 발화자가 발화했던 파일임을 의미한다.- Python 코드는 아래와 같다.
from pyannote.database import registry
protocol = registry.get_protocol('MyDatabase.SpeakerVerification.MyProtocol')
for trial in protocol.train_trial():
print(f"{trial['reference']} {trial['file1']['uri']} {trial['file2']['uri']}")
1 filename1 filename2
0 filename1 filename3
Meta-protocols and requirments
pyannote.database
는 다른 데이터베이스들로부터 온 여러 프로토콜들을 하나로 묶을 수 있다.- 이는 특별한
X
데이터베이스와 config 파일을 통하여 정의된다.
Requirements:
- /path/to/my/database/database.yml # defines MyDatabase protocols
- /path/to/my/other/database/database.yml # defines MyOtherDatabase protocols
Protocols:
X:
Protocol:
MyMetaProtocol:
train:
MyDatabase.Protocol.MyProtocol: [train, development]
MyOtherDatabase.Protocol.MyOtherProtocol: [train, ]
development:
MyDatabase.Protocol.MyProtocol: [test, ]
MyOtherDatabase.Protocol.MyOtherProtocol: [development, ]
test:
MyOtherDatabase.Protocol.MyOtherProtocol: [test, ]
- 새로운
X.Protocol.MyMetaProtocol
은 메타train
부분집합을 만들기 위해MyOtherDatabase.Protocol.MyOtherProtocol
의 훈련 부분집합과MyDatabase.Protocol.MyProtocol
의 train, develpment 부분집합들을 조합했다. - 이 새로운 메타 프로토콜은 다른 여타 프로토콜들과 동일하게 사용될 수 있다.
from pyannote.database import registry
protocol = registry.get_protocol('X.Protocol.MyMetaProtocol')
for resource in protocol.train():
pass
API
Databases and tasks
- 데이터베이스에 관한 모든 것은
registry
에 저장되어 있다.
from pyannote.database import registry
- 모든 데이터베이스는 아래와 같이 인스턴스화될 수 있다.
database = registry.get_database("MyDatabase")
- 몇몇 데이터베이스는 여러 task 수행을 위해 사용될 수 있다. 또한, 아래의 메서드를 활용하면 task의 리스트를 가져올 수 있다.
database.get_tasks()
["SpeakerDiarization"]
Custom data loaders
- 기본적으로
pyannote.database
는 rttm, uem, ctm 포맷의 파일들을 위한 내장 data loader들을 제공한다. 그러나, 다른 포맷의 파일을 올리고 싶다면 custom data loader를 활용할 수 있다. - Defining custom data loaders
ext1
과ext2
라는 포맷을 가진 파일들을 위한 2개의 custom data loader들을 각각 정의한다고 가정하자.your_package
라고 불리는 Python package의 코드는 다음과 같을 것이다.# ~~~~~~~~~~~~~~~~ YourPackage/your_package/loader.py ~~~~~~~~~~~~~~~~ from pyannote.database import ProtocolFile from pathlib import Path class Ext1Loader: def __init__(self, ext1: Path): print(f'Initializing Ext1Loader with {ext1}') # your code should obviously do something smarter. # see pyannote.database.loader.RTTMLoader for an example. self.ext1 = ext1 def __call__(self, current_file: ProtocolFile) -> Text: uri = current_file["uri"] print(f'Processing {uri} with Ext1Loader') # your code should obviously do something smarter. # see pyannote.database.loader.RTTMLoader for an example. return f'{uri}.ext1' class Ext2Loader: def __init__(self, ext2: Path): print(f'Initializing Ext2Loader with {ext2}') # your code should obviously do something smarter. # see pyannote.database.loader.RTTMLoader for an example. self.ext2 = ext2 def __call__(self, current_file: ProtocolFile) -> Text: uri = current_file["uri"] print(f'Processing {uri} with Ext2Loader') # your code should obviously do something smarter. # see pyannote.database.loader.RTTMLoader for an example. return f'{uri}.ext2' # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Registering custom data laoders
- Testing custom data loaders
Protocols
- 실험 프로토콜은
SpeakerDiarizationProtocol
에서 상속받는 클래스를 생성함으로써 프로그램틱하게 정의되고,train_iter
,development_iter
,test_iter
메서드들을 통하여 적용된다.
class MyProtocol(Protocol):
def train_iter(self) -> Iterator[Dict]:
yield {"uri": "filename1", "any_other_key": "..."}
yield {"uri": "filename2", "any_other_key": "..."}
{subset}_iter
는 보통 파일 이름에 해당하는 유니크한 파일 식별기, 즉 “uri” 키를 제공한다.
protocol = MyProtocol()
for file in protocol.train():
print(file["uri"])
filename1
filename2
- collections
files_iter
메서드를 통하여 실행된다.class MyCollection(CollectionProtocol): def files_iter(self) -> Iterator[Dict]: yield {"uri": "filename1", "any_other_key": "..."} yield {"uri": "filename2", "any_other_key": "..."} yield {"uri": "filename3", "any_other_key": "..."}
- 파일 이름을 반환.
collection = MyCollection() for file in collection.files(): print(file["uri"]) filename1 filename2 filename3
- Speaker diarication
uri
키 : 파일 식별자. 보통 파일 이름이다.annotation
키 : train과 development 세트에서 필수적으로,pyannote.core.Annotation
인스턴스로서 speaker diarication reference를 제공.annotated
: 파일의 어떤 부분이 annotate되었는지를 묘사하는pyannote.core.Timeline
인스턴스. annotate되지 않은 부분을 제거될 것이다.protocol = MySpeakerDiarizationProtocol() for file in protocol.train(): print(file["uri"]) filename1 filename2
class MySpeakerDiarizationProtocol(SpeakerDiarizationProtocol): def train_iter(self) -> Iterator[Dict]: yield {"uri": "filename1", "annotation": Annotation(...), "annotated": Timeline(...)} yield {"uri": "filename2", "annotation": Annotation(...), "annotated": Timeline(...)}
- Speaker verification