<jupyter_start><jupyter_text>주요감사업무 수행내용 지도학습   가. 전처리   1. 엑셀 전처리 1. 외부감사 실시내용에서 공시되는 주요감사업무 수행내용 Text 입수 (2017년 1월말 - 2019년 12월말 법인 공시자료) → 352,002건2. 엑셀 전처리 : ① 전반감사계획(감사착수단계) 제외 ② 공백(NA) 또는 의미없는 부호 제거 ③ 중복 제거 → 17,540건  2. Python 전처리 전처리에 사용되는 모듈을 가져온다 엑셀에서 1차적으로 처리가 되어있어서, 정규식으로 특수문자를 제거하고, 띄어쓰기를 무시하기 위하여 공백을 제거하는 처리만 수행한다. * 띄어쓰기를 무시하는 이유는, ① 본 분석에서 사용하는 데이터에는 일반적으로 사용되는 지도 방식의 형태소 분석기(ex. KoNLPy)의 퍼포먼스가 좋지 않았고, ② 입수 텍스트의 띄어쓰기 품질이 좋지 않았기 때문이다. 본 분석에서는 비지도방식의 형태소 분석기인 soynlp의 MaxScoreTokenizer를 사용한다.[참고]1. soynlp github 2. https://wikidocs.net/92961<jupyter_code>import pandas as pd
import re<jupyter_output><empty_output><jupyter_text>엑셀로 전처리가 수행된 입수 데이터를 Dataframe으로 가져온다<jupyter_code>df = pd.read_excel("data.xlsx")
df.head()<jupyter_output><empty_output><jupyter_text>전처리 로직으로 특수 문자/공백/불용어를 제거한다. 형태소분석기에서 불용어를 적절히 분리해내지 못하므로, 선택적으로 제거한다.[참고] 1. https://somjang.tistory.com/2. https://wikidocs.net/22530 3. https://stackoverflow.com/questions/3411771/best-way-to-replace-multiple-characters-in-a-string<jupyter_code>data = []
for i in list(df["DATA"]):
    temp = re.sub('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…》]', '', str(i))
    temp = ''.join(temp.split())
    temp = temp.replace("의견","$$").replace("과목","%%").replace("효과성","##").replace("결과","@@")
    stopword = ["유의적", "및", "의", "등", "에대한", "에대해", "에대하여", "와", "과", "를", "을"]
    for word in stopword:
        temp = temp.replace(word," ")
    temp = temp.replace("$$", "의견").replace("%%", "과목").replace("##", "효과성").replace("@@","결과")
    data.append(temp)
df["DATA"] = data
df.head()<jupyter_output><empty_output><jupyter_text>나. Export Unsupervised dataset  엑셀 전처리 과정에서 입수된 데이터는 정렬되어있다. 이를 임의로 섞어서 이후 추출할 Train, Test 자료의 대표성을 높여준다.[참고]1. https://stackoverflow.com/questions/29576430/shuffle-dataframe-rows<jupyter_code>df = df.sample(frac=1).reset_index(drop=True)
df.head()<jupyter_output><empty_output><jupyter_text>각각의 자료에서 다시 임의로 600개씩 선택하여 Train과 Test 데이터로 만든다. (최초 생성 데이터 계속 활용)<jupyter_code>train, test = df.sample(n=600), df.sample(n=600)<jupyter_output><empty_output><jupyter_text>Train과 Test Dataset을 Excel로 추출한다<jupyter_code>train.to_excel("train.xlsx")
test.to_excel("test.xlsx")<jupyter_output><empty_output><jupyter_text>다. 데이터에 전문가적 판단 반영 (엑셀에서 수행) 지도학습을 수행하기 위하여 추출된 1,200개의 샘플에 전문가적 판단에 따라 아래의 label을 붙인다. (분반기 검토 업무의 경우 분반기 시간이 따로 공시되는 점을 고려하여 따로 Label을 붙인다.)            1: 위험평가/계획        2: 입증감사절차/의견형성        3: 분반기검토    -예외 처리 규칙-            두 가지 단계가 섞여있다고 판단되는 경우 감사의 경우 입증감사절차, 검토의 경우 분반기검토로 분류한다.        기초잔액감사, 기중 하드클로징 등의 경우 기중 수행업무임에도 입증감사절차로 분류한다.        전기 재무제표 검토의 경우 기초잔액감사와 달리 위험평가로 분류한다.        분석적 절차/거래 검토의 경우 입증 절차로 분류한다.        해외 자회사 방문 등 기중 실사의 경우 입증 목적이 아닌 경우 위험평가/계획으로 분류한다.          라. 형태소 분석기 교육  위에 언급한 바, 본 분석의 경우 분석대상 형태소가 사전에 주어진 KoNLPy의 분석툴 사용이 적절하지 않다. 많은 회계감사의 용어가 해당 툴의 사전에 반영되어 있지 않아, 부적절한 분석결과가 산출될 수 있다. 아래의 예로 이 상황을 확인할 수 있다.<jupyter_code>from konlpy.tag import Okt

