データプロジェクトの概要 - 賃貸住宅データ分析(完了)

 データホエール乾物 

著者: Pi Qianchao、アモイ大学、Datawhale メンバー

深セン賃貸住宅データの徹底分析

2020年11月に深センの賃貸データ分析に関する最初の記事を公開してから、深層学習フレームワークKerasに基づく今回のモデリング分析と予測まで、3つの記事の特徴は次のとおりです。

1. 最初の記事:

2020 年 11 月に執筆された著者は、データ アナリストとして、Python、SQL、クローラー、視覚化、および機械学習のいくつかの一般的なアルゴリズムとモデルを学習しているため、最初の記事の焦点は「統計分析と視覚分析」ですこの記事を読んだ友人は、美しい視覚化チャートがたくさんあることを知っているはずです (以下にいくつかの写真を示します)。

百聞は一見に如かず. 統計と視覚化チャートの観点から、データの分布と変化する傾向を迅速かつ直感的に確認できます。この記事で使用されている視覚化ライブラリは Plotly です。これは優れた動的視覚化ライブラリであり、学習することを強くお勧めします。

cfec97a5c0b3813711a55b6ca1e25a81.png

記事アドレス

https://mp.weixin.qq.com/s/DEsclUfdnmVqICiK5rM57Q

2. パート 2:

2022 年 3 月に書かれたものですが、著者はまだデータ アナリストです。2020年末から2022年初頭までの約1年で、著者は機械学習アルゴリズム、特徴量エンジニアリング、モデル解釈可能性などの知識点に触れ、さらに学びました。

この記事では、著者は 10 分野の前処理と特徴量エンジニアリングに多大な労力を費やし、その後のさまざまな回帰モデルへの入力とさまざまなモデルの比較を容易にするエンコード処理をどのように行うかに焦点を当てています。

最後に、著者は、主に人気のある解釈可能なライブラリである SHAP に基づいて、モデルの解釈可能性を調査します。

SHAP はすべての機能を「貢献者」とみなします。予測サンプルごとに、モデルは対応する予測値を生成します。SHAP 値は、サンプル内の各特徴に割り当てられる値です。

特徴量エンジニアリングの研究に関して、著者は「特徴量エンジニアリングの入門と実践」という本をお勧めします。

5e02ef559b8135a84f52ff4314b8eb38.png

記事アドレス

https://mp.weixin.qq.com/s/iO47yo6IgYgw6xZ8W-8lbQ

3. 3本目(今回の記事)

3 番目の記事 (つまり、私が見た記事) を書いたとき、著者はまだデータ アナリストでした。今年の学習の焦点は、ディープ ラーニングと kaggle コンペティションに移行しました。最近、分類と回帰問題をモデリングするための DL の基礎と Keras フレームワークを学びました。モデリング プロセス全体の基本プロセスは、ネットワーク モデルの構築のステップから完了し、コンパイルとネットワークトレーニングです。

DL モデリング プロセスを徹底的に検討した後、次のステップで既存のモデルが最適化されます。

ba1e9fd56d00d80d778d1be4da24a1c7.png

末尾に記載:3つの記事は筆者の学習経験や知識の一部をまとめたものであり、記事の具体的な内容に関して、もっと良い対処法や不適切な点があると思われる場合は、ご指摘ください。そして一緒に話し合ってください~

Datawhaleの舞台裏で 深センは レンタルデータをダウンロードできると返答

この記事は深セン賃貸データの 3 回目の分析であり、主にデータの前処理、サンプリング処理、Keras ベースのモデリングなどが含まれます。

cf96b56e9f4dada5db483ae3400a8008.png

インポートライブラリ

import numpy as np
import pandas as pd

import plotly.express as px
import plotly.graph_objects as go
import matplotlib.pyplot as plt
import seaborn as sns
# plt.style.use("fivethirtyeight")
plt.style.use('ggplot')

import sklearn.preprocessing as pre_processing
from imblearn.over_sampling import SMOTE   
from sklearn.preprocessing import StandardScaler,MinMaxScaler

import tensorflow as tf
from keras import models
from keras import layers

np.random.seed(123)

pd.options.mode.chained_assignment = None

