Recursieve SQL-query's

Ik heb alle Engelse artikelen van deze blog naar de nieuwe gekopieerd. Bovendien lijkt het erop dat ik eindelijk veel nieuwe onderwerpen heb om te delen! Ik geloof echt dat je de komende maanden iets interessants zult vinden op mijn nieuwe blog . Geloof me, ik ben een ontwikkelaar! :)

Zondag 7 juni 2015

Verder gaan

Zoals u in de bovenstaande tekst kunt zien, wordt het opslaan van statistieken op de bestandssysteem is een grotere stap - we hebben ze daar niet nodig. In plaats daarvan kunnen we ze rechtstreeks naar Logstash sturen. Dit feit leidt ons naar de nieuwe architectuur:

Hier kunnen we slechts één Logstash-instantie (op dezelfde host als ElasticSearch) hebben die naar UDP-socket luistert. In dit geval vermijden we het schrijven van gegevens op de lokale FS en configureren we onze Log4j-appender om rechtstreeks naar Logstash te schrijven. Deze aanpak doet enigszins denken aan hoe SysLog werkt.

Log4J-configuratie: # Rootlogger-optie log4j. rootLogger= INFO, logstash # Directe logberichten naar Logstash log4j. aanhangsel. logstash=org. apache. log4j. ontvangers. netto. UDPappender log4j. aanhangsel. logopslag. remoteHost=logstash. hostlog4j. aanhangsel. logopslag. poort= 6666 log4j. aanhangsel. logopslag. application= UwProject log4j. aanhangsel. logopslag. codering = UTF-8 log4j. aanhangsel. logopslag. lay-out= naam . krestjaninoff. loggen. json. LogstashJsonLayout
Logstash-configuratie: invoer (udp (poort => 6666 codec => json type => "udp_json")) ... uitvoer (elasticsearch (host => localhost))
LogstashJsonLayout-code (slechts een proof of concept): /** * JSON-lay-out voor Logstash */ public class LogstashJsonLayout breidt Layout uit ( private finale Gson gson = new GsonBuilder() . create() ; private finale String hostname = getHostname() . toLowerCase () ; private finale String-gebruikersnaam = System . getProperty ("user.name" ) @ Overschrijf het openbare String-formaat (LoggingEvent-bestand) ( Map< String , Object >bericht = nieuwe LinkedHashMap< >() ;

bericht. put("@timestamp" , oftewel timeStamp) ;

bericht. put("hostnaam" , hostnaam) ;

bericht. put("gebruikersnaam", gebruikersnaam) ; bericht. put("level" , le. getLevel() . toString() ) ; bericht. put("thread" , dat wil zeggen getThreadName() );

if (le. getMessage() instantie van Map) ( message. putAll(le. getMessage() ) ; ) else ( message. put("message" , null ! = le. getMessage() ? le. getMessage() . toString( ) : null ) ) retourneert gson. toJson(bericht) + " \n " ;

) privé statisch String getHostname() ( String hostnaam; try ( hostnaam = java. net. InetAddress . getLocalHost() . getHostName() ; ) catch (Uitzondering e) ( hostnaam = "Onbekend, " + e. getMessage() ; ) return hostnaam; ) @ Openbare booleaanse negeertThrowable() overschrijven ( return false ; ) @ Openbare ongeldige activatieOptions() ( ) ) Zondag 23 november 2014 Leren

Stel je nu voor dat we een reeks uitspraakvoorbeelden hebben van het woord 'odin', gegenereerd door dezelfde persoon. Met behulp van deze informatie kunnen we M-modelparameters aanpassen om de

P(O|M)

waarschijnlijkheid voor elk monster. Met andere woorden: we kunnen het model 'leren' en de herkenningskwaliteit verbeteren. Dit "leren" kan worden gedaan door het Baum-Welch-algoritme (specificatie / implementatie).

Onderwijs

Laten we ons nu voorstellen dat we verschillende uitspraakvoorbeelden van het woord 'één' hebben gegenereerd

specifieke persoon

Frames zijn geschiktere eenheden voor analyse in plaats van waarden van de amplitudes van het signaal, alleen maar omdat we te maken hebben met golven, en golven moeten worden geanalyseerd op intervallen, niet op specifieke punten :) Ondertussen zorgt de overlapping van frames ervoor dat we de analyse kunnen versoepelen resultaten door een frame te gebruiken als een soort “venster”, dat langs het originele signaal beweegt (de amplitudewaarden ervan).

Experimenten laten ons zien dat de optimale framelengte moet overeenkomen met het interval van 10 ms, "overlap" - tot 50%. Gegeven dat de gemiddelde woordlengte 500 ms is (althans in mijn experimenten), levert deze lengte ons ongeveer 500 / (10 * 0,5) = 100 frames per woord op.

Woorden splitsen

Het eerste probleem dat moet worden opgelost tijdens het spraakherkenningsproces is het opsplitsen van de spraak in verschillende woorden. Laten we onze taak vereenvoudigen en stellen dat de toespraak enkele pauzes (stilte-intervallen) bevat, die kunnen worden gebruikt als woordensplitser.

In dit geval moeten we de drempel vinden. Alle waarden daarboven zullen woorden zijn, daaronder – stilte. Hier kunnen we verschillende manieren gebruiken:

  • gebruik een constante (het originele signaal moet altijd onder dezelfde omstandigheden en op dezelfde manier worden gegenereerd);
  • cluster de signaalwaarden in twee sets: een met woordenwaarden en een andere met stilte (werkt alleen als stilte een grote rol speelt in het oorspronkelijke signaal);
  • analyseer de entropie;

