1
1

More than 1 year has passed since last update.

Pythonでインドネシア語検定B級の対策を練る

Last updated at Posted at 2021-10-25

調べようと思った背景

 インドネシア駐在歴4年(今は日本)。
 帰任後、腕試しでインドネシア語検定B級を受験し、2018年7月不合格。
 過去問を数年分購入し、リベンジも2020年1月不合格。さらに過去問を遡り購入し、
 2020年7月再度受験、不合格。何回落としてくれんだ!!!。
 点数は、いつもギリギリである意味で呪われているレベル(笑)。
Inked第53回不合格_LI.jpg
Inked第56回不合格_LI.jpg
Inked第57回不合格_LI.jpg
 ・・・いつも通り過去問をただ漫然と繰返し解くのはもう飽きた。むしろそのやり方が正しいのか?。勉強時間が多く取れるわけでもないので、効率的に勉強して今度こそ4度目のリベンジを果たしたい。
 ・・・Pythonならできるのでは???、Python何とかしてくれ!!!!

(補足)インドネシア語検定とは : http://www.i-kentei.com/

日本インドネシア語検定協会が開催する語学試験で毎年1月と7回の年に2回開催される。
ランクは難しい順番から特A級、A級、B級、C級、D級、E級となっている。

筆者のPython歴

2021年8月からPythonを始めて3カ月目に突入。日々わからない事だらけで頭痛が絶えない。以前はプログラム歴なし。

実際に調べたこと

  1. インドネシア語検定の合格率と難易度把握
  2. 過去問の収集とPDF/OCR化
  3. 過去問からB級のみを抽出
  4. B級のテキスト化とノイズ言語の削除
  5. テキストから単語化、StopWords使用 ・・・以上までがデータ収集と前処理
  6. 各年度の語彙量の統計値と合格率の相関性
  7. 頻出単語の有無確認         ・・・以上までがデータ基礎解析
  8. 単語特徴分類モデルの準備(インドネシア版Word2Vec/k-means手法)
  9. 単語学習モデルの可視化(PCA:主成分分析)
  10. 単語特徴分類後の過去問出題傾向を再考察
  11. 過去問の類似単語抽出によるテスト対策・・・以上までが機械学習モデルの活用
  12. まとめ
  13. 感想

開発環境

 Jupyter lab
 Python3.9.6
 Windows

1.合格率と難易度把握

インドネシア語検定の合格率は以下のサイトに掲載されている。
http://www.i-kentei.com/peserta/data/index.html
ただ、直近5年分しか掲載されていないようなので、5年以上遡るには、ウェブアーカイブサイトが便利!!!
https://web.archive.org/
このウェブサイトから2008年から2018年までのA級からE級の合格率データを収集した。
なお集計にはExcelを使用。
image.png
早速、合格率の推移を時系列でグラフ化した。
Excelで同じ事できる(むしろそっちの方が早い?)が勉強のためにわざとPythonを使用しましたよ。

インドネシア語検定の合格率推移
#ライブラリのインポート
import pandas as pd
import matplotlib.pyplot as plt 
#インドネシア語検定合格率データの読込み(上表画像をExcel化すれば同じ事ができます)
df1=pd.read_excel("インドネシア語検定合格率推移.xlsx",skiprows=1,index_col=0)
#特A級はNANデータがあり、obeject型となっているのでfloat型に変更
df1["特A級合格率"]=df1["特A級合格率"].astype("float")
#小数1ケタにデータを丸める
df1.round(1)
#インドネシア語検定合格率をグラフ化する
plt.plot(df1["A級合格率"],linestyle=":", color="b", marker="o", markerfacecolor="w",label="A_rank")
plt.plot(df1["B級合格率"],linestyle="-", color="r", marker="o", markerfacecolor="r",label="B_rank")
plt.plot(df1["C級合格率"],linestyle=":", color="g", marker="o", markerfacecolor="w",label="C_rank")
plt.plot(df1["D級合格率"],linestyle=":", color="c", marker="o", markerfacecolor="w",label="D_rank")
plt.plot(df1["E級合格率"],linestyle=":", color="m", marker="o", markerfacecolor="w",label="E_rank")
plt.xticks(df1.index,["2008/1","2008/7","2009/1","2009/7","2010/1","2010/7",
            "2011/1","2011/7","2012/1","2012/7","2013/1","2013/7",
            "2014/1","2014/7","2015/1","2015/7","2016/1","2016/7",
            "2017/1","2017/1","2018/1","2018/7","2019/1","2019/7",
            "2020/1","2020/7","2021/1","2021/7"],rotation=90)
plt.title("Indonesian TEST")
plt.xlabel("year/month")
plt.ylabel("pass ratio(%)")
plt.legend(loc = "upper right")
plt.show()
#インドネシア語検定B級の情報だけに絞る
df1_B=df1.loc[:,["年度","B級合格率"]]
print(df1_B.describe().round(1))

image.png

