在 egs/wsj/s5/steps/nnet3/chain/gen_topo*.py 與 src/hmm/hmm-topology.cc 檔案進行對應
在 gen_topo*.p{l, y} 檔案中進行自動建立 topo 檔案, 然後在 hmm-topology.cc 檔案中的 HmmTopology::Read() 函數中解析 topo 檔案生成 HmmTopology 對象。
在 steps/nnet3/chain/gen_topo*.p{l, y} 檔案中可以看到 topo 檔案大緻結構:
args = parser.parse_args()
silence_phones = [ int(x) for x in args.silence_phones.split(":") ]
nonsilence_phones = [ int(x) for x in args.nonsilence_phones.split(":") ]
all_phones = silence_phones + nonsilence_phones
print("<Topology>")
print("<TopologyEntry>")
# 目前 TopologyEntry 配置相關的所有 Phone 元素
print("<ForPhones>")
print(" ".join([str(x) for x in all_phones]))
print("</ForPhones>")
# 目前 State 配置相關
# state 0 is nonemitting
print("<State> 0 <Transition> 1 0.5 <Transition> 2 0.5 </State>")
# state 1 is for when we traverse it in 1 state
print("<State> 1 <PdfClass> 0 <Transition> 4 1.0 </State>")
# state 2 is for when we traverse it in >1 state, for the first state.
print("<State> 2 <PdfClass> 2 <Transition> 3 1.0 </State>")
# state 3 is for the self-loop. Use pdf-class 1 here so that the default
# phone-class clustering (which uses only pdf-class 1 by default) gets only
# stats from longer phones.
print("<State> 3 <PdfClass> 1 <Transition> 3 0.5 <Transition> 4 0.5 </State>")
print("<State> 4 </State>")
print("</TopologyEntry>")
print("</Topology>")
在 hmm-topology.h 檔案中
/// TopologyEntry is a typedef that represents the topology of
/// a single (prototype) state.
typedef std::vector<HmmState> TopologyEntry;
對于 HmmTopology::Read() 函數中可以了解 topo 檔案解析的過程
ExpectToken(is, binary, "<ForPhones>");
std::vector<int32> phones;
std::string s;
// 擷取所有 phones 清單
while (1) {
is >> s;
if (is.fail()) KALDI_ERR << "Reading HmmTopology object, unexpected end of file while expecting phones.";
if (s == "</ForPhones>") break;
else {
int32 phone;
if (!ConvertStringToInteger(s, &phone))
KALDI_ERR << "Reading HmmTopology object, expected "
<< "integer, got instead " << s;
phones.push_back(phone);
}
}
// 建構狀态轉換機率
std::vector<HmmState> this_entry;
std::string token;
ReadToken(is, binary, &token);
while (token != "</TopologyEntry>") {
if (token != "<State>")
KALDI_ERR << "Expected </TopologyEntry> or <State>, got instead "<<token;
int32 state;
ReadBasicType(is, binary, &state);
if (state != static_cast<int32>(this_entry.size()))
KALDI_ERR << "States are expected to be in order from zero, expected "
<< this_entry.size() << ", got " << state;
ReadToken(is, binary, &token);
int32 forward_pdf_class = kNoPdf; // -1 by default, means no pdf.
if (token == "<PdfClass>") {
// 根據 pdfClass 來建立一個 HmmState
ReadBasicType(is, binary, &forward_pdf_class);
this_entry.push_back(HmmState(forward_pdf_class));
ReadToken(is, binary, &token);
if (token == "<SelfLoopPdfClass>")
KALDI_ERR << "pdf classes should be defined using <PdfClass> "
<< "or <ForwardPdfClass>/<SelfLoopPdfClass> pair";
} else if (token == "<ForwardPdfClass>") {
// 根據 <ForwardPdfClass> <SelfLoopPdfClass> 組合, 來建立一個 HmmState
int32 self_loop_pdf_class = kNoPdf;
ReadBasicType(is, binary, &forward_pdf_class);
ReadToken(is, binary, &token);
KALDI_ASSERT(token == "<SelfLoopPdfClass>");
ReadBasicType(is, binary, &self_loop_pdf_class);
this_entry.push_back(HmmState(forward_pdf_class, self_loop_pdf_class));
ReadToken(is, binary, &token);
} else {
// 若 <State> 後沒有 <PdfClass>, <ForwardPdfClass> , 則添加 kNoPdf 的 HmmState
this_entry.push_back(HmmState(forward_pdf_class));
}
while (token == "<Transition>") {
int32 dst_state;
BaseFloat trans_prob;
ReadBasicType(is, binary, &dst_state);
ReadBasicType(is, binary, &trans_prob);
// 擷取最後一個 HmmState 狀态 并将 transitions 中配置 狀态切換的機率
this_entry.back().transitions.push_back(std::make_pair(dst_state, trans_prob));
ReadToken(is, binary, &token);
}
if(token == "<Final>") // TODO: remove this clause after a while.
KALDI_ERR << "You are trying to read old-format topology with new Kaldi.";
if (token != "</State>")
KALDI_ERR << "Reading HmmTopology, unexpected token "<<token;
ReadToken(is, binary, &token);
}
int32 my_index = entries_.size();
// entries_ 中為 TopologyEntry 清單
entries_.push_back(this_entry);
// 注意: 這裡将所有涉及的 phones 通過 phone2idx 數組将 phone 與 my_index 進行對應起來,這樣擷取 phone 的狀态相關的 HmmState
for (size_t i = 0; i < phones.size(); i++) {
int32 phone = phones[i];
if (static_cast<int32>(phone2idx_.size()) <= phone)
phone2idx_.resize(phone+1, -1); // -1 is invalid index.
KALDI_ASSERT(phone > 0);
if (phone2idx_[phone] != -1)
KALDI_ERR << "Phone with index "<<(i)<<" appears in multiple topology entries.";
// 這裡将每個 phone 相關的 HmmState 切換過程TopologyEntry的 index 設定到 phone2idx 中
phone2idx_[phone] = my_index;
phones_.push_back(phone);
}