Zoals je kunt raden, gaan we het laatste punt bespreken:) Laten we beginnen met de entropiedefinitie - “een maatstaf voor wanorde” (c). In ons geval laat de entropie zien hoeveel ons signaal “varieert” binnen een gegeven kader.

Om de entropie van een bepaald frame te berekenen, voert u de volgende stappen uit:

Dus we hebben de waarde van de entropie. Maar dit is slechts een kenmerk van een ander frame. We moeten het nog steeds ergens mee vergelijken als we het geluid en de stilte willen scheiden.
Gelukkig is entropie (in tegenstelling tot Root Mean Square) een relatief onafhankelijke maatstaf. Dit feit stelde ons in staat de drempelwaarde ervan als een constante te stellen (bijvoorbeeld 0,1).

Niettemin eindigen de problemen hier niet: (De entropie kan in het midden van een woord inzakken (in klinkers) of plotseling springen (bij een beetje ruis). repareer de eerste probleem moeten we de "minimale afstand tussen twee woorden" gebruiken en nabijgelegen framesets "lijmen", die verdeeld waren vanwege de doorbuiging. Het tweede probleem kan worden opgelost door “minimale woordlengte” te gebruiken en alle kandidaten te verwijderen zijn niet zolang we nodig hebben (en niet worden gebruikt in het eerste punt).

Als de toespraak geen pauzes tussen woorden heeft (of als de spreker te snel spreekt), kunnen we proberen sets van verschillende subreeksen uit de originele frameset te maken/extraheren. Vervolgens kunnen al deze subreeksen als bron voor de herkenning worden gebruikt Maar dat is absoluut een ander verhaal :)

MFCC

We hebben dus de set frames die overeenkomt met een bepaald woord. We kunnen de weg van de minste weerstand kiezen en het gemiddelde kwadraat (root mean square) van alle frame-elementen gebruiken als hun numerieke karakteristiek. Een dergelijke metriek geeft ons echter vrij weinig informatie voor verdere analyse.

Daarom is het tijd om Mel-Frequency Cepstral Coëfficiënten te introduceren. Volgens Wikipedia (die, zoals we weten, nooit liegt) zijn MFCC een soort signaalenergierepresentatie. De voordelen van hun gebruik zijn als volgt:

  • gebruik van het spectrum van het signaal (d.w.z. uitbreiding door de orthogonale basis van sinusfuncties), waarbij rekening wordt gehouden met de “golf”-aard van het signaal;
  • het spectrum wordt geprojecteerd op een speciale mel-schaal, waardoor we de meest waardevolle frequenties voor menselijke waarneming kunnen extraheren;
  • het aantal berekende coëfficiënten kan worden beperkt door elke waarde (bijvoorbeeld 12), die ons in staat stelt het frame te “knijpen” en, als gevolg daarvan, de hoeveelheid informatie voor verdere verwerking;

Het is tijd om het proces van MFCC-computergebruik voor een bepaald frame te overwegen.

Laten we ons frame beschouwen als een vector, waarbij N de grootte is.

Fourier-transformatie

Laten we eerst het spectrum van het signaal berekenen door de discrete Fourier-transformatie te berekenen (bij voorkeur de “snelle” FFT-implementatie).

Het wordt ook aanbevolen om de zogenaamde “venster” Hamming-functie toe te passen op “vloeiende” waarden aan de grenzen van het frame.

Als resultaat krijgen we de volgende vector:

Het is belangrijk om te begrijpen dat we na deze conversie de X-as hebben voor het frequentiesignaal (hz) en de Y-as voor de magnitude (als een manier om aan de complexe waarden te ontsnappen):

Berekening van Mel-coëfficiënten

Laten we beginnen bij de definitie van ‘mel’. Volgens Wikipedia (opnieuw) is mel een ‘psychofysische eenheid van geluidshoogte, gebaseerd op de subjectieve perceptie van een gemiddeld mens. Het hangt vooral af van de frequentie van het geluid (maar ook van volume en toon). Met andere woorden, deze waarde laat ons zien hoeveel het geluid van een bepaalde frequentie voor ons "waardevol" is.

Frequentie kan worden omgezet in mel-waarden met de volgende formule (herinner het als "formule-1"):

Achterwaartse transformatie ziet er op de volgende manier uit (herinner het als "formule-2"):

Grafiek van mel/frequentieschaal:

Maar laten we teruggaan naar onze taak. Stel dat we een frame hebben met de grootte N = 256 elementen. We weten (uit de gegevens van het audioformaat) dat de frequentie van het frame 16.000 Hz is. Laten we zeggen dat de menselijke spraak behoort tot het bereik Hz. Het aantal mel-coëfficiënten dat we willen vinden is M = 10.

Om het spectrum (dat we hierboven hebben berekend) uit te breiden met de mel-schaal, moeten we de “kam” van mel-filters creëren. In feite is elk mel-filter een driehoekige vensterfunctie, die de hoeveelheid mel-filters samenvat. energie bij een bepaald frequentiebereik (berekent dus de mel-coëfficiënt). Omdat we dus de hoeveelheid mel-coëfficiënten en het frequentiebereik kennen, kunnen we de volgende set filters construeren:

Merk op dat hoe groter het indexnummer van de mel-coëfficiënt is, hoe breder de basis van het filter is. Dit komt omdat het proces van het splitsen van frequentiebanden in de filterintervallen plaatsvindt op de mel-schaal (niet op de frequentieschaal).

Maar ik dwaal af. Voor ons geval is het frequentiebereik . Volgens de “formule-1” op de mel-schaal transformeert dit interval naar .

Vervolgens hebben we 12 referentiepunten nodig om 10 driehoekige filters te bouwen:

Merk op dat op de mel-schaal de punten uniform zijn gelegen. Laten we de waarden terug transformeren naar frequentie met behulp van de “formule-2”:

Zoals je kunt zien, beginnen onze waarden zich uit te strekken, waardoor de groeidynamiek van de "betekenis" van het geluid op de lage en hoge frequenties op één lijn komt te staan.

Laten we nu de schaal (die we hierboven hebben gevonden) opleggen aan het spectrum van ons frame. Zoals we ons herinneren, is de X-as voor frequentie. De lengte van het spectrum is 256 elementen, wat verwijst naar 16.000 Hz diapason. verkrijg de volgende formule:

in ons geval is dit gelijk aan

Dat is alles! Omdat we de referentiepunten op de X-as van ons spectrum kennen, kunnen we eenvoudig de benodigde filters construeren met de volgende formule:

Het toepassen van de mel-filters

Het toepassen van het filter op het spectrum betekent het paarsgewijze vermenigvuldigen van de filterwaarden met de spectrumwaarden. De som van de elementen in de resultaatvector is een mel-coëfficiënt. Omdat we M-filters hebben, zal het aantal mel-coëfficiënten hetzelfde zijn.

We moeten echter mel-filters niet op het spectrum toepassen, maar op de energie ervan. Vervolgens moeten we het resultaat logaritmisch berekenen. Er wordt aangenomen dat de bovenstaande truc de gevoeligheid voor ruisverhoudingen vermindert.

Cosinus-transformatie

Discrete Cosine Transform (DCT) wordt gebruikt om de "cepstrale" coëfficiënten te verkrijgen. Het maakt het mogelijk om de resultaten te ‘knijpen’, waardoor het belang van de eerste coëfficiënten toeneemt en het belang van de laatste wordt verminderd.

In het huidige geval gebruiken we DCT-II zonder extra vermenigvuldiging van de schaalfactor.

Nu hebben we de set M mfcc-coëfficiënten voor elk frame. Deze coëfficiënten kunnen (en zullen) worden gebruikt voor de verdere analyse!

Herkenningsalgoritme

Hier, beste lezer, zul je te maken krijgen met de grote teleurstelling. Op internet heb ik veel zeer (en niet erg) intelligente debatten gezien over wat de beste manier is voor spraakherkenning. Iemand komt op voor verborgen Markov-modellen, iemand - voor neurale netwerken... iemands gedachten kunnen helemaal niet worden begrepen :)

Het enige dat we met dat algoritme moeten doen, is het veranderen van de logica voor afstandsberekening. We moeten niet vergeten dat de mfcc-vector van het model een reeks mfcc-subvectoren (M-grootte) is, verkregen uit de frames. Het DTW-algoritme moet dus de afstanden tussen deze subvectoren berekenen, niet hun elementen. Elementen van de afstandsmatrix zijn dus Euclides-afstanden tussen op frames gebaseerde mfcc-subvectoren.

Experimenten

Ik heb de aanpak niet kunnen verifiëren op een groot aantal “trainings”-voorbeelden. Wat betreft testresultaten op basis van 3 voorbeelden van onderwijs... Ze zijn behoorlijk slecht – slechts 65% correcte erkenningen.

Mijn taak was echter de implementatie van de eenvoudigste spraakherkenningstoepassing. Zogenaamde “proof of concept” :)

Uitvoering

De oplettende lezer heeft gemerkt dat het artikel veel referenties bevat over het GitHub-project. Het is de moeite waard om te zeggen dat dit mijn eerste C++-project is sinds de universiteitstijd. Het is ook mijn eerste poging om iets ingewikkelder te berekenen dan het rekenkundig gemiddelde sinds dezelfde tijd... Met andere woorden: "er is absoluut geen garantie voor" (c) :)

Proloog

Laten we beginnen met het feit dat onze spraak een reeks geluiden is. Geluid is op zijn beurt een superpositie (superpositie) van geluidstrillingen (golven) met verschillende frequenties. Een golf wordt, zoals we uit de natuurkunde kennen, gekenmerkt door twee attributen: amplitude en frequentie.

Op deze manier worden mechanische trillingen omgezet in een reeks getallen die geschikt zijn voor verwerking op moderne computers.

Hieruit volgt dat de taak van spraakherkenning neerkomt op het "vergelijken" van een reeks numerieke waarden (digitaal signaal) en woorden uit een bepaald woordenboek (bijvoorbeeld Russisch).

Laten we eens kijken hoe deze "vergelijking" feitelijk kan worden geïmplementeerd.

Gegevens invoeren

Laten we zeggen dat we een bestand/stream met audiogegevens hebben. Allereerst moeten we begrijpen hoe het werkt en hoe we het moeten lezen. Laten we naar de eenvoudigste optie kijken: een WAV-bestand.

