<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>滑天下之大稽</title>
  
  <subtitle>无问西东</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://logicjake.github.io/"/>
  <updated>2023-01-28T12:32:53.025Z</updated>
  <id>https://logicjake.github.io/</id>
  
  <author>
    <name>LogicJake</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>推荐算法上岸历程—备战实习秋招</title>
    <link href="https://logicjake.github.io/2021/12/01/%E7%AE%97%E6%B3%95%E7%A7%8B%E6%8B%9B%E4%B8%8A%E5%B2%B8%E5%8E%86%E7%A8%8B%E2%80%94%E5%A4%87%E6%88%98%E5%AE%9E%E4%B9%A0%E7%A7%8B%E6%8B%9B/"/>
    <id>https://logicjake.github.io/2021/12/01/%E7%AE%97%E6%B3%95%E7%A7%8B%E6%8B%9B%E4%B8%8A%E5%B2%B8%E5%8E%86%E7%A8%8B%E2%80%94%E5%A4%87%E6%88%98%E5%AE%9E%E4%B9%A0%E7%A7%8B%E6%8B%9B/</id>
    <published>2021-12-01T11:16:42.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<p>秋招正式结束，记录一下从备战到秋招的整个过程和一些体会。具体公司的面经已经单独发过，所以这里着重介绍准备的过程和心得体会。</p><h1 id="背景介绍"><a href="#背景介绍" class="headerlink" title="背景介绍"></a>背景介绍</h1><p>211本硕，计算机科班，没有顶会顶刊，找实习和秋招的时候只有一篇水会和一篇在投SCI期刊。研究方向是动态网络表示，属于数据挖掘，在推荐算法里有落地场景。研一和研二上半学期基本完成了学院毕业要求，也和老师谈好了实习的事情，整个过程大致分为4个时间段：</p><ul><li>2019.7 ~ 2020.9 打比赛</li><li>2020.9 ~ 2021.3 刷题，夯实机器学习、深度学习基础，整理以往竞赛</li><li>2021.3 ~ 2021.5 实习面试，刷题，夯实机器学习、深度学习基础</li><li>2021.6 ~ 2021.9 实习，秋招面试</li></ul><p>个人主要优势在竞赛经历，当然不是ACM那种大佬竞赛，这里指数据挖掘竞赛。实习+秋招所有大厂的推荐算法都面了一遍，秋招上岸广告推荐算法。</p><h1 id="备战"><a href="#备战" class="headerlink" title="备战"></a>备战</h1><p>确定读研后就明确工作要走数据挖掘（后面在竞赛中慢慢了解到推荐算法这个方向，并以此为目标）方向，当时了解到算法岗位3大敲门砖：实习，项目和论文。考虑到自己和研究组的水平，发顶会顶刊基本不太可能，所以努力的方向集中在实习和项目上。学生没资源，搞项目也很难，所以这里项目特指数据挖掘竞赛。</p><h2 id="竞赛"><a href="#竞赛" class="headerlink" title="竞赛"></a>竞赛</h2><p>竞赛是一个周期比较长的过程，所以准备的比较早，也没啥打基础的过程，以赛带练，边学边打。第一个比赛是大四暑假参加的，预测学生考试成绩，赛题很简单但却很经典，基本上囊括了数据挖掘竞赛需要掌握的所有东西。做竞赛的经验就是多去找之前竞赛的开源方案或者代码，学习他们是怎么做特征工程的，怎么构建流程的，甚至是一些场景下面的trick。这方面的资料其实挺多，有很多赛圈大佬愿意在赛后开源代码或者开文章介绍方案细节，愿意去找，愿意去学，愿意去实现，进步会很快。另外就是竞赛中组队的事情，很多人喜欢在群里找人带，但其实很少有前排愿意这样做，一方面大家都喜欢强强联合，所以一般会找和自己成绩差不多的人或者和熟悉的人组队，一方面前期大家都是单干的，后期组队融合上分。所以竞赛小白更重要的是自己先变强，尽可能把分数刷高点，后期组队更进一步，赛后多学习前排代码和方案，刚开始成绩不理想不可怕，一次完整的竞赛经历对自己的帮助是巨大的，后面会越来越好。</p><p>最开始打的比赛基本都是表格数据，纯数据挖掘，这一段时期主要是锻炼特征工程（python，pandas）和熟悉树模型（xgboost，lightgbm，catboost梭哈）。推荐相关的竞赛比较少，完整的推荐流程包括召回，（粗排），精排。大部分和推荐相关的竞赛都集中在精排部分，也是就点击率预估（ctr）竞赛，比如我参加过的图灵联邦视频点击预测大赛（2019）和一点资讯技术编程大赛CTR赛道（2021）。但近些年的趋势，纯ctr的竞赛也不多，前些年的腾讯广告算法大赛很多是ctr相关的，现在慢慢转向多任务多模态了，这其实也是业界关注的问题风向。如果想蹲这方面的竞赛，可以多关注华为的DIGIX全球校园AI算法精英大赛和腾讯广告算法大赛，每年都有推荐算法方面的赛题，如果能获奖，面试的时候作为项目来讲加分不少。我参加的包含完整推荐流程的就两个：KDD Cup Debias 竞赛（2020）和天池新闻推荐竞赛（2020，入门赛，没奖金，竞争小）。KDD Cup Debias 方案流程包含了召回和排序，所以业务相关性非常强，而且比赛规格高（KDD和阿里合办的），如果能获奖牌子也比较硬。在实习面试和秋招面试的时候，KDD Cup Debias竞赛被问的最多，基本上每个面试官都要问一次，导致后面要说的词都背的滚瓜烂熟了，实习的时候mentor也说没有这个经历你简历过不了。也有一些比赛使用深度学习去做，比如2020腾讯广告算法大赛和Kaggle的Answer Correctness Prediction。</p><p>纵观来说，竞赛对自己的帮助是很大的，夯实基础（python，pandas，深度学习，机器学习），了解业务流程（推荐算法相关），丰富简历。但最开始也说过竞赛是一个周期比较长的过程，每个竞赛基本都会持续2-3个月，而且短时间内不一定有好的竞赛（平台大，赛题方向好）出现，所以如果想在这方面有所收益，需要投入的时间不少，而且现在狼多肉少，想进前排也越来越难了。如果想走这条路，平时多关注各大平台的赛题，比如kaggle，天池，华为DIGIX校园赛和腾讯广告算法大赛等等，我自己做了个聚合网站，用爬虫抓取比赛信息，感兴趣可以看看<a href="https://logicjake.github.io/MLCompetitionHub">MLCompetitionHub</a>。</p><h2 id="刷题"><a href="#刷题" class="headerlink" title="刷题"></a>刷题</h2><p>现在找工作做题是必不可少的，所以刷题也很重要，我个人主要在LeetCode上完成。我刷题的经验比较简单粗暴，按照编号刷，median的题为主，常见的hard题为辅，中间穿插一些专题专攻，多做几遍《剑指offer》和《程序员面试金典》（这两本书不要买，LeetCode都有），遇到不会的题收藏起来，空闲时间拿出来再过一遍。作为一个菜鸡，我觉得就是多刷多记，勤能补拙，刷的多见得也多，基本上能覆盖面试官问的题目，大部分面试官都是出的原题。我从2020年8月份开始大批量刷题，LeetCode有个进度管理功能，我开了两个进度，总共做了近800道题，当然这中间有重复的。此外就是持之以恒，保持做题的状态，我基本上一天的安排，做5道新题，复习10道收藏的当时不会做的题。中后期注重做高频题目，推荐<a href="https://codetop.cc/" target="_blank" rel="noopener">codetop</a>，可以分公司分部门分岗位查看高频题。</p><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://raw.githubusercontent.com/LogicJake/imghub/master/leetcode%E8%BF%9B%E5%BA%A6.png" alt="" title="">                </div>                <div class="image-caption"></div>            </figure></div><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://raw.githubusercontent.com/LogicJake/imghub/master/leetcode%E8%BF%9B%E5%BA%A62.png" alt="" title="">                </div>                <div class="image-caption"></div>            </figure></div><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://raw.githubusercontent.com/LogicJake/imghub/master/leetcode%E8%BF%9B%E5%BA%A63.png" alt="" title="">                </div>                <div class="image-caption"></div>            </figure></div><h2 id="基础"><a href="#基础" class="headerlink" title="基础"></a>基础</h2><p>基础包括机器学习，深度学习和业务相关。主要学习途径就是看书，结合别人的面经抓抓重点。</p><p>机器学习我看的《统计学习方法》和周志华的西瓜书。周志华大佬的西瓜书讲的很细致，数学的东西比较多，看的时候感觉不是太适合我，所以主要还是以《统计学习方法》为主。《统计学习方法》内容比较全面，不需要全过，结合自己的面试经验来看，掌握常见的机器学习模型就好：线性回归，逻辑回归，SVM，朴素贝叶斯，决策树，集成学习，K近邻。需要知道以上模型的原理和优缺点，会手推（尤其是逻辑回归的求梯度）。lightgbm和xgboost也很重要，经常被问原理和优缺点，这里推荐几篇文章：<a href="https://zhuanlan.zhihu.com/p/78293497" target="_blank" rel="noopener">LightGBM算法梳理</a>和<a href="https://zhuanlan.zhihu.com/p/142413825" target="_blank" rel="noopener">机器学习 | XGBoost详解</a>。此外推荐《百面机器学习》，有常见面试点的总结和讲解。</p><p>大四看吴恩达的课入门深度学习，面试备战主要看《百面深度学习》，然后自己总结了一些点：RNN，CNN（没被问过），Dropout，激活函数，正则化，优化器，Transformer。Word2Vec也被问的比较多，暂且也把他归到深度学习这块，主要了解negative sampling，hierarchical softmax，CBOW和Skip-gram区别。深度学习比较频繁的考察点有：</p><ul><li>如何解决过拟合</li><li>梯度爆炸和梯度消失</li><li>BN原理</li><li>Dropout原理</li><li>常见的优化器和他们的优缺点</li><li>Transformer结构</li><li>self-attention原理</li><li>自己搞深度学习的有啥心得和体会（各讲各话了）</li></ul><p>业务相关就看个人了，看你的岗位方向。我主要看的是推荐算法相关的，推荐《深度学习推荐系统》，包含了推荐系统的各大模型和其他问题，适合入门和了解初步的推荐系统体系。除了这本书之外，也可以多关注业界发展，比如大厂发的论文或者业界大佬发的文章，追踪一下热点，比如冷启动问题，长序列建模，多任务模型。尽可能丰富自己对推荐系统的了解，让面试官看到你身上的亮点，会有面试官让你谈谈你对推荐系统的认识或者最近看的论文。</p><h1 id="实习"><a href="#实习" class="headerlink" title="实习"></a>实习</h1><p>实习有利有弊，好处在于实习转正相对比较容易，有个保底，也可以提前了解组内业务和环境；坏处就是会和秋招冲突，耽误面试。今年因为疫情，面试都是线上，所以对我来说实习的好处还是远远大于坏处的。还有一点建议，如果有高追求的话，不要实习转正就躺平，哪怕你非常想待在实习的组，因为你all in之后有可能被压价，而且不能预料业务会不会发生变动。</p><p>实习面试的时候建议先从小公司面起，积累经验，形成自己的自我介绍和项目介绍模板，另外也可以看看公司注重哪些点，查漏补缺。具体的面经就不在这里展开了，可以查看<a href="https://logicjake.github.io/2021/03/23/2021%E6%8E%A8%E5%B9%BF%E6%90%9C%E6%9A%91%E5%81%87%E5%AE%9E%E4%B9%A0%E9%9D%A2%E7%BB%8F/">算法暑假实习面经</a>或者进我的牛客主页看。小公司的实习消息和投递可以关注牛客的<a href="https://nowpick.nowcoder.com/w/intern/center" target="_blank" rel="noopener">实习广场</a>，大公司的实习投递时间基本上参考往年就可以了，都有自己的投递系统。我个人的时间安排如下，基本上过完年回来就开始高强度面试，3月份最为忙碌。面试内容基本围绕做的比赛展开，介绍方案，由方案引出一些细节。</p><ul><li>2021.2 阿里各部门开始宣传收简历，支持提前面试</li><li>2021.2 ~ 2021.3 面试了一些小公司</li><li>2021.3.1 阿里系统正式开放</li><li>2021.3月初 字节，阿里，快手，腾讯，百度开始面试</li><li>2021.4月初 美团开始面试 </li><li>2021.5月初 拼多多开始面试 </li></ul><h1 id="秋招"><a href="#秋招" class="headerlink" title="秋招"></a>秋招</h1><p>秋招我是一边实习一遍准备的，6月底去实习后刷题就不多了，面试时间也尽量安排在晚上或者早上，甚至周末。大部分公司都是会提前约面试时间，和面试官沟通就好。各个公司校招开始可以关注牛客的<a href="https://www.nowcoder.com/school/schedule?firstScroll=true" target="_blank" rel="noopener">校招日历</a>，比较全面。秋招时间不是太充裕，要保证实习工作的完成，所以基本都投的大厂。实习在北京，后面正式工作想去上海，所以后面放弃了不在上海的大厂。上海腾讯算法岗太少，我在BOSS上勾搭了好几个腾讯的招聘人员都没啥进展，系统里也没上海的捞。秋招面经可以看<a href="https://logicjake.github.io/2021/07/03/2021%E6%8E%A8%E5%B9%BF%E6%90%9C%E7%AE%97%E6%B3%95%E7%A7%8B%E6%8B%9B%E9%9D%A2%E7%BB%8F/">算法秋招面经</a>，也不展开了。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;秋招正式结束，记录一下从备战到秋招的整个过程和一些体会。具体公司的面经已经单独发过，所以这里着重介绍准备的过程和心得体会。&lt;/p&gt;
&lt;h1 id=&quot;背景介绍&quot;&gt;&lt;a href=&quot;#背景介绍&quot; class=&quot;headerlink&quot; title=&quot;背景介绍&quot;&gt;&lt;/a&gt;背景介绍&lt;
      
    
    </summary>
    
    
    
  </entry>
  
  <entry>
    <title>Connect X 实践</title>
    <link href="https://logicjake.github.io/2021/10/08/connectx%E5%AE%9E%E8%B7%B5/"/>
    <id>https://logicjake.github.io/2021/10/08/connectx%E5%AE%9E%E8%B7%B5/</id>
    <published>2021-10-08T20:55:47.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<p>游戏目标为在你的对手之前，在游戏板上水平、垂直或对角地放置连续4个跳棋。轮到你时，把你的一个跳棋“投”到棋盘一列中，棋子会掉落在这一列最底部的空位置。</p><h3 id="random"><a href="#random" class="headerlink" title="random"></a>random</h3><p>随机选取尚有空位置的一列进行投放。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">my_agent</span><span class="params">(obs, config)</span>:</span></span><br><span class="line">    <span class="keyword">from</span> random <span class="keyword">import</span> choice</span><br><span class="line">    <span class="keyword">return</span> choice([c <span class="keyword">for</span> c <span class="keyword">in</span> range(config.columns) <span class="keyword">if</span> obs.board[c] == <span class="number">0</span>])</span><br></pre></td></tr></table></figure><h3 id="minimax"><a href="#minimax" class="headerlink" title="minimax"></a>minimax</h3><p>适用于零和博弈场景，每次操作即搜索选择对自己有利的情况，如果我们令甲胜的局面值为1，乙胜的局面值为-1，而和局的值为0。当轮到甲走时，甲定会选择子节点值最大的走法；而轮到乙时，乙则会选择子节点值最小的走法。所以对于中间节点的值有如下计算方法：如果该节点所对应的局面轮到甲走棋，则该节点的值是其所有子节点中值最大的一个的值。而如果该节点所对应的局面轮到乙走棋，则该节点的值是其所有子节点中值最小的一个的值，这就是minimax的搜索思想。minimax算法本质还是穷尽，解空间大的时候不适用。此时可以约束game tree的深度，另外不一定需要知道终局分数，也能对当前棋面做出大致评估。在下面的代码中，棋面的评估仅被分为输、赢、平3种状态。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://raw.githubusercontent.com/LogicJake/imghub/master/minmax反向.png" alt="minmax反向" title="">                </div>                <div class="image-caption">minmax反向</div>            </figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">minimax_agent</span><span class="params">(obs, config)</span>:</span></span><br><span class="line">    <span class="keyword">from</span> math <span class="keyword">import</span> inf <span class="keyword">as</span> infinity</span><br><span class="line">    <span class="keyword">from</span> random <span class="keyword">import</span> choice</span><br><span class="line">    <span class="comment"># 电脑</span></span><br><span class="line">    COMP = <span class="number">2</span></span><br><span class="line">    <span class="comment"># 玩家</span></span><br><span class="line">    HUMAN = <span class="number">1</span></span><br><span class="line">    </span><br><span class="line">    columns = config.columns</span><br><span class="line">    rows = config.rows</span><br><span class="line">    <span class="comment"># 因为是提前一个落子检查，所以只需要满足inarow - 1个连续</span></span><br><span class="line">    inarow = config.inarow - <span class="number">1</span></span><br><span class="line">    size = rows * columns</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">is_win</span><span class="params">(board, player, column)</span>:</span></span><br><span class="line">        <span class="comment"># 找到当前列的落子位置</span></span><br><span class="line">        row = max([r <span class="keyword">for</span> r <span class="keyword">in</span> range(rows) <span class="keyword">if</span> board[column + (r * columns)] == <span class="number">0</span>])</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">def</span> <span class="title">count</span><span class="params">(offset_row, offset_column)</span>:</span></span><br><span class="line">            <span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">1</span>, inarow + <span class="number">1</span>):</span><br><span class="line">                r = row + offset_row * i</span><br><span class="line">                c = column + offset_column * i</span><br><span class="line">                <span class="comment"># 停止条件</span></span><br><span class="line">                <span class="keyword">if</span> (</span><br><span class="line">                    r &lt; <span class="number">0</span></span><br><span class="line">                    <span class="keyword">or</span> r &gt;= rows</span><br><span class="line">                    <span class="keyword">or</span> c &lt; <span class="number">0</span></span><br><span class="line">                    <span class="keyword">or</span> c &gt;= columns</span><br><span class="line">                    <span class="keyword">or</span> board[c + (r * columns)] != player</span><br><span class="line">                ):</span><br><span class="line">                    <span class="keyword">return</span> i - <span class="number">1</span></span><br><span class="line">            <span class="keyword">return</span> inarow</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> (</span><br><span class="line">            count(<span class="number">1</span>, <span class="number">0</span>) &gt;= inarow  <span class="comment"># 垂直方向，向下搜</span></span><br><span class="line">            <span class="keyword">or</span> (count(<span class="number">0</span>, <span class="number">1</span>) + count(<span class="number">0</span>, <span class="number">-1</span>)) &gt;= inarow  <span class="comment"># 水平方向，左右两边搜</span></span><br><span class="line">            <span class="keyword">or</span> (count(<span class="number">-1</span>, <span class="number">-1</span>) + count(<span class="number">1</span>, <span class="number">1</span>)) &gt;= inarow  <span class="comment"># 主对角线方向</span></span><br><span class="line">            <span class="keyword">or</span> (count(<span class="number">-1</span>, <span class="number">1</span>) + count(<span class="number">1</span>, <span class="number">-1</span>)) &gt;= inarow  <span class="comment"># 次对角线方向</span></span><br><span class="line">        )</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">play</span><span class="params">(board, column, player)</span>:</span></span><br><span class="line">        row = max([r <span class="keyword">for</span> r <span class="keyword">in</span> range(rows) <span class="keyword">if</span> board[column + (r * columns)] == <span class="number">0</span>])</span><br><span class="line">        board[column + (row * columns)] = player</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">minimax</span><span class="params">(board, player, depth)</span>:</span></span><br><span class="line">        <span class="keyword">if</span> player == HUMAN:</span><br><span class="line">            best_score = -infinity</span><br><span class="line">            best_column = <span class="literal">None</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            best_score = infinity</span><br><span class="line">            best_column = <span class="literal">None</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment"># 递归终止条件1 深度达到设定值</span></span><br><span class="line">        <span class="keyword">if</span> depth == <span class="number">0</span>:</span><br><span class="line">            <span class="keyword">return</span> [<span class="number">0</span>, <span class="literal">None</span>]</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># 递归终止条件2 一方获胜</span></span><br><span class="line">        <span class="keyword">for</span> column <span class="keyword">in</span> range(columns):</span><br><span class="line">            <span class="comment"># 遍历可选列，检查是否可以获胜</span></span><br><span class="line">            <span class="keyword">if</span> board[column] == <span class="number">0</span>:</span><br><span class="line">                <span class="keyword">if</span> is_win(board, player, column):</span><br><span class="line">                    <span class="comment">## 玩家获胜</span></span><br><span class="line">                    <span class="keyword">if</span> player == HUMAN:</span><br><span class="line">                        <span class="keyword">return</span> [<span class="number">1</span>, column]</span><br><span class="line">                    <span class="comment">## 电脑获胜</span></span><br><span class="line">                    <span class="keyword">else</span>:</span><br><span class="line">                        <span class="keyword">return</span> [<span class="number">-1</span>, column]</span><br><span class="line">            </span><br><span class="line">        <span class="keyword">for</span> column <span class="keyword">in</span> range(columns):</span><br><span class="line">            <span class="keyword">if</span> board[column] == <span class="number">0</span>:</span><br><span class="line">                next_board = board[:]</span><br><span class="line">                play(next_board, column, player)</span><br><span class="line">                <span class="comment"># 向后看，计算分数</span></span><br><span class="line">                score, _ = minimax(next_board, player % <span class="number">2</span> + <span class="number">1</span>, depth - <span class="number">1</span>)</span><br><span class="line">                </span><br><span class="line">                <span class="keyword">if</span> player == HUMAN:</span><br><span class="line">                    <span class="keyword">if</span> score &gt; best_score:</span><br><span class="line">                        best_score = score</span><br><span class="line">                        best_column = column</span><br><span class="line">                <span class="keyword">else</span>:</span><br><span class="line">                    <span class="keyword">if</span> score &lt; best_score:</span><br><span class="line">                        best_score = score</span><br><span class="line">                        best_column = column</span><br><span class="line">                        </span><br><span class="line">        <span class="keyword">return</span> [best_score, best_column]</span><br><span class="line">    </span><br><span class="line">    max_depth = <span class="number">4</span></span><br><span class="line">    _, column = minimax(obs.board[:], HUMAN, max_depth)</span><br><span class="line">    <span class="comment"># 兜底策略，如果minimax没找到解，则使用random算法</span></span><br><span class="line">    <span class="keyword">if</span> column == <span class="literal">None</span>:</span><br><span class="line">        column = choice([c <span class="keyword">for</span> c <span class="keyword">in</span> range(columns) <span class="keyword">if</span> obs.board[c] == <span class="number">0</span>])</span><br><span class="line">    <span class="keyword">return</span> column</span><br></pre></td></tr></table></figure><p>[1] <a href="https://www.bilibili.co7m/video/BV1Eb411a7Vb" target="_blank" rel="noopener">https://www.bilibili.co7m/video/BV1Eb411a7Vb</a><br>[2] <a href="https://github.com/Cledersonbc/tic-tac-toe-minimax" target="_blank" rel="noopener">https://github.com/Cledersonbc/tic-tac-toe-minimax</a>  </p><h3 id="negamax"><a href="#negamax" class="headerlink" title="negamax"></a>negamax</h3><p>negamax是minimax的改进版，在效果上没有改进，仅仅进行了视角的统一。在minimax算法中，我们和对手之间是对立的视角，所以一个是最大化，一个是最小化。但是在negamax进行了视角的统一，大家追求的都是最大化。因此就有两个改进点，第一个就是当有一方胜利是，可以不加角色区分都返回1；第二个就在于对子节点的score进行选择时，统一最大化返回。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">negamax_agent</span><span class="params">(obs, config)</span>:</span></span><br><span class="line">    <span class="keyword">from</span> math <span class="keyword">import</span> inf <span class="keyword">as</span> infinity</span><br><span class="line">    <span class="keyword">from</span> random <span class="keyword">import</span> choice</span><br><span class="line">    <span class="comment"># 电脑</span></span><br><span class="line">    COMP = <span class="number">2</span></span><br><span class="line">    <span class="comment"># 玩家</span></span><br><span class="line">    HUMAN = <span class="number">1</span></span><br><span class="line">    </span><br><span class="line">    columns = config.columns</span><br><span class="line">    rows = config.rows</span><br><span class="line">    <span class="comment"># 因为是提前一个落子检查，所以只需要满足inarow - 1个连续</span></span><br><span class="line">    inarow = config.inarow - <span class="number">1</span></span><br><span class="line">    size = rows * columns</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">is_win</span><span class="params">(board, player, column)</span>:</span></span><br><span class="line">        <span class="comment"># 找到当前列的落子位置</span></span><br><span class="line">        row = max([r <span class="keyword">for</span> r <span class="keyword">in</span> range(rows) <span class="keyword">if</span> board[column + (r * columns)] == <span class="number">0</span>])</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">def</span> <span class="title">count</span><span class="params">(offset_row, offset_column)</span>:</span></span><br><span class="line">            <span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">1</span>, inarow + <span class="number">1</span>):</span><br><span class="line">                r = row + offset_row * i</span><br><span class="line">                c = column + offset_column * i</span><br><span class="line">                <span class="comment"># 停止条件</span></span><br><span class="line">                <span class="keyword">if</span> (</span><br><span class="line">                    r &lt; <span class="number">0</span></span><br><span class="line">                    <span class="keyword">or</span> r &gt;= rows</span><br><span class="line">                    <span class="keyword">or</span> c &lt; <span class="number">0</span></span><br><span class="line">                    <span class="keyword">or</span> c &gt;= columns</span><br><span class="line">                    <span class="keyword">or</span> board[c + (r * columns)] != player</span><br><span class="line">                ):</span><br><span class="line">                    <span class="keyword">return</span> i - <span class="number">1</span></span><br><span class="line">            <span class="keyword">return</span> inarow</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> (</span><br><span class="line">            count(<span class="number">1</span>, <span class="number">0</span>) &gt;= inarow  <span class="comment"># 垂直方向，向下搜</span></span><br><span class="line">            <span class="keyword">or</span> (count(<span class="number">0</span>, <span class="number">1</span>) + count(<span class="number">0</span>, <span class="number">-1</span>)) &gt;= inarow  <span class="comment"># 水平方向，左右两边搜</span></span><br><span class="line">            <span class="keyword">or</span> (count(<span class="number">-1</span>, <span class="number">-1</span>) + count(<span class="number">1</span>, <span class="number">1</span>)) &gt;= inarow  <span class="comment"># 主对角线方向</span></span><br><span class="line">            <span class="keyword">or</span> (count(<span class="number">-1</span>, <span class="number">1</span>) + count(<span class="number">1</span>, <span class="number">-1</span>)) &gt;= inarow  <span class="comment"># 次对角线方向</span></span><br><span class="line">        )</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">play</span><span class="params">(board, column, player)</span>:</span></span><br><span class="line">        row = max([r <span class="keyword">for</span> r <span class="keyword">in</span> range(rows) <span class="keyword">if</span> board[column + (r * columns)] == <span class="number">0</span>])</span><br><span class="line">        board[column + (row * columns)] = player</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">negamax</span><span class="params">(board, player, depth)</span>:</span></span><br><span class="line">        <span class="comment"># 递归终止条件1 深度达到设定值</span></span><br><span class="line">        <span class="keyword">if</span> depth == <span class="number">0</span>:</span><br><span class="line">            <span class="keyword">return</span> [<span class="number">0</span>, <span class="literal">None</span>]</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># 递归终止条件2 一方获胜</span></span><br><span class="line">        <span class="keyword">for</span> column <span class="keyword">in</span> range(columns):</span><br><span class="line">            <span class="comment"># 遍历可选列，检查是否可以获胜</span></span><br><span class="line">            <span class="keyword">if</span> board[column] == <span class="number">0</span>:</span><br><span class="line">                <span class="keyword">if</span> is_win(board, player, column):</span><br><span class="line">                    <span class="keyword">return</span> [<span class="number">1</span>, column]</span><br><span class="line"></span><br><span class="line">            </span><br><span class="line">        best_score = -infinity</span><br><span class="line">        best_column = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> column <span class="keyword">in</span> range(columns):</span><br><span class="line">            <span class="keyword">if</span> board[column] == <span class="number">0</span>:</span><br><span class="line">                next_board = board[:]</span><br><span class="line">                play(next_board, column, player)</span><br><span class="line">                <span class="comment"># 向后看，计算分数</span></span><br><span class="line">                score, _ = negamax(next_board, player % <span class="number">2</span> + <span class="number">1</span>, depth - <span class="number">1</span>)</span><br><span class="line">                score = -score</span><br><span class="line">                </span><br><span class="line">                <span class="keyword">if</span> score &gt; best_score:</span><br><span class="line">                    best_score = score</span><br><span class="line">                    best_column = column</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> [best_score, best_column]</span><br><span class="line">    </span><br><span class="line">    max_depth = <span class="number">4</span></span><br><span class="line">    _, column = negamax(obs.board[:], HUMAN, max_depth)</span><br><span class="line">    <span class="comment"># 兜底策略，如果minimax没找到解，则使用random算法</span></span><br><span class="line">    <span class="keyword">if</span> column == <span class="literal">None</span>:</span><br><span class="line">        column = choice([c <span class="keyword">for</span> c <span class="keyword">in</span> range(columns) <span class="keyword">if</span> obs.board[c] == <span class="number">0</span>])</span><br><span class="line">    <span class="keyword">return</span> column</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;游戏目标为在你的对手之前，在游戏板上水平、垂直或对角地放置连续4个跳棋。轮到你时，把你的一个跳棋“投”到棋盘一列中，棋子会掉落在这一列最底部的空位置。&lt;/p&gt;
