Archive

Archive for the ‘life is fun’ Category

fastText 源码分析

October 26, 2017 Leave a comment

fastText 源码分析

介绍

fastText 是 facebook 近期开源的一个词向量计算以及文本分类工具,该工具的理论基础是以下两篇论文:

Enriching Word Vectors with Subword Information

这篇论文提出了用 word n-gram 的向量之和来代替简单的词向量的方法,以解决简单 word2vec 无法处理同一词的不同形态的问题。fastText 中提供了 maxn 这个参数来确定 word n-gram 的 n 的大小。

Bag of Tricks for Efficient Text Classification

这篇论文提出了 fastText 算法,该算法实际上是将目前用来算 word2vec 的网络架构做了个小修改,原先使用一个词的上下文的所有词向量之和来预测词本身(CBOW 模型),现在改为用一段短文本的词向量之和来对文本进行分类。

在我看来,fastText 的价值是提供了一个 更具可读性,模块化程度较好 的 word2vec 的实现,附带一些新的分类功能,本文详细分析它的源码。

顶层结构

fastText 的代码结构以及各模块的功能如下图所示:

fasttext-arch

分析各模块时,我只会解释该模块的 主要调用路径 下的源码,以 注释 的方式说明,其它的功能性代码请大家自行阅读。如果对 word2vec 的理论和相关术语不了解,请先阅读这篇 word2vec 中的数学原理详解

训练数据格式

训练数据格式为一行一个句子,每个词用空格分割,如果一个词带有前缀“__label__”,那么它就作为一个类标签,在文本分类时使用,这个前缀可以通过-label参数自定义。训练文件支持 UTF-8 格式。

fasttext 模块

fasttext 是最顶层的模块,它的主要功能是训练预测,首先是训练功能的调用路径,第一个函数是 train,它的主要作用是 初始化参数,启动多线程训练,请大家留意源码中的相关部分。

void FastText::train(std::shared_ptr<Args> args) {
  args_ = args;
  dict_ = std::make_shared<Dictionary>(args_);
  std::ifstream ifs(args_->input);
  if (!ifs.is_open()) {
    std::cerr << "Input file cannot be opened!" << std::endl;
    exit(EXIT_FAILURE);
  }
  // 根据输入文件初始化词典
  dict_->readFromFile(ifs);
  ifs.close();

   // 初始化输入层, 对于普通 word2vec,输入层就是一个词向量的查找表,
   // 所以它的大小为 nwords 行,dim 列(dim 为词向量的长度),但是 fastText 用了
   // word n-gram 作为输入,所以输入矩阵的大小为 (nwords + ngram 种类) * dim
   // 代码中,所有 word n-gram 都被 hash 到固定数目的 bucket 中,所以输入矩阵的大小为
   // (nwords + bucket 个数) * dim
  input_ = std::make_shared<Matrix>(dict_->nwords()+args_->bucket, args_->dim);
  
  // 初始化输出层,输出层无论是用负采样,层次 softmax,还是普通 softmax,
  // 对于每种可能的输出,都有一个 dim 维的参数向量与之对应
  // 当 args_->model == model_name::sup 时,训练分类器,
  // 所以输出的种类是标签总数 dict_->nlabels()
  if (args_->model == model_name::sup) {
    output_ = std::make_shared<Matrix>(dict_->nlabels(), args_->dim);
  } else {
  // 否则训练的是词向量,输出种类就是词的种类 dict_->nwords()
    output_ = std::make_shared<Matrix>(dict_->nwords(), args_->dim);
  }
  input_->uniform(1.0 / args_->dim);
  output_->zero();

  start = clock();
  tokenCount = 0;
  
  // 库采用 C++ 标准库的 thread 来实现多线程
  std::vector<std::thread> threads;
  for (int32_t i = 0; i < args_->thread; i++) {
    // 实际的训练发生在 trainThread 中
    threads.push_back(std::thread([=]() { trainThread(i); }));
  }
  for (auto it = threads.begin(); it != threads.end(); ++it) {
    it->join();
  }
  
  // Model 的所有参数(input_, output_)是在初始化时由外界提供的,
  // 此时 input_ 和 output_ 已经处于训练结束的状态
  model_ = std::make_shared<Model>(input_, output_, args_, 0);

  saveModel();
  if (args_->model != model_name::sup) {
    saveVectors();
  }
}

下面,我们进入 trainThread函数,看看训练的主体逻辑,该函数的主要工作是 实现了标准的随机梯度下降,并随着训练的进行逐步降低学习率。

void FastText::trainThread(int32_t threadId) {

  std::ifstream ifs(args_->input);
  // 根据线程数,将训练文件按照总字节数(utils::size)均分成多个部分
  // 这么做的一个后果是,每一部分的第一个词有可能从中间被切断,
  // 这样的"小噪音"对于整体的训练结果无影响
  utils::seek(ifs, threadId * utils::size(ifs) / args_->thread);

  Model model(input_, output_, args_, threadId);
  if (args_->model == model_name::sup) {
    model.setTargetCounts(dict_->getCounts(entry_type::label));
  } else {
    model.setTargetCounts(dict_->getCounts(entry_type::word));
  }

  // 训练文件中的 token 总数
  const int64_t ntokens = dict_->ntokens();
  // 当前线程处理完毕的 token 总数
  int64_t localTokenCount = 0;
  std::vector<int32_t> line, labels;
  // tokenCount 为所有线程处理完毕的 token 总数
  // 当处理了 args_->epoch 遍所有 token 后,训练结束 
  while (tokenCount < args_->epoch * ntokens) {
    // progress = 0 ~ 1,代表当前训练进程,随着训练的进行逐渐增大
    real progress = real(tokenCount) / (args_->epoch * ntokens);
    // 学习率根据 progress 线性下降
    real lr = args_->lr * (1.0 - progress);
    localTokenCount += dict_->getLine(ifs, line, labels, model.rng);
    // 根据训练需求的不同,这里用的更新策略也不同,它们分别是:
    // 1. 有监督学习(分类)
    if (args_->model == model_name::sup) {
      dict_->addNgrams(line, args_->wordNgrams);
      supervised(model, lr, line, labels);
    // 2. word2vec (CBOW)
    } else if (args_->model == model_name::cbow) {
      cbow(model, lr, line);
    // 3. word2vec (SKIPGRAM)
    } else if (args_->model == model_name::sg) {
      skipgram(model, lr, line);
    }
    // args_->lrUpdateRate 是每个线程学习率的变化率,默认为 100,
    // 它的作用是,每处理一定的行数,再更新全局的 tokenCount 变量,从而影响学习率
    if (localTokenCount > args_->lrUpdateRate) {
      tokenCount += localTokenCount;
      // 每次更新 tokenCount 后,重置计数
      localTokenCount = 0;
      // 0 号线程负责将训练进度输出到屏幕
      if (threadId == 0) {
        printInfo(progress, model.getLoss());
      }
    }
  }
  if (threadId == 0) {
    printInfo(1.0, model.getLoss());
    std::cout << std::endl;
  }
  ifs.close();
}

一哄而上的并行训练:每个训练线程在更新参数时并没有加锁,这会给参数更新带来一些噪音,但是不会影响最终的结果。无论是 google 的 word2vec 实现,还是 fastText 库,都没有加锁。

从 trainThread 函数中我们发现,实际的模型更新策略发生在 supervised,cbow,skipgram三个函数中,这三个函数都调用同一个 model.update 函数来更新参数,这个函数属于 model 模块,但在这里我先简单介绍它,以方便大家理解代码。

update 函数的原型为

void Model::update(const std::vector<int32_t>& input, int32_t target, real lr)

该函数有三个参数,分别是“输入”,“类标签”,“学习率”。

  • 输入是一个 int32_t数组,每个元素代表一个词在 dictionary 里的 ID。对于分类问题,这个数组代表输入的短文本,对于 word2vec,这个数组代表一个词的上下文。
  • 类标签是一个 int32_t 变量。对于 word2vec 来说,它就是带预测的词的 ID,对于分类问题,它就是类的 label 在 dictionary 里的 ID。因为 label 和词在词表里一起存放,所以有统一的 ID 体系。

下面,我们回到 fasttext 模块的三个更新函数:

void FastText::supervised(Model& model, real lr,
                          const std::vector<int32_t>& line,
                          const std::vector<int32_t>& labels) {
  if (labels.size() == 0 || line.size() == 0) return;
  // 因为一个句子可以打上多个 label,但是 fastText 的架构实际上只有支持一个 label
  // 所以这里随机选择一个 label 来更新模型,这样做会让其它 label 被忽略
  // 所以 fastText 不太适合做多标签的分类
  std::uniform_int_distribution<> uniform(0, labels.size() - 1);
  int32_t i = uniform(model.rng);
  model.update(line, labels[i], lr);
}

void FastText::cbow(Model& model, real lr,
                    const std::vector<int32_t>& line) {
  std::vector<int32_t> bow;
  std::uniform_int_distribution<> uniform(1, args_->ws);
  
  // 在一个句子中,每个词可以进行一次 update
  for (int32_t w = 0; w < line.size(); w++) {
    // 一个词的上下文长度是随机产生的
    int32_t boundary = uniform(model.rng);
    bow.clear();
    // 以当前词为中心,将左右 boundary 个词加入 input
    for (int32_t c = -boundary; c <= boundary; c++) {
      // 当然,不能数组越界
      if (c != 0 && w + c >= 0 && w + c < line.size()) {
        // 实际被加入 input 的不止是词本身,还有词的 word n-gram
        const std::vector<int32_t>& ngrams = dict_->getNgrams(line[w + c]);
        bow.insert(bow.end(), ngrams.cbegin(), ngrams.cend());
      }
    }
    // 完成一次 CBOW 更新
    model.update(bow, line[w], lr);
  }
}

void FastText::skipgram(Model& model, real lr,
                        const std::vector<int32_t>& line) {
  std::uniform_int_distribution<> uniform(1, args_->ws);
  for (int32_t w = 0; w < line.size(); w++) {
    // 一个词的上下文长度是随机产生的
    int32_t boundary = uniform(model.rng);
    // 采用词+word n-gram 来预测这个词的上下文的所有的词
    const std::vector<int32_t>& ngrams = dict_->getNgrams(line[w]);
    // 在 skipgram 中,对上下文的每一个词分别更新一次模型
    for (int32_t c = -boundary; c <= boundary; c++) {
      if (c != 0 && w + c >= 0 && w + c < line.size()) {
        model.update(ngrams, line[w + c], lr);
      }
    }
  }
}

训练部分的代码已经分析完毕,预测部分的代码就简单多了,它的主要逻辑都在 model.predict 函数里。

