이 프로젝트는 @레이첼 외 1인과 함께 한 것임을 밝힙니다.

판결문의 성인지 감수성 탐지 프로젝트는 n번방 사건을 접하며 시작했다.

n번방은 판결을 먹고 자랐다

성범죄 솜방망이 처벌이 심각하다

판사들은 방조자와 다름없다

사회 곳곳에서 위와 같은 목소리가 쏟아졌다. 물론 법조계 내부에서도 예외는 아니었다. '강간 문화'를 간과하는 나의 법관 동료들에게라는 글이 시사인에 실리기도 했다. 이런 뉴스를 접하고 궁금증이 생겼다.
n번방은 정말 판결문을 먹고 자란걸까? 법원의 성인지 감수성이 정말 심각한 수준인가? 혹시 몇몇 나쁜 사례가 언론을 통해 ‘과대표'된 건 아닐까? 법원의 성인지 감수성은 정말 낮을까?

궁금증을 풀기 위해 성폭력범죄 판결문을 직접 들여다봤습니다. 그리고 그 분석 결과를 ‘성인지 감수성 지수'로 나타내보았다.

최종 보고서 (1).png

1. 사용 데이터

판결문 데이터를 분석하려면 먼저 판결문이 있어야한다. 판결문 데이터는 전자법률도서관에서 수집했다. 사이트에서 ‘성폭력범죄'를 검색해서, 검색 결과 잡히는 판결문 전체를 웹크롤링, json 파일로 저장했다.

그 결과 1995년부터 2020년 6월 3일까지, 2400여개(총 2395개) 판결문을 확보했다.

최종 보고서 (2).png

이후 간단한 전처리를 거쳤다.

2. 어떤 기준으로 '성인지 감수성'을 평가할 것인가?

최종 보고서 (3).png

위 세 가지 기준을 참고해 성인지 감수성이 낮은 판결문 8개를 선정, 기준으로 삼았다.

최종 보고서 (4).png

최종 보고서 (5).png

3. 판결문의 '성인지 감수성'을 어떻게 지수화할 것인가?

IF-IDF 알고리즘을 이용한다.

최종 보고서 (6).png

from google.colab import drive
drive.mount('/content/drive')
Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount("/content/drive", force_remount=True).
import csv
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import re

