「Pythonで動かして学ぶ 自然言語処理入門」を読んだ
「Pythonで動かして学ぶ 自然言語処理入門」を読んだ。
Pythonで動かして学ぶ 自然言語処理入門(柳井 孝介 庄司 美沙)|翔泳社の本
今年の目標の一つは Kaggle に挑戦することだ。 もっとも機械学習や統計学は門外漢であり、最初から Kaggle に挑むと容易に挫折することが予想されたので、仕事で開発しているサービスと多少関連がありそうな自然言語処理をテーマにざっくり学ぶことから始めてみることにした。 この記事では本を読みながら考えたこと、読む上での注意点、最後に読んだ感想について記す。
読書メモ
今回は Docker で環境構築して、サンプルコードも(途中まで)写経して動かしてみた [^1]。
Docker コンテナ上で依存関係を pip で管理し、実行時は python
コマンドを叩くという古典的な方法を取っていたが、 Jupyter や Google Colaboratory を使ったらもっと手早くできたのかもしれない。
とは言え CRF++ や mecab などのビルド手順を Dockerfile に記述できたので、今後必要があれば参考にできそう [^2] 。
また Docker で環境構築するなら、 Docker コンテナ上にインストールしたライブラリからでも自動補完ができるようなエディタの設定を事前に検証した方が良い。 自分は今回 Vim でコーディングしたが、ライブラリの自動補完をする方法がすぐに分からず放置した結果、読み進めるごとに後悔が増した。
第0章 自然言語処理とは
- 特になし。
第1章 データを準備しよう
- 特になし。
第2章 テキストデータを収集しよう
Web上のデータを取得する際には、robots.txtなどを確認してデータ取得のルールを守る必要があります。
本格的にスクレイピングに取り組む前に Google による robots.txt の解説には一度目を通しておいた方が良さそう。
HTMLデータからテキストを抜き出すには、BeautifulSoupというライブラリを使います。
Beautiful Soup 以外にも Scrapy というライブラリも有名らしい。 Python のスクレイピングのライブラリの定番はどれだろう?
第3章 データベースに格納しよう
本書では、データベースとしてSQLiteを使い、検索エンジンとしてSolrを使います。
Elasticsearch と比較すると Solr の人気は下落傾向のようだ。 検索機能の開発を新たに行う場合、 Solr の優位点は何だろう?
以下でdocという名前のコアを作成します。
$ ~/nlp/solr7.5.1/bin/solr create -c doc Created new core 'doc'
Dockerfile 上で Solr のコア作成をコード化するとき、 create
コマンドを使うとエラーが発生するため、代わりに precreate-core スクリプトを使う。
第4章 構文解析をしよう
ダウンロードのSourceのところから、cabochax.x.tar.bz2をダウンロードしましょう。
Cabocha のダウンロード時、 Google Drive のウイルススキャン機能に関する確認ダイアログが出る影響で、単純に wget するだけではファイルをダウンロードできない。 confirm というパスパラメータを渡す必要があるので、そのパスを引っこ抜いて URL に付与する。
第5章 テキストにアノテーションを付ける
今回のような抽出問題では、RecallとPrecisionの2つの精度指標があります。
Recall と Precision に加えて F-score というのもよく使われるらしい。
第6章 アノテーションを可視化する
次のコマンドでダウンロードしたファイルを展開し、インストールしましょう。
(中略)
$ sh ./install.sh -u
brat のインストールが対話式なのでコード化が地味に辛い。 expect スクリプトを久々に書いた。
第7章 単語の頻度を数えよう
TF-IDFは、TFとIDFを掛け算した値です。TF(Term Frequency)とは、その文書中における単語の出現回数であり、今注目している文書に対して計算する値です。一方、IDF(Inverse Document Frequency)は、全文書に対して計算する値であり、全文書数をその単語が出現する文書数で割った値の対数です。
TF-IDF は文書と単語の2成分を持つものとして定式化される。 文書の成分を固定したときに単語の自由度だけ残ってベクトルとみなせると理解。 コサイン類似度で内積を取るとき、各単語の TF-IDF の値を掛け合わせている。
LDA(Latent Dirichlet Allocation)は、トピックモデルと呼ばれる手法の一つで、テキストをクラスタリングするときに使われます。
トピックモデルはのトピックについていまいちイメージできていないが、政治やスポーツのように何かしらの意味付けができるものと考えておけば良いらしい。各トピックと関連する各単語が確率的に生成されたと考える。
第8章 知識データを活用しよう
加えて、redirect変数に格納されたURIをラベルに変換したものを、synonym変数に格納しています。DBpedia内で
<http://www.w3.org/2000/01/rdfschema#label>
という関係により保存されている情報、つまりWikipediaの記事タイトルが、synonym変数として取得されます。
SPARQL の
?redirect <http://www.w3.org/2000/01/rdf-schema#label> ?synonym
について補足。
具体的に http://ja.dbpedia.org/page/USA などを開くと rdfs:label
というプロパティに USA という値が定義されている。
この値を ?synonym
変数に格納するという宣言になっている。
それではinfoboxから、人口の値を取得してみましょう。src/dbpediaknowledge.pyに、get_population関数を追加します(リスト8.5)。
サンプルプログラムで DBpedia から取得したアメリカ合衆国の人口値が 3 と出力され、何かのバグかと思ったら実際にデータが 3 だった [^3] 。 他の国のデータを見た感じだと、億や万の単位より下が切り捨てられているのでは?と推測している。
まず準備として、WordNet本体のデータと、多言語版のOpenMultillingualWordNetのデータ(omw)をダウンロードしておきましょう。以下のように、python3をインタラクティブモード(引数なし)で起動し、表示される「>>>」に続けてコマンドを実行します。
python -m nltk.downloader wordnet omw
でもインストール可能だった。
本書ではWikipediaのテキストから学習したWord2Vecのモデルをダウンロードしてきて使います。以下のページで配布されているモデルを使いましょう。
Word2Vec のモデルのダウンロードをコード化する場合は Cabocha と同様に confirm パスパラメータが必要。 ただライブラリと違って Docker イメージに含める必要もなかったので、手動ダウンロードしてリポジトリの方に含めた。
src/word2vec.pyを新規に作成し、類語を取得するget_synonyms関数と類似度を取得するcalc_similarity関数を定義します(リスト8.12)。
gensim の最新バージョン 4.0.2 だと Word2Vec のモデルを読み込めなかった。 モデルのトレーニングに使った gensim のバージョンが古いらしく、 gensim のバージョンを 3.8.3 に落としたら直った。 第7章のトピックモデルを使ったクラスタリングのサンプルプログラムも gensim を使っているが、こちらは両方のバージョンで動作した。
第9章 テキストを検索しよう
リスト3.6(src/solrindexer.py)にsearch関数とsearch_annotation関数を追加します(リスト9.1)。
solrindexer.py
に追加した検索用のメソッドのバグあり。
opener.open(req)
ではなく urllib.request.urlopen(req)
が正しい。
Solr のデータを永続化するため、コア等のデータが格納されている /opt/solr/server/solr
に Docker の data volume をマウントさせる方法を取っていた。
しかし volume をマウントしたままだと precreate-core
で後からコアを追加できないことが分かった。
仕方なく、データをロードするスクリプトを実行するコマンドを Makefile に追加した。
第10章 テキストを分類しよう
数学的に厳密な説明は省略しますが、この直線を決定するというのは、特徴量の各次元の重み(重要度)を調整することと同じになります。
線形 SVM で境界となる超平面を決定することが特徴量の各次元の重みを調整することと同義であるという点について、あまり正確に把握している自信がない。 重みの大きな次元ほど、超平面を跨ぐために必要な成分の大きさが小さく済むので評価されやすいみたいなイメージで合っているだろうか。
続いてリスト10.5に、作成した正解データを読み込んでモデルを学習するプログラムを示します。
sample_10_05.py
の from sklearn.externals import joblib
でエラーが発生した。
teratail の回答によると joblib は sklearn.externals にバンドルされなくなった模様。
pip list | grep joblib
を実行したら引っかかったので、そのまま import joblib
とした。
リスト10.6を実行すると、result/model.pklとresult/vocab.pklが読み込まれ、学習済みモデルにおける各単語の重みが表示されます。
BoW 特徴量の線形 SVM による分類は、評価用データが殆ど False でラベリングされた文に偏ってしまい、うまく学習できたのかよく分からなかった。
sample_10_06.py
による BoW 特徴量を線形 SVM で分類した際の重みの出力結果の一部を抽出すると以下の通り。
| coef_[0] | word | | --: | :-- | | 0.869609 | 海底 | | 0.556880 | 緊張 | | 0.556880 | 原油 | | 0.528749 | カラ | | 0.491447 | アルミニウム | | 0.428120 | ロシア | | 0.422154 | 国産 | | 0.422154 | アヘン | | 0.419625 | 世界中 | | 0.365326 | 歩調 | | ... | ... | | -0.150936 | アメリカ合衆国 | | -0.152481 | 独占 | | -0.162184 | 競争 | | -0.175274 | ニカラグア | | -0.185956 | 樹立 | | -0.185956 | 沖縄 | | -0.190492 | 全国 | | -0.228915 | ユーラシア | | -0.241589 | 左右 | | -0.241589 | シルクロード |
第11章 評価分析をしよう
極性辞書として、本章では以下の2つの辞書を用います。
サンプルコードで利用されていた極性辞書の形式にはバラつきがあったが、他の辞書も形式は特に統一されていないものなのだろうか?
第12章 テキストからの情報抽出
特になし。
第13章 系列ラベリングに挑戦しよう
特になし。
所感など
自然言語処理の手法としてどのようなものがあるか、 Python を使ってどのようにコードを書くのかざっくりイメージを掴むことができた。 一方、元からある程度 Python のコードを読むことができ、かつ Web アプリケーションの基本的な仕組みが分かっていないと読み進めるのが辛いだろうと感じる箇所もあった。 自分と同じように、自然言語処理や機械学習は良く知らないが、何か足掛かりになる本を探しているエンジニアには読んでみる価値があるかもしれない。
[^1]: 10.6 で例示されたコードが Chainer の特定のコミットに依存しており、そこから動作確認が辛くなってきたので、以降の写経は止めた。 [^2]: 個人的にはリポジトリを公開したいが、本の内容が多分に含まれており、著作権の点でアウトかどうか判断できなかったため非公開にしている。 Docker 周りは本の内容と無関係のため、需要があれば Gist でそこだけ公開するかも。 [^3]: 2021年7月10日時点。