Het formaat impliceert de aanwezigheid van twee blokken in het bestand. Het eerste blok is een header met informatie over de audiostream: bitrate, frequentie, aantal kanalen, bestandslengte, etc. Het tweede blok bestaat uit ‘ruwe’ gegevens: datzelfde digitale signaal, een reeks amplitudewaarden.

De logica voor het lezen van gegevens is in dit geval vrij eenvoudig. We lezen de header, controleren enkele beperkingen (bijvoorbeeld geen compressie) en slaan de gegevens op in een speciaal toegewezen array.

Herkenning

Puur theoretisch kunnen we nu (element voor element) het monster dat we hebben vergelijken met een ander monster waarvan de tekst ons al bekend is. Dat wil zeggen, probeer spraak te ‘herkennen’... Maar het is beter om dit niet te doen :)

Onze aanpak moet bestand zijn (nou ja, in ieder geval een beetje) tegen veranderingen in het timbre van de stem (van de persoon die het woord uitspreekt), het volume en de snelheid van de uitspraak. Uiteraard kan dit niet worden bereikt door element-voor-element vergelijking van twee audiosignalen.

Daarom zullen we een iets ander pad bewandelen.

Lijsten

Laten we eerst onze gegevens opdelen in kleine tijdsperioden: frames. Bovendien mogen de frames niet strikt na elkaar gaan, maar "overlappen". Die. het einde van het ene frame moet het begin van een ander frame kruisen.

Frames zijn een geschiktere eenheid voor gegevensanalyse dan specifieke signaalwaarden, omdat het veel handiger is om golven over een bepaald interval te analyseren dan op specifieke punten. Door de "overlappende" opstelling van frames kunt u de resultaten van frameanalyse gladstrijken, waardoor het idee van frames verandert in een "venster" dat langs de oorspronkelijke functie (signaalwaarden) beweegt.

Experimenteel is vastgesteld dat de optimale framelengte moet overeenkomen met een tussenruimte van 10 ms, met een “overlap” van 50%. Gezien het feit dat de gemiddelde woordlengte (althans in mijn experimenten) 500 ms is, levert deze stap ons ongeveer 500 / (10 * 0,5) = 100 frames per woord op.

Woorden splitsen

De eerste taak die moet worden opgelost bij het herkennen van spraak is het verdelen van deze spraak in afzonderlijke woorden. Laten we voor de eenvoud aannemen dat in ons geval de spraak enkele pauzes (stilte-intervallen) bevat, die kunnen worden beschouwd als ‘scheiders’ van woorden.

In dit geval moeten we een bepaalde waarde vinden, een drempelwaarde - waarden waarboven een woord is, waaronder stilte. Er kunnen hier verschillende opties zijn:

  • ingesteld als constante (werkt als het originele signaal altijd onder dezelfde omstandigheden en op dezelfde manier wordt gegenereerd);
  • signaalwaarden clusteren door expliciet de reeks waarden te selecteren die overeenkomen met stilte (dit werkt alleen als stilte een aanzienlijk deel van het oorspronkelijke signaal in beslag neemt);
  • entropie analyseren;

Zoals je misschien al geraden hebt, zullen we het nu over het laatste punt hebben :) Laten we beginnen met het feit dat entropie een maatstaf is voor wanorde, “een maatstaf voor de onzekerheid van elke ervaring” (c). In ons geval betekent entropie hoeveel ons signaal binnen een bepaald frame ‘fluctueert’.

En dus kregen we de entropiewaarde. Maar dit is slechts een ander kenmerk van het frame, en om geluid van stilte te scheiden, moeten we het nog steeds ergens mee vergelijken. Sommige artikelen raden aan om de entropiedrempel gelijk te stellen aan het gemiddelde tussen de maximale en minimale waarden (van alle frames). In mijn geval leverde deze aanpak echter geen goede resultaten op.
Gelukkig is entropie (in tegenstelling tot hetzelfde gemiddelde kwadraat van waarden) een relatief onafhankelijke grootheid. Hierdoor kon ik de waarde van de drempelwaarde selecteren in de vorm van een constante (0,1).

Niettemin houden de problemen daar niet op: (Entropie kan inzakken in het midden van een woord (op klinkers), of kan plotseling omhoog springen als gevolg van een beetje ruis. Om het eerste probleem aan te pakken, moeten we de concept van “minimale afstand tussen woorden” en “aan elkaar lijmen” nabijgelegen sets frames gescheiden door verzakkingen. Het tweede probleem wordt opgelost door de “minimale woordlengte” te gebruiken en alle kandidaten af ​​te snijden die niet door de selectie zijn gekomen (en dat ook niet waren). gebruikt in het eerste punt).

Als de toespraak in principe niet 'gearticuleerd' is, kunt u proberen de oorspronkelijke reeks frames op te splitsen in op een bepaalde manier voorbereide subreeksen, die elk aan een herkenningsprocedure worden onderworpen. Maar dat is een heel ander verhaal :)

MFCC

En dus hebben we een reeks frames die overeenkomen met een bepaald woord. We kunnen het pad van de minste weerstand volgen en het wortelgemiddelde van al zijn waarden gebruiken als numeriek kenmerk van het frame. Een dergelijke metriek bevat echter zeer weinig informatie die geschikt is voor verdere analyse.