&lt;h3 id=&quot;random&quot;&gt;&lt;a href=&quot;#random&quot; class=&quot;headerlink&quot; titl
      
    
    </summary>
    
    
      <category term="强化学习" scheme="https://logicjake.github.io/categories/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0/"/>
    
    
      <category term="强化学习" scheme="https://logicjake.github.io/tags/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>一点资讯技术编程大赛CTR赛道-赛后总结</title>
    <link href="https://logicjake.github.io/2021/09/20/%E4%B8%80%E7%82%B9%E8%B5%84%E8%AE%AF%E6%8A%80%E6%9C%AF%E7%BC%96%E7%A8%8B%E5%A4%A7%E8%B5%9BCTR%E8%B5%9B%E9%81%93-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/"/>
    <id>https://logicjake.github.io/2021/09/20/%E4%B8%80%E7%82%B9%E8%B5%84%E8%AE%AF%E6%8A%80%E6%9C%AF%E7%BC%96%E7%A8%8B%E5%A4%A7%E8%B5%9BCTR%E8%B5%9B%E9%81%93-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/</id>
    <published>2021-09-20T00:11:45.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<p>最终成绩：第一<br>比赛地址：<a href="https://tech.yidianzixun.com/competition/" target="_blank" rel="noopener">https://tech.yidianzixun.com/competition/</a><br>源码：<a href="https://github.com/LogicJake/yidianzixun-ctr-top1" target="_blank" rel="noopener">https://github.com/LogicJake/yidianzixun-ctr-top1</a> </p><p>典型的点击率预估问题，但是数据量非常大，总共有189766959条训练集，但只有5w条测试集。所以这题采用NN能取得比较好的效果，但由于训练集和测试集分布不一致的问题，有效抽取特征是一个难点。</p><a id="more"></a><h1 id="赛题背景"><a href="#赛题背景" class="headerlink" title="赛题背景"></a>赛题背景</h1><p>点击率（Click-Through Rate，简称CTR）预估是推荐算法的重要模块，通常用于在用户请求推荐系统时对内容进行排序，其结果直接影响产品的核心指标和用户的消费体验。在真实环境里，用户对某条内容是否产生点击行为的原因非常复杂，既包含内容本身的信息呈现和优质程度，又包含用户的基础属性（性别、年龄等）和个体偏好，甚至与当前所处的网络环境、地理位置也息息相关。如何抽取出这些复杂的诱因并对其进行建模学习，精准预估出海量用户对不同内容的点击概率，一直是推荐算法的一个重要研究方向。</p><h1 id="赛题内容"><a href="#赛题内容" class="headerlink" title="赛题内容"></a>赛题内容</h1><h2 id="任务"><a href="#任务" class="headerlink" title="任务"></a>任务</h2><p>本次大赛提供抽样用户过去一段时间内在一点资讯APP上的真实曝光和点击记录，以及所涉及用户和文章的基础属性，参赛者需要基于这些数据进行分析和建模。同时，大赛提供这批用户之后一段时间的曝光文章列表，参赛者最终提交给系统每个用户在之后曝光文章上的点击概率预估值（0-1之间的浮点数）。系统根据点击概率预估值和用户真实点击情况的差异，来评估预估任务的准确程度。 本次大赛提供的数据将隐去能代表用户身份的所有信息，对部分必要的敏感信息也进行了加密处理。</p><h2 id="评价指标"><a href="#评价指标" class="headerlink" title="评价指标"></a>评价指标</h2><p>大赛采用AUC值评估CTR预估任务的精准度，只考虑预测结果的相对顺序，消除指标分数对于阈值的依赖性。</p><h1 id="建模"><a href="#建模" class="headerlink" title="建模"></a>建模</h1><h2 id="特征工程"><a href="#特征工程" class="headerlink" title="特征工程"></a>特征工程</h2><p>特征工程只有两组，基于用户历史记录统计和基于全局统计。用户历史统计主要针对预测目标click和强相关的duration（消费时长）展开。这两个特征和预测目标强相关，所以基于历史信息统计，避免标签泄漏。全局信息下可以计算各组count，反应热度。</p><p>这个赛题有个明显的特点就是测试集和训练集分布不一致，训练集中每天的数据以百万记，但测试集当天的数据只有5w，明显测试集经过了采样，所以很多抽取的特征（譬如很有用的时间特征）都无法取得线上收益。第二名队伍“莲”基于上述问题，根据测试集的规模对训练集进行了分组，在组内进行特征抽取，从而做到抽取的特征分布一致，化腐朽为神奇，线上稳定上分。</p><h2 id="模型结构"><a href="#模型结构" class="headerlink" title="模型结构"></a>模型结构</h2><p>主体结构为deepfm，fm做显式二阶交叉，deep做高阶交叉。在做数据分析的时候，有些特征不同特征值的后验ctr相差较大，针对每个离散特征计算特征值对应的ctr的方差，以此来衡量后验ctr的分布差异。可以发现provice，device和city对应的特征值后验ctr分布差异明显。虽说基于对深度学习的假设，NN是可以自动学习这种分布，但是受限于数据不充分，NN无法充分学习到这种差异。因此我们将先验知识强加给NN，在现有的网络结构中显式加入“个性化学习组件”，类比FM结构让网络显式做二阶交叉。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> col <span class="keyword">in</span> tqdm([c <span class="keyword">for</span> c <span class="keyword">in</span> train.columns <span class="keyword">if</span> c <span class="keyword">not</span> <span class="keyword">in</span> [<span class="string">'userid'</span>, <span class="string">'docid'</span>, <span class="string">'click'</span>]]):</span><br><span class="line">    s = train.groupby([col])[<span class="string">'click'</span>].mean()</span><br><span class="line">    print(col, s.std(), s.max(), s.min())</span><br></pre></td></tr></table></figure><table><thead><tr><th>特征</th><th>ctr方差</th></tr></thead><tbody><tr><td>province</td><td>0.130</td></tr><tr><td>device</td><td>0.119</td></tr><tr><td>city</td><td>0.110</td></tr><tr><td>os</td><td>0.031</td></tr><tr><td>age</td><td>0.025</td></tr><tr><td>gender</td><td>0.032</td></tr><tr><td>category1st</td><td>0.036</td></tr><tr><td>category2st</td><td>0.053</td></tr></tbody></table><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://raw.githubusercontent.com/LogicJake/imghub/master/ydzz_model.png" alt="" title="">                </div>                <div class="image-caption"></div>            </figure><p>如上图所示，原始DNN的每层输入值乘上一个同维度的 scale 向量，该scale向量由一个独立的小网络得到，该小网络最后一层的激活函数是sigmoid，且✖️2，从而保证scale向量的值既能做到提升也能做到打压，拟合不同特征值巨大的分布差异。</p><p>前面做特征的时候也说到，click和duration是强相关的，duration是click之后的延伸，有点类似click和convert的关系。所以自然想到使用多任务模型结构，将duration信息迁移到click主任务。但实际尝试share bottom和mmoe均对click主任务无提升，其他队伍也尝试过ESMM。从一点资讯的实操结果看，多任务模型结构对click无提升，甚至还有损，在实际场景中使用多任务往往是出于推荐场景下的多指标综合提升。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>总的来说，比赛的数据很不错，充足的数据量给了NN很大的舞台，当然树模型也能做的很好。赛题主办方线下组织也很用心，在比赛之余看到了公司的文化和对员工的关怀。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最终成绩：第一&lt;br&gt;比赛地址：&lt;a href=&quot;https://tech.yidianzixun.com/competition/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tech.yidianzixun.com/competition/&lt;/a&gt;&lt;br&gt;源码：&lt;a href=&quot;https://github.com/LogicJake/yidianzixun-ctr-top1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/LogicJake/yidianzixun-ctr-top1&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;典型的点击率预估问题，但是数据量非常大，总共有189766959条训练集，但只有5w条测试集。所以这题采用NN能取得比较好的效果，但由于训练集和测试集分布不一致的问题，有效抽取特征是一个难点。&lt;/p&gt;
    
    </summary>
    
    
      <category term="比赛总结" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/tags/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
      <category term="深度学习" scheme="https://logicjake.github.io/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
    
      <category term="推荐系统" scheme="https://logicjake.github.io/tags/%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F/"/>
    
  </entry>
  
  <entry>
    <title>2021推广搜算法秋招面经</title>
    <link href="https://logicjake.github.io/2021/07/03/2021%E6%8E%A8%E5%B9%BF%E6%90%9C%E7%AE%97%E6%B3%95%E7%A7%8B%E6%8B%9B%E9%9D%A2%E7%BB%8F/"/>
    <id>https://logicjake.github.io/2021/07/03/2021%E6%8E%A8%E5%B9%BF%E6%90%9C%E7%AE%97%E6%B3%95%E7%A7%8B%E6%8B%9B%E9%9D%A2%E7%BB%8F/</id>
    <published>2021-07-03T21:37:56.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<h1 id="360-推荐算法"><a href="#360-推荐算法" class="headerlink" title="360-推荐算法"></a>360-推荐算法</h1><h2 id="一面"><a href="#一面" class="headerlink" title="一面"></a>一面</h2><p>算法题：1. 非递归的二叉树中序遍历；2.  指定概率列表，写一个随机采样算法</p><ol><li>介绍竞赛</li><li>召回的多样性</li><li>数据规模</li><li>ANN使用的什么工具</li><li>点击率预估为什么选择 lgb</li><li>排序为什么不使用基于 pair wise 的模型</li><li>了解 LambdaMart 算法吗</li><li>lightgbm 相较于 xgboost 的优势</li><li>了解的其他点击率预估算法</li><li>对特征交叉方面的个人理解</li><li>wide &amp; deep 模型 wide 部分和 deep 部分分别侧重学习什么信息</li><li>deepfm 一定优于 wide &amp; deep 吗</li><li>如何解决稀疏问题（回答的 hash embedding，不知道对不对）</li><li>在模型侧如何打压热门商品</li></ol><h2 id="二面"><a href="#二面" class="headerlink" title="二面"></a>二面</h2><ol><li>介绍比赛背景</li><li>怎么分工的，你负责的部分</li><li>为什么使用 w2v，有试过其他 embedding 工具吗</li><li>w2v 的参数怎么调节的</li><li>w2v 的 min_count 设置的多大，从什么方面考虑的</li><li>介绍 NN 的结构</li><li>怎么调参的</li><li>了解哪些正则化方法（layernorm，batchnom）</li><li>上面两者有什么区别</li><li>有没有遇到过梯度消失，梯度爆炸，怎么解决的</li><li>多任务模型结构了解吗</li><li>知道哪些点击率预估模型</li><li>点击率预估任务中负样本过多怎么办</li><li>下采样后，训练样本和线上 server 样本分布不一样怎么办（纠偏公式）</li><li>冷启动该怎么做</li><li>开放题：现在做一个搜索系统，你会考虑哪些东西，各方面都行</li><li>未来的职业规划</li></ol><h1 id="百度-推荐算法"><a href="#百度-推荐算法" class="headerlink" title="百度-推荐算法"></a>百度-推荐算法</h1><h2 id="一面-1"><a href="#一面-1" class="headerlink" title="一面"></a>一面</h2><p>算法题：leetcode 34</p><ol><li>研究方向</li><li>学习的课程</li><li>推荐流程中哪一步最重要</li><li>怎么办精排做的更准</li><li>特征挖掘比较核心的技术</li><li>特征挖掘方面，深度学习比机器学习的优点</li><li>深度学习有能力做特征交叉，那哪些部分还需要我们人工去做</li><li>如何解决新特征没有embedding的问题</li><li>面对一个新的深度模型，如何去分析（参数，结构）</li><li>介绍比赛</li><li>比赛分工</li><li>word2vec 向量作为embedding层初始化能提升多少效率</li><li>word2vec 工作原理</li><li>负采样的原理是什么</li><li>二分类的损失函数</li><li>多分类的损失函数</li><li>sofamax公式是啥</li><li>用的什么优化算法，为什么</li><li>你的模型和别人相比有什么优点</li><li>模型集成是模型越多越好吗</li><li>模型的差异体现在哪里</li><li>如何判断新加的特征是否有效</li><li>MapReduce的原理知道吗</li></ol><h2 id="二面-1"><a href="#二面-1" class="headerlink" title="二面"></a>二面</h2><p>算法题：leetcode 1047</p><ol><li>介绍比赛</li><li>比赛分工</li><li>itemcf如何改进的</li><li>手写itemcf伪代码</li><li>工业上召回怎么做的（双塔）</li><li>为什么物品的embedding离线生成，用户的embedding是在线计算，能反过来吗</li><li>如果只有用户历史点击序列，怎么生成用户和物品的embedding</li><li>word2vec和bert有什么区别</li><li>召回的样本标签怎么构造</li></ol><h2 id="三面"><a href="#三面" class="headerlink" title="三面"></a>三面</h2><p>聊实习，聊项目，聊人生</p><h1 id="触宝-推荐算法"><a href="#触宝-推荐算法" class="headerlink" title="触宝-推荐算法"></a>触宝-推荐算法</h1><h2 id="一面-2"><a href="#一面-2" class="headerlink" title="一面"></a>一面</h2><p>算法题：Leetcode 15. 三数之和</p><ol><li>介绍竞赛</li><li>多路召回的介绍</li><li>为什么选择lgb做点击率预测</li><li>后处理打压的原因和做法</li><li>为什么用transformer对有序序列处理</li><li>transformer里残差连接的作用</li></ol><h2 id="二面-2"><a href="#二面-2" class="headerlink" title="二面"></a>二面</h2><ol><li>简述树的前序遍历，中序遍历，后序遍历</li><li>什么是排序算法的稳定性，哪些排序算法是稳定的</li><li>了解哪些传统的分类算法</li><li>FM了解吗</li><li>xgboost了解吗</li><li>梯度消失和梯度爆炸，解决方法</li><li>dropout和batchnorm可以放在一起用吗，顺序是怎么样的</li><li>数值特征放到神经网络之前需要做哪些处理</li><li>如何在端到端中进行自动分桶</li><li>如何保证用户向量和物品向量在一个向量空间</li><li>多任务学习了解吗</li><li>多任务loss怎么结合</li><li>联合训练和交替训练的区别</li><li>推荐的整体流程</li><li>召回有哪些做法</li><li>精排模型有哪些</li><li>推荐有哪些指标</li><li>auc的变种</li><li>推荐有哪些在线指标</li><li>介绍竞赛项目</li><li>如何评估NN的特征重要性</li><li>树模型的特征重要性怎么计算的</li><li>现在反思项目有哪些优化点</li><li>竞赛如何分工合作的</li></ol><h2 id="三面-1"><a href="#三面-1" class="headerlink" title="三面"></a>三面</h2><ol><li>介绍竞赛</li><li>如果有新的商品上架，优化摆放位置，如何建模，有什么约束</li><li>对触宝的了解</li><li>以后的打算</li></ol><h1 id="华为-消费者BG"><a href="#华为-消费者BG" class="headerlink" title="华为-消费者BG"></a>华为-消费者BG</h1><h2 id="一面（FX计划挂）"><a href="#一面（FX计划挂）" class="headerlink" title="一面（FX计划挂）"></a>一面（FX计划挂）</h2><p>算法题：Leetcode 200. 岛屿数量 Leetcode 397. 整数替换</p><ol><li>介绍比赛</li><li>介绍研究方向</li><li>介绍聚类kmeans，还知道哪些聚类方法</li><li>如何确定聚类数量</li><li>介绍KNN</li><li>TF-IDF 计算方法</li><li>余弦距离和欧氏距离区别</li><li>卷积神经网络的变种有哪些</li><li>深度学习克服过拟合的方法</li><li>dropout在循环神经网络需要注意什么</li></ol><h2 id="一面（正式批）"><a href="#一面（正式批）" class="headerlink" title="一面（正式批）"></a>一面（正式批）</h2><p>算法题：Leetcode 1143. 最长公共子序列</p><ol><li>说一下笔试题的思路</li><li>介绍竞赛</li><li>介绍实习</li></ol><h2 id="二面-3"><a href="#二面-3" class="headerlink" title="二面"></a>二面</h2><p>算法题：大数据找中位数</p><ol><li>介绍研究内容、研究背景、指标提升情况</li><li>任选一个竞赛讲一下</li><li>基于图片emb的召回和基于文本emb的召回哪个效果好，为什么</li></ol><h2 id="三面-2"><a href="#三面-2" class="headerlink" title="三面"></a>三面</h2><p>没问技术问题，忽略</p><h1 id="shein-推荐算法"><a href="#shein-推荐算法" class="headerlink" title="shein-推荐算法"></a>shein-推荐算法</h1><h2 id="一面（挂）"><a href="#一面（挂）" class="headerlink" title="一面（挂）"></a>一面（挂）</h2><p>算法题：leetcode 802. 找到最终的安全状态</p><ol><li>介绍比赛</li><li>ndcg计算方式</li><li>如果使用auc，而不用ndcg，会有什么问题</li><li>集成模型bagging和boosting的区别</li><li>bagging和boosting在偏差和方差上的区别</li><li>xgboost和gbdt的区别</li><li>xgboost的叶子生成方式</li><li>lightgbm在计算速度上做了哪些优化</li><li>模型打压热门有哪些做法</li><li>知道ee吗</li><li>skip-gram和cbow的区别</li><li>skip-gram和cbow哪一个预测速度快</li><li>skip-gram和cbow哪一个对稀疏词效果好</li><li>skip-gram 负采样和分层softmax的时间复杂度各是多少</li><li>multi-head的作用是什么</li><li>self-attention为什么要缩放</li></ol><h1 id="拼多多-推荐算法"><a href="#拼多多-推荐算法" class="headerlink" title="拼多多-推荐算法"></a>拼多多-推荐算法</h1><h2 id="一面-3"><a href="#一面-3" class="headerlink" title="一面"></a>一面</h2><p>算法题：Leetcode 82. 删除排序链表中的重复元素 II</p><ol><li>介绍简历上的竞赛</li><li>介绍ANN的召回</li><li>工业界中，排序使用召回的信息会有什么问题</li><li>工业界中，w2v向量做为Embedding层初始化会有什么问题</li><li>比赛分工</li></ol><h2 id="二面-4"><a href="#二面-4" class="headerlink" title="二面"></a>二面</h2><p>算法题：股票一次买入和卖出，求最佳收益和买入卖出点</p><ol><li>介绍竞赛</li><li>介绍研究方向</li><li>如果资源无限，去掉前置的召回阶段，直接进行全量的排序，模型的效果会有什么影响</li><li>深度学习在推荐方面的应用</li></ol><h2 id="三面-3"><a href="#三面-3" class="headerlink" title="三面"></a>三面</h2><ol><li>了解拼多多吗</li><li>为什么想做推荐</li><li>从哪些地方了解的相关知识</li><li>介绍推荐系统</li><li>双塔召回为什么离线索引物品embedding</li><li>概率题：北京一般有雾霾的概率是1/4，有三个同事都说今天有雾霾，但他们说真话的概率为3/4，问今天真的有雾霾的概率是多少。</li></ol><h1 id="哔哩哔哩-广告推荐算法"><a href="#哔哩哔哩-广告推荐算法" class="headerlink" title="哔哩哔哩-广告推荐算法"></a>哔哩哔哩-广告推荐算法</h1><h2 id="一面-4"><a href="#一面-4" class="headerlink" title="一面"></a>一面</h2><p>算法题：leetcode 518. 零钱兑换 II</p><ol><li>介绍多任务工作</li><li>介绍优化器算法，演化进程</li><li>LR有大量的特征交叉，如何做特征筛选</li><li>L1和L2正则化的区别</li><li>神经网络初始化方法</li><li>神经网络都初始化成相同的参数会有什么影响，如何缓解</li><li>粗排是怎么做</li></ol><h2 id="二面-5"><a href="#二面-5" class="headerlink" title="二面"></a>二面</h2><p>问实习，问未来打算</p><h1 id="阿里巴巴-淘特-推荐广告"><a href="#阿里巴巴-淘特-推荐广告" class="headerlink" title="阿里巴巴-淘特-推荐广告"></a>阿里巴巴-淘特-推荐广告</h1><h2 id="一面-5"><a href="#一面-5" class="headerlink" title="一面"></a>一面</h2><ol><li>介绍实习</li><li>端外的转换label怎么得到的</li><li>介绍竞赛</li></ol><h2 id="二面-6"><a href="#二面-6" class="headerlink" title="二面"></a>二面</h2><ol><li>介绍实习</li><li>阿里和字节算法的优缺点</li><li>介绍广告整体的请求背景</li><li>介绍现在推荐的热点，发展方向</li></ol><h1 id="美团-广告推荐算法"><a href="#美团-广告推荐算法" class="headerlink" title="美团-广告推荐算法"></a>美团-广告推荐算法</h1><h2 id="一面-6"><a href="#一面-6" class="headerlink" title="一面"></a>一面</h2><ol><li>介绍实习内容</li><li>多任务的 loss 怎么设计</li><li>有没有考虑辅助任务带偏主任务的情况</li><li>在线指标有哪些</li><li>广告主价值是什么</li><li>多任务更应该关注哪些在线指标</li><li>为什么要下掉ID类特征</li><li>ocpx里的 bid 出价指什么</li><li>过拟合的解决方式</li><li>dropout原理</li><li>广告系统的有哪些模块</li><li>混排流程</li><li>混排在排序侧做还是出价侧做</li><li>广告推荐各个模块用的 point wise，pair wise 还是 list wise（答案不唯一）</li><li>概率题：两个人抛硬币，抛到正面的人获胜，先手抛获胜的概率是多少</li><li>算法题：给定一串数列长度为n（无序），给定子序列长度为k（k&lt;=n）,求出长度为k的子序列，使其数字总和为最大值的所有情况。值域（-无穷，+无穷）,输出为序列元素的idx。数列[1,2,3] k=3 ，输出[0,1,2], k=2 输出[1,2]<br>[1,2,3,3] k=3 输出[1,2,3], k=1 输出[2], [3]</li></ol><h2 id="二面-7"><a href="#二面-7" class="headerlink" title="二面"></a>二面</h2><ol><li>浅拷贝和深拷贝的区别，python里的具体实现</li><li>c++栈和堆的区别</li><li>最大后验和最大似然的区别</li><li>xgboost、lightgbm、GBDT之间的区别</li><li>树的分裂增益是什么</li><li>介绍实习内容</li><li>介绍竞赛</li><li>职业规划</li><li>算法题：数字0和1的矩阵，判断矩阵中的1能否围成一个圈</li></ol><h2 id="三面-4"><a href="#三面-4" class="headerlink" title="三面"></a>三面</h2><ol><li>介绍实习</li><li>多任务中有没有辅助任务带偏主任务的情况</li><li>介绍竞赛</li><li>比赛分工</li><li>和前排方案的差距</li></ol><h1 id="爱奇艺-广告推荐算法"><a href="#爱奇艺-广告推荐算法" class="headerlink" title="爱奇艺-广告推荐算法"></a>爱奇艺-广告推荐算法</h1><h2 id="一面-7"><a href="#一面-7" class="headerlink" title="一面"></a>一面</h2><ol><li>介绍竞赛</li><li>xgboost、lightgbm、GBDT之间的区别</li><li>NN的优化调参思路</li><li>有没有试过BN，效果怎么样</li><li>算法题：Leetcode 5. 最长回文子串</li></ol><h2 id="二面-8"><a href="#二面-8" class="headerlink" title="二面"></a>二面</h2><ol><li>介绍竞赛</li><li>商品的属性 embedding 可以怎么得到</li><li>工业场景下召回的主流做法</li><li>协同过滤和双塔模型各有什么缺点</li><li>召回双塔最后加DNN做交叉，产生的计算复杂度会在哪些方面</li><li>如何解决冷门商品召回</li><li>介绍实习内容</li><li>算法题：Leetcode 142. 环形链表 II</li></ol><h2 id="三面-5"><a href="#三面-5" class="headerlink" title="三面"></a>三面</h2><ol><li>介绍实习</li><li>为什么要做多目标</li><li>业界中多目标怎么做的</li><li>熟悉什么编程语言</li><li>会用 tensorflow 吗</li><li>什么时候毕业</li><li>有哪些 offer</li></ol><h1 id="网易云音乐-推荐算法"><a href="#网易云音乐-推荐算法" class="headerlink" title="网易云音乐-推荐算法"></a>网易云音乐-推荐算法</h1><h2 id="一面-8"><a href="#一面-8" class="headerlink" title="一面"></a>一面</h2><ol><li>聊项目</li><li>如何改进 itemcf 的</li><li>session 截断怎么做的</li><li>ANN 的用户向量表示怎么得到的</li><li>特征工程怎么做的</li><li>算法题：字符串逆序并转换大小写：”Hello Word” 转换为 “wORLD hELLO”</li></ol><h2 id="二面-9"><a href="#二面-9" class="headerlink" title="二面"></a>二面</h2><ol><li>介绍实习</li><li>mmoe和esmm解决的问题是什么</li><li>上面两者的loss函数是什么</li><li>介绍精排模型结构</li><li>DIN和DIEN的区别</li><li>大规模稀疏特征优化器如何选择</li><li>如何缓解模型过拟合</li><li>L1正则和L2正则的区别</li><li>树的分裂方式</li><li>GBDT和随机森林的区别</li></ol><h1 id="快手-社区推荐算法"><a href="#快手-社区推荐算法" class="headerlink" title="快手-社区推荐算法"></a>快手-社区推荐算法</h1><h2 id="一面-9"><a href="#一面-9" class="headerlink" title="一面"></a>一面</h2><ol><li>介绍实习</li><li>精排模型结构</li><li>介绍竞赛</li><li>算法题：浮点数的三次方根</li></ol><h2 id="二面-10"><a href="#二面-10" class="headerlink" title="二面"></a>二面</h2><ol><li>知道哪些字符串匹配算法，介绍一下kmp</li><li>快排算法，是否是稳定的，如何改进成稳定的</li><li>两个骰子根据各自的高斯分布出随机值，随机选取一个骰子摇，得到一个随机数，求他是哪个骰子得到的</li><li>如何求目标函数的最小值</li><li>nn 怎么求导</li><li>rnn 怎么求导</li><li>过拟合的缓解方法</li><li>介绍实习内容</li></ol><h1 id="小红书-社区算法"><a href="#小红书-社区算法" class="headerlink" title="小红书-社区算法"></a>小红书-社区算法</h1><h2 id="一面-10"><a href="#一面-10" class="headerlink" title="一面"></a>一面</h2><p>算法题：leetcode 239. 滑动窗口最大值</p><ol><li>介绍研究内容</li><li>网络表示在工业界的应用现状</li><li>介绍竞赛里如何作的debias</li><li>竞赛过程中哪些地方提升明显</li><li>介绍以下transformer</li><li>如何学习机器学习的</li><li>业界推荐算法下的痛点和热点</li><li>介绍LR</li><li>LR的损失函数为什么是连乘</li><li>介绍AUC的含义</li><li>roc曲线的两个坐标轴分别是什么</li><li>测试集负样本不变，正样本变为一半，auc指标有什么变化</li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;360-推荐算法&quot;&gt;&lt;a href=&quot;#360-推荐算法&quot; class=&quot;headerlink&quot; title=&quot;360-推荐算法&quot;&gt;&lt;/a&gt;360-推荐算法&lt;/h1&gt;&lt;h2 id=&quot;一面&quot;&gt;&lt;a href=&quot;#一面&quot; class=&quot;headerlink&quot; titl
      
    
    </summary>
    
    
      <category term="面经" scheme="https://logicjake.github.io/categories/%E9%9D%A2%E7%BB%8F/"/>
    
    
  </entry>
  
  <entry>
    <title>2021推广搜算法暑假实习面经</title>
    <link href="https://logicjake.github.io/2021/03/23/2021%E6%8E%A8%E5%B9%BF%E6%90%9C%E6%9A%91%E5%81%87%E5%AE%9E%E4%B9%A0%E9%9D%A2%E7%BB%8F/"/>
    <id>https://logicjake.github.io/2021/03/23/2021%E6%8E%A8%E5%B9%BF%E6%90%9C%E6%9A%91%E5%81%87%E5%AE%9E%E4%B9%A0%E9%9D%A2%E7%BB%8F/</id>
    <published>2021-03-23T21:53:56.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<p>过完年以来好像就一直在准备暑假实习的事情，投了不少简历，面试了（和待面试）很多公司，积累了不少面经。虽然面经都在牛客发过，但总信不过把自己的东西放在别人的网站上，这里纯当做个备份吧。</p><h1 id="卡牛科技"><a href="#卡牛科技" class="headerlink" title="卡牛科技"></a>卡牛科技</h1><p>面试流程非常快，基本一天一个流程。面试官和 HR 也很好，主要围绕项目做一些扩展。公司主要算法业务为广告推荐，最近新引入了电商，做商品推荐。实习生可以住公司统一安排的二人间公寓。</p><h2 id="一面"><a href="#一面" class="headerlink" title="一面"></a>一面</h2><ol><li>介绍比赛项目，前排用的什么方案</li><li>机器学习方法和深度学习方法的应用场景</li><li>为什么使用 lightgbm 模型，优缺点</li><li>简述 word2vec 的技巧</li><li>transformer在序列模型中大放异彩，你认为他和传统的rnn模型相比强在哪里？</li><li>工程实践问题，项目中类别分布不平衡怎么办，负样本多怎么办</li><li>如果使用了下采样破坏了真实分布，如何在上线之后将预测的概率拉回真实的分布</li></ol><h2 id="二面"><a href="#二面" class="headerlink" title="二面"></a>二面</h2><ol><li>介绍比赛流程</li><li>对冷门商品的解决方法</li><li>如何保证召回的多样性</li><li>如何推荐新上架商品</li><li>模型推荐购买过的商品怎么处理</li><li>lightgbm 与 xgboost 区别</li><li>广告反欺诈中如果给 GPS 信息怎么处理</li></ol><h1 id="蚂蚁金服—支付宝—广告算法"><a href="#蚂蚁金服—支付宝—广告算法" class="headerlink" title="蚂蚁金服—支付宝—广告算法"></a>蚂蚁金服—支付宝—广告算法</h1><p>阿里好多部门在过完年之后就开始在各大群里发 jd，声势十分浩大。群里有人发了支付宝部门的 jd，说是有学长直推，而且方向和我很匹配，就去联系了一下学长，学长很 nice，帮我改简历，提了很多建议。阿里支持预先面，也是我第一次大厂面试，总共两轮技术面，本来有的交叉面被取消了。</p><h2 id="一面-1"><a href="#一面-1" class="headerlink" title="一面"></a>一面</h2><ol><li>映象比较深刻的比赛</li><li>在 kdd cup 竞赛中负责内容</li><li>介绍改进的itemcf</li><li>用户 embedding 和商品 embedding 不在一个特征空间，怎么计算相似性</li><li>有没有考虑把用户和商品的关系建为异构图</li><li>后处理如何打压热门商品</li><li>介绍 kdd cup 第一名的方案</li><li>介绍竞赛广告反欺诈的比赛</li></ol><h2 id="二面-1"><a href="#二面-1" class="headerlink" title="二面"></a>二面</h2><p>二面应该是个领导，约的会议室快到时间了，所以问的比较简单，了解了一下竞赛项目和到岗时间就没了。</p><h1 id="字节跳动—商业变现—广告算法"><a href="#字节跳动—商业变现—广告算法" class="headerlink" title="字节跳动—商业变现—广告算法"></a>字节跳动—商业变现—广告算法</h1><p>本来想把字节放在后面投的，所以只在系统里填了简历，没有投岗位，但 HR 加微信问我要不要试试他们部门，所以就阴差阳错开始了，总共三轮技术面。</p><h2 id="一面-2"><a href="#一面-2" class="headerlink" title="一面"></a>一面</h2><p>算法题：leetcode 排序矩阵查找，leetcode 102 二叉树的层序遍历</p><ol><li>介绍一个你认为做的最好的比赛</li><li>ANN用的什么工具，介绍一下原理</li><li>为什么选择Annoy</li><li>还知道其他ANN算法吗？介绍了一下局部敏感hash</li><li>做用户商品交互特征的时候，你知道业界是怎么做的？扯了一下DIN模型的和目标商品的attention做法</li><li>lightgbm和xgboost的区别</li><li>排序阶段你知道业界是怎么做的？说了一下点击率模型：deepfm，nfm，wide &amp; deep，dcn，deepcrossing</li><li>介绍一下word2vec</li><li>介绍一下transformer</li><li>bert的两种预训练方式</li><li>为什么要用mask？说了padding mask和sequence mask</li><li>为什么要sequence mask？防止信息穿越</li></ol><h2 id="二面-2"><a href="#二面-2" class="headerlink" title="二面"></a>二面</h2><p>算法题：求从左上角到右下角的最小开销。给定一个二维数组arr[i][j]，数组中每个点表示经过该点的开销，求从左上角为起点，右下角为终点的最小开销，在每个点时只能往右或者往下走，同时中途可能会有障碍，即有些点不能走，obs[i][j]=1时表示(i, j)不能走。dp和dp状态压缩。</p><ol><li>介绍一个比赛</li><li>ANN用的什么工具，介绍一下原理</li><li>如何提高冷门商品的推荐效果的</li><li>了不了解大数据框架</li><li>说一下你熟悉的linux命令</li><li>一个文件每行一个数字，用命令统计所有数的平均值和数字个数</li><li>逻辑回归损失函数和求导</li><li>介绍一下你了解的优化器和各自的优缺点</li><li>Adam和Adagrad的区别</li></ol><h2 id="三面"><a href="#三面" class="headerlink" title="三面"></a>三面</h2><p>算法题：有一个长度为n的数组，求一个数k，k的取值区间为[1, n-1]，使得数组的前k个数和后n-k个数的方差和最小。要求化简方差公式，达到计算子序列方差的时间复杂度为O(n)。解出来后又要求空间复杂度为常数级别。</p><ol><li>写逻辑回归的logloss损失函数</li><li>逻辑回归损失函数可以用mse吗（从梯度角度）</li><li>逻辑回归建模，如果只有9个正样本，一个负样本，那么有一列特征，这个特征对于的权重是正还是负  </li><li>介绍认为做的最好的项目</li><li>项目中如何缓解曝光偏差的</li><li>Embedding ANN召回用的什么工具</li></ol><h1 id="快手-社区科学部-推荐算法"><a href="#快手-社区科学部-推荐算法" class="headerlink" title="快手-社区科学部-推荐算法"></a>快手-社区科学部-推荐算法</h1><p>快手是先统一面试，技术面结束后，HR 联系才知道具体的小组。快手的面试风格感觉和字节很像，先做两道算法题。一面先做题，第二题卡了好久才做完，所以剩下的时间不多，面试官也没多问，就简单了解了一下竞赛项目。二面也是先做题，基础问的不多，也问到了“逻辑回归损失函数可以用mse吗”，大部分时间都在和我讨论 kdd cup 的项目流程是否合理和为什么要这么做。快手总共两轮技术面，隔了大概两周才有 HR 沟通后续。</p><h1 id="腾讯-应用宝-推荐算法"><a href="#腾讯-应用宝-推荐算法" class="headerlink" title="腾讯-应用宝-推荐算法"></a>腾讯-应用宝-推荐算法</h1><p>投的腾讯微视，被应用宝捞了。</p><h2 id="一面-3"><a href="#一面-3" class="headerlink" title="一面"></a>一面</h2><ol><li>介绍一个最有把握的比赛</li><li>介绍比赛中itemcf具体做法</li><li>在itemcf中如何引入时间间隔信息和位置间隔信息</li><li>基于商品属性embedding的召回这么做的</li><li>Annoy的原理</li><li>基于w2v的ANN怎么做的</li><li>w2v的原理</li><li>w2v的loss函数</li><li>loss函数还有哪些</li><li>如何解决冷启动问题</li><li>如何评价召回的效果</li><li>如何把召回的信息引入到排序</li><li>GBDT，xgboost，lightgbm改进的过程</li><li>简单推导xgboost</li><li>构建一棵树分裂特征怎么选择</li><li>计算分裂增益的方法有哪些</li><li>LSTM的门机制</li><li>LSTM相较于RNN的优势</li><li>transformer的机制</li><li>推荐里面深度模型的了解</li><li>NN怎么达到泛化性</li><li>如何保证的稀疏性</li><li>L1正则为什么可以达到模型的稀疏性</li></ol><h2 id="二面-3"><a href="#二面-3" class="headerlink" title="二面"></a>二面</h2><ol><li>比赛分工</li><li>embedding存在之前没有出现过id怎么办</li><li>w2v的样本怎么构造</li><li>有没有过滤低频id</li><li>cbow 与 skip-gram 的区别和优缺点</li><li>多任务学习有哪些结构</li><li>多个召回怎么合并的</li><li>多个召回分数之间是可比的吗</li><li>各个召回的权重是怎么选取的</li><li>推荐系统里面是如何考虑冷门商品</li><li>新增一路召回，在排序阶段需要做什么改进</li><li>树模型对离散特征怎么处理的</li><li>树模型怎么决定一个叶子结点是否要分裂</li><li>xgboost正则化项和什么有关</li></ol><h2 id="三面-1"><a href="#三面-1" class="headerlink" title="三面"></a>三面</h2><p>做了三道算法题：leetcode 53.最大子序和，leetcode 75.颜色分类，leetcode 442.数组中重复的数据</p><h2 id="四面"><a href="#四面" class="headerlink" title="四面"></a>四面</h2><ol><li>介绍项目</li><li>算法题：剑指 Offer 60.n个骰子的点数</li></ol><h1 id="百度"><a href="#百度" class="headerlink" title="百度"></a>百度</h1><p>应该是百度的实习专场，三轮技术连着面。</p><h2 id="一面-4"><a href="#一面-4" class="headerlink" title="一面"></a>一面</h2><p>算法题：leetcode 70.爬楼梯</p><ol><li>介绍竞赛项目</li><li>竞赛数据规模</li><li>ANN中用户的embedding怎么得到的</li><li>w2v的召回具体怎么做的</li><li>ANN的工具用的啥</li><li>基于深度的召回怎么做的</li><li>排序阶段的特征有哪些</li><li>后打压怎么做的</li></ol><h2 id="二面-4"><a href="#二面-4" class="headerlink" title="二面"></a>二面</h2><p>算法题：假设现在有很多海岛，有些海岛之间有桥连接，你已知海岛连接情况。 ① 我现在想从到 A岛 去 B岛，问是否能通过陆路到达（过桥） ②如果可以，最少需要过几次桥 ③输出一条最短的路径 （P.S. 最好可以写一个非递归的形式）</p><ol><li>介绍竞赛</li><li>评估标准是啥</li><li>哪些特征比较有用</li><li>多路召回的分数为什么要相加</li><li>有没有分析 bad case</li><li>优化思路</li><li>讲一下推荐的方法</li><li>一个全连接层有多少参数</li><li>介绍 attention</li></ol><h2 id="三面（手百搜索部门）"><a href="#三面（手百搜索部门）" class="headerlink" title="三面（手百搜索部门）"></a>三面（手百搜索部门）</h2><ol><li>介绍研究方向</li><li>论文中 embedding 效果的评价方法和指标</li><li>为什么选择数据挖掘这个方向</li><li>对于实习的目标</li><li>反问部门业务和技术栈</li></ol><h1 id="美团-外卖-搜索算法"><a href="#美团-外卖-搜索算法" class="headerlink" title="美团-外卖-搜索算法"></a>美团-外卖-搜索算法</h1><h2 id="一面-5"><a href="#一面-5" class="headerlink" title="一面"></a>一面</h2><ol><li>介绍竞赛</li><li>工程挑战有哪些</li><li>介绍transformer</li><li>介绍self-attention</li><li>算法题：Leetcode 8. 字符串转换整数 (atoi)，考虑科学计数法</li></ol><h2 id="二面-5"><a href="#二面-5" class="headerlink" title="二面"></a>二面</h2><p>算法题：合并两个有序数组并去重</p><ol><li>介绍竞赛</li><li>如何提高冷门商品的推荐效果</li><li>为什么能缓解冷门商品的召回</li><li>怎么评价冷门商品的推荐效果</li><li>排序特征如何对冷门商品做倾向的</li><li>根据点击次数提权，整体效果会下降吗</li><li>如何平衡全量商品和冷门商品的指标</li><li>如何使用点击序列的</li><li>为什么使用lightgbm</li><li>lightgbm，xgboost的原理</li><li>如何训练embedding</li><li>概率题：有两个无限大且不透明的箱子，100个白球和100个黑球。100个白球和100个黑球可以任意放置在两个箱子里，求能摸到黑球的最大概率</li></ol><h1 id="拼多多-搜索广告算法"><a href="#拼多多-搜索广告算法" class="headerlink" title="拼多多-搜索广告算法"></a>拼多多-搜索广告算法</h1><h2 id="一面-6"><a href="#一面-6" class="headerlink" title="一面"></a>一面</h2><p>算法题：leetcode 687. 最长同值路径</p><ol><li>介绍竞赛</li><li>itemcf 的改进点</li><li>如何提高冷门商品的推荐效果</li><li>基于深度学习的召回怎么做的</li><li>现在来看，方案有哪些改进的点</li></ol><h2 id="二面-6"><a href="#二面-6" class="headerlink" title="二面"></a>二面</h2><p>算法题：leetcode 322. 零钱兑换</p><ol><li>介绍竞赛</li><li>对稀疏特征有没有做什么处理</li><li>介绍基于商品属性的向量召回</li><li>哪一种召回效果最好</li><li>itemcf 如何改进的</li><li>介绍学校研究方向</li></ol><h2 id="三面-2"><a href="#三面-2" class="headerlink" title="三面"></a>三面</h2><ol><li>介绍竞赛</li><li>数据规模</li><li>如何提高冷门商品的推荐效果</li><li>介绍比赛的指标</li><li>排序任务为什么要建模为二分类</li><li>为什么不直接排序而需要先召回</li><li>如何构造分类样本</li><li>有没有了解前排方案</li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;过完年以来好像就一直在准备暑假实习的事情，投了不少简历，面试了（和待面试）很多公司，积累了不少面经。虽然面经都在牛客发过，但总信不过把自己的东西放在别人的网站上，这里纯当做个备份吧。&lt;/p&gt;
