Keras+LSTM+CRF を使用した固有表現抽出 NER の練習

Keras+LSTM+CRF を使用した固有表現抽出 NER の練習

[[339715]]

テキスト分割、品詞タグ付け、固有表現認識は、自然言語処理の分野では非常に基本的なタスクです。それらの精度が下流のタスクの精度を決定します。実は、私はこれまで固有表現認識の仕事にあまり触れたことがありませんでした。大学院時代には断続的にそのようなプロジェクトに参加していましたが、卒業後は表面的な理解しかしていないと感じていました。最近、再び取り組んで、実践を主な学習手段として、固有表現認識などのタスクを比較的体系的に理解、学習、適用したいと思っています。

今日のアプリケーションには、ディープラーニングが関与しないタスクはほとんどありません。多くのサブタスクの開発履歴は驚くほど似ています。当初、研究とアプリケーションのほとんどは機械学習の分野に集中していました。その後、ディープラーニングモデルの開発に伴い、広く使用されるようになりました。名前付きエンティティ認識などのシーケンスラベリングタスクも当然例外ではありません。LSTM + CRFに基づくディープラーニングエンティティ認識に関する関連研究はありましたが、以前の方向性と一致しなかったため、あまり注意を払っていませんでした。最近、たまたまNERを学んでいました。以前の関連記事では、機械学習手法に基づいて簡単な名前付きエンティティ認識を実践しました。ここでは、ディープラーニングモデルに基づいてNERを実装しています。

名前付きエンティティの認識は、シーケンスのラベル付けタスクに属しますが、これは実際には分類タスクに似ています。NER は、テキスト内の定義済みのエンティティ タイプを識別することです。

NER はシーケンス ラベリングの問題であるため、データのラベリング方法もシーケンス ラベリングの問題の方法 (主に BIO と BIOES) に従います。ここではBIOESが直接紹介されています。BIOESを理解すれば、BIOもマスターできます。

まず、BIOES の意味を列挙してみましょう。

  1. B、つまりBeginは開始を意味します 
  2. Iは中級を意味します。  
  3. EはEndの略で、終わりを意味します 
  4. SはSingleの略で、1文字を意味します。  
  5. O は Other (その他) の略で、無関係な文字をマークするために使用されます。

たとえば、次の文の場合:

  1. ヤオミンはバスケットボールをするためにハルビン工業大学体育館へ行った

注釈結果は次のとおりです。

  1. ヤオミンはバスケットボールをするためにハルビン工科大学スタジアムへ行った 
  2. B-PER E-PER O B-ORG I-ORG I-ORG I-ORG I-ORG I-ORG E-ORG B-LOC I-LOC E-LOC O O O

これで簡単なレビューは終わりです。次に、この記事の実践的な部分に移ります。まず、データセットはインターネットから取得します。以下に示すように、サンプルデータを見てみましょう。

train_data のサンプルデータは次のとおりです。

  1. Oが 
  2. シー・オー 
  3. 希望 
  4. 仕事 
  5. チェン・オー 
  6. 保存O  
  7. ヘルプO  
  8.  
  9. 百O  
  10. 10,000 オー 
  11. 子供 
  12. チェン・オー 
  13. ロングO  
  14. Oから 
  15. おいでよ 
  16. 、O  
  17. セクションO  
  18. ティーチO  
  19. 星澳 
  20. 国O  
  21. ウェイ・オー 
  22. はい 
  23. チェン・オー 
  24.  
  25. 時間 
  26. 、O  
  27. 今日O  
  28. O日目 
  29. はい 
  30. Oを集める 
  31. 隠されたO  
  32. 価格O  
  33. 値O  
  34.  
  35. ブックO  
  36. あなた 
  37. いいえ 
  38. Oを購入 
  39. 、O  
  40. ミン・オー 
  41. O日目 
  42. ただO  
  43. お電話ください 
  44. あなた 
  45. 後悔 
  46. いいえ 
  47. Oが 
  48. イニシャルO  
  49. !お

