数値解析系

2023年8月24日 (木)

GPT-3.5 Turboがファインチューニング可能に、GPT-4も今秋に可能となる予定

ChatGPTが盛り上がってますが、このほど、GPT-3.5 Turboというモデルがファインチューニング可能となった模様です。

GPT-3.5 Turboがカスタマイズ可能に ファインチューニング機能提供 GPT-4も秋対応 - ITmedia NEWS

OpenAIのサイトから無料で使えるのがこの3.5ですが、これを自前の言語データで学習してカスタマイズすることが可能となった模様。
といっても、GPT-APIでの話のようです。
なお、その料金はというと、1000トークンあたり0,008ドル(約1.17円)とのこと。一見すると割安感がありますけど、利用時にもお金がかかることに注意。
なお、GPT-4も今秋にはファインチューニングできるようになるとのアナウンスも出されております。来年頭にはGPT-5なるものが出るとの話もあったので、しばらくはOpenAIの周辺は騒がしくなりそうです。

ところで、そのファインチューニングってどうやるんでしょうね?
今回の新しいモデルではないですが、GPT-3をファインチューニングしてみたという記事が以下にあります。

OpenAI APIのFine-tuningを試してみる | DevelopersIO

この記事の最後に「GPT-3.5でもFine-tuningできるようにならんかな。。。」と書かれてますが、できるようになりましたね。きっと今ごろはこの記事の主様は狂喜乱舞していることでしょう。

当然ですが、データはOpenAIのクラウド上にあげることになります。このため、機密情報などの取り扱いには注意が必要。
でないと、あなたの性癖が世界中に晒されることに・・・

やっぱり、オンプレ環境でのLLMを使う道筋も欲しいところですね。

そういえば今日は名古屋に行ってきたついでに、本屋を巡ってきました。
相変わらず、ChatGPTの本が多いですね。
ところが、その中にホリエモンのChatGPT本なんてものがありました。どんな内容なのか?気になるところです。

ChatGPTブームは、しばらく続きそうですよね。


堀江貴文のChatGPT大全 (幻冬舎単行本)

2023年8月18日 (金)

東大 松尾研究室製の大規模言語モデル「Weblab-10B」が公開されたので試してみました

本日の朝、東大の松尾研究室で開発された大規模言語モデル(LLM)である「Weblab-10B」が公開されました。

100億パラメータサイズ・日英2ヶ国語対応の大規模言語モデル“Weblab-10B”をオープンソースで公開しました。 – 東京大学松尾研究室 – Matsuo Lab

日本の大学で開発されたものなので、日本語にも対応(正確には、日英に対応とのこと)。パラメータ数は100億。

2つのモデルがあり、ベースとなるモデル「Weblab-10B」と、Alpacaなどのデータセットによる事後学習(ファインチューニング)を行った「Weblab-10B-instruction-sft」の2つです。
なお、前者のベースモデルは与えた文章の続きを書き出し、後者の事後モデルでは質問に対して返信することができるモデルとなってます。
ちなみに、jsquadなどの日本語向けのベンチマークスコアによると、「Weblab-10B」が66.38、「Weblab-10B-instruction-sft」が78.78となってます。

以前、このブログ記事(りんなチャットAI「rinna japanese-gpt-neox-3.6b」と楽しく??会話してみたら……: EeePCの軌跡)でも試したりんなAI「rinna japanese-gpt-neox-3.6b」が55.17とのことなので、相当賢いAIモデルであることが分かるかと思います。

一つ、注意点ですが、この「Weblab-10B」、「Weblab-10B-instruction-sft」共に商用利用不可とのこと。

個人で試す分には問題ないので、早速試してみました。
なお、コードはそれぞれ、以下のサイトを参考にしてます。

「Weblab-10B」:matsuo-lab/weblab-10b · Hugging Face
「Weblab-10B-instruction-sft」:matsuo-lab/weblab-10b-instruction-sft · Hugging Face

実は、これらのサイトにかかれたサンプルコードではうまく動かなかったので、以下のサイトも参考にしました。

Google Colab で Weblab-10B を試す|npaka

今朝プレスリリースされたばかりなのに、もう試している人がいることに驚きです。

ではまずは、「Weblab-10B」を試します。
以下のコードを「weblab-10b.py」という名前で保存。


import torch
from transformers import AutoTokenizer, AutoModelForCausalLM

