Warning: file_get_contents(https://nbviewer.jupyter.org/url/github.com/sarthakbabbar3/Sentiment_Analysis_IMDB/blob/master/Sentiment%20analysis%20imdb.ipynb): failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found in /var/www/datasciencehack/wp-content/plugins/nbconvert-master/nbconvert.php on line 87
Warning: DOMDocument::loadHTML(): Empty string supplied as input in /var/www/datasciencehack/wp-content/plugins/nbconvert-master/nbconvert.php on line 121
Warning: file_get_contents(https://api.github.com/repos/sarthakbabbar3/Sentiment_Analysis_IMDB/commits/master?path=Sentiment%20analysis%20imdb.ipynb&page=1): failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found in /var/www/datasciencehack/wp-content/plugins/nbconvert-master/nbconvert.php on line 38
Language Detecting with sklearn by determining Letter Frequencies
/0 Comments/in Artificial Intelligence, Big Data, Business Analytics, Business Intelligence, Data Mining, Data Science, Data Science at the Command Line, Data Science Hack, Hacking, Machine Learning, Main Category, Python, Python, Text Mining, Tools, Tutorial, Use Case, Use Cases, Visualization /by Christopher KippOf course, there are better and more efficient methods to detect the language of a given text than counting its lettes. On the other hand this is a interesting little example to show the impressing ability of todays machine learning algorithms to detect hidden patterns in a given set of data.
For example take the sentence:
“Ceci est une phrase française.”
It’s not to hard to figure out that this sentence is french. But the (lowercase) letters of the same sentence in a random order look like this:
“eeasrsçneticuaicfhenrpaes”
Still sure it’s french? Regarding the fact that this string contains the letter “ç” some people could have remembered long passed french lessons back in school and though might have guessed right. But beside the fact that the french letter “ç” is also present for example in portuguese, turkish, catalan and a few other languages, this is still a easy example just to explain the problem. Just try to guess which language might have generated this:
“ogldviisnntmeyoiiesettpetorotrcitglloeleiengehorntsnraviedeenltseaecithooheinsnstiofwtoienaoaeefiitaeeauobmeeetdmsflteightnttxipecnlgtetgteyhatncdisaceahrfomseehmsindrlttdthoaranthahdgasaebeaturoehtrnnanftxndaeeiposttmnhgttagtsheitistrrcudf”
While this looks simply confusing to the human eye and it seems practically impossible to determine the language it was generated from, this string still contains as set of hidden but well defined patterns from which the language could be predictet with almost complete (ca. 98-99%) certainty.
First of all, we need a set of texts in the languages our model should be able to recognise. Luckily with the package NLTK there comes a big set of example texts which actually are protocolls of the european parliament and therefor are publicly availible in 11 differen languages:
- Danish
- Dutch
- English
- Finnish
- French
- German
- Greek
- Italian
- Portuguese
- Spanish
- Swedish
Because the greek version is not written with the latin alphabet, the detection of the language greek would just be too simple, so we stay with the other 10 languages availible. To give you a idea of the used texts, here is a little sample:
“Resumption of the session I declare resumed the session of the European Parliament adjourned on Friday 17 December 1999, and I would like once again to wish you a happy new year in the hope that you enjoyed a pleasant festive period.
Although, as you will have seen, the dreaded ‘millennium bug’ failed to materialise, still the people in a number of countries suffered a series of natural disasters that truly were dreadful.”
Train and Test
The following code imports the nessesary modules and reads the sample texts from a set of text files into a pandas.Dataframe object and prints some statistics about the read texts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
from pathlib import Path import random from collections import Counter, defaultdict import numpy as np import pandas as pd from sklearn.neighbors import * from matplotlib import pyplot as plt from mpl_toolkits import mplot3d %matplotlib inline def read(file): '''Returns contents of a file''' with open(file, 'r', errors='ignore') as f: text = f.read() return text def load_eu_texts(): '''Read texts snipplets in 10 different languages into pd.Dataframe load_eu_texts() -> pd.Dataframe The text snipplets are taken from the nltk-data corpus. ''' basepath = Path('/home/my_username/nltk_data/corpora/europarl_raw/langs/') df = pd.DataFrame(columns=['text', 'lang', 'len']) languages = [None] for lang in basepath.iterdir(): languages.append(lang.as_posix()) t = '\n'.join([read(p) for p in lang.glob('*')]) d = pd.DataFrame() d['text'] = '' d['text'] = pd.Series(t.split('\n')) d['lang'] = lang.name.title() df = df.append(d.copy(), ignore_index=True) return df def clean_eutextdf(df): '''Preprocesses the texts by doing a set of cleaning steps clean_eutextdf(df) -> cleaned_df ''' # Cuts of whitespaces a the beginning and and df['text'] = [i.strip() for i in df['text']] # Generate a lowercase Version of the text column df['ltext'] = [i.lower() for i in df['text']] # Determining the length of each text df['len'] = [len(i) for i in df['text']] # Drops all texts that are not at least 200 chars long df = df.loc[df['len'] > 200] return df # Execute the above functions to load the texts df = clean_eutextdf(load_eu_texts()) # Print a few stats of the read texts textline = 'Number of text snippplets: ' + str(df.shape[0]) print('\n' + textline + '\n' + ''.join(['_' for i in range(len(textline))])) c = Counter(df['lang']) for l in c.most_common(): print('%-25s' % l[0] + str(l[1])) df.sample(10) |
1 2 3 4 5 6 7 8 9 10 11 12 |
Number of text snippplets: 56481 ________________________________ French 6466 German 6401 Italian 6383 Portuguese 6147 Spanish 6016 Finnish 5597 Swedish 4940 Danish 4914 Dutch 4826 English 4791 |
1 2 3 4 5 6 7 8 9 10 11 |
lang len text ltext 135233 Finnish 346 Vastustan sitä , toisin kuin tämän parlamentin... vastustan sitä , toisin kuin tämän parlamentin... 170400 Danish 243 Desuden ødelægger det centraliserede europæisk... desuden ødelægger det centraliserede europæisk... 85466 Italian 220 In primo luogo , gli accordi di Sharm el-Sheik... in primo luogo , gli accordi di sharm el-sheik... 15926 French 389 Pour ce qui est concrètement du barrage de Ili... pour ce qui est concrètement du barrage de ili... 195321 English 204 Discretionary powers for national supervisory ... discretionary powers for national supervisory ... 160557 Danish 304 Det er de spørgmål , som de lande , der udgør ... det er de spørgmål , som de lande , der udgør ... 196310 English 355 What remains of the concept of what a company ... what remains of the concept of what a company ... 110163 Portuguese 327 Actualmente , é do conhecimento dos senhores d... actualmente , é do conhecimento dos senhores d... 151681 Danish 203 Dette er vigtigt for den tillid , som samfunde... dette er vigtigt for den tillid , som samfunde... 200540 English 257 Therefore , according to proponents , such as ... therefore , according to proponents , such as ... |
Above you see a sample set of random rows of the created Dataframe. After removing very short text snipplets (less than 200 chars) we are left with 56481 snipplets. The function clean_eutextdf() then creates a lower case representation of the texts in the coloum ‘ltext’ to facilitate counting the chars in the next step.
The following code snipplet now extracs the features – in this case the relative frequency of each letter in every text snipplet – that are used for prediction:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def calc_charratios(df): '''Calculating ratio of any (alphabetical) char in any text of df for each lyric calc_charratios(df) -> list, pd.Dataframe ''' CHARS = ''.join({c for c in ''.join(df['ltext']) if c.isalpha()}) print('Counting Chars:') for c in CHARS: print(c, end=' ') df[c] = [r.count(c) for r in df['ltext']] / df['len'] return list(CHARS), df features, df = calc_charratios(df) |
Now that we have calculated the features for every text snipplet in our dataset, we can split our data set in a train and test set:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
def split_dataset(df, ratio=0.5): '''Split the dataset into a train and a test dataset split_dataset(featuredf, ratio) -> pd.Dataframe, pd.Dataframe ''' df = df.sample(frac=1).reset_index(drop=True) traindf = df[:][:int(df.shape[0] * ratio)] testdf = df[:][int(df.shape[0] * ratio):] return traindf, testdf featuredf = pd.DataFrame() featuredf['lang'] = df['lang'] for feature in features: featuredf[feature] = df[feature] traindf, testdf = split_dataset(featuredf, ratio=0.80) x = np.array([np.array(row[1:]) for index, row in traindf.iterrows()]) y = np.array([l for l in traindf['lang']]) X = np.array([np.array(row[1:]) for index, row in testdf.iterrows()]) Y = np.array([l for l in testdf['lang']]) |
After doing that, we can train a k-nearest-neigbours classifier and test it to get the percentage of correctly predicted languages in the test data set. Because we do not know what value for k may be the best choice, we just run the training and testing with different values for k in a for loop:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
def train_knn(x, y, k): '''Returns the trained k nearest neighbors classifier train_knn(x, y, k) -> sklearn.neighbors.KNeighborsClassifier ''' clf = KNeighborsClassifier(k) clf.fit(x, y) return clf def test_knn(clf, X, Y): '''Tests a given classifier with a testset and return result text_knn(clf, X, Y) -> float ''' predictions = clf.predict(X) ratio_correct = len([i for i in range(len(Y)) if Y[i] == predictions[i]]) / len(Y) return ratio_correct print('''k\tPercentage of correctly predicted language __________________________________________________''') for i in range(1, 16): clf = train_knn(x, y, i) ratio_correct = test_knn(clf, X, Y) print(str(i) + '\t' + str(round(ratio_correct * 100, 3)) + '%') |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
k Percentage of correctly predicted language __________________________________________________ 1 97.548% 2 97.38% 3 98.256% 4 98.132% 5 98.221% 6 98.203% 7 98.327% 8 98.247% 9 98.371% 10 98.345% 11 98.327% 12 98.3% 13 98.256% 14 98.274% 15 98.309% |
As you can see in the output the reliability of the language classifier is generally very high: It starts at about 97.5% for k = 1, increases for with increasing values of k until it reaches a maximum level of about 98.5% at k ≈ 10.
Using the Classifier to predict languages of texts
Now that we have trained and tested the classifier we want to use it to predict the language of example texts. To do that we need two more functions, shown in the following piece of code. The first one extracts the nessesary features from the sample text and predict_lang() predicts the language of a the texts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
def extract_features(text, features): '''Extracts all alphabetic characters and add their ratios as feature extract_features(text, features) -> np.array ''' textlen = len(text) ratios = [] text = text.lower() for feature in features: ratios.append(text.count(feature) / textlen) return np.array(ratios) def predict_lang(text, clf=clf): '''Predicts the language of a given text and classifier predict_lang(text, clf) -> str ''' extracted_features = extract_features(text, features) return clf.predict(np.array(np.array([extracted_features])))[0] text_sample = df.sample(10)['text'] for example_text in text_sample: print('%-20s' % predict_lang(example_text, clf) + '\t' + example_text[:60] + '...') |
1 2 3 4 5 6 7 8 9 10 |
Italian Auspico che i progetti riguardanti i programmi possano contr... English When that time comes , when we have used up all our resource... Portuguese Creio que o Parlamento protesta muitas vezes contra este mét... Spanish Sobre la base de esta posición , me parece que se puede enco... Dutch Ik voel mij daardoor aangemoedigd omdat ik een brede consens... Spanish Señor Presidente , Señorías , antes que nada , quisiera pron... Italian Ricordo altresì , signora Presidente , che durante la preced... Swedish Betänkande ( A5-0107 / 1999 ) av Berend för utskottet för re... English This responsibility cannot only be borne by the Commissioner... Portuguese A nossa leitura comum é que esse partido tem uma posição man... |
With this classifier it is now also possible to predict the language of the randomized example snipplet from the introduction (which is acutally created from the first paragraph of this article):
1 2 3 |
example_text = "ogldviisnntmeyoiiesettpetorotrcitglloeleiengehorntsnraviedeenltseaecithooheinsnstiofwtoienaoaeefiitaeeauobmeeetdmsflteightnttxipecnlgtetgteyhatncdisaceahrfomseehmsindrlttdthoaranthahdgasaebeaturoehtrnnanftxndaeeiposttmnhgttagtsheitistrrcudf" predict_lang(example_text) 'English' |
The KNN classifier of sklearn also offers the possibility to predict the propability with which a given classification is made. While the probability distribution for a specific language is relativly clear for long sample texts it decreases noticeably the shorter the texts are.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
def dict_invert(dictionary): ''' Inverts keys and values of a dictionary dict_invert(dictionary) -> collections.defaultdict(list) ''' inverse_dict = defaultdict(list) for key, value in dictionary.items(): inverse_dict[value].append(key) return inverse_dict def get_propabilities(text, features=features): '''Prints the probability for every language of a given text get_propabilities(text, features) ''' results = clf.predict_proba(extract_features(text, features=features).reshape(1, -1)) for result in zip(clf.classes_, results[0]): print('%-20s' % result[0] + '%7s %%' % str(round(float(100 * result[1]), 4))) example_text = 'ogldviisnntmeyoiiesettpetorotrcitglloeleiengehorntsnraviedeenltseaecithooheinsnstiofwtoienaoaeefiitaeeauobmeeetdmsflteightnttxipecnlgtetgteyhatncdisaceahrfomseehmsindrlttdthoaranthahdgasaebeaturoehtrnnanftxndaeeiposttmnhgttagtsheitistrrcudf' print(example_text) get_propabilities(example_text + '\n') print('\n') example_text2 = 'Dies ist ein kurzer Beispielsatz.' print(example_text2) get_propabilities(example_text2 + '\n') |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
ogldviisnntmeyoiiesettpetorotrcitglloeleiengehorntsnraviedeenltseaecithooheinsnstiofwtoienaoaeefiitaeeauobmeeetdmsflteightnttxipecnlgtetgteyhatncdisaceahrfomseehmsindrlttdthoaranthahdgasaebeaturoehtrnnanftxndaeeiposttmnhgttagtsheitistrrcudf Danish 0.0 % Dutch 0.0 % English 100.0 % Finnish 0.0 % French 0.0 % German 0.0 % Italian 0.0 % Portuguese 0.0 % Spanish 0.0 % Swedish 0.0 % Dies ist ein kurzer Beispielsatz. Danish 0.0 % Dutch 0.0 % English 0.0 % Finnish 0.0 % French 18.1818 % German 72.7273 % Italian 9.0909 % Portuguese 0.0 % Spanish 0.0 % Swedish 0.0 % |
Background and Insights
Why does a relative simple model like counting letters acutally work? Every language has a specific pattern of letter frequencies which can be used as a kind of fingerprint: While there are almost no y‘s in the german language this letter is quite common in english. In french the letter k is not very common because it is replaced with q in most cases.
For a better understanding look at the output of the following code snipplet where only three letters already lead to a noticable form of clustering:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
projection='3d') legend = [] X, Y, Z = 'e', 'g', 'h' def iterlog(ln): retvals = [] for n in ln: try: retvals.append(np.log(n)) except: retvals.append(None) return retvals for X in ['t']: ax = plt.axes(projection='3d') ax.xy_viewLim.intervalx = [-3.5, -2] legend = [] for lang in [l for l in df.groupby('lang') if l[0] in {'German', 'English', 'Finnish', 'French', 'Danish'}]: sample = lang[1].sample(4000) legend.append(lang[0]) ax.scatter3D(iterlog(sample[X]), iterlog(sample[Y]), iterlog(sample[Z])) ax.set_title('log(10) of the Relativ Frequencies of "' + X.upper() + "', '" + Y.upper() + '" and "' + Z.upper() + '"\n\n') ax.set_xlabel(X.upper()) ax.set_ylabel(Y.upper()) ax.set_zlabel(Z.upper()) plt.legend(legend) plt.show() |
Even though every single letter frequency by itself is not a very reliable indicator, the set of frequencies of all present letters in a text is a quite good evidence because it will more or less represent the letter frequency fingerprint of the given language. Since it is quite hard to imagine or visualize the above plot in more than three dimensions, I used a little trick which shows that every language has its own typical fingerprint of letter frequencies:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
legend = [] fig = plt.figure(figsize=(15, 10)) plt.axes(yscale='log') langs = defaultdict(list) for lang in [l for l in df.groupby('lang') if l[0] in set(df['lang'])]: for feature in 'abcdefghijklmnopqrstuvwxyz': langs[lang[0]].append(lang[1][feature].mean()) mean_frequencies = {feature:df[feature].mean() for feature in 'abcdefghijklmnopqrstuvwxyz'} for i in langs.items(): legend.append(i[0]) j = np.array(i[1]) / np.array([mean_frequencies[c] for c in 'abcdefghijklmnopqrstuvwxyz']) plt.plot([c for c in 'abcdefghijklmnopqrstuvwxyz'], j) plt.title('Log. of relative Frequencies compared to the mean Frequency in all texts') plt.xlabel('Letters') plt.ylabel('(log(Lang. Frequencies / Mean Frequency)') plt.legend(legend) plt.grid() plt.show() |
What more?
Beside the fact, that letter frequencies alone, allow us to predict the language of every example text (at least in the 10 languages with latin alphabet we trained for) with almost complete certancy there is even more information hidden in the set of sample texts.
As you might know, most languages in europe belong to either the romanian or the indogermanic language family (which is actually because the romans conquered only half of europe). The border between them could be located in belgium, between france and germany and in swiss. West of this border the romanian languages, which originate from latin, are still spoken, like spanish, portouguese and french. In the middle and northern part of europe the indogermanic languages are very common like german, dutch, swedish ect. If we plot the analysed languages with a different colour sheme this border gets quite clear and allows us to take a look back in history that tells us where our languages originate from:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
legend = [] fig = plt.figure(figsize=(15, 10)) plt.axes(yscale='linear') langs = defaultdict(list) for lang in [l for l in df.groupby('lang') if l[0] in {'German', 'English', 'French', 'Spanish', 'Portuguese', 'Dutch', 'Swedish', 'Danish', 'Italian'}]: for feature in 'abcdefghijklmnopqrstuvwxyz': langs[lang[0]].append(lang[1][feature].mean()) colordict = {l[0]:l[1] for l in zip([lang for lang in langs], ['brown', 'tomato', 'orangered', 'green', 'red', 'forestgreen', 'limegreen', 'darkgreen', 'darkred'])} mean_frequencies = {feature:df[feature].mean() for feature in 'abcdefghijklmnopqrstuvwxyz'} for i in langs.items(): legend.append(i[0]) j = np.array(i[1]) / np.array([mean_frequencies[c] for c in 'abcdefghijklmnopqrstuvwxyz']) plt.plot([c for c in 'abcdefghijklmnopqrstuvwxyz'], j, color=colordict[i[0]]) # plt.plot([c for c in 'abcdefghijklmnopqrstuvwxyz'], i[1], color=colordict[i[0]]) plt.title('Log. of relative Frequencies compared to the mean Frequency in all texts') plt.xlabel('Letters') plt.ylabel('(log(Lang. Frequencies / Mean Frequency)') plt.legend(legend) plt.grid() plt.show() |
As you can see the more common letters, especially the vocals like a, e, i, o and u have almost the same frequency in all of this languages. Far more interesting are letters like q, k, c and w: While k is quite common in all of the indogermanic languages it is quite rare in romanic languages because the same sound is written with the letters q or c.
As a result it could be said, that even “boring” sets of data (just give it a try and read all the texts of the protocolls of the EU parliament…) could contain quite interesting patterns which – in this case – allows us to predict quite precisely which language a given text sample is written in, without the need of any translation program or to speak the languages. And as an interesting side effect, where certain things in history happend (or not happend): After two thousand years have passed, modern machine learning techniques could easily uncover this history because even though all these different languages developed, they still have a set of hidden but common patterns that since than stayed the same.
Sentiment Analysis using Python
/3 Comments/in Business Analytics, Business Intelligence, Data Mining, Data Science, Machine Learning, Python, Text Mining, Use Case /by Aakash ChughOne of the applications of text mining is sentiment analysis. Most of the data is getting generated in textual format and in the past few years, people are talking more about NLP. Improvement is a continuous process and many product based companies leverage these text mining techniques to examine the sentiments of the customers to find about what they can improve in the product. This information also helps them to understand the trend and demand of the end user which results in Customer satisfaction.
As text mining is a vast concept, the article is divided into two subchapters. The main focus of this article will be calculating two scores: sentiment polarity and subjectivity using python. The range of polarity is from -1 to 1(negative to positive) and will tell us if the text contains positive or negative feedback. Most companies prefer to stop their analysis here but in our second article, we will try to extend our analysis by creating some labels out of these scores. Finally, a multi-label multi-class classifier can be trained to predict future reviews.
Without any delay let’s deep dive into the code and mine some knowledge from textual data.
There are a few NLP libraries existing in Python such as Spacy, NLTK, gensim, TextBlob, etc. For this particular article, we will be using NLTK for pre-processing and TextBlob to calculate sentiment polarity and subjectivity.
1 2 3 4 5 6 7 8 9 |
import pandas as pd import matplotlib.pyplot as plt %matplotlib inline import nltk from nltk import word_tokenize, sent_tokenize from nltk.corpus import stopwords from nltk.stem import LancasterStemmer, WordNetLemmatizer, PorterStemmer from wordcloud import WordCloud, STOPWORDS from textblob import TextBlob |
The dataset is available here for download and we will be using pandas read_csv function to import the dataset. I would like to share an additional information here which I came to know about recently. Those who have already used python and pandas before they probably know that read_csv is by far one of the most used function. However, it can take a while to upload a big file. Some folks from RISELab at UC Berkeley created Modin or Pandas on Ray which is a library that speeds up this process by changing a single line of code.
1 |
amz_reviews = pd.read_csv("1429_1.csv") |
After importing the dataset it is recommended to understand it first and study the structure of the dataset. At this point we are interested to know how many columns are there and what are these columns so I am going to check the shape of the data frame and go through each column name to see if we need them or not.
1 2 3 4 5 6 7 8 9 10 11 |
amz_reviews.shape (34660, 21) amz_reviews.columns Index(['id', 'name', 'asins', 'brand', 'categories', 'keys', 'manufacturer', 'reviews.date', 'reviews.dateAdded', 'reviews.dateSeen', 'reviews.didPurchase', 'reviews.doRecommend', 'reviews.id', 'reviews.numHelpful', 'reviews.rating', 'reviews.sourceURLs', 'reviews.text', 'reviews.title', 'reviews.userCity', 'reviews.userProvince', 'reviews.username'], dtype='object') |
There are so many columns which are not useful for our sentiment analysis and it’s better to remove these columns. There are many ways to do that: either just select the columns which you want to keep or select the columns you want to remove and then use the drop function to remove it from the data frame. I prefer the second option as it allows me to look at each column one more time so I don’t miss any important variable for the analysis.
1 2 3 4 5 |
columns = ['id','name','keys','manufacturer','reviews.dateAdded', 'reviews.date','reviews.didPurchase', 'reviews.userCity', 'reviews.userProvince', 'reviews.dateSeen', 'reviews.doRecommend','asins', 'reviews.id', 'reviews.numHelpful', 'reviews.sourceURLs', 'reviews.title'] df = pd.DataFrame(amz_reviews.drop(columns,axis=1,inplace=False)) |
Now let’s dive deep into the data and try to mine some knowledge from the remaining columns. The first step we would want to follow here is just to look at the distribution of the variables and try to make some notes. First, let’s look at the distribution of the ratings.
1 |
df['reviews.rating'].value_counts().plot(kind='bar') |
Graphs are powerful and at this point, just by looking at the above bar graph we can conclude that most people are somehow satisfied with the products offered at Amazon. The reason I am saying ‘at’ Amazon is because it is just a platform where anyone can sell their products and the user are giving ratings to the product and not to Amazon. However, if the user is satisfied with the products it also means that Amazon has a lower return rate and lower fraud case (from seller side). The job of a Data Scientist relies not only on how good a model is but also on how useful it is for the business and that’s why these business insights are really important.
Data pre-processing for textual variables
Lowercasing
Before we move forward to calculate the sentiment scores for each review it is important to pre-process the textual data. Lowercasing helps in the process of normalization which is an important step to keep the words in a uniform manner (Welbers, et al., 2017, pp. 245-265).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
## Change the reviews type to string df['reviews.text'] = df['reviews.text'].astype(str) ## Before lowercasing df['reviews.text'][2] 'Inexpensive tablet for him to use and learn on, step up from the NABI. He was thrilled with it, learn how to Skype on it already...' ## Lowercase all reviews df['reviews.text'] = df['reviews.text'].apply(lambda x: " ".join(x.lower() for x in x.split())) df['reviews.text'][2] ## to see the difference 'inexpensive tablet for him to use and learn on, step up from the nabi. he was thrilled with it, learn how to skype on it already...' |
Special characters
Special characters are non-alphabetic and non-numeric values such as {!,@#$%^ *()~;:/<>\|+_-[]?}. Dealing with numbers is straightforward but special characters can be sometimes tricky. During tokenization, special characters create their own tokens and again not helpful for any algorithm, likewise, numbers.
1 2 3 4 5 |
## remove punctuation df['reviews.text'] = df['reviews.text'].str.replace('[^\w\s]','') df['reviews.text'][2] 'inexpensive tablet for him to use and learn on step up from the nabi he was thrilled with it learn how to skype on it already' |
Stopwords
Stop-words being most commonly used in the English language; however, these words have no predictive power in reality. Words such as I, me, myself, he, she, they, our, mine, you, yours etc.
1 2 3 4 5 |
stop = stopwords.words('english') df['reviews.text'] = df['reviews.text'].apply(lambda x: " ".join(x for x in x.split() if x not in stop)) df['reviews.text'][2] 'inexpensive tablet use learn step nabi thrilled learn skype already' |
Stemming
Stemming algorithm is very useful in the field of text mining and helps to gain relevant information as it reduces all words with the same roots to a common form by removing suffixes such as -action, ing, -es and -ses. However, there can be problematic where there are spelling errors.
1 2 3 4 |
st = PorterStemmer() df['reviews.text'] = df['reviews.text'].apply(lambda x: " ".join([st.stem(word) for word in x.split()])) df['reviews.text'][2] 'inexpens tablet use learn step nabi thrill learn skype alreadi' |
This step is extremely useful for pre-processing textual data but it also depends on your goal. Here our goal is to calculate sentiment scores and if you look closely to the above code words like ‘inexpensive’ and ‘thrilled’ became ‘inexpens’ and ‘thrill’ after applying this technique. This will help us in text classification to deal with the curse of dimensionality but to calculate the sentiment score this process is not useful.
Sentiment Score
It is now time to calculate sentiment scores of each review and check how these scores look like.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
## Define a function which can be applied to calculate the score for the whole dataset def senti(x): return TextBlob(x).sentiment df['senti_score'] = df['reviews.text'].apply(senti) df.senti_score.head() 0 (0.3, 0.8) 1 (0.65, 0.675) 2 (0.0, 0.0) 3 (0.29545454545454547, 0.6492424242424243) 4 (0.5, 0.5827777777777777) Name: senti_score, dtype: object |
As it can be observed there are two scores: the first score is sentiment polarity which tells if the sentiment is positive or negative and the second score is subjectivity score to tell how subjective is the text. The whole code is available here.
In my next article, we will extend this analysis by creating labels based on these scores and finally we will train a classification model.
Einstieg in Natural Language Processing – Teil 2: Preprocessing von Rohtext mit Python
/4 Comments/in Business Analytics, Business Intelligence, Customer Analytics, Data Engineering, Data Mining, Data Science, Data Science Hack, Machine Learning, Main Category, Natural Language Processing, Python, Python, Text Mining, Tool Introduction, Tools, Tutorial, Visualization /by Christopher KippDies ist der zweite Artikel der Artikelserie Einstieg in Natural Language Processing.
In diesem Artikel wird das so genannte Preprocessing von Texten behandelt, also Schritte die im Bereich des NLP in der Regel vor eigentlichen Textanalyse durchgeführt werden.
Tokenizing
Um eingelesenen Rohtext in ein Format zu überführen, welches in der späteren Analyse einfacher ausgewertet werden kann, sind eine ganze Reihe von Schritten notwendig. Ganz allgemein besteht der erste Schritt darin, den auszuwertenden Text in einzelne kurze Abschnitte – so genannte Tokens – zu zerlegen (außer man bastelt sich völlig eigene Analyseansätze, wie zum Beispiel eine Spracherkennung anhand von Buchstabenhäufigkeiten ect.).
Was genau ein Token ist, hängt vom verwendeten Tokenizer ab. So bringt NLTK bereits standardmäßig unter anderem BlankLine-, Line-, Sentence-, Word-, Wordpunkt- und SpaceTokenizer mit, welche Text entsprechend in Paragraphen, Zeilen, Sätze, Worte usw. aufsplitten. Weiterhin ist mit dem RegexTokenizer ein Tool vorhanden, mit welchem durch Wahl eines entsprechenden Regulären Ausdrucks beliebig komplexe eigene Tokenizer erstellt werden können.
Üblicherweise wird ein Text (evtl. nach vorherigem Aufsplitten in Paragraphen oder Sätze) schließlich in einzelne Worte und Interpunktionen (Satzzeichen) aufgeteilt. Hierfür kann, wie im folgenden Beispiel z. B. der WordTokenizer oder die diesem entsprechende Funktion word_tokenize() verwendet werden.
1 2 3 4 5 6 |
rawtext = 'This is a short example text that needs to be cleaned.' tokens = nltk.word_tokenize(rawtext) tokens ['This', 'is', 'a', 'short', 'example', 'text', 'that', 'needs', 'to', 'be', 'cleaned', '.'] |
Stemming & Lemmatizing
Andere häufig durchgeführte Schritte sind Stemming sowie Lemmatizing. Hierbei werden die Suffixe der einzelnen Tokens des Textes mit Hilfe eines Stemmers in eine Form überführt, welche nur den Wortstamm zurücklässt. Dies hat den Zweck verschiedene grammatikalische Formen des selben Wortes (welche sich oft in ihrer Endung unterscheiden (ich gehe, du gehst, er geht, wir gehen, …) ununterscheidbar zu machen. Diese würden sonst als mehrere unabhängige Worte in die darauf folgende Analyse eingehen.
Neben bereits fertigen Stemmern bietet NLTK auch für diesen Schritt die Möglichkeit sich eigene Stemmer zu programmieren. Da verschiedene Stemmer Suffixe nach unterschiedlichen Regeln entfernen, sind nur die Wortstämme miteinander vergleichbar, welche mit dem selben Stemmer generiert wurden!
Im forlgenden Beispiel werden verschiedene vordefinierte Stemmer aus dem Paket NLTK auf den bereits oben verwendeten Beispielsatz angewendet und die Ergebnisse der gestemmten Tokens in einer Art einfachen Tabelle ausgegeben:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# Ready-to-use stemmers in nltk porter = nltk.PorterStemmer() lancaster = nltk.LancasterStemmer() snowball = nltk.SnowballStemmer(language='english') # Printing a table to compare the different stemmers header = 'Token\tPorter\tLancas.\tSnowball' print(header + '\n' + len(header) * '-') for token in tokens: print('\t'.join([token, porter.stem(token), lancaster.stem(token), snowball.stem(token)])) Token Porter Lancas. Snowball ----------------------------- This thi thi this is is is is a a a a short short short short example exampl exampl exampl text text text text that that that that needs need nee need to to to to be be be be cleaned clean cle clean . . . . |
Sehr ähnlich den Stemmern arbeiten Lemmatizer: Auch ihre Aufgabe ist es aus verschiedenen Formen eines Wortes die jeweilige Grundform zu bilden. Im Unterschied zu den Stemmern ist das Lemma eines Wortes jedoch klar als dessen Grundform definiert.
1 2 3 4 5 |
from nltk.stem import WordNetLemmatizer lemmatizer = WordNetLemmatizer() lemmas = [lemmatizer.lemmatize(t) for t in tokens()] |
Vokabular
Auch das Vokabular, also die Menge aller verschiedenen Worte eines Textes, ist eine informative Kennzahl. Bezieht man die Größe des Vokabulars eines Textes auf seine gesamte Anzahl verwendeter Worte, so lassen sich hiermit Aussagen zu der Diversität des Textes machen.
Außerdem kann das auftreten bestimmter Worte später bei der automatischen Einordnung in Kategorien wichtig werden: Will man beispielsweise Nachrichtenmeldungen nach Themen kategorisieren und in einem Text tritt das Wort „DAX“ auf, so ist es deutlich wahrscheinlicher, dass es sich bei diesem Text um eine Meldung aus dem Finanzbereich handelt, als z. B. um das „Kochrezept des Tages“.
Dies mag auf den ersten Blick trivial erscheinen, allerdings können auch mit einfachen Modellen, wie dem so genannten „Bag-of-Words-Modell“, welches nur die Anzahl des Auftretens von Worten prüft, bereits eine Vielzahl von Informationen aus Texten gewonnen werden.
Das reine Vokabular eines Textes, welcher in der Variable “rawtext” gespeichert ist, kann wie folgt in der Variable “vocab” gespeichert werden. Auf die Ausgabe wurde in diesem Fall verzichtet, da diese im Falle des oben als Beispiel gewählten Satzes den einzelnen Tokens entspricht, da kein Wort öfter als ein Mal vorkommt.
1 2 3 4 5 6 |
from nltk import wordpunct_tokenizer from nltk.stem import WordNetLemmatizer lemma = WordNetLemmatizer() vocab = set([WordNetLemmatizer().lemmatize(t) for t in wordpunct_tokenize(text.lower())]) |
Stopwords
Unter Stopwords werden Worte verstanden, welche zwar sehr häufig vorkommen, jedoch nur wenig Information zu einem Text beitragen. Beispiele in der beutschen Sprache sind: der, und, aber, mit, …
Sowohl NLTK als auch cpaCy bringen vorgefertigte Stopwordsets mit.
1 2 3 4 5 6 |
from nltk.corpus import stopwords stoplist = stopwords.words('english') stopset = set(stopwords.words('english')) [t for t in tokens if not t in stoplist] ['This', 'short', 'example', 'text', 'needs', 'cleaned', '.'] |
Vorsicht: NLTK besitzt eine Stopwordliste, welche erst in ein Set umgewandelt werden sollte um die lookup-Zeiten kurz zu halten – schließlich muss jedes einzelne Token des Textes auf das vorhanden sein in der Stopworditerable getestet werden!
1 2 |
%timeit [w for w in tokens if not w in stopset] # 1.11 ms %timeit [w for w in tokens if not w in stoplist] # 26.6 ms |
POS-Tagging
POS-Tagging steht für „Part of Speech Tagging“ und entspricht ungefähr den Aufgaben, die man noch aus dem Deutschunterricht kennt: „Unterstreiche alle Subjekte rot, alle Objekte blau…“. Wichtig ist diese Art von Tagging insbesondere, wenn man später tatsächlich strukturiert Informationen aus dem Text extrahieren möchte, da man hierfür wissen muss wer oder was als Subjekt mit wem oder was als Objekt interagiert.
Obwohl genau die selben Worte vorkommen, bedeutet der Satz „Die Katze frisst die Maus.“ etwas anderes als „Die Maus frisst die Katze.“, da hier Subjekt und Objekt aufgrund ihrer Reihenfolge vertauscht sind (Stichwort: Subjekt – Prädikat – Objekt ).
Weniger wichtig ist dieser Schritt bei der Kategorisierung von Dokumenten. Insbesondere bei dem bereits oben erwähnten Bag-of-Words-Modell, fließen POS-Tags überhaupt nicht mit ein.
Und weil es so schön einfach ist: Die obigen Schritte mit spaCy
Die obigen Methoden und Arbeitsschritte, welche Texte die in natürlicher Sprache geschrieben sind, allgemein computerzugänglicher und einfacher auswertbar machen, können beliebig genau den eigenen Wünschen angepasst, einzeln mit dem Paket NLTK durchgeführt werden. Dies zumindest einmal gemacht zu haben, erweitert das Verständnis für die funktionsweise einzelnen Schritte und insbesondere deren manchmal etwas versteckten Komplexität. (Wie muss beispielsweise ein Tokenizer funktionieren der den Satz “Schwierig ist z. B. dieser Satz.” korrekt in nur einen Satz aufspaltet, anstatt ihn an jedem Punkt welcher an einem Wortende auftritt in insgesamt vier Sätze aufzuspalten, von denen einer nur aus einem Leerzeichen besteht?) Hier soll nun aber, weil es so schön einfach ist, auch das analoge Vorgehen mit dem Paket spaCy beschrieben werden:
1 2 3 4 |
import spacy nlp = spacy.load('en') doc = nlp(rawtext) |
Dieser kurze Codeabschnitt liest den an spaCy übergebenen Rohtext in ein spaCy Doc-Object ein und führt dabei automatisch bereits alle oben beschriebenen sowie noch eine Reihe weitere Operationen aus. So stehen neben dem immer noch vollständig gespeicherten Originaltext, die einzelnen Sätze, Worte, Lemmas, Noun-Chunks, Named Entities, Part-of-Speech-Tags, ect. direkt zur Verfügung und können.über die Methoden des Doc-Objektes erreicht werden. Des weiteren liegen auch verschiedene weitere Objekte wie beispielsweise Vektoren zur Bestimmung von Dokumentenähnlichkeiten bereits fertig vor.
Die Folgende Übersicht soll eine kurze (aber noch lange nicht vollständige) Übersicht über die automatisch von spaCy generierten Objekte und Methoden zur Textanalyse geben:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# Textabschnitte doc.text # Originaltext sents = doc.sents # Sätze des Dokuments tokens = [token for token in doc] # Tokens/Worte des Dokuments parags = doc.text_with_ws.split('\n\n') # Absätze des Dokuments # Eigenschaften einzelner Tokens [t.lemma_ for t in doc] # Lemmata der einzelnen Tokens [t.tag_ for t in doc] # POS-Tags der einzelnen Tokens # Objekte zur Textanalyse doc.vocab # Vokabular des Dokuments doc.sentiment # Sentiment des Dokuments doc.noun_chunks # NounChunks des Dokuments entities = [ent for ent in doc.ents] # Named Entities (Persons, Locations, Countrys) # Objekte zur Dokumentenklassifikation doc.vector # Vektor doc.tensor # Tensor |
Diese „Vollautomatisierung“ der Vorabschritte zur Textanalyse hat jedoch auch seinen Preis: spaCy geht nicht gerade sparsam mit Ressourcen wie Rechenleistung und Arbeitsspeicher um. Will man einen oder einige Texte untersuchen so ist spaCy oft die einfachste und schnellste Lösung für das Preprocessing. Anders sieht es aber beispielsweise aus, wenn eine bestimmte Analyse wie zum Beispiel die Einteilung in verschiedene Textkategorien auf eine sehr große Anzahl von Texten angewendet werden soll. In diesem Fall, sollte man in Erwägung ziehen auf ressourcenschonendere Alternativen wie zum Beispiel gensim auszuweichen.
Wer beim lesen genau aufgepasst hat, wird festgestellt haben, dass ich im Abschnitt POS-Tagging im Gegensatz zu den anderen Abschnitten auf ein kurzes Codebeispiel verzichtet habe. Dies möchte ich an dieser Stelle nachholen und dabei gleich eine Erweiterung des Pakets spaCy vorstellen: displaCy.
Displacy bietet die Möglichkeit, sich Zusammenhänge und Eigenschaften von Texten wie Named Entities oder eben POS-Tagging graphisch im Browser anzeigen zu lassen.
1 2 3 4 5 6 7 8 |
import spacy from spacy import displacy rawtext = 'This is a short example sentence that needs to be cleaned.' nlp = spacy.load('en') doc = nlp(rawtext) displacy.serve(doc, style='dep') |
Nach ausführen des obigen Codes erhält man eine Ausgabe die wie folgt aussieht:
1 2 |
Serving on port 5000... Using the 'dep' visualizer |
Nun öffnet man einen Browser und ruft die URL ‘http://127.0.0.1:5000’ auf (Achtung: localhost anstatt der IP funktioniert – warum auch immer – mit displacy nicht). Im Browser sollte nun eine Seite mit einem SVG-Bild geladen werden, welches wie folgt aussieht
Die Abbildung macht deutlich was POS-Tagging genau ist und warum es von Nutzen sein kann wenn man Informationen aus einem Text extrahieren will. Jedem Word (Token) ist eine Wortart zugeordnet und die Beziehung der einzelnen Worte durch Pfeile dargestellt. Dies ermöglicht es dem Computer zum Beispiel in dem Satzteil “der grüne Apfel”, das Adjektiv “grün” auf das Nomen “Apfel” zu beziehen und diesem somit als Eigenschaft zuzuordnen.
Nachdem dieser Artikel wichtige Schritte des Preprocessing von Texten beschrieben hat, geht es im nächsten Artikel darum was man an Texten eigentlich analysieren kann und welche Analysemöglichkeiten die verschiedenen für Python vorhandenen Module bieten.
II. Einführung in TensorFlow: Grundverständnis für TensorFlow
/0 Comments/in Data Science, Deep Learning, Machine Learning, Python, Tutorial /by Hoang Tu Nguyeno. Installation von TensorFlow
Bevor wir richtig durchstarten können, müssen wir natürlich TensorFlow erstmal installieren. Auf dieser Seite findet ihr eine ausführliche Anleitung, wie man TensorFlow auf allen möglichen Systemen installiert. Die nächsten Schritte beschränken sich auf die Installation auf Windows.
o.1. Installation mit pip
Um TensorFlow zu nutzen, müssen wir diesen Framework auch erstmal installieren. Am einfachsten ist die Installation, wenn ihr Python in reiner Form auf euren Rechner habt. Dann ist es vollkommen ausreichend, wenn ihr folgenden Befehl in eure Eingabeaufforderung(Windows: cmd) eingebt:
1 |
pip3 install --upgrade tensorflow |
Stellt bei dieser Installation sicher, dass ihr keine ältere Version von Python habt als 3.5.x. Außerdem ist es erforderlich, dass ihr pip installiert habt und Python bei euch in der PATH-Umgebung eingetragen ist.Besitzt ihr eine NVIDIA® Grafikkarte so könnt ihr TensorFlow mit GPU Support nutzen. Dazu gebt ihr statt des oben gezeigten Befehls folgendes ein:
1 |
pip3 install --upgrade tensorflow gpu |
o.2. Installation mit Anaconda
Ein wenig aufwendiger wird es, wenn ihr die beliebte Anaconda Distribution nutzt, weil wir da eine Anaconda Umgebung einrichten müssen. Auch hier müssen wir wieder in den Terminal bzw. in die Eingabeaufforderung und folgenden Befehl eingeben:
1 |
conda create -n tensorflow pip python=3.x |
Tauscht das x mit eurer genutzten Version aus.(x = 5, 6) Danach aktiviert ihr die erstellte Umgebung:
1 |
activate tensorflow |
Nun installieren wir TensorFlow in unsere erstellte Umgebung. Ohne GPU Support
1 |
pip install --ignore-installed --upgrade tensorflow |
mit GPU Support
1 |
pip install --ignore-installed --upgrade tensorflow-gpu |
Es sei erwähnt, dass das Conda package nur von der Community unterstützt wird, jedoch nicht offiziell seitens Google.
o.3. Validierung der Installation
Der einfachste Weg um zu überprüfen ob unsere Installation gefruchtet hat und funktioniert können wir anhand eines einfachen Beispiels testen. Dazu gehen wir wieder in den/die Terminal/Eingabeaufforderung und rufen python auf, indem wir python eingeben.
1 2 3 4 |
import tensorflow as tf hello = tf.constant('Hello, TensorFlow!') sess = tf.Session() print(sess.run(hello)) |
1. Grundverständnis für TensorFlow
1.1. Datenstrom-orientierte Programmierung
In diesem Artikel wollen wir näher auf die Funktionsweise von TensorFlow eingehen. Wie wir aus dem ersten Artikel dieser Serie wissen, nutzt TensorFlow das datenstrom-orientierte Paradigma. In diesem wird ein Datenfluss-Berechnungsgraph erstellt, welcher aus Knoten und Kanten besteht. Ein Datenfluss-Berechnungsgraph, Datenflussgraph oder auch Berechnungsgraph kann mehrere Knoten haben, die wiederum durch die Kanten verbunden sind. In TensorFlow steht jeder Knoten für eine Operation, die Auswirkungen auf eingehende Daten haben.

Abb.1: Knoten und Kanten: Das Eingangssignal wird durch Kanten in den Knoten eingespeist, verändert und ausgegeben

Abb. 1.5: Achterbahn mit fehlender Verbindung [Quelle]
Analogie-Beispiel: Stellt euch vor ihr seid in einem Freizeitpark und habt Lust eine Achterbahn zu fahren. Am Anfang seid ihr vielleicht ein wenig nervös, aber euch geht es noch sehr gut. Wie jeder von euch weiß, hat eine Achterbahn verschiedene Fahrelemente eingebaut, die unsere Emotionen triggern und bei manchen vielleicht sogar auf den Magen schlagen. Diese Elemente sind äquivalent unsere Knoten. Natürlich müssen diese Elemente auch verbunden sein, sonst wäre eine Fahrt mit dieser Achterbahn in meinen Augen nicht empfehlenswert. Diese Verbindungsstücke sind unsere Kanten und somit sind wir die Daten/Signale, die von Knoten zu Knoten durch die Kanten weitergeleitet werden. Schauen wir uns Abb. 2 an, in der eine schematische Darstellung einer fiktiven Achterbahn zu sehen ist, welche mit 4 Fahrelementen dienen kann.

Abb. 2: Oben: Schematische Darstellung eines Datenflussgraphen anhand unserer fiktiven Achterbahn Unten: Unsere fiktive Achterbahn
- Airtime-Hügel: Ein Airtime-Hügel erzeugt bei der Überfahrt Schwerelosigkeit und in manchen Fällen ein Abheben aus dem Sitz. Ein guter Einstieg für die Mitfahrer, wie ich finde.
- Klassischer Looping: Wir kennen ihn alle, den Looping. Mit hoher Geschwindigkeit geht es in einen vertikalen Kreis hinein und man sich am höchsten Punkt kopfüber befindet. Für Leute mit nicht so starken Nerven fragen sich spätestens jetzt, warum sie überhaupt mitgefahren sind.
- Korkenzieher/Schraube: Der Korkenzieher kann als auseinander gezogener Looping beschrieben werden.
- Schraubel-Looping : Und zu guter Letzt kombinieren wir einen Looping mit einer Schraube! Ein Teil unserer Mitfahrer sucht den nächsten Busch auf, ein anderer Teil will am liebsten nochmal fahren und der Rest wird jetzt einen Pause brauchen.
Fakt ist, dass die Fahrelemente/Knoten unsere anfänglichen Emotionen/Eingangsdatensignale geändert haben.
1.2. Genereller Ablauf in TensorFlow
Anhand unser fiktiven Achterbahn haben wir das Prinzip der datenstrom-orientierten Programmierung eingefangen. Damit wir aber erst einmal Achterbahn fahren können, müssen wir diese konstruieren. Das gilt auch in TensorFlow und können die Arbeit in zwei wesentliche Phasen unterteilen:
- Erstellen eines Berechnungsgraphen: Wie auch bei einer Achterbahn müssen wir unser Modell erst einmal modellieren. Je nachdem welche Ressourcen uns zur Verfügung gestellt werden, welche Bedingungen wir folgen müssen, können wir unser Modell darauf aufbauen und gestalten.
- Ausführung des Berechnungsgraphen: Nachdem wir das Modell/den Graph fertig konstruiert haben, führen wir diese nun aus, d.h. für unsere Achterbahn, dass wir den Strom anschalten und losfahren können.
2. Erstellung eines Graphen
2.1. TensorFlow-Operatoren
Wie bereits erwähnt können Knoten verschiedene Operationen in sich tragen. Das können z.B. Addition, Substraktion oder aber auch mathematische Hyperbelfunktionen à la Tangens Hyperbolicus Operatoren sein. Damit TensorFlow mit den Operatoren arbeiten kann, müssen wir diese mit den zur Verfügung gestellten Operatoren von TensorFlow auskommen. Eine vollständige Dokumentation findet ihr hier.
2.2. Platzhalter
Wenn in TensorFlow Daten aus externen Quellen in den Berechnungsgraph integriert werden sollen, dann wird eine eigens dafür entwickelte Struktur genutzt um die Daten einzulesen; dem Platzhalter. Ihr könnt euch den Platzhalter als Wagon unserer Achterbahn vorstellen, der die Mitfahrer (Daten bzw. Tensoren) durch die Achterbahn (Berechnungsgraph) jagt.
Es ist bei der Modellierung eines Berechnungsgraphen nicht notwendig, die Daten am Anfang einzuspeisen. Wie der Name schon sagt, setzt TensorFlow eine ‘leere Größe’ ein, die in der zweiten Phase gefüllt wird.
Eine Frage, die ich mir damals gestellt habe war, warum man einen Platzhalter braucht? Dazu können wir uns wieder unsere Achterbahn nehmen. Bei 2-3 Fahrgästen besteht kein Problem; wir hätten genug Platz/Ressourcen um diese unterzubringen. Aber was machen wir, wenn wir 10.000 Gäste haben, wie es auch in der Realität ist ? Das ist auch bei neuronalen Netzen der Fall, wenn wir zu viele Daten haben, dann stoßen wir irgendwann an unser Leistungslimit. Wir teilen unsere Daten/Gäste so auf, dass wir damit arbeiten können.
2.3. Variable
Stellen wir uns folgendes Szenario vor: Wir haben eine Achterbahn fertig konstruiert – wahrscheinlich die beste und verrückteste Achterbahn, die es jemals gegeben hat. Je nachdem welchen Effekt wir mit unserer Achterbahn erzielen wollen; z.B. ein einfacher Adrenalinschub, ein flaues Gefühl im Magen oder den vollständigen Verlust jeglicher Emotionen aus purer Angst um das eigene Leben, reicht es nicht nur ein schönes Modell zu bauen. Wir müssen zusätzlich verschiedene Größen anpassen um das Erlebnis zu maximieren. Eine wichtige Größe für unsere Achterbahn wäre die Geschwindigkeit (in neuronalen Netzen sind es die Gewichte), die über den Fahrspaß entscheidet. Um die optimale Geschwindigkeit zu ermitteln, müssen viele Versuche gemacht werden (sei es in der Realität oder in der Simulation) und nach jedem Test wird die Geschwindigkeit nach jedem Test angepasst. Zu diesem Zweck sind die Variablen da. Sie passen sich nach jedem Versuch an.
2.4. Optimierung
Damit die Variablen angepasst werden können, müssen wir TensorFlow Anweisungen geben, wie er die Variablen optimiert werden soll. Dafür müssen wir eine Formel an TensoFlow übermitteln, die dann optimiert wird. Auch hat man die Auswahl von verschiedenen Optimierer, die die Aufgabe anders optimieren. Die Wahl der richtigen Formel und des passenden Optimierer ist jedoch eine Sache, die ohne weiteres nicht zu beantworten ist. Wir wollen ein anderes Mal Bezug auf diese Frage nehmen.
3. Ausführung eines Graphen
Wie die Ausführung des Graphen von statten läuft, schauen wir uns im nächsten Abschnitt genauer an. Es sei so viel gesagt, dass um eine Ausführung einzuleiten wir den Befehl tf.Session()
benötigen. Die Session wird mit tf.Session().run()
gestartet und am Ende mit tf.Session().close()
geschlossen. In der Methode .run()
müssen die ausgeführten Größen stehen und außerdem der Befehl feed_dict=
zum Befüllen der Platzhalter.
4. Beispiel: Achterbahn des Grauens – Nichts für schwache Nerven
4.1 Erklärung des Beispiels
Wir haben jetzt von so vielen Analogien gesprochen, dass es alles ein wenig verwirrend sein kann. Daher nochmal eine Übersicht zu den wesentlichen Punkten:
TensorFlow | Neuronales Netz | Achterbahn |
Knoten | Neuron | Fahrelement |
Variable | Gewichte, Bias | Geschwindigkeit |
Kanten | Signale | Zustand der Fahrer |
Platzhalter | Wagon |
Nun haben wir so viel Theorie gehört, jetzt müssen auch Taten folgen! Weshalb wir unsere Achterbahn modellieren wollen. Zu unserem Beispiel: Wir wollen eine Achterbahn bauen, welche ängstlichen Mitfahrer noch ängstlicher machen soll und diese sollen am Ende der Fahrt sich wünschen nie mitgefahren zu sein. (Es wird natürlich eine stark vereinfachte Variante werden, die aber auf all unsere Punkte eingehen soll, die wir im oberen Teil angesprochen haben.)
Wie im bereits beschrieben, unterteilt sich die Arbeit in TensorFlow in zwei Phasen:
- Erstellung des Graphen: In unserem Falle wäre das die Konstruktion unserer Achterbahn.
- Ausführung des Graphen: In dieser Phase lassen wir unsere Insassen einfach los und schauen mal was passiert.
Um die Zahlen zu verstehen, möchte ich euch zudem erklären, was überhaupt das Ziel unseres Modells ist. Wir haben 8 Probanden mit verschiedenen Angstzuständen. Der Angstzustand ist in unserem Beispiel ein quantitativer Wert, Menge der ganzen Zahlen und je größer dieser Wert ist, desto ängstlicher sind unsere Probanden. Unser Ziel ist es alle Probanden in Angst und Schrecken zu versetzen, die einen Angstzustand >5 haben und sich nach der Fahrt wünschen unserer Achterbahn nie mitgefahren zu sein! Die Größe die wir dabei optimieren wollen, ist die Geschwindigkeit. Wenn die Geschwindigkeit zu schnell ist, dann fürchten sich zu viele, wenn wir zu langsam fahren, dann fürchtet sich womöglich niemand. Außerdem benötigen wir noch eine Starthöhe, die wir dem Modell zugeben müssen.
Wir haben somit eine Klassifikationsaufgabe mit dem Ziel die Geschwindigkeit und die Starthöhe zu optimieren, damit sich Fahrgäste mit einem Angstzustand > 5 so eine schlechte Erfahrung machen, dass sie am liebsten nie mitgefahren wären.
Wir benötigen außerdem für unser Beispiel folgende Module:
1 2 3 |
import tensorflow as tf import matplotlib.pyplot as plt import numpy as np |
4.2. Eingangssignale: Zustände der Gäste
Wir sehen hier zwei Vektoren bzw. Tensoren die Informationen über unsere Gäste haben.
x_input
ist der Angstzustand unserer Gästey_input
ist unser gewünschtes Ausgangsssignal: 0 → normal, 1 → Wunsch nicht mitgefahren zu sein
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# Eingangssignale, Tensor # Zustand der Gäste -> je größer die Zahl desto größer der Angstzustand der Gäste x_input = [[-10], # <- Angstzustand Gast 1 [-5], # <- Angstzustand Gast 2 [-2], # <- Angstzustand Gast 3 [-1], # . [2], # . [1], # . [6], # . [9]] # <- Angstzustand Gast 8 # gewünschtes Ausgangssignal, Tensor # Endzustand der Gäste -> Wunsch die Bahn nie gefahren zu sein y_input = [[0], [0], [0], [0], [0], [0], [1], # <- bereut die Fahrt [1]] # <- bereut die Fahrt |
4.3. Erstellung unseres Graphen: Konstruktion der Achterbahn
Nun konstruieren wir unsere Achterbahn des Grauens:
Eine Gleichrichter-Aktivierungsfunktion (engl. rectifier) mit einer Matrizenmultiplikation aus einem Vektor und einem Skalar mit anschließender Fehleroptimierung! MuhahahahaHAHAHAHA!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# Platzhalter # Wagon, Eingangsgröße wag = tf.placeholder(tf.float32, shape = [8, 1]) # gewünschter Endzustand der Gäste y_true = tf.placeholder(tf.float32, shape = [8, 1]) # Variable # Geschwindigkeit des Wagons v = tf.Variable([[1.0,]]) # Starthöhe des Wagons h = tf.Variable([[-2.0]]) # Knoten mit Matrizenoperator, Fahrelement, z.B. Airtime-Hügel z = tf.matmul(wag, v) + h # Knoten mit ReLu-Aktivierungsfunktion y_pred = tf.nn.relu(z) # Fehlerfunktion err = tf.square(y_true - y_pred) # Optimierer opt = tf.train.AdamOptimizer(learning_rate=0.01).minimize(err) # Initialisierung der Variablen (Geschwindigkeit) init = tf.global_variables_initializer() |
Auf den ersten Blick vielleicht ein wenig verwirrend, weshalb wir alles Schritt für Schritt durchgehen:
wag = tf.placeholder(tf.float32, shape = [8, 1])
ist unser Wagon, welcher die Achterbahn auf und ab fährt. Gefüllt mit unseren Probanden. Die Daten der Probanden (x_input)
sind externe Daten und damit geeignet für einen Platzhalter.- Wichtig bei Platzhalter ist, dass ihr den Datentyp angeben müsst!
- Optional könnt ihr auch die Form angeben. Bei einem so überschaubaren Beispiel machen wir das auch. (Form unseres Vektors: 8×1)
y_true = tf.placeholder(tf.float32, shape = [8, 1])
ist der gewünschte Endzustand unserer Gäste, den wir uns für die Probanden erhoffen, d.h. es ist unsery_input
. Auch hier kommen die Daten von außerhalb und daher wird der Platzhalter genutzt.v, h
sind Geschwindigkeit und Starthöhe, die optimiert werden müssen; perfekt für eine Variable!- Variablen brauchen am Anfang immer einen Initialisierungswert. Für
v
soll es 1 sein und fürh
soll es -2 sein. Außerdem liegen diese Größen als Skalare (1×1) vor.
- Variablen brauchen am Anfang immer einen Initialisierungswert. Für
Nun zum zweiten Teil der Modellierung in dem wir ein klein wenig Mathematik benötigen. Schauen wir uns folgende Gleichung an:
z = tf.matmul(wag, v) + h
: ist unsere Matrizenmultiplikation -> Da unsere Größen in Vektoren/Tensoren vorliegen, können wir diese nicht einfach multiplizieren, wie z.B. 2*2 = 4. Bei der Multiplikation von Matrizen oder Vektoren müssen bestimmte Bedingungen herrschen, damit diese überhaupt multipliziert werden können. Eine ausführlichere Erklärungen soll demnächst folgen.y_pred = tf.nn.relu(z)
: Für all diejenigen, die sich bereits mit neuronalen Netzen beschäftigt haben;relu
ist in unserem Fall die Aktivierungsfunktion. Für alle anderen, die mit der Aktivierungsfunktion noch nichts anfangen können: Die Kombination (Matrizenmultiplikation) aus dem Angstzustand und der Geschwindigkeit ist der Wert Z. Je nachdem welche Aktivierungsfunktion genutzt wird, triggert der Wert Z unsere Emotionen, so dass wir den Wunsch verspüren, die Bahn nie gefahren zu sein.err = tf.square(y_true - y_pred):
Quadriert die Differenz der tatsächlichen und der ermittelten Werte. -> die zu optimierende Funktionopt = tf.train.AdamOptimizer(learning_rate=0.01).minimize(err)
Unser gewählter Optimierer mit der Lernrate 0.01.init = tf.global_variables_initializer()
Initialisierung der Variablen
4.4. Ausführung des Graphen: Test der Achterbahn
Wenn wir den unten stehenden Code mal grob betrachten, dann fällt vor allem die Zeile mit dem with
-(Python)Operator und dem tf.Session()
-(TensorFlow)Operator auf. Der tf.Session()
-Operator leitet unsere Ausführung ein. Warum wir with
nutzen hat den Grund, dass dieser Operator uns das Leben einfacher macht, da dieser die nachfolgenden Befehle wieder schließt und damit wieder Leistungsressourcen frei werden. Werden zum Beispiel Daten aus externen Quellen benötigt – sei es eine Excel- oder eine SQL-Tabelle – dann schließt uns der with
Operator die geöffneten Dateien, nachdem er alle unsere Befehle durchgeführt hat.
Durch die Methode .run()
werden dann die in der Klammer befindenden Größen bearbeitet. Mit dem Parameter feed_dict=
füllen wir den Graphen mit unseren gewünschten Dateien.
Wir lassen das Ganze 100 mal Testfahren um die optimalen Variablen zu finden. In Abb. 4 sehen wir die Verläufe der Fehlerfunktion, der Geschwindigkeit und der Höhe.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# Rundenanzahl runden = 100 # Array zum notieren der Größen v_array = [] h_array = [] # Aufzeichnung des Fehlerwertes loss = [] # Ausführung des Graphen with tf.Session() as sess: # Initialisierung dar Variablen sess.run(init) # Beginn der 100 Fahrten for i in range(runden): # Ausgabe der Werte _, geschw ,hoehe ,Y_pred , error = sess.run([opt,v,h , y_pred, err], feed_dict = {wag:x_input, y_true:y_input } ) loss.append(np.mean(error)) v_array.append(float(geschw)) h_array.append(float(hoehe)) # Ausgabe der letzten Werte print('Angstlvl: \n {}'.format(Y_pred)) print('\n Geschwindigkeit: {}'.format(geschw)) print('\n Starthöhe: {}'.format(hoehe)) print('\n Fehler: {}'.format(error)) |
In Tab.2 sind nun zwei Fahrgäste zu sehen, die sich wünschen, die Bahn nie gefahren zu sein! Deren Angstlevel () ist über 0 und damit wird der Wunsch getriggert wurde; so wie wir es auch beabsichtigt haben!
Angstlvl berechnet: | Fehler: | Geschwindigkeit: | Starthöhe: |
[0. ] | [0. ] | [0.4536] | [-2.5187] |
[0. ] | [0. ] | ||
[0. ] | [0. ] | ||
[0. ] | [0. ] | ||
[0. ] | [0. ] | ||
[0. ] | [0. ] | ||
[0.2060 ] -> Wunsch getriggert | [0.6304] | ||
[1.5685] -> Wunsch getriggert | [0.3231] |

Abb.4: Verläufe der Fehler-, Geschwindigkeits- und Höhenfunktion durch Optimierung
5. Zusammenfassung und Ausblick
Zugegeben ist dieser ganze Aufwand für ein mehr oder weniger linearen Zusammenhang etwas übertrieben und bestimmt ist dem einen oder anderen aufgefallen, dass unser Beispiel mit der Achterbahn an manchen Stellen hinkt. Dennoch hoffe ich, dass ich mit der Analogie das Verständnis von TensorFlow rüberbringen konnte. Lasst uns daher nochmal die wichtigsten Punkte zusammenfassen:
Die Arbeit mit TensorFlow unterteilt sich in folgende Phasen:
- Erstellung des Graphen: In dieser Phase konzentrieren wir uns darauf einen Berechnungsgraphen zu erstellen, welcher so konzipiert wird, dass er uns am Ende das Ergebnis ausgibt, welches wir uns wünschen.
- Platzhalter: Eine der wichtigsten Sturkturen in TensorFlow ist der Platzhalter. Er ist dafür zuständig, wenn es darum geht externe Daten in unseren Graph einfließen zu lassen. Bei der Erstellung eines Platzhalters müssen wir zumindest den Datentypen angeben.
- Variable: Wenn es darum geht Größen für ein Modell zu optimieren, stellt TensorFlow Variablen zur Verfügung. Diese benötigen eine Angabe, wie die Form des Tensors aussehen soll.
- Ausführung des Graphen: Nachdem wir unseren Graphen entwickelt haben, ist der nächste Schritt diesen auszuführen.
- Dies machen wir mit dem Befehl
tf.Session()
und führen diesen dann mit der Methode.run()
aus - Ebenfalls hat die Optimierung einen wichtigen Bestand in dieser Phase
- Um unseren Graphen mit den Daten zu füllen, nutzen wir den wird den Parameter
feed_dict=
- Dies machen wir mit dem Befehl
Um diesen Artikel nicht in die Länge zu ziehen, wurden die Themen der Matrizenmultiplikation, Aktivierungsfunktion und Optimierung erstmal nur angerissen. Wir wollen in einem separaten Artikel näher darauf eingehen. Für den Anfang genügen wir uns damit, dass wir von diesen Elementen wissen und dass sie einen wichtigen Bestandteil haben, wenn wir neuronale Netze aufbauen wollen.
In nächsten Artikel werden wir dann ein Perzeptron erstellen und gehen auch näher auf die Themen ein, die wir in diesem Teil nur angerissen haben. Bleibt gespannt!
6. Bonus-Material
Mit Tensorboard ist es möglich unseren entwickelten Graphen auch plotten und auszugeben zu lassen. So sieht unser Graph aus:
Den Programmiercode könnt ihr in diesem Link auch als Ganzes betrachten.
Einstieg in Natural Language Processing – Teil 1: Natürliche vs. Formale Sprachen
/1 Comment/in Data Science, Data Science Hack, Natural Language Processing, Python, Python, Text Mining, Tool Introduction, Tools, Tutorial /by Christopher KippDies ist Artikel 1 von 4 der Artikelserie Einstieg in Natural Language Processing – Artikelserie.
Versuche und erste Ansätze, Maschinen beizubringen menschliche Sprache zu verstehen, gibt es bereits seit den 50er Jahren. Trotz der jahrzehntelangen Forschung und Entwicklung gelingt dies bis heute nicht umfassend. Woran liegt dies?
Um diese Frage zu beantworten, hilft es, sich die Unterschiede zwischen „natürlichen“, also sich selbstständig entwickelnden, typischerweise von Menschen gesprochenen Sprachen und den von Computern interpretieren formalen Sprachen klar zu machen. Formale Sprachen, wie zum Beispiel Python zum Ausführen der Codebeispiele in dieser Artikelserie, HTML (Hyper Text Markup Language) zur Darstellung von Webseiten und andere typische Programmier- und Skriptsprachen, sind üblicherweise sehr streng strukturiert.
Alle diese Sprachen weisen eine Reihe von Gemeinsamkeiten auf, welche es Computern einfach machen, sie korrekt zu interpretieren (also den Informationsinhalt zu “verstehen”). Das vermutlich auffälligste Merkmal formaler Sprachen ist eine relativ strikte Syntax, welche (wenn überhaupt) nur geringe Abweichungen von einem Standard erlaubt. Wie penibel die jeweilige Syntax oft einzuhalten ist, wird am ehesten deutlich, wenn diese verletzt wird:
1 2 3 4 5 6 7 |
>>> print('Correct Syntax') Correct Syntax >>> print{'Wrong Syntax'} print{'Wrong Syntax'} ^ SyntaxError: invalid syntax |
Solche so genannten “Syntax Error” gehören daher zu den häufigsten Fehlern beim Schreiben von Quellcode.
Ganz anders dagegen sieht es in der Kommunikation mit natürlichen Sprachen aus. Zwar fördert falsche Komma-Setzung in der Regel nicht die Leserlichkeit eines Textes, jedoch bleibt dieser in der Regel trotzdem verständlich. Auch macht es keinen Unterschied ob ich sage „Es ist heiß heute.“ oder „Heute ist es heiß.“. Genau wie in der deutschen Sprache funktioniert dieses Beispiel auch im Englischen sowie in anderen natürlichen Sprachen. Insbesondere Spanisch ist ein Beispiel für eine Sprache mit extrem variabler Satzstellung. Jedoch kann in anderen Fällen eine andere Reihenfolge der selben Worte deren Bedeutung auch verändern. So ist „Ist es heute heiß?“ ganz klar eine Frage, obwohl exakt die selben Worte wie in den Beispielsätzen oben vorkommen.
Ein weiterer wichtiger, hiermit verwandter Unterschied ist, dass es bei formalen Sprachen in der Regel einen Ausdruck gibt, welcher eine spezifische Bedeutung besitzt, während es in natürlichen Sprachen oft viele Synonyme gibt, die ein und dieselbe Sache (oder zumindest etwas sehr ähnliches) ausdrücken. Ein wahrer boolscher Wert wird in Python als
1 |
True |
geschrieben. Es gibt keine andere Möglichkeit, diesen Wert auszudrücken (zumindest nicht ohne irgend eine Art von Operatoren wie das Doppelgleichheitszeichen zu benutzen und damit z. B. “0 == 0” zu schreiben). Anders hingegen zum Beispiel in der Deutschen Sprache: Wahr, richtig, korrekt, stimmt, ja, …
Um einen Vorstellung davon zu bekommen, wie verbreitet Synonyme in natürlichen Sprachen sind, lässt sich die Internetseite https://www.openthesaurus.de verwenden. Beispielshalber findet man dutzende Synonyme für das Wort „schnell“ hier: https://www.openthesaurus.de/synonyme/schnell
Eine weitere große Schwierigkeit, welche in den meisten natürlichen Sprachen und nahezu allen Arten von Texten zu finden ist, stellen verschiedene grammatikalische Formen eines Wortes dar. So sind die Worte bin, wäre, sind, waren, wirst, werden… alles Konjugationen desselben Verbs, nämlich sein. Eine durchaus beeindruckende Übersicht über die verwirrende Vielfalt von Konjugationen dieses kleinen Wörtchens, findet sich unter: https://www.verbformen.de/konjugation/sein.htm.
Dieses Problem wird um so schwerwiegender, da viele Verben, insbesondere die am häufigsten genutzten, sehr unregelmäßige Konjugationsformen besitzen und damit keiner generellen Regel folgen. Daher ist computerintern oft ein Mapping für jede mögliche Konjugationsform bei vielen Verben die einzige Möglichkeit, an die Grundform zu kommen (mehr dazu in Teil 3 dieser Artikelserie).
Die Liste der sprachlichen Schwierigkeiten beim computergestützten Auswerten natürlicher Sprache ließe sich an diesem Punkt noch beliebig weiter fortsetzen:
- Rechtschreibfehler
- falsche Grammatik
- Smileys
- der „Substantivverkettungswahn“ im Deutschen
- mehrdeutige Worte und Abkürzungen
- abwegige Redewendungen (z. B. “ins Gras beißen”)
- Ironie
- und, und, und …
Ob und welche Rolle jede dieser Schwierigkeiten im einzelnen spielt, hängt natürlich sehr stark von den jeweiligen Texten ab und kann nicht pauschalisiert werden – ein typischer Chatverlauf wird ganz andere Probleme bereithalten als ein Wikipedia-Artikel. Wie man einige dieser Probleme in der Praxis vereinfachen oder sogar lösen kann und welche Ansätze und Methoden zur Verfügung stehen und regelmäßig zur Anwendung kommen wird im nächsten Teil dieser Artikelserie an praktischen Codebeispielen genauer unter die Lupe genommen.
NLTK vs. Spacy – Eine kurze Übersicht
Möchte man einen (oder auch einige) Text(e) mit den Methoden des natural language processings untersuchen um die darin verwendete Sprache auswerten oder nach bestimmten Informationen suchen, so sind insbesondere die Pakete NLTK und spaCy zu empfehlen (bei sehr vielen Texten sieht das schon wieder anders aus und wird am Ende der Artikelserie mit dem Paket gensim vorgestellt); beide bieten eine unglaubliche Vielzahl von Analysemöglichkeiten, vorgefertigten Wortsets, vortrainierte Stemmer und Lemmatiser, POS Tagger und, und, und…
Ist man vor allem an den Ergebnissen der Analyse selbst interessiert, so bietet sich spaCy an, da hier bereits mit wenigen Zeilen Code viele interessante Informationen generiert werden können.
Wer dagegen gerne selber bastelt oder wissen möchte wie die einzelnen Tools und Teilschritte genau funktionieren oder sich seine eigenen Stemmer, Tagger ect. trainieren will, ist vermutlich mit NLTK besser beraten. Zwar ist hier oft mehr Quellcode für das gleiche Ergebnis notwendig, allerdings kann das Preprocessing der Texte hierbei relativ einfach exakt den eigenen Vorstellungen angepasst werden. Zudem bietet NLTK eine Vielzahl von Beispieltexten und bereits fertig getagte Daten, mit welchen eigene Tagger trainiert und getestet werden können.
Einstieg in Natural Language Processing – Artikelserie
/1 Comment/in Business Analytics, Business Intelligence, Customer Analytics, Data Mining, Data Science, Data Science Hack, Main Category, Natural Language Processing, Python, Python, Text Mining, Tutorial /by Christopher KippUnter Natural Language Processing (NLP) versteht man ein Teilgebiet der Informatik bzw. der Datenwissenschaft, welches sich mit der Analyse und Auswertung , aber auch der Synthese natürlicher Sprache befasst. Mit natürlichen Sprachen werden Sprachen wie zum Beispiel Deutsch, Englisch oder Spanisch bezeichnet, welche nicht geplant entworfen wurden, sondern sich über lange Zeit allein durch ihre Benutzung entwickelt haben. Anders ausgedrückt geht es um die Schnittstelle zwischen unserer im Alltag verwendeten und für uns Menschen verständlichen Sprache auf der einen, und um deren computergestützte Auswertung auf der anderen Seite.
Diese Artikelserie soll eine Einführung in die Thematik des Natural Language Processing sein, dessen Methoden, Möglichkeiten, aber auch der Grenzen . Im einzelnen werden folgende Themen näher behandelt:
1. Artikel – Natürliche vs. Formale Sprachen
2. Artikel – Preprocessing von Rohtext mit Python
3. Artikel – Möglichkeiten/Methoden der Textanalyse an Beispielen (erscheint demnächst…)
4. Artikel – NLP, was kann es? Und was nicht? (erscheint demnächst…)
Zur Verdeutlichung der beschriebenen Zusammenhänge und Methoden und um Interessierten einige Ideen für mögliche Startpunkte aufzuzeigen, werden im Verlauf der Artikelserie an verschiedenen Stellen Codebeispiele in der Programmiersprache Python vorgestellt.
Von den vielen im Internet zur Verfügung stehenden Python-Paketen zum Thema NLP, werden in diesem Artikel insbesondere die drei Pakete NLTK, Gensim und Spacy verwendet.
I. Einführung in TensorFlow: Einleitung und Inhalt
/0 Comments/in Artificial Intelligence, Data Science, Deep Learning, Machine Learning, Python, Tutorial /by Hoang Tu Nguyen
1. Einleitung und Inhalt
Früher oder später wird jede Person, welche sich mit den Themen Daten, KI, Machine Learning und Deep Learning auseinander setzt, mit TensorFlow in Kontakt geraten. Für diejenigen wird der Zeitpunkt kommen, an dem sie sich damit befassen möchten/müssen/wollen.
Und genau für euch ist diese Artikelserie ausgelegt. Gemeinsam wollen wir die ersten Schritte in die Welt von Deep Learning und neuronalen Netzen mit TensorFlow wagen und unsere eigenen Beispiele realisieren. Dabei möchten wir uns auf das Wesentlichste konzentrieren und die Thematik Schritt für Schritt in 4 Artikeln angehen, welche wie folgt aufgebaut sind:
- In diesem und damit ersten Artikel wollen wir uns erst einmal darauf konzentrieren, was TensorFlow ist und wofür es genutzt wird.
- Im zweiten Artikel befassen wir uns mit der grundlegenden Handhabung von TensorFlow und gehen den theoretischen Ablauf durch.
- Im dritten Artikel wollen wir dann näher auf die Praxis eingehen und ein Perzeptron – ein einfaches künstliches Neuron – entwickeln. Dabei werden wir die Grundlagen anwenden, die wir im zweiten Artikel erschlossen haben.
Wenn ihr die Praxisbeispiele in den Artikeln 3 & 4 aktiv mit bestreiten wollt, dann ist es vorteilhaft, wenn ihr bereits mit Python gearbeitet habt und die Grundlagen dieser Programmiersprache beherrscht. Jedoch werden alle Handlungen und alle Zeilen sehr genau kommentiert, so dass es leicht verständlich bleibt.
Neben den Programmierfähigkeiten ist es hilfreich, wenn ihr euch mit der Funktionsweise von neuronalen Netzen auskennt, da wir im späteren Verlauf diese modellieren wollen. Jedoch gehen wir vor der Programmierung kurz auf die Theorie ein und werden das Wichtigste nochmal erwähnen.
Zu guter Letzt benötigen wir für unseren Theorie-Teil ein Mindestmaß an Mathematik um die Grundlagen der neuronalen Netze zu verstehen. Aber auch hier sind die Anforderungen nicht hoch und wir sind vollkommen gut damit bedient, wenn wir unser Wissen aus dem Abitur noch nicht ganz vergessen haben.
2. Ziele dieser Artikelserie
Diese Artikelserie ist speziell an Personen gerichtet, welche einen ersten Schritt in die große und interessante Welt von Deep Learning wagen möchten, die am Anfang nicht mit zu vielen Details überschüttet werden wollen und lieber an kleine und verdaulichen Häppchen testen wollen, ob dies das Richtige für sie ist. Unser Ziel wird sein, dass wir ein Grundverständnis für TensorFlow entwickeln und die Grundlagen zur Nutzung beherrschen, um mit diesen erste Modelle zu erstellen.
3. Was ist TensorFlow?
Viele von euch haben bestimmt von TensorFlow in Verbindung mit Deep Learning bzw. neuronalen Netzen gehört. Allgemein betrachtet ist TensorFlow ein Software-Framework zur numerischen Berechnung von Datenflussgraphen mit dem Fokus maschinelle Lernalgorithmen zu beschreiben. Kurz gesagt: Es ist ein Tool um Deep Learning Modelle zu realisieren.
Zusatz: Python ist eine Programmiersprache in der wir viele Paradigmen (objektorientiert, funktional, etc.) verwenden können. Viele Tutorials im Bereich Data Science nutzen das imperative Paradigma; wir befehlen Python also Was gemacht und Wie es ausgeführt werden soll. TensorFlow ist dahingehend anders, da es eine datenstrom-orientierte Programmierung nutzt. In dieser Form der Programmierung wird ein Datenfluss-Berechnungsgraph (kurz: Datenflussgraph) erzeugt, welcher durch die Zusammensetzung von Kanten und Knoten charakterisiert wird. Die Kanten enthalten Daten und können diese an Knoten weiterleiten. In den Knoten werden Operationen wie z. B. Addition, Multiplikation oder auch verschiedenste Variationen von Funktionen ausgeführt. Bekannte Programme mit datenstrom-orientierten Paradigmen sind Simulink, LabView oder Knime.
Für das Verständnis von TensorFlow verrät uns der Name bereits erste Informationen über die Funktionsweise. In neuronalen Netzen bzw. in Deep-Learning-Netzen können Eingangssignale, Gewichte oder Bias verschiedene Erscheinungsformen haben; von Skalaren, zweidimensionalen Tabellen bis hin zu mehrdimensionalen Matrizen kann alles dabei sein. Diese Erscheinungsformen werden in Deep-Learning-Anwendungen allgemein als Tensoren bezeichnet, welche durch ein Datenflussgraph ‘fließen’. [1]

4. Warum TensorFlow?
Wer in die Welt der KI einsteigen und Deep Learning lernen will, hat heutzutage die Qual der Wahl. Neben TensorFlow gibt es eine Vielzahl von Alternativen wie Keras, Theano, Pytorch, Torch, Caffe, Caffe2, Mxnet und vielen anderen. Warum also TensorFlow?
Das wohl wichtigste Argument besteht darin, dass TensorFlow eine der besten Dokumentationen hat. Google – Herausgeber von TensorFlow – hat TensorFlow stets mit neuen Updates beliefert. Sicherlich aus genau diesen Gründen ist es das meistgenutzte Framework. Zumindest erscheint es so, wenn wir die Stars&Forks auf Github betrachten. [3] Das hat zur Folge, dass neben der offiziellen Dokumentation auch viele Tutorials und Bücher existieren, was die Doku nur noch besser macht.
Natürlich haben alle Frameworks ihre Vor- und Nachteile. Gerade Pytorch von Facebook erfreut sich derzeit großer Beliebtheit, da die Berechnungsgraphen dynamischer Natur sind und damit einige Vorteile gegenüber TensorFlow aufweisen.[2] Auch Keras wäre für den Einstieg eine gute Alternative, da diese Bibliothek großen Wert auf eine einsteiger- und nutzerfreundliche Handhabung legt. Keras kann man sich als eine Art Bedienoberfläche über unsere Frameworks vorstellen, welche vorgefertigte neuronale Netze bereitstellt und uns einen Großteil der Arbeit abnimmt.
Möchte man jedoch ein detailreiches und individuelles Modell bauen und die Theorie dahinter nachvollziehen können, dann ist TensorFlow der beste Einstieg in Deep Learning! Es wird einige Schwierigkeiten bei der Gestaltung unserer Modelle geben, aber durch die gute Dokumentation, der großen Community und der Vielzahl an Beispielen, werden wir gewiss eine Lösung für aufkommende Problemstellungen finden.

5. Zusammenfassung und Ausblick
Fassen wir das Ganze nochmal zusammen: TensorFlow ist ein Framework, welches auf der datenstrom-orientierten Programmierung basiert und speziell für die Implementierung von Machine/Deep Learning-Anwendungen ausgelegt ist. Dabei fließen unsere Daten durch eine mehr oder weniger komplexe Anordnung von Berechnungen, welche uns am Ende ein Ergebnis liefert.
Die wichtigsten Argumente zur Wahl von TensorFlow als Einstieg in die Welt des Deep Learnings bestehen darin, dass TensorFlow ausgezeichnet dokumentiert ist, eine große Community besitzt und relativ einfach zu lesen ist. Außerdem hat es eine Schnittstelle zu Python, welches durch die meisten Anwender im Bereich der Datenanalyse bereits genutzt wird.
Wenn ihr es bis hier hin geschafft habt und immer noch motiviert seid den Einstieg mit TensorFlow zu wagen, dann seid gespannt auf den nächsten Artikel. In diesem werden wir dann auf die Funktionsweise von TensorFlow eingehen und einfache Berechnungsgraphen aufbauen, um ein Grundverständnis von TensorFlow zu bekommen. Bleibt also gespannt!
Quellen
[1] Hope, Tom (2018): Einführung in TensorFlow: DEEP-LEARNING-SYSTEME PROGRAMMIEREN, TRAINIEREN, SKALIEREN UND DEPLOYEN, 1. Auflage
[2] https://www.marutitech.com/top-8-deep-learning-frameworks/
[3] https://github.com/mbadry1/Top-Deep-Learning
[4] https://www.bigdata-insider.de/was-ist-keras-a-726546/
How To Remotely Send R and Python Execution to SQL Server from Jupyter Notebooks
/0 Comments/in Data Mining, Data Science, Data Science Hack, Data Warehousing, Database, Main Category, Python, Python, R Statistics, SQL, Tools, Tutorial /by Kyle WellerIntroduction
Did you know that you can execute R and Python code remotely in SQL Server from Jupyter Notebooks or any IDE? Machine Learning Services in SQL Server eliminates the need to move data around. Instead of transferring large and sensitive data over the network or losing accuracy on ML training with sample csv files, you can have your R/Python code execute within your database. You can work in Jupyter Notebooks, RStudio, PyCharm, VSCode, Visual Studio, wherever you want, and then send function execution to SQL Server bringing intelligence to where your data lives.
This tutorial will show you an example of how you can send your python code from Juptyter notebooks to execute within SQL Server. The same principles apply to R and any other IDE as well. If you prefer to learn through videos, this tutorial is also published on YouTube here:
Environment Setup Prerequisites
- Install ML Services on SQL Server
In order for R or Python to execute within SQL, you first need the Machine Learning Services feature installed and configured. See this how-to guide.
- Install RevoscalePy via Microsoft’s Python Client
In order to send Python execution to SQL from Jupyter Notebooks, you need to use Microsoft’s RevoscalePy package. To get RevoscalePy, download and install Microsoft’s ML Services Python Client. Documentation Page or Direct Download Link (for Windows).
After downloading, open powershell as an administrator and navigate to the download folder. Start the installation with this command (feel free to customize the install folder): .\Install-PyForMLS.ps1 -InstallFolder “C:\Program Files\MicrosoftPythonClient”
Be patient while the installation can take a little while. Once installed navigate to the new path you installed in. Let’s make an empty folder and open Jupyter Notebooks: mkdir JupyterNotebooks; cd JupyterNotebooks; ..\Scripts\jupyter-notebook
Create a new notebook with the Python 3 interpreter:
To test if everything is setup, import revoscalepy in the first cell and execute. If there are no error messages you are ready to move forward.
Database Setup (Required for this tutorial only)
For the rest of the tutorial you can clone this Jupyter Notebook from Github if you don’t want to copy paste all of the code. This database setup is a one time step to ensure you have the same data as this tutorial. You don’t need to perform any of these setup steps to use your own data.
- Create a database
Modify the connection string for your server and use pyodbc to create a new database.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import pyodbc # creating a new db to load Iris sample in new_db_name = "MLRemoteExec" connection_string = "Driver=SQL Server;Server=localhost\MSSQLSERVER2017;Database={0};Trusted_Connection=Yes;" cnxn = pyodbc.connect(connection_string.format("master"), autocommit=True) cnxn.cursor().execute("IF EXISTS(SELECT * FROM sys.databases WHERE [name] = '{0}') DROP DATABASE {0}".format(new_db_name)) cnxn.cursor().execute("CREATE DATABASE " + new_db_name) cnxn.close() print("Database created") |
- Import Iris sample from SkLearn
Iris is a popular dataset for beginner data science tutorials. It is included by default in sklearn package.
1 2 3 4 |
from sklearn import datasetsimport pandas as pd # SkLearn has the Iris sample dataset built in to the packageiris = datasets.load_iris() df = pd.DataFrame(iris.data, columns=iris.feature_names) |
- Use RecoscalePy APIs to create a table and load the Iris data
(You can also do this with pyodbc, sqlalchemy or other packages)
1 2 3 4 5 |
from revoscalepy import RxSqlServerData, rx_data_step # Example of using RX APIs to load data into SQL table. You can also do this with pyodbc table_ref = RxSqlServerData(connection_string=connection_string.format(new_db_name), table="Iris")rx_data_step(input_data = df, output_file = table_ref, overwrite = True)print("New Table Created: Iris") print("Sklearn Iris sample loaded into Iris table") |
Define a Function to Send to SQL Server
Write any python code you want to execute in SQL. In this example we are creating a scatter matrix on the iris dataset and only returning the bytestream of the .png back to Jupyter Notebooks to render on our client.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
def send_this_func_to_sql(): from revoscalepy import RxSqlServerData, rx_import from pandas.tools.plotting import scatter_matrix import matplotlib.pyplot as plt import io # remember the scope of the variables in this func are within our SQL Server Python Runtime connection_string = "Driver=SQL Server;Server=localhost\MSSQLSERVER2017; Database=MLRemoteExec;Trusted_Connection=Yes;" # specify a query and load into pandas dataframe df sql_query = RxSqlServerData(connection_string=connection_string, sql_query = "select * from Iris") df = rx_import(sql_query) scatter_matrix(df) # return bytestream of image created by scatter_matrix buf = io.BytesIO() plt.savefig(buf, format="png") buf.seek(0) return buf.getvalue() |
Send execution to SQL
Now that we are finally set up, check out how easy sending remote execution really is! First, import revoscalepy. Create a sql_compute_context, and then send the execution of any function seamlessly to SQL Server with RxExec. No raw data had to be transferred from SQL to the Jupyter Notebook. All computation happened within the database and only the image file was returned to be displayed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from IPython import display import matplotlib.pyplot as plt from revoscalepy import RxInSqlServer, rx_exec# create a remote compute context with connection to SQL Server sql_compute_context = RxInSqlServer(connection_string=connection_string.format(new_db_name)) # use rx_exec to send the function execution to SQL Server image = rx_exec(send_this_func_to_sql, compute_context=sql_compute_context)[0] # only an image was returned to my jupyter client. All data remained secure and was manipulated in my db. display.Image(data=image) |
While this example is trivial with the Iris dataset, imagine the additional scale, performance, and security capabilities that you now unlocked. You can use any of the latest open source R/Python packages to build Deep Learning and AI applications on large amounts of data in SQL Server. We also offer leading edge, high-performance algorithms in Microsoft’s RevoScaleR and RevoScalePy APIs. Using these with the latest innovations in the open source world allows you to bring unparalleled selection, performance, and scale to your applications.
Learn More
Check out SQL Machine Learning Services Documentation to learn how you can easily deploy your R/Python code with SQL stored procedures making them accessible in your ETL processes or to any application. Train and store machine learning models in your database bringing intelligence to where your data lives.
- Basic R and Python Execution in SQL Server: https://aka.ms/BasicMLServicesExecution
- Set up Machine Learning Services in SQL Server: https://aka.ms/SetupMLServices
- End-to-end tutorial solutions on Github: https://microsoft.github.io/sql-ml-tutorials/
Other YouTube Tutorials:
- How to Install SQL Server Machine Learning Services: https://aka.ms/InstallMLServices
- How to Enable SQL Server Machine Learning Services: https://aka.ms/EnableMLServices
- Basics of R and Python Execution in SQL: https://aka.ms/ExecuteMLServices
Bringing intelligence to where data lives: Python & R embedded in T-SQL
/1 Comment/in Business Analytics, Business Intelligence, Data Engineering, Data Science, Data Science Hack, Data Science News, Main Category, Python, R Statistics, SQL, Tool Introduction, Tutorial /by Kyle WellerIntroduction
Did you know that you can write R and Python code within your T-SQL statements? Machine Learning Services in SQL Server eliminates the need for data movement. Instead of transferring large and sensitive data over the network or losing accuracy with sample csv files, you can have your R/Python code execute within your database. Easily deploy your R/Python code with SQL stored procedures making them accessible in your ETL processes or to any application. Train and store machine learning models in your database bringing intelligence to where your data lives.
You can install and run any of the latest open source R/Python packages to build Deep Learning and AI applications on large amounts of data in SQL Server. We also offer leading edge, high-performance algorithms in Microsoft’s RevoScaleR and RevoScalePy APIs. Using these with the latest innovations in the open source world allows you to bring unparalleled selection, performance, and scale to your applications.
If you are excited to try out SQL Server Machine Learning Services, check out the hands on tutorial below. If you do not have Machine Learning Services installed in SQL Server,you will first want to follow the getting started tutorial I published here:
How-To Tutorial
In this tutorial, I will cover the basics of how to Execute R and Python in T-SQL statements. If you prefer learning through videos, I also published the tutorial on YouTube.
Basics
Open up SQL Server Management Studio and make a connection to your server. Open a new query and paste this basic example: (While I use Python in these samples, you can do everything with R as well)
1 2 |
EXEC sp_execute_external_script @language = N'Python', @script = N'print(3+4)' |
Sp_execute_external_script is a special system stored procedure that enables R and Python execution in SQL Server. There is a “language” parameter that allows us to choose between Python and R. There is a “script” parameter where we can paste R or Python code. If you do not see an output print 7, go back and review the setup steps in this article.
Parameter Introduction
Now that we discussed a basic example, let’s start adding more pieces:
1 2 3 4 5 6 |
EXEC sp_execute_external_script @language =N'Python', @script = N' OutputDataSet = InputDataSet; ', @input_data_1 =N'SELECT 1 AS Col1'; |
Machine Learning Services provides more natural communications between SQL and R/Python with an input data parameter that accepts any SQL query. The input parameter name is called “input_data_1”.
You can see in the python code that there are default variables defined to pass data between Python and SQL. The default variable names are “OutputDataSet” and “InputDataSet” You can change these default names like this example:
1 2 3 4 5 6 7 8 |
EXEC sp_execute_external_script @language =N'Python', @script = N' MyOutput = MyInput; ', @input_data_1_name = N'MyInput', @input_data_1 =N'SELECT 1 AS foo', @output_data_1_name =N'MyOutput'; |
As you executed these examples, you might have noticed that they each return a result with “(No column name)”? You can specify a name for the columns that are returned by adding the WITH RESULT SETS clause to the end of the statement which is a comma separated list of columns and their datatypes.
1 2 3 4 5 6 7 8 9 10 11 12 |
EXEC sp_execute_external_script @language =N'Python', @script=N' MyOutput = MyInput; ', @input_data_1_name = N'MyInput', @input_data_1 =N' SELECT 1 AS foo, 2 AS bar ', @output_data_1_name =N'MyOutput' WITH RESULT SETS ((MyColName int, MyColName2 int)); |
Input/Output Data Types
Alright, let’s discuss a little more about the input/output data types used between SQL and Python. Your input SQL SELECT statement passes a “Dataframe” to python relying on the Python Pandas package. Your output from Python back to SQL also needs to be in a Pandas Dataframe object. If you need to convert scalar values into a dataframe here is an example:
1 2 3 4 5 6 7 8 9 10 |
EXEC sp_execute_external_script @language =N'Python', @script=N' import pandas as pd c = 1/2 d = 1*2 s = pd.Series([c,d]) df = pd.DataFrame(s) OutputDataSet = df ' |
Variables c and d are both scalar values, which you can add to a pandas Series if you like, and then convert them to a pandas dataframe. This one shows a little bit more complicated example, go read up on the python pandas package documentation for more details and examples:
1 2 3 4 5 6 7 8 |
EXEC sp_execute_external_script @language =N'Python', @script=N' import pandas as pd s = {"col1": [1, 2], "col2": [3, 4]} df = pd.DataFrame(s) OutputDataSet = df ' |
You now know the basics to execute Python in T-SQL!
Did you know you can also write your R and Python code in your favorite IDE like RStudio and Jupyter Notebooks and then remotely send the execution of that code to SQL Server? Check out these documentation links to learn more: https://aka.ms/R-RemoteSQLExecution https://aka.ms/PythonRemoteSQLExecution
Check out the SQL Server Machine Learning Services documentation page for more documentation, samples, and solutions. Check out these E2E tutorials on github as well.
Would love to hear from you! Leave a comment below to ask a question, or start a discussion!
Interesting links
Here are some interesting links for you! Enjoy your stay :)Pages
- @Data Science Blog
- Autor werden!
- Autoren
- Become an Author
- CIO Interviews
- Computational and Data Science
- Coursera Data Science Specialization
- Coursera 用Python玩转数据 Data Processing Using Python
- Data Leader Day 2016 – Rabatt für Data Scientists!
- Data Science
- Data Science Insights
- Data Science Partner
- DATANOMIQ Big Data & Data Science Seminare
- DATANOMIQ Process Mining Workshop
- DATANOMIQ Seminare für Führungskräfte
- DataQuest.io – Interactive Learning
- Datenschutz
- Donation / Spende
- Education / Certification
- Fraunhofer Academy Zertifikatsprogramm »Data Scientist«
- Für Presse / Redakteure
- HARVARD Data Science Certificate Courses
- Home
- Impressum / Imprint
- MapR Big Data Expert
- Masterstudiengang Data Science
- Masterstudiengang Management & Data Science
- MongoDB University Online Courses
- O’Reilly Video Training – Data Science with R
- Products
- qSkills Workshop: Industrial Security
- Science Digital Intelligence & Marketing Analytics
- Show your Desk!
- Stanford University Online -Statistical Learning
- TU Chemnitz – Masterstudiengang Business Intelligence & Analytics
- TU Dortmund – Datenwissenschaft – Master of Science
- TU Dortmund berufsbegleitendes Zertifikatsstudium
- Weiterbildung mit Hochschulzertifikat Data Science & Business Analytics für Einsteiger
- WWU Münster – Zertifikatsstudiengang “Data Science”
- Zertifikatskurs „Data Science“
- Zertifizierter Business Analyst
Categories
- Apache Spark
- Artificial Intelligence
- Audit Analytics
- Big Data
- Books
- Business Analytics
- Business Intelligence
- Carrier
- Certification / Training
- Cloud
- Connected Car
- Customer Analytics
- Data Engineering
- Data Migration
- Data Mining
- Data Science
- Data Science at the Command Line
- Data Science Hack
- Data Science News
- Data Security
- Data Warehousing
- Database
- Datacenter
- Deep Learning
- Devices
- Education / Certification
- Events
- Excel / Power BI
- Experience
- Gerneral
- GPU-Processing
- Graph Database
- Hacking
- Hadoop
- Hadoop Framework
- Industrie 4.0
- InMemory
- Insights
- Interview mit CIO
- Interviews
- Java
- JavaScript
- Machine Learning
- Main Category
- Manufacturing
- Mathematics
- Mobile Device Management
- Mobile Devices
- Natural Language Processing
- Neo4J
- NoSQL
- Octave
- optimization
- Predictive Analytics
- Process Mining
- Projectmanagement
- Python
- Python
- R Statistics
- Re-Engineering
- Realtime Analytics
- Recommendations
- Scala
- Sponsoring Partner Posts
- SQL
- Statistics
- TensorFlow
- Text Mining
- Tool Introduction
- Tools
- Tutorial
- Uncategorized
- Use Case
- Use Cases
- Visualization
Archive
- December 2019
- November 2019
- October 2019
- September 2019
- August 2019
- July 2019
- June 2019
- May 2019
- April 2019
- March 2019
- February 2019
- January 2019
- December 2018
- November 2018
- October 2018
- September 2018
- August 2018
- July 2018
- June 2018
- May 2018
- April 2018
- March 2018
- February 2018
- January 2018
- December 2017
- November 2017
- October 2017
- September 2017
- August 2017
- July 2017
- June 2017
- May 2017
- April 2017
- March 2017
- February 2017
- January 2017
- December 2016
- November 2016
- October 2016
- September 2016
- August 2016
- July 2016
- June 2016
- May 2016
- April 2016
- March 2016
- February 2016
- January 2016
- December 2015
- November 2015
- October 2015
- September 2015
- August 2015
- July 2015
- June 2015
- May 2015