test_data のサンプルデータは次のとおりです。

  1. ハイO  
  2. Oを上げる 
  3.  
  4. 国O  
  5. メインO  
  6. とO  
  7. 社会 
  8. 意思 
  9. メインO  
  10.  
  11. 2つのO  
  12. ヌードルO  
  13. 旗O  
  14. 旗O  
  15. 、O  
  16. グループO  
  17. 結び目 
  18. オールO  
  19. ボディO  
  20. チェン・オー 
  21. メンバーO  
  22.  
  23. とO  
  24.  
  25. ユニオンO  
  26. システムO  
  27.  
  28. Oに戻る 
  29. 華僑
  30. 、O  
  31. 華僑 
  32. 依存 
  33. 、O  
  34.  
  35. ヤン・オ 
  36.  
  37. 国O  
  38. レザー 
  39. ライフO  
  40.  
  41. ライトO  
  42. ロン・オー 
  43. バイオグラフィー 
  44. システムO  
  45. 、O  
  46. Oの場合 
  47. システムO  
  48. ワンオー 
  49. 祖澳 
  50. 国O  
  51. 、O  
  52. 振動O  
  53. 星澳 
  54. 中型B-LOC  
  55. 華I-LOC  
  56. そしてO  
  57. ニュー 
  58. フォースO  
  59. 熱烈な 
  60. ファイトO  
  61. ;お

トレーニング セットとテスト セットのデータ構造を簡単に理解したら、後続のデータ処理に進むことができます。主な目的は、特徴データを生成することです。コア コードの実装は次のとおりです。

  1. open('test_data.txt', encoding = 'utf-8' ) を f として実行します:  
  2. test_data_list = [one.strip().split('\t') は、f.readlines() 内の one に対して、one.strip() の場合に実行されます]  
  3. open('train_data.txt', encoding = 'utf-8' ) を f として実行します:  
  4. train_data_list = [one.strip().split('\t') は、f.readlines() 内の one に対して、one.strip() の場合に実行されます]  
  5. char_list = [test_data_list 内の 1 つの場合は one[0]] + [train_data_list 内の 1 つの場合は one[0]]  
  6. label_list = [test_data_list 内の 1 つの場合は one[-1]] + [train_data_list 内の 1 つの場合は one[-1]]  
  7. print('char_list_length: ', len(char_list))  
  8. print('label_list_length: ', len(label_list))  
  9. print('char_num: ', len(list(set(char_list))))  
  10. print('label_num: ', len(list(set(label_list))))
  11. 文字数、ラベル数={},{}  
  12. #文字頻度統計 
  13. char_list に 1 つの場合:  
  14. char_count に 1 がある場合:  
  15. char_count[1]+=1  
  16. それ以外:  
  17. char_count[1]=1  
  18. label_list 内の 1 つ:  
  19. label_count に 1 つある場合:  
  20. ラベル数[1]+=1  
  21. それ以外:  
  22. ラベル数[1]=1  
  23. # 頻度の降順で並べ替え 
  24. ソート済みsorted_char = sorted(char_count.items(), key = lambda e:e[1], reverse = True )  
  25. ソート済みsorted_label = sorted(label_count.items(), key = lambda e:e[1], reverse = True )  
  26. #文字IDマッピング関係の構築 
  27. char_map_dict = {}  
  28. ラベルマップディクショナリ= {}  
  29. i が範囲(len(sorted_char))内にある場合:  
  30. char_map_dict[ソートされた文字[i][0]]=i  
  31. char_map_dict[str(i)]=ソートされた文字[i][0]  
  32. i が範囲(len(sorted_label))内にある場合:  
  33. ラベルマップ辞書[ソートされたラベル[i][0]]=i  
  34. label_map_dict[str(i)]=ソートされたラベル[i][0]  
  35. #結果の保存 
  36. open('charMap.json','w') を f として実行します:  
  37. f.write(json.dumps(char_map_dict))  
  38. open('labelMap.json','w') を f として実行します:  
  39. f.write(json.dumps(label_map_dict))

コードは非常に明確で、重要な部分には対応するコメントがあります。ここでは説明しません。中心となるアイデアは、文字またはタグ カテゴリ データを対応するインデックス データにマッピングすることです。ここでは頻度のフィルタリングしきい値を設定していません。実装によっては、1 回だけ表示されるデータをフィルタリングするものもあります。必要に応じて変更できます。

charMap データのサンプルは次のとおりです。

labelMap データのサンプルは次のとおりです。

