用戶畫像——搜狗用戶挖掘:文本分類

這是搜狗的一個比賽,大約是去年還是前年的比賽來著,我是近些天無意在網上找到了這個東西,原鏈接已經找不到了。

用戶畫像是對用戶的描述,一般用來精準營銷。思路是多樣的:比如比較low的詞雲,還有我曾經看過的一個利用貝葉斯對不同特徵組合計算概率從而得到轉化率比較高的組合來精準營銷。

至於這個,則是搜狗的數據集,格式是這樣的,train:id,年齡分類12345,性別12,教育12345,然後用戶在某個時間段的輸入句子,每個用戶的句子有很多,以 鏈接。我是奔著用戶畫像去的,結果看了要求,發現碼的,這根本就是文本分類,只不過分類是用戶的標籤而已,像這種不愛吹牛逼,自然也不懂這東西有多大價值,不過就當練手了。

接著說文本分類,方法有很多,不過用的多還是貝葉斯分類,這個我也是比較熟練。如果你們又擅長其他方向,請多多指教。貝葉斯自己設計演算法是比較簡單的,不過我只設計過二分類,多分類我還是要調包,一個是NTLK的樸素貝葉斯包,一個是sklearn的一個m開頭的貝葉斯多項式包。

閑話少說,我們開始。

--------------------------------------------------------------------------------------------

1.原數據集是沒有後綴名的,直接用pandas的csv格式打開的話,id和性別辭彙全連在一起,然後我添了個excel的後綴名,前三列是分開了,但後面的句子也被分開了,也就是說:每一行除了標籤列,還有很多列,每一列有一個句子,這樣切詞不方便,excel合併一合計算就爆炸,所以就不偷懶了,還是用python直接在文本上操作。然後寫進新的標準格式的csv,這樣後期再用pandas處理。

思路:導入csv模塊,新建一個csv文件,第一行寫入行名稱然後直接打開沒有後綴名的源文件:以 為分隔符分割,然後把id,age,gender,education,提取出來,然後再對分列之後的句子合併,並轉碼。最後逐行寫入csv文件。

import csvadd = user_tag_query.10W.TRAIN #path of the original train filecsvfile = file(add + .csv, wb)# the path of the generated train filewriter = csv.writer(csvfile)writer.writerow([ID,age,Gender,Education,QueryList])with open(add, r) as f: for line in f: line.strip() data = line.split(" ") writedata = [data[0],data[1],data[2],data[3]] querystr = data[-1]=data[-1][:-1] for d in data[4:]: try: querystr += d.decode(GB18030).encode(utf8)+ except: print data[0],querystr querystr = querystr[:-1] writedata.append(querystr) writer.writerow(writedata)add = user_tag_query.10W.TEST#path of the original test filecsvfile = file(add + .csv, wb)# the path of the generated test filewriter = csv.writer(csvfile)writer.writerow([ID,QueryList])with open(add, r) as f: for line in f: data = line.split(" ") writedata = [data[0]] querystr = data[-1]=data[-1][:-1] for d in data[1:]: try: querystr +=d.decode(GB18030).encode(utf8)+ except: print data[0],querystr querystr = querystr[:-1] writedata.append(querystr) writer.writerow(writedata)

2.現在新的csv文件已經生成,並做了一些簡單的描述探索,因為是比賽數據所以和我想的一樣,基本沒有錯誤。那麼開始處理吧,第二部把標籤單獨提取出來保存為文件(當然不提直接在pandas操作也可以,無所謂的事,個人習慣)

#coding=utf-8import pandas as pdtrain_path=user_tag_query.10W.TRAIN.csvtest_path=user_tag_query.10W.TEST.csvtrain_data=pd.read_csv(train_path)test_data=pd.read_csv(test_path)train_data.age.to_csv(train_data_age.csv,index=False)train_data.Gender.to_csv(train_data_gender.csv,index=False)train_data.Education.to_csv(train_data_education.csv,index=False)train_data.QueryList.to_csv(train_data_querylist.csv,index=False)test_data=pd.read_csv(test_path)test_data.QueryList.to_csv(test_data_quesylist.csv,index=False)print train_data.Gender.value_counts()

