pythonメニュー

未完成 検討中 tensorflow RNNによる深層学習 文章の生成

データの取得、管理で行った青空文庫の取得データ群から、指定の作家の文章を学習させて、 その後、その作者風の文章を生成させます。
以下の工程では、

学習する文章の準備

# -*- coding:utf-8 -*-
#<meta charset='utf-8'>

import argparse
import csv
import os
import sys
import zipfile
import pandas as pd
import re

# 青空文庫より得たデータ群
DEFAULT_INDEX_FILE_PATH = '../data/raw/aozorabunko/index_pages/list_person_all_extended_utf8.zip'
DEFAULT_INPUT_DIR =       '../data/parsed/aozorabunko/morph'

DEFAULT_AUTHOR_NAME = '江戸川乱歩' # 学習対象の作者 他の例→DEFAULT_AUTHOR_NAME = '芥川竜之介'

DEFAULT_OUTPUT_DIR = 'data/prepared/aozorabunko/generate/morph_3' # 学習データを格納するフォルダ
DEFAULT_OUTPUT_PATH = DEFAULT_OUTPUT_DIR + "/" +'train_3.csv'     # 学習データを記憶するファイル
ENCODING = 'utf-8' # 上記ファイルのキャラクタセット

z = zipfile.ZipFile(DEFAULT_INDEX_FILE_PATH)
with z.open(z.filelist[0]) as i_:
     index_file = pd.read_csv(i_)

print(DEFAULT_INDEX_FILE_PATH,'からpandas.core.frame.DataFrameに読み込みました。')
print(index_file)
print(index_file.info())
print(index_file[['作品ID','作品名','姓','名']][67:68])
print("'作品ID'の名前の'.txt'ファイルが作品内容で、「"+DEFAULT_INPUT_DIR +"」の所に存在する")
content_path = index_file['作品ID'].map(lambda x: '{}/{}.txt'.format(DEFAULT_INPUT_DIR,x))
print(content_path)
exists_only=index_file[content_path.map(os.path.exists)]
author_name = exists_only['姓'] + exists_only['名']
authors_only = exists_only[author_name == DEFAULT_AUTHOR_NAME]
paths = content_path[authors_only.index]
print(paths)
'''上記の実行例
        作品ID                     作品名                                   作品名読み        ...         XHTML/HTMLファイル符号化方式 XHTML/HTMLファイル文字集合 XHTML/HTMLファイル修正回数
0      56078                    駅伝馬車                                 えきでんばしゃ        ...                    ShiftJIS         JIS X 0208                0.0
1      56033               クリスマス・イーヴ                               クリスマス・イーヴ        ...                    ShiftJIS         JIS X 0208                0.0
2
...      ...
15927  45210                 純粋経済学要論                         じゅんすいけいざいがくようろん        ...                    ShiftJIS         JIS X 0208                0.0

[15928 rows x 55 columns]
'''
print("以上が",DEFAULT_AUTHOR_NAME,"の作品のパスです")

def rnn_data_generator(paths):
    prog = re.compile('[0-9]+')
    for path in paths:
        print("ファイル=====",path)
        with open(path, encoding=ENCODING) as i_:
            for line in i_:
                line = line.strip()
                if line == '' :continue
                if prog.match(line) :continue
                yield line
                
os.makedirs(DEFAULT_OUTPUT_DIR, exist_ok=True)
with open(DEFAULT_OUTPUT_PATH, 'w', encoding=ENCODING) as o_:
    generator = rnn_data_generator(list(paths.values))
    for line in generator:
       o_.write(line)
       print(line)

def test():
    g = rnn_data_generator(['../data/parsed/aozorabunko/morph/57105.txt'])
    for line in g:
       print(line)

#test()

文章の学習

# -*- coding: utf-8 -*-

# 参考 TensorFlowの活用ガイド(技術評論社)
# 参考 https://deepinsider.jp/tutor/introtensorflow/whatisrnn
#<meta charset='utf-8'>

import argparse
import os
import sys

import numpy as np
import tensorflow as tf
from tensorflow.python.layers.core import Dense

sys.path.append('..') # 親ディレクトリのモージュールを利用
import utils

#拡張子が.csvであるが、単なるテキストファイル
DEFAULT_DATA_PATH =  'data/prepared/aozorabunko/generate/morph/train.csv'

