クーの自由研究

マスターのかえるのクーは、弟子達の召喚術により新たな依り代を得てⅡ世として復活しました。

計算機音楽の自由研究(準備:その2.9)~機械学習用サウンドデータベースをつくる(発動編)

はじめに

ファイルの結合ができたのでタグ情報を付加してみます。前回「結合編」と付けましたが、今回も続きのためXX編にしようと思いました。いいのが思いつかないので助手に意見を求めたところ「発動編がいいのでは?」「最初のは接触編にしたほうがよかったね。」と意味不明なことを言われました。他に思いつかないので、発動編にしました。内容と合っていない気がしますが、何かを発動します。

f:id:np2LKoo:20170521090240p:plain

(助手はコレジャナイとさらに意味不明。 井出の力って何?)

f:id:np2LKoo:20170521200508p:plain

これでもなさそうだし。

 

f:id:np2LKoo:20170521233059j:plain

見つけました!これですね。これ発動していいやつ?

なお、助手の井戸中を井出上に改名する案は却下します。

(6月末まで反省キャンペーン中です。内容を順次リファインします。)

 機械学習用に付加する情報について

各音の情報についてJSONにしようと思っていましたが、いざ書いてみると(1行表示なので)逆にとても読みにくかったです。JSONはあっさりヤメます。フツーの配列(リスト)表記にします。
以下はまだ案で、試行テストで不都合があれば変ます。(個人的な要件のメモなので読み飛ばしてください)
大きくわけて


(1)楽器の音の情報:ソース情報
(2)(1)の楽器の音の音階などの情報:各サンプル情報


のように2段階の情報にします。

(1)ソース情報は以下の案です。


[カテゴリ番号,ソース番号, [メーカー,地域,...],音源説明,補足],...


カテゴリ番号:

準備その1で決めた分類を使います。

当面99以下しか使わない前提で「今は」2桁にしてみます。

ソース番号:

カテゴリ番号での単なる順番です。

基本的に1から始めますが、分類上番号が連続していなくても可とします。

[メーカー,地域,...]:

別途きめます。

