<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>给荔枝打气</title>
  
  <subtitle>给荔枝打气</subtitle>
  <link href="https://zekizz.github.io/atom.xml" rel="self"/>
  
  <link href="https://zekizz.github.io/"/>
  <updated>2022-04-05T11:34:20.631Z</updated>
  <id>https://zekizz.github.io/</id>
  
  <author>
    <name>zekizz</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>博文中插入echarts</title>
    <link href="https://zekizz.github.io/bo-ke/echarts-in-blog/"/>
    <id>https://zekizz.github.io/bo-ke/echarts-in-blog/</id>
    <published>2022-04-04T12:37:32.000Z</published>
    <updated>2022-04-05T11:34:20.631Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>最近想做一个基金的标注工具，用echarts开发了前端部分，顺便看到<code>fluid</code>这个主题可以很方便的在博客中插入echarts图表，做一个记录。</p><span id="more"></span><p>教程参见: <a href="https://hexo.fluid-dev.com/posts/hexo-echarts/">使用 ECharts 插件绘制炫酷图表</a></p><p>第一步是安装插件<br><pre><code class="hljs shell">npm install hexo-tag-echarts --save</code></pre></p><script src="https://cdn.jsdelivr.net/npm/echarts@4.8.0/dist/echarts.min.js"></script><script src="https://cdn.jsdelivr.net/npm/echarts-gl@1.1.1/dist/echarts-gl.min.js"></script><p>基础图表的例子<br><pre><code class="hljs js">&#123;% echarts <span class="hljs-number">400</span> <span class="hljs-string">&#x27;85%&#x27;</span> %&#125;option = &#123;  <span class="hljs-attr">xAxis</span>: &#123;    <span class="hljs-attr">type</span>: <span class="hljs-string">&#x27;category&#x27;</span>,    <span class="hljs-attr">data</span>: [<span class="hljs-string">&#x27;Mon&#x27;</span>, <span class="hljs-string">&#x27;Tue&#x27;</span>, <span class="hljs-string">&#x27;Wed&#x27;</span>, <span class="hljs-string">&#x27;Thu&#x27;</span>, <span class="hljs-string">&#x27;Fri&#x27;</span>, <span class="hljs-string">&#x27;Sat&#x27;</span>, <span class="hljs-string">&#x27;Sun&#x27;</span>]  &#125;,  <span class="hljs-attr">yAxis</span>: &#123;    <span class="hljs-attr">type</span>: <span class="hljs-string">&#x27;value&#x27;</span>  &#125;,  <span class="hljs-attr">series</span>: [    &#123;      <span class="hljs-attr">data</span>: [<span class="hljs-number">150</span>, <span class="hljs-number">230</span>, <span class="hljs-number">224</span>, <span class="hljs-number">218</span>, <span class="hljs-number">135</span>, <span class="hljs-number">147</span>, <span class="hljs-number">260</span>],      <span class="hljs-attr">type</span>: <span class="hljs-string">&#x27;line&#x27;</span>    &#125;  ]&#125;;&#123;% endecharts %&#125;</code></pre></p><div id="echarts56" style="width: 85%;height: 400px;margin: 0 auto"></div><script type="text/javascript">        // 基于准备好的dom，初始化echarts实例        var myChart = echarts.init(document.getElementById('echarts56'));        // 指定图表的配置项和数据        var option = option = {  xAxis: {    type: 'category',    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']  },  yAxis: {    type: 'value'  },  series: [    {      data: [150, 230, 224, 218, 135, 147, 260],      type: 'line'    }  ]};        // 使用刚指定的配置项和数据显示图表。        myChart.setOption(option);</script><p>标注工具代码<br><pre><code class="hljs js"><span class="hljs-keyword">let</span> base = +<span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>(<span class="hljs-number">2010</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>);<span class="hljs-keyword">let</span> oneDay = <span class="hljs-number">24</span> * <span class="hljs-number">3600</span> * <span class="hljs-number">1000</span>;<span class="hljs-keyword">let</span> date = [];<span class="hljs-keyword">let</span> s = <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">random</span>() * <span class="hljs-number">300</span>;<span class="hljs-keyword">let</span> data = [[s, s, s, s]];<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">1</span>; i &lt; <span class="hljs-number">365</span>*<span class="hljs-number">12</span>; i++) &#123;  <span class="hljs-keyword">var</span> now = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>((base += oneDay));  date.<span class="hljs-title function_">push</span>([now.<span class="hljs-title function_">getFullYear</span>(), now.<span class="hljs-title function_">getMonth</span>() + <span class="hljs-number">1</span>, now.<span class="hljs-title function_">getDate</span>()].<span class="hljs-title function_">join</span>(<span class="hljs-string">&#x27;/&#x27;</span>));  <span class="hljs-keyword">var</span> v = ((<span class="hljs-title class_">Math</span>.<span class="hljs-title function_">random</span>() - <span class="hljs-number">0.5</span>) * <span class="hljs-number">0.05</span> + <span class="hljs-number">1</span>)*data[i - <span class="hljs-number">1</span>][<span class="hljs-number">1</span>];  <span class="hljs-comment">// 开盘价、收盘价、最低价、最高价</span>  data.<span class="hljs-title function_">push</span>([v*(<span class="hljs-number">1</span> + (<span class="hljs-title class_">Math</span>.<span class="hljs-title function_">random</span>() - <span class="hljs-number">0.5</span>)*<span class="hljs-number">0.01</span>), v, v*<span class="hljs-number">0.995</span>, v*<span class="hljs-number">1.005</span>]);&#125;option = &#123;  <span class="hljs-attr">toolbox</span>: &#123;    <span class="hljs-attr">feature</span>: &#123;      <span class="hljs-attr">dataZoom</span>: &#123;        <span class="hljs-attr">yAxisIndex</span>: <span class="hljs-literal">false</span>      &#125;,      <span class="hljs-attr">brush</span>: &#123;        <span class="hljs-attr">type</span>: [<span class="hljs-string">&#x27;lineX&#x27;</span>, <span class="hljs-string">&#x27;clear&#x27;</span>]      &#125;    &#125;  &#125;,  <span class="hljs-attr">brush</span>: &#123;    <span class="hljs-attr">xAxisIndex</span>: <span class="hljs-number">0</span>,    <span class="hljs-attr">throttleType</span>: <span class="hljs-string">&#x27;debounce&#x27;</span>,    <span class="hljs-attr">throttleDelay</span>: <span class="hljs-number">600</span>,    <span class="hljs-attr">inBrush</span>: &#123;      <span class="hljs-attr">symbolSize</span>: <span class="hljs-number">4</span>,      <span class="hljs-attr">color</span>: <span class="hljs-string">&#x27;#FF3333&#x27;</span>    &#125;,    <span class="hljs-attr">outOfBrush</span>: &#123;      <span class="hljs-attr">colorAlpha</span>: <span class="hljs-number">0.1</span>    &#125;  &#125;,  <span class="hljs-attr">dataZoom</span>: [    &#123;      <span class="hljs-attr">type</span>: <span class="hljs-string">&#x27;inside&#x27;</span>,      <span class="hljs-attr">start</span>: <span class="hljs-number">0</span>,      <span class="hljs-attr">end</span>: <span class="hljs-number">100</span>    &#125;,    &#123;      <span class="hljs-attr">start</span>: <span class="hljs-number">0</span>,      <span class="hljs-attr">end</span>: <span class="hljs-number">100</span>    &#125;  ],  <span class="hljs-attr">xAxis</span>: &#123;    <span class="hljs-attr">type</span>: <span class="hljs-string">&#x27;category&#x27;</span>,    <span class="hljs-attr">data</span>: date  &#125;,  <span class="hljs-attr">yAxis</span>: &#123;    <span class="hljs-attr">type</span>: <span class="hljs-string">&#x27;value&#x27;</span>  &#125;,  <span class="hljs-attr">series</span>: [    &#123;      <span class="hljs-attr">data</span>: data,      <span class="hljs-attr">type</span>: <span class="hljs-string">&#x27;candlestick&#x27;</span>,      <span class="hljs-attr">lineStyle</span>: &#123;        <span class="hljs-attr">normal</span>: &#123;          <span class="hljs-attr">width</span>: <span class="hljs-number">2</span>,        &#125;      &#125;    &#125;  ]&#125;;myChart.<span class="hljs-title function_">on</span>(<span class="hljs-string">&#x27;brushSelected&#x27;</span>, <span class="hljs-keyword">function</span> (<span class="hljs-params">params</span>) &#123;  <span class="hljs-keyword">var</span> brushed = [];  <span class="hljs-keyword">var</span> target = params.<span class="hljs-property">batch</span>[<span class="hljs-number">0</span>][<span class="hljs-string">&#x27;selected&#x27;</span>][<span class="hljs-number">0</span>][<span class="hljs-string">&#x27;dataIndex&#x27;</span>];  <span class="hljs-keyword">if</span>(target.<span class="hljs-property">length</span>&gt;<span class="hljs-number">0</span>)&#123;    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(target);    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(target.<span class="hljs-property">length</span>);  &#125;    &#125;);</code></pre></p><p><strong>这里需要注意的是：</strong></p><p>如果直接把上述所有代码放入<code>&#123;% echarts 400 '85%' %&#125; &#123;% endecharts %&#125;</code>里是无法显示的。因为<code>hexo-tag-echarts</code>这个插件在生成js代码的时候是将里面的东西均作为配置来用模板套用。比如上面的那个简单的折线图，生成的结果如下：<br><pre><code class="hljs js">&lt;script type=<span class="hljs-string">&quot;text/javascript&quot;</span>&gt;        <span class="hljs-comment">// 基于准备好的dom，初始化echarts实例</span>        <span class="hljs-keyword">var</span> myChart = echarts.<span class="hljs-title function_">init</span>(<span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;echarts8038&#x27;</span>));        <span class="hljs-comment">// 指定图表的配置项和数据</span>        <span class="hljs-keyword">var</span> option = option = &#123;  <span class="hljs-attr">xAxis</span>: &#123;    <span class="hljs-attr">type</span>: <span class="hljs-string">&#x27;category&#x27;</span>,    <span class="hljs-attr">data</span>: [<span class="hljs-string">&#x27;Mon&#x27;</span>, <span class="hljs-string">&#x27;Tue&#x27;</span>, <span class="hljs-string">&#x27;Wed&#x27;</span>, <span class="hljs-string">&#x27;Thu&#x27;</span>, <span class="hljs-string">&#x27;Fri&#x27;</span>, <span class="hljs-string">&#x27;Sat&#x27;</span>, <span class="hljs-string">&#x27;Sun&#x27;</span>]  &#125;,  <span class="hljs-attr">yAxis</span>: &#123;    <span class="hljs-attr">type</span>: <span class="hljs-string">&#x27;value&#x27;</span>  &#125;,  <span class="hljs-attr">series</span>: [    &#123;      <span class="hljs-attr">data</span>: [<span class="hljs-number">150</span>, <span class="hljs-number">230</span>, <span class="hljs-number">224</span>, <span class="hljs-number">218</span>, <span class="hljs-number">135</span>, <span class="hljs-number">147</span>, <span class="hljs-number">260</span>],      <span class="hljs-attr">type</span>: <span class="hljs-string">&#x27;line&#x27;</span>    &#125;  ]&#125;;        <span class="hljs-comment">// 使用刚指定的配置项和数据显示图表。</span>        myChart.<span class="hljs-title function_">setOption</span>(option);&lt;/script&gt;</code></pre><br>所以需要将数据部分单独拎出来，放到<code>&lt;script&gt;&lt;/script&gt;</code>中。</p><script>let base = +new Date(2010, 1, 1);  let oneDay = 24 * 3600 * 1000;  let date = [];  let s = Math.random() * 300;  let data = [[s, s, s, s]];  for (let i = 1; i < 365*12; i++) {    var now = new Date((base += oneDay));    date.push([now.getFullYear(), now.getMonth() + 1, now.getDate()].join('/'));    var v = ((Math.random() - 0.5) * 0.05 + 1)*data[i - 1][1];    // 开盘价、收盘价、最低价、最高价    data.push([v*(1 + (Math.random() - 0.5)*0.01), v, v*0.995, v*1.005]);  };</script><div id="echarts1257" style="width: 85%;height: 400px;margin: 0 auto"></div><script type="text/javascript">        // 基于准备好的dom，初始化echarts实例        var myChart = echarts.init(document.getElementById('echarts1257'));        // 指定图表的配置项和数据        var option = option = {  toolbox: {    feature: {      dataZoom: {        yAxisIndex: false      },      brush: {        type: ['lineX', 'clear']      }    }  },  brush: {    xAxisIndex: 0,    throttleType: 'debounce',    throttleDelay: 600,    inBrush: {      symbolSize: 4,      color: '#FF3333'    },    outOfBrush: {      colorAlpha: 0.1    }  },  dataZoom: [    {      type: 'inside',      start: 0,      end: 100    },    {      start: 0,      end: 100    }  ],  xAxis: {    type: 'category',    data: date  },  yAxis: {    type: 'value'  },  series: [    {      data: data,      type: 'candlestick',      lineStyle: {        normal: {          width: 2,        }      }    }  ]};myChart.on('brushSelected', function (params) {  var brushed = [];  var target = params.batch[0]['selected'][0]['dataIndex'];  if(target.length>0){    console.log(target);    console.log(target.length);  }    });        // 使用刚指定的配置项和数据显示图表。        myChart.setOption(option);</script>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近想做一个基金的标注工具，用echarts开发了前端部分，顺便看到&lt;code&gt;fluid&lt;/code&gt;这个主题可以很方便的在博客中插入echarts图表，做一个记录。&lt;/p&gt;</summary>
    
    
    
    <category term="博客" scheme="https://zekizz.github.io/categories/%E5%8D%9A%E5%AE%A2/"/>
    
    
    <category term="博客" scheme="https://zekizz.github.io/tags/%E5%8D%9A%E5%AE%A2/"/>
    
  </entry>
  
  <entry>
    <title>正则表达式四种预查</title>
    <link href="https://zekizz.github.io/python/regex-preview/"/>
    <id>https://zekizz.github.io/python/regex-preview/</id>
    <published>2021-10-24T13:40:44.000Z</published>
    <updated>2021-10-24T14:39:23.136Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>正则表达式有[正反]向[肯否]定预查四种预查形式，结论是正向只能放置于末尾，反向只能放置于开头。</p><span id="more"></span><p>参考手册：<a href="https://tool.oschina.net/uploads/apidocs/jquery/regexp.html">https://tool.oschina.net/uploads/apidocs/jquery/regexp.html</a></p><p>只看以上手册，比较难搞清楚使用范围和使用姿势，以下对这四种预查分别做单元测试。</p><h2 id="1-正向肯定"><a href="#1-正向肯定" class="headerlink" title="1. 正向肯定"></a>1. 正向肯定</h2><p>测试用例</p><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">setUp</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-literal">None</span>:  self.cases = [    <span class="hljs-string">&#x27;123&#x27;</span>,    <span class="hljs-string">&#x27;a123&#x27;</span>,    <span class="hljs-string">&#x27;abc123&#x27;</span>,    <span class="hljs-string">&#x27;ad123&#x27;</span>,    <span class="hljs-string">&#x27;ac123&#x27;</span>,    <span class="hljs-string">&#x27;ae123&#x27;</span>,    <span class="hljs-string">&#x27;abcac123&#x27;</span>,    <span class="hljs-string">&#x27;abcae123&#x27;</span>,    <span class="hljs-string">&#x27;123abc&#x27;</span>,    <span class="hljs-string">&#x27;123ad&#x27;</span>,    <span class="hljs-string">&#x27;123ac&#x27;</span>,    <span class="hljs-string">&#x27;abc&#x27;</span>  ]<span class="hljs-keyword">def</span> <span class="hljs-title function_">test_forward_positive</span>(<span class="hljs-params">self</span>):    <span class="hljs-string">&quot;&quot;&quot;正向肯定预查，在任何匹配pattern的字符串开始处匹配查找字符串。&quot;&quot;&quot;</span>    p1 = <span class="hljs-string">&#x27;(?=abc|ad)123&#x27;</span>  <span class="hljs-comment"># 不能处理正则的最开头</span>    p2 = <span class="hljs-string">&#x27;123(?=abc|ad)&#x27;</span>  <span class="hljs-comment"># 只能处于末尾，必须匹配预查的字符，只是不捕获，</span>    p3 = <span class="hljs-string">&#x27;(123)(abc|ad)&#x27;</span>  <span class="hljs-comment"># 取group1后等价于p2</span>    p4 = <span class="hljs-string">&#x27;a(?=bc|d)123&#x27;</span>  <span class="hljs-comment"># 不能处于正则的中间</span>    <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> self.cases:      <span class="hljs-built_in">print</span>(c)      <span class="hljs-built_in">print</span>(p1, re.search(p1, c))      <span class="hljs-built_in">print</span>(p2, re.search(p2, c))      <span class="hljs-built_in">print</span>(p3, re.search(p3, c))      <span class="hljs-built_in">print</span>(p4, re.search(p4, c))      <span class="hljs-built_in">print</span>()</code></pre><p>输出为如下，可以看到只有放于最后的才有效，放于最前面的<code>p1</code>捕获不了<code>abc123</code>，放置于中间的<code>p4</code>捕获不了<code>abc123</code>和<code>ae</code>123。而且正确放置在最后的<code>p2</code>，完全等价于<code>p3</code>（对<code>p3</code>的匹配取group1），无法捕获<code>123</code>，不实用，不如直接用<code>p3</code>。</p><pre><code class="hljs coq"><span class="hljs-number">123</span>(?=abc|<span class="hljs-type">ad</span>)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?=abc|<span class="hljs-type">ad</span>) None(<span class="hljs-number">123</span>)(abc|<span class="hljs-type">ad</span>) Nonea(?=bc|<span class="hljs-type">d</span>)<span class="hljs-number">123</span> Nonea123(?=abc|<span class="hljs-type">ad</span>)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?=abc|<span class="hljs-type">ad</span>) None(<span class="hljs-number">123</span>)(abc|<span class="hljs-type">ad</span>) Nonea(?=bc|<span class="hljs-type">d</span>)<span class="hljs-number">123</span> Noneabc123(?=abc|<span class="hljs-type">ad</span>)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?=abc|<span class="hljs-type">ad</span>) None(<span class="hljs-number">123</span>)(abc|<span class="hljs-type">ad</span>) Nonea(?=bc|<span class="hljs-type">d</span>)<span class="hljs-number">123</span> Nonead123(?=abc|<span class="hljs-type">ad</span>)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?=abc|<span class="hljs-type">ad</span>) None(<span class="hljs-number">123</span>)(abc|<span class="hljs-type">ad</span>) Nonea(?=bc|<span class="hljs-type">d</span>)<span class="hljs-number">123</span> Noneac123(?=abc|<span class="hljs-type">ad</span>)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?=abc|<span class="hljs-type">ad</span>) None(<span class="hljs-number">123</span>)(abc|<span class="hljs-type">ad</span>) Nonea(?=bc|<span class="hljs-type">d</span>)<span class="hljs-number">123</span> Noneae123(?=abc|<span class="hljs-type">ad</span>)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?=abc|<span class="hljs-type">ad</span>) None(<span class="hljs-number">123</span>)(abc|<span class="hljs-type">ad</span>) Nonea(?=bc|<span class="hljs-type">d</span>)<span class="hljs-number">123</span> Noneabcac123(?=abc|<span class="hljs-type">ad</span>)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?=abc|<span class="hljs-type">ad</span>) None(<span class="hljs-number">123</span>)(abc|<span class="hljs-type">ad</span>) Nonea(?=bc|<span class="hljs-type">d</span>)<span class="hljs-number">123</span> Noneabcae123(?=abc|<span class="hljs-type">ad</span>)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?=abc|<span class="hljs-type">ad</span>) None(<span class="hljs-number">123</span>)(abc|<span class="hljs-type">ad</span>) Nonea(?=bc|<span class="hljs-type">d</span>)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>abc(?=abc|<span class="hljs-type">ad</span>)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?=abc|<span class="hljs-type">ad</span>) &lt;re.Match object; span=(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>), <span class="hljs-keyword">match</span>=&#x27;<span class="hljs-number">123</span>&#x27;&gt;(<span class="hljs-number">123</span>)(abc|<span class="hljs-type">ad</span>) &lt;re.Match object; span=(<span class="hljs-number">0</span>, <span class="hljs-number">6</span>), <span class="hljs-keyword">match</span>=&#x27;<span class="hljs-number">123</span>abc&#x27;&gt;a(?=bc|<span class="hljs-type">d</span>)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>ad(?=abc|<span class="hljs-type">ad</span>)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?=abc|<span class="hljs-type">ad</span>) &lt;re.Match object; span=(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>), <span class="hljs-keyword">match</span>=&#x27;<span class="hljs-number">123</span>&#x27;&gt;(<span class="hljs-number">123</span>)(abc|<span class="hljs-type">ad</span>) &lt;re.Match object; span=(<span class="hljs-number">0</span>, <span class="hljs-number">5</span>), <span class="hljs-keyword">match</span>=&#x27;<span class="hljs-number">123</span>ad&#x27;&gt;a(?=bc|<span class="hljs-type">d</span>)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>ac(?=abc|<span class="hljs-type">ad</span>)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?=abc|<span class="hljs-type">ad</span>) None(<span class="hljs-number">123</span>)(abc|<span class="hljs-type">ad</span>) Nonea(?=bc|<span class="hljs-type">d</span>)<span class="hljs-number">123</span> Noneabc(?=abc|<span class="hljs-type">ad</span>)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?=abc|<span class="hljs-type">ad</span>) None(<span class="hljs-number">123</span>)(abc|<span class="hljs-type">ad</span>) Nonea(?=bc|<span class="hljs-type">d</span>)<span class="hljs-number">123</span> None</code></pre><h2 id="2-正向否定"><a href="#2-正向否定" class="headerlink" title="2. 正向否定"></a>2. 正向否定</h2><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">test_forward_negative</span>(<span class="hljs-params">self</span>):    <span class="hljs-string">&quot;&quot;&quot;正向否定预查，在任何不匹配pattern的字符串开始处匹配查找字符串。&quot;&quot;&quot;</span>    p1 = <span class="hljs-string">&#x27;(?!abc|ad)123&#x27;</span>  <span class="hljs-comment"># 放在最前面不起作用</span>    p2 = <span class="hljs-string">&#x27;123(?!abc|ad)&#x27;</span>  <span class="hljs-comment"># 只能放置于最后，后面能为空</span>    p3 = <span class="hljs-string">&#x27;a(?!bc|d)123&#x27;</span>  <span class="hljs-comment"># 无效，只能匹配a123</span>    p4 = <span class="hljs-string">&#x27;a(?!b|d)123&#x27;</span>  <span class="hljs-comment"># 无效</span>    <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> self.cases:        <span class="hljs-built_in">print</span>(c)        <span class="hljs-built_in">print</span>(p1, re.search(p1, c))        <span class="hljs-built_in">print</span>(p2, re.search(p2, c))        <span class="hljs-built_in">print</span>(p3, re.search(p3, c))        <span class="hljs-built_in">print</span>(p4, re.search(p4, c))        <span class="hljs-built_in">print</span>()</code></pre><p>放在最前面的<code>p1</code>捕获了<code>abc123</code>不符合预期，放在中间的<code>p3</code>和<code>p4</code>写了等于没写，只有放在最后的p2正确匹配了，并且后面可以为空（匹配了<code>123</code>）。</p><pre><code class="hljs erlang-repl"><span class="hljs-number">123</span>(?!abc|ad)<span class="hljs-number">123</span> &lt;re.Match object; span=(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;<span class="hljs-number">123</span>(?!abc|ad) &lt;re.Match object; span=(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;a(?!bc|d)<span class="hljs-number">123</span> Nonea(?!b|d)<span class="hljs-number">123</span> Nonea123(?!abc|ad)<span class="hljs-number">123</span> &lt;re.Match object; span=(<span class="hljs-number">1</span>, <span class="hljs-number">4</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;<span class="hljs-number">123</span>(?!abc|ad) &lt;re.Match object; span=(<span class="hljs-number">1</span>, <span class="hljs-number">4</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;a(?!bc|d)<span class="hljs-number">123</span> &lt;re.Match object; span=(<span class="hljs-number">0</span>, <span class="hljs-number">4</span>), match=<span class="hljs-string">&#x27;a123&#x27;</span>&gt;a(?!b|d)<span class="hljs-number">123</span> &lt;re.Match object; span=(<span class="hljs-number">0</span>, <span class="hljs-number">4</span>), match=<span class="hljs-string">&#x27;a123&#x27;</span>&gt;abc123(?!abc|ad)<span class="hljs-number">123</span> &lt;re.Match object; span=(<span class="hljs-number">3</span>, <span class="hljs-number">6</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;<span class="hljs-number">123</span>(?!abc|ad) &lt;re.Match object; span=(<span class="hljs-number">3</span>, <span class="hljs-number">6</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;a(?!bc|d)<span class="hljs-number">123</span> Nonea(?!b|d)<span class="hljs-number">123</span> Nonead123(?!abc|ad)<span class="hljs-number">123</span> &lt;re.Match object; span=(<span class="hljs-number">2</span>, <span class="hljs-number">5</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;<span class="hljs-number">123</span>(?!abc|ad) &lt;re.Match object; span=(<span class="hljs-number">2</span>, <span class="hljs-number">5</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;a(?!bc|d)<span class="hljs-number">123</span> Nonea(?!b|d)<span class="hljs-number">123</span> Noneac123(?!abc|ad)<span class="hljs-number">123</span> &lt;re.Match object; span=(<span class="hljs-number">2</span>, <span class="hljs-number">5</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;<span class="hljs-number">123</span>(?!abc|ad) &lt;re.Match object; span=(<span class="hljs-number">2</span>, <span class="hljs-number">5</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;a(?!bc|d)<span class="hljs-number">123</span> Nonea(?!b|d)<span class="hljs-number">123</span> Noneae123(?!abc|ad)<span class="hljs-number">123</span> &lt;re.Match object; span=(<span class="hljs-number">2</span>, <span class="hljs-number">5</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;<span class="hljs-number">123</span>(?!abc|ad) &lt;re.Match object; span=(<span class="hljs-number">2</span>, <span class="hljs-number">5</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;a(?!bc|d)<span class="hljs-number">123</span> Nonea(?!b|d)<span class="hljs-number">123</span> Noneabcac123(?!abc|ad)<span class="hljs-number">123</span> &lt;re.Match object; span=(<span class="hljs-number">5</span>, <span class="hljs-number">8</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;<span class="hljs-number">123</span>(?!abc|ad) &lt;re.Match object; span=(<span class="hljs-number">5</span>, <span class="hljs-number">8</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;a(?!bc|d)<span class="hljs-number">123</span> Nonea(?!b|d)<span class="hljs-number">123</span> Noneabcae123(?!abc|ad)<span class="hljs-number">123</span> &lt;re.Match object; span=(<span class="hljs-number">5</span>, <span class="hljs-number">8</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;<span class="hljs-number">123</span>(?!abc|ad) &lt;re.Match object; span=(<span class="hljs-number">5</span>, <span class="hljs-number">8</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;a(?!bc|d)<span class="hljs-number">123</span> Nonea(?!b|d)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>abc(?!abc|ad)<span class="hljs-number">123</span> &lt;re.Match object; span=(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;<span class="hljs-number">123</span>(?!abc|ad) Nonea(?!bc|d)<span class="hljs-number">123</span> Nonea(?!b|d)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>ad(?!abc|ad)<span class="hljs-number">123</span> &lt;re.Match object; span=(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;<span class="hljs-number">123</span>(?!abc|ad) Nonea(?!bc|d)<span class="hljs-number">123</span> Nonea(?!b|d)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>ac(?!abc|ad)<span class="hljs-number">123</span> &lt;re.Match object; span=(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;<span class="hljs-number">123</span>(?!abc|ad) &lt;re.Match object; span=(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>), match=<span class="hljs-string">&#x27;123&#x27;</span>&gt;a(?!bc|d)<span class="hljs-number">123</span> Nonea(?!b|d)<span class="hljs-number">123</span> Noneabc(?!abc|ad)<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?!abc|ad) Nonea(?!bc|d)<span class="hljs-number">123</span> Nonea(?!b|d)<span class="hljs-number">123</span> None</code></pre><h2 id="3-反向肯定"><a href="#3-反向肯定" class="headerlink" title="3. 反向肯定"></a>3. 反向肯定</h2><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">test_backward_positive</span>(<span class="hljs-params">self</span>):    <span class="hljs-string">&quot;&quot;&quot;反向肯定&quot;&quot;&quot;</span>    <span class="hljs-comment"># p1 = &#x27;(?&lt;=abc|ad)123&#x27;  # 报错, re.error: look-behind requires fixed-width pattern</span>    p1 = <span class="hljs-string">&#x27;((?&lt;=abc)|(?&lt;=ad|ac))123&#x27;</span>  <span class="hljs-comment"># 只能处于最前面，等价于(abc|ad|ac)123</span>    p2 = <span class="hljs-string">&#x27;123((?&lt;=abc)|(?&lt;=ad))&#x27;</span>  <span class="hljs-comment"># 无效</span>    p3 = <span class="hljs-string">&#x27;a((?&lt;=bc)|(?&lt;=d))123&#x27;</span>  <span class="hljs-comment"># 错误用法</span>    p4 = <span class="hljs-string">&#x27;123(?&lt;=abc)&#x27;</span>  <span class="hljs-comment"># 无效</span>    <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> self.cases:        <span class="hljs-built_in">print</span>(c)        <span class="hljs-built_in">print</span>(p1, re.search(p1, c))        <span class="hljs-built_in">print</span>(p2, re.search(p2, c))        <span class="hljs-built_in">print</span>(p3, re.search(p3, c))        <span class="hljs-built_in">print</span>(p4, re.search(p4, c))        <span class="hljs-built_in">print</span>()</code></pre><p>反向的时候需要注意，如果预查多个，不能之前那样写<code>(?&lt;=abc|ad)</code>，同一个预查里的长度必须是相等的。若想处理多个，得写过个预查<code>((?&lt;=abc)|(?&lt;=ad|ac))</code>。</p><p>此外，反向只能放置在最前面，放置在中间完全不符合预期，放在后面也未生效。但正确的写法<code>p1</code>也等价于<code>(abc|ad|ac)123</code>，用预查写起来还麻烦，不如直接group筛选。</p><pre><code class="hljs coq"><span class="hljs-number">123</span>((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>|<span class="hljs-type">ac</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>)) Nonea((?&lt;=bc)|<span class="hljs-type">(?&lt;=d</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?&lt;=abc) Nonea123((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>|<span class="hljs-type">ac</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>)) Nonea((?&lt;=bc)|<span class="hljs-type">(?&lt;=d</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?&lt;=abc) Noneabc123((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>|<span class="hljs-type">ac</span>))<span class="hljs-number">123</span> &lt;re.Match object; span=(<span class="hljs-number">3</span>, <span class="hljs-number">6</span>), <span class="hljs-keyword">match</span>=&#x27;<span class="hljs-number">123</span>&#x27;&gt;<span class="hljs-number">123</span>((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>)) Nonea((?&lt;=bc)|<span class="hljs-type">(?&lt;=d</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?&lt;=abc) Nonead123((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>|<span class="hljs-type">ac</span>))<span class="hljs-number">123</span> &lt;re.Match object; span=(<span class="hljs-number">2</span>, <span class="hljs-number">5</span>), <span class="hljs-keyword">match</span>=&#x27;<span class="hljs-number">123</span>&#x27;&gt;<span class="hljs-number">123</span>((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>)) Nonea((?&lt;=bc)|<span class="hljs-type">(?&lt;=d</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?&lt;=abc) Noneac123((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>|<span class="hljs-type">ac</span>))<span class="hljs-number">123</span> &lt;re.Match object; span=(<span class="hljs-number">2</span>, <span class="hljs-number">5</span>), <span class="hljs-keyword">match</span>=&#x27;<span class="hljs-number">123</span>&#x27;&gt;<span class="hljs-number">123</span>((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>)) Nonea((?&lt;=bc)|<span class="hljs-type">(?&lt;=d</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?&lt;=abc) Noneae123((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>|<span class="hljs-type">ac</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>)) Nonea((?&lt;=bc)|<span class="hljs-type">(?&lt;=d</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?&lt;=abc) Noneabcac123((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>|<span class="hljs-type">ac</span>))<span class="hljs-number">123</span> &lt;re.Match object; span=(<span class="hljs-number">5</span>, <span class="hljs-number">8</span>), <span class="hljs-keyword">match</span>=&#x27;<span class="hljs-number">123</span>&#x27;&gt;<span class="hljs-number">123</span>((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>)) Nonea((?&lt;=bc)|<span class="hljs-type">(?&lt;=d</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?&lt;=abc) Noneabcae123((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>|<span class="hljs-type">ac</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>)) Nonea((?&lt;=bc)|<span class="hljs-type">(?&lt;=d</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?&lt;=abc) None<span class="hljs-number">123</span>abc((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>|<span class="hljs-type">ac</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>)) Nonea((?&lt;=bc)|<span class="hljs-type">(?&lt;=d</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?&lt;=abc) None<span class="hljs-number">123</span>ad((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>|<span class="hljs-type">ac</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>)) Nonea((?&lt;=bc)|<span class="hljs-type">(?&lt;=d</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?&lt;=abc) None<span class="hljs-number">123</span>ac((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>|<span class="hljs-type">ac</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>)) Nonea((?&lt;=bc)|<span class="hljs-type">(?&lt;=d</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?&lt;=abc) Noneabc((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>|<span class="hljs-type">ac</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>((?&lt;=abc)|<span class="hljs-type">(?&lt;=ad</span>)) Nonea((?&lt;=bc)|<span class="hljs-type">(?&lt;=d</span>))<span class="hljs-number">123</span> None<span class="hljs-number">123</span>(?&lt;=abc) None</code></pre><h2 id="4-反向否定"><a href="#4-反向否定" class="headerlink" title="4. 反向否定"></a>4. 反向否定</h2><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">test_backward_negative</span>(<span class="hljs-params">self</span>):    <span class="hljs-string">&quot;&quot;&quot;反向否定&quot;&quot;&quot;</span>    p1 = <span class="hljs-string">&#x27;((?&lt;!abc)|(?&lt;!ad|ac))123&#x27;</span>  <span class="hljs-comment"># 错误写法，只要满足不等于其中一个即可</span>    p2 = <span class="hljs-string">&#x27;123((?&lt;!abc)|(?&lt;!ad))&#x27;</span>  <span class="hljs-comment">#</span>    p3 = <span class="hljs-string">&#x27;a((?&lt;!bc)|(?&lt;!d))123&#x27;</span>  <span class="hljs-comment"># 错误写法，只能匹配a123</span>    p4 = <span class="hljs-string">&#x27;123(?&lt;!abc)&#x27;</span>  <span class="hljs-comment"># 无效</span>    p5 = <span class="hljs-string">&#x27;(?&lt;!abc)123&#x27;</span>  <span class="hljs-comment">#</span>    p6 = <span class="hljs-string">&#x27;(?&lt;!abc)(?&lt;!ad|ac)123&#x27;</span>  <span class="hljs-comment"># 正确写法，必须满足全部不等于</span>    <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> self.cases:        <span class="hljs-built_in">print</span>(c)        <span class="hljs-built_in">print</span>(p1, re.search(p1, c))        <span class="hljs-built_in">print</span>(p2, re.search(p2, c))        <span class="hljs-built_in">print</span>(p3, re.search(p3, c))        <span class="hljs-built_in">print</span>(p4, re.search(p4, c))        <span class="hljs-built_in">print</span>(p5, re.search(p5, c))        <span class="hljs-built_in">print</span>(p6, re.search(p6, c))        <span class="hljs-built_in">print</span>()</code></pre><p>同上，只有放置在最前面的是正确的，这里需要注意的事<code>p1</code>和<code>p6</code>。<code>p1</code>只要不等于其一都可匹配，比如<code>abc123</code>不匹配<code>ad|ac</code>，这明显不符合预期。这个时候需要取并集，只需要拆开写过个就好，顺序无关，如见<code>p6</code>。</p><pre><code class="hljs xml">123((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>))123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>123((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>)) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>a((?<span class="hljs-meta">&lt;!bc)|(?&lt;!d))123 <span class="hljs-keyword">None</span></span><span class="hljs-meta">123(?&lt;!<span class="hljs-keyword">abc</span>) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>)123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>a123((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>))123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(1, 4), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>123((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>)) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(1, 4), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>a((?<span class="hljs-meta">&lt;!bc)|(?&lt;!d))123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 4), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;a123&#x27;</span>&gt;</span>123(?<span class="hljs-meta">&lt;!abc) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(1, 4), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(1, 4), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>)123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(1, 4), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>abc123((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>))123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(3, 6), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>123((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>)) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(3, 6), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>a((?<span class="hljs-meta">&lt;!bc)|(?&lt;!d))123 <span class="hljs-keyword">None</span></span><span class="hljs-meta">123(?&lt;!<span class="hljs-keyword">abc</span>) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(3, 6), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)123 <span class="hljs-keyword">None</span></span><span class="hljs-meta">(?&lt;!<span class="hljs-keyword">abc</span>)(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>)123 <span class="hljs-keyword">None</span></span><span class="hljs-meta"></span><span class="hljs-meta"><span class="hljs-keyword">ad123</span></span><span class="hljs-meta">((?&lt;!<span class="hljs-keyword">abc</span>)|(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>))123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(2, 5), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>123((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>)) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(2, 5), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>a((?<span class="hljs-meta">&lt;!bc)|(?&lt;!d))123 <span class="hljs-keyword">None</span></span><span class="hljs-meta">123(?&lt;!<span class="hljs-keyword">abc</span>) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(2, 5), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(2, 5), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>)123 <span class="hljs-keyword">None</span></span><span class="hljs-meta"></span><span class="hljs-meta"><span class="hljs-keyword">ac123</span></span><span class="hljs-meta">((?&lt;!<span class="hljs-keyword">abc</span>)|(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>))123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(2, 5), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>123((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>)) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(2, 5), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>a((?<span class="hljs-meta">&lt;!bc)|(?&lt;!d))123 <span class="hljs-keyword">None</span></span><span class="hljs-meta">123(?&lt;!<span class="hljs-keyword">abc</span>) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(2, 5), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(2, 5), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>)123 <span class="hljs-keyword">None</span></span><span class="hljs-meta"></span><span class="hljs-meta"><span class="hljs-keyword">ae123</span></span><span class="hljs-meta">((?&lt;!<span class="hljs-keyword">abc</span>)|(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>))123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(2, 5), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>123((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>)) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(2, 5), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>a((?<span class="hljs-meta">&lt;!bc)|(?&lt;!d))123 <span class="hljs-keyword">None</span></span><span class="hljs-meta">123(?&lt;!<span class="hljs-keyword">abc</span>) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(2, 5), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(2, 5), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>)123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(2, 5), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>abcac123((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>))123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(5, 8), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>123((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>)) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(5, 8), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>a((?<span class="hljs-meta">&lt;!bc)|(?&lt;!d))123 <span class="hljs-keyword">None</span></span><span class="hljs-meta">123(?&lt;!<span class="hljs-keyword">abc</span>) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(5, 8), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(5, 8), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>)123 <span class="hljs-keyword">None</span></span><span class="hljs-meta"></span><span class="hljs-meta"><span class="hljs-keyword">abcae123</span></span><span class="hljs-meta">((?&lt;!<span class="hljs-keyword">abc</span>)|(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>))123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(5, 8), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>123((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>)) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(5, 8), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>a((?<span class="hljs-meta">&lt;!bc)|(?&lt;!d))123 <span class="hljs-keyword">None</span></span><span class="hljs-meta">123(?&lt;!<span class="hljs-keyword">abc</span>) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(5, 8), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(5, 8), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>)123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(5, 8), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>123abc((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>))123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>123((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>)) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>a((?<span class="hljs-meta">&lt;!bc)|(?&lt;!d))123 <span class="hljs-keyword">None</span></span><span class="hljs-meta">123(?&lt;!<span class="hljs-keyword">abc</span>) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>)123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>123ad((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>))123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>123((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>)) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>a((?<span class="hljs-meta">&lt;!bc)|(?&lt;!d))123 <span class="hljs-keyword">None</span></span><span class="hljs-meta">123(?&lt;!<span class="hljs-keyword">abc</span>) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>)123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>123ac((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>))123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>123((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>)) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>a((?<span class="hljs-meta">&lt;!bc)|(?&lt;!d))123 <span class="hljs-keyword">None</span></span><span class="hljs-meta">123(?&lt;!<span class="hljs-keyword">abc</span>) &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>(?<span class="hljs-meta">&lt;!abc)(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>)123 &lt;re.Match <span class="hljs-keyword">object</span>; <span class="hljs-keyword">span</span>=(0, 3), <span class="hljs-keyword">match</span>=<span class="hljs-string">&#x27;123&#x27;</span>&gt;</span>abc((?<span class="hljs-meta">&lt;!abc)|(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>))123 <span class="hljs-keyword">None</span></span><span class="hljs-meta">123((?&lt;!<span class="hljs-keyword">abc</span>)|(?&lt;!<span class="hljs-keyword">ad</span>)) <span class="hljs-keyword">None</span></span><span class="hljs-meta">a((?&lt;!<span class="hljs-keyword">bc</span>)|(?&lt;!d))123 <span class="hljs-keyword">None</span></span><span class="hljs-meta">123(?&lt;!<span class="hljs-keyword">abc</span>) <span class="hljs-keyword">None</span></span><span class="hljs-meta">(?&lt;!<span class="hljs-keyword">abc</span>)123 <span class="hljs-keyword">None</span></span><span class="hljs-meta">(?&lt;!<span class="hljs-keyword">abc</span>)(?&lt;!<span class="hljs-keyword">ad</span>|<span class="hljs-keyword">ac</span>)123 <span class="hljs-keyword">None</span></span></code></pre>]]></content>
    
    
    <summary type="html">&lt;p&gt;正则表达式有[正反]向[肯否]定预查四种预查形式，结论是正向只能放置于末尾，反向只能放置于开头。&lt;/p&gt;</summary>
    
    
    
    <category term="python" scheme="https://zekizz.github.io/categories/python/"/>
    
    
    <category term="python" scheme="https://zekizz.github.io/tags/python/"/>
    
    <category term="正则表达式" scheme="https://zekizz.github.io/tags/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title>pyspark经验小结</title>
    <link href="https://zekizz.github.io/da-shu-ju/pyspark-notes/"/>
    <id>https://zekizz.github.io/da-shu-ju/pyspark-notes/</id>
    <published>2021-08-14T15:49:08.000Z</published>
    <updated>2021-08-15T03:34:13.599Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>记录spark使用过程中的一些常用点，不定期更新~</p><span id="more"></span><h2 id="pyspark环境打包"><a href="#pyspark环境打包" class="headerlink" title="pyspark环境打包"></a>pyspark环境打包</h2><p>先本地构建虚拟环境，打包后提交到集群，已conda为例</p><pre><code class="hljs shell">conda create -n py37 python=3.7 pipsource activate py37pip install -r requirements.txt<span class="hljs-meta prompt_"></span><span class="hljs-meta prompt_"># </span><span class="language-bash">install conda pack</span>pip install conda-pack<span class="hljs-meta prompt_"></span><span class="hljs-meta prompt_"># </span><span class="language-bash">Pack environment my_env into out_name.tar.gz</span>conda pack -n py37 -o py37_env.tar.gz</code></pre><p>打包完成后，可重现环境检查下</p><pre><code class="hljs shell">mkdir -p test_envtar -xzf py37_env.tar.gz -C test_envsource py37_env/bin/activate<span class="hljs-meta prompt_"># </span><span class="language-bash">查看安装包是否一致</span>pip list</code></pre><p>这里插一个，如果想看conda环境安装的包，路径为<code>~conda_path/envs/py37/lib/python3.7/site-packages</code></p><p>采用spark-submit</p><pre><code class="hljs shell">/opt/tiger/spark_deploy/spark-stable/bin/spark-submit \    --master yarn \    --deploy-mode cluster \    --queue $&#123;queue_name&#125; \    --conf spark.driver.memory=4g \    --conf spark.dynamicAllocation.enabled=true \    --conf spark.dynamicAllocation.minExecutors=10 \    --conf spark.dynamicAllocation.initialExecutors=10 \    --conf spark.dynamicAllocation.maxExecutors=20 \    --conf spark.executor.memory=4g \    --conf spark.executor.cores=4 \    --name spark_test_env \    --conf spark.hadoop.yarn.cluster.name=$&#123;cluster_name&#125; \    --conf spark.pyspark.python=./py37_env/bin/python3 \    --conf spark.pyspark.driver.python=./py37_env/bin/python3 \    --archives hdfs_path/py37_env.tar.gz#py37_env    main.py</code></pre><p>参考</p><ul><li><p><a href="https://www.cnblogs.com/qi-yuan-008/p/12199152.html">spark-submit提交任务到集群，分发虚拟环境和第三方包</a></p></li><li><p><a href="https://blog.csdn.net/sgyuanshi/article/details/114648247">pyspark打包依赖包&amp;使用python虚拟环境</a></p></li></ul><h2 id="工程打包"><a href="#工程打包" class="headerlink" title="工程打包"></a>工程打包</h2><p>pyspark主程序依赖的文件比较少的时候，可以通过<code>--py-files</code>逐个添加，但当依赖的比较多，甚至是整个工程，有比较复杂的目录结构的时候，就需要打包了，打包成.zip,.egg等格式也可通过<code>--py-files</code>传入，这些文件会加入PYTHONPATH中。</p><p>这里需要注意的是，打成zip包的时候，必须是一级目录，这里给出一种正确的打包方式</p><pre><code class="hljs shell">cd project_homezip -r ../project_home.zip .</code></pre><h2 id="常用参数"><a href="#常用参数" class="headerlink" title="常用参数"></a>常用参数</h2><h3 id="Spark-Conf"><a href="#Spark-Conf" class="headerlink" title="Spark Conf"></a>Spark Conf</h3><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span>  <span class="hljs-attr">&quot;spark.dynamicAllocation.minExecutors&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;200&quot;</span><span class="hljs-punctuation">,</span>  <span class="hljs-attr">&quot;spark.dynamicAllocation.initialExecutors&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;200&quot;</span><span class="hljs-punctuation">,</span>  <span class="hljs-attr">&quot;spark.dynamicAllocation.maxExecutors&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;500&quot;</span><span class="hljs-punctuation">,</span>  <span class="hljs-attr">&quot;spark.driver.memory&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2g&quot;</span><span class="hljs-punctuation">,</span>  <span class="hljs-attr">&quot;spark.executor.memory&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;4g&quot;</span><span class="hljs-punctuation">,</span>  <span class="hljs-attr">&quot;spark.driver.cores&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;4&quot;</span><span class="hljs-punctuation">,</span>  <span class="hljs-attr">&quot;spark.yarn.executor.memoryOverhead&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;4g&quot;</span><span class="hljs-punctuation">,</span>  <span class="hljs-attr">&quot;spark.sql.adaptive.enabled&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;true&quot;</span><span class="hljs-punctuation">,</span>  <span class="hljs-attr">&quot;spark.sql.adaptive.skewedJoin.enabled&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;true&quot;</span><span class="hljs-punctuation">,</span>  <span class="hljs-attr">&quot;spark.sql.adaptive.skewedJoinWithAgg.enabled&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;true&quot;</span><span class="hljs-punctuation">,</span>  <span class="hljs-attr">&quot;spark.sql.adaptive.multipleSkewedJoin.enabled&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;true&quot;</span><span class="hljs-punctuation">&#125;</span></code></pre><p>其中倒数三个参数是Spark AE SkewedJoin优化的一般参数，应对数据偏斜（处理偏斜优先分析key的取值是否均匀，比如空值等异常值需要被提前先移除掉）。</p><h3 id="依赖资源"><a href="#依赖资源" class="headerlink" title="依赖资源"></a>依赖资源</h3><p>资源大多通过以下几种方式引入</p><ul><li>files: 逗号分隔的文件，这些文件放在每个executor的工作目录下面</li><li>py-files: 逗号分隔的”.zip”,”.egg”或者”.py”文件，这些文件放在python app的<code>PYTHONPATH</code>下面</li><li>jars: 逗号分隔的本地jar包，包含在driver和executor的classpath下</li><li>archives: 指定压缩文件地址，压缩文件被分发到 executor 上，并且解压，解压路径可通过<code>#</code>指定</li></ul><p>对于以上几个的区别主要是是否会被加入<code>classpath</code>和<code>pythonpath</code>，一般来说数据文件通过<code>--files</code>和<code>--archives</code>来添加，代码文件通过<code>--py-files</code>来添加，后者能被直接import。</p><p>详情参见<a href="https://stackoverflow.com/questions/38066318/whats-the-difference-between-archives-files-py-files-in-pyspark-job-argum">What’s the difference between —archives, —files, py-files in pyspark job arguments</a></p><h2 id="跑模型"><a href="#跑模型" class="headerlink" title="跑模型"></a>跑模型</h2><p>通过spark跑深度学习模型时（当然是cpu啦），要想充分利用高并发并且减少模型加载时间的影响，可以使用<code>mapPartitions</code>。</p><p>先看官方给的例子</p><pre><code class="hljs shell"><span class="hljs-meta prompt_">&gt;</span><span class="language-bash">&gt;&gt; rdd = sc.parallelize([1, 2, 3, 4], 2)</span><span class="hljs-meta prompt_">&gt;</span><span class="language-bash">&gt;&gt; def f(iterator): yield <span class="hljs-built_in">sum</span>(iterator)</span><span class="hljs-meta prompt_">&gt;</span><span class="language-bash">&gt;&gt; rdd.mapPartitions(f).collect()</span>[3, 7]</code></pre><p>传入和穿出都是迭代器，一条一条的计算。但是如果我们想一个batch一个batch的预测，显然有三个做法</p><ul><li>还是迭代器一条一条，攒一批过模型，再把结果迭代器传出</li><li>利用处理流数据的dataset，如pytorch的<code>IterableDataset</code></li><li>直接把一个partition全部转成list来处理，<strong>需要注意是否会爆内存</strong></li></ul><p>分别给出前两种写法的例子</p><h3 id="方式一：攒数据"><a href="#方式一：攒数据" class="headerlink" title="方式一：攒数据"></a>方式一：攒数据</h3><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">infer_batch_by_partition</span>(<span class="hljs-params">partition</span>):    batch_size = <span class="hljs-number">16</span>    count = <span class="hljs-number">0</span>    row = partition    data_batch = []    <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:        <span class="hljs-keyword">try</span>:            content = <span class="hljs-built_in">next</span>(row)            <span class="hljs-keyword">if</span> count &lt; batch_size:                count += <span class="hljs-number">1</span>                data_batch.append(content)            <span class="hljs-keyword">if</span> count &gt;= batch_size:                count = <span class="hljs-number">0</span>                <span class="hljs-comment"># 过模型</span>                predicts = model_predict(data_batch)                <span class="hljs-keyword">for</span> p <span class="hljs-keyword">in</span> predicts:                    <span class="hljs-keyword">yield</span> p                data_batch = []        <span class="hljs-keyword">except</span> StopIteration:            <span class="hljs-comment"># 最后一个batch，数据量不一定是batch_size</span>            <span class="hljs-keyword">if</span> data_batch:                predicts = model_predict(data_batch)                <span class="hljs-keyword">for</span> p <span class="hljs-keyword">in</span> predicts:                    <span class="hljs-keyword">yield</span> p                    rdd_pair_pred = rdd_pair.repartition(<span class="hljs-number">2000</span>).mapPartitions(infer_batch_by_partition)</code></pre><h3 id="方式二：IterableDataset"><a href="#方式二：IterableDataset" class="headerlink" title="方式二：IterableDataset"></a>方式二：IterableDataset</h3><p>Iterabledataset 的一个例子</p><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyIterableDataset</span>(torch.utils.data.IterableDataset):<span class="hljs-meta">... </span>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, your_args</span>):<span class="hljs-meta">... </span>        <span class="hljs-built_in">super</span>(MyIterableDataset).__init__()<span class="hljs-meta">... </span>        <span class="hljs-comment"># this depends on your dataset, suppose your dataset contains </span><span class="hljs-meta">... </span>        <span class="hljs-comment"># images whose path you save in this list</span><span class="hljs-meta">... </span>        self.dataset = <span class="hljs-comment"># something for load the path of images </span>...<span class="hljs-meta">... </span>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__iter__</span>(<span class="hljs-params">self</span>):<span class="hljs-meta">... </span>        <span class="hljs-keyword">for</span> image_path <span class="hljs-keyword">in</span> self.dataset:<span class="hljs-meta">... </span>            sample, label = read_single_example(image_path) <span class="hljs-comment"># read an individual sample and its label</span><span class="hljs-meta">... </span>            <span class="hljs-keyword">yield</span> sample, label</code></pre><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">seq_padding</span>(<span class="hljs-params">seq, max_len, value=<span class="hljs-number">0</span></span>):    x = [value] * max_len    x[:<span class="hljs-built_in">len</span>(seq)] = seq[:max_len]    <span class="hljs-keyword">return</span> x<span class="hljs-keyword">class</span> <span class="hljs-title class_">DedupePairDatasetV2Iter</span>(<span class="hljs-title class_ inherited__">IterableDataset</span>):    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, text_data_iter, tokenizer, max_len=<span class="hljs-number">128</span>, max_sen=<span class="hljs-number">16</span>, mode=<span class="hljs-string">&#x27;test&#x27;</span></span>):        <span class="hljs-built_in">super</span>(DedupePairDatasetV2Iter, self).__init__()        self.max_len = max_len        self.max_sen = max_sen        self.tokenizer = tokenizer        self.text_data_iter = text_data_iter    <span class="hljs-keyword">def</span> <span class="hljs-title function_">gen_doc_input</span>(<span class="hljs-params">self, doc</span>):        sentences = doc[<span class="hljs-string">&#x27;summary&#x27;</span>]        sentences = sentences[:self.max_sen]        <span class="hljs-comment"># 构造每个句子的bert输入</span>        input_ids_list, token_type_ids_list, attention_mask_list = [], [], []        <span class="hljs-keyword">for</span> sen <span class="hljs-keyword">in</span> sentences:            token_type_ids = [<span class="hljs-number">0</span>] * self.max_len            input_ids, attention_mask = [], []            <span class="hljs-keyword">for</span> ch <span class="hljs-keyword">in</span> sen[:self.max_len - <span class="hljs-number">2</span>]:                token_idx = self.tokenizer.encode(ch)                <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(token_idx) != <span class="hljs-number">3</span>:                    <span class="hljs-comment"># 空格等，转为[UNK]</span>                    input_ids.append(<span class="hljs-number">100</span>)                <span class="hljs-keyword">else</span>:                    input_ids.append(token_idx[<span class="hljs-number">1</span>])                attention_mask.append(<span class="hljs-number">1</span>)            input_ids = [<span class="hljs-number">101</span>] + input_ids + [<span class="hljs-number">102</span>]            attention_mask = [<span class="hljs-number">1</span>] + attention_mask + [<span class="hljs-number">1</span>]            input_ids_list.append(seq_padding(input_ids, self.max_len))            token_type_ids_list.append(seq_padding(token_type_ids, self.max_len))            attention_mask_list.append(seq_padding(attention_mask, self.max_len))        <span class="hljs-comment"># pad doc sentence</span>        <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-built_in">len</span>(input_ids_list), self.max_sen):            input_ids_list.append([<span class="hljs-number">101</span>] + [<span class="hljs-number">0</span>] * (self.max_len - <span class="hljs-number">2</span>) + [<span class="hljs-number">102</span>])            token_type_ids_list.append([<span class="hljs-number">0</span>] * self.max_len)            attention_mask_list.append([<span class="hljs-number">1</span>] * self.max_len)        <span class="hljs-comment"># 增加句子长度，每一个字段分开batch</span>        n_sen = <span class="hljs-built_in">min</span>(<span class="hljs-built_in">len</span>(sentences), self.max_sen)        sen_mask = [<span class="hljs-number">1</span>] * n_sen + [<span class="hljs-number">0</span>] * (self.max_sen - n_sen)        inputs = &#123;            <span class="hljs-string">&#x27;input_ids&#x27;</span>: torch.tensor(input_ids_list, dtype=torch.long),            <span class="hljs-string">&#x27;token_type_ids&#x27;</span>: torch.tensor(token_type_ids_list, dtype=torch.long),            <span class="hljs-string">&#x27;attention_mask&#x27;</span>: torch.tensor(attention_mask_list, dtype=torch.long),            <span class="hljs-string">&#x27;sen_mask&#x27;</span>: torch.tensor(sen_mask, dtype=torch.long)        &#125;        <span class="hljs-keyword">return</span> inputs    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__iter__</span>(<span class="hljs-params">self</span>):        <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> self.text_data_iter:            left = self.gen_doc_input(item[<span class="hljs-string">&#x27;left&#x27;</span>])            right = self.gen_doc_input(item[<span class="hljs-string">&#x27;right&#x27;</span>])            <span class="hljs-keyword">yield</span> left, right, json.dumps(item, ensure_ascii=<span class="hljs-literal">False</span>)<span class="hljs-keyword">def</span> <span class="hljs-title function_">infer_duplicate_pair_iter</span>(<span class="hljs-params">model, </span><span class="hljs-params">                              tokenizer, </span><span class="hljs-params">                              pair_list, </span><span class="hljs-params">                              batch_size=<span class="hljs-number">16</span>, </span><span class="hljs-params">                              max_sen=<span class="hljs-number">16</span>, </span><span class="hljs-params">                              max_seq_len=<span class="hljs-number">128</span>,</span><span class="hljs-params">                              sent_hidden_size=<span class="hljs-number">64</span></span>):    device = torch.device(<span class="hljs-string">&#x27;cpu&#x27;</span>)    <span class="hljs-comment"># test_set = DedupePairDatasetV2(pair_list, tokenizer, mode=&#x27;test&#x27;, max_sen=max_sen, max_len=max_seq_len)</span>    test_set = DedupePairDatasetV2Iter(pair_list, tokenizer, mode=<span class="hljs-string">&#x27;test&#x27;</span>, max_sen=max_sen, max_len=max_seq_len)    test_params = &#123;<span class="hljs-string">&#x27;batch_size&#x27;</span>: batch_size,                   <span class="hljs-string">&#x27;shuffle&#x27;</span>: <span class="hljs-literal">False</span>,                   <span class="hljs-string">&#x27;num_workers&#x27;</span>: <span class="hljs-number">0</span>,                   &#125;    testing_loader = DataLoader(test_set, **test_params)    model.<span class="hljs-built_in">eval</span>()    <span class="hljs-keyword">with</span> torch.no_grad():        <span class="hljs-keyword">for</span> _, inputs <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(testing_loader, <span class="hljs-number">0</span>):            left, right, items = inputs            <span class="hljs-keyword">for</span> k <span class="hljs-keyword">in</span> left:                left[k] = left[k].to(device, dtype=torch.long)            <span class="hljs-keyword">for</span> k <span class="hljs-keyword">in</span> right:                right[k] = right[k].to(device, dtype=torch.long)            logits = model(left, right)            logits = logits.cpu().detach().numpy()            score = softmax(logits, axis=<span class="hljs-number">1</span>).tolist()            <span class="hljs-keyword">for</span> pair, pred <span class="hljs-keyword">in</span> <span class="hljs-built_in">zip</span>(items, score):                pair = json.loads(pair)                pair[<span class="hljs-string">&#x27;left&#x27;</span>].pop(<span class="hljs-string">&#x27;summary&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>)                pair[<span class="hljs-string">&#x27;right&#x27;</span>].pop(<span class="hljs-string">&#x27;summary&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>)                pair[<span class="hljs-string">&#x27;score&#x27;</span>] = pred[<span class="hljs-number">1</span>]                <span class="hljs-keyword">yield</span> json.dumps(pair, ensure_ascii=<span class="hljs-literal">False</span>)<span class="hljs-keyword">def</span> <span class="hljs-title function_">infer_batch_by_partition</span>(<span class="hljs-params">partition</span>):    <span class="hljs-keyword">return</span> infer_duplicate_pair_iter(model_path, tokenizer, partition, batch_size=<span class="hljs-number">8</span>)rdd_pair_pred = rdd_pair.repartition(<span class="hljs-number">2000</span>).mapPartitions(infer_batch_by_partition)</code></pre><p>参考：</p><ul><li><a href="https://cloud.tencent.com/developer/article/1805305">Spark原理 | 关于 mapPartitions 的误区</a></li></ul><h2 id="基本操作"><a href="#基本操作" class="headerlink" title="基本操作"></a>基本操作</h2><p><strong>读取json</strong></p><pre><code class="hljs python"><span class="hljs-keyword">from</span> pyspark.sql <span class="hljs-keyword">import</span> SparkSessionspark = SparkSession.builder.config(<span class="hljs-string">&quot;spark.sql.warehouse.dir&quot;</span>, <span class="hljs-string">&quot;file:///C:/temp&quot;</span>).appName(<span class="hljs-string">&quot;readJSON&quot;</span>).getOrCreate()readJSONDF = spark.read.json(<span class="hljs-string">&#x27;Simple.json&#x27;</span>)readJSONDF.show(truncate=<span class="hljs-literal">False</span>)</code></pre><p>参考：<a href="https://zhuanlan.zhihu.com/p/267353998">用pyspark读取json数据的方法汇总</a></p><p><strong>读取csv</strong></p><pre><code class="hljs python">df = spark.read.csv(hdfs_path, header=<span class="hljs-literal">True</span>, inferSchema=<span class="hljs-string">&quot;true&quot;</span>)df = df.select(<span class="hljs-string">&#x27;doc_id&#x27;</span>, <span class="hljs-string">&#x27;title&#x27;</span>)df = df.withColumnRenamed(<span class="hljs-string">&#x27;title&#x27;</span>, <span class="hljs-string">&#x27;doc_title&#x27;</span>)df = df.withColumn(<span class="hljs-string">&#x27;doc_id&#x27;</span>, df[<span class="hljs-string">&#x27;doc_id&#x27;</span>].cast(StringType()))</code></pre><p><strong>写入hive分区</strong></p><pre><code class="hljs python"><span class="hljs-comment"># 指定分区进行重写，而不会重写整张表</span>df.createOrReplaceTempView(<span class="hljs-string">&#x27;tmp_push&#x27;</span>)query = <span class="hljs-string">&#x27;INSERT OVERWRITE TABLE aaa.bbb PARTITION(date=&quot;$&#123;date&#125;&quot;) SELECT * FROM tmp_push&#x27;</span>spark.sql(query)</code></pre><p><strong>多个rdd的联合</strong></p><pre><code class="hljs python">rdd_list.append(rdd1)rdd_list.append(rdd2)rdd_union = sc.union(rdd_list)</code></pre><p><strong>join</strong></p><p><strong>能用dataframe尽量用dataframe</strong>，有性能优化</p><p><code>left_anti</code>只有dataframe有接口</p><pre><code class="hljs python">df_a_not_in_b = df_a.join(df_b, on=[<span class="hljs-string">&#x27;key&#x27;</span>], how=<span class="hljs-string">&#x27;left_anti&#x27;</span>)</code></pre><p><strong>udf</strong></p><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">make_pair_key</span>(<span class="hljs-params">id1, id2</span>):  id1, id2 = <span class="hljs-built_in">str</span>(id1), <span class="hljs-built_in">str</span>(id2)  <span class="hljs-keyword">if</span> id1 &gt; id2:    id1, id2 = id2, id1  <span class="hljs-keyword">return</span> id1 + <span class="hljs-string">&#x27;-&#x27;</span> + id2udf_make_pair_key = F.udf(make_pair_key, StringType())df = df.withColumn(<span class="hljs-string">&#x27;key&#x27;</span>, udf_make_pair_key(df_hist.id1, df_hist.id2))</code></pre><h2 id="骚操作"><a href="#骚操作" class="headerlink" title="骚操作"></a>骚操作</h2><h4 id="运行过程中安装依赖包"><a href="#运行过程中安装依赖包" class="headerlink" title="运行过程中安装依赖包"></a>运行过程中安装依赖包</h4><p>有一些包的安装比较麻烦，比如spacy的模型<code>zh_core_web_md</code>，正常的安装方式为</p><pre><code class="hljs shell">pip install -U pip setuptools wheelpip install -U spacypython -m spacy download zh_core_web_md</code></pre><p>最后一步我们没有办法写到requirements中，一个骚操作是把模型文件上传，然后在代码中解压，亲测有效</p><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">load_spacy_model</span>(<span class="hljs-params">env=<span class="hljs-string">&#x27;local&#x27;</span></span>):    <span class="hljs-keyword">import</span> zipfile    <span class="hljs-keyword">def</span> <span class="hljs-title function_">unzip_model</span>(<span class="hljs-params">zip_file, out_path=<span class="hljs-string">&#x27;.&#x27;</span></span>):        zFile = zipfile.ZipFile(zip_file, <span class="hljs-string">&quot;r&quot;</span>)        <span class="hljs-keyword">for</span> fileM <span class="hljs-keyword">in</span> zFile.namelist():            zFile.extract(fileM, out_path)        zFile.close()     zip_file = <span class="hljs-string">&#x27;zh_core_web_md-3.0.0.zip&#x27;</span>  out_path = <span class="hljs-string">&#x27;.&#x27;</span>    unzip_model(zip_file, out_path)    NLP = spacy.load(out_path + <span class="hljs-string">&#x27;/zh_core_web_md-3.0.0&#x27;</span>)</code></pre><p>但其实并不需要这么麻烦，更好的做法是直接加载模型</p><pre><code class="hljs python"><span class="hljs-keyword">from</span> pyspark.sql <span class="hljs-keyword">import</span> SparkSession<span class="hljs-keyword">import</span> os<span class="hljs-keyword">import</span> sys<span class="hljs-keyword">import</span> zipfilespark = SparkSession.builder.enableHiveSupport().getOrCreate()<span class="hljs-keyword">def</span> <span class="hljs-title function_">test_load_model</span>():    sys.path.insert(<span class="hljs-number">0</span>, <span class="hljs-string">&#x27;zh_core_web_md-3.0.0.zip&#x27;</span>)  <span class="hljs-comment"># 通过py-files引入，作为一个路径</span>    files = os.listdir(<span class="hljs-string">&#x27;./&#x27;</span>)    <span class="hljs-built_in">print</span>(<span class="hljs-string">&#x27;x&#x27;</span>*<span class="hljs-number">60</span>, sys.stderr)    <span class="hljs-built_in">print</span>(<span class="hljs-string">&#x27;\n&#x27;</span>.join(files), sys.stderr)    <span class="hljs-keyword">import</span> spacy    nlp = spacy.load(<span class="hljs-string">&#x27;zh_core_web_md-3.0.0.zip/zh_core_web_md-3.0.0&#x27;</span>)    text = <span class="hljs-string">&#x27;风寒三友即岁寒三友，指松、竹、梅三种植物。&#x27;</span>    doc = nlp(text)    <span class="hljs-keyword">for</span> token <span class="hljs-keyword">in</span> doc:        <span class="hljs-comment"># info = [token.text, token.lemma_, token.pos_, token.tag_, token.dep_,</span>        <span class="hljs-comment">#             token.shape_, token.is_alpha, token.is_stop]</span>        <span class="hljs-built_in">print</span>(token.text, file=sys.stdout)</code></pre><p><code>zh_core_web_md-3.0.0</code>的文件内容为：</p><pre><code class="hljs shell">accuracy.jsonattribute_rulerconfig.cfgmeta.jsonnerparsersentertaggertok2vectokenizervocab</code></pre>]]></content>
    
    
    <summary type="html">&lt;p&gt;记录spark使用过程中的一些常用点，不定期更新~&lt;/p&gt;</summary>
    
    
    
    <category term="大数据" scheme="https://zekizz.github.io/categories/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    
    
    <category term="pyspark" scheme="https://zekizz.github.io/tags/pyspark/"/>
    
    <category term="spark" scheme="https://zekizz.github.io/tags/spark/"/>
    
    <category term="大数据" scheme="https://zekizz.github.io/tags/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    
  </entry>
  
  <entry>
    <title>pycharm远程连接并运行</title>
    <link href="https://zekizz.github.io/python/pycharm-remote/"/>
    <id>https://zekizz.github.io/python/pycharm-remote/</id>
    <published>2020-02-03T12:25:33.000Z</published>
    <updated>2020-02-04T11:10:06.390Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>使用pycharm远程连接repo并远程运行。</p><span id="more"></span><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><ol><li>本地新建repo（可以先clone远程repo），并用Pycharm（pycharm-professional-2019.3.2）打开</li><li>选择pycharm中，<code>Tools &gt; Deployment &gt; Configuration</code>，点击左上角的<code>+</code>，添加配置</li><li>配置信息说明<ol><li>Connection<ol><li>Type: SFTP</li><li>Host: 远程ip地址</li><li>User name: 远程机器用户名</li><li>Password: 远程机器密码</li><li>Root path: 远程根目录，需要自己选择（更新：最好选择根目录，防止后面mapping出问题）</li></ol></li><li>Mappings<ol><li>Local path: 本地repo地址</li><li>Deployment path: 远程repo地址</li><li>Web path: 暂时不用设置，貌似 Web 相关的程序会用到</li></ol></li><li>Excluded Paths: 可以设置一些不想同步的目录，例如软件的配置文件目录等，可忽略</li></ol></li><li>以上弄完，点击确认，接着配置解释器<ol><li>打开<code>Pycharm &gt; Preferences &gt; Project &gt; Project Interpreter</code></li><li>点击<code>project Interpreter</code>右边的齿轮，点击<code>Add</code></li><li>选择<code>SSH Interpreter</code>，由于之前（第3步）配置过远程连接，直接选择<code>Existing server configuration</code>即可</li><li>3点击后，下方会出现一个<code>Create</code>，点击后，点击<code>Next</code></li><li>在最上面的<code>Interpreter</code>中选择远程的python解释器，注意是绝对路径</li><li>在下面的<code>Sync floder</code>中选择要同步的远程repo即可</li></ol></li></ol><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><h3 id="上传文件"><a href="#上传文件" class="headerlink" title="上传文件"></a>上传文件</h3><p>新版默认是及时同步，手动同步选择<code>Tools &gt; Deployment &gt; Upload to</code></p><h3 id="运行程序"><a href="#运行程序" class="headerlink" title="运行程序"></a>运行程序</h3><p>直接右键运行即可，与本地运行没有使用上差异，实际上从控制台可以看出，是ssh远程执行</p><pre><code class="hljs bash">ssh://user@rempte_ip:22/home/user/anaconda3/envs/nlp3/bin/python3 -u /home/user/test_repo_path/test.py</code></pre>]]></content>
    
    
    <summary type="html">&lt;p&gt;使用pycharm远程连接repo并远程运行。&lt;/p&gt;</summary>
    
    
    
    <category term="python" scheme="https://zekizz.github.io/categories/python/"/>
    
    
    <category term="python" scheme="https://zekizz.github.io/tags/python/"/>
    
  </entry>
  
  <entry>
    <title>mac通过vnc连接ubuntu远程桌面</title>
    <link href="https://zekizz.github.io/linux/mac-ubuntu-vnc/"/>
    <id>https://zekizz.github.io/linux/mac-ubuntu-vnc/</id>
    <published>2019-12-09T12:06:48.000Z</published>
    <updated>2020-08-29T15:29:17.185Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>虽然ssh能解决大部分问题，但是有时也需要远程桌面。为此，可能大家都采用<code>teamviewer</code>和<code>anydesk</code>来完成。  </p><p><br>  </p><p>但是不久前<code>teamviewer</code>出了问题，<code>anydesk</code>在使用过程中经常出现连接中断、连接慢等毛病。尝试发现通过<code>vnc</code>在局域网环境下连接异常稳定，基本不掉，键盘映射也比anydesk好，做个记录。</p><span id="more"></span><h2 id="ubuntu配置"><a href="#ubuntu配置" class="headerlink" title="ubuntu配置"></a>ubuntu配置</h2><h3 id="安装x11vnc"><a href="#安装x11vnc" class="headerlink" title="安装x11vnc"></a>安装<code>x11vnc</code></h3><pre><code class="hljs shell">sudo apt-get install x11vnc</code></pre><h3 id="设置vnc密码"><a href="#设置vnc密码" class="headerlink" title="设置vnc密码"></a>设置vnc密码</h3><pre><code class="hljs shell">x11vnc -storepasswd</code></pre><p>回车，在提示下输入和确认密码。保存在默认位置<code>$HOME/.vnc/passwd</code></p><h3 id="启动vnc服务"><a href="#启动vnc服务" class="headerlink" title="启动vnc服务"></a>启动vnc服务</h3><pre><code class="hljs shell">x11vnc -forever -shared -rfbauth ~/.vnc/passwd</code></pre><p>当然也可以放在后台运行</p><p>将上诉命名放在sh脚本中</p><pre><code class="hljs shell">nohup sh ./start.sh &amp;</code></pre><p>可以在nohup.out中最后几行查看其vnc运行的端口号：<code>PORT=5900</code></p><h2 id="mac连接"><a href="#mac连接" class="headerlink" title="mac连接"></a>mac连接</h2><p>mac自带一个vnc客户端：  </p><p><code>/System/Library/CoreServices/Applications/Screen Sharing.app</code>  </p><p><br>  </p><p> 打开后，输入ubuntu的局域网ip即可（试过貌似不需要端口号），然后输入之前设置的密码就可以了。  </p><p><br>  </p><p>可能会发现页面缩放的问题，点击<code>Screen Sharing</code>左上角的<code>scaling</code>即可。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;虽然ssh能解决大部分问题，但是有时也需要远程桌面。为此，可能大家都采用&lt;code&gt;teamviewer&lt;/code&gt;和&lt;code&gt;anydesk&lt;/code&gt;来完成。  &lt;/p&gt;
