Word2Vec 简介

Word2vec是一个用于处理文本的双层神经网络。它的输入是文本语料,输出则是一组向量:该语料中词语的特征向量。

虽然 Word2vec 并不是深度神经网络,但它可以将文本转换为深度神经网络能够理解的数值形式。

Word2vec的应用不止于解析自然语句。它还可以用于基因组、代码、点赞、播放列表、社交媒体图像等其他语言或符号序列,同样能够有效识别其中存在的模式。

为什么呢?

因为这些数据都是与词语相似的离散状态,而我们的目的只是求取这些状态之间的转移概率,即它们共同出现的可能性。

所以gene2vec、like2vec和follower2vec都是可行的。而以下的教程就将介绍怎样为任何一组离散且同时出现的状态创建神经向量。

Word2vec 的目的和功用是在向量空间内将词的向量按相似性进行分组。它能够识别出数学上的相似性。Word2vec 能生成向量,以分布式的数值形式来表示词的上下文等特征。而这一过程无需人工干预。

给出足够的数据、用法和上下文,Word2vec 就能根据过去经验对词的意义进行高度准确的预测。

这样的预测结果可以用于建立一个词与其他词之间的联系(例如,“男人”和“男孩”的关系与“女人”和“女孩”的关系相同),或者可以将文档聚类并按主题分类。

而这些聚类结果是搜索、情感分析和推荐算法的基础,广泛应用于科研、调查取证、电子商务、客户关系管理等领域。

Word2vec神经网络的输出是一个词汇表,其中每个词都有一个对应的向量,可以将这些向量输入深度学习网络,也可以只是通过查询这些向量来识别词之间的关系。

Word2vec衡量词的余弦相似性,无相似性表示为 90° 角,而相似度为1的完全相似则表示为 0° 角,即完全重合;

例如,瑞典与瑞典完全相同,而挪威与瑞典的余弦距离为0.760124,高于其他任何国家。

神经词向量

我们用来表示词的向量称为神经词向量,这样的表示方式很奇特。

虽然是两种完全不同的事物,但却能用其中一种来描述另一种事物。

正如Elvis Costello所说:“写关于音乐的文字就像是跳关于建筑的舞蹈一样。Word2vec将词向量化,从而使自然语言能被计算机读取-这样我们就可以对词语进行复杂的数学运算来识别词之间的相似性。

所以神经词向量用数字来表示词。这是一种简单而又不可思议的“翻译”。

Word2vec 与自动编码器相似,它将每个词编码为向量,但 Word2vec 不会像受限玻尔兹曼机 那样通过重构输入的词语来定型, 而是根据输入语料中相邻的其他词来进行每个词的定型。

方式

具体的方式有两种,一种是用上下文预测目标词(连续词袋法,简称CBOW),另一种则是用一个词来预测一段目标上下文,称为 skip-gram 方法。我们使用后一种方法,因为它处理大规模数据集的结果更为准确。

2017-11-28-word2vec-cbow-skipgram.png

若指定给一个词的特征向量无法准确预测出这个词的上下文,则向量的要素会被调整。语料中每个词的上下文就是传回误差信号以调整特征向量的老师。若不同词的向量按上下文判定为相似,则会调整向量中的数字,使这些向量靠得更近。

类似的事物与概念也会比较“靠近”。他们的相对意义被转译为可衡量的距离。质量变为数量,让算法得以运行。但相似性只是Word2vec可以学习的许多种关联的基础。

举例而言,Word2vec还可以衡量一种语言的词语之间的关系,并且将这些词相互映射。

以这些向量为基础,可以得到更全面的词的几何分布。

罗马、巴黎、柏林、北京这些首都的名称不仅会聚集在一处,彼此靠近,同时每个词在向量空间内到其对应国家名称的距离也是相似的;

例如:罗马-意大利 = 北京-中国。如果你只知道意大利的首都是罗马,而不知道中国的首都在哪里,那么可以由计算式 罗马-意大利 + 中国 得出是北京。

有趣的结果

让我们来看看 Word2vec 可以得出哪些其他的关联。

我们不用加号、减号和等号,而是用逻辑类比符号表示结果,其中 : 代 表…与…的关系,而 :: 代表相当于

比如“罗马与意大利的关系相当于北京与中国的关系” = Rome:Italy::Beijing:China。

接下来我们不会直接提供“答案”,而是给出一个 Word2vec 模型在给定最初三个词后生成的词表:

king:queen::man:[woman, Attempted abduction, teenager, girl] 
//有点奇怪,但能看出有些关联

