自分のブログ名

sheephumanのブログ

ひつじ好きな人間のブログ。

音声認識ランチャー制作における調査とテスト結果

元記事:

https://qiita.com/EndOfData/items/6ff96a5a7ce190fbf1d6

参考記事

https://wildpie.hatenablog.com/entry/2014/10/13/122909

コードに目だった漏れや記述ミスは見当たらない。 GetData1()の呼び出しが見当たらない程度。 ChatGPTにレビューさせたが同様の回答だった(凄いけどどれだけ当てに出来るのか)。

Visual studio 2022 .net7です。 最新のしか用意しないのはあまりに古くてまともに実行出来ないproject fileがやたらと多いからです。

Visual studio を使用したgitの扱いにまだ慣れていないので修正。

git clone -b BPMatching_Brunch https://github.com/Sheephuman/VoiceLancherTests.git 

追加実装 recode_Button2を追加し、同様の呼び出しと比較用List配列の宣言。

外観(と実行結果) image.png

追加実装

コントロールいくつか追加 コントロールの判定処理追加

Button fieldButton;
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            InitializeWaveIn();
            fieldButton = (Button)sender;
        }
 private void WaveIn_DataAvailable(object sender, WaveInEventArgs e)
       {
         
            // 32bitで最大値1.0fにする
            for (int index = 0; index < e.BytesRecorded; index += 2)
            {
                short sample = (short)((e.Buffer[index + 1] << 8) | e.Buffer[index + 0]);

                float sample32 = sample / 32768f;
                if (fieldButton.Name == StartButton.Name)
                     ProcessSample(sample32, _recorded);

                else
                    ProcessSample(sample32, target_recorded);

             }


       }
     if (recode.Count == 65536)
            {
                var points = recode.Select((v, index) =>
                        new DataPoint((double)index, v)
                    ).ToList();


                _lineSeries.Points.Clear();
                _hammingLineSeries.Points.Clear();


                _lineSeries.Points.AddRange(points);
                _hammingLineSeries.Points.AddRange(HammingMethod(recode));

                plotView.InvalidatePlot(true);
                hammingPlotView.InvalidatePlot(true);

                //

                recode.Clear();

         ///以下追加実装
                MessageBox.Show("end");
                waveIn.StopRecording();                
                waveIn.DataAvailable -= WaveIn_DataAvailable;
            }
            

呼び出し

private void DpMattingButton_Click(object sender, RoutedEventArgs e)
            {
             var dp = new DPmatthing();

             dp.LoadData(_recorded,target_recorded);

             dp.StartDP();
             label2.Content = dp.BackTrace().ToString();
            }