其中提一下性別,按理說性別只有12,但在excel里看到有3,就在最後加了一句,

12應該是男女了,但這個0我就有點蒙圈了,到底是錯誤數據(沒打上標籤的),還是真實的比如同性之類的,因為原始的比賽鏈接了,所以對真實狀況不了解,我這裡決定暫時放著不管,當成百合和鈣吧。

3.數據已經規範起來了,那我們開始切詞,切詞我已經提過好幾次了,這裡不過多重複,可以去翻前面的。

#encoding=utf-8import pandas as pdimport jiebaimport jieba.possegimport timecutpath=train_data_querylist.csvcutfile=open(cutpath,r,encoding=utf8)#print cutfile[0][0]after_cut_train=open(aftercuttrain.csv,w)time1=time.clock()pos={}for i in cutfile: #print (i) s=[] str1= words=jieba.cut(i) for word in words: #pos[flag]=pos.get(flag,0)+1 if len(word)>=2: str1+=word+ s.append(str1) after_cut_train.write(str1+
)after_cut_train.closeprint (pos)time2=time.clock()print(total time:%f s%(time2-time1))

注意這裡,pos是一個字典,目前是空的,我原本想用帶詞性標註的分詞,然後用字典來存儲flag,但win沒有並行分詞,實在是慢的急人,就刪掉了,至於s的列表,原本是想把所有分詞寫進去,但後來發現太麻煩了,不如直接str拼接write。最後這裡用的3.x,這個分詞的編碼問題實在是頭疼,雖然有decode和encode,以及,wb,rb等各種方法,但混來混去,把我自己都搞暈了,報錯越來越多,所以還是用了3.x

這裡過去粗略,後期還有很多東西,比如findall中文字元,停用詞表刪詞,後期修飾時再補充修改

--------------------------------------------------------------------------------

原來一直用的貝葉斯,最近新得到一個東西。gensim的word2vec可以訓練詞向量,並保存模型,利用的神經網路,一步到位。所以決定試試這個東西。

4.先把train分詞和test分詞合併起來,用pandas的concat,axis=0按行拼接。

然後導入到word2vex訓練並保存模型。

#coding=utf-8import class_w2vimport preprocessimport numpy as npimport csvdef input(trainname): """ load file :param trainname:path :return: list """ traindata = [] with open(trainname, rb) as f: reader = csv.reader(f) count = 0 for line in reader: try: traindata.append(line[0]) count += 1 except: print "error:", line, count traindata.append(" ") return traindataif __name__ == __main__: """ 使用方法:先訓練wv的model,然後再生成wv的向量,最後可以使用2-fold驗證效果 主要目的:生成WV向量,提供給下一個步驟:特徵融合。 注意路徑 """ print ---------w2v---------- #order = train w2v model #order=getvec order = test print order is, order classob = class_w2v.w2v(300) if order == train w2v model: #訓練WV的model totalname = jieba_total_cut.csv #純文本文件路徑 classob.train_w2v(totalname) exit() elif order == getvec: #利用生成的model得到文檔的WV的向量,使用求和平均法 trainname = aftercuttrain.csv testname = aftercuttest.csv traindata = input(trainname) testdata = input(testname) res1 = classob.load_trainsform(traindata) res2 = classob.load_trainsform(testdata) print res1.shape,res2.shape np.save(wv300_win100.train.npy, res1)#保存生成的向量 np.save(wv300_win100.test.npy, res2) exit() #以下為測試wv向量,即僅僅使用wv向量做這個比賽,目的在於尋找最好參數的WV向量 print 載入所有的w2v向量中.. w2vtrain = np.load(wv300_win100.train.npy) w2vtest = np.load(wv300_win100.test.npy) #防止出現非法值 if np.any((np.isnan(w2vtrain))): print nan to num! w2vtrain = np.nan_to_num(w2vtrain) if np.any((np.isnan(w2vtest))): print nan to num! w2vtest = np.nan_to_num(w2vtest) #載入label文件 label_genderfile_path = train_data_gender.csv label_agefile_path = train_data_age.csv label_edufile_path = train_data_education.csv genderdata = np.loadtxt(open(label_genderfile_path, r)).astype(int) agedata = np.loadtxt(open(label_agefile_path, r)).astype(int) educationdata = np.loadtxt(open(label_edufile_path, r)).astype(int) print 預處理中.. preprocessob = preprocess.preprocess() gender_traindatas, genderlabel = preprocessob.removezero(w2vtrain, genderdata) age_traindatas, agelabel = preprocessob.removezero(w2vtrain, agedata) edu_traindatas, edulabel = preprocessob.removezero(w2vtrain, educationdata) # ------------------------------------------------------ if order == test: #使用2-fold進行驗證 res1 = classob.validation(gender_traindatas, genderlabel, kind=gender) res2 = classob.validation(age_traindatas, agelabel, kind=age) res3 = classob.validation(edu_traindatas, edulabel, kind=edu) print avg is:, (res1+res2+res3)/3.0 else: print error! exit()

