AIが生成した文章と人間が書いた文章を分類できるのか（初期検証）

# 検証を思い至った経緯
生成AIのブームが起き、ネット上に生成AIを使用して作られたものが増えてきている中で、[モデル崩壊に関する論文][0]を読む機会があった。この論文では、小規模なLLMのモデルを使用することによって再帰的に生成されたものを学習させ続けることによって、出現確率の低いものが低く評価されることによって確率の低い部分が失われていってしまい、モデルの多様性が失われてしまうとしている。
実際のネット上にあるものとしては、人間が作ったものも混在しているため論文の内容は極端な例ではある。しかしながら最近では企業内でもLLMを活用しており、企業内でのデータを使用してファインチューニングをすることによって特化したものを作るといった試みも行われている。一方で、このような取り組みは企業内で蓄積しているデータにもLLMが作成したものが混ざるということであり、長期的にみたときにLLMを使用した取り組みの障害となり得るのではないかと考え、今回LLMで生成したものと人間が作った文章を分類できるかどうかを検証しようと考えた。

# 実施方法の説明
今回の検証を実施するにあたって、最初に障害となったのはデータセットの準備である。人間の作った文章のデータセットは、ネット上で多く出回っているもののAIが作った文章をまとめているものは無く（自分の探した範囲では）また、準備するとしてかなりの時間が必要であると考えられた。そこで今回は検証を2段階で行うことにした。
## 1段階目
運よく、動物の名前を270ほどまとめたJSONを手に入れることができたので、それを使用して人が作成した文章としてはWikipediaのAPIを使用して動物の名前に対してsummaryを取得することにした。AIの生成した文章としては、ChatGPTのAPIを使用して、動物の名前に対してWkipediaを真似て300文字程度で説明するように指示を指示を出し、それによって作成されたものを使用することにした。これらによって、動物の説明に関する文章が人とAIで合わせて540ほど準備できたため、これを10%の検定データと90%のトレーニングデータに分て検証することによって、実際にできそうかどうかを検証することにした。
作成に使用したコードに関しては後ほど簡単に紹介し、データに関しては[Git][1]上に公開しています。

## 2段階目
2段階目としては、tensorflow-datasetsのwiki40b/jaというデータセットを使用してwikipediaのタイトルを抽出する。人間側のデータセットとしては、1段階目で使用したコードを使用してタイトルからwikipediaのsummaryを取得して使用する。AI側のデータセットとしては、ChatGPTのAPIを使用した場合コストがとんでもないことになることがわかっていたので、公開されているLLMモデルを使用して文章を生成して使用することにした。使用したモデルは、LINEが公開していたjapanese-large-lm-3.6b-instruction-sftを使用する。これらを使用して、AIと人間でそれぞれ4万件程を準備する予定であるがかなりの時間がかかるため、最初はスモールな検証から進めていく。

# データの準備
今回、[こちら][2]の動物の名前一覧を使わせていただきデータセットの作成を行なった。
## 人間側データの準備
人間側のデータセットに関しては、動物の名前を検索しそれに対するsummaryをwikipediaのAPIを使用して取得することによって作成した。一部の動物が曖昧さ回避のページになってしまった結果取得できなかったものの、260件ほどのデータが取得できたためこれを使用することにした。データセットに関しては[こちら][3]に保存してある。
```python
import pandas as pd
import json
import sys
import wikipedia

#変換したいJSONファイルを読み込む
df = pd.read_json('animal_name.json')
df = pd.DataFrame(df)

def get_wikipedia_summary(word):
    try:
        # 言語を日本語に設定
        wikipedia.set_lang("jp")

        # 検索ワードを用いて検索
        words = wikipedia.search(word)

        if not words:
            return 
        else:
            #検索ワードがヒットすれば要約を取得
            line = str(wikipedia.summary(words[0])).replace('\n', '')

            return line
    except:
        print(word)
        return 

df['wikipedia_sentence'] = df['animal_name'].apply(lambda x:get_wikipedia_summary(x))
df = df.loc[~df['wikipedia_sentence'].isnull()]
df.to_csv('wikipedia_datasets_animal.csv', index=False)
```

## ChatGPT側データの準備
AI側のデータセットに関しては、ChatGPTのAPIを使用してgpt-3.5-turboを使用して作成した。こちらに関しても、Runtime errorなどが発生したものもあったものの、260程のデータを準備することができた。データに関したは[こちら][4]に保存してある。
```python
import openai
import pandas as pd

api_key = "**********"

def get_chatgpt_responce(api_key, system_sentence, user_sentence):
    try:
        openai.api_key = api_key
        response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo",
            messages=[
                {"role": "system", "content": system_sentence},
                {"role": "user", "content": user_sentence}
            
            ]   
        )

        return response["choices"][0]["message"]["content"].replace('\n', '')

    except Exception as e:
        print(user_sentence)
        print(e)
        return 

df = pd.read_json('animal_name.json')
df = pd.DataFrame(df)

system_sentence = "これから動物の名前を入力するので、その動物に関してwikipediaの概要を真似て300文字程度で説明してください。"
df['chatGPT_sentence'] = df['animal_name'].apply(lambda x:get_chatgpt_responce(api_key, system_sentence, x))
df.to_csv('chatgpt_datasets_animal.csv', index=False)
```

