<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Posts on bitJoy</title><link>https://bitjoy.net/posts/</link><description>Recent content in Posts on bitJoy</description><generator>Hugo -- 0.148.2</generator><language>en</language><lastBuildDate>Sun, 05 Apr 2026 09:37:56 +0800</lastBuildDate><atom:link href="https://bitjoy.net/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>论文阅读：LREF: A Novel LLM-based Relevance Framework for E-commerce</title><link>https://bitjoy.net/posts/2026-04-05-jd-lref-paper-reading/</link><pubDate>Sun, 05 Apr 2026 09:37:56 +0800</pubDate><guid>https://bitjoy.net/posts/2026-04-05-jd-lref-paper-reading/</guid><description>&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2026-04-05-jd-lref-paper-reading/lref-cover.png">&lt;/p>
&lt;h1 id="基本信息">基本信息&lt;/h1>
&lt;ul>
&lt;li>论文标题：LREF: A Novel LLM-based Relevance Framework for E-commerce&lt;/li>
&lt;li>作者单位：京东&lt;/li>
&lt;li>论文链接：&lt;a href="https://arxiv.org/abs/2503.09223">https://arxiv.org/abs/2503.09223&lt;/a>&lt;/li>
&lt;li>来源：WWW25&lt;/li>
&lt;/ul>
&lt;h1 id="motivation论文要解决的问题是什么">Motivation：论文要解决的问题是什么&lt;/h1>
&lt;p>电商搜索相关性任务是指给定搜索词query和商品item，判断两者在语义上是否相关。针对这个问题，业界通常微调BERT来做判别式任务。随着LLM的兴起，大家都在尝试将LLM应用到搜索相关性任务上，但LLM做搜索相关性任务存在3个挑战：&lt;/p>
&lt;ul>
&lt;li>如何获得高质量数据。对于大模型微调来说，开源大模型本身已经具备很强的世界知识了，针对常规的(q,i)相关性问题已经能比较好地处理。微调进一步提升大模型效果的关键在于如何准备高质量的(q,i)相关性数据。&lt;/li>
&lt;li>LLM微调的时候，如何增强LLM在电商场景下根据特定规则进行推理的能力，即如何激发大模型按一定规则进行推理，再判断相关性&lt;/li>
&lt;li>大模型有时候过于仁慈，有时候倾向于把不相关的商品判断成很相关，如何纠正大模型的这种问题是个挑战&lt;/li>
&lt;/ul>
&lt;p>总之直接把LLM用到相关性判别问题上，有很多挑战，需要逐一解决。&lt;/p>
&lt;h1 id="数据筛选方法">数据筛选方法&lt;/h1>
&lt;p>首先需要认识到的是，电商相关性任务通常需要人工标注大量的(q,i,label)三元组数据来训练相关性模型。相关性label通常分为5档：Exact, Significant, Marginal, Trivial, and Irrelevant。&lt;/p>
&lt;p>作者发现LLM本身已经具备比较强的通识的相关性判别能力，需要重点加强的是LLM针对难例的相关性判别能力，故需要从大量人工标注数据中筛选出难例进行微调。此外，人工标注数据中也存在一些噪声，需要把这些噪声过滤掉。总之，在数据筛选这个环节，核心目标就是如何从大量人工标注数据中筛选出高质量的难例样本。&lt;/p>
&lt;p>如图Fig 1所示，作者微调了3个大模型来做数据筛选，3个大模型都是从开源的LLaMA-2-7B 开始微调：&lt;/p>
&lt;ul>
&lt;li>Initial Model (IM): 初始模型，从人工标注数据中随机采样(q,i,label)微调LLM得到。由于人工标注数据和线上曝光数据分布一致，即简单样本占大多数，故IM可识别常规简单的q-i相关性问题，但对长尾难例识别能力不足&lt;/li>
&lt;li>Challenge Identifier (CI): 把人工标注数据按照曝光分布划分成热门、腰部、尾部(q,i,label)，每一部分都采样等比例的样本，用来训练CI。其实本质上就是增加了腰尾部数据的占比，提升CI对中长尾样本（难例）的识别能力&lt;/li>
&lt;li>Mislabeled Supervisor (MS): 从人工标注数据中随机选一些样本(q,i,label)，问GPT当前标注结果label最有可能替换成哪个，如果GPT回答是label’，则说明label和label’都有可能是合理的。因此，进一步推测人工标注的时候，人类也可能出错，把label’误标成label（或反之）。故用(q,i,label’)数据微调MS，在后续数据筛选中，把MS预估结果作为潜在的错误结果&lt;/li>
&lt;/ul>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2026-04-05-jd-lref-paper-reading/lref-fig1.png">&lt;/p>
&lt;p>微调得到上述3个模型之后，最终筛选出来的样本如下，L(x)表示人工标注结果。下面的数据有两个含义：&lt;/p>
&lt;ul>
&lt;li>难样本：IM预测错，CI预测对&lt;/li>
&lt;li>去掉噪声样本：如上所述，MS预估结果是潜在的错误结果，所以对于MS(x)=L(x)的样本，人工标注的L(x)也是潜在错误样本，需要把这些样本去掉，即条件MS(x)≠L(x)&lt;/li>
&lt;/ul>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2026-04-05-jd-lref-paper-reading/lref-formula4.png">&lt;/p>
&lt;h1 id="多cot微调">多CoT微调&lt;/h1>
&lt;p>经过上面的环节，我们已经拿到了高质量的难样本(q,i,label)，接下来开始正式微调LLM进行相关性判别任务了。由于LLM都是decoder-only架构，在相关性判别的时候，增加CoT能激发LLM的推理能力，提升判别效果。为此，作者设计了3个CoT微调任务：&lt;/p>
&lt;ul>
&lt;li>专家解释：Expert Explaining Chain of Thought (EE-CoT)，把(q,i,label)喂给GPT，让GPT解释为什么q和i的相关性结果是label，得到EE-CoT，因此得到新的标注数据(q,i,label,EE-CoT)。微调相关性大模型的时候，喂给大模型(q,i)，让其输出EE-CoT和label。&lt;/li>
&lt;li>遵守规则：Rule Adherence Chain-of-Thought (RA-CoT)，把(rule, q, i, label)喂给GPT，让GPT根据rule，推导出q和i的相关性是label的过程，得到RA-CoT，因此得到新的标注数据(rule, q, i, label, RA-CoT)。微调相关性大模型的时候，喂给大模型(rule, q, i)，让其输出RA-CoT和label。&lt;/li>
&lt;li>决策反思：Decision Reflection Chain of Thought (DR-CoT)，对样本(q,i,label)随机生成错误结果incorrect decision，得到样本(incorrect decision,q,i,label)。把(incorrect decision,q,i,label)喂给GPT，让其分析incorrect decision为什么错误，并给出推导过程，得到DR-CoT，因此得到新的标注数据(incorrect decision,q,i,label,DR-CoT)。微调相关性大模型的时候，喂给大模型(incorrect decision,q,i)，让其输出DR-CoT和正确label。&lt;/li>
&lt;/ul>
&lt;p>简要总结一下，这个环节就是用GPT做CoT的伪标注，然后通过数据蒸馏的方式把CoT能力蒸馏到相关性大模型中。&lt;/p></description></item><item><title>论文阅读：Retentive Relevance: Capturing Long-Term User Value in Recommendation Systems</title><link>https://bitjoy.net/posts/2026-04-04-meta-retentive-relevance-paper-reading/</link><pubDate>Sat, 04 Apr 2026 17:22:20 +0800</pubDate><guid>https://bitjoy.net/posts/2026-04-04-meta-retentive-relevance-paper-reading/</guid><description>&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2026-04-04-meta-retentive-relevance-paper-reading/meta-rr-cover.png">&lt;/p>
&lt;h1 id="基本信息">基本信息&lt;/h1>
&lt;ul>
&lt;li>论文标题：Retentive Relevance: Capturing Long-Term User Value in Recommendation Systems&lt;/li>
&lt;li>作者单位：Meta&lt;/li>
&lt;li>论文链接：&lt;a href="https://arxiv.org/abs/2510.07621">https://arxiv.org/abs/2510.07621&lt;/a>&lt;/li>
&lt;li>来源：arxiv&lt;/li>
&lt;/ul>
&lt;h1 id="motivation论文要解决的问题是什么">Motivation：论文要解决的问题是什么&lt;/h1>
&lt;p>现有推荐系统通常以用户的短期行为作为优化目标，例如用户的click、like等。但是这些短期信号通常存在噪声，且比较稀疏，而且难以捕捉用户的长期需求和留存情况。&lt;/p>
&lt;p>本文提出了一种基于调查问卷的留存相关性模型（Retentive Relevance），通过设计针对后续留存的调查问卷，直接获得用户的长期留存信号。并据此训练了一个长期留存模型，用此模型的打分来校准排序模型的打分，由此让推荐系统更加关注用户的长期留存价值。&lt;/p>
&lt;p>举个我自己脑补的例子：比如你刷抖音的时候点了一个段子手的搞笑视频，你看完了但是觉得并不搞笑，由于没有显式负反馈，推荐系统只能获取到你点击了这个视频，并且也看完了的正向信号，所以推荐系统在后续训练的时候会把这个搞笑视频当做正样本，再给你推荐类似的搞笑视频。这种以短期行为作为优化目标的方法，无疑是误导了推荐系统，对用户的长期留存是有害的。&lt;/p>
&lt;p>如果此时APP弹出来一个调查问卷，问你看完这个视频之后，你以后还会回来看类似的视频吗？你如果点击了是或者否，则系统就能显式获取到你的长期留存label，也就是你对此类视频的真实兴趣情况，这种真实反馈比click或者完播更加可靠，而且是和长期价值高度相关的（复访）。有了这种标注数据，则可以训练一个长期留存的模型，预估用户u对商品i的长期留存概率。用这个打分来修正传统的以即时行为为优化目标的推荐系统的打分。让推荐系统在推荐视频的时候能更多地关注用户的长期留存指标。&lt;/p>
&lt;h1 id="调查问卷设计方案">调查问卷设计方案&lt;/h1>
&lt;p>作者对比了3种不同的问卷方案：&lt;/p>
&lt;ul>
&lt;li>Retentive Relevance：问以后是否会再回来看类似视频（看未来，长期价值）&lt;/li>
&lt;li>Interest Matching：问当前视频是否符合用户兴趣（问当下，即时兴趣）&lt;/li>
&lt;li>Worth Your Time：问当前视频是否值得看（问当下，即时价值）&lt;/li>
&lt;/ul>
&lt;p>注意：Interest Matching和Worth Your Time并不等价，感兴趣的视频并不一定值得花这么多时间去看（例如没有营养的搞笑视频），有价值的视频并不一定感兴趣（例如枯燥无味的高数视频）。并且这两者都是对当前观看视频的即时反馈，而Retentive Relevance则更加宽泛一些，它不问用户对当前视频是否感兴趣或者是否有价值，而是问用户以后还会不会回来看类似的视频，非常巧妙，如果用户觉得感兴趣或者有价值，以后都有可能会回来看类似的视频，所以Retentive Relevance能一定程度上覆盖Interest Matching和Worth Your Time，并且是对未来的长期价值的直接提问。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2026-04-04-meta-retentive-relevance-paper-reading/meta-rr-table1.png">&lt;/p>
&lt;p>论文中还展示了调查问卷的app界面：&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2026-04-04-meta-retentive-relevance-paper-reading/meta-rr-figure1.png">&lt;/p>
&lt;h1 id="调查问卷结果的分析">调查问卷结果的分析&lt;/h1>
&lt;h2 id="一致性分析">一致性分析&lt;/h2>
&lt;p>三种问卷调查结果的一致性比较高，说明三种调查问卷有比较大的overlap，结果比较可靠。&lt;/p>
&lt;blockquote>
&lt;p>Retentive Relevance showed substantial correlations with Worth Your Time (r = 0.63, p &amp;lt; 0.001, 95%CI [0.71, 0.75]) and Interest Matching (r = 0.58, p &amp;lt; 0.001, 95% CI [0.66, 0.70]).&lt;/p></description></item><item><title>论文阅读：QARM V2: Quantitative Alignment Multi-Modal Recommendation for Reasoning User Sequence Modeling</title><link>https://bitjoy.net/posts/2026-03-21-qarm-v2-paper-reading/</link><pubDate>Sat, 21 Mar 2026 08:53:18 +0800</pubDate><guid>https://bitjoy.net/posts/2026-03-21-qarm-v2-paper-reading/</guid><description>&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2026-03-21-qarm-v2-paper-reading/qarm-v2-cover.png">&lt;/p>
&lt;p>这篇论文是&lt;a href="https://arxiv.org/pdf/2411.11739">QARM V1&lt;/a>的升级版，建议先看&lt;a href="https://bitjoy.net/posts/2025-10-04-qarm-paper-reading/">QARM V1的阅读总结&lt;/a>。&lt;/p>
&lt;h1 id="基本信息">基本信息&lt;/h1>
&lt;ul>
&lt;li>论文标题：QARM V2: Quantitative Alignment Multi-Modal Recommendation for Reasoning User Sequence Modeling&lt;/li>
&lt;li>作者单位：快手&lt;/li>
&lt;li>论文链接：&lt;a href="https://arxiv.org/abs/2602.08559">https://arxiv.org/abs/2602.08559&lt;/a>&lt;/li>
&lt;li>来源：arxiv&lt;/li>
&lt;/ul>
&lt;h1 id="motivation论文要解决的问题是什么">Motivation：论文要解决的问题是什么&lt;/h1>
&lt;p>QARM V1确定了“LLM微调→生产SID→排序模型应用”的多模态在排序场景应用的范式，本文发现QARM V1存在如下不足：&lt;/p>
&lt;p>（1）QARM V1用于LLM微调的i2i训练数据有很多噪声&lt;/p>
&lt;p>QARM V1有2种生产i2i样本的方法：&lt;/p>
&lt;ul>
&lt;li>使用swing这种i2i召回模型收集i2i pair。这个方式容易受到商品热度的偏置，如Fig2a所示，酱油和洗衣液被swing判定为i2i正样本，只不过是因为他们都很热门，并不是因为他们在多模态语义上相似。&lt;/li>
&lt;li>使用U2I召回模型收集i2i pair。这个方式召回的i2i正样本pair灵活多变，很容易出bad case。&lt;/li>
&lt;/ul>
&lt;p>总之，QARM V1构造的i2i正样本有很多噪声，需要去噪。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2026-03-21-qarm-v2-paper-reading/qarm-v2-fig23.png">&lt;/p>
&lt;p>（2）QARM V1使用RQ-KMeans生产的SID序列冲突率太高&lt;/p>
&lt;p>QARM V1使用3层RQ-KMeans训练产出SID序列，作者发现电商商品的分布非常不均匀，甚至是二八分布的情况，即少数几类商品量非常大，大多数类目的商品数量很少。如果只用RQ-KMeans进行分层残差聚类，在商品分布不均匀的情况下，产出的SID序列的冲突率很高。如图Fig3a所示，KMeans聚类严重依赖于数据分布，当数据分布本身不均匀的时候，聚类结果本身也是不均匀的，这就会导致SID序列冲突。&lt;/p>
&lt;h1 id="使用推理大模型去噪的i2i样本构造方法">使用推理大模型去噪的I2I样本构造方法&lt;/h1>
&lt;p>如下图Fig4所示，针对QARM V1的i2i样本噪声多的问题，本文分别使用Qwen3-0.6B和Qwen3-8B对swing和u2i模型召回的i2i样本进行清洗，清洗的prompt如下图所示，就是让大模型判断i2i pair的两个商品是否是相似或者相关商品，输出yes或no。作者认为u2i模型召回的i2i pair更加灵活多变，所以使用了更大的8B模型对这部分数据进行清洗。经过作者的清洗，发现QARM V1的i2i样本中有10%的swing召回的i2i和70%的U2I召回的i2i都是噪声，这噪声的比例也太大了吧。。。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2026-03-21-qarm-v2-paper-reading/qarm-v2-fig4.png">&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2026-03-21-qarm-v2-paper-reading/qarm-v2-i2i-prompt.png">&lt;/p>
&lt;p>除此之外，为了增强emb对商品的理解能力，作者又使用更大的多模态大模型Qwen2.5-VL-72B，对商品进行理解，生成针对该商品的QA pair，作为后续NTP任务的训练数据。如上图Fig4右上角部分，针对那个辣条，Qwen2.5-VL-72B生成的QA会问这个商品名称是啥，回答是：亲嘴烧。这一步使用的prompt如下图所示。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2026-03-21-qarm-v2-paper-reading/qarm-v2-qa-prompt.png">&lt;/p>
&lt;h1 id="微调llm产出多模态表征">微调LLM产出多模态表征&lt;/h1>
&lt;p>微调结构如Fig4下图部分，LLM输入包括标题、图片、属性等特征，此外还新增了3个特殊token &amp;lt;EMB&amp;gt;，用这3个特殊token的输出mean pooling得到最终的多模态emb，然后进行对比学习训练。不理解为啥需要用3个特殊token，1个足够了吧？&lt;/p>
&lt;p>上述对比学习结构都比较常规，本文新增的是对QA pair的NTP任务，就是在三个特殊token后面，让LLM继续以NTP的形式生成QA pair，即样本构造环节通过大模型产出的QA pair。&lt;/p>
&lt;p>而且这个NTP任务是以三个特殊token为起点的，因为LLM都是decoder-only结构，为了让NTP任务只看到前面三个特殊token，而不看到开头的标题、图片等商品信息，作者设计了Three-Segment Attention Mask，即NTP任务最多看到三个特殊token往后的token。作者这么设计的原因，是希望通过NTP训练，让特殊token产出的商品表征，能够蕴含QA pair里面的信息。&lt;/p>
&lt;h1 id="基于res-kmeansfsq的sid序列生产">基于Res-KmeansFSQ的SID序列生产&lt;/h1>
&lt;p>QARM V1是直接使用3层RQ-KMeans残差聚类生产SID序列，作者在Fig3a中认为电商数据天然的不均匀性，会导致KMeans聚类出来的sid存在很大的冲突。为了缓解这个问题，作者将最后一层的KMeans替换成FSQ。其实正如Fig3a所示，KMeans是对向量空间的柔性的量化，而FSQ直接就是四舍五入，是刚性的量化，在码本空间比较大的情况下，FSQ的冲突率比KMeans低。&lt;/p>
&lt;p>如下图所示，M^2就是第二层的残差，在公式(3)中，先通过σ函数把残差映射到0~1之间，然后乘以量化值域L，相当于把残差投影到长度为L的线段上，Fig3b的网格等宽网格很好理解。有关FSQ的介绍可以参考这篇博客：&lt;a href="https://spaces.ac.cn/archives/9826">https://spaces.ac.cn/archives/9826&lt;/a>&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2026-03-21-qarm-v2-paper-reading/qarm-v2-fsq.png">&lt;/p>
&lt;h1 id="基于多模态表征及sid序列的用户行为建模方法">基于多模态表征及SID序列的用户行为建模方法&lt;/h1>
&lt;p>多模态emb和SID序列在排序模型中的应用也比较常规。整体还是TWIN或者说SIM的先GSU进行soft-search，然后在ESU进行target attention。具体来说，作者在GSU使用的是原始多模态emb进行soft-search，在ESU使用的是SID序列和排序模型进行端到端训练。最终离在线效果都有很大的提升。&lt;/p></description></item><item><title>论文阅读：Scaling Deep Contrastive Learning Batch Size under Memory Limited Setup</title><link>https://bitjoy.net/posts/2026-01-25-grad-cache-paper-reading/</link><pubDate>Sun, 25 Jan 2026 18:34:04 +0800</pubDate><guid>https://bitjoy.net/posts/2026-01-25-grad-cache-paper-reading/</guid><description>&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2026-01-25-grad-cache-paper-reading/GradCache-cover.png">&lt;/p>
&lt;h1 id="基本信息">基本信息&lt;/h1>
&lt;ul>
&lt;li>论文标题：Scaling Deep Contrastive Learning Batch Size under Memory Limited Setup&lt;/li>
&lt;li>作者单位：CMU&lt;/li>
&lt;li>论文链接：&lt;a href="https://arxiv.org/pdf/2101.06983">https://arxiv.org/pdf/2101.06983&lt;/a>&lt;/li>
&lt;li>来源：arxiv&lt;/li>
&lt;/ul>
&lt;h1 id="一问题">一、问题&lt;/h1>
&lt;p>对比学习通常使用InfoNCE loss进行训练，公式如下（1）：&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2026-01-25-grad-cache-paper-reading/GradCache-formula1.png">&lt;/p>
&lt;p>其中：&lt;/p>
&lt;ul>
&lt;li>\(s_i\)是anchor，\(f(s_i)\)是anchor embedding，\(f\)是anchor encoder&lt;/li>
&lt;li>\(t_{r_i}\)是\(s_i\)对应的positive，\(g(t_{r_i})\)是positive embedding，\(g\)是positive encoder&lt;/li>
&lt;li>\(t_j \in T\)是batch内其他\(s\)对应的positives，作为\(s_i\)的in-batch negatives。在没有hard negative的情况下，即每条样本是&amp;lt;anchor, positive&amp;gt;这种二元组的情况下，\(|T|\)等于batchsize&lt;/li>
&lt;li>\(\tau\)是温度系数，通常是一个常数，为讨论方便，后续省略该参数&lt;/li>
&lt;li>在经典的双塔对比学习场景下，函数\(f\)和\(g\)通常是两个不同的网络（比如CLIP）；在LLM/VLM emb场景下，函数\(f\)和\(g\)通常是共享参数的&lt;/li>
&lt;/ul>
&lt;p>对于对比学习，通常有in-batch negatives数量\(|T|\)越大，效果越好。但\(|T|\)越大，意味着batchsize也越大，训练时占用的显存也越多。如何在增大\(|T|\)的情况下不显著增加显存占用，是个很大的挑战。&lt;/p>
&lt;h1 id="二方法">二、方法&lt;/h1>
&lt;p>通常我们会使用梯度累积的方法在不增加显存的情况下增大batchsize，但梯度累积只适用于instance-wise loss，即每条样本的loss计算是独立的，这样可以把大batch拆成多个小batch分别计算梯度，然后累加起来。&lt;/p>
&lt;p>但是由于对比学习loss计算时涉及到anchor和in-batch negative的运算，即对比学习loss是batch-wise loss，直接把大batch拆成多个小batch不加额外处理的话，小batch内的in-batch negatives就少了，影响对比学习效果。&lt;/p>
&lt;p>本文的核心思想是，把公式1中的 对比学习loss 对 模型参数 的梯度求解过程拆分成：loss对表征\(f(s)\)的梯度 乘以 表征 对 模型参数 的梯度。由于只有loss计算需要batch-wise的运算，故上述拆解只有前半部分需要batch-wise运算，后半部分仍然可以instance-wise的计算然后梯度累加。下面来看具体过程。&lt;/p>
&lt;p>为方便讨论，我们只看loss对\(f\)的参数的梯度求解过程（比如在\(f\)和\(g\)共享参数的情况下），对\(g\)的梯度求解过程的分析类似。&lt;/p>
&lt;p>根据链式法则，对比学习loss \(\mathcal{L}\)对\(f\)的参数\(\Theta\)的梯度求解过程如下：把它拆解成第一项是\(\mathcal{L}\) 对表征\(f(s_i)\)的梯度，第二项是表征\(f(s_i)\)对模型参数\(\Theta\)的梯度，两项相乘就是loss \(\mathcal{L}\)对模型参数\(\Theta\)的梯度。
&lt;img loading="lazy" src="https://bitjoy.net/posts/2026-01-25-grad-cache-paper-reading/GradCache-formula2.png">&lt;/p>
&lt;p>通过上述拆解为什么能显著降低显存呢，详细分析如下：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>对于公式(2)右边第一项\(\frac{\partial \mathcal{L}}{\partial f\left(s_{i}\right)}\)，根据公式(1)可以得到第一项的梯度如下公式(5)。也就是说第一项的梯度只与batch内所有的表征\(f(s)\)和\(g(t)\)有关，而与模型参数\(\Theta\)无关。所以我们可以先进行一次不含梯度的前向过程，拿到batch内所有的表征\(f(s)\)和\(g(t)\)，由此可计算出公式(5)，即loss \(\mathcal{L}\)对表征的梯度。由于这次前向不包含梯度（类似inference过程），所以不用记录各种中间激活值（activations）和梯度，可以大大节省显存，详细可看&lt;a href="https://mingchao.wang/4KTgtnFc">https://mingchao.wang/4KTgtnFc&lt;/a> 的分析（即模型训练过程中activations是占显存的大头）。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>计算完公式(5)之后，可以把这部分梯度缓存起来，用于后续计算。这部分缓存的梯度只需要额外占用\((|S|d+|T|d)\)的显存，显著小于海量的模型参数和中间activations的参数量。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2026-01-25-grad-cache-paper-reading/GradCache-formula4.png">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2026-01-25-grad-cache-paper-reading/GradCache-formula5.png">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2026-01-25-grad-cache-paper-reading/GradCache-extra-memory.png">&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;/tbody>
&lt;/table>
&lt;ul>
&lt;li>
&lt;p>对于公式(2)右边第二项\(\frac{\partial f\left(s_{i}\right)}{\partial \Theta}\)，这部分梯度就是表征\(f(s_i)\)对模型参数\(\Theta\)的梯度，和常规梯度没什么两样，是instance-wise的，即每个样本的这个梯度计算是独立的。因此可以像常规梯度累积一样，进行mini-batch的计算，然后累加起来。为了完成这第二个过程，需要对每个样本\(s_i\)重新进行一次前向计算，由于需要对参数\(\Theta\)求梯度，所以这一次前向需要记录所有梯度和activations中间值。但是由于这个过程每个样本\(s_i\)可以独立计算，所以可以像梯度累积一样，把大batch拆分成多个小batch，每个mini-batch进行前向计算并进行梯度反向传播，所以显存峰值由mini-batch size决定，也不会太大。&lt;/p></description></item><item><title>论文阅读：Large Reasoning Embedding Models: Towards Next-Generation Dense Retrieval Paradigm</title><link>https://bitjoy.net/posts/2025-12-13-alibaba-large-reasoning-embedding-model-lrem-paper-reading/</link><pubDate>Sat, 13 Dec 2025 18:25:12 +0800</pubDate><guid>https://bitjoy.net/posts/2025-12-13-alibaba-large-reasoning-embedding-model-lrem-paper-reading/</guid><description>&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-12-13-alibaba-large-reasoning-embedding-model-lrem-paper-reading/LREM-paper-cover.png">&lt;/p>
&lt;h1 id="基本信息">基本信息&lt;/h1>
&lt;ul>
&lt;li>论文标题：Large Reasoning Embedding Models: Towards Next-Generation Dense Retrieval Paradigm&lt;/li>
&lt;li>作者单位：阿里巴巴&lt;/li>
&lt;li>论文链接：&lt;a href="https://arxiv.org/abs/2510.14321">https://arxiv.org/abs/2510.14321&lt;/a>&lt;/li>
&lt;li>来源：arxiv&lt;/li>
&lt;/ul>
&lt;h1 id="一问题">一、问题&lt;/h1>
&lt;p>电商emb召回场景，目前的方法都是直接字面语义上的对比学习训练（direct-embedding methods），即q2i的对比学习训练。对于复杂、困难的query，语义理解能力不足，比如下图Fig1中的query=&amp;ldquo;比茶更提神的饮料&amp;rdquo;，仍然会召回很多茶，因为字面理解没有理解query背后的深层含义。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-12-13-alibaba-large-reasoning-embedding-model-lrem-paper-reading/LREM-Fig1.png">&lt;/p>
&lt;h1 id="二方法">二、方法&lt;/h1>
&lt;p>使用LLM强大的推理能力（reasoning），先推理出CoT，然后基于CoT再产emb。比如上面的例子中，经过LLM推理之后，推理出咖啡、红牛等关键词，通过这些关键词再去产emb然后召回，效果就好很多。&lt;/p>
&lt;h2 id="21-训练样本构造方法">2.1 训练样本构造方法&lt;/h2>
&lt;p>如下图Fig2中的Data Construction部分：&lt;/p>
&lt;ul>
&lt;li>收集线上query，尤其是那种困难query，就是在现有direct-embedding表现不好的query&lt;/li>
&lt;li>把这些query喂给现有召回模型，得到召回商品集合①&lt;/li>
&lt;li>然后使用强大的Qwen3-30B-A3B-Instruct生产CoT扩展信息
&lt;ul>
&lt;li>Unconstrained Reasoning：首先不加任何限制地生产CoT，尽可能利用大模型的世界知识和推理能力，生产充分完全的CoT信息&lt;/li>
&lt;li>Information Extraction：由于上一步产出的CoT信息太长了，不利于线上推理，因此把上一步产出的CoT和原始query再次输入给大模型，让大模型抽取其中的关键信息，以keyword list形式输出&lt;/li>
&lt;li>Post Processing：最后对上一步抽取的关键词进行后处理，去除重复词，去除query中已有的词等，得到精简、干净的关键词列表，列表最大长度是16&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>接着把query和CoT喂给已有的向量召回模型，得到扩展的召回商品集合②
&lt;ul>
&lt;li>由于要训练模型的Reasoning能力，所以只取出集合②-①的差集部分，这部分是CoT带来的增益商品集合&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>最后使用相关性模型对商品集合②-①进行过滤，过滤出相关的商品&lt;/li>
&lt;li>通过上述步骤，产出约7.5kw的&amp;lt;query, CoT, item&amp;gt;三元组&lt;/li>
&lt;li>把上述样本划分成两部分，7.1kw的&amp;lt;query, CoT, item&amp;gt;三元组用于Cold start预训练；剩余400w的&amp;lt;query, item&amp;gt;用于RL微调&lt;/li>
&lt;/ul>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-12-13-alibaba-large-reasoning-embedding-model-lrem-paper-reading/LREM-Fig2.png">&lt;/p>
&lt;h2 id="22-cold-start预训练">2.2 Cold Start预训练&lt;/h2>
&lt;p>对应图Fig2左下角部分，该模块通过大规模的&amp;lt;query, CoT,item&amp;gt;三元组数据预训练，想要达到两个目的：一是让基础模型具备think能力；二是让基础模型产出的emb和下游q2i任务对齐。&lt;/p>
&lt;p>这里使用的基础模型是Qwen2.5-3B-Instruct，比生产CoT的模型（Qwen3-30B-A3B-Instruct）小，其实也有点蒸馏的感觉，把大模型的CoT能力蒸馏到小模型中。&lt;/p>
&lt;p>训练任务包括两个，一个是CoT的NTP loss（对应图中的SFT loss），另一个是q2i的对比学习InfoNCE loss。query塔和item塔共享参数，他们的emb都是最后一个特殊token &amp;lt;emb&amp;gt; 的emb。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-12-13-alibaba-large-reasoning-embedding-model-lrem-paper-reading/LREM-formula4.png">
&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-12-13-alibaba-large-reasoning-embedding-model-lrem-paper-reading/LREM-formula5-7.png">&lt;/p>
&lt;p>Loss组合：
&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-12-13-alibaba-large-reasoning-embedding-model-lrem-paper-reading/LREM-formula8.png">&lt;/p>
&lt;h2 id="23-rl微调">2.3 RL微调&lt;/h2>
&lt;p>上一步的SFT主要进行模仿学习，模仿更大的大模型的think能力，小模型本身的reasoning能力受限，接下来需要用GRPO对小模型进行RL微调。RL微调同时对生产CoT和生产emb两个任务都有作用，具体看下面的reward：&lt;/p>
&lt;p>RL微调设计了3个reward：&lt;/p>
&lt;ul>
&lt;li>Format Reward：产出的CoT格式符合“&amp;lt;think&amp;gt; Specific CoT &amp;lt;/think&amp;gt;&amp;lt;emb&amp;gt;”就得1分，否则得0分&lt;/li>
&lt;li>Length Reward：产出的CoT格式符合长度限制（&amp;lt;=16）就得1分，否则得0分&lt;/li>
&lt;li>Retrieval Accuracy Reward：联合原始query和产出的CoT产出的增强query emb，与batch内所有的item emb求相似度，正确item所在的排名为\(rank(d_i)\)，再根据公式12计算一个排名的reward。核心思想是：正确的item与query的相似度排名越高则reward越大（即rank值越小则reward越大）。
&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-12-13-alibaba-large-reasoning-embedding-model-lrem-paper-reading/LREM-formula11-12.png">&lt;/li>
&lt;/ul>
&lt;p>最后，上述3个reward通过三个β系数组合起来：
&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-12-13-alibaba-large-reasoning-embedding-model-lrem-paper-reading/LREM-formula13.png">&lt;/p></description></item><item><title>解决Hugo的Giscus评论系统出现URI_TOO_LONG的问题</title><link>https://bitjoy.net/posts/2025-11-09-resolve-the-uri-too-long-error-of-giscus-in-hugo/</link><pubDate>Sun, 09 Nov 2025 13:08:18 +0800</pubDate><guid>https://bitjoy.net/posts/2025-11-09-resolve-the-uri-too-long-error-of-giscus-in-hugo/</guid><description>&lt;p>本博客使用的评论系统是&lt;a href="https://github.com/giscus/giscus">Giscus&lt;/a>，日常使用没啥问题，但是当博客内容很长的时候，就会出现&lt;code>URI_TOO_LONG&lt;/code>的问题：&lt;/p>
&lt;pre tabindex="0">&lt;code>An error occurred
URI_TOO_LONG
hkg1::
&lt;/code>&lt;/pre>&lt;p>例如下面两篇博客：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://bitjoy.net/posts/2017-10-08-2017-solo-travel-in-zhengzhou-and-hangzhou/">2017年国庆旅行——郑州、杭州&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2018-02-04-2018-campus-recruiting/">伪·2018届校招面经&lt;/a>&lt;/li>
&lt;/ul>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">正常Giscus评论&lt;/th>
&lt;th style="text-align: center">异常Giscus评论&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-11-09-resolve-the-uri-too-long-error-of-giscus-in-hugo/normal-giscus.png">&lt;/td>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-11-09-resolve-the-uri-too-long-error-of-giscus-in-hugo/abnormal-giscus.png">&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>针对这个问题，网上搜&lt;code>URI_TOO_LONG&lt;/code>说的都是网页请求的URI太长导致的，但都没找到和Giscus相关的内容。&lt;/p>
&lt;p>后来在Giscus的Github Issue里找到一个相同的问题：&lt;a href="https://github.com/giscus/giscus/issues/1340">https://github.com/giscus/giscus/issues/1340&lt;/a>，里面一个人提到和Hugo所用的主题有关，另一个人提到和博客的&lt;code>meta name=&amp;quot;description&amp;quot;&lt;/code>内容太长有关。但都蜻蜓点水，说的不是很详细，也没有给出一个通用的解决办法。&lt;/p>
&lt;p>后来怀疑这个报错可能是在加载Giscus评论系统的时候，发起的URI请求太长有关。因此针对出问题的博客，通过Chrome右键&lt;code>检查&lt;/code>，找到Giscus模块出现的URI链接，如下图所示。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-11-09-resolve-the-uri-too-long-error-of-giscus-in-hugo/giscus-analysis.png">&lt;/p>
&lt;p>光这么看看不出来这个URI的长度，可以把这个URI拷贝出来，粘贴到Word中，你会发现这个URI真的非常非常长，而且包含了完整的博客正文！进一步分析发现是URI中的&lt;code>description&lt;/code>字段非常长，包含完整的博客正文。&lt;/p>
&lt;p>定位到问题之后，解决办法就很简单了，目标就是如何缩短博客html代码中的&lt;code>description&lt;/code>字段长度。这里有很多种方法，最简单的方法就是，在每篇博客的头信息区域，增加自定义配置的&lt;code>description&lt;/code>内容，简短一点就行，我就直接复用了博客标题。通过这种方法就缩短了Giscus发起URI中的&lt;code>description&lt;/code>字段长度了。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-11-09-resolve-the-uri-too-long-error-of-giscus-in-hugo/giscus-solution.png">&lt;/p>
&lt;p>此外，还可以在主题中搜一下&lt;code>meta name=&amp;quot;description&amp;quot;&lt;/code>出现的位置，把里面的代码改掉或者注释掉，都能解决这个问题。&lt;/p></description></item><item><title>论文阅读：Enhancing Embedding Representation Stability in Recommendation Systems with Semantic ID</title><link>https://bitjoy.net/posts/2025-10-09-meta-prefix-ngram-sid-paper-reading/</link><pubDate>Thu, 09 Oct 2025 18:17:16 +0800</pubDate><guid>https://bitjoy.net/posts/2025-10-09-meta-prefix-ngram-sid-paper-reading/</guid><description>&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-09-meta-prefix-ngram-sid-paper-reading/meta-ngram-sid-paper-cover.png">&lt;/p>
&lt;h1 id="基本信息">基本信息&lt;/h1>
&lt;ul>
&lt;li>论文标题：Enhancing Embedding Representation Stability in Recommendation Systems with Semantic ID&lt;/li>
&lt;li>作者单位：Meta&lt;/li>
&lt;li>论文链接：&lt;a href="https://arxiv.org/pdf/2504.02137">https://arxiv.org/pdf/2504.02137&lt;/a>&lt;/li>
&lt;li>来源：RecSys 2025&lt;/li>
&lt;/ul>
&lt;h1 id="motivation论文要解决的问题是什么">Motivation：论文要解决的问题是什么&lt;/h1>
&lt;p>搜推广的模型严重依赖于item id embedding的表征质量，但在工业场景下，搜推广的id表征存在如下挑战：&lt;/p>
&lt;ul>
&lt;li>id量级非常大，常常是数十亿甚至是百亿的规模。因此，通常不可能给每个id一个单独的embedding（即文中的individual embedding, IE），IE的成本太高&lt;/li>
&lt;li>id分布非常不均匀，马太效应严重。文中统计：0.1%的头部item占据了25%的曝光量；5.5%的腰部item占据了50%的曝光量；94.4%的尾部item只占据了25%的曝光量&lt;/li>
&lt;li>id分布漂移严重：搜推广场景中item的变化非常频繁，无时无刻不在发生着新id的产生和旧id的退出，而且不同id存活的时间周期也不尽相同，所以id的准入准出策略很难完美适配所有item&lt;/li>
&lt;/ul>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-09-meta-prefix-ngram-sid-paper-reading/meta-ngram-sid-fig2.png">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-09-meta-prefix-ngram-sid-paper-reading/meta-ngram-sid-fig3.png">&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;/tbody>
&lt;/table>
&lt;p>针对上述问题，常见的做法是对item id采用hash然后查emb的方式（即文中的random hash，RH），将所有id hash到一个固定大小的空间，然后查emb。但是RH方式有如下缺点：&lt;/p>
&lt;ul>
&lt;li>存在hash冲突，把不相关的id hash到一个桶里，导致语义混乱，学习效果不佳&lt;/li>
&lt;li>无法解决id分布漂移的问题，比如hash到同一个桶的A、B两个id，如果B出现频率变高，则会带偏A的分布，影响了A的效果&lt;/li>
&lt;li>无法进行知识共享，例如新出了商品iphone15，iphone15无法共享到老的iphone14的emb知识，iphone15的id emb必须完全重新学习。针对这种情况，作者做了一个更加极端的AA实验，就是copy一个完全相同的商品，只换item id，如果是IE或者RH策略，则新商品由于id emb是随机初始化的，效果不佳，这是id-based的通病&lt;/li>
&lt;/ul>
&lt;h1 id="基于前缀n-gram的semantic-id表征方法">基于前缀n-gram的semantic id表征方法&lt;/h1>
&lt;p>针对上述问题，作者沿用了semantic id的思路，首先使用内容理解团队产出的文本、图片等多模态emb，然后基于过去3个月的item多模态emb，训练RQ-VAE模型，并产出所有item的semantic id。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-09-meta-prefix-ngram-sid-paper-reading/meta-ngram-sid-fig1.png">&lt;/p>
&lt;p>上述过程都是常规操作，重点在于如何基于semantic id得到item emb表征。假设semantic id是L层，每层的codebook size是K：&lt;/p>
&lt;ul>
&lt;li>最常规的做法：每层都初始化一个K*d的emb table，每层sid查各自的emb table，然后把L层的sid emb加起来。但是本文完全没有提这种方法，也没有和这种方法比较，非常奇怪。
&lt;ul>
&lt;li>为了比较，我个人再详细描述下这种常规做法。比如老item A的sid是(c1,c2,c3)；新来一个item B，它的sid是(c1,c2,c4)。用常规方法，A的emb是c1+c2+c3，B的emb是c1+c2+c4。两者c1、c2是可以共享的，所以常规方法也能起到一定的知识共享的效果，共享项有2项：c1、c2。但是因为&lt;a href="https://arxiv.org/abs/2407.21488">RQ-VAE的沙漏问题&lt;/a>，c2很有可能是沙漏瓶颈，信息量不足。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>作者对比了Table 1中的几种方法：&lt;/li>
&lt;li>Trigram和Fourgram差不多，如果L=3用Trigram、L=4用Fourgram的话，本质上是把L个sid映射成了一个无冲突的int。但是这种方法映射出来的int数量太多了，是\(K^L\)。如果K=1024、L=3，则\(K^L\)就已经超过10亿了，这和直接无冲突的IE方法一样了，而且存在新id无法共享老id学到的知识的问题&lt;/li>
&lt;li>All bigrams，就是所有的sid的2-gram。还是上面的例子，A的emb相当于\(c_1c_2+c_2c_3\)，B的emb相当于\(c_1c_2+c_2c_4\)，两者可共享\(c_1c_2\)项，相比于常规方法，虽然共享项数变少了，但粒度更精细了，孰好孰坏未可知。由Table 2可知，All bigrams的效果至少比Trigram和Fourgram好很多了，而且如果层数L越大，可共享项越多&lt;/li>
&lt;li>Prefix-ngram（简称Prefix-SID方法），本文提出的新方法，把所有前缀组合成新id查emb，然后所有emb再求和。还是上面的例子，A的emb相当于\(c_1+c_1c_2+c_1c_2c_3+c_2+c_2c_3+c_3\)，B的emb相当于\(c_1+c_1c_2+c_1c_2c_4+c_2+c_2c_4+c_4\)，两者可共享\(c_1, c_1c_2, c_2\)三项，比之前的所有方法可共享的信息都多，而且如果层数L越大，可共享项越多，因此这种方法的效果最好，训练也最稳定&lt;/li>
&lt;/ul>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-09-meta-prefix-ngram-sid-paper-reading/meta-ngram-sid-tab1.png">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-09-meta-prefix-ngram-sid-paper-reading/meta-ngram-sid-tab2.png">&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;/tbody>
&lt;/table>
&lt;p>实验结果很丰富，做了很多分析，Prefix-SID方法有如下优势：&lt;/p>
&lt;ul>
&lt;li>相比于IE和RH方法，Prefix-SID方法对中长尾item的提升尤其显著，因为新id和老id的表征有了知识共享&lt;/li>
&lt;li>对id分布漂移问题更不敏感：由于电商模型训练时消费数据的顺序是和数据的时间一致的，比如一个月的数据，按照1号、2号、&amp;hellip;31号这样的时间先后顺序依次训练，理论上4号的模型在4号的测试集上的效果是最好的。作者做了一个实验，分别用20号和4号的模型都在4号的测试集上进行评测，看看20号的模型指标相比4号降低了多少。作者发现，使用Prefix-SID方法和IE方法，两者的指标降低幅度都差不多，都比较小。首先IE方法由于不存在hash冲突，所以20号的模型仍然能比较好地预测4号的数据；其次，Prefix-SID方法虽然有hash冲突，但是因为冲突的item都是语义相似的，可以进行新老item的知识共享，所以这个冲突反而是好事，对模型效果无影响。但是作者发现RH方法的20号的模型在4号数据上评测指标下降比较多，因为有hash冲突，而且冲突是随机的，20号的分布已经变化很大了，导致在4号数据上效果不佳。Table 4的指标越小越好。
&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-09-meta-prefix-ngram-sid-paper-reading/meta-ngram-sid-tab4.png">&lt;/li>
&lt;li>基于Prefix-SID方法虽然也有hash冲突，但是冲突到同一个semantic id的item表征更相似，而RH冲突到同一个桶里的item是完全随机的，相似度差。作者以IE为base，把Prefix-SID和RH都各自都冲突到同一个桶的IE emb提取出来，计算类内相似度和类间相似度，发现基于Prefix-SID的类内相似度方差小，类间距离大，说明Prefix-SID确实能把相似item聚到一起。
&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-09-meta-prefix-ngram-sid-paper-reading/meta-ngram-sid-tab5.png">&lt;/li>
&lt;/ul>
&lt;h1 id="评论">评论&lt;/h1>
&lt;ul>
&lt;li>可借鉴
&lt;ul>
&lt;li>基于Prefix-SID方法确实能提高新item和老item的信息共享数量，方法值得借鉴&lt;/li>
&lt;li>论文实验分析很丰富&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>可改进
&lt;ul>
&lt;li>基于Prefix-SID方法居然没有和最常规的加和方法比较，是本文最大的不足&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul></description></item><item><title>论文阅读：VL-CLIP: Enhancing Multimodal Recommendations via Visual Grounding and LLM-Augmented CLIP Embeddings</title><link>https://bitjoy.net/posts/2025-10-08-vl-clip-paper-reading/</link><pubDate>Wed, 08 Oct 2025 23:31:54 +0800</pubDate><guid>https://bitjoy.net/posts/2025-10-08-vl-clip-paper-reading/</guid><description>&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-08-vl-clip-paper-reading/VL-CLIP-paper-cover.png">&lt;/p>
&lt;h1 id="基本信息">基本信息&lt;/h1>
&lt;ul>
&lt;li>论文标题：VL-CLIP: Enhancing Multimodal Recommendations via Visual Grounding and LLM-Augmented CLIP Embeddings&lt;/li>
&lt;li>作者单位：沃尔玛&lt;/li>
&lt;li>论文链接：&lt;a href="https://arxiv.org/pdf/2507.17080">https://arxiv.org/pdf/2507.17080&lt;/a>&lt;/li>
&lt;li>来源：RecSys 2025&lt;/li>
&lt;/ul>
&lt;h1 id="motivation论文要解决的问题是什么">Motivation：论文要解决的问题是什么&lt;/h1>
&lt;p>多模态q2i召回通常使用CLIP的对比学习方式进行训练，在电商场景下存在2个问题：&lt;/p>
&lt;ul>
&lt;li>CLIP这种方式通常是对图片整体的表征，缺乏细粒度的目标检测能力，尤其在电商场景，比如fig1，卖衣服场景，传统CLIP只能识别整张图片是一件T恤，难以关注T恤上的图案等细节特征；另外，电商图片往往存在很多附加背景、道具、模特等元素，会影响主体物体的表征&lt;/li>
&lt;li>电商标题、属性等文本描述通常参差不齐，存在错误、堆砌、图文不符等问题，导致CLIP训练时图文对齐效果不佳&lt;/li>
&lt;/ul>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-08-vl-clip-paper-reading/VL-CLIP-fig1.png">&lt;/p>
&lt;h1 id="vl-clip解决方案">VL-CLIP解决方案&lt;/h1>
&lt;p>针对图片的处理：&lt;/p>
&lt;ul>
&lt;li>将图片和商品类型（product type）输入到开源模型&lt;a href="https://github.com/IDEA-Research/GroundingDINO">Grounding DINO&lt;/a>中，让模型进行目标检测，将可信度超过某个阈值且可信度最高的区域抠出来，输入到CLIP的图像encoder中。通过这步预处理，相当于对电商图片进行了关键主体识别和提取，只提取和商品最相关的主体进行图像表征。文中使用的图像编码器是ViT-B/32。&lt;/li>
&lt;/ul>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-08-vl-clip-paper-reading/VL-CLIP-fig2.png">&lt;/p>
&lt;p>针对文本的处理：&lt;/p>
&lt;ol>
&lt;li>将商品的类型、标题、描述、性别、年龄等文本描述以及图片本身输入到Summarizer多模态大模型，让大模型产出精简、准确的文本描述\(q_0\)&lt;/li>
&lt;li>将\(q_0\)和商品图文信息输入到Evaluator多模态大模型，让大模型对\(q_0\)的质量进行评判，如果\(q_0\)质量很好，则直接输出&amp;lt;STOP&amp;gt;；否则指出\(q_0\)的问题所在，并说明改进方法&lt;/li>
&lt;li>如果第2步输出不是&amp;lt;STOP&amp;gt;，则将第2步的输出再输入到Refiner大模型，让大模型根据第2步的结果继续调整并输出更优的文本描述\(q_i\)&lt;/li>
&lt;li>不断重复第2、3步，直到输出&amp;lt;STOP&amp;gt;，或者最多重复5遍&lt;/li>
&lt;li>将产出的精准的文本描述q输入到CLIP的文本encoder中，文中使用的是BERT系列。产出的emb维度是512&lt;/li>
&lt;li>上述Summarizer、Evaluator、Refiner都是VLM，文中使用的是GPT-4o，三个任务的prompt设计参考论文附录Table 9&lt;/li>
&lt;/ol>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-08-vl-clip-paper-reading/VL-CLIP-fig3.png">&lt;/p>
&lt;p>上述对图片和文本的处理本质上是去噪，提取图片的主体物品、让文本描述更加精准。&lt;/p>
&lt;p>产出多模态emb之后，后续的操作就是常规的召回流程了，使用HNSW进行ANN召回。&lt;/p>
&lt;h1 id="评论">评论&lt;/h1>
&lt;ul>
&lt;li>可借鉴
&lt;ul>
&lt;li>使用Grounding DINO对图片进行主体识别，值得借鉴&lt;/li>
&lt;li>使用VLM对商品标题、描述等文本信息进行去噪，值得借鉴&lt;/li>
&lt;li>但如果商品量级很大的话，这两个步骤估计会很耗时&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>可改进
&lt;ul>
&lt;li>如果是q2i场景，直接用query文本是不是更真实，更接近搜索日子的真实数据分布？&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul></description></item><item><title>论文阅读：Generative Recommendation with Semantic IDs: A Practitioner’s Handbook</title><link>https://bitjoy.net/posts/2025-10-07-grid-paper-reading/</link><pubDate>Tue, 07 Oct 2025 12:09:43 +0800</pubDate><guid>https://bitjoy.net/posts/2025-10-07-grid-paper-reading/</guid><description>&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-07-grid-paper-reading/GRID-paper-cover.png">&lt;/p>
&lt;h1 id="基本信息">基本信息&lt;/h1>
&lt;ul>
&lt;li>论文标题：Generative Recommendation with Semantic IDs: A Practitioner’s Handbook&lt;/li>
&lt;li>作者单位：Snap&lt;/li>
&lt;li>论文链接：&lt;a href="https://arxiv.org/pdf/2507.22224">https://arxiv.org/pdf/2507.22224&lt;/a>&lt;/li>
&lt;li>来源：CIKM 2025&lt;/li>
&lt;/ul>
&lt;p>这是CIKM 2025的一篇resource文章，比较简单。核心内容是开源了一个基于semantic id的生成式推荐框架GRID，可以很方便地做各种消融对比实验。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-07-grid-paper-reading/GRID-fig1.png">&lt;/p>
&lt;h1 id="主要内容">主要内容&lt;/h1>
&lt;p>主要结论如下：&lt;/p>
&lt;ul>
&lt;li>对于semantic id生成算法，简单的RQ-KMeans效果反而是最好的，好于R-VQ和RQ-VAE&lt;/li>
&lt;li>生产pretrain emb的LLM模型参数量越大，效果越好，但是提升幅度有限&lt;/li>
&lt;li>生产semantic id的codebook size和网络层数并不是越大越好，常规的3层，每层256个id效果反而最好&lt;/li>
&lt;li>生成式推荐时，是否需要在用户行为序列基础上增加一个user id，实验发现增加user id效果反而变差，不增加user id效果最好&lt;/li>
&lt;li>生成式网络结构encoder-decoder对比decoder-only，发现前者效果更好，因为前者能充分学习到行为序列完整的信息&lt;/li>
&lt;li>对行为流进行滑动窗口数据增强能提升模型的泛化能力&lt;/li>
&lt;li>当semantic id到item存在映射冲突时，随机选一个item的效果和对冲突item追加一个区分标识（digit），两者效果差不多&lt;/li>
&lt;li>在生成式beam search的时候，限制只输出合法semantic id和不增加限制，两者效果差不多&lt;/li>
&lt;/ul>
&lt;h1 id="评论">评论&lt;/h1>
&lt;p>看这篇文章主要是想看看不同semantic id生产方法的对比，发现RQ-KMeans居然比RQ-VAE更好。个人感觉这两个方法效果应该差不多，后者应该更好点才对。首先，RQ-VAE的量化loss本质上和KMeans聚类是一个意思；其次，RQ-VAE还增加了一个重构loss，感觉产出来的semantic id和原始emb的信息损失应该更少。&lt;/p>
&lt;p>此外，本文的所有实验都是基于亚马逊的公开数据集，数据量肯定不能和真正的工业数据集相提并论，所以文中很多结论有可能只适用于本文的设定，换一个场景估计结论就变了，所以看看就好。&lt;/p>
&lt;p>最后，文中很多结论只写了现象，要是能增加原因分析就好了。&lt;/p></description></item><item><title>论文阅读：Progressive Semantic Residual Quantization for Multimodal-Joint Interest Modeling in Music Recommendation</title><link>https://bitjoy.net/posts/2025-10-06-psrq-paper-reading/</link><pubDate>Mon, 06 Oct 2025 21:01:25 +0800</pubDate><guid>https://bitjoy.net/posts/2025-10-06-psrq-paper-reading/</guid><description>&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-06-psrq-paper-reading/PSRQ-paper-cover.png">&lt;/p>
&lt;h1 id="基本信息">基本信息&lt;/h1>
&lt;ul>
&lt;li>论文标题：Progressive Semantic Residual Quantization for Multimodal-Joint Interest Modeling in Music Recommendation&lt;/li>
&lt;li>作者单位：网易云音乐&lt;/li>
&lt;li>论文链接：&lt;a href="https://arxiv.org/pdf/2508.20359">https://arxiv.org/pdf/2508.20359&lt;/a>&lt;/li>
&lt;li>来源：CIKM 2025&lt;/li>
&lt;/ul>
&lt;h1 id="motivation论文要解决的问题是什么">Motivation：论文要解决的问题是什么&lt;/h1>
&lt;p>多模态emb在搜推的应用方式，通常是先将多模态emb转换成semantic id，然后把semantic id用到搜推模型中，这种方式有如下两个问题：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>模态内语义退化&lt;/strong>：多模态emb转换成semantic id通常使用RQ-VAE或者RQ-KMeans的方法，这种方法在不断残差的过程中，后续残差聚类结果已经不能反映初始emb的聚类效果了。其实就是semantic id的沙漏问题，具体可以看&lt;a href="https://arxiv.org/abs/2407.21488">这篇文章&lt;/a>，后续有空再分享这个问题。
&lt;ul>
&lt;li>简单来说，如下图所示，初始有DJ、Rock、Lullaby、Choir四个类，但是对残差emb（即RQ-VAE的第二层）聚类的话，初始的四个类的item就打散了，会聚到不同的簇中，也就是RQ-VAE的后续层的聚类效果已经和初始emb的聚类效果很不一样了，这就是文中说的语义退化问题&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>模态间建模差异&lt;/strong>：搜推场景的item通常有多种模态特征，比如文本、图像、音频等，传统方法在多模态融合方面比较简单，不能很好地捕捉多模态之间的关系。&lt;/li>
&lt;/ul>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-06-psrq-paper-reading/PSRQ-fig1.png">&lt;/p>
&lt;h1 id="psrq生产semantic-id">PSRQ生产semantic id&lt;/h1>
&lt;p>本文是音乐推荐场景，主要用到两种模态：text和audio，分别用百川和MERT提取text和audio的模态emb。&lt;/p>
&lt;p>生产semantic id的方法如下图所示：&lt;/p>
&lt;ul>
&lt;li>fig2a是传统的RQ-KMeans的方法，每一层都用上一层的残差进行聚类。如上文所述，由于沙漏问题，会导致后续层次的semantic id存在语义退化问题&lt;/li>
&lt;li>fig2b是本文新提出的PSRQ量化方法，在RQ-KMeans基础上，每一层除了有上一层的残差向量，还会concat上初始emb减去残差emb后的向量。&lt;strong>这样就能区分出残差相似，但初始emb不同的item了&lt;/strong>，也就避免了RQ方法的沙漏问题，后续semantic id也能保留初始emb的语义信息。fig1d能看出来第二层semantic id仍然能够反映初始emb的分类效果。&lt;/li>
&lt;/ul>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-06-psrq-paper-reading/PSRQ-fig2.png">&lt;/p>
&lt;h1 id="semantic-id在下游的应用方法">Semantic id在下游的应用方法&lt;/h1>
&lt;p>如下图所示：&lt;/p>
&lt;ul>
&lt;li>每个item有两套多模态emb：text和audio，但是有三套semantic id，除了text和audio各自产一套semantic id之外，还会把text和audio的emb concat起来，再产一套semantic id，相当于多模态融合的semantic id&lt;/li>
&lt;li>semantic id的emb在排序模型中随机初始化，然后端到端训练&lt;/li>
&lt;li>semantic id在用户建模时，使用DIN模型，query用的是多模态融合的semantic id emb，行为流分别用text和audio的semantic id emb。作者说这种方法既能捕捉到单模态细粒度的信息，又能建模跨模态的交互信息&lt;/li>
&lt;/ul>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-06-psrq-paper-reading/PSRQ-fig3.png">&lt;/p>
&lt;h1 id="评论">评论&lt;/h1>
&lt;ul>
&lt;li>可借鉴
&lt;ul>
&lt;li>PSRQ的semantic id生产方法确实很有意思，在每一层都用上原始emb，这样不同簇的item在每一层都能分开，不会出现沙漏问题，使得每一层的semantic id都能保留原始emb的语义聚类信息&lt;/li>
&lt;li>产了多套semantic id，单模态semantic id是常规操作；多模态emb concat后也产一套semantic id，是个创新点&lt;/li>
&lt;li>用户建模时query用多模态semantic id，行为流用单模态semantic id，也是个创新点，虽然论文说这种方法效果最好，但是有点存疑&lt;/li>
&lt;li>论文有个实验结果对比了不同semantic id量化方法的效果，结论是：PSRQ &amp;gt; RQ-KMeans = RQ-VAE &amp;gt; VQ &amp;gt; PQ&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>可改进
&lt;ul>
&lt;li>pretrain emb和semantic id的生产都没有对齐协同信号&lt;/li>
&lt;li>semantic id在下游应用时直接端到端训练，而没有使用codebook初始化，会不会丢失信息比较多？&lt;/li>
&lt;li>产semantic id的过程中，模态内语义退化的问题，描述了现象，但是没有用定量的指标来说明问题，感觉可以借鉴&lt;a href="https://bitjoy.net/posts/2025-10-04-mme-sid-paper-reading/">【论文阅读：Empowering Large Language Model for Sequential Recommendation via Multimodal Embeddings and Semantic IDs】&lt;/a>的方法，定量说明后续层的semantic id的聚类效果或者说区分能力相比初始emb已经相差甚远了&lt;/li>
&lt;li>fig2b中，第一层的codebook的dim=d，后续层的codebook的dim=2d，那么后续层的残差dim也是2d，那么初始emb怎么和后续的残差emb相减呢，维度对不上啊？我理解可能是这样的，后续层聚类的时候用的是concat的dim=2d的emb，但是算聚类中心的时候只用了残差本身的emb，这样就能解释得通了，但是文中对这部分的细节没有解释。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul></description></item><item><title>论文阅读：DAS: Dual-Aligned Semantic IDs Empowered Industrial Recommender System</title><link>https://bitjoy.net/posts/2025-10-05-das-paper-reading/</link><pubDate>Sun, 05 Oct 2025 20:26:43 +0800</pubDate><guid>https://bitjoy.net/posts/2025-10-05-das-paper-reading/</guid><description>&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-05-das-paper-reading/das-paper-cover.png">&lt;/p>
&lt;h1 id="基本信息">基本信息&lt;/h1>
&lt;ul>
&lt;li>论文标题：DAS: Dual-Aligned Semantic IDs Empowered Industrial Recommender System&lt;/li>
&lt;li>作者单位：快手&lt;/li>
&lt;li>论文链接：&lt;a href="https://arxiv.org/pdf/2508.10584">https://arxiv.org/pdf/2508.10584&lt;/a>&lt;/li>
&lt;li>来源：CIKM 2025&lt;/li>
&lt;/ul>
&lt;h1 id="motivation论文要解决的问题是什么">Motivation：论文要解决的问题是什么&lt;/h1>
&lt;p>Semantic id生产时，要么没有和协同信号对齐（fig2(1)），要么是两阶段对齐方式（fig2(2)）：&lt;/p>
&lt;ul>
&lt;li>例如&lt;a href="https://arxiv.org/abs/2405.07314">LETTER&lt;/a>先生成协同emb，然后和semantic id对齐&lt;/li>
&lt;li>或者例如&lt;a href="https://arxiv.org/pdf/2411.11739">QARM&lt;/a>，先协同对齐emb，再生产semantic id&lt;/li>
&lt;/ul>
&lt;p>把协同对齐和生产semantic id分成两个阶段，天然有信息损失，不是最优的。本文的目的就是把生产协同emb，以及semantic id的协同对齐放到一个模型中联合训练完成，尽量减少信息损失（fig2(3)）。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-05-das-paper-reading/das-fig2.png">&lt;/p>
&lt;h1 id="主模型">主模型&lt;/h1>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-05-das-paper-reading/das-fig3.png">&lt;/p>
&lt;p>主模型如上图所示，中间的ICDM是user和item的双塔模型，用于学习user和item的协同id-based emb；两边分别是生产user和item的semantic id的量化模型。&lt;/p>
&lt;p>中间的ICDM就是经典的召回双塔模型，使用点击样本进行训练，唯一不同的是，在user和item塔都有流行度去偏模块，用于学习user和item的无偏emb，后续user和item的semantic id协同对齐用的也是无偏的emb。&lt;/p>
&lt;p>两边分别是user和item的semantic id量化模型，两者比较类似，以item为例：&lt;/p>
&lt;ol>
&lt;li>先把item的各种信息，如title、desc、ocr等信息用文本构造成prompt，输入到LLM，借助LLM的summary和reasoning能力，产出item的详细描述&lt;/li>
&lt;li>然后把LLM产出的描述再输入到一个预训练的embedding模型PLM，文中用的是bge m3模型，得到item emb&lt;/li>
&lt;li>后续就是标准的RQ-VAE过程了&lt;/li>
&lt;/ol>
&lt;p>需要注意的是，上述前两步，分别用到了LLM和PLM两个大模型，而且看图上这两个模型都是freeze的，也就是说并不微调这两个大模型。后续协同对齐用的emb是RQ-VAE重构emb的中间层结果，即图中的item quantized emb。&lt;/p>
&lt;p>semantic id的协同对齐方面，有三大类对齐任务：&lt;/p>
&lt;ul>
&lt;li>U2I对齐：量化user emb和协同item emb对齐、量化item emb和协同user emb对齐&lt;/li>
&lt;li>U2U和I2I对齐：量化user emb和协同user emb对齐、量化item emb和协同item emb对齐&lt;/li>
&lt;li>U2U和I2I的共现对齐：点击相同item的两个量化user emb对齐、同一个user点击的两个item的量化item emb对齐&lt;/li>
&lt;/ul>
&lt;p>由于fig3中的协同模型和semantic id模型是联合训练的，总共有3大类loss：&lt;/p>
&lt;ul>
&lt;li>中间的ICDM的双塔召回模型的loss&lt;/li>
&lt;li>两边的产semantic id的loss&lt;/li>
&lt;li>三个模块的对齐loss&lt;/li>
&lt;/ul>
&lt;h1 id="评论">评论&lt;/h1>
&lt;ul>
&lt;li>可借鉴
&lt;ul>
&lt;li>把semantic id的生产和协同信号对齐统一成一阶段的模式，信息损失更少&lt;/li>
&lt;li>中间的ICDM模型生产协同emb时进行了去偏，协同对齐的时候用的是去偏的emb，这是其他论文很少提到的&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>可改进
&lt;ul>
&lt;li>太复杂了！3个模块，3大类loss，每类loss又有很多个小loss，总loss数量加起来有十多个。。。&lt;/li>
&lt;li>任务太多，各种去偏、对齐loss，真的不会互相影响吗？&lt;/li>
&lt;li>中间的ICDM模块有必要吗？我理解ICDM本质是为了训练产出协同emb，但是因为训练样本本身是点击样本，样本本身已经包含了搜推场景的协同信号，也就是ICDM本身没必要存在了，直接用相同的样本训练两边的semantic id量化模型就行了，也能实现在训练semantic id的过程中，完成协同信号的对齐&lt;/li>
&lt;li>生产semantic id的emb来自LLM和PLM，但是这两个大模型都是freeze的，如果把这两个模型也sft，效果会不会更好？其实我原本以为的一阶段就是这样的，这也是我在&lt;a href="https://bitjoy.net/posts/2025-10-04-mme-sid-paper-reading/">【论文阅读：Empowering Large Language Model for Sequential Recommendation via Multimodal Embeddings and Semantic IDs】&lt;/a>中提到的一阶段方法。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul></description></item><item><title>论文阅读：QARM: Quantitative Alignment Multi-Modal Recommendation at Kuaishou</title><link>https://bitjoy.net/posts/2025-10-04-qarm-paper-reading/</link><pubDate>Sat, 04 Oct 2025 18:24:40 +0800</pubDate><guid>https://bitjoy.net/posts/2025-10-04-qarm-paper-reading/</guid><description>&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-04-qarm-paper-reading/QARM-paper-cover.png">&lt;/p>
&lt;h1 id="基本信息">基本信息&lt;/h1>
&lt;ul>
&lt;li>论文标题：QARM: Quantitative Alignment Multi-Modal Recommendation at Kuaishou&lt;/li>
&lt;li>作者单位：快手&lt;/li>
&lt;li>论文链接：&lt;a href="https://arxiv.org/pdf/2411.11739">https://arxiv.org/pdf/2411.11739&lt;/a>&lt;/li>
&lt;li>来源：CIKM 2025&lt;/li>
&lt;/ul>
&lt;h1 id="motivation论文要解决的问题是什么">Motivation：论文要解决的问题是什么&lt;/h1>
&lt;p>多模态emb在搜推场景应用时通常采用如下图的两阶段方式，先预训练多模态emb，然后作为一个冻结特征放到搜推模型中。这种方式存在2个问题：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>表征不对齐&lt;/strong>：多模态emb预训练的任务通常是图片分类或者文本的MLM，和下游搜推任务不对齐&lt;/li>
&lt;li>&lt;strong>表征不更新&lt;/strong>：多模态emb在搜推任务中作为冻结特征，没有更新&lt;/li>
&lt;/ul>
&lt;p>本文的方法就是想要解决上述2个问题。
&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-04-qarm-paper-reading/QARM-fig1.png">&lt;/p>
&lt;h1 id="对齐搜推任务的多模态emb预训练">对齐搜推任务的多模态emb预训练&lt;/h1>
&lt;p>为了解决多模态emb表征不对齐的问题，本文提出的多模态emb预训练任务直接对齐搜推场景，使用U2I和I2I召回模型，挖掘出相似item pair，然后通过对比学习微调多模态大模型。&lt;/p>
&lt;p>具体来说，通过U2I和I2I模型，能够拿到item emb；然后用每一个target item emb去行为流中检索出最相似的商品，作为trigger item emb。&amp;lt;trigger, target&amp;gt;构成一对正样本，然后进行对比学习训练。&lt;/p>
&lt;p>通过召回模型构造的训练样本，和搜推场景的协同信号对齐了，解决了开头提到的第一个问题，即表征不对齐的问题。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-04-qarm-paper-reading/QARM-fig3.png">&lt;/p>
&lt;h1 id="semantic-id生产方法">Semantic id生产方法&lt;/h1>
&lt;p>Semantic id的生产方法如上图右半部分所示，有两种方式：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>VQ&lt;/strong>：直接圈定一定数量（如N）的item emb作为底池，编号1~N，然后任意来一个item emb，通过对底池emb进行KNN搜索，找出top-k相似商品，假设是(a,b,&amp;hellip;,k)，则VQ编码的semantic id就是(a,b,&amp;hellip;,k)。文中取k=25，感觉挺大的。。。&lt;/li>
&lt;li>&lt;strong>RQ-Kmeans&lt;/strong>：对圈定的N个item emb不断进行Kmeans聚类、求残差、残差继续Kmeans聚类的过程。文中取迭代次数为L=6，但是没说每次聚到多少个类。&lt;/li>
&lt;/ul>
&lt;p>注意：文中的RQ-Kmeans方法和RQ-VAE还不一样，RQ-Kmeans没有训练过程，也没有重构loss，纯粹是每次进行聚类，然后选聚类中心作为码本的过程。文中也没有对比过为啥不用RQ-VAE。&lt;/p>
&lt;p>产出两套semantic id之后，直接在下游排序任务中进行端到端更新，解决开头提到的表征不更新的问题。具体建模方法比较常规，不是本文的重点，略讲。&lt;/p>
&lt;h1 id="评论">评论&lt;/h1>
&lt;ul>
&lt;li>可借鉴
&lt;ul>
&lt;li>多模态emb预训练任务是i2i的，直接和下游搜推任务对齐&lt;/li>
&lt;li>semantic id有两种产出方式，VQ和RQ-Kmeans，尽可能多地保留原始多模态emb的信息&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>可改进
&lt;ul>
&lt;li>多模态emb预训练和下游任务对齐，在2025年不算新鲜事了，常规操作。而且文中i2i的构造过程依赖U2I和I2I召回模型，有外部依赖，不够漂亮&lt;/li>
&lt;li>VQ的方法，k=25这也太长了吧，相当于一个小型行为流了，会导致下游任务的特征处理更复杂&lt;/li>
&lt;li>为什么用RQ-Kmeans而不是RQ-VAE，没有任何说明与对比&lt;/li>
&lt;li>从pretrain emb量化成semantic id的过程中，存在严重的信息丢失，这在&lt;a href="https://bitjoy.net/posts/2025-10-04-mme-sid-paper-reading/">Empowering Large Language Model for Sequential Recommendation via Multimodal Embeddings and Semantic IDs&lt;/a>论文中有讨论&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul></description></item><item><title>论文阅读：Empowering Large Language Model for Sequential Recommendation via Multimodal Embeddings and Semantic IDs</title><link>https://bitjoy.net/posts/2025-10-04-mme-sid-paper-reading/</link><pubDate>Sat, 04 Oct 2025 11:10:11 +0800</pubDate><guid>https://bitjoy.net/posts/2025-10-04-mme-sid-paper-reading/</guid><description>&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-04-mme-sid-paper-reading/mme-sid-paper-cover.png">&lt;/p>
&lt;h1 id="基本信息">基本信息&lt;/h1>
&lt;ul>
&lt;li>论文标题：Empowering Large Language Model for Sequential Recommendation via Multimodal Embeddings and Semantic IDs&lt;/li>
&lt;li>作者单位：香港城市大学&amp;amp;腾讯&lt;/li>
&lt;li>论文链接：&lt;a href="https://arxiv.org/pdf/2509.02017">https://arxiv.org/pdf/2509.02017&lt;/a>&lt;/li>
&lt;li>来源：CIKM 2025&lt;/li>
&lt;/ul>
&lt;h1 id="motivation论文要解决的问题是什么">Motivation：论文要解决的问题是什么&lt;/h1>
&lt;p>LLM4SR的基本范式如下，即用LLM直接来做搜推的范式（这种方式在学术界常见，但在工业界不常见）。由于LLM的输入词表范围是有限的（通常比较小），因此其token emb dim通常比较大，比如2048或者4096；而搜推场景的item量级很大，而且在不断更新，因此工业界经典的id-based的搜推模型的item emb dim通常比较小，比如64或128。经典的id-based的搜推模型能比较好地学习到搜推场景的协同信号，为了让LLM模型也能感知这种信息，LLM4SR范式通常会先预训练一个id-based的经典搜推模型，然后将其中的item id emb通过下图的Linear Projection的映射层，映射到LLM token emb的空间，让LLM也能感知搜推的协同信号。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-04-mme-sid-paper-reading/E4SRec.png">&lt;/p>
&lt;p>上述LLM4SR范式存在两个问题：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>维度坍缩&lt;/strong>：id-based训出来的id emb dim比较小（如64），LLM token emb dim比较大（如4096），在由id emb通过Linear Projection映射到toen emb的过程中，虽然64映射到4096空间了，但扩维后的矩阵存在低秩问题，即还是只利用了4096中的64维的空间。&lt;/p>
&lt;ul>
&lt;li>论文中，作者分两种情况进行了分析，如果Linear Projection只是一个线性层的话，通过公式推导能得出上述结论；如果Linear Projection包含非线性变换，作者通过实验分析也发现了维度坍缩的现象。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>灾难遗忘&lt;/strong>：除了使用id-based模型产出的id emb，LLM4SR也常用多模态模型产出item emb表征，然后转换成semantic id输入到LLM4SR中。在这种情况下，产出的semantic id通过会遗忘多模态item emb的信息，导致下游LLM4SR的效果不佳。&lt;/p>
&lt;ul>
&lt;li>论文中，作者用公式9来衡量semantic id保留pretrain多模态emb的信息量。具体来说，如果行为流中的商品序列是{A,B,C,D}，target item是E。使用pretrain多模态emb能计算出E和A~D的相似度，例如相似度&amp;lt;E,A&amp;gt; &amp;gt; &amp;lt;E,B&amp;gt;。如果将pretrain多模态emb转换成semantic id，然后由semantic id恢复出新的A~E的emb之后，再计算E和A~D的相似度，如果仍然有&amp;lt;E,A&amp;gt; &amp;gt; &amp;lt;E,B&amp;gt;，则认为一致（concordant），否则不一致（disconcordant）。这个分析方法挺好的，通过这个指标能估算出转换成semantic id之后，仍然保留原有pretrain多模态emb对搜推场景的&lt;strong>序&lt;/strong>关系的保留程度。&lt;/li>
&lt;li>作者发现，转换成semantic id之后，信息只保留了37.14%；进一步，如果semantic id是在下游任务中端到端训练的，则信息只保留了5.5%，也就是说94.5%的pretrain emb的序的信息都丢掉了，也就是灾难遗忘。
&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-04-mme-sid-paper-reading/mme-sid-formula9.png">&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h1 id="semantic-id构建方法">Semantic id构建方法&lt;/h1>
&lt;ul>
&lt;li>3套emb来源，一套id-based经典搜推模型产出的包含协同信号的emb，另外两套是LLM2CLIP产出的多模态文本和图片emb。作者提到传统CLIP对长文本处理能力较弱，所以升级到LLM2CLIP，能更好地处理长文本。&lt;/li>
&lt;li>Semantic id构建方法是经典的RQ-VAE的方法，但有如下两个改进点：&lt;/li>
&lt;li>将emb的重构loss由MSE升级成MMD (maximum mean discrepancy)，MSE是计算原始emb和重构emb的欧式距离的误差，而MMD是计算两个分布的diff，实验表明能MMD比MSE能保留更多的pretrain多模态emb信息（即上述公式9），保留44.36%&lt;/li>
&lt;li>对量化后的emb做了对齐，因为LLM2CLIP本身进行了图文模态的对齐，所以文中只新增了id emb分别和文本、图片模态的对齐&lt;/li>
&lt;li>此外，还有一点论文没提但可能和常规RQ-VAE不同之处，就是原始emb在进行RQ-VAE之前，有一个Encoder升维的操作，在重构loss前对应有一个Decoder降维的操作，而semantic id量化恢复emb是Decoder之前的那个。这一升一降，估计也有助于缓解维度坍缩。&lt;/li>
&lt;/ul>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2025-10-04-mme-sid-paper-reading/mme-sid-fig2.png">&lt;/p></description></item><item><title>公告</title><link>https://bitjoy.net/posts/2025-08-15-announcement/</link><pubDate>Fri, 15 Aug 2025 22:03:06 +0800</pubDate><guid>https://bitjoy.net/posts/2025-08-15-announcement/</guid><description>&lt;p>博客数据恢复中，敬请期待！&lt;/p>
&lt;p>测试图片：
&lt;img alt="这是图片" loading="lazy" src="https://bitjoy.net/posts/2025-08-15-announcement/myimg.png">&lt;/p>
&lt;p>测试代码：&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// Necessary header files for input output functions
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">using&lt;/span> &lt;span style="color:#66d9ef">namespace&lt;/span> std;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// main() function: where the execution of
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// C++ program begins
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// This statement prints &amp;#34;Hello World&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> cout &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Hello World&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>测试数学公式：
This is an inline \(a^*=x-b^*\) equation.&lt;/p></description></item><item><title>明朝那些事儿（伍）——帝国飘摇</title><link>https://bitjoy.net/posts/2020-02-15-stories-about-ming-dynasty-5/</link><pubDate>Sat, 15 Feb 2020 11:16:39 +0800</pubDate><guid>https://bitjoy.net/posts/2020-02-15-stories-about-ming-dynasty-5/</guid><description>明朝那些事儿（伍）——帝国飘摇</description></item><item><title>明朝那些事儿（肆）——粉饰太平</title><link>https://bitjoy.net/posts/2020-01-21-stories-about-ming-dynasty-4/</link><pubDate>Tue, 21 Jan 2020 11:03:58 +0800</pubDate><guid>https://bitjoy.net/posts/2020-01-21-stories-about-ming-dynasty-4/</guid><description>明朝那些事儿（肆）——粉饰太平</description></item><item><title>明朝那些事儿（叁）——妖孽宫廷</title><link>https://bitjoy.net/posts/2019-12-01-stories-about-ming-dynasty-3/</link><pubDate>Sun, 01 Dec 2019 10:51:11 +0800</pubDate><guid>https://bitjoy.net/posts/2019-12-01-stories-about-ming-dynasty-3/</guid><description>明朝那些事儿（叁）——妖孽宫廷</description></item><item><title>明朝那些事儿（贰）——万国来朝</title><link>https://bitjoy.net/posts/2019-10-12-stories-about-ming-dynasty-2/</link><pubDate>Sat, 12 Oct 2019 10:34:32 +0800</pubDate><guid>https://bitjoy.net/posts/2019-10-12-stories-about-ming-dynasty-2/</guid><description>明朝那些事儿（贰）——万国来朝</description></item><item><title>明朝那些事儿（壹）——洪武大帝</title><link>https://bitjoy.net/posts/2019-09-12-stories-about-ming-dynasty-1/</link><pubDate>Thu, 12 Sep 2019 10:27:22 +0800</pubDate><guid>https://bitjoy.net/posts/2019-09-12-stories-about-ming-dynasty-1/</guid><description>&lt;p>朱元璋，外号朱重八，1328年生于安徽凤阳。元朝取名字的习惯是父母年龄相加，所以推测此时朱元璋的父母年龄相加为88岁。元朝末年，朝廷腐败，苛捐杂税导致民不聊生。另外，1344年黄河泛滥、淮河流域遭遇严重瘟疫和干旱。朱元璋出生贫农，在这种天灾人祸的情况下，朱元璋的父、母、大哥、大哥长子等相继饿死，家里除了二哥，已经没有其他成员了。在这种情况下，朱元璋为了活下去，去附近的皇觉寺当和尚，主要工作是讨饭。&lt;/p>
&lt;p>后来元朝进一步衰败，各地不断爆发农民起义。元朝的腐朽官吏迫于朝廷压力，在无法镇压起义军的情况下，会把老百姓抓取交差，把他们当起义军杀掉。在这种情况下，朱元璋面临一个两难的选择，如果不起义，则有可能被元军抓去当作起义军杀掉；如果起义，则万一起义失败，则同样性命难保。就在此时，朱元璋幼年的朋友汤和写信邀请朱元璋加入他的起义军，共图大业。朱元璋本不想加入，但由于书信被别人告发，朱元璋被逼上梁山，正式开始了他的造反事业。&lt;/p>
&lt;p>造反的过程就不赘述了，朱元璋一开始投靠了汤和的老板郭子兴，后来由于出色的才能，脱离郭子兴开始独立创业。朱元璋从安徽开始，一路向东，沿途占领了很多城市，最终攻下南京（应天）作为自己的根据地。在南京，朱元璋又相继灭掉了他的两个强大的邻居，同样是起义军的陈友谅和张士诚。其中，和陈友谅的鄱阳湖水战被视为中世纪世界规模最大的水战，同时也是中国历史上继赤壁之战后的又一个以少胜多的典型战例，而且也是其中一方巧用火攻取得了胜利。&lt;/p>
&lt;p>消灭了几大起义军之后，朱元璋迎来了他最后的敌人——元朝，朱元璋北伐开始。但已到强弩之末的元朝实在是太弱了，没多久就被朱元璋赶到了大漠之外。1368年，朱元璋即皇帝位，定年号为洪武，国号为明，明朝正式成立。&lt;/p>
&lt;p>这里有必要提一下朱元璋霸业中的文臣武将。谋臣刘基，字伯温，通经史、晓天文、精兵法，他以辅佐明太祖朱元璋完成帝业、开创明朝并保持国家安定，因而驰名天下，被后人比作为诸葛武侯。后人赞赏刘基道：“三分天下诸葛亮，一统江山刘伯温”。武将就更多了，比如徐达和常遇春，一直跟着朱元璋征战沙场，特别是在鄱阳湖水战中，冲锋陷阵，对陈友谅发动突然袭击，为朱元璋的胜利立下了汗马功劳；此外还有李文忠、汤和、蓝玉、傅友德等人，他们要么是在军阀混战时期脱颖而出，要么是在北伐元朝的时候脱颖而出，总之都是战功赫赫、功高震主。当然，因为朱元璋，他们中的很多人下场都很惨，简要介绍请看这里：&lt;a href="https://kknews.cc/history/m2n34eg.html">https://kknews.cc/history/m2n34eg.html&lt;/a>。&lt;/p>
&lt;p>明朝建立之后，朱元璋一方面为了巩固自己的权力，另一方面为了防止自己死后开国功臣抢儿子的权力，总之就是为了巩固朱家的江山，开始对开国功臣大开杀戒，制造了很多惨案。其中比较有名的是洪武四大案：胡惟庸案，空印案，郭桓案和蓝玉案。胡惟庸与蓝玉案件习称“胡蓝之狱”，是朱元璋诛杀开国功臣的政治事件，而“空印案”与“郭桓案”则是对涉嫌贪污的官吏进行大规模的镇压。除此之外，大大小小还有很多诛杀开国功臣的案件，基本上把前面列举的所有开国功臣都杀了。其中朱元璋的发小汤和得以善终。&lt;/p>
&lt;p>当朱元璋把开国功臣杀得差不多的时候，朱元璋终于老去了，皇位传给了长孙朱允炆。朱元璋共有26个儿子，长子是朱标（太子）、四子朱棣（燕王）、十七子朱权（宁王）。朱元璋本打算把皇位传给长子朱标，但朱标先于其父亲朱元璋去世了，于是皇位就顺延到朱标的长子朱允炆手中。朱允炆继位之后很忧郁，因为朱元璋有二十多个儿子，被分封在全国各地，藩王势力日益膨胀，朱允炆于是与亲信大臣开始了一系列的削藩措施。大多数藩王都乖乖认命，有被贬的，被下狱的，被逼自杀的。当削藩到四叔朱棣头上时，朱棣起兵反抗，随后挥师南下，史称“靖难之役”。但仅凭朱棣一个藩的力量，和中央政权相比还是太过弱小，朱棣挟持了另一个藩王宁王，收编了其手下骁勇善战的朵颜三卫骑兵，一起反抗南京政权。由于朱棣被分封在北京，经常要和蒙古人大战，所以朱棣的有勇有谋，军事实力很强。而朱允炆领导的中央军队，由于绝大部分骁勇善战的开国功臣或老了或被朱元璋杀了，新生力量又没经历过真刀真枪，所以南京中央军队的军事实力比较弱。在这种情况下，朱棣的军队一路南下，最终攻下首都南京，夺取了侄子朱允炆的皇位，史称永乐大帝。而朱允炆在逃难的过程中下落不明。&lt;/p></description></item><item><title>【论文速读】End-to-End Differentiable Learning of Protein Structure</title><link>https://bitjoy.net/posts/2019-08-30-paper-reading-end-to-end-differentiable-learning-of-protein-structure/</link><pubDate>Fri, 30 Aug 2019 17:45:12 +0800</pubDate><guid>https://bitjoy.net/posts/2019-08-30-paper-reading-end-to-end-differentiable-learning-of-protein-structure/</guid><description>&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-30-paper-reading-end-to-end-differentiable-learning-of-protein-structure/ProteinStructurePrediction.png">&lt;/p>
&lt;p>首先放出本文的Hightlights：&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-30-paper-reading-end-to-end-differentiable-learning-of-protein-structure/Highlights.png">&lt;/p>
&lt;p>蛋白质三级结构预测一般分为两种方法，一种是基于模板的预测方法（Template-Based Modeling, TBM），另一种是从头测序方法（Free Modeling, FM）。TBM方法目前已经能达到比较好的预测精度，但并不是所有蛋白都有同源模板，当模板蛋白和目标蛋白的相似性低于某个阈值时，TBM方法的性能就会比较差。而传统的FM方法，需要过多的人工特征，使得整个流程非常复杂。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-30-paper-reading-end-to-end-differentiable-learning-of-protein-structure/Figure1.png">
&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-30-paper-reading-end-to-end-differentiable-learning-of-protein-structure/Rosetta.png">&lt;/p>
&lt;p>作者在一次报告中，将传统的蛋白质结构预测算法（上图）比作10年前的图像识别算法（下图），虽然10年前的图像识别算法也能达到比较好的性能，但需要很多人工设计的特征，比如SIFT特征等，不够简洁漂亮。随着深度学习的兴起，现在图像识别不再需要人工设计特征，只要搭建好神经网络，输入原始图片即可完成识别和分类，性能比之前的人工方法还要好。所以，作者也希望能提出一个简洁、纯深度学习的模型来预测蛋白质的三级结构。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-30-paper-reading-end-to-end-differentiable-learning-of-protein-structure/Pascal-voc-2009.png">&lt;/p>
&lt;p>本文的模型（Recurrent Geometric Networks, RGN）从宏观上来说就如博客开篇的图片所示，非常的简洁漂亮，输入是蛋白质的一维序列，经过神经网络，输出是每个氨基酸残基的三个扭转角，然后再通过三维重构，得到蛋白质的笛卡尔坐标。&lt;/p>
&lt;p>更具体来说，RGN包括三个部分，分别是模型预测、三维重构和误差反向传播，下面分别介绍这三个部分。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-30-paper-reading-end-to-end-differentiable-learning-of-protein-structure/Figure2.png">&lt;/p>
&lt;p>模型预测是上图的左下角部分，即输入是蛋白质序列，输出是每个氨基酸残基的三个扭转角。因为每个氨基酸对应三个扭转角输出，每个氨基酸和其上下文的氨基酸有关联，所以使用双向LSTM最合适不过了。Bi-LSTM没什么好讲的，关键讲讲其模型的输入和输出。输入部分，作者把每个氨基酸编码成一个41维的向量，如上图所示，其中包括20维氨基酸的one-hot向量（因为只有20种氨基酸）、20维PSSM位置向量和1维具体的位置信息。其中的PSSM位置向量可以理解为这个位置上的不同氨基酸的概率分布，由于有20种氨基酸，所以PSSM向量维度也是20。网上没有找到氨基酸的PSSM向量示例，找到一个DNA的，如下图，每个位置上，字母越大表示出现该核苷酸的概率越大，换成氨基酸是类似的道理。所以整个网络的输入，除了PSSM矩阵，没有任何人工设计的特征，已经很优雅了。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-30-paper-reading-end-to-end-differentiable-learning-of-protein-structure/mef2_seq_logo.png">
&lt;a href="https://davetang.org/muse/2013/10/01/position-weight-matrix/">https://davetang.org/muse/2013/10/01/position-weight-matrix/&lt;/a>&lt;/p>
&lt;p>Bi-LSTM的输出是三个扭转角，但并不是三个实数这么简单。作者首先把整个拉氏图平面聚类，比如聚类成m=60个点，然后就把输出离散化成60类的分类问题。分类输出采用Softmax归一化，这样就会得到60类的概率分布，如RGN网络图最右边的子图所示。60类的概率分布再通过加权平均的方式得到最终的三个扭转角的实数值。我很好奇为什么需要经过一个离散再加权平均的方法，Bi-LSTM直接回归输出三个扭转角的实数不是更省事吗？&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-30-paper-reading-end-to-end-differentiable-learning-of-protein-structure/Cluster2Alphabet.png">&lt;/p>
&lt;p>预测得到三个扭转角之后，进入RGN的第二个阶段，就是三维重构，在RGN网络图的左上角。三维重构说起来也简单，就是根据每个氨基酸残基的三个扭转角，重构出蛋白质的三维结构。由于常规的蛋白质三维结构坐标系是笛卡尔坐标系（直角坐标系），所以需要把扭转角坐标转换为笛卡尔坐标，以便于求解误差。这个部分作者没有细说，因为是另一篇论文：&lt;a href="https://onlinelibrary.wiley.com/doi/full/10.1002/jcc.25772">Parallelized Natural Extension Reference Frame: Parallelized Conversion from Internal to Cartesian Coordinates&lt;/a>。&lt;/p>
&lt;p>最后就是怎样求解误差以及误差反向传播了。这个也比较有意思，想想看，对于一条长为L的蛋白质序列，给定预测的三维结构和真实的三维结构，怎样计算它们之间的误差。不能直接对应坐标相减，因为有可能两个坐标系的坐标原点不一样。作者的方法是这样的：对于预测结构，求每两个氨基酸的距离差，就是\(\tilde{d}_{j,k}\)；对于真实结构，也做类似的操作。这样做的好处是抹掉了坐标原点的影响，用两点之间的相对距离来表示三维结构。然后，对真实的\(\tilde{d}_{j,k}^{(exp)}\)和预测的\(\tilde{d}_{j,k}^{(pred)}\)再求二范数\(||D||_2\)，最后除以长度进行归一化，就得到了误差dRMSD。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-30-paper-reading-end-to-end-differentiable-learning-of-protein-structure/dRMSD.png">&lt;/p>
&lt;p>上述dRMSD误差相比于之前领域内常用的TMscore，好处就是可微分，可自动求导，可梯度下降了；另外，如上所述，dRMSD不要求预测结构和真实结构进行对齐；但是有一点是dRMSD对size敏感，而且不能识别镜面对称这种错误结构，比如左手和右手的结构是镜面对称的，如果真实结构是左手，但模型预测成了右手，dRMSD是检测不到这种错误的。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-30-paper-reading-end-to-end-differentiable-learning-of-protein-structure/dRMSD.vs.TMscore.png">&lt;/p>
&lt;p>模型介绍完毕，训练和测试数据集来自CASP竞赛。作者把每一届CASP比赛的数据集作为测试集，从CASP7~CASP12；每一届比赛之前公布的所有PDB数据集（seq, structure）作为对应测试集的训练集。其中，CASP11分出一部分作为验证集，用来优化网络超参数。&lt;/p>
&lt;p>测试结果如Table 1所示，可以看到，在没有模板的FM类别中，本文的RGN预测误差是最小的；在有模板的TBM类别中，RGN的性能几乎垫底，当然这里参与评测的都是当届比赛中Top-5的模型，所以RGN和这5个模型比是垫底，但差距是很小的。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-30-paper-reading-end-to-end-differentiable-learning-of-protein-structure/Table1.png">&lt;/p>
&lt;p>另外，作者提到，在TBM类别的数据集中，CASP的参赛模型比较依赖模板的质量。具体来说，对于真实的结构，如果模板结构和真实结构误差很小（y轴），则模型的预测结构和真实结构的误差也很小（x轴），这两个变量成一定的线性相关关系（第一行）。而对于本文的RGN，则没有这种相关关系，说明RGN一视同仁，不会受模板质量的影响，因为RGN是纯深度学习的模型，根本就没有用到模板。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-30-paper-reading-end-to-end-differentiable-learning-of-protein-structure/Figure4.png">&lt;/p>
&lt;p>在预测速度上，RGN虽然需要训练几周甚至上月的时间，但预测速度是毫秒级别的，是评测的几个模型中最快的。快速的RGN能使一些新的应用成为可能，比如药物发现、蛋白质设计等。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-30-paper-reading-end-to-end-differentiable-learning-of-protein-structure/Table2.png">&lt;/p>
&lt;p>最后，总结一下本文的主要工作、创新点和局限性：&lt;/p>
&lt;p>三个特点：&lt;/p>
&lt;ul>
&lt;li>torsional angles，局部信息&lt;/li>
&lt;li>geometric units ，全局信息&lt;/li>
&lt;li>dRMSD，局部+全局&lt;/li>
&lt;/ul>
&lt;p>创新点：&lt;/p>
&lt;ul>
&lt;li>简洁，Model replaces structure prediction pipelines with one mathematical function&lt;/li>
&lt;li>另辟蹊径，纯deeplearning，不依赖structural templates、co-evolutionary information、energy model等，本文预测融合了本文方法和领域知识的新模型有望解决蛋白质结构预测问题&lt;/li>
&lt;/ul>
&lt;p>局限性：&lt;/p>
&lt;ul>
&lt;li>依赖PSSM矩阵&lt;/li>
&lt;/ul>
&lt;p>本文作者来自哈佛医学院系统药理学实验室，文章只有作者一个人，很了不起了。哈佛医学院的另一个教授评价作者：“AlQuraishi 研究的特点在于，一名埋头在哈佛医学院和波士顿生物医学社区丰富研究生态系统中的研究人员，居然能够在计算机科学最热门的领域里抗衡谷歌等巨头。——Peter Sorger”，太棒了，我也想做这样的研究。&lt;/p></description></item><item><title>CS224N（2.12）Convolutional Networks for NLP</title><link>https://bitjoy.net/posts/2019-08-05-cs224n-0212-convolutional-networks-for-nlp/</link><pubDate>Mon, 05 Aug 2019 16:23:57 +0800</pubDate><guid>https://bitjoy.net/posts/2019-08-05-cs224n-0212-convolutional-networks-for-nlp/</guid><description>&lt;p>今天我们介绍如何使用CNN解决NLP问题。截止目前，我们学习了很多RNN模型来解决NLP问题，由于NLP是序列的问题，使用RNN这种循环神经网络是很符合直觉的，而且也取得了不错的效果。但是，由于RNN速度较慢，而且梯度消失问题比较严重，人们就想借用CV领域的CNN，看是否能解决NLP的问题。&lt;/p>
&lt;p>我们在之前的博客中已经详细介绍过&lt;a href="https://bitjoy.net/posts/2019-05-04-neural-networks-and-deep-learning-6-dl/">卷积神经网络CNN&lt;/a>，这里不再详细介绍。下面我们以一篇paper中使用CNN对句子进行情感分类为例，简要介绍下怎样将CNN应用到NLP中。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-05-cs224n-0212-convolutional-networks-for-nlp/p27.png">&lt;/p>
&lt;p>上图是一个非常简单的CNN网络，用来对影评进行情感分类，输入是一个长度为7的句子，我们把每个词用长度为5的词向量来表示，则对于输入来说，得到了一个7×5的矩阵，这不就相当于一张图片了吗，后续操作就很像CV了。第二步，需要对输入“图片“进行卷积操作，请注意，虽然输入可以看做图片，但其本质上是“一维”的句子，所以我们设计卷积核大小时，卷积核的宽度要固定为5，保证卷积核能对完整的词向量进行操作。这里共设计了3个不同大小的卷积核，每种大小有2个卷积核，共6个卷积核。卷积操作完成之后得到了6个特征图，对每个特征图取max pooling再拼接起来，得到一个长为6的向量，这就是用CNN对句子抽取的特征向量。最后再接一个softmax进行二分类。&lt;/p>
&lt;p>除了上图展示的CNN操作外，还有一些CNN操作有可能会用到：&lt;/p>
&lt;ol>
&lt;li>卷积操作的stride=k，每k行一个group进行卷积，默认卷积操作是k=1&lt;/li>
&lt;li>卷积操作的dilation=k，跨k行进行卷积，默认卷积操作是k=1&lt;/li>
&lt;li>padding，上图卷积操作之后，feature map相比于输入维度变小了，如果要想保持维度不变，可对输入进行padding&lt;/li>
&lt;li>max/avg pooling over time，上图的max pooling即为max pooling over time，即对整个句子所有时间步的feature取max&lt;/li>
&lt;li>k-max pooling，对整个句子的所有时间步的feature取top-k的max值，同时保持feature的相对顺序不变，上述max pooling相当于1-max pooling&lt;/li>
&lt;li>local max pooling，stride=k，对每k个feature取max，这个和CV里默认的max pooling是一样的，CV里就是画一个框取max&lt;/li>
&lt;li>dropout=p，对于每个连接，随机以概率p丢弃，属于一种正则化技术，能有效增加模型的鲁棒性&lt;/li>
&lt;li>skip connections，之前讲过很多次了，直连线路，没有中间商赚差价&lt;/li>
&lt;li>batch normalization，对每次卷积操作的输出进行z-score标准化，使得均值为0，标准差为1，能有效增加模型的鲁棒性&lt;/li>
&lt;li>卷积核大小为1×1的卷积，相当于卷积前后的feature map的全连接，但又比全连接的参数少，因为一个卷积核的参数是共享的&lt;/li>
&lt;/ol>
&lt;p>最后，给出我们目前所学的工具箱：&lt;/p>
&lt;ul>
&lt;li>词袋模型：对于一个句子，简单的把所有词的词向量进行平均，也能取得不错的baseline效果&lt;/li>
&lt;li>基于滑动窗口的模型：对于POS、NER等不需要很长的上下文信息的问题来说，效果不错&lt;/li>
&lt;li>CNN：对分类问题效果很好，容易在GPU上并行，所以效率很高&lt;/li>
&lt;li>RNN：对于NLP问题来说，符合认知，对分类问题效果不是很好（如果只用最后一个隐状态的话），加上Attention性能提升明显，特别适合序列标注、语言模型等序列问题&lt;/li>
&lt;/ul>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-05-cs224n-0212-convolutional-networks-for-nlp/p32.png">&lt;/p></description></item><item><title>CS224N（2.7）Question Answering</title><link>https://bitjoy.net/posts/2019-08-04-cs224n-0207-question-answering/</link><pubDate>Sun, 04 Aug 2019 15:45:07 +0800</pubDate><guid>https://bitjoy.net/posts/2019-08-04-cs224n-0207-question-answering/</guid><description>&lt;p>这节课的内容比较简单，是问答系统（Question Answering, QA）的入门介绍。&lt;/p>
&lt;h1 id="qa简介">QA简介&lt;/h1>
&lt;p>首先，为什么需要QA？目前各大搜索引擎对于一个查询，给出的都是一个结果列表。但是很多查询是一个问题，答案也往往比较确定，比如“现任美国总统是谁？”，此时，返回一堆结果列表就显得太过啰嗦了，尤其是在手机等移动设备上搜索时，简单的给出回答也许会更好一些。另一方面，智能手机上的助手如Siri、Google Now之类的，用户期望的也是简洁的答案，而不是一堆网页列表。&lt;/p>
&lt;p>QA系统的组成主要有两个部分，一部分是根据问题检索到相关的文档，这部分是传统的信息检索的内容；另一部分是对检索到的文档进行阅读理解，抽取出能回答问题的答案，这部分就是本文要介绍的QA系统。&lt;/p>
&lt;p>QA的历史可追溯到上世纪七十年代，但真正取得突破性进展也就是最近几年。2015/2016年，几个大规模QA标注数据集的发表，极大的推动了这个领域的发展。这其中比较有名的数据集是斯坦福大学发布的Stanford Question Answering Dataset (SQuAD)。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-04-cs224n-0207-question-answering/p16.png">&lt;/p>
&lt;p>SQuAD数据集的每一个样例包含一段描述P，一个问题Q，以及对Q的人工标注答案A。为了使数据集更加鲁棒，对于每个问题，都给出了三个人工标注答案。每个答案都是描述P中的一小段文字，称为一个span。所以，问题相对来说比较简单，答案可以直接从描述中提取sub-sequence得到。&lt;/p>
&lt;p>QA系统的评价指标有两个，一个是确定性匹配Exact match，即对于每个问题，模型给出的回答如果和3个答案中的任意一个完全匹配，则加1分，否则不加分。另一个是F1指标，使用词袋模型（不考虑词的顺序），对于每个问题，模型给出的回答和3个答案中的每一个计算F1，这个问题的F1是3个F1的最大值，最终得分是所有问题的F1打分的均值。Exact match和F1都不考虑标点符号和冠词。相对来说，F1比Exact match更可靠和鲁棒一些。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-04-cs224n-0207-question-answering/p17.png">&lt;/p>
&lt;p>经过两年的两三年的刷榜，SQuAD数据集的最好性能已经超越了人类的性能，为了增加数据集的难度，斯坦福后续推出升级版本SQuAD 2.0。&lt;/p>
&lt;p>由于1.1版本的问题都有答案，所以QA系统变成了一个排序系统，只需要把Answer列表中排名第一的结果输出就好了。2.0在1.1的基础上，增加了没有答案的问题，可以理解为假问题，对系统造成干扰。此时就要求系统判断这个问题能否从描述P中获得答案，如果没有答案的话，就不输出任何回答&amp;lt;No Answer&amp;gt;。对于没有答案的问题，如果系统没有输出答案，得1分，否则输出任何答案都得0分。&lt;/p>
&lt;p>SQuAD数据集的局限性：&lt;/p>
&lt;ol>
&lt;li>回答都是span-based类型的，没有yes/no、计数、why等的问答。&lt;/li>
&lt;li>由于构造问题q的时候，已知了描述P，那么q和P的描述会很像，无论是用词还是语法。而搜索引擎面临的真实情况往往是，q根本不知道P是什么，有可能q和P的描述在行文及用词上有很大差别。&lt;/li>
&lt;li>描述P比较简单，因为答案是一个span，所以模型把q和P匹配，找到可能的答案位置就行。实际的复杂场景有可能要综合好几个句子的信息，还要理解不同的指代关系等才能得出最终答案。&lt;/li>
&lt;/ol>
&lt;p>虽然SQuAD数据集还有不少局限性，但由于其是一个well-targeted、well-structured、clean dataset，在QA发展初期，还是为促进QA发展立下了汗马功劳。&lt;/p>
&lt;h1 id="stanford-attentive-reader">Stanford Attentive Reader&lt;/h1>
&lt;p>下面介绍一下Chris Manning组针对SQuAD数据集开发的QA系统——Stanford Attentive Reader。该系统目前虽然不是最好性能，但它包含QA的基本模块，可以作为QA的一个baseline模型。&lt;/p>
&lt;p>首先模型对问题q进行表征的方法如下，输入是q中每个词的词向量，然后使用一个Bi-LSTM提取句子特征，由于是双向的LSTM，所以模型把正向和反向的LSTM的最后一个隐状态拼接起来，作为对整个句子的表征。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-04-cs224n-0207-question-answering/p28.png">&lt;/p>
&lt;p>由于SQuAD数据集的回答都是描述P中的一个span，那么，模型只需要预测出这个span在P中的起始位置和终止位置即可，具体方法如下图所示。其实也很简单，上一步我们得到的句子的表征向量q，下一步，我们对描述P也使用Bi-LSTM，得到描述P中每个词的表征向量\(\tilde p_i\)。然后，使用两次Attention，用q查询集合\(P=[\tilde p_1,…,\tilde p_n]\)，得到答案span的起始位置\(\alpha_i\)和终止位置\(\alpha’_i\)。另外，由于\(q\)和\(\tilde p_i\)的维度可能不一样，又或者为了提升模型性能，在计算Attention score的时候，不是简单的向量点积，而是采用了线性变换的方法，增加了参数\(W\)。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-04-cs224n-0207-question-answering/p30.png">&lt;/p>
&lt;p>后来，Chris Manning组又推出了升级版本Stanford Attentive Reader++，主要包括两个方面。首先，对表征问题的网络进行了改进，\(q\)不仅包含Bi-LSTM的两个尾结点的隐状态，而是包含整个问题所有隐状态的加权平均，而且网络层数增加到了3层。其次，对描述P的表征方面，原来的输入只包含词向量，现在还包含语言特征（如POS、NER的标签）、词频、以及近义词的相似度等。改进版模型性能提升了不少。&lt;/p>
&lt;p>另一个比较流行的QA系统是BiDAF，如下图所示，这里不再详细介绍。它的特点一方面输入不仅包含词向量，还包含字符级别的特征。另一大创新是在Attention Flow Layer，相对于Stanford Attentive Reader，BiDAF的Attention是双向的，不但包含q对P的Attention，还包含P对q的Attention。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-04-cs224n-0207-question-answering/p37.png">&lt;/p></description></item><item><title>CS224N（1.31）Translation, Seq2Seq, Attention</title><link>https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/</link><pubDate>Fri, 02 Aug 2019 11:50:31 +0800</pubDate><guid>https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/</guid><description>&lt;p>今天介绍另一个NLP任务——机器翻译，以及神经网络机器翻译模型seq2seq和一个改进技巧attention。&lt;/p>
&lt;p>机器翻译最早可追溯至1950s，由于冷战的需要，美国开始研制由俄语到英语的翻译机器。当时的机器翻译很简单，就是自动从词典中把对应的词逐个翻译出来。&lt;/p>
&lt;p>后来在1990s~2010s，统计机器翻译（Statistical Machine Translation, SMT）大行其道。假设源语言是法语\(x\)，目标语言是英语\(y\)，机器翻译的目标就是寻找\(y\)，使得\(P(y|x)\)最大，也就是下图的公式。进一步，通过贝叶斯公式可拆分成两个概率的乘积：其中\(P(y)\)就是之前介绍过的语言模型，最简单的可以用n-gram的方法；\(P(x|y)\)是由目标语言到源语言的翻译模型。为什么要把\(P(y|x)\)的求解变成\(P(x|y)*P(y)\)？逐个击破的意思，\(P(x|y)\)专注于翻译模型，翻译好局部的短语或者单词；而\(P(y)\)就是之前学习的语言模型，用来学习整个句子\(y\)的概率，专注于翻译出来的句子从整体上看起来更加通顺、符合语法与逻辑。所以问题就转化为怎样求解\(P(x|y)\)。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p7.png">&lt;/p>
&lt;p>SMT进一步把\(P(x|y)\)分解成\(P(x,a|y)\)，其中\(a\)表示一个对齐alignment，可以认为是两种语言之间单词和单词或短语和短语的一个对齐关系。如下图所示是一个英语和法语的alignment。对齐本身就很复杂，存在1对1，1对多，多对1，多对多等情况，所以\(P(x,a|y)\)的求解在给定\(y\)的情况下，不但要考虑对齐方案\(a\)的情况\(P(a|y)\)，还需要考虑对齐之后词与词的翻译情况\(P(x|a,y)\)，可能的情况非常多。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p10.png">&lt;/p>
&lt;p>那么，SMT怎样找到\(\arg max_y\)呢？穷举所有情况是不可能的，启发式搜索是可行的。形象描述就是在搜索过程中，对概率较低的路径进行剪枝，只保留概率较大的翻译情况。如下图的搜索树，对于概率较低的路径就不往下搜索了。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p18.png">&lt;/p>
&lt;p>总之，统计机器翻译非常复杂，有很多的子模块，需要很多的人工干预和特征工程。&lt;/p>
&lt;hr>
&lt;p>2014年，seq2seq模型横空出世，神经网络机器翻译（Neural Machine Translation, NMT）方兴未艾。seq2seq顾名思义，就是从序列到序列的模型，因为机器翻译的源语言和目标语言都是seq。&lt;/p>
&lt;p>seq2seq的NMT如下图所示，它由两个RNN组成，左边的红色部分称为Encoder RNN，它负责对源语言进行编码（Encode）；右边的绿色部分称为Decoder RNN，它负责对目标语言进行解码（Decode）。首先，Encoder RNN可以是任意一个RNN，比如朴素RNN、LSTM或者GRU。Encoder RNN负责对源语言进行编码，学习源语言的隐含特征。Encoder RNN的最后一个神经元的隐状态作为Decoder RNN的初始隐状态。Decoder RNN是一个条件语言模型，一方面它是一个语言模型，即用来生成目标语言的；另一方面，它的初始隐状态是基于Encoder RNN的输出，所以称Decoder RNN是条件语言模型。Decoder RNN在预测的时候，需要把上一个神经元的输出作为下一个神经元的输入，不断的预测下一个词，直到预测输出了结束标志符&amp;lt;END&amp;gt;，预测结束。Encoder RNN的输入是源语言的word embeding，Decoder RNN的输入是目标语言的word embeding。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p24.png">&lt;/p>
&lt;p>seq2seq是一个很强大的模型，不但可以用来做机器翻译，还可以用来做很多NLP任务，比如自动摘要、对话系统等。&lt;/p>
&lt;p>seq2seq作为一个条件语言模型，形式化来说，它直接对\(P(y|x)\)进行建模，在生成\(y\)的过程中，始终有\(x\)作为条件，正如下图的条件概率所示。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p26.png">&lt;/p>
&lt;p>上面介绍了seq2seq的预测过程，seq2seq的训练过程如下图所示。训练的时候，我们同时需要源语言和翻译好的目标语言，分别作为Encoder RNN和Deocder RNN的输入。对于Encoder RNN没什么好说的。Decoder RNN在训练阶段，每一个时间步的输入是提供的正确翻译词，输出是预测的下一个时间步的词的概率分布，比如在\(t=4\)，预测输出是\(\hat y_4\)，而正确答案是“with”，根据交叉熵损失函数，\(J_4=-\log P(“with”)\)。总的损失函数就是所有时间步的损失均值。&lt;/p>
&lt;p>seq2seq的训练过程是end2end的，即把Encoder RNN和Decoder RNN作为一个整体进行训练，不会像SMT一样有很多的子模块单独训练。当然seq2seq也可以单独对encoder和deconder进行训练优化，再组合，但是这个效果不一定会比整体优化encoder和deconder更好。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p27.png">&lt;/p>
&lt;p>上上张图介绍的seq2seq的预测过程，实际上是一个贪心的预测过程，即在Decoder RNN的每一步都贪心选择\(\hat y_t\)概率最大的那个词。但是贪心只能保证每一步是最优的，无法保证预测出来的句子整体是最优的。特别是如果在\(t\)时刻贪心选择的词不是全局最优，会导致\(t\)时刻往后的所有预测词都是错误的，没有回头路了。但是如果每个时间步都穷举所有可能的情况的话，时间复杂度\(O(V^T)\)又太高了。&lt;/p>
&lt;p>Beam search搜索策略是贪心策略和穷举策略的一个折中方案，它在预测的每一步，都保留Top-k高概率的词，作为下一个时间步的输入。k称为beam size，k越大，得到更好结果的可能性更大，但计算消耗也越大。请注意，这里的Top-k高概率不仅仅指当前时刻的\(\hat y_t\)的最高概率，而是截止目前这条路径上的累计概率之和，如下图的公式所示。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p31.png">&lt;/p>
&lt;p>举例如下，假设\(k=2\)，第一个时间步保留Top-2的词为”he”和”I”，他们分别作为下一个时间步的输入。”he”输入预测输出前两名是”hit”和”struck”，则”hit”这条路的累加概率是”he”的概率加上”hit”的概率=-1.7，类似的可以算出其他几个词对应路径的概率打分。最后在这4条路上保留\(k=2\)条路，所以”hit”和”was”对应路径保留，作为下一个时间步的输入；”struck”和”got”对应路径被剪枝。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p34.png">&lt;/p>
&lt;p>最终的搜索树如下图所示，可以看到在每个时间步都只保留了\(k=2\)个节点往下继续搜索。最后”pie”对应的路径打分最高，通过回溯法得到概率最高的翻译句子。请注意，beam search作为一种剪枝策略，并不能保证得到全局最优解，但它能以较大的概率得到全局最优解，同时相比于穷举搜索极大的提高了搜索效率。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p44.png">&lt;/p>
&lt;p>在beam search的过程中，不同路径预测输出结束标志符&amp;lt;END&amp;gt;的时间点可能不一样，有些路径可能提前结束了，称为完全路径，暂时把这些完全路径放一边，其他路径接着beam search。beam search的停止条件有很多种，可以设置一个最大的搜索时间步数，也可以设置收集到的最多的完全路径数。&lt;/p>
&lt;p>当beam search结束时，需要从n条完全路径中选一个打分最高的路径作为最终结果。由于不同路径的长度不一样，而beam search打分是累加项，累加越多打分越低，所以需要用长度对打分进行归一化，如下图所示。那么，为什么不在beam search的过程中就直接用下面的归一化打分来比较呢？因为在树搜索的过程中，每一时刻比较的两条路径的长度是一样的，即分母是一样的，所以归一化打分和非归一化打分的大小关系是一样的，即在beam search的过程中就没必要对打分进行归一化了。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-02-cs224n-0131-translation-seq2seq-attention/p46.png">&lt;/p></description></item><item><title>CS224N（1.29）Vanishing Gradients, Fancy RNNs</title><link>https://bitjoy.net/posts/2019-08-01-cs224n-0129-vanishing-gradients-fancy-rnns/</link><pubDate>Thu, 01 Aug 2019 11:03:17 +0800</pubDate><guid>https://bitjoy.net/posts/2019-08-01-cs224n-0129-vanishing-gradients-fancy-rnns/</guid><description>&lt;h1 id="梯度消失">梯度消失&lt;/h1>
&lt;p>今天介绍RNN的梯度消失问题以及为了解决这个问题引出的RNN变种，如LSTM何GRU。&lt;/p>
&lt;p>在&lt;a href="https://bitjoy.net/posts/2019-07-31-cs224n-0124-language-models-and-rnns/">上一篇博客&lt;/a>中，通过公式推导，我们已经解释了RNN为什么容易产生梯度消失或梯度爆炸的问题，核心问题就是RNN在不同时间步使用共享参数\(W\)，导致\(t+n\)时刻的损失对\(t\)时刻的参数的偏导数存在\(W\)的指数形式，一旦\(W\)很小或很大就会导致梯度消失或梯度爆炸的问题。下图形象的显示了梯度消失的问题，即梯度不断反传，梯度不断变小（箭头不断变小）。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-01-cs224n-0129-vanishing-gradients-fancy-rnns/p11.png">&lt;/p>
&lt;p>梯度消失会带来哪些问题呢？一个很明显的问题就是参数更新更多的受到临近词的影响，那些和当前时刻\(t\)较远的词对当前的参数更新影响很小。如下图所示，\(h^{(1)}\)对\(J^{(2)}(\theta)\)的影响就比对\(J^{(4)}(\theta)\)的影响大。久而久之，因为梯度消失，我们就不知道\(t\)时刻是真的对\(t+n\)时刻没影响还是因为梯度消失导致我们没学习到这种影响。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-01-cs224n-0129-vanishing-gradients-fancy-rnns/p14.png">&lt;/p>
&lt;p>下图是一个更形象的例子，假设我们需要预测句子The writer of the books下一个单词，由于梯度消失，books对下一个词的影响比writer对下一个词的影响更大，导致模型错误的预测成了are，但这显然是不对的。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-01-cs224n-0129-vanishing-gradients-fancy-rnns/p17.png">&lt;/p>
&lt;p>类似的，如果梯度爆炸，则根据梯度下降的更新公式，参数会一瞬间更新非常大，导致网络震荡，甚至出现Inf或NaN的情况。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-01-cs224n-0129-vanishing-gradients-fancy-rnns/p18.png">&lt;/p>
&lt;p>梯度爆炸一个比较好的解决方法是梯度裁剪，即如果发现梯度的范数大于某个阈值，则以一定的比例缩小梯度的范数，但不改变其方向。如下下图所示，左子图是没有梯度裁剪的情况，由于RNN的梯度爆炸问题，导致快接近局部极小值时，梯度很大，参数突然爬上悬崖，然后又飞到右边一个随机的区域，miss掉了中间的局部极小值。右子图是增加了梯度裁剪之后，更新步伐变小，参数稳定在局部极小值附近。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-01-cs224n-0129-vanishing-gradients-fancy-rnns/p19.png">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-01-cs224n-0129-vanishing-gradients-fancy-rnns/p20.png">&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;/tbody>
&lt;/table>
&lt;p>总的来说，梯度爆炸相对好解决，但梯度消失就没那么简单了。在RNN中，每个时刻\(t\)，都改写了前一个时刻的隐状态，而由于梯度消失问题，长距离以前的状态对当前时刻的影响又很小，所以导致无法建模长距离依赖关系。那么，如果把每个时刻的状态单独保存起来，是否能解决长距离依赖问题呢？&lt;/p>
&lt;h1 id="lstm">LSTM&lt;/h1>
&lt;p>LSTM就是这样一个思路，请大家结合如下两幅图来理解：&lt;/p>
&lt;ul>
&lt;li>（下图）首先，从宏观上来说，LSTM的隐层神经元不仅包含隐状态\(h_t\)，还专门开辟了一个cell来保存过去的“记忆”\(c_t\)，LSTM希望用\(c_t\)来传递很久以前的信息，以达到长距离依赖的目的。所以LSTM隐层神经元的输入是上一时刻的隐状态\(h_{t-1}\)和记忆\(c_{t-1}\)，输出是当前时刻的隐状态\(h_t\)和希望传递给下一个时刻的记忆\(c_t\)。&lt;/li>
&lt;li>（上图）每个时刻\(t\)，为了调控遗忘哪些记忆，写入哪些新记忆，LSTM设置了两个门，分别是遗忘门\(f^{(t)}\)和写入门\(i^{(t)}\)。它们都是上一时刻的隐状态\(h^{(t-1)}\)和当前时刻的输入\(x^{(t)}\)的函数。\(f^{(t)}\)控制遗忘哪些记忆，即\(f^{(t)}\circ c^{(t-1)}\)；\(i^{(t)}\)控制写入哪些新记忆，即\(i^{(t)}\circ \tilde c^{(t)}\)，其中\(\tilde c^{(t)}\)即为期望写入的新记忆，它也是\(h^{(t-1)}\)和\(x^{(t)}\)的函数。最终，新时刻\(t\)的记忆就是这两部分的组合，请看上图\(c^{(t)}\)表达式。&lt;/li>
&lt;li>（上图）输出门\(o^{(t)}\)控制哪些记忆需要输出到下一个隐状态\(h^{(t)}\)，\(o^{(t)}\)自己又是\(h^{(t-1)}\)和\(x^{(t)}\)的函数。&lt;/li>
&lt;/ul>
&lt;p>大家结合上图的公式和下图的示意图就不难理解了。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-01-cs224n-0129-vanishing-gradients-fancy-rnns/p23.png">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-01-cs224n-0129-vanishing-gradients-fancy-rnns/p25.png">&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;/tbody>
&lt;/table>
&lt;p>LSTM解决梯度消失最直接的方法就是，遗忘门选择不遗忘，每一时刻的\(f^{(t)}\)都选择记住前一时刻的记忆\(c^{(t-1)}\)，然后直接传递给下一时刻。那么，所有前\(t-1\)时刻的记忆都会被完整的传递给第\(t\)时刻，从而对\(t\)时刻的输出产生影响。&lt;/p>
&lt;p>而朴素RNN无法保存前期状态的原因就是因为朴素RNN把之前时间步的信息都一股脑存储在隐状态\(h^{(t)}\)中了，隐状态\(h^{(t)}\)成为了整个网络的瓶颈，一旦出现梯度消失，则很久以前的信息对当前时刻的影响就微乎其微了。LSTM的关键就是开辟了一个新的cell来存储记忆，这个新的cell相当于记忆的一条捷径，时刻\(t\)除了可以像常规RNN一样通过\(h^{(t-1)}\)来获取很久以前的信息，还可以通过cell存储的记忆\(c^{(t-1)}\)来便捷地获取到很久以前的信息，所以隐状态\(h^{(t)}\)不再成为整个网络的瓶颈，有新的cell来分担。&lt;/p>
&lt;p>需要提醒的是，虽然LSTM开辟新的cell来存储记忆，但这个记忆也会受到连续梯度相乘的影响，所以依然存在梯度消失或梯度爆炸的问题，但从实际效果来看，LSTM性能很不错，也很鲁棒。&lt;/p>
&lt;h1 id="gru">GRU&lt;/h1>
&lt;p>另一种能缓解RNN梯度消失的网络——GRU。为了简化LSTM，GRU又没有cell了，但依然保留了门来控制信息的传递。首先看下图最后一个公式，当前时刻的隐状态\(h^{(t)}\)等于上一时刻的隐状态\(h^{(t-1)}\)和新写入的隐状态\(\tilde h^{(t)}\)的加权平均，通过更新门\(u^{(t)}\)来控制它们之间的比例，\(u^{(t)}\)是上一时刻的隐状态\(h^{(t-1)}\)和当前时刻的输入\(x^{(t)}\)的函数。新写入的隐状态\(\tilde h^{(t)}\)又通过一个重置门\(r^{(t)}\)来控制，类似的，\(r^{(t)}\)也是\(h^{(t-1)}\)和\(x^{(t)}\)的函数。&lt;/p>
&lt;p>个人觉得，GRU中的更新门\(u^{(t)}\)类似于LSTM中的输出门\(o^{(t)}\)；GRU中的重置门\(r^{(t)}\)类似于LSTM中的遗忘门\(f^{(t)}\)和写入门\(i^{(t)}\)的组合；GRU中新写入的隐状态\(\tilde h^{(t)}\)类似于LSTM中的细胞记忆\(c^{(t)}\)。所以，可以把GRU看作LSTM的简化版本。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-01-cs224n-0129-vanishing-gradients-fancy-rnns/p28.png">&lt;/p>
&lt;p>直观来说，GRU和LSTM类似，解决梯度消失的策略就是新增\(u^{(t)}\)来控制\(h^{(t-1)}\)和\(\tilde h^{(t)}\)的比例，如果\(u^{(t)}=0\)，则\(h^{(t)}=h^{(t-1)}\)，即\(t\)时刻的隐状态和上一时刻的隐状态相同，虽然这肯定效果不好，但至少说明GRU是有能力保留之前的隐状态的。&lt;/p>
&lt;p>GRU和LSTM的性能差不多，但GRU参数更少，更简单，所以训练效率更高。但是，如果数据的依赖特别长且数据量很大的话，LSTM的效果可能会稍微好一点，毕竟参数量更多。所以默认推荐使用LSTM。&lt;/p>
&lt;h1 id="其他缓解梯度消失的策略">其他缓解梯度消失的策略&lt;/h1>
&lt;p>由于链式法则，或者所选非线性激活函数的原因，不仅仅RNN，所有神经网络都存在梯度消失或者梯度爆炸的问题，比如&lt;a href="https://bitjoy.net/posts/2019-03-18-neural-networks-and-deep-learning-3-1-gradient-vanishing/">全连接网络&lt;/a>和CNN。一些通用解决方法如下：&lt;/p>
&lt;p>ResNet。因为梯度是在传递的过程中逐渐减小并消失的，如果跨越好几层直接进行连接，天然能保持远距离信息。个人理解，这就相当于买家和卖家直接相连，没有中间商赚差价\(\mathcal F(x)\)，买到的价格最接近卖出的价格\(x\)。能一定程度上减弱梯度消失的问题。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-01-cs224n-0129-vanishing-gradients-fancy-rnns/p30.png">&lt;/p>
&lt;p>更激进的是DenseNet，把跨越多层之间的很多神经元都连起来，也就是说有更多的线路没有中间商赚差价，进一步减弱梯度消失问题。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-01-cs224n-0129-vanishing-gradients-fancy-rnns/p31.png">&lt;/p>
&lt;p>HighwayNet。借鉴了LSTM和GRU的思路，不是像ResNet一样直接新增一条直连线路\(x\)，而是搞一个平衡因子\(u\)，卖家到买家的价格由\(u\)进行调和平均：\(u*\mathcal F(x)+(1-u)*x\)，用\(u\)来控制多少走中间商，多少走直连线路。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-01-cs224n-0129-vanishing-gradients-fancy-rnns/p32.png">&lt;/p>
&lt;p>虽然所有神经网络都存在梯度消失的问题，但RNN的这个问题更严重，因为它连乘的是相同的权重矩阵W，而且RNN针对的是序列问题，往往更深。&lt;/p>
&lt;h1 id="双向rnn">双向RNN&lt;/h1>
&lt;p>假设我们在对句子进行情感分类，如下图所示。对于terribly这个词，常规RNN，terribly的梯度只能看到左边的信息，看不到右边的信息，因为网络是从左到右的。单独看terribly或者从左往右看，在没有看到exciting时，可能认为terribly是贬义词，但是如果跟右边的exciting结合的话，则意思变为强烈的褒义词，所以有必要同时考虑左边和右边的信息。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-01-cs224n-0129-vanishing-gradients-fancy-rnns/p36.png">&lt;/p>
&lt;p>双向RNN包含两个RNN，一个从左往右，一个从右往左，两个RNN的参数是独立的。最后把两个RNN的输出拼接起来作为整体输出。那么，对于terribly这个词，它的梯度能同时看到左边和右边的信息。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-01-cs224n-0129-vanishing-gradients-fancy-rnns/p37.png">&lt;/p>
&lt;p>由于双向RNN对于某个时刻\(t\)，既需要知道\(t\)时刻前的信息（Forward RNN），又需要知道\(t\)时刻之后的信息（Backward RNN），所以双向RNN无法用于学习语言模型，因为语言模型只知道时刻\(t\)之前的信息，下一时刻的词需要模型来预测。对于包含完整序列的NLP问题，双向RNN应该是默认选择，它通常比单向RNN效果更好。&lt;/p>
&lt;h1 id="多层rnn">多层RNN&lt;/h1>
&lt;p>前面展示的RNN从时间\(t\)的维度上来说可以认为是多层的，但是RNN还可以从另一个维度来增加层数。如下图所示，将上一层（RNN layer 1）的输出作为下一层（RNN layer 2）的输入，不断堆叠下去，变成一个多层RNN。通常来说，深度越大，性能越好，如果梯度下降能训练好的话。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-08-01-cs224n-0129-vanishing-gradients-fancy-rnns/p42.png">&lt;/p>
&lt;p>RNN的层数通常不会很深，不会像CNN一样，达到上百层，RNN通常2层，最多也就8层。一方面是RNN的梯度消失问题比较严重，另一方面是RNN训练的时候是串行的，不易并行化，导致网络太深的话训练很花时间。&lt;/p></description></item><item><title>CS224N（1.24）Language Models and RNNs</title><link>https://bitjoy.net/posts/2019-07-31-cs224n-0124-language-models-and-rnns/</link><pubDate>Wed, 31 Jul 2019 21:29:18 +0800</pubDate><guid>https://bitjoy.net/posts/2019-07-31-cs224n-0124-language-models-and-rnns/</guid><description>&lt;p>今天要介绍一个新的NLP任务——语言模型（Language Modeling, LM），以及用来训练语言模型的一类新的神经网络——循环神经网络（Recurrent Neural Networks, RNNs）。&lt;/p>
&lt;p>语言模型就是预测一个句子中下一个词的概率分布。如下图所示，假设给定一个句子前缀是the students opened their，语言模型预测这个句子片段下一个词是books、laptops、exams、minds或者其他任意一个词的概率。形式化表示就是计算概率&lt;/p>
$$\begin{eqnarray}P(x^{(t+1)}|x^{(t)},…,x^{(1)})\tag{1}\end{eqnarray}$$&lt;p>\(x^{(t+1)}\)表示第\(t+1\)个位置（时刻）的词是\(x\)，\(x\)可以是词典\(V\)中的任意一个词。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-07-31-cs224n-0124-language-models-and-rnns/p3.png">&lt;/p>
&lt;p>既然语言模型在给定前t个词之后可以预测第t+1个词的概率，那么预测到第t+1个词之后，又可以递归的根据前t+1个词预测第t+2个词，如此不断的进行下去，就可以预测一整个句子的概率了。所以，也可以把语言模型看做一个可以计算一个句子出现的概率的系统，形式化表示就是如果一个句子是\(x^{(1)},…,x^{(T)}\) ，那么语言模型可以计算句子概率&lt;/p>
$$\begin{eqnarray}P(x^{(1)},…,x^{(T)})&amp; = &amp; P(x^{(1)})\times P(x^{(2)}|x^{(1)})\times…\times P(x^{(T)}|x^{(T-1)},…,x^{(1)}) \tag{2}\\&amp; = &amp; \prod_{t=1}^T P(x^{(t)}|x^{(t-1)},…,x^{(1)})\tag{3}\end{eqnarray}$$&lt;p>可以看到(3)式连乘的项就是(1)式，所以这两个定义的内涵是一样的。&lt;/p>
&lt;p>那么语言模型有什么用呢？它的用处可大了，比如现在的输入法会根据前一个输入的词预测下一个将要输入的词，此所谓智能输入法；比如在百度或谷歌搜索时，输入前几个关键词，搜索引擎会自动预测接下来可能的几个词；比如网上有很多智能AI会自动生成新闻、诗歌；还比如用在语音识别、机器翻译、问答系统等等。可以说语言模型是很多NLP任务的基础模块，具有非常重要的作用。&lt;/p>
&lt;p>在前-深度学习时代，人们使用n-gram方法来学习语言模型。对于一个句子，n-gram表示句子中连续的n个词，比如还是上图的例子，n-gram对于n=1,2,3,4的结果是：&lt;/p>
&lt;ul>
&lt;li>1-grams (unigrams): “the”, “students”, “opened”, “their”&lt;/li>
&lt;li>2-grams (bigrams): “the students”, “students opened”, “opened their”&lt;/li>
&lt;li>3-grams (trigrams): “the students opened”, “students opened their”&lt;/li>
&lt;li>4-grams: “the students opened their”&lt;/li>
&lt;/ul>
&lt;p>n-gram方法有一个前提假设，即假设每个词出现的概率只和前n-1个词有关，比如2-gram对于每个词出现的概率只和前面一个词有关，和更前面的词以及后面的词都没有关系，所以我们有如下图的assumption。又这是一个条件概率，展开之后得到如下除法的形式。n-gram的计算方法就是，统计语料库中出现\(x^{(t)},…,x^{(t-n+2)}\)的次数（分母），以及在这个基础上再接一个词\(x^{(t+1)}\)的次数\(x^{(t+1)},x^{(t)},…,x^{(t-n+2)}\)（分子），用后者除以前者来近视这个条件概率。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-07-31-cs224n-0124-language-models-and-rnns/p8.png">&lt;/p>
&lt;p>举个例子，假设完整的句子是as the proctor started the clock, the students opened their，需要预测下一个词的概率分布。对于4-gram方法，则只有students opened their对下一个词有影响，前面的词都没有影响。然后我们统计训练集语料库中发现，分母students opened their出现1000次，其后接books即students opened their books出现了400次，所以P(books|students opened their)=400/1000=0.4。类似的，可以算出下一个词为exams的概率是0.1。所以4-gram方法认为下一个词是books的概率更大。&lt;/p></description></item><item><title>CS224N（1.22）Dependency Parsing</title><link>https://bitjoy.net/posts/2019-07-29-cs224n-0122-dependency-parsing/</link><pubDate>Mon, 29 Jul 2019 23:42:19 +0800</pubDate><guid>https://bitjoy.net/posts/2019-07-29-cs224n-0122-dependency-parsing/</guid><description>&lt;p>Dependency Parsing是指对句子进行语法分析并画出句子成分的依赖关系，比如对于句子“She saw the video lecture”，首先可以分析出主语、谓语、宾语等句子成分；其次可以分析出依赖关系，比如saw依赖于She等。这就是句法分析。完成句法分析的算法被称为句法分析器parser，一个parser的性能可以用UAS和LAS来衡量，UAS就是parse出来的依赖关系对比正确依赖关系的正确率，LAS就是句子成分分析的正确率。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-07-29-cs224n-0122-dependency-parsing/p34.png">&lt;/p>
&lt;p>那么，为什么需要句法分析呢？因为：(i)了解句子结构能更好的理解句子的含义；(ii)人们在交流的时候，通过组合简单的句子成分来表达复杂的含义；(iii)我们需要知道句子中成分之间的依赖关系，以此来正确解读句子的含义。&lt;/p>
&lt;p>其实说到底就是为了更正确的理解句子含义。比如对于句子“San Jose cops kill man with knife”这个句子就有歧义，with knife到底是修饰man还是修饰kill，句子的意思大不相同。如果新闻小编学过NLP的话，大概不会写出这种歧义的句子。&lt;/p>
&lt;p>句法分析有很多算法，本世纪初当机器学习兴起后，Nivre等人提出了基于转移的句法分析器。该算法维护一个堆栈stack，用来存放已经分析过的词，以及一个buffer，用来存放还未分析的词。初始时，stack只有一个额外添加的[root]节点，buffer保存了完整的句子。然后，对于buffer中的每一个词，有三种操作，分别是：(i)shift，将buffer中的一个词压入stack；(ii)left arc，对于stack中的词，如果栈的第二个元素是栈顶元素的依赖项，则把第二个元素出栈；(iii)right arc，对于stack中的词，如果栈顶元素是栈的第二个元素的依赖项，则把栈顶元素出栈。如此循环，直到buffer为空以及stack中只剩下[root]。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-07-29-cs224n-0122-dependency-parsing/p31.png">&lt;/p>
&lt;p>对于上述算法，核心问题是，对于stack+buffer的不同状态，应该选择shift、left arc和right arc中的哪个操作呢？对于本世纪初的研究者来说，他们选择了机器学习方法。很简单嘛，每一个stack+buffer的状态相当于输入，3种操作相当于输出，把这个问题建模成分类问题不就行了吗。于是Nivre等人对每一个stack+buffer的状态，人工抽取出很多的特征，然后使用logistic或者svm进行分类。但是，当时的特征设计都是0/1状态的，特征向量很稀疏；特征又多，抽取特征很花时间。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-07-29-cs224n-0122-dependency-parsing/p33.png">&lt;/p>
&lt;p>当神经网络火了之后，人们自然想到了用神经网络替代logistic或svm，提出了新的句法分析器，该课程老师所在团队就是这么干的。他们对于每一个stack+buffer的状态，抽取出words、POS tags和arc labels三种不同类型的特征，都用词向量来表示。然后输入只有一个隐层的全连接网络，效果立马超过了之前所有人工设计的特征和方法。基于这个工作，后续又有很多改进版本。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-07-29-cs224n-0122-dependency-parsing/figure2.png">
&lt;a href="https://nlp.stanford.edu/pubs/emnlp2014-depparser.pdf">https://nlp.stanford.edu/pubs/emnlp2014-depparser.pdf&lt;/a>&lt;/p>
&lt;p>好了，上述就是这节课的主要内容，由于本人对句法分析不太感兴趣，没有亲自做作业，大家可以参考别人的&lt;a href="https://github.com/lrs1353281004/CS224n_winter2019_notes_and_assignments/tree/master/homework_my_solution/homework3">解答&lt;/a>。&lt;/p></description></item><item><title>CS224N（1.15 &amp; 1.17）Backpropagation</title><link>https://bitjoy.net/posts/2019-07-29-cs224n-0115-0117-backpropagation/</link><pubDate>Mon, 29 Jul 2019 23:11:36 +0800</pubDate><guid>https://bitjoy.net/posts/2019-07-29-cs224n-0115-0117-backpropagation/</guid><description>&lt;p>这篇博客把1.15和1.17两次课内容合并到一起，因为两次课的内容都是BP及公式推导，和之前的&lt;a href="https://bitjoy.net/posts/2018-12-14-neutral-networks-and-deep-learning-2-bp/">Neural Networks and Deep Learning（二）BP网络&lt;/a>内容基本相同，这里不再赘述。下面主要列一些需要注意的知识点。&lt;/p>
&lt;p>使用神经网络进行表示学习，不用输入的x直接预测输出，而是加一个中间层（图中橙色神经元），让中间层对输入层做一定的变换，然后中间层负责预测输出是什么。那么中间层能学到输入层的特征，相当于表示学习，自动学习特征。对于word2vec，中间层就是词向量。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-07-29-cs224n-0115-0117-backpropagation/p21.png">&lt;/p>
&lt;p>命名实体识别（Named Entity Recognition, NER），任务是把一个句子中的一些实体词识别出来，比如下图中识别出地点LOC、机构ORG和人名PER等。通常采用的方法是把需要判断类型的词及其周围的几个词的词向量拼接起来，输入到神经网络进行分类。但是由于一个句子中真正的实体词较少，而很多其他词Others会很多，导致样本不均衡，此时需要进行采样，具体方法可以搜索怎样处理NER样本不均衡的问题。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-07-29-cs224n-0115-0117-backpropagation/p25.png">
&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-07-29-cs224n-0115-0117-backpropagation/p30.png">
&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-07-29-cs224n-0115-0117-backpropagation/p34.png">&lt;/p>
&lt;p>我们知道词向量其实是NLP实际任务中的副产品，任何一个NLP任务都可以得到词向量。这就存在一个问题，当我们在实现一个具体的NLP任务时，是使用预训练的词向量，还是根据实际任务现场训练一个词向量呢？建议是，如果有可用的预训练词向量，则最好使用预训练词向量。因为预训练的词向量通常在很大规模的数据集上进行过训练，词向量的质量还不错，而某个具体的NLP任务的样本数可能不太多，导致训练得到的词向量还没人家预训练的好。所以，如果实际任务的数据量较小，则用预训练的词向量；否则，可以尝试一下根据实际任务fine tune词向量。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-07-29-cs224n-0115-0117-backpropagation/p11.png">&lt;/p>
&lt;p>这节课的核心就是不断使用链式法则对BP算法求导，然后反向传播。在反向传播的过程中，可以利用上游计算好的梯度，增量式的更新下游的梯度，如下图所示，就是公式[downstream gradient] = [upstream gradient] × [local gradient]。这个在之前&lt;a href="https://bitjoy.net/posts/2018-12-14-neutral-networks-and-deep-learning-2-bp/">介绍BP网络&lt;/a>的时候也提到过，其实就是那篇博客的(BP2)公式，误差对\(w\)的偏导可以通过误差对\(b\)的偏导乘以神经元输出得到。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-07-29-cs224n-0115-0117-backpropagation/p20.png">&lt;/p>
&lt;p>当有多个输入的时候，也是一样，只不过local gradients变为了多个分支。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-07-29-cs224n-0115-0117-backpropagation/p22.png">&lt;/p>
&lt;p>很有意思的是老师总结到：加法相当于把上游的梯度分发给下游；max相当于路由；乘法相当于开关。听老师讲下面的实例会有切身的体会。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-07-29-cs224n-0115-0117-backpropagation/p38.png">&lt;/p>
&lt;p>当然，现在的流行的神经网络框架都帮我们完成了自动求导，我们只需要把local gradient定义好，框架会自动帮我们进行反向传播。需要定义的就两点，一个是正向经过该神经元，output=forward(intput)；另一个是反向经过该神经元时，input_gradient=backward(output_gradient)。下面第二个图是一个很简单的例子，定义了forward和backward两个操作。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-07-29-cs224n-0115-0117-backpropagation/p43.png">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-07-29-cs224n-0115-0117-backpropagation/p46.png">&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;/tbody>
&lt;/table>
&lt;p>最后，简要介绍了6个注意事项：&lt;/p>
&lt;ul>
&lt;li>使用正则化避免过拟合&lt;/li>
&lt;li>使用python的向量和矩阵运行，而不是for循环，前者相比于后者有~10x加速&lt;/li>
&lt;li>目前流行的非线性激活函数是ReLU，Sigmoid和tanh比较少用了&lt;/li>
&lt;li>参数（权重）初始化，初始值最好是随机的很小的值，有一些专门的策略，如Xavier&lt;/li>
&lt;li>sgd优化器效果还不错，不过目前流行的优化器是Adam&lt;/li>
&lt;li>学习率最好是10的倍数，而且可以成10倍的放大或缩小；一些fancy的优化器会对设定的学习率进行逐步缩减（比如Adam），所以对于这些优化器，一开始的学习率可以设大一点，比如0.1&lt;/li>
&lt;/ul>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-07-29-cs224n-0115-0117-backpropagation/p2.png">&lt;/p>
&lt;p>最后是本周作业，包含两部分内容，一部分是手动求导，编辑公式太麻烦了，我就写在了纸上，大家可以参考这位仁兄的&lt;a href="https://github.com/lrs1353281004/CS224n_winter2019_notes_and_assignments/blob/master/homework_my_solution/homework2/written_part.pdf">解答&lt;/a>。另一部分是根据手动推导的梯度公式，补充word2vec算法中的求解梯度的算法以及sgd更新公式，如果是第一次接触这方面内容，可以参考这位仁兄的&lt;a href="https://github.com/lrs1353281004/CS224n_winter2019_notes_and_assignments/tree/master/homework_my_solution/homework2">实现&lt;/a>。&lt;/p>
&lt;p>但是，根据上一篇博客的介绍，word2vec除了可以根据作业中的极大似然的方法求解，还可以用3层全连接网络来实现，相比于极大似然更简洁也更容易理解，具体可以参考&lt;a href="https://towardsdatascience.com/implementing-word2vec-in-pytorch-skip-gram-model-e6bae040d2fb">这篇博客&lt;/a>以及&lt;a href="https://github.com/Andras7/word2vec-pytorch">这篇具体实现&lt;/a>。&lt;/p></description></item><item><title>《三国演义》人物命运走向</title><link>https://bitjoy.net/posts/2019-07-22-character-arcs-in-romance-of-the-three-kingdoms/</link><pubDate>Mon, 22 Jul 2019 11:29:06 +0800</pubDate><guid>https://bitjoy.net/posts/2019-07-22-character-arcs-in-romance-of-the-three-kingdoms/</guid><description>《三国演义》人物命运走向</description></item><item><title>《三国演义》每回内容梗概（91~120）</title><link>https://bitjoy.net/posts/2019-07-20-summary-of-the-romance-of-the-three-kingdoms-91-120/</link><pubDate>Sat, 20 Jul 2019 19:22:24 +0800</pubDate><guid>https://bitjoy.net/posts/2019-07-20-summary-of-the-romance-of-the-three-kingdoms-91-120/</guid><description>《三国演义》每回内容梗概（91~120）</description></item><item><title>《三国演义》每回内容梗概（61~90）</title><link>https://bitjoy.net/posts/2019-07-17-summary-of-the-romance-of-the-three-kingdoms-61-90/</link><pubDate>Wed, 17 Jul 2019 17:37:06 +0800</pubDate><guid>https://bitjoy.net/posts/2019-07-17-summary-of-the-romance-of-the-three-kingdoms-61-90/</guid><description>《三国演义》每回内容梗概（61~90）</description></item><item><title>《三国演义》每回内容梗概（31~60）</title><link>https://bitjoy.net/posts/2019-07-11-summary-of-the-romance-of-the-three-kingdoms-31-60/</link><pubDate>Thu, 11 Jul 2019 11:31:06 +0800</pubDate><guid>https://bitjoy.net/posts/2019-07-11-summary-of-the-romance-of-the-three-kingdoms-31-60/</guid><description>《三国演义》每回内容梗概（31~60）</description></item><item><title>《三国演义》每回内容梗概（1~30）</title><link>https://bitjoy.net/posts/2019-07-10-summary-of-the-romance-of-the-three-kingdoms-1-30/</link><pubDate>Wed, 10 Jul 2019 15:32:49 +0800</pubDate><guid>https://bitjoy.net/posts/2019-07-10-summary-of-the-romance-of-the-three-kingdoms-1-30/</guid><description>&lt;h1 id="第一回-宴桃园豪杰三结义-斩黄巾英雄首立功">第一回 宴桃园豪杰三结义 斩黄巾英雄首立功&lt;/h1>
&lt;p>黄巾起义，刘关张结为兄弟，大小顺序为刘关张，张飞是卖猪肉的，很有钱。刘备：双股剑；关羽：青龙偃月刀；张飞：丈八蛇矛。&lt;/p>
&lt;h1 id="第二回-张翼德怒鞭督邮-何国舅谋诛宦竖">第二回 张翼德怒鞭督邮 何国舅谋诛宦竖&lt;/h1>
&lt;p>刘关张镇压黄巾起义有功，但只得到一个很小的县令官，与民秋毫无犯，但被上面下来检查的督邮视察时，因没有贿赂督邮，被督邮穿小鞋，张飞怒不可遏，鞭打督邮。&lt;/p>
&lt;p>此时，朝廷内，十常侍专权，把持朝政。汉灵帝驾崩之后，何进拥立刘辩为皇，因为刘辩是灵帝和何进的姐姐的儿子。同时，把董后鸩杀。同时，何进为了除掉十常侍，引董卓入宫。&lt;/p>
&lt;h1 id="第三回-议温明董卓叱丁原-馈金珠李肃说吕布">第三回 议温明董卓叱丁原 馈金珠李肃说吕布&lt;/h1>
&lt;p>十常侍压力山大，为了保命，先下手为强，请何皇后把何进单独召进宫，进宫的路上，把何进杀了。宫内大乱，十常侍被何进部下杀掉。同时，董卓入宫，为彰显威严，欲废少帝辩，立刘协为新皇帝，在温明园讨论废立之事时，忠臣丁原挺身反对，董卓仗势欺人，斥责丁原。无奈丁原背后站着义子吕布，董卓奈何不了丁原。&lt;/p>
&lt;p>次日，丁原携吕布向董卓宣战，吕布骁勇善战，无人能破。此时，董卓部下李肃是吕布的老朋友，带着金银珠宝和董卓的赤兔马来劝说吕布，凭着李肃的三寸不烂之舌和吕布的头脑简单，吕布被说服，杀掉义父丁原，同时投奔董卓麾下。&lt;/p>
&lt;h1 id="第四回-废汉帝陈留践位-谋董贼孟德献刀">第四回 废汉帝陈留践位 谋董贼孟德献刀&lt;/h1>
&lt;p>董卓既得吕布，态度更加强硬，9月，废少帝，立陈留王刘协为新皇帝。把刘辩打入冷宫，某日，刘辩发牢骚写了首诗，董卓抓住机会，赐鸩酒把刘辩和何太后都杀了。&lt;/p>
&lt;p>曹操为了谋杀奸臣董卓，偷偷带着司徒王允的七宝刀，找了一个机会靠近董卓，本来要刺杀董卓，没成想被董卓从衣镜里发现了，曹操马上改口说有一口很好的宝刀，要献给董卓，然后开溜。董卓后来才get到曹操是要来刺杀自己的。&lt;/p>
&lt;h1 id="第五回-发矫诏诸镇应曹公-破关兵三英战吕布">第五回 发矫诏诸镇应曹公 破关兵三英战吕布&lt;/h1>
&lt;p>曹操逃出来之后，被董卓通缉，所以只能发布通告，尽书董卓恶行，招兵买马，准备讨伐董卓。各大诸侯太守都来响应，组成了一个十八路诸侯联盟，盟主是原朝廷重臣袁绍。但是袁绍统领诸侯，调度大军的能力不够，各诸侯也各怀鬼胎，没有凝聚力。&lt;/p>
&lt;p>前锋部队孙坚在进军汜水关时被华雄击败，华雄不可一世，在潘凤等大将接连被华雄斩杀之时，关羽主动请缨前去战华雄，在温酒未冷却的极短时间内斩杀华雄，关羽从此名震诸侯。此即温酒斩华（huà）雄的故事。&lt;/p>
&lt;p>董卓折了华雄，起兵二十万，兵分两路，其中一路由董卓亲自带队，和吕布等人，守住虎牢关，就是标题中的关兵中的关。在虎牢关处，吕布骑着赤兔马，不可一世，盟军无人能敌。最后，刘关张三人亲自出马，大战吕布，吕布败走。&lt;/p>
&lt;h1 id="第六回-焚金阙董卓行凶-匿玉玺孙坚背约">第六回 焚金阙董卓行凶 匿玉玺孙坚背约&lt;/h1>
&lt;p>吕布新败，董卓引兵回洛阳，迁都长安，同时把洛阳的宫殿烧毁。孙坚飞奔洛阳，救火的同时，在井中发现了传国玉玺，并私藏起来。后来被盟主袁绍发现，孙坚感到被羞耻了，拔寨离洛阳而去。袁绍大怒，写信给荆州刘表，因为孙坚回老家江东——扬州（？）要经过刘表家，所以袁绍写信给刘表，叫他半路截住孙坚。果然在半路上，孙坚和刘表来了一场恶战，亏孙坚部下三员大将程普、黄盖、韩当死救得脱。自此，孙坚和刘表结怨。&lt;/p>
&lt;h1 id="第七回-袁绍磐河战公孙-孙坚跨江击刘表">第七回 袁绍磐河战公孙 孙坚跨江击刘表&lt;/h1>
&lt;p>袁绍屯兵河内，缺少粮草，向冀州太守韩馥借粮，谋士逢纪说大丈夫落到向别人借粮，可耻啊，冀州乃钱粮广盛之地，不如取而代之。袁绍于是和公孙瓒密谋，让公孙瓒出兵冀州，则韩馥必向袁绍求救，袁绍乘虚而入，占领冀州，然后和公孙瓒平分冀州。&lt;/p>
&lt;p>公孙瓒照做，但是当袁绍占领冀州之后，并没有和公孙瓒平分冀州，而是独占了。公孙瓒派弟弟公孙越去袁绍处，想要分点油水，没想到反被袁绍杀害。于是，公孙瓒大怒，举兵攻打袁绍。两军交战于磐河。此战互有胜负，公孙瓒手下赵云出场，和同在公孙瓒手下的刘关张相见，一见如故，分别时泪如雨下。&lt;/p>
&lt;p>袁绍的弟弟袁术，在南阳，听说哥哥新得冀州，想要哥哥赏赐点马匹，袁绍不给，自此兄弟不睦。袁术又向荆州刘表借粮，刘表也不给。袁术怒了，写信给孙坚，说昔日孙坚私藏玉玺回老家的路上，被刘表伏击，今日，我袁术愿与你结盟，攻打刘表。于是孙坚果然起兵，跨过汉水（长江），攻打刘表。没成想中了刘表部下蒯良的计谋，被杀了，可惜啊。孙坚部下黄盖生擒刘表部下黄祖，于是和刘表交换回孙坚尸体。孙坚大儿子孙策，字伯符；二儿子孙权，字仲谋。&lt;/p>
&lt;h1 id="第八回-王司徒巧使连环计-董太师大闹凤仪亭">第八回 王司徒巧使连环计 董太师大闹凤仪亭&lt;/h1>
&lt;p>董卓在长安听说孙坚死了，更加骄奢淫逸。此时，司徒王允为了江山社稷愁死了，王允府上的歌伎貂蝉，特别漂亮，允以亲女待之。貂蝉想帮王允分担忧愁，于是王允想出了一个连环计。因董卓和其干儿子吕布都是有勇无谋，贪财好色之徒，王允先把貂蝉许配给吕布，然后又悄悄把貂蝉送给董卓。貂蝉从中挑不离间，致使父子二人翻脸。&lt;/p>
&lt;p>因为貂蝉已经被送到董卓府上，有一天，吕布趁着董卓和汉献帝聊天，偷偷来到董卓府上，在府上的凤仪亭看到了貂蝉，和貂蝉搂搂抱抱，被董卓赶回来发现了，董卓大闹凤仪亭，追着吕布打。自此父子二人结怨。&lt;/p>
&lt;h1 id="第九回-除暴徒吕布助司徒-犯长安李傕听贾诩">第九回 除暴徒吕布助司徒 犯长安李傕听贾诩&lt;/h1>
&lt;p>经过了上面的事情，貂蝉劝董卓搬家，于是董卓和貂蝉搬到郿坞去了。司徒王允和吕布想了一个计策，说汉献帝病刚好，想召集文武百官吃个饭，于是派昔日董卓心腹李肃（就是第三回的李肃，因为董卓没有给李肃升官，李肃对董卓也有怨念），去郿坞宣旨。董卓傻乎乎兴高采烈来到宫内，被早就在此埋伏的王允、吕布、李肃等人杀死，同时去郿坞把董卓全家灭口，包括杀了董卓谋士李儒。吕布得到貂蝉。&lt;/p>
&lt;p>董卓手下四员大将李傕、郭汜、张济、樊稠，听说董卓被杀，打算吃个散伙饭，各自逃命。谋士贾诩说，我们都被通缉，既然自首也要死，不如来个你死我活。四人听了之后认为有道理，于是在西凉起兵，杀奔长安。而且他们制定的军事政策是，其中两个人在山外前后诱杀吕布，但又不恋战，另两个人偷偷起兵直接攻打长安城，让吕布和长安城首尾不能接应。此计果然奏效，董卓余党在长安城内为内应，打开城门，四员大将进入城中，烧杀抢掠，把王允也杀了。吕布弃了家小，投袁术去了。&lt;/p>
&lt;h1 id="第十回-勤王室马腾举义-报父仇曹操兴师">第十回 勤王室马腾举义 报父仇曹操兴师&lt;/h1>
&lt;p>李傕、郭汜、张济、樊稠占领宫内之后，骄奢蛮横，残虐百姓。西凉太守马腾和并州刺史韩遂，密谋贼党，但是都失败了。此时，马腾之子马超出场。樊稠因放过同乡人韩遂，被李傕郭汜杀掉。&lt;/p>
&lt;p>因朝廷昏庸无能，青州黄巾起义又起，太傅朱儁推荐派曹操去剿灭黄巾起义，李傕郭汜同意。东郡太守曹操和济北相鲍信一同破贼。&lt;/p>
&lt;p>曹操在兖州，招贤纳士，群贤毕至。文官：荀彧、荀攸，叔侄二人；程昱；郭嘉；刘晔；满宠；吕虔；毛玠。武官：于禁；典韦。自是曹操部下文有谋臣，武有猛将，威镇山东。&lt;/p>
&lt;p>曹操一高兴，打算把琅琊郡的老父亲曹嵩接过来享天伦之乐，于是，曹嵩和弟弟曹德并一家老小准备赶往兖州。途径徐州，徐州太守陶谦，想讨好曹操，大设宴席款待曹嵩等人，并派部下张闿护送曹嵩。没曾想，张闿原是黄巾余党，在陶谦处没有得到重用，今贼心不改，在路上把曹嵩一家老小全杀了。曹操听到消息，大怒，亲自起兵杀奔徐州。&lt;/p>
&lt;h1 id="第十一回-刘皇叔北海救孔融-吕温侯濮阳破曹操">第十一回 刘皇叔北海救孔融 吕温侯濮阳破曹操&lt;/h1>
&lt;p>于是，徐州陶谦，向北海孔融求救，正商议间，黄巾余党管亥来北海攻打孔融。幸好孔融平时人品好，善待城外的一个老奶奶，老奶奶听说孔融有难，叫回来省亲的儿子太史慈去救孔融。太史慈虽然很厉害，但毕竟只有一个人，孔融就叫太史慈杀出重围，请刘备来救援。刘备于是向公孙瓒借了赵云，带着关张来救孔融。&lt;/p>
&lt;p>刘备来了之后，先给曹操写了封信，好言相劝，劝和。正好，这个时候，从宫中逃出来的吕布，攻陷了曹操的老巢兖州和濮阳，于是曹操送刘备一个人情，撤兵回老巢了。&lt;/p>
&lt;p>曹操经过商议之后，准备亲自领兵，去夺回濮阳，在濮阳和吕布进行了恶战，战败，差点被吕布围剿。&lt;/p>
&lt;h1 id="第十二回-陶恭祖三让徐州-曹孟德大战吕布">第十二回 陶恭祖三让徐州 曹孟德大战吕布&lt;/h1>
&lt;p>吕布的谋士陈宫，诡计多端，出了一个点子，诱使曹操进濮阳城，待曹操进城之后，关门放火，差点把曹操灭了，众将死救得脱。&lt;/p>
&lt;p>陶谦在徐州，已经63岁了，儿子又无才，于是，想把徐州送给刘备接管，刘备死活不肯要。就这样来来回回三次，陶谦都要死了，刘备才肯接管徐州。&lt;/p>
&lt;p>曹操自从被吕布打了个败仗，回老家待着。谋士荀彧说，现在收成不好，可以去陈地、汝南、颍川抢占地盘，这些地方都是黄巾余党，乌合之众，轻易可破，又可得粮草。曹操听之，果然占领了这些地方，顺带还收了一员武将许褚。&lt;/p>
&lt;p>某天听说兖州吕布手下大将薛兰、李封都出去劫掠了，可以乘虚而入，夺回兖州。曹操听之，果然夺回兖州，同时六员大将齐战吕布，吕布败走。&lt;/p>
&lt;h1 id="第十三回-李傕郭汜大交兵-杨奉董承双救驾">第十三回 李傕郭汜大交兵 杨奉董承双救驾&lt;/h1>
&lt;p>吕布败走之后，来徐州投奔刘备，屯兵小沛。&lt;/p>
&lt;p>却说李傕郭汜在宫廷横行无忌，太尉杨彪和大司农朱儁密谋诛杀李傕郭汜。杨彪献一反间计：郭汜的妻子妒忌心很强，可派人秘密告诉郭汜妻子，郭汜在和李傕夫人偷情。反间计成功，李傕郭汜反目成仇，李傕劫了天子，郭汜劫了文武百官，每日厮杀。&lt;/p></description></item><item><title>CS224N（1.10）Word Vectors 2 and Word Senses</title><link>https://bitjoy.net/posts/2019-06-23-cs224n-1-10-word-vectors-2-and-word-senses/</link><pubDate>Sun, 23 Jun 2019 13:07:09 +0800</pubDate><guid>https://bitjoy.net/posts/2019-06-23-cs224n-1-10-word-vectors-2-and-word-senses/</guid><description>&lt;p>这一讲是上一讲的补充，内容比较零碎，包括：Word2vec回顾、优化、基于统计的词向量、GloVe、词向量评价、词义等，前两个内容没必要再介绍了，下面逐一介绍后四个内容。&lt;/p>
&lt;h1 id="基于统计的词向量">基于统计的词向量&lt;/h1>
&lt;p>词向量的目的就是希望通过低维稠密向量来表示词的含义，而词的分布式语义表示方法认为词的含义由其上下文语境决定。Word2vec把中心词和临近词抽取出来，通过预测的方式训练得到词向量。在Word2vec之前，传统的方式通过统计词的共现性来得到词向量，即一个词的词向量表示为其临近词出现的频率，如果两个词的含义很相近，则其临近词分布会比较像，得到的词向量也比较像。其具体计算过程在&lt;a href="https://github.com/01joy/stanford-cs224n-winter-2019/blob/master/1.8/assignment/a1/exploring_word_vectors.ipynb">第一次作业&lt;/a>中有详细的描述，这里再简单回顾如下。&lt;/p>
&lt;p>假设一个语料库中包含三个句子，共有8个特异词（包括点号），对于每个词，统计其前后一个词的词频（临近窗口为1），由此能得到一个8×8的对称矩阵，其每一行（或每一列）表示该词的词向量。比如对于like这个词，在三个句子中，其左右共出现2次I，1次deep和1次NLP，所以like对应的词向量中，I、deep和NLP维的值分别为2,1,1。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-06-23-cs224n-1-10-word-vectors-2-and-word-senses/co-occurrence_matrix.png">&lt;/p>
&lt;p>这种基于词频统计的方法很简单，但是它有如下不足：&lt;/p>
&lt;ul>
&lt;li>特异的词很多，所以矩阵很大，维度很高，需要的存储空间也很大&lt;/li>
&lt;li>特异词的数目是在不断增长的，则词向量的维度也在不断增长&lt;/li>
&lt;li>矩阵很稀疏，即词向量很稀疏，会导致很多NLP任务会遇到稀疏计算的问题&lt;/li>
&lt;/ul>
&lt;p>所以需要把上述计数矩阵转换为一个低维稠密的矩阵，方法就是SVD分解。上述矩阵原本是一个\(n\times n\)的矩阵，SVD分解后能得到一个\(n\times k\)的矩阵，其中\(k\ll n\)。即原本的词向量是一个\(n\)维的高维稀疏向量，变成了\(k\)维的低维稠密向量，而且还不会损失太多的信息。&lt;/p>
&lt;p>&lt;a href="https://pdfs.semanticscholar.org/73e6/351a8fb61afc810a8bb3feaa44c41e5c5d7b.pdf">2005年的一篇文章&lt;/a>对上述简单的计数方法进行了改进，包括去掉停用词、使用倾斜窗口、使用皮尔逊相关系数等，提出了COALS模型，该模型得到的词向量效果也不错，也具有句法特征和语义特征。使用统计的方法和使用预测的方法训练词向量，两者的对比如下。基于统计计数的方法的主要特点是：训练速度快，能充分利用统计信息，主要用来捕获词的相似性。基于预测的方法的主要特点是：对语料库的大小可扩展，没有充分利用统计信息，能捕获除了词的相似性之外的其他复杂特征。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-06-23-cs224n-1-10-word-vectors-2-and-word-senses/count_based_vs_direct_prediction.png">&lt;/p>
&lt;h1 id="glove">GloVe&lt;/h1>
&lt;p>GloVe的全称是&lt;a href="https://nlp.stanford.edu/projects/glove/">GloVe: Global Vectors for Word Representation&lt;/a>，正是这门课的老师Christopher D. Manning的研究成果。有关GloVe论文的详细解读，可以看&lt;a href="https://blog.csdn.net/coderTC/article/details/73864097">这篇博客&lt;/a>。GloVe希望能综合上述基于统计和基于预测的两种方法的优点。&lt;/p>
&lt;p>GloVe的基本思想依然是基于统计的方法，当统计得到共现矩阵X之后，可以计算得到词\(k\)是词\(i\)的临近词的概率：&lt;/p>
$$P_{i,k}=\dfrac{X_{i,k}}{X_{i}}$$&lt;p>再定义两个\(P\)的比值：&lt;/p>
$$ratio_{i,j,k}=\dfrac{P_{i,k}}{P_{j,k}}$$&lt;p>如果词\(k\)在两个词\(i\)和\(j\)的临近概率相同，无论是同样大（water）还是同样小（fashion），经过比值计算后，\(ratio_{i,j,k}\)都约等于1了，说明在维度water和fashion上，无法区分ice和steam。而在维度solid和gash上，由于概率\(P\)的差异，导致\(ratio_{i,j,k}\)很大或者很小，这是有意义的，说明在solid和gas维度上，可以区分ice和steam的语义。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-06-23-cs224n-1-10-word-vectors-2-and-word-senses/GloVe.png">&lt;/p>
&lt;p>基于这样的观察，GloVe首先统计语料库中三元组\(i,j,k\)的\(ratio_{i,j,k}\)，然后初始化词向量\(v\)，构造函数\(g\)，使得利用词向量计算得到的\(g(v_{i},v_{j},v_{k})\)和真实\(ratio_{i,j,k}\)尽量接近。&lt;/p>
$$\dfrac{P_{i,k}}{P_{j,k}}=ratio_{i,j,k}=g(v_{i},v_{j},v_{k})$$$$J=\sum_{i,j,k}^N(\dfrac{P_{i,k}}{P_{j,k}}-g(v_{i},v_{j},v_{k}))^2$$&lt;p>但是上述方法的复杂度太高了，对于一个\(N\times N\)的共现矩阵，上述算法需要计算所有的三元组，复杂度是\(N\times N\times N\)。GloVe文章通过各种转换技巧，把复杂度降为了一个\(N\times N\)的问题，具体过程可以看上面提到的博客或者paper原文。&lt;/p>
&lt;p>总的来说，基于共现矩阵这种统计的方法，能捕获整个语料库全局的信息；而类似word2vec的预测的方法，则主要捕获局部的滑动窗口内的共现信息，两种方法训练得到的词向量效果都不错。&lt;/p>
&lt;h1 id="词向量评价">词向量评价&lt;/h1>
&lt;p>评价词向量的好坏主要有两个尺度，一是内部任务评价（intrinsic），一是外部任务评价（extrinsic），两者的主要特点如下。大概意思是内部任务是根据词本身具有的性质，比如近义、反义等，评价词向量本身的性能。外部任务是指词向量对NLP下游任务的性能的影响，比如同样是一个文本分类问题，换不同的词向量，对文本分类任务的性能的影响能反映出词向量的性能。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-06-23-cs224n-1-10-word-vectors-2-and-word-senses/word_vector_evaluation.png">&lt;/p>
&lt;p>常见的内部任务评价是词的类比推理（Word Vector Analogies），就是类似man:woman :: king:queen这种，word2vec还专门整理出了这样的测试数据：&lt;a href="https://code.google.com/archive/p/word2vec/source/default/source">word2vec/trunk/questions-words.txt&lt;/a>。另一个内部任务评价是使用训练得到的词向量计算的词相似度，和人类认为的相似度做比较，有团队专门整理出了人类对两个词的相似度打分，具体可以看&lt;a href="http://www.cs.technion.ac.il/~gabr/resources/data/wordsim353/">这里&lt;/a>。&lt;/p>
&lt;p>对词向量的外部任务评价就很多了，几乎所有的NLP任务都可以用来作为词向量的外部任务评价，比如命名实体识别、文本分类等等，这里不再展开。&lt;/p>
&lt;h1 id="词义">词义&lt;/h1>
&lt;p>一个词往往具有多个含义（word senses），特别是对于常用的词或者存在很久的词。那么一个词向量能同时包含这个词的多个语义吗？有文章把一个词的多个语义通过线性加权的方式叠加到一个词向量中，然后还能通过稀疏编码的方式求解出每个语义的词向量，具体可以看下图中的参考文献。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-06-23-cs224n-1-10-word-vectors-2-and-word-senses/word_senses.png">&lt;/p></description></item><item><title>CS224N（1.8）Introduction and Word Vectors</title><link>https://bitjoy.net/posts/2019-06-22-cs224n-1-8-introduction-and-word-vectors/</link><pubDate>Sat, 22 Jun 2019 11:11:16 +0800</pubDate><guid>https://bitjoy.net/posts/2019-06-22-cs224n-1-8-introduction-and-word-vectors/</guid><description>&lt;p>今天开始介绍大名鼎鼎的NLP网课Stanford-CS224N，我学的是Winter 2019的版本，课程网站：&lt;a href="https://web.stanford.edu/class/archive/cs/cs224n/cs224n.1194/">https://web.stanford.edu/class/archive/cs/cs224n/cs224n.1194/&lt;/a>。第一讲内容为课程简介和词向量。&lt;/p>
&lt;p>词向量即用来表示这个词的含义的向量。早期的NLP常用one-hot编码来表示词向量，假如词典中共有10000个词，则这个one-hot向量长度就是10000，该词在词典中所处位置对应的值为1，其他值为0。&lt;/p>
&lt;p>one-hot表示方法虽然简单，但其有诸多缺点：1. 词典中的词是不断增多的，比如英语，通过对原有的词增加前缀和后缀，可以变换出很多不同的词，one-hot编码会导致向量维度非常大，且每个向量是稀疏的；2. 不同词的one-hot编码向量是垂直的，在向量空间中无法表示近似关系，即使两个含义相近的词，它们的词向量点积也为0。&lt;/p>
&lt;p>既然one-hot编码有这么多缺点，那我们就换一种编码，one-hot是高维稀疏向量，那新的编码就改用低维稠密向量，这样就解决了上述问题，那么怎样得到一个词的低维稠密的词向量呢？这就是word2vec算法。&lt;/p>
&lt;p>word2vec采用了分布式语义的方法来表示一个词的含义。本质上，一个词的含义就是这个词所处的上下文语境。回想一下我们高中做英语完形填空时，一篇短文，挖了好多空，让我们根据空缺词的上下文语境选择合适的词。也就是说上下文语境已经能够确定这个词的含义了，如果选词正确，也就意味着我们理解了这个空缺词的含义。&lt;/p>
&lt;p>word2vec算法发表于2013年，包括两种训练算法Skip-grams (SG)和Continuous Bag of Words (CBOW)，这两种方法很类似，其中CBOW和上述介绍到的英语完形填空几乎是一样的，由上下文词预测中心词；而SG则和CBOW正好相反，由中心词预测上下文词，本文主要介绍SG算法。&lt;/p>
&lt;p>给定一个语料库，这个语料库包含了很多文章，每篇文章又包含很多句子，每个句子又包含很多词语。所以一个语料库是一个天然的标注集，因为对于每一个选定的中心词，我们都知道其临近的词是什么。这样一个（中心词，临近词）对就构成了一个标注集。SG算法的中心思想就是对于每个选定的中心词，尽量准确的预测其周围可能出现的词的概率分布。具体来说，SG算法首先随机初始化每个词的词向量；然后预测不同临近词出现的概率，最后最大化实际临近词出现的概率。&lt;/p>
&lt;p>形式化来说，就是用极大似然估计的方法，求解每个词的词向量。其目标函数如下，其中\(\theta\)是待求解的参数；\(t\)为选定的中心词位置；对于每个\(t\)（外层\(\prod\)），估计其邻域\(\pm m\)个词出现的概率（内层\(\prod\)）。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-06-22-cs224n-1-8-introduction-and-word-vectors/word2vec_objective_function.png">&lt;/p>
&lt;p>求解极大似然估计的方法比较成熟，一般先把极大似然转换为最小化-log似然，然后用梯度下降求解。所以核心问题就变成了如何求解\(P(w_{t+j}|w_t;\theta)\)。&lt;/p>
&lt;p>对于每个词\(w\)，定义其两个词向量：\(v_w\)表示当\(w\)为中心词时的词向量，\(u_w\)表示当\(w\)为其他词的临近词时的词向量。则对于一个中心词\(c\)和其临近词\(o\)，有：&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-06-22-cs224n-1-8-introduction-and-word-vectors/word2vec_prediction_function.png">&lt;/p>
&lt;p>上式本质是一个softmax函数，因为给定\(c\)，\(o\)相当于是标注结果，所以把它们的点积作为分子，希望分子越大越好；而分母则是所有可能的\(u_w\)和\(v_c\)的点积之和，起到归一化作用。&lt;/p>
&lt;p>题外话：讲这张幻灯片时，还提到softmax的一个形象解释。softmax包括max和soft两层含义。假设对于一个数组[1,2,3,4]，直接max也就是hard max的结果是保留最大值，其他全变为0，即[0,0,0,4]。但是softmax对他们求\(\frac{exp(x_i)}{\sum_{j=1}^nexp(x_j)}\)，变成了[0.03, 0.09, 0.24, 0.64]，最大的还是第4个数，但第四个数的优势被放大了，原来4只是1的4倍，现在0.64是0.03的21倍。所以softmax不但保留了max的属性，还变得更soft了，原来小的数不会被抹为0，只不过拉大了差异。&lt;/p>
&lt;p>使用梯度下降还需要求解\(P\)对参数\(\theta\)的梯度，在这里\(\theta\)代表了所有词的中心词向量和临近词向量。对于上式，\(u_o\)、\(v_c\)等就是\(\theta\)的一部分。不断利用求导的链式法则，容易得到：&lt;/p>
$$\begin{eqnarray}\frac{\partial P(o|c)}{\partial v_c}=u_o-\sum_{w\in V}P(w|c)u_w.\tag{1}\end{eqnarray}$$&lt;p>最后算出来的梯度很有意思，\(u_o\)表示观察到的上下文词向量（o表示observed），减号后面的是这个位置的期望的词向量，期望=概率*值。差值就是真实观察词向量减去期望词向量，这就是梯度。当它们的差值很小时，说明给定\(c\)能很好的预测其临近词的概率分布。&lt;/p>
&lt;p>OK，当以上内容都准备妥当之后，我们就可以开始求解词向量了。首先随机初始化每个词\(w\)的中心词向量\(v_w\)和临近词向量\(u_w\)；然后求解-log损失函数\(J(\theta)\)；最后根据梯度下降更新所有参数\(\theta\)。&lt;/p>
&lt;hr>
&lt;p>上述word2vec算法简单，直观，但写代码实现比较复杂。在实际应用场景中，人们往往使用神经网络的方法来求解词向量，具体教程请看这里： &lt;a href="http://mccormickml.com/2016/04/19/word2vec-tutorial-the-skip-gram-model/">http://mccormickml.com/2016/04/19/word2vec-tutorial-the-skip-gram-model/&lt;/a> 。&lt;/p>
&lt;p>我们把训练词向量的问题转换为端到端的文本分类问题。如下图所示，对于语料库中的一个句子，假设临近窗口为前后两个词，则可以抽取出如下图右边所示的训练样本，每个训练样本是一个（中心词，临近词）的pair。比如对于（the, quick）训练样本，我们希望神经网络在输入the这个中心词时，能以较高的概率预测出quick这个词。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-06-22-cs224n-1-8-introduction-and-word-vectors/training_data.png">&lt;/p>
&lt;p>网络的结构如下图所示，也非常简单，是仅含一个隐藏层的全连接网络。比如上图的一组训练数据是（the, quick），表示输入是the的one-hot编码，输出是quick的one-hot编码。假设词典里有10,000个不同的词，则one-hot编码长度为10,000。有一个隐藏层的全连接网络，对应权重构成两个权重矩阵，和输入层连接的矩阵为\(V\)，其每一行表示词作为中心词时的词向量。输入行向量乘以\(V\)正好得到输入词的词向量，这对应课上讲的作为中心词的词向量\(v_c\)。&lt;/p>
&lt;p>隐层和输出层连接的权重矩阵为\(U\)，其每一列表示输出层的词的临近词词向量。隐层的行向量\(v_c\)乘以矩阵\(U\)，得到词\(c\)的临近词的概率分布，再经过softmax激活，进行归一化。其实反过来看，从输出往隐层看，相当于输出层的行向量乘以\(U\)的转置，得到隐层词向量。这其实就是另一种训练词向量的方法CBOW，即英语完形填空，用临近词来预测中心词。&lt;/p>
&lt;p>对于下图的神经网络，输出用softmax激活，损失函数使用-log损失，训练网络时使用梯度下降，其效果正好是课上讲的使用极大似然估计的方法！&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-06-22-cs224n-1-8-introduction-and-word-vectors/skip_gram_net_arch2.jpg">&lt;/p>
&lt;p>另一方面，上图的这种结构是skip-gram模型，如果把对应的箭头反一下，输入变输出，输出变输入，其实就变成了CBOW模型了。&lt;/p>
&lt;hr>
&lt;p>上述全连接网络虽然能很方便的计算词向量，但存在两个问题：1. 网络过于庞大，参数量太多；2. 训练样本太多，每个样本都更新所有参数，训练速度慢。针对这两个问题，作者分别提出了 subsampling 和 negative sampling 的技巧，具体请看教程：
&lt;a href="http://mccormickml.com/2017/01/11/word2vec-tutorial-part-2-negative-sampling/">http://mccormickml.com/2017/01/11/word2vec-tutorial-part-2-negative-sampling/&lt;/a> 。&lt;/p>
&lt;p>第一个问题，网络参数量太多。假设有1w个特异的词，词向量长度为300，整个网络就有两个300w的矩阵（上图的V和U）参数需要优化。另一方面，训练语料库往往是很大的，随随便便就是成百上千万的文章，由此拆分得到的训练词组对就更大了，很容易到上亿的级别。几百万的参数，几亿的训练数据， 导致网络太过庞大，训练不动。&lt;/p>
&lt;p>subsampling技巧是指，每个词有一个保留概率p，以这个概率p保留其在训练数据中，以1-p删掉这个词。比如上面的例子，删掉了fox，则fox对应的4个训练数据也一并删掉了，所以能减少较多的训练数据。对于词\(w_i\)，其概率\(P(w_i)\)公式如下，其中\(z(w_i)\)是词\(w\)的词频。概率p和这个词在语料库中的词频有关，词频越大，保留概率越低，即被删掉的概率越大，所以subsampling之后应该能较大的减少训练数据。&lt;/p>
$$\begin{eqnarray}P(w_i) = (\sqrt{\frac{z(w_i)}{0.001}} + 1) \cdot \frac{0.001}{z(w_i)} .\tag{2}\end{eqnarray}$$&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/mccormickml.com/assets/word2vec/subsample_func_plot.png">&lt;/p>
&lt;p>第二个问题，原来的网络在训练时，对于每个输入中心词，都会对两个很大的参数矩阵V和U（和上面假设一样，300w）进行轻微的更新，更新操作太多了。&lt;/p>
&lt;p>negative sampling技巧，只更新一小部分参数。比如对于(“fox”, “quick”)，期望的输出是quick的one-hot，即只有quick对应位为1，其他为0。但网络的softmax输出肯定不可能是完完全全的quick为1，其他为0；有可能是quick为0.8，其他位有些0.001，0.03之类的非0值，这就导致输出层的所有神经元都有误差。按照传统的方法，输出层所有神经元对应的U矩阵的权重都要更新。negative sampling技巧是，只更新和quick连接的U权重以及随机选5个输出神经元的连接权重进行更新，这样一下把需要更新的U权重个数从300w降到了6*300=1800，只需要更新0.06%的参数，大大减小了参数更新量！&lt;/p>
&lt;p>5个随机选中的神经元（输出位置，即对应1w维的某个词）被称为negative sample，被选中的概率和词频成正比，词频越大的词被选中的概率越大，和上面subsampling类似。概率公式如下，其中\(f(w_i)\)应该和(2)中的\(z(w_i)\)一样，都表示词频。&lt;/p>
$$\begin{eqnarray}P(w_i) = \frac{ {f(w_i)}^{3/4} }{\sum_{j=0}^{n}\left( {f(w_j)}^{3/4} \right) }.\tag{3}\end{eqnarray}$$&lt;hr>
&lt;p>作业请见GitHub：
&lt;a href="https://github.com/01joy/stanford-cs224n-winter-2019/blob/master/1.8/assignment/a1/exploring_word_vectors.ipynb">https://github.com/01joy/stanford-cs224n-winter-2019/blob/master/1.8/assignment/a1/exploring_word_vectors.ipynb&lt;/a>&lt;/p></description></item><item><title>“蛋白质结构预测”问题描述</title><link>https://bitjoy.net/posts/2019-05-25-introduction-to-protein-structure-prediction/</link><pubDate>Sat, 25 May 2019 22:32:54 +0800</pubDate><guid>https://bitjoy.net/posts/2019-05-25-introduction-to-protein-structure-prediction/</guid><description>&lt;p>相信很多学CS的同学之前都没听说过“蛋白质结构预测”这个问题，直到2018年12月初，一则劲爆消息瞬间引爆了CSer的朋友圈，那就是Google Deepmind团队开发的AlphaFold一举拿下当年的CASP比赛冠军，而且远远甩开了第二名。我当时就转载过类似的公众号文章，大家可以阅读并想象当时朋友圈的欢呼声：&lt;a href="https://mp.weixin.qq.com/s?__biz=MzUyOTcxNDA2MA==&amp;amp;mid=2247484072&amp;amp;idx=1&amp;amp;sn=4ced43b28439e193e2a88f402c81cb2f&amp;amp;chksm=fa5d9c4bcd2a155df9c5d7450b7d6f0424af39fcfc8634c2345ff0b01300192b4ea6c0565341&amp;amp;mpshare=1&amp;amp;scene=1&amp;amp;srcid=1203qx87iYulfkOp7jvZx5uJ&amp;amp;key=e93dd65619c9c8dbcde90e46cec14857b94dda95e137fa444083b63060f0711ed2b7bd7e3b247f8c8ab36b68388bad7974855e5725ecc112fdd999179387a498599090c13da887f08290f51de8576a8d&amp;amp;ascene=1&amp;amp;uin=MTgzOTAyODQw&amp;amp;devicetype=Windows&amp;#43;10&amp;amp;version=62060739&amp;amp;lang=zh_CN&amp;amp;pass_ticket=HhfIKFFBQhij05w1DGJ%2BS23fCqJLztSSaonfMvhvStM%3D">阿尔法狗再下一城 | 蛋白结构预测AlphaFold大胜传统人类模型&lt;/a>。&lt;/p>
&lt;p>当时，很多同学也转载过类似的文章，但其实很少有人真正明白“蛋白质结构预测”这个问题是什么，它的难度有多大，CASP是个什么比赛，以及AlphaFold的内部原理是什么。当然，对于这一连串的问题，我当时也是懵逼的。不过自己好歹也是个跟蛋白质有关的PhD，如此热点事件，自然是要关注的。不过之后一直没时间，直到今年相关顶级文章再次爆出，我就借着准备文献讲评的机会了解了相关的知识，在这里跟大家分享一下。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://upload.wikimedia.org/wikipedia/commons/a/a9/Protein_folding.png">
&lt;a href="https://upload.wikimedia.org/wikipedia/commons/a/a9/Protein_folding.png">https://upload.wikimedia.org/wikipedia/commons/a/a9/Protein_folding.png&lt;/a>&lt;/p>
&lt;hr>
&lt;p>蛋白质结构分为四级，分别是一级结构、二级结构、三级结构和四级结构，下面分别描述。&lt;/p>
&lt;h1 id="一级结构">一级结构&lt;/h1>
&lt;p>蛋白质的一级结构可以理解为一条线性的字符串，比如MSFIKTFSGKHFYYDKINKDDIVINDIAVSLSNICR。其基本组成单元是一个个的氨基酸，即一个个的字母。氨基酸有单字母表示和三字母表示，为了简洁，本文使用单字母表示，下图的例子是三字母表示。常见的氨基酸只有20种，所以一级结构的字符串通常只包含20种字母，不包含的6种字母是BJOUXZ。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-05-25-introduction-to-protein-structure-prediction/PrimaryProteinStructure.png">
&lt;a href="http://oregonstate.edu/instruct/bb450/450material/schedule450s17e.html">http://oregonstate.edu/instruct/bb450/450material/schedule450s17e.html&lt;/a> 本文大部分蛋白质基础知识都来源于此&lt;/p>
&lt;p>20种氨基酸的结构符合一个通式，如下图所示，中间的碳原子称为Cα碳原子，表示它处在α位；左边连了一个氨基-NH2，称为N端；右边连了一个羧基-COOH，称为C端。20种不同氨基酸的差别就在于Cα上连接的侧链基团R，具体的差别网上一搜就能查到。
&lt;img loading="lazy" src="https://upload.wikimedia.org/wikipedia/commons/c/ce/AminoAcidball.svg">
&lt;a href="https://upload.wikimedia.org/wikipedia/commons/c/ce/AminoAcidball.svg">https://upload.wikimedia.org/wikipedia/commons/c/ce/AminoAcidball.svg&lt;/a>&lt;/p>
&lt;p>20种氨基酸连接的方式为脱水缩合，即一个氨基酸的羧基-COOH和另一个氨基酸的氨基-NH2反应，丢掉一个H2O，形成一个肽键-CO-NH-，如下图所示。丢掉了羧基和氨基的氨基酸被称为氨基酸残基，这个名词很形象，氨基酸缺胳膊少腿，所以变成了“残”基。
&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-05-25-introduction-to-protein-structure-prediction/PeptideBonds.png">&lt;/p>
&lt;h1 id="二级结构">二级结构&lt;/h1>
&lt;p>二级结构就是在一级结构的字符串的基础上，肽链怎样进行盘旋、折叠等变换，形成一种&lt;strong>局部&lt;/strong>的三维结构，这种局部的三维结构通常由氢键支撑。常见的二级结构有α螺旋和β折叠，如下图所示。其中α螺旋的每个残基的-NH的H和临近的第4个残基的-CO的O形成氢键，由此支撑α螺旋的结构稳定性，如下图的箭头所指虚线。β折叠则是两条肽链，平行排列，对应残基的-NH的H和-CO的O形成氢键，由此形成两股β折叠的结构，多股β折叠形成类似手风琴的样子。β折叠分为平行和反平行排列，我们前面介绍到肽段分为N端和C端，如果形成β折叠的两股链都是从N到C（或从C到N），则称为平行排列，否则是反平行排列。每股β折叠都有一个大箭头表示其方向。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-05-25-introduction-to-protein-structure-prediction/AlphaHelix.png">
&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-05-25-introduction-to-protein-structure-prediction/BetaSheets.png">&lt;/p>
&lt;p>细分的话，蛋白质的二级结构总共有8种，包括转角、无规则卷曲等。目前常采用&lt;a href="https://en.wikipedia.org/wiki/Protein_secondary_structure#DSSP_classification">DSSP的分类方法&lt;/a>，有些文献会把8种结构粗分为α螺旋、β折叠和转角这三种结构。&lt;/p>
&lt;p>由上图可知，蛋白质的二级结构极大的决定了其三级结构（下面介绍），所以有很多工作是研究怎样准确预测蛋白质的二级结构的，即预测每个氨基酸残基处于哪一种二级结构中。形式化表示就是，对于一个蛋白质一级结构字符串\(A_1A_2A_3A_4A_5…\)，输出\(a_1a_2a_3a_4a_5…\)，其中\(a_i\)∈{α螺旋，β折叠，转角}。所以，蛋白质的二级结构是一个端到端的问题，很像机器翻译，目前很多文章都会用深度学习NLP的方法来预测蛋白质的二级结构。&lt;/p>
&lt;h1 id="三级结构">三级结构&lt;/h1>
&lt;p>简单理解，三级结构就是把多个二级结构拼接到一起，折叠成一个完整的蛋白质三维结构，如下图所示。维持蛋白质三级结构的力比较多样，除了氢键之外，还有二硫键、金属键等。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-05-25-introduction-to-protein-structure-prediction/TertiaryStructure.png">
&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-05-25-introduction-to-protein-structure-prediction/ForcesStabilizingTertiaryStructure.png">&lt;/p>
&lt;h1 id="四级结构">四级结构&lt;/h1>
&lt;p>简单理解，四级结构就是多个三级结构分子组合成一个复合物，就是四级结构。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-05-25-introduction-to-protein-structure-prediction/ProteinQuaternaryStructure.png">
&lt;a href="https://en.wikipedia.org/wiki/Protein_quaternary_structure">https://en.wikipedia.org/wiki/Protein_quaternary_structure&lt;/a>&lt;/p>
&lt;p>对于CSer来说，由于四级结构仅仅是多个三级结构组合到一起，我们常说的蛋白质三维结构预测问题，通常是指预测蛋白质的三级结构。问题是，构成蛋白质链的原子非常多，我们怎样形式化描述一条蛋白质的三维结构呢？这还要从最原始的一级结构说起。&lt;/p>
&lt;hr>
&lt;h1 id="蛋白质结构预测问题">蛋白质结构预测问题&lt;/h1>
&lt;p>前面提到，两个氨基酸通过脱水缩合的方式形成肽键从而连接到一起形成一级结构（本文图四），肽键虽然是单键，但它具有类似双键的特点，即难以旋转（比如羧基中的-C=O键就是双键，无法旋转）。所以，由肽键及周围的6个原子形成了一个固定的肽键平面，这6个原子分别是-C-CO-NH-C-，如下图所示，箭头所指的红色键就是肽键，它周围画出了一个平面，就是肽键平面。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-05-25-introduction-to-protein-structure-prediction/MultiplePeptideBondPlanes.png">
&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-05-25-introduction-to-protein-structure-prediction/PhiandPsiAngles.png">&lt;/p>
&lt;p>肽键平面的存在极大的简化了蛋白质结构，可以认为这6个原子的相对位置是固定的了！另一方面，跟这个平面相连的左右两个C原子的两个键是单键，所以他们可以旋转，旋转的角度称为扭转角ϕ和ψ，为了更直观的感受肽链的肽键平面和两个扭转角，可以看下面的动画：&lt;a href="https://bitjoy.net/posts/2019-05-25-introduction-to-protein-structure-prediction/K0045879-Rotation_around_amide_bonds_in_protein.mp4">K0045879-Rotation_around_amide_bonds_in_protein.mp4&lt;/a>（来自&lt;a href="https://www.sciencephoto.com/media/639617/view">https://www.sciencephoto.com/media/639617/view&lt;/a>）&lt;/p>
&lt;p>事实上，扭转角ϕ和ψ并不是在360°范围内随机均匀分布的，1963年就有科学家统计过扭转角ϕ和ψ的分布，他们发现稳定的蛋白质结构的ϕ和ψ通常只分布在一小部分区域，如下图的拉氏图所示，这些区域正好对应了常见的α螺旋和β折叠的结构。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-05-25-introduction-to-protein-structure-prediction/RamachandranPlotLabeled.png">&lt;/p>
&lt;p>最后，我们还需要介绍一个角度，那就是ω。前面提到，虽然肽键具有双键的特点，难以旋转，但它在少数情况下还是可以旋转的。假设通常情况下，肽键的角度定义为ω=0°，如下图所示，红色的键即为肽键，这种结构的好处是它能让形成肽键的两个残基的侧链R（图中黑色基团）离得尽量的远，这样能保持比较稳定的结构。如果肽键旋转为ω=180°，变为下图的样子，则两个侧链R很靠近，就产生&lt;a href="https://baike.baidu.com/item/%E7%A9%BA%E9%97%B4%E4%BD%8D%E9%98%BB%E6%95%88%E5%BA%94">位阻效应&lt;/a>，就不稳定，所以这种情况比较少见。但不管怎么说，肽键的扭转角ω也是一个变量因素。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-05-25-introduction-to-protein-structure-prediction/StericHindrance.png">&lt;/p>
&lt;p>综上所述，对于一条肽链，如果知道每个残基的三个扭转角ϕ、ψ和ω，则可以重构出肽链的主干部分的三维结构，这就像将极坐标转换为直角坐标一样容易。需要提醒的是，本文提到的蛋白质三维结构预测问题，对蛋白质的结构进行了简化，包括：1. 仅预测蛋白质或肽链的主干结构，不考虑侧链R的结构；2. 假设肽链主干中每个键的长度是固定的；3. 不考虑键的角度，比如对于上图的肽键，仅考虑肽键绕肽键轴本身的旋转，不考虑肽键绕着某一端原子的旋转，比如固定左边的蓝色小球，肽键和右边的红色小球旋转出平面了。 下图的肽键平面，详细的标识出了各个相对固定的值。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://github.com/01joy/bitjoy.net/raw/master/blog/2019/05/AmidePlane.jpg">
Figure 8-1 from Fundamentals of Biochemistry&lt;/p>
&lt;p>所以，对于CSer来说，蛋白质的三维结构预测问题，就可以看成一个端到端的学习问题，输入是一个字符串，输出是每个字符（残基）对应的三个扭转角ϕ、ψ和ω，问题看起来非常的简洁漂亮。而且，这个问题和NLP中的序列标注、机器翻译等问题很像，所以很多NLP的技术可以用来预测蛋白质的三维结构。下图的插画就是最近发表在Cell Sytems上的一篇用LSTM预测蛋白质三维结构的文章，我会在下一篇博客中和大家分享这篇文章。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-05-25-introduction-to-protein-structure-prediction/ProteinStructurePrediction.png">
&lt;a href="https://www.sciencedirect.com/science/article/pii/S2405471219300766?via%3Dihub">https://www.sciencedirect.com/science/article/pii/S2405471219300766?via%3Dihub&lt;/a>&lt;/p>
&lt;p>有关“蛋白质结构预测”本身的最后一个问题是，为什么能仅仅通过一级结构的序列信息，预测得到其三级结构呢？也就是说蛋白质结构预测这个问题是否可解，如果蛋白质的三级结构还由其他因素决定，那么即使Deeplearning玩出花了，在生物上也是不可行的。所以，每遇到一个新问题，都要自问一下，这个问题从原理上是否可解。对于“蛋白质结构预测”这个问题，最开始也有人进行了类似的自问，得到的答案是可行的：&lt;/p>
&lt;blockquote>
&lt;p>1965年，安芬森（Anfinsen）基于还原变性的牛胰RNase在不需其他任何物质帮助下，仅通过去除变性剂和还原剂就使其恢复天然结构的实验结果，提出了“多肽链的氨基酸序列包含了形成其热力学上稳定的天然构象所必需的全部信息”的“自组装学说”，随后这个学说又得到一些补充。这些学说表明：氨基酸序列确定其空间构象，从而为蛋白质结构预测提供了可行性。&lt;/p>&lt;/blockquote>
&lt;p>&lt;a href="http://chinaxiv.org/user/download.htm?id=6478">http://chinaxiv.org/user/download.htm?id=6478&lt;/a>&lt;/p>
&lt;hr>
&lt;h1 id="casp比赛">CASP比赛&lt;/h1>
&lt;p>提到蛋白质三级结构预测，不得不提的是CASP这个比赛。CASP的全称是The Critical Assessment of protein Structure Prediction (CASP)，即蛋白质结构预测的关键评估，被誉为蛋白质结构预测的奥林匹克竞赛。CASP从1994年开始举办，每两年一届，最近的一届是2018年的CASP13。&lt;/p>
&lt;p>每一届CASP比赛，都会提供大约100条未知结构的蛋白质序列，让所有参赛者进行结构预测，比赛结束之后，主办方会通过生化方法测定这些蛋白质的三维结构，然后和参赛者预测的结果进行比对，然后给出预测得分。提供的蛋白质序列分为两类：一类序列和PDB数据库中已有结构的序列有相似性，由此可以基于模板预测，准确度比较高，这类算法称为Template-Based Modeling；另一类序列和PDB库已知结构的序列相似度很低，可以认为是全新的蛋白质，因为无法利用已有模板信息，需要进行从头测序（De novo或ab initio或Free Modeling），目前的准确率比较低。参赛选手也分为两组，一组是servers only，即仅允许算法参赛，给定3天的时间；另一组是human and servers，即允许人和算法合作，共同预测蛋白质结构，给定3周的时间。&lt;/p>
&lt;p>CASP同时提供多种比赛项目，比如常规的结构预测（Regular targets）、数据辅助预测（Data-Assisted targets）和蛋白质接触面预测（Contact predictions）等，其中数据辅助预测中提供了核磁数据（NMR）、交联数据（XLMS）等，对的，交联数据就是我目前研究的pLink处理的数据。&lt;/p></description></item><item><title>Neural Networks and Deep Learning（七）番外篇·Pytorch MNIST教程</title><link>https://bitjoy.net/posts/2019-05-19-neural-networks-and-deep-learning-appendix-pytorch-mnist-tutorial/</link><pubDate>Sun, 19 May 2019 22:07:51 +0800</pubDate><guid>https://bitjoy.net/posts/2019-05-19-neural-networks-and-deep-learning-appendix-pytorch-mnist-tutorial/</guid><description>&lt;p>由于本书成书较早（2015），作者当时使用的是Theano，但Theano已不再维护，所以本博客使用当下流行的Pytorch框架讲解MNIST图片分类的代码实现，具体就是Pytorch官方给出的MNIST代码：&lt;a href="https://github.com/pytorch/examples/tree/master/mnist">https://github.com/pytorch/examples/tree/master/mnist&lt;/a>。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-05-19-neural-networks-and-deep-learning-appendix-pytorch-mnist-tutorial/nn.png">
使用该工具在线制作：&lt;a href="http://alexlenail.me/NN-SVG/LeNet.html">http://alexlenail.me/NN-SVG/LeNet.html&lt;/a>&lt;/p>
&lt;p>下面，我首先贴出经过我注释的Pytorch MNIST代码，然后对一些关键问题进行解释。&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 20
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 21
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 22
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 23
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 24
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 25
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 26
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 27
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 28
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 29
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 30
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 31
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 32
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 33
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 34
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 35
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 36
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 37
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 38
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 39
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 40
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 41
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 42
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 43
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 44
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 45
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 46
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 47
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 48
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 49
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 50
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 51
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 52
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 53
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 54
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 55
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 56
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 57
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 58
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 59
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 60
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 61
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 62
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 63
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 64
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 65
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 66
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 67
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 68
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 69
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 70
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 71
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 72
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 73
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 74
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 75
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 76
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 77
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 78
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 79
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 80
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 81
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 82
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 83
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 84
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 85
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 86
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 87
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 88
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 89
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 90
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 91
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 92
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 93
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 94
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 95
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 96
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 97
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 98
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 99
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">100
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">101
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">102
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">103
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">104
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">105
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">106
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">107
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">108
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">109
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">110
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">111
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">112
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">113
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">114
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">115
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">116
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">117
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">118
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">119
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">120
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">121
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">122
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">123
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">124
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">125
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">126
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">127
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">128
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">129
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">130
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">131
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">132
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">133
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">134
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">135
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> __future__ &lt;span style="color:#f92672">import&lt;/span> print_function
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> argparse
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> torch
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> torch.nn &lt;span style="color:#66d9ef">as&lt;/span> nn
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> torch.nn.functional &lt;span style="color:#66d9ef">as&lt;/span> F
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> torch.optim &lt;span style="color:#66d9ef">as&lt;/span> optim
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> torchvision &lt;span style="color:#f92672">import&lt;/span> datasets, transforms
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 所有网络类要继承nn.Module&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Net&lt;/span>(nn&lt;span style="color:#f92672">.&lt;/span>Module):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">__init__&lt;/span>(self):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> super(Net, self)&lt;span style="color:#f92672">.&lt;/span>&lt;span style="color:#a6e22e">__init__&lt;/span>() &lt;span style="color:#75715e"># 调用父类构造函数&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>conv1 &lt;span style="color:#f92672">=&lt;/span> nn&lt;span style="color:#f92672">.&lt;/span>Conv2d(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">20&lt;/span>, &lt;span style="color:#ae81ff">5&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#75715e"># (in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>conv2 &lt;span style="color:#f92672">=&lt;/span> nn&lt;span style="color:#f92672">.&lt;/span>Conv2d(&lt;span style="color:#ae81ff">20&lt;/span>, &lt;span style="color:#ae81ff">50&lt;/span>, &lt;span style="color:#ae81ff">5&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#75715e"># 这一层的in_channels正好是上一层的out_channels&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>fc1 &lt;span style="color:#f92672">=&lt;/span> nn&lt;span style="color:#f92672">.&lt;/span>Linear(&lt;span style="color:#ae81ff">4&lt;/span>&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">4&lt;/span>&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">50&lt;/span>, &lt;span style="color:#ae81ff">500&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>fc2 &lt;span style="color:#f92672">=&lt;/span> nn&lt;span style="color:#f92672">.&lt;/span>Linear(&lt;span style="color:#ae81ff">500&lt;/span>, &lt;span style="color:#ae81ff">10&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">forward&lt;/span>(self, x):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> x &lt;span style="color:#f92672">=&lt;/span> F&lt;span style="color:#f92672">.&lt;/span>relu(self&lt;span style="color:#f92672">.&lt;/span>conv1(x))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> x &lt;span style="color:#f92672">=&lt;/span> F&lt;span style="color:#f92672">.&lt;/span>max_pool2d(x, &lt;span style="color:#ae81ff">2&lt;/span>, &lt;span style="color:#ae81ff">2&lt;/span>) &lt;span style="color:#75715e"># kernel_size=2, stride=2，pooling之后的大小除以2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> x &lt;span style="color:#f92672">=&lt;/span> F&lt;span style="color:#f92672">.&lt;/span>relu(self&lt;span style="color:#f92672">.&lt;/span>conv2(x))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> x &lt;span style="color:#f92672">=&lt;/span> F&lt;span style="color:#f92672">.&lt;/span>max_pool2d(x, &lt;span style="color:#ae81ff">2&lt;/span>, &lt;span style="color:#ae81ff">2&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> x &lt;span style="color:#f92672">=&lt;/span> x&lt;span style="color:#f92672">.&lt;/span>view(&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">4&lt;/span>&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">4&lt;/span>&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">50&lt;/span>) &lt;span style="color:#75715e"># 展开成 (z, 4*4*50)，其中z是通过自动推导得到的，所以这里设置为-1，这里相当于展开成行向量，便于后续全连接&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> x &lt;span style="color:#f92672">=&lt;/span> F&lt;span style="color:#f92672">.&lt;/span>relu(self&lt;span style="color:#f92672">.&lt;/span>fc1(x))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> x &lt;span style="color:#f92672">=&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>fc2(x)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> F&lt;span style="color:#f92672">.&lt;/span>log_softmax(x, dim&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#75715e"># log_softmax 即 log(softmax(x))；dim=1对行进行softmax，因为上面x.view展开成行向量了，log_softmax速度和数值稳定性都比softmax好一些&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">train&lt;/span>(args, model, device, train_loader, optimizer, epoch):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> model&lt;span style="color:#f92672">.&lt;/span>train() &lt;span style="color:#75715e"># 告诉pytorch，这是训练阶段 https://stackoverflow.com/a/51433411/2468587&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> batch_idx, (data, target) &lt;span style="color:#f92672">in&lt;/span> enumerate(train_loader):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> data, target &lt;span style="color:#f92672">=&lt;/span> data&lt;span style="color:#f92672">.&lt;/span>to(device), target&lt;span style="color:#f92672">.&lt;/span>to(device)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> optimizer&lt;span style="color:#f92672">.&lt;/span>zero_grad() &lt;span style="color:#75715e"># 每个batch的梯度重新累加&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> output &lt;span style="color:#f92672">=&lt;/span> model(data)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> loss &lt;span style="color:#f92672">=&lt;/span> F&lt;span style="color:#f92672">.&lt;/span>nll_loss(output, target) &lt;span style="color:#75715e"># 这里的nll_loss就是Michael Nielsen在ch3提到的log-likelihood cost function，配合softmax使用，batch的梯度/loss要求均值mean&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> loss&lt;span style="color:#f92672">.&lt;/span>backward() &lt;span style="color:#75715e"># 求loss对参数的梯度dw&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> optimizer&lt;span style="color:#f92672">.&lt;/span>step() &lt;span style="color:#75715e"># 梯度下降，w&amp;#39;=w-η*dw&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> batch_idx &lt;span style="color:#f92672">%&lt;/span> args&lt;span style="color:#f92672">.&lt;/span>log_interval &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#39;Train Epoch: &lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74"> [&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">/&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74"> (&lt;/span>&lt;span style="color:#e6db74">{:.0f}&lt;/span>&lt;span style="color:#e6db74">%)]&lt;/span>&lt;span style="color:#ae81ff">\t&lt;/span>&lt;span style="color:#e6db74">Loss: &lt;/span>&lt;span style="color:#e6db74">{:.6f}&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> epoch, batch_idx &lt;span style="color:#f92672">*&lt;/span> len(data), len(train_loader&lt;span style="color:#f92672">.&lt;/span>dataset),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">100.&lt;/span> &lt;span style="color:#f92672">*&lt;/span> batch_idx &lt;span style="color:#f92672">/&lt;/span> len(train_loader), loss&lt;span style="color:#f92672">.&lt;/span>item()))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test&lt;/span>(args, model, device, test_loader):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> model&lt;span style="color:#f92672">.&lt;/span>eval() &lt;span style="color:#75715e"># 告诉pytorch，这是预测（评价）阶段&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> test_loss &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> correct &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> torch&lt;span style="color:#f92672">.&lt;/span>no_grad(): &lt;span style="color:#75715e"># 预测时不需要误差反传，https://discuss.pytorch.org/t/model-eval-vs-with-torch-no-grad/19615/2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> data, target &lt;span style="color:#f92672">in&lt;/span> test_loader:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> data, target &lt;span style="color:#f92672">=&lt;/span> data&lt;span style="color:#f92672">.&lt;/span>to(device), target&lt;span style="color:#f92672">.&lt;/span>to(device)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> output &lt;span style="color:#f92672">=&lt;/span> model(data)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> test_loss &lt;span style="color:#f92672">+=&lt;/span> F&lt;span style="color:#f92672">.&lt;/span>nll_loss(output, target, reduction&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;sum&amp;#39;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>item() &lt;span style="color:#75715e"># sum up batch loss，预测时的loss求sum，L54再求均值&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pred &lt;span style="color:#f92672">=&lt;/span> output&lt;span style="color:#f92672">.&lt;/span>argmax(dim&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>, keepdim&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>) &lt;span style="color:#75715e"># get the index of the max log-probability&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> correct &lt;span style="color:#f92672">+=&lt;/span> pred&lt;span style="color:#f92672">.&lt;/span>eq(target&lt;span style="color:#f92672">.&lt;/span>view_as(pred))&lt;span style="color:#f92672">.&lt;/span>sum()&lt;span style="color:#f92672">.&lt;/span>item()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> test_loss &lt;span style="color:#f92672">/=&lt;/span> len(test_loader&lt;span style="color:#f92672">.&lt;/span>dataset)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">Test set: Average loss: &lt;/span>&lt;span style="color:#e6db74">{:.4f}&lt;/span>&lt;span style="color:#e6db74">, Accuracy: &lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">/&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74"> (&lt;/span>&lt;span style="color:#e6db74">{:.0f}&lt;/span>&lt;span style="color:#e6db74">%)&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> test_loss, correct, len(test_loader&lt;span style="color:#f92672">.&lt;/span>dataset),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">100.&lt;/span> &lt;span style="color:#f92672">*&lt;/span> correct &lt;span style="color:#f92672">/&lt;/span> len(test_loader&lt;span style="color:#f92672">.&lt;/span>dataset)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">plot1digit&lt;/span>(data_loader):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">import&lt;/span> numpy &lt;span style="color:#66d9ef">as&lt;/span> np
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">import&lt;/span> matplotlib.pyplot &lt;span style="color:#66d9ef">as&lt;/span> plt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> examples &lt;span style="color:#f92672">=&lt;/span> enumerate(data_loader)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> batch_idx, (Xs, ys) &lt;span style="color:#f92672">=&lt;/span> next(examples) &lt;span style="color:#75715e"># 读取到的是一个batch的所有数据&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> X&lt;span style="color:#f92672">=&lt;/span>Xs[&lt;span style="color:#ae81ff">0&lt;/span>]&lt;span style="color:#f92672">.&lt;/span>numpy()[&lt;span style="color:#ae81ff">0&lt;/span>] &lt;span style="color:#75715e"># Xs[0]取出batch中的第一个数据，由tensor转换为numpy，因为pytorch tensor的格式是[channel, height, width]，所以最后[0]取出其第一个通道的[h,w]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> y&lt;span style="color:#f92672">=&lt;/span>ys[&lt;span style="color:#ae81ff">0&lt;/span>]&lt;span style="color:#f92672">.&lt;/span>numpy() &lt;span style="color:#75715e"># y没有通道，就一个标量值&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> np&lt;span style="color:#f92672">.&lt;/span>savetxt(&lt;span style="color:#e6db74">&amp;#39;../../../fig/&lt;/span>&lt;span style="color:#e6db74">%d&lt;/span>&lt;span style="color:#e6db74">.csv&amp;#39;&lt;/span>&lt;span style="color:#f92672">%&lt;/span>y, X, delimiter&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;,&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> plt&lt;span style="color:#f92672">.&lt;/span>imshow(X, cmap&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;Greys&amp;#39;&lt;/span>) &lt;span style="color:#75715e"># or &amp;#39;Greys_r&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> plt&lt;span style="color:#f92672">.&lt;/span>savefig(&lt;span style="color:#e6db74">&amp;#39;../../../fig/&lt;/span>&lt;span style="color:#e6db74">%d&lt;/span>&lt;span style="color:#e6db74">.png&amp;#39;&lt;/span>&lt;span style="color:#f92672">%&lt;/span>y)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> plt&lt;span style="color:#f92672">.&lt;/span>show()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Training settings&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser &lt;span style="color:#f92672">=&lt;/span> argparse&lt;span style="color:#f92672">.&lt;/span>ArgumentParser(description&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;PyTorch MNIST Example&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#f92672">.&lt;/span>add_argument(&lt;span style="color:#e6db74">&amp;#39;--batch-size&amp;#39;&lt;/span>, type&lt;span style="color:#f92672">=&lt;/span>int, default&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">64&lt;/span>, metavar&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;N&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> help&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;input batch size for training (default: 64)&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#f92672">.&lt;/span>add_argument(&lt;span style="color:#e6db74">&amp;#39;--test-batch-size&amp;#39;&lt;/span>, type&lt;span style="color:#f92672">=&lt;/span>int, default&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1000&lt;/span>, metavar&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;N&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> help&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;input batch size for testing (default: 1000)&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#f92672">.&lt;/span>add_argument(&lt;span style="color:#e6db74">&amp;#39;--epochs&amp;#39;&lt;/span>, type&lt;span style="color:#f92672">=&lt;/span>int, default&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">10&lt;/span>, metavar&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;N&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> help&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;number of epochs to train (default: 10)&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#f92672">.&lt;/span>add_argument(&lt;span style="color:#e6db74">&amp;#39;--lr&amp;#39;&lt;/span>, type&lt;span style="color:#f92672">=&lt;/span>float, default&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0.01&lt;/span>, metavar&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;LR&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> help&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;learning rate (default: 0.01)&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#f92672">.&lt;/span>add_argument(&lt;span style="color:#e6db74">&amp;#39;--momentum&amp;#39;&lt;/span>, type&lt;span style="color:#f92672">=&lt;/span>float, default&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0.5&lt;/span>, metavar&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;M&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> help&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;SGD momentum (default: 0.5)&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#f92672">.&lt;/span>add_argument(&lt;span style="color:#e6db74">&amp;#39;--no-cuda&amp;#39;&lt;/span>, action&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;store_true&amp;#39;&lt;/span>, default&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">False&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> help&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;disables CUDA training&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#f92672">.&lt;/span>add_argument(&lt;span style="color:#e6db74">&amp;#39;--seed&amp;#39;&lt;/span>, type&lt;span style="color:#f92672">=&lt;/span>int, default&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>, metavar&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;S&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> help&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;random seed (default: 1)&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#f92672">.&lt;/span>add_argument(&lt;span style="color:#e6db74">&amp;#39;--log-interval&amp;#39;&lt;/span>, type&lt;span style="color:#f92672">=&lt;/span>int, default&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">10&lt;/span>, metavar&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;N&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> help&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;how many batches to wait before logging training status&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#f92672">.&lt;/span>add_argument(&lt;span style="color:#e6db74">&amp;#39;--save-model&amp;#39;&lt;/span>, action&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;store_true&amp;#39;&lt;/span>, default&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">False&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> help&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;For Saving the current Model&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> args &lt;span style="color:#f92672">=&lt;/span> parser&lt;span style="color:#f92672">.&lt;/span>parse_args()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> use_cuda &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">not&lt;/span> args&lt;span style="color:#f92672">.&lt;/span>no_cuda &lt;span style="color:#f92672">and&lt;/span> torch&lt;span style="color:#f92672">.&lt;/span>cuda&lt;span style="color:#f92672">.&lt;/span>is_available()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> torch&lt;span style="color:#f92672">.&lt;/span>manual_seed(args&lt;span style="color:#f92672">.&lt;/span>seed)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> device &lt;span style="color:#f92672">=&lt;/span> torch&lt;span style="color:#f92672">.&lt;/span>device(&lt;span style="color:#e6db74">&amp;#34;cuda&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> use_cuda &lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#e6db74">&amp;#34;cpu&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> kwargs &lt;span style="color:#f92672">=&lt;/span> {&lt;span style="color:#e6db74">&amp;#39;num_workers&amp;#39;&lt;/span>: &lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;pin_memory&amp;#39;&lt;/span>: &lt;span style="color:#66d9ef">True&lt;/span>} &lt;span style="color:#66d9ef">if&lt;/span> use_cuda &lt;span style="color:#66d9ef">else&lt;/span> {}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> train_loader &lt;span style="color:#f92672">=&lt;/span> torch&lt;span style="color:#f92672">.&lt;/span>utils&lt;span style="color:#f92672">.&lt;/span>data&lt;span style="color:#f92672">.&lt;/span>DataLoader(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> datasets&lt;span style="color:#f92672">.&lt;/span>MNIST(&lt;span style="color:#e6db74">&amp;#39;../data&amp;#39;&lt;/span>, train&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>, download&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> transform&lt;span style="color:#f92672">=&lt;/span>transforms&lt;span style="color:#f92672">.&lt;/span>Compose([ &lt;span style="color:#75715e"># https://discuss.pytorch.org/t/can-some-please-explain-how-the-transforms-work-and-why-normalize-the-data/2461/3&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> transforms&lt;span style="color:#f92672">.&lt;/span>ToTensor(), &lt;span style="color:#75715e"># 把[0,255]的(H,W,C)的图片转换为[0,1]的(channel,height,width)的图片&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> transforms&lt;span style="color:#f92672">.&lt;/span>Normalize((&lt;span style="color:#ae81ff">0.1307&lt;/span>,), (&lt;span style="color:#ae81ff">0.3081&lt;/span>,)) &lt;span style="color:#75715e"># 进行z-score标准化，这两个数分别是MNIST的均值和标准差&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ])),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> batch_size&lt;span style="color:#f92672">=&lt;/span>args&lt;span style="color:#f92672">.&lt;/span>batch_size, shuffle&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>, &lt;span style="color:#f92672">**&lt;/span>kwargs)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> test_loader &lt;span style="color:#f92672">=&lt;/span> torch&lt;span style="color:#f92672">.&lt;/span>utils&lt;span style="color:#f92672">.&lt;/span>data&lt;span style="color:#f92672">.&lt;/span>DataLoader(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> datasets&lt;span style="color:#f92672">.&lt;/span>MNIST(&lt;span style="color:#e6db74">&amp;#39;../data&amp;#39;&lt;/span>, train&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">False&lt;/span>, transform&lt;span style="color:#f92672">=&lt;/span>transforms&lt;span style="color:#f92672">.&lt;/span>Compose([
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> transforms&lt;span style="color:#f92672">.&lt;/span>ToTensor(),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> transforms&lt;span style="color:#f92672">.&lt;/span>Normalize((&lt;span style="color:#ae81ff">0.1307&lt;/span>,), (&lt;span style="color:#ae81ff">0.3081&lt;/span>,))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ])),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> batch_size&lt;span style="color:#f92672">=&lt;/span>args&lt;span style="color:#f92672">.&lt;/span>test_batch_size, shuffle&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>, &lt;span style="color:#f92672">**&lt;/span>kwargs)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># plot1digit(train_loader)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> model &lt;span style="color:#f92672">=&lt;/span> Net()&lt;span style="color:#f92672">.&lt;/span>to(device)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> optimizer &lt;span style="color:#f92672">=&lt;/span> optim&lt;span style="color:#f92672">.&lt;/span>SGD(model&lt;span style="color:#f92672">.&lt;/span>parameters(), lr&lt;span style="color:#f92672">=&lt;/span>args&lt;span style="color:#f92672">.&lt;/span>lr, momentum&lt;span style="color:#f92672">=&lt;/span>args&lt;span style="color:#f92672">.&lt;/span>momentum)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> epoch &lt;span style="color:#f92672">in&lt;/span> range(&lt;span style="color:#ae81ff">1&lt;/span>, args&lt;span style="color:#f92672">.&lt;/span>epochs &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> train(args, model, device, train_loader, optimizer, epoch)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> test(args, model, device, test_loader)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (args&lt;span style="color:#f92672">.&lt;/span>save_model):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> torch&lt;span style="color:#f92672">.&lt;/span>save(model&lt;span style="color:#f92672">.&lt;/span>state_dict(),&lt;span style="color:#e6db74">&amp;#34;mnist_cnn.pt&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> __name__ &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#39;__main__&amp;#39;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> main()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>首先是MNIST数据格式的问题，在L108~L120，我们使用Pytorch的DataLoader载入了训练和测试数据，数据格式本质上和本系列博客的&lt;a href="https://bitjoy.net/posts/2018-11-25-neutral-networks-and-deep-learning-1-mnist-dataset/">第一篇博客&lt;/a>介绍的是一致的，即每张图片都是28*28的灰度图片，因为是灰度图片，所以只有一个通道数，默认格式是(H,W,C)，且值域范围是[0,255]。但上述代码对原始图片进行了两个变换，分别是ToTensor和Normalize。ToTensor将[0,255]的灰度图片(H,W,C)转换为[0,1]的灰度图片(C,H,W)，即Pytorch对2D图片的格式要求都是channel在前。所以经过这一转换，一张图片的shape是(1,28,28)，是一个三维矩阵；如果是彩色图片的话，有R,G,B三个通道，C=3。Normalize对图片数据进行z-score标准化，即减去均值再除以标准差；L112的两个值就是预先计算的MNIST数据集的均值和标准差。这些操作的好处是能让模型更加平稳快速收敛。同第一篇博客一样，我们可以把Pytorch格式的图片打印出来以便直观理解，L61的plot1digit函数就是这个作用。&lt;/p></description></item><item><title>《三体》始末</title><link>https://bitjoy.net/posts/2019-05-18-introduction-of-the-three-body-problem/</link><pubDate>Sat, 18 May 2019 22:35:51 +0800</pubDate><guid>https://bitjoy.net/posts/2019-05-18-introduction-of-the-three-body-problem/</guid><description>《三体》始末</description></item><item><title>Neural Networks and Deep Learning（六）深度学习</title><link>https://bitjoy.net/posts/2019-05-04-neural-networks-and-deep-learning-6-dl/</link><pubDate>Sat, 04 May 2019 19:35:01 +0800</pubDate><guid>https://bitjoy.net/posts/2019-05-04-neural-networks-and-deep-learning-6-dl/</guid><description>&lt;p>今天我们终于进入到了本书的重头戏——深度学习。其实，这一章的深度学习主要介绍的是卷积神经网络，即CNN。&lt;/p>
&lt;p>本书之前的章节介绍的都是如下图的全连接网络，虽然全连接网络已经能够在MNIST数据集上取得98%以上的测试准确率，但有两个比较大的缺点：1. 训练参数太多，容易过拟合；2. 难以捕捉图片的局部信息。第一点很好理解，参数一多，网络就难以训练，难以加深。对于第二点，因为全连接的每个神经元都和上一层的所有神经元相连，无论距离远近，也就是说网络不会捕捉图片的局部信息和空间结构信息。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/tikz41.png">&lt;/p>
&lt;p>本章要介绍的卷积神经网络，相对于全连接网络，有如下三个特点：1. 局部感知local receptive fields 2. 权值共享shared weights 3. 池化pooling，下面分别介绍这三部分内容。&lt;/p>
&lt;h1 id="局部感知">局部感知&lt;/h1>
&lt;p>对于MNIST的一张28*28灰度图片，全连接网络的输入把图片展开成一个维度为784的向量，这就天然丢失了图片的空间结构信息。而CNN的输入保持了图片28*28的二维空间结构信息，相应的，CNN的中间层也是二维的。这就涉及到输入层的二维图片和隐藏层的二维图片如何对应的问题。&lt;/p>
&lt;p>CNN使用一个被称为“卷积核”的东西，把输入图片转换为隐藏层的特征图（feature map），如下图所示，假设卷积核大小为5*5，则输入图片每5*5的一个小区域被转换为隐藏层的一个神经元（像素），这个小区域就称为局部感受野。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/tikz44.png">&lt;/p>
&lt;p>当卷积核不断的在输入图片中移动时，假设每次移动一格（stride=1），则原来28*28的图片，经过一次卷积后，得到的feature map大小为24*24，相比输入图片小了一圈。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/tikz45.png">&lt;/p>
&lt;h1 id="权值共享">权值共享&lt;/h1>
&lt;p>那么，这个卷积操作具体是怎样执行的呢，非常简单。5*5的卷积核本质是一个5*5的矩阵，矩阵中的每个值相当于这个卷积核的参数，或者说权值w。每次卷积时，5*5的矩阵和输入图片中5*5的感受野对应位相乘再相加得到隐藏层的一个值。&lt;/p>
&lt;p>下图是一个缩小版的动图例子，左图的绿色大图相当于输入的5*5图片，移动的黄色小图相当于当前卷积的感受野，大小为3*3。在这个3*3的感受野中，每个单元格居中的数字是输入图片的像素值，右下角的红色小字表示卷积核的权值。每次卷积操作，感受野内的图片像素和卷积核权值相乘再相加，得到右图红色小图中的一个单元格的值，这就完成了一次卷积。当黄色感受野不断在输入图片中移动时，右边的feature map也不断被填充，直到一轮卷积完成。整个过程进行了9次卷积，feature map的大小为3*3=9卷积次数。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-05-04-neural-networks-and-deep-learning-6-dl/e7ff1-1zcjpufrb6ehpri4eyp6aaa.gif">
&lt;a href="https://hackernoon.com/visualizing-parts-of-convolutional-neural-networks-using-keras-and-cats-5cc01b214e59">https://hackernoon.com/visualizing-parts-of-convolutional-neural-networks-using-keras-and-cats-5cc01b214e59&lt;/a>&lt;/p>
&lt;p>这里又涉及到CNN的第二个特点——权值共享。注意到，对于上图的一轮卷积操作，不同感受野内右下角的权值矩阵是一样的，也就是说9次卷积的卷积核权值是一样的。权值共享有两个好处，一是特征位置无关，二是参数量大大下降。&lt;/p>
&lt;p>对于特征位置无关 。这个3*3的卷积核相当于一个特征提取器或者说&lt;a href="https://www.zhihu.com/question/39022858/answer/224446917">滤波器&lt;/a>，比如这个特征提取器能够提取“猫”这个特征，则无论猫在输入图片的左上角还是右下角，“猫”这个特征都能被提取出来，因为卷积核在小范围移动，无论“猫”位于图片的哪个区域，当卷积核移动到这个区域时，卷积得到的输出比较大，被激活，得到“猫”这个特征。所以CNN对位置不敏感，这对图像处理尤其有利。正因为这个特点，经过卷积核卷积操作之后的小图片（上图右边的红色图片）被称为特征图（feature map），因为它就是用卷积核提取出来的符合这个卷积核描述的一个特征。&lt;/p>
&lt;p>对于参数量大大下降。事实上，一次卷积操作除了上面动图显示的卷积核与感受野内的图片相乘再相加之外，还会对加和之后的值做一个激活输出。回到我们的MNIST例子，一次卷积操作用公式来表示就是：&lt;/p>
$$\begin{eqnarray}\sigma\left(b + \sum_{l=0}^4 \sum_{m=0}^4 w_{l,m} a_{j+l, k+m} \right).\tag{1}\end{eqnarray}$$&lt;p>\(w\)表示卷积核权值矩阵，\(a\)表示感受野内的输入图片，两个累加\(\sum\)就是上面动图显示的相乘相加过程，得到和之后，还会加上一个偏移量\(b\)，最后进行激活输出\(\sigma\)。所以一个5*5的卷积核，参数量为5*5+1=26。如果有20个卷积核，参数总量为20*26=520。但如果是全连接网络，假设隐藏层有30个，则参数量为784*30+30=23550。所以仅考虑隐藏层的参数量，CNN就比全连接网络少了45倍的参数，参数量少了，就能加快训练，网络也有可能加深。&lt;/p>
&lt;h1 id="池化">池化&lt;/h1>
&lt;p>池化就很好理解了，对于卷积得到的feature map，再画一个框（类似于卷积层的感受野），把框内的最大值取出来作为池化之后的值，这就是max-pooling。池化的目的是用来简化信息的，相当于降维。池化的框也可以称为核kernel，如果kernel的大小是2*2的，则一个24*24的feature map，经过max-pooling之后就变成了12*12了，维度瞬间降了一半， 把原来的feature map变成了一个紧凑的feature map。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/tikz47.png">&lt;/p>
&lt;p>池化层往往跟在卷积层的后面，下图表示一张28*28的图片，使用3个5*5的卷积核之后，得到了3个24*24的feature map，再经过2*2的max-pooling，得到3个12*12的feature map。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/tikz48.png">&lt;/p>
&lt;p>到这里，CNN的三大特点就介绍完毕了。对于上图，三个卷积核相当于提取了三种特征，我们还需要完成最终的分类任务，这时候还得把全连接网络请过来。经过max-pooling之后，我们再接一个包含10个神经元的全连接层，作为输出层，完整的网络结果如下：&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/tikz49.png">&lt;/p>
&lt;p>最后的全连接层和我们前面介绍的全连接网络是完全一样的，只不过全连接的输入是3个经过max-pooling之后的feature map，再和输出层相连时，可以想象成先把3个12*12的feature map展开并首尾相连，得到一个3*12*12=432的向量，再和输出层的10个神经元进行全连接。这就是一个非常简单的CNN网络，包含一个输入层、一个卷积层、一个池化层和一个输出层。&lt;/p>
&lt;p>本文的代码示例network3.py中，构建了一个和上图类似的简单的CNN网络，如下图所示，使用了20个卷积核，相当于提取了20种特征；max-pooling之后使用了两个全连接层，前一层包含100个隐藏神经元，使用sigmoid激活；后一层包含10个神经元，使用softmax激活，作为输出层。就是这么一个简单的CNN网络，其在测试集上的准确率达到了98.78%，超过了本文之前构建的所有的全连接网络。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/simple_conv.png">&lt;/p>
&lt;p>由于原文使用的是已经不再维护的Theano，本博客不打算详细介绍其代码实现，我将在稍后的博文中分享Pytorch的CNN代码。不过我还是把原文对CNN的优化过程总结如下，用测试集的准确率作为性能指标：&lt;/p>
&lt;ol>
&lt;li>上图简单的CNN网络，98.78%&lt;/li>
&lt;li>增加一个卷积层，且把激活函数换成ReLU，99.23%&lt;/li>
&lt;li>数据增强，把原有的5000张图片，上下左右各平移一个像素，增加了4倍数据，99.37%&lt;/li>
&lt;li>增加一个全连接层，且全连接层神经元增加为1000个，使用dropout=0.5，epoch相应减少到40个，99.6%。因为卷积层有权值共享，天然参数少防止过拟合，所以dropout一般只用于全连接层&lt;/li>
&lt;li>模型融合ensemble，5个上述模型，采用majority vote，99.67%，已接近人类水平&lt;/li>
&lt;/ol>
&lt;p>虽然经过上述5步，准确率没有达到100%，但那些分类错误的图片，真的很难说分错了，因为图片看起来就不是它标注的结果（右上角），就应该是分错的结果（右下角）。总的来说，我觉得已经非常不错了。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/ensemble_errors.png">&lt;/p>
&lt;p>稍微解释两个问题。&lt;/p></description></item><item><title>Neural Networks and Deep Learning（五）为什么深度神经网络难以训练</title><link>https://bitjoy.net/posts/2019-04-14-neural-networks-and-deep-learning-5-why-are-nn-hard-to-train/</link><pubDate>Sun, 14 Apr 2019 19:09:46 +0800</pubDate><guid>https://bitjoy.net/posts/2019-04-14-neural-networks-and-deep-learning-5-why-are-nn-hard-to-train/</guid><description>&lt;p>本章我们将分析一下为什么深度神经网络难以训练的问题。&lt;/p>
&lt;p>首先来看问题：如果神经网络的层次不断加深，则在BP误差反向传播的过程中，网络前几层的梯度更新会非常慢，导致前几层的权重无法学习到比较好的值，这就是梯度消失问题（The vanishing gradient problem）。&lt;/p>
&lt;p>以我们在&lt;a href="https://bitjoy.net/posts/2019-03-18-neural-networks-and-deep-learning-3-1-gradient-vanishing/">第三章学习的network2.py&lt;/a>为例（交叉熵损失函数+Sigmoid激活函数），我们可以计算每个神经元中误差对偏移量\(b\)的偏导\(\partial C/ \partial b\)，根据&lt;a href="https://bitjoy.net/posts/2018-12-14-neutral-networks-and-deep-learning-2-bp/">第二章BP网络&lt;/a>的知识，\(\partial C/ \partial b\)也是\(\partial C/ \partial w\)的一部分（BP3和BP4的关系），所以如果\(\partial C/ \partial b\)的绝对值大，则说明梯度大，在误差反向传播的时候，\(b\)和\(w\)更新就快。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/tikz21.png">&lt;/p>
&lt;p>假设network2的网络结构是[784,30,30,10]，即有两个隐藏层，则我们可以画出在误差反向传播过程中，隐藏层每个神经元的\(\partial C/ \partial b\)的大小，用柱子长度表示。由下图可知，我们发现第二个隐藏层的梯度普遍大于第一个隐藏层的梯度，这会是一般现象吗，还是偶然现象？&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-04-14-neural-networks-and-deep-learning-5-why-are-nn-hard-to-train/ch5.1.png">&lt;/p>
&lt;p>既然梯度出现了层与层的差异，则可以定义第\(l\)层的梯度（如不加说明，则默认是误差\(C\)对偏移量\(b\)的梯度）向量的长度为\(\| \delta^l \|\)，比如\(\| \delta^1 \|\)表示第一个隐藏层中每个神经元的\(\partial C/ \partial b\)的绝对值之和，就是一范数，如果\(\| \delta^l \|\)越大，则说明这一层权重的更新越快。&lt;/p>
&lt;p>由此，我们可以画出当有两个隐藏层时，\(\| \delta^l \|\)随epoch的变化情况：&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/training_speed_2_layers.png">&lt;/p>
&lt;p>当有三个隐藏层时：&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/training_speed_3_layers.png">&lt;/p>
&lt;p>当有四个隐藏层时：&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/training_speed_4_layers.png">&lt;/p>
&lt;p>我们发现，规律是惊人的一致，即越靠近输出层的隐藏层，\(\| \delta^l \|\)越大，即梯度更新越快；越靠近输入层的隐藏层，\(\| \delta^l \|\)越小，即梯度更新越慢。&lt;/p>
&lt;p>这就会导致梯度消失的问题（The vanishing gradient problem）：即在误差&lt;strong>反向&lt;/strong>传播过程中，刚开始权重更新比较快，越到后面（越靠近输入层），则权重更新变得很慢，无法搜索到比较优的值。&lt;/p>
&lt;p>所以，对于同样的network2，其他参数都不变，只是单纯增加网络层数，验证集上的准确率反而会下降！按理说网络层数增加，验证集上的准确率会上升，或者不变，至少不应该下降啊，因为最不济增加的网络层什么都不做，准确率应该一样才对，为什么反而下降了呢。虽然层数增加了，但因为上述梯度消失问题，靠近输入层的权重反而没学好，因为权重是随机初始化的，所以验证集上的准确率反而下降了。&lt;/p>
&lt;p>那么，为什么层数增加会导致梯度消失问题呢，我们可以从BP的更新公式中一探究竟。&lt;/p>
&lt;p>为了简化问题，假设我们的网络每一层只有一个神经元：&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/tikz37.png">&lt;/p>
&lt;p>则根据BP的更新公式，可以计算得到&lt;/p>
$$\begin{eqnarray}\frac{\partial C}{\partial b_1} = \sigma'(z_1) \, w_2 \sigma'(z_2) \,w_3 \sigma'(z_3) \, w_4 \sigma'(z_4) \, \frac{\partial C}{\partial a_4}.\tag{1}\end{eqnarray}$$&lt;p>计算过程其实很简单，对照本博客开头的那张图，\(\sigma'(z_4) \, \frac{\partial C}{\partial a_4}\)就是(BP1)，把(BP1)带入(BP2)，就是不断乘以\(w^{l+1} \sigma'(z^l)\)，然后就能得到下图的公式。&lt;/p></description></item><item><title>Neural Networks and Deep Learning（四）图解神经网络为什么能拟合任意函数</title><link>https://bitjoy.net/posts/2019-04-07-neural-networks-and-deep-learning-4-why-nn-can-compute-any-function/</link><pubDate>Sun, 07 Apr 2019 18:34:01 +0800</pubDate><guid>https://bitjoy.net/posts/2019-04-07-neural-networks-and-deep-learning-4-why-nn-can-compute-any-function/</guid><description>&lt;p>我们应该都听说过神经网络强大到能拟合任意一个函数，但细究起来很少有人能论证这个观点，这一章就用通俗易懂的图解方式来证明神经网络为什么能拟合任意一个函数。&lt;/p>
&lt;p>开始介绍之前，有两点需要注意：&lt;/p>
&lt;ol>
&lt;li>并不是说神经网络可以精确计算任意一个函数\(f(x)\)，而是说当隐藏层神经元增加时，可以无限逼近\(f(x)\)，比如对于任何一个输入\(x\)，网络的输出\(g(x)\)和正确值\(f(x)\)的差小于某个阈值，\(|g(x) – f(x)| &lt; \epsilon\)；&lt;/li>
&lt;li>神经网络拟合的是连续函数，而不是那种不连续、离散、急剧变化的函数。&lt;/li>
&lt;/ol>
&lt;p>假设给定一个下图的连续函数，函数形式未知，本章将用图解的方式来证明，一个单隐层的神经网络就可以很好的拟合这个未知函数。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-04-07-neural-networks-and-deep-learning-4-why-nn-can-compute-any-function/ch4.1.png">&lt;/p>
&lt;p>首先，假设我们的隐藏层只有两个神经元，激活函数使用Sigmoid，并且我们暂时只关注上面那个神经元的参数和输出。则通过调整该神经元的\(w\)和\(b\)，可以得到不同形状的Sigmoid函数形式。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-04-07-neural-networks-and-deep-learning-4-why-nn-can-compute-any-function/ch4.2.png">&lt;/p>
&lt;p>极端情况下，如果\(w\)很大而\(b\)很小，则可以用Sigmoid函数模拟阶梯函数：&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/high_weight_function.jpg">&lt;/p>
&lt;p>如果令\(s = -b/w\)，则只用一个\(s\)就可以确定Sigmoid的函数图像：&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-04-07-neural-networks-and-deep-learning-4-why-nn-can-compute-any-function/ch4.3.png">&lt;/p>
&lt;p>如果把隐藏层下面那个神经元也考虑进来，并且令隐藏层的两个神经元和输出层的神经元的连接权重互为相反数，则输出层未激活值\(z=w_1 a_1 + w_2 a_2\)的函数图像变成了一个神奇的鼓包，这个鼓包就是我们后续拟合任意函数的基本单元。根据严格的函数形式，还可以知道\(w_1\)和\(w_2\)的绝对值控制着鼓包的高度，\(s_1\)和\(s_2\)的值控制着鼓包的位置和宽度。&lt;a href="http://neuralnetworksanddeeplearning.com/chap4.html#bump_fn">大家可以去原始网页上体验一下作者给出的可交互版本，很有意思&lt;/a>。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/bump_function.jpg">&lt;/p>
&lt;p>有了这个基本单元之后，我们可以通过增加隐藏层神经元的个数来增加鼓包的个数，比如再增加一对隐层神经元，可增加一个鼓包。虽然下图的例子中两个鼓包相互独立，但通过调整4个\(s\)，可以让两个鼓包相连甚至交错，&lt;a href="http://neuralnetworksanddeeplearning.com/chap4.html#double_bump">大家可以去原网页试一试&lt;/a>。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-04-07-neural-networks-and-deep-learning-4-why-nn-can-compute-any-function/ch4.4.png">&lt;/p>
&lt;p>继续增加隐层神经元个数，则可以继续增加鼓包的数量，如下图所示。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-04-07-neural-networks-and-deep-learning-4-why-nn-can-compute-any-function/ch4.5.png">&lt;/p>
&lt;p>到这里想必大家马上知道了为什么神经网络能拟合任何一个函数了，如果隐层神经元足够多，则右图的小鼓包可以足够密，通过调整每个鼓包的高度，则无穷多个鼓包的顶点连线可以拟合任意一个函数。这和我们求函数积分（函数下方面积）时使用多个小矩形近似是一个道理！&lt;/p>
&lt;p>所以对于本章开头的未知函数，我们通过调整不同鼓包的高度，可以使得小矩形面积之和与真实积分的差在\( \epsilon=0.4\)以内。如果无限增加隐层神经元个数，则可以无限逼近真实值。这就说明神经网络确实可以拟合任意一个函数。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-04-07-neural-networks-and-deep-learning-4-why-nn-can-compute-any-function/ch4.6.png">&lt;/p>
&lt;p>上述推导稍微需要注意的一点是，右图的输出是未激活函数值\(\sum_j w_j a_j\)，而网络真正的输出是激活值\(\sigma(\sum_j w_j a_j + b)\)。这没有太大的关系，因为上面已经说明未激活输出能拟合任意函数，激活函数也是一个函数。增加激活函数就要求右图需要拟合激活函数和真实函数的嵌套函数。既然未激活输出能拟合任意函数，肯定能拟合这个嵌套函数\(\sigma^{-1} \circ f(x)\)，再用激活函数作用一下\(\sigma\circ\sigma^{-1} \circ f(x)\)，激活函数抵消了，正好得到\(f(x)\)。&lt;/p>
&lt;p>如果输入是多维，或者输出是多维，都是类似的道理。这就说明神经网络确实可以拟合任意函数，真的很强大哦。&lt;/p></description></item><item><title>Neural Networks and Deep Learning（三·三）权重初始化及其他</title><link>https://bitjoy.net/posts/2019-04-06-neural-networks-and-deep-learning-3-3-weight-initialization/</link><pubDate>Sat, 06 Apr 2019 17:52:56 +0800</pubDate><guid>https://bitjoy.net/posts/2019-04-06-neural-networks-and-deep-learning-3-3-weight-initialization/</guid><description>&lt;h1 id="权重初始化">权重初始化&lt;/h1>
&lt;p>在之前的章节中，我们都是用一个标准正态分布\(N(0,1^2)\)初始化所有的参数\(w\)和\(b\)，但是当神经元数量比较多时，会出现意想不到的问题。&lt;/p>
&lt;p>假设一个神经网络的输入层有1000个神经元，且某个样本的1000维输入中，恰好有500维是0，另500维是1。我们目前考察隐藏层的第一个神经元，则该神经元为激活的输出为\(z = \sum_j w_j x_j+b\)，因为输入中的500维是0，所以\(z\)相当于有501个来自\(N(0,1^2)\)的随机变量相加。因为\(w_j\)和\(b\)的初始化都是独立同分布的，所以\(z\)也是一个正态分布，均值为0，但方差变成了\(\sqrt{501} \approx 22.4\)，即\(z\sim N(0,\sqrt{501}^2)\)。我们知道对于正态分布，如果方差越小，则分布的形状是高廋型的；如果方差越大，则分布的形状是矮胖型的。所以\(z\)有很大的概率取值会远大于1或远小于-1。又因为激活函数是sigmoid，当\(z\)远大于1或远小于-1时，\(\sigma (z)\)趋近于1或者0，且导数趋于0，变化缓慢，导致梯度消失。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/tikz32.png">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-04-06-neural-networks-and-deep-learning-3-3-weight-initialization/ch3.8.png">&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;/tbody>
&lt;/table>
&lt;p>请注意，这里的梯度消失和&lt;a href="https://bitjoy.net/posts/2019-03-18-neural-networks-and-deep-learning-3-1-gradient-vanishing/">之前介绍得梯度消失&lt;/a>稍有不同，之前是说在误差反向传播过程中，损失函数对权重的导数中包含梯度消失项，所以可以通过更换损失函数来解决。但是这里的梯度消失并不是在误差反向传播过程中产生的，而是在正向传播产生的，跟损失函数没关系。&lt;/p>
&lt;p>解决这个问题的方法很简单，根据上面的分析，如果输入\(x_j\)全为1，\(w\)和\(b\)都来自\(N(0,1^2)\)，则\(z\sim N(0, \sqrt{n+1}^2)\)，其中\(n\)为输入样本的维度。要减小\(z\)的方差，减小\(w\)和\(b\)的方差就可以了。因为\(b\)只有一个，对整体的影响不大，可以不修改\(b\)的分布，\(b\)依然来自\(N(0,1^2)\)。把\(w_j\)的分布修改为\(N(0, (\frac{1}{\sqrt{n}})^2)\)，此时\(z\sim N(0, \sqrt{2}^2)\)，\(\sqrt{2}=1.414\)就非常接近1了，\(z\)的分布也变成了一个高廋型的，梯度消失问题也就不存在了。&lt;/p>
&lt;p>如果是开头的例子，输入维度为1000，其中500为0，500为1，\(w_j\sim N(0, (\frac{1}{\sqrt{1000}})^2)\)，\(b\sim N(0,1^2)\)，则\(z\sim N(0, \sqrt{\frac{3}{2}}^2)\)，\(\sqrt{3/2} = 1.22\ldots\)也是高廋型的，不会有梯度消失的问题。&lt;/p>
&lt;p>由下图可知，在新的权重初始化策略下，网络很快就收敛了，比之前的方法快很多。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/weight_initialization_30.png">&lt;/p>
&lt;h1 id="怎样选择超参数">怎样选择超参数&lt;/h1>
&lt;p>大原则：在网络优化的前期，尽量使网络结构、问题简单，以便快速得实验结果，不断尝试超参数取值，当找到正确的优化方向后，再慢慢把网络和问题变复杂，精细调整超参数。比如MNIST问题，开始可以减少训练数据，只取0和1的图片，做二分类；同时可以减少网络层数，验证集大小等，以便快速得到网络输出，判断网络性能变化。这样可以快速尝试新的超参数。&lt;/p>
&lt;h1 id="学习率">学习率\(\eta\)&lt;/h1>
&lt;p>在误差反向传播中，学习率太大，虽然可以加速学习，但在后期可能导致网络震荡，无法收敛；学习率太小，导致学习速度太慢，训练时间过长。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/multiple_eta.png">&lt;/p>
&lt;p>确定学习率的方法是：首先随便选定一个值，比如0.01，然后不断增大10倍：0.1, 1, 10, 100…如果发现cost曲线在震荡，说明选大了，要降低，直到找到一个比较合适的值。这个过程只要找到合适的数量级就可以了，不一定要非常精确。比如发现0.1是比较合适的，那么可以再尝试0.2,0.3…，如果发现0.5不错，可以设学习率为0.5的一半0.25，这样可以使得在后续epoch中，不容易发生震荡。最好的方法是可变学习率，即前期学习率稍大（0.5），后期学习率稍小（0.1）之类的。&lt;/p>
&lt;h1 id="epoch">epoch&lt;/h1>
&lt;p>no-improvement-in-ten rule，就是说如果模型在最近的10个epoch中，验证集的accuracy都没有提高，则可以stop了。在早期实验中可以这么做，后续精细优化时可以改变ten，比如no-improvement-in-20/30等。&lt;/p>
&lt;h1 id="正则化参数">正则化参数\(\lambda\)&lt;/h1>
&lt;p>首先不要正则（\(\lambda=0\)），使用上面提到的方法确定学习率\(\eta\)，在确定的学习率情况下，正则\(\lambda=1\)开始进行优化，比如每次乘以10或者除以10，观察验证集上的accuracy指标，找到正则化所在的合适的数量级，然后再fine-tune。&lt;/p>
&lt;h1 id="mini-batch-size">Mini-batch size&lt;/h1>
&lt;p>太小了，无法利用现有软件包的矩阵操作的优势，速度会很慢。极端情况下，如果mini-batch size=1，就是说每次只用一个sample做BP，则100次mini-batch=1会比一次mini-batch=100操作慢很多，因为很多软件包对矩阵操作有优化，而没有对for循环优化。太大了，则一次BP要很久，参数更新的次数也比较少。&lt;/p>
&lt;h1 id="其他技术">其他技术&lt;/h1>
&lt;h2 id="随机梯度下降sgd的变种">随机梯度下降SGD的变种&lt;/h2>
&lt;h3 id="海森矩阵法">海森矩阵法&lt;/h3>
&lt;p>SGD优化的目标就是最小化损失函数\(C\)，\(C\)是所有参数\(w = w_1, w_2, \ldots\)的函数，即\(C=C(w)\)。希望能够通过改变\(w\)，不断最小化\(C\)，即找一个\(\Delta w\)，使得\(C(w+\Delta w)\)最小化。把\(C(w+\Delta w)\)泰勒展开得到：&lt;/p>
$$\begin{eqnarray}C(w+\Delta w) &amp; = &amp; C(w) + \sum_j \frac{\partial C}{\partial w_j} \Delta w_j\nonumber \\ &amp; &amp; + \frac{1}{2} \sum_{jk} \Delta w_j \frac{\partial^2 C}{\partial w_j\partial w_k} \Delta w_k + \ldots\tag{1}\end{eqnarray}$$&lt;p>写成矩阵形式就是：&lt;/p></description></item><item><title>Neural Networks and Deep Learning（三·二）过拟合与正则化</title><link>https://bitjoy.net/posts/2019-03-24-neural-networks-and-deep-learning-3-2-overfitting-and-regularization/</link><pubDate>Sun, 24 Mar 2019 12:14:22 +0800</pubDate><guid>https://bitjoy.net/posts/2019-03-24-neural-networks-and-deep-learning-3-2-overfitting-and-regularization/</guid><description>&lt;h1 id="过拟合介绍">过拟合介绍&lt;/h1>
&lt;p>首先介绍一下神经网络中不同数据集的功能，包括训练集、验证集和测试集。&lt;/p>
&lt;p>训练集是用来训练网络参数的。当觉得在训练集上训练得差不多时，就可以在验证集上进行测试，如果验证集上的性能不好，则需要调整网络结构或者超参数，重新在训练集上训练。所以本质上验证集指导训练过程，也参与了训练和调参。为了防止网络对验证集过拟合，当网络在训练集和验证集上表现都不错时，就可以在测试集上进行测试了。测试集上的性能代表了模型的最终性能。&lt;/p>
&lt;p>当然如果发现网络在测试集上性能不好，可能还会反过来去优化网络，重新训练和验证，这么说测试集最终也变相参与了调优。如果一直这么推下去的话，就没完没了了，所以一般还是认为用验证集对模型进行优化，用测试集对模型性能进行测试。&lt;/p>
&lt;p>过拟合的含义就是网络在训练集上性能很好，但是在验证集（或者测试集）上的性能较差，这说明网络在训练集上训练过头了，对训练集产生了过拟合。为了便于叙述，本文没有验证集，直接使用测试集作为验证集对模型进行调优，所以主要考察网络在训练集和测试集上的性能表现。&lt;/p>
&lt;p>判断网络是否过拟合的方法就是观察网络在训练集和测试集上的accuracy和loss的变化曲线。对于accuracy，如果训练集的accuracy很高接近100%且收敛了，但测试集上的accuracy和训练集上的accuracy相差较大也收敛了（如下图收敛到82%左右），说明网络过拟合了。对于loss，如果训练集的loss一直在下降，但测试集的loss先下降后又上升，也说明网络过拟合了。这两种现象，虽然指标不同，但含义是一样的，即网络在训练集上的性能一直在提高甚至到完美水平，但在测试集上的性能提高到一定水平后不再变化甚至下降了。&lt;/p>
&lt;p>不过下面几张图反应的过拟合epoch时间可能不一样，比如对于测试集上的accuracy，可能在280左右过拟合，但是对于测试集上的loss，在15和280左右都可以认为是过拟合了，尤其是15，loss最低，之后loss反升，可以认为是一个合理的过拟合的点。具体哪个epoch之后过拟合，取决于问题本身关注哪个指标，比如MNIST分类问题，可能关注分类accuracy，所以可重点关注测试集上的accuracy那个图，认为是280左右过拟合，因为200~280的accuracy还一直有提升，虽然提升很有限。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/overfitting4.png">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/overfitting2.png">&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/overfitting1.png">&lt;/td>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/overfitting3.png">&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>应对过拟合最好的方法就是增加训练数据，如果能把所有可能的数据都收集到，对所有数据产生过拟合，那相当于对所有数据都能预测得很好，那问题本质上已经解决了。&lt;/p>
&lt;p>但是，在实际应用场景中，不可能收集到所有数据，而且数据往往是严重不足的，此时，应对过拟合主要有三种方法：正则化、Dropout和数据增强，下面分别介绍这三个部分。&lt;/p>
&lt;h1 id="正则化">正则化&lt;/h1>
&lt;p>正则化的思路就是修改损失函数，使损失函数考虑模型复杂度。考虑正则化的损失函数的通用公式如下：&lt;/p>
$$\begin{eqnarray} C = C_0(w,x,y) + \lambda\Omega(w)\tag{1}\end{eqnarray}$$&lt;p>其中\(C_0\)为原始的没有正则化项的损失函数，比如MSE或者交叉熵损失等，\(\Omega(w)\)表示正则化项，即用来惩罚模型复杂度的，\(\lambda\)表示正则化参数，用来平衡\(C_0\)和\(\Omega(w)\)的重要性。&lt;/p>
&lt;p>正则化又分为L2正则和L1正则，它们很类似，先详细介绍下L2正则。&lt;/p>
&lt;p>举个例子，L2正则化后的损失函数如下：&lt;/p>
$$\begin{eqnarray} C = C_0 + \frac{\lambda}{2n}\sum_w w^2,\tag{2}\end{eqnarray}$$&lt;p>前半部分就是普通的损失函数（比如MSE或者交叉熵损失），后半部分就是L2正则。L2正则是对网络中的所有权重\(w\)求平方和（\(\vec w\)的L2范数，所以叫L2正则），然后除以\(2n\)，其中\(n\)是训练样本数，除以2应该是为了后面求导方便。&lt;/p>
&lt;p>(2)式的直观含义是，\(\min C\)的过程中，我不但希望损失函数本身\(C_0\)足够小，还希望网络的权重\(w\)也比较小，最好不要出现很大的\(w\)。如果\(\lambda\)越大，表示正则化越厉害，对大的\(w\)惩罚越严重。&lt;/p>
&lt;p>加入L2正则后的梯度也很容易计算，如下：&lt;/p>
$$\begin{eqnarray}\frac{\partial C}{\partial w} &amp; = &amp; \frac{\partial C_0}{\partial w} + \frac{\lambda}{n} w \tag{3}\\ \frac{\partial C}{\partial b} &amp; = &amp; \frac{\partial C_0}{\partial b}.\tag{4}\end{eqnarray}$$&lt;p>对应的参数更新公式如下：&lt;/p>
$$\begin{eqnarray}b &amp; \rightarrow &amp; b -\eta \frac{\partial C_0}{\partial b}.\tag{5}\end{eqnarray}$$$$\begin{eqnarray} w &amp; \rightarrow &amp; w-\eta \frac{\partial C_0}{\partial w}-\frac{\eta \lambda}{n} w \tag{6}\\ &amp; = &amp; \left(1-\frac{\eta \lambda}{n}\right) w -\eta \frac{\partial C_0}{\partial w}. \tag{7}\end{eqnarray}$$&lt;p>由(5)可知，偏移量\(b\)的梯度更新和没有正则化时是一样的，因为正则化并没有惩罚\(b\)，这个后面会解释为什么。由(7)可知，对\(w\)的梯度更新和没有正则化时很类似，只不过需要先对\(w\)进行缩放，缩放因子为\(1-\frac{\eta\lambda}{n}\)，因为训练样本\(n\)往往很大，所以缩放因子在(0,1)，即先对\(w\)进行缩小，然后正常梯度下降，这种操作也被称为权值衰减。\(\lambda\)最好根据\(n\)的大小进行调整，如果\(n\)非常大的话，\(\lambda\)最好也大一些，否则权值衰减因子就会很小，正则化效果就不明显。&lt;/p></description></item><item><title>Neural Networks and Deep Learning（三·一）梯度消失</title><link>https://bitjoy.net/posts/2019-03-18-neural-networks-and-deep-learning-3-1-gradient-vanishing/</link><pubDate>Mon, 18 Mar 2019 10:33:32 +0800</pubDate><guid>https://bitjoy.net/posts/2019-03-18-neural-networks-and-deep-learning-3-1-gradient-vanishing/</guid><description>&lt;p>原文的第三章内容较多，本博客将分三个部分进行介绍：梯度消失、过拟合与正则化、权重初始化及其他，首先介绍梯度消失问题。&lt;/p>
&lt;p>为简单起见，假设网络只包含一个输入和一个神经元，网络的损失是均方误差损失MSE，激活函数是Sigmoid函数。则该网络的参数只包含权重\(w\)和偏移量\(b\)。我们想训练这个网络，使得当输入为1时，输出0。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/tikz28.png">&lt;/p>
&lt;p>假设我们随机初始化\(w_0=0.6\)，\(b_0=0.9\)，则网络的损失随着训练的epoch变化曲线如下，看起来挺好的，一开始损失下降很快，随着epoch增加，损失下降逐渐平缓，直至收敛。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-03-18-neural-networks-and-deep-learning-3-1-gradient-vanishing/ch3.1.png">&lt;/p>
&lt;p>但是，如果随机初始化\(w_0=2.0\)，\(b_0=2.0\)，则网络的损失一开始下降得很缓慢，要训练到快200个epoch时，损失才快速下降。可以看到同样是300个epoch，由于初始化权重的差别，损失下降的趋势完全不一样，而且对于下面这种情况，到300个epoch时，损失还有下降的空间，所以期望的output不如上面的接近目标值0。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2019-03-18-neural-networks-and-deep-learning-3-1-gradient-vanishing/ch3.2.png">&lt;/p>
&lt;p>为什么同样的网络，只是因为初始化权重的差异，损失的变化曲线却相差这么多呢，这和我们选择的损失函数与激活函数有关。&lt;/p>
&lt;p>回顾一下，我们在&lt;a href="https://bitjoy.net/posts/2018-12-14-neutral-networks-and-deep-learning-2-bp/">上一讲&lt;/a>的末尾介绍到如果损失函数是MSE且激活函数是Sigmoid时，有\(\delta^L = (a^L-y) \odot \{\sigma(z^L)(1-\sigma(z^L))\}\)，又因为网络只有一个神经元，所以梯度如下：&lt;/p>
$$\begin{eqnarray}\frac{\partial C}{\partial w} &amp; = &amp; (a-y)\sigma'(z) x = a \sigma'(z),\tag{1}\\\frac{\partial C}{\partial b} &amp; = &amp; (a-y)\sigma'(z) = a \sigma'(z)\tag{2}\end{eqnarray}$$&lt;p>其中第二个等号是把\(x=1\)和\(y=0\)带入得到的。由此可见，误差对两个参数\(w\)和\(b\)的梯度都和激活函数的导数有关，因为激活函数是Sigmoid，当神经元的输出接近0或1时，梯度几乎为0，误差反向传播就会非常慢，导致上图出现损失下降非常慢的现象。这就是梯度消失的原因。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://upload.wikimedia.org/wikipedia/commons/8/88/Logistic-curve.svg">&lt;/p>
&lt;p>为了解决这个问题，我们可以采取两种策略，一是替换损失函数，一是替换激活函数。&lt;/p>
&lt;p>第一种方法是将MSE的损失函数替换为交叉熵损失函数，激活函数依然是Sigmoid。我们考虑一个比本文开头更复杂的网络，仍然是一个输出神经元，但包含多个输入神经元。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/tikz29.png">&lt;/p>
&lt;p>此时，交叉熵损失函数定义如下，其中的\(n\)表示训练样本数，\(\frac{1}{n}\sum_x\)表示对所有输入样本\(x\)的交叉熵损失求均值。&lt;/p>
$$\begin{eqnarray}C = -\frac{1}{n} \sum_x \left[y \ln a + (1-y ) \ln (1-a) \right]\tag{3}\end{eqnarray}$$&lt;p>我们首先考察为什么(3)可以是一个损失函数，损失函数需要满足如下两个条件：&lt;/p>
&lt;ol>
&lt;li>非负；&lt;/li>
&lt;li>当网络输出和目标答案越接近，损失越小；反之损失越大。&lt;/li>
&lt;/ol>
&lt;p>简单代入几组不同的样本很容易验证交叉熵满足上述两个条件 ，所以交叉熵可以作为一个损失函数。&lt;/p>
&lt;p>下面我们再考察一下为什么交叉熵损失函数+Sigmoid激活函数可以解决梯度消失的问题。首先推导交叉熵损失\(C\)对权重\(w_j\)和\(b\)的梯度：&lt;/p>
$$\begin{eqnarray}\frac{\partial C}{\partial w_j} &amp; = &amp; -\frac{1}{n} \sum_x \left(\frac{y }{\sigma(z)} -\frac{(1-y)}{1-\sigma(z)} \right)\frac{\partial \sigma}{\partial w_j} \tag{4}\\&amp; = &amp; -\frac{1}{n} \sum_x \left(\frac{y}{\sigma(z)}-\frac{(1-y)}{1-\sigma(z)} \right)\sigma'(z) x_j\tag{5}\\&amp; = &amp; \frac{1}{n}\sum_x \frac{\sigma'(z) x_j}{\sigma(z) (1-\sigma(z))}(\sigma(z)-y).\tag{6}\end{eqnarray}$$&lt;p>上式分子Sigmoid的导数正好可以和分母抵消，得到：&lt;/p></description></item><item><title>Neural Networks and Deep Learning（二）BP网络</title><link>https://bitjoy.net/posts/2018-12-14-neutral-networks-and-deep-learning-2-bp/</link><pubDate>Fri, 14 Dec 2018 12:18:07 +0800</pubDate><guid>https://bitjoy.net/posts/2018-12-14-neutral-networks-and-deep-learning-2-bp/</guid><description>&lt;p>这一讲介绍误差反向传播（backpropagation）网络，简称BP网络。&lt;/p>
&lt;p>以上一讲介绍的MNIST手写数字图片分类问题为研究对象，首先明确输入输出：输入就是一张28×28的手写数字图片，展开后可以表示成一个长度为784的向量；输出可以表示为一个长度为10的one-hot向量，比如输入是一张“3”的图片，则输出向量为(0,0,0,1,0,0,0,0,0,0,0)。&lt;/p>
&lt;p>然后构造一个如下的三层全连接网络。第一层为输入层，包含784个神经元，正好对应输入的一张28×28的图片。第二层为隐藏层，假设隐藏层有15个神经元。第三层为输出层，正好10个神经元，对应该图片的one-hot结果。&lt;/p>
&lt;p>全连接网络表示上一层的每个神经元都和下一层的每个神经元有连接，即每个神经元的输入来自上一层所有神经元的输出，每个神经元的输出连接到下一层的所有神经元。每条连边上都有一个权重w。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/tikz12.png">&lt;/p>
&lt;p>每个神经元执行的操作非常简单，就是把跟它连接的每个输入乘以边上的权重，然后累加起来。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/tikz0.png">&lt;/p>
&lt;p>比如上面的一个神经元，它的输出就是：&lt;/p>
$$\begin{eqnarray}\mbox{output} = \left\{ \begin{array}{ll}0 &amp; \mbox{if} \sum_j w_j x_j \leq \mbox{ threshold} \\1 &amp; \mbox{if} \sum_j w_j x_j > \mbox{threshold}\end{array}\right.\tag{1}\end{eqnarray}$$&lt;p>其中的threshold就是该神经元激活的阈值，如果累加值超过threshold，则该神经元被激活，输出为1，否则为0。这就是最原始的感知机网络。感知机网络也可以写成如下的向量形式，用激活阈值b代替threshold，然后移到左边。神经网络中，每条边具有权重w，每个神经元具有激活阈值b。&lt;/p>
$$\begin{eqnarray}\mbox{output} = \left\{ \begin{array}{ll} 0 &amp; \mbox{if } w\cdot x + b \leq 0 \\1 &amp; \mbox{if } w\cdot x + b > 0\end{array}\right.\tag{2}\end{eqnarray}$$&lt;p>&lt;img loading="lazy" src="https://upload.wikimedia.org/wikipedia/commons/d/d9/Dirac_distribution_CDF.svg">
&lt;img loading="lazy" src="https://upload.wikimedia.org/wikipedia/commons/8/88/Logistic-curve.svg">&lt;/p>
&lt;p>但是感知机网络的这种激活方式不够灵活，它在threshold左右有一个突变，如果输入或者某个边上的权重稍微有一点变化，输出结果可能就千差万别了。于是后来人们提出了用sigmoid函数来当激活函数，它在0附近的斜率较大，在两边的斜率较小，能达到和阶梯函数类似的效果，而且函数光滑可导。sigmoid的函数形式如下，其中\(z\equiv w \cdot x + b\)为神经元激活之前的值。&lt;/p>
$$\begin{eqnarray} \sigma(z) \equiv \frac{1}{1+e^{-z}}\tag{3}\end{eqnarray}$$&lt;p>sigmmoid函数还有一个优点就是它的导数很好计算，可以用它本身来表示：&lt;/p>
$$\begin{eqnarray}\sigma'(z)=\sigma(z)(1-\sigma(z))\tag{4}\end{eqnarray}$$&lt;p>BP网络的参数就是所有连线上的权重w和所有神经元中的激活阈值b，如果知道这些参数，给定一个输入x，则可以很容易的通过正向传播（feedforward）的方法计算到输出，即不断的执行\(w \cdot x + b\)操作，然后用sigmoid激活，再把上一层的输出传递给下一层作为输入，直到最后一层。&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">feedforward&lt;/span>(self, a):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Return the output of the network if ``a`` is input.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> b, w &lt;span style="color:#f92672">in&lt;/span> zip(self&lt;span style="color:#f92672">.&lt;/span>biases, self&lt;span style="color:#f92672">.&lt;/span>weights):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> a &lt;span style="color:#f92672">=&lt;/span> sigmoid(np&lt;span style="color:#f92672">.&lt;/span>dot(w, a)&lt;span style="color:#f92672">+&lt;/span>b)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> a
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>同时，网络的误差可以用均方误差（mean squared error, MSE）表示，即网络在最后一层的激活值（即网络的输出值）\(a\)和对应训练集输入\(x\)的正确答案\(y(x)\)的差的平方。有\(n\)个输入则误差取平均，\(\dfrac{1}{2}\)是为了后续求导方便。&lt;/p></description></item><item><title>Neural Networks and Deep Learning（一）MNIST数据集介绍</title><link>https://bitjoy.net/posts/2018-11-25-neutral-networks-and-deep-learning-1-mnist-dataset/</link><pubDate>Sun, 25 Nov 2018 11:33:45 +0800</pubDate><guid>https://bitjoy.net/posts/2018-11-25-neutral-networks-and-deep-learning-1-mnist-dataset/</guid><description>&lt;p>最近开始学习神经网络和深度学习，使用的是网上教程：&lt;a href="http://neuralnetworksanddeeplearning.com/">http://neuralnetworksanddeeplearning.com/&lt;/a>，这是学习心得第一讲，介绍经典的MNIST手写数字图片数据集。&lt;/p>
&lt;p>MNIST（Modified National Institute of Standards and Technology database）数据集改编自美国国家标准与技术研究所收集的更大的NIST数据集，该数据集来自250个不同人手写的数字图片，一半是人口普查局的工作人员，一半是高中生。该数据集包括60000张训练集图片和10000张测试集图片，训练集和测试集都提供了正确答案。每张图片都是28×28=784大小的灰度图片，也就是一个28×28的矩阵，里面每个值是一个像素点，值在[0,1]之间，0表示白色，1表示黑色，(0,1)之间表示不同的灰度。下面是该数据集中的一些手写数字图片，可以有一个感性的认识。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/neuralnetworksanddeeplearning.com/images/digits_separate.png">&lt;/p>
&lt;p>MNIST数据集可以在Yann LeCun的网站上下载到：&lt;a href="http://yann.lecun.com/exdb/mnist/">http://yann.lecun.com/exdb/mnist/&lt;/a>，但是他提供的MNIST数据集格式比较复杂，需要自己写代码进行解析。目前很多深度学习框架都自带了MNIST数据集，比较流行的是转换为pkl格式的版本：&lt;a href="http://deeplearning.net/data/mnist/mnist.pkl.gz">http://deeplearning.net/data/mnist/mnist.pkl.gz&lt;/a>，该版本把原始的60000张训练集进一步划分成了50000张小训练集和10000张验证集，下面以这个版本为例进行介绍。&lt;/p>
&lt;p>pkl是python内置的一种格式，可以将python的各种数据结构序列化存储到磁盘中，需要时又可以读取并反序列化到内存中。mnist.pkl.gz做了两次操作，先pkl序列化，再gz压缩存储，所以要读取该文件，需要先解压再反序列化，在python3中，读取mnist.pkl.gz的方式如下：&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> pickle
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> gzip
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>f &lt;span style="color:#f92672">=&lt;/span> gzip&lt;span style="color:#f92672">.&lt;/span>open(&lt;span style="color:#960050;background-color:#1e0010">‘&lt;/span>&lt;span style="color:#f92672">../&lt;/span>data&lt;span style="color:#f92672">/&lt;/span>mnist&lt;span style="color:#f92672">.&lt;/span>pkl&lt;span style="color:#f92672">.&lt;/span>gz&lt;span style="color:#960050;background-color:#1e0010">’&lt;/span>, &lt;span style="color:#960050;background-color:#1e0010">‘&lt;/span>rb&lt;span style="color:#960050;background-color:#1e0010">’&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>training_data, validation_data, test_data &lt;span style="color:#f92672">=&lt;/span> pickle&lt;span style="color:#f92672">.&lt;/span>load(f, encoding&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">’&lt;/span>bytes&lt;span style="color:#960050;background-color:#1e0010">’&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>f&lt;span style="color:#f92672">.&lt;/span>close()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样就得到了训练集、验证集和测试集。将数据集序列化到文件中的方法也很简单，需要注意的是pickle在序列化和反序列化时有不同的协议，可以用protocol参数进行设置。&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>dataset&lt;span style="color:#f92672">=&lt;/span>[training_data, validation_data, test_data]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>f&lt;span style="color:#f92672">=&lt;/span>gzip&lt;span style="color:#f92672">.&lt;/span>open(&lt;span style="color:#960050;background-color:#1e0010">‘&lt;/span>&lt;span style="color:#f92672">../&lt;/span>data&lt;span style="color:#f92672">/&lt;/span>mnist3&lt;span style="color:#f92672">.&lt;/span>pkl&lt;span style="color:#f92672">.&lt;/span>gz&lt;span style="color:#960050;background-color:#1e0010">’&lt;/span>,&lt;span style="color:#960050;background-color:#1e0010">’&lt;/span>wb&lt;span style="color:#960050;background-color:#1e0010">’&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>pickle&lt;span style="color:#f92672">.&lt;/span>dump(dataset,f,protocol&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">3&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>f&lt;span style="color:#f92672">.&lt;/span>close()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我们从mnist.pkl.gz读取到的training_data, validation_data, test_data这三个数据的结构是一样的，每个都是一个二维的tuple。以training_data为例，training_data[0]是训练样本，是一个50000×784的矩阵，表示有50000个训练样本，每个训练样本是一个784的一维数组，784就是把一张28×28的图片展开reshape成的一维数组；training_data[1]是训练样本对应的类标号，大小为50000的一维数组，每个值为0~9中的某个数，表示对应样本的数字标号。&lt;/p></description></item><item><title>Ubuntu下使用VSCode连接Github</title><link>https://bitjoy.net/posts/2018-11-13-access-github-from-vscode-in-ubuntu/</link><pubDate>Tue, 13 Nov 2018 11:09:48 +0800</pubDate><guid>https://bitjoy.net/posts/2018-11-13-access-github-from-vscode-in-ubuntu/</guid><description>&lt;p>VSCode是微软开源的一个很强大的IDE，可以支持几乎所有编程语言，而且是跨平台的，Linux用户终于可以用上宇宙最强IDE了。我最近在使用VSCode编写调试Python项目，其调试功能很强大，和VS上调试C++的感觉是一样的，强烈推荐。&lt;/p>
&lt;p>VSCode还可以连接Github，进行版本控制。下面以我最近学习的深度学习项目为例，介绍下怎样在Ubuntu下使用VSCode连接Github。以我fork的repo为例：&lt;a href="https://github.com/01joy/neural-networks-and-deep-learning">https://github.com/01joy/neural-networks-and-deep-learning&lt;/a>。&lt;/p>
&lt;p>连接Github有两种方式，一种是HTTPS，另一种是SSH，在每个repo页面的右边，有一个Clone or download按钮，可以获取到这两种连接方式的地址。HTTPS方式和网址类似，以HTTPS开头；SSH方式以git@githu.com开头。使用HTTPS连接比较简单，但是每次push的时候需要输入用户名和密码，比较麻烦，如果想记住密码，需要把用户名和密码以明文的形式保存到一个文件中，个人感觉不方便且不安全。下面以SSH连接为例进行介绍。&lt;/p>
&lt;ol>
&lt;li>首先设置Github提交时的用户名和密码，一般设置成全局的：&lt;a href="https://help.github.com/articles/setting-your-username-in-git/">https://help.github.com/articles/setting-your-username-in-git/&lt;/a>、&lt;a href="https://help.github.com/articles/setting-your-commit-email-address-in-git/">https://help.github.com/articles/setting-your-commit-email-address-in-git/&lt;/a>&lt;/li>
&lt;li>生成一对新的SSH公钥和私钥，并添加到ssh-agent中。注意生成的时候需要输入passphrase，这个passphrase不是Github的密码，自己随便取一个记住就好。&lt;a href="https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/">https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/&lt;/a>&lt;/li>
&lt;li>把SSH的公钥添加到Github账号中：&lt;a href="https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/">https://help.github.com/articles/adding-a-new-ssh-key-to-your-github-account/&lt;/a>&lt;/li>
&lt;li>测试SSH连接是否成功：&lt;a href="https://help.github.com/articles/testing-your-ssh-connection/">https://help.github.com/articles/testing-your-ssh-connection/&lt;/a>&lt;/li>
&lt;li>（可选）修改SSH密码，即第1步设置的passphrase：&lt;a href="https://help.github.com/articles/working-with-ssh-key-passphrases/">https://help.github.com/articles/working-with-ssh-key-passphrases/&lt;/a>&lt;/li>
&lt;li>到这里，本级就能通过SSH连接Github了。&lt;/li>
&lt;/ol>
&lt;p>如果没有安装VSCode，可以直接通过Ubuntu的终端连接Github，步骤如下：&lt;/p>
&lt;ol>
&lt;li>在本地创建一个和远程repo名称一样的空文件夹&lt;/li>
&lt;li>终端cd到该文件夹内&lt;/li>
&lt;li>git init # 在该文件夹内初始化&lt;/li>
&lt;li>git remote add origin &lt;a href="mailto:git@github.com">git@github.com&lt;/a>:01joy/notes-on-writing.git # 使用repo的SSH地址&lt;/li>
&lt;li>git pull origin master # 把远程代码拉到本地&lt;/li>
&lt;li>修改代码&lt;/li>
&lt;li>git add . # 在根目录执行，添加所有修改&lt;/li>
&lt;li>git commit -m ‘comments’ # commit第7步添加的修改&lt;/li>
&lt;li>git push origin master # 把第8步发布到远程&lt;/li>
&lt;/ol>
&lt;p>如果安装了VSCode，其实和直接用终端是一样的，在菜单栏的Terminal下新建一个终端，在这个终端内执行上述代码，如果在第4步出现”Enter password to unlock the private key”时，输入创建SSH时第2步的密码即可，只需一次，下次就不用再输入密码了。点击File的Open Folder打开本地repo文件夹。点击VSCode左边栏的Explorer可以在编辑器下修改代码。切换到左边栏的Source Control可以进行Git相关操作，修改的文件右边会出现一个M，点击这个M会出现diff视图；Source Control左边的右上角有三个点，点击这个按钮会出现很多Git操作，包括commit、push等，其实相当于调用上述代码，效果是一样的。&lt;/p>
&lt;p>VSCode快捷键Ctrl+Shift+P会出现命令窗口，在里面输入commit、push等会出现相关操作的，能起到一定的加速效果，当然也可以自定义快捷键。&lt;/p>
&lt;p>Have Fun!&lt;/p></description></item><item><title>Word2016批注行距太大的问题</title><link>https://bitjoy.net/posts/2018-09-01-the-line-space-problem-of-annotation-in-word-2016/</link><pubDate>Sat, 01 Sep 2018 10:53:25 +0800</pubDate><guid>https://bitjoy.net/posts/2018-09-01-the-line-space-problem-of-annotation-in-word-2016/</guid><description>&lt;p>最近导师给我批注文章，说我的Word文档的批注行距极大，从入学到现在一直都是如此，对他造成了极大的困扰，希望我能解决这个问题。&lt;/p>
&lt;p>但是我自己用Word2016查看导师的批注，看不出行距极大的问题，显示完全是正常的。后来猜测导师用的是旧版的Word2010，于是在虚拟机中安装了Word2010，进行测试。&lt;/p>
&lt;p>经过长时间的Debug，终于发现问题所在。Word针对批注有一个默认的样式，为“&lt;strong>批注文字（使用前隐藏）&lt;/strong>”，可以点击样式右下角的箭头，或者直接按快捷键Ctrl+Shift+Alt+S调出样式窗口。然后点击底部的管理样式就可以看到所有的样式了。有意思的是，批注文字样式默认是隐藏的，所以在下图的样式列表中是找不到这个样式的。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2018-09-01-the-line-space-problem-of-annotation-in-word-2016/word2016-open-style.webp">&lt;/p>
&lt;p>找到批注文字样式，点击修改，在弹出的窗口中点击左下角的格式，选择段落，就可以看到批注的默认格式了。段落格式看不出什么异常，Word2016和Word2010的批注段落格式都是一样的，其中“如果定义了文档网格，则对齐到网格”都是默认选中的。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2018-09-01-the-line-space-problem-of-annotation-in-word-2016/word2016-comment-style.webp">&lt;/p>
&lt;p>有意思的是，Word2016和Word2010对网格的默认设置却不一样。在布局、页面设置中点击右下角的箭头，打开页面设置对话框。切换到文档网格选项卡。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2018-09-01-the-line-space-problem-of-annotation-in-word-2016/word2016-open-page-setting.webp">&lt;/p>
&lt;p>Word2016默认指定了行网格，而Word2010默认却是无网格。因为批注文字样式中选中了“&lt;strong>如果定义了文档网格，则对齐到网格&lt;/strong>”这个选项，Word2016默认指定了行网格，所以批注文字会对齐到行网格，导致行间距太大，Word2010默认没有指定任何网格，所以其批注文字的行间距是正常。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2018-09-01-the-line-space-problem-of-annotation-in-word-2016/word2016-doc-grid.webp">
Word2016文档网格，默认指定了行网格&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2018-09-01-the-line-space-problem-of-annotation-in-word-2016/word2010-doc-grid.webp">
Word2010文档网格，默认无网格&lt;/p>
&lt;p>解决办法就是，修改Word2016的设置，选中“无网格”，并设置为默认值，这样以后新建的Word文档默认都是无网格，批注的行间距也就正常了。&lt;/p></description></item><item><title>2017年终总结</title><link>https://bitjoy.net/posts/2018-02-18-summary-of-2017/</link><pubDate>Sun, 18 Feb 2018 21:00:20 +0800</pubDate><guid>https://bitjoy.net/posts/2018-02-18-summary-of-2017/</guid><description>&lt;p>2017年是目前为止最折腾的一年。&lt;/p>
&lt;h1 id="科研工作">科研工作&lt;/h1>
&lt;p>今年的科研任务包括两个方面，一方面是pLink2软件的完善和发布，另一方面是完成pLink2文章初稿。软件方面，上半年忙着修改完善算法，下半年重写界面代码，修改各种bug并反复测试。最终在12月31日晚通过邮件正式发布。文章方面，元旦软件发布之后，一月份抽两周时间完成了初稿，算是自己的第一篇全英文初稿，正文加附录接近一万词。不过因为有一个实验结果不太好，还需要接着完善算法，文字也很稚嫩，需要反复修改。希望能在2018年上半年投出去。&lt;/p>
&lt;h1 id="个人提高">个人提高&lt;/h1>
&lt;p>上半年忙着找工作，一直在刷题看书攒面经，经过自己的不懈努力，收获了微软、百度、头条、Face++等心仪的Offer。在确定自己找工作没问题之后，被李沐的博客“忽悠”，下半年华丽丽的转博了，赶在了2015级最后一次转博前夕。&lt;/p>
&lt;p>因为找工作，看了7本专业书；因为有Kindle，以及忙里偷闲，竟也看了9本非专业书。元旦完成软件发布任务之后，奖励自己去电影院连看了一整天的电影，这种休假方式也是蛮奇葩的，今年累计去电影院看的电影数已经达到16了。上半年和欣欣看了一场“OFO轻睐演唱会”，第一次参加演唱会，现场的感觉和看视频不一样，气氛很热烈，大家都很兴奋，会情不自禁跟着一起唱。国庆第一次一个人远行，去了郑州、登封和杭州，加上12月份参加厦门质谱会议，今年去的第三个城市已经达到了4个。&lt;/p>
&lt;p>运动方面。在两个师兄的帮助以及室友的陪练下，真的学会了蛙泳，今年8月份还拿到了深水证，为此贺老师每个月奖励我100块钱，简单粗暴又有效的奖励机制:-)。临近年底的时候，心血来潮，准备提高乒乓球技术，混入了所里的乒乓球圈子，拜师王老师门下，经过训练以及看视频学习，竟偶尔能赢浩哥了，今年再接再厉。&lt;/p>
&lt;p>今年约好了和哥一起回家过年，给家里买了一台55寸的乐视超级电视，给父母的红包也涨了不少。总体来说，家里在一年一年变好。&lt;/p>
&lt;p>对照年初定的目标，&lt;/p>
&lt;ol>
&lt;li>发表pLink 2文章。只完成了初稿。&lt;/li>
&lt;li>至少完成毕业工作的80%。转博了。&lt;/li>
&lt;li>刷完LeetCode所有简单题和中等题，找工作之前最好刷完两遍。完成。&lt;/li>
&lt;li>找到一个满意的工作。完成，具体请看&lt;a href="https://bitjoy.net/posts/2018-02-04-2018-campus-recruiting/">https://bitjoy.net/posts/2018-02-04-2018-campus-recruiting/&lt;/a>。&lt;/li>
&lt;li>读10本书。完成16：《编程珠玑》、《C++ Primer》、《程序员面试笔试宝典》、《STL源码剖析》、《剑指Offer》、《深度探索C++对象模型》、《编程之美》、《人间失格》、《枪炮、病菌与钢铁》、《杀死一只知更鸟》、《别闹了，费曼先生》、《月亮与六便士》、《突破极限》、《解忧杂货店》、《北京折叠》、《以色列，一个国家的诞生123》。&lt;/li>
&lt;li>去电影院看10场电影。完成16：《爱乐之城》、《摔跤吧！爸爸》、《银河护卫队2》、《战狼2》、《敦刻尔克》、《羞羞的铁拳》、《看不见的客人》、《东方快车谋杀案》、《寻梦环游记》、《帕丁顿熊2》、《芳华》、《解忧杂货店》、《前任3：再见前任》、《无问西东》、《南极之恋》、《太空救援》。&lt;/li>
&lt;li>看一场话剧（音乐会、歌剧等都可以）。完成。2017年7月2日，北京工人体育场，OFO轻睐演唱会。&lt;/li>
&lt;li>学会游泳。完成，学会蛙泳和踩水，年中拿到深水证。&lt;/li>
&lt;li>去第三个城市。完成，国庆去了郑州、登封、杭州，12月份第一次坐飞机去了厦门。&lt;/li>
&lt;/ol>
&lt;p>总体来说，2017年的目标都完成了，而且好几项是超额完成。2018年目标如下：&lt;/p>
&lt;ol>
&lt;li>发表pLink2文章，科研的重中之重。&lt;/li>
&lt;li>开展新课题，或SUMO或深度学习。&lt;/li>
&lt;li>完成博士课程的学习。&lt;/li>
&lt;li>读10本书。&lt;/li>
&lt;li>去电影院看10场电影。&lt;/li>
&lt;li>去北京公园年票范围中的19家公园。&lt;/li>
&lt;li>看一场话剧（音乐会、歌剧等都可以）。&lt;/li>
&lt;li>学会自由泳。&lt;/li>
&lt;li>乒乓球稳赢。&lt;/li>
&lt;li>去第三个城市。&lt;/li>
&lt;li>机动目标，高温假带父母来北京玩。&lt;/li>
&lt;/ol>
&lt;p>突然发现每年的目标都差不多，读万卷书和行万里路是每年都有的保留项目。&lt;/p>
&lt;p>找工作，分手，转博，软件发布，文章写作构成了我2017年的365天，喜忧参半，在跌跌撞撞中前行。2018年要保持一如既往的冲劲，打赢转博之后的第一仗！&lt;/p></description></item><item><title>一念成博</title><link>https://bitjoy.net/posts/2018-02-14-why-phd/</link><pubDate>Wed, 14 Feb 2018 20:28:36 +0800</pubDate><guid>https://bitjoy.net/posts/2018-02-14-why-phd/</guid><description>&lt;p>证明你能做一件事的最好方法就是做成这件事！&lt;/p>
&lt;p>从保研开始，我根本就没打算读博士，心里想的是，好好学习，认真刷题，顺利毕业，高薪就业。&lt;/p>
&lt;p>2016年底，也就是进实验室半年之后，贺老师开始“怂恿”我读博：“贫寒人家子弟，有个高学历，在这个拼爹的时代，更容易出人头地。年轻时多读点儿难读的书，也会更好地在未来人工智能时代生存。”我不为所动。&lt;/p>
&lt;p>2017年春节在家，&lt;a href="http://code.bitjoy.net/">疯狂刷题看书&lt;/a>，为开学后的实习面试以及半年之后的校招面试准备着。&lt;/p>
&lt;p>2017年2月份，开年工作计划会，老师说只要我愿意读博，博士的三个课题都帮我规划好了，要知道我们实验室没有哪个博士生是在三年级之前就确定方向的，大家都是摸着石头过河。&lt;/p>
&lt;p>我还是不为所动，疯狂刷题看书，攒实习面经。&lt;/p>
&lt;p>到了8月，老师最后来信希望我能认真考虑一下读博的事情：“pFind+NIBS是难得的良性成长环境，换一个新环境未见得能成长像现在这么快”。甚至把我的博士女朋友都搬出来了。我跟欣欣聊了聊，思想开始有点动摇了。但是那段时间忙于找工作，没空想太多。&lt;/p>
&lt;p>9月10日教师节，我和欣欣分手，与工作无关，与硕士博士无关。&lt;/p>
&lt;p>后来有一天，当我在对比百度凤巢和微软的Offer时，偶然看到凤巢前辈李沐博士写的一篇博客：&lt;a href="https://zhuanlan.zhihu.com/p/25099638">《博士这五年》&lt;/a>。李沐是上海交大ACM班的，毕业之后去了百度凤巢，但是后来毅然辞职去CMU攻读博士学位。博士五年期间，他不但发表了多篇很牛的paper，而且亲手写了一个类似TensorFlow的深度学习平台MXNet，MXNet现已加入Apache家族，并被Amazon选为官方深度学习框架。他博士答辩的评委有来自Google, Amazon, Apple的AI负责人，阵容非常强大。最后，李沐光荣毕业，加入Amazon。&lt;/p>
&lt;p>这篇博客的结尾在谈到如何选择工作和读博时有一段话，令我印象深刻：“不过我觉得还是会选择读博。赚钱以后还有大把时间可以，但是能花几年时间在某个领域从入门到精通甚至到推动这个领域发展的机会就一次……更重要的是理想和情怀。人一生要工作五十年，为什么不花五年来追求下理想和情怀呢？”。&lt;/p>
&lt;p>看完这篇博客之后，那一整天，我都没心思上班。校招至今，拿了一堆Offer，不能说轻而易举，但是我现在知道拿Offer就跟考试一样，只要准备好，不会差到哪里去。甚至可以说，校招面试比回答贺老师的问题要简单多了。未来可以工作的时间还很长，何不花几年时间来挑战自己呢。当你在面对两难选择时，选择更难的那个，日后你会感谢当初自己的选择。&lt;/p>
&lt;p>人拼了命的工作，到底是为了什么，除了更早的成为工厂里的螺丝钉，成为房奴、孩奴，你还能得到什么。是的，提前三年工作，你也许能赚到100万，能积累更丰富的工作经验和更广阔的人脉。但是，这又怎样，这些东西该来的肯定会来，博士毕业之后，我同样能得到，只是比大多数人晚了一点。&lt;/p>
&lt;p>与其早早毕业，成为一个nobody，还不如再潜心修炼几年，成为somebody。博士这几年，我能得到更加系统全面的训练，pFind+NIBS的良性科研环境，也不可多得。曾经在知乎上看到有个人回答为什么选择读博：“在一个很安全的环境里，父母健在，自己不用操心赚钱养家；有老板给你提供指导、资金；你可以安心研究自己感兴趣的问题；发表的文章也将署上自己的名字，流传后世；习得的技能也将转化为自己的能力；获得的博士学位也将是自己的荣誉…”，这么好的事情，为什么不去做呢？&lt;/p>
&lt;p>那一天之后，我内心几乎就决定要转博了。&lt;/p>
&lt;p>然而，话虽如此，想到要放弃到手的大Offer，继续在实验室里待至少三年；想到免不了要经历大多数博士师兄师姐们经历过的痛苦日子；想到彼时同学们都已经年入x万，有房有车，说不定我日后的面试官就是现在的同学；想到我最亲密的女朋友对我的不信任；想到自己的苦衷无处倾诉…&lt;/p>
&lt;p>那段日子过得很艰难，也许是我到目前为止最低谷的时期。左手是各大互联网公司的Offer，立即可以实现我很多的愿望；右手是若干年未知的博士磨砺。虽然内心知道往右走是正确的，但还是下不了这个狠心。脑海中的两个小人，吵个不停。&lt;/p>
&lt;p>期间和很多在读的、已毕业的博士师兄师姐们聊过，也和很多公司的面试官聊过，当然也和老师父母聊过。得到的回答无外乎三种：读、不读，根据自己的情况决定。这些谈话更像是换了种方式的倾述，我已经记不清具体的内容了，只知道，我做选择的决心越来越坚定了。&lt;/p>
&lt;p>之后恰逢十一长假，给自己放了一个长长的假。规划去了郑州、登封、杭州。了却夙愿，重新开始。&lt;/p>
&lt;p>10月9日，长假结束。我给老师发了一封邮件：“贺老师，您好。非常感谢您的信任和等待，我决定读博了！这将是我人生二十多年来所作的第一个重大决定，我接受挑战！”&lt;/p>
&lt;p>人这一生，说长也长，说短也短，去做你认为对的事情吧，去追求你想要的生活。愿我们都能绽放美丽，不负芳华！&lt;/p></description></item><item><title>伪·2018届校招面经</title><link>https://bitjoy.net/posts/2018-02-04-2018-campus-recruiting/</link><pubDate>Sun, 04 Feb 2018 19:03:36 +0800</pubDate><guid>https://bitjoy.net/posts/2018-02-04-2018-campus-recruiting/</guid><description>伪·2018届校招面经</description></item><item><title>厦门之行</title><link>https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/</link><pubDate>Sat, 16 Dec 2017 10:29:58 +0800</pubDate><guid>https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/</guid><description>&lt;p>2017年12月7日~13日，打着参加“第三届全国质谱分析学术报告会”的旗号，实验室一行11人开启了为期一周的厦门之旅。有关学术交流的总结报告，已经在实验室内部分享了，这篇博客还是来聊聊吃喝玩乐的事吧:)&lt;/p>
&lt;h1 id="第一次乘坐飞机">第一次乘坐飞机&lt;/h1>
&lt;p>本次出行分为飞机组和火车组，大部队说飞机不安全要坐火车，我因为想体验一下乘坐飞机的感受，于是选择了飞机组。第一次坐飞机，体验有三点：1）快。从北京到厦门，横跨整个中国，只花了3个小时，真的好快，除去吃午餐等时间，连一部电影都没看完！2）噪。起飞和降落的时候噪声特别大，平飞的时候噪声也不小，而且快要到目的地时，会有短暂的耳胀，孙老师说第一次坐飞机的人好像都会出现耳胀的情况。还有就是有时候飞机比较颠簸，放在桌子上的水都快晃出来了。3）美。我因为第一次坐飞机，特地选了一个靠窗的位置，想体验一把俯瞰神州大地的感觉。从窗户往下看时，地形地貌和谷歌卫星地图一摸一样，窗外的云就像一朵朵棉花糖，很漂亮，很像各种神话剧中的天庭。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/airport.webp">
飞机上不让开手机，这是在北京首都国际机场起飞前抓拍的照片，以后坐飞机记得带相机&lt;/p>
&lt;h1 id="南国风光">南国风光&lt;/h1>
&lt;p>下飞机之后，立即体验到了厦门这座城市的“热情”，20℃左右的温度，卸下厚重的羽绒服，看着路边的红花绿叶正艳，来到沙滩上，吹吹海风，一身清爽。南方的城市由于经常下雨，街道看起来很干净，柏油马路显露着其原本的黝黑色，路边的叶子绿的发亮，不像在北京被蒙上厚厚的一层灰。路上车辆和红绿灯不多，也很少听到鸣笛，听说厦门全岛禁止鸣笛，斑马线处虽然没有红绿灯，但是有摄像头，强制车辆遇到行人时必须礼让。这种规定在北京是不可能实行的吧。总体而言，厦门彰显了南方城市应有的魅力，和杭州类似，但又更加清新靓丽，是一个休闲生活的好地方。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/xiamen1.webp">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/xiamen2.webp">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/xiamen3.webp">&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;/tbody>
&lt;/table>
&lt;h1 id="厦门大学也许是中国最美丽的大学">厦门大学——也许是中国最美丽的大学&lt;/h1>
&lt;p>本次参会，我们特地提前一天到达，留出时间来参观游览，地点之一就是厦门大学。厦大很美，这是来自一个在武大待了4年的人的由衷赞美。得益于其依山傍水的地理位置，校园内有锦绣的芙蓉湖、美丽的情人谷水库等景点，气氛十分静谧，这点相比于武大小小的鉴湖就好很多。由于地处热带，校园内随处可见高大挺拔的棕榈树，配上古朴的清水墙、多彩的琉璃顶，给人一种别样的美感。如果你要问厦大和武大哪个更美，我只能说她们都美！武大是幽静娴雅的大家闺秀，厦大则像开放热情的新时代女子。武大更加符合中国传统的园林审美，厦大更具风情和现代气息。两校都有山有水，都无愧于中国最美丽的大学的称号！&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/xmu1.webp">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/xmu2.webp">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/xmu3.webp">&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/xmu4.webp">&lt;/td>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/xmu5.webp">&lt;/td>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/xmu6.webp">&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/xmu7.webp">
看见有人在厦大的情人谷水库泛舟，真是“亦可赛艇”呀:)&lt;/p>
&lt;p>厦大还有一个特别的地方——芙蓉隧道，长达一公里，隧道内壁两侧，画满了风格各异，色彩缤纷的涂鸦，号称是中国最文艺的隧道，中国最长的涂鸦隧道。致青春，怀念那种开放、包容的大学氛围。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/doodle1.webp">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/doodle2.webp">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/doodle3.webp">&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/doodle4.webp">&lt;/td>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/doodle5.webp">&lt;/td>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/doodle6.webp">&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/doodle7.webp">&lt;/td>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/doodle8.webp">&lt;/td>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/doodle9.webp">&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h1 id="环游鼓浪屿">环游鼓浪屿&lt;/h1>
&lt;p>会议结束的那天下午，我们前往国家5A级旅游景区鼓浪屿风景名胜区游玩。鼓浪屿，面积不到2平方千米，人口约2万，有“海上花园”、“万国建筑博览会”、“钢琴之岛”之美称。除环岛电动车外不允许机动车辆上岛，因此气氛幽静。2005年《中国国家地理》杂志将鼓浪屿评为“中国最美的城区”第一名。2017年7月在波兰克拉科夫举行的第41届世界遗产大会上被正式列入《世界遗产名录》。鼓浪屿、厦门岛和大陆的关系，就像月球、地球和太阳的关系。&lt;/p>
&lt;p>我们首先从厦门国际邮轮中心乘坐邮轮前往鼓浪屿的三丘田码头，邮轮北边能看到远处横跨鹭江海沧大桥，非常的简洁漂亮，据说是亚洲第一、世界第二（仅次于丹麦）的三跨连续全漂浮钢箱梁悬索桥，代表着20世纪中国建桥水平最高成就。由于是国际邮轮中心，所以也能看到不少外国邮轮，我当时就看到了欧洲和韩国的邮轮经过。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/gulangyu1.webp">&lt;/p>
&lt;p>20分钟之后，就到达了鼓浪屿的三丘田码头。我们沿着鼓浪屿的海岸线，优哉游哉的走着，嬉笑打闹，听着浪涛声，赏着落日余晖，好不惬意。&lt;/p>
&lt;p>我们原本是打算步行环岛游览一周的，但是走到卢戆章雕塑的地方，好多人都走不动了，打算穿过岛屿，提前返回。我个人其实挺想继续前进到鼓声洞的，鼓浪屿名称的由来就是因为在鼓声洞能听到阵阵浪涛拍击岩洞而发出轰隆巨响。&lt;/p>
&lt;p>横穿鼓浪屿的路不是很好走，蜿蜒曲折、上下颠簸，走到龙头路小吃街的时候，大家都饿得不行。小吃街排队的食客特别多，为了早点吃饱，大家分头行动，每两三个人排一家店，所以最终还吃了不少美食，比如沈家闽南肠粉、小马哥起司马铃薯、汤满贯等。有意思的是，在沈家闽南肠粉和小马哥起司马铃薯的中间，有一家土耳其冰淇淋店，两边的食客都排起了长龙，唯独这家店门前冷落鞍马稀。大家吃着美食就开始聊起来了，虽说厦门冬天不冷，但也没有热到对冰淇淋有那么大的渴望。更重要的是，走到此处的游客肯定饿了，首先想到的是填饱肚子，冰淇淋属于饭后甜点，应该算“奢侈品”了，如果这个店换成“土耳其烤肉”，估计能火，哈哈。&lt;/p>
&lt;p>岛上其实还有很多景点，比如风琴博物馆、菽庄花园、日光岩、海底世界等，但是我们都没进去，可能是因为沿途的风景太美了，这些收费的室内景点还不足以吸引我们吧。鼓浪屿的风景太美，可玩、可看的景点太多，半天的时间绝对不够用。下次再去，一定要细细品味。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/gulangyu2.webp">
&lt;a href="http://j.map.baidu.com/M2cPN">http://j.map.baidu.com/M2cPN&lt;/a>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/gulangyu3.webp">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/gulangyu4.webp">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/gulangyu5.webp">&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/gulangyu6.webp">&lt;/td>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/gulangyu7.webp">&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h1 id="厦门植物园">厦门植物园&lt;/h1>
&lt;p>回北京之前，我们去了火车站附近的厦门植物园游玩。厦门植物园也称为万石植物园，背靠五老峰南普陀，集植物景观、自然景观、人文景观于一体，景色相当不错。我们沿路经过了百花厅、奇趣植物区、新碑林、摩崖石刻、长寿峡、半山观景台、多肉植物区、雨林世界、药用植物区，看到了很多奇花异草，整个行程在多肉植物区看到高大的仙人掌时达到高潮，非常值得游览的一个景点。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">厦门植物园石碑&lt;/th>
&lt;th style="text-align: center">类似食人花的猪笼草，开启之后像一个笼子&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/xmbg1.webp">&lt;/td>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/xmbg2.webp">&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">奇丑无比的白花异木棉&lt;/th>
&lt;th style="text-align: center">万石丛中&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/xmbg3.webp">&lt;/td>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/xmbg4.webp">&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">半山观景台，背景是厦门世茂双子塔&lt;/th>
&lt;th style="text-align: center">疯狂生长的仙人掌和仙人球&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/xmbg5.webp">&lt;/td>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/xmbg6.webp">&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">类似毛毛虫的仙人掌。。。&lt;/th>
&lt;th style="text-align: center">放了一个大招，生出许多仙人掌:)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/xmbg7.webp">&lt;/td>
&lt;td style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/xmbg8.webp">&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h1 id="胡吃海喝">胡吃海喝&lt;/h1>
&lt;p>在厦门的这一周，大家都说不是在吃就是在吃的路上，不论是酒店的自助餐还是在外面吃饭，都吃得很不错，我基本上每餐都是十分饱。在酒店的伙食，相对来说，早餐是最好的，午餐和晚餐比较一般，可能是因为早餐酒店提供，午餐和晚餐是会议主办方提供吧，毕竟会议注册费很便宜，伙食也不会太好。不过每餐都有海鲜、厦门特色沙茶面、各种肉、水果、甜点、饮料，相比于北京的食堂是好太多了。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-16-a-trip-to-xiamen/breakfast.webp">
丰盛的早餐&lt;/p>
&lt;p>到厦门的第一天晚上，我和师弟去中山路小吃街逛了逛，结果并没有任何惊喜，感觉全国的小吃街都差不多：烧烤、臭豆腐、各种煎饼包子、粥、饼。中山路的新华书店倒是值得一逛，他们家第三层的书不少，国内外文学、小说、工具书一应俱全，一改我对新华书店只有教科书的旧思想。&lt;/p>
&lt;p>在外面吃的话，每餐必点被称为花蛤的“虫子”，还有另一种称为蛏（cheng）子的“虫子”，和花蛤很像，但是是长方形，且有两条美腿。知乎上有一个回答仔细的辨析了&lt;a href="https://www.zhihu.com/question/25165185/answer/30233547">花蛤、蛏子、蚬子、蚶子、蛤蜊、海瓜子、贝壳的区别&lt;/a>，很有意思。不过话说这些海底动物长得都很奇怪，有些还有很多软体触角，看着让人恶心。席间，有个师兄讲了个笑话，问为啥海底动物长得都这么丑，答案是因为海底没光，反正都看不见，大家就随便长长了:)。&lt;/p>
&lt;p>说到海产品，必不可少的是鱼了，厦门的清蒸金昌鱼非常好吃，味道鲜美，没有小刺，和上次在杭州吃到的西湖醋鱼简直是一个天上，一个地下，强烈推荐。&lt;/p></description></item><item><title>逻辑回归之Python应用实例</title><link>https://bitjoy.net/posts/2017-12-05-logistic-regression-in-python/</link><pubDate>Tue, 05 Dec 2017 21:20:01 +0800</pubDate><guid>https://bitjoy.net/posts/2017-12-05-logistic-regression-in-python/</guid><description>&lt;p>上一篇博客主要介绍了逻辑回归的理论知识，这篇博客咱们用Python机器学习包sklearn中的LogisticRegression做一个分类的实例。&lt;/p>
&lt;p>数据还是学生样本，只有两个特征，分别是两门课的分数score1和score2，类标号y表示这个学生是否能被录取。先上分类效果图：&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-12-05-logistic-regression-in-python/lr_face_3.png">&lt;/p>
&lt;p>完整的Python代码如下：&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 20
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 21
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 22
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 23
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 24
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 25
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 26
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 27
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 28
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 29
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 30
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 31
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 32
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 33
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 34
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 35
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 36
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 37
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 38
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 39
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 40
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 41
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 42
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 43
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 44
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 45
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 46
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 47
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 48
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 49
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 50
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 51
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 52
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 53
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 54
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 55
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 56
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 57
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 58
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 59
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 60
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 61
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 62
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 63
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 64
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 65
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 66
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 67
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 68
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 69
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 70
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 71
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 72
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 73
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 74
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 75
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 76
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 77
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 78
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 79
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 80
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 81
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 82
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 83
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 84
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 85
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 86
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 87
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 88
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 89
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 90
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 91
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 92
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 93
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 94
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 95
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 96
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 97
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 98
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 99
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">100
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">101
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">102
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">103
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">104
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">105
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">106
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">107
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">108
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">109
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">110
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">111
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">112
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">113
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">114
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">115
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">116
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">117
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">118
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">119
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">120
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">121
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">122
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">123
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">124
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">125
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">126
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">127
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">128
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">129
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">130
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">131
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">132
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">133
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">134
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">135
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">136
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">137
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">138
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">139
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">140
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">141
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">142
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">143
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">144
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">145
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># -*- coding: utf-8 -*-&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Created on Wed Nov 08 17:49:41 2017
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">@author: zhenlin
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> numpy &lt;span style="color:#66d9ef">as&lt;/span> np
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> pandas &lt;span style="color:#66d9ef">as&lt;/span> pd
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> sklearn.cross_validation &lt;span style="color:#f92672">import&lt;/span> train_test_split
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> sklearn.linear_model &lt;span style="color:#f92672">import&lt;/span> LogisticRegression
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> matplotlib.pyplot &lt;span style="color:#66d9ef">as&lt;/span> plt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> sklearn.metrics &lt;span style="color:#f92672">import&lt;/span> precision_recall_curve
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> sklearn.metrics &lt;span style="color:#f92672">import&lt;/span> classification_report
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 1. 构造数据&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sample_number &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">200&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 第一个高斯分布参数&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mean1 &lt;span style="color:#f92672">=&lt;/span> [&lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">4&lt;/span>] &lt;span style="color:#75715e"># 两个维度上的均值&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cov1 &lt;span style="color:#f92672">=&lt;/span> [[&lt;span style="color:#ae81ff">5&lt;/span>, &lt;span style="color:#ae81ff">3&lt;/span>], [&lt;span style="color:#ae81ff">3&lt;/span>, &lt;span style="color:#ae81ff">10&lt;/span>]] &lt;span style="color:#75715e"># 两个维度的协方差矩阵，必须满足对称半正定&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 第二个高斯分布参数&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mean2 &lt;span style="color:#f92672">=&lt;/span> [&lt;span style="color:#ae81ff">7&lt;/span>, &lt;span style="color:#ae81ff">5&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cov2 &lt;span style="color:#f92672">=&lt;/span> [[&lt;span style="color:#ae81ff">7&lt;/span>, &lt;span style="color:#ae81ff">2&lt;/span>], [&lt;span style="color:#ae81ff">2&lt;/span>, &lt;span style="color:#ae81ff">15&lt;/span>]]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 从两个二元高斯分布中随机采样数据点&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>class1_x1, class1_x2 &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>random&lt;span style="color:#f92672">.&lt;/span>multivariate_normal(mean1, cov1, sample_number)&lt;span style="color:#f92672">.&lt;/span>T &lt;span style="color:#75715e"># .T表示转置&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>class2_x1, class2_x2 &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>random&lt;span style="color:#f92672">.&lt;/span>multivariate_normal(mean2, cov2, sample_number)&lt;span style="color:#f92672">.&lt;/span>T
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 两个高斯分布对应两个类标号&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>data &lt;span style="color:#f92672">=&lt;/span> [[class1_x1[i],class1_x2[i],&lt;span style="color:#ae81ff">0&lt;/span>] &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> range(sample_number)]&lt;span style="color:#f92672">+&lt;/span>[[class2_x1[i],class2_x2[i],&lt;span style="color:#ae81ff">1&lt;/span>] &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> range(sample_number)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 填充到pandas中&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>data &lt;span style="color:#f92672">=&lt;/span> pd&lt;span style="color:#f92672">.&lt;/span>DataFrame(data,columns&lt;span style="color:#f92672">=&lt;/span>[&lt;span style="color:#e6db74">&amp;#39;score1&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;score2&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;result&amp;#39;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>score_data &lt;span style="color:#f92672">=&lt;/span> data[[&lt;span style="color:#e6db74">&amp;#39;score1&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;score2&amp;#39;&lt;/span>]]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>result_data &lt;span style="color:#f92672">=&lt;/span> data[&lt;span style="color:#e6db74">&amp;#39;result&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 2. 训练模型&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>average_precision &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#75715e"># 平均准确度&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>iters &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">10&lt;/span> &lt;span style="color:#75715e"># 交叉验证次数&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> xrange(iters):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 数据划分，80%用于训练，20%用于预测&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> x_train, x_test, y_train, y_test &lt;span style="color:#f92672">=&lt;/span> train_test_split(score_data, result_data, test_size &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0.2&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 构造默认逻辑回归模型&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> model &lt;span style="color:#f92672">=&lt;/span> LogisticRegression()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 训练&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> model&lt;span style="color:#f92672">.&lt;/span>fit(x_train, y_train)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 预测&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> predict_y &lt;span style="color:#f92672">=&lt;/span> model&lt;span style="color:#f92672">.&lt;/span>predict(x_test)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 计算测试集上的准确度&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> average_precision &lt;span style="color:#f92672">+=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>mean(predict_y &lt;span style="color:#f92672">==&lt;/span> y_test)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>average_precision &lt;span style="color:#f92672">/=&lt;/span> iters
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 3. 绘制分类面 - 法1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>x1_min, x1_max &lt;span style="color:#f92672">=&lt;/span> score_data[&lt;span style="color:#e6db74">&amp;#39;score1&amp;#39;&lt;/span>]&lt;span style="color:#f92672">.&lt;/span>min() &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">.5&lt;/span>, score_data[&lt;span style="color:#e6db74">&amp;#39;score1&amp;#39;&lt;/span>]&lt;span style="color:#f92672">.&lt;/span>max() &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">.5&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">generate_face&lt;/span>(prob):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> y &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>np&lt;span style="color:#f92672">.&lt;/span>log(&lt;span style="color:#ae81ff">1.0&lt;/span> &lt;span style="color:#f92672">/&lt;/span> prob &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1.0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> n &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">500&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> x1 &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>linspace(x1_min, x1_max, n)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># w1x1+w2x2+b=y&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> x2 &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#f92672">-&lt;/span>model&lt;span style="color:#f92672">.&lt;/span>coef_[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#ae81ff">0&lt;/span>] &lt;span style="color:#f92672">/&lt;/span> float(model&lt;span style="color:#f92672">.&lt;/span>coef_[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#ae81ff">1&lt;/span>])) &lt;span style="color:#f92672">*&lt;/span> x1 &lt;span style="color:#f92672">+&lt;/span> (y &lt;span style="color:#f92672">-&lt;/span> model&lt;span style="color:#f92672">.&lt;/span>intercept_) &lt;span style="color:#f92672">/&lt;/span> float(model&lt;span style="color:#f92672">.&lt;/span>coef_[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#ae81ff">1&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> x1, x2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>pos_data &lt;span style="color:#f92672">=&lt;/span> data[data[&lt;span style="color:#e6db74">&amp;#39;result&amp;#39;&lt;/span>] &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>neg_data &lt;span style="color:#f92672">=&lt;/span> data[data[&lt;span style="color:#e6db74">&amp;#39;result&amp;#39;&lt;/span>] &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>scatter(x &lt;span style="color:#f92672">=&lt;/span> pos_data[&lt;span style="color:#e6db74">&amp;#39;score1&amp;#39;&lt;/span>], y &lt;span style="color:#f92672">=&lt;/span> pos_data[&lt;span style="color:#e6db74">&amp;#39;score2&amp;#39;&lt;/span>], color &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;black&amp;#39;&lt;/span>, marker &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;o&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>scatter(x &lt;span style="color:#f92672">=&lt;/span> neg_data[&lt;span style="color:#e6db74">&amp;#39;score1&amp;#39;&lt;/span>], y &lt;span style="color:#f92672">=&lt;/span> neg_data[&lt;span style="color:#e6db74">&amp;#39;score2&amp;#39;&lt;/span>], color &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;red&amp;#39;&lt;/span>, marker &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;*&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>face_04_x1, face_04_x2 &lt;span style="color:#f92672">=&lt;/span> generate_face(&lt;span style="color:#ae81ff">0.4&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>face_05_x1, face_05_x2 &lt;span style="color:#f92672">=&lt;/span> generate_face(&lt;span style="color:#ae81ff">0.5&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>face_06_x1, face_06_x2 &lt;span style="color:#f92672">=&lt;/span> generate_face(&lt;span style="color:#ae81ff">0.6&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>plot(face_04_x1, face_04_x2)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>plot(face_05_x1, face_05_x2)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>plot(face_06_x1, face_06_x2)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>xlim(score_data[&lt;span style="color:#e6db74">&amp;#39;score1&amp;#39;&lt;/span>]&lt;span style="color:#f92672">.&lt;/span>min(), score_data[&lt;span style="color:#e6db74">&amp;#39;score1&amp;#39;&lt;/span>]&lt;span style="color:#f92672">.&lt;/span>max())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>ylim(score_data[&lt;span style="color:#e6db74">&amp;#39;score2&amp;#39;&lt;/span>]&lt;span style="color:#f92672">.&lt;/span>min(), score_data[&lt;span style="color:#e6db74">&amp;#39;score2&amp;#39;&lt;/span>]&lt;span style="color:#f92672">.&lt;/span>max())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>xlabel(&lt;span style="color:#e6db74">&amp;#39;score1&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>ylabel(&lt;span style="color:#e6db74">&amp;#39;score2&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>legend([&lt;span style="color:#e6db74">&amp;#39;prob_threshold = 0.4&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;prob_threshold = 0.5&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;prob_threshold = 0.6&amp;#39;&lt;/span>], loc&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;center left&amp;#39;&lt;/span>, bbox_to_anchor&lt;span style="color:#f92672">=&lt;/span>(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">0.865&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>show()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 4. 绘制分类面 - 法2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>pos_data &lt;span style="color:#f92672">=&lt;/span> data[data[&lt;span style="color:#e6db74">&amp;#39;result&amp;#39;&lt;/span>] &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>neg_data &lt;span style="color:#f92672">=&lt;/span> data[data[&lt;span style="color:#e6db74">&amp;#39;result&amp;#39;&lt;/span>] &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>h &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0.02&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>s1_min, s1_max &lt;span style="color:#f92672">=&lt;/span> score_data[&lt;span style="color:#e6db74">&amp;#39;score1&amp;#39;&lt;/span>]&lt;span style="color:#f92672">.&lt;/span>min() &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">.5&lt;/span>, score_data[&lt;span style="color:#e6db74">&amp;#39;score1&amp;#39;&lt;/span>]&lt;span style="color:#f92672">.&lt;/span>max() &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">.5&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>s2_min, s2_max &lt;span style="color:#f92672">=&lt;/span> score_data[&lt;span style="color:#e6db74">&amp;#39;score2&amp;#39;&lt;/span>]&lt;span style="color:#f92672">.&lt;/span>min() &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">.5&lt;/span>, score_data[&lt;span style="color:#e6db74">&amp;#39;score2&amp;#39;&lt;/span>]&lt;span style="color:#f92672">.&lt;/span>max() &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">.5&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 生成s1在[s1_min, s1_max]，且s2在[s2_min, s2_max]的网格数据点&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># meshgrid含义参见：http://blog.sciencenet.cn/blog-791749-675394.html&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>s1, s2 &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>meshgrid(np&lt;span style="color:#f92672">.&lt;/span>arange(s1_min, s1_max, h), np&lt;span style="color:#f92672">.&lt;/span>arange(s2_min, s2_max, h))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 把两个坐标的值按列拼在一起构成二维数据点&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Z &lt;span style="color:#f92672">=&lt;/span> model&lt;span style="color:#f92672">.&lt;/span>predict(np&lt;span style="color:#f92672">.&lt;/span>c_[s1&lt;span style="color:#f92672">.&lt;/span>ravel(), s2&lt;span style="color:#f92672">.&lt;/span>ravel()])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 绘制边界和散点&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Z &lt;span style="color:#f92672">=&lt;/span> Z&lt;span style="color:#f92672">.&lt;/span>reshape(s1&lt;span style="color:#f92672">.&lt;/span>shape)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 坐标点是(s1[i], s2[i])，对应颜色是Z[i]，颜色主题使用plt.cm.Paired&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>pcolormesh(s1, s2, Z, cmap &lt;span style="color:#f92672">=&lt;/span> plt&lt;span style="color:#f92672">.&lt;/span>cm&lt;span style="color:#f92672">.&lt;/span>Paired)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>scatter(x &lt;span style="color:#f92672">=&lt;/span> pos_data[&lt;span style="color:#e6db74">&amp;#39;score1&amp;#39;&lt;/span>], y &lt;span style="color:#f92672">=&lt;/span> pos_data[&lt;span style="color:#e6db74">&amp;#39;score2&amp;#39;&lt;/span>], color &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;black&amp;#39;&lt;/span>, marker &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;o&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>scatter(x &lt;span style="color:#f92672">=&lt;/span> neg_data[&lt;span style="color:#e6db74">&amp;#39;score1&amp;#39;&lt;/span>], y &lt;span style="color:#f92672">=&lt;/span> neg_data[&lt;span style="color:#e6db74">&amp;#39;score2&amp;#39;&lt;/span>], color &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;red&amp;#39;&lt;/span>, marker &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;*&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>xlim(s1&lt;span style="color:#f92672">.&lt;/span>min(), s1&lt;span style="color:#f92672">.&lt;/span>max())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>ylim(s2&lt;span style="color:#f92672">.&lt;/span>min(), s2&lt;span style="color:#f92672">.&lt;/span>max())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>xlabel(&lt;span style="color:#e6db74">&amp;#39;score1&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>ylabel(&lt;span style="color:#e6db74">&amp;#39;score2&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>show()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 5. 评估模型&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 对于测试数据，模型输出1的概率&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>answer &lt;span style="color:#f92672">=&lt;/span> model&lt;span style="color:#f92672">.&lt;/span>predict_proba(x_test)[:,&lt;span style="color:#ae81ff">1&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 计算不同概率阈值下的P和R&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>precision, recall, thresholds &lt;span style="color:#f92672">=&lt;/span> precision_recall_curve(y_test, answer)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># prob &amp;gt; 0.5的报告为1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>report &lt;span style="color:#f92672">=&lt;/span> answer &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0.5&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(classification_report(y_test, report, target_names &lt;span style="color:#f92672">=&lt;/span> [&lt;span style="color:#e6db74">&amp;#39;neg&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;pos&amp;#39;&lt;/span>]))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#39;average precision: &lt;/span>&lt;span style="color:#e6db74">%f&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#f92672">%&lt;/span>average_precision)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 6. 绘制PRC曲线&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># step阶跃图，在点(recall[i],precision[i])进行跳变&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>step(recall, precision, color&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;b&amp;#39;&lt;/span>, alpha&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0.2&lt;/span>, where&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;post&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 对PRC下方填充颜色&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>fill_between(recall, precision, step&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;post&amp;#39;&lt;/span>, alpha&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0.2&lt;/span>, color&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;b&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>xlabel(&lt;span style="color:#e6db74">&amp;#39;Recall&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>ylabel(&lt;span style="color:#e6db74">&amp;#39;Precision&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>ylim([&lt;span style="color:#ae81ff">0.0&lt;/span>, &lt;span style="color:#ae81ff">1.05&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>xlim([&lt;span style="color:#ae81ff">0.0&lt;/span>, &lt;span style="color:#ae81ff">1.0&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>title(&lt;span style="color:#e6db74">&amp;#39;2-class Precision-Recall curve&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plt&lt;span style="color:#f92672">.&lt;/span>show()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>下面将逐模块介绍代码细节，大神可以略过。&lt;/p></description></item><item><title>初探逻辑回归</title><link>https://bitjoy.net/posts/2017-11-26-introduction-to-logistic-regression/</link><pubDate>Sun, 26 Nov 2017 18:54:44 +0800</pubDate><guid>https://bitjoy.net/posts/2017-11-26-introduction-to-logistic-regression/</guid><description>&lt;p>最近实验室在组织学习&lt;a href="http://open.163.com/special/opencourse/machinelearning.html">NG的机器学习视频&lt;/a>，我也跟进了一下。讲到逻辑回归那一课，一直想不明白，逻辑回归到底是怎样分类的？逻辑回归的分类面在哪里？逻辑回归有像SVM的max margin那样清晰的推导过程吗？为什么需要Sigmoid函数？今天就让我们来一窥逻辑回归的始末。&lt;/p>
&lt;p>假设有一堆学生样本，为了简单起见，只有两个特征，分别是两门课的分数score1和score2，类标号y表示这个学生是否能被录取。可视化如下图，黑点表示y=1即被录取，红点表示y=0即未被录取，可以看到黑点处在score1和score2都比较高的区域。我们的任务就是给定这些训练样本，需要确定一个分类面来划分这两类数据。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-11-26-introduction-to-logistic-regression/lr_samples.png">&lt;/p>
&lt;p>分类面的实质就是\(y=\mathbf{w^T x}+b\)，其中\(\mathbf{w}\)和\(\mathbf{x}\)都是向量，对应到本例中，展开为\(y=w_1x_1+w_2x_2+b\)。所以，寻找分类面的过程就是寻找倾斜度\(\mathbf{w}\)和截距\(b\)。&lt;/p>
&lt;p>因为分类的结果是离散的，只有0或者1，可以用感知机来分类。即&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-11-26-introduction-to-logistic-regression/perceptron.svg">&lt;/p>
&lt;p>但是怎样找这里的\(\mathbf{w}\)和\(b\)使得分类结果最好呢，我们需要定义一个优化的目标函数或者说损失函数。这里只能定义为分类错误的个数，只能一点点挪动超平面来找分类错误最少的超平面了，即只能用暴力枚举的方法来找\(\mathbf{w}\)和\(b\)。&lt;/p>
&lt;p>另一种方法是令我们的分类面算出来的值和真实标号越接近越好，即最小化误差\((\mathbf{w^T}x^{(i)}+b-y^{(i)})^2\)，然后通过梯度下降求解\(\mathbf{w}\)和\(b\)。&lt;/p>
&lt;p>但是这会有一个问题，对于上图数据，可以求到一个比较好的分类面，如下左图。但是如果数据中出现了如下右图右边的一个离群点或者噪声，为了最小化这个点的误差，即\((\mathbf{w^T}x^{(i)}+b-1)^2\)，导致分类面往右偏了，这一偏直接导致很多y=1的样本分类错误。所以这种最小化误差的方法对离群点很敏感。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-11-26-introduction-to-logistic-regression/lr_face_1.png">&lt;/th>
&lt;th style="text-align: center">&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-11-26-introduction-to-logistic-regression/lr_face_2.png">&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;/tbody>
&lt;/table>
&lt;p>假设分类超平面还是\(f(\mathbf{x})=\mathbf{w^T x}+b\)，我们希望的分类效果是这样的：\(f(\mathbf{x})=0\)是分类面；\(f(\mathbf{x})>0\)分类为1，且不管\(f(\mathbf{x})\)多大，都分为1；\(f(\mathbf{x})&lt;0\)分类为0，且不管\(f(\mathbf{x})\)多小，都分为0。&lt;/p>
&lt;p>因为类标号是离散的{0,1}，所以想到把\(f(\mathbf{x})\)映射到[0,1]之间，即\(g(f(\mathbf{x}))\)。为了满足上述条件，\(g(f(\mathbf{x}))\)需要满足：&lt;/p>
&lt;ol>
&lt;li>\(g(0)=0.5\)，即在分类面上无法判断类标号是0还是1&lt;/li>
&lt;li>当\(f(\mathbf{x})>0\)时，\(g(f(\mathbf{x}))>0.5\)&lt;/li>
&lt;li>当\(f(\mathbf{x})\rightarrow+\infty\)，\(g(f(\mathbf{x}))\rightarrow 1\)，且\(g'(f(\mathbf{x}))\rightarrow 0\)，即对于上右图右边的离群点，分类为1，且导数趋近于0，表示对其不敏感&lt;/li>
&lt;li>当\(f(\mathbf{x})&lt;0\)时，\(g(f(\mathbf{x}))&lt;0.5\)&lt;/li>
&lt;li>当\(f(\mathbf{x})\rightarrow-\infty\)，\(g(f(\mathbf{x}))\rightarrow 0\)，且\(g'(f(\mathbf{x}))\rightarrow 0\)，和第3点类似&lt;/li>
&lt;/ol>
&lt;p>满足上述性质的函数之一就是Sigmoid函数，其定义域为\([-\infty,+\infty]\)，值域为[0,1]，正好把原始的函数结果\(f(\mathbf{x})\)映射到了[0,1]，而概率的取值范围正好是[0,1]，所以Sigmoid函数正好可以作为分类为1的概率。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://upload.wikimedia.org/wikipedia/commons/8/88/Logistic-curve.svg">&lt;/p>
&lt;p>所以逻辑回归最终的形式就是：&lt;/p>
$$g(\mathbf{x})=\frac{1}{1+e^{-(\mathbf{w^T x}+b)}}$$&lt;p>分类面依然还是\(f(\mathbf{x})=\mathbf{w^T x}+b=0\)，因为\(f(\mathbf{x})=0\)时，\(g(\mathbf{x})=0.5\)，正好满足上述条件1。&lt;/p>
&lt;p>Sigmoid函数的另一个优点是，它把原始函数值映射到了概率空间，这样就可以用极大似然的方法求解参数\(\mathbf{w}\)和\(b\)。下面用参数\(\mathbf{\theta}\)代表参数\(\mathbf{w}\)和\(b\)，用\(h_{\mathbf{\theta}}(\mathbf{x})\)代表\(g(f(\mathbf{x}))\)。则有：&lt;/p>
$$P(y=1|\mathbf{x};\mathbf{\theta})=h_{\mathbf{\theta}}(\mathbf{x})$$$$P(y=0|\mathbf{x};\mathbf{\theta})=1-h_{\mathbf{\theta}}(\mathbf{x})$$&lt;p>合并成一个式子就是：&lt;/p>
$$P(y|\mathbf{x};\mathbf{\theta})=h_{\mathbf{\theta}}(\mathbf{x})^y(1-h_{\mathbf{\theta}}(\mathbf{x}))^{1-y}$$&lt;p>由于所有样本独立同分布（I.I.D.），似然函数就是&lt;/p>
$$L(\mathbf{\theta})=P(\mathbf{y}|X;\mathbf{\theta})=\prod\limits_{i}P(y^{(i)}|\mathbf{x}^{(i)};\mathbf{\theta})=\prod\limits_{i}h_{\mathbf{\theta}}(\mathbf{x}^{(i)})^{y^{(i)}}(1-h_{\mathbf{\theta}}(\mathbf{x}^{(i)}))^{1-y^{(i)}}$$&lt;p>最大化似然的含义就是，在给定样本\(X\)的情况下，我们想找一个参数\(\mathbf{\theta}\)，使得观测到类标号\(\mathbf{y}\)的概率最大。&lt;/p>
&lt;p>最大化似然等价于最大化log似然，log展开之后就是：&lt;/p>
$$l(\mathbf{\theta})=logL(\mathbf{\theta})=\sum\limits_{i}y^{(i)}logh_{\mathbf{\theta}}(\mathbf{x}^{(i)})+(1-y^{(i)})log(1-h_{\mathbf{\theta}}(\mathbf{x}^{(i)}))$$&lt;p>而很巧的是，最大化log似然，其实等效于最小化log对数损失。对于单个样本，损失为：&lt;/p>
$$cost(h_{\theta}(\mathbf{x}),y) = \begin{cases}-log(h_{\theta}(\mathbf{x})) &amp; \text {if y=1} \\ -log(1-h_{\theta}(\mathbf{x})) &amp; \text{if y=0} \end{cases}$$&lt;p>即如果正确类标号是1，但算出来的\(h_{\theta}(\mathbf{x})\)很接近0的话，则损失\(-log(h_{\theta}(\mathbf{x}))\)就会很大。类标号为0的情况类似。把这两种情况合成一个式子就是：&lt;/p>
$$cost(h_{\theta}(\mathbf{x}),y) = -ylog(h_{\theta}(\mathbf{x})) – (1-y)log(1-h_{\theta}(\mathbf{x}))$$&lt;p>所有样本的损失之和就是：&lt;/p>
$$J(\mathbf{\theta})=cost(h_{\theta}(X),\mathbf{y}) = \sum\limits_{i}-y^{(i)}logh_{\mathbf{\theta}}(\mathbf{x}^{(i)})-(1-y^{(i)})log(1-h_{\mathbf{\theta}}(\mathbf{x}^{(i)}))$$&lt;p>所以最大化对数似然\(\max l(\mathbf{\theta})\)和最小化对数损失\(\min J(\mathbf{\theta})\)是等效的！最优化求解的方法用梯度下降和牛顿法都可以。&lt;/p>
&lt;p>和Sigmoid很像的函数还有很多，比如y=arctan(x)也可以，不过Sigmoid有一个很好的特点，即它的导数可以由自身算出来，\(f'(x)=f(x)(1-f(x))\)。&lt;/p>
&lt;hr>
&lt;p>传统的逻辑回归只能处理二分类问题，那么怎样将其扩展到解决多分类问题呢？有两种方法，一种方法是建立k个二元分类器，比如类标号有A,B,C，则建立3个二元分类器，分别是1）A和非A；2）B和非B；3）C和非C。训练每个2元分类器时，都对所有的数据重新标号为0或1，这样共需要训练k个二元分类器。&lt;/p>
&lt;p>还有一种方法是直接将二元逻辑回归推广到多元回归，即Softmax回归，&lt;a href="http://ufldl.stanford.edu/wiki/index.php/Softmax%E5%9B%9E%E5%BD%92">有关Softmax回归的内容，请参考此博客，非常详细&lt;/a>。简单来说，二元逻辑回归的假设函数是：多元Softmax回归的假设函数在形式上有所不同：其中是模型的参数。请注意这一项对概率分布进行归一化，使得所有概率之和为1。&lt;/p>
&lt;p>Softmax回归的损失函数如下，其实就是logistic回归损失函数的推广：&lt;/p>
&lt;p>二元逻辑回归是多元Softmax回归在k=2时的特例，令k=2并利用Softmax回归参数冗余的特点，可以得到一般形式的二元逻辑回归假设函数，具体可以看原博客。&lt;/p>
&lt;p>面对一个多元分类问题，是选择Softmax回归还是k个二元分类器呢，这取决于你的类别之间是否互斥，如果互斥，可以用Softmax回归，否则，请用k个二元分类器。&lt;/p>
&lt;p>这就是逻辑回归的理论知识，下一篇博客将介绍逻辑回归在Python中的应用。&lt;/p>
&lt;p>参考：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="http://www.cnblogs.com/sparkwen/p/3441197.html">http://www.cnblogs.com/sparkwen/p/3441197.html&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tech.meituan.com/intro_to_logistic_regression.html">https://tech.meituan.com/intro_to_logistic_regression.html&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://blog.csdn.net/bitcarmanlee/article/details/51165444">http://blog.csdn.net/bitcarmanlee/article/details/51165444&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://ufldl.stanford.edu/wiki/index.php/Softmax%E5%9B%9E%E5%BD%92">http://ufldl.stanford.edu/wiki/index.php/Softmax%E5%9B%9E%E5%BD%92&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>2017年国庆旅行——郑州、杭州</title><link>https://bitjoy.net/posts/2017-10-08-2017-solo-travel-in-zhengzhou-and-hangzhou/</link><pubDate>Sun, 08 Oct 2017 10:56:16 +0800</pubDate><guid>https://bitjoy.net/posts/2017-10-08-2017-solo-travel-in-zhengzhou-and-hangzhou/</guid><description>2017年国庆旅行——郑州、杭州</description></item><item><title>Gperftools性能分析工具使用教程</title><link>https://bitjoy.net/posts/2017-02-07-gperftools-tutorial/</link><pubDate>Tue, 07 Feb 2017 23:21:41 +0800</pubDate><guid>https://bitjoy.net/posts/2017-02-07-gperftools-tutorial/</guid><description>&lt;p>&lt;a href="https://bitjoy.net/posts/2017-02-07-introduction-to-performance-analysis-tools-in-linux/">上一篇博客简单介绍了Linux平台下不同性能分析工具的特点&lt;/a>，最后是Google出品的gperftools胜出。今天我们将举一个用gperftools对链接库进行性能分析的例子。&lt;/p>
&lt;p>假设我们创建了一个TestGperftools项目，其中包括一个动态链接库模块complex和主程序main，build文件夹用于存放动态链接库和可执行程序，文件结构如下所示。&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>czl@ubuntu:~/czl/TestGperftools$ tree
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├── build
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├── complex
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ ├── complex.cpp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ ├── complex.h
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ └── Makefile
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├── main
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ ├── main.cpp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ └── Makefile
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>└── Makefile
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">3&lt;/span> directories, &lt;span style="color:#ae81ff">6&lt;/span> files
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>其中complex借用了&lt;a href="http://www.cppfans.com/articles/basecalc/c_pi_10000.asp">这篇博客&lt;/a>的第三个公式计算圆周率π，主要用来模拟一个很耗时的操作。该模块会生成一个libcomplex.so动态链接库供外部调用。其complex.cpp函数如下：&lt;/p></description></item><item><title>Linux性能分析工具简介</title><link>https://bitjoy.net/posts/2017-02-07-introduction-to-performance-analysis-tools-in-linux/</link><pubDate>Tue, 07 Feb 2017 19:15:56 +0800</pubDate><guid>https://bitjoy.net/posts/2017-02-07-introduction-to-performance-analysis-tools-in-linux/</guid><description>&lt;p>这半年一直在研究pLink 2的加速策略，了解了相关的性能分析工具，现记录如下。&lt;/p>
&lt;p>对软件进行加速的技术路线一般为：首先对代码进行性能分析（ performance analysis，也称为 profiling），然后针对性能瓶颈提出优化方案，最后在大数据集上评测加速效果。所以进行性能优化的前提就是准确地测量代码中各个模块的时间消耗。听起来很简单，不就是测量每行代码的运行时间吗，直接用time_t t=clock();不就好了，但是事情并没有那么简单。如果只进行粗粒度的性能分析，比如测量几个大的模块的运行时间，clock()还比较准确，但是如果测量的是运算量比较小的函数调用，而且有大量的小函数调用，clock()就不太准确了。&lt;/p>
&lt;p>比如下面的一段代码，我最开始的性能分析方法是在fun1()~fun3()前后添加time_t t=clock()，然后作差求和的。但是3个fun()加起来的时间居然不等于整个while循环的时间，有将近50%的时间不翼而飞了！&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">while&lt;/span> (true) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (fun1()) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> (&lt;span style="color:#66d9ef">int&lt;/span> i &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; i &lt;span style="color:#f92672">&amp;lt;&lt;/span> k; &lt;span style="color:#f92672">++&lt;/span>k) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (flag1) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fun2();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fun3();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>一种可能是while循环以及内部的for循环本身占用了较多的时间，但是好像不太可能呀。还有一种可能是clock()测量有误，time_t只能精确到秒，clock_t只能精确到毫秒，如果一次fun*()的时间太短，导致一次测量几乎为0，那么多次的while和for循环调用累加的时间也几乎为0，导致实际测量到的fun*()时间远小于真实时间。所以自己用代码进行性能分析可能会有较大的误差，最好借助已有的性能分析工具。&lt;/p></description></item><item><title>2016年终总结</title><link>https://bitjoy.net/posts/2017-01-28-2016-summary/</link><pubDate>Sat, 28 Jan 2017 21:26:42 +0800</pubDate><guid>https://bitjoy.net/posts/2017-01-28-2016-summary/</guid><description>&lt;p>2017农历新年的钟声都已经敲响了，我这2016的年终总结才开始动笔。&lt;/p>
&lt;p>2016年，经历了很多，也成长了很多，遇到了很多曾经以为只会出现在电视剧中的场景，令人开始怀疑这个世界。前几天在朋友圈看到一个同学发的状态，觉得很适合作为这篇年终总结的开端。（同学你要是觉得被侵权了，告诉我，我立马删掉:-)）&lt;/p>
&lt;blockquote>
&lt;p>每个家庭的故事都会是一部长篇史诗。曾经总以为很多情节只会出现在电视剧中，现实的生活很是平淡无味，没有任何波澜，偶尔甚至还会抱怨一下自己不是故事的女主角，其不知现实的生活相比于电视剧，往往是有过之而无不及。
今天哥哥来电话了，从“天津”，一个美丽的谎言，一直在继续，还会坚持很久…
奶奶很开心…
——by angel&lt;/p>&lt;/blockquote>
&lt;p>&lt;strong>关于学习和科研&lt;/strong>。&lt;a href="https://bitjoy.net/posts/2016-08-20-2016-mid-year-summary/">上半年在雁栖湖完成了研一的下半个学期，完完全全的结束了自己的学生时代。&lt;/a>下半年开始进入实验室，直面惨淡的科研。原本以为会由组里的大师兄超哥指导我，没想到中秋前3天，直接接到H Boss的指令，要在中秋前完成一项我从来没做过的评测。不知道怎样设计实验，不知道怎样计算评价指标，甚至连需要评测的软件都不熟。不过好在加班加点完成了。&lt;/p>
&lt;p>&lt;img alt="my-office-cubicle" loading="lazy" src="https://bitjoy.net/posts/2017-01-28-2016-summary/my-office-cubicle.jpg">
凌乱的工位&lt;/p>
&lt;p>从9月到10月中旬，一直在各种数据集上做各种对比评测，基本上一周做完一个评测数据集，完成一份报告，直接提交给H Boss，下一周在做另一个评测的同时，要根据导师的反馈建议修改完上一个评测报告。这一个多月的时间，共完成了5份报告共计14个版本，真的是要吐了。&lt;/p>
&lt;p>后来发现pLink要比对手慢，于是就尝试各种加速策略。开始从外围查找原因，尝试了各种策略，虽然多多少少能加速，但是效果都不完美，有可能对精度有影响。直到12月份，借助谷歌的开源性能分析工具gperftools才找到了软件的性能瓶颈，开始优化并取得了好几倍的加速效果。12月份对软件本身的性能优化大概是我这半年做的唯一一个和计算机本身有点关系的工作吧，可能也是为数不多的我比较享受的一件事。&lt;/p>
&lt;p>整个这半年的工作，都是H Boss御驾亲征，亲自指导，当然这样有利有弊。好处是能直接和H Boss对话，机会难得呀，H那种严于律己、追求完美的品质，H的为人处世、口才都是非常值得我学习的。坦白说，虽然我只是无名小卒一个，但从小到大，真正让我从心里佩服的人没几个，H Boss绝对算是其中之一。当然不便之处也非常明显，首先会感觉特别累和压抑，除了每周一次的面谈，每天的邮件，还经常在晚上11点多收到老师的工作微信。所以这半年确实不轻松，工作日晚上基本都在加班，而且加班到晚上11点也是常事，周末也至少工作一天。几乎没有时间运动，身体素质应该下降了不少。与人的沟通也非常少，好几个师兄师姐都问我为啥看我整天都在工位上坐着，好像从来没有动过。夸张点说就是每天晚上下班要走的时候，才发现自己这一天还没有说话。&lt;/p>
&lt;p>当然半年高强度的工作，也有不少的收获，基本摸清了pLink代码的来龙去脉，也加速了两三倍，自己的表现也稍微得到了老师和同学的肯定。不过我自己还是不太满意的，加速并没有达到理想的效果。&lt;/p>
&lt;p>&lt;strong>关于亲情&lt;/strong>。随着我们两兄弟的大学毕业，家里的情况也在稍稍好转，但是只能算是曲折前进吧。以前小的时候，都是爸妈两个人闹，现在哥哥出场了，真是可笑。谈了快两年的女朋友，女方父母又是要查户口本，又是催着要付定金，说什么不给定金就要拉回老家相亲。这TM比电视剧还荒唐，真把自己当商品了，是不是给的钱多就跟谁呀，混蛋。哥哥也不是个省油的灯，分手之后没过多久说什么被公司派去新加坡学习了，去了之后，连个固定的联系方式都没有，三天两头失联，都老大不小的人了，还让父母担心。工作了两年，一分钱都没攒到，连大学的助学贷款都要我这个还在上学的弟弟替他还。女朋友分手也就算了，没赚到钱也不要紧，关键是你不能让家人这样担心你呀，你定期给家人打个电话，说说你到底在哪里干什么，既然到了新加坡，发几张国外的照片回来分享一下，不可以吗？已经两年过年没回家了，而且两年除夕居然连个电话都没有，这不是不孝是什么，混蛋。&lt;/p>
&lt;p>今年妈妈也终于愿意外出挣钱了，虽然不多，但是起码在和爸爸一起努力。家里装修好了一层房子，但是也就是把墙什么的弄好了，家具还没制备。本来想着过年回家给家里买个小米电视，但是爸妈死活说不要买，现在买了也就过年看几天，不划算。后来只好作罢。放假给老爸买的红米手机，终于在除夕这一天拿到手了。给爸妈包的红包，也在按计划逐年的递增着。&lt;/p>
&lt;p>自己有时候也埋怨家里，为什么家境这么的不堪，为什么父母没有达到我理想的高度，为什么哥哥这么不争气，为什么没有人关心我。但是埋怨有用吗，肯定是没用的，还是要看各自的造化。&lt;/p>
&lt;p>&lt;strong>关于爱情&lt;/strong>。我还是太幼稚，看看我的家境吧，有哪个女生愿意摊上我家这些破事呢，我就不应该奢望有什么爱情。不过今年上半年，爱情确实来过，抛开所有的一切，纯粹的校园爱情。可是10月份的一件事，彻底打醒了我，爱情没有那么简单，需要考虑的问题太多了。关于那段时间的记忆，写过很多文字，也流过很多泪。回顾整个下半年，欣欣和我的状态都不太好，除了那件事的原因，和工作变化也有很大的关系。我们都从雁栖湖到中关村，需要经历一个由学生到科研工作者的角色转变，面对科研的未知，都显得有些手足无措。科研的不顺，生活的压力以及家里的一些烦心事，一股脑的涌向了我们，矛盾也时有发生。经历过不少的磕磕绊绊，总算顺利度过了2016年，没有了热恋时期的疯狂，生活终要归于平淡。正所谓陪伴是最长情的告白，爱情的意义是否就在于两个人一起经历，一起成长呢。让我们共同守护。&lt;/p>
&lt;p>&lt;strong>关于友情&lt;/strong>。是的，我还欠很多人一顿饭。很多同学，如果长时间不见面，恐怕真的要忘掉了。&lt;/p>
&lt;p>&lt;strong>关于个人提高&lt;/strong>。上半年忙于课程学习，下半年忙于科研，花在个人提高上的时间真的是太少了。不过感谢有欣欣一直做我的榜样，我现在的小目标就是希望比欣欣看更多的书、刷更多的题。&lt;/p>
&lt;p>另外下半年回到市区之后，也去了一些之前没去过的地方，比如：清华艺术博物馆、繁星戏剧村、中国美术馆、三联书店、香山等地。其中前两个地方都是和欣欣一起去的，感觉超棒~第一次看达芬奇的特展，开始了解这样一个天才，后来还看过他的传记；第一次去剧院看话剧，感觉和看电影完全不一样，小剧场的效果也是棒棒的。2016年12月31日也是一个值得纪念的日子：&lt;/p>
&lt;p>&lt;img alt="from-2016-to-2017" loading="lazy" src="https://bitjoy.net/posts/2017-01-28-2016-summary/from-2016-to-2017.jpg">
2017跨年活动~&lt;/p>
&lt;p>今年借着CNCP2016会议的机会，去了一次大连，见识了一下海滨城市的风貌，后来还跑去渤海学游泳，海水很脏，而且咸得发苦。希望今后每年都去一个除了上班地点和家里之外的第三个城市。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-01-28-2016-summary/dalian1-201608.jpg">
大连滨海国家地质公园&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://bitjoy.net/posts/2017-01-28-2016-summary/dalian2-201608.jpg">
大连滨海国家地质公园&lt;/p>
&lt;p>最后看看&lt;a href="https://bitjoy.net/posts/2016-01-03-2016-happy-new-year/">年初计划&lt;/a>的完成情况：&lt;/p>
&lt;ol>
&lt;li>&lt;del>完成国科大下学期的课程任务：完成&lt;/del>&lt;/li>
&lt;li>&lt;del>接手pLink软件：完成&lt;/del>&lt;/li>
&lt;li>刷完LeetCode所有题目：进度147/461，没有完成&lt;/li>
&lt;li>&lt;del>读10本书：目前读了《数学之美》、《大话设计模式》、《我不知道该说什么，关于死亡还是爱情》、《男人来自火星、女人来自金星，卷I》、《只有医生知道，卷I》、《文学的种子》、《讲理》、《暗时间》、《达·芬奇传：放飞的心灵》、《人间失格》，刚好10本，圆满完成任务:-)&lt;/del>&lt;/li>
&lt;li>去电影院看10场电影：目前看了《美人鱼》、《北京遇上西雅图之不二情书》、《忍者神龟2：破影而出》、《七月与安生》、《湄公河行动》、《比利·林恩的中场战事》、《你的名字》、《血战钢锯岭》，只有8场，其中7场是和欣欣一起看的~&lt;/li>
&lt;li>改正坐姿：有一段时间刻意改正了，但是这东西貌似改不过来？&lt;/li>
&lt;/ol>
&lt;p>除了LeetCode完成度太差之外，其他计划完成度还是蛮高的。下面定一下2017年的年度计划：&lt;/p>
&lt;ol>
&lt;li>发表pLink 2文章&lt;/li>
&lt;li>至少完成毕业工作的80%&lt;/li>
&lt;li>刷完LeetCode所有简单题和中等题，找工作之前最好刷完两遍&lt;/li>
&lt;li>找到一个满意的工作&lt;/li>
&lt;li>读10本书&lt;/li>
&lt;li>去电影院看10场电影&lt;/li>
&lt;li>看一场话剧（音乐会、歌剧等都可以）&lt;/li>
&lt;li>学会游泳&lt;/li>
&lt;li>去第三个城市&lt;/li>
&lt;/ol>
&lt;p>简短总结一下我的2016：完成了由上课到科研的转变；开始有能力感恩家人；遇到了欣欣，由一个人变成了两个人；第一次去剧场看话剧。展望2017，找工作和准备毕业迫在眉睫，注定又是繁忙的一年！&lt;/p>
&lt;p>最后用汪老师的年终总结PPT封面的一句话来结束吧：&lt;/p>
&lt;p>&lt;img alt="from-2016-to-2017-2" loading="lazy" src="https://bitjoy.net/posts/2017-01-28-2016-summary/from-2016-to-2017-2.png">&lt;/p></description></item><item><title>电动汽车加电站</title><link>https://bitjoy.net/posts/2017-01-03-electric-vehicle-charging-station/</link><pubDate>Tue, 03 Jan 2017 21:16:08 +0800</pubDate><guid>https://bitjoy.net/posts/2017-01-03-electric-vehicle-charging-station/</guid><description>&lt;p>今天中午和超哥在食堂吃饭的时候聊起了电动汽车加电站的问题，很有意思。&lt;/p>
&lt;p>超哥现在开的是一辆电动汽车，他说目前也挺满意的，只要在北京市内开，几乎没问题，充电桩到处都有，马力也足，开起来没有任何噪声。唯一的问题是需要每天充电，去到稍微远一点的京郊可能会电量不足。&lt;/p>
&lt;p>然后就讨论到目前电动汽车的瓶颈，主要还是在电池上，一个是续航时间短，另一个是充电时间慢。&lt;/p>
&lt;p>然后我就想现在的加油车为什么没有上面的两个问题呢，第一个问题，如果加的油少的话，是不是也会出现续航时间短的问题呢，所以把电动汽车的电池做大一点，密度高一点是不是就可以了呢，虽然技术上可能会有难度，但是我觉得并没有第二个问题严重，所以我觉得电池续航短不是太大的问题。。。&lt;/p>
&lt;p>对于第二个问题，在加油站给汽车加油只需要几分钟的时间，但是电动车在充电桩充电可能需要几十分钟甚至一个多小时，所以充电时间慢确实是一个很严重的问题。我当时就说一辆越野车如果要跑沙漠的话，会在车上预存好几桶油备用，类似的，电动车能不能在车上备上几个电池，没电了就换呢，就像手机备用电池一样。然后超哥进一步说不如干脆在现有加油站的基础上，建一个加电站，每辆电动车进站之后，卸下车上的电池，换上提前充满电的电池，整个过程和加油完全一样，还比加油干净。&lt;/p>
&lt;p>我突然觉得，哇塞，这个idea不错呀，统一所有电动车的电池，电池没电之后，到站换电池，一个电池就像一个小型的集装箱（或者docker）一样，被卸下来，然后插上满电电池，so easy~但是为啥没公司这么做呢。此时旁边坐着的一位老师也加入了对话，他说电池寿命有长有短，如果自己刚买的新车，没电了拿去换了一个旧电池，肯定不爽；再说了，要统一全国的电池标准，几乎是不可能的，在全国建这样的加电站，没有哪个公司能承担得起这样的成本，最终羊毛出在羊身上，电动车的价格肯定会上涨的….&lt;/p>
&lt;p>这位老师说的都对，但是我依然觉得这个idea是可行的，关键是要看有关部门有没有这个魄力来做这件事。比如国家或行业层面可以强制统一电池的标准，XX汽车协会规定今后的汽车电池必须做成0.5m*0.5m*0.5m的方块，正负极距离5cm，便于拆卸等；同时要求电动汽车的电池安放位置必须在汽车的后右侧等一些列规定。即使国家层面没人愿意做这件事，哪家有魄力的电动汽车公司，是不是可以尝试一下呢，比如特斯拉，统一旗下所有电车的电池规格，并在全球建造加电站，统一更换特斯拉的电池。如果特斯拉这样做了，我相信买特斯拉的车主是愿意承担一部分费用的，毕竟这样的加电站最终还是方便了自己。至于说新车换到旧电池，其实大可不必担心，说不定你的旧车会换到别人的新电池呢，而且特斯拉可以建立一个标准，只有电池能量转换效率大于80%的电池才能进入加电站循环，这样保证了每个人换到的电池续航有保障，至于新旧，我才不管呢，反正下一次加电又要换了。&lt;/p>
&lt;p>我个人还是挺喜欢电动汽车的，节能、环保、静音，还看起来酷酷的:-)好希望这个idea能在未来实现呀~&lt;/p>
&lt;p>知乎：&lt;a href="https://www.zhihu.com/question/28793345">电动汽车为什么不统一电池，充电站更换相同档次满电电池？&lt;/a>&lt;/p></description></item><item><title>最怕空气突然安静</title><link>https://bitjoy.net/posts/2016-10-28-waiting-for-you/</link><pubDate>Fri, 28 Oct 2016 20:57:21 +0800</pubDate><guid>https://bitjoy.net/posts/2016-10-28-waiting-for-you/</guid><description>&lt;pre tabindex="0">&lt;code>最怕空气突然安静
最怕朋友突然的关心
最怕回忆突然翻滚绞痛着不平息
最怕突然听到你的消息
想念如果会有声音
不愿那是悲伤的哭泣
事到如今终於让自已属於我自已
只剩眼泪还骗不过自己
突然好想你你会在哪里
过的快乐或委屈
突然好想你突然锋利的回忆
突然模糊的眼睛
我们像一首最美丽的歌曲
变成两部悲伤的电影
为什麽你带我走过最难忘的旅行
然後留下最痛的纪念品
我们那麽甜那麽美那麽相信
那麽疯那麽热烈的曾经
为何我们还是要奔向
各自的幸福和遗憾中老去
突然好想你你会在哪里
过的快乐或委屈
突然好想你突然锋利的回忆
突然模糊的眼睛
最怕空气突然安静
最怕朋友突然的关心
最怕回忆突然翻滚绞痛着不平息
最怕突然听到你的消息
最怕此生已经决定自己过
没有你却又突然听到你的消息
&lt;/code>&lt;/pre>&lt;p>就好像是突然之间，整个世界都失去了你的声音，以前每天都会收到你的喜怒哀乐、衣食住行、午安晚安，突然之间，空气都安静了，没有了你的消息。翻遍你的空间、朋友圈、微博、博客，都没有消息，不知道你在干什么，好伤心。&lt;/p>
&lt;p>想你，想知道你在哪里，想知道你在干什么，想要发消息给你，又害怕不能收到你的回信，然后郁闷一整天。想要引起你的注意，绞尽脑汁故意发一些不着边际的微博，等了一整天，没有收到你的点赞或评论。&lt;/p>
&lt;p>约你出来吃饭，你一句简单得不能再简单的“可以”，冷冰冰。见到你，裹着厚厚的棉衣，戴着帽子和手套，没有一丝的眼神交流，两个人就这样默默的吃着不知道什么味道的饭菜。&lt;/p>
&lt;p>想要打破这宁静的空气，之前想到无数要和你说的人和事，现在却一句话也说不出。两个最熟悉的人，突然之间，像多年未见的朋友，因完全不同的人生轨迹而没有任何共同语言，成了最熟悉的陌生人。&lt;/p>
&lt;p>你说最近科研压力很大，周围的同学又是发论文，又是发专利，你却一无所有。挑战赛答辩和开题答辩在即，手足无措。&lt;/p>
&lt;p>你说未来太渺茫，没钱买房买车，就算月入两万，要在北京买房也得攒20年。即使买了房，在北京还要买车还要摇号，看病也很贵，还要担心孩子上学各种问题。&lt;/p>
&lt;p>我向来是个不会安慰别人的人，但是我尽力想要告诉你，你已经很优秀了，从小到大的尖子生，多才多艺，研一在雁栖湖那么多高手，你照样轻松拿下第一。你的编程能力也远超你们实验室的人，只不过你目前处于一种有力没处使的状态，你的导师让你干一些杂活，如果你的导师也让专心指导你的挑战赛，你现在肯定也写论文了。&lt;/p>
&lt;p>我尽力安慰你，希望你对对未来乐观一点。面包会有的，不要太在意这些东西，生活快乐最重要，不要太在意别人的看法，自己纵向比较有进步就行了。突然觉得自己词穷，完全不会安慰一个人。&lt;/p>
&lt;p>世界上比你悲惨的人多得太多了，为什么不想想自己有的，至少你有一个健康的身体。&lt;/p>
&lt;p>是的，我猜到了，你不理我和我们上周的体检结果有关。我有病，是的，我有病，而且是不可能治好的病，是随时都可能发病的病。我知道这对于你来说很为难，你有矛盾，我理解，只是我希望你能把你的所有顾虑都告诉我，至少让我和你一起分担，如果你说因为我的病，因为我那卑微的背景，想要离开我，我无话可说，这也无可厚非，我接受。&lt;/p>
&lt;p>刚开始交往时，我没有告诉你，是我的错。上周的检查结果至少说明你是健康的，我很庆幸，没有伤害到你。如果因为我而让你不开心，让你纠结，请一定让我知道，我会默默走开，我是一个讲理的人。希望你永远健康快乐。&lt;/p>
&lt;p>你说这东西还有窗口期，我认，再过几个月，我们再去检查一次，我默默祈祷你是健康的。我知道你和我一样，对一件微小的事也要纠结半天，我不想让你那样难过。&lt;/p>
&lt;p>我原本以为我是一个耐得住安静和寂寞的人，我一度还觉得你像一个叽叽喳喳的小鸟不停的在我耳边发出噪声。直到有一天，我发现你的声音不见了，我开始慌了，不安、烦躁、忧郁充斥我的大脑，想要马上见到你问个一清二楚。也许这就是&lt;a href="http://zhihu.com/question/26049681/answer/32014770">日久生情&lt;/a>，这就是感情吧。&lt;/p>
&lt;p>熟悉了你随性而又纠结的脾气；习惯了每次和你一起吃饭时纠结菜里到底有没有混猪肉；习惯了每次吃饭时要双份碗筷和三份汤的感觉；渐渐喜欢上了和你一起漫无目的的逛街试衣服的感觉；习惯了买两本一样的书，然后每次你看得都比我快，逼迫我不得不也快快的看；喜欢和你一起看电影出去玩的感觉。&lt;/p>
&lt;p>也不知从什么时候开始，微信里的情侣表情都积了厚厚的一层灰；好久好久都没有掰你了，你的手指应该纤细如初了吧。带上耳机，听了一整晚的音乐，已经很久很久没有听歌了。&lt;/p>
&lt;p>以前常常对电视剧里的爱情嗤之以鼻，完全不能理解他们为什么要为爱情哭得死去活来，现在，我大概理解了。&lt;/p>
&lt;p>你说每个正常人遇到我这种情况，都会矛盾，会需要思考、抉择和权衡，是的，爱情完全不是小说里的义无反顾，说到底是各种利益的权衡。&lt;/p>
&lt;p>10月真是一个让人忧伤的月份，亲人的离世，自己也前前后后进了三次医院，揭开了深藏心底N年的伤疤，曾经一直陪伴在身边的人也想要离开。科研上的压力就更不说了，老板一直不停的催促，每天活得像个陀螺，没有方向的转，没有一丝的停顿。&lt;/p>
&lt;p>这病态的社会。&lt;/p>
&lt;p>亲爱的，我愿意等你，我尊重你的决定，如果你离开，我会祝福你，如果你留下，我希望你不是在怜悯我。&lt;/p></description></item><item><title>C++基本数据类型备忘</title><link>https://bitjoy.net/posts/2016-09-24-memo-about-cpp-built-in-types/</link><pubDate>Sat, 24 Sep 2016 20:37:05 +0800</pubDate><guid>https://bitjoy.net/posts/2016-09-24-memo-about-cpp-built-in-types/</guid><description>&lt;p>今天阅读《C++ Primer, 5e》的第二章，介绍C++的基本内置类型，觉得有一些平时工作容易出错的知识点，现摘录如下：&lt;/p>
&lt;hr>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> c &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>; &lt;span style="color:#75715e">// 假设char占8比特，c的值为255
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>当我们赋给无符号类型一个超出它表示范围的值时，结果是初始值对无符号类型表示数值总数取模后的余数。例如，8比特大小的unsigned char可以表示0至255区间内的值，如果我们赋了一个区间以外的值，则实际的结果是该值对256取模后所得的余数。因此，把-1赋给8比特大小的unsigned char所得的结果是255。&lt;/p>
&lt;hr>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">signed&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> c2 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">256&lt;/span>; &lt;span style="color:#75715e">// 假设char占8比特，c2的值是未定义的
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>当我们赋给带符号类型一个超出它表示范围的值时，结果是未定义的（undefined）。此时，程序可能继续工作、可能崩溃，也可能生成垃圾数据。&lt;/p>
&lt;hr>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">unsigned&lt;/span> u &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">10&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> i &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">42&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>std&lt;span style="color:#f92672">::&lt;/span>cout &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> i &lt;span style="color:#f92672">+&lt;/span> i &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>endl; &lt;span style="color:#75715e">// 输出-84
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>std&lt;span style="color:#f92672">::&lt;/span>cout &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> u &lt;span style="color:#f92672">+&lt;/span> i &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>endl; &lt;span style="color:#75715e">// 如果int占32位，输出4294967264
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在第一个输出表达式里，两个（负）整数相加并得到了期望的结果。在第二个输出表达式里，相加前首先把整数-42转换成无符号数。把负数转换成无符号数类似于直接给无符号数赋一个负数，结果等于这个负数加上无符号数的模。unsigned (int)的取值范围是0~\(2^{32}-1\)，所以总数有\(2^{32}\)个数，-42%\(2^{32}\)=-42+\(2^{32}\)，u+i=10+(-42+\(2^{32}\))=4294967264。&lt;/p>
&lt;hr>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">unsigned&lt;/span> u1 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">42&lt;/span>, u2 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">10&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>std&lt;span style="color:#f92672">::&lt;/span>cout &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> u1 &lt;span style="color:#960050;background-color:#1e0010">–&lt;/span> u2 &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>endl; &lt;span style="color:#75715e">// 正确：输出32
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>std&lt;span style="color:#f92672">::&lt;/span>cout &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> u2 &lt;span style="color:#960050;background-color:#1e0010">–&lt;/span> u1 &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>endl; &lt;span style="color:#75715e">// 正确：不过，结果是取模后的值
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>当从无符号数中减去一个值时，不管这个值是不是无符号数，我们都必须确保结果不能是一个负值。&lt;/p></description></item><item><title>随机矩阵及其特征值</title><link>https://bitjoy.net/posts/2016-08-23-the-eigenvalue-of-stochastic-matrix/</link><pubDate>Tue, 23 Aug 2016 18:59:57 +0800</pubDate><guid>https://bitjoy.net/posts/2016-08-23-the-eigenvalue-of-stochastic-matrix/</guid><description>&lt;p>&lt;a href="https://en.wikipedia.org/wiki/Stochastic_matrix">随机矩阵&lt;/a>是这样一类方阵，其元素为非负实数，且行和或列和为1。如果行和为1，则称为行随机矩阵；如果列和为1，则称为列随机矩阵；如果行和和列和都为1，则称为双随机矩阵。&lt;/p>
&lt;p>前面我们介绍的&lt;a href="https://bitjoy.net/posts/2016-08-04-googles-pagerank-and-beyond/">谷歌矩阵&lt;/a>和&lt;a href="https://bitjoy.net/posts/2016-08-20-introduction-to-hmm-1/">HMM中的转移矩阵&lt;/a>都属于随机矩阵，所以随机矩阵也称为概率矩阵、转移矩阵、或马尔可夫矩阵。&lt;/p>
&lt;p>随机矩阵有一个性质，就是其所有特征值的绝对值小于等于1，且其最大特征值为1。下面通过两种方法证明这个结论。&lt;/p>
&lt;p>首先，随机矩阵A肯定有特征值1，即&lt;/p>
$$\begin{equation}A\vec 1=1\times\vec 1\end{equation}$$&lt;p>其中的单位向量\(\vec 1=(\frac{1}{n},…,\frac{1}{n})^T\)，因为A的行和为1，所以上述等式成立。即1是A的特征值。&lt;/p>
&lt;h1 id="反证法">&lt;a href="https://mikespivey.wordpress.com/2013/01/17/eigenvalue-stochasti/">反证法&lt;/a>&lt;/h1>
&lt;p>假设存在大于1的特征值\(\lambda\)，则有\(A\vec x=\lambda\vec x\)。令\(x_k\)是\(\vec x\)中最大的元素。又因为A的元素非负，且行和为1，所以\(\lambda\vec x\)中的每个元素都是\(\vec x\)中元素的凸组合，所以\(\lambda\vec x\)中的每个元素都小于等于\(x_k\)。&lt;/p>
$$\begin{equation}a_{i1}x_1+a_{i2}x_2+…+a_{in}x_n=\lambda x_i\leq x_k\end{equation}$$&lt;p>但是如果\(\lambda>1\)，则\(\lambda x_k>x_k\)，和(2)式矛盾，所以\(\lambda\leq 1\)。又因为(1)式，所以A的最大特征值为1。&lt;/p>
&lt;h1 id="常规证法">常规证法&lt;/h1>
&lt;p>设&lt;strong>对称随机矩阵A&lt;/strong>的特征值\(\lambda\)对应的特征向量为\(x\)（为了简便，以下省略向量符号），则有\(Ax=\lambda x\)，即\(x^TAx=\lambda x^Tx\)，欲证明\(|\lambda|\leq 1\)，只需证明&lt;/p>
$$\begin{equation}\lambda=\frac{&lt; x, Ax >}{&lt; x, x >}\leq 1\end{equation}$$&lt;p>根据定义有：&lt;/p>
$$\begin{equation}&lt; x, Ax >=\sum_{i=1}^na_{ii}x_i^2+2\sum_{i &lt; j, i\sim j}a_{ij}x_ix_j\end{equation}$$&lt;p>对于\(i &lt; j, i\sim j\)，有：&lt;/p>
$$\begin{equation}a_{ij}(x_i-x_j)^2=a_{ij}x_i^2-2a_{ij}x_ix_j+a_{ij}x_j^2\end{equation}$$&lt;p>两边求和并移项得到：&lt;/p>
$$
\begin{equation}
\begin{array}
\displaystyle{2\sum_{i &lt; j}}a_{ij}x_ix_j &amp; = &amp; \displaystyle{\sum_{i &lt; j}a_{ij}x_i^2+\sum_{i &lt; j}a_{ij}x_j^2-\sum_{i &lt; j}a_{ij}(x_i-x_j)^2}\\
&amp; = &amp; \displaystyle{\sum_{i &lt; j}a_{ij}x_i^2+\sum_{i &lt; j}a_{ji}x_j^2-\sum_{i &lt; j}a_{ij}(x_i-x_j)^2}\\ &amp; = &amp; \displaystyle{\sum_{i &lt; j}a_{ij}x_i^2+\sum_{i > j}a_{ij}x_i^2-\sum_{i &lt; j}a_{ij}(x_i-x_j)^2}\\
&amp; = &amp; \displaystyle{\sum_i(\sum_{j\neq i}a_{ij}x_i^2)-\sum_{i &lt; j}a_{ij}(x_i-x_j)^2}\\
&amp; = &amp; \displaystyle{\sum_i(x_i^2(1-a_{ii}))-\sum_{i &lt; j}a_{ij}(x_i-x_j)^2}
\end{array}
\end{equation}
$$&lt;p>第2、3个等号都是因为A是对称矩阵，所以可以把\(a_{ij}\)替换为\(a_{ji}\)，然后互换\(i,j\)下标。最后一个等号是因为A的行和为1。&lt;/p></description></item><item><title>马尔可夫聚类算法</title><link>https://bitjoy.net/posts/2016-08-22-the-markov-cluster-algorithm/</link><pubDate>Mon, 22 Aug 2016 18:41:45 +0800</pubDate><guid>https://bitjoy.net/posts/2016-08-22-the-markov-cluster-algorithm/</guid><description>&lt;p>马尔可夫聚类算法（The Markov Cluster Algorithm, MCL）是一种快速可扩展的基于图的聚类算法。它的基本思想为：在一个稀疏图G中，如果某个区域A是稠密的（是一个聚类），则在A中随机游走k步，还在A内的概率很大，也就是说，A内的k步路径（k-length path）很多。所以我们可以在图中随机游走k步，如果某个区域连通的概率很大，则该区域是一个聚类。随机游走的下一步只和当前所处节点有关，也就是说这是一个马尔可夫的随机游走过程。&lt;/p>
&lt;p>我们用一个例子来演示马尔可夫聚类算法的过程。&lt;/p>
&lt;p>&lt;img alt="mcl-1" loading="lazy" src="https://bitjoy.net/posts/2016-08-22-the-markov-cluster-algorithm/mcl-1.png">&lt;/p>
&lt;p>上图是一个很小的网络，我们用肉眼大概能看出有三个聚类，分别是左边的{1,6,7,10}，中间的{2,3,5}和右边的{4,8,9,11,12}。我们用MCL看看结果如何。&lt;/p>
&lt;p>为了随机游走，我们常用邻接矩阵来表示图，如果i,j有边，则N[i][j]=1，否则N[i][j]=0。又随机游走可能有自回路，所以加上单位矩阵I，得到矩阵N+I。&lt;/p>
&lt;p>MCL有两个关键的步骤，分别是Expansion和Inflation。&lt;/p>
&lt;p>&lt;strong>Expansion&lt;/strong>就是不断对矩阵进行幂次运算，相当于随机游走。假设随机游走了2步，则得到如下图的关联矩阵\((N+I)^2\)，第1行第10列为4，说明1到10的2-length path有4条：1→6→10，1→7→10，1→1→10，1→10→10。随机游走k步之后，\((N+I)^k[i][j]\)越大，说明\(i\)和\(j\)之间的连通性越强。&lt;/p>
$$\begin{equation}Expand(M)=M^k\end{equation}$$&lt;p>&lt;img alt="mcl-2" loading="lazy" src="https://bitjoy.net/posts/2016-08-22-the-markov-cluster-algorithm/mcl-2.png">&lt;/p>
&lt;p>&lt;strong>Inflation&lt;/strong>是为了增强更强的连接，减弱更弱的连接，只有这样才能得到边界比较明确的聚类。MCL的做法是对元素做幂次运算，然后按列归一化，公式为：&lt;/p>
$$\begin{equation}(\Gamma_rM)_{pq}=\frac{(M_{pq})^r}{\sum_{i=1}^k(M_{iq})^r}\end{equation}$$&lt;p>参数经验值是\(k=r=2\)。不断做Expansion和Inflation操作，直到算法收敛，得到若干个聚类。&lt;a href="https://bitjoy.net/posts/2016-08-22-the-markov-cluster-algorithm/mcl-3.pdf">中间过程请点此查看&lt;/a>，下图为最终结果。&lt;/p>
&lt;p>&lt;img alt="mcl-4" loading="lazy" src="https://bitjoy.net/posts/2016-08-22-the-markov-cluster-algorithm/mcl-4.png">&lt;/p>
&lt;p>从图中可以看出，和1有边的只剩下6,7,10了，所以得到聚类{1,6,7,10}，同理能得到聚类{2,3,5}和{4,8,9,11,12} ，和我们肉眼得到的结果是一致的。&lt;/p>
&lt;p>MCL算法的原理很简单，得到的聚类效果也不错。下面总结一下MCL的算法过程：&lt;/p>
&lt;ol>
&lt;li>给定无向图G，Expansion和Inflation的参数\(k\)和\(r\)&lt;/li>
&lt;li>生成G的邻接矩阵\(N\)&lt;/li>
&lt;li>添加自回路，得到矩阵\(N+I\)&lt;/li>
&lt;li>循环对\(N+I\)做Expansion和Inflation操作，即计算公式(1)和(2)，直到收敛&lt;/li>
&lt;li>根据最终得到的矩阵，进行划分聚类&lt;/li>
&lt;/ol>
&lt;p>此算法是我在上《生物信息学中的算法设计》课上是学到的，当时觉得这个算法真是神奇，如此简单，但又如此有效，实在高明。查阅文献得知，此为&lt;a href="http://www.micans.org/mcl/">Stijn van Dongen的博士论文&lt;/a>，本博客的图片均来自其博士论文，想深入了解图聚类算法，请&lt;a href="http://micans.org/mcl/lit/svdthesis.pdf.gz">下载他的论文&lt;/a>。&lt;/p></description></item><item><title>隐马尔可夫模型及其应用（2）学习问题&amp;识别问题</title><link>https://bitjoy.net/posts/2016-08-21-introduction-to-hmm-2/</link><pubDate>Sun, 21 Aug 2016 18:46:01 +0800</pubDate><guid>https://bitjoy.net/posts/2016-08-21-introduction-to-hmm-2/</guid><description>&lt;p>上一回介绍了HMM的解码问题，今天我们介绍HMM的学习问题和识别问题，先来看学习问题。&lt;/p>
&lt;hr>
&lt;p>正如上一回结束时所说，&lt;strong>HMM的学习问题&lt;/strong>是：仅已知观测序列\(\vec y\)，要估计出模型参数组\(\vec\lambda=(\mu,A,B)\)，其中\(\mu\)为初始概率分布向量，\(A\)为转移概率矩阵，\(B\)为发射概率矩阵。&lt;/p>
&lt;h1 id="算法设计">算法设计&lt;/h1>
&lt;p>求解HMM的参数学习问题，就是求解如下的最优化问题：&lt;/p>
$$\begin{equation} P(\vec Y = \vec y|\hat \lambda)=\max\limits_{\vec \lambda} P(\vec Y = \vec y|\vec \lambda)\end{equation}$$&lt;p>也就是找一个参数\(\vec \lambda\)，使得模型在该参数下最有可能产生当前的观测\(\vec y\)。如果使用极大似然法求解，对于似然函数\(P(\vec Y=\vec y|\vec \lambda)=\sum\limits_{i_1,…,i_T}\mu_{i_1}b_{i_1y_1}a_{i_1i_2}…a_{i_{T-1}i_T}b_{i_Ty_T}\)而言，这个最大值问题的计算量过大，在实际中是不可能被采用的。为此，人们构造了一个递推算法，使其能相当合理地给出模型参数\(\vec \lambda\)的粗略估计。其核心思想是：并不要求备选\(\vec\lambda\)使得\(P(\vec Y=\vec y|\vec \lambda)\)达到最大或局部极大，而只要求使\(P(\vec Y=\vec y|\vec \lambda)\)相当大，从而使计算变为实际可能。&lt;/p>
&lt;h1 id="em算法">EM算法&lt;/h1>
&lt;p>为此，我们定义一个描述模型“趋势”的量\(Q(\vec\lambda^*|\vec\lambda)\)代替似然函数\(P(\vec Y=\vec y|\vec\lambda)\)，其定义为：&lt;/p>
$$\begin{equation} Q(\vec\lambda^*|\vec\lambda)=\sum\limits_{\vec x}P(\vec x,\vec y|\vec\lambda)\ln P(\vec x,\vec y|\vec\lambda^*)\end{equation}$$&lt;p>利用在\(0 &lt; x &lt; 1\)时，不等式\(\ln x\leq x-1\)成立，可以证明：&lt;/p>
$$\begin{equation} Q(\vec\lambda^*|\vec\lambda)-Q(\vec\lambda|\vec\lambda)\leq P(\vec Y=\vec y|\vec\lambda^*)-P(\vec Y=\vec y|\vec\lambda)\end{equation}$$&lt;p>由此可见，对于固定的\(\vec\lambda\)，只要\(Q(\vec\lambda^*|\vec\lambda)>Q(\vec\lambda|\vec\lambda)\)，就有\(P(\vec Y=\vec y|\vec\lambda^*)>P(\vec Y=\vec y|\vec\lambda)\)。于是想把模型\(\vec\lambda_m\)修改为更好的模型\(\vec\lambda_{m+1}\)，只需找\(\vec\lambda_{m+1}\)使得：&lt;/p>
$$\begin{equation}Q(\vec\lambda_{m+1}|\vec\lambda_m)=\sup_{\vec\lambda}Q(\vec\lambda|\vec\lambda_m)\end{equation}$$&lt;p>即只要把\(Q(\vec\lambda|\vec\lambda_m)\)关于\(\vec\lambda\)的最大值处取成\(\vec\lambda_{m+1}\)，就有\(P(\vec Y=\vec y|\vec\lambda_{m+1})>P(\vec Y=\vec y|\vec\lambda_m)\)。&lt;/p>
&lt;p>这样得到的模型序列\(\{\vec\lambda_m\}\)能保证\(P(\vec Y=\vec y|\vec\lambda_m)\)关于\(m\)是严格递增的，虽然在这里还不能在理论上证明\(P(\vec Y=\vec y|\vec\lambda_m)\)收敛到\(\max_{\vec\lambda}P(\vec Y=\vec y|\vec\lambda)\)，但是当\(m\)充分大时，\(\vec\lambda_m\)也还能提供在实际中较为满意的粗略近似。&lt;/p></description></item><item><title>隐马尔可夫模型及其应用（1）简介&amp;解码问题</title><link>https://bitjoy.net/posts/2016-08-20-introduction-to-hmm-1/</link><pubDate>Sat, 20 Aug 2016 18:12:53 +0800</pubDate><guid>https://bitjoy.net/posts/2016-08-20-introduction-to-hmm-1/</guid><description>&lt;p>&lt;a href="https://zh.wikipedia.org/wiki/%E9%9A%90%E9%A9%AC%E5%B0%94%E5%8F%AF%E5%A4%AB%E6%A8%A1%E5%9E%8B">隐马尔可夫模型&lt;/a>（Hidden Markov Model, HMM）是统计模型，它用来描述一个含有隐含未知参数的马尔可夫过程。其难点是从可观察的参数中确定该过程的隐含参数。然后利用这些参数来作进一步的分析，例如模式识别。&lt;/p>
&lt;p>先举一个简单的例子以直观地理解HMM的实质——韦小宝的骰子。&lt;/p>
&lt;p>&lt;img alt="hmm-2" loading="lazy" src="https://bitjoy.net/posts/2016-08-20-introduction-to-hmm-1/hmm-2.jpg">&lt;/p>
&lt;p>假设韦小宝有两个骰子，一个正常的骰子A，A以1/6的概率均等的出现每个点；一个不正常的骰子B，B出现5,6点数的概率为0.3，出现其他点数的概率为0.1。显然投掷B更容易出现大的点数。每次试验&lt;strong>第一次投掷时&lt;/strong>，韦小宝会以0.4的概率出千（即投掷B）。但是在一次试验中，韦小宝不太可能一直出千，所以骰子会在A、B之间转换，比如这次投了B，下次可能会以0.1的概率投A。A、B之间的转移概率如下图。&lt;/p>
&lt;p>&lt;img alt="hmm-1" loading="lazy" src="https://bitjoy.net/posts/2016-08-20-introduction-to-hmm-1/hmm-1.png">&lt;/p>
&lt;p>某一次试验，我们观察到韦小宝掷出的骰子序列为\(O=(1,3,4,5,5,6,6,3,2,6)\)，请问韦小宝什么时候出千了。这个问题就可以通过HMM求解。&lt;/p>
&lt;p>HMM有2个状态：&lt;/p>
&lt;ol>
&lt;li>观测状态。我们观察到的骰子序列称为观测状态\(\mathbf{Y}=\{y_1,y_2,…,y_T\}\)&lt;/li>
&lt;li>隐状态。隐含在每个观测状态里面的是隐状态\(\mathbf{X}=\{x_1,x_2,…,x_T\}\)&lt;/li>
&lt;/ol>
&lt;p>T是时间，也可以认为是观测的次数。HMM有3个参数：&lt;/p>
&lt;ol>
&lt;li>初始分布\(\mathbf{\mu}=(\mu_i)\)，\(\mu_i=Pr(x_1=i)\)，即第一次观测时，每个隐状态出现的概率&lt;/li>
&lt;li>转移概率矩阵\(A=(a_{ij})\)，\(a_{ij}=Pr(x_{t+1}=j|x_t=i)\)，即t时刻的隐状态为i，t+1时刻转移到隐状态j的概率&lt;/li>
&lt;li>发射概率矩阵\(B=(b_{il})\)，\(b_{il}=Pr(y_t=l|x_t=i)\)，即t时候隐状态为i的情况下，观测到状态为l的概率&lt;/li>
&lt;/ol>
&lt;p>参数\(\mathbf{\lambda=\{\mu,A,B\}}\)称为HMM的模型参数。具体到上面的例子，我们有初始分布和转移概率为：&lt;/p>
&lt;p>&lt;img alt="hmm-3" loading="lazy" src="https://bitjoy.net/posts/2016-08-20-introduction-to-hmm-1/hmm-3.png">&lt;/p>
&lt;p>发射概率为：&lt;/p>
&lt;p>&lt;img alt="hmm-4" loading="lazy" src="https://bitjoy.net/posts/2016-08-20-introduction-to-hmm-1/hmm-4.png">&lt;/p>
&lt;p>观测状态为\(\mathbf{Y}=(1,3,4,5,5,6,6,3,2,6)\)，问题就是求解出隐状态\(\mathbf{X}\)，此问题被称为HMM的解码问题，可以由著名的&lt;a href="https://zh.wikipedia.org/wiki/%E7%BB%B4%E7%89%B9%E6%AF%94%E7%AE%97%E6%B3%95">维特比算法（Viterbi algorithm）&lt;/a>解决。&lt;/p>
&lt;p>解码问题是要求出使得观测状态\(Y\)出现概率最大的隐状态\(X\)，假设有N个隐状态（本例为2），共有T个时刻（本例为10），则每个时刻有N个取值可能，则共有\(N^T\)条可能的隐状态链（本例为\(2^{10}\)）。我们需要求出每一条隐状态链下T个发射概率的乘积，然后取最大值，这是指数时间复杂度的（\(O(N^T)\)）。&lt;/p>
&lt;p>&lt;img alt="hmm-5" loading="lazy" src="https://bitjoy.net/posts/2016-08-20-introduction-to-hmm-1/hmm-5.png">&lt;/p>
&lt;p>但是Viterbi算法是一个动态规划算法，只需多项式时间即可解决该问题。该算法的原理很好理解，假设我们求得到\(s_{i2}\)的最大概率路径为下图中的红线\(s_{11}\rightarrow s_{22}\rightarrow … s_{i2}\)，则在求经过\(s_{i2}\)到\(s_{(i+1)1}\)的最大概率路径时，不需要再测试\(s_{13}\rightarrow s_{21}\rightarrow s_{i2}\rightarrow s_{(i+1)1}\)这条路径（下图蓝线），因为显然已经知道红线概率大于蓝线概率了。图中还有很多类似蓝线的路径都可以不用计算了，大大提高了求解速度。&lt;/p>
&lt;p>因为计算第\(i+1\)时刻的累积概率只和第\(i\)时刻的概率有关，每次至多计算\(N*N\)个概率乘积（可以从\(i\)时刻的\(N\)个状态到达\(i+1\)时刻的某个状态，\(i+1\)时刻共有\(N\)个状态），最多计算T次（共T个时刻），所以时间复杂度降到了\(O(N^2T)\)。&lt;/p>
&lt;p>&lt;img alt="hmm-6" loading="lazy" src="https://bitjoy.net/posts/2016-08-20-introduction-to-hmm-1/hmm-6.png">&lt;/p>
&lt;p>下面我们形式化的描述Viterbi算法。&lt;/p>
&lt;p>假设\(\delta_t(i)\)为\(t\)时刻取到隐状态\(i\)，且1~t的观测状态都符合观测值\(Y\)的各个路径的最大概率，即&lt;/p>
$$
\begin{equation}\delta_t(i)=\underset{i_1,…,i_{t-1}}{\max}Pr(X_t=i,X_{t-1}=i_{t-1},…,X_1=i_1,Y_t=y_t,…,Y_1=y_1|\mathbf{\lambda})\end{equation}
$$&lt;p>联系上图，可认为\(\delta_t(i)\)为红线。则递推公式为：&lt;/p>
$$
\begin{equation}\delta_{t+1}(i)=b_{iy_{t+1}}\underset{j}{\max}(\delta_t(j)a_{ji})\end{equation}
$$&lt;p>由\(j\)到\(i\)的转移概率，再乘上\(i\)发射\(y_{t+1}\)的概率。&lt;/p>
&lt;p>在初始时刻\(t=1\)，有：&lt;/p>
$$
\begin{equation}\delta_1(i)=\mu_ib_{iy_1}\end{equation}
$$&lt;p>最后的全局最大概率为\(\underset{j}{\max}\delta_T(j)\)。为了得到完整路径，我们保留每一隐状态取得最大概率时的上一隐状态，即：&lt;/p>
$$
\begin{equation}\psi_{t+1}(i)=j^*\end{equation}
$$&lt;p>其中\(j^*\)要满足&lt;/p>
$$
\begin{equation}\delta_{t+1}(i)=b_{iy_{t+1}}\delta_t(j^*)a_{j^*i}\end{equation}
$$&lt;p>最后使用如下回溯法得到所有最佳隐状态：&lt;/p>
$$\begin{equation}X_T=i^*\in\{i:\delta_T(i)=\underset{j}{\max}\delta_T(j)\}\end{equation}$$$$\begin{equation}X_t=\psi_{t+1}(X_{t+1})\end{equation}$$&lt;p>下面我们利用Viterbi算法来求解韦小宝的骰子这个例子。&lt;/p>
&lt;p>\(t=1\)时，\(y_1=1\)，有\(\delta_1(A)=0.6*1/6=0.1\)，\(\delta_1(B)=0.4*0.1=0.04\)。&lt;/p>
&lt;p>\(t=2\)时，\(y_2=3\)，有：&lt;/p>
&lt;ol>
&lt;li>隐状态为A：a）A-&amp;gt;A有\(\delta_2(A)=(1/6)*0.1*0.8=1.33*10^{-2}\)；b）B-&amp;gt;A有\(\delta_2(A)=(1/6)*0.04*0.1=6.6*10^{-4}\)。所以A-&amp;gt;A，\(\psi_2(A)=A\)。&lt;/li>
&lt;li>隐状态为B：a）A-&amp;gt;B有\(\delta_2(B)=0.1*0.1*0.2=2*10^{-3}\)；b）B-&amp;gt;B有\(\delta_2(B)=0.1*0.04*0.9=3.6*10^{-3}\)。所以B-&amp;gt;B，\(\psi_2(B)=B\)。
如此计算下去，可以得到如下表：&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="hmm-7" loading="lazy" src="https://bitjoy.net/posts/2016-08-20-introduction-to-hmm-1/hmm-7.png">&lt;/p>
&lt;p>\(t=10\)时最大概率为\(\delta_{10}(B)\)，经过回溯得到最佳隐状态为：&lt;/p>
&lt;p>&lt;img alt="hmm-8" loading="lazy" src="https://bitjoy.net/posts/2016-08-20-introduction-to-hmm-1/hmm-8.png">&lt;/p>
&lt;p>所以HMM很神奇吧，可以抓住韦小宝从第5次开始就一直在出千，而且出千之后，掷出的点数大部分为5和6。&lt;/p>
&lt;p>Viterbi算法还可用于解决语音识别或者拼音输入法。我们知道中文的一个拼音可以对应多个汉字，连续的一段拼音就能组成成千上万种可能的句子，哪一个句子才是最佳候选呢？我们可以把每个拼音当成观测状态，同音的汉字当成可能的隐状态。通过背景语料库统计得到每个汉字出现在词首的概率、汉字之间的转移概率和汉字与拼音之间的发射概率，这样我们就能得到模型参数，然后利用Viterbi算法求解出一个最佳的隐状态序列，这样就能完成一个简易的拼音输入法。&lt;/p>
&lt;p>HMM在实际中主要有3个方面的应用，分别是：&lt;/p>
&lt;ol>
&lt;li>从一段观测序列\(\mathbf{Y}\)及已知模型\(\mathbf{\lambda=(\mu,A,B)}\)出发，估计出隐状态\(\mathbf{X}\)的最佳值，称为解码问题，这是状态估计问题。这篇博客讨论的就是这个问题。&lt;/li>
&lt;li>从一段观测序列\(\mathbf{Y}\)出发，估计模型参数组\(\mathbf{\lambda=(\mu,A,B)}\)，称为学习问题，就是参数估计问题。&lt;/li>
&lt;li>对于一个特定的观测链\(\mathbf{Y}\)，已知它可能是由已经学习好的若干模型之一所得的观测，要决定此观测究竟是得自其中哪一个模型，这称为识别问题，就是分类问题。&lt;/li>
&lt;/ol>
&lt;p>关于HMM的学习问题和识别问题，请听下回分解。&lt;/p></description></item><item><title>2016年中总结</title><link>https://bitjoy.net/posts/2016-08-20-2016-mid-year-summary/</link><pubDate>Sat, 20 Aug 2016 11:33:26 +0800</pubDate><guid>https://bitjoy.net/posts/2016-08-20-2016-mid-year-summary/</guid><description>&lt;p>半年的时光又过去了，圆满结束了一年的集中教学任务，离开了美丽的雁栖湖，回到闹市中关村。&lt;/p>
&lt;p>&lt;img alt="ucas-schedule-2016-spring" loading="lazy" src="https://bitjoy.net/posts/2016-08-20-2016-mid-year-summary/ucas-schedule-2016-spring.png">&lt;/p>
&lt;p>这半年基本上延续了研一上学期的高强度学习，四门硬课。《高级算法》这门课由四位大师级的老师授课，内容囊括了近似算法、计算复杂性、随机算法、局部搜索、全息规约等，完全是神一样的课。最后复习的时候，大家都生不如死啊，不过经过一个月的挑灯夜战，我还是取得了97分的好成绩，值了。&lt;/p>
&lt;p>&lt;img alt="advanced-algorithm" loading="lazy" src="https://bitjoy.net/posts/2016-08-20-2016-mid-year-summary/advanced-algorithm.png">&lt;/p>
&lt;p>《大数据系统与大规模数据分析》这门课的老师是一个年轻的海归，要求很严格，有专门的算法检查平时作业是否抄袭，真的有好几个同学因为抄袭而得0分。这门课的大作业是在GraphLite上实现SVD，我带领队员经过一个月的努力比较圆满的完成了大作业，感谢组里的编程大神。&lt;/p>
&lt;p>《机器学习方法与应用》是面向电子学院的课程，讲得太简单，考试基本是概念题，不建议选修。&lt;/p>
&lt;p>《生物信息学中的算法设计》这门课其实应该叫统计机器学习在生物信息领域的应用，讲的内容比《机器学习方法与应用》的内容更深更广。不过内容太多也难以消化，好好做大作业应该会有不少收获。&lt;/p>
&lt;p>集中教学一年，研一上的GPA是87分，研一下的GPA是89.3分，平均是88.1分。&lt;/p>
&lt;p>除了完成若干个课程大作业，这学期还完成了两个组内大作业，分别是倒排索引和蛋白质搜索引擎，也多谢XN和我一起查Bug、对答案。（天啊，我半年是做了多少个大作业啊…）&lt;/p>
&lt;p>这半年每周二回所和师姐交接任务，真是要感谢天真呆萌的JL师姐，当初保研的时候就被师姐的热情所感染，现在又有幸接替师姐的接力棒，好幸运。&lt;/p>
&lt;p>要说上半年最大的收获，应该是收获了一枚女朋友吧~没错，就是我&lt;del>这篇博客&lt;/del>里提到的欣欣~真的没想到这么聊得来，一起吃饭、看电影、聊代码、骑行、游山玩水。这半年拍的照片，比我前22年拍的照片还多。和她在一起很开心，不过有时候也会很累，身体累（羸弱），有时候也心累，毕竟课程压力和组内压力摆在那里，白天去玩了，晚上还是要加班补回来的。有时候冷落了她，也会感到愧疚不安，特别是我在复习《高级算法》期间，两人都很少见面，那一次是真的惹欣欣生气了:-(&lt;/p>
&lt;p>总结一下在雁栖湖一年的收获，大致有如下图的四个方面：&lt;/p>
&lt;p>&lt;img alt="mid-year 2016 summary" loading="lazy" src="https://bitjoy.net/posts/2016-08-20-2016-mid-year-summary/mid-year-2016-summary.png">&lt;/p>
&lt;p>看看&lt;a href="https://bitjoy.net/posts/2016-01-03-2016-happy-new-year/">年初计划&lt;/a>的完成情况：&lt;/p>
&lt;ol>
&lt;li>&lt;del>完成国科大下学期的课程任务：完成&lt;/del>&lt;/li>
&lt;li>&lt;del>接手pLink软件：完成&lt;/del>&lt;/li>
&lt;li>刷完LeetCode所有题目：上半年基本没刷题，下半年一定完成&lt;/li>
&lt;li>读10本书：目前读了《数学之美》、《大话设计模式》、《我不知道该说什么，关于死亡还是爱情》、《男人来自火星、女人来自金星，卷I》，还差6本，下半年加油！&lt;/li>
&lt;li>去电影院看10场电影：目前看了《美人鱼》、《北京遇上西雅图之不二情书》、《忍者神龟2：破影而出》，还差好多…&lt;/li>
&lt;li>改正坐姿：有一段时间刻意改正了，但是这东西貌似改不过来？&lt;/li>
&lt;/ol>
&lt;p>下半年就进入实验室，开始科研实战了，做交联的师兄师姐都毕业了，留下我一个人，感觉好艰难，希望我能顺利进入角色，协助师兄把文章发了，维护好pLink2的软件，并且开发集群版。&lt;/p></description></item><item><title>稳定版快速排序算法</title><link>https://bitjoy.net/posts/2016-08-18-the-stable-quick-sort/</link><pubDate>Thu, 18 Aug 2016 11:00:43 +0800</pubDate><guid>https://bitjoy.net/posts/2016-08-18-the-stable-quick-sort/</guid><description>&lt;p>我们知道常规的快速排序算法是一个不稳定的算法，也就是两个相等的数排序之后的顺序可能和在原序列中的顺序不同。这是因为当选定一个枢轴（pivot），要把其他数分到小于pivot和大于pivot的两边的时候，不同实现的分法不一样。&lt;/p>
&lt;p>下面我实现了一种稳定版快速排序算法，在Partition函数中保持了原序列中所有元素的相对顺序，只把pivot放到了它的正确位置。具体方法是三遍扫描原序列：1）第一遍先把小于pivot的元素按先后顺序放到tmp里，然后把pivot放到它的正确位置tmp[k]；2）第二遍把大于pivot的元素按先后顺序追加在tmp里，这样除了pivot以前的其他元素，都保持了和原序列中一样的顺序；3）第三遍把tmp赋值回原数组A。&lt;/p>
&lt;p>当排序算法稳定之后，就可以借此统计逆序数了，文件&lt;a href="https://bitjoy.net/posts/2016-08-18-the-stable-quick-sort/Q5.zip">Q5.txt&lt;/a>中共包含100000个&lt;strong>不同&lt;/strong>的整数，每行一个数。我们可以使用稳定版快速排序算法对其排序，并统计出其中的逆序数个数。&lt;/p>
&lt;p>具体的Python 3实现如下：&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">42
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">43
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">44
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">45
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">46
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># -*- coding: utf-8 -*-&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Created on Tue Oct 6 00:21:37 2015
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">@author: bitjoy
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> time
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>inversions &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">Partition&lt;/span>(A, p, r):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">global&lt;/span> inversions
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tmp &lt;span style="color:#f92672">=&lt;/span> [&lt;span style="color:#ae81ff">0&lt;/span>] &lt;span style="color:#f92672">*&lt;/span> (r&lt;span style="color:#f92672">-&lt;/span>p&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pivot &lt;span style="color:#f92672">=&lt;/span> A[p]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> k &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> range(p&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>, r&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>): &lt;span style="color:#75715e"># first&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> A[i] &lt;span style="color:#f92672">&amp;lt;&lt;/span> pivot:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tmp[k] &lt;span style="color:#f92672">=&lt;/span> A[i]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> inversions &lt;span style="color:#f92672">=&lt;/span> inversions &lt;span style="color:#f92672">+&lt;/span> i &lt;span style="color:#960050;background-color:#1e0010">–&lt;/span> k &lt;span style="color:#960050;background-color:#1e0010">–&lt;/span> p
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> k &lt;span style="color:#f92672">=&lt;/span> k &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tmp[k] &lt;span style="color:#f92672">=&lt;/span> pivot
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ans &lt;span style="color:#f92672">=&lt;/span> k &lt;span style="color:#f92672">+&lt;/span> p
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> k &lt;span style="color:#f92672">=&lt;/span> k &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> range(p&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>, r&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>): &lt;span style="color:#75715e"># second&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> A[i] &lt;span style="color:#f92672">&amp;gt;&lt;/span> pivot:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tmp[k] &lt;span style="color:#f92672">=&lt;/span> A[i]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> k &lt;span style="color:#f92672">=&lt;/span> k &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> k &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> range(p, r&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>): &lt;span style="color:#75715e"># third&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> A[i] &lt;span style="color:#f92672">=&lt;/span> tmp[k]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> k &lt;span style="color:#f92672">=&lt;/span> k &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> ans
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">QuickSortAndCount&lt;/span>(A, p, r):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> p &lt;span style="color:#f92672">&amp;lt;&lt;/span> r:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> q &lt;span style="color:#f92672">=&lt;/span> Partition(A, p, r)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> QuickSortAndCount(A, p, q&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> QuickSortAndCount(A, q &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>, r)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> __name__ &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;__main__&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Q5 &lt;span style="color:#f92672">=&lt;/span> open(&lt;span style="color:#e6db74">&amp;#39;Q5.txt&amp;#39;&lt;/span>, encoding &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;utf-8&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> data &lt;span style="color:#f92672">=&lt;/span> [ int(x) &lt;span style="color:#66d9ef">for&lt;/span> x &lt;span style="color:#f92672">in&lt;/span> Q5 ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Q5&lt;span style="color:#f92672">.&lt;/span>close()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> start &lt;span style="color:#f92672">=&lt;/span> time&lt;span style="color:#f92672">.&lt;/span>clock()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> QuickSortAndCount(data, &lt;span style="color:#ae81ff">0&lt;/span>, len(data) &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> end &lt;span style="color:#f92672">=&lt;/span> time&lt;span style="color:#f92672">.&lt;/span>clock()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;number of inversions:&lt;/span>&lt;span style="color:#e6db74">%d&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">time:&lt;/span>&lt;span style="color:#e6db74">%f&lt;/span>&lt;span style="color:#e6db74"> s&amp;#34;&lt;/span>&lt;span style="color:#f92672">%&lt;/span>(inversions,end&lt;span style="color:#f92672">-&lt;/span>start))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>虽然这种快排的时间复杂度还是O(nlgn)，但是在Partition函数中扫描了3次数组，并且借用了辅助数组tmp，不再是in-place排序算法，所以排序用时会比常规快排或者归并排序要慢。&lt;/p></description></item><item><title>Huffman编码压缩算法及其实现</title><link>https://bitjoy.net/posts/2016-08-18-the-implementation-of-huffman-code/</link><pubDate>Thu, 18 Aug 2016 09:54:55 +0800</pubDate><guid>https://bitjoy.net/posts/2016-08-18-the-implementation-of-huffman-code/</guid><description>&lt;p>哈弗曼编码是一个很经典的压缩算法，压缩率能达到50%，甚至更低。它的基本原理包括四个步骤：&lt;/p>
&lt;ol>
&lt;li>统计文件中每个字符出现的频率。&lt;/li>
&lt;li>构建一个哈弗曼树。建树的过程是不断的合并频率最小的两个节点，父亲节点的频率为两个孩子节点的频率之和。如此循环直到合并成一个根节点。叶子节点为不同的字符及其频率。&lt;/li>
&lt;li>生成哈弗曼编码。从树根开始对树进行编码，比如进入左孩子的边标记为0，进入右孩子的边标记为1，这里的0和1都是二进制位。这样之后，每个叶子节点都有一个唯一的二进制编码，这就是哈弗曼编码。频率越低的字符哈弗曼编码越长，频率越高的字符哈弗曼编码越短，这样就能起到压缩的效果。&lt;/li>
&lt;li>第二遍扫描文件，把字符转换为对应的哈弗曼编码，保存成压缩文件。&lt;/li>
&lt;/ol>
&lt;p>解压缩的过程就是解析二进制位，然后查找哈弗曼树，每找到一个叶子节点，就解析出一个字符，直到解析完所有二进制位。下面详细解释我的C++实现。&lt;/p>
&lt;p>首先定义一个哈弗曼编码类，对外只提供压缩Compress和解压缩Decompress两个接口。值得注意的是有一个Node结构体，用于构成哈弗曼树的节点。此外count_node的key是字符频率，value是所在节点，且是multimap类型的，所以count_node会自动按字符频率有小到大排序，在构建哈弗曼树时，每次只需要取count_node的前两个节点进行合并即可。&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">HuffmanCode&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> HuffmanCode();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">Compress&lt;/span>(string src, string dest);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">Decompress&lt;/span>(string src, string dest);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">virtual&lt;/span> &lt;span style="color:#f92672">~&lt;/span>HuffmanCode();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">private&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> CountLetter(string src);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">ConstructHuffmanTree&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">GenerateHuffmanCode&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">WriteHuffmanCode&lt;/span>(ofstream &lt;span style="color:#f92672">&amp;amp;&lt;/span>os);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">Compressing&lt;/span>(string src, string dest);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">InsertIntoHuffmanTree&lt;/span>(&lt;span style="color:#66d9ef">char&lt;/span> letter, string &lt;span style="color:#f92672">&amp;amp;&lt;/span>code, &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#f92672">&amp;amp;&lt;/span>k);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">ConstructHuffmanTreeFromFile&lt;/span>(ifstream &lt;span style="color:#f92672">&amp;amp;&lt;/span>is);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">Decompressing&lt;/span>(ifstream &lt;span style="color:#f92672">&amp;amp;&lt;/span>is, ofstream &lt;span style="color:#f92672">&amp;amp;&lt;/span>os);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> map&lt;span style="color:#f92672">&amp;lt;&lt;/span>&lt;span style="color:#66d9ef">char&lt;/span>, &lt;span style="color:#66d9ef">int&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span> letter_count;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">typedef&lt;/span> &lt;span style="color:#66d9ef">struct&lt;/span> &lt;span style="color:#a6e22e">Node&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> id;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">bool&lt;/span> is_leaf;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">char&lt;/span> letter;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> parent, lchild, rchild;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Node() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Node(&lt;span style="color:#66d9ef">int&lt;/span> i, &lt;span style="color:#66d9ef">bool&lt;/span> il, &lt;span style="color:#66d9ef">char&lt;/span> lt, &lt;span style="color:#66d9ef">int&lt;/span> p, &lt;span style="color:#66d9ef">int&lt;/span> lc, &lt;span style="color:#66d9ef">int&lt;/span> rc) &lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> id(i), is_leaf(il), letter(lt), parent(p), lchild(lc), rchild(rc) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> multimap&lt;span style="color:#f92672">&amp;lt;&lt;/span>&lt;span style="color:#66d9ef">int&lt;/span>, Node&lt;span style="color:#f92672">&amp;gt;&lt;/span> count_node;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> vector&lt;span style="color:#f92672">&amp;lt;&lt;/span>Node&lt;span style="color:#f92672">&amp;gt;&lt;/span> huffman_tree;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> map&lt;span style="color:#f92672">&amp;lt;&lt;/span>&lt;span style="color:#66d9ef">char&lt;/span>, vector&lt;span style="color:#f92672">&amp;lt;&lt;/span>&lt;span style="color:#66d9ef">char&lt;/span>&lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> letter_hcode; &lt;span style="color:#75715e">// hufman code for each letter
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>压缩函数Compress串起压缩的整个流程，包括统计字符频率、构建哈弗曼树、生成哈弗曼编码以及最后将原始文件转换成哈弗曼编码的二进制文件。&lt;/p></description></item><item><title>还原谷歌PageRank算法真相</title><link>https://bitjoy.net/posts/2016-08-04-googles-pagerank-and-beyond/</link><pubDate>Thu, 04 Aug 2016 16:38:33 +0800</pubDate><guid>https://bitjoy.net/posts/2016-08-04-googles-pagerank-and-beyond/</guid><description>&lt;p>之前写了&lt;a href="https://bitjoy.net/categories/%E5%92%8C%E6%88%91%E4%B8%80%E8%B5%B7%E6%9E%84%E5%BB%BA%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/">七篇博客&lt;/a>详细介绍了搜索引擎的工作原理。彼时的搜索引擎主要讲查询和网页的&lt;strong>相关性&lt;/strong>匹配，是动态的、在线的、实时的。相关性匹配有一个问题，网页很容易作弊，比如可以在一个网页中写满诸如“免费”、“美容”之类的垃圾关键词，进而提升查询相关性。但是用户在查询时，一定希望返回的网页比较&lt;strong>权威可信&lt;/strong>，比如同样搜索“苹果电脑”，排名第一的应该是Apple的官网，而不应该是中关村在线之类的第三方网站。&lt;/p>
&lt;p>权威性是一个静态的（或者说变化较慢的）衡量网页重要性的指标。但是应该怎样度量权威性呢，&lt;a href="https://en.wikipedia.org/wiki/HITS_algorithm">HITS算法&lt;/a>使用authority来度量，即指向自身的网页数量越多，则自身的authority值越大。谷歌的&lt;a href="https://en.wikipedia.org/wiki/PageRank">PageRank算法&lt;/a>是用PageRank值来衡量权威性的。&lt;a href="http://blog.sina.com.cn/s/blog_72995dcc01013bkb.html">HITS和PageRank一个比较大的区别是HITS和查询有关，而PageRank和查询无关，所以PageRank可以离线计算。&lt;/a>下面主要介绍PageRank算法。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/press.princeton.edu/images/k8216.gif">&lt;/p>
&lt;p>PageRank’s thesis is that a webpage is important if it is pointed to by other important pages.&lt;/p>
&lt;p>我先不加解释的给出PageRank的公式，然后带领大家一步步推导出这个公式。&lt;/p>
$$\pi^T=\pi^T(\alpha S+(1-\alpha)E)$$&lt;p>我们首先明确目标：PageRank计算的是网页的静态权威度（PR值），也就是如果给定了一个网络结构，则每个网页的PR值就可以通过PageRank算法计算出。假设网页\(P_i\)的PR值为\(r(P_i)\)，则\(r(P_i)\)等于所有指向\(P_i\)的网页的PR值之和，即&lt;/p>
$$\begin{equation}r(P_i)=\sum\limits_{P_j\in B_{P_i}}\frac{r(P_j)}{|P_j|}\end{equation}$$&lt;p>其中\(B_{P_i}\)为指向\(P_i\)的网页集合，\(|P_j|\)为\(P_j\)的出边的数量。这个式子很好理解，包括两方面内容：1）\(\sum\limits_{P_j\in B_{P_i}}\)表示如果指向\(P_i\)的网页数量越多，说明网页\(P_i\)越重要；2）\(\frac{r(P_j)}{|P_j|}\)表示如果\(P_j\)指向的页面数量越少，但有一个指向了\(P_i\)，说明网页\(P_i\)越重要（如果一个大牛写了很多推荐信（\(|P_j|\)大），则这些推荐信的效力就下降了，如果大牛只给你写了推荐信（\(|P_j|=1\)），则这封推荐信的效力一定很高）。&lt;/p>
&lt;p>(1)式有一个问题，初始给定一个网络结构时，并不知道\(r(P_i), r(P_j)\)，如何计算呢？Brin和Page利用递归的思想求解，初始假设所有网页的PR值相等，都为\(\frac{1}{n}\)，其中\(n\)为网络中网页的数量。则第\(k+1\)轮的PR计算公式为：&lt;/p>
$$\begin{equation}r_{k+1}(P_i)=\sum\limits_{P_j\in B_{P_i}}\frac{r_k(P_j)}{|P_j|}\end{equation}$$&lt;p>初始对所有网页\(P_i\)有\(r_0(P_i)=\frac{1}{n}\)，迭代\(k\)步之后，可以计算出所有网页的PR值，然后按PR值从大到小排序，就可以知道每个网页的重要性了。&lt;/p>
&lt;p>&lt;img alt="pr-1" loading="lazy" src="https://bitjoy.net/posts/2016-08-04-googles-pagerank-and-beyond/pr-1.png">
对于上图的小网络，我们可以计算出其每一步的PR值：&lt;/p>
&lt;p>&lt;img alt="pr-2" loading="lazy" src="https://bitjoy.net/posts/2016-08-04-googles-pagerank-and-beyond/pr-2.png">
可以看到经过2次迭代之后，节点4的PR值最大，从图中也可以看出，节点4的出入边较多，它可能比较重要。&lt;/p>
&lt;p>注意到对于(2)式，当\(i,j\)之间有边时，\(\frac{1}{|P_j|}\)相当于对\(P_j\)出度的归一化，设矩阵\(H\)为图的邻接矩阵的行归一化矩阵，对于上图，为&lt;/p>
&lt;p>&lt;img alt="pr-3" loading="lazy" src="https://bitjoy.net/posts/2016-08-04-googles-pagerank-and-beyond/pr-3.png">
设行向量\(\pi^{(k)T}\)为第\(k\)轮迭代时所有网页的PR值，则式(2)可以转换为如下的矩阵形式：&lt;/p>
$$\begin{equation}\pi^{(k+1)T}=\pi^{(k)T}H\end{equation}$$&lt;p>初始有\(\pi^{(0)T}=\frac{1}{n}e^T\)，\(e^T\)为全1的行向量。我们可以从(3)式观测出几点信息：&lt;/p>
&lt;ul>
&lt;li>(3)式的每一轮计算涉及到向量和矩阵的乘法，复杂度为\(O(n^2)\)，\(n\)为矩阵\(H\)的大小&lt;/li>
&lt;li>\(H\)是一个稀疏矩阵，因为大部分网页只和很少的网页有链接关系，所以上述向量和矩阵的乘法复杂度还可以降低&lt;/li>
&lt;li>\(H\)有点像马尔科夫链中的随机转移矩阵，但又不完全是，因为如果有dangling nodes，则这一行就是全0，所以\(H\)被称为substochastic matrix&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="pr-4" loading="lazy" src="https://bitjoy.net/posts/2016-08-04-googles-pagerank-and-beyond/pr-4.png">
上图中的节点3就是一个dangling node，它只有入边，没有出边，也就是说，每一轮迭代，PR值只会流入3号节点，不会从3号节点流出，久而久之，3就像一个水槽(sink)一样，吸走了大部分的PR，导致PR值虚高。&lt;/p>
&lt;p>所以问题随之而来，怎样保证(3)式一定能够收敛到一个平稳概率分布\(\pi^T\)，\(\pi^T\)和\(\pi^{(0)T}\)有关吗，怎样解决dangling nodes问题，等等。此时需要引入一点马尔科夫链理论的知识。&lt;/p>
&lt;p>在马尔科夫理论呢中，如果一个矩阵\(P\)是随机的（stochastic）、不可约的（irreducible）和非周期的（aperiodic），则对于任意的起始向量，都能收敛到一个唯一的平稳正向量。所以如果PageRank矩阵\(H\)满足上述三个条件，则可以用幂法（Power Method）找到一个平稳概率分布\(\pi^T\)。&lt;a href="https://en.wikipedia.org/wiki/Power_iteration">幂法&lt;/a>是用来计算最大特征值的特征向量。因为\(H\)的最大特征值为1，所以可以用幂法找到稳态时（\(\pi^T=\pi^TH\)）的概率分布\(\pi^T\)。&lt;/p>
&lt;p>下面我们就将矩阵\(H\)调整为随机的（stochastic）、不可约的（irreducible）和非周期的（aperiodic）。&lt;/p>
&lt;p>行随机矩阵是指行和为1的非负矩阵。如果图中含有dangling nodes，则\(H\)不是随机的，比如上面的例子，第二行为全0。所以第一个调整是对于所有dangling nodes，都加上一个随机跳转向量\(e^T/n\)，含义就是如果进入死胡同（dangling nodes），则随机跳转到网络中的任意一个网页。定义向量\(a\)：&lt;/p>
$$\begin{equation}a_i=\begin{cases}1\quad\text{if page}~i\text{ is a dangling node}\\0\quad\text{otherwise}\end{cases}\end{equation}$$&lt;p>则新的Google矩阵为：&lt;/p>
$$\begin{equation}S=H+a\frac{1}{n}e^T\end{equation}$$&lt;p>新矩阵\(S\)就是一个行随机矩阵了。对于上图的例子，有&lt;/p>
&lt;p>&lt;img alt="pr-5" loading="lazy" src="https://bitjoy.net/posts/2016-08-04-googles-pagerank-and-beyond/pr-5.png">
为了保证矩阵\(S\)满足&lt;a href="http://mathworld.wolfram.com/ReducibleMatrix.html">不可约性（irreducible）&lt;/a>和&lt;a href="https://en.wikipedia.org/wiki/Aperiodic_graph">非周期性（aperiodic）&lt;/a>，必须使\(S\)对应的图是强连通的且每个节点有自回路。所以再次调整为：&lt;/p>
$$\begin{equation}G=\alpha S+(1-\alpha)\frac{1}{n}ee^T\end{equation}$$&lt;p>令&lt;/p>
$$\begin{equation}E=\frac{1}{n}ee^T\end{equation}$$&lt;p>则得到本博客开头的Google矩阵公式：&lt;/p>
$$\begin{equation}G=\alpha S+(1-\alpha)E\end{equation}$$&lt;p>\(E\)即为随机平均游走矩阵。矩阵\(G\)也很好解释，大家上网的时候以\(\alpha\)的概率沿着某个网页里面的链接一步步深入进去（\(S\)），当沿着链接走累的时候，以\(1-\alpha\)的概率在地址栏输入一个新地址，随机跳走了（\(E\)）。&lt;/p>
&lt;p>此时的矩阵\(G\)满足随机性（stochastic）、不可约性（irreducible）和非周期性（aperiodic），所以可以根据幂法（Power Method）找到一个平稳概率分布\(\pi^T\)，\(\pi^T_i\)就衡量了网页\(P_i\)的重要性或者权威性。&lt;/p></description></item><item><title>调查问卷的有效性（2）相对误差</title><link>https://bitjoy.net/posts/2016-07-23-the-validity-of-the-questionnaire-2/</link><pubDate>Sat, 23 Jul 2016 16:21:54 +0800</pubDate><guid>https://bitjoy.net/posts/2016-07-23-the-validity-of-the-questionnaire-2/</guid><description>$$\begin{equation}Pr(|\hat{p}-p|\geq 5\%)\leq 5\%\end{equation}$$&lt;p>&lt;a href="https://bitjoy.net/posts/2016-07-23-the-validity-of-the-questionnaire-1/">上一回&lt;/a>我们讲到当\(p\)本身很小的时候，容易被5%（绝对误差）给淹没掉，导致结果的不可信。我们可以引入相对误差，把(1)式转换为如下的不等式&lt;/p>
$$\begin{equation}Pr(|\hat{p}-p|\geq\delta p)\leq\epsilon\end{equation}$$&lt;p>同理，我们可以用&lt;/p>
$$\begin{equation}\hat{p}=\frac{x_1+x_2+…+x_n}{n}\end{equation}$$&lt;p>代替\(\hat{p}\)（建议先看&lt;a href="https://bitjoy.net/posts/2016-07-23-the-validity-of-the-questionnaire-1/">上一篇博客&lt;/a>），转换为&lt;/p>
$$\begin{equation}Pr(|X-np|\geq\delta np)\end{equation}$$&lt;p>类似的，\(X=x_1+x_2+…+x_n\)，\(E(X)=\mu=np\)，所以(4)式等价为&lt;/p>
$$\begin{equation}Pr(|X-\mu|\geq\delta\mu)\end{equation}$$&lt;p>这个时候，因为不等号右边和均值\(\mu\)有关，不能再用切比雪夫不等式了，我们需要另外一个武器：&lt;a href="https://en.wikipedia.org/wiki/Chernoff_bound">Chernoff bound&lt;/a>。它有两种形式：&lt;/p>
$$\begin{equation}Pr(X\geq (1+\delta)\mu)\leq[\frac{e^\delta}{(1+\delta)^{1+\delta}}]^\mu\leq e^{-\frac{\mu}{3}\delta^2}\quad\forall\delta>0\end{equation}$$$$\begin{equation}Pr(X\leq (1-\delta)\mu)\leq[\frac{e^{-\delta}}{(1-\delta)^{1-\delta}}]^\mu\leq e^{-\frac{\mu}{2}\delta^2}\quad\forall 0&lt;\delta&lt;1\end{equation}$$&lt;p>Chernoff bound的证明需要用到马尔可夫不等式，有一点技巧。以上两种形式可以统一成&lt;/p>
$$\begin{equation}Pr(|X-\mu|\geq\delta\mu)\leq 2e^{-\frac{\mu}{3}\delta^2}\end{equation}$$&lt;p>也是一个很漂亮的不等式。&lt;/p>
&lt;p>利用Chernoff bound求解(5)式：&lt;/p>
$$\begin{equation}Pr(|X-\mu|\geq\delta\mu)\leq 2e^{-\frac{\mu}{3}\delta^2}\\=2e^{-\frac{np}{3}\delta^2}\leq\epsilon\end{equation}$$&lt;p>解得&lt;/p>
$$\begin{equation}n\geq\left\lceil\frac{3ln\frac{2}{\epsilon}}{p\delta^2}\right\rceil\end{equation}$$&lt;p>这个结果看起来就很复杂了。也就是说，如果要设计调查问卷使满足(2)式的精度，抽样的样本数必须满足(10)式。从(10)式可知，当要求的精度越高（即\(\delta\)和\(\epsilon\)越小），所需的样本数越大。并且结果还和真实值\(p\)有关。&lt;/p></description></item><item><title>调查问卷的有效性（1）绝对误差</title><link>https://bitjoy.net/posts/2016-07-23-the-validity-of-the-questionnaire-1/</link><pubDate>Sat, 23 Jul 2016 16:01:59 +0800</pubDate><guid>https://bitjoy.net/posts/2016-07-23-the-validity-of-the-questionnaire-1/</guid><description>&lt;p>每年春晚过后，央视又要吹嘘说今年春晚收视率创新高了，但是我们总感觉央视在骗我们，因为我是越长大越不看春晚了[笑cry]，所以收视率到底是怎么统计出来的，央视的说法是否靠谱呢？&lt;/p>
&lt;p>最近的美国大选真是热闹，很多机构都会发放一些调查问卷，然后统计出希拉里或者唐纳德的民众支持率是多少，但是我并没有收到调查问卷，凭什么就得出了民众支持率了，意思是把我排除在民众之外咯？所以引出这样一个问题，调查问卷是否可信，即调查问卷的有效性。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/www.carp.ca/wp-content/uploads/2012/08/questionnaire1.jpg">&lt;/p>
&lt;p>其实，央视统计收视率并不要问全中国14亿人口有多少人看了春晚，他只需要从14亿人口里面随机抽\(n\)个人，问一下这\(n\)个人里有多少人看了春晚，然后把看的人数除以总数就大概估计出全国的收视率了。同理调查民众支持率也是一样，只需要随机调查\(n\)个人的意向，把支持希拉里的人数除以总数就大概得到了希拉里的支持率。&lt;/p>
&lt;p>但是你要问了，通过抽样调查出来的收视率和支持率靠谱吗，需要随机抽样多少人才能得到一个比较好的全局近似解呢？今天我们就来解决这个问题。&lt;/p>
&lt;p>假设我们随机抽样了\(n\)个人，分别是\(x_1,x_2,…,x_n\)。如果第\(i\)个人看了春晚，则\(x_i=1\)，否则\(x_i=0\)。那么通过这\(n\)个人的收视情况，我们可以估计出一个收视率&lt;/p>
$$\begin{equation}\hat{p}=\frac{x_1+x_2+…+x_n}{n}\end{equation}$$&lt;p>假设全国的真实收视率是\(p\)，那么平均到每一个人，他看了春晚的概率就是\(p\)，也即\(Pr(x_i=1)=p\)，所以有&lt;/p>
$$\begin{equation}E(x_i)=p\quad E(x_i^2)=p\quad Var(x_i)=p(1-p)\end{equation}$$&lt;p>我们的目的就是希望通过\(n\)个人估计出来的\(\hat{p}\)和\(p\)越接近越好。换句话说，我们希望\(\hat{p}\)和\(p\)相差大于5%的概率要小于5%。再换句话说就是有至少95%的概率，\(\hat{p}\)和\(p\)相差在5%以内，即\(\hat{p}\)和\(p\)很接近。注意这里的两个5%都是可以换成任意你想要的精度。用数学语言表示就是，\(n\)至少为多少时，以下不等式可以被满足。&lt;/p>
$$\begin{equation}Pr(|\hat{p}-p|\geq 5\%)\leq 5\%\end{equation}$$&lt;p>把(1)式代入(3)式，用\(\frac{1}{20}\)代替5%，得到等价形式：&lt;/p>
$$\begin{equation}Pr(|(\frac{x_1+x_2+…+x_n}{n})-p|\geq\frac{1}{20})\\ \Longleftrightarrow~Pr(|X-np|\geq\frac{n}{20})\end{equation}$$&lt;p>其中\(X=x_1+x_2+…+x_n\)。根据期望的线性可加性，有&lt;/p>
$$\begin{equation}E(X)=E(x_1+x_2+…+x_n)=E(x_1)+E(x_2)+…+E(x_n)=np\end{equation}$$&lt;p>所以(4)又等价于&lt;/p>
$$\begin{equation}Pr(|X-E(X)|\geq\frac{n}{20})\end{equation}$$&lt;p>我们需要利用著名的&lt;a href="https://en.wikipedia.org/wiki/Chebyshev%27s_inequality">切比雪夫不等式&lt;/a>来求解上式，切比雪夫不等式如下：&lt;/p>
$$\begin{equation}Pr(|X-E(X)|\geq~c)\leq\frac{Var(X)}{c^2}\end{equation}$$&lt;p>切比雪夫不等式可以直接由&lt;a href="https://en.wikipedia.org/wiki/Markov%27s_inequality">马尔可夫不等式&lt;/a>得到，马尔可夫不等式的证明也不难，略过。&lt;/p>
&lt;p>利用切比雪夫不等式求解(6)式&lt;/p>
$$\begin{equation}Pr(|X-E(X)|\geq\frac{n}{20})\leq\frac{Var(X)}{n^2}*400\\ =\frac{n*Var(x_i)}{n^2}*400\\ =\frac{p(1-p)}{n}*400\\ \leq\frac{1/4}{n}*400=\frac{100}{n} \end{equation}$$&lt;p>第一个等号是因为\(n\)个变量是独立同分布的，所以方差也有类似于(5)式的线性性质。最后一个不等号是因为\(p(1-p)\)是一个开口向下的抛物线，在\(p=1/2\)时取到极值\(1/4\)。&lt;/p>
&lt;p>回到最初的不等式(3)，则(8)式要满足\(\frac{100}{n}\leq 5\%\)，解得\(n\geq 2000\)。注意到求出的\(n\)和总体人数是无关的，也就是说，虽然全中国有十几亿人口，但是央视只要随机抽样调查2000个人的收视情况，就能以比较高的概率准确估计出全国的收视率。&lt;/p>
&lt;p>这个结论还是很漂亮的，但是这种方法有两个限制条件：&lt;/p>
&lt;ol>
&lt;li>采样满足独立同分布，即这\(n\)个人是独立同分布的，不能针对某一特定人群调查&lt;/li>
&lt;li>(3)式的5%是一个绝对误差，当\(p\)本身很小的时候，容易被5%淹没&lt;/li>
&lt;/ol>
&lt;p>对于第1个问题，稍微好处理一点，抽样的时候尽量随机一点。对于第2个问题，比较好的解决办法是引入相对误差，即把(3)式转换为如下的不等式&lt;/p>
$$\begin{equation}Pr(|\hat{p}-p|\geq\delta p)\leq\epsilon\end{equation}$$&lt;p>(9)式的求解就比较复杂了，得出的结论也没有上面那么简单，具体的求解方法请听下回分解。&lt;/p></description></item><item><title>有趣的交互式证明</title><link>https://bitjoy.net/posts/2016-07-14-the-interesting-interactive-proofs/</link><pubDate>Thu, 14 Jul 2016 20:56:37 +0800</pubDate><guid>https://bitjoy.net/posts/2016-07-14-the-interesting-interactive-proofs/</guid><description>&lt;p>你是否想过如下问题：怎样向色盲证明两只袜子的颜色是不一样的？怎样证明两个图是不同构的？怎样证明一个数是二次非剩余的？&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/writtenbyrel.com/wp-content/uploads/2014/06/socks.jpg">&lt;/p>
&lt;p>咋听起来觉得很有意思吧，色盲是区分不了颜色的，怎么能让他相信两只袜子的颜色不一样呢。图同构问题目前既没有被证明属于P，也没有被证明属于NP-Complete。二次非剩余问题也没有被证明属于NP。&lt;/p>
&lt;p>这些听起来很“难”的问题，却可以通过交互式证明进行证明，下面先通过“向色盲证明两只袜子的颜色不同”这个有趣的例子一窥交互式证明的强大。&lt;/p>
&lt;h1 id="向色盲证明两只袜子的颜色不同">向色盲证明两只袜子的颜色不同&lt;/h1>
&lt;p>P有一只红袜子和黄袜子，她的一个色盲朋友V不相信P的袜子颜色不同，P如何才能让V相信这是真的呢？一个简单的办法如下：&lt;/p>
&lt;ol>
&lt;li>P把两只袜子给V，V每只手拿了一只袜子&lt;/li>
&lt;li>P转过身背对V&lt;/li>
&lt;li>V抛一枚硬币，如果头面朝上，则保持两只袜子不动，否则交换左右手的袜子&lt;/li>
&lt;li>P转过身，V问P是否交换过袜子&lt;/li>
&lt;li>如果P回答错误，则V不相信；否则，重复100次实验，如果P都回答正确，则V相信这两只袜子是不同颜色的&lt;/li>
&lt;/ol>
&lt;p>如果两只袜子的颜色确实不一样，则P通过区分两只袜子的颜色能正确回答V有没有交换过袜子。但是如果两只袜子颜色一样，则不管V有没有交换过，P都无法分辨这两只袜子，所以只好猜V有没有交换，而猜对的概率只有1/2，重复100次，都猜对的概率只有\((1/2)^{100}\)，这是一个非常小的数，可以认为几乎不会发生，即出错的概率极低。&lt;/p>
&lt;p>这就是交互式证明的一个例子，上述证明有三个特点：1）交互过程，整个证明需要P和V进行交互才能完成；2）具有随机性，即V需要抛一枚硬币，来决定是否交换袜子；3）零知识，虽然V最终相信了这两只袜子是不同颜色的，但V还是不知道这两只袜子是什么颜色的。&lt;/p>
&lt;p>下面我们给出交互式证明的形式化定义。&lt;/p>
&lt;h1 id="交互式证明interactive-proofs-ip">交互式证明（Interactive Proofs, IP）&lt;/h1>
&lt;p>令\(k\)是\(N\rightarrow N\)的一个函数，我们称语言\(L\)属于\(IP[k]\)，如果存在一个\(k(|x|)\)多项式时间概率图灵机TM \(V\)，使得：&lt;/p>
$$
\begin{equation}
\begin{cases}
x\in L \Longrightarrow\exists P\quad Pr[V \text{ accepts }x, V(x)=1]\geq 2/3 \\
x\notin L \Longrightarrow\forall P\quad Pr[V \text{ accepts }x, V(x)=1]\leq 1/3
\end{cases}
\end{equation}
$$&lt;p>定义&lt;/p>
$$IP=\underset{c}{\bigcup} IP[n^c]$$&lt;p>上述定义的第一条称为“完备性”（Completeness）：如果\(x\in L\)，则存在一个证明者P（prover），使得验证者V（verfier）能以多项式时间接受\(x\)，且接受的概率大于2/3；第二条称为“可靠性”（Soundness），如果\(x\notin L\)，则对于所有证明者P，V接受\(x\)的概率都不会超过1/3。&lt;/p>
&lt;p>对应到上面的例子，完备性：当两只袜子的颜色确实不同时，V接受的概率为1&amp;gt;2/3；可靠性：当两只袜子的颜色相同时，重复100次实验，V接受的概率只有\((1/2)^{100}&lt;1/3\)。&lt;/p>
&lt;p>IP这个复杂性类就是所有IP[k]的并集。在IP中，P的能力是无穷的，但它不一定是诚实的；V能力较弱，只能进行多项式时间的计算。&lt;/p>
&lt;p>下面我们给出另外两个交互式证明的协议。&lt;/p>
&lt;h1 id="图不同构graph-non-isomorphism-gni的交互式证明">图不同构（Graph Non-isomorphism, GNI）的交互式证明&lt;/h1>
&lt;p>如果图\(G_1\)和\(G_2\)可以通过对顶点进行恰当标记来将它们转换为同一个图，则称\(G_1\)和\(G_2\)同构，记为\(G_1\cong G_2\)。换句话说，如果\(G_1\)和\(G_2\)同构，则在\(G_1\)的所有顶点标签上存在一个置换\(\pi\)使得\(\pi (G_1)=G_2\)，其中\(\pi (G_1)\)是将\(\pi\)作用到\(G_1\)的各个顶点上之后得到的图。下图就是两个同构图，右边给出了置换\(\pi\)。&lt;/p>
&lt;p>&lt;img alt="isomorphism graph" loading="lazy" src="https://bitjoy.net/posts/2016-07-14-the-interesting-interactive-proofs/isomorphism-graph.webp">&lt;/p>
&lt;p>图同构的补集为图不同构（Graph Non-isomorphism, GNI），即判定给定的两个图是否不同构。下面是GNI的一个交互式证明过程。&lt;/p>
&lt;p>给定两个图\(G_1\)和\(G_2\)，证明者P想要向验证者V证明\(G_1\ncong G_2\)。&lt;/p>
&lt;ol>
&lt;li>V：随机选一个\(i\in \{1,2\}\)，对\(G_i\)做一个随机的置换，得到新图\(H\)，则有\(H\cong G_i\)，将\(H\)发送给P&lt;/li>
&lt;li>P：发送\(j\)给V&lt;/li>
&lt;li>V：如果\(i\neq j\)，则拒绝；否则重复100次实验，都有\(i==j\)，则相信\(G_1\ncong G_2\)&lt;/li>
&lt;/ol>
&lt;p>完备性：如果\(G_1\ncong G_2\)，则\(H\)只和\(G_1, G_2\)中的一个图同构，P因为能力无穷，一定能找出和\(H\)同构的图\(G_j\)，且满足\(j==i\)。&lt;/p></description></item><item><title>LaTeX写作完美解决方案</title><link>https://bitjoy.net/posts/2016-05-16-an-easy-to-use-latex-toolkit/</link><pubDate>Mon, 16 May 2016 20:14:50 +0800</pubDate><guid>https://bitjoy.net/posts/2016-05-16-an-easy-to-use-latex-toolkit/</guid><description>&lt;p>&lt;img loading="lazy" src="https://www.ctan.org/lion/ctan_lion_350x350.png">&lt;/p>
&lt;p>\(\LaTeX\)的强大我就不赘述了，这里简单介绍一下怎样在Windows快速配置一个完美好用的\(\LaTeX\)环境。&lt;/p>
&lt;p>我大学刚接触\(\LaTeX\)的时候，使用的是&lt;a href="http://www.ctex.org/HomePage">CTeX&lt;/a>，CTeX是一个大礼包，整合了编译器编辑器等，但是由于久不更新，很多宏包和语法都变了，而且CTeX附带的WinEdt是商业软件，30天之后需要收费，我又不想用盗版，所以就打算自己配置\(\LaTeX\)环境。&lt;/p>
&lt;p>目前使用的是&lt;a href="http://miktex.org/">MiKTeX&lt;/a>+&lt;a href="http://www.xm1math.net/texmaker/">Texmaker&lt;/a>的完美组合！MiKTeX是\(\LaTeX\)编译器，Texmaker是\(\LaTeX\)编辑器。两者都是开源软件。&lt;/p>
&lt;p>MiKTeX非常棒的地方在于“MiKTeX has the ability to install needed packages automatically (on-the-fly)”，就是说，你用MiKTeX时，不需要担心某个宏包是否存在，你只管用就是了，MiKTeX会在你第一次用到某个宏包时，自动从网上下载，非常方便。正因为这样，MiKTeX的安装包很小，只有175MB。当然，因为是on-the-fly的，所以必须联网使用，而且MiKTeX只有Windows版本。&lt;/p>
&lt;p>MiKTeX自带了一个&lt;a href="https://www.tug.org/texworks/">TeXworks&lt;/a>编辑器的，但是这软件用户体验并不好。我以前一直都用WinEdt，很好用，但是它是商业软件，我又不想盗版（说到底是没钱…），所以换了Texmaker。Texmaker可以媲美WinEdt，软件布局合理，各种快捷键用起来也很方便。不过在上手之前要简单配置一下。&lt;/p>
&lt;p>如果是写英文文章，点击“快速构建”左边的箭头（或者F1快捷键），就能一键编译并刷新pdf视图。但是默认的快速构建使用的引擎是PdfLaTeX，如果你是中文用户，使用了xeCJK宏包，则必须使用XeLaTeX引擎编译，所以依次点击“选项-&amp;gt;配置Texmaker-&amp;gt;（左边）快速构建”，选择快速构建命令为”XeLaTeX + View PDF”。&lt;/p>
&lt;p>&lt;img alt="Texmaker-1" loading="lazy" src="https://bitjoy.net/posts/2016-05-16-an-easy-to-use-latex-toolkit/Texmaker-1.png">&lt;/p>
&lt;p>构建好的PDF默认是以弹窗的形式展现的，我们可以设置让代码和PDF并排显示，这样方便在PDF和源代码之间切换，配置如下：&lt;/p>
&lt;p>&lt;img alt="Texmaker-2" loading="lazy" src="https://bitjoy.net/posts/2016-05-16-an-easy-to-use-latex-toolkit/Texmaker-2.png">&lt;/p>
&lt;p>Texmaker自带了一个PDF阅读器，当然你也可以使用外部阅读器，比如非常棒的&lt;a href="http://www.sumatrapdfreader.org/free-pdf-reader.html">Sumatra PDF&lt;/a>，只需填入Sumatra PDF的路径跟上%.pdf，并选中External Viewer。&lt;/p>
&lt;p>Texmaker还有一个很好用的功能是“正向/反向搜索”。反向搜索是点击PDF某个位置，会跳到tex源代码对应位置，快捷方式是ctrl+click。正向搜索是点击tex源代码某个位置，会跳到PDF对应的位置，默认快捷方式ctrl+space，但是这个快捷方式好像用不了，可以自行配置成其他快捷方式，比如ctrl+1，我当时是打开下图的快捷方式窗口才发现这个问题的。&lt;/p>
&lt;p>&lt;img alt="Texmaker-3" loading="lazy" src="https://bitjoy.net/posts/2016-05-16-an-easy-to-use-latex-toolkit/Texmaker-3.png">&lt;/p>
&lt;p>正反向搜索都可以通过鼠标右键菜单实现，但是快捷键还是更方便的。最重要的一点是，源文件*.tex所在路径不能有中文！！！要不然正反向搜索不能用，这点很重要，我当时郁闷了好久。&lt;/p>
&lt;p>另外还可以配置一下编辑器的字体，勾选”Backup documents every 10 min”之类的。&lt;/p>
&lt;p>OK，大功告成，这种三段式的界面、F1快速构建以及正向/反向搜索，用起来真是太顺手了，Just Enjoy \(\LaTeX\)~&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/www.xm1math.net/texmaker/texmakertop_big.png">&lt;/p>
&lt;p>下面是我常用的\(\LaTeX\)中文模板：&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-05-16-an-easy-to-use-latex-toolkit/LaTeXDemo.pdf">LaTeXDemo.pdf&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-05-16-an-easy-to-use-latex-toolkit/LaTeXDemo.tex">LaTeXDemo.tex&lt;/a>&lt;/p></description></item><item><title>SVM之序列最小最优化算法（SMO算法）</title><link>https://bitjoy.net/posts/2016-05-02-svm-smo-algorithm/</link><pubDate>Mon, 02 May 2016 15:12:39 +0800</pubDate><guid>https://bitjoy.net/posts/2016-05-02-svm-smo-algorithm/</guid><description>&lt;h1 id="svm回顾">SVM回顾&lt;/h1>
&lt;p>&lt;img loading="lazy" src="https://upload.wikimedia.org/wikipedia/commons/2/20/SVM_Example_of_Hyperplanes.png">&lt;/p>
&lt;p>支持向量机（SVM）的一大特点是最大化间距（max margin）。对于如上图的二分类问题，虽然有很多线可以将左右两部分分开，但是只有中间的红线效果是最好的，因为它的可活动范围（margin）是最大的，从直观上来说很好理解。&lt;/p>
&lt;p>对于线性二分类问题，假设分类面为&lt;/p>
$$\begin{equation} u=\vec w \cdot \vec x-b \end{equation}$$&lt;p>则margin为&lt;/p>
$$\begin{equation} m=\frac{1}{||w||_2} \end{equation}$$&lt;p>根据max margin规则和约束条件，得到如下优化问题，我们要求的就是参数\(\vec w\)和\(b\)：&lt;/p>
$$\begin{equation} \min\limits_{\vec w,b}\frac{1}{2}||\vec w||^2 \quad\text{subject to}\quad y_i(\vec w\cdot \vec x_i-b) \geq 1, \forall i,\end{equation}$$&lt;p>对于正样本，类标号\(y_i\)为+1，反之则为-1。根据拉格朗日对偶，(3)可以转换为如下的二次规划（QP）问题，其中\(\alpha_i\)为拉格朗日乘子。&lt;/p>
$$\begin{equation} \min\limits_{\vec \alpha}\Psi(\vec\alpha)=\min\limits_{\vec \alpha}\frac{1}{2}\sum_{i=1}^N\sum_{j=1}^Ny_iy_j(\vec x_i\cdot\vec x_j)\alpha_i\alpha_j-\sum_{i=1}^N\alpha_i,\end{equation}$$&lt;p>其中N为样本数量。上式还需满足如下两个约束条件：&lt;/p>
$$\begin{equation} \alpha_i\geq 0, \forall i,\end{equation}$$$$\begin{equation} \sum_{i=1}^Ny_i\alpha_i=0.\end{equation}$$&lt;p>一旦求解出所有的拉格朗日乘子，则我们可以通过如下的公式得到分类面参数\(\vec w\)和\(b\)。&lt;/p>
$$\begin{equation}\vec w=\sum_{i=1}^Ny_i\alpha_i\vec x_i,\quad b=\vec w\cdot\vec x_k-y_k\quad\text{for some}\quad\alpha_k>0.\end{equation}$$&lt;p>当然并不是所有的数据都可以完美的线性划分，可能有少量数据就是混在对方阵营，这时可以通过引入松弛变量\(\xi_i\)得到软间隔形式的SVM：&lt;/p>
$$\begin{equation} \min\limits_{\vec w,b,\vec\xi}\frac{1}{2}||\vec w||^2+C\sum_{i=1}^N\xi_i \quad\text{subject to}\quad y_i(\vec w\cdot \vec x_i-b) \geq 1-\xi_i, \forall i,\end{equation}$$&lt;p>其中的\(\xi_i\)为松弛变量，能假装把错的样本分对，\(C\)对max margin和margin failures的trades off。对于这个新的优化问题，约束变成了一个box constraint：&lt;/p>
$$\begin{equation}0\leq\alpha_i \leq C,\forall i.\end{equation}$$&lt;p>而松弛变量\(\xi_i\)不再出现在对偶公式中了。&lt;/p></description></item><item><title>和GRE纠缠的岁月</title><link>https://bitjoy.net/posts/2016-03-16-hello-gre-do-you-like-me/</link><pubDate>Wed, 16 Mar 2016 11:44:37 +0800</pubDate><guid>https://bitjoy.net/posts/2016-03-16-hello-gre-do-you-like-me/</guid><description>&lt;p>&lt;img alt="GRE3000" loading="lazy" src="https://bitjoy.net/posts/2016-03-16-hello-gre-do-you-like-me/GRE3000.jpg">&lt;/p>
&lt;p>yn说最近在备考GMAT和托福，把手机都清理了只为专心学习。xx说TCP/IP大作业要用Qt做一个网络监控的软件，问我Qt好不好学。&lt;/p>
&lt;p>GRE遇见Qt，会擦出怎样的火花呢~没错，我用Qt写了一个强化背诵GRE单词的软件——Cracking 3000&lt;/p>
&lt;p>&lt;img alt="Cracking-3000-1" loading="lazy" src="https://bitjoy.net/posts/2016-03-16-hello-gre-do-you-like-me/Cracking-3000-1.png"> &lt;img alt="Cracking-3000-2" loading="lazy" src="https://bitjoy.net/posts/2016-03-16-hello-gre-do-you-like-me/Cracking-3000-2.png">&lt;/p>
&lt;p>当时的一本GRE单词书有3000个单词，用杨鹏17天刷过之后，很多都记不住，于是想有没有办法把记不住的单词筛选出来，集中力量强化记忆。网上已经有3000的Excel表，所以我很自然的想到了把表格导入软件，用软件快速测试，并把不认识的单词筛选到新的Excel表格中。这样就可以把不认识的单词表打印出来，记完之后再导入软件进行新一轮的测试筛选，直到不认识的单词数为零。&lt;/p>
&lt;p>有了软件需求，代码实现起来就很快了。由于当时用Qt库比较多，所以直接拿来用了。软件实现这一块，主要是Excel表格的导入和导出，需要查一些文档，其他的就很简单了。&lt;/p>
&lt;p>&lt;img alt="GRE3000-2" loading="lazy" src="https://bitjoy.net/posts/2016-03-16-hello-gre-do-you-like-me/GRE3000-2.jpg">&lt;/p>
&lt;p>虽然和GRE纠缠了一个多月，最终却没有考，但是想起当初早上6点爬起来背单词，晚上回宿舍抹黑写代码的情景，心情还是有一点小小的激动！以后类似的体验估计不会太多吧。&lt;/p>
&lt;p>最后祝yn GT考试顺利，xx大作业圆满完成！&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-03-16-hello-gre-do-you-like-me/Cracking_3000.zip">Cracking_3000软件安装包及说明&lt;/a>&lt;/p></description></item><item><title>爸妈老了</title><link>https://bitjoy.net/posts/2016-02-26-leave-home-again/</link><pubDate>Fri, 26 Feb 2016 11:30:17 +0800</pubDate><guid>https://bitjoy.net/posts/2016-02-26-leave-home-again/</guid><description>&lt;p>刚坐上去北京的列车，就收到了妈妈的微信语音：霖，早上收拾东西怎么忘了带上我给你洗好的鞋呀。我这才想起早上妈妈把洗好的鞋和叠好的衣服放在我房间，我却忘了带鞋。&lt;/p>
&lt;p>后来和爸妈在群里聊了起来。当我问爸爸什么时候返回学校时，他却说前天突然请假回家惹老板不高兴了，可能要被炒鱿鱼。是，老爸在那个学校当老师十几年了，我平时老数落他当老师工资那么低，为什么不改行，可突然听到这个消息，心里却不是滋味。&lt;/p>
&lt;p>其实老爸没必要请假回来的。前几天我发脾气，老爸好像真的决定转行搞种植业了，托我在淘宝买了好多枸杞树，自己带回了五十棵脐橙树苗，还准备去某个地方考察什么药材。&lt;/p>
&lt;p>离家前一天，妈妈特地跑到县城买了好多排骨回来，还煮了十个土鸡蛋要我带着路上吃。老爸买了好多苹果、香蕉、猕猴桃要我带着路上吃。今天早上收拾行李的时候，从来不动手的爸爸，也抢着往我包里塞各种牛奶和水果。&lt;/p>
&lt;p>二十多年了，经历了多少次的离家，从来没有像今天这样的不舍。二十多年了，我突然发现爸爸妈妈变矮了，爸爸的额头黑得发亮，妈妈的眼角也长出了好多鱼尾纹。&lt;/p>
&lt;p>今年难得有一个月的寒假，但我整天忙着看论文、书和电视剧，和爸妈的交流反而少了。有天吃过晚饭，发现妈妈独自坐在客厅戳着她的手机。我问过才发现原来妈妈想看哥哥和他女朋友的照片，但是怎么都弄不出来，我帮妈妈找出来之后，还教她怎么用微信和qq，妈妈说wifi图标像降落伞，我说你什么时候想上网就把降落伞打开，我说你如果想和哥哥聊天，就按住底部的按钮，等到出现小喇叭之后就可以说话了，说完放开手，听到“嗖“”的一声，说的话就发过去了，但是妈妈经常忘记打开降落伞，经常忘记按小喇叭。。。&lt;/p>
&lt;p>刚刷QQ空间的时候，看到一个同学的说说“马上又要去坐火车回武汉了，在家的时间越来越少了，没能好好陪陪父母，我不是称职的儿子。”&lt;/p>
&lt;p>坐在火车上，看着窗外闪过的霓虹灯，突然觉得这个世界好陌生好无情，每个人在时间面前是多么的渺小。&lt;/p>
&lt;p>2016年2月26日于z68列车上。&lt;/p></description></item><item><title>国科大半年体验报告</title><link>https://bitjoy.net/posts/2016-02-04-half-year-experience-report-in-ucas/</link><pubDate>Thu, 04 Feb 2016 00:42:30 +0800</pubDate><guid>https://bitjoy.net/posts/2016-02-04-half-year-experience-report-in-ucas/</guid><description>&lt;p>现在是2016年2月4日，距离农历新年不到4天，结束了半年的国科大研一生活，躺在被窝里，松了一口气……&lt;/p>
&lt;p>&lt;img alt="ucas-2015-1" loading="lazy" src="https://bitjoy.net/posts/2016-02-04-half-year-experience-report-in-ucas/ucas-2015-1.jpg">&lt;/p>
&lt;p>来国科大之前，在贴吧上了解到国科大雁栖湖校区地处偏远农村，周边几乎没有娱乐场所；但同时学校的软硬件设施非常的棒：豪华单人间，研究员甚至院士亲自授课等等。所以对国科大雁栖湖校区满是憧憬。至今还清楚的记得坐校车从玉泉路过来时，沿途看到APEC主会场的鸟蛋、国科大桥、钟楼以及国科大正门几个大字时的激动心情~&lt;/p>
&lt;p>&lt;img alt="ucas-2015-7" loading="lazy" src="https://bitjoy.net/posts/2016-02-04-half-year-experience-report-in-ucas/ucas-2015-7.jpg"> &lt;img alt="ucas-2015-3" loading="lazy" src="https://bitjoy.net/posts/2016-02-04-half-year-experience-report-in-ucas/ucas-2015-3.jpg">&lt;/p>
&lt;p>入住国科大，着实被UCAS的蓝天白云、青山绿水给迷住了。&lt;/p>
&lt;p>&lt;img alt="ucas-2015-2" loading="lazy" src="https://bitjoy.net/posts/2016-02-04-half-year-experience-report-in-ucas/ucas-2015-2.jpg"> &lt;img alt="ucas-2015-4" loading="lazy" src="https://bitjoy.net/posts/2016-02-04-half-year-experience-report-in-ucas/ucas-2015-4.jpg"> &lt;img alt="ucas-2015-5" loading="lazy" src="https://bitjoy.net/posts/2016-02-04-half-year-experience-report-in-ucas/ucas-2015-5.jpg"> &lt;img alt="ucas-2015-6" loading="lazy" src="https://bitjoy.net/posts/2016-02-04-half-year-experience-report-in-ucas/ucas-2015-6.jpg">&lt;/p>
&lt;p>当然，凡事有利必有弊，因为这里远离市区，环境好，但正因为远离市区，几乎没有年轻人的娱乐活动，想要看个电影唱个歌少说也得跑城里，再要想感受下帝都奢靡的生活，必须各种倒车近2个小时到市里。&lt;/p>
&lt;p>研一这上学期，半年只进市里两次，一次是买山地车，一次是回所里开会。购物主要靠天猫超市。&lt;/p>
&lt;p>九十月份，大家都和大一新生似的，各种疯玩，野长城、雁栖湖、慕田峪、青龙峡、密云水库。进入十一月，新鲜劲过去了，又开始各种赶大小作业，复习考试。&lt;/p>
&lt;p>&lt;img alt="ucas-schedule-2015-fall" loading="lazy" src="https://bitjoy.net/posts/2016-02-04-half-year-experience-report-in-ucas/ucas-schedule-2015-fall.png">&lt;/p>
&lt;p>这是我这学期的课表，看着课好像不多，每天都有半天休息，但是真的感觉回到了大三呀！尤其数据挖掘、信息检索、矩阵论一周上两次课，当天上完的课如果没有及时复习，隔一天再学新内容完全跟不上啊，而且矩阵论每次课都有好多作业啊，这数学课不做练习完全消化不了呀。更神的课还要数周五的卜神算法，君不见，每到周四晚上，西A、西B两栋宿舍，灯火通明，大家都在熬夜赶算法作业啊，不熬个两三点都不好意思和别人说你熬夜了呀。&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/">大家可以感受一下我整理的卜神算法作业~~&lt;/a>&lt;/p>
&lt;p>正是因为这奇葩的课程安排，这半年几乎没有12点前睡过觉，估计平均是1:30才睡觉，早上8点多才起，中午也没午休。想想大学的时候按时作息，真是惭愧。期间有一次听说搜狐一同届华科毕业生猝死，朋友圈传得沸沸扬扬，大伙都吓得要命，纷纷表示绝不熬夜，早睡早起，我那天也是吓坏了，决定早睡，11:30就爬床上了，但是不知道是因为紧张还是熬夜习惯了，辗转反侧，到12点多才睡着的。&lt;/p>
&lt;p>我们研一在国科大上课是有补助的，但是在帝都完全不够用啊，而且CS相关的几个所补助都比ICT高，so当时还公车上书，各种写联名信、起义，经过半年之久的持久战，所里终于答应从2016年开始给我们涨500块钱的工资。涨了之后差不多够吃饭了。&lt;/p>
&lt;p>虽然这半年课业繁重，但是也抽空锻炼了身体，天气不是很冷的时候，隔一天就会去夜跑；而且选了乒乓球课，从直拍转为了横拍，并且在课上结识了路路，打球好厉害的一个女生，每次老师来指导的时候，都叫路路温柔点 O__O “…&lt;/p>
&lt;p>另外花了一千多块钱买了一辆二手山地车，骑着到处转悠了一下。很有缘的是，认识了一位才女。本来我们骑行社一块去美利达准备买车，但是由于种种原因我和小欣都没买，然后我们一块坐小黑车回村，在车上聊着聊着就认识，没想到后来还成为了好朋友，小欣的台球和乒乓球都打得不错，琴棋书画样样精通。突然发现身边的学霸才子佳人好多，更加深刻感受到有些东西不是你努力就能够弥补的，天赋、眼界、才艺、品味、性格……&lt;/p>
&lt;p>来国科大的这半年，自我感觉变化最大的是自己变得爱说话了，而且带着一种zhuang bi气息，不知道是不是受某几个我一直崇拜的人的影响。有时候静下心来想想都不敢相信之前的话是我说的，和大学时的我完全判若两人。当然这种事情有利也有弊，还在慢慢找平衡点，可能正如CL说的“话怎么说是一回事，内心要知道自己想要的”。&lt;/p>
&lt;p>这半年突然害怕一个人上学，一个人吃饭，一个人自习了，更喜欢face-to-face的交谈，少了对网络的依赖，不知道是不是因为性格的变化、环境的变化、抑或是认识的人多了，有了念想。&lt;/p>
&lt;p>半年时光，认识了不多不少几个好朋友：良辰、发文章、牛牛、欣儿、路路，有你们真好，谢谢你们~&lt;/p>
&lt;p>2016猴赛雷，即将从研一的上课转入课题组工作，很关键的一年，加油！&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/image.thepaper.cn/www/image/4/753/520.jpg">&lt;/p></description></item><item><title>卜神算法作业整理</title><link>https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/</link><pubDate>Fri, 29 Jan 2016 17:07:11 +0800</pubDate><guid>https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/</guid><description>&lt;p>这学期选修了&lt;a href="http://bioinfo.ict.ac.cn/~dbu/AlgorithmCourses/CS711008Z/CS711008Z_2015.html">卜老师的算法课&lt;/a>，都说这课是神课，上过之后果然是神课。同样是算法课，别人12月底就考完了，我们要1月底才考试。&lt;/p>
&lt;p>本课程主要讲了以下几个专题：&lt;/p>
&lt;ul>
&lt;li>Divide-and-conquer&lt;/li>
&lt;li>Dynamic programming&lt;/li>
&lt;li>Greedy&lt;/li>
&lt;li>Linear programming&lt;/li>
&lt;li>Linear programming: duality&lt;/li>
&lt;li>Network flow&lt;/li>
&lt;li>Problem hardness: Polynomial-time reduction&lt;/li>
&lt;li>NP-Completeness&lt;/li>
&lt;li>Approximation algorithm&lt;/li>
&lt;/ul>
&lt;p>前三个专题的算法大多数本科时学过的，但是经卜老师讲一遍还会有新的收获。后六个专题接触较少，学到了很多新算法。&lt;/p>
&lt;p>下图是卜老师每节课必讲的问题求解思路图：&lt;/p>
&lt;p>（待我回家把图画出来…）&lt;/p>
&lt;p>本课程最神的要数课后作业了，一般deadline是周五，每到周四晚上，大家都做好熬通宵赶作业的准备，没熬到两三点都不好意思睡觉，我同学有一次甚至熬到了第二天六点！&lt;/p>
&lt;p>每次作业大概有10题，前7题是算法设计，后3题是算法实现，每题都不是省油的灯，不过如果把每道题都理解消化，算法及编程能力会有很大的提高。&lt;/p>
&lt;p>下面是我整理出来的算法题目和个人解答，大家感受一下。（&lt;strong>仅供完成作业之后交流使用，拒绝抄袭！&lt;/strong>）&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/Assignment1_DandC.zip">Assignment1_DandC.zip&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A1sol.pdf">A1sol.pdf&lt;/a> | &lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A1sol.tex">A1sol.tex&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A1sol_supplement.pdf">A1sol_supplement.pdf&lt;/a> | &lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A1sol_supplement.tex_.zip">A1sol_supplement.tex_.zip&lt;/a>&lt;/li>
&lt;li>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/Assignment2_DP.zip">Assignment2_DP.zip&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A2sol.pdf">A2sol.pdf&lt;/a> | &lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A2sol.tex_.zip">A2sol.tex_.zip&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A2sol_supplement.pdf">A2sol_supplement.pdf&lt;/a> | &lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A2sol_supplement.tex">A2sol_supplement.tex&lt;/a>&lt;/li>
&lt;li>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/Assignment3_Greedy.zip">Assignment3_Greedy.zip&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A3sol.pdf">A3sol.pdf&lt;/a> | &lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A3sol.tex_.zip">A3sol.tex_.zip&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A3sol_supplement.pdf">A3sol_supplement.pdf&lt;/a> | &lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A3sol_supplement.tex">A3sol_supplement.tex&lt;/a>&lt;/li>
&lt;li>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/Assignment4_LP.zip">Assignment4_LP.zip&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A4sol.pdf">A4sol.pdf&lt;/a> | &lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A4sol.tex">A4sol.tex&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A4sol_supplement.pdf">A4sol_supplement.pdf&lt;/a> | &lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A4sol_supplement.tex">A4sol_supplement.tex&lt;/a>&lt;/li>
&lt;li>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/Assignment5_NF.zip">Assignment5_NF.zip&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A5sol.pdf">A5sol.pdf&lt;/a> | &lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A5sol.tex">A5sol.tex&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A5sol_supplement.pdf">A5sol_supplement.pdf&lt;/a> | &lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A5sol_supplement.tex">A5sol_supplement.tex&lt;/a>&lt;/li>
&lt;li>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/Assignment6_NP.pdf">Assignment6_NP.pdf&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A6sol.pdf">A6sol.pdf&lt;/a> | &lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A6sol.tex">A6sol.tex&lt;/a>&lt;/li>
&lt;li>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/Assignment7_App.pdf">Assignment7_App.pdf&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A7sol.pdf">A7sol.pdf&lt;/a> | &lt;a href="https://bitjoy.net/posts/2016-01-29-algorithm-design-and-analysis-by-dbu/A7sol.tex">A7sol.tex&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>和我一起构建搜索引擎（七）总结展望</title><link>https://bitjoy.net/posts/2016-01-09-introduction-to-building-a-search-engine-7/</link><pubDate>Sat, 09 Jan 2016 23:52:13 +0800</pubDate><guid>https://bitjoy.net/posts/2016-01-09-introduction-to-building-a-search-engine-7/</guid><description>&lt;p>至此，整个新闻搜索引擎构建完毕，总体效果令人满意，不过还是有很多可以改进的地方。下面总结一下本系统的优点和不足。&lt;/p>
&lt;p>&lt;strong>优点&lt;/strong>&lt;/p>
&lt;p>倒排索引存储方式。因为不同词项的倒排记录表长度一般不同，所以没办法以常规的方式存入关系型数据库。通过将一个词项的倒排记录表序列化成一个字符串再存入数据库，读取的时候通过反序列化获得相关数据，整个结构类似于邻接表的形式。&lt;/p>
&lt;p>推荐阅读实现方式。利用特征提取的方法，用25个关键词表示一篇新闻，大大减小了文档词项矩阵规模，提高计算效率的同时不影响推荐新闻相关性。&lt;/p>
&lt;p>借用了Reddit的热度公式，融合了时间因素。&lt;/p>
&lt;p>&lt;strong>不足&lt;/strong>&lt;/p>
&lt;p>构建索引时，为了降低索引规模，提高算法速度，我们将纯数字词项过滤了，同时忽略了词项大小写。虽然索引规模下降了，但是牺牲了搜索引擎的正确率。&lt;/p>
&lt;p>构建索引时，采用了jieba的精确分词模式，比如句子“我来到北京清华大学”的分词结果为“我/ 来到/ 北京/ 清华大学”，“清华大学”作为一个整体被当作一个词项，如果搜索关键词是“清华”，则该句子不能匹配，但显然这个句子和“清华”相关。所以后续可以采用结巴的搜索引擎分词模式，虽然索引规模增加了，但能提升搜索引擎的召回率。&lt;/p>
&lt;p>在推荐阅读模块，虽然进行了维度约减，但是当数据量较大时（数十万条新闻），得到的文档词项矩阵也是巨大的，会远远超过现有PC的内存大小。所以可以先对新闻进行粗略的聚类，再在类内计算两两cosine相似度，得到值得推荐的新闻。&lt;/p>
&lt;p>在热度公式中，虽然借用了Reddit的公式，大的方向是正确的，但是引入了参数\(k_1\)和\(k_2\)，而且将其简单的设置为1。如果能够由专家给出或者经过机器学习训练得到，则热度公式的效果会更好。&lt;/p>
&lt;hr>
&lt;p>完整可运行的新闻搜索引擎Demo请看我的Github项目&lt;a href="https://github.com/01joy/news_search_engine">news_search_engine&lt;/a>。&lt;/p>
&lt;p>以下是系列博客：&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-04-introduction-to-building-a-search-engine-1/">和我一起构建搜索引擎（一）简介&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-04-introduction-to-building-a-search-engine-2/">和我一起构建搜索引擎（二）网络爬虫&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-07-introduction-to-building-a-search-engine-3/">和我一起构建搜索引擎（三）构建索引&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-07-introduction-to-building-a-search-engine-4/">和我一起构建搜索引擎（四）检索模型&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-09-introduction-to-building-a-search-engine-5/">和我一起构建搜索引擎（五）推荐阅读&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-09-introduction-to-building-a-search-engine-6/">和我一起构建搜索引擎（六）系统展示&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-09-introduction-to-building-a-search-engine-7/">和我一起构建搜索引擎（七）总结展望&lt;/a>&lt;/p></description></item><item><title>和我一起构建搜索引擎（六）系统展示</title><link>https://bitjoy.net/posts/2016-01-09-introduction-to-building-a-search-engine-6/</link><pubDate>Sat, 09 Jan 2016 23:32:10 +0800</pubDate><guid>https://bitjoy.net/posts/2016-01-09-introduction-to-building-a-search-engine-6/</guid><description>&lt;p>前几个博客已经介绍完搜索引擎的所有功能，为了实现更好的用户体验，需要一个web界面。这一部分是另一个队员做的，我这里借用他的代码。&lt;/p>
&lt;p>我们利用开源的&lt;a href="http://flask.pocoo.org/">Flask Web框架&lt;/a>搭建了展示系统，搜索引擎只需要两个界面，一个是搜索界面，另一个是展示详细新闻的页面（实际搜索引擎没有这个页面）。编写好这两个模板页面并调用前面给出的接口，得到数据，展示出来就可以。&lt;/p>
&lt;p>这一部分没有太多需要讲解的算法，直接上效果图（点击图片可以查看大图）。&lt;/p>
&lt;p>&lt;img alt="图1. 搜索页面" loading="lazy" src="https://bitjoy.net/posts/2016-01-09-introduction-to-building-a-search-engine-6/News-Search-Engine1.webp">
图1. 搜索页面&lt;/p>
&lt;p>&lt;img alt="图2. 新闻详情页面" loading="lazy" src="https://bitjoy.net/posts/2016-01-09-introduction-to-building-a-search-engine-6/News-Search-Engine2.webp">
图2. 新闻详情页面&lt;/p>
&lt;p>由于数据量不大，只有1000条新闻，所以第一页中后面几个结果相关度就不是很高了。但是经过测试，在大数据量的情况下，不论是搜索的速度、准确率、召回率以及推荐阅读的相关度，都达到了不错的效果。&lt;/p>
&lt;hr>
&lt;p>完整可运行的新闻搜索引擎Demo请看我的Github项目&lt;a href="https://github.com/01joy/news_search_engine">news_search_engine&lt;/a>。&lt;/p>
&lt;p>以下是系列博客：&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-04-introduction-to-building-a-search-engine-1/">和我一起构建搜索引擎（一）简介&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-04-introduction-to-building-a-search-engine-2/">和我一起构建搜索引擎（二）网络爬虫&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-07-introduction-to-building-a-search-engine-3/">和我一起构建搜索引擎（三）构建索引&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-07-introduction-to-building-a-search-engine-4/">和我一起构建搜索引擎（四）检索模型&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-09-introduction-to-building-a-search-engine-5/">和我一起构建搜索引擎（五）推荐阅读&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-09-introduction-to-building-a-search-engine-6/">和我一起构建搜索引擎（六）系统展示&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-09-introduction-to-building-a-search-engine-7/">和我一起构建搜索引擎（七）总结展望&lt;/a>&lt;/p></description></item><item><title>和我一起构建搜索引擎（五）推荐阅读</title><link>https://bitjoy.net/posts/2016-01-09-introduction-to-building-a-search-engine-5/</link><pubDate>Sat, 09 Jan 2016 22:34:56 +0800</pubDate><guid>https://bitjoy.net/posts/2016-01-09-introduction-to-building-a-search-engine-5/</guid><description>&lt;p>虽然主要的检索功能实现了，但是我们还需要一个“推荐阅读”的功能。当用户浏览某条具体新闻时，我们在页面底端给出5条和该新闻相关的新闻，也就是一个最简单的推荐系统。&lt;/p>
&lt;p>&lt;img alt="搜狐新闻“相关新闻”模块" loading="lazy" src="https://bitjoy.net/posts/2016-01-09-introduction-to-building-a-search-engine-5/sohu-news3.webp">
搜狐新闻“相关新闻”模块&lt;/p>
&lt;p>推荐模块的思路是度量两两新闻之间的相似度，取相似度最高的前5篇新闻作为推荐阅读的新闻。&lt;/p>
&lt;p>我们前面讲过，一篇文档可以用一个向量表示，向量中的每个值是不同词项t在该文档d中的词频tf。但是一篇较短的文档（如新闻）的关键词并不多，所以我们可以提取每篇新闻的关键词，用这些关键词的tfidf值构成文档的向量表示，这样能够大大减少相似度计算量，同时保持较好的推荐效果。&lt;/p>
&lt;p>&lt;a href="https://github.com/fxsjy/jieba#3-%E5%85%B3%E9%94%AE%E8%AF%8D%E6%8F%90%E5%8F%96">jieba分词组件自带关键词提取功能&lt;/a>，并能返回关键词的tfidf值。所以对每篇新闻，我们先提取tfidf得分最高的前25个关键词，用这25个关键词的tfidf值作为文档的向量表示。由此能够得到一个1000*m的文档词项矩阵M，矩阵每行表示一个文档，每列表示一个词项，m为1000个文档的所有互异的关键词（大概10000个）。矩阵M当然也是稀疏矩阵。&lt;/p>
&lt;p>得到文档词项矩阵M之后，我们利用&lt;a href="http://sklearn.metrics.pairwise.pairwise_distances/">sklearn的pairwise_distances函数&lt;/a>计算M中行向量之间的cosine相似度，对每个文档，得到与其最相似的前5篇新闻id，并把结果写入数据库。&lt;/p>
&lt;p>推荐阅读模块的代码如下：&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 20
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 21
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 22
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 23
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 24
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 25
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 26
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 27
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 28
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 29
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 30
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 31
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 32
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 33
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 34
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 35
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 36
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 37
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 38
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 39
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 40
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 41
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 42
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 43
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 44
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 45
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 46
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 47
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 48
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 49
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 50
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 51
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 52
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 53
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 54
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 55
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 56
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 57
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 58
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 59
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 60
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 61
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 62
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 63
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 64
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 65
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 66
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 67
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 68
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 69
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 70
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 71
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 72
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 73
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 74
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 75
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 76
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 77
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 78
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 79
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 80
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 81
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 82
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 83
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 84
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 85
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 86
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 87
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 88
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 89
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 90
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 91
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 92
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 93
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 94
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 95
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 96
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 97
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 98
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 99
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">100
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">101
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">102
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">103
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">104
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">105
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">106
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">107
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">108
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">109
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">110
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">111
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">112
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">113
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">114
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">115
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">116
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">117
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">118
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">119
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">120
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">121
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">122
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">123
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">124
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">125
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">126
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">127
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">128
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">129
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">130
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">131
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">132
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">133
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">134
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">135
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">136
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">137
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">138
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">139
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">140
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">141
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">142
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">143
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">144
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">145
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">146
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">147
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">148
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">149
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">150
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">151
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">152
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">153
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">154
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">155
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">156
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">157
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">158
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># -*- coding: utf-8 -*-&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Created on Wed Dec 23 14:06:10 2015
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">@author: bitjoy.net
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> os &lt;span style="color:#f92672">import&lt;/span> listdir
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> xml.etree.ElementTree &lt;span style="color:#66d9ef">as&lt;/span> ET
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> jieba
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> jieba.analyse
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> sqlite3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> configparser
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> datetime &lt;span style="color:#f92672">import&lt;/span> &lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> math
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> pandas &lt;span style="color:#66d9ef">as&lt;/span> pd
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> numpy &lt;span style="color:#66d9ef">as&lt;/span> np
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> sklearn.metrics &lt;span style="color:#f92672">import&lt;/span> pairwise_distances
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">RecommendationModule&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> stop_words &lt;span style="color:#f92672">=&lt;/span> set()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> k_nearest &lt;span style="color:#f92672">=&lt;/span> []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config_path &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config_encoding &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> doc_dir_path &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> doc_encoding &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> stop_words_path &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> stop_words_encoding &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> idf_path &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> db_path &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">__init__&lt;/span>(self, config_path, config_encoding):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>config_path &lt;span style="color:#f92672">=&lt;/span> config_path
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>config_encoding &lt;span style="color:#f92672">=&lt;/span> config_encoding
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config &lt;span style="color:#f92672">=&lt;/span> configparser&lt;span style="color:#f92672">.&lt;/span>ConfigParser()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config&lt;span style="color:#f92672">.&lt;/span>read(config_path, config_encoding)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>doc_dir_path &lt;span style="color:#f92672">=&lt;/span> config[&lt;span style="color:#e6db74">&amp;#39;DEFAULT&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;doc_dir_path&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>doc_encoding &lt;span style="color:#f92672">=&lt;/span> config[&lt;span style="color:#e6db74">&amp;#39;DEFAULT&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;doc_encoding&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>stop_words_path &lt;span style="color:#f92672">=&lt;/span> config[&lt;span style="color:#e6db74">&amp;#39;DEFAULT&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;stop_words_path&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>stop_words_encoding &lt;span style="color:#f92672">=&lt;/span> config[&lt;span style="color:#e6db74">&amp;#39;DEFAULT&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;stop_words_encoding&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>idf_path &lt;span style="color:#f92672">=&lt;/span> config[&lt;span style="color:#e6db74">&amp;#39;DEFAULT&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;idf_path&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>db_path &lt;span style="color:#f92672">=&lt;/span> config[&lt;span style="color:#e6db74">&amp;#39;DEFAULT&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;db_path&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> f &lt;span style="color:#f92672">=&lt;/span> open(self&lt;span style="color:#f92672">.&lt;/span>stop_words_path, encoding &lt;span style="color:#f92672">=&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>stop_words_encoding)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> words &lt;span style="color:#f92672">=&lt;/span> f&lt;span style="color:#f92672">.&lt;/span>read()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>stop_words &lt;span style="color:#f92672">=&lt;/span> set(words&lt;span style="color:#f92672">.&lt;/span>split(&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">write_k_nearest_matrix_to_db&lt;/span>(self):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> conn &lt;span style="color:#f92672">=&lt;/span> sqlite3&lt;span style="color:#f92672">.&lt;/span>connect(self&lt;span style="color:#f92672">.&lt;/span>db_path)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c &lt;span style="color:#f92672">=&lt;/span> conn&lt;span style="color:#f92672">.&lt;/span>cursor()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c&lt;span style="color:#f92672">.&lt;/span>execute(&lt;span style="color:#e6db74">&amp;#34;&amp;#39;DROP TABLE IF EXISTS knearest&amp;#39;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c&lt;span style="color:#f92672">.&lt;/span>execute(&lt;span style="color:#e6db74">&amp;#34;&amp;#39;CREATE TABLE knearest(id INTEGER PRIMARY KEY, first INTEGER, second INTEGER, third INTEGER, fourth INTEGER, fifth INTEGER)&amp;#39;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> docid, doclist &lt;span style="color:#f92672">in&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>k_nearest:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c&lt;span style="color:#f92672">.&lt;/span>execute(&lt;span style="color:#e6db74">&amp;#34;INSERT INTO knearest VALUES (?, ?, ?, ?, ?, ?)&amp;#34;&lt;/span>, tuple([docid] &lt;span style="color:#f92672">+&lt;/span> doclist))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> conn&lt;span style="color:#f92672">.&lt;/span>commit()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> conn&lt;span style="color:#f92672">.&lt;/span>close()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">is_number&lt;/span>(self, s):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> float(s)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">True&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">except&lt;/span> &lt;span style="color:#a6e22e">ValueError&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">False&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">construct_dt_matrix&lt;/span>(self, files, topK &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">200&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> jieba&lt;span style="color:#f92672">.&lt;/span>analyse&lt;span style="color:#f92672">.&lt;/span>set_stop_words(self&lt;span style="color:#f92672">.&lt;/span>stop_words_path)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> jieba&lt;span style="color:#f92672">.&lt;/span>analyse&lt;span style="color:#f92672">.&lt;/span>set_idf_path(self&lt;span style="color:#f92672">.&lt;/span>idf_path)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> M &lt;span style="color:#f92672">=&lt;/span> len(files)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> N &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> terms &lt;span style="color:#f92672">=&lt;/span> {}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dt &lt;span style="color:#f92672">=&lt;/span> []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> files:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> root &lt;span style="color:#f92672">=&lt;/span> ET&lt;span style="color:#f92672">.&lt;/span>parse(self&lt;span style="color:#f92672">.&lt;/span>doc_dir_path &lt;span style="color:#f92672">+&lt;/span> i)&lt;span style="color:#f92672">.&lt;/span>getroot()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> title &lt;span style="color:#f92672">=&lt;/span> root&lt;span style="color:#f92672">.&lt;/span>find(&lt;span style="color:#e6db74">&amp;#39;title&amp;#39;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>text
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> body &lt;span style="color:#f92672">=&lt;/span> root&lt;span style="color:#f92672">.&lt;/span>find(&lt;span style="color:#e6db74">&amp;#39;body&amp;#39;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>text
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> docid &lt;span style="color:#f92672">=&lt;/span> int(root&lt;span style="color:#f92672">.&lt;/span>find(&lt;span style="color:#e6db74">&amp;#39;id&amp;#39;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>text)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tags &lt;span style="color:#f92672">=&lt;/span> jieba&lt;span style="color:#f92672">.&lt;/span>analyse&lt;span style="color:#f92672">.&lt;/span>extract_tags(title &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#39;。&amp;#39;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> body, topK&lt;span style="color:#f92672">=&lt;/span>topK, withWeight&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">#tags = jieba.analyse.extract_tags(title, topK=topK, withWeight=True)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cleaned_dict &lt;span style="color:#f92672">=&lt;/span> {}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> word, tfidf &lt;span style="color:#f92672">in&lt;/span> tags:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> word &lt;span style="color:#f92672">=&lt;/span> word&lt;span style="color:#f92672">.&lt;/span>strip()&lt;span style="color:#f92672">.&lt;/span>lower()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> word &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span> &lt;span style="color:#f92672">or&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>is_number(word):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cleaned_dict[word] &lt;span style="color:#f92672">=&lt;/span> tfidf
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> word &lt;span style="color:#f92672">not&lt;/span> &lt;span style="color:#f92672">in&lt;/span> terms:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> terms[word] &lt;span style="color:#f92672">=&lt;/span> N
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> N &lt;span style="color:#f92672">+=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dt&lt;span style="color:#f92672">.&lt;/span>append([docid, cleaned_dict])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dt_matrix &lt;span style="color:#f92672">=&lt;/span> [[&lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> range(N)] &lt;span style="color:#66d9ef">for&lt;/span> j &lt;span style="color:#f92672">in&lt;/span> range(M)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> i &lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> docid, t_tfidf &lt;span style="color:#f92672">in&lt;/span> dt:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dt_matrix[i][&lt;span style="color:#ae81ff">0&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> docid
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> term, tfidf &lt;span style="color:#f92672">in&lt;/span> t_tfidf&lt;span style="color:#f92672">.&lt;/span>items():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dt_matrix[i][terms[term]] &lt;span style="color:#f92672">=&lt;/span> tfidf
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> i &lt;span style="color:#f92672">+=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dt_matrix &lt;span style="color:#f92672">=&lt;/span> pd&lt;span style="color:#f92672">.&lt;/span>DataFrame(dt_matrix)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dt_matrix&lt;span style="color:#f92672">.&lt;/span>index &lt;span style="color:#f92672">=&lt;/span> dt_matrix[&lt;span style="color:#ae81ff">0&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#39;dt_matrix shape:(&lt;/span>&lt;span style="color:#e6db74">%d&lt;/span>&lt;span style="color:#e6db74"> &lt;/span>&lt;span style="color:#e6db74">%d&lt;/span>&lt;span style="color:#e6db74">)&amp;#39;&lt;/span>&lt;span style="color:#f92672">%&lt;/span>(dt_matrix&lt;span style="color:#f92672">.&lt;/span>shape))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> dt_matrix
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">construct_k_nearest_matrix&lt;/span>(self, dt_matrix, k):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tmp &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>array(&lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">–&lt;/span> pairwise_distances(dt_matrix[dt_matrix&lt;span style="color:#f92672">.&lt;/span>columns[&lt;span style="color:#ae81ff">1&lt;/span>:]], metric &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;cosine&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> similarity_matrix &lt;span style="color:#f92672">=&lt;/span> pd&lt;span style="color:#f92672">.&lt;/span>DataFrame(tmp, index &lt;span style="color:#f92672">=&lt;/span> dt_matrix&lt;span style="color:#f92672">.&lt;/span>index&lt;span style="color:#f92672">.&lt;/span>tolist(), columns &lt;span style="color:#f92672">=&lt;/span> dt_matrix&lt;span style="color:#f92672">.&lt;/span>index&lt;span style="color:#f92672">.&lt;/span>tolist())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> similarity_matrix&lt;span style="color:#f92672">.&lt;/span>index:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tmp &lt;span style="color:#f92672">=&lt;/span> [int(i),[]]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> j &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> j &lt;span style="color:#f92672">&amp;lt;=&lt;/span> k:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> max_col &lt;span style="color:#f92672">=&lt;/span> similarity_matrix&lt;span style="color:#f92672">.&lt;/span>loc[i]&lt;span style="color:#f92672">.&lt;/span>idxmax(axis &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> similarity_matrix&lt;span style="color:#f92672">.&lt;/span>loc[i][max_col] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> max_col &lt;span style="color:#f92672">!=&lt;/span> i:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tmp[&lt;span style="color:#ae81ff">1&lt;/span>]&lt;span style="color:#f92672">.&lt;/span>append(int(max_col)) &lt;span style="color:#75715e">#max column name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> j &lt;span style="color:#f92672">+=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>k_nearest&lt;span style="color:#f92672">.&lt;/span>append(tmp)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">gen_idf_file&lt;/span>(self):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> files &lt;span style="color:#f92672">=&lt;/span> listdir(self&lt;span style="color:#f92672">.&lt;/span>doc_dir_path)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> n &lt;span style="color:#f92672">=&lt;/span> float(len(files))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> idf &lt;span style="color:#f92672">=&lt;/span> {}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> files:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> root &lt;span style="color:#f92672">=&lt;/span> ET&lt;span style="color:#f92672">.&lt;/span>parse(self&lt;span style="color:#f92672">.&lt;/span>doc_dir_path &lt;span style="color:#f92672">+&lt;/span> i)&lt;span style="color:#f92672">.&lt;/span>getroot()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> title &lt;span style="color:#f92672">=&lt;/span> root&lt;span style="color:#f92672">.&lt;/span>find(&lt;span style="color:#e6db74">&amp;#39;title&amp;#39;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>text
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> body &lt;span style="color:#f92672">=&lt;/span> root&lt;span style="color:#f92672">.&lt;/span>find(&lt;span style="color:#e6db74">&amp;#39;body&amp;#39;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>text
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> seg_list &lt;span style="color:#f92672">=&lt;/span> jieba&lt;span style="color:#f92672">.&lt;/span>lcut(title &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#39;。&amp;#39;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> body, cut_all&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">False&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> seg_list &lt;span style="color:#f92672">=&lt;/span> set(seg_list) &lt;span style="color:#960050;background-color:#1e0010">–&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>stop_words
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> word &lt;span style="color:#f92672">in&lt;/span> seg_list:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> word &lt;span style="color:#f92672">=&lt;/span> word&lt;span style="color:#f92672">.&lt;/span>strip()&lt;span style="color:#f92672">.&lt;/span>lower()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> word &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span> &lt;span style="color:#f92672">or&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>is_number(word):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> word &lt;span style="color:#f92672">not&lt;/span> &lt;span style="color:#f92672">in&lt;/span> idf:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> idf[word] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> idf[word] &lt;span style="color:#f92672">=&lt;/span> idf[word] &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> idf_file &lt;span style="color:#f92672">=&lt;/span> open(self&lt;span style="color:#f92672">.&lt;/span>idf_path, &lt;span style="color:#e6db74">&amp;#39;w&amp;#39;&lt;/span>, encoding &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;utf-8&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> word, df &lt;span style="color:#f92672">in&lt;/span> idf&lt;span style="color:#f92672">.&lt;/span>items():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> idf_file&lt;span style="color:#f92672">.&lt;/span>write(&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#e6db74">%s&lt;/span>&lt;span style="color:#e6db74"> &lt;/span>&lt;span style="color:#e6db74">%.9f&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#f92672">%&lt;/span>(word, math&lt;span style="color:#f92672">.&lt;/span>log(n &lt;span style="color:#f92672">/&lt;/span> df)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> idf_file&lt;span style="color:#f92672">.&lt;/span>close()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">find_k_nearest&lt;/span>(self, k, topK):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>gen_idf_file()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> files &lt;span style="color:#f92672">=&lt;/span> listdir(self&lt;span style="color:#f92672">.&lt;/span>doc_dir_path)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dt_matrix &lt;span style="color:#f92672">=&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>construct_dt_matrix(files, topK)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>construct_k_nearest_matrix(dt_matrix, k)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>write_k_nearest_matrix_to_db()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> __name__ &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;__main__&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#39;—–start time: &lt;/span>&lt;span style="color:#e6db74">%s&lt;/span>&lt;span style="color:#e6db74">—–&amp;#39;&lt;/span>&lt;span style="color:#f92672">%&lt;/span>(datetime&lt;span style="color:#f92672">.&lt;/span>today()))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rm &lt;span style="color:#f92672">=&lt;/span> RecommendationModule(&lt;span style="color:#e6db74">&amp;#39;../config.ini&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;utf-8&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rm&lt;span style="color:#f92672">.&lt;/span>find_k_nearest(&lt;span style="color:#ae81ff">5&lt;/span>, &lt;span style="color:#ae81ff">25&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#39;—–finish time: &lt;/span>&lt;span style="color:#e6db74">%s&lt;/span>&lt;span style="color:#e6db74">—–&amp;#39;&lt;/span>&lt;span style="color:#f92672">%&lt;/span>(datetime&lt;span style="color:#f92672">.&lt;/span>today()))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这个模块的代码量最多，主要原因是需要构建文档词项矩阵，并且计算k邻居矩阵。矩阵数据结构的设计需要特别注意，否则会严重影响系统的效率。我刚开始把任务都扔给了pandas.DataFrame，后来发现当两个文档向量合并时，需要join连接操作，当数据量很大时，非常耗时，所以改成了先用python原始的list存储，最后一次性构造一个完整的pandas.DataFrame，速度比之前快了不少。&lt;/p></description></item><item><title>和我一起构建搜索引擎（四）检索模型</title><link>https://bitjoy.net/posts/2016-01-07-introduction-to-building-a-search-engine-4/</link><pubDate>Thu, 07 Jan 2016 19:31:25 +0800</pubDate><guid>https://bitjoy.net/posts/2016-01-07-introduction-to-building-a-search-engine-4/</guid><description>&lt;p>构建好倒排索引之后，就可以开始检索了。&lt;/p>
&lt;p>检索模型有很多，比如向量空间模型、概率模型、语言模型等。其中最有名的、检索效果最好的是基于概率的BM25模型。&lt;/p>
&lt;p>给定一个查询Q和一篇文档d，d对Q的BM25得分公式为&lt;/p>
$$BM25_{score}(Q,d)=\sum_{t\in Q}w(t,d)$$$$w(t,d)=\frac{qtf}{k_3+qtf}\times \frac{k_1\times tf}{tf+k_1(1-b+b\times l_d/avg\_l)}\times log_2\frac{N-df+0.5}{df+0.5}$$&lt;p>公式中变量含义如下：&lt;/p>
&lt;ul>
&lt;li>\(qtf\)：查询中的词频&lt;/li>
&lt;li>\(tf\)：文档中的词频&lt;/li>
&lt;li>\(l_d\)：文档长度&lt;/li>
&lt;li>\(avg\_l\)：平均文档长度&lt;/li>
&lt;li>\(N\)：文档数量&lt;/li>
&lt;li>\(df\)：文档频率&lt;/li>
&lt;li>\(b,k_1,k_3\)：可调参数&lt;/li>
&lt;/ul>
&lt;p>这个公式看起来很复杂，我们把它分解一下，其实很容易理解。第一个公式是外部公式，一个查询Q可能包含多个词项，比如“苹果手机”就包含“苹果”和“手机”两个词项，我们需要分别计算“苹果”和“手机”对某个文档d的贡献分数w(t,d)，然后将他们加起来就是整个文档d相对于查询Q的得分。&lt;/p>
&lt;p>第二个公式就是计算某个词项t在文档d中的得分，它包括三个部分。第一个部分是词项t在查询Q中的得分，比如查询“中国人说中国话”中“中国”出现了两次，此时qtf=2，说明这个查询希望找到的文档和“中国”&lt;strong>更&lt;/strong>相关，“中国”的权重应该更大，但是通常情况下，查询Q都很短，而且不太可能包含相同的词项，所以这个因子是一个常数，我们在实现的时候可以忽略。&lt;/p>
&lt;p>第二部分类似于&lt;a href="https://en.wikipedia.org/wiki/Tf%E2%80%93idf">TFIDF模型&lt;/a>中的TF项。也就是说某个词项t在文档d中出现次数越多，则t越重要，但是文档长度越长，tf也倾向于变大，所以使用文档长度除以平均长度\(l_d/avg\_l\)起到某种归一化的效果，\(k_1\)和\(b\)是可调参数。&lt;/p>
&lt;p>第三部分类似于&lt;a href="https://en.wikipedia.org/wiki/Tf%E2%80%93idf">TFIDF模型&lt;/a>中的IDF项。也就是说虽然“的”、“地”、“得”等停用词在某文档d中出现的次数很多，但是他们在很多文档中都出现过，所以这些词对d的贡献分并不高，接近于0；反而那些很稀有的词如”糖尿病“能够很好的区分不同文档，这些词对文档的贡献分应该较高。&lt;/p>
&lt;p>所以根据BM25公式，我们可以很快计算出不同文档t对查询Q的得分情况，然后按得分高低排序给出结果。&lt;/p>
&lt;p>下面是给定一个查询句子sentence，根据BM25公式给出文档排名的函数&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">result_by_BM25&lt;/span>(self, sentence):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> seg_list &lt;span style="color:#f92672">=&lt;/span> jieba&lt;span style="color:#f92672">.&lt;/span>lcut(sentence, cut_all&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">False&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> n, cleaned_dict &lt;span style="color:#f92672">=&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>clean_list(seg_list)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> BM25_scores &lt;span style="color:#f92672">=&lt;/span> {}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> term &lt;span style="color:#f92672">in&lt;/span> cleaned_dict&lt;span style="color:#f92672">.&lt;/span>keys():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> r &lt;span style="color:#f92672">=&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>fetch_from_db(term)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> r &lt;span style="color:#f92672">is&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> df &lt;span style="color:#f92672">=&lt;/span> r[&lt;span style="color:#ae81ff">1&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> w &lt;span style="color:#f92672">=&lt;/span> math&lt;span style="color:#f92672">.&lt;/span>log2((self&lt;span style="color:#f92672">.&lt;/span>N &lt;span style="color:#f92672">-&lt;/span> df &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">0.5&lt;/span>) &lt;span style="color:#f92672">/&lt;/span> (df &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">0.5&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> docs &lt;span style="color:#f92672">=&lt;/span> r[&lt;span style="color:#ae81ff">2&lt;/span>]&lt;span style="color:#f92672">.&lt;/span>split(&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> doc &lt;span style="color:#f92672">in&lt;/span> docs:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> docid, date_time, tf, ld &lt;span style="color:#f92672">=&lt;/span> doc&lt;span style="color:#f92672">.&lt;/span>split(&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\t&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> docid &lt;span style="color:#f92672">=&lt;/span> int(docid)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tf &lt;span style="color:#f92672">=&lt;/span> int(tf)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ld &lt;span style="color:#f92672">=&lt;/span> int(ld)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> s &lt;span style="color:#f92672">=&lt;/span> (self&lt;span style="color:#f92672">.&lt;/span>K1 &lt;span style="color:#f92672">*&lt;/span> tf &lt;span style="color:#f92672">*&lt;/span> w) &lt;span style="color:#f92672">/&lt;/span> (tf &lt;span style="color:#f92672">+&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>K1 &lt;span style="color:#f92672">*&lt;/span> (&lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#f92672">-&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>B &lt;span style="color:#f92672">+&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>B &lt;span style="color:#f92672">*&lt;/span> ld &lt;span style="color:#f92672">/&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>AVG_L))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> docid &lt;span style="color:#f92672">in&lt;/span> BM25_scores:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> BM25_scores[docid] &lt;span style="color:#f92672">=&lt;/span> BM25_scores[docid] &lt;span style="color:#f92672">+&lt;/span> s
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> BM25_scores[docid] &lt;span style="color:#f92672">=&lt;/span> s
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> BM25_scores &lt;span style="color:#f92672">=&lt;/span> sorted(BM25_scores&lt;span style="color:#f92672">.&lt;/span>items(), key &lt;span style="color:#f92672">=&lt;/span> operator&lt;span style="color:#f92672">.&lt;/span>itemgetter(&lt;span style="color:#ae81ff">1&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> BM25_scores&lt;span style="color:#f92672">.&lt;/span>reverse()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> len(BM25_scores) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>, []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>, BM25_scores
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>首先将句子分词得到所有查询词项，然后从数据库中取出词项对应的倒排记录表，对记录表中的所有文档，计算其BM25得分，最后按得分高低排序作为查询结果。&lt;/p></description></item><item><title>和我一起构建搜索引擎（三）构建索引</title><link>https://bitjoy.net/posts/2016-01-07-introduction-to-building-a-search-engine-3/</link><pubDate>Thu, 07 Jan 2016 19:07:17 +0800</pubDate><guid>https://bitjoy.net/posts/2016-01-07-introduction-to-building-a-search-engine-3/</guid><description>&lt;p>目前正是所谓的“大数据”时代，数据量多到难以计数，怎样结构化的存储以便于分析计算，是当前的一大难题。上一篇博客我们简单抓取了1000个搜狐新闻数据，搜索的过程就是从这1000个新闻中找出和关键词相关的新闻来，那么怎样快速搜索呢，总不可能依次打开xml文件一个字一个字的找吧，这时就需要借助倒排索引这个强大的数据结构。&lt;/p>
&lt;p>在讲倒排索引之前，我们先介绍一下布尔检索。布尔检索只是简单返回包含某个关键词的文档，比如查询“苹果手机”，则返回所有包含“苹果”和“手机”关键词的文档，布尔检索并不对返回结果排序，所以有可能返回的第一个文档是“某个男孩边吃苹果边玩手机…“。&lt;/p>
&lt;p>实现布尔检索并不难，我们需要构建一个如下图的词项文档矩阵：&lt;/p>
&lt;p>&lt;img alt="图1. 布尔检索中的词项文档矩阵" loading="lazy" src="https://bitjoy.net/posts/2016-01-07-introduction-to-building-a-search-engine-3/td_matrix.png">
图1. 布尔检索中的词项文档矩阵&lt;/p>
&lt;p>每行对应一个词项，每列对应一个文档，如果该值为1，表示该行词项出现在该列文档中。比如词项”苹果“出现在doc1和doc3文档中，如果我们要找同时出现”苹果“和”手机“的文档，只需把他们对应的向量取出来进行”与“操作，此为101&amp;amp;011=001，所以doc3同时出现了”苹果“和”手机“两个关键词，我们将其返回。&lt;/p>
&lt;p>布尔检索虽然很快，但是它也有很多缺陷，比如不能对结果排序，词项只有出现和不出现两种状态，但是一篇文档中出现10次“苹果“和只出现1次”苹果“，他们的相关度肯定是不相同的。所以需要对布尔检索进行改进。&lt;/p>
&lt;p>在扫描文档时，不但记录某词项出现与否，还记录该词项出现的次数，即词项频率(tf)；同时我们记录该文档的长度(ld)，以及某词项在不同文档中出现的次数，即文档频率(df)。&lt;/p>
&lt;p>&lt;img alt="图2. 倒排索引结构图" loading="lazy" src="https://bitjoy.net/posts/2016-01-07-introduction-to-building-a-search-engine-3/inverted-index.png">
图2. 倒排索引结构图&lt;/p>
&lt;p>这样我们就得到了如上图的倒排索引。左边部分被称为词典，存储的是1000个新闻中所有不同的词项；右边部分被称为倒排记录表，存储的是出现Term_i的那些文档信息。倒排索引中存储的变量都是为了给后续检索模型使用。&lt;/p>
&lt;p>讲到这里，我们需要解决如下几个问题。&lt;/p>
&lt;ol>
&lt;li>怎样得到一篇文档中的所有词项。给我们一篇新闻稿子，人类很容易分辨出”苹果“和”手机“是两个不同的词项，但是计算机怎么知道是这两个词呢？为什么不是”苹”、”国手“和”机“呢？这就需要进行中文分词，我们可以借助开源的&lt;a href="https://github.com/fxsjy/jieba">jieba中文分词组件&lt;/a>来完成，jieba分词能够将一个中文句子切成一个个词项，这样我们就可以统计tf, df了。&lt;/li>
&lt;li>有些词，如”的“、”地“、”得“、”如果“等，几乎每篇文档都会出现，他们起不到很好的区分文档的效果，这类词被称为”停用词“，我们需要把他们去掉。去停词的步骤可以在jieba分词之后完成。&lt;/li>
&lt;li>怎样存储倒排记录表。假设1000个文档共有20000个不同的词项，如果用类似图1的矩阵形式存储，需要耗费1000&lt;em>20000=2&lt;/em>10^7个存储单元，但是图1往往是一个稀疏矩阵，因为一个文档中可能只出现了200个不同的词项，剩余的19800个词项都是空的。用矩阵方式存储时空效率都不高。所以我们可以采用图2的方式，词典用B-树或hash存储，倒排记录表用邻接链表存储方式，这样能大大减少存储空间。如果我们要将图2保存到数据库，可以对倒排记录表序列化成一个长的字符串，写入到一个单元格，读取的时候再反序列化。比如每个Doc内部用’\t’连接，Doc之间用’\n’连接，读取的时候split即可。&lt;/li>
&lt;/ol>
&lt;p>倒排索引构建算法使用内存式单遍扫描索引构建方法（SPIMI），其实就是依次对每篇新闻进行分词，如果出现新的词项则插入到词典中，否则将该文档的信息追加到词项对应的倒排记录表中。SPIMI的伪代码如下：&lt;/p>
&lt;p>&lt;img alt="图3. SPIMI算法伪代码" loading="lazy" src="https://bitjoy.net/posts/2016-01-07-introduction-to-building-a-search-engine-3/SPIMI-algo.png">
图3. SPIMI算法伪代码&lt;/p>
&lt;p>下面是构建索引的所有代码：&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 20
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 21
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 22
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 23
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 24
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 25
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 26
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 27
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 28
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 29
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 30
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 31
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 32
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 33
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 34
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 35
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 36
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 37
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 38
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 39
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 40
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 41
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 42
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 43
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 44
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 45
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 46
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 47
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 48
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 49
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 50
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 51
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 52
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 53
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 54
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 55
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 56
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 57
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 58
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 59
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 60
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 61
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 62
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 63
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 64
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 65
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 66
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 67
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 68
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 69
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 70
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 71
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 72
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 73
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 74
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 75
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 76
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 77
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 78
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 79
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 80
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 81
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 82
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 83
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 84
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 85
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 86
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 87
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 88
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 89
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 90
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 91
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 92
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 93
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 94
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 95
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 96
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 97
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 98
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 99
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">100
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">101
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">102
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">103
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">104
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">105
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">106
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">107
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">108
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">109
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">110
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">111
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">112
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">113
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">114
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># -*- coding: utf-8 -*-&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Created on Sat Dec 5 23:31:22 2015
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">@author: bitjoy.net
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> os &lt;span style="color:#f92672">import&lt;/span> listdir
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> xml.etree.ElementTree &lt;span style="color:#66d9ef">as&lt;/span> ET
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> jieba
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> sqlite3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> configparser
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Doc&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> docid &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> date_time &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tf &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ld &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">__init__&lt;/span>(self, docid, date_time, tf, ld):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>docid &lt;span style="color:#f92672">=&lt;/span> docid
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>date_time &lt;span style="color:#f92672">=&lt;/span> date_time
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>tf &lt;span style="color:#f92672">=&lt;/span> tf
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>ld &lt;span style="color:#f92672">=&lt;/span> ld
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">__repr__&lt;/span>(self):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span>(str(self&lt;span style="color:#f92672">.&lt;/span>docid) &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\t&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>date_time &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\t&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> str(self&lt;span style="color:#f92672">.&lt;/span>tf) &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\t&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> str(self&lt;span style="color:#f92672">.&lt;/span>ld))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">__str__&lt;/span>(self):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span>(str(self&lt;span style="color:#f92672">.&lt;/span>docid) &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\t&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>date_time &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\t&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> str(self&lt;span style="color:#f92672">.&lt;/span>tf) &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\t&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> str(self&lt;span style="color:#f92672">.&lt;/span>ld))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">IndexModule&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> stop_words &lt;span style="color:#f92672">=&lt;/span> set()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> postings_lists &lt;span style="color:#f92672">=&lt;/span> {}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config_path &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config_encoding &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">__init__&lt;/span>(self, config_path, config_encoding):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>config_path &lt;span style="color:#f92672">=&lt;/span> config_path
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>config_encoding &lt;span style="color:#f92672">=&lt;/span> config_encoding
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config &lt;span style="color:#f92672">=&lt;/span> configparser&lt;span style="color:#f92672">.&lt;/span>ConfigParser()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config&lt;span style="color:#f92672">.&lt;/span>read(config_path, config_encoding)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> f &lt;span style="color:#f92672">=&lt;/span> open(config[&lt;span style="color:#e6db74">&amp;#39;DEFAULT&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;stop_words_path&amp;#39;&lt;/span>], encoding &lt;span style="color:#f92672">=&lt;/span> config[&lt;span style="color:#e6db74">&amp;#39;DEFAULT&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;stop_words_encoding&amp;#39;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> words &lt;span style="color:#f92672">=&lt;/span> f&lt;span style="color:#f92672">.&lt;/span>read()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>stop_words &lt;span style="color:#f92672">=&lt;/span> set(words&lt;span style="color:#f92672">.&lt;/span>split(&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">is_number&lt;/span>(self, s):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> float(s)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">True&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">except&lt;/span> &lt;span style="color:#a6e22e">ValueError&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">False&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">clean_list&lt;/span>(self, seg_list):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cleaned_dict &lt;span style="color:#f92672">=&lt;/span> {}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> n &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> seg_list:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> i &lt;span style="color:#f92672">=&lt;/span> i&lt;span style="color:#f92672">.&lt;/span>strip()&lt;span style="color:#f92672">.&lt;/span>lower()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> i &lt;span style="color:#f92672">!=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span> &lt;span style="color:#f92672">and&lt;/span> &lt;span style="color:#f92672">not&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>is_number(i) &lt;span style="color:#f92672">and&lt;/span> i &lt;span style="color:#f92672">not&lt;/span> &lt;span style="color:#f92672">in&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>stop_words:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> n &lt;span style="color:#f92672">=&lt;/span> n &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> cleaned_dict:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cleaned_dict[i] &lt;span style="color:#f92672">=&lt;/span> cleaned_dict[i] &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cleaned_dict[i] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> n, cleaned_dict
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">write_postings_to_db&lt;/span>(self, db_path):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> conn &lt;span style="color:#f92672">=&lt;/span> sqlite3&lt;span style="color:#f92672">.&lt;/span>connect(db_path)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c &lt;span style="color:#f92672">=&lt;/span> conn&lt;span style="color:#f92672">.&lt;/span>cursor()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c&lt;span style="color:#f92672">.&lt;/span>execute(&lt;span style="color:#e6db74">&amp;#34;&amp;#39;DROP TABLE IF EXISTS postings&amp;#39;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c&lt;span style="color:#f92672">.&lt;/span>execute(&lt;span style="color:#e6db74">&amp;#34;&amp;#39;CREATE TABLE postings(term TEXT PRIMARY KEY, df INTEGER, docs TEXT)&amp;#39;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> key, value &lt;span style="color:#f92672">in&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>postings_lists&lt;span style="color:#f92672">.&lt;/span>items():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> doc_list &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>join(map(str,value[&lt;span style="color:#ae81ff">1&lt;/span>]))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t &lt;span style="color:#f92672">=&lt;/span> (key, value[&lt;span style="color:#ae81ff">0&lt;/span>], doc_list)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c&lt;span style="color:#f92672">.&lt;/span>execute(&lt;span style="color:#e6db74">&amp;#34;INSERT INTO postings VALUES (?, ?, ?)&amp;#34;&lt;/span>, t)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> conn&lt;span style="color:#f92672">.&lt;/span>commit()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> conn&lt;span style="color:#f92672">.&lt;/span>close()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">construct_postings_lists&lt;/span>(self):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config &lt;span style="color:#f92672">=&lt;/span> configparser&lt;span style="color:#f92672">.&lt;/span>ConfigParser()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config&lt;span style="color:#f92672">.&lt;/span>read(self&lt;span style="color:#f92672">.&lt;/span>config_path, self&lt;span style="color:#f92672">.&lt;/span>config_encoding)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> files &lt;span style="color:#f92672">=&lt;/span> listdir(config[&lt;span style="color:#e6db74">&amp;#39;DEFAULT&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;doc_dir_path&amp;#39;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AVG_L &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> files:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> root &lt;span style="color:#f92672">=&lt;/span> ET&lt;span style="color:#f92672">.&lt;/span>parse(config[&lt;span style="color:#e6db74">&amp;#39;DEFAULT&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;doc_dir_path&amp;#39;&lt;/span>] &lt;span style="color:#f92672">+&lt;/span> i)&lt;span style="color:#f92672">.&lt;/span>getroot()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> title &lt;span style="color:#f92672">=&lt;/span> root&lt;span style="color:#f92672">.&lt;/span>find(&lt;span style="color:#e6db74">&amp;#39;title&amp;#39;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>text
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> body &lt;span style="color:#f92672">=&lt;/span> root&lt;span style="color:#f92672">.&lt;/span>find(&lt;span style="color:#e6db74">&amp;#39;body&amp;#39;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>text
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> docid &lt;span style="color:#f92672">=&lt;/span> int(root&lt;span style="color:#f92672">.&lt;/span>find(&lt;span style="color:#e6db74">&amp;#39;id&amp;#39;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>text)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> date_time &lt;span style="color:#f92672">=&lt;/span> root&lt;span style="color:#f92672">.&lt;/span>find(&lt;span style="color:#e6db74">&amp;#39;datetime&amp;#39;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>text
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> seg_list &lt;span style="color:#f92672">=&lt;/span> jieba&lt;span style="color:#f92672">.&lt;/span>lcut(title &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#39;。&amp;#39;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> body, cut_all&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">False&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ld, cleaned_dict &lt;span style="color:#f92672">=&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>clean_list(seg_list)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AVG_L &lt;span style="color:#f92672">=&lt;/span> AVG_L &lt;span style="color:#f92672">+&lt;/span> ld
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> key, value &lt;span style="color:#f92672">in&lt;/span> cleaned_dict&lt;span style="color:#f92672">.&lt;/span>items():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> d &lt;span style="color:#f92672">=&lt;/span> Doc(docid, date_time, value, ld)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> key &lt;span style="color:#f92672">in&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>postings_lists:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>postings_lists[key][&lt;span style="color:#ae81ff">0&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>postings_lists[key][&lt;span style="color:#ae81ff">0&lt;/span>] &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#75715e"># df++&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>postings_lists[key][&lt;span style="color:#ae81ff">1&lt;/span>]&lt;span style="color:#f92672">.&lt;/span>append(d)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>postings_lists[key] &lt;span style="color:#f92672">=&lt;/span> [&lt;span style="color:#ae81ff">1&lt;/span>, [d]] &lt;span style="color:#75715e"># [df, [Doc]]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AVG_L &lt;span style="color:#f92672">=&lt;/span> AVG_L &lt;span style="color:#f92672">/&lt;/span> len(files)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config&lt;span style="color:#f92672">.&lt;/span>set(&lt;span style="color:#e6db74">&amp;#39;DEFAULT&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;N&amp;#39;&lt;/span>, str(len(files)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config&lt;span style="color:#f92672">.&lt;/span>set(&lt;span style="color:#e6db74">&amp;#39;DEFAULT&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;avg_l&amp;#39;&lt;/span>, str(AVG_L))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(self&lt;span style="color:#f92672">.&lt;/span>config_path, &lt;span style="color:#960050;background-color:#1e0010">‘&lt;/span>w&lt;span style="color:#960050;background-color:#1e0010">’&lt;/span>, encoding &lt;span style="color:#f92672">=&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>config_encoding) &lt;span style="color:#66d9ef">as&lt;/span> configfile:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config&lt;span style="color:#f92672">.&lt;/span>write(configfile)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>write_postings_to_db(config[&lt;span style="color:#960050;background-color:#1e0010">‘&lt;/span>DEFAULT&lt;span style="color:#960050;background-color:#1e0010">’&lt;/span>][&lt;span style="color:#960050;background-color:#1e0010">‘&lt;/span>db_path&lt;span style="color:#960050;background-color:#1e0010">’&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> __name__ &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;__main__&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> im &lt;span style="color:#f92672">=&lt;/span> IndexModule(&lt;span style="color:#e6db74">&amp;#39;../config.ini&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;utf-8&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> im&lt;span style="color:#f92672">.&lt;/span>construct_postings_lists()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>运行之后会在./data/下生成一个ir.db数据库文件，这就是构建好的索引数据库。&lt;/p></description></item><item><title>和我一起构建搜索引擎（二）网络爬虫</title><link>https://bitjoy.net/posts/2016-01-04-introduction-to-building-a-search-engine-2/</link><pubDate>Mon, 04 Jan 2016 16:23:19 +0800</pubDate><guid>https://bitjoy.net/posts/2016-01-04-introduction-to-building-a-search-engine-2/</guid><description>&lt;p>网络爬虫又称网络蜘蛛、Web采集器等，它是一种按照一定的规则，自动地抓取万维网信息的程序或者脚本。&lt;/p>
&lt;p>我们在设计网络爬虫的时候需要注意两点：&lt;/p>
&lt;p>&lt;strong>鲁棒性&lt;/strong>。Web中有些服务器会制造采集器陷阱（spider traps），这些陷阱服务器实际上是Web页面的生成器，它能在某个域下生成无数网页，从而使采集器陷入到一个无限的采集循环中去。采集器必须能从这些陷阱中跳出来。当然，这些陷阱倒不一定都是恶意的，有时可能是网站设计疏忽所导致的结果。&lt;/p>
&lt;p>&lt;strong>礼貌性&lt;/strong>。Web服务器具有一些隐式或显式的政策来控制采集器访问它们的频率。设计采集器时必须要遵守这些代表礼貌性的访问政策。&lt;/p>
&lt;p>采集器的基本架构如下图所示。&lt;/p>
&lt;p>&lt;img alt="the basic crawler architecture" loading="lazy" src="https://bitjoy.net/posts/2016-01-04-introduction-to-building-a-search-engine-2/the-basic-crawler-architecture.png">&lt;/p>
&lt;p>基本上是“抓取→分析→得到新的URL→再抓取→再分析”这样一个死循环过程。&lt;/p>
&lt;p>以上内容摘自王斌老师翻译的《信息检索导论》课本。&lt;/p>
&lt;p>由于我们要做的是一个新闻搜索引擎，所以抓取的是新闻数据，对于爬虫，网上也有很多的开源程序，如nutch等，Github上还有人专门开发了抓取新闻的组件&lt;a href="https://github.com/codelucas/newspaper">newspaper&lt;/a>，可以很方便的提取新闻标题、正文、时间等信息。不过用python写爬虫也是分分钟的事情，下面我们一起来试一试。&lt;/p>
&lt;p>首先找一个新闻网站，为简单起见，要找那种结构清晰、html代码便于解析的门户网站，比如&lt;a href="http://news.sohu.com/1/0903/61/subject212846158.shtml">搜狐新闻&lt;/a>、&lt;a href="http://www.cankaoxiaoxi.com/china/szyw/">参考消息&lt;/a>等。&lt;/p>
&lt;p>搜狐新闻的国内要闻列表如下：&lt;/p>
&lt;p>&lt;img alt="sohu news1" loading="lazy" src="https://bitjoy.net/posts/2016-01-04-introduction-to-building-a-search-engine-2/sohu-news1.png">&lt;/p>
&lt;p>结构非常清楚，左边是带URL的标题，右边括号里有新闻时间。这一页列表就有200条新闻，如果我们要获取1000条，只要不断模拟点击下一页即可。下一页的URL也只是在首页的基础上加上_xxx.shtml，xxx就是不同的页码。&lt;/p>
&lt;p>查看列表的html源码，得知列表都在类名为newsblue1的td中，所以只需要解析html源码就可以得到新闻标题、URL和时间，python解析html可以用BeautifulSoup包，非常方便。&lt;/p>
&lt;p>进入到新闻详细页面，正文部分如下：&lt;/p>
&lt;p>&lt;img alt="sohu news2" loading="lazy" src="https://bitjoy.net/posts/2016-01-04-introduction-to-building-a-search-engine-2/sohu-news2.png">&lt;/p>
&lt;p>查看html源码，正文位于类名为text clear的div中，据此可以很方便的提取新闻正文。&lt;/p>
&lt;p>得到一条新闻的所有数据之后，我们需要将之结构化成xml文件，借助相应的xml包可以很方便的完成这项工作。xml格式定义如下：&lt;/p>
&lt;p>&lt;img alt="xml format" loading="lazy" src="https://bitjoy.net/posts/2016-01-04-introduction-to-building-a-search-engine-2/xml-format.png">&lt;/p>
&lt;p>注意爬虫需要访问网络，难免会出现一些异常，所以捕获异常是非常有必要的。另外，搜狐每篇新闻正文后面都会有一段’//’开始的注释，这个需要过滤掉，短于140个字的新闻我也过滤掉了。整个搜索系统的配置参数都存储在config.ini文件中。&lt;/p>
&lt;p>下面是完整的python 3.4+代码。&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">42
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">43
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">44
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">45
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">46
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">47
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">48
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">49
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">50
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">51
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">52
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">53
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">54
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">55
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">56
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">57
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">58
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">59
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">60
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">61
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">62
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">63
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">64
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">65
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">66
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">67
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">68
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">69
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">70
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">71
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">72
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">73
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">74
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">75
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># -*- coding: utf-8 -*-&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Created on Sat Dec 19 11:57:01 2015
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">@author: bitjoy.net
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> bs4 &lt;span style="color:#f92672">import&lt;/span> BeautifulSoup
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> urllib.request
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> xml.etree.ElementTree &lt;span style="color:#66d9ef">as&lt;/span> ET
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> configparser
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">get_news_pool&lt;/span>(root, start, end):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> news_pool &lt;span style="color:#f92672">=&lt;/span> []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> range(start,end,&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> page_url &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> i &lt;span style="color:#f92672">!=&lt;/span> start:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> page_url &lt;span style="color:#f92672">=&lt;/span> root &lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#e6db74">&amp;#39;_&lt;/span>&lt;span style="color:#e6db74">%d&lt;/span>&lt;span style="color:#e6db74">.shtml&amp;#39;&lt;/span>&lt;span style="color:#f92672">%&lt;/span>(i)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> page_url &lt;span style="color:#f92672">=&lt;/span> root &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#39;.shtml&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response &lt;span style="color:#f92672">=&lt;/span> urllib&lt;span style="color:#f92672">.&lt;/span>request&lt;span style="color:#f92672">.&lt;/span>urlopen(page_url)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">except&lt;/span> &lt;span style="color:#a6e22e">Exception&lt;/span> &lt;span style="color:#66d9ef">as&lt;/span> e:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;—–&lt;/span>&lt;span style="color:#e6db74">%s&lt;/span>&lt;span style="color:#e6db74">: &lt;/span>&lt;span style="color:#e6db74">%s&lt;/span>&lt;span style="color:#e6db74">—–&amp;#34;&lt;/span>&lt;span style="color:#f92672">%&lt;/span>(type(e), page_url))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> html &lt;span style="color:#f92672">=&lt;/span> response&lt;span style="color:#f92672">.&lt;/span>read()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> soup &lt;span style="color:#f92672">=&lt;/span> BeautifulSoup(html)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> td &lt;span style="color:#f92672">=&lt;/span> soup&lt;span style="color:#f92672">.&lt;/span>find(&lt;span style="color:#e6db74">&amp;#39;td&amp;#39;&lt;/span>, class_ &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;newsblue1&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> a &lt;span style="color:#f92672">=&lt;/span> td&lt;span style="color:#f92672">.&lt;/span>find_all(&lt;span style="color:#e6db74">&amp;#39;a&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> span &lt;span style="color:#f92672">=&lt;/span> td&lt;span style="color:#f92672">.&lt;/span>find_all(&lt;span style="color:#e6db74">&amp;#39;span&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> range(len(a)):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> date_time &lt;span style="color:#f92672">=&lt;/span> span[i]&lt;span style="color:#f92672">.&lt;/span>string
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> url &lt;span style="color:#f92672">=&lt;/span> a[i]&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;href&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> title &lt;span style="color:#f92672">=&lt;/span> a[i]&lt;span style="color:#f92672">.&lt;/span>string
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> news_info &lt;span style="color:#f92672">=&lt;/span> [&lt;span style="color:#e6db74">&amp;#39;2016-&amp;#39;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>date_time[&lt;span style="color:#ae81ff">1&lt;/span>:&lt;span style="color:#ae81ff">3&lt;/span>]&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#e6db74">&amp;#39;-&amp;#39;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>date_time[&lt;span style="color:#ae81ff">4&lt;/span>:&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>]&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#e6db74">&amp;#39;:00&amp;#39;&lt;/span>,url,title]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> news_pool&lt;span style="color:#f92672">.&lt;/span>append(news_info)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span>(news_pool)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">crawl_news&lt;/span>(news_pool, min_body_len, doc_dir_path, doc_encoding):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> i &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> news &lt;span style="color:#f92672">in&lt;/span> news_pool:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response &lt;span style="color:#f92672">=&lt;/span> urllib&lt;span style="color:#f92672">.&lt;/span>request&lt;span style="color:#f92672">.&lt;/span>urlopen(news[&lt;span style="color:#ae81ff">1&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">except&lt;/span> &lt;span style="color:#a6e22e">Exception&lt;/span> &lt;span style="color:#66d9ef">as&lt;/span> e:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;—–&lt;/span>&lt;span style="color:#e6db74">%s&lt;/span>&lt;span style="color:#e6db74">: &lt;/span>&lt;span style="color:#e6db74">%s&lt;/span>&lt;span style="color:#e6db74">—–&amp;#34;&lt;/span>&lt;span style="color:#f92672">%&lt;/span>(type(e), news[&lt;span style="color:#ae81ff">1&lt;/span>]))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> html &lt;span style="color:#f92672">=&lt;/span> response&lt;span style="color:#f92672">.&lt;/span>read()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> soup &lt;span style="color:#f92672">=&lt;/span> BeautifulSoup(html)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> body &lt;span style="color:#f92672">=&lt;/span> soup&lt;span style="color:#f92672">.&lt;/span>find(&lt;span style="color:#e6db74">&amp;#39;div&amp;#39;&lt;/span>, class_ &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;text clear&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>find(&lt;span style="color:#e6db74">&amp;#39;div&amp;#39;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>get_text()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">except&lt;/span> &lt;span style="color:#a6e22e">Exception&lt;/span> &lt;span style="color:#66d9ef">as&lt;/span> e:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;—–&lt;/span>&lt;span style="color:#e6db74">%s&lt;/span>&lt;span style="color:#e6db74">: &lt;/span>&lt;span style="color:#e6db74">%s&lt;/span>&lt;span style="color:#e6db74">—–&amp;#34;&lt;/span>&lt;span style="color:#f92672">%&lt;/span>(type(e), news[&lt;span style="color:#ae81ff">1&lt;/span>]))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#e6db74">&amp;#39;//&amp;#39;&lt;/span> &lt;span style="color:#f92672">in&lt;/span> body:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> body &lt;span style="color:#f92672">=&lt;/span> body[:body&lt;span style="color:#f92672">.&lt;/span>index(&lt;span style="color:#e6db74">&amp;#39;//&amp;#39;&lt;/span>)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> body &lt;span style="color:#f92672">=&lt;/span> body&lt;span style="color:#f92672">.&lt;/span>replace(&lt;span style="color:#e6db74">&amp;#34; &amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> len(body) &lt;span style="color:#f92672">&amp;lt;=&lt;/span> min_body_len:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> doc &lt;span style="color:#f92672">=&lt;/span> ET&lt;span style="color:#f92672">.&lt;/span>Element(&lt;span style="color:#e6db74">&amp;#34;doc&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ET&lt;span style="color:#f92672">.&lt;/span>SubElement(doc, &lt;span style="color:#e6db74">&amp;#34;id&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>text &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">%d&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#f92672">%&lt;/span>(i)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ET&lt;span style="color:#f92672">.&lt;/span>SubElement(doc, &lt;span style="color:#e6db74">&amp;#34;url&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>text &lt;span style="color:#f92672">=&lt;/span> news[&lt;span style="color:#ae81ff">1&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ET&lt;span style="color:#f92672">.&lt;/span>SubElement(doc, &lt;span style="color:#e6db74">&amp;#34;title&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>text &lt;span style="color:#f92672">=&lt;/span> news[&lt;span style="color:#ae81ff">2&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ET&lt;span style="color:#f92672">.&lt;/span>SubElement(doc, &lt;span style="color:#e6db74">&amp;#34;datetime&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>text &lt;span style="color:#f92672">=&lt;/span> news[&lt;span style="color:#ae81ff">0&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ET&lt;span style="color:#f92672">.&lt;/span>SubElement(doc, &lt;span style="color:#e6db74">&amp;#34;body&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>text &lt;span style="color:#f92672">=&lt;/span> body
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tree &lt;span style="color:#f92672">=&lt;/span> ET&lt;span style="color:#f92672">.&lt;/span>ElementTree(doc)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tree&lt;span style="color:#f92672">.&lt;/span>write(doc_dir_path &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">%d&lt;/span>&lt;span style="color:#e6db74">.xml&amp;#34;&lt;/span>&lt;span style="color:#f92672">%&lt;/span>(i), encoding &lt;span style="color:#f92672">=&lt;/span> doc_encoding, xml_declaration &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">True&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> i &lt;span style="color:#f92672">+=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> __name__ &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#39;__main__&amp;#39;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config &lt;span style="color:#f92672">=&lt;/span> configparser&lt;span style="color:#f92672">.&lt;/span>ConfigParser()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config&lt;span style="color:#f92672">.&lt;/span>read(&lt;span style="color:#e6db74">&amp;#39;../config.ini&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;utf-8&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> root &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;http://news.sohu.com/1/0903/61/subject212846158&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> news_pool &lt;span style="color:#f92672">=&lt;/span> get_news_pool(root, &lt;span style="color:#ae81ff">854&lt;/span>, &lt;span style="color:#ae81ff">849&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> crawl_news(news_pool, &lt;span style="color:#ae81ff">140&lt;/span>, config[&lt;span style="color:#e6db74">&amp;#39;DEFAULT&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;doc_dir_path&amp;#39;&lt;/span>], config[&lt;span style="color:#e6db74">&amp;#39;DEFAULT&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;doc_encoding&amp;#39;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#39;done!&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;hr>
&lt;p>完整可运行的新闻搜索引擎Demo请看我的Github项目&lt;a href="https://github.com/01joy/news_search_engine">news_search_engine&lt;/a>。&lt;/p></description></item><item><title>和我一起构建搜索引擎（一）简介</title><link>https://bitjoy.net/posts/2016-01-04-introduction-to-building-a-search-engine-1/</link><pubDate>Mon, 04 Jan 2016 13:56:23 +0800</pubDate><guid>https://bitjoy.net/posts/2016-01-04-introduction-to-building-a-search-engine-1/</guid><description>&lt;p>我们上网用得最多的一项服务应该是搜索，不管大事小情，都喜欢百度一下或谷歌一下，那么百度和谷歌是怎样从浩瀚的网络世界中快速找到你想要的信息呢，这就是搜索引擎的艺术，属于信息检索的范畴。&lt;/p>
&lt;p>这学期学习了《现代信息检索》课程，使用的是Stanford的教材&lt;a href="http://nlp.stanford.edu/IR-book/">Introduction to Information Retrieval&lt;/a>，网上有电子版，大家可以参考。&lt;/p>
&lt;p>本课程的大作业是完成一个新闻搜索引擎，要求是这样的：定向采集3-4个新闻网站，实现这些网站信息的抽取、索引和检索。网页数目不少于10万条。能按相关度、时间和热度（需要自己定义）进行排序，能实现相似新闻的自动聚类。&lt;/p>
&lt;p>截止日期12月31日，我们已经在规定时间完成了该系统，自认为检索效果不错，所以在此把过程记录如下，欢迎讨论。&lt;/p>
&lt;p>网上有很多开源的全文搜索引擎，比如Lucene、Sphinx、Whoosh等，都提供了很好的API，开发者只需要调用相关接口就可以实现一个全功能的搜索引擎。不过既然学习了IR这门课，自然要把相关技术实践一下，所以我们打算自己实现一个搜索引擎。&lt;/p>
&lt;p>这是简介部分，主要介绍整个搜索引擎的思路和框架。&lt;/p>
&lt;p>&lt;img alt="search engine outline" loading="lazy" src="https://bitjoy.net/posts/2016-01-04-introduction-to-building-a-search-engine-1/search-engine-architecture.png">&lt;/p>
&lt;p>上图为本搜索引擎的框架图。首先爬虫程序从特定的几个新闻网站抓取新闻数据，然后过滤网页中的图片、视频、广告等无关元素，抽取新闻的主体内容，得到结构化的xml数据。然后一方面使用内存式单遍扫描索引构建方法（SPIMI）构建倒排索引，供检索模型使用；另一方面根据向量空间模型计算两两新闻之间的余弦相似度，供推荐模块使用。最后利用概率检索模型中的BM25公式计算给定关键词下的文档相关性评分，BM25打分结合时间因素得到热度评分，根据评分给出排序结果。&lt;/p>
&lt;p>在后续博文中，我会详细介绍每个部分的实现。&lt;/p>
&lt;hr>
&lt;p>完整可运行的新闻搜索引擎Demo请看我的Github项目&lt;a href="https://github.com/01joy/news_search_engine">news_search_engine&lt;/a>。&lt;/p>
&lt;p>以下是系列博客：&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-04-introduction-to-building-a-search-engine-1/">和我一起构建搜索引擎（一）简介&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-04-introduction-to-building-a-search-engine-2/">和我一起构建搜索引擎（二）网络爬虫&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-07-introduction-to-building-a-search-engine-3/">和我一起构建搜索引擎（三）构建索引&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-07-introduction-to-building-a-search-engine-4/">和我一起构建搜索引擎（四）检索模型&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-09-introduction-to-building-a-search-engine-5/">和我一起构建搜索引擎（五）推荐阅读&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-09-introduction-to-building-a-search-engine-6/">和我一起构建搜索引擎（六）系统展示&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://bitjoy.net/posts/2016-01-09-introduction-to-building-a-search-engine-7/">和我一起构建搜索引擎（七）总结展望&lt;/a>&lt;/p></description></item><item><title>认真你就赢了</title><link>https://bitjoy.net/posts/2016-01-04-attitude-is-everything/</link><pubDate>Mon, 04 Jan 2016 12:42:21 +0800</pubDate><guid>https://bitjoy.net/posts/2016-01-04-attitude-is-everything/</guid><description>&lt;p>突然发现，从小到大，自己做事都做得很慢，别人一会做完的作业，我可能要花好几个小时。但拿作业一对比，明显能看出差距，自己精雕细琢的作品不是别人随随便便就能比的。&lt;/p>
&lt;p>最近几次和同学合作完成大作业也遇到了类似的情况，数据抓取的同学给我的数据，不是格式不对就是内容缺胳膊少腿，质量极其差，还不愿修改，曰：只是做一个演示系统，有数据就行了。他不知道他这样的数据给我，我们后面做得再好，最终的演示效果也不会好，他这样的随意，后面的人不知要多花多少时间来弥补。我也无意跟他多费口舌，自己挽起袖子重做了他的工作。&lt;/p>
&lt;p>类似的事情，我遇到的不在少数，和别人沟通的时间远远超过了自己完成任务的时间。所以往往一个很简单的工作，我要花比别人多两到三倍的时间。这个过程就像工匠在雕琢自己的作品，是不计时间的，直到自己认为完美为止。这大概就是老罗所说的工匠精神吧。&lt;/p>
&lt;p>在一个完美主义者的眼里，这是一个千疮百孔的世界。&lt;/p>
&lt;p>糟糕的文档排版，错别字和错误标点一堆，一群人并排走挡了后面或对面的人，开水房离宿舍十万八千里，蚊香的设计，电脑接口位置的设计，U盘接口的设计，凸出的摄像头，插队，说脏话。。。&lt;/p>
&lt;p>当然也有同学劝我，这些东西差不多就行了，何必花这么多时间做这么好干什么，还不如去看个电影打个球。也经常听人说Take it easy，别太认真，认真你就输了。&lt;/p>
&lt;p>但是我始终相信，态度决定一切。你一天认真做了，别人不一定看得到，但坚持一个月甚至一年，总会有志同道合的人发现你，而你的坚持也将一点点的改变这个行业这个世界。就像老罗做手机，虽然销量不怎么样，但他的工匠精神、他的情怀，值得每一个人尊敬。T2统一听筒和各种传感器的位置、消失的电源键、消失的SIM卡插槽、消失的金属中框断点完全是超出iPhone的美好设计。希望老罗的情怀之路能够坚持下去、越走越远。&lt;/p>
&lt;p>&lt;img alt="smartisan-T2-2015" loading="lazy" src="https://bitjoy.net/posts/2016-01-04-attitude-is-everything/smartisan-T2-2015.jpg">&lt;/p></description></item><item><title>2016新年快乐</title><link>https://bitjoy.net/posts/2016-01-03-2016-happy-new-year/</link><pubDate>Sun, 03 Jan 2016 12:25:50 +0800</pubDate><guid>https://bitjoy.net/posts/2016-01-03-2016-happy-new-year/</guid><description>&lt;p>2015年过得好快，梳理一下，2015年的时间线大概是这样的：&lt;/p>
&lt;p>3月来北京计算所做毕设→5月返回武大修改论文→5月30公开答辩→6月毕业季→7月回北京计算所→8月回家陪父母→9月国科大开学→持续高强度的学习→2016元旦还在图书馆研究NPC问题。&lt;/p>
&lt;p>2015年给我的总体感受是很忙，但忙的事情都很琐碎，并没有什么大的里程碑事件，不过以下三件事情我认为值得一提。&lt;/p>
&lt;ol>
&lt;li>本科四年修成正果，研究生三年新的起航&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="whu_certificate" loading="lazy" src="https://bitjoy.net/posts/2016-01-03-2016-happy-new-year/whu_certificate.jpg">
&lt;img alt="ucas_admission" loading="lazy" src="https://bitjoy.net/posts/2016-01-03-2016-happy-new-year/ucas_admission.jpg">&lt;/p>
&lt;ol start="2">
&lt;li>买了一辆属于自己的山地车，1k2，虽然是二手的，但足够我骑着它去看世界了:-)&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="bike" loading="lazy" src="https://bitjoy.net/posts/2016-01-03-2016-happy-new-year/bike.jpg">
&lt;img alt="2015_cycling_1" loading="lazy" src="https://bitjoy.net/posts/2016-01-03-2016-happy-new-year/2015_cycling_1.jpg">
&lt;img alt="2015_cycling_2" loading="lazy" src="https://bitjoy.net/posts/2016-01-03-2016-happy-new-year/2015_cycling_2.jpg">&lt;/p>
&lt;p>也许是在城市里待久了，我特别享受这种亲近大自然的感觉，蓝天、白云、草原、大海这些美景永远也看不够。&lt;/p>
&lt;ol start="3">
&lt;li>在国科大认识了两个好基友，虽然都是单身汪，但至少想看电影吃火锅的时候还可以有个伴。（此处居然少了三人合照）&lt;/li>
&lt;/ol>
&lt;p>2015年共写了14篇博客，包含3篇技术博客，bitjoy.net 历史累计PV1039，UV520，IP502。&lt;/p>
&lt;p>展望2016年，大的方向基本都确定了，目标如下：&lt;/p>
&lt;ol>
&lt;li>完成国科大下学期的课程任务&lt;/li>
&lt;li>接手pLink软件&lt;/li>
&lt;li>刷完LeetCode所有题目&lt;/li>
&lt;li>读10本书&lt;/li>
&lt;li>去电影院看10场电影（2015下半年在怀柔村里没看一部电影/(ㄒoㄒ)/~~）&lt;/li>
&lt;li>改正坐姿&lt;/li>
&lt;/ol>
&lt;p>大家一起见证！&lt;/p></description></item><item><title>北国的雪</title><link>https://bitjoy.net/posts/2015-11-22-snow-in-beijing/</link><pubDate>Sun, 22 Nov 2015 12:16:00 +0800</pubDate><guid>https://bitjoy.net/posts/2015-11-22-snow-in-beijing/</guid><description>&lt;p>&lt;img loading="lazy" src="https://upload.wikimedia.org/wikipedia/commons/f/fd/Von_Koch_curve.gif">&lt;/p>
&lt;p>第一次在北方过冬，今年北京11月6日就下雪了，然而我在广州的小伙伴还穿着短袖吃着冰棍呢。。。&lt;/p>
&lt;p>&lt;img alt="2015_11_06_beijing_snow" loading="lazy" src="https://bitjoy.net/posts/2015-11-22-snow-in-beijing/2015_11_06_beijing_snow.jpg">
北京2015年的第一场雪，比以往时候来的更早一些&lt;/p>
&lt;p>今天又下起了第二场雪，下了整整两天的大雪，然而我房间的暖气却不暖了，大叔来修了两次，无功而返，说是一楼的宿舍暖气都有问题，当初设计有缺陷(╯‵□′)╯︵┻━┻&lt;/p>
&lt;p>这样也好，给了自己去图书馆的理由❉&lt;/p></description></item><item><title>解决Win10间歇性断网的问题</title><link>https://bitjoy.net/posts/2015-11-13-solution-to-win10s-network-problem/</link><pubDate>Fri, 13 Nov 2015 10:55:51 +0800</pubDate><guid>https://bitjoy.net/posts/2015-11-13-solution-to-win10s-network-problem/</guid><description>&lt;p>安装WIN10一个月以来，校园有线网经常间歇性断网，通常是20分钟不到就断了，需要重启或者把有线连接关闭再打开才可以。在微博上问过微软客服也无果，&lt;a href="http://www.pcadvisor.co.uk/forum/windows-29/windows-10-no-internet-trough-ethernet-4540238/">后来Google到某国外的解决办法&lt;/a>，现记录如下。&lt;/p>
&lt;p>说到底WIN10断网的问题还是和驱动有关，先看一下我的有线网卡Broadcom NetLink (TM) Gigabit Ethernet，驱动信息是这样的：&lt;/p>
&lt;p>&lt;img alt="bcm-driver-before" loading="lazy" src="https://bitjoy.net/posts/2015-11-13-solution-to-win10s-network-problem/bcm-driver-before.png">&lt;/p>
&lt;p>还是13年的驱动，版本号是15.6.0.14，于是第一想到的是更新驱动。点击驱动右键选更新-&amp;gt;自动搜索更新的驱动程序软件-&amp;gt;提示“已安装适合设备的最佳驱动程序软件”，但这明明不是最新的驱动啊！&lt;/p>
&lt;p>&lt;a href="https://www.broadcom.com/support/ethernet-nic-netxtreme-i-desktop-mobile">于是在Broadcom的官网上找到了最新驱动win_b57_x64-17.2.0.2&lt;/a>，版本号是17.2.0.2，更新日期2015-10-27，原来这才是最新的驱动。&lt;/p>
&lt;p>（&lt;strong>2018.1.25&lt;/strong>更新：上面的地址已失效，&lt;a href="https://www.broadcom.cn/products/ethernet-connectivity/controllers/bcm5720#downloads">最新地址请点击此处&lt;/a>，并选择DOWNLOADS→Software→NetLink®/NetXtreme® I Desktop/Mobile/Server (x64)，&lt;a href="https://bitjoy.net/posts/2015-11-13-solution-to-win10s-network-problem/win_b57_x64-17.2.0.2.zip">也可以从本站下载&lt;/a>。）&lt;/p>
&lt;p>在安装最新驱动之前，我们需要关闭WIN10的自动更新驱动功能，因为WIN10会认为它的15.6.0.14版本是最新的，在windows update时把实际最新的17.2.0.2版本替换掉。具体做法是在Cortana中搜索“&lt;strong>更改设备安装设置&lt;/strong>”并打开，选择否，从不安装来自Windows更新的驱动程序软件，如下。&lt;/p>
&lt;p>&lt;img alt="change-device-installation" loading="lazy" src="https://bitjoy.net/posts/2015-11-13-solution-to-win10s-network-problem/change-device-installation.png">&lt;/p>
&lt;p>然后&lt;a href="http://jingyan.baidu.com/article/fea4511a72cb38f7ba912543.html">重启进入安全模式&lt;/a>，再次在设备管理器中右键点击网卡驱动，选择更新-&amp;gt;浏览计算机以查找驱动程序软件-&amp;gt;从计算机的设备驱动程序列表中选取-&amp;gt;点击从磁盘安装按钮-&amp;gt;浏览找到你之前在网上下载的最新驱动（*.inf格式）-&amp;gt;选中-&amp;gt;依次确定。刷新之后再次查看驱动信息如下：&lt;/p>
&lt;p>&lt;img alt="bcm-driver-after" loading="lazy" src="https://bitjoy.net/posts/2015-11-13-solution-to-win10s-network-problem/bcm-driver-after.png">&lt;/p>
&lt;p>可以看到驱动已经更新到最新的版本了。再次重启进入正常模式，目前用了两天了也没有再断过网。&lt;/p>
&lt;p>其他WIN10驱动问题应该也可以用类似的方法解决。&lt;/p>
&lt;p>（话说我的WIN10偶尔会死机，就是用着用着突然鼠标和键盘完全动不了了，只能强制重启，有谁知道这是怎么回事吗？）&lt;/p></description></item><item><title>读施一公博客有感</title><link>https://bitjoy.net/posts/2015-10-31-review-about-shiyigongs-blogs/</link><pubDate>Sat, 31 Oct 2015 10:23:57 +0800</pubDate><guid>https://bitjoy.net/posts/2015-10-31-review-about-shiyigongs-blogs/</guid><description>&lt;p>大家好，&lt;a href="http://blog.sciencenet.cn/home.php?mod=space&amp;amp;uid=46212&amp;amp;do=blog&amp;amp;view=me&amp;amp;from=space">施一公老师的26篇博客&lt;/a>大概可以分为四类：1）讲述个人生活经历2）评论社会问题3）讨论国家科技和人才引进政策4）介绍学习方法。这些博客比较全面地反应了施一公的求学经历、由学生到教授的转变过程以及回国之后为中国人才引进所做出的努力。&lt;/p>
&lt;p>给我感触最深的有3点：1）环境对人的影响很大2）坚持总会有所收获3）做一个有担当、有社会责任感的科研人。&lt;/p>
&lt;h1 id="1环境对人的影响很大">1）环境对人的影响很大&lt;/h1>
&lt;p>《从&amp;lt;高考1977&amp;gt;说起》这篇博客详细介绍了施一公高考前的家庭情况，施一公的父亲是哈工大毕业，母亲是北京矿业学院（今中国矿业大学）毕业，在上世纪五六十年代，父母都是名校大学毕业，可谓是少有的知识分子家庭。施一公还有两个姐姐、一个哥哥、一个表哥和一个表姐，哥哥姐姐们刻苦的学习、父亲悉心的辅导以及不错的高考成绩对施一公产生了很大的影响，争强好胜的施一公自然不甘示弱，以84年全国数学联赛省第一名的成绩保送清华。诚然，施一公的成绩和他自己的刻苦努力分不开，但是从小良好的家庭氛围也功不可没。&lt;/p>
&lt;h1 id="2坚持总会有所收获">2）坚持总会有所收获&lt;/h1>
&lt;p>这可以从施一公的两个例子中看出。&lt;/p>
&lt;p>《今天3000米》讲到施一公从82年跑步的“倒数第一“、“颜面尽失”之后开始坚持长跑，直到89年从未间断，在85年的清华新生运动会3000米竞走中，以16分10秒轻松获得第一名。&lt;/p>
&lt;p>跑步这件事我也深有体会，我高中几乎没有体育锻炼，大一刚入学的体能测试中，1000米项目跑了4’15’’，小组倒数第一，跑完全程脸都发白，当时真的担心大学因体育挂科毕不了业。后来大三下的时候，开始坚持跑步，一开始每晚跑3圈，一个月后加一圈，最后稳定在每晚跑5圈，一直坚持到毕业。在毕业体能测试中，还是1000米项目，我居然跑了3’42’’，小组顺数第一名，跑完之后虽然有点累，但并不感觉难受，连我自己都不太相信。&lt;/p>
&lt;p>《如何做一名优秀的博士生：（一）时间的付出》中，施一公讲到他在留学期间的时间付出。“留学的第一年，我情绪波动很大，内心浮躁而迷茫，根本无心念书。”“第二年，每周五天、每天从上午9点做实验到晚上7、8点，周末也会去两个半天。””到了第三年，晚上常常干到11点多，赶最后一班校车从霍普金斯医学院回Homewood campus（我住在附近）。””研究生阶段后期，我的刻苦在实验室是出了名的。每天晚上做实验到半夜三点左右，回到住处躺下来睡觉时常常已是四点以后；但每天早晨八点都会被窗外纽约第一大道(First Avenue)上的汽车喧闹声吵醒，九点左右又回到实验室开始了新的一天…”&lt;/p>
&lt;p>施一公几年如一日的坚持没有白费，他顺利毕业并获得名校终生教职席位。&lt;/p>
&lt;p>其实正如H老师所说“&lt;strong>以大多数人努力的程度，根本还没到拼智商的时候。&lt;/strong>” 坚持做一件事，点滴积累，做到极致。无论什么事情，坚持做下去，一定能有所收获，对于体力活更是如此。&lt;/p>
&lt;h1 id="3做一个有担当有社会责任感的科研人">3）做一个有担当、有社会责任感的科研人&lt;/h1>
&lt;p>在读施一公博客的时候，心潮澎湃，热血沸腾，无论是施一公自己排除万难坚持回国的行动，还是施一公回国之后号召海龟回国的倡议，亦或是施一公为人才引进，千人计划建言献策的付出，都真真切切的体现了他的强烈的爱国热情。&lt;/p>
&lt;p>施一公回国后的去私心、敢担当、有作为，坚持职业操守，“我申请基金的时候一定不和评委在评审前或评审后做任何形式的私下沟通；我当评委的时候一定不和申请人在评审前或评审后做任何形式的私下沟通”等都在用切身行动一点点改善国内的科研环境。&lt;/p>
&lt;p>在pFind组，H老师也时常教导我们要&lt;strong>对学术保留一点敬畏之心&lt;/strong>，做好科研，尽自己一份力改善国际社会对中国学术界的看法。&lt;/p>
&lt;p>总的来说，施一公老师的博客内容丰富，让我受益匪浅，也给了我很多启发，关于如何做一名合格的研究生，我还完全是门外汉，前面的师兄师姐都给出了很多方法论的解读，我也把施一公关于如何做一名优秀的博士生的几个要点罗列如下，希望用此标准来要求自己。&lt;/p>
&lt;p>如何一名优秀的博士生：&lt;/p>
&lt;ol>
&lt;li>时间的付出&lt;/li>
&lt;li>方法论的转变
&lt;ol>
&lt;li>正确分析负面结果&lt;/li>
&lt;li>耗费时间的完美主义阻碍创新进取&lt;/li>
&lt;li>科研文献与学术讲座的取与舍&lt;/li>
&lt;li>挑战传统思维&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ol>
&lt;p>祝大家工作顺利！&lt;/p>
&lt;p>-bitJoy&lt;/p></description></item><item><title>浮点数知识及Grisu算法介绍</title><link>https://bitjoy.net/posts/2015-08-30-introduction-to-floating-point-numbers-and-grisu-algorithm/</link><pubDate>Sun, 30 Aug 2015 20:45:36 +0800</pubDate><guid>https://bitjoy.net/posts/2015-08-30-introduction-to-floating-point-numbers-and-grisu-algorithm/</guid><description>&lt;p>进入研究生生涯完成的第一个新生培训作业是“2.5亿个浮点数的外部排序算法”，前后折腾了将近一个月，结果是在i7处理器上，限制512MB内存，排序用时250秒左右。&lt;/p>
&lt;p>这个作业的常规思路大部分人都能想到，按块读取文件-&amp;gt;atof转换为double-&amp;gt;内部快速排序或基数排序-&amp;gt;dtoa转换为char*-&amp;gt;按块写入文件。这里面中间的三个过程都很耗时，特别是atof和dtoa，因为精度只要求保留9位小数，所以可以自己实现atof和dtoa来加速，也可以使用多线程加速。&lt;/p>
&lt;p>整个作业都是基于对IEEE754浮点数的深刻理解展开的，所以下面详细讲解浮点数的一些知识。&lt;/p>
&lt;h1 id="ieee754双精度浮点数">IEEE754双精度浮点数&lt;/h1>
&lt;p>目前大多数CPU内浮点数的表示都遵循IEEE754标准，IEEE754双精度浮点数（double）表示如下图所示。&lt;/p>
&lt;p>&lt;img alt="IEEE754 double在内存中的形式[1]" loading="lazy" src="https://upload.wikimedia.org/wikipedia/commons/a/a9/IEEE_754_Double_Floating_Point_Format.svg">
IEEE754 double在内存中的形式[1]&lt;/p>
&lt;ul>
&lt;li>sign bit：符号位，1位，用来表示正负号，0表示非负；1表示负&lt;/li>
&lt;li>exponent：指数位，11位，用来表示次方数，是一个无符号数&lt;/li>
&lt;li>fraction：尾数位，52位，用来表示精确度，也是一个无符号数，有些资料也叫做mantissa或significand&lt;/li>
&lt;/ul>
&lt;p>这种表示形式有两点需要注意。&lt;/p>
&lt;p>&lt;strong>第一，既然exponent是无符号的，那么怎样表示负指数呢？&lt;/strong>&lt;/p>
&lt;p>IEEE754规定，二进制串中算得的e需要减去一个偏移量bias，对于double，bias=1023，即e’=e-bias。因为\(e\in[0,2^{11}-1]\)，所以最终\(e’\in[-2^{10}+1,2^{10}]\)。但是如果把e本身看作有符号数e”，则\(e”\in[-2^{10},2^{10}-1]\)，既然e”和e’相差微小，为什么不直接把e看成有符号数，而非要把它看成无符号数，再减去一个偏移量bias呢？&lt;/p>
&lt;p>&lt;a href="http://stackoverflow.com/questions/2612775/why-ieee-floating-point-number-calculate-exponent-using-a-biased-form">这是因为如果把e看成无符号数再减偏移量，浮点数大小比较速度更快。&lt;/a>引用&lt;a href="https://en.wikipedia.org/wiki/Exponent_bias">维基百科&lt;/a>的一段话：&lt;/p>
&lt;blockquote>
&lt;p>By arranging the fields so that the sign bit is in the most significant bit position, the biased exponent in the middle, then the mantissa in the least significant bits, the resulting value will be ordered properly, whether it’s interpreted as a floating point or integer value. This allows high speed comparisons of floating point numbers using fixed point hardware.&lt;/p></description></item><item><title>百度图片批量下载器（python3 + pyqt5 + eric6 + cx_Freeze4）</title><link>https://bitjoy.net/posts/2015-08-13-baidu-image-downloader-python3-pyqt5-eric6-cx_freeze4/</link><pubDate>Thu, 13 Aug 2015 17:27:15 +0800</pubDate><guid>https://bitjoy.net/posts/2015-08-13-baidu-image-downloader-python3-pyqt5-eric6-cx_freeze4/</guid><description>&lt;p>去年暑假在北大计算所实习的时候，任务之一就是批量下载百度图片。当时没学python，用c#实现了一个简易版本的批量下载器，如下图。&lt;/p>
&lt;p>&lt;img alt="C#版本百度图片批量下载器（抓的是百度的wap站点，现在好像不能用了）" loading="lazy" src="https://bitjoy.net/posts/2015-08-13-baidu-image-downloader-python3-pyqt5-eric6-cx_freeze4/BaiduImageDownloader1.png">
C#版本百度图片批量下载器（抓的是百度的wap站点，现在好像不能用了）&lt;/p>
&lt;p>当时“时间紧，任务重“，既没仔细研究百度图片API，也没处理好界面线程阻塞的问题。这个问题其实很有意思，趁着暑假在家，实现了一个比较完美的python版本，先上效果图。&lt;/p>
&lt;p>&lt;img alt="python3版本百度图片批量下载器" loading="lazy" src="https://bitjoy.net/posts/2015-08-13-baidu-image-downloader-python3-pyqt5-eric6-cx_freeze4/BaiduImageDownloader2.png">
python3版本百度图片批量下载器&lt;/p>
&lt;p>新版使用了&lt;a href="https://www.python.org/ftp/python/3.4.3/python-3.4.3.amd64.msi">python-3.4.3.amd64.msi&lt;/a> + &lt;a href="http://sourceforge.net/projects/pyqt/files/PyQt5/PyQt-5.5/PyQt5-5.5-gpl-Py3.4-Qt5.5.0-x64.exe">PyQt5-5.5-gpl-Py3.4-Qt5.5.0-x64.exe&lt;/a> + &lt;a href="http://downloads.sourceforge.net/project/eric-ide/eric6/stable/6.0.8/eric6-6.0.8.zip?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Feric-ide%2Ffiles%2Feric6%2Fstable%2F&amp;amp;ts=1439435222&amp;amp;use_mirror=nchc">eric6-6.0.8.zip&lt;/a> + &lt;a href="http://www.lfd.uci.edu/~gohlke/pythonlibs/3i673h27/cx_Freeze-4.3.4-cp34-none-win_amd64.whl">cx_Freeze-4.3.4-cp34-none-win_amd64.whl&lt;/a>，完整项目在&lt;a href="https://github.com/01joy/BaiduImageDownloader">我的GitHub上&lt;/a>。大致有如下几点工作：&lt;/p>
&lt;ol>
&lt;li>研究百度图片API，获取原始图片URL列表&lt;/li>
&lt;li>使用python3进行多线程下载&lt;/li>
&lt;li>利用pyqt5实现界面&lt;/li>
&lt;li>利用cx_Freeze4打包整个程序&lt;/li>
&lt;/ol>
&lt;p>下面记录每个步骤的要点，供后人参考。&lt;/p>
&lt;h1 id="百度图片api">百度图片API&lt;/h1>
&lt;p>正常使用百度图片搜索的时候，URL是这样的：&lt;/p>
&lt;p>&lt;a href="http://image.baidu.com/search/index?ct=201326592&amp;amp;z=0&amp;amp;tn=baiduimage&amp;amp;ipn=r&amp;amp;word=%E6%AD%A6%E6%B1%89%E5%A4%A7%E5%AD%A6&amp;amp;pn=0&amp;amp;istype=2&amp;amp;ie=utf-8&amp;amp;oe=utf-8&amp;amp;cl=2&amp;amp;lm=-1&amp;amp;st=-1&amp;amp;fr=&amp;amp;fmq=1439374041843_R&amp;amp;ic=0&amp;amp;se=&amp;amp;sme=&amp;amp;width=0&amp;amp;height=0&amp;amp;face=0">http://image.baidu.com/search/index?ct=201326592&amp;z=0&amp;tn=baiduimage&amp;ipn=r&amp;word=%E6%AD%A6%E6%B1%89%E5%A4%A7%E5%AD%A6&amp;pn=0&amp;istype=2&amp;ie=utf-8&amp;oe=utf-8&amp;cl=2&amp;lm=-1&amp;st=-1&amp;fr=&amp;fmq=1439374041843_R&amp;ic=0&amp;se=&amp;sme=&amp;width=0&amp;height=0&amp;face=0&lt;/a>&lt;/p>
&lt;p>里面有很多参数，有些我们并不需要，精简之后大概是这样的：&lt;/p>
&lt;p>&lt;a href="http://image.baidu.com/i?tn=baiduimage&amp;amp;ie=utf-8&amp;amp;word=%E7%BE%8E%E5%A5%B3&amp;amp;pn=&amp;amp;rn=&amp;amp;z=">http://image.baidu.com/i?tn=baiduimage&amp;ie=utf-8&amp;word=%E7%BE%8E%E5%A5%B3&amp;pn=&amp;rn=&amp;z=&lt;/a>&lt;/p>
&lt;p>word为搜索关键词；pn为page number&lt;del>当前是第几页，&lt;/del>&lt;strong>实际含义是image id&lt;/strong>，表示第几张图片，从0开始；rn为每一页的图片数量，最大为60；z表示图片尺寸，z=9特大尺寸，z=3大尺寸，z=2中等尺寸，z=1小尺寸，z=0所有尺寸。&lt;/p>
&lt;p>但是这个URL是给”人“看的，下一页的图片是动态加载的，其html代码的图片URL数量固定。一番查询之后发现，将tn=baiduimage换成tn=resultjson_com能获取到所有图片URL的json，json当然是给”猴“看的，这样就能轻松获取到所有图片的URL。&lt;/p>
&lt;p>慢着，仔细看看json中的objURL，是一串连”猴“都看不懂的字符串，原来百度把图片真实URL加密了，好在加密方法是简单的字符映射，参考&lt;a href="http://blog.csdn.net/hbuxiaoshe/article/details/44780653">这篇博客&lt;/a>成功解密。&lt;/p>
&lt;p>&lt;strong>更新：tn=resultjson_com的objURL是加密了，但是tn=resultjson的objURL并没有加密，所以采用tn=resultjson最佳。&lt;/strong>&lt;/p>
&lt;p>通过控制pn和rn就能获取指定数量的图片URL，但是我发现rn最大只能为60，并且不同的pn可能会有相同的图片url（比如&lt;a href="http://image.baidu.com/i?tn=resultjson_com&amp;amp;ie=utf-8&amp;amp;word=mit&amp;amp;pn=0&amp;amp;rn=60&amp;amp;z=9">pn=0&lt;/a>和&lt;a href="http://image.baidu.com/i?tn=resultjson_com&amp;amp;ie=utf-8&amp;amp;word=mit&amp;amp;pn=1&amp;amp;rn=60&amp;amp;z=9">pn=1&lt;/a>都有ippr_z2C$qAzdH3FAzdH3Fooo_z&amp;amp;e3Bd8vs7k_z&amp;amp;e3Bv54_z&amp;amp;e3BvgAzdH3F7rs5w1utsjAzdH3Fda8nAzdH3Fa080AzdH3Fda8na080aldm9bb8m_z&amp;amp;e3B3r2这个objURL），所以使用python的集合（set）去重。&lt;/p>
&lt;p>&lt;strong>更新：pn实际上指图片的id，pn=0、rn=60能获取到从0~59这60个URL列表，pn=1、rn=60能获取到从1~60这60个URL列表，所以pn=0和pn=1的列表中当然有59个是重复的。正确的做法是pn=0、rn=60获取0~59这60个URL列表，然后pn=60、rn=60获取60~119这60个列表，以此类推，这样获取到的URL就不会有重复的了。&lt;/strong>&lt;/p>
&lt;p>获取图片URL列表的简要代码如下：&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">ParseJSON&lt;/span>(self, pn, rn, st):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> url &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;http://image.baidu.com/i?tn=resultjson_com&amp;amp;amp;amp;ie=utf-8&amp;amp;amp;amp;word=&lt;/span>&lt;span style="color:#e6db74">%s&lt;/span>&lt;span style="color:#e6db74">&amp;amp;amp;amp;pn=&lt;/span>&lt;span style="color:#e6db74">%d&lt;/span>&lt;span style="color:#e6db74">&amp;amp;amp;amp;rn=&lt;/span>&lt;span style="color:#e6db74">%d&lt;/span>&lt;span style="color:#e6db74">&amp;amp;amp;amp;z=&lt;/span>&lt;span style="color:#e6db74">%d&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#f92672">%&lt;/span>(self&lt;span style="color:#f92672">.&lt;/span>word, pn, rn, self&lt;span style="color:#f92672">.&lt;/span>size)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">#print(url)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> request &lt;span style="color:#f92672">=&lt;/span> urllib&lt;span style="color:#f92672">.&lt;/span>request&lt;span style="color:#f92672">.&lt;/span>Request(url &lt;span style="color:#f92672">=&lt;/span> url, headers &lt;span style="color:#f92672">=&lt;/span> my_header)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> html &lt;span style="color:#f92672">=&lt;/span> urllib&lt;span style="color:#f92672">.&lt;/span>request&lt;span style="color:#f92672">.&lt;/span>urlopen(request)&lt;span style="color:#f92672">.&lt;/span>read()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> hjson &lt;span style="color:#f92672">=&lt;/span> json&lt;span style="color:#f92672">.&lt;/span>loads(html&lt;span style="color:#f92672">.&lt;/span>decode(&lt;span style="color:#e6db74">&amp;#39;gbk&amp;#39;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> range(&lt;span style="color:#ae81ff">0&lt;/span>, len(hjson[&lt;span style="color:#e6db74">&amp;#39;data&amp;#39;&lt;/span>])&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>):&lt;span style="color:#75715e">#最后一个数据为空&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> img_url &lt;span style="color:#f92672">=&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>DecodeURL(hjson[&lt;span style="color:#e6db74">&amp;#39;data&amp;#39;&lt;/span>][i][&lt;span style="color:#e6db74">&amp;#39;objURL&amp;#39;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> img_url &lt;span style="color:#f92672">not&lt;/span> &lt;span style="color:#f92672">in&lt;/span> st:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> st&lt;span style="color:#f92672">.&lt;/span>add(img_url)&lt;span style="color:#75715e">#去重&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>progressBar_updated_signal&lt;span style="color:#f92672">.&lt;/span>emit()&lt;span style="color:#75715e">#更新进度条&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>DecodeURL是解密函数。很奇怪，json最后一个数据是空的。&lt;/p></description></item><item><title>再见武大</title><link>https://bitjoy.net/posts/2015-06-28-farewell-to-whu/</link><pubDate>Sun, 28 Jun 2015 11:32:18 +0800</pubDate><guid>https://bitjoy.net/posts/2015-06-28-farewell-to-whu/</guid><description>&lt;p>2015年6月27日，武汉大学在梅园操场举办了2015年毕业典礼。&lt;/p>
&lt;p>&lt;img alt="武汉大学2015年毕业典礼，正在发言的是雷军学长[1]" loading="lazy" src="https://i0.wp.com/ww1.sinaimg.cn/large/634fd979jw1etiecww24cj20hs0dctbj.jpg">
武汉大学2015年毕业典礼，正在发言的是雷军学长[1]&lt;/p>
&lt;p>&lt;img alt="武汉大学2015年毕业典礼[2]" loading="lazy" src="https://i0.wp.com/news.whu.edu.cn/_mediafile/whu_news/2015/06/27/29kc177qza.jpg">
武汉大学2015年毕业典礼[2]&lt;/p>
&lt;p>坐在台下，顿感恍惚，四年前同样在梅操，同样是这些人，我们举办开学典礼。&lt;/p>
&lt;p>&lt;img alt="武汉大学2011年开学典礼[3]" loading="lazy" src="https://i0.wp.com/ww3.sinaimg.cn/large/005Muw7zjw1etjl1cs943j30j60bpgpm.jpg">
武汉大学2011年开学典礼[3]&lt;/p>
&lt;p>原来梅操到梅操的距离只有四年。&lt;/p>
&lt;p>毕业典礼结束，下午集体收拾行李，大家都默默无语。晚上去红牛—-大一第一次聚餐的地方—-吃最后的晚餐，这次聚餐喝了两箱啤酒，6瓶白酒！白酒下肚，前一口酒落地又向上翻滚，和后一口酒相互撞击，四年的往事喷涌而出。离别之际，每个人都把自己的心声说出来了，说出了自己的家境、对某某的感情、一个宿舍的兄弟情，说出了自己的抱负、未来的理想，再互相拥抱、道一声珍重。&lt;/p>
&lt;p>&lt;img alt="whu_bbs_graduation_2015" loading="lazy" src="https://bitjoy.net/posts/2015-06-28-farewell-to-whu/whu_bbs_graduation_2015.jpg">
山水一程，三生有幸[4]&lt;/p>
&lt;p>回到宿舍，所有人吐得一塌糊涂，昏睡过去。也许这就是离别的滋味，折磨着你，让你难受，只有把它吐出来，离开了，平静了，一切就好了。&lt;/p>
&lt;p>第二天醒来，发现隔壁宿舍的几个哥们已经走了，宿舍冷清了许多。去小卖部买了一些非必需品，只为把卡里的几十块钱用掉。打包行李，准备出发。&lt;/p>
&lt;p>毕业了，离开了，那些大一的迷茫、兼职，大二的信息安全竞赛，大三繁重的课业、为保研奋斗的数学建模竞赛，大四悠闲的生活也将躲藏在记忆的某个角落，不再被轻易的发现。武大的樱花、牌坊、樱顶、新图、青楼、梅操电影、珞珈之声、每天晚上在奥场穿着17号球衣跑步的女生、一起练笛的同学、在梅园食堂吃饭的一对情侣、幽默装逼的室友，这一幅幅画面，也将随着时间的车轮，慢慢消散。&lt;/p>
&lt;p>天下没有不散的筵席，我们来到这个世上，就注定要历经悲欢离合。在中国最美丽的大学，度过了我人生中最美好的年华，山水一程，三生有幸，感谢有你。&lt;/p>
&lt;p>别怕，梦的方向叫做闯，青春还没散场！&lt;/p>
&lt;p>参考：&lt;/p>
&lt;p>[1]. 武汉大学官方微博&lt;/p>
&lt;p>[2]. 武大新闻网：&lt;a href="http://news.whu.edu.cn/info/1002/43788.htm">http://news.whu.edu.cn/info/1002/43788.htm&lt;/a>&lt;/p>
&lt;p>[3]. 守望珞珈的新浪博客：&lt;a href="http://blog.sina.com.cn/s/blog_4da93d1f0100t6v9.html">http://blog.sina.com.cn/s/blog_4da93d1f0100t6v9.html&lt;/a>&lt;/p>
&lt;p>[4]. 珞珈山水bbs毕业封面：&lt;a href="http://bbs.whu.edu.cn/">http://bbs.whu.edu.cn/&lt;/a>&lt;/p></description></item><item><title>交流的乐趣</title><link>https://bitjoy.net/posts/2015-06-12-the-joy-of-communication/</link><pubDate>Fri, 12 Jun 2015 11:07:04 +0800</pubDate><guid>https://bitjoy.net/posts/2015-06-12-the-joy-of-communication/</guid><description>&lt;p>今天参加了&lt;a href="http://www.lib.whu.edu.cn/news/view.asp?id=3354">【珞珈阅读广场第89期】《礼物》（影像阅读）&lt;/a>，感触很多，收获也很多，其中最大的收获就是体会到了交流的乐趣。&lt;/p>
&lt;p>&lt;img alt="【珞珈阅读广场第89期】《礼物》（影像阅读）[1]" loading="lazy" src="https://i0.wp.com/www.lib.whu.edu.cn/news/tc/readSqua89.jpg">
【珞珈阅读广场第89期】《礼物》（影像阅读）[1]&lt;/p>
&lt;p>影片&lt;a href="http://movie.douban.com/subject/25878911/">《礼物》&lt;/a>讲述了一位功成名就的大叔和身为小偷的女生相互救赎的故事。大叔白手起家，一路打拼，最后坐拥万贯家财，却抛弃了妻子和女儿，导致妻子自杀；女生的父亲早逝，母亲生活又不检点，女生曾差点一刀把母亲捅死，为了偿还男朋友的债务，女生甚至当起了小偷。在一次行窃过程中被大叔抓住，大叔要求女生当他的司机和搬运工，带他去东京，给女儿送一个礼物。在去东京的路上，大叔走访了自己曾经到过的很多地方，但大多数都是激情满怀而去，失望而归。到达东京，当女生知道大叔要送给女儿的礼物是自己的心脏，自己的生命的时候，女生陷入了两难的境地。但是女生最终答应的大叔的请求，帮其送出了礼物，大叔得到了救赎，女生也因为大叔的一句“要好好活着”而坚强乐观的面对生活。&lt;/p>
&lt;p>90分钟的观影结束之后，主持人抛出了如下几个问题。&lt;/p>
&lt;p>&lt;img alt="关于《礼物》这部电影，主持人提出的若干问题" loading="lazy" src="https://bitjoy.net/posts/2015-06-12-the-joy-of-communication/questions-about-gift.jpg">
关于《礼物》这部电影，主持人提出的若干问题&lt;/p>
&lt;p>因为上学期我也参加过珞珈阅读广场的观影活动，当时也体会到了与他人交流的乐趣，现在马上要毕业了，所以跃跃欲试，想和同学们交流一下。正好第一个问题主持人点名叫我谈一谈。我当时谈了一下我对大叔这种献出自己生命拯救外孙女的行为表示了理解，并表示自己也会做出类似的事情。在场的另外一个老师就表达了他的观点，他对日本这种“野蛮粗暴”的拯救方式不太理解，也不太赞同，大叔最后的死亡过程类似于日本武士的剖腹自尽。&lt;/p>
&lt;p>关于第三个问题，大家也畅所欲言，从很多个方面谈了自己的想法，大部分还是认为大叔想要给外孙女抽一个好彩头的观点。其实这个观点要到最后大叔把这个“大吉”签绑到外孙女的病床上才能感觉得到，在对后面内容不知道的情况下，我认为最合理的解释应该是这一行为体现了大叔好强甚至“蛮横”的性格，因为他抽签的时候说自己从白手起家到现在亿万富翁，就像中了头彩一样，那么我现在抽签，也要像我经商一样，取得最好的结果。有的同学甚至解读出了大叔“执念”这一层含义。&lt;/p>
&lt;p>对于第四个问题，好几个同学分享了自己的经历或者想法。我当时表达了“虽然你现在面临不幸，请不要过多的抱怨，珍惜当下，因为你现在所遭遇的，正是你将来所怀念的；当你再故地重游的时候，也许像这位大叔一样，再也找不到当初那种美好的感觉了。“，并顺带告诉学弟学妹们，珍惜在武大的美好时光，自己马上要毕业了，对武大的一花一木都非常的不舍。&lt;/p>
&lt;p>最后一个问题，主持人给出了很好的解答，并且阐明了要拯救一个身处绝望的人的困难性，很精彩。&lt;/p>
&lt;p>交流过程中很有意思的一件事情是，主持人给出了这样一个观点”婚姻或家庭不幸的人，其子女的性格往往也会偏离常态，并且子女的婚姻或家庭也很可能会不幸。”，对于这个观点，大家的反应比较激烈，特别是在场的那位老师，表达了他的反对意见。我因为自己的家庭环境原因，反而表示了积极的一面，就是父母婚姻不幸的人，其孩子有可能反而更加珍惜婚姻，珍惜家庭，所以家庭有可能比一般家庭更加幸福。当然也有同学表示对爱的人抓得太紧，有可能适得其反，导致婚姻的破裂。主持人讲了这样一段话，很好的表达了这个观点：&lt;/p>
&lt;p>让爱恰到好处－不让疯长的孤独烧毁世界，也不让泛滥的博爱窒息自由。&lt;/p>
&lt;p>电影从晚上7:00到8:30，讨论从8:30到10:00。讨论结束的时候，主持人把本期两本书分别赠送给了我和另一位硕士毕业生，我的赠书是&lt;a href="http://book.douban.com/subject/25904481/">《那一天》&lt;/a>。&lt;/p>
&lt;p>&lt;img alt="赠书《那一天》[2]" loading="lazy" src="https://img1.doubanio.com/lpic/s27950209.jpg">
赠书《那一天》[2]&lt;/p>
&lt;p>讨论结束，临走的时候大家还意犹未尽，主持人对大家的讨论表示感谢，此时有一位同学表示主持人的发言也很不错。确实，整个讨论环节，主持人很好的带动起了大家发言的兴趣和积极性，包括问题的设置，主持人的点名提问以及主持人自身精彩的解说，都非常好的带动了现场的气氛，打开了观众的话匣子。不久前我刚好参加过这位主持人主持的”周末艺苑·外院专场“演出，当时主持人随机应变的能力和绝妙的口才给我留下了深刻的印象，学弟不错，加油！&lt;/p>
&lt;p>这次观影交流达到了真正交流的目的，观众中有大一大二的新生，有即将毕业的本科生和研究生，也有已经成家的中年老师，大家基于各自的背景，表达自己的想法，聆听他人的观点，达到了很好的思维发散、观点碰撞的目的。此时我想到了高中背的萧伯纳讲过的一句话：&lt;/p>
&lt;blockquote>
&lt;p>两个人各有一个苹果，交换之后，每个人还是只有一个苹果；然而，当两个人各有一种思想，交换之后，每个人却拥有了两种思想。&lt;/p>&lt;/blockquote>
&lt;p>前几天观看了踪点剧社的两部毕业大戏《理想》和《禁闭》，两部很有深意的话剧，话剧结束的时候，也有一个短暂的交流会，也很精彩。&lt;/p>
&lt;p>人很多时候会沉浸在自我的世界中，产生很多偏见，此时不妨听一听他人的观点，也许会豁然开朗或眼前一亮，觉得世界真奇妙。最后用H老师的一句话结束：&lt;strong>技术上要多钻研，技术外要多沟通，生存两个法则。&lt;/strong>&lt;/p>
&lt;p>希望每个人都能发现并享受交流的乐趣。&lt;/p>
&lt;p>参考：&lt;/p>
&lt;p>[1]. 珞珈阅读广场第89期公告：&lt;a href="http://www.lib.whu.edu.cn/news/view.asp?id=3354">http://www.lib.whu.edu.cn/news/view.asp?id=3354&lt;/a>&lt;/p>
&lt;p>[2]. 豆瓣读书《那一天》：&lt;a href="http://book.douban.com/subject/25904481/">http://book.douban.com/subject/25904481/&lt;/a>&lt;/p></description></item><item><title>我的高中生活</title><link>https://bitjoy.net/posts/2015-06-08-my-high-school-life/</link><pubDate>Mon, 08 Jun 2015 20:49:07 +0800</pubDate><guid>https://bitjoy.net/posts/2015-06-08-my-high-school-life/</guid><description>&lt;p>前几天给毕设指导老师发邮件，麻烦老师写申优推荐理由，老师回复我说她的儿子这几天高考，她不在学院，最晚要9号才能帮我写。我才意识到又是一年高考时，距离自己参加高考已经过去了四个春秋，但高中生活的场景在脑海中却依稀可见，想来是要写一篇文章追忆那平凡或不平凡的高中生活。&lt;/p>
&lt;p>&lt;img alt="classroom" loading="lazy" src="https://bitjoy.net/posts/2015-06-08-my-high-school-life/classroom.jpg">&lt;/p>
&lt;p>在我读初中的时候，县里只有一所高中－县一中，每年中考之前，县一中都会组织一次提前批考试，如果提前批考试被录取了，正式中考的时候只要过线就能上，和现在的大学自主招生有点类似。我当时考试成绩不错，大概是全县十名左右，被分到所谓的奥赛班了。正式中考完之后，县五中开始正式招生，五中是抚州市一个老板办的私立学校，据说办学模式借鉴临川一中。当时县五中为了抢占优质生源，承诺只要去五中读书，除了3年学费全免之外，还额外奖励3000块钱，而且如果高考考上清华北大，奖励10万，考上其他十大名牌大学，奖励3万。当时考虑到五中刚开始办学，又借鉴临川一中的办学模式，老师大多是抚州的“名师”，教学质量应该不错，而且去五中读书能省不少钱，爸爸建议我去五中。我当时其实是不太情愿的，毕竟一中奥赛班聚集了全县最优秀的老师和同学，不论是环境还是各项措施，都比刚办学的五中要好。不过我还是以“只要自己认真读，在哪个学校都能取得好成绩”的理由安慰自己，去了五中。&lt;/p>
&lt;p>说实话，五中学生的层次确实要比一中学生差。不过好在学校把几十个成绩比较好的安排在了一个班里，配备了更好的老师，实行特殊管理。&lt;/p>
&lt;p>在我的印象中，高中的三年过得都一样，高三并没有比高一高二累多少，或者说高一高二并没有比高三轻松多少。每天早上6点准时起床，洗漱完之后6:20做早操，高三的时候不用做早操，改早自习了。大概6:50吃早餐，7:10开始早读。因为是理科，早读的内容不外乎英语单词、英语作文、语文背诵诗词、语文作文。8:05开始正式上课，上午四节课一直上到11:40。下课之后回家吃饭，因为妈妈在校内陪读，所以午餐能在15分钟之内解决，然后马上回到教室做几道题或者看一两个作文素材。下午1点准时午休半个小时，2:05正式上课，下午三节课一直上到4:40，好像高三的时候改成了四节课，记不清了。和中午一样，快速解决晚餐，马上回到教室，首先复习或学习语文字词，包括拼音和常考成语，当时基本把《现代汉语词典》翻熟了；然后正式晚自习，一直到晚上11点才回家洗漱睡觉。当时学校规定其他班学生10点之后就必须离开教室，但是我们班特殊规定可以自习到11点。回家洗漱完之后大概11:30了，高三的时候，我还经常在睡之前打着小电筒复习一下白天学习的内容。&lt;/p>
&lt;p>学校每两个星期放1天假，再过两个星期放2天半假，大多数同学只有在2天半假的时候才回家一趟。放假期间，除了做一两套卷子，大多数时间是在看电视，另外会去书店逛一次，不过买的大多数是高考复习资料，仅有的算得上是课外读物的就是《疯狂阅读》或者《读者》之类的了，小县城的书店也没有其他的“闲书”。高考要求阅读的几篇经典名著，几乎没有完整阅读过纸质版，高三的时候为了应付高考，时间紧，任务重，直接从机房下载了《巴黎圣母院》、《堂吉诃德》等改编电影，这才稍微了解了一下主要内容。学校也没有像样的图书馆，在石城那个小县城，不可能买到这些“高大上”的书。我相信大城市的很多高中生肯定看过很多这类世界名著，周末或者放假的时候也是在忙着学琴棋书画。这可能就是所谓的城乡教育差别吧，虽然这种差别在高考的时候体现得并不明显，农村的孩子在高中稍微刻苦一下，也能上不错的大学；但是一旦到了大学，大城市的孩子和我这种从农村走出来的孩子的差别一下就能看出来。大城市的孩子不论是在交际、口才、学识、才艺等方面都能轻松碾压农村的孩子，农村孩子虽然你很刻苦，卷面成绩不错，但是知识面不够宽广，格局比较小，几乎没有才艺；并不是城里人歧视你，不和你玩，但是和你聊美术，你懂吗，和你聊莎士比亚剧作，你看过吗，和你排练音乐舞蹈，你会吗。来到武大之后，我对这种城乡读书孩子之间的差别真的深有体会，无论我多么努力，好像总达不到他们的高度，总是无法融入他们的生活。&lt;/p>
&lt;p>高中不像大学，每个班有固定的教师，每个人有固定的座位，有自己的“左邻右舍”，坐在座位上，真的感觉很温暖。每到下课的时候，班上都闹哄哄的，同班同学之间的交流也很多，班集体的荣誉感以及个人的归属感也很强。每次打扫卫生的时候，几乎要经过每个同学的座位，问一问有没有垃圾要处理的；每次发考卷的时候，也会左顾右盼，相互逗个乐。一年中要数元旦晚会最为热闹，犹记得高三那年元旦，我为了演唱“海阔天空”，每天回家吃饭的时候就听mp3，走在路上也会小声哼唱，当然对于五音不全的我，演唱效果并不是很好:-) 晚会当天下午开始布置场地，所有人把书搬回宿舍，清空教室，在玻璃窗上贴上气球，圣诞树贴纸，或者某个小画家直接在上面画一幅画，电风扇和墙上都会挂上彩带；在教室四周摆上课桌，课桌上摆上事先买好的瓜子、花生、糖果、饮料等；同学们借来音响话筒，老师也把自己的笔记本搬到教室，一场简朴晚会现场就布置好了。晚会的所有工作人员、演员、主持人都是自己班上的同学，大家欢聚一堂，过着小集体的节日，有时候在同学和主持人的怂恿下，老师们也会激情献唱一首。现在回想起来，这种小集体归属感真的很美好，高中毕业之后，我大概再也没有过这种感觉了。&lt;/p>
&lt;p>春节过后，就是高考的紧要关头了，每周一的班会课上，老师都会给我们加油鼓劲，告诉我们大学有多轻松美好。百日会战那天，班主任甚至亲自泼墨，写下“辛苦数日，幸福一生”的对联，贴在教室的后墙上。高三，每天就是不停的做卷子、刷题，日考、周考、月考，不断的考试，往往上一张考卷还没有讲评，下一次考试又到了。考得多了，对成绩也不那么看重了，不过也基本稳定在前三。&lt;/p>
&lt;p>&lt;img alt="图片来自[1]" loading="lazy" src="https://i0.wp.com/a3.att.hudong.com/66/83/300000908235130708833431958_950.jpg">
图片来自[1]&lt;/p>
&lt;p>距离高考只剩一个星期的时候，题量开始下降，老师也变得温柔起来，开始提醒我们注意饮食，调整生物钟，保持充足睡眠等。考前3天，学校放假，让我们回家吃好喝好，放松心情。考前1天，看考场，当时坐我前面的一个同学找到我，叫我给他抄，并威胁我如果不给他抄，则影响我考试，碰到这样一个人渣，对我的心情还是有一定影响的，我也没敢把这件事告诉我妈。当天晚上英语老师找我谈话，宽慰我，跟我说考试的时候不要遮住试卷，他能不能抄到是他的事了，况且他最多只能抄到选择填空题，主观题还得靠真才实学。高考那两天，全校其他年级放假，为的是给所有考生创造一个安静舒适的校园环境，这一点给学校点赞。高考第一天和第二天上午还算顺利，正常发挥，第二天午休没有睡着，下午考英语的时候，听力几乎没有进入状态，哈欠连连，英语是我考得最差的一科了，当然英语本来就不是我的强项。&lt;/p>
&lt;p>考完之后，回到家中，妈妈给我和哥哥洗了两个甜瓜吃，寓意我们苦尽甘来:-) 高考完的那个暑假，妈妈说让我们好好在家待着休养生息，所以基本过着猪一样的生活。6月底高考成绩出来了，六百多吧，和估分差不多，纠结的是填志愿。当时也不像现在，互联网这么发达，基本上是通过《全国普通高等学校报考指南》了解每个学校，对提前批的情况也不甚了解。后来根据往年分数线以及自己的兴趣，报了武大、吉大、川大这几个学校，很幸运，录取了第一志愿第一专业WHUCS。说实话，在填志愿之前，我只知道清华北大这两个学校，对武大这所“全国最美丽的大学”一无所知，我不是狂妄自大，而是孤陋寡闻。&lt;/p>
&lt;p>高中真的很累、很辛苦，要想坚持下去，一定要找一个可靠的精神支柱，不论是做什么事都是这样，一直以来，支持我勇往直前的都是我的亲人和我想要改变命运的决心！要说高中3年的收获，那就是它磨砺了我的意志，增强了我忍受孤独的能力，当然高中并不是我最孤独的时候，至少有我前面提到的小集体归属感；高中三年，也认识了很要好的同学兼老乡WQ、WS和MZ。&lt;/p>
&lt;p>对了，高中那会每个人都会有一个座右铭，我也不例外，很大众化，汪国真的“既然选择了远方，便只顾风雨兼程”。惊闻汪老师于2015年4月26日逝世，令人嘘唏。&lt;/p>
&lt;p>参考：&lt;/p>
&lt;p>[1]. &lt;a href="http://tupian.baike.com/84178/2.html">永不过时的高考记忆&lt;/a>&lt;/p></description></item><item><title>家乡遭受几十年一遇的洪灾</title><link>https://bitjoy.net/posts/2015-05-21-severe-flooding-hits-my-hometown/</link><pubDate>Thu, 21 May 2015 20:32:19 +0800</pubDate><guid>https://bitjoy.net/posts/2015-05-21-severe-flooding-hits-my-hometown/</guid><description>&lt;p>5月19号的中午吃完饭后随手刷了一下朋友圈，发现MS表哥分享了一个&lt;a href="http://mp.weixin.qq.com/s?__biz=MzAwNDUzMjg5OA==&amp;amp;mid=208222165&amp;amp;idx=1&amp;amp;sn=8586e0f5d9830cfb4cf51921bfe1269a&amp;amp;scene=2&amp;amp;from=timeline&amp;amp;isappinstalled=0#rd">链接&lt;/a>，说家乡发生了十多年未遇洪灾。仔细看了一下，发现这次洪灾真的很严重，然后就给妈妈打了个电话，妈妈说从昨天下午开始下大雨，到晚上下暴雨，我家后院有一座小山，也出现了滑坡；附近的一座桥也淹了，家门口的一片农田（不是我家的）也全被淹了。妈妈说她也是头一回看到这么大而持久的暴雨。&lt;/p>
&lt;p>后来看QQ空间，全是关于家乡灾情的状态，很多新闻媒体也报道了。县的下游地区受灾比较严重，隔壁有个叫横江的村镇，这个镇因为坐落在横江水旁边，受灾最严重，听说整个镇断水断电，都快成孤岛了。我有一个亲戚在横江，他们家一层楼就被淹了，很多人家养的鸡鸭鱼猪等都被冲走了，几乎所有的农田被淹，损失真的很严重。&lt;/p>
&lt;p>&lt;img alt="横江镇政府被淹情形[1]" loading="lazy" src="https://bitjoy.net/posts/2015-05-21-severe-flooding-hits-my-hometown/flood201505-1.jpg">
横江镇政府被淹情形[1]&lt;/p>
&lt;p>&lt;img alt="这是通往横江镇的公路，下坡后就是横江镇，可以看到整个村镇已经被淹[2]" loading="lazy" src="https://bitjoy.net/posts/2015-05-21-severe-flooding-hits-my-hometown/flood201505-2.jpg">
这是通往横江镇的公路，下坡后就是横江镇，可以看到整个村镇已经被淹[2]&lt;/p>
&lt;p>&lt;img alt="停在路边的小汽车都被冲走了！[2]" loading="lazy" src="https://bitjoy.net/posts/2015-05-21-severe-flooding-hits-my-hometown/flood201505-3.jpg">
停在路边的小汽车都被冲走了！[2]&lt;/p>
&lt;p>&lt;img alt="整个村子一片汪洋[2]" loading="lazy" src="https://bitjoy.net/posts/2015-05-21-severe-flooding-hits-my-hometown/flood201505-4.jpg">
整个村子一片汪洋[2]&lt;/p>
&lt;p>&lt;img alt="特写[2]" loading="lazy" src="https://bitjoy.net/posts/2015-05-21-severe-flooding-hits-my-hometown/flood201505-5.jpg">
特写[2]&lt;/p>
&lt;p>&lt;img alt="估计我家后山的滑坡比这严重很多[1]" loading="lazy" src="https://bitjoy.net/posts/2015-05-21-severe-flooding-hits-my-hometown/flood201505-6.jpg">
估计我家后山的滑坡比这严重很多[1]&lt;/p>
&lt;p>我记得我小的时候（大概五六岁？），可能是1998年吧，也发生过一次特大洪水，当时也是晚上，爷爷把我和哥哥叫起来，爬到后山上去避难了。当时虽然洪水也大，但也就一晚上，第二天很快就退了，不像这次持续的特大暴雨。&lt;/p>
&lt;p>晚些时候我又打电话给妈妈，听妈妈说因为怕山体滑坡，已经去半山腰上的烤烟房里过夜了。那个烤烟房是我还没出生（或者我很小）的时候盖的土坯房，专门用来烤烟的。 后来一家人常年在外，也没怎么管它，很多瓦都碎了。前几年我爸回家补漏，现在是不会漏水了。但是毕竟年代久远了，而且是土坯房，又下那么大雨，还是不放心。想到妈妈一个人在家，晚上要住在一个没有水没有电的土坯房里，心里真不是滋味。以后有钱了应该把烤烟房改建成砖房，万一出现什么灾害，也有个落脚的地方。&lt;/p>
&lt;p>刚看了下天气预报，现在还在下雨，周六周一还有雷阵雨，希望不要再滑坡了。&lt;/p>
&lt;p>&lt;img alt="石城5月份历史天气，从15号开始连下了6天的大雨，以18,19,20为最[4]" loading="lazy" src="https://bitjoy.net/posts/2015-05-21-severe-flooding-hits-my-hometown/shicheng_history_weather.png">
石城5月份历史天气，从15号开始连下了6天的大雨，以18,19,20为最[4]&lt;/p>
&lt;p>在这次灾害中，也要感谢当今发达的互联网，让全国各地的人实时了解家乡的情况，很多救援队也纷纷赶赴家乡展开救援。希望洪水快快退去，还家乡人民安宁的生活。&lt;/p>
&lt;p>此次特大灾害直播：&lt;a href="http://mp.weixin.qq.com/s?__biz=MjM5MzEzODgyMA==&amp;amp;mid=206189476&amp;amp;idx=1&amp;amp;sn=3e2cc4f01b7ff42f933420063f4e5a74#rd">http://mp.weixin.qq.com/s?__biz=MjM5MzEzODgyMA==&amp;amp;mid=206189476&amp;amp;idx=1&amp;amp;sn=3e2cc4f01b7ff42f933420063f4e5a74#rd&lt;/a>&lt;/p>
&lt;p>520，对石城说“我爱你”：&lt;a href="http://v.qq.com/boke/page/z/0/2/z0154dvf6d2.html">http://v.qq.com/boke/page/z/0/2/z0154dvf6d2.html&lt;/a>&lt;/p>
&lt;p>灾后场景：&lt;a href="http://mp.weixin.qq.com/s?__biz=MzAwNDUzMjg5OA==&amp;amp;mid=208435542&amp;amp;idx=1&amp;amp;sn=346f8e17448bf3a976c55591636b6e02&amp;amp;scene=1&amp;amp;from=singlemessage&amp;amp;isappinstalled=0#rd">http://mp.weixin.qq.com/s?__biz=MzAwNDUzMjg5OA==&amp;amp;mid=208435542&amp;amp;idx=1&amp;amp;sn=346f8e17448bf3a976c55591636b6e02&amp;amp;scene=1&amp;amp;from=singlemessage&amp;amp;isappinstalled=0#rd&lt;/a>&lt;/p>
&lt;p>2015年5月21号石城新闻：&lt;a href="http://v.qq.com/boke/page/w/0/4/w015442vnu4.html">http://v.qq.com/boke/page/w/0/4/w015442vnu4.html&lt;/a>&lt;/p>
&lt;p>相关媒体报道：&lt;/p>
&lt;p>&lt;a href="http://pic.people.com.cn/n/2015/0520/c1016-27031534.html?k=1">http://pic.people.com.cn/n/2015/0520/c1016-27031534.html?k=1&lt;/a>&lt;/p>
&lt;p>&lt;a href="http://news.163.com/15/0521/05/AQ48GSQU00014Q4P.html">http://news.163.com/15/0521/05/AQ48GSQU00014Q4P.html&lt;/a>&lt;/p>
&lt;p>&lt;a href="http://news.163.com/15/0521/06/AQ4CVUIF00014AEE.html">http://news.163.com/15/0521/06/AQ4CVUIF00014AEE.html&lt;/a>&lt;/p>
&lt;p>参考：&lt;/p>
&lt;p>[1]. 赣江源头微信公众号文章&lt;/p>
&lt;p>[2]. 石城热线微信公众号文章&lt;/p>
&lt;p>[3]. 百度搜索“石城天气”，数据来源中国天气网&lt;/p>
&lt;p>[4]. 数据来源：&lt;a href="http://15tianqi.cn/shicheng5yuetianqi/">http://15tianqi.cn/shicheng5yuetianqi/&lt;/a>&lt;/p></description></item><item><title>“刺猬困境”与人际交往距离</title><link>https://bitjoy.net/posts/2015-05-02-hedgehogs-dilemma-and-interpersonal-distance/</link><pubDate>Sat, 02 May 2015 09:44:52 +0800</pubDate><guid>https://bitjoy.net/posts/2015-05-02-hedgehogs-dilemma-and-interpersonal-distance/</guid><description>&lt;p>其实在中学的时候就遇到过“刺猬困境”，只是那时候不知道这个名词，直接促使我了解这方面内容的原因是和XN的一次不愉快的交谈。&lt;/p>
&lt;p>那是半个多月以前的事情了，有一天中午我们一起吃完午饭回来，我们身后突然有一辆小车要启动，XN就一个劲叫我靠边走；但与此同时我边上迎面来了一辆电动车，于是我就和XN说我这边也有车，但是XN回我一句“叫你靠边走你不靠边走”，当时我听了很不舒服，还是告诉她我这边也有车，但是语气稍微重了点，我以为XN会到此为止，没想到她还是回了一句“你那么大声干嘛呀”……因为那天心情就不太好，还被她这么一说，那天我就没理她了。&lt;/p>
&lt;p>本来以为以XN的性格，这件事会很快过去，没想到XN一气之下不和我一起上下班了，也不和我一起打球吃饭了。我觉得事情还是有那么一点“严重”，这都还没正式入组，就和最熟悉的组员闹僵了，以后三年该怎么办呀，所以第二天晚上我还是诚恳的向XN道歉了。&lt;/p>
&lt;p>后来回想起这件事，确实是我的错。XN当时叫我靠边是一番好意，她多说了几句，我一个男生反倒先急了，确实是气度不够大。&lt;/p>
&lt;p>不过说实话，XN也并不是完全没有错，在我和她解释之后，她还不依不饶，似乎有点过了。她之前也跟我讲过自己有一次在班群里开某同学的玩笑，导致那个同学和她理论的事情。&lt;/p>
&lt;p>写到这的时候又让我想起了有一次教XN打乒乓球的时候，在公共场合批评她打球不够认真，哪里哪里打得不好之类的，当时我自己觉得不以为然，教她打球，当然要指出她的不对之处，但是后来XN告诉我她很生气，哪有我这样一直批评学生的老师啊。我仔细一想，对呀，我为什么在公共场合批评她打得不好呢，我是她的教练吗，我和她熟到能无话不谈吗，好像没有，所以这就是我没有把握好人际交往的一个度。我和XN只是同届同门师弟妹，我们之间应该保持一定的距离，距离太近了，说话做事无所顾忌，迟早会对某一方产生伤害，导致矛盾。&lt;/p>
&lt;p>&lt;img alt="hedgehogs dilemma" loading="lazy" src="https://bitjoy.net/posts/2015-05-02-hedgehogs-dilemma-and-interpersonal-distance/hedgehogs-dilemma.jpg">&lt;/p>
&lt;p>刺猬困境是由叔本华提出的一个概念。寒冷的冬天，刺猬本想拥作一团、互相取暖，但一靠近便被彼此刺伤了；想分开避免扎伤，又觉得寒冷而想再彼此靠近。几个反复后，刺猬发现它们最好保持一点距离。&lt;/p>
&lt;p>与人交往也是这样，当两个人关系逐渐亲密起来，成为所谓的好基友、好闺蜜的时候，往往容易忽视对方的感受，说出一些伤人的话。&lt;/p>
&lt;p>高中的时候因为成绩还可以，班上的LL同学经常向我请教问题，我每次都会很耐心很热情的帮他解答，慢慢的关系比较好，他就经常和我一起上下课，课间跑到我的位置上和我聊天开玩笑，甚至左拍一下我右捏一下我。这让我感到很不舒服，感觉个人空间被入侵，后来我就慢慢的和他疏远，保持一个合理的距离。&lt;/p>
&lt;p>我和XN的那次不愉快交谈，就相当于刺猬间相互取暖导致彼此受伤的过程，不过我相信，经过几个回合，我们能慢慢的找到合适的距离，既相互取暖，又不至于受伤。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://i0.wp.com/upload.wikimedia.org/wikipedia/commons/thumb/3/35/Personal_Space.svg/640px-Personal_Space.svg.png">
Edward T. Hall’s personal reaction bubbles, showing radius in feet and meters[1]&lt;/p>
&lt;p>上图是爱德华·霍尔提出的人际交往的四个距离，从内到外依次是亲密距离-&amp;gt;个人距离-&amp;gt;社交距离-&amp;gt;公共距离。和朋友同事之间的距离应该保持在个人距离0.45米。&lt;/p>
&lt;p>P.S.希望能够找到那个和我共享亲密距离的人-:)&lt;/p>
&lt;p>参考：&lt;/p>
&lt;p>[1]. 维基百科“Personal space”条目：&lt;a href="http://en.wikipedia.org/wiki/Personal_space">http://en.wikipedia.org/wiki/Personal_space&lt;/a>&lt;/p></description></item><item><title>我学繁体字</title><link>https://bitjoy.net/posts/2015-04-21-learning-traditional-chinese-characters/</link><pubDate>Tue, 21 Apr 2015 09:30:34 +0800</pubDate><guid>https://bitjoy.net/posts/2015-04-21-learning-traditional-chinese-characters/</guid><description>&lt;p>其实很早之前就打算学习繁体字了，但是直接驱动我开始行动的还是上周末的一件事。&lt;/p>
&lt;p>上周末组内一起去天津蓟县盘山景区游玩，进入景区看到的第一个“景点”就是下面的乾隆御笔&lt;/p>
&lt;p>&lt;img alt="乾隆《游盘山记》" loading="lazy" src="https://bitjoy.net/posts/2015-04-21-learning-traditional-chinese-characters/panshan.jpg">
乾隆《游盘山记》&lt;/p>
&lt;p>当时JL师姐念了一遍，有几个繁体字不认识，在场的其他人也都模棱两可。我当时就后悔为什么不早点把繁体字学了呢。所以回所之后马上买了下面的这本《繁简字对照字典》，决定每天看一两页。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://img3.doubanio.com/lpic/s2988301.jpg">
《繁简字对照字典》[1]&lt;/p>
&lt;p>网上也有《游盘山记》的简体版，如下&lt;/p>
&lt;p>连太行，拱神京，放碣石，距沧溟，走蓟野，枕长城，盖蓟州之天作，俯临重壑，如众星拱北而莫敢与争者也。—-乾隆御笔&lt;/p>
&lt;p>对照图片中的繁体字，学习一下。&lt;/p>
&lt;p>有些繁体字和简体字不是一一对应的，比如同样是“汇”字，“汇聚”对应的繁体字为“匯聚”，而“词汇”对应的繁体字为“詞彙”，这一点需要注意，网上有开源的繁简字转换工具，可以看&lt;a href="http://opencc.byvoid.com/">这里&lt;/a>。&lt;/p>
&lt;p>&lt;a href="http://www.zhihu.com/question/25389359">关于繁简字的争论，网上已经很多了&lt;/a>，我也不想评论，我只想说，学习繁体字完全是个人兴趣，我觉得繁体字很美，很有意思，一个字可以研究半天，外出游玩的时候也能顺带“和古人交流交流”，所以就学了-:)&lt;/p>
&lt;p>参考：&lt;/p>
&lt;p>[1]. 豆瓣读书《繁简字对照字典》：&lt;a href="http://book.douban.com/subject/2234412/">http://book.douban.com/subject/2234412/&lt;/a>&lt;/p></description></item><item><title>不求助的人</title><link>https://bitjoy.net/posts/2015-04-18-someone-who-doesnt-ask-for-help/</link><pubDate>Sat, 18 Apr 2015 22:58:38 +0800</pubDate><guid>https://bitjoy.net/posts/2015-04-18-someone-who-doesnt-ask-for-help/</guid><description>&lt;p>师姐推荐了最新一期《人物周刊》上的一篇文章–&lt;a href="https://mp.weixin.qq.com/s?__biz=MTY0MzI5NDcwMQ==&amp;amp;mid=206411797&amp;amp;idx=1&amp;amp;sn=c0da9db2ed5a6a5bdedbfb871e45c8e6#rd">“不求助的人”&lt;/a>。这篇文章讲到，在上月末德国空难事件中，副驾驶卢比茨选择了坠毁飞机。调查人员随后了解到，他可能患有抑郁症，同时，他的弱视或许在持续恶化。视力和心理问题给卢比茨的职业前景笼上阴影，&lt;strong>而他没有选择求助&lt;/strong>。&lt;/p>
&lt;p>“求助”似乎从来不是首选项。遇到难题自己解决，不行就上网搜搜方法，还不行，则“咬紧牙关熬过去”。不求助的表面理由是不麻烦别人，但更真实的担忧大概是“如果开口求助，别人会认为我能力低下，我会因此丧失各种机会”。本质原因在于，给自己定的目标是“在他人面前表现出众”。&lt;/p>
&lt;p>目标可细分为两种，“精熟型目标（mastery goal）”和“绩效型目标（performancegoal）”。精熟型目标更重视过程而非结果，认为目的是自我提升，不是获得肯定。哪怕现在还“做不到”，但通过不断努力也能有所进步。既然目标是“成为更好的自己”，那么遇到困难时，自然会寻求帮助。而绩效型目标只看最终结果，你要么“能做到”，要么“不能做到”，要么力压众人表现出色，要么挑战失败沦为笑柄。既然目的是“从他人那里获得肯定”，感觉上像是“示弱”的求助就不会被列入选项。&lt;/p>
&lt;p>“不求助之人”并不少见。不过，“绩效型目标”者不知道的是，求助他人时，其实会提升此人对你的评价。每个人都觉得自己智慧过人，可以为别人授业解惑，而“懂得向聪明的我询问智慧建言的人，一定也是聪明人”。沃顿商学院的研究者发现，脑力竞赛中接到“搭档”求助的人，赛后给搭档打了更高的能力分。2010年，美国西北大学研究发现，老板其实更喜欢那些遇到困境会主动求援的下属，某种意义上，“求指点迷津”可能是对老板最好的恭维。&lt;/p>
&lt;p>说到底，不管目标是获得成长还是赞赏，求助都是帮助达成目标的大道。越早寻求帮助，越有机会让自己成长，也越有可能掌握技能、成功解决问题，周遭人对你的评价也会因此上升。反倒是不求助的人，万一拖到事情无法收拾，自己的自信和风评都会落到极低。&lt;/p>
&lt;p>这篇文章讲得很有道理，我发现身边就有很多不求助的人。他们从小到大很少向别人求助，自己能做的事尽量自己做，遇到难题也尽量自己扛，给人一种能力很强、很自信的印象；同时他们也很鄙视那些经常向别人求助的人，认为这些人“就知道问别人，这么简单的都不会”。你可以称赞“不求助的人”独立自主、坚韧刻苦，但是从某种程度上这恰恰反映了他们内心的不自信，他们害怕自己的求助暴露了自己的智商，显得自己水平不够。他们属于绩效型人群，只看重结果，不看重过程，如果当上领导，下属的压力肯定不小，久而久之，就会产生类似上面的案例，宁愿选择坠毁，也不愿向他人求助。&lt;/p>
&lt;p>不求助的人因为很少向他人求助，他们的交际圈也很窄，他们经常把自己封锁起来，甚至把主动伸出援手的人拒之门外。长期的封闭往往导致一些心理和精神疾病，以至于做出一些病态的选择。&lt;/p>
&lt;p>其实，和”不求助的人“的想法相反，文中沃顿商学院的研究结果很有道理，遇到问题喜欢求助的人，反而会受到别人较高的评价，因为被求助者会潜意识的认为“懂得向聪明的我询问智慧建言的人，一定也是聪明人”，说不定双方还能由此发展出一段不错的关系；而且越早求助，就能越早解决问题。这一箭多雕的事情，恐怕是不求助的人没有想到的吧。&lt;/p>
&lt;p>从某种程度上来说，我自己也是一个“不求助的人”，H老师估计早就猜透了，我希望能够在读研期间&lt;a href="http://pfind.net/people/hesimin/Chinese/Favorite%20Books.htm">“收获一点成就感、一点自信心”&lt;/a>，从绩效型人群转移到精熟型人群。&lt;/p></description></item><item><title>人生，平凡之路</title><link>https://bitjoy.net/posts/2015-04-12-my-ordinary-life/</link><pubDate>Sun, 12 Apr 2015 22:31:02 +0800</pubDate><guid>https://bitjoy.net/posts/2015-04-12-my-ordinary-life/</guid><description>&lt;p>趁着周末，看了韩寒导演的处女作《后会无期》，说来奇怪，看的过程中没有丝毫感觉，情节松散，直到听到了片尾曲”平凡之路“，内心为之一颤，想来应该写点什么纪念一下。&lt;/p>
&lt;p>&lt;img alt="The-Continent1.jpg" loading="lazy" src="https://bitjoy.net/posts/2015-04-12-my-ordinary-life/The-Continent1.jpg">&lt;/p>
&lt;p>影片中三个年轻人离开家乡小岛，一路向西，横穿中国大陆，路上落下了胡生，错过了假装”小姐“的”骗子“，告别了一直”恋爱“着的笔友，遇到了善恶莫测的奇怪旅人，送走了最好的朋友，只有流浪的小狗留在了身边。几番告白，几番告别，勾勒出几段截然不同的平凡人生之路。&lt;/p>
&lt;p>突然间，我从影片中看到了萧瑟冷漠的世界，看到了饱经沧桑的老人挣扎着，反抗着，但最终离开了。&lt;/p>
&lt;p>影片给我影响最深的两句话是：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>你连世界都没观过，你哪来的世界观。&lt;/strong>&lt;/li>
&lt;li>&lt;strong>如果要告别，一定要用力一点,因为任何多看一眼,都有可能成为最后一眼,多说一句,都可能是最后一句。&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>经常有人惊讶于我小小年纪就表现得如此成熟，不知道是不是因为从小跟着父母外出闯荡，经历得多了，世界观不一样了。&lt;/p>
&lt;p>很小的时候就跟着父母去了广东JY，当时父亲帮别人挖煤，后来当过老师，开过早餐店，开过出租车。挖煤的时候每天都要在臭气熏天的河里挖半天煤，然后用船运回去，又要顶着炎炎夏日做半天的煤。每天完事之后脚乌漆墨黑，老茧长得跟树皮一样。开早餐店的时候，每天凌晨三四点就要起床开始和面，做包子，熬豆浆，炸油条。忙完了早餐还要去学校上班。&lt;/p>
&lt;p>那一年有天晚上，爸爸妈妈正在收拾店铺，准备第二天早晨的面料，YT睡到半夜突然KTBM，脚一直在发抖，我被吵醒之后马上告诉了爸爸妈妈。当时都已经很晚了，地段也比较偏僻，路上少有行人，幸好隔壁开茶叶店的老板还没有走，他用摩托车把YT送到了医院。那一天晚上格外的冷，我只记得妈妈站在医院门口不停的祈祷着什么。后来几经折腾，转院到汕头的大医院，病床好像在走廊里，医院的快餐比家里的还好吃。&lt;/p>
&lt;p>一家人出门在外，父母的工资很低，在外读书，一学期的学费要几百块钱，再加上YT的那场大病，家里的经济压力着实不小。父母经常为一些事大吵大闹，有几次还大打出手，作为小孩子的我只能哭着求着他们不要再吵不要再打了，过往的事件历历在目，那都是血和泪的记忆。&lt;/p>
&lt;p>在外漂泊的日子过得很辛苦，对于大人如此，对于我们这些青少年也一样。走在路上经常会被一些本地的小混混打，晚自习回家一定要结伴而行，不要走人少的路。我记得有一次我和表哥一起走在街上，一个骑自行车的小混混从我们背后踢了我们两脚，当时表哥正要反击，我把他拉住了，因为我知道，外地人在这势单力薄，根本不是这些人的对手，自己吃点亏，能不惹麻烦尽量不惹麻烦。但是这个小事给我的印象很深。&lt;/p>
&lt;p>也许是在外打拼的日子太苦，初一下学期，我、YT和妈妈回老家了，爸爸继续在JY打拼着。回到家之后，去了一个稍微好一点的初中，妈妈在我们身边陪读。&lt;/p>
&lt;p>因为在JY的时候，家里很穷，但是过年的时候，父母总还是会给我们买新衣服，所以每年就给我们买便宜又得体的西装。回到老家之后，城里的同学看我们经常穿西装，索性给我们取了一个外号”西装“，这导致我后来对西装厌恶至极。&lt;/p>
&lt;p>有一天晚上，晚自习回家，我和YT刚走出校门的时候，有一群小混混和我们逆行，他们跑的时候不小心把水溅到我们身上了，我想想也就算了，但是YT不服，故意把水溅到他们身上。我当时暗想坏了，他们会不会回来找我们算账啊，果不其然，没过多久，一帮人就追着我们打，幸好当时有一个老师路过，我向她”求救“才得以脱险回家。&lt;/p>
&lt;p>这些小事反应到我的性格上来就是忍气吞声，处世中庸，”吃亏是福“。这种性格在某种程度上也是一件好事，高中三年给我省了很多麻烦，也能让我沉下心来，埋头苦干，高考的时候考了全县第十一名，考取WHU也算是对我那几年的一个回报。其实农村孩子这种”两耳不闻窗外事，一心只读圣贤书“的单一发展，也给我视野狭窄、其他技能缺失埋下了伏笔，这里暂时按下不表。&lt;/p>
&lt;p>我还在JY的时候，有一天妈妈说我们要马上回家看外公，外公病了。回到老家之后，我和YT在院子里玩耍，后来妈妈拉我去见了外公最后一面。那大概是我记忆中第一次亲人离别。后来妈妈告诉我，外公当时还怪我到家之后为什么没有马上去看他呢，对呀，我当时为什么没有马上去看外公而是在院子里玩耍呢，也许那时候还不知道什么是离别吧。&lt;/p>
&lt;p>后来又经历了曾祖母的离别。记忆最深的是爷爷的离别。那大概是一年前吧，我当时正在图书馆准备保研的事情，突然爷爷给我打了一个电话，爷爷很少给我打电话的，而且那时候我们好像还不知道对方的手机号码，爷爷说是从YT那知道我的号码的。他问了我一些近况，叫我要好好照顾自己的身体，不要担心他；他还说他给YT也打了电话，给CY打电话但是没有打通；末了，他说这个电话没别的意思，就这样吧，挂了。我可以明显的感受到电话那头爷爷凄凉孤独的心，这通电话听起来很怪，我马上给爸爸打了个电话，告诉了他情况，爸爸说爷爷一个人在家，也许是太孤单了，或者是犯了老年痴呆症，爸爸还说爷爷也经常打类似的电话给他。是啊，奶奶在我很小的时候就去世了，爷爷一个人孤苦伶仃生活了将近二十年，纵然有三个儿子一个女儿，但是几个儿子儿媳之间为了老人的赡养问题竟成陌路，小儿子老大不小了也还没有成家，是孤独的在这个世界苟活着，给儿子儿媳带来更多的麻烦还是默默的离开，给年轻人省去一个包袱，爷爷心里恐怕早已有了答案。&lt;/p>
&lt;p>&lt;img alt="The-Continent2.jpg" loading="lazy" src="https://bitjoy.net/posts/2015-04-12-my-ordinary-life/The-Continent2.jpg">&lt;/p>
&lt;p>几天之后，噩耗传来，没想到那竟成了我和爷爷最后的通话。&lt;/p>
&lt;p>给爷爷办后事的时候，几个叔叔姑姑都回来了，这竟是我记忆中唯一一次看到大家坐在一张桌子上吃饭。&lt;/p>
&lt;p>很多人都说我冷漠、沉默寡言，其实我小时候不是这样的，可能是这些年经历的事太多了，我对很多事情漠不关心，很多不必要的、无意义的话也不讲了，很多点头之交的朋友也不联系了，也变得不喜欢和人争辩了；我开始喜欢独处，喜欢一个人走在路上，看过往匆匆行人，喜欢看《文化苦旅》、《一九八四》、《百年孤独》、《活着》……&lt;/p>
&lt;p>身边很多同学喜欢三五成群出门，经常见他们和各种各样的朋友打招呼。有一次我和一个夏令营认识的同学打招呼，XN居然诧异的告诉我这是她第一次在ICT发现我也有认识的人。为什么要有那么多朋友呢，可能你会告诉我你的QQ好友都上千了，但是真正在你社交圈里的朋友，能够和你交心的朋友，超过10个吗？逢年过节，为了维系那990个你都不记得他/她的模样的朋友，群发着各种短信，朋友圈、QQ空间、微博里不停的给别人点赞，有必要吗？我已经厌倦了这些虚情假意。&lt;/p>
&lt;p>令我很感动的是，前几天，我正在实验室敲代码的时候，接到了WQ的一个电话，我跟他抱怨了一下在北京的各种不顺，他跟我说一个人出门在外，要好好照顾自己；同时另一个好友WS也经常跟我说想和我聊聊。这让我感到非常温暖，虽然我现在一无所有，但是有一两个至交，足以。&lt;/p>
&lt;p>我已经走过了二十多年，是时候走出去看看世界，说不准世界观就形成了呢。&lt;/p></description></item><item><title>One month in Beijing</title><link>https://bitjoy.net/posts/2015-03-30-one-month-in-beijing/</link><pubDate>Mon, 30 Mar 2015 19:23:42 +0800</pubDate><guid>https://bitjoy.net/posts/2015-03-30-one-month-in-beijing/</guid><description>&lt;p>2015年2月28日到达北京，到现在一月有余，是时候月度总结了。&lt;/p>
&lt;h1 id="这是我第三次来北京">这是我第三次来北京&lt;/h1>
&lt;p>2014年的6月份第一次来北京，去北大计算所实习十天。感受了一下北大计算所p老师及其博士生z的冷漠。我还记得刚到的那天晚上一个人孤零零的走在北京的大街上寻找北大方正员工宿舍的场景。每天重复着朝九晚十的固定模式，每个人都像机器一样的工作，实验室只有敲击键盘的声音，偶尔看到p的时候他也是阴沉着脸，临走的时候还被z批评了。虽然p承诺说只要我通过夏令营面试就会要我，但是这明显就是废话呀；而且这段不算愉快的实习经历让我对p的实验室产生了厌恶。&lt;/p>
&lt;p>回学校之后开始准备期末考试和7月份的夏令营，并于2014年7月9日到达北京，这次的行程包括北大计算所和中科院计算所的夏令营。北大计算所因为机试和综合排名不占优势，败下阵来，不过现在想想当初幸好没有进p的实验室，要不然天天对着p的臭脸，估计要疯。&lt;/p>
&lt;p>中科院计算所的风格完全不像北大，中科院真正做到了海纳百川，一视同仁，不像北大看不起校外学生。来北京之前和ict的h老师沟通过很多次，发现我和h老师的性格很相似，我非常崇拜h老师严谨的处事风格，经过认真的准备，我也顺利通过了ict的夏令营，然后跑去深圳siat玩耍了。&lt;/p>
&lt;h1 id="北京的物价真是贵">北京的物价真是贵&lt;/h1>
&lt;p>这次是来ict完成我的本科毕业设计的，大概要待到5月初。一来从家里不方便带太多东西，二来想想要在北京待三年，所以准备在北京重新置办生活用品。&lt;/p>
&lt;p>2月28刚到北京，去了师兄推荐的家乐福大超市。超市很大，有两层，不过里面的东西真是贵。也就买了被子、三件套、桶、盆，花了四五百，很可恨的是，很薄的春秋单人被子，居然要169，这还是最便宜的，床垫比被子还贵，要199。&lt;/p>
&lt;p>没必要的用品可以不买，但是饭不能不吃。以前在学校一天也就十几块，到北京后发现，一顿饭就十多块了。早餐一个鸡蛋要2块，一个小包子要1.5块，哎，想想以前自己家做早餐的时候1块钱4个大肉包子啊，当初怎么不多吃几个呢。&lt;/p>
&lt;p>餐厅里的饭菜确实贵，不过国科大食堂的饭菜既便宜又好吃，赞一个。&lt;/p>
&lt;h1 id="北京的雾霾">北京的雾霾&lt;/h1>
&lt;p>来北京之前对北京的雾霾也略有耳闻，觉得那是北京人的小题大做，武汉也是扬尘满天飞，我也活得好好的啊；不过真来北京之后，确实受不了，放眼望去，街道上灰蒙蒙的一片，路上的特大广告牌都看不清。也许是雾霾加干燥的原因，刚来北京那一个星期，嗓子特别不舒服，每天早晨刷牙的时候都恶心干呕，不过后来慢慢好了。&lt;/p>
&lt;p>3月28那天，北京还遭遇了沙尘暴，那场景真像外星人袭击地球。所以来北京，口罩是必需品。&lt;/p>
&lt;p>&lt;img alt="3月28日北京的沙尘暴" loading="lazy" src="https://i0.wp.com/y0.ifengimg.com/cmpp/2015/03/28/14/1120a1ac-4317-49ca-8114-5962823270e1_size48_w600_h400.jpg">
3月28日北京的沙尘暴[1]&lt;/p>
&lt;h1 id="北京的风景">北京的风景&lt;/h1>
&lt;p>来北京的前3个星期，忙于组内布置的任务，也是过着朝九晚十的生活，后来想想也不能天天这样，我应该出去透透气，于是选择了颐和园，3月份属于颐和园景区的淡季，加上学生证，门票不贵。颐和园不愧是皇家园林，里面的景色确实很漂亮，有万寿山、昆明湖，湖水还算干净。&lt;/p>
&lt;p>我去的那天天气很好，空气质量也不错；人虽多，但不算拥挤，部分原因是颐和园真是太大了。我在游玩的过程中看到了很多外国旅游团，听到过英语、法语、日语、韩语、粤语、中文，让我默默联想起了八国联军侵略中国的场景。&lt;/p>
&lt;p>在游玩的过程中遇到了一个很温馨的“外国小家庭”，妈妈是外国人，爸爸应该也是外国人，爸爸和妈妈聊天的时候是用英语，但是爸爸和儿子却是用中文，他们还有一个坐在婴儿车里的小女儿，儿子坐在湖边玩ipad，小女儿在车里哭个不停…&lt;/p>
&lt;p>虽然颐和园的风景很好，但是里面居然有很多蚊子（小虫子）！期间有一个虫子撞到我的嘴巴，还有一个钻进了我的耳朵。可能是春天到了，万物复苏，加上颐和园内潮湿，植物丰富，给虫子提供了很好的环境。&lt;/p>
&lt;p>下面放几张游玩时拍的照片。&lt;/p>
&lt;p>&lt;img alt="谁能告诉我这是什么乐器" loading="lazy" src="https://bitjoy.net/posts/2015-03-30-one-month-in-beijing/Summer-Palace1.jpg">
谁能告诉我这是什么乐器&lt;/p>
&lt;p>&lt;img alt="学太极的外国人" loading="lazy" src="https://bitjoy.net/posts/2015-03-30-one-month-in-beijing/Summer-Palace2.jpg">
学太极的外国人&lt;/p>
&lt;p>&lt;img alt="这也是迎春花？" loading="lazy" src="https://bitjoy.net/posts/2015-03-30-one-month-in-beijing/Summer-Palace3.jpg">
这也是迎春花？&lt;/p>
&lt;p>&lt;img alt="这个角度的颐和园很美" loading="lazy" src="https://bitjoy.net/posts/2015-03-30-one-month-in-beijing/Summer-Palace4.jpg">
这个角度的颐和园很美&lt;/p>
&lt;p>&lt;img alt="长廊" loading="lazy" src="https://bitjoy.net/posts/2015-03-30-one-month-in-beijing/Summer-Palace5.jpg">
长廊&lt;/p>
&lt;p>&lt;img alt="准备回去的时候看到的，各种长枪大炮，据说是在抓拍一种珍稀鸟类。。。" loading="lazy" src="https://bitjoy.net/posts/2015-03-30-one-month-in-beijing/Summer-Palace6.jpg">
准备回去的时候看到的，各种长枪大炮，据说是在抓拍一种珍稀鸟类。。。&lt;/p>
&lt;p>颐和园的风景虽好，但就自然风光来说，比不上南方早已复苏的大自然。&lt;/p>
&lt;p>&lt;img alt="家乡的油菜花，比颐和园的迎春花漂亮多了-:)" loading="lazy" src="https://bitjoy.net/posts/2015-03-30-one-month-in-beijing/rape-flowers.jpg">
家乡的油菜花，比颐和园的迎春花漂亮多了-:)&lt;/p>
&lt;p>&lt;img alt="武大樱花已经争奇斗艳了，北方还是萧瑟一片" loading="lazy" src="https://i0.wp.com/i2.chinanews.com/simg/hd/2015/03/21/7e9527ca232f4253bfb86eef15cee517.jpg">
武大樱花已经争奇斗艳了，北方还是萧瑟一片[2]&lt;/p>
&lt;h1 id="后记">后记&lt;/h1>
&lt;p>没来北京之前，我想着以后一定要在北京工作，还要争取在北京安家落户。虽然北京生活压力很大，但是机会和挑战也多，这不就是我要的生活吗；而且北京是全国的政治、经济和文化中心，可以很方便的去感受中国几千年的文化和历史。在北京生活一个月之后，我的想法变了，很重要的原因是北京的雾霾和气候。雾霾不是一个人一天两天能够解决的，你必须尽量减少不必要的出行，跑步健身什么的就更别提了；北京属于北温带季风气候，冬天很干燥，而且放眼望去看不到绿色，ict旁边种的树，光秃秃的只有树干。这种钢筋混凝土构筑起来的城市，我真的不喜欢。&lt;/p>
&lt;p>&lt;strong>和南方的一些城市相比，北京真的不适宜居住，尤其对于南方人！&lt;/strong>&lt;/p>
&lt;h1 id="参考">参考&lt;/h1>
&lt;p>[1]. 新京报网：&lt;a href="http://www.bjnews.com.cn/news/2015/03/28/358122.html">http://www.bjnews.com.cn/news/2015/03/28/358122.html&lt;/a>&lt;/p>
&lt;p>[2]. 中新网：&lt;a href="http://www.chinanews.com/tp/hd2011/2015/03-21/496005.shtml">http://www.chinanews.com/tp/hd2011/2015/03-21/496005.shtml&lt;/a>&lt;/p></description></item><item><title>从一个数字序列中取若干个数字使得和为某个数，问共有多少种取数方案？</title><link>https://bitjoy.net/posts/2014-11-15-subset-sum-problem/</link><pubDate>Sat, 15 Nov 2014 00:11:32 +0800</pubDate><guid>https://bitjoy.net/posts/2014-11-15-subset-sum-problem/</guid><description>&lt;p>在上一题POJ Problem 1837: Balance中，我们曾讲到，如果只有两个挂钩，问题会好办得多，就拿题目给的样例数据来说：&lt;/p>
&lt;pre tabindex="0">&lt;code>Sample Input
2 4
-2 3
3 4 5 8
Sample Output
2
&lt;/code>&lt;/pre>&lt;p>&lt;img alt="number-balance.png" loading="lazy" src="https://bitjoy.net/posts/2014-11-15-subset-sum-problem/number-balance.png">
如上图所示，给定重量为3,4,5,8的砝码，放在一个左右臂长分别为2和3的天平上，要使天平平衡，问有多少种方法。&lt;/p>
&lt;p>这个问题可以稍加转换，假设放在左边的重量为x，右边为y，则有如下方程组成立：&lt;/p>
$$
\begin{cases}
x+y=3+4+5+8=20\\
2x=3y
\end{cases}
$$&lt;p>马上解出x=12,y=8。这样就相当于把原问题转换为：&lt;strong>已知序列3,4,5,8，问从中取若干个数使和为12（或8）的方案数有多少个？&lt;/strong> 因为取出数字和为8，则剩余和为12，所以和为8和12的方案数是相等的。&lt;/p>
&lt;p>因为这里只有4个数字，一眼就能看出有(3,4,5)，(4,8)能使和为12，即只有两种方案。如果给的数字较多较大，该怎样写代码求出呢？可以使用动态规划求解。&lt;/p>
&lt;p>设dp[i][j]表示从前i个数中选若干个数使得和为j的方案数，则我们可以得到这样的状态转换方程：&lt;/p>
$$
\begin{cases}
dp[i][j]=1\qquad\qquad\qquad\qquad\qquad\qquad\qquad\qquad\text{if}i=0\&amp;\&amp;j=0\\
dp[i][j]=dp[i-1][j]\qquad\qquad\qquad\qquad\qquad\qquad\text{if}w[i]>j\\
dp[i][j]=dp[i-1][j]+dp[i-1][j-w[i]]\qquad\quad\text{if}w[i]&lt;=j
\end{cases}
$$&lt;ol>
&lt;li>当i=0&amp;amp;&amp;amp;j=0时，dp[i][j]=1表示从0个数中取若干个数使得和为0，当然只有1种方案，那就是什么都不取&lt;/li>
&lt;li>当w[i]&amp;gt;j时，第i个数用不上，因为你单个数字都超过j了，怎么使和为j呢，所以直接dp[i][j]=dp[i-1][j]&lt;/li>
&lt;li>当w[i]&amp;lt;=j时，第i个数可以用了，这个时候分两种情况，用或者不用第i个数，如果不用，则和w[i]&amp;gt;j时一样dp[i][j]=dp[i-1][j]，如果用的话，则要从前i-1个数中取若干个数使和为j-w[i]，也就是dp[i-1][j-w[i]]，这样总的方案数就是用和不用第i个数的方案数之和，即dp[i][j]=dp[i-1][j]+dp[i-1][j-w[i]]&lt;/li>
&lt;/ol>
&lt;p>下面是针对这个例子我手算的一个图：&lt;/p>
&lt;p>&lt;img alt="20141115_170507.jpg" loading="lazy" src="https://bitjoy.net/posts/2014-11-15-subset-sum-problem/20141115_170507.jpg">&lt;/p>
&lt;p>以上面的内容设计一个OJ题如下：&lt;/p>
&lt;pre tabindex="0">&lt;code>描述：
给定一个正整数数字序列，从中取出若干个数字，使得这些数字之和为某一个特定的值，求所有取法的方案数。
输入：
输入包含多个测试用例，每个测试用例的第一行有两个数N,S，N表示这个数字序列共有多少个数字；S表示取出的数字之和为S。后面一行包含N个正整数。
N,S为0程序结束
输出：
每个测试用例输出一行，表示从N个数中取若干个数使得和为S的方案总数。
样例输入：
4 8
3 4 5 8
4 12
3 4 5 8
10 10
10 9 8 7 6 5 4 3 2 1
0 0
样例输出：
2
2
10
&lt;/code>&lt;/pre>&lt;p>知道了状态转换方程，我们可以很快的写出以上OJ的代码：&lt;/p></description></item><item><title>Git相关笔记</title><link>https://bitjoy.net/posts/2014-11-11-git-notes/</link><pubDate>Tue, 11 Nov 2014 23:12:34 +0800</pubDate><guid>https://bitjoy.net/posts/2014-11-11-git-notes/</guid><description>&lt;h1 id="一第一次使用github的步骤">一、第一次使用Github的步骤：&lt;/h1>
&lt;ol>
&lt;li>在&lt;a href="https://github.com/new">这个页面&lt;/a>中填写Repo名称&lt;/li>
&lt;li>不要勾选Initialize this repository with a README&lt;/li>
&lt;li>点击Create repository&lt;/li>
&lt;li>在本地使用Git命令行工具进入到和第1步填写Repo相同名称的文件夹中（此文件夹中已包含你要push到Github上的内容），执行以下几个命令：&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>git init
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>touch README.md &lt;span style="color:#75715e">#optional&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git add .
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git commit -m &lt;span style="color:#e6db74">&amp;#39;your comment&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git remote add origin https://github.com/UserName/RepoName
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git push origin master
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;ol start="5">
&lt;li>如果你在第2步中勾选了Initialize this repository with a README，那么在第4步中省略touch README.md并且在git add .之前，执行第5行代码，然后git pull origin master将远端（remote）的内容pull到本地&lt;/li>
&lt;li>关于Git命令中的fetch和pull的区别，请看&lt;a href="https://blog.csdn.net/wfdtxz/article/details/8632811">这篇博文&lt;/a>&lt;/li>
&lt;li>关于Git bash和Github的连接，请看&lt;a href="https://www.cnblogs.com/fnng/archive/2011/08/25/2153807.html">这篇博文&lt;/a>&lt;/li>
&lt;/ol>
&lt;h1 id="二git命令中fetch和pull的区别转载">二、Git命令中fetch和pull的区别（&lt;a href="https://blog.csdn.net/wfdtxz/article/details/8632811">转载&lt;/a>）&lt;/h1>
&lt;p>Git中从远程的分支获取最新的版本到本地有这样2个命令：&lt;/p></description></item></channel></rss>