Definiowanie korelacji

Teraz, gdy wprowadziliśmy regresję liniową, zróbmy szybką dygresję, aby bardziej szczegółowo omówić słowo „korelacja”. W najściślejszym sensie dwie zmienne są skorelowane, jeśli związek między nimi można opisać za pomocą linii prostej. Innymi słowy, korelacja jest tylko miarą tego, jak dobrze można zastosować regresję liniową do modelowania zależności między dwiema zmiennymi. Korelacja 0 wskazuje, że nie ma interesującej linii, która łączy dwie zmienne. Korelacja 1 wskazuje, że istnieje idealnie dodatnia linia prosta (w górę), która wiąże dwie zmienne. A korelacja –1 wskazuje, że istnieje idealnie ujemna linia prosta (zejście w dół), która wiąże te dwie zmienne. Aby wszystko było precyzyjne, przejrzyjmy krótki przykład. Najpierw wygenerujemy dane, które nie są ściśle liniowe, a następnie wykreślimy je:

x <- 1:10

y <- x ^ 2

ggplot (data.frame (X = x, Y = y), aes (x = X, y = Y)) +

geom_point () +

geom_smooth (method = ‘lm’, se = FALSE)

Te przykładowe dane pokazano na rysunku

Jak widać, linia narysowana za pomocą geom_smooth nie przechodzi przez wszystkie punkty, więc relacja między x i y nie może być idealnie liniowa. Ale jak blisko jest? Aby odpowiedzieć na to pytanie, obliczamy korelację w R za pomocą funkcji cor:

cor (x, y)

# [1] 0,9745586

Tutaj widzimy, że x i y można całkiem dobrze powiązać za pomocą linii prostej. Funkcja cor pozwala oszacować, jak silny jest ten związek. W tym przypadku jest to 0,97, czyli prawie 1. Jak moglibyśmy obliczyć korelację dla siebie, zamiast używać kor? Możemy to zrobić za pomocą lm, ale najpierw musimy przeskalować zarówno x, jak i y. Skalowanie polega na odjęciu średniej obu zmiennych, a następnie podzieleniu przez odchylenie standardowe. Możesz wykonać

skalowanie w R za pomocą funkcji skalowania:

coef (lm (skala (y) ~ skala (x)))

Skala # (przechwytująca) (x)

# -1.386469e-16 9.745586e-01

Jak widać, w tym przypadku korelacja między x i y jest dokładnie równa współczynnikowi odnoszącemu się do nich w regresji liniowej po skalowaniu obu z nich. Jest to ogólny fakt na temat działania korelacji, dlatego zawsze możesz użyć regresji liniowej, aby pomóc ci dokładnie wyobrazić sobie, co to znaczy, że dwie zmienne mają być skorelowane. Ponieważ korelacja jest tylko miarą liniowości zależności między dwiema zmiennymi, nie mówi nam nic o przyczynowości. Prowadzi to do maksymy, że „korelacja nie jest przyczyną”. Niemniej jednak bardzo ważne jest, aby wiedzieć, czy dwie rzeczy są skorelowane, jeśli chcesz użyć jednej z nich do przewidywania drugiej. To kończy nasze wprowadzenie do regresji liniowej i pojęcia korelacji. Później pokażemy, jak uruchomić znacznie bardziej wyrafinowane modele regresji, które mogą obsługiwać wzorce nieliniowe w danych i jednocześnie zapobiegać nadmiernemu dopasowaniu.

Prognozowanie ruchu w sieci

Teraz, gdy przygotowaliśmy Cię do pracy z regresjami, studium przypadku tej części skupi się na użyciu regresji do przewidywania liczby odsłon stron dla 1000 najlepszych stron internetowych w 2011 r. Pięć górnych wierszy tego zestawu danych, które dostarczone nam przez Neila Kodnera, pokazano w tabeli 5-3.

Do naszych celów będziemy pracować tylko z podzbiorem kolumn tego zestawu danych. Skupimy się na pięciu kolumnach: Rank, PageViews, UniqueVisitors, HasAdvertising i IsEnglish.

Kolumna Rank informuje nas o pozycji witryny na pierwszej liście 1000. Jak widać, Facebook jest witryną numer jeden w tym zestawie danych, a YouTube jest drugą. Rank jest interesującym rodzajem pomiaru, ponieważ jest liczbą porządkową, w której liczby są używane nie dla ich prawdziwych wartości, ale po prostu dla ich kolejności. Jednym ze sposobów uświadomienia sobie, że wartości nie mają znaczenia, jest uświadomienie sobie, że nie ma realnej odpowiedzi na pytania typu „Jaka jest 1.578. strona na tej liście?” Tego rodzaju pytanie miałoby odpowiedź, gdyby użyte liczby były wartościami kardynalnymi. Innym sposobem na podkreślenie tego rozróżnienia jest zwrócenie uwagi na rangi 1, 2, 3 i 4 na A, B, C i D i nie utracić żadnych informacji.

Następna kolumna, PageViews, to wynik, który chcemy przewidzieć w tym studium przypadku, i pokazuje nam, ile razy strona była widziana w tym roku. Jest to dobry sposób mierzenia popularności witryn, takich jak Facebook, z wielokrotnie odwiedzanymi użytkownikami, którzy często wracają. Po przeczytaniu całej części dobrym ćwiczeniem byłoby porównanie odsłon strony z UniqueVisitors, aby znaleźć sposób na stwierdzenie, które rodzaje witryn mają wiele powtarzających się odwiedzin, a które z nich mają bardzo niewiele powtórzeń. Kolumna UniqueVisitors mówi nam, ile różnych osób przyszło na stronę w ciągu miesiąca, w którym dokonano tych pomiarów. Jeśli uważasz, że wyświetlenia strony można łatwo zawyżać, powodując, że ludzie niepotrzebnie odświeżają strony, UniqueVisitors to dobry sposób na zmierzenie, ile różnych osób widzi witrynę. Kolumna HasAdvertising informuje nas, czy witryna zawiera reklamy. Możesz pomyśleć, że reklamy byłyby irytujące, a ludzie, wszyscy inni są równi, unikają witryn z reklamami. Możemy to wyraźnie przetestować za pomocą regresji. W rzeczywistości jedną z wielkich wartości regresji jest to, że pozwala ona próbować odpowiadać na pytania, w których musimy mówić o tym, że „wszystko inne jest równe”. Mówimy „spróbuj”, ponieważ jakość regresji jest tak dobra, jak nasze dane wejściowe. Jeśli w naszych danych wejściowych brakuje ważnej zmiennej, wyniki regresji mogą być bardzo dalekie od prawdy. Z tego powodu należy zawsze zakładać, że wyniki regresji są wstępne: „Gdyby dane wejściowe były wystarczające, aby odpowiedzieć na to pytanie, odpowiedź byłaby…”. Kolumna IsEnglish mówi nam, czy strona internetowa jest głównie w języku angielskim. Przeglądając listę, widać, że większość najlepszych witryn to przede wszystkim witryny w języku angielskim lub chińskim. Uwzględniliśmy tę kolumnę, ponieważ interesujące jest pytanie, czy znajomość języka angielskiego jest pozytywna, czy nie. Uwzględniamy również tę kolumnę, ponieważ jest to przykład, w którym kierunek przyczynowości nie jest wcale jasny z regresji; czy pisanie w języku angielskim zwiększa popularność witryny, czy też bardziej popularne witryny decydują się na konwersję na angielski, ponieważ jest to lingua franca w Internecie? Model regresji może powiedzieć, że dwie rzeczy są ze sobą powiązane, ale nie może powiedzieć, czy jedna rzecz powoduje drugą, czy też faktycznie działa odwrotnie. Teraz, gdy opisaliśmy dane wejściowe i zdecydowaliśmy, że PageViews będą naszymi wynikami, zacznijmy rozumieć, w jaki sposób te rzeczy odnoszą się do siebie. Zaczniemy od utworzenia wykresu rozrzutu, który wiąże odsłon strony z UniqueVisitors. Zawsze zalecamy rysowanie wykresów rozrzutu dla zmiennych numerycznych, zanim spróbujesz powiązać je za pomocą regresji, ponieważ wykres rozrzutu może wyjaśnić, kiedy założenie regresji nie jest spełnione.

top.1000.sites <- read.csv(‘data/top_1000_sites.tsv’,

sep = ‘\t’,

stringsAsFactors = FALSE)

ggplot(top.1000.sites, aes(x = PageViews, y = UniqueVisitors)) +

geom_point()

Wykres rozrzutu, który otrzymujemy z tego wywołania do ggplot 

wygląda okropnie:

prawie wszystkie wartości są zebrane razem na osi X i tylko niewielka liczba wyskakuje z paczki. Jest to powszechny problem podczas pracy z danymi, które zwykle nie są dystrybuowane, ponieważ użycie skali wystarczająco dużej, aby pokazać pełny zakres wartości, powoduje, że większość punktów danych jest tak blisko siebie, że nie można ich oddzielić wizualnie. Aby potwierdzić, że kształt danych stanowi problem z tym wykresem, możemy przyjrzeć się rozkładowi odsłon stron:

ggplot(top.1000.sites, aes(x = PageViews)) +

geom_density()

Ten wykres gęstości (pokazany na rysunku 5-7)

 wygląda tak samo nieprzenikniony jak wcześniejszy wykres rozrzutu. Gdy zobaczysz bezsensowne wykresy gęstości, dobrym pomysłem jest wypróbowanie dziennika wartości, które próbujesz przeanalizować, i wykonanie wykresu gęstości tych wartości przekształconych w log. Możemy to zrobić za pomocą prostego wywołania funkcji dziennika R:

ggplot (top.1000.sites, aes (x = log (PageViews))) +

geom_density ()

Ten wykres gęstości (pokazany na rysunku 5-8)

wygląda o wiele bardziej rozsądnie. Z tego powodu odtąd zaczniemy korzystać z przekształconych w dzienniki odsłon strony i UniqueVisitors. Odtworzenie naszego wcześniejszego wykresu rozrzutu na skali dziennika jest łatwe:

ggplot (top.1000.sites, aes (x = log (PageViews), y = log (UniqueVisitors))) +

geom_point ()

Pakiet ggplot2 zawiera również funkcję wygody do zmiany skali osi do dziennika. W takim przypadku możesz użyć dziennika scale_x_log lub scale_y_log. Przypomnijmy również z naszej wcześniejszej dyskusji, że w niektórych przypadkach będziesz chciał użyć funkcji logp w R, aby uniknąć błędów związanych z przyjmowaniem logarytmu zerowego. W tym przypadku jednak nie stanowi to problemu. Powstały wykres rozproszenia pokazany poniżej

wygląda na to, że istnieje potencjalna linia do narysowania za pomocą regresji. Zanim użyjemy lm do dopasowania linii regresji, użyjmy geom_smooth z argumentem method = ‘lm’, aby zobaczyć, jak będzie wyglądać linia regresji:

ggplot(top.1000.sites, aes(x = log(PageViews), y = log(UniqueVisitors))) +

geom_point() +

geom_smooth(method = ‘lm’, se = FALSE)

Powstała linia, pokazana tu

 wygląda obiecująco, więc znajdźmy wartości, które określają jego nachylenie i przechwytują, wywołując lm:

lm.fit <- lm (log (PageViews) ~ log (UniqueVisitors),

data = top.1000.sites)

Teraz, gdy już dopasowaliśmy linię, chcielibyśmy uzyskać jej krótkie podsumowanie. Możemy spojrzeć na współczynniki za pomocą cefe, lub możemy spojrzeć na RMSE za pomocą reszt. Ale wprowadzimy kolejną funkcję, która tworzy znacznie bardziej złożone podsumowanie, które możemy wykonać krok po kroku. Ta funkcja jest dogodnie nazywana summary:

summary(lm.fit)

#Call:

#lm(formula = log(PageViews) ~ log(UniqueVisitors), data = top.1000.sites)

#

#Residuals:

# Min 1Q Median 3Q Max

#-2.1825 -0.7986 -0.0741 0.6467 5.1549

#

#Coefficients:

# Estimate Std. Error t value Pr(>|t|)

#(Intercept) -2.83441 0.75201 -3.769 0.000173 ***

#log(UniqueVisitors) 1.33628 0.04568 29.251 < 2e-16 ***

#—

#Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

#

#Residual standard error: 1.084 on 998 degrees of freedom

#Multiple R-squared: 0.4616, Adjusted R-squared: 0.4611

#F-statistic: 855.6 on 1 and 998 DF, p-value: < 2.2e-16