基本データ情報

読み取りデータ

df = pd.read_excel("leyoujia.xls")
df.head()
e273a97e6db6e6950be4d0943668fa0a.png

データ形状

[3] では:

# 数据形状

df.shape

アウト[3]:

(2020, 12)

データ形状はリストを返します。最初の値はデータの行数を示し、2 番目の値は属性の数、つまりフィールドの数を示します。

フィールドタイプ

[4] では:

# 数据的字段类型

df.dtypes

アウト[4]:

それらのほとんどは文字列型で、お金のみです。つまり、最終的な予測値は数値です。

name           object
layout         object
location       object
size           object
sizeInside     object
zhuangxiu      object
numberFloor    object
time           object
zone           object
position       object
money           int64
way            object
dtype: object

[5] では:

# 数据中的缺失值

df.isnull().sum()

アウト[5]:

name           0
layout         0
location       0
size           0
sizeInside     0
zhuangxiu      0
numberFloor    0
time           6  # 缺失值
zone           0
position       0
money          0
way            0
dtype: int64

欠損値の処理

欠損値を見つける

時刻フィールドに欠損値があります。欠損値が存在する行データ情報を見つけます。

134a96d835188e40fd67c6d162c641ec.png

欠損値を埋める

欠損値を埋める方法はいくつかあります。

  • 特定の値を入力してください

  • 平均値、最頻値などの既存のデータの統計値を入力します。

  • 前後の項目の値などを記入します。

この記事では、インターネット上の各コミュニティの具体的な時間を直接見つけて記入します。

[7] では:

# 2019 2003 2004  2019 2019 2020

times = ["2019年", "2003年", "2004年", "2019年", "2019年", "2020年"]

# 通过对应的索引位置来填充缺失值
for i in range(len(df0)):
    df.iloc[df0.index.tolist()[i], 7] = times[i]

[8] では:

df.isnull().sum()

アウト[8]:

入力後、欠損値がないことがわかります。

name           0
layout         0
location       0
size           0
sizeInside     0
zhuangxiu      0
numberFloor    0
time           0
zone           0
position       0
money          0
way            0
dtype: int64

前処理

次のコンテンツは、豊富な機能エンジニアリング作業を含む、さまざまな分野の前処理です。

名前

コミュニティの名前はモデリングに役に立たないため、直接削除することを検討してください。

[9] では:

df.drop("name",axis=1,inplace=True)  # inplace=True表示原地修改

レイアウト

レイアウトは部屋、ホール、バスルームの 3 つの属性に分かれており、レイアウトが「店舗」の場合は、この部分のデータを直接削除します

[10] では:

df[df["layout"] == "商铺"]
5e277f6e5ae673ecae36ae0de7b56c74.png

いくつかの部屋、ホール、バスルームを抽出します。ここでは Pandas の抽出関数を使用します。

df1 = df["layout"].str.extract(r'(?P<shi>\d)室(?P<ting>\d)厅(?P<wei>\d)卫')
df1.head()
39e2d8b84904de4f3fa028965e39e3dd.png
# 合并到原数据
df = pd.concat([df1,df],axis=1)
# 原地删除原字段layout
df.drop("layout",axis=1,inplace=True)
68691e23ea2568015f9c50732fa34659.png

3 つのフィールドの null 値を削除します。

# 基于3个字段删除空值
df.dropna(subset=["shi","ting","wei"],inplace=True)
df
d5543a674c4ea3f602bfeb23a066444c.png

位置

各場所での出現数をカウントします。

[14] では:

df["location"].value_counts()

アウト[14]:

朝南     552
朝南北    284
朝东南    241
朝北     241
朝西南    174
朝西北    142
朝东北    140
朝东     132
朝西      92
朝东西      2
Name: location, dtype: int64

[15] では:

さまざまな方向での家賃の価格分布:

fig = px.violin(df,y="money",color="location")

fig.show()
3f17a6f72cf241f0f472614cf298eed7.png

常識的に考えれば、家の価格は東向きと西向きよりも北向きと南向きのほうが高くなります。ここでは、下方向の各方向の最大値を取得します。