【考察】

  • A級とB級は圧倒的に難しいがC級とD級も2019年以降から合格率が下がり傾向にあり。
  • B級の合格率は横ばいで安定傾向。合格率は約8%(5~10%)。受験数は約100人なので、毎回8名程度しか合格者が出ない狭き門であることが確認できた。

2. 過去問の収集とPDF/OCR化

  • 過去問は紙媒体で販売されている(下画像イメージ)。
  • 年2回分のA級とB級がパーケージ化(ホチキス止め)されて販売されている。
  • 試験対策で購入していた2008年~2018年までの過去問を利用した。
  • それらを全てPDF化/OCR化(英語読取り)した。
  • PDF化/OCRは近所の自炊スキャン代行にお願いした。 image.png

3. 過去問からB級のみを抽出

PDF化した過去問にはA級の試験問題が混ざっているのでB級に特化した対策ができない。
PDF化したものを、B級の試験ページのみを指定して各年の1月、7月に分割し、2008年~2018年までの計22回分をB級のPDFに分割した。
以下はそのコードになる。

2008年~2018年までのA/B級試験をB級のみを分割PDF化
#必要ライブラリのインポート
import PyPDF2
#各年度1月と7月の年2回試験があり、2008~2018年までB級の1月と7月をPDFに分割
#B1:1月度試験、B7:7月度試験
indo_year_pages = {"2008":[{"B1":[0, 8]}, {"B7":[23, 32]}],
                   "2009":[{"B1":[0, 8]}, {"B7":[22, 30]}],
                   "2010":[{"B1":[0, 8]}, {"B7":[25, 33]}],
                   "2011":[{"B1":[0, 9]}, {"B7":[22, 31]}],
                   "2012":[{"B1":[0, 9]}, {"B7":[21, 29]}],
                   "2013":[{"B1":[0, 9]}, {"B7":[21, 30]}],
                   "2014":[{"B1":[0, 9]}, {"B7":[22, 31]}],
                   "2015":[{"B1":[0, 9]}, {"B7":[20, 29]}],
                   "2016":[{"B1":[0, 9]}, {"B7":[21, 29]}],
                   "2017":[{"B1":[0, 9]}, {"B7":[22, 31]}],
                   "2018":[{"B1":[0, 9]}, {"B7":[22, 31]}]}            
for year, d in indo_year_pages.items():
    for p_dict in d: # ここでforループでlistを展開をする
        for month, pages in p_dict.items():
            merger = PyPDF2.PdfFileMerger()
            merger.append(f"./Indo{year}_AB.pdf", pages=PyPDF2.pagerange.PageRange(f"{pages[0]}:{pages[1]}")) 
            merger.write(f"./Indo{year}_{month}.pdf")# ここでdictのkey, valueを取得するためにもう一度forループで展開する
            merger.close

4. B級のテキスト化とノイズ言語の削除

PDFファイルのテキスト化を実施する。
参考URL: https://webmaking.rei-farms.jp/webmaking/python/5931/

B級PDFのテキスト化(文字起こし)
#pdfファイルのテキスト化
def open_pdf_text(file_name):
    text = ''
    with open(file_name, "rb") as f:
        reader = PyPDF2.PdfFileReader(f)
        for page_no in range(reader.numPages):
            page = reader.getPage(page_no)
            if page.extractText():
                text += page.extractText()
    return text
#各年度のテキスト化を実施する
Indo2008_B1_text=open_pdf_text("./Indo2008_B1.pdf")
Indo2008_B7_text=open_pdf_text("./Indo2008_B7.pdf")
Indo2009_B1_text=open_pdf_text("./Indo2009_B1.pdf")
Indo2009_B7_text=open_pdf_text("./Indo2009_B7.pdf")
Indo2010_B1_text=open_pdf_text("./Indo2010_B1.pdf")
Indo2010_B7_text=open_pdf_text("./Indo2010_B7.pdf")
Indo2011_B1_text=open_pdf_text("./Indo2011_B1.pdf")
Indo2011_B7_text=open_pdf_text("./Indo2011_B7.pdf")
Indo2012_B1_text=open_pdf_text("./Indo2012_B1.pdf")
Indo2012_B7_text=open_pdf_text("./Indo2012_B7.pdf")
Indo2013_B1_text=open_pdf_text("./Indo2013_B1.pdf")
Indo2013_B7_text=open_pdf_text("./Indo2013_B7.pdf")
Indo2014_B1_text=open_pdf_text("./Indo2014_B1.pdf")
Indo2014_B7_text=open_pdf_text("./Indo2014_B7.pdf")
Indo2015_B1_text=open_pdf_text("./Indo2015_B1.pdf")
Indo2015_B7_text=open_pdf_text("./Indo2015_B7.pdf")
Indo2016_B1_text=open_pdf_text("./Indo2016_B1.pdf")
Indo2016_B7_text=open_pdf_text("./Indo2016_B7.pdf")
Indo2017_B1_text=open_pdf_text("./Indo2017_B1.pdf")
Indo2017_B7_text=open_pdf_text("./Indo2017_B7.pdf")
Indo2018_B1_text=open_pdf_text("./Indo2018_B1.pdf")
Indo2018_B7_text=open_pdf_text("./Indo2018_B7.pdf")

