函数调用在哪里?

今天看到一段函数,如下:

std::string comm_done_filename(const std::string & base_done_filename, const int & job_id) {
    return base_done_filename + ".done." + IntToString(job_id);
}

std::string comm_avg_model_name(const std::string & base_model_filename, const int &count) {
    return base_model_filename + ".avg." + IntToString(count);
}

隐约觉得可以用宏来解决, 因为函数名中的“done” 和“avg”,与返回值中包含的是相同的,而且两个函数形式完全一样,没有必要写成两个函数。

代码如下:

#define BT(name,flags,base,count) \
    std::string comm_##flags##_##name (const std::string & base,const int & count) { \
    return base + "."+ #flags + "." + IntToString(count); \
}

BT(model_name,avg,kaka,akak)
BT(filename,done,kaka,akak)

注意:函数名中的flags前后要有两个##, 而name只有前面有##,因为name之后没有东西了。

声名中kaka, akak是没有什么意义的,起个形参的作用。

调用的时候,当然可以直接用函数名:comm_done_filename,comm_avg_model_name。

另外,也可以使用函数指针数组或者用map。

首先,定义一个函数指针:

typedef std::string (*TransFun)(const std::string &, const int &);

函数指针数组如下:

 TransFun fun[] = {comm_done_filename,comm_avg_model_name};

map如下:

 map<string,TransFun> FunStrMap;

调用的时候当然是看不到函数名的:

 string aa = FunStrMap["done"]("cheng",3);
 string AA = fun[0]("cheng",3);

不多说,整个代码如下:

#include <iostream>
#include <string>
#include <sstream>
#include <map>

using namespace std;

std::string IntToString(const int &i) {
    std::stringstream ss;
    ss << i;
    return ss.str();
}
#define BT(name,flags,base,count) \
    std::string comm_##flags##_##name (const std::string & base,const int & count) { \
    return base + "."+ #flags + "." + IntToString(count); \
}

BT(model_name,avg,kaka,akak)
BT(filename,done,kaka,akak)

/*
std::string comm_done_filename(const std::string & base_done_filename, const int & job_id) {
    return base_done_filename + ".done." + IntToString(job_id);
}

std::string comm_avg_model_name(const std::string & base_model_filename, const int &count) {
    return base_model_filename + ".avg." + IntToString(count);
}
*/
typedef std::string (*TransFun)(const std::string &, const int &);
int main(){

    TransFun fun[] = {comm_done_filename,comm_avg_model_name};
    map<string,TransFun> FunStrMap;
    FunStrMap["done"] = fun[0];
    //FunStrMap["avg"] = fun[1];
    FunStrMap.insert(pair<string,TransFun>("avg",fun[1]));//不同添加元素的方法
    string aa = FunStrMap["done"]("cheng",3);
    string AA = fun[0]("cheng",3);
    string bb = FunStrMap["avg"]("yu",8);
    string BB = fun[1]("yu",8);
    cout<<aa<<"\n"<<bb<<endl;
    cout<<AA<<"\n"<<BB<<endl;
    return 0;

}

输出结果:

cheng.done.3
yu.avg.8
cheng.done.3
yu.avg.8

总结一下知识点:

宏定义,函数指针,函数指针数组、map

CTC的理解

CTC技术近些年搭着End2End的顺风火了起来,查了一下原文,竟然是出在2006 Pittsburgh “International Conference on Machine Learning”的会议文章。Paper很容易找,搜索一下,能找到很多可用链接。

还是郑重地给出这篇文章的名字:

Connectionist Temporal Classification: Labelling Unsegmented
Sequence Data with Recurrent Neural Networks

要想理解这篇文章,要解决几个问题:

1. 如何实现了end2end?

2. 损失函数有什么特别?

3. 前后向算法在这个算法中的作用是什么?

其实理解这篇文章之后,这三个问题的本质是一样。

第一个问题:CTC如何实现了end2end?

在ASR领域,像DNN,LSTM这样的深度模型,输出的CD-phone的state。要做后面的文字的输出,需要加入字典(Lex)、语言模型(LM),与声学模型(AM)构成解码图后才可以。CTC越过字典与语言模型,直接输出phone或者字母(英文)、字(中文),虽然在性能上达不到其他的非e2e的模型的识别率,但这肯定是未来的大趋势,毕竟技术的发展应该是越来越简单,越来越直接。而且在实现的工程上,CTC在做识别时,也会结合LM,效果会更好。