&lt;h1 id=&quot;卡牛科技&quot;&gt;&lt;a href=&quot;#卡牛科技&quot; class=&quot;head
      
    
    </summary>
    
    
      <category term="面经" scheme="https://logicjake.github.io/categories/%E9%9D%A2%E7%BB%8F/"/>
    
    
  </entry>
  
  <entry>
    <title>零基础入门推荐系统新闻推荐正式赛-赛后分享</title>
    <link href="https://logicjake.github.io/2021/02/07/%E9%9B%B6%E5%9F%BA%E7%A1%80%E5%85%A5%E9%97%A8%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E6%AD%A3%E5%BC%8F%E8%B5%9B-%E8%B5%9B%E5%90%8E%E5%88%86%E4%BA%AB/"/>
    <id>https://logicjake.github.io/2021/02/07/%E9%9B%B6%E5%9F%BA%E7%A1%80%E5%85%A5%E9%97%A8%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E6%AD%A3%E5%BC%8F%E8%B5%9B-%E8%B5%9B%E5%90%8E%E5%88%86%E4%BA%AB/</id>
    <published>2021-02-07T13:20:01.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<p>最终成绩：2 / 5272<br>比赛地址：<a href="https://tianchi.aliyun.com/competition/entrance/531842" target="_blank" rel="noopener">https://tianchi.aliyun.com/competition/entrance/531842</a><br>源码：<a href="https://github.com/LogicJake/tianchi-news-recommendation" target="_blank" rel="noopener">https://github.com/LogicJake/tianchi-news-recommendation</a></p><h1 id="赛题背景"><a href="#赛题背景" class="headerlink" title="赛题背景"></a>赛题背景</h1><p>赛题以新闻APP中的新闻推荐为背景，要求选手根据用户历史浏览点击新闻文章的数据信息预测用户未来点击行为，即用户的最后一次点击的新闻文章，测试集对最后一次点击行为进行了剔除。</p><a id="more"></a><h1 id="数据介绍"><a href="#数据介绍" class="headerlink" title="数据介绍"></a>数据介绍</h1><p>赛题以预测用户未来点击新闻文章为任务，数据集报名后可见并可下载，该数据来自某新闻APP平台的用户交互数据，包括30万用户，近300万次点击，共36万多篇不同的新闻文章，同时每篇新闻文章有对应的embedding向量表示。为了保证比赛的公平性，将会从中抽取20万用户的点击日志数据作为训练集，5万用户的点击日志数据作为测试集A，5万用户的点击日志数据作为测试集B。</p><h2 id="数据表"><a href="#数据表" class="headerlink" title="数据表"></a>数据表</h2><p>train_click_log.csv：训练集用户点击日志<br>testA_click_log.csv：测试集用户点击日志<br>articles.csv：新闻文章信息数据表<br>articles_emb.csv：新闻文章embedding向量表示<br>sample_submit.csv：提交样例文件  </p><details><summary>详细数据字段说明</summary><table><thead><tr><th align="center">Field</th><th align="center">Description</th></tr></thead><tbody><tr><td align="center">user_id</td><td align="center">用户id</td></tr><tr><td align="center">click_article_id</td><td align="center">点击文章id</td></tr><tr><td align="center">click_timestamp</td><td align="center">点击时间戳</td></tr><tr><td align="center">click_environment</td><td align="center">点击环境</td></tr><tr><td align="center">click_deviceGroup</td><td align="center">点击设备组</td></tr><tr><td align="center">click_os</td><td align="center">点击操作系统</td></tr><tr><td align="center">click_country</td><td align="center">点击城市</td></tr><tr><td align="center">click_region</td><td align="center">点击地区</td></tr><tr><td align="center">click_referrer_type</td><td align="center">点击来源类型</td></tr><tr><td align="center">article_id</td><td align="center">文章id，与click_article_id相对应</td></tr><tr><td align="center">category_id</td><td align="center">文章类型id</td></tr><tr><td align="center">created_at_ts</td><td align="center">文章创建时间戳</td></tr><tr><td align="center">words_count</td><td align="center">文章字数</td></tr><tr><td align="center">emb_1,emb_2,…,emb_249</td><td align="center">文章embedding向量表示</td></tr></tbody></table></details>  <h1 id="方案总结"><a href="#方案总结" class="headerlink" title="方案总结"></a>方案总结</h1><p>为了构造稳定的线下验证，从训练集用户中随机采样5w个用户作为作为线下验证集用户，将验证集用户的最后一次点击记录从原训练集的点击日志中剔除。合并这时候的训练集点击日志和测试集点击日志作为总的历史点击记录，预测验证集用户的最后一次点击作为线下验证，实验证明该验证方式很稳定，和线上指标同增同减且相差不大。</p><h1 id="召回"><a href="#召回" class="headerlink" title="召回"></a>召回</h1><p>本方案采用了3路召回，通过不同策略的差异性提高整体新闻的召回率。新闻推荐有强热点依赖，用户往往会短时间内点击许多相关新闻，所以需要从历史点击序列中挖掘新闻之间的关系信息。</p><h2 id="改进版-itemcf"><a href="#改进版-itemcf" class="headerlink" title="改进版 itemcf"></a>改进版 itemcf</h2><p>原始的 itemcf 将用户点击过的新闻看做一个无序的集合，但在实际应用中，应该考虑点击次序带来的影响。在计算同一序列中两个新闻的相似度时，不仅需要考虑共现次数，也需要考虑两个新闻之间的次序关系。同一点击序列中两个新闻位置越远，相关性应该减小。新闻对顺序和逆序的权重也不同，在点击序列A，B，C中，”BC”这样的正序权重应该大于”BA”这样的逆序权重。更多关于 itemcf 的改进可以看之前 KDD CUP 的相关方案总结。</p><blockquote><p>日志中所有出现过的新闻只有3w多个，而整个新闻库中却有30多万。用户可能会点击一些没有在日志数据中出现的新闻。从线下验证集来看，大概有12%待预测新闻没有出现在历史点击记录里。为了缓解冷启动问题，在计算新闻相似度的时候可以考虑加入内容相似度。但是基于数据集给的新闻 embedding 向量表示做向量召回的效果很差劲，所以最后在 itemcf 中没有引入新闻的内容相似度。</p></blockquote><p>建立新闻的相似度关系后，进入到召回阶段，根据用户的历史点击新闻，结合相似度选择 TOP100 关联新闻。选取关联新闻时，除了考虑和历史点击新闻的相似度，还要加入位置距离衰减。新闻点击是强热点相关，所以历史点击新闻对下一次点击预测的影响传播不会太远。在实际测试中，利用所有历史点击新闻做召回，hitrate_5 指标只有0.20，限定只用最近点击的两个新闻来做召回的话，可以大幅提升至0.33。</p><h2 id="基于网络关系的召回"><a href="#基于网络关系的召回" class="headerlink" title="基于网络关系的召回"></a>基于网络关系的召回</h2><p>该方法源自”Bipartite network projection and personal recommendation”，代码采用自<a href="https://tianchi.aliyun.com/forum/postDetail?postId=104936" target="_blank" rel="noopener">论坛开源</a>。该方法也分为两个阶段，新闻相似度计算和基于用户点击历史的新闻召回。在相似度计算阶段，通过用户将两个新闻连接起来。计算相似度时考虑两种因素：1)两个新闻的共同被点击用户过多，则相似度减少；2)共同被点击用户的点击新闻过多，相似度也要减少。基于用户点击历史的新闻召回和 itemcf 类似，不再赘述。类似地，在召回时只利用了最新一次点击的新闻做召回，相较于利用全量历史记录召回，效果提高了10%。</p><h2 id="w2v-向量召回"><a href="#w2v-向量召回" class="headerlink" title="w2v 向量召回"></a>w2v 向量召回</h2><p>除了使用人工规则从序列中提取相似度，我们还可以使用序列学习模型 Word2Vec 为新闻学习向量表示。将用户的新闻点击序列作为句子喂到 Word2Vec 模型，然后选取和用户最近点击新闻最相似的关联新闻。向量学习和寻找相似全部使用 gensim 库的 Word2Vec 实现。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 模型训练</span><br><span class="line">model &#x3D; Word2Vec(sentences&#x3D;sentences, size&#x3D;256,  window&#x3D;3,  min_count&#x3D;1, sg&#x3D;1, hs&#x3D;0, seed&#x3D;seed, negative&#x3D;5, workers&#x3D;10, iter&#x3D;1)</span><br></pre></td></tr></table></figure><h2 id="召回合并"><a href="#召回合并" class="headerlink" title="召回合并"></a>召回合并</h2><p>多路召回之间肯定存在重复召回的情况，这也是为什么召回策略讲究差异性，其实就是为了减少重复召回的数量。利用重复召回的新闻数量占该路召回数量之比做相似度参考，3路召回的相似度见热力图。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://raw.githubusercontent.com/LogicJake/imghub/master/new1.png" alt="图1 召回方式相似度" title="">                </div>                <div class="image-caption">图1 召回方式相似度</div>            </figure><p>重复被召回的新闻在多个召回方式中的得分是不一样的，得分是排序时的强特，需要进行得分合并。对各个召回结果，以用户为单位进行最大最小归一化。分数合并测试了sum，mean和max，效果对比见下表。max丢失的消息较多，mean对重复次数多的新闻不公平。  </p><table><thead><tr><th align="center">合并方式</th><th align="center">hitrate_5</th></tr></thead><tbody><tr><td align="center">sum</td><td align="center">0.3700630930498286</td></tr><tr><td align="center">mean</td><td align="center">0.33700134134830345</td></tr><tr><td align="center">max</td><td align="center">0.35155745441899744</td></tr></tbody></table><p>去重之后，平均每个用户召回到153个新闻。删除没有召回到真实点击的验证集用户，减少了无用负样本的数量，也提高了后续排序模型的效果，但是在计算线下指标的时候这部分用户的数量要包括进去，否则指标会虚高。合并前后各召回方式指标如下表所示：</p><table><thead><tr><th align="center"></th><th align="center">hitrate_5</th><th align="center">mrr_5</th></tr></thead><tbody><tr><td align="center">itemcf</td><td align="center">0.33628098762978786</td><td align="center">0.19792256611521228</td></tr><tr><td align="center">binetwork</td><td align="center">0.3127825525361419</td><td align="center">0.19114627320448885</td></tr><tr><td align="center">w2v</td><td align="center">0.15706195041979235</td><td align="center">0.08342813850663192</td></tr><tr><td align="center">合并</td><td align="center">0.3667842416414129</td><td align="center">0.2195861692085987</td></tr></tbody></table><h1 id="排序"><a href="#排序" class="headerlink" title="排序"></a>排序</h1><p>本方案把排序建模为二分类任务，分为特征工程和二分类模型预测两部分。特征工程主要围绕召回策略进行，保证实时性召回的新闻能在排序阶段被推出去。模型采用 LGB 模型。</p><h2 id="特征工程"><a href="#特征工程" class="headerlink" title="特征工程"></a>特征工程</h2><p>特征工程分为3类：新闻特征，用户特征，用户-新闻交互特征。数据集本身给出的属性信息较少，所以特征工程主要围绕交互属性展开。</p><p>新闻特征包括：</p><ul><li>新闻字数</li><li>新闻创建时间</li><li>新闻被阅读数量</li></ul><p>用户特征包括：</p><ul><li>用户点击新闻的创建时间差的平均值</li><li>用户点击新闻的点击时间差的平均值</li><li>用户点击新闻的点击-创建时间差的统计值：mean，std</li><li>用户点击新闻的 click_datetime_hour 统计值</li><li>用户点击新闻的字数统计值</li><li>用户点击新闻的创建时间统计值</li><li>用户点击新闻的点击时间统计值</li><li>用户新闻阅读数量</li><li>用户某种类新闻阅读数量</li></ul><p>交互特征主要基于之前的召回策略进行，通过保存召回阶段的新闻相似度信息或向量，我们能够间接或直接得到用户对待预测新闻的评分。基于 itemcf， 网络关系和 w2v 的召回得到的只是新闻之间的相似度，需要和用户的历史点击新闻计算间接得到用户-新闻评分，采用如下方式：</p><ul><li>待预测新闻和用户所有历史点击新闻相似度按次序加权求和</li><li>待预测新闻和用户最近一次点击新闻相似度</li></ul><h2 id="模型"><a href="#模型" class="headerlink" title="模型"></a>模型</h2><p>模型采用单模 LGB 进行预测，特征重要性见下表。排序之后，hitrate_5 涨到0.4394406080778976，mrr_5 涨到0.26552279464123835。</p><table><thead><tr><th align="center">feature_name</th><th align="center">gain</th></tr></thead><tbody><tr><td align="center">sim_score</td><td align="center">557646.594097</td></tr><tr><td align="center">user_last_click_timestamp_diff</td><td align="center">192108.389757</td></tr><tr><td align="center">user_last_click_article_itemcf_sim</td><td align="center">124281.856153</td></tr><tr><td align="center">user_last_click_article_w2v_sim</td><td align="center">73809.001815</td></tr><tr><td align="center">user_clicked_article_itemcf_sim_sum</td><td align="center">65907.806015</td></tr><tr><td align="center">article_id_cnt</td><td align="center">65031.067219</td></tr><tr><td align="center">user_last_click_created_at_ts_diff</td><td align="center">53308.117885</td></tr><tr><td align="center">user_last_click_article_binetwork_sim</td><td align="center">35544.272514</td></tr><tr><td align="center">user_click_article_w2w_sim_sum_2</td><td align="center">22115.916349</td></tr><tr><td align="center">user_click_timestamp_created_at_ts_diff_mean</td><td align="center">20967.278802</td></tr><tr><td align="center">user_click_timestamp_created_at_ts_diff_std</td><td align="center">19246.184266</td></tr><tr><td align="center">article_id</td><td align="center">19177.854082</td></tr><tr><td align="center">words_count</td><td align="center">18412.732866</td></tr><tr><td align="center">user_click_last_article_click_time</td><td align="center">17445.728666</td></tr><tr><td align="center">user_click_datetime_hour_std</td><td align="center">17065.241513</td></tr><tr><td align="center">user_id_category_id_cnt</td><td align="center">16947.363850</td></tr><tr><td align="center">created_at_ts</td><td align="center">16833.830176</td></tr><tr><td align="center">user_last_click_words_count_diff</td><td align="center">16622.374522</td></tr><tr><td align="center">user_clicked_article_words_count_mean</td><td align="center">16606.933470</td></tr><tr><td align="center">user_id_click_diff_mean</td><td align="center">15513.968470</td></tr><tr><td align="center">user_id_click_article_created_at_ts_diff_mean</td><td align="center">15014.800224</td></tr><tr><td align="center">user_id</td><td align="center">14344.929410</td></tr><tr><td align="center">user_click_last_article_words_count</td><td align="center">14080.826424</td></tr><tr><td align="center">user_clicked_article_click_time_mean</td><td align="center">13204.633566</td></tr><tr><td align="center">user_click_last_article_created_time</td><td align="center">10290.474666</td></tr><tr><td align="center">user_clicked_article_created_time_max</td><td align="center">9965.635575</td></tr><tr><td align="center">user_id_cnt</td><td align="center">8507.025577</td></tr><tr><td align="center">category_id</td><td align="center">6201.554409</td></tr></tbody></table>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最终成绩：2 / 5272&lt;br&gt;比赛地址：&lt;a href=&quot;https://tianchi.aliyun.com/competition/entrance/531842&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tianchi.aliyun.com/competition/entrance/531842&lt;/a&gt;&lt;br&gt;源码：&lt;a href=&quot;https://github.com/LogicJake/tianchi-news-recommendation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/LogicJake/tianchi-news-recommendation&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;赛题背景&quot;&gt;&lt;a href=&quot;#赛题背景&quot; class=&quot;headerlink&quot; title=&quot;赛题背景&quot;&gt;&lt;/a&gt;赛题背景&lt;/h1&gt;&lt;p&gt;赛题以新闻APP中的新闻推荐为背景，要求选手根据用户历史浏览点击新闻文章的数据信息预测用户未来点击行为，即用户的最后一次点击的新闻文章，测试集对最后一次点击行为进行了剔除。&lt;/p&gt;
    
    </summary>
    
    
      <category term="比赛总结" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
    
      <category term="推荐系统" scheme="https://logicjake.github.io/tags/%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F/"/>
    
      <category term="召回" scheme="https://logicjake.github.io/tags/%E5%8F%AC%E5%9B%9E/"/>
    
      <category term="排序" scheme="https://logicjake.github.io/tags/%E6%8E%92%E5%BA%8F/"/>
    
      <category term="新闻推荐" scheme="https://logicjake.github.io/tags/%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90/"/>
    
  </entry>
  
  <entry>
    <title>2020厦门国际银行数创金融杯建模大赛-赛后总结</title>
    <link href="https://logicjake.github.io/2021/01/31/2020%E5%8E%A6%E9%97%A8%E5%9B%BD%E9%99%85%E9%93%B6%E8%A1%8C%E6%95%B0%E5%88%9B%E9%87%91%E8%9E%8D%E6%9D%AF%E5%BB%BA%E6%A8%A1%E5%A4%A7%E8%B5%9B-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/"/>
    <id>https://logicjake.github.io/2021/01/31/2020%E5%8E%A6%E9%97%A8%E5%9B%BD%E9%99%85%E9%93%B6%E8%A1%8C%E6%95%B0%E5%88%9B%E9%87%91%E8%9E%8D%E6%9D%AF%E5%BB%BA%E6%A8%A1%E5%A4%A7%E8%B5%9B-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/</id>
    <published>2021-01-31T22:30:58.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<p>最终成绩：前15（靠方案从第18向前摸了个优胜奖） / 1478<br>比赛地址：<a href="https://js.dclab.run/v2/cmptDetail.html?id=439" target="_blank" rel="noopener">https://js.dclab.run/v2/cmptDetail.html?id=439</a><br>源码：<a href="https://github.com/LogicJake/2020-Xiamen-International-Bank-Financial-Cup" target="_blank" rel="noopener">https://github.com/LogicJake/2020-Xiamen-International-Bank-Financial-Cup</a>   </p><h1 id="赛题背景"><a href="#赛题背景" class="headerlink" title="赛题背景"></a>赛题背景</h1><p>在数字金融时代，大数据、人工智能技术在银行业内的发展日新月异，业内各机构都在加速数字化转型发展。厦门国际银行作为有特色的科技领先型中小银行，多年来始终坚持发挥数字金融科技力量，践行“数字赋能”理念，持续推进智慧风控、智慧营销、智慧运营、智慧管理，运用人工智能和大数据分析技术建立智能化客户服务模式和金融智慧营销服务体系，提升营销过程的智慧化、精准化水平，在为客户提供更贴心更具可用性的金融服务。</p><a id="more"></a><h1 id="任务"><a href="#任务" class="headerlink" title="任务"></a>任务</h1><p>随着科技发展，银行陆续打造了线上线下、丰富多样的客户触点，来满足客户日常业务办理、渠道交易等客户需求。面对着大量的客户，银行需要更全面、准确地洞察客户需求。在实际业务开展过程中，需要发掘客户流失情况，对客户的资金变动情况预判；提前/及时针对客户进行营销，减少银行资金流失。本次竞赛提供实际业务场景中的客户行为和资产信息为建模对象，一方面希望能借此展现各参赛选手的数据挖掘实战能力，另一方面需要选手在复赛中结合建模的结果提出相应的营销解决方案，充分体现数据分析的价值。</p><h1 id="数据介绍"><a href="#数据介绍" class="headerlink" title="数据介绍"></a>数据介绍</h1><p>数据共分为三个部分，训练集(包括 2019 年第三、第四季度，每个季度的客户信息、资产数据、行为数据、重大历史数据、存款数据)、测试集(包括 2020 年第一季度的客户信息、资产数据、行为数据、重大历史数据、存款数据)，以及标签数据。建模的目标即根据训练集对模型进行训练，并对测试集进行预测。训练集和测试集中包含了有效客户和无效客户，而所给的标签中，只有有效用户的标签，同时赛题也要求只对有效用户建模，因此，本文以下的分析均为有效客户，不包含无效客户。  </p><p>本方案采用的方式是将将训练集(客户信息、资产数据、行为数据、重大历史数据、存款数据)按季度合并起来，合并之后的训练集有 82899 个有效客户，而测试集有 76722 个有效客户。  </p><details><summary>详细数据字段说明</summary><p>a) aum_m(Y) 代表第 Y 月的月末时点资产数据<br><img src="https://raw.githubusercontent.com/LogicJake/imghub/master/a.png" alt=""></p><p>b) behavior_m(Y) 代表第Y月的行为数据<br><img src="https://raw.githubusercontent.com/LogicJake/imghub/master/b.png" alt=""></p><p>c) big_event_Q(Z) 代表第 Z 季度的客户重大历史数据<br><img src="https://raw.githubusercontent.com/LogicJake/imghub/master/c.png" alt=""></p><p>d) cunkuan_m(Y) 代表第 Y 月的存款数据<br><img src="https://raw.githubusercontent.com/LogicJake/imghub/master/d.png" alt=""></p><p>e) cust_avli _Q(Z) 代表第 Z 季度的有效客户 仅有 cust_no  </p><p>f) cust_info_q(Z) 代表第 Z 季度的客户信息<br><img src="https://raw.githubusercontent.com/LogicJake/imghub/master/f.png" alt="">  </p></details>  <h1 id="方案总结"><a href="#方案总结" class="headerlink" title="方案总结"></a>方案总结</h1><p>根据工作人员的答疑，标签是根据一定业务规则和客户存款情况去给客户打标签, 反映的是客户资产的变化情况。</p><ul><li>-1 代表客户资产下降</li><li>0 代表客户资产维稳</li><li>1 代表客户资产上升  </li></ul><p>训练集中 63.9% 的客户属于类别 1 (资产上升)；20.8% 属于类别 2 (资产维稳)；15.3% 属于类别 -1 (资产下降)。</p><h2 id="特征工程"><a href="#特征工程" class="headerlink" title="特征工程"></a>特征工程</h2><h3 id="行为数据相关特征"><a href="#行为数据相关特征" class="headerlink" title="行为数据相关特征"></a>行为数据相关特征</h3><p>在行为数据中有一列特征为最近一次交易时间B6，用当季度最后一天的日期减去B6可以得到客户没有交易的天数（用B6_gap表示），以此来衡量用户的沉寂时间。该特征在线下能提升4个k，但线上却起到了反作用。探查数据后发现，存在最近一次交易日期并不在该季度的日期范围之内的现象，也就是说很多客户在这一个季度内都没用交易过。这些用户的沉寂时间过长，因此在多个季度的数据中，最后一次交易时间都是同一个很久远的日期，所以需要进行截断，将最近一次交易日期截断在本季度所属日期范围内，防止同一个用户出现越后面的季度B6_gap越大的现象。重新处理后该特征<strong>线上能提升4个k</strong>。</p><p>对其他行为数据进行了交叉处理，构造了转入转出金额之差、之比，平均每次转入金额和平均每次转出金额。</p><p>行为数据是按月给出的，需要聚合为以季度为单位的特征。对原始的行为特征和衍生的交叉特征，对季度内的月数据进行mean，std，max，min，diff，last统计。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">for f in tqdm([&#39;B1&#39;, &#39;B2&#39;, &#39;B3&#39;, &#39;B4&#39;, &#39;B5&#39;, &#39;B5_B3_minus&#39;, &#39;B3_B2_ratio&#39;, &#39;B5_B4_ratio&#39;, &#39;B5_B3_ratio&#39;]):</span><br><span class="line">    df_temp &#x3D; df_behavior.groupby([&#39;cust_no&#39;, &#39;q&#39;])[f].agg(&#123;</span><br><span class="line">        &#39;q_&#123;&#125;_mean&#39;.format(f): &#39;mean&#39;,</span><br><span class="line">        &#39;q_&#123;&#125;_std&#39;.format(f): &#39;std&#39;,</span><br><span class="line">        &#39;q_&#123;&#125;_max&#39;.format(f): &#39;max&#39;,</span><br><span class="line">        &#39;q_&#123;&#125;_min&#39;.format(f): &#39;min&#39;,</span><br><span class="line">        &#39;q_&#123;&#125;_diff&#39;.format(f): lambda x: x.values[-1] - x.values[0],</span><br><span class="line">        &#39;q_&#123;&#125;_last&#39;.format(f): &#39;last&#39;,</span><br><span class="line">    &#125;).reset_index()</span><br><span class="line">    df_feature &#x3D; df_feature.merge(df_temp, how&#x3D;&#39;left&#39;)</span><br></pre></td></tr></table></figure><h3 id="资产数据相关特征"><a href="#资产数据相关特征" class="headerlink" title="资产数据相关特征"></a>资产数据相关特征</h3><p>该表包含了月末客户的各类资产余额，很自然的可以求和得到所有资产的总和，顺便得到拥有的不同资产的类别。本季度最后一个月的月末数据可以视为本季度结束时的数据，各类资产余额除以总资产余额可以得到季度结束时各资产的配比。资产数据也是按月给出的，对资产的总和和资产类别数，进行mean，std，max，min，diff，last统计。</p><h3 id="存款数据相关特征"><a href="#存款数据相关特征" class="headerlink" title="存款数据相关特征"></a>存款数据相关特征</h3><p>不是很明白存款数据和资产数据的区别，该表给出了当月存款产品金额和存款产品个数，交叉得到平均每个存款产品的金额。按月对存款金额进行diff得到存款金额的月变化情况。同样对原始特征和衍生的交叉特征，对季度内的月数据进行mean，std，max，min，diff，last统计。</p><h3 id="历史事件数据相关特征"><a href="#历史事件数据相关特征" class="headerlink" title="历史事件数据相关特征"></a>历史事件数据相关特征</h3><p>该表有客户一系列的“第一次”的日期，虽然说时间特征一般是很强的，但实际并没有挖出强有力的代表特征，仅仅对这一系列日期特征交叉计算日期差。</p><h3 id="客户基本信息相关特征"><a href="#客户基本信息相关特征" class="headerlink" title="客户基本信息相关特征"></a>客户基本信息相关特征</h3><p>直接merge进主表就完事了，对类别特征做了count编码，探究的比较粗糙。</p><h3 id="季度间特征"><a href="#季度间特征" class="headerlink" title="季度间特征"></a>季度间特征</h3><p>上面的特征都是在季度内做统计，我们完全可以把每个季度最后一个月的数据视为季度数据，比如季度资产余额。这样我们就可以以季度为单位计算diff，得到各类资产余额距离上个季度的变化情况，从而得到客户的资产变动情况。该类特征提升较大，线上接近<strong>3个百</strong>。</p><h2 id="多分类预测概率权重搜索"><a href="#多分类预测概率权重搜索" class="headerlink" title="多分类预测概率权重搜索"></a>多分类预测概率权重搜索</h2><p>这也是多分类任务的一个很重要的提分trick，基本思想为暴力搜索各个类别的权重，使得预测类别的概率乘以权重后，kappa指标能提升。本质上是将模型预测的类别概率分布尽量拉到真实的标签分布，该trick线上能提升大概<strong>1.5个百</strong>。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">def search_weight(valid_y, raw_prob, init_weight&#x3D;[1.0]*class_num, step&#x3D;0.001):</span><br><span class="line">    weight &#x3D; init_weight.copy()</span><br><span class="line">    f_best &#x3D; cohen_kappa_score(valid_y, raw_prob.argmax(</span><br><span class="line">        axis&#x3D;1))</span><br><span class="line">    flag_score &#x3D; 0</span><br><span class="line">    round_num &#x3D; 1</span><br><span class="line">    while(flag_score !&#x3D; f_best):</span><br><span class="line">        print(&#39;round: &#39;, round_num)</span><br><span class="line">        round_num +&#x3D; 1</span><br><span class="line">        flag_score &#x3D; f_best</span><br><span class="line">        for c in range(class_num):</span><br><span class="line">            for n_w in range(0, 2000, 10):</span><br><span class="line">                num &#x3D; n_w * step</span><br><span class="line">                new_weight &#x3D; weight.copy()</span><br><span class="line">                new_weight[c] &#x3D; num</span><br><span class="line"></span><br><span class="line">                prob_df &#x3D; raw_prob.copy()</span><br><span class="line">                prob_df &#x3D; prob_df * np.array(new_weight)</span><br><span class="line"></span><br><span class="line">                f &#x3D; cohen_kappa_score(valid_y, prob_df.argmax(</span><br><span class="line">                    axis&#x3D;1))</span><br><span class="line">                if f &gt; f_best:</span><br><span class="line">                    weight &#x3D; new_weight.copy()</span><br><span class="line">                    f_best &#x3D; f</span><br><span class="line">    return weight</span><br></pre></td></tr></table></figure><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>这个比赛前期花费的时间比较多，后期随着挖特征收益太小，线上线下不一致，担心切榜就是摸奖，所以最后一个月基本就没提交了，就和队友融合了一下就放弃了。没想到b榜分数还是挺稳定的，而且前面有很多小号退赛，混进了前20，哈哈，无力吐槽。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最终成绩：前15（靠方案从第18向前摸了个优胜奖） / 1478&lt;br&gt;比赛地址：&lt;a href=&quot;https://js.dclab.run/v2/cmptDetail.html?id=439&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://js.dclab.run/v2/cmptDetail.html?id=439&lt;/a&gt;&lt;br&gt;源码：&lt;a href=&quot;https://github.com/LogicJake/2020-Xiamen-International-Bank-Financial-Cup&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/LogicJake/2020-Xiamen-International-Bank-Financial-Cup&lt;/a&gt;   &lt;/p&gt;