void FastText::predict(const std::string& filename, int32_t k, bool print_prob) {
  std::vector<int32_t> line, labels;
  std::ifstream ifs(filename);
  if (!ifs.is_open()) {
    std::cerr << "Test file cannot be opened!" << std::endl;
    exit(EXIT_FAILURE);
  }
  while (ifs.peek() != EOF) {
    // 读取输入文件的每一行
    dict_->getLine(ifs, line, labels, model_->rng);
    // 将一个词的 n-gram 加入词表,用于处理未登录词。(即便一个词不在词表里,我们也可以用它的 word n-gram 来预测一个结果)
    dict_->addNgrams(line, args_->wordNgrams);
    if (line.empty()) {
      std::cout << "n/a" << std::endl;
      continue;
    }
    std::vector<std::pair<real, int32_t>> predictions;
    // 调用 model 模块的预测接口,获取 k 个最可能的分类
    model_->predict(line, k, predictions);
    // 输出结果
    for (auto it = predictions.cbegin(); it != predictions.cend(); it++) {
      if (it != predictions.cbegin()) {
        std::cout << ' ';
      }
      std::cout << dict_->getLabel(it->second);
      if (print_prob) {
        std::cout << ' ' << exp(it->first);
      }
    }
    std::cout << std::endl;
  }
  ifs.close();
}

通过对 fasttext 模块的分析,我们发现它最核心的预测和更新逻辑都在 model 模块中,接下来,我们进入 model 模块一探究竟。

model 模块

model 模块对外提供的服务可以分为 update 和 predict 两类,下面我们分别对它们进行分析。由于这里的参数较多,我们先以图示标明各个参数在模型中所处的位置,以免各位混淆。

fasttext-model-arch

图中所有变量的名字全部与 model 模块中的名字保持一致,注意到 wo_ 矩阵在不同的输出层结构中扮演着不同的角色。

update

update 函数的作用已经在前面介绍过,下面我们看一下它的实现:

void Model::update(const std::vector<int32_t>& input, int32_t target, real lr) {
  // target 必须在合法范围内
  assert(target >= 0);
  assert(target < osz_);
  if (input.size() == 0) return;
  // 计算前向传播:输入层 -> 隐层
  hidden_.zero();
  for (auto it = input.cbegin(); it != input.cend(); ++it) {
    // hidden_ 向量保存输入词向量的均值,
    // addRow 的作用是将 wi_ 矩阵的第 *it 列加到 hidden_ 上
    hidden_.addRow(*wi_, *it);
  }
  // 求和后除以输入词个数,得到均值向量
  hidden_.mul(1.0 / input.size());
  
  // 根据输出层的不同结构,调用不同的函数,在各个函数中,
  // 不仅通过前向传播算出了 loss_,还进行了反向传播,计算出了 grad_,后面逐一分析。
  // 1. 负采样
  if (args_->loss == loss_name::ns) {
    loss_ += negativeSampling(target, lr);
  } else if (args_->loss == loss_name::hs) {
  // 2. 层次 softmax
    loss_ += hierarchicalSoftmax(target, lr);
  } else {
  // 3. 普通 softmax
    loss_ += softmax(target, lr);
  }
  nexamples_ += 1;

  // 如果是在训练分类器,就将 grad_ 除以 input_ 的大小
  // 原因不明
  if (args_->model == model_name::sup) {
    grad_.mul(1.0 / input.size());
  }
  // 反向传播,将 hidden_ 上的梯度传播到 wi_ 上的对应行
  for (auto it = input.cbegin(); it != input.cend(); ++it) {
    wi_->addRow(grad_, *it, 1.0);
  }
}

下面我们看看三种输出层对应的更新函数:negativeSampling,hierarchicalSoftmax,softmax

model 模块中最有意思的部分就是将层次 softmax 和负采样统一抽象成多个二元 logistic regression 计算。

如果使用负采样,训练时每次选择一个正样本,随机采样几个负样本,每种输出都对应一个参数向量,保存于 wo_ 的各行。对所有样本的参数更新,都是一次独立的 LR 参数更新。

如果使用层次 softmax,对于每个目标词,都可以在构建好的霍夫曼树上确定一条从根节点到叶节点的路径,路径上的每个非叶节点都是一个 LR,参数保存在 wo_ 的各行上,训练时,这条路径上的 LR 各自独立进行参数更新。

无论是负采样还是层次 softmax,在神经网络的计算图中,所有 LR 都会依赖于 hidden_的值,所以 hidden_的梯度 grad_ 是各个 LR 的反向传播的梯度的累加。

LR 的代码如下:

real Model::binaryLogistic(int32_t target, bool label, real lr) {
  // 将 hidden_ 和参数矩阵的第 target 行做内积,并计算 sigmoid
  real score = utils::sigmoid(wo_->dotRow(hidden_, target));
  // 计算梯度时的中间变量
  real alpha = lr * (real(label) - score);
  // Loss 对于 hidden_ 的梯度累加到 grad_ 上
  grad_.addRow(*wo_, target, alpha);
  // Loss 对于 LR 参数的梯度累加到 wo_ 的对应行上
  wo_->addRow(hidden_, target, alpha);
  // LR 的 Loss
  if (label) {
    return -utils::log(score);
  } else {
    return -utils::log(1.0 - score);
  }
}

经过以上的分析,下面三种逻辑就比较容易理解了:

real Model::negativeSampling(int32_t target, real lr) {
  real loss = 0.0;
  grad_.zero();
  for (int32_t n = 0; n <= args_->neg; n++) {
    // 对于正样本和负样本,分别更新 LR
    if (n == 0) {
      loss += binaryLogistic(target, true, lr);
    } else {
      loss += binaryLogistic(getNegative(target), false, lr);
    }
  }
  return loss;
}

real Model::hierarchicalSoftmax(int32_t target, real lr) {
  real loss = 0.0;
  grad_.zero();
  // 先确定霍夫曼树上的路径
  const std::vector<bool>& binaryCode = codes[target];
  const std::vector<int32_t>& pathToRoot = paths[target];
  // 分别对路径上的中间节点做 LR
  for (int32_t i = 0; i < pathToRoot.size(); i++) {
    loss += binaryLogistic(pathToRoot[i], binaryCode[i], lr);
  }
  return loss;
}

// 普通 softmax 的参数更新
real Model::softmax(int32_t target, real lr) {
  grad_.zero();
  computeOutputSoftmax();
  for (int32_t i = 0; i < osz_; i++) {
    real label = (i == target) ? 1.0 : 0.0;
    real alpha = lr * (label - output_[i]);
    grad_.addRow(*wo_, i, alpha);
    wo_->addRow(hidden_, i, alpha);
  }
  return -utils::log(output_[target]);
}

predict

predict 函数可以用于给输入数据打上 1 ~ K 个类标签,并输出各个类标签对应的概率值,对于层次 softmax,我们需要遍历霍夫曼树,找到 top-K 的结果,对于普通 softmax(包括负采样和 softmax 的输出),我们需要遍历结果数组,找到 top-K。

void Model::predict(const std::vector<int32_t>& input, int32_t k, std::vector<std::pair<real, int32_t>>& heap) {
  assert(k > 0);
  heap.reserve(k + 1);
  // 计算 hidden_
  computeHidden(input);
  
  // 如果是层次 softmax,使用 dfs 遍历霍夫曼树的所有叶子节点,找到 top-k 的概率
  if (args_->loss == loss_name::hs) {
    dfs(k, 2 * osz_ - 2, 0.0, heap);
  } else {
  // 如果是普通 softmax,在结果数组里找到 top-k
    findKBest(k, heap);
  }
  // 对结果进行排序后输出
  // 因为 heap 中虽然一定是 top-k,但并没有排好序
  std::sort_heap(heap.begin(), heap.end(), comparePairs);
}

void Model::findKBest(int32_t k, std::vector<std::pair<real, int32_t>>& heap) {
  // 计算结果数组
  computeOutputSoftmax();
  for (int32_t i = 0; i < osz_; i++) {
    if (heap.size() == k && utils::log(output_[i]) < heap.front().first) {
      continue;
    }
    // 使用一个堆来保存 top-k 的结果,这是算 top-k 的标准做法
    heap.push_back(std::make_pair(utils::log(output_[i]), i));
    std::push_heap(heap.begin(), heap.end(), comparePairs);
    if (heap.size() > k) {
      std::pop_heap(heap.begin(), heap.end(), comparePairs);
      heap.pop_back();
    }
  }
}

void Model::dfs(int32_t k, int32_t node, real score, std::vector<std::pair<real, int32_t>>& heap) {
  if (heap.size() == k && score < heap.front().first) {
    return;
  }

  if (tree[node].left == -1 && tree[node].right == -1) {
    // 只输出叶子节点的结果
    heap.push_back(std::make_pair(score, node));
    std::push_heap(heap.begin(), heap.end(), comparePairs);
    if (heap.size() > k) {
      std::pop_heap(heap.begin(), heap.end(), comparePairs);
      heap.pop_back();
    }
    return;
  }
  
  // 将 score 累加后递归向下收集结果
  real f = utils::sigmoid(wo_->dotRow(hidden_, node - osz_));
  dfs(k, tree[node].left, score + utils::log(1.0 - f), heap);
  dfs(k, tree[node].right, score + utils::log(f), heap);
}

其它模块

除了以上两个模块,dictionary 模块也相当重要,它完成了训练文件载入,哈希表构建,word n-gram 计算等功能,但是并没有太多算法在里面。

其它模块例如 Matrix, Vector 也只是封装了简单的矩阵向量操作,这里不再做详细分析。

附录:构建霍夫曼树算法分析

在学信息论的时候接触过构建 Huffman 树的算法,课本中的方法描述往往是:

找到当前权重最小的两个子树,将它们合并

算法的性能取决于如何实现这个逻辑。网上的很多实现都是在新增节点都时遍历一次当前所有的树,这种算法的复杂度是 O(n2)O(n2),性能很差。

聪明一点的方法是用一个优先级队列来保存当前所有的树,每次取 top 2,合并,加回队列。这个算法的复杂度是 O(nlogn)O(nlogn),缺点是必需使用额外的数据结构,而且进堆出堆的操作导致常数项较大。

word2vec 以及 fastText 都采用了一种更好的方法,时间复杂度是 O(nlogn)O(nlogn),只用了一次排序,一次遍历,简洁优美,但是要理解它需要进行一些推理。

算法如下:

void Model::buildTree(const std::vector<int64_t>& counts) {
  // counts 数组保存每个叶子节点的词频,降序排列
  // 分配所有节点的空间
  tree.resize(2 * osz_ - 1);
  // 初始化节点属性
  for (int32_t i = 0; i < 2 * osz_ - 1; i++) {
    tree[i].parent = -1;
    tree[i].left = -1;
    tree[i].right = -1;
    tree[i].count = 1e15;
    tree[i].binary = false;
  }
  for (int32_t i = 0; i < osz_; i++) {
    tree[i].count = counts[i];
  }
  // leaf 指向当前未处理的叶子节点的最后一个,也就是权值最小的叶子节点
  int32_t leaf = osz_ - 1;
  // node 指向当前未处理的非叶子节点的第一个,也是权值最小的非叶子节点
  int32_t node = osz_;
  // 逐个构造所有非叶子节点(i >= osz_, i < 2 * osz - 1)
  for (int32_t i = osz_; i < 2 * osz_ - 1; i++) {
    // 最小的两个节点的下标
    int32_t mini[2];
    
    // 计算权值最小的两个节点,候选只可能是 leaf, leaf - 1,
    // 以及 node, node + 1
    for (int32_t j = 0; j < 2; j++) {
      // 从这四个候选里找到 top-2
      if (leaf >= 0 && tree[leaf].count < tree[node].count) {
        mini[j] = leaf--;
      } else {
        mini[j] = node++;
      }
    }
    // 更新非叶子节点的属性
    tree[i].left = mini[0];
    tree[i].right = mini[1];
    tree[i].count = tree[mini[0]].count + tree[mini[1]].count;
    tree[mini[0]].parent = i;
    tree[mini[1]].parent = i;
    tree[mini[1]].binary = true;
  }
  // 计算霍夫曼编码
  for (int32_t i = 0; i < osz_; i++) {
    std::vector<int32_t> path;
    std::vector<bool> code;
    int32_t j = i;
    while (tree[j].parent != -1) {
      path.push_back(tree[j].parent - osz_);
      code.push_back(tree[j].binary);
      j = tree[j].parent;
    }
    paths.push_back(path);
    codes.push_back(code);
  }
}

算法首先对输入的叶子节点进行一次排序(O(nlogn)O(nlogn) ),然后确定两个下标 leaf 和 nodeleaf 总是指向当前最小的叶子节点,node 总是指向当前最小的非叶子节点,所以,最小的两个节点可以从 leaf, leaf – 1, node, node + 1 四个位置中取得,时间复杂度 O(1)O(1),每个非叶子节点都进行一次,所以总复杂度为 O(n)O(n),算法整体复杂度为 O(nlogn)O(nlogn)

Advertisements
Categories: life is fun

DL AI 芯片 市场整理

October 9, 2017 Leave a comment

DL市场整理

近一年各种深度学习平台和硬件层出不穷,各种xPU的功耗和面积数据也是满天飞,感觉有点乱。在这里我把我看到的一点情况做一些小结,顺便列一下可能的市场。在展开之前,我想强调的是,深度学习的应用无数,我能看到的只有能在千万级以上的设备中部署的市场,各个小众市场并不在列。

深度学习目前最能落地的应用有两个方向,一个是图像识别,一个是语音识别。这两个应用可以在如下市场看到:个人终端(手机,平板),监控,家庭,汽车,机器人,服务器。

先说手机和平板。这个市场一年的出货量在30亿颗左右(含功能机),除苹果外总值300亿刀。手机主要玩家是苹果(3亿颗以下),高通(8亿颗以上),联发科(7亿颗以上),三星(一亿颗以下),海思(一亿颗),展讯(6亿颗以上),平板总共4亿颗左右。而28纳米工艺,量很大的话(1亿颗以上),工程费用可以摊的很低,平均1平方毫米的成本是8美分左右,低端4G芯片(4核)的面积差不多是50平方毫米以下,成本就是4刀。中端芯片(8核)一般在100平方毫米左右,成本8刀。16纳米以及往上,同样的晶体管数,单位成本会到1.5倍。一般来说,手机的物料成本中,处理器芯片(含基带)价格占了1/6左右。一个物料成本90刀的手机,用的处理器一般在15刀以下,甚至只有10刀。这个10刀的芯片,包含了处理器,图形处理器,基带,图像信号处理器,每一样都是高科技的结晶,却和肯德基全家桶一个价,真是有点惨淡。然而生产成本只是一部分,人力也是很大的开销。一颗智能机芯片,软硬开发,测试,生产,就算全用的成熟IP,也不会少于300人,每人算10万刀的开销,量产周期两年,需要6000万刀。外加各种EDA工具,IP授权和开片费,芯片还没影子,1亿刀就下去了。

言归正传,手机上的应用,最直接的就是美颜相机,AR和语音助手。这些需求翻译成硬件指令就是对8位整数点乘(INT8)和16位浮点运算(FP16)的支持。具体怎么支持?曾经看到过一张图,我觉得较好的诠释了这一点:

智能手机和平板上,是安卓的天下,所有独立芯片商都必须跟着谷歌爸爸走。谷歌已经定义了Android NN作为上层接口,可以支持它的TensorFlow以及专为移动设备定义的TensorFlow Lite。而下层,针对各种不同场景,可以是CPU,GPU,DSP,也可以是硬件加速器。它们的能效比如下图:

可以看到,在TSMC16纳米工艺下,大核能效比是10-100Gops/W(INT8),小核可以做到100G-1Tops/W,手机GPU是300Gops/W,而要做到1Tops/W以上,必须使用加速器。这里要指出的是,小核前端设计思想与大核完全不同,在后端实现上也使用不同的物理单元,所以看上去和大核的频率只差50%,但是在逻辑运算能效比上会差4倍以上,在向量计算中差的就更多了。

手机的长时间运行场景下,芯片整体功耗必须小于2.5瓦,分给深度学习任务的,不会超过1.5瓦。相对应的,如果做到1Tops/W,那这就是1.5T(INT8)的处理能力。对于照片识别而言,情况要好些,虽然对因为通常不需要长时间连续的处理。这时候,CPU是可以爆发然后休息的。语音识别对性能要求比较低,100Gops可以应付一般应用,用小核也足够。但有些连续的场景,比如AR环境识别,每秒会有30-60帧的图像送进来,如果不利用前后文帮助判断,CPU是没法处理的。此时,就需要GPU或者加速器上场。

上图是NVidia的神经网络加速器DLA,它只有Inference的功能。前面提到在手机上的应用,也只需要Inference来做识别,训练可以在服务端预先处理,训练好的数据下载到手机就行,识别的时候无需连接到服务端。

DLA绿色的模块形成类似于固定的流水线,上面有一个控制模块,可以用于动态分配计算单元,以适应不同的网络。我看到的大多数加速器其实都是和它大同小异,除了这了几百K字节的SRAM来存放输入和权值(一个273×128, 128×128, 128×128 ,128×6 的4层INT8网络,需要70KBSRAM)外,而有些加速器增加了一个SmartDMA引擎,可以通过简单计算预取所需的数据。根据我看到的一些跑分测试,这个预取模块可以把计算单元的利用率提高到90%以上。

至于能效比,我看过的加速器,在支持INT8的算法下,可以做到1.2Tops/W (1Ghz@T16FFC),1Tops/mm^2,并且正在向1.5Tops/W靠近。也就是说,1.5W可以获得2Tops(INT8)的理论计算能力。这个计算能力有多强呢?我这目前处理1080p60FPS的图像中的60×60及以上的像素大小的人脸识别,大致需要0.5Tops的计算能力,2Tops完全可以满足。当然,如果要识别复杂场景,那肯定是计算力越高越好。

为什么固定流水的能效比能做的高?ASIC的能效比远高于通用处理器已经是一个常识,更具体一些,DLA不需要指令解码,不需要指令预测,不需要乱序执行,流水线不容易因为等待数据而停顿。下图是某小核各个模块的动态功耗分布,计算单元只占1/3,而指令和缓存访问占了一半。

但是移动端仅仅有神经网络加速器是远远不够的。比如要做到下图效果,那首先要把人体的各个细微部位精确识别,然后用各种图像算法来打磨。而目前主流图像算法和深度学习没有关系,也没看到哪个嵌入式平台上的加速器在软件上有很好的支持。目前图像算法的支持平台还主要是PC和DSP,连嵌入式GPU做的都一般。

那这个问题怎么解决?我看到两种思路:

第一种,GPU内置加速器。下图是Verisilicon的Vivante改的加速器,支持固定流水的加速器和可编程模块Vision core(类似GPU中的着色器单元),模块数目可配,可以同时支持视觉和深度学习算法。不过在这里,传统的图形单元被砍掉了,以节省功耗和面积。只留下调度器等共用单元,来做异构计算的调度。

这类加速器比较适合于低端手机,自带的GPU和CPU本身并不强,可能光支持1080p的UI就已经耗尽GPU资源了,需要额外的硬件模块来完成有一定性能需求的任务。

对于中高端手机,GPU和CPU的资源在不打游戏的时候有冗余,那么就没有必要去掉图形功能,直接在GPU里面加深度学习加速器就可以,让GPU调度器统一调度,进行异构计算。

上图是某款GPU的材质计算单元,你有没有发现,其实它和神经网络加速器的流水线非常类似?都需要权值,都需要输入,都需要FP16和整数计算,还有数据压缩。所不同的是计算单元的密度,还有池化和激活。稍作改动,完全可以兼容,从而进一步节省面积。

但是话说回来,据我了解,目前安卓手机上各种图像,视频和视觉的应用,80%其实都是用CPU在处理。而谷歌的Android NN,默认也是调用CPU汇编。当然,手机芯片自带的ISP及其后处理,由于和芯片绑的很紧,还是能把专用硬件调动起来的。而目前的各类加速器,GPU,DSP,要想和应用真正结合,还有挺长的路要走。

终端设备上还有一个应用,AR。据说iPhone8会实现这个功能,如果是的话,那么估计继2015的VR/AR,2016的DL,2017的NB-IOT之后,2018年又要回锅炒这个了。

那AR到底用到哪些技术?我了解的如下,先是用深度传感器得到场景深度信息,然后结合摄像头拍到的2维场景,针对某些特定目标(比如桌子,面部)构建出一个真实世界的三维物体。这其中需要用到图像识别来帮助判断物体,还需要确定物体边界。有了真实物体的三维坐标,就可以把所需要渲染的虚拟对象,贴在真实物体上。然后再把摄像头拍到的整个场景作为材质,贴到背景图层,最后把所有这些图层输出到GPU或者硬件合成器,合成最终输出。这其中还需要判断光源,把光照计算渲染到虚拟物体上。这里每一步的计算量有多大?