Pierwszą rzeczą, którą mówi nam summary, jest rozmowa z lm. Nie jest to bardzo przydatne, gdy pracujesz przy konsoli, ale może być pomocne, gdy pracujesz w większych skryptach, które wykonują wiele wywołań do lm. W takim przypadku informacje te pomagają w uporządkowaniu wszystkich modeli, dzięki czemu można dokładnie zrozumieć, jakie dane i zmienne trafiły do ​​każdego modelu. Następną rzeczą, którą mówi nam podsumowanie, są kwantyle reszt, które wyliczyłbyś, gdybyś nazwał kwantylem (reszty (lm.fit)). Osobiście nie uważamy tego za bardzo pomocne, chociaż inni wolą szukać symetrii w minimalnej i maksymalnej wartości resztkowej, niż patrzeć na wykres rozrzutu danych. Jednak prawie zawsze uważamy, że wykresy są bardziej pouczające niż streszczenia numeryczne. Następnie podsumowanie mówi nam o współczynnikach regresji o wiele bardziej szczegółowo niż zrobiłby to Coef. Dane wyjściowe z cewki kończą się w tej tabeli jako kolumna „Oszacowanie”. Następnie są kolumny dla „Std. Błąd ”,„ wartość t ”i wartość p każdego współczynnika. Wartości te służą do oceny niepewności, którą mamy w obliczonych przez nas szacunkach; innymi słowy, mają one być miarą naszej pewności, że wartości, które wyliczyliśmy z określonego zestawu danych, są dokładnymi opisami świata rzeczywistego, który wygenerował dane „Std. Błąd ”, na przykład, można wykorzystać do uzyskania 95% przedziału ufności dla współczynnika. Interpretacja tego przedziału może być myląca i czasami jest prezentowana w sposób wprowadzający w błąd. Przedział wskazuje granice, dla których możemy powiedzieć: „95% czasu, algorytm, którego używamy do konstruowania przedziałów, będzie zawierał prawdziwy współczynnik wewnątrz przedziałów, które wytwarza”. Jeśli jest to dla ciebie niejasne, to normalne: analiza niepewności jest niezwykle ważna, ale o wiele trudniejsza niż inne materiały, które omawiamy .Jeśli naprawdę chcesz zrozumieć ten materiał, zalecamy kupienie odpowiedniego podręcznika statystyki. Na szczęście tego rodzaju jakościowe rozróżnienia, które wymagają zwrócenia uwagi na standardowe błędy, zwykle nie są konieczne, jeśli próbujesz po prostu zhakować modele, aby coś przewidzieć. Zarówno „wartość t”, jak i wartość p (zapisane jako „Pr (> | t |)” w danych wyjściowych ze streszczenia) są miarami tego, jak jesteśmy pewni, że prawdziwy współczynnik nie jest równy zero. Mówi się o tym, że jesteśmy przekonani, że istnieje rzeczywisty związek między wynikami a danymi wejściowymi, które obecnie badamy. Na przykład tutaj możemy użyć kolumny „wartość t”, aby ocenić, czy jesteśmy pewni, że Wyświetlenia strony naprawdę są powiązane z UniqueVisitors. Naszym zdaniem te dwie liczby mogą być przydatne, jeśli rozumiesz, jak z nimi pracować, ale są one wystarczająco skomplikowane, aby ich użycie pomogło ludziom założyć, że nigdy nie w pełni rozumieją statystyki. Jeśli zależy ci na tym, aby mieć pewność, że dwie zmienne są ze sobą powiązane, sprawdź, czy oszacowanie wynosi co najmniej dwa standardowe błędy od zera. Na przykład współczynnik log (UniqueVisitors) wynosi 1,33628, a błąd standardowy wynosi 0,04568. Oznacza to, że współczynnik wynosi 1,33628 / 0,04568 == 29,25306 błędów standardowych od zera. Jeśli dzieli Cię więcej niż trzy standardowe błędy od zera, możesz mieć pewność, że Twoje dwie zmienne są powiązane. Wartości t i p są przydatne przy podejmowaniu decyzji, czy związek między dwiema kolumnami w danych jest prawdziwy, czy tylko przypadkiem. Decyzja o istnieniu związku jest cenna, ale zrozumienie związku to zupełnie inna sprawa. Regresje nie mogą ci w tym pomóc. Ludzie próbują je zmusić, ale ostatecznie, jeśli chcesz zrozumieć powody, dla których dwie rzeczy są ze sobą powiązane, potrzebujesz więcej informacji niż zwykła regresja.

Tradycyjnym punktem odcięcia dla pewności, że dane wejściowe są powiązane z naszymi wynikami, jest znalezienie współczynnika oddalonego od zera co najmniej o dwa standardowe błędy. Następną informacją, którą wypluwa podsumowanie, są kody istotności współczynników. Są to gwiazdki pokazane wzdłuż boku, które mają wskazywać, jak duża jest „wartość t” lub jak mała jest wartość p. W szczególności gwiazdka mówi ci niezależnie od tego, czy minął szereg arbitralnych wartości odcięcia, przy których wartość p jest mniejsza niż 0,1, mniejsza niż 0,05, mniejsza niż 0,01 lub mniejsza niż 0,001. Nie martw się o te wartości; są niepokojąco popularne w środowisku akademickim, ale tak naprawdę są reliktami z czasów, gdy analiza statystyczna była wykonywana ręcznie, a nie na komputerze. W tych liczbach nie ma dosłownie żadnej interesującej treści, która nie zostałaby znaleziona w pytaniu, ile standardowych błędów szacuje się od 0. Rzeczywiście, mogliśmy zauważyć w naszym wcześniejszym obliczeniu, że „wartość t” dla log (UniqueVisitors) była dokładnie liczbą standardu błędu od 0, że współczynnik log (UniqueVisitors) był. Ta zależność między wartościami t a liczbą błędów standardowych od zera jest na ogół prawdziwa, dlatego sugerujemy, aby w ogóle nie pracować z wartościami p. Ostateczne informacje, które otrzymujemy, są związane z mocą predykcyjną modelu liniowego, który dopasowaliśmy do naszych danych. Pierwszy element, „resztkowy błąd standardowy”, jest po prostu RMSE modelu, który moglibyśmy obliczyć za pomocą qrt (średnia (reszty (lm.fit) ^ 2)). „Stopnie swobody” odnoszą się do pojęcia, że ​​efektywnie wykorzystaliśmy dwa punkty danych w naszej analizie, dopasowując dwa współczynniki: punkt przecięcia i współczynnik log (UniqueVisitors). Ta liczba, 998, jest istotna, ponieważ niski współczynnik RMSE nie jest imponujący, jeśli użyłeś 500 współczynników w swoim modelu, aby dopasować 1000 punktów danych. Korzystanie z wielu współczynników, gdy masz bardzo mało danych, jest formą nadmiernego dopasowania, o której powiemy więcej później. Następnie widzimy „wielokrotne R-kwadrat”. Jest to standardowy „R-kwadrat”, który opisaliśmy wcześniej, który mówi nam, jaki procent wariancji w naszych danych został wyjaśniony przez nasz model. Tutaj wyjaśniamy 46% wariancji przy użyciu naszego modelu, czyli całkiem dobre.

„Skorygowany R-kwadrat” to druga miara, która penalizuje „Wiele R-kwadrat” za liczbę użytych współczynników. W praktyce osobiście ignorujemy tę wartość, ponieważ uważamy, że jest ona nieco ad hoc, ale jest wielu ludzi, którzy bardzo ją lubią. Wreszcie ostatnia informacja, którą zobaczysz, to „Statystyka F.” Jest to miara ulepszenia twojego modelu w porównaniu z wykorzystaniem jedynie średniej do prognozowania. Jest to alternatywa dla „R-kwadrat”, która pozwala obliczyć „wartość p”. Ponieważ uważamy, że „wartość p” jest zwykle zwodnicza, zachęcamy was, abyście nie pokładali zbyt dużej wiary w statystykę F. „Wartości p” mają swoje zastosowanie, jeśli całkowicie rozumiesz mechanizm zastosowany do ich obliczenia, ale w przeciwnym razie mogą zapewnić fałszywe poczucie bezpieczeństwa, które sprawi, że zapomnisz, że złotym standardem wydajności modelu jest moc predykcyjna danych, które nie były używane do dopasowania modelu, a nie jego wydajności w stosunku do danych, do których był dopasowany. Omówimy metody oceny zdolności Twojego modelu do przewidywania nowych danych w rozdziale 6. Te dane wyjściowe z podsumowania prowadzą nas dość daleko, ale chcielibyśmy zawrzeć inne rodzaje informacji poza powiązaniem odsłon strony z UniqueVisitors. Uwzględnimy także HasAdvertising i IsEnglish, aby zobaczyć, co się stanie, gdy damy naszemu modelowi więcej danych do pracy:

lm.fit <- lm(log(PageViews) ~ HasAdvertising + log(UniqueVisitors) + InEnglish,

data = top.1000.sites)

summary(lm.fit)

#Call:

#lm(formula = log(PageViews) ~ HasAdvertising + log(UniqueVisitors) +

# InEnglish, data = top.1000.sites)

#

#Residuals:

# Min 1Q Median 3Q Max

#-2.4283 -0.7685 -0.0632 0.6298 5.4133

#

#Coefficients:

# Estimate Std. Error t value Pr(>|t|)

#(Intercept) -1.94502 1.14777 -1.695 0.09046 .

#HasAdvertisingYes 0.30595 0.09170 3.336 0.00088 ***

#log(UniqueVisitors) 1.26507 0.07053 17.936 < 2e-16 ***

#InEnglishNo 0.83468 0.20860 4.001 6.77e-05 ***

#InEnglishYes -0.16913 0.20424 -0.828 0.40780

#—

#Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

#

#Residual standard error: 1.067 on 995 degrees of freedom

#Multiple R-squared: 0.4798, Adjusted R-squared: 0.4777

#F-statistic: 229.4 on 4 and 995 DF, p-value: < 2.2e-16

Ponownie widzimy, że summary przypomina nasze wezwanie do lm i wypisuje resztki. Nowością w tym podsumowaniu są współczynniki dla wszystkich zmiennych, które uwzględniliśmy w naszym bardziej skomplikowanym modelu regresji. Ponownie widzimy przecięcie wydrukowane jako (Inter cept). Następny wpis jest zupełnie inny niż wszystko, co widzieliśmy wcześniej, ponieważ nasz model zawiera teraz czynnik. Gdy użyjesz współczynnika w regresji, model musi zdecydować o dołączeniu jednego poziomu jako części przechwytywania, a drugiego poziomu jako czegoś, co ma być modelowane jawnie. Tutaj możesz zobaczyć, że HasAdvertising został zamodelowany w taki sposób, że strony, dla których HasAdvertising == „Tak” są oddzielone od przechwytywania, podczas gdy strony, dla których HasAdvertising == „Nie” są składane do przechwytywania. Innym sposobem na opisanie tego jest stwierdzenie, że przechwytywanie jest prognozą dla witryny, która nie ma reklam i ma zerowy dziennik (UniqueVisitors), co faktycznie występuje, gdy masz jednego UniqueVisitor. Możemy zobaczyć tę samą logikę dla InEnglish, z wyjątkiem tego, że ten czynnik ma wiele wartości NA, więc tak naprawdę istnieją trzy poziomy tej zmiennej zastępczej: NA, „Nie” i „Tak”. W takim przypadku R traktuje wartość NA jako domyślną wartość zagięcia do przechwytu regresji i pasuje do oddzielnych współczynników dla poziomów „Nie” i „Tak”. Teraz, gdy zastanowiliśmy się, w jaki sposób możemy wykorzystać te czynniki jako dane wejściowe do naszego modelu regresji, porównajmy każde z trzech danych wejściowych, które wykorzystaliśmy osobno, aby zobaczyć, która z nich ma najbardziej przewidywalną moc, gdy jest używana samodzielnie. Aby to zrobić, możemy wyodrębnić R-kwadrat dla każdego podsumowania osobno:

lm.fit <- lm(log(PageViews) ~ HasAdvertising,

data = top.1000.sites)

summary(lm.fit)$r.squared

#[1] 0.01073766

lm.fit <- lm(log(PageViews) ~ log(UniqueVisitors),

data = top.1000.sites)

summary(lm.fit)$r.squared

#[1] 0.4615985

lm.fit <- lm(log(PageViews) ~ InEnglish,

data = top.1000.sites)

summary(lm.fit)$r.squared

#[1] 0.03122206

