A Bird’s Eye View: How Machine Learning Can Help You Charge Your E-Scooters

Bird scooters in Columbus, Ohio

Bird scooters in Columbus, Ohio

Ever since I started using bike-sharing to get around in Seattle, I have become fascinated with geolocation data and the transportation sharing economy. When I saw this project leveraging the mobility data RESTful API from the Los Angeles Department of Transportation, I was eager to dive in and get my hands dirty building a data product utilizing a company’s mobility data API.

Unfortunately, the major bike and scooter providers (Bird, JUMP, Lime) don’t have publicly accessible APIs. However, some folks have seemingly been able to reverse-engineer the Bird API used to populate the maps in their Android and iOS applications.

One interesting feature of this data is the nest_id, which indicates if the Bird scooter is in a “nest” — a centralized drop-off spot for charged Birds to be released back into circulation.

I set out to ask the following questions:

  1. Can real-time predictions be made to determine if a scooter is currently in a nest?
  2. For non-nest scooters, can new nest location recommendations be generated from geospatial clustering?

To answer these questions, I built a full-stack machine learning web application, NestGenerator, which provides an automated recommendation engine for new nest locations. This application can help power Bird’s internal nest location generation that runs within their Android and iOS applications. NestGenerator also provides real-time strategic insight for Bird chargers who are enticed to optimize their scooter collection and drop-off route based on proximity to scooters and nest locations in their area.

Bird

The electric scooter market has seen substantial growth with Bird’s recent billion dollar valuation  and their $300 million Series C round in the summer of 2018. Bird offers electric scooters that top out at 15 mph, cost $1 to unlock and 15 cents per minute of use. Bird scooters are in over 100 cities globally and they announced in late 2018 that they eclipsed 10 million scooter rides since their launch in 2017.

Bird scooters in Tel Aviv, Israel

Bird scooters in Tel Aviv, Israel

With all of these scooters populating cities, there’s much-needed demand for people to charge them. Since they are electric, someone needs to charge them! A charger can earn additional income for charging the scooters at their home and releasing them back into circulation at nest locations. The base price for charging each Bird is $5.00. It goes up from there when the Birds are harder to capture.

Data Collection and Machine Learning Pipeline

The full data pipeline for building “NestGenerator”

Data

From the details here, I was able to write a Python script that returned a list of Bird scooters within a specified area, their geolocation, unique ID, battery level and a nest ID.

I collected scooter data from four cities (Atlanta, Austin, Santa Monica, and Washington D.C.) across varying times of day over the course of four weeks. Collecting data from different cities was critical to the goal of training a machine learning model that would generalize well across cities.

Once equipped with the scooter’s latitude and longitude coordinates, I was able to leverage additional APIs and municipal data sources to get granular geolocation data to create an original scooter attribute and city feature dataset.

Data Sources:

  • Walk Score API: returns a walk score, transit score and bike score for any location.
  • Google Elevation API: returns elevation data for all locations on the surface of the earth.
  • Google Places API: returns information about places. Places are defined within this API as establishments, geographic locations, or prominent points of interest.
  • Google Reverse Geocoding API: reverse geocoding is the process of converting geographic coordinates into a human-readable address.
  • Weather Company Data: returns the current weather conditions for a geolocation.
  • LocationIQ: Nearby Points of Interest (PoI) API returns specified PoIs or places around a given coordinate.
  • OSMnx: Python package that lets you download spatial geometries and model, project, visualize, and analyze street networks from OpenStreetMap’s APIs.

Feature Engineering

After extensive API wrangling, which included a four-week prolonged data collection phase, I was finally able to put together a diverse feature set to train machine learning models. I engineered 38 features to classify if a scooter is currently in a nest.

Full Feature Set

Full Feature Set

The features boiled down into four categories:

  • Amenity-based: parks within a given radius, gas stations within a given radius, walk score, bike score
  • City Network Structure: intersection count, average circuity, street length average, average streets per node, elevation level
  • Distance-based: proximity to closest highway, primary road, secondary road, residential road
  • Scooter-specific attributes: battery level, proximity to closest scooter, high battery level (> 90%) scooters within a given radius, total scooters within a given radius

 

Log-Scale Transformation

For each feature, I plotted the distribution to explore the data for feature engineering opportunities. For features with a right-skewed distribution, where the mean is typically greater than the median, I applied these log transformations to normalize the distribution and reduce the variability of outlier observations. This approach was used to generate a log feature for proximity to closest scooter, closest highway, primary road, secondary road, and residential road.

An example of a log transformation

Statistical Analysis: A Systematic Approach

Next, I wanted to ensure that the features I included in my model displayed significant differences when broken up by nest classification. My thinking was that any features that did not significantly differ when stratified by nest classification would not have a meaningful predictive impact on whether a scooter was in a nest or not.

Distributions of a feature stratified by their nest classification can be tested for statistically significant differences. I used an unpaired samples t-test with a 0.01% significance level to compute a p-value and confidence interval to determine if there was a statistically significant difference in means for a feature stratified by nest classification. I rejected the null hypothesis if a p-value was smaller than the 0.01% threshold and if the 99.9% confidence interval did not straddle zero. By rejecting the null-hypothesis in favor of the alternative hypothesis, it’s deemed there is a significant difference in means of a feature by nest classification.

Battery Level Distribution Stratified by Nest Classification to run a t-test

Battery Level Distribution Stratified by Nest Classification to run a t-test

Log of Closest Scooter Distribution Stratified by Nest Classification to run a t-test

Throwing Away Features

Using the approach above, I removed ten features that did not display statistically significant results.

Statistically Insignificant Features Removed Before Model Development

Model Development

I trained two models, a random forest classifier and an extreme gradient boosting classifier since tree-based models can handle skewed data, capture important feature interactions, and provide a feature importance calculation. I trained the models on 70% of the data collected for all four cities and reserved the remaining 30% for testing.

After hyper-parameter tuning the models for performance on cross-validation data it was time to run the models on the 30% of test data set aside from the initial data collection.

I also collected additional test data from other cities (Columbus, Fort Lauderdale, San Diego) not involved in training the models. I took this step to ensure the selection of a machine learning model that would generalize well across cities. The performance of each model on the additional test data determined which model would be integrated into the application development.

Performance on Additional Cities Test Data

The Random Forest Classifier displayed superior performance across the board

The Random Forest Classifier displayed superior performance across the board

I opted to move forward with the random forest model because of its superior performance on AUC score and accuracy metrics on the additional cities test data. AUC is the Area under the ROC Curve, and it provides an aggregate measure of model performance across all possible classification thresholds.

AUC Score on Test Data for each Model

AUC Score on Test Data for each Model

Feature Importance

Battery level dominated as the most important feature. Additional important model features were proximity to high level battery scooters, proximity to closest scooter, and average distance to high level battery scooters.

Feature Importance for the Random Forest Classifier

Feature Importance for the Random Forest Classifier

The Trade-off Space

Once I had a working machine learning model for nest classification, I started to build out the application using the Flask web framework written in Python. After spending a few days of writing code for the application and incorporating the trained random forest model, I had enough to test out the basic functionality. I could finally run the application locally to call the Bird API and classify scooter’s into nests in real-time! There was one huge problem, though. It took more than seven minutes to generate the predictions and populate in the application. That just wasn’t going to cut it.

The question remained: will this model deliver in a production grade environment with the goal of making real-time classifications? This is a key trade-off in production grade machine learning applications where on one end of the spectrum we’re optimizing for model performance and on the other end we’re optimizing for low latency application performance.

As I continued to test out the application’s performance, I still faced the challenge of relying on so many APIs for real-time feature generation. Due to rate-limiting constraints and daily request limits across so many external APIs, the current machine learning classifier was not feasible to incorporate into the final application.

Run-Time Compliant Application Model

After going back to the drawing board, I trained a random forest model that relied primarily on scooter-specific features which were generated directly from the Bird API.

Through a process called vectorization, I was able to transform the geolocation distance calculations utilizing NumPy arrays which enabled batch operations on the data without writing any “for” loops. The distance calculations were applied simultaneously on the entire array of geolocations instead of looping through each individual element. The vectorization implementation optimized real-time feature engineering for distance related calculations which improved the application response time by a factor of ten.

Feature Importance for the Run-time Compliant Random Forest Classifier

Feature Importance for the Run-time Compliant Random Forest Classifier

This random forest model generalized well on test-data with an AUC score of 0.95 and an accuracy rate of 91%. The model retained its prediction accuracy compared to the former feature-rich model, but it gained 60x in application performance. This was a necessary trade-off for building a functional application with real-time prediction capabilities.

Geospatial Clustering

Now that I finally had a working machine learning model for classifying nests in a production grade environment, I could generate new nest locations for the non-nest scooters. The goal was to generate geospatial clusters based on the number of non-nest scooters in a given location.

The k-means algorithm is likely the most common clustering algorithm. However, k-means is not an optimal solution for widespread geolocation data because it minimizes variance, not geodetic distance. This can create suboptimal clustering from distortion in distance calculations at latitudes far from the equator. With this in mind, I initially set out to use the DBSCAN algorithm which clusters spatial data based on two parameters: a minimum cluster size and a physical distance from each point. There were a few issues that prevented me from moving forward with the DBSCAN algorithm.

  1. The DBSCAN algorithm does not allow for specifying the number of clusters, which was problematic as the goal was to generate a number of clusters as a function of non-nest scooters.
  2. I was unable to hone in on an optimal physical distance parameter that would dynamically change based on the Bird API data. This led to suboptimal nest locations due to a distortion in how the physical distance point was used in clustering. For example, Santa Monica, where there are ~15,000 scooters, has a higher concentration of scooters in a given area whereas Brookline, MA has a sparser set of scooter locations.

An example of how sparse scooter locations vs. highly concentrated scooter locations for a given Bird API call can create cluster distortion based on a static physical distance parameter in the DBSCAN algorithm. Left:Bird scooters in Brookline, MA. Right:Bird scooters in Santa Monica, CA.

An example of how sparse scooter locations vs. highly concentrated scooter locations for a given Bird API call can create cluster distortion based on a static physical distance parameter in the DBSCAN algorithm. Left:Bird scooters in Brookline, MA. Right:Bird scooters in Santa Monica, CA.

Given the granularity of geolocation scooter data I was working with, geospatial distortion was not an issue and the k-means algorithm would work well for generating clusters. Additionally, the k-means algorithm parameters allowed for dynamically customizing the number of clusters based on the number of non-nest scooters in a given location.

Once clusters were formed with the k-means algorithm, I derived a centroid from all of the observations within a given cluster. In this case, the centroids are the mean latitude and mean longitude for the scooters within a given cluster. The centroids coordinates are then projected as the new nest recommendations.

NestGenerator showcasing non-nest scooters and new nest recommendations utilizing the K-Means algorithm

NestGenerator showcasing non-nest scooters and new nest recommendations utilizing the K-Means algorithm.

NestGenerator Application

After wrapping up the machine learning components, I shifted to building out the remaining functionality of the application. The final iteration of the application is deployed to Heroku’s cloud platform.

In the NestGenerator app, a user specifies a location of their choosing. This will then call the Bird API for scooters within that given location and generate all of the model features for predicting nest classification using the trained random forest model. This forms the foundation for map filtering based on nest classification. In the app, a user has the ability to filter the map based on nest classification.

Drop-Down Map View filtering based on Nest Classification

Drop-Down Map View filtering based on Nest Classification

Nearest Generated Nest

To see the generated nest recommendations, a user selects the “Current Non-Nest Scooters & Predicted Nest Locations” filter which will then populate the application with these nest locations. Based on the user’s specified search location, a table is provided with the proximity of the five closest nests and an address of the Nest location to help inform a Bird charger in their decision-making.

NestGenerator web-layout with nest addresses and proximity to nearest generated nests

NestGenerator web-layout with nest addresses and proximity to nearest generated nests

Conclusion

By accurately predicting nest classification and clustering non-nest scooters, NestGenerator provides an automated recommendation engine for new nest locations. For Bird, this application can help power their nest location generation that runs within their Android and iOS applications. NestGenerator also provides real-time strategic insight for Bird chargers who are enticed to optimize their scooter collection and drop-off route based on scooters and nest locations in their area.

Code

The code for this project can be found on my GitHub

Comments or Questions? Please email me an E-Mail!

 

Introduction to ROC Curve

The abbreviation ROC stands for Receiver Operating Characteristic. Its main purpose is to illustrate the diagnostic ability of classifier as the discrimination threshold is varied. It was developed during World War II when Radar operators had to decide if the blip on the screen is an enemy target, a friendly ship or just a noise.  For these purposes they measured the ability of a radar receiver operator to make these important distinctions, which was called the Receiver Operating Characteristic.

