scikit-learn: FeatureUnion для включения функций, созданных вручную - PullRequest
1 голос
/ 10 января 2020

Я выполняю классификацию по нескольким меткам для текстовых данных. Я sh смогу использовать комбинированные функции tfidf и пользовательские лингвистические c функции, подобные примеру здесь , используя FeatureUnion .

Я уже создал пользовательские функции языка c, которые представлены в виде словаря, в котором ключи представляют метки, а (список) значений представляют функции.

custom_features_dict = {'contact':['contact details', 'e-mail'], 
                       'demographic':['gender', 'age', 'birth'],
                       'location':['location', 'geo']}

Структура обучающих данных выглядит следующим образом:

text                                            contact  demographic  location
---                                              ---      ---          ---
'provide us with your date of birth and e-mail'  1        1            0
'contact details and location will be stored'    1        0            1
'date of birth should be before 2004'            0        1            0

Как можно включить вышеуказанное dict в FeatureUnion? Насколько я понимаю, должна вызываться пользовательская функция, которая возвращает логические значения, соответствующие наличию или отсутствию строковых значений (из custom_features_dict) в данных обучения.

Это дает следующие list из dict для данных данных обучения:

[
    {
       'contact':1,
       'demographic':1,
       'location':0
    },
    {
       'contact':1,
       'demographic':0,
       'location':1
    },
    {
       'contact':0,
       'demographic':1,
       'location':0
    },
] 

Как вышеуказанное list может использоваться для реализации подгонки и преобразования?

Код указан ниже:

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_extraction import DictVectorizer
#from sklearn.metrics import accuracy_score
from sklearn.multiclass import OneVsRestClassifier
from nltk.corpus import stopwords
stop_words = set(stopwords.words('english'))
from sklearn.svm import LinearSVC
from sklearn.pipeline import Pipeline
from io import StringIO

data = StringIO(u'''text,contact,demographic,location
provide us with your date of birth and e-mail,1,1,0
contact details and location will be stored,0,1,1
date of birth should be before 2004,0,1,0''')

df = pd.read_csv(data)

custom_features_dict = {'contact':['contact details', 'e-mail'], 
                        'demographic':['gender', 'age', 'birth'],
                        'location':['location', 'geo']}

my_features = [
    {
       'contact':1,
       'demographic':1,
       'location':0
    },
    {
       'contact':1,
       'demographic':0,
       'location':1
    },
    {
       'contact':0,
       'demographic':1,
       'location':0
    },
]

bow_pipeline = Pipeline(
    steps=[
        ("tfidf", TfidfVectorizer(stop_words=stop_words)),
    ]
)

manual_pipeline = Pipeline(
    steps=[
        # This needs to be fixed
        ("custom_features", my_features),
        ("dict_vect", DictVectorizer()),
    ]
)

combined_features = FeatureUnion(
    transformer_list=[
        ("bow", bow_pipeline),
        ("manual", manual_pipeline),
    ]
)

final_pipeline = Pipeline([
            ('combined_features', combined_features),
            ('clf', OneVsRestClassifier(LinearSVC(), n_jobs=1)),
        ]
)

labels = ['contact', 'demographic', 'location']

for label in labels:
    final_pipeline.fit(df['text'], df[label]) 

Ответы [ 2 ]

1 голос
/ 13 января 2020

Вы должны определить Transformer, который принимает ваш текст в качестве ввода. Примерно так:

from sklearn.base import BaseEstimator, TransformerMixin

custom_features_dict = {'contact':['contact details', 'e-mail'], 
                   'demographic':['gender', 'age', 'birth'],
                   'location':['location', 'geo']}

#helper function which returns 1, if one of the words occures in the text, else 0
#you can add more words or categories to custom_features_dict if you want
def is_words_present(text, listofwords):
  for word in listofwords:
    if word in text:
      return 1
  return 0