# 不同朝向下的均值:
price = (df.groupby("location")["money"].max()
         .reset_index()
         .sort_values("money")
         .reset_index(drop=True))

price
1617b1a8806d7e09fc8e6b68f2844ba1.png

注: 次に、方向のインデックス番号に従ってエンコードします: east-west-0、north-west-1、North-south-9

この点が2回目の分析とは異なります。2 番目の分析は、価格分布のバイオリン プロットのカスタム コーディング順序に基づいています。

# 第二弹中编码自定义:location = ["朝东西","朝东北","朝西","朝西北","朝东","朝西南","朝东南","朝南","朝北","朝南北"]

location = price["location"].tolist()
location_dict = {}

for n, i in enumerate(location):
    location_dict[i] = n+1 # 编码从1开始 
    
df["location"] = df["location"].map(location_dict)
df.head()
5f6ca5271f704f38c8135098f1a084c7.png

サイズとサイズ内側

構築領域と内部領域の処理:元のデータから数値と小数点を抽出します。2 つの方法を提供します

[18] では:

df.dtypes

アウト[18]:

shi            object
ting           object
wei            object
location        int64
size           object
sizeInside     object
zhuangxiu      object
numberFloor    object
time           object
zone           object
position       object
money           int64
way            object
dtype: object

[19] では:

# 1、通过切割的方式来提取
df["size"] = df["size"].apply(lambda x: x.split("面积")[1].split("㎡")[0])
df.head()
13addc3ba5acd690d205f21f7aba8595.png
# 2、使用正则的方式提取
df["sizeInside"] = df["sizeInside"].str.extract(r'面积(?P<sizeInside>[\d.]+)')
df.head()
7d8f33b7f8c2aa00715829b8210a5769.png

荘秀

装飾の違いは、カスタムのハードコーディングによって異なります。

[21] では:

df["zhuangxiu"].value_counts()

アウト[21]:

精装    1172
普装     747
豪装      62
毛坯      19
Name: zhuangxiu, dtype: int64

主観的な意味でのアイデア: ブランクのグレードは最も低く、高級感は最も高いため、ここではカスタムのハードコーディングされたメソッドが直接使用されます。

[22] では:

# 硬编码
zhuangxiu = {"毛坯":1,"普装":2, "精装":3, "豪装":4}
zhuangxiu

アウト[22]:

{'毛坯': 1, '普装': 2, '精装': 3, '豪装': 4}

[23] では:

df["zhuangxiu"] = df["zhuangxiu"].map(zhuangxiu)

階数

床の高さも価格に大きく影響します。中層階、低層階、高層階と金額の関係を分析すると以下のようになります。

[24] では:

# 提取中低高楼层

df["numberFloor"] = df["numberFloor"].apply(lambda x: x.split("(")[0])
df.head()
3fdaff8ab6d2f134ec9599964097abb3.png
# 中低高楼层和价格money之间的关系
fig = px.violin(df,y="money",color="numberFloor")

fig.show()
6c6d97315f5952d2590aad73cb4a0b51.png

ワンホット コード形式でのエンコード: get_dummie 関数を使用

93a25cca78414ab2a1f3a4baf34c38f0.png
# 中高低楼层采用独热码的形式

df = (df.join(pd.get_dummies(df["numberFloor"]))
    .rename(columns={"中楼层":"middleFloor",
                    "低楼层":"lowFloor",
                    "高楼层":"highFloor"}))

df.drop("numberFloor", axis=1, inplace=True)

df.head()
b2d36c3cf499b04a45740169074efe44.png

時間

コミュニティ内の住宅の完成時期:

[30] では:

df["time"].value_counts()

# 部分结果
2003年建成    133  # 数量
2005年建成    120
2006年建成    114
2004年建成    111
2010年建成    104
2007年建成    101
2016年建成     94
2008年建成     92
2002年建成     79
2015年建成     78

生データから特定の年の情報を抽出します。

8ce51d9c7bea01e36306b82cb9e8d4b3.png

これを数値に変換し、2022 年までの時間間隔を求めます。

# time转成数值型
df["time"] = df["time"].astype("float")
# 建成时间和当前年份的时间间隔
df["time"] = 2022 - df["time"]
df.head()
28e124b971bc4cac9edec83190edaec1.png