EMB_SIZE = 128
#EMB_SIZE = 64 #★
VOCAB_SIZE = 50000
VOCAB_SIZE = 40000 #★
HIDDEN_SIZE = 256
HIDDEN_SIZE = 96 #★
HIDDEN_SIZE = 192 #★
N_LAYERS = 2
BATCH_SIZE = 64
#BATCH_SIZE = 16 #★
MAX_LEN = 32
#MAX_LEN = 16 #★
STEPS = 100000
LOG_DIR = 'log'
MODEL_DIR = 'models/generate_rnn'

ENCODING = 'utf-8'

# http://testpy.hatenablog.com/entry/2017/05/07/122323
# 上記を参考にして、メモリアロケーションのサイズを減らして実行

def rnn_model(inputs, num_steps, rnn_cell, initial_state, embedding_size, vocab_size):
    """
    RNNによる文章生成モデル.学習時と推定時でネットワーク構造が異なる。

    Args:
        inputs (Tensor): [batch_size, max_sequence_length] の入力データ.
        num_steps (int): 出力データの最大長.
        rnn_cell (RNNCell): RNNセル.
        intitial_state (Tensor or tupple of Tensor): RNNの初期状態(型はRNNセルに依存する).
        embedding_size (int): 単語の埋め込み空間のサイズ.
        vocab_size (int): 語彙数(単語IDの最大値).
    Returns:
        学習時の出力、推定時の出力
    """
    with tf.variable_scope('decoder'):
        # 分類器と同様に単語の埋め込みをおこなう
        with tf.variable_scope('emb'):
            emb_w = tf.get_variable(
                'w',
                shape=(vocab_size, embedding_size),
                initializer=tf.contrib.layers.xavier_initializer()
            )
            emb = tf.nn.embedding_lookup(emb_w, inputs)
    
    # 学習用のネットワーク定義
    with tf.variable_scope('decoder'):
        # seq2seq.Decoder では、 output_layerを経由して入力値を調整する
        output_layer = Dense(vocab_size)
        # 学習時は TrainingHelper を用いる
        train_helper = tf.contrib.seq2seq.TrainingHelper(
            emb,
            tf.ones_like(inputs[:, 0])*num_steps
        )
        train_decoder = tf.contrib.seq2seq.BasicDecoder(
            rnn_cell,
            train_helper,
            initial_state,
            output_layer
        )
        train_outputs, _, _ = tf.contrib.seq2seq.dynamic_decode(
            train_decoder,
            impute_finished=True,
            maximum_iterations=num_steps
        )
    
    # 推定(文章生成)用のネットワーク定義
    with tf.variable_scope('decoder', reuse=True):
        # Helperを選ぶことで「次の単語として何を使うか」が決まる。
        inference_helper = tf.contrib.seq2seq.GreedyEmbeddingHelper(
            emb_w,
            inputs[:, 0],
            0
        )
        inference_decoder = tf.contrib.seq2seq.BasicDecoder(
            rnn_cell,
            inference_helper,
            initial_state,
            output_layer
        )
        inference_outputs, _, _ = tf.contrib.seq2seq.dynamic_decode(
            inference_decoder,
            impute_finished=True,
            maximum_iterations=num_steps
        )
    
    return train_outputs, inference_outputs

# text の文章内からランダムに始点を決めて、「batch_size個の語彙で構成される文」を抽出するジェネレータを返す。
# inputs、outputs の2つのnumpy配列(語彙に対応する番号)で、 
#   outputsの文は inputsの文に対いて、1つ語彙だけ後ろにずらした文になるようにしている。
def build_batch_generator(text, batch_size, max_len):
    n_tokens = len(text)
    while True:
        inputs = np.zeros((batch_size, max_len), dtype=np.int32)
        outputs = np.zeros((batch_size, max_len), dtype=np.int32)
        for e in range(batch_size):
            idx = np.random.randint(n_tokens - max_len)
            inputs[e] = text[idx: idx + max_len]
            outputs[e] = text[idx + 1: idx + max_len + 1]
        yield inputs, outputs



# 文章ファイルを読み込んで、textに記憶する。
with open(DEFAULT_DATA_PATH, encoding=ENCODING) as i_:
        text = i_.read()