class CustomFeatureTransformer(BaseEstimator, TransformerMixin):
    def __init__(self, custom_feature_dict):
       self.custom_feature_dict = custom_feature_dict
    def fit(self, x, y=None):
        return self    
    def transform(self, data):
        result_arr = []
        for text in data:
          arr = []
          for key in self.custom_feature_dict:
            arr.append(is_words_present(text, self.custom_feature_dict[key]))
          result_arr.append(arr)
        return result_arr

Примечание. Этот Transformer генерирует массив, который выглядит следующим образом: [1, 0, 1], он не генерирует словарь, что позволяет нам сэкономить DictVectorizer.

Кроме того, я изменил способ обработки Multilabel-классификации, см. здесь :

#first, i generate a new column in the dataframe, with all the labels per row:
def create_textlabels_array(row):
  arr = []
  for label in ['contact', 'demographic', 'location']:
    if row[label]==1:
      arr.append(label)
  return arr

df['textlabels'] = df.apply(create_textlabels_array, 1) 

#then we generate the binarized Labels:
from sklearn.preprocessing import MultiLabelBinarizer
mlb = MultiLabelBinarizer().fit(df['textlabels'])
y = mlb.transform(df['textlabels'])

Теперь мы можем добавить все вместе в конвейер:

bow_pipeline = Pipeline(
    steps=[
        ("tfidf", TfidfVectorizer(stop_words=stop_words)),
    ]
)

manual_pipeline = Pipeline(
    steps=[
        ("costum_vect", CustomFeatureTransformer(custom_features_dict)),
    ]
)

combined_features = FeatureUnion(
    transformer_list=[
        ("bow", bow_pipeline),
        ("manual", manual_pipeline),
    ]
)

final_pipeline = Pipeline([
        ('combined_features', combined_features),
        ('clf', OneVsRestClassifier(LinearSVC(), n_jobs=1)),
    ]
)

#train your pipeline
final_pipeline.fit(df['text'], y) 

#let's predict something: (Note: of course training data is a bit low in that examplecase here)
pred = final_pipeline.predict(["write an e-mail to our location please"])
print(pred) #output: [0, 1, 1] 

#reverse the predicted array to the actual labels:
print(mlb.inverse_transform(pred)) #output: [('demographic', 'location')]
0 голосов
/ 13 января 2020

Если мы просто хотим исправить эту часть кода, помеченную как исправленную, все, что нам нужно, это реализовать новый оценщик, расширяющий класс sklearn.base.BaseEstimator (класс TemplateClassifier является хорошим примером здесь ).

Однако, похоже, здесь есть концептуальная ошибка. Информация в списке my_features, похоже, представляет собой сами ярлыки (ну, можно утверждать, что они являются очень сильными функциями ...). Таким образом, мы не должны помещать метки в конвейер функций.

Как описано здесь ,

Трансформаторы обычно объединяются с классификаторами, регрессорами или другими оценщиками для построить составную оценку. Самый распространенный инструмент - это Pipeline. Конвейер часто используется в сочетании с FeatureUnion, который объединяет выходные данные трансформаторов в составное пространство признаков. TransformedTargetRegressor имеет дело с преобразованием цели (то есть log-transform y). Напротив, конвейеры преобразуют только наблюдаемые данные (X) .

Тем не менее, если вы все еще хотите поместить эту информацию списка в метод преобразования, это будет что-то вроде это:

def transform_str(one_line_text: str) -> dict:
    """ Transforms one line of text to dict features using manually extracted information"""
    # manually extracted information
    custom_features_dict = {'contact': ['contact details', 'e-mail'],
                            'demographic': ['gender', 'age', 'birth'],
                            'location': ['location', 'geo']}
    # simple tokenization. it can be improved using some text pre-processing lib
    tokenized_text = one_line_text.split(" ")
    output = dict()
    for feature,tokens in custom_features_dict.items():
        output[feature] = False
        for word in tokenized_text:
            if word in tokens:
                output[feature] = True
    return output

def transform(text_list: list) -> list:
    output = list()
    for one_line_text in text_list:
        output.append(transform_str(one_line_text))
    return output

В этом случае вам не нужен метод подгонки, потому что подгонка была сделана вручную.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...