Belajar Dari Kesalahan Kita 📈

Bagaimana Python, scikit-learn, Regresi Logistik, dan Looker bersatu untuk membantu manajer penjualan kami menemukan 'jarum di tumpukan jerami'.

Ini adalah artikel pendek yang ditujukan untuk analis dan praktisi penjualan yang mungkin ingin mengeksplorasi potensi penggunaan ML dalam alur kerja mereka. Ada cuplikan kode, contoh, dan pelajaran yang saya pelajari dari menyematkan model klasifikasi ke dalam proses penjualan kami di sini Qubit.

Konteks

Di Qubit, kami menggunakan Salesforce sebagai alat CRM inti kami dan selama bertahun-tahun sejumlah besar bidang khusus dan alur kerja di balik layar telah diciptakan untuk menangkap sebanyak mungkin konteks tentang peluang penjualan.

Pertama dan terpenting, tidak ada orang yang lebih dekat dengan peluang penjualan atau memiliki lebih banyak konteks daripada perwakilan penjualan. Namun demikian, ada beberapa ciri (korelasi) peluang historis yang dapat digunakan untuk melengkapi pengambilan keputusan kita saat ini.

Intinya.

Bisakah kita membangun model ML yang diawasi untuk mengumpulkan nilai dari data Penjualan historis kita?

Model klasifikasi yang efektif di bidang ini akan memungkinkan kami memprioritaskan peluang penjualan dan mengalokasikan sumber daya secara strategis untuk mencapai kesepakatan yang tepat. Intinya, menjadi lebih mudah ditebak sebagai sebuah bisnis.

Saya harus menyatakan bahwa di balik layar di Qubit, ada banyak orang yang memberikan nasihat dan bimbingan yang baik selama proyek ini, tanpa mereka, hal ini tidak akan mungkin terjadi. Hal ini merupakan tambahan dari berbagai sumber daya online yang bagus (dikutip di bawah).

(Artikel ini tidak akan membahas luas dan pemahaman ML, ada sejumlah artikel yang ditulis lebih baik untuk itu! Saya juga berasumsi Anda sudah menginstal Python 3 dalam kapasitas tertentu.)

Algoritme mana yang harus saya gunakan?

Untuk tantangan seperti ini, berdasarkan pengalaman dan pemahaman saya, pada awalnya biasanya dilakukan eksplorasi efektivitas Minimum Viable Product (MVP). Dalam konteks ini, menggunakan algoritme yang banyak dikutip dengan sedikit atau tanpa rekayasa fitur atau prapemrosesan data, untuk memahami di mana 'titik terendah' ​​terkait dengan presisi dan akurasi model. MVP saya melibatkan algoritma Regresi Logistik, Pohon Keputusan, dan Hutan Acak karena variabel targetnya adalah biner. Agar artikel ini tetap fokus, Regresi Logistik Biner dipilih dan berikut adalah beberapa sumber utama yang pasti membantu:

Buku

Sumber Daya Online

Artikel sedang

Persatuan Analytics

  • Di Qubit, kami memiliki Analytics Guild internal, yang menyediakan lingkungan terbuka dan jujur ​​untuk melaporkan kembali pengembangan model untuk mendapatkan masukan (beri tahu semua yang terlibat! 👊🏻).

Saya memiliki Apa dan Mengapa untuk Bagaimana…

Qubit mengadopsi Looker untuk BI internal pada tahun 2017, yang memungkinkan saya menggabungkan API Looker dan Gudang Data Qubit di dalam Jupyter Notebooks, membuka akses ke salah satu perpustakaan pembelajaran mesin Python — scikit-learn.

Asumsi yang umum dianut adalah:

“… 'ukuran kesepakatan historis yang kami adakan terlalu kecil' dan 'konsistensi bidang tertentu' terlalu lemah untuk membangun apa pun yang bisa berhasil.”

Untuk mengatasi hal di atas dan mengungkap apakah algoritme ML yang diawasi dapat memperoleh manfaat, saya melakukan langkah-langkah berikut:

  1. Pemrosesan Awal Data: memuat data, fitur penskalaan, imputasi, dan satu fitur kategorikal pengkodean panas.
  2. Latih + Uji:memisahkan data pelatihan, memuat algoritme Regresi Logistik scikit-learn, menyetel hyperparameter, dan menyimpan model ke disk.
  3. Kinerja:kurva pembelajaran, validasi, dan ROC untuk dibandingkan dengan matriks konfusi.
  4. Bagikan:menyebarkan keluaran model ke seluruh Looker.

