---
# System prepended metadata

title: Multitask learning
tags: [kaldi]

---

# 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`