# coding=utf-8from sklearn.cross_validation import KFold, StratifiedKFoldfrom gensim.models import word2vec#import xgboost as xgbimport numpy as npfrom sklearn.linear_model import SGDClassifier, LogisticRegressionfrom sklearn.svm import LinearSVC,SVCfrom sklearn.ensemble import VotingClassifierfrom sklearn.naive_bayes import MultinomialNB, BernoulliNBfrom sklearn.preprocessing import MinMaxScaler,StandardScalerclass w2v(): def __init__(self,size=300): random_rate = 8240 self.size=size self.svc= SVC(C=1, random_state=random_rate) self.LR = LogisticRegression(C=1.0, max_iter=100, class_weight=balanced, random_state=random_rate, n_jobs=-1) self.clf = LinearSVC(random_state=random_rate) def fit(self, X, Y, T): """ train and predict """ print fitting.. self.LR.fit(X, Y) res = self.LR.predict(T) return res def validation(self,X,Y,kind): """ 使用2-fold進行驗證 """ print validating... fold_n=2 folds = list(StratifiedKFold(Y, n_folds=fold_n, random_state=0)) score=np.zeros(fold_n) for j, (train_idx, test_idx) in enumerate(folds): print j + 1, -fold X_train = X[train_idx] y_train = Y[train_idx] X_test = X[test_idx] y_test = Y[test_idx] res = self.fit(X_train, y_train, X_test) cur = sum(y_test == res) * 1.0 / len(res) score[j] = cur print score, score.mean() return score.mean() def train_w2v(self, filename): """ 訓練wv模型 :param filename:path :return:none """ sentences = word2vec.LineSentence(filename) # 載入語料,要求語料為「一行一文本」的格式 print 正在訓練w2v 針對語料:,str(filename) print size is: ,self.size model = word2vec.Word2Vec(sentences, size=self.size, window=100,workers=48) # 訓練模型; 注意參數window 對結果有影響 一般5-100 savepath = 20w_size_win100_ + str(self.size)+.model # 保存model的路徑 print 訓練完畢,已保存: , savepath, model.save(savepath) def load_trainsform(self,X): """ 載入模型,並且生成wv向量 :param X:讀入的文檔,list :return:np.array """ print 載入模型中 model = word2vec.Word2Vec.load(20w_size_win100_300.model) #填寫你的路徑 print 載入成功 res=np.zeros((len(X),self.size)) print 生成w2v向量中.. for i,line in enumerate(X): line=line terms=line.split() count=0 for j,term in enumerate(terms): try:#---try失敗說明X中有單詞不在model中,訓練的時候model的模型是min_count的 忽略了一部分單詞 count += 1 res[i]+=np.array(model[term]) except: 1 == 1 if count!=0: res[i]=res[i]/float(count) # 求均值 return res