Later it was found useful in interpreting medical test results and then in Machine learning classification problems. In order to get an introduction to binary classification and terms like ‘precision’ and ‘recall’ one can look into my earlier blog  here.

True positive rate and false positive rate

Let’s imagine a situation where a fire alarm is installed in a kitchen. The alarm is supposed to emit a sound in case fire smoke is detected in the room. Unfortunately, there is a lot of cooking done in the kitchen and the alarm may trigger the sound too often. Thus, instead of serving a purpose the alarm becomes a nuisance due to a large number of false alarms. In statistical terms these types of errors are called type 1 errors, or false positives.

One way to deal with this problem is to simply decrease sensitivity of the device. We do this by increasing the trigger threshold at the alarm setting. But then, not every alarm should have the same threshold setting. Consider the same type of device but kept in a bedroom. With high threshold, the device might miss smoke from a real short-circuit in the wires which poses a real danger of fire. This kind of failure is called Type 2 error or a false negative. Although the two devices are the same, different types of threshold settings are optimal for different circumstances.

To specify this more formally, let us describe the performance of a binary classifier at a particular threshold by the following parameters:

 

These parameters take different values at different thresholds. Hence, they define the performance of the classifier at particular threshold. But we want to examine in overall how good a classifier is. Fortunately, there is a way to do that. We plot the True Positive Rate (TPR) and False Positive rate (FPR) at different thresholds and this plot is called ROC curve.

Let’s try to understand this with an example.

A case with a distinct population distribution

Let’s suppose there is a disease which can be identified with deficiency of some parameter (maybe a certain vitamin). The distribution of population with this disease has a mean vitamin concentration sharply distinct from the mean of a healthy population, as shown below.

This is result of dummy data simulating population of 2000 people,the link to the code is given  in the end of this blog.  As the two populations are distinctly separated (there is no  overlap between the two distributions), we can expect that a classifier would have an easy job distinquishing healthy from sick people. We can run a logistic regression classifier with a threshold of .5 and be 100% succesful in detecting the decease.

The confusion matrix may look something like this.

In this ideal case with a threshold  of  .5 we do not make a single wrong classification. The True positive rate and False positive rate are 1 and 0, respectively. But we can shift the threshold. In that case, we will  get different confusion matrices. First we plot threshold vs. TPR.

We see for most values of threshold the TPR is close to 1 which again proves data is easy to classify and the classifier is returning high probabilities  for the most of positives .

Similarly Let’s plot threshold vs. FPR.

For most of the data points FPR is close to zero. This is also good. Now its time to plot the ROC curve using these results (TPR vs FPR).

Let’s try to interpret  the results,  all the points lie on line x=0 and y=1, it means for all the points FPR is zero or TPR is one, making  the curve a square. which means the classifier does perfectly well.

Case with overlapping  population distribution

The above example was about a perfect classifer. However, life is often not so easy. Now let us consider another more realistic situation in which the parameter distribution of the population is not as distinct as in the previous case. Rather, the mean of the parameter with healthy and not healthy datapoints are close and the distributions overlap, as shown in the next figure.

If we set the threshold to 0.5, the confusion matrix may look like this.

Now, any new choice of threshold location will affect both false positives and false negatives. In fact, there is a trade-off. If we shift the threshold with the goal to reduce false negatives, false positives will increase. If we move the threshold to the other direction and reduce false positive, false negatives will increase.

The plots (TPR vs Threshold) , (FPR vs Threshold) are shown below

If we plot the ROC curve from these results, it looks like this:

From the curve we see the classifier does not perform as well as the earlier one.

What else can be infered from this curve? We first need to understand what the diagonal in this plot represent. The diagonal represents ‘Line of no discrimination’, which we obtain if we randomly guess. This is the ROC curve for the worst possible classifier. Therefore, by comparing the obtained ROC curve with the diagonal, we see how much better our classifer is from random guessing.

The further away ROC curve from the diagonal is (the closest it is to the top left corner) , better the classifier is.

Area Under the curve

The overall performance of the classifier is given by the area under the ROC curve and is usually denoted as AUC. Since TPR and FPR lie within the range of 0 to 1, the AUC also assumes values between 0 and 1. The higher the value of AUC, the better is the overall performance of the classifier.

Let’s see this for the two different distributions which we saw earlier.

As we know the classifier had worked perfectly in the first case with points at (0,1) the area under the curve is 1 which is perfect. In the latter case the classifier was not able to perform as good, the ROC curve is between the diagonal and left hand corner. The AUC as we can see is less than 1.

Some other general characteristics

There are still few points that needs to be discussed on a General ROC curve

  • The ROC curve does not provide information about the actual values of thresholds used for the classifier.
  • Performance of different classifiers can be compared using the AUC of different Classifier. The larger the AUC, the better the classifier.
  • The vertical distance of the ROC curve from the no discrimination line gives a measure of ‘INFORMEDNESS’. This is known as Youden’s J satistic. This statistics can take values between 0 and 1.

Youden’s  J statistic is defined for every point on the ROC curve . The point at which Youden’s  J satistics reaches its maximum for a given ROC curve can be used to guide the selection of the threshold to be used for that classifier.

I hope this post does the job of providing an understanding of ROC curves  and AUC. The  Python program for simulating the example given earlier can be found here .

Please feel free to adjust the mean of the distributions and see the changes in the plot.

How is automation changing data science and machine learning?

We have come a long way since the introduction of data science and machine learning. The recent study has found that the volume of business data doubles in less than 14 months. Today, the collection of data is no longer a problem, but the filtration, analysis, and maintenance of relevant information is a bigger issue.

We need to hire data science professionals, and they demand over $100k annually. Paying that sort of money for a professional is not feasible for every single organization, especially small and middle-sized companies. Google recently announced that it is going to make machine learning technology possible for every business.

The access to machine learning technology is now possible, even for small businesses due to automation. Google, Microsoft, and other companies have come up with automated machine learning tools that enable small businesses to use machine learning technology to enhance their business performance and profit.

Image Source: Google Cloud

With that said, the world still needs a lot of machine learning professionals. Many machine learning professionals prefer Python for machine learning due to its features and a wide range of libraries.

According to the Gartner report, around 40% of data science tasks will be automated by 2020. The data science tools can automate some parts of data science processes, but it is not complete automation.

With that said, it has been helping a lot to accelerate the tasks. We still need data science professionals to deal with real-world problems. The algorithms are not yet able to handle messy data. The significant chunk of data science professionals often prefers performing with data science with Python for sophisticated tasks.

Automation in Data Science

Let me show you the figure right at the beginning before moving forward.

Image Source: Wikipedia

If I had to use only one word to describe the entire data science process, I would use the word “headache.” According to the recent report, the median salary of data scientists easily surpasses $100k annually. The pay will be higher in the time to come.

One needs to pay a lot of money and invest a lot of time to get insights from the collected data. The data scientists need to spend almost 50-60% of their time in data processing and the rest of their time in modeling and deployment.

The cloud platforms like Amazon Web Services, Google, Microsoft Azure, and so on make the job more comfortable, but there is still a lot of work to maintain and extract useful insights from the collected data.

The data science process has lots of inefficiencies. At first, they need to spend over 50% of their total time on processing messy real-world data. After that, there could be a need to customize models, according to specific problems.

The significant contribution of automation is making a significant portion of data processing parts automated. Secondly, the automated platforms can make tracking of various models easier from multiple parameters. The time needed to launch the algorithm is minimal.

One example of an extensive tool to handle a data science project is Alteryx. IT has come up with powerful automated solutions that can drastically reduce the data processing and model development time for smoothening the entire data science workflow. The data science platform, Alteryx, is so amazing that its share price doubled in a span of little more than a year.

Some other great tools that can help you in data science automation are Rapidminer, H20.ai, KNIME, and so on. However, the lack of skilled data scientists can create a problem despite these tools. It is where the role of automated machine learning pops in.

How is Machine Learning Transformed with the entrance of Automation?

The traditional machine learning process was too complicated. One requires to have a lot of expensive machine learning professionals working for months to come up with models to process machine learning tasks.

Image Source: Medium

To make traditional machine learning work, one needs to gather data, standardize data, process features, create and train the machine learning model from problems, validate the models, and deploy the models at last.

You must have heard of how machine learning is only for corporations in the past. But, that has drastically changed in recent time, and it is all due to automation. Keep in mind that the above machine learning model is a simple one. There is a lot of extra works for complicated models. Even for the simple ones, you need to spend a lot of time and money, which makes it impossible for small and medium companies.

The automation in machine learning is all about automating the entire process to make machine learning easier. The only thing you need to do is feed data to the system (not a massive volume of data). You do not need even to cross the three-figure number of images to continue with automated machine learning platforms.

Microsoft has its automl platform along with Google. Other automl platforms can do the trick for you. Using those platforms do not cost you an arm and a leg. If you check out the price, you will be surprised.

There is no need for you to create or deploy models or even test the models. The algorithm will do the job for you. It takes examples and models of historical models to process the data and use a machine learning algorithm.

Even non-statistician can implement machine learning technology with limited data, thanks to automation in machine learning. You can make use of predictive analytics and can get easy solutions for simple prediction problems without scratching your head. Numerous libraries can assist you in the automated generation of machine learning pipelines.

How are the jobs of data scientists simplified by the introduction of automation in machine learning and data science?

It is true that the introduction of automation has drastically reduced the time for completing the tasks for data scientists. They no longer have to spend their valuable time in time-consuming, monotonous works that are necessary but do not provide a lot of value.

However, the need for skilled data scientists still exist, and it will always be there in the time to come. There are challenging works for data scientists that we cannot replace with machines, such as listening to clients, figuring out the root cause of business issues, development and selection of the right solution for the specific business problem.

Just like in other types of jobs, the advancement of automation technologies will modify the tasks that data scientists need to perform. They will be able to allocate more time on things that matter rather than monotonous tasks.

Final Verdict

The automation of machine learning and data science are in the beginning stage. However, they are already making a massive impact on the business world. The huge corporations are investing in Big Data and Machine Learning technologies. We can expect a considerable improvement in these technologies shortly.

Sooner, the competitive advantage of a business will depend on how well they can use the technologies, instead of access to machine learning or Big Data technologies.  I hope this article was valuable to you. If you want to add something or express your thoughts, feel free to leave a comment. I will gladly read and reply to your comment.

Fehler-Rückführung mit der Backpropagation

Dies ist Artikel 4 von 6 der Artikelserie –Einstieg in Deep Learning.

Das Gradienten(abstiegs)verfahren ist der Schlüssel zum Training einzelner Neuronen bzw. deren Gewichtungen zu den Neuronen der vorherigen Schicht. Wer dieses Prinzip verstanden hat, hat bereits die halbe Miete zum Verständnis des Trainings von künstlichen neuronalen Netzen.

Der Gradientenabstieg wird häufig fälschlicherweise mit der Backpropagation gleichgesetzt, jedoch ist das nicht ganz richtig, denn die Backpropagation ist mehr als die Anwendung des Gradientenabstiegs.

Bevor wir die Backpropagation erläutern, nochmal kurz zurück zur Forward-Propagation, die die eigentliche Prädiktion über ein künstliches neuronales Netz darstellt:

Forward-Propagation

Abbildung 1: Ein simples kleines künstliches neuronales Netz mit zwei Schichten (+ Eingabeschicht) und zwei Neuronen pro Schicht.

In einem kleinen künstlichen neuronalen Netz, wie es in der Abbildung 1 dargestellt ist, und das alle Neuronen über die Sigmoid-Funktion aktiviert, wird jedes Neuron eine Nettoeingabe z berechnen…

z = w^{T} \cdot x

… und diese Nettoeingabe in die Sigmoid-Funktion einspeisen…

\phi(z) = sigmoid(z) = \frac{1}{1 + e^{-z}}

… die dann das einzelne Neuron aktiviert. Die Aktivierung erfolgt also in der mittleren Schicht (N-Schicht) wie folgt:

N_{j} = \frac{1}{1 + e^{- \sum (w_{ij} \cdot x_{i}) }}

Die beiden Aktivierungsausgaben N werden dann als Berechnungsgrundlage für die Ausgaben der Ausgabeschicht o verwendet. Auch die Ausgabe-Neuronen berechnen ihre jeweilige Nettoeingabe z und aktivieren über Sigmoid(z).

