[iOS 10] SFSpeechRecognizerの音声認識処理の仕組みを見てみる

2016.09.14

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

おばんです、女友達に「彼女が欲しいならモテる努力をしないとダメだな」と切り捨てられた田中です。精進します。

前回の[iOS 10] SFSpeechRecognizerで音声認識を試してみたではSFSpeechRecognizerの基本的な使い方について見てきました。
今回はその中の仕組みを、クラスの構造とともに見ていこうと思います。

SFSpeechRecognitionResult

前回のリクエストの結果をラベルに反映する処理のあたりの話です。

recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { [weak self] result, error in
    guard let `self` = self else { return }
            
    var isFinal = false
            
    if let result = result {
        self.label.text = result.bestTranscription.formattedString
        isFinal = result.isFinal
        print(result.bestTranscription.segments)
    }
    
    // エラー時の処理とか
    ......
    ...
}

SFSpeechRecognitionResultの中身について見ると以下のようになっています。
これを見ると音声認識の仕組みがどのように行われているかのイメージがつきます。

open class SFSpeechRecognitionResult : NSObject, NSCopying, NSSecureCoding {
    @NSCopying open var bestTranscription: SFTranscription { get }

    open var transcriptions: [SFTranscription] { get }    

    open var isFinal: Bool { get }
}
  • bestTranscription: もっとも確度の高い結果
  • transcriptions: 処理結果候補一覧
  • isFinal: 最後の認識だったかどうか(候補に変化がなかった場合?)

transcriptionsとあるようにいくつかの候補がある中で点数でベストの値の結果を保持していてくれるようです。

SFTranscription

さらにSFTranscriptionの中に潜ってみると以下のような構成になっています。

open class SFTranscription : NSObject, NSCopying, NSSecureCoding {

    open var formattedString: String { get }

    open var segments: [SFTranscriptionSegment] { get }
}
  • formattedString: ユーザーに見せるべきように成形された文字列
  • segments: このtranscriptionの詳細

なんとなくformattedStringは変数名が変わりそうですね。
最後に、音声認識処理に関してはもっとも深い階層になるsegmentsを見ましょう。

SFTranscriptionSegment

SFTranscriptionSegmentの中身は以下のようになっています。

open class SFTranscriptionSegment : NSObject, NSCopying, NSSecureCoding {    
    open var substring: String { get }

    open var substringRange: NSRange { get }

    open var timestamp: TimeInterval { get }

    open var duration: TimeInterval { get }

    open var confidence: Float { get }

    open var alternativeSubstrings: [String] { get }
}
  • substring: 文字列
  • substringRange: 文字列のRange
  • timestamp: 発話開始時間
  • duration: 継続時間。語の発話にかかった時間の長さ
  • confidence: このSegmentの確度。おそらくSFSpeechRecognitionResultのbestTranscriptionの判定材料。
  • alternativeSubstrings: substringの他に確かそうである文字列の候補

SFTranscriptionSegmentの検証

とりあえずsubstringRangeに関してはNSRangeであることはわかるので省略。
このブログの最初に書いた箇所でsegmentsに対してforEachをかけてprintしてみます。

recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { result, error in
    
    if let result = result {
        print(result.bestTranscription.segments.forEach {
            print("--- SEGMENT ---")
            print("substring            : \($0.substring)")
            print("timestamp            : \($0.timestamp)")
            print("duration             : \($0.duration)")
            print("confidence           : \($0.confidence)")
            print("alternativeSubstrings: \($0.alternativeSubstrings)")
            print("")
            })
    }
}

ここで試しに
「真姫ちゃん可愛いかきくけこ」
という言葉を発音してみると以下のような結果でした。

--- SEGMENT ---
substring            : まきちゃん
timestamp            : 1.43
duration             : 0.71
confidence           : 0.918
alternativeSubstrings: []

--- SEGMENT ---
substring            : 可愛い
timestamp            : 2.14
duration             : 0.72
confidence           : 0.848
alternativeSubstrings: ["かわいい"]

--- SEGMENT ---
substring            : かきくけこ
timestamp            : 2.86
duration             : 4.34
confidence           : 0.919
alternativeSubstrings: []

「可愛い」という言葉の他に "かわいい" という言葉も候補だったようですね。
認識の確度の高さが僕の真姫ちゃんへの愛を物語っているテストです。

まとめ

単純に認識結果を得るところに着目すればresult.bestTranscription.formattedStringで文字列を取り出して終わりでしたが、その他にも候補がいくつかあったりそれぞれに確度があったり、クラス構造を見てみると単純に一つの結果のみが返ってきているわけではなくて面白いですね。