MENU

Doc2Vecを利用してモデルの作成

import zipfile

import os.path

import urllib.request as req

import MeCab

from gensim import models

from gensim.models.doc2vec import TaggedDocument

 

#Mecabの初期化

mecab = MeCab.Tagger()

mecab.parse("")

 

#学習対象とする青空文庫の作品リスト --- (*1)

list = [

    {"auther":{

        "name":"宮澤 賢治",

        "url":"https://www.aozora.gr.jp/cards/000081/files/"},

     "books":[

        {"name":"銀河鉄道の夜","zipname":"43737_ruby_19028.zip"},

        {"name":"注文の多い料理店","zipname":"1927_ruby_17835.zip"},

        {"name":"セロ弾きのゴーシュ","zipname":"470_ruby_3987.zip"},

        {"name":"やまなし","zipname":"46605_ruby_29758.zip"},

        {"name":"どんぐりと山猫","zipname":"43752_ruby_17595.zip"},

    ]},

    {"auther":{

        "name":"芥川 竜之介",

        "url":"https://www.aozora.gr.jp/cards/000879/files/"},

     "books":[

        {"name":"羅生門","zipname":"127_ruby_150.zip"},

        {"name":"鼻","zipname":"42_ruby_154.zip"},

        {"name":"河童","zipname":"69_ruby_1321.zip"},

        {"name":"歯車","zipname":"42377_ruby_34744.zip"},

        {"name":"老年","zipname":"131_ruby_241.zip"},

    ]},

    {"auther":{

        "name":"ポー エドガー・アラン",

        "url":"https://www.aozora.gr.jp/cards/000094/files/"},

     "books":[

        {"name":"ウィリアム・ウィルスン","zipname":"2523_ruby_19896.zip"},

        {"name":"落穴と振子","zipname":"1871_ruby_17551.zip"},

        {"name":"黒猫","zipname":"530_ruby_20931.zip"},

        {"name":"群集の人","zipname":"56535_ruby_69925.zip"},

        {"name":"沈黙","zipname":"56537_ruby_70425.zip"},

    ]},

    {"auther":{

        "name":"紫式部",

        "url":"https://www.aozora.gr.jp/cards/000052/files/"},

     "books":[

        {"name":"源氏物語 01 桐壺","zipname":"5016_ruby_9746.zip"},

        {"name":"源氏物語 02 帚木","zipname":"5017_ruby_9752.zip"},

        {"name":"源氏物語 03 空蝉","zipname":"5018_ruby_9754.zip"},

        {"name":"源氏物語 04 夕顔","zipname":"5019_ruby_9761.zip"},

        {"name":"源氏物語 05 若紫","zipname":"5020_ruby_11253.zip"},

    ]},

]

 

#作品リストを取得してループ処理に渡す --- (*2)

def book_list():

    for novelist in list:

        auther = novelist["auther"]

        for book in novelist["books"]:

            yield auther, book

        

#Zipファイルを開き、中の文書を取得する --- (*3)

def read_book(auther, book):

    zipname = book["zipname"]

    #Zipファイルが無ければ取得する

    if not os.path.exists(zipname):

        req.urlretrieve(auther["url"] + zipname, zipname)

    zipname = book["zipname"]

    #Zipファイルを開く

    with zipfile.ZipFile(zipname,"r") as zf:

        #Zipファイルに含まれるファイルを開く。

        for filename in zf.namelist():

            # テキストファイル以外は処理をスキップ

            if os.path.splitext(filename)[1] != ".txt":

                continue

            with zf.open(filename,"r") as f:

                #今回読むファイルはShift-JISなので指定してデコードする

                return f.read().decode("shift-jis")

 

#引数のテキストを分かち書きして配列にする ---(*4)

def split_words(text):

    node = mecab.parseToNode(text)

    wakati_words =

    while node is not None:

        hinshi = node.feature.split(",")[0]

        if  hinshi in ["名詞"]:

            wakati_words.append(node.surface)

        elif hinshi in ["動詞", "形容詞"]:

            wakati_words.append(node.feature.split(",")[6])

        node = node.next

    return wakati_words

 

#作品リストをDoc2Vecが読めるTaggedDocument形式にし、配列に追加する --- (*5)

documents =

#作品リストをループで回す

for auther, book in book_list():

    #作品の文字列を取得

    words = read_book(auther, book)

    #作品の文字列を分かち書き

    wakati_words = split_words(words)

    #TaggedDocumentの作成 文書=分かち書きにした作品 タグ=作者:作品名

    document = TaggedDocument(

        wakati_words, [auther["name"] + ":" + book["name"]])

    documents.append(document)

    

#TaggedDocumentの配列を使ってDoc2Vecの学習モデルを作成 --- (*6)