Artikel ini tentu saja tidak menjelaskan secara lengkap proses yang telah/sedang diulangi untuk lebih mengutak-atik modelnya!

Cakupan Peluang

Sebelum perselisihan data, saya membuat sketsa ide menggunakan titik data yang saya tahu kami miliki seputar peluang penjualan. Setelah banyak iterasi, inilah daftar yang saya lanjutkan:

  • Kecepatan Peluang:dalam hal usia dan waktu yang dihabiskan pada tahap penjualan untuk mendapatkan peluang.
  • Keterlibatan Perwakilan Penjualan:dalam hal perubahan pada bidang peluang utama seperti 'tanggal penutupan' dan jarak bersih yang ditempuh (melalui jalur penjualan).
  • Keterlibatan HubSpot:kami menggunakan HubSpot sebagai alat pemasaran, yang berisi skor empiris yang dapat kami kumpulkan dan gabungkan untuk menghasilkan peluang.
  • Lokasi Transaksi:lokasi geografis transaksi dan perwakilan keduanya memiliki riwayat tingkat kemenangan yang dapat memberikan konteks.
  • Ukuran Transaksi:bergantung pada jenis bisnis SaaS yang Anda jalankan dan strategi masuk ke pasar Anda, ini mungkin merupakan fitur yang berguna.
  • Jenis Peluang: kami di Qubit menjual beberapa jenis produk, dirancang dengan mempertimbangkan siklus penjualan yang berbeda.
  • Konteks Penjualan:di Qubit kami memiliki banyak konteks dalam kerangka kerja Penjualan seperti MEDDPICC, yang terbukti merupakan fitur yang bagus.

Daftar ini dilengkapi dengan percakapan dari seluruh bisnis dan analisis ad-hoc untuk mengonfirmasi pentingnya fitur. Hal ini terus ditinjau namun menjadi titik awal untuk pengembangan model.

1. Pemrosesan Awal Data

Bagian ini akan membahas bagaimana saya menggunakan API Looker, fungsi 'pandas.read_sql_query' Pandas, dan SQLAlchemy untuk menyederhanakan prosesnya.

Muat Perpustakaan

Muat Data

Berkat fungsi di bawah ini saya sekarang dapat mengimpor data terbaru dari Looker atau dari MySQL Views hanya dalam beberapa baris.

'query_id' dapat ditemukan dalam tab 'Kueri' dalam panel Admin Looker (Dokumentasi Looker).

Fungsi di atas berguna untuk memuat tabel gabungan sebelumnya, yang mungkin berisi kolom kalkulasi, dalam potongan kode yang lebih pendek dan rapi.

Catatan, fitur 'penggabungan hasil' Looker yang baru, belum berfungsi dengan API dan contoh 'query_id' hanya relevan untuk instance kami.

# Looker API
age_from_creation = get_data('147670')
deal_size = get_data('138578')
hubspot = get_data('146956')
# Data Warehouse
sql = "select * from pipe_qualifier_data"
days_in_stage_df = pd.read_sql_query(sql, data_warehouse)

Imputasi & Kategorisasi

Regresi Logistik tidak dapat menangani NaN dan oleh karena itu seluruh kerangka data perlu diperhitungkan. Selain memasukkan fitur, diperlukan 'satu pengkodean panas' untuk fitur tertentu. Berikut adalah beberapa contoh yang saya gunakan:

# Impute NaNs caused by merging (categorically and continuously)
df.feature_X.fillna(value="Unknown", inplace=True)
df.feature_Y.fillna(value=0, inplace=True)
# Impute Negative Continious Variables
df.loc[df['days_at_0'] < 0, 'days_at_0'] = 0
df.loc[df['days_at_1'] < 0, 'days_at_1'] = 0
df.loc[df['days_at_2'] < 0, 'days_at_2'] = 0
# String Search Categorisation
def check_opp_name(oppname):
    if 'xxx' in oppname.lower():
        answer = 'x_opp'
    elif 'xxc' in oppname.lower():
        answer = 'x_opp'
    elif 'y' in oppname.lower():
        answer = 'y_opp'
    else:
        answer = 'non_xy_opp'
    return answer
df['opp_definition'] = df.opportunity_name.apply(check_opp_name)
# Create dummy variables (one hot encoding)
df = pd.get_dummies(df)
# Assign target variable and drop unwanted columns
df['target_variable'] = df['contract_stage']
df.drop(
    [
        'contract_stage_Contract_Signed', 'contract_stage_Lost',
        'key_deal_0'
    ],
    axis=1,
    inplace=True)