okt = Okt()  
print(okt.morphs("중요 계정잔액 및 공시내용에 대해 질문 분석적절차 표본점검 문서검증 재계산 등"))<jupyter_output>['중요', '계정', '잔액', '및', '공', '시', '내용', '에', '대해', '질문', '분석', '적', '절차', '표본', '점검', '문서', '검증', '재', '계산', '등']<jupyter_text>따라서 이번 분석에서는 비지도학습 기반 형태소 분석기인 soynlp를 사용하는데, 전체 데이터를 이용하여 응집확률을 계산하고, 이를 활용하여 형태소를 분석하고 일부 응집 점수를 튜닝하여 TF-IDF 적용전 Vectorize된 Data를 만든다.<jupyter_code>from soynlp import DoublespaceLineCorpus
from soynlp.word import WordExtractor
from soynlp.tokenizer import MaxScoreTokenizer

word_extractor = WordExtractor()
word_extractor.train(df["DATA"])
word_score_table = word_extractor.extract()

scores = {word:score.cohesion_forward for word, score in word_score_table.items()}
tokenizer = MaxScoreTokenizer(scores=scores)<jupyter_output>training was done. used memory 0.352 Gb
all cohesion probabilities was computed. # words = 5993
all branching entropies was computed # words = 8064
all accessor variety was computed # words = 8064<jupyter_text>아래는 분석기 적용내용을 확인후, 필요한 형태소에 가중치 1을 부여하는 score 튜닝을 적용한 내용이다.<jupyter_code>tuning = {'중요계정잔액':1, '공시내용':1, '분석적절차':1, '표본점검':1, '문서검증':1, '재계산':1
         ,'중요거래유형':1, '내부통제':1, '분석적검토':1, '중간감사':1, '조회서':1, '감사계약':1
         ,'입증감사':1, '기말감사':1, '재고자산':1, '실사입회':1, '내부회계관리제도':1, '연결재무제표':1
         ,'연결조정분개':1, '계획':1, '이해':1, '투자자본상계':1, '일반감사절차':1, '감사결과':1
         ,'반기검토':1, '분기검토':1, '후속기간':1, '통제테스트':1, '잔액검증':1, '입증감사':1
         ,'중요한왜곡표시':1, '식별':1, '기초잔액':1, '실증절차':1, '표본테스트':1, '현장감사':1
         ,'별도재무제표':1, '경영자주장':1, '설계':1, '운영':1, '주석사항':1, '효과성':1
         ,'회계처리':1, '재무제표':1, '전산감사':1
         ,'사전검토':1, '계정별':1, '감사보고서':1, '종속기업':1, '수행':1, '금융자산':1
         ,'이해및평가':1, '내부통제제도':1, '중요공시절차':1, '테스트':1, 'test':1, '실재성':1
         ,'감사절차':1, '주석':1, '성격범위시기':1, '내부회계':1, '피투자회사':1, '지배회사':1
         ,'분반기검토':1, '프로세스':1, '거래유형':1, '통제위험':1, '외부조회':1, '경영자':1
         ,'계정과목':1, '중요계정':1, '종속회사':1, '내부통제테스트':1, '입증절차':1, '보고서':1
         ,'변동사항':1, '업무수행':1, '전기조서':1, '감사위험':1, '예비적':1, '지배기구':1
         ,'입증시사':1, '1분기':1, '2분기':1, '3분기':1, '일반기업회계기준':1, '조기입증':1
         ,'부문감사인':1, '그룹감사':1, '중요성':1, '초도감사':1, '의견형성':1, '자회사':1}

