# 日本語の”機械学習”
# 必要なモジュールを呼び出す
import os, re, sys
from janome.charfilter import *
from janome.analyzer import Analyzer
from janome.tokenizer import Tokenizer
from janome.tokenfilter import CompoundNounFilter,POSKeepFilter,POSStopFilter,LowerCaseFilter
from openpyxl import load_workbook
from openpyxl.styles import Font,Alignment
# 機械学習のExcelファイルがあるディレクトリ「dir」を指定する
dir = r'C:\Users\......\......\......\......\......'
# 【 形態素解析の設定 】----------------------------------------
# 形態素解析において前処理で除去する文字(正規表現)
mo = [
'[0-9]','[0-9]', # 半角数字、全角数字
'[A-Z]','[a-z]','[A-Z]','[a-z]', # 半角アルファベット、全角アルファベット
'[。-ン]','[ァ-ヶ]', # 半角カタカナ、全角カタカナ
'[!-/]','[:-@]','[[-`]','[{-~]', # 半角記号
'[、。,.・:;?!゛゜´`¨^ ̄_ヽヾゝゞ〃仝々〆〇ー―‐/\]', # 全角記号(全部を羅列)
'[~∥|…‥‘’“”()〔〕[]{}〈〉《》「」『』【】+-±×÷=≠<>≦≧∞∴]',
'[♂♀°′″℃¥$¢£%#&*@§☆★○●◎◇◆□■△▲▽▼※〒→←↑↓]'
]
# 「janome」による形態素解析のパラメーター設定
char_filters = []
tokenizer = Tokenizer()
token_filters = [
POSStopFilter(['名詞,固有名詞','名詞,非自立']), # 固有名詞や非自立名詞を除く
# 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つにつなげた「単語集」
# テキストを前処理(数字、アルファベット、カタカナ、記号を除去)する
for j in mo:
text = re.sub(j,'',text)
# 前処理したテキストを「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
# 【 学習フェーズ 】----------------------------------------
c_n = {} # カテゴリーの出現回数を記録する辞書型変数
cw_n = {} # カテゴリー別の単語の出現回数を記録する辞書型変数
# 学習用のExcelファイル名「nam」を指定し読み込む
nam = '国交省ネガティブ情報201910~202209.xlsx'
path = os.path.join(dir,nam)
wb = load_workbook(path)
ws = wb.active
# 形態素解析が適切かどうか確認するため、抽出した「単語集」を保存する列を挿入する
col = [['K',20,'K1','形態素解析']] # 列番号(アルファベット)、列幅、列タイトル名の組合せ
ws.insert_cols(11,len(col))
for i in range(len(col)):
ws.column_dimensions[col[i][0]].width = col[i][1] # 列幅
ws[col[i][2]].value = col[i][3] # 列タイトル名
ws[col[i][2]].font = Font(bold=True) # 列タイトル書式(太字)
ws[col[i][2]].alignment = Alignment(horizontal='center') # 列タイトル書式(中央揃え)
# 学習用データを1行ずつ取り出し、「処分理由」項目の内容を形態素解析して単語(名詞のみ)に分かち書きし、
# 「建設業法違反」または「その他法令違反」項目の内容をカテゴリーとして、
# カテゴリーの出現回数や、カテゴリー別の単語出現回数をカウントする
for i in range(2,ws.max_row+1):
# 「処分理由」項目を形態素解析するテキスト(text)とする
text = ws.cell(i,7).value
# 「建設業法違反」項目または「その他法令違反」項目をカテゴリー(c)とする
if ws.cell(i,8).value != '-':
c = ws.cell(i,8).value
else:
c = ws.cell(i,9).value
# テキスト(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
# 形態素解析して抽出した「単語集」をK列に保存する
ws.cell(i,11,value=words)
# 形態素解析の結果を残すExcelファイルとして、「nam」に「(学習)」という語句を加えたファイル名「nam1」でファイル保存する
nam1 = nam.replace('.xlsx','')+'(学習)'+'.xlsx'
path1 = os.path.join(dir,nam1)
wb.save(path1)
wb.close()
print('パス '+path1+' でファイルを保存しました')
# 【 予測フェーズ 】----------------------------------------
# 予測用のExcelファイル名「nam2」を指定し読み込む
nam2 = '国交省ネガティブ情報201910~202209.xlsx'
path2 = os.path.join(dir,nam2)
wb = load_workbook(path2)
ws = wb.active
# 学習・予測結果を保存する列として4列挿入する
col = [['K',20,'K1','形態素解析'], # 列番号(アルファベット)、列幅、列タイトル名の組合せ
['L',20,'L1','機械学習予測'],
['M',8,'M1','判定'],
['N',20,'N1','スコアリスト']
]
ws.insert_cols(11,len(col))
for i in range(len(col)):
ws.column_dimensions[col[i][0]].width = col[i][1] # 列幅
ws[col[i][2]].value = col[i][3] # 列タイトル名
ws[col[i][2]].font = Font(bold=True) # 列タイトル書式(太字)
ws[col[i][2]].alignment = Alignment(horizontal='center') # 列タイトル書式(中央揃え)
# 予測用データ(「処分理由」項目)の内容を1行ずつ取り出し、形態素解析して単語(名詞のみ)に分かち書きし、
# 学習用データにおけるカテゴリー別の単語の出現回数から、予測用データに各カテゴリー別のスコアを付け、
# 最もスコアの高かったカテゴリーを予測カテゴリーとする
# また、予測カテゴリーと、学習用データにおけるカテゴリー(「建設業法違反」または「その他法令違反」項目)を比較して、一致率を計算する
ok = 0 # カテゴリーの一致数をカウントする変数
ng = 0 # カテゴリーの不一致数をカウントする変数
for i in range(2,ws.max_row+1):
sc_list = [] # 予測用データ1行ごとに、カテゴリー別のスコアを記録するリスト
best_c = '' # 予測用データ1行ごとに、最もスコアの高かったカテゴリー名を記録する変数
best_sc = 0 # 予測用データ1行ごとに、最もスコアの高かったカテゴリーのスコアを記録する変数
# 「処分理由」項目を形態素解析するテキスト(text)とする
text = ws.cell(i,7).value
# テキスト(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)+');'))
# 形態素解析して抽出した「単語集」をK列に保存する
ws.cell(i,11,value=words)
# 予測カテゴリーをL列に保存する
ws.cell(i,12,value=best_c)
# 予測カテゴリーと「建設業法違反」項目または「その他法令違反」項目とを比較し、判定(一致='OK'、不一致='NG')をM列に保存する
# また、一致数、不一致数をカウントする
if ws.cell(i,8).value != '-':
c_text = ws.cell(i,8).value
else:
c_text = ws.cell(i,9).value
if c_text == best_c:
result = 'OK'
ok += 1
else:
result = 'NG'
ng += 1
ws.cell(i,13,value=result)
# カテゴリー名(スコア付)をスコアの降順に並び替えて1つにつなげ、N列に保存する
con_sc_list = ''
sort_sc_list = sorted(sc_list,reverse=True)
for j in sort_sc_list:
con_sc_list += j[1]
ws.cell(i,14,value=con_sc_list)
# 予測の結果を残すExcelファイルとして、「nam2」に「(予測)」という語句を加えたファイル名「nam3」でファイル保存する
nam3 = nam2.replace('.xlsx','')+'(予測)'+'.xlsx'
path3 = os.path.join(dir,nam3)
wb.save(path3)
wb.close()
print('パス '+path3+' でファイルを保存しました')
# 一致率を計算し画面表示する
cr = ok/(ok+ng)*100
print('一致率は '+str(ok)+' / '+str(ok+ng)+' = '+ "{:.1f}".format(cr)+'% でした')