/
main_firstTry_cluster.py
380 lines (295 loc) · 14.8 KB
/
main_firstTry_cluster.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# coding: utf-8
#import packages
from pyspark import SparkContext
import loadFiles as lf
import numpy as np
import nltk
from random import randint
from pyspark.mllib.classification import NaiveBayes
from functools import partial
from pyspark.mllib.linalg import SparseVector
from pyspark.mllib.regression import LabeledPoint
from pyspark.ml.pipeline import Transformer
from pyspark.ml.param.shared import HasInputCol, HasOutputCol, Param
from pyspark.ml.util import keyword_only
from pyspark.sql.functions import udf
from pyspark.sql.types import ArrayType, StringType
from pyspark.sql import SQLContext
#create Sparkcontext
sc = SparkContext(appName="Simple App")
#Load the database for training
import loadFilesPartial as lfp
data,Y=lf.loadLabeled("./data/train")
print len(data)
#**********************************************************************
#----------------------Pre-processing data-----------------------------
#**********************************************************************
print "Pre-processing data"
# Le item est nécessaire pour convertir au format Double (python natif)
# car les dataframes semblent avoir des problèmes avec les types numpy
labeledData = zip(data,[y.item() for y in Y])
labeledRdd = sc.parallelize(labeledData)
#from BeautifulSoup import BeautifulSoup as bfs
# Définition d'une fonction de nettoyage : on remplace les balises par des espaces
# à l'aide du parser de hmtl, et on met tout en minuscules.
#def cleanLower(doc):
# clean = bfs(doc).get_text(separator=' ')
# return clean.lower()
#Unfortunately Bs4 is not available on the X-cluster: removing html tags
#is going to be more empirical!!
# Définition d'une fonction de nettoyage : on remplace les balises par des espaces
# à l'aide du parser de hmtl, et on met tout en minuscules. (sans utiliser BeautifulSoup)
def cleanLower(doc):
return doc.replace("<br />","").lower()
# On applique le nettoyage à la RDD
cleanRdd = labeledRdd.map(lambda doc : (cleanLower(doc[0]),doc[1]))
# On va utiliser la librairie spark ML donc on travaille avec des dataframes
# EN THEORIE, c'est plus facile. C'est un mensonge.
sqlContext = SQLContext(sc) #for the X-cluster
df = sqlContext.createDataFrame(cleanRdd, ['review', 'label'])
dfTrain, dfTest = df.randomSplit([0.8,0.2])
print "Training set (for the cross-validation): "
dfTrain.show()
print "Test set (for the cross-validation): "
dfTest.show()
#Tokenizing
from pyspark.ml.feature import Tokenizer
tokenizer = Tokenizer(inputCol='review', outputCol='words')
dfTrainTok = tokenizer.transform(dfTrain)
# La ponctuation n'a pas l'air gérée très correctement...
print "La ponctuation n'a pas l'air gérée très correctement..."
dfTrainTok.select('words').take(1)
# J'ai écrit cette fonction au cas où on souhaiterait absolument passer par des
# pipelines, et intégrer les stopword dans la structure d'un transformer devait
# passer par ce code grosso modo. Au final, on se passera surement des pipelines
# car leurs possibilités sont limitées sur spark ml. Très limitées. C'est de la
# merde en fait. Voila.
#----------------------------------------------------------------------
#----------------------Removing Stop words-----------------------------
#----------------------------------------------------------------------
print "Removing Stop words"
import nltk
from pyspark.ml.pipeline import Transformer
from pyspark.ml.param.shared import HasInputCol, HasOutputCol, Param
from pyspark.ml.util import keyword_only
from pyspark.sql.functions import udf
from pyspark.sql.types import ArrayType, StringType
class NLTKWordPunctTokenizer(Transformer, HasInputCol, HasOutputCol):
@keyword_only
def __init__(self, inputCol=None, outputCol=None, stopwords=None):
super(NLTKWordPunctTokenizer, self).__init__()
self.stopwords = Param(self, "stopwords", "")
self._setDefault(stopwords=set())
kwargs = self.__init__._input_kwargs
self.setParams(**kwargs)
@keyword_only
def setParams(self, inputCol=None, outputCol=None, stopwords=None):
kwargs = self.setParams._input_kwargs
return self._set(**kwargs)
def setStopwords(self, value):
self._paramMap[self.stopwords] = value
return self
def getStopwords(self):
return self.getOrDefault(self.stopwords)
def _transform(self, dataset):
stopwords = self.getStopwords()
def f(s):
tokens = nltk.tokenize.wordpunct_tokenize(s)
return [t for t in tokens if t.lower() not in stopwords]
t = ArrayType(StringType())
out_col = self.getOutputCol()
in_col = dataset[self.getInputCol()]
return dataset.withColumn(out_col, udf(f, t)(in_col))
#remove Stop-words
# Par contre cette fonction marche bien
tokenizerNoSw = NLTKWordPunctTokenizer(inputCol="review", outputCol="wordsNoSw",stopwords=set(nltk.corpus.stopwords.words('english')))
dfTrainTokNoSw = tokenizerNoSw.transform(dfTrainTok)
print "Training set without stop words: "
dfTrainTokNoSw.show()
# La ponctuation est bien gérée
print "La ponctuation est bien gérée :"
dfTrainTokNoSw.select('wordsNoSw').take(1)
#----------------------------------------------------------------------
#----------------------------------Tagging-----------------------------
#----------------------------------------------------------------------
from pyspark.sql.types import StructType, StructField
class NLTKPosTagger(Transformer, HasInputCol, HasOutputCol):
@keyword_only
def __init__(self, inputCol=None, outputCol=None, tagset=None):
super(NLTKPosTagger, self).__init__()
self.tagset = Param(self, "tagset", "")
self._setDefault(tagset='universal')
kwargs = self.__init__._input_kwargs
self.setParams(**kwargs)
@keyword_only
def setParams(self, inputCol=None, outputCol=None, tagset=None):
kwargs = self.setParams._input_kwargs
return self._set(**kwargs)
def setTagset(self, value):
self._paramMap[self.tagset] = value
return self
def getTagset(self):
return self.getOrDefault(self.tagset)
def _transform(self, dataset):
tagset = self.getTagset()
def f(s):
return nltk.pos_tag(s,tagset=tagset)
fields = (StructField('word',StringType(),True),StructField('tag',StringType(),True))
t = ArrayType(StructType(fields))
out_col = self.getOutputCol()
in_col = dataset[self.getInputCol()]
return dataset.withColumn(out_col, udf(f, t)(in_col))
posTagger = NLTKPosTagger(inputCol="words", outputCol="tagWords")
dfTagged = posTagger.transform(dfTrainTok)
#dfTagged.show()
#----------------------------------------------------------------------
#------------------------------Bigrams---------------------------------
#----------------------------------------------------------------------
from pyspark.ml.feature import NGram
bigram = NGram(inputCol="words", outputCol="bigrams")
dfBigram = bigram.transform(dfTrainTokNoSw)
print "DataFrame des Bigram: "
dfBigram.show()
#**********************************************************************
#------------------------Feature selection-----------------------------
#**********************************************************************
# Pour la suite on a le choix entre l'encodage utilisé par le prof (le mot y est ou n'y est pas)
# ou la version en apparence plus informative du tfidf. En vrai, le tfidf peut être trompeur
# donc je construis quand même les dictionnaires d'unigrammes et de bigrammes pour pouvoir
# calculer les sparse vectors du prof.
import itertools
lists=dfBigram.map(lambda r : r.words).collect()
dictUnigrams=set(itertools.chain(*lists))
lists2=dfBigram.map(lambda r : r.bigrams).collect()
dictBigrams=set(itertools.chain(*lists2))
dictionaryUni={}
for i,word in enumerate(dictUnigrams):
dictionaryUni[word]=i
dictionaryBigrams={}
for i,word in enumerate(dictBigrams):
dictionaryBigrams[word]=i
'!' in dictionaryUni
# Voila qui est mieux...
lists3=dfBigram.map(lambda r : r.wordsNoSw).collect()
dict3=set(itertools.chain(*lists3))
dictionary3 = {}
for i,word in enumerate(dict3):
dictionary3[word]=i
'!' in dictionary3
from string import punctuation
import re
r = re.compile(r'[\s{}]+'.format(re.escape(punctuation)))
#for k in dict3:
# m = r.search(k)
# if m:
# print m.group()
# Fonction calculant le sparse vector correspondant à un ensemble de tokens
from pyspark.mllib.linalg import SparseVector
def vectorizeUni(tokens):
vector_dict={}
for w in tokens:
vector_dict[dictionaryUni[w]]=1
return SparseVector(len(dictionaryUni),vector_dict)
def vectorizeBi(tokens):
vector_dict={}
for w in tokens:
vector_dict[dictionaryBigrams[w]]=1
return SparseVector(len(dictionaryBigrams),vector_dict)
# In[52]:
# La ca devient le bordel, j'en ai chié pour arriver à appliquer une fonction à toute une colonne
# d'une dataframe. Contrairement à pandas, y a pas de fonction "apply", il faut recourir à des
# UserDefinedFunctions, et penser que le type sparseVector ne sera pas reconnu par la dataframe, qui
# n'est compatible qu'avec un nombre restreint de types
# EDIT : en fait je m'en suis pas rendu compte, mais cette manip je l'avais déjà faite pour la surcharge des
# tokenizer et postagger... les cinq dernières lignes à la fin avec udf et tout
from pyspark.sql.functions import UserDefinedFunction
from pyspark.mllib.linalg import VectorUDT
udfVectorizeUni=UserDefinedFunction(lambda x : vectorizeUni(x),VectorUDT())
# Une dataframe est un objet immutable, donc pas la peine d'essayer de modifier une colonne,
# à la place on crée une deuxième dataframe où on ajoute la colonne qu'on veut.
dfVect = dfBigram.withColumn("words", udfVectorizeUni("words"))
# On a bien remplacé ici du coup les mots par les vecteurs sparse
print "DataFrame(1-gram): On a bien remplacé ici du coup les mots par les vecteurs sparse"
dfVect.show()
udfVectorizeBi=UserDefinedFunction(lambda x : vectorizeBi(x),VectorUDT())
dfVect2 = dfVect.withColumn("bigrams", udfVectorizeBi("bigrams"))
print "DataFrame(bi-gram): On a bien remplacé ici du coup les mots par les vecteurs sparse"
dfVect2.show()
# Pour les opérations de traitement du langage, il est d'usage de normaliser (L2)
# les vecteurs de features : c'est ce qui marche le mieux apparemment.
from pyspark.ml.feature import Normalizer
normalizerUni = Normalizer(inputCol='words',outputCol='normWords',p=2.0)
normalizerBi = Normalizer(inputCol="bigrams",outputCol='normBigrams',p=2.0)
dfNorm = normalizerUni.transform(dfVect2)
dfNorm2 = normalizerBi.transform(dfNorm)
print "DataFrame(bi-gram): normalisé"
dfNorm2.select('words','normWords').show()
# La différence n'apparait pas dans la table puisqu'on n'a la place de visualiser que les indices des élements
# non nuls et pas leur valeur
# On passe au TFIDF
# Evidemment en choisissant la bonne dataframe parmi celle du dessus, on peut appliquer ces calculs
# à n'importz quelle colonne (bigrammes, avec stop words ou sans...)
from pyspark.ml.feature import HashingTF
htf = HashingTF(inputCol='words',outputCol='wordsTF',numFeatures=10000)
dfTrainTF = htf.transform(dfTrainTokNoSw)
# INverse doc frequency
from pyspark.ml.feature import IDF
idf = IDF(inputCol=htf.getOutputCol(),outputCol="wordsTFIDF")
idfModel = idf.fit(dfTrainTF)
dfTrainTFIDF = idfModel.transform(dfTrainTF)
dfTrainTFIDF.select('review','wordsTF','wordsTFIDF').show()
# Je sais que cette étape m'a été utile une fois, la ça a pas trop l'air
from pyspark.ml.feature import StringIndexer
string_indexer = StringIndexer(inputCol='label', outputCol='target_indexed')
string_indexer_model = string_indexer.fit(dfTrainTFIDF)
dfTrainFinal = string_indexer_model.transform(dfTrainTFIDF)
dfTrainFinal.select('review','label','target_indexed').show()
#**********************************************************************
#-----------Training the model for prediction--------------------------
#**********************************************************************
from pyspark.ml.classification import DecisionTreeClassifier
dt = DecisionTreeClassifier(featuresCol=idf.getOutputCol(),labelCol=string_indexer.getOutputCol())
dt_model = dt.fit(dfTrainFinal)
# On applique le même à notre ensemble de test ridicule.
# En théorie le pipeline permet d'automatiser tout ça mais bon, on s'en servira probablement pas
# EDIT : en fait c'est plutot facile de créer des transformers à partir de chaque étape, donc peut
# être que les pipelines c'est faisables. A voir
df_test_words = tokenizer.transform(dfTest)
df_test_tf = htf.transform(df_test_words)
df_test_tfidf = idfModel.transform(df_test_tf)
df_test_final = string_indexer_model.transform(df_test_tfidf)
# Les prédictions
df_test_pred = dt_model.transform(df_test_final)
df_test_pred.select('review', 'target_indexed', 'prediction', 'probability').show(5)
# Je fais un pipeline très basique
from pyspark.ml import Pipeline
# Instanciate all the Estimators and Transformers necessary
tokenizer = Tokenizer(inputCol='review', outputCol='reviews_words')
hashing_tf = HashingTF(inputCol=tokenizer.getOutputCol(), outputCol='reviews_tf', numFeatures=10000)
idf = IDF(inputCol=hashing_tf.getOutputCol(), outputCol="reviews_tfidf")
string_indexer = StringIndexer(inputCol='label', outputCol='target_indexed')
dt = DecisionTreeClassifier(featuresCol=idf.getOutputCol(), labelCol=string_indexer.getOutputCol(), maxDepth=10)
# Instanciate a Pipeline
pipeline = Pipeline(stages=[tokenizer,hashing_tf,idf,string_indexer,dt])
pipeline_model = pipeline.fit(dfTrain)
df_test_pred = pipeline_model.transform(dfTest)
df_test_pred.select('review', 'target_indexed', 'prediction', 'probability').show()
# Un outil automatique pour calculer le taux de bonne classif.
# La encore pas très utile en vrai
from pyspark.ml.evaluation import MulticlassClassificationEvaluator
evaluator = MulticlassClassificationEvaluator(predictionCol='prediction', labelCol='target_indexed', metricName='precision')
evaluator.evaluate(df_test_pred)
#**********************************************************************
#-----------Cross Validation--------------------------
#**********************************************************************
# La cross validation et le test des différents paramètres du classifieurs c'est pas trop dur sur spark ML,
# c'est en fait la seule raison pour laquelle cette librairie me paraissait mieux... avec du recul j'aurais
# perdu moins de temps à recoder moi-même la cross valid et la grid search...
from pyspark.ml.tuning import ParamGridBuilder
from pyspark.ml.tuning import CrossValidator
grid=(ParamGridBuilder().baseOn([evaluator.metricName,'precision']).addGrid(dt.maxDepth, [10,20]).build())
cv = CrossValidator(estimator=pipeline, estimatorParamMaps=grid,evaluator=evaluator)
cv_model = cv.fit(dfTrain)
df_test_pred = cv_model.transform(dfTest)
resultat=evaluator.evaluate(df_test_pred)
print "Pourcentage de bonne classification(0-1): ",resultat