&lt;p&gt;&lt;br&gt;  &lt;/p&gt;
&lt;p&gt;但是不久前&lt;code&gt;teamviewer&lt;/code&gt;出了问题，&lt;code&gt;anydesk&lt;/code&gt;在使用过程中经常出现连接中断、连接慢等毛病。尝试发现通过&lt;code&gt;vnc&lt;/code&gt;在局域网环境下连接异常稳定，基本不掉，键盘映射也比anydesk好，做个记录。&lt;/p&gt;</summary>
    
    
    
    <category term="linux" scheme="https://zekizz.github.io/categories/linux/"/>
    
    
    <category term="ubuntu" scheme="https://zekizz.github.io/tags/ubuntu/"/>
    
    <category term="mac" scheme="https://zekizz.github.io/tags/mac/"/>
    
  </entry>
  
  <entry>
    <title>论文学习——MathGraph</title>
    <link href="https://zekizz.github.io/zhi-shi-tu-pu/math-graph/"/>
    <id>https://zekizz.github.io/zhi-shi-tu-pu/math-graph/</id>
    <published>2019-11-19T15:36:56.000Z</published>
    <updated>2020-08-29T11:18:07.866Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p><a href="https://www.researchgate.net/publication/332580594_MathGraph_A_Knowledge_Graph_for_Automatically_Solving_Mathematical_Exercises">MathGraph: A Knowledge Graph for Automatically Solving Mathematical Exercises</a></p><p>一个用于解决高中数学练习题的知识图谱。</p><p>MathGraph提出了一种有效的算法，将数学问题与MathGraph对齐，并使用对齐的子图来解决数学练习。</p><span id="more"></span><h2 id="Overview"><a href="#Overview" class="headerlink" title="Overview"></a>Overview</h2><p>MathGraph, a knowledge graph aiming to solve high school mathematical exercises.</p><p>一个用于解决高中数学练习题的知识图谱。</p><p>数学知识图谱与其他领域的知识图谱不一样，需要被特别的设计，原因有如下几点：</p><ol><li>数学知识图谱中的知识属于非常特定的领域：需要数学知识，一般基于广泛语义（维基百科等）构建的知识图谱很难获取数学问题中的语义信息。</li><li>数学知识图谱中存储的知识应该定义在class-level上，而不是instance-level：一般知识图集中于提取实例，类别以及实例之间的关系。例如，一个三元组（北京，isCaptialOf，中国）显示了两个实例之间的关系。但是，在MathGraph中，原始图中没有实例，只有许多类级别的数学对象（例如，复数，椭圆等）。仅当遇到具体的练习时，才会相应地创建实例。</li><li>数学知识图谱应该支持数学推导和计算：数学问题的推理过程不同于其他问题，因为除了逻辑关系外，知识图中还必须包含数学推导来解决数学练习。</li></ol><p>对于前两条毋庸置疑，对于第三条，MathGraph是提供了几个推理算法，具体的实操性有待商榷。但是我们在构建数学知识图谱的时候，也应该有这样的念头，我的图谱怎样支持推理？仅仅提供知识点之间的前后依赖关系？大小概念关系？仅仅只提供信息查询我觉得是不够的，<strong>支持推理是知识图谱的灵魂</strong>。</p><p>MathGraph提出了一种有效的算法，将数学问题与MathGraph对齐，并使用对齐的子图来解决数学练习。</p><p>这个过程就很符合STC-AOG的理念，构建的MathGraph是完备的AOG，具体的习题过来生成的实例就是一个具体的PG，在PG上做推理，比如代数问题中让所有约束条件得以解决，就解了这个题。</p><h2 id="MathGraph"><a href="#MathGraph" class="headerlink" title="MathGraph"></a>MathGraph</h2><p>要理解这部分，需要保持面向对象的思想，注意这里的笔记并<strong>不完全符合原文</strong>，根据我自己的理解做了<strong>取舍和修改</strong>，感兴趣的可以阅读原文。</p><h3 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h3><h4 id="数学对象和实例"><a href="#数学对象和实例" class="headerlink" title="数学对象和实例"></a>数学对象和实例</h4><p>一个数学概念是一个抽象对象，它有具体的定义、一些属性，可以用作某些运算或派生的目标。</p><p>注意，可以根据其他对象来定义数学对象（也就是可以嵌套，比如分数的分子和分母都是有理数）。满足数学对象定义的具体对象称为实例。</p><p>不同的数学对象应该有不同的结构，在MathGraph中，数学对象用关键属性（<strong>key properties</strong>） (p1, p2, · · · , pn)的元组来表示。数学对象的关键属性是可以一起形成和描述该对象实例的所有信息的那些属性。这里列举了几个数学对象：</p><p><img src="https://picbed-1252770021.cos.ap-chengdu.myqcloud.com/KG/Screen%20Shot%202019-11-19%20at%2011.36.41%20AM.png" alt=""></p><p>以复数来说</p><ul><li>定义：A complex number is a number that can be in the form $a + bi$, where $a$ and $b$ are both real numbers and $i$ is the imaginary unit which satisfies $i^2 = −1$.</li><li>属性： 复数$a + bi$ 的属性有三个，实部$a$，虚部$b$和$i$。</li><li>运算：例如，$(a_1 + b_1i) · (a_2 + b_2i) = (a_1a_2 − b_1b_2) + (a_1b_2 + a_2b_1)i$</li><li>推导：例如，如果$a_1 + b_1 i$和$a_2 + b_2 i$是共轭关系，那么$a_1 = a_2, b_1+b_2=0$</li></ul><h4 id="Operation-运算"><a href="#Operation-运算" class="headerlink" title="Operation(运算)"></a>Operation(运算)</h4><p>通常，运算是一种操作或过程。在给定一个或多个数学对象作为输入（称为操作数）的情况下，该操作或过程产生一个新的对象。简单的例子包括加，减，乘，除和求幂。</p><h4 id="Constraint-限制条件"><a href="#Constraint-限制条件" class="headerlink" title="Constraint(限制条件)"></a>Constraint(限制条件)</h4><p>约束是对一个或多个实例的描述或条件，其中至少一个是不确定的实例。具体有4中限制：</p><ol><li>描述限制：比如，复数$x$和$y$共轭</li><li>相等限制：$a+2=b$</li><li>不等限制：$a^2&lt;5$</li><li>集合限制：$a \in N$</li></ol><p>对于确定与不确定实例，根据实例是否包含某些不确定值作为其关键属性，可以将实例分为某些实例和不确定实例。如果所有关键属性都是确定的，那么实例就是确定的实例。否则情况不确定。</p><p>For example, a real number 2.3 and a function f(x) = x+sin(x) are certain; a complex number 3+ai (where a ∈ R) and a random triangle △ABC are uncertain. </p><h3 id="MathGraph结构"><a href="#MathGraph结构" class="headerlink" title="MathGraph结构"></a>MathGraph结构</h3><p>MathGraph是一个有向图$G = ⟨V, E⟩ $, 其中$v \in V$是一个数学对象、操作或者限制。边为它们之间的关系。</p><p>MathGraph is a directed graph $G = ⟨V, E⟩$, in which each node $v ∈ V $denotes a mathematical object, an operation or a constraint, and each edge $e ∈ E$ is the relation of two nodes. </p><h4 id="Nodes"><a href="#Nodes" class="headerlink" title="Nodes"></a>Nodes</h4><p>有三种nodes: object nodes, operation nodes and constraint nodes. </p><p><strong>Object Nodes</strong>  An object node $v_o = (t,P,C)$ represents a mathematical object, 其中</p><ul><li>$t$: an <strong>instance template</strong> of this mathematical object, 实例模板，也就是schema</li><li>$P = (P_1, P_2, \cdots , P_n) $：关键属性（<strong>key properties</strong>）</li><li>$C$：a set of constraints。根据该定义或某些定理，该数学对象必须满足的一组约束。</li></ul><p><img src="https://picbed-1252770021.cos.ap-chengdu.myqcloud.com/KG/Screen%20Shot%202019-11-19%20at%202.15.55%20PM.png" alt="三角形示例"></p><p><strong>Operation Nodes</strong>  An operation node $v_p = (X_1, X_2, · · · , X_k, Y, f)$ represents a k-ary operation ,其中</p><ul><li>$X_i(i = 1, 2, · · · , k)$ and $Y$ are object nodes representing the domain of the ith operand xi and the result of the operation y respectively, </li><li>$f$：is a function that implements the operation and can be finished by a series of symbolic execution process using a symbolic execution library (e.g. SymPy[10], Mathematica[7]) even if some operands are uncertain instances. </li></ul><p>比如获得复数的模就一个Operation，$X_1 = ⟨Complex Number⟩$, $Y = ⟨Real Number⟩$, and $f 4can be implemented by the following symbolic execution process: (1) Get the real part of x1; (2) Get the imaginary part of x1; (3) Return the squared root of the sum of (1) squared and (2) squared. </p><p><strong>Constraint Nodes</strong>   A constraint node $v_c = (d, X_1, X_2, · · · , X_k, f)$ represents a descriptive constraints of k instances, 其中，</p><ul><li>$d$：描述</li><li>$X_i(i = 1, 2, · · · , k)$，涉及的Object</li><li>$f$ :  a function which maps this descriptive constraint into several equality constraints, inequality constraints and set constraints. </li></ul><p>例如，a constraint node represents that $x_1$ and $x_2$ are a conjugate pair, where $X1 = X2 = ⟨Complex Number⟩$, and f can be implemented by the following process: (1) Get the real part of x1 as a1; (2) Get the real part of x2 as a2; (3) Get the imaginary part of x1 as b1; (4) Get the imaginary part of x2 as b2; (5) Return two equality constraints: a1 = a2 and b1 + b2 = 0. </p><p>我觉得Object节点是必须的，但是Operation节点和Constraint节点，在当前我们构建的数学知识图谱中并必须要。</p><p>但是，相应的，我们可以有正对性的设计成Object节点这个class的子对象字段。</p><h4 id="Edges"><a href="#Edges" class="headerlink" title="Edges"></a>Edges</h4><p>有两种边，<strong>the derive edges</strong> and <strong>the flow edges</strong>. </p><p><strong>The derive edges</strong>  $E_{derive} = (X,Y,f) $是一种general-special关系，比如从三角形到等腰三角形。 其中f为定义在其两端的限制条件，满足条件才返回true，也就是可以将general节点替换成special节点。</p><p>在解决练习时，将实例重新分配给更特定的对象节点<strong>将带来</strong>对<strong>该对象的更多约束</strong>，并有助于找到答案。</p><p><strong>The flow edge</strong> 练习求解过程中实例的流向，这些实例只能从对象节点到操作节点，从操作节点到对象节点或从对象节点到约束节点存在。</p><p><img src="https://picbed-1252770021.cos.ap-chengdu.myqcloud.com/KG/Screen%20Shot%202019-11-19%20at%202.38.44%20PM.png" alt=""></p><p>我觉得<strong>derive edges</strong>是必须的，flow edge反而在目前不需要。<strong>derive edges</strong>的定义加上节点的定义，让我感觉这很像一个概率图模型，边上定义有function，表示节点之间的转移关系。</p><p><img src="https://jessicastringham.net/assets/2019-01-09-factor-graph.png" alt=""></p><h3 id="解题"><a href="#解题" class="headerlink" title="解题"></a>解题</h3><p>解题第一步，将文本的题目parse到MathGraph上。</p><p>First, we use a <strong>semantic parser</strong> mapping exercise text to the instances, operations and constraints respectively. Then, we solve the constraints and update uncertain instances. Finally, we return the answer of this exercise. </p><p><img src="https://picbed-1252770021.cos.ap-chengdu.myqcloud.com/KG/Screen%20Shot%202019-11-19%20at%202.42.54%20PM.png" alt=""></p><p>parse得到的图其实就是一个pg</p><p><img src="https://picbed-1252770021.cos.ap-chengdu.myqcloud.com/KG/Screen%20Shot%202019-11-19%20at%202.44.51%20PM.png" alt=""></p><p>这块我们完全可以替换成我们自己的STC-AOG算法，这篇文章提出的解法也并不是很通用，实操性也待商榷，不是我关注的重点，就不做介绍。</p><h2 id="实验结果"><a href="#实验结果" class="headerlink" title="实验结果"></a>实验结果</h2><p>We collect four real-world datasets of mathematical exercises of Chinese high schools, namely <strong>Complex, Triangle, Conic and Solid</strong>. <strong>The exercises are stored in plain text</strong>, and <strong>the mathematical expressions are stored in the LaTeX format</strong>. </p><p><img src="https://picbed-1252770021.cos.ap-chengdu.myqcloud.com/KG/Screen%20Shot%202019-11-19%20at%202.47.31%20PM.png" alt=""></p><p><img src="https://picbed-1252770021.cos.ap-chengdu.myqcloud.com/KG/Screen%20Shot%202019-11-19%20at%202.49.40%20PM.png" alt=""></p><p><img src="https://picbed-1252770021.cos.ap-chengdu.myqcloud.com/KG/Screen%20Shot%202019-11-19%20at%202.49.50%20PM.png" alt=""></p><h2 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h2><p>我们的数学知识图谱该怎样建，这篇文章又能给我们什么样的启发。总结下来有如下几点</p><ul><li>数学知识图谱由于其学科的特殊性、逻辑的严密性、知识点的多样性，采用常规的信息查询方式构建图谱的话，如果不细心处理，估计也就只能查信息了，无法进行推理。</li><li><strong>推理是知识图谱的灵魂</strong></li><li>节点的属性可以打开思路，其属性不仅仅只能是干巴巴的文本描述，我们可以放入更多的东西，比如数学性质、定义中的约束，甚至是运算方式。这有点类似复写对象中的比较器、toString等方法。</li><li>图谱的边也可以打开思路。目前的边只是一个字符串，但是也可以在边上定义属性，甚至是约束条件。比如，一般三角形—-&gt;等腰三角形的边上可以有约束条件，比如，两边相等/两角相等。用一个array of string就可以。</li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://www.researchgate.net/publication/332580594_MathGraph_A_Knowledge_Graph_for_Automatically_Solving_Mathematical_Exercises&quot;&gt;MathGraph: A Knowledge Graph for Automatically Solving Mathematical Exercises&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;一个用于解决高中数学练习题的知识图谱。&lt;/p&gt;
