
Scikit-LearnとVonageによるユーザー解約予測モデルの構築
所要時間:2 分
VonageのConversation APIは、開発者が独自のコンタクトセンター・ソリューションを構築することを可能にします。音声、テキスト、そして他のソリューションとの統合により、Vonageは開発を複雑にすることなく、複雑なソリューションを構築することを可能にします。
コンタクトセンター・ソリューションを構築する際には、スマートである必要がある。最も強力なソリューションは、AIを使用して、コールのルーティング、テキストの翻訳、製品の推奨などを行います。素晴らしいのは、博士号を持つAI研究者でなくてもAIをアプリケーションに統合できることです。また、サードパーティのシステムに頼る必要もない。これは一見不可能に思えるが、多くの機械学習ライブラリーが進歩しており、開発者として機械学習システムをソリューションに組み込むことができる。この投稿では、顧客が解約する可能性を予測する方法を追加することに注目しよう。
解約とは何か?
チャーン(解約)とは、定義上、製品の利用を止めた顧客の数を総顧客数で割ったものである。例えば、1,000人の顧客で月1%の解約率がある会社は、毎月1,000人の顧客のうち10人がその会社のサービスを利用しなくなることを意味する。この数値は、その企業の業績を示す指標として使われます。解約率が低い企業は、通常、顧客が定着していることを意味する。解約率が高いということは、顧客は製品やサービスを利用しているが、その後離れていくということである。
コンタクトセンターは、顧客がビジネス、特にカスタマーサービスと直接やり取りする場所のひとつです。そして、悪いサポートで顧客を失うことは、解約、ひいては企業としての健全性に影響を与える可能性がある。
この投稿では、Vonage Conversation APIを使用して、顧客とエージェント間の会話をシミュレートし、解約の可能性を予測するアプリケーションを構築する方法について説明します。