#coding=utf-8import numpy as npclass preprocess(): # 主要功能:去除缺失值 def removezero(self, x, y): nozero = np.nonzero(y) y = y[nozero] x = np.array(x) x = x[nozero] return x, y

class里有三個大函數,一個是訓練cut之後train和test的合併的所以詞,進行訓練,構建語料庫和模型。第二個函數是在模型的基礎上分別導入train和test兩個文本,構建矩陣保存,第三個函數是2fload驗證。

-----------------------------------------------------------------------------------

好了,今天接著補充。我以前搞的文本都是比較入門級(就是利用分詞倆構造字典,格式為每個詞在兩個類或者多類的分別存儲個數,然後利用貝葉斯公式加入拉普拉斯平滑來處理),這個略有區別,利用了神經網路的word2vec在訓練詞向量。

先用word2vec下的linesentence函數將分詞後的格式化,轉換為word2vec可以用的,注意文本一定要為utf8格式,然後用word2vec函數訓練,這個函數的參數,可以百度,min——count是忽略低詞頻,默認為5,size為神經網路隱藏層數,這裡為300,也就是矩陣的列數,然後保存模型。

現在根據詞庫矩陣已經搭建完成,每一個詞都對應著300列的一個矩陣模型。

接下來就計算每一行文本所有詞於璇距離加和。先用np.zeros搭建一個行為文本行數,列為模型列數的0矩陣,然後對每一行計算,沒進來一個詞就把它對應的矩陣值加進來,就形成了每一行文本對應的w2v向量,然後保存為npy格式,方便np直接load.

通俗一點說,那麼現在每一行都一個矩陣了,那麼是不是就可以訓練了。是也不是,

我們先跑一下吧。

首先要解釋一下cross_validation,交叉驗證。此為百度百科介紹三萬字,略去(2333)

優點很多,最直接的就是訓練更準確,防止過擬合。這裡在logistics模型前面實行了2折交叉驗證,說白了就是將源數據集的訓練集分割為2份(是分層抽樣,返回的是索引),把一份作為train,一份作為test,並且這樣搞兩次,訓練train,預測test,然後來看正確率,(參考入門級的28分層,然後看混淆矩陣的準確率和召回率)

從圖中可以看到,用LR(邏輯回歸)預測性別在68%,年齡41%,教育層次在51%,三者平均值在50%,這樣的模型肯定不行,如果用其他模型比如決策樹,隨機森林,svm可能會好一點,估計在%65左右,當然我還沒試過。臆測,臆測,大家可以試一下。

---------------------------------------------------------------------------------------------

向量我們搞好了,也就是說已經把文本數據轉換數學數字了,也用lr預測了,只是準確率不理想。

然後我在git上找到一個東西。

是的,strack,比賽經典套路,模型融合。(主要就是利用多種模型,比如lr,linersvc等十幾種分類模型,在預測,預測值構造一個矩陣,對應格式為文本第一行,模型1預測值,模型二預測值,模型三預測值............一直延續下去,然後把預測矩陣和w2v向量矩陣合併,這個矩陣是非常大,然後在交叉驗證下,這次要分為5折,也就是我們常用的28分層,然後用svm下的svc默認的線性核升級到高維空間訓練分類,然後預測),可以看到性別的準確率上升到83%且聽下回分解-2017-12-14


推薦閱讀:

Python 編碼習慣(Coding Conventions)
數據科學入門篇3:數據處理利器Pandas使用手冊
黃哥Python 轉載的霸氣文章"Yes, Python is Slow, and I Don』t Care"
黃哥漫談Python 閉包。
xpath 使用教程

TAG:Python | 數據分析 | 數據挖掘 |