hang: nnetbin/sat-nnet-train-frmshuff.cc注解4

主体训练过程

  1. 循环条件
  • Done()函数:data_end_ - data_begin_ < conf_.minibatch_size,即不够填充一个mini_batch数据
  • Next()函数:data_begin_ += conf_.minibatch_size
  • Value()函数:
    a. 判断有一个minibatch的数据( data_end_ - data_begin_ > minibatch_size),其实该条和Done是重复的.
    b. 读取一个minibatch数据,注意读的是引用.
  1. 前向传递(Propagate_SpkCode函数)
  • 前向传递根据不同的component的处理不同
    std::vector<Component*> components_;
    components_[0]为Speakercode层;components_[1]~components_[size-1]为常规结构
  • GetSpkInfo(in),in为每一帧所对应说话人的id,current_code为一个临时CuMatrix变量,大小为in.size*code_length_;speaker_code_中存放的是所有人的code;利用CopyRows按in的索引拷贝.
  • current_code_id_ = in,注意这里current_code_id_是CuArray类型的成员变量,用于后续反向传播.
  • connect_nnet_为一个仅有一个component的网络,进行前向传递,结果存于code_out_,为CuMatrix的成员变量,用于与各个层加和,改变偏执.
  • Nnet的Propagate():propagate_buf_[0]存放输入;对每一个component调用Propagate;输出存于propagate_buf_i+1.输出为out=propagate_buf_[component_.size()].
  • 将connect_out_diff_清空,重置为0,用于存储梯度,大小为in.size*connet_nnet_.OutputDim().
  • 由于component_[0]为speakercode层,所以正常输入存于propagate_buf_[1].
  • 进行前向
    for(int32 i=1; i < (int32)components_.size(); i++) {
      components_[i]->Propagate(propagate_buf_[i], &propagate_buf_[i+1]);
      sat_layer->PropagateFnc(&propagate_buf_[i+1], i);
    }
    
    循环从i=1到size-1.由于0为speakercode层.举个例子.
    0=speakercode;1=affine;2=sigmoid;3=affine;4=softmax.此时component_.size()=5
    循环的components为1,2,3,4.最终的结果存放在propagate_buf_[5]中.
    
    sat_layer->PropagateFnc(CuMatrix<BaseFloat> *out, int layer_id)为这样
    定义:std::map<int32, std::pair<int32, int32 > > adapt_layers_;
    
    layer_id为proto文件中写好的,adapt_layers_.find(layer_id) != adapt_layers_.end(),从adapt_layers_中查找。
    这里adapt_layers_=[1,3].当i处在一个adapt_layers_时,将偏执加上去.
    out->AddMat(1.0, code_out_.ColRange(adapt_layers_[layer_id].first, adapt_layers_[layer_id].second));
    第一个值为起始位置,第二个值为宽度.(其实位置为之前adapt_layers宽度加和即可)
    
  1. post转换为CuMatrix形式
  • typedef std::vector<std::vector<std::pair<int32,BaseFloat>>> Posterior;其中第一个vector中的每个对象为一帧数据;第二个vector长度通常为1;存一个pair对;int32为transition-id;BaseFloat为概率.
  • 之所以设计成vector<vector<pair>>的形式,而不是vector<pair>,目的在于支持多状态后验概率的形式.但很少见,通常第二个vector长度为1.
  • PosteriorToMatrix函数
    void PosteriorToMatrix(const Posterior &post, int32 num_cols, CuMatrix<Real> *mat) {
    // Make a host-matrix,
    int32 num_rows = post.size();
    Matrix<Real> m(num_rows, num_cols, kSetZero); // zero-filled
    // Fill from Posterior,
    for (int32 t = 0; t < post.size(); t++) {
      for (int32 i = 0; i < post[t].size(); i++) { 
        int32 col = post[t][i].first;
        if (col >= num_cols) {
          KALDI_ERR << "Out-of-bound Posterior element with index " << col
          << ", higher than number of columns " << num_cols;
        } 
        m(t, col) = post[t][i].second;
      }     
    }       
    // Copy to output GPU matrix,
    (*mat) = m;
    }        
    
  • post.size()为帧数,新矩阵的大小=帧数*目标状态数;核心为m(t,col)= post[t][i].second;最后返回CuMatrix形式.这样nnet_tgt中为每帧对应target矩阵形式(通常每行为one-hot形式).
  1. 计算误差
  • xent.Eval(frm_weights, nnet_out, nnet_tgt, &obj_diff);梯度会被frm_weights缩放
  • 该函数第一部分:计算diff=目标函数对输出求偏导数=(net_out-target)
  • 该函数第二部分:计算loss+=cross_entropy=求和(-tlogy);entropy=求和(-tlogt);每1h的数据报告一次(loss-entropy/frames)
  1. 反向传播调整
  • 如果不是crossvalidate是进行
  • 传入参数为obj_diff,也就是(net_out-target)
  • 根据参数update_codeonly分为两种更新
  1. 主体更新函数Backpropagate_SpkCode(obj_diff,NULL)
  • dynamic_case<SpeakerCode *>(components_[0]);
  • 将梯队out_diff赋值给backpropagate_buf_[NumComponents()],即最后一个.
  • 循环从NumComponent()-1开始(即最后一个component),到component[1]结束,因为component[0]为sat_component.
  • 循环体内
    sat_layer->BackpropagateFnc(backpropagate_buf_[i+1], i);
    如果i恰好在adapt_layers_中(adapt_layers_通常为线性层),存储backpropagate_[i+1]
    的梯度到connect_out_diff_的相应列中,准备进行传递得到speakercode的梯度.
      components_[i]->Backpropagate(propagate_buf_[i],propagate_buf_[i+1],
                                backpropagate_buf_[i+1],&backpropagate_buf_[i]);
    每一层传递前的梯度存于backpropagate_buf_[i+1],传递后存于backpropagate_buf_[i],而前向的buf在某些component中会被利用.
    
  • 更新speakercode层:sat_layer->Update(propagate_buf_[0], backpropagate_buf_[1]);实际上参数并没有用,更新的梯度存在connect_out_diff中;用于更新speakercode的梯度存于speaker_code_diff_cur_中.connect_nnet_.Backpropagate在获得speaker_code的梯度同时,更新connect_out_网络(实际为一个)
  • current_code_id_中存放的是该minibatch的所有对应speaker_id;赋值给spks后进行sort和unique,注意unique实际是去掉相邻的重复.然后spks.resize为[std::distance(spks.begin(),it)],相当于无重复长度.
  • 处理冲量
    for (int32 i = 0; i < spks.size(); i++) {
      speaker_code_corr_.Row(current_code_id_[i]).Scale(mmt);
    }
    这样事有问题的做冲量运算。此处需要修改
    假设该minibatch对应的current_code_id_为[1,2,3,2,4,3],这样得到的spks为[1,2,3,4]。
    所以i取0~3.current_code_id_[0~3]为[1,2,3,2],这样编号为4的speaker并没有处理以前累计的梯度。
    如果经常出现类似code_id_顺序,会导致某些speaker的梯度持续累加,以前很多轮的minibatch的梯度被反复累加。
    
  • 为每个说话人累加梯度
    for (int32 i = 0; i < current_code_id_.size(); i++) {
      speaker_code_corr_.Row(current_code_id_[i]).AddVec(1.0,
      speaker_code_diff_cur_.Row(i));
    }
    其中speaker_code_diff_cur_.Row(i)相当于取出第i帧的梯度
    current_code_id_[i]相当于取第i帧对应的说话人id
    speaker_code_corr_大小=人数*长度,用来存储累加梯度。
    speaker_code_corr_.Row(current_code_id_[i])相当于取出第i帧所对应说话人的累计总梯度
    
  • 调整speaker_code_
    for (int32 i = 0; i < spks.size(); i++) {
      speaker_code_.Row(spks[i]).AddVec(-lr, speaker_code_corr_.Row(spks[i]));;
    }
    spks[i]为取出第i个人的id.speaker_code_.Row(spks[i])为取出第i个人的code值
    AddVec(-lr, speaker_code_corr_.Row(spks[i]))与该人的累计梯度相加
    
  1. 打印log信息和Write新模型
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,248评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,681评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,443评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,475评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,458评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,185评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,451评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,112评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,609评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,083评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,163评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,803评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,357评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,357评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,590评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,636评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,925评论 2 344