
Teilen Sie:
IOS-Entwickler, der sich für Data Science und maschinelles Lernen begeistert. Ich möchte, dass die Menschen verstehen, was maschinelles Lernen ist und wie wir es in unseren Applications nutzen können.
Aufbau eines Modells für maschinelles Lernen zur Beantwortung maschineller Erkennungsaufgaben
Lesedauer: 6 Minuten
Wollten Sie schon immer wissen, wann ein Anrufbeantworter einen Voice-Anruf tätigt? Nein? Das ist okay. Ich schon!
Voraussetzungen
In diesem Beitrag wird davon ausgegangen, dass Sie über grundlegende Python-Kenntnisse verfügen und ein sehr grundlegendes Verständnis von maschinellem Lernen haben. Wir gehen auf einige grundlegende Konzepte des maschinellen Lernens ein und haben in diesem Beitrag auf weitere Ressourcen verlinkt.
Vor ein paar Wochen erhielt ich von einem unserer Vertriebsingenieure eine Anfrage zu einem Anrufbeantworter-Erkennungsdienst für einen Kunden. Er wollte eine Möglichkeit haben, eine Nachricht an einen Anrufbeantworter zu senden, wenn der Anruf auf der Mailbox landet.
Ich habe einige Nachforschungen darüber angestellt, und es scheint möglich zu sein, aber ich konnte nichts darüber finden, WIE das gemacht wurde. Also habe ich beschlossen, es herauszufinden...
Der erste Gedanke war, ein maschinelles Lernmodell zu entwickeln, das erkennt, wann der beep Ton eines Anrufbeantworters zu hören ist. In diesem Beitrag gehen wir darauf ein, wie das Modell trainiert und in einer Anwendung eingesetzt wurde.
Ausbildungsdaten
Bevor wir mit dem Aufbau eines maschinellen Lernmodells beginnen können, benötigen wir einige Daten. Für dieses Problem benötigen wir eine Reihe von Audiodateien mit den Geräuschen des Anrufbeantworters beep Geräuschen, etwa so:
https://soundcloud.com/user-872225766-984610678/7eaeb600-0202-11e9-bb68-51880c8718e4 oder dies:
https://soundcloud.com/user-872225766-984610678/7eaeb600-0202-11e9-bb68-51880c8718e4
Wir müssen auch Beispiele einbeziehen, die den Piepton nicht enthalten:
https://soundcloud.com/user-872225766-984610678/7eaeb600-0202-11e9-bb68-51880c8718e4
Da es diese Art von Daten im Internet nicht zu geben scheint, mussten wir so viele Proben wie möglich von Pieptönen und anderen Geräuschen von Anrufen sammeln, um unser Modell zu trainieren. Zu diesem Zweck habe ich eine Webseite erstellt, auf der jeder seine Voicemail-Ansage aufzeichnen kann.
Wenn Sie die Vonage-Nummer anrufen, wird die Anwendung einen ausgehenden Anruf an dieselbe Nummer tätigen. Wenn der Anruf eingeht, müssen Sie ihn nur direkt an die Voicemail senden. Von dort aus zeichnen wir den Anruf mit der record Aktion auf und speichern die Datei in einem Google Cloud Storage Bucket. Nachdem wir eine Reihe von Beispielen gesammelt haben, können wir uns die Daten ansehen.
Bei jedem Projekt zum maschinellen Lernen müssen wir uns zunächst die Daten ansehen und sicherstellen, dass wir mit ihnen arbeiten können.
Da es sich um Audio handelt, können wir nicht ansehen direkt ansehen, aber wir können die Audiodateien mit einem Mel-Spektrogramm visualisieren, das wie folgt aussieht:

Ein Mel-Spektrogramm zeigt eine Reihe von Frequenzen an (die niedrigste unten, die höchste oben) und gibt an, wie laut Ereignisse bei verschiedenen Frequenzen sind. Im Allgemeinen werden laute Ereignisse hell und leise Ereignisse dunkel dargestellt.
Wir müssen ein paar Dateien mit beiden Arten von Klängen laden, sie darstellen und sehen, wie sie aussehen. Um das mel-Spektrogramm zu zeigen, werden wir ein Python-Paket namens Librosa um die Audioaufnahme zu laden und dann das mel-Spektrogramm mit matplotlib, einem weiteren Python-Paket zum Erstellen von Diagrammen und Kurven.
import glob
import librosa
import matplotlib.pyplot as plt
%matplotlib inline
def plot_specgram(file_path):
y, sr = librosa.load(file_path)
S = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=128,fmax=8000)
plt.figure(figsize=(10, 4))
librosa.display.specshow(librosa.power_to_db(S,ref=np.max),y_axis='mel', fmax=8000,x_axis='time')
plt.colorbar(format='%+2.0f dB')
plt.title(file_path.split("/")[-2])
plt.tight_layout()
sound_file_paths = [
"answering-machine/07a3d677-0fdd-4155-a804-37679c039a8e.wav",
"answering-machine/26b25bb7-6825-43e7-b8bd-03a3884ed694.wav",
"answering-machine/2a685eda-8dd9-4a4d-b00e-4f43715f81a4.wav",
"answering-machine/55b654e5-7d9f-4132-bc98-93e576b2d665.wav",
"speech-recordings/110ac98e-34fa-42e7-bbc5-450c72851db5.wav",
"speech-recordings/3840b850-02e6-11e9-aa3d-ad1a095d8d72.wav",
"speech-recordings/55b654e5-7d9f-4132-bc98-93e576b2d665.wav",
"speech-recordings/81270a2a-088b-4e3c-9f47-fd927a90b0ab.wav"
]
for file in sound_file_paths:
plot_specgram(file)Schauen wir uns an, wie die einzelnen Audiodateien aussehen.

Sie können deutlich erkennen, welche Audiodatei eine beep ist und welche nur speech.
Bevor wir unser Modell trainieren, nehmen wir alle Aufnahmen, die wir haben, sowohl für beeps und Nicht-Pieptöne, die als speechund wandeln jede Aufzeichnung in einen Vektor von Numbers um, da unser Modell nur Zahlen und keine Bilder akzeptiert.
Um die Daten zu berechnen, verwenden wir die Mel-Frequenz-Cepstral-Koeffizienten (MFCCs) der einzelnen Proben. Anschließend speichern wir diesen Wert in einer csv-Datei, damit wir die MFCCs nicht erneut berechnen müssen.
Für jedes Audiobeispiel enthält die csv-Datei den Pfad zum Audiobeispiel, die Bezeichnung des Audiobeispiels(beep, oder speech), den MFCC und die Dauer des Tonstücks (mit der get_duration Funktion in librosa). Wir haben auch einige andere Audiomerkmale ausprobiert, darunter Chroma, Kontrast und Tonnetz). Diese Merkmale wurden jedoch in der neuesten Version des Modells nicht verwendet.
Schauen wir uns nun die ersten 5 Zeilen der csv-Datei an, um zu sehen, wie die Daten aussehen.

Jede Zeile enthält einen eindimensionalen Vektor für jedes der Audiomerkmale. Diese Daten werden wir zum Trainieren unseres Modells verwenden.
Ausbildung
Jetzt werden wir diese Daten nehmen und ein Modell damit trainieren. Für das Training verwenden wir das Paket Scikit-learn. Scikit-learn ist ein großartiges Paket, mit dem Sie einfache Modelle für maschinelles Lernen erstellen können, ohne ein Experte für maschinelles Lernen sein zu müssen.
Für jedes Modell nahmen wir unseren Datenrahmen, der die Bezeichnung jeder Audiodatei enthielt, (beep, speech), mit dem MFCC für jedes Sample, teilten ihn in einen Trainings- und einen Testdatensatz auf und ließen jedes Modell durch die Daten laufen.
def train(features, model):
X, y = generateFeaturesLabels(features)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
model.fit(X_train, y_train)
print("Score:",model.score(X_test, y_test))
cross_val_scores = cross_val_score(model, X, y, cv=5, scoring='f1_macro')
print("cross_val_scores:", cross_val_scores)
print("Accuracy: %0.2f (+/- %0.2f)" % (cross_val_scores.mean(), cross_val_scores.std() * 2))
predictions = model.predict(X_test)
cm = metrics.confusion_matrix(y_test, predictions)
plot_confusion_matrix(cm, class_names)
return modelDie Funktion train nimmt eine Liste von Merkmalen, die wir verwenden wollen, d.h. nur MFCC des Audiobeispiels, sowie das Modell, mit dem wir trainieren wollen. Dann geben wir unsere Punktzahl aus, die angibt, wie gut das Modell abgeschnitten hat. Wir drucken auch die Kreuzvalidierungsbewertung aus. Dadurch wird sichergestellt, dass unser Modell korrekt trainiert wurde. Die Funktion plot_confusion_matrix Funktion stellt eine Konfusionsmatrix dar, die genau zeigt, was das Modell richtig und was falsch gemacht hat.

