シンプルなプロジェクトでの機械学習

私はPreplyプロジェクトのCTOであり、すべてのプログラマーが夢見ていること、つまり単純なプロジェクトの複雑で興味深いタスクについて少しお話ししたいと思います。



より正確には、ビジネスに少し科学を追加し、結果として少しの利益を得る方法についてです。 この記事では、実際のプロジェクトで機械学習を使用するコンテキストの1つを説明しようとします。



問題



私たちはPreplyのチュータープラットフォームであり、誰もが私たちを欺きたいです。



当社のウェブサイトのユーザーは、チューターの申し込みを離れ、条件に同意した後、ウェブサイトを通じてレッスンの料金を支払います。 クラスがSkypeに参加する場合、サイトを通じてすべての支払いを受け付けます。 彼らがライブで会う場合、私たちの手数料は最初のレッスンの費用です。



チューターと学生は、何らかの理由で 、サイトを通じてレッスンの支払いを回避しようとします。 これを行うには、内部メッセージングシステムを使用します。これは、今後のクラスに関する詳細を明確にするように設計されており、アプリケーションを講師に送信した後に利用できます。 連絡先の共有の例を次に示します。



私のスカイプはvasiliy.p、tel +789123456です。 4月1日の19:00に!


こんばんは あなたはあなたの番号を書くか、私の+ 78-975-12-34を呼び出すことができます


レッスンの前に支払いたくありません。私の名前はVasily Pupkinです。VKで見つけてください。




経験豊富なプログラマーはすぐに、「メッセージを交換するための可能なオプションの正規表現を書くことの問題は何ですか?」と言うでしょう。 問題はありませんが、このソリューションにはいくつかの欠点があります。



  1. 不適切な(つまり、連絡先を含む)メッセージのすべてのオプションを提供することは困難です。 たとえば、製品の最初のバージョンでは、電話番号の正規表現のセットがありましたが、次の形式のメッセージが機能し、ブロックされました。

    金曜日-13 00-15 00-15 15 30 ...グループレッスンの費用はいくらですか?


    より複雑なケースでは、メールに正規表現が使用されました。 次のようなメッセージをブロックすることを目的としたメール。

    vasya(dog)pupkin(dot)る


    しかし、同時に完全に無害なテキストをブロックしました:

    私は犬として英語を知っています。私はすべてを理解していますが、それを言うことはできません。


    Skypeという言葉さらに複雑です。Skypeを交換しようとする試みを含むメッセージを区別するのは非常に困難です。

    スカイプで私を追加してください-vasya82pupkin


    メッセージを明確にすることから:

    あなたはスカイプまたはローカルレッスンを受けたいですか?


  2. 信頼のしきい値を制御することはできません。 つまり、メッセージはブロックされているかどうかのいずれかです。 ロジックを変更するには、コードに登る必要があります。 実際には、最初のタイプのエラー(誤報)よりも2番目のタイプの誤答(メッセージのスキップ)の方がはるかに簡単です。これは、ユーザーがサポートを書く誤報では、サポートマネージャーが誤ったブロッキングとブロック解除のメッセージについて謝罪するのに時間がかかるためです。このサービスを使用したときの甘やかされた経験に言及するため。 一方、連絡先を交換するユーザーが顧客になることはめったにないので、2番目のタイプの間違いを犯すのは簡単です。お金をmakeけないからです(そうです、これはビジネスです)。




ある時点で、ロックプロセスをより科学的にするために週末を過ごすことにしました。 以下は、その由来を説明しています。 私の目標は、すべてを正しく、正確に、科学的に行うことではなく、間違いなく機能させ、収入にプラスの影響を与えることでした。



解決策



Andrew NgのCoursera Machine Learningコースで記憶した正しい/正しくないメッセージを正しく分類するために、3つの機械学習方法を試すことにしました。



最初の問題は、トレーニングの基礎を準備することです。 以前に古いシステムで分類された50,000を超える投稿がありました。 私はそのうちの5,000人だけを取り、前のシステムがミスを犯したメッセージの誤った分類を修正しようとして約2〜3時間費やしました。 理論的には、ベースが大きいほど良いですが、現実の世界では、大きなサンプルを手動で準備することは非常に困難です(言い換えると、怠)。



サンプル調製の時間のかかるプロセスの微妙な違いの1つは、プロセスの倫理です。 他の人のメッセージを読むのはあまり便利ではないことを認めます。その前に言葉を混同し、疑わしいメッセージを見たときに本質を理解せずに見えるようにしました。 例:



それは:

2月にクラスを開始したいのですが、これは可能ですか? 1月の正確な時間もわかりますが、18:00より早くなることはありません



次のようになりました:

2月以降は授業がありませんか? 1月には正確で、18:00よりも早くなりますが、月を始めたいと思うことができます。



それは:

私の電話、役立ってよかったです。 (012)345-678電話、同意します、ありがとう


次のようになりました:

電話番号 私が役に立つことを嬉しく思います、私の電話、私たちは同意します、ありがとう(012)345-678


結果は〜5000行のcsvファイルで、不正なメッセージはゼロでマークされ、正しいメッセージは1でマークされます。 その後、データの操作に基づいて、「目で」分類に影響を与える一連のメッセージ特性を決定しました。







それぞれの特性を定義した後、次のようないくつかの正規表現を作成しました。



import re SEPARATOR = "|" reg_arr = [ re.compile(u'|facebook|linkedin|vkontakt|',re.IGNORECASE | re.UNICODE), re.compile(u'.{1,10}', re.UNICODE)] re.compile(u'[^]|skype', re.IGNORECASE | re.UNICODE), re.compile(u'', re.IGNORECASE | re.UNICODE), re.compile(u'[і].*\s[a-zZ-Z]', re.IGNORECASE | re.UNICODE), re.compile('\d{3}[-\.\s]??\d{3}[-\.\s]??\d{4}|\(\d{3}\)\s*\d{3}[-\.\s]??\d{4}|\d{3}[-\.\s]??\d{4}'), ... re.compile('http|www|\.com', re.IGNORECASE), re.compile(u'|my', re.IGNORECASE | re.UNICODE), re.compile(u'|find', re.IGNORECASE | re.UNICODE), re.compile(u'|add', re.IGNORECASE | re.UNICODE), ... re.compile('\w+@\w+', re.IGNORECASE), re.compile('.{0,50}', re.IGNORECASE), re.compile('.{50,200}', re.IGNORECASE), .... ] def feature_vector(text): return map(lambda x: 1 if x.search(text) else 0, reg_arr) fi=open('db_machine.csv', 'r') fo=open('db_machine_result.csv', 'w') for line in fi: [text, result] = line.split(SEPARATOR) output = feature_vector(text).append(result) fo.write(",".join(map(lambda x: str(x), output )) + "\n") fo.close() fi.close()
      
      







したがって、すべての特性(現在約100個)のすべてのメッセージを処理した後、特性のベクトルと分類結果をファイルに書き込みます。



データを準備した後、サンプルを3つの部分に分割する必要があります。トレーニング(トレーニングセット)、パラメーターの選択(クロス検証セット)、および検証(テストセット)です。 コースからのアドバイスに従い、トレーニングサンプルのサイズ、パラメーターの選択、テストは比率60/20/20で相関されます。



 import random with open('db_machine_result.csv','r') as source: data = [ (random.random(), line) for line in source ] data.sort() n = len(data) with open('db_machine_result_train.csv','w') as target: for _, line in data[:int(n*0.60)]: target.write( line ) with open('db_machine_result_cross.csv','w') as target: for _, line in data[int(n*0.60):int(n*0.80)]: target.write( line ) with open('db_machine_result_test.csv','w') as target: for _, line in data[int(n*0.80):]: target.write( line )
      
      







次に、車輪を再発明せず、可能な限り迅速に結果を得るという原則に基づいて、機械学習コースラのスクリプトを使用し、ロジスティック回帰、SVM、ニューラルネットワークアルゴリズムを使用してサンプルを実行しました。 スクリプトは単にコースから取得されます。たとえば、SVMは次のようになります。



 clear ; close all; clc data_train = load('db_machine_result_train.csv'); X = data_train(:, 1:end-1); y = data_train(:,end); data_val = load('db_machine_result_cross.csv'); Xval = data_val(:, 1:end-1); yval = data_val(:,end); data_test = load('db_machine_result_test.csv'); Xtest = data_test(:, 1:end-1); ytest = data_test(:,end); [C, sigma] = dataset3Params(X, y, Xval, yval); %    cross-validation set fprintf('C: %f\n', C); fprintf('sigma``: %f\n', sigma); model= svmTrain(X, y, C, @(x1, x2) gaussianKernel(x1, x2, sigma)); p = svmPredict(model, X); fprintf('Training Accuracy: %f\n', mean(double(p == y)) * 100); p = svmPredict(model, Xtest); fprintf('Test Accuracy: %f\n', mean(double(p == ytest)) * 100); fprintf('Program paused. Press enter to continue.\n'); pause;
      
      