pasuje do oddzielnych współczynników dla poziomów „Nie” i „Tak”. Teraz, gdy zastanowiliśmy się, w jaki sposób możemy wykorzystać te czynniki jako dane wejściowe do naszego modelu regresji, porównajmy każde z trzech wejść, które wykorzystaliśmy osobno, aby zobaczyć, która z nich ma najbardziej przewidywalną moc, gdy jest używana samodzielnie. Aby to zrobić, możemy wyodrębnić R-kwadrat dla każdego podsumowania osobno:

lm.fit <- lm(log(PageViews) ~ HasAdvertising,

data = top.1000.sites)

summary(lm.fit)$r.squared

#[1] 0.01073766

lm.fit <- lm(log(PageViews) ~ log(UniqueVisitors),

data = top.1000.sites)

summary(lm.fit)$r.squared

#[1] 0.4615985

lm.fit <- lm(log(PageViews) ~ InEnglish,

data = top.1000.sites)

summary(lm.fit)$r.squared

#[1] 0.03122206

Jak widać, HasAdvertising wyjaśnia tylko 1% wariancji, UniqueVisitors wyjaśnia 46%, a InEnglish wyjaśnia 3%. W praktyce warto uwzględnić wszystkie dane wejściowe w modelu predykcyjnym, gdy są one tanie w zakupie, ale gdyby HasAdvertising był trudny do nabycia programowo, zalecamy umieszczenie go w modelu z innymi danymi wejściowymi, które mają o wiele większą moc predykcyjną

Regresja liniowa w pigułce

Dwa największe założenia, które przyjmujemy przy użyciu regresji liniowej do przewidywania wyników, są następujące:

Rozdzielność / addytywność

Jeśli istnieje wiele informacji, które mogłyby wpłynąć na nasze domysły, zgadujemy, sumując efekty każdej informacji, tak jakby każda informacja była używana osobno. Na przykład, jeśli alkoholicy żyją o rok mniej niż osoby niebędące alkoholikami, a palacze żyją pięć lat mniej niż osoby niepalące, alkoholik, który jest także palaczem, powinien żyć 1 + 5 = 6 lat mniej niż niepalący. Założenie, że skutki rzeczy w izolacji sumują się, gdy zdarzają się razem, jest bardzo dużym założeniem, ale jest dobrym miejscem do rozpoczęcia wielu zastosowań regresji. Później porozmawiamy o idei interakcji, która jest techniką obejścia założenia separowalności w regresji liniowej, gdy na przykład wiesz, że efekt nadmiernego picia jest znacznie gorszy, jeśli również palisz.

Monotoniczność / liniowość

Model jest monotoniczny, gdy zmiana jednego z wejść zawsze powoduje wzrost lub spadek przewidywanej mocy wyjściowej. Na przykład, jeśli przewidujesz ciężary, używając wysokości jako danych wejściowych, a Twój model jest monotoniczny, to zakładasz, że za każdym razem, gdy wzrost kogoś wzrośnie, Twoja prognoza jego masy wzrośnie. Monotoniczność jest silnym założeniem, ponieważ można sobie wyobrazić wiele przykładów, w których moc wyjściowa rośnie nieco z wejściem, a następnie zaczyna spadać, ale założenie monotoniczności jest w rzeczywistości znacznie mniej silne niż pełne założenie algorytmu regresji liniowej, który nazywa się liniowością. Liniowość jest terminem technicznym o bardzo prostym znaczeniu: jeśli wykreślisz dane wejściowe i wyjściowe na wykresie rozrzutu, powinieneś zobaczyć linię, która wiąże dane wejściowe z danymi wyjściowymi, a nie coś o bardziej skomplikowanym kształcie, takim jak krzywa lub fala. Dla tych, którzy myślą mniej wizualnie, liniowość oznacza, że ​​zmiana wejścia o jedną jednostkę zawsze dodaje N jednostek do wyjścia lub zawsze odejmuje N jednostek od wyjścia. Każdy model liniowy jest monotoniczny, ale krzywe mogą być monotoniczne bez liniowości. Z tego powodu liniowość jest bardziej restrykcyjna niż monotoniczność. Wyjaśnijmy dokładnie, co mamy na myśli, gdy mówimy linię, krzywą i falę, patrząc na przykłady wszystkich trzech na rysunku

Zarówno krzywe, jak i linie są monotoniczne, ale fale nie są monotoniczne, ponieważ czasami wznoszą się i opadają. Standardowa regresja liniowa będzie działać tylko wtedy, gdy dane będą wyglądać jak linia, gdy wydrukujesz dane wyjściowe względem każdego z danych wejściowych. W rzeczywistości można zastosować regresję liniową do dopasowania krzywych i fal, ale jest to bardziej zaawansowany temat, o którym będziemy mówić później.

Mając na uwadze założenia dotyczące addytywności i liniowości, zacznijmy od prostego przykładu regresji liniowej. Wróćmy do korzystania z naszych zestawów danych dotyczących wysokości i wag jeszcze raz, aby pokazać, jak regresja liniowa działa w tym kontekście. Na rycinie widzimy wykres rozrzutu, który narysowaliśmy wiele razy wcześniej.

Niewielka zmiana naszego kodu kreślarskiego pokaże nam linię, którą wytworzy model regresji liniowej jako metodę przewidywania wag za pomocą wysokości, jak pokazano na rysunku

Po prostu musimy dodać wywołanie do funkcji geom_smooth, określając, że chcemy użyć metody lm, która implementuje „modele liniowe”.

library(‘ggplot2’)

heights.weights <- read.csv(‘data/01_heights_weights_genders.csv’,

header = TRUE,

sep = ‘,’)

ggplot(heights.weights, aes(x = Height, y = Weight)) +

geom_point() +

geom_smooth(method = ‘lm’)

Spojrzenie na ten wykres  powinno Cię przekonać, że użycie linii do przewidzenia masy osoby, biorąc pod uwagę jej wzrost, może działać całkiem dobrze. Na przykład linia, którą widzimy, mówi, że powinniśmy przewidzieć wagę 105 funtów dla kogoś, kto ma 60 cali wzrostu, i powinniśmy przewidzieć wagę 225 funtów dla kogoś, kto ma 75 cali wzrostu. Rozsądne wydaje się zatem zaakceptowanie, że używanie linii do prognozowania jest mądrym posunięciem. W związku z tym pytanie, na które musimy odpowiedzieć, brzmi: „Jak znaleźć liczby określające linię, którą widzimy na tym wykresie?” W tym miejscu R naprawdę błyszczy jako język uczenia maszynowego: istnieje prosta funkcja o nazwie lm w R, która wykona za nas całą pracę. Aby użyć lm, musimy określić formułę regresji za pomocą operatora ~. W naszym przykładzie zamierzamy przewidzieć wagę na podstawie wzrostu, więc piszemy Waga ~ wzrost. Gdybyśmy mieli przewidywać w przeciwnym kierunku, zapisalibyśmy Wzrost ~ Waga. Jeśli chcesz wymawiać te formuły, osobiście lubimy mówić, że Wzrost ~ Waga oznacza „wzrost jako funkcja wagi”. Oprócz specyfikacji regresji musimy powiedzieć R, gdzie jesteśmy danymi korzystanie jest przechowywane. Jeśli zmienne w regresji są zmiennymi globalnymi, możesz to pominąć, ale społeczność R. nie lubi tego typu zmiennych globalnych. Przechowywanie zmiennych globalnych w R jest uważane za złą praktykę, ponieważ łatwo jest zgubić to, co zostało załadowane do pamięci, a co nie. Może to powodować niezamierzone konsekwencje w kodowaniu, ponieważ łatwo jest stracić kontrolę nad tym, co znajduje się w pamięci i co z niej nie ma. Ponadto zmniejsza łatwość udostępniania kodu. Jeśli zmienne globalne nie są wyraźnie zdefiniowane w kodzie, ktoś, kto jest mniej zaznajomiony z procesem ładowania danych, może coś przeoczyć, w wyniku czego kod może nie działać, ponieważ brakuje jakiegoś zestawu danych lub zmiennej. Lepiej jest jawnie określić źródło danych za pomocą parametru danych. Zestawiając je razem, przeprowadzamy regresję liniową w R w następujący sposób:

fitted.regression <- lm(Weight ~ Height,

data = heights.weights)

Po uruchomieniu tego wywołania do lm możesz wyciągnąć punkt przecięcia linii regresji z wywołaniem funkcji coef, która zwraca współczynniki dla modelu liniowego, który odnosi dane wejściowe do wyników. Mówimy „model liniowy”, ponieważ regresja może działać w więcej niż dwóch wymiarach. W dwóch wymiarach dopasowujemy linie (stąd część „liniowa”), ale w trzech wymiarach dopasowujemy płaszczyzny, a w więcej niż trzech wymiarach dopasowujemy hiperpłaszczyzny.

Jeśli te terminy nic dla ciebie nie znaczą, intuicja jest prosta: płaska powierzchnia w dwóch wymiarach jest linią, podczas gdy w trzech wymiarach jest to płaszczyzna, aw więcej niż trzech wymiarach, płaska powierzchnia jest nazywana hiperpłaszczyzną. Jeśli nie jest to wystarczająco jasne, zalecamy przeczytanie Flatland.

coef(fitted.regression)

#(Intercept) Height

#-350.737192 7.717288

Zrozumienie tego wyniku jest w pewnym sensie rozumieniem regresji liniowej. Najprostszym sposobem na zrozumienie danych wyjściowych z cewki jest wyraźne zapisanie relacji, którą implikuje:

intercept <- coef(fitted.regression)[1]

slope <- coef(fitted.regression)[2]

# predicted.weight <- intercept + slope * observed.height

# predicted.weight == -350.737192 + 7.717288 * observed.height

Oznacza to, że każdy wzrost 1 cala wzrostu kogoś powoduje wzrost jego masy o 7,7 funta. To wydaje nam się dość rozsądne. W przeciwieństwie do tego, przechwytywanie jest nieco dziwniejsze, ponieważ mówi ci, ile waży osoba o wzroście 0 cali. Według R. ważyłaby –350 funtów. Jeśli chcesz zrobić algebrę, możesz wywnioskować, że dana osoba musi mieć 45 cali wysokości, aby nasz algorytm przewidywania powiedział, że waży 0 funtów. Krótko mówiąc, nasz model regresji nie jest tak świetny dla dzieci ani wyjątkowo niskich dorosłych. Jest to w rzeczywistości systematyczny problem regresji liniowej w ogólności: modele predykcyjne zwykle nie są zbyt dobre w przewidywaniu wyników dla danych wejściowych, które są bardzo dalekie od wszystkich danych wejściowych, które widziałeś w przeszłości. Często możesz popracować poprawić jakość zgadywania poza zakresem danych używanych do trenowania modelu, ale w tym przypadku prawdopodobnie nie ma takiej potrzeby, ponieważ zazwyczaj będziesz przewidywał tylko osoby, które mają ponad 4 ‘i poniżej 8’ wzrostu . Oprócz prowadzenia regresji i analizowania uzyskanych współczynników, w R można zrobić znacznie więcej, aby zrozumieć wyniki regresji liniowej. Przejrzenie wszystkich różnych wyników, które może wytworzyć R, zajęłoby całą książkę, więc skupimy się na kilku najważniejszych częściach po wyodrębnieniu współczynników. Gdy tworzysz prognozy w praktycznym kontekście, wszystkie współczynniki są wszystkim, co musisz wiedzieć. Po pierwsze, chcemy dowiedzieć się, gdzie nasz model jest zły. Robimy to, obliczając prognozy modelu i porównując je z danymi wejściowymi. Aby znaleźć przewidywania modelu, używamy funkcji przewidywania w R:

predict(fitted.regression)

Kiedy już mamy nasze przewidywania, możemy obliczyć różnicę między naszymi przewidywaniami a prawdą za pomocą prostego odejmowania:

true.values <- with(heights.weights,Weight)

errors <- true.values – predict(fitted.regression)

Błędy, które obliczamy w ten sposób, nazywane są resztkami, ponieważ stanowią one część naszych danych, które pozostały po uwzględnieniu części, którą wiersz może wyjaśnić. Możesz uzyskać te reszty bezpośrednio w R bez funkcji przewidywania, używając zamiast tego funkcji reszty:

residuals(fitted.regression)

Typowym sposobem diagnozowania oczywistych błędów, które popełniamy podczas regresji liniowej, jest wykreślanie resztek względem prawdy:

plot(fitted.regression, which = 1)

