福澤諭吉 学問のすすめ 形態素解析とビジュアル化


󠄁󠄁福澤 諭吉(ふくざわ ゆきち、旧字体: 福󠄁澤 諭󠄀吉、天保5年12月12日〈1835年1月10日〉- 明治34年〈1901年〉2月3日)は、幕末から明治期の日本の啓蒙思想家、教育家。慶應義塾の創設者。諱は範(はん)。字は子圍(しい)。揮毫の落款印は「明治卅弐年後之福翁」。雅号は、三十一谷人(さんじゅういっこくじん)。

学問のすゝめ(學問ノスヽメ、がくもんのすすめ)は、福沢諭吉の著書のひとつであり代表作である。初編から17編までシリーズとして発行された。初編のみ小幡篤次郎共著。

1872年(明治5年2月)初編出版。以降、数年かけて順次刊行され、1876年(明治9年11月25日)十七編出版を以って一応の完成をみた。その後1880年(明治13年)に「合本學問之勸序」という前書きを加え、一冊の本に合本された。

ライブラリのインポート

import MeCab
import csv
import pandas as pd
import re
import matplotlib as mpl
import matplotlib.cm as cm
import matplotlib.pyplot as plt

import japanize_matplotlib
import seaborn as sns
from wordcloud import WordCloud

from matplotlib import rcParams
from matplotlib.colors import Normalize

import warnings
warnings.filterwarnings('ignore', category=matplotlib.MatplotlibDeprecationWarning)


%matplotlib inline

ファイル読み取り

学問のすすめのテキストファイルは青空文庫より拝借

MaCabファイルを作成する

with open("susume.txt") as file:
    text = file.read()
    # 全角スペースを削除
    text = text.replace(' ', '')
    
    # Mecab適応
    mecab = MeCab.Tagger()
    susume_mecab = mecab.parse(text)
    
len(susume_mecab.split())
321082

データ内容の確認

susume = susume_mecab.split('\n')

print(susume[10:20])
['ず\tズ\tズ\tず\t助動詞\t助動詞-ヌ\t連用形-一般\t', '人\tヒト\tヒト\t人\t名詞-普通名詞-一般\t\t\t0', 'の\tノ\tノ\tの\t助詞-格助詞\t\t\t', '下\tシタ\tシタ\t下\t名詞-普通名詞-一般\t\t\t0', 'に\tニ\tニ\tに\t助詞-格助詞\t\t\t', '人\tヒト\tヒト\t人\t名詞-普通名詞-一般\t\t\t0', 'を\tオ\tヲ\tを\t助詞-格助詞\t\t\t', '造ら\tツクラ\tツクル\t作る\t動詞-一般\t五段-ラ行\t未然形-一般\t2', 'ず\tズ\tズ\tず\t助動詞\t助動詞-ヌ\t終止形-一般\t', '」\t\t\t」\t補助記号-括弧閉\t\t\t']
print(len(susume_mecab.split('\n')))
55647

データフレームの作成

## タブと改行を削除してリスト化
susume_list = [i.split('\t') for i in susume_mecab.split('\n')]

df_susume = pd.DataFrame(susume_list)
# データ確認
df_susume
0 1 2 3 4 5 6 7
0 「 「 補助記号-括弧開
1 天 テン テン 天 名詞-普通名詞-一般 1
2 は ワ ハ は 助詞-係助詞
3 人 ヒト ヒト 人 名詞-普通名詞-一般 0
4 の ノ ノ の 助詞-格助詞
... ... ... ... ... ... ... ... ...
55642 する スル スル 為る 動詞-非自立可能 サ行変格 連体形-一般 0
55643 なかれ ナカレ ナイ 無い 形容詞-非自立可能 文語形容詞-ク 命令形 1
55644 。 。 補助記号-句点
55645 EOS None None None None None None None
55646 None None None None None None None

55647 rows × 8 columns

55645行目のEOS以降は、及び5列目からは不要なのでデータを整理

df_susume = df_susume.iloc[:-2,:5]

df_susume.head()
0 1 2 3 4
0 「 「 補助記号-括弧開
1 天 テン テン 天 名詞-普通名詞-一般
2 は ワ ハ は 助詞-係助詞
3 人 ヒト ヒト 人 名詞-普通名詞-一般
4 の ノ ノ の 助詞-格助詞

4列目に品詞が複数存在しているのでハイフンで分ける

正規表現で品詞を分割