print(text[0:100])
print("・・・・・省略・・・・・・・")
print(text[-100:])
print('以上が、{}のテキストファイル内容で、{}個の文字数'.format(DEFAULT_DATA_PATH, len(text)))
'''上記の実行例
ある に ち よう 日 の ご ご 、 丹下 サト子 ちゃん と 、 木村 ミドリ ちゃん と 、 野崎 サユリ ちゃん の 三 人 が 、 友だち の ところ へ あそび に 行っ た か えり に
・・・・・省略・・・・・・・
がり まし た 。 そして 、 みんな は おどりあがる よう に し て 叫ぶ の でし た 。「 少年 探偵 団 ばん ざあい ......。」「 チンピラ 別働隊 ばん ざあい ......。」
以上が、data/prepared/aozorabunko/generate/morph/train.csvのテキストファイル内容で、5598339個の文字数
'''
#sys.exit()

text_encoder = utils.TextEncoder()
text_encoder.build_vocab([text], VOCAB_SIZE) # text_encoder内に辞書を構築
text_encoder.print_info()   #情報表示
'''上記の実行例

build_vocabメソッドで文字列からトークンを構築します。
['、', 'の', 'て', 'た', 'に', '。', 'は', 'を', 'が', 'と', 'で', 'です', 'も', 'まし', 'し', 'な', 'だ', 'い', 'ない', 'か']
・・・省略・・・・
['黙想', '黙礼', '黙祷', '黙許', '黙認', '黯痣', '黴', '鼎', '鼬', '鼻たかだか', '鼻声', '鼻孔', '鼻毛', '鼻汁', '鼻炎', '鼻茸', '齎さ', '齎し', '齟齬', '龍之介']
上記[self.vocab]は、  で、この出現数の順で番号を付けた。
その[self.vocab_reversed]を以下で検証。(この番号はself.vocabの添え字)
0:、:0
1:の:1
2:て:2
3:た:3
4:に:4
5:。:5
6:は:6
・・・省略・・・・
39995:龍之介:39995
39994:齟齬:39994
39993:齎し:39993
--------------print_info 以上
'''

# エンコーダとデコーダを検証
txt_enc = text_encoder.encode("今 ごろ あの 紳士 は どうして ゐる かしら 。")
print(type(txt_enc), txt_enc)
print(text_encoder.decode( txt_enc ))
#sys.exit()
# [188, 801, 75, 755, 10, 317, 9813, 504, 9]
# 今 ごろ あの 紳士 は どうして ゐる かしら 。


txt_enc = text_encoder.encode(text)
print("最後の一部を出力したコー:",text_encoder.decode( txt_enc[-20:] ))
'''上記の実行例

最後の一部を出コード: よう に し て 叫ぶ の でし た 。「 少年 探偵 団 ばん ざあい ......。」「 チンピラ 別働隊 ばん ざあい ......。」

'''
gen = build_batch_generator(
    txt_enc,
    BATCH_SIZE,
    MAX_LEN
)
print("build_batch_generator :生成:", gen)
# build_batch_generator :生成: <generator object build_batch_generator at 0x000001E95F1F5360>

#inputs, output = next(gen)
#print( "実験batch inputs[0]***************" , inputs[0],text_encoder.decode( inputs[0] ) )
#print( "実験batch output[0]***************" , output[0],text_encoder.decode( output[0] ) )
#sys.exit()
'''上記を実行した場合の実行例(これが)

実験batch inputs[0]*************** [  665    12    59     7   414     8     4 18139     5    39    11   575
    11  3710     6   588  1295     7  9323     5   243    11   341     6
    21     7     9 18139     8    10  2000    12] 相違 が あっ た 為 に 、 沙漠 の 中 を 円 を 描い て 歩き 続け た 旅人 の 話 を 聞い て い た 。 沙漠 に は 雲 が
実験batch output[0]*************** [   12    59     7   414     8     4 18139     5    39    11   575    11
  3710     6   588  1295     7  9323     5   243    11   341     6    21
     7     9 18139     8    10  2000    12  6846] が あっ た 為 に 、 沙漠 の 中 を 円 を 描い て 歩き 続け た 旅人 の 話 を 聞い て い た 。 沙漠 に は 雲 が はれ

'''
inputs = tf.placeholder(tf.int32, (None, None), name='inputs')
print("input用プレイスフォルダ生成:",inputs)
#input用プレイスフォルダ生成: Tensor("inputs:0", shape=(?, ?), dtype=int32)

print("tf.contrib.rnn.MultiRNNCellの生成")
rnn_cell = tf.contrib.rnn.MultiRNNCell([
        tf.contrib.rnn.GRUCell(HIDDEN_SIZE)
        for _ in range(N_LAYERS)
])
print("rnn_cell:", type(rnn_cell))
#rnn_cell: <class 'tensorflow.python.ops.rnn_cell_impl.MultiRNNCell'>

