تعلم الآلة لصيدك المسطح. الجزء 1

هل سبق لك أن بحثت عن شقة؟ هل ترغب في إضافة بعض التعلم الآلي وجعل عملية أكثر إثارة للاهتمام؟


شقق في يكاترينبرج

اليوم سوف نفكر في تطبيق Machine Learning لإيجاد شقة مثالية.


مقدمة


بادئ ذي بدء ، أود توضيح هذه اللحظة وشرح معنى "شقة مثالية". إنها شقة بها مجموعة من الخصائص المختلفة مثل "المنطقة" ، "الحي" ، "عدد الشرفات" وما إلى ذلك. وبالنسبة إلى ميزات الشقة ، نتوقع سعرًا محددًا. يشبه الدالة التي تأخذ العديد من المعلمات وإرجاع رقم. أو ربما الصندوق الأسود الذي يوفر بعض السحر.


لكن ... هناك "لكن" كبيرة ، في بعض الأحيان يمكنك مواجهة شقة مبالغ فيها بسبب مجموعة من الأسباب مثل الموقع الجغرافي الجيد. أيضا ، هناك المزيد من المناطق المرموقة في وسط المدينة والمناطق خارج المدينة. أو ... في بعض الأحيان يريد الناس بيع شققهم لأنهم ينتقلون إلى نقطة أخرى من الأرض. بمعنى آخر ، هناك العديد من العوامل التي يمكن أن تؤثر على السعر. هل يبدو مألوفا؟


خطوة صغيرة جانبا


قبل أن أواصل ، اسمحوا لي أن أصنع القليل من الانغماس الغنائي.
عشت في يكاترينبرج (المدينة بين أوروبا وآسيا ، واحدة من المدن التي عقدت بطولة العالم لكرة القدم في عام 2018) لمدة 5 سنوات.


كنت في حالة حب مع هذه الأدغال الخرسانية. وكرهت تلك المدينة لفصل الشتاء والنقل العام. إنها مدينة متنامية وهناك كل شهر الآلاف والآلاف من الشقق التي سيتم بيعها.


نعم ، إنها مدينة مكتظة وملوثة. في الوقت نفسه - إنه مكان جيد لتحليل سوق العقارات. تلقيت الكثير من الإعلانات عن الشقق ، من الإنترنت. وسوف استخدم هذه المعلومات إلى حد ما.


أيضًا ، حاولت تصور العروض المختلفة على خريطة يكاترينبرج. نعم ، إنها صورة جذابة من haracut ، وهي مصنوعة على Kepler.gl


صورة


يوجد أكثر من ألفي شقة بغرفة نوم واحدة تم بيعها في يوليو 2019 في يكاترينبرج. كان لديهم سعر مختلف ، من أقل من مليون إلى ما يقرب من 14 مليون روبل.


تشير هذه النقاط إلى موقعها الجغرافي. يمثل لون النقاط على الخريطة السعر ، وكلما انخفض السعر بالقرب من اللون الأزرق ، كلما ارتفع السعر بالقرب من اللون الأحمر. يمكنك اعتباره تشبيهًا للألوان الباردة والدافئة ، فالأكثر دفئًا هو السعر الأكبر.
من فضلك ، تذكر تلك اللحظة ، اللون الأحمر هو اللون ، وكلما ارتفعت قيمة شيء ما. نفس الفكرة تعمل باللون الأزرق ولكن في اتجاه أقل سعر.


الآن لديك نظرة عامة على الصورة والوقت لتحليل قادم.


هدف


ماذا أردت عندما عشت في ايكاترينبرج؟ بحثت عن شقة جيدة بما فيه الكفاية ، أو إذا تحدثنا عن ML - أردت أن أبني نموذجًا من شأنه أن يقدم لي توصية بشأن الشراء.


من ناحية ، إذا كان سعر الشقة مبالغًا فيه ، ينبغي أن يوصي النموذج بانتظار انخفاض السعر عن طريق إظهار السعر المتوقع لتلك الشقة.
من ناحية أخرى - إذا كان السعر جيدًا بما يكفي ، وفقًا لحالة السوق - فربما ينبغي عليّ التفكير في هذا العرض.