&lt;h1 id=&quot;赛题背景&quot;&gt;&lt;a href=&quot;#赛题背景&quot; class=&quot;headerlink&quot; title=&quot;赛题背景&quot;&gt;&lt;/a&gt;赛题背景&lt;/h1&gt;&lt;p&gt;在数字金融时代，大数据、人工智能技术在银行业内的发展日新月异，业内各机构都在加速数字化转型发展。厦门国际银行作为有特色的科技领先型中小银行，多年来始终坚持发挥数字金融科技力量，践行“数字赋能”理念，持续推进智慧风控、智慧营销、智慧运营、智慧管理，运用人工智能和大数据分析技术建立智能化客户服务模式和金融智慧营销服务体系，提升营销过程的智慧化、精准化水平，在为客户提供更贴心更具可用性的金融服务。&lt;/p&gt;
    
    </summary>
    
    
      <category term="比赛总结" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
    
      <category term="机器学习" scheme="https://logicjake.github.io/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/tags/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
      <category term="金融" scheme="https://logicjake.github.io/tags/%E9%87%91%E8%9E%8D/"/>
    
  </entry>
  
  <entry>
    <title>Kaggle Riiid! Answer Correctness Prediction-赛后总结</title>
    <link href="https://logicjake.github.io/2021/01/09/Riiid-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/"/>
    <id>https://logicjake.github.io/2021/01/09/Riiid-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/</id>
    <published>2021-01-09T18:57:00.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<p>最终成绩：74 / 3395 银牌<br>比赛地址：<a href="https://www.kaggle.com/c/riiid-test-answer-prediction" target="_blank" rel="noopener">https://www.kaggle.com/c/riiid-test-answer-prediction</a></p><p>这比赛线上测评采用 API 方式逐步给出测试数据，模拟真实场景下的在线预测，所以基本避免了穿越特征的出现。此外由于在线测评的原因，要兼顾线上特征构造和模型预测的时间复杂度和空间复杂度，一个不满足都会导致线上提交失败。</p><p>赛题提供的数据量很大且是典型的序列数据，所以前排有许多基于 transformer 的 NN 方案，这一点是我们队伍的欠缺。本方案的线下验证集划分采用 tito 开源的 <a href="https://www.kaggle.com/its7171/cv-strategy" target="_blank" rel="noopener">CV Strategy</a>，特征构造框架也是采用 tito 大佬开源的 <a href="https://www.kaggle.com/its7171/lgbm-with-loop-feature-engineering" target="_blank" rel="noopener">LGBM with Loop Feature Engineering</a>，这里就不赘述了，直接进入特征工程部分。</p><h1 id="特征工程"><a href="#特征工程" class="headerlink" title="特征工程"></a>特征工程</h1><p>我们将构造的特征分为静态特征和动态特征，静态特征直接由训练集统计得到，不需要随着数据的增加逐步更新。赛题的线上测试集中是不包含新题目的，所以可以认为题目处于一种比较稳定的状态，不需要迭代更新。动态特征主要与用户相关，进来一条新数据，就需要更新与之相关的状态字典。</p><h2 id="静态特征"><a href="#静态特征" class="headerlink" title="静态特征"></a>静态特征</h2><ul><li>题目的 tags 提取出来的第一个标签 tag1</li><li>题目被回答次数，回答正确次数，正确率</li><li>题目所属 bundle_id 有多少题目</li><li>题目所属 bundle_id 的被回答次数，正确回答次数，正确率</li><li>题目所属 part 的被回答次数，正确回答次数，正确率</li><li>对 part 进行分桶，以 part=5 为分界划分是听力测试还是阅读测试</li><li>题目所属 tag1 所有题目的正确率的平均值和方差</li><li>对题目的 tags 做 embedding(dimension=2)，题目所有的 tag embedding 的平均值</li><li>题目出现次数，题目 part 出现次数，题目 tag1 出现次数在训练集中的占比</li><li>题目被多少个不同的人回答过</li></ul><h2 id="动态特征"><a href="#动态特征" class="headerlink" title="动态特征"></a>动态特征</h2><p>动态特征需要根据新数据不断更新，为了避免线上线下特征构造方式的差异性带来的潜在问题，我们在线下也使用了迭代式的特征构造方式。主要逻辑如下面的伪代码所示，核心在于3个函数，add_user_feats 用于线下数据的特征构造和状态字典更新，线上推理的时候，一批数据的真实标签需要在下批数据中给出，所以特征构造和状态字典更新需要在函数 add_user_feats_without_update 和函数 update_user_feats 分别进行。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"># 线下特征构造</span><br><span class="line">特征状态字典 &#x3D; &#123;&#125;</span><br><span class="line"></span><br><span class="line">def add_user_feats(df, 特征状态字典):</span><br><span class="line">    for ... in df.values:</span><br><span class="line">        根据 key 查询特征状态字典，构造本样本的特征</span><br><span class="line">        更新特征状态字典</span><br><span class="line"></span><br><span class="line">add_user_feats(训练集数据, 特征状态字典)</span><br><span class="line"></span><br><span class="line">保存特征状态字典</span><br><span class="line"></span><br><span class="line"># 线上特征构造</span><br><span class="line">加载线下保存的特征状态字典</span><br><span class="line"></span><br><span class="line">def update_user_feats(df, 特征状态字典):</span><br><span class="line">    for ... in df.values:</span><br><span class="line">        更新特征状态字典</span><br><span class="line"></span><br><span class="line">def add_user_feats_without_update(df, 特征状态字典):</span><br><span class="line">    for ... in df.values:</span><br><span class="line">        根据 key 查询特征状态字典，构造本样本的特征</span><br><span class="line"></span><br><span class="line">previous_test_df &#x3D; None</span><br><span class="line"></span><br><span class="line">for test_df in 线上测试集:</span><br><span class="line">    if previous_test_df is not None:</span><br><span class="line">        update_user_feats(previous_test_df, 特征状态字典)</span><br><span class="line"></span><br><span class="line">    previous_test_df &#x3D; test_df.copy()</span><br><span class="line">    add_user_feats_without_update(test_df, 特征状态字典)</span><br></pre></td></tr></table></figure><p>动态特征选取了不同的组合维度，包括用户，用户和题目 part 组合，用户和题目 tag1 组合，用户和题目组合。时间属性能够反应很多信息，构造时间差特征能有效反应学生的做题效率，做题热情等信息。下面斜体部分的特征都是围绕时间差展开构造的，总共大概有1.7个百分位的提升。</p><ul><li><p>用户侧特征</p><ul><li>用户答题次数，回答正确的次数，答题正确率</li><li><em>用户距离上次，上上次，上上上次答对题目的时间差</em></li><li><em>用户距离上次，上上次，上上上次，上上上上次答题的时间差和时间差之间的差值</em></li><li><em>用户距离上次，上上次听讲座的时间差</em></li><li><em>用户距离上次，上上次，上上次答错题目的时间差</em></li><li><em>用户距离上次，上上次学习（包括做题和听讲座）的时间差</em></li><li><strong>用户这次题目所属 container_id 和上个题目所属 container_id 的差值</strong></li><li>用户之前查看题目解答的次数</li><li>用户之前答题正确查看题目解答的次数</li><li>用户听力测试题目和阅读测试题目答题次数，回答正确的次数，答题正确率</li><li>用户听讲座的次数</li></ul></li><li><p>用户和题目 part 组合特征</p><ul><li>用户对同类型的题目回答次数，回答正确次数，正确率</li><li><em>用户距离上次同类型的题目回答正确和回答错误的时间差</em></li></ul></li><li><p>用户和题目 tag1 组合特征</p><ul><li>用户对同标签的题目回答次数，回答正确次数，正确率</li></ul></li><li><p>用户和题目组合</p><ul><li><strong>用户之前是否回答过这道题</strong></li><li><strong><em>用户距离上次回答该问题的时间差</em></strong></li></ul></li></ul><h3 id="关于-task-container-id-的强特"><a href="#关于-task-container-id-的强特" class="headerlink" title="关于 task_container_id 的强特"></a>关于 task_container_id 的强特</h3><p>Kaggle 上关于 task_container_id 的讨论不少，<a href="https://www.kaggle.com/c/riiid-test-answer-prediction/discussion/189465" target="_blank" rel="noopener">这则帖子</a>指出了 timestamp 和 task_container_id 不连续的问题，如图1所示。  </p><p><img src="https://raw.githubusercontent.com/LogicJake/imghub/master/kaggle_riiid_1.png" alt="图1">  </p><p>根据 Kaggle 工作人员在其他 discussion 里的答疑，timestamp 代表的是用户结束回答题目的时间。task_container_id 来源于这样一个场景：用户每次做题会得到一批题目，task_container_id 代表的是这批题目的编号，编号从0开始自增，所以 task_container_id 的大小反应了用户开始做题的顺序。如果用户都是做完一批题目再做下一批题目，那么 task_container_id 应该是随着 timestamp 依次增大的。图2代表的就是做完一批题目再做下一批题目的场景（方框中编号代表 task_container_id）。  </p><p><img src="https://raw.githubusercontent.com/LogicJake/imghub/master/kaggle_riiid_2.png" alt="图2">  </p><p>但是在上学的时候，老师应该教过我们：遇到不会的题目先跳过，过一会再做。task_container_id 不连续的原因就在于此，以图3为例，用户做到 task_container_id 为1的题目时发现题目不会做，所以先行跳过做 task_container_id 为2的题目。task_container_id 为2的题目比较简单，做完之后再返回去攻克 task_container_id 为1的题目。这就导致了 task_container_id 不随着 timestamp 自增的现象。根据这个现象我们可以构造一个强特：当前题目所属 task_container_id 和之前一批题目的 task_container_id 的差值，差值的绝对值越大，代表回答这次题目时跳过的越多。<strong>这个特征提升约1.5k</strong>。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># 求和上批题目 task_container_id 的差值</span><br><span class="line">utci[cnt] &#x3D; task_container_id - u_task_container_id_dict[user_id]</span><br><span class="line"></span><br><span class="line"># 更新最新批题目的 task_container_id </span><br><span class="line">if task_container_id !&#x3D; u_task_container_id_dict[user_id]:</span><br><span class="line">    u_task_container_id_dict[user_id] &#x3D; task_container_id</span><br></pre></td></tr></table></figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://raw.githubusercontent.com/LogicJake/imghub/master/kaggle_riiid_3.png" alt="图3" title="">                </div>                <div class="image-caption">图3</div>            </figure><h3 id="用户和题目组合特征"><a href="#用户和题目组合特征" class="headerlink" title="用户和题目组合特征"></a>用户和题目组合特征</h3><p>用户和题目的交叉特征非常有用，但受限于二者交叉之后巨大维度带来的内存问题，只构造了2个特征，且都需要特殊的数据结构减少内存占用。</p><p><a href="https://www.kaggle.com/c/riiid-test-answer-prediction/discussion/194266" target="_blank" rel="noopener">这则帖子</a>发现用户会重复回答某些问题，所以可以构造特征：用户第几次回答这个问题。如果采用之前的字典方式，user_id 和 content_id 交互后 key 的数量十分巨大，十分占内存。经原贴评论区大佬指导，可以采用 bitarray 这个数据结构退而求其次记录用户之前是否做过这个题目。每个用户维护长度为14000的 bitarray，content_id 因为已经是从0开始的连续值，所以可以用下标查询的方式确定 bitarry 对应位置是否为1。<strong>该特征提升幅度4个k。</strong></p><p>之前也说过时间差是个很重要的特征，所以继续构造用户距离上次回答该问题的时间差，如果时间差比较短，那么用户可能记忆比较深刻，做错的概率也较小。构造这个特征就没办法使用 bitarray 了，还是得延续之前的字典做法。之前字典爆内存的原因在于 key 过多，所以我们采用 LRU 缓存的方式，只维护指定长度的字典，一旦新加入记录时字典满了，直接删除最近最少使用的记录。python 的数据结构 OrderedDict 有个特性，他会按照 key 加入的顺序排序。所以我们每次查询一条记录，如果该记录存在，则把他从 OrderedDict 中删除，再重新添加到 OrderedDict 中去，这样 OrderedDict 的末尾保存的就是最近使用的记录。同样当字典满了，直接删除第一条记录，就可以起到删除最近最少使用的记录的作用。<strong>该特征提升幅度2个k。</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">class LRUCache(OrderedDict):</span><br><span class="line">    def __init__(self, capacity&#x3D;100):</span><br><span class="line">        self.capacity &#x3D; capacity</span><br><span class="line">        self.cache &#x3D; OrderedDict()</span><br><span class="line"></span><br><span class="line">    def get(self, key):</span><br><span class="line">        if key in self.cache:</span><br><span class="line">            value &#x3D; self.cache.pop(key)</span><br><span class="line">            self.cache[key] &#x3D; value</span><br><span class="line">        else:</span><br><span class="line">            value &#x3D; -1</span><br><span class="line"></span><br><span class="line">        return value</span><br><span class="line"></span><br><span class="line">    def set(self, key, value):</span><br><span class="line">        if key in self.cache:</span><br><span class="line">            value &#x3D; self.cache.pop(key)</span><br><span class="line">            self.cache[key] &#x3D; value</span><br><span class="line">        else:</span><br><span class="line">            if len(self.cache) &#x3D;&#x3D; self.capacity:</span><br><span class="line">                self.cache.popitem(last&#x3D;False)</span><br><span class="line">                self.cache[key] &#x3D; value</span><br><span class="line">            else:</span><br><span class="line">                self.cache[key] &#x3D; value</span><br></pre></td></tr></table></figure><h1 id="模型融合"><a href="#模型融合" class="headerlink" title="模型融合"></a>模型融合</h1><p>最终模型选用 LGB 融合<a href="https://www.kaggle.com/gilfernandes/riiid-self-attention-transformer" target="_blank" rel="noopener">开源</a>的0.77的 transformer 模型，0.75 * LGB 预测概率 + 0.25 * NN 预测概率，融合大概能带来3k的提升。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;最终成绩：74 / 3395 银牌&lt;br&gt;比赛地址：&lt;a href=&quot;https://www.kaggle.com/c/riiid-test-answer-prediction&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.kagg
      
    
    </summary>
    
    
      <category term="比赛总结" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
    
      <category term="机器学习" scheme="https://logicjake.github.io/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
      <category term="教育" scheme="https://logicjake.github.io/tags/%E6%95%99%E8%82%B2/"/>
    
      <category term="知识追踪" scheme="https://logicjake.github.io/tags/%E7%9F%A5%E8%AF%86%E8%BF%BD%E8%B8%AA/"/>
    
  </entry>
  
  <entry>
    <title>零基础入门金融风控-贷款违约预测-赛后总结</title>
    <link href="https://logicjake.github.io/2021/01/08/%E9%9B%B6%E5%9F%BA%E7%A1%80%E5%85%A5%E9%97%A8%E9%87%91%E8%9E%8D%E9%A3%8E%E6%8E%A7-%E8%B4%B7%E6%AC%BE%E8%BF%9D%E7%BA%A6%E9%A2%84%E6%B5%8B-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/"/>
    <id>https://logicjake.github.io/2021/01/08/%E9%9B%B6%E5%9F%BA%E7%A1%80%E5%85%A5%E9%97%A8%E9%87%91%E8%9E%8D%E9%A3%8E%E6%8E%A7-%E8%B4%B7%E6%AC%BE%E8%BF%9D%E7%BA%A6%E9%A2%84%E6%B5%8B-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/</id>
    <published>2021-01-08T20:17:36.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<p>最终成绩：11 / 4780<br>比赛地址：<a href="https://tianchi.aliyun.com/competition/entrance/531830/introduction" target="_blank" rel="noopener">https://tianchi.aliyun.com/competition/entrance/531830/introduction</a><br>开源地址：<a href="https://github.com/LogicJake/tianchi-loan-default-prediction-top11" target="_blank" rel="noopener">https://github.com/LogicJake/tianchi-loan-default-prediction-top11</a></p><p>这个比赛属于天池办的一系列入门赛的其一，没奖金只有一些小奖品，所以竞争很小。总的来说该方案没有什么耀眼的操作，就是简单的数据处理，暴力特征工程和模型融合，单纯看好久没更博文了，发上来凑个数。</p><h1 id="赛题背景"><a href="#赛题背景" class="headerlink" title="赛题背景"></a>赛题背景</h1><p>赛题以金融风控中的个人信贷为背景，要求选手根据贷款申请人的数据信息预测其是否有违约的可能，以此判断是否通过此项贷款，这是一个典型的分类问题。</p><h1 id="数据介绍"><a href="#数据介绍" class="headerlink" title="数据介绍"></a>数据介绍</h1><p>赛题以预测用户贷款是否违约为任务，数据集报名后可见并可下载，该数据来自某信贷平台的贷款记录，总数据量超过120w，包含47列变量信息，其中15列为匿名变量。为了保证比赛的公平性，将会从中抽取80万条作为训练集，20万条作为测试集A，20万条作为测试集B，同时会对employmentTitle、purpose、postCode和title等信息进行脱敏。  </p><table><thead><tr><th align="center">Field</th><th align="center">Description</th></tr></thead><tbody><tr><td align="center">id</td><td align="center">为贷款清单分配的唯一信用证标识</td></tr><tr><td align="center">loanAmnt</td><td align="center">贷款金额</td></tr><tr><td align="center">term</td><td align="center">贷款期限（year）</td></tr><tr><td align="center">interestRate</td><td align="center">贷款利率</td></tr><tr><td align="center">installment</td><td align="center">分期付款金额</td></tr><tr><td align="center">grade</td><td align="center">贷款等级</td></tr><tr><td align="center">subGrade</td><td align="center">贷款等级之子级</td></tr><tr><td align="center">employmentTitle</td><td align="center">就业职称</td></tr><tr><td align="center">employmentLength</td><td align="center">就业年限（年）</td></tr><tr><td align="center">homeOwnership</td><td align="center">借款人在登记时提供的房屋所有权状况</td></tr><tr><td align="center">annualIncome</td><td align="center">年收入</td></tr><tr><td align="center">verificationStatus</td><td align="center">验证状态</td></tr><tr><td align="center">issueDate</td><td align="center">贷款发放的月份</td></tr><tr><td align="center">purpose</td><td align="center">借款人在贷款申请时的贷款用途类别</td></tr><tr><td align="center">postCode</td><td align="center">借款人在贷款申请中提供的邮政编码的前3位数字</td></tr><tr><td align="center">regionCode</td><td align="center">地区编码</td></tr><tr><td align="center">dti</td><td align="center">债务收入比</td></tr><tr><td align="center">delinquency_2years</td><td align="center">借款人过去2年信用档案中逾期30天以上的违约事件数</td></tr><tr><td align="center">ficoRangeLow</td><td align="center">借款人在贷款发放时的fico所属的下限范围</td></tr><tr><td align="center">ficoRangeHigh</td><td align="center">借款人在贷款发放时的fico所属的上限范围</td></tr><tr><td align="center">openAcc</td><td align="center">借款人信用档案中未结信用额度的数量</td></tr><tr><td align="center">pubRec</td><td align="center">贬损公共记录的数量</td></tr><tr><td align="center">pubRecBankruptcies</td><td align="center">公开记录清除的数量</td></tr><tr><td align="center">revolBal</td><td align="center">信贷周转余额合计</td></tr><tr><td align="center">revolUtil</td><td align="center">循环额度利用率，或借款人使用的相对于所有可用循环信贷的信贷金额</td></tr><tr><td align="center">totalAcc</td><td align="center">借款人信用档案中当前的信用额度总数</td></tr><tr><td align="center">initialListStatus</td><td align="center">贷款的初始列表状态</td></tr><tr><td align="center">applicationType</td><td align="center">表明贷款是个人申请还是与两个共同借款人的联合申请</td></tr><tr><td align="center">earliesCreditLine</td><td align="center">借款人最早报告的信用额度开立的月份</td></tr><tr><td align="center">title</td><td align="center">借款人提供的贷款名称</td></tr><tr><td align="center">policyCode</td><td align="center">公开可用的策略_代码=1新产品不公开可用的策略_代码=2</td></tr><tr><td align="center">n系列匿名特征</td><td align="center">匿名特征n0-n14，为一些贷款人行为计数特征的处理</td></tr></tbody></table><p>提交结果为每个测试样本是1的概率，也就是y为1的概率。评价方法为AUC评估模型效果。</p><h1 id="方案总结"><a href="#方案总结" class="headerlink" title="方案总结"></a>方案总结</h1><h2 id="数据处理"><a href="#数据处理" class="headerlink" title="数据处理"></a>数据处理</h2><p>原数据中有贷款等级和贷款等级之子级两个类别特征，但因为有明显的大小关系，所以需要将其映射为数值。贷款等级范围为“A-G”，将其映射为“1-7”。子级在每个等级上又细分了5个数字等级，所以用“等级×5+细分等级”映射。</p><p>就业年限（年）特征也是具有明显大小关系的类别特征。首先将“10+ years”替换成“10 years”，“&lt; 1 year”替换成“0 years”。随后提取数字年份为新特征。</p><h2 id="特征工程"><a href="#特征工程" class="headerlink" title="特征工程"></a>特征工程</h2><h3 id="业务特征"><a href="#业务特征" class="headerlink" title="业务特征"></a>业务特征</h3><p>业务特征就做了两个没细挖。用贷款发放的年份减去借款人最早报告的信用额度开立的年份可以大致得到借款人的年龄（？）。贷款金额除以贷款期限（year）可以得到每年的负债，再除以年收入可以得到年负债比。</p><h3 id="暴力特征"><a href="#暴力特征" class="headerlink" title="暴力特征"></a>暴力特征</h3><p>暴力特征主要围绕类别特征展开，对类别特征进行批量无筛选式的特征构造。主要进行了一阶和二阶组合下的 count 运算。</p><p>也对数值特征进行了二阶相加构造，但对模型没有提升就删去了。</p><h3 id="欺诈率"><a href="#欺诈率" class="headerlink" title="欺诈率"></a>欺诈率</h3><p>类比点击率特征，针对是否会欺诈的01标签，同样也可以利用五折交叉统计计算欺诈率特征。欺诈率以类别为单位统计，所以同样可以对类别特征进行一阶和二阶下的暴力构造。</p><h2 id="模型融合"><a href="#模型融合" class="headerlink" title="模型融合"></a>模型融合</h2><p>模型融合采用树模型三兄弟：LightGBM，Xgboost和Catboost。融合方式采用指数加权融合，三个模型的权重都是0.5。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;最终成绩：11 / 4780&lt;br&gt;比赛地址：&lt;a href=&quot;https://tianchi.aliyun.com/competition/entrance/531830/introduction&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https
      
    
    </summary>
    
    
      <category term="比赛总结" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
    
      <category term="机器学习" scheme="https://logicjake.github.io/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/tags/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
      <category term="风控" scheme="https://logicjake.github.io/tags/%E9%A3%8E%E6%8E%A7/"/>
    
  </entry>
  
  <entry>
    <title>第二届翼支付杯大数据建模大赛信用风险用户识别-Top2 赛后总结</title>
    <link href="https://logicjake.github.io/2020/10/01/%E7%AC%AC%E4%BA%8C%E5%B1%8A%E7%BF%BC%E6%94%AF%E4%BB%98%E6%9D%AF%E5%A4%A7%E6%95%B0%E6%8D%AE%E5%BB%BA%E6%A8%A1%E5%A4%A7%E8%B5%9B-%E4%BF%A1%E7%94%A8%E9%A3%8E%E9%99%A9%E7%94%A8%E6%88%B7%E8%AF%86%E5%88%AB-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/"/>
    <id>https://logicjake.github.io/2020/10/01/%E7%AC%AC%E4%BA%8C%E5%B1%8A%E7%BF%BC%E6%94%AF%E4%BB%98%E6%9D%AF%E5%A4%A7%E6%95%B0%E6%8D%AE%E5%BB%BA%E6%A8%A1%E5%A4%A7%E8%B5%9B-%E4%BF%A1%E7%94%A8%E9%A3%8E%E9%99%A9%E7%94%A8%E6%88%B7%E8%AF%86%E5%88%AB-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/</id>
    <published>2020-10-01T14:03:48.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<p>最终成绩：2 / 392<br>比赛地址：<a href="https://www.dcjingsai.com/v2/cmptDetail.html?id=410" target="_blank" rel="noopener">https://www.dcjingsai.com/v2/cmptDetail.html?id=410</a><br>源码：<a href="https://github.com/LogicJake/2020-yizhifu-credit-risk-user-identification-Top2" target="_blank" rel="noopener">https://github.com/LogicJake/2020-yizhifu-credit-risk-user-identification-Top2</a>  </p><h1 id="赛题背景"><a href="#赛题背景" class="headerlink" title="赛题背景"></a>赛题背景</h1><p>央行发布《金融科技FinTech》报告，强调金融科技成为推动金融转型升级的新引擎，成为促进普惠金融发展的新机遇。运用大数据、人工智能等技术建立金融风控模型，有效甄别高风险交易，智能感知异常交易，实现风险早识别、早预警、早处置，提升金融风险技防能力，是 “金融+科技”成果的显著体现。</p><a id="more"></a><h1 id="任务"><a href="#任务" class="headerlink" title="任务"></a>任务</h1><p>信用风险用户识别，本次比赛采用auc对结果进行评分。</p><h1 id="数据介绍"><a href="#数据介绍" class="headerlink" title="数据介绍"></a>数据介绍</h1><details><summary>详细数据字段说明</summary><h2 id="基础信息"><a href="#基础信息" class="headerlink" title="基础信息"></a>基础信息</h2><table><thead><tr><th>字段名</th><th>字段说明（数据经过脱敏处理）</th></tr></thead><tbody><tr><td>user</td><td>样本编号，e.g., Train_00000、Train_00001…</td></tr><tr><td>sex</td><td>性别，编码后取值为：category 0、category1</td></tr><tr><td>age</td><td>年龄，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>provider</td><td>运营商类型，编码后取值为：category 0、category 1…</td></tr><tr><td>level</td><td>用户等级，编码后取值为：category 0、category 1…</td></tr><tr><td>verified</td><td>是否实名，编码后取值为：category 0、category1</td></tr><tr><td>using_time</td><td>使用时长，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>regist_type</td><td>注册类型，编码后取值为：category 0、category 1…</td></tr><tr><td>card_a_cnt</td><td>a类型卡的数量，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>card_b_cnt</td><td>b类型卡的数量，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>card_c_cnt</td><td>c类型卡的数量，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>card_d_cnt</td><td>d类型卡的数量，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>op1_cnt</td><td>某类型1操作数量，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>op2_cnt</td><td>某类型2操作数量，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>service1_cnt</td><td>某业务1产生数量，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>service1_amt</td><td>某业务1产生金额，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>service2_cnt</td><td>某业务2产生数量，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>agreement_total</td><td>开通协议数量，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>agreement1</td><td>是否开通协议1，编码后取值为：category 0、category1</td></tr><tr><td>agreement2</td><td>是否开通协议2，编码后取值为：category 0、category1</td></tr><tr><td>agreement3</td><td>是否开通协议3，编码后取值为：category 0、category1</td></tr><tr><td>agreement4</td><td>是否开通协议4，编码后取值为：category 0、category1</td></tr><tr><td>acc_count</td><td>账号数量，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>login_cnt_period1</td><td>某段时期1的登录次数，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>login_cnt_period2</td><td>某段时期2的登录次数，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>ip_cnt</td><td>某段时期登录ip个数，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>login_cnt_avg</td><td>某段时期登录次数均值，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>login_days_cnt</td><td>某段时期登录天数，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>province</td><td>省份，处理成类别编码</td></tr><tr><td>city</td><td>城市，处理成类别编码</td></tr><tr><td>balance</td><td>余额等级，处理成保留大小关系的类别编码：level 1、level2… 例如：level 2 &gt; level 1</td></tr><tr><td>balance_avg</td><td>近某段时期余额均值等级，处理成保留大小关系的类别编码：level 1、level2… 例如：level 2 &gt; level 1</td></tr><tr><td>balance1</td><td>类型1余额等级，处理成保留大小关系的类别编码：level 1、level2… 例如：level 2 &gt; level 1</td></tr><tr><td>balance1_avg</td><td>近某段时期类型1余额均值等级，处理成保留大小关系的类别编码：level 1、level2… 例如：level 2 &gt; level 1</td></tr><tr><td>balance2</td><td>类型2余额等级，处理成保留大小关系的类别编码：level 1、level2… 例如：level 2 &gt; level 1</td></tr><tr><td>balance2_avg</td><td>近某段时期类型2余额均值等级，处理成保留大小关系的类别编码：level 1、level2… 例如：level 2 &gt; level 1</td></tr><tr><td>service3</td><td>是否服务3用户，编码后取值为：category 0、category1</td></tr><tr><td>service3_level</td><td>服务3等级，编码后取值为：category 0、category1…</td></tr><tr><td>product1_amount</td><td>产品1金额等级，处理成保留大小关系的类别编码：level 1、level2… 例如：level 2 &gt; level 1</td></tr><tr><td>product2_amount</td><td>产品2金额等级，处理成保留大小关系的类别编码：level 1、level2… 例如：level 2 &gt; level 1</td></tr><tr><td>product3_amount</td><td>产品3金额等级，处理成保留大小关系的类别编码：level 1、level2… 例如：level 2 &gt; level 1</td></tr><tr><td>product4_amount</td><td>产品4金额等级，处理成保留大小关系的类别编码：level 1、level2… 例如：level 2 &gt; level 1</td></tr><tr><td>product5_amount</td><td>产品5金额等级，处理成保留大小关系的类别编码：level 1、level2… 例如：level 2 &gt; level 1</td></tr><tr><td>product6_amount</td><td>产品6金额等级，处理成保留大小关系的类别编码：level 1、level2… 例如：level 2 &gt; level 1</td></tr><tr><td>product7_cnt</td><td>产品7申请次数，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>product7_fail_cnt</td><td>产品7申请失败次数，处理后仅保留大小关系，为某一区间的整数</td></tr></tbody></table><h2 id="操作信息"><a href="#操作信息" class="headerlink" title="操作信息"></a>操作信息</h2><table><thead><tr><th>字段名</th><th>字段说明（数据经过脱敏处理）</th></tr></thead><tbody><tr><td>user</td><td>样本编号，e.g., Train_00000、Train_00001…</td></tr><tr><td>op_type</td><td>操作类型编码，处理成类别编码</td></tr><tr><td>op_mode</td><td>操作模式编码，处理成类别编码</td></tr><tr><td>op_device</td><td>操作设备编码，处理成类别编码</td></tr><tr><td>ip</td><td>设备ip编码，处理成类别编码</td></tr><tr><td>net_type</td><td>网络类型编码，处理成类别编码</td></tr><tr><td>channel</td><td>渠道类型编码，处理成类别编码</td></tr><tr><td>ip_3</td><td>设备ip前三位编码，处理成类别编码</td></tr><tr><td>tm_diff</td><td>距离某起始时间点的时间间隔，处理成如下格式。例如： 9 days 09:02:45.000000000，表示距离某起始时间点9天9小时2分钟45秒</td></tr></tbody></table><h2 id="交易信息"><a href="#交易信息" class="headerlink" title="交易信息"></a>交易信息</h2><table><thead><tr><th>字段名</th><th>字段说明（数据经过脱敏处理）</th></tr></thead><tbody><tr><td>user</td><td>样本编号，e.g., Train_00000、Train_00001…</td></tr><tr><td>platform</td><td>平台类型编码，处理成类别编码</td></tr><tr><td>tunnel_in</td><td>来源类型编码，处理成类别编码</td></tr><tr><td>tunnel_out</td><td>去向类型编码，处理成类别编码</td></tr><tr><td>amount</td><td>交易金额，处理后仅保留大小关系，为某一区间的整数</td></tr><tr><td>type1</td><td>交易类型1编码，处理成类别编码</td></tr><tr><td>type2</td><td>交易类型2编码，处理成类别编码</td></tr><tr><td>ip</td><td>设备ip编码，处理成类别编码</td></tr><tr><td>ip_3</td><td>设备ip前三位编码，处理成类别编码</td></tr><tr><td>tm_diff</td><td>距离某起始时间点的时间间隔，处理成如下格式。例如： 9 days 09:02:45.000000000，表示距离某起始时间点9天9小时2分钟45秒</td></tr></tbody></table></details> <h1 id="方案总结"><a href="#方案总结" class="headerlink" title="方案总结"></a>方案总结</h1><h2 id="特征工程"><a href="#特征工程" class="headerlink" title="特征工程"></a>特征工程</h2><p>特征工程之前先进行了一波预处理，数据介绍里说过所给数据进行了高度匿名化，因此引入了一些不合理性。比如余额等级这个特征被转换成类别编码，但同时存在大小关系：level 2 &gt; level 1。如果将其不处理作为类别特征输入，模型无法学习到之间的大小关系，所以我将其中的数字提取出来作为数值特征，能帮助模型提高9个w。特征工程围绕基础数据的交叉衍生，群体黑样本比例，操作行为特征和交易行为特征展开。</p><h3 id="基础特征"><a href="#基础特征" class="headerlink" title="基础特征"></a>基础特征</h3><p>基础数据分为两大类：数值特征和类别特征。对所有的数值特征进行一波二阶相加，另外结合对数据的理解，进行了一些细致的二阶或者三阶的相加或相除运算。在这一步可以构造出一个强有力的特征：用户产品7申请失败比率（用户申请失败次数/用户申请次数），在正常用户和异常用户上的分布如图1所示，该特征可以帮助模型提升3个k。我猜测翼支付本身肯定有一套风险控制系统，对于潜在的风险用户提高了准入门槛，使得特定产品的申请失败率相较于正常用户较高。另外正常用户和风险用户偏好的登录时段也不一样，如图2和图3所示，正常用户更多地在时期1登录，而风险用户则在时期2登录。类别特征可以用拼接进行二阶交叉，也可以计数反应类别热度。</p><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/%E7%94%A8%E6%88%B7%E4%BA%A7%E5%93%817%E5%A4%B1%E8%B4%A5%E6%AF%94%E4%BE%8B.png" alt="图1 用户产品7申请失败比率分布。橘黄色代表风险用户，蓝色代表正常用户" title="">                </div>                <div class="image-caption">图1 用户产品7申请失败比率分布。橘黄色代表风险用户，蓝色代表正常用户</div>            </figure></div><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/%E7%99%BB%E5%BD%95%E6%97%B6%E6%AE%B51.png" alt="图2 登录时段2分布。橘黄色代表风险用户，蓝色代表正常用户" title="">                </div>                <div class="image-caption">图2 登录时段2分布。橘黄色代表风险用户，蓝色代表正常用户</div>            </figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/%E7%99%BB%E5%BD%95%E6%97%B6%E6%AE%B52.png" alt="图3 登录时段1分布。橘黄色代表风险用户，蓝色代表正常用户" title="">                </div>                <div class="image-caption">图3 登录时段1分布。橘黄色代表风险用户，蓝色代表正常用户</div>            </figure></div><h3 id="群体黑样本比例"><a href="#群体黑样本比例" class="headerlink" title="群体黑样本比例"></a>群体黑样本比例</h3><p>之前参加过的反欺诈或者风控比赛，对群体的识别都比较有效。一般欺诈行为都是团队作战，位于同一个区域或者使用同一种价格低廉的手机。所以可以根据省份，城市，设备等属性划分群体，甚至两两属性组合进一步细分群体。划分群体之后，对群体样本进行五折交叉统计，得到群体的黑样本比率，或者称该群体发生违约风险的概率。该特征作为先验知识帮助模型提高识别准确率。图4和图5可以看出，不同群体之间的黑样本比率相差较大，在真实的业务场景中，可以针对不同的群体用户，采取不同等级的审核力度，从而尽可能减少风险用户。以设备和年龄组合这个细分群体来说，每个年龄段都有其偏好的手机类型，比如年轻人可能偏好于华为或者苹果，而老年人可能倾向于价格便宜的OPPO或者vivo（仅做举例，不一定准确）。但对一欺诈团队，其不一定符合这一现象，比如他们的设备往往可能是统一采购的低廉机型，从而出现与其年龄段不相称的使用机型，通过这一细分群体的划分可以帮助我们定位风险用户群体。</p><div style="margin: auto;width:60%"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/%E8%AE%BE%E5%A4%87%E7%BE%A4%E4%BD%93.png" alt="图4 不同设备群体黑样本比例" title="">                </div>                <div class="image-caption">图4 不同设备群体黑样本比例</div>            </figure></div><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/%E5%9F%8E%E5%B8%82%E7%BE%A4%E4%BD%93.png" alt="图5 不同城市用户群体黑样本比例" title="">                </div>                <div class="image-caption">图5 不同城市用户群体黑样本比例</div>            </figure></div><h3 id="操作行为特征"><a href="#操作行为特征" class="headerlink" title="操作行为特征"></a>操作行为特征</h3><p>对操作行为表主要是进行时空特征统计，具体流程如图6所示。这组特征主要使用pivot_table完成，所以构造的特征维度较高。另外在时间维度上，采取滑窗选取样本进行细化统计，比如用户最近15天进行过多少次操作类型为×××的操作。</p><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/%E6%93%8D%E4%BD%9C%E8%A1%8C%E4%B8%BA%E7%89%B9%E5%BE%81.png" alt="图6 操作行为特征构造思路" title="">                </div>                <div class="image-caption">图6 操作行为特征构造思路</div>            </figure></div><h3 id="交易行为特征"><a href="#交易行为特征" class="headerlink" title="交易行为特征"></a>交易行为特征</h3><p>交易行为特征的构造和操作行为特征差不多，具体流程如图7所示，只不过交易行为相较于操作行为多了一个很重要的特征：交易额。操作行为特征主要是计算多少次操作（count），而交易行为特征围绕交易额进行了一系列时间，空间或者时空维度的统计，比如：用户在××平台上交易额的平均值，最大值，用户最近15天交易额的平均值，总和，用户最近3天进行交易类型1的交易额的总和…</p><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/%E4%BA%A4%E6%98%93%E8%A1%8C%E4%B8%BA%E7%89%B9%E5%BE%81.png" alt="图7 交易行为特征构造思路" title="">                </div>                <div class="image-caption">图7 交易行为特征构造思路</div>            </figure></div><h2 id="模型"><a href="#模型" class="headerlink" title="模型"></a>模型</h2><p>模型方面没有使用过多繁杂的融合手段。初赛时候构造特征比较细致，特征的测试维度都是以个计算，追求线上和线下的一致性，避免过拟合。复赛由于入场较晚，没有过多的时间测试，所以测试维度都是以组为单位进行，生成的特征维度较高，但模型效果提升很快。所以最后采用了两套特征工程，一套是初赛方案，保证整体的鲁棒性，另外一套是复赛的梭哈版本，极大提高了模型效果。对两套特征工程分别使用lgb和xgb模型生成4个概率文件，使用指数加权融合得到最后的提交结果。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>初赛的时候由于平台算分bug，导致和前排分数差异太大，所以早早放弃保平安了。复赛再参与进来，剩余时间不多，也错过了组队机会，这一题感觉如果有队友融合提升应该很明显。自己没有队友融合，所以最后死马当作活马医，和初赛方案尝试融合了一下，没想到竟然比模型差异融合带来的效果还好。</p><p>决赛答辩的时候，很多队伍都提到了对行为数据提取序列特征，但word2vec，tfidf和countvec在我这边效果都不好，只有初赛的时候对amount做word2vec有点提升。可能是因为暴力梭哈特征维度太高了，已经包含了序列特征能反映的信息。所以比赛还是有队友差异性融合比较好，所有的思路不要都放在一个方案里。</p><p>比赛过程还尝试过其他思路，比如伪标签，但预测出来的概率值普遍不高，都没有超过0.9，所以放弃了。另外由于序列行为的存在，尝试了一波基于LSTM的nn建模，但效果也不好，融合也没有提升。</p><p>这题的数据格式倒是好像挺常见，之前参与的2020腾讯游戏安全技术竞赛机器学习组赛题数据与之类似。此外本次比赛第一名大佬在答辩时提到的将操作行为和交易行为融合成一张大表做统计的思路挺好的，自己比赛时没有想到。</p><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ul><li><a href="https://logicjake.github.io/2020/04/16/2020%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8%E6%8A%80%E6%9C%AF%E7%AB%9E%E8%B5%9B%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E7%BB%84-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/">2020腾讯游戏安全技术竞赛机器学习组-TOP 4赛后总结</a></li><li><a href="http://mp.weixin.qq.com/s?__biz=MzU0MjE2MzcxMA==&mid=2247483934&idx=1&sn=ebdbd88ae5d6bc5e0a708b578e583a60&chksm=fb1f958fcc681c99b31385f4cfee6471c4b0231d783af25cf09531b37ed2550216826e3788cd#rd" target="_blank" rel="noopener">第二届翼支付杯大数据建模大赛-信用风险用户识别 baseline 分享</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最终成绩：2 / 392&lt;br&gt;比赛地址：&lt;a href=&quot;https://www.dcjingsai.com/v2/cmptDetail.html?id=410&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.dcjingsai.com/v2/cmptDetail.html?id=410&lt;/a&gt;&lt;br&gt;源码：&lt;a href=&quot;https://github.com/LogicJake/2020-yizhifu-credit-risk-user-identification-Top2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/LogicJake/2020-yizhifu-credit-risk-user-identification-Top2&lt;/a&gt;  &lt;/p&gt;