Dit is waar Mel-frequentie cepstrale coëfficiënten een rol gaan spelen. Volgens Wikipedia (die, zoals je weet, niet liegt), is MFCC een soort weergave van het energiespectrum van een signaal. De voordelen van het gebruik ervan zijn als volgt:

  • Er wordt gebruik gemaakt van het signaalspectrum (dat wil zeggen de basisuitbreiding van orthogonale [co]sinusfuncties), wat het mogelijk maakt om bij verdere analyse rekening te houden met de golfaard van het signaal;
  • Het spectrum wordt geprojecteerd op een speciale mel-schaal, waardoor u de belangrijkste frequenties voor menselijke waarneming kunt benadrukken;
  • Het aantal berekende coëfficiënten kan worden beperkt tot elke waarde (bijvoorbeeld 12), waardoor u het frame kunt "comprimeren" en, als gevolg daarvan, de hoeveelheid verwerkte informatie;

Laten we eens kijken naar het proces van het berekenen van MFCC-coëfficiënten voor een bepaald frame.

Laten we ons frame voorstellen als een vector, waarbij N de grootte van het frame is.

Uitbreiding van de Fourier-serie

Allereerst berekenen we het spectrum van het signaal met behulp van de discrete Fourier-transformatie (bij voorkeur de “snelle” FFT-implementatie).

Dat wil zeggen, het resultaat is een vector met de volgende vorm:

Het is belangrijk om te begrijpen dat we na deze transformatie langs de X-as de frequentie (hz) van het signaal hebben, en langs de Y-as de magnitude (als een manier om weg te komen van complexe waarden):

Berekening van melfilters

Laten we beginnen met wat mel is. Nogmaals, volgens Wikipedia is mel een ‘psychofysische eenheid van toonhoogte’, gebaseerd op de subjectieve perceptie van gemiddelde mensen. Hangt voornamelijk af van de frequentie van het geluid (evenals het volume en de klankkleur). Met andere woorden, deze waarde laat zien hoeveel een geluid van een bepaalde frequentie voor ons “betekenisvol” is.

Je kunt de frequentie omzetten in krijt met behulp van de volgende formule (onthoud het als “formule-1”):

De inverse transformatie ziet er als volgt uit (onthoud het als “formule-2”):

mel/frequentiegrafiek:

Maar laten we terugkeren naar onze taak. Laten we zeggen dat we een frame van 256 elementen hebben. We weten (uit de audioformaatgegevens) dat de audiofrequentie in dit frame 16.000 Hz is. Laten we aannemen dat de menselijke spraak in het bereik van Hz ligt. Laten we het aantal vereiste kleine coëfficiënten instellen op M = 10 (aanbevolen waarde).

Om het hierboven verkregen spectrum langs de mel-schaal te ontleden, zullen we een "kam" van filters moeten creëren. In wezen is elk mel-filter een driehoekige vensterfunctie waarmee u de hoeveelheid energie in een bepaald frequentiebereik kunt optellen en zo de mel-coëfficiënt kunt verkrijgen. Als we het aantal kleine coëfficiënten en het geanalyseerde frequentiebereik kennen, kunnen we een reeks filters als deze bouwen:

Houd er rekening mee dat hoe hoger het serienummer van de krijtcoëfficiënt, hoe breder de filterbasis. Dit komt door het feit dat de verdeling van het frequentiebereik dat voor ons van belang is in bereiken die door filters worden verwerkt, plaatsvindt op krijtschaal.

Maar we werden weer afgeleid. En dus is voor ons geval het frequentiebereik dat ons interesseert gelijk aan . Volgens formule-1 verandert dit bereik op de krijtschaal in .

m[ik] =

Houd er rekening mee dat de punten op de krijtschaal gelijkmatig verdeeld zijn. Laten we de schaal terug naar hertz converteren met behulp van formule-2:

h[ik] =

Zoals je kunt zien, begint de schaal zich nu geleidelijk uit te rekken, waardoor de dynamiek van de groei van “significantie” bij lage en hoge frequenties wordt geëgaliseerd.

Nu moeten we de resulterende schaal over het spectrum van ons frame heen leggen. Zoals we ons herinneren, hebben we langs de X-as frequentie. De lengte van het spectrum is 256 elementen, terwijl 16000 Hz erin past. Nadat u een eenvoudig deel hebt opgelost, kunt u de volgende formule krijgen:

f(i) = vloer((frameSize+1) * h(i) / sampleRate)

wat in ons geval gelijkwaardig is

f(ik) = 4, 8, 12, 17, 23, 31, 40, 52, 66, 82, 103, 128

Dat is het! Als we de referentiepunten op de X-as van ons spectrum kennen, is het eenvoudig om de filters te construeren die we nodig hebben met behulp van de volgende formule:

Toepassing van filters, logaritme van spectrumenergie

De toepassing van een filter bestaat uit een paarsgewijze vermenigvuldiging van de waarden ervan met de spectrumwaarden. Het resultaat van deze bewerking is de mel-coëfficiënt. Omdat we M-filters hebben, zullen er hetzelfde aantal coëfficiënten zijn.

We moeten mel-filters echter niet op de spectrumwaarden toepassen, maar op de energie ervan. Neem vervolgens de logaritme van de resultaten. Er wordt aangenomen dat dit de gevoeligheid van de coëfficiënten voor ruis vermindert.

Cosinus-transformatie

