J-PlatPat特許検索データで自然言語処理BERTによるFIコードの予測

　科学技術と関連するWebでの情報調査と分析を行っています。
特許データはJ-PlatPatで収集できます。J-PlatPatは独立行政法人工業所有権情報・研修館が提供する特許検索情報サイトです。特許庁のHPからもアクセスすることができます。

https://www.j-platpat.inpit.go.jp/



J-PlatPatでは、検索結果をcsvファイルでダウンロードできます。csvファイルには要約を含めることができます。この要約の文章でFI分類コード推定のための機械学習モデルを作成してみました。FIはIPCを基礎として細展開された日本国特許庁独自の特許分類です。 国内特許文献のサーチキーとして利用されています。新規に特許出願する際に、事前に類似特許を検索するのに使われます。
コーディングは「BERTによる自然言語処理入門ーTransformerを使った実践プログラムー」の第７章マルチラベル文章分類を参考にしました。
https://github.com/stockmarkteam/bert-book/blob/master/Chapter7.ipynb
![Screenshot 2022-10-09 08.49.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481830/3d23ec2e-281b-5de2-4955-3723e1cf7b9b.png)


# J-PlatPat検索条件
検索サイト：　J-PlatPat
検索論理式：　[食用油脂/TI+食用油脂/AB+食用油脂/CL+食用油脂/BI]
        TI：題名、AB:要約、CL：請求項、BI：書誌的事項
日付指定：　公知日、20150101〜
検索件数：　500件　　＊csvにダウンロードできるのは500件まで。超えた場合は、公知年を分けて検索ダウンロードして、結合して分析します。

# 開発環境
Google Colaboratory ノートブック, colab Pro
python
ランタイムのタイプ：　ハードウェア アクセラレータ（GPU)
                   ランタイムの仕様（標準）



# 割り当てられたGPUの確認
筆者はcolab Proを使用しています。接続して割り当てられたGPUの仕様を確認しておきます。
GPUによって機械学習の時間、メモリの容量が変わります。
```python:GPU
!nvidia-smi
```
下記が出力されます。GPUはTesla T4でメモリ容量は15109MiBです。機械学習実行後に再度上記コードを実行すると使用されたメモリが確認できます。
![Screenshot 2022-10-08 13.36.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481830/5431e38c-1ceb-15e5-2568-2ac37ed0fb34.png)

