심판 판결 결과 예측 해보기

Step. 1 데이터 준비

심판문 데이터는 2018년도 심판문 중 결정요지 데이터를 활용했습니다. 해당 데이터는 조세심판원 홈페이지에 공개 되어 있어 다운로드 받을 수 있습니다.

def read_data(filename):
    with io.open(filename, 'r',encoding='utf-8') as f:
        data = [line for line in f.read().splitlines()]
        data = data[1:]
    return data 

train_data = read_data('./data/2018_simpan_newgroup.csv')

해당 파일은 “번호|심판결정요지|유형”으로 구분되어 있습니다. train_data를 읽어보면 아래와 같습니다.

train_data[0:3]
['0|이 건 심판청구는 처분청의 직권경정으로 인하여 심리일 현재 청구의 대상이 되는 처분이 존재하지 아니하므로 부적법한 청구로 판단됨|0',
 '1|처분청의 2016년 제2기 부가가치세 경정결정 후 청구인이 심판청구를 제기하여 2017.10.24. 이미 기각결정을 받았으므로 이 건 심판청구는 동일한 처분에 대하여 중복하여 제기된 점, 청구인은 당초 심판청구와 동일한 내용의 경정청구를 하였고, 그에 대한 처분청의 거부통지는 민원회신에 불과한 것이어서 심판청구의 대상이 되는 처분으로 볼 수 없는 점 등에 비추어 이 건 심판청구는 부적법한 청구로 판단됨|0',
 '2|처분청이 청구주장을 받아들여 이 건 과세처분을 직권으로 감액경정하였으므로 이 건 심판청구는 심리일 현재 불복 대상이 되는 처분이 존재하지 아니하여 부적법한 청구에 해당하는 것으로 판단됨|0']

다음으로 받은 데이터를 konlpy.Okt()를 사용하여 형태소를 분리합니다. 분리한 데이터를 토큰화 하여 데이터 셋을 만듭니다. 데이터 셋을 만드는 과정에서 심판문의 개인정보를 익명처리하기 위해서 사용했던 특수기호들을 제거합니다.

train_tokens[0:3]
array([[list(['이', '건', '심판', '청구', '는', '처분', '청', '의', '직권', '경정', '으로', '인하다', '심리', '일', '현재', '청구', '의', '대상', '이', '되다', '처분', '이', '존재', '하다', '아니다', '부', '적법하다', '청구', '로', '판단', '되다']),
        0],
       [list(['처분', '청', '의', '2016년', '제', '2', '기', '부가가치세', '경정', '결정', '후', '청구인', '이', '심판', '청구', '를', '제기', '하다', '2017', '10', '24', '이미', '기', '각', '결정', '을', '받다', '이', '건', '심판', '청구', '는', '동일하다', '처분', '에', '대하', '여', '중복', '하다', '제기', '되다', '점', ',', '청구인', '은', '당초', '심판', '청구', '와', '동일하다', '내용', '의', '경정', '청구', '를', '하다', ',', '그', '에', '대한', '처분', '청', '의', '거부', '통지', '는', '민원', '회신', '에', '불과하다', '것', '이어서', '심판', '청구', '의', '대상', '이', '되다', '처분', '으로', '볼', '수', '없다', '점', '등', '에', '비추다', '이', '건', '심판', '청구', '는', '부', '적법하다', '청구', '로', '판단', '되다']),
        0],
       [list(['처분', '청', '이', '청구', '주장', '을', '받아들이다', '이', '건', '과세', '처분', '을', '직권', '으로', '감액', '경정', '하다', '이', '건', '심판', '청구', '는', '심리', '일', '현재', '불복', '대상', '이', '되다', '처분', '이', '존재', '하다', '아니다', '부', '적법하다', '청구', '에', '해당', '하다', '것', '으로', '판단', '되다']),
        0]], dtype=object)

해당 작업을 거치면 위와 같은 형태로 변경됩니다.

# supervised learning을 위한 text, label 생성
train_X = train_tokens[:,0]
train_Y = train_tokens[:,1]

판결은 각하, 기각, 취소,경정,재조사로 나눌 수 있습니다. 이중 취소, 경정, 재조사는 인용으로 다시 분류할 수 있어 최종 데이터는 각하(0), 기각(1), 인용(2)의 형태로 label을 만들 수 있습니다.

train_Y[0:10]
array([0, 0, 0, 1, 1, 0, 1, 1, 0, 1], dtype=object)
W2V = Word2Vec.Word2Vec()
train_Y_ = W2V.One_hot(train_Y)  ## Convert to One-hot
train_X_ = W2V.Convert2Vec("./model/FastText.model",train_X)

train_x 데이터는 word-embedding 형태로 만들어줍니다. 이는 one-hot-encoding 형태의 데이터보다 word- embedding이 학습에 더 유리하기 때문입니다.
자세한 설명는 word-embedding 자료를 보시길 권해드립니다.

W2V.Convert2Vec은 train_x의 값을 사전에 훈련한 FastText 모델의 벡터로 변환해주는 함수입니다. FastText는 본 블로그에 간단히 기술노트에 간단한 Test Code를 올려놨습니다.

Step. 2 모델 준비

다음과 같은 모델을 준비합니다.
예측을 위해 BiLSTM 모델을 사용했습니다. BiLSTM은 RNN 모델의 하나로 높은 성능을 발휘하는 모델로 알려져있습니다.

RNN 모델의 특성상 [batch_size, sequence_length, output_size]의 형태의 입력이 필요합니다. batch_size는 문장의 크기, sequence_length는 문장의 길이, output_size는 문장의 vector size입니다.