&lt;h1 id=&quot;赛题背景&quot;&gt;&lt;a href=&quot;#赛题背景&quot; class=&quot;headerlink&quot; title=&quot;赛题背景&quot;&gt;&lt;/a&gt;赛题背景&lt;/h1&gt;&lt;p&gt;央行发布《金融科技FinTech》报告，强调金融科技成为推动金融转型升级的新引擎，成为促进普惠金融发展的新机遇。运用大数据、人工智能等技术建立金融风控模型，有效甄别高风险交易，智能感知异常交易，实现风险早识别、早预警、早处置，提升金融风险技防能力，是 “金融+科技”成果的显著体现。&lt;/p&gt;
    
    </summary>
    
    
      <category term="比赛总结" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
    
      <category term="机器学习" scheme="https://logicjake.github.io/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/tags/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
      <category term="风控" scheme="https://logicjake.github.io/tags/%E9%A3%8E%E6%8E%A7/"/>
    
  </entry>
  
  <entry>
    <title>2020腾讯广告算法大赛-赛后总结</title>
    <link href="https://logicjake.github.io/2020/09/24/2020%E8%85%BE%E8%AE%AF%E5%B9%BF%E5%91%8A%E7%AE%97%E6%B3%95%E5%A4%A7%E8%B5%9B-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/"/>
    <id>https://logicjake.github.io/2020/09/24/2020%E8%85%BE%E8%AE%AF%E5%B9%BF%E5%91%8A%E7%AE%97%E6%B3%95%E5%A4%A7%E8%B5%9B-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/</id>
    <published>2020-09-24T22:29:45.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<p>最终成绩：12 / 10000+<br>比赛地址：<a href="https://algo.qq.com/index.html" target="_blank" rel="noopener">https://algo.qq.com/index.html</a><br>源码：<a href="https://github.com/LogicJake/Tencent_Ads_Algo_2020_TOP12" target="_blank" rel="noopener">https://github.com/LogicJake/Tencent_Ads_Algo_2020_TOP12</a>  </p><p>今年腾讯赛一改往年机器学习为主流的状况，纯 id 数据导致特征工程的发力点弱化，整场比赛变成了一场深度学习比拼。不过感谢腾讯提供的机器（Tione真香），降低了参与门槛。</p><a id="more"></a><h1 id="赛题背景"><a href="#赛题背景" class="headerlink" title="赛题背景"></a>赛题背景</h1><p>本届算法大赛的题目来源于一个重要且有趣的问题。众所周知，像用户年龄和性别这样的人口统计学特征是各类推荐系统的重要输入特征，其中自然也包括了广告平台。这背后的假设是，用户对广告的偏好会随着其年龄和性别的不同而有所区别。许多行业的实践者已经多次验证了这一假设。然而，大多数验证所采用的方式都是以人口统计学属性作为输入来产生推荐结果，然后离线或者在线地对比用与不用这些输入的情况下的推荐性能。本届大赛的题目尝试从另一个方向来验证这个假设，即以用户在广告系统中的交互行为作为输入来预测用户的人口统计学属性。我们认为这一赛题的“逆向思考”本身具有其研究价值和趣味性，此外也有实用价值和挑战性。例如，对于缺乏用户信息的实践者来说，基于其自有系统的数据来推断用户属性，可以帮助其在更广的人群上实现智能定向或者受众保护。与此同时，参赛者需要综合运用机器学习领域的各种技术来实现更准确的预估。</p><h1 id="赛题内容"><a href="#赛题内容" class="headerlink" title="赛题内容"></a>赛题内容</h1><h2 id="数据"><a href="#数据" class="headerlink" title="数据"></a>数据</h2><p>在比赛期间，主办方将为参赛者提供一组用户在长度为 91 天（3 个月）的时间窗口内的广告点击历史记录作为训练数据集。每条记录中包含了日期（从 1 到 91）、用户信息（年龄，性别），被点击的广告的信息（素材 id、广告 id、产品 id、产品类目 id、广告主id、广告主行业 id 等），以及该用户当天点击该广告的次数。测试数据集将会是另一组用户的广告点击历史记录。</p><h2 id="评价指标"><a href="#评价指标" class="headerlink" title="评价指标"></a>评价指标</h2><p>大赛会根据参赛者提交的结果计算预测的准确率（accuracy）。年龄预测和性别预测将分别评估准确率，两者之和将被用作参赛者的打分。测试数据集会和训练数据集一起提供给参赛者。</p><h1 id="建模"><a href="#建模" class="headerlink" title="建模"></a>建模</h1><p>不管是平时赛题群交流，还是赛后的前排方案开源，核心解题思路基本一致：先利用 word2vec 对数据中的各列 id 进行表示学习，然后对用户的广告点击序列进行建模。在建模步骤，可以尝试 LSTM，Transformer，Bert 甚至 CNN。相较于鱼佬的巨无霸 Bert 模型，我们团队走的是小模型融合路线。我用 pytorch 进行建模，其他两个队友使用 Keras 建模并尝试传统的机器学习方案（特征工程+Lightgbm）。利用深度学习框架差异性，输出差异性，输入差异性甚至优化器的差异性来提高融合效果。</p><p>以产品 id 为例，将用户点击过的产品 id 按照点击日期排序，得到相对有序的产品 id 序列。将每个用户的点击产品 id 序列视为句子，输入到 word2vec 模型学习每个产品 id 的表示。用户的点击序列不定长，可以使用 keras 的 pad_sequences，对长截断对短补全，使得同一 id 下的用户序列长度相等。至此完成数据预处理，后面就是搭建一个序列分类模型，整体网络结构如图1所示。</p><p>其中各个 id 的 embedding 层使用 word2vec 得到的预训练向量填充，并在模型训练过程中冻结。剩下的主要任务就是调参，自己本身也是个深度学习入门选手，所以也是边学习边尝试，以下是总结的从腾讯赛学到的调参思路和 trick：</p><p><strong>通用</strong>：</p><ul><li>Embedding 维度</li><li>序列截取的最大长度的选择</li><li>预训练模型 Word2Vec 的参数选择：window，min_count…</li><li>序列处理时是否需要筛掉低频 id</li><li>优化器 optimizer 的选择，可以尝试一些最新的，比如 ranger</li><li>激活函数的选择：mish…</li><li>学习率衰减，比如：ReduceLROnPlateau</li></ul><p><strong>LSTM</strong>：</p><ul><li>units 的大小</li><li>堆叠多少层 LSTM layer</li></ul><p><strong>CNN</strong>：</p><ul><li>filter_size 除了 3，4，5 还可以更大吗</li><li>num_filters 大小</li></ul><p><strong>Transformer</strong>：</p><ul><li>学习率小一点</li><li>head_num 大小</li><li>堆叠层数</li></ul><h1 id="融合"><a href="#融合" class="headerlink" title="融合"></a>融合</h1><h2 id="stacking"><a href="#stacking" class="headerlink" title="stacking"></a>stacking</h2><p>使用了两种 stacking 方式:</p><p>纯概率特征 stacking：将各个模型跑出来的概率分层进入 DNN (如 keras 产生的概率与 keras 产生的概率合并，torch 概率与 torch 概率合并, keras 概率先进入, torch 在 keras 概率经过了几层全连接之后再 concat, 实验证明这种做法可以避免相关性较高的概率带来的融合不利影响), 五折线上分数大概为 0.525  </p><p>混合特征 stacking: 在采用了两 id 序列输入的 transformers 模型基础上，在最后经过全连接层之前跟上面的九个相关性较低的模型进行 concat, 起到类似于残差的作用，避免过拟合, 五折线上分数为 0.523</p><h2 id="blending"><a href="#blending" class="headerlink" title="blending"></a>blending</h2><h2 id="Age"><a href="#Age" class="headerlink" title="Age"></a>Age</h2><p>比赛结束前一周我们使用了 huggingface transformers 重新实现了 transformer + LSTM 模型, 五折分数为 0.519, 而且相关度与之前实现的 keras 和 torch 都较低, 只有 0.93,0.94 左右 (相比 keras 之间的相似度高达 0.98, torch 之间相似度 0.96), 所有我们单独将这个模型与上面所生成的两个 stacking 模型进行融合，取得 0.52780 线上分数：</p><p>0.50 * DNN_stacking + 0.15 * transformer_stacking + 0.35 * age_m13</p><h2 id="Gender"><a href="#Gender" class="headerlink" title="Gender"></a>Gender</h2><p>Gender 主要是三个 transformer 模型进行基本均等的融合：线上分数 0.95048</p><p>0.35 * gender_m1 + 0.35 * gender_m2 + 0.30 * gender_m3</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>第一名方案使用了 Bert 预训练模型，而与这次腾讯赛建模思路比较相似的另外一场比赛——<a href="https://tianchi.aliyun.com/competition/entrance/531810/information" target="_blank" rel="noopener">零基础入门NLP - 新闻文本分类</a>，第四名方法也使用了 Bert 模型，后面要自己尝试一下，对比一下实验效果。</p><p>时间太久了，好多细节都记不清楚了，以后还是比赛结束就写总结比较好。</p><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ul><li><a href="https://zhuanlan.zhihu.com/p/166710532" target="_blank" rel="noopener">2020腾讯广告算法大赛方案分享（冠军）</a></li><li><a href="https://tianchi.aliyun.com/forum/postDetail?postId=128734" target="_blank" rel="noopener">Rank4 NLP新闻文本分类-开源代码+经验分享@惊鹊</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最终成绩：12 / 10000+&lt;br&gt;比赛地址：&lt;a href=&quot;https://algo.qq.com/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://algo.qq.com/index.html&lt;/a&gt;&lt;br&gt;源码：&lt;a href=&quot;https://github.com/LogicJake/Tencent_Ads_Algo_2020_TOP12&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/LogicJake/Tencent_Ads_Algo_2020_TOP12&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;今年腾讯赛一改往年机器学习为主流的状况，纯 id 数据导致特征工程的发力点弱化，整场比赛变成了一场深度学习比拼。不过感谢腾讯提供的机器（Tione真香），降低了参与门槛。&lt;/p&gt;
    
    </summary>
    
    
      <category term="比赛总结" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/tags/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
      <category term="深度学习" scheme="https://logicjake.github.io/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
    
      <category term="推荐系统" scheme="https://logicjake.github.io/tags/%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F/"/>
    
  </entry>
  
  <entry>
    <title>KDD Cup 2020 Challenges for Modern E-Commerce Platform: Debiasing TOP13赛后总结</title>
    <link href="https://logicjake.github.io/2020/06/16/KDD-debias-TOP13/"/>
    <id>https://logicjake.github.io/2020/06/16/KDD-debias-TOP13/</id>
    <published>2020-06-16T14:18:17.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<p>最终成绩：Full榜15，Half榜13<br>比赛地址：<a href="https://tianchi.aliyun.com/competition/entrance/231785/" target="_blank" rel="noopener">https://tianchi.aliyun.com/competition/entrance/231785/</a><br>源码：<a href="https://github.com/LogicJake/2020_KDD_Debiasing_TOP13" target="_blank" rel="noopener">https://github.com/LogicJake/2020_KDD_Debiasing_TOP13</a>  </p><h1 id="赛题背景"><a href="#赛题背景" class="headerlink" title="赛题背景"></a>赛题背景</h1><p>该挑战着重于曝光的公平性，即推荐过去很少曝光的项目，以对抗在推荐系统中经常遇到的 Matthew 效应。特别是，在对点击数据进行训练时执行偏差减少对该任务的成功至关重要。就像现代推荐系统中记录的点击数据与实际的在线环境有差距一样，训练数据与测试数据也会有差距，主要体现在趋势和物品的受欢迎程度上。获胜的解决方案需要在历史上很少暴露的项目上表现良好。训练数据和测试数据是在许多时期收集的，其中包括大规模的销售活动。由于趋势的变化，为了进行可靠的预测，减少偏差是不可避免的。我们提供物品的多模式特性以及一些(匿名的)关键用户特性，以帮助参与者探索解决方案，以调整数据偏差，并能够很好地处理未探索过的物品。</p><h1 id="建模"><a href="#建模" class="headerlink" title="建模"></a>建模</h1><p>比赛给出的数据如下所示，××_click_× 是用户在各个阶段的点击记录，××_qtime_× 是需要预测的用户相对的最后一次点击。数据按划窗的方式逐阶段（phase）给出，各阶段的时间分布如图1所示。各阶段大概有4天的数据，相邻阶段数据时间有交叉。且单阶段中，训练集用户和测试集用户不交叉。原始数据集只给出了测试集的 qtime ，为了获取稳定的线下验证，需要我们自己构造有标签的训练集 qtime。基于题设，我们抽出每个阶段训练集用户的最后一条 click 数据作为训练集 qtime 数据，用于线下验证，形式化表示如图2所示。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">|-- data</span><br><span class="line">|-- underexpose_train</span><br><span class="line">|-- underexpose_user_feat.csv</span><br><span class="line">|-- underexpose_item_feat.csv</span><br><span class="line">|-- underexpose_train_click-0.csv</span><br><span class="line">|-- ...</span><br><span class="line">|-- underexpose_train_click-9.csv</span><br><span class="line">|-- underexpose_test</span><br><span class="line">|-- underexpose_test_click-0</span><br><span class="line">|-- underexpose_test_qtime-0.csv</span><br><span class="line">|-- underexpose_test_click-0.csv</span><br><span class="line">|-- ...</span><br><span class="line">|-- underexpose_test_click-9</span><br><span class="line">|-- underexpose_test_qtime-9.csv</span><br><span class="line">|-- underexpose_test_click-9.csv</span><br></pre></td></tr></table></figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/time.png" alt="图1 各阶段时间分布" title="">                </div>                <div class="image-caption">图1 各阶段时间分布</div>            </figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/data_construct.png" alt="图2 训练集 qtime 构造方式" title="">                </div>                <div class="image-caption">图2 训练集 qtime 构造方式</div>            </figure><p>问题被切分为召回和排序两个部分，总共六路召回，最后合并删除重复召回的商品。排序阶段建模为二分类问题，采用了两个深度模型和一个LGB模型。整体流程如图3所示。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/structure.png" alt="图3 整体流程图" title="">                </div>                <div class="image-caption">图3 整体流程图</div>            </figure><h1 id="召回"><a href="#召回" class="headerlink" title="召回"></a>召回</h1><p>在召回阶段，我们采用了6路召回，通过不同策略的差异性提高整体商品的召回率。这6种方法或利用点击序列信息提高了热门商品的召回率，或采用商品的属性信息提高冷门商品的召回率，或二者兼顾。在所有召回策略中，均是分 phase 召回。</p><h2 id="改进版-itemcf"><a href="#改进版-itemcf" class="headerlink" title="改进版 itemcf"></a>改进版 itemcf</h2><p>原始的 itemcf 将用户点击过的商品看做一个无序的集合，但在实际应用中，应该考虑到点击次序和时间带来的影响。在计算同一序列中两个商品的相似度时，不仅需要考虑其共现次数，也需要考虑两个商品之间的次序关系和时间关系。同一点击序列中两个商品位置越远或者点击时间间隔越大，相关性应该减小。正因为存在时间和距离衰减，我们跳过了点击距离过远（大于5）或点击时间过长（大于0.000003）的商品对的相似度计算。商品对顺序和逆序的权重也不同，在点击序列A，B，C中，”BC”这样的正序权重应该大于”BA”这样的逆序权重。为了解决偏差问题，除了从序列上建立相似性关系，也可以将商品之间的文本相似度和图像相似度纳入相似性因素，引入这二者可以极大地提高冷门商品的召回率。</p><p>建立商品的相似度关系后，进入到给用户召回商品阶段，根据用户的交互商品，结合商品相似度选择 TOP100 关联商品。选取关联商品时，除了考虑和历史交互商品的相似度，还要加入时间衰减，位置距离衰减和候选商品热度。</p><p>我们总共有两版 itemcf，二者或在上述策略的部分选用上有所差异或者衰减函数有所差异。我们保存了本阶段得到的商品相似度，供后续特征工程进一步使用。</p><h2 id="item2vec"><a href="#item2vec" class="headerlink" title="item2vec"></a>item2vec</h2><p>除了使用人工规则从序列中提取相似度，我们还可以使用序列学习模型 Word2Vec 为商品学习向量表示。将用户的商品点击序列作为句子喂到 Word2Vec 模型，然后选取和用户最近交互商品最相似的关联商品。向量学习和寻找相似全部使用 gensim 库的 Word2Vec 实现，向量维度和迭代次数对最终结果影响较大。该方法能够极好地召回冷门商品。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"># 模型训练</span><br><span class="line">model &#x3D; Word2Vec(sentences, size&#x3D;256, window&#x3D;5, min_count&#x3D;1,</span><br><span class="line">                         sg&#x3D;1, hs&#x3D;0, seed&#x3D;seed, iter&#x3D;300, negative&#x3D;5, workers&#x3D;6)</span><br><span class="line"></span><br><span class="line"># 寻找关联商品</span><br><span class="line">interacted_items &#x3D; user_item[user_id]</span><br><span class="line">sim_items &#x3D; model.wv.most_similar(positive&#x3D;[str(x) for x in interacted_items[-2:]], topn&#x3D;100)</span><br></pre></td></tr></table></figure><h2 id="基于商品属性的-ANN-召回"><a href="#基于商品属性的-ANN-召回" class="headerlink" title="基于商品属性的 ANN 召回"></a>基于商品属性的 ANN 召回</h2><p>基于交互序列的 itemcf 倾向于召回热门商品，为了进一步提高冷门商品的召回，我们采用了基于商品属性信息的 ANN 召回方法。数据集本身给出了商品的文本表示和图像表示，所以可以分别针对商品的文本 embedding 和图像 embedding 进行向量召回。我们对用户历史交互商品的向量表示进行加权平均得到用户的表示向量。在相似度计算召回阶段，如果使用矩阵运算空间复杂度会非常高，如果采用双层 for 循环则时间复杂度又太高，所以我们使用 Annoy 工具。Annoy 是 Spotify 开源的一个用于近似最近邻查询的 C++/Python 工具，对内存使用进行了优化，索引可以在硬盘保存或者加载。简单来说，先将商品的向量表示建立索引，然后用用户的向量表示寻找近似的最相似商品。Annoy 稍微牺牲了相似度计算的准确度，却极大地提高了查找速度。</p><p>对比基于商品文本信息和图像信息的召回，基于文本的召回方式要胜于基于图像的召回方式，可能是因为文本能更好地描述商品，而图片信息噪声相对较大。</p><h2 id="基于网络的召回"><a href="#基于网络的召回" class="headerlink" title="基于网络的召回"></a>基于网络的召回</h2><p>该方法源自”Bipartite network projection and personal recommendation”，代码采用自论坛开源。该方法也分为两个阶段，商品相似度计算和基于用户交互历史的商品召回。在相似度计算阶段，通过用户将两个商品连接起来。计算相似度时考虑两种因素：1)两个商品的共同被点击用户过多，则相似度减少；2)共同被点击用户的交互商品过多，相似度也要减少。基于用户交互历史的商品召回和 itemcf 类似，不再赘述。</p><p>我们设置每路都召回100个商品，但这之间肯定存在重复召回的情况，这也是为什么召回策略讲究差异性，其实就是为了减少重复召回的数量。去重之后，平均每个用户召回到440个商品。因为每个用户在 qtime 中只会点击一个商品，所以召回构造的样本正负比也会比较大，所以进一步地我们删除了召回未命中的用户样本，减少了无用负样本的数量，也提高了后续排序模型的效果。</p><p>假设训练集用户A 在阶段0的 qtime 真实点击了商品i，基于用户A的阶段0历史交互记录，我们为其召回到商品i，j，k，l，那么在召回阶段结束我们可以得到如下样本。<br>| user_id | phase |  query_time  | item_id | label |<br>| :—–: | :—: | :———-: | :—–: | :—: |<br>|    A    |   0   | 0.9839420823 |    i    |   1   |<br>|    A    |   0   | 0.9839420823 |    j    |   0   |<br>|    A    |   0   | 0.9839420823 |    k    |   0   |<br>|    A    |   0   | 0.9839420823 |    l    |   0   |</p><h1 id="排序"><a href="#排序" class="headerlink" title="排序"></a>排序</h1><p>我们把排序建模为二分类任务，分为特征工程和二分类模型预测两部分。特征工程主要围绕召回策略进行，大量使用用户历史交互商品和待预测商品的相似度特征。模型包括两个基于 LSTM 的深度模型和一个 LGB 模型，两个深度模型的预测概率作为 LGB 模型的特征。</p><h2 id="特征工程"><a href="#特征工程" class="headerlink" title="特征工程"></a>特征工程</h2><p>特征工程分为三大类：商品属性，用户属性，用户-商品交互属性。数据集本身给出的属性信息较少，所以特征工程主要围绕交互属性展开。商品信息给出了128维的图像向量和128维的文本向量，原始向量维度较大，所以我们分别对其使用 PCA 降维，降维后反而提高了模型效果。除此以外统计了商品被点击次数，平均点击间隔，点击用户的年龄统计和性别统计。</p><p>用户特征包括：</p><ul><li>user_age_level：用户所属的年龄段（原始属性）  </li><li>user_gender：用户的性别，可以为空（原始属性）  </li><li>user_city_level：用户所在城市的等级（原始属性）  </li><li>历史点击商品次数</li><li>历史点击时间的统计特征（min,max,std）</li><li>预测时间点距离用户最进一次点击的时间差</li><li>用户历史点击时间跨度（max-min）</li></ul><p>交互特征主要基于之前的召回策略进行，通过保存召回阶段的商品相似度信息或向量，我们能够间接或直接得到用户对待预测商品的评分。基于商品属性的ANN召回已经得到用户和商品的向量表示，通过余弦相似度可以直接得到用户对待预测商品的评分。基于 itemcf， Bipartite network 和 item2vec 的召回得到的只是商品之间的相似度，需要和用户的历史交互商品计算间接得到用户-商品评分，采用如下方式：</p><ul><li>待预测商品和用户所有历史交互商品最大相似度</li><li>待预测商品和用户所有历史交互商品相似度按次序加权求和</li><li>待预测商品和用户最近一次交互商品相似度</li><li>待预测商品和用户最近k次交互商品相似度之和</li><li>待预测商品和用户最近k次交互商品相似度平均</li></ul><h2 id="模型"><a href="#模型" class="headerlink" title="模型"></a>模型</h2><p>两个深度模型的结构分别如图4和图5所示，LSTM 能够很好地学习用户交互序列，所以两个模型都用到了 LSTM 层。图4的深度模型除了使用 LSTM 学习序列特征，还使用了 Attention 层进一步捕捉序列关系。除了学习序列特征，其还使用了特征工程部分输出的特征，将两类特征 concat 送入两层全连接层。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/nn1.png" alt="图4 深度模型1结构图" title="">                </div>                <div class="image-caption">图4 深度模型1结构图</div>            </figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/nn2.png" alt="图5 深度模型2结构图" title="">                </div>                <div class="image-caption">图5 深度模型2结构图</div>            </figure><p>图5模型与图4的主要不同点在于，将阶段 0-6 的序列输入到 Word2Vec 模型，预训练得到商品的 embedding，然后冻结模型中的 embedding layer。</p><p>特征工程得到的特征加上两个深度模型的预测概率特征输入到 LGB 模型，输出每个用户点击某商品的最终概率。</p><h1 id="后处理"><a href="#后处理" class="headerlink" title="后处理"></a>后处理</h1><p>我们进一步对预测概率值进行后处理，减轻商品推荐的偏差问题。统计每个商品历史被点击次数，适当让点击次数较少的商品被推荐概率放大。最开始我们让概率除以商品点击次数，但这种方式较为极端，极大提高 half 指标的同时，full 指标下降严重。因为复赛要保证 full 指标在前65成绩才有效，为了稳妥起见，我们选取了另外一种后处理方式：概率除以商品点击次数的开方，提高 half 的同时平衡 full 指标的下降。</p><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><p><a href="https://tianchi.aliyun.com/forum/postDetail?postId=105787" target="_blank" rel="noopener">改进青禹小生baseline，phase3线上：0.2</a><br><a href="https://tianchi.aliyun.com/forum/postDetail?postId=103530" target="_blank" rel="noopener">A simple itemCF Baseline, score:0.1169(phase0-2)</a><br><a href="https://tianchi.aliyun.com/forum/postDetail?postId=104936" target="_blank" rel="noopener">A Simple Recall Method based on Network-based Inference，score:0.18 (phase0-3)</a><br><a href="https://zhuanlan.zhihu.com/p/100827940" target="_blank" rel="noopener">天池-安泰杯跨境电商智能算法大赛分享（冠军）</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;最终成绩：Full榜15，Half榜13&lt;br&gt;比赛地址：&lt;a href=&quot;https://tianchi.aliyun.com/competition/entrance/231785/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tia
      
    
    </summary>
    
    
      <category term="比赛总结" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
    
      <category term="推荐系统" scheme="https://logicjake.github.io/tags/%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F/"/>
    
      <category term="召回" scheme="https://logicjake.github.io/tags/%E5%8F%AC%E5%9B%9E/"/>
    
      <category term="排序" scheme="https://logicjake.github.io/tags/%E6%8E%92%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title>天池二手车交易价格预测-赛后总结</title>
    <link href="https://logicjake.github.io/2020/04/21/%E5%A4%A9%E6%B1%A0%E4%BA%8C%E6%89%8B%E8%BD%A6%E4%BA%A4%E6%98%93%E4%BB%B7%E6%A0%BC%E9%A2%84%E6%B5%8B-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/"/>
    <id>https://logicjake.github.io/2020/04/21/%E5%A4%A9%E6%B1%A0%E4%BA%8C%E6%89%8B%E8%BD%A6%E4%BA%A4%E6%98%93%E4%BB%B7%E6%A0%BC%E9%A2%84%E6%B5%8B-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/</id>
    <published>2020-04-21T20:36:11.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<p>最终成绩：62 / 2776<br>比赛地址：<a href="https://tianchi.aliyun.com/competition/entrance/231784/introduction" target="_blank" rel="noopener">https://tianchi.aliyun.com/competition/entrance/231784/introduction</a><br>源码：<a href="https://github.com/LogicJake/competition_baselines/tree/master/competitions/tianchi_car_sale/final" target="_blank" rel="noopener">https://github.com/LogicJake/competition_baselines/tree/master/competitions/tianchi_car_sale/final</a></p><h1 id="比赛介绍"><a href="#比赛介绍" class="headerlink" title="比赛介绍"></a>比赛介绍</h1><p>本次新人赛是Datawhale与天池联合发起的0基础入门系列赛事第一场 —— 零基础入门数据挖掘之二手车交易价格预测大赛。</p><p>赛题以二手车市场为背景，要求选手预测二手汽车的交易价格，这是一个典型的回归问题。通过这道赛题来引导大家走进AI数据竞赛的世界，主要针对于于竞赛新人进行自我练习、自我提高。</p><p>为了更好的引导大家入门，我们同时为本赛题定制了系列学习方案，其中包括数据科学库、通用流程和baseline方案学习三部分。通过对本方案的完整学习，可以帮助掌握数据竞赛基本技能。同时我们也将提供专属的视频直播学习通道。</p><h1 id="EDA"><a href="#EDA" class="headerlink" title="EDA"></a>EDA</h1><p>详细的 eda 可以参考天才儿童在天池的分享: <a href="https://tianchi.aliyun.com/notebook-ai/detail?postId=95276" target="_blank" rel="noopener">https://tianchi.aliyun.com/notebook-ai/detail?postId=95276</a></p><p>数据集的格式如下：  </p><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/carsale_data.jpeg" alt="数据格式" title="">                </div>                <div class="image-caption">数据格式</div>            </figure></div><p>特征可以分成三类:</p><ul><li>日期特征: regDate, creatDate</li><li>类别特征: name, model, brand, bodyType, fuelType, gearbox, notRepairedDamage, regionCode, seller, offerType</li><li>数值特征: power, kilometer和15个匿名特征</li></ul><p>这里主要关注特征的缺失率和 nunique 信息，主要是看有没有缺失过多或 nunique 太少的特征，一般情况下这两种特征对模型学习起不到作用。数值特征 power 和 kilometer nunique 值比较少，也不知道是不是数据做了处理，抹去了精度。seller 和 offerType 只有两个甚至1个不同的值，所以可以删去, 对模型学习起不到作用，模型的特征重要性也为0。</p><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/carsale_eda1.png" alt="数据统计" title="">                </div>                <div class="image-caption">数据统计</div>            </figure></div><p>匿名特征的分布见下图，匿名特征在最后的模型重要性都挺高的，可以好好挖掘一下。</p><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/carsale_eda2.jpeg" alt="匿名特征" title="">                </div>                <div class="image-caption">匿名特征</div>            </figure></div><h1 id="数据处理"><a href="#数据处理" class="headerlink" title="数据处理"></a>数据处理</h1><h2 id="缺失值处理"><a href="#缺失值处理" class="headerlink" title="缺失值处理"></a>缺失值处理</h2><p>缺失值主要集中在bodyType，fuelType，gearbox，我的思路是汽车的指标往往和其所属的品牌和车型有较大关系，所以采用该品牌车型下的众数来填补缺失值。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">from scipy import stats</span><br><span class="line"></span><br><span class="line">cols &#x3D; [&#39;bodyType&#39;, &#39;fuelType&#39;, &#39;gearbox&#39;]</span><br><span class="line">df_feature[&#39;gp&#39;] &#x3D; df_feature[&#39;brand&#39;].astype(</span><br><span class="line">    &#39;str&#39;) + df_feature[&#39;model&#39;].astype(&#39;str&#39;)</span><br><span class="line">gp_col &#x3D; &#39;gp&#39;</span><br><span class="line"></span><br><span class="line">df_na &#x3D; df_feature[cols].isna()</span><br><span class="line">df_mode &#x3D; df_feature.groupby(gp_col)[cols].agg(</span><br><span class="line">    lambda x: stats.mode(x)[0][0])</span><br><span class="line"></span><br><span class="line">for col in cols:</span><br><span class="line">    na_series &#x3D; df_na[col]</span><br><span class="line">    names &#x3D; list(df_feature.loc[na_series, gp_col])</span><br><span class="line"></span><br><span class="line">    t &#x3D; df_mode.loc[names, col]</span><br><span class="line">    t.index &#x3D; df_feature.loc[na_series, col].index</span><br><span class="line"></span><br><span class="line">    df_feature.loc[na_series, col] &#x3D; t</span><br><span class="line"></span><br><span class="line">del df_feature[&#39;gp&#39;]</span><br><span class="line">df_feature[cols].isnull().sum()</span><br></pre></td></tr></table></figure><h2 id="目标变量分布变换"><a href="#目标变量分布变换" class="headerlink" title="目标变量分布变换"></a>目标变量分布变换</h2><p>一般来说对于回归问题，目标变量正态化对模型预测有帮助，下图展示了使用 log1p 前后的价格分布情况。</p><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/carsale_eda3.png" alt="价格分布" title="">                </div>                <div class="image-caption">价格分布</div>            </figure></div><h2 id="无效特征删除"><a href="#无效特征删除" class="headerlink" title="无效特征删除"></a>无效特征删除</h2><p>seller 和 offerType 只有两个甚至1个不同的值，所以可以删去, 对模型学习起不到作用，模型的特征重要性也为0。</p><h1 id="特征工程"><a href="#特征工程" class="headerlink" title="特征工程"></a>特征工程</h1><h2 id="基础特征"><a href="#基础特征" class="headerlink" title="基础特征"></a>基础特征</h2><p>对于两个日期特征汽车注册日期和开始售卖时间，可以二者做差值计算汽车售卖时的使用时间，我这里使用了年和天来刻画。除此以外，汽车是哪一年注册的对价格的影响也挺大。数据中存在一些异常日期数据：月份为0，处理的时候将其置为1即可。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">df_feature[&#39;car_age_day&#39;] &#x3D; ( df_feature[&#39;creatDate&#39;] - df_feature[&#39;regDate&#39;]).dt.days</span><br><span class="line">df_feature[&#39;car_age_year&#39;] &#x3D; round(df_feature[&#39;car_age_day&#39;] &#x2F; 365, 1)</span><br></pre></td></tr></table></figure><p>对于类别特征, 可以计算count属性, 反应销售热度。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">df_feature[&#39;name_count&#39;] &#x3D; df_feature.groupby([&#39;name&#39;])[&#39;SaleID&#39;].transform(&#39;count&#39;)</span><br></pre></td></tr></table></figure><p>数值特征往往结合类别特征进行统计。比如可以统计不同汽车品牌下匿名特征的统计特征：mean, std, max, min。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">l &#x3D; [&#39;name&#39;, &#39;model&#39;, &#39;brand&#39;, &#39;bodyType&#39;]</span><br><span class="line">for f1 in tqdm(l):</span><br><span class="line">    for f2 in v_cols:</span><br><span class="line">        df_feature &#x3D; stat(df_feature, df_feature, [f1], &#123;</span><br><span class="line">            f2: [&#39;mean&#39;, &#39;max&#39;, &#39;min&#39;, &#39;std&#39;]&#125;)</span><br></pre></td></tr></table></figure><p>目标变量 price 也是数值特征，所以也可以结合类别进行统计，比如计算某品牌，某车型的平均交易价格，这种做法称为目标编码。但需要注意的是，假如使用全局标签信息统计会出现标签泄露的问题，所以一般使用五折统计法，用四折的标签数据做统计给另外一折的数据做特征。</p><h2 id="匿名特征"><a href="#匿名特征" class="headerlink" title="匿名特征"></a>匿名特征</h2><p>简单一点，可以直接统计每辆车15个匿名特征的统计值，得到v_mean，v_max，v_min和v_std。然后再统计汽车交易名称下这四个特征的统计值，这道题，汽车交易名称也是一个很重要的特征。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">v_cols &#x3D; [&#39;v_&#39;+str(i) for i in range(15)]</span><br><span class="line"></span><br><span class="line">df_feature[&#39;v_mean&#39;] &#x3D; df_feature[v_cols].mean(axis&#x3D;1)</span><br><span class="line">df_feature[&#39;v_max&#39;] &#x3D; df_feature[v_cols].max(axis&#x3D;1)</span><br><span class="line">df_feature[&#39;v_min&#39;] &#x3D; df_feature[v_cols].min(axis&#x3D;1)</span><br><span class="line">df_feature[&#39;v_std&#39;] &#x3D; df_feature[v_cols].std(axis&#x3D;1)</span><br><span class="line"></span><br><span class="line">for col in [&#39;v_mean&#39;, &#39;v_max&#39;, &#39;v_min&#39;, &#39;v_std&#39;]:</span><br><span class="line">    df_feature[f&#39;name_&#123;col&#125;_mean&#39;] &#x3D; df_feature.groupby(&#39;name&#39;)[</span><br><span class="line">        col].transform(&#39;mean&#39;)</span><br><span class="line">    df_feature[f&#39;name_&#123;col&#125;_std&#39;] &#x3D; df_feature.groupby(&#39;name&#39;)[</span><br><span class="line">        col].transform(&#39;std&#39;)</span><br><span class="line">    df_feature[f&#39;name_&#123;col&#125;_max&#39;] &#x3D; df_feature.groupby(&#39;name&#39;)[</span><br><span class="line">        col].transform(&#39;max&#39;)</span><br><span class="line">    df_feature[f&#39;name_&#123;col&#125;_min&#39;] &#x3D; df_feature.groupby(&#39;name&#39;)[</span><br><span class="line">        col].transform(&#39;min&#39;)</span><br></pre></td></tr></table></figure><p>匿名特征无法知道具体的业务含义，所以只能梭哈操作，写了个程序对匿名特征进行二阶或三阶组合，计算相加和相减，最后筛选保留以下特征：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">df_feature[&#39;v_0_add_v_4&#39;] &#x3D; df_feature[&#39;v_0&#39;] + df_feature[&#39;v_4&#39;]</span><br><span class="line">df_feature[&#39;v_0_add_v_8&#39;] &#x3D; df_feature[&#39;v_0&#39;] + df_feature[&#39;v_8&#39;]</span><br><span class="line">df_feature[&#39;v_1_add_v_3&#39;] &#x3D; df_feature[&#39;v_1&#39;] + df_feature[&#39;v_3&#39;]</span><br><span class="line">df_feature[&#39;v_1_add_v_4&#39;] &#x3D; df_feature[&#39;v_1&#39;] + df_feature[&#39;v_4&#39;]</span><br><span class="line">df_feature[&#39;v_1_add_v_5&#39;] &#x3D; df_feature[&#39;v_1&#39;] + df_feature[&#39;v_5&#39;]</span><br><span class="line">df_feature[&#39;v_1_add_v_12&#39;] &#x3D; df_feature[&#39;v_1&#39;] + df_feature[&#39;v_12&#39;]</span><br><span class="line">df_feature[&#39;v_2_add_v_3&#39;] &#x3D; df_feature[&#39;v_2&#39;] + df_feature[&#39;v_3&#39;]</span><br><span class="line">df_feature[&#39;v_4_add_v_11&#39;] &#x3D; df_feature[&#39;v_4&#39;] + df_feature[&#39;v_11&#39;]</span><br><span class="line">df_feature[&#39;v_4_add_v_12&#39;] &#x3D; df_feature[&#39;v_4&#39;] + df_feature[&#39;v_12&#39;]</span><br><span class="line">df_feature[&#39;v_0_add_v_12_add_v_14&#39;] &#x3D; df_feature[&#39;v_0&#39;] + \</span><br><span class="line">    df_feature[&#39;v_12&#39;] + df_feature[&#39;v_14&#39;]</span><br><span class="line"></span><br><span class="line">df_feature[&#39;v_4_add_v_9_minu_v_13&#39;] &#x3D; df_feature[&#39;v_4&#39;] + \</span><br><span class="line">    df_feature[&#39;v_9&#39;] - df_feature[&#39;v_13&#39;]</span><br><span class="line">df_feature[&#39;v_2_add_v_4_minu_v_11&#39;] &#x3D; df_feature[&#39;v_2&#39;] + \</span><br><span class="line">    df_feature[&#39;v_4&#39;] - df_feature[&#39;v_11&#39;]</span><br><span class="line">df_feature[&#39;v_2_add_v_3_minu_v_11&#39;] &#x3D; df_feature[&#39;v_2&#39;] + \</span><br><span class="line">    df_feature[&#39;v_3&#39;] - df_feature[&#39;v_11&#39;]</span><br></pre></td></tr></table></figure><h2 id="尝试过的无用特征"><a href="#尝试过的无用特征" class="headerlink" title="尝试过的无用特征"></a>尝试过的无用特征</h2><p>论坛分享了不少结合业务的特征，但我自己尝试后发现效果都不行，这也是让我很困惑的地方。官方赛题分享提到可以截取regionCode，提取城市信息，但 regionCode 已经被编码脱敏成0~8121的数字，已经无法进行信息提取。</p><h1 id="模型"><a href="#模型" class="headerlink" title="模型"></a>模型</h1><p>两个树模型：lgb 和 xgb 分别预测，然后根据得分进行简单的加权，按照 0.45*xgb_pred+0.55 *lgb_pred 得到最后的汽车预测价格。最后线上得分433，rank：62 / 2776。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;最终成绩：62 / 2776&lt;br&gt;比赛地址：&lt;a href=&quot;https://tianchi.aliyun.com/competition/entrance/231784/introduction&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https
      
    
    </summary>
    
    
      <category term="比赛总结" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
    
      <category term="机器学习" scheme="https://logicjake.github.io/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/tags/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
      <category term="价格预测" scheme="https://logicjake.github.io/tags/%E4%BB%B7%E6%A0%BC%E9%A2%84%E6%B5%8B/"/>
    
  </entry>
  
  <entry>
    <title>2020腾讯游戏安全技术竞赛机器学习组-TOP 4赛后总结</title>
    <link href="https://logicjake.github.io/2020/04/16/2020%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8%E6%8A%80%E6%9C%AF%E7%AB%9E%E8%B5%9B%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E7%BB%84-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/"/>
    <id>https://logicjake.github.io/2020/04/16/2020%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8%E6%8A%80%E6%9C%AF%E7%AB%9E%E8%B5%9B%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E7%BB%84-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/</id>
    <published>2020-04-16T18:38:56.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<p>最终成绩：优胜奖（TOP 4）<br>比赛地址：<a href="https://gslab.qq.com/html/competition/2020/index.htm" target="_blank" rel="noopener">https://gslab.qq.com/html/competition/2020/index.htm</a><br>源码：<a href="https://github.com/LogicJake/2020-gslab-ml-top4" target="_blank" rel="noopener">https://github.com/LogicJake/2020-gslab-ml-top4</a></p><h1 id="赛题背景"><a href="#赛题背景" class="headerlink" title="赛题背景"></a>赛题背景</h1><p>在游戏中，有一些人或组织，通过非法手段获取大量游戏内金币物品等资源，这就是打金工作室（Gold Farming）。他们一般拥有大量帐号，使用外挂或挂机软件批量进行游戏行为，破坏游戏经济系统，影响玩家游戏体验。可自行搜索打金工作室相关资料，了解他们是如何运作，游戏方如何打击，他们又是如何逃避打击的。</p><h2 id="竞赛目标"><a href="#竞赛目标" class="headerlink" title="竞赛目标"></a>竞赛目标</h2><p>使用 2020年03月01日的数据(含标签) 来训练识别打金工作室的模型。用此模型, 预测2020年03月05日的打金工作室。取当日login或logout中出现过的帐号，判断这些帐号中哪些是打金工作室。</p><h2 id="衡量标准"><a href="#衡量标准" class="headerlink" title="衡量标准"></a>衡量标准</h2><p>得分 = 4PR / (P+3R)</p><p>P为准确度, 即提交结果有多大比率是真正的工作室<br>R为覆盖率, 即提交结果覆盖了全体工作室的比率</p><a id="more"></a><h1 id="数据说明"><a href="#数据说明" class="headerlink" title="数据说明"></a>数据说明</h1><p>数据来自某MMORPG(大型多人在线角色扮演游戏), 并经过脱敏处理</p><ul><li>日期<ul><li>2020年03月01日的数据(含标签) 为训练集</li><li>2020年03月05日的数据为测试集</li></ul></li><li>基础知识<ul><li>uin: 唯一标识游戏内的一个用户, 比如你的qq或微信</li><li>roleid: 一个uin可能有多个角色</li></ul></li><li>存储格式<ul><li>文件名为: 年月日.txt</li><li>以文本存储</li><li>以竖线|分隔</li><li>空 或 \N 表示数据缺失</li></ul></li><li>目录说明<ul><li>label_black 黑标签: 打金工作室帐号</li><li>label_white 白标签: 非打金工作室帐号</li><li>role_login 角色登入游戏</li><li>role_logout 角色登出游戏</li><li>role_create 创建新角色</li><li>uin_chat 按天统计的帐号发言次数</li><li>以下数据仅在决赛时提供<ul><li>role_moneyflow 角色的详细金钱流水信息(当天按时间顺序前300条记录)</li><li>role_itemflow 角色的详细物品流水信息(当天按时间顺序前300条记录)</li></ul></li></ul></li></ul><h2 id="role-login-角色登入游戏"><a href="#role-login-角色登入游戏" class="headerlink" title="role_login 角色登入游戏"></a>role_login 角色登入游戏</h2><table><thead><tr><th align="center">#</th><th align="center">列名</th><th align="center">类型</th><th align="center">备注</th></tr></thead><tbody><tr><td align="center">1</td><td align="center">dteventtime</td><td align="center">STRING</td><td align="center">时间,格式YYYY-MM-DD HH:MM:SS</td></tr><tr><td align="center">2</td><td align="center">platid</td><td align="center">BIGINT</td><td align="center">ios=0/android=1</td></tr><tr><td align="center">3</td><td align="center">areaid</td><td align="center">BIGINT</td><td align="center">微信=1/手Q=2/游客=3</td></tr><tr><td align="center">4</td><td align="center">worldid</td><td align="center">BIGINT</td><td align="center">游戏小区(已加密)</td></tr><tr><td align="center">5</td><td align="center">uin</td><td align="center">STRING</td><td align="center">openid(已加密)</td></tr><tr><td align="center">6</td><td align="center">roleid</td><td align="center">STRING</td><td align="center">角色id(已加密)</td></tr><tr><td align="center">7</td><td align="center">rolename</td><td align="center">STRING</td><td align="center">角色名(已置空)</td></tr><tr><td align="center">8</td><td align="center">job</td><td align="center">STRING</td><td align="center">职业</td></tr><tr><td align="center">9</td><td align="center">rolelevel</td><td align="center">BIGINT</td><td align="center">等级</td></tr><tr><td align="center">10</td><td align="center">power</td><td align="center">BIGINT</td><td align="center">战力</td></tr><tr><td align="center">11</td><td align="center">friendsnum</td><td align="center">BIGINT</td><td align="center">好友数量</td></tr><tr><td align="center">12</td><td align="center">network</td><td align="center">STRING</td><td align="center">3G/WIFI/2G/NULL</td></tr><tr><td align="center">13</td><td align="center">clientip</td><td align="center">STRING</td><td align="center">客户端IP(已加密)</td></tr><tr><td align="center">14</td><td align="center">deviceid</td><td align="center">STRING</td><td align="center">设备ID(已加密)</td></tr></tbody></table><h2 id="role-logout-角色登出游戏"><a href="#role-logout-角色登出游戏" class="headerlink" title="role_logout 角色登出游戏"></a>role_logout 角色登出游戏</h2><table><thead><tr><th align="center">#</th><th align="center">列名</th><th align="center">类型</th><th align="center">备注</th></tr></thead><tbody><tr><td align="center">1</td><td align="center">dteventtime</td><td align="center">STRING</td><td align="center">时间,格式YYYY-MM-DD HH:MM:SS</td></tr><tr><td align="center">2</td><td align="center">platid</td><td align="center">BIGINT</td><td align="center">ios=0/android=1</td></tr><tr><td align="center">3</td><td align="center">areaid</td><td align="center">BIGINT</td><td align="center">微信=1/手Q=2/游客=3</td></tr><tr><td align="center">4</td><td align="center">worldid</td><td align="center">BIGINT</td><td align="center">游戏小区(已加密)</td></tr><tr><td align="center">5</td><td align="center">uin</td><td align="center">STRING</td><td align="center">openid(已加密)</td></tr><tr><td align="center">6</td><td align="center">roleid</td><td align="center">STRING</td><td align="center">角色id(已加密)</td></tr><tr><td align="center">7</td><td align="center">rolename</td><td align="center">STRING</td><td align="center">角色名(已置空)</td></tr><tr><td align="center">8</td><td align="center">job</td><td align="center">STRING</td><td align="center">职业</td></tr><tr><td align="center">9</td><td align="center">rolelevel</td><td align="center">BIGINT</td><td align="center">等级</td></tr><tr><td align="center">10</td><td align="center">power</td><td align="center">BIGINT</td><td align="center">战力</td></tr><tr><td align="center">11</td><td align="center">friendsnum</td><td align="center">BIGINT</td><td align="center">好友数量</td></tr><tr><td align="center">12</td><td align="center">network</td><td align="center">STRING</td><td align="center">3G/WIFI/2G/NULL</td></tr><tr><td align="center">13</td><td align="center">clientip</td><td align="center">STRING</td><td align="center">客户端IP(已加密)</td></tr><tr><td align="center">14</td><td align="center">deviceid</td><td align="center">STRING</td><td align="center">设备ID(已加密)</td></tr><tr><td align="center">15</td><td align="center">onlinetime</td><td align="center">BIGINT</td><td align="center">在线时长(秒)</td></tr></tbody></table><h2 id="role-create-创建新角色"><a href="#role-create-创建新角色" class="headerlink" title="role_create 创建新角色"></a>role_create 创建新角色</h2><table><thead><tr><th align="center">#</th><th align="center">列名</th><th align="center">类型</th><th align="center">备注</th></tr></thead><tbody><tr><td align="center">1</td><td align="center">dteventtime</td><td align="center">STRING</td><td align="center">YYYY-MM-DD HH#MM#SS</td></tr><tr><td align="center">2</td><td align="center">platid</td><td align="center">BIGINT</td><td align="center">ios=0/android=1</td></tr><tr><td align="center">3</td><td align="center">areaid</td><td align="center">BIGINT</td><td align="center">微信=1/手Q=2/游客=3</td></tr><tr><td align="center">4</td><td align="center">worldid</td><td align="center">BIGINT</td><td align="center">游戏小区(已加密)</td></tr><tr><td align="center">5</td><td align="center">uin</td><td align="center">STRING</td><td align="center">openid(已加密)</td></tr><tr><td align="center">6</td><td align="center">roleid</td><td align="center">STRING</td><td align="center">角色id(已加密)</td></tr><tr><td align="center">7</td><td align="center">rolename</td><td align="center">STRING</td><td align="center">角色名(已置空)</td></tr><tr><td align="center">8</td><td align="center">job</td><td align="center">STRING</td><td align="center">职业</td></tr><tr><td align="center">9</td><td align="center">regchannel</td><td align="center">STRING</td><td align="center">注册渠道</td></tr><tr><td align="center">10</td><td align="center">network</td><td align="center">STRING</td><td align="center">3G/WIFI/2G</td></tr><tr><td align="center">11</td><td align="center">clientip</td><td align="center">STRING</td><td align="center">客户端IP(已加密)</td></tr><tr><td align="center">12</td><td align="center">deviceid</td><td align="center">STRING</td><td align="center">设备ID(已加密)</td></tr></tbody></table><h2 id="uin-chat-按天统计的帐号发言次数"><a href="#uin-chat-按天统计的帐号发言次数" class="headerlink" title="uin_chat 按天统计的帐号发言次数"></a>uin_chat 按天统计的帐号发言次数</h2><table><thead><tr><th align="center">#</th><th align="center">列名</th><th align="center">类型</th><th align="center">备注</th></tr></thead><tbody><tr><td align="center">1</td><td align="center">uin</td><td align="center">STRING</td><td align="center">openid(已加密)</td></tr><tr><td align="center">2</td><td align="center">chat_cnt</td><td align="center">BIGINT</td><td align="center">发言条数</td></tr></tbody></table><h2 id="role-moneyflow-帐号的详细金钱流水信息"><a href="#role-moneyflow-帐号的详细金钱流水信息" class="headerlink" title="role_moneyflow 帐号的详细金钱流水信息"></a>role_moneyflow 帐号的详细金钱流水信息</h2><table><thead><tr><th align="center">#</th><th align="center">列名</th><th align="center">类型</th><th align="center">备注</th></tr></thead><tbody><tr><td align="center">1</td><td align="center">dteventtime</td><td align="center">STRING</td><td align="center">时间,格式YYYY-MM-DD HH:MM:SS</td></tr><tr><td align="center">2</td><td align="center">worldid</td><td align="center">BIGINT</td><td align="center">游戏小区(已加密)</td></tr><tr><td align="center">3</td><td align="center">uin</td><td align="center">STRING</td><td align="center">openid(已加密)</td></tr><tr><td align="center">4</td><td align="center">roleid</td><td align="center">STRING</td><td align="center">角色id(已加密)</td></tr><tr><td align="center">5</td><td align="center">rolelevel</td><td align="center">BIGINT</td><td align="center">等级</td></tr><tr><td align="center">6</td><td align="center">iMoneyType</td><td align="center">STRING</td><td align="center">货币类型</td></tr><tr><td align="center">7</td><td align="center">iMoney</td><td align="center">BIGINT</td><td align="center">货币变化数</td></tr><tr><td align="center">8</td><td align="center">AfterMoney</td><td align="center">BIGINT</td><td align="center">动作后的货币存量</td></tr><tr><td align="center">9</td><td align="center">AddOrReduce</td><td align="center">BIGINT</td><td align="center">货币增加 0/减少 1</td></tr><tr><td align="center">10</td><td align="center">Reason</td><td align="center">STRING</td><td align="center">货币流动一级原因</td></tr><tr><td align="center">11</td><td align="center">SubReason</td><td align="center">STRING</td><td align="center">货币流动二级原因</td></tr></tbody></table><h2 id="role-itemflow-帐号的详细物品流水信息"><a href="#role-itemflow-帐号的详细物品流水信息" class="headerlink" title="role_itemflow 帐号的详细物品流水信息"></a>role_itemflow 帐号的详细物品流水信息</h2><table><thead><tr><th align="center">#</th><th align="center">列名</th><th align="center">类型</th><th align="center">备注</th></tr></thead><tbody><tr><td align="center">1</td><td align="center">dteventtime</td><td align="center">STRING</td><td align="center">时间,格式YYYY-MM-DD HH:MM:SS</td></tr><tr><td align="center">2</td><td align="center">worldid</td><td align="center">BIGINT</td><td align="center">游戏小区(已加密)</td></tr><tr><td align="center">3</td><td align="center">uin</td><td align="center">STRING</td><td align="center">openid(已加密)</td></tr><tr><td align="center">4</td><td align="center">roleid</td><td align="center">STRING</td><td align="center">角色id(已加密)</td></tr><tr><td align="center">5</td><td align="center">rolelevel</td><td align="center">BIGINT</td><td align="center">等级</td></tr><tr><td align="center">6</td><td align="center">Itemtype</td><td align="center">STRING</td><td align="center">道具类型</td></tr><tr><td align="center">7</td><td align="center">Itemid</td><td align="center">STRING</td><td align="center">道具ID</td></tr><tr><td align="center">8</td><td align="center">Count</td><td align="center">BIGINT</td><td align="center">道具变动数量</td></tr><tr><td align="center">9</td><td align="center">Aftercount</td><td align="center">BIGINT</td><td align="center">动作后道具剩余数量</td></tr><tr><td align="center">10</td><td align="center">Addorreduce</td><td align="center">BIGINT</td><td align="center">增加 0/减少 1</td></tr><tr><td align="center">11</td><td align="center">Reason</td><td align="center">STRING</td><td align="center">道具流动一级原因</td></tr><tr><td align="center">12</td><td align="center">SubReason</td><td align="center">STRING</td><td align="center">道具流动二级原因</td></tr></tbody></table><h1 id="简单-eda"><a href="#简单-eda" class="headerlink" title="简单 eda"></a>简单 eda</h1><p>训练集总账户数为74704，其中打金工作室账户10202，正常用户64502，正负样本分布不均匀。登入数据的离散数据统计信息如图1所示。</p><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/tencent2020_1.png" alt="图1 登入数据的离散数据统计" title="">                </div>                <div class="image-caption">图1 登入数据的离散数据统计</div>            </figure></div><p>登出数据字段和登入数据差不多，仅多了在线时长（onlinetime）特征，所以在后续的处理中，将二者合并成 operation 表放在一起使用，使得代码更简洁。rolename 全为空没有使用价值。从业务上讲，deviceid 是一个很重要的特征，根据设备id我们可以判断多账号共享设备的情况。但在数据集中，每天仅有一个deviceid，3.1号的 deviceid 全为6259A4950D8B0CA5，3.5号的 deviceid 全为71A1315F1949F262，失去了使用价值。</p><p>数据集给出了按天统计的帐号发言次数，整体分布如图2所示，具有很明显的长尾分布。分是否是工作室观察发言次数分布，如图3和图4所示，可以明显看出工作室的发言次数相较于正常用户较多，推测是在游戏中进行叫卖或者交易聊天。</p><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/tencent2020_2.png" alt="图2 整体帐号发言次数" title="">                </div>                <div class="image-caption">图2 整体帐号发言次数</div>            </figure></div><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/tencent2020_3.png" alt="图3 工作室帐号发言次数" title="">                </div>                <div class="image-caption">图3 工作室帐号发言次数</div>            </figure></div><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/tencent2020_4.png" alt="图4 正常用户帐号发言次数" title="">                </div>                <div class="image-caption">图4 正常用户帐号发言次数</div>            </figure></div><p>用户创建角色数据，主要需要关注regchannel（注册渠道），总共有76种不同的注册渠道，主要渠道数量分布如图5所示。</p><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/tencent2020_5.png" alt="图5 主要注册渠道数量分布" title="">                </div>                <div class="image-caption">图5 主要注册渠道数量分布</div>            </figure></div><h1 id="特征工程"><a href="#特征工程" class="headerlink" title="特征工程"></a>特征工程</h1><p>数据集给出了操作时间，可以提取出小时信息进行进一步统计。ip 特征能够很好的反映是否存在同一 ip 下的多账户行为。ip 特征比较细化，根据基本的网络知识，我们可以将其分段，得到隐藏的地域信息。所以在本方案中，构造了clientip_3 和 clientip_2，分别取 clientip 的前三段和前两段。logout 表给出了在线时长特征，类似的，我们可以将 login 表按时间排序，登入时间减去上一次的登出时间可以得到离线时长特征。</p><h2 id="统计特征"><a href="#统计特征" class="headerlink" title="统计特征"></a>统计特征</h2><p>统计特征包括两方面，以uin为单位，分别采用不同的方法对数值特征和类别特征进行统计。对数值特征采取下列统计方法：</p><ul><li>在线时长：sum，median，mean;</li><li>离线时长：mean，max</li><li>角色等级：mean，max;</li><li>战力：mean，max；</li><li>好友数量：mean，max；</li><li>用户操作时间（小时）：mean，min，max。</li></ul><p>对下列类别特征进行count统计：platid, worldid, roleid, job, network, clientip, clientip_3, hour, regchannel。</p><h2 id="ip-特征"><a href="#ip-特征" class="headerlink" title="ip 特征"></a>ip 特征</h2><p>查资料可知，工作室往往会购买大量机器，利用自动化脚本进行打金，所以会出现多个账号共享 ip 的情况。我们计算每个 ip 下登录过多少个账户，再对某账户使用过的所有 ip 计算其登录过账户数的平均值，该值越大，说明该账户所处网段有大量账户登录，越有可能是工作室。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">for f in [&#39;clientip&#39;, &#39;clientip_3&#39;]:</span><br><span class="line">    df_temp &#x3D; operation[[&#39;uin&#39;, f]]</span><br><span class="line">    df_temp.drop_duplicates(inplace&#x3D;True)</span><br><span class="line">    df_temp &#x3D; df_temp.groupby([f])[&#39;uin&#39;].nunique().reset_index()</span><br><span class="line">    df_temp.columns &#x3D; [f, &#39;uin_count&#39;]</span><br><span class="line"></span><br><span class="line">    df_temp2 &#x3D; operation[[&#39;uin&#39;, f]]</span><br><span class="line">    df_temp2.drop_duplicates(inplace&#x3D;True)</span><br><span class="line">    df_temp &#x3D; df_temp2.merge(df_temp, how&#x3D;&#39;left&#39;)</span><br><span class="line"></span><br><span class="line">    df_temp &#x3D; df_temp.groupby([&#39;uin&#39;])[&#39;uin_count&#39;].agg(&#123;</span><br><span class="line">        &#39;&#123;&#125;_uin_count_mean&#39;.format(f):</span><br><span class="line">        &#39;mean&#39;</span><br><span class="line">    &#125;).reset_index()</span><br><span class="line">    df_feature &#x3D; df_feature.merge(df_temp, how&#x3D;&#39;left&#39;)</span><br><span class="line">    del df_temp, df_temp2</span><br><span class="line">    gc.collect()</span><br></pre></td></tr></table></figure><h2 id="标签五折交叉统计特征"><a href="#标签五折交叉统计特征" class="headerlink" title="标签五折交叉统计特征"></a>标签五折交叉统计特征</h2><p>在推荐任务中，经常使用点击率特征，即统计某个类别特征下点击商品的概率。同样作为二分类任务，我们也可以构造某个类别特征下是工作室的概率。该类特征利用了标签信息，容易出现标签泄露问题，所以在实际操作中，将训练集分为5份，每次使用4份做标签统计，得到的概率值给另外1份做特征。本方案构造了 chat_cnt 和 clientip_3 类别下的工作室概率。</p><h2 id="embedding-特征"><a href="#embedding-特征" class="headerlink" title="embedding 特征"></a>embedding 特征</h2><p>ip 字段能帮助我们很好的判断是否存在账户聚集行为，假如我们能够将无法定量计算的 ip 地址转成模型能够识别的数字特征，将会帮助模型更好地利用 ip 信息。我们整理出每个账户在哪些 ip 操作过，将这些 ip 列表作为 sentence 输入到 word2vec 模型，将 ip 映射为向量，经常出现在上下文中的 ip 在 embedding 空间中也相近。最后将账户下所有 ip 的向量取平均作为新特征。效仿对 ip 特征的处理，我们还对 worldid 和 job 做了类似的操作。</p><h2 id="流水特征"><a href="#流水特征" class="headerlink" title="流水特征"></a>流水特征</h2><p>决赛给出了角色的详细货币和物品流水信息(当天按时间顺序前300条记录)。结合业务考虑，工作室往往会进行大量的交易行为，实现物品或货币套利。所以对货币变化数，道具变动数量进行统计，包括求和，求平均值，求最大值和计数。货币变化可以详细到增加或减少，可以进一步在这两种情况下进行统计。此外数据集还给出了交易的理由，我们还可以统计每个理由下进行交易的货币数和物品数。货币类型，货币流动一级原因，道具流动一级原因，尤其是道具ID，这几个类别特征维度较大，分别进行统计会引入大量特征，而且数据普遍存在稀疏问题。以道具ID为例，ID 为342的物品只交易过一次，所以“物品342的交易数量”特征就会十分稀疏，只有一个账户下该特征有统计值。</p><p>最终，本方案总共使用704个特征，重要性比较高的特征如图6所示，可以看出账户的在线时长，离线时长特征很重要，因为工作室相较于正常用户会花费更多的时间进行游戏，追求利益的最大化。时段（hour）特征也比较重要，工作室不分白天黑夜进行游戏，而正常的用户不会在白天花费大量的时间进行游戏。加入流水特征后，Reason12下的货币交易尤为重要。</p><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/tencent2020_6.png" alt="图6 特征重要性" title="">                </div>                <div class="image-caption">图6 特征重要性</div>            </figure></div><h1 id="伪标签"><a href="#伪标签" class="headerlink" title="伪标签"></a>伪标签</h1><p>在训练模型时，我们总希望输入的数据数据量尽可能大，数据标签尽可能分布均匀。在对测试集进行预测时，LightGBM 模型输出的是账户是打金工作室的概率，也可以理解为置信度，从图7可以看出，概率分布大多分布在两头，极有可能是和极有可能不是。我们完全可以将置信度较高的测试集数据作为标签为1的新训练集，从而达到扩增训练集且稍微平衡样本标签的目的。由于本次比赛没有榜单，所以这部分选择比较谨慎，只挑出概率值大于0.99的测试集数据添加到训练集，线下大概能提升0.006。</p><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/tencent2020_7.png" alt="图7 概率值分布" title="">                </div>                <div class="image-caption">图7 概率值分布</div>            </figure></div><h1 id="模型"><a href="#模型" class="headerlink" title="模型"></a>模型</h1><p>利用 LightGBM 模型进行二分类任务，工作室账号标签设置为1，正常账号设置为0。采用五折交叉法，将3.1号的数据分为五份，每次使用4份训练，另外一份用来验证。由于样本标签分布不均衡，我们不能用0.5作为正负样本的分割点，而应该采用比例法。正样本大概占总样本的13.66%，所以将测试集的预测概率按从大到小排序，前13.66%挑选为工作室账号。在本地验证集上，精确率达到0.9700214132762313，召回率达到0.9324642227014311，比赛指标达到0.9603513111071851。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>由于是高校赛，主办方大概考虑到学生党的机器性能限制，所以数据集并不是很大，比赛时间也比较紧凑，初赛12个小时，决赛三天，整体体验比较好。赛后阅读第一大佬的方案，特征工程思路基本相同，但大佬在流水统计方面做的更细致，并没有像我直接一股脑的对所有分类进行统计。此外还利用对抗训练进行特征筛选，删去在训练集和测试集上分布差异较大的特征。</p><blockquote><p>通过role_itemflow，构造了主要物品类型（类型20025, 20035, 20036, 20028）的流水次数和物品变动数量，主要流水原因（原因84, 12, 85, 5）的流水次数。</p></blockquote><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ul><li><a href="https://gslab.qq.com/article-699-1.html" target="_blank" rel="noopener">赛道第一名方案分享</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最终成绩：优胜奖（TOP 4）&lt;br&gt;比赛地址：&lt;a href=&quot;https://gslab.qq.com/html/competition/2020/index.htm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gslab.qq.com/html/competition/2020/index.htm&lt;/a&gt;&lt;br&gt;源码：&lt;a href=&quot;https://github.com/LogicJake/2020-gslab-ml-top4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/LogicJake/2020-gslab-ml-top4&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;赛题背景&quot;&gt;&lt;a href=&quot;#赛题背景&quot; class=&quot;headerlink&quot; title=&quot;赛题背景&quot;&gt;&lt;/a&gt;赛题背景&lt;/h1&gt;&lt;p&gt;在游戏中，有一些人或组织，通过非法手段获取大量游戏内金币物品等资源，这就是打金工作室（Gold Farming）。他们一般拥有大量帐号，使用外挂或挂机软件批量进行游戏行为，破坏游戏经济系统，影响玩家游戏体验。可自行搜索打金工作室相关资料，了解他们是如何运作，游戏方如何打击，他们又是如何逃避打击的。&lt;/p&gt;