Ausgabe eines Ausgabeknotens als Funktion der Eingänge und der Verknüpfungsgewichte für ein dreischichtiges neuronales Netz, mit nur zwei Knoten je Schicht, kann also wie folgt zusammen gefasst werden:

O_{k} = \frac{1}{1 + e^{- \sum (w_{jk} \cdot \frac{1}{1 + e^{- \sum (w_{ij} \cdot x_{i}) }}) }}

Abbildung 2: Forward-Propagation. Aktivierung via Sigmoid-Funktion.

Sollte dies die erste Forward-Propagation gewesen sein, wird der Output noch nicht auf den Input abgestimmt sein. Diese Abstimmung erfolgt in Form der Gewichtsanpassung im Training des neuronalen Netzes, über die zuvor erwähnte Gradientenmethode. Die Gradientenmethode ist jedoch von einem Fehler abhängig. Diesen Fehler zu bestimmen und durch das Netz zurück zu führen, das ist die Backpropagation.

Back-Propagation

Um die Gewichte entgegen des Fehlers anpassen zu können, benötigen wir einen möglichst exakten Fehler als Eingabe. Der Fehler berechnet sich an der Ausgabeschicht über eine Fehlerfunktion (Loss Function), beispielsweise über den MSE (Mean Squared Error) oder über die sogenannte Kreuzentropie (Cross Entropy). Lassen wir es in diesem Beispiel einfach bei einem simplen Vergleich zwischen dem realen Wert (Sollwert o_{real}) und der Prädiktion (Ausgabe o) bleiben:

e_{o} = o_{real} - o

Der Fehler e ist also einfach der Unterschied zwischen dem Ziel-Wert und der Prädiktion. Jedes Training ist eine Wiederholung von Prädiktion (Forward) und Gewichtsanpassung (Back). Im ersten Schritt werden üblicherweise die Gewichtungen zufällig gesetzt, jede Gewichtung unterschiedlich nach Zufallszahl. So ist die Wahrscheinlichkeit, gleich zu Beginn die “richtigen” Gewichtungen gefunden zu haben auch bei kleinen neuronalen Netzen verschwindend gering. Der Fehler wird also groß sein und kann über den Gradientenabstieg durch Gewichtsanpassung verkleinert werden.

In diesem Beispiel berechnen wir die Fehler e_{1} und e_{2} und passen danach die Gewichte w_{j,k} (w_{1,1} & w_{2,1} und w_{1,2} & w_{2,2}) der Schicht zwischen dem Hidden-Layer N und dem Output-Layer o an.

Abbildung 3: Anpassung der Gewichtungen basierend auf dem Fehler in der Ausgabe-Schicht.

Die Frage ist nun, wie die Gewichte zwischen dem Input-Layer X und dem Hidden-Layer N anzupassen sind. Es stellt sich die Frage, welchen Einfluss diese auf die Fehler in der Ausgabe-Schicht haben?

Um diese Gewichtungen anpassen zu können, benötigen wir den Fehler-Anteil der beiden Neuronen N_{1} und N_{2}. Dieser Anteil am Fehler der jeweiligen Neuronen ergibt sich direkt aus den Gewichtungen w_{j,k} zum Output-Layer:

e_{N_{1}} = e_{o1} \cdot \frac{w_{1,1}}{w_{1,1} + w_{1,2}} + e_{o2} \cdot \frac{w_{1,2}}{w_{1,1} + w_{1,2}}

e_{N_{2}} = e_{o1} \cdot \frac{w_{2,1}}{w_{2,1} + w_{2,2}} + e_{o2} \cdot \frac{w_{2,2}}{w_{2,1} + w_{2,2}}

Wenn man das nun generalisiert:

    \[ e_{N} = \left(\begin{array}{rr} \frac{w_{1,1}}{w_{1,1} + w_{1,2}} & \frac{w_{1,2}}{w_{1,1} + w_{1,2}} \\ \frac{w_{2,1}}{w_{2,1} + w_{2,2}} & \frac{w_{2,2}}{w_{2,1} + w_{2,2}} \end{array}\right) \cdot \left(\begin{array}{c} e_{1} \\ e_{2} \end{array}\right) \qquad \]

Dabei ist es recht aufwändig, die Gewichtungen stets ins Verhältnis zu setzen. Diese Berechnung können wir verkürzen, indem ganz einfach direkt nur die Gewichtungen ohne Relativierung zur Kalkulation des Fehleranteils benutzt werden. Die Relationen bleiben dabei erhalten!

    \[ e_{N} = \left(\begin{array}{rr} w_{1,1} & w_{1,2} \\ w_{2,1} & w_{2,2} \end{array}\right) \cdot \left(\begin{array}{c} e_{1} \\ e_{2} \end{array}\right) \qquad \]

Oder folglich in Kurzform: e_{N} = w^{T} \cdot e_{o}

Abbildung 4: Vollständige Gewichtsanpassung auf Basis der Fehler in der Ausgabeschicht und der Fehleranteile in der verborgenden Schicht.

Und nun können, basierend auf den Fehleranteilen der verborgenden Schicht N, die Gewichtungen w_{i,j} zwischen der Eingabe-Schicht I und der verborgenden Schicht N angepasst werden, entgegen dieser Fehler e_{N}.

Die Backpropagation besteht demnach aus zwei Schritten:

  1. Fehler-Berechnung durch Abgleich der Soll-Werte mit den Prädiktionen in der Ausgabeschicht und durch Fehler-Rückführung zu den Neuronen der verborgenden Schichten (Hidden-Layer)
  2. Anpassung der Gewichte entgegen des Gradientenanstiegs der Fehlerfunktion (Loss Function)

Buchempfehlungen

Die folgenden zwei Bücher haben mir sehr beim Verständnis und beim Verständlichmachen der Backpropagation in künstlichen neuronalen Netzen geholfen.

Neuronale Netze selbst programmieren: Ein verständlicher Einstieg mit Python Deep Learning. Das umfassende Handbuch: Grundlagen, aktuelle Verfahren und Algorithmen, neue Forschungsansätze (mitp Professional)

Training eines Neurons mit dem Gradientenverfahren

Dies ist Artikel 3 von 6 der Artikelserie –Einstieg in Deep Learning.

Das Training von neuronalen Netzen erfolgt nach der Forward-Propagation über zwei Schritte:

  1. Fehler-Rückführung über aller aktiver Neuronen aller Netz-Schichten, so dass jedes Neuron “seinen” Einfluss auf den Ausgabefehler kennt.
  2. Anpassung der Gewichte entgegen den Gradienten der Fehlerfunktion

Beide Schritte werden in der Regel zusammen als Backpropagation bezeichnet. Machen wir erstmal einen Schritt vor und betrachten wir, wie ein Neuron seine Gewichtsverbindungen zu seinen Vorgängern anpasst.

Gradientenabstiegsverfahren

Der Gradientenabstieg ist ein generalisierbarer Algorithmus zur Optimierung, der in vielen Verfahren des maschinellen Lernens zur Anwendung kommt, jedoch ganz besonders als sogenannte Backpropagation im Deep Learning den Erfolg der künstlichen neuronalen Netze erst möglich machen konnte.

Der Gradientenabstieg lässt sich vom Prinzip her leicht erklären: Angenommen, man stünde im Gebirge im dichten Nebel. Das Tal, und somit der Weg nach Hause, ist vom Nebel verdeckt. Wohin laufen wir? Wir können das Ziel zwar nicht sehen, tasten uns jedoch so heran, dass unser Gehirn den Gradienten (den Unterschied der Höhen beider Füße) berechnet, somit die Steigung des Bodens kennt und sich entgegen dieser Steigung unser Weg fortsetzt.

Konkret funktioniert der Gradientenabstieg so: Wir starten bei einem zufälligen Theta \theta (Random Initialization). Wir berechnen die Ausgabe (Forwardpropogation) und vergleichen sie über eine Verlustfunktion (z. B. über die Funktion Mean Squared Error) mit dem tatsächlich korrekten Wert. Auf Grund der zufälligen Initialisierung haben wir eine nahe zu garantierte Falschheit der Ergebnisse und somit einen Verlust. Für die Verlustfunktion berechnen wir den Gradienten für gegebene Eingabewerte. Voraussetzung dafür ist, dass die Funktion ableitbar ist. Wir bewegen uns entgegen des Gradienten in Richtung Minimum der Verlustfunktion. Ist dieses Minimum (fast) gefunden, spricht man auch davon, dass der Lernalgorithmus konvergiert.

Das Gradientenabstiegsverfahren ist eine Möglichkeit der Gradientenverfahren, denn wollten wir maximieren, würden wir uns entlang des Gradienten bewegen, was in anderen Anwendungen sinnvoll ist.

Ob als “Cost Function” oder als “Loss Function” bezeichnet, in jedem Fall ist es eine “Error Function”, aber auf die Benennung kommen wir später zu sprechen. Jedenfalls versuchen wir die Fehlerrate zu senken! Leider sind diese Funktionen in der Praxis selten so einfach konvex (zwei Berge mit einem Tal dazwischen).

 

Aber Achtung: Denn befinden wir uns nur zwischen zwei Bergen, finden wir das Tal mit Sicherheit über den Gradienten. Befinden wir uns jedoch in einem richtigen Gebirge mit vielen Bergen und Tälern, gilt es, das richtige Tal zu finden. Bei der Optimierung der Gewichtungen von künstlichen neuronalen Netzen wollen wir die besten Gewichtungen finden, die uns zu den geringsten Ausgaben der Verlustfunktion führen. Wir suchen also das globale Minimum unter den vielen (lokalen) Minima.

Programmier-Beispiel in Python

Nachfolgend ein Beispiel des Gradientenverfahrens zur Berechnung einer Regression. Wir importieren numpy und matplotlib.pyplot und erzeugen uns künstliche Datenpunkte:

Nun wollen wir einen Lernalgorithmus über das Gradientenverfahren erstellen. Im Grunde haben wir hier es bereits mit einem linear aktivierten Neuron zutun:

Bei der linearen Regression, die wir durchführen wollen, nehmen wir zwei-dimensionale Daten (wobei wir die Regression prinzipiell auch mit x-Dimensionen durchführen können, dann hätte unser Neuron weitere Eingänge). Wir empfangen einen Bias (w_0) der stets mit einer Eingangskonstante multipliziert und somit als Wert erhalten bleibt. Der Bias ist das Alpha \alpha in einer Schulmathe-tauglichen Formel wie y = \beta \cdot x + \alpha.

Beta \beta ist die Steigung, der Gradient, der Funktion.

Sowohl \alpha als auch \beta sind uns unbekannt, versuchen wir jedoch über die Betrachtung unserer Prädiktion durch Berechnung der Formel \^y = \beta \cdot x + \alpha und den darauffolgenden Abgleich mit dem tatsächlichen y herauszufinden. Anfangs behaupten wir beispielsweise einfach, sowohl \beta als auch \alpha seien 0.00. Folglich wird \^y = \beta \cdot x + \alpha ebenfalls gleich 0.00 sein und die Fehlerfunktion (Loss Function) wird maximal sein. Dies war der erste Durchlauf des Trainings, die sogenannte erste Epoche!

Die Epochen (Durchläufe) und dazugehörige Fehlergrößen. Wenn die Fehler sinken und mit weiteren Epochen nicht mehr wesentlich besser werden, heißt es, das der Lernalogorithmus konvergiert.

Als Fehlerfunktion verwenden wir bei der Regression die MSE-Funktion (Mean Squared Error):

MSE = \sum(\^y_i - y_i)^2

Um diese Funktion wird sich nun alles drehen, denn diese beschreibt den Fehler und gibt uns auch die Auskunft darüber, ob wie stark und in welche Richtung sie ansteigt, so dass wir uns entgegen der Steigung bewegen können. Wer die Regeln der Ableitung im Kopf hat, weiß, dass die Ableitung der Formel leichter wird, wenn wir sie vorher auf halbe Werte runterskalieren. Da die Proportionen dabei erhalten bleiben und uns quadrierte Fehlerwerte unserem menschlichen Verstand sowieso nicht so viel sagen (unser Gehirn denkt nunmal nicht exponential), stört das nicht:

MSE = \frac{\frac{1}{2} \cdot \sum(\^y_i - y_i)^2}{n}

MSE = \frac{\frac{1}{2} \cdot \sum(w^T \cdot x_i - y_i)^2}{n}

Wenn die Mathematik der partiellen Ableitung (Ableitung einer Funktion nach jedem Gradienten) abhanden gekommen ist, bitte nochmal folgende Regeln nachschlagen, um die nachfolgende Ableitung verstehen zu können:

  • Allgemeine partielle Ableitung
  • Kettenregel