model = models.Doc2Vec(

    documents, dm=0, vector_size=300, window=15, min_count=1)

 

#Doc2Vecの学習モデルを保存

model.save('aozora.model')

 

print("モデル作成完了")

 

-----------------------------------------------------------------------------------

 

このプログラムではZipファイルをダウンロードして、文学作品を読み込んで学習し、学習した結果を[aozora.model](モデルの作成)というファイルに保存するプログラムである。

 

処理の実行順に明記していく

モジュールインポート

import zipfile

import os.path

import urllib.request as req

import MeCab

from gensim import models

from gensim.models.doc2vec import TaggedDocument

 

import zipfile

zipファイルというzipというデータ圧縮のフォーマットで圧縮されたファイルのことで例えば、[aaaaaaaaaa]を[a10]と表現することで7文字少なく表現することができる。このファイルを解凍するためにimport zipfileで標準ライブラリのzipfileを使用可能にしてzipファイルの圧縮、解凍を行えるようにしている。

 

import os.path

os.pathではファイルやディレクトリが指定したパスに存在するかどうか確認できたり、パスからファイル名や拡張子を取得することができるモジュールである。

 

import urllib.request as req

urllibモジュールはpythonでURLを扱うモジュールで、urllib.requestモジュールはHTTPリクエストに特化したモジュールである。

 

from gensim.models.doc2vec import TaggedDocument

Doc2Vecの学習にはTaggedDocumentクラスのオブジェクトが必要なのでインスタンスを作成できるようインポートする。

 

MeCabの初期化

mecab = MeCab.Tagger()

mecab.parse("")

 

学習対象とする青空文庫の作品リストの情報を持ったリストの作成

list = [

    {"auther":{

        "name":"宮澤 賢治",

        "url":"https://www.aozora.gr.jp/cards/000081/files/"},

     "books":[

        {"name":"銀河鉄道の夜","zipname":"43737_ruby_19028.zip"},

        {"name":"注文の多い料理店","zipname":"1927_ruby_17835.zip"},

        {"name":"セロ弾きのゴーシュ","zipname":"470_ruby_3987.zip"},

        {"name":"やまなし","zipname":"46605_ruby_29758.zip"},

        {"name":"どんぐりと山猫","zipname":"43752_ruby_17595.zip"},

    ]},

    {"auther":{

        "name":"芥川 竜之介",

        "url":"https://www.aozora.gr.jp/cards/000879/files/"},

     "books":[

        {"name":"羅生門","zipname":"127_ruby_150.zip"},

        {"name":"鼻","zipname":"42_ruby_154.zip"},

        {"name":"河童","zipname":"69_ruby_1321.zip"},

        {"name":"歯車","zipname":"42377_ruby_34744.zip"},

        {"name":"老年","zipname":"131_ruby_241.zip"},

    ]},

    {"auther":{

        "name":"ポー エドガー・アラン",

        "url":"https://www.aozora.gr.jp/cards/000094/files/"},

     "books":[

        {"name":"ウィリアム・ウィルスン","zipname":"2523_ruby_19896.zip"},

        {"name":"落穴と振子","zipname":"1871_ruby_17551.zip"},

        {"name":"黒猫","zipname":"530_ruby_20931.zip"},

        {"name":"群集の人","zipname":"56535_ruby_69925.zip"},

        {"name":"沈黙","zipname":"56537_ruby_70425.zip"},

    ]},

    {"auther":{

        "name":"紫式部",

        "url":"https://www.aozora.gr.jp/cards/000052/files/"},

     "books":[

        {"name":"源氏物語 01 桐壺","zipname":"5016_ruby_9746.zip"},

        {"name":"源氏物語 02 帚木","zipname":"5017_ruby_9752.zip"},

        {"name":"源氏物語 03 空蝉","zipname":"5018_ruby_9754.zip"},

        {"name":"源氏物語 04 夕顔","zipname":"5019_ruby_9761.zip"},

        {"name":"源氏物語 05 若紫","zipname":"5020_ruby_11253.zip"},

    ]},

]

 

今回は「青空文庫」にあるテキストを利用するので青空文庫には、著作権が消滅した多くの作品が公開されており、ZIP形式でダウンロードできる。

 

作品リストをDoc2Vecが読めるTaggedDocument形式にし、配列を追加する

documents =

#作品リストをループで回す

for auther, book in book_list():

    #作品の文字列を取得

    words = read_book(auther, book)

    #作品の文字列を分かち書き

    wakati_words = split_words(words)

    #TaggedDocumentの作成 文書=分かち書きにした作品 タグ=作者:作品名

    document = TaggedDocument(

        wakati_words, [auther["name"] + ":" + book["name"]])

    documents.append(document)

 

