Help us understand the problem. What is going on with this article?

Objective-CアプリでWatsonQAと日本語で会話?してみる

More than 3 years have passed since last update.

サマリ

Bluemixには"Watson Q&A"というデモ用のQ&Aサービスがあり、"ヘルスケア"と"トラベル"の分野について回答してくれるサービスがあります。Githubにこれを使ったサンプルアプリ(ただし英語版)があったので、翻訳サービスを使って日本語化しつつ、元のコードも拝見してお勉強しましたというお話。

元のアプリはAndrew triceさん作成のこちら。

Voice-Driven Native Mobile Apps with IBM Watson & IBM MobileFirst
http://www.tricedesigns.com/2015/07/13/voice-driven-native-mobile-apps-with-ibm-watson/

作った(書き換えた)もの

"ヘルスケア"に関する質問を音声認識し、その質問への回答を音声で読み上げるアプリ。イメージで言うと↓な感じ。

Kobito.v1vEXF.png

動画だとこんな感じ(https://www.youtube.com/watch?v=0zFuuHjsBaE&feature=youtu.be)

ただ、学習させている内容から、”会話"にはちょっと疑問符がつくかも。応答部分を自前で作ると、より会話らしくなりそう。

アプリのアーキテクチャーと処理順序

Kobito.XRbdjD.png

1.iOSアプリ上でマイクを有効にしRecording、WAVファイルで保存
2.WAVをNode.jsにPostし、Node.jsからSpeech to Textで音声認識。同時に、翻訳サービスをつかって認識した文字列を英訳。音声認識した文字列の日本語・英語をモバイルアプリへ応答。
3.iOSアプリで2で認識した日本語を表示。英訳した質問をNode.jsに対してPost
4.Node.jsからQuestion and Answerに対して質問、得た回答をモバイルアプリへ応答
5.モバイルアプリから、得た回答のうち、最もスコアの高いものを取得しPost。Node.jsが翻訳して応答
6.モバイルアプリが応答された日本語を読み上げ

アプリは、Andrewさんの許可を得て、以下にforkしてアップしています。

IBM-Watson-Speech-QA-Japanese-iOS
https://github.com/GodaiAoki/IBM-Watson-Speech-QA-Japanese-iOS

QAアプリのポイント

WAVファイルのPostとハンドリング

今回ので一番勉強になったのは、音声であるwavファイルのPost方法。multipart/form-dataでPostしてます。

モバイルアプリからWAVファイルをPostするコード(objective-c)

-(void) postToServer {

    //update ui in main thread
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.recordButton setEnabled:NO];
    });

    [logger logInfoWithMessages:@"posting WAV to server..."];

    IMFResourceRequest * imfRequest = [IMFResourceRequest requestWithPath:transcribeURL method:@"POST"];
    NSData *data = [NSData dataWithContentsOfURL:audioRecorder.url];
    NSStringEncoding encoding = NSUTF8StringEncoding;

    NSString *boundary = @"------------------------------------------------------";
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];

    [imfRequest setValue:contentType forHTTPHeaderField: @"Content-Type"];

    NSMutableData *body = [NSMutableData data];

    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:encoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"audio\"; filename=\"%@\"\r\n", @"audio.wav"] dataUsingEncoding:encoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", @"audio/wav"] dataUsingEncoding:encoding]];
    [body appendData:data];
    [body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:encoding]];


    [imfRequest setHTTPBody:body];
    [imfRequest sendWithCompletionHandler:^(IMFResponse *response, NSError *error) {

        NSDictionary* json = response.responseJson;
        if (json == nil) {
            json = @{@"transcript":@""};
            [logger logErrorWithMessages:@"Unable to retrieve results from server.  %@", [error localizedDescription]];
        }


        //change start
        NSString *resultTranscript = [json objectForKey:@"transcript"];
        //サーバーサイドでresultStringに英訳文字を追加
        NSArray *resultarray =[resultTranscript componentsSeparatedByString:@"!%!"];
        NSString *resultString = resultarray[0];
        //change end

        BOOL animating = YES;



        if ( error != nil ) {
            resultString = [NSString stringWithFormat:@"%@ Try again later.", [error localizedDescription]];
            animating = NO;
        }
        else if (resultString == nil || [resultString length] <= 0 || [resultString isEqualToString:@""]) {

            resultString = @"Sorry, I didn't catch that.  Try again?";
            animating = NO;
        }
        else {
            //change start
            //英訳した文字列を投げる
            [self requestQA:resultarray[1]];
            //change end
        }

        [logger logInfoWithMessages:@"Transcript: %@", resultString];

        //update ui in main thread
        dispatch_async(dispatch_get_main_queue(), ^{
            if( !animating) {
                [self.activityView stopAnimating];
                [self.activityView setHidden:YES];
            }
            [self.queryLabel setText:resultString];
            [self.recordButton setEnabled:YES];
        });
    }];
}


