9. シンプルなファインチューニング例

  • やりたいこと

  • 方針

    • fastTextにより「Wikipedia(en)コーパスの一部」を用いて事前学習する。(異なるソースを元に言語モデルを構築する)

      • なお、ここではfastTextで学習するためにどのようにデータを要ししたら良いのかを確認しやすくするためにWikipediaコーパスから事前学習を行っている。しかし自前でWikipedia事前学習するぐらいなら、最初からFastTextで公開されている事前学習済みモデルをダウンロードして用いるほうが良い。

    • fastText学習済みモデルを用いて、20 newsgroups textの記事をベクトル化する。

    • 比較対象としてTF-IDFによるベクトル化も用意する。

    • 分類学習にはナイーブベイズ、SVM、NNを用いる。なお、ナイーブベイズは基本的にはカウント情報を想定しているため、fastTextベクトルには適用できないことからTF-IDFのみに適用する。

!date
Wed Jun  2 10:09:51 UTC 2021

9.1. 事前学習

9.1.1. 環境構築

  • fastTextモデルのためにgensimを利用。

!pip install --upgrade gensim
Collecting gensim
?25l  Downloading https://files.pythonhosted.org/packages/44/52/f1417772965652d4ca6f901515debcd9d6c5430969e8c02ee7737e6de61c/gensim-4.0.1-cp37-cp37m-manylinux1_x86_64.whl (23.9MB)
     |████████████████████████████████| 23.9MB 159kB/s 
?25hRequirement already satisfied, skipping upgrade: scipy>=0.18.1 in /usr/local/lib/python3.7/dist-packages (from gensim) (1.4.1)
Requirement already satisfied, skipping upgrade: numpy>=1.11.3 in /usr/local/lib/python3.7/dist-packages (from gensim) (1.19.5)
Requirement already satisfied, skipping upgrade: smart-open>=1.8.1 in /usr/local/lib/python3.7/dist-packages (from gensim) (5.0.0)
Installing collected packages: gensim
  Found existing installation: gensim 3.6.0
    Uninstalling gensim-3.6.0:
      Successfully uninstalled gensim-3.6.0
Successfully installed gensim-4.0.1

9.1.2. データセットの準備

  • 英語版Wikipediaのダンプデータをダウンロードし、これを事前学習用コーパスとして利用する。なお、全データを用いると圧縮状態で15GBを超えて待ち時間が長いため、ここでは小規模で提供されているものを指定している。

  • ダンプデータはbzcatで確認しているように、XML形式で書かれている。

#!curl -O https://dumps.wikimedia.org/enwiki/latest/enwiki-latest-pages-articles.xml.bz2
!curl -O https://dumps.wikimedia.org/enwiki/latest/enwiki-latest-pages-articles1.xml-p1p41242.bz2
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  237M  100  237M    0     0  4668k      0  0:00:52  0:00:52 --:--:-- 4760k
!bzcat enwiki-latest-pages-articles1.xml-p1p41242.bz2 | head
<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.10/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.10/ http://www.mediawiki.org/xml/export-0.10.xsd" version="0.10" xml:lang="en">
  <siteinfo>
    <sitename>Wikipedia</sitename>
    <dbname>enwiki</dbname>
    <base>https://en.wikipedia.org/wiki/Main_Page</base>
    <generator>MediaWiki 1.37.0-wmf.5</generator>
    <case>first-letter</case>
    <namespaces>
      <namespace key="-2" case="first-letter">Media</namespace>
      <namespace key="-1" case="first-letter">Special</namespace>

9.1.3. 環境構築2

import multiprocessing
from gensim.corpora.wikicorpus import WikiCorpus
from gensim.models.fasttext import FastText as FT_gensim
/usr/local/lib/python3.7/dist-packages/gensim/similarities/__init__.py:15: UserWarning: The gensim.similarities.levenshtein submodule is disabled, because the optional Levenshtein package <https://pypi.org/project/python-Levenshtein/> is unavailable. Install Levenhstein (e.g. `pip install python-Levenshtein`) to suppress this warning.
  warnings.warn(msg)

ダンプデータから本文抽出する様子。sentencesにlatestの全本文があり、文書数は15025件。1件目の文書に含まれる単語数は50単語。