--以下2008年1月B級の文字切取り。--
インドネシア語もアルファベット表記のため、テキスト化は問題なく実行できていそう。
ただ、大文字小文字の混在、数値や%-,/などの記号類は解析の外乱となるため、前処理が
必要であることを実感。
image.png

★ノイズ除去コードの作成★
ここで大苦戦した。
参考URL: https://murashun.jp/article/programming/regular-expression.html
この呪文みたいなコードを理解するのにAidemyの方に随分とサポートいただいた。
ノイズ関数として、①重複を削除、②大文字から全て小文字変換、③3文字以下の要素を削除、④記号のみの要素削除、⑤数字のみの要素削除を定義した。
以下コード

ノイズ言語の削除関数を定義
#正規表現ライブラリのインポート
import re
#ノイズ言語削除の関数定義
def no_stopwords(word_list, is_lower=True, min_character=3, without_only_symbol=True, without_only_number=True, is_unique=True):
    word_list_tmp = word_list    
    if is_unique:  # is_unique=False の場合、重複した単語を除外しない 
        word_list_tmp = list(set(word_list_tmp))
        #print("重複を削除")        
    if is_lower:  # is_lower=True の場合、全て小文字に変換する
        word_list_tmp = list(map(lambda x: x.lower(), word_list_tmp))
        #print("小文字に変換")        
    if min_character:
        word_list_tmp = [word for word in word_list_tmp if not re.search('^.{1,' + str(min_character) + '}$', word)]
        #print(min_character, "文字以下の要素は削除")        
    if without_only_symbol:
        word_list_tmp = [word for word in word_list_tmp
                       if not re.search('^[!"#$%&\'\\\\()*+,-./:;<=>?@[\\]^_`{|}~]+$', word)]
        #print("記号のみの要素を削除")        
    if without_only_number:
        word_list_tmp = [word for word in word_list_tmp if not re.search('^[0-9.\-]+$', word)]
        #print("数値のみの要素を削除")        
    return word_list_tmp

5. テキストから単語化、StopWords適用

  • テキストから単語の抽出には、Tokenizerを利用した。
  • インドネシア語にもStopWords(自然言語データの処理の前後に除外されるストップリスト内の任意の単語)があり、758単語登録されていた。
  • 中身を確認するとada(is), karena(because)などの平易な単語だけでなく、meningatkan(remind), masalah(problem)などの、これ省くの?レベルの単語まで含まれていたので、StopWords_OFF(未使用)/ON(使用)の両方で処理することにした。

以下コード

トークナイズ化:StopWord_OFF/ON
#ライブラリのインポート
import nltk
from nltk.tokenize import word_tokenize
#各年度の実行_StopWords不使用OFF_Version
Indo2008_B1_text_off_stopwords=no_stopwords(word_tokenize(Indo2008_B1_text))
Indo2008_B7_text_off_stopwords=no_stopwords(word_tokenize(Indo2008_B7_text))
Indo2009_B1_text_off_stopwords=no_stopwords(word_tokenize(Indo2009_B1_text))
Indo2009_B7_text_off_stopwords=no_stopwords(word_tokenize(Indo2009_B7_text))
Indo2010_B1_text_off_stopwords=no_stopwords(word_tokenize(Indo2010_B1_text))
Indo2010_B7_text_off_stopwords=no_stopwords(word_tokenize(Indo2010_B7_text))
Indo2011_B1_text_off_stopwords=no_stopwords(word_tokenize(Indo2011_B1_text))
Indo2011_B7_text_off_stopwords=no_stopwords(word_tokenize(Indo2011_B7_text))
Indo2012_B1_text_off_stopwords=no_stopwords(word_tokenize(Indo2012_B1_text))
Indo2012_B7_text_off_stopwords=no_stopwords(word_tokenize(Indo2012_B7_text))
Indo2013_B1_text_off_stopwords=no_stopwords(word_tokenize(Indo2013_B1_text))
Indo2013_B7_text_off_stopwords=no_stopwords(word_tokenize(Indo2013_B7_text))
Indo2014_B1_text_off_stopwords=no_stopwords(word_tokenize(Indo2014_B1_text))
Indo2014_B7_text_off_stopwords=no_stopwords(word_tokenize(Indo2014_B7_text))
Indo2015_B1_text_off_stopwords=no_stopwords(word_tokenize(Indo2015_B1_text))
Indo2015_B7_text_off_stopwords=no_stopwords(word_tokenize(Indo2015_B7_text))
Indo2016_B1_text_off_stopwords=no_stopwords(word_tokenize(Indo2016_B1_text))
Indo2016_B7_text_off_stopwords=no_stopwords(word_tokenize(Indo2016_B7_text))
Indo2017_B1_text_off_stopwords=no_stopwords(word_tokenize(Indo2017_B1_text))
Indo2017_B7_text_off_stopwords=no_stopwords(word_tokenize(Indo2017_B7_text))
Indo2018_B1_text_off_stopwords=no_stopwords(word_tokenize(Indo2018_B1_text))
Indo2018_B7_text_off_stopwords=no_stopwords(word_tokenize(Indo2018_B7_text))