&lt;p&gt;MathGraph提出了一种有效的算法，将数学问题与MathGraph对齐，并使用对齐的子图来解决数学练习。&lt;/p&gt;</summary>
    
    
    
    <category term="知识图谱" scheme="https://zekizz.github.io/categories/%E7%9F%A5%E8%AF%86%E5%9B%BE%E8%B0%B1/"/>
    
    
    <category term="知识图谱" scheme="https://zekizz.github.io/tags/%E7%9F%A5%E8%AF%86%E5%9B%BE%E8%B0%B1/"/>
    
    <category term="论文学习" scheme="https://zekizz.github.io/tags/%E8%AE%BA%E6%96%87%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>NLP训练数据生成之chatito</title>
    <link href="https://zekizz.github.io/zi-ran-yu-yan-chu-li/chatito/"/>
    <id>https://zekizz.github.io/zi-ran-yu-yan-chu-li/chatito/</id>
    <published>2019-11-17T13:11:53.000Z</published>
    <updated>2020-08-29T15:24:15.210Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>巧妇难为无米之炊，NLP任务也需好的数据来作为支撑。这里就有两个方面：</p><ol><li>完全没有数据</li><li>有大量未标注脏数据，标注极少甚至没有</li></ol><p>这个问题我打算用几篇博客一一讨论，本篇针对完全没数据的场景，介绍使用chatito来生成数据。</p><span id="more"></span><h1 id="Chatito简介"><a href="#Chatito简介" class="headerlink" title="Chatito简介"></a>Chatito简介</h1><p><a href="https://github.com/rodrigopivi/Chatito">Chatito</a>使用简单易上手的<a href="https://github.com/rodrigopivi/Chatito/blob/master/spec.md">DSL</a>语法来为几类场景的NLP任务生成数据。原话是</p><blockquote><p>Generate datasets for AI chatbots, NLP tasks, named entity recognition or text classification models using a simple DSL!</p></blockquote><p>亲测确实方便生成一定量的数据，但是生成的训练集和测试集都是一个模板（构成规则）出来的，训练测试数据同源同构，很容易造成严重的过拟合。典型的表现是在测试集上的准确率和F1等指标会接近1，在未知数据上的泛化性会不好。</p><p>显然，采用这种方式生成数据并不是最好的方式。但在实在没有数据的情况下，怎样去解决同源的问题呢？想到的解决方式有几点：</p><ol><li>生成的过程中，只填入几条典型的场景，同类型的采用词典，并后续任务上构造词典特征</li><li>生成后采用一些数据增强方式（同义词替换、位置交换等），增加训练数据的多样性。</li></ol><p>本文不具体介绍这两种方式，会另外用实际的例子和博客分别进行记录，包括本文的例子都放在repo: <a href="https://github.com/zekizz/DataGeneratorForNLP">DataGeneratorForNLP</a></p><h2 id="使用前准备"><a href="#使用前准备" class="headerlink" title="使用前准备"></a>使用前准备</h2><h3 id="安装node-js"><a href="#安装node-js" class="headerlink" title="安装node.js"></a>安装node.js</h3><p>首先需安装node.js &gt;= v8.11</p><ol><li><a href="https://nodejs.org/en/">官网</a>下载编译好的包</li><li>解压</li><li>设置软连接</li></ol><pre><code class="hljs shell">ln -s /usr/software/nodejs/bin/npm   /usr/local/bin/ ln -s /usr/software/nodejs/bin/node   /usr/local/bin/</code></pre><p>在mac上直接采用<code>homebrew</code>安装即可</p><pre><code class="hljs shell">brew install nodebrew install npm</code></pre><h3 id="npm配置"><a href="#npm配置" class="headerlink" title="npm配置"></a>npm配置</h3><pre><code class="hljs shell">npm config set registry https://registry.npm.taobao.org --globalnpm config set disturl https://npm.taobao.org/dist --global<span class="hljs-meta prompt_"># </span><span class="language-bash">更新</span>npm install -g npm</code></pre><h3 id="安装chatito-npm-package"><a href="#安装chatito-npm-package" class="headerlink" title="安装chatito npm package"></a>安装chatito npm package</h3><pre><code class="hljs shell">npm i chatito --save</code></pre><h2 id="编写构成脚本"><a href="#编写构成脚本" class="headerlink" title="编写构成脚本"></a>编写构成脚本</h2><p>因为一个脚本只能生成一个类型的，比如在分类问题中要生成多个类的数据，最好一个类一个生成文件。所以，最好新建一个文件夹，存放所有脚本，比如<code>chatito</code></p><p>下面以对话中介绍新朋友这样一个场景为例，介绍脚本的写法，完整的语法参见<a href="https://github.com/rodrigopivi/Chatito/blob/master/spec.md">DSL</a>。</p><p>新建一个以<code>.chatito</code>结尾文件，命名为<code>intro_new_user.chatito</code>，内容为：</p><pre><code class="hljs less"><span class="hljs-selector-tag">import</span> ./<span class="hljs-selector-tag">common</span><span class="hljs-selector-class">.chatito</span>%<span class="hljs-selector-attr">[intro_new_user]</span>(<span class="hljs-string">&#x27;training&#x27;</span>: <span class="hljs-string">&#x27;100&#x27;</span>, <span class="hljs-string">&#x27;testing&#x27;</span>: <span class="hljs-string">&#x27;50&#x27;</span>)    *<span class="hljs-selector-attr">[60%]</span> ~<span class="hljs-selector-attr">[hi?]</span>，~<span class="hljs-selector-attr">[pre1?]</span>~<span class="hljs-selector-attr">[pre2]</span>~<span class="hljs-selector-attr">[pre3]</span>，~<span class="hljs-selector-attr">[indicate?]</span>@<span class="hljs-selector-attr">[username]</span>    *<span class="hljs-selector-attr">[20%]</span> ~<span class="hljs-selector-attr">[hi?]</span>，~<span class="hljs-selector-attr">[indicate]</span>@<span class="hljs-selector-attr">[username]</span>    *<span class="hljs-selector-attr">[20%]</span> ~<span class="hljs-selector-attr">[indicate]</span>@<span class="hljs-selector-attr">[username]</span>~<span class="hljs-selector-attr">[pre1]</span>    给你~<span class="hljs-selector-attr">[pre2]</span>    介绍    认识    了解~<span class="hljs-selector-attr">[pre3]</span>    一个新朋友    一位新朋友    个新朋友    位新朋友    一个朋友    一位朋友    个朋友    位朋友    一下    下~<span class="hljs-selector-attr">[indicate]</span>    这是    他是    她是    他叫    她叫    我是    我叫@<span class="hljs-selector-attr">[username]</span>    小红    小花    大黄    小明</code></pre><p>其中<code>common.chatito</code>为另外一个提供通用组成部分的脚本，内容为</p><pre><code class="hljs css">~<span class="hljs-selector-attr">[hi]</span>    你好    嗨    嘿    哈喽    hi    hello~<span class="hljs-selector-attr">[please]</span>    请~<span class="hljs-selector-attr">[thanks]</span>    谢谢    谢了    thx    谢谢你</code></pre><p>比如<code>hi</code>，import后就可以采用<code>~[hi]</code>直接引用了。</p><p>因为chatito初衷是给对话生成数据，所以脚本里的概念有三个：意图（%[intent_name]）、槽值（@[slot_name]）和别称（~[alias_name]）。意图可以视为分类问题的类别，槽值可视为NER问题的实体，别称只是为了方便组合，随机选取，有点像正则的里中括号里的内容（如<code>[a-zA-Z]</code>）。p.s. 别称里的内容不会被认为是实体。</p><p>在上面的例子中<code>%[intro_new_user](&#39;training&#39;: &#39;100&#39;, &#39;testing&#39;: &#39;50&#39;)</code>，表明想生成的意图是<code>intro_new_user</code>。并且训练集生成100个样本，测试集50个。</p><p>接下来的一行, <code>*[60%] ~[hi?]，~[pre1?]~[pre2]~[pre3]，~[indicate?]@[username]</code></p><ul><li><code>*[60%]</code>： 表示这一行的构成规则在最好生成数据中占的比例</li><li><code>~[hi?]</code>：随机选择别称<code>hi</code>的一个（比如，选择<code>你好</code>），<code>?</code>表示可以不选，这个与正则中的概念相似</li><li><code>@[username]</code>：随机选取槽<code>username</code>中的一个，在生成的数据中，选取的槽值会被标记为实体，可用于实体识别，有位置信息。</li></ul><p><strong>这里需要注意的是</strong>：各个部分之间如果有空格，生成的结果中也会有空格。生成结果只是替换<code>~[hi?]</code>，<code>~[hi?]</code>和其后面的<code>~[pre1?]</code>之间的<strong>任何内容都会原封不动保留</strong>，比如这里的逗号。所以，<strong>对于要考虑分词的误差的场景下</strong>，建议各个部分之间不要采用空格分隔的方式，保持中文的自然连接。</p><p>基本这些内容就够用了，其他用法可以自行探索<a href="https://github.com/rodrigopivi/Chatito/blob/master/spec.md">DSL</a>。</p><h2 id="生成数据"><a href="#生成数据" class="headerlink" title="生成数据"></a>生成数据</h2><p>生成语法为</p><pre><code class="hljs shell">npx chatito &lt;pathToFileOrDirectory&gt; --format=&lt;format&gt; --formatOptions=&lt;formatOptions&gt; --outputPath=&lt;outputPath&gt; --trainingFileName=&lt;trainingFileName&gt; --testingFileName=&lt;testingFileName&gt; --defaultDistribution=&lt;defaultDistribution&gt; --autoAliases=&lt;autoAliases&gt;</code></pre><ul><li><code>&lt;pathToFileOrDirectory&gt;</code> path to a <code>.chatito</code> file or a directory that contains chatito files. If it is a directory, will search recursively for all <code>*.chatito</code> files inside and use them to generate the dataset. e.g.: <code>lightsChange.chatito</code> or <code>./chatitoFilesFolder</code></li><li><code>&lt;format&gt;</code> Optional. <code>default</code>, <code>rasa</code>, <code>luis</code>, <code>flair</code> or <code>snips</code>.</li><li><code>&lt;formatOptions&gt;</code> Optional. Path to a .json file that each adapter optionally can use</li><li><code>&lt;outputPath&gt;</code> Optional. The directory where to save the generated datasets. Uses the current directory as default.</li><li><code>&lt;trainingFileName&gt;</code> Optional. The name of the generated training dataset file. Do not forget to add a .json extension at the end. Uses ``_dataset_training.json as default file name.</li><li><code>&lt;testingFileName&gt;</code> Optional. The name of the generated testing dataset file. Do not forget to add a .json extension at the end. Uses ``_dataset_testing.json as default file name.</li><li><code>&lt;defaultDistribution&gt;</code> Optional. The default frequency distribution if not defined at the entity level. Defaults to <code>regular</code> and can be set to <code>even</code>.</li><li><code>&lt;autoAliases&gt;</code> Optional. The generaor behavior when finding an undefined alias. Valid opions are <code>allow</code>, <code>warn</code>, <code>restrict</code>. Defauls to ‘allow’.</li></ul><p>可以生成<a href="https://rasa.com/docs/rasa/">Rasa</a>、<a href="https://github.com/zalandoresearch/flair">Flair</a>、<a href="https://www.luis.ai/">LUIS</a>、<a href="https://snips-nlu.readthedocs.io/en/latest/">Snips NLU</a>格式的数据，以Rasa为例。</p><pre><code class="hljs shell">npx chatito ./chatito --format=rasa --outputPath=./data</code></pre><p>生成的结果放在<code>data</code>文件夹下</p><pre><code class="hljs autohotkey">ras<span class="hljs-built_in">a_dataset</span>_testing.jsonras<span class="hljs-built_in">a_dataset</span>_training.json</code></pre><p>生成的文件是一行的json，采用<code>pbcopy &lt; rasa_dataset_testing.json</code>, 粘贴在<a href="http://www.totootool.com/json.html">http://www.totootool.com/json.html</a>。</p><p><img src="https://picbed-1252770021.cos.ap-chengdu.myqcloud.com/NLU/Screen%20Shot%202019-11-17%20at%2010.42.30%20PM.png" alt=""></p><p>训练集会多两项</p><pre><code class="hljs elixir"><span class="hljs-string">&quot;regex_features&quot;</span><span class="hljs-symbol">:[]</span>,<span class="hljs-string">&quot;entity_synonyms&quot;</span><span class="hljs-symbol">:[]</span></code></pre><p>具体跟rasa有关，这里不再赘述。</p><p>接下来，会介绍如何使用<a href="https://www.snorkel.org/">snorkel</a>做NLP数据增强和弱监督训练数据生成。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;巧妇难为无米之炊，NLP任务也需好的数据来作为支撑。这里就有两个方面：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;完全没有数据&lt;/li&gt;