&lt;h2 id=&quot;竞赛目标&quot;&gt;&lt;a href=&quot;#竞赛目标&quot; class=&quot;headerlink&quot; title=&quot;竞赛目标&quot;&gt;&lt;/a&gt;竞赛目标&lt;/h2&gt;&lt;p&gt;使用 2020年03月01日的数据(含标签) 来训练识别打金工作室的模型。用此模型, 预测2020年03月05日的打金工作室。取当日login或logout中出现过的帐号，判断这些帐号中哪些是打金工作室。&lt;/p&gt;
&lt;h2 id=&quot;衡量标准&quot;&gt;&lt;a href=&quot;#衡量标准&quot; class=&quot;headerlink&quot; title=&quot;衡量标准&quot;&gt;&lt;/a&gt;衡量标准&lt;/h2&gt;&lt;p&gt;得分 = 4PR / (P+3R)&lt;/p&gt;
&lt;p&gt;P为准确度, 即提交结果有多大比率是真正的工作室&lt;br&gt;R为覆盖率, 即提交结果覆盖了全体工作室的比率&lt;/p&gt;
    
    </summary>
    
    
      <category term="比赛总结" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
    
      <category term="机器学习" scheme="https://logicjake.github.io/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/tags/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
      <category term="风控" scheme="https://logicjake.github.io/tags/%E9%A3%8E%E6%8E%A7/"/>
    
  </entry>
  
  <entry>
    <title>图灵联邦视频点击预测大赛-第三赛后总结</title>
    <link href="https://logicjake.github.io/2020/02/10/%E5%9B%BE%E7%81%B5%E8%81%94%E9%82%A6%E8%A7%86%E9%A2%91%E7%82%B9%E5%87%BB%E9%A2%84%E6%B5%8B%E5%A4%A7%E8%B5%9B-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/"/>
    <id>https://logicjake.github.io/2020/02/10/%E5%9B%BE%E7%81%B5%E8%81%94%E9%82%A6%E8%A7%86%E9%A2%91%E7%82%B9%E5%87%BB%E9%A2%84%E6%B5%8B%E5%A4%A7%E8%B5%9B-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/</id>
    <published>2020-02-10T14:38:57.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<p>最终成绩：A, B榜第三。<br>比赛地址：<a href="https://www.turingtopia.com/competitionnew/detail/e4880352b6ef4f9f8f28e8f98498dbc4/sketch" target="_blank" rel="noopener">https://www.turingtopia.com/competitionnew/detail/e4880352b6ef4f9f8f28e8f98498dbc4/sketch</a><br>代码地址：<a href="https://github.com/LogicJake/tuling-video-click-top3" target="_blank" rel="noopener">https://github.com/LogicJake/tuling-video-click-top3</a></p><h1 id="赛题背景"><a href="#赛题背景" class="headerlink" title="赛题背景"></a>赛题背景</h1><p>移动互联网的快速发展，催生了海量视频数据的产生，也为用户提供了类型丰富的视频数据类型。面对如何从海量视频数据类型中选择用户喜欢的类型的这一难题，作为一家拥有海量视频素材和用户行为的数据公司，希望通过用户行为数据，用户特征，以及视频特征，可以在充足数据基础上精准的推荐给用户喜欢的视频类型。</p><p>本次竞赛的目的是以用户的视频行为数据为基础，构建推荐模型，参赛队伍则需要搭建个性化推荐模型。希望参赛队伍能够挖掘数据背后丰富的内涵，为移动用户在合适的时间、合适的地点精准推荐用户感兴趣的内容，提高用户在数据集上的点击行为。</p><p>参赛者通过构建推荐模型，预测待测试数据中用户在对应的视频上是否会产生点击行为。</p><h1 id="数据说明"><a href="#数据说明" class="headerlink" title="数据说明"></a>数据说明</h1><p>第一部分是用户在资讯全集上的移动端行为数据（D）。</p><p>训练数据包含了抽样出来的一定量用户在三天之内的移动端行为数据（D），评分数据是这些用户在之后一天对子集（P）的点击数据。参赛者要使用训练数据建立推荐模型，并输出用户在接下来一天点击行为的预测结果。</p><h1 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h1><h2 id="数据探索"><a href="#数据探索" class="headerlink" title="数据探索"></a>数据探索</h2><p>通过数据来源，我们能够更清楚地了解数据，了解业务背景。比赛数据采集于一款叫“亿刻看点”的 APP，此 APP 安卓独占，因为其某些流氓操作在苹果那边应该过不了审。这款 APP 以阅读赚钱（看应用市场评论提现很困难）为吸引点吸引用户使用，但有些迷之操作，在使用过程中会出现广告和下载弹窗，甚至退出这款 APP 后也能给我弹（如图1左）。APP 推荐的视频大多是影视剪辑，没有任何时效性，推荐策略也很单一：推荐与上次点击相似的视频。从图1右可以看到，当我点击了“那年花开”的视频片段后，刷新之后再推荐的就是该影视剧相关。</p><center class="half">    <img src="http://pic.logicjake.xyz/app%E5%B1%95%E7%A4%BA.jpg" style="margin: auto;max-width: 30%; margin-right:10%;"/><img src="http://pic.logicjake.xyz/app%E5%B1%95%E7%A4%BA%E8%A7%86%E9%A2%91.gif" style="margin: auto;max-width: 30%;"/>    <div style="margin-top: 2%">图1 APP 使用展示</div></center><p>从一个用户的角度，这款 APP 的用户体验不佳，首先广告弹出，误触下载让我无法忍受，所以不可能持续使用。即使有些人为了赚钱使用，也有极大几率为了奖励去点击视频，这样会产生大量不可信的操作数据。当一个人的行为数据不可信，对其未来的点击预估也就无从谈起了。在比赛中也有类似的体会，许多 ctr 预估中使用的常规特征收益很低甚至无效。</p><p>回归到数据本身，训练集总共给出了从2019.11.08 ~ 2019.11.10的三天数据，需要预测2019.11.11的用户点击行为。首先观察一下新老用户分布，下表列出了9，10，11三天昨日老用户占比。可以看出昨日老用户占比还是挺高，所以在后面的特征工程做了大量针对用户昨日行为的特征。按道理说这种体验感不好的 APP，在用户手机上存活时间不会太长，应该不会存在这么高的老用户比例，也不知道是奖励起到了作用还是数据提供方特意筛选出一部分活跃用户。</p><table><thead><tr><th align="center">day</th><th align="center">昨日老用户比例</th></tr></thead><tbody><tr><td align="center">9</td><td align="center">0.6501773380623483</td></tr><tr><td align="center">10</td><td align="center">0.6388528497721992</td></tr><tr><td align="center">11</td><td align="center">0.7410879161463921</td></tr></tbody></table><h2 id="特征工程"><a href="#特征工程" class="headerlink" title="特征工程"></a>特征工程</h2><p>这一题数据量比较大，模型训练起来也比较慢，所以针对 lgb 模型准备了两套参数，一套学习率比较大用于快速迭代验证特征效果，训练一次大概在半小时左右，线上分数0.825。另外一套学习率为0.01的参数用于正式提交，运行一次大概需要15小时，线上分数0.8377。</p><h3 id="穿越特征"><a href="#穿越特征" class="headerlink" title="穿越特征"></a>穿越特征</h3><p>前面说过，这题常规特征收益很小，但由于数据给出了视频曝光时间（ts），所以可以借助其构造大量穿越特征，或者称作为视频点击后的模式特征。基本构造方法就是计算距离下一次视频曝光的时间差。这么做的原因也很好理解，假如一个人点击了某个视频，那么必然会观看一段时间，那么距离下一次视频的曝光就会久一点，ts 差值也较大。相反，连续两次视频的曝光时间间隔应该很小。距离上次视频的曝光时间差也是有效的，根据 APP 的推荐规则，在点击视频后下次推荐的也是相关视频，从而再次点击的可能性较大。我们构造了大量组合下的视频曝光时间间隔，曝光跨度也从1往后扩展，主要构造如下特征：</p><ul><li>deviceid 前x次曝光到当前的时间差</li><li>deviceid netmodel 前x次曝光到当前的时间差</li><li>deviceid 后x次曝光到当前的时间差</li><li>deviceid pos 后x次曝光到当前的时间差</li><li>deviceid netmodel 后x次曝光到当前的时间差</li><li>deviceid netmodel pos 后x次曝光到当前的时间差</li><li>deviceid lng_lat 后x次曝光到当前的时间差</li><li>deviceid lng_lat pos 后x次曝光到当前的时间差</li></ul><p>除 ts 是个强特征之外，pos 也是强特。pos 取值范围为0 ～ 8，取值分布如图2左所示。抓包分析可知0 ～ 3这四个值对应首页推荐四个位置，但从图2右可以看到界面大小最多只够显示2个半，有曝光才有流量，所以才会导致 pos 取0，1，2的数据量相较于3较大。pos中的其他取值并没有找到在何处产生，猜想可能来自于相关推荐视频点击或者消息栏推送点击。进一步分析在不同 pos 下的点击率，如图3所示。可以看出同样是首页推荐位置，位置3虽然总数据不多，但点击率远高于其他首页位置。初步猜想大部分用户可能只打开看了一下，只看到首页屏幕大小展示的三个视频，并不会下滑查看更多推荐，所以才会出现0 ～ 2的数据量大，但点击率非常低的情况。对于深度使用用户，其要么出于完成任务拿奖励的目的还是正常使用，点击欲望相对更高，这部分用户在使用探索中会更多地看到 pos 3以上的视频。</p><center class="half">    <img src="http://pic.logicjake.xyz/pos%E5%88%86%E5%B8%83.png" style="margin: auto;max-width: 30%; margin-right:10%;"/><img src="http://pic.logicjake.xyz/%E8%A7%86%E9%A2%91%E4%BD%8D%E7%BD%AE.jpg" style="margin: auto;max-width: 30%;"/>    <div style="margin: 2%">图2 左：pos 取值分布；右：app 视频展示位</div></center><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/pos%20%E7%82%B9%E5%87%BB%E7%8E%87.png" alt="图3 各pos是否点击分布" title="">                </div>                <div class="image-caption">图3 各pos是否点击分布</div>            </figure></div><p>效仿 ts 的穿越特征构造，我们又尝试构造了后x次视频曝光位置，同样效果也十分显著。仔细研究 APP 发现，假如你不是直接在首页点击播放视频，而是点击进入该视频的详情页，同样会触发视频观看，同时在视频详情页下会出现相关推荐视频，如图4红框所示。综上猜测，pos 中某些取值对应于相关推荐位，所以当下次视频曝光位置为上述相关推荐位，则表示当次视频一定是被用户点击观看的。</p><div style="margin: auto;max-width: 30%; "><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90.jpg" alt="图4 相关推荐" title="">                </div>                <div class="image-caption">图4 相关推荐</div>            </figure></div><p>通过学习上述两大类穿越特征，模型的效果已然十分明显，在快速迭代模型上，已经能够达到0.80114的分数。相较于最高分0.825，说明常规特征带来的收益只能提高0.024。穿越特征只有30+，但常规特征却有140左右，收益对比一目了然。工程实践中穿越特征自然无法使用，但作为一项数据挖掘比赛，当然是追求已有数据的最大化利用。</p><p>除了上述两大类穿越特征，还构造了下一次视频曝光的网络环境和基于经纬度的位置变化。</p><h3 id="历史特征"><a href="#历史特征" class="headerlink" title="历史特征"></a>历史特征</h3><p>历史特征主要用过去一个时间单位的数据进行统计，然后作为当前时刻的特征。由于数据中昨日老用户占比较多，所以本方案大量构造了昨日数据统计特征。在这部分特征中大量涉及到各种点击率的构造，点击率使用到了标签数据，因此相较于全局统计点击率，构造昨日点击率避免了标签泄露和数据穿越问题。主要构造的点击统计特征如下：</p><ul><li>deviceid 点击次数，点击率</li><li>deviceid, hour 点击率</li><li>deviceid, netmodel 点击率</li><li>newsid 点击次数，点击率</li><li>next_pos 点击率</li></ul><p>原数据给出了两个时间特征，一个是视频曝光时间 ts 和视频假如被点击时的点击时间 timestamp。二者的差值可以表示用户的反应时间，反应时间越短说明用户越喜欢该类视频。针对反应时间分别以 deviceid 和 newsid 为单位构造统计特征，构造方式包括：max，min，mean，std，median，kurt 和 quantile。反应时间从用户侧提取，所以针对 newsid 做统计误差较大，效果不明显，所以只保留了 std 统计。</p><p>除了以天为单位构造昨日数据特征，还以小时为单位构造上一小时的统计特征，尝试下来只有上一小时的 deviceid 点击次数和点击率有点效果。</p><h3 id="count-统计"><a href="#count-统计" class="headerlink" title="count 统计"></a>count 统计</h3><p>这部分特征以不同时间单位进行 count 统计，时间单位包括：10分钟，小时，天，全局。不同于历史特征，这部分 count 统计往往会出现数据穿越问题。count 特征主要反应偏好属性，比如今天哪个视频曝光量大，用户倾向于在哪个时间，哪个网络环境下使用 APP。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">cat_list = [[<span class="string">'deviceid'</span>], [<span class="string">'guid'</span>], [<span class="string">'newsid'</span>], [<span class="string">'deviceid'</span>, <span class="string">'pos'</span>], [<span class="string">'newsid'</span>, <span class="string">'pos'</span>], </span><br><span class="line">            [<span class="string">'deviceid'</span>, <span class="string">'guid'</span>, <span class="string">'newsid'</span>], [<span class="string">'deviceid'</span>, <span class="string">'next_pos'</span>]]</span><br><span class="line"><span class="keyword">for</span> f <span class="keyword">in</span> tqdm(cat_list):</span><br><span class="line">    df_feature[<span class="string">'&#123;&#125;_day_count'</span>.format(<span class="string">'_'</span>.join(f))] = df_feature.groupby([<span class="string">'day'</span>] + f)[<span class="string">'id'</span>].transform(<span class="string">'count'</span>)</span><br><span class="line">    </span><br><span class="line">cat_list = [[<span class="string">'deviceid'</span>], [<span class="string">'guid'</span>], [<span class="string">'deviceid'</span>, <span class="string">'pos'</span>], [<span class="string">'deviceid'</span>, <span class="string">'netmodel'</span>]]</span><br><span class="line"><span class="keyword">for</span> f <span class="keyword">in</span> tqdm(cat_list):</span><br><span class="line">    df_feature[<span class="string">'&#123;&#125;_minute10_count'</span>.format(<span class="string">'_'</span>.join(f))] = df_feature.groupby([<span class="string">'day'</span>, <span class="string">'hour'</span>, <span class="string">'minute10'</span>] + f)[<span class="string">'id'</span>].transform(<span class="string">'count'</span>)</span><br><span class="line">    </span><br><span class="line">cat_list = [[<span class="string">'deviceid'</span>, <span class="string">'netmodel'</span>]]</span><br><span class="line"><span class="keyword">for</span> f <span class="keyword">in</span> tqdm(cat_list):</span><br><span class="line">    df_feature[<span class="string">'&#123;&#125;_hour_count'</span>.format(<span class="string">'_'</span>.join(f))] = df_feature.groupby([<span class="string">'hourl'</span>] + f)[<span class="string">'id'</span>].transform(<span class="string">'count'</span>)</span><br><span class="line">    </span><br><span class="line">cat_list = [[<span class="string">'deviceid'</span>, <span class="string">'group'</span>, <span class="string">'pos'</span>]]</span><br><span class="line"><span class="keyword">for</span> f <span class="keyword">in</span> tqdm(cat_list):</span><br><span class="line">    df_feature[<span class="string">'&#123;&#125;_count'</span>.format(<span class="string">'_'</span>.join(f))] = df_feature.groupby(f)[<span class="string">'id'</span>].transform(<span class="string">'count'</span>)</span><br></pre></td></tr></table></figure><h3 id="embedding-特征"><a href="#embedding-特征" class="headerlink" title="embedding 特征"></a>embedding 特征</h3><p>作为推荐中重要的一方，数据集给出的视频信息十分少，只有一列简单的视频 id，并没有视频分类之类的对推荐重要的属性信息，所以我们只能从视频被推荐给哪些用户下手。以视频为单位，找出被推荐的用户列表，每个列表作为句子喂到 Word2Vec 模型得到每个用户的 embedding 向量，用视频所有被推荐的用户的 embedding 向量平均值表示视频。得到 embedding 之后，就可以度量两个视频之间的相似度，所以也就产生了另外一种思路，当一个新视频被推荐给用户后，计算新视频与之前用户被推荐过（或者看过）的视频的平均相似度，平均相似度越大，用户点击的可能性越大。但在实际试验中效果不好，也不知道是不是 embedding 效果达不到的原因。</p><h3 id="user-表的打开方式"><a href="#user-表的打开方式" class="headerlink" title="user 表的打开方式"></a>user 表的打开方式</h3><p>这一题特征构造主要还是围绕 train 表做的，user 表打开过多次都没啥效果。用户画像属性 tag 和 outertag 维度特别高，尝试过 PCA 降维和各种 embedding 方式都不行，最后还是采用的开源方法，对 tag 的分数做一些简单的统计。app 表的 applist 维度更高，也是各种尝试无效。</p><h3 id="其他特征"><a href="#其他特征" class="headerlink" title="其他特征"></a>其他特征</h3><p>我们根据连续使用将用户的使用时间分段，曝光时间间隔少于3分钟的视为同一个使用时段。然后统计每个使用时段中上一次曝光时间差的 mean 和 std 特征。还统计了每个用户下一次曝光时间差的 mean，std，median，skew。针对曝光时间差的统计可以反应用户当前使用时段或者整体上的视频观看时长信息。</p><p>此外还构造了用户平均每小时被曝光多少视频，该视频是一小时内被曝光给用户的第几个视频，今日曝光给用户的视频量与昨日曝光给用户的视频量的差值，未来一小时用户在该网络环境下的视频曝光数量。</p><p>当然也有许多尝试后效果不好的操作，比如数据集给出了用户的设备信息：设备厂商，设备版本，可以效仿<a href="https://logicjake.github.io/2020/02/06/%E7%A7%BB%E5%8A%A8%E5%B9%BF%E5%91%8A%E5%8F%8D%E6%AC%BA%E8%AF%88%E7%AE%97%E6%B3%95%E6%8C%91%E6%88%98%E8%B5%9B%E7%AE%97%E6%B3%95-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/">科大讯飞反欺诈</a>中的操作，对其进行数据处理，但可能由于自身重要性就不大，数据处理也没啥用。借助百度地图 api 可以将数据集中的经纬度转化成具体的城市，也没啥效果。</p><h2 id="模型"><a href="#模型" class="headerlink" title="模型"></a>模型</h2><p>这题主要有两种建模方式，一种是常规的五折交叉验证，在划分每折数据时，按 day 所占比例划分比以是否点击比例划分要好，可以让每折模型学习到三天的数据，做到时间上同分布。另一种按时间划分，线下用8，9两天的数据做训练集，10号数据做验证集，保留模型的最优迭代次数，然后线上模型用完整的三天数据预测测试集。对比来看，第二种验证集划分方式和线上更为相似，所以与线上分数 gap 较小，训练时间也更短，所以我们主要使用第二种建模方式。但第一种可以得到全数据集上的点击概率，可以作为新的特征加到数据集中，可以提升0.002-0.003。需要注意的是假如采用这种方式集成，五折模型的学习率不能太小，否则模型效果足够好导致概率值特征过强，使得时间划分模型直接拟合概率值，过早早停，无法学习其他特征。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>个人感觉，受限于 APP 的用户体验，单纯的用户属性对预测作用不大，这也是为什么 app 表和 user 无法正确打开的原因。所以我们需要从用户的行为数据（也就是 train 表）构造特征，但行为数据中也有大量不可信操作。有些用户为了完成 APP 的任务拿奖励，会随意点击观看视频，这部分行为完全随机，无法从中正确提炼出用户的兴趣所在，也就很难去预测未来的点击行为。但用户观看视频后的模式是固定的，视频的播放时间是必定有的，所以 ts 的时间差是可信的。点击视频详情页出现的相关视频推荐也是必然存在的（这是我猜测的，可能不正确），所以下一次视频曝光的 pos 也是可信的。所以这一题常规特征干不过穿越特征，无法过于信任用户行为，只能信任机器行为。</p><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><p><a href="https://www.turingtopia.com/models/details/notebook/b698f36b82f846e087cc349f91d0eba0" target="_blank" rel="noopener">比赛初期0.65开源</a><br><a href="https://www.turingtopia.com/models/details/notebook/417565783ede4251a7ef268019355259" target="_blank" rel="noopener">天才儿童0.81高分开源</a><br><a href="https://www.turingtopia.com/models/details/notebook/d854d4fd5b4e401ca20e944c4b353cbd" target="_blank" rel="noopener">user 表处理方式开源</a><br><a href="https://www.bilibili.com/video/av78282108" target="_blank" rel="noopener">麻婆豆腐视频讲解</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;最终成绩：A, B榜第三。&lt;br&gt;比赛地址：&lt;a href=&quot;https://www.turingtopia.com/competitionnew/detail/e4880352b6ef4f9f8f28e8f98498dbc4/sketch&quot; target=&quot;_blank&quot;
      
    
    </summary>
    
    
      <category term="比赛总结" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
    
      <category term="机器学习" scheme="https://logicjake.github.io/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/tags/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
      <category term="点击率预估" scheme="https://logicjake.github.io/tags/%E7%82%B9%E5%87%BB%E7%8E%87%E9%A2%84%E4%BC%B0/"/>
    
  </entry>
  
  <entry>
    <title>国能日新第二届光伏功率预测赛-赛后总结</title>
    <link href="https://logicjake.github.io/2020/02/07/%E5%9B%BD%E8%83%BD%E6%97%A5%E6%96%B0%E7%AC%AC%E4%BA%8C%E5%B1%8A%E5%85%89%E4%BC%8F%E5%8A%9F%E7%8E%87%E9%A2%84%E6%B5%8B%E8%B5%9B-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/"/>
    <id>https://logicjake.github.io/2020/02/07/%E5%9B%BD%E8%83%BD%E6%97%A5%E6%96%B0%E7%AC%AC%E4%BA%8C%E5%B1%8A%E5%85%89%E4%BC%8F%E5%8A%9F%E7%8E%87%E9%A2%84%E6%B5%8B%E8%B5%9B-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/</id>
    <published>2020-02-07T13:45:57.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<p>最终成绩：线上第六，无决赛。<br>比赛地址：<a href="https://www.dcjingsai.com/common/cmpt/%E5%9B%BD%E8%83%BD%E6%97%A5%E6%96%B0%E7%AC%AC%E4%BA%8C%E5%B1%8A%E5%85%89%E4%BC%8F%E5%8A%9F%E7%8E%87%E9%A2%84%E6%B5%8B%E8%B5%9B_%E7%AB%9E%E8%B5%9B%E4%BF%A1%E6%81%AF.html" target="_blank" rel="noopener">https://www.dcjingsai.com/common/cmpt/%E5%9B%BD%E8%83%BD%E6%97%A5%E6%96%B0%E7%AC%AC%E4%BA%8C%E5%B1%8A%E5%85%89%E4%BC%8F%E5%8A%9F%E7%8E%87%E9%A2%84%E6%B5%8B%E8%B5%9B_%E7%AB%9E%E8%B5%9B%E4%BF%A1%E6%81%AF.html</a></p><h1 id="赛题背景"><a href="#赛题背景" class="headerlink" title="赛题背景"></a>赛题背景</h1><p>光伏发电具有波动性和间歇性，大规模光伏电站的并网运行对电力系统的安全性和稳定造成较大的影响。对光伏电站输出功率的高精度预测，有助于调度部门统筹安排常规能源和光伏发电的协调配合，及时调整调度计划，合理安排电网运行方式。因此，本题旨在通过利用气象信息、历史数据，通过机器学习、人工智能方法，预测未来电站的发电功率，进一步为光伏发电功率提供准确的预测结果。</p><p>本场比赛不分A/B榜。</p><h1 id="任务"><a href="#任务" class="headerlink" title="任务"></a>任务</h1><p>通过学习历史一段时间内的数值天气预测数据和对应的光伏发电功率训练模型，结合未来某时间点的数值天气预测数据，预测该时间点的光伏发电功率。</p><h1 id="数据介绍"><a href="#数据介绍" class="headerlink" title="数据介绍"></a>数据介绍</h1><p>原始数据集字段比较少，为每15分钟预测的气象数据，包括时间，辐照度，风速，风向，温度，压强，湿度，直辐射，散辐射，总辐射，实际辐照度，实际功率。除了实际辐照度为真实值，实际功率为任务需要预测外，其余都是预测的气象数据，这本身就是一个奇特的地方，实际功率的预测误差有一部分是由气象数据的预测误差引起的。主办方的奖励方案分为三挡，较高奖励需要指标达到0.11和0.1，然而即使比赛延期，最高分也只有0.13345。</p><p>数据包含10个电站的数据，每个电站的发电功率不一样，在计算指标 MAE 的时候，只考虑实际功率的值大于等于装机容量*3%的测量样本。</p><h1 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h1><p>各个电站的分布差异性较大，模型在有些电站上拟合较好，有些较差，最好的电站 MAE 只有0.5左右，而差劲的电站能达到0.15。基于上述情况，采取各个站分开建模的方式，相较于统一建模效果提升显著。</p><h2 id="数据预处理"><a href="#数据预处理" class="headerlink" title="数据预处理"></a>数据预处理</h2><p>原始属性并没有缺失值，但异常值较多，除了官方提示的“电站9 2016/01/01 9:00 ~ 2017/03/21 23:45之间的实测数据可以删除；电站7 2018/03/01 00:00 ~ 2018/04/04 23:45之间的实测数据可以删除”，我们还额外进行了异常数据的删除。在训练集中，计算出每个站每天的平均功率，分别绘制散点图，会发现大量的离群点，我们根据离群点的范围加上手动调整，确定每日平均辐照度的合理范围，从而筛选出合理的训练数据。</p><p>查阅资料我们可以知道，对实际功率影响最大的气象因素为辐照度。在原始数据中，辐照度和实际功率的组合有很多重复值，大量的此类重复数据对模型的学习没有意义，使得模型学习偏向辐照度的影响，从而影响对其他特征的学习，所以我们删去了此类重复数据。但在特征工程中，有大量时序相关特征，为了保证时间上的连续性，我们在构造完特征后才删除重复数据。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">df_train = df_feature[df_feature[<span class="string">'实际功率'</span>].notnull()]</span><br><span class="line">df_test = df_feature[df_feature[<span class="string">'实际功率'</span>].isnull()]</span><br><span class="line">df_train.drop_duplicates([<span class="string">'辐照度'</span>, <span class="string">'实际功率'</span>], inplace=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure><p>晚上没有太阳电站是不发电的，所以夜间数据也是无效的。所以我们将晚上7点之后，早上6点之前的数据进行了删除。</p><p>此外，原始气象数据成偏态分布，我们采用 np.log1p 将其正态化。</p><h2 id="特征工程"><a href="#特征工程" class="headerlink" title="特征工程"></a>特征工程</h2><p>特征工程部分主要分为常规（统计为主）特征和业务特征。某个时间点的发电量不是孤立点，往往与前后的气象数据有关，因此在本方案中首先对气象数据进行平移，得到前15分钟，后15分钟，后30分钟，后45分钟的气象数据。除此以外还计算了一些窗口特征，如以当前时刻向前一个半小时时间段内风速, 辐照度, 温度, 压强, 湿度的 mean，std，sum，kurt 和 peak。窗口特征反映了站点局部气象变化情况，我们还可以放大时间跨度，以天，月，小时为单位有选择地统计气象数据，统计方法主要包括 median，max，min，std。气象数据都是数值型特征，我们可以利用分箱将其离散化，从而提高模型的稳定性和鲁棒性。之前我们说过辐照度对实际功率的影响最大，我们尝试刻画二者之前的关系，但假如直接用辐照度连续值来刻画，会受到异常值的影响，误差敏感。所以我们将辐照度进行等距分箱，采用五折统计框架统计每个分箱下对应的平均发电功率，用平均值降低异常值的影响。</p><p>气象数据对发电功率有很大影响，所以我们可以衡量气象数据趋势，比如风速，温度，湿度的趋势。在本方案中采用 LR 拟合一天的数据，用回归系数表示气象趋势。在基础气象数据的基础上，衍生构造出更多气象特征，比如：温差，湿度差，日温差。查阅资料可知：温度上升1℃，晶体硅太阳电池最大输出功率下降0.04%，开路电压下降0.04%(-2mv/℃)，短路电流上升0.04%。为了避免温度对发电量的影响，应该保持组件良好的通风条件。温度对太阳能板的发电功率影响很大，但在原始气象特征中，温度指示的是环境温度。但对于太阳能板，影响其发电功率的应该是板温，板温不仅和环境温度有关，也有风速有关，风越大，散热效果也就越好。进一步查阅资料发现板温与辐照度也有较大相关性，因此我们对辐照度，风速和环境温度进行综合计算得到板温。太阳入射角度和发电板倾斜角影响着太阳辐射到达太阳能板的实际辐照度，倾斜角度无法从已有数据获取，但太阳入射角（也叫天顶角）可以利用处于一年中的第几天，当地时间和经纬度位置计算得到，其中经纬度直接采用中国中部的大概位置。根据2018年DF光伏发电的冠军方案，距离周期内峰值的距离（dis2peak）也比较重要, 其反应一个测量点在一天，也就是一个周期中的位置，原因是每天中差不多的时间，太阳，温度等状态都差不多，发电量也就相似。</p><p>实际中，光伏发电量还与许多其他业务特征有关，比如和发电板电压，转换效率，但本次比赛只给了环境信息，无法构造。其他气象特征可能由于赛题本身提供的预测气象数据不准确，导致效果也不行。此外我们猜想天气会影响太阳能发电，比如下雪天积雪导致发电效能下降，所以尝试对气象数据进行 K-means 聚类，但效果也不好。</p><h2 id="模型"><a href="#模型" class="headerlink" title="模型"></a>模型</h2><p>光伏发电往往随着时间周期变化，往小了有一天当中以小时为单位的周期变化，再往上一个月内以天为单位的周期变化。所以在五折交叉验证中，采用 StratifiedKFold 对 day+hour 划分，保证每折模型能学习各阶段数据特性，不会出现有偏学习，换句话说每个模型的训练数据是同分布的。模型方面我们使用了 lgb + xgb，最后使用 stacking 的方式融合。</p><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><p><a href="https://zhuanlan.zhihu.com/p/44755488" target="_blank" rel="noopener">Data Fountain光伏发电量预测 Top1 开源分享</a><br><a href="https://mp.weixin.qq.com/s/Yix0xVp2SiqaAcuS6Q049g" target="_blank" rel="noopener">XGBoost+LightGBM+LSTM:一次机器学习比赛中的高分模型方案</a><br><a href="http://www.wanguan.com/news/117094.html" target="_blank" rel="noopener">浅谈影响光伏发电量减少的主要因素</a><br><a href="http://html.rhhz.net/yyqxxb/html/20140204.htm" target="_blank" rel="noopener">气象要素对太阳能电池板温度的影响</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;最终成绩：线上第六，无决赛。&lt;br&gt;比赛地址：&lt;a href=&quot;https://www.dcjingsai.com/common/cmpt/%E5%9B%BD%E8%83%BD%E6%97%A5%E6%96%B0%E7%AC%AC%E4%BA%8C%E5%B1%8A%E5%
      
    
    </summary>
    
    
      <category term="比赛总结" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
    
      <category term="机器学习" scheme="https://logicjake.github.io/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/tags/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
      <category term="光伏功率" scheme="https://logicjake.github.io/tags/%E5%85%89%E4%BC%8F%E5%8A%9F%E7%8E%87/"/>
    
  </entry>
  
  <entry>
    <title>2019科大讯飞移动广告反欺诈算法挑战赛算法-亚军赛后总结</title>
    <link href="https://logicjake.github.io/2020/02/06/%E7%A7%BB%E5%8A%A8%E5%B9%BF%E5%91%8A%E5%8F%8D%E6%AC%BA%E8%AF%88%E7%AE%97%E6%B3%95%E6%8C%91%E6%88%98%E8%B5%9B%E7%AE%97%E6%B3%95-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/"/>
    <id>https://logicjake.github.io/2020/02/06/%E7%A7%BB%E5%8A%A8%E5%B9%BF%E5%91%8A%E5%8F%8D%E6%AC%BA%E8%AF%88%E7%AE%97%E6%B3%95%E6%8C%91%E6%88%98%E8%B5%9B%E7%AE%97%E6%B3%95-%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93/</id>
    <published>2020-02-06T13:06:34.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<p>最终成绩：复赛第二，决赛亚军。<br>比赛地址：<a href="http://challenge.xfyun.cn/2019/gamedetail?type=detail/mobileAD" target="_blank" rel="noopener">http://challenge.xfyun.cn/2019/gamedetail?type=detail/mobileAD</a></p><h1 id="赛题背景"><a href="#赛题背景" class="headerlink" title="赛题背景"></a>赛题背景</h1><p>讯飞AI营销云基于深耕多年的人工智能和大数据技术，赋予营销智慧创新的大脑，以健全的产品矩阵和全方位的服务，帮助广告主用AI+大数据实现营销效能的全面提升，打造数字营销新生态。</p><p>广告欺诈是数字营销面临的一个重大挑战，随着基础技术的成熟化（篡改设备信息、IPv4服务化、黑卡、接码平台等），广告欺诈呈现出规模化、集团化的趋势，其作弊手段主要包括机器人、模拟器、群控、肉鸡/后门、众包等。广告欺诈不断蚕食着营销生态，反欺诈成为数字营销领域亟待突破的关键问题。</p><a id="more"></a><h1 id="数据说明"><a href="#数据说明" class="headerlink" title="数据说明"></a>数据说明</h1><p>本次比赛为参赛选手提供了5类数据：基本数据、媒体信息、时间、IP信息和设备信息。基本数据提供了广告请求会话sid，以及“是否作弊”的标识。媒体信息、时间、IP信息和设备信息等4类数据，提供了对作弊预估可能有帮助的辅助信息。</p><p>出于数据安全保证的考虑，所有数据均为脱敏处理后的数据。数据集提供了若干天的样本，最后一天数据用于预测，不提供“是否作弊”标识，其余日期的数据作为训练数据。</p><h1 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h1><h2 id="数据预处理"><a href="#数据预处理" class="headerlink" title="数据预处理"></a>数据预处理</h2><p>这一题数据预处理占了很多篇幅，主要包括两部分：对无意义字符或乱码的处理；基于业务需要的数据处理。在我们的方案中，着重对操作系统（osv），手机制造厂商（make）和机型（model）进行处理。有些 osv 包含无意义的字段，比如“??????????????????????????؉*o\x18”或“..þ\x06&lt;þ\x1f&lt;þf&lt;þ`&lt;þ]&gt;þܾþ\x08?þ2?þz?þ|?þ\u07ffþ?þ”，我们直接将其全部替换成-1。osv 主要由“.”分隔的数字组成，如7.1.9的形式，但在实际数据中包含一些由其他字符甚至字母分隔的osv，将这些分隔符替换成“.”，保证统一性。</p><p>make 最主要的问题是同一厂商下的中英文名称共存，比如“华为”和“huawei”没有必要认为其是两个不同的厂商（滑稽），所以统一将中文名称转成小写英文名称。此外对于一些过于精细化的分类进行粗放，比如“荣耀”归到“huawei”，“m1”，“m2”，“m3”全都归到“xiaomi”。</p><p>model 大部分数据格式都是“厂商 具体机型”，但很多数据都是采用厂商自己独有的编号，比如 OPPO 就有“PACM00”，“PBAM00”…因为 OPPO 这类数据较多，所以特意对其进行标准格式转换。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">data[<span class="string">'model'</span>].replace(<span class="string">'PACM00'</span>,<span class="string">"OPPO R15"</span>, inplace=<span class="literal">True</span>)</span><br><span class="line">data[<span class="string">'model'</span>].replace(<span class="string">'PBAM00'</span>,<span class="string">"OPPO A5"</span>, inplace=<span class="literal">True</span>)</span><br><span class="line">data[<span class="string">'model'</span>].replace(<span class="string">'PBEM00'</span>,<span class="string">"OPPO R17"</span>, inplace=<span class="literal">True</span>)</span><br><span class="line">data[<span class="string">'model'</span>].replace(<span class="string">'PADM00'</span>,<span class="string">"OPPO A3"</span>, inplace=<span class="literal">True</span>)</span><br><span class="line">data[<span class="string">'model'</span>].replace(<span class="string">'PBBM00'</span>,<span class="string">"OPPO A7"</span>, inplace=<span class="literal">True</span>)</span><br><span class="line">data[<span class="string">'model'</span>].replace(<span class="string">'PAAM00'</span>,<span class="string">"OPPO R15_1"</span>, inplace=<span class="literal">True</span>)</span><br><span class="line">data[<span class="string">'model'</span>].replace(<span class="string">'PACT00'</span>,<span class="string">"OPPO R15_2"</span>, inplace=<span class="literal">True</span>)</span><br><span class="line">data[<span class="string">'model'</span>].replace(<span class="string">'PABT00'</span>,<span class="string">"OPPO A5_1"</span>, inplace=<span class="literal">True</span>)</span><br><span class="line">data[<span class="string">'model'</span>].replace(<span class="string">'PBCM10'</span>,<span class="string">"OPPO R15x"</span>, inplace=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure><p>以上三列数据还存在一个共有问题：存在一些 url 编码字符，将其转换成对应的字符。另外对于’h’, ‘w’, ‘ppi’这些数值属性的缺失值，采用同一设备生产厂商下该属性的平均值填充。</p><h2 id="特征工程"><a href="#特征工程" class="headerlink" title="特征工程"></a>特征工程</h2><p>这一题有个明显的特点就是类别特征特别多，众所周知 catboost 擅长处理类别特征，所以直接将原始特征喂到 catboost，相对于其他模型效果非常好。在我的理解中，判断欺诈行为，其实就是通过多因素的累加来逐步确认欺诈的可能性，比如说来自某个城市的点击欺诈行为可能性为20%，假如其同时又是使用的某个欺诈行为频繁的机型，则欺诈可能性就又会提高。所以在这题中，对类别特征进行大量的交叉组合是有效的，这也是为什么 catboost 的效果要好，catboost 会自动进行类别特征的组合。在比赛交流群，有人问既然 catboost 已经能够完成特征组合，那么特征工程还有必要做大量的组合特征吗？我认为是必要的，这个道理其实和“深度模型理论上能够完成特征的高阶组合，但在比赛中人工特征工程还是存在的”一样，都是为了在源头上降低拟合的难度，提升性能的上限。</p><p>我们还可以根据 count 和 nunique 来挖掘欺诈行为，比如 imei 是手机串号，一部手机对应一个唯一的 imei。欺诈团伙为了躲避制裁往往会刷机，从而改变 model 值，比如将原本的华为手机刷成小米。针对以上行为，我们可以统计 imei 有多少个不同的 model ，通过这个特征判断这是一个存在刷机行为的设备。类似的，针对设备挂代理可以统计 imei 有多少个不同的 city。我们也可以构造其他特征来寻找欺诈行为，比如欺诈团伙会同时控制大量的手机进行广告点击，因此会大量采购某些价廉的机型，对应的这些机型会点击大量且不同的广告。这都是可以通过 count 和 nunique 特征和正常点击行为分开的，因为正常的用户不会去点击那么多广告。</p><p>除了 count 和 nunique 统计特征，还可以模仿点击率构造欺诈率特征，比如某个 model 或者 city，欺诈次数占总次数的比率。构造欺诈率特征的出发点来自赛题背景中所说的“广告欺诈呈现出规模化、集团化的趋势”，规模化，集团化的显著特点就是欺诈行为集中在某个城市，大量使用某个机型进行欺诈。此外，赛题中的 ip 信息也可以用来发现规模化，集团化行为，ip 能给出很多有用信息，比如 ip 能反应运营商，设备所在网段，从而反应这部分设备是否处在一个大量且集中的环境下，如下图所示。所以可以对 ip 按’.‘进行分段，取前一个，前两个，前三个作为新的网段类别特征，相较于 city 进一步细化了集团化，规模化概念。</p><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/%E6%89%8B%E6%9C%BA%E9%9B%86%E7%BE%A4.jpg" alt="手机集群（图片来自网络）" title="">                </div>                <div class="image-caption">手机集群（图片来自网络）</div>            </figure></div><h2 id="模型"><a href="#模型" class="headerlink" title="模型"></a>模型</h2><p>这题毫无疑问采用 catboost 比较好，但巨大的数据量加上大量的类别特征导致十分吃内存，模型训练完成回退到最优迭代轮次的时候经常内存溢出。初赛的时候采用 lgb 和 catboost 进行投票，到复赛直接就 catboost 但模型了。最后复赛 A 榜98.26379，B 榜98.24133，还是很稳定的。 </p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>观看现场前三的答辩，总体思路上大家都差不多。第三名队伍小兔子乖乖采用了“伪标签”，将测试集当中预测概率值大于0.999的作为正样本，小于0.001的作为负样本加入到训练集重新训练，提高了训练集的数据量，这一做法使得模型效果提高了0.0035左右。第一名直接是一个公司组的队伍，其主要亮点就在于通过部分有规则碰撞的方式还原 MD5 加密了的 imei，然后对还原后的 imei 进行截断，具体流程见下图。</p><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/imei%E7%A2%B0%E6%92%9E1.png" alt="imei 逆向1" title="">                </div>                <div class="image-caption">imei 逆向1</div>            </figure></div><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/imei%E7%A2%B0%E6%92%9E2.png" alt="imei 逆向2" title="">                </div>                <div class="image-caption">imei 逆向2</div>            </figure></div><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ul><li><a href="https://www.zhihu.com/question/364517083" target="_blank" rel="noopener">DNN可以进行高阶特征交互，为什么Wide&amp;Deep和DeepFM等模型仍然需要显式构造Wide部分？</a></li><li><a href="https://1024.iflytek.com/h5/" target="_blank" rel="noopener">现场决赛录播</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最终成绩：复赛第二，决赛亚军。&lt;br&gt;比赛地址：&lt;a href=&quot;http://challenge.xfyun.cn/2019/gamedetail?type=detail/mobileAD&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://challenge.xfyun.cn/2019/gamedetail?type=detail/mobileAD&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;赛题背景&quot;&gt;&lt;a href=&quot;#赛题背景&quot; class=&quot;headerlink&quot; title=&quot;赛题背景&quot;&gt;&lt;/a&gt;赛题背景&lt;/h1&gt;&lt;p&gt;讯飞AI营销云基于深耕多年的人工智能和大数据技术，赋予营销智慧创新的大脑，以健全的产品矩阵和全方位的服务，帮助广告主用AI+大数据实现营销效能的全面提升，打造数字营销新生态。&lt;/p&gt;