tokenizer = AutoTokenizer.from_pretrained("matsuo-lab/weblab-10b")
model = AutoModelForCausalLM.from_pretrained(
    "matsuo-lab/weblab-10b",torch_dtype=torch.float16,device_map="auto"

text
= "吾輩は猫である。"
token_ids = tokenizer.encode(text, add_special_tokens=False, return_tensors="pt")

with torch.no_grad():
    output_ids = model.generate(
        token_ids.to(model.device),
        max_new_tokens=100,
        do_sample=True,
        temperature=0.7,
        top_p=0.9,
    )

output
= tokenizer.decode(output_ids.tolist()[0])
print(output)

これを実行します。

> python weblab-10b.py

コードにあるように「吾輩は猫である。」の文章の続きを書かせてみたんですが、

藪医者の独り言
医療・介護現場での人手不足が深刻化している。厚生労働省の調査によると、2015年の医療・介護の離

と返ってきました。うーん、ちょっと変な文章ですねぇ。
ちなみに、コード内にある「device="auto"」のおかげで、うちの4GBしかないGPU(GTX1650Ti)でもいい感じにGPUを使ってくれました。
が、それでもたった100語の文書を吐き出すのに数分かかってますね。

ベースモデルの動作を確認したので、続いて「Weblab-10B-instruction-sft」を動かしてみます。
コードは以下。「weblab-10b-instruction-sft.py」という名前で保存。


import torch
from transformers import AutoTokenizer, AutoModelForCausalLM

tokenizer = AutoTokenizer.from_pretrained("matsuo-lab/weblab-10b-instruction-sft")
model = AutoModelForCausalLM.from_pretrained(
    "matsuo-lab/weblab-10b-instruction-sft",torch_dtype=torch.float16,device_map="auto")

text = """以下は、タスクを説明する指示です。要求を適切に満たす応答を書きなさい。
### 指示:
大規模言語モデルについて説明してください。

### 応答:
"""
token_ids = tokenizer.encode(text, add_special_tokens=False, return_tensors="pt")

with torch.no_grad():
    output_ids = model.generate(
        token_ids.to(model.device),
        max_new_tokens=256,
        do_sample=True,
        temperature=0.7,
        top_p=0.9,
    )
output = tokenizer.decode(output_ids.tolist()[0])
print(output)

なお、真ん中辺りにある「###指示:」の下に、質問文を書く感じです。
これを実行。

> python weblab-10b-instruction-sft.py

実行時にはメインメモリーが29GB、GPUの4GBは目一杯使ってました。それで、実行時間は約7分。
(初回のモデルダウンロード時間を除く。なお、どちらも最初だけ21.5GBのダウンロードが行われます)

で、以下のようなアウトプットが出てきました。

以下は、タスクを説明する指示です。要求を適切に満たす応答を書きなさい。

### 指示:
大規模言語モデルについて説明してください。

### 応答:
大規模言語モデルは、特定の分野の言語モデルのネットワークであり、これらのネットワークを使用してデータを分析し、言語を理解するために使用することができる。大規模言語モデルは、単語レベルでの言語の分析、または単語レベルでの単語間の関係の分析に使用できる。大規模言語モデルは、言語の複雑さを理解し、言語の多様な側面をよりよく理解するために使用できる。

うん、語数を100から256まで増やしたこともあるんでしょうが、かなりまともな文章ですね。
じゃあ、ちょっと質問を変えてみましょう。「指示:」の後ろを「データドリブン経営について説明してください。」と変えてみました。

で、その結果「応答:」は以下のように出てきます。

データドリブン経営は、データを使用して意思決定を行い、意思決定に基づいて行動することを目指す経営手法です。データドリブン経営では、企業はビジネスの成功に影響を与える可能性のあるデータを収集し、それを分析して、顧客、競合、環境のパターンを理解することができます。これらのパターンを理解することで、企業は将来の成功を予測し、意思決定を行うことができます。

こういう一般論的な文章は、得意みたいですね。さほどおかしなことは言ってません。

これを職場用に向けてファインチューニングしたいところなんですが、商用利用不可なんですよねぇ。
しかも、これをファインチューニングするためのハードウェアってどんなもんがいるんでしょう?想像もつきません。

と、いうことで、なかなかいい感じのモデルだということは分かりました。


ChatGPT&生成AI 最強の仕事術 すぐに仕事で役立つ! AIツール100選

 

2023年8月 6日 (日)

暗い部屋に入ったら不気味な顔が目の前に現れる!的な仕掛けを作った(mediapipeを活用)

なんていうんでしょうか、お化け屋敷的なものを作りたくて、作ってみました。
背景を言いますと、実は今年、自治会の活動に参加しておりまして。
その一環でこの秋に、町の拠点でとあるイベントを行うことになっております。
で、私の関わる役目が防災関係の紹介でして、その中で防災用品が備えられた地価の倉庫を案内する、というものがあります。
が、子供中心のイベントで、ただ暗いだけの倉庫なんて見せて面白いか?
ということになって、何かひと工夫入れることになりました。

で、思いついたのが、mediapipeを使ってなんか面白いものを作れないか?

てことで、手早く動かせるmediapipeを以下からダウンロード。

GitHub - code4fukui/mediapipe-test

こいつの「FaceMesh」というのを使います。ちょっとだけソースをいじって、背景を黒にして、かつ色を少し変えてます。
これを次男用の動画編集PC(全然使われてません)にぶち込んで、そいつにプロジェクターとWebカメラをつなぎます。

Img_3778

これを、我が家のウォークインクローゼットに仕込んでおきました。
で、次男を呼んで、クローゼットの扉を開かせます。

Img_3781

すると・・・天井になにか、奇妙なものが!?

Img_3779

少しでも不気味な感じにするため、目を緑に、顔全体の色をオリーブ色にしてみました。
実物を見ると、顔の動きに合わせてふわふわと浮かんだ不気味な顔が現れたように見えます。

Img_3780

ちなみに、二人同時もいけました。

小学6年生には、大うけでしたね。しばらく遊んでました。

これ、お化け屋敷的にも使えるんじゃないの?できる事なら、Vtuberのように別のグラフィックを連動させたいですよね。


VTuberスタイル 2023年8月号

2023年7月26日 (水)

MT法で音声の異常検知をやってみた

久しぶりに「機械学習」的なネタです。今回、MT法というのを使いました。

「MT法とは何ぞや?」という方は、以下を参照。

MT法で異常検知(python) - Qiita

こっちの動画もおすすめです。

要するにですね、正規分布に従っているデータからの外れっぷりを示す「マハラノビス距離」を使って、そのデータの異常度合を測るという方法で、以下のように使います。

(1) まず、正常データのみをそろえて、そのデータの平均値、分散を計算する。
(2) その平均、分散を使って、未知のデータのマハラノビス距離を計算する。

すると、異常なデータほど、このマハラノビス距離ってやつが大きくなるため、その数値を見て異常発生キターッ!と分かるというわけです。

この手法の特徴としては、正常データの分布を元に予測するため、大量の「正常データ」からごくわずかな「異常データ」の出現を測ることができます。
現実問題として、正常がたくさんで異常が滅多に出ないという製品が多いですから、そういう現場では重宝されます。

なので、まともそうな業者の保険修理件数があれば、ビッ〇モーターの保険修理の異常っぷりも検出できた・・・
いや、それは異常検知以前の問題ですね。

さて、今回はこのMT法を使って「音」の異常を検知してみます。

使った音は、うちの充電式シェーバーで髭を剃る時の音、です。

Img_3560

この何の変哲もない髭剃り機の何の異常を検知するのか?

この充電式のシェーバー、充電したてのころは元気がいいんですが、二週間近くなるとだんだんと電池が切れかかってきたのか、音が明らかに弱ってきます。といっても、毎日聞いていると気づかないのですが、充電した直後に明らかに音が変わるので、すぐに分かります。

つまり、徐々にではあるんですが日々、音が弱くなってきているんだろうな、と。

その衰えっぷりを、このマハラノビス距離ってやつで表現できないかと考えた次第。

てことで、今回用意したのは、充電した直後の1日目のデータ(hige01.wav)と、充電切れかけヤバヤバの12日目のデータ(hige12.wav)を使いました。どちらもだいたい50秒程度のデータ。

で、どうやってこの異常を検知しようか?

方針を決めるためにはまず、データを可視化してみましょう。

Hige01

まずは充電ほやほやの元気のいいシェーバーの音。
周波数は0~5000Hzに絞ってます。
だいたい1500~3000Hzに音の中心が集まっているのが分かります。

Hige12
一方こちらは充電切れ寸前のシェーバーの音データ。

明らかに音圧が減ってますが、ところどころ2000~3000Hzの音がガクッと下がってますね。

ひげを剃ってる間、負荷によってモーターの回転数が下がるみたいで、それがこのまだらな模様の原因となっているものと推測されます。

てことで、なんとなく分かったのは、「1500~3000Hz辺りの音データから特徴を取り出してやれば、シェーバーの充電時期が検知できるんじゃないか?」ということです。

ということで、以下のような方針を立てました。

(1) 1500~3000Hzの音を、250Hz刻みで平均化して出力
(2) その際、1秒ごとに音を分類できるように、IDをつけておく

上のスペクトログラムを各プログラムと合わせて、この(1)、(2)を実行するプログラムを以下に載せておきます。

import os
import sys
import numpy as np
import librosa
import matplotlib.pyplot as plt
import librosa.display
import pandas as pd
 
#音声ファイル読み込み
args = sys.argv
wav_filename = "hige01.wav"

data
, rate = librosa.load(wav_filename, sr=None, mono=False)
# フレーム長
fft_size = 2048
# フレームシフト長
hop_length = int(fft_size * 0.2)
# 時間の刻み幅 (s)
step_time = hop_length / rate
# 周波数の刻み幅 (Hz)
step_freq = rate / fft_size
# 短時間フーリエ変換実行
amplitude = np.abs(librosa.core.stft(data, n_fft=fft_size, hop_length=hop_length))
# 振幅をデシベル単位に変換
log_power = librosa.core.amplitude_to_db(amplitude)
fig_filename = os.path.splitext(os.path.basename(wav_filename))[0]
fft_spect = log_power.T
print(fft_spect.shape)
time_ax = []
freq_ax = []
for i in range(fft_spect.shape[0]):
    time_ax.append(step_time * i)
for j in range(fft_spect.shape[1]):
    freq_ax.append(step_freq * j)
time_ax = np.array(time_ax)
freq_ax = np.array(freq_ax)
df_spect = pd.DataFrame(fft_spect, columns=freq_ax)
df_time = pd.Series(time_ax,name="Time[s]")
df_spect_time = pd.concat([df_time,df_spect],axis=1)
df_spect_time.to_csv(fig_filename + '_pd.csv')
# グラフ表示
librosa.display.specshow(log_power, sr=rate, hop_length=hop_length, x_axis='time', y_axis='hz', cmap='jet')
plt.ylim(0,4000)
plt.xlim(0,10)
plt.colorbar(format='%+2.0f dB')  
plt.title('スペクトログラム' ,fontname="MS Gothic")
plt.savefig("10s_4000Hz_" + fig_filename)
#plt.show()
# データ加工
# 1500~3000Hzを250Hzづつ足して平均化し出力する
start_freq = 1500
interval_freq = 250
wave_feat = []

for k in range(fft_spect.shape[0]):
    tmp_wave = 0
    for l in range(int(interval_freq / step_freq)):
        tmp_wave += fft_spect[k, int(start_freq / step_freq) + l]
        tmp_wave = tmp_wave / (500 / step_freq)
    wave_feat.append(tmp_wave)

start_freq1
= 1750
wave_feat1 = []

for k in range(fft_spect.shape[0]):
    tmp_wave = 0
    for l in range(int(interval_freq / step_freq)):
        tmp_wave += fft_spect[k, int(start_freq1 / step_freq) + l]
        tmp_wave = tmp_wave / (500 / step_freq)
    wave_feat1.append(tmp_wave)

start_freq2 = 2000
wave_feat2 = []

for
k in range(fft_spect.shape[0]):
    tmp_wave = 0
    for l in range(int(interval_freq / step_freq)):
        tmp_wave += fft_spect[k, int(start_freq2 / step_freq) + l]
        tmp_wave = tmp_wave / (500 / step_freq)
    wave_feat2.append(tmp_wave)

start_freq3
= 2250
wave_feat3 = []

for k in range(fft_spect.shape[0]):
    tmp_wave = 0
    for l in range(int(interval_freq / step_freq)):
        tmp_wave += fft_spect[k, int(start_freq2 / step_freq) + l]
        tmp_wave = tmp_wave / (500 / step_freq)
    wave_feat3.append(tmp_wave)

start_freq4 = 2500
wave_feat4 = []

for k in range(fft_spect.shape[0]):
    tmp_wave = 0
    for l in range(int(interval_freq / step_freq)):
        tmp_wave += fft_spect[k, int(start_freq4 / step_freq) + l]
        tmp_wave = tmp_wave / (500 / step_freq)
    wave_feat4.append(tmp_wave)

start_freq5 = 2750
wave_feat5 = []

for k in range(fft_spect.shape[0]):
    tmp_wave = 0
    for l in range(int(interval_freq / step_freq)):
        tmp_wave += fft_spect[k, int(start_freq5 / step_freq) + l]
        tmp_wave = tmp_wave / (500 / step_freq)
    wave_feat5.append(tmp_wave)

time_id_step = 100 # IDをつけるためのID刻み幅
time_id = []

for
i in range(time_ax.shape[0]):
    time_id.append(i // time_id_step)
wave_feat_all = np.array([time_ax, wave_feat,wave_feat1,
         
wave_feat2,wave_feat3,wave_feat4,wave_feat5]).T

df_wave_feat = pd.DataFrame(wave_feat_all, columns=["time",start_freq,start_freq1,
                            start_freq2,start_freq3,start_freq4,start_freq5])
df_id = pd.Series(time_id,name="id")
df_wave_time = pd.concat([df_id,df_wave_feat],axis=1)
# IDで出力範囲指定する
df_wave_time = df_wave_time.query('0< id < 54')
df_wave_time.to_csv(fig_filename + '_feat.csv')
#
df_wave_feat.plot(subplots=True, sharex=True, figsize=(6,12))
plt.savefig("1500-3000Hz_wave_feat_" + fig_filename)

そういえばですが、予めnumpy、matplotlib、pandas、scikit-learn、scipy、seabornなどをpipコマンドでインストールしておいてください。
そしてこのプログラムでは「librosa」というライブラリを使ってます。
上のプログラム、ちょっとやっつけな部分がありますがご容赦ください。このプログラム名を「sound_spect.py」としておきます。
で、これをまず実行。

> python sound_spect.py

これを、10行目の「wave_filename = 」の後ろを「hige01.wav」「hige12.wav」と変えてそれぞれ実行すると、「hige01_feat.csv」「hige12_feat.csv」が出力されます。
なお、ファイルごとに録音時間がバラバラなので、このプログラムでは1~53秒までの音を抜き出すようにしております。

20230724-200718

こんな感じのファイルが作成されます。なお、列名が1500となっているのは、1500Hzから250Hz分の平均値、という意味です。また2列目のIDは、1~53まで変わります。

さて、この時系列データの特徴のとり方というのもいろいろな方法があるんですが、ここではオーソドックスに、

・ 1秒ごとに(つまり同じIDごとに)、1500~2750Hz帯それぞれの平均、最大、最小、標準偏差 を計算する

という方法を取ります。

で、それをやってくれるプログラムが、以下。

import pandas as pd
import numpy as np
def process_timeseries_data(input_file, output_file):
    # CSVファイルを読み込む
    df = pd.read_csv(input_file)
    # idごとにグループ化し、time、No.1、No.2、No.3の平均、最大、最小、標準偏差を計算
    grouped = df.groupby('id').agg({
        'time': 'first',   # 各グループ内の最初のtimeを取得
        '1500': ['mean', 'max', 'min', 'std'],  # No.1の平均、最大、最小、標準偏差を計算
        '1750': ['mean', 'max', 'min', 'std'],  # No.2の平均、最大、最小、標準偏差を計算
        '2000': ['mean', 'max', 'min', 'std'],   # No.3の平均、最大、最小、標準偏差を計算
        '2250': ['mean', 'max', 'min', 'std'],   # No.4の平均、最大、最小、標準偏差を計算
        '2500': ['mean', 'max', 'min', 'std'],   # No.5の平均、最大、最小、標準偏差を計算
        '2750': ['mean', 'max', 'min', 'std'],   # No.6の平均、最大、最小、標準偏差を計算
    })
    # 列名を整理
    grouped.columns = ['_'.join(col) for col in grouped.columns]
    # 出力ファイルに書き込む
    grouped.to_csv(output_file, index=True)
if __name__ == "__main__":
    input_file = "hige01_feat.csv"    # 入力ファイル名を適切に指定してください
    output_file = "output_hige01.csv"  # 出力ファイル名を適切に指定してください
    process_timeseries_data(input_file, output_file)

実はこのコード、考えるのがめんどくさくなって全部ChatGPTに作らせました。なんと、一発で動きましたよ。賢いでちゅね~、ChatGPT。
このプログラムは「spect_feat.py」と名付けます。
これを実行します。

> python spect_feat.py

なお、読み込むファイル名は下から3行目の「input_file =」の後ろ、出力ファイル名はその下の「output_file =」を変えます。

で、これを使って2つの音源ファイルから作った「hige01_feat.csv」「hige12_feat.csv」を読み込ませ、「output_hige01.csv」「output_hige12.csv」を得ます。

20230724-200735

なお、出力されたファイルの中身はこんな感じ。ID、時間に続いて、1500Hz帯の平均、最大、最小、標準偏差・・・という感じに並んでます。

それぞれの周波数帯に対して、4つの値。これが5つあって、かつ時間が53秒分あるため、全部で20×53のデータになります。
元が1025×6000とかいう巨大なデータなので、これでもかなり圧縮されております。

なお、「output_hige01.csv」のデータを「正常データ」、「output_hige12.csv」を「異常データ」とみなして、これをMT法によってより分けられるかを検証していきます。

まずは、この2つのデータをExcel等を使い「id」「time」の2列を消し、上から01、12の順に結合しておきます。ファイル名は、ここでは「hige01_12.csv」としておきます。

このファイルは、1行目に列名、次の53行は「正常データ」、その後ろの53行は「異常データ」ということになってます。

その上で、次のプログラムを実行します。

# 直交表から任意の因子数についての要因効果図を作る
# 使う直交表はL12~L64まで
# パッケージインポート
import os
import re
import collections
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import math
import sklearn
from sklearn import preprocessing
import scipy
from scipy.spatial import distance
import functools
import seaborn as sns
import sys
import tqdm as tq
from tqdm import tqdm
import gzip
import glob
import datetime as dt
import gc
import time
sns.set()

# =========== ユーザー定義 ==================
# データ読み込み
data0 = pd.read_csv('hige01_02.csv', encoding='cp932') # 51番目からは異常判定のあるデータ
# 正常データの行数 53行ある場合は、0始まりの52行目、という意味で「52」と入れる
s_data = 52
# ==========================================

# データを表示(表、グラフ)
print(data0.head())
data0.plot(figsize=(10, 6))
plt.show()

# マハラノビス距離計算前に、まずデータの正規化しておく(平均値0、標準偏差1のデータにする)
ss = preprocessing.StandardScaler()
data1 = ss.fit_transform(data0)
data = pd.DataFrame(data=data1, columns=data0.columns.values)
# 以下、要因効果図作成のため使用する直交表の一覧(2水準系)を定義
# L12 → 因子数が11以下
orthogonal_array_L12 = [
    [1,1,1,1,1,1,1,1,1,1,1],
    [2,1,2,1,1,1,2,2,2,1,2],
    [2,2,1,2,1,1,1,2,2,2,1],
    [1,2,2,1,2,1,1,1,2,2,2],
    [2,1,2,2,1,2,1,1,1,2,2],
    [2,2,1,2,2,1,2,1,1,1,2],
    [2,2,2,1,2,2,1,2,1,1,1],
    [1,2,2,2,1,2,2,1,2,1,1],
    [1,1,2,2,2,1,2,2,1,2,1],
    [1,1,1,2,2,2,1,2,2,1,2],
    [2,1,1,1,2,2,2,1,2,2,1],
    [1,2,1,1,1,2,2,2,1,2,2]
    ]
# L32 → 因子数が21以下
orthogonal_array_L32 = [
    [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
    [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2],
    [1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2],
    [1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1],
    [1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2],
    [1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1],
    [1,1,1,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,1,1,1,1],
    [1,1,1,2,2,2,2,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,1,1,1,1,2,2,2,2],
    [1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2],
    [1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1],
    [1,2,2,1,1,2,2,2,2,1,1,2,2,1,1,1,1,2,2,1,1,2,2,2,2,1,1,2,2,1,1],
    [1,2,2,1,1,2,2,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,1,1,2,2,1,1,2,2],
    [1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1],
    [1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2],
    [1,2,2,2,2,1,1,2,2,1,1,1,1,2,2,1,1,2,2,2,2,1,1,2,2,1,1,1,1,2,2],
    [1,2,2,2,2,1,1,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,1,1,2,2,2,2,1,1],
    [2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2],
    [2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1],
    [2,1,2,1,2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,1,2,1,2,2,1,2,1,2,1,2,1],
    [2,1,2,1,2,1,2,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,1,2,1,2,1,2,1,2],
    [2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1],
    [2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2],
    [2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,1,2,1,2,2,1,2,1,2,1,2,1,1,2,1,2],
    [2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,1,2,1,2,2,1,2,1],
    [2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1],
    [2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2],
    [2,2,1,1,2,2,1,2,1,1,2,2,1,1,2,1,2,2,1,1,2,2,1,2,1,1,2,2,1,1,2],
    [2,2,1,1,2,2,1,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,1,2,2,1,1,2,2,1],
    [2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2],
    [2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1],
    [2,2,1,2,1,1,2,2,1,1,2,1,2,2,1,1,2,2,1,2,1,1,2,2,1,1,2,1,2,2,1],
    [2,2,1,2,1,1,2,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,1,2,2,1,2,1,1,2]
    ]
# L64 → 因子数が63以下
orthogonal_array_L64 = [
    [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
    [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2],
    [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2],
    [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
    [1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,1,1,
1,1,1,1,1,1,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2],
    [1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1],
    [1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1],
    [1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,2,
2,2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2],
    [1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,
1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2],
    [1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,2,2,
2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1],
    [1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,1,1,
1,1,2,2,2,2,1,1,1,1,2,2,2,2,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1],
    [1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,
2,2,1,1,1,1,2,2,2,2,1,1,1,1,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2],
    [1,1,1,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,1,1,1,1,1,1,
1,1,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,1,1,1,1],
    [1,1,1,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,1,1,1,1,2,2,
2,2,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,2,2,2],
    [1,1,1,2,2,2,2,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,1,1,1,1,2,2,2,2,1,1,
1,1,2,2,2,2,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,1,1,1,1,2,2,2,2],
    [1,1,1,2,2,2,2,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,1,1,1,1,2,2,2,2,2,2,
2,2,1,1,1,1,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,2,2,2,2,1,1,1,1],
    [1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,
2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2],
    [1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,2,2,
1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1],
    [1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,1,1,
2,2,1,1,2,2,1,1,2,2,1,1,2,2,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1],
    [1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,
1,1,2,2,1,1,2,2,1,1,2,2,1,1,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2],
    [1,2,2,1,1,2,2,2,2,1,1,2,2,1,1,1,1,2,2,1,1,2,2,2,2,1,1,2,2,1,1,1,1,
2,2,1,1,2,2,2,2,1,1,2,2,1,1,1,1,2,2,1,1,2,2,2,2,1,1,2,2,1,1],
    [1,2,2,1,1,2,2,2,2,1,1,2,2,1,1,1,1,2,2,1,1,2,2,2,2,1,1,2,2,1,1,2,2,
1,1,2,2,1,1,1,1,2,2,1,1,2,2,2,2,1,1,2,2,1,1,1,1,2,2,1,1,2,2],
    [1,2,2,1,1,2,2,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,1,1,2,2,1,1,2,2,1,1,
2,2,1,1,2,2,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,1,1,2,2,1,1,2,2],
    [1,2,2,1,1,2,2,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,1,1,2,2,1,1,2,2,2,2,
1,1,2,2,1,1,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,2,2,1,1,2,2,1,1],
    [1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,
2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1],
    [1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,2,2,
1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2],
    [1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,1,1,
2,2,2,2,1,1,1,1,2,2,2,2,1,1,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2],
    [1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,2,
1,1,1,1,2,2,2,2,1,1,1,1,2,2,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1],
    [1,2,2,2,2,1,1,2,2,1,1,1,1,2,2,1,1,2,2,2,2,1,1,2,2,1,1,1,1,2,2,1,1,
2,2,2,2,1,1,2,2,1,1,1,1,2,2,1,1,2,2,2,2,1,1,2,2,1,1,1,1,2,2],
    [1,2,2,2,2,1,1,2,2,1,1,1,1,2,2,1,1,2,2,2,2,1,1,2,2,1,1,1,1,2,2,2,2,
1,1,1,1,2,2,1,1,2,2,2,2,1,1,2,2,1,1,1,1,2,2,1,1,2,2,2,2,1,1],
    [1,2,2,2,2,1,1,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,1,1,2,2,2,2,1,1,1,1,
2,2,2,2,1,1,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,1,1,2,2,2,2,1,1],
    [1,2,2,2,2,1,1,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,1,1,2,2,2,2,1,1,2,2,
1,1,1,1,2,2,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,2,2,1,1,1,1,2,2],
    [2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,
1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2],
    [2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,2,1,
2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1],
    [2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,1,2,
1,2,1,2,1,2,1,2,1,2,1,2,1,2,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1],
    [2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,
2,1,2,1,2,1,2,1,2,1,2,1,2,1,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2],
    [2,1,2,1,2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,1,2,1,2,2,1,2,1,2,1,2,1,1,2,
1,2,1,2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,1,2,1,2,2,1,2,1,2,1,2,1],
    [2,1,2,1,2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,1,2,1,2,2,1,2,1,2,1,2,1,2,1,
2,1,2,1,2,1,1,2,1,2,1,2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,1,2,1,2],
    [2,1,2,1,2,1,2,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,1,2,1,2,1,2,1,2,1,2,
1,2,1,2,1,2,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,1,2,1,2,1,2,1,2],
    [2,1,2,1,2,1,2,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,1,2,1,2,1,2,1,2,2,1,
2,1,2,1,2,1,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,2,1,2,1,2,1,2,1],
    [2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,
1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1],
    [2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,2,1,
2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2],
    [2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,1,2,
1,2,2,1,2,1,1,2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2],
    [2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,
2,1,1,2,1,2,2,1,2,1,1,2,1,2,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1],
    [2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,1,2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,1,2,
1,2,2,1,2,1,2,1,2,1,1,2,1,2,1,2,1,2,2,1,2,1,2,1,2,1,1,2,1,2],
    [2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,1,2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,2,1,
2,1,1,2,1,2,1,2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,1,2,1,2,2,1,2,1],
    [2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,1,2,1,2,2,1,2,1,1,2,
1,2,2,1,2,1,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,1,2,1,2,2,1,2,1],
    [2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,1,2,1,2,2,1,2,1,2,1,
2,1,1,2,1,2,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,2,1,2,1,1,2,1,2],
    [2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,
2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1],
    [2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,2,1,
1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2],
    [2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,1,2,
2,1,1,2,2,1,1,2,2,1,1,2,2,1,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2],
  [2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,
1,2,2,1,1,2,2,1,1,2,2,1,1,2,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1],
    [2,2,1,1,2,2,1,2,1,1,2,2,1,1,2,1,2,2,1,1,2,2,1,2,1,1,2,2,1,1,2,1,2,
2,1,1,2,2,1,2,1,1,2,2,1,1,2,1,2,2,1,1,2,2,1,2,1,1,2,2,1,1,2],
    [2,2,1,1,2,2,1,2,1,1,2,2,1,1,2,1,2,2,1,1,2,2,1,2,1,1,2,2,1,1,2,2,1,
1,2,2,1,1,2,1,2,2,1,1,2,2,1,2,1,1,2,2,1,1,2,1,2,2,1,1,2,2,1],
    [2,2,1,1,2,2,1,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,1,2,2,1,1,2,2,1,1,2,
2,1,1,2,2,1,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,1,2,2,1,1,2,2,1],
    [2,2,1,1,2,2,1,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,1,2,2,1,1,2,2,1,2,1,
1,2,2,1,1,2,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,2,1,1,2,2,1,1,2],
    [2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,
2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2],
    [2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,2,1,
1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1],
    [2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,1,2,
2,1,2,1,1,2,1,2,2,1,2,1,1,2,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1],
    [2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,
1,2,1,2,2,1,2,1,1,2,1,2,2,1,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2],
    [2,2,1,2,1,1,2,2,1,1,2,1,2,2,1,1,2,2,1,2,1,1,2,2,1,1,2,1,2,2,1,1,2,
2,1,2,1,1,2,2,1,1,2,1,2,2,1,1,2,2,1,2,1,1,2,2,1,1,2,1,2,2,1],
    [2,2,1,2,1,1,2,2,1,1,2,1,2,2,1,1,2,2,1,2,1,1,2,2,1,1,2,1,2,2,1,2,1,
1,2,1,2,2,1,1,2,2,1,2,1,1,2,2,1,1,2,1,2,2,1,1,2,2,1,2,1,1,2],
    [2,2,1,2,1,1,2,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,1,2,2,1,2,1,1,2,1,2,
2,1,2,1,1,2,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,1,2,2,1,2,1,1,2],
    [2,2,1,2,1,1,2,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,1,2,2,1,2,1,1,2,2,1,
1,2,1,2,2,1,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,2,1,1,2,1,2,2,1]
]

# MDの関数を定義
def get_mean_cov(feat):
    mean = np.mean(feat.astype(float).values, axis=0)
    cov = np.cov(feat.astype(float).values.T)
    return mean, cov
def get_score(feat, mean, cov):
    result = []
    # 分散共分散行列の逆行列を計算
    cov_i = np.linalg.pinv(cov)
    feat = feat.astype(float).values
    for i in range(len(feat)):
        result.append(distance.mahalanobis(feat[i], mean, cov_i))
    return result
# 正常と異常データを分ける
normal_feat = data.iloc[:s_data,:]
anomaly_feat = data.iloc[s_data+1:,:]
print(normal_feat.shape, anomaly_feat.shape)
# 平均、分散を求める
mean, cov = get_mean_cov(normal_feat)
# マハラノビス距離の計算(正常、異常それぞれ)
normal_score = get_score(normal_feat, mean, cov)
anomaly_score = get_score(anomaly_feat, mean, cov)
# 正常、異常のマハラノビス距離をプロット
plt.plot(normal_score)
plt.plot(anomaly_score)
plt.show()

md_ot = []

num = data.shape[1] # データから因子数を取得

# 因子数ごとに使う直交表を選ぶ (最大因子数は127まで)
if num<12:
    orthogonal_array = orthogonal_array_L12
elif num<31:
    orthogonal_array = orthogonal_array_L32
elif num<63:
    orthogonal_array = orthogonal_array_L64
else:
    print("因子数は最大63まで!")
    sys.exit()
k = 0
for c in orthogonal_array:
    b = []
    for i in range(num):
        if c[i]==2:
            b.append(i) # 直交表で「2」の因子を取り除く為のインデックスの配列を作成
    data_orth = data.drop(columns=data.columns[b]) # 直交表で”2”の項目を取り除く
    normal_feat = data_orth.iloc[:s_data,:]     # 正常データ
    anomaly_feat = data_orth.iloc[s_data+1:,:]  # 異常データ
    # 直交表によって選択された因子のみでマハラノビス計算用の平均、分散を計算
    mean, cov = get_mean_cov(normal_feat)
   
    # 上の平均、分散を用いて、すべての異常値についてのマハラノビス距離を計算
    anomaly_score = get_score(anomaly_feat, mean, cov)
    # 異常値のマハラノビス距離の平均値を結合
    md_ot.append(sum(anomaly_score)/len(anomaly_score))
sum_md = np.zeros((2,num))
count_md_p = np.zeros(num)
count_md_n = np.zeros(num)
sum_msd = np.zeros((2,num))
# マハラノビス距離計算後の処理:因子ありなしの差を可視化する
k=0
for c in orthogonal_array:
    for i in range(num):
        if c[i]==1:
            sum_md[0,i] += md_ot[k]
            sum_msd[0,i] += 1 / (md_ot[k])**2
            count_md_p[i] +=1
           
        else:
            sum_md[1,i] += md_ot[k]
            sum_msd[1,i] += 1 / (md_ot[k])**2
            count_md_n[i] +=1
    k +=1
# それぞれの因子のあり(posi)、なし(nega)のMD平均値を格納する
posi_mean_md = np.zeros(num)
nega_mean_md = np.zeros(num)
for i in range(num):
    posi_mean_md[i] = sum_md[0,i] / count_md_p[i]
    nega_mean_md[i] = sum_md[1,i] / count_md_n[i]
# 各々の因子有無のマハラノビス距離
print(posi_mean_md)
print(nega_mean_md)
# マハラノビス距離についての要因効果図を頑張って書く
# Y軸の範囲を固定
ymin = min([np.min(posi_mean_md), np.min(nega_mean_md)])*0.95
ymax = max([np.max(posi_mean_md), np.max(nega_mean_md)])*1.05
t = np.linspace(0, 1, 2)
y =  np.zeros((num,2))
fig , axes = plt.subplots(1 , num , figsize=(10,5))
for i in range(num):
    y[i] = [posi_mean_md[i], nega_mean_md[i]]
    axes[i].set_title(data.columns.values[i], fontsize=7)
    axes[i].set_xlim(-0.3, 1.3)
    axes[i].set_ylim(ymin, ymax)
    axes[i].plot(t, y[i], color="black", marker='.',markersize=10, label=str(i+1))
    if i>0:
        axes[i].tick_params(labelleft=False, labelright=False,
labeltop=False, length=0, labelsize=7)
    else:
        axes[i].tick_params(length=0, labelsize=7)
plt.show()

これが、今回の肝となるコードになります。プログラム名を「MT_method.py」としておきます。
これを、例によって実行します。

> python MT_method.py

このプログラムがやっていることは、以下の3つ。

(1) まず最初の53行の「正常データ」を使って、平均、分散を計算する
(2) 続いて、この「正常データ」とその下53行の「異常データ」それぞれのマハラノビス距離を算出しグラフ化
(3)たくさんの因子を使って計算したのですが、その因子の寄与度を求めるために、正常データを使ってその因子の有り無しの平均、分散を計算し、異常値のマハラノビス距離を計算、その距離値がどれくらい変動するかを「0:あり」「1:なし」で比較する

ということをやってます。

特にこの(3)が肝で、プログラムの途中に見える1と2だけの強烈な配列(直交表)は、この寄与度を算出するために入れております。
このプログラム自体は汎用性があって、最大63因子までのMT法と、その寄与度分析が可能となっております。
先日の東京出張は、まさにこれを作るために行ったようなものです。

では、結果を見ていきましょう。まずは(2)のマハラノビス距離の比較です。

Hige_graph02

こんなのが出ました。青線が正常、オレンジ色が異常データのものです。
若干ラップしてますが、大体5のところに線を引けば、概ね分けられます。

一般にMT法では、マハラノビス距離が4で分ける、というのが定番ですけど、正常データで4を超えているところが多過ぎて・・・あまりいいデータじゃないってことですかね。

で、このマハラノビス距離の寄与が大きい因子を見るため、(3)を実行した結果が以下。

Hige_graph03

ここでは「要因効果図」と呼びますが、この図では後ろから4つ目、5つ目が大きく変動してますね。これはそれぞれ、「2500_std」「2750_mean」、つまり「2500~2750Hzの標準偏差」と「2750~3000Hzの平均値」がマハラノビス距離に対して効いていることを示しています。

Hige_graph04

試しにこの二つを縦横軸にしてプロットしてみると、御覧の通り、この2軸だけでまあまあ正常/異常が分離できてます。
他の因子を縦横にしても、この青とオレンジが結構入り乱れてました。

最初の可視化の時点で、3000Hz付近の音の揺れの違いがあるとみていたので、これはまあ納得の結果、といったところでしょうか。

という具合に、音の異常を身近なもので実験してみました。

もっとも、たまたま分離できただけかもしれません。本来ならもっとたくさんのデータを使ったほうが精度が高いと思われます。

正直言いますと、1日目、12日目だけでなく、2日目、3日目・・・のデータもあるんですが、実はどれも似たようなマハラノビス距離の分離っぷりになってしまいました。1日目から見れば、どいつもこいつも「異常」ということに。
なので、シェーバーの音自体というよりも、録音環境(マイクの位置、髭の剃り方の違い、など)の方が効いているのかもしれません。

また、時系列データの特徴量抽出にはよく「tsfresh」というライブラリが使われます。今回は時系列データの特徴量としては基本の平均、最大、最小、標準偏差だけを使いましたが、tsfreshでは一つの時系列データから779種類の特徴量を作り出してくれます。そちらを使えば、もっといい特徴量が見つかったかも。

まあ、そういうわけで今回の話は、ご参考ということで。


よくわかるMTシステム―品質工学によるパターン認識の新技術

2023年6月 3日 (土)

「深層距離学習」をお手軽??に体験できるプログラムコード書いた

ChatGPTやStable Diffusion等の生成AIが大流行りですが、少し原点に戻って、畳み込みニューラルネットワークな話をします。

といっても、ただの画像認識ではなくて、metric learning、すなわち「距離学習」をやります。
以下、プログラムコードを含めて参考にしたのは、以下のサイト。

【深層距離学習】Center Lossを徹底解説 -Pytorchによる実践あり-|はやぶさの技術ノート

例えば、よく画像分類の初歩で使われる「MNIST」(えむにすと、と読みます)という手書きの数値が大量に集められた画像セットがあります。

20230603-175358

0から9までの10種類の手書きの数字データなんですが、これをいかに正確に分類するかという学習をやるために、いわゆる「畳み込みニューラルネットワーク(CNN)」という手法を用います。

が、これを通常のCNN手法で分類すると、こんな感じになります。

0

全部で10種類のデータがきれいに切り分けられている(つまり、分類が上手くいっている)ことを示している図なのですが、なんだか気持ち悪いところがあります。
それは、原点からしゅーっと伸びたような、妙なデータ分布をしているということ。

こういう広い許容範囲を持っていると、例えば0から9までの数値とは似ても似つかない文字(アルファベットのAなど)を入力しても、強引に0から9に解釈されてしまいます。

0から9までの数値と、それ以外の文字を分けるためには、以下のような感じの分類が望ましいです。

Epoch100

それぞれの数値の分類が、綺麗に分かれています。
こんな具合に、分類の距離を離してやることで分類のブレをなくしてやろうという手法が「距離学習」です。
(かなーり大雑把に説明していますが、そんなようなものだと思ってください)

例えば「1」という手書きの数字が入力されたら、機械学習モデルを通して得られた出力データは、この色のついた点群のどこかに収まるはずです。

逆に数字ではない「A」のような文字が入力されたら、この点群とはまったく別のところにプロットされるはずです。

というのを、実際に体感してみましょう、というプログラムです。

今回のプログラムですが、Jupyter notebookで作成したため、ブツ切りとなってます。
そのままコピーして一つにまとめて動かしていただいても大丈夫なはずです。

まずは、必要なライブラリから。


import os
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
from  torch.utils.data import DataLoader
import torch.optim.lr_scheduler as lr_scheduler
import matplotlib.pyplot as plt
from torchinfo import summary
from torch.autograd.function import Function
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image

%matplotlib inline

pytorchで動かしてます。ちなみにバージョンは1.12.1です。
pytorchのインストール方法は、以下を参照。

Previous PyTorch Versions | PyTorch

他には、numpy、torchinfo、matplotlibが必要です。


epochs = 100 # 繰り返し回数

use_cuda = torch.cuda.is_available() and True
device = torch.device("cuda" if use_cuda else "cpu") # CPUかGPUの自動判別
 
if not os.path.exists("images"):
    # ディレクトリが存在しない場合、ディレクトリを作成する
    os.makedirs("images")

初期値を設定します。GPUが使える環境か否かを、自動判別させてます。


class CenterLoss(nn.Module):
    def __init__(self, num_classes, feat_dim, size_average=True):
        super(CenterLoss, self).__init__()
        self.centers = nn.Parameter(torch.randn(num_classes, feat_dim))
        self.centerlossfunc = CenterlossFunc.apply
        self.feat_dim = feat_dim
        self.size_average = size_average

    def forward(self, label, feat):
        batch_size = feat.size(0)
        feat = feat.view(batch_size, -1)
        # To check the dim of centers and features
        if feat.size(1) != self.feat_dim:
            raise ValueError("Center's dim: {0} should be equal to input feature's \
                            dim: {1}".format(self.feat_dim,feat.size(1)))
        batch_size_tensor = feat.new_empty(1).fill_(batch_size if self.size_average else 1)
        loss = self.centerlossfunc(feat, label, self.centers, batch_size_tensor)
        return loss

class CenterlossFunc(Function):
    def forward(ctx, feature, label, centers, batch_size):
        ctx.save_for_backward(feature, label, centers, batch_size)
        centers_batch = centers.index_select(0, label.long())
        return (feature - centers_batch).pow(2).sum() / 2.0 / batch_size

    def backward(ctx, grad_output):
        feature, label, centers, batch_size = ctx.saved_tensors
        centers_batch = centers.index_select(0, label.long())
        diff = centers_batch - feature
        # init every iteration
        counts = centers.new_ones(centers.size(0))
        ones = centers.new_ones(label.size(0))
        grad_centers = centers.new_zeros(centers.size())

        counts = counts.scatter_add_(0, label.long(), ones)
        grad_centers.scatter_add_(0, label.unsqueeze(1).expand(feature.size()).long(), diff)
        grad_centers = grad_centers/counts.view(-1, 1)
        return - grad_output * diff / batch_size, None, grad_centers / batch_size, None

Center Lossというものを定義します。
通常、画像分類ではSoftmax Lossが最小となるように最適化計算をしますが、これに加えてこのCenter Lossというものを最小化することで、距離学習を行います。

すごくざっくり言うと、0から9までの10種類の手書き数字の出力データ群の中心を離し、それぞれがその中心に寄せるように学習させるためのLoss値ということのようです。だからCenter Lossと言うようで。


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1_1 = nn.Conv2d(1, 32, kernel_size=5, padding=2)
        self.prelu1_1 = nn.PReLU()
        self.conv1_2 = nn.Conv2d(32, 32, kernel_size=5, padding=2)
        self.prelu1_2 = nn.PReLU()
        self.conv2_1 = nn.Conv2d(32, 64, kernel_size=5, padding=2)
        self.prelu2_1 = nn.PReLU()
        self.conv2_2 = nn.Conv2d(64, 64, kernel_size=5, padding=2)
        self.prelu2_2 = nn.PReLU()
        self.conv3_1 = nn.Conv2d(64, 128, kernel_size=5, padding=2)
        self.prelu3_1 = nn.PReLU()
        self.conv3_2 = nn.Conv2d(128, 128, kernel_size=5, padding=2)
        self.prelu3_2 = nn.PReLU()
        self.preluip1 = nn.PReLU()
        self.ip1 = nn.Linear(128*3*3, 2)
        self.ip2 = nn.Linear(2, 10, bias=False)

    def forward(self, x):
        x = self.prelu1_1(self.conv1_1(x))
        x = self.prelu1_2(self.conv1_2(x))
        x = F.max_pool2d(x,2)
        x = self.prelu2_1(self.conv2_1(x))
        x = self.prelu2_2(self.conv2_2(x))
        x = F.max_pool2d(x,2)
        x = self.prelu3_1(self.conv3_1(x))
        x = self.prelu3_2(self.conv3_2(x))
        x = F.max_pool2d(x,2)
        x = x.view(-1, 128*3*3)
        ip1 = self.preluip1(self.ip1(x))
        ip2 = self.ip2(ip1)
        return ip1, F.log_softmax(ip2, dim=1)

続いて、ネットワークです。MNISTは28 X 28のモノクロ画像なので、非常に小さいネットワークで構成されてますね。
なお、ip1というのは畳み込みニューラルネットワークから結合層となった直後に出力値を使い、それを二次元化した特徴ベクトル、ip2というのは10次元のベクトルで、Softmaxをかける前の出力ベクトルのようです。


def visualize(feat, labels, epoch):
    c = ['#ff0000', '#ffff00', '#00ff00', '#00ffff', '#0000ff',
         '#ff00ff', '#990000', '#999900', '#009900', '#009999']
    plt.clf()
    for i in range(10):
        plt.plot(feat[labels == i, 0], feat[labels == i, 1], '.', c=c[i])
    plt.legend(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], loc = 'upper right')
    plt.xlim(xmin=-8,xmax=8)
    plt.ylim(ymin=-8,ymax=8)
    plt.text(-7.8,7.3,"epoch=%d" % epoch)
    plt.savefig('./images/epoch=%d.jpg' % epoch)

そしてこのvisualizeという関数は、特徴ベクトルの出力点群をグラフ化し、画像として残すためのアルゴリズムです。最初に出したカラフルな点群グラフは、これで作られてます。


def train(epoch):
    print("Training... Epoch = %d" % epoch)
    ip1_loader = []
    idx_loader = []
    for i,(data, target) in enumerate(train_loader):
        data, target = data.to(device), target.to(device)

        ip1, pred = model(data)
        loss = nllloss(pred, target) + loss_weight * centerloss(target, ip1)

        optimizer4nn.zero_grad()
        optimzer4center.zero_grad()

        loss.backward()

        optimizer4nn.step()
        optimzer4center.step()

        ip1_loader.append(ip1)
        idx_loader.append((target))

    feat = torch.cat(ip1_loader, 0)
    labels = torch.cat(idx_loader, 0)
    visualize(feat.data.cpu().numpy(),labels.data.cpu().numpy(),epoch)
   
    torch.save(model.state_dict(), './mnist.pth')
    return feat, labels

で、こちらは学習用の関数。最後に「mnist.pth」という名前でモデルを保存します。


# Dataset
trainset = datasets.MNIST('./MNIST', download=True,train=True, transform=transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,))]))
train_loader = DataLoader(trainset, batch_size=128, shuffle=True, num_workers=0)