# Install
BERTが実装されているtransormers, 日本語の形態素解析ツールのMeCabが実装されているFugashi, MeCabで形態素解析に用いる辞書ipadic, 機械学習に使うpytorch-lightningをインストールします。
```python:install
!pip install transformers==4.18.0 fugashi==1.1.0 ipadic==1.0.0 pytorch-lightning==1.6.1
```
# Import
```python:import
import pandas as pd
import random
from tqdm import tqdm

import torch
from torch.utils.data import DataLoader
from transformers import BertJapaneseTokenizer, BertModel
import pytorch_lightning as pl

# 日本語の事前学習モデル
MODEL_NAME = 'cl-tohoku/bert-base-japanese-whole-word-masking'
```
# J-PlatPatからダウンロードしたcsvファイルを読み込んでデータフレームに代入
```python:read_csv
df = pd.read_csv('input/特実_国内文献.csv')
print(df.info())
```
# 学習・推定するFIコードをone-hot encodingでリスト作成
500件の検索結果でFIコードランキングの上位３位のA23D9(181/500), A23L27(150/500), A23D7(97/500)について検討しました。
```python:one-hot encoding
A23D9_l = []
A23L27_l = []
A23D7_l = []

for fi in df['FI']:
  #print(fi)
  #print('A23D9' in fi)
  if 'A23D9' in fi:
    encode = 1
  else:
    encode = 0
  A23D9_l.append(encode)

  if 'A23L27' in fi:
    encode = 1
  else:
    encode = 0
  A23L27_l.append(encode)

  if 'A23D7' in fi:
    encode = 1
  else:
    encode = 0
  A23D7_l.append(encode)
```
# BERTによるマルチラベル分類するためのモデルをclassで定義
```python:BERTMaltilabel
class BertForSequenceClassificationMultiLabel(torch.nn.Module):

  def __init__(self, modl_name, num_labels):
    super() .__init__()
    # BertModelのロード
    self.bert = BertModel.from_pretrained(MODEL_NAME)
    # 線形変換を初期化しておく
    self.linear = torch.nn.Linear(
        self.bert.config.hidden_size, num_labels
    )

  def forward(
      self,
      input_ids=None,
      attention_mask=None,
      token_type_ids=None,
      labels=None     
  ):
    # データを入力しBERTの最終層の出力を得る
    bert_output = self.bert(
        input_ids=input_ids,
        attention_mask=attention_mask,
        token_type_ids=token_type_ids)
    last_hidden_state = bert_output.last_hidden_state

    # [PAD]以外のトークンで隠れ状態の平均をとる
    averaged_hedden_state = \
      (last_hidden_state*attention_mask.unsqueeze(-1)).sum(1) \
      / attention_mask.sum(1, keepdim=True)

    # 線形変換
    scores = self.linear(averaged_hedden_state)

    # 出力の形式を整える
    output = {'logits': scores}

    # labelsが入力に含まれていたら、損失を計算し出力する
    if labels is not None:
      loss = torch.nn.BCEWithLogitsLoss() (scores, labels.float())
      output['loss'] = loss

    # 属性でアクセスできるようにする
    output = type('bert_output', (object,), output)

    return output
```
# Pytorch Lightningでファインチューニングするモデルのclass定義
```python:BERTMaltilabel_pl
class BertForSequenceClassificationMultiLabel_pl(pl.LightningModule):

  def __init__(self, model_name, num_labels, lr):
    super() .__init__()
    self.save_hyperparameters()
    self.bert_scml = BertForSequenceClassificationMultiLabel(
        model_name, num_labels=num_labels
    )

  def training_step(self, batch, batch_idx):
    output = self.bert_scml(**batch)
    loss = output.loss
    self.log('train_loss', loss)
    return loss

  def validation_step(self, batch, batch_idx):
    output = self.bert_scml(**batch)
    val_loss = output.loss
    self.log('val_loss', val_loss)

  def test_step(self, batch, batch_idx):
    labels = batch.pop('labels')
    output = self.bert_scml(**batch)
    scores = output.logits
    labels_predicted = ( scores > 0 ).int()
    num_correct = ( labels_predicted == labels ).all(-1).sum().item()
    accuracy = num_correct/scores.size(0)
    self.log('accuracy', accuracy)

  def configure_optimizers(self):
    return torch.optim.Adam(self.parameters(), lr=self.hparams.lr)

checkpoint = pl.callbacks.ModelCheckpoint(
    monitor = 'val_loss',
    mode = 'min',
    save_top_k=1,
    save_weights_only=True,
    dirpath = 'model/',
)

trainer = pl.Trainer(
    gpus=1,
    max_epochs=5,
    callbacks = [checkpoint]
)
```
# トークナイザーとデータローダー
ダウンロードしたcsvの列'要約'の文章には、改行記号などが含まれていますので削除します。
```python:tokenizer&dataloader
tokenizer = BertJapaneseTokenizer.from_pretrained(MODEL_NAME)
# 各データの形式を整える
max_length = 256
dataset_for_loader = []

for i in range(500):
  text = str(df['要約'][i])
  text = text.replace('【要約】\r\n', '')
  text = text.replace('\r\n', '')
  text = text.replace('<BR>', '')

  
  labels = [A23D9_l[i], A23L27_l[i], A23D7_l[i]]
  encoding = tokenizer(
      text,
      max_length=max_length,
      padding='max_length',
      truncation=True
  )
  encoding['labels'] = labels
  encoding = { k: torch.tensor(v) for k, v in encoding.items() }
  dataset_for_loader.append(encoding)

# データセットの分割
random.shuffle(dataset_for_loader)
n = len(dataset_for_loader)
n_train = int(0.6*n)
n_val = int(0.2*n)
dataset_train = dataset_for_loader[:n_train]  # 学習データ
dataset_val = dataset_for_loader[n_train:n_train+n_val]  # 検証データ
dataset_test = dataset_for_loader[n_train+n_val:]  # テストデータ

# データセットからデータローダを作成
dataloader_train = DataLoader(
    dataset_train, batch_size=32, shuffle=True
)
dataloader_val = DataLoader(dataset_val, batch_size=256)
dataloader_test = DataLoader(dataset_test, batch_size=256)
```

# モデル作成と評価　
```python:model&evalu
model = BertForSequenceClassificationMultiLabel_pl(
    MODEL_NAME,
    num_labels=3,   #対象とするFI数
    lr=1e-5
)
trainer.fit(model, dataloader_train, dataloader_val)
test = trainer.test(dataloaders=dataloader_test)
print(f'Accuracy: {test[0] ["accuracy"]:.2f}')
```
```python:出力結果
Accuracy: 0.57
```
3つのFIとものAccuracy(正答率)は0.57前後になりました。各FIのAccuracyは0.8前後でした。参考にするツールとしてはまずまずのようですが、もう少し高めたいですね。
出現頻度の低いFIもありますので、他の評価指標、適合率（Precision）、再現率（Recall）、
F値（F-measure）でも見ておいたほうが良さそうです。