首先是深度信息计算。获取深度信息目前有三个方法,双目摄像头,结构光传感器还有TOF。他们分别是根据光学图像差异,编码后的红外光模板和反射模板差异,以及光脉冲飞行时间来的得到深度信息。第一个的缺点是需要两个摄像头之间有一定距离并且对室内光线亮度有要求,第二个需要大量计算并且室外效果不佳,第三个方案镜头成本较高。据说苹果会用结构光方案,主要场景是室内,避免了缺点。结构光传感器的成本在2-3刀之间,也是可以接受的。而对于计算力的要求,最基本的是对比两个经过伪随机编码处理过的发射模板以及接受模板,计算出长度差,然后用矩阵倒推平移距离,从而得到深度信息。这可以用专用模块来处理,我看到单芯片的解决方案,720p60FPS的处理能力,需要20GFLOPS FP32的计算量以上。换成CPU,就是8核。当然,我们完全可以先识别出目标物体,用图像算法计算出轮廓,还可以降低深度图的精度(通常不需要很精确),从而大大降低计算量。而识别本身的计算量前文已经给出,计算轮廓是经典的图像处理手段,针对特定区域的话计算量非常小,1-2个核就可以搞定。

接下去是根据深度图,计算真实物体的三维坐标,并输出给GPU。这个其实就是GPU渲染的第一阶段的工作,称作顶点计算。在移动设备上,这部分通常只占GPU总计算量的10%,后面的像素计算才是大头。产生虚拟物体的坐标也在这块,同样也很轻松。

接下去是生成背景材质,包括产生minimap等。这个也很快,没什么计算量,把摄像头传过来的原始图像放到内存,告诉GPU就行。

稍微麻烦一些的是计算虚拟物体的光照。背景贴图的光照不需要计算,使用原图中的就可以。而虚拟物体需要从背景贴图抽取亮度和物体方向,还要计算光源方向。我还没有见过好的算法,不过有个取巧,就是生成一个光源,给一定角度从上往下照,如果对AR要求不高也凑合了。

其他的渲染部分,和VR有些类似,什么ATW啊,Front Buffer啊,都可以用上,但是不用也没事,毕竟不是4K120FPS的要求。总之,AR如果做的不那么复杂,对CPU和GPU的性能要求并不高,搞个图像识别模块,再多1-2个核做别的足矣。

有了计算量,深度学习加速器对于带宽的需求是多少?如果SRAM足够大,1Tops的计算量需要5GB/s以下的带宽。连接方法可以放到CPU的加速口ACP(跑在1.8GHz的ARMv8.2内部总线可以提供9GB/s带宽)。只用一次的数据可以设成非共享类型,需要和CPU交换或者常用的数据使用Cacheable和Shareable类型,既可以在三级缓存分配空间,还可以更高效的做监听操作,免掉刷缓存。

不过,上述前提成立的前提是权值可以全部放到SRAM或者缓存。对于1TOPS INT8的计算量,所需权值的大小是512GB。如果全部放DDR,由于手机的带宽最多也就是30GB/S,是完全不够看的。对于输入,中间值和输出数据,我在上文有个例子,一个273×128, 128×128, 128×128 ,128×6 的4层INT8网络,需要70KB的SRAM(片内)放权值,共7万个。但是输入,输出和中间结果加起来却只有535个,相对来说并不大。这里的运算量是14万次(乘和加算2次)。对于1T的运算量来说,类似。中间数据放寄存器,输出数据无关延迟,只看带宽,也够。最麻烦的就是权值,数据量大到带宽无法接受。我看到的有些深度学习的算法,权值在几十到200兆,这样无论如何是塞不进SRAM的。哪怕只有10%需要读入,那也是50GB/s的带宽。我觉得,现阶段宣传的各种漂亮的跑分和图像识别速度,一旦权值太大,一定会让计算单元利用率大大下降。虽说现在有压缩算法压缩稀疏矩阵,也有人号称有几十倍的压缩率,但对于繁杂的各类应用,可能最后实际效果没比CPU好到哪里去。

如果加速器在GPU上,那么还是得用传统的ACE口,一方面提高带宽,一方面与GPU的核交换数据在内部进行,当然,与CPU的交互必然会慢一些。

在使用安卓的终端设备上,深度学习可以用CPU/DSP/GPU,也可以是加速器,但不管用哪个,一定要跟紧谷歌爸爸。谷歌以后会使用Vulkan Compute来替代OpenCL,使用Vulkan 来替代OpenGL ES,做安卓GPU开发的同学可以早点开始熟悉了。

高通推过用手机做训练,然后手机间组网,形成强大的计算力。从我的角度看,这个想法问题多多,先不说实际应用,谁会没事开放手机给别人训练用?耗电根本就吃不消。并且,要是我知道手机偷偷的上传我的图像和语音模板到别人那里,绝对不会买。

第二个市场是家庭,包括机顶盒/家庭网关(4亿颗以下),数字电视(3亿颗以下),电视盒子(1亿以下)三大块。整个市场出货量在7亿片,电器里面的MCU并没有计算在内。这个市场公司比较散,MStar/海思/博通/Marvell/Amlogic都在里面,小公司更是无数。如果没有特殊要求,拿平板的芯片配个wifi就可以用。当然,中高端的对画质还是有要求,MTK现在的利润从手机移到了电视芯片,屏幕显示这块有独到的技术。很多机顶盒的网络连接也不是以太网,而是同轴电缆等,这种场合也得专门的芯片。最近,这个市场里又多了一个智能音箱,各大互联网公司又拿出当年追求手机入口的热情来布局,好不热闹。

家庭电子设备里还有一个成员,游戏机。Xbox和PS每年出货量均在千万级别。VR/AR和人体识别早已经用在其中。

对于语音设别,100Gops的性能需求对于无风扇设计引入的3瓦功耗限制,CPU/DSP和加速器都可以选。不过工艺就得用28纳米了或者更早的了,毕竟没那么多量,撑不起16纳米。最便宜的方案,可以使用RISC-V+DLA,没有生态系统绑定的情况下最省成本。独立的加速器本身对CPU要求并不高,不像GPU那样需要支持OpenCL/OpenGL ES。8核G71@900Mhz差不多需要一个2GHz的A73来支持。并且由于OpenGL ES的限制,还不能使用小核来分担任务。而100Gops的语音处理我估计几百兆赫兹的处理器就可以了。

图像方面的应用,主要还是人脸识别和播放内容识别,不过这还没有成为一个硬需求。之前提过,0.5Tops足以搞定简单场景,4K分辨率的话,性能需求是1080p的四倍。

接下去是监控市场。监控市场上的图像识别是迄今为止深度学习最硬的需求。监控芯片市场本身并不大,有1亿颗以上的量,销售额20亿刀左右。主流公司有安霸,德州仪器和海思,外加几个小公司,OEM自己做芯片的也有。

传统的监控芯片数据流如上图蓝色部分,从传感器进来,经过图像信号处理单元,然后送给视频编码器编码,最后从网络输出。如果要对图像内容进行识别,那可以从传感器直接拿原始数据,或者从ISP拿处理过的图像,然后进行识别。中高端的监控芯片中还会有个DSP,做一些后处理和识别的工作。现在深度学习加速器进来,其实和DSP是有些冲突的。以前的一些经典应用,比如车牌识别等,DSP其实就已经做得很好了。如果要做识别以外的一些图像算法,这颗DSP还是得在通路上,并不能被替代。并且,DSP对传统算法的软件库支持要好得多。这样,DSP替换不掉,额外增加处理单元在成本上就是一个问题。

对于某些低功耗的场景,我看到有人在走另外一条路。那就是完全扔掉DSP,放弃存储和传输视频及图像,加入加速器,只把特征信息和数据通过NB-IOT上传。这样整个芯片功耗可以控制在500毫瓦之下。整个系统结合传感器,只在探测到有物体经过的时候打开,平时都处于几毫瓦的待机状态。在供电上,采用太阳能电池,100mmx100mm的面板,输出功率可以有几瓦。不过这个产品目前应用领域还很小众。

做识别的另一个途径是在局端。如果用显卡做,GFX1080的FP32 GLOPS是9T,180瓦,1.7Ghz,16纳米,320mm。而一个Mali G72MP32提供1T FP32的GFLOPS,16纳米,850Mhz,8瓦,9T的话就是72瓦,666mm。当然,如果G72设计成跑在1.7Ghz,我相信不会比180瓦低。此外桌面GPU由于是Immediate rendering的,带宽大,但对缓存没有很大需求,所以移动端的GPU面积反而大很多,但相对的,它对于带宽需求小很多,相应的功耗少很多

GPU是拿来做训练的,而视频识别只需要做Inference,如果用固定流水的加速器,按照NVIDIA Tesla P40的数据,48T INT8 TOPS,使用固定流水加速器,在16nm上只需要48mm。48Tops对应的识别能力是96路1080p60fps,96路1080p60fps视频解码器对应的面积差不多是 50mm,加上SRAM啥的,估计200mm以下。如果有一千万的量,那芯片成本可以做到40美金以下(假定良率还可以,不然路数得设计的小一点),而一块Tesla P40板子的售价是500美金(包括DDR颗粒),还算暴利。国内现在不少小公司拿到了投资在做这块的芯片。

第四个市场是机器人/无人机。机器人本身有多少量我没有数据,手机和平板的芯片也能用在这个领域。无人机的话全球一年在200万左右,做视觉处理的芯片也应该是这个量级。。用到的识别模块目前看还是DSP和CPU为主,因为DSP还可以做很多图像算法,和监控类似。这个市场对于ISP和深度信息的需求较高,双摄和结构光都可以用来算深度计算,上文提过就不再展开。

在无人机上做ISP和视觉处理,除了要更高的清晰度和实时性外,还比消费电子多了一个要求,容错。无人机的定位都靠视觉,如果给出的数据错误或者模块无反应都不符合预期。解决这个问题很简单,一是增加各种片内存储的ECC和内建自检,二是设两个同样功能的模块,错开时钟输入以避免时钟信号引起的问题,然后输出再等相同周期,同步到一个时钟。如果两个结果不一致,那就做特殊处理,避免扩散数据错误。

第五个市场是汽车,整个汽车芯片市场近300亿刀,玩家众多:

在汽车电子上,深度学习的应用就是ADAS了。在ADAS里面,语音和视觉从技术角度和前几个市场差别不大,只是容错这个需求进一步系统化,形成Function Safety,整个软硬件系统都需要过认证,才容易卖到前装市场。Function Safety比之前的ECC/BIST/Lock Step更进一步,需要对整个芯片和系统软件提供详细的测试代码和文档,分析在各类场景下的错误处理机制,连编译器都需要过认证。认证本身分为ASIL到A-ASIL-D四个等级,最高等级要求系统错误率小于1%。我对于这个认证并不清楚,不过国内很多手机和平板芯片用于后装市场的ADAS,提供语音报警,出货量也是过百万的。

最后放一张ARM的ADAS参考设计框图。

可能不会有人照着这个去设计ADAS芯片,不过有几处可以借鉴:

右方是安全岛,内涵Lock Step的双Cortex-R52,这是为了能够保证在左边所有模块失效的情况下复位整个系统或者进行异常中断处理的。中部蓝色和绿色的CryptoCell模块是对整个系统运行的数据进行保护,防止恶意窃取的。关于Trustzone设计我以前的文章有完整介绍这里就不展开了。

以上几个市场基本都是Inference的需求,其中大部分是对原有产品的升级,只有ADAS,智能音箱和服务器端的视频识别检测是新的市场。其中智能音箱达到了千万级别,其它的两个还都在扩张。

接下去的服务端的训练硬件,可以用于训练的移动端GPU每个计算核心面积是1.5mm(TSMC16nm),跑在1Ghz的时候能效比是300Gops/W。其他系统级的性能数据我就没有了。虽然这个市场很热,NVidia的股票也因此很贵,但是我了解到全球用于深度学习训练的GPU销售额,一年只有1亿刀不到。想要分一杯羹,可能前景并没有想象的那么好。

最近970发布,果然上了寒武纪。不过2T ops FP16的性能倒是让我吃了一惊,我倒推了下这在16nm上可能是6mm的面积,A73MP4+A53MP4(不含二级缓存)也就是这点大小。麒麟芯片其实非常强调面积成本,而在高端特性上这么舍得花面积,可见海思要在高端机上走出自己的特色之路的决心,值得称道。不过寒武纪既然是个跑指令的通用处理器,那除了深度学习的计算,很多其他场合也能用上,比如ISP后处理,计算结构光深度信息等等,能效可能比DSP还高些。

Categories: life is fun

用小米盒子和酷我K歌将客厅变身卡拉OK厅(解决麦克问题)

October 7, 2017 Leave a comment

用小米盒子和酷我K歌将客厅变身卡拉OK厅(解决麦克问题)

发表在玩机教程2014-07-02 15:48:15 来自老版论坛复制链接手机看帖52131582

我从来没觉得自己是个米粉,但我喜欢小米这个朝气蓬勃的公司,从小米创办开始,绝大部分小米的产品我都买过,其中最喜欢的莫过于小米盒子。简单易用,老少皆宜。自从装了电视猫视频能看电视直播后,老爸老妈干脆连电视机顶盒都不用了,每天就盯着小米盒子消磨时光。

最近无意中注意到小米盒子有个第三方的软件叫做“酷我K歌”,还有个软件叫“歌吧”,都是做KTV相关的软件。按它们自己的介绍:“酷我K歌是一款K歌必备的在线唱歌软件,海量的卡拉OK曲库,超强的练唱图谱功能,最新KTV点唱榜单,让你足不出户的在线卡拉OK,享受疯狂的K歌体验。”这个软件有电脑,手机和电视的版本,其中电视的版本界面是仿照KTV歌厅的界面,还可以和手机版本互动。

简单在小米盒子上安装了一下后发现界面非常友好,和KTV里面用的软件界面很像,而且歌很全,最新的网络歌曲全都有,非常强大。
只是,有个严重的问题,麦克风怎么输入?
这个问题看似很简单,实际问题多多,步步是坑,我经过了几天的折腾,参考了无数文档,又致电小米客服咨询,竟然没找到一篇合适我的介绍,一直不能搞定麦克风的输入。好在终于各种折腾后解决,所以写下这篇文章跟大家分享。

解决问题的逻辑:
1   最开始的我,实在把问题想得过于简单,只是想家里没有麦克风,就直接在京东买了两个购买人数最多的无线麦克风。回来就发现问题了,这个麦克风应该接在哪里呢?小米盒子没有音频输入。那肯定得接在别处,于是我参考了酷我K歌的帮助。
2   酷我K歌的帮助写的麦克风有两种接法:
a.  如果电视机上有音频输入,接在电视机的音频输入上。
b.  如果电视机没有音频输入,接在功放的MIC输入上。
说实话,这真是我看过最不靠谱的官方解答了。因为,无论是电视机还是音响功放,很少有支持麦克风混音的(除了一些国产专门标注卡拉OK的型号)。这就是说,如果你把麦克风比如接在电视上,只有在那一路输入源时才有声音。例如,我家的几台电视,麦克风的输入都是和电脑输入在一起的,只有信号源切换到电脑时,麦克风的声音才能听到。切换到常用的HDMI,就没有麦克的声音了。接到功放上问题也是相同,只有正好你的功放支持卡拉OK混音,才有可能直接插了麦克工作。很不幸,我的功放没有这么高级的功能,只好继续研究。
3   在小米盒子的论坛翻了又翻,终于知道了如果要卡拉OK,需要一个叫做卡拉OK混响器的东西。把音乐和麦克风都接在它上面,它会把两者进行混音输出,然后再接在功放或音箱上就行了。逻辑看起来很简单,但实际的问题是,到底怎么接?
4   首先看了所有的卡拉OK混响器,没有支持HDMI作为输入的,也就是说,不能直接用小米盒子的HDMI接口作为音频输入给卡拉OK混响器。那就只剩下一个办法,使用小米盒子的**输出接口。确切的说,我们需要的是:视频从HDMI输出,音频从AV线接口输出到卡拉OK混响再到音响。
5   小米盒子上这个2.5mm的输出接口,可以转成两种线材,一种是红黄白的**线,一种是转成SPDIF的莲花头。不幸的是,我的功放没有SPDIF的莲花口输入,也不知道怎么能把SPDIF转成红白音频线(所有功放都支持),事实上,因为小米盒子的音频输出需要首先接在卡拉OK混响器的音频输入上,而我所看到的所有卡拉OK混响器都不支持SPDIF。于是就只剩下华山一条路,用**线。
6   所幸功夫不负有心人,在买了卡拉OK混响器和小米2.5转**线后,最终搞定了声音的输出,彻底解决了问题。

下面,总结一下具体的步骤:

需要的材料列表:
1    小米盒子(我用的是小米盒子2代)

2    小米盒子2.5转**线。如果是小米盒子增强版,需要用的3.5mm线
不让发连接,大家到小米官网自行搜索吧

3    麦克风

这个根据你的需求随便选,调研才知道这玩意儿也可以极其发烧。

4    卡拉OK混响器

5    功放&音箱
这个就不多说了,反正各家都不相同,只要有莲花头的就可以。

安装步骤:
1   在小米盒子上安装“酷我K歌”或“歌吧”(推荐前者,后者速度太慢,bug较多),同时也可以安装两者的手机客户端,可以直接用手机遥控电视点歌。歌吧甚至号称可以用手机做麦克风(但实际没法用,人声延迟太多)
2   将小米盒子通过**线的红白两根音频线接入卡拉OK混响器的音频输入
3   将卡拉OK混响器的音频输出接入功放的音频输入。
4   将麦克风接入卡拉OK混响器

大功告成的效果图(基本完美)

音响连接

无线MIC

存在的小问题:
小米盒子的HDMI和2.5mm视频线在连接时,默认是两者共同输出的。所以我们才有机会实现这个连接方式。但有时候碰到小米盒子在结束休眠后,会自动关闭视频线的输出,只保留HDMI输出。这时,需要拔掉HDMI重启一下,机器启动后再插上HDMI,就恢复一切正常。

总结:
尽管安装操作比较复杂,但最终的效果还是相当不错的。花了几百元买了无线麦克风和卡拉OK混响器后,在家里唱歌的效果基本达到了一般KTV的水准,还是让人非常满意的。希望这篇文章对相关朋友有b

Categories: life is fun

zz 浅谈CSRF攻击方式

September 19, 2017 Leave a comment

浅谈CSRF攻击方式

2009-04-09 22:44 by hyddd, 216765 阅读, 118 评论, 收藏编辑

一.CSRF是什么?

CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。

二.CSRF可以做什么?

你这可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账……造成的问题包括:个人隐私泄露以及财产安全。

三.CSRF漏洞现状

CSRF这种攻击方式在2000年已经被国外的安全人员提出,但在国内,直到06年才开始被关注,08年,国内外的多个大型社区和交互网站分别爆出CSRF漏洞,如:NYTimes.com(纽约时报)、Metafilter(一个大型的BLOG网站),YouTube和百度HI……而现在,互联网上的许多站点仍对此毫无防备,以至于安全业界称CSRF为“沉睡的巨人”。

四.CSRF的原理

下图简单阐述了CSRF攻击的思想:

从上图可以看出,要完成一次CSRF攻击,受害者必须依次完成两个步骤:

1.登录受信任网站A,并在本地生成Cookie。

2.在不登出A的情况下,访问危险网站B。

看到这里,你也许会说:“如果我不满足以上两个条件中的一个,我就不会受到CSRF的攻击”。是的,确实如此,但你不能保证以下情况不会发生:

1.你不能保证你登录了一个网站后,不再打开一个tab页面并访问另外的网站。

2.你不能保证你关闭浏览器了后,你本地的Cookie立刻过期,你上次的会话已经结束。(事实上,关闭浏览器不能结束一个会话,但大多数人都会错误的认为关闭浏览器就等于退出登录/结束会话了……)

3.上图中所谓的攻击网站,可能是一个存在其他漏洞的可信任的经常被人访问的网站。

 

上面大概地讲了一下CSRF攻击的思想,下面我将用几个例子详细说说具体的CSRF攻击,这里我以一个银行转账的操作作为例子(仅仅是例子,真实的银行网站没这么傻:>)

  示例1:

银行网站A,它以GET请求来完成银行转账的操作,如:http://www.mybank.com/Transfer.php?toBankId=11&money=1000

危险网站B,它里面有一段HTML的代码如下:

  <img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>

首先,你登录了银行网站A,然后访问危险网站B,噢,这时你会发现你的银行账户少了1000块……

为什么会这样呢?原因是银行网站A违反了HTTP规范,使用GET请求更新资源。在访问危险网站B的之前,你已经登录了银行网站A,而B中的<img>以GET的方式请求第三方资源(这里的第三方就是指银行网站了,原本这是一个合法的请求,但这里被不法分子利用了),所以你的浏览器会带上你的银行网站A的Cookie发出Get请求,去获取资源“http://www.mybank.com/Transfer.php?toBankId=11&money=1000”,结果银行网站服务器收到请求后,认为这是一个更新资源操作(转账操作),所以就立刻进行转账操作……

示例2:

为了杜绝上面的问题,银行决定改用POST请求完成转账操作。

银行网站A的WEB表单如下:

  <form action=”Transfer.php” method=”POST”>
<p>ToBankId: <input type=”text” name=”toBankId” /></p>
<p>Money: <input type=”text” name=”money” /></p>
<p><input type=”submit” value=”Transfer” /></p>
</form>

后台处理页面Transfer.php如下:

复制代码

