データホエール乾物
著者: Pi Qianchao、アモイ大学、Datawhale メンバー
深セン賃貸住宅データの徹底分析
2020年11月に深センの賃貸データ分析に関する最初の記事を公開してから、深層学習フレームワークKerasに基づく今回のモデリング分析と予測まで、3つの記事の特徴は次のとおりです。
1. 最初の記事:
2020 年 11 月に執筆された著者は、データ アナリストとして、Python、SQL、クローラー、視覚化、および機械学習のいくつかの一般的なアルゴリズムとモデルを学習しているため、最初の記事の焦点は「統計分析と視覚分析」です。この記事を読んだ友人は、美しい視覚化チャートがたくさんあることを知っているはずです (以下にいくつかの写真を示します)。
百聞は一見に如かず. 統計と視覚化チャートの観点から、データの分布と変化する傾向を迅速かつ直感的に確認できます。この記事で使用されている視覚化ライブラリは Plotly です。これは優れた動的視覚化ライブラリであり、学習することを強くお勧めします。
https://mp.weixin.qq.com/s/DEsclUfdnmVqICiK5rM57Q
2. パート 2:
2022 年 3 月に書かれたものですが、著者はまだデータ アナリストです。2020年末から2022年初頭までの約1年で、著者は機械学習アルゴリズム、特徴量エンジニアリング、モデル解釈可能性などの知識点に触れ、さらに学びました。
この記事では、著者は 10 分野の前処理と特徴量エンジニアリングに多大な労力を費やし、その後のさまざまな回帰モデルへの入力とさまざまなモデルの比較を容易にするエンコード処理をどのように行うかに焦点を当てています。
最後に、著者は、主に人気のある解釈可能なライブラリである SHAP に基づいて、モデルの解釈可能性を調査します。
SHAP はすべての機能を「貢献者」とみなします。予測サンプルごとに、モデルは対応する予測値を生成します。SHAP 値は、サンプル内の各特徴に割り当てられる値です。
特徴量エンジニアリングの研究に関して、著者は「特徴量エンジニアリングの入門と実践」という本をお勧めします。
https://mp.weixin.qq.com/s/iO47yo6IgYgw6xZ8W-8lbQ
3. 3本目(今回の記事)
3 番目の記事 (つまり、私が見た記事) を書いたとき、著者はまだデータ アナリストでした。今年の学習の焦点は、ディープ ラーニングと kaggle コンペティションに移行しました。最近、分類と回帰問題をモデリングするための DL の基礎と Keras フレームワークを学びました。モデリング プロセス全体の基本プロセスは、ネットワーク モデルの構築のステップから完了し、コンパイルとネットワークトレーニングです。
DL モデリング プロセスを徹底的に検討した後、次のステップで既存のモデルが最適化されます。
末尾に記載:3つの記事は筆者の学習経験や知識の一部をまとめたものであり、記事の具体的な内容に関して、もっと良い対処法や不適切な点があると思われる場合は、ご指摘ください。そして一緒に話し合ってください~
Datawhaleの舞台裏で 深センは レンタルデータをダウンロードできると返答
この記事は深セン賃貸データの 3 回目の分析であり、主にデータの前処理、サンプリング処理、Keras ベースのモデリングなどが含まれます。
インポートライブラリ
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()
データ形状
[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
欠損値の処理
欠損値を見つける
時刻フィールドに欠損値があります。欠損値が存在する行データ情報を見つけます。
欠損値を埋める
欠損値を埋める方法はいくつかあります。
特定の値を入力してください
平均値、最頻値などの既存のデータの統計値を入力します。
前後の項目の値などを記入します。
この記事では、インターネット上の各コミュニティの具体的な時間を直接見つけて記入します。
[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"] == "商铺"]
いくつかの部屋、ホール、バスルームを抽出します。ここでは Pandas の抽出関数を使用します。
df1 = df["layout"].str.extract(r'(?P<shi>\d)室(?P<ting>\d)厅(?P<wei>\d)卫')
df1.head()
# 合并到原数据
df = pd.concat([df1,df],axis=1)
# 原地删除原字段layout
df.drop("layout",axis=1,inplace=True)
3 つのフィールドの null 値を削除します。
# 基于3个字段删除空值
df.dropna(subset=["shi","ting","wei"],inplace=True)
df
位置
各場所での出現数をカウントします。
[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()
常識的に考えれば、家の価格は東向きと西向きよりも北向きと南向きのほうが高くなります。ここでは、下方向の各方向の最大値を取得します。
# 不同朝向下的均值:
price = (df.groupby("location")["money"].max()
.reset_index()
.sort_values("money")
.reset_index(drop=True))
price
注: 次に、方向のインデックス番号に従ってエンコードします: 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()
サイズとサイズ内側
構築領域と内部領域の処理:元のデータから数値と小数点を抽出します。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()
# 2、使用正则的方式提取
df["sizeInside"] = df["sizeInside"].str.extract(r'面积(?P<sizeInside>[\d.]+)')
df.head()
荘秀
装飾の違いは、カスタムのハードコーディングによって異なります。
[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()
# 中低高楼层和价格money之间的关系
fig = px.violin(df,y="money",color="numberFloor")
fig.show()
ワンホット コード形式でのエンコード: get_dummie 関数を使用
# 中高低楼层采用独热码的形式
df = (df.join(pd.get_dummies(df["numberFloor"]))
.rename(columns={"中楼层":"middleFloor",
"低楼层":"lowFloor",
"高楼层":"highFloor"}))
df.drop("numberFloor", axis=1, inplace=True)
df.head()
時間
コミュニティ内の住宅の完成時期:
[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
生データから特定の年の情報を抽出します。
これを数値に変換し、2022 年までの時間間隔を求めます。
# time转成数值型
df["time"] = df["time"].astype("float")
# 建成时间和当前年份的时间间隔
df["time"] = 2022 - df["time"]
df.head()
ゾーン+ポジション
行政区域と特定の場所が価格に与える影響
[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()
ゾーンとポジションをマージし、各特定のポジションの平均価格を計算し、平均のサイズに従ってエンコードします。
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
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()
道
家を借りるさまざまな方法。ここでは、全額家賃と共有家賃の 2 つの情報のみを抽出します。
[39] では:
fig = px.violin(df,y="money",color="way")
fig.show()
さまざまなレンタル方法が価格に与える影響: 明らかに、1 回払いと 2 回払いが最も一般的な方法です
リース全体と共同リースを抽出し、型エンコーディングを実装します。
# 整租-合租
df["way"] = df["way"].apply(lambda x: x.split(" ")[0])
df["way"] = df["way"].map({"整租":1,"合租":0})
df["way"].value_counts() # 1-整租 0-合租
調べてみると、ほとんどが丸ごとレンタルされており、シェアされているものはほとんどないことがわかりました。この2種類のサンプルはアンバランスとなっており、サンプリング処理は後ほど実装します。
サンプリング処理
前述したように、リース全体と共有リースのサンプル数は非常にアンバランスですが、ここではアップサンプリングを実行して共有リースの数を増やし、両方が同じになるようにします。
サンプリングの前に:
サンプリング後:
型変換
上記はさまざまなフィールドを前処理してエンコードし、一部のフィールドの型を変換する必要があることがわかります。
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)
すべて数値になります:
モデリング
以下は、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
一般にニューラル ネットワーク内のデータは比較的小さいことが必要ですが、ここでは同じ従属変数を一律に数万単位のデータに変換します。
[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] では:
トレーニングネットワーク
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()
定期的に紹介します
主に 2 つの側面があります。L1 または L2 正則化項目の追加、ドロップアウト層の追加、および早期停止戦略の実装です。
この論文では、L2 正規項が導入されます。
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()
再予測
[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 ページをクロール)
著者は最近再びデバッグし、直接実行できるようになりました。
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')
さまざまなフィールドを解析する
他のフィールドの正規解析式:
整理整頓が難しくて3倍好き↓