# データセットの一部を可視化
data_iter = iter(train_loader)
images, labels = data_iter.next()
# matplotlib で1つ目のデータを可視化
for idx, _ in enumerate(labels):
    plt.figure(idx+1)    
    npimg = images[idx].numpy()
    npimg = npimg.reshape((28, 28))
    plt.imshow(npimg, cmap='Greens')
    plt.title('Label: {}'.format(labels[idx]))

ここからは、メイン処理に入ります。
初回のみMNISTのデータセットをダウンロードし、「MNIST」というフォルダに入れます。

一応、中身を確認したいと思ったので、一部を表示させてます。

20230603-174110

こんな表示がずらずらっと出てきます。


# Model
model = Net().to(device)

summary(model)

続いてモデル定義です。summaryのところで、ネットワーク図と各層のパラメータ数を表示させてます。

=================================================================
Layer (type:depth-idx) Param #
=================================================================
Net --
├─Conv2d: 1-1 832
├─PReLU: 1-2 1
├─Conv2d: 1-3 25,632
├─PReLU: 1-4 1
├─Conv2d: 1-5 51,264
├─PReLU: 1-6 1
├─Conv2d: 1-7 102,464
├─PReLU: 1-8 1
├─Conv2d: 1-9 204,928
├─PReLU: 1-10 1
├─Conv2d: 1-11 409,728
├─PReLU: 1-12 1
├─PReLU: 1-13 1
├─Linear: 1-14 2,306
├─Linear: 1-15 20
=================================================================
Total params: 797,181
Trainable params: 797,181
Non-trainable params: 0
=================================================================