W tym miejscu poprosiliśmy R, aby wykreślił tylko pierwszy wykres diagnostyczny regresji, określając, który = 1. Istnieją inne wykresy, które można uzyskać, i zachęcamy do eksperymentowania, aby sprawdzić, czy któryś z pozostałych jest pomocny. W tym przykładzie możemy stwierdzić, że nasz model liniowy działa dobrze, ponieważ w resztach nie ma systematycznej struktury. Ale oto inny przykład, w którym linia jest nieodpowiednia:

x <- 1:10

y <- x ^ 2

fitted.regression <- lm(y ~ x)

plot(fitted.regression, which = 1)

Widzimy oczywistą strukturę w pozostałościach tego problemu. Zasadniczo jest to złe podczas modelowania danych: model powinien podzielić świat na sygnał (który daje ci dyktando) i hałas (który daje reszty). Jeśli gołym okiem widzisz sygnał w resztkach, oznacza to, że Twój model nie jest wystarczająco silny, aby wyodrębnić cały sygnał i pozostawić jedynie prawdziwy szum jako resztki. Aby rozwiązać ten problem, później omówimy mocniejsze modele regresji niż prosty model regresji liniowej, nad którym obecnie pracujemy. Ponieważ jednak wielka moc niesie ze sobą wielką odpowiedzialność, skupimy się na unikatowych problemach, które pojawiają się, gdy używamy modeli tak potężnych, że w rzeczywistości są zbyt potężne, aby można było z nich korzystać bez ostrożności. Na razie porozmawiajmy dokładniej o tym, jak dobrze sobie radzimy z regresją liniową. Posiadanie resztek jest świetne, ale jest ich tak wiele, że praca z nimi może być przytłaczająca. Chcielibyśmy, aby jedna liczba podsumowała jakość naszych wyników. Najprostszym pomiarem błędu jest (1) pobranie wszystkich reszt, (2) wyprostowanie ich w celu uzyskania kwadratowych błędów dla naszego modelu i (3) zsumowanie ich.

x <- 1:10

y <- x ^ 2

fitted.regression <- lm(y ~ x)

errors <- residuals(fitted.regression)

squared.errors <- errors ^ 2

sum(squared.errors)

#[1] 528

Ta prosta suma kwadratów błędów jest przydatna do porównywania różnych modeli, ale ma pewne dziwactwa, których większość ludzi nie lubi.

Po pierwsze, suma błędów kwadratu jest większa dla dużych zbiorów danych niż dla małych zbiorów danych. Aby się o tym przekonać, wyobraź sobie ustalony zestaw danych i sumę kwadratów błędów dla tego zestawu danych. Teraz dodaj jeszcze jeden punkt danych, który nie jest dokładnie przewidziany. Ten nowy punkt danych musi zwiększyć sumę błędów podniesionych do kwadratu, ponieważ dodanie liczby do poprzedniej sumy błędów kwadratu może tylko zwiększyć sumę. Istnieje jednak prosty sposób na rozwiązanie tego problemu: raczej wykorzystaj średnią z kwadratów błędów niż sumę. To daje nam miarę MSE, której używaliśmy wcześniej w tym rozdziale. Chociaż MSE nie będzie rosło konsekwentnie, ponieważ otrzymamy więcej danych, niż w przypadku surowej sumy błędów kwadratu, MSE nadal ma problem: jeśli średnia prognoza jest wyłączona tylko o 5, wtedy średni błąd kwadratu wyniesie 25. To dlatego, że wyrównujemy błędy, zanim wykorzystamy ich średnią.

x <- 1:10

y <- x ^ 2

dopasowanie. regresja <- lm (y ~ x)

błędy <- resztki (dopasowanie. regresja)

squared.errors <- błędy ^ 2

mse <- mean (squared.errors)

mse

# [1] 52,8

Rozwiązanie tego problemu skali jest trywialne: bierzemy pierwiastek kwadratowy średniego błędu kwadratu, aby uzyskać pierwiastek średni błąd kwadratu, czyli pomiar RMSE, który również wypróbowaliśmy wcześniej w tym rozdziale. RMSE jest bardzo popularną miarą wydajności do oceny algorytmów uczenia maszynowego, w tym algorytmów o wiele bardziej wyrafinowanych niż regresja liniowa. Tylko w jednym przykładzie nagrodę Netflix przyznano za pomocą RMSE jako ostatecznej miary skuteczności algorytmów uczestników.

x <- 1:10

y <- x ^ 2

dopasowanie. regresja <- lm (y ~ x)

błędy <- resztki (dopasowanie. regresja)

squared.errors <- błędy ^ 2

mse <- mean (squared.errors)

rmse <- sqrt (mse)

rmse

# [1] 7.266361

Jeden problem, którą ludzie mają z RMSE, polega na tym, że nie jest od razu jasne, co to jest mierna wydajność. Doskonała wydajność wyraźnie daje RMSE równą 0, ale dążenie do perfekcji nie jest realistycznym celem w tych zadaniach. Podobnie nie jest łatwo rozpoznać, kiedy model działa bardzo słabo. Na przykład, jeśli wszyscy mają wysokość 5, a przewidujesz 5000, otrzymasz ogromną wartość dla RMSE. Możesz zrobić coś gorszego, przewidując 50 000 „, a jeszcze gorzej, przewidując 5 000 000”. Nieograniczone wartości, które może przyjąć RMSE, utrudniają ustalenie, czy wydajność twojego modelu jest rozsądna.

Podczas regresji liniowych rozwiązaniem tego problemu jest użycie R2 (wymawiane „R do kwadratu”). Ideą R2 jest sprawdzenie, o ile lepszy jest Twój model, niż gdybyśmy tylko użyli tego środka. Aby ułatwić interpretację, R2 zawsze będzie zawierać się w przedziale od 0 do 1. Jeśli nie masz nic lepszego niż średnia, będzie wynosić 0. każdy punkt danych idealnie, R2 będzie wynosić 1. Ponieważ R2 ma zawsze wartość od 0 do 1, ludzie mają tendencję do mnożenia go przez 100 i mówią, że jest to procent wariancji w danych, które wyjaśniłeś swoim modelem. To jest wygodny sposób budowania intuicji na temat dokładności modelu, nawet w nowej domenie, w której nie masz doświadczenia w zakresie standardowych wartości RMSE. Aby obliczyć R2, musisz obliczyć RMSE dla modelu, który wykorzystuje tylko średnią wydajność do prognozowania dla wszystkich przykładowych danych. Następnie musisz obliczyć RMSE dla swojego modelu. Następnie jest to prosta operacja arytmetyczna na utworzenie R2, którą opisaliśmy w kodzie tutaj:

mean.rmse <- 1.09209343

model.rmse <- 0,954544

r2 <- 1 – (model.rmse / mean.rmse)

r2

# [1] 0,12559502

Regresja za pomocą zmiennych manekin

Jak więc możemy wykorzystać te informacje? Zacznijmy od prostej sprawy, zanim zajmiemy się szerszym problemem. Jak możemy wykorzystać informacje o tym, czy ludzie palą, aby lepiej zgadywać o swojej długowieczności? Jednym prostym pomysłem jest oszacowanie średniego wieku w chwili śmierci dla palaczy i niepalących, a następnie wykorzystanie tych dwóch osobnych wartości jako naszych domysłów dla przyszłych przypadków, w zależności od tego, czy nowa osoba pali. Tym razem zamiast korzystać z MSE, wykorzystamy błąd średniej kwadratowej (RMSE), który jest bardziej popularny w literaturze dotyczącej uczenia maszynowego. Oto jeden ze sposobów obliczenia RMSE w R po podzieleniu palaczy i niepalących na dwie grupy, które są modelowane osobno:

Informacja : Błąd średni kwadratowy

Błąd bez informacji o paleniu : 5.737096

Błąd przy podawaniu informacji o paleniu : 5.148622

ages <- read.csv(‘data/longevity.csv’)

constant.guess <- with(ages, mean(AgeAtDeath))

with(ages, sqrt(mean((AgeAtDeath – constant.guess) ^ 2)))

smokers.guess <- with(subset(ages, Smokes == 1),

mean(AgeAtDeath))

non.smokers.guess <- with(subset(ages, Smokes == 0),

mean(AgeAtDeath))

ages <- transform(ages,

NewPrediction = ifelse(Smokes == 0,

non.smokers.guess,

smokers.guess))

with(ages, sqrt(mean((AgeAtDeath – NewPrediction) ^ 2)))

Jak widać, patrząc na RMSE, które otrzymujemy, nasze przewidywania naprawdę poprawiają się po tym, jak dołączymy więcej informacji o badanych ludziach: nasz błąd prognozowania przy szacowaniu długości życia ludzi staje się o 10% mniejszy, gdy uwzględniamy informacje o zwyczaju palenia u ludzi . Ogólnie rzecz biorąc, możemy zrobić coś więcej niż używać tylko wartości średniej, ilekroć mamy binarne rozróżnienia oddzielające dwa typy punktów danych – zakładając, że te binarne rozróżnienia są powiązane z wynikami, które staramy się przewidzieć. Kilka prostych przykładów, w których binarne rozróżnienia mogą pomóc, to kontrastowanie mężczyzn z kobietami lub kontrastowanie demokratów z republikanami w amerykańskim dyskursie politycznym. Mamy teraz mechanizm włączania zmiennych zastępczych do naszych prognoz. Ale w jaki sposób możemy wykorzystać bogatsze informacje o obiektach w naszych danych? Przez bogatszy mamy na myśli dwie rzeczy: po pierwsze, chcemy wiedzieć, w jaki sposób możemy korzystać z danych wejściowych, które nie są binarnymi rozróżnieniami, lecz ciągłe wartości, takie jak wysokości lub wagi; po drugie, chcemy wiedzieć, w jaki sposób możemy wykorzystać wiele źródeł informacji naraz, aby poprawić nasze prognozy. W naszym przykładzie aktuarialnym załóżmy, że wiedzieliśmy (a) czy ktoś był palaczem oraz (b) wiek, w którym zmarli jego rodzice. Naszą intuicją jest to, że posiadanie tych dwóch oddzielnych źródeł informacji powinno powiedzieć nam więcej niż jedną z tych zmiennych w oderwaniu. Ale wykorzystanie wszystkich posiadanych informacji nie jest łatwym zadaniem. W praktyce musimy poczynić pewne uproszczenia, aby wszystko zaczęło działać. Założenia, które opiszemy, są tymi, które leżą u podstaw regresji liniowej, która jest jedynym typem regresji, który opiszemy w tej części. Używanie tylko regresji liniowej jest mniejszym ograniczeniem niż mogłoby się wydawać, ponieważ regresja liniowa jest używana w co najmniej 90% praktycznych zastosowań regresji i może zostać zhakowana w celu uzyskania bardziej wyrafinowanych form regresji przy niewielkim nakładzie pracy.

Model podstawowy

To może wydawać się głupie, ale najprostszym możliwym sposobem wykorzystania informacji, które mamy jako dane wejściowe, jest całkowite zignorowanie danych wejściowych i przewidywanie przyszłych wyników na podstawie jedynie średniej wartości wyników, które widzieliśmy w przeszłości. W przykładzie aktuariusza możemy całkowicie zignorować dokumentację medyczną osoby i po prostu zgadnąć, że będzie ona żyła tak długo, jak żyje przeciętna osoba. Zgadywanie średniego wyniku dla każdego przypadku nie jest tak naiwne, jak mogłoby się wydawać: jeśli jesteśmy zainteresowani prognozami, które są jak najbliższe prawdy, jak to możliwe, bez korzystania z innych informacji, zgadywanie średniego wyniku okazuje się najlepsze Chyba możemy to zrobić. Trochę pracy wymaga zdefiniowania „najlepszego”, aby nadać temu twierdzeniu określone znaczenie. Jeśli czujesz się niekomfortowo, gdy rzucamy słowo „najlepszy”, nie mówiąc o tym, co mamy na myśli, obiecujemy, że wkrótce podamy formalną definicję. Zanim omówimy, jak najlepiej zgadywać, załóżmy, że mamy dane z wyimaginowanej bazy danych aktuarialnych, której część pokazano w tabelce:

Palił :  Wiek w chwili śmierci

1  : 75

1 : 72

1 : 66

1 : 74

1 : 69

… : …

0 : 66

0 : 80

0 : 84

0 : 63

0 : 79

… : …