上記のマッピング データを生成した後、元のテキスト データを変換して計算し、必要な特徴データを生成できます。コア コードの実装は次のとおりです。

  1. X_train、y_train、X_test、 y_test = [],[],[],[]  
  2. #トレーニングデータセット 
  3. i が範囲(len(trainData))内である場合:  
  4. one_sample = [one.strip().split('\t') は trainData[i] 内の 1 つのサンプルです]  
  5. char_list = [O[0] (Oはone_sample内)]  
  6. label_list = [O[1] は one_sample 内の O の場合]  
  7. char_vec = [char_map_dict[char_list[v]] 範囲内のv(len(char_list))]  
  8. label_vec = [label_map_dict[label_list[l]] l が範囲(len(label_list))内にある場合]  
  9. X_train.append(char_vec)  
  10. y_train.append(ラベルベクトル)  
  11. #テストデータセット 
  12. i が範囲(len(testData))内にある場合:  
  13. one_sample = [one.strip().split('\t') は、testData[i] 内の 1 つのサンプルに対して実行されます。
  14. char_list = [O[0] (Oはone_sample内)]  
  15. label_list = [O[1] は one_sample 内の O の場合]  
  16. char_vec = [char_map_dict[char_list[v]] 範囲内のv(len(char_list))]  
  17. label_vec = [label_map_dict[label_list[l]] l が範囲(len(label_list))内にある場合]  
  18. X_test.append(char_vec)  
  19. y_test.append(ラベルベクトル)  
  20. 機能={}  
  21. 特徴['X_train']、特徴['y_train']=X_train、y_train  
  22. 特徴['X_test']、特徴['y_test']=X_test、y_test  
  23. #結果の保存 
  24. open('feature.json','w') を f として実行します:  
  25. f.write(json.dumps(機能))

この時点で、必要な特徴データを取得し、テスト セット データとトレーニング セット データを分割しました。

次に、モデルを構築します。実装を簡素化するために、ネイティブ Tensorflow フレームワークよりも入門レベルが低い Keras フレームワークを使用します。コア コードの実装は次のとおりです。

  1. # データセットをロードする 
  2. open('feature.json') を f として実行します:  
  3. f = json.load (f) です。  
  4. X_train、X_test、y_train、 y_test = F ['X_train']、F['X_test']、F['y_train']、F['y_test']  
  5. #データ整列操作
  6.   X_train = pad_sequences (X_train、 maxlen = max_len = 0 )  
  7. y_train = pad_sequences (y_train、 maxlen = max_len = -1)  
  8. y_train = np.expand_dims (y_train、2)  
  9. X_test = pad_sequences (X_test、 maxlen = max_len = 0 )  
  10. y_test = pad_sequences (y_test、 maxlen = max_len = -1)  
  11. y_test = np.expand_dims (y_test, 2)  
  12. #モデルの初期化、トレーニング 
  13. os.path.exists(saveDir) が存在しない場合は:  
  14. os.makedirs(ディレクトリを保存)  
  15. #モデルの初期化 
  16. モデル=シーケンシャル()
  17. model.add(埋め込み(voc_size, 256, mask_zero = True ))  
  18. モデルを追加します(双方向(LSTM(128, return_sequences = True )))  
  19. model.add(ドロップアウト(レート= 0 .5))  
  20. model.add(Dense(tag_size))  
  21. crf = CRF (タグサイズ、スパースターゲット= True )  
  22. モデルを追加します(crf)  
  23. モデル.要約()
  24. model.compile('adam',損失= crf .loss_function,メトリック= [crf.accuracy])  
  25. # トレーニングとフィッティング 
  26. 履歴=モデル.fit(X_train,y_train,バッチサイズ= 100 ,エポック= 500 ,検証データ=[X_test,y_test])  
  27. モデルを保存(saveDir+'model.h5')  
  28. #モデル構造の可視化 
  29. 試す:
  30.   plot_model(モデル、 to_file = saveDir + "model_structure.png"、 show_shapes = True )  
  31. except 例外を e として:  
  32. print('例外: ', e)
  33.   #結果の視覚化 
  34. plt.clf()  
  35. plt.plot(履歴.履歴['acc'])  
  36. plt.plot(履歴.履歴['val_acc'])  
  37. plt.title('モデルの精度')  
  38. plt.ylabel('精度')  
  39. plt.xlabel('エポック')  
  40. plt.legend(['train','test'], loc = '左上' )  
  41. plt.savefig(saveDir+'train_validation_acc.png')  
  42. plt.clf()  
  43. plt.plot(履歴.履歴['損失'])  
  44. plt.plot(履歴.履歴['val_loss'])  
  45. plt.title('モデル損失')  
  46. plt.ylabel('損失')  
  47. plt.xlabel('エポック')  
  48. plt.legend(['train', 'test'], loc = '左上' )  
  49. plt.savefig(saveDir+'train_validation_loss.png')  
  50. スコア=モデル.evaluate(X_test,y_test,詳細= 0 )  
  51. print("精度: %.2f%%" % (スコア[1]*100))  
  52. モデルmodel_json = model.to_json()  
  53. open(saveDir+'structure.json','w') を f として実行します:  
  54. f.write(モデルjson)  
  55. model.save_weights(saveDir+'weight.h5')  
  56. print('===終了====')

トレーニングが完了すると、構造ディレクトリのファイル構造は次のようになります。

モデル構造図は以下のとおりです。

トレーニング中の精度曲線は次のとおりです。

トレーニング中の損失値曲線は次のとおりです。

トレーニングの計算リソースが比較的大きく、時間も比較的長いため、ここでは単純に 20 回の計算反復を設定しました。実際の状況に応じて、さまざまなニーズを満たすために反復回数を高くしたり低くしたりできます。

簡単な予測例を以下に示します。

この記事の実践はこれで終わりです。後で時間があるときに、さらに詳しく勉強し続けます。皆さんのお役に立てれば幸いです。良い仕事と実りある勉強を祈っています!

<<:  IDC: 人工知能への世界的支出は4年で倍増すると予想

>>:  「AIライター」が人気です!大学生がGPT-3を使って「チキンスープ記事」を書き、大勢の人を騙してテクノロジーの見出しを飾った

ブログ    

推薦する

...

...

ロボット兵士はもはやSFではない

ロボット兵士はまもなく現実のものとなり、戦争作戦の遂行において人間の兵士を支援し、負傷した兵士に医療...

...

Belcorp CIO: AI による IT 研究開発の見直し

多国籍美容企業ベルコープは過去3年間、パンデミック、消費者行動の変化、サプライチェーンの混乱、インフ...

資金調達は引き続き好調:6月の自動運転分野における資金調達活動の概要

近年、自動運転の開発が本格化し、多くの企業や資本が参入しています。こうした背景から、もうすぐ終わる6...

DeepMind がワンクリックで「Mole」言語モデルを起動します。 2,800億のパラメータがSOTAに到達可能

太い眉毛と大きな目を持つ「強化学習の専門家」も、大規模言語モデルに取り組み始めているのでしょうか? ...

OpenAIは、GPT-4の「怠惰」問題を近い将来に修正し、オフライン評価とA/Bテストの後にモデルを更新すると発表した。

IT Homeは12月12日、OpenAIが先週、一部のユーザーから苦情を受けたと報じた。多くのユ...

650億のパラメータ、すべてのパラメータを8つのGPUで微調整可能:Qiu Xipengのチームは大規模モデルの閾値を下げました

大規模モデルに向けて、テクノロジー大手はより大規模なモデルをトレーニングしており、学界はそれらを最適...

人気のLlama 2は1週間で15万回以上ダウンロードされ、誰かがRust実装をオープンソース化した。

数日前、Meta は Llama 2 の無料商用バージョンをリリースし、AI コミュニティに大きなセ...

Google mBERT の秘密を解明: ディープラーニングは人間の言語をどのように処理するのか?

[[384615]]言語機能を備えたディープラーニングシステムは、人々の生活の中で広く利用されてき...

...

買い物客の4分の3がレジなし店舗を試してみたいと考えている

[[418996]]画像ソース: https://pixabay.com/images/id-391...

フェイフェイ・リーのチームの新しい作品: AI 透視眼、障害物を通して見る、そして人体のレンダリングと遮蔽における新たなブレークスルー

人物画像のビデオレンダリングは、AR/VR、映画、医療などの分野で広く使用されています。単眼カメラか...

サム・アルトマンは、AGI が 2030 年までに登場し、GPT-10 の知能が全人類の知能の合計を超えると予測しています。

「人類は2030年までにAGIを開発するかもしれない。」サム・アルトマンは最近のポッドキャストのイ...