data = pd.read_csv('/content/drive/Shareddrives/텍스트마이닝_씨스루조/data/final(noJudge).csv')
data = data[['사건번호', '법원명', '날짜', '죄명', '주문', '이유']]
data.head(3)
사건번호 법원명 날짜 죄명 주문 이유
0 2012고합543 2012전고31 울산지방법원 2013.6.14 ['[성폭력범죄의처벌및피해자보호등에관한법률위반(특수강도강간등)', '성폭력범죄의처벌... 주 문피고인을 징역 30년에 처한다.압수된 조립컴퓨터 본체(증 제5호) 중 피해자들... 이 유범죄사실 및 부착명령 원인사실[범죄사실]1. 성폭력범죄의처벌및피해자보호등에관한...
1 2010고합63 2010전고2 대구지방법원 2010.3.26 ['[가.성폭력범죄의처벌및피해자보호등에관한법률위반(강간등상해)나.성폭력범죄의처벌및피... [주문]피고인을 징역 10년에 처한다.피고인에 대한 공개정보를 10년간 정보통신망을... [이유]▣ 범죄사실 및 부착명령 원인사실피고인 겸 피부착명령청구인(이하 ‘피고인’이...
2 2005헌마256 헌법재판소 2005.3.22 ['[성폭력범죄의처벌및피해자보호등에관한법률위헌확인]'] 주 문이 사건 심판청구를 각하한다. 이 유1. 사건의 개요청구인은 성폭력범죄의처벌및피해자보호등에관한법률위반(강간등상해)...
bad_cases = [' 96도791', '95도2914', '2019누10176', '2008고합220', '2009고합6', '2018노3606', '2015노1180', '2018노2855']
bad_list = list()

for i, row in data.iterrows():
  for number in bad_cases:
    if number in row['사건번호']:
      bad_list.append(row['이유']) # 판결문의 '이유' 부분에 판결 요지 등이 담겨 있으므로 이 부분을 분석한다.
# 이를 위해서는 먼저 colab에 mecab을 설치해야 한다 
%%bash
apt-get update
apt-get install g++ openjdk-8-jdk python-dev python3-dev
pip3 install JPype1
pip3 install konlpy
Hit:1 http://security.ubuntu.com/ubuntu bionic-security InRelease
Hit:2 https://cloud.r-project.org/bin/linux/ubuntu bionic-cran40/ InRelease
Ign:3 https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64  InRelease
Ign:4 https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64  InRelease
Hit:5 https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64  Release
Hit:6 https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64  Release
Hit:7 http://archive.ubuntu.com/ubuntu bionic InRelease
Hit:8 http://ppa.launchpad.net/c2d4u.team/c2d4u4.0+/ubuntu bionic InRelease
Hit:9 http://archive.ubuntu.com/ubuntu bionic-updates InRelease
Hit:10 http://archive.ubuntu.com/ubuntu bionic-backports InRelease
Hit:11 http://ppa.launchpad.net/cran/libgit2/ubuntu bionic InRelease
Hit:12 http://ppa.launchpad.net/deadsnakes/ppa/ubuntu bionic InRelease
Hit:14 http://ppa.launchpad.net/graphics-drivers/ppa/ubuntu bionic InRelease
Reading package lists...
Reading package lists...
Building dependency tree...
Reading state information...
python-dev is already the newest version (2.7.15~rc1-1).
g++ is already the newest version (4:7.4.0-1ubuntu2.3).
python3-dev is already the newest version (3.6.7-1~18.04).
openjdk-8-jdk is already the newest version (8u292-b10-0ubuntu1~18.04).
0 upgraded, 0 newly installed, 0 to remove and 58 not upgraded.
Requirement already satisfied: JPype1 in /usr/local/lib/python3.7/dist-packages (1.3.0)
Requirement already satisfied: typing-extensions; python_version < "3.8" in /usr/local/lib/python3.7/dist-packages (from JPype1) (3.7.4.3)
Requirement already satisfied: konlpy in /usr/local/lib/python3.7/dist-packages (0.5.2)
Requirement already satisfied: colorama in /usr/local/lib/python3.7/dist-packages (from konlpy) (0.4.4)
Requirement already satisfied: JPype1>=0.7.0 in /usr/local/lib/python3.7/dist-packages (from konlpy) (1.3.0)
Requirement already satisfied: lxml>=4.1.0 in /usr/local/lib/python3.7/dist-packages (from konlpy) (4.2.6)
Requirement already satisfied: numpy>=1.6 in /usr/local/lib/python3.7/dist-packages (from konlpy) (1.19.5)
Requirement already satisfied: beautifulsoup4==4.6.0 in /usr/local/lib/python3.7/dist-packages (from konlpy) (4.6.0)
Requirement already satisfied: tweepy>=3.7.0 in /usr/local/lib/python3.7/dist-packages (from konlpy) (3.10.0)
Requirement already satisfied: typing-extensions; python_version < "3.8" in /usr/local/lib/python3.7/dist-packages (from JPype1>=0.7.0->konlpy) (3.7.4.3)
Requirement already satisfied: requests[socks]>=2.11.1 in /usr/local/lib/python3.7/dist-packages (from tweepy>=3.7.0->konlpy) (2.23.0)
Requirement already satisfied: six>=1.10.0 in /usr/local/lib/python3.7/dist-packages (from tweepy>=3.7.0->konlpy) (1.15.0)
Requirement already satisfied: requests-oauthlib>=0.7.0 in /usr/local/lib/python3.7/dist-packages (from tweepy>=3.7.0->konlpy) (1.3.0)
Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests[socks]>=2.11.1->tweepy>=3.7.0->konlpy) (2.10)
Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests[socks]>=2.11.1->tweepy>=3.7.0->konlpy) (1.24.3)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests[socks]>=2.11.1->tweepy>=3.7.0->konlpy) (2021.5.30)
Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests[socks]>=2.11.1->tweepy>=3.7.0->konlpy) (3.0.4)
Requirement already satisfied: PySocks!=1.5.7,>=1.5.6; extra == "socks" in /usr/local/lib/python3.7/dist-packages (from requests[socks]>=2.11.1->tweepy>=3.7.0->konlpy) (1.7.1)
Requirement already satisfied: oauthlib>=3.0.0 in /usr/local/lib/python3.7/dist-packages (from requests-oauthlib>=0.7.0->tweepy>=3.7.0->konlpy) (3.1.1)
%env JAVA_HOME "/usr/lib/jvm/java-8-openjdk-amd64"
env: JAVA_HOME="/usr/lib/jvm/java-8-openjdk-amd64"
%%bash
bash <(curl -s https://raw.githubusercontent.com/konlpy/konlpy/master/scripts/mecab.sh)
pip3 install /tmp/mecab-python-0.996
mecab-ko is already installed
mecab-ko-dic is already installed
mecab-python is already installed
Done.
Processing /tmp/mecab-python-0.996
Building wheels for collected packages: mecab-python
  Building wheel for mecab-python (setup.py): started
  Building wheel for mecab-python (setup.py): finished with status 'done'
  Created wheel for mecab-python: filename=mecab_python-0.996_ko_0.9.2-cp37-cp37m-linux_x86_64.whl size=141815 sha256=a56485f12e7e53231647f675a18ddfabd12ed624970603ab6d2c8ea65ff84068
  Stored in directory: /root/.cache/pip/wheels/99/75/a6/e9e73a1dbd73579383644942ef18a6d17ad728a3052a1147fb
Successfully built mecab-python
Installing collected packages: mecab-python
  Found existing installation: mecab-python 0.996-ko-0.9.2
    Uninstalling mecab-python-0.996-ko-0.9.2:
      Successfully uninstalled mecab-python-0.996-ko-0.9.2
Successfully installed mecab-python-0.996-ko-0.9.2
data.head(2)
사건번호 법원명 날짜 죄명 주문 이유
0 2012고합543 2012전고31 울산지방법원 2013.6.14 ['[성폭력범죄의처벌및피해자보호등에관한법률위반(특수강도강간등)', '성폭력범죄의처벌... 주 문피고인을 징역 30년에 처한다.압수된 조립컴퓨터 본체(증 제5호) 중 피해자들... 이 유범죄사실 및 부착명령 원인사실[범죄사실]1. 성폭력범죄의처벌및피해자보호등에관한...
1 2010고합63 2010전고2 대구지방법원 2010.3.26 ['[가.성폭력범죄의처벌및피해자보호등에관한법률위반(강간등상해)나.성폭력범죄의처벌및피... [주문]피고인을 징역 10년에 처한다.피고인에 대한 공개정보를 10년간 정보통신망을... [이유]▣ 범죄사실 및 부착명령 원인사실피고인 겸 피부착명령청구인(이하 ‘피고인’이...
def pre_process(text):
  '''
  문자열을 받아 형태소 분석을 한 후, 명사형과 동사형만 필터링한다.
  '''
  import konlpy
  from konlpy.tag import Kkma, Komoran, Hannanum, Okt
  from konlpy.utils import pprint
  from konlpy.tag import Mecab # 형태소 분석기로 Mecab 사용 

  sent_tag = list() # 형태소 태그를 담을 리스트 
  words = str()  # 특정 형태소만 필터링한 단어들만 남길 문자열

  mecab = Mecab()
  morph = mecab.pos(text)
  sent_tag.append(morph)

  for sent in sent_tag:
    for word, tag in sent:
      if tag in ['NNP', 'NNG', 'VV']: # 명사형 및 동사형만 
        words += word
        words += ' '

  return words
processed_bad_list = list() # 전처리를 마친 데이터가 담길 리스트

for case in bad_list:
  processed = pre_process(case)
  processed_bad_list.append(processed)
from sklearn.feature_extraction.text import TfidfVectorizer

tfidf_vectorizer = TfidfVectorizer() # TF-IDF 객체 선언
tfidf_vectorizer.fit(processed_bad_list) # 단어를 학습시킴

bad_array = tfidf_vectorizer.transform(processed_bad_list).toarray() # TF-IDF를 이용해 단어 가중치가 적용된 숫자 벡터를 어레이 형태로 보자 
bad_array
array([[0.        , 0.        , 0.        , ..., 0.        , 0.04970097,
        0.        ],
       [0.02002635, 0.        , 0.02389556, ..., 0.        , 0.        ,
        0.        ],
       [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ],
       ...,
       [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ],
       [0.        , 0.02772433, 0.        , ..., 0.        , 0.        ,
        0.        ],
       [0.        , 0.        , 0.        , ..., 0.01779676, 0.07118704,
        0.0212352 ]])

최종 보고서 (7).png

def cos_sim(doc1, doc2): 
    '''
    코사인 유사도로 doc1과 doc2간 유사도를 구한다. 
    '''
    from sklearn.metrics.pairwise import linear_kernel
    result = linear_kernel(doc1, doc2)
    return result

def mean_sim(doc_list, target):
    '''
    문서간 유사도 평균 구하기 
    '''
    temp = 0
    n = 1
    for doc in doc_list:
        temp += cos_sim(doc, target)
        n += 1
    return temp/len(doc_list)

4. 성인지 감수성 지수 성능을 어떻게 평가할 것인가?

최종 보고서 (8).png

최종 보고서 (9).png

최종 보고서 (10).png

그 결과 판결문 3개를 제외하고 나머지 7개 판결문은 비슷한 점수가 나왔다. 왼쪽은 문서간 유사도를 통해 산출한 성인지 감수성 점수이고 오른쪽은 연구자들이 직접 매긴 점수의 평균이다.

꽤 잘 들어맞는다!

5. Summary

최종 보고서 (11).png