# StopWordsライブラリのインポート
from nltk.corpus import stopwords
#各年度の実行_StopWords使用ON_Version
def remove_stopwords(word_list):
    stop_words = set(stopwords.words("indonesian"))
    filtered_text = [word for word in word_list if word not in stop_words]
    return filtered_text
Indo2008_B1_text_on_stopwords=remove_stopwords(Indo2008_B1_text_off_stopwords)
Indo2008_B7_text_on_stopwords=remove_stopwords(Indo2008_B7_text_off_stopwords)
Indo2009_B1_text_on_stopwords=remove_stopwords(Indo2009_B1_text_off_stopwords)
Indo2009_B7_text_on_stopwords=remove_stopwords(Indo2009_B7_text_off_stopwords)
Indo2010_B1_text_on_stopwords=remove_stopwords(Indo2010_B1_text_off_stopwords)
Indo2010_B7_text_on_stopwords=remove_stopwords(Indo2010_B7_text_off_stopwords)
Indo2011_B1_text_on_stopwords=remove_stopwords(Indo2011_B1_text_off_stopwords)
Indo2011_B7_text_on_stopwords=remove_stopwords(Indo2011_B7_text_off_stopwords)
Indo2012_B1_text_on_stopwords=remove_stopwords(Indo2012_B1_text_off_stopwords)
Indo2012_B7_text_on_stopwords=remove_stopwords(Indo2012_B7_text_off_stopwords)
Indo2013_B1_text_on_stopwords=remove_stopwords(Indo2013_B1_text_off_stopwords)
Indo2013_B7_text_on_stopwords=remove_stopwords(Indo2013_B7_text_off_stopwords)
Indo2014_B1_text_on_stopwords=remove_stopwords(Indo2014_B1_text_off_stopwords)
Indo2014_B7_text_on_stopwords=remove_stopwords(Indo2014_B7_text_off_stopwords)
Indo2015_B1_text_on_stopwords=remove_stopwords(Indo2015_B1_text_off_stopwords)
Indo2015_B7_text_on_stopwords=remove_stopwords(Indo2015_B7_text_off_stopwords)
Indo2016_B1_text_on_stopwords=remove_stopwords(Indo2016_B1_text_off_stopwords)
Indo2016_B7_text_on_stopwords=remove_stopwords(Indo2016_B7_text_off_stopwords)
Indo2017_B1_text_on_stopwords=remove_stopwords(Indo2017_B1_text_off_stopwords)
Indo2017_B7_text_on_stopwords=remove_stopwords(Indo2017_B7_text_off_stopwords)
Indo2018_B1_text_on_stopwords=remove_stopwords(Indo2018_B1_text_off_stopwords)
Indo2018_B7_text_on_stopwords=remove_stopwords(Indo2018_B7_text_off_stopwords)

6. 各年度の語彙量の統計値と合格率の相関性

各年度から抽出した単語数をstopwordの使用有無でデータフレームとして整理し、
B級合格率と単語数に関係性があるか、散布図でプロットした。

以下コード

各年度の単語数と合格率の関係性を可視化
#必要ライブラリのインポート
import pandas as pd
#Stop_OFFの各年度の語彙量抽出とデータフレーム化
df2_off_stop=pd.DataFrame([len(Indo2008_B1_text_off_stopwords),
                          len(Indo2008_B7_text_off_stopwords),
                          len(Indo2009_B1_text_off_stopwords),
                          len(Indo2009_B7_text_off_stopwords),
                          len(Indo2010_B1_text_off_stopwords),
                          len(Indo2010_B7_text_off_stopwords),
                          len(Indo2011_B1_text_off_stopwords),
                          len(Indo2011_B7_text_off_stopwords),
                          len(Indo2012_B1_text_off_stopwords),
                          len(Indo2012_B7_text_off_stopwords),
                          len(Indo2013_B1_text_off_stopwords),
                          len(Indo2013_B7_text_off_stopwords),
                          len(Indo2014_B1_text_off_stopwords),
                          len(Indo2014_B7_text_off_stopwords),
                          len(Indo2015_B1_text_off_stopwords),
                          len(Indo2015_B7_text_off_stopwords),
                          len(Indo2016_B1_text_off_stopwords),
                          len(Indo2016_B7_text_off_stopwords),
                          len(Indo2017_B1_text_off_stopwords),
                          len(Indo2017_B7_text_off_stopwords),
                          len(Indo2018_B1_text_off_stopwords),
                          len(Indo2018_B7_text_off_stopwords)],columns={"B_words_off"})  