こんなのが表示されるはずです。


# NLLLoss
nllloss = nn.NLLLoss().to(device) #CrossEntropyLoss = log_softmax + NLLLoss
# CenterLoss
loss_weight = 1
centerloss = CenterLoss(10, 2).to(device)

そして、Loss関数を定義し、


# optimzer4nn
optimizer4nn = optim.SGD(model.parameters(),lr=0.001,momentum=0.9, weight_decay=0.0005)
sheduler = lr_scheduler.StepLR(optimizer4nn,20,gamma=0.8)

# optimzer4center
optimzer4center = optim.SGD(centerloss.parameters(), lr =0.5)

最適化手法を定義したら、


for epoch in range(epochs):
    sheduler.step()
    feat, labels = train(epoch+1)

学習開始です。

100サイクルほど回しましたが、我が家では大体30分ほどかかりました。

20230603-194654

最後にこんな感じの絵が出てくれば、終了です。

一応、各エポックごとの画像もimagesフォルダに出力されます。

Epoch3

3エポック目は、ほとんど未分離だった各分類が、

Epoch22

だんだんと別れ始め、

Epoch58

Epoch100_20230603203901

そして100エポックできれいに分かれました。
これくらい綺麗に分離できていたら、学習は終了です。

学習はここまでですが、ここで自分で作った手書きの画像データを使って、それを「推論」させてみます。

