# 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中的實踐方法

在steps/libs/nnet3/train/frame_level_objf/common.py裡面task2weight的參數會被傳到multitask_egs_opts裡面並且當成參數傳到nnet3-copy-egs。因此就能寫出scaling 過後的output posterior。 我們再來看nnet3-copy-egs.cc的檔案

檔案裡面會使用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`