모델의 예측이 어떻게 나오는지를 확인하고 나서 해야하는 작업은 어노테이터들에게 모델의 예측을 제공하여 음원의 event를 체크하도록 하는 것이다. 이 작업을 위해서는 예측 결과를 확인하고 수정할 수 있는 frontend 웹 페이지가 필요하며 label-studio를 활용하기로 했다.
https://labelstud.io/
label-studio는 open source data labeling tool로서 서버에서 구현이 가능한 frontend 사이트를 제공한다. 우리에게는 외부에서 접속이 가능한 ip와 label-studio를 구동할 docker가 있는 상황이었다.
model의 prediction은 label-studio를 구동한 서버의 local storage와 연동을 해야하며 sqlite로 만들어지는 data 또한 연동을 해주어야 한다. 이를 위해서 우선 model prediction을 label-studio에서 읽을 수 있는 json파일의 형식으로 바꿔주어야 한다.
코드는 슈퍼맨 교수님께서 정리해주셨다.
if __name__ == "__main__":
parser = argparse.ArgumentParser("sed_inference")
parser.add_argument('--path', type=str, default='/home/clay/label_studio_files/data_v4/01/',
help='directory path to the dataset')
parser.add_argument('--vocab_path', type=str, default='vocab.json',
help='directory path to the dataset')
parser.add_argument('--threshold', type=float, default=0.1,
help='sound event detection threshold value')
parser.add_argument('--batch_size', type=int, default=8,
help='data batch size')
args = parser.parse_args()
dataset = OnFlyAudio(args.path)
meta_manager = MetaCreator(args.vocab_path)
data_loader = DataLoader(dataset, batch_size=args.batch_size, collate_fn=pad_collate, pin_memory=True, num_workers=2, drop_last=False)
# load panns model
sed = SoundEventDetection(checkpoint_path=None, device='cuda')
smoother = Smoother() # 적분
pred = []
for i, batch in tqdm(enumerate(data_loader)):
audio, lens = batch
framewise_output = sed.inference(audio)
# framewise_output.shape = (batch_size, seq_len, num_tags), where seq_len is the length of the audio in 10ms step
smoothed_output = smoother(framewise_output)
pred += quantize_prediction(smoothed_output, lens, shifted_index= i * data_loader.batch_size, threshold=args.threshold)
jsonified_pred = jsonify(pred, dataset, meta_manager)
with open(f"prediction_threshold{str(args.threshold)}.json", "w") as json_file:
json.dump(jsonified_pred, json_file, ensure_ascii=False)
print(pred)
간단하게 과정을 요약하면 함께 제공 받은 metadata를 정리 + 모델의 예측 중 on, offset 부분을 label-studio에서 원하는 형태에 집어넣는 것이다.
def jsonify(event_labels, dataset, meta_manager:MetaCreator):
'''
event_labels (list of list of dict):
each item has {'data_id': int, 'label': str, 'onset': float, 'offset': float}
dataset (NeutuneSet)
'''
json_list = [] # list of dictionary. Each dictionary item is annotation/data-path to a single data sample
for piece_event in event_labels:
# piece_event는 list of dict
event_id = piece_event[0]['data_id']
#sample_path = dataset.wav_list[event_id].relative_to(dataset.path) # dataset path 안에서 wav sample의 상대 경로
sample_path = dataset.mp3_list[event_id].relative_to(dataset.path) # mp3 sample의 상대 경로
sample_path = '/data/local-files/?d=data_v4/01/' + str(sample_path)
annotations =[{'id': 1,
'result': [{ #"value"의 key는 label-studio에서 인식되는 key들이라 변경하면 안됨
"value":{"start":event['onset'],"end":event['offset'],"labels":[event['label']],"score":float(event["confidence"])},
"from_name":"label",
"to_name":"audio",
"type":"labels",
} for event in piece_event]}]
json_event = {'id':event_id, 'predictions': annotations, 'data': {'audio': sample_path, 'text': meta_manager.get_class_name_and_title(dataset.wav_list[event_id])}}
json_list.append(json_event)
return json_list
시간이 정말 오래 걸렸던 부분은 label-studio에 알려준 path와 local의 path를 연결하는 작업이었다. 이 문제는 사실 label-studio에서 제공하는 guide를 미리 보았더라면 바로 해결이 가능했는데 우리는 어노테이터들이 수정할 수 없도록 settings ui를 삭제한 상태였기 때문에 이걸 인지하는데에 꽤 길고 많은 시행착오 과정이 필요했다.
https://labelstud.io/guide/storage.html#Local-storage
컨테이너를 몇번을 팠는지 모르겠다.
sudo docker run -it -p 40001:8080 -v /home/maler/label_studio_serve/data/:/label-studio/data -v /home/maler/label_studio_serve/files/data_v4/:/label-studio/files heartexlabs/label-studio:latest
시행착오를 좀 살펴보자면 우선 local storage와 host에서 label-studio에 제공하는 storage를 똑같이 맞춰주기 위해서 local을 작업하는 docker를 다시 파는 작업을 했다. 그래도 해결이 되지 않았기 때문에 label-studio의 버젼을 맞춰주는 작업도 시도를 했다. 결국 settings를 발견하므로 해결이 되었다.