소설 「고배는 고양이이다」로 벤포드의 법칙이 성립하고 있는지를 확인해 본다

1. 소개



프로그래밍을 학습하기 시작 자연어 처리에 관심이 있었기 때문에 자연언어처리 100개 노크 2020을 공부하기 시작했다. 제4장: 형태소 해석 을 참고로 하면서 나츠메 소세키의 소설 '고배는 고양이이다'의 문장중의 숫자를 추출하여 벤포드의 법칙이 성립되어 있는지 조사하기로 했다.

2.목차



1. 소개
2.목차
3. 벤포드의 법칙
4. 코드
5. 정리
참고

3. 벤포드의 법칙



Benford의 법칙은 주소, 주가, 전화 요금 등 우리 몸에있는 것의 숫자의 첫 번째 자리의 분포에 대한 법칙입니다. 이들의 숫자는 1 ~ 9의 숫자 중 하나이며, 그 분포는 첫 번째 자리의 숫자를 n, 그 분포를 p로 하면
$$p =\log_{10}\frac{n+1}{n}$$
된다. 이것을 표로 하면 다음과 같이 된다.


n
1
2
3
4
5
6
7
8
9


p×100
30.1%
17.6%
12.5%
9.7%
7.9%
6.7%
5.8%
5.1%
4.6%


4. 코드



소설의 숫자 집합을 조사합니다.

n = []
def parse_line(line):

    (surface, attr) = line.split('\t')
    arr = attr.split(',')
    return {
        'surface': surface,
        'base': arr[6],
        'pos': arr[0],
        'pos1': arr[1]
    }

"""
    mecabでは以下のように読み込まれる
    表層形\t品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用形,活用型,原形,読み,発音
    ・・・
  表層形\t品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用形,活用型,原形,読み,発音
  EOS
  
    >>> parse_line('吾輩\\t名詞,代名詞,一般,*,*,*,吾輩,ワガハイ,ワガハイ')
    {'surface': '吾輩', 'base': '吾輩', 'pos': '名詞', 'pos1': '代名詞'}

    """
def read_mecab_file(filename) -> [[{}]]:

    sentence = []

    with open(filename, mode='rt', encoding='utf-8') as f:
        for line in f:
            line = line.rstrip('\n')
            if line == 'EOS':
                yield sentence
                sentence = []
            else:
                sentence.append(parse_line(line))


sentences = read_mecab_file('neko.txt.mecab')

for sentence in sentences:
    for word in sentence:
        if word['pos'] == '名詞' and word["pos1"] == "数":
            n.append(word['surface'])

print(list(set(n)))
['ひと', '一', '幾', '三', '余', '六', 'いく', '1', '億', '2', '7', '○', '3', '八', '数', '半', '2', '七', '1', '五', '九', '十', '万', '〇', '一返御駄仏', '四', '6', '二', '何', '百', '千']

와 같이 출력된다. 숫자 이외의 것이 섞여 있기 때문에 그들을 제거합니다.
while "半" in n :
    n.remove("半")
while "幾" in n: 
    n.remove("幾")
while "数" in n:
    n.remove("数")    
while "いく" in n:    
    n.remove("いく")
while "〇" in n:
    n.remove("〇")
while "何" in n:
    n.remove("何") 
while "ひと" in n:
    n.remove("ひと")
while "一返御駄仏" in n:
    n.remove("一返御駄仏")
while "○" in n:
    n.remove("○") 
while "余" in n:
    n.remove("余")
print(list(set(n)))
['一', '三', '六', '1', '億', '2', '7', '3', '八', '2', '七', '1', '五', '九', '十', '万', '四', '6', '二', '百', '千']

여기서 두 자리 이상의 숫자를 첫 번째 숫자로 변환합니다.
`for i in range(len(n)):
    if n[i] == "1":
        n[i] = "一"
    if n[i] =="1":
        n[i] = "一"
    if n[i] == "6":
        n[i] = "六"
    if n[i] == "百":
        n[i] = "一"
    if n[i] =="2":
        n[i] = "二"
    if n[i] =="十":
        n[i] = "一"
    if n[i] =="万":
        n[i] = "一"
    if n[i] =="億":
        n[i] = "一"
    if n[i] =="2":
        n[i] = "二"
    if n[i] =="千":
        n[i] = "一"
    if n[i] =="7":
        n[i] = "七"
    if n[i] =="3":
        n[i] = "三"

히스토그램을 만듭니다.
from kanjize import int2kanji, kanji2int
from matplotlib import pyplot as plt
import japanize_matplotlib

name = ['一', '二', '三', '四',"五","六","七","八","九"]
value = [n.count("一"), n.count("二"),n.count("三"), n.count("四"),n.count("五"),n.count("六"),n.count("七"),n.count("八"),n.count("九")]
plt.bar(name, value)
plt.show()



각 숫자의 비율은
p=[]
for i in range(len(value)):
    p.append(value[i]/sum(value))
print(p)
[0.45576823590274185, 0.1603724780134506, 0.1660631143300569, 0.05069839627521987, 0.05276771857216762, 0.04449042938437662, 0.02379720641489912, 0.03362648732540093, 0.012415933781686497]

되었다. 결과를 표에 정리한다.


n
1
2
3
4
5
6
7
8
9


p×100
30.1%
17.6%
12.5%
9.7%
7.9%
6.7%
5.8%
5.1%
4.6%

결과
45.6%
16.0%
16.6%
5.1%
5.3%
4.4%
2.4%
3.4%
1.2%


(수치를 반올림했다)

5. 정리



결과를 보면 역시 제일이 가장 많고, 숫자가 커짐에 따라 대략 출현 빈도는 떨어지고 있다. 이론치와 어긋난 이유로서는 샘플수가 1933으로 적은 것, 혹은 특정의 소설에는 성립하지 않는 등이 생각된다.
프로그래밍 초보자이자 벤포드의 법칙의 수식을 자세하게 팔로우하고 있지 않기 때문에, 지적, 코멘트 등을 받으면 다행입니다.

참고





6.추기



코멘트에서의 지적대로 이 방법에서는 간과하고, 잘못된 카운트가 되어 버리게 됩니다. 이번의 결과가 크게 바뀌는 것은 (아마) 없게 느껴집니다만, 해결 방법 등 있으면 가르쳐 주시면 매우 기쁩니다.

좋은 웹페이지 즐겨찾기