といっても、それぞれの画像が上の点群グラフの中でどのあたりに来るのか?を見るだけです。


def visualize_pred(feat, labels, out_feat):
    c = ['#ff0000', '#ffff00', '#00ff00', '#00ffff', '#0000ff',
         '#ff00ff', '#990000', '#999900', '#009900', '#009999']
    plt.clf()
    for i in range(10):
        plt.plot(feat[labels == i, 0], feat[labels == i, 1], '.', c=c[i])
    plt.plot(out_feat[0],out_feat[1], marker="*", markersize=30, markerfacecolor="r")
    plt.legend(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], loc = 'upper right')
    plt.xlim(xmin=-11,xmax=11)
    plt.ylim(ymin=-11,ymax=11)
    plt.text(-10.8,10.3,"test image")
    plt.savefig('./images/test_pred.jpg')

まずは、推論用の可視化関数を作ります。


test_model = Net()
test_model.load_state_dict(torch.load('./mnist.pth'))
test_model.eval()


# 画像の読み込みと前処理
image = Image.open('test_1.png').convert('L')
transform = transforms.Compose([
    transforms.Resize((28, 28)),
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])
image = transform(image)
image = image.unsqueeze(0)

# 推論の実行
with torch.no_grad():
    output = test_model(image)

output_xy = (output[0].data.cpu().numpy())[0]
print(output_xy[0], output_xy[1])

visualize_pred(feat.data.cpu().numpy(),labels.data.cpu().numpy(), output_xy)

ここで「test_1.jpg」という画像を読み込み、その結果をプロットさせます。
ちなみに「test_1.jpg」という画像は以下。

Test_1

はい、数字の「1」ですね。
これを推論させたら、どうなるか?

Test_pred_1

こうなりました。赤い星が見えるかと思いますが、それが先の画像の推論結果です。
ちなみに、黄色い点群が「1」で、この赤い星印はその黄色い点群の中にいます。
要するに、「1」だと認識されていることが分かります。

Test_aa

それじゃ、気を取り直してこんどは「あ」という文字をぶち込んでみます。
0から9の数字とは似ても似つかないこの文字、果たして結果はどうなるか?

Test_pred_aa

こうなりました。どの点群にも入らず、「1」と「5」の間よりちょっと原点寄りにいますね。
要するに、「その他」と認識されたってことですね。

Test_a

今度は、アルファベットの「A」を入れてみました。

Test_pred_a

うーん、「8」と「3」の間辺り?まあ、「その他」のゾーンですね。

Test_3

しかし、困ったのはこの数字の「3」。

Test_pred_3

うーん、原点から見れば、「3」の集団である水色の点群の先にいるんですが、ちょっと離れすぎちゃいませんかね?
実は、いくつかの手書き数字で確かめたんですが、この「3」のような微妙な結果になることが多かったです。

やや、過学習気味でしたかね?

とにもかくにも、距離学習をイメージできるプログラムができました。

ところで、今はこのCenter Lossと言うのはあまり使われていないようで、もっぱらよく使われるのは「SphereFace」、「CosFace」、「ArcFace」あたりでしょうか。このCenter Lossとはまた違う距離の離し方をしますが、分類ごとの距離を離すよう学習させるという点では同じようなものです。
詳しくは、以下をご参照願います。

モダンな深層距離学習 (deep metric learning) 手法: SphereFace, CosFace, ArcFace - Qiita

ちなみに、この深層距離学習というのは異常検知手法や顔認証のようなもので使われてます。私の場合はArcfaceを使って異常検知させるものをよく作ってます。はい。


物体・画像認識と時系列データ処理入門 [TensorFlow2/PyTorch対応第2版] NumPy/TensorFlow2(Keras)/PyTorchによる実装ディープラーニング

2023年5月25日 (木)

りんなチャットAI「rinna japanese-gpt-neox-3.6b」と楽しく??会話してみたら……