#Stop_ONの各年度の語彙量抽出とデータフレーム化
df2_on_stop=pd.DataFrame([len(Indo2008_B1_text_on_stopwords),
                          len(Indo2008_B7_text_on_stopwords),
                          len(Indo2009_B1_text_on_stopwords),
                          len(Indo2009_B7_text_on_stopwords),
                          len(Indo2010_B1_text_on_stopwords),
                          len(Indo2010_B7_text_on_stopwords),
                          len(Indo2011_B1_text_on_stopwords),
                          len(Indo2011_B7_text_on_stopwords),
                          len(Indo2012_B1_text_on_stopwords),
                          len(Indo2012_B7_text_on_stopwords),
                          len(Indo2013_B1_text_on_stopwords),
                          len(Indo2013_B7_text_on_stopwords),
                          len(Indo2014_B1_text_on_stopwords),
                          len(Indo2014_B7_text_on_stopwords),
                          len(Indo2015_B1_text_on_stopwords),
                          len(Indo2015_B7_text_on_stopwords),
                          len(Indo2016_B1_text_on_stopwords),
                          len(Indo2016_B7_text_on_stopwords),
                          len(Indo2017_B1_text_on_stopwords),
                          len(Indo2017_B7_text_on_stopwords),
                          len(Indo2018_B1_text_on_stopwords),
                          len(Indo2018_B7_text_on_stopwords)],columns={"B_words_on"}) 

#df1はインドネシア語検定合格率データフレームで使用
df2=pd.concat([df2_off_stop, df2_on_stop],axis=1)
#df1から2019年以降のデータを削除する(過去問は2018年まで)
df1_cut=df1.drop(index=["第54回","第55回","第56回","第57回","第58回","第59回"])
#df2にdf1のIndex付与
df2.index=df1_cut.index
#データフレーム同士を結合する
df3=df1_cut.join(df2)
df_B=df3.loc[:,["年度","B級合格率","B_words_off","B_words_on"]]
#インドネシア語検定B級の合格率と単語数の相関性を調べる
plt.scatter(df_B["B_words_off"],df_B["B級合格率"],label="stopwords_OFF")
plt.scatter(df3["B_words_on"],df_B["B級合格率"],label="Stopwords_ON")
plt.xlabel("number of words(numbers)")
plt.ylabel("pass ratio(%)")
plt.legend()
plt.show()
print(df_B.describe().round(1))

image.png
ぱっと見で相関がなさそう。。。一応無相関の検定を実施した。

無相関の検定
#ピアソン相関係数の検定
#必要ライブラリのインポート
from scipy.stats import pearsonr
result_on = pearsonr(df_B["B_words_on"],df_B["B級合格率"])
result_off = pearsonr(df_B["B_words_off"],df_B["B級合格率"])
print("相関係数on:", result_on[0])
print("p値on:", result_on[1])
print("相関係数off:", result_off[0])
print("p値off:", result_off[1])
#相関係数on: 0.052370283374945745
#p値on: 0.8169587241696906
#相関係数off: 0.06855776767226002
#p値off: 0.761774930758141

予想通り、統計的にも相関がないという結果となった。

【考察】

  • 予想に反して単語数と合格率には全く相関性が見られなかった。
  • 因みに文字数除外"min_character=3"を2~8文字まで変更したが傾向変わらなかった。
  • 要因として単語数は毎年800~1000語(stopwords使用)に絞られており、この範囲での単語   数の違いでは明確な難易度差が見えなかった可能性がある。
  • また、出題内容や受験生のレベルなどの変動要因の方がもしかしたら大きい?
  • ただ、シンプルに語彙量を増やせば合格に近づくというわけではなさそう。

7. 頻出単語の有無確認

  • 合格率と単語数に相関性が見られないことから、年度ごとに共通の単語がどの程度存在するかを調べた。
  • 2008年~2018年の過去問を全て集約すれば、共通の単語が増える(頻度が2回以上になる)≒過去に出題された単語がまた出ている≒過去問をやる事が効果的な対策と判断できると考えた。
  • また頻度ごとに分類しながら、単語リストを作成を試みた。

各回ごとに重複を除いた単語数同士をリストで結合(Setだと結合時に重複がカットされるので注意)し、2008年~2018年(計22回)の単語をリスト化した。
さらに、頻出度を以下のように定義して、解析した。
 ・extrme_words:計11~22回・・・「毎年必ず出る単語」
 ・high_words :計5~10回 ・・・「2年に1回出る単語」
 ・medium_words: 計3~4回 ・・・「3年~4年に1回出る単語」
 ・low_words  :計2回   ・・・「5年に1回出る単語」
 ・neglibible_words:計1回 ・・・「今後出ないであろう単語」
以下コード