df_parse = df_susume[4].str.extract(r'(\w+)-?(\w+)?-?(\w+)?')

dfの必要部分と品詞データフレームを結合

df = pd.concat([df_susume.iloc[:,:4], df_parse], axis=1)

コラム名を追加

df = df.set_axis(['surface','read1','read2','base','parse1', 'parse2', 'parse3'], axis=1)
df
surface read1 read2 base parse1 parse2 parse3
0 「 「 補助記号 括弧開 NaN
1 天 テン テン 天 名詞 普通名詞 一般
2 は ワ ハ は 助詞 係助詞 NaN
3 人 ヒト ヒト 人 名詞 普通名詞 一般
4 の ノ ノ の 助詞 格助詞 NaN
... ... ... ... ... ... ... ...
55640 を オ ヲ を 助詞 格助詞 NaN
55641 毛嫌い ケギライ ケギライ 毛嫌い 名詞 普通名詞 一般
55642 する スル スル 為る 動詞 非自立可能 NaN
55643 なかれ ナカレ ナイ 無い 形容詞 非自立可能 NaN
55644 。 。 補助記号 句点 NaN

55645 rows × 7 columns

頻出単語を探す

全単語の上位20

sns.set(font='IPAexGothic')
most_freq = df.surface.value_counts()[:20].sort_values()

my_cmap = cm.get_cmap('copper')
my_norm = Normalize(vmin=most_freq.min(), vmax=most_freq.max())
 
fig = plt.subplots(figsize=(12, 8))

ax = most_freq.plot(kind = 'barh',
                    color = my_cmap(my_norm(most_freq.values)),
                    alpha = .8
                   )

ax.bar_label(ax.containers[0], fontsize=12)
ax.title.set_size(24)

plt.title('頻出語 上位20',size=30)
plt.xlabel('回数', labelpad=15, size=18)
plt.ylabel("頻\n出\n語", labelpad=18, size=18, rotation=0, va='center')
plt.show()
def w_cloud(word_list, rank):

    # ワードクラウドオブジェクトの作成
    word_object = ','.join(word_list) 

    # フォントパスを指定
    font = '/opt/anaconda3/lib/python3.9/site-packages/japanize_matplotlib/fonts/ipam.ttc'

    # ワードクラウドを生成
    wordcloud = WordCloud(font_path=font,
                          background_color="lightgray", 
                          max_words = rank,
                          colormap = 'BrBG_r',
                          width = 600,
                          height = 400,
                         )

    # ワードクラウドを可視化
    wordcloud.generate(word_object)
    return wordcloud.to_image()
w_cloud(list(df.surface.values), 300)

品詞別 上位10

parse1_freq = df.parse1.value_counts().sort_values()

my_cmap = cm.get_cmap('copper')
my_norm = Normalize(vmin=parse1_freq.min(), vmax=parse1_freq.max())

fig = plt.subplots(figsize=(12, 8))

ax = parse1_freq.plot(kind = 'barh',
                    color = my_cmap(my_norm(parse1_freq.values)),
                    alpha = .8
                   )

ax.bar_label(ax.containers[0], fontsize=12)
ax.title.set_size(24)

plt.title('品詞別頻度',size=30)
plt.xlabel('語数', labelpad=15, size=18)
plt.ylabel("品\n詞\n名", labelpad=18, size=18, rotation=0, va='center')
plt.show()

単語検索

df_par = df.groupby('parse1').value_counts()
df_par = pd.DataFrame(df_par)
df_par
0
parse1 surface read1 read2 base parse2 parse3
名詞 こと コト コト 事 普通名詞 一般 474
人 ヒト ヒト 人 普通名詞 一般 378
もの モノ モノ 物 普通名詞 サ変可能 334
者 モノ モノ 者 普通名詞 一般 297
政府 セーフ セイフ 政府 普通名詞 一般 264
... ... ... ... ... ... ... ...
接尾辞 新 シン シン 新 名詞的 一般 1
掛け カケ カケ 掛け 名詞的 一般 1
挽き ヒキ ヒキ 挽き 名詞的 一般 1
ごと ゴト ゴト 毎 名詞的 一般 1
魚 ギョ ギョ 魚 名詞的 一般 1

3256 rows × 1 columns