Batch_size = 32
Total_size = len(train_X)
Vector_size = 300
train_seq_length = [len(x) for x in train_X] # flexible input lenght
Maxseq_length = max(train_seq_length) ## 95
learning_rate = 0.001
lstm_units = 128
num_class = 3
training_epochs = 100

아래와 같이 모델을 선언합니다. BiLSTM은 LSTM 모델을 두개를 사용하여 forward, backward 방향으로 학습함으로 구현할 수 있습니다.

keras나 pytorch등을 사용하면 더 간단히 구현할 수 있습니다.

# ?, cell count, input dimension(one-hot)
X = tf.placeholder(tf.float32, shape = [None, Maxseq_length, Vector_size], name = 'X')

# ?, output(true,false)
Y = tf.placeholder(tf.float32, shape = [None, num_class], name = 'Y')

seq_len = tf.placeholder(tf.int32, shape = [None])
keep_prob = tf.placeholder(tf.float32, shape = None)

with tf.compat.v1.variable_scope('forward', reuse = tf.compat.v1.AUTO_REUSE):
    # hidden_size : 128
    lstm_fw_cell = tf.nn.rnn_cell.LSTMCell(lstm_units, forget_bias=1.0, state_is_tuple=True)
    lstm_fw_cell = tf.contrib.rnn.DropoutWrapper(lstm_fw_cell, output_keep_prob = keep_prob)

with tf.compat.v1.variable_scope('backward', reuse = tf.compat.v1.AUTO_REUSE):
    lstm_bw_cell = tf.nn.rnn_cell.LSTMCell(lstm_units, forget_bias=1.0, state_is_tuple=True)
    lstm_bw_cell = tf.contrib.rnn.DropoutWrapper(lstm_bw_cell, output_keep_prob = keep_prob)

with tf.compat.v1.variable_scope('Weights', reuse = tf.compat.v1.AUTO_REUSE):
    W = tf.get_variable(name="W", shape=[2 * lstm_units, num_class], dtype=tf.float32, initializer = tf.contrib.layers.xavier_initializer())
    b = tf.get_variable(name="b", shape=[num_class], dtype=tf.float32, initializer=tf.zeros_initializer())

두 모델을 합하여 하나의 학습 모델을 완성합니다.

with tf.variable_scope("loss", reuse = tf.AUTO_REUSE):
    (output_fw, output_bw), states = tf.nn.bidirectional_dynamic_rnn(lstm_fw_cell, lstm_bw_cell, dtype=tf.float32, inputs = X, sequence_length = seq_len)
    ## concat fw, bw final states
    outputs = tf.concat([states[0][1], states[1][1]], axis=1) #bi-lstm fully connected layer
    logits = tf.matmul(outputs, W) + b # hypothesis
    
    with tf.compat.v1.variable_scope("loss"):
        loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits = logits , labels = Y)) # Softmax loss
        optimizer = tf.train.AdamOptimizer(learning_rate=0.001).minimize(loss) # Adam Optimizer
prediction = tf.nn.softmax(logits)
correct_pred = tf.equal(tf.argmax(prediction, 1), tf.argmax(Y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

total_batch = int(len(train_X) / Batch_size)

train_acc = []
train_loss = []
history_loss = []

print("Start training!")
with tf.Session(config = config) as sess:
    start_time = time.time()
    sess.run(tf.global_variables_initializer())
    
    for epoch in range(training_epochs):

        avg_acc, avg_loss = 0. , 0.
        mask = np.random.permutation(len(train_X_)) #shuffle all row
        train_X_ = train_X_[mask]
        train_Y_ = train_Y_[mask]
        
        for step in range(total_batch):
            train_batch_X = train_X_[step*Batch_size : step*Batch_size+Batch_size] # 32 batch size
            train_batch_Y = train_Y_[step*Batch_size : step*Batch_size+Batch_size]
            batch_seq_length = train_seq_length[step*Batch_size : step*Batch_size+Batch_size]
            
            train_batch_X = W2V.Zero_padding(train_batch_X, Batch_size, Maxseq_length, Vector_size) # 32, 255, 300 -> fill zero with empty rows words. max row words 255
            
            sess.run(optimizer, feed_dict={X: train_batch_X, Y: train_batch_Y, seq_len: batch_seq_length})
            
            # Compute average loss
            loss_ = sess.run(loss, feed_dict={X: train_batch_X, Y: train_batch_Y, seq_len: batch_seq_length, keep_prob : 0.75})
            avg_loss += loss_ / total_batch
            
            acc_ = sess.run(accuracy , feed_dict={X: train_batch_X, Y: train_batch_Y, seq_len: batch_seq_length, keep_prob : 0.75})
            avg_acc += acc_ / total_batch
            
            history_loss.append(loss_)
            print("epoch :{}-{}  {:02d} step : {:04d} loss = {:.6f} accuracy= {:.6f}".format(step*Batch_size, step*Batch_size+Batch_size, epoch+1, step+1, loss_, acc_))
   
        print("<Train> Loss = {:.6f} Accuracy = {:.6f}".format(avg_loss, avg_acc))
      
        train_loss.append(avg_loss)
        train_acc.append(avg_acc)


    save_path = saver.save(sess, modelName)
    
    print ('save_path',save_path)

학습이 종료가 되고 아래와 같은 코드를 입력하여 학습이 잘 됐는지 확인해봅니다.

import matplotlib.pyplot as plt
plt.figure(figsize=(10,5))
plt.plot(history_loss)

“심판 판결 결과 예측 해보기”에 대한 2개의 댓글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다