UTAUの周波数表ファイルをPythonで読む
UTAU音源にくっついてくる.frqファイルのデータをPythonで読むコードです。
.frqのフォーマットが書かれたツイートを見つけたので,それをそのまま実装しました。形式が解析済みって嬉しいですよね。これが無かったら労力が桁違い。
frqのフォーマットはこんなです https://t.co/tBk7w7DrCF pic.twitter.com/4ziYgmzlSB
— masao (@namiyome) October 5, 2015
何となく分かるものについて各データの説明をすると,
key_frequency
: 平均のピッチ的なもの(恐らく外れ値は除外されているだろう)175くらいだったらF3かなーとか分かるdatacount
:frequency
およびamplitude
の配列の長さfrequency
: 周波数
あとはよくわかりません。
structモジュールの仕様でchar[]は文字列ではなくバイト列として返ってくるので,必要に応じてdecodeしてください。
Wikipediaの他言語の記事名を得るPythonスクリプト
最近「あれ,この用語英語でなんていうんだ…?」(専門用語なので和英辞書は望み薄)という状況が何回かあって,その度にWikipediaの記事を見つけて英語版のタイトルを見る,とかやってたんですが,めんどくさいのでシュッとできるようにしました。
こんなツール絶対既出だと思ったんですけど軽くググっても出ないので自分で書きました。APIのドキュメント読んで適切なクエリを投げておしまい。WikipediaのAPIを扱えるパッケージは存在しますが,普段venvでいろんな環境を飛び回っていることを考えて標準ライブラリでやります。
これを適当なところに置いて,chmodして,パス通して,alias wikipedica=wikipedica.py
したらなんかそれっぽくなります。
$ wikipedica 百科事典 Encyclopedia
多言語対応。
$ wikipedica 百科事典 ja de Enzyklopädie
なお例外処理は放棄しました。読めばわかるじゃん。
エディタ画面から離れることなく辞書を引けるのはとても嬉しいですね。英語力の低さをひしひしと感じます。それでは。
語彙力ゲーム「弓箭」であそぶ
この記事はTSG Advent Calendar 2018の12日目として書かれたものです。昨日はmoratorium08さんによる「 TSG live AI コンテストの内容と感想、盤面生成に関する妄想など」でした。
さてお久しぶりです、pizzacat83です。今年からTSGというサークルに入りまして,Advent Calendarになんか書こうということで本当はクラスで使ってるSlackのお話をするつもりだったんですが,その記事を半分くらい書いたところでこのゲームを思いついたのでこっちのお話をします。TSGはコミュニケーションツールとしてSlackを使っているのですが,そこで語彙力で戦うゲーム「弓箭」を遊べるbotを作りました。
弓箭とは
お題の単語に対して“近い”単語を回答すると,その単語とお題の類似度と,その単語のレア度(コーパス中の出現頻度の低さ)に応じた点数をもらえるゲームです。つまり,お題に似ていて,かつあまり使われない単語を答えた人の勝ちです。
ゲームの名前「弓箭(きゅうせん)」は弓矢,弓矢を扱う武士,弓矢を用いた戦いなどを意味する単語*1です。「戦い」を意味する単語で語彙力が高そうなやつ,といういかにも語彙力のなさそうな理由で名付けました。英語表記はとりあえずvocabwarにしていますが,Blitzkrieg*2とかにしようかな。
ちなみにこれは私がオリジナルで考えたゲームではなくて,英単語についてほとんど同じルール(ただし類似度とレア度ではなく類似度と解答スピードを評価)のゲームがあるんですが,ググり力がなくて見つかりませんでした。黒い背景に水色の文字みたいな見た目だったと思うんですが,有識者教えてください。
ゲームの様子
Slackに「弓箭」と書き込むと,お題の単語を10個提案してくれます。単語を一つ選ぶと回答を書き込むフェーズに入ります。
お題選ぶ必要なくない?という指摘があったので,「即弓箭」と書き込むと提案なしでランダムにお題が決まるようになっています。運が悪いと知らない単語が出てきてたほいやになります。
ちなみに形容詞・形容動詞・副詞のみが出題されるようになっています。というのも,名詞は類義語が作りづらいことがありそうなのと,たほいやが名詞ばっかりなのでそちらに一任したいという気持ちがあります。あと日本語は細かいニュアンスを動詞の違いではなく修飾語によって表現することが多い(ex. glimpse「ちらりと見る」 stare「じっと見る」) (接頭語もあるけど)というのもあります。
回答を書き込みます。未知語は弾かれます。(私が👍しているように見えるのはbot用トークンを使っていないからです)
30秒経過すると結果発表です。参加人数の2倍のダミーを混ぜて,類似度が高い順に上位1/3が正の得点を得ます。
元がスピード勝負のゲームだったのでこちらも時間を厳しく設定しているのですが,得点が期待できる単語を考えている間に結果発表されてしまうこともしばしば。難しい単語を考えるとなるともう少し長めにした方がいいかもしれませんね。
ちなみにダミーはレア度の高い単語が多めに出るようにしていて,結果発表にコトバンクから取ってきた単語の意味も記載しているので,結果発表の後はダミーを眺めて次の戦いに備え語彙力を高めることができます。たほいやのダミー選択肢を覚えてるこわい人多いし,弓箭のダミー単語もどんどん覚えられていくんじゃないですかね。
実装
たほいやは博多市さんが一晩で作ったらしいですが,私は強くないので弓箭をn晩かけて作りました。全体的にたほいやに似ているので,大枠のコードはほとんど一緒です。弓箭の本質は類似度とレア度なので,その辺の話を軽く。
類似度はword2vecを使っているんですが,分かち書きされたコーパスが必要なのでWikipedia全文データを取ってきて分かち書きをします。で,このときついでに単語の出現回数を数えます。Wikipediaの全文データはXMLになっているので、wp2txt
でプレーンテキストにしてあげます。
$ sudo gem install wp2txt $ wp2txt --input-file ./jawiki-latest-pages-articles.xml
まあこれ以外にも同様のツール色々あるので探してください。
んでこれを分かち書きしつつ単語の出現頻度を見ます。TSGのslackbotはNode.jsで動いていて,tashibotという別のbotがkuromoji.jsを使っていたので私も使ってみたんですが,なんせ巨大ファイルを入出力するので(おそらく出力の仕方が下手で)メモリをバカ食いして死にました。よく考えたらNode.jsやkuromoji.jsでやる必要がないことに気づいたのでPythonで適当に書きます。こっちは常識的なメモリを食べていました。
これで分かち書きされたコーパスとレア度(出現頻度の逆数の常用対数)を得られました。さてword2vecしようということですが,Node.jsからword2vecできるパッケージが存在します。
ということで早速学習させてみたんですが今度はCPUとメモリをバカ食いして死にました。小さなコーパスでやってみたら学習結果の出力形式が単語 0.000000 0.000000 ...
という感じの分かりやすいテキストファイルになっていて,よく考えたらNode.jsでやる必要がないことに気づいたのでPythonで適当に書きます。gensimは良い子なのでメモリは500MBくらい,2時間ほどで学習終わったかな。
from gensim.models import word2vec data = word2vec.Text8Corpus("corpus/wiki_wakachi.txt") model = word2vec.Word2Vec(data, size=300) model.save("corpus/wiki_wakati.model") # モデルの保存 model.wv.save_word2vec_format("corpus/wiki.wv.txt") # node.jsでも読める単語ベクトル一覧のテキストファイル
Node.jsで読めるように出力関数を自分で書こうとしたらgensimが用意してくれてました。本当に良い子。ただテキストファイルで単語ベクトル一覧を保存する場合,拡張子は.txt
にすべきです。それ以外だとNode.jsのword2vecパッケージがバイナリとして解釈してよくわからん感じになります(これにn時間溶かした)。ただ中身を見る必要がないならファイルサイズも小さくなるしバイナリにした方が良いと思います。この場合は最下行が次のようになります。
model.wv.save_word2vec_format("corpus/wiki.wv.txt", binary=True)
これでレア度と類似度を求めることができます。
ちなみに「ゲームの様子」で書いた通りダミーはレア度の高い単語が多めに出るようになっています。要するに重み付き乱択です。重み付き乱択って色々アルゴリズムがあって見るたびに賢いなーっていつも思います。私は走査1回で済ませるやつと前処理して二分探索するやつが好きです。が,今回は重み付き非復元抽出なのと前処理をする気にならない(ただでさえメモリ食べてるので…)のとで,愚直に「重みなしで1つ抽出してレア度に応じた確率で棄却する」という方針でやっています。数学的に確かめてないんですけどこれで重みに応じて選択されるんですかね。
ちなみに高得点を得るコツが分かってくると,人間の尺度ではなくword2vecの尺度で「近い」単語を考えるようになります。弓箭を極めて歩くword2vecになろう。
今後
現状では得点が保存されないんですが実装した方がいいですかね。それからさらなる語彙力の向上のために,結果発表に模範解答(類似度そこそこ高く,レア度が高い)を含めようかなとか思ってます。まあこれは賛否両論かもしれませんが。
あとWikipediaの語彙力だけでは少し物足りないので青空文庫も学習させようと思ってるんですが,gensimで追加学習しようとすると怒られます。
model.train(corpus_file="corpus/aozora/wakachi.txt", epochs=model.epochs, total_examples=12345, total_words=1234567)
--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) (中略) */venv/lib/python3.7/site-packages/gensim/models/base_any2vec.py in _train_epoch_corpusfile(self, corpus_file, cur_epoch, total_examples, total_words, **kwargs) 403 raise ValueError("total_words must be provided alongside corpus_file argument.") 404 --> 405 from gensim.models.word2vec_corpusfile import CythonVocab 406 from gensim.models.fasttext import FastText 407 cython_vocab = CythonVocab(self.wv, hs=self.hs, fasttext=isinstance(self, FastText)) ModuleNotFoundError: No module named 'gensim.models.word2vec_corpusfile'
古いバージョン使ったら行けたりするんですかね。有識者教えてください。最初から学習し直すのも一手か。
あとはそもそも日本語ってあんまり弓箭向いてないんですよね。外来語の取り込み方が上手なので,類義語が簡単に思いついてしまってあんまり面白くないです(編集距離で加点しようかな)。英語とかだといろんな言語からの借用語が統合されないので類義語の綴りが全然違っていて,もう少し楽しくなるんですけどね。ただ「英語版弓箭」は最初の方に書いた通り弓箭の元ネタとして既存(名前を忘れたけど)なので,どうせやるなら情報科学の論文のみをコーパスとする弓箭とか面白いんじゃないですかね。実用的だし。
ということで,TSG Advent Calendar 2018 12日目でした。明日はfiordさんによる「TSG LIVE2!の感想・反省等を適当に書いたポエム - アルゴリズマーの備忘録」です。
Pythonの複合文をインデントせずに書く
この前p3+q3+r3=s3 (p, q, r, sは自然数)の解って(3, 4, 5, 6)の立法数倍以外にあるのかなーと思ってpaiza_runしてみたところ、
エラーが途中までしか見えませんがSyntaxErrorになりました。悲しい。RuntimeError: File "Main.py", line 2
— paiza_run (@paiza_run) 2017年5月29日
r=range;for w in r(1,10):for x in r(1,10):for y in r(... #paiza_run_result
インデントのない複合文
複合文っていうのは要するにifとかforとかwithとかの、 :の後に文を書くアレです。とりあえずifで説明をします。
ifっていうと大抵こんな感じですよね。
if flag:
hoge()
条件と:のあと改行・インデントして処理を書きます。これは、以下のように書くことと同等です。
if flag: hoge()
インデントをすると文字数が爆発するので、paiza_runとかではこのようにしてインデントを回避できると便利です。
ちなみに、
if flag: hoge(); fuga(); piyo()
これは、以下のコードと同等です。
if flag:
hoge()
fuga()
piyo()
直感的といえば直感的なんですが、C/C++とは違うので注意が必要かもしれません。
ネストがしたい
そういえば私はp3+q3+r3=s3 (p, q, r, sは自然数)の解を探し求めているのでした。頭を全く使いたくないので愚直に(p, q, r, s)を4重ループで回します。
インデントをしたくないのでこう書きたくなります。
r=range(1,10) for w in r:for x in r:for y in r:for z in r:if w**3+x**3+y**3==z**3:print(w,x,y,z)
しかしこれは冒頭で述べたようにSyntaxErrorになります。リファレンスを見てみましょう。
8. 複合文 (compound statement) — Python 3.6.1 ドキュメント
スイートは、ヘッダのコロンの後ろにセミコロンで区切られた一つまたはそれ以上の単純文を並べるか、ヘッダ行後のインデントされた文の集まりです。後者の形式のスイートに限り、ネストされた複合文を入れることができます ; 以下の文は、 else 節がどの if 節に属するかがはっきりしないという理由から不正になります :if test1: if test2: print(x)
じゃあwithとかなら良いのかっていうとこれもSyntaxErrorになります。複合文はインデントしないとネストできないのです。大人しくインデントしましょう。
それでも僕はインデントをしたくない
残念ながらTwitterは140字の制限があるのです。インデントなんかしてたらpaiza_runに投げられないのです。そこで登場するのがリスト内包表記。
リスト内包表記を使うと4重ループとifをワンライナーできます。join()とかで適当に整形すれば最初にやろうとしていた出力と同じ出力を得ることもできますが、それをやるには140字は少なすぎる。リスト内包表記はいいぞ。[(1, 6, 8, 9), (2, 12, 16, 18), (3, 4, 5, 6), (3, 10, 18, 19), (3, 18, 24, 27), (4, 17, 22, 25)... #paiza_run_result
— paiza_run (@paiza_run) 2017年5月29日
余談
13+63+83=93という結構小さい解が普通にあって意外でした。ところで連続する3立方数の和が立方数になるのは、33+43+53=63の場合以外にあるのでしょうか。数学のプロ教えてください。
2016年度文化祭展示作品「文豪チャット」公開
今年度も文化祭の作品として、さくらミケ氏と合作でプログラムを作成しました。UIはA.js(仮名)氏が作ってくれました。
どんなプログラムかざっくり言うと、
「文豪風に喋る会話プログラム」
です。会話の相手は夏目漱石や太宰治など、また文豪以外にも猫とかエヴァとかあります。
Vectorにて配布中! 是非お楽しみくださいませ。
文豪チャットの詳細情報 : Vector ソフトを探す!
操作説明は以下のさくらミケ氏のブログにあるので、私のブログにおいては割愛させていただきます。
2017年文化祭展示作品 文豪チャット|猫もあきれるプログラミング日記
ソースコードはこちらから。
GitHub - sakurakitten/Bungo-chat: 文豪チャット( http://www.vector.co.jp/soft/winnt/amuse/se515415.html )のソースなどです. 必要なデータファイルはこちら(https://github.com/sakurakitten/Bungo-chat-data )
バグ報告はこの記事のコメント欄でもGitHubのIssueでも何でも良いですが、作者から返信が来ていないかたまにチェックしていただけるとありがたいです。(その点ではGitHubのIssueが一番ですね)
既知の不具合については、GitHub以外で報告されたものも気付き次第Issueを立てておくので、バグ報告する前に同じバグ報告がされていないかチェックしていただけると助かります。
さて、この記事では、会話の応答文の生成に用いられている手法などについて書いていきます。なお、分担して制作したプログラムのうち、さくらミケ氏担当の部分については、この記事では割愛させていただきます。
目次
コンセプト
目指したもの
りんな
目指してないもの
iOS - Siri - Apple
両方とも人間の発話に応答するプログラムですが、私にとってのりんなとSiriの違いを的確に表す画像がこちらです。
要するに、今回のプログラムでは、「道案内」や「Webで検索しました」などの便利機能はなしで、単に会話の相手を務めることを目指しています。
最初は「コミュ障カメレオン」というオリジナルキャラクターの会話AIを作るみたいな流れだったんですが、日本語の文章を大量に入手する手段として青空文庫を紹介したのがきっかけで、文豪の会話AIを作ることになりました。
各種制約
「文化祭作品」としての制約
- インターネット環境なし
- Visual C++ 2008
- Windows XP/Vista/7 で動作
- あんまり自由に色々インストールできない雰囲気
会話文生成プログラム
マルコフ連鎖による文生成
文生成の手法としてマルコフ連鎖というものが有名らしい、ということで実装してみました。
この手法では、ある単語についてその次に来うる単語のリストを作成しておき、文生成の時には、ある単語に対して次に来うる単語を適当に選び、さらにその単語に対して次に来うる単語を…、ということを繰り返して文を生成します。
たとえば、「きのこの山は好き。だけどたけのこの里はそこまで好きじゃない。」*1という文章に対して、前後の単語のつながりを表にすると、
前 | 後 |
---|---|
きのこ | の |
の | 山, 里 |
山 | は |
は | 好き, そこまで |
好き | 。, じゃ |
。 | だけど |
だけど | たけのこ |
たけのこ | の |
里 | は |
じゃ | ない |
ない | 。 |
ここから単語を適当に辿って新たな文を作ります。たとえば、「たけのこ」から始めると、
「たけのこ」→「の」→「山」→「は」→「好き」→「。」
なんてのが作れます。元の文章にはない文ができました。
この例では一つ前の単語から次の単語を決めていますが、実際のプログラムでは、一つ前の単語と二つ前の単語が一致する単語のリストから単語を選んでいます。
さて、これをどう会話に組み込むかについて、最初は相手の発言に含まれる単語の一つを文を生成するようにしたのですが、全然会話になりませんでした。ボツにすることも考えたのですが、それももったいないので、相手が寡黙な時に自分から話を始めるために、これを用いて適当な文を生成しています。今思うと、「相手の発言に含まれる単語の一つを含む文」という条件は会話には緩すぎるので、相手の会話に含まれる単語やその関連語の出現確率を上げるとかするともっと良くなるかもしれないです。
この手法による文生成は、文法的には不自然でない文を作りやすいですが、文が長くなるにつれて前後で内容のつながりがなくなり、意味的にはさっぱりわからない文ができることが多いです。短い文が出るまで繰り返すみたいなことをやると改善するかもしれません。また、地の文を基にして文を生成しているため、会話として不自然になりがちなので、会話文のみを基にするのが良い気がします。
返答リストから乱択
何かの参考になるかなと自分のLINEのトーク履歴を眺めていたところ、9割くらい「へえ」「そう」「はあ」「ほー」「なるほど」「はい」のような当たり障りのない相槌*2で会話していることに気がつきました。LINEみたいな短文でのやりとりでは、相槌は結構役に立つ気がしたので、これを取り入れてみようと思いました。
ただ、確率で「へえ」とか「なるほど」とか言うという作りでは、「文豪らしい会話を!」とかいう制約に引っかかるので、著書から相槌を抽出して、それを使うようにしています。その抽出方法とは、
手動
手動です手動。文章から「」『』で囲まれた文を取り出して、その中から相槌に使えそうなものを人手で集めました。感動詞のみからなる文を抽出するとか、短い/単語数が少ない文を抽出するとかで自動化もできたかもしれませんし、学習的なことをさせても良かったのですが、そんなことを考えるよりも手動でやった方が早い気がしました。こういう人がいるから神エクセルが無くならないのでしょう。
ただこれが結構上手く行くんですよね。当たり障りのない返事ばかりなので。
ついでに、質問に対し適当に答えるものも作りました。たとえば「誰」が含まれる発言に対し、「妻がその人の名をいいましたか」(夏目漱石)といったような応答が用意されています。また、質問をはぐらかす、「今日は駄目です」(夏目漱石)のような応答もあります。特に「いつ」については、答えの中の日時にあたる部分を適当な日時に置き換えることでちょっとバラエティを出す、という小細工が仕組まれています。
ちなみにこれは黒歴史なんですが、公開当時は「君の名は。」という映画が流行していた*3もので、「君の名は。」と質問すると「名前はまだ無い。」(夏目漱石)とか「如何にも自分は隴西の李徴である」(中島敦)とか答えるようになっています。「名前はなんですか」みたいな尋ね方でも同様に返答できます。
この手法は、作業さえ頑張ればそこそこのクオリティになるので、割とおすすめです。作業さえ。
はじめに
はじめまして、ぴざきゃっと(@pizzacat83)と申します。
このブログについて
このブログでは主にプログラミングについて書いていくつもりです。初心者なのでおかしなことを書くかもしれません。コメント等でご指摘いただけると嬉しいです。
このブログのタイトルは「null部の部誌」ですが、これは架空の組織であり、仮にそんな部活が実在していたとしてもこのブログとは無関係です。
かといって、何らかの実在の部活の部誌でもないです。非公式の部誌でもありません。単なる個人のブログです。
要するに、タイトルに深い意味は何らありません。
また、このブログに書かれた内容は、ソースコードを含めて、すべて無保証です。このブログに書かれていることを実行することなどは、自己責任でお願いします。