2008年~2018年度の頻出度の可視化
#単語リストの作成
#各年度から一意の単語を抽出し、さらに年度ごとにリスト結合することで、年度間の共通単語を拾う。
#stopwords不使用OFF
Indo20082018_off_list=list(Indo2008_B1_text_off_stopwords)+list(Indo2008_B7_text_off_stopwords)+\
list(Indo2009_B1_text_off_stopwords)+list(Indo2009_B7_text_off_stopwords)+\
list(Indo2010_B1_text_off_stopwords)+list(Indo2010_B7_text_off_stopwords)+\
list(Indo2011_B1_text_off_stopwords)+list(Indo2011_B7_text_off_stopwords)+\
list(Indo2012_B1_text_off_stopwords)+list(Indo2012_B7_text_off_stopwords)+\
list(Indo2013_B1_text_off_stopwords)+list(Indo2013_B7_text_off_stopwords)+\
list(Indo2014_B1_text_off_stopwords)+list(Indo2014_B7_text_off_stopwords)+\
list(Indo2015_B1_text_off_stopwords)+list(Indo2015_B7_text_off_stopwords)+\
list(Indo2016_B1_text_off_stopwords)+list(Indo2016_B7_text_off_stopwords)+\
list(Indo2017_B1_text_off_stopwords)+list(Indo2017_B7_text_off_stopwords)+\
list(Indo2018_B1_text_off_stopwords)+list(Indo2018_B7_text_off_stopwords)
print(len(Indo20082018_off_list))

#stopwords不使用ON
Indo20082018_on_list=list(Indo2008_B1_text_on_stopwords)+list(Indo2008_B7_text_on_stopwords)+\
list(Indo2009_B1_text_on_stopwords)+list(Indo2009_B7_text_on_stopwords)+\
list(Indo2010_B1_text_on_stopwords)+list(Indo2010_B7_text_on_stopwords)+\
list(Indo2011_B1_text_on_stopwords)+list(Indo2011_B7_text_on_stopwords)+\
list(Indo2012_B1_text_on_stopwords)+list(Indo2012_B7_text_on_stopwords)+\
list(Indo2013_B1_text_on_stopwords)+list(Indo2013_B7_text_on_stopwords)+\
list(Indo2014_B1_text_on_stopwords)+list(Indo2014_B7_text_on_stopwords)+\
list(Indo2015_B1_text_on_stopwords)+list(Indo2015_B7_text_on_stopwords)+\
list(Indo2016_B1_text_on_stopwords)+list(Indo2016_B7_text_on_stopwords)+\
list(Indo2017_B1_text_on_stopwords)+list(Indo2017_B7_text_on_stopwords)+\
list(Indo2018_B1_text_on_stopwords)+list(Indo2018_B7_text_on_stopwords)
print(len(Indo20082018_on_list))

#ライブラリのインポート
import collections
#Stopwords不使用OFF
c_off = collections.Counter(Indo20082018_off_list)
#毎年必ず出る単語
extrme_words_off=[i[0] for i in c_off.items() if 11<= i[1] <= 22]
#2年以内に1回必ず出る単語
high_words_off=[i[0] for i in c_off.items() if 5<= i[1] <= 10]
#3年以内に1回出る単語
medium_words_off=[i[0] for i in c_off.items() if 3<= i[1] <= 4]
#5年以内に1回出る単語
low_words_off=[i[0] for i in c_off.items() if i[1]==2]
#1回しか出ないレア単語
neglibible_words_off=[i[0] for i in c_off.items() if i[1]==1]
#neglibible_wordsは約5400単語あり、そのうち約20%は誤検出が確認できたので-1080とした。
c_off_freq=[len(extrme_words_off),len(high_words_off),len(medium_words_off),
            len(low_words_off),len(neglibible_words_off)-1080]
print(sum(c_off_freq))
#Stopwords使用ON
c_on = collections.Counter(Indo20082018_on_list)
#毎年必ず出る単語
extrme_words_on=[i[0] for i in c_on.items() if 11<= i[1] <= 22]
#2年以内に1回必ず出る単語
high_words_on=[i[0] for i in c_on.items() if 5<= i[1] <= 10]
#3年以内に1回出る単語
medium_words_on=[i[0] for i in c_off.items() if 3<= i[1] <= 4]
#5年以内に1回出る単語
low_words_on=[i[0] for i in c_on.items() if i[1]==2]
#1回しか出ないレア単語
neglibible_words_on=[i[0] for i in c_on.items() if i[1]==1]
#neglibible_wordsは約5400単語あり、そのうち約20%は誤検出が確認できたので-1080とした。
c_on_freq=[len(extrme_words_on),len(high_words_on),len(medium_words_on),
            len(low_words_on),len(neglibible_words_on)-1080]
print(sum(c_on_freq))

#円グラフ書く
label = ["Extreme", "High", "Medium", "Low", "neglibible"]
#円グラフ (外側)
plt.pie(c_on_freq, labels=label, counterclock=False, startangle=90,autopct='%1.1f%%',labeldistance=1.0)
#円グラフ (内側, 半径 70% で描画)
plt.pie(c_off_freq, counterclock=False, startangle=90, radius=0.7)
# 中心 (0,0) に 40% の大きさで円を描画
centre_circle = plt.Circle((0,0),0.4,color='black', fc='white',linewidth=1.25)
fig = plt.gcf()
fig.gca().add_artist(centre_circle)
plt.show()

image.png

【考察】

  • 驚くべきことに、毎年55%程度新しい単語が出ている事がわかった。
  • 各年度の単語数は約900単語(上述)より、受験する度に約500の新単語と格闘する事になる。しかもそれらは、次回は出てこない可能性が極めて高い。
  • 以上から、過去問の語彙を集中的に覚えることは効果的な対策とは言えない可能性大。