بالطبع ، لا يوجد شيء مثالي وكنت مستعدًا لقبول خطأ في الحسابات. عادة ، بالنسبة لهذا النوع من المهام ، استخدم خطأ في التنبؤ وكنت على استعداد لخطأ 10 ٪. على سبيل المثال ، إذا كان لديك 2-3 مليون روبل روسي ، فيمكنك تجاهل الخطأ في 200-300 ألف ، يمكنك تحمله. كما بدا لي.


إعداد


كما ذكرت من قبل ، كان هناك الكثير من الشقق ، دعونا ننظر إليها عن كثب.
استيراد الباندا كما PD


df = pd.read_csv('flats.csv') df.shape 

صورة


2310 شقة لمدة شهر واحد ، يمكننا استخراج شيء مفيد من ذلك. ماذا عن نظرة عامة على البيانات؟


 df.describe() 

صورة
لا يوجد شيء غير عادي - خط الطول ، خط العرض ، سعر شقة (التسمية " التكلفة ") ، وهلم جرا. نعم ، في تلك اللحظة التي استخدمت فيها " التكلفة " بدلاً من " السعر " ، آمل ألا يؤدي ذلك إلى سوء فهم ، فالرجاء اعتبارها كما هي.


تنظيف


هل كل سجل له نفس المعنى؟ يتم تمثيل بعض الشقق مثل مقصورة ، يمكنك العمل هناك ، ولكنك لا ترغب في العيش هناك. إنها غرف صغيرة ضيقة وليست شقة حقيقية. دع إزالتها.


 df = df[df.total_area >= 20] 

سعر تنبؤ الشقة يأتي من أقدم القضايا في الاقتصاد والمجالات ذات الصلة. لم يكن هناك شيء متعلق بمصطلح "ML" وحاول الناس تخمين السعر على أساس متر مربع / قدم.
لذلك ، ننظر إلى هذه الأعمدة / العلامات ونحاول الحصول على توزيعها.


 numerical_fields = ['total_area','cost'] for col in numerical_fields: mask = ~np.isnan(df[col]) sns.distplot(df[col][mask], color="r",label=col) plot.show() 

صورة


حسنا ... لا يوجد شيء خاص ، يشبه التوزيع الطبيعي. ربما نحن بحاجة للذهاب أعمق؟


 sns.pairplot(df[numerical_fields]) 

صورة


عفوًا ... هناك خطأ ما. تنظيف القيم المتطرفة في هذه الحقول ومحاولة تحليل بياناتنا مرة أخرى.


 #Remove outliers df = df[abs(df.total_area - df.total_area.mean()) <= (3 * df.total_area.std())] df = df[abs(df.cost - df.cost.mean()) <= (3 * df.cost.std())] #Redraw our data sns.pairplot(df[numerical_fields]) 

صورة


لقد انتهى المتطرفون ، والآن يبدو أفضل.


تحول


يجب تحويل التسمية "سنة" ، والتي تتم الإشارة إليها في سنة الإنشاء ، إلى شيء أكثر إفادة. فليكن عصر البناء ، وبعبارة أخرى كيف منزل معين هو القديم.


 df['age'] = 2019 -df['year'] 

دعنا نلقي نظرة على النتيجة.


 df.head() 

صورة


هناك كل أنواع البيانات ، الفئوية ، قيم نان ، وصف النص وبعض المعلومات الجغرافية (خطوط الطول والعرض). فلنضع جانبا آخرهم لأنهم في تلك المرحلة كانوا بلا فائدة. سوف نعود إليهم لاحقًا.


 df.drop(columns=["lon","lat","description"],inplace=True) 

البيانات الفئوية


عادة ، بالنسبة للبيانات الفئوية ، يستخدم الناس أنواعًا مختلفة من الترميز أو أشياء مثل CatBoost التي توفر فرصة للعمل معهم كما هو الحال مع المتغيرات العددية.
ولكن ، هل يمكننا استخدام شيء أكثر منطقية وأكثر سهولة؟ لقد حان الوقت لجعل بياناتنا أكثر قابلية للفهم دون أن تفقد معناها.


المناطق