&lt;li&gt;有大量未标注脏数据，标注极少甚至没有&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个问题我打算用几篇博客一一讨论，本篇针对完全没数据的场景，介绍使用chatito来生成数据。&lt;/p&gt;</summary>
    
    
    
    <category term="自然语言处理" scheme="https://zekizz.github.io/categories/%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7%90%86/"/>
    
    
    <category term="自然语言处理" scheme="https://zekizz.github.io/tags/%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7%90%86/"/>
    
    <category term="数据生成" scheme="https://zekizz.github.io/tags/%E6%95%B0%E6%8D%AE%E7%94%9F%E6%88%90/"/>
    
    <category term="NLU" scheme="https://zekizz.github.io/tags/NLU/"/>
    
    <category term="ML" scheme="https://zekizz.github.io/tags/ML/"/>
    
  </entry>
  
  <entry>
    <title>hexo添加评论和访问统计填坑</title>
    <link href="https://zekizz.github.io/bo-ke/blog-comment-view/"/>
    <id>https://zekizz.github.io/bo-ke/blog-comment-view/</id>
    <published>2019-11-10T05:42:26.000Z</published>
    <updated>2020-08-29T15:22:28.364Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>hexo采用<a href="https://www.livere.com/">LiveRe</a>(来必力)添加评论系统，<a href="https://busuanzi.ibruce.info/">不蒜子</a>添加访客统计。</p><span id="more"></span><h1 id="评论系统"><a href="#评论系统" class="headerlink" title="评论系统"></a>评论系统</h1><p>本来之前采用的是gittalk，想着照顾没有github账号和想匿名留言的同学。调查了一通，发现了一个比较好的工具：<a href="https://leancloud.cn/">LeanCloud</a>。不仅有匿名留言，还有访客统计。满怀期待，最后发现还要实名认证，弃！</p><p>逛了一圈，综合稳定性和美观性，选用了<a href="https://www.livere.com/">LiveRe</a>(来必力)。去注册一个账号，选择社区，复制data-uid，填到主题的<code>_config.yml</code>中的<code>livere=your id</code>即可。</p><pre><code class="hljs js">&lt;div id=<span class="hljs-string">&quot;lv-container&quot;</span> data-id=<span class="hljs-string">&quot;city&quot;</span> data-uid=<span class="hljs-string">&quot;you id&quot;</span>&gt;</code></pre><p>需要注意的是，需要在<code>管理页面</code>中设置社交平台和评论提醒邮件地址。</p><h1 id="不蒜子"><a href="#不蒜子" class="headerlink" title="不蒜子"></a>不蒜子</h1><p>参考<a href="https://busuanzi.ibruce.info/">不蒜子</a>官网的介绍，使用只需</p><pre><code class="hljs js">&lt;script <span class="hljs-keyword">async</span> src=<span class="hljs-string">&quot;//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js&quot;</span>&gt;&lt;/script&gt;<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">&quot;busuanzi_container_site_pv&quot;</span>&gt;</span>本站总访问量<span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">&quot;busuanzi_value_site_pv&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>次<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span></code></pre><p>但是默认，默认开启后，发现前端不显示，进一步查发现<code>display:none</code>。</p><p>解决方案如下</p><h2 id="1-post-pug"><a href="#1-post-pug" class="headerlink" title="1. post.pug"></a>1. post.pug</h2><p>找到不蒜子部分，删掉<code>span#busuanzi_container_page_pv= &#39; | &#39;</code>中的<code>#busuanzi_container_page_pv</code>，完整的不蒜子部分</p><pre><code class="hljs js"><span class="hljs-keyword">if</span> theme.<span class="hljs-property">busuanzi</span> == <span class="hljs-literal">true</span>        <span class="hljs-comment">//- script(src=&#x27;https://dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js&#x27;, async) // 老版，不蒜子已更换域名</span>        &lt;script <span class="hljs-keyword">async</span> src=<span class="hljs-string">&quot;//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js&quot;</span>&gt;&lt;/script&gt;        span= <span class="hljs-string">&#x27; | &#x27;</span>          span#busuanzi_value_page_pv          span= <span class="hljs-string">&#x27; &#x27;</span> + <span class="hljs-title function_">__</span>(<span class="hljs-string">&#x27;Hits&#x27;</span>)</code></pre><h2 id="2-footer-pug"><a href="#2-footer-pug" class="headerlink" title="2. footer.pug"></a>2. footer.pug</h2><p><a href="https://github.com/tufu9441/maupassant-hexo">maupassant-hexo</a>默认主题下是没有在footer中做站点统计的，只有篇章统计。添加如下</p><pre><code class="hljs js">#footer  <span class="hljs-keyword">if</span> theme.<span class="hljs-property">busuanzi</span> == <span class="hljs-literal">true</span>      &lt;script <span class="hljs-keyword">async</span> src=<span class="hljs-string">&quot;//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js&quot;</span>&gt;&lt;/script&gt;      <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>本站总访问量<span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">&quot;busuanzi_value_site_pv&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>次<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>      <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>总访客<span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">&quot;busuanzi_value_site_uv&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>人<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>      &lt;br&gt;  = <span class="hljs-string">&#x27;Copyright © &#x27;</span> + <span class="hljs-title function_">date</span>(<span class="hljs-title class_">Date</span>.<span class="hljs-title function_">now</span>(), <span class="hljs-string">&#x27;YYYY&#x27;</span>) + <span class="hljs-string">&#x27; &#x27;</span>  <span class="hljs-title function_">a</span>(href=<span class="hljs-title function_">url_for</span>(<span class="hljs-string">&#x27;.&#x27;</span>), rel=<span class="hljs-string">&#x27;nofollow&#x27;</span>)= config.<span class="hljs-property">title</span> + <span class="hljs-string">&#x27;.&#x27;</span>  |  <span class="hljs-title class_">Powered</span> by  <span class="hljs-title function_">a</span>(rel=<span class="hljs-string">&#x27;nofollow&#x27;</span>, target=<span class="hljs-string">&#x27;_blank&#x27;</span>, href=<span class="hljs-string">&#x27;https://hexo.io&#x27;</span>)  <span class="hljs-title class_">Hexo</span>.  <span class="hljs-title function_">a</span>(rel=<span class="hljs-string">&#x27;nofollow&#x27;</span>, target=<span class="hljs-string">&#x27;_blank&#x27;</span>, href=<span class="hljs-string">&#x27;https://github.com/tufu9441/maupassant-hexo&#x27;</span>)  <span class="hljs-title class_">Theme</span>  |  by  <span class="hljs-title function_">a</span>(rel=<span class="hljs-string">&#x27;nofollow&#x27;</span>, target=<span class="hljs-string">&#x27;_blank&#x27;</span>, href=<span class="hljs-string">&#x27;https://github.com/pagecho&#x27;</span>)  <span class="hljs-title class_">Cho</span>.</code></pre><p>这里也填了一个坑。</p><p>如果按照其他家博客的介绍，</p><pre><code class="hljs js">&lt;span id=<span class="hljs-string">&quot;这里是有id的（busuanzi_container_site_pv）&quot;</span>&gt;本站总访问量&lt;span id=<span class="hljs-string">&quot;busuanzi_value_site_pv&quot;</span>&gt;&lt;<span class="hljs-regexp">/span&gt;次&lt;/</span>span&gt;</code></pre><p>同理，这里也两个<code>span</code>的id删掉了。</p><p>大工告成。</p><h1 id="看板娘"><a href="#看板娘" class="headerlink" title="看板娘"></a>看板娘</h1><p>多说一句，如果想使用自己的看板娘，参考: <a href="https://www.yanjiayu.cn/posts/ff508987.html">Hexo 博客利用 live2d 插件放置一个萌萌哒看板娘</a></p><p>简单做法</p><pre><code class="hljs markdown"><span class="hljs-bullet">1.</span> 在您博客根目录下创建一个 live2d<span class="hljs-emphasis">_models 文件夹</span><span class="hljs-emphasis">2. 在此文件夹内新建一个子文件夹, 如 <span class="hljs-strong">**mymodel**</span></span><span class="hljs-emphasis">3. 将你的 Live2D 模型复制到这个子文件夹中，自有模型应当有一个 .model.json 文件 (例如 mymodel.model.json)</span><span class="hljs-emphasis">4. 将子文件夹的名称输入 _</span>config.yml 的 model.use 中</code></pre><p>重新<code>hexo g</code>就好</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;hexo采用&lt;a href=&quot;https://www.livere.com/&quot;&gt;LiveRe&lt;/a&gt;(来必力)添加评论系统，&lt;a href=&quot;https://busuanzi.ibruce.info/&quot;&gt;不蒜子&lt;/a&gt;添加访客统计。&lt;/p&gt;</summary>
    
    
    
    <category term="博客" scheme="https://zekizz.github.io/categories/%E5%8D%9A%E5%AE%A2/"/>
    
    
    <category term="博客" scheme="https://zekizz.github.io/tags/%E5%8D%9A%E5%AE%A2/"/>
    
    <category term="不蒜子" scheme="https://zekizz.github.io/tags/%E4%B8%8D%E8%92%9C%E5%AD%90/"/>
    
    <category term="liveRe" scheme="https://zekizz.github.io/tags/liveRe/"/>
    
  </entry>
  
  <entry>
    <title>考虑未定义类型的多分类</title>
    <link href="https://zekizz.github.io/ji-qi-xue-xi/sklearn-multi-label/"/>
    <id>https://zekizz.github.io/ji-qi-xue-xi/sklearn-multi-label/</id>
    <published>2019-11-07T14:41:57.000Z</published>
    <updated>2020-08-29T15:31:56.530Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>考虑这样一个场景，我们要处理一个多分类问题，由于目标的空间是开放的，我们无法穷举所有的类别。目前我们制定了K类去训练一个多分类模型，在预测未知数据时，有可能出现未识别的类型，此时我们的多分类模型会赋予它k类中的一类。然鹅，这个赋予是错误的。那怎样去避免和处理这样的现象呢？</p><span id="more"></span><h2 id="方案思考"><a href="#方案思考" class="headerlink" title="方案思考"></a>方案思考</h2><p>遇到这个问题，很多人的第一直观想法是，分别给没类训练一个one-class classifier（比如one-class svm）不就好了。</p><p>这确实是一个看起来很不错的想法。但是one-class classifer本质上是做异常检测的，如果某一类的样本空间分布很分散，并没有聚成团，此时可能正确的样本都会被判断成错误的类别。实际中遇到各种乱七八糟的数据，我对one-class classifier的性能是打问号的。当然如果能精心设计，以上当我扯淡。</p><p>除了one-class classifer外，我粗略考虑到的两种稍微靠谱一点的做法，这里做一下记录。（貌似找到了这方面的paper，比如<a href="https://arxiv.org/abs/1801.05609">Unseen Class Discovery in Open-world Classification</a>，之后找时间再写一篇学习下这类做法）</p><ol><li>将multi-class改成multi-label任务</li><li>设计一个pipline，第一个分类器判断是否见过，第二就是常规的多分类</li></ol><p>第二个做法中第一个分类器的设计，见仁见智。可以设计成one-class分类器，也可以计算与已知样本/类别的距离等这样简单的做法。</p><p>目前实践了第一种做法，做一下记录</p><h2 id="multi-label"><a href="#multi-label" class="headerlink" title="multi-label"></a>multi-label</h2><p>Multi-label指的是，一个样本可能有多个标签，不再是单一标签。比如一副海滩的图片，我们对其进行分类，其可能同时具备标签，白云、大海、沙滩、山、树。将其只分给其中一个比如大海，是不合适的。</p><blockquote><p>扯个题外话，分类问题我觉得可以分为四类</p><ol><li>Binary classification</li><li>Multi-class classification</li><li>Multi-label classification</li><li>Multi-target classification</li></ol></blockquote><p>提供两个工具：<a href="http://scikit.ml/">scikit-multilearn</a>、<a href="http://waikato.github.io/meka/">meka</a></p><p>Multi-label算法的核心其实是想利用label和label之间的相关性，我们这里处理未见类别其实没有利用到这个思想。</p><p>具体算法可以分为如下几类</p><ol><li>Binary Relevance：每个标签看作一个单独的类分类问题。</li><li>Classifier Chain：单独分类每个类别，但是前面得分类伪标签会作为后面分类的特征。（利用相关性，需要组合不同的链）</li><li>Label Powerset：组合类别做多个多分类问题</li><li>Adaptive：这类就比较丰富了，MLKNN、神经网络、集成学习</li></ol><p>在本次考虑的场景下，我们并没有考虑类别之间的关系，所以采用BR做一个测试，本次没有使用scikit-multilearn，仅仅用了sklearn的multi-label工具。</p><pre><code class="hljs python"><span class="hljs-keyword">from</span> sklearn.feature_extraction.text <span class="hljs-keyword">import</span> CountVectorizer<span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> ShuffleSplit, train_test_split<span class="hljs-keyword">from</span> sklearn.linear_model <span class="hljs-keyword">import</span> LogisticRegression<span class="hljs-keyword">from</span> sklearn.svm <span class="hljs-keyword">import</span> SVC<span class="hljs-keyword">from</span> sklearn.metrics <span class="hljs-keyword">import</span> classification_report<span class="hljs-keyword">from</span> sklearn.preprocessing <span class="hljs-keyword">import</span> LabelBinarizer, LabelEncoder<span class="hljs-keyword">from</span> sklearn.multiclass <span class="hljs-keyword">import</span> OneVsRestClassifier<span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np<span class="hljs-keyword">def</span> <span class="hljs-title function_">train_one_vs_rest</span>(<span class="hljs-params">self</span>):    all_data_file = <span class="hljs-string">&#x27;data.txt&#x27;</span>    data_all, labels_all = self.process_data(all_data_file)    <span class="hljs-comment"># lb = LabelEncoder()</span>    lb = LabelBinarizer()    y = lb.fit_transform(labels_all)    <span class="hljs-built_in">print</span>(lb.classes_)    text_train, text_test, y_train, y_test = \        train_test_split(data_all, y, test_size=<span class="hljs-number">0.33</span>, random_state=<span class="hljs-number">0</span>)    count_vect = CountVectorizer()    train_vec = count_vect.fit_transform(text_train)    clf = OneVsRestClassifier(LogisticRegression())    clf.fit(train_vec, y_train)    <span class="hljs-built_in">print</span>(<span class="hljs-built_in">len</span>(clf.estimators_))    test_vec = count_vect.transform(text_test)    pred = clf.predict(test_vec)    <span class="hljs-built_in">print</span>(classification_report(y_test, pred))    self.model = clf    self.vectorizer = count_vect    pred = self.pred_multi_label(test_vec)    <span class="hljs-built_in">print</span>(pred)    <span class="hljs-keyword">def</span> <span class="hljs-title function_">pred_multi_label</span>(<span class="hljs-params">self, vectors</span>):    preds = []    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-built_in">len</span>(self.model.estimators_)):        preds.append(            self.model.estimators_[i].predict_proba(vectors)[:, <span class="hljs-number">1</span>])    <span class="hljs-keyword">return</span> np.column_stack(<span class="hljs-built_in">tuple</span>(preds))</code></pre><p>这里需要注意的是，在预测过程中，如果采用OneVsRestClassifier默认的predict<em>proba，得到的没类的概率是归一化的。我想得到的就是具体属于每一类的概率，不要归一化。所以，这里采用了其属性`estimators</em>`来计算具体的概率。</p><p>实验发现，效果贼好。对于已知类别，对应类上的概率基本大于80%，而对于未见类别，最高也在57%左右。</p><p>此时我们完全可以采用70%去截断，如果一个类别未达到，那就是未知类别了。</p><p>当然，这种做法还有一些问题，接下来梳理下更好的做法。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;考虑这样一个场景，我们要处理一个多分类问题，由于目标的空间是开放的，我们无法穷举所有的类别。目前我们制定了K类去训练一个多分类模型，在预测未知数据时，有可能出现未识别的类型，此时我们的多分类模型会赋予它k类中的一类。然鹅，这个赋予是错误的。那怎样去避免和处理这样的现象呢？&lt;/p&gt;</summary>
    
    
    
    <category term="机器学习" scheme="https://zekizz.github.io/categories/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
    
    <category term="机器学习" scheme="https://zekizz.github.io/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="multi-label" scheme="https://zekizz.github.io/tags/multi-label/"/>
    
    <category term="sklearn" scheme="https://zekizz.github.io/tags/sklearn/"/>
    
  </entry>
  
  <entry>
    <title>vue文件上传下载</title>
    <link href="https://zekizz.github.io/qian-duan/vue-wen-jian-shang-chuan-xia-zai/"/>
    <id>https://zekizz.github.io/qian-duan/vue-wen-jian-shang-chuan-xia-zai/</id>
    <published>2019-10-31T14:40:00.000Z</published>
    <updated>2020-08-29T15:32:22.471Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>使用booststrap-vue和FileReader做文件读取、file-saver做文件下载</p><span id="more"></span><h1 id="文件读取"><a href="#文件读取" class="headerlink" title="文件读取"></a>文件读取</h1><h2 id="1-选取本地文件"><a href="#1-选取本地文件" class="headerlink" title="1. 选取本地文件"></a>1. 选取本地文件</h2><p>参考bootstrap-vue的<a href="https://bootstrap-vue.js.org/docs/components/form-file">form-file</a>，来选取本地文件</p><pre><code class="hljs js">&lt;b-form-file  v-model=<span class="hljs-string">&quot;dataFile&quot;</span>  :state=<span class="hljs-string">&quot;Boolean(dataFile)&quot;</span>  placeholder=<span class="hljs-string">&quot;选择文件&quot;</span>  drop-placeholder=<span class="hljs-string">&quot;拖拽到这里&quot;</span>  style=<span class="hljs-string">&quot;text-align: left;&quot;</span>  size=<span class="hljs-string">&quot;sm&quot;</span>&gt;&lt;/b-form-file&gt;</code></pre><p>选取到的文件存放在compenent data的dataFile中。</p><h2 id="2-读取文件内容"><a href="#2-读取文件内容" class="headerlink" title="2. 读取文件内容"></a>2. 读取文件内容</h2><pre><code class="hljs javascript">loadFile () &#123;  <span class="hljs-keyword">const</span> reader = <span class="hljs-keyword">new</span> <span class="hljs-title class_">FileReader</span>();  <span class="hljs-keyword">const</span> _this = <span class="hljs-variable language_">this</span>;  reader.<span class="hljs-title function_">readAsText</span>(_this.<span class="hljs-property">dataFile</span>);  reader.<span class="hljs-property">onload</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) &#123;    <span class="hljs-comment">// this.result为读取到的json字符串，需转成json对象</span>    _this.<span class="hljs-property">importJSON</span> = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">result</span>);  &#125;&#125;,</code></pre><h1 id="文件下载"><a href="#文件下载" class="headerlink" title="文件下载"></a>文件下载</h1><p>比如我们在前端编辑了一些数据，希望将这些数据下载为json文件，查了一圈发现还是<a href="https://www.npmjs.com/package/file-saver">file-saver</a>最好用。</p><p>安装</p><pre><code class="hljs shell">npm i file-saver</code></pre><p>不需要在头部import，采用<code>require</code>的方式</p><pre><code class="hljs js"><span class="hljs-title function_">saveData</span>(<span class="hljs-params"></span>)&#123;    <span class="hljs-keyword">var</span> <span class="hljs-title class_">FileSaver</span> = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;file-saver&#x27;</span>);    <span class="hljs-keyword">var</span> data = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">graph</span>, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>);    <span class="hljs-keyword">var</span> blob = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Blob</span>([data], &#123;<span class="hljs-attr">type</span>: <span class="hljs-string">&quot;application/json;charset=utf-8&quot;</span>&#125;);    <span class="hljs-title class_">FileSaver</span>(blob, <span class="hljs-string">&quot;output.json&quot;</span>);&#125;</code></pre><p>注意：这里的api与github上readme上写的已经不一样了，我目前的最新版本号<code>2.0.2</code>。这里不再采用<code>FileSaver.saveAs(file)</code></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;使用booststrap-vue和FileReader做文件读取、file-saver做文件下载&lt;/p&gt;</summary>
    
    
    
    <category term="前端" scheme="https://zekizz.github.io/categories/%E5%89%8D%E7%AB%AF/"/>
    
    
    <category term="前端" scheme="https://zekizz.github.io/tags/%E5%89%8D%E7%AB%AF/"/>
    
    <category term="可视化" scheme="https://zekizz.github.io/tags/%E5%8F%AF%E8%A7%86%E5%8C%96/"/>
    
    <category term="文件" scheme="https://zekizz.github.io/tags/%E6%96%87%E4%BB%B6/"/>
    
  </entry>
  
  <entry>
    <title>NLU调研</title>
    <link href="https://zekizz.github.io/zi-ran-yu-yan-chu-li/nlu-survey/"/>
    <id>https://zekizz.github.io/zi-ran-yu-yan-chu-li/nlu-survey/</id>
    <published>2019-10-27T02:57:00.000Z</published>
    <updated>2020-08-29T15:31:29.707Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>[TOC]</p><p>业务场景：小样本数据上的任务型对话理解。</p><p>对话领域三类</p><ol><li>问答类</li><li>任务类</li><li>闲聊类</li></ol><span id="more"></span><h1 id="1-规则方法"><a href="#1-规则方法" class="headerlink" title="1. 规则方法"></a>1. 规则方法</h1><h2 id="1-1-意图识别"><a href="#1-1-意图识别" class="headerlink" title="1.1 意图识别"></a>1.1 意图识别</h2><ul><li>词典法</li><li>CFG（上下文无关语法）</li><li>JSGF（JSpeech Grammar Format）</li></ul><p>参考资料：</p><ul><li><a href="https://cs.nju.edu.cn/daixinyu/nlp-traditional.pdf">CFG原理</a></li><li><a href="https://github.com/Samurais/text-cfg-parser">自然语言处理之CFG句法分析</a></li><li><a href="https://github.com/Danesprite/pyjsgf">JSpeech Grammar Format (JSGF) compiler, matcher and parser package for Python.</a></li></ul><h2 id="1-2-命名实体识别"><a href="#1-2-命名实体识别" class="headerlink" title="1.2 命名实体识别"></a>1.2 命名实体识别</h2><p>需要构造词典</p><ul><li>AC自动机算法（Aho–Corasick算法）</li><li><a href="http://www.hankcs.com/program/algorithm/aho-corasick-double-array-trie.html">Aho Corasick自动机结合DoubleArrayTrie极速多模式匹配</a></li><li>基于规则的模型</li></ul><p>参考：</p><ul><li><a href="https://sara-hy.github.io/2018/11/02/intent_slot/">Review on Intent Classification and Slot Filling</a></li><li><a href="https://ifreele.com/2017/11/05/tech-2017-11-5-chatbot/">基于Rasa_NLU的微信chatbot</a></li></ul><h1 id="2-模型方法"><a href="#2-模型方法" class="headerlink" title="2. 模型方法"></a>2. 模型方法</h1><p><a href="https://github.com/AtmaHou/Task-Oriented-Dialogue-Dataset-Survey">A dataset survey about task-oriented dialogue, including recent datasets and SoA results &amp; papers.</a></p><h2 id="2-1-pipeline"><a href="#2-1-pipeline" class="headerlink" title="2.1 pipeline"></a>2.1 pipeline</h2><p>pipeline方法将意图识别和槽填充分为两个独立的部分，分别进行训练。</p><h3 id="2-1-1-意图识别"><a href="#2-1-1-意图识别" class="headerlink" title="2.1.1 意图识别"></a>2.1.1 意图识别</h3><p>本质上是短文本分类任务，一般的文本分类算法都可以处理</p><p>传统算法：</p><ul><li>LR</li><li>SVM</li><li>KNN</li><li>RF</li><li>GBDT</li><li>…</li></ul><p>深度学习方法</p><ul><li>Fasttext</li><li>TextCNN</li><li>GRU</li><li>LSTM</li><li>IDCNN</li><li>TextRNN</li></ul><p>经调研，预训练fasttext词向量+单层textcnn从分类效果和速度上都相对较优，作为优先选择。</p><p>TextCNN的改进：</p><ul><li>K-max pooling</li><li>DPCNN</li><li>…</li></ul><h3 id="2-1-2-槽填充"><a href="#2-1-2-槽填充" class="headerlink" title="2.1.2 槽填充"></a>2.1.2 槽填充</h3><ul><li>CRF</li><li>RNN/LSTM/CNN+CRF</li><li>BiLSTM+CRF</li><li>BiLSTM+CNN+CRF</li></ul><h2 id="2-2-joint-model"><a href="#2-2-joint-model" class="headerlink" title="2.2 joint model"></a>2.2 joint model</h2><ul><li><p><a href="https://github.com/HadoopIt/rnn-nlu">A TensorFlow implementation of Recurrent Neural Networks for Sequence Classification and Sequence Labeling</a></p></li><li><p><a href="https://github.com/applenob/RNN-for-Joint-NLU">Attention-Based Recurrent Neural Network Models for Joint Intent Detection and Slot Filling</a></p></li><li><p><a href="https://www.coursera.org/lecture/language-processing/intent-classifier-and-slot-tagger-nlu-RmVnE">https://www.coursera.org/lecture/language-processing/intent-classifier-and-slot-tagger-nlu-RmVnE</a></p></li><li><p><a href="https://github.com/AtmaHou/Task-Oriented-Dialogue-Dataset-Survey">Task-Oriented Dialogue Dataset Survey</a></p></li></ul><p>其中第三条提到的模型: <a href="https://arxiv.org/pdf/1705.03122.pdf">Convolutional Sequence to Sequence Learning</a></p><h1 id="3-企业做法"><a href="#3-企业做法" class="headerlink" title="3. 企业做法"></a>3. 企业做法</h1><h2 id="3-1-阿里小蜜"><a href="#3-1-阿里小蜜" class="headerlink" title="3.1 阿里小蜜"></a>3.1 阿里小蜜</h2><p>Arxiv: <a href="https://arxiv.org/pdf/1801.05032.pdf">AliMe Assist: An Intelligent Assistant for Creating an Innovative E-commerce Experience</a></p><p>note: 经内部人员考证，这套框架太老已弃用</p><ul><li>business rule parser: 大量的样式(patterns)组成的前缀树匹配结构(trie-based)</li><li>Intention classifier: 场景分类，pre-train采用fasttext，分类采用单层cnn<ul><li>requesting for assistance</li><li>asking for information or solution</li><li>chatting</li></ul></li><li>Semantic Parser: a trie-based, 匹配知识图谱中的实体</li></ul><h2 id="3-2-美团"><a href="#3-2-美团" class="headerlink" title="3.2 美团"></a>3.2 美团</h2><p>参考：<a href="https://www.infoq.cn/article/w0EfTYKY29I8All*bG6G">美团对话理解技术及实践</a></p><p>上下文无关文法，工具，规则的写法</p><h1 id="4-数据"><a href="#4-数据" class="headerlink" title="4. 数据"></a>4. 数据</h1><ul><li><a href="https://spaces.ac.cn/archives/4338">【语料】百度的中文问答数据集WebQA</a></li><li><a href="https://github.com/SophonPlus/ChineseNlpCorpus">SophonPlus/ChineseNlpCorpus</a></li><li><a href="https://github.com/candlewill/Dialog_Corpus">candlewill/Dialog_Corpus</a>: 用于训练中英文对话系统的语料库 Datasets for Training Chatbot System</li><li><a href="https://github.com/brightmart/nlp_chinese_corpus">brightmart/nlp_chinese_corpus</a>: 大规模中文自然语言处理语料 Large Scale Chinese Corpus for NLP</li></ul><h1 id="5-开源工具"><a href="#5-开源工具" class="headerlink" title="5. 开源工具"></a>5. 开源工具</h1><h2 id="5-1-ChatterBot"><a href="#5-1-ChatterBot" class="headerlink" title="5.1 ChatterBot"></a>5.1 ChatterBot</h2><p><a href="https://github.com/gunthercox/ChatterBot">github 9.1k</a> </p><p>没有NLU模块，做法是匹配式，训练的输入是一系列完整的对话过程，数据库存储。</p><p><img src="https://chatterbot.readthedocs.io/en/stable/_images/training-graph.svg" alt=""></p><p>通过<code>Logic adapters</code>来获取输出结果</p><ul><li>BestMatch</li><li>TimeLogicAdapter</li><li>MathematicalEvaluation</li></ul><p>这个框架主要对问题文本 使用<strong>相似度匹配</strong>，找出库中预定好的答案。 比较适合，<strong>知识问答类</strong>的情形。</p><h2 id="5-2-rasa"><a href="#5-2-rasa" class="headerlink" title="5.2 rasa"></a>5.2 rasa</h2><ul><li><a href="https://github.com/RasaHQ/rasa">rasa 6.8k</a></li><li><a href="https://github.com/crownpku/Rasa_NLU_Chi">Rasa_NLU_Chi 848</a></li></ul><p><strong>数据</strong></p><ul><li>语料标注工具：<a href="https://rasahq.github.io/rasa-nlu-trainer/">rasa-nlu-trainer</a></li><li>数据生成工具：<a href="https://rodrigopivi.github.io/Chatito/">chatito</a></li></ul><p><strong>意图识别</strong></p><ul><li>KeywordIntentClassifier：This classifier is mostly used as a placeholder. It is able to recognize hello and goodbye intents by searching for these keywords in the passed messages.</li><li>MitieIntentClassifier： This classifier uses MITIE to perform intent classification. The underlying classifier is using a <strong>multi-class linear SVM with a sparse linear kernel</strong> 。</li><li>SklearnIntentClassifier： The sklearn intent classifier trains a linear SVM which gets optimized using a grid search.需要前置feature extractor</li><li>EmbeddingIntentClassifier： The embedding intent classifier embeds user inputs and intent labels into the same space. Supervised embeddings are trained by maximizing similarity between them. This algorithm is based on <a href="https://arxiv.org/abs/1709.03856">StarSpace</a>.</li></ul><p><strong>实体识别</strong></p><ul><li>MitieEntityExtractor：The underlying classifier is using a multi class linear SVM with a sparse linear kernel and custom features</li><li>SpacyEntityExtractor：Using spaCy this component predicts the entities of a message. spacy uses a statistical BILOU transition model. </li><li>EntitySynonymMapper： Maps synonymous entity values to the same value. 通过数据中的<code>value</code>来提供</li><li>CRFEntityExtractor：spaCy has to be installed. 貌似用的spaCy的实现</li><li>DucklingHTTPExtractor： Duckling lets you extract common entities like dates, amounts of money, distances, and others in a number of languages.</li></ul><p><strong>槽填充</strong></p><p><a href="https://rasa.com/docs/rasa/core/slots/">官方文档：slot的使用</a></p><p>参考：</p><ul><li><a href="https://github.com/GaoQ1/rasa-nlp-architect">GaoQ1/rasa-nlp-architect</a>: 采用nlp-architect实现rasa-nlu中文意图提取和槽填充</li><li>Building contextual assistants with Rasa Forms: <a href="https://blog.rasa.com/building-contextual-assistants-with-rasa-formaction/">原文</a>, <a href="https://zhuanlan.zhihu.com/p/84678559">译文</a></li></ul><p>均可自定义component: <a href="https://blog.rasa.com/enhancing-rasa-nlu-with-custom-components/">Enhancing Rasa NLU models with Custom Components</a></p><h2 id="5-3-DeepPavlov"><a href="#5-3-DeepPavlov" class="headerlink" title="5.3 DeepPavlov"></a>5.3 DeepPavlov</h2><p><a href="http://git.azurewebsites.net/deepmipt/DeepPavlov">deepmipt/DeepPavlov</a>: 3.6k</p><blockquote><p>An open source library for deep learning end-to-end dialog systems and chatbots. <a href="https://deeppavlov.ai/">https://deeppavlov.ai</a></p></blockquote><p>支持英文和俄语。功能全面，可作为学习参考。</p><p><img src="https://miro.medium.com/max/2326/1*DEHpboBRNsb7HY-NhL0G0A.png" alt=""></p><p>基本概念</p><ul><li><code>Agent</code> is a conversational agent communicating with users in natural language (text).</li><li><code>Skill</code> fulfills user’s goal in some domain. Typically, this is accomplished by presenting information or completing transaction (e.g. answer question by FAQ, booking tickets etc.). However, for some tasks a success of interaction is defined as continuous engagement (e.g. chit-chat).</li><li><code>Model</code> is any NLP model that doesn’t necessarily communicates with user in natural language.</li><li><code>Component</code> is a reusable functional part of <code>Model</code> or <code>Skill</code>.</li><li><code>Rule-based Models</code> cannot be trained.</li><li><code>Machine Learning Models</code> can be trained only stand alone.</li><li><code>Deep Learning Models</code> can be trained independently and in an end-to-end mode being joined in a chain.</li><li><code>Skill Manager</code> performs selection of the <code>Skill</code> to generate response.</li><li><code>Chainer</code> builds an agent/model pipeline from heterogeneous components (Rule-based/ML/DL). It allows to train and infer models in a pipeline as a whole.</li></ul><p>Models:</p><ul><li><a href="http://docs.deeppavlov.ai/en/master/features/overview.html#ner-model-docs">NER model </a><a href="http://docs.deeppavlov.ai/en/master/features/models/ner.html">[docs]</a>: BERT-based and Bi-LSTM+CRF.</li><li><a href="http://docs.deeppavlov.ai/en/master/features/overview.html#slot-filling-models-docs">Slot filling models </a><a href="http://docs.deeppavlov.ai/en/master/features/models/slot_filling.html">[docs]</a>: </li><li><a href="http://docs.deeppavlov.ai/en/master/features/overview.html#classification-model-docs">Classification model </a><a href="http://docs.deeppavlov.ai/en/master/features/models/classifiers.html">[docs]</a></li><li><a href="http://docs.deeppavlov.ai/en/master/features/overview.html#automatic-spelling-correction-model-docs">Automatic spelling correction model </a><a href="http://docs.deeppavlov.ai/en/master/features/models/spelling_correction.html">[docs]</a></li><li><a href="http://docs.deeppavlov.ai/en/master/features/overview.html#ranking-model-docs">Ranking model </a><a href="http://docs.deeppavlov.ai/en/master/features/models/neural_ranking.html">[docs]</a></li><li><a href="http://docs.deeppavlov.ai/en/master/features/overview.html#tf-idf-ranker-model-docs">TF-IDF Ranker model </a><a href="http://docs.deeppavlov.ai/en/master/features/models/tfidf_ranking.html">[docs]</a></li><li><a href="http://docs.deeppavlov.ai/en/master/features/overview.html#question-answering-model-docs">Question Answering model </a><a href="http://docs.deeppavlov.ai/en/master/features/models/squad.html">[docs]</a></li><li><a href="http://docs.deeppavlov.ai/en/master/features/overview.html#morphological-tagging-model-docs">Morphological tagging model </a><a href="http://docs.deeppavlov.ai/en/master/features/models/morphotagger.html">[docs]</a></li><li><a href="http://docs.deeppavlov.ai/en/master/features/overview.html#frequently-asked-questions-faq-model-docs">Frequently Asked Questions (FAQ) model </a><a href="http://docs.deeppavlov.ai/en/master/features/skills/faq.html">[docs]</a></li></ul><p><strong>意图识别</strong></p><ul><li><strong>BERT classifier</strong> (see <a href="http://docs.deeppavlov.ai/en/master/apiref/models/bert.html">here</a>) builds BERT <a href="http://docs.deeppavlov.ai/en/master/features/models/classifiers.html#id21">8</a> architecture for classification problem on Tensorflow.</li><li><strong>Keras classifier</strong> (see <a href="http://docs.deeppavlov.ai/en/master/apiref/models/classifiers.html">here</a>) builds neural network on Keras with tensorflow backend.</li><li><strong>Sklearn classifier</strong> (see <a href="http://docs.deeppavlov.ai/en/master/apiref/models/sklearn.html">here</a>) builds most of sklearn classifiers.</li></ul><p>模型很丰富</p><p><strong>NER</strong></p><ul><li>standard RNN based and BERT based. </li><li>Multilingual BERT Zero-Shot Transfer</li><li>Few-shot Language-Model based</li></ul><p><strong>槽填充</strong></p><p>官方文档: <a href="http://docs.deeppavlov.ai/en/master/features/models/slot_filling.html">Neural Named Entity Recognition and Slot Filling</a></p><blockquote><p>This model solves Slot-Filling task using Levenshtein search and different neural network architectures for NER. </p><p>Slotfiller will perform fuzzy search through the all variations of all entity values of given entity type. The entity type is determined by the NER component.</p></blockquote><p>使用博客：<a href="https://github.com/deepmipt/dp_notebooks">DeepPavlov articles with Python code</a></p><p><strong>规则编写</strong></p><p>只见到了对话规则的编写，通过<code>PatternMatchingSkill</code>，使用正则编写pattern和response</p><p>有一个包装rasa的<code>Rasa Skill</code></p><p><strong>DeepPavlov存在的问题</strong></p><ol><li>环境依赖<ul><li>DeepPavlov是基于TensorFlow和Keras实现的，不能继承其他计算框架的模型实现（如PyTorch）。</li></ul></li><li>语言支持<ul><li>Pre-train模型和评测数据集主要基于英文和俄文，不支持中文。</li></ul></li><li>生产环境部署<ul><li>DeepPavlov在运行时需要依赖整个框架源码，开发环境对框架修改后，生产环境需要更新整个框架。</li><li>也不能直接将功能Component作为服务独立导出，不适合在生产环境的部署和发布。</li></ul></li></ol><h2 id="5-4-Snips-nlu"><a href="#5-4-Snips-nlu" class="headerlink" title="5.4 Snips-nlu"></a>5.4 Snips-nlu</h2><p><a href="http://git.azurewebsites.net/snipsco/snips-nlu">snipsco/snips-nlu</a>: 3k</p><blockquote><p>Snips Python library to extract meaning from text <a href="https://snips-nlu.readthedocs.io/">https://snips-nlu.readthedocs.io</a></p></blockquote><p>不支持中文</p><p><a href="https://snips-nlu.readthedocs.io/en/latest/tutorial.html">Tutorial</a>： 意图和槽值都放在训练数据中了</p><pre><code class="hljs yaml"><span class="hljs-comment"># turnLightOn intent</span><span class="hljs-meta">---</span><span class="hljs-attr">type:</span> <span class="hljs-string">intent</span><span class="hljs-attr">name:</span> <span class="hljs-string">turnLightOn</span><span class="hljs-attr">slots:</span>  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">room</span>    <span class="hljs-attr">entity:</span> <span class="hljs-string">room</span><span class="hljs-attr">utterances:</span>  <span class="hljs-bullet">-</span> <span class="hljs-string">Turn</span> <span class="hljs-string">on</span> <span class="hljs-string">the</span> <span class="hljs-string">lights</span> <span class="hljs-string">in</span> <span class="hljs-string">the</span> [<span class="hljs-string">room</span>]<span class="hljs-string">(kitchen)</span>  <span class="hljs-bullet">-</span> <span class="hljs-string">give</span> <span class="hljs-string">me</span> <span class="hljs-string">some</span> <span class="hljs-string">light</span> <span class="hljs-string">in</span> <span class="hljs-string">the</span> [<span class="hljs-string">room</span>]<span class="hljs-string">(bathroom)</span> <span class="hljs-string">please</span>  <span class="hljs-bullet">-</span> <span class="hljs-string">Can</span> <span class="hljs-string">you</span> <span class="hljs-string">light</span> <span class="hljs-string">up</span> <span class="hljs-string">the</span> [<span class="hljs-string">room</span>]<span class="hljs-string">(living</span> <span class="hljs-string">room)</span> <span class="hljs-string">?</span>  <span class="hljs-bullet">-</span> <span class="hljs-string">switch</span> <span class="hljs-string">the</span> [<span class="hljs-string">room</span>]<span class="hljs-string">(bedroom)&#x27;s</span> <span class="hljs-string">lights</span> <span class="hljs-string">on</span> <span class="hljs-string">please</span></code></pre><blockquote><p>This parser parses text using two steps: first it classifies the intent using an <a href="https://snips-nlu.readthedocs.io/en/latest/api.html#snips_nlu.intent_classifier.IntentClassifier"><code>IntentClassifier</code></a> and once the intent is known, it using a <a href="https://snips-nlu.readthedocs.io/en/latest/api.html#snips_nlu.slot_filler.SlotFiller"><code>SlotFiller</code></a> in order to extract the slots.</p></blockquote><p>IntentClassifier</p><ul><li>Logistic Regression</li><li>Feature extractor for text classification relying on ngrams tfidf and optionally word cooccurrences features</li><li>scikit-learn TfidfVectorizer</li><li>Featurizer that takes utterances and extracts ordered word cooccurrence features matrix from them</li></ul><p>SlotFiller</p><ul><li>Linear-Chain Conditional Random Fields</li></ul><h2 id="5-5-其他"><a href="#5-5-其他" class="headerlink" title="5.5 其他"></a>5.5 其他</h2><ul><li><a href="https://github.com/charlesXu86/Chatbot_CN">基于金融-司法领域(兼有闲聊性质)的聊天机器人</a></li><li><a href="https://github.com/GaoQ1/rasa_chatbot_cn">基于最新版本rasa搭建的对话系统demo</a></li><li><a href="https://github.com/Aguila-team/Chinese_NLU_by_using_RASA_NLU">使用 RASA NLU 来构建中文自然语言理解系统</a></li><li><a href="https://github.com/crownpku/Awesome-Chinese-NLP">crownpku/Awesome-Chinese-NLP</a></li></ul><hr><p>参考：</p><ol><li><a href="https://www.xuqingtang.top/2019/06/17/2019-06-17问答系统项目落地调研/">2019-06-17问答系统项目落地调研</a></li><li><a href="http://hainanumeeting.net/YSSNLP2019/file/17.pdf">YSSNLP2019 人机对话研究热点及前沿技术概述</a></li><li><a href="https://www.infoq.cn/article/w0EfTYKY29I8All*bG6G">美团对话理解技术及实践</a></li><li><a href="https://www.jianshu.com/p/d713678fddfb">对话系统 NLU/DM 任务详解</a></li><li><a href="http://www.shuang0420.com/2017/04/27/NLP笔记 - NLU之意图分类/">NLP笔记 - NLU之意图分类</a></li><li><a href="http://bbs.cnaiplus.com/thread-5258-1-1.html">自然语言理解中的槽位填充</a></li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;业务场景：小样本数据上的任务型对话理解。&lt;/p&gt;
