精华内容
下载资源
问答
  • 什么是文本的嵌入?
    万次阅读 多人点赞
    2018-11-21 11:30:52

    前言

        词嵌入是单词的一种数值化表示方式,一般情况下会将一个单词映射到一个高维的向量中(词向量)来代表这个单词。例如我们将:  

    1.  ‘机器学习’表示为 [1,2,3]   
    2.  深度学习‘表示为[2,3,3]  
    3.  ‘英雄联盟‘表示为[9,1,3]

       对于词向量,我们可以使用余弦相似度在计算机中来判断单词之间的距离: 

    机器学习’与‘深度学习‘的距离:cos(\Theta_1 )=\frac{1*2+2*3+3*3}{\sqrt{1^2+2^2+3^3}\sqrt{2^2+3^2+3^3}}=0.97

    机器学习’与‘英雄联盟‘的距离:cos(\Theta_2 )=\frac{1*9+2*1+3*3}{\sqrt{1^2+2^2+3^3}\sqrt{9^2+1^2+3^3}}=0.56

    通过这篇文章你可以学习到下面内容:

    1. 文本的词嵌入是什么,以及它与其他特征提取方法有什么区别。
    2. 介绍3种主流的从文本数据中学习词嵌入的方法。
    3. 怎么训练新的词嵌入以及如何在日常的NLP任务中使用预先训练好的词嵌入。  

     

    什么是词嵌入?

         词嵌入是一种对文本算法学习后的表示形式,甚至,你可以理解为一个单词在算法中的储存形式。大家知道存入计算机的都是0101的数值化序列,这里也是同理,词嵌入就是将文本数值化以方便拟合算法。这种将单词或者文档数字化表示的方式被认为是深度学习在自然语言处理任务中最具有挑战性的问题之一。

     

    使用密集和低维向量的一个好处是方便计算:大多数神经网络工具包不能很好地处理非常高维,稀疏的向量。......密集表示的主要好处是泛化能力,如果我们认为某些特征可能提供类似的线索,那么提供能够捕获这些相似性的表示是值得的。

     — Page 92, Neural Network Methods in Natural Language Processing, 2017.

           词嵌入实际上是一种将各个单词在预定的向量空间中表示为实值向量的一类技术。每个单词被映射成一个向量(初始随机化),并且这个向量可以通过神经网络的方式来学习更新。因此这项技术基本集中应用与深度学习领域。

         这项技术的关键点在于如何用密集的分布式向量来表示每个单词。这样做的好处在于与one-hot这样的编码对比,使用词嵌入表示的单词向量往往只有几十或者几百个维度。极大的减少了计算和储存量。 

     

    将词汇表中的每个词与分布式词特征向量相关联...特征向量表示词的不同方面特征,每个词与向量空间中的点相关联。特征的数量......远小于词汇的大小

    — A Neural Probabilistic Language Model, 2003.

          这种分布式的词向量表示方式依赖于单词的使用习惯,这就使得具有相似使用方式的单词具有相似的表示形式。这个怎么理解呢?回想我们在看游戏直播,IG获得冠军时候的弹幕,很多主播(无论游戏还是娱乐)的直播间都在发‘IG牛逼’和 ‘77777’,所以我们算法能学习到,‘IG牛逼’和 ‘77777’是向量空间中很相近的2个词。

          虽然看起来有些粗糙,但是这个方法背后有很深的语言学理论支撑。即Zellig Harris的“distributional hypothesis”。这个假设可以归纳为:具有相似语境的词语具有相似的含义。有关更深入的信息,请参阅Harris的1956年论文 “Distributional structure“。

    这种使用语境定义单词的概念可以通过John Firth经常重复的谚语来概括:

     

    You shall know a word by the company it keeps!

    — Page 11, “A synopsis of linguistic theory 1930-1955“, in Studies in Linguistic Analysis 1930-1955, 1962.

    小知识:(链接

    英语中有俗语You shall know a person by the company it keeps.

    而英国语言学家 J. R. Firth在此基础上造出You shall know a word by the company it keeps.的语料库语言学名句。

    类似的说法,在中国古已有之。


    其一:不知其人视其友 出自《孔子家语》(中华书局版,2009:136页)电子版下载
    子曰:"商也好与贤己者处,赐也好说不若己者。不知其子视其父,不知其人视其友,不知其君视其所使,不知其地视其草木。故曰与善人居,如入芝兰之室,久而不闻其香,即与之化矣。与不善人居,如入鲍鱼之肆,久而不闻其臭,亦与之化矣。丹之所藏者赤,漆之所藏者黑,是以君子必慎其所与处者焉。"

    其二:观人于其所友
    李敖引自中国古语,详细出处未考出。也或许为李敖所造。

    词嵌入算法

    词嵌入是从文本语料中学习到的一种将单词表示为预定义大小的实值向量形式。学习过程一般与某个神经网络的模型任务一同进行,比如文档分类。

    下面介绍3中词嵌入技术。

    1.Embedding Layer

           这里并没有专业的名次,使用就称之为Embedding Layer吧。Embedding Layer是特定的自然语言处理任务(例如语言建模或者文档分类)上的与神经网络模型共同学习更新的词嵌入方法。

          使用Embedding Layer通常步骤一般是先预处理语料文本,将每个单词转化成one-hot形式的编码。而此单词对应的词向量其实是算法模型的其中一部分,词向量用预定义的维度来表示,大小我们随机初始化。在这里Embedding Layer其实就是神经网络的input layer,而词向量矩阵即是input layer 到 hidden layer中间的权值矩阵。

    … when the input to a neural network contains symbolic categorical features (e.g. features that take one of k distinct symbols, such as words from a closed vocabulary), it is common to associate each possible feature value (i.e., each word in the vocabulary) with a d-dimensional vector for some d. These vectors are then considered parameters of the model, and are trained jointly with the other parameters.

    — Page 49, Neural Network Methods in Natural Language Processing, 2017.

        牢骚一下: 神经网络本来可解释性就不高,懂了也未必能理解其中参数的意味

        这种将one-hot编码的单词映射到预定维度向量空间的方法,在处理句子时候,如果使用多层感知器模型,那么在作为模型的输入之前,需要将多个单词的向量连接起来(理解成CNN的全连接层吧),如果使用RNN来做的话,就不用这么考虑了,因为RNN本来就是以序列为输入的。

           这种学习嵌入层的方法需要大量的训练数据并且可能很慢,但是确实能学习到针对特定文本数据和NLP任务的词嵌入。

    2. Word2Vec

          Word2Vec是一种能有效从文本语料库中学习到独立词嵌入的统计方法。它是由Tomas Mikolov等人2013年在谷歌开发的一个基于神经网络的词嵌入学习方法,并从那时起,开创了预训练单词嵌入的这种标准。

         这项工作涉及到语义基本的分析和单词向量化的数学探索。举个例子,在wrod2vec词向量空间中,“ 国王 ” -“ 男人 ”=“ 女王 ” -“ 女人”,这就好像算法模型学习到了 国王对于女王的意义就好比男人对于女人的意义。

    We find that these representations are surprisingly good at capturing syntactic and semantic regularities in language, and that each relationship is characterized by a relation-specific vector offset. This allows vector-oriented reasoning based on the offsets between words. For example, the male/female relationship is automatically learned, and with the induced vector representations, “King – Man + Woman” results in a vector very close to “Queen.”

    — Linguistic Regularities in Continuous Space Word Representations, 2013.

    • Continuous Bag-of-Words, or CBOW model.
    • Continuous Skip-Gram Model. 

    关于算法原理参数解释可以参考我之前的文章:【word2vec】算法原理 公式推导

    3. GloVe

    GloVe算法是对于word2vec方法的扩展,更为有效。它由斯坦福大学Pennington等人开发。

    传统经典的词向量空间模型会使用诸如LSA(潜在语义分析)之类的矩阵分解技术来生成单词的词向量表示形式。这种技术在全文检索统计方面做的很好,但是不如word2vec可以捕获单词的语义。

    GloVe是一种将矩阵分解技术(如LSA)的全局统计与word2vec中的基于上下文的学习相结合的方法。GloVe不是使用窗口来定义局部上下文,而是使用整个文本语料库中的统计信息构造显式的单词上下文或单词共现矩阵。这样的词嵌入方法可能会决定学习模型的好坏。

    GloVe, is a new global log-bilinear regression model for the unsupervised learning of word representations that outperforms other models on word analogy, word similarity, and named entity recognition tasks.

    — GloVe: Global Vectors for Word Representation, 2014.

     

    使用词嵌入

    在自然语言处理项目中使用单词嵌入时,你可以选择下面几种方式。

     

    自己学一个词嵌入

    你可以通过你要解决的NLP问题自己学习一个词嵌入方法。当然这需要大量的文本数据来确保你的学习的词嵌入是有用的。例如几百万或者几十亿个单词。

    另外,训练词嵌入的时候,你也有2个选项:

    1. Learn it Standalone,训练模型学习词嵌入,训练好的结果保存下来,作为另一个模型的一部分。之后还可以作为其他任务的词嵌入。
    2. Learn Jointly, 将训练词嵌入作为一个大型任务模型的其中一部分,让训练词嵌入与NLP任务模型一同训练更新。但是这样往往是训练好的词嵌入只能提供给这一个模型使用

     

    使用别人训练好的词嵌入

    研究人员通常可以免费提供经过预先训练的单词嵌入,这样你就可以在自己的学术或商业项目中下载并使用它们。

    比如最近腾讯就开源出了800W的词嵌入向量模型。不过15G的词嵌入文件,真不是一般人玩得起来的。这里提供一个地址:

    https://github.com/cliuxinxin/TX-WORD2VEC-SMALL 这个git主人,对我的学习指导很多,希望大家star一下。

    另外,使用已经训练好的词嵌入的时候,你也有2个选项:

    1. Static,直接套用
    2. Updated,作为初始权值.用自己的数据.更新一下

     

    对于使用训练好的还是自己训练在业内目前应该还没有个标准,使用在实际工作中,你需要做一些不同的尝试来发掘能提升项目能力的一些方法。加油~

     

    参考:

    What Are Word Embeddings for Text?

    by Jason Brownlee on October 11, 2017 in Deep Learning for Natural Language Processing

     

     

     

    更多相关内容
  • 首先通过基于语义分类的关联规则挖掘关联主题词并建立候选标签集合,然后以关联词在数据集中的概率分布来设计相关性判别函数,计算候选标签和主题模型的相关度,最后根据最大边缘相关选择高语义覆盖度和区分度的标签....
  • 通过设计基于语义相似度的关联词柔性簇模型,为概念检索提供了一种关键词语义匹配的实现方法,并且可以根据需要动态地调节匹配范围。最后给出了该簇模型在数字海洋共享平台中的示范应用。
  • 通过设计基于语义相似度的关联词柔性簇模型,为概念检索提供了一种关键词语义匹配的实现方法,并且可以根据需要动态地调节匹配范围。最后给出了该簇模型在数字海洋共享平台中的示范应用。
  • 下面将围绕什么是词嵌入、三种嵌入的主要算法展开讲解,并通过案例具体讲解如何利用嵌入进行文本的情感分析。 什么是词嵌入? 嵌入实际上是一类技术,单个在预定义的向量空间中被表示为实数向量,每个单词...

    词嵌入(word embedding)是一种词的类型表示,具有相似意义的词具有相似的表示,是将词汇映射到实数向量的方法总称。词嵌入是自然语言处理的重要突破之一。下面将围绕什么是词嵌入、三种词嵌入的主要算法展开讲解,并通过案例具体讲解如何利用词嵌入进行文本的情感分析。

    什么是词嵌入?

    词嵌入实际上是一类技术,单个词在预定义的向量空间中被表示为实数向量,每个单词都映射到一个向量。举个例子,比如在一个文本中包含“猫”“狗”“爱情”等若干单词,而这若干单词映射到向量空间中,“猫”对应的向量为(0.1 0.2 0.3),“狗”对应的向量为(0.2 0.2 0.4),“爱情”对应的映射为(-0.4 -0.5 -0.2)(本数据仅为示意)。像这种将文本X{x12345……xn12345……yn },这个映射的过程就叫做词嵌入。

    之所以希望把每个单词都变成一个向量,目的还是为了方便计算,比如“猫”,“狗”,“爱情”三个词。对于我们人而言,我们可以知道“猫”和“狗”表示的都是动物,而“爱情”是表示的一种情感,但是对于机器而言,这三个词都是用0,1表示成二进制的字符串而已,无法对其进行计算。而通过词嵌入这种方式将单词转变为词向量,机器便可对单词进行计算,通过计算不同词向量之间夹角余弦值cosine而得出单词之间的相似性。

    此外,词嵌入还可以做类比,比如:v(“国王”)-v(“男人”)+v(“女人”)≈v(“女王”),v(“中国”)+v(“首都”)≈v(“北京”),当然还可以进行算法推理。有了这些运算,机器也可以像人一样“理解”词汇的意思了。

    词嵌入主要算法

    那么如何进行词嵌入呢?目前主要有三种算法:

    Embedding Layer

    由于缺乏更好的名称,Embedding Layer是与特定自然语言处理上的神经网络模型联合学习的单词嵌入。该嵌入方法将清理好的文本中的单词进行one hot编码(热编码),向量空间的大小或维度被指定为模型的一部分,例如50、100或300维。向量以小的随机数进行初始化。Embedding Layer用于神经网络的前端,并采用反向传播算法进行监督。

    被编码过的词映射成词向量,如果使用多层感知器模型MLP,则在将词向量输入到模型之前被级联。如果使用循环神经网络RNN,则可以将每个单词作为序列中的一个输入。

    这种学习嵌入层的方法需要大量的培训数据,可能很慢,但是可以学习训练出既针对特定文本数据又针对NLP的嵌入模型。

    Word2Vec(Word to Vector)/ Doc2Vec(Document to Vector)

    Word2Vec是由Tomas Mikolov 等人在《Efficient Estimation of Word Representation in Vector Space》一文中提出,是一种用于有效学习从文本语料库嵌入的独立词语的统计方法。其核心思想就是基于上下文,先用向量代表各个词,然后通过一个预测目标函数学习这些向量的参数。Word2Vec 的网络主体是一种单隐层前馈神经网络,网络的输入和输出均为词向量,其主要训练的是图中的红圈部分。

     

    该算法给出了两种训练模型,CBOW (Continuous Bag-of-Words Model) 和 Skip-gram (Continuous Skip-gram Model)。CBOW将一个词所在的上下文中的词作为输入,而那个词本身作为输出,也就是说,看到一个上下文,希望大概能猜出这个词和它的意思。通过在一个大的语料库训练,得到一个从输入层到隐含层的权重模型;而Skip-gram它的做法是,将一个词所在的上下文中的词作为输出,而那个词本身作为输入,也就是说,给出一个词,希望预测可能出现的上下文的词,2-gram比较常用。

    通过在一个大的语料库训练,得到一个从输入层到隐含层的权重模型。给定xx预测xxx的模型的输入都是词的向量,然后通过中间各种深度学习DL的CNN或RNN模型预测下一个词的概率。通过优化目标函数,最后得到这些词汇向量的值。Word2Vec虽然取得了很好的效果,但模型上仍然存在明显的缺陷,比如没有考虑词序,再比如没有考虑全局的统计信息。

    Doc2Vec与Word2Vec的CBOW模型类似,也是基于上下文训练词向量,不同的是,Word2Vec只是简单地将一个单词转换为一个向量,而Doc2Vec不仅可以做到这一点,还可以将一个句子或是一个段落中的所有单词汇成一个向量,为了做到这一点,它只是将一个句子标签视为一个特殊的词,并且在这个特殊的词上做了一些处理,因此,这个特殊的词是一个句子的标签。如图所示,词向量作为矩阵W中的列被捕获,而段落向量作为矩阵D中的列被捕获。

     

     

    GloVe(Global Vectors for Word Representation)

    GloVe是Pennington等人开发的用于有效学习词向量的算法,结合了LSA矩阵分解技术的全局统计与word2vec中的基于局部语境学习。

    LSA全称Latent semantic analysis,中文意思是隐含语义分析,LSA算是主体模型topic model的一种,对于LSA的直观认识就是文章里有词语,而词语是由不同的主题生成的,比如一篇文章包含词语:计算机,另一篇文章包含词语:电脑,在一般的向量空间来看,这两篇文章不相关,但是在LSA看来,这两个词属于同一个主题,所以两篇文章也是相关的。该模型不依赖本地上下文,是对全局字词同现矩阵的非零项进行训练,其中列出了给定语料库中单词在彼此间共同出现的频率。

    从本质上说,GloVe是具有加权最小二乘法目标的对数双线性模型。字词共现概率的比率又编码成某种形式的潜在可能意义。例如,以下是基于60亿词汇语料库的各种关于冰和蒸汽的词的共现概率:

     

    如上表所示,“ice(冰)”与“solid(固体)”共现的可能性比“gas(气体)”大,“steam(蒸汽)”与“gas(气体)”共现的可能性比“solid(固体)”大,从而很轻易地可以区别出二者区别。而“ice(冰)”和“steam(蒸汽)”都与“water(水)”的共现概率较大,都与“fashion(时尚)”共现概率很小,因此无法区别“ice”和“steam”。只有在可能性的比率中(图表第三行),才会将像“water”和“fashion”这样的非区别性词汇(non-discriminative)的噪音相抵消,可能性比率越大(远大于1)的词与“ice”特性相关联,可能性比率越小(远小于1)则与“steam”的特性相关联。以这种方式,可能性比率编码了许多粗略形式的意义,这些意义与热力学相位的抽象概念相关联。

    GloVe的训练目标是学习词向量,使得它们的点积等于“共现概率”的对数,由于比率的对数等于对数差,这个目标将共现概率的比率与词向量空间中的向量相关联,由于这些比率可以编码某种形式的意义,所以该信息也被编码为向量差异。所以所得到的词向量在单词类比任务上执行的很好。

    词嵌入应用案例

    当您在自然语言处理项目中使用词嵌入时,您可以选择自主学习词嵌入,当然这需要大量的数百万或数十亿文本数据,以确保有用的嵌入被学习。您也可以选择采用开源的预先训练好的词嵌入模型,研究人员通常会免费提供预先训练的词嵌入,例如word2vec和GloVe词嵌入都可以免费下载。

    下面我们具体来看一下如何在KNIME中利用词嵌入进行情感分析,整体流程如下图所示:

     

    首先,我们从IMDb网站上获取关于《Girlfight》这部影片的2000条评论,储存为.CSV格式的文件,利用File Reader这个节点把文本读入。

     

     

    然后我们要将文件中的字符串转化成文档,把文件中除了文档的列都过滤掉,在结构图中是Document Creation这个节点,这是一个节点合集,点开包含三个子节点:

    接下来是进行文本的词向量训练,我们预设置词向量维度为200维,并通过上文所介绍的Doc2Vec算法将2000条评论文本转换成2000个向量数值,获得如下图所示的2000个词向量:

     

    另一方面,在进行词向量训练的同时导入预先设定的情感分类标签:

     

    然后,在Joiner节点处,带有”POS”或”NEG”标签的情感词典与训练好的2000条词向量进行匹配,使得2000条词向量分别贴上”POS”或”NEG”标签,这样就获得了2000条词向量的标签数据,以便后面训练情感分析模型。

     

     

     

    接下来,由于200维的向量数据量太大,通过主成分分析PCA算法将200维的数据进行降维处理转化为二维向量,以便对其分类效果进行可视化,通过颜色标记,最终2000条评论的情感分析情况如图所示:

     

     

    从图上看,POS和NEG的情感正负标签很明显,分类比较理想。

    然后,我们开始训练模型。先将数据进行分区,训练集和测试集,七三开分为两部分,一部分是训练数据,一部分是检验数据。

     

    训练模型为Random Forest模型,顾名思义,就是用随机的方式建立一个“森林”,“森林”里面有很多的决策树组成,随机森林的每一颗决策树之间是没有关联的。在得到森林之后,当有一个新的输入样本进入的时候,就让森林中的每一颗决策树分别进行一下判断,看看这个样本该属于哪一类(对于分类算法),然后看看哪一类被选择最多,就预测这个样本为那一类。最终训练出来的Random Forest模型如下图所示:

     

    最后,通过检测数据的测试,我们得出该模型的精度为95.5%,由此可见拟合的模型较为精确。通过这个案例的练习,我们可以更好的理解词嵌入技术和算法对自然语言处理NLP的新思路,也是深度学习技术的革命性体现。

     

    展开全文
  • 写在前面每次写博客都爱先扯点乱七八糟的东西,这是工作准备写的第2篇博客,之前写过一篇hadoop入门,那里还留下了一个搜索引擎的demo没有去完成,这次学习热词关联刚好也是和搜索引擎相关,所以借此机会把这篇记录...

    目录:

    • 《剑指offer》面试题-topk算法
    • 搜索热词关联算法
    • 代码实现以及java学习

    写在前面

    每次写博客都爱先扯点乱七八糟的东西,这是工作准备写的第2篇博客,之前写过一篇hadoop入门,那里还留下了一个搜索引擎的demo没有去完成,这次学习热词关联刚好也是和搜索引擎相关,所以借此机会把这篇记录下来,一方面花了3天来学习了这个内容,确实学到了不少东西,二来下次写搜索引擎的hadoop的demo时候可以把这个整合到一起,如果有空把关于搜索的东西整合到一起,添加一些爬虫相关的只是内容,就可以简单的搭建一个搜索引擎了,想想还是挺不错的。好啦,我们来开始学习吧!

    topK算法

    这里写图片描述
    这个题目实现不难,在没有什么限制的情况下我们很快能得到答案。

    解法1 排序

    对数组排序,然后找到最小的k个数字,这个思路和粗暴,实际上我们把问题转化成了排序算法,那么合理的想法就是去找排序中时间复杂度最小的快排(O(nlgn)),这里对于此方法有一个问题就是在于需要改变原数组,如果题目中存在此种限制,自然是需要考虑其他算法。

    解法2 partition算法

    parition算法,说起这个算法可能对于算法不太熟悉的同学真没什么印象,但是如果说快排大家肯定都知道了。我先贴一段java实现的代码给大家看一看。

    //快速排序 虽然快排思想比较简单,但是有些=还是需要注意一下勒,网上不少博客的代码都有点小问题,所以自己写了跑了下才贴出来。
        public static void qsort(int[] arr,int begin,int end) {
    
            int index = partition(arr, begin,end);
    
            if(begin >= end -1) {
                return; 
            }
    
            qsort(arr,begin,index-1);
            qsort(arr,index+1,end);
    
        }
    //用一个哨兵来分割大于小于的两部分
        private static int partition(int[] arr,int begin,int end) {
            if(begin >= end) {
                return -1;
            }
    
            int pos = arr[begin];
            while(begin < end) {
                while(arr[end] >= pos && begin < end) {
    
                    end --;
                }
                if(begin < end) {
                    arr[begin] = arr[end];
                }
                while(arr[begin] <= pos && begin < end) {
                    begin ++;
                }
                if(begin < end) {
                    arr[end] = arr[begin];
                }
            }
    
            arr[begin] = pos;
            return begin;
        }

    以上代码中有很重要的一块就是partition,很多快排的写法里面没有将其作为单独的一个函数,他的思想就是取出一个哨兵,然后把大于他的放到一边,小于他的放到另一边。这样如果我们按着这个思路,先找到一个数partition一次,判断这个树的最终位置是不是在k处,如果大于则找前面的部分(假设左小右大),如此直到我们找到第k个值的位置,此时k之前的都比k小,就得到了题解。下面我大概举个例子,给大家一个形象的表示。
    arr = 4,3,5,9,2,4,6 找到最小的3个值
    partition1 2 3 4 9 5 4 6 index = 3 分了一次刚好index返回3,所以最小的是2 3 4,对没毛病!
    那我们现在来看一看这个算法的时间复杂度,逆序的时候复杂度最高为O(n^2),如果是随机的话,T(N) = T(T/2) + N,时间复杂度为O(N)。那么我们可以在O(N)的时间复杂度把这个问题给解决了。这比上述排序好多了,因为针对上述排序中,我们每次都要把序列找到一个哨兵然后左右都要去排序,这个时候,我们只处理我们需要处理的部分,时间复杂度就降低了下来。虽然简单,还是画个图表示一下下。如下图,如果我们想要去找前3小的数字时,如果哨兵是5,那么我们就可以不用管后面部分,只需要考虑前面绿色填充的数字,这样节约了很多时间。
    这里写图片描述
    但是这个算法仍然有点问题,同解法1,这个算法会调整数据,当数据量不断增加时,我们有时候希望能增量式的去处理,而不是每次有数据进来都乾坤大挪移,那么我们需要考虑外部存储来辅助这个算法保证这个原数组不会改变。

    解法3 外部存储-小(大)根堆

    我们日常也会遇到这样的算法例子,偶尔我们会用一个外部数组来存储,每次进来一个数字就判断。比如我们想找一个数组里面最大的3个数字,我开一个3空间的数组,那么我们遍历一次原数组,找到最大的3个依次放入,每次放入时和外部数组比较一下,这样也可以在O(N)时间内完成,且不改变原数组,好啦。貌似我们对这个算法已经了解的很深入了。
    且慢,各位看客想一想,如果这个N非常非常大时候,如果我们要在几百万的数据中找前10,那会有什么不同么。对于算法复杂度来说,O(N)应该是不可避免了,至少每个数字都要遍历到,但是对于大数据处理来说,复杂度中隐藏的时间常熟因子也是十分关键的。我们现在来分析一波,对于外部数组,如果我们是找最大的K个数,那么我们每次需要找到数组中最小的,如果最小我们就要替换,所以会有替换操作。那么对于一个无顺序数组的话,大概O(K)可以完成,然后我们算法整体就是O(K*N),如果我们来维护一个有序数组的话,开销没什么区别。如果熟悉数据结构的同学,现在一定看出问题了,我们需要用堆来完成这些操作,取最小堆可以O(1),来完成,而插入堆也可以在O(lgN)完成(平均),OK,数据量一大时候,这个差异是非常大的,先给大家看一个感性的认识,我没有具体去算时间,只是进行了一下对比,heap为我自己实现的小根堆,orderarr是网上借鉴的别人实现的有序数组。下面应该十分明显了,k小时没有啥区别,k的变大,这个差距会越来越大。

        int n = 3000000;
        int k = 100;
        orderarr程序运行时间: 16ms
        heap程序运行时间: 13ms
    
        int n = 3000000;
        int k = 100000;
        orderarr程序运行时间: 5137ms
        heap程序运行时间: 59ms
    

    算法不难,此处介绍一下思路即可,晚上有很多介绍堆思路的,算法导论中有heapify来维护堆的,java中的优先队列好像是用shiftup,shitfdown来维护insert操作,个人觉得都可以,思想都是一致的。大家有兴趣可以翻翻我的github,文末给出,我把这些代码都放在里面,有不对之处大家也可以指教。

    public static void testHeap(int n,int k) {
            int[] arr = new int[n];
            Random random = new Random();
    
            for(int i=0;i<arr.length;i++) {
                    arr[i] = random.nextInt();
                }
    
                int length = arr.length;
                MinHeap mHeap = new MinHeap();
    
                long startTime=System.currentTimeMillis();   //获取开始时间 
    
                for(int i=0;i<length;i++) {
                    if(i<=k-1) {
                        mHeap.insert(arr[i]);
                    }else {
                        if(arr[i] > mHeap.getTop()) {
                            mHeap.removeTop();
                            mHeap.insert(arr[i]);
                        }
                    }
                }
    //      mHeap.show();
            long endTime=System.currentTimeMillis(); //获取结束时间  
            System.out.println("heap程序运行时间: "+(endTime-startTime)+"ms");  
    
        }

    热词搜索提示

    现在终于到正题了,之前半天都是在介绍算法,现在也讲讲该算法的应用,现在xx搜索引擎公司需要根据用户的输入来给其他用户做输入提示,那么我们有很多输入词条,现在需要提示热度最高的。这实际就是一个topK问题,额外之处一个是操作对象不在是一个整数,而是一个键值对,另外一个问题是我们需要构建一颗trie树来帮助我们找到需要排序的词语。当然对于日志信息来说,数据是一条一条的,我们还需要用到hash表来帮我们进行统计。

    第一步 hashMap统计

    对于hash表来说,没有特别要多说的,统计一个大数据量,如果内存够的话,一张hash表无疑是很好的选择,O(n)的时间就可以搞定,当然这个大数据也是有一个限制的,如果上T或者更大,那可能就需要想其他的办法了。G级别的这个还是没问题的。此处我们使用java中的hashMap来完成。

    第二步 构建trie树

    因为涉及到应用,当输入“北”的时候,希望能提示“北京”,或者“北海”,不能提示“南京”吧,那么我们需要有一颗前缀树来实现,每次找到输入的节点的子树,对子树中的节点遍历,取得最大的K个,为了方便,前缀树结构如下,每个节点放置到当前节点位置的所有字符,并且添加对应频次,路过的词语频次为0,结构图大致如下。
    这里写图片描述

    第三部 topK算法

    topK说的很多了,我们需要改成能针对键值对的就OK啦! ~^_^~

    代码实现以及java学习

    决策树部分

    下面是决策树的实现,决策树中学习到的一个比较重要的点就是需要自己实现一个迭代器,之前的数组可以直接遍历,for循环就可以了,但是树没有这么简单,便需要实现一个iterator来帮助完成遍历。

    首先class需要实现Iterable接口,调用x.iterator()返回一个Iterator类,这个类通常含有2个方法,hasnext(),next()。结构如下,具体请查看其他介绍,此处不赘述。
    public class MyIteratorClass implements Iterable{
    
        @Override
        public Iterator iterator() {
            // TODO Auto-generated method stub
            return new MyIterator;
        }
        private class MyIterator implements Iterator<TrieNode>{
            @Override
            //返回是否还有下一个值
            public boolean hasNext() {
                return null;
            }
    
            @Override
            //返回下一个迭代的值
            public TrieNode next() {
                return null;
            }
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.Queue;
    import datastructure.TrieTree.TrieNode;
    
    public class TrieTree implements Iterable{     
        private  TrieNode root;
    
        public static void main(String[] args) {
            TrieTree trieTree = new TrieTree();
            trieTree.insert("北京动物园", 2);
            trieTree.insert("北京天安门", 3);
            trieTree.insert("北京", 1);
            String word = "北京";
            TrieNode subTree = trieTree.getSubTreeByWord(word);
            Iterator<TrieNode> iterator = trieTree.iterator(subTree);
            while(iterator.hasNext()) {
                TrieNode node = iterator.next();
                System.out.println(node.value + " " + node.count);
            }
            //trieTree.showTrieTree();
        }
    
        public TrieNode getRoot() {
            return root;
        }
    
        public TrieTree() {
            root = new TrieNode("root",0);
        }
    
        public class TrieNode{
            private String value;
            private ArrayList<TrieNode> son;
            private int count; //当前路径上统计数
            public TrieNode() {
                // TODO Auto-generated constructor stub
                this.value = "null";
                this.count = 0;
                this.son = new ArrayList<TrieNode>();
            }
    
            public TrieNode(String value,int count) {
                // TODO Auto-generated constructor stub
                this.value = value;
                this.count = count;
                this.son = new ArrayList<TrieNode>();
            }
    
            public String getValue() {
                return value;
            }
    
            public int getCount() {
                return count;
            }
        }
    
    
    
    
        //根据输入获取子树
        public TrieNode getSubTreeByWord(String str) {
            return _getSubTreeByWord(root,str);
        }
        private TrieNode _getSubTreeByWord(TrieNode root,String str) {
            int sonNum = root.son == null? 0 :root.son.size();
    
            if(root.value.equals(str)) {
                return root;
            }
    
            for(int i=0;i<sonNum;i++) {
                TrieNode node = _getSubTreeByWord(root.son.get(i),str);
                if(node != null) {
                    return node;
                }
            }
            return null;
        }
    
        //插入时,把count放在最后一个节点上
        public void insert(String str,int count) {
            _insertNode(root, str, count ,1);
        }
        private void _insertNode(TrieNode root,String str,int count ,int index) {
            int sonNum = root.son.size();
            int findFlag = 0;
    
    
            for(int i=0;i<sonNum;i++) {
                if(root.son.get(i).value.equals(str.substring(0, index))) {
                    findFlag = 1;
                    if(str.length() == index) {
                        root.son.get(i).count = count;
                        return;
                    }else {
                        _insertNode(root.son.get(i), str, count ,index+1);
                    }
                    break;
                }
            }
            //遍历之后没有找到就创建一个
            if(findFlag == 0) {
            //  System.out.println(str.substring(0, index));
                String newValue = str.substring(0, index);
                int newCount = index != str.length() ? 0 : count;
                TrieNode sonNode = new TrieNode(newValue,newCount);
                root.son.add(sonNode);
                if(str.length() != index) {
                    _insertNode(sonNode, str, count ,index+1);
                }
            }
        }
    
        //循环遍历输出字典树内容
        public void showTrieTree() {
            _showTrieTree(root);
        }
        private void _showTrieTree(TrieNode root) {
            System.out.println(root.value + root.count);
            int sonNum = root.son.size();
            for(int i=0;i<sonNum;i++) {
                _showTrieTree(root.son.get(i));
            }
        }
    
    
        @Override
        public Iterator<TrieNode> iterator() {
            // TODO Auto-generated method stub
            return new TrieTreeIterator();
        }
    
    
        public Iterator<TrieNode> iterator(TrieNode itrRoot) {
            // TODO Auto-generated method stub
            return new TrieTreeIterator(itrRoot);
        }
    
        private class TrieTreeIterator implements Iterator<TrieNode>{
            private TrieNode next;
            private  Queue<TrieNode> queue;
    
            public TrieTreeIterator() {
                // TODO Auto-generated constructor stub
                next = root;
                queue = new LinkedList<TrieNode>();
                if(next == null) {
                    return;
                }
            }
    
            public TrieTreeIterator(TrieNode itrRoot) {
                // TODO Auto-generated constructor stub
                next = itrRoot;
                queue = new LinkedList<TrieNode>();
                if(next == null) {
                    return;
                }
            }
    
            @Override
            public boolean hasNext() {
                // TODO Auto-generated method stub
                int sonNum = next.son.size();
                for(int i=0;i<sonNum;i++) {
                    queue.add(next.son.get(i));
                }
                if(queue.isEmpty()) {
                    return false;
                }else {
                    return true;
                }
    
            }
    
            @Override
            public TrieNode next() {
                // TODO Auto-generated method stub
    
                next = queue.remove();
                return next;
            }
    
        }
    
    }  

    Heap部分

    此处借鉴了网友的代码,但是不记得哪里抄来的了,抱歉。
    这里学到的比较重要的东西就是泛型的使用,对于这份代码来说,是适用与int的,但是我想拿这份代码来做键值对的处理,利用泛型和提供的Comparator就可以很方便的实现代码的复用。此处我定义了键值对的类型,提供了一些基础方法。然后出现了另外一个重要的问题,那就是关于对象复制的问题,这里学习了深克隆的方式,如果setRoot方法不传入clone(),则只是传入了索引,而不是对堆内进行赋值,这样逻辑上有误。所以这里传入一定是clone的内容。关于clone有深浅之分,这里我使用了序列化的方式,下面这博客写的不错,推一推。
    克隆学习:https://www.cnblogs.com/Qian123/p/5710533.html

    //代码复用可以学习的地方
    
    //这是之前的代码
    int[] arr = new int[n]
    //此处省略,很多,这里重点在于描述差异之处
    mHeap.setRoot(arr[i]);
    
    //这是修改后的代码
    KeyPair<String, Integer> tempKeyPair = new KeyPair<String, Integer>("", 0);
    mHeap.setRoot(tempKeyPair.clone());
    //建立heap来做键值对比较
    Comparator<KeyPair<String, Integer>> comp = new Comparator<KeyPair<String, Integer>>() {  
                @Override
                public int compare(KeyPair<String, Integer> o1, KeyPair<String, Integer> o2) {
                    // TODO Auto-generated method stub
                    return o2.getValue() - o1.getValue();
                }  
            };  
    //克隆可以学习的部分,序列化方式克隆。
    
    public class KeyPair<K, V> implements Serializable {
        private K key;
        private V value;
    
        public KeyPair clone() {
                KeyPair outer = null;
                try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    ObjectOutputStream oos = new ObjectOutputStream(baos);
                    oos.writeObject(this);
                    // 将流序列化成对象
                    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                    ObjectInputStream ois = new ObjectInputStream(bais);
                    outer = (KeyPair) ois.readObject();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
                return outer;
              }
        }
    
    public class Heap<T> {  
    
        /** 
         * 以数组形式存储堆元素 
         */  
        private T[] heap;  
    
        /** 
         * 用于比较堆中的元素。c.compare(根,叶子) > 0。  
         * 使用相反的Comparator可以创建最大堆、最小堆。 
         */  
        private Comparator<T> c;  
    
        public Heap(T[] a, Comparator<T> c) {  
            this.heap = a.clone();  
            this.c = c;  
            buildHeap();  
        }  
    
        /** 
         * 返回值为i/2 
         *  
         * @param i 
         * @return 
         */  
        private int parent(int i) {  
            return (i - 1) >> 1;  
        }  
    
        /** 
             * 
         * 返回指定节点的left子节点数组索引。相当于2*(i+1)-1 
             * 
         *  
         * @param i 
         * @return 
         */  
        private int left(int i) {  
            return ((i + 1) << 1) - 1;  
        }  
    
        /** 
         * 返回指定节点的right子节点数组索引。相当于2*(i+1) 
         *  
         * @param i 
         * @return 
         */  
        private int right(int i) {  
            return (i + 1) << 1;  
        }  
    
        /** 
         * 堆化 
         *  
         * @param i 
         *           堆化的起始节点 
         */  
        private void heapify(int i) {  
            heapify(i, heap.length);  
        }  
    
        /** 
         * 堆化, 
         *  
         * @param i 
         * @param size 堆化的范围 
         */  
        private void heapify(int i, int size) {  
            int l = left(i);  
            int r = right(i);  
            int next = i;  
            if (l < size && c.compare(heap[l], heap[i]) > 0)  
                next = l;  
            if (r < size && c.compare(heap[r], heap[next]) > 0)  
                next = r;  
            if (i == next)  
                return;  
            swap(i, next);  
            heapify(next, size);  
        }  
    
    
        /** 
         * 对堆进行排序 
         */  
        public void sort() {  
            // buildHeap();  
            for (int i = heap.length - 1; i > 0; i--) {  
                swap(0, i);  
                heapify(0, i);  
            }  
        }  
    
        /** 
         * 交换数组值 
         *  
         * @param arr 
         * @param i 
         * @param j 
         */  
        private void swap(int i, int j) {  
            T tmp = heap[i];  
            heap[i] = heap[j];  
            heap[j] = tmp;  
        }  
    
        /** 
         * 创建堆 
         */  
        private void buildHeap() {  
            for (int i = (heap.length) / 2 - 1; i >= 0; i--) {  
                heapify(i);  
            }  
        }  
    
        public void setRoot(T root) {  
            heap[0] = root;  
            heapify(0);  
        }  
    
        public T root() {  
            return heap[0];  
        }  
    
        public T getByIndex(int i) {
            if(i<heap.length) {
                return heap[i];
            }
            return null;
        }
    }

    总结

    这里写图片描述

    这里写图片描述

    上面贴出了测试数据和最终结果,以上代码估计跑不通,贴出来是为了突出重点,如果想看跑,可以follow我的github,上面有完整实例,另外还有一些问题没有解决完全,一个是大数据下的测试,另外有一个就是交互问题,因为考虑到后面还会写相关搜索引擎的板块,所以这里先不急着完善,以后有机会再做交互和完整测试。

    哈,写了3个多小时,终于总结完了~,看完有收货的小伙伴,记得赞一个噢~。我们一起天天学算法~

    github:https://github.com/Continue7777/algorithms

    展开全文
  • 为了获得术语关联视图片段,提出了一种用于本体的术语关联图模型,并引入了最大r半径子图的概念,将术语关联图分解为连通的子图,从而保持了各个之间的紧密联系。 为了实现紧凑,在与查询相关的最大r半径子图中,...
  • 助记什么,有什么用?

    千次阅读 2019-02-04 19:36:34
    助记什么,有什么用?    玩加密货币的朋友相信对助记都不陌生,我们在使用钱包之前,会让你备份12个单词,在备份期间不允许截图操作,并且不断强调这12个单词非常重要,最好用物理方式备份,备份时身边...

    助记词是什么,有什么用?

     

            玩加密货币的朋友相信对助记词都不陌生,我们在使用钱包之前,会让你备份12个单词,在备份期间不允许截图操作,并且不断强调这12个单词非常重要,最好用物理方式备份,备份时身边不要有任何人。

      对于普通用户来说,如果只是一味的向他们强调助记词重要性的结论,而不告诉背后的原因的话,是很难调动起人的底层动力的,很可能过几天就忘了助记词的重要性(小编已经看过不少在群里呼唤自己因为助记词丢失而导致破产的杯具)。

      助记词的英文是Mnemonic,在大部分人的印象中,助记词=私钥,是导入钱包的工具,其实准确的说,助记词≥私钥,那么,助记词到底是从何而来,助记词到底有什么用呢?

    多账户需求

      我们先拿现实生活的例子打一个比方,通常来说,我们存在银行的钱都会有很多账户,有的账户用来买煎饼果子的零钱账户,有的是用来对公业务的账户,有的是存大额资产的账户。这些账户只需要一个身份证就能够办理,我们用一个身份证可以在网银上登录很多账户,万一银行卡不慎丢失了,也可以通过身份证进行补办。在现实生活中,身份证是无比重要的。

      而到了数字货币的世界,道理也是一样的,为了业务的方便,我们通常想要有多个账户,满足不同场景的需求。如果说每次创建账户都在公链上生成一个私钥,那就像每次办理银行卡都要记忆一长串的银行卡号一样的反人性。

    降低出错概率

      众所周知,在区块链世界里,只要保留了私钥,就能解锁账户,但是通常来说私钥是由64位的很长的字符串组成,如果我们想要创建100个账户,那我们就要记100个私钥,记录和保存成本是非常大的,况且,在记录海量的私钥的过程中,可能还会出现地址和私钥匹配不上的问题,或者记录时粗心大意写错字母的问题,等等,总而言之,出错的概率是很高的,由于数字货币这种“只认私钥不认人”的特点,这些小的错误很可能导致资产无法找回。

    保护隐私

      由于区块链公有链的公开的特性,链上的所有转账记录任何人都可以查到,如果同一个账户关联的收入支出太多了,最终是可以摸出一些线索,把你和一些交易给匹配起来的,所以为了隐私考虑,我们的很多隐私业务通常会创建出新的账户来进行交易。

      聪明的开发人员为了解决上述问题,提出了Bip39协议。Bip39协议的全称是Bitcoin protocol,最初是由比特币社区的开发者提出,后来被其他的主流区块链项目所认可,继而成为了整个行业共识和规范。

    BIP39协议的核心是,由12个单词来确定自己的账户,12个单词会生成很大的种子,从2的256次方选出一个数,由于随机生成的数是很大的,所以完全不用担心生成的12个单词会重复。

    12个单词生成的账户是固定的,拿到的12个单词就可以创建无数多的私钥、公钥和地址。

      用公式表示他们之间的关系,可写成如下形式:

      私钥=算法1(助记词)

      公钥=算法2 (私钥)

      公钥哈希=算法3 (公钥)

      地址=算法4 (公钥哈希)

      所以,地址=算法4(算法3(算法2(算法1(助记词))))

      当然,算法1、2、3、4都是公开的算法。

      通过密码学的保证,生成的单词顺序和内容是不可能会重复的,通过助记词,我们可以生成任意公链的地址,需要多少的地址就能够生成有多少个地址。

      大家可以通过https://iancoleman.io/bip39/生成助记词或者查看助记词对应的私钥、公钥和地址。

      并且,BIP39协议几乎是支持所有公链的。

           小豹拿小豹私人的Matemask的助记词做了个实验:

     

      可以看到,通过我的Matemask的助记词创建的账户地址,和网站生成的地址是一模一样的,就连顺序也是高度一致的。

    技术角度理解助记词

      从技术上的角度来说,BIP39 是通过12个助记词的单词序列,通过 PBKDF2 与 HMAC-SHA512 函数创建出随机种子作为 BIP32 的种子(通常是16进制的)。

      相比于BIP32协议,我们可以看出那一种备份起来更友好:

    //BIP32 随机数种子090ABCB3A6e1400e9345bC60c78a8BE7

    //BIP39 助记词种子

    candy maple cake sugar pudding cream honey rich smooth crumble sweet treat

    使用助记词作为种子其实包含2个部分:助记词生成及助记词推导出随机种子。

    生成助记词

      助记词生成的过程是这样的:先生成一个128位随机数,再加上对随机数做的校验4位,得到132位的一个数,然后按每11位做切分,这样就有了12个二进制数,然后用每个数去查BIP39定义的单词表,这样就得到12个助记词,这个过程图示如下:

    助记词推导出种子

      这个过程使用密钥拉伸(Key stretching)函数,被用来增强弱密钥的安全性,PBKDF2是常用的密钥拉伸算法中的一种。

    PBKDF2基本原理是通过一个为随机函数(例如 HMAC 函数),把助记词明文和盐值作为输入参数,然后重复进行运算最终产生生成一个更长的(512 位)密钥种子。这个种子再构建一个确定性钱包并派生出它的密钥。

      密钥拉伸函数需要两个参数:助记词和盐。盐可以提高暴力破解的难度。 盐由常量字符串 “mnemonic” 及一个可选的密码组成,注意使用不同密码,则拉伸函数在使用同一个助记词的情况下会产生一个不同的种子,这个过程图示图下:

    助记词推动了区块链的普及

      有了助记词之后,任何一笔交易,我们都可以创建一个新的账户,账户里面的钱也可以进行自由转移。

      在区块链世界,只需要记住12个简单的助记词,就间接记住了所有区块链上的资产,就如同现实生活中的身份证一样便捷、高效。

      有了助记词之后,大大减轻了普通用户的使用成本,从体验上来说对用户是非常友好的,这拉进了区块链与普罗大众的距离,同时也满足了高端用户的隐私问题。可以这么说,BIP39协议大大的推动了区块链的普及。

      可能有人会说,“我最讨厌英语啊,让我记12个英语单词,简直就是要了我的命啊!”

      对于这部分用户我先不做评价,但是聪明的开发者早就想到了这一点,BIP39协议目前是支持了几大主流语言的,英语、日语、西班牙语、韩语、法语,当然也少不了中文,所以大家完全可以用12个汉字作为助记词。

      如果连12个汉字都懒得记的话,那我只能说,施主,区块链世界实在与你无缘,找个好人就嫁了吧。

      当然,任何事物都有两面性,助记词虽然带来了许多便利,但是,也诞生了另外的安全隐患,那就是一旦12个单词泄露,或者被黑客获取,助记词是未经加密的私钥,任何人得到了你的助记词,可以不费吹灰之力的夺走你的资产控制权。

    助记词是利用固定算法,将我们64位的私钥转换成十多个常见的英文单词,单词由私钥和固定的算法在固定的词库里选出。助记词和私钥是互通的,可以相互转换,它只是私钥的一种容易记录的表现形式。所以在此强调:助记词即私钥!助记词即私钥!助记词即私钥!一般助记词只会在钱包开通时出现一次,后面就再也不会出现了,所以开通钱包时就要做好备份。

      最后再说一下 Keystore。Keystore 在以太坊钱包App中比较常见,它是把私钥通过钱包密码再加密得来的,一般可保存为文本或json格式。换句话说,Keystore 需要用钱包密码解密后才等同于私钥。因此,Keystore需要配合钱包密码来使用,才能导入钱包。

      如果我们忘记了钱包密码,备份的Keystore就没有用了,只能用私钥或者助记词来重新导入钱包设定钱包密码。那我们要Keystore干嘛呢,直接用私钥或助记词不更好吗?实际上,没忘记密码的情况下,私钥和助记词要尽量少用,能少见光就少见光,用Keystore加密码的方式导入钱包会更安全些。逻辑有些类似于把鸡蛋放到两个篮子里。

     

    展开全文
  • 什么是HSE? HSE是什么?

    千次阅读 2020-12-24 14:15:47
    原标题:什么是HSE? HSE是什么?什么是HSE? HSE是什么? 对HSE一无所知的人问:什么是HSE? 整天泡在HSE的人想:HSE是什么?什么是HSE?HSE是Health(健康)、Safety(安全)、Environment(环境)的英文缩写。不同的地方...
  • RPA是干什么的?

    万次阅读 2020-09-18 12:56:06
    一句话让你明白这个技术是什么,一种模拟电脑鼠标键盘操作且可以代替人进行重复性、规则化电脑端操作的技术,展开想象会发现能够应用在公司各个部门各个业务线,简单整理如下: 感觉还是很迷糊? 钉钉跟客户发...
  • NLP系列(10)_向量之图解Word2vec

    万次阅读 多人点赞 2019-04-08 09:58:35
    通过观察数值我们看不出什么,但是让我们稍微给它可视化,以便比较其它向量。我们把所有这些数字放在一行: 让我们根据它们的值对单元格进行颜色编码(如果它们接近2则为红色,接近0则为白色,接近-2则为蓝色)...
  • 新名词|什么是「电源」程序员?

    万次阅读 多人点赞 2020-03-27 13:08:07
    什么是计算机系统 计算机系统(A computer system) 是由硬件和软件组成的,它们协同工作运行程序。不同的系统可能会有不同实现,但是核心概念是一样的,通用的。 不同的系统有 Microsoft Windows、Apple Mac OS X、...
  • 首先基于词频、文档频、位置及关联性构建词条特征模型,重点研究了位置属性及关联性属性的权值计算方法,改进了Apriori算法用于关联性属性权值计算;然后通过改进的K-means聚类算法对词条特征模型...
  • 挖掘连续属性可能揭示数据的内在联系,包含连续属性的关联规则通常称作量化关联规则(quantitative association rule)。 主要讨论三种对连续数据进行关联分析的方法 基于离散化的方法基于统计学的方法非离散化...
  • mysql指引(一):mysql到底是什么

    千次阅读 2020-01-13 21:06:15
    主要讲mysql是什么,重点理清“关系”这个概念
  • ctrl+alt+空格 在建议完成模式和标准完成模式之间切换。
  • 从本专栏开始,作者正式研究Python深度学习、神经网络及人工智能相关...本篇文章将分享《人工智能狂潮》书籍内容,包括人工智能的应用和什么是人工智能,结合作者的理解分享人工智能原理及基础知识,希望对您有所帮助!
  • 本文是一篇翻译文章,原文地址(https://towardsdatascience.com/why-do-we-use-embeddings-in-nlp-2f20e1b632d2). ...比如当我读到"猫"这个时,就会想象到许多内容——它是一种可爱的小毛茸茸的动物,喜
  • 本文主要从什么是微信小程序、微信小程序的介绍、小程序开发流程、小程序代码构成、小程序安装使用、配置分析、开发特点、实战项目等多角度手把手带你详解微信小程序。 第一章-什么是微信小程序?(以下都有直接用...
  • 【离散数学】2.1什么是命题

    千次阅读 2017-12-03 02:02:16
    什么是命题定义具有确切真值的陈述句称为命题(proposition)。该命题可以取一个“值”,称为真值。真值只有“真”和“假”两种,分别用“T”(或“1”)和“F”(或“0”)表示。举例 成都是一个旅游城市。真值:T ...
  • 用三个来描述你对人生的理解

    千次阅读 2020-03-31 22:05:32
    如果让你用最简短的三个来描述你对人生的理解,或者你认为人活一辈子最关键的三个什么
  • 什么是敏捷开发?

    万次阅读 多人点赞 2019-05-31 10:49:56
    DevOps 是 Development 和 Operations 的合成,其目标是要加强开发人员、测试人员、运维人员之间的沟通协调。如何实现这一目标呢?需要我们的项目做到持续集成、持续交付、持续部署。 时下流行的 Jenkins、...
  • word2vec向量训练及中文文本相似度计算

    万次阅读 多人点赞 2016-02-18 00:35:41
    本文是讲述如何使用word2vec的...Word2vec是Google公司在2013年开放的一款用于训练向量的软件工具。它根据给定的语料库,通过优化后的训练模型快速有效的将一个词语表达成向量形式,其核心架构包括CBOW和Skip-gram。
  • 文章目录什么是词嵌入?嵌入算法如何使用嵌入 什么是词嵌入? 嵌入是文本的学习表示,其中具有相同含义的单词具有相似的表示。 正是这种表示单词和文档的方法可能被认为是深度学习在具有挑战性的自然语言处理...
  • 什么是网络位,什么是主机位 位:二进制数字 (Bit: Binary Digit) A Bit is an abbreviation of "Binary Digit". 位是“二进制数字”的缩写 。 It is the smallest basic unit of information, which is used to ...
  • 关于相似性以及文档特征、特征有太多种说法。弄得好乱,而且没有一个清晰逻辑与归类,包括一些经典书籍里面也分得概念模糊,所以擅自分一分。 ————————————————————————————...
  • idea关联maven的使用

    千次阅读 2021-03-25 17:46:50
    maven简介: ...由于Maven的面向项目的方法,许多ApacheJakarta项目在发布时都使用Maven,并且采用Maven的公司项目所占的比例持续增长。Maven Maven这个来自意第绪语(犹太语),意思是知识的积累,最
  • 根据想要的关键词爬取AppStore中的所有联想,可以根据需要设计递归层级,代码中默认3层,比如输入“斗地主”可获取1000多个跟斗地主相关的联想并且打印关联度,通过Python实现
  • 挖掘商品关联性(2): FP-growth算法

    千次阅读 2019-05-28 11:38:47
    在前面我们介绍了一种简单的挖掘商品关联性算法Aporiori算法。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 89,406
精华内容 35,762
关键字:

并且是什么关联词