与传统的声学模型训练相比,采用CTC作为损失函数的声学模型训练,是一种完全端到端的声学模型训练,不需要预先对数据做对齐,只需要一个输入序列和一个输出序列即可以训练。这样就不需要对数据对齐和一一标注,并且CTC直接输出序列预测的概率,不需要外部的后处理。

对于一个200帧的音频数据,假设真实的输出是7。经过神经网络后(可以dnn,rnn,lstm等),出来的序列还是200。比如说“chengyu”,将chengyu扩展到200的长度,路径有许多种。扩展的原则是:将一个字符扩展成连续多个,所有字符的长度总和为200,所有以下这几种都是合理的:

ccccc…hhhh…eeeeeee…ngyu

chengyyyyyyyyyy….uuuuuuuu

这时有一个问题,那就是像hello中有两个l,怎样处理?方法是在两个l之间插入一个无意义的\epsilon,例如hh…e…lllll\epsilonlll…o..。

RNN模型本质是对 P(z|x) 建模,其中s表示输入序列,o表示输出序列,z表示最终的label,o和l存在多对一的映射关系,即:P(z│x)=P(o|x),其中o是所有映射到z的输出序列。因此,只需要穷举出所有的o,累加一起即可得到P(z│x),从而使得RNN模型对最终的label进行建模。

第二个问题:CTC的损失函数有什么特别?

不同于用于分类的cross entropy准则和回归的MSE准则,CTC的损失函数巧妙在设计为-logP(z│x)。使P(z│x)的最大,等价于损失最小,通过求log的负值,转化成一个求最小值问题。

3. 前后算法在这个算法中的作用是什么?

要想计算出每一条可能路径的得分再将所有这些得分加起来,用蛮力方式显然是不可能,巧妙地运用语音识别中的前后向算法,即baum-welch算法可以大大减少计算量,用动态规划的算法思想有效在提高了效率。

C++之static_cast

cplusplus.com中的解释:

“static_cast can perform conversions between pointers to related classes, not only upcasts (from pointer-to-derived to pointer-to-base), but also downcasts (from pointer-to-base to pointer-to-derived). No checks are performed during runtime to guarantee that the object being converted is in fact a full object of the destination type. Therefore, it is up to the programmer to ensure that the conversion is safe. On the other side, it does not incur the overhead of the type-safety checks of dynamic_cast.

1
2
3
4
classBase {};classDerived:publicBase {};
Base * a =newBase;
Derived * b =static_cast<Derived*>(a);

This would be valid code, although b would point to an incomplete object of the class and could lead to runtime errors if dereferenced.
Therefore, static_cast is able to perform with pointers to classes not only the conversions allowed implicitly, but also their opposite conversions.
static_cast is also able to perform all conversions allowed implicitly (not only those with pointers to classes), and is also able to perform the opposite of these. It can:

  • Convert from void* to any pointer type. In this case, it guarantees that if the void* value was obtained by converting from that same pointer type, the resulting pointer value is the same.
  • Convert integers, floating-point values and enum types to enum types.

Additionally, static_cast can also perform the following:

  • Explicitly call a single-argument constructor or a conversion operator.
  • Convert to rvalue references.
  • Convert enum class values into integers or floating-point values.
  • Convert any type to void, evaluating and discarding the value.

从上面的内容来看,static_cast的功能还是很多的。记录重要的几点:

1. 相当于C语言的强制类型转换。如把int转成size_t:static_cast<size_t> i_value

2. 做基类的下行转换时,不安全,建议用dynamic_cast.

3. 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。

4. 把空指针转换成目标类型的空指针。

5. 把任何类型的表达式转换成void类型。

6. static_cast不能转换掉expression的const、volatile、或者__unaligned属性

C++之reinterpret_cast

在cplusplus.com中的解释如下:

reinterpret_cast converts any pointer type to any other pointer type, even of unrelated classes. The operation result is a simple binary copy of the value from one pointer to the other. All pointer conversions are allowed: neither the content pointed nor the pointer type itself is checked.

It can also cast pointers to or from integer types. The format in which this integer value represents a pointer is platform-specific. The only guarantee is that a pointer cast to an integer type large enough to fully contain it (such as intptr_t), is guaranteed to be able to be cast back to a valid pointer.

The conversions that can be performed by reinterpret_cast but not by static_cast are low-level operations based on reinterpreting the binary representations of the types, which on most cases results in code which is system-specific, and thus non-portable.