China:Taiwan::Russia:[Ukraine, Moscow, Moldova, Armenia]
//两个大国和与之相邻且关系微妙的较小国家或地区

house:roof::castle:[dome, bell_tower, spire, crenellations, turrets]

knee:leg::elbow:[forearm, arm, ulna_bone]

New York Times:Sulzberger::Fox:[Murdoch, Chernin, Bancroft, Ailes]
//Sulzberger-Ochs家族是《纽约时报》所有人和管理者。
//Murdoch家族持有新闻集团,而福克斯新闻频道为新闻集团所有。 
//Peter Chernin曾连续13年担任新闻集团的首席运营官。
//Roger Ailes是福克斯新闻频道的总裁。 
//Bancroft家族将华尔街日报出售给新闻集团。

love:indifference::fear:[apathy, callousness, timidity, helplessness, inaction]
//这个组合真是饱含诗意……

Donald Trump:Republican::Barack Obama:[Democratic, GOP, Democrats, McCain]
//有趣的是,就像奥巴马和麦凯恩是竞争对手一样,
//Word2vec认为特朗普也与共和党人这个概念对立。

monkey:human::dinosaur:[fossil, fossilized, Ice_Age_mammals, fossilization]
//人类是变成化石的猴子?人类是 
//猴子遗留下来的东西?人类是打败了猴子的物种,
//就像冰川世纪的哺乳动物打败了恐龙那样?好像有点道理。

building:architect::software:[programmer, SecurityCenter, WinPcap]

要知道,从未有人教过 Word2vec 算法任何一条英语句法规则。它对于世界一无所知,与一切符号逻辑和知识图并无关联。

但它学到的内容要多于大多数知识图经过多年人工作业得到的学习成果,而且是以灵活自动的方式进行学习。

它在开始接受谷歌新闻文档定型时只是白纸一张,而当定型结束后,Word2vec 模型已能计算出人类多少能够理解的复杂类比。

你也可以用一个Word2vec模型查询其他关联。并非所有关联都必须是两组相似的类比。(后文有具体说明……)

地缘政治:伊拉克 - 暴力 = 约旦
特性:人类 - 动物 = 伦理
总统 - 权力 = 总理
图书馆 - 书 = 大厅
类比:股市 ≈ 温度计

通过确定一个词与其他相似的词之间的近似度(两个词不一定包含同样的字母),我们让硬性的标记变为更连续且更全面的意义。

神经词向量

解析 DL4J 中的 Word2vec

下是Deeplearning4j的自然语言处理组件:

  • SentenceIterator/DocumentIterator:用于数据集的迭代。SentenceIterator返回字符串,而DocumentIterator则处理输入流。

  • Tokenizer/TokenizerFactory:用于对文本进行分词。在NLP术语中,一个句子表示为一系列词例(token)。TokenizerFactory为一个”句子”创建一个分词器(tokenizer)实例。

  • VocabCache:用于跟踪元数据,包括词频、含有某个词的文档的数量、词例的集合(不是词汇,而是已经出现过的词例),词汇(词袋以及词向量查找表中包含的特征)

  • 倒排索引:存储有关词的出现位置的元数据。可用于理解数据集。会自动创建以Lucene实现[1]的索引。

Word2vec指一类彼此相关的算法,而以下是采用Skip-Gram负样本采样模型的实现方法。

Simple Demo

完整代码示例

  • pom.xml

使用 maven 引入必须的 jar

<dependency>
    <groupId>org.deeplearning4j</groupId>
    <artifactId>deeplearning4j-core</artifactId>
    <version>0.8.0</version>
</dependency>
<dependency>
    <groupId>org.nd4j</groupId>
    <artifactId>nd4j-native-platform</artifactId>
    <version>0.8.0</version>
</dependency>
<dependency>
    <groupId>org.deeplearning4j</groupId>
    <artifactId>deeplearning4j-nlp</artifactId>
    <version>0.8.0</version>
</dependency>
  • Word2Vec.java
import org.deeplearning4j.models.embeddings.WeightLookupTable;
import org.deeplearning4j.models.embeddings.loader.WordVectorSerializer;
import org.deeplearning4j.models.word2vec.Word2Vec;
import org.deeplearning4j.text.sentenceiterator.LineSentenceIterator;
import org.deeplearning4j.text.sentenceiterator.SentenceIterator;
import org.deeplearning4j.text.sentenceiterator.SentencePreProcessor;
import org.deeplearning4j.text.tokenization.tokenizer.preprocessor.CommonPreprocessor;
import org.deeplearning4j.text.tokenization.tokenizerfactory.DefaultTokenizerFactory;
import org.deeplearning4j.text.tokenization.tokenizerfactory.TokenizerFactory;
import org.nd4j.linalg.api.ndarray.INDArray;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;