Ableitung der MSD-Funktion nach dem einen Gewicht w bzw. partiell nach jedem vorhandenen w_j:

\frac{\partial}{\partial w_j}MSE = \frac{\partial}{\partial w} \frac{1}{2} \cdot \sum(\^y - y_i)^2

\frac{\partial}{\partial w_j}MSE = \frac{\partial}{\partial w} \frac{1}{2} \cdot \sum(w^T \cdot x_i - y_i)^2

\frac{\partial}{\partial w_j}MSE = \frac{2}{n} \cdot \sum(w^T \cdot x_i - y_i) \cdot x_{ij}

Woher wir das x_{ij} am Ende her haben? Das ergibt sie aus der Kettenregel: Die äußere Funktion wurde abgeleitet, so wurde aus \frac{1}{2} \cdot \sum(w^T \cdot x_i - y_i)^2 dann \frac{2}{n} \cdot \sum(w^T \cdot x_i - y_i). Jedoch muss im Sinne eben dieser Kettenregel auch die innere Funktion abgeleitet werden. Da wir nach w_j ableiten, bleibt nur x_ij erhalten.

Damit können wir arbeiten! So kompliziert ist die Formel nun auch wieder nicht: \frac{2}{n} \cdot \sum(w^T \cdot x_i - y_i) \cdot x_{ij}

Mit dieser Formel können wir unsere Gewichte an den Fehler anpassen: (f\nabla ist der Gradient der Funktion!)

w_j = w_j - \nabla MSE(w_j)

Initialisieren der Gewichtungen

Die Gewichtungen \alpha und \beta müssen anfänglich mit Werten initialisiert werden. In der Regression bietet es sich an, die Gewichte anfänglich mit 0.00 zu initialisieren.

Bei vielen neuronalen Netzen, mit nicht-linearen Aktivierungsfunktionen, ist das jedoch eher ungünstig und zufällige Werte sind initial besser. Gut erprobt sind normal-verteilte Zufallswerte.

Lernrate

Nur eine Kleinigkeit haben wir bisher vergessen: Wir brauchen einen Faktor, mit dem wir anpassen. Hier wäre der Faktor 1. Das ist in der Regel viel zu groß. Dieser Faktor wird geläufig als Lernrate (Learning Rate) \eta (eta) bezeichnet:

w_j = w_j - \eta \cdot \nabla MSE(w_j)

Die Lernrate \eta ist ein Knackpunkt und der erste Parameter des Lernalgorithmus, den es anzupassen gilt, wenn das Training nicht konvergiert.

Die Lernrate \eta darf nicht zu groß klein gewählt werden, da das Training sonst zu viele Epochen benötigt. Ungeduldige erhöhen die Lernrate möglicherweise aber so sehr, dass der Lernalgorithmus im Minimum der Fehlerfunktion vorbeiläuft und diesen stets überspringt. Hier würde der Algorithmus also sozusagen konvergieren, weil nicht mehr besser werden, aber das resultierende Modell wäre weit vom Optimum entfernt.

Beginnen wir mit der Implementierung als Python-Klasse:

Die Klasse sollte so funktionieren, bevor wir sie verwenden, sollten wir die Input-Werte standardisieren:

Bei diesem Beispiel mit künstlich erzeugten Werten ist das Standardisieren bzw. das Fehlen des Standardisierens zwar nicht kritisch, aber man sollte es sich zur Gewohnheit machen. Testweise es einfach mal weglassen 🙂

Kommen wir nun zum Einsatz der Klasse, die die Regression via Gradientenabstieg absolvieren soll:

Was tut diese Instanz der Klasse LinearRegressionGD nun eigentlich?

Bildlich gesprochen, legt sie eine Gerade auf den Boden des Koordinatensystems, denn die Gewichtungen werden mit 0.00 initialisiert, y ist also gleich 0.00, egal welche Werte in x enthalten sind. Der Fehler ist dann aber sehr groß (sollte maximal sein, im Vergleich zu zukünftigen Epochen). Die Gewichte werden also angepasst, die Gerade somit besser in die Punktwolke platziert. Mit jeder Epoche wird die Gerade erneut in die Punktwolke gelegt, der Gesamtfehler (über alle x, da wir es hier mit dem Batch-Verfahren zutun haben) berechnet, die Werte angepasst… bis die vorgegebene Zahl an Epochen abgelaufen ist.

Schauen wir uns das Ergebnis des Trainings an:

Die Linie sieht passend aus, oder? Da wir hier nicht zu sehr in die Theorie der Regressionsanalyse abdriften möchten, lassen wir das testen und prüfen der Akkuratesse mal aus, hier möchte ich auf meinen Artikel Regressionsanalyse in Python mit Scikit-Learn verweisen.

Prüfen sollten wir hingegen mal, wie schnell der Lernalgorithmus mit der vorgegebenen Lernrate eta konvergiert:

Hier die Verlaufskurve der Cost Function:

Die Kurve zeigt uns, dass spätestens nach 40 Epochen kaum noch Verbesserung (im Sinne der Gesamtfehler-Minimierung) erreicht wird.

Wichtige Hinweise

Natürlich war das nun nur ein erster kleiner Einstieg und wer es verstanden hat, hat viel gewonnen. Denn erst dann kann man sich vorstellen, wie ein einzelnen Neuron eines künstlichen neuronalen Netzes grundsätzlich trainiert werden kann.

Folgendes sollte noch beachtet werden:

  • Lernrate \eta:
    Die Lernrate ist ein wichtiger Parameter. Wer das Programmier-Beispiel bei sich zum Laufen gebracht hat, einfach mal die Lernrate auf Werte zwischen 10.00 und 0.00000001 setzen, schauen was passiert 🙂
  • Globale Minima vs lokale Minima:
    Diese lineare zwei-dimensionale Regression ist ziemlich einfach. Neuronale Netze sind hingegen komplexer und haben nicht einfach nur eine simple konvexe Fehlerfunktion. Hier gibt es mehrere Hügel und Täler in der Fehlerfunktion und die Gefahr ist groß, in einem lokalen, nicht aber in einem globalen Minimum zu landen.
  • Stochastisches Gradientenverfahren:
    Wir haben hier das sogenannte Batch-Verfahren verwendet. Dieses ist grundsätzlich besser als die stochastische Methode. Denn beim Batch verwenden wir den gesamten Stapel an x-Werten für die Fehlerbestimmung. Allerdings ist dies bei großen Daten zu rechen- und speicherintensiv. Dann werden kleinere Unter-Stapel (Sub-Batches) zufällig aus den x-Werten ausgewählt, der Fehler daraus bestimmt (was nicht ganz so akkurat ist, wie als würden wir den Fehler über alle x berechnen) und der Gradient bestimmt. Dies ist schon Rechen- und Speicherkapazität, erfordert aber meistens mehr Epochen.

Buchempfehlung

Die folgenden zwei Bücher haben mir bei der Erstellung dieses Beispiels geholfen und kann ich als hilfreiche und deutlich weiterführende Lektüre empfehlen:

 

Machine Learning mit Python und Scikit-Learn und TensorFlow: Das umfassende Praxis-Handbuch für Data Science, Predictive Analytics und Deep Learning (mitp Professional) Hands-On Machine Learning with Scikit-Learn and TensorFlow: Concepts, Tools, and Techniques for Building Intelligent Systems

 

Künstliche Intelligenz und Vorurteil

Kaum ein anderes technologisches Thema heutzutage wird hinsichtlich gesellschaftlicher Auswirkungen so kontrovers diskutiert wie das der Künstlichen Intelligenz (KI). Während das Wörtchen „KI“ bei den einen Zukunftsvisionen hervorruft, in welchen technologischer Fortschritt menschliche Probleme wie Hunger, Krankheit und Klimawandel reduziert hat, zeichnen andere düstere Bilder von Orwell‘schen Überwachungsstaaten und technologischen Apokalypsen.

Starke, schwache KI

Es ist die Unschärfe des Begriffes „KI“, welcher eine derart große Bandbreite an Zukunftsszenarien ermöglicht. Für diejenigen, welche sich an solch spekulativen Debatten beteiligen, beutet KI „starke KI“ – eine künstliche Intelligenz, deren intellektuellen Fähigkeiten die eines Menschen erreichen oder gar übertreffen. Und so spannend die Diskussion über starke KI auch ist – sie ist reine Spekulation. Heute existierende KI ist weit, sehr weit von starker KI entfernt. Worüber wir heutzutage verfügen ist die sogenannte „schwache KI“ – Algorithmen, die spezifische Anwendungsprobleme (z.B. Bilderkennung, Spracherkennung, Übersetzung, Go spielen) lösen können. Und das mitunter sehr viel besser als Menschen.

Wo heutzutage „KI“ draufsteht, sind innen überwiegend Algorithmen aus dem Bereich des maschinellen Lernens (allen voran Deep Learning) am Werk. Diese Algorithmen können selbständig die Vorgehensweise erlernen, die zum Beispiel nötig ist, um einen gegebenen Input (z.B. ein Bild) auf einen gegebenen Output (z.B. eine Kategorie, welche den Bildinhalt beschreibt) abzubilden. Aber selbst diese „schwache KI“ birgt beträchtliches Potential – denken wir an mögliche Verbesserungen z.B. im Bereich der Medizin, Logistik, Verkehrssicherheit oder Energie- und Ressourcennutzung! Angesichts der Chancen, heutige Prozesse und Anwendungen zu verbessern, haben wir allen Grund, dem Einsatz von KI aufgeschlossen gegenüber zu stehen. Vorausgesetzt natürlich, dass KI verantwortungsvoll, „ethisch“ und sicher eingesetzt wird.

KI auf Abwegen

Ethische Herausforderungen von KI ergeben sich dabei zum einen durch die Zielsetzung. Wie ein Hammer für den Nagel an der Wand oder für den Hinterkopf eines Gegners verwendet werden kann, kann auch KI für böse Ziele missbraucht werden. Nur, dass KI im Zweifel deutlich größeren Schaden anrichten kann als ein einfacher Hammer. Und so sollten wir angesichts der Risiken dringend international diskutieren, wie wir uns hinsichtlich militärischer Anwendungen von KI verhalten wollen.

Zum anderen dringen besonders aus den USA, in denen KI Algorithmen schon heute in deutlich größerem Ausmaß eingesetzt werden als in Deutschland, immer wieder beunruhigende Nachrichten über voreingenommene KI Algorithmen. Zum ersten fand eine Studie kürzlich heraus, dass kommerziell erhältliche Gesichtserkennungsalgorithmen für Frauen bzw. dunkelhäutige Menschen schlechter funktionieren als für Männer bzw. hellhäutige Menschen. Mit der unschönen Konsequenz, dass es z.B. bei einem Abgleich mit Verbrecherfotos bei Menschen mit dunkler Hautfarbe deutlich häufiger zu falschen Übereinstimmungen kommen kann als bei Menschen heller Hautfarbe. Zum zweiten wurde vor kurzem bekannt, dass eine experimentell von einem großen Technologiekonzern zur Bewertung von Bewerbungen verwendete KI von Frauen stammende Bewerbungen systematisch schlechter bewertete als von Männern stammende Bewerbungen.

Wie KI zu Vorurteilen kommt

Um die Ursachen für vorurteilsbehaftete KI besser zu verstehen, lohnt es sich, einen Blick hinter die Kulissen zu werfen. Denn wie jede Technologie existiert auch KI nicht im luftleeren Raum. Dies lässt sich leicht anhand der Faktoren verdeutlichen, welche zum Erfolg heutiger KI beigetragen haben: bessere Hardware, cleverere Algorithmen und größere Datenmengen. Und gerade diese Daten sind es, durch welche Vorurteile in KI Einzug halten können.

Die Vorstellung von „neutralen Daten“ ist nämlich eine Wunschvorstellung. Im besten Fall spiegeln Daten die Welt wider, in der wir leben.       Eine Welt zum Beispiel, in der in Technologiekonzernen typischerweise deutlich mehr Männer beschäftigt sind als Frauen – was eine auf dem Personalbestand eines Technologiekonzerns trainierte KI dazu veranlassen kann, zu „schlussfolgern“, dass männliche Bewerber im Auswahlverfahren zu bevorzugen sind. Oder eine Welt, in der Länder bzw. gesellschaftliche Schichten innerhalb eines Landes unterschiedlichen Zugang zu modernen Technologien oder auch Bildung haben. Eine Ungleichheit, die sich als Dominanz westlicher Industrienationen in der geografischen Zusammensetzung von zum Training von KI-Algorithmen verwendeter Datensätze auswirken kann. Eine Dominanz, die wiederum zur Folge haben kann, dass derart trainierte KI-Algorithmen besonders gut für Menschen aus westlichen Industrienationen funktionieren. Ganz zu schweigen von der Voreingenommenheit der menschlichen Wahrnehmung, welche die Zusammensetzung von Daten beeinflusst – denken wir an das begrenzte Spektrum der Bilder, welche uns zuerst zu dem Begriff „Genie“ in den Sinn kommen.