大意是说主要用于指针类型的转换,以及指针与足够大的整数类型之间的转换;从整数类型(包括枚举类型)到指针类型,无视大小。比特位不变。安全性由程序员自己保证

判断大小端及字节交换

之前写过一篇“如何判断机器的CPU是大端还是小端?http://www.orangespeech.com/?p=89”的blog, 现在更新一下开源代码中的简洁实现:

int IsLittleEndian() {
int check = 1;
  return (*reinterpret_cast<char*>(&check) != 0);
}

C的实现如下:

 #define BIG_ENDIAN      0
 #define LITTLE_ENDIAN   1

  int little_endian(void)
  {
      short int w = 0x0001;
      char *byte = (char *) &w;
      return(byte[0] ? LITTLE_ENDIAN : BIG_ENDIAN);
  }

顺带给出字节交换的代码,同样厉害!

#define SWAP8(a) { \
  int t = ((char*)&a)[0]; ((char*)&a)[0]=((char*)&a)[7]; ((char*)&a)[7]=t;\
      t = ((char*)&a)[1]; ((char*)&a)[1]=((char*)&a)[6]; ((char*)&a)[6]=t;\
      t = ((char*)&a)[2]; ((char*)&a)[2]=((char*)&a)[5]; ((char*)&a)[5]=t;\
      t = ((char*)&a)[3]; ((char*)&a)[3]=((char*)&a)[4]; ((char*)&a)[4]=t;}
#define SWAP4(a) { \
  int t = ((char*)&a)[0]; ((char*)&a)[0]=((char*)&a)[3]; ((char*)&a)[3]=t;\
      t = ((char*)&a)[1]; ((char*)&a)[1]=((char*)&a)[2]; ((char*)&a)[2]=t;}
#define SWAP2(a) { \
  int t = ((char*)&a)[0]; ((char*)&a)[0]=((char*)&a)[1]; ((char*)&a)[1]=t;}

纯虚类与dynamic_cast

今天看到一段代码,用dynamic_cast将每个vector的成员cast到基类。但是,如果基类是一个纯虚类,似乎没有这个必要。以下是我的实验:

#include <iostream>
#include <vector>

using namespace std;

class Father{
        public:
                virtual void get()=0;
};
class Son: public Father{
        public:
                void get(){ cout<<"Son"<<endl;}
};

class Daughter: public Father{
        public:
                void get() {cout<<"Doughter"<<endl;}
};

int main(){

        vector<Father *> a;

        Son s1;
        a.push_back(&s1);
        Daughter d1;
        a.push_back(&d1);

        for(size_t i=0;i<a.size();i++){
            //Father *ts = dynamic_cast<Father *>(a[i]);
            //ts->get();
            a[i]->get();//可以获得与上两行一样的结果
        }
        return 0;
}

最近读到《资治通鉴》上一段话,觉得说得太好了,摘抄下来,供今后慢品。

 “夫信者,人君之大宝也。国保于民,民保于信;非信无以使民,非民无以保国。是故古之王者不欺四海,霸者不欺四邻,善为国者不欺其民,善为家者不欺其亲。不善者反之,欺其邻国,欺其百姓,甚者欺其兄弟,欺其父子。上不信下,下不信上,上下离心,以至于败。所利不能药其所伤,所获不能补其所亡,岂不哀哉!”

回忆

若干年后,我们将如何回忆这样一场变故?从个人的角度,谈一谈对于许多事情的看法。
对于生命,多一份珍惜与敬畏。科技发达的今天,一个病毒足以摧毁整个医疗系统,生命变得如此脆弱。如果不好好珍惜,逝去有时很轻易。如果不多一份敬畏,生命的休止符常常来得悄无声息。
对于善与美,多一份感动与感激。那些战斗在一线的人,他们可以是出于职责,可以是出于号召,但不能不被他们感动。安宁的日子里,都不过是平凡角色的扮演者;在这些特殊的日子,他们站在了第一线,为我们筑起防线,保卫了我们的健康与安宁。
对于丑与恶,多了一份不耻与严厉。无论身居何位,无论影响大小,图一己之私利,于公众利益而不顾,更有甚者,趁火打劫,为非做歹,常叹良知何在!
对于未来,多了一份期待与信心。经过这一场变故,我们才会发现,安居乐业的日子不容易,才是真真切切的幸福。我们也会发现那些需要改进的地方,只有做得更好,更完善,才能抵御风浪,才能有实实在在的幸福!