&lt;p&gt;广告欺诈是数字营销面临的一个重大挑战，随着基础技术的成熟化（篡改设备信息、IPv4服务化、黑卡、接码平台等），广告欺诈呈现出规模化、集团化的趋势，其作弊手段主要包括机器人、模拟器、群控、肉鸡/后门、众包等。广告欺诈不断蚕食着营销生态，反欺诈成为数字营销领域亟待突破的关键问题。&lt;/p&gt;
    
    </summary>
    
    
      <category term="比赛总结" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
    
      <category term="机器学习" scheme="https://logicjake.github.io/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/tags/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
      <category term="风控" scheme="https://logicjake.github.io/tags/%E9%A3%8E%E6%8E%A7/"/>
    
  </entry>
  
  <entry>
    <title>“添翼”杯人工智能创新应用大赛-智慧教育赛道-三等奖赛后总结</title>
    <link href="https://logicjake.github.io/2020/01/01/tianyicup-education/"/>
    <id>https://logicjake.github.io/2020/01/01/tianyicup-education/</id>
    <published>2020-01-01T00:00:00.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>这是我接触数据挖掘后的第一个与之有关的比赛，很标准的表格题，业务背景也不复杂，自我感觉非常适合像我这样的新人去完整地了解，熟悉数据挖掘比赛的整体流程。我也是在这次比赛中慢慢了解特征工程的重要性，也了解并应用了一些基础的特征构造方法，也第一次听说并使用（还没探索其背后的原理）那些个 boosting 模型：xgboost，catboost，lightgbm。</p><a id="more"></a><p>在比赛中也认识了队友，油菜花和勇哥，团队带来的学习增益和激励增益是庞大的，队友积累的经验和知识也是一笔巨大的财富。</p><p>最终成绩：复赛第三，决赛三等奖。</p><p>代码地址：<a href="https://github.com/LogicJake/tianyicup-education" target="_blank" rel="noopener">https://github.com/LogicJake/tianyicup-education</a></p><h1 id="赛题背景"><a href="#赛题背景" class="headerlink" title="赛题背景"></a>赛题背景</h1><p>随着人工智能(AI)的发展，“AI＋教育”“智慧课堂”等名词逐渐出现在大众视野，越来越多的学校将人工智能助手融入课堂，当下中国正逐步进入“智慧教育”时代。在传统课堂中，由于时间和精力的限制，老师和家长无法兼顾学生的学习状态和学业进展，不会关注大量对于学生能反应其真实问题和情况的数据。</p><p>智慧教育通过将传统教育行业的场景和当下最新的人工智能算法紧密结合，深度挖掘学生在各个知识点上的历史答题表现数据，最终预测学生在考试中的分数表现。</p><h1 id="赛题描述"><a href="#赛题描述" class="headerlink" title="赛题描述"></a>赛题描述</h1><p>请参赛选手，利用比赛对应训练集提供的学生信息、考试知识点信息、考试总得分信息等建立模型，预测测试集中学生在指定考试中的成绩总分，预测目标如下：</p><p>初赛：利用初中最后一年的相关考试和考点信息，预测初中最后一学期倒数第二、第三次考试的成绩。<br>复赛：利用初中 4 年中的相关考试和考点信息，预测初中最后一学期最后一次考试的的成绩。</p><h1 id="数据下载"><a href="#数据下载" class="headerlink" title="数据下载"></a>数据下载</h1><p><a href="https://www.kesci.com/urls/740cd3de" target="_blank" rel="noopener">初赛数据下载（右键保存下载）</a>，复赛数据不可下载。</p><h1 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h1><h2 id="简单的数据分析"><a href="#简单的数据分析" class="headerlink" title="简单的数据分析"></a>简单的数据分析</h2><table><thead><tr><th align="center">课程类别</th><th align="center">课程名称</th><th align="center">知识点数量</th></tr></thead><tbody><tr><td align="center">course_class1</td><td align="center">course1</td><td align="center">368</td></tr><tr><td align="center"></td><td align="center">course2</td><td align="center">367</td></tr><tr><td align="center"></td><td align="center">course3</td><td align="center">262</td></tr><tr><td align="center">course_class2</td><td align="center">course4</td><td align="center">219</td></tr><tr><td align="center"></td><td align="center">course5</td><td align="center">335</td></tr><tr><td align="center"></td><td align="center">course6</td><td align="center">146</td></tr><tr><td align="center"></td><td align="center">course7</td><td align="center">316</td></tr><tr><td align="center"></td><td align="center">course8</td><td align="center">223</td></tr></tbody></table><p>课程数据包括课程类别，课程名称和所包含的知识点。课程类别只有两类，大概率就是理科和文科的区别。主要难点在于课程的知识点维度非常大，课程考试虽然每次只包含少量的知识点，但是为了向量空间统一，假如采用01向量表示考试的知识点分布情况，该向量将是巨维且十分稀疏的。</p><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/education_1.png" alt="成绩分布" title="">                </div>                <div class="image-caption">成绩分布</div>            </figure></div><p>该数据集还有个好处就是数据完整，不存在数据缺失或异常值的情况，除了标签数据‘成绩’出现了0分。从上图成绩分布可以看到，大部分成绩都在60分以上，60以下的就很稀疏。我们将0分看做异常值，可以认为是由学生缺考或者作弊被取消成绩导致的，这部分0分数据不能代表学生正常的考试水平且模型也学习不到这种无规律情况，所以将这部分数据进行剔除。由上面成绩分布可以知道，模型的预测也大概率集中在60以上，所以对于0分样本，计算出来的MSE指标非常大。所以这一题线上线下MSE分数差距比较大，可以确定线上测试集也包含了0分样本。当时曾经和队友讨论能否单独建模预测0分的出现，但效果很差，毕竟学生缺考和作弊行为也没法从给出的数据中推演出来（笑）。</p><h2 id="特征工程"><a href="#特征工程" class="headerlink" title="特征工程"></a>特征工程</h2><h3 id="基础特征"><a href="#基础特征" class="headerlink" title="基础特征"></a>基础特征</h3><p>基础的特征工程比较简单，就是做一下统计特征，比如考试次序，考试的知识点数量和知识点跨度。将知识点映射为其所属段落和类别，又可以构造数量和跨度特征。每个知识点有难度属性，每门考试的知识点占比乘以对于难度，得到衡量考试整体难度的特征。在数据分析中提到，知识点维度巨大且稀疏，假如使用原始01向量代表知识点的分布，会大大降低模型的效率。所以我们使用经典的PCA进行降维，在实际操作中，对考试的知识点占比，知识点种类和段落占比分别降维到60维。当然我们也尝试过使用 deepwalk 之类的 embedding 方法进行降维，但效果不行，在决赛答辩阶段，听其他队伍介绍也使用了 deepwalk， 但当评委问起 embedding 效果时，也回答效果不好。此外还简单使用了交叉特征，如知识点数量和考试难度拼接交叉。</p><h3 id="相似考试特征"><a href="#相似考试特征" class="headerlink" title="相似考试特征"></a>相似考试特征</h3><p>在构造特征的时候，我们认为一个学生面对一门新考试，其成绩应该与和这次考试相似的考试成绩相近。所以我们找出每门考试最相似的三门考试，然后对学生三门考试成绩与考试相似度进行加权求和，得到预估的本次考试成绩，作为一个伪标签。在定义相似时，我们采用了三个指标：知识点分布相似，知识点段落分布相似，知识点类别分布相似。</p><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/education_2.png" alt="相似考试特征构造示意图" title="">                </div>                <div class="image-caption">相似考试特征构造示意图</div>            </figure></div><h3 id="五折交叉标签统计特征"><a href="#五折交叉标签统计特征" class="headerlink" title="五折交叉标签统计特征"></a>五折交叉标签统计特征</h3><p>在接触这个比赛之前，我一直认为特征是特征，标签是标签，标签信息是无法使用或丢给模型学习的。这一点本质没错，因为直接使用标签进行学习，必然造成数据泄露，得到一个毫无意义的模型。但只要稍加变通，就能充分使用标签信息。换句话说，我们在评估一个学生的成绩好坏时，常常会说这个学生的平均成绩如何如何，这个时候不也是用了标签信息吗？甚至我们可以因此推断，学生的考试平均成绩，因为是一个强特征，它对预测学生考试成绩时十分重要。因此引入了特征工程中一个比较重要的构造方式，target encode，其在 ctr 类型的比赛中使用广泛。刚刚我们说过，一方面平均成绩很重要，但另一方面引入标签信息会泄露。泄露的本质就是在为样本构造特征的时候，使用了本样本的标签信息，那么为了避免泄露，我们在 target encode 时，只要避免使用本次样本的标签信息就可以了，常见的做法为五折交叉统计，将历史考试成绩数据分为五折，每次用4折构造特征，给训练集中的另外1折。</p><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/education_3.jpg" alt="五折交叉统计(引用自鱼佬知乎专栏)" title="">                </div>                <div class="image-caption">五折交叉统计(引用自鱼佬知乎专栏)</div>            </figure></div><p>以此类推，我们不仅可以构造成绩平均值，还可以借助五折框架构造更多关于成绩的统计特征。<br>将构造过程分为对象和统计方法两部分：<br>对象:</p><ul><li>学生所有考试成绩</li><li>学生所有考试排名</li><li>学生考试成绩 / 本次考试平均分</li><li>学生在所有考试上表现出来的抗压能力</li><li>学生在某门课考试上表现出来的抗压能力</li><li>某门课程的所有考试成绩</li><li>学生在某门课程上的考试成绩</li><li>学生在某门课程上某次考试成绩 / 某次考试平均成绩</li><li>某性别学生在某门课程上所有考试成绩  </li></ul><p>统计方法:  </p><ul><li>最大值(max)</li><li>最小值(min)</li><li>平均值(mean)</li><li>标准差(std)</li><li>中值(median)</li><li>变异系数(cv)</li></ul><h3 id="时序特征"><a href="#时序特征" class="headerlink" title="时序特征"></a>时序特征</h3><p>学生最近几次的考试成绩可以反映一个学生最近的学习状态，所以我们可以用最近几次的考试成绩构造时序特征。差分，窗口是时序常用的方法。</p><ul><li>最近三次考试成绩 mean std</li><li>最近三次考试排名 mean std</li><li>最近三次考试成绩差值的平均</li><li>对最近三次考试成绩平均分做窗口为8的平均</li><li>对最近三次考试成绩平均分差值做窗口为3的平均</li><li>最近三次考试成绩</li></ul><h3 id="“嫁接”特征"><a href="#“嫁接”特征" class="headerlink" title="“嫁接”特征"></a>“嫁接”特征</h3><p>“嫁接”学习源于这样一个问题：初赛复赛数据分布不一致。在IJCAL2018比赛中,大家发现前六天和最后一天数据分布不同，大部分人于是用同分布的第七天上半天的数据预测下半天，而植物大佬在这个基础用前六天训练了一个模型，预测第七天得到的 probability 作为第七天模型的 feature ，再用第七天上半天的数据预测下半天，使最后的分数遥遥领先，轻松得到 solo 冠军。套用到这个比赛，复赛利用初中 4 年中的相关考试和考点信息，预测初中最后一学期最后一次考试的的成绩。我们可以认为前三年的考试和最后一年的考试是不同分布，而要预测的最后一次考试和最后一年的考试是同分布的，所以我们利用前三年数据构造部分特征，预测最后一年的考试成绩。利用全量数据构造的特征加上预测的最后一年的考试成绩预测最后一次考试成绩。</p><h2 id="模型"><a href="#模型" class="headerlink" title="模型"></a>模型</h2><p>本方案采用五折交叉验证框架，分别训练 lightgbm 和 xgboost 模型，将两个模型的预测分数根据线上模型得分加权融合。模型分数见下表。</p><table><thead><tr><th align="center">模型</th><th align="center">Public分数</th></tr></thead><tbody><tr><td align="center">xgboost</td><td align="center">7.289815178</td></tr><tr><td align="center">lightgbm</td><td align="center">7.288464145</td></tr><tr><td align="center">融合</td><td align="center">7.286831301</td></tr></tbody></table><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>赛后拜读了第一大佬的开源。第一名方案对特征分别建模训练，总共分为三部分模型：第一部分模型将成绩预测问题视为回归问题进行分析，第二部分模型将成绩预测问题视为一种短期时间序列预测的问题，第三部分模型对成绩、试卷考点分布、学生考点掌握整体进行数学抽象。在第一部分模型当中，处理考试的知识点特征的时候，采用 NMF 算法进行降维处理。最让我感兴趣的是第三部分模型，在我的方案中，其实对知识点处理的不是很好，无法构造出学生对知识点的掌握情况。第一名方案采用这样一种假设：试卷-考点分布矩阵（exam）乘以学生-考点掌握矩阵（stu）就能得到试卷-学生成绩矩阵（score）。exam 矩阵和 score 矩阵都是已知，所以直接采用优化算法解得 stu 矩阵即可。形式化表示如下：</p><ul><li>score : m * n (exam_number * student_numbers) 试卷-学生成绩矩阵</li><li>exam : m * s(exam_number * course_section) 试卷-考点分布矩阵</li><li>stu : s * n (course_section * student_numbers) 学生-考点掌握矩阵</li></ul><p>其中，m 为各科考试次数，n 为学生数目 s 为各科考点数，目标函数为 <strong>Argmin (Σ(score - exam * stu)^2)</strong>。<br>最后通过学生-考点掌握矩阵乘以要预测的考试知识点分布矩阵，从而得到待预测考试的预测成绩。从模型效果来看，该部分方案十分优秀，线上得分7.33，令人佩服。</p><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ul><li><a href="https://zhuanlan.zhihu.com/p/69351598" target="_blank" rel="noopener">2019腾讯广告算法大赛方案分享（初赛冠军）</a></li><li><a href="https://zhuanlan.zhihu.com/p/51901122" target="_blank" rel="noopener">结构化数据的迁移学习：嫁接学习</a></li><li><a href="https://github.com/yzh1994414/Tianyicup-IntelligentEducation" target="_blank" rel="noopener">第一名方案</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;这是我接触数据挖掘后的第一个与之有关的比赛，很标准的表格题，业务背景也不复杂，自我感觉非常适合像我这样的新人去完整地了解，熟悉数据挖掘比赛的整体流程。我也是在这次比赛中慢慢了解特征工程的重要性，也了解并应用了一些基础的特征构造方法，也第一次听说并使用（还没探索其背后的原理）那些个 boosting 模型：xgboost，catboost，lightgbm。&lt;/p&gt;
    
    </summary>
    
    
      <category term="比赛总结" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/categories/%E6%AF%94%E8%B5%9B%E6%80%BB%E7%BB%93/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
    
      <category term="机器学习" scheme="https://logicjake.github.io/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
      <category term="数据挖掘" scheme="https://logicjake.github.io/tags/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/"/>
    
      <category term="教育" scheme="https://logicjake.github.io/tags/%E6%95%99%E8%82%B2/"/>
    
  </entry>
  
  <entry>
    <title>A*算法理解及代码实现</title>
    <link href="https://logicjake.github.io/2019/12/28/A_star/"/>
    <id>https://logicjake.github.io/2019/12/28/A_star/</id>
    <published>2019-12-28T12:12:07.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<p>A*寻路算法要解决的问题就是在有障碍物的情况下，如何快速找到一条到达目的节点的最短路径。</p><p>把问题抽象成以下场景：在一个由M×N的方块组成的区域中，绿色代表起始点，蓝色代表无法越过的障碍物，红色代表终点。需要注意的是，我们在寻路的时候无法越过“墙角”，对照到下图就是不能走红色路线，必须走蓝色路线。这是因为在抽象场景下，移动物体是无体积所以可以直接沿着红线穿过去，但在实际情况下，比如无人车寻路，考虑到体积因素是无法行进红色路线的，所以在建模的时候需要加上这样的约束条件。当然在不涉及到墙角的情况下是可以走斜线的，这是毋庸置疑的。</p><a id="more"></a><div style="margin: auto"><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/a_star1.jpg" alt="不能直接越过墙角" title="">                </div>                <div class="image-caption">不能直接越过墙角</div>            </figure></div><h1 id="算法流程"><a href="#算法流程" class="headerlink" title="算法流程"></a>算法流程</h1><p>首先定义open list和close list，open list存放已知但还没有探索过的区块，close list存放已经探索过的区块。</p><p>最短路径肯定涉及到距离度量，在A*算法中距离分为两个部分：G 和H，总距离F=G + H。</p><p>G等于从起点移动到指定方格的移动代价。在本例中，相邻节点间，横向和纵向的移动代价为 10 ，对角线的移动代价为 14 （10×根号2的近似）。为了方便计算和寻路，我们为每个节点设置一个父节点。父节点可以这样理解，在目前已知条件下，存在一条从起点到当前指定方格的最优路径，而父亲节点就是这条路径上的指定方格的上一个节点，计算当前方格的 G 值的方法就是找出其父亲的 G 值，然后按在父亲节点直线方向还是斜线方向加上 10 或 14。</p><p>H为从当前节点到终点的估计距离，是对剩余距离的估算值，而不是实际值。它是一种理想值，忽略了障碍物的影响。在本例中使用曼哈顿距离（街区距离）来度量剩余距离。</p><p>整个算法流程为：</p><ul><li>把起点加入open list，重复以下流程<ul><li>如果open list为空，寻路失败，找不到到达终点的路径。遍历 open list ，查找 F 值最小的节点，把它作为当前要处理的节点。</li><li>把这个节点移到 close list</li><li>对当前方格的 8 个相邻方格的每一个方格<ul><li>如果它是不可抵达的或者它在 close list 中，忽略。</li><li>如果它不在 open list 中，把它加入 open list ，并且把当前方格设置为它的父亲，计算该方格的 F ， G 和 H 值。</li><li>如果它已经在 open list 中，检查通过当前方格到达该方格是否代价更小，即G值更小。如果是这样，把它的父亲设置为当前方格，并重新计算它的 G 和 F 值。</li></ul></li></ul></li><li>如果终点加入到了open list中，此时路径已经找到，从终点开始，每个方格沿着父节点移动直至起点，这就是最优路径。</li></ul><p>由算法可以看出通过总距离F选出当前处理节点，通过G来更新路径（改变节点的父节点就是改变了路径）。</p><p>另外需要注意：在寻找F值最小的时候可能会出现不止一个节点的情况，此时处于节省寻路时间的考虑，选择最后放入open list的节点。因为最后放入open list的节点是上一个处理节点的邻居节点，从而保证寻路时的连贯性，不会出现在寻路过程中突然跳到另外的地方重新开辟一条新路径。</p><h1 id="流程解释"><a href="#流程解释" class="headerlink" title="流程解释"></a>流程解释</h1><p>解释流程前，先说明图例：</p><ul><li>绿色填充方块：起点</li><li>蓝色填充方块：障碍</li><li>红色填充方块：终点</li><li>绿色边的方块：open list中的方块</li><li>黄色边框方块：close list中的方块</li><li>方块中白色箭头指向父亲节点</li><li>方块中左上角数字代表F值，左下角G值，右下角H值*</li></ul><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/a_star2.jpg" alt="第1次搜索" title="">                </div>                <div class="image-caption">第1次搜索</div>            </figure><h2 id="开始搜索"><a href="#开始搜索" class="headerlink" title="开始搜索"></a>开始搜索</h2><p>从open list中取出起始点，将起点加入close list。起点周围8个方格都可到达所以都加入到open list中，设置父节点为起点，并计算各自的F，G，H值。结果如上图所示。</p><h2 id="继续搜索"><a href="#继续搜索" class="headerlink" title="继续搜索"></a>继续搜索</h2><p>从open list中找出F值最小的方格，起点右边的方格F值为40最小，暂且称该节点为A。将A从open list剔除，加入到close list。A右边为障碍物，忽略；其余方向的方格都已经在open list中且加入A并没有减小他们的G值，所以维持原样不变。</p><p>结果如下图所示，可见起点右边的方格加上了黄色框，代表进入close list，其余不变。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/a_star3.jpg" alt="第2次搜索结果" title="">                </div>                <div class="image-caption">第2次搜索结果</div>            </figure><p>重复以上步骤，值得注意的是在第5次搜索，随着起点正下方方格（称其为B）加入到close list，处于B下方的方格（称其为C）因为B的加入，起点到C的距离缩短到80，所以C的父节点跟新为B，并相应跟新F，G，H的值。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/a_star4.jpg" alt="第4次搜索结果" title="">                </div>                <div class="image-caption">第4次搜索结果</div>            </figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/a_star5.jpg" alt="第5次搜索结果" title="">                </div>                <div class="image-caption">第5次搜索结果</div>            </figure><p>不断重复上述步骤，最后终点被加入到open list中，从终点开始，每个方格沿着父节点移动直至起点，就是最优路径。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/a_star6.jpg" alt="最终结果" title="">                </div>                <div class="image-caption">最终结果</div>            </figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://pic.logicjake.xyz/a_star7.webp" alt="全部过程" title="">                </div>                <div class="image-caption">全部过程</div>            </figure><h1 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h1><p><a href="https://github.com/LogicJake/A-star-search" target="_blank" rel="noopener">https://github.com/LogicJake/A-star-search</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;A*寻路算法要解决的问题就是在有障碍物的情况下，如何快速找到一条到达目的节点的最短路径。&lt;/p&gt;
