HTS-2.3.2のソースコードをのぞいてそのDNNの構造を調べてみる
ざっとみてみた結果、DNNTraining.pyのmain関数の最初に定義される以下の変数configにDNNの構造が書かれているらしい。
100行目
config = DNNDataIO.load_config(args.config)
最初にこのconfigがどう指定されるかを追ってみる。
まず、DNNDataIO.pyのload_config関数は以下のようなもの。
245行目
def load_config(config_file, verbose=True): config_parser = ConfigParser.SafeConfigParser() config_parser.read(config_file) config = {} for section in config_parser.sections(): for option in config_parser.options(section): config[option] = yaml.safe_load(config_parser.get(section, option)) if verbose: print('Configuration Parameters[%d]' % len(config)) print(' %-25s s' % ('Parameter', 'Value')) for key in sorted(config.keys()): print(' %-25s s' % (key, str(config[key]))) print() config['num_io_units'] = (config['num_input_units'], config['num_output_units']) return config
引数として指定されたconfigureファイルのsection中のoptionを辞書オブジェクトとして返す関数であることがわかった。
冒頭の話に戻ると、DNNTraining.pyでは引数としてargs.configを指定していたのであった。args.configとは何か。再びDNNTraining.pyをみてみる。
60行目
parser = argparse.ArgumentParser()
65行目
parser.add_argument('-C', metavar='cf', dest='config', type=str, required=True, help='set config file to cf')
73行目
args = parser.parse_args()
DNNTraining.pyを実行する際指定するオプション-Cの値であることがわかった。
DNNTraining.pyはどのように実行され、オプション-Cには何が入るのであろうか。Training.plには以下の記述がある。
243行目
$cfg{'trj'} = "$prjdir/configs/ver${ver}/trj_dnn.cnf";
269行目
foreach $dir ( 'models', 'stats', 'edfiles', 'trees', 'gv', 'mspf', 'dnn', 'voices', 'gen', 'proto', 'configs' ) { mkdir "$prjdir/$dir", 0755; mkdir "$prjdir/$dir/ver${ver}", 0755; }
1974行目
open( CONF, ">$cfg{'trj'}" ) || die "Cannot open $!"; print CONF "[Architecture]\n"; print CONF "num_input_units: $nin\n"; print CONF "num_hidden_units: [$nhid]\n"; print CONF "num_output_units: $nout\n"; print CONF "hidden_activation: \"$activations[$activation]\"\n"; print CONF "output_activation: \"$activations[0]\"\n"; print CONF "\n[Strategy]\n"; print CONF "optimizer: \"$optimizers[$optimizer]\"\n"; print CONF "learning_rate: $trjLearnRate\n"; print CONF "gv_weight: $dnnGVWeight\n"; print CONF "keep_prob: $keepProb\n"; print CONF "queue_size: $queueSize\n"; print CONF "batch_size: 1\n"; print CONF "num_epochs: $nTrjEpoch\n"; print CONF "num_threads: $nThread\n"; print CONF "random_seed: $randomSeed\n"; print CONF "frame_by_frame: 0\n"; print CONF "adaptation: 0\n"; print CONF "\n[Output]\n"; print CONF "num_models_to_keep: $nKeep\n"; print CONF "log_interval: $logInterval\n"; print CONF "save_interval: $saveInterval\n"; print CONF "\n[Others]\n"; print CONF "all_spkrs: [\"$spkr\"]\n"; print CONF "num_feature_dimensions: [";
930行目
if ($useDNN) { mkdir "$dnnmodelsdir{'trj'}", 0755; shell("cp $dnnmodels/model.ckpt.* $dnnmodelsdir{'trj'}"); shell("$PYTHON $datdir/scripts/DNNTraining.py -C $cfg{'trj'} -S $scp{'tdn'} -H $dnnmodelsdir{'trj'} -z $datdir/stats -w $windir"); }
プログラム実行時に新しくディレクトリを作り、そこにconfigureファイルを作って、それをオプションとして指定してPythonを動かしていた。
隠れユニットの数やoptimizerの種類などのDNNの構造が変数によって指定されているが、これら変数はConfig.pm.inで定義されているもので、その中で以下のように記述されている。
$useDNN = @USEDNN@; # train a deep neural network after HMM training $nHiddenUnits = '@NHIDDENUNITS@'; # number of hidden units in each layer $activation = @ACTIVATION@; # activation function for hidden units (0 -> Linear, 1 -> Sigmoid, 2 -> Tanh, 3 -> ReLU) $optimizer = @OPTIMIZER@; # optimizer (0 -> SGD, 1 -> Momentum, 2 -> AdaGrad, 3-> AdaDelta, 4 -> Adam, 5 -> RMSprop) $learnRate = @LEARNRATE@; # learning rate $trjLearnRate = @TRJLEARNRATE@; # learning rate for trajectory training $dnnGVWeight = @DNNGVWEIGHT@; # weight for GV $keepProb = @KEEPPROB@; # probability for not randomly setting activities to zero $queueSize = @QUEUESIZE@; # queue size $batchSize = @BATCHSIZE@; # mini-batch size $nEpoch = @NEPOCH@; # number of epochs $nTrjEpoch = @NTRJEPOCH@; # number of epochs for trajectory training $nThread = @NTHREAD@; # number of threads $randomSeed = @RANDOMSEED@; # random seed used for initialization $nKeep = 5; # number of models to keep $logInterval = 100; # output training log at regular steps $saveInterval = 10000; # save model at regular steps
この@で囲まれた表記がよく分からなかった。何らかの方法でこの@で囲まれた変数に値を入力するらしい。例えば、活性化関数に関してはコメントによると
という具合に指定されるらしい。
結局configがどのように指定されるかは分からず仕舞いであった。
何らかの方法で指定されたconfigを参照してDNNTraining.pyにしたがってモデルが学習されるわけだが、詳しいことは別ファイルとしてDNNDefine.pyに記述されている。調べてわかったことは以下の通り。
- 選択できる活性化関数は恒等関数、シグモイド関数、ハイパボリックタンジェント、ReLU関数の4種類。
- 選択できるoptimizerはGradientDescentOptimizer、MomentumOptimizer、AdagradOptimizer、AdadeltaOptimizer、AdamOptimizer、RMSPropOptimizerの6種類。
- 隠れ層ではmodeによって活性化関数に渡す式が変わる。modeが"SD"の時は簡単な式(Wx+b)だが、modeが"SAT"または"ADAPT"の時は重みWが2種類あり、auxially_inputという謎のinputが考慮されて複雑になる。modeを指定する条件は不明。
- 出力層の活性化関数に渡す式は一様にWx+bであった。
- コスト関数もcostとtrajectory_costの2種類用意されている。costの方は以下のように記述されている。
def cost(predicted_outputs, observed_outputs, variances): gconst = tf.cast(np.log(2.0 * np.pi), tf.float32) covdet = tf.reduce_mean(tf.log(variances)) mahala = tf.reduce_mean(tf.divide( tf.square(observed_outputs - predicted_outputs), variances)) cost = 0.5 * (gconst + covdet + mahala) return cost, predicted_outputs
式で表すと以下のようになる。
trajectory_costの方はこれより複雑である。どちらのコスト関数が選択されるかはconfigで指定されるframe_by_frameによって決まるが、これが何を表すかは不明。