・・・そもそも効率的な過去問勉強という前提が崩れるような結果となり、単語リストを作る意味すら分からなくなってきたが、一応printだけ実施しておく。

以下コード

2008年~2018年度の頻出度の単語一覧
#stopword使用限定
print("---Extreme---")
print(extrme_words_on)
print("---High---")
print(high_words_on)
print("---Medium---")
print(medium_words_on)
print("---Low---")
print(low_words_on)
print("---neglibible---")
print(neglibible_words_on)

Low wordsの切取り図。
全てのテキストデータが欲しい方が要ればコメントください!!。
image.png

8. 単語特徴分類モデルの準備(インドネシア版Word2Vec/k-means手法)

毎年半分以上の新単語が出ていることがわかったので、個々の単語にフォーカスを当てるのは有効ではない。そこで、単語から単語群に対象を広げる、つまり単語の特徴分類を実施し、分類分けした単語からどういう特徴の単語群があるかを調べて対策の指針とした。

単語の分類、いわゆるクラスタリングを実施するのは、単語を数値に置き換えてベクトル的な方向表現をする必要があるので、分散表現によって単語の「意味」を数学的に表現する、
Word2Vecを使用した。
インドネシア語のWord2Vecは以下のkaggleサイトにあったので、ダウンロードした。
https://www.kaggle.com/ilhamfp31/word2vec-100-indonesian
以下、コード

インドネシア語版のWor2Vecの導入
#必要ライブラリのインポート
import gensim
from gensim.models import word2vec
DIR_DATA_MISC = "."
path = '{}/idwiki_word2vec_100.model'.format(DIR_DATA_MISC)
id_w2v = gensim.models.word2vec.Word2Vec.load(path)

次に、K-means手法による単語分類のモデルを利用した。
★注意★
ここで、実際にモデルを動かしたところ、Word2Vecに登録されていない単語があったり、テキスト化した際の誤変換テキスト化があったり(例えば、本当はmなのにrnとしてテキスト化されているものがあった)でまた中断してしまった。 以下エラーメッセージ例
image.png
この単語エラーを解消するのにはtry-exceptの例外処理があることをAidemyの方に教えてもらい、次に進むことができた。

以下コード

2008年~2018年の過去問からK-meansの学習モデル導入
#2008年-2018年までの単語重複削除, stopword OFFで検証
Indo20082018_off_set=list(set(Indo20082018_off_list))
#必要ライブラリのインポート
from sklearn.cluster import KMeans
#単語ベクトル空リスト
vector_list=[]
#Word2Vecに存在する単語、存在しない単語をTrue/Falseラベル付け
exist_bool=[]
#リストに格納, クラスター数は10とした
for word in Indo20082018_off_set:
    try:
        vector_list.append(id_w2v.wv[word])
        exist_bool.append(True)
    except:
        exist_bool.append(False)
kmeans_model = KMeans(n_clusters=10, verbose=1, random_state=42, n_jobs=-1)
kmeans_model.fit(vector_list)

9.単語学習モデルの可視化(PCA:主成分分析)

主成分析に入る前に、groupby関数で簡易的に分類された単語を観察。
以下コード

分類された単語のgroupby
#データフレーム化
df=pd.DataFrame(Indo20082018_off_set, columns=["word"])
df["exist_bool"]=exist_bool
df=df[df["exist_bool"]]
df["cluster"]=kmeans_model.labels_
#kmeans_model.labels_で分類ラベルのarrayが格納されている。
for group_name, df_group in df.groupby("cluster"):
    print(df_group)

以下は、可視化グラフイメージ
image.png

結構、特徴的に分類されていることがわかって安心した。例えばCatergory0は場所に
関する言葉並んでいたり、Category2は対比ができる言葉(enak:美味しい,marah:怒るなど)であったり、Catergory4と5はそれぞれ自動詞と他動詞で分けられていたり、となっていることが確認できた。

次に、分類したカテゴリの位置関係をざっくりと把握するために主成分分析で2次元上に
可視化した。

以下コード

主成分分析による可視化
#必要ライブラリのインポート
from sklearn.decomposition import PCA
pca= PCA(n_components=2)
vector_2d=pca.fit_transform(vector_list)
import numpy as np
np.random.seed(0)
cmap=plt.get_cmap("tab20")
fig=plt.figure(figsize=(20,20))
plt.rcParams["font.size"]=12
for i in range(vector_2d.shape[0]):#shape[0]データの長さを取ってくる
    #全部表示するとごちゃごちゃしたので、1/10で可視化
    if np.random.rand()>=0.1:
        continue        
    plt.plot(vector_2d[i][0],vector_2d[i][1],ms=12,marker=".",c=cmap(kmeans_model.labels_[i]))
    plt.annotate(df.iloc[i,0],(vector_2d[i][0],vector_2d[i][1]))   

以下可視化グラフイメージ
image.png

他動詞(me-)と自動詞(di-)が近い位置関係にあり、これは能動態⇔受動態の関係であったりするので、近しい位置にプロットされていると考えれば、それなりに妥当な表示になっていると解釈できる。