# Df tidy up
df.columns = map(str.lower, df.columns)
df = df.rename(
    index=str,
    columns={
        "meddpicc": "meddpicc_populated",
        "key_deal": "key_deal"
    })

Tantangan Data

Sekarang setelah saya memiliki data dalam struktur yang lebih dapat digunakan, penting untuk mengeksplorasi distribusinya untuk memahami apakah distribusi tersebut dapat dipisahkan secara linier. Kegagalan dalam hal ini akan membuka kebutuhan akan 'fitur interaksi' atau pustaka dekomposisi untuk mengurangi dimensi.

# SNS pair plot
g = sns.pairplot(
    df[[
        "age_of_opp", "close_date_movements", 
        "days_at_0", "pipe_exp",
        "sum_of_close_date_moves", "days_at_1", 
        "max_hubspot_score", "target_variable"
    ]],
    hue="target_variable",
    diag_kind="hist")
for ax in g.axes.flat:
    plt.setp(ax.get_xticklabels(), rotation=45)

Tantangannya adalah situasi klasik 'jarum di tumpukan jerami', yang mana set data pelatihan sangat tidak seimbang, dengan kelas target berjumlah ‹10% dari total. Hal ini ditambah dengan fakta bahwa ‹10% dari total. Hal ini disertai dengan fakta bahwa ‹10% dari total. strong>.shape dari bingkai data hanya mengembalikan baris 2010.

Mayoritas fitur memiliki kemiringan positif tetapi tampaknya memiliki beberapa pengelompokan variabel target, namun masih ada ruang untuk perbaikan untuk iterasi model di masa mendatang.

Kumpulan data yang tidak seimbang ini menambah motivasi lebih lanjut untuk penerapan ML, tetapi hampir pasti akan menghasilkan model yang sebagian besar mengklasifikasikan setiap instance sebagai kelas non-target (menghasilkan hasil dengan presisi tinggi). Bergantung pada penerapan dan matriks konfusi, kita dapat mengubah cara kita menerapkan model ini dalam praktik (lihat di bawah, langkah 3. Kinerja).

2. Pelatihan Model + Pengujian

Masukan distandarisasi menggunakan fungsi skala() sklearn, yang menunjukkan beberapa peningkatan kinerja karena outlier mempunyai dampak signifikan pada Regresi Logistik. Pertimbangan di masa depan adalah menggunakan 'RobustScalar()' karena menggunakan rentang interkuartil untuk menghilangkan pengaruh outlier dengan lebih baik.

Mengingat ukuran sampel yang kecil, pembagian pelatihan vs pengujian 70/30 dipilih — idealnya, pembagian ini adalah 60/40 untuk menguji lebih lanjut kemampuan model dalam melakukan generalisasi. Pertimbangan di masa depan adalah mengeksplorasi penggunaan 'StratifiedKFold()' dan validasi silang untuk meningkatkan ukuran set pengujian/pelatihan dan menjaga keseimbangan kelas.

# Splitting df into target and features
X = df[df.loc[:, df.columns != 'target_variable'].columns]
y = df['target_variable']
# Splitting features into a 70/30 train/test ratio
X_train, X_test, y_train, y_test = train_test_split(X, 
                                                    y, 
                                                    test_size=0.3)
# Scale inputs
X_train_scale = scale(X_train)
X_test_scale = scale(X_test)
# Logistic Regression C Value
logreg_scaled = LogisticRegression(C=1.0)
logreg_scaled.fit(X_train_scale, y_train)

Fungsi LogisticRegression Scikit-Learn memiliki parameter 'class_weight=', yang dapat menerapkan pembobotan secara otomatis berbanding terbalik dengan frekuensi kelas.

# Logistic Regression Class Weighting
logreg_scaled_balanced = LogisticRegression(C=1.0,                 
                                            class_weight='balanced')
logreg_scaled_balanced.fit(X_train_scale, y_train)

Saya menjelajahi hal ini dan menemukan skor perolehan untuk kelas target meningkat pesat, namun presisinya juga menurun. Salah satu risiko dari menyeimbangkan kelas yang tidak seimbang secara artifisial adalah penyesuaian yang berlebihan. Saya memutuskan untuk melanjutkan dengan set yang tidak seimbang.

Fungsi di bawah ini sangat berguna untuk menampilkan dengan jelas kurva Validasi dan Pembelajaran model Regresi Logistik saya selama pelatihan dan pengujian.

Kurva pembelajaran adalah cara yang baik untuk memahami bagaimana 'pembelajaran' (sumbu y) berubah dengan lebih banyak 'pengalaman' (sumbu x). Grafik menunjukkan berdasarkan fitur saat ini, jumlah contoh pelatihan tampaknya cukup memadai untuk konvergensi.

