之前發佈了一個對求職信息的網頁爬蟲,這之後做了一些機器學習的探索,這段時間項目基本介紹了,整理一下發布出來,供大家交流。
3基於邏輯迴歸的崗位分類器設計
3.1 ;邏輯迴歸算法簡介
假設數據集有n個獨立的特徵,x1到xn爲樣本的n個特徵。常規的迴歸算法的目標是擬合出一個多項式函數,使得預測值與真實值的誤差最小:
而我們希望這樣的f(x)能夠具有很好的邏輯判斷性質,最好是能夠直接表達具有特徵x的樣本被分到某類的概率。比如f(x)>0.5的時候能夠表示x被分爲正類,f(x)<0.5表示分爲反類。而且我們希望f(x)總在[0, 1]之間。有這樣的函數嗎?
sigmoid函數就出現了。這個函數的定義如下:
先直觀的瞭解一下,sigmoid函數的圖像如下所示:
sigmoid函數具有我們需要的一切優美特性,其定義域在全體實數,值域在[0, 1]之間,並且在0點值爲0.5。
那麼,如何將f(x)轉變爲sigmoid函數呢?令p(x)=1爲具有特徵x的樣本被分到類別1的概率,則p(x)/[1-p(x)]被定義爲讓步比(odds ratio)。引入對數:
上式很容易就能把p(x)解出來得到下式:
現在,我們得到了需要的sigmoid函數。接下來只需要和往常的線性迴歸一樣,擬合出該式中n個參數c即可。
3.2 :算法流程
3.3 :算法運用
1數據清洗
數據清洗是整個數據挖掘過程中必不可少的一部分,數據清洗的質量直接影響後續的模型效果和最終結果。從52job、智聯招聘利用爬蟲爬取的數據都是有噪聲的,不完全的和不一致的。在數據清洗的過程中進行填補遺漏數據、清除異常數據、平滑噪聲數據和糾正不一致數據。
2.中文分詞
英文中,計算機可以很容易的通過空格分辨英文單詞。但中文與英文不同,中以字爲單位,詞是由倆個字或者多個字組成。計算機很難理解哪些是中文的詞,把中文的漢字序列切分成有意義的詞,就是中文分詞。在分詞中,我們運用到的工具如下:
分詞工具 | 分詞粒度 | 出錯情況 | 詞性標註 | 認證方法 | 接口 |
Nltk | 多選擇 | 有 | 有 | 無 | 多語言工具 |
jieba分詞 | 多選擇 | 無 | 有 | 無 | Python庫 |
3.去停用詞
在文本挖掘中,爲提高算法效率和節省存儲空間,一般會把一些過於常用的詞,或者沒有意義的詞去除,這些詞是停用詞。在我們的系統中採用了中文停用詞表,進行去停用詞處理。
另外,在詞頻統計中,從高頻詞的分佈上我們發現大量對分類貢獻率低的詞,於是我們人爲添加一個停用詞表
類別 | 分詞去停用詞後文檔示例 |
大數據 | Hadoop 數據挖掘 Hive HBase Spark Storm Flume hadoop Map Reduce... |
雲計算 | Openstack TCP IP socket CCNA CCNP Citrix VMware SDN NFV ..... |
人工智能 | 機器學習 深度學習 計算機視覺 自然語言處理 語音處理 神經網絡.. |
物聯網 | 智慧 物聯網 製造 socket FTP 通信 DLNA AireKiss mqtt 5G時代... |
4 TF-IDF算法詞向量化
在將招聘信息分詞去停用詞後我們用TF-IDF特徵提取算法來把單詞文檔轉化成向量矩陣,用於邏輯迴歸訓練。
TF-IDF(TermFrequency-Inverse Document Frequency)是一種資訊檢索與資訊探勘的常用加權技術。TF-IDF值代表一個詞語在體文檔中的相對重要性。
| 包含該詞的文檔數(K) | IDF | TFIDF |
hadoop | 37.8 | 3.421 | 0.0898 |
Openstack | 14.3 | 2.713 | 0.0643 |
計算機視覺 | 2.4 | 0.603 | 0.0121 |
5G通信 | 5.5 | 2.410 | 0.0482 |
5邏輯迴歸
得到的招聘信息矩陣我們使用機器學習算法邏輯迴歸對其進行分類訓練,保留訓練矩陣,在之後網頁端,求職者輸入其職業技能,對其輸入信息進行算法智能分類。在邏輯迴歸算法特徵工程中,我們設置單詞貢獻率區間max_df=0.95 去除了比如‘數據’‘公司’這類出現機率很高的,但是對分類貢獻率很低的詞, min_df=2 去除出現頻度少於2次的單詞。建立了包涵30000個特徵詞的語料庫,在特徵詞組成上 選取1-4個字組成的單詞。
下面是實例代碼:
NBmain模塊(主模塊):
import os
import pandas as pd
import nltk
from NBtools import proc_text, split_train_test, get_word_list_from_data, \
extract_feat_from_data, cal_acc
from nltk.text import TextCollection
from sklearn.linear_model import LogisticRegression
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import cross_val_score
import matplotlib.pylab as plt
dataset_path = './dataset'
# 原始數據的csv文件
output_text_filename = '51job.csv'
# 清洗好的文本數據文件
output_cln_text_filename = 'clean_job_text.csv'
# 處理和清洗文本數據的時間較長,通過設置is_first_run進行配置
# 如果是第一次運行需要對原始文本數據進行處理和清洗,需要設爲True
# 如果之前已經處理了文本數據,並已經保存了清洗好的文本數據,設爲False即可
is_first_run = False
def run_main():
"""
主函數
"""
# 1. 數據讀取,處理,清洗,準備
if is_first_run:
print('處理清洗文本數據中...', end=' ')
# 如果是第一次運行需要對原始文本數據進行處理和清洗
# 讀取原始文本數據,將標籤和文本數據保存成csv
# 讀取處理好的csv文件,構造數據集
text_df = pd.read_csv(os.path.join(dataset_path, output_text_filename),usecols = ['崗位分類','崗位需求'],encoding = 'gbk').dropna()
# #字典映射處理類別
# title = {'大數據': 0, '雲計算': 1 , '人工智能': 2 ,'物聯網': 3}
# text_df['崗位分類'] = text_df['崗位分類'].map(title)
# 處理文本數據
text_df['崗位需求'] = text_df['崗位需求'].apply(proc_text)
# 過濾空字符串
text_df = text_df[text_df['崗位需求'] != '']
# 保存處理好的文本數據
text_df.to_csv(os.path.join(dataset_path, output_cln_text_filename),
index=None, encoding='utf-8')
print('完成,並保存結果。')
# 2. 分割訓練集、測試集
print('加載處理好的文本數據')
clean_text_df = pd.read_csv(os.path.join(dataset_path, output_cln_text_filename),
encoding='gbk')
# 分割訓練集和測試集
train_text_df, test_text_df = split_train_test(clean_text_df)
# 查看訓練集測試集基本信息
print('訓練集中各類的數據個數:', train_text_df.groupby('崗位分類').size())
print('測試集中各類的數據個數:', test_text_df.groupby('崗位分類').size())
vectorizer = TfidfVectorizer(
max_df=0.90, min_df=2,
sublinear_tf=True,
token_pattern=r'\w{1,}',
ngram_range=(1, 4),
max_features=30000)
vectorizer.fit(clean_text_df['崗位需求'])
train_features = vectorizer.transform(train_text_df['崗位需求'])
test_features = vectorizer.transform(test_text_df['崗位需求'])
print('TfidfVectorizer done.... start train')
train_target = train_text_df['崗位分類']
classifier = LogisticRegression(solver='sag')
cv_score = np.mean(cross_val_score(
classifier, train_features, train_target, cv=3))
print('對 {} 的準確率是 {}'.format('崗位分類', cv_score))
# # 3. 特徵提取
# # 計算詞頻
# n_common_words = 200
# # 將訓練集中的單詞拿出來統計詞頻
# print('統計詞頻...')
# all_words_in_train = get_word_list_from_data(train_text_df)
# fdisk = nltk.FreqDist(all_words_in_train)
# common_words_freqs = fdisk.most_common(n_common_words)
# print('出現最多的{}個詞是:'.format(n_common_words))
# for word, count in common_words_freqs:
# print('{}: {}次'.format(word, count))
# print()
# # 在訓練集上提取特徵
# text_collection = TextCollection(train_text_df['崗位需求'].values.tolist())
# print('訓練樣本提取特徵...', end=' ')
# train_X, train_y = extract_feat_from_data(train_text_df, text_collection, common_words_freqs)
# print('完成')
# print()
# print('測試樣本提取特徵...', end=' ')
# test_X, test_y = extract_feat_from_data(test_text_df, text_collection, common_words_freqs)
# print('完成')
# #4. 訓練模型Naive Bayes
# print('訓練模型...', end=' ')
# #gnb = GaussianNB()
# gnb = LogisticRegression(solver='sag')
# gnb.fit(train_X, train_y)
# print('完成')
# print()
# # 5. 預測
# print('測試模型...', end=' ')
# test_pred = gnb.predict(test_X)
# print('完成')
# # 輸出準確率
# print('邏輯迴歸準確率:', cal_acc(test_y, test_pred))
NBtools導入模塊代碼:
import re
import jieba.posseg as pseg
import pandas as pd
import math
import numpy as np
# 加載常用停用詞
stopwords1 = [line.rstrip() for line in open('./中文停用詞庫.txt', 'r', encoding='utf-8')]
stopwords2 = [line.rstrip() for line in open('./哈工大停用詞表.txt', 'r', encoding='utf-8')]
stopwords = stopwords1 + stopwords2
def proc_text(raw_line):
"""
處理每行的文本數據
返回分詞結果
"""
# # 1. 使用正則表達式去除非中文字符
# filter_pattern = re.compile('[^\u4E00-\u9FD5]+')
# chinese_only = filter_pattern.sub('', raw_line)
# 2. 結巴分詞+詞性標註
words_lst = pseg.cut(raw_line)
# 3. 去除停用詞
meaninful_words = []
for word, flag in words_lst:
# if (word not in stopwords) and (flag == 'v'):
# 也可根據詞性去除非動詞等
if word not in stopwords:
meaninful_words.append(word)
return ' '.join(meaninful_words)
def split_train_test(text_df, size=0.8):
"""
分割訓練集和測試集
"""
# 爲保證每個類中的數據能在訓練集中和測試集中的比例相同,所以需要依次對每個類進行處理
train_text_df = pd.DataFrame()
test_text_df = pd.DataFrame()
#labels = ['大數據', '雲計算', '人工智能','物聯網']
labels = [0, 1, 2, 3]
for label in labels:
# 找出label的記錄
text_df_w_label = text_df[text_df['崗位分類'] == label]
# 重新設置索引,保證每個類的記錄是從0開始索引,方便之後的拆分
text_df_w_label = text_df_w_label.reset_index()
# 默認按80%訓練集,20%測試集分割
# 這裏爲了簡化操作,取前80%放到訓練集中,後20%放到測試集中
# 當然也可以隨機拆分80%,20%(嘗試實現下DataFrame中的隨機拆分)
# 該類數據的行數
n_lines = text_df_w_label.shape[0]
split_line_no = math.floor(n_lines * size)
text_df_w_label_train = text_df_w_label.iloc[:split_line_no, :]
text_df_w_label_test = text_df_w_label.iloc[split_line_no:, :]
# 放入整體訓練集,測試集中
train_text_df = train_text_df.append(text_df_w_label_train)
test_text_df = test_text_df.append(text_df_w_label_test)
train_text_df = train_text_df.reset_index()
test_text_df = test_text_df.reset_index()
return train_text_df, test_text_df
def get_word_list_from_data(text_df):
"""
將數據集中的單詞放入到一個列表中
"""
word_list = []
for _, r_data in text_df.iterrows():
word_list += r_data['崗位需求'].split(' ')
return word_list
def extract_feat_from_data(text_df, text_collection, common_words_freqs):
"""
特徵提取
"""
# 這裏只選擇TF-IDF特徵作爲例子
# 可考慮使用詞頻或其他文本特徵作爲額外的特徵
n_sample = text_df.shape[0]
n_feat = len(common_words_freqs)
common_words = [word for word, _ in common_words_freqs]
# 初始化
X = np.zeros([n_sample, n_feat])
y = np.zeros(n_sample)
print('提取特徵...')
for i, r_data in text_df.iterrows():
if (i + 1) % 5000 == 0:
print('已完成{}個樣本的特徵提取'.format(i + 1))
text = r_data['崗位需求']
feat_vec = []
for word in common_words:
if word in text:
# 如果在高頻詞中,計算TF-IDF值
tf_idf_val = text_collection.tf_idf(word, text)
else:
tf_idf_val = 0
feat_vec.append(tf_idf_val)
# 賦值
X[i, :] = np.array(feat_vec)
y[i] = int(r_data['崗位分類'])
return X, y
def cal_acc(true_labels, pred_labels):
"""
計算準確率
"""
n_total = len(true_labels)
correct_list = [true_labels[i] == pred_labels[i] for i in range(n_total)]
acc = sum(correct_list) / n_total
return acc
代碼可能會有很多不足之處,還能優化。請大家指點