De discrete cosinustransformatie (DCT) wordt gebruikt om deze “cepstrale” coëfficiënten te verkrijgen. De betekenis ervan is om de verkregen resultaten te ‘comprimeren’, waardoor de betekenis van de eerste coëfficiënten toeneemt en de betekenis van de laatste afneemt.

IN in dit geval DCTII wordt gebruikt zonder enige vermenigvuldiging met (schaalfactor).

Nu hebben we voor elk frame een set M mfcc-coëfficiënten die kunnen worden gebruikt voor verdere analyse.

Voorbeeldcode voor de bovenstaande methoden is te vinden.

Herkenningsalgoritme

Dit, beste lezer, is waar de grootste teleurstelling op u wacht. Op internet heb ik veel zeer intelligente (en niet zo intelligente) debatten gezien over welke herkenningsmethode beter is. Sommige mensen zijn voorstander van verborgen Markov-modellen, anderen zijn voorstander neurale netwerken, iemands gedachten zijn in principe onmogelijk om te begrijpen :)

In ieder geval worden er veel voorkeuren gegeven aan SMM, en het is hun implementatie die ik aan mijn code ga toevoegen... in de toekomst :)

Op op dit moment Ik stel voor om ons te concentreren op een veel minder effectieve, maar veel eenvoudigere methode.

En laten we dus niet vergeten dat het onze taak is om een ​​woord uit een bepaald woordenboek te herkennen. Voor de eenvoud zullen we de namen van de eerste tien cijfers herkennen: "één", "twee", "drie", "vier", "vijf", "zes", "zeven", "acht", "negen", "tien".

Laten we nu een iPhone/Android pakken en L-collega's doornemen met het verzoek deze woorden te dicteren voor opname. Laten we vervolgens (in een lokale database of eenvoudig bestand) met elk woord L sets mfcc-coëfficiënten van de overeenkomstige records associëren.

We zullen deze correspondentie “Model” noemen, en het proces zelf: Machine Learning! In feite heeft het simpelweg toevoegen van nieuwe monsters aan de database een uiterst zwak verband machinaal leren... Maar de term is te modieus :)

Nu komt onze taak neer op het selecteren van het “dichtstbijzijnde” model voor een bepaalde set mfcc-coëfficiënten (herkend woord). Op het eerste gezicht kan het probleem vrij eenvoudig worden opgelost:

  • voor elk model vinden we de gemiddelde (Euclidische) afstand tussen de geïdentificeerde mfcc-vector en de modelvectoren;
  • we selecteren als het juiste model de gemiddelde afstand die het kleinst is;

Hetzelfde woord kan echter zowel door Andrei Malakhov als door enkele van zijn Estse collega's worden uitgesproken. Met andere woorden: de grootte van de mfcc-vector voor hetzelfde woord kan verschillend zijn.

Gelukkig is het probleem van het vergelijken van reeksen van verschillende lengtes al opgelost in de vorm van het Dynamic Time Warping-algoritme. Dit dynamische programmeeralgoritme wordt zowel in de burgerlijke Wiki als op de Orthodoxe Habr perfect beschreven.

De enige verandering die eraan moet worden aangebracht, is de manier waarop de afstand wordt gevonden. We moeten niet vergeten dat de mfcc-vector van een model feitelijk een reeks mfcc-‘subvectoren’ van dimensie M is, verkregen uit frames. Het DTW-algoritme moet dus de afstand vinden tussen reeksen van dezelfde “subvectoren” van dimensie M. Dat wil zeggen dat de afstanden (Euclidisch) tussen mfcc “subvectoren” van frames moeten worden gebruikt als de waarden van de afstandsmatrix.

Experimenten

Ik heb niet de kans gehad om het werk te controleren deze aanpak op een grote ‘training’-steekproef. De resultaten van tests op een steekproef van 3 exemplaren voor elk woord in niet-synthetische omstandigheden lieten, op zijn zachtst gezegd, niet het beste resultaat zien: 65% correcte herkenningen.

Mijn doel was echter om een ​​zo eenvoudig mogelijke spraakherkenningstoepassing te maken. Om zo te zeggen “proof of concept” :)

Uitvoering

Een aandachtige lezer merkte op dat het artikel veel links naar het GitHub-project bevat. Het is vermeldenswaard dat dit mijn eerste C++-project is sinds de universiteit. Dit is ook mijn eerste poging om iets complexer te berekenen dan het rekenkundig gemiddelde sinds dezelfde universiteit... Met andere woorden, er is absoluut geen garantie voor (c) :)


Een CTE (common table expression) kan naar zichzelf verwijzen, waardoor een recursieve CTE ontstaat.
Een recursieve CTE wordt herhaaldelijk uitgevoerd om een ​​subset van de gegevens terug te sturen totdat een uiteindelijke resultaatset is verkregen.
Meestal worden recursieve queries gebruikt om hiërarchische gegevens terug te geven, bijvoorbeeld: het weergeven van werknemers in een organisatiestructuur of het genereren van een reeks.

Structuur van een recursieve CTE

WITH cte_name (kolomnaam [,...n]) AS (CTE_query_definition –- Ankerlid is gedefinieerd. UNION ALL CTE_query_definition –- Recursief lid is gedefinieerd verwijzend naar cte_name.) -- Verklaring met behulp van de CTE SELECT * FROM cte_name
CTE is onderverdeeld in vast En recursief elementen. Begint vast element met de creatie van de eerste oproep. Recursief het element verwijst naar het vastgezette element en wordt aangeroepen totdat het een lege set retourneert.