# ファインチューニングの実行
今回モデルに関しては、[bert-base-japanese-whole-word-masking][5]を使用して用意したデータの10%を検証データ、90%をトレーニングデータに使用して実施した。
以下に今回実施した際のコードを紹介していく。環境についてはgoogle colab　proを使用して実行した。
```python
!pip install -q git+https://github.com/huggingface/transformers
!pip install -q  sentencepiece
!pip install -q mecab-python3
!pip install -q fugashi
!pip install -q ipadic
!apt install aptitude swig
!aptitude install mecab libmecab-dev mecab-ipadic-utf8 git make curl xz-utils file -y

import pandas as pd
import torch

device = "cuda" if torch.cuda.is_available() else "cpu"
```
transformersの他にも、内部処理としてMeCabが必要なため必要なものをインストールしている。また、GPUを使用するためにdeviceをcudaに設定している

```python
from transformers import BertJapaneseTokenizer
# tokenizerを設定
tokenizer = BertJapaneseTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking')

input_ids = []
attention_masks = []

# 1文づつ処理
for sent in sentences:
    encoded_dict = tokenizer.encode_plus(
                        sent,
                        add_special_tokens = True,
                        max_length =512,
                        pad_to_max_length = True,
                        return_attention_mask = True,
                        return_tensors = 'pt',
                   )

    # 単語IDを取得
    input_ids.append(encoded_dict['input_ids'])

    # Attention　maskの取得
    attention_masks.append(encoded_dict['attention_mask'])

# リストに入ったtensorを縦方向（dim=0）へ結合
input_ids = torch.cat(input_ids, dim=0)
attention_masks = torch.cat(attention_masks, dim=0)

# tenosor型に変換
labels = torch.tensor(labels)
```
tokenizerに関しても、bert-base-japanese-whole-word-maskingのものを使用している。max_lengthに関しては、モデルのinput可能な最大の512を選択している。

```python
from torch.utils.data import TensorDataset, random_split
from torch.utils.data import DataLoader, RandomSampler, SequentialSampler

# データセットクラスの作成
dataset = TensorDataset(input_ids, attention_masks, labels)

# trainデータ、validationデータに分割
train_size = int(0.9 * len(dataset))
val_size = len(dataset) - train_size
train_dataset, val_dataset = random_split(dataset, [train_size, val_size])

# データローダーの作成
batch_size = 32

# 訓練データローダー
train_dataloader = DataLoader(
            train_dataset,
            sampler = RandomSampler(train_dataset),
            batch_size = batch_size
        )

# 検証データローダー
validation_dataloader = DataLoader(
            val_dataset,
            sampler = SequentialSampler(val_dataset),
            batch_size = batch_size
        )
```
準備したデータセットを、分割しそれぞれでデータローダーを作成している。

```python
from transformers import BertForSequenceClassification, AdamW, BertConfig

# BertForSequenceClassificationのロード
model = BertForSequenceClassification.from_pretrained(
    "cl-tohoku/bert-base-japanese-whole-word-masking",
    num_labels = 2,
    output_attentions = False,
    output_hidden_states = False
)

# モデルをGPUに転送
if device == 'cuda':
  model.cuda()

# optimizerの設定
optimizer = AdamW(model.parameters(), lr=2e-5)
```
BertForSequenceClassificationの訓練済みモデルを設定し、モデルをGPUに設定optimizerとしてAdamWを設定している。

```python
# 訓練の処理の定義
def train(model):
    model.train()
    train_loss = 0
    for batch in train_dataloader:
        b_input_ids = batch[0].to(device)
        b_input_mask = batch[1].to(device)
        b_labels = batch[2].to(device)
        optimizer.zero_grad()
        loss= model(b_input_ids,
                             token_type_ids=None,
                             attention_mask=b_input_mask,
                             labels=b_labels).loss
        loss.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
        optimizer.step()
        train_loss += loss.item()
    return train_loss

# テストの処理の定義
def validation(model):
    model.eval()
    val_loss = 0
    with torch.no_grad():
        for batch in validation_dataloader:
            b_input_ids = batch[0].to(device)
            b_input_mask = batch[1].to(device)
            b_labels = batch[2].to(device)
            with torch.no_grad():
                loss = model(b_input_ids,
                                    token_type_ids=None,
                                    attention_mask=b_input_mask,
                                    labels=b_labels).loss
            val_loss += loss.item()
    return val_loss
```
訓練、検証それぞれのプロセスを定義している。両者で違う点は、検証側では```model.eval()```にしており、```torch.no_grad()```で勾配を計算しないように設定している。

