# 日本語の”機械学習”-2(契約約款の比較表作成)
# 必要なモジュールを呼び出す
import os, re, sys
import docx
import pandas as pd
from janome.charfilter import *
from janome.analyzer import Analyzer
from janome.tokenizer import Tokenizer
from janome.tokenfilter import CompoundNounFilter,POSKeepFilter,POSStopFilter,LowerCaseFilter
# 機械学習する契約約款のWordファイルがあるディレクトリ「dir」を指定する
dir = r'C:\Users\......\......\......\......\......'
# 【 形態素解析の設定 】----------------------------------------
# 「janome」による形態素解析のパラメーター設定
char_filters = []
tokenizer = Tokenizer()
token_filters = [
CompoundNounFilter(), # 連続する名詞は複合名詞にする
POSKeepFilter(['名詞']), # 名詞のみを取得する
]
analyzer = Analyzer(char_filters=char_filters,tokenizer=tokenizer,token_filters=token_filters)
# 形態素解析関数「keitaiso」の定義
def keitaiso(text):
w_list = [] # 形態素解析で順に取り出した単語のリスト(同一単語の重複あり)
w_list_u = [] # 形態素解析で順に取り出した単語のリスト(同一単語の重複なし)
words = '' # w_listの単語を /(スラッシュ記号) で区切りながら1つにつなげた「単語集」
# 前処理したテキストを「janome」のパラメーター設定に基づき形態素解析して単語(名詞のみ)を取り出しリスト化(w_list)する
# また、それらの単語をつなげて「単語集」(words)を作る
for j in analyzer.analyze(text):
wbf = j.base_form
w_list.append(wbf)
words += wbf+'/'
# w_list から同一単語の重複を取り除いたユニークリスト w_list_u を作る
w_list_u = set(w_list)
# w_list、w_list_u 、words を形態素解析関数「keitaiso」の結果として返す
return w_list,w_list_u,words
# 【 学習フェーズ-1(Wordの約款をExcel形式に変換) 】----------------------------------------
# 学習用の約款(Wordファイル)のファイル名「nam_g」を指定し読み込む
# 約款は、1行目が約款名、2行目は空白行、3行目以降は各条項(条項見出し行+条文+空白行の組合せ)とし、Wordの表形式は使用されていないものとする
nam_g = '民間建設工事標準請負契約約款(甲)_一部変更'
nam1_g = nam_g+'.docx'
path1_g = os.path.join(dir,nam1_g)
doc = docx.Document(path1_g)
yakkan_g = doc.paragraphs[0].text # 1行目から約款名を取得
# Wordファイルから読み込んだ約款データを機械学習するため、Excel形式に変換して保存することとし、このためデータフレーム(df_g)を準備する
# df_gのA列は「カテゴリー」として()で括られた各条項見出しを書き込む
# df_gのB列は当該条項見出しに対応した各条文を書き込む
# df_gのC列は形態素解析が適切かどうか確認するため、抽出した「単語集」を保存する
df_g = pd.DataFrame(data=None,index=None,columns=['カテゴリー','条文('+yakkan_g+')','形態素解析'])
# Wordファイルを1パラグラフずつ読み込み、各条項見出しをA列のセルに書き込み、
# 当該条項見出しに対応した条文の全てのパラグラフ(条項見出しを含む)をB列のセルに書き込む
category = '' # 条項見出し(カテゴリー)
jyoubun = '' # 条文
jyoubun_c = [] # 条文が正しく保存されたチェックするためのリスト
i = 1
for para in doc.paragraphs:
if para.text != '' and para.text != yakkan_g:
jyoubun += para.text + '\n' # 1つの条文内でもパラグラフごとに改行記号(\n)を挿入
else:
if para.text == '' and jyoubun != '':
c1 = jyoubun.find('(')
c2 = jyoubun.find(')')
category = jyoubun[c1+1:c2] # カテゴリーは条項見出しから括弧を取り除いたものとする
df2_g = pd.DataFrame(data={'カテゴリー':[category],'条文('+yakkan_g+')':[jyoubun],'形態素解析':['']})
df_g = pd.concat([df_g, df2_g])
jyoubun_c += [jyoubun]
i += 1
jyoubun = ''
# 【 学習フェーズ-2(学習) 】----------------------------------------
# 学習用データを1行ずつ取り出し、「条文」項目の内容を形態素解析して単語(名詞のみ)に分かち書きし、
# カテゴリーの出現回数や、カテゴリー別の単語出現回数をカウントする
c_n = {} # カテゴリーの出現回数を記録する辞書型変数
cw_n = {} # カテゴリー別の単語の出現回数を記録する辞書型変数
for i in range(df_g.shape[0]):
# 「条文」項目を形態素解析するテキスト(text)とする
text = df_g.iloc[i,1]
# 「カテゴリー」項目をカテゴリー(c)とする
c = df_g.iloc[i,0]
# テキスト(text)を形態素解析関数「keitaiso」で処理し結果(w_list,w_list_u,words)を得る
w_list,w_list_u,words = keitaiso(text)
# カテゴリー(c)別の単語(w)の出現回数を辞書型変数(cw_n)にカウントする
for w in w_list:
if not c in cw_n:
cw_n[c] = {}
if not w in cw_n[c]:
cw_n[c][w] = 0
cw_n[c][w] += 1
# カテゴリー(c)の出現回数を辞書型変数(c_n)にカウントする
if not c in c_n:
c_n[c] = 0
c_n[c] += 1
# 形態素解析して抽出した「単語集」をC列に保存する
df_g.iloc[i,2] = words
# 形態素解析の結果を残すExcelファイルとして、「nam_g」に「(学習)」という語句を加えたファイル名でファイル保存する
nam2_g = nam_g+'(学習)'+'.xlsx'
path2_g = os.path.join(dir,nam2_g)
df_g.to_excel(path2_g,header=True,index=False)
print('パス '+path2_g+' でファイルを保存しました')
# 条文がExcelファイル上で欠損なく保存されたかチェックする(欠損が生じた場合はその状況を画面印刷する)
flag = '0'
for i in range(df_g.shape[0]):
if df_g.iloc[i,1] != jyoubun_c[i]:
print('【保存した欠損のあるデータ】')
print(df_g.iloc[i,1])
print('↓')
print('【正しいデータ】')
print(jyoubun_c[i])
flag = '1'
if flag != '1':
print('学習データの各条文は正しく保存されました。')
# 【 予測フェーズ-1(Wordの約款をExcel形式に変換)】----------------------------------------
# 予測用の約款(Wordファイル)のファイル名「nam_y」を指定し読み込む
# 約款は、1行目が約款名、2行目は空白行、3行目以降は各条項(条項見出し行+条文+空白行の組合せ)とし、Wordの表形式は使用されていないものとする
# なお、予測用の約款では条項見出し行は無くても構わない
nam_y = '民間(七会)連合協定工事請負契約約款_一部変更'
nam1_y = nam_y+'.docx'
path1_y = os.path.join(dir,nam1_y)
doc = docx.Document(path1_y)
yakkan_y = doc.paragraphs[0].text # 1行目から約款名を取得
# Wordファイルから読み込んだ約款データを機械学習するため、Excel形式に変換して保存することとし、このためデータフレーム(df_y)を準備する
# df_yのA列は条文の内容から予測した「カテゴリー」を書き込む
# df_yのB列は各条文を書き込む
# df_yのC列は形態素解析が適切かどうか確認するため、抽出した「単語集」を保存する
# df_yのD列はカテゴリーを予測した根拠である「スコアリスト」を保存する
df_y = pd.DataFrame(data=None,index=None,columns=['カテゴリー','条文('+yakkan_y+')','形態素解析','スコアリスト'])
# Wordファイルを1パラグラフずつ読み込み、各条文の全てのパラグラフをB列のセルに書き込む
jyoubun = '' # 条文
jyoubun_c = [] # 条文が正しく保存されたチェックするためのリスト
i = 1
for para in doc.paragraphs:
if para.text != '' and para.text != yakkan_y:
jyoubun += para.text + '\n' # 1つの条文内でもパラグラフごとに改行記号(\n)を挿入
else:
if para.text == '' and jyoubun != '':
df2_y = pd.DataFrame(data={'カテゴリー':[''],'条文('+yakkan_y+')':[jyoubun],'形態素解析':[''],'スコアリスト':['']})
df_y = pd.concat([df_y, df2_y])
jyoubun_c += [jyoubun]
i += 1
jyoubun = ''
# 【 予測フェーズ-2(予測) 】----------------------------------------
# 予測用データ(「条文」項目)の内容を1行ずつ取り出し、形態素解析して単語(名詞のみ)に分かち書きし、
# 学習用データにおけるカテゴリー別の単語の出現回数から、予測用データに各カテゴリー別のスコアを付け、
# 最もスコアの高かったカテゴリーを予測カテゴリーとする
for i in range(df_y.shape[0]):
sc_list = [] # 予測用データ1行ごとに、カテゴリー別のスコアを記録するリスト
best_c = '' # 予測用データ1行ごとに、最もスコアの高かったカテゴリー名を記録する変数
best_sc = 0 # 予測用データ1行ごとに、最もスコアの高かったカテゴリーのスコアを記録する変数
# 「条文」項目を形態素解析するテキスト(text)とする
text = df_y.iloc[i,1]
# テキスト(text)を形態素解析関数「keitaiso」で処理し結果(w_list,w_list_u,words)を得る
w_list,w_list_u,words = keitaiso(text)
# 学習フェーズで出現したカテゴリーを1つずつ取り出し、以下の作業を繰り返す
for c in c_n.keys():
# 学習フェーズでの当該カテゴリーの出現率をスコア(sc)の基礎点とする(100分率とする)…スコア計算①
sc = (c_n[c]/sum(c_n.values()))*100
# 予測用データで出現した単語のリスト(同一単語の重複なし w_list_U)から単語(w)を1つずつ取り出し、以下の作業を繰り返す
n = 0
for w in w_list_u:
# 当該単語(w)が学習フェーズで当該カテゴリーにおいて出現していた場合は、そのときの出現数を分子(n)とする
if w in cw_n[c]:
n = cw_n[c][w]
# また、学習フェーズで当該カテゴリーで出現した単語の総数を分母(d)とする
d = sum(cw_n[c].values())
# 当該カテゴリーにおける当該単語の学習フェーズ出現率(分子/分母)を計算して、スコア(sc)に加算していく(100分率とする)…スコア計算②
sc += (n/d)*100
# 予測用データで出現した単語のリスト(同一単語の重複あり w_list)から単語(w)を1つずつ取り出し、以下の作業を繰り返す
n = 0
for w in w_list:
# 当該単語(w)が学習フェーズで当該カテゴリーにおいて出現していた場合は、分子(n)に1を加える
# (出現回数が増えると比例して分子(n)の数も増えていくのでスコア(sc)も累進的に増加する)
if w in cw_n[c]:
n += 1
# また、予測用データで出現した単語の総数を分母(d)とする
d = len(w_list)
# 当該カテゴリーにおける当該単語の予測フェーズ出現率(分子/分母)を計算して、スコア(sc)に加算していく(100分率とする)…スコア計算③
sc += (n/d)*100
# 当該カテゴリーのスコアが他のカテゴリーのスコアよりも大きければ、そのカテゴリー(best_c)とスコア(best_sc)を記録する
if sc > best_sc:
best_c = c
best_sc = sc
# 当該カテゴリーのスコアとカテゴリー名(スコア付)をリスト化(sc_list)する
sc_list.append((sc , c+'('+"{:.1f}".format(sc)+');'))
# 形態素解析して抽出した「単語集」をC列に保存する
df_y.iloc[i,2] = words
# 予測カテゴリーをA列に保存する
df_y.iloc[i,0] = best_c
# カテゴリー名(スコア付)をスコアの降順に並び替えて1つにつなげ、D列に保存する
con_sc_list = ''
sort_sc_list = sorted(sc_list,reverse=True)
for j in sort_sc_list:
con_sc_list += j[1]
df_y.iloc[i,3] = con_sc_list
# 予測の結果を残すExcelファイルとして、「nam2_y」に「(予測)」という語句を加えたファイル名でファイル保存する
nam2_y = nam_y+'(予測)'+'.xlsx'
path2_y = os.path.join(dir,nam2_y)
df_y.to_excel(path2_y,header=True,index=False)
print('パス '+path2_y+' でファイルを保存しました')
# 条文がExcelファイル上で欠損なく保存されたかチェックする(欠損が生じた場合はその状況を画面印刷する)
flag = '0'
for i in range(df_y.shape[0]):
if df_y.iloc[i,1] != jyoubun_c[i]:
print('【保存した欠損のあるデータ】')
print(df_y.iloc[i,1])
print('↓')
print('【正しいデータ】')
print(jyoubun_c[i])
flag = '1'
if flag != '1':
print('予測データの各条文は正しく保存されました。')
# 【 比較表作成フェーズ 】----------------------------------------
# 学習用データの各カテゴリーに対応する学習用データの条文と予測用データの条文を比較するExcelファイルを作成する
# そのため、データフレーム(df_h)を準備する
# df_hのA列は学習用データの「カテゴリー」を書き込む
# df_hのB列は学習用データの「条文」を書き込む
# df_hのC列は「カテゴリー」に対応した予測用データの「条文」を書き込む
df_h = pd.DataFrame(data=None,index=None,columns=['カテゴリー','条文('+yakkan_g+')','条文('+yakkan_y+')'])
for i in range(df_g.shape[0]):
jyoubun_y = ''
for j in range(df_y.shape[0]): # 「カテゴリー」に対応した予測用データの「条文」が複数ある場合は改行キー(\n)を挟んで続けて書き込む
if df_g.iloc[i,0] == df_y.iloc[j,0]:
jyoubun_y += df_y.iloc[j,1] + '\n'
if df_g.iloc[i,1][0:1] == '*': # 学習用データの「条文」は、もし先頭に「*」が付いていれば本来存在しない条項なので書き込まない
df_g.iloc[i,1] = ''
if df_g.iloc[i,1] != '' or jyoubun_y != '':
df2_h = pd.DataFrame(data={'カテゴリー':[df_g.iloc[i,0]],'条文('+yakkan_g+')':[df_g.iloc[i,1]],'条文('+yakkan_y+')':[jyoubun_y]})
df_h = pd.concat([df_h, df2_h])
# 学習データと予測データの比較した結果を残すExcelファイルとして、「nam2_h」に「(比較)」という語句を加えたファイル名でファイル保存する
nam2_h = nam_y+'(比較)'+'.xlsx'
path2_h = os.path.join(dir,nam2_h)
df_h.to_excel(path2_h,header=True,index=False)
print('パス '+path2_h+' でファイルを保存しました')