def word_search(word,rank):
    """
    品詞名と表示するランクを渡すとグラフを返す
    """
    df_result = df[df.parse1.isin([word])]
    freq = df_result.surface.value_counts()[:rank].sort_values()

    _cmap = cm.get_cmap('copper')
    _norm = Normalize(vmin=freq.min(), vmax=freq.max())
 
    fig = plt.subplots(figsize=(12, (rank/4)+2))
    
    ax = freq.plot(kind = 'barh',
                    color = _cmap(_norm(freq.values)),
                    alpha = .8
                   )
    title = '頻出' + word + ' 上位' + str(rank)
    y_label = "".join([i + '\n' for i in word])
    x_label = '回数'
    plt.title(title,size=30)
    plt.xlabel(x_label, labelpad=15, size=18)
    plt.ylabel(y_label, labelpad=18, size=18, rotation=0, va='center')

    
    return plt.show()
word_search('名詞',20)
df_noun= df[df.parse1.isin(['名詞'])]

w_cloud(list(df_noun.surface.values), 500)
word_search('形容詞', 10)
df_adj= df[df.parse1.isin(['形容詞'])]

w_cloud(list(df_adj.surface.values), 500)
word_search('動詞',30)
df_verb= df[df.parse1.isin(['動詞'])]

w_cloud(list(df_verb.surface.values), 300)
word_search('代名詞', 10)
df_pronoun= df[df.parse1.isin(['代名詞'])]

w_cloud(list(df_pronoun.surface.values), 100)

漢字を含む語のみの場合

# パターンを定義
pt = r'[一-龥]'

# パターンを含む語のみのリストを作成
kanji_list = [i for i in df.surface.values if re.compile(pt).findall(i)]

len(set(kanji_list))
4468

4468種類を確認

リストからグラフ用のデータを作成

# pd.Seriesでカウントし、上位20に絞り、ソート
kanji = pd.Series(kanji_list).value_counts()[:20].sort_values()
my_norm = Normalize(vmin=kanji.min(), vmax=kanji.max())
 
fig = plt.subplots(figsize=(12, 8))

ax = kanji.sort_values().plot(kind = 'barh',
                    color = my_cmap(my_norm(kanji.values)),
                    alpha = .8
                   )

ax.bar_label(ax.containers[0], fontsize=12)
ax.title.set_size(24)

plt.title('漢字を含む頻出語 上位20',size=30)
plt.xlabel('回数', labelpad=15, size=18)
plt.ylabel("頻\n出\n語", labelpad=18, size=18, rotation=0, va='center')
plt.show()

漢字を含む上位1000語のワードクラウド出力

w_cloud(kanji_list, 1000)

片仮名の単語の場合

# パターンを定義
pt = r'[ァ-ヶ]'

# パターンを含む語のみのリストを作成
kana_list = [i for i in df.surface.values if re.compile(pt).findall(i)]

len(set(kana_list))
56
kana = pd.Series(kana_list).value_counts()[:20].sort_values()
my_norm = Normalize(vmin=kana.min(), vmax=kana.max())
 
fig = plt.subplots(figsize=(12, 8))

ax = kana.sort_values().plot(kind = 'barh',
                    color = my_cmap(my_norm(kana.values)),
                    alpha = .8
                   )

ax.bar_label(ax.containers[0], fontsize=12)
ax.title.set_size(24)

plt.title('片仮名を含む頻出語 上位20',size=30)
plt.xlabel('回数', labelpad=15, size=18)
plt.ylabel("頻\n出\n語", labelpad=18, size=18, rotation=0, va='center')
plt.show()

片仮名の単語はそもそも少ない

w_cloud(kana_list, 50)

福澤諭吉の画像にワードクラウドを適応してみる

執筆時の思惑、心情を理解するため可視化

必要ライブラリのインポート

from PIL import Image
from pylab import *

import cv2
import numpy as np
import urllib.request
import requests

画像urlの準備

# 画像urlはwikipediaから拝借

# 福澤諭吉
url_face = 'https://upload.wikimedia.org/wikipedia/commons/1/1a/Yukichi_Fukuzawa_1891.jpg'

# 一万円札
url_bill = 'https://upload.wikimedia.org/wikipedia/commons/d/d5/10000_Yenes_%28Anverso%29.jpg'

マスク画像の作成

お札画像から同じサイズの背景を作成

お札の画像確認

im = Image.open(requests.get(url_bill, stream=True).raw)
im

お札画像のサイズを取得する

w, h = im.size
w, h
(700, 333)

グレースケールで同じ大きさの画像を生成

blank_img = Image.new('L', (w, h))
blank_img.size
(700, 333)