/**
 * 2017/11/28
 *
 * @author houbinbin
 * @version 1.0
 * @since 1.7
 */
public class Word2VecTest {

    public static void main(String[] args) throws IOException {
        final String filePath = "/Users/houbinbin/IT/dl4j-demo/src/main/resources/raw_english.txt";

        // 删去每行前后的空格
        SentenceIterator iter = new LineSentenceIterator(new File(filePath));

        //单词变小写
        iter.setPreProcessor(new SentencePreProcessor() {
            @Override
            public String preProcess(String sentence) {
                return sentence.toLowerCase();
            }
        });


        // 在每行中按空格分词
        // Word2vec的输入应是词,而非整句句子,所以下一步需要对数据进行分词。文本分词就是将文本分解为最小的组成单位,比如每遇到一个空格就创建一个新的词例。
        TokenizerFactory t = new DefaultTokenizerFactory();
        t.setTokenPreProcessor(new CommonPreprocessor());

        //模型定型
        Word2Vec vec = new Word2Vec.Builder()
                .minWordFrequency(5)
                .iterations(1)
                .layerSize(100)
                .seed(42)
                .windowSize(5)
                .iterate(iter)
                .tokenizerFactory(t)
                .build();
        System.out.println("Fitting Word2Vec model....");
        vec.fit();
        // batchSize是每次处理的词的数量。
        // minWordFrequency是一个词在语料中必须出现的最少次数。本例中出现不到五次的词都不予学习。
        // 词语必须在多种上下文中出现,才能让模型学习到有用的特征。对于规模很大的语料库,理应提高出现次数的下限。
        // useAdaGrad-AdaGrad为每个特征生成一个不同的梯度。在此处不需要考虑。
        // layerSize指定词向量中的特征数量,与特征空间的维度数量相等。以500个特征值表示的词会成为一个500维空间中的点。
        // iterations是网络在处理一批数据时允许更新系数的次数。迭代次数太少,网络可能来不及学习所有能学到的信息;迭代次数太多则会导致网络定型时间变长。
        // learningRate是每一次更新系数并调整词在特征空间中的位置时的步幅。
        // minLearningRate是学习速率的下限。学习速率会随着定型词数的减少而衰减。如果学习速率下降过多,网络学习将会缺乏效率。这会使系数不断变化。
        // iterate告知网络当前定型的是哪一批数据集。
        // tokenizer将当前一批的词输入网络。
        // vec.fit()让已配置好的网络开始定型。


        // 编写词向量
        WordVectorSerializer.writeWordVectors(vec, "pathToWriteto.txt");

        Collection<String> lst = vec.wordsNearest("god", 10);
        System.out.println("Closest Words:" + lst);
        System.out.println("similarity of day and lights" + vec.similarity("day", "lights"));


        // 保存模型
        // Deeplearning4j中保存模型的一般方法是使用序列化工具(Java的序列化与Python的腌制过程相似,将一个对象转换为一个字节的序列)。
        // 将把向量存入一个名为 pathToSaveModel.txt 的文件,保存在 Word2vec 定型所在目录的根目录中。文件中的输出应当是每行一个词,词后面跟着一个表示词向量的数字序列。
        WordVectorSerializer.writeWord2VecModel(vec, "pathToSaveModel.txt");


        // 要继续使用这些向量,只需按以下代码调用vec方法:
        // Word2vec最经典的词语算式例子是“king - queen = man - woman”(国王 - 王后 = 男人 - 女人),可由此推出“king - queen + woman = man”(国王 - 王后 + 女人 = 男人)。
        // 上述示例会输出距离king - queen + woman这一向量最近的10个词,其中应当包括 man。wordsNearest的第一个参数必须包括king和woman这两个“正的”词,它们都带有加号;第二个参数则包括queen这个“负的”词,带有减号(此处的正负不代表任何潜在的感情色彩);
        Collection<String> kingList = vec.wordsNearest(Arrays.asList("king", "woman"), Arrays.asList("queen"), 2);
        System.out.println("king - queen + woman = man  ====> " + kingList);
        // 参数可任意组合,但只有查询在语料中出现频率足够高的词,才能返回有意义的结果。显然,返回相似的词(或文档)的能力是搜索以及推荐引擎的基础。

        // 向量重新载入内存
        // 随后可以将Word2vec作为查找表:如果词不属于已知的词汇,Word2vec会返回一串零。
        Word2Vec word2Vec = WordVectorSerializer.readWord2VecModel("pathToSaveModel.txt");
        WeightLookupTable weightLookupTable = word2Vec.lookupTable();

        Iterator<INDArray> vectors = weightLookupTable.vectors();
        INDArray wordVectorMatrix = word2Vec.getWordVectorMatrix("day");
        double[] wordVector = word2Vec.getWordVector("day");
        System.out.println("wordVector.length: " + wordVector.length);
        System.out.println("wordVector: " + Arrays.toString(wordVector));

    }
}
  • raw_english.txt