<?php
session_start();
if (isset($_REQUEST[‘toBankId’] && isset($_REQUEST[‘money’]))
{
buy_stocks($_REQUEST[‘toBankId’], $_REQUEST[‘money’]);
}
?>

复制代码

危险网站B,仍然只是包含那句HTML代码:

  <img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>

和示例1中的操作一样,你首先登录了银行网站A,然后访问危险网站B,结果…..和示例1一样,你再次没了1000块~T_T,这次事故的原因是:银行后台使用了$_REQUEST去获取请求的数据,而$_REQUEST既可以获取GET请求的数据,也可以获取POST请求的数据,这就造成了在后台处理程序无法区分这到底是GET请求的数据还是POST请求的数据。在PHP中,可以使用$_GET和$_POST分别获取GET请求和POST请求的数据。在JAVA中,用于获取请求数据request一样存在不能区分GET请求数据和POST数据的问题。

示例3:

经过前面2个惨痛的教训,银行决定把获取请求数据的方法也改了,改用$_POST,只获取POST请求的数据,后台处理页面Transfer.php代码如下:

复制代码

<?php
session_start();
if (isset($_POST[‘toBankId’] && isset($_POST[‘money’]))
{
buy_stocks($_POST[‘toBankId’], $_POST[‘money’]);
}
?>

复制代码

然而,危险网站B与时俱进,它改了一下代码:

复制代码

<html>
<head>

function steal()
{
iframe = document.frames[“steal”];
iframe.document.Submit(“transfer”);
}

</head>

<body onload=”steal()”>

</body>
</html>

复制代码

如果用户仍是继续上面的操作,很不幸,结果将会是再次不见1000块……因为这里危险网站B暗地里发送了POST请求到银行!

总结一下上面3个例子,CSRF主要的攻击模式基本上是以上的3种,其中以第1,2种最为严重,因为触发条件很简单,一个<img>就可以了,而第3种比较麻烦,需要使用JavaScript,所以使用的机会会比前面的少很多,但无论是哪种情况,只要触发了CSRF攻击,后果都有可能很严重。

理解上面的3种攻击模式,其实可以看出,CSRF攻击是源于WEB的隐式身份验证机制!WEB的身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的!

五.CSRF的防御

我总结了一下看到的资料,CSRF的防御可以从服务端和客户端两方面着手,防御效果是从服务端着手效果比较好,现在一般的CSRF防御也都在服务端进行。

1.服务端进行CSRF防御

服务端的CSRF方式方法很多样,但总的思想都是一致的,就是在客户端页面增加伪随机数。

(1).Cookie Hashing(所有表单都包含同一个伪随机值):

这可能是最简单的解决方案了,因为攻击者不能获得第三方的Cookie(理论上),所以表单中的数据也就构造失败了:>

  <?php
//构造加密的Cookie信息
$value = “DefenseSCRF”;
setcookie(”cookie”, $value, time()+3600);
?>

在表单里增加Hash值,以认证这确实是用户发送的请求。

复制代码

<?php
$hash = md5($_COOKIE[‘cookie’]);
?>
<form method=”POST” action=”transfer.php”>
<input type=”text” name=”toBankId”>
<input type=”text” name=”money”>
<input type=”hidden” name=”hash” value=”<?=$hash;?>”>
<input type=”submit” name=”submit” value=”Submit”>
</form>

复制代码

然后在服务器端进行Hash值验证

复制代码

<?php
if(isset($_POST[‘check’])) {
$hash = md5($_COOKIE[‘cookie’]);
if($_POST[‘check’] == $hash) {
doJob();
} else {
//…
}
} else {
//…
}
?>

复制代码

这个方法个人觉得已经可以杜绝99%的CSRF攻击了,那还有1%呢….由于用户的Cookie很容易由于网站的XSS漏洞而被盗取,这就另外的1%。一般的攻击者看到有需要算Hash值,基本都会放弃了,某些除外,所以如果需要100%的杜绝,这个不是最好的方法。
(2).验证码

这个方案的思路是:每次的用户提交都需要用户在表单中填写一个图片上的随机字符串,厄….这个方案可以完全解决CSRF,但个人觉得在易用性方面似乎不是太好,还有听闻是验证码图片的使用涉及了一个被称为MHTML的Bug,可能在某些版本的微软IE中受影响。

(3).One-Time Tokens(不同的表单包含一个不同的伪随机值)

在实现One-Time Tokens时,需要注意一点:就是“并行会话的兼容”。如果用户在一个站点上同时打开了两个不同的表单,CSRF保护措施不应该影响到他对任何表单的提交。考虑一下如果每次表单被装入时站点生成一个伪随机值来覆盖以前的伪随机值将会发生什么情况:用户只能成功地提交他最后打开的表单,因为所有其他的表单都含有非法的伪随机值。必须小心操作以确保CSRF保护措施不会影响选项卡式的浏览或者利用多个浏览器窗口浏览一个站点。

以下我的实现:

1).先是令牌生成函数(gen_token()):

复制代码

<?php
function gen_token() {
//这里我是贪方便,实际上单使用Rand()得出的随机数作为令牌,也是不安全的。
//这个可以参考我写的Findbugs笔记中的《Random object created and used only once》
$token = md5(uniqid(rand(), true));
return $token;
}

复制代码

2).然后是Session令牌生成函数(gen_stoken()):

复制代码

<?php
function gen_stoken() {
$pToken = “”;
if($_SESSION[STOKEN_NAME]  == $pToken){
//没有值,赋新值
$_SESSION[STOKEN_NAME] = gen_token();
}
else{
//继续使用旧的值
}
}
?>

复制代码

3).WEB表单生成隐藏输入域的函数:

复制代码

<?php
function gen_input() {
gen_stoken();
echo “<input type=\”hidden\” name=\”” . FTOKEN_NAME . “\”
value=\”” . $_SESSION[STOKEN_NAME] . “\”> “;
}
?>

复制代码

4).WEB表单结构:

复制代码

<?php
session_start();
include(”functions.php”);
?>
<form method=”POST” action=”transfer.php”>
<input type=”text” name=”toBankId”>
<input type=”text” name=”money”>
<? gen_input(); ?>
<input type=”submit” name=”submit” value=”Submit”>
</FORM>

复制代码

5).服务端核对令牌:

这个很简单,这里就不再啰嗦了。

上面这个其实不完全符合“并行会话的兼容”的规则,大家可以在此基础上修改。

 

其实还有很多想写,无奈精力有限,暂且打住,日后补充,如果错漏,请指出:>

PS:今天下午写这篇文档的时候FF崩溃了一次,写了一半文章的全没了,郁闷好久T_T…….