Ponieważ jest to zupełnie nowy zestaw danych, dobrze jest postępować zgodnie z naszymi sugestiami w bardziej szczegółowej części i eksplorować dane przed przeprowadzeniem jakiejkolwiek formalnej analizy. Mamy jedną kolumnę numeryczną i drugą kolumnę, która jest kodem obojętnym, więc naturalne jest tworzenie wykresów gęstości w celu porównania palaczy z osobami niepalącymi

ages <- read.csv (‘data / longevity.csv’)

ggplot (ages, aes (x = AgeAtDeath, fill = współczynnik (Smokes))) +

geom_density() +

facet_grid(Smokes ~ .)

Powstały w ten sposób wykres wydaje się uzasadniony, aby sądzić, że palenie ma znaczenie dla długowieczności, ponieważ środek rozkładu długości życia palaczy jest przesunięty w prawo od środka długości życia palaczy. Innymi słowy, średnia długość życia osoby niepalącej jest dłuższa niż średnia długość życia osoby palącej. Ale zanim opiszemy, w jaki sposób możesz wykorzystać posiadane przez nas informacje o nawykach palenia danej osoby, aby przewidzieć jej długość życia, udawajmy przez chwilę, że nie posiadałeś żadnej z tych informacji. W takim przypadku musisz wybrać jeden numer jako prognozę dla każdej nowej osoby, niezależnie od jej nawyków związanych z paleniem. Jaki numer wybrać? To pytanie nie jest trywialne, ponieważ liczba, którą powinieneś wybrać, zależy od tego, co Twoim zdaniem oznacza dobre przewidywanie. Istnieje wiele rozsądnych sposobów na określenie dokładności prognoz, ale istnieje jedna miara jakości, która dominowała praktycznie w całej historii statystyki. Miara ta nazywana jest błędem do kwadratu. Jeśli próbujesz przewidzieć wartość y (prawdziwy wynik) i zgadniesz h (twoja hipoteza na temat y), wtedy kwadratowy błąd zgadnięcia jest po prostu (y – h) ^ 2. Powyżej wartości nieodłącznie wynikającej z następujących konwencji zrozumiałe dla innych, istnieje wiele dobrych powodów, dla których warto zastosować kwadratowy błąd do pomiaru jakości domysłów. Nie zajmiemy się nimi teraz, ale porozmawiamy trochę więcej o sposobach pomiaru błędu, gdy mówimy o algorytmach optymalizacji w uczeniu maszynowym. Na razie postaramy się cię przekonać o czymś fundamentalnym: jeśli używamy błędu kwadratu do mierzenia jakości naszych prognoz, wtedy możemy zgadywać, jak długo możemy żyć na temat długości życia danej osoby – bez żadnych dodatkowych informacji o nawyki człowieka – to długość życia przeciętnego człowieka. Aby przekonać Cię o tym roszczeniu, zobaczmy, co się stanie, jeśli użyjemy innych domysłów zamiast średniej. Dzięki zestawowi danych dotyczących długowieczności średni AgeAtDeath wynosi 72,723, a na razie zaokrąglamy do 73. Możemy wtedy zapytać: „Jak źle przewidzielibyśmy wiek osób w naszym zbiorze danych, gdybyśmy zgadli, że wszyscy żyją w wieku 73 lat?” Aby odpowiedzieć na to pytanie w R, możemy użyć następującego kodu, który łączy wszystkie błędy kwadratowe dla każdej osoby w naszym zbiorze danych, obliczając średnią błędu kwadratowego, który nazywamy średnim błędem kwadratowym (lub MSE dla krótki):

ages <- read.csv(‘data/longevity.csv’)

guess <- 73

with(ages, mean((AgeAtDeath – guess) ^ 2))

#[1] 32.991

Po uruchomieniu tego kodu okazuje się, że średni błąd kwadratowy, który popełniamy zgadując 73 dla każdej osoby w naszym zestawie danych, wynosi 32,991. Ale to samo w sobie nie powinno być wystarczające, aby przekonać cię, że zrobilibyśmy gorzej z przypuszczeniem, że nie jest to 73. Aby przekonać cię, że 73 jest najlepszym przypuszczeniem, musimy zastanowić się, jak byśmy się zachowali, gdybyśmy coś zrobili innych możliwych domysłów. Aby obliczyć te inne wartości w R, zapętlamy sekwencję możliwych domysłów od 63 do 83:

ages <- read.csv(‘data/longevity.csv’)

guess.accuracy <- data.frame()

for (guess in seq(63, 83, by = 1))

{

prediction.error <- with(ages,

mean((AgeAtDeath – guess) ^ 2))

guess.accuracy <- rbind(guess.accuracy,

data.frame(Guess = guess,

Error = prediction.error))

}

ggplot(guess.accuracy, aes(x = Guess, y = Error)) +

geom_point() +

geom_line()

Jak widać z rysunku 5-2,

użycie jakichkolwiek domysłów innych niż 73 daje nam gorsze prognozy dla naszego zestawu danych. Jest to właściwie ogólny wynik teoretyczny, który można udowodnić matematycznie: aby zminimalizować błąd do kwadratu, chcesz przewidzieć średnią wartość w swoim zestawie danych. Jednym z ważnych implikacji tego jest to, że wartość predykcyjna posiadania informacji o paleniu powinna być mierzona w kategoriach stopnia poprawy, jaki uzyskujesz dzięki wykorzystaniu tych informacji w porównaniu z wykorzystaniem jedynie wartości średniej jako wartości domyślnej dla bardzo widocznej osoby.

Regresja: przewidywanie wyświetleń strony

Przedstawiamy regresję

W skrócie regresja jest bardzo prostym pojęciem: chcesz przewidzieć jeden zestaw liczb na podstawie innego zestawu liczb. Na przykład aktuariusze mogą chcieć przewidzieć, jak długo dana osoba przeżyje, biorąc pod uwagę swoje nawyki związane z paleniem, podczas gdy meteorolodzy mogą chcieć przewidzieć temperaturę na następny dzień, biorąc pod uwagę temperaturę z poprzedniego dnia. Zasadniczo będziemy wywoływać liczby, które otrzymałeś, i liczby, które chcesz przewidzieć. Czasem też można usłyszeć, że ludzie nazywają dane wejściowe mianem predyktorów lub funkcji. Tym, co odróżnia regresję od klasyfikacji, jest fakt, że dane wyjściowe to tak naprawdę liczby. W problemach z klasyfikacją, takich jak te, które opisaliśmy wcześniej, możesz użyć liczb jako fałszywego kodu dla kategorycznego rozróżnienia, tak że 0 oznacza ham, a 1 spam. Ale te liczby są tylko symbolami; nie wykorzystujemy „liczby” 0 lub 1, gdy używamy zmiennych zastępczych. W regresji istotnym faktem na temat wyników jest to, że tak naprawdę są liczbami: chcesz przewidzieć takie rzeczy, jak temperatura, która może wynosić 20 stopni lub 41 stopni. Ponieważ przewidujesz liczby, chcesz być w stanie wydać mocne oświadczenia na temat związku między danymi wejściowymi i wyjściowymi: możesz na przykład powiedzieć, że gdy liczba paczek papierosów osoby palącej dziennie podwaja się, ich przewidywana długość życia zostaje zmniejszona o połowę. Problem polega oczywiście na tym, że chęć precyzyjnego przewidywania numerycznego to nie to samo, co faktyczne przewidywanie. Aby dokonać prognoz ilościowych, musimy opracować zasadę, która może wykorzystać informacje, do których mamy dostęp. Różne algorytmy regresji opracowane przez statystyków w ciągu ostatnich 200 lat zapewniają różne sposoby prognozowania, przekształcając dane wejściowe w dane wyjściowe. W tym rozdziale omówimy model regresji woła roboczego, który nazywa się regresją liniową.

Szkolenie i testowanie oceniającego

Aby wygenerować priorytetową rangę dla każdej wiadomości w naszych danych treningowych, musimy pomnożyć wszystkie wagi wyprodukowane w poprzedniej sekcji. Oznacza to, że dla każdej wiadomości w danych musimy przeanalizować wiadomość e-mail, pobrać wyodrębnione funkcje, a następnie dopasować je do odpowiednich ramek danych wagi, aby uzyskać odpowiednią wartość wagi. Następnie weźmiemy iloczyn tych wartości, aby stworzyć jedną – i niepowtarzalną – wartość rangi dla każdej wiadomości. Następująca funkcja rank.message to pojedyncza funkcja, która pobiera ścieżkę pliku do komunikatu i generuje priorytetowy priorytet dla tej wiadomości na podstawie zdefiniowanych przez nas funkcji i ich kolejnych wag. Funkcja rank.message opiera się na wielu funkcjach, które już zdefiniowaliśmy, a także na nowej funkcji get.weights, która wyszukuje wagę, gdy funkcja nie jest mapowana na pojedynczą wagę – tzn. Temat i treść wiadomości.

get.weights <- function(search.term, weight.df, term=TRUE) {

if(length(search.term) > 0) {

if(term) {

term.match <- match(names(search.term), weight.df$Term)

}

else {

term.match <- match(search.term, weight.df$Thread)

}

match.weights <- weight.df$Weight[which(!is.na(term.match))]

if(length(match.weights) > 1) {

return(1)

}

else {

return(mean(match.weights))

}

}

else {

return(1)

}

}

Najpierw definiujemy get.weights, który przyjmuje trzy argumenty: niektóre wyszukiwane hasła (ciąg znaków), ramkę danych wagi, w której należy wyszukiwać, oraz pojedynczą wartość logiczną dla terminu. Ten końcowy parametr pozwoli nam powiedzieć aplikacji, czy sprawdza ramkę danych terminu, czy ramkę danych wątku. Będziemy traktować te wyszukiwania nieco inaczej ze względu na różnice w etykietach kolumn w ramce danych wątku. Wagi, więc musimy wprowadzić to rozróżnienie. Proces tutaj jest dość prosty, ponieważ używamy funkcji dopasowania, aby znaleźć elementy w ramce danych wagi, które pasują do search.term i zwrócić wartość masy. Ważniejsze jest to, jak funkcja obsługuje niedopasowania. Po pierwsze, przeprowadzamy jedną kontrolę bezpieczeństwa, aby upewnić się, że szukany termin przekazywany do get.weights jest prawidłowy, sprawdzając, czy ma pewną długość dodatnią. Jest to ten sam rodzaj kontroli, który przeprowadziliśmy podczas analizowania danych e-mail, aby sprawdzić, czy wiadomość e-mail rzeczywiście ma temat. Jeśli jest to niepoprawny wyszukiwany termin, zwracamy po prostu 1 (co według podstawowej matematyki nie zmieni produktu obliczonego w następnym kroku z powodu reguł pomnożenia przez 1). Następnie funkcja dopasowania zwróci wartość NA dla wszystkich elementów w wektorze wyszukiwania, które nie pasują do search.term. Dlatego wyodrębniamy wartości masy tylko dla tych dopasowanych elementów, które nie są NA. Jeśli nie ma żadnych dopasowań, wektor term.match będzie wszystkimi NA, w takim przypadku dopasuj. Ciężary będą miały zerową długość. Tak więc wykonujemy dodatkową kontrolę dla tego przypadku, a jeśli napotkamy ten przypadek, ponownie zwracamy 1. Jeśli dopasowaliśmy niektóre wartości masy, zwracamy średnią z wszystkich tych ciężarów jako wynik.

rank.message <- function(path) {

msg <- parse.email(path)

# Weighting based on message author

# First is just on the total frequency

from <- ifelse(length(which(from.weight$From.EMail == msg[2])) > 0,

from.weight$Weight[which(from.weight$From.EMail == msg[2])], 1)

# Second is based on senders in threads, and threads themselves

thread.from <- ifelse(length(which(senders.df$From.EMail == msg[2])) > 0,

senders.df$Weight[which(senders.df$From.EMail == msg[2])], 1)

subj <- strsplit(tolower(msg[3]), “re: “)

is.thread <- ifelse(subj[[1]][1] == “”, TRUE, FALSE)

if(is.thread) {

activity <- get.weights(subj[[1]][2], thread.weights, term=FALSE)

}

else {

activity <- 1

}

# Next, weight based on terms

# Weight based on terms in threads

thread.terms <- term.counts(msg[3], control=list(stopwords=stopwords()))

thread.terms.weights <- get.weights(thread.terms, term.weights)

# Weight based terms in all messages

msg.terms <- term.counts(msg[4], control=list(stopwords=stopwords(),

removePunctuation=TRUE, removeNumbers=TRUE))

msg.weights <- get.weights(msg.terms, msg.weights)

# Calculate rank by interacting all weights

rank <- prod(from, thread.from, activity,

thread.terms.weights, msg.weights)

return(c(msg[1], msg[2], msg[3], rank))

}