```python
max_epoch = 4
train_loss_ = []
test_loss_ = []

for epoch in range(max_epoch):
    train_ = train(model)
    test_ = train(model)
    train_loss_.append(train_)
    test_loss_.append(test_)
```
ここでmax_epochを4としてモデルのtrainを行なっている。
```python
model.eval()
for batch in validation_dataloader:
    b_input_ids = batch[0].to(device)
    b_input_mask = batch[1].to(device)
    b_labels = batch[2].to(device)
    with torch.no_grad():
        preds = model(b_input_ids,
                            token_type_ids=None,
                            attention_mask=b_input_mask)
print(f'出力:{preds}')
```
最終的に、validationのデータを使用して予測結果を出力している。

# 結果について考察と今後の検証
今回の検証において、用意したデータを使用して何度かカーネルを再起動してデータのsplitをランダムに実施したが、いずれの場合においても用意したデータに関しては、問題なく分類ができていた。しかしながら、以下の4つの文章を持ってきたうえで分類を実施してみたところ出力するたびに予測結果が変わるといった結果となった。
```python
sentence_list_human = [
    '有史以前は太平洋を渡ってやってきたポリネシア人たちが持ち込んだ伝統を守りつつ生活を営んでいたが、1778年のジェームズ・クックによる「発見」以降、ハワイは近代化の波へ飲み込まれることとなる。島同士の内戦を経てハワイ王国という100年に及ぶ統一国家が確立し、欧米人との接触に伴って社会は急速に変容し始める。19世紀前半より宗教的基盤の確立と経済発展を求めた欧米入植者たちとその末裔は、次第に経済的安定を保障するための政治権力を欲するようになり、その影響は時代を経るにつれて強力なものとなっていった。サトウキビ農園とその交易による莫大な土地と富を手に入れた成功者たちは更なる産業発展を求めて安価な労働力を日本を中心とする様々な地域より大量に呼び込み、ハワイ社会は多くの人種が混合した複雑な文化を育んでいった。白人勢力はやがてハワイ人国家を倒し、近代化の名の下に1900年にはアメリカ合衆国の領土として併合がなされた。さらに戦時下においては東西に台頭したアメリカと日本の確執の余波をまともに受け、太平洋上の重要な軍事拠点として開発が進む一方で、ハワイへ労働者としてやってきた大量の日本人移民は深刻な差別に曝された。現代は観光都市として発展を見せる一方で、開発による環境汚染、歴史遺構の破壊や人口増加による地価・物価の高騰、ハワイ人問題事務局が提唱しているハワイ人による自治権の獲得など、複数の問題を抱えている。ハワイは、その解決の糸口を模索しながら今日に至っている。', # wikipedia[ハワイの歴史]概要
    '哺乳(ほにゅう)綱食肉目イヌ科に属する動物。人間にもっとも早く飼いならされ、家畜にされた種である。イヌ科の動物は、分類学者のコーベットG. B. Corbetらによれば10属35種を数え、オーストラリア区を除く全世界に分布する。オーストラリアにはディンゴが生息するが、これは土着の野生種ではなく、人の移住について南アジア方面から入ったイヌが野生化したものと考えられている。',       # コトバンク[イヌ]
    '狭い意味では，人工語に対して，既存の，民族に固有の言語をいう。自然語ともいう。もっと広い意味では，人工国際語まで含めて人間の言語をさすのに用い，コンピュータのための機械言語に対立させる。',      # コトバンク[自然言語]
    'コンピューターやロボットなどの機械に自動的に概念や行動プログラムを学習させる研究分野。さまざまな分野で多岐にわたる手法が開発されているが，現状では，パラメーター調節などの特殊な場合を除いて，人間が直接知識を与える方法にまさる学習手段は開発されていない。人間が教師役を務め，正解あるいは解答の正誤を提示しながら進める教師ありsupervised学習と，機械が勝手に学習を進める教師なしunsupervised学習がある。'     # コトバンク[機械学習]
    ]
```
以上のことから、少なくとも500件前後のデータにおいては、学習したデータと同じ系統であれば分類することが可能そうではあるということが分かった。一方で、モデルの汎化性能に関しては高くないためデータセットを増やしたうえでの実験を行う必要があると考えられる。


[0]:https://arxiv.org/abs/2305.17493
[1]:https://github.com/N-OKAMOTO1031/datasets/tree/main
[2]:https://gist.github.com/uneco/5856580
[3]:https://github.com/N-OKAMOTO1031/datasets/blob/main/detect_AI_generated_sentences/initial_verification/wikipedia_datasets_animal.csv
[4]:https://raw.githubusercontent.com/N-OKAMOTO1031/datasets/main/detect_AI_generated_sentences/initial_verification/chatgpt_datasets_animal.csv
[5]:https://huggingface.co/cl-tohoku/bert-base-japanese-whole-word-masking