转载请说明出处,谢谢[hyddd(http://www.cnblogs.com/hyddd/)%5D

六.参考文献

[1].Preventing CSRF

[2].Security Corner: Cross-Site Request Forgeries

[3].《深入解析跨站请求伪造漏洞:原理剖析》

[4].《Web安全测试之跨站请求伪造(CSRF)》

[5].《深入解析跨站请求伪造漏洞:实例讲解》

[6].http://baike.baidu.com/view/1609487.htm

Categories: life is fun

湾区买房必看:2017年房市走势分析

September 10, 2017 Leave a comment

zz湾区买房必看:2017年房市走势分析

 2017-02-17 15:47
 moonbbs | 月下闲聊
我们是华尔街的前trader, 开了一个分析湾区房市的公众号:bayareahousingmarket, 专门分析湾区(今后也会讨论全美国的)房市投资, 以及投资中的各种理念方法等。欢迎大家订阅。

经历了09年房市大跌,11年重现生气,湾区已经经历了以palo alto为首的4个疯狂上涨的年头。和前几年相比,16年出现了很多slow down的迹象,要分析17年的房市,咱们就不得不回顾16年的房市走势。
整体表现一般,尤其是高房价地区

16年湾区房市总体表现不怎么样,很多排名都被甩出美国前10。要知道以前湾区总是稳坐冠军宝座的,排名下跌这么厉害,不知道它会不会很失落 . 16年最强烈的一个信号就是高房价区房价平稳甚至微跌,低房价区仍有大幅上涨。为了方便大家阅读理解,小编把10个湾区华人中出镜率很高的区分为两个团队 — 白富美团队 (high price area) 和土肥圆团队 (low price area)。大家先来看看两个队的队员情况:

白富美区的房价15年底均值大概是 2.3 million, 土肥圆地区15年底均价差不多是77万。相差三倍。我们来看看这两个团队在11 – 16年的增长率。

上图中,红色代表的是低价区每年的涨幅,蓝色代表的是高价区每年涨幅。哎呀,15-16年高价区怎么突然矮了那么一大截?没有增长啊!往年高价区每年都有10%以上的增长率,而16年只有0。相反,虽然低房价地区增长也不如往年,但怎么都还是有6%的增长。这种高房价地区slow down在很多人看来是一种危险的信号,人民的affordability不太跟的上了!大家实在买不起高房价地区的房子只好转战低房价区。这其实是房市的一种历史规律。好区先涨,涨到一定高峰带领差区上涨。差区因为基数小,上去的百分比更大。但大跌来临差区先跌,跌幅较猛,而好区相对坚挺,跌幅较小。
因为高房价地区疲软,16年可以算作屌丝逆袭年了。
租金回调
去年中期,各大新闻机构普大喜奔 — 美国租房最贵的三番,租金终于降了。很多有经验的投资者都用租金作为房市的market indicator, 租金下调也是一个房价危险的信号。

 

本来各大地产经纪看着16年平淡无味的房市,都告诉客人房市要降温了,估计17年会撑不住了。结果却出乎人们的意料。自从16年底Z*F决定加息以来,湾区房市突然变的火热起来。虽然阴雨连绵让被好天气宠坏了的湾区人民没法出门,但open house里人来人往,熙熙攘攘,每次去都能感受到清明上河图里那种盛景。以前半岛大部分房子都得open house两个周末才成交,12月底的时候基本都只需要一个open house,甚至有些周三上市,周五就pending了。从成交上来看,好房子20多个offer不是新鲜事,大家加价也比较放得开。看来不少童鞋都想赶在加息前搞定房子,不管怎么说,现在的利率还算是史上最低,能踏上这班车当然是最好。
可能有小伙伴要问了,那我要不要加入抢房大军,不顾一切地抢到一个房子?小编认为,不管在任何市场下,买房都要慎重。房子是大部分人人生最大的投资,而且总体说来很不liquid, 买起来容易脱手难。其实,虽然14年到15年我们看到了房价大幅度上涨,但14年疯狂加价拿下来的房子也不一定比15年低价买的房子便宜。其实每年的开春房市都会稍稍火爆一下的,我们目前看到的这种火爆有一部分是季节性因素。去年开春也有不少通稿说房市火爆了。另外,17年房市的这种热潮到底能否维持下去还有很多不确定性,小编来给大家列一列17年房价的不确定因素:
interest rate

根据现在的利率来看,如果想买一个在旧金山地区中等价位的房子(median house price  = 1.3 Million), 家庭收入差不多得17万 。 但如果利率涨到5%, 买同样的房子家庭收入得20万左右,当利率涨到6%的时候,收入得达到21.5万。所以利率上升房子就更买不起了。

affordability
湾区房价经过几年疯狂增长,到底有多少人能买的起一个旧金山地区中等价位 (median value = 130万) 的房子呢?研究证明,湾区大概有14% 的家庭能买的起。和全美国比,这个数字差了点,房价相对于人民的收入来说确实太贵了。但这个数字比07年的时候强多了,07年的时候只有8%的人能买的起中等价位的房子。所以目前的AFFORDABILITY还不算湾区最差。
这就是房价翻了翻,但人民的收入也提高了太多。最新的经纪人协会数据表明,湾区几个比较大的county 大概有6%左右的家庭收入都超过500K。大家收入很牛呀!
国内汇款政策收紧
如果党真的不让咱们寄钱投资了,那影响可能真不小。毕竟华人是湾区买房的主力军。虽然湾区的工程师们个个收入都吓死人,但说到首付不少还是得靠家里。到底我党有多大的决心整治这事现在还不太好说,我们只能静观其变。党的事情小编也不好多说。
最最最重要的,IT公司的就业形势
IT公司是湾区这几年房价跳跃式发展的第一功臣。湾区目前产业还比较单一,对IT业的依赖性很强。笔者认为,IT产业的发展会是湾区房价最重要的因素。现在要买的起湾区的中等房子(130万)大概需要家里至少有一个不错的IT收入(250K),如果IT业面临裁员,这部分人的购买能力会大受影响,也可能有部分家庭无法继续支付贷款,foreclosure数量大增导致房价跳水。相反,IT业发展了,会带来大量的高收入年轻人,目前湾区收入250K的家庭每年增长2%。这批人买房是刚需,而且买得起房。
相比13/14/15的快速发展期,16年**公司招人也有一些slow down。一些公司招人的重点也从new grads变成了experienced。相对应的,也有不少报道说独角兽公司处境比前几年艰难了许多,VC给funding也没有以前大方了。这些算是不利因素。但fb google最近在earning上的表现还算不错,year to year growth大多超过华尔街预期,所以笔者估计如果没有大的金融危机的影响,在17年这些IT公司不会有太大的问题,虽然也很难看到13年那样疯狂招人的局面。
Categories: life is fun

北极光创投邓锋:红海竞争将越来越多,而胜负取决于创始人特质


北极光创投邓锋:红海竞争将越来越多,而胜负取决于创始人特质

2017-07-15 邓锋 捕手志

 

 

有人评价,邓锋先生总能较为全面地看待一个问题,而且还能提出一些建设性意见。这与他过去的经历有着密不可分的关系,作为一位出色的投资人与成功创业者,他早年创办的NetScreen(网屏技术)在纳斯达克上市,后以42亿美金成功出售。之后他回国创办了北极光创投,成功投资了一批公司,包括美团网、汉庭酒店、WIFI万能钥匙、酷我、百合网、纷享销客等。

这一期,捕Sir要与你分享邓锋先生的一篇文章,文中他详细回顾了自己早年创办NetScreen公司的细节,分享了这些年来他在陪伴创业者成长的过程中,所观察提炼出的优秀创业者的特质,以及大多数创业者在早期都会感到困惑的一个问题。

 红海竞争将越来越多,而胜负取决于创始人特质

作者|邓锋   编辑|潘宇波

文章来源:北极光创投、盛景网联

*转载请留言后台

创业经历

我大学毕业后,自己就跑去国外读研究生,后去了Intel,做了几年工程师。1997年11月我在美国硅谷和几个清华同学一起创办了一个信息安全的企业——NetScreen,花了4年的时间将公司送上了纳斯达克,2004年以42亿美金把公司卖给了另一家大的互联网公司Juniper(瞻博网络)。

当时,互联网泡沫已经破灭了,所以我们能在较短的时间内取得这样一个成绩,还是挺不错的。到公司卖掉时,我们的产品一共销售到了110多个国家,团队大概1000多人,营业额一年有近6亿美金。

表面上看去,你可能会觉得我们整个创业过程很顺利,但实际上公司从成立到上市的4年时间中也经历了很多不为人知的波折。我们创业时,几个人一共放弃了差不多100多万美金的股票期权,所以当时我们想,只要能挣到200万美金就算值了。当公司做到1年零9个月时,有一个上市公司要来收购我们,出价是4.5亿美金,我当时挺高兴的,觉得不到2年就做到4.5亿美金,远超我们当初的设想,所以当然要卖了。

于是,我们就兴高采烈地配合人家做了尽职调查,结果谁知到了正式签合同那天对方突然不签了,我们这才发现对方已经买下了竞争对手。当时我们就傻了,公司没卖成,还把信息都告诉了别人,在员工面前觉得一点面子都没有。后来,经过一番安抚,才让我们的员工继续好好工作。

三个月后,又一家纳斯达克上市公司找我们,又是谈收购的事。

这次,我就不敢和员工讲了。对方觉得我们公司不错,出价9亿美金,我当时想:幸亏上次没卖,3个月就翻倍啊!这次我们直到尽职调查做完,签了Definitive Agreement(最终合同)后才和员工说这次收购的事。那时候虽然还没有完成交割,但在法律上等于对方已经购买了我们公司,员工也都为公司卖了个更高的价钱而高兴,但厄运又来到了。

我们都不曾想到,几乎在签合同的同时股票市场突然暴跌,而且买我们的这家公司是当时受打击最严重的公司之一。我们签了合同一个多星期后,高兴劲儿还没过去,公司股票一天就下跌了20%,签合同不到一个月,股票价值还剩不到原来的1/3,这就是说原来的9亿美金变成3亿美金。这时候所有员工都觉得,犯一次错误并不算什么,但在这么短的时间内连续犯同样的错误,实在是太糟糕了!

那时硅谷的所有公司都在到处挖人,我的每个员工手里都拿到了不少Offer,好在我原来在清华是做辅导员的,就和员工谈心,一谈就是14个小时。硅谷的高科技公司说到根本还是靠人,关键员工如果留不住,公司马上就没了。我就和员工说,虽然我们和这家公司领了结婚证,不过还没进洞房嘛,可以想办法往回退。好在收购我们的这家公司整个团队都很好,也没在这方面太为难我们,所以我们就顺利退出来了。

经过这两次收购后,我最终决定谁也不卖了,自己去上市。团队也都愿意再相信我一次,所以大家就都奔着上市的目标前进。因为我们的收入不断增长,技术产品也一直很好,当我们开始找华尔街的投行时,高盛觉得我们不错,答应带着我们上市。员工们很高兴,意气风发,又重新找回了创业的激情,但意料之外的事又发生了。

在我们还差三天就要报美国证监会的时,突然做审计的E&Y(安永)说:「抱歉,我们不能给你们出好的审计报告,你们公司有实质性的弱点。虽然你们的业绩非常不错,但是财务一塌糊涂,管理极其混乱。」高盛一看就说那算了,他们也选择不带我们上了,这使得我又没办法和员工交代了。

在硅谷,员工真的是老大,在这种情况下,我们也得和员工交代。我们开掉了财务部门所有的人,重新招人,这是一件很痛苦的事,我们花了一年的时间才把财务给调整过来。然后我们又和高盛聊了一次,高盛认为我们公司真不错,这么大的问题很快都改过来了,而且现在一切变得极其规范,所以愿意和我们合作。

很快一切准备就绪,马上就要上市了,但谁知道又突然发生了9·11,纳斯达克从5000点跌到1400点。可是已经和员工说好了上市,上市的路线图都设计好了,没办法我们只能咬着牙,甭管是不是9·11该上还得上。

2001年12月11号,公司终于上市了。

我们是9·11之后第一家上市的公司,所有华尔街投资经理没别的公司可投,就我们这么一家,所以我们得到了很多投资,最后竟有了30倍的超购,当天公司的市值达到了26亿美金。这里我想说,有些企业你不要看表面,说4年时间成长又快又好,但这中间它一定经历了各种的起起伏伏。

创业,有时候真的是靠技术、产品、聪明、勤奋;有时候看不清楚,靠的就是信念,外加一些运气,总之是各个方面糅合在一起的结果。很多时候,我们都不能预料到明天公司又会发生什么事情,但我们必须要一步步往前

 成功者的特质

现在大家都把企业做多大、有没有上市、收入利润多少、创始人有多少钱当成是一个成功创业者的标准,其实不尽然。我看到有很多企业做得很大,但创始人未必很幸福,也没有得到社会的尊重;也有很多我认为他的企业没做得很大,也没有多么赚钱,但是他的心态很好,大家很认可他。

这些年,我也在想如何定义一个企业的成功?

后来,我认为要做一个成功的企业就要看你是否能平衡好与客户、员工、投资人这三方的关系,并使得他们与你合作能感到快乐。如果只是为了赚钱,虽然能给股东带来不错的回报,但这未必能让员工感到快乐。如果企业能够提供客户所喜欢的服务或产品,在业界有品牌能得到尊重,公司的员工流失率很低,大家感觉工作环境是愉快的,每个人不仅能获得物质上的回报还有精神上的满足,在我看来这会是一家成功的企业,不管它规模大小、上市与否

一个企业做到最后就是人的问题,而能解决这个问题的只有创始人,他(她)对企业的发展最为重要。投资人看企业的时候,最主要的就是看企业的创始人,毕竟中国的商业环境正在逐渐成熟,之前靠一些特权、批文,可以走在灰色地带,但这种赚钱的机会将越来越少,越来越多的机会是在阳光下、在成熟市场的环境下竞争,红海竞争的局面也会越来越多。

在这种情况下创始人的重要性将会更为突出,创始人的特质也会更重要。我个人认为有些特质是在做企业之前必须有的,如果你做企业之后再去培养的话已经晚了。比如,我们世界观的形成、思维方式、做事行为方式,这其实是在自己年轻的时候就已经基本形成了。所以,投资人大多数的时候是在选人而不是在培养人,以下是我理解的成功创业者所具备的特质:

1)要具备快速学习的能力。大多数创业者刚开始创业时,可能只有一方面很强,其它方面都能可能是缺点。做早期投资,也存在两种不同的思维方式:一种是看这个创业者有多少缺点;一种看这个创业者有多少优点。

创业者能走多远多快,决定着企业能做多大。比如说一个工程师技术能力很强,没有管理团队的能力,没有用技术去定义一款产品的能力,没有做市场营销的能力,更谈不到去制订战略,在一个大公司里训练很多年然后出来创业也未必能成功。真正好的创业者从跨国企业中高管出来的很少,大多数都是草根创业,在学习中不断挑战自我,变成技术的观察者或管理营销专家

2)具备持续创新的能力非常重要。如果一个企业没有创新的基因,企业是很难做得很远,也很难做大的。有些创业者认为自己的创意属机密,其实好的点子不能成就一个企业,一个伟大的企业一定要不断的创新,如果建立不了壁垒,别人可以很快超越你。创新是做出优秀企业非常重要的基因。