حسنًا ، هناك أكثر من عشرين منطقة محتملة ، هل يمكننا إضافة أكثر من 20 متغيرًا إضافيًا في نموذجنا؟ بالطبع ، يمكننا ، لكن ... هل ينبغي علينا؟ نحن أشخاص ويمكننا مقارنة الأشياء ، أليس كذلك؟
بادئ ذي بدء - ليست كل منطقة تعادل منطقة أخرى. في وسط المدينة ، ترتفع أسعار المتر المربع ، أبعد من وسط المدينة - يصبح الانخفاض. هل يبدو منطقيا؟ هل يمكننا استخدام ذلك؟
نعم ، بالتأكيد يمكننا أن نطابق أي منطقة مع معامل معين ، أما الحي الآخر فهو الشقق الرخيصة.


بعد مطابقة المدينة واستخدام خريطة خدمة ويب أخرى (ArcGIS Online) تغيرت ولديها وجهة نظر مماثلة
صورة


لقد استخدمت نفس الفكرة لتصور الشقة. الحي "المرموق" و "الغالي" الملون باللون الأحمر والأقل - الأزرق. درجة حرارة اللون ، هل تتذكر ذلك؟
أيضا ، يجب علينا القيام ببعض التلاعب على dataframe لدينا.


 district_map = {'alpha': 2, 'beta': 4, ... 'delta':3, ... 'epsilon': 1} df.district = df.district.str.lower() df.replace({"district": district_map}, inplace=True) 

سيتم استخدام نفس النهج لوصف الجودة الداخلية للشقة. في بعض الأحيان تحتاج إلى بعض الإصلاحات ، وأحيانًا تكون المسطح جيدًا وجاهزًا للعيش. وفي حالات أخرى ، يجب أن تنفق أموالًا إضافية على جعلها تبدو أفضل (لتغيير الصنابير ، ولطلاء الجدران). يمكن أن يكون هناك أيضا استخدام المعاملات.


 repair = {'A': 1, 'B': 0.6, 'C': 0.7, 'D': 0.8} df.repair.fillna('D', inplace=True) df.replace({"repair": repair}, inplace=True) 

بالمناسبة ، عن الجدران. بطبيعة الحال ، فإنه يؤثر أيضا على سعر الشقة. المواد الحديثة أفضل من الأقدم ، والطوب أفضل من الخرسانة. الجدران من الخشب هي لحظة مثيرة للجدل إلى حد بعيد ، وربما هي اختيار جيد للريف ، ولكنها ليست جيدة للحياة الحضرية.


نحن نستخدم نفس النهج كما كان من قبل ، بالإضافة إلى تقديم اقتراح حول الصفوف التي لا نعرف شيئًا. نعم ، في بعض الأحيان لا يقدم الأشخاص كل المعلومات حول شقتهم. علاوة على ذلك ، بناءً على التاريخ ، يمكننا محاولة تخمين مواد الجدران. في فترة زمنية محددة (على سبيل المثال الفترة الرائدة في خروتشوف) - نحن نعرف عن المواد النموذجية للبناء.


 walls_map = {'brick': 1.0, ... 'concrete': 0.8, 'block': 0.8, ... 'monolith': 0.9, 'wood': 0.4} mask = df[df['walls'].isna()][df.year >= 2010].index df.loc[mask, 'walls'] = 'monolith' mask = df[df['walls'].isna()][df.year >= 2000].index df.loc[mask, 'walls'] = 'concrete' mask = df[df['walls'].isna()][df.year >= 1990].index df.loc[mask, 'walls'] = 'block' mask = df[df['walls'].isna()].index df.loc[mask, 'walls'] = 'block' df.replace({"walls": walls_map}, inplace=True) df.drop(columns=['year'],inplace=True) 

أيضا ، هناك معلومات حول الشرفة. في رأيي المتواضع - الشرفة شيء مفيد حقًا ، لذا لم أتمكن من مساعدة نفسي في التفكير فيه.
لسوء الحظ ، هناك بعض القيم الخالية. إذا كان مؤلف الإعلان قد فحص المعلومات المتعلقة به ، فسنحصل على معلومات أكثر واقعية.
حسنًا ، إذا لم تكن هناك معلومات فهذا يعني "عدم وجود شرفة".


 df.balcony.fillna(0,inplace=True) 

