使用 Apache Spark 进行 Word2Vec

Word2Vec:词向量

Word2Vec是由谷歌研究所在2013年提出的一个算法,该算法基于假设”在相同上下文中使用的单词具有相同的含义”,使用向量来表示单词的特征。通过将单词及其含义向量化,语义上相似的单词被表示为在空间中靠近的向量,因此该算法可用于提取近义词。

主题 (Zhǔ tí)

这次我们将使用Apache Spark的MLlib,尝试使用Word2Vec。作为主题,我们将加载维基百科的“昭和”和“平成”等文章,然后将它们作为输入来生成模型。我们想要看看,当我们输入各种术语时,能得到哪些近义词。

核心程序 (natively in Chinese)

执行使用的是 spark-shell,但核心程序需要事先写在文件中,并加载并使用它。以下是该程序。

import java.io.StringReader
import org.apache.spark.mllib.feature.{Word2Vec, Word2VecModel}
import org.apache.lucene.analysis.ja.JapaneseTokenizer
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute
import org.apache.lucene.analysis.ja.tokenattributes.BaseFormAttribute
import org.apache.lucene.analysis.ja.tokenattributes.PartOfSpeechAttribute
import org.apache.spark.mllib.linalg.Vectors

import scala.collection.mutable.ArrayBuffer
def tokenize(sentence: String): Seq[String] = {
  val word: ArrayBuffer[String] = new ArrayBuffer[String]()
  lazy val stream = new JapaneseTokenizer(
    new StringReader(sentence),
    null,
    false,
    JapaneseTokenizer.Mode.NORMAL)

  try {
    while(stream.incrementToken()) {
      var charAtt = stream.getAttribute(
        classOf[CharTermAttribute]
      ).toString

      var bfAtt = stream.getAttribute(
        classOf[BaseFormAttribute]
      ).getBaseForm

      var partOfSpeech = stream.getAttribute(
        classOf[PartOfSpeechAttribute]
      ).getPartOfSpeech().split("-")(0)

      (partOfSpeech, bfAtt) match {
        case ("名詞", _)  => word += charAtt
        case ("動詞", null)  => word += charAtt
        case ("動詞", baseForm)  => word += baseForm
        case (_, _)  =>
      }
    }
  } finally {
    stream.close
  }
  word.toSeq
}

下载数据源

我們使用以下頁面的內容。我們只將內容粘貼到文本中,不包括HTML標籤。
這些文本文件將保存在/data/mllib/word2vec下。
昭和
平成
令和

启动 spark-shell

这次使用的是 Spark(版本2.1.1)。
使用 -i 选项来加载上述的 word2vec.scala 并启动 spark-shell。
这样在 spark-shell 中就可以使用 tokenize() 函数了。

/usr/hdp/2.6.2.0-205/spark2/bin/spark-shell --master local --deploy-mode client --packages org.apache.lucene:lucene-kuromoji:3.6.2 --conf spark.serializer=org.apache.spark.serializer.KryoSerializer -i /spark/src/mllib/word2vec.scala

执行命令

以下是在spark-shell中的命令:
1. 读取文本文件
读取预先准备好的文本文件并进行分词。

val input = sc.textFile("/data/mllib/word2vec").map(line => tokenize(line))

设置单词出现的下限数和生成单词向量的维度数量。

val word2vec = new Word2Vec()
word2vec.setMinCount(3)
word2vec.setVectorSize(30)

3. 模型的生成
通过以上步骤,模型将会完成。

val model = word2vec.fit(input)

试着寻找同义词

按顺序从得分高的开始,检测出五个项目。

model.findSynonyms("平成",5)
res2: Array((昭和,0.8683416787288002), (年,0.7191081645948658), (:,0.7109386795501776), (1970,0.6096518744108191), (日本,0.5882052253525422))

“昭和”作为与”平成”相似的词汇被检测到位于顶部(即使是偶然),这真是太棒了。
“:”这种意义不明的符号应该被预先移除,作为模型生成之前需要处理的文本。

model.findSynonyms("昭和",5)
res3: Array[(String, Double)] = Array((平成,0.8683416675677946), (年,0.7414342466252306), (現代,0.7280670946869915), (敗戦,0.6568551583363003), (1970,0.6326117643599328))

如果通过“昭和”进行搜索,会发现“平成”作为顶级同义词出现。
可能应该在文本中剔除明显表示时代的关键词,例如“1970”等。

model.findSynonyms("令和",5)
java.lang.IllegalStateException: 令和 not in vocabulary

生成的模型中应该频繁出现了“令和”这个词语,但是在所使用的lucene-kuromoji版本中没有将它注册为一个单词。这可能是因为所使用的版本较旧,导致无法对“令和”进行词法分析。

model.findSynonyms("元号",5)
res9: Array[(String, Double)] = Array((令,0.7863372339299479), (''',0.7558561207740144), (和,0.7263245520368361), (淑,0.7139302506327456), (曲,0.7057422726424456))

除了“令”排名第一和单引号排名第二之外,我们发现“和”排名第二作为“元号”的同义词被检测出来。虽然我们不知道这是否源自于原本的词汇“令和”,但结果十分有趣。

只输入了3页维基百科信息,但我发现隐约找到了相关的词语。
然而,增加信息量并不意味着提高准确性,因为额外的符号和数字等冗余信息存在,准确性会下降。
如何确保输入信息的一致性和高质量可能成为良好模型生成的一个关键要素。

广告
将在 10 秒后关闭
bannerAds