先日紹介した「こっちの方が本命か!?日本語専用生成AI「rinna japanese-gpt-neox-3.6b」: EeePCの軌跡」のコードを、下記サイトを参考に作り替えてみました。

Rinnaの新しい3Bモデルを試してみる - きしだのHatena

ぶっちゃけ言えば、会話できるようにしました。
質問文を打ち込むと、回答を返してくる、さらに質問を受け付ける……というループ処理を入れてあります。

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from colorama import Fore, Back, Style, init

init(autoreset=True)
model_name = "rinna/japanese-gpt-neox-3.6b-instruction-sft"

print
("model:" + model_name)
model = AutoModelForCausalLM.from_pretrained(model_name).to("cpu")
tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast = False)

output = ""
i = 1

while True:
    user_input = input("ユーザー入力" + str(i) + ":")
    if user_input=="":
        break
    #prompt = output + "<NL>ユーザー:" + user_input + "<NL>システム:" # 会話をつなげるやつ
    prompt = "ユーザー:" + user_input + "<NL>システム:" # 独立した質問文を投げるやつ

    inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
    with torch.no_grad():
        tokens = model.generate(
            **inputs,
            max_new_tokens=128,
            do_sample=True,
            temperature=0.8,
            pad_token_id=tokenizer.pad_token_id,
        )
    output = tokenizer.decode(tokens[0], skip_special_tokens=True)
    disp_prompt = prompt.replace("<NL>", "\n")
    disp_output = output.replace("<NL>", "\n")

   
print(f"{Fore.YELLOW}{disp_prompt}{Fore.WHITE}{disp_output[len(disp_prompt):]}")
    i +=1

ただこれ、正確には「会話」ではありません。単発の質問を受け付けるだけのプログラムです。
20行目あたりにある「#promtp = output + ~」というコメントアウトされた行があると思いますが、このコメントアウトを外し、代わりにその下の行をコメントアウトすると、過去の回答履歴をAIモデルに渡せるため、前の文脈を加味した「会話」ができることになります。

が、やってみた限りでは、会話が溜まるとなぜかぐちゃぐちゃな回答しか返ってこなくなるため、敢えて単発質問しか受け付けない仕様に変えました。
プログラム上は一応、会話モードを残しております。

前置きはここまでにして、実際の「会話」、といっても、質問の単発を投げ続けた結果を以下に紹介します。

20230525-074116
よほど支離滅裂(かつ面白くない)なもの以外を、ここに載せます。

【登場人物】
ユーザー:私
システム:りんなちゃん(AI)

ユーザー:大阪にある美味しいお寿司屋さんはどこですか?

最近、海鮮系を食べ歩くYouTuberの動画を見ているので、つい気になって聞いてみました。
が、返ってきたのは以下。

システム:日本食を試してみたい場合は、Washitasをお勧めします。寿司だけでなく、様々な日本食を提供しています。

……Washitasって、何?ググってみましたが、沖縄にあるという「わしたショップオンライン」くらいしか出てきませんでした。しかも、寿司関係ないし。
うーん、寿司はハードル高かったかな。「食べ物」で聞いてみましょう。

ユーザー:大阪でおすすめの食べ物はなんですか?
システム:「お好み焼き」は、野菜、肉、卵がたっぷり入った、日本の料理です。粉や水で溶いた生地を鉄板に敷き、肉、野菜、ソー
スで焼いて仕上げます。ソースは、たっぷりとした甘さのソースと、ピリッとした辛さのソースがあります。大阪では、お好み焼きは人気のある料理です。

おお!なんかそれっぽい答えが導き出せました。
ですが、お好み焼きに関しては、別の場所が本場だという主張もあります。なので、聞いてみた。

ユーザー:お好み焼きは広島の食べ物ではないの?

そういえばですが、この「りんなAI」はどういうわけか、?で終わる文でないと支離滅裂度が上がるみたいです。このため、なるべく質問には?で終わる聞き方をしております。
と、ここまで気を使ったというのに、

システム:5月4日5時45分

もう、意味わかんない。もしかして、聞き方が悪かったかな?
聞き方を変えてみます。

ユーザー:お好み焼きといえば広島ではありませんか?
システム:この質問は少し曖昧ですが、お好み焼きは広島発祥の料理です。

だそうです。うーん。

気を取り直し、今度は名古屋に切り替えます。

ユーザー:名古屋のおすすめの食べ物は何ですか?
システム:アボカド。私はアボカドが大好きです。

し、知らなかった、名古屋ってアボカドが有名……なわけあるかい。
よく見ると「名古屋の」は「名古屋で」にしないと日本語的に変な感じなので、聞き直してみました。

ユーザー:名古屋でお勧めの食べ物は何ですか?
システム:最近、友達が「名古屋で食べられるものをお勧めしてください」と頼まれたので、私の経験からいくつかお教えします。ま ず、味噌と豚 肉が好きな方は、是非味噌とんかつを試してみてください。また、味噌カツもお勧めで

ちょっと余計な言葉が多いものの、概ね正しい答えですね。
ただ一点、突っ込むならば、「味噌とんかつ」と「味噌カツ」が違うもののように書いてますが、これって同じでは?
普通「味噌とんかつ」とは言いませんし。

なんだかごく普通の質問ばかりをしているので、ここで知的レベルを上げてみます。

ユーザー:経営方針を考えるうえでマネージャーが気を付けるべき点は何がありますか?
システム:アッシュ・ハントマンの「The Price is Right」は、マネージャーが考えるべきいくつかの重要な問題を含んでいます。彼は、管理者がマネージャー自身の時間をどのように管理するか、管理者が部下をどのようにマネジメントするか、管理者が従業員の士気を向上させるために何をす るか、管理者が従業員の動機付けやパフォーマンスをどのように向上させるかなど、さまざまな問題に取り組むように勧めています。例えば、アッ シュ・ハントマンは、管理者のためのいくつかのガイドラインを提案したり、次のような質問をして、管理者が自分自身と自分の行動を反省するよ う勧めています。「マネージャーであるあなたは、自分自身の時間の使い方

文章が途切れてしまいましたが、なんだか知的な答えが返ってきた感じです。ただし「アッシュ・ハントマ」でググると、多機能ナイフ(ハントマン)が出てきましたので、微妙にフェイク情報が含まれているのが玉にキズです。

調子に乗って、もう一つ。

ユーザー:経営戦略を考える上で、AIを活用することについてどう思いますか?
システム:今すぐ無料ダウンロードしてスマホやタブレットのための最高のAndroidオペレーティングシステムを手に入れましょう!

なぜ、アンドロイド?りんなちゃんの考えることは、よく分かりません。

ユーザー:AIを効率的に活用する方法は?
システム:「AIは、特定の状況での人間を支援するために設計されています。そのため、AIの助けを借りてユーザーのニーズに対処することができます。人間は、ロボットやAIを含む現代の技術を使用して、人間が生活する環境をより良いものにすることができます。」

めげずにAIについて尋ねたら、なんとなくそれっぽい回答を返してきました。これはまあ、合格点かな。

急にアニメ系の知識が気になったので、それ系の質問をぶつけてみます。

ユーザー:機動戦士ガンダムに出てくるモビルスーツで最高のものは何だと思いますか?
システム:はい、わかりました。それでは見てみましょう...

1. ザクII MS-06R
2. ザクII SBR-M
3. ザクII RGM-79
4. ザクII SBR-M2
5. ドム VAMBR-01
6. ズゴック VAMBR-02
7. ゴッグ VAMBR-02

はい、ザクII推しだということは、よく理解しました。でも、型番があってるのは最初だけ?

ガンダムとくれば、ヤマトでしょう。

ユーザー:宇宙戦艦ヤマトについて何か知ってますか?
システム:「宇宙戦艦ヤマト」は、森雪が率いる架空の艦船である宇宙戦艦ヤマトのシリーズ作品です。シリーズは1977年に始まり、1990年代後半にはより大きな続編である宇宙戦艦ヤマト2が作成されました。シリーズのオリジナルは、SF作家である豊田有恒によって書かれ、後にアニメシリーズ「銀河鉄道999」でも使用されました。「宇宙戦艦ヤマト」は、映画、テレビ、ビデオゲーム、漫画など、多数のメディアフォーマットで利用可能です。シリーズは、現代日本の漫画の代表作の1つとして広く認知されており、批評家からも高く評価されています。

故・松本零士氏が聞いたら、草葉の影から号泣しそうな回答が出てきました。
豊田有恒氏は確かに宇宙戦艦ヤマトに関わっている人ではありますが(「『宇宙戦艦ヤマト』の真実」という本も書かれている)、ここの説明はどう見たって松本零士氏のことでしょう。混乱しています。もちろん、森雪が率いるなんて設定はありません。

ただし、これでもボトムズよりはマシです。

ユーザー:装甲騎兵ボトムズについて何か知ってますか?
システム:やめた方がいいです。あなたは間違っています。

や、やめた方がいいのか……?そんなに間違ってる?むせる。

ユーザー:ボトムズのことは知ってる?
システム:「いいえ、ボトムズは知っています。それは、世界で一番有名なテレビシリーズの一つです。」

さらに聞いてみたら、「世界で一番有名」とか言いやがった。どっちやねん。
でもさすがに世界的には「グレンダイザー」「ボルテスV」よりは知名度低いと思う。(前者はフランス、後者はフィリピンで熱狂ファンがいたとかどうとか)

ユーザー:イデオンのことは知ってる?
システム:はい、知っています。イデオンは、TVシリーズや他のアニメーション作品で、巨大な「異星人」の種族が地球に侵攻する様子を描いたSFスリラー・アニメーション映画シリーズです。イデオンは、宇宙の神話や神々が現実に存在するというストーリーを広めました。

こっちは概ね正確……ではないな。イデオンに出てくる異星人って、地球侵略してたっけ?

ユーザー:ダンバインのことは知ってる?
システム:「ダンバイン」は、日本のアニメシリーズ「ザ・シンプソンズの」の第1シーズンのエピソードです。

ダンバインに至っては、ザ・シンプソンズのシリーズにされてしまった。あれはアメリカのアニメでは?

ユーザー:ザクについて何か知ってますか?
システム:「Call of Duty」のジャンルに属するゲームですか?

ザク推しだったので、ザクについて尋ねてみたら、全然違うゲームを挙げてきました。
りんなよ、お前のザク愛(?)はその程度だったのか。

そういえば、YouTubeで「ねずっちチャンネル」というのを時々見るんですが、そこでSiriやChatGPTなどAIとやり取りする動画があってですね、これが面白かったので。

その「ねずっち」さんについて尋ねてみました。

ユーザー:ねずっちという芸人を知ってますか?
システム:「ねずっち」とは、お笑い芸人で、本名は守屋賢一です。彼は、テレビや雑誌などのメディアで人気を集め、数多くのテレビ番組に出演しています。彼の芸風は、特に滑稽な動きや奇妙な物真似で知られています。彼の主な活動分野は、コメディやジョーク、そして奇妙でおかしな物真 似です。ねずっちは、かつてM-1グランプリの準優勝者でもあり、今でも多くのテレビに出演しています。

うーん、ねずっちさんは物まね芸人だったんだ。なわけあるかい。本名も明らかに違うし。ツッコミどころが多過ぎて困る。

総論として、やはり完成度としてはまだまだですね。
ただ、36億パラメータであることと、中身はともかく文章の体は成しているので、それだけでもすごいことではあると思いました。

家庭用PCで動いていることを思えば、なかなかのものですね。今後に期待しましょう。