بعد ذلك ، نقوم بإسقاط الأعمدة بمعلومات حول سنة البناء (لدينا بديل جيد لها). أيضًا ، نزيل عمودًا يحتوي على معلومات حول نوع المبنى لأنه يحتوي على الكثير من قيم NaN ولم أجد أي فرصة لملء هذه الفجوات. ونحن إسقاط جميع الصفوف مع NaN التي لدينا.


 df.drop(columns=['type_house'],inplace=True) df = df.astype(np.float64) df.dropna(inplace=True) 

فحص


لذلك ... استخدمنا نهجًا غير قياسي واستبدلنا بالقيم الفئوية لتمثيلها العددي. والآن انتهينا من تحويل بياناتنا.
تم إسقاط جزء من البيانات ، لكن بشكل عام ، يعد مجموعة بيانات جيدة جدًا. دعونا نلقي نظرة على العلاقة بين المتغيرات المستقلة.


 def show_correlation(df): sns.set(style="whitegrid") corr = df.corr() * 100 # Select upper triangle of correlation matrix mask = np.zeros_like(corr, dtype=np.bool) mask[np.triu_indices_from(mask)] = True # Set up the matplotlib figure f, ax = plt.subplots(figsize=(15, 11)) # Generate a custom diverging colormap cmap = sns.diverging_palette(220, 10) # Draw the heatmap with the mask and correct aspect ratio sns.heatmap(corr, mask=mask, cmap=cmap, center=0, linewidths=1, cbar_kws={"shrink": .7}, annot=True, fmt=".2f") plot.show() # df[columns] = scale(df[columns]) return df df1 = show_correlation(df.drop(columns=['cost'])) 

صورة


إدارة مخاطر المؤسسات ... أصبحت مثيرة للاهتمام للغاية.
علاقة إيجابية
المساحة الكلية - الشرفات . لم لا؟ إذا كانت شقتنا كبيرة فسيكون هناك شرفة.
العلاقة السلبية
المساحة الكلية - العمر . الأحدث مسطح ، أكبر مساحة للعيش. الصوت المنطقي ، الجديد مسطح أكثر اتساعًا من الأقدم.
العمر - شرفة . الأكبر سنا هو عدد أقل من الشرفات. يبدو مثل العلاقة من خلال متغير آخر. ربما يكون مثلث Age-Balcony-Area حيث يكون لأحد المتغيرات تأثير ضمني على الآخر. ضع هذا في الانتظار لفترة من الوقت.
العمر - حي. المسطحة القديمة هي الاحتمال الكبير الذي سيتم وضعه في المناطق الأكثر شهرة. هل يمكن أن يرتبط ارتفاع الأسعار بالقرب من المركز؟


أيضا ، يمكننا أن نرى العلاقة مع المتغير التابع


 plt.figure(figsize=(6,6)) corr = df.corr()*100.0 sns.heatmap(corr[['cost']], cmap= sns.diverging_palette(220, 10), center=0, linewidths=1, cbar_kws={"shrink": .7}, annot=True, fmt=".2f") 

صورة


هنا نذهب ...


العلاقة القوية للغاية بين مساحة مسطحة والسعر. إذا كنت ترغب في الحصول على مكان أكبر للعيش ، فسيتطلب ذلك المزيد من المال.
هناك علاقة سلبية بين أزواج " العمر / التكلفة " و " المقاطعة / التكلفة ". شقة في منزل أحدث أقل بأسعار معقولة من القديم. وفي الريف الشقق أرخص.
على أي حال ، يبدو واضحا ومفهوما ، لذلك قررت أن أذهب معه.


نموذج


للمهام المتعلقة بسعر تنبؤ الشقة عادة ، استخدم الانحدار الخطي. وفقًا لترابط كبير من مرحلة سابقة ، يمكننا محاولة استخدامه أيضًا. إنه مكان عمل مناسب للعديد من المهام.
قم بإعداد بياناتنا للإجراءات التالية


 from sklearn.model_selection import train_test_split y = df.cost X = df.drop(columns=['cost']) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) 