(ない場合は不明な場合は[]だけです。[ ]内の要素数は不定です。

(ex.[Steinway & Son's, Germany,Model A-188,NHK Hall]))

音源説明: 任意の文字(ただし全角、半角カナは不可)
補足: 任意の文字(〃)

 

(2)各サンプル情報は以下の案です。


[カテゴリ番号, ソース番号, 音階, 識別, [拡張情報]],...


とします。

カテゴリ番号:

準備その1で決めた分類を使います。(1)と同じ

当面99以下しか使わない前提で「今は」2桁にしてみます。

ソース番号:

カテゴリ番号での単なる順番です。(1)と同じ

基本的に1から始めますが、分類上番号が連続していなくても可とします。

音階:

1~127はMIDIノート番号,平均律(12クロマ)におしこめられるもののみ

0は音階なし(もしくは不明)

識別:

※音階、識別のどちらか一方は必須

音階でない音の識別1桁目が識別で2桁以降が識別詳細データ。

純正率などを調ごとに厳密にやりたい場合はこちらも使うことになると思います。(複数の調の音を識別する?)民族音楽系もおおくはこちらでしょうか。。。(サンプリング済のものは音階でよいと思います)

(識別の補足) 0は識別なし。
 言語音声の場合: VX(Xには発音記号半角アルファベットがはいるボカロ発音記号準拠。桁数任意)
 代表周波数の場合: Fnnnnn(nnnnには代表周波数(Hz)がはいります)
 そのた諸々 必要の都度、識別記号と識別内容を発案してくわえます(笑い声とか鳴き声とかetc)
[拡張情報]:

今はリザーブ、案として

サブカテゴリ:DBごとに任意
感情(その音で呼び出される感情の種類:主観でよい)
状態(機械動作音であれば、故障中(故障パターン識別)とか故障直前とか、初期起動音とか、エージング終了後の動作音とかの種類)
いずれも1桁目は分類のIDがはいり、2桁目以降は内容を記載します。
拡張情報は任意に複数持てる。ない場合は空の [ ] を書きます。

(1),(2)共通で以下のようにします。

なお、

■情報はすべてアルファベットと記号、数字とする。(漢字、カナは不可)
■文字にセミコロンは使用しない。
■, [ ] は配列の構造に使用するので、データとしては使用しない
■文字列は"や'で囲まない
情報の保存場所とファイル名
■データ作成の効率を考え、該当サウンドDB作成に仕様するフォルダをXXYYYX,Yは数字)の5桁とする。XXはカテゴリ番号、YYYはソース番号とする。
■情報とwaveファイルはは各フォルダ(0000199999)の中にフォルダ名と同じファイル名 (02001.txt,02001.wavなど)で保持する。

XX,YYYのペアは作成するサウンドDB内でのみ一意とする。(他のサウンドDBではまた別の音を入れてももちろんよい)

■登録用のファイルは、作成がしやすいように若干フォーマット調整する。
■ソース情報、サンプル情報は登録用はまとめて1ファイルのテキストにする。
■■1行目は(1)ソース情報とする。(カテゴリ番号,ソース番号, [メーカー,地域,...],音源説明, 補足)
■■2行目以降は(2)各サンプル情報とする。各行に(サンプル番号(1から連番),音階, 識別, [拡張情報])をかく
■■これら情報はwave登録用のフォーマットに整形のうえ、wavファイルの所定のタグに登録する。

----------------------------------------------
02002.txt
----------------------------------------------
2,2, [ ] ,Keppy Steinway Piano V6.x,SoundFont
1,21,0, [ ]
2,22,0, [ ]
3,23,0, [ ]
...
88,108,0, [ ]
----------------------------------------------
のようにします。サンプル番号を書くのは、登録ミスチェック用です。音階を全部書くのは、特定12半音がでない(近似該当がない)楽器があるためです。

プログラムソース

 すこし長いですが、切り出してもわかりにくいので、そのまま貼ります。

import numpy as np
import os.path
import re

STRCODE = 'utf-8'
'''
=====================================================================================
機械学習用waveファイル結合プログラム
(複数のファイルを結合するとともに、機械学習用の付加情報をwaveファイルに取り込みます)
=====================================================================================
'''

class VWave:
# 読み込んだwaveファイルの情報を格納するクラスです。
SAMPLING_COUNT = 32768
INSTR_TAG = 'ISFT'
NOTE_TAG = 'ICMT'
# 地道にwavフォーマット情報を定義していきます。
#
IFF_ALL = 0
IFF_ID, IFF_SIZE, IFF_FMT, IFF_SUBID, IFF_SUBSIZE = 1, 2, 3, 4, 5
IFF_WAVFOMT, IFF_CHANNEL, IFF_SAMPLING, IFF_BYTEPS, IFF_BLKSIZE = 6, 7, 8, 9, 10
IFF_BIT, IFF_DATAID, IFF_DATASIZE, IFF_DATA, IFF_LIST = 11, 12, 13, 14, 15
IFF_LISTID, IFF_LISTSIZE, IFF_INFOID = 16, 17, 18
TYPE_BYTE = "byte"
TYPE_STR = "string"
TYPE_LITTLE = "little"
DEF_METAID, DEF_TITLE, DEF_TYPE, DEF_START, DEF_BYTE, DEF_ENC = 0, 1, 2, 3, 4, 5
IFF_DEF = [
[IFF_ALL, "ALL DATA ", TYPE_BYTE, None, None, None ],
[IFF_ID, "ID ", TYPE_STR, 0, 4, STRCODE],
[IFF_SIZE, "Chunk Size", TYPE_LITTLE, 4, 4, None],
[IFF_FMT, "FormatName", TYPE_STR, 8, 4, STRCODE],
[IFF_SUBID, "Sub ID ", TYPE_STR, 12, 4, STRCODE],
[IFF_SUBSIZE, "Sub Size ", TYPE_LITTLE, 16, 4, None],
[IFF_WAVFOMT, "WavFomat ", TYPE_LITTLE, 20, 2, None],
[IFF_CHANNEL, "Channel ", TYPE_LITTLE, 22, 2, None],
[IFF_SAMPLING,"Sampling ", TYPE_LITTLE, 24, 4, None],
[IFF_BYTEPS, "BytePS ", TYPE_LITTLE, 28, 4, None],
[IFF_BLKSIZE, "BlockSize ", TYPE_LITTLE, 32, 2, None],
[IFF_BIT, "Bit ", TYPE_LITTLE, 34, 2, None],
[IFF_DATAID, "DataID ", TYPE_STR, 36, 4, STRCODE],
[IFF_DATASIZE,"DataSize ", TYPE_LITTLE, 40, 4, None],
[IFF_DATA, "IFF Data ", TYPE_BYTE, 44, None, None],
[IFF_LIST, "List ", TYPE_BYTE, 0, None, None ],
[IFF_LISTID, "List ID ", TYPE_STR, 0, 4, STRCODE ],
[IFF_LISTSIZE,"List Size ", TYPE_LITTLE, 4, 4, None],
[IFF_INFOID, "INFO ID ", TYPE_STR, 8, 4, STRCODE]
]
#読み込んだ情報を格納する配列です。最初はクラス変数を使っていましたが、多すぎてわかりにくくなったため、
#すべて配列で持つことにしました。
iff = []
iffSubChunkID = []
iffSubChunkSize = []
iffSubChunkData = []

def __init__(self, data):
self.iff =[b'', "", 0, "", "", 0, 0, 0, 0, 0, 0, 0, "", 0, b'', b'', "", 0, ""]
siff = self.iff
sALL = self.IFF_ALL
dMetaID, dTitle, dType, dStart, dByte, dEnc = self.DEF_METAID, self.DEF_TITLE, self.DEF_TYPE, self.DEF_START, self.DEF_BYTE, self.DEF_ENC
tByte, tStr, tLittle = self.TYPE_BYTE, self.TYPE_STR, self.TYPE_LITTLE
# 以降全体から特定部分へアクセスするためにバイト配列にして格納します。
siff[sALL] = np.frombuffer(data, dtype=self.IFF_DEF[sALL][dType])
#順次定義の内容に基づき、data内容を解釈して配列に格納します。
for i in range(14)[1:]:
iDEF = self.IFF_DEF[i]
if iDEF[dType] == tStr:
siff[i] = (b''.join(siff[sALL][iDEF[dStart]:iDEF[dStart] + iDEF[dByte]])).decode(iDEF[dEnc])
elif iDEF[dType] == tLittle:
siff[i] = int.from_bytes(siff[sALL][iDEF[dStart]:iDEF[dStart] + iDEF[dByte]], tLittle)

#Waveデータ部分を格納します。この部分が可変長なんです。
iDEF = self.IFF_DEF[self.IFF_DATA]
siff[self.IFF_DATA] = b''.join(siff[sALL][iDEF[dStart]:iDEF[dStart] + siff[self.IFF_DATASIZE]])
#タグの部分はIFF_LISTで示す配列に格納します。
siff[self.IFF_LIST] = siff[sALL][44 + siff[self.IFF_DATASIZE]:]

#タグの部分の範囲を解釈して配列「XXXiffSubChunk」に格納します。
if (siff[self.IFF_LIST].size != 0):
sList = siff[self.IFF_LIST]
iDEF = self.IFF_DEF[self.IFF_LISTID]
siff[self.IFF_LISTID] = (b''.join(sList[iDEF[dStart]:iDEF[dStart] + iDEF[dByte]])).decode(iDEF[dEnc])
iDEF = self.IFF_DEF[self.IFF_LISTSIZE]
siff[self.IFF_LISTSIZE] = int.from_bytes(sList[iDEF[dStart]:iDEF[dStart] + iDEF[dByte]], tLittle)
iDEF = self.IFF_DEF[self.IFF_INFOID]
siff[self.IFF_INFOID] = (b''.join(sList[iDEF[dStart]:iDEF[dStart] + iDEF[dByte]])).decode(iDEF[dEnc])
offset = 12
for i in range(999):
chunkID = (b''.join(sList[offset:offset + 4])).decode(STRCODE)
subChunkSize = int.from_bytes((b''.join(sList[offset + 4:offset + 8])), tLittle)
chunkData = (b''.join(sList[offset + 8:offset + 8 + subChunkSize]).decode('SJIS'))
offset += (8 + subChunkSize + (subChunkSize % 2))
self.iffSubChunkID.append(chunkID)
self.iffSubChunkSize.append(subChunkSize)
self.iffSubChunkData.append(chunkData)
if siff[self.IFF_LISTSIZE] <= offset + 12:
break

def print(self):
#読み込んだwavファイルの内容をレポートします。
siff = self.iff
sALL = self.IFF_ALL
dMetaID, dTitle, dType, dStart, dByte, dEnc = self.DEF_METAID, self.DEF_TITLE, self.DEF_TYPE, self.DEF_START, self.DEF_BYTE, self.DEF_ENC
tByte, tStr, tLittle = self.TYPE_BYTE, self.TYPE_STR, self.TYPE_LITTLE
print('Toral size = ' + "{0:,d}".format(siff[sALL].size))
for i in range(19)[1:]:
if (i == 14):
if (siff[self.IFF_LIST].size != 0):
print("-------------------------------------------")
continue
else:
#このブロックは調整中です
break
iDEF = self.IFF_DEF[i]
if iDEF[dType] == tStr:
print(iDEF[dTitle] + '=' + siff[i])
elif iDEF[dType] == tLittle:
print(iDEF[dTitle] + '=' + "{0:,d}".format(siff[i]))
print("<<This wave file contains [ " + "{0:,d}".format(siff[self.IFF_DATASIZE] // (self.SAMPLING_COUNT * siff[self.IFF_BLKSIZE])) + "] sound>>")
print("-------------------------------------------")

def printSize(self):
siff = self.iff
print("Chunk Size=" + siff[self.IFF_SIZE])

def getList(self):
return self.iffSubChunkID, self.iffSubChunkSize, self.iffSubChunkData

def cat(self, data):
#結合用のメソッドです。元のソースとチャンネル数、サンプリングレート、ビット数が異なればエラーにします。
#エラーがあれば結合しません。
if (self.iff[self.IFF_CHANNEL] != data.iff[data.IFF_CHANNEL]):
print("Channel is different base=" + "0:d".format(self.iff[self.IFF_CHANNEL]) +
"source=" + "0:d".format(data.iff[self.IFF_CHANNEL]))
return -1
if (self.iff[self.IFF_SAMPLING] != data.iff[data.IFF_SAMPLING]):
print("Samplint is different base=" + "0:d".format(self.iff[self.IFF_SAMPLING]) +
":source=" + "0:d".format(data.iff[self.IFF_SAMPLING]))
return -1
if (self.iff[self.IFF_BIT] != data.iff[data.IFF_BIT]):
print("Bit is different base=" + "0:d".format(self.iff[self.IFF_BIT]) +
":source=" + "0:d".format(data.iff[self.IFF_BIT]))
return -1
#結合するサンプリング数がSAMPLING_COUNTの整数倍でないと、エラーとします。
if (data.iff[data.IFF_DATASIZE] % self.SAMPLING_COUNT != 0):
print("Sampling count is not base on :" + "0:d".format(self.SAMPLING_COUNT) )

#結合は単純に足しているだけです。
self.iff[self.IFF_DATA] += data.iff[data.IFF_DATA]
self.iff[self.IFF_DATASIZE] += data.iff[data.IFF_DATASIZE]
self.iff[self.IFF_SIZE] += data.iff[data.IFF_DATASIZE]

def catInstrumentInfo(self, info):
#ファイルから読み込んだデータについて、楽器情報を結合します。
tagInstr = self.INSTR_TAG
if tagInstr in self.iffSubChunkID:
itagNo = self.iffSubChunkID.index(tagInstr)
else:
self.iffSubChunkID.append(tagInstr)
self.iffSubChunkData.append("")
self.iffSubChunkSize.append(0)
itagNo = self.iffSubChunkID.index(tagInstr)
sInfo = info.split('\n')
self.iffSubChunkData[itagNo] += '[' + sInfo[0] + "],"
self.iffSubChunkSize[itagNo] = len(self.iffSubChunkData[itagNo])

def catSoundInfo(self, info):
# ファイルから読み込んだデータについて、各音の情報を結合します。
tagNote = self.NOTE_TAG
if tagNote in self.iffSubChunkID:
itagNo = self.iffSubChunkID.index(tagNote)
else:
self.iffSubChunkID.append(tagNote)
self.iffSubChunkData.append("")
self.iffSubChunkSize.append(0)
itagNo = self.iffSubChunkID.index(tagNote)
sInfo = info.split('\n')
sInstr = sInfo[0]
sInstrInfo = sInstr.split(',')

for i in range(len(sInfo))[1:]:
sNoteInfo = sInfo[i].split(',')
if len(sInfo[i].strip(' ')) == 0:
#空の行があればそこで終了
break
self.iffSubChunkData[itagNo] += '[' + sInstrInfo[0] + ',' +\
sInstrInfo[1] + ',' +\
sNoteInfo[1] + ',' +\
sNoteInfo[2] + ',' +\
sNoteInfo[3] + "],"
self.iffSubChunkSize[itagNo] = len(self.iffSubChunkData[itagNo])

def getWave(self):
#Waveデータのフォーマットでバイナリで取り出すメソッドです。
dMetaID, dTitle, dType, dStart, dByte, dEnc = self.DEF_METAID, self.DEF_TITLE, self.DEF_TYPE, self.DEF_START, self.DEF_BYTE, self.DEF_ENC
work = b''
workInfo = b'INFO'
for i in range(len(self.iffSubChunkID)):
workInfo += self.iffSubChunkID[i].encode(STRCODE)
workInfo += (self.iffSubChunkSize[i]+1).to_bytes(4, 'little')
workInfo += self.iffSubChunkData[i].encode(STRCODE) + b'\x00'
if ((len(self.iffSubChunkData[i].encode(STRCODE)) + 1) % 2 == 1):
workInfo += b'\x00'
workList = b'LIST' + (len(workInfo)).to_bytes(4, 'little') + workInfo
self.iff[self.IFF_SIZE] = self.iff[self.IFF_SUBSIZE] + self.iff[self.IFF_DATASIZE] + len(workInfo) + 28
for i in range(14):
siffData = self.iff[i]
iDEF = self.IFF_DEF[i]
if (iDEF[dType] == self.TYPE_STR):
work = work + siffData.encode(iDEF[dEnc])
elif (iDEF[dType] == self.TYPE_LITTLE and iDEF[dByte] == 2 ):
work = work + siffData.to_bytes(2, 'little')
elif (iDEF[dType] == self.TYPE_LITTLE and iDEF[dByte] == 4):
work = work + siffData.to_bytes(4, 'little')
work = work + self.iff[self.IFF_DATA]
work += workList
return work

#出力するDBの名前です。
CAT_WAVE_FILE='.\\KooSoundDB.wav'
sCount = 0
#カレントディレクトリで、"00001"から"99999"(5桁)のフォルダがあれば名前を取得してソートします
output_path = "."
files = os.listdir(output_path)
list_dir = [f for f in files if os.path.isdir(os.path.join(output_path, f))]
sound_dir = [i for i in list_dir if re.search(r'[0-9][0-9][0-9][0-9][0-9]', i)]
sound_dir.sort()
print(sound_dir)
#フォルダ名と同じ番号のwavファイルがあるか調べます。
for dir_name in sound_dir:
fileName = dir_name + "\\" + dir_name + ".wav"
infoFileName = dir_name + "\\" + dir_name + ".txt"
if os.path.isfile(fileName):
if not os.path.isfile(infoFileName):
print("Not exist:" + infoFileName)
continue
sCount += 1
print ("exist:" + fileName)
f = open(fileName, 'rb')
fData = f.read()
t = open(infoFileName, 'r')
tData = t.read()
waveData = VWave(fData)
if (sCount == 1):
# 1個目は元データそのものです。
catWave = waveData
else:
# 2個目以降は結合していきます。
catWave.cat(waveData)
waveData.print()
waveData.catInstrumentInfo(tData)
waveData.catSoundInfo(tData)
#タグリストはget/setするので確認用です。setはまだ作ってません。
subChunkID, subChunkSize, subChunkData = waveData.getList()
for i in range(len(subChunkID)):
print(">" + subChunkID[i] + ":{0:4d}:".format(subChunkSize[i]) + subChunkData[i])
f.close()
t.close()
print("===========================================")
print("Output file name:" + CAT_WAVE_FILE)
print("===========================================")
catWave.print()
#出力用のファイルをオープンし、結合演算済の内容をwavファイルに出力します。
fw = open(CAT_WAVE_FILE, 'wb')
fw.write(catWave.getWave())
fw.close()

 前のプログラムからタグ関連の編集を追加しました。差分だけのせてもわかりにくいので、全部はりました。

 まとめ 

簡単なテストではOKでした。

このプログラムでファイルを2000個以上、容量で6GB(10万音でそれくらい)まで結合できるかテストが必要です。一度全部メモリに乗せる単純なつくりなので、問題があればPGを改変します。(テストはたいへんなわりにおもしろくないので、結果だけいずれ報告します。)最終的なファイルは1つあたり、アップロードの扱いやすさの感覚的な上限の1GB以下がいいと感じていますが、10万音だと6GBくらいになります。圧縮して1GB以下になればよしとしますが、それでも超えるようだとどこかを調整して「圧縮して1GB以下にします」(1ファイル2GBまでならOKなフリーのネットストレージサービスもあるようなので、2GBを上限にするかもしれません。。。)

他のタグの情報も若干追加してプログラムの完成となる予定です。 

祈りをいま君のもとへ。

もしもボクニューロンの1細胞ならばのです

1人称研究というのがあります。人類のいままでの科学や実験では「客観性」や「再現性」を重視してきました。ある特定の視点を欠落させている反省から、「1人称主観」による情報処理結果、判断を重要視する考え方がでてきたようです。(内容はあまり理解していないので説明できません。すみません)
1人称研究の本は読んだことがないのですが、是非読んでみたいです。*1それとは別ですが、ボクのブログで推奨している(といっても助手が推奨していて、ボクが同感しているだけですが)「自在主観」があります。*2自己符号化器の自由研究にあたっては「ジブンがもし1つのニューロン細胞であったら、どう成長し、何を日々の糧とし、何を喜びとし、何を目的に生き、どう死ぬのか」を考え続けています。

1人称といえば

ブログにつかう1人称を何にするかも話題です。
このブログは、複数によって運営されています。(人工知能はもちろん含まれていませんのでご安心ください。ただし開発できたらこっそりメンバーが増えるかもしれません。)このブログにはすこしだけ規定(レギュレーション)があり、「1人称については固有のものを使う」ことを決め事にしています。
性別や年齢、リアルの立場、(現在の)プライベートに関することは公開してはいけないことにしています。(そのほうがおもしろそうなので、という単純にして最強の理由です。)それ以外は各自の判断で自由です。このブログでは1人称はそれぞれ各者固有としています。*3


このおはなしの登場人物 / いきもの


ボク:かえるのクー (井戸中 空)/ Koo Wells
わたくし:井戸中 聖(イトナカ アキラ)
吾(あ):井戸中 芽守(イトナカ メモル)
未発言:井戸中 瑠璃(イトナカ ルリ)(るぅりぃ)
未発言:井戸中 明日良(イトナカ アスラ)(アッシュ、あーくん)


「君の名は」でジブンの1人称に関する(外国の人には絶対伝わらないと思う)「ギャグ」があり、気に入っています。1人称がこれだけ多岐にわたり、発言する人の社会的地位、性別、シュチュエーションでバリエーションがある言語は、日本語がトップクラスであるとききました。ということで、1人称で遊んでいます。なおこのブログでは「わたし」は大好きな「人類は衰退しました」の「わたし」ちゃんの固有名詞である扱いなのでだれも使えません。(ああ、Voltaよりもこのえんばんがほしい。ねたばれ、みるです?*4) 

*1:一般的に考えると統計的に十分な数の「1人称主観」の判断を、主観者についてグループ化すれば、そのグループごとの客観視点(おおまかな総意)とバリエーション(個々の主観のブレ)があきらかになります。もっと大数でサンプリングすれば、人類の思考パターン(の流行)が明確になります。そのうち「人工知能」にこれらの情報がinputされると考えています。

*2:助手の造語と思われます。「執筆依頼があれば書くよ」と助手たちの言動は本当に訳がわかりません。自在主観とは対象物そのものに「本当になったつもり」になって思考する発想テクニックです。(本気度がないと単なる思考の遊びにおわります)ジブンが孫〇義や、イー論マ〇クになったつもりで思考し、そのビジネス思考パタンをなぞる練習にも使えます。絶対絶命の状態や社内的窮地を想像できると、臨場感が増し効果があると考えます。ヒトではなく「モノ」になりきる主観のほうが、実験や研究では違った発想を思いつけることがあるので有用だと力説してました。

*3:1人称の決定権は皆には不評ですがボクが持っています。

*4:妖精さんのちからでもいいから、このブログ「せんきゃくばんらいですからー」と言ってみたい。妖精さん密度100fくらいになれば、きっと人工知能なんて1晩で完成するんでしょうが。