initial_state = rnn_cell.zero_state(BATCH_SIZE, dtype=tf.float32)
print("initial_state=", initial_state)
#initial_state=(<tf.Tensor 'MultiRNNCellZeroState/GRUCellZeroState/zeros:0' shape=(64, 192) dtype=float32>, 
#  <tf.Tensor 'MultiRNNCellZeroState/GRUCellZeroState_1/zeros:0' shape=(64, 192) dtype=float32>)

print("rnn_model の生成")
train_outputs, inference_outputs = rnn_model(
        inputs,            #[batch_size, max_sequence_length] (Tensor)の入力データ
        MAX_LEN,           #(int): 出力データの最大長
        rnn_cell,          # RNNセル
        initial_state,     #RNNセルに依存する型の初期状態
        EMB_SIZE,#(int):   #単語の埋め込み空間のサイズ
        VOCAB_SIZE# (int): #語彙数(単語IDの最大値)
)
print("\n学習時の出力train_outputs:", train_outputs)
#学習時の出力train_outputs: BasicDecoderOutput(rnn_output=<tf.Tensor 'decoder_1/decoder/transpose:0' shape=(64, ?, 40000) dtype=float32>, 
#     sample_id=<tf.Tensor 'decoder_1/decoder/transpose_1:0' shape=(64, ?) dtype=int32>)

print("\n推定時の出力inference_outputs:", inference_outputs)
#推定時の出力inference_outputs: BasicDecoderOutput(rnn_output=<tf.Tensor 'decoder_2/decoder/transpose:0' shape=(64, ?, 40000) dtype=float32>, 
#     sample_id=<tf.Tensor 'decoder_2/decoder/transpose_1:0' shape=(64, ?) dtype=int32>)

targets = tf.placeholder(tf.int32, (None, None), name='targets')
print("\n targets用プレイスフォルダ生成:",targets)
# targets用プレイスフォルダ生成: Tensor("targets:0", shape=(?, ?), dtype=int32)

with tf.variable_scope('loss'):
    masks = tf.ones_like(targets, dtype=tf.float32)
    loss = tf.contrib.seq2seq.sequence_loss(
        train_outputs.rnn_output,
        targets,
        masks
    )

print("\n masks=tf.ones_like(targets):",masks)
# masks=tf.ones_like(targets): Tensor("loss/ones_like:0", shape=(?, ?), dtype=float32)

print("\n loss 交差エントロピー誤差(損失関数):",loss)
# loss 交差エントロピー誤差(損失関数): Tensor("loss/sequence_loss/truediv:0", shape=(), dtype=float32)

with tf.variable_scope('opt'):
    optimizer = tf.train.AdamOptimizer() # 最適化
    train_op = optimizer.minimize(loss) # 損失関数結果が最小になる訓練指示

print("\n optimizer 最適化:",optimizer)
# optimizer 最適化: <tensorflow.python.training.adam.AdamOptimizer object at 0x0000020BB58A1358>

print("\n train_op  損失関数結果が最小になる訓練指示\n",train_op)
'''  train_op  損失関数結果が最小になる訓練指示
 name: "opt/Adam"
op: "NoOp"
input: "^opt/Adam/update_decoder/emb/w/group_deps"
input: "^opt/Adam/update_decoder/decoder/multi_rnn_cell/cell_0/gru_cell/gates/kernel/ApplyAdam"
input: "^opt/Adam/update_decoder/decoder/multi_rnn_cell/cell_0/gru_cell/gates/bias/ApplyAdam"
input: "^opt/Adam/update_decoder/decoder/multi_rnn_cell/cell_0/gru_cell/candidate/kernel/ApplyAdam"
input: "^opt/Adam/update_decoder/decoder/multi_rnn_cell/cell_0/gru_cell/candidate/bias/ApplyAdam"
input: "^opt/Adam/update_decoder/decoder/multi_rnn_cell/cell_1/gru_cell/gates/kernel/ApplyAdam"
input: "^opt/Adam/update_decoder/decoder/multi_rnn_cell/cell_1/gru_cell/gates/bias/ApplyAdam"
input: "^opt/Adam/update_decoder/decoder/multi_rnn_cell/cell_1/gru_cell/candidate/kernel/ApplyAdam"
input: "^opt/Adam/update_decoder/decoder/multi_rnn_cell/cell_1/gru_cell/candidate/bias/ApplyAdam"
input: "^opt/Adam/update_decoder/decoder/dense/kernel/ApplyAdam"
input: "^opt/Adam/update_decoder/decoder/dense/bias/ApplyAdam"
input: "^opt/Adam/Assign"
input: "^opt/Adam/Assign_1"
'''