Klassiek voorbeeld met medewerkers

DECLARE @Employees TABEL (ID int NOT NULL, nvarchar(200) NOT NULL, ManagerID int NULL) INSERT INTO @Employees WAARDEN (1, N"Ken", NULL) ,(2, N"Brian",1) ,(3 , N"Stephen", 2) ,(4, N"Michael", 2) ,(5, N"Linda", 3) ,(6, N"Syed", 4) ,(7, N"Lynn", 5) ,(8, N"David", NULL) ,(9, N"Maria", 8);
ManagerID - directe beheerder.
In dit geval hebben we twee bazen, Ken en David, we hebben alle medewerkers van Ken's afdeling nodig.
MET DirectReports (ID, ManagerID, Niveau) AS (- Ankerliddefinitie SELECT e.ID, e., ManagerID, 0 AS Niveau FROM @Employees AS e WHERE e.ID = 1 UNION ALL -- Recursieve liddefinitie SELECT e .ID, bijv., e.ManagerID, Level + 1 FROM @Employees AS e INNER JOIN DirectReports AS d ON e.ManagerID = d.ID) -- Verklaring die de CTE SELECT ID, , ManagerID, Level FROM DirectReports uitvoert

Latijnse hoofdletters

Vaak is er ook behoefte aan het genereren van verschillende sequenties.
;WITH Letters AS(SELECT ASCII("A") code, CHAR(ASCII("A")) letter UNION ALL SELECT code+1, CHAR(code+1) FROM Letters WHERE code+1<= ASCII("Z")) SELECT letter FROM Letters;

Nummerreeks

DECLARE @startnum INT=1 DECLARE @eindnum INT=55 ; MET gen AS (SELECT @startnum AS num UNION ALLES SELECTEER num+1 VAN gen WAAR num+1<=@endnum) SELECT * FROM gen option (maxrecursion 55)

De MAXRECURSION-hint kan worden gebruikt om toegang tot een oneindige lus te voorkomen als gevolg van een verkeerd opgemaakte recursieve CTE-expressie.

Tijdslots combineren

Er is een directory met prijzen voor producten, om het eten van aangrenzende intervallen te optimaliseren
DECLARE @ProductPrice TABEL (ProductID int, Price money, BeginDate date, EndDate date NULL) INSERT INTO @ProductPrice VALUES (1, 11.11, "20170101", "20170215") ,(1, 11.11, "20170216", "20170615") ,(1, 22.1, "20170616", null) ,(2, 33, "20170101", "20170201") ,(2, 33, "20170501", "20170601") ,(3, 12, "20170101", "20170215") ,(3, 12, "20170216", null);
Het is nodig om het zo te krijgen
--1 11 "20170101" "20170615" --1 22,1 "20170616" nul --2 33 "20170101" "20170201" --2 33 "20170501" "20170601" --3 12 "20170101" nul
Eén oplossing is recursie in CTE
;met cte as (selecteer a.ProductID, a.Price, a.BeginDate, a.EndDate van @ProductPrice a links join @ProductPrice b op a.ProductID=b.ProductID en dateadd(day,-1,a.BeginDate) =b.EndDate en a.Price=b.Price waarbij b.BeginDate nul is, selecteer allemaal a.ProductID, a.Price, a.BeginDate, b.EndDate van cte a join @ProductPrice b op a.ProductID=b. ProductID en dateadd(day,-1,b.BeginDate)=a.EndDate en a.Price=b.Price) selecteer ProductID, Price, BeginDate, nullif(max(isnull(EndDate,"32121231")),,"32121231 " ) Einddatum uit cte-groep op ProductID, Prijs, Begindatum

Query's van hetzelfde type combineren

Geef het aantal verkopers weer met het label 'sterke verkoper' of 'zwak'. Een verkoper met een gemiddelde transactiewaarde van meer dan 500 wordt als sterk beschouwd, een zwakke verkoper wordt als minder dan 500 beschouwd.(het verzoek staat op blad 39 , resultaat - Tafel 51 ):

GROEPEREN OP N_Verkoper

GEMIDDELD (kosten)<500

GROEPEREN OP N_Verkoper

MET gemiddelde (kosten)> 500;

Tafel 51. Resultaat van een zoekopdracht bij de Union-operator

Geef het aantal verkopers weer met het label 'sterke verkoper' of 'zwak'. Een verkoper met een gemiddelde transactiewaarde van meer dan 500 wordt als sterk beschouwd, een zwakke verkoper is minder dan 500, gesorteerd op activiteit(het verzoek staat op blad 40 , resultaat - Tafel 52 ):

SELECTEER N_Seller, 'Zwakke verkoper' als activiteit UIT Transacties

GROEPEREN OP N_Verkoper

GEMIDDELD (kosten)<500

UNION SELECT N_Seller, 'Sterke verkoper' als activiteit UIT Transacties

GROEPEREN OP N_Verkoper

MET gemiddelde (kosten)> 500

BESTEL PER Activiteit;

Tafel 52. Resultaat van een verzoek bij Union en Order by

De operators voor snijpunten en uitzonderingen werken op dezelfde manier en komen overeen met de snijpunt- en verschilbewerkingen in relationele algebra. Voor het onderwerp 'verkoop' zijn de lijsten met steden van kopers en verkopers mogelijk niet hetzelfde, dus we kunnen met deze lijsten een aantal problemen oplossen, zoals weergegeven in de tabel. 53.