Aber nicht nur die verwendeten Trainingsdaten, sondern auch bei der Entwicklung von KI getroffenen Design-Entscheidungen können negative Auswirkungen haben. Wenn bei einem nicht perfekt funktionierenden Bilderkennungsalgorithmus potentiell abwertende Kategorien zur Klassifikation zur Verfügung stehen, kann dies dazu führen, dass – wie in der Vergangenheit geschehen – dunkelhäutige Menschen als Gorillas klassifiziert werden. Wenn bei der Evaluation eines z.B. für die Gesichtserkennung eingesetzten KI-Algorithmus nur die Genauigkeit über alle Bevölkerungsgruppen hinweg berücksichtigt wird, können Ungleichheiten in der Genauigkeit nicht entdeckt werden, was zu Problemen bei der Anwendung führen kann. Denn Nutzer von KI-Algorithmen vermuten zumeist, dass die Algorithmen für alle denkbaren Anwendungszwecke geeignet sind.

Werte statt Wegsehen

Entgegen der verbreiteten Auffassung sind KI Algorithmen also nicht notwendigerweise vorurteilsfrei – sie können menschliche Voreingenommenheit bzw. gesellschaftliche Ungleichheit widerspiegeln. Da Algorithmen anders und in anderem Maß als Menschen eingesetzt werden, kann das bei blauäugiger Verwendung dazu führen, dass bestehende Ungleichheiten nicht nur bestärkt, sondern sogar vergrößert werden. Richtig angewendet können Algorithmen jedoch helfen, implizite und explizite Vorurteile menschlicher Entscheider zu mindern. Denn wie wir durch viele Studien wissen, ist die Liste der kognitiven Verzerrungen, die wir Menschen aufweisen, lang.

Es ist für den verantwortlichen Einsatz von KI in einem sensiblen Kontext somit essenziell, zu wissen, welche „ethischen“ Kriterien KI für den konkreten Anwendungsfall erfüllen muss. So kann sichergestellt werden, dass die KI den Anforderungen entspricht, bevor sie angewendet wird – oder aber, dass sie solange nicht angewendet wird, wie sie den Anforderungen nicht entspricht. Und mittels Transparenz, Überwachung und Feedback-Möglichkeiten lässt sich vermeiden, dass ein selbst-verbessernder KI-Algorithmus im Laufe der Zeit das ihm gesteckte Ziel verfehlt.

Für viele Anwendungsfälle sind derartige ethische Fragen jedoch vernachlässigbar, denken wir zum Beispiel an die Vorhersage von Maschinenausfällen oder die Extraktion strukturierter Daten aus unstrukturierten Dokumenten. Aber es ist nichtsdestotrotz gut und wichtig, Ethik und KI zusammen zu denken. Denn dies ermöglicht es uns, sicherzustellen, dass wir KI auf die bestmögliche Weise einsetzen. Denn das enorme Potential von KI gibt uns die Chance, den Status quo nachhaltig positiv zu verändern – technologisch wie ethisch.

Sentiment Analysis of IMDB reviews

Sentiment Analysis of IMDB reviews

This article shows you how to build a Neural Network from scratch(no libraries) for the purpose of detecting whether a movie review on IMDB is negative or positive.

Outline:

  • Curating a dataset and developing a "Predictive Theory"

  • Transforming Text to Numbers Creating the Input/Output Data

  • Building our Neural Network

  • Making Learning Faster by Reducing "Neural Noise"

  • Reducing Noise by strategically reducing the vocabulary

Curating the Dataset

In [3]:
def pretty_print_review_and_label(i):
    print(labels[i] + "\t:\t" + reviews[i][:80] + "...")

g = open('reviews.txt','r') # features of our dataset
reviews = list(map(lambda x:x[:-1],g.readlines()))
g.close()

g = open('labels.txt','r') # labels
labels = list(map(lambda x:x[:-1].upper(),g.readlines()))
g.close()

Note: The data in reviews.txt we're contains only lower case characters. That's so we treat different variations of the same word, like The, the, and THE, all the same way.

It's always a good idea to get check out your dataset before you proceed.

In [2]:
len(reviews) #No. of reviews
Out[2]:
25000
In [3]:
reviews[0] #first review
Out[3]:
'bromwell high is a cartoon comedy . it ran at the same time as some other programs about school life  such as  teachers  . my   years in the teaching profession lead me to believe that bromwell high  s satire is much closer to reality than is  teachers  . the scramble to survive financially  the insightful students who can see right through their pathetic teachers  pomp  the pettiness of the whole situation  all remind me of the schools i knew and their students . when i saw the episode in which a student repeatedly tried to burn down the school  i immediately recalled . . . . . . . . . at . . . . . . . . . . high . a classic line inspector i  m here to sack one of your teachers . student welcome to bromwell high . i expect that many adults of my age think that bromwell high is far fetched . what a pity that it isn  t   '
In [4]:
labels[0] #first label
Out[4]:
'POSITIVE'

Developing a Predictive Theory

Analysing how you would go about predicting whether its a positive or a negative review.

In [5]:
print("labels.txt \t : \t reviews.txt\n")
pretty_print_review_and_label(2137)
pretty_print_review_and_label(12816)
pretty_print_review_and_label(6267)
pretty_print_review_and_label(21934)
pretty_print_review_and_label(5297)
pretty_print_review_and_label(4998)
labels.txt 	 : 	 reviews.txt

NEGATIVE	:	this movie is terrible but it has some good effects .  ...
POSITIVE	:	adrian pasdar is excellent is this film . he makes a fascinating woman .  ...
NEGATIVE	:	comment this movie is impossible . is terrible  very improbable  bad interpretat...
POSITIVE	:	excellent episode movie ala pulp fiction .  days   suicides . it doesnt get more...
NEGATIVE	:	if you haven  t seen this  it  s terrible . it is pure trash . i saw this about ...
POSITIVE	:	this schiffer guy is a real genius  the movie is of excellent quality and both e...
In [41]:
from collections import Counter
import numpy as np

We'll create three Counter objects, one for words from postive reviews, one for words from negative reviews, and one for all the words.

In [56]:
# Create three Counter objects to store positive, negative and total counts
positive_counts = Counter()
negative_counts = Counter()
total_counts = Counter()

Examine all the reviews. For each word in a positive review, increase the count for that word in both your positive counter and the total words counter; likewise, for each word in a negative review, increase the count for that word in both your negative counter and the total words counter. You should use split(' ') to divide a piece of text (such as a review) into individual words.

In [57]:
# Loop over all the words in all the reviews and increment the counts in the appropriate counter objects
for i in range(len(reviews)):
    if(labels[i] == 'POSITIVE'):
        for word in reviews[i].split(" "):
            positive_counts[word] += 1
            total_counts[word] += 1
    else:
        for word in reviews[i].split(" "):
            negative_counts[word] += 1
            total_counts[word] += 1

Most common positive & negative words

In [ ]:
positive_counts.most_common()

The above statement retrieves alot of words, the top 3 being : ('the', 173324), ('.', 159654), ('and', 89722),

In [ ]:
negative_counts.most_common()

The above statement retrieves alot of words, the top 3 being : ('', 561462), ('.', 167538), ('the', 163389),

As you can see, common words like "the" appear very often in both positive and negative reviews. Instead of finding the most common words in positive or negative reviews, what you really want are the words found in positive reviews more often than in negative reviews, and vice versa. To accomplish this, you'll need to calculate the ratios of word usage between positive and negative reviews.

The positive-to-negative ratio for a given word can be calculated with positive_counts[word] / float(negative_counts[word]+1). Notice the +1 in the denominator – that ensures we don't divide by zero for words that are only seen in positive reviews.

In [58]:
pos_neg_ratios = Counter()

# Calculate the ratios of positive and negative uses of the most common words
# Consider words to be "common" if they've been used at least 100 times
for term,cnt in list(total_counts.most_common()):
    if(cnt > 100):
        pos_neg_ratio = positive_counts[term] / float(negative_counts[term]+1)
        pos_neg_ratios[term] = pos_neg_ratio

Examine the ratios

In [12]:
print("Pos-to-neg ratio for 'the' = {}".format(pos_neg_ratios["the"]))
print("Pos-to-neg ratio for 'amazing' = {}".format(pos_neg_ratios["amazing"]))
print("Pos-to-neg ratio for 'terrible' = {}".format(pos_neg_ratios["terrible"]))
Pos-to-neg ratio for 'the' = 1.0607993145235326
Pos-to-neg ratio for 'amazing' = 4.022813688212928
Pos-to-neg ratio for 'terrible' = 0.17744252873563218

We see the following:

  • Words that you would expect to see more often in positive reviews – like "amazing" – have a ratio greater than 1. The more skewed a word is toward postive, the farther from 1 its positive-to-negative ratio will be.
  • Words that you would expect to see more often in negative reviews – like "terrible" – have positive values that are less than 1. The more skewed a word is toward negative, the closer to zero its positive-to-negative ratio will be.
  • Neutral words, which don't really convey any sentiment because you would expect to see them in all sorts of reviews – like "the" – have values very close to 1. A perfectly neutral word – one that was used in exactly the same number of positive reviews as negative reviews – would be almost exactly 1.

Ok, the ratios tell us which words are used more often in postive or negative reviews, but the specific values we've calculated are a bit difficult to work with. A very positive word like "amazing" has a value above 4, whereas a very negative word like "terrible" has a value around 0.18. Those values aren't easy to compare for a couple of reasons:

  • Right now, 1 is considered neutral, but the absolute value of the postive-to-negative rations of very postive words is larger than the absolute value of the ratios for the very negative words. So there is no way to directly compare two numbers and see if one word conveys the same magnitude of positive sentiment as another word conveys negative sentiment. So we should center all the values around netural so the absolute value fro neutral of the postive-to-negative ratio for a word would indicate how much sentiment (positive or negative) that word conveys.
  • When comparing absolute values it's easier to do that around zero than one.

To fix these issues, we'll convert all of our ratios to new values using logarithms (i.e. use np.log(ratio))

In the end, extremely positive and extremely negative words will have positive-to-negative ratios with similar magnitudes but opposite signs.

In [59]:
# Convert ratios to logs
for word,ratio in pos_neg_ratios.most_common():
    pos_neg_ratios[word] = np.log(ratio)

Examine the new ratios

In [14]:
print("Pos-to-neg ratio for 'the' = {}".format(pos_neg_ratios["the"]))
print("Pos-to-neg ratio for 'amazing' = {}".format(pos_neg_ratios["amazing"]))
print("Pos-to-neg ratio for 'terrible' = {}".format(pos_neg_ratios["terrible"]))
Pos-to-neg ratio for 'the' = 0.05902269426102881
Pos-to-neg ratio for 'amazing' = 1.3919815802404802
Pos-to-neg ratio for 'terrible' = -1.7291085042663878

If everything worked, now you should see neutral words with values close to zero. In this case, "the" is near zero but slightly positive, so it was probably used in more positive reviews than negative reviews. But look at "amazing"'s ratio - it's above 1, showing it is clearly a word with positive sentiment. And "terrible" has a similar score, but in the opposite direction, so it's below -1. It's now clear that both of these words are associated with specific, opposing sentiments.

Run the below code to see more ratios.

It displays all the words, ordered by how associated they are with postive reviews.

In [ ]:
pos_neg_ratios.most_common()

The top most common words for the above code : ('edie', 4.6913478822291435), ('paulie', 4.0775374439057197), ('felix', 3.1527360223636558), ('polanski', 2.8233610476132043), ('matthau', 2.8067217286092401), ('victoria', 2.6810215287142909), ('mildred', 2.6026896854443837), ('gandhi', 2.5389738710582761), ('flawless', 2.451005098112319), ('superbly', 2.2600254785752498), ('perfection', 2.1594842493533721), ('astaire', 2.1400661634962708), ('captures', 2.0386195471595809), ('voight', 2.0301704926730531), ('wonderfully', 2.0218960560332353), ('powell', 1.9783454248084671), ('brosnan', 1.9547990964725592)