Funkcja rank.message używa reguł podobnych do funkcji get.weights do przypisywania wartości masy do funkcji wyodrębnionych z każdego e-maila w zestawie danych. Po pierwsze, wywołuje funkcję arse.email w celu wyodrębnienia czterech interesujących cech. Następnie stosuje serię klauzul „jeśli-to”, aby ustalić, czy którakolwiek z funkcji wyodrębnionych z bieżącego e-maila jest obecna w którejkolwiek z ramek danych wagi używanych do rangowania, i odpowiednio przypisuje wagi. od i thread.from użyj funkcji interakcji społecznościowych, aby znaleźć wagę na podstawie adresu e-mail nadawcy. Zauważ, że w obu przypadkach jeśli funkcja ifelse  nie pasuje do niczego w ramkach danych masy danych, zwracana jest wartość 1. Jest to ta sama strategia, którą zastosowano w funkcji get.weights. W przypadku ważenia opartego na wątkach i terminach wykonuje się wewnętrzne parsowanie tekstu. W przypadku wątków najpierw sprawdzamy, czy oceniany e-mail jest częścią wątku, dokładnie tak samo, jak w fazie szkolenia. Jeśli tak, sprawdzamy pozycję; w przeciwnym razie przypisujemy 1. W przypadku ważenia opartego na terminach korzystamy z funkcji term.counts, aby uzyskać informacje o zainteresowaniach z funkcji wiadomości e-mail, a następnie odpowiednio przypisywać wagę. W ostatnim kroku generujemy ranking, przekazując wszystkie wartości wagi, które właśnie sprawdziliśmy, do funkcji prod. Funkcja rank.message zwraca następnie wektor z datą / godziną e-maila, adresem nadawcy, tematem i pozycją.

train.paths <- Priority.df $ Ścieżka [1: (okrągły (nrow (Prior.df) / 2))]

test.paths <- Priority.df $ Ścieżka [((okrągły (nrow (Prior.df) / 2)) + 1): Nrow (Prior.df)]

train.ranks <- lapply (train.paths, rank.message)

train.ranks.matrix <- do.call (rbind, train.ranks)

train.ranks.matrix <- cbind (train.paths, train.ranks.matrix, „TRAINING”)

train.ranks.df <- data.frame (train.ranks.matrix, stringsAsFactors = FALSE)

names (train.ranks.df) <- c („Wiadomość”, „Data”, „Od”, „Subj”, „Pozycja”, „Typ”)

train.ranks.df $ Rank <- as.numeric (train.ranks.df $ Rank)

priority.threshold <- mediana (train.ranks.df $ Rank)

train.ranks.df $ Priority <- ifelse (train.ranks.df $ Rank> = priorytet. próg, 1, 0)

Jesteśmy teraz gotowi do uruchomienia naszego rankingu! Zanim przejdziemy dalej, podzielimy nasze dane na dwa chronologicznie podzielone zestawy. Pierwszym będą dane treningowe, które nazywamy ścieżkami. Wykorzystamy wygenerowane dane rankingowe do ustalenia wartości progowej dla komunikatu „priorytetowego”. Gdy już to zrobimy, możemy uruchomić ranking nad e-mailami w test.paths, aby określić, które z nich są priorytetowe i oszacować ich wewnętrzną kolejność. Następnie zastosujemy funkcję rank.messages do wektora train.paths, aby wygenerować listę wektorów zawierających funkcje i priorytetową pozycję dla każdego e-maila. Następnie wykonujemy podstawowe czynności porządkowe w celu przekształcenia tej listy w macierz. Na koniec przekształcamy tę macierz w ramkę danych z nazwami kolumn i odpowiednio sformatowanymi wektorami. Możesz zauważyć, że train.ranks <- lapply (train.paths, rank.message) powoduje, że R rzuca ostrzeżenie. To nie jest problem, ale po prostu wynik sposobu, w jaki zbudowaliśmy ranking. Możesz zawinąć lapply w funkcję suppressWarnings, jeśli chcesz wyłączyć to ostrzeżenie. Wykonujemy teraz krytyczny krok obliczania wartości progowej dla priorytetowego e-maila. Jak widać, w tym ćwiczeniu postanowiliśmy wykorzystać medianę wartości rangi jako nasz próg. Oczywiście moglibyśmy użyć wielu innych statystyk podsumowujących dla tego progu. Ponieważ do określenia tego progu nie używamy wcześniejszych przykładów, jak należy klasyfikować wiadomości e-mail, wykonujemy zadanie, które tak naprawdę nie jest standardowym rodzajem nadzorowanego uczenia się. Ale wybraliśmy medianę z dwóch zasadniczych powodów. Po pierwsze, jeśli zaprojektowaliśmy dobrego rangera, to rangi powinny mieć płynną dystrybucję, przy czym większość e-maili ma niską rangę, a wiele mniej e-maili ma wysoką rangę. Szukamy „ważnych wiadomości e-mail”, tj. Takich, które są najbardziej wyjątkowe lub w przeciwieństwie do normalnego przepływu ruchu pocztowego. Będą to wiadomości e-mail w prawym ogonie rozkładu rang. W takim przypadku wartości większe niż mediana będą wartościami nieco większymi niż typowa ranga. Intuicyjnie właśnie tak myślimy o poleceniu priorytetowego e-maila: wybranie tych, których ranga jest większa niż typowy e-mail. Po drugie, wiemy, że dane testowe będą zawierać wiadomości e-mail, które zawierają dane, które nie pasują do niczego w naszych danych szkoleniowych. Ciągle napływają nowe e-maile, ale biorąc pod uwagę naszą konfigurację, nie mamy możliwości zaktualizowania naszego rankingu. W związku z tym możemy chcieć mieć regułę dotyczącą priorytetu, która jest bardziej obejmująca niż wyłączna. Jeśli nie, możemy pominąć wiadomości, które są tylko częściowo zgodne z naszymi funkcjami. W ostatnim kroku dodajemy nową kolumnę binarną Priority do train.ranks.df, wskazując, czy e-mail będzie polecany jako priorytet przez nasz ranking.

Rysunek

pokazuje szacunkową gęstość rang obliczoną na podstawie naszych danych treningowych. Pionowa linia przerywana to próg środkowy, który wynosi około 24. Jak widać, nasze szeregi są bardzo ciężkie, dlatego stworzyliśmy ranking, który dobrze radzi sobie z danymi treningowymi. Widzimy również, że mediana progu jest bardzo obejmująca, a znaczna część gęstości opadania jest uwzględniona jako priorytetowy e-mail. Znowu dzieje się to celowo. Znacznie mniej inkluzywnym progiem byłoby użycie standardowego odchylenia rozkładów, które możemy obliczyć za pomocą sd (train.ranks.df $ Rank). Standardowe odchylenie wynosi około 90, co prawie dokładnie wykluczałoby wiadomości e-mail poza ogonem.

Teraz obliczymy wartości rang dla wszystkich e-maili w naszym zestawie testowym. Ten proces przebiega dokładnie tak samo, jak w przypadku naszych danych szkoleniowych, więc aby zaoszczędzić miejsce, nie będziemy tutaj odtwarzać kodu. Aby zobaczyć kod, zapoznaj się z plikiem code / priority_inbox.R zawartym w tej części, zaczynając od około linii 308. Po obliczeniu rang w przypadku danych testowych możemy porównać wyniki i zobaczyć, jak dobrze radził sobie nasz ranking w nowych e-mailach.

Poniższy rysunek pokrywa gęstość szeregów z danych testowych dotyczących gęstości na powyższym rysunku.

Ta ilustracja jest bardzo pouczająca pod względem jakości naszego rankingu. Po pierwsze, zauważamy, że gęstość danych testowych jest znacznie większa na bardzo niskich końcach rozkładów. Oznacza to, że jest o wiele więcej wiadomości e-mail o niskiej randze. Ponadto oszacowanie gęstości testu jest znacznie mniej płynne niż dane treningowe. Jest to dowód na to, że dane testowe obejmują wiele obserwacji, których nie ma w naszych danych treningowych. Ponieważ te obserwacje nie pasują do niczego w naszych danych treningowych, ranking skutecznie ignoruje te informacje. Chociaż jest to problematyczne, unikamy katastrof, ponieważ zastosowaliśmy próg obejmujący priorytetowe wiadomości e-mail. Zauważ, że nadal istnieje rozsądna gęstość danych testowych po prawej stronie linii progowej. Oznacza to, że nasz ranking nadal mógł znaleźć e-maile, które mogą polecić jako ważne z danych testowych. Na koniec sprawdźmy, które z nich ,e-maile,  nasz ranking przesunął na szczyt. Tworzenie rankingowego tego typu cechuje nieodłączna „niepoznawalna” jakość. W całym tym ćwiczeniu przyjęliśmy założenia dotyczące każdej funkcji uwzględnionej w naszym projekcie i próbowaliśmy je intuicyjnie uzasadnić. Jednak nigdy nie możemy poznać „prawdziwej prawdy” dotyczącej tego, jak dobrze radzi sobie nasz ranking, ponieważ nie możemy wrócić i zapytać użytkownika, dla kogo te e-maile zostały wysłane, czy zamówienie jest dobre, czy może sens. W ćwiczeniu dotyczącym klasyfikacji znaliśmy etykiety dla każdego e-maila w zestawie szkoleniowym i testowym, dzięki czemu mogliśmy wyraźnie zmierzyć, jak dobrze klasyfikator radzi sobie, stosując macierz nieporozumień. W takim przypadku nie możemy, ale możemy spróbować wywnioskować, jak dobrze radzi sobie ranking, patrząc na wyniki. To właśnie czyni to ćwiczenie czymś innym niż standardowe nadzorowane uczenie się.

Poniżej pokazujemy 40 najnowszych e-maili w danych testowych, które zostały ocenione jako priorytetowe przez nasz ranking. Tabela ma naśladować to, co możesz zobaczyć w skrzynce odbiorczej e-maila, jeśli korzystałeś z tego rankingu w celu priorytetowego sortowania skrzynki odbiorczej nad wiadomościami e-mail, z dodanymi informacjami o randze wiadomości e-mail. Jeśli możesz usprawiedliwić nieco dziwne lub kontrowersyjne nagłówki tematów, zbadamy te wyniki, aby sprawdzić, w jaki sposób ranking grupuje e-maile.

Najbardziej zachęcające w tych wynikach jest to, że ranking bardzo dobrze grupuje wątki. Możesz zobaczyć kilka przykładów tego w tabeli, gdzie e-maile z tego samego wątku zostały oznaczone jako priorytetowe i są zgrupowane razem. Ponadto ranking wydaje się przyznawać odpowiednio wysokie rangi e-mailom od częstych nadawców, jak ma to miejsce w przypadku nadawców odstających, takich jak tomwhore@slack.net i yyyy@spamassassin.taint.org. Wreszcie, i być może najbardziej zachęcające, osoba oceniająca priorytetowo traktuje wiadomości, które nie były obecne w danych szkoleniowych. W rzeczywistości tylko 12 z 85 wątków w danych testowych oznaczonych jako priorytetowe są kontynuacjami danych treningowych (około 14%). To pokazuje, że nasz ranking jest w stanie zastosować obserwacje z danych treningowych do nowych wątków w danych testowych i wydawać zalecenia bez aktualizacji. To jest bardzo dobre! W tej części przedstawiliśmy ideę wyjścia poza zestaw funkcji tylko jeden element do bardziej złożonego modelu z wieloma funkcjami. W rzeczywistości osiągnęliśmy dość trudne zadanie, a mianowicie zaprojektowanie modelu rankingu dla wiadomości e-mail, gdy widzimy tylko połowę transakcji. Opierając się na interakcjach społecznościowych, aktywności wątków i wspólnych terminach, określiliśmy cztery interesujące cechy i wygenerowaliśmy pięć ramek danych ważących do przeprowadzenia rankingu. Wyniki, które właśnie zbadaliśmy, były zachęcające, choć bez gruntownej prawdy, trudne do jednoznacznego przetestowania. Mając za sobą ostatnie dwa rozdziały, przeszedłeś przez stosunkowo prosty przykład nadzorowanego uczenia się stosowanego do przeprowadzania klasyfikacji spamu i bardzo podstawowej formy rankingu opartego na heurystyce. Jesteś gotowy na przejście do uczenia statystycznego: regresji.

Ważenie z aktywności wątku e-mail