顔写真からマスクを作成

顔写真を取得

# urlから画像を取得
req = urllib.request.urlopen(url_face)
arr = np.asarray(bytearray(req.read()), dtype=np.uint8)

# img変数に格納
# flag https://docs.opencv.org/3.4/d8/d6a/group__imgcodecs__flags.html
img = cv2.imdecode(arr, 0)

画像確認

Image.fromarray(img)

サイズ確認

Image.fromarray(img).size
(585, 756)

画像のトリミング

[上から:下、左から:右] のピクセル値で指定

cropped_img = img[110:526, 80:500]

お札の画像に近くするためにサイズ調整 おおよそ80%でアスペクト比を保持

re_img = cv2.resize(cropped_img, dsize=None, fx=0.8, fy=0.8)
Image.fromarray(re_img)

サイズ確認

re_img.shape
(333, 336)

二極化する

二極化の閾値のバリエーションで比較して確認する

ret, img_t64 = cv2.threshold(re_img, 64, 255, cv2.THRESH_BINARY)
ret, img_t96 = cv2.threshold(re_img, 96, 255, cv2.THRESH_BINARY)
ret, img_t128 = cv2.threshold(re_img, 128, 255, cv2.THRESH_BINARY)

fig = plt.subplots(figsize=(18, 8))

plt.subplot(1, 4, 1)
plt.title('Original')
plt.imshow(re_img, cmap='gray')

plt.subplot(1, 4, 2)
plt.title('64')
plt.imshow(img_t64, cmap='gray')

plt.subplot(1, 4, 3)
plt.title('96')
plt.imshow(img_t96, cmap='gray')

plt.subplot(1, 4, 4)
plt.title('128')
plt.imshow(img_t128, cmap='gray')

plt.show()

閾値 96 あたりから微調整して最終確認

ret, gray = cv2.threshold(re_img, 90,255, cv2.THRESH_BINARY)
Image.fromarray(gray)

反転してマスクを作成

mask = cv2.inRange(gray, 0, 50)
Image.fromarray(mask)
# 配列からイメージ作成
mask_img = np.array(mask)
bg_img = np.array(blank_img)

背景と顔を結合

concat_img = np.concatenate((bg_img, mask_img), axis=1)
concat_img.shape
(333, 1036)
# Image.fromarray(concat_img)

お札のサイズにトリミング

resized_mask =  concat_img[0:333, 336:1036]

Image.fromarray(resized_mask)

ワードクラウド関数を定義

リスト、語数、画像を入力値として渡す

def w_cloud_img(word_list, rank, mask):
    
    word_object = ','.join(word_list) 
    font = '/opt/anaconda3/lib/python3.9/site-packages/japanize_matplotlib/fonts/ipam.ttc'
    
    wordcloud = WordCloud(
                        mask = mask,
                        font_path = font,
                        background_color= 'rgb(249,245,198)',  # 画像から紙幣に近い色をピッキング
                        colormap = 'copper',#'BrBG_r',
                        max_words = rank,
                        prefer_horizontal = 0.9999,
                        collocations=True,
                        contour_width= 5, 
                        contour_color='rgb(111,99,75)'  # 画像から使用色をピッキング
                         )
    wordcloud.generate(word_object)
    
    return wordcloud.to_image()

作成した画像にワードクラウドを表示してみる

漢字を含む語のワードクラウド

# リスト、単語数、マスクでワードクラウドを作成

w_cloud_img(kanji_list, 1000, resized_mask)

すべての語によるワードクラウド

w_cloud_img(list(df.surface.values),5000, resized_mask)

マスクを生成する関数を定義

def creat_mask(url, threshold):
    """
    画像のurlと閾値わたすと生成した画像マスクを返す
    """
    req = urllib.request.urlopen(url)
    arr = np.asarray(bytearray(req.read()), dtype=np.uint8)
    img = cv2.imdecode(arr, 0)
    
    ret, gray = cv2.threshold(img, threshold, 255, cv2.THRESH_BINARY)
    mask = cv2.inRange(gray, 0, 50)
    
    return mask

紙幣の画像のマスクを作成してワードクラウドを適応すると、見にくい

w_cloud_img(list(df.surface.values), 1000, creat_mask(url_bill,80))

すべての名詞のワードクラウドをご本人様画像を使用して表示してみるとこのようになる

w_cloud_img(list(df_noun.surface.values), 15000, creat_mask(url_face,90))

Thank you !