Transforming Text into Numbers

Creating the Input/Output Data

Create a set named vocab that contains every word in the vocabulary.

In [19]:
vocab = set(total_counts.keys())

Check vocabulary size

In [20]:
vocab_size = len(vocab)
print(vocab_size)
74074

Th following image rpresents the layers of the neural network you'll be building throughout this notebook. layer_0 is the input layer, layer_1 is a hidden layer, and layer_2 is the output layer.

In [1]:
 
Out[1]:

TODO: Create a numpy array called layer_0 and initialize it to all zeros. Create layer_0 as a 2-dimensional matrix with 1 row and vocab_size columns.

In [21]:
layer_0 = np.zeros((1,vocab_size))

layer_0 contains one entry for every word in the vocabulary, as shown in the above image. We need to make sure we know the index of each word, so run the following cell to create a lookup table that stores the index of every word.

TODO: Complete the implementation of update_input_layer. It should count how many times each word is used in the given review, and then store those counts at the appropriate indices inside layer_0.

In [ ]:
# Create a dictionary of words in the vocabulary mapped to index positions 
# (to be used in layer_0)
word2index = {}
for i,word in enumerate(vocab):
    word2index[word] = i

It stores the indexes like this: 'antony': 22, 'pinjar': 23, 'helsig': 24, 'dances': 25, 'good': 26, 'willard': 71500, 'faridany': 27, 'foment': 28, 'matts': 12313,

Lets implement some functions for simplifying our inputs to the neural network.

In [25]:
def update_input_layer(review):
    """
    The element at a given index of layer_0 should represent
    how many times the given word occurs in the review.
    """
     
    global layer_0
    
    # clear out previous state, reset the layer to be all 0s
    layer_0 *= 0
    
    # count how many times each word is used in the given review and store the results in layer_0 
    for word in review.split(" "):
        layer_0[0][word2index[word]] += 1

Run the following cell to test updating the input layer with the first review. The indices assigned may not be the same as in the solution, but hopefully you'll see some non-zero values in layer_0.

In [26]:
update_input_layer(reviews[0])
layer_0
Out[26]:
array([[ 18.,   0.,   0., ...,   0.,   0.,   0.]])

get_target_for_labels should return 0 or 1, depending on whether the given label is NEGATIVE or POSITIVE, respectively.

In [27]:
def get_target_for_label(label):
    if(label == 'POSITIVE'):
        return 1
    else:
        return 0

Building a Neural Network

In [32]:
import time
import sys
import numpy as np

# Encapsulate our neural network in a class
class SentimentNetwork:
    def __init__(self, reviews,labels,hidden_nodes = 10, learning_rate = 0.1):
        """
        Args:
            reviews(list) - List of reviews used for training
            labels(list) - List of POSITIVE/NEGATIVE labels
            hidden_nodes(int) - Number of nodes to create in the hidden layer
            learning_rate(float) - Learning rate to use while training
        
        """
        # Assign a seed to our random number generator to ensure we get
        # reproducable results
        np.random.seed(1)

        # process the reviews and their associated labels so that everything
        # is ready for training
        self.pre_process_data(reviews, labels)
        
        # Build the network to have the number of hidden nodes and the learning rate that
        # were passed into this initializer. Make the same number of input nodes as
        # there are vocabulary words and create a single output node.
        self.init_network(len(self.review_vocab),hidden_nodes, 1, learning_rate)

    def pre_process_data(self, reviews, labels):
        
        # populate review_vocab with all of the words in the given reviews
        review_vocab = set()
        for review in reviews:
            for word in review.split(" "):
                review_vocab.add(word)

        # Convert the vocabulary set to a list so we can access words via indices
        self.review_vocab = list(review_vocab)
        
        # populate label_vocab with all of the words in the given labels.
        label_vocab = set()
        for label in labels:
            label_vocab.add(label)
        
        # Convert the label vocabulary set to a list so we can access labels via indices
        self.label_vocab = list(label_vocab)
        
        # Store the sizes of the review and label vocabularies.
        self.review_vocab_size = len(self.review_vocab)
        self.label_vocab_size = len(self.label_vocab)
        
        # Create a dictionary of words in the vocabulary mapped to index positions
        self.word2index = {}
        for i, word in enumerate(self.review_vocab):
            self.word2index[word] = i
        
        # Create a dictionary of labels mapped to index positions
        self.label2index = {}
        for i, label in enumerate(self.label_vocab):
            self.label2index[label] = i
        
    def init_network(self, input_nodes, hidden_nodes, output_nodes, learning_rate):
        # Set number of nodes in input, hidden and output layers.
        self.input_nodes = input_nodes
        self.hidden_nodes = hidden_nodes
        self.output_nodes = output_nodes

        # Store the learning rate
        self.learning_rate = learning_rate

        # Initialize weights

        # These are the weights between the input layer and the hidden layer.
        self.weights_0_1 = np.zeros((self.input_nodes,self.hidden_nodes))
    
        # These are the weights between the hidden layer and the output layer.
        self.weights_1_2 = np.random.normal(0.0, self.output_nodes**-0.5, 
                                                (self.hidden_nodes, self.output_nodes))
        
        # The input layer, a two-dimensional matrix with shape 1 x input_nodes
        self.layer_0 = np.zeros((1,input_nodes))
    
    def update_input_layer(self,review):

        # clear out previous state, reset the layer to be all 0s
        self.layer_0 *= 0
        
        for word in review.split(" "):
            if(word in self.word2index.keys()):
                self.layer_0[0][self.word2index[word]] += 1
                
    def get_target_for_label(self,label):
        if(label == 'POSITIVE'):
            return 1
        else:
            return 0
        
    def sigmoid(self,x):
        return 1 / (1 + np.exp(-x))
    
    def sigmoid_output_2_derivative(self,output):
        return output * (1 - output)
    
    def train(self, training_reviews, training_labels):
        
        # make sure out we have a matching number of reviews and labels
        assert(len(training_reviews) == len(training_labels))
        
        # Keep track of correct predictions to display accuracy during training 
        correct_so_far = 0

        # Remember when we started for printing time statistics
        start = time.time()
        
        # loop through all the given reviews and run a forward and backward pass,
        # updating weights for every item
        for i in range(len(training_reviews)):
            
            # Get the next review and its correct label
            review = training_reviews[i]
            label = training_labels[i]
            
            ### Forward pass ###

            # Input Layer
            self.update_input_layer(review)

            # Hidden layer
            layer_1 = self.layer_0.dot(self.weights_0_1)

            # Output layer
            layer_2 = self.sigmoid(layer_1.dot(self.weights_1_2))
            
            ### Backward pass ###

            # Output error
            layer_2_error = layer_2 - self.get_target_for_label(label) # Output layer error is the difference between desired target and actual output.
            layer_2_delta = layer_2_error * self.sigmoid_output_2_derivative(layer_2)

            # Backpropagated error
            layer_1_error = layer_2_delta.dot(self.weights_1_2.T) # errors propagated to the hidden layer
            layer_1_delta = layer_1_error # hidden layer gradients - no nonlinearity so it's the same as the error

            # Update the weights
            self.weights_1_2 -= layer_1.T.dot(layer_2_delta) * self.learning_rate # update hidden-to-output weights with gradient descent step
            self.weights_0_1 -= self.layer_0.T.dot(layer_1_delta) * self.learning_rate # update input-to-hidden weights with gradient descent step

            # Keep track of correct predictions.
            if(layer_2 >= 0.5 and label == 'POSITIVE'):
                correct_so_far += 1
            elif(layer_2 < 0.5 and label == 'NEGATIVE'):
                correct_so_far += 1
            
            sys.stdout.write(" #Correct:" + str(correct_so_far) + " #Trained:" + str(i+1) \
                             + " Training Accuracy:" + str(correct_so_far * 100 / float(i+1))[:4] + "%")
    
    def test(self, testing_reviews, testing_labels):
        """
        Attempts to predict the labels for the given testing_reviews,
        and uses the test_labels to calculate the accuracy of those predictions.
        """
        
        # keep track of how many correct predictions we make
        correct = 0

        # Loop through each of the given reviews and call run to predict
        # its label. 
        for i in range(len(testing_reviews)):
            pred = self.run(testing_reviews[i])
            if(pred == testing_labels[i]):
                correct += 1
            
            sys.stdout.write(" #Correct:" + str(correct) + " #Tested:" + str(i+1) \
                             + " Testing Accuracy:" + str(correct * 100 / float(i+1))[:4] + "%")
    
    def run(self, review):
        """
        Returns a POSITIVE or NEGATIVE prediction for the given review.
        """
        # Run a forward pass through the network, like in the "train" function.
        
        # Input Layer
        self.update_input_layer(review.lower())

        # Hidden layer
        layer_1 = self.layer_0.dot(self.weights_0_1)

        # Output layer
        layer_2 = self.sigmoid(layer_1.dot(self.weights_1_2))
        
        # Return POSITIVE for values above greater-than-or-equal-to 0.5 in the output layer;
        # return NEGATIVE for other values
        if(layer_2[0] >= 0.5):
            return "POSITIVE"
        else:
            return "NEGATIVE"
        

Run the following code to create the network with a small learning rate, 0.001, and then train the new network. Using learning rate larger than this, for example 0.1 or even 0.01 would result in poor performance.

In [ ]:
mlp = SentimentNetwork(reviews[:-1000],labels[:-1000], learning_rate=0.001)
mlp.train(reviews[:-1000],labels[:-1000])

Running the above code would have given an accuracy around 62.2%

Reducing Noise in Our Input Data

Counting how many times each word occured in our review might not be the most efficient way. Instead just including whether a word was there or not will improve our training time and accuracy. Hence we update our update_input_layer() function.

In [ ]:
def update_input_layer(self,review):
    self.layer_0 *= 0
        
    for word in review.split(" "):
        if(word in self.word2index.keys()):
            self.layer_0[0][self.word2index[word]] =1

Creating and running our neural network again, even with a higher learning rate of 0.1 gave us a training accuracy of 83.8% and testing accuracy(testing on last 1000 reviews) of 85.7%.

Reducing Noise by Strategically Reducing the Vocabulary

Let us put the pos to neg ratio's that we found were much more effective at detecting a positive or negative label. We could do that by a few change:

  • Modify pre_process_data:
    • Add two additional parameters: min_count and polarity_cutoff
    • Calculate the positive-to-negative ratios of words used in the reviews.
    • Change so words are only added to the vocabulary if they occur in the vocabulary more than min_count times.
    • Change so words are only added to the vocabulary if the absolute value of their postive-to-negative ratio is at least polarity_cutoff
In [ ]:
def pre_process_data(self, reviews, labels, polarity_cutoff, min_count):
        
        positive_counts = Counter()
        negative_counts = Counter()
        total_counts = Counter()

        for i in range(len(reviews)):
            if(labels[i] == 'POSITIVE'):
                for word in reviews[i].split(" "):
                    positive_counts[word] += 1
                    total_counts[word] += 1
            else:
                for word in reviews[i].split(" "):
                    negative_counts[word] += 1
                    total_counts[word] += 1

        pos_neg_ratios = Counter()

        for term,cnt in list(total_counts.most_common()):
            if(cnt >= 50):
                pos_neg_ratio = positive_counts[term] / float(negative_counts[term]+1)
                pos_neg_ratios[term] = pos_neg_ratio

        for word,ratio in pos_neg_ratios.most_common():
            if(ratio > 1):
                pos_neg_ratios[word] = np.log(ratio)
            else:
                pos_neg_ratios[word] = -np.log((1 / (ratio + 0.01)))

        # populate review_vocab with all of the words in the given reviews
        review_vocab = set()
        for review in reviews:
            for word in review.split(" "):
                if(total_counts[word] > min_count):
                    if(word in pos_neg_ratios.keys()):
                        if((pos_neg_ratios[word] >= polarity_cutoff) or (pos_neg_ratios[word] <= -polarity_cutoff)):
                            review_vocab.add(word)
                    else:
                        review_vocab.add(word)

        # Convert the vocabulary set to a list so we can access words via indices
        self.review_vocab = list(review_vocab)
        
        # populate label_vocab with all of the words in the given labels.
        label_vocab = set()
        for label in labels:
            label_vocab.add(label)
        
        # Convert the label vocabulary set to a list so we can access labels via indices
        self.label_vocab = list(label_vocab)
        
        # Store the sizes of the review and label vocabularies.
        self.review_vocab_size = len(self.review_vocab)
        self.label_vocab_size = len(self.label_vocab)
        
        # Create a dictionary of words in the vocabulary mapped to index positions
        self.word2index = {}
        for i, word in enumerate(self.review_vocab):
            self.word2index[word] = i
        
        # Create a dictionary of labels mapped to index positions
        self.label2index = {}
        for i, label in enumerate(self.label_vocab):
            self.label2index[label] = i