概要
このデモでは、顧客とエージェントの2人のユーザーペルソナを設定します。この例では、会社はテレビサービスプロバイダであり、顧客はそのサービスについて質問があると仮定します。また、この顧客はその会社としばらく取引があり、それを裏付けるデータもあると仮定します。
この例では、ユーザーに関する情報があります。これは次のようなものです:
顧客がサービスを利用した月数。
現在の支払い方法(小切手、クレジットカード)
利用しているサービス。(ライブTV、セットトップボックス、ストリーミングサービスなど)。
他にもたくさんある。
私たちのデモでは、顧客がエージェントと対話すると同時に、エージェントの画面にユーザーが解約する可能性を表示します。これは、会話を始める前にエージェントにとって有用であり、解約の可能性に応じて顧客にもっと注意を払うことができます。
前提条件
アクセス グーグルコラボ
使用するのは ホイ・ジン・チェンの素晴らしい ブログ記事をスターターとして使います。私たちはこのアプリケーションの上に解約予測機能を追加する予定です。
アプリケーションをローカルで実行するために レポをクローンし ngrok.Ngrokに慣れていない場合は、チュートリアルの チュートリアルを参照してください。
アプリケーションを説明する前に、まずモデルを構築する必要がある。その構築には Jupyterノートブックを使います。Jupyterノートブックはコードを実行するインタラクティブな方法で、データサイエンスや機械学習で広く使われている。Google Colabは、これらのノートブックをクラウド上で実行できる無料のサービスだ。ノートブックを構築するコードは ここにあるこのノートブックを実行するには、Google Colabにアップロードしてください。
このチュートリアルでは、機械学習とは何かを基本的に理解していることを前提に説明するが、すべてを完全に理解していなくてもついていけるだろう。
モデルを構築するには、まずデータが必要だ。この例では IBMのテレコム解約データセット.このデータセットには、通信事業者の匿名化されたユーザーデータ7043行が含まれています。データセットを理解するために、Pandasを使ってデータの最初の10行を見てみましょう。 Pandasは、データを処理して理解するためのパイソンライブラリです。各ユーザについて、23の列があり、特徴として知られています。これらには、顧客の性別(男性、女性)、勤続年数(顧客である期間)、電話、インターネット、テレビなどの異なるサービスを持っているかどうかが含まれる。
モデルを構築するには、まずデータセットに空の値がないことを確認する必要がある。これを確認せずにモデルを構築しようとすると、エラーが発生する。
データセットを読み込んで、空の値を削除しよう。
df = pd.read_csv("/content/WA_Fn-UseC_-Telco-Customer-Churn.csv")
df = df.dropna(axis='columns', inplace=True)次に df.head()を使って最初の10行を表示する。このデータを見ると、多くの行に文字列が含まれていることがわかる。(YES, NO).機械学習モデルは数字しか扱えないので、これらの文字列を数字に変換しなければならない。
文字列を含む各行について、文字列が一意であるかどうかを確認する必要があります。この列に含まれる可能性のあるすべての値を表示するために、pandasには unique()という関数があります。
df.Partner.unique()を返す: array(['Yes', 'No'], dtype=object)
これは、行には値 YESと NO.この列については、これらの文字列をブーリアン(1,0)に変換することができる。しかし、他の行、例えば PaymentMethodよりも多くの値がある。 YESまたは NO.
df.PaymentMethod.unique()収益 array(['Electronic check', 'Mailed check', 'Bank transfer (automatic)', 'Credit card (automatic)'], dtype=object)
そのため、このカラムではもう少し工夫が必要だ。値が YESまたは NOである場合、それを 1または 0に変換する。それ以外の文字列の場合は、それぞれ -1.繰り返しになるが、機械学習モデルはNumbersしか使えない。 -1.
他の列を見てみよう、 PhoneService, MultipleLines, OnlineSecurity, OnlineBackup, DeviceProtection, TechSupport, StreamingTVそして StreamingMoviesこれらは似ているように見える。では、各列を調べて文字列をintに変換する関数を書いてみよう。
numeric_features = ['Partner', 'Dependents', 'PhoneService', 'MultipleLines','OnlineSecurity', 'OnlineBackup','DeviceProtection', 'TechSupport','StreamingTV', 'StreamingMovies', 'PaperlessBilling', 'Churn']
def to_numeric(s):
if s == "Yes":
return 1
elif s == "No":
return 0
else: return -1
for feature in numeric_features:
df[feature] = df[feature].apply(to_numeric)Numeric_featuresは、更新が必要なすべてのカラムのリストです。 to_numericは各行の値を取り込み、文字列をintに変換する関数です。のすべての項目をループし、pandas関数を呼び出します。 to_numericのすべての項目をループし、pandas関数 applyを呼び出します。最初の10行を見てVerifyしてみよう。
これらの行は有効になったようだが、他の列を処理しなければならない。まずは Contactして、表示されている値を確認しよう。
df.Contract.unique()を返す: array(['Month-to-month', 'One year', 'Two year'], dtype=object)
これらの値は文字列であることに変わりはない。 1や 0's.このデータセットには似たようなカラムが他にもある。 PhoneService, MultipleLines, InternetService, OnlineSecurity, OnlineBackup, DeviceProtection, TechSupport, StreamingTV, StreamingMovies, Contract, PaperlessBillingそして PaymentMethod.
幸運なことに、pandas では、これらの値を次のように変換できます。 get_dummies().この関数をカラムに適用すると、すべての可能な値に対して新しいカラムが作成されます。そして、これらの新しい列のそれぞれの値は 1または 0.これは ワンホットエンコーディング.
例えば Contractの値を含む Month-to-month, One yearと Two year.を使って get_dummies()という3つの新しいカラムを作成する。 Contract_Month-to-month, Contract_One yearと Contract_Two year.これらのカラムの値はすべて 1または 0.
categorical_features = [
'PhoneService',
'MultipleLines',
'InternetService',
'OnlineSecurity',
'OnlineBackup',
'DeviceProtection',
'TechSupport',
'StreamingTV',
'StreamingMovies',
'Contract',
'PaperlessBilling',
'PaymentMethod']
df = pd.get_dummies(df, columns=categorical_features)ここでは、カテゴリーに変換したい特徴のリストを作成し、DataFrame() を使用して get_dummies関数を呼び出します。df) と列のリスト(categorical_features).もう一度、最初の 10 行を表示して、作業を再確認しましょう。DataFrame が 41 個の特徴になったので、このセルにリンクします。 ここにDataFrame のスクリーンショットを表示するのではなく、ここにセルにリンクします。
カラムはすべて数値のようだ。モデルを構築しよう。モデルを構築するために scikit-learn.scikit-learnには、データを処理してモデルを学習するための組み込み関数がたくさんあります。
まず、2つの行列が必要だ、 Xと y. Xは、予測に使用する特徴(Churn). Yの値だけである。 Churnである。 1または 0.
X = df.drop(labels='Churn',axis=1)
Y = df.Churn
print(X.shape, Y.shape)と返される: ((7043, 40), (7043,))つまり、Xには7043行40列がある。 Yの場合、7043行ある。
次に、データを訓練セットとテストセットに分ける必要がある。モデルをトレーニングするときは、データセットの一部だけを使う。残りのデータはテストに使う。これは、モデルがどの程度データを学習したかを確認するためです。Scikit-Learn を使って、データセットを train-test-split()関数を使います。
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X,Y,test_size=0.3, random_state=42)最後に、私たちはモデルを作ることができる。
この例では、単純なモデルを使う。しかし現実の世界では、データセットを使ってさまざまなモデルをテストし、何が最適かを確認する必要がある。これが、機械学習における多くの作業である。データに合ったモデルを選ぶのに、厳密なルールはない。常にデータセットに依存する。
この例では ロジスティック回帰を使います。ロジスティック回帰は、一般的にブーリアン値の予測に適しているため、最初に使うモデルとして適しています。Scikit-learnはこのモデルを非常に簡単に実装できます。
from sklearn.linear_model import LogisticRegression
from sklearn import metrics
model = LogisticRegression()
model.fit(X_train, y_train)モデルがトレーニングセットで学習されたら、テストセットでそれを使うことができます。 predict関数を呼び出し、テストセット(X_test).
model.predict(X_test)
# Print the prediction accuracy
print (metrics.accuracy_score(y_test, prediction_test))を使用すると metrics.accuracy_scoreを使うと、精度が 0.8135352579271179 と出力されます。これは、我々のモデルが予測を行うためにテスト・セットのデータを与えられたとき、その結果が約89%の確率で正しいことを意味します。この精度は、モデルを評価する際の多くの測定基準のうちの1つに過ぎません。 モデルの評価.
モデルができたら、コンタクトセンターアプリで使用するために保存する必要があります。モデルを保存するために、joblibの関数 ダンプ.
また、トレーニングに使用したカラムの名前も保存しておく必要がある。予測を行うサーバーを構築する際に使用するからだ。
model_columns = list(df.columns)
model_columns.remove('Churn')
joblib.dump(model_columns, 'model_columns.pkl')次に、このモデルを提供する簡単なPythonサーバーを構築する。
フラスコを使ったモデルサービング
次に を作ります。アプリを作り、モデルをホストする基本的なサーバーアプリケーションを作ります。モデルをホストし、予測を行う方法が必要です。もしこれが本番のアプリケーションであれば、ユーザーの情報を保存する必要があります。この情報には、データセットのトレーニングに使ったのと同じ情報が含まれるでしょう。これらの情報には、ユーザがその会社にどれだけの期間在籍しているか(在職期間)、ユーザがその会社に InternetService, PhoneService, OnlineBackupなどが含まれる。こうすることで、予測を行う際に、データベースに問い合わせを行い、ユーザー情報を返し、モデルから予測を行い、解約の可能性を返すことになる。しかし、この例では、ユーザーの情報を生成し、その予測をアプリケーションに送り返します。
では server.pyというエンドポイントを1つだけ持つシンプルなFlaskアプリを作成します。 /predict.このエンドポイントは、ユーザーのデータを生成し、モデルを呼び出し、予測をJSONレスポンスとして返します。
予測を行う前に、まずサーバーの起動時に保存したモデルをロードする必要がある。
app = Flask(__name__)
@app.route('/predict', methods=['GET'])
@cross_origin()
def predict():
#will return prediction
return
if __name__ == '__main__':
model = joblib.load('model/model.pkl')
model_columns = joblib.load('model/model_columns.pkl')
app.run()ここでは joblibを使用して、モデルとトレーニングに使用したカラムをロードする。ここで model.pklそして model_columns.pklをサーバーアプリケーションにコピーしておく必要があります。この predict()関数の中で、ランダムなユーザーのデータを生成し、保存されたカラム名を使ってデータから新しいDataFrameを作成します。
def predict():
random_user_data = generate_data()
#https://towardsdatascience.com/a-flask-api-for-serving-scikit-learn-models-c8bcdaa41daa
query = pd.get_dummies(pd.DataFrame(random_user_data, index=[0]))
query = query.reindex(columns=model_columns, fill_value=0)
#return prediction as probability in percent
prediction = round(model.predict_proba(query)[:,1][0], 2)* 100
return jsonify({'churn': prediction})この generate_data()関数は、トレーニング・データセットと同じカラムを含む新しい辞書を作成し、それぞれにランダムな値を割り当てます。
def random_bool():
return random_number()
def random_number(low=0, high=1):
return random.randint(low,high)
def generate_data():
internetServices = ['DSL', 'Fiber optic', 'No']
contracts = ['Month-to-month', 'One year', 'Two year']
paymentMethods = ['Electronic check', 'Mailed check', 'Bank transfer (automatic)','Credit card (automatic)']
random_data = {
'name':'customer',
'Partner': random_bool(),
'Dependents': random_bool(),
'tenure': random_number(0,50),
'PhoneService': random_bool(),
'MultipleLines': random_number(-1),
'InternetService': random.choice(internetServices),
'OnlineSecurity': random_number(-1),
'OnlineBackup': random_number(-1),
'DeviceProtection': random_number(-1),
'TechSupport': random_number(-1),
'StreamingTV': random_number(-1),
'StreamingMovies': random_number(-1),
'Contract': random.choice(contracts),
'PaperlessBilling': random_bool(),
'PaymentMethod': random.choice(paymentMethods)
}
return random_dataのために InternetService, Contractおよび PaymentMethodについては、それぞれに使用可能な値をハードコードし、ランダムな値を選択する。その他の特徴については、もし Yesまたは Noの値しかトレーニングセットに含まれていない場合は 1または 0を使用した特徴については、ランダムに Yes, Noと他の文字列が使われている特徴については 1, 0と -1とする。
次に、エンドポイントへのリクエストがあったときに呼び出されるpredict関数について説明します。/predictエンドポイントにリクエストがあったときに呼び出されます。
@app.route('/predict', methods=['GET'])
@cross_origin()
def predict():
random_user_data = generate_data()
query = pd.get_dummies(pd.DataFrame(random_user_data, index=[0]))
query = query.reindex(columns=model_columns, fill_value=0)
#return prediction as probability in percent
prediction = round(model.predict_proba(query)[:,1][0], 2)* 100
return jsonify({'churn': prediction})ここでは、ランダムなユーザのデータを生成し、モデルのトレーニングに使用したDataFrameと同じようなDataFrameを作成します。これは同じ列を持ちますが、データセットの行は1行だけです。
最後に predict_probaを呼び出して、ユーザーが解約する可能性のベクトルを返します。 [[0.79329917 0.20670083]]この行列には、ユーザーが解約する確率(0.79329917)と解約しない確率(0.20670083)が含まれます。ベクトルの最後の項目を取り出し、小数点以下2桁に丸め、パーセンテージに変換します。
prediction = round(model.predict_proba(query)[:,1][0], 2)* 100これは 21.0を返します。
最後に、この値をJSONとして churnオブジェクトで返します。
次に、モデルを実行し、ローカルにデプロイする。
ターミナルで model_serverフォルダに移動して実行する:
これで、モデルサーバーが実行され、エンドポイントにGETリクエストをしてテストできるようになりました。 /predictエンドポイントに