2023年5月最新情報 ChatGPT の 応答精度 は プロンプト が9割 テンプレート 付 GPT-4: GPT-3.5 Bing AI Perplexity Notion AI との比較 【Google Bard】【 プロンプトエンジニアリング 】【 copilot 】【 人工知能 】【 チャットボット ... チャットGPT 】【 文章生成AI 】【GPT-4】 おばちゃんでもわかるシリーズ

2023年5月21日 (日)

こっちの方が本命か!?日本語専用生成AI「rinna japanese-gpt-neox-3.6b」

先日の「日本語対応のオンプレ生成AIとしては現時点で最強!?な感じの「OpenCalm 7B」: EeePCの軌跡」という記事を書いている時にも薄々勘づいてました。rinnaというもう一つの日本語専用AIの最新版も公開されるらしい、と。

で、それが出てたので、早速試してみました。
参考サイトはいか。

rinna/japanese-gpt-neox-3.6b-instruction-sft · Hugging Face

日本語特化型の大規模言語モデル、OpenCALMとrinna/japanese-gpt-neox-3.6bの検証 - Qiita

ところでこのrinna/japanese-gpt-neox-3.6ですが、大きく二種類あります。
簡単に言うと、前回紹介した「OpenCalm 7B」のように文章の続きを書くやつ。
そしてもう一つが、質問文→回答 を返すタイプのもの。
正直、文章の続きを書かれてもという気持ちなので、後者の「質問→回答」型のやつをいきなり動かします。

プログラムコードは以下(rinna-3.6b-instruction-sft.py)。

import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
# prompt定義
prompt = [
    {
        "speaker": "ユーザー",
        "text": "AIを有効に活用するにはどうすればいいですか?"
    }
]
prompt = [
    f"{uttr['speaker']}: {uttr['text']}"
    for uttr in prompt
]
prompt = "<NL>".join(prompt)
prompt = (
    prompt
    + "<NL>"
    + "システム: "
)
print(prompt)
# ここまで
tokenizer = AutoTokenizer.from_pretrained("rinna/japanese-gpt-neox-3.6b-instruction-sft", use_fast=False)
model = AutoModelForCausalLM.from_pretrained("rinna/japanese-gpt-neox-3.6b-instruction-sft")
#if torch.cuda.is_available():
#    model = model.to("cuda")
model = model.to("cpu")

token_ids
= tokenizer.encode(prompt, add_special_tokens=False, return_tensors="pt")
with torch.no_grad():
    output_ids = model.generate(
        token_ids.to(model.device),
        do_sample=True,
        max_new_tokens=256,
        temperature=0.7,
        pad_token_id=tokenizer.pad_token_id,
        bos_token_id=tokenizer.bos_token_id,
        eos_token_id=tokenizer.eos_token_id
    )
output = tokenizer.decode(output_ids.tolist()[0][token_ids.size(1):])
output = output.replace("<NL>", "\n")
print(output)

プロンプトの与え方が、ちょっと変わってます。まるでChatGPT APIに受け渡す形式のようですね。
まあ、要するに「"text": 」の後ろに質問文を書けばいいだけです。
また、我が家のGPU環境(GTX1650Ti GPUメモリーは4GB)ではGPUでの動作ができなかったため、model = model.to("cpu")をしております。

なお、うちではモデルダウンロード時にエラーが出ました。以下のライブラリを入れたら、エラーを解消しました。
> pip install sentencepiece

では早速、実行です。
> python rinna-3.6b-instruction-sft.py

20230521-141401
メモリー使用量はMaxで30GB程度。64GBメモリー搭載の我が家のメインPCに死角はない。
ちなみに、モデルダウンロードを除くと、実行から回答出力まで大体2分でした。

で、質問文と得られた回答を並べます。

【質問文】
AIを有効に活用するにはどうすればいいですか?

【回答】
AIは、さまざまなタスクを実行するために設計されたコンピュータプログラムです。
AIは、人間が行うような複雑で詳細なタスクを実行することができますが、通常、人間が行うよりも効率が低くなります。
AIを有効に活用するためには、AIに大量のデータを学習させ、それらのデータを検証して、人間が行うよりも効率が高いかどうかを確認する必要があります。
また、AIがより効率的にタスクを実行するために、特定のタスクに特化したソフトウェアを作成することも可能です。
これらを行うには、AIに専門的な知識や技術が必要であり、人間が行うよりもはるかに高価になることがあります。

うん、なかなかいい感じじゃないでしょうか。3.6B(36億パラメータ)とは思えないほどのいい回答っぷりです。

調子に乗って、少し難易度の高い質問もしてみました。

【質問文】
データドリブン経営を実現するには、まず何から取り組めばよいですか?

【回答】
私は、データドリブン経営を実現するためのさまざまな方法があると思います。以下は、データドリブン経営を実現するための一般的な方法のいくつかです:

- データや事実を分析し、そこから学びを得る
- データを解釈し、その解釈から価値を生み出す
- データから学び、データが示す現実に基づいてアクションを起こす
- データを解釈し、ビジネスモデルや戦略を改善する
- データと事実に基づいて行動し、データから学び、プロセスを改善する

あなたはどのようにデータドリブン経営を実現したいですか?

なぜか、質問に質問を返してきました。
おそらくここでチャット形式に質問を続けると、さらなる答えが引き出される……のかもしれません。この回答自体をプロンプトに加えて、再び質問です。

ということで、プロンプト部分を以下のように変えました。

prompt = [
    {
        "speaker": "ユーザー",
        "text": "データドリブン経営を実現するには、まず何から取り組めばよいですか?"
    },
    {
        "speaker": "システム",

        "text": "私は、データドリブン経営を実現するためのさまざまな方法があると思います。\
            以下は、データドリブン経営を実現するための一般的な方法のいくつかです:\
            - データや事実を分析し、そこから学びを得る\
            - データを解釈し、その解釈から価値を生み出す\
            - データから学び、データが示す現実に基づいてアクションを起こす\
            - データを解釈し、ビジネスモデルや戦略を改善する\
            - データと事実に基づいて行動し、データから学び、プロセスを改善する\
            あなたはどのようにデータドリブン経営を実現したいですか?"
    },
  {
        "speaker": "ユーザー",
        "text": "データと事実に基づいて行動し、データから学び、プロセスを改善することに重きを置きたいと考えてます。"
    }
]

で、返ってきたのはこちら。

【回答2】
素晴らしいです!
そして、データドリブン経営を実現するためには、データから学び、データから学び、プロセスを改善するさまざまな方法があります。
以下にいくつかの例を挙げます:
- データから学び、データを解釈して、現実に基づいてアクションを起こす
- データを使って学び、データを解釈し、プロセスを改善する
- データを解釈し、ビジネスモデルや戦略を改善する
- データを解釈し、プロセスを改善する
- データを解釈し、ビジネスモデルや戦略を改善する

うん、心を広く持てば、一応は会話になっているのかなとも思える回答です。

ということは、このプロンプトの部分をJSON形式のファイルとして書き出し、同じスレのチャット上で過去の会話も残しつつモデルに受け渡すようなプログラムを組んだら、いわゆるチャット型のAIが作れる気がします。

と言いたいところですが、実のところ、同じプロンプトで2、3回動かして、わりとマシな回答となったものをここに載せているため、必ずしもこのクオリティの回答が得られるとは限りません。時々、繰り返し文のようなものを返すこともあります。
これでチャットアプリを作るなら、安定的なパラメータ値が欲しいところです。

ちなみにですが、このjapanese-gpt-neox-3.6bのモデルは「The MIT Lisence」、つまり商用利用は可能なやつのようです。

にしても、今年に入ってからのオープンソースな文章生成AIの勢いが止まりませんね。この調子だと今年の終わりごろには、どうなってるんでしょうか?


週刊東洋経済 2023/4/22号(ChatGPT 仕事術革命)

2023年5月18日 (木)

日本語対応のオンプレ生成AIとしては現時点で最強!?な感じの「OpenCalm 7B」

生成AIというやつはどいつもこいつも英語対応ばかりで、なかなか日本語を顧みてくれません。
が、ようやく日本語対応を謳う生成AIモデルが登場しました。

cyberagent (CyberAgent)

サイバーエージェント社が、つい一昨日に公開されたばかりの生成AIモデルで、日本語版Wikipediaなどをベースに作ったもののようです。
モデルサイズですが、160M~7B(1.6億~68億)パラメータまであるようです。

ということで、まずは最小モデルの「small」(160M)から試してみます。


import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

model = AutoModelForCausalLM.from_pretrained("cyberagent/open-calm-small", device_map="auto", torch_dtype=torch.float16)
tokenizer = AutoTokenizer.from_pretrained("cyberagent/open-calm-small")

inputs = tokenizer("AIによって私達の暮らしは、", return_tensors="pt").to(model.device)
with torch.no_grad():
    tokens = model.generate(
        **inputs,
        max_new_tokens=256,
        do_sample=True,
        temperature=0.7,
        top_p=0.9,
        repetition_penalty=1.05,
        pad_token_id=tokenizer.pad_token_id,
    )

output = tokenizer.decode(tokens[0], skip_special_tokens=True)
print(output)