&lt;p&gt;对话领域三类&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;问答类&lt;/li&gt;
&lt;li&gt;任务类&lt;/li&gt;
&lt;li&gt;闲聊类&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="自然语言处理" scheme="https://zekizz.github.io/categories/%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7%90%86/"/>
    
    
    <category term="NLP" scheme="https://zekizz.github.io/tags/NLP/"/>
    
    <category term="NER" scheme="https://zekizz.github.io/tags/NER/"/>
    
    <category term="NLU" scheme="https://zekizz.github.io/tags/NLU/"/>
    
    <category term="machine learning" scheme="https://zekizz.github.io/tags/machine-learning/"/>
    
  </entry>
  
  <entry>
    <title>configparser配置解析</title>
    <link href="https://zekizz.github.io/python/configparse/"/>
    <id>https://zekizz.github.io/python/configparse/</id>
    <published>2019-03-31T06:07:05.000Z</published>
    <updated>2019-10-13T09:20:48.297Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>使用configparser解析ini格式的配置文件<br><span id="more"></span></p><ul><li>lib: <a href="https://pypi.org/project/configparser/">https://pypi.org/project/configparser/</a></li><li>doc: <a href="https://docs.python.org/3/library/configparser.html">https://docs.python.org/3/library/configparser.html</a><pre><code class="hljs cmake">pip <span class="hljs-keyword">install</span> configparser</code></pre></li></ul><p>get的时候，如果key在DEFAULT段中，get设置的deflault无效，总是返回DEFAULT段中的值</p><p>ini格式的config文件示例<br><pre><code class="hljs ini"><span class="hljs-section">[DEFAULT]</span><span class="hljs-attr">ServerAliveInterval</span> = <span class="hljs-number">45</span><span class="hljs-attr">Compression</span> = <span class="hljs-literal">yes</span><span class="hljs-attr">CompressionLevel</span> = <span class="hljs-number">9</span><span class="hljs-attr">ForwardX11</span> = <span class="hljs-literal">yes</span><span class="hljs-section">[bitbucket.org]</span><span class="hljs-attr">User</span> = hg<span class="hljs-section">[topsecret.server.com]</span><span class="hljs-attr">Port</span> = <span class="hljs-number">50022</span><span class="hljs-attr">ForwardX11</span> = <span class="hljs-literal">no</span></code></pre></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;使用configparser解析ini格式的配置文件&lt;br&gt;</summary>
    
    
    
    <category term="python" scheme="https://zekizz.github.io/categories/python/"/>
    
    
    <category term="python" scheme="https://zekizz.github.io/tags/python/"/>
    
  </entry>
  
  <entry>
    <title>excel去除文本中的不可打印字符</title>
    <link href="https://zekizz.github.io/shu-ju-chu-li/excel-trick/"/>
    <id>https://zekizz.github.io/shu-ju-chu-li/excel-trick/</id>
    <published>2019-03-31T06:05:57.000Z</published>
    <updated>2020-08-29T15:24:37.105Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>处理csv文件的时候，有时文本中有一些不可打印的字符，影响诸如文件分行和excel的解析。<br>主要需要去除文本中的换行符和制表符。</p><span id="more"></span><h3 id="方法一：用CLEAN函数"><a href="#方法一：用CLEAN函数" class="headerlink" title="方法一：用CLEAN函数"></a>方法一：用CLEAN函数</h3><p> CLEAN函数可以删除文本中不能打印的字符。</p><p> 假如A1单元格包含换行符，可在B1单元格中输入公式：<br> <pre><code class="hljs 1c"><span class="hljs-string">&quot;=CLEAN(A1)&quot;</span></code></pre><br> 即可删除换行符。</p><h3 id="方法二：查找替换法-不好用"><a href="#方法二：查找替换法-不好用" class="headerlink" title="方法二：查找替换法(不好用)"></a>方法二：查找替换法(不好用)</h3><ol><li>按快捷键<strong>Ctrl+H</strong>，打开“查找和替换”对话框；</li><li>选择“查找内容”后的文本框，按住<strong>Alt</strong>键，在数字键盘中输入“<strong>0010</strong>”。需要注意的是这样输入后，在“查找内容”后的文本框中不会显示任何内容，但实际上是有的；</li><li>单击“全部替换”按钮，换行符将被全部替换。</li></ol><p>参考文献:<br><a href="http://blog.sina.com.cn/s/blog_49f78a4b0102e3br.html">如何快速批量删除Excel单元格中的“换行符”</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;处理csv文件的时候，有时文本中有一些不可打印的字符，影响诸如文件分行和excel的解析。&lt;br&gt;主要需要去除文本中的换行符和制表符。&lt;/p&gt;</summary>
    
    
    
    <category term="数据处理" scheme="https://zekizz.github.io/categories/%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86/"/>
    
    
    <category term="excel" scheme="https://zekizz.github.io/tags/excel/"/>
    
  </entry>
  
  <entry>
    <title>git笔记</title>
    <link href="https://zekizz.github.io/git/git-notes/"/>
    <id>https://zekizz.github.io/git/git-notes/</id>
    <published>2019-03-31T05:11:52.000Z</published>
    <updated>2020-08-29T15:25:28.422Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>git常用小结</p><span id="more"></span><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><p>获取配置信息<br><pre><code class="hljs brainfuck"><span class="hljs-comment">git config</span> <span class="hljs-literal">--</span><span class="hljs-comment">system</span> <span class="hljs-literal">--</span><span class="hljs-comment">list</span><span class="hljs-comment">git config</span> <span class="hljs-literal">--</span><span class="hljs-comment">local</span> <span class="hljs-literal">--</span><span class="hljs-comment">list</span></code></pre><br>git config<br><pre><code class="hljs stylus">git config <span class="hljs-attr">--global</span> user<span class="hljs-selector-class">.name</span> <span class="hljs-string">&quot;Your Name&quot;</span>git config <span class="hljs-attr">--global</span> user<span class="hljs-selector-class">.email</span> <span class="hljs-string">&quot;email@example.com&quot;</span></code></pre><br>github配置ssh</p><ol><li>本地生成ssh密钥对<pre><code class="hljs ebnf"><span class="hljs-attribute">ssh-keygen -t rsa</span></code></pre></li><li>密钥对生成完成后存放于当前用户 ~/.ssh 目录中，查看 id_rsa.pub<pre><code class="hljs arcade">cat ~<span class="hljs-regexp">/.ssh/i</span>d_rsa.pub</code></pre></li><li>添加入github的setting中</li></ol><h2 id="常用命令"><a href="#常用命令" class="headerlink" title="常用命令"></a>常用命令</h2><pre><code class="hljs perl">git diff --cached  <span class="hljs-comment"># 查看已经暂存起来的文件和上次提交时的快照之间的差异</span>git diff --staged  <span class="hljs-comment"># Git 1.6.1及更高版本，效果同上</span>git commit -a -m <span class="hljs-string">&#x27;comment&#x27;</span>  <span class="hljs-comment"># 自动把所有已经跟踪过的文件暂存起来一并提交</span>git commit --amend  <span class="hljs-comment"># 撤销操作重新提交</span>    <span class="hljs-comment"># 只生成一个commit</span>    git commit -m <span class="hljs-string">&#x27;initial commit&#x27;</span>    git add forgotten_file    git commit --amendgit rm --cached filename  <span class="hljs-comment"># 移除跟踪但不删除文件</span>git <span class="hljs-keyword">log</span>     --pretty=oneline  <span class="hljs-comment"># 每个提交放在一行显示, 其他：short，full 和 fuller</span>    --pretty=<span class="hljs-keyword">format</span>:<span class="hljs-string">&quot;%h %s&quot;</span>  <span class="hljs-comment"># 更加简洁的信息 </span>    --graph  <span class="hljs-comment"># oneline 或 format 时结合 --graph 选项</span>    - p  <span class="hljs-comment"># 显示每次提交的内容差异</span>    - <span class="hljs-number">2</span>  <span class="hljs-comment"># 显示最近的两次更新</span>git <span class="hljs-keyword">log</span> --graph --pretty=oneline  <span class="hljs-comment"># 常用查看log</span>git <span class="hljs-keyword">reset</span> HEAD &lt;file&gt;  <span class="hljs-comment"># 已经add, 把暂存区的修改撤销掉</span>git checkout -- &lt;file&gt;  <span class="hljs-comment"># 还未add, 撤销工作区的修改</span><span class="hljs-comment"># git log 查看版本号，再版本回退，若想再次恢复到新版本，git reflog 查看版本号</span>git <span class="hljs-keyword">reset</span> --hard [commit id]  git checkout branchname  <span class="hljs-comment"># 切换分支</span>git checkout -b brachname  <span class="hljs-comment"># 创建并切换分支</span>git branch -d branchname  <span class="hljs-comment"># 删除分支</span><span class="hljs-comment"># 强制禁用Fast forward模式，Git就会在merge时生成一个新的commit</span>git merge --<span class="hljs-keyword">no</span>-ff -m <span class="hljs-string">&#x27;comment&#x27;</span> branch  git stash  <span class="hljs-comment"># bug 分支</span>    <span class="hljs-number">1</span>. 在当前分支git stash，工作区恢复到最近一次commit    <span class="hljs-number">2</span>. 处理完其他分支问题    <span class="hljs-number">3</span>. 在当前分支git stash list查看stash内容    <span class="hljs-number">4</span>. git stash <span class="hljs-keyword">pop</span>，恢复并删除stashgit remote -v  <span class="hljs-comment"># 查看远程库分支</span>git <span class="hljs-keyword">push</span> origin master/dev  <span class="hljs-comment"># 推送分支</span>git checkout -b dev origin/dev  <span class="hljs-comment"># 创建远程origin的dev分支到本地，需先创建本地dev分支</span>git pull  <span class="hljs-comment"># 拉取远程到本地，遇到推送有冲突的时候，先 git pull，本地解决冲突，再push</span>git remote add origin git@github.com:xxx/xxx.git  <span class="hljs-comment"># 关联远程库</span></code></pre>]]></content>
    
    
    <summary type="html">&lt;p&gt;git常用小结&lt;/p&gt;</summary>
    
    
    
    <category term="git" scheme="https://zekizz.github.io/categories/git/"/>
    
    
    <category term="git" scheme="https://zekizz.github.io/tags/git/"/>
    
  </entry>
  
  <entry>
    <title>基于句法依存树的信息抽取</title>
    <link href="https://zekizz.github.io/zi-ran-yu-yan-chu-li/information-extraction/"/>
    <id>https://zekizz.github.io/zi-ran-yu-yan-chu-li/information-extraction/</id>
    <published>2018-12-16T11:34:59.000Z</published>
    <updated>2020-08-29T15:28:46.156Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>信息抽取是一个常见的nlp任务，为经常一起提到的知识图谱的基础。</p><p>这里有一份比较好的知识图谱入门资料:<br><a href="https://kgtutorial.github.io/">Mining Knowledge Graphs from Text</a></p><p>信息抽取分为有监督和无监督方法。实际中监督信息往往是缺失的，所以本文主要提无监督方法。</p><p>在无监督方法中，一个广泛采用的工具是句法依存树，或者叫句法解析树（Dependency Tree）。<br><span id="more"></span></p><p>可视化工具：</p><ul><li><a href="http://spyysalo.github.io/conllu.js/">conllu.js</a></li><li><a href="http://nlp.nju.edu.cn/tanggc/tools/DependencyViewer.html">Dependency Viewer</a></li></ul><p>工作中总结出来的一种基于句法解析树的信息提取的一般方法（尚未完善）：</p><ol><li>从CONLL格式的句法依存解析结果，生成具备孩子节点和父节点索引的树，并获取根结点root_id；</li><li>自上而下递归的进行解析，孩子节点的解析结果上传到父节点进行汇总；<ol><li>如果当前节点为叶子节点（无孩子节点），判断当前节点类型，返回dict，上传给父节点；</li><li>如果当前词为动词（一般句子的核心是动词，一般方法也都是从动词开始扩展）或者用户指定的目标词，根据制定的规则提取指定关系类型的孩子，如定中关系、状中关系、动宾关系、主谓关系等，这里需要注意的是并列关系，有可能是补充，有可能是同级并列；</li><li>如果当前词非动词，先判断其所属信息类型，若能判断，关系向孩子传递，如不能判断，等待孩子节点上传的结果；</li><li>合并所有孩子的信息，按原始句子顺序排序</li></ol></li><li>修正后处理</li></ol><p>以提取文本中，关于道路信息的（时间、原因、地点、时间）四元组为例，输入句子：</p><pre><code class="hljs makefile"><span class="hljs-section">黄石高速:因交通管制,晋州站、辛集站、藁城东站双向关闭。因沧州服务区附近K81处黄骅方向发生交通事故,沧州服务区附近K81处黄骅方向车辆缓慢通行约4公里</span></code></pre><p>句法解析树：</p><pre><code class="hljs apache"><span class="hljs-attribute">1</span>黄石黄石nhnr_<span class="hljs-number">3</span>SBV__<span class="hljs-attribute">2</span>高速高速dd_<span class="hljs-number">3</span>ADV__<span class="hljs-attribute">3</span>::vv_<span class="hljs-number">0</span>HED__<span class="hljs-attribute">4</span>因因pp_<span class="hljs-number">14</span>ADV__<span class="hljs-attribute">5</span>交通管制交通管制vv_<span class="hljs-number">4</span>POB__<span class="hljs-attribute">6</span>,,wpw_<span class="hljs-number">4</span>WP__<span class="hljs-attribute">7</span>晋州站晋州站nsns_<span class="hljs-number">14</span>SBV__<span class="hljs-attribute">8</span>、、wpw_<span class="hljs-number">9</span>WP__<span class="hljs-attribute">9</span>辛集站辛集站nn_<span class="hljs-number">7</span>COO__<span class="hljs-attribute">10</span>、、wpw_<span class="hljs-number">12</span>WP__<span class="hljs-attribute">11</span>藁城藁城nsns_<span class="hljs-number">12</span>ATT__<span class="hljs-attribute">12</span>东站东站nn_<span class="hljs-number">7</span>COO__<span class="hljs-attribute">13</span>双向双向dd_<span class="hljs-number">14</span>ADV__<span class="hljs-attribute">14</span>关闭关闭vv_<span class="hljs-number">3</span>COO__<span class="hljs-attribute">15</span>。。wpw_<span class="hljs-number">3</span>WP__<span class="hljs-attribute">16</span>因因pp_<span class="hljs-number">24</span>ADV__<span class="hljs-attribute">17</span>沧州沧州nsns_<span class="hljs-number">18</span>ATT__<span class="hljs-attribute">18</span>服务区服务区nn_<span class="hljs-number">19</span>ATT__<span class="hljs-attribute">19</span>附近附近ndf_<span class="hljs-number">21</span>ATT__<span class="hljs-attribute">20</span>K81K81wsnx_<span class="hljs-number">21</span>ATT__<span class="hljs-attribute">21</span>处处nn_<span class="hljs-number">22</span>ATT__<span class="hljs-attribute">22</span>黄骅黄骅nsns_<span class="hljs-number">23</span>ATT__<span class="hljs-attribute">23</span>方向方向nn_<span class="hljs-number">16</span>POB__<span class="hljs-attribute">24</span>发生发生vv_<span class="hljs-number">3</span>COO__<span class="hljs-attribute">25</span>交通事故交通事故nn_<span class="hljs-number">24</span>VOB__<span class="hljs-attribute">26</span>,,wpw_<span class="hljs-number">24</span>WP__<span class="hljs-attribute">27</span>沧州沧州nsns_<span class="hljs-number">28</span>ATT__<span class="hljs-attribute">28</span>服务区服务区nn_<span class="hljs-number">29</span>ATT__<span class="hljs-attribute">29</span>附近附近ndf_<span class="hljs-number">31</span>ATT__<span class="hljs-attribute">30</span>K81K81wsnx_<span class="hljs-number">31</span>ATT__<span class="hljs-attribute">31</span>处处nn_<span class="hljs-number">34</span>ATT__<span class="hljs-attribute">32</span>黄骅黄骅nsns_<span class="hljs-number">33</span>ATT__<span class="hljs-attribute">33</span>方向方向nn_<span class="hljs-number">34</span>ATT__<span class="hljs-attribute">34</span>车辆车辆nn_<span class="hljs-number">36</span>SBV__<span class="hljs-attribute">35</span>缓慢缓慢aad_<span class="hljs-number">36</span>ADV__<span class="hljs-attribute">36</span>通行通行vv_<span class="hljs-number">24</span>COO__<span class="hljs-attribute">37</span>约约dd_<span class="hljs-number">38</span>ATT__<span class="hljs-attribute">38</span><span class="hljs-number">4</span><span class="hljs-number">4</span>mm_<span class="hljs-number">39</span>ATT__<span class="hljs-attribute">39</span>公里公里qq_<span class="hljs-number">36</span>CMP__<span class="hljs-attribute">40</span>。。wpw_<span class="hljs-number">3</span>WP__</code></pre><p><img src="https://picbed-1252770021.cos.ap-chengdu.myqcloud.com/NLP/test_dependency.png" alt=""><br>核心代码如下：</p><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">WordBean</span>(<span class="hljs-title class_ inherited__">object</span>):   <span class="hljs-string">&#x27;&#x27;&#x27; 扩展conllword，存储父节点与孩子节点索引 &#x27;&#x27;&#x27;</span>       <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self</span>):           self.lemma = <span class="hljs-literal">None</span>           self.postag = <span class="hljs-literal">None</span>           self.relation = <span class="hljs-literal">None</span>           self.head_id = <span class="hljs-literal">None</span>           self.flag = <span class="hljs-literal">True</span>  <span class="hljs-comment"># 是否还处于树中标志位，已合并的ATT将置为False</span>           self.child = []          <span class="hljs-keyword">def</span> <span class="hljs-title function_">set_word</span>(<span class="hljs-params">self, conll_word</span>):           self.lemma = conll_word.LEMMA           self.postag = conll_word.POSTAG           self.relation = conll_word.DEPREL           self.head_id = conll_word.HEAD.ID - <span class="hljs-number">1</span>          <span class="hljs-keyword">def</span> <span class="hljs-title function_">add_child</span>(<span class="hljs-params">self, child_id, child_relation</span>):           self.child.append((child_id, child_relation))             <span class="hljs-keyword">def</span> <span class="hljs-title function_">extract_entity_tuple</span>(<span class="hljs-params">self, dependency_tree, seed_id, head_type=<span class="hljs-string">&#x27;&#x27;</span></span>):       <span class="hljs-string">&#x27;&#x27;&#x27;自上而下解析树&#x27;&#x27;&#x27;</span>       res_entity_tuple_list = []       res_dict = <span class="hljs-built_in">dict</span>()       res_dict[<span class="hljs-string">&#x27;time&#x27;</span>] = []       res_dict[<span class="hljs-string">&#x27;reason&#x27;</span>] = []       res_dict[<span class="hljs-string">&#x27;place&#x27;</span>] = []       res_dict[<span class="hljs-string">&#x27;status&#x27;</span>] = []       <span class="hljs-comment"># 判断是否是叶子节点</span>       <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(dependency_tree[seed_id].child) == <span class="hljs-number">0</span>:           <span class="hljs-keyword">if</span> dependency_tree[seed_id].lemma <span class="hljs-keyword">in</span> self.status_set:               res_dict[<span class="hljs-string">&#x27;status&#x27;</span>].append((seed_id, dependency_tree[seed_id].lemma))           <span class="hljs-keyword">elif</span> self.check_is_time(dependency_tree, seed_id):               res_dict[<span class="hljs-string">&#x27;time&#x27;</span>].append((seed_id, dependency_tree[seed_id].lemma))           <span class="hljs-keyword">elif</span> head_type == <span class="hljs-string">&#x27;reason&#x27;</span>:               res_dict[<span class="hljs-string">&#x27;reason&#x27;</span>].append((seed_id, dependency_tree[seed_id].lemma))           <span class="hljs-keyword">elif</span> head_type == <span class="hljs-string">&#x27;time&#x27;</span>:               res_dict[<span class="hljs-string">&#x27;time&#x27;</span>].append((seed_id, dependency_tree[seed_id].lemma))           <span class="hljs-keyword">else</span>:               <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> dependency_tree[seed_id].lemma <span class="hljs-keyword">in</span> self.discard_word_set:                   res_dict[<span class="hljs-string">&#x27;place&#x27;</span>].append((seed_id, dependency_tree[seed_id].lemma))           res_entity_tuple_list.append(res_dict)           <span class="hljs-keyword">return</span> res_entity_tuple_list       <span class="hljs-comment"># 非叶子节点需要向下递归解析</span>       <span class="hljs-keyword">if</span> dependency_tree[seed_id].lemma <span class="hljs-keyword">in</span> self.status_set:           <span class="hljs-comment"># 当前节点为状态节点</span>           status_merge_list = []           <span class="hljs-keyword">for</span> c_id, c_relation <span class="hljs-keyword">in</span> dependency_tree[seed_id].child:               child_bean = dependency_tree[c_id]               <span class="hljs-keyword">if</span> c_relation <span class="hljs-keyword">in</span> (<span class="hljs-string">&#x27;COO&#x27;</span>, <span class="hljs-string">&#x27;并列关系&#x27;</span>):                   <span class="hljs-comment"># 假设状态下不存在嵌套状态，有视为补充</span>                   <span class="hljs-keyword">if</span> dependency_tree[c_id].postag == <span class="hljs-string">&#x27;v&#x27;</span> <span class="hljs-keyword">and</span> <span class="hljs-built_in">len</span>(dependency_tree[c_id].child) == <span class="hljs-number">0</span>:                       res_dict[<span class="hljs-string">&#x27;status&#x27;</span>].append((c_id, dependency_tree[c_id].lemma))                   <span class="hljs-keyword">else</span>:                       child_dict_list = self.extract_entity_tuple(dependency_tree, c_id)                       <span class="hljs-keyword">for</span> child_dict <span class="hljs-keyword">in</span> child_dict_list:                           self.merge_two_tuple_dict(res_dict, child_dict)               <span class="hljs-keyword">elif</span> c_relation <span class="hljs-keyword">in</span> (<span class="hljs-string">&#x27;ADV&#x27;</span>, <span class="hljs-string">&#x27;状中结构&#x27;</span>):                   <span class="hljs-comment"># 处理状中结构</span>                   <span class="hljs-keyword">if</span> child_bean.lemma <span class="hljs-keyword">in</span> (<span class="hljs-string">&#x27;因&#x27;</span>, <span class="hljs-string">&#x27;受&#x27;</span>, <span class="hljs-string">&#x27;由于&#x27;</span>):                       <span class="hljs-comment"># 处理原因</span>                       child_dict_list = self.extract_entity_tuple(dependency_tree, c_id)                       <span class="hljs-keyword">for</span> child_dict <span class="hljs-keyword">in</span> child_dict_list:                           self.merge_two_tuple_dict(res_dict, child_dict)                   <span class="hljs-keyword">elif</span> child_bean.lemma == <span class="hljs-string">&#x27;处&#x27;</span> <span class="hljs-keyword">or</span> child_bean.postag == <span class="hljs-string">&#x27;p&#x27;</span>:                       child_dict_list = self.extract_entity_tuple(dependency_tree, c_id)                       <span class="hljs-keyword">for</span> child_dict <span class="hljs-keyword">in</span> child_dict_list:                           self.merge_two_tuple_dict(res_dict, child_dict)                   <span class="hljs-keyword">elif</span> child_bean.postag <span class="hljs-keyword">in</span> (<span class="hljs-string">&#x27;a&#x27;</span>, <span class="hljs-string">&#x27;ad&#x27;</span>, <span class="hljs-string">&#x27;d&#x27;</span>):                       self.merge_att(dependency_tree, c_id)                       status_merge_list.append(c_id)                   <span class="hljs-keyword">elif</span> self.check_is_time(dependency_tree, c_id):                       self.merge_att(dependency_tree, c_id)                       res_dict[<span class="hljs-string">&#x27;time&#x27;</span>].append((c_id, dependency_tree[c_id].lemma))               <span class="hljs-keyword">elif</span> c_relation <span class="hljs-keyword">in</span> (<span class="hljs-string">&#x27;POB&#x27;</span>, <span class="hljs-string">&#x27;介宾关系&#x27;</span>) <span class="hljs-keyword">and</span>                        child_bean.lemma <span class="hljs-keyword">in</span> (<span class="hljs-string">&#x27;因&#x27;</span>, <span class="hljs-string">&#x27;受&#x27;</span>, <span class="hljs-string">&#x27;由于&#x27;</span>):                   self.merge_att(dependency_tree, c_id)                   res_dict[<span class="hljs-string">&#x27;reason&#x27;</span>].append((c_id, dependency_tree[c_id].lemma))               <span class="hljs-keyword">elif</span> c_relation <span class="hljs-keyword">in</span> (<span class="hljs-string">&#x27;CMP&#x27;</span>, <span class="hljs-string">&#x27;动补结构&#x27;</span>):                   self.merge_att(dependency_tree, c_id)                   status_merge_list.append(c_id)               <span class="hljs-keyword">elif</span> c_relation <span class="hljs-keyword">in</span> (<span class="hljs-string">&#x27;SBV&#x27;</span>, <span class="hljs-string">&#x27;主谓关系&#x27;</span>):                   <span class="hljs-comment"># 处理主谓关系，解析具体地点</span>                   child_dict_list = self.extract_entity_tuple(dependency_tree, c_id)                   <span class="hljs-keyword">for</span> child_dict <span class="hljs-keyword">in</span> child_dict_list:                       self.merge_two_tuple_dict(res_dict, child_dict)               <span class="hljs-keyword">elif</span> c_relation <span class="hljs-keyword">in</span> (<span class="hljs-string">&#x27;VOB&#x27;</span>, <span class="hljs-string">&#x27;动宾关系&#x27;</span>):                   <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(dependency_tree[c_id].child) == <span class="hljs-number">0</span>:                       res_dict[<span class="hljs-string">&#x27;status&#x27;</span>].append((c_id, dependency_tree[c_id].lemma))                   <span class="hljs-keyword">else</span>:                       child_dict_list = self.extract_entity_tuple(dependency_tree, c_id)                       <span class="hljs-keyword">for</span> child_dict <span class="hljs-keyword">in</span> child_dict_list:                           self.merge_two_tuple_dict(res_dict, child_dict)           status_buffer = []           status_merge_list.append(seed_id)           status_merge_list.sort()           <span class="hljs-keyword">for</span> <span class="hljs-built_in">id</span> <span class="hljs-keyword">in</span> status_merge_list:               status_buffer.append(dependency_tree[<span class="hljs-built_in">id</span>].lemma)           res_dict[<span class="hljs-string">&#x27;status&#x27;</span>].append((seed_id, <span class="hljs-string">&#x27;&#x27;</span>.join(status_buffer)))           res_entity_tuple_list.append(res_dict)           <span class="hljs-keyword">return</span> res_entity_tuple_list       <span class="hljs-keyword">else</span>:           <span class="hljs-comment"># 当前节点为非状态节点</span>           pre_head_type = head_type           <span class="hljs-keyword">if</span> self.check_is_time(dependency_tree, seed_id):               <span class="hljs-comment"># 为时间节点</span>               head_type = <span class="hljs-string">&#x27;time&#x27;</span>               res_dict[<span class="hljs-string">&#x27;time&#x27;</span>].append((seed_id, dependency_tree[seed_id].lemma))           <span class="hljs-keyword">elif</span> dependency_tree[seed_id].lemma <span class="hljs-keyword">in</span> (<span class="hljs-string">&#x27;因&#x27;</span>, <span class="hljs-string">&#x27;受&#x27;</span>, <span class="hljs-string">&#x27;由于&#x27;</span>) <span class="hljs-keyword">or</span> head_type == <span class="hljs-string">&#x27;reason&#x27;</span>:               <span class="hljs-comment"># 为原因节点</span>               head_type = <span class="hljs-string">&#x27;reason&#x27;</span>               res_dict[<span class="hljs-string">&#x27;reason&#x27;</span>].append((seed_id, dependency_tree[seed_id].lemma))           <span class="hljs-keyword">else</span>:               <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> dependency_tree[seed_id].lemma <span class="hljs-keyword">in</span> self.discard_word_set:                   res_dict[<span class="hljs-string">&#x27;place&#x27;</span>].append((seed_id, dependency_tree[seed_id].lemma))           child_dict_list = []           coo_list = []           <span class="hljs-keyword">for</span> c_id, c_relation <span class="hljs-keyword">in</span> dependency_tree[seed_id].child:               <span class="hljs-comment"># if c_relation in (&#x27;COO&#x27;, &#x27;并列关系&#x27;):</span>               <span class="hljs-comment">#     coo_list.append(c_id)</span>               <span class="hljs-keyword">if</span> c_relation <span class="hljs-keyword">in</span> (<span class="hljs-string">&#x27;WP&#x27;</span>, <span class="hljs-string">&#x27;标点符号&#x27;</span>):                   <span class="hljs-keyword">continue</span>               <span class="hljs-keyword">else</span>:                   <span class="hljs-keyword">if</span> head_type == <span class="hljs-string">&#x27;reason&#x27;</span> <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> pre_head_type == <span class="hljs-string">&#x27;reason&#x27;</span>:                       <span class="hljs-keyword">if</span> c_relation <span class="hljs-keyword">in</span> (<span class="hljs-string">&#x27;POB&#x27;</span>, <span class="hljs-string">&#x27;介宾关系&#x27;</span>):                           child_dict_list.extend(self.extract_entity_tuple(dependency_tree, c_id, head_type))                           <span class="hljs-keyword">if</span> dependency_tree[c_id].lemma <span class="hljs-keyword">in</span> self.status_set:                               res_dict[<span class="hljs-string">&#x27;reason&#x27;</span>].append((c_id, dependency_tree[c_id].lemma))                   <span class="hljs-keyword">else</span>:                       child_dict_list.extend(self.extract_entity_tuple(dependency_tree, c_id, head_type))           <span class="hljs-comment"># 先合并非状态</span>           status_dict_list = []           <span class="hljs-keyword">for</span> child_dict <span class="hljs-keyword">in</span> child_dict_list:               <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(child_dict[<span class="hljs-string">&#x27;status&#x27;</span>]) &gt; <span class="hljs-number">0</span>:                   status_dict_list.append(child_dict)               <span class="hljs-keyword">else</span>:                   self.merge_two_tuple_dict(res_dict, child_dict)           <span class="hljs-comment"># 再合并存在状态的</span>           <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(status_dict_list) == <span class="hljs-number">0</span>:               res_entity_tuple_list.append(res_dict)           <span class="hljs-keyword">else</span>:               <span class="hljs-keyword">for</span> child_dict <span class="hljs-keyword">in</span> status_dict_list:                   <span class="hljs-comment"># tmp_dict = res_dict.copy()</span>                   tmp_dict = copy.deepcopy(res_dict)                   self.merge_two_tuple_dict(tmp_dict, child_dict)                   res_entity_tuple_list.append(tmp_dict)           <span class="hljs-keyword">return</span> res_entity_tuple_list   <span class="hljs-keyword">def</span> <span class="hljs-title function_">extract_information</span>(<span class="hljs-params">self, line</span>):       segs = self.nlp_tokenizer.seg(line)       <span class="hljs-comment"># fix segs</span>       self.fix_seged_postag(segs)       conll_words = self.parser.parse(segs).getWordArray()       dependency_tree, root_id = self.construct_dependency_tree(conll_words)       res_entity.append(self.extract_entity_tuple(dependency_tree, i))       res_entity = self.extract_entity_tuple(dependency_tree, root_id)       <span class="hljs-comment"># print entity tuples</span>       <span class="hljs-keyword">for</span> entity <span class="hljs-keyword">in</span> res_entity:           entity[<span class="hljs-string">&#x27;time&#x27;</span>].sort()           entity[<span class="hljs-string">&#x27;place&#x27;</span>].sort()           entity[<span class="hljs-string">&#x27;reason&#x27;</span>].sort()           entity[<span class="hljs-string">&#x27;status&#x27;</span>].sort()           self.fix_entity_tuple_dict(entity)           <span class="hljs-built_in">print</span>(entity)       <span class="hljs-keyword">return</span> res_entity</code></pre><p>实验结果：</p><pre><code class="hljs prolog"># 输入句子<span class="hljs-string">&#x27;黄石高速:因交通管制,晋州站、辛集站、藁城东站双向关闭。因沧州服务区附近K81处黄骅方向发生交通事故,沧州服务区附近K81处黄骅方向车辆缓慢通行约4公里。&#x27;</span># 信息提取结果&#123;<span class="hljs-string">&#x27;time&#x27;</span>: [], <span class="hljs-string">&#x27;reason&#x27;</span>: [<span class="hljs-string">&#x27;因交通管制&#x27;</span>], <span class="hljs-string">&#x27;place&#x27;</span>: [<span class="hljs-string">&#x27;黄石高速:, 晋州站, 辛集站, 藁城东站&#x27;</span>], <span class="hljs-string">&#x27;status&#x27;</span>: [<span class="hljs-string">&#x27;交通管制, 双向关闭&#x27;</span>]&#125;&#123;<span class="hljs-string">&#x27;time&#x27;</span>: [], <span class="hljs-string">&#x27;reason&#x27;</span>: [<span class="hljs-string">&#x27;因沧州服务区附近K81处黄骅方向&#x27;</span>], <span class="hljs-string">&#x27;place&#x27;</span>: [<span class="hljs-string">&#x27;黄石高速:, 发生交通事故, 沧州服务区附近K81处黄骅方向&#x27;</span>], <span class="hljs-string">&#x27;status&#x27;</span>: [<span class="hljs-string">&#x27;缓慢通行约4公里&#x27;</span>]&#125;</code></pre><p>可以看到，第一句的解析没问题，但是第二句原因的解析边界出错。当前的解析方法仍然比较依赖于句法依存树的准确性，实体的边界的准确性不够，也是需要改进的地方。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;信息抽取是一个常见的nlp任务，为经常一起提到的知识图谱的基础。&lt;/p&gt;