Tafel 53. Join-operatoren gebruiken voor query's van hetzelfde type

Naar blad. 25 gaf een voorbeeld van een verzoek waarbij ondergeschikten van Ivanov werden gefouilleerd. De vraag rijst hoe de hele hiërarchie van ondergeschikten moet worden opgebouwd, en niet alleen zijn directe ondergeschikten. In feite moeten we dezelfde Sheet-query toepassen. 25 op het resultaat van het zelf uitvoeren, dan weer, enz., totdat er ondergeschikten zijn op steeds lagere niveaus van de hiërarchie.

Construeer een hiërarchie van alle ondergeschikten van Ivanov in “lengte”(het verzoek staat op blad 41 , resultaat - Tafel 54 ).

Recursieve zoekopdrachten kunnen worden geïdentificeerd door het sleutelwoord WITH RECURSIVE. Om recursief te kunnen worden aangeroepen, moet de zoekopdracht daadwerkelijk een naam hebben (in dit geval - Prod). Het verzoek bestaat uit twee delen, gecombineerd met behulp van UNION. In het eerste deel - de bazen, in het tweede deel - hun ondergeschikten. De zoekopdracht vindt de eerste slaaf, vervolgens de eerste slaaf van de eerste, enzovoort. Deze zoekopdrachten worden lange zoekopdrachten genoemd.

Laken. 41. Recursieve zoekopdracht “in lengte”

(SELECTEER N, Naam

VAN Verkopers

WAAR Naam = 'Ivanov'

SELECTEER Verkopers. N, Verkoper.Naam

VAN Verkopers, Verkoop

SELECTEER * VAN Cont;

Tafel 54. Resultaat van een recursieve zoekopdracht “in lengte”

Als we eerst alle ondergeschikten van Ivanov moeten opsommen, daarna de ondergeschikten van Ivanov, enz., dan moeten we breedte-eerst recursieve zoekopdrachten gebruiken met behulp van de SEARCH BREADTH FIRST operator. De query stelt het iteratienummer in met behulp van de SET-operator.

Bouw een hiërarchie van alle ondergeschikten van Ivanov “in de breedte”(het verzoek staat op blad 42 , resultaat - Tafel 55 ).

Laken. 42. Recursieve breedte-eerste zoekopdracht

MET RECURSIEVE Cont (N, Naam, N_Head) als

(SELECTEER N, Naam

VAN Verkopers

WAAR Naam = 'Ivanov'

VAN Verkopers, Verkoop

WAAR Cont.N = Verkopers.N_Chief);

ZOEK EERST IN DE BREEDTE DOOR N_Chief, N

SET order_kolom

SELECTEER * VAN Cont

BESTEL OP order_kolom;

Tafel 55. Resultaat van de “breedte eerst”-vraag

Bij een recursieve query is een lussituatie mogelijk. Hoewel dit niet in ons voorbeeld voorkomt, kunnen we aannemen dat er situaties zijn waarin één team aan twee projecten werkt, en één persoon in het eerste project de baas is, en in het tweede een ondergeschikte, en omgekeerd, de tweede persoon in het eerste project is een ondergeschikte. En in de tweede - de baas. In dit geval worden eerdere recursieve zoekopdrachten in een lus weergegeven.

Bouw een hiërarchie van alle ondergeschikten van Ivanov, rekening houdend met de mogelijkheid van looping(Blad 43) .

Laken. 43. Recursieve zoekopdracht waarbij rekening wordt gehouden met looping

MET RECURSIEVE Cont (N, Naam) als

(SELECTEER N, Naam

VAN Verkopers

WAAR Naam = 'Ivanov'

SELECTEER Verkopers. N, Verkopers. Naam

VAN Verkopers, Verkoop

WAAR Cont.N = Verkopers.N_Chief);

SET cyclusmarkering op “Y” standaard “N”

GEBRUIKEN fietspad

SELECTEER * VAN Cont

D/Z 6. Voor het voorbeeld uit D/Z 4 bedenk je de volgende vragen:

  1. Een enkele tabelquery die een aggregatiefunctie evalueert met behulp van de instructies 'waar', 'volgorde per'.
  2. Query's voor het samenvoegen van meerdere tabellen met behulp van de Where-clausule, één tabel moet meerdere keren worden gebruikt in de query onder aliassen.
  3. Zoekopdracht via rechts, links of volledige join.
  4. Query met twee subquery's in waar en met clausules
  5. Query's uitvoeren met een subquery waarbij gebruik wordt gemaakt van alles, enige of bestaande.
  6. Een geneste query met een samenvoeging van twee tabellen (een van de join-variëteiten), indirect verbonden door een derde.
  7. Query's van hetzelfde type combineren.
  8. Recursieve zoekopdracht.

Zelftestvragen:

1. Wat is het verschil tussen het gebruik van de operatoren groeperen op, groeperen op samenvouwen, groeperen op samenvattende kubus, groeperen, organisator op, partitie?

2. Wat is het verschil tussen het gebruik van waar en het hebben van instructies?

3. Met welke join...on-query komt de Where-clausule overeen?

4. Welke relationele algebra-bewerking komt overeen met de waar-operator?

5. Met welke relationele algebra-operatie komt de join-operator overeen?