コードはこんな感じ。元々のサンプルコードに対し、「max_new_tokens=」のところを、64から256に変えてあります。
なお、「inputs = tokenizer("」の後ろに、文章の書き出しを書きます。

これ、先日紹介した「StableLMの日本語版(α版)がテスト公開中: EeePCの軌跡」と同様、質問文ではなく、生成したい文章の書き出しをインプットしてやるという方式のようです。質問をしても、その続きを書かれてしまうので注意。

ただ、StableLMの日本語版がサイト上でしか動かせないのに対し、こちらはオンプレミス環境、つまり自分のPCで動かせます。そこは強みですねぇ。

さて、上のコードを実行すると、こんな感じの文章が返ってきました。

AIによって私達の暮らしは、どんな変化を遂げようとしています。
そんな私たちの生活を支える、私たちの大事な「資源」と「エネルギー」。
「資源」を資源として、わたしたちが大切にしたい「エネルギー」。
私たちは「エネルギー」を、社会から生み出していきたいと考えています。
そのために私たちは、様々な分野の技術や、それらを組み合わせて、様々な

うん、なんだか尻切れしてますが、なんとなくいい感じの文章・・・なのかなぁ。エネルギーの話にすり替えられてます。

他の文章も作らせてみました。

例えば、出だしを「これからの日本の新しい経営の在り方は、」としてみると、

これからの日本の新しい経営の在り方は、どんなものなのか?
これからの時代を生き抜くためには、会社に対する思いや姿勢は問われなければならない。
会社という組織における経営とは、「人」と「物」である。
経営という仕事は、人が人として、あるいは人が組織人として行うものであり、企業経営とは、その企業で働く者が、会社を経営する立場として、あるいは企業で働く者が、組織経営者として、仕事を行う者のことを指す。
そして、その業務に従事する者は、会社の経営を、経営という仕事を通して、自分の考えや判断で成し遂げることが必要である。
社員の皆さんは、会社という組織において、会社という存在意義をどのように認識しているだろうか?

あれ、至極まっとうな感じの答えが返ってきました。
よーく読めば、わりとフワッとした文章なんですけど、それでも体裁は整ってます。悪くないですね。

もう少し、マニアな書き出しで攻めてみます。

「日本のアニメ文化の今後は、」

に対しては、

日本のアニメ文化の今後は、海外展開も視野に入れるべきだ」
「アニメ評論家」がアニメ評論家から干された理由。
「アイドルマスター」はアニメ業界にとって“死語”になる...脚本家・松原智恵子氏の決断の重要性
「ガンダム」で「ガンダム」を語れる人は少ない。
【大投票】アニメ評論家ランキング! 圧倒的支持率のトップ10は?
「ガンダム」で「ガンダム」を語る上で外せないのは、「ガンダム」シリーズの歴史だ。
アニメ評論家ランキング(20)は「ガンダム」に票を投じた人たち。
「ガンダム」は『機動戦士ガンダム』などアニメ史上最も売れた作品となった。その第1作目は、2001年に『機動戦士ガンダム』が放 送され、その人気に陰りが見え始めたころだった。
しかし、第2作目でさらに人気が高まって、第4作目『機動戦士ガンダム00』まで放送された。第5作目『機動戦士ガンダム0080 ポケットの中の戦争』では、第1作目のガンダムから続くアニメ史の幕開けともいえる作品である。
そして、第6作目『機動戦士Zガンダム』では、新三部作の完結編として、新たな物語とモビルスーツの開発が始まろうとしている。
しかし、第7作目『機動戦士ガンダム0083 ジオンの残光』は、テレビアニメ史に残る名作として、その後に続く

後半、めちゃくちゃ「ガンダム」してます。最初の一行目はそれらしいんですけどね。

なんとなく思ったんですが、これ、Wikipediaを教師データにしているらしいので、その中の文章で逸れっぽい部分を抜き出して組み合わせているだけじゃないですかね?

実際にはアレンジしているんでしょうけど、なんとなくそんな感じの文章です。

ということで、やはり最小モデルではちょっと物足りない。

てことで、最大モデルである7Bを試してみます。


import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
import os

os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "garbage_collection_threshold:0.6, max_split_size_mb:128"

model = AutoModelForCausalLM.from_pretrained("cyberagent/open-calm-7b", device_map="auto", torch_dtype=torch.float16)
tokenizer = AutoTokenizer.from_pretrained("cyberagent/open-calm-7b")

inputs = tokenizer("AIによって私達の暮らしは、", return_tensors="pt").to(model.device)
with torch.no_grad():
    tokens = model.generate(
        **inputs,
        max_new_tokens=128,
        do_sample=True,
        temperature=0.7,
        top_p=0.9,
        repetition_penalty=1.05,
        pad_token_id=tokenizer.pad_token_id,
    )

output = tokenizer.decode(tokens[0], skip_special_tokens=True)
print(output)

ほぼさっきのsmallのコードで、「cyberagent/open-calm-small」が「cyberagent/open-calm-7b」に変わっているだけのようですが、

(1) 「os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "garbage_collection_threshold:0.6, max_split_size_mb:128」"を付け加えている
(2) 「max_new_tokens=」の値を256ではなく、128にしてある

という2か所も変わってます。いずれも、4GBのGPUメモリーしか搭載してないうちの環境に合わせてます。

で、実行してみると、やはり7Bなので結構重いです。数分はかかる感じ。それでも、同じStableLM の7B(英語版)よりは早いですかね。
メモリーに関しては、メインメモリーは23GB程度ですが、GPUメモリー4GBを目いっぱい使ってくれちゃってます。

20230518-200357
トークン数を上げてると、この微妙なところでさまよっているメモリー使用量が振り切っちゃうみたいで、強制終了してしまいます。もうちょっとそれなりのGPUが欲しいところですね。

さて、smallと同じ書き出し文3つの結果を、以下に並べてみます。

「AIによって私達の暮らしは、」

AIによって私達の暮らしは、大きく変化しています。
そして今こそ人工知能研究の最前線に立ち向かう研究者達の物語とその先に見える未来をご紹介します。

「これからの日本の新しい経営の在り方は、」

これからの日本の新しい経営の在り方は、今のままではいけないと思っています。私は今までいろいろな仕事を経験してきましたが、仕事を通じて多くの人と知り合うことができました。その人たちから多くの知恵や気づきをもらいました。そして、人の力の素晴らしさを実感しています。これからは、これまで以上に様々な立場の人たちとの出会いを大切にし、互いに協力しあいながら未来を築いていきたいと考えています。そのために、常に世の中の変化に目を向け、変化に対応できる企業でありたいと思っています。そして、お客様や社会の期待に応えるために努力し続けることをお約束いたします。

「日本のアニメ文化の今後は、」

日本のアニメ文化の今後は、海外配信で稼ぐしかない。
『NARUTO』10月5日より新章突入!ついにカカシとサクラの因縁決着か!?うわあああああああ!!
「仮面ライダー電王」はイマジンと契約した時点で勝ち確定だったろ?それって誰得だよ・・・
ガンダムベース限定「νガンダムvsサザビー(宿命の対決セット)」発売開始!涙のラストバトルが再現されるぞ!!!
コピックのマーカーでよく飛びそうとか言われてた奴って何なの...? ←一応コレもアルコール由来ですwwwww

最後のはやっぱり、ガンダムですねぇ。草生えてますし。
トークン数を絞ってるのも悪いのかもしれませんが、短い文章が多いですね。それよりも、最初の2つはまるでコンサルティング会社の売り文句のようになっております。もしかして、拡販用、営業用の文章作成を狙って作られたAIなのかも?

とはいえ、StableLMの日本語版よりは自然な文章が返ってくる感じ。日本の経営云々の話をさせるなら、意外とsmallでも悪くありませんね。AIやアニメと言ったややニッチなは無しよりは、経営という比較的メジャーなキーワードに対して強そうな気がします。

ところでこのOpenCalmですが、ライセンスは「CC-BY-SA 4.0」というやつでした。
こちらのサイトによれば、

【保存版】意外と覚えづらいCCライセンスを一言で簡潔に説明しよう! | degitekunote2

ざっくり言うと「著作者の情報を表示しつつ、元のCCライセンスの継承だけは守ってね。商用利用と改変と再配布はOKだよ。」ということのようでした。著作者の情報表示が必要ですが、商用利用は可能なようです。

これをベースにした転移学習モデルを作れば、そこそこのものができるかもしれません。


Excel×ChatGPTでビジネスが加速する!AI仕事術: 「○○を教えて」だけじゃない!仕事に役立つ50の活用事例 (エクセル兄さん出版)

2023年4月20日 (木)

Stable Diffusionを作ったStability AI社が商用利用も可能で無料の文章生成AI「StableLM」を公開したので使ってみた

2週続けて商用利用可能な無料のLLMが公開されているので、こりゃあ今週もまた出てくるんじゃないのかと冗談で話していたんですが。

まさかまさかの、本当に出てきました。

しかも今度は、あのStable Diffusionを作ったStability AI社による言語モデル「StableLM」です。

Stability AI 言語モデル「StableLM Suite」の第一弾をリリース - (英語Stability AI

今のところ、後悔されているのは30億、70億パラメータのモデル(3B、7B)のみのようですが、いずれ650億パラメータのモデルなんかも出てくる模様。

てことで、早速Githubからこいつのコードを入手して実験。

GitHub - Stability-AI/StableLM: StableLM: Stability AI Language Models

と、行きたかったのですが、ここのコード(Jupyter notebook形式)そのままでは、GPUメモリーが不足してますエラーが出て、まるで動きません。

で、すったもんだした挙句、以下のようなコードを作成。

「stableLM_7B.py」


from transformers import pipeline
import torch
import os

os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "garbage_collection_threshold:0.6, max_split_size_mb:128"

model = "stabilityai/stablelm-tuned-alpha-7b"

text = "How can AI be used effectively?"

pipe = pipeline(model=model,torch_dtype=torch.bfloat16,
                trust_remote_code=True,device_map="auto")
generated_text = pipe(text, max_length=128, do_sample=False, no_repeat_ngram_size=2)[0]
print(generated_text['generated_text'])

一つ工夫しているところがあって、「os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "garbage_collection_threshold:0.6, max_split_size_mb:128"」という行で、GPUメモリーが不足するのを防いでます。

20230420-205302

なお、実行中のタスクマネージャーはこんな感じ。

70億パラメータですから、メモリー的には余裕があります。GPUを除いて。

これでなんとなく動くのですが、毎度の質問文「How can AI be used effectively?」に対しては、以下のような回答が返ってきます。

You can start by defining what AI is. Is it a machine that follows instructions? Is a human mind? How can it be both? What is the difference between a computer program and a general AI? These are some of the questions that need to be answered before we can begin to explore how AI can be useful.

One of AI's biggest challenges is that it is difficult to define what makes an AI "good" or "bad". It is a very subjective question. What makes something "artificial intelligence" is also subjective. Some people consider AI to have no value, while others

(Google翻訳)

AI とは何かを定義することから始めることができます。 指示に従う機械ですか? 人間の心ですか? どうすれば両方になることができますか? コンピュータプログラムと一般的な AI の違いは何ですか? これらは、AI がどのように役立つかを探求し始める前に、答える必要があるいくつかの質問です。

AI の最大の課題の 1 つは、何が AI を「良い」か「悪い」かを定義するのが難しいことです。 これは非常に主観的な質問です。 何かを「人工知能」にするのも主観的なものです。 AIに価値がないと考える人もいれば、

悪くない内容です。が、よく見ていただくと分かりますが、文が途中で終わってます。

おそらく、コード中の「max_length=128」を大きくすれば行けるかもしれませんが、この数値を大きくすると、GPUメモリー不足のエラーで落ちます。

残念ながら、我が家の環境ではこれ以上の数値を受け付けそうにありません。

が、70億パラメータ程度でこの文章。なかなか侮れない結果です。

画像生成AIの方(Stable Diffusion)はもはや画像生成AIの標準とまでなりつつある状況ですが、このStableLMも流行るんでしょうか?

ファインチューニングによる独自のモデルを作れるならば、これも流行りそうです。


週刊東洋経済 2023/4/22号(ChatGPT 仕事術革命)

2023年4月18日 (火)

ChatGPTを使う上でやってはいけないこと3つ

最近、ChatGPTを使うか使うまいか、という議論をよく見かけます。

企業によっては禁止しているところもあるんですが、ストレートに言えば禁止は「不可能」でしょう。

今やスマホを持ち歩く時代、たとえ会社のPCが生成系AIにアクセスできないとしても、その手元のスマホから打ち込んでしまえば使えてしまいます。文書丸々は無理でも、機密用語や個人情報を含む文章を書き込むことくらいはできてしまいます。

イタリアのように国ごと禁止されているところならばまだしも、ここ日本ではOpenAIのサイトにつながってしまいますから、使うなと言うのは無理な話です。

ということで、業務利用に関して禁止ではなく、あるルールをもって利用可とするところが出てきている模様です。

ChatGPTを利用する際にやってはいけない3つのこと - ITmedia Mobile

そんなルールの参考となりそうなのが、この記事です。

大雑把に言うと「機密情報や個人情報を書かない」「内容をうのみにしない」「依存しすぎない」の3本です。

一つ目は当然ですが、二つ目のうのみにしない、というのは、やっぱりChatGPTはよく「嘘」を吐くからということが背景にあります。

なにしろ、ChatGPTから宇宙戦艦ヤマト2199の艦長は「古代進」だと告げられた人もいましたから(お……沖田艦長……)。

あと、差別用語やモラルのない文章をつくることもあるようです。作らせた文章を何かに用いる際は、それを受け取った人がしっかりと吟味する必要がある、ということですね。

三つめは、今のところ無料でも使えるChatGPT、Bingチャットですが、利用頻度の増加に伴って有償化されていく可能性があります。

いつまでも無料で使えるわけではないから、注意しましょうという意味のようです。

新しいものに嫌悪感や恐怖感から、それを禁止したくなるという気持ちは、分からないでもありません。

が、一度開けられてしまったパンドラの壺は、もはや閉じられることはないのです。

うまく付き合っていくしかないですね。


生成AI ビジネスを変えるAIの想像力

より以前の記事一覧

当ブログ内検索

スポンサード リンク

ブログ村

無料ブログはココログ