for auther, book in book_list()でbook_list()関数から文学作品の情報を取得し、read_book()関数でZipファイルを開き、文書の文字列を取得する。split_words()で作品の文字列を分かち書きにしてそのデータをTaggedDocument()に渡すことでDoc2Vecを使用したモデルの作成に必要なTaggerdDocumentオブジェクトの配列を作成している。

TaggedDocument(単語文字列, タグ)タグは第一引数で渡した文字列を表す名前。

 

book_list()関数、作品リストを取得してループ処理に渡す関数

def book_list():

    for novelist in list:

        auther = novelist["auther"]

        for book in novelist["books"]:

            yield auther, book

 

この関数では文学作品の情報を持った変数listから作者名、ファイル先のURLを格納したauther、作品名とファイル名が格納してあるbooksを取得し、呼び出し元のループ処理に渡している。

yieldでは関数を一時的に実行停止させることができる機能を持つ文である。その時点での戻り値を返し、また処理を再開させることができる。

returnでは一度に大きなデータを持ったリストなどを返すと一度にたくさんのメモリを消費してしまことになってしまう。yieldによってその都度処理を停止して少量ずつデータを送ることで消費メモリを抑えることができる。

 

read_book()関数、zipファイルを開き、中身の文書を取得する関数

def read_book(auther, book):

    zipname = book["zipname"]

    #Zipファイルが無ければ取得する

    if not os.path.exists(zipname):

        req.urlretrieve(auther["url"] + zipname, zipname)

    zipname = book["zipname"]

    #Zipファイルを開く

    with zipfile.ZipFile(zipname,"r") as zf:

        #Zipファイルに含まれるファイルを開く。

        for filename in zf.namelist():

            # テキストファイル以外は処理をスキップ

            if os.path.splitext(filename)[1] != ".txt":

                continue

            with zf.open(filename,"r") as f:

                #今回読むファイルはShift-JISなので指定してデコードする

                return f.read().decode("shift-jis")

 

book_list()関数から渡されたauther(作者、ファイル先のURL),books(作品名、ファイル名)を引数として受け取っている。

 

  if not os.path.exists(zipname):では指定したパスが存在しているかどうかの確認を行っている。ファイルでもディレクトリでも存在すればTrue、存在しなければFalseを返す。

 

req.urlretrive()はネット上からファイルをダウンロードし保存するために使う。

urlretrive(目的のURL, 保存先のファイル名)と指定する。

 

with zipfile.ZipFile(zipname,"r") as zf:ではzipファイルを解凍し、zfに格納している。

for filename in zf.namelist():のzf.namelist()ではzipファイルの中身のファイルを取得することができる。

 

if os.path.splitext(filename)[1] != ".txt":

                continue

このif文でテキストファイル以外の時処理をスキップしている。os.path.splitext()ではファイルやフォルダ名から拡張子を取得することができるメソッドである。

 

with zf.open(filename,"r") as f:で処理したファイル名を指定してファイルを開いている。

 

return f.read().decode("shift-jis")ではread()でファイルを読み込んで、decode()では読み込むファイルはShift-JISなので指定してデコードしている。元のデータ形式から変換することを「エンコード」、エンコードされた形式から元の形式に戻すことをデコードという。今回は読み込んだファイルがShift-JISで書かれたものなのでそれに合わせた処理をして呼び出し元にファイルの中身の文字列を返している。

 

split_words()関数、引数のデータを分かち書きにして配列にする

def split_words(text):

    node = mecab.parseToNode(text)

    wakati_words =

    while node is not None:

        hinshi = node.feature.split(",")[0]

        if  hinshi in ["名詞"]:

            wakati_words.append(node.surface)

        elif hinshi in ["動詞", "形容詞"]:

            wakati_words.append(node.feature.split(",")[6])

        node = node.next

    return wakati_words

 

引数で受け取った文字列をMeCabを使用して分かち書きににして配列にしている。

if文で条件分岐することでストップワードの除去を行っている。

 

TaggedDocumentの配列を使ってDoc2Vecの学習モデルを作成

model = models.Doc2Vec(

    documents, dm=0, vector_size=300, window=15, min_count=1)

 

第一引数にTaggedDocumentの配列, dmはDec2Vecで使用するアルゴリズムの選択(1=dmpw、0=DBOW), vector_sizeはベクトルの次元の設定でDec2Vecでは基本的に300がいいとされている, windowは学習する単語の前後数(DROWでは15がいいとされている), min_countは最低何回出てきた文字列を対象とするかの設定(今回は作家ごとに独特の言い回しがあると考えられるので、一回でも出てきた文字列を対象にしている)

 

 

 

 

学習モデル保存

model.save('aozora.model')