!date

!curl -O https://dumps.wikimedia.org/enwiki/latest/enwiki-latest-pages-articles11.xml-p6899367p7054859.bz2
wikipedia_data = "./enwiki-latest-pages-articles11.xml-p6899367p7054859.bz2"

# expand and extarct
print("get texts from {}".format(wikipedia_data))
wiki = WikiCorpus(wikipedia_data, dictionary={})
sentences = list(wiki.get_texts())

# 出力確認
print(len(sentences))
print(len(sentences[0]))
print(sentences[0][0:5])

!date
Wed Jun  2 10:10:53 UTC 2021
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 45.4M  100 45.4M    0     0  4338k      0  0:00:10  0:00:10 --:--:-- 4689k
get texts from ./enwiki-latest-pages-articles11.xml-p6899367p7054859.bz2
15025
50
['waterhouse', 'swamp', 'rat', 'scapteromys', 'tumidus']
Wed Jun  2 10:12:36 UTC 2021

9.1.4. fastTextによる事前学習

  • build_vocab() により、まずボキャブラリ(単語一覧)を作成する。

  • その後、コーパスとそれに対する基本情報、エポック数を指定して学習する。

!date

# faxtText
ft_model = FT_gensim(vector_size=200, window=10, min_count=10, workers=max(1, multiprocessing.cpu_count() - 1))

# build the vocabulary
print("building vocab...")
ft_model.build_vocab(sentences)

# train the model
print("training model...")
ft_model.train(
    sentences,
    epochs = ft_model.epochs,
    total_examples = ft_model.corpus_count,
    total_words = ft_model.corpus_total_words
)

print("training done.")

!date
Wed Jun  2 10:12:36 UTC 2021
building vocab...
training model...
training done.
Wed Jun  2 10:24:45 UTC 2021

9.1.5. 事前学習により得られたモデルの確認

  • 単語でも文章でもベクトル化できる。

  • “hoge” は元々の文書には存在しない(False)が、ベクトル化できている。(サブワードによる未知語対応)

  • ベクトル化できたため、類似単語も確認可能。

# 動作確認
print(ft_model.wv['artificial'].shape)
print(ft_model.wv['artificial'][:5])
print(ft_model.wv["more like funchuck,Gave this"][:5])

print("===========")
print("hoge" in ft_model.wv.key_to_index)
print(ft_model.wv["hoge"][:5])

print("===========")
print(ft_model.wv.most_similar("computer"))
print(ft_model.wv.most_similar("programming"))
print(ft_model.wv.most_similar("apple"))
(200,)
[-0.6973012  -0.26773152  0.46666357 -0.25891605  1.3251537 ]
[-0.17531493 -0.01367316 -0.04481015 -0.02450226  0.121394  ]
===========
False
[ 0.26534227  1.1729028   0.04152503 -2.065968    0.08280751]
===========
[('compute', 0.9225346446037292), ('computers', 0.874762773513794), ('computing', 0.8344529867172241), ('compulsory', 0.8319492936134338), ('computable', 0.7980512380599976), ('computerized', 0.794980525970459), ('compulsive', 0.7739043235778809), ('computed', 0.7671758532524109), ('computational', 0.762458324432373), ('computability', 0.7551568746566772)]
[('programme', 0.9124019145965576), ('programmes', 0.9014513492584229), ('programmer', 0.8962664604187012), ('programmers', 0.8907719254493713), ('programmed', 0.8655120134353638), ('programmable', 0.8621218204498291), ('program', 0.8598142266273499), ('programs', 0.8576140999794006), ('programmatic', 0.8526832461357117), ('prog', 0.8027951121330261)]
[('appleby', 0.9099407196044922), ('apples', 0.7911590933799744), ('applause', 0.7831224799156189), ('downloadable', 0.78069007396698), ('applicable', 0.7683167457580566), ('appleseed', 0.7469122409820557), ('app', 0.7379465699195862), ('appleyard', 0.7336268424987793), ('apply', 0.7324667572975159), ('pineapple', 0.7182422876358032)]

9.2. ファインチューニング

fastTextによる事前学習を終えた。これを用いて本当にやりたい 20 news 分類学習に移る。