10.単語特徴分類後の過去問出題傾向を再考察

上述で単語ごとの傾向解析をしたが、今度はK-means手法で分類した10カテゴリーの中でテスト回ごとに特徴的な傾向があるかを調査した。

単語カテゴリーによる過去問出題傾向分析
#関数定義
def create_word_vec(word_list):
    vector_list=[]
    exist_bool=[]
    for word in word_list:
        try:
            vector_list.append(id_w2v.wv[word])
            exist_bool.append(True)
        except:
            exist_bool.append(False)
    return vector_list, exist_bool
Indo_text_off_stopwords_list=["2008_B1","2008_B7","2009_B1","2009_B7","2010_B1","2010_B7",
                              "2011_B1","2011_B7","2012_B1","2012_B7","2013_B1","2013_B7",
                              "2014_B1","2014_B7","2015_B1","2015_B7","2016_B1","2016_B7",
                              "2017_B1","2017_B7","2018_B1","2018_B7"]
df_cluster_count=pd.DataFrame(columns=[list(range(10))])
for year_month in Indo_text_off_stopwords_list:
    exec("target_list=Indo{}_text_off_stopwords".format(year_month))   
    vector_list,exist_bool= create_word_vec(target_list)
    predict_cluster=kmeans_model.predict(vector_list)
    df_cluster_count.loc[year_month]=pd.Series(predict_cluster).value_counts().sort_index().values
df_cluster_count.columns=list(range(10))
for i in range(10):
    plt.plot(df_cluster_count[i],label='{}'.format(i))
plt.title("Indonesian TEST")
plt.xlabel("year/month")
plt.ylabel("labels_count")
plt.legend(bbox_to_anchor=(1,1))
plt.xticks(rotation=90)
plt.show()

以下、可視化グラフ。
image.png
このグラフからも、テスト回や年度によって典型的な傾向があるというわけではなさそう。
ただ、Catergory6(日常表現?),Catergory9(その他?),Catergory3(経済用語?)が頻度良く出題されているおり、Catergory9(その他?)の単語数が減少傾向にも見えることから、
ひょっとすると、出題テーマが具体化されている可能性がある。

11.過去問の類似単語抽出によるテスト対策

「7. 頻出単語の有無確認」に記載の通り、テストの度に約55%の新単語が出ている可能性があるので、過去問の単語をやみくもに暗記する事は効果的なテスト対策とは言えない。
そこで、10カテゴリ―に分類した平均ベクトルを算出し、この平均ベクトルに類似する単語をインドネシア版Word2Vecから抽出した。これにより、過去問に依らず、カテゴリー毎に親和性を高い単語を抽出することができるので、効率的なテスト対策となると考える。
ここでは、試しに各カテゴリーのトップ10をデータフレーム化することにした。

単語カテゴリーごとの新単語リスト作成
#各クラスターごとの平均ベクトルを計算する
vector_mean_dic={}
for group_name, df_group in df.groupby("cluster"):
    vector_list=[]
    for word in df_group["word"]:
        try:
            vector_list.append(id_w2v.wv[word])
        except:
            continue
    vector_2d=np.stack(vector_list)
    vector_mean=np.average(vector_2d,axis=0)
    vector_mean_dic[group_name]=vector_mean
#print(vector_2d.shape)
#print(vector_mean.shape)
#毎単語リスト作成
df_myworddic=pd.DataFrame()
for cluster in range(10):
    similar_result=id_w2v.wv.similar_by_vector(vector_mean_dic[cluster],topn=10)
    df_myworddic["category"+str(cluster)]=[tp[0] for tp in similar_result]
df_myworddic

以下、可視化グラフイメージ
image.png

上記単語リストが、2008年-2018年の過去問単語から今後、カテゴリー毎に出題される可能性が最も高い上位10単語といえる。
これを、上位毎に覚えていくことがインドネシア語検定B級合格の最短ルートになるか?
結果は、2022年1月試験にて立証していきたい。

12. まとめ

  • Pythonのおかげでインドネシア語検定B級の過去問の単語に関する解析を効率的に行うことができた。
  • 予想外であったが、インドネシア語検定B級は毎年新しい単語が半分以上出ており、過去問の単語を闇雲に覚えても合格率が上がらない可能性が高いことがわかった。
  • k-means手法による単語の分類分けで、どのような単語群が良く出題されているかといった特徴を掴むことができた。実際には日常表現+経済用語などが頻出されていると推定された。
  • 類似度計算によって、2008-2018年度出題単語と親和性が高い単語群をリストアップすることができた。これにより、覚えるべき単語の優先順位が定まった。

13. 感想

  • 言語抽出した際に発生する各種ノイズを除去する効果的なコード構成が解析を進めるうえで第一関門となると感じた。
  • Word2Vec/k-meansは活用方法次第で、語学試験対策にも活かせる強力なツールとなりそう。
  • 今後は作成した単語帳リストを元に、インドネシア語検定に特化した単語アプリの作成にチャレンジしたい。
1
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1