Anschließend haben wir die folgenden Modelle ausprobiert und ihre Genauigkeit (0-100%) angegeben.
RandomForestClassifier 97% Genauigkeit
LogistischeRegression 96% Genauigkeit
Support-Vektor-Maschinen 84% Genauigkeit
Alle diese Modelle schnitten sehr gut ab, außer Support Vector Machines. Das beste Modell war Gaussian Naive Bayes, also werden wir dieses Modell verwenden. In unserer oben dargestellten Konfusionsmatrix waren von den 67 Beispielen 40 Proben, die als a beep waren tatsächlich beepsund 22 Proben, die als a vorhergesagt wurden speech waren in der Tat speech Beispiele. Allerdings war 1 Beispiel, das als a vorhergesagt wurde beep war tatsächlich speech.
Nachdem wir unser Modell erstellt haben, müssen wir es in einer Datei speichern und dann in unsere VAPI-Anwendung importieren.
import pickle
filename = "model.pkl"
pickle.dump(model, open(filename, 'wb')) Erstellung der Anwendung
Der letzte Teil besteht nun darin, unser Modell in eine VAPI-Anwendung zu integrieren.
Vonage API-Konto
Um dieses Tutorial durchzuführen, benötigen Sie ein Vonage API-Konto. Wenn Sie noch keines haben, können Sie sich noch heute anmelden und mit einem kostenlosen Guthaben beginnen. Sobald Sie ein Konto haben, finden Sie Ihren API-Schlüssel und Ihr API-Geheimnis oben auf dem Vonage-API-Dashboard.
In diesem Lernprogramm wird auch eine virtuelle Telefonnummer verwendet. Um eine zu erwerben, gehen Sie zu Rufnummern > Rufnummern kaufen und suchen Sie nach einer Nummer, die Ihren Anforderungen entspricht.
Wir werden eine Anwendung erstellen, mit der ein Benutzer eine Vonage-Nummer wählen kann. Dann wird der Benutzer aufgefordert, eine Telefonnummer einzugeben, die er anrufen möchte. Sobald die Nummer eingegeben ist, verbinden wir den Anruf mit dem aktuellen Gespräch und stellen eine Verbindung zu unserem Websocket her. Verwendung von Vonage-Websocketssind wir in der Lage, den Audioanruf in unsere Anwendung zu streamen.
Zunächst müssen wir unser Modell in unsere Anwendung laden.
loaded_model = pickle.load(open("models/model.pkl", "rb"))Wenn der Benutzer die Vonage-Nummer zum ersten Mal wählt, geben wir eine NCCO mit folgendem Inhalt zurück:
class EnterPhoneNumberHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
def get(self):
ncco = [
{
"action": "talk",
"text": "Please enter a phone number to dial"
},
{
"action": "input",
"eventUrl": ["https://3c66cdfa.ngrok.io/ivr"],
"timeOut":10,
"maxDigits":12,
"submitOnHash":True
}
]
self.write(json.dumps(ncco))
self.set_header("Content-Type", 'application/json; charset="utf-8"')
self.finish()Wir senden zunächst eine Text-zu-Sprache-Aktion in den Anruf und fordern den Benutzer auf, eine Telefonnummer einzugeben. Wenn die Telefonnummer eingegeben wird, erhalten wir die Ziffern aus der https://3c66cdfa.ngrok.io/ivr url.
class AcceptNumberHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
def post(self):
data = json.loads(self.request.body)
ncco = [
{
"action": "connect",
"eventUrl": ["https://3c66cdfa.ngrok.io"/event"],
"from": NEXMO_NUMBER,
"endpoint": [
{
"type": "phone",
"number": data["dtmf"]
}
]
},
{
"action": "connect",
"eventUrl": ["https://3c66cdfa.ngrok.io/event"],
"from": NEXMO_NUMBER,
"endpoint": [
{
"type": "websocket",
"uri" : "ws://3c66cdfa.ngrok.io/socket",
"content-type": "audio/l16;rate=16000"
}
]
}
]
self.write(json.dumps(ncco))
self.set_header("Content-Type", 'application/json; charset="utf-8"')
self.finish()Nachdem die Telefonnummer eingegeben wurde, erhalten wir einen Rückruf von der https://3c66cdfa.ngrok.io/ivr url. Hier nehmen wir die Telefonnummer, die der Benutzer eingegeben hat, von data["dtmf"] und führen eine Verbindungsaktion zu dieser Telefonnummer und führen dann eine weitere Verbindungsaktion zu unserem Websocket durch. Jetzt ist unser Websocket in der Lage, den Anruf mitzuhören.
Während der Anruf in den Websocket gestreamt wird, müssen wir mit Hilfe der Voice Activity Detection Sprachfetzen erfassen, in einer Wave-Datei speichern und mit Hilfe unseres trainierten Modells unsere Vorhersagen für diese Wave-Datei treffen.
class AudioProcessor(object):
def __init__(self, path, rate, clip_min, uuid):
self.rate = rate
self.bytes_per_frame = rate/25
self._path = path
self.clip_min_frames = clip_min // MS_PER_FRAME
self.uuid = uuid
def process(self, count, payload, id):
if count > self.clip_min_frames: # If the buffer is less than CLIP_MIN_MS, ignore it
fn = "{}rec-{}-{}.wav".format('', id, datetime.datetime.now().strftime("%Y%m%dT%H%M%S"))
output = wave.open(fn, 'wb')
output.setparams((1, 2, self.rate, 0, 'NONE', 'not compressed'))
output.writeframes(payload)
output.close()
self.process_file(fn)
self.removeFile(fn)
else:
info('Discarding {} frames'.format(str(count)))
def process_file(self, wav_file):
if loaded_model != None:
X, sample_rate = librosa.load(wav_file, res_type='kaiser_fast')
mfccs = np.mean(librosa.feature.mfcc(y=X, sr=sample_rate, n_mfcc=40).T,axis=0)
X = [mfccs]
prediction = loaded_model.predict(X)
if prediction[0] == 0:
beep_captured = True
print("beep detected")
else:
beep_captured = False
for client in clients:
client.write_message({"uuids":uuids, "beep_detected":beep_captured})
else:
print("model not loaded")
def removeFile(self, wav_file):
os.remove(wav_file)
Sobald wir eine wav-Datei haben, verwenden wir librosa.load um die Datei zu laden, und verwenden dann die librosa.feature.mfcc Funktion, um den MFCC des Samples zu erzeugen. Wir rufen dann loaded_model.predict([mfccs]) auf, um unsere Vorhersage zu treffen. Wenn die Ausgabe dieser Funktion lautet 0, a beep erkannt worden. Wenn sie ausgibt 1aus, dann ist es speech. Wir generieren dann eine JSON-Nutzdaten, ob a beep erkannt wurde, und die Uuids der Konversation. Auf diese Weise kann unsere Client-Anwendung unter Verwendung der uuids ein TTS in den Anruf senden.
Websocket-Client
Der letzte Schritt besteht darin, einen Client zu erstellen, der eine Verbindung zum Websocket herstellt, beobachtet, wenn ein Piepton erkannt wird, und einen TTS in den Anruf sendet, wenn die Voicemail erkannt wird.
Zunächst müssen wir eine Verbindung zum Websocket herstellen.
ws = websocket.WebSocketApp("ws://3c66cdfa.ngrok.io/socket",
on_message = on_message,
on_error = on_error,
on_close = on_close)
ws.on_open = on_open
ws.run_forever()Als Nächstes warten wir einfach auf jede eingehende Nachricht von unserem Websocket.
def on_message(ws, message):
data = json.loads(message)
if data["beep_detected"] == True:
for id in data["uuids"]:
response = client.send_speech(id, text='Answering Machine Detected')
time.sleep(4)
for id in data["uuids"]:
try:
client.update_call(id, action='hangup')
except:
pass<a href="https://www.nexmo.com/wp-content/uploads/2019/02/amd-confusion-matrix.png"><img src="https://www.nexmo.com/wp-content/uploads/2019/02/amd-confusion-matrix-600x300.png" alt="" width="300" height="150" class="alignnone size-medium wp-image-28012" /></a>
<a href="https://www.nexmo.com/wp-content/uploads/2019/02/amd-df.png"><img src="https://www.nexmo.com/wp-content/uploads/2019/02/amd-df-600x300.png" alt="" width="300" height="150" class="alignnone size-medium wp-image-28015" /></a>
<a href="https://www.nexmo.com/wp-content/uploads/2019/02/amd-eda.jpg"><img src="https://www.nexmo.com/wp-content/uploads/2019/02/amd-eda-600x300.jpg" alt="" width="300" height="150" class="alignnone size-medium wp-image-28018" /></a>
Wir parsen die eingehende Nachricht als JSON und prüfen dann, ob die beep_detected Eigenschaft ist True. Wenn ja, dann wurde ein beep erkannt worden. Dann wird ein TTS in den Anruf gesendet, das besagt, dass ein Anrufbeantworter erkannt wurde, und dann wird eine hangup Aktion in den Anruf.
Schlussfolgerung
Wir haben gezeigt, wie wir ein Modell zur Erkennung von Anrufbeantwortern mit einer Genauigkeit von 96 % erstellt haben, indem wir ein paar Audiobeispiele von beeps und speech um unser Modell zu trainieren. Wir hoffen, dass wir Ihnen gezeigt haben, wie Sie maschinelles Lernen in Ihren Projekten einsetzen können. Viel Spaß!
