# Multitask learning Multitask learning 目前講解最詳細的就是[ Josh Meyer](http://jrmeyer.github.io/asr/2020/03/21/overview-mtl-in-asr.html)的blog 以及他的博士論文(附在他的blog內文中),但是Josh通常用的是他簡化過得版本,想要進階版的script比較建議參照[Pegah Ghahremani 的github](https://github.com/pegahgh/kaldi/blob/multilingual-modified-config/egs/babel_multilang/s5/local/nnet3/run_tdnn_multilingual.sh)。 麻煩的是Kaldi團隊還沒有commit很多關於multitask 的code,尤其chain的部份會有點麻煩[1](https://groups.google.com/g/kaldi-help/c/y6kI3bWISPM)[2](https://groups.google.com/g/kaldi-help/c/cu53VzWD_-8),因此目前有的multitask的版本只有[nnet3](https://github.com/pegahgh/kaldi/blob/multilingual-modified-config/egs/babel_multilang/s5/local/nnet3/run_tdnn_multilingual.sh)的, 程式源自於某一篇進行transfer learning比較的論文[3](https://ieeexplore.ieee.org/document/8268947)。 multitask learning主要的code如下: ``` if [ $stage -le 9 ]; then echo "$0: Generates separate egs dir per language for multilingual training." # sourcing the "vars" below sets #model_left_context=(something) #model_right_context=(something) #num_hidden_layers=(something) . $dir/configs/vars || exit 1; ivec="${multi_ivector_dirs[@]}" if $use_ivector; then ivector_opts=(--online-multi-ivector-dirs "$ivec") fi local/nnet3/prepare_multilingual_egs.sh --cmd "$decode_cmd" \ "${ivector_opts[@]}" \ --cmvn-opts "--norm-means=false --norm-vars=false" \ --left-context $model_left_context --right-context $model_right_context \ $num_langs ${multi_data_dirs[@]} ${multi_ali_dirs[@]} ${multi_egs_dirs[@]} || exit 1; fi ``` ``` if [ $stage -le 10 ] && [ ! -z $megs_dir ]; then echo "$0: Generate multilingual egs dir using " echo "separate egs dirs for multilingual training." if [ ! -z "$lang2weight" ]; then egs_opts="--lang2weight '$lang2weight'" fi common_egs_dir="${multi_egs_dirs[@]} $megs_dir" steps/nnet3/multilingual/combine_egs.sh $egs_opts \ --cmd "$decode_cmd" \ $num_langs ${common_egs_dir[@]} || exit 1; fi ``` kaldi nnet3 nodel通常會使用local/nnet3/get_egs.sh來生成訓練所需的input/output, 通常會另外開一個資料夾$dir/egs來存這些資料,有關於nnet3的資料準備方式請參照[楊超](http://placebokkk.github.io/)和[Josh meyer](http://jrmeyer.github.io/asr/2016/12/15/DNN-AM-Kaldi.html)的文章 在訓練完模型後還得調整一下model的prior ``` if [ $stage -le 12 ]; then for lang_index in `seq 0 $[$num_langs-1]`;do lang_dir=$dir/${lang_list[$lang_index]} mkdir -p $lang_dir echo "$0: rename output name for each lang to 'output' and " echo "add transition model." nnet3-copy --edits="rename-node old-name=output-$lang_index new-name=output" \ $dir/final.raw - | \ nnet3-am-init ${multi_ali_dirs[$lang_index]}/final.mdl - \ $lang_dir/final.mdl || exit 1; cp $dir/cmvn_opts $lang_dir/cmvn_opts || exit 1; echo "$0: compute average posterior and readjust priors for language ${lang_list[$lang_index]}." steps/nnet3/adjust_priors.sh --cmd "$decode_cmd" \ --use-gpu true \ --iter final --use-raw-nnet false --use-gpu true \ $lang_dir ${multi_egs_dirs[$lang_index]} || exit 1; done fi ``` Task2Weight的應用 <以下皆是我的腦補,如有誤請務必告知> --- 因為pegah設計的multitask似乎沒有完整的document可以follow,我根據[Josh Meyer的提問](https://groups.google.com/forum/#!msg/kaldi-help/guj3gIe7rCg/ZkVIl15kBwAJ) 來猜出task2weightk的參數的實際作用,是在training的時候scale output的posterior。 舉例來說: 原本nnet3的egs.*.ark 內容的尾巴會像這樣 `<NnetIo> output <I1V> 8 <I1> 0 0 0 <I1> 0 1 0 <I1> 0 2 0 <I1> 0 3 0 <I1> 0 4 0 <I1> 0 5 0 <I1> 0 6 0 <I1> 0 7 0 rows=8 dim=856 [ 524 1 ] dim=856 [ 524 1 ] dim=856 [ 524 1 ] dim=856 [ 178 1 ] dim=856 [ 533 1 ] dim=856 [ 480 1 ] dim=856 [ 480 1 ] dim=856 [ 240 1 ]` 在dim=<num> [<num> 1] 裡面,dim指得是output acoustic model output的dimension (總共decision tree statistics的個數),接著中括弧裡面的<num>指的是第幾個tree statistics,緊隨著的那個1指的是posterior的大小,跟[Josh Meyer提問](https://groups.google.com/forum/#!msg/kaldi-help/guj3gIe7rCg/ZkVIl15kBwAJ) 講得一樣預設會是[0, 0, 0, 1, 0, 0 ],可以靠著weight scaling調成[0, 0, 0, 0.5, 0, 0 ],如此就能將multi-task的auxilary task降低它的貢獻度了 ### kaldi中的實踐方法 ![](https://i.imgur.com/v2BPCYX.png) 在steps/libs/nnet3/train/frame_level_objf/common.py裡面task2weight的參數會被傳到multitask_egs_opts裡面並且當成參數傳到nnet3-copy-egs。因此就能寫出scaling 過後的output posterior。 我們再來看nnet3-copy-egs.cc的檔案 ![](https://i.imgur.com/kVMhBz0.png) 檔案裡面會使用ScaleSupervisionWeight來scale egs。 ``` void ScaleSupervisionWeight(BaseFloat weight, NnetExample *eg) { if (weight == 1.0) return; bool found_output = false; for (std::vector<NnetIo>::iterator it = eg->io.begin(); it != eg->io.end(); ++it) { if (it->name == "output") { it->features.Scale(weight); found_output = true; } } if (!found_output) KALDI_ERR << "No supervision with name 'output'" << "exists in eg."; } ``` ScaleSupervisionWeight裡面就會在egs.*.ark裡面找到output這個tag,並且改寫他的output posterior。 查看egs.*.ark裡面資料的指令 --- `nnet3-copy-egs ark:egs.1.ark ark,t:- | head` references --- - [An Overview of Multi-Task Learning in Speech Recognition](http://jrmeyer.github.io/asr/2020/03/21/overview-mtl-in-asr.html) - [Pegah Ghahremani 的github](https://github.com/pegahgh/kaldi/blob/multilingual-modified-config/egs/babel_multilang/s5/local/nnet3/run_tdnn_multilingual.sh) [1](https://groups.google.com/g/kaldi-help/c/y6kI3bWISPM) [2](https://groups.google.com/g/kaldi-help/c/cu53VzWD_-8) [3](https://ieeexplore.ieee.org/document/8268947) ###### tags: `kaldi`