测试时可以将此文件路径修改为你电脑上对应的路径,文件内容如下:

Good good study, and day day up
Good good study, and day day up
Good good study, and day day up
Good good study, and day day up
Good good study, and day day up
king queen
king queen
king queen
king queen
king queen
woman man
woman man
woman man
woman man
woman man
woman man
In the beginning God created the heavens and the earth.
The earth was formless and void, and darkness was over the surface of the deep, and the Spirit of God was moving over the surface of the waters.
Then God said, "Let there be light"; and there was light.
God saw that the light was good; and God separated the light from the darkness.
God called the light day, and the darkness He called night And there was evening and there was morning, one day.


Then God said, "Let there be an expanse in the midst of the waters, and let it separate the waters from the waters."
God made the expanse, and separated the waters which were below the expanse from the waters which were above the expanse; and it was so.
God called the expanse heaven. And there was evening and there was morning, a second day.
Then God said, "Let the waters below the heavens be gathered into one place, and let the dry land appear"; and it was so.
God called the dry land earth, and the gathering of the waters He called seas; and God saw that it was good.

Then God said, "Let the earth sprout vegetation, plants yielding seed, and fruit trees on the earth bearing fruit after their kind with seed in them"; and it was so.
The earth brought forth vegetation, plants yielding seed after their kind, and trees bearing fruit with seed in them, after their kind; and God saw that it was good.
There was evening and there was morning, a third day.
Then God said, "Let there be lights in the expanse of the heavens to separate the day from the night, and let them be for signs and for seasons and for days and years;
and let them be for lights in the expanse of the heavens to give light on the earth"; and it was so.

God made the two great lights, the greater light to govern the day, and the lesser light to govern the night; He made the stars also.
God placed them in the expanse of the heavens to give light on the earth,
and to govern the day and the night, and to separate the light from the darkness; and God saw that it was good.
There was evening and there was morning, a fourth day.
Then God said, "Let the waters teem with swarms of living creatures, and let birds fly above the earth in the open expanse of the heavens."

God created the great sea monsters and every living creature that moves, with which the waters swarmed after their kind, and every winged bird after its kind; and God saw that it was good.
God blessed them, saying, "Be fruitful and multiply, and fill the waters in the seas, and let birds multiply on the earth."
There was evening and there was morning, a fifth day.
Then God said, "Let the earth bring forth living creatures after their kind: cattle and creeping things and beasts of the earth after their kind"; and it was so.
God made the beasts of the earth after their kind, and the cattle after their kind, and everything that creeps on the ground after its kind; and God saw that it was good.

Then God said, "Let Us make man in Our image, according to Our likeness; and let them rule over the fish of the sea and over the birds of the sky and over the cattle and over all the earth, and over every creeping thing that creeps on the earth."
God created man in His own image, in the image of God He created him; male and female He created them.
God blessed them; and God said to them, "Be fruitful and multiply, and fill the earth, and subdue it; and rule over the fish of the sea and over the birds of the sky and over every living thing that moves on the earth."
Then God said, "Behold, I have given you every plant yielding seed that is on the surface of all the earth, and every tree which has fruit yielding seed; it shall be food for you;
and to every beast of the earth and to every bird of the sky and to every thing that moves on the earth which has life, I have given every green plant for food"; and it was so.

God saw all that He had made, and behold, it was very good. And there was evening and there was morning, the sixth day.
  • result

测试结果日志如下:

Fitting Word2Vec model....
Closest Words:[and, of, waters, to, from, the, on, their, expanse, let]
similarity of day and lightsNaN
king - queen + woman = man  ====> [so, evening]
wordVector.length: 100
wordVector: [0.12239271402359009, 0.18211302161216736, ..., -0.13165584206581116]

Process finished with exit code 0

中文案例

中文的特殊性在于,中文句子需要进行分词。可以使用结巴分词等工具,将所有标点符号去除。

红楼梦解读