checkpoint_path = os.path.join(MODEL_DIR, 'model.ckpt')
print("パラメタ保存パス checkpoint_path:" + checkpoint_path)
#パラメタ保存パス checkpoint_path:models/generate_rnn\model.ckpt

saver = tf.train.Saver()
print(" saver = tf.train.Saver():", saver)
# saver = tf.train.Saver(): <tensorflow.python.training.saver.Saver object at 0x0000026AFCA446D8>

print(" セッションの実行開始")
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    sum_loss = 0
    for step in range(1, STEPS + 1):
        inputs_, outputs_ = next(gen)
        loss_, _ = sess.run(
            [loss, train_op],
            feed_dict={
                inputs: inputs_,
                targets: outputs_
            }
        )
        sum_loss += loss_
        if step <= 2 :
            print("----STEP:" + str(step), "結果のloss_:", loss_ , _)
            print("   inputs_ :", inputs_.shape, inputs_ )
            print("   outputs_:", outputs_.shape, outputs_)
        
        if step % 100 == 0:
            saver.save(sess, checkpoint_path, global_step=step)
            print("save--------STEP:" + str(step), "sum_loss/100: ",sum_loss/100)
            sum_loss = 0
            # 直前の input_ に対する推定結果を得て、表示
            inferences = sess.run(
                inference_outputs.sample_id,
                feed_dict={ inputs: inputs_[:, :1] }
            )
            for _ in range(3):
                print('---- SEED ----')
                print(text_encoder.decode(inputs_[_, :1]))
                print('---- OUTPUT ----')
                print(text_encoder.decode(inferences[_]))


セッションの実行結果の例
----STEP:1 結果のloss_: 10.596933 None
   inputs_ : (64, 32) [[  75   40   11 ...   10 1130    5]
 [1536   32   19 ...   32    8   18]
 [ 158    8   10 ...  655    8 1237]
 ...
 [   6   21    7 ...   32    8   18]
 [  11 1911  497 ...   32   14   36]
 [  39   24    4 ...  350   15  344]]
   outputs_: (64, 32) [[   40    11    16 ...  1130     5 17854]
 [   32    19   115 ...     8    18    17]
 [    8    10     4 ...     8  1237     6]
 ...
 [   21     7    87 ...     8    18     6]
 [ 1911   497    49 ...    14    36     9]
 [   24     4   149 ...    15   344    37]]
----STEP:2 結果のloss_: 10.594896 None
   inputs_ : (64, 32) [[   5  121   12 ... 1315   37    5]
 [ 228    4  189 ...    8 8062   11]
 [1136   22   31 ... 6681   19   63]
 ...
 [   5 9366  361 ...   54   40    8]
 [  96   17    7 ...    6    4   36]
 [  41    9  103 ...   10    4 4090]]
   outputs_: (64, 32) [[ 121   12    4 ...   37    5  394]
 [   4  189   14 ... 8062   11 2892]
 [  22   31  596 ...   19   63   14]
 ...
 [9366  361   15 ...   40    8   10]
 [  17    7    9 ...    4   36   72]
 [   9  103    4 ...    4 4090   12]]
'models/generate_rnn\\model.ckpt-100'
save--------STEP:100 sum_loss/100:  7.081988639831543
---- SEED ----
に
---- OUTPUT ----
、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、
---- SEED ----
あたり
---- OUTPUT ----
、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、
---- SEED ----
を
---- OUTPUT ----
、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、
'models/generate_rnn\\model.ckpt-200'
save--------STEP:200 sum_loss/100:  6.246347107887268
---- SEED ----
。「
---- OUTPUT ----
、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、
---- SEED ----
いる
---- OUTPUT ----
、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、
---- SEED ----
は
---- OUTPUT ----
、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、
'models/generate_rnn\\model.ckpt-300'
save--------STEP:300 sum_loss/100:  6.23431734085083
---- SEED ----
、
---- OUTPUT ----
、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、
---- SEED ----
中
---- OUTPUT ----
、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、
---- SEED ----
では
---- OUTPUT ----
、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、
・・・・・省略・・・・・・


文章の生成