3)创业者要具备「赢」的雄心。创业者一定要有雄心,一定要敢于挑战,不惧怕权威,甚至面对大企业要有必胜的信念。胆子要大,要有野心。如果没有「我要赢」这种雄心,碰到困难就往后缩,企业是做不了的。

4)创业者要有理想。理想不等于激情,激情可能能持续一年或者更长时间,但未必能坚持很长时间。一些优秀创业者的理想不是「我要赚钱成为中国的首富」,他们都有着超越物质财富回报的理想,之所以选择创业完全来自于他们强烈的兴趣爱好。

5)要能坚持不懈。马云讲过今天很残酷,明天更残酷,后天很美好。有时候企业要靠你的才干往前走,有时候靠运气,创业者总要能坚信些什么才行,毕竟周围有很多诱惑,能够坚持下来最后做成的概率会高一些。在企业最困难的时候没有掉头,坚持往下做,最后做出来了,今天中国最优秀的创业者都是这样的。

6)具备责任感。创业者能够坚持、能够成功,很重要的是来自于他的责任。对家人、亲人、朋友、员工及投资人的责任,上下游可能也是依靠这个企业成长而成长的,创业者必须要付起这个责任,这件事不干好对不起他们。

7)胸怀是最终成事的重要因素。我们最不愿意投资自私的创业者,把自己利益放在企业之上,这样的企业发展再好,技术再创新也不投,因为它绝对是做不成长久的。创业者学习能力可以不断培养,胸怀等特质是培养不了的。

8)有智商还要有情商。企业做得越大情商越高。真正优秀的创业者具有很强的领导力,他不是技术专家,不是营销专家,但他有很向心的领导力把这些人吸引过来,把企业做大做强。

在公司运营当中,虽然你也不是公司的每个战略决定都要告诉员工,但你一定要将自己的真实想法与团队分享,让他们和你同喜同忧。千万别装酷,别装得自己什么都行。我们公司经营4年就成功上市了,这个过程并不是一帆风顺,而是经历了三起三落。低谷的时候我会和员工开诚布公,对他们说,很抱歉,我们真的犯错误了。

 

融资后的六个注意事项

2005年,我从硅谷回国,创立了北极光创投。截至目前,我们有幸与很多优秀的创业者合作,在陪伴创业者成长的过程中,我们发现一个相对明显的问题——有不少创业者,在拿到天使轮或者Pre-A轮融资后,作为企业的创始CEO或创始团队成员,不知道应该注意些什么?其实,我认为六点是值得很多创业者注意的:

1)现金流

在当前的市场环境下,这尤其是CEO的第一大要务。有很多本身不错的公司,因为花钱速度太快,结果自己败在了现金流失控的问题上。再好的企业,如果现金流突然断了,团队也没有办法稳定。

创始人对公司员工负责、对公司股东负责的首要任务是把公司的现金流控制好。当现金还有六个月就要花完的时就要开始注意了,尤其是当公司只够三个月的时,公司的处境就更危险了。CEO要保证哪怕公司没有融到资,还能运转至少半年,把控好公司的现金流,这个说多少遍都不为过。

2)

创业者一旦拿到钱后,从第一天起就要把注意力放在人身上。做企业最关键是建立一个组织,这其中,人是最重要的。招人、留人、用人、培养人都很重要。招人是CEO最重要的工作,创始人要评估自己每周有多少时间花在招人和与员工的沟通上。

时间的分配是衡量自己聚焦点的好方法。判断在人上的聚焦够不够的标准,就是统计自己在人上花了多少时间。我认为花在招人、与内部员工沟通、与外部客户沟通的时间应该占到一个CEO的50%以上的时间

3)执行力和速度

执行力是能把想的事情在多快的时间内执行到位,而且要能看得出效果。最怕的是事情没做成,但却不知道自己是战略错误还是执行不到位

速度也是一个很关键的判断执行力的指标,就像前面我们说时间分配是衡量聚焦点的重要指标。速度的重要性不是说销售成长快,而在于一个事情能不能解决,解决的到不到位。小企业要以快取胜,这里的快不是销售产生的速度,而是事情执行到位的速度

4)产品

公司早期要把产品放在第一位,而不是技术也不是营销(Marketing)。所谓做产品,最关键的是对客户需求的不断定位,找到客户的需求,把自己的资源和客户进行匹配,在很快的时间内把产品做出来。

在中国创业,技术很重要,但很多技术导向型的企业一定不是靠技术来做生意的,尤其是一些To B方向的企业,这是一个强关系型的生意,要知道买你产品的决策人是谁。比如,面向大企业的业务一定是部门做决策的,那影响他们决策的因素是什么,这些都是你在做产品的时候需要想到的。

归根结底,作为公司的老大,在一开始的阶段对产品的关心要高于对技术的关心,以及对市场营销的关心。你要能将销售和营销分开来看,产品刚出来的时候要重视Sales、轻视Marketing。

5)企业文化

很多创业者在初期不太注意这一点,其实企业文化从企业早期就逐渐形成了。如果早期有意培养了优秀的企业文化,那么在企业文化内核确定了之后,只要进新人的速度不是太快,公司文化就会沿着这个方向发展。

创始人应该好好想一想公司企业文化的这个「核」是什么。各家有各家的选择,但我要特别提醒一下,企业文化不是靠口号宣传出来的,是要靠创始人在点点滴滴的小事儿上实际出来的。比如,如果你的企业文化是关爱员工,那么一个新员工加入后,你能不能第一次见面就叫出他(她)的名字。企业文化的本质是创始人领导力的体现,创始人是什么样的人,创办的企业就会是什么模样的。

6)融资能力

创业者的融资能力正在变得很重要,这是我不愿意看到但在中国市场正在发生的事实。今天的中国VC市场上,创业者可能是需要一点忽悠的能力,但这个度怎么把握确实很难说清楚。很多企业级服务和医疗项目的创业者之前是工程师、产品经理,都不知道怎么宣传自己和公司,但作为创始人你要学会怎么把自己的公司和产品从更高的角度、用更大的概念讲好。不管我们喜欢不喜欢,融资的时候不会讲故事就会吃亏

Categories: life is fun

Natural Language Pipeline for Chatbots


Natural Language Pipeline for Chatbots

Chatbot developers usually use two technologies to make the bot understand the meaning of user messages: machine learning andhardcoded rules. See more details on chatbot architecture in my previous article.

Machine learning can help you to identify the intent of the message and extract named entities. It is quite powerful but requires lots of data to train the model. Rule of thumb is to have around 1000 examples for each class for classification problems.

 

If you don’t have enough labeled data then you can handcraft rules which will identify the intent of a message. Rules can be as simple as “if a sentence contains words ‘pay’ and ‘order’ then the user is asking to pay for an order”. And the simplest implementation in your favorite programming language could look like this:

def isRefundRequest(message):
    return 'pay' in message or 'order' in message

Any intent classification code can make errors of two types. True positives: the user doesn’t express an intent, but the chatbot identifies an intent. False positives: the user expresses an intent, but the chatbot doesn’t find it. This simple solution will make lots of errors:

  1. The user can use words “pay” and “order” in different sentences: “I make an order by mistake. I won’t pay.”
  2. A keyword is a substring of another word: “Can I use paypal for order #123?”
  3. Spelling errors: “My orrder number is #123. How can I pay?”
  4. Different forms of words: “How can I pay for my orders?”

 

Your chatbot needs a preprocessing NLP pipeline to handle typical errors. It may include these steps:

  1. Spellcheck

Get the raw input and fix spelling errors. You can do something very simple  or build a spell checker using deep learning.

  1. Split into sentences

It is very helpful to analyze every sentence separately. Splitting the text into sentences is easy, you can use one of NLP libraries, e.g. NLTK, StanfordNLP, SpaCy.

  1. Split into words

This is also very important because hardcoded rules typically operate with words. Same NLP libraries can do it.

  1. POS tagging

Some words have multiple meanings, for an example “charge” as a noun and “charge” as a verb. Knowing a part of speech can help to disambiguate the meaning. You can use same NLP libraries, or Google SyntaxNet, that is a little bit more accurate and supports multiple languages.

  1. Lemmatize words

One word can have many forms: “pay”, “paying”, “paid”. In many cases, an exact form of the word is not important for writing a hardcoded rule. If preprocessing code can identify a lemma, a canonical form of the word, it helps to simplify the rule. Lemmatization, identifying lemmas, is based on dictionaries which list all forms of every word. The most popular dictionary for English is WordNet. NLTK and some other libraries allow using it for lemmatization.

  1. Entity recognition: dates, numbers, proper nouns

Dates and numbers can be expressed in different formats: “3/1/2016″, “1st of March”, “next Wednesday”, “2016-03-01″, “123″, “one hundred”, etc. It may be helpful to convert them to unified format before doing pattern matching. Other entities which require special treatment: locations (countries, regions, cities, street addresses, places), people, phone numbers.

  1. Find concepts/synonyms

If you want to search for a breed of a dog, you don’t want to list all the dog breeds in the rule, because there are hundreds of them. It is nice if preprocessing code identified a dog breed in the message and marked the word with  a special tag. Then you can just look for that tag when applying the rule.

WordNet can be used to identify common concepts. You may need to add domain specific concept libraries, e.g. a list of drug names if you are building a healthcare bot.

 

After preprocessing is done you have a nice clean list of sentences and lists of words inside each sentence. Each word is marked with a part of speech and concepts, and you have a lemma for every word. The next step is to define patterns for intent identification.

You can invent your own pattern language using common logical operators AND, OR, NOT. The rule can look like this if you create an internal DSL (domain-specific language) based on Python:

r = Rule(
    And(
        Or('cancel', 'close'),
        'membership',
    Respond('Would you like to cancel your membership immediately?'))

Alternatively, you can invent external DSL, which can be more readable, but you will need extra work to create a compiler or an interpreter for that language. If you use a ChatScript language, it can look like this:

u: (<<[cancel close] membership>>)
    Would you like to cancel your membership immediately?

Do you use a chatbot engine with hardcoded rules? Have your developed your own? What issues have you encountered when building or using a chatbot engine? Please share in comments!

Categories: life is fun