ゾーン+ポジション

行政区域と特定の場所が価格に与える影響

[33] では:

df["zone"].value_counts()

アウト[33]:

龙岗      548
福田      532
龙华      293
南山      218
宝安      173
罗湖      167
光明       32
坪山       31
盐田        5
大鹏新区      1
Name: zone, dtype: int64

[34] では:

fig = px.violin(df,y="money",color="zone")

fig.show()
f06ecb1748f8f602e59520529a3b7365.png

ゾーンとポジションをマージし、各特定のポジションの平均価格を計算し、平均のサイズに従ってエンコードします

df["zone_position"] = df["zone"] + "_" + df["position"]

zone_position_mean = (df.groupby("zone_position")["money"].mean()
                      .reset_index()
                      .sort_values("money",ascending=True,ignore_index=True)) # 升序排列

zone_position_mean
da1da9a04c8de51d5fbdcaacaf9c2fc7.png
zone_position = zone_position_mean["zone_position"].tolist()

zone_position_dict = {}

for n, i in enumerate(zone_position):
    zone_position_dict[i] = n+1     
df["zone_position"] = df["zone_position"].map(zone_position_dict)
df.drop(["zone","position"],axis=1,inplace=True)  # 原地删除

df.head()
902ffeb14c764ff684b9650b8ca9735a.png

家を借りるさまざまな方法。ここでは、全額家賃と共有家賃の 2 つの情報のみを抽出します。

[39] では:

fig = px.violin(df,y="money",color="way")

fig.show()

さまざまなレンタル方法が価格に与える影響: 明らかに、1 回払いと 2 回払いが最も一般的な方法です

361cb976476a1ecac0057ee6773ca87e.png

リース全体と共同リースを抽出し、型エンコーディングを実装します。

# 整租-合租
df["way"] = df["way"].apply(lambda x: x.split(" ")[0])
df["way"] = df["way"].map({"整租":1,"合租":0})

df["way"].value_counts()  # 1-整租 0-合租
9fbd88f5729d5cbf009146d0189f6b04.png

調べてみると、ほとんどが丸ごとレンタルされており、シェアされているものはほとんどないことがわかりました。この2種類のサンプルはアンバランスとなっており、サンプリング処理は後ほど実装します。

サンプリング処理

前述したように、リース全体と共有リースのサンプル数は非常にアンバランスですが、ここではアップサンプリングを実行して共有リースの数を増やし、両方が同じになるようにします。

サンプリングの前に:

8acdfa62596338f3a40afd64d4d931f1.png 77c7b0a6c6c4c4fe1379dadef396c8d9.png

サンプリング後:

0897039ced929b4e51c21ab0c2949e7b.png

型変換

上記はさまざまなフィールドを前処理してエンコードし、一部のフィールドの型を変換する必要があることがわかります。

624998350fdc21293107979160261df9.png
col1 = ["shi","ting","wei","time"]
for i in col1:
    smoted_df[i] = smoted_df[i].apply(lambda x: round(x))

col2 = ["size", "sizeInside"]
for i in col2:
    smoted_df[i] = smoted_df[i].astype(float)

すべて数値になります:

ab2a4b03842c75a4acb598b71b5c2f4e.png

モデリング

以下は、smoted_df データをモデル化するものです。

機能とラベル

X = smoted_df.drop("money",axis=1)  # 特征
y = smoted_df["money"] # 标签

データを分割する

from sklearn.model_selection import train_test_split

# 8-2的比例
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.2,random_state=44)

データの標準化

mean = X_train.mean(axis=0)
X_train -= mean  
std = X_train.std(axis=0)
X_train /= std

# 测试集:使用训练集的均值和标准差来归一化
X_test -= mean 
X_test /= std
26697cfb990b06ee432d83db915d39b7.png

一般にニューラル ネットワーク内のデータは比較的小さいことが必要ですが、ここでは同じ従属変数を一律に数万単位のデータに変換します。

[58] では:

y_train = y_train / 10000
y_test = y_test / 10000

y_train[:5]

アウト[58]:

3201    0.6000
3013    0.2154
597     0.5000
1524    0.4300
3354    0.1737
Name: money, dtype: float64

ネットワークを構築する

トレーニング セットのサンプルの総数は 3000 以上ですが、ここでは 2 つの隠れ層と各層に 64 ユニットのみを持つ非常に小さなネットワークを使用します。

ネットワークの最後の層にはユニットが 1 つだけあり、他の活性化関数はなく、比較的純粋な線形層です。

[59] では:

model = models.Sequential()
model.add(tf.keras.layers.Dense(64,
                                activation="relu",
                                input_shape=(X_train.shape[1],)))

model.add(tf.keras.layers.Dense(64,
                                activation="relu"))

model.add(tf.keras.layers.Dense(1))

ネットワークをコンパイルする

このモデリングでは、損失関数 loss は mse: 平均二乗誤差、平均二乗誤差、予測​​値とターゲットの実際の値の差の二乗を使用します。

監視される指標は mae: 平均絶対誤差で、予測値と実際の目標値の差の絶対値を表します。

[60]:

model.compile(optimizer="rmsprop",  # 优化器
              loss="mse",  # 损失函数
              metrics=["mae"]  # 评估指标:平均绝对误差
             )

ネットワークアーキテクチャ

ネットワーク全体の基本アーキテクチャを表示する

[61] では:

4091b330ae9fc027e7bf84c7d4a6f4fe.png

トレーニングネットワーク

5 分割相互検証の導入: トレーニング データ セットから特定の検証データ セットを分割します。

[62] では:

k = 5
number_val = len(X_train) // k  # 验证数据集的大小
number_epochs = 20
all_mae_scores = []
all_loss_scores = []

for i in range(k):
    # 只取i到i+1部分作为验证集
    vali_X = X_train[i * number_val: (i+1) * number_val]
    vali_y = y_train[i * number_val: (i+1)*number_val]
    
    # 训练集
    part_X_train = np.concatenate([X_train[:i * number_val],
                                  X_train[(i+1) * number_val:]],
                                  axis=0
                                 )
    part_y_train = np.concatenate([y_train[:i * number_val],
                                  y_train[(i+1) * number_val:]],
                                  axis=0
                                 )
    
    # 模型训练
    history = model.fit(part_X_train,
                        part_y_train,
                        epochs=number_epochs
                        # 传入验证集的数据
                        validation_data=(vali_X, vali_y),
                        batch_size=300,
                        verbose=0  # 0-静默模式 1-日志模式
                       )
    
    mae_history = history.history["mae"]
    loss_history = history.history["loss"]
    all_mae_scores.append(mae_history)
    all_loss_scores.append(loss_history)

ネットワークメトリクス

# 每个轮次中的平均值
average_mae = [np.mean([x[i] for x in all_mae_scores]) for i in range(number_epochs)]
average_loss = [np.mean([x[i] for x in all_loss_scores]) for i in range(number_epochs)]
# 每个轮次(20轮)的均值
average_mae 

# 结果
[0.14793895035982133,
 0.12548727840185164,
 0.1141199067234993,
 0.1111918956041336,
 0.10730082243680954,
 0.10863531827926635,
 0.10383812189102173,
 0.10521284639835357,
 0.10574782490730286,
 0.10005746781826019,
 0.10514769405126571,
 0.10096234679222107,
 0.10278342366218567,
 0.0960465505719185,
 0.10629244297742843,
 0.09704757779836655,
 0.09838753342628478,
 0.10160793513059616,
 0.0998133972287178,
 0.09991184771060943]

損失と mae の全体の平均は次のとおりです。

# 整体均值
print("mae的均值:",np.mean(average_mae))
print("loss的均值:",np.mean(average_loss))

mae的均值: 0.1068765591084957
loss的均值: 0.058070002682507026

maeは約0.1で、予測値と実際の値の差は約1,000元(単位は万)であることを意味します。

モデルの評価

関数評価を通じてモデルを評価し、テスト セット内のデータを渡します。

[65] では:

model.evaluate(X_test, y_test)
25/25 [==============================] - 0s 3ms/step - loss: 0.0696 - mae: 0.1295

アウト[65]:

[0.06960742175579071, 0.12945905327796936]

損失の値は0.0696、前値は約0.1295であることがわかります。これは、予測値と実際の値の差が0.1295万元、約1295元であることを意味します。

損失前可視化

# 损失绘图
import matplotlib.pyplot as plt

history_dict = history.history
loss_values = average_loss
mae_values = average_mae

epochs = range(1,len(loss_values) + 1)

plt.plot(epochs,  # 循环轮数
         loss_values,  # loss取值
         "r",  
         label="loss"
        )

plt.plot(epochs,
         mae_values,
         "b",
         label="mae"
        )

plt.title("Loss and Mae")
plt.xlabel("Epochs")
plt.legend()

plt.show()
9160178423118ac30e07aef68e06d198.png

定期的に紹介します

主に 2 つの側面があります。L1 または L2 正則化項目の追加、ドロップアウト層の追加、および早期停止戦略の実装です。

この論文では、L2 正規項が導入されます。

c0e229327c1e0f9a1de966d24d18c018.png
mae的均值: 0.11811128027737142
loss的均值: 0.07401402860879899

新しく生成された loss-mae の視覚化:

# 损失绘图
import matplotlib.pyplot as plt

history_dict = history.history
loss_values = average_loss
mae_values = average_mae

epochs = range(1,len(loss_values) + 1)

# 训练
plt.plot(epochs,  # 循环轮数
         loss_values,  
         "r",  # 红色
         label="loss"
        )

plt.plot(epochs,
         mae_values,
         "b",
         label="mae"
        )

plt.title("Loss and Mae")
plt.xlabel("Epochs")
plt.legend()

plt.show()
f7ab9854720d6d28e3b3eebfd6496166.png

再予測

[69] では:

model.evaluate(X_test, y_test)
25/25 [==============================] - 0s 3ms/step - loss: 0.0634 - mae: 0.1101

アウト[69]:

[0.06338492780923843, 0.110066257417202]

# 优化前 [0.06960742175579071, 0.12945905327796936]

正規項の導入後、モデルは最適化されます。損失と前値の両方に一定の減少があります。maeは0.11となり、予測値と実際の値の差は約1100元となる。

まとめ

この論文は、Webからクロールされたレンタルデータから始まり、データの基本情報の探索、欠損値処理、特徴量エンジニアリング、サンプルの不均衡処理、Kerasベースの深層学習モデルの構築と最適化など、複数のステップからモデリング分析を実行します。 .、レンタルデータ価格の予測と分析を完了し、最終的な誤差は約1,100元に制御されます。

モデルに正規項を追加した後、モデルの損失と前値が最適化されました~

要約する

3 つの記事の概要コンテンツを記述する前に、クローラーのコードを貼り付けます。実行するにはリクエスト ヘッダーを変更するだけです。

1. クローラーコード

2 つの異なる方法でのレンタル データ クローラーのソース コードを以下に示します。

import pandas as pd
import numpy as np

import json
from lxml import etree
import requests
import xlwt
import re
import time

1. xpath に基づくクローラー コード (Web 全体から 100 ページをクロール)

著者は最近再びデバッグし、直接実行できるようになりました。

20320ba65b51482bf6c98ff301230945.png

2. 通常の解析に基づくクロール (単一ページのクロールと解析)

最近、著者は正規表現を使用してフィールドを分析しました。

import pandas as pd
import numpy as np
import requests
import re

url = "https://shenzhen.leyoujia.com/zf/?n=1"
headers = {"User-Agent": "个人请求头"}  # 更换请求头即可

# 返回的是解析内容
response = requests.get(url=url, headers=headers).content.decode('utf-8', 'ignore')

さまざまなフィールドを解析する

5f3f76ac7744a406e44ec6ccaf2ccdf2.png ed7383cbeb80376ee4c6817551d63652.png f9e73dad999cfe54f15ef4b41fd8dcba.png

他のフィールドの正規解析式:

22ce2f052022564aff1b66160d2665c8.png

956082b0aab2c78793fae80e9b21b191.png

整理整頓が難しくて3好き

おすすめ

転載: blog.csdn.net/Datawhale/article/details/125093131