svmTrain / svmPredict関数がどのようにコースのウェブサイトで実装されているか、たとえばこちらをご覧ください



交差検定サンプルのすべてのアルゴリズムは、内部パラメーター(正則化のλ 、ガウス関数のσC 、ニューラルネットワークの隠れ層のサイズのサイズ)をソートしました。 それらのいくつかの最終的な精度の結果を以下に示します。



ニューラルネットワーク ロジスティック回帰 SVM
サイズ= 30、λ= 1 サイズ= 30、λ= 0.01 サイズ= 30、λ= 0.001 λ= 0 λ= 0.01 λ= 1 線形(λ0.001、σ= 0.001) ガウス(λ0.1、σ= 0.1) ガウス(λ0.001、σ= 0.001)
96.41% 97.88% 98.16% 97.51% 97.88% 98.16% 96.48% 97.14% 98.89%




ここでは、システムを準備する過程で結果が非常に悪く(たとえばSVMで96.6%)、デバッグが非常に具体的な改善を行ったことを明確にする必要があります。 サンプル全体の実際のデータで最も単純かつ高速なロジスティック回帰を開始し、分類結果を修正しました。 ケースの30%で人によるメッセージの分類にエラーがあったため(私が書いたように、私は〜5000のメッセージを見て、判明したところ、どこかで30-40の分類エラーを起こした)、システムすべてを正しく分類しました。 デバッグ中に、データベースのエラーを修正したため、メソッドの精度が向上しました。 さらに、興味深いパターンがシステムによって処理されないことがわかった場合は、特性ベクトルを拡張しました。



SVMメソッドを使用することを選択しました。一般的なサンプルの特性は次のとおりです。



メッセージ

事実

正解

間違っている

予測

正解

4998 36
間違っている

11 390




システムにはクラスが歪んだクラスであるという特性があるため、アルゴリズムを比較するためのパラメーターも指定します。



精度 リコール 精度
99.28% 99.78% 99.13%




最終的に、SVMとガウス関数のコアを使用して、サイト上のメッセージをフィルター処理することにしました。 ロジスティック回帰よりも複雑ですが、動作は遅くなりますが、かなり良い結果が得られます。

完全なメッセージ処理パスは次のとおりです。



これまでのところ、それはうまく機能しており、これに満足しています。



結論



機械学習が古いシステムよりもうまく機能する理由を理解するのは非常に簡単です-専門家の観察では隠されていた特性間の関係を明らかにします。 たとえば、イベントに正規表現といくつかのif条件がありました。テキストにキリル文字とラテン文字があり、数桁でメッセージが短い場合、これはほとんどの場合、連絡先の交換です。 ここで、個々のイベントを数えるだけで、システム自体がイベント間の関係を理解し​​、代わりにルールを作成します。



現在、私たちは本番環境で実際にSVMを使用して、精度の良いインジケータによりメッセージを分類しています。 非常に簡単な方法で使用します。最適なモデルの重みのセットを取得し、上記のPythonに移植されたsvmPredict関数を使用して分類します。 理想的な世界では、管理者が分類エラーを指摘し、システムが重みを調整して改善できるように、教師とフィードバックシステムを作成する必要があります。 しかし、私たちのプロジェクトは、時間=お金の現実の世界に住んでおり、これまでのところ、不正なブロックに関するサポートリクエストの数が2倍に減ったという事実を楽しんでいます。 また、信頼のしきい値と、それに応じて、最初と2番目のタイプのエラーのバランスをとることは興味深い考えですが、これまでのところ、すべてが私たちに合っています。 「メッセージのスキップ」などのエラーの数を測定することは非常に困難です。 システムの導入後のアプリケーションの支払いへの変換が落ちていないことのみを明確にします。 言い換えると、パスが多くても、これはビジネスに影響しません。 しかし、目に見えるパスは少なかった。 したがって、これは週末にわたって非常に良い結果です。



トピックがあなたにとって興味深いものである場合、私たちが行うチュートリアルの推奨事項に対する協調フィルタリングのアプローチについて書く準備ができています。 コードが必要な場合も連絡してください-秘密はありませんが、記事ではパイプラインについて詳しく説明したいと思います。



PS:私たちは成長しています。将来的には、キエフオフィスで2人の優秀で責任あるプログラマを探しています。 Python / DjangoとJS / Backboneのスタック。 多くの興味深いタスクとベストプラクティス。 メールdmytro@preply.com



All Articles