Nilai C sebesar 0,1 dan 1,0 digunakan untuk membandingkan performa dan menyeimbangkan risiko over-fitting — C=1,0 digunakan dalam konfigurasi model akhir.

Metode alternatifnya adalah dengan menggunakan fungsi ``GridSearchCV'` dari scikit-learn, bersama dengan validasi silang untuk mengonfirmasi secara menyeluruh nilai hyperparameter terbaik.

C : mengambang, default: 1.0

Kebalikan dari kekuatan regularisasi; harus berupa pelampung positif. Seperti pada mesin vektor dukungan, nilai yang lebih kecil menentukan regularisasi yang lebih kuat.

Regresi Logistik - Scikit-Learn

("sumber")

Dengan menggunakan pustaka acar Python, dengan cepat dan mudah untuk 'meratakan' atau 'membuat serial' model dan konfigurasinya saat ini ke dalam file .sav.

Modul pickle mengimplementasikan algoritma yang mendasar namun kuat untuk membuat serialisasi dan de-serialisasi struktur objek Python. “Pickling” adalah proses dimana hierarki objek Python diubah menjadi aliran byte, dan “unpickling” adalah operasi kebalikannya, dimana aliran byte diubah kembali menjadi hierarki objek.

# To Save
filename = 'pipe_qualifier_model.sav'
pickle.dump(logreg_scaled, open(filename, 'wb'))
# To Open
logreg_scaled = pickle.load(open('pipe_qualifier_model.sav', 'rb'))

Menyimpan secara lokal ke disk berguna untuk memproduksi penggunaan model, tanpa perlu memuat ulang, melatih, dan menguji model secara terus-menerus.

3. Kinerja

Kurva karakteristik operasi penerima (“ROC”), atau area di bawah kurva (“AUCROC”), biasanya digunakan untuk mengevaluasi performa ambang batas model klasifikasi pembelajaran mesin. Sering dibandingkan dengan pengklasifikasi yang menebak secara acak, ditunjukkan dengan garis lurus melalui (0,0) dan (1,1).

logit_roc_auc = roc_auc_score(y_test,
                              logreg_scaled.predict(X_test_scale))
fpr, tpr, thresholds = roc_curve(y_test,
                       logreg_scaled.predict_proba(X_test_scale)             
                       [:,1])
plt.figure()
plt.plot(fpr, tpr, label = 'Logistic Regression Scaled (area = %0.2f)' % logit_roc_auc)
plt.plot([0, 1], [0, 1],'r--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve (scaled)')
plt.legend(loc="lower right")
plt.show()

Berdasarkan definisi kurva ROC, kurva tersebut dapat dikaitkan dengan matriks konfusi. Pertimbangan di masa depan adalah mengurangi ambang klasifikasi model untuk memengaruhi optimisme secara artifisial — dengan mempertimbangkan penerapan sebenarnya.

Kurva ROC dibuat dengan memplot tingkat positif sebenarnya (TPR) terhadap tingkat positif palsu (FPR) pada berbagai pengaturan ambang batas — Wikipedia

Jika keluarannya merupakan kategori terpisah, Confusion Matrix mungkin menunjukkan lebih banyak wawasan tentang performa model. Matriks kebingungan adalah metode yang baik untuk mendeskripsikan kemampuan model dalam mengklasifikasikan data sesuai dengan kelas target dalam kumpulan data pengujian (yang nilainya diketahui).

Seperti yang diharapkan, karena ketidakseimbangan dalam data, model tersebut mendapat skor tinggi untuk kelas Presisi & Ingatan non-target (kesepakatan yang merugikan) — karena mereka merupakan mayoritas populasi.

Model ini mendapat skor tepat untuk Presisi terhadap kelas target (memenangkan kesepakatan), dengan skor Ingatan yang memerlukan kerja keras. Ini bukanlah awal yang buruk (IMO), dengan area fokus yang jelas untuk perbaikan model.

# Confusion Matrix
y_pred = logreg_scaled.predict(X_test_scale)
print(classification_report(y_test, y_pred))

Dalam istilah awam:

Presisi:

  • 97% transaksi yang diprediksi akan kalah ternyata hilang.
  • 80% transaksi yang diprediksi menang ternyata dimenangkan.

Ingat:

  • 99% transaksi yang hilang dalam data diklasifikasikan dengan benar.
  • 50% transaksi yang dimenangkan dalam data diklasifikasikan dengan benar.