&lt;p&gt;这里有一份比较好的知识图谱入门资料:&lt;br&gt;&lt;a href=&quot;https://kgtutorial.github.io/&quot;&gt;Mining Knowledge Graphs from Text&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;信息抽取分为有监督和无监督方法。实际中监督信息往往是缺失的，所以本文主要提无监督方法。&lt;/p&gt;
&lt;p&gt;在无监督方法中，一个广泛采用的工具是句法依存树，或者叫句法解析树（Dependency Tree）。&lt;br&gt;</summary>
    
    
    
    <category term="自然语言处理" scheme="https://zekizz.github.io/categories/%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7%90%86/"/>
    
    
    <category term="自然语言处理" scheme="https://zekizz.github.io/tags/%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7%90%86/"/>
    
    <category term="NLP" scheme="https://zekizz.github.io/tags/NLP/"/>
    
  </entry>
  
  <entry>
    <title>样本类别不均衡处理</title>
    <link href="https://zekizz.github.io/ji-qi-xue-xi/imblanced-samples/"/>
    <id>https://zekizz.github.io/ji-qi-xue-xi/imblanced-samples/</id>
    <published>2018-12-16T11:29:59.000Z</published>
    <updated>2020-08-29T15:26:51.478Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>分类任务中样本类别不均衡是常有的事，当样本之间的不均衡程度较小的时候，可以不作处理，当正负样本比例较大（比如10:1）且训练数据较少的时候，就需要做不均衡的处理。常见的处理方式可以分为如下几类：</p><ul><li>采样方法<ul><li>下采样（或欠采样，under-sampling）</li><li>上采样（或过采样，over-sampling）</li><li>混合采样</li></ul></li><li>数据增强<ul><li>收集更多的数据</li><li>造数据</li></ul></li><li>更改评价指标</li><li>cost sensitive（代价敏感，class weight）</li><li>模型集成</li><li>one-class classifier<span id="more"></span></li></ul><h2 id="采样方法"><a href="#采样方法" class="headerlink" title="采样方法"></a>采样方法</h2><p>对于下采样，最简单方便的是随机采样。以两分类为例，这里涉及两个问题：</p><ol><li>应该采样哪些样本？</li><li>采样比例应该采样为1:1么？</li></ol><p>理想情况下，我们希望采样的样本能反应实际的数据空间分布。所以可以通过对多数类进行聚类，然后挑选中心。<a href="http://imbalanced-learn.org/en/stable/index.html">imblearn</a>中的<a href="http://imbalanced-learn.org/en/stable/generated/imblearn.under_sampling.ClusterCentroids.html#imblearn.under_sampling.ClusterCentroids">ClusterCentroids</a>就是这种思想。<a href="http://imbalanced-learn.org/en/stable/index.html">imblearn</a>还提供了其他几种样本挑选方式：</p><ol><li>NearMiss-1：到少数类样本的n近邻的平均距离的最小的多数样本</li><li>NearMiss-2：到n个最远的少数类样本的平均距离的最小的多数样本</li><li>NearMiss-3：对于每个少数类样本，先保留其多数类M近邻，再从中挑选N近邻平均距离最大的</li><li>EditedNearestNeighbours：通过近邻，移除与邻居差异较大的样本</li></ol><p>还有几种EditedNearestNeighbours的扩张不再赘述。以上几种方法的本质是<strong>挑选分类边界附近的样本</strong>。这一部分其实可以参考半监督学习，可参考周志华老师的一篇经典paper:<a href="https://papers.nips.cc/paper/4176-active-learning-by-querying-informative-and-representative-examples.pdf">Active Learning by Querying Informative and Representative Examples</a>，挑选最具信息量和最具代表性的样本。</p><p>对于上采样，这一部分与数据增强有一部分重叠，因为其本质是生成新样本。</p><p>简单copy少数样本，新生成的样本也就是数据集中样本的一个复制，这样对有些算法是无效的。</p><p>其次，可以通过简单差值的方式生成新样本。<br>最后，常用的两个方法：</p><ol><li>the Synthetic Minority Oversampling Technique (SMOTE)</li><li>the Adaptive Synthetic (ADASYN)</li></ol><h2 id="更改评价指标"><a href="#更改评价指标" class="headerlink" title="更改评价指标"></a>更改评价指标</h2><p>一般分类问题的评价指标为，准确率和p、r、f值。当类别失衡时，准确率就不太可信。<br>这是还可以使用AUC和ROC，但是AUC在类别不均衡时也不太可信，一般还要综合看一下PR曲线。</p><h2 id="代价敏感"><a href="#代价敏感" class="headerlink" title="代价敏感"></a>代价敏感</h2><p>我使用代价敏感这个词，最开始是在贝叶斯中学习得来的，有个最小风险贝叶斯估计。其中会指定一个风险矩阵，调整loss function。</p><p>一般机器学习方法的损失函数为交叉熵、log损失、最小二乘、指数损失、hinge损失等。<br>下面以深度学习中的交叉熵为例。</p><p>常用计算方式如下<br><pre><code class="hljs reasonml">losses = tf.nn.softmax<span class="hljs-constructor">_cross_entropy_with_logits(<span class="hljs-params">logits</span>=<span class="hljs-params">self</span>.<span class="hljs-params">scores</span>, <span class="hljs-params">labels</span>=<span class="hljs-params">self</span>.<span class="hljs-params">input_y</span>)</span></code></pre><br>那么能不能像sklearn那样简单添加class weight的方式来调整损失呢？</p><p>答案是可以的。tf中有一个API可以帮忙解决来，<strong>tf.losses.softmax_cross_entropy</strong>。<br>其中有一个参数为：weights。反映的是batch中每个样本的权重，我们可以通过生成这个weights来变相实现class_weight。具体方式如下：</p><pre><code class="hljs python">self.class_weight = tf.placeholder(tf.float32, shape=[<span class="hljs-number">1</span>, num_classes], name=<span class="hljs-string">&#x27;class_weight&#x27;</span>)sample_weights = tf.reduce_sum(tf.multiply(self.input_y, self.class_weight), <span class="hljs-number">1</span>) <span class="hljs-comment"># size of class_weights: [1, num_classes]</span>losses = tf.losses.softmax_cross_entropy(onehot_labels=self.input_y, logits=self.scores,                                                     weights=sample_weights)</code></pre><p>这里的class_weight可以手工指定，也可以通过训练数据计算得出。</p><pre><code class="hljs python">categoris = np.argmax(y_train, axis=<span class="hljs-number">1</span>)train_class_weight = n_train_samples / (n_classes * np.bincount(categoris))train_class_weight = train_class_weight.reshape(<span class="hljs-number">1</span>, n_classes)</code></pre><h2 id="模型集成"><a href="#模型集成" class="headerlink" title="模型集成"></a>模型集成</h2><p>模型集成也分为两种</p><ol><li>单纯的集成方法，比如adboost、gbdt、random forest等；</li><li>通过数据采样来造成训练样本差异</li></ol><p>第一种方法就不用说了，树模型天生对数据不均衡不敏感。<br>第二种方法，通过不同的采样率生成不同正负样本比例的数据集进行训练，然后再集成这些模型。</p><p>比如：</p><ul><li>分别设置采样率为1:1, 1:2, 1:3等的采样数据集</li><li>保留n个少数类样本，并随机抽取10*n 个多数类样本。然后，只需将 10*n个样本分成10份，并训练10个不同的模型。</li></ul><h2 id="one-class-classifier"><a href="#one-class-classifier" class="headerlink" title="one-class classifier"></a>one-class classifier</h2><p>转为一分类问题，one-class classifier本身为一种异常检查算法，尽量学得目标类别的边界。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;分类任务中样本类别不均衡是常有的事，当样本之间的不均衡程度较小的时候，可以不作处理，当正负样本比例较大（比如10:1）且训练数据较少的时候，就需要做不均衡的处理。常见的处理方式可以分为如下几类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;采样方法&lt;ul&gt;
&lt;li&gt;下采样（或欠采样，under-sampling）&lt;/li&gt;
&lt;li&gt;上采样（或过采样，over-sampling）&lt;/li&gt;
&lt;li&gt;混合采样&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;数据增强&lt;ul&gt;
&lt;li&gt;收集更多的数据&lt;/li&gt;
&lt;li&gt;造数据&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;更改评价指标&lt;/li&gt;
&lt;li&gt;cost sensitive（代价敏感，class weight）&lt;/li&gt;
&lt;li&gt;模型集成&lt;/li&gt;
&lt;li&gt;one-class classifier</summary>
    
    
    
    <category term="机器学习" scheme="https://zekizz.github.io/categories/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
    
    <category term="机器学习" scheme="https://zekizz.github.io/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="machine learning" scheme="https://zekizz.github.io/tags/machine-learning/"/>
    
  </entry>
  
  <entry>
    <title>html自定义标签</title>
    <link href="https://zekizz.github.io/qian-duan/html-tag/"/>
    <id>https://zekizz.github.io/qian-duan/html-tag/</id>
    <published>2018-10-12T14:30:17.000Z</published>
    <updated>2020-08-29T15:26:23.408Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>我们在分析文本时，比如命名实体识别，可能想将不同的实体词文本用不同的颜色高亮标识出来，那么采用html自定义标签就是一个比较轻量的方法。</p><span id="more"></span><p>给出一个例子<br><pre><code class="hljs html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-keyword">html</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>自定义标签Demo<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;text/css&quot;</span>&gt;</span><span class="language-css"></span><span class="language-css"></span><span class="language-css"><span class="hljs-selector-tag">time</span>&#123;</span><span class="language-css"><span class="hljs-attribute">color</span>: <span class="hljs-number">#2980B9</span> ;</span><span class="language-css">&#125;</span><span class="language-css"></span><span class="language-css">location&#123;</span><span class="language-css"><span class="hljs-attribute">color</span>: <span class="hljs-number">#8E44AD</span>;</span><span class="language-css">&#125;</span><span class="language-css"></span><span class="language-css">status&#123;</span><span class="language-css"><span class="hljs-attribute">color</span>: <span class="hljs-number">#FF0000</span>;</span><span class="language-css">&#125;</span><span class="language-css"></span><span class="language-css"></span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">&quot;margin: 20px&quot;</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>自定义标签：<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">time</span>&gt;</span>时间<span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">location</span>&gt;</span>地点<span class="hljs-tag">&lt;/<span class="hljs-name">location</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">status</span>&gt;</span>状态<span class="hljs-tag">&lt;/<span class="hljs-name">status</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">&quot;margin: 20px&quot;</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">time</span>&gt;</span>2018年10月12日<span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span>，我在<span class="hljs-tag">&lt;<span class="hljs-name">location</span>&gt;</span>中关村<span class="hljs-tag">&lt;/<span class="hljs-name">location</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">status</span>&gt;</span>打代码<span class="hljs-tag">&lt;/<span class="hljs-name">status</span>&gt;</span>。<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></code></pre></p><p>效果<br><img src="https://picbed-1252770021.cos.ap-chengdu.myqcloud.com/html/customer_tag.png" alt=""></p><p>这里需要注意的是标签之间的覆盖规则，选择最近的标签，参见<a href="https://blog.csdn.net/wl110231/article/details/7642652">CSS样式覆盖规则</a></p><p>给出两个网页颜色选择器</p><ul><li><a href="https://tools.guardui.net/nose/page.html">网页颜色选择器</a></li><li><a href="https://htmlcolorcodes.com/zh/">html颜色代码</a></li></ul><p>参考：</p><p><a href="http://www.ruanyifeng.com/blog/2017/06/custom-elements.html">HTML 自定义元素教程</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;我们在分析文本时，比如命名实体识别，可能想将不同的实体词文本用不同的颜色高亮标识出来，那么采用html自定义标签就是一个比较轻量的方法。&lt;/p&gt;</summary>
    
    
    
    <category term="前端" scheme="https://zekizz.github.io/categories/%E5%89%8D%E7%AB%AF/"/>
    
    
    <category term="html" scheme="https://zekizz.github.io/tags/html/"/>
    
    <category term="前端" scheme="https://zekizz.github.io/tags/%E5%89%8D%E7%AB%AF/"/>
    
  </entry>
  
  <entry>
    <title>网页文本中空格</title>
    <link href="https://zekizz.github.io/qian-duan/web-space/"/>
    <id>https://zekizz.github.io/qian-duan/web-space/</id>
    <published>2018-10-12T14:28:11.000Z</published>
    <updated>2020-08-29T15:32:28.976Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>网页中文本空格存在如下几类</p><ul><li>u200b：零宽度空格</li><li>u0020：普通空格</li><li>u00a0：不换行空格</li><li>&nbsp; 不换行空格的转义字符</li></ul><span id="more"></span><p>处理时，先替换成常规的空格字符</p><pre><code class="hljs Java">sentence.replaceAll(<span class="hljs-string">&quot;\\u200B|\\u0020|\\u00a0&quot;</span>, <span class="hljs-string">&quot; &quot;</span>).trim()</code></pre><p>参考：</p><p><a href="https://objcer.com/2017/05/22/Unicode-spaces/">Unicode 之神奇的空格</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;网页中文本空格存在如下几类&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;u200b：零宽度空格&lt;/li&gt;
&lt;li&gt;u0020：普通空格&lt;/li&gt;
&lt;li&gt;u00a0：不换行空格&lt;/li&gt;
&lt;li&gt;&amp;nbsp; 不换行空格的转义字符&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="前端" scheme="https://zekizz.github.io/categories/%E5%89%8D%E7%AB%AF/"/>
    
    
    <category term="html" scheme="https://zekizz.github.io/tags/html/"/>
    
    <category term="前端" scheme="https://zekizz.github.io/tags/%E5%89%8D%E7%AB%AF/"/>
    
    <category term="文本处理" scheme="https://zekizz.github.io/tags/%E6%96%87%E6%9C%AC%E5%A4%84%E7%90%86/"/>
    
  </entry>
  
  <entry>
    <title>双数组Trie树(DoubleArrayTrie)</title>
    <link href="https://zekizz.github.io/zi-ran-yu-yan-chu-li/doublearraytrie/"/>
    <id>https://zekizz.github.io/zi-ran-yu-yan-chu-li/doublearraytrie/</id>
    <published>2018-10-08T16:13:19.000Z</published>
    <updated>2020-08-29T15:16:40.089Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>双数组Trie树(DoubleArrayTrie)</p><p>NLP领域又很多需要词典匹配的需求，也就是，字典树+词典的基本方案。对于中文这种字较多的语言，双数组Trie树是比Trie树更好的结构。</p><span id="more"></span><p>原理讲解：</p><ul><li><a href="https://segmentfault.com/a/1190000008877595">小白详解 Trie 树</a></li><li><a href="https://zhuanlan.zhihu.com/p/35193582">双数组前缀树（Double-Array Trie）</a></li><li><a href="https://blog.csdn.net/heiyeshuwu/article/details/42526461">Trie树优化算法：Double Array Trie 双数组Trie</a></li></ul><p>代码实现：</p><ul><li><a href="https://github.com/komiya-atsushi/darts-java">darts-java: Double-ARray Trie System Java implementation</a></li><li><a href="http://www.hankcs.com/program/java/%E5%8F%8C%E6%95%B0%E7%BB%84trie%E6%A0%91doublearraytriejava%E5%AE%9E%E7%8E%B0.html">双数组Trie树(DoubleArrayTrie)Java实现</a></li><li><a href="https://cloud.tencent.com/developer/article/1057813">从Trie树到双数组Trie树</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;双数组Trie树(DoubleArrayTrie)&lt;/p&gt;
&lt;p&gt;NLP领域又很多需要词典匹配的需求，也就是，字典树+词典的基本方案。对于中文这种字较多的语言，双数组Trie树是比Trie树更好的结构。&lt;/p&gt;</summary>
    
    
    
    <category term="自然语言处理" scheme="https://zekizz.github.io/categories/%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7%90%86/"/>
    
    
    <category term="数据结构" scheme="https://zekizz.github.io/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    
    <category term="自然语言处理" scheme="https://zekizz.github.io/tags/%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7%90%86/"/>
    
  </entry>
  
  <entry>
    <title>推荐系统学习笔记</title>
    <link href="https://zekizz.github.io/tui-jian-xi-tong/recommendation-system/"/>
    <id>https://zekizz.github.io/tui-jian-xi-tong/recommendation-system/</id>
    <published>2018-10-06T12:36:45.000Z</published>
    <updated>2020-08-29T15:31:46.255Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>最近对以前学习推荐系统的知识点笔记的一个汇总。</p><span id="more"></span><h1 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h1><p>你需要推荐系统吗？</p><ol><li>看看产品的目的：建立越多连接越好。</li><li>看产品现有的连接：产品的数量</li></ol><p>一个简单指标</p><script type="math/tex; mode=display"> \frac{\Delta connection}{ \Delta user \times \Delta item}</script><p>分子是增加的连接数，分母是增加的活跃用户数和增加的有效物品数。<br>如果增加的连接数主要靠增加的活跃用户数和增加的物品数贡献，则该值较小，不适合加入推荐系统。反之，如果增加的连接数和新增活跃用户和物品关系不大，说明连接数已经有自发生长的趋势，适合加入推荐系统加速这个过程。</p><p>推荐系统的问题/任务</p><ol><li>评分预测</li><li>行为预测</li></ol><h2 id="评分预测"><a href="#评分预测" class="headerlink" title="评分预测"></a>评分预测</h2><p>显示打分，目标减小预测分数与实际分数之间的误差，回归问题。</p><p>评价标准：RMSE、MAE</p><script type="math/tex; mode=display">RMSE = \sqrt{ \frac{\sum_{t=1}^n (\widehat{y}_t - y_t)^2}{n} }</script><script type="math/tex; mode=display">MAE = \frac{\sum_{t=1}^n | \widehat{y}_t - y_t| }{n}</script><p>评分预测存在的问题：</p><ol><li>数据不易收集</li><li>数据质量不能保证</li><li>评分分布不稳定</li></ol><p>显示反馈很少，更多的是隐式反馈，通常为各类用户行为。行为预测更多地利用这部分数据。</p><h2 id="行为预测"><a href="#行为预测" class="headerlink" title="行为预测"></a>行为预测</h2><p>隐式反馈：登陆刷新、购买、收藏、浏览、点击等</p><p>行为预测有两种方式：</p><ol><li>直接预测用户行为：CTR预估</li><li>预测物品的相对排序：learning2rank</li></ol><p>隐式数据的好处：</p><ol><li>比显式更加稠密</li><li>隐式更加代表用户的真实想法</li><li>隐式反馈常常和模型的目标函数关联更密切，也因此通常更容易在 AB 测试中和测试指标挂钩。</li></ol><p>推荐系统中几个普遍的问题</p><ol><li>冷启动问题</li><li>探索与利用问题：Exploit 和 Explore （EE问题）</li><li>安全问题<ol><li>给出不靠谱的推荐结果，影响用户体验并最终影响品牌形象；</li><li>收集了不靠谱的脏数据，这个影响会一直持续留存在产品中，很难完全消除；</li><li>损失了产品的商业利益，这个是直接的经济损失。</li></ol></li></ol><h1 id="推荐方法"><a href="#推荐方法" class="headerlink" title="推荐方法"></a>推荐方法</h1><h2 id="基于内容推荐"><a href="#基于内容推荐" class="headerlink" title="基于内容推荐"></a>基于内容推荐</h2><h3 id="用户画像"><a href="#用户画像" class="headerlink" title="用户画像"></a>用户画像</h3><blockquote><p>用户画像应该给机器看，而不是给人看。</p></blockquote><p>用户画像是将用户向量化后的结果，其关键因素：维度和量化。</p><p>维度：</p><ol><li>每个维度的名称是可理解的</li><li>维度的数目是拍脑袋决定的</li><li>维度的筛选也是拍脑袋决定的</li></ol><p>维度越多越精细，但是计算代价会变大，同时也会引入噪声</p><p>量化：<br>不要为了用户画像而用户画像，它只是推荐系统的一个副产品，所以要根据推荐效果（排序好坏、召回覆盖等指标）来指导用户画像量化。</p><p>用户画像构建方法</p><ol><li>直接使用原始数据。比如人口统计学信息、购买历史、浏览历史等。</li><li>堆数据。堆积历史数据，做统计工作，常用的比如兴趣标签。</li><li>机器学习方法，比如隐语义模型、矩阵分解等embedding，构建无法直观理解的稠密向量。</li></ol><p>从文本数据中挖掘用户画像</p><ul><li>用户：昵称、姓名、性别、动态、评论等</li><li>物品：标题、描述、内容等</li></ul><p>构建用户画像步骤</p><ol><li>分析用户的文本和物品的文本，使其结构化；</li><li>标签选择，为用户挑选有信息量的结构化数据，作为其画像内容。</li></ol><p><strong>结构化文本算法</strong></p><ol><li>关键词提取：TF-IDF、TextRank。</li><li>实体识别NER：常用基于词典的方法结合 CRF 模型。</li><li>内容分类：将文本按照频道体系分类，用分类来表达较粗粒度的结构化信息。短文本常用Facebook 开源的 <strong>FastText</strong>。</li><li>聚类：无监督聚类，分簇，使用编号</li><li>主题模型：LDA</li><li>编码embedding</li></ol><script type="math/tex; mode=display">TF = count(w)</script><script type="math/tex; mode=display">IDF = log \frac{N}{n+1}</script><p>实体识别还有比较实用化的非模型做法：词典法。提前准备好各种实体的词典，使用trie-tree结构存储，拿着分好的词去词典寻找。<br>工业级工具：spaCy</p><p>LDA工具：Gensim和PLDA等</p><p><strong>标签选择</strong></p><p>通过户端的文本，物品端的文本如何结构化，得到了诸如标签（关键词、分类等）、主题、词嵌入向量。接下来就是第二步：如何把物品的结构化信息给用户呢？</p><p>我们把用户对物品的行为，消费或者没有消费看成是一个分类问题。用户用实际行动帮我们标注了若干数据，那么挑选出他实际感兴趣的特性就变成了特征选择问题。</p><p>最常用的是两个方法：卡方检验（CHI）和信息增益（IG）。基本思想是：</p><ol><li>把物品的结构化内容看成文档；</li><li>把用户对物品的行为看成是类别；</li><li>每个用户看见过的物品就是一个文本集合；</li><li>在这个文本集合上使用特征选择算法选出每个用户关心的东西。</li></ol><p>卡方检验</p><div class="table-container"><table><thead><tr><th>卡方检验</th><th>属于类别C_j</th><th>不属于类别C_j</th><th>总计</th></tr></thead><tbody><tr><td>包含词W_i</td><td>A</td><td>B</td><td>A+B</td></tr><tr><td>不包含词W_i</td><td>C</td><td>D</td><td>C+D</td></tr><tr><td>总计</td><td>A+C</td><td>B+D</td><td>N = A+B+C+D</td></tr></tbody></table></div><p>计算每一个词和每一个类别的卡方值：</p><script type="math/tex; mode=display">\chi^2 (W_i, C_j) = \frac{N(AD-BC)^2}{ (A+C)(A+B)(B+D)(C+D)}</script><ol><li>每个词和每个类别都要计算，只要对其中一个类别有帮助的词都应该留下；</li><li>由于是比较卡方值的大小，所以公式中的 N 可以不参与计算，因为它对每个词都一样，就是总的文本数；</li><li>卡方值越大，意味着偏离“词和类别相互独立”的假设越远，靠“词和类别互相不独立”这个备择假设越近。</li></ol><p><strong>误区：</strong> 基于内容的推荐系统，标签只是很小一部分。而且就算是标签，衡量质量的方式也不是数目够不够。</p><p>所谓的基于内容推荐，通俗一点来讲，就是一个包装成推荐系统的信息检索系统。这听上去有点残酷，但通常一个复杂的推荐系统很可能是从基于内容推荐成长起来的。</p><p>为什么基于内容的推荐系统这么重要呢？因为内容数据非常易得，哪怕是在一个产品刚刚上线，用心找的话总能找到一些可以使用的内容，不需要有用户行为数据就能够做出推荐系统的第一版。</p><p>要把基于内容的推荐做好，需要做好“抓、洗、挖、算”四门功课。它们分别是：</p><ol><li>抓：一直持续抓数据丰富自己的内容，所以做好一个基于内容的推荐，抓取数据补充内容源，增加分析的维度，两者必不可少。</li><li>洗：冗余的内容、垃圾内容、政治色情等敏感内容等等都需要被洗出去。</li><li>挖：很多推荐系统提升效果并不是用了更复杂的推荐算法，而是对内容的挖掘做得更加深入。</li><li>算：匹配用户的兴趣和物品的属性，计算出更合理的相关性，这是推荐系统本身的使命，不仅仅是基于内容的推荐才要做的。</li></ol><p>结合基于内容推荐的框架看上诉几个步骤</p><p><img src="https://picbed-1252770021.cos.ap-chengdu.myqcloud.com/RS/ContentConfig.png" alt=""></p><p>内容这一端：内容源经过内容分析，得到结构化的内容库和内容模型，也就是物品画像。用户这一端：用户看过推荐列表后，会产生用户行为数据，结合物品画像，经过用户分析得到用户画像。</p><p>内容分析的产出</p><ol><li>结构化内容库</li><li>内容分析模型<br>结构化的内容库，最重要的用途是结合用户反馈行为去学习用户画像。容易被忽略的是第二个用途，在内容分析过程中得到的模型，比如说：<br><ol><li>分类器模型；</li><li>主题模型；</li><li>实体识别模型；</li><li>嵌入模型。</li></ol><p>这些模型主要用在：当新的物品刚刚进入时，需要实时地被推荐出去，这时候对内容的实时分析，提取结构化内容，再于用户画像匹配。</p></li></ol><p>内容推荐算法</p><ol><li>直接计算相似度，BM25F算法</li><li>转为预估问题、分类问题<br><p>一种最典型的场景：提高某种行为的转化率，如点击、收藏、转发等。那么标准的做法是：收集这类行为的日志数据，转换成训练样本，训练预估模型。</p><br>每一条样本由两部分构成：一部分是特征，包含用户端的画像内容，物品端的结构化内容，可选的还有日志记录时一些上下文场景信息，如时间、地理位置、设备等等，另一部分就是用户行为，作为标注信息，包含“有反馈”和“无反馈”两类。</li></ol><p>二分类：LR+GBDT</p><h2 id="协同过滤"><a href="#协同过滤" class="headerlink" title="协同过滤"></a>协同过滤</h2><h3 id="User-based-CF"><a href="#User-based-CF" class="headerlink" title="User-based CF"></a>User-based CF</h3><script type="math/tex; mode=display">p(u,i) = \sum_{v \in S(u,K) \cap N(i)} w_{uv} r_{vi}</script><p>$S(u, K)$ 和用户 $ u $ 兴趣最接近的K个用户,$N(i)$ 是对物品i有过行为的用户集合，$w<em>{uv} $ 用户u和v之间的相似度，$ r</em>{ui} $ 用户v对物品i的兴趣，一般为{0,N}  </p><p>用户相似度计算</p><p>用户向量</p><ol><li>向量的维度就是物品的个数；</li><li>向量是稀疏的，也就是说并不是每个维度上都有数值，原因当然很简单，这个用户并不是消费过所有物品，废话嘛，连我们压箱底的都给用户推荐了，那当然不用再推荐什么了；</li><li>向量维度上的取值可以是简单的 0 或者 1，也就是布尔值，1 表示喜欢过，0 表示没有，当然因为是稀疏向量，所以取值为 0 的就忽略了。</li></ol><p>Jaccard相似性</p><script type="math/tex; mode=display">w_{nv} = \frac{|N(u) \cap |N(v)|}{|N(u) \cup |N(v)|}</script><p>余弦相似性</p><script type="math/tex; mode=display">w_{nv} = \frac{|N(u) \cap |N(v)|}{\sqrt{|N(u)| |N(v)|}}</script><p>用户间相似度改进：惩罚热门</p><script type="math/tex; mode=display">w_{uv} = \frac{\sum_ {i \in N(u) \cap N(v)} \frac{1}{log 1+|N(i)|}}{\sqrt{|N(u)| |N(v)|}}</script><p>两个用户对冷门物品采取过同样的行为更能说明他们兴趣的相似度</p><p>对两两用户都利用余弦相似度计算相似度。这种方法的时间复杂度是O(|U|*|U|)，这在用户数很大时非常耗时，可以采用物品到用户的倒排表。</p><p>存在问题及解决</p><ol><li>构造矩阵：采用稀疏矩阵存储，COO（行号，列号，数值）</li><li>相似度计算：数据量大时处理</li></ol><p>第一个办法是：将相似度计算拆成 Map Reduce 任务，将原始矩阵 Map 成键为用户对，值为两个用户对同一个物品的评分之积，Reduce 阶段对这些乘积再求和，Map Reduce 任务结束后再对这些值归一化；</p><p>map: &lt; <u1,u2>, r_u1i * r_u2i&gt; ， 各个维度上做乘法</p><p>reduce：求和并归一化</p><p>第二个办法是：不用基于用户的协同过滤，采用基于图的算法。</p><p>另外，这种计算对象两两之间的相似度的任务，如果数据量不大，一般来说不超过百万个，然后矩阵又是稀疏的，那么有很多单机版本的工具其实更快，比如 KGraph、 GraphCHI 等。</p><ol><li>推荐计算</li></ol><script type="math/tex; mode=display">p(u,i) = \sum_{v \in S(u,K) \cap N(i)} w_{uv} r_{vi}</script><p>得到了用户之间的相似度之后。接下来还有一个硬骨头，计算推荐分数。显然，为每一个用户计算每一个物品的推荐分数，计算次数是矩阵的所有元素个数，这个代价，你当然不能接受啊。</p><p>采用MapReduce</p><ol><li>遍历每个用户喜欢的物品列表；</li><li>获取该用户的相似用户列表；</li><li>把每一个喜欢的物品 Map 成两个记录发射出去，一个是键为 &lt; 相似用户 ID，物品 ID，1&gt; 三元组，可以拼成一个字符串，值为 &lt; 相似度 &gt;，另一个是键为 &lt; 相似用户 ID，物品 ID，0&gt; 三元组，值为 &lt; 喜欢程度 * 相似度 &gt;，其中的 1 和 0 为了区分两者，在最后一步中会用到；</li><li>Reduce 阶段，求和后输出；</li><li>&lt; 相似用户 ID，物品 ID, 0&gt; 的值除以 &lt; 相似用户 ID，物品 ID, 1&gt; 的值</li></ol><p>3: </p><ul><li>&lt; 相似用户 ID，物品 ID，1 &gt;  -&gt; w_{uv}</li><li>&lt; 相似用户 ID，物品 ID，0 &gt;  -&gt; w<em>{uv}*r</em>{vi}</li></ul><p>4: reduce应该是对相似用户求和</p><p>5：做归一化处理</p><h3 id="Item-CF"><a href="#Item-CF" class="headerlink" title="Item-CF"></a>Item-CF</h3><script type="math/tex; mode=display">p_{ui} = \sum_{j \in N(u) \cap S(i,K) } w_{ij} r_{uj}</script><p>物品相似度计算</p><p>注：相似度计算基于的是评分矩阵或者布尔化的行为矩阵</p><p>Jaccard</p><script type="math/tex; mode=display">w_{ij} = \frac{ \sum_{u \in U} r_{ui} r_{uj} }{ \sqrt{\sum_{v \in U} r_{vi}^2 \sum_{v \in U} r_{vj}^2 }}</script><p>物品去中心化</p><script type="math/tex; mode=display">w_{ij} = \frac{ \sum_{u \in U} (r_{ui} - \bar{r}_i) (r_{uj} - \bar{r}_j) }{ \sqrt{ \sum_{v \in U} (r_{ui} - \bar{r}_i)^2 \sum_{v \in U} (r_{uj} - \bar{r}_j)^2 } }</script><p>用户去中心化</p><script type="math/tex; mode=display">w_{ij} = \frac{ \sum_{u \in U} (r_{ui} - \bar{r}\_u) (r_{uj} - \bar{r}\_u) }{ \sqrt{ \sum_{v \in U} (r_{ui} - \bar{r}_u)^2 \sum_{v \in U} (r_{uj} - \bar{r}_u)^2 }}</script><p>更一般的相似性计算，比如余弦</p><ol><li><script type="math/tex; mode=display">w_{ij} = \frac{< r_i, r_j>}{|r_i|}</script></li><li>热门关联<script type="math/tex; mode=display">w_{ij} = \frac{< r_i, r_j>}{|r_i||r_j|}</script></li><li>对热门的打压<script type="math/tex; mode=display">w_{ij} = \frac{< r_i, r_j>}{|r_i|^{\alpha} |r_j|^{ 1- \alpha }}</script>$ \alpha $ 为0 最大限度打压热门，为1 不打压</li><li>用户打压<script type="math/tex; mode=display"><r_i, r_j> = \sum_{u \in U} \frac{r_{ui} r_{uj}}{ \log (1 + N(u))}</script>IUF（Inverse User Frequence）</li></ol><ol><li>热传导</li></ol><p>冷门受益</p><script type="math/tex; mode=display">w_{ij}^H = \frac{1}{k_i} \sum_{u \in U} \frac{r_{ui} r_{uj}}{k_u}</script><p>热门受益</p><script type="math/tex; mode=display">w_{ij}^P = \frac{1}{k_j} \sum_{u \in U} \frac{r_{ui} r_{uj}}{k_u}</script><p>看除的分母，热门物品度大，冷门度小</p><p>调和</p><script type="math/tex; mode=display">w_{ij}^P = \frac{1}{k_i^{ 1- \lambda} k_j^{\lambda}} \sum_{u \in U} \frac{r_{ui} r_{uj}}{k_u}</script><h2 id="矩阵分解-隐语义模型LFM-latent-factor-model"><a href="#矩阵分解-隐语义模型LFM-latent-factor-model" class="headerlink" title="矩阵分解/隐语义模型LFM(latent factor model)"></a>矩阵分解/隐语义模型LFM(latent factor model)</h2><blockquote><p>评分预测问题只是很典型，其实并不大众，毕竟在实际的应用中，评分数据很难收集到，属于典型的精英问题；与之相对的另一类问题行为预测，才是平民级推荐问题，处处可见。             </p></blockquote><p>近邻模型存在的问题：</p><ol><li>物品之间存在相关性，信息量并不随着向量维度增加而线性增加；</li><li>矩阵元素稀疏，计算结果不稳定，增减一个向量维度，导致近邻结果差异很大的情况存在。</li></ol><p>矩阵分解的目的分解评分矩阵A</p><script type="math/tex; mode=display">A_{m \times n} \cong U_{m \times k} V_{n \times k}^T</script><p>推荐过程</p><script type="math/tex; mode=display">\widehat{r}_{ui} = p_u q^T_i</script><p>$ p_u $ 用户向量，$ q_i $ 物品向量。</p><h3 id="基本SVD"><a href="#基本SVD" class="headerlink" title="基本SVD"></a>基本SVD</h3><p>SVD（奇异值分解）的损失函数：</p><script type="math/tex; mode=display">\min _{q^ *, p^ *} \sum_{u,i} (r_{ui} - p_u q_i^T) ^2 + \lambda ( ||q_i||^2 + ||p_u||^2 )</script><p>SVD学习过程</p><ol><li>准备好用户物品的评分矩阵，每一条评分数据看做一条训练样本；</li><li>给分解后的 U 矩阵和 V 矩阵随机初始化元素值；</li><li>用 U 和 V 计算预测后的分数；</li><li>计算预测的分数和实际的分数误差；</li><li>按照梯度下降的方向更新 U 和 V 中的元素值；</li><li>重复步骤 3 到 5，直到达到停止条件。</li></ol><h3 id="增加偏置"><a href="#增加偏置" class="headerlink" title="增加偏置"></a>增加偏置</h3><script type="math/tex; mode=display">\widehat{r}_{ui} = \mu + b_i + b_u + p_u q^T_i</script><p>分别为全局评分、物品偏置、用户偏置</p><p>偏置的计算为，当前评分-对应的平均分</p><p>对应的损失函数</p><script type="math/tex; mode=display">\min_{q^*, p^*} \sum_{u,i} (r_{ui} - \mu - b_i - b_u -  p_u q_i^T)^2 + \lambda ( ||q_i||^2 + ||p_u||^2 + b_i^2 + b_u^2)</script><h3 id="增加隐式数据-历史行为"><a href="#增加隐式数据-历史行为" class="headerlink" title="增加隐式数据/历史行为"></a>增加隐式数据/历史行为</h3><p>在 SVD 中结合用户的隐式反馈行为和属性，这套模型叫做 SVD++。</p><p>隐式反馈的加入方法：</p><p>除了假设评分矩阵中的物品有一个隐因子向量外，用户有过行为的物品集合也都有一个隐因子向量，维度是一样的。把用户操作过的物品隐因子向量加起来，用来表达用户的兴趣偏好。</p><p>类似的，用户属性，全都转换成 0-1 型的特征后，对每一个特征也假设都存在一个同样维度的隐因子向量，一个用户的所有属性对应的隐因子向量相加，也代表了他的一些偏好。</p><p>综合两者，SVD++ 的目标函数中，只需要把推荐分数预测部分稍作修改，原来的用户向量那部分增加了隐式反馈向量和用户属性向量：</p><script type="math/tex; mode=display">\widehat{r}\_{ui} = \mu + b_i + b_u + (p_u + \frac{1}{|N(u)|} \sum_{j \in N(u)} x_j + \sum_{a \in Au} y_a ) q_i^T</script><h3 id="加入时间因素"><a href="#加入时间因素" class="headerlink" title="加入时间因素"></a>加入时间因素</h3><ol><li>对评分按照时间加权，让久远的评分更趋近平均值；</li><li>对评分时间划分区间，不同的时间区间内分别学习出隐因子向量，使用时按照区间使用对应的隐因子向量来计算；</li><li>对特殊的期间，如节日、周末等训练对应的隐因子向量。</li></ol><h3 id="损失函数优化方法"><a href="#损失函数优化方法" class="headerlink" title="损失函数优化方法"></a>损失函数优化方法</h3><p>SGD V.S. ALS</p><p>ALS的思想就是固定一个优化另外一个，所以叫交替最小二乘。其好处：</p><ol><li>在交替的其中一步，也就是假设已知其中一个矩阵求解另一个时，要优化的参数是很容易并行化的；</li><li>在不那么稀疏的数据集合上，交替最小二乘通常比随机梯度下降要更快地得到结果，事实上这一点就是我马上要说的，也就是关于隐式反馈的内容。</li></ol><p>相比“预测用户会打多少分”，“预测用户会不会去浏览”更加有意义，而且，用户浏览数据远远多于打分评价数据。也就是说，实际上推荐系统关注的是预测行为，行为也就是一再强调的隐式反馈。</p><p>对隐式反馈的矩阵分解，需要将交替最小二乘做一些改进，改进后的算法叫做加权交替最小二乘：Weighted-ALS。</p><ol><li>如果用户对物品无隐式反馈则认为评分是 0；</li><li>如果用户对物品有至少一次隐式反馈则认为评分是 1，次数作为该评分的置信度。</li></ol>但是这里的数据只有正样本，负样本是我们“认为”的，这个“认为”可能不太准确，这就是One-class问题。所以需要对负样本进行采样。随机采样很不靠谱，一种比较好的方法是，按照物品的热门程度采样。关于负采样方法，word2vec等模型中都有介绍，后续专门写一篇博客。按照物品热门程度采样的思想就是：一个越热门的物品，用户越可能知道它的存在。那这种情况下，用户还没对它有反馈就表明：这很可能就是真正的负样本。现在的目标函数：$$ \min_{q^*, p^*} \sum_{u,i} c_{ui} (r_{ui} - p_u q^T\_i)^2 + \lambda ( ||q_i||^2 + ||p_u||^2 ) $$$$ c_{ui} = 1+ \alpha C $$C_ui就是置信度，跟反馈次数C有关，$ \alpha $ 默认取40。## 推荐计算在得到了分解后的矩阵后，每个用户得到了稠密的隐因子向量，同时每个物品也得到了一个稠密向量，代表它的语义或主题。看上去，让用户和物品的隐因子向量两两相乘，计算点积就可以得到所有的推荐结果了。但是实际上复杂度还是很高，尤其对于用户数量和物品数量都巨大的应用，如Facebook，就更不现实。于是 Facebook提出了两个办法得到真正的推荐结果。第一种，利用一些专门设计的数据结构存储所有物品的隐因子向量，从而实现通过一个用户向量可以返回最相似的 K 个物品。Facebook 给出了自己的开源实现 Faiss，类似的开源实现还有 Annoy，KGraph，NMSLIB。其中 Facebook 开源的 Faiss 和 NMSLIB（Non-Metric Space Library）都用到了 ball tree 来存储物品向量。第二种，就是拿着物品的隐因子向量先做聚类，海量的物品会减少为少量的聚类。然后再逐一计算用户和每个聚类中心的推荐分数，给用户推荐物品就变成了给用户推荐物品聚类。得到给用户推荐的聚类后，再从每个聚类中挑选少许几个物品作为最终推荐结果。这样做的好处除了大大减小推荐计算量之外，还可以控制推荐结果的多样性，因为可以控制在每个类别中选择的物品数量。## 贝叶斯个性化排序BPR矩阵分解，本质上都是在预测用户对一个物品的偏好程度，其实就是做编码embedding。得到这样的矩阵分解结果后，常常在实际使用时，又是用这个预测结果来排序。所以，口口声声宣称想要模型的预测误差最小化，结果绕了一大圈最后还是只想要一个好点的排序。这种针对单个用户对单个物品的偏好程度进行预测，得到结果后再排序的问题，在排序学习中的行话叫做 point-wise。与之相对的，还有直接预测物品两两之间相对顺序的问题，就叫做 pair-wise。矩阵分解都属于 point-wise模型。这类模型的尴尬是：只能收集到正样本，没有负样本，于是认为缺失值就是负样本，再以预测误差为评判标准去使劲逼近这些样本。逼近正样本没问题，但是同时逼近的负样本只是缺失值而已，还不知道真正呈现在用户面前，到底是不喜欢还是喜欢呢？虽然这些模型采取了一些措施来规避这个问题，比如负样本采样，但是尴尬还是存在的，为了排序而绕路也是事实。贝叶斯个性化排序(Bayesian Personalized Ranking, BPR)就直接采用pair-wise来做矩阵分解。在BPR算法中，我们将任意用户u对应的物品进行标记，如果用户u在同时有物品i和j的时候点击了i，那么我们就得到了一个三元组<u, i, j>，它表示对用户u来说，i的排序要比j靠前。三元组中，i和j都只能是行为过和未行为过中的一种，不包含都行为和都未行为的情况。这样一来，学习的数据是反应用户偏好的相对顺序，而在使用时，面对的是所有用户还没行为过的物品，这些物品仍然可以在这样的模型下得到相对顺序，这就比三元组 point-wise 样本要直观得多。现在，每条样本包含的是两个物品，样本预测目标是两个物品的相对顺序。用个符号来表示这个差：Xu12，表示的是对用户 u，物品 1 和物品 2 的矩阵分解预测分数差。然后再用 sigmoid 函数把这个分数差压缩到 0 到 1 之间。$$\Theta = \frac{1}{1+ e ^{-X_{u12}}}$$也其实就是用这种方式预测了物品 1 排在物品 2 前面的似然概率，所以最大化交叉熵就是目标函数了。<p>目标函数通常还要防止过拟合，加上正则项，正则项其实认为模型参数还有个先验概率，这是贝叶斯学派的观点，也是 BPR 这个名字中“贝叶斯”的来历。</p>BPR 认为模型的先验概率符合正态分布，对应到正则化方法就是 L2 正则，具体参见[贝叶斯个性化排序算法小结](https://www.cnblogs.com/pinard/p/9128682.html)。$$ \prod_{u \in U} P(>_u|\theta) P(\theta) = \prod_{(u,i,j) \in D}   \sigma(X_{u12})  P(\theta) = \prod_{(u,i,j) \in D} \sigma(\overline{x}\_{ui} - \overline{x}\_{uj}) P(\theta) $$训练方法：梯度下降+mini-batch# 特征工程实施特征工程之前，需要先理解业务。在推荐场景中![](https://picbed-1252770021.cos.ap-chengdu.myqcloud.com/RS/recom_context.png)特征- 先归一化，统一量纲- 离散化，引入非线性关系- 特征交叉- GBDT无个性化特征信息交叉会造成所有用户结果一样。交叉特征：- 性别&分布（转化分布）- 分布&ID在样本给定下：1. 仔细考虑用何种特征构造方法2. 从聚合/内积开始，逐渐增加参数，直到过拟合过拟合判断方法：如，训练集AUC远大于测试集AUC# 模型融合推荐系统在技术实现上一般划分为三个阶段：挖掘、召回、排序。挖掘的工作就是对用户和物品做非常深入的结构化分析，庖丁解牛一样，各个角度各个层面的特征都被呈现出来，并且建好索引，供召回阶段使用，大部分挖掘工作都是离线进行的。接下来就是召回，为什么会有召回？因为物品太多了，每次给一个用户计算推荐结果时，如果对全部物品挨个计算，那将是一场灾难，取而代之的是用一些手段从全量的物品中筛选出一部分比较靠谱的。最后就是排序，针对筛选出的一部分靠谱的做一个统一的排序。进一步召回：在召回阶段，其实就是各种简单的、复杂的推荐算法，比如说基于内容的推荐，会产生一些推荐结果，比如基于物品的协同过滤会产生一些结果，矩阵分解会产生一些结果，等等。正则化的方法一般是：限定总的树个数、树的深度、以及叶子节点的权重大小。实数特征的分裂。推荐系统：唯快不破在线算法- [FTRL](https://zhuanlan.zhihu.com/p/20447450)- TG- FOBOS- RDA[Boosting For Transfer Learning](http://www.cs.ust.hk/~qyang/Docs/2007/tradaboost.pdf)特征组合- GBDT+LR- FM(Factorization Machine)：因子分解机- FFM(Field-aware Factorization Machine）- [Wide & Deep 模型](https://github.com/tensorflow/models/tree/master/official/wide_deep)<ol><li>深宽模型是一个结合了传统线性模型和深度模型的工程创新。</li><li>这个模型适合高维稀疏特征的推荐场景，稀疏特征的可解释性加上深度模型的泛化性能，双剑合璧。</li><li>这个模型已经开源在 TensorFlow 中。</li><li>为了提高模型的训练效率，每一次并不从头开始训练，而是用上一次模型参数来初始化当前模型的参数。</li><li>将类别型特征先做嵌入学习，再将嵌入稠密向量送入深度模型中。</li><li>为了提高服务的响应效率，对每次请求要计算的多个候选 App 采用并行评分计算的方式，大大降低响应时间。</li></ol><h1 id="MAB问题"><a href="#MAB问题" class="headerlink" title="MAB问题"></a>MAB问题</h1><p>多臂赌博机问题 (Multi-armed bandit problem, K-armed bandit problem, MAB)，简称 MAB 问题。</p><p>推荐系统的使命就是：为用户匹配到最佳的物品，在某个时间某个位置为用户选择最好的物品。</p><p>推荐就是选择</p><h2 id="Bandit-算法"><a href="#Bandit-算法" class="headerlink" title="Bandit 算法"></a>Bandit 算法</h2><p>小心翼翼地试，越确定某个选择好，就多选择它，越确定某个选择差，就越来越少选择它。</p><p>一种走一步看一步的推荐算法， Bandit 算法。Bandit 算法把每个用户看成一个多变的环境，待推荐的物品就如同赌场里老虎机的摇臂，如果推荐了符合用户心目中喜欢的，就好比是从一台老虎机中摇出了金币一样。</p><p>Bandit 算法有汤普森采样，UCB 算法，Epsilon 贪婪。汤普森采样以实现简单和效果显著而被人民群众爱戴，你需要时不妨首先试试它。</p><p>Bandit解决冷启动</p><ol><li><p>用分类或者 Topic 来表示每个用户兴趣，我们可以通过几次试验，来刻画出新用户心目中对每个 Topic 的感兴趣概率。</p></li><li><p>这里，如果用户对某个 Topic 感兴趣，就表示我们得到了收益，如果推给了它不感兴趣的 Topic，推荐系统就表示很遗憾 (regret) 了。</p></li><li><p>当一个新用户来了，针对这个用户，我们用汤普森采样为每一个 Topic 采样一个随机数，排序后，输出采样值 Top N 的推荐 Item。注意，这里一次选择了 Top N 个候选臂。</p></li><li><p>等着获取用户的反馈，没有反馈则更新对应 Topic 的 b 值，点击了则更新对应 Topic 的 a 值。</p></li></ol><h3 id="LinUCB"><a href="#LinUCB" class="headerlink" title="LinUCB"></a>LinUCB</h3><p>“Yahoo!”的科学家们在 2010 年基于 UCB 提出了 LinUCB 算法，它和传统的 UCB 算法相比，最大的改进就是加入了特征信息，每次估算每个候选的置信区间，不再仅仅是根据实验，而是根据特征信息来估算，这一点就非常的“机器学习”了。</p><p>优点：</p><ol><li>由于加入了特征，所以收敛比 UCB 更快，也就是比 UCB 更快见效；</li><li>各个候选臂之间参数是独立的，可以互相不影响地更新参数；</li><li>由于参与计算的是特征，所以可以处理动态的推荐候选池，编辑可以增删文章；</li></ol><p>LinUCB 只是一个推荐框架，可以将这个框架应用在很多地方，比如投放广告，为用户选择兴趣标签等。</p><h3 id="COFIBA-算法"><a href="#COFIBA-算法" class="headerlink" title="COFIBA 算法"></a>COFIBA 算法</h3><p>概要：</p><ol><li>在时刻 t，有一个用户来访问推荐系统，推荐系统需要从已有的候选池子中挑一个最佳的物品推荐给他，然后观察他的反馈，用观察到的反馈来更新挑选策略。</li><li>这里的每个物品都有一个特征向量，所以这里的 Bandit 算法是 context 相关的，只不过这里虽然是给每个用户维护一套参数，但实际上是由用户所在的聚类类簇一起决定结果的。</li><li>这里依然是用岭回归去拟合用户的权重向量，用于预测用户对每个物品的可能反馈（payoff），这一点和我们上一次介绍的 LinUCB 算法是一样的。</li></ol><p>与linUCB算法的不同：</p><ol><li>基于用户聚类挑选最佳的物品，即相似用户集体动态决策；</li><li>基于用户的反馈情况调整用户和物品的聚类结果。</li></ol><p>算法流程：</p><ol><li>首先计算用户 i 的 Bandit 参数 W，做法和 LinUCB 算法相同，但是这个参数并不直接参与到选择决策中，注意这和 LinUCB 不同，只是用来更新用户聚类。</li><li>遍历候选物品，每一个物品已经表示成一个向量 x 了。</li><li>每一个物品都对应一个物品聚类类簇，每一个物品类簇对应一个全量用户聚类结果，所以遍历到每一个物品时，就可以判断出当前用户在当前物品面前，自己属于哪个用户聚类类簇，然后把对应类簇中每个用户的 M 矩阵 (对应 LinUCB 里面的 A 矩阵)，b 向量（表示收益向量，对应 LinUCB 里面的 b 向量）加起来，从而针对这个类簇求解一个岭回归参数（类似 LinUCB 里面单独针对每个用户所做），同时计算其收益预测值和置信区间上边界。</li><li>每个待推荐的物品都得到一个预测值及置信区间上界，挑出那个上边界最大的物品作为推荐结果。</li><li>观察用户的真实反馈，然后更新用户自己的 M 矩阵和 b 向量，只更新每个用户，对应类簇里其他的不更新。</li></ol><p>Bandit 算法系列，主要是解决推荐系统中的冷启动和 EE 问题。探索和利用这一对矛盾一直客观存在，而 Bandit 算法是公认的一种比较好的解决 EE 问题的方案。</p><h2 id="深度学习在推荐上的应用"><a href="#深度学习在推荐上的应用" class="headerlink" title="深度学习在推荐上的应用"></a>深度学习在推荐上的应用</h2><h2 id="排行榜的构建"><a href="#排行榜的构建" class="headerlink" title="排行榜的构建"></a>排行榜的构建</h2><p>热度计算</p><ol><li>Hacker News</li></ol><script type="math/tex; mode=display">\frac{P-1}{(T+2)^G}</script><ol><li>P：得票数，去掉帖子作者自己投票。</li><li>T：帖子距离现在的小时数，加上帖子发布到被转帖至 Hacker News 的平均时长。</li><li>G：帖子热度的重力因子。</li></ol><p>公式中，分子是简单的帖子数统计，一个小技巧是去掉了作者自己的投票。分母就是将前面说到的时间因素考虑在内，随着帖子的发表时间增加，分母会逐渐增大，帖子的热门程度分数会逐渐降低。</p><ol><li>牛顿冷却定律</li></ol><script type="math/tex; mode=display">T(t) = H + C e^{-\alpha t}</script><ul><li>H：为环境维度，可以认为是平均票数，比如电商中的平均销量，由于不影响排序，可以不使用。</li><li>C：为净剩票数，即时刻 t 物品已经得到的票数，也就是那个最朴素的统计量，比如商品的销量。</li><li>t：为物品存在时间，一般以小时为单位。</li><li>\alpha：是冷却系数，反映物品自然冷却的快慢。</li></ul><h2 id="其他算法"><a href="#其他算法" class="headerlink" title="其他算法"></a>其他算法</h2><h3 id="加权采样算法"><a href="#加权采样算法" class="headerlink" title="加权采样算法"></a>加权采样算法</h3><p>有限数据集</p><script type="math/tex; mode=display">S_{i} = R^{\frac{1}{w_{i}}}</script><p><ol></p><p><li>wi 是每个样本的权重，比如用户标签权重；</li></p><p><li>R 是遍历每个样本时产生的 0 到 1 之间的随机数；</li></p><p><li>Si 就是每个样本的采样分数</li><br>&lt;/ol&gt;</p><p>你可以看到，每个样本采样概率和它的权重成正比。</p><p>指数分布采样</p><p>无限数据集：蓄水池采样</p><p>内容去重算法</p><ul><li>Simhash</li><li>布隆过滤器</li></ul><h1 id="工程实践"><a href="#工程实践" class="headerlink" title="工程实践"></a>工程实践</h1><p>信息流，feed流</p><p>信息流框架</p><p>Netflix架构</p><p>TODO</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近对以前学习推荐系统的知识点笔记的一个汇总。&lt;/p&gt;</summary>
    
    
    
    <category term="推荐系统" scheme="https://zekizz.github.io/categories/%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F/"/>
    
    
    <category term="机器学习" scheme="https://zekizz.github.io/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="推荐系统" scheme="https://zekizz.github.io/tags/%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F/"/>
    
  </entry>
  
</feed>
