完全没有准备的一次面试……意外

两部分:Word2vec + 中位数 (还有聊对科研的想法和自己研究能力的评价?

word2vec

//解释模型
原本是one-hot,存在缺点:稀疏和无法表现语义,词与词之间无法计算距离 //CBOW和skip-gram简要介绍一下是什么
CBOW模型的训练输入是某一个特征词的上下文相关的词对应的词向量,而输出就是这特定的一个词的词向量。
Skip-Gram模型和CBOW的思路是反着来的,即输入是特定的一个词的词向量,而输出是特定词对应的上下文词向量。 //这两种算法如何选择
CBOW对小型数据比较合适,skip-gram在大型语料中表现更好 //word2vec的损失函数是什么
NCE-loss: NCE的主要思想是,对于每一个样本,除了他自己的label,同时采样出N个其他的label,从而我们只需要计算样本在这N+1个label上的概率,而不用计算样本在所有label上的概率。
峰哥教我是负采样?使用所谓的“负采样”(negative sampling)来改进优化对象,这将造成每一个训练的样本只会更对模型权重的很小一个比例的更新。
//瞎扯了交叉熵
因为上面的问题回答错了嘛……sigh
信息熵 + 相对熵= 交叉熵
K-L散度:就是相对熵,是一种量化两种概率分布P和Q之间差异的方式。
换句话说,KL散度计算的就是数据的原分布与近似分布的概率的对数差的期望值。

如何找出一个整数数组的中位数:

//最简单的思路
排序,quicksort然后找中位数【nlogn】 //quicksort的最差情况
时间复杂度【n^】就是每次选定的数都是最大或者最小 //改进一点的找中位数的做法
partition法 ,利用快排关键字的查找方法
a.随机选取一个关键字key,将序列二分;
b.若关键字的下标大于N/,则继续对序列的左半部分执行partition;
c.若关键字的下标小于N/,则继续对序列的左半部分执行partition;
d.若关键字的下标等于N/,则返回key。
这种算法:partition的时间复杂度为【O(n)】因为是n+n/+n/+n/+…所以是O(n)
取中位数的时间复杂度是【O()】
简单解释就是:每次会丢掉一半 //堆
原理分析:
中位数无非就是将序列分为两个部分,左边的部分都小于中位数,右边的序列都大于中位数。这比较符合堆的特性(看看数据结构在算法中的重要性,选择好的数据结构能够让算法事半功倍)。可以将序列分成两个部分,左边的部分够着大根堆,右边的部分构造小根堆。
具体实现细节:
a.如果堆中元素的个数为偶数时,将新数字插入小根堆中(插入后堆元素的个数为奇数,此时结束插入,返回小根堆堆顶元素);如果堆中的元素个数为奇数时,将新数字插入大根堆中(插入后堆元素的个数为偶数,此时结束插入,返回两堆堆顶元素的均值)。
b.若插入小根堆的元素大于大根堆堆顶的元素,说明新元素位于序列的右半部分,应当插入大根堆。而此时大根堆堆顶元素应当位于左半序列(小顶堆)中,因此需要将大根堆堆顶元素插入小根堆。若插入若插入小顶堆的元素不大于大顶堆堆顶的元素,则直接插入小根堆。
c.同理,向大根堆插入元素时也有如上考虑。
调整堆的时间复杂度为【O(logn)】,取中位数的时间复杂度为【O()】。 参考:https://blog.csdn.net/cpu_12593/article/details/48231213
面试题41

partition函数找中位数的写法(做递归之前要判断):

求第k大数的时候,pivot的不满足条件的那一侧数据不需要再去处理了,平均时间复杂度为O(N+N/2+N/4+...)=O(N)。而快排则需要处理,复杂度为O(nlogn)。

public int findMiddleByPartition(int[] array, int left, int right) {
int i = left, j = right;
int key = array[left];
while(i < j) {
// j向左走,直到找到一个小于key的数
while(i < j && array[j] >= key) j--;
if(i < j) {
array[i] = array[j];
i++;
}
// i向右走,直到找到一个大于key的数
while(i < j && array[i] <= key) i++;
if(i < j) {
array[j] = array[i];
j--;
}
}
array[i] = key;
if(i == array.length / 2) {
return i;
}else if(i < array.length / 2){
return findMiddleByPartition(array, i + 1, right);
}else {
return findMiddleByPartition(array, left, i - 1);
} }

  

堆排序的方法,自己的语言复述:

将数据平均分配到构建的最大堆和最小堆中,为了实现平均分配,可以在数据的总数目是偶数的时候把新数据插入最小堆,否则插入最大堆。

为了保证最大堆中的所有数据都小于最小堆中的数据,当数据的数目是偶数的时候,先把这个新的数据加入最大堆,然后把最大堆中的最大数字拿出来插入最小堆,反之当数据中的数目是奇数的时候,先把这个新的数据加入最小堆,然后把最小堆中的最小值加入最大堆。

template<typename T> class DynamicArray{
public:
  void Insert (T num){
    if ((min.size() + max.size())& == ){    //这是偶数
      if (max.size()> && num <max[]){  
        max.push_back(num);       //加入最大堆
        push_heap(max.begin(). max.end(), less<T>());
        num = max[];          //取max的最大
        pop_heap(max.begin(), max.end(), less<T>());
        max.pop_back();
      }
      min.push_back(num);        //将max的最大放入min
      push_heap(min.begin(),min.end(),greater<T>());
    }
    else{
      //类似上面的部分,只是加入最小堆,把最小堆的最小取出来,放入最大堆
    }
  }
  
  T GetMedian(){      //返回中位数
    int size = min.size() + max.size();
    if (size == )
      throw exception("No numbers are available");
    T median = ;
    if ((size &)==)
      median = min[];
    else
      median = (min[]+max[])/;
    return median;
  } private:
  vector<T> min;
  vector<T> max;  //用vector实现堆!!!
}

  

附加题:如果数据非常多,要确定准确的中位数,应该怎么做?

这里,采用基于二进制位比较 和 快速排序算法中的“分割思想”来寻找中位数。具体如下:

假设10亿个数字保存在一个大文件中,依次读一部分文件到内存(不超过内存的限制:1GB),将每个数字用二进制表示,比较二进制的最高位(第32位),如果数字的最高位为0,则将这个数字写入 file_0文件中;如果最高位为 1,则将该数字写入file_1文件中。【这里的最高位类似于快速排序中的枢轴元素】

从而将10亿个数字分成了两个文件(几乎是二分的),假设 file_0文件中有 6亿 个数字,file_1文件中有 4亿 个数字。那么中位数就在 file_0 文件中,并且是 file_0 文件中所有数字排序之后的第 1亿 个数字。

【为什么呢?因为10亿个数字的中位数是10亿个数排序之后的第5亿个数。现在file_0有6亿个数,file_1有4亿个数,file_0中的数都比file_1中的数要大(最高位为符号位,file_1中的数都是负数,file_0中的数都是正数,也即这里一共只有4亿个负数,排序之后的第5亿个数一定是正数,那么排序之后的第5亿个数一定位于file_0中)】。除去4亿个负数,中位数就是6亿个正数从小到大排序之后 的第 1 亿个数。

现在,我们只需要处理 file_0 文件了(不需要再考虑file_1文件)。对于 file_0 文件,同样采取上面的措施处理:将file_0文件依次读一部分到内存(不超内存限制:1GB),将每个数字用二进制表示,比较二进制的 次高位(第31位),如果数字的次高位为0,写入file_0_0文件中;如果次高位为1,写入file_0_1文件 中。

现假设 file_0_0文件中有3亿个数字,file_0_1中也有3亿个数字,则中位数就是:file_0_0文件中的数字从小到大排序之后的第1亿个数字。

抛弃file_0_1文件,继续对 file_0_0文件 根据 次次高位(第30位) 划分,假设此次划分的两个文件为:file_0_0_0中有0.5亿个数字,file_0_0_1中有2.5亿个数字,那么中位数就是 file_0_0_1文件中的所有数字排序之后的 第 0.5亿 个数。

......

按照上述思路,直到划分的文件可直接加载进内存时(比如划分的文件中只有5KW个数字了),就可以直接对数字进行快速排序,找出中位数了。当然,你也使用“快排的分割算法”来找出中位数(比使用快速排序要快)

总结:上面的海量数据寻找中位数,其实就是利用了“分割”思想,每次将 问题空间 大约分解成原问题空间的一半左右。(划分成两个文件,直接丢弃其中一个文件),故总的复杂度可视为O(logN)

05-11 17:49