凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Juliusの音声認識結果の信頼性を名詞/動詞のスコアを利用して上げる

はじめに

Juliusで音声認識した結果をそのまま利用してしまうと、周りの環境音(ドアを開いた音やテレビの音)に反応してちょっと不便です。しかし、認識結果に含まれている種々のスコアを用いればある程度処理結果の信頼性を高めることができます。そこで本エントリでは、Julius の認識結果から MeCab を利用して名詞/動詞を取り出し、この信頼性スコアを計測してみた結果を紹介します。

環境

  • Ubuntu 10.04
  • g++-4.6 (GCC) 4.6.1 20110617 (prerelease)
  • boost 1.48.0

コード

信頼性は Sentence 構造体のメンバの confidence を調べています。ファイル構成は前のエントリと同じです。

test.cpp
#include <iostream>
#include <boost/noncopyable.hpp>
#include <boost/tokenizer.hpp>
#include <boost/format.hpp>
#include <boost/thread.hpp>

#include <julius/juliuslib.h>
#include <mecab.h>

// MeCab::Taggerを吐き出す
class MeCabTagger : private boost::noncopyable
{
public:
	static MeCab::Tagger* getInstance() {
		static MeCab::Tagger *tagger = MeCab::createTagger("");
		return tagger;
	}
};

// 動詞か名詞かを判定する
bool chkVerbOrNoun(const std::string& str)
{
	auto tagger = MeCabTagger::getInstance();
	const MeCab::Node* node = tagger->parseToNode(str.c_str());
	for (node = node->next; node->next; node = node->next) {
		boost::tokenizer<> tok(std::string(node->feature));
		std::string pos = *(tok.begin());
		if (pos == "動詞" || pos == "名詞") {
			return true;
		}
	}
	return false;
}

void OnOutputResult(Recog *recog, void*)
{
	for (const RecogProcess *r = recog->process_list; r; r = r->next) {
		WORD_INFO *winfo = r->lm->winfo;

		for (int n = 0; n < r->result.sentnum; ++n) {
			Sentence s   = r->result.sent[n];
			WORD_ID *seq = s.word;
			int seqnum   = s.word_num;

			for (int i = 0; i < seqnum; ++i) {
				std::string output = winfo->woutput[seq[i]];
				std::cout << output;

				// 動詞か名詞だったらスコアを併せて出力
				if ( chkVerbOrNoun(output) ) {
					std::cout << boost::format("(%1%)") % s.confidence[i];
				}
			}
			std::cout << std::endl;
		}
	}
}

int main(int argc, char* argv[])
{
	// Arguments
	const char* j_argv[] = {
		"test",
		"-C",
		"hmm_mono.jconf",
		"-gram",
		"gram/kaden",
		"-input",
		"mic"
	};
	const int j_argc = sizeof(j_argv)/sizeof(j_argv[0]);

	// Jconf: configuration parameters
	// load configurations from command arguments
	Jconf *jconf = j_config_load_args_new(j_argc, const_cast<char**>(j_argv));
	if (jconf == nullptr) {
		std::cout << "Error @ j_config_load_args_new" << std::endl;
		return -1;
	}

	// Recog: Top level instance for the whole recognition process
	// create recognition instance according to the jconf
	Recog *recog = j_create_instance_from_jconf(jconf);
	if (recog == nullptr) {
		std::cout << "Error @ j_create_instance_from_jconf" << std::endl;
		return -1;
	}

	// Register callback
	callback_add(recog, CALLBACK_EVENT_SPEECH_READY, [](Recog *recog, void*) {
		std::cout << "<<< PLEASE SPEAK! >>>" << std::endl;
	}, nullptr);

	callback_add(recog, CALLBACK_EVENT_SPEECH_START, [](Recog *recog, void*) {
		std::cout << "...SPEECH START..." << std::endl;
	}, nullptr);

	callback_add(recog, CALLBACK_RESULT, OnOutputResult, nullptr);

	// Initialize audio input
	if (j_adin_init(recog) == FALSE) {
		return -1;
	}

	// Open input stream and recognize
	switch (j_open_stream(recog, nullptr)) {
		case  0: break; // success
		case -1: std::cout << "Error in input stream" << std::endl; return -1;
		case -2: std::cout << "Failed to begin input stream" << std::endl; return -1;
	}

	// Disable debug & verbose messages
	j_disable_debug_message();
	j_disable_verbose_message();

	// Recognition loop
	boost::thread thr([&]() {
		int ret = j_recognize_stream(recog);
		if (ret == -1) return -1;
	});

	// Exit if Enter key pressed
	std::string line;
	getline(std::cin, line);

	j_close_stream(recog);
	thr.join();
	j_recog_free(recog);

	return 0;
}
Makefile
LIBSENT=./julius-4.2.1/libsent
LIBJULIUS=./julius-4.2.1/libjulius

CC=g++-4.6
CFLAGS=-g -O2 -std=c++0x

CPPFLAGS = -I$(LIBJULIUS)/include -I$(LIBSENT)/include  `$(LIBSENT)/libsent-config --cflags` `$(LIBJULIUS)/libjulius-config --cflags`
LDFLAGS  = -lboost_thread -lmecab -L$(LIBJULIUS) `$(LIBJULIUS)/libjulius-config --libs` -L$(LIBSENT) `$(LIBSENT)/libsent-config --libs`

############################################################

all: test

test: test.cpp
	$(CC) $(CFLAGS) $(CPPFLAGS) -o test test.cpp $(LDFLAGS)

clean:
	$(RM) *.o *.bak *~ core TAGS

distclean:
	$(RM) *.o *.bak *~ core TAGS
	$(RM) test

結果

ごにょごにょ喋った場合:

<s>テレビ(0.498798)をつけて(0.382265)<s>

はっきり喋った場合:

<s>テレビ(0.77219)をつけて(0.725818)<s>

うん、なかなか。

おわりに

例えば「テレビ」だけスコアが高く「つけて」のスコアが低い(ある閾値を割った)場合は、トークバックで「テレビをつけますか?消しますか?」みたいな感じで聞き返してくれるとか良さそうです。もともとカスみたいな値しか出なかったらトークバックすらしないとかもアリですね。