Użyj scikit-learn, aby podzielić na wiele kategorii

80

Próbuję użyć jednej z metod nadzorowanego uczenia scikit-learn, aby sklasyfikować fragmenty tekstu w jedną lub więcej kategorii. Funkcja przewidywania wszystkich algorytmów, które wypróbowałem, zwraca tylko jedno dopasowanie.

Na przykład mam fragment tekstu:

"Theaters in New York compared to those in London"

Wyszkoliłem algorytm, aby wybierał miejsce dla każdego fragmentu tekstu, który go karmię.

W powyższym przykładzie chciałbym, aby powrócił New Yorki London, ale tylko zwraca New York.

Czy można użyć scikit-learn do zwrócenia wielu wyników? A może nawet zwrócić etykietę z kolejnym największym prawdopodobieństwem?

Dzięki za pomoc.

---Aktualizacja

Próbowałem użyć, OneVsRestClassifierale nadal otrzymuję tylko jedną opcję z powrotem na fragment tekstu. Poniżej znajduje się przykładowy kod, którego używam

y_train = ('New York','London')


train_set = ("new york nyc big apple", "london uk great britain")
vocab = {'new york' :0,'nyc':1,'big apple':2,'london' : 3, 'uk': 4, 'great britain' : 5}
count = CountVectorizer(analyzer=WordNGramAnalyzer(min_n=1, max_n=2),vocabulary=vocab)
test_set = ('nice day in nyc','london town','hello welcome to the big apple. enjoy it here and london too')

X_vectorized = count.transform(train_set).todense()
smatrix2  = count.transform(test_set).todense()


base_clf = MultinomialNB(alpha=1)

clf = OneVsRestClassifier(base_clf).fit(X_vectorized, y_train)
Y_pred = clf.predict(smatrix2)
print Y_pred

Wynik: [„Nowy Jork” „Londyn” „Londyn”]

CodeMonkeyB
źródło

Odpowiedzi:

111

To, czego chcesz, nazywa się klasyfikacją z wieloma etykietami. Scikits-learn może to zrobić. Zobacz tutaj: http://scikit-learn.org/dev/modules/multiclass.html .

Nie jestem pewien, co się dzieje w twoim przykładzie, moja wersja sklearn najwyraźniej nie ma WordNGramAnalyzer. Być może jest to kwestia wykorzystania większej liczby przykładów szkoleniowych lub wypróbowania innego klasyfikatora? Należy jednak pamiętać, że klasyfikator z wieloma etykietami oczekuje, że celem będzie lista krotek / list etykiet.

U mnie działa:

import numpy as np
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.svm import LinearSVC
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.multiclass import OneVsRestClassifier

X_train = np.array(["new york is a hell of a town",
                    "new york was originally dutch",
                    "the big apple is great",
                    "new york is also called the big apple",
                    "nyc is nice",
                    "people abbreviate new york city as nyc",
                    "the capital of great britain is london",
                    "london is in the uk",
                    "london is in england",
                    "london is in great britain",
                    "it rains a lot in london",
                    "london hosts the british museum",
                    "new york is great and so is london",
                    "i like london better than new york"])
y_train = [[0],[0],[0],[0],[0],[0],[1],[1],[1],[1],[1],[1],[0,1],[0,1]]
X_test = np.array(['nice day in nyc',
                   'welcome to london',
                   'hello welcome to new york. enjoy it here and london too'])   
target_names = ['New York', 'London']

classifier = Pipeline([
    ('vectorizer', CountVectorizer(min_n=1,max_n=2)),
    ('tfidf', TfidfTransformer()),
    ('clf', OneVsRestClassifier(LinearSVC()))])
classifier.fit(X_train, y_train)
predicted = classifier.predict(X_test)
for item, labels in zip(X_test, predicted):
    print '%s => %s' % (item, ', '.join(target_names[x] for x in labels))

Dla mnie to daje wynik:

nice day in nyc => New York
welcome to london => London
hello welcome to new york. enjoy it here and london too => New York, London

Mam nadzieję że to pomoże.

mwv
źródło
1
Próbowałem usunąć dwa ostatnie przykłady szkoleń, które łączą nazwy miast i otrzymałem: witaj, witaj w nowym jorku. ciesz się tym tutaj i londyn też => Nowy Jork Nie zwraca już dwóch etykiet. Dla mnie to tylko zwracanie dwóch etykiet, jeśli trenuję kombinacje dwóch miast. Czy coś mi brakuje?
Jeszcze
1
To tylko zbiór danych zabawki, nie wyciągnąłbym z tego zbyt wielu wniosków. Czy wypróbowałeś tę procedurę na swoich prawdziwych danych?
mwv
3
@CodeMonkeyB: naprawdę powinieneś zaakceptować tę odpowiedź, jest poprawna z programistycznego punktu widzenia. To, czy zadziała w praktyce, zależy od danych, a nie od kodu.
Fred Foo
2
Czy ktoś inny ma problem z min_ni max_n. Muszę je zmienić ngram_range=(1,2)na działanie
emmagras
1
Wyświetla ten błąd: ValueError: Wydaje się, że używasz starszej reprezentacji danych z wieloma etykietami. Sekwencja sekwencji nie jest już obsługiwana; zamiast tego użyj tablicy binarnej lub rzadkiej macierzy.
MANU
61

EDYCJA: Zaktualizowano dla Pythona 3, scikit-learn 0.18.1 przy użyciu MultiLabelBinarizer zgodnie z sugestią.