&lt;p&gt;把问题抽象成以下场景：在一个由M×N的方块组成的区域中，绿色代表起始点，蓝色代表无法越过的障碍物，红色代表终点。需要注意的是，我们在寻路的时候无法越过“墙角”，对照到下图就是不能走红色路线，必须走蓝色路线。这是因为在抽象场景下，移动物体是无体积所以可以直接沿着红线穿过去，但在实际情况下，比如无人车寻路，考虑到体积因素是无法行进红色路线的，所以在建模的时候需要加上这样的约束条件。当然在不涉及到墙角的情况下是可以走斜线的，这是毋庸置疑的。&lt;/p&gt;
    
    </summary>
    
    
      <category term="机器学习" scheme="https://logicjake.github.io/categories/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
    
      <category term="算法" scheme="https://logicjake.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    
  </entry>
  
  <entry>
    <title>hello world</title>
    <link href="https://logicjake.github.io/2019/12/27/hello-world/"/>
    <id>https://logicjake.github.io/2019/12/27/hello-world/</id>
    <published>2019-12-27T22:10:07.000Z</published>
    <updated>2023-01-28T12:32:53.025Z</updated>
    
    <content type="html"><![CDATA[<p>之前博客的服务器不小心被我格式化了，还是搞个静态的托管在 github page 省心。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;之前博客的服务器不小心被我格式化了，还是搞个静态的托管在 github page 省心。&lt;/p&gt;

      
    
    </summary>
    
    
    
  </entry>
  
</feed>