9.2.1. データセットを用意

  • 20 newsのデータセットを用意。

# fine-tuneing stage.
# デーセットの用意
# こちらも時間かかるので、変換したデータセットを指定した場所に保存。
# 既に保存済みデータセットの利用にも対応。

from sklearn.datasets import fetch_20newsgroups
#categories = ['alt.atheism', 'sci.space']
categories = ['comp.os.ms-windows.misc',  'comp.sys.mac.hardware',  'misc.forsale']
newsgroups_train = fetch_20newsgroups(subset='train', categories=categories)
train_text = newsgroups_train.data
train_label = newsgroups_train.target
newsgroups_test = fetch_20newsgroups(subset='test', categories=categories)
test_text = newsgroups_test.data
test_label = newsgroups_test.target
Downloading 20news dataset. This may take a few minutes.
Downloading dataset from https://ndownloader.figshare.com/files/5975967 (14 MB)

9.2.2. 事前学習モデルによるベクトル化

!date
# 事前学習したfastTextにより、文章をベクトルに変換
def sentence2vector(sentences, model):
    vectors = []
    for sent in sentences:
        vectors.append(model.wv[sent])
    return vectors

ft_train_vectors = sentence2vector(train_text, ft_model)
ft_test_vectors = sentence2vector(test_text, ft_model)

!date
Wed Jun  2 10:24:56 UTC 2021
Wed Jun  2 10:25:28 UTC 2021

9.2.3. 分類学習モデルによる学習(fastText版)

!date

#from sklearn.naive_bayes import MultinomialNB
from sklearn import svm
from sklearn.neural_network import MLPClassifier

#clf1 = MultinomialNB()
clf2 = svm.SVC(gamma='scale')
clf3 = MLPClassifier(max_iter=500)
clfs = {"SVM":clf2, "NN":clf3}

ft_scores = []
for name, clf in clfs.items():
  clf.fit(ft_train_vectors, train_label)
  score = clf.score(ft_test_vectors, test_label)
  ft_scores.append(score)
  print("ft_score = {} by {}".format(score,name))

!date
Wed Jun  2 10:25:28 UTC 2021
ft_score = 0.7681779298545766 by SVM
ft_score = 0.7895637296834902 by NN
Wed Jun  2 10:25:44 UTC 2021
/usr/local/lib/python3.7/dist-packages/sklearn/neural_network/_multilayer_perceptron.py:571: ConvergenceWarning: Stochastic Optimizer: Maximum iterations (500) reached and the optimization hasn't converged yet.
  % self.max_iter, ConvergenceWarning)

9.2.4. 分類学習(TF-IDF版)

# 比較対象の、事前学習なし実験。
# BoW + TFIDFによるベクトル生成

from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer()
tfidf_train_vectors = vectorizer.fit_transform(newsgroups_train.data)
print("train_vectors.shape=", tfidf_train_vectors.shape)
print("len(train_label)=",len(train_label))

tfidf_test_vectors = vectorizer.transform(newsgroups_test.data)
print("test_vectors.shape=", tfidf_test_vectors.shape)
print("len(test_label)=",len(test_label))
train_vectors.shape= (1754, 54317)
len(train_label)= 1754
test_vectors.shape= (1169, 54317)
len(test_label)= 1169
!date

from sklearn.naive_bayes import MultinomialNB
from sklearn import svm
from sklearn.neural_network import MLPClassifier

clf1 = MultinomialNB()
clf2 = svm.SVC(gamma='scale')
clf3 = MLPClassifier(max_iter=500)
clfs = {"NB":clf1, "SVM":clf2, "NN":clf3}

tfidf_scores = []
for name, clf in clfs.items():
  clf.fit(tfidf_train_vectors, train_label)
  score = clf.score(tfidf_test_vectors, test_label)
  tfidf_scores.append(score)
  print("tfidf_scores = {} by {}".format(score,name))

!date
Wed Jun  2 10:25:45 UTC 2021
tfidf_scores = 0.9024807527801539 by NB
tfidf_scores = 0.9230111206159111 by SVM
tfidf_scores = 0.9084687767322498 by NN
Wed Jun  2 10:27:23 UTC 2021
!date
Wed Jun  2 10:27:23 UTC 2021