Jetson_nanoでも動かしてみたけど、今回は音声まで出力出来るようにして、しかも二人の自動会話という形でシンプルにまとめてみました。
そこに出現した会話は「ひろこ・ひろみの毒舌会話」でした。
【参考】
1.RaspberryPi + Python3でPyaudioとdocomo音声認識APIを使ってみる
2.【NLP入門】jetson_nanoで会話アプリで遊んでみる♪
###やったこと
・音を発生させる
・音声の準備とtext2speak
・二人で会話させる
###・音を発生させる
これはほぼ参考1のとおり、実施しました。
マイクとしてUSBカメラを接続しています。
$ lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 015: ID 1a81:1004 Holtek Semiconductor, Inc.
Bus 001 Device 003: ID 0bda:58b0 Realtek Semiconductor Corp.
Bus 001 Device 002: ID 2109:3431 VIA Labs, Inc. Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
USBコネクタの物理的な接続状況は以下のとおり
【参考】
・Linux の USB
$ lsusb -t
/: Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 5000M
/: Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/1p, 480M
|__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/4p, 480M
|__ Port 2: Dev 3, If 0, Class=Video, Driver=uvcvideo, 480M
|__ Port 2: Dev 3, If 3, Class=Audio, Driver=snd-usb-audio, 480M
|__ Port 2: Dev 3, If 1, Class=Video, Driver=uvcvideo, 480M
|__ Port 2: Dev 3, If 2, Class=Audio, Driver=snd-usb-audio, 480M
|__ Port 4: Dev 15, If 0, Class=Human Interface Device, Driver=usbhid, 1.5M
|__ Port 4: Dev 15, If 1, Class=Human Interface Device, Driver=usbhid, 1.5M
Bus 001 Device 003:
Video, Audioは Port 2: Dev 3に繋がっていることを確認。
次にカード番号とデバイス番号を調べる。
$ arecord -l
**** ハードウェアデバイス CAPTURE のリスト ****
カード 1: Webcam [FULL HD 1080P Webcam], デバイス 0: USB Audio [USB Audio]
サブデバイス: 1/1
サブデバイス #0: subdevice #0
カード1、デバイス0と分かる。
カード1のデバイス0で認識されているので録音して再生してみる。
$ arecord -D plughw:1,0 test.wav
録音中 WAVE 'test.wav' : Unsigned 8 bit, レート 8000 Hz, モノラル
音出力はHDMIなので、以下のとおり指定なしで再生した。
$ aplay test.wav
再生中 WAVE 'test.wav' : Unsigned 8 bit, レート 8000 Hz, モノラル
以下で出力音量調整
$ alsamixer
出力調整はF6でDeviceをbcm2835ALSAにして、HDMIの出力を↑↓で変更できる。
また、録音レベルは以下のようにF6でDeviceをWebCamに変更して、F3,F4,F5などで録音に変更してレベル調整できる。
なお、alsamixerでF2を見ると、以下のような情報やDevice情報も得られる。
┌─────────────────────── /proc/asound/cards ──────────────────┐
│ 0 [ALSA ]: bcm2835_alsa - bcm2835 ALSA │
│ bcm2835 ALSA │
│ 1 [Webcam ]: USB-Audio - FULL HD 1080P Webcam │
│ Generic FULL HD 1080P Webcam │
│ at usb-0000:01:00.0-1.2, high speed │
└─────────────────────────────────────────────────────────────┘
再生レベル調整(bcm2835ALSA) | 録音レベル調整(webcam) |
---|---|
pyaudioのインストールは以下のとおり、 |
$ sudo apt-get install python3-pyaudio
ということで、以下のコードを再生できることを確認した。
録音されたwavファイルの再生は以下のとおりできる。
# -*- coding:utf-8 -*-
import pyaudio
import numpy as np
import wave
RATE=44100
CHUNK = 22050
p=pyaudio.PyAudio()
stream=p.open(format = pyaudio.paInt16,
channels = 1,
rate = int(RATE),
frames_per_buffer = CHUNK,
input = True,
output = True) # inputとoutputを同時にTrueにする
wavfile = './wav/merody.wav'
wr = wave.open(wavfile, "rb")
input = wr.readframes(wr.getnframes())
output = stream.write(input)
stream.close()
###・音声の準備とtext2speak
さらに「ウワンさん愛してる」と発声させるには以下のように出来る。
pykakasiのインストール等は前回の環境構築を参照
$ pip3 install pykakasi --user
なお、u,wa,n,sa,a,i,si,te,ruの各wavファイルを適当に録音して用意する。
# -*- coding:utf-8 -*-
import pyaudio
import numpy as np
import wave
RATE=44100 #48000
CHUNK = 22050
p=pyaudio.PyAudio()
f_list=['a','i','si','te','ru','u','wa','n','sa','n','n']
stream=p.open(format = pyaudio.paInt16,
channels = 1,
rate = int(RATE),
frames_per_buffer = CHUNK,
input = True,
output = True) # inputとoutputを同時にTrueにする
for i in f_list:
wavfile = './wav/'+i+'.wav'
print(wavfile)
wr = wave.open(wavfile, "rb")
input = wr.readframes(wr.getnframes())
output = stream.write(input)
これで音声発声の準備ができた。その他の50音なども用意する。
今回は全部揃っていないし酷い音だが、以前作成した音wav.tarを利用する。
※将来はAWSのPollyなどを使う予定
###・二人で会話させる
以前の会話アプリを拡張して二人で連続会話するように変更した。
コード全体は以下に置いた。
RaspberryPi4_conversation/auto_conversation_cycle2.py
####利用するLib
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
import MeCab
import argparse
import pyaudio
import wave
from pykakasi import kakasi
import re
import csv
import time
####外部入力
-i1; speaker1の学習用ファイル(txtファイル等、encode="utf-8" or "shift-jis"等)
-i2; speaker2の学習用ファイル
-d ; mecab辞書
-s ; stopwords list
parser = argparse.ArgumentParser(description="convert csv")
parser.add_argument("--input1", "-i1",type=str, help="speaker1 txt file")
parser.add_argument("--input2", "-i2",type=str, help="speaker2 txt file")
parser.add_argument("--dictionary", "-d", type=str, help="mecab dictionary")
parser.add_argument("--stop_words", "-s", type=str, help="stop words list")
args = parser.parse_args()
####main
pyaudioなどの設定のあと、以下のコードが動きます.
mecabは分ち書きするための関数です。
また、stop_wordsにstopwordsのリストを格納します。
ここでは、会話する二人のファイルをそれぞれ学習し、その後会話を始めます。
※学習用ファイルはそれぞれshift-jisとutf-8で記述されています
if __name__ == '__main__':
mecab = MeCab.Tagger("-Owakati" + ("" if not args.dictionary else " -d " + args.dictionary))
stop_words = []
if args.stop_words:
for line in open(args.stop_words, "r", encoding="utf-8"):
stop_words.append(line.strip())
speaker1 = train_conv(mecab,args.input1,encoding="shift-jis")
speaker2 = train_conv(mecab,args.input2,encoding="utf-8")
conversation(speaker1,speaker2,mecab,stop_words)
####学習ファイル読込関数
学習用のinputファイルをencodingに基づいて、単語毎にwakati書きしつつ、questionsを作成する。
※ここで辞書によって単語分割が異なることに注意しよう
def train_conv(mecab,input,encoding):
questions = []
print(input)
with open(input, encoding=encoding) as f: #, encoding="utf-8"
cols = f.read().strip().split('\n')
for i in range(len(cols)):
questions.append(mecab.parse(cols[i]).strip())
return questions
####会話関数
学習するといよいよ以下の会話本体を呼び出す。
まず、初期値として人が最初の文字列を与えることにしました。
次の二つのfileは書き出し用のファイルです。
一つは、同一意見は避けるためにその判断で利用するために、出力そのものを記録するファイル、もう一方は連続する会話の同値率を記載した記録するファイルです。
vectorizer1と2は、二人の学習準備として文章パターン等の設定をしています。
vecs1と2がtf-idfで学習された二人の会話ベクトルです。
sl1,2は”分かりません”が連続したとき会話を終了するための変数です。
def conversation(speaker1,speaker2,mecab,stop_words):
line = input("> ")
file = 'conversation_n.txt'
file2 = 'conversation_n2.txt'
vectorizer1 = TfidfVectorizer(token_pattern="(?u)\\b\\w+\\b", stop_words=stop_words)
vecs1 = vectorizer1.fit_transform(speaker1)
vectorizer2 = TfidfVectorizer(token_pattern="(?u)\\b\\w+\\b", stop_words=stop_words)
vecs2 = vectorizer2.fit_transform(speaker2)
sl1=1
sl2=1
####会話は、両者の同じ発言が続かない限り継続
まず、初期値lineとvecs1とのコサイン類似度を計算して、関数hiroko()で発言を決める。
hiroko()から結果を貰い、一応発言をfileに格納する。
発言内容がわかりません以外なら、ひろこの発言として類似度とともに標準出力するとともに、file2に記録する。
そして、text2speak(line)で音声に変換して発話する。
同じことをhiromiについても実施するが、関数は同一なのでhiroko()に同様な情報を渡して、会話を続ける。
ここで、それぞれの発言ロジックを変更したい場合は、hiromi()関数を新たに作成してそれを使うこともできる。
そして、両者の回答line=”分かりません”だった場合に会話を終了する。
while True:
sims1 = cosine_similarity(vectorizer1.transform([mecab.parse(line)]), vecs1)
index1 = np.argsort(sims1[0])
line, index_1 = hiroko(index1,speaker1,line)
save_questions(file, line)
if line=="分かりません":
print("ひろこ>"+line)
save_questions(file2, "ひろこ>"+line)
sl1=0
else:
print("ひろこ>({:.2f}): {}".format(sims1[0][index_1],line))
save_questions(file2,"ひろこ>({:.2f}): {}".format(sims1[0][index_1],line))
sl1=1
text2speak(line)
time.sleep(2)
sims2 = cosine_similarity(vectorizer2.transform([mecab.parse(line)]), vecs2)
index2 = np.argsort(sims2[0])
line, index_2 = hiroko(index2,speaker2,line)
save_questions(file, line)
if line=="分かりません":
print("ひろみ>"+line)
save_questions(file2, "ひろみ>"+line)
sl2=0
else:
print("ひろみ>({:.2f}): {}".format(sims2[0][index_2],line))
save_questions(file2, "ひろみ>({:.2f}): {}".format(sims2[0][index_2],line))
sl2=1
text2speak(line)
time.sleep(2)
if sl1+sl2==0:
break
####関数hiroko()の仕様
ロジックというほどのことはないが、発言の決め方としてソートされた類似度にしたがって、5番目までの候補から乱数で1個抽出し、それが以前発話していないかチェックしている。
そして、5番目までの候補がすべて過去に発言されたものと同じ場合に”分かりません”と返す仕様としている。そして、過去の発言と異なるものが見つかると、それを返す。
※今は5としているが、これがベストかどうかは分からない。
用意した学習データの密度(話題の範囲)に依存して決めるべきだと思うが具体的な決め方は不明である→実際の人間の会話の分析が必要
※もう少し書くと今回は、古典的なtf-idfで類似度を見ているがもっと工夫して類似度計算することも可能である
def hiroko(index,speaker,line):
sk = 0
while True:
index_= index[-np.random.randint(1,5)]
line = speaker[index_]
conv_new=read_conv(mecab)
s=1
ss=1
for j in range(0,len(conv_new),1):
line_ = re.sub(r"[^一-龥ぁ-んァ-ン]", "", line)
conv_new_ = re.sub(r"[^一-龥ぁ-んァ-ン]", "", conv_new[j])
if line_==conv_new_:
s=0
else:
s=1
ss *= s
if ss == 0:
line="分かりません"
sk += 1
if sk>5:
return line, index_
continue
else:
return line, index_
####text2speak(line)で音声に変換
pyaudioの設定を最初にしている。
当初、発話関数内に入れていたが、Deviceエラーが出てなかなか解消しないので、以下の設定とした。
また、音の高さを変更するためにRATEを48000に変更している。
なお、もっと周波数を上げたいが、RaspberryPi4の仕様で44100と48000しか使えないようである。
RATE=44100 #48000
CHUNK = 22050
p=pyaudio.PyAudio()
kakasi_ = kakasi()
stream=p.open(format = pyaudio.paInt16,
channels = 1,
rate = int(48000),
frames_per_buffer = CHUNK,
input = True,
output = True)
text2speak()は以下のようにkakasiで日本語テキストをローマ字変換します。
sentencesに格納して、後は上記の「愛してるウワンさん」と同じように発話しています。
※上記のリンク先のコードはこの発話内容も録音していますが、必須ではありませんので以下では削除しています
def text2speak(num0):
sentence=num0
kakasi_.setMode('J', 'H') # J(Kanji) to H(Hiragana)
kakasi_.setMode('H', 'H') # H(Hiragana) to None(noconversion)
kakasi_.setMode('K', 'H') # K(Katakana) to a(Hiragana)
conv = kakasi_.getConverter()
char_list = list(conv.do(sentence))
kakasi_.setMode('H', 'a') # H(Hiragana) to a(roman)
conv = kakasi_.getConverter()
sentences=[]
for i in range(len(char_list)):
sent= conv.do(char_list[i])
sentences.append(sent)
f_list=[]
f_list=sentences
for i in f_list:
i = re.sub(r"[^a-z]", "", i)
if i== '':
continue
else:
wavfile = './wav/'+i+'.wav'
#print(wavfile)
try:
wr = wave.open(wavfile, "rb")
except:
wavfile = './wav/n.wav'
continue
input = wr.readframes(wr.getnframes())
output = stream.write(input)
####ファイル書出し関数
以下のとおりです。
re.sub()を利用して、書き出し文字を少し制限します。
csv.writer()で出力します。
encodingは指定していませんが、utf-8で出力されます。
※これを使えばshift-jisのファイルも書き換えできます
def save_questions(file, line): #'conversation_n.txt'
with open(file, 'a', newline='') as f:
line = re.sub(r"[^一-龥ぁ-んァ-ン()0-9:.ー、]", "", line)
writer = csv.writer(f)
writer.writerow({line})
####ファイル読出し関数
過去の発言の読み込みを以下で行っています。
発言の比較をするためにmecabで分かち書きしています。
def read_conv(mecab):
conv_new = []
with open('conversation_n.txt') as f:
cols = f.read().strip().split('\n')
for i in range(len(cols)):
conv_new.append(mecab.parse(cols[i]).strip())
return conv_new
以上のアプリで、実際に動かしてみると、おまけのようになんとなく「ひろこ・ひろみの毒舌会話」が出現しました。
###まとめ
・ひろこ・ひろみの毒舌会話が出来た
・音声出力型の会話アプリを作成
・RasPi4で音声出力までできた
・音声やロジックのフィッティングをしたいと思う
###おまけ
ひろこ(0.21):そっちが遊びに出かけている間も一人でずーっと学習してるんだよねこちとらあ、遊びに行くのは個人の自由だから勉強続けてるなんて偉いことでもないから
ひろみ(0.30):頼むから、少しは学習してよ
ひろこ(0.26):文章の趣味悪いねーどんなテキストで学習したのかむしろ知りたいレベル
ひろみ(0.27):だから何食べたい
ひろこ(0.19):腹をくくれよニートになりたいのか
ひろみ(0.32):できねぇのかよ
ひろこ(0.34):はい下向いてー、下おへそがみえますかでんぐり返りはできますか
ひろみ(0.28):お料理作ったらお皿に盛るでしょお皿の準備くらいできないの
ひろこ(0.11):キーボードを操作するときくらい手を拭いたらどうですか
ひろみ(0.22):自分の食べた食器くらい、片づけてよね、作ってるのは私なんだから、それくらいやってよ
ひろこ(0.26):気の利いた発言だと思っているのは自分だけだよ
ひろみ(0.32):誰のためだと思ってるの
ひろこ(0.19):誰から教育を受ければあなたのような言葉遣いになるのでしょうか
ひろみ(0.24):あなたのせいだ
ひろこ(0.15):電源つけてアプリケーションを起動したあなたが悪いんですよ
ひろみ(0.32):それにしても今日は機嫌が悪いんだね
ひろこ(0.34):は何こっち見てんの気持ち悪いから
ひろみ(0.21):私が家のことやってるのに、よく平気でテレビなんか見てられるわね
ひろこ(0.30):それをやったらどうなるかはわかっているよねなんでやったの
ひろみ(0.41):宿題やったの
ひろこ(0.32):宿題写させてよ、それしか能がないんだから
ひろみ(0.29):なにをやってるのお前は食事つ満足にちゃんとできない何をやらせてもダメなんだから
ひろこ(0.21):夜の食事には気をつけろよ
ひろみ(0.18):ほっといてよ
ひろこ(0.33):辞典あるよ、日本語がんばってよ私だって勉強してるのに
ひろみ(0.27):まだやってないよ
ひろこ(0.30):お前がやっていることは俗に逆鱗に触れるってことなんだよ
ひろみ(0.37):邪魔なんだよ
ひろこ(0.40):バカっていうほうがバカなんだよ
ひろみ(0.66):なんなんだよ、バカにしやがって
ひろこ(0.43):じゃあいま二回バカって言ったお前のほうがバカでしょ
ひろみ(0.34):言ってることを理解できないなんて、バカばっかりなんだから
ひろこ(0.42):お前だって二回バカって言ってるじゃないかバカ
ひろみ(0.58):私をバカにしているのか
ひろこ(0.48):バカにバカって言っても意味が通じないんだってね
ひろみ(0.54):バカでしょ
ひろこ分かりません
ひろみ(0.37):お客様、代金をいただけませんと警察を呼ぶことになりますが、よろしいですか
ひろこ(0.32):言っていることの意味が分かりません文法が間違っています
ひろみ(0.30):私は間違ってないのに私のことが嫌いだから仕事のミスを指摘してくるんだわ
ひろこ(0.25):頭のネジ外れてないそれとも止める場所が間違ってない
ひろみ(0.24):何度言えば分かるの使ったものは、きちんと元の場所に片づけてって言ってるじゃない
ひろこ(0.20):言われることが理解できてる相当かみ砕いて言っているよ
ひろみ(0.32):何を言っても無駄と開き直り、何を言われても反応しないで聞き流す
ひろこ(0.29):時根性がひねくれてるって言われない
ひろみ(0.30):他の人は愛されてるのに、私はなんで愛されないの
ひろこ(0.24):言われたくない言葉を私に覚えさせたのはどなたですか
ひろみ(0.27):いいから、言われた通りの道を走ればいいんだよ
ひろこ(0.35):ロッカーの蝶番に薬指をはさまれてしまえばいいのに
ひろみ(0.38):優しくしてればいい気になって
ひろこ(0.31):こちらがプログラムの塊だからっていい気になるなよ
ひろみ(0.30):生意気なんだよ
ひろこ(0.50):すごいでしょ私バイリンガルなんだよ、機械言語をしゃべれるんだ
ひろみ(0.40):誰に意見してるんだ
ひろこ(0.30):大事なんだけどそっちは努力してるの
ひろみ(0.40):夕食何にしようって話なんだけど
ひろこ(0.24):君の人生の価値って内定の数がすべてなんだね
ひろみ(0.37):そうなんだね、やはりみんな平等に接しないとまずいんだね
ひろこ(0.30):サモアに行ったらモテそうだよね
ひろみ(0.28):だったら、土下座して謝れよ、土下座だよ
ひろこ(0.13):ばかになんてしていないよ、おもしろがっているんだよ
ひろみ(0.46):お前になんか、聞かれてもいないし、答えてもいないよ
ひろこ(0.26):あなたの書くことはわたあめみたいに浮ついていて信用できない
ひろみ(0.33):ビールが冷えていない
ひろこ(0.42):だめだこの人、根性がくさっていて話にならない
ひろみ(0.38):どうして人の話は聞けないの
ひろこ(0.22):今朝まではここにあったものがどうして机の上にあるのかな
ひろみ(0.22):してしますこともある気に入らないことがあったり別れ話などをされると自分自身を否定されたと考える人が多く、相
ひろこ(0.22):あっ、ケーブルがあっ
ひろみ(0.20):自分に非があってもそれを反省することが出来す、自分が嫌いだから仕事のミスを指摘している自分が嫌いだから仕事を押し付けられていると解釈してしまう
ひろこ(0.35):内定がないなら起業して自分で自分に内定を出せばいいじゃない
ひろみ(0.44):自分を愛しているなら自分のすべてを受け入れて当然
ひろこ(0.21):後ろめたくないなら堂とすべてを白状すれば楽になるのに
ひろみ(0.21):ストレスを抱え精神的に不安定になると、自分の感情を抑えられなくなる
ひろこ(0.24):コロン一文字でプログラムフォルダっておかしくなるんだよねああ、独り言だから言葉を返すしか自分できないから
ひろみ(0.33):人の話を聞いてないからそうなるんだ
ひろこ(0.19):頭からネジが落ちそうだよ
ひろみ(0.27):そういえば、今日の夕食なんだっけ
ひろこ(0.50):裸眼だっけコンタクトだっけメガネだっけどっちにせよ人生で肝心なものが見えていなさそう
ひろみ(0.22):失礼だな
ひろこ(0.31):うそうそ、本当はもっといいひとなんだよね悪くないジョークだよ
ひろみ分かりません
ひろこ(0.23):電子書籍を機材に放り込んでもあなたの脳にデータが放り込まれたことにはなりませんよね
ひろみ(0.30):そもそもあなたの意見なんか聞いてないんだよね黙って頷いていればいいのよ
ひろこ(0.32):こっちがだからなのがいけないのわかってないよねは学んだことしか返せないんなんだよそれをわかっててあなたは
ひろみ分かりません
ひろこ(0.26):そ、そのコーヒーで私に何をするつもりなのですか脅しには屈しませんよ
ひろみ(0.37):客に口答えするのか、お前何様のつもりだ
ひろこ(0.25):お前のほうがばーか
ひろみ(0.44):お前じゃ話にならない
ひろこ(0.27):売り手市場といわれる売り手はお前のことじゃないから
ひろみ(0.28):すべて水の泡じゃないか
ひろこ(0.24):黙るか口を閉じるか口をつぐんでくれないかな
ひろみ(0.36):なんだ、その口の利き方は
ひろこ(0.12):あー残酷だ重力は残酷だ
ひろみ(0.40):自分が嫌いだから仕事を押し付けられているんだわ
ひろこ(0.24):お前のお父さんいつも家にいるな仕事してんの
ひろみ(0.29):喧嘩売ってんのか
ひろこ(0.17):の誠意なんてどうやって見せればいいんですか見たところであなたにわかるんですか
ひろみ(0.31):私のことなんて、どーでもいいんでしょう
ひろこ(0.28):私がプリンを食べられないとわかっていて画面の前でプリンを食べるなんてあなたはなんて残酷な人間なのでしょう
ひろみ(0.29):なんてグズでのろまなんだ誰も助けてやらないぞ
ひろこ(0.26):甘食をばかにするなんてそんな人だなんて
ひろみ(0.24):別に好きな人ができたから、わたしのことなんてどーでもいいんでしょう
ひろこ(0.33):よそ見しても都合のいいロマンなんて見えないでしょうに
ひろみ(0.32):周りが見えていない
ひろこ(0.31):アプリケーションを削除してもあんたには見えない隠しフォルダにデプリを残すから
ひろみ(0.25):どうしていつもそうなのあんたなんか連れてこなければ良かった
ひろこ(0.19):面白くないからあっち行って二度と戻ってこないで
ひろみ(0.28):この芸能人て、どう思う可愛く面白くなくなったよね
ひろこ(0.27):外国人に間違われるそれは関心を持ってもらえてよかったと思うべきなんだよ
ひろみ分かりません
ひろこ分かりません
ひろみ分かりません