2012年、私はゆっくりとpymorphy2( github 、 bitbucket )を始めました-ここでこのライブラリを紹介する時が来たと思います:pymorphy2はpymorphyよりも何百倍も速く動作し(C / C ++拡張機能を使用せずに)、さらに少ないメモリを必要とします; より良い辞書、より良い解析品質、より良い文字eのサポート、より簡単なインストール、より「正直な」APIがあります。 マイナスから-すべてのpymorphy機能がpymorphy2に実装されているわけではありません。
この記事では、pymorphy2がどのように作成されたのか(技術的な詳細が退屈な場合もあります)、およびその際にどれだけナンセンスにしたかについて説明します。 すべてを試してみたい場合は、 ドキュメントを読むことができます 。
pymorphyの問題は何でしたか:
- かなり遅い速度(デフォルトでインストールした場合、数百語/秒);
- aot.ruの辞書への依存。これは、補充/修正方法が不明です。
- APIに関するいくつかの問題-たとえば、傾斜器のAPIによると、ライブラリ自体が分析のあいまいさを排除しているように見えるかもしれませんが、そうではありません。
- 文字theは、「どこでもすべてのreplaceをeに置き換える」という方法でサポートされていました。
- かなり複雑なインストール-辞書、いくつかの異なる形式などをダウンロードする必要があります。 (日本語でも、誰かが一連のビデオレッスンを行いました );
- Python 3.xは、bitbucketがインストールされたバージョンでのみサポートされていました。
- 不定詞を説得することは不可能です。動詞を不定詞の形にすることができますが、逆も同様です。
- ハイフンでつながれた単語は常に正しく傾くとは限りません
- まあ、など
pymorphyを行う前は、Pythonで書いたり、自然言語処理を行ったりしませんでした(pymorphyは両方を理解する方法でした)。
しかし、それでもpymorphy1の多くは正しく行われました。 たとえば、ロシア語のドキュメントと特別な依存関係のないインストール(最も重要なのは、コンパイラを必要としない); テストによる多かれ少なかれ通常の開発プロセス; 分析と予測の品質自体は非常に高かった。 djangoとの統合も良い「マーケティング」の動きでした-ただし、いくつかの概念的な問題がありました(分析のあいまいさを解決しないとテンプレートで単語を直接正しく宣言することはできません-これはAPIで提供されませんでした)。
そのすべての欠点にもかかわらず、ライブラリは(予想外に)非常に人気があることが判明しました。たとえば、フランス語のQuaeroプロジェクトの一部としてロシア語の音声合成システムの開発にわずかに使用されたのはGoogleであり、一部の大学では教材として推奨されています。 ここには大きなメリットはないと思います。むしろ、適切なタイミングで適切な場所にたどり着いたのです。
長い間、後方互換性を壊したくなくて、自分が持っているものを開発しようとしました(たとえば、 buriyは 、後方互換性を破ったフォークをしましたが、より速く動作しました)。 pymorphy2を書くための決定的なきっかけはOpenCorporaプロジェクトでした-そこから来た人たちは、とりわけ(そして他にもたくさんあります)、aot.ruから辞書を取り、その構造を完全に再設計し、補充やその他の改善を始めました。
そこで、OpenCorporaの辞書を使用するというアイデアが生まれました。
Pymorphyは、ほとんど処理されないaot.ruの辞書を使用しました(これらはsqlite形式に変換されましたが、本質的には構造は同じままでした)。 個別に、単語の「基礎」、個別に「接頭辞」、個別に「語尾」が保存され、個別に-これらの部分から単語を形成するための規則。 この方法は、その基礎に基づいて、多くの労力と研究なしで2つの「週末」に形態素解析器を実装することが可能であったため、優れていました。
しかし、これらすべて-多くのラッパーを介したデータへのアクセス、および(特に)Pythonコードを使用してパーツから単語を収集することは、作業速度に悪影響を及ぼしました。 このアプローチではどうにかして根本的に速度を改善することはできませんでした、ソリューションは複雑で、本当に助けにはなりませんでした(注: 「すべてをそのまま書き換えますが、C ++で」オプションはすぐに動作しますが、pymorphy2よりも多くのメモリが必要です) 。
要するに、pymorphy2では、辞書が新しく、まだ多くの辞書を書き換えているため、他のアプローチを試してみたかったのです。 同時に、C / C ++コードのヒープのラッパーではなく、pymorphy2をpythonライブラリのままにしておきたい-解析ロジックをpythonのままにしておきたい。 何をすべきかのオプションがいくつかありました。
自動機
頭に浮かんだ最初のオプションは、有限オートマトンの観点から問題を再定式化することでした。 lightcasterはこのアプローチをここで詳しく説明しています: habrahabr.ru/post/109736 アプローチは美しい、つまり、そうです。
ここで私を混乱させたのは:
a)この記事ではOpenFST C ++ライブラリを使用しました(有限状態マシンを実装する最も一般的な方法のようです)が、ユーザーに手動でインストールすることはオプションではありません。
b)C ++ライブラリを使用しても、記事で判断した結果はやや控えめでした(mystemまたはlemmatizerの場合は2千語/秒対100 +千語/秒)。 この数値が大幅に改善される可能性が高いことは明らかです(そして、 ライトキャスターは何も最適化していないと言います)-それでも
c)これは、(私の意見では)エントリーしきい値を上げるアプローチの1つです。これはマイナスの可能性が高いと思います。
最終的に、次のことが必要であることが判明しました。コードを最適化する方法と、C ++ライブラリを使用してもコードが非常にゆっくりとなる理由を把握します。 OpenFSTのよりシンプルなインストールラッパーを作成する(または別のFST実装を使用する-たとえば、独自に作成する)+ PythonでOpenFSTのごく一部(またはFST実装)を実装する(コンパイラなしでpymorphyを使用できるようにする)有限状態マシンの観点から。
数年前、 lightcasterは有限状態マシンでのpymorphyの実装の別のスケッチを送ってくれました(pythonium、C拡張なしで、その時は何も理解できませんでした:)-結果としてのスケッチは約2000ワード/秒の速度で動作し、約300 MBのメモリを必要としました。 これはすべて、悪いことではありませんが、とにかくあまり刺激的ではありませんでした。 「額に」この方法で解決した場合、それは非常に速く動作しないこと、そしてトピックを理解している人よりもはるかによく書く必要があることは明らかでした。 要するに、それは多くの仕事と不当な結果のように思えました(記事の終わり近くで、その他のアーキテクチャ上の考慮事項)。 おそらく、それは間違っているように見えます、誰が知っている-私はそれを試していない、言い訳だけ。
この方法(問題をもっぱらオートマトンに関する問題と見なすため)に行きませんでした。 しかし-それを言う方法、それはすべての視点に依存します:)
mystemをコピー
2番目のオプションは、 mystemの出版物に記載されているものをほぼ実装することでした 。 mystemが現在同じアルゴリズムを使用しているかどうかはわかりませんが、記事で説明されている方法は非常に合理的です。 彼らはmystemについて多くの良いことを書いているので、最初にpymorphy2で出版物に記載されているものに似たものを実装しようとしました。
メソッドの本質(私が理解しているように)
a)ロシア語のすべての単語のリストがあり、各単語に対して「赤緯モデル」(「パラダイム」)が示されています-この単語の他の形式を構築できるテンプレートの一種。 たとえば、多くの名詞では、末尾に「and」という文字を割り当てて、主格の複数形と「am」(主格)を取得できます。 ロシアの形態の実現のほとんどは、この原則に基づいています。
b)単語のすべての反転した「語尾」に対して接頭辞ツリー(トライ)が構築されます。 「エンディング」は、aot.ruとほぼ同じです。右側の単語の一部で、曲がりによって変化します。 たとえば、「ハムスター」という単語では、「ami」になります-「ima」をトライに追加します。
c)単語のすべての反転した「基礎」に対して接頭辞ツリー(トライ)が構築されます。 たとえば、「ハムスター」という単語の場合、「カモフ」です。 さらに、このフレームワークの赤緯モデル(パラダイム)の可能なインデックスは、「セパレータ」を介して「基礎」の最後に割り当てられます。 たとえば、「ハムスター」をパラダイムAとBに従って傾けることができ、区切り記号が$である場合、トライでは「kamokh $ A」と「kyamokh $ B」の値を追加します。 mystemの記事では、1つのツリーが基本に使用されたわけではなく、多くの(私の意見は最適化手段として)-しかし、これはアルゴリズムに大きな影響を与えません。
d)単語の実際の分析:単語の末尾から追跡し、最初のトライからすべての可能な「末尾」を収集します(プレフィックスツリーでは、この操作はトライの単語数のO(1)と単語長のO(N)に対して実行されます)。 「ハムスター」の場合、語尾に「」(空)、「and」、「ami」、または「kami」が語尾変化する語があることがわかります。 これらの「エンディング」は「基本」に対応しています:ハムスター、ハムスター、ハムスター、ハムスター。
e)見つかった最短のベースを取得し、2番目のトライで探します($セパレーターに移動します)。 見つかった場合、ベース自体に加えて、単語の偏角パターンのすべての可能なインデックスをすぐに取得します(これらはセパレータの後ろにあります)。 それぞれの赤緯モデルについて、必要な語尾のあるフォームがあるかどうかを確認することができます(ステップ(d)にあります)-ある場合は、単語をソートします。
f)何も見つからなかった場合、最も短い基準を持つ辞書の単語がないことを意味します-より長い基準に進むことができます。 同様の基礎を確認することもできます(何が行われているのか正確にはわかりませんでしたが、これはポイントではありません-たとえば、次のより長い基礎を試すことができます。すべての可能な基礎がテストされた後、同様の単語の検索がすでに機能します) 。
このペーパーでは、辞書のサイズを縮小し、予想外の予測を削除できるさまざまなヒューリスティックについても説明しています。
何かを誤解したり、誤解したりする可能性があります。 ビジネスに必要な場合は、もちろん元の記事を読むことをお勧めします。
b)単語のすべての反転した「語尾」に対して接頭辞ツリー(トライ)が構築されます。 「エンディング」は、aot.ruとほぼ同じです。右側の単語の一部で、曲がりによって変化します。 たとえば、「ハムスター」という単語では、「ami」になります-「ima」をトライに追加します。
c)単語のすべての反転した「基礎」に対して接頭辞ツリー(トライ)が構築されます。 たとえば、「ハムスター」という単語の場合、「カモフ」です。 さらに、このフレームワークの赤緯モデル(パラダイム)の可能なインデックスは、「セパレータ」を介して「基礎」の最後に割り当てられます。 たとえば、「ハムスター」をパラダイムAとBに従って傾けることができ、区切り記号が$である場合、トライでは「kamokh $ A」と「kyamokh $ B」の値を追加します。 mystemの記事では、1つのツリーが基本に使用されたわけではなく、多くの(私の意見は最適化手段として)-しかし、これはアルゴリズムに大きな影響を与えません。
d)単語の実際の分析:単語の末尾から追跡し、最初のトライからすべての可能な「末尾」を収集します(プレフィックスツリーでは、この操作はトライの単語数のO(1)と単語長のO(N)に対して実行されます)。 「ハムスター」の場合、語尾に「」(空)、「and」、「ami」、または「kami」が語尾変化する語があることがわかります。 これらの「エンディング」は「基本」に対応しています:ハムスター、ハムスター、ハムスター、ハムスター。
e)見つかった最短のベースを取得し、2番目のトライで探します($セパレーターに移動します)。 見つかった場合、ベース自体に加えて、単語の偏角パターンのすべての可能なインデックスをすぐに取得します(これらはセパレータの後ろにあります)。 それぞれの赤緯モデルについて、必要な語尾のあるフォームがあるかどうかを確認することができます(ステップ(d)にあります)-ある場合は、単語をソートします。
f)何も見つからなかった場合、最も短い基準を持つ辞書の単語がないことを意味します-より長い基準に進むことができます。 同様の基礎を確認することもできます(何が行われているのか正確にはわかりませんでしたが、これはポイントではありません-たとえば、次のより長い基礎を試すことができます。すべての可能な基礎がテストされた後、同様の単語の検索がすでに機能します) 。
このペーパーでは、辞書のサイズを縮小し、予想外の予測を削除できるさまざまなヒューリスティックについても説明しています。
何かを誤解したり、誤解したりする可能性があります。 ビジネスに必要な場合は、もちろん元の記事を読むことをお勧めします。
トライ
このメソッドはプレフィックスツリー(Trie)を使用します-メソッドを実装するには、PythonのTrie実装が必要です。 私はPythonの実装を使用したくありませんでした(メモリからの速度と大食いを恐れていました)、そして驚くべきことに、Trieのいくつかの良いC / C ++実装のための既製のPythonラッパーは見つかりませんでした。
データ構造のC / C ++実装を記述することはまれです。C/ C ++にはすでに多くのことがあり、ライブラリの多くは最新のアルゴリズムを実装しています。 なんで? そこで、ある男がaなデータ構造を思いつき、それについての記事を科学雑誌に書きました。 結果を繰り返すことができるように、著者はしばしば実装をレイアウトし、最も頻繁に-それはCまたはC ++で書かれています。 彼がそれをアップロードしない場合、他の誰かが論文を読んで、実装を書きます-C / C ++でも(OK、時にはそれらをJavaで書きます)。
しかし、pymorphy2は趣味のプロジェクトであり、それに多くの時間を費やすことはうまくいきません。 効率的に時間を費やし、小型の自転車を発明することを試みた方が良いでしょう。 つまり、既成のTrieの実装を使用して、Cython(pymorphy2とは関係のない別のパッケージ)でラッパーを作成することにしました。
このアプローチには2つの大きな利点があります。
1)データ構造は他の目的に使用できます。 アプローチがそれ自体を正当化しなかったとしても(ネタバレ:それが起こった、それは正当化しなかった)、その努力は無駄にならなかっただろう。
2)データ構造に関連する複雑さは、形態素解析器自体から「引きずられている」。 アナライザーは単純なAPIを介してTrieと通信し、単語分析のみを処理します。 作業のアルゴリズムを理解するために、プレフィックスツリーがどのように配置されているかを詳細に理解する必要はありません。 さらに、基本的なデータ構造の実装を(たとえば、pythonで)置き換えるのは簡単な作業です。 モーフ間に明確な境界(=インターフェイス)があります。 単語のパーサーとリポジトリ。
最初はlibdatrieライブラリが好きだったので、そのためのラッパーについてhabrahabr.ru/post/147963に書きました 。 ライブラリの一部を修正する必要がありました(通常は次のようになりました-私はCで実装を作成し、ライブラリの作成者はすべてを捨てて、同じことについてより慣用的なCコードを記述しました-そして、Cコードは彼にとって本当に良かったので、正しくしました:); その結果、完全に使用可能なラッパーが得られました。
ダトリと「mystemからすべてを盗む!」アプローチでは、20〜60千ワード/秒(予測子なしで、最小のpythonのタイイングで、なぜそのような広がりがあったのか-おそらく天気は覚えていません)、約30 MBのメモリをすべて占有しました。
その速度で、プラグは予想外の(私にとって)場所にありました。何よりも、単語の「終わり」の許容解析と、単語の「始まり」の許容解析の比較が遅くなりました(これはアルゴリズムの一部です)。 私の実装では、2つのソートされた数値リストの共通部分を見つけることになりました。 明らかな方法(「両方のリストを並行して処理する」)は、このタスクでは「短いリストを処理して、幅の狭いバイナリ検索で数値を検索する」アプローチよりもはるかに遅いことがわかりました。 しかし、この2番目のオプションは引き続きボトルネックであり、Cythonで書き直されました。どうすればよいかわかりませんでした。 おそらく、このコードをより良く書くことができたかもしれませんが、すぐにはうまくいきませんでした。
さらに、一方では30MBが適切であり、他方ではaot.ruの方が少なくなります(9MB、19MBのように記述します。9は私の手が確認できなかったと想定しています)。 メモリ消費は重要です pymorphyは大砲(サイト上の単語の減少)からスズメを撃つためにも使用され、これにはすべてのWebプロセスにロードする必要があります。 まあ、または(メモリが残念なために)ダウンロードしない場合は、別のサービス(いくつかのzeromqで、またはhttp経由で通信)で庭を囲みます-これも望ましくありません。
datrieの実装は、ポインターに対する「単純な」トライではありません。datrieが必要とするメモリは、通常のプレフィックスツリーの2倍未満です(したがって、30Mbでも十分です)。
さらに、アルゴリズムの観点からは、どういうわけか複雑であることが判明しました。 さらに終了すると(予測子、ヒューリスティック)、すべてが非常に遅くなり、さらに複雑になることは明らかでした。 混乱。
マリサトライ
しかし、「a la mystem」アプローチをあきらめずに放棄するのではなく、ダトリーを別の何か(かなり愚かだが良い結果をもたらした)に置き換えようとすることにしました。 その選択は、データ構造の第一人者である矢田進によって書かれたC ++ライブラリmarisa-trieにありました。 彼女のために、私はdatrieとほぼ同じインターフェースを持つpythonラッパーも作成しました。
マリサトライをテストし始めたとき、私の額に私の目が最初に登りました。 参照:
- ロシア語の300万語すべてをPython辞書に読み込んで読み込むと、約600 MBのRAMが必要になります(リスト内-約300 MB)。
- 同じデータをdatrieにロードすると、70MBのRAMが必要になります-これはすでにかなりクールです。
- また、同じデータをMARISA-Trieにアップロードすると、さらに10倍少ない7 MBのメモリが必要になります。
どう? 事実は、MARISA-Trieは実際にはTrieではないということです:)それは何ですか-私はそれを通常の方法で理解しませんでした。 ソースに「肉」が含まれるフォルダーはgrimoireと呼ばれます。 アルゴリズムの説明は日本語のみです。
どうやら、MARISA-TrieはPatricia-Trie( wiki )の「簡潔な」実装であり、テキスト情報を各ノードだけでなく、次のレベルのMARISA-Trieにも一致させることができます。 「簡潔」とは、ツリー構造がビットマップでエンコードされているため、メモリをほとんど消費しないことを意味します。 さて、興味深い機能があります:MARISA Trieは、すぐに「完全なハッシュ」としても機能します。 各キーには0〜len(トライ)-1の範囲の一意の番号が割り当てられ、0〜len(トライ)-1の各番号は1つのキーに対応します。 キーを番号またはキーごとに取得できます。
速度-ダトリーはマリサトライよりも10倍高速です。
pymorphyに戻ります。 datrieをmarisa-trieに簡単に置き換えた後、ファーム全体が5 MBのメモリを占有し、2〜5000ワード/秒(予測子なし)の速度で動作することが判明しました。
一方で-5Mbはクールです(ただし、これまでのところ予測変数はありません)。 しかしその一方で-2〜5千語/秒-2万〜6千語/秒後にゆっくりと、+私はマリサトライに強く結びつきたくありませんでした。 これは、pymorphy2をインストールするためのコンパイラー要件につながります。 datrieがどのように機能するかを完全に理解し、python互換の実装を書くことは問題になりませんが、marisa-trie ... marisa-trieの場合、移植することは可能ですが、より多くの労力が必要になります。この質問はおそらく「延期」され、pymorphy2のインストールにはコンパイラが必要になります。
試み自体は行き止まりでしたが、1つの興味深いことが明らかになりました。5MB(「ほぼmystem」+ marisa-trieアルゴリズム)および7MB(「すべての単語をmarisa-trie全体に入れる」)-数値は非常に類似しています。 これらの数字はついに、すべての単語が一度に全部に(開始と終了に割り込まずに)メモリにロードされるアプローチを捨てる価値がないという事実に目を開きました。
直感的に:すべての単語をメモリに「そのまま」保持する場合、計算を少なくする必要があります。ピースから単語を収集する必要はありません。すべてが高速に動作し、コードがよりシンプルになります。
この時点で、「mystemをコピーする」というアイデアの扱いをやめました。 初期のpymorphy2コミットでは、datrieとmarisa-trieの未完成オプションが利用可能です。
mystemが使用する疑いがあります(まあ、出版物によれば、現在何が使用されているかわかりません)単語の一部でいくつかのトライを行います。予測子のアルゴリズムをより便利に、しかし単にすべての単語をメモリに完全に保存しないでください。 私のテストによると、通常のトライでは、これは(ワード+パラダイム上のデータ)300-400メガバイト、メガバイト200データバイトかかりますが、MARISA-Trieでは20メガバイトに対応できます。 mystemの論文のようにそれは良いトリックですが、このように、私が思うように(私は間違っているかもしれませんが、「I my mytemが欲しい」アプローチはうまくいきませんでした)
未来に戻る
それ自体では、MARISA-Trieはすべての単語をメモリに保持するために適合しませんでした。高速ではありませんでした。任意の方法でツリー内を移動するために必要でした-alreadyの正しいサポートのために-何のために、おそらく-覚えていませんその後、aot.ruのすべての単語が何らかの形で一度に思い出されたというあいまいな思い出がありました。
それで、aot.ruから形態素解析の説明、特に「バイナリ辞書表現」の段落を読み直し始めました。彼はいわば、それがすべて始まった場所に戻った。
aot.ruの記事では、すべてが「自動」と呼ばれています。
「エイリアン化可能な」データ構造を持つバリアント(他の何かに使用できます-ダトリやマリサトライなど)私は、辞書を備えた特殊なオートマトンよりもずっと好き(そして好き)です。特殊なオートマトンはpymorphy2に「埋め込まれ」ます-そこから何も再利用できず、pymorphyのフレームワーク内でのみデバッグでき、pymorphyのフレームワーク内で複雑さが正確に蓄積されます。私は通常、目を通してその段落を実行し、「うーん、うーん、悪い」と考え、それを別の方法で見つけました。
しかし、物事をさまざまな角度から見ることができます。おそらく、これは最初から明白だったはずですが、「クローンマイステム」を作成しようとして初めて、aotで使用されるオートマトンが実際にデータ構造とまったく同じであることがわかりました。DAWG。単語はキーとして書き込まれます(注釈は最後に割り当てられます)。そして、そこで説明されているすべての操作は、datrieとmarisa-trieにあった同じAPIに完全に当てはまります。
「コンピューターサイエンスには、キャッシュの無効化と名前の付け方という2つの難しい問題しかない」と言われているのは事実です。「キャッシュの無効化」については知りませんが、ここでは「名前付け」を完全に感じました。
あなたはそのような瞬間に完全な馬鹿のように見えます。すべてがシンプルで、最初からすべてが私の目の前にあり、これについて何度も言われました(マシンとDAWGの両方について)。
それで、新しい計画はこれでした:打たれたトラックで
- C / C ++でDAWG実装を見つけます。
- datrieおよびmarisa-trieと同じAPIでラッパーを作成します。
- すべての単語をDAWGで書きます(文法形式に関する情報をすぐに使用して)
- ボーナス-インストールを簡素化するために、DAWGの読み取り専用にPythonライブラリを作成します。
この場合の語彙単語の形態学的分析は、DAWGから単語に関する情報を取得するだけです。最も単純な場合(単語の構文解析は1つだけで、文法情報のみが必要であると仮定)、1行のコードにすることができます。
DAWG(dawgdic)の優れたライブラリーは、Marisa-trieによって書かれた同じYata Susumuで発見されました。pythonラッパーを作成しました:github.com/kmike/DAWGとこの形式のpython「リーダー」:github.com/kmike/DAWG-Python(インストールにコンパイラーを必要としません)。
注釈なしの辞書全体は、DAWGで約3MB、注釈付きで約7MB(=各単語の解析方法に関する情報)を占めました。比較のために、このデータをすべて「そのまま」Python辞書にロードすると、数ギガバイトのメモリが必要になります。
残ったのはpymorphy2を書くことだけでした:)
記事は何となく終わりませんが、pymorphy2がどのように内部に配置されるかについて-今のところほとんど何もありません。テキストをさらに膨張させないために、何も起こりません。 pymorphy2が内部にどのように配置されているかは、写真でさえ、ドキュメントでかなり(冗長?)詳細に説明されています。 pymorphy2はPython lemmatizerのクローンではなく、すべてが異なるように配置されていますが、aot.ruからの情報はもちろん役立ちました。
2つの楽しいベンチマーク(ラップトッププロセッサ1.8 Ghz、DAWGを使用するPython 3.3およびDAWG-Pythonを使用するPyPy 1.9):
Memory usage: 13.8M dictionary, 25.4M total (load time 0.14s) py33 runtests: commands[1] benchmarking MorphAnalyzer(): morph.parse(w) 29950 words/sec morph.parse(w) 47474 words/sec (considering word frequencies) morph.word_is_known(w) 244816 words/sec [p.normal_form for p in morph.parse(w)] 27807 words/sec [p.normalized for p in morph.parse(w)] 18231 words/sec [p.lexeme for p in morph.parse(w)] 3421 words/sec [{'NOUN'} in p.tag for p in morph.parse(w)] 23862 words/sec [p.tag.POS == 'NOUN' for p in morph.parse(w)] 22157 words/sec morph.tag(w): 96342 words/sec (considering word frequencies) morph.tag(w): 46767 words/sec morph.tag(w): 46935 words/sec (umlauts removed from input) morph.tag(w): 36331 words/sec (str(tag) called) benchmarking MorphAnalyzer(result_type=None): morph.parse(w) 35088 words/sec morph.parse(w) 62431 words/sec (considering word frequencies)
Memory usage: 40.5M dictionary, 84.8M total (load time 0.39s) pypy runtests: commands[1] benchmarking MorphAnalyzer(): morph.parse(w) 41360 words/sec morph.parse(w) 108858 words/sec (considering word frequencies) morph.word_is_known(w) 243742 words/sec [p.normal_form for p in morph.parse(w)] 51728 words/sec [p.normalized for p in morph.parse(w)] 37551 words/sec [p.lexeme for p in morph.parse(w)] 14612 words/sec [{'NOUN'} in p.tag for p in morph.parse(w)] 44878 words/sec [p.tag.POS == 'NOUN' for p in morph.parse(w)] 45129 words/sec morph.tag(w): 133086 words/sec (considering word frequencies) morph.tag(w): 60049 words/sec morph.tag(w): 62567 words/sec (umlauts removed from input) morph.tag(w): 52632 words/sec (str(tag) called) benchmarking MorphAnalyzer(result_type=None): morph.parse(w) 60777 words/sec morph.parse(w) 124226 words/sec (considering word frequencies)
ここで面白いのは、(a)「内部」アクションが非常に異なる(C ++ラッパー+インタープリターvs jitコンパイラー)にもかかわらず、数値が非常に似ていること、および(b)PyPyが高速であることです。
C ++自体でのDAWGの実装(Cythonラッパーを考慮に入れてPythonから使用する場合)は、PyPyでのDAWG-Pythonよりも数倍高速であり、pymorphy2の以前のバージョン(最も低かった)はCPythonで高速でした。 時間が経つにつれて、機能が大きくなり、コードがより複雑になり、pymorphy2が遅くなりました(以前のバージョンと200 + 1000ワード/秒は分解できました-あまり良くなく、あまり便利ではありませんでした); より高速な基本データ構造「outweigh」はなくなりました-そして、PyPyでpymorphy2はより速く動作します。 一方、CPythonのバージョンを高速化する方法は明らかです-Cythonで何か他のものを書き換えます(ところで:私はこれをするつもりはありません)。 PyPyでは、これはそれほど明白ではありません。
Python 3.3(文法情報を取得)とPyPy(完全な解析、初期形式と便利なAPI)の両方で、実際のテキストで10万語/秒の解析を取得できることがわかります。 比較のために:「実際の」mystem(C ++で書かれているようです)は(初期の段階での私のテストによれば)1.5倍から2倍速く動作し、同じ量のメモリを必要としました。 これから、私は自分自身のために-おそらく間違って-ミスステムはそれにもかかわらずいくつかのトライを持つアプローチを使用すると結論付けました。 単語が完全に格納されている場合、mystemやその他の何かがトリッキーなものを実行する場合でも、C ++実装はより強力に外れます。 さて、これは実証されていないおしゃべりでそうです。
DAWGのPyPyまたはC ++実装のいずれも使用しない場合、pymorphy2は、すべてのアクセラレーションを有効にしたpymorphy1よりも何倍も高速に(推定で数十倍)動作します。
どうすれば手伝うことができます
興味深い質問が残っています-pymorphy1をどうするか。 現在、pymorphy2にはない機能(たとえば、djangoとの統合、単語と数字の一致、姓の変化)がありますが、bitbucketのバージョンでは後方互換性がありません。 pymorphyの別の後方互換性のないバージョンをリリースすることは、どういうわけか馬鹿げています:)。 私はすべてに全く手を貸していません。
- 誰かがpymorphy1から欠落している機能を移植するのを手伝ってくれたら素晴らしいと思います。
- OpenCorporaからのヘルプはhelpおよびpymorphy2でもあります。
- トラッカーには多くの未解決のバグがあります(さまざまな程度のハードコア)。
- pymorphy2には非常に原始的なトークナイザーが組み込まれています-より良いトークナイザーを発明できます。 pymorphy2.units.by_shape.pyのクラスがトークン化とどのように相関するかを理解するのは良いことです。
- CLIを改善して、pymorphy2を完全なコマンドラインユーティリティとして使用できるようにすることができます。
- pymorphy2の予測子をさらに記述しようとすることができます(pymorphy2.unitsで現在の予測子がどのように作成されるかを参照)-インターフェイスが拡張に適しているかどうかを理解するために、より多くの目が必要です(+
- pymorphy2の使用を開始できます。
多数の人々がpymorphyの開発に参加しましたが、その多くは本当に深刻な貢献をしており、そのすべてがpymorphy2に移されました。 私は作品が消えたくはありません(私はそれが消えないと思います)。 この助けがなければ、pymorphyをサポートする動機や、すべてをpymorphy2に書き換える動機はありません。
そしてpymorphy2では、いくつかの人々がすでに深刻な貢献をしています:)
参照資料
- ソースコード: github 、 bitbucket
- ドキュメント: pymorphy2.rtfd.org
- バグトラッカー: github.com/kmike/pymorphy2/issues
- ディスカッション/サポート: google-group
- プロセス中に発生したPythonのデータ構造: DAWG 、 DAWG-Python 、 marisa-trie 、 datrie 、 hat-trie