はじめに: 『R環境で小説のテキストマイニング』の連載シリーズです。
テキストマイニングは、テキストデータを定量的に扱って、有益な情報を抽出するデータマイニング手法の1つです。 このようなテキストの情報解析では、自然言語処理、形態素解析、キーワード抽出、共起分析、ネットワーク可視化、機械学習、感情分析など、様々な分析手法が用いられます。 近年では各種のAPIやWebスクレイピングなどを利用して、ウェブやSNS、地図上のクチコミからテキストデータを取得して、テキストマイニングに活用されています。
この記事は、夏目漱石の「坊っちゃん」が対象小説で、 形態素解析と品詞のルールベースの複合語抽出、pytermextractによる複合語抽出を扱った内容となっています。
連載シリーズの目次
2. 「坊っちゃん」のテキストの前処理
3. 形態素解析と辞書設定
4. 形態素解析と複合語抽出 (ルールベース抽出、pytermextract)
5. テキストマイニングと形態素のワードクラウド
6. テキストマイニングとN-gramのネットワーク表現(章ごとの関係性とか)
7. テキストマイニングと単語の埋め込み生成(word2vecとか)
8. 文章の感情分析(感情ポジネガ分析とか、英語感情対応表とか、ML-askとか)
9. ミクロとマクロなテキスト解析〜応用篇として内容を考え中〜
「4. 形態素解析と複合語抽出 (ルールベース抽出、pytermextract)」に関する内容をやっていきます。
実行環境
#ターミナル上のR環境 R version 4.1.2 (2021-11-01) -- "Bird Hippie" Copyright (C) 2021 The R Foundation for Statistical Computing Platform: aarch64-apple-darwin20.6.0 (64-bit) #RStudioターミナル上のR環境 R version 4.1.2 (2021-11-01) -- "Bird Hippie" Copyright (C) 2021 The R Foundation for Statistical Computing Platform: x86_64-apple-darwin17.0 (64-bit)
M1 MacだとRStudioでRMeCabパッケージがうまく動作せず、 引き続き、形態素解析の結果はターミナルからRを実行して得た結果(result1.Rds)を使っています。
名詞、接頭辞、接尾辞をくっつける、品詞のルールベースの複合語抽出
RMeCabで形態素解析をすると、 形態素とともに、それに対応する品詞情報が出力されます。 この品詞情報をもとに、ルールベースで品詞、つまりは対応する形態素をくっつけて、 複合語を抽出します。
この複合語抽出プログラムでは、ある特定の品詞が出現した時に、 その品詞をルールベースでくっつけていくというものです。
例えば、RMeCabの出力で見られる品詞の場合では、 名詞、接頭詞、あるいは接尾詞が連続する箇所を見つけて、 それらの形態素を結合させて、複合語を生成します。 その後、複合語かどうか判定して*1、 複合語として抽出するものです。
今回、品詞ルールベースの複合語抽出関数をR言語で実装して、
Compound_calc
という自作関数を用意しました。
RコードをGitHubからソースして、形態素解析の結果を入力して実行します。
##形態素解析の結果(result1.Rds)をR環境にロードする Rds_path_all <- "https://github.com/kumeS/Blog/raw/master/R_text_analysis/R_02/result1.Rds" download.file(url=Rds_path_all, destfile = basename(Rds_path_all)) result1 <- readRDS(file = "result1.Rds") #ヘッド表示 head(result1) # parts mor #1 名詞 親譲り #2 助詞 の #3 名詞 無鉄砲 #4 助詞 で #5 名詞 小供 #6 助詞 の #複合語抽出: Compound_calc()関数を使います source("https://raw.githubusercontent.com/kumeS/Blog/master/R_text_analysis/R_03/Compound_calc.R") #実行 result2 <- Compound_calc(result = result1) #結果カウント: 137個の複合語が抽出できました sum(result2$parts == "複合語") #[1] 137 #結果表示 head(result2[result2$parts == "複合語",], n=20) # parts mor #18 複合語 時分学校 #89 複合語 ーい #115 複合語 二階 #150 複合語 西洋製 #238 複合語 幸ナイフ #274 複合語 二十歩 #280 複合語 南上がり #296 複合語 一本 #342 複合語 庭続き #350 複合語 十三四 #368 複合語 四つ目垣 #381 複合語 夕方折戸 #397 複合語 時勘太郎 #549 複合語 四つ目垣 #579 複合語 片袖 #594 複合語 晩母 #606 複合語 片袖 #614 複合語 外いたずら #622 複合語 兼公 #633 複合語 人参畠
結果として、Compound_calc()関数で、137個の複合語が抽出できました。
多いと思うのか少ないと思うのかは、貴方次第です。。。
pytermextractを使った複合語抽出
ターミナル環境上での、pytermextractを使った複合語抽出
Pythonライブラリである、termextractは、テキストデータから専門用語を取り出すツールです。 termextractの特徴は、複合語からなる専門用語を抽出できることで、用語は重要度でランキングされます。重要度の低い用語も抽出されますが、それらはノイズとなる可能性が高くなります。
まずは、ターミナルを起動して、pytermextractとかjanomeとかをセットアップします。
#pytermextractのダウンロード・解凍 mkdir pytermextract cd pytermextract wget http://gensen.dl.itc.u-tokyo.ac.jp/soft/pytermextract-0_02.zip unzip pytermextract-0_02.zip #pytermextractのセットアップ pip3 install . #... #Successfully installed termextract-0.12b0 #janomeのインストール pip3 install janome #... #Successfully installed janome-0.4.2
次に、テスト実行(1)として、 janome(日本語形態素解析器)の和文解析結果をもとに、 複合語・専門用語の抽出を試みてみます。 入力ファイルとしては、termextractのtest_data内のテキストファイルを使っています。
#テスト実行(1) #janome(日本語形態素解析器)の和文解析結果をもとに、専門用語を抽出する python3 ./pytermex/termex_janome.py ./test_data/jpn_sample.txt #結果表示 head janome_extracted.txt #人工知能 477.401125619928 #AI 117.77945491468365 #知能 108.83014288330233 #人間 32.526911934581186 #研究 23.237900077244504 #システム 22.360679774997898 #計算知能 19.934680700343144 #エキスパートシステム 16.548754598234364 #人工知能学会 13.456666729201407 #手法 12.727922061357857
次の実行例は、MeCabの和文解析結果をもとに、専門用語を抽出する例となります。
#Python版MeCabのインストール pip3 install mecab #... #Successfully installed mecab-0.996.3 #入力ファイル head ./test_data/mecab_out_sample.txt #人工 名詞,一般,*,*,*,*,人工,ジンコウ,ジンコー #知能 名詞,一般,*,*,*,*,知能,チノウ,チノー #EOS #テスト実行(2) #MeCabの和文解析結果をもとに、専門用語を抽出する python3 ./pytermex/termex_mecab.py ./test_data/mecab_out_sample.txt #結果表示 head mecab_extracted.txt #janomeの結果とほぼ同じなので省略
R環境上での、pytermextractを使った複合語抽出
R環境からpytermextractパッケージを使う場合には色々な方法がありますが、
手っ取り早いと思われる、
system()
関数を介して上記のpythonコマンドを実行する方法を紹介します。
実際に、コマンド実行する際の注意点としては、
Pythonのパスはフルパスを指定することです。
例えば、homebrewのフルパスなら、
/opt/homebrew/bin/python3
とかで記述してください。
相対パスにすると、何故かコケます。
R環境での、pytermextractの実行例と結果の読み込みについては、以下に示します。 pyファイルと入力テキストを作業ディレクトリに置いて、system()関数からpythonを実行します。
#termex_janome.pyのダウンロード file_path1 <- "https://raw.githubusercontent.com/kumeS/Blog/master/R_text_analysis/R_03/termex_janome.py" download.file(url=file_path1, destfile=basename(file_path1)) #「坊ちゃん」の全テキストのダウンロード file_path2 <- "https://raw.githubusercontent.com/kumeS/Blog/master/R_text_analysis/R_01/Bochan_text.all.txt" download.file(url=file_path2, destfile=basename(file_path2)) #system関数で、Python3実行: janome_extracted.txtに結果が出力されます system(paste0("/opt/homebrew/bin/python3 ", basename(file_path1), " ", basename(file_path2))) #結果の読み込み Comp <- read.table("janome_extracted.txt", sep="\t", header=F) #結果表示 head(Comp, n=20) # V1 V2 #1 赤シャツ 4388.31843 #2 清 444.48622 #3 人 364.00000 #4 学校 273.66403 #5 生徒 249.41532 #6 校長 235.72442 #7 野 219.59736 #8 山嵐 217.78889 #9 先生 203.64675 #10 教師 152.73506 #11 下宿 143.30038 #12 気 129.69194 #13 顔 108.89444 #14 目 104.57055 #15 自分 93.53074 #16 手 93.33810 #17 東京 93.33810 #18 江戸っ子 91.65151 #19 田舎 87.63561 #20 通り 84.24963
pytermextractは、専門用語の文章だと、 複合語・専門用語の抽出が上手くいってそうだったけども、 小説の文章だと、「う〜〜ん、この一語って複合語なのか?」 という出力結果が多い印象ですね。
推測ですが、小説の語彙構成だと、 決まった複合語の頻度が明らかに専門文書よりも少ないと考えられる。 pytermextractだと、うまく複合語が抽出されないのかもですね。
小説の場合だと、 品詞のルールベースで複合語を抽出するのも捨てたもんじゃないですね。 また、RMeCabのデフォルト辞書では無く、neologd辞書の結果を利用しているのも影響大かもしれないけども。
まとめ
今回のトピックは、複合語抽出(いわゆる、専門用語の抽出)でした。
日本語の複合語の判定と抽出は、面白くもあり、難題なテーマですね。
まぁ、形態素解析用の辞書が時代や分野に合わせて、常に充実していたら*2、特段複合語抽出も問題にはならないのですが。それは、理想郷ですよね。
補足
テキスト処理の関連記事
R Script - Compound_calc()
Compound_calc()は、RMeCabでの形態素解析結果のデータフレームを入力として、 品詞ルールベースで複合語を抽出するプログラムです。
Compound_calc <- function(result){ if(!any(colnames(result1) == c("parts", "mor"))){ stop("not proper column name") } result$id1 <- 1:nrow(result) result$id2 <- NA result$parts1 <- NA result$mor1 <- NA x <- 1 for(n in 1:nrow(result)){ #n <- 3 a <- result[n,1] %in% c("名詞", "接頭詞", "接尾詞") if(a){ if(n > 1){ if(result[n-1,1] %in% c("名詞", "接頭詞", "接尾詞")){ result[n,4] <- x }else{ x <- x + 1 result[n,4] <- x } }else{ result[n,4] <- x } }else{ x <- x + 1 result[n,4] <- x } } #head(result) if(n == nrow(result)){ for(m in 1:max(result$id2)){ #m <- 18 b <- result$mor[result$id2 == m] b1 <- paste0(b, collapse = "") d <- result$id1[result$id2 == m][1] if(length(b) == 1){ result$parts1[d] <- result$parts[d] result$mor1[d] <- b1 }else{ result$parts1[d] <- "複合語" result$mor1[d] <- b1 } } } result2 <- na.omit(result[,c("parts1", "mor1")]) colnames(result2) <- c("parts", "mor") return(data.frame(result2, row.names = 1:nrow(result2))) }