أيضًا ، نقوم بإنشاء بعض الوظائف البسيطة للتنبؤ وتقييم النتيجة. دعونا نبذل قصارى جهدنا للتنبؤ السعر!


 def predict(X, y_test, model): y = model.predict(X) score = round((r2_score(y_test, y) * 100), 2) print(f'Score on {model.__class__.__name__} is {score}') return score def train_model(X, y, regressor): model = regressor.fit(X, y) return model 

 from sklearn.linear_model import LinearRegression regressor = LinearRegression() model = train_model(X_train, y_train, regressor) predict(X_test, y_test, model) 

صورة


حسنا ... 76.67 ٪ من الدقة. هل هو عدد كبير أم لا؟ وفقا لوجهة نظري ، أنها ليست سيئة. علاوة على ذلك ، إنها نقطة انطلاق جيدة. بالطبع ، ليست مثالية ، وهناك إمكانية للتحسين.


في الوقت نفسه - حاولنا التنبؤ بجزء واحد فقط من البيانات. ماذا عن تطبيق نفس الاستراتيجية على البيانات الأخرى؟ نعم ، وقت التحقق من الصحة.


 def do_cross_validation(X, y, model): from sklearn.model_selection import KFold, cross_val_score regressor_name = model.__class__.__name__ fold = KFold(n_splits=10, shuffle=True, random_state=0) scores_on_this_split = cross_val_score(estimator=model, X=X, y=y, cv=fold, scoring='r2') scores_on_this_split = np.round(scores_on_this_split * 100, 2) mean_accuracy = scores_on_this_split.mean() print(f'Crossvaladaion accuracy on {model.__class__.__name__} is {mean_accuracy}') return mean_accuracy do_cross_validation(X, y, model) 

صورة


نتيجة التحقق من صحة الآن نأخذ نتيجة أخرى. 73 أقل من 76. لكن ، إنه أيضًا مرشح جيد حتى لحظة سيكون لدينا فيها أفضل. أيضًا ، هذا يعني أن الانحدار الخطي يعمل بشكل مستقر على مجموعة البيانات الخاصة بنا.


والآن هو وقت الخطوة الأخيرة.


سوف ننظر إلى أفضل ميزة الانحدار الخطي - التفسير .
هذه المجموعة من النماذج ، على عكس النماذج الأكثر تعقيدًا ، لديها قدرة أفضل على الفهم. هناك فقط بعض الأرقام مع المعاملات ويمكنك وضع الأرقام الخاصة بك في المعادلة ، وجعل بعض الرياضيات البسيطة والحصول على نتيجة.


دعونا نحاول تفسير نموذجنا


 def estimate_model(model): sns.set(style="white", context="talk") f, ax = plot.subplots(1, 1, figsize=(10, 10), sharex=True) sns.barplot(x=model.coef_, y=X.columns, palette="vlag", ax=ax) for i, v in enumerate(model.coef_.astype(int)): ax.text(v + 3, i + .25, str(v), color='black') ax.set_title(f"Coefficients") estimate_model(regressor) 

معاملات نموذجنا


الصورة تبدو منطقية للغاية. الشرفة / الحوائط / المساحة / الإصلاح تقدم مساهمة إيجابية في سعر ثابت.
مزيد من شقة هو أكبر مساهمة سلبية . ينطبق أيضا على العمر. شقة كبار السن هو أقل سعر سيكون.


لذلك ، كانت رحلة رائعة.
لقد بدأنا من الأرض ، واستخدمنا النهج غير النموذجي لتحويل البيانات استنادًا إلى وجهة نظر الإنسان (الأرقام بدلاً من المتغيرات الوهمية) ، والمتغيرات التي تم فحصها وعلاقتها ببعضها البعض. بعد ذلك ، نحن نبني نموذجنا البسيط ، يستخدم التحقق المتبادل لاختباره. وبما أن الكرز على الكعكة - انظر إلى الأجزاء الداخلية للنموذج ، ما الذي يمنحنا الثقة في طريقنا.


ولكن! انها ليست النهاية رحلتنا ولكن فقط استراحة. سنحاول تغيير نموذجنا في المستقبل وربما (ربما فقط) يزيد من دقة التنبؤ.


شكرا للقراءة!


الجزء الثاني هو هناك

ملاحظة: توجد بيانات المصدر و Ipython-notebook هناك

Source: https://habr.com/ru/post/ar468053/


All Articles