PostされたWavをNode.js側ではfsのcreateReadStreamで読み取り

//fs = require('fs') 

// Handle the form POST containing an audio file and return transcript (from mobile)
app.post('/transcribe', function(req, res){

    var file = req.files.audio;
    var readStream = fs.createReadStream(file.path);
    console.log("opened stream for " + file.path);
        var params = {
        audio:readStream,
        model:'ja-JP_BroadbandModel',
        content_type:'audio/l16; rate=16000; channels=1',
        continuous:"true"
    };

    //var params = {
      //  audio:readStream,
        //content_type:'audio/l16; rate=16000; channels=1',
        //continuous:"true"
    //};

    speechToText.recognize(params, function(err, response) {

        readStream.close();

        if (err) {
            return res.status(err.code || 500).json(err);
        } else {
            var result = {};
            if (response.results.length > 0) {
                var finalResults = response.results.filter( isFinalResult );

                if ( finalResults.length > 0 ) {
                   result = finalResults[0].alternatives[0];
                   console.log('result=' + shallowStringify(result));
                   //英訳追加
                    var params = {
                        text: result.transcript
                        , from: 'ja'
                        , to: 'en'
                    };
                    msclient.translate(params, function(err, data) {
                        if(err) console.log('err!' +err);
                        console.log('translated=' + data);
                        result.transcript = result.transcript +"!%!" +data;
                        return res.send( result );
                    });
                }
            }
        }
    });
});

翻訳

翻訳はMicrosoft Translater APIを利用。Node.jsのmstranlator module( https://www.npmjs.com/package/mstranslator )を利用すると楽だった。

npm install mstranslator
MsTranslator = require('mstranslator');


var msclient = new MsTranslator({
      client_id: "Microsoft Tranlater APIに登録したappID"
      , client_secret: "登録時に発行されたクライアントキー"
    }, true);

app.post('/translate', function(req, res){
    console.log('translation start');
    console.log('req=' + shallowStringify(req));
    console.log('req.body.text=' + req.body.text);
    var params = {
        text: req.body.text
        , from: 'en'
        , to: 'ja'
    };
    msclient.translate(params, function(err, data) {
        if(err) {
            console.log('err!' +err);
            res.status(500).send('Bad Query')
        }
        console.log('translated=' + data);
        return res.send( data );
    });

});

音声読み上げ

こちらは決まったやり方があるので特に難しくない。

-(void) speak:(NSDictionary*)data {

    BOOL speaking = self.synthesizer.isSpeaking;
    if (speaking)
        [self.synthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];

    if (!speaking || currentData != data) {

        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];

        NSString *text = [data objectForKey:@"text"];
        NSArray *sentences = [text componentsSeparatedByString:@"."];

        for (int i=0;i<[sentences count]; i++) {
            AVSpeechUtterance *utterance = [AVSpeechUtterance
                                            speechUtteranceWithString:[sentences objectAtIndex:i]];

            utterance.rate = 0.5;
            utterance.preUtteranceDelay = 0.0;
            utterance.volume = 1.0;

            [self.synthesizer speakUtterance:utterance];
        }
    }
    currentData = data;
}

終わりに

音声受け取り・認識・読み上げあたりの実装方法はわかったので、より会話らしくするために、応答を自前で作ったものを試してみたい。

BluemixのWatson APIを駆使して日本語質問応答システムを作る
http://qiita.com/VegaSato/items

Bluemix Dialog
http://dialog-demo.mybluemix.net/?cm_mc_uid=95527112266014321770359&cm_mc_sid_50200000=1450239397

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away