Drugą funkcją, którą chcemy wyodrębnić z danych, jest aktywność wątku e-mail. Jak już wspomniano, nie mamy możliwości dowiedzieć się, czy użytkownik, dla którego budujemy ranking, odpowiedział na e-maile, ale możemy grupować wiadomości według ich wątków i mierzyć ich aktywność od początku. Ponownie, naszym założeniem w budowaniu tej funkcji jest to, że czas jest ważny, a zatem wątki, które wysyłają więcej wiadomości w krótkim czasie, są bardziej aktywne, a zatem ważniejsze. Wiadomości e-mail w naszym zestawie danych nie zawierają określonych identyfikatorów wątków, ale logicznym sposobem na identyfikację wątków w danych szkoleniowych jest poszukiwanie wiadomości e-mail ze wspólnym tematem. Oznacza to, że jeśli znajdziemy temat zaczynający się od „re:”, wówczas wiemy, że jest to część jakiegoś wątku. Gdy widzimy taki komunikat, możemy rozejrzeć się za innymi wiadomościami w tym wątku i zmierzyć aktywność.

find.threads <- function(email.df) {

response.threads <- strsplit(email.df$Subject, “re: “)

is.thread <- sapply(response.threads, function(subj) ifelse(subj[1] == “”, TRUE,

FALSE))

threads <- response.threads[is.thread]

senders <- email.df$From.EMail[is.thread]

threads <- sapply(threads, function(t) paste(t[2:length(t)], collapse=”re: “))

return(cbind(senders,threads))

}

threads.matrix<-find.threads(priority.train)

Właśnie to próbuje zrobić funkcja find.threads z naszymi danymi treningowymi. Jeśli podzielimy każdy przedmiot w naszych danych treningowych przez „re:”, możemy znaleźć wątki, szukając wektorów znaków podzielonych z pustym znakiem jako pierwszym elementem. Kiedy już wiemy, które obserwacje w danych szkoleniowych są częścią wątków, możemy wyodrębnić nadawców z tych wątków i tematu. Macierz wyników będzie zawierać wszystkich nadawców i początkowy temat wątku w naszych danych szkoleniowych.

email.thread <- function(threads.matrix) {

senders <- threads.matrix[, 1]

senders.freq <- table(senders)

senders.matrix <- cbind(names(senders.freq), senders.freq, log(senders.freq + 1))

senders.df <- data.frame(senders.matrix, stringsAsFactors=FALSE)

row.names(senders.df) <- 1:nrow(senders.df)

names(senders.df) <- c(“From.EMail”, “Freq”, “Weight”)

senders.df$Freq <- as.numeric(senders.df$Freq)

senders.df$Weight <- as.numeric(senders.df$Weight)

return(senders.df)

}

senders.df <- email.thread(threads.matrix)

Następnie stworzymy wagę na podstawie nadawców, którzy są najbardziej aktywni w wątkach. Będzie to uzupełnienie ważenia opartego na wolumenie, które właśnie wykonaliśmy dla całego zestawu danych, ale teraz skupimy się tylko na tych nadawcach obecnych w wątku. Matrix. Funkcja email.thread weźmie inputs.matrix i wygeneruje to wtórne ważenie oparte na wolumenie. Będzie to bardzo podobne do tego, co zrobiliśmy w poprzedniej sekcji, ale tym razem użyjemy funkcji tabeli do zliczenia częstotliwości nadawców w wątkach. Odbywa się to po prostu w celu pokazania innej metody wykonania tego samego obliczenia na macierzy w R, a nie na ramce danych przy użyciu plyr. Większość tej funkcji po prostu wykonuje porządek w ramce danych senders.df, ale zauważ, że ponownie używamy ważenia logu naturalnego. Jako ostatni element funkcji wątku e-mail utworzymy ważenie na podstawie wątków, o których wiemy, że są aktywne. Zidentyfikowaliśmy już wszystkie wątki w naszych danych szkoleniowych i stworzyliśmy ważenie na podstawie warunków w tych wątkach. Teraz chcemy wziąć tę wiedzę i nadać dodatkową wagę znanym wątkom, które są również aktywne. Zakłada się tutaj, że jeśli znamy już wątki, oczekujemy, że użytkownik przywiąże większą wagę do wątków, które są bardziej aktywne.

get.threads <- function(threads.matrix, email.df) {

threads <- unique(threads.matrix[, 2])

thread.counts <- lapply(threads, function(t) thread.counts(t, email.df))

thread.matrix <- do.call(rbind, thread.counts)

return(cbind(threads, thread.matrix))

}

thread.counts <- function(thread, email.df) {

thread.times <- email.df$Date[which(email.df$Subject == thread

| email.df$Subject == paste(“re:”, thread))]

freq <- length(thread.times)

min.time <- min(thread.times)

max.time <- max(thread.times)

time.span <- as.numeric(difftime(max.time, min.time, units=”secs”))

if(freq < 2) {

return(c(NA,NA,NA))

}

else {

trans.weight <- freq / time.span

log.trans.weight <- 10 + log(trans.weight, base=10)

return(c(freq,time.span, log.trans.weight))

}

}

thread.weights <- get.threads(threads.matrix, priority.train)

thread.weights <- data.frame(thread.weights, stringsAsFactors=FALSE)

names(thread.weights) <- c(“Thread”,”Freq”,”Response”,”Weight”)

thread.weights$Freq <- as.numeric(thread.weights$Freq)

thread.weights$Response <- as.numeric(thread.weights$Response)

thread.weights$Weight <- as.numeric(thread.weights$Weight)

thread.weights <- subset(thread.weights, is.na(thread.weights$Freq) == FALSE)

Korzystając z właśnie utworzonego wątku threads.matrix, wrócimy do danych szkoleniowych, aby znaleźć wszystkie wiadomości e-mail w każdym wątku. W tym celu tworzymy funkcję get.threads, która przyjmuje argumenty threads.matrix i nasze dane szkoleniowe. Za pomocą unikalnego polecenia tworzymy wektor wszystkich tematów wątków w naszych danych. Teraz musimy wziąć te informacje i zmierzyć aktywność każdego wątku. Wykona to funkcja thread.counts. Wykorzystując temat wątku i dane szkoleniowe jako parametry, zbierzemy wszystkie daty i znaczniki czasu dla wszystkich wiadomości e-mail pasujących do wątku w wektorze thread.times. Możemy zmierzyć, ile e-maili otrzymano w danych szkoleniowych dla tego wątku, mierząc długość thread.times.

Wreszcie, aby zmierzyć poziom aktywności, musimy wiedzieć, jak długo wątek istniał w naszych danych szkoleniowych. Domniemane jest obcinanie po obu stronach tych danych. Oznacza to, że mogą istnieć wiadomości e-mail otrzymane w wątku przed rozpoczęciem naszych danych szkoleniowych lub po zakończeniu gromadzenia danych. Nic nie możemy na to poradzić, więc weźmiemy minimalną i maksymalną datę / czas dla każdego wątku i wykorzystamy je do pomiaru przedziału czasu. Funkcja difftime obliczy czas, jaki upłynął między dwoma obiektami POSIX w niektórych jednostkach. W naszym przypadku chcemy mieć najmniejszą możliwą jednostkę: sekundy.

Z powodu obcięcia może być tak, że obserwujemy tylko jedną wiadomość w wątku. Może to być wątek, który zakończył się w momencie, gdy dane szkolenia zostały zebrane lub rozpoczęty po zakończeniu gromadzenia. Zanim będziemy mogli utworzyć wagę w oparciu o aktywność w czasie wątku, musimy oflagować te wątki, dla których mamy tylko jedną wiadomość. Ifstatement na końcu Thread.counts sprawdza to i zwraca wektor NA, jeśli bieżący wątek ma tylko jedną wiadomość. Później wykorzystamy to do wyodrębnienia ich z danych ważenia opartych na aktywności. Ostatnim krokiem jest stworzenie wagi dla tych wiadomości, które możemy zmierzyć. Zaczynamy od obliczenia stosunku wiadomości do sekund, które upłynęły w wątku. Więc jeśli wiadomość była wysyłana co sekundę w danym wątku, wynikiem będzie jeden. Oczywiście w praktyce liczba ta jest znacznie niższa: średnia liczba wiadomości w każdym wątku wynosi około 4,5, a średni czas, który upłynął, wynosi około 31 000 sekund (8,5 godziny). Biorąc pod uwagę te skale, zdecydowana większość naszych stosunków to małe ułamki. Tak jak poprzednio, nadal chcemy przekształcić te wartości za pomocą logarytmów, ale ponieważ mamy do czynienia z wartościami ułamkowymi, spowoduje to liczby ujemne. W naszym schemacie nie możemy mieć ujemnej wartości wagowej, więc będziemy musieli przeprowadzić dodatkową transformację, która formalnie nazywana jest transformacją afiniczną. Transformacja afiniczna jest po prostu liniowym ruchem punktów w przestrzeni. Wyobraź sobie kwadrat narysowany na kawałku papieru milimetrowego. Jeśli chcesz przesunąć ten kwadrat w inne miejsce na papierze, możesz to zrobić, definiując funkcję, która przesunęła wszystkie punkty w tym samym kierunku. To jest afiniczna transformacja. Aby uzyskać nieujemne wagi w log.trans.weight, po prostu dodamy 10 do wszystkich wartości przekształconych w log. Zapewni to, że wszystkie wartości będą miały odpowiednią wagę z wartością dodatnią. Tak jak poprzednio, po wygenerowaniu danych wagi za pomocą get.threads i thread.counts, wykonamy podstawowe czynności porządkowe w ramce danych thread.weights, aby zachować spójność nazewnictwa z innymi ramkami danych wagi. W ostatnim kroku używamy funkcji podzestawu, aby usunąć wszystkie wiersze, które odnoszą się do wątków zawierających tylko jeden komunikat (tj. Obcięte wątki). Możemy teraz użyć head (thread.weights) do sprawdzenia wyników

head(thread.weights)

Waga Częstotliwość Odpowiedź Waga

1 please help a newbie compile mplayer 🙂 4 42309 5.975627

2 prob. w/ install/uninstall 4 23745 6.226488

3 http://apt.nixia.no/ 10 265303 5.576258

4 problems with ‘apt-get -f install’ 3 55960 5.729244

5 problems with apt update 2 6347 6.498461

6 about apt, kernel updates and dist-upgrade 5 240238 5.318328

Pierwsze dwa wiersze są dobrymi przykładami tego, jak ten schemat ważenia wycenia aktywność wątku. W obu tych wątkach pojawiły się cztery wiadomości. The prob. W / Wątek instalacji / deinstalacji znajduje się jednak w danych przez około pół sekundy. Biorąc pod uwagę nasze założenia, uważalibyśmy, że ten wątek jest ważniejszy i dlatego powinien mieć większą wagę. W tym przypadku przekazujemy wiadomości z tego wątku o masie około 1,04 razy większej niż wiadomości z pomocy nowicjuszowi w skompilowaniu wątku mplayer :-). Może się to wydawać rozsądne lub nie, i stanowi część wiedzy w zakresie projektowania i stosowania takiego schematu do ogólnego problemu. Być może w tym przypadku nasz użytkownik nie doceniałby rzeczy w ten sposób, podczas gdy inni mogą, ale ponieważ chcemy ogólnego rozwiązania, musimy zaakceptować konsekwencje naszych założeń.

term.counts <- funkcja (term.vec, kontrola) {

vec.corpus <- Corpus (VectorSource (term.vec))

vec.tdm <- TermDocumentMatrix (vec.corpus, control = control)

return (rowSums (as.matrix (vec.tdm)))

}

thread.terms <- term.counts (thread.weights $ Thread,

control = list (stopwords = stopwords ()))

thread.terms <- names (thread.terms)

term.weights <- sapply (thread.terms,

function (t) mean (thread.weights $ Weight [grepl (t, thread.weights $ Thread,

naprawiono = PRAWDA)]))

term.weights <- data.frame (lista (Term = imiona (term.weights), Weight = term.weights),

stringsAsFactors = FALSE, row.names = 1: length (term.weights))

