MENU

単語の類似度の出力と単語の計算 (Word2Vec)

wikipediaから取得したデータで学習させたモデルを使って、単語の類似語を出力するプログラム。

f:id:hanamichi_sukusuku:20210119185627p:plain

実行結果

f:id:hanamichi_sukusuku:20210119185656p:plain

モジュールインポート

word2vecは単語をベクトル化させるライブラリ。

 

モデルの読み込み

word2vec.Word2Vec.load()でwikipediaから取得したデータから学習させたモデルを用意しておき読み込む。

 

単語から類似度が高いものを取り出す

 

model.wv.most_similar()では学習したモデルに任意の単語を渡し類似するものを取得できる。

model.wv['業務’]では単語から訓練されたベクトルが取得できる。

こんな感じで

f:id:hanamichi_sukusuku:20210119193505p:plain

今回のmodel.wv.most_similar(positive=['業務'])では単語と類似度の高いものを取得する。逆に(negative=['業務'])を指定すると類似度の低いものを取得する。

 

 

出力

類似度の高いもののリストデータをfor文で出力。

 

 

次に単語の計算を行っていく

f:id:hanamichi_sukusuku:20210119193950p:plain

実行結果

f:id:hanamichi_sukusuku:20210119201212p:plain



モジュールのインポートとモデル読み込みは類似度出力と同じでmost_similar()のオプションだけ変わっている。

 

計算にはpositiveに足すもの、negativeに引くものを指定する。

おそらくpositiveに指定したものと類似度が高いもの、negativeで指定したものと類似度が低いものを出力しており、つまり王、女のベクトルから男のベクトルを引いた単語を取得している。

式で表すと

王 ー 男 + 女 =女王

になる

 

一連の流れについては理解できたが予想もつかなかった結果が多く出てきているのでこれはコーパスを改善するなどの余地がある。

 

 

 

余談

生成したオブジェクトにデータを渡す前にオブジェクト.parse("")を記述することがあるがそれはUnicodeDecodeErrorを避けることが出来るからである。おそらくプログラム内で標準で使用されている文字エンコードで初期化しているんだと思う。

こんな時

tagger = MeCab.Tagger("-d /var/lib/mecab/dic/mecab-ipadic-neologd")
tagger.parse("")

Word2Vec,学習モデル作成

単語をベクトル化するにはWord2Vecを使用する。

Word2Vecはディープラーニングの技術を利用して単語をベクトル化する技術である。

大量の文章から単語をベクトル化し単語の意味を推測する。(単語の計算や単語の類似度を計算することができる)

 

Word2Vecは既に分かち書きになっている単語を読み込んで学習する理論なので分かち書きデータ(コーパス)を用意する。

学習モデルを作成するためにコーパスが必要。コーパスとはモデルを作成するための大量の分かち書きデータを含め、コンピューターによる検索が可能な大量の言語データのことである。

 

from gensim.models import word2vec
#コーパスの読み込み --- (*1)
sentences = word2vec.Text8Corpus('./wiki_wakati.txt')
#モデルの作成 --- (*2)
model = word2vec.Word2Vec(sentences, sg=0, size=100, window=5, min_count=5)
#モデルの保存 --- (*3)
model.save("./wiki.model")

今回のプログラムではWord2Vecを使用し単語の意味を計算するための学習モデルを作成していく。

 

モジュールインポート

Word2Vecw使用するには自然言語処理ライブラリーのgensimを利用する。これを使うとWord2Vecのような単語のベクトル化ができたり、文章をジャンル分するトピック分析をしたりと自然言語処理に役立つ。

 

コーパスの読み込み

word2vec.Text8Corpus()では分かち書きファイルを読み込んでコーパスとして使用できるようにしている。

 

モデルの作成

word2vec.Word2Vec()ではモデルを作成している。

第一引数にコーパス

sgはWord2Vecで使用するアルゴリズムの選択をしていて、1=Skip-gram,0=CBOW。簡単に

説明するとSkip-gramは精度が高く速度が遅い、CBOWはその逆(精度が低いとはいえ使えないというわけではないらしい)、今回はお試しなので速度の早いCBOWを選択している。

sizeはベクトルの次元数の設定。大体100~200ほどが一般的。多いほどモデルの作成の時間が増加する。

windowは学習する単語の前後数。Word2Vecの考え方として単語はその周りにある単語と関係があるというのがあるのでwindowの数が小さければ小さいほど関連付けがされにくくなる。

min_countは指定した数の回数出現した単語を破棄するというオプションである。精度をあげるため動詞、助動詞などの余計な単語を破棄している。

 

モデルの保存

save()で作成したモデルを保存。

 

 

 

単語の意味をベクトル化

単語ベクトル化を行うと「単語の意味を計算すること」や「単語の類似度を計算すること」ができるようになる。

例えば

「王」ー「男」+「女」=「女王」

この式は、王様のベクトルから、男性のベクトルを引き、女性のベクトルを足すと、女王のベクトルと親しいものになるということを表現している。

ここでいうベクトルというのは単語の意味を表現するのに一つの単語を認識ために複数の特徴でその単語の意味を認識できるようにしている。つまり単語をベクトルで表現していてベクトルとはその単語を示す特徴の一つと言える。よって上記の式が成り立つ。

 

分散表現

単語のベクトル表現は正式には分散表現と言い単語を高次元の実数ベクトルで表現する技術である。

高次元の実数ベクトルと言ってもピンと来ないので

単語
大きさ
甘さ
黄色さ
りんご
0.23
0.42
0.02
バナナ
0.21
0.73
0.94
0.89
0.04
0.10

 

このように一つの単語を複数の特徴と数字で表したもの。そして、この特徴は200~300個の項目にして表現する。

 

 

 

 

 

 

 

 

 

 

python ストップワードの除去

ストップワードとはあまりにも利用頻度が高い言葉であるために、処理対象外とする単語のことである。例えば助詞や助動詞など(が、の、です、ます)がそれに該当する。

どのような場面でストップワードの除去を行うかというと例えば形態素解析した結果を機械学習させて文章の意図を判定する場合、利用頻度が高いが意図の判定に利用できない単語は除去しておく方が精度を向上させることができる。

ストップワードの除去には様々な方法があるが今回のプログラムでは形態素解析の結果の品詞情報を利用して除去していく。

f:id:hanamichi_sukusuku:20210116223411p:plain

実行結果

f:id:hanamichi_sukusuku:20210116223135p:plain

名詞、動詞、形容詞の場合のみ出力することでストップワードを除去している。

 

parseToNode()メソッド

parse()メソッドが文字列を返すのに対してparseToNode()メソッドはMeCab.Nodeクラスオブジェクトを返す。parseToNode()メソッドに渡した文章はnodeクラスに格納されて解析結果を呼び出すにはクラスメソッドを使用する必要がある。つまり部分的に解析結果を取得したい場合はこのメソッドを使用する認識でいいと思う。

繰り返し処理で呼び出す必要があるのでwhile文を使用して要素を取得していく。

 

ストップワードを除去したリストの作成

result = []
while node is not None:
  # 品詞情報取得 --- (*2)
  hinshi = node.feature.split(",")[0]
   if hinshi in ["名詞"]:
  # 表層形の取得 --- (*3)
    result.append(node.surface)
   elif hinshi in ["動詞", "形容詞"]:
  # 形態素情報から原形情報を取得 --- (*4)
     result.append(node.feature.split(",")[6])
  node = node.next

変数nodeがNoneでない時間繰り返し処理をする。

feature()メソッドで品詞情報の取得。feature()メソッドでは単語の表層形以外の情報を文字列で取得できる。

 

今回のwhile文の中でprint(node.feature)を行った結果

f:id:hanamichi_sukusuku:20210116230017p:plain

表層形以外の情報を文字列で取得している。

 

これをsplitメソッドで[,]で区切ったリストを作成し直し、インデックス番号が0(リストの先頭)の要素を取得することで品詞情報を取得している。

 

if hinshi in ["名詞"]:の部分では変数hinshiに["名詞"]が含まれている時、surface()メソッドで表層形を取得し、最終的に出力するリストに追加している。

 

elif hinshi in ["動詞", "形容詞"]:の部分では変数hinshiに動詞、形容詞が含まれている時feature()メソッドを利用して原型情報を取得してリストに追加している。ストップワードの除去には関係ないが動詞や形容詞の場合、送り仮名などに違いが出るためこのように原型を取得してリストに追加している。

 

node.nextでオブジェクト内の次の要素に処理を行うため次のデータをnode.nextで取得し変数nodeに格納することで無限ループの阻止と次のデータの処理を行っている。

 

 

 

 

 

 

 

 

 

 

 

python 形態素解析(mecab-ipadic-NEologdの使用)

mecad-ipadic-NEologdとは新し語や固有表現を追加することでIPADICを拡張したMeCab用のシステム辞書である。

毎週2回更新されていて、はてなキーワードやニュース記事などから新しい情報資源から単語を抽出して辞書を作成している。

f:id:hanamichi_sukusuku:20210115204410p:plain

実行結果

f:id:hanamichi_sukusuku:20210115204434p:plain

MeCabで利用するシステム辞書を変更するにはMeCabオブジェクト生成時に-dオプションを指定し、辞書ファイルが保存されているパスを指定する。(事前にmecab-ipadic-neologdをインストールしてある)

MeCabでは大きく分けてシステム辞書とユーザー辞書といった二つの辞書が利用できる。システム辞書を変更したいときは-dオプション、ユーザー辞書を利用したいときは-uオプションを指定する。

 

parse()メソッドで形態素解析を行い結果を出力する。

 

今回では恋ダンスという言葉が恋とダンスで分かれずに出力されていることがわかる。

 

python 文章を単語に分割する(形態素解析)

まず形態素解析について

形態素解析とは対象言語の文法辞書や、単語辞書(品詞情報などが付与された単語一覧)に基づいて、意味を持つ最小単位(形態素)に文章を分割し、各形態素に品詞情報などを付与することである。(まだ意味わかんないと思う)

例えば英語であればit's, don'tなどの特定の単語をIt is , do not,に変化させるというルールに従って変換すれば容易に形態素に分解することができる。(少しイメージ湧いたかもくらいでいい)

まとめると文章を単語に分割する作業。

日本語は難しい。なぜなら単語と単語の区切りが明確ではないなど複雑だからである。

ただ研究が進み様々な形態素解析のライブラリがオープンソースで公開されている。

今回はその中でも代表的なMeCabを使用してみる。

 

f:id:hanamichi_sukusuku:20210114212758p:plain

実行結果

f:id:hanamichi_sukusuku:20210114212834p:plain

 

このように文章を分割してそれぞれを単語に分割して品詞、活用型、原型、読み、発音などの情報を取得できる。

 

プログラムに関しては

MeCab.Tagger()でオブジェクトを生成している。生成にはTagger()コンストラクターを利用する。様々なオプションが用意されており、例えばTagger('-Owakati')とするとわかち書きされた結果を出力できる。

f:id:hanamichi_sukusuku:20210114214808p:plain

このように単語と単語にスペースがある文章を出力できる。

 

 

tagger.parse()ではparse()メソッドを利用して形態素解析をしている。

parse()メソッドは指定された文字列を形態素解析し、Tagger()コンストラクターで指定されたフォーマットで結果を文字列で返す。

 

このようにMeCabを利用すると形態素解析を行えるが今回「恋ダンス」という単語を分割されていて、その要因としてはこの形態素解析には単語辞書を用いておりIPADICやUniDicという単語辞書が用いられている。しかし、これらは現在ほとんど更新されていないので新しい単語が含まれていない。結果として「恋ダンス」のように現在では一つの単語として認識して欲しいのでmecab-ipadic-NEologbという新しい単語に強い辞書を使ってみるといい。

 

 

 

 

 

 

python 利用されている文字が同じ言語の判定

import numpy as np
from sklearn.naive_bayes import GaussianNB
from sklearn.metrics import accuracy_score
import glob

# Unicodeのコードポイント頻度測定
def count_codePoint(str):
# Unicodeのコードポイントをアドレスとする配列を用意
counter = np.zeros(65535)

for i in range(len(str)):
# 各文字をUnicodeのコードポイントに変換
code_point = ord(str[i])
if code_point > 65535 :
continue
# 対応するアドレスの出現回数をインクリメント
counter[code_point] += 1

# 各要素を文字数で割って正規化
counter = counter/len(str)
return counter

# 学習データの準備 --- (*1)
index = 0
x_train =
y_train =
for file in glob.glob('./train/*.txt'):
# 言語情報の取得し、ラベルに設定 --- (*2)
y_train.append(file[8:10])
 
# ファイル内の文字列を連結後、Unicodeのコードポイント頻度測定し、入力データに設定 --- (*3)
file_str = ''
for line in open(file, 'r'):
file_str = file_str + line
x_train.append(count_codePoint(file_str))

# 学習する
clf = GaussianNB()
clf.fit(x_train, y_train)

# 評価データの準備 --- (*4)
index = 0
x_test =
y_test =
for file in glob.glob('./test/*.txt'):
# 言語情報の取得し、ラベルに設定
y_test.append(file[7:9])
 
# ファイル内の文字列を連結後、Unicodeのコードポイント頻度測定し、入力データに設定
file_str = ''
for line in open(file, 'r'):
file_str = file_str + line
x_test.append(count_codePoint(file_str))

# 評価する
y_pred = clf.predict(x_test)
print(y_pred)
print("正解率 = " , accuracy_score(y_test, y_pred))

実行結果

f:id:hanamichi_sukusuku:20210113211836p:plain

このプログラムではラテン文字(ローマ字)を使用する英語、スペイン語、ドイツ語の言語判定を行う。

前提としてtrainとtestというフォルダを同じ階層に作成し、その中にwikipediaから収集した各言語のデータを学習用(train)に3つ、テスト用(test)に1つずつ格納してある。

 

学習データの用意

index = 0
x_train =
y_train =
for file in glob.glob('./train/*.txt'):
  # 言語情報の取得し、ラベルに設定 --- (*2)
  y_train.append(file[8:10])
 
  # ファイル内の文字列を連結後、Unicodeのコードポイント頻度測定し、入力データに設定 --- (*3)
  file_str = ''
   for line in open(file, 'r'):
     file_str = file_str + line
  x_train.append(count_codePoint(file_str))

globモジュールではglob.glob()で引数に取得したいファイル名のパスを与えることでそのファイル名を取得できる。

glob.glob('./train/*.txt')では./同じ階層のtrainフォルダ(学習用の各言語のデータ)の中身の.txtとマッチするファイル名を全て取得している。

 

file[8:10]ではfileには./train/de_cat.txtや./train/es_dog.txtというファイル名が入っており、インデックス番号の8番目と9番目の文字を取得してラベルデータとして使用している。

 

open(file, 'r')では変数lineに1行ずつ文章を格納するのでfile_strに"を代入し空の変数を作成。そしてlineに格納されたfile内の文字列を連結してコードポイントの頻度を測定する関数の引数に渡す。返された値を学習用データの配列に追加。

 

コードポイントの頻度測定の関数

def count_codePoint(str):
  # Unicodeのコードポイントをアドレスとする配列を用意
  counter = np.zeros(65535)

  for i in range(len(str)):
  # 各文字をUnicodeのコードポイントに変換
   code_point = ord(str[i])
    if code_point > 65535 :
      continue
  # 対応するアドレスの出現回数をインクリメント
   counter[code_point] += 1

 # 各要素を文字数で割って正規化
 counter = counter/len(str)
  return counter

 

引数で測定する文字列を取得。

Unicodeのコードポイント分0の要素を持った配列を作成。

文字列の数だけ処理を行い、先頭から出現頻度をカウントしていく。

 

counter/len(str)で全体の文字数に対してどの割合なのかの数値にするため各要素を文字数で割る。

出現頻度の割合が格納された配列を呼び出し元に返す。

 

学習

clf = GaussianNB()
clf.fit(x_train, y_train)

 

評価データの準備

index = 0
x_test =
y_test =
for file in glob.glob('./test/*.txt'):
  # 言語情報の取得し、ラベルに設定
y_test.append(file[7:9])
 
  # ファイル内の文字列を連結後、Unicodeのコードポイント頻度測定し、入力データに設定
file_str = ''
  for line in open(file, 'r'):
   file_str = file_str + line
x_test.append(count_codePoint(file_str))

学習用データと同じようにtestフォルダからファイル名の一覧を取得。

テスト用のラベルデータにファイル名からそれぞれde,es,enを格納。

open()でファイルからデータを取得して1行ずつ取得するデータを連結。

file_str = "は空の変数を作成している。

文字の出現頻度を測定する関数にデータを格納した変数を渡す。

返り値をテストデータに追加。

 

評価

y_pred = clf.predict(x_test)
print(y_pred)
print("正解率 = " , accuracy_score(y_test, y_pred))