# 要約文章を入力してFIを推定
５つの特許の要約を入力してFIの推定を出力させました。
```python:FI推定
# 入力する文章リスト
text_list = []
for i in range(5):
  text  = str(df['要約'][i])
  text = text.replace('【要約】\r\n', '')
  text = text.replace('\r\n', '')
  text = text.replace('<BR>', '')
  text_list.append(text)

# モデルのロード
best_model_path = checkpoint.best_model_path
model = BertForSequenceClassificationMultiLabel_pl.load_from_checkpoint(best_model_path)
bert_scml = model.bert_scml.cuda()

# データの符号化
encoding = tokenizer(
    text_list,
    padding = 'longest',
    return_tensors = 'pt'
)
encoding = { k: v.cuda() for k, v in encoding.items() }

# BERTへデータを入力し分類スコアを得る
with torch.no_grad():
  output = bert_scml(**encoding)
scores = output.logits
labels_predicted = ( scores > 0).int().cpu().numpy().tolist()

# 結果を表示
for text, label in zip(text_list, labels_predicted):
  print('--')
  print(f'入力 : {text}')
  print(f'出力 : {label}')
```
リストはA23D9, A23L27, A23D7の順です。
1がyes,0がnoです。
```python:出力結果
入力 : 【課題】米飯等の澱粉含有食品の製造時に用いるだけで、澱粉含有食品の風味を損なうことなく、十分な血糖上昇抑制効果を付与することができる、澱粉含有食品の血糖上昇抑制用添加剤及び澱粉含有食品の製造方法を提供する。【解決手段】食用油脂５〜６０％、リゾリン脂質０．１〜１０％、多価アルコール１０〜６５％、及び水分５〜３０％を含有することを特徴とする、澱粉含有食品の血糖上昇抑制用添加剤【選択図】  なし
出力 : [0, 0, 0]
--
入力 : (57)【課題】ベーカリー食品の風味を強く感じさせたり味のコク味を高めたりする作用を有するベーカリー食品用油脂組成物を提供する。さらに、ベーカリー食品用油脂組成物の製造方法、ベーカリー生地、およびベーカリー食品を提供する。【解決手段】本発明のベーカリー食品用油脂組成物は、原料油脂に対しレトルト処理を施すことで得られたレトルト油脂を含有する。【選択図】なし
出力 : [1, 0, 0]
--
入力 : (57)【課題】本発明の目的は、様々な食品に風味付けを行うことができ、当該食品の風味や香りを向上できる風味油の製造方法を提供することである。【解決手段】水分含量が４０〜９０質量％の食品素材を含む食用油脂を一次加熱する一次加熱工程と、前記食用油脂を一次加熱よりも１０℃以上高い温度で二次加熱する二次加熱工程とを含む、風味油の製造方法とする。なお、前記一次加熱工程において、加熱温度は１００〜１２０℃であり、また、前記二次加熱工程において、加熱温度が１３０〜１５０℃であることが好ましい。【選択図】なし
出力 : [0, 0, 0]
--
入力 : (57)【課題】本発明は、複合ベーカリー生地の内生地に使用することで製造時の作業性が良好であり、歩留まりが向上し、さらに焼成された複合ベーカリー製品の腰もちが向上し、複合ベーカリー製品の焼成内生地のソフトさが向上し、焼成内生地から焼成上生地への水分移行を抑制する油脂組成物を提供することを目的とする。【解決手段】上記課題を解決するために、（Ａ）食用油脂中に、（Ｂ）ブランチングエンザイムを含有する複合ベーカリー生地の内生地に使用する油脂組成物を提供する。（Ａ）食用油脂中に、（Ｂ）ブランチングエンザイムを含有する油脂組成物を、複合ベーカリー生地の内生地に使用することで製造時の作業性が良好であり、歩留まりが向上し、さらに焼成された複合ベーカリー製品の腰もちが向上し、複合ベーカリー製品の焼成内生地のソフトさが向上し、焼成内生地から焼成上生地への水分移行を抑制することができる。【選択図】なし
出力 : [0, 0, 0]
--
入力 : (57)【課題】構成脂肪酸総量に占めるα−リノレン酸の含有量が３０質量％以上８０質量％以下であるα−リノレン酸高含有食用油脂を含む油脂組成物の加熱臭を抑制する方法を提供する。【解決手段】α−リノレン酸高含有食用油脂を含む油脂組成物に食用油脂Ａを５０質量％以上８０質量％以下含有させる前記油脂組成物の加熱臭の抑制方法であって、  前記α−リノレン酸高含有食用油脂が構成脂肪酸総量に占めるα−リノレン酸の含有量が３０質量％以上８０質量％以下であり、  前記食用油脂Ａがコーン油および米油から選ばれる一種又は二種である、前記方法である。【選択図】なし
出力 : [1, 0, 0]
```