Ostateczne dane ważenia, które uzyskamy z wątków, są częstymi terminami w tych wątkach. Podobnie do tego, co zrobiliśmy wcześniej, tworzymy ogólną funkcję o nazwie term.counts, która pobiera wektor terminów i listę kontrolną TermDocumentMatrix w celu wygenerowania TDM i wyodrębnienia liczby terminów we wszystkich wątkach. Założeniem przy tworzeniu tych danych ważenia jest to, że częste terminy w aktywnych wątkach są ważniejsze niż terminy, które są albo rzadsze, albo nie są aktywne w wątkach. Staramy się dodawać jak najwięcej informacji do naszego rankingu, aby stworzyć bardziej szczegółowe rozwarstwienie wiadomości e-mail. Aby to zrobić, zamiast szukać tylko wątków już aktywnych, chcemy również obciążyć wątki, które „wyglądają” jak poprzednio aktywne wątki, a ważenie terminów jest jednym ze sposobów.

msg.terms <- term.counts (priorytet.train $ Wiadomość,

control = list (stopwords = stopwords (),

removePunctuation = TRUE, removeNumbers = TRUE))

msg.weights <- data.frame (lista (Termin = nazwy (msg.terms),

Weight = log (msg.terms, base = 10)), stringsAsFactors = FALSE,

row.names = 1: length (msg.terms))

msg. wagi <- podzbiór (msg. wagi, waga> 0)

Ostateczne dane ważenia, które zbudujemy, są oparte na częstotliwości terminów we wszystkich wiadomościach e-mail w danych szkoleniowych. Będzie to przebiegać prawie identycznie jak w przypadku naszej metody liczenia terminów w procedurze klasyfikacji spamu; tym razem jednak przypiszemy wagi przekształcone logarytmicznie na podstawie tych liczb. Podobnie jak w przypadku ważenia termin-częstotliwość dla tematów wątków, domyślnym założeniem w ramce danych msg.weights jest to, że nowa wiadomość, która wygląda jak inne wiadomości, które widzieliśmy wcześniej, jest ważniejsza niż wiadomość, która jest dla nas całkowicie obca. Mamy teraz pięć ramek danych wagi, z którymi możemy przeprowadzić nasz ranking! Obejmuje to from.weight (funkcja aktywności społecznościowej), senders.df (aktywność nadawcy w wątkach),

thread.weights (aktywność wiadomości wątkowych), term.weights (terminy z aktywnych wątków) i msg.weights (wspólne terminy we wszystkich wiadomościach e-mail). Jesteśmy teraz gotowi do uruchomienia naszych danych treningowych przez ranking, aby znaleźć próg oznaczenia wiadomości jako ważnej.

Schemat ważenia logarytmicznego

Odpowiedź przychodzi w przekształcaniu skal. Musimy sprawić, by relacje liczbowe między jednostkami w naszym zestawie funkcji były mniej ekstremalne. Jeśli porównamy bezwzględne liczby częstotliwości, wiadomość e-mail od tim.one@comcast.ent będzie ważona jako 15 razy ważniejsza niż wiadomość e-mail od przeciętnego nadawcy. Jest to bardzo problematyczne, ponieważ będziemy chcieli ustalić próg bycia wiadomością priorytetową lub nie, w oparciu o zakres wartości masy wytworzonych przez naszego rankingowego na etapie uczenia się. Przy tak ekstremalnym skośności nasz próg będzie albo o wiele za niski, albo o wiele za wysoki, dlatego musimy przeskalować jednostki, aby uwzględnić charakter naszych danych. To prowadzi nas do logarytmów i transformacji logarytmów. Prawdopodobnie znasz logarytmy z matematyki elementarnej, ale jeśli nie, koncepcja jest dość prosta. Logarytm jest funkcją, która zwraca wartość wykładnika, która spełniałaby równanie, w którym liczba podstawowa podniesiona do tego wykładnika jest równa liczbie podanej funkcji logarytmicznej. Wartość podstawowa w transformacji logarytmów ma kluczowe znaczenie. Jako hakerzy znamy myślenie o rzeczy o podstawie dwa  lub binarnej. Możemy bardzo łatwo skonstruować logarytmiczną transformację podstawy dwa. W takim przypadku rozwiązalibyśmy równanie wartości wykładniczej, gdzie wartość wejściowa jest równa dwóm podniesionym do tego wykładnika. Na przykład, jeśli przekształcimy 16 za pomocą logarytmu base-two, będzie to cztery, ponieważ dwa podniesione do czwartej potęgi wynoszą 16. Zasadniczo „odwracamy” wykładniczy, więc tego rodzaju transformacje działają najlepiej, gdy dane pasują do takich funkcja. Dwie najczęstsze transformacje logów to tak zwany log naturalny i transformacja log-base-10. W pierwszym przypadku podstawą jest specjalna wartość e, która jest stałą niewymierną  (jak pi) równą około 2,718. Nazwa logarytm naturalny jest często oznaczana ln. Szybkości zmian tej stałej są bardzo często obserwowane w przyrodzie, a w rzeczywistości wyprowadzenie może być wykonane geometrycznie jako funkcja kątów wewnątrz koła. Prawdopodobnie bardzo dobrze znasz kształty i relacje zgodne z naturalnym dziennikiem, chociaż może nie pomyślałeś o nich w tych kategoriach. Rysunek ilustruje naturalną spiralę logarytmiczną, którą można zaobserwować w wielu naturalnie występujących zjawiskach. Niektóre przykłady obejmują wewnętrzną strukturę skorupy łodzika i spiralne wiatry huraganu (lub tornada).

Nawet rozproszenie cząstek międzygwiezdnych w naszej galaktyce następuje po naturalnej spirali logarytmicznej. Ponadto wiele otworów w ustawieniach profesjonalnych kamer różni się w zależności od logarytmów naturalnych. Biorąc pod uwagę intymny związek między tą wartością a wieloma naturalnie występującymi zjawiskami, jest to świetna funkcja do przeskalowywania danych społecznościowych – jako aktywności e-mail – która ma charakter wykładniczy. Alternatywnie, transformacja logarytmiczna podstawa-10, często oznaczana log10, zastępuje wartość e w naturalnej transformacji logarytmicznej 10. Biorąc pod uwagę, jak działa transformacja logarytmiczna, wiemy, że transformacja log-podstawa 10 zmniejszy duże wartości do znacznie mniejszych te niż naturalny dziennik. Na przykład transformacja logarytmu base-10 wynosząca 1000 wynosi 3, ponieważ 10 podniesiona do potęgi trzeciej to 1000, podczas gdy logarytm naturalny wynosi około 6,9. Dlatego sensowne jest użycie transformacji log-base-10, gdy nasze dane są skalowane według bardzo dużego wykładnik. Sposoby, w jakie obie te opcje przekształcą nasze dane dotyczące liczby wiadomości e-mail, pokazano na rysunku

Na tym rysunku widzimy, że liczba wiadomości e-mail wysyłanych przez użytkowników w danych szkoleniowych jest dość gwałtowna. Przekształcając te wartości za pomocą logarytmu naturalnego i log-base-10, znacznie spłaszczamy tę linię. Jak wiemy, logarytm base-10 znacznie przekształca wartości, podczas gdy log naturalny wciąż zapewnia pewne zmiany, które pozwolą nam wyciągnąć znaczące wagi z tych danych treningowych. Z tego powodu użyjemy dziennika naturalnego, aby zdefiniować wagę naszej funkcji objętości e-maila.

Jak już to zrobiliśmy i jak szczegółowo wyjaśniliśmy wcześniej, zawsze dobrze jest eksplorować dane wizualnie podczas pracy przez jakikolwiek problem z uczeniem maszynowym. Chcemy wiedzieć, w jaki sposób wszystkie obserwacje w naszym zestawie funkcji odnoszą się do siebie w celu uzyskania najlepszych prognoz. Często najlepszym sposobem na to jest wizualizacja danych.

from.weight <- transform (from.weight, Weight = log (Freq + 1))

Na koniec przypomnij sobie z matematyki w szkole swoje zasady dotyczące wykładników. Wszystko podniesione do zera zawsze jest równe jeden. Jest to bardzo ważne, o czym należy pamiętać przy stosowaniu transformacji logarytmicznej w schemacie ważenia, ponieważ każda obserwacja równa jednej zostanie przekształcona na zero. Jest to problematyczne w schemacie ważenia, ponieważ pomnożenie innych wag przez zero spowoduje wyzerowanie całej wartości. Aby tego uniknąć, zawsze dodajemy jedną do wszystkich obserwacji przed pobraniem logów.

W R jest funkcja o nazwie log1p, która oblicza log (1 + p), ale dla celów uczenia się i wyrażania się, dodamy to „ręcznie”.

Biorąc pod uwagę przeskalowanie, nie wpływa to na nasze wyniki i utrzymuje wszystkie wagi powyżej zera. W tym przypadku używamy domyślnej wartości podstawowej dla funkcji dziennika, która jest dziennikiem naturalnym.

Dla naszych celów nigdy nie będziemy mieć obserwacji w naszym zestawie funkcji, która jest równa zero, ponieważ liczymy rzeczy. Jeśli nie ma żadnych obserwacji, to po prostu nie wprowadza naszych danych treningowych. W niektórych przypadkach nie będzie to jednak prawdą i możesz mieć zerowe obserwacje w swoich danych. Dziennik zerowy jest niezdefiniowany, a jeśli spróbujesz go obliczyć w R, zwróci specjalną wartość -Inf. Często występujące w danych -Inf powodują, że wszystko się wysadza.

Tworzenie systemu ważenia dla rankingu

Zanim będziemy mogli wdrożyć system ważenia, musimy krótko przejść do dygresji, aby omówić wagi. Zastanów się przez chwilę nad własną działalnością e-mail. Czy regularnie kontaktujesz się z mniej więcej tymi samymi ludźmi? Czy wiesz o tym, ile e-maili otrzymujesz w ciągu jednego dnia? Ile e-maili otrzymujesz od nieznajomych w ciągu tygodnia? Jeśli jesteś podobny do nas i podejrzewamy, podobnie jak większość innych osób, twoja aktywność e-mailowa jest z grubsza zgodna z stereotypem 80/20. Oznacza to, że około 80% Twojej aktywności e-mailowej jest prowadzone z około 20% całkowitej liczby osób w Twojej książce adresowej. Dlaczego to takie ważne? Musimy być w stanie opracować schemat ważenia obserwacji niektórych cech w naszych danych, ale ze względu na potencjalne różnice w skali między tymi obserwacjami nie możemy ich bezpośrednio porównać. Dokładniej, nie możemy bezpośrednio porównać ich wartości bezwzględnych. Weźmy dane treningowe, które właśnie zakończyliśmy analizować. Wiemy, że jedną z funkcji, które dodajemy do naszego rankingu, jest przybliżenie interakcji społecznościowych na podstawie liczby wiadomości e-mail otrzymanych z każdego adresu w naszych danych szkoleniowych.

from.weight <–ddply (Prior.train,. (From.EMail), podsumowanie, Freq = długość (Temat))

Aby rozpocząć badanie, w jaki sposób to skalowanie wpływa na naszą pierwszą funkcję, będziemy musieli policzyć, ile razy każdy adres e-mail pojawia się w naszych danych. Aby to zrobić, użyjemy pakietu plyr, który już załadowaliśmy jako zależność dla ggplot2. Jeśli pracowałeś z przykładem z początkowych wpisów, widziałeś już plyra w akcji. W skrócie, rodzina funkcji w plyr służy do rąbania danych na mniejsze kwadraty i kostki, abyśmy mogli operować tymi kawałkami jednocześnie. (Jest to bardzo podobne do popularnego paradygmatu Map-Reduce stosowanego w wielu środowiskach analizy danych na dużą skalę.) Tutaj wykonamy bardzo proste zadanie: znajdź wszystkie kolumny z pasującymi adresami w kolumnach From.E-mail i policz je. Aby to zrobić, używamy funkcji ddply, która działa na ramkach danych, z naszymi danymi szkoleniowymi. Składnia każe nam zdefiniować grupowanie danych, które chcemy najpierw – którym w tym przypadku jest tylko wymiar From.EMail – a następnie operację, którą przejdziemy nad tym grupowaniem. Tutaj użyjemy opcji podsumowania, aby utworzyć nową kolumnę o nazwie Freq z informacjami o liczbie. Możesz użyć polecenia head (from.weight), aby sprawdzić wyniki.

W takim przypadku operacja prosi o długość wektora w kolumnie Temat w pociętej ramce danych, ale tak naprawdę moglibyśmy użyć dowolnej nazwy kolumny z danych szkoleniowych, aby uzyskać ten sam wynik, ponieważ wszystkie kolumny spełniające nasze kryteria będą miały ta sama długość. Zapoznanie się z używaniem plyr do manipulowania danymi będzie dla Ciebie niezwykle cenne i bardzo polecamy dokumentację autora pakietu.