scores.update(tuning)<jupyter_output><empty_output><jupyter_text>아래에서 튜닝되지 않은 OKT와 튜닝된 soynlp 형태소 분석기의 성능 차이를 확인할 수 있다. (사실 이렇게 되면 훈련된 것으로 볼 수 있어서, okt의 사전 추가 등으로 같은 결과를 얻을 수 있을것 같다.)<jupyter_code>print("soynlp(tuned): {0}".format(tokenizer.tokenize('중요계정잔액및공시내용에대해질문분석적절차표본점검문서검증재계산등)')))
print("okt(not-tuned): {0}".format(okt.morphs("중요 계정잔액 및 공시내용에 대해 질문 분석적절차 표본점검 문서검증 재계산 등")))<jupyter_output>soynlp(tuned): ['중요계정잔액', '및', '공시내용', '에대해', '질문', '분석적절차', '표본점검', '문서검증', '재계산', '등)']
okt(not-tuned): ['중요', '계정', '잔액', '및', '공', '시', '내용', '에', '대해', '질문', '분석', '적', '절차', '표본', '점검', '문서', '검증', '재', '계산', '등']<jupyter_text>마. 형태소 분석기 TF-IDF 로직 반영 (Narrative)<jupyter_code>from sklearn.feature_extraction.text import TfidfVectorizer
tfidf_vect = TfidfVectorizer(tokenizer=tokenizer)
tfidf_vect.fit(df["DATA"])<jupyter_output>C:\Users\yoont\anaconda3\lib\site-packages\sklearn\feature_extraction\text.py:489: UserWarning: The parameter 'token_pattern' will not be used since 'tokenizer' is not None'
  warnings.warn("The parameter 'token_pattern' will not be used"<jupyter_text>바. Supervised Machine Learning - Random Forest<jupyter_code>df_train = pd.read_excel("train_supervised.xlsx")
data = []
for i in list(df_train["DATA"]):
    temp = re.sub('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…》]', '', i)
    temp = ''.join(temp.split())
    temp = temp.replace("의견","$$").replace("과목","%%").replace("효과성","##").replace("결과","@@")
    stopword = ["유의적", "및", "의", "등", "에대한", "에대해", "에대하여", "와", "과", "를", "을"]
    for word in stopword:
        temp = temp.replace(word," ")
    temp = temp.replace("$$", "의견").replace("%%", "과목").replace("##", "효과성").replace("@@","결과")
    data.append(temp)
df_train["DATA"] = data
tfidf_matrix_train = tfidf_vect.transform(df_train["DATA"])
y_train = df_train["LABEL"]
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import accuracy_score
dt_clf_rfc = RandomForestClassifier(random_state=156)
parameters_rfc = {'max_depth':[30, 40, 50],
                  'n_estimators':[40, 50, 60]}
grid_dtree_rfc = GridSearchCV(dt_clf_rfc, param_grid=parameters_rfc, cv=20, refit=True)
grid_dtree_rfc.fit(tfidf_matrix_train, y_train)
estimator_rfc = grid_dtree_rfc.best_params_
print(estimator_rfc)
pred_rfc = grid_dtree_rfc.predict(tfidf_matrix_train)
accuracy_rfc = accuracy_score(y_train, pred_rfc)
print(accuracy_rfc)
df_train["pred"] = pred_rfc
df_train.to_excel("train_pred.xlsx")<jupyter_output><empty_output><jupyter_text>다음 단계로 넘어가기 전에 예측 데이터를 추출하여 지도 단계의 오류를 train_supervised 파일에 반영하여 모델을 다시 생성하는 절차를 반복한다.<jupyter_code>df_test = pd.read_excel("test_supervised.xlsx")
data = []
for i in list(df_test["DATA"]):
    temp = re.sub('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…》]', '', i)
    temp = ''.join(temp.split())
    temp = temp.replace("의견","$$").replace("과목","%%").replace("효과성","##").replace("결과","@@")
    stopword = ["유의적", "및", "의", "등", "에대한", "에대해", "에대하여", "와", "과", "를", "을"]
    for word in stopword:
        temp = temp.replace(word," ")
    temp = temp.replace("$$", "의견").replace("%%", "과목").replace("##", "효과성").replace("@@","결과")
    data.append(temp)
df_test["DATA"] = data
tfidf_matrix_test = tfidf_vect.transform(df_test["DATA"])
y_test = df_test["LABEL"]
pred = grid_dtree_rfc.predict(tfidf_matrix_test)
accuracy = accuracy_score(y_test, pred)
print(accuracy)
df_test["pred"] = pred
df_test
df_test.to_excel("test_pred.xlsx")<jupyter_output><empty_output><jupyter_text>600개 학습모형의 TEST 결과 Accuracy score가 89.5%로 개선이 필요하였다. 분류 결과는 아래 표에서 확인할 수 있다. 이에 따라, 원자료에서 Train 데이터에 임의선택한 3번 레이블 300개와 1번 레이블 100개를 추가하여 학습모형을 개선하였다.       Supervised    Prediction    Count    Percentage        1    1    215    91         1    2    21    9         1    3    1    0             소계    237    100               2    1    16    5         2    2    312    95         2    3    1    0             소계    329    100         3    1    9    26         3    2    14    41         3    3    11    32             소계    34    100    데이터를 추가한 학습 DATA로 모형을 다시 학습한다.<jupyter_code>df_train = pd.read_excel("train_supervised_second.xlsx")
data = []
for i in list(df_train["DATA"]):
    temp = re.sub('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…》]', '', i)
    temp = ''.join(temp.split())
    temp = temp.replace("의견","$$").replace("과목","%%").replace("효과성","##").replace("결과","@@")
    stopword = ["유의적", "및", "의", "등", "에대한", "에대해", "에대하여", "와", "과", "를", "을"]
    for word in stopword:
        temp = temp.replace(word," ")
    temp = temp.replace("$$", "의견").replace("%%", "과목").replace("##", "효과성").replace("@@","결과")
    data.append(temp)
df_train["DATA"] = data
tfidf_matrix_train = tfidf_vect.transform(df_train["DATA"])
y_train = df_train["LABEL"]
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import accuracy_score
dt_clf_rfc = RandomForestClassifier(random_state=156)
parameters_rfc = {'max_depth':[60, 70, 80],
                  'n_estimators':[50, 60, 70]}
grid_dtree_rfc = GridSearchCV(dt_clf_rfc, param_grid=parameters_rfc, cv=20, refit=True)
grid_dtree_rfc.fit(tfidf_matrix_train, y_train)
estimator_rfc = grid_dtree_rfc.best_params_
print(estimator_rfc)
pred_rfc = grid_dtree_rfc.predict(tfidf_matrix_train)
accuracy_rfc = accuracy_score(y_train, pred_rfc)
print(accuracy_rfc)
df_train["pred"] = pred_rfc
df_train.to_excel("train_second_pred.xlsx")<jupyter_output><empty_output><jupyter_text>Test data에 다시 확인한다. 약간 개선되었다. (0.9 → 0.92)<jupyter_code>pred = grid_dtree_rfc.predict(tfidf_matrix_test)
accuracy = accuracy_score(y_test, pred)
print(accuracy)
df_test["pred_second"] = pred
df_test.to_excel("test_pred.xlsx")<jupyter_output><empty_output><jupyter_text>사. Unlabelled Data 분류 작업<jupyter_code>df_pred = pd.read_excel("predict.xlsx")
df_pred.head()
data = []
for i in list(df_pred["ACTIVITY"]):
    temp = re.sub('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…》]', '', str(i))
    temp = ''.join(temp.split())
    temp = temp.replace("의견","$$").replace("과목","%%").replace("효과성","##").replace("결과","@@")
    stopword = ["유의적", "및", "의", "등", "에대한", "에대해", "에대하여", "와", "과", "를", "을"]
    for word in stopword:
        temp = temp.replace(word," ")
    temp = temp.replace("$$", "의견").replace("%%", "과목").replace("##", "효과성").replace("@@","결과")
    data.append(temp)
df_pred["ACTIVITY"] = data
tfidf_matrix_pred = tfidf_vect.transform(df_pred["ACTIVITY"])
pred = grid_dtree_rfc.predict(tfidf_matrix_pred)
df_pred["LABEL"] = pred
df_pred.head()
df_pred.to_excel("predict_labelled.xlsx")<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.19509495018308778, 'fraction_numerical': 0.026654176956484716, 'mean_word_length': 4.4219759926131115, 'pattern_counts': {'":': 0, '<': 66, '<?xml version=': 0, '>': 66, 'https://': 5, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '16', 'dir': 'jupyter-structured-clean-dedup', 'id': '145863', 'license': 'MIT', 'n_tokens_mistral': 6530, 'n_tokens_neox': 7944, 'n_words': 1068, 'path': '(F-5) textClassification.ipynb', 'repo_name': 'chunsj/dart-scraping', 'size': '11743'}