[[339715]] テキスト分割、品詞タグ付け、固有表現認識は、自然言語処理の分野では非常に基本的なタスクです。それらの精度が下流のタスクの精度を決定します。実は、私はこれまで固有表現認識の仕事にあまり触れたことがありませんでした。大学院時代には断続的にそのようなプロジェクトに参加していましたが、卒業後は表面的な理解しかしていないと感じていました。最近、再び取り組んで、実践を主な学習手段として、固有表現認識などのタスクを比較的体系的に理解、学習、適用したいと思っています。 今日のアプリケーションには、ディープラーニングが関与しないタスクはほとんどありません。多くのサブタスクの開発履歴は驚くほど似ています。当初、研究とアプリケーションのほとんどは機械学習の分野に集中していました。その後、ディープラーニングモデルの開発に伴い、広く使用されるようになりました。名前付きエンティティ認識などのシーケンスラベリングタスクも当然例外ではありません。LSTM + CRFに基づくディープラーニングエンティティ認識に関する関連研究はありましたが、以前の方向性と一致しなかったため、あまり注意を払っていませんでした。最近、たまたまNERを学んでいました。以前の関連記事では、機械学習手法に基づいて簡単な名前付きエンティティ認識を実践しました。ここでは、ディープラーニングモデルに基づいてNERを実装しています。 名前付きエンティティの認識は、シーケンスのラベル付けタスクに属しますが、これは実際には分類タスクに似ています。NER は、テキスト内の定義済みのエンティティ タイプを識別することです。 NER はシーケンス ラベリングの問題であるため、データのラベリング方法もシーケンス ラベリングの問題の方法 (主に BIO と BIOES) に従います。ここではBIOESが直接紹介されています。BIOESを理解すれば、BIOもマスターできます。 まず、BIOES の意味を列挙してみましょう。 - B、つまりBeginは開始を意味します
- Iは中級を意味します。
- EはEndの略で、終わりを意味します
- SはSingleの略で、1文字を意味します。
- O は Other (その他) の略で、無関係な文字をマークするために使用されます。
たとえば、次の文の場合: - ヤオミンはバスケットボールをするためにハルビン工業大学体育館へ行った
注釈結果は次のとおりです。 - ヤオミンはバスケットボールをするためにハルビン工科大学スタジアムへ行った
- 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 のサンプルデータは次のとおりです。 - Oが
- シー・オー
- 希望
- 仕事
- チェン・オー
- 保存O
- ヘルプO
- お
- 百O
- 10,000 オー
- お
- 子供
- チェン・オー
- ロングO
- Oから
- おいでよ
- 、O
- セクションO
- ティーチO
- 星澳
- 国O
- ウェイ・オー
- はい
- チェン・オー
- 風
- 時間
- 、O
- 今日O
- O日目
- はい
- Oを集める
- 隠されたO
- 価格O
- 値O
- お
- ブックO
- あなた
- いいえ
- Oを購入
- 、O
- ミン・オー
- O日目
- ただO
- お電話ください
- あなた
- 後悔
- いいえ
- Oが
- イニシャルO
- !お
test_data のサンプルデータは次のとおりです。 - ハイO
- Oを上げる
- 愛
- 国O
- メインO
- お
- とO
- 社会
- 意思
- メインO
- お
- 2つのO
- ヌードルO
- 旗O
- 旗O
- 、O
- グループO
- 結び目
- オールO
- ボディO
- チェン・オー
- メンバーO
- お
- とO
- お
- ユニオンO
- システムO
- お
- Oに戻る
- 華僑
- 、O
- 華僑
- 依存
- 、O
- お
- ヤン・オ
- 愛
- 国O
- レザー
- ライフO
- お
- ライトO
- ロン・オー
- バイオグラフィー
- システムO
- 、O
- Oの場合
- システムO
- ワンオー
- 祖澳
- 国O
- 、O
- 振動O
- 星澳
- 中型B-LOC
- 華I-LOC
- そしてO
- ニュー
- フォースO
- 熱烈な
- ファイトO
- ;お
トレーニング セットとテスト セットのデータ構造を簡単に理解したら、後続のデータ処理に進むことができます。主な目的は、特徴データを生成することです。コア コードの実装は次のとおりです。 - open('test_data.txt', encoding = 'utf-8' ) を f として実行します:
- test_data_list = [one.strip().split('\t') は、f.readlines() 内の one に対して、one.strip() の場合に実行されます]
- open('train_data.txt', encoding = 'utf-8' ) を f として実行します:
- train_data_list = [one.strip().split('\t') は、f.readlines() 内の one に対して、one.strip() の場合に実行されます]
- char_list = [test_data_list 内の 1 つの場合は one[0]] + [train_data_list 内の 1 つの場合は one[0]]
- label_list = [test_data_list 内の 1 つの場合は one[-1]] + [train_data_list 内の 1 つの場合は one[-1]]
- print('char_list_length: ', len(char_list))
- print('label_list_length: ', len(label_list))
- print('char_num: ', len(list(set(char_list))))
- print('label_num: ', len(list(set(label_list))))
- 文字数、ラベル数={},{}
- #文字頻度統計
- char_list に 1 つの場合:
- char_count に 1 がある場合:
- char_count[1]+=1
- それ以外:
- char_count[1]=1
- label_list 内の 1 つ:
- label_count に 1 つある場合:
- ラベル数[1]+=1
- それ以外:
- ラベル数[1]=1
- # 頻度の降順で並べ替え
- ソート済みsorted_char = sorted(char_count.items(), key = lambda e:e[1], reverse = True )
- ソート済みsorted_label = sorted(label_count.items(), key = lambda e:e[1], reverse = True )
- #文字IDマッピング関係の構築
- char_map_dict = {}
- ラベルマップディクショナリ= {}
- i が範囲(len(sorted_char))内にある場合:
- char_map_dict[ソートされた文字[i][0]]=i
- char_map_dict[str(i)]=ソートされた文字[i][0]
- i が範囲(len(sorted_label))内にある場合:
- ラベルマップ辞書[ソートされたラベル[i][0]]=i
- label_map_dict[str(i)]=ソートされたラベル[i][0]
- #結果の保存
- open('charMap.json','w') を f として実行します:
- f.write(json.dumps(char_map_dict))
- open('labelMap.json','w') を f として実行します:
- f.write(json.dumps(label_map_dict))
コードは非常に明確で、重要な部分には対応するコメントがあります。ここでは説明しません。中心となるアイデアは、文字またはタグ カテゴリ データを対応するインデックス データにマッピングすることです。ここでは頻度のフィルタリングしきい値を設定していません。実装によっては、1 回だけ表示されるデータをフィルタリングするものもあります。必要に応じて変更できます。 charMap データのサンプルは次のとおりです。 labelMap データのサンプルは次のとおりです。 上記のマッピング データを生成した後、元のテキスト データを変換して計算し、必要な特徴データを生成できます。コア コードの実装は次のとおりです。 - X_train、y_train、X_test、 y_test = [],[],[],[]
- #トレーニングデータセット
- i が範囲(len(trainData))内である場合:
- one_sample = [one.strip().split('\t') は trainData[i] 内の 1 つのサンプルです]
- char_list = [O[0] (Oはone_sample内)]
- label_list = [O[1] は one_sample 内の O の場合]
- char_vec = [char_map_dict[char_list[v]] 範囲内のv(len(char_list))]
- label_vec = [label_map_dict[label_list[l]] l が範囲(len(label_list))内にある場合]
- X_train.append(char_vec)
- y_train.append(ラベルベクトル)
- #テストデータセット
- i が範囲(len(testData))内にある場合:
- one_sample = [one.strip().split('\t') は、testData[i] 内の 1 つのサンプルに対して実行されます。
- char_list = [O[0] (Oはone_sample内)]
- label_list = [O[1] は one_sample 内の O の場合]
- char_vec = [char_map_dict[char_list[v]] 範囲内のv(len(char_list))]
- label_vec = [label_map_dict[label_list[l]] l が範囲(len(label_list))内にある場合]
- X_test.append(char_vec)
- y_test.append(ラベルベクトル)
- 機能={}
- 特徴['X_train']、特徴['y_train']=X_train、y_train
- 特徴['X_test']、特徴['y_test']=X_test、y_test
- #結果の保存
- open('feature.json','w') を f として実行します:
- f.write(json.dumps(機能))
この時点で、必要な特徴データを取得し、テスト セット データとトレーニング セット データを分割しました。 次に、モデルを構築します。実装を簡素化するために、ネイティブ Tensorflow フレームワークよりも入門レベルが低い Keras フレームワークを使用します。コア コードの実装は次のとおりです。 - # データセットをロードする
- open('feature.json') を f として実行します:
- f = json.load (f) です。
- X_train、X_test、y_train、 y_test = F ['X_train']、F['X_test']、F['y_train']、F['y_test']
- #データ整列操作
- X_train = pad_sequences (X_train、 maxlen = max_len 、値= 0 )
- y_train = pad_sequences (y_train、 maxlen = max_len 、値= -1)
- y_train = np.expand_dims (y_train、2)
- X_test = pad_sequences (X_test、 maxlen = max_len 、値= 0 )
- y_test = pad_sequences (y_test、 maxlen = max_len 、値= -1)
- y_test = np.expand_dims (y_test, 2)
- #モデルの初期化、トレーニング
- os.path.exists(saveDir) が存在しない場合は:
- os.makedirs(ディレクトリを保存)
- #モデルの初期化
- モデル=シーケンシャル()
- model.add(埋め込み(voc_size, 256, mask_zero = True ))
- モデルを追加します(双方向(LSTM(128, return_sequences = True )))
- model.add(ドロップアウト(レート= 0 .5))
- model.add(Dense(tag_size))
- crf = CRF (タグサイズ、スパースターゲット= True )
- モデルを追加します(crf)
- モデル.要約()
- model.compile('adam',損失= crf .loss_function,メトリック= [crf.accuracy])
- # トレーニングとフィッティング
- 履歴=モデル.fit(X_train,y_train,バッチサイズ= 100 ,エポック= 500 ,検証データ=[X_test,y_test])
- モデルを保存(saveDir+'model.h5')
- #モデル構造の可視化
- 試す:
- plot_model(モデル、 to_file = saveDir + "model_structure.png"、 show_shapes = True )
- except 例外を e として:
- print('例外: ', e)
- #結果の視覚化
- plt.clf()
- plt.plot(履歴.履歴['acc'])
- plt.plot(履歴.履歴['val_acc'])
- plt.title('モデルの精度')
- plt.ylabel('精度')
- plt.xlabel('エポック')
- plt.legend(['train','test'], loc = '左上' )
- plt.savefig(saveDir+'train_validation_acc.png')
- plt.clf()
- plt.plot(履歴.履歴['損失'])
- plt.plot(履歴.履歴['val_loss'])
- plt.title('モデル損失')
- plt.ylabel('損失')
- plt.xlabel('エポック')
- plt.legend(['train', 'test'], loc = '左上' )
- plt.savefig(saveDir+'train_validation_loss.png')
- スコア=モデル.evaluate(X_test,y_test,詳細= 0 )
- print("精度: %.2f%%" % (スコア[1]*100))
- モデルmodel_json = model.to_json()
- open(saveDir+'structure.json','w') を f として実行します:
- f.write(モデルjson)
- model.save_weights(saveDir+'weight.h5')
- print('===終了====')
トレーニングが完了すると、構造ディレクトリのファイル構造は次のようになります。 モデル構造図は以下のとおりです。 トレーニング中の精度曲線は次のとおりです。 トレーニング中の損失値曲線は次のとおりです。 トレーニングの計算リソースが比較的大きく、時間も比較的長いため、ここでは単純に 20 回の計算反復を設定しました。実際の状況に応じて、さまざまなニーズを満たすために反復回数を高くしたり低くしたりできます。 簡単な予測例を以下に示します。 この記事の実践はこれで終わりです。後で時間があるときに、さらに詳しく勉強し続けます。皆さんのお役に立てれば幸いです。良い仕事と実りある勉強を祈っています! |