Perhatikan kolom 'dukungan' yang rendah, yang menunjukkan ukuran set pengujian.

Ini adalah bagian dari proses yang dalam praktiknya diulangi. Fitur ditinjau dan model dilatih ulang untuk mencoba dan meningkatkan kinerja empiris.

Dalam praktiknya, kami mengamati tingkat akurasi yang serupa (Skor F1 = 62%) saat kami menerapkan model ini selama 'percobaan' di akhir Kuartal Penjualan. Masukan ini sangat berguna dalam pemilihan fitur baru untuk menangkap konteks yang mungkin kita lewatkan saat ini.

4. Bagikan

Untuk menerapkan model ini, diperlukan hasil yang nyata, berupa heuristik baru atau 'pelajaran yang dipetik' untuk kepemimpinan penjualan.

“Apakah ada manfaat dari pelajaran yang didapat?”

Cara yang paling berdampak untuk menyebarkan pembelajaran baru adalah dalam bentuk pentingnya fitur. Saya menggunakan dua teknik untuk menyorot fitur-fitur yang berpengaruh:

Koefisien Model Logit

# loop through columns and append the corresponding coefficient
w_con = []
for idx, col_name in enumerate(X_train.columns):    
    w_con.append([col_name, logreg_scaled.coef_[0][idx]])
print("Model Logit Coefficients")
# Return the real and absolute values for sorting
df = pd.DataFrame(w_con)
df["abs_coeffs"] = abs(df[1])
df.sort_values(by=["abs_coeffs"], ascending=False, inplace=True)
df = df.rename(index=str, columns={0: "Feature", 1: "Coefficient"})
df.head(10)

# Load up Random Forest
rf = RandomForestClassifier()
rf.fit(X_train_scale, y_train)
importances = rf.feature_importances_
# Sort by importance
std = np.std([tree.feature_importances_ for tree in rf.estimators_],
             axis=0)
indices = np.argsort(importances)[::-1]
feature_importances = pd.DataFrame(
    rf.feature_importances_, index=X_train.columns,
    columns=['importance']).sort_values(
        'importance', ascending=False)
print("Random Forest Classifier: Feature Importance")
feature_importances.head(10)

Tidak hanya menggunakan ukuran bobot namun juga arahnya, saya dapat mengubah bobot internal menjadi kesimpulan yang dikontekstualisasikan.

Berikut sinopsisnya:

  • Peluang penuaan mendapat sanksi(kami sebenarnya menemukan titik batas, setelah itu kami sekarang menurunkan prioritas)
  • Stagnasi pada tiga langkah pertama corong akan dikenakan sanksi(hal ini selaras dengan proses kualifikasi kami)
  • Terus diusir akan dikenakan sanksi(periode batas baru sekarang digunakan untuk menantang konten saluran Penjualan)
  • Peluang untuk Akun yang telah berinteraksi dengan kontak kami akan dihargai(titik data bersama ini telah mendekatkan tim Pemasaran dan Penjualan kami secara operasional)

Selain hal di atas, saya memutuskan untuk memasukkan hasilnya kembali ke Looker untuk kemudian disebarluaskan ke kelompok pengguna tertentu. Hal ini memungkinkan saya menyajikan informasi bersamaan dengan konteks penjualan yang lebih luas — menyelesaikan bagian akhir dari Alur Kerja Pengembangan Model.

Makan, Tidur, Sambutan Hangat, Ulangi

Pengembangan model merupakan penyebab berulang dan umpan balik sangat penting bagi kinerja. Ada beberapa keluaran yang berdampak dari latihan ini (selain kinerja model), yang digunakan oleh manajer penjualan di Qubit.

Tantangannya adalah menerapkan pembelajaran, mempertanyakan fitur-fitur model, dan mengulangi latihan ini untuk mengupayakan perbaikan. Sekarang, saya tidak ingin hal ini terdengar sepele, perubahan adalah salah satu latihan yang paling memakan waktu dalam sebuah bisnis dan bukan keputusan yang bisa dianggap enteng.

Anggaplah proyek ini sebagai misi kepanduan, di mana nilai tingkat permukaan telah ditemukan, namun ada potensi untuk lebih banyak lagi. Menerapkan aturan 80/20; Saya berpendapat bahwa sebagian besar laba atas investasi (waktu) awal telah diketahui, namun ada pula yang mungkin sulit untuk mendapatkan imbalan sebelum penurunan laba mulai berlaku.

Nantikan bacaan singkat lainnya mengenai pekerjaan BI yang kami lakukan di Qubit!

🚀