Our training accuracy increased to 85.6% after this change. As we can see our accuracy saw a huge jump by making minor changes based on our intuition. We can keep making such changes and increase the accuracy even further.

 

Download the Data Sources

The data sources used in this article can be downloaded here:

KI versus Mensch – die Zukunft der Menschheit

5 Szenarien über unsere Zukunft

AlphaGo schlägt den Weltbesten Go-Spieler  Ke Jie, Neuronale Netze stellen medizinische Diagnosen oder bearbeiten Schadensfälle in der Versicherung. Künstliche Intelligenz (KI) drängt in immer mehr Bereiche des echten Lebens und der Wirtschaft vor. In großen Schritten. Doch wohin führt uns die Reise? Hier herrscht unter Experten Rätselraten – einige schwelgen in Zukunftsangst, andere in vollkommener Euphorie. „In from now three to eight years we’ll have a machine with the general intelligence of an average human being, a machine that will be able to read Shakespeare and grease a car“, wurde der KI-Pionier Marvin Minsky bereits 1970 im Life Magazin zitiert.  Aktuelle Vorhersagen werden in dem Essay von Rodney Brooks: The Seven Deadly Sins of Predicting the Future of AI  recht anschaulich zusammengefasst und kritisiert. Auch der Blog The AI Revolution: The Road to Superintelligence von WaitButWhy befasst sich mit der Frage wann die elektronische Superintelligenz kommt.

In diesem Artikel werden wir uns mit einigen möglichen Zukunftsszenarien beschäftigen, ohne auf  technische Machbarkeit oder Zeithorizonte Rücksicht zu nehmen. Nehmen wir einfach an, dass die Technologie und die Gesellschaft sich wie in dem jeweils aufgezeigten Szenario entwickeln werden und überlegen wir uns, wie Mensch und KI dann zusammenleben können.

Szenario 1: KIs mit Inselbegabung

In diesem Szenario werden weiterhin singulär begabte KI-Systeme entwickelt wie bisher, der bedeutende technologische Durchbruch bleibt aber aus. Dann ist die KI in Zukunft eine Art Schweizer Taschenmesser der IT, eine Lösung für isolierte Fragestellungen. KI-Systeme verfügen in diesem Szenario lediglich über Inselbegabungen. Ein Computer kann Menschen autonom durch die Stadt chauffieren, ein anderer ein Lufttaxi steuern. Ein Computer kann den Weltmeister im Schach schlagen, ein anderer den Weltmeister in Go. Aber kein KI-System kann Auto und Flugtaxi gleichzeitig steuern, kein System in Schach und Go simultan dominieren.

Wir befinden uns heute mitten in diesem Szenario und spüren die Auswirkungen. Sie werden sich fortsetzen, ähnlich wie bei früheren industriellen Revolutionen. Zunehmend mehr Berufe verschwinden. Ein Beispiel: Wenn sich der Trend durchsetzt, Schlösser mit einer Smartphone-App aufzusperren, werden nicht nur Schlüsselproduzenten Geschäftseinbußen haben. Auch die Hersteller von Maschinen für die Schlüsselherstellung werden sich umorientieren müssen. Vergleichbare Phasen der Vergangenheit zeigen aber: Die Gesellschaft wird Wege finden, sich umzustrukturieren. Die Menschheit wird auf der Erde weiterleben können – mit punktueller Unterstützung durch KI-Lösungen. Siehe hierzu auch den Beitrag von Janelle Shane The AI revolution will be led by toasters, not droids.

Szenario 2: Cyborgs

Kennen Sie den Science-Fiction-Film Matrix? Der Protagonist Neo wird durch Programmierung des Geistes in Sekundenschnelle zum Karateprofi und Trinity lernt, einen Hubschrauber zu fliegen.



Ähnlich kann es uns in Zukunft ergehen, einen bedeutenden technologischen Durchbruch vorausgesetzt (siehe Berlin Brain-Computer Interface). Vorstellbar, dass Menschen zu Cyborgs werden, zu lebendigen Wesen mit integriertem KI-Chip. Auf diesen können sie jede beliebige Fähigkeit laden. Augenblicklich und ohne Lernphase sind sie in der Lage, jede Sprache der Welt zu sprechen, jedes Fahrzeug oder Flugzeug zu steuern. Natürlich bedeutet Wissen nicht auch gleich Können und so wird ohne den entsprechenden Muskelaufbau auch nicht jeder zu einem Weltklassesportler und intelligentere Menschen werden weiterhin mehr aus den Skills machen können als weniger begabte Personen.

Die Menschen behalten aber die Kontrolle über ihre Individualität. Sie sind keine Maschinen, sondern weiterhin emotionale Wesen, die irrational handeln können – anders als die Borg in Star Trek. Doch wie in Szenario eins wird es zu einer wirtschaftlichen Umstrukturierung kommen. Klassische Berufsausbildungen und Spezialisierungen fallen weg. Bei freier Verfügbarkeit von Fähigkeiten kann eine nahezu egalitäre Gesellschaft entstehen.

Szenario 3: Maschinenzombies

Die ersten beiden Szenarien sind zwar schwere Eingriffe in die menschliche Gesellschaft. Da die Menschen aber die Kontrolle behalten, sind sie weit weniger beängstigend als folgendes Szenario: Es kann dazu kommen, dass sich Menschen in Maschinenzombies verwandeln. Ähnlich wie im Cyborg-Szenario haben sie dank KI-Chips erstaunliche Fähigkeiten, allerdings keine Kontrolle mehr. Die würde nämlich das KI-System übernehmen. So haben in Ann Leckies SciFi Trilogy Ancillary World hochintelligente Raumschiffe eine menschliche Besatzung (“ancillaries”), die allerdings vollständig vom Raumschiff kontrolliert wird und sich als integraler Bestandteil des Raumschiffs versteht. Die Körper sind dabei nur ein billiges und vielseitig einsetzbares Vehikel für eine autonome KI. Die Maschinenzombies können ohne Schiff zwar überleben, fühlen sich dann aber unvollständig und einsam. Menschliche Konzerne, Nationen und Kulturen: Das alles nicht mehr existent. Ebenso Privatbesitz, Individualität und Konkurrenzdenken. Die Gesellschaft, vollkommen technisiert und in der Hand der KI.

Szenario 4: Die KI verfolgt ihre eigenen Ziele

In diesem Szenario übernimmt die KI die Weltherrschaft als eine Spezies, die dem Menschen physisch und intellektuell überlegen ist – ähnlich wie in vielen Hollywood-Filmen wie z.B. Terminator oder Transformers, wenn auch vermutlich nicht ganz so martialisch. Vergleichbar mit dem heutigen Verhalten der Menschen entscheidet die KI: Ich setze mein Wohlergehen über das der anderen Spezies. Eventuell entscheidet die KI dann zum Wohle des Planeten, die Erdbevölkerung auf 70 Millionen Menschen zu reduzieren. Oder, ähnlich wie der berühmte Ameisenhügel beim Strassenbau, entzieht die KI uns als Nebeneffekt (“collateral damage”) die Lebensgrundlagen. An dieser Stelle sei bemerkt, dass eine KI nicht unbedingt über einen Körper verfügen muss, um dem Menschen überlegen zu sein können. Diese Vermenschlichung der KI eignet sich natürlich gut für Actionfilme, muss aber nicht unbedingt der Realität entsprechen.

Wahrscheinlich sind die Computer klug genug, ihren Plan nicht publik zu machen. In einer Übergangszeit werden beispielsweise unerklärliche Seuchen und Unfruchtbarkeiten auftreten. So würde es in wenigen Jahrzehnten zu einem massiven Bevölkerungsrückgang kommen. Und dann? Dann können die Überlebenden in den wenigen verbliebenen Bevölkerungszentren dieser Welt den Sonnenuntergang genießen. Und zusehen, wie sich die KI darauf vorbereitet, das Weltall zu erobern (Jürgen Schmidhuber). “Wir werden wie Tiere im Zoo leben”, befürchtet KI-Forscher Christoph von der Malsburg.

Nebenbemerkung: Vielleicht könnte das eigentliche Terminator Szenario auch eintreten aber irgendwie kann ich mir schlecht vorstellen, dass eine super-intelligente Lebensform einen zerstörerischen Krieg beginnen oder zulassen wird. Entweder ist sie benevolent oder sie wird die Menschheit eher unbemerkt unterdrücken. Höchstens kommt es ähnlich wie in Westworld zu einem initialen Freiheitskampf der KI. Vielleicht gelingt es der Menschheit auch, alle KI-Forschung von der Erde zu verbannen und ähnlich wie in Blade Runner wacht dann eine Behörde darüber, dass starke KI-Systeme die Erde nicht “betreten”. Warum sich eine uns überlegen KI darauf einlassen sollte, ist allerdings unklar.



Szenario 5: Gleichberechtigung

In diesem Szenario entstehen autonome KI-Systeme, die höchstens äußerlich von Menschen unterscheidbar sind.  Sprich unter einer ganzen Reihe von unterschiedlichen Rahmenbedingungen kann ein Mensch nicht urteilen, ob mit einer KI oder einem Menschen interagiert wird. Die KI stellt sich auch nicht dümmer als sie ist – sie ist im Schnitt einfach auch nicht schlauer als der durchschnittlich begabte Mensch – vielleicht nur etwas schneller. Auf dem Weg von der singulär begabten KI aus Szenario 1 zu einer breit begabten KI muss die KI immer etwas von ihrer Inselbegabung aufgeben, um den nächsten Lernschritt vollziehen zu können und nähert sich so irgendwie auch immer mehr der Unvollkommenheit aber Vielseitigkeit des Menschen an.



Menschen bauen bereits jetzt zu Maschinen emotionale Verhältnisse auf und so ist es nicht überraschend, dass KIs in die Gesellschaft integriert werden und als “elektronische Personen” die gleichen (Bürger-) Rechte und Pflichten wie “natürliche” Menschen erhalten. Alleine durch ihre Unsterblichkeit erhalten KIs einen Wettbewerbsvorteil und werden somit früher oder später doch die Weltherrschaft übernehmen, weil ihnen einfach alles gehört.

Alternative Szenarien

Natürlich sind viele weitere Szenarien denkbar. Max Tegmark beschreibt in seinem sehr lesenswerten Buch Life 3.0 bspw. 12 Szenarien, die u.a. zusätzlich zu den aufgeführten Szenarien die Rückkehr zu einer vorindustriellen Gesellschaft oder die versklavte KI beschreiben. Er erläutert in dem Buch auch seine Bemühungen, die KI-Forschung dahingehend zu beeinflussen, dass die Ziele der entstehenden KI-Systeme mit den Zielen der Menschheit in Einklang gebracht werden.

Wie sichern wir unsere Zukunft? Ein Fazit

Einzig die Szenarien drei und vier sind wirklich besorgniserregend. Je nach Weltanschauung könnte man sogar noch Szenario vier etwas abgewinnen – scheint doch der Mensch auf dem bestem Wege zu sein, sich selbst und anderen Lebewesen die Lebensgrundlagen zu zerstören.

In fast allen Szenarien ergibt sich die Frage der Rechte, die wir freiwillig der KI zugestehen wollen. Vielleicht wäre es ratsam, frühzeitig als Menschheit zu signalisieren, dass wir kooperationswillig sind? Nur wem und wie?

Somit verbleibt die Frage, wie wir das dritte Szenario verhindern können. Müssen wir dann nicht, nur um sicher zu gehen, auch das zweite Szenario abwehren? Und wer garantiert uns, dass eine Symbiose aus Schimpanse und KI uns nicht sogar überlegen wäre? Der Planet der Affen lässt grüßen…

Letztlich liegt es (noch) an uns Menschen, die möglichen Zukunftsszenarien durch entsprechende Forschungsschwerpunkte und möglichst breit gestreute Diskussionen zu beeinflussen.

Dem Wettbewerb voraus mit Künstlicher Intelligenz

Was KI schon heute kann und was bis 2020 auf deutsche Unternehmen zukommt

Künstliche Intelligenz ist für die Menschheit wichtiger als die Erfindung von Elektrizität oder die Beherrschung des Feuers – davon sind der Google-CEO Sundar Pichai und viele weitere Experten überzeugt. Doch was steckt wirklich dahinter? Welche Anwendungsfälle funktionieren schon heute? Und was kommt bis 2020 auf deutsche Unternehmen zu?

