Juliaによるテキストデータの前処理

記事の内容


Juliaによるテキストデータの前処理をまとめました. TextAnalysis.jlを試しに使用. まだ使いこなせていないので, 機会があればまたまとめ直したいです.

ドキュメントの作成

とりあえず以下のツールを用います.

【初期化】
using TextAnalysis
using WordTokenizers
import TinySegmenter

TextAnalysisにはいくつかの構造体が定義されています. そのうちの1つがファイルドキュメント. ただのファイルですが, 様々なフィールドが定義されています.

【ファイルドキュメントの作成】
#create file dicument
fd = FileDocument("../../data/testfile.txt")
【実行結果】
A FileDocument
 * Language: Languages.English()
 * Title: ../../data/testfile.txt
 * Author: Unknown Author
 * Timestamp: Unknown Time
 * Snippet: Hello World! This is a test file. O Romeo, Romeo,

他にもStringDocumentというものもあります. これは文字列です.

【文字列ドキュメントの作成】
#create string document
sd = StringDocument("O Romeo, Romeo, wherefore art thou Romeo?")
【実行結果】
A StringDocument{String}
 * Language: Languages.English()
 * Title: Untitled Document
 * Author: Unknown Author
 * Timestamp: Unknown Time
 * Snippet: O Romeo, Romeo, wherefore art thou Romeo?

これらのドキュメントからテキストを取り出すには次のようにします.

【ドキュメントのテキスト】
#text of the document
sd.text
【実行結果】
"O Romeo, Romeo, wherefore art thou Romeo?"

Authorフィールドも書き換えてみます.

【著者の変更】
#change the author name
sd.metadata.author = "W.Shakespeare"
sd
【実行結果】
A StringDocument{String}
 * Language: Languages.English()
 * Title: Untitled Document
 * Author: W.Shakespeare
 * Timestamp: Unknown Time
 * Snippet: O Romeo, Romeo, wherefore art thou Romeo?

前処理

いくつかの前処理を試します.

テキストのクリーニング

prepare!を用いると, 文字列中の様々な要素を除去できます. 例えばstrip_hrml_tagsを指定すれば, HTMLのタグを除去できます.

【HTMLタグの除去】
sd = StringDocument("<a href='Romeo and Juliet'>O Romeo, Romeo, wherefore art thou Romeo?</a">)
sd.text |> println
prepare!(sd,strip_html_tags) #remove the HTML tags
sd.text |> println
【実行結果】
<a href='Romeo and Juliet'>O Romeo, Romeo, wherefore art thou Romeo?</a>
O Romeo, Romeo, wherefore art thou Romeo?

ストップワードの除去

自然言語処理において処理の対象外となるストップワードを除去してみます. これもprepare!で簡単にできます.

【ストップワードの除去】
sd = StringDocument("One for all, all for one.")
sd.text |> println
prepare!(sd,strip_stopwords) #remove the stop words
sd.text |> println
【実行結果】
One for all, all for one.
One  ,   .

処理内容によっては削りすぎかもしれませんね...

テキストの正規化

同じ単語でも大文字や小文字が混じっていると厄介です. 小文字に揃えるには以下のようにします.

【小文字に統一】
#unify the font
sd = StringDocument("O Romeo, Romeo, wherefore art thou Romeo?")
remove_case!(sd)
sd.text |> println
【実行結果】
 romeo, romeo, wherefore art thou romeo?

また, 単数形や複数形などの揺れも厄介です. 単数形に統一するにはstem!を用います.

【文字の統一】
#stem
sd = StringDocument("musical 'cats'")
stem!(sd)
sd.text |> println
【実行結果】
music ' cat ' is a stori of a cat .

musicalmusicになるのはちょっと... storyも変な感じになってしまいました.

単語分割

次は単語分割です. tokensを使えば一発です.

【トークン化】
#tokenize
sd = StringDocument("O Romeo, Romeo, wherefore art thou Romeo?")
tokens(sd)
【実行結果】
10-element Vector{String}:
 "O"
 "Romeo"
 ","
 "Romeo"
 ","
 "wherefore"
 "art"
 "thou"
 "Romeo"
 "?"

日本語だとうまくいきません.

【トークン化】
#default tokenize
sd = StringDocument("ああロミオどうしてあなたはロミオなの")
tokens(sd)
【実行結果】
1-element Vector{String}:
 "ああロミオどうしてあなたはロミオなの"

こんな時は, tokenizerを変えてやります. tokenizerはいくつか定義されています. 日本語に関しては, TinySegmenterを使うとうまくいくようです.

【トークン化】
#change the tokenizer
set_tokenizer(TinySegmenter.tokenize)
sd = StringDocument("ああロミオどうしてあなたはロミオなの")
tokens(sd)
【実行結果】
10-element Vector{SubString{String}}:
 "ああ"
 "ロミオ"
 "どう"
 "して"
 "あ"
 "なた"
 "は"
 "ロミオ"
 "な"
 "の"

デフォルトの設定に戻しておきます.

【元に戻す】
#change the tokenizer to the default one
set_tokenizer(toktok_tokenize)

Bag of Words

ドキュメントをいくつか寄せ集めてコーパスを作成できます. 前処理も先ほど同様できます. prepare!に複数処理を命じる時は, パイプライン処理を施します.

【コーパスの作成】
#create corpus
crps = Corpus([
        StringDocument("<a href='Romeo and Juliet'>To be or not to be. </a>"),
        StringDocument("<a href='日本の思想'>To do or not to do. </a>")
        ])
prepare!(crps,strip_html_tags|strip_non_letters) #preprocessing
remove_case!(crps) #unify to lower case
update_lexicon!(crps) #update lexicon 

これを踏まえて, テキストをベクトル, または行列で表現します.

count encoding

count encodingでは, テキスト中に現れた回数を各単語に関して記録します. 横が単語, 縦がドキュメントの行列を作成します. 横の単語の並びは, termsフィールドから確認できます.

【count encoding】
#represent document as a matrix
M = DocumentTermMatrix(crps)
M.terms |> print #words
dtm(M) |> print #frequency
【実行結果】
["be", "do", "not", "or", "to"]
 2  ⋅  1  1  2
 ⋅  2  1  1  2

TF-IDF

TF-IFDは, tf_idf関数を使えばOKです.

【TF-IDF】
#represent document as a matrix
M = DocumentTermMatrix(crps)
M.terms |> print #words
tf_idf(M) #TF-IDF
【実行結果】
["be", "do", "not", "or", "to"]
2×5 SparseArrays.SparseMatrixCSC{Float64, Int64} with 8 stored entries:
 0.231049   ⋅        0.0  0.0  0.0
  ⋅        0.231049  0.0  0.0  0.0
参考文献

      [1]TextAnalysis.jlの公式ドキュメント
      [2]中山光樹, 機械学習・深層学習による自然言語処理入門 scikit-learnとTensorFlowを使った実践プログラミング, マイナビ出版, 2020