外観(XAML

<Grid>
        <StackPanel VerticalAlignment="Center" HorizontalAlignment="Left">
            <Button Content="Start Recoding" Margin="0,0,0,10"
                        x:Name="StartButton"         Height="40" Width="100" Click="Button_Click"/>
            <Button Content="Recode 2" Margin="0,0,0,10"
                    x:Name="Recode2"            Height="40" Width="100" Click="Recode2_Click"/>
            <Button Content="DPMatching" Margin="0,0,0,10"
                          x:Name="dpattingButton"      Height="40" Width="100" Click="DpMattingButton_Click"/>
            <Button Content="Stop" Margin="0,0,0,10"
                          x:Name="StopButton"      Height="40" Width="100" Click="StopButton_Click"/>
        </StackPanel>
        <StackPanel>
        <Label VerticalAlignment="Top"
            Background="AliceBlue" Height="50" Width="700" x:Name="label1" />
        <Label VerticalAlignment="Top" HorizontalAlignment="Left"
            Background="Beige" Height="30" Width="200" x:Name="label2"
               Margin="50,0,0,0"
               />
        </StackPanel>
        <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right">
            <oxy:PlotView 
             Model="{Binding oxyModel, ElementName=root}"
             Controller="{Binding Controller, ElementName=root}" 
            x:Name="plotView" HorizontalAlignment="Left" Margin="0,71,0,0" VerticalAlignment="Top" Height="270" Width="345"/>
            <oxy:PlotView 
             Model="{Binding hammingOxyModel, ElementName=root}"
             Controller="{Binding Controller, ElementName=root}" 
            x:Name="hammingPlotView" HorizontalAlignment="Left" Margin="{Binding Margin, ElementName=plotView}" VerticalAlignment="Top" Height="270" Width="345"/>
        </StackPanel>
        <StackPanel Margin="0,70,0,-70"/>
    </Grid>

DPmatthing.cs 内

public double BackTrace() { }とし、 return _minDistance; とした。 これをLabel渡してtoString()して表示させてる(大体いつもやってるやつ)

全Code

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
using System.Windows.Forms;

namespace VoiceLancherTests
    {
        internal class DPmatthing
        {
            private readonly List<double> _data1 = new List<double>();       

            private readonly List<double> _data2 = new List<double>();
       


            enum DPLabel
            {
                A, B, C, NON,
            };


            private DPLabel[,] _dplabel = null!;

            private double[,] _dpmap = null!;
            private double[,] _acumDistance = null!;
            private int _sourceLength;
            private int _targetLength;


            public int MatchedIndexBegin { private set; get; }
            public int MatchedIndexEnd { private set; get; }


            public IEnumerable<double> GetData1()
            {
                return _data1.Skip(1); // 1つめは0なので飛ばす
            }


            public IEnumerable<double> GetData2()
            {
                return _data2.Skip(1); // 1つめは0なので飛ばす
            }


            public void LoadData(List<float> source, List<float> target)
            {
                _data1.Clear();
                _data2.Clear();
                _data1.Add(0.0);
                _data2.Add(0.0);
                _data1.AddRange(source.Select(v => (double)v).ToList());
           

                _data2.AddRange(target.Select(v => (double)v).ToList());
           

                _sourceLength = 8000;
                _targetLength = 8000;
            }

            private double LocalDistance(int i, int j)
            {
                return (_data1[i] - _data2[j]) * (_data1[i] - _data2[j]);
            }

            public void StartDP()
            {
                // mapの大きさを設定
                _dpmap = new double[_sourceLength + 1, _targetLength + 1];
                _dplabel = new DPLabel[_sourceLength + 1, _targetLength + 1];
                _acumDistance = new double[_sourceLength + 1, _targetLength + 1];

                // 初期化
                // 端点フリーDPなので0
                //for (var i = 0; i < _dpmap.GetLength(0); i++)
                //{
                //    _dpmap[i, 0] = 0.0;
                //}


                for (var j = 1; j < _dpmap.GetLength(1); j++)
                {
                    _dpmap[0, j] = double.MaxValue;
                }

                for (var i = 0; i < _dplabel.GetLength(0); i++)
                {
                    for (var j = 0; j < _dplabel.GetLength(1); j++)
                    {
                        _dplabel[i, j] = DPLabel.NON;
                    }
                }

                //  for (var i = 1; i < _dpmap.GetLength(0); i++)

                //
            
                //Parallel.For(0, _dpmap.GetLength(0), i =>
                //{
                   for (var i = 1; i < _dpmap.GetLength(0); i++)
                   {

                        for (var j = 1; j < _dpmap.GetLength(1); j++)
                        {
                     
                            double a, b, c;
                            if (i - 2 < 0 || j - 2 < 0)
                            {
                                a = double.MaxValue;
                                b = _dpmap[i - 1, j - 1] + LocalDistance(i, j);
                                c = double.MaxValue;
                            }
                            else
                            {
                                a = _dpmap[i - 1, j - 2] + 2.0 * LocalDistance(i, j - 1);
                                b = _dpmap[i - 1, j - 1] + LocalDistance(i, j);
                                c = _dpmap[i - 2, j - 1] + 2.0 * LocalDistance(i - 1, j);
                            }
                            var min = Math.Min(a, Math.Min(b, c));

                            if (a == min)
                            {
                                _dplabel[i, j] = DPLabel.A;
                                _acumDistance[i, j] = _acumDistance[i - 1, j - 2] + 3;
                            }


                            else if (b == min)
                            {
                                _dplabel[i, j] = DPLabel.B;
                                _acumDistance[i, j] = _acumDistance[i - 1, j - 1] + 2;
                            }

                            else if (c == min)
                            {
                                _dplabel[i, j] = DPLabel.C;
                                _acumDistance[i, j] = _acumDistance[i - 2, j - 1] + 3;
                            }


                            _dpmap[i, j] = min + LocalDistance(i, j);


                        
                        }

                   }

            
            }
            private double _minDistance;
            public double BackTrace()
            {
                _minDistance = double.MaxValue;
                int index = _targetLength / 2 + 1;
                for (int i = index; i < _dpmap.GetLength(0); i++)
                {
                    var normalizedDistance = _dpmap[i, _targetLength] / _acumDistance[i, _targetLength];
                    if (normalizedDistance < _minDistance)
                    {
                        _minDistance = normalizedDistance;
                        index = i;
                    }
                }
                MatchedIndexEnd = index - 1;
                //var ii = index;
                //var jj = _targetLength;

                //while (jj > 0)
                //{
                //    switch (_dplabel[ii, jj])
                //    {
                //        case DPLabel.A:
                //            ii -= 1;
                //            jj -= 2;
                //            break;

                //        case DPLabel.B:
                //            ii -= 1;
                //            jj -= 1;
                //            break;

                //        case DPLabel.C:
                //            ii -= 2;
                //            jj -= 1;
                //            break;
                //    }

                //    MatchedIndexBegin = ii;
                //}
                return _minDistance;
            }

        
        }
    
    }

分かった事。

計算にかなりの時間が掛かる(約35秒ぐらい) 波形の差によっては更に掛かる?? OutOfMemoryExceptionを起こすことがある。

検証までにかかった時間(Typoやその他諸々のムダを含む) 2時間ぐらい

※以下MinDistanceは波形同士の最小距離を示す。 同じキーワード「羊乃モコ」では 

3.1977(MinDistance)

2回目では更に近い結果が得られた

1.3343147(MinDistance)

Timer計測の結果: 34秒 StartRecodingと違うワードにしてみた結果(メリーさんのひつじ)

7.81179(MinDistance)

2回目 7.565614(MinDistance) Timer計測の結果:56.4736204 Timer計測の結果 2回目:49.9063976

波形が近いほどMinDistanceは0に近い値を返すと思われるが、計算処理に時間が掛かり過ぎる。

→2021/3/31 Haminng窓を掛けた波形は有効な要素が先頭の8000pointほどに限られるらしく(要検証)、要素数を絞る事で精度を保ったまま実行速度を短縮することが出来た。 下記にて後述している。 また、ノイズが混入するから(PCのけったいなファンのせいだが)ローパスフィルタ等の処理を入れないと出力結果が安定しない事が分かった。

Pythonメソッドを使う必要があるかもしれない。 協力者の方は歓迎します。

最近分かったのはノイズを拾うから

対策

BackTrace() While文の箇所をコメントアウトしてみた。実のところ何をやっているのかよく分からない。出力結果に差は出なかった。  そもそもこれで実装の意味を成しているのかどうか疑問なのだが。

enum DPLabel
        {
            A, B, C, NON,
        };

多分 列挙体でラベリングのような事をしているのだと思う。

対策2 要素数を絞る

 折角HamingWindowを掛けたのにその波形を渡してなかったので修正した上で、DPMatting.LoadData()に渡す。

波形を観察したらわかったのだが、HamingWondowを掛けた波形は先頭から8000Point分しか有効な波形が無い(OxyPlotの制約かもしれないが)。 List型の要素数そのものは変わらない。 image.png

これは配列private double[,] _dpmapの要素数を8000まで絞り込める事を意味する(かなり多分)。 これにより速度を大幅に改善する事が出来た。

public void LoadData(List<float> source, List<float> target)
            {     
          ///要素数を有効(と見られる数)まで絞り込む
                _sourceLength = 8000;
                _targetLength = 8000;
            }

結果 1回目「羊乃モコ」  3.341295236918195E-08  00:00:06.1162549(秒) 2回目「メリーさんのひつじ」  4.316430133692367E-08  00:00:06.3854461(秒)

実行速度こそ現実的になったものの、RAMもかなり食われるのが問題である。

1次元配列に直す

 ChatGPT(略)

対策3 HashSetを使う

HashSetだとAddRangeメソッドが使えないので、List<doubble>にAddRangeした後にNewで割り当てる手法を取った。

 マイクから波形取得でListの代わりに使うのも駄目かもっていうかたぶん波形が原形を留めんかもしれん。まだ試してないけど望ましくなさそ。  →後日試したら想像以上に酷い結果になった。    →0..0000034とかになった

53.0838213 余計遅くなった。。。

 public void LoadData(List<float> source, List<float> target)
        {
            _data1.Clear();
            _data2.Clear();
            _data1.Add(0.0);
            _data2.Add(0.0);
            _data1.AddRange(source.Select(v => (double)v).ToList());
           //追加 
           hash_data1 = new HashSet<double>(_data1);

            _data2.AddRange(target.Select(v => (double)v).ToList());
            //追加 
            hash_data2 = new HashSet<double>(_data2);

            _sourceLength = source.Count;
            _targetLength = target.Count;
        }

.toList要らない説

 結果:1分以上掛かった

初期化処理のコメントアウト

newしてるから多分要らんだろと思ったけど大差はない。遅くはならなかった。

 // 初期化
            // 端点フリーDPなので0
            //for (var i = 0; i < _dpmap.GetLength(0); i++)
            //{
            //    _dpmap[i, 0] = 0.0;
            //}            

forからForeachへの書き直し

 C#の仕様が面倒で難航している。普段はforeachなんて簡単に書けるんだが.... 上手くいけば10秒以上短縮出来たりするかも。

 睡眠時間がまた削れた。

ターゲット波形の内、音が入っていない部分を削除する

 おそらくもっとも有効な施策だと思う。

Pararel.forによる高速化

 ChatGPTに聞いたら答えた高速化方法の一つ。 かなりRMMとCPUを占有する。