-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 138 KB
/
content.json
1
[{"title":"Deep Learning for Sentiment Analysis A Survey 笔记","date":"2019-02-26T13:20:03.607Z","path":"posts/2019/02/26/Deep_Learning_for_Sentiment_Analysis_A_Survey_note.html","text":"(未完整版…)  该论文详细论述了近年来深度学习在情感分析上的研究应用。论文前半部分先介绍了情感分析涉及的一些基本模型:FNN(前馈神经网络)、word embedding(提及word2vec,glove)、自编码及降噪自编码、CNN、RNN、LSTM、RNN与注意力机制、MemNN(记忆网络)、递归神经网络。后半部分对 Document level、sentence level、aspect level及其他一些比较细/前沿的情感分析方法进行调研。根据目前的工作,仅重点关注document level 和 sentence level所使用的一些方法。 情感分析任务情感分析任务主要分为三个: Document level sentence level aspect level(如“the voice quality of iPhone is great, bu its battery sucks 要识别 voice quality 是正向的,battery 是负向的) Document level下表总结了文档级情感分析的深度学习方法 简要介绍一下这些方法: 1、 Document-level sentiment classification: An empirical comparison between SVM and ANN  该论文主要是比较SVM 和ANN在Document level 上的情感分析应用效果,结论是大部分情况下ANN 优于 SVM ,尤其是在benchmark dataset of Movies reviews 上。2、Distributed Representations of Sentences and Documents  为了克服BOW的缺点(没考虑词之间的顺序,以及词之间的关系),Le and Mikolov 提出了一种可以学习可变长度文本的段落向量,如句子、段落、文档。该段落向量表示通过预测抽取文本周围的词来学习。? 3、Domain Adaptation for Large-Scale Sentiment Classification:A Deep Learning Approach  Glorot et al 研究了大规模情感分类中的域自适应问题,他们提出了一种基于多层降噪自动编码器和 sparse rectifier units的深度学习系统,该系统能够根据标注/无标注提取无监督文本特征/表示,这些特征能够很好地应用于域适应的情感分类。 4、Semisupervised autoencoder for sentiment analysis  Zhai and Zhang介绍了一种半监督自编码的方法,为了获得更好的document vectors ,该方法能够在学习过程中考虑更多的情感信息。具体来说就是,模型通过将自编码器中的损失函数松弛到Bregman散度以及从标签信息中推导出判别损失函数来学习特定任务的文本数据表示。 5、Effective Use of Word Order for Text Categorization with Convolutional Neural Networks  Johnson and Zhang 提出了一种CNN的变体方法:BOW-CNN,该方法在卷积层使用了BOW。另外,他们也设计了一种新的模型:Seq-cnn,通过拼接多个词的one-hot向量来保持连续词的信息。 6、Document Modeling with Gated Recurrent Neural Network for Sentiment Classification  Tang 等人提出了一种能够学习文档表示的神经网络,该网络考虑了句子之间的关系。该方法首先使用CNN 或LSTM从word embedding中学习句子的表示,然后使用GRU自适应地编码句子的语义以及文档中句子之间的关系。 7、learning semantic representations of users and products for document level sentiment classification  Tang 等人将用户表示和产品表示应用于评论分类中。想法就是,这些(用户和产品)表示能够捕获一些重要的全局线索,比如能够提供更好的文本表示的 用户的个人偏好、产品的整体质量。 8、Neural sentiment classification with user and product attention  Chen 等人也在分类中考虑了用户和产品信息,但是是通过词和句子级别的注意力机制来实现的。该方法能够考虑到整体用户的偏好以及产品特性。同样地,Dou使用了一个深度记忆网络来捕获用户和产品信息,该模型可以分为两个独立的部分,第一部分,使用LSTM学习文档表示,第二部分,利用由多个计算层(hops)组成的深度记忆网络来预测每个文档的评分等级(review rating)。 9、Cached Long Short-Term Memory Neural Networks for Document-Level Sentiment Classification  Xu 等人提出了一种缓存LSTM模型,该模型能够在长文本中捕获整体语义信息。模型中的memory被分成几个不同遗忘率的组。能够让低遗忘率的memory捕获全局语义特征,让高遗忘率的memory捕获局部语义特征。 10、hierarchical attention networks for document classification  Yang 等人提出了一个多层attention 网络用于documennt level 的评论情感预测。该模型包含两个level 的注意力机制:一个是word level,能够让模型更多地关注个体词;另一个是sentence level ,能够让模型更多地关注文档中的句子的结构表示。 11、Document-level multi-aspect sentiment classification as machine comprehension.  Yin等人将文档级的目标情感等级预测任务作为机器阅读理解问题来解决, 同时提出了一个基于注意力的多层交互模型。Specifically,documents and pseudo aspect-questions are interleaved to learn aspect-aware document representation. 12、Attention-based LSTM Network for Cross-Lingual Sentiment Classification  Zhou 等人针对文档级的跨语言情感分类问题,设计了一个基于注意力机制的LSTM模型。该模型用了两个基于注意力机制的LSTM网络,并且每个LSTM都是分成结构的。这种方法,能够有效地将情感信息从资源丰富的语言(英语)转换为资源匮乏的语言(中文),有助于提升分类性能。 13、End-to-End Adversarial Memory Network for Cross-domain Sentiment Classification  li 等人将跨领域情感分类视为迁移学习,提出了一种对抗记忆网络,将源数据和目标域放在一起建模。该模型将情感分类和域分类放到一起训练(也就是,不管文档是来自源还是来自目标域) Sentence level  sentence level 的情感分析一般被当成一个三分类问题,也就是预测句子情感是正向、负向还是中性。相较于文档,句子级的情感分析可以使用一些语法语义信息,比如:分析树、观点词典、POS,以及其他的一些评论等级(1-5星)、社交关系、跨领域信息都能派上用场。但是随着神经网络的发展,使用CNN、RNN等模型不再需要额外提取分析树等特征,因为CNN、RNN将词嵌入作为输入,在encode的时候已经考虑了句子的语法和语义信息,模型也能学到句子中词之间的内在关系。下面介绍NN在sentence level的应用。 1、Semi-Supervised Recursive Autoencoders for Predicting Sentiment Distributions  Socher 等人首次提出一种半监督的递归自编码网络(RAE)用于句子级别的情感分类,该模型得到一个句子的降维向量表示。他也提出了一个Matrix-vector递归神经网络,其中,在树结构中,每个词都与一个矩阵表示(不是与矩阵的向量表示)相关联。该树结构从额外的语法解析器获得。Socher et al进一步介绍了一种递归神经张量网络(RNTN),它是基于张量的复合函数,能够更好地捕捉元素之间的相互作用。Qian 等人提出了两个更先进的模型:Tag-guided递归神经网络(TG-RNN)和Tag-embedded递归神经网络/递归神经张量网络(TE-RNN/RNTN),前者根据短语的词性标注来选择复合函数来训练,后者先学习标签嵌入,然后将标签嵌入和词嵌入融合在一起来训练。 2、A convolutional neural network for modelling sentences  Kalchbrenner 等人提出了动态CNN(DCNN)用于句子的语义建模。DCNN采用动态K-Max池化作为非线性下采样,该网络生成的特征图能够捕获词之间的关系。Kim也使用CNN做句子级的情感分类,并尝试了CNN的几种变体:RNN-rand(随机初始化词嵌入)、CNN-static(词向量已预训练好并且是静态的)、CNN-non-static(对预训练好的词向量微调)、CNN-multichannel(使用多个词向量集) 3、Deep convolutional neural networks for sentiment analysis for short texts.  dos Santos and Gatti 提出了一种字符到句子的CNN(CharSCNN)模型,CharSCNN采用两个卷积层从任意大小的词和句子中提取相关特征。Wang等人采用LSTM通过模拟词语在成词过程中的相互作用来对twitter进行情感分类。与使用简单加法的循环神经网络相比,LSTM在门结构中使用乘法对word embedding操作更加灵活以及达到更好的组合效果。?跟双向RNN相似,单向LSTM能够通过在隐藏层双向链接扩展成双向LSTM。 4、Dimensional Sentiment Analysis Using a Regional CNN-LSTM Model  wang等人提出了一种区域CNN-LSTM(regional CNN-LSTM)模型,该模型包含两部分:regional CNN 和 LSTM,用来预测文本中valence arousal数值(Valence表示兴奋或平静的程度, Arousal表示与正面或者负面的程度)。 5、Combination of Convolutional and Recurrent Neural Network for Sentiment Analysis of Short Texts  wang 等人提出了一种联合CNN和RNN的模型,用于短文本情感分类。该模型汲取了CNN的粗粒度特征和RNN的长距离依赖的优点。 6、CNN- and LSTM-based Claim Classification in Online User Comments  Guggilla等人提出了一种基于LSTM和CNN的深度神经网络模型,该模型使用word2vec 和语言嵌入进行陈述分类(claim classification,分为事实或者观点) 7、encoding syntactic knowledge in neural networks for sentiment classification  Huang 等人将句法知识(如POS tags)编码到树结构的LSTM中,以提升语法和句子的表示 8、a multilayer perceptron based ensemble technique for fine-grained financial sentiment analysis  Akhtar等人提出了几个基于多层感知机的融合模型,用于微博和新闻的金融细粒度情感分类。 9、Weakly-supervised deep learning for customer review sentiment classification  Guan 等人使用弱监督CNN对句子级(也可以用于aspect level)进行情感分类。该模型包含两个学习步骤:先通过整体评论评分来弱监督学习句子的表示,然后,使用句子(aspect)level 标签微调。 10、context-sensitive lexicon features for neural sentiment analysis  Teng 等人在一个简单的加权模型基础上,提出了一种基于词库的上下文感知方法?运用双向lstm分析情感词汇在构成句子情感价值时的情感强度、和情感否定程度。 11、learning sentence embeddings with auxiliary tasks for cross-domain sentiment classification  Yu and Jiang 研究了跨领域情感分类中的通用语句嵌入学习问题,并且设计了一个包含两个分离的CNN的神经网络模型,这两个CNN能够共同从标记和未标记的数据中学习两个隐藏特征表示。 12、microblog sentiment classification via recurrent random walk network learning  Zhao 等人提出了一种循环随机漫走网络(recurrent random walk network)学习方法,用于通过利用用户发布tweets及其社交关系的深层语义表示的tweets观点分类。 13、Learning Cognitive Features from Gaze Data for Sentiment and Sarcasm Classification using Convolutional Neural Network  Mishra等人借鉴人类阅读文本视角(眼睛移动阅读)的方法,采用CNN自动提取认知特征,作为丰富情感分类的特征。 14、Linguistically Regularized LSTM for Sentiment Classification  Qian 等人为该任务提出了一个语言规范化的LSTM,该模型将情感词汇、否定词汇、强度词汇整合在一起放入LSTM从而能够获得更好的情感分类效果。 note: 后面部分暂不作了解,等什么时候有时间再读下。 Aspect levelAspect 抽取和分类观点表达提取SENTIMENT COMPOSITIONOPINION HOLDER EXTRACTIONTEMPORAL OPINION MININGSENTIMENT ANALYSIS WITH WORD EMBEDDINGSARCASM ANALYSISEMOTION ANALYSISMULTIMODAL DATA FOR SENTIMENT ANALYSISRESOURCE-POOR LANGUAGE AND MULTILINGUAL SENTIMENT ANALYSISOTHER RELATED TASKSCONCLUSION","tags":[{"name":"NLP","slug":"NLP","permalink":"http://yoursite.com/tags/NLP/"},{"name":"情感分类","slug":"情感分类","permalink":"http://yoursite.com/tags/情感分类/"}]},{"title":"从attention到transformer","date":"2019-02-25T08:17:26.515Z","path":"posts/2019/02/25/attention_to_transformer.html","text":"   attention机制最早是应用于图像领域,2014年由Bahdanau等人在论文Neural Machine Translation by Jointly Learning to Align and Translate中首次将attention机制应于NLP领域,其在seq2seq机器翻译中取得显著效果,在随后的几年attention得到越来越广泛的应用,尤其是NLP领域。最近在做情感分类的迁移学习用到了attention,疗效不错,考虑到以后接触attention的机会只多不少,便把之前看的论文整理下,做个总结。 (soft) attention   传统的seq2seq模型由encoder-decoder组成,在翻译任务中,encoder端将待翻译的语句作为input,对input中的每个word $x_1,x_2,…,x_n$ 依次喂入rnn,得到对应的隐藏状态 $h_1,h_2,…,h_n$ ,常见的方法是将最后一个状态 $h_n$ 作为固定的context vector C(其他方法请参照下图,C由隐藏状态通过一个非线性函数获得),然后dencoder端拿到这个C向量解码获得目标语句。这种做好就好像是我给你一个输入,这个输入可能是一个短语、一句话、甚至一篇文档,你看了一遍之后就不在看了,然后默默地根据脑海里记得的进行翻译,如果我给你的是短语或者一句话还好,如果是一篇文档就GG了,很难只看一遍就能把整个文档翻译出来的嘛。cho等人还专门对这种框架的翻译效果做了论证,结论是待翻译的输入句子越长,翻译质量下降越厉害。 Bahdanau等人将attention应用于翻译中,在decoder端每次解码的时候使用的context向量C都是不一样的。下图展示了encoder端的隐藏状态是如何影响decoder端的目标词汇的。通过以下两个公式也可以看出来:在预测的时候,传统的seq2seq是求条件概率:使用attention之后是求条件概率:   现在来看看attention是怎么应用的,还是encoder端将待翻译的语句作为input,对input中的每个word $x_1,x_2,…,x_n$ 依次喂入rnn(论文里面是使用了双向GRU,但这不是重点),得到对应的隐藏状态 $h_1,h_2,…,h_n$,我们先初始化decoder端的隐藏状态 $s_0$,拿过来跟encoder端的每个隐藏状态放入一个非线性函数做一个match,得到一系列的标量 $e_1,e_2,…,e_n$ 。   这里的match函数种类还蛮多的,没有统一的规范,最终的目的是每个encoder端的隐藏状态会得到一个数值,这个数值表示decoder端该时刻输出与输入相应word的匹配程度,你可以使用余弦函数求余弦值也可以通过一个线性层得到,这个match函数里面的参数可以作为整个网络的参数一起学习更新,下图列出了常用的三种方法   在计算得到 $e_1,e_2,…,e_n$ 之后,经过一个softmax层,得到 $\\alpha_ij$ 和为1的权重系数,再用这个权重系数与对应的encoder端隐藏状态 $h_1,h_2,…,h_n$ 做加权求和得到最终的向量 $c_i$。对于其他时刻的decoder也如法炮制,看下图就会很清晰了   为了比较attention的突出效果,论文中分别训练了四个模型:RNNenc-30、RNNenc-50、RNNsearch-30、RNNsearch-50;RNNenc是传统的seq2seq模型,RNNsearch是加了attention的seq2seq模型,30 和50代表句子长度,除了attention 模型中的encoder使用了双向GRU,其他参数配置跟传统模型的参数配置一样,用Adatlta优化器训练了近5天效果如下左图,右图是翻译的时候,encoder端的输入对decoder端的每个输出的权重。    可以看到,attention的提升效果还是很明显的,尤其是在长句子上,RNNsearch-50甚至在更长的句子上表现也比较平稳。 hard attention   Kelvin等人在图像主题生成中提出了soft attention 和 hard attention 两个概念,上面提及的是soft attention,soft 和 hard attention 是相对的,soft attention 是decoder每次输出都会对encoder端的所有hidden state 都计算权重,并且每个hidden state 的权重是不一样的,而hard attention 每次只关注一个位置,论文将要关注的位置 $s_t$ 作为一个变量,让模型自己去在生成主题的时候应该关注哪个位置,关注的位置对应取值为1,其他位置取值为0,也就是一个one-hot的向量。一个显而易见的问题就是,将one-hot放到网络里面是不可微的,然后作者就做了一系列的转换,这个在实际实施过程中还是比较麻烦的。    作者将这两个类型的attention应用于Image Caption Generation,均取得了不错的效果,其中hard attention 比soft attention 要好那么一丢丢。 global attention   Luong等人同样也是从如何更好地生成动态的context向量C入手考虑,在论文《Effective Approaches to Attention-based Neural Machine Translation》 提出了global attention 和 local attention。 global attention其实跟soft attention 是一样的(虽然论文提及的global attention 跟上面soft attention的有点细微差别,这里使用的是stacking LSTM,上面提及的soft attention 是用双向GRU,并且在计算 $\\alpha_ij$ 上也做了一点点简化,但本质是一样的) local attention   global attention存在的一个明显问题就是,对于每一个target word 都需要重新对 encoder 的所有hidden state 计算一遍权重,计算开销大效率低,其实在对一些长句子甚至是长文档做翻译的时候,每翻译一个词并不需要将整个句子或者整个文档都考虑一遍的,一般只需要关注正在翻译词语附近的词就行了。怎么把目标词语附近范围划分出来呢,论文提出的local attention对于每个target word 都生成一个对齐位置 $p_t$,context向量 $c_t$从窗口 $[p_t - D, p_t +D]$ 中的隐藏状态加权求和得到,位置 $p_t$ 的挑选有两种方法,一种很简单,直接令 $p_t=t$ 就ok了。 另一种方法是将 $p_t$ 也作为目标进行预测,这样模型就有了两个任务,在预测decoder输出的时候也预测生成 $c_t$ 所需要的位置 $p_t$。 local attention 可以认为是hard attention 和 soft attention 的一种结合,这样的方式使得计算量更小,并且反向传播是可微的。 self attention   上面提及的attention都是encoder和decoder结合的,重点关注对于输出有利的输入,而尽量忽略对输出没什么影响的输入,这对于处理序列对序列问题是非常适合的。而self-attention 顾名思义就是自己对自己做注意力,self attention 是论文Hierarchical Attention Networks for Document Classification提出的,self attention 其实跟传统的attention 是非常相似的,区别在于计算score的时候,不考虑decoder了,而是引入一个参数W,这个参数可以是一个向量也可以是一个多层的线性网络层,会跟着网络的其他参数一起学习,也是不统一的,anyway,最终生成的结果是,输入的每个隐藏状态都有一个对应的数值score。比如将 $x_1,x_2,…,x_n$ 喂入RNN,得到对应的隐藏状态 $h_1,h_2,…,h_n$,如果隐藏状态向量的维度为 $(1,m)$,只要引入一个大小为 $(m, 1)$ 的参数w,让 $h_1,h_2,…,h_n$ 分别与 $w$ 做内积,最后就会得到$n$ 个标量,后面的softmax还有加权求和跟传统的attention 是一样的。    在文本分类上,self attention 还能区分出影响分类结果的关键词都有哪些,这样能够很清楚地知道模型在训练过程中到底学到哪些信息。   谷歌在17年的论文Attention Is All You Need中将attention 抽象为一个查询(query)到一系列键值对(key-value)的映射,其中query、key、value及对应的输出值都是向量,此时attention的输入输出都不用依赖于RNN,这对attention 的本质会有一个更清晰的理解,同对后续理解multi-head attention 和 transformer也会很有帮助。    这个是从网上找到图,很清晰。query,key,value在不同的场景下取值不一样,以翻译为例,seq2seq中的attention是对于decoder端的每一个target word 的隐藏状态 与encoder端的输入的所有隐藏状态做一个match 函数得到对应评分,然后对评分softmax得到权值,将隐藏状态与权值加权求和得到向量 $c_t$,这里可以将decoder端的每一个target word 的隐藏状态看作是query,encoder端的输入的所有隐藏状态看作是key,通过计算query和各个key之间的匹配程度,得到key对应的value权重系数,然后对value加权求和得到attention值,这里key=value,都是encoder端的隐藏状态。如果是self attention,那么query=key=value。 multi-head attention   要讲清楚multi-head attention 需要先了解下 scaled dot product attention。跟常规attention是将RNN的隐藏状态作为输入不一样,这里的query、key、value 不是RNN的隐藏状态,而是输入的word的embedding向量。首先,我们一个包含 $n$ 个词的句子 $(x_1,x_2,…,x_n)$ 作为输入,经过一个embedding层得到每个词的词嵌入向量 $(v_1,v_2,…,v_n)$ ,因为是self attention,query=key=value,对于某个query $v_i$ 要计算这个query对应的attention value 的话,是将 $v_i$ 与所有的key做相似度计算得到对应的value权重系数,然后对value加权求和得到attention值 这是单个query的情况,对于所有的query,可以将 $Q_i$ 堆叠起来一起计算 为了防止内积过大,对内积做了 $\\frac{1}{\\sqrt{d_{k}}}$ 的缩放,这就是scaled dot product attention了。    如果理解了scaled dot product attention,那么multi-head attention 就很好理解了,multi-head attention 是多个scaled dot product attention 的叠加。如果要叠加h次,就是对 $(Q,K,V)$ 做h次的线性变换,得到新的h个 $(Q_i,K_i,V_i)$,分别做内积缩放得到h个不同的attention值,然后将这h个不同的attention值拼接(concat)起来,再做一个线性变化,得到的结果就是multi-head attention 的输出。 在encoder-decoder连接层的attention有一点点不一样的地方就是,query是decoder端上一时刻的隐藏状态,而key和value是来自于encoder端的输出。 transformer   首先强推一篇文章The Illustrated Transformer,这篇文章像剥洋葱一样将transformer从外到里一层一层剥开来讲解,写的非常赞。   transformer中使用了大量的attention。论文中的transformer由堆叠的encoder和decoder组成,encoder 下由6个子编码器组成,而每一个编码器的第一层是一个multi-head self attention,第二层是一个简单的全连接前馈神经网络。decoder的层级结构基本一样,也是由6个子解码器组成,但在multi-head self attention 和前馈神经网络层之间会有一个attention层,以便更多地关注解码和编码特征向量之间的关系。","tags":[{"name":"NLP","slug":"NLP","permalink":"http://yoursite.com/tags/NLP/"},{"name":"attention","slug":"attention","permalink":"http://yoursite.com/tags/attention/"}]},{"title":"新词发现","date":"2019-02-24T02:23:44.708Z","path":"posts/2019/02/24/New_Word_discovery.html","text":"  新词发现,从文本中找出未登录词。本文主要参考互联网时代的社会语言学:基于SNS的文本数据挖掘 实现,实现的技术主要包括互信息、左右信息熵、n-gram。 另外为了方便提取新词,设计了一个评分函数,评分越高成词可能性越高。 左右信息熵  左右信息熵用来衡量该词语所处语境的丰富程度,信息熵是用来衡量随机变量不确定度的一种度量,不确定程度越高,信息熵越高,越难以预测。 对于一个随机变量X,它的熵可以表示成以下形式: 其中 $p(x)$ 代表随机变量 $x$ 出现的概率,在新词发现中,可以通过计算一个候选词左边和右边的信息熵来反映一个词是否有丰富的左右搭配,如果达到一定阈值则我们认为两个片段可以成为一个新词。例如(该例子来源于互联网时代的社会语言学:基于SNS的文本数据挖掘)> 考虑这么一句话 “吃葡萄不吐葡萄皮不吃葡萄倒吐葡萄皮”,“葡萄”一词出现了四次,其中左邻字分别为 {吃, 吐, 吃, 吐} ,右邻字分别为 {不, 皮, 倒, 皮} 。根据公式,“葡萄”一词的左邻字的信息熵为 – (1/2) · log(1/2) – (1/2) · log(1/2) ≈ 0.693 ,它的右邻字的信息熵则为 – (1/2) · log(1/2) – (1/4) · log(1/4) – (1/4) · log(1/4) ≈ 1.04 。可见,在这个句子中,“葡萄”一词的右邻字更加丰富一些。因此,如果一个词所处的语境越丰富(不确定程度越高),则表明这个词与周围的字成词概率越低,该词独立性越好。# 点间互信息  互信息(mutual information),表示的是两个随机变量X,Y共享的信息量。 或者说,互信息代表着知道了任意一个变量之后对另一个变量不确定性的减少。 计算公式为: 点间互信息或称 内部聚合度、凝固度,相对于互信息,点间互信息仅计算了两个随机变量之间的互信息,两者相关性越大,点间互信息越大。例子参考文章互联网时代的社会语言学:基于SNS的文本数据挖掘中“电影院”的例子 n-gram  n-gram 是将文本分成多个片段比较常用的方法,这里采用的是k-gram,其中 $k=2,…,n,n+1$ ,这里n为指定最大切词长度, $n+1$ 是为了计算词的左右信息熵。以词语“新词发现算法”为例,如果设定最大切词长度为3,则所有的切词片段为:新词、词发、发现、现算、算法、新词发、词发现、发现算、现算法、新词发现、词发现算、发现算法 评分函数  为了能够方便地评估一个词语的成词程度,设计公式:(词最小互信息 - 累计字信息熵 + 词最小左右信息熵) * 词频 对成词进行评分,分数越高成词效果越好。 算法流程具体算法实现流程如下: 输入:一个长文本字符串string 输出:新词 及 成词score 设定成词最大长度n;最小凝固度,最小自由度 将空格、逗号、分号等符号统一转换成换行符; 分别对每个句子、反转句子进行 $1,2,..,n,n+1-gram$ 分词; 将分好的词插入tire树中。并统计每个词的词频; 计算每个词的互信息,过滤掉不满足最小凝固度的词语; 计算每个词、反转词的左右信息熵,过滤掉不满足最小信息熵的词语; 计算每个字的左右信息熵,取最小值; 累计每个词的字信息熵; 计算词的成词score:(词最小互信息 - 累计字信息熵 + 词最小左右信息熵) * 词频 效果  下面是针对一些文本的抽取结果:西游记:1行者, 八戒, 师父, 菩萨, 和尚, 唐僧, 三藏, 者道, 怎么, 大圣, 沙僧, 甚么, 行者道, 悟空, 笑道, 妖精, 徒弟, 我们, 宝贝, 老孙, 袈裟, 吩咐, 闻言, 长老, 兄弟, 呆子, 铁棒, 葫芦, 国王, 仔细, 这般, 两个, 模样, 哥哥, 太宗, 左右, 兵器, 贫僧, 爷爷, 道士, 玉帝, 公主, 哪吒, 玄奘, 欢喜, 东土, 这等, 揭谛, 毕竟, 钉钯, 行李, 娘娘, 芭蕉, 收拾, 陛下, 师徒, 老爷, 猢狲, 如何, 衣服, 今日, 毫毛, 性命, 不知, 壁厢, 不曾, 取经, 战兢兢, 师兄, 包袱, 乾坤, 孙行者, 皇帝, 观音, 猪八戒, 安排, 罗刹, 不敢, 诗曰, 手段 通信行业会话数据:1套餐, 小和, 流量, 可以, 办理, 取消, 国内, 服务, 飞享, 已经, 人工, 回复, 4G, 享套餐, 飞享套餐, 优惠, 分钟, 什么, 本机, 成功, 8元, 使用, 介绍, 其他, 现在, 请问, 业务, 省内, 手机, 问题, 短信, 需要, 退出, 温馨, 为您, 免费, 麻烦, 赠送, 生效, 青春, 微笑, 内通, 查询, 青春版, 谢谢, 直接, 港澳, 效劳, 月租, 没有, 号码, 确定, 显示, 支持, 记得, 身边, 4G飞享, 资费, 系统, 稍等, 通话, 开通, 接听, 宽带, 国内通用, 这个, 提醒, 个月, 光纤, 套餐青春, 拨打, 短号, 语音, 满意, 通用流量, ^_, _^, 扣费 红楼梦: 宝玉, 什么, 凤姐, 黛玉, 姑娘, 贾母, 宝钗, 怎么, 太太, 笑道, 告诉, 丫头, 贾政, 自己, 东西, 鸳鸯, 奶奶, 紫鹃, 贾琏, 咱们, 晴雯, 袭人, 媳妇, 如今, 知道, 老爷, 探春, 李纨, 姨妈, 尤氏, 我们, 老太太, 湘云, 姐姐, 薛姨妈, 丫鬟, 刘姥姥, 薛蟠, 这样, 吩咐, 薛姨, 姊妹, 姥姥, 答应, 你们, 妹妹, 夫人, 香菱, 两个, 那里, 贾珍, 听见, 平儿, 周瑞, 屋里, 麝月, 二爷, 糊涂, 预备, 雨村, 林黛玉, 雪雁, 一个, 他们, 兄弟, 惜春, 所以, 贾赦, 伏侍, 只管, 不敢, 小厮, 贾蓉, 姨娘, 如此, 收拾, 那边, 哥哥, 嬷嬷, 悄悄, 喜欢","tags":[{"name":"NLP","slug":"NLP","permalink":"http://yoursite.com/tags/NLP/"},{"name":"新词发现","slug":"新词发现","permalink":"http://yoursite.com/tags/新词发现/"}]},{"title":"基于CRF的命名实体识别","date":"2019-02-23T06:47:33.867Z","path":"posts/2019/02/23/基于CRF的命名实体识别.html","text":"   分词、词性标注(POS)、实体识别(NER)都属于序列标注问题,解决的套路可以是一样的,在文章分词-基于CRF中讲了如何使用CRF分词,照葫芦画瓢,本文将会使用CRF提取1998年人民日报语料中的时间(TIME)、人名(PERSON)、地名(LOCATION)、组织机构(ORGANIZATION)四类实体。 语料和工具 1998年人民日报标注语料 CRF++0.58 python 数据清洗98年的人民日报语料据说是全人工标注,数据质量还是比较高的,需要清洗的主要有以下几点: 全角转半角, 数据中有些文字是全角字符,如“1998”,统一转成半角字符处理; 姓名合并,标注数据中,姓和名是分开的,如:“李/nr 鹏/nr”,需要合并成:”李鹏/nr”; 实体名合并,数据中对于部分实体是分开标注的,并用中括号引起来表明其为某个实体,如:“[中国/ns 政府/n]nt”,“[人民/n 大会堂/n]ns”,需要将中括号内非标签文字合并,整理成:“中国政府/nt”形式。 特征构建在特征构建上,主要从字、字边界特征、词法特征、时间常用词、指示词(姓名、地名、组织机构)这六个方面去构建,如果有时间的话还可以整理下中国的姓名、地名、组织机构常用词特征的,anyway,构造特征的本质就是尽量找出对标签有利的信息 字、字边界特征字边界分为:B(词语首部)、M(词语中标)、E(词语尾部)、S(单字),对每个词标记其字边界,并整理成以下格式: 不 B 依 M 不 M 饶 E , S 喋 B 喋 M 不 M 休 E ... 词法特征词法特征,人民日报语料是做好词性标注的,但是如果需要对外部语料测试的话,需要标注词性会比较麻烦,因此这里使用thulac(效率好慢好慢…)将语料重新做词性标注。 不 i 依 i 不 i 饶 i , w 喋 i 喋 i 不 i 休 i ... 时间常用词如果词语中包含:今、明、昨、年、月、日、时、分、秒 则标记为Y,否则标记为N。把时间这个作为实体放上来,只是想测试下CRF的效果,时间的识别其实还是相对简单的,如果项目时间不充足,正则写的溜的话,基于正则识别时间应该也是没有问题的。 指示词通常在人名、地名或者组织机构名出现的前后会有一些指示词,比如”国家/主席/XXX/发表”、“新华社/记者/XXX/摄”、“在/人民大会堂/召开”,这些指示词包含有上下文信息,能够有效地帮助我们识别实体,本文将出现实体的前后两个词作为候选指示词,然后从所有候选指示词中提取频数大于10的作为最终的指示词。如果是指示词则标记Y,否则标记N。 新 Y 华 Y 社 Y 记 Y 者 Y 樊 N 如 N 钧 N 摄 Y 模型构建数据划分将人民日报语料抽70%做训练集,30%做测试集。 训练数据处理好的训练数据如下:特征顺序依次为:字、字边界、词性、时间常用词、人名指示词、地名指示词、组织机构指示词、标签 迈 B v N N N N O 向 E v N N N N O 充 B v N N N N O 满 E v N N N N O 希 B n N Y Y N O 望 E n N Y Y N O 的 S u N Y Y Y O 新 S a N Y Y N O 世 B n N N Y N O 纪 E n N N Y N O — B w N Y Y N O — E w N Y Y N O 一 B t N N N N B_TIME 九 M t N N N N M_TIME 九 M t N N N N M_TIME 八 M t N N N N M_TIME 年 E t Y N N N E_TIME 新 B t N N Y N B_TIME 年 E t Y N Y N E_TIME .... 特征模板# Unigram U00:%x[-2,0] U01:%x[-1,0] U02:%x[0,0] U03:%x[1,0] U04:%x[2,0] U05:%x[-2,0]/%x[-1,0]/%x[0,0] U06:%x[-1,0]/%x[0,0]/%x[1,0] U07:%x[0,0]/%x[1,0]/%x[2,0] U10:%x[-2,1] U11:%x[-1,1] U12:%x[0,1] U13:%x[1,1] U14:%x[2,1] U15:%x[-1,0]/%x[0,1] U16:%x[0,0]/%x[1,1] U17:%x[-1,0]/%x[-1,1] U18:%x[-2,2] U19:%x[-1,2] U20:%x[0,2] U21:%x[1,2] U22:%x[2,2] U23:%x[-2,0]/%x[0,2] U24:%x[-1,0]/%x[0,2] U25:%x[0,0]/%x[0,2] U26:%x[-2,0]/%x[-1,0]/%x[0,2] U27:%x[-1,0]/%x[0,0]/%x[0,2] U28:%x[-2,3] U29:%x[-1,3] U30:%x[0,3] U31:%x[-2,3]/%x[-1,3]/%x[0,3] U32:%x[-2,4]/%x[-1,4]/%x[1,4]%x[2,4] U33:%x[-2,5]/%x[-1,5]/%x[1,5]%x[2,5] U34:%x[-2,6]/%x[-1,6]/%x[1,6]%x[2,6] # Bigram B 训练及效果评估先来看下单字作为特征的情况,仅仅用单字特征识别实体效果勉强还行。 processed 552006 tokens with 73148 phrases; found: 69378 phrases; correct: 61864. accuracy: 97.13%; precision: 89.17%; recall: 84.57%; FB1: 86.81 B_LOCATION: precision: 90.09%; recall: 85.84%; FB1: 87.91 6256 B_ORGANIZATION: precision: 84.47%; recall: 82.13%; FB1: 83.28 3193 B_PERSON: precision: 95.74%; recall: 89.97%; FB1: 92.76 5535 B_TIME: precision: 95.40%; recall: 92.58%; FB1: 93.97 5870 E_LOCATION: precision: 88.37%; recall: 84.21%; FB1: 86.24 6257 E_ORGANIZATION: precision: 85.78%; recall: 83.40%; FB1: 84.58 3193 E_PERSON: precision: 95.10%; recall: 89.37%; FB1: 92.15 5535 E_TIME: precision: 95.21%; recall: 92.40%; FB1: 93.78 5870 M_LOCATION: precision: 81.19%; recall: 73.40%; FB1: 77.10 3609 M_ORGANIZATION: precision: 78.75%; recall: 72.37%; FB1: 75.42 14725 M_PERSON: precision: 94.98%; recall: 93.18%; FB1: 94.07 5421 M_TIME: precision: 98.04%; recall: 97.36%; FB1: 97.70 3835 S_PERSON: precision: 96.20%; recall: 45.78%; FB1: 62.04 79","tags":[{"name":"NLP","slug":"NLP","permalink":"http://yoursite.com/tags/NLP/"},{"name":"NER","slug":"NER","permalink":"http://yoursite.com/tags/NER/"}]},{"title":"CRF笔记","date":"2019-01-30T14:24:13.476Z","path":"posts/2019/01/30/CRF笔记.html","text":"  本文是台湾AI届最强YouTuber李宏毅老师的Strutured Learning课程中关于CRF讲解的笔记。HMM很容易理解,但是在看CRF的时候一脸懵逼,看了李航老师的《统计学习》和周志华老师的西瓜书也是一知半解,不得要领,直到看了李宏毅老师的CRF课程视频才恍然大悟,哈哈哈。 线性连条件随机场是判别式模型,目的是求给定 $X$ 的情况下 $Y$ 的条件概率分布,模型公式为: CRF看起来跟HMM很不一样,但其实原理是相通的。 HMM为生成式模型,计算的是 $x, y$ 的联合概率分布(计算方法如下图),将公式两边分别取log,这样公式由原来的相乘都变成了相加。 (1)我们先关注相加的最后一项,这一项是对于长度为L的句子,给定状态y的情况下出现观测x的概率的对数和。 将该项稍微做下转化: 举个栗子,假设整个数据集中一共有10个不同的词,5种不同词性,对于句子“The dog ate the homework”,对应的词性为:“D/N/V/D/N”,要求这个句子在给定状态y的情况下出现观测x的概率的对数和,在HMM模型中,我们只统计句子中出现的(词,词性)对,比如 $P(the|D)$,而没有出现在该句子中(词,词性)对就不统计了,但CRF会把没有出现在该句子中的(词,词性)对也考虑进来,只不过对应的次数为0,所以下面公式中倒数第二个等式做了简化,其实是有 $5x10 - 1 =49$ 项,有一项重复,其他未出现的项次数为0(所以CRF才会有这么多无用的特征..)。 同理,我们可以对公式(1)中的每一项都照葫芦画瓢,将其调整为对数条件概率乘以相应次数的形式,这样就可以把 $log P(x,y)$ 写成一大推两项相乘的形式,将其描述成两个向量的内积,$w \\cdot \\phi(x,y)$ ,这里的 $\\phi(x,y)$ 其实就是CRF里面的特征向量。 跟HMM的发射概率、状态转移概率、初始概率直接从训练数据中统计不同的是,这里的参数w是从训练数据中不断迭代学习得到的,既然是训练得到的,不加限制的话,参数w里面的每个元素是可正可负的,如果参数w是负数的话,计算 $P(x, y) = exp(w \\cdot \\phi(x,y))$ 得到的结果小于1,将其解析成概率好像没什么毛病,但是如果参数 $w$ 是正数的话,得到的 $P(x, y)$ 大于1,这时候将其解析成概率就很奇怪了,另外,在训练中,也没办法保证给定某个状态 $s_i$,对于所有的观测 $t$ ,其概率和为1(从理论上统计的话,和肯定是1的),所以,严格意义上,式子 $P(x, y) = exp(w \\cdot \\phi(x,y))$ 两边是不相等的,所以稍微做了下修改,认为 $P(x,y)$ 是跟 $exp(w \\cdot \\phi(x,y))$ 成正比的,写成:$$P(x,y) \\varpropto exp(w \\cdot \\phi(x,y))     (2)$$我们知道,公式(2),中的参数 $w$ 其实是特征权重,是从训练中学习得到的,而 $\\phi(x,y)$ 是特征向量,这个特征向量长什么样子的呢,继续以下图的例子进行解析。 这里的特征主要包含两部分:part1: 标签与词之间的关系前面解析过,CRF的特征是将所有可能的组合都考虑进来,不管这个组合在实际数据中有没有出现,其特征其实就是出现的次数。如果数据中有 $|S|$ 种可能的标签,非重复词的个数为 $|L|$,对应的特征维度大小为 $|S| x |L|$ 标签与标签之间的关系跟part1 同理,这里计算的是标签与标签之间的所有组合的特征,另外将start 和 end 这两个位置也考虑进来,如果有 $|S|$ 种标签,那么对应的特征维度大小为 $|S| x |S| + 2|S|$。 这样在训练CRF模型的时候,就可以将part1 和part2两个部分的特征接起来,当做CRF里面拿来算概率的vector,这里其实是跟HMM一样的,但是不一样的地方在于,CRF将其概率描述成一个 $w$ 和 特征向量的内积,可以自己定义特征,比如对于词性标注,可以尝试增加,该字是否句子首尾字,或者首字母是否大写(英文语料)等特征,CRF真正强大的地方也正在于此。CRF的原理到这里就结束了,在训练的时候,目标是找到恰当的参数 $w$ ,使得 $P(y|x)$ 最大。","tags":[{"name":"NLP","slug":"NLP","permalink":"http://yoursite.com/tags/NLP/"},{"name":"分词","slug":"分词","permalink":"http://yoursite.com/tags/分词/"}]},{"title":"分词-基于CRF","date":"2019-01-06T03:25:40.057Z","path":"posts/2019/01/06/基于CRF的中文分词.html","text":"  上篇使用HMM进行中文分词,HMM存在两个比较严格的假设条件:观测独立性和当前状态只与上一状态有关,相比于HMM,CRF不受这两个假设的限制,并且可以自定义特征。为了保持分词方法的连贯性,CRF的推理会在下篇给出,本文主要是使用CRF++分词,CRF虽然理解起来困难,但是CRF++使用起来却是很容易,只要按照规定的格式提供训练数据模板和特征模板就好了,因此本文会将重点会放在两个模板的理解上面。 语料和工具 语料采用SIGHAN Bakeoff 2005 微软亚洲研究院中文分词语料 CRF++0.58 python 安装  从官网上下载对应系统版本的CRF++ 0.58 并解压安装,安装方法和使用说明官网都有(win系统的好像是开袋即食,解压就可以用了)。安装好之后可以在example/chunking下执行crf_learn -a MIRA template train.data model 体验下。 数据模板训练和测试数据只需要按照给定的格式提供即可。训练数据模板: 12345678910不 B依 M不 M饶 E, S喋 B喋 M不 M休 E...   每一列是一种类型特征,最后一列为序列标签 ,列与列之间使用制表符\\t分割,句子与句子之间使用\\n分开(句子间多个换行符有问题?待求证)。上图只有原始输入的字本身这一列特征,序列标签为BEMS。需要注意的是,对于所有的行,列的数量必须都保持一致,不能有些行多一列有些行少一些。 特征模板特征模板如下: 1234567891011121314# UnigramU00:%x[-2,0]U01:%x[-1,0]U02:%x[0,0]U03:%x[1,0]U04:%x[2,0]U05:%x[-2,0]/%x[-1,0]/%x[0,0]U06:%x[-1,0]/%x[0,0]/%x[1,0]U07:%x[0,0]/%x[1,0]/%x[2,0]U08:%x[-1,0]/%x[0,0] U09:%x[0,0]/%x[1,0]# BigramB 特征模板分为两部分,Unigram feature 和 Bigram feature。 Unigram feature: 用来生成状态特征函数 标识符id, Unigram 特征根据第一个字符’U’识别,用于描述 unigram 的特征模板。U后面的数字00、01代表特征id,只是用于标记,不一定需要连续,只要不重复应该就ok了,当然,如果不考虑上下文相关,需要做bag-of-words的训练,可以不需要写id。 行列偏移%x[row, col] x是观测序列,row代表相对的行偏移, col代表相对的列偏移。比如 %x[1, 0]代表后一个字对当前字的影响。%x[-2, 4] 代表前第二个字对第5列特征的影响(上图只有一列特征)。当然也可以进行特征组合,如U05:%x[-2,0]/%x[-1,0]/%x[0,0],代表当前字的前第二个字、前一个字和当前字的特征组合。还是以上图为例,假设当前行为”满”,那么对应的特征为: 特征模板 意义 代表特征 U00:%x[-2,0] -2行,0列 向 U01:%x[-1,0] -1行,0列 充 U02:%x[0,0] 0行,0列 满 U03:%x[1,0] 1行,0列 希 U04:%x[2,0] 2行,0列 望 U05:%x[-2,0]/%x[-1,0]/%x[0,0] -2列,0行与-1列,0行和0列0行组合 向/充/满 U06:%x[-1,0]/%x[0,0]/%x[1,0] -1列,0行与0列,0行和1列0行组合 充/满/希 U07:%x[0,0]/%x[1,0]/%x[2,0] -1列,0行与0列,0行和1列0行组合 满/希/望 U08:%x[-1,0]/%x[0,0] -1列,0行与0列,0行组合 充/满 U09:%x[0,0]/%x[1,0] 0列,0行与1列,0行组合 满/希   unigram特征模板是对训练数据的每一行中的每列特征都生效的,所以会产生很多的特征,对于特征模板中的每一行,产生的特征数为 $L \\times N$ ,其中 $L$ 是输出序列标签类别数, $N$ 是此行模板在训练集上展开的唯一样本数。这里比较困惑的就是模板如何在训练集上展开,以及唯一样本数是怎么统计的了,下面说说我的理解。以特征模板: U02:%x[0,0] 对上面训练模板中的部分样本“不依不饶,喋喋不休”为例,分词标签为BEMS四类,则会生成以下 $4 \\times 6$ 个特征函数: 123456789func1 = if(output=B and feature=U02:'不') return 1 else 0func2 = if(output=E and feature=U02:'不') return 1 else 0func3 = if(output=M and feature=U02:'不') return 1 else 0func4 = if(output=S and feature=U02:'不') return 1 else 0...func37 = if(output=B and feature=U02:'休') return 1 else 0func38 = if(output=E and feature=U02:'休') return 1 else 0func39 = if(output=M and feature=U02:'休') return 1 else 0func40 = if(output=S and feature=U02:'休') return 1 else 0 也就是该特征与标签组合的唯一样本数。 Bigram feature: 用来生成转移特征函数  Bigram 特征根据第一个字符’B’识别,用于描述 Bigram 的特征模板。 这个特征一般不用处理,只保留一个 $B$ 就好了。也可以参照unigram的格式生成模板,只不过这样模型会变得很复杂。Bigram在生成转移特征函数的时候会把上一时刻标签 $y_{i-1}$ 也考虑进来,有点类似于: func1 = if(pred_output=B and output=B and feature=U02:'不') return 1 else 0   因此产生的特征数为 $(L \\times L \\times N)$,其中,$L$ 是输出序列标签类别数, $N$ 是此行模板在训练集上展开的唯一样本数。 训练按照以上格式整理好训练就可以直接调用CRF++来对训练语料进行分词。在训练数据目录下执行: # crf_learn template_file train_file model_file 其中,template_file 和 train_file 分别为事先准备好的特征模板文件和训练数据文件,model_file 是训练生成的模型文件,用于测试。 测试训练好之后,按照训练数据的格式整理好测试数据,然后执行: # crf_test model_file test_file > test_result_file 效果评估评估方法以下三种:  1、自己写个脚本评估效果,评估方式参考:中文分词器分词效果的评测方法  2、使用Bakeoff 2005 icwb2-data数据集中自带的评分脚本:icwb2-data/scripts/score  3、使用conlleval.perl评测,该方法可以直接对crf_test生成的结果进行评估,无需做进一步处理。 # perl conlleval.pl -r -d \"\\t\"< pku_test_result.data processed 172733 tokens with 172733 phrases; found: 172733 phrases; correct: 162436. accuracy: 94.04%; precision: 94.04%; recall: 94.04%; FB1: 94.04 B: precision: 94.85%; recall: 96.24%; FB1: 95.54 57713 E: precision: 94.65%; recall: 96.04%; FB1: 95.34 57713 M: precision: 78.98%; recall: 86.42%; FB1: 82.53 12560 S: precision: 96.42%; recall: 90.85%; FB1: 93.55 44747 问题整理在使用crf++分词过程中遇到一些问题,记录下:  1、训练数据格式错误:encoder.cpp(340) [feature_index.open(templfile, trainfile)] feature_index.cpp(174) [max_size == size] inconsistent ;出现这个错误是没有按照规定的格式来整理训练数据,特征之间用制表符分隔,句子之间换行符…   2、x86_64-conda_cos6-linux-gnu-gcc错误安装python 接口, 执行python setup.py build 的时候出现错误:error: command ‘x86_64-conda_cos6-linux-gnu-gcc’ failed with exit status 1 。解决方法:需要安装gxx_linux-64,执行命令:conda install gxx_linux-64。   3、在python环境中导入CRFPP出现错误:ImportError: libcrfpp.so.0: cannot open shared object file: No such file or directory解决方法见ubuntu16.04 CRF++安装及报错处理","tags":[{"name":"NLP","slug":"NLP","permalink":"http://yoursite.com/tags/NLP/"},{"name":"分词","slug":"分词","permalink":"http://yoursite.com/tags/分词/"}]},{"title":"分词-基于HMM","date":"2019-01-06T02:06:30.144Z","path":"posts/2019/01/06/基于马尔可夫的中文分词.html","text":"  基于统计的分词方法有:N-gram、隐马尔可夫(Hidden Markov Model ,HMM)、最大熵模型(Maximum Entropy Model, ME)和条件随机场(Conditional Random Fields,CRF)等,一般用的比较多的是HMM和CRF,比如简单又好用的jieba就是字符串匹配+HMM 的分词方法,HanLP等有用到CRF。一般来说CRF分词效果要好于HMM,本文主要介绍HMM分词。  HMM由俄国数学家A.A.马尔可夫于1907年提出,主要用于描述随机生成过程, HMM在语音识别、信息科学、NLP等诸多领域有着极其广泛的应用。在NLP领域更多的是用来处理分词、词性标注、实体识别等序列标注问题,HMM属于生成式模型,原理在这里不打算细讲了,李航的《统计学习方法》和周志华的西瓜书都讲得很详细了,网上也是随便一搜一大堆的街货。 HMM基本问题HMM有三个基本问题: 概率计算问题。给定模型 $\\lambda=(A,B,\\pi)$ 和观测序列 $O=(o_1,o_2,…,o_T)$ ,计算在模型 $\\lambda$ 下观测序列 $O$ 出现的概率 $P(O|\\lambda)$ 。也就是模型、参数、结果已知,求出现该结果的概率。 学习问题。 已知观测序列 $O=(o_1,o_2,…,o_T)$ , 估计模型 $\\lambda=(A,B,\\pi)$ 的参数,使得在该模型下观测序列概率 $P(O|\\lambda)$ 最大,即用极大似然估计的方法估计参数。即模型已知,参数未知 预测问题。 也称为解码问题,已知模型 $\\lambda=(A,B,\\pi)$ 和观测序列 $O=(o_1,o_2,…,o_T)$ ,求对给定观测序列条件概率 $P(I|O)$ 最大的状态序列 $I=(i_1,i_2,…,i_T)$ ,即给定观测序列,求最有可能的对应状态序列。 中文分词属于第三个问题,也就是给定一个待分词句子/文本,找出最有可能的隐藏状态序列。 基于HMM的中文分词假如现在要对这样一句话分词:“我们都是追梦人”,嗯,很简单,幼儿园水平,不假思索就分出来了,结果是:我们/都/是/追梦人 。如果使用BMES分别表示词首位、词中间位置、词末尾位置、单字词,那么:我们/都/是/追梦人 可以表示为:BE/S/S/BME。这里分词的语句被称为观测序列,“BE/S/S/BME”为隐藏状态序列。我们人工分词的话,一般是从左(句首)往右(句尾)浏览要分词的文本,根据场景以及语句的通顺程度将句子逐一切分,有时遇到易混淆的地方还会前后追溯,如“我们都是追梦人” 就可能会误分为:我们/都/是/追梦/人,HMM在分词时不会考虑这么多,其假设: 1、当前时刻t的状态只依赖于其前一时刻的状态,与其他时刻状态及观测无关,也与时刻t无关。也称为齐次马尔可夫性假设; 2、任意时刻的观测只依赖于该时刻的马尔可夫链的状态,与其他观测及状态无关。也称为观测独立性假设这两个假设条件只是为了简化计算,不是必须的。对于假设1,如果当前时刻t的状态与前2个时刻的状态有关的话就是二阶马尔可夫,与前3个时刻的状态有关的话就是三阶马尔可夫,以此类推。 上图,箭头指向代表依赖关系,如 $A \\rightarrow B$ 表示 $B$ 依赖于 $A$ ,但 $A$事件发生与 $B$ 无关,无箭头指向的事件代表不相关,如观测“我”和观测“们”无关。由t时刻到t+1时刻的状态可能性称为状态概率,各种状态之间可能性组成状态概率矩阵。 状态转移概率矩阵: B S E M B -3.14E+100 -3.14E+100 -0.1569 -1.9297 S -0.5003 -0.9323 -3.14E+100 -3.14E+100 E -0.5138 -0.9118 -3.14E+100 -3.14E+100 M -3.14E+100 -3.14E+100 -0.3933 -1.1234 状态到观测的可能性称为发射概率,不同状态到不同观测的可能性组成的矩阵称为发射概率矩阵。 发射概率矩阵: 我 们 都 是 追 梦 人 B -5.1686 -3.14E+100 -9.6289 -8.1219 -8.0042 -9.3606 -4.6367 S -5.6816 -6.4581 -5.6245 -3.9834 -9.9594 -9.6229 -5.2732 E -9.0729 -4.9170 -7.4431 -5.9310 -12.1938 -9.8912 -5.1183 M -8.7115 -11.0629 -7.8849 -8.4239 -10.1466 -9.1911 -5.3862 初始概率,组成初始概率矩阵。 初始状态: $\\pi$ P B -0.4691 S -0.9823 E -3.14E+100 M -3.14E+100 为了方便计算,上面矩阵的概率都转成了对数。这里有两个问题,第一是,发射矩阵、状态转移矩阵、初始概率矩阵怎么计算出来的;第二是:如何找出最有可能的隐藏状态序列。 概率矩阵计算矩阵的计算很简单,直接根据语料库统计出来就好了。初始概率矩阵: $$p(y_1=s)=\\frac{count(y_1=s)}{\\sum count(y_1 = s_i)}$$ 发射概率矩阵: $$p(y_{l+1}=s^{\\prime}|y_l=s) = \\frac{count(s \\rightarrow s^{\\prime})}{count(s)}$$ 其中 $s$是当前时刻状态,$s^{\\prime}$ 是下一时刻状态。状态转移概率矩阵: $$p(x_l=t|y_l=s) = \\frac{count(s \\rightarrow t)}{count(s)}$$ 其中 $s$ 是当前时刻状态,t是当前时刻观测。 viterbi  前面计算了初始概率矩阵、发射概率矩阵和状态转移矩阵,理论上有了这三个矩阵,就可以进行分词了,最简单直观的方法是直接按照概率公式计算,列举出所有可能的路径,然后从所有可能的路径中找到概率最大的路径,但是这样计算量很大,对于观测序列长度为T,状态为N,算法复杂度为 $N^T$,计算量太大了,并不是有效的办法。针对这种情况,可以考虑使用动态规划来求解,viterbi算法天然适用于求解HMM路径问题,其计算复杂度为 $O(T|N|^2)$。  关于维特比算法的原理讲解可以看这篇文章小白给小白详解维特比算法(一),通俗易懂。为了方便说明,这里以“我们都是追梦人”为例介绍viterbi算法。 对于“我”字,通过初始状态与发射概率相乘(初始状态、发射概率矩阵、状态转移矩阵沿用上面三个表格数据,这里将概率取对数后计算,也就是相加),其各隐藏状态概率为: 我 B -0.4691 + -5.1686 = -5.6377 S -0.9823 + -5.6816 = -6.6639 E -3.14E+100 + -9.0729 = -3.14E+100 M -3.14E+100 + -8.7115 = -3.14E+100 这里初始最优路径有4条。对于第二个字“们”,其计算方法如下(手算比较麻烦,这里偷个懒): 们 B $max(E \\rightarrow B, S \\rightarrow B)$ S $max(S \\rightarrow S, E \\rightarrow S)$ E $max(B \\rightarrow E, M \\rightarrow E)$ M $max(B \\rightarrow M, M \\rightarrow M)$ 其中 $B \\rightarrow E, S \\rightarrow S$ 这些表示 $t-1$ 时刻(我)的状态到 $t$时刻(们)的状态的路径,剩下的“都是追梦人”的计算方法也是一样。如果还不是很清楚的话,可以看下图: 我们要计算 $t=3$ 时刻状态为E的最大路径,只需要比较:$max(E \\rightarrow B, S \\rightarrow B) + B \\rightarrow E$ 和 $max(B \\rightarrow M, M \\rightarrow M) + M \\rightarrow E$ ,而无需将每条路径都遍历一次。根据viterbi算法计算得到句子的4条可能路径为: 结束状态 概率 路径 B -45.866 ‘B’, ‘E’, ‘S’, ‘S’, ‘B’, ‘E’, ‘B’ E -47.300 ‘B’, ‘E’, ‘S’, ‘S’, ‘B’, ‘M’, ‘E’ M -48.298 ‘B’, ‘E’, ‘S’, ‘S’, ‘B’, ‘M’, ‘M’ S -46.901 ‘B’, ‘E’, ‘S’, ‘S’, ‘B’, ‘E’, ‘S’ 因为句子结束状态只能是ES之一,两者中概率最大为-46.901,所以分词结果为:我们/都/是/追梦/人 。嗯,语料不太够。 存在问题1、不存在语料库的字发射概率为0由于发射概率矩阵是从训练词汇中统计的,导致出现的一种情况是,HMM对于未曾出现在语料库中的字的发射概率为0,进而导致该字后面的路径概率都为0,分词不work也就无法有效地找出最佳路径,虽然在实际计算的时候对于不存在的词往往会默认给一个最小的数。对于下面两句话:12句式1:每个月几乎都有两三条船停靠中国港口。 句式2:每个月几乎都有6条船停靠中国港口。 句式1均为中文,句式2有一个数字(该数字不在jieba预训练的语料库中),为方便测试,可以调用jieba的纯HMM模式切词,得到的效果完全不一样。 list(jieba.finalseg.__cut('每个月几乎都有两三条船停靠中国港口。')) #output: ['每个', '月', '几乎', '都', '有', '两三条', '船', '停靠', '中国港', '口', '。'] list(jieba.finalseg.__cut('每个月几乎都有6条船停靠中国港口。')) #output: ['每个', '月', '几乎', '都', '有', '6', '条', '船', '停', '靠', '中', '国', '港', '口', '。'] jieba对此问题的处理方式是先将中文提取出来,喂入HMM,对一些非中文字符如数字百分号则单独处理。2、优化的方法就是增加语料,调整初始、发射、转移概率矩阵。这是好事也是坏事,免去了做特征的繁琐,但也没有什么可以优化的了。 总结  HMM简单且强大,将HMM应用于中文分词,其实就是求解带切分句子最有可能隐藏状态序列,其初始状态、转移状态、发射概率矩阵都可以从训练数据中统计而来,对于句子的所有可能隐藏状态序列,穷举复杂度太高,可以使用viterbi算法求解。另外,基于HMM的中文分词方法脚本已经上传至我的github,包括生成初始概率矩阵、状态转移矩阵、发射概率矩阵的脚本,为了方便后续使用HMM训练专有领域数据,并结合jieba使用,这里生成的三个矩阵结果文件跟jieba调用文件一致。","tags":[{"name":"NLP","slug":"NLP","permalink":"http://yoursite.com/tags/NLP/"},{"name":"分词","slug":"分词","permalink":"http://yoursite.com/tags/分词/"}]},{"title":"分词-基于字符串匹配","date":"2019-01-06T01:20:19.062Z","path":"posts/2019/01/06/基于字符串匹配分词.html","text":"  分词是NLP领域基础但极其重要的功能,作为任务的上游,其分词质量直接影响了下游任务的性能(虽然现在流行end-to-end了)。分词方法主要有三大类:基于字符串匹配的分词方法、基于理解的分词方法和基于统计的分词方法。 第一种将指定长度的子串与准备好的词典匹配,如果匹配成功则返回,第二种是在分词的时候同时进行句法和语义分析,第三种是使用统计方法对已分词的样本训练学习其规律从而实现对未知文本的切分,在使用时基本会结合第一种方法一起使用,该方法也是目前的主流方法。本文将重点介绍基于字符串匹配的分词方法,字符串匹配是分词方法中最简单的分词方法,该方法严重依赖于词典,无法识别未登录词以及歧义处理能力不佳。常用的字符串匹配方法有以下几种: 最大正向匹配法 最大逆向匹配法 双向最大匹配法 最大正向匹配  该方法核心思想是,设定词语的最大匹配长度m(往往是词典中词的最大长度),从左往右取待切词的句子m个字符,在词典中查找是否存在这m个字符,如果存在则匹配成功,如果不存在则将这m个字符的最后一个字符去掉,重新从词典中查找,直到找到为止。 最大逆向匹配法  与最大正向匹配相反,最大逆向匹配算法首先设定词语的最大匹配长度m(往往是词典中词的最大长度),从右往左取带切词的句子m个字符,在词典中查找是否存在这m个字符,如果存在则匹配成功,如果不存在,则将这m个字符的最左边一个字符去掉,重新从词典中查找,直到找到为止。一般情况下逆向匹配结果比正向匹配结果更为准确。 双向最大匹配法  双向最大匹配法是将最大正向匹配的分词结果和最大逆向匹配的分词结果结合起来比较,一般会遵循以下原则: 分词粒度越大越好(根据算法性质,如果词存在与词典中的话,会优先匹配颗粒度最大的词,如中华人民共和国,如果拆分成中华/人民/共和国,会产生歧义) 非词典词越少越好 总体词数越少越好本文实现思路是: 优先正反向总体分词词数最少的结果; 如果正反向分词结果词数相同,则选单个词在词典中数量较多的结果; 如果单个词在词典中的数量一致,则从正反向中随机选择一个结果。 总结字符串匹配分词原理非常简单,因为只是简单的匹配,效率自然也是贼高。分词效果一般是:双向>逆向>正向。","tags":[{"name":"NLP","slug":"NLP","permalink":"http://yoursite.com/tags/NLP/"},{"name":"分词","slug":"分词","permalink":"http://yoursite.com/tags/分词/"}]},{"title":"基于牛顿法优化逻辑回归损失函数","date":"2018-09-01T06:47:52.521Z","path":"posts/2018/09/01/Newton's_method_1.html","text":"  牛顿法,又称为牛顿-拉佛森方法,最开始是用来求解高次方程的根,其核心思想就是利用迭代点在曲线上的切线不断逼近曲线的根,直至收敛。我们知道,连续可微曲线的最值可以通过对曲线进行求导,并令导数为0来求解,所以牛顿法也可以作为一种最优化方法求最值。如果是把最优化(最小值)比作从山顶移动到山脚的过程,之前在文章梯度下降法有提到,梯度下降法是选择往相对当前位置来说最陡峭的地方向下移动,而牛顿法的目光会相对长远一点,牛顿法在选择方向的时候,不仅会考虑当前这一步是否是最陡峭的,还会结合下一步一起考虑,就像下象棋能够看到走完这一步之后下一步还应该怎么走才是最有利的,这是因为牛顿法是二阶收敛,梯度下降法是一阶收敛的。关于牛顿法二阶收敛的证明请看这里,根据收敛的方式,牛顿法的收敛速度显然要快于梯度下降算法。 原理  其实在上大学的时候有学过牛顿法,但是作为数学系一名不折不扣的学渣,当然是考完就忘啦。关于牛顿法的原理,强烈推荐马同学在知乎上的这个回答如何通俗易懂地讲解牛顿迭代法求开方? 行文流畅,脉络清晰明了,一看就懂,总结起来就是: (1)牛顿法首先随机找一个初始点$x_0$,作为迭代点; (2)计算函数$f(x)$ 在改点的切线,将该切线与$x$轴的交点作为新的迭代点,其更新方式为$$x_{n+1} = x_n - \\frac{f(x_n)}{f’(x_n)}$$ (3)重复步骤(2),直至找到函数的根或者满足指定的迭代条件。 牛顿法应用下面来看下,牛顿法是如何对逻辑回归的损失函数进行优化的。上面提及的迭代更新方式$$x_{n+1} = x_n - \\frac{f(x_n)}{f’(x_n)} \\qquad (1)$$ 是求函数的根的,也就是求解$f(x)=0$。而求曲线的最值,是令曲线导数等于0来求解,所以,逻辑回归损失函数的优化目标应该是$f’(x) = 0$。举个例子,对于函数$f(x) = (x+2)(x-2)(x-4)$,基于(1)进行迭代,其实就是求解$f(x)$的根,也就是下图的$x_0,x_1,x_2 $三个点。 但这不是我们的目的,我们的目的是要求该图中的点 $ x_3, x_4 $,这个好办,只要对$f(x)$求导,并求$f’(x) =0$ 的解就可以了,也就是下图的点 我们在函数$f’(x)$随机找一个点,作该点在$f’(x)$的上切线,将该切线与$x$轴的交点作为新的迭代点,那么更新方式就变为(也可以通过二阶泰勒展开式直接得出):$$x_{n+1} = x_n - \\frac{f’(x_n)}{f’’(x_n)} $$ 逻辑回归的方程为:则其对应的似然损失函数为: 求损失函数的一阶导,有由一阶导组成的矩阵,就是雅可比矩阵,记为$\\nabla$对应的二阶导为: 由二阶导组成的矩阵,就是Hessian矩阵,记为$H$,所以,损失函数优化迭代的方式为:$$\\theta \\leftarrow \\theta -H^{-1}\\nabla $$","tags":[{"name":"机器学习","slug":"机器学习","permalink":"http://yoursite.com/tags/机器学习/"},{"name":"最优化","slug":"最优化","permalink":"http://yoursite.com/tags/最优化/"}]},{"title":"为什么R方是解析变量的非减函数","date":"2018-09-01T06:45:09.118Z","path":"posts/2018/09/01/R-Square.html","text":"  在回归模型中,决定系数$R^2$表示反应变量$y$的总变异中可由回归模型中自变量解释的部分所占的比例,它是衡量所建立模型效果好坏的评价指标之一。根据$R^2$的计算方法,显然$R^2$越大越好,但是有一点需要注意的是,向模型中增加变量会导致$R^2$增大,或者至少保持不变,这就会造成一种假象,只要我不断地向模型中增加变量,$R^2$会越来越大,模型效果貌似越来越好,即使所增加的变量对于目标变量来说没有任何意义。为什么会这样子呢,下面从理论证明之。 预备知识先把$R^2$的公式丢上来:$$R^2 =\\frac{ESS}{TSS} =1 - \\frac{\\sum{(y-\\hat{y})^2}}{\\sum{(y-\\bar{y})^2}}$$  其中,$y$是样本真实值,$\\bar{y}$ 是样本均值$\\frac{\\sum_{i=1}^m{y_i}}{n}$,$\\hat{y}$是预测值。乍一看好像$R^2$的大小跟自变量$X$没有半毛钱关系,只跟实际$y$与预测$\\hat{y}$值有关,其实事情并没有那么简单。对于线性回归模型$$y = X\\beta + \\mu$$其中$ X = (x_0,x_1,x_2,…,x_n) $ ,n为变量个数。 $\\mu$ 为回归模型随机误差。 我们一般会通过最小二乘法(OLS)来估计未知参数$\\beta$,也就是将线性回归模型的残差平方和(sum of squared residuals,SSR)作为损失函数来优化,如下: 显然,残差平方和越小越好,我们的目标就是找到一组参数$\\beta$,使得残差平方和最小,这时拟合的曲线是最好的。求一个函数的最值,顺手一个求导并令导数等于0,就可搞定。 $$\\min SSR(\\beta) = min \\sum_{i=1}^m{(y_i - X_i \\hat{\\beta})^2} $$求关于$\\beta$的导数,并令导数等于0 根据上面公式,可以得到OLS估计量满足一阶条件$-2X(y-X \\hat{\\beta})^T = 0$,从而有$X\\mu^T = 0$   证明有了上面的知识点,证明起来就非常简单了。设线性回归模型$y = X \\beta + \\mu $  (1)的决定系数为:$$R_1^2 =1 - \\frac{\\sum{(y-\\hat{y_1})^2}}{\\sum{(y-\\bar{y})^2}}$$在原有模型基础上,增加一个变量,线性回归模型变为$y = X_0 \\hat{\\beta}_0+X \\hat{\\beta} + \\nu $  (2),对应的决定系数为$$R_2^2 =1 - \\frac{\\sum{(y-\\hat{y_2})^2}}{\\sum{(y-\\bar{y})^2}}$$现在需要证明$R_2^2 \\geqslant R_1^2$   证明:根据$R^2$的计算公式,可以推导出 因此,只要证明$\\mu^T \\mu \\geqslant \\nu^T \\nu $ 即可。 由变量之间相互独立,以及OLS的一阶条件推导出的$X\\mu^T = 0$,所以有$\\mu^T = X_0 \\nu^t = X \\nu^T = 0$将(1)和(2)式合并起来:$$ X \\beta + \\mu = X_0 \\hat{\\beta}_0+X \\hat{\\beta} + \\nu $$上式两边乘以$\\mu^T$ ,有 相似地,两边乘$\\nu^T$,有 结合上面两个式子,有 为了方便后面公式展示,我这里先令 对$\\nu^T \\nu$进行推导,如下: 因此有 证明完毕。 总结由证明可知,用$R^2$评价回归模型拟合效果具有一定的局限性,$R^2$越大并不能代表模型效果越好,针对此问题,出现了校正的决定系数(公式如下),相对于$R^2$,当向模型中增加相对于目标变量无意义的自变量时,校正的决定系数值会减小。当然,稍微看下校正的决定系数计算公式就知道,问题还是存在的,就是当建模的样本量远远大于自变量个数的时候,$\\bar{R^2}$趋近于$R^2$,此时校正的效果几乎消失。$$\\bar{R^2} = 1 - (1-R^2)\\frac{n-1}{n-p-1}$$","tags":[{"name":"机器学习","slug":"机器学习","permalink":"http://yoursite.com/tags/机器学习/"},{"name":"效果评估","slug":"效果评估","permalink":"http://yoursite.com/tags/效果评估/"}]},{"title":"聚类算法使用小结","date":"2018-09-01T06:39:59.980Z","path":"posts/2018/09/01/unsupervised.html","text":"  在实际的工程应用中,无监督聚类算法的使用频率相对于有监督分类预测算法的使用频率并不高,我们在开展业务过程中,很多时候,会很明确需要挖掘哪种类型的目标客户,比如是否会购买产品、是否潜在流失、是否投诉用户、是否欺诈用户等等。有时候也会有客户直接丢一堆无标签的数据过来,让你分析下这坨数据有什么特征,或者客户对他们产品的使用用户都是由哪些群体构成的并不清晰,无法针对性的做营销,这时候就可能需要用到聚类模型去对数据、用户群进行划分了。   聚类算法和分类预测算法的建模流程大同小异,都包括前期数据清洗、特征工程、模型构建、效果评估,由于聚类算法没有label作为参照物,在特征工程、效果评估上面会相对分类预测模型就显得更加麻烦、更有意思了。现在,对项目中经常用到的一些聚类算法做个简单的总结。 k-means原理   k-means 可以说是最简单的聚类算法了(但是看sklearn里面k-means的源码实现发现事情并不简单orz)。算法首先随机选取k个质心,对每一个样本,分别计算该样本到这K个质心的距离,然后将该样本归到距离最短的簇中。将簇中所有样本的均值作为新的质心,再将每个样本分配到最近的簇中,一直迭代直至达到指定的迭代次数或者质心不在发生变化时,算法结束。 优点 简单直接高效 收敛贼快(业界用的多不是没有原因的) 结果解析性较强(效果好的情况下) 缺点 基于样本中心作为质心注定了k-means会对异常值、噪声敏感 追求类内平方和最小也决定了其只能处理凸型数据,对复杂形状的数据无能为力,大多数时候聚类结果还是比较粗糙的。 无法处理离散型数据 需要指定聚类的个数(很多时候并不知道数据应该分成几类才是最好的) 需要指定初始点(初始点的选定对聚类结果影响较大) sklearn 调参sklearn 里的kmeans 比较重要的参数有两个,n_clusters(聚类个数) 和 init(质心初始化方法)。n_clusters的选定,业务跟数据结合着来会比较好,或者你觉得这个数据大概可以分成10个类,可以从5-15类都试下,挑个效果最好的。但有时效果最好的其业务解析性并不一定好,还有的时候将数据分成6类效果是最好的,但业务部门说了我就要5类…init 的值其实对聚类结果影响蛮大的,初始值选的不好可能无法得到有效的聚类结果。kmeans 对init的初始化有三种方法:random,k-means++,或者传入一个ndarray向量,默认是使用k-means++方法来初始化质心,其核心思想是:初始化的聚类中心之间的相互距离要尽可能的远。一般默认就行。 凝聚聚类原理  凝聚聚类是一种自低向上的聚类方法。首先将每个样本当做一个聚类,然后合并距离最近或者最相似的两个类,直到满足某种停止准则未知。最核心的点是在如何衡量两个类的距离或者相似度。找了些相关资料,发现定义相似度的方法还是蛮多的,这里列举几个。 单链,两个不同的簇中,离得最近的两个点之间的距离,取距离值最小的两个簇进行合并,即MIN() 全链,两个不同的簇中,离得最远的两个点之间的距离,取距离值最小的两个簇进行合并,即MAX() 平均连,每个簇中所有点之间的平均距离,取点平均距离值最小的的两个簇进行合并,即AVERAGE 方差,将簇中方差增加最小的两个簇进行合并(sklearn 中的ward) 优点 能够处理具有复杂形状(非凸型)的数据 无需指定聚类的个数(sklearn 中的 AgglomerativeClustering 将聚类的数量作为算法的停止准则) 缺点 每次只合并两个簇,计算复杂度高,不适用于大数据量的聚类 只能基于已有的数据聚类,无法对新的数据进行预测 DBSCAN原理DBSCAN(density-based spatial clustering of applications with noise),翻译过来就是:具有噪声的基于密度的空间聚类应用。DBSCAN能够根据数据中的密度识别密集区域,并将密度高的区域划分为簇,这些簇由数据中相对密度相对较低的区域分隔开。 优点 无需指定聚类的个数 可以处理复杂形状的数据 抗噪声,能够识别噪声点缺点 簇之间的密度不均匀时,聚类效果可能不好。(比如某个簇,密度较大,另一个簇密度较稀疏,当调大邻域内最小样本点时,密度较稀疏的簇会变化较快。) 跟凝聚聚类一样,无法对新的数据进行预测sklearn 调参该算法在大多数数据集上面都能够获得不错的效果,但是调参过程有时非常坎坷。关键是目前对复杂形状的聚类评估效果并不理想。 eps min_samples 高斯混合聚类原理算法假定每个聚类的簇都符合高斯分布(正太分布),样本数据呈现的分布就是各个聚类的分布的叠加,所以称为高斯混合。该算法首先指定高斯混合成分个数K(这里K就是要聚类的个数),随机给每一个分布的均值和方差(协方差)赋初始值。对每一个样本,计算其在各个高斯分布下的后验概率(EM中的E步),在根据最大似然估计,将每个样本对该高斯分布的概率作为权重来计算加权均值和方差(协方差)(EM中的M步),用更新之后的值替换原来的初始值,直至模型满足停止条件(比如迭代次数),算法结束。 优点 只要给定的成分个数足够多,理论上可以任意逼近任何连续的概率分布 缺点 计算量较大 其实真实数据好多都是不服从正太分布的,对于一些复杂数据出现偏差的可能性还是挺高的。 MeanShift原理MeanShift是一种非参数聚类算法,无需指定聚类的数目。主要涉及两个概念Mean(均值)、Shift(偏移)。其算法思想很简单,算法首先随机选取初始迭代点$x$,将该点到附近区域内所有点分别组成向量求和取平均偏移量,移动该点$x$至平均偏移向量末端,作为新的迭代点,不断移动,直至满足指定条件,算法结束。 调参MeanShift这个算法有稍微尝试了下,但是好像效果相对其他聚类算法不突出,用的不多 高维可视化PCA、TSNE  很多时候,即使做了前期的业务了解和数据探索,你也很难判断应该选择哪种聚类模型。如果每个模型都试一遍的话,时间成本未免太高,而将高维数据可视化,能够为我们选择聚类模型提供一些指导性意见。数据可视化首先是将高维数据降到低维(一般二维),然后基于低维数据进行可视化,常用的方法有两种:PCA 和TSNE。  PCA是一种被广泛应用的数据压缩、数据降维方法,该方法以方差最大的方向作为坐标轴方向对数据旋转以保留主要信,旋转后的特征在统计上不相关。这里以sklearn里的iris数据集为例,数据属性为:花萼长度、花萼宽度、花瓣长度、花瓣宽度 四个特征,将这四个特征标准化处理后用PCA降维得到下图。 PCA在旋转时没有用到任何类别信息,降维后的数据相对于原数据其实相当于新的数据变换,一般很难对图中的两个轴做出解析。有一类用于可视化的算法称为流行学习算法,TSNE是其中的一种,降维效果奇好,其思想是找到数据的一个二维表示,尽可能地保持数据点之间的距离。让在原始特征空间中距离较近的点更加靠近,原始特征空间中相距较远的点更加远离。t-SNE 重点关注距离较近的点,而不是保持距离较远的点之间的距离。换句话说,它试图保存那些表示哪些点比较靠近的信息,这里同样使用iris数据进行tsne可视化。 tsne只能应用于训练的数据,不能应用于测试集,并且在处理稍大数据量的时候效率很低,如果要聚类的数据比较大,可以考虑抽样可视化。如果数据形状复杂,这时候基于指标的效果评估并不好,可以考虑基于抽样数据聚类,然后基于聚类结果建立分类模型。这里pca和tsne可视化iris数据的效果区分度不是很明显,其实从使用经验来看,在数据量在千以上的情况下,同一份数据使用tsne进行可视化,数据的区分度比pca要好很多,这是因为pca需要对数据进行标准化处理,使得大部分数据都集中在一个很小的范围内。但是数据量越大tsne的处理速度越慢,很无奈。 无监督特征选择  无监督机器学习模型的特征选择,sklearn上还没有相关的API,其实挺麻烦的,应该使用何种评判标准来认为这个特征是对聚类算法有效的?网上有一些方法,比如基于遗传算法、模式相似性判断、复相关系数之类的,也找了几篇论文,看到一篇讲到可以参考有监督学习的wrapper方法进行特征选择,感觉还蛮有意思的,参考着实现了下,用起来效果也还OK。 效果评估  聚类结果的效果评估,方法还是挺多的,有分有标签的评价指标和无标签的评价指标两类。有标签的评价。比如,这些方法的核心都基本一样,就是将类,尤其是针对任意形状的簇,很多时候,你用某个指标评估聚类效果,如果指标值非常好,那说明模型用对了,如果指标值表现很差,那很有可能是评估方法选错了。 总结“所有模型都是错的,但有些模型是有用的”","tags":[{"name":"机器学习","slug":"机器学习","permalink":"http://yoursite.com/tags/机器学习/"},{"name":"聚类","slug":"聚类","permalink":"http://yoursite.com/tags/聚类/"},{"name":"无监督监督学习","slug":"无监督监督学习","permalink":"http://yoursite.com/tags/无监督监督学习/"}]},{"title":"softmax","date":"2017-09-03T07:35:43.317Z","path":"posts/2017/09/03/softmax.html","text":"  上一篇讲了逻辑回归的由来,在分类问题上,逻辑回归是一个二分类模型,而实际的项目中有可能需要处理一些多分类的问题(比如经典的MNIST),这时候如果使用二分类模型去处理多分类问题,会相对麻烦,这里介绍一种处理多分类问题的简单模型-softmax。softmax相当于做了一个归一化的工作,让强者相对更强,弱者相对更弱。 模型推导  softmax模型是逻辑回归模型的推广,逻辑回归模型是softmax模型的一个特例,这两者都算是广义线性模型(Generalized Linear Model,GLM)。广义线性模型假设在给定属性$x$和参数$\\theta$之后,类别$y$的条件概率$p(y|x;\\theta)$服从指数分布族,它是长这样子的:$$P(y;\\eta)=b(y)exp(\\eta^TT(y) - a(\\eta))$$下面就是根据类别的联合分布概率密度搞成上面公式的样子。假设现在有$k$种分类$y \\in (1,2,…,k)$的数据,对于每一条观测(样本)都有一个对应的类别,假设每种分类对应的概率是$(p_1,p_2,…,p_k)$,那么所有类别的概率之和就是: 则第$k$类的概率也可以写成: 对于多分类,可以把类别写成编码向量的形式,向量的第$i$个位置为1,表示第$i$个类别,向量的其他位置为0。比如有5个分类,类别1可以表示成$(1,0,0,0,0)$,类别2可以表示成$(0,1,0,0,0)$。因为第$k$类可以用前$k-1$类表示,所以可以用$k-1$维向量$T(y)$表示类别。 用函数$\\mu(y=i)=1$表示第$y=i$为真,当$y=i$为假时,有$\\mu(y=i)=0$。这样就可以将所有了类别的概率整合起来:$$P(y;p)=p_1^{\\mu(y=1)}p_2^{\\mu(y=2)}…p_k^{\\mu(y=k)}$$因为第$k$类可以用前$k-1$类表示,所以上面的式子可以写成: 当$y=1$时,$P(y;\\eta)=p_1$,当$y=2$时,$P(y;\\eta)=p_2$,以此类推。再用向量$T(y)$表示,就变成了: 将上面式子的右边,写成下面这种形式: 也就是先取对数,再将结果作为$e$的指数(整个推导过程中,最关键的技巧就是在这里了),然后稍微做下推导,就变成这样子: 然后,另:$b(y)=1$,以及$a(\\eta)=-lnp_k$,还有$\\eta$: 上面的公式就可以写成(终于凑成了指数分布族的形式): 由于 因为 所以 所以: 再根据$p_i=p_ke^{\\eta_i}$,可以得到: 再令$\\eta = w^Tx+b$,所以对于类别$i$,其概率可以表示成:   softmax模型推导到这里已经算是结束了。  我们再来看看,为什么说softmax相当于做了一个归一化的工作,让强者相对更强,弱者相对更弱。先看归一化,很简单,分母其实就是将分子从$1$到$k$累加起来,所以对于任意的$p_i$都有$p_i \\in [0,1]$。对于后者,softmax相当于做了一个拉大差距的工作,假设已经算好$w^Tx+b$就是以下几个值$[5,4,3,2,1]$,根据公式计算出来的对应的softmax值为$[0.636, 0.234, 0.086, 0.032, 0.012]$,可以看到,各个数之间的相对差距拉的更远了,所以softmax能够很好地凸显较大的数值。 似然函数  像上篇逻辑回归一样,继续使用对数似然函数去估计参数。对于单个样本,当然是命中样本实际类别的概率越大越好。 这里$x^{(i)}$表示第$i$个样本,相对应的,$y^(i)$表示第$i$个样本对应的类别。同理,对于所有的样本,也是希望命中样本实际类别的概率越大越好,然后再取个对数(这里为了方便书写公式,将$w^Tx+b$写成$\\theta^T x$),这样就有: 最优化  搞定了代价函数,剩下的工作就是求解参数了,一般可以使用梯度下降(上升)或者是牛顿迭代法来求解,这里以梯度下降(转化成求极小值)为例(梯度上升的话就是直接求解上面公式的极大值)。  因为公式会写的比较长,这里为了方便,将$w^Tx+b$写成$\\theta ^Tx$的形式(都毕业了,还要把微积分捡回来,也是蛋疼)。  首先是求导,在求导之前,先放两个需要用到的公式,这样整个求导过程会更加清晰。对于对数和分数的求导有下面两个公式: 对第$l$类的参数$\\theta _l$进行求导,所以就会有: 然后通过梯度上升更新参数: 这里的$\\alpha$为学习率。","tags":[{"name":"机器学习","slug":"机器学习","permalink":"http://yoursite.com/tags/机器学习/"},{"name":"监督学习","slug":"监督学习","permalink":"http://yoursite.com/tags/监督学习/"},{"name":"对数线性模型","slug":"对数线性模型","permalink":"http://yoursite.com/tags/对数线性模型/"},{"name":"广义线性模型","slug":"广义线性模型","permalink":"http://yoursite.com/tags/广义线性模型/"},{"name":"多分类模型","slug":"多分类模型","permalink":"http://yoursite.com/tags/多分类模型/"}]},{"title":"逻辑回归","date":"2017-09-02T02:11:02.897Z","path":"posts/2017/09/02/logistic.html","text":"  逻辑回归(Logistic Regression)是一个非常经典的回归模型,在神经网络里作为激活函数时也称为sigmoid函数。逻辑回归模型既可以处理线性分类问题也可以处理非线性分类问题,但其本质上是一个线性模型。作为机器学习入门级模型,该模型既简单又强大,在现实中有着广泛应用,比如国外成熟的信用卡评分模型就是基于逻辑回归模型建立的。 模型推导  线性模型试图通过属性间的线性组合来预测目标值,它的模型形式是这样的:$$y=w_1x_1+w_2x_2+…+w_dx_d+b$$其中$x_i, i\\in (1,2,…,d)$表示$x$在第$i$个属性上的取值。上式表示成向量的形式就是:$$y=w^Tx+b$$其中$w=(w_1;w_2;…;w_3)$。  在二维平面上,线性回归模型,其实就是一条直线,再来看下另外一个函数$y=e^x$。不难看出对数函数和线性函数都是单调的,那么肯定可以找到一个映射函数使得对于直线上的任意一个点$y=w^Tx+b$,都能在函数$y=e^x$上找到唯一的一个点与之相对应。对于函数$y=e^x$不妨令:$$x \\leftarrow w^Tx+b$$则有$y=e^{w^Tx+b}$,两边取对数之后,就有$lny=w^Tx+b$。 给定数据集$D= \\{ (x_1,y_1),(x_2,y_2),…,(x_m,y_m) \\} $,其中 $y_i \\in \\{0,1\\}$。我们可以用概率$p,p \\in [0,1]$来表示样本$x$被判为$y=1$时的可能性,$p$值越大,代表$x$被归为正样本$y=1$的可能性越大,也就是$p(y=1|x)$的概率为$p$,那么对应的$p(y=0|x)$的概率就是$1-p$,这两者的比值$$\\frac{p}{1-p}$$称为“几率”也有些家伙会称它为“优势比”。这个时候,我们再回到取对数之后的等式:$lny=w^Tx+b$,令$y=\\frac{p}{1-p}$,可以将等式转化为:$$ln\\frac{p}{1-p}=w^Tx+b$$ 同时将等式两边的值作为指数,以$e$为底,可以得到:$$\\frac{p}{1-p} = e^{w^Tx+b}$$ 稍微处理一下就可以推导出经典的逻辑回归模型:也就是说,对于$y=1$,我们有$$p(y=1|x)=\\frac {e^{w^Tx+b}}{1+e^{w^Tx+b}}$$对于$y=0$,我们有$$p(y=0|x)=\\frac {1}{1+e^{w^Tx+b}}$$为了看的更舒服一点,令$z=w^Tx+b$,分子分母再同时除以$e^z$,则模型写成以下形式:$$f(z)=\\frac {1}{1+e^{-z}}$$上面的等式其实是一个单调“S”形函数,$f(z)$的取值在$(0,1)$之间,当$z$取值越大时,$f(z)$越接近于1,反之,当$z$取值越小时,$f(z)$越接近于0,当$z=0$时,$f(z)=0.5$。其工作方式是,当$z>0$时,对应的样本被判为正类$y=1$,当$z0$,对于所有的$y=0$有$f(x_1,x_2)","tags":[{"name":"机器学习","slug":"机器学习","permalink":"http://yoursite.com/tags/机器学习/"},{"name":"分类模型","slug":"分类模型","permalink":"http://yoursite.com/tags/分类模型/"},{"name":"监督学习","slug":"监督学习","permalink":"http://yoursite.com/tags/监督学习/"},{"name":"对数线性模型","slug":"对数线性模型","permalink":"http://yoursite.com/tags/对数线性模型/"},{"name":"广义线性模型","slug":"广义线性模型","permalink":"http://yoursite.com/tags/广义线性模型/"}]},{"title":"支持向量机(三)","date":"2017-09-02T01:47:45.236Z","path":"posts/2017/09/02/SVM3.html","text":"  硬间隔支持向量机要求训练数据是完全线性可分的,否则会宕机(无法工作);软间隔支持向量机则允许很少量的样本线性不可分,模型的容错能力相对前者好一点;但是,对于非线性问题应该如何处理呢,怎么用一个平面将二维平面中的两个不同半径的同心圆分隔开,怎么将高维线性不可分的图形分隔开,在实际应用中,遇到非线性问题(大部分都是),如何用SVM解决?这就需要引入核函数的概念了。 核函数定义李航在《统计学习方法》中对核函数的定义如下: 核函数原理先上总结:简单来说,核函数就是内积,或者说是用来计算映射到高维空间之后的内积的一种简便方法。我们现在来细看下,核函数到底是个什么东西。在支持向量机(一) 中,我们已经推导出来,分类函数为: (1)其中,$x_i^Tx$其实就是向量内积,可以用$<·,·>$表示。继续开篇提的问题,如下图,怎么用一条直线将二维平面中的两个不同半径的同心圆分隔开?设上面的点用$x=(x_1,x_2)$ 表示,如果直接基于分类函数(1)找一个平面将两个圆分割开,这样肯定不行,因为在二维平面上无论怎么调整,你都无法找到一条直线完好地区分上图中的两个圆。但是换个角度看可能就不一样了,我们把它映射到三维空间看一下。 通过将数据从二维平面映射到三维空间,两个无法线性分隔的图形变成线性可分的了。这里使用的映射函数是$\\phi(x)=(x_1^2,\\sqrt{2}x_1x_2,x_2^2)$。 再看下面这个例子,如何用一条直线将两类不同颜色的点分开,显然在二维平面上无论怎么划分都无济于事。类似的方法,可以将这些点映射到三维空间,然后用一个平面将这两类数据轻易地区分出来。这里使用的映射函数是$x=(x1,x2,x1*x2)$上面两个例子,套路都是:本来非线性的数据,找到一个映射函数,将其从低维映射到高维,然后在高维空间用线性分类方法将数据分割开来。到目前为止,推理起来都比较顺畅,现在针对上面的例子做一些延伸,针对一个二次曲线,可以表示成:$$a1X_1 + a2X_1^2 + a3X_2 + a4X_2^2 + a5X_1X_2 + a6 = 0$$构造一个五维空间,其中五个坐标的值为$$Z1 = X_1;Z2 = X_1^2 ;Z3 = X_2;Z4 = X_2^2 ;Z5 = X_1X_2$$如果是一个三维曲线,那么需要构造一个19维的空间,可以看到,这个映射空间的维度会随着原空间维度的增加而成指数增长,这给会给$\\phi(\\cdot)$的计算带来了非常大的困难,而且如果遇到无穷维的情况,就根本无从计算了。更何况,映射到高维空间数据是否就线性可分还不一定呢。如果我先在低维空间计算好两点之间的内积呢,这样就无需先映射到高维空间找映射函数,然后在根据映射函数计算内积了。针对第一个例子,设曲线上的任意两点为$x=(x_1,x_2),y=(y_1,y_2)$,其映射函数为$\\phi(x)=(x_1^2,\\sqrt{2}x_1x_2,x_2^2)$,那么映射函数计算内积后其实是跟$ x\\cdot y $ 的平方的结果一样的,对应的核函数为。 这样的话,针对三次曲线方程或者更高维曲线方程,无需先映射到高维空间,可以直接将两点之间的内积代入分类函数求解未知参数。这种直接在低维空间中计算内积的方法就称为核函数或者核技巧。   核函数真正强大之处在于,只需要定义核函数在低维空间进行内积计算,而不需要显式地定义映射函数,这对本来就是高维或者无限维的特征空间来说尤为有效。另外,映射函数并不是唯一的,比如还是回到第一个同心圆的例子,映射函数可以是$\\phi(x)=(x_1^2,\\sqrt{2}x_1x_2,x_2^2)$,也可以是$\\phi(x)=(x_1,x_2,x_1^2+x_2^2)$  网上其实有很多关于SVM、核函数的讲解和讨论,关于讲解可以参考支持向量机通俗导论(理解SVM的三层境界)讲得是真心好。关于讨论可以参考知乎上的一个问题:机器学习有很多关于核函数的说法,核函数的定义和作用是什么? 核函数类型sklearn 中,可选的核函数有以下四种: 线性 : 多项式: RBF : sigmoid: 后记  核函数的作用其实很简单,一言蔽之,就是计算映射到高维空间之后的内积的一种简便方法或者技巧,它能够将原本需要在高维空间计算的内积直接在原特征空间进行,无需变换空间。   说实话,写的这几篇文章中,核函数这篇是最耗时的,写到这里,感觉我对kenel trick 的理解还是很懵懂,不深刻,比如kenel 可以往那些方面进行推广、在实际应用中应该怎么选择kenel。写本文的时候,希望能够尽量通俗易懂地把它写明白,但是通俗易懂的前提是自己对这些概念的理解要很深入,所以在写的过程中出现过几次定好思路,写了大部分的时候发现不理想,重新整理的情况。不过从应用层面来说,了解SVM 和核函数的基本概念原理,知道模型是怎么运作的,在遇到问题的时候能够定位问题,并根据模型、方法的特性进行恰当的调整已经基本够用了。其他的再漫漫求索吧。 ps: hexo渲染公式有时出个bug真能逼死强迫症。","tags":[{"name":"机器学习","slug":"机器学习","permalink":"http://yoursite.com/tags/机器学习/"},{"name":"分类模型","slug":"分类模型","permalink":"http://yoursite.com/tags/分类模型/"},{"name":"监督学习","slug":"监督学习","permalink":"http://yoursite.com/tags/监督学习/"}]},{"title":"支持向量机(二)","date":"2017-09-02T01:46:27.994Z","path":"posts/2017/09/02/SVM2.html","text":"  上一篇对线性支持向量机的原理做了推导,这个推导的前提跟感知机模型一样,假设训练样本是线性可分的,也就是存在一个超平面能够将正负样本完全分隔开,但是这种假设在实际项目中存在的可能性跟国足挺进世界杯的可能性是一样一样的。 软间隔  上一篇文章中提到的间隔,严格来说应该称之为“硬间隔”,因为该间隔存在的前提是所有训练样本都要满足约束条件$y_i(w^Tx_i+b) \\geq 1$,但是对于下图中的两类样本点,线性支持向量机并不适用,因为没办法在不映射到高维的情况下找到一条直线能把两个类别分隔开。   如果使用感知机的话,模型会无法收敛,并且找到的超平面有可能效果还可以也有可能很差。上图两个类别之间,混淆的样本点其实不多,总不能因为几颗芝麻就丢了西瓜吧。那么有没有办法使得支持向量机“容忍”这些不多的样本点出错,而保证绝大部分的样本点被正确分类呢,其中一个办法就是使用“软间隔”,允许某些样本不满足约束条件$y_i(w^Tx_i+b) \\geq 1$,也就是让某些样本点满足条件$y_i(w^Tx_i+b) < 1$ 软间隔最大化  为了获得最佳的划分超平面,还需要找出对应的损失函数来衡量划分超平面的效果。一方面,对于支持向量及以外的点,可以继续使用上一篇介绍的间隔最大化$\\frac{2}{||w||}$来评估,也就是$min \\frac{1}{2}||w||^2$;另一方面,对于支持向量内的点,没办法满足间隔大于等于1的约束条件,这种情况下,可以考虑引入松弛变量$\\xi _i$ (其中$ \\xi _i \\geq 0$),使得函数间隔在加上松弛变量之后大于等于1,也就是:$$y_i(w^Tx_i+b) + \\xi _i \\geq 1 $$将$\\xi _i$挪到右边:$$y_i(w^Tx_i+b) \\geq 1 -\\xi _i $$  松弛变量可以理解为一种作用力,将误分类的点往正确分类的方向拉,拉回到对应的支持向量上。误分类的点与正确分类的边界越远,需要拉扯的力量越大,也就是$\\xi_i$越大,对于支持向量后方的点,$\\xi_i$值可以为0。  然后,同时对于误分类的点,给它一个惩罚因子(cost),用字母$C$表示($C \\geq 0$),用来衡量误分类的代价,或者说,对离群点的重视程度(其尝试找出,在出现误分类的情况下,边界间隔最大的超平面以及保证数据点偏差量最小),这样损失函数就变成了:   这里的损失函数相对上篇只是多了后半部分。显然,对于惩罚因子$C$,当$C$无穷大时,表明只要有一个样本点是特异点的话就需要付出无限的代价,上面式子趋向于无穷大,这时将会使得所有的样本点都满足式子$y_i(w^Tx_i+b) \\geq 1 $,这样的话软间隔最大化问题就变成硬间隔最大化问题(或者说是线性支持向量问题),但是由于给定的数据不是线性可分的,这样就会导致问题没有解;当$C$取有限值时,上式允许存在一些的样本不满足约束。  加上约束条件,目标函数变成如下凸二次规划问题: 跟支持向量机(一)中,目标函数的求解方式一样,可以通过拉格朗日乘子法得到对应的拉格朗日函数: 其中$\\alpha _i \\geq 0 , \\mu _i \\geq 0$是拉格朗日乘子。一样的套路,求偏导,令$L(w,b,\\alpha _i,\\xi _i,\\mu _i)$对$w,b,\\xi _i$的偏导为0,可得: 将上面三个等式代入拉格朗日函数可以得到(公式推理写的有点宽,就不在这里展示了): 与上一篇相似,上式同样需要满足KKT条件,也就是要求:   这里的$f(x_i)$其实就是$w^Tx_i+b$,通过KKT条件,我们能更加深入地了解SVM的本质–不管是硬间隔支持向量机还是软间隔支持向量机,最终根据模型求得的分隔超平面仅与支持向量有关。  对于上面KKT条件中的等式$\\alpha_i(y_i f(x_i)-1+\\xi _i)=0$,对于任意的训练样本,总会有$\\alpha_i = 0$或者$y_i f(x_i)-1+\\xi _i=0$,不妨把训练样本分为两种:支持向量以及支持向量以外的点,现在来分情况讨论下: $\\alpha_i=0$,这个时候$y_i f(x_i)-1+\\xi _i$等于任意值都是ok的,也就是$\\alpha_i=0$的时候,不会对样本有任何的影响。如上一篇提到的那样,在衡量目标损失的时候,对于非支持向量(也就是支持向量以外的点),为了满足损失函数最大化,$\\alpha_i$必定等于0。 $\\alpha_i> 0$,为了满足KKT条件,必然有$y_i f(x_i)-1+\\xi _i=0$,也就意味着,样本$(x_i,y_i)$是支持向量 再结合偏导的等式$C=\\alpha_i + \\mu_i$ 和KKT条件$\\mu_i \\xi_i=0$、$\\mu_i \\geq 0$。由于$\\mu_i$是大于等于0的,所以$\\alpha_i$只能小于或等于$C$: $\\alpha_i<C$,此时有$\\mu_i$大于0,因此$\\xi_i=0$,这个时候,则对应的样本$(x_i,y_i)$刚好落在最大间隔边界上面。 $\\alpha_i = C$,此时有$\\mu_i =0$,此时若$\\xi_i \\leq 1$,则对应的样本$(x_i,y_i)$落在最大间隔内部;若$\\xi_i > 1$,则样本$(x_i,y_i)$分类错误。引出核函数后记  软间隔支持向量机虽然比硬间隔支持向量机好那么一丢丢,对噪声的容忍程度比硬间隔支持向量机稍微强一点,但是用来处理现实复杂的数据,还是远远不够的。","tags":[{"name":"机器学习","slug":"机器学习","permalink":"http://yoursite.com/tags/机器学习/"},{"name":"分类模型","slug":"分类模型","permalink":"http://yoursite.com/tags/分类模型/"},{"name":"监督学习","slug":"监督学习","permalink":"http://yoursite.com/tags/监督学习/"}]},{"title":"支持向量机(一)","date":"2017-08-10T14:15:11.873Z","path":"posts/2017/08/10/SVM.html","text":"  支持向量机(Support Vector Machines, SVM)在实际应用中,算是一大分类神器了。原始的支持向量机是一种线性二分类模型,当支持向量机使用一些核技巧之后,可以从本质上变成非线性分类器。区别于感知机模型,线性可分支持向量机利用间隔(两个不同类的支持向量到超平面的距离)最大化求最优分离超平面,求得的解是唯一的。 感知机回顾  从感知机模型我们知道,感知机的目标是只要能找到一个将训练数据集的正样本和负样本完全正确分割开的分离超平面就可以了,并不要求样本能被最大限度地划分,但是这样的话会导致模型的泛化能力不强,比如下左图,直线Y能把A,B两类样本完全分割开,是感知机模型的一个解,如果我们用Y去预测未知的数据,如下右图,$a1,a2$两个数据点实际为类别A,但却被直线Y误判为B类,说明该直线对未知数据的泛化能力并不强。   直观上,下图中的红色直线的划分效果比其他直线的划分效果都要好,因为该直线恰好在两个类的中间,尽量“公平”地远离了两个不同的类别。但是,如何才能找到这样的一个超平面使之能最好地区分正负样本,并且对未见的数据的泛化能力也是最强的呢?这就是支持向量机要解决的问题。 什么是支持向量?  支持向量(Support Vector)的定义其实很简单,其实就是距离超平面最近的样本点,如下图中的$a1,a2,a3$三个点就称为支持向量。 为什么叫支持向量呢,因为所有的训练样本点其实都是以向量的形式表示的(尤其是当属性很多的时候),而上图的划分的超平面,仅仅由$a1,a2,a3$这三个点决定,与其他训练样本点一毛钱关系都没有(这也就是为什么SVM效率贼高的原因)!也就是说我们要找的划分超平面,其实是由这三个向量(vector)来支撑(support)的,因此我们称$a1,a2,a3$这三个点为Support Vector。so,只要找到支持向量,划分超平面也就找到了。 好了,知道了支持向量的概念,我们就可以按照模型→策略→算法 的步骤去求解SVM了。 模型  上面的分析我们已经清楚地知道,我们的模型(目标函数)其实就是一个超平面,这个超平面可以通过如下线性方程来描述:$$w^Tx+b = 0$$ 策略  有了模型,还需要有一个恰当的策略(损失函数)来评估模型的分类效果。前面有提到,我们要找的超平面,应该尽可能地离两个不同样本点都远,以保证泛化能力,而在空间中,衡量样本点到划分超平面的远近,很自然会想到用欧式距离来度量。样本空间中,任意一点$x$到超平面$w^Tx+b = 0$的距离可写为:$$d=\\frac{1}{||w||}|w^Tx+b|$$带上类别之后,上面的距离公式可以写成:$$d=\\frac{1}{||w||}y_i(w^Tx_i+b)$$ 对于下图类别A中的数据点$a1$,假设,在该点处,与分割超平面平行的超平面为:$w^Tx+b = c$,因为超平面的参数$w$和$b$同时放大或缩小$k(k\\neq 0)$倍时,超平面是一样的(比如$2x+4y=6$和参数同时除以2之后的$x+2y=3$)。那么,不妨对$w^Tx+b = c$做一个变换,令$$w \\leftarrow \\frac{w}{c}$$ $$b \\leftarrow \\frac{b}{c}$$ 所以$a1$点处的超平面可以写成$w^Tx+b = 1$,同理,$a2,a3$处的超平面可以写成:$w^Tx+b = -1$。显然,对于所有的正例(类别A),以及所有的负例(类别B),使得以下不等式成立: 这个时候,我们套上距离的公式,对于两个不同类别的支持向量$a1$和$a2,a3$到超平面的距离之和它可以写成:$$d = \\frac{2}{||w||} $$这里$\\frac{2}{||w||} $也称为间隔。 我们的目标是让间隔尽量地大,这样划分超平面对未见数据的预测能力会更强,也就是希望: 因为函数$\\frac{2}{||w||}$的单调性与$\\frac{1}{2} {||w||}^2$是相反的,所以,最大化$\\frac{2}{||w||}$时,相当于最小化$\\frac{1}{2} {||w||}^2$,这样,最优化问题可以转化为凸二次规划问题(目标函数是二次的,约束条件是线性的): so,损失函数就这样被找出来了。 最优化  在求取有约束条件的最优化问题时,为了更容易求解,我们可以使用拉格朗日乘子法将其转化为原问题的对偶问题进行求解。也就是,对于上面式子的每条约束添加拉格朗日乘子$\\alpha_i \\geq 0$,对应的拉格朗日函数可以写为:这里的$\\alpha=(\\alpha 1;\\alpha 2;…;\\alpha m)$,$\\alpha 1$代表第一个不等式的拉格朗日乘子。成功避开约束条件之后,我们就可以更加便利地去求解极值了。对于函数$L(w,b,\\alpha)$分别求参数$w$和$b$的偏导数,并令偏导数为0,可以得到:把等式$w$代入到函数$L(w,b,\\alpha)$:因为所以可以把等式中的划掉。最后得到:根据对偶问题的性质,也就是任何一个求极小值的线性规划问题都可以转化为求极大值的线性规划问题。所以:可以计算出:这样,最终的模型可以写成: 这里还需要注意一下的就是,$x_i^Tx$其实就是向量内积,可以用$<·,·>$表示,也就是: 之所以写成这样,是方便后面理解支持向量机是如何使用Kernel进行非线性分类的。上面这个式子可以说是支持向量机核函数的基本形式了。对于含有不等式约束的最优化,还必须满足KKT条件,也就是: 根据条件中的等式$\\alpha_i(y_if(x_i)-1)=0$,对于任意的训练样本,总会有$\\alpha_i = 0$或者$y_i f(x_i)-1=0$,若$\\alpha_i=0$,样本$(x_i,y_i)$不会对损失函数有任何的影响;若$\\alpha >0$,则有$y_i f(x_i)-1 =0$,此时,样本$(x_i,y_i)$其实就是支持向量。 后记  基于数据线性可分假设的SVM显然会对噪声很敏感。相对于硬间隔支持向量机,后续会介绍对噪声容忍度稍微强一点的软间隔支持向量机,以及装备核函数之后的强大SVM;总的来说,SVM的基本原理以及使用都是比较简单的,如果只是为了调包,了解基础原理已经可以了。如果是想深入了解,仅仅知道这些还是远远不够的。  公式编辑已弃疗,复杂一点的公式还是截图显示吧,哎。","tags":[{"name":"机器学习","slug":"机器学习","permalink":"http://yoursite.com/tags/机器学习/"},{"name":"分类模型","slug":"分类模型","permalink":"http://yoursite.com/tags/分类模型/"},{"name":"监督学习","slug":"监督学习","permalink":"http://yoursite.com/tags/监督学习/"}]},{"title":"梯度下降","date":"2017-08-07T08:19:01.063Z","path":"posts/2017/08/07/Gradient_descent.html","text":"  李航在其《统计学习方法》中提到,机器学习的核心由三大块组成,分别为模型、策略、算法,其实说白了,机器学习就是一个模型 + 一个损失函数 + 一个最优化算法。损失函数是为了让模型能够更好地拟合或分类数据,最优化算法是用来优化损失函数的,而在机器学习中,最常用的最优化算法应该算梯度下降了,这篇文章就来讲讲梯度下降,探究下它是如何优化损失函数的。 梯度下降原理  上文的感知机模型中有提到,梯度,其实就是求偏导,梯度下降就是沿着梯度的负方向移动。为了更直观地理解梯度下降,这里举一个网上的例子(实在想不出比这个更形象的解析了),比如我们现在站在山上的某个位置,如下图中的点A,目标是要最快速地下到山脚下,怎么一步一步走下去呢?我们可以选择往相对当前位置来说最陡峭的地方向下移动(也就是梯度的负方向),这样一直走到山脚。这个山脚有可能是整个大山的最低处(B点),也有可能是大山的局部低处(C或D点),这取决于我们移动的方向和幅度。这种情况下,梯度下降不一定能够找到全局最优解,有可能是一个局部最优解。 但如果是下面这个凸函数图,我们就一定可以移动到最低处,因为不管我们处于山上的哪一个位置,只要我们保持每次移动都是向下的,就一定能到达最低点。这种情况下,梯度下降法得到的解就一定是全局最优解。 梯度下降算法梯度下降算法如下(该算法摘抄自李航《统计学习方法》):输入:目标函数$f(x)$,梯度函数$g(x) = \\nabla f(x)$,计算精度$\\epsilon $输出:$f(x)$的极小点$x^*$  (1) 取初始值$x^{(0)} \\in R^n$,置$k=0$  (2) 计算$f(x^{(k)})$  (3) 计算梯度$g_k = g(x^{(k)})$,当$||g_k||<\\epsilon$时,停止迭代,令$x^* = x^{(k)}$;否则,令$p_k = -g(x^{(k)})$,求$\\lambda_k $使得$$f(x^{(k)}+ \\lambda_k p_k) = min f(x^{(k)} + \\lambda p_k) $$  (4) 置$x^{(k+1)} = x^{(k)} + \\lambda_k p_k $,计算$f(x^{(k+1)})$,当$||f(x^{(k+1)}) - f(x^{(k)})|| < \\epsilon$ 或 $x^{(k+1)} - x^{(k)} < \\epsilon$时,停止迭代,令$x^* = x^{(k+1)}$  (5) 否则,令$k=k+1$,转$(3)$   这里的$p_k$是搜索方向,$\\lambda_k$是步长。假设有一个损失函数$y = x^2 - 4*x - 5 $,目标是找到$x$的值使得$y$取最小值,使用梯度下降求解如下: 梯度$$\\nabla x = -(2*x - 4)$$ 更新$x$的值$$x \\leftarrow x + \\eta \\nabla x $$ 设$x$的初始值为10,学习速率$\\eta$为0.2,则第一次迭代:梯度$$\\nabla x =-(2*10 - 4) = -16$$  $x$的值相应更新为$$ x = 10 - 0.2 * 16 = 6.8$$  对应的$y$值为$$y = 6.8*6.8 - 4*6.8 - 5 = 14.04$$第二次迭代:梯度$$\\nabla x =-(2*6.8 - 4) = -9.6$$  $x$的值相应更新为$$ x = 6.8 + 0.2 * (-9.6) = 4.88$$  对应的$y$值为$$y = 4.88*4.88 - 4*4.88 - 5 = -0.7056$$  如此循环迭代,直到达到指定迭代次数或者$y$值最优。这里一直迭代到第38次时,求得极小值$ y =-9.0$。因此,梯度下降就是一个不断沿着梯度的负方向移动直到达到局部或全局最优点的一个过程。 梯度下降的三种形式梯度下降算法可以分为以下三种: 批量梯度下降法(Batch Gradient Descent) 随机梯度下降法(Stochastic Gradient Descent) 小批量梯度下降法(Mini-batch Gradient Descent) 损失函数  为了更好地阐明这三种方式的本质与区别,这里以线性回归模型为例。对于任意一个线性方程,我们可以写成以下形式:$$h_\\theta (x)= \\theta_0 + \\theta_1 x_1 + \\theta_2 x_2 + …+\\theta_n x_n$$ 这里的$(\\theta_0,\\theta_1,…,\\theta_n)$为参数,也称为权重。假设现在有一堆数据$X=(x^0,x^1,x^2,…,x^m)$以及对应的$y$值$Y=(y^0,y^1,y^2,…,y^m)$。我们的目标是要找到参数$(\\theta_0,\\theta_1,…,\\theta_n)$使得$y$值与theta(符号theta在这里硬是显示不出来,我也是醉了)值尽可能地接近。这里用h_theta(x)与y的平方误差作为损失函数来评估h_theta(x)与y值的接近程度。有了损失函数之后,我们就可以来聊一聊这三种梯度下降的形式了。 批量梯度下降法 批量梯度下降法每次迭代都需要用到全量的训练数据,优化过程比较耗时。   前面我们已经知道了梯度下降其实就是对参数求偏导,然后沿着梯度的负方向去更新参数。对于上面的损失函数$J(\\theta)$,我们随机初始化权重$\\theta$,然后重复执行以下更新:$$\\theta_j:=\\theta_j + (-\\alpha \\frac{\\partial}{\\partial\\theta_j}J(\\theta))$$  这里的$\\theta_j:$是指分别对$j=0,1,2,…,n$个参数求偏导。$\\alpha$指学习速率,代表每次向着$J(\\theta)$最陡峭的方向移动的步幅。从上面公式可以看到,为了更新参数$\\theta_j:$,式子右边的$\\frac{\\partial}{\\partial\\theta_j}J(\\theta))$我们还没有知道的。当我们只有一个数据点$(x,y)$的时候,$J$的偏导数: 因此,对于单个训练样本,其更新规则为:对于所有的训练样本,累加上述损失函数的偏导为:于是,每个参数的更新规则就变成为:  从上面公式可以清楚看到,参数的每一次更新(迭代)都要用到全量训练数据(下图红色框位置)  这种更新方式,在迭代过程中,参数的方差是最小的,收敛的过程也是最稳定的,但是如果训练数据量$m$很大,批量梯度下降将会是一个非常耗时的过程。 随机梯度下降法 随机梯度下降法的每次更新,是随机对一个样本求梯度并更新相应的参数。   从上面批量梯度下降法的求解过程可以看到,对于一个样本的损失函数,其对应参数的更新方式为:  这种做法在面对大数据集时不会出现冗余,能够进行快速的迭代。因为每次仅迭代一个样本,随机梯度下降法求解极值的过程,并不是总是向着整体最优的方向迭代的,参数的方差变化很大,收敛很不稳定,相比批量梯度下降会更加曲折,因此准确率相对于批量梯度下降法会有所下降。 小批量梯度下降法 小批量梯度下降法每次更新参数,仅使用一小部分的训练数据进行迭代   使用批量梯度下降法准确率很高,但是效率低,随机梯度下降法效率高,但是准确率低,而小批量梯度下降法算是批量下降法和随机梯度下降法的一种折衷,其集合了前面两种下降方法的优势,具体操作过程如下:   这里的$n$一般会选一个很小的数,比如10或者100,这样的话,在迭代过程中,参数值的方差不至于太大,收敛的过程会更加稳定。 后记  在机器学习中,梯度下降法通常用于优化损失函数,优化的过程,是沿着梯度的负方向不断逼近极值。从第二点‘梯度下降算法’里的实例图可以看到,一开始算法的下降速度很快,但是在极值附近时,算法的收敛速度很慢,比如在第二点的例子,在第四次迭代时已经逼近极小值了,但是在第38次迭代才解出极小值。另外,步长$\\alpha$的选取很关键,步长过程可能会达不到极值点,甚至有可能发散,步长过短会导致收敛速度很慢。  批量梯度下降法每次迭代都用到所有训练数据,准确度高同时复杂度也高;随机梯度下降法每次迭代随机选取一个数据样本进行更新,准确度不高,复杂度较低;小批量梯度下降法集合了前面两种下降方法的优势,训练复杂度较低,精确度也较高。","tags":[{"name":"机器学习","slug":"机器学习","permalink":"http://yoursite.com/tags/机器学习/"},{"name":"最优化","slug":"最优化","permalink":"http://yoursite.com/tags/最优化/"}]},{"title":"感知机模型","date":"2017-08-01T03:16:20.734Z","path":"posts/2017/08/01/perceptron_model.html","text":"  未来几周应该都不会很忙,趁着闲暇时间整理一下机器学习方面的知识。  先从最简单的感知机模型说起。感知机是一种二分类的线性模型,其假设训练数据集是线性可分的,目标是找到一个能够将训练数据集的正样本和负样本完全正确分割开的分离超平面。其实,模型只要找到该分离超平面,学习目的就达到了,并不要求样本能被最大限度地划分。 分类器  分类器能够通过输入的特征向量映射到给定类别中的一个,也就是所谓的物以聚类人以群分。如下图一,蓝色直线将A和B完全分割开,那么该直线$y=ax+b$就是一个分类器。根据分类器是否线性可分的性质,分类器有线性分类器(图一)和非线性分类器(图二图三),从类别上,有二元分类器(图一)和多元分类器(两个类别以上,图二图三),而本章要讨论的感知机模型,就是一个二分类线性模型。 感知机模型  感知机模型很简单,为什么说它简单呢,因为该模型只用了一个线性方程$y = ax + b$ 以及 一个符号函数$sign(x)$(初中的知识是不是?)  下面来看下感知机模型的定义。  假设有$N$维的特征输入$X = \\lbrace x_1,x_2,…,x_n \\rbrace $,输出的类别有两类$ Y = \\lbrace +1,-1\\rbrace $,则由特征输入映射到类别输出的函数$$f(x) = sign(w*x+b)$$称为感知机模型,其中,$w$ 和$b$是我们要求解的模型的参数,这里的$w$其实是一个跟特征输入维度一样$N$维的权值向量(也称为权重),而参数$b$为偏置(在二维平面上,$b$就是截距)。  我们从公式上去解读下为什么说感知机模型是一个线性二分类模型。  先看符号函数里面的线性方程$$y=w*x+b$$显然,方程$ y = w*x +b $在二维平面上是一条直线,在三维空间上是一个平面,在四维甚至更高维空间上就是一个超平面。(哈哈哈,这个实在是画不出来)也就是说,不管是在二维三维空间还是更高维空间,超平面都可以将空间一分为二,并且都可以表示成方程$ y = w*x +b $,所以感知机模型首先是一个线性模型。  再看符号函数,这个更简单,符号函数的定义如下:$$sign(x)=\\begin{cases}+1,\\quad x\\geq 0\\\\-1, \\quad x<0\\end{cases}$$ 如果$x>=0$则$f(x)=+1$,否则$f(x)=-1$。那么对于函数$f(x) = sign(w*x+b)$,如果输入样本$w*x+b>=0$,那么该样本会被判为+1类,否则判为-1类。举个例子,回到上文图一,对于直线上方有$w*x+b>0$,所以所有的A样本都会归为+1类,对于直线下方有$w*x+b<0$,这样所有的B样本都会归为-1类。因此,感知机模型是一个线性二分类模型,其任务就是,找到这样的一个超平面,能够把两个不同的分类完全分割开。 学习策略  原理清楚了,那么现在的问题是,给定一个包含正负类的训练样本,我们应该如何找出这样的一个超平面,使之能够将正样例和负样例点完全分割开,也就是应该如何确定模型的参数$w$和$b$   如上图,如何找到一条直线,使之能够将蓝色实例和褐色实例分割开(这里只是举个例子哈,在实际构建模型过程中,涉及的数据动不动就成百上千万,你想拿支笔来,往两个样本点之间一画一条直线,这是妥妥的不行的,更何况实际处理的数据维度一般都有两位数以上)。直接求解好像无从下手,不妨先假设参数$w$和$b$已知,也就是说这个超平面$S$我们已经找到了,但是不知道这超平面对样例数据的分类效果怎样。如何去评估分类的效果呢,一个很自然的想法就是看有多少样例数据是误分类的,也就是将总误分类数作为模型的损失函数,但是这样的函数对于参数$w$和$b$来说不是连续可导的,难以优化。另一种方法就是可以通过衡量误分类点到超平面的总距离来评估效果。高中的时候我们就学过点到平面的距离的公式是这样子的。$$d=\\frac{|A*x_0+B*y_0+C*z_0+D|}{\\sqrt{A^2+B^2+C^2}}$$ 其中$(A,B,C)$是平面法向量,在这里是权值向量,上面距离公式也可以简写成$$d=\\frac{1}{||w||}|wx_0+b|$$ 其中$w=(A,B,C)$, 这里||w||也称为$w$的L2范数。依然以上文图一为例,对于任意一个样本点$(x_i,y_i)$: 如果该样本点是位于直线上方,并且刚好属于类别A的话,那么该样本点被直线正确分类,此时有$w*x_i+b>0,y_i=+1$,显然$-y_i*(w*x_i+b)<0$ 如果该样本点位于直线下方,并且刚好属于类别B的的话,那么该样本点被直线正确分类,此时有$w*x_i+b<0,y_i=-1$,显然$-y_i*(w*x_i+b)<0$ 如果该样本点位于直线上方,并且刚好属于类别B的的话,那么该样本点被直线错误分类,此时有$w*x_i+b>0,y_i=-1$,显然$-y_i*(w*x_i+b)>0$ 如果该样本点位于直线下方,并且刚好属于类别A的的话,那么该样本点被直线错误分类,此时有$w*x_i+b0$   所以,对于误分类的点$(x_i,y_i)$来说$-y_i*(w*x_i+b)>0$成立。  我们可以用$-y_i*(w*x_i+b)$(相当于$|w*x_i+b|$)去衡量误分类点的效果,$w*x_i+b$值越大,说明该点离分离超平面越远,误分类效果越差(虽然分错了就是分错了)。如下图中的红色直线对于点A的分类效果明显比点B的要差 有了单个误分类点离超平面的距离,那么对于所有的误分类点有:$$ -\\frac{1}{||w||}\\sum_i^m y_i(wx_i+b)$$ 其中$m$为误分类点数量。当不考虑$\\frac{1}{||w||}$时($\\frac{1}{||w||}$恒大于0,去掉的话不影响评估效果),就得到感知机模型的损失函数$Loss Function$(用来度量模型预测的好坏):$$ L(w,b) =-\\sum_i^m y_i(wx_i+b)$$既然误分类点离分离超平面越远,误分类效果越差,那么,误分类点离分离超平面越近,误分类效果越好,当$$-\\sum_i^m y_i(wx_i+b) = 0$$时,样本没有被误分类,所有样本都被超平面恰当地分割开。所以只要找到$w$和$b$使得$L(w,b)$取最小值,此时超平面的分类效果是最好的,这个时候我们可以将参数$w$和$b$的求解转化为最小化函数$L(w,b)$ $$minL(w,b) = -\\sum_i^m y_i(wx_i+b)$$ 算法实现  最优化方法有很多,比如牛顿下降法、拉格朗日乘子法、共轭梯度下降法、梯度下降法,这里使用最简单也最常用的一样方法:随机梯度下降法(梯度下降法的一种方法)来优化上文提到的损失函数$L(w,b)$ $$L(w,b) = -\\sum_i^m y_i(wx_i+b)$$ 梯度其实就是求偏导,随机梯度下降其实就是一次随机选一个误分类点,使其函数值$L(w,b)$往着负梯度方向移动,不断逼近最小值的一个过程。函数$L(w,b)$关于参数$w$和$b$的梯度分别为:$$\\nabla_w L(w,b)= -\\sum_i^m y_i x_i $$ $$\\nabla_b L(w,b)= -\\sum_i^m y_i $$ 现在,我们随便找一个分割超平面,比如$w=0,b=0$(注意$w$是一个向量),然后随机选一个实例点,判断$-y_i*(w*x_i+b)$ 是否大于等于0,如果大于等于0则说明该点被误分类,这时对$(w,b)$进行如下更新(如果$-y_i*(w*x_i+b)$小于0则不更新$w$和$b$):$$w \\leftarrow w+\\eta y_i x_i$$ $$b \\leftarrow b+\\eta y_i$$ 这里的$\\eta$$(0<=\\eta<=1)$为步长,也称为学习率,这样,通过不断选取误分类点,更新参数$(w,b)$,降低函数$L(w,b)$的值,直到$L(w,b)=0$时对应的$(w,b)$的取值就是要求解的值。算法很简单,分成4步,也就是: (1)选取初值$(w_0,b_0)$; (2)在训练数据集中随机选取一个数据$(x_i,y_i)$; (3)如果$-y_i*(w*x_i+b)>=0$,则进行如下更新:$$w \\leftarrow w+\\eta y_i x_i$$ $$b \\leftarrow b+\\eta y_i$$ (4)转至(2),直至数据集中没有误分类的点或者达到指定的迭代次数。 用python实现基于随机梯度下降的简单感知机模型。 import numpy as np import random import matplotlib.pyplot as plt %matplotlib inline from sklearn.datasets.samples_generator import make_blobs #随机生成特征维度为2,分别以[-1,-1],[1,1]为中心,类别方差为0.4,0.5的两个类 x,y = make_blobs(n_samples=100,n_features=2,centers=[[-1,-1],[1,1]],cluster_std=[0.4,0.5]) # 将 0 替换成-1 y[y==0] = -1 w = np.zeros(2)#初始权重赋值为0 b = 0 #初始偏置为0 k=200 #最大迭代次数 l_rate = 0.5 #学习率 i=0 while i <= k: i = i+1 #生成随机数 random_num = random.randint(0,99) #损失函数 if sum(-y[random_num]*(w*x[random_num] + b)) >= 0: #梯度更新权重 w = w + l_rate*y[random_num]*x[random_num] #梯度更新偏置 b = b + l_rate*y[random_num] #---------------------画图------------------------- x1 = -3.0 y1 = -(b + w[0] * x1) /w[1] x2 = 3.0 y2 = -(b + w[0] * x2) / w[1] plt.figure(figsize=(8, 6)) plt.plot([x1,x2],[y1,y2],'r') plt.scatter(x[:,0],x[:,1],marker='o',c=y,s=50) plt.xlabel('x1') plt.ylabel('x2') plt.show()   因为这里的两个样本区分度太明显,所以经过几次迭代就已经收敛了。最终求得参数$w=[1.03972853 \\ 0.97360177] ,b=0$,根据参数$(w,b)$画出的直线如下图所示:  选取不同的初始值$(w,b)$或者不同的误分类点,划分的超平面很有可能会不一样,这也就是为什么在线性可分数据集中,用感知机模型求出来的解有无穷多个。如下面直线$A,B,C$都可作为解。 后记  感知机模型假设训练数据集是线性可分的,如果线性不可分,算法会一直震荡无法收敛,所以其无法处理一些复杂的数据,如在分类器中提到的图二和图三。考虑到实际业务的数据一般比较复杂,简单的感知机模型无法有效地处理如此复杂的数据,所以在建模中很少会使用该模型。但是感知机模型还是比较重要的,为什么这么说呢,因为只要稍微修改下模型的损失函数,感知机模型就可转变为广受欢迎的分类神器:支持向量机,而通过简单的堆叠可演变为神经网络模型。","tags":[{"name":"机器学习","slug":"机器学习","permalink":"http://yoursite.com/tags/机器学习/"},{"name":"分类模型","slug":"分类模型","permalink":"http://yoursite.com/tags/分类模型/"},{"name":"监督学习","slug":"监督学习","permalink":"http://yoursite.com/tags/监督学习/"}]},{"title":"TF-IDF原理及应用","date":"2017-03-15T09:36:24.153Z","path":"posts/2017/03/15/TF-IDF.html","text":"你说广州塔,我知道是在广州,你说黄果树瀑布,我知道是在贵州,你说布达拉宫,我知道是在拉萨,你说公交车,我都不知道你在说哪个城市的公交车。这就是TF-IDF。 概念及原理TF-IDF全称Term Frequency and Inverse Document Frequency,直译过来就是’词频-逆向文件频率’,’TF’是指某一个给定的词语在该文件中出现的频率,’IDF’是指总文件数除以包含该词的文件数,再取对数。TF-IDF一般用来评估在一堆语料库或一堆文件集中,某个字词对于该语料库或该文件的重要程度。怎么理解呢,举个例子,假设现在手上有10篇文章,‘水果’这个词在某一篇文章出现的频率很高,但是在这10篇文章中的仅有2篇文章提到,那么‘水果’这个词的TF-IDF会很高,如果10篇文章中有8篇提到‘水果’这个词,那么这个词的‘TF-IDF’会相对偏低。主要思想就是,一个词越能将一篇文章与其他文章区分开来,那么这个词的权重越高。 计算公式TF计算: (markdown编辑数学公式还不怎么熟,先用mathtype搞好再截图吧)比如上面的例子,’水果’,’硬盘’在文章1(共有10个词)中出现的次数分别为2次,4次,那么:12TF(水果) = 2/10 = 0.2 TF(硬盘) = 4/10 = 0.4 IDF计算: 如果这10篇文章中,有2篇文章包含有’水果’这个词,有5篇包含’硬盘’这个词,那么:12IDF(水果) = log(10/2) = 1.6094 IDF(硬盘) = log(10/5) = 0.6931 TF-IDF计算算好TF和IDF之后,就可以计算’水果’和’硬盘’的TF-IDF了,只需要将TF和IDF相乘就ok。所以’水果’的TF-IDF为:10.2*1.6094 ‘硬盘’的TF-IDF为:10.4*0.6931 如果算’水果’和’硬盘’这两个词与文章1的相关性呢,很简单,只要将这两个词的TF-IDF加起来。10.2*1.6094 + 0.4*0.6931 python中计算TF-IDF使用的工具 jieba scikit-learn 切词其实切词只是计算TF-IDF的前期准备工作,在对中文文本进行TF-IDF计算的话,切词这一步应该是怎么也逃不过去了。平常工作中基本都是用jieba切词,这里也打算用jieba对文本进行处理。例如我现在有5个文本:1content = [['萨德系统核心装备X波段雷达'],['美韩当局部署萨德的步伐也在加速进行'],['纵观如今的手机处理器市场已经不是高通一家独大的局面'],['三星的Exynos处理器以及华为的海思麒麟芯片这些年风头正盛'],['魅族每年数以千万计的销量对于芯片厂商的贡献也是不可小看的']] 首先需要对文本进行切词,切词代码及结果如下:1234567891011def cut_words(text): results = [] for content in contents: seg_list = jieba.cut(content[0],cut_all=False) # 实际应用过程中,这里需要去除停用词 seg = ' '.join(seg_list) results.append(seg) return resultsresult = cut_words(contents)result = ['萨德 系统核心 装备 X 波段 雷达', '美韩 当局 部署 萨德 的 步伐 也 在 加速 进行', '纵观 如今 的 手机 处理器 市场 已经 不是 高通 一家独大 的 局面', '三星 的 Exynos 处理器 以及 华为 的 海思 麒麟 芯片 这些 年 风头 正 盛', '魅族 每年 数以千万计 的 销量 对于 芯片 厂商 的 贡献 也 是 不可 小看 的'] 准备工作做好之后,我们就可以进行TF-IDF计算了。 词语转矩阵词语转矩阵需要用到CountVectorizer这个函数,其作用是统计词汇的数量,并转为矩阵。1234#coding:utf-8from sklearn.feature_extraction.text import CountVectorizervectorizer = CountVectorizer()vector_location = vectorizer.fit_transform(result) 通过type(vector_location)可以看到,函数fit_transform把result二维数组表示成一个稀疏矩阵:123print(type(vector_location))#输出<class 'scipy.sparse.csr.csr_matrix'> 同时可以看下,vercot_location的输出结果:1234567891011121314151617181920212223242526272829303132333435363738394041424344print(vector_location)#输出#(0, 27) 1#(0, 23) 1#(0, 28) 1#(0, 21) 1#(0, 34) 1#(1, 27) 1#(1, 25) 1#(1, 16) 1#(1, 32) 1#(1, 19) 1#(1, 6) 1#(1, 31) 1#(2, 24) 1#(2, 10) 1#(2, 17) 1#(2, 9) 1#(2, 15) 1#(2, 14) 1#(2, 4) 1#(2, 36) 1#(2, 1) 1#(2, 13) 1#(3, 9) 1#(3, 2) 1#(3, 0) 1#(3, 5) 1#(3, 7) 1#(3, 22) 1#(3, 38) 1#(3, 26) 1#(3, 30) 1#(3, 35) 1#(4, 26) 1#(4, 37) 1#(4, 20) 1#(4, 18) 1#(4, 33) 1#(4, 11) 1#(4, 8) 1#(4, 29) 1#(4, 3) 1#(4, 12) 1 输出结果表示的是这个稀疏矩阵的第几行第几列有值,比如(0, 27) 1表示矩阵的第0行第27列有值。转成矩阵的形式之后,我们就可以很容易地算出每个词对应的TF-IDF了,这里使用TfidfTransformer函数进行计算。1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950from sklearn.feature_extraction.text import TfidfTransformertransformer = TfidfTransformer()tf_idf = transformer.fit_transform(vector_location)print(type(tf_idf))#输出,同样是稀疏矩阵的形式#<class 'scipy.sparse.csr.csr_matrix'>print(tf_idf)#输出#(0, 34) 0.463693222732#(0, 21) 0.463693222732#(0, 28) 0.463693222732#(0, 23) 0.463693222732#(0, 27) 0.37410477245#(1, 31) 0.387756660106#(1, 6) 0.387756660106#(1, 19) 0.387756660106#(1, 32) 0.387756660106#(1, 16) 0.387756660106#(1, 25) 0.387756660106#(1, 27) 0.312839631859#(2, 13) 0.321896111462#(2, 1) 0.321896111462#(2, 36) 0.321896111462#(2, 4) 0.321896111462#(2, 14) 0.321896111462#(2, 15) 0.321896111462#(2, 9) 0.259703755905#(2, 17) 0.321896111462#(2, 10) 0.321896111462#(2, 24) 0.321896111462#(3, 35) 0.327880622184#(3, 30) 0.327880622184#(3, 26) 0.264532021474#(3, 38) 0.327880622184#(3, 22) 0.327880622184#(3, 7) 0.327880622184#(3, 5) 0.327880622184#(3, 0) 0.327880622184#(3, 2) 0.327880622184#(3, 9) 0.264532021474#(4, 12) 0.321896111462#(4, 3) 0.321896111462#(4, 29) 0.321896111462#(4, 8) 0.321896111462#(4, 11) 0.321896111462#(4, 33) 0.321896111462#(4, 18) 0.321896111462#(4, 20) 0.321896111462#(4, 37) 0.321896111462#(4, 26) 0.259703755905 如果需要把稀疏矩阵转成平常用的行列形式的矩阵的话。这里可以使用todense()或者toarray()函数,前者是将稀疏矩阵转成matrix的形式,后者是将稀疏矩阵转成ndarray的形式1234567weight = tf_idf.toarray()#orweight1 = tf_idf.todense()print(weight)#输出#(5,39) 这里还有一个问题,就是我怎么知道每个权重对应的是哪个词呢?这里可以将词作为列名,将数组转成Dataframe进行查看。1234word=vectorizer.get_feature_names()df = pd.DataFrame(weight)df.columns = wordprint(df) 源代码最后照例附上本次分析的源代码123456789101112131415161718192021222324252627282930313233343536#coding:utf-8#author:linchartimport jiebaimport pandas as pdfrom sklearn.feature_extraction.text import CountVectorizerfrom sklearn.feature_extraction.text import TfidfTransformercontents = [['萨德系统核心装备X波段雷达'],\\ ['美韩当局部署萨德的步伐也在加速进行'],\\ ['纵观如今的手机处理器市场已经不是高通一家独大的局面'],\\ ['三星的Exynos处理器以及华为的海思麒麟芯片这些年风头正盛'],\\ ['魅族每年数以千万计的销量对于芯片厂商的贡献也是不可小看的']]def cut_words(text): results = [] for content in contents: seg_list = jieba.cut(content[0],cut_all=False) # 实际应用过程中,这里需要去除停用词 seg = ' '.join(seg_list) results.append(seg) return resultsdef tf_idf(words): vectorizer = CountVectorizer() vector_location = vectorizer.fit_transform(result) transformer = TfidfTransformer() tf_idf = transformer.fit_transform(vector_location) weight = tf_idf.toarray() word = vectorizer.get_feature_names() df = pd.DataFrame(weight) df.columns = word return dfresult = cut_words(contents)df = tf_idf(result)print(df)","tags":[{"name":"NLP","slug":"NLP","permalink":"http://yoursite.com/tags/NLP/"},{"name":"python","slug":"python","permalink":"http://yoursite.com/tags/python/"}]},{"title":"爬取微信文章","date":"2017-03-13T09:59:18.349Z","path":"posts/2017/03/13/WeChat_Article1.html","text":"  有时在微信公众号上面看到一些写的比较好的文章,但又没有时间细看,闲下来想找这些文章的时候又忘了是在哪个公众号看的了、文章名字也想不起来,因此想搞个爬虫把想看的文章爬下来,一来可以在闲时咀嚼一下,二来也可以收藏一些好文章,做些知识积累。只是想把自己平常做的一些东西记录下来,非教程 工具 Python 3.5.1 使用的库 re pdfkit requests BeautifulSoup 功能输入微信文章名称或者对应的文章链接,输出文章的pdf文件。 思路 如果同时提供文章链接和文章名称,则优先通过文章链接爬取,如果文章链接爬取失败,则通过文章名称爬取; 如果仅提供文章链接,则通过文章链接爬取; 如果仅提供文章名称,则通过搜狗微信接口搜索微信文章,找到对应文章链接,然后在通过文章链接爬取。 爬取流程获取文章链接将提供的文章名称传入搜狗微信搜索引擎搜索,将结果列表中的第一篇文章作为目标文章下载。下面代码返回目标文章链接。 12345678910111213def get_article_link(query): base_url = r'http://weixin.sogou.com/weixin' User_Agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36' Host = 'weixin.sogou.com' Connection = 'keep-alive' headers = {'User-Agent': User_Agent, 'Host': Host, 'Connection': Connection} params = {'type': 2, 'ie': 'utf-8', 'w': '01019900', 'sut': '707','query':query} request = requests.get(base_url, headers=headers, params=params) request.encoding = 'utf-8' bsobj = BeautifulSoup(request.text, 'lxml') # 仅提取列表中的第一篇文章 first_article_link = bsobj.select('#sogou_vr_11002601_title_0')[0]['href'] return first_article_link 将文章转为html解析文章链接,将文章内容保存为html文件。这里需要注意的是,在解析文章的时候,如果文章中包含有图片的话,正常情况下是无法下载下来的,因为爬取的文章链接为临时链接,非永久链接,无法直接解析src里面的链接。但是,data-src这个属性的值还是可以解析出来的,所以只要把data-src替换为src就可以下载图片了。 12345678910111213141516171819def get_article_html(link): # 为了保险起见,这里使用不同的headers User_Agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/3.0 Safari/536.11' article_headers = {'User_Agent': User_Agent} article_obj = requests.get(link, headers=article_headers) article_obj.encoding = 'utf-8' soup = BeautifulSoup(article_obj.content, 'html5lib') # 以实际的文章名称为准 article_name = soup.select('#activity-name')[0].text.strip() content = soup.find('div', {'id': 'page-content'}) html = str(content) # 把属性data-src替换成src,前面无法将属性src解析出来,data-src,只是LAZY用的, # 延迟加载图片所以显示不出来,LAZYLOAD src_compile = re.compile('data-src') html_new = re.sub(src_compile, 'src', html) # 存储成html with open('wechat_article.html', 'w', encoding='GB18030') as f: f.write(html_new) return article_name html转pdfhtml文件转pdf调用了pdfkit这个包,使用这个包需要安装wkhtmltopdf软件(pdfkit依赖于wkhtmltopdf,因此需要配置路径)。在运行过程中,发现pdfkit在html转pdf时,生成的pdf文件名中如果包含有| / *这些特殊符号时会报错,因此如果以原文章名对pdf命名失败时,仅保留文章名的汉字、字母和数字进行命名。 1234567891011121314151617def html_to_pdf(query_article): path_wk = r'D:\\Program Files\\wkhtmltopdf\\bin\\wkhtmltopdf.exe' config = pdfkit.configuration(wkhtmltopdf=path_wk) options = { 'page-size': 'Letter', 'encoding': \"GB18030\", 'custom-header': [ ('Accept-Encoding', 'gzip') ] } try : pdfkit.from_file('wechat_article.html', '%s.pdf' % query_article, configuration=config, options=options) except: name_compile = re.compile('[a-zA-Z\\u4e00-\\u9fa5][a-zA-Z0-9\\u4e00-\\u9fa5]+') pdf_name = re.findall(name_compile,query_article)[0] pdfkit.from_file('wechat_article.html', '%s.pdf' % pdf_name, configuration=config, options=options) print('文件名已被修改为:%s' %pdf_name) 源代码最后附上文章爬取的完整代码。1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283#coding:utf-8#author:linchartimport requestsfrom bs4 import BeautifulSoupimport reimport pdfkitdef get_article_link(query): base_url = r'http://weixin.sogou.com/weixin' User_Agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36' Host = 'weixin.sogou.com' Connection = 'keep-alive' headers = {'User-Agent': User_Agent, 'Host': Host, 'Connection': Connection} params = {'type': 2, 'ie': 'utf-8', 'w': '01019900', 'sut': '707','query':query} request = requests.get(base_url, headers=headers, params=params) request.encoding = 'utf-8' bsobj = BeautifulSoup(request.text, 'lxml') # 仅提取列表中的第一篇文章 first_article_link = bsobj.select('#sogou_vr_11002601_title_0')[0]['href'] return first_article_linkdef get_article_html(link): # 需要不同的headers User_Agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/3.0 Safari/536.11' article_headers = {'User_Agent': User_Agent} article_obj = requests.get(link, headers=article_headers) article_obj.encoding = 'utf-8' soup = BeautifulSoup(article_obj.content, 'html5lib') # 以实际的文章名称为准 article_name = soup.select('#activity-name')[0].text.strip() content = soup.find('div', {'id': 'page-content'}) html = str(content) # 把属性data-src替换成src,前面无法将属性src解析出来,data-src,只是LAZY用的, # 延迟加载图片所以显示不出来,LAZYLOAD src_compile = re.compile('data-src') html_new = re.sub(src_compile, 'src', html) # 存储成html with open('wechat_article.html', 'w', encoding='GB18030') as f: f.write(html_new) return article_namedef html_to_pdf(query_article): path_wk = r'D:\\Program Files\\wkhtmltopdf\\bin\\wkhtmltopdf.exe' config = pdfkit.configuration(wkhtmltopdf=path_wk) options = { 'page-size': 'Letter', 'encoding': \"GB18030\", 'custom-header': [ ('Accept-Encoding', 'gzip') ] } try : pdfkit.from_file('wechat_article.html', '%s.pdf' % query_article, configuration=config, options=options) except: name_compile = re.compile('[a-zA-Z\\u4e00-\\u9fa5][a-zA-Z0-9\\u4e00-\\u9fa5]+') pdf_name = re.findall(name_compile,query_article)[0] pdfkit.from_file('wechat_article.html', '%s.pdf' % pdf_name, configuration=config, options=options) print('文件名已被修改为:%s' %pdf_name)def wechat_article(query=None,link=None): if link : try : article_name = get_article_html(link) html_to_pdf(article_name) print('文章下载成功') except : article_link = get_article_link(query) get_article_html(article_link) html_to_pdf(query) print('文章下载成功') else : article_link = get_article_link(query) get_article_html(article_link) html_to_pdf(query) print('文章下载成功')# PDF可以用中文命名,但是命名中不可以包含* \\/|等特殊字符。if __name__ == '__main__': link = None query = '文本分析|词频与余弦相似度' wechat_article(query=query,link=link)","tags":[{"name":"python","slug":"python","permalink":"http://yoursite.com/tags/python/"},{"name":"爬虫","slug":"爬虫","permalink":"http://yoursite.com/tags/爬虫/"}]},{"title":"博客的搭建","date":"2017-03-04T18:08:32.627Z","path":"posts/2017/03/05/blog.html","text":"参考文档这个博客的搭建,完全得益于教你免费搭建个人博客,Hexo&Github和使用GitHub和Hexo搭建免费静态Blog这两篇文章,非常详细地描述了基于hexo+github搭建个人博客的准备工作及安装和配置流程。 遇到的问题虽然上面这两篇文章写得很详细,但是我在按着教程搭建的过程中还是遇到一些小问题,这里记录一下: 1、注意运行路径在浏览器中查看自带的hello world文章,需要执行 hexo generate 和hexo server两个命令,这里要注意一下这两个命令的执行路径,需要在hello world文章路径下执行。 2、MarkdownPad无法预览win10下首次安装MarkdownPad会出现右侧浏览页面无法浏览的情况,这种情况下需要安装Awesomium 1.6.6 SDK,安装完成之后问题可解决。 3、yilia主题头像无法显示若加载头像后,头像无法显示,需要将’themes/yilia/layout/_partial’路径下的文件left-col.ejs中的第6行修改为: <img src=\"<%=theme.avatar%>\" class=\"js-avatar show\"> 4、部署上传部署上传时执行以下命令时 hexo d 报’ERROR Deployer not found: git’错误,可能是deployer-git插件未安装,在根目录下执行下面代码安装该插件即可。 npm install hexo-deployer-git --save","tags":[]}]