Pracowałem nad tym również i nieznacznie ulepszyłem doskonałą odpowiedź mwv, która może być przydatna. Pobiera etykiety tekstowe jako dane wejściowe, a nie etykiety binarne i koduje je za pomocą MultiLabelBinarizer.

import numpy as np
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.svm import LinearSVC
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.multiclass import OneVsRestClassifier
from sklearn.preprocessing import MultiLabelBinarizer

X_train = np.array(["new york is a hell of a town",
                    "new york was originally dutch",
                    "the big apple is great",
                    "new york is also called the big apple",
                    "nyc is nice",
                    "people abbreviate new york city as nyc",
                    "the capital of great britain is london",
                    "london is in the uk",
                    "london is in england",
                    "london is in great britain",
                    "it rains a lot in london",
                    "london hosts the british museum",
                    "new york is great and so is london",
                    "i like london better than new york"])
y_train_text = [["new york"],["new york"],["new york"],["new york"],["new york"],
                ["new york"],["london"],["london"],["london"],["london"],
                ["london"],["london"],["new york","london"],["new york","london"]]

X_test = np.array(['nice day in nyc',
                   'welcome to london',
                   'london is rainy',
                   'it is raining in britian',
                   'it is raining in britian and the big apple',
                   'it is raining in britian and nyc',
                   'hello welcome to new york. enjoy it here and london too'])
target_names = ['New York', 'London']

mlb = MultiLabelBinarizer()
Y = mlb.fit_transform(y_train_text)

classifier = Pipeline([
    ('vectorizer', CountVectorizer()),
    ('tfidf', TfidfTransformer()),
    ('clf', OneVsRestClassifier(LinearSVC()))])

classifier.fit(X_train, Y)
predicted = classifier.predict(X_test)
all_labels = mlb.inverse_transform(predicted)

for item, labels in zip(X_test, all_labels):
    print('{0} => {1}'.format(item, ', '.join(labels)))

To daje mi następujący wynik:

nice day in nyc => new york
welcome to london => london
london is rainy => london
it is raining in britian => london
it is raining in britian and the big apple => new york
it is raining in britian and nyc => london, new york
hello welcome to new york. enjoy it here and london too => london, new york
J Maurer
źródło
13
labelBinarizerjest nieaktualny. Użyj lb = preprocessing.MultiLabelBinarizer()zamiast tego
Roman
1
Nie daje Wielkiej Brytanii, ponieważ jedynymi etykietami wyjściowymi są New Yorki London.
umop aplsdn
2
Według scikit-learn One-Vs-All jest obsługiwany przez wszystkie modele liniowe z wyjątkiem sklearn.svm.SVC, a także multilabel jest obsługiwany przez: Drzewa decyzyjne, Losowe lasy, Najbliższe sąsiedzi, więc nie użyłbym LinearSVC () dla tego typu zadania (aka klasyfikacja multilabel, którą zakładam, że chcesz użyć)
PeterB
2
Fyi One-Vs-All, o których wspomina @mindstorm, odpowiada klasie scikit-learn „OneVsRestClassifier” (zauważ „Odpocznij” zamiast „wszystko”). Ta strona pomocy scikit-learn wyjaśnia to.
lucid_dreamer
1
Jak wspomina @mindstorm, prawdą jest, że na tej stronie dokumentacja wspomina: „One-Vs-All: wszystkie modele liniowe z wyjątkiem sklearn.svm.SVC”. Jednak inny przykład multilabel z dokumentacji scikit-learn pokazuje przykład multilabel z tą linią classif = OneVsRestClassifier(SVC(kernel='linear')). Zdziwiony.
lucid_dreamer
8

Właśnie w to też wpadłem i problem polegał na tym, że mój y_Train był sekwencją ciągów znaków, a nie sekwencją ciągów ciągów. Najwyraźniej OneVsRestClassifier zdecyduje na podstawie formatu etykiety wejściowej, czy użyć wieloklasowego czy wieloznakowego. Więc zmień:

y_train = ('New York','London')

do

y_train = (['New York'],['London'])

Najwyraźniej zniknie to w przyszłości, ponieważ zerwania wszystkich etykiet są takie same: https://github.com/scikit-learn/scikit-learn/pull/1987

user2824135
źródło
8

Zmień tę linię, aby działała w nowych wersjach Pythona

# lb = preprocessing.LabelBinarizer()
lb = preprocessing.MultiLabelBinarizer()
Srini Sydney
źródło
2

Kilka przykładów klasyfikacji wielokrotnych jest takich jak: -

Przykład 1:-

import numpy as np
from sklearn.preprocessing import LabelBinarizer
encoder = LabelBinarizer()

arr2d = np.array([1, 2, 3,4,5,6,7,8,9,10,11,12,13,14,1])
transfomed_label = encoder.fit_transform(arr2d)
print(transfomed_label)

Wyjście jest

[[1 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 1 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 1 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 1 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 1 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 1 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 1 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 1 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 1 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 1 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 1 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 1 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 1 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 1]
 [1 0 0 0 0 0 0 0 0 0 0 0 0 0]]

Przykład 2: -

import numpy as np
from sklearn.preprocessing import LabelBinarizer
encoder = LabelBinarizer()

arr2d = np.array(['Leopard','Lion','Tiger', 'Lion'])
transfomed_label = encoder.fit_transform(arr2d)
print(transfomed_label)

Wyjście jest

[[1 0 0]
 [0 1 0]
 [0 0 1]
 [0 1 0]]
Goyal Vicky
źródło