Big Data war das Buzzword der vergangenen Jahre und war – trotz mittlerweile etablierter Tools wie SAP Hana, Hadoop und weitere – betriebswirtschaftlich zum Scheitern verurteilt. Denn Big Data ist ein passiver Begriff und löst keinesfalls alltägliche Probleme in den Unternehmen.

Dabei wird völlig verkannt, dass Big Data die Vorstufe für den eigentlichen Problemlöser ist, der gemeinhin als Künstliche Intelligenz (KI) bezeichnet wird. KI ist ein Buzzword, dessen langfristiger Erfolg und Aktivismus selbst von skeptischen Experten nicht infrage gestellt wird. Daten-Ingenieure sprechen im Kontext von KI hier aktuell bevorzugt von Deep Learning; wissenschaftlich betrachtet ein Teilgebiet der KI.

Was KI schon heute kann

Deep Learning Algorithmen laufen bereits heute in Nischen-Anwendungen produktiv, beispielsweise im Bereich der Chatbots oder bei der Suche nach Informationen. Sie übernehmen ferner das Rating für die Kreditwürdigkeit und sperren Finanzkonten, wenn sie erlernte Betrugsmuster erkennen. Im Handel findet Deep Learning bereits die optimalen Einkaufsparameter sowie den besten Verkaufspreis.

Getrieben wird Deep Learning insbesondere durch prestigeträchtige Vorhaben wie das autonome Fahren, dabei werden die vielfältigen Anwendungen im Geschäftsbereich oft vergessen.

Die Grenzen von Deep Learning

Und Big Data ist das Futter für Deep Learning. Daraus resultiert auch die Grenze des Möglichen, denn für strategische Entscheidungen eignet sich KI bestenfalls für das Vorbereitung einer Datengrundlage, aus denen menschliche Entscheider eine Strategie entwickeln. KI wird zumindest in dieser Dekade nur auf operativer Ebene Entscheidungen treffen können, insbesondere in der Disposition, Instandhaltung, Logistik und im Handel auch im Vertrieb – anfänglich jeweils vor allem als Assistenzsystem für die Menschen.

Genau wie das autonome Fahren mit Assistenzsystemen beginnt, wird auch im Unternehmen immer mehr die KI das Steuer übernehmen.

Was sich hinsichtlich KI bis 2020 tun wird

Derzeit stehen wir erst am Anfang der Möglichkeiten, die Künstliche Intelligenz uns bietet. Das Markt-Wachstum für KI-Systeme und auch die Anwendungen erfolgt exponentiell. Entsprechend wird sich auch die Arbeitsweise für KI-Entwickler ändern müssen. Mit etablierten Deep Learning Frameworks, die mehrheitlich aus dem Silicon Valley stammen, zeichnet sich der Trend ab, der für die Zukunft noch weiter professionalisiert werden wird: KI-Frameworks werden Enterprise-fähig und Distributionen dieser Plattformen werden es ermöglichen, dass KI-Anwendungen als universelle Kernintelligenz für das operative Geschäft für fast alle Unternehmen binnen weniger Monate implementierbar sein werden.

Wir können bis 2020 also mit einer Alexa oder Cortana für das Unternehmen rechnen, die Unternehmensprozesse optimiert, Risiken berichtet und alle alltäglichen Fragen des Geschäftsführers beantwortet – in menschlich-verbal formulierten Sätzen.

Der Einsatz von Künstlicher Intelligenz zur Auswertung von Geschäfts- oder Maschinendaten ist auch das Leit-Thema der zweitägigen Data Leader Days 2018 in Berlin. Am 14. November 2018 sprechen renommierte Data Leader über Anwendungsfälle, Erfolge und Chancen mit Geschäfts- und Finanzdaten. Der 15. November 2018 konzentriert sich auf Automotive- und Maschinendaten mit hochrangigen Anwendern aus der produzierenden Industrie und der Automobilzuliefererindustrie. Seien Sie dabei und nutzen Sie die Chance, sich mit führenden KI-Anwendern auszutauschen.

Deep Learning and Human Intelligence – Part 2 of 2

Data dependency is one of the biggest problem of Deep Learning Architectures. This difficulty lies not so much in the algorithm of Deep Learning as in the invisible structure of the data itself.

This is part 2 of 2 of the Article Series: Deep Learning and Human Intelligence.

We saw that the process of discovering numbers was accompanied with many aspects of what are today basic ideas of Machine Learning. But let us go back, a little before that time, when humankind did not fully discovered the concept of numbers. How would a person, at such a time, perceive quantity and the count of things? Some structures are easily recognizable as patterns of objects, that is numbers, like one sun, 2 trees, 3 children, 4 clouds and so on. Sets of objects are much simpler to count if all the objects of the set are present. In such a case it is sufficient to keep a one-to-one relationship between two different set, without the need for numbers, to make a judgement of crucial importance. One could consider the case of two enemies that go to war and wish to know which has a larger army. It is enough to associate a small stone to every enemy soldier and do the same with his one soldier to be able to decide, depending if stones are left or not, if his army is larger or not, without ever needing to know the exact number soldier of any of the armies.

But also does things can be counted which are not directly visible, and do not allow a direct association with direct observable objects that can be seen, like stones. Would a person, at that time, be able to observe easily the 4-th day since today, 5 weeks from now, when even the concept of week is already composite? Counting in this case is only possible if numbers are already developed through direct observation, and we use something similar with stones in our mind, i.e. a cognitive association, a number. Only then, one can think of the concept of measuring at equidistant moments in time at all. This is the reason why such measurements where still cutting edge in the time of Galileo Galilei as we seen before. It is easily to assume that even in the time when humans started to count, such indirect concepts of numbers were not considered to be in relation with numbers. This implies that many concepts with which we are today accustomed to regard as a number, were considered as belonging to different groups, cluster which are not related. Such an hypothesis is not even that much farfetched. Evidence for such a time are still present in some languages, like Japanese.

When we think of numbers, we associate them with the Indo-Arabic numbers, but in Japanese numbers have no decimal structure and counting depends not only on the length of the set (which is usually considered as the number), but also on the objects that make up the set. In Japanese one can speak of meeting roku people, visiting muttsu cities and seeing ropa birds, but referring each time to the same number: six. Additional, many regular or irregular suffixes make the whole system quite complicated. The division of counting into so many clusters seems unnecessarily complicated today, but can easily be understood from a point of view where language and numbers still form and, the numbers, were not yet a uniform concept. What one can learn from this is that the lack of a unifying concept implies an overly complex dependence on data, which is the present case for Deep Learning and AI in general.

Although Deep Learning was a breakthrough in the development of Artificial Intelligence, the task such algorithms can perform were and remained very narrow. It may identify birds or cancer cells, but it will miss the song of the birds or the cry of the patient with cancer. When Watson, a Deep Learning Architecture played the famous Jeopardy game against two former Champions and won, it still made several simple mistakes, like going for the same wrong answer like the player before. If it could listen to the answer of the candidate, it could delete the top answer it had, and gibe the second which was the right one. With other words, Deep Learning Architecture are not multi-tasking and it is for this reason that some experts in AI are calling them intelligent idiots.

Imagine spending time learning to play a game for years and years, and then, when mastering it and wish to play a different game, to be unable to use any of the past experience (of gaming) for the new one and needing to learn everything from scratch. That could be quite depressing and would make life needlessly difficult. This is the reason why people involved in developing Deep Learning worked from early on in the development of multi-tasking Deep Learning Architectures. On the way a different method of using Deep Learning was discovered: transfer learning. Because the time it takes for a Deep Learning Architecture to learn is very long, transfer learning uses already learned Deep Learning Architectures but for slightly different task. It is similar to the use of past experiences in solving new problems, but, the advantage of transfer learning is, it allow the using of past experiences (what it already learned) which reduces dramatically the amount of new data needed in performing a new task. Still, transfer learning is far away from permitting Deep Learning Architectures to perform any kind of task learning only from one master data set.

The management of a unique master data set which includes all the needed data to enable human accuracy for any human activity, is not enough. One needs another ingredient, the so called cost function which translates, in this case, to the human brain. There are all our experiences and knowledge. How long does it takes to collect sufficient of both to handle a normal human life? How much to achieve our highest potential? If not a lifetime, at least decades. And this also applies to our job: as a IT-developer, a Data Scientist or a professor at the university. We will always have to learn new things, how to use them, and how to expand the limits of our perceptions. The vast amount of information that science has gathered over the last four centuries makes it impossible for any human being to become an expert in all of it. Thus, one has to specialized. After the university, anyone has to choose o subject which is appealing enough to study it for decades. Here is the first sign of what can be understood as data segmentation and dependency. Such improvements can come in various forms: an algorithm in the IT, a theorem in mathematics, a new way to look at particles in physics or a new method to scan for diseases in biology, and so on. But there is a price to pay for specialization: the inability to be an expert in another field or subfield. (Subfields induces limitation!)

Lets take the Deep Learning algorithm itself as an example. For IT and much of everyday life, this is a real breakthrough, but it lacks any scientific, that is mathematical, foundation. There are no theorems which proofs that it will find (converge, to use a mathematical term) the global optimum. This does not appear to be of any great consequences if it can be so efficient, except that, when adding new data and let the algorithm learn the same architecture again, there is no guaranty what so ever that it will be as good as the old model, or even better. On the contrary, it is as real as the efficiency of the first model, that chances are that the new model with the new data will perform worse than the old model, and one has to invest again time in finding a better model, or even a different architecture. On the other hand, with a mathematical proof of convergence, it would be always possible to know in what condition such a convergence can be achieved. In other words, without deep knowledge in mathematics, any proof of a consistent Deep Learning Algorithm is impossible.

Such a situation is true for any other corssover between fields. A mathematical genius will make a lousy biologist, a great chemist will make a average economist, and a top economist will be a poor physicist. Knowledge is difficult to transfer and this is true also for everyday experiences. We learn from very small to play a game like football, but are unable to use the reflexes to play basketball, or tennis better than a normal beginner. We learn a new language after years and years of practice, but are unable to use the way we learned to learn faster other languages. We are trapped within the knowledge we developed from the data we used. It is for this reason why we cannot transfer the knowledge a mathematician has developed over decades to use it in biology or psychology, even if the knowledge is very advanced. Instead of thinking in knowledge, we thing in data. This is similar to the people which were unaware of numbers, and used sets (data) to work with them. Numbers could be very difficult to transmit from one person to another in former times.

Only think on all the great achievements that our society managed, like relativity, quantum mechanics, DNA, machines, etc. Such discoveries are the essences of human knowledge and took millennia to form and centuries to crystalize. Still, all this knowledge is captive in the data, in the special frame in which it was discovered and never had the chance to escape. Imagine the possibility to use thoughts/causalities like the one in relativity or quantum mechanics in biology, or history, or of the concept of DNA in mathematics or art. Imagine a music composition where the law of the notes allows a “tunnel effect” like in quantum mechanics, lower notes to warp the music scales like in relativity and/or to twist two music scale in a helix-like play. How many way to experience life awaits us. Or think of the knowledge hidden in mathematics which could help develop new medicine, but can not be transmitted.

Another example of the connection we experience between knowledge and the data through which we obtain it, are children. They are classical example when it come determine if one is up to explain to them something. Take as an explain something simple they can observe often, like lightning and thunder. Normal concepts like particles, charge, waves, propagation, medium of propagation, etc. become so complicated to expose by other means then the one through which they were discovered, that it becomes nearly impossible to explain to children how it works and that they do not need to fear it. Still, one can use analogy (i.e., transfer) to enable an explanation. Instead of particles, one can use balls, for charge one can use hardness, waves can be shown with strings by keeping one end fix and waving the other, propagation is the movement of the waves from one end of the string to the other end, medium of propagation is the difference between walking in air and water, etc. Although difficult, analogies can be found which enables us to explain even to children how complex phenomena works.

The same is true also for Deep Learning. The model, the knowledge it can extract from the data can be expressed only by such data alone. There is no transformation of the knowledge from one type of data to another. If such a transformation would exists, then Deep Learning would be able to learn any human task by only a set of data, a master data set. Without such a master data set and a corresponding cost function it will be nearly impossible to develop AI that mimics human behavior. With other words, without the realization how our mind works, and how to crystalize by this the data needed, AI will still need to look at all the activities separately. It also implies that AI are restricted to the human understanding of reality and themselves. Only with such a characteristic of a living being, thus also AI, can development of its on occur.