第一次内招面试惨不忍睹无所谓了大不了就当转行AI infra的开始1.从0实现链表 写不出来一直用AI写真是全忘了。。2. 深度学习小白 transformer 讲不出来 最重要的MHA 多注意力机制不懂3. 二作强化学习论文没有讲清楚优化里的最大熵问题 MPDQN比牛顿迭代法好在哪。后续主动和面试官说准备一下他说周一再战。。。目标1. 重新捡起手写代码能力 C2. 学习transformer3. 有时间的话 看看那篇强化学习论文讲明白都做了什么6.4日整体目标1. C基础语法复习 :类和对象 看了类和结构体的定义构造函数析构函数拷贝构造函数链表 从0 开始写了两遍vector语法setunordered_map2. transformer宏观入门快速过一遍RNN视频阅读 Jay Alammar 的图解文章《The Illustrated Transformer》中文版。重点理解它为什么比以前的 RNN 强并行计算、全局视野以及 Encoder-Decoder 的大致分工。C C语法1. 指针和引用引用和指针的区别1. 不存在空引用 必须连接到合法内存2. 一旦引用被初始化为一个对象 那么就不可以再指向到另一对象指针可以随时改变指向3. 引用必须在创建时被初始化指针不需要4. 引用的对象是一个变量指针是一个地址2. 结构体和类基本一致只是访问权限的区别struct ListNode { int value; ListNode* next; ListNode(int v):value(v),next(nullptr){} };3. 类 用结构体和类实现链表例子class用于管理链表再class中再定义结构体从零开始构建链表#includeiostream #includestring using namespace std; class MyList { public : struct ListNode { int value; ListNode* next; ListNode(int v):value(v),next(nullptr){} }; MyList(){ _dummyhead new ListNode(0); len 0; } ~MyList(){ delete _dummyhead; } int get(int index){ if(index len || index 0)return -1; ListNode* cur _dummyhead - next; while(index--){ cur cur - next; } return cur-value; } void addathead (int v){ ListNode* cur _dummyhead; ListNode* newnode new ListNode(v); newnode-next cur - next; cur-next newnode; len; } void addattail (int v){ ListNode* cur _dummyhead; while(cur-next ! nullptr){ cur cur-next; } ListNode* newnode new ListNode(v); cur-next newnode; len; } void addatindex(int index , int v){ if(index len || index 0)return; ListNode* newnode new ListNode(v); ListNode* cur _dummyhead; while(index--){ cur cur-next; } ListNode* tmp cur-next; cur-next newnode; newnode-next tmp; len; } void deleteatindex(int index){ if(index len || index 0)return; ListNode* cur _dummyhead; while(index--){ cur cur-next; } cur -next cur-next-next; len--; } private: ListNode* _dummyhead; int len; }; int main(){ MyList myl; myl.addathead(1); myl.addattail(2); myl.addatindex(1,3); coutmyl.get(1)endl; myl.deleteatindex(1); coutmyl.get(1)endl; return 0; }对象成员函数写在里面不用 加上符号写在外面要 加上 类名构造函数和类名一致可以使用成员列表方式构造 在创造对象时可以初始化成员变量内部可以调用成员函数析构函数类名之前多了个 在析构函数一般用来释放内存啥的拷贝构造4. vectorvectorint v1(4) //长度为4的数组 vectorint v2(4,0)//长度为4元素均为0的数组 vectorint v3 {1,2,3,4}//用初始化列表 //给二维数组初始化留个地方 // v1.push_back(1) //尾部添加元素 v1.pop_back() v1.size() //大小 v1.resize(4,0)//重新调整成长度为4元素为0 的数组但好像一般只有一个参数来调整长度5.二叉树 使用类和结构体 实现一个简单的二叉树包括前中后序递归遍历和层序遍历方式复习了递归的基本写法层序遍历基本写法。#include iostream #include vector #include queue using namespace std; struct TreeNode{ int value; TreeNode* left; TreeNode* right; TreeNode(int v):value(v),left(nullptr),right(nullptr){} }; class BinTree{ private: TreeNode* root; void clear(TreeNode* c){ if(c NULL) return; TreeNode* tmp c; clear(c-left); clear(c-right); delete tmp; } void preOrder(TreeNode* c){ if(c nullptr)return; coutc-value ; preOrder(c-left); preOrder(c-right); return; } void inOrder(TreeNode* c){ if(c nullptr)return; inOrder(c-left); coutc-value ; inOrder(c-right); return; } void postOrder(TreeNode* c){ if(c nullptr)return; postOrder(c-left); postOrder(c-right); coutc-value ; return; } public: BinTree(){ buildsimpletree(); coutcreateendl; } ~BinTree(){ clear(root); coutclearendl; } void buildsimpletree(){ root new TreeNode(1); root - left new TreeNode(2); root - right new TreeNode(3); root - left -left new TreeNode(4); root - left -right new TreeNode(5); } //外部接口 void preOrder(){ preOrder(root); coutendl; } void inOrder(){ inOrder(root); coutendl; } void postOrder(){ postOrder(root); coutendl; } vectorvectorintlayerOrder(){ vectorvectorintresult; queueTreeNode* q; if(!root)return{}; q.push(root); while(q.size() ! 0){ vectorintl; int size q.size(); for(int i 0;isize;i){ TreeNode* cur q.front(); l.push_back(cur - value); q.pop(); if(cur-left ! nullptr)q.push(cur-left); if(cur-right ! nullptr)q.push(cur-right); } result.push_back(l); for(int num : l){ coutnum ; } coutendl; } return result; } }; int main(){ BinTree bt; bt.preOrder(); bt.inOrder(); bt.postOrder(); bt.layerOrder(); return 0; }6.medium题练手二分数组哈希栈队列 链表动态规划两道简单的7.set 和unordered_map 复习set 存储了一组唯一的元素且按一定顺序存储的。使用红黑树实现的unordered_map没有顺序方便查找set类型s; s.insert元素); s.erase(元素); s.find(元素);// 返回对应位置迭代器如果不存在返回s.end() s.size();//返回大小 s.empty();//返回是否为空 unordered_mapkey类型,value类型m;8. 递归思路总结确定参数和返回值确定终止条件确定单层递归逻辑9.动态规划思路dp数组以及下标的含义递推公式dp数组的初始化遍历顺序打印dp数组图解transformer李沐中文版链接https://zhuanlan.zhihu.com/p/219714713https://www.bilibili.com/video/BV1pu411o7BE/?buvidYD409ED1AA281F7048B582E641079E1E7B6Bfrom_spmidsearch.search-result.0.0is_story_h5falsemidA9%2FUnMMCMAA6qPn1agLbfA%3D%3Dplat_id116share_fromugcshare_mediumiphoneshare_platiosshare_session_idB80A17D2-DC91-4B84-AC10-B2DE426F83A6share_sourceWEIXINshare_tags_ispmidunited.player-video-detail.0.0timestamp1779847636unique_kMmfuc4pup_id1567748478vd_source0cd5e9ebf84cd3a1dec858a3b424edfb看的过程中一些问题记录Transformer是什么transformer是一种基于Attention的序列建模架构通过self- attention建立不同位置之间的关系并利用位置编码补充顺序信息从而实现更强的并行能力和更好的长距离依赖建模。1. embedding是什么有必要深入了解吗将文字转变为向量能捕捉语义关系在数学向量空间中 意思相近的词向量间距离也越近在把单词变为embedding向量后模型会给这些向量加上位置编码 告诉模型这个单词在句子中的位置总结输入文字经过embedding变成了带有语义的向量最后加上了位置编码再送入编码器2. transformer究竟解决了什么问题之前主流的RNN要串行处理文本过长时会遗忘开头内容难以捕捉远距离词汇的关联但transformer引入自注意力机制一次性看到任意两个词之间的关联度无论两个词相距多远都能快速建立联系解决了算得慢无法并行的问题除了文本翻译外还高度通用能进行视觉音频NLP等领域的处理3. Self-Attention是什么将query 和一堆key-value对映射成一个输出的一个函数想通过三个线性层或者说权重矩阵将输入的embedding向量转换成Q,K,V对于序列中的每一个token的输出结果是实用他的Query和自身以及其余的key进行scaled dot-production并将结果放入softman函数得到权重系数在乘value权重系数表示当前token和句子中每一个token间的之间的关联度对应当前token的输出结果是根据注意力权重系数把所有token的特征加权融合后的新特征向量。4. 为什么要使用多头注意力使用多头注意力主要为了提升模型捕捉信息的能力捕捉多种依赖关系 不同的注意力头可以捕捉到不同位置之间不同依赖关系提高模型容量使模型能学习到更复杂表示提升范化能力并行计算提升训练和推理的效率多头注意力 假设使用八个对于每一个注意力头是将embedding向量用不同的W矩阵降维成d/h长度 的Q,K,V向量最后的得到d/h长度的输出将八个注意力头的输出通过权重矩阵拼接起来作为整个自注意力层的输出。公式可见论文果只使用一个头模型的表达能力和学习能力会受到限制可能无法充分捕捉到输入数据中的复杂依赖关系从而影响模型的性能。多头注意力机制通过组合多个注意力头的输出能够提供更丰富、更强大的表示4.1 为什么在使用多头注意力时要进行降维降低计算量 每个头工作在较低维度注意力的计算复杂度降低每个注意力头在低维空间中可以专注捕捉特定类型的特征或模式缓解高维向量空间直接计算点积导致数值容易变得很大的现象。可能会导致Softmax进入梯度较小的饱和区配合scaled缩放因子使用5. 为什么需要位置编码因为注意力机制不携带位置信息6. 残差连接和layernorm的作用整体公式LayerNorm(xSubLayer(x))残差连接帮助避免了深度神经网络中的梯度消失问题层归一化是在模型的训练过程中加速收敛的一种技术它对层的输入进行归一化处理使得其均值为0方差为16.1 残差结构意义缓解梯度消失提升训练速度允许信息直接传递增强网络能力保持前向信息的完整性6.2 LayerNorm25:07https://www.bilibili.com/video/BV1pu411o7BE/?buvidYD409ED1AA281F7048B582E641079E1E7B6Bfrom_spmidsearch.search-result.0.0is_story_h5falsemidA9%2FUnMMCMAA6qPn1agLbfA%3D%3Dplat_id116share_fromugcshare_mediumiphoneshare_platiosshare_session_idB80A17D2-DC91-4B84-AC10-B2DE426F83A6share_sourceWEIXINshare_tags_ispmidunited.player-video-detail.0.0timestamp1779847636unique_kMmfuc4pup_id1567748478vd_source0cd5e9ebf84cd3a1dec858a3b424edfb7.在解码器的第一层是masked self-attention 层这个masked是什么意思如何进行mask的或者说解码器和编码器中的self attention层有什么区别/ transformer中的三种自注意力层Encoder的自注意力层能完整查看 输入序列每个位置都可以自由地注意序列中的所有其他位置。这意味着计算注意力分数时并没有位置上的限制。Decoder 有两层注意力层首先是 masked self -attention层为了保证输出时的自回归性生成当前词只依赖于前面的词要确保在计算注意力分数时每个位置只计算它之前的位置。为了实现这一点使用序列掩码。在计算注意力分数之前将当前位置分数以及之后位置分数设为无穷大的负数这样在接下来的softmax操作中这些位置的注意力权重为0紧接着第二层是 encoder-decoder attention层这一层的 kv矩阵来自编码器的输出q矩阵来自于解码器masked self-attention 层的输出。这种机制使Decoder能够关注输入序列的不同部分特别是在生成每个新单词时。例如在机器翻译任务中当模型生成目标语言的一个单词时它可以通过这种机制来聚焦于源语言句子中的相关部分8. 前馈神经网络Feed-Forward Neural Network, FFN前馈神经网络的意义是语义空间的转换。是自注意力层后的重要组成部分每个前馈神经网络包含两个线性变换中间夹着一个激活函数激活函数使用的是ReLURectified Linear Unit前馈神经网络 对每个位置不同词/token进行的是相同的操作但不同层不同编码器/解码器的前馈神经网络之间不共享参数优点1. 引入了非线性变换使模型能学习到更复杂的映射2. 并行计算同一层前馈神经网络对每个位置的计算是独立的9. Transformer的并行体现在什么地方自注意力机制并行化模型计算输入序列的所有单词之间的注意力分数时不同位置单词与其他单词之间的注意力分数计算是相互独立的多头注意力的并行不同头在不同处理单元上计算前向反馈网络 对输入序列每个位置的计算是并行的层与层之间没有递归关系计算也可以是并行
内招 冲刺中 目标6.8号
第一次内招面试惨不忍睹无所谓了大不了就当转行AI infra的开始1.从0实现链表 写不出来一直用AI写真是全忘了。。2. 深度学习小白 transformer 讲不出来 最重要的MHA 多注意力机制不懂3. 二作强化学习论文没有讲清楚优化里的最大熵问题 MPDQN比牛顿迭代法好在哪。后续主动和面试官说准备一下他说周一再战。。。目标1. 重新捡起手写代码能力 C2. 学习transformer3. 有时间的话 看看那篇强化学习论文讲明白都做了什么6.4日整体目标1. C基础语法复习 :类和对象 看了类和结构体的定义构造函数析构函数拷贝构造函数链表 从0 开始写了两遍vector语法setunordered_map2. transformer宏观入门快速过一遍RNN视频阅读 Jay Alammar 的图解文章《The Illustrated Transformer》中文版。重点理解它为什么比以前的 RNN 强并行计算、全局视野以及 Encoder-Decoder 的大致分工。C C语法1. 指针和引用引用和指针的区别1. 不存在空引用 必须连接到合法内存2. 一旦引用被初始化为一个对象 那么就不可以再指向到另一对象指针可以随时改变指向3. 引用必须在创建时被初始化指针不需要4. 引用的对象是一个变量指针是一个地址2. 结构体和类基本一致只是访问权限的区别struct ListNode { int value; ListNode* next; ListNode(int v):value(v),next(nullptr){} };3. 类 用结构体和类实现链表例子class用于管理链表再class中再定义结构体从零开始构建链表#includeiostream #includestring using namespace std; class MyList { public : struct ListNode { int value; ListNode* next; ListNode(int v):value(v),next(nullptr){} }; MyList(){ _dummyhead new ListNode(0); len 0; } ~MyList(){ delete _dummyhead; } int get(int index){ if(index len || index 0)return -1; ListNode* cur _dummyhead - next; while(index--){ cur cur - next; } return cur-value; } void addathead (int v){ ListNode* cur _dummyhead; ListNode* newnode new ListNode(v); newnode-next cur - next; cur-next newnode; len; } void addattail (int v){ ListNode* cur _dummyhead; while(cur-next ! nullptr){ cur cur-next; } ListNode* newnode new ListNode(v); cur-next newnode; len; } void addatindex(int index , int v){ if(index len || index 0)return; ListNode* newnode new ListNode(v); ListNode* cur _dummyhead; while(index--){ cur cur-next; } ListNode* tmp cur-next; cur-next newnode; newnode-next tmp; len; } void deleteatindex(int index){ if(index len || index 0)return; ListNode* cur _dummyhead; while(index--){ cur cur-next; } cur -next cur-next-next; len--; } private: ListNode* _dummyhead; int len; }; int main(){ MyList myl; myl.addathead(1); myl.addattail(2); myl.addatindex(1,3); coutmyl.get(1)endl; myl.deleteatindex(1); coutmyl.get(1)endl; return 0; }对象成员函数写在里面不用 加上符号写在外面要 加上 类名构造函数和类名一致可以使用成员列表方式构造 在创造对象时可以初始化成员变量内部可以调用成员函数析构函数类名之前多了个 在析构函数一般用来释放内存啥的拷贝构造4. vectorvectorint v1(4) //长度为4的数组 vectorint v2(4,0)//长度为4元素均为0的数组 vectorint v3 {1,2,3,4}//用初始化列表 //给二维数组初始化留个地方 // v1.push_back(1) //尾部添加元素 v1.pop_back() v1.size() //大小 v1.resize(4,0)//重新调整成长度为4元素为0 的数组但好像一般只有一个参数来调整长度5.二叉树 使用类和结构体 实现一个简单的二叉树包括前中后序递归遍历和层序遍历方式复习了递归的基本写法层序遍历基本写法。#include iostream #include vector #include queue using namespace std; struct TreeNode{ int value; TreeNode* left; TreeNode* right; TreeNode(int v):value(v),left(nullptr),right(nullptr){} }; class BinTree{ private: TreeNode* root; void clear(TreeNode* c){ if(c NULL) return; TreeNode* tmp c; clear(c-left); clear(c-right); delete tmp; } void preOrder(TreeNode* c){ if(c nullptr)return; coutc-value ; preOrder(c-left); preOrder(c-right); return; } void inOrder(TreeNode* c){ if(c nullptr)return; inOrder(c-left); coutc-value ; inOrder(c-right); return; } void postOrder(TreeNode* c){ if(c nullptr)return; postOrder(c-left); postOrder(c-right); coutc-value ; return; } public: BinTree(){ buildsimpletree(); coutcreateendl; } ~BinTree(){ clear(root); coutclearendl; } void buildsimpletree(){ root new TreeNode(1); root - left new TreeNode(2); root - right new TreeNode(3); root - left -left new TreeNode(4); root - left -right new TreeNode(5); } //外部接口 void preOrder(){ preOrder(root); coutendl; } void inOrder(){ inOrder(root); coutendl; } void postOrder(){ postOrder(root); coutendl; } vectorvectorintlayerOrder(){ vectorvectorintresult; queueTreeNode* q; if(!root)return{}; q.push(root); while(q.size() ! 0){ vectorintl; int size q.size(); for(int i 0;isize;i){ TreeNode* cur q.front(); l.push_back(cur - value); q.pop(); if(cur-left ! nullptr)q.push(cur-left); if(cur-right ! nullptr)q.push(cur-right); } result.push_back(l); for(int num : l){ coutnum ; } coutendl; } return result; } }; int main(){ BinTree bt; bt.preOrder(); bt.inOrder(); bt.postOrder(); bt.layerOrder(); return 0; }6.medium题练手二分数组哈希栈队列 链表动态规划两道简单的7.set 和unordered_map 复习set 存储了一组唯一的元素且按一定顺序存储的。使用红黑树实现的unordered_map没有顺序方便查找set类型s; s.insert元素); s.erase(元素); s.find(元素);// 返回对应位置迭代器如果不存在返回s.end() s.size();//返回大小 s.empty();//返回是否为空 unordered_mapkey类型,value类型m;8. 递归思路总结确定参数和返回值确定终止条件确定单层递归逻辑9.动态规划思路dp数组以及下标的含义递推公式dp数组的初始化遍历顺序打印dp数组图解transformer李沐中文版链接https://zhuanlan.zhihu.com/p/219714713https://www.bilibili.com/video/BV1pu411o7BE/?buvidYD409ED1AA281F7048B582E641079E1E7B6Bfrom_spmidsearch.search-result.0.0is_story_h5falsemidA9%2FUnMMCMAA6qPn1agLbfA%3D%3Dplat_id116share_fromugcshare_mediumiphoneshare_platiosshare_session_idB80A17D2-DC91-4B84-AC10-B2DE426F83A6share_sourceWEIXINshare_tags_ispmidunited.player-video-detail.0.0timestamp1779847636unique_kMmfuc4pup_id1567748478vd_source0cd5e9ebf84cd3a1dec858a3b424edfb看的过程中一些问题记录Transformer是什么transformer是一种基于Attention的序列建模架构通过self- attention建立不同位置之间的关系并利用位置编码补充顺序信息从而实现更强的并行能力和更好的长距离依赖建模。1. embedding是什么有必要深入了解吗将文字转变为向量能捕捉语义关系在数学向量空间中 意思相近的词向量间距离也越近在把单词变为embedding向量后模型会给这些向量加上位置编码 告诉模型这个单词在句子中的位置总结输入文字经过embedding变成了带有语义的向量最后加上了位置编码再送入编码器2. transformer究竟解决了什么问题之前主流的RNN要串行处理文本过长时会遗忘开头内容难以捕捉远距离词汇的关联但transformer引入自注意力机制一次性看到任意两个词之间的关联度无论两个词相距多远都能快速建立联系解决了算得慢无法并行的问题除了文本翻译外还高度通用能进行视觉音频NLP等领域的处理3. Self-Attention是什么将query 和一堆key-value对映射成一个输出的一个函数想通过三个线性层或者说权重矩阵将输入的embedding向量转换成Q,K,V对于序列中的每一个token的输出结果是实用他的Query和自身以及其余的key进行scaled dot-production并将结果放入softman函数得到权重系数在乘value权重系数表示当前token和句子中每一个token间的之间的关联度对应当前token的输出结果是根据注意力权重系数把所有token的特征加权融合后的新特征向量。4. 为什么要使用多头注意力使用多头注意力主要为了提升模型捕捉信息的能力捕捉多种依赖关系 不同的注意力头可以捕捉到不同位置之间不同依赖关系提高模型容量使模型能学习到更复杂表示提升范化能力并行计算提升训练和推理的效率多头注意力 假设使用八个对于每一个注意力头是将embedding向量用不同的W矩阵降维成d/h长度 的Q,K,V向量最后的得到d/h长度的输出将八个注意力头的输出通过权重矩阵拼接起来作为整个自注意力层的输出。公式可见论文果只使用一个头模型的表达能力和学习能力会受到限制可能无法充分捕捉到输入数据中的复杂依赖关系从而影响模型的性能。多头注意力机制通过组合多个注意力头的输出能够提供更丰富、更强大的表示4.1 为什么在使用多头注意力时要进行降维降低计算量 每个头工作在较低维度注意力的计算复杂度降低每个注意力头在低维空间中可以专注捕捉特定类型的特征或模式缓解高维向量空间直接计算点积导致数值容易变得很大的现象。可能会导致Softmax进入梯度较小的饱和区配合scaled缩放因子使用5. 为什么需要位置编码因为注意力机制不携带位置信息6. 残差连接和layernorm的作用整体公式LayerNorm(xSubLayer(x))残差连接帮助避免了深度神经网络中的梯度消失问题层归一化是在模型的训练过程中加速收敛的一种技术它对层的输入进行归一化处理使得其均值为0方差为16.1 残差结构意义缓解梯度消失提升训练速度允许信息直接传递增强网络能力保持前向信息的完整性6.2 LayerNorm25:07https://www.bilibili.com/video/BV1pu411o7BE/?buvidYD409ED1AA281F7048B582E641079E1E7B6Bfrom_spmidsearch.search-result.0.0is_story_h5falsemidA9%2FUnMMCMAA6qPn1agLbfA%3D%3Dplat_id116share_fromugcshare_mediumiphoneshare_platiosshare_session_idB80A17D2-DC91-4B84-AC10-B2DE426F83A6share_sourceWEIXINshare_tags_ispmidunited.player-video-detail.0.0timestamp1779847636unique_kMmfuc4pup_id1567748478vd_source0cd5e9ebf84cd3a1dec858a3b424edfb7.在解码器的第一层是masked self-attention 层这个masked是什么意思如何进行mask的或者说解码器和编码器中的self attention层有什么区别/ transformer中的三种自注意力层Encoder的自注意力层能完整查看 输入序列每个位置都可以自由地注意序列中的所有其他位置。这意味着计算注意力分数时并没有位置上的限制。Decoder 有两层注意力层首先是 masked self -attention层为了保证输出时的自回归性生成当前词只依赖于前面的词要确保在计算注意力分数时每个位置只计算它之前的位置。为了实现这一点使用序列掩码。在计算注意力分数之前将当前位置分数以及之后位置分数设为无穷大的负数这样在接下来的softmax操作中这些位置的注意力权重为0紧接着第二层是 encoder-decoder attention层这一层的 kv矩阵来自编码器的输出q矩阵来自于解码器masked self-attention 层的输出。这种机制使Decoder能够关注输入序列的不同部分特别是在生成每个新单词时。例如在机器翻译任务中当模型生成目标语言的一个单词时它可以通过这种机制来聚焦于源语言句子中的相关部分8. 前馈神经网络Feed-Forward Neural Network, FFN前馈神经网络的意义是语义空间的转换。是自注意力层后的重要组成部分每个前馈神经网络包含两个线性变换中间夹着一个激活函数激活函数使用的是ReLURectified Linear Unit前馈神经网络 对每个位置不同词/token进行的是相同的操作但不同层不同编码器/解码器的前馈神经网络之间不共享参数优点1. 引入了非线性变换使模型能学习到更复杂的映射2. 并行计算同一层前馈神经网络对每个位置的计算是独立的9. Transformer的并行体现在什么地方自注意力机制并行化模型计算输入序列的所有单词之间的注意力分数时不同位置单词与其他单词之间的注意力分数计算是相互独立的多头注意力的并行不同头在不同处理单元上计算前向反馈网络 对输入序列每个位置的计算是并行的层与层之间没有递归关系计算也可以是并行