サーバーは、無作為のユーザーが解約する可能性のパーセンテージを示すJSONレスポンスを返す。
ウェブアプリケーション
機械学習モデルを構築し、予測を提供するバックエンドを作成したら、今度はそれをウェブアプリに統合して、顧客サービス担当者に解約予測を表示します。
解約予測を表示するために、次のようなカスタム会話イベントを作成します。 カスタム会話イベントを作成します。 churn-predictionというカスタム会話イベントを作成します。
フォルダに移動する。 ui_appフォルダ内の common.jsファイルに移動し publicフォルダに移動し、以下を追加する:
function getChurnForUser(conversation) {
//Send custom event to agent
if (window.location.pathname == "/") {
fetch("http://127.0.0.1:3001/predict",{
mode: 'cors',
headers: {
'Access-Control-Allow-Origin':'*'
}
})
.then(response => {return response.json()})
.then(json => {
conversation.sendCustomEvent({ type: 'churn-prediction', body: json}).then(() => {
console.log('custom event was sent');
}).catch((error)=>{
console.log('error sending the custom event', error);
});
})
.catch(error => console.log('error', error));
}
}
この関数はactivateの会話を受け取り、以前に構築したモデルサーバーを呼び出します。使いやすいようにURLとポートはハードコードしてあります。
本番環境では、そのユーザーの予測を生成するために user-idを渡してそのユーザーの予測を生成します。しかし、この例では、前に示したように、解約予測モデルに使用するランダムなユーザー情報を生成します。
次に、エージェント画面に h2タグを追加します。次に churn-predictionイベントのリスナーを追加します。 h2タグ内のテキストを更新します。中に setupListeners関数の中に追加します:
activeConversation.on('churn-prediction', (sender, message) => {
if (window.location.pathname == "/agent") {
document.getElementById("churn_text").innerHTML = "Likelihood of customer churn: " + message["body"]["churn"] + "%"
console.log(sender, message);
}
});
イベントが発生すると churn-predictionイベントが発生すると、解約予測を messageプロパティを送信し innerHTMLテキストを更新します。
これで、新規ユーザーがエージェントと話すたびに、そのユーザーの解約の可能性を見ることができます。もし、解約の可能性が高いのであれば、少し親切にする必要があるかもしれません。)
結論
全体として、機械学習モデルの構築は魔法ではなく、誰でも有用で本番環境で使用できるものを構築できることを示しました。我々のモデルは89%の精度で解約の確率を予測できることを示した。これは最初のモデルとしては素晴らしいことだ。しかし、さらに優れたモデルを構築するためにできることはたくさんある。学習する最善の方法のひとつは、実際にやってみることです!
付属の グーグル・コラボ・ノートブックをスタータープロジェクトとしてお使いいただけます。ご不明な点がありましたら、いつでも私たちの コミュニティスラック.