<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Oliver Flasch's Blog]]></title><description><![CDATA[AI and Data Strategy for German SMEs]]></description><link>https://blog.oliverflasch.de/</link><image><url>https://blog.oliverflasch.de/favicon.png</url><title>Oliver Flasch&apos;s Blog</title><link>https://blog.oliverflasch.de/</link></image><generator>Ghost 3.42</generator><lastBuildDate>Mon, 16 Mar 2026 11:37:33 GMT</lastBuildDate><atom:link href="https://blog.oliverflasch.de/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[What LLMs Can and Cannot Do]]></title><description><![CDATA[Will the capabilities of LLMs increase or plateau? Are current AI systems already good enough to cause significant transformation? Join me on a quick exploration of the possibilities and limitations of today's predominant AI paradigm.]]></description><link>https://blog.oliverflasch.de/what-llms-can-and-cannot-do/</link><guid isPermaLink="false">69b52c0f8971200c940db6d8</guid><category><![CDATA[LLM]]></category><category><![CDATA[AI]]></category><category><![CDATA[Philosophy]]></category><dc:creator><![CDATA[Oliver Flasch]]></dc:creator><pubDate>Sat, 14 Mar 2026 13:59:13 GMT</pubDate><media:content url="https://blog.oliverflasch.de/content/images/2026/03/ben-blumentritt-pY0VLxe9Lxw-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.oliverflasch.de/content/images/2026/03/ben-blumentritt-pY0VLxe9Lxw-unsplash.jpg" alt="What LLMs Can and Cannot Do"><p>Will the power of Large Language Models (LLMs) continue to increase, or will they reach a plateau soon? Might the capabilities of current frontier models and AI systems be already good enough to cause massive disruption as implementations ripple through the economy? Or will the limitations of large deep learning models trained on massive amounts of tokens, mainly text, limit their reach?</p><h3 id="emergent-consensus">Emergent Consensus</h3><p>At first sight, even experts seem to struggle to reach a consensus on these questions. I think that, on further investigation, this is no longer true. The predictions of imminent danger from superintelligent AI did not come to pass, shifting the "AI doomer" narrative to a less dramatic, yet still very serious scenario of massive job loss. On the "AI boomer" side, voices promising Artificial General Intelligence are noticeably subdued, while the focus shifts from models to complex agentic systems that seem to be able to automate (most) knowledge work (very soon). In other words, after a period of hype, a consensus seems to form, albeit slowly. Let's speculate on what that consensus might look like, maybe even answering some of our initial questions on the way.</p><h3 id="a-world-of-text">A World of Text</h3><p>First, note that LLMs live in a world of symbols ("tokens"), nearly all of them sourced from massive amounts of text graciously copied from the Internet. Today, training an LLM consists of three stages: Data collection and preprocessing, pre-training, and post-training. Empirical results, i.e. "scaling laws" show that the capabilities of LLMs seem to scale with three factors: Model size, dataset size, and training compute. As more or less all easily obtainable data is already used in pre-training, scaling the remaining factors lead to the current  investments in massive data centers. Algorithmic advances can be understood as a less controllable fourth factor, which recently resulted in "reasoning models". These models are post-trained by Reinforcement Learning with Verifiable Rewards (RLVR), a sort of "self play", where language models increase their performance by trying to find solutions to (often synthetically created) problems that are hard to solve, but whose solutions are easy to verify, such as certain mathematical proofs. Even multi-modal models that can process images, videos, and sound in addition to text, are, for reasons of efficiency and data availability, mainly pre-trained on text.</p><h3 id="embodiment">Embodiment</h3><p>Second, contrast how animals (including us) learn to how LLMs are trained. While animals continuously learn while interacting with an evolving physical and social world, current LLMs are pre-trained on a massive, but static corpus of mostly text tokens, then post-trained on a static set of tasks and human (i.e. often commercial) preferences. This naturally leads to book-smart, somewhat biased LLM agents whose "world models" fail at common sense, suggesting you take a walk to the car wash to save on gas. These limits of current LLMs clearly show the importance of "true" understanding by "grasping" the real world in a literal sense, as an embodied intelligent being.</p><p>In principle, the deep learning paradigm, given continous training, should be sufficient to create these embodied artifical intelligences. In practice, building efficient neural architectures and large datasets to learn from true interactions with the real world, augmented with simulations where possible, is a considerably more complex task than collecting an Internet worth of text and training on that. Current "world models" trained on video data or video games already demonstrate what is possible beyond LLMs, but are also limited by the lack of true interaction with the physical world.</p><h3 id="ai-agents-and-the-future-of-work">AI Agents and the Future of Work</h3><p>Applying these ideas to current and future LLMs, I'd conclude that the "street-smarts" of LLM-based AI agents will remain limited in often surprising ways. Their flexibility will stay constrained by what is possible through "in-context learning" for several years to come, meaning that LLM-based AI agents will not be able to truly gain experience on their jobs. As these agents continue to "live" in a world of text, they should not be able to distinguish between fact and fiction in a dependable way for the foreseeable future, excluding certain high-stakes use cases and necessitating complex "guard rails". Their "creativity" should be limited to the <a href="https://plato.stanford.edu/entries/creativity/">combinatorial kind</a>.</p><p>Taking these constraints into account, I think it's safe to say that LLM-based AI agents should not be able to directly replace humans in most roles. Productivity gains should arrive slowly, as organizations will need to change processes to create roles compatible with the limitations of LLM-based AI agents. When these roles are created though, AI agents should be able to automatically explore and synthesize solutions from existing ideas present in their massive training data sets, leading to interesting, even transformative results. </p><p><em>Photo by <a href="https://unsplash.com/de/fotos/eine-gruppe-bunter-vogel-sitzt-auf-einem-zaun-pY0VLxe9Lxw">Ben Blumentritt</a></em></p>]]></content:encoded></item><item><title><![CDATA[gerenbench - A Small Proprietary Benchmark for German RAG]]></title><description><![CDATA[RAG connects proprietary data to LLMs. This article presents results of "gerenbench", a small proprietary benchmark that uses German encyclopedia articles to estimate the accuracy of LLMs on RAG tasks.]]></description><link>https://blog.oliverflasch.de/gerenbench-a-small-proprietary-benchmark-for-german-rag/</link><guid isPermaLink="false">69ab2ee18971200c940db33a</guid><category><![CDATA[NLP]]></category><category><![CDATA[LLM]]></category><category><![CDATA[Data Science]]></category><dc:creator><![CDATA[Oliver Flasch]]></dc:creator><pubDate>Sat, 07 Mar 2026 12:48:00 GMT</pubDate><media:content url="https://blog.oliverflasch.de/content/images/2026/03/henrik-donnestad-HO1Evlp1p1o-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.oliverflasch.de/content/images/2026/03/henrik-donnestad-HO1Evlp1p1o-unsplash.jpg" alt="gerenbench - A Small Proprietary Benchmark for German RAG"><p>Retrieval Augmented Generation (RAG) is quickly maturing into a useful (and boring) technology to connect proprietary, often local data to Large Language Models (LLMs) to augment their training data with knowledge and skills through in-context learning. RAG can enable important use cases such as tailored AI assistants for answering questions, generating complex reports ("Deep Research") or automating tasks ("Agentic AI").</p><p>RAG employs an information retrieval system, like a database or search engine, to augment the context of an LLM with relevant information. There are many different ways of varying complexity to achieve this, from simple approaches that enrich a user’s prompt with search results every time to agentic workflows that enable an LLM to freely use multiple search engines or databases as tools. When connecting an LLM to proprietary data, it is extremely important to ensure that it cannot access public data sources at the same time, to prevent data exhilaration.</p><p>This article presents results of "gerenbench", a small proprietary benchmark that uses German encyclopedia articles to estimate the accuracy of LLMs on RAG tasks. It provides a glimpse at the native and RAG performance of open weight LLMs runnable on consumer hardware as of March 2026.</p><h2 id="benchmark-setup">Benchmark Setup</h2><p>The benchmarks contains 100 multiple choice questions based on 25 German encyclopedia articles from to mid-nineties. Each question contains the relevant article and four possible answers. It encompasses the following two scenarios:</p><ol><li>LLM-Only: The LLM is presented with the question and four possible answers. The relevant article is withheld.</li><li>LLM+RAG: The LLM is presented with the relevant article, the question and four possible answers.</li></ol><p>In both scenarios, the LLM is instructed (via the prompts given in the appendix) to answer the question by generating a single letter (A, B, C, or D) designating the correct answer. Therefore, scenario 1 tests world knowledge, while scenario 2 tests world knowledge and RAG capability. Result analysis is fully deterministic and automated. A redacted example task is given in the appendix of this article. Note that scenario 2 assumes the best case scenario where the relevant article is included in the LLMs context exclusively every time.</p><p>All experiments were run on an Apple M3 with 24 GB of unified RAM using <a href="https://ollama.com">Ollama</a> Version 0.17.4.</p><h2 id="results">Results</h2><p>The following 10 open weight LLMs where tested on both scenarios (Table 1):</p><!--kg-card-begin: markdown--><center>Table 1: LLMs Tested</center>
<table>
<thead>
<tr>
<th style="text-align:left">LLM Name</th>
<th style="text-align:left">Provider</th>
<th style="text-align:left">Model Card</th>
<th style="text-align:left">Size</th>
<th style="text-align:left">Quantization</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">Gemma 3 1b</td>
<td style="text-align:left">Google DeepMind</td>
<td style="text-align:left"><a href="https://huggingface.co/google/gemma-3-1b-it">Hugging Face Link</a></td>
<td style="text-align:left">1b</td>
<td style="text-align:left"><code>q4_K_M</code></td>
</tr>
<tr>
<td style="text-align:left">Gemma 3 12b</td>
<td style="text-align:left">Google DeepMind</td>
<td style="text-align:left"><a href="https://huggingface.co/google/gemma-3-12b-it">Hugging Face Link</a></td>
<td style="text-align:left">12b</td>
<td style="text-align:left"><code>q4_K_M</code></td>
</tr>
<tr>
<td style="text-align:left">gpt-oss-20b</td>
<td style="text-align:left">OpenAI</td>
<td style="text-align:left"><a href="https://huggingface.co/openai/gpt-oss-20b">Hugging Face Link</a></td>
<td style="text-align:left">20b</td>
<td style="text-align:left"><code>q4_K_M</code></td>
</tr>
<tr>
<td style="text-align:left">LFM2 24b-a2b</td>
<td style="text-align:left">Liquid AI</td>
<td style="text-align:left"><a href="https://huggingface.co/LiquidAI/LFM2-24B-A2B">Hugging Face Link</a></td>
<td style="text-align:left">24b-a2b</td>
<td style="text-align:left"><code>q4_K_M</code></td>
</tr>
<tr>
<td style="text-align:left">Ministral 3 3b</td>
<td style="text-align:left">Mistral AI</td>
<td style="text-align:left"><a href="https://huggingface.co/mistralai/Ministral-3-3B-Instruct-2512">Hugging Face Link</a></td>
<td style="text-align:left">3b</td>
<td style="text-align:left"><code>q4_K_M</code></td>
</tr>
<tr>
<td style="text-align:left">Ministral 3 8b</td>
<td style="text-align:left">Mistral AI</td>
<td style="text-align:left"><a href="https://huggingface.co/mistralai/Ministral-3-8B-Instruct-2512">Hugging Face Link</a></td>
<td style="text-align:left">8b</td>
<td style="text-align:left"><code>q4_K_M</code></td>
</tr>
<tr>
<td style="text-align:left">Ministral 3 14b</td>
<td style="text-align:left">Mistral AI</td>
<td style="text-align:left"><a href="https://huggingface.co/mistralai/Ministral-3-14B-Instruct-2512">Hugging Face Link</a></td>
<td style="text-align:left">14b</td>
<td style="text-align:left"><code>q4_K_M</code></td>
</tr>
<tr>
<td style="text-align:left">Mistral Small 3.2 24b</td>
<td style="text-align:left">Mistral AI</td>
<td style="text-align:left"><a href="https://huggingface.co/mistralai/Mistral-Small-3.2-24B-Instruct-2506">Hugging Face Link</a></td>
<td style="text-align:left">24 b</td>
<td style="text-align:left"><code>q4_K_M</code></td>
</tr>
<tr>
<td style="text-align:left">Phi-4-mini 3.8b</td>
<td style="text-align:left">Microsoft</td>
<td style="text-align:left"><a href="https://huggingface.co/microsoft/Phi-4-mini-instruct">Hugging Face Link</a></td>
<td style="text-align:left">3.8b</td>
<td style="text-align:left"><code>q4_K_M</code></td>
</tr>
<tr>
<td style="text-align:left">Phi-4 14b</td>
<td style="text-align:left">Microsoft</td>
<td style="text-align:left"><a href="https://huggingface.co/microsoft/phi-4">Hugging Face Link</a></td>
<td style="text-align:left">14b</td>
<td style="text-align:left"><code>q4_K_M</code></td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>Table 2 shows mean accuracies and processing times for scenario 1, i.e. LLM-Only performance, without RAG. "Prompt Duration" denotes the mean time in seconds taken to process the prompt, "Generate Duration" denotes the mean time in seconds taken to generate the answer. Best results are shown in bold font.</p><!--kg-card-begin: markdown--><center>Table 2: Accuracy and Processing Time without RAG (Ordered by Mean Accuracy)</center>
<table>
<thead>
<tr>
<th style="text-align:left">LLM Name</th>
<th style="text-align:right">Accuracy (Mean)</th>
<th style="text-align:right">Prompt Duration (Mean, s)</th>
<th style="text-align:right">Generate Duration (Mean, s)</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">Ministral 3 14b (it-2512-q4_K_M)</td>
<td style="text-align:right"><strong>0.76962</strong></td>
<td style="text-align:right">2.21029</td>
<td style="text-align:right">0.15751</td>
</tr>
<tr>
<td style="text-align:left">Mistral Small 3.2 24b (it-2506-q4_K_M)</td>
<td style="text-align:right">0.75916</td>
<td style="text-align:right">3.31204</td>
<td style="text-align:right">0.177936</td>
</tr>
<tr>
<td style="text-align:left">Ministral 3 8b (it-2512-q4_K_M)</td>
<td style="text-align:right">0.74703</td>
<td style="text-align:right">1.25622</td>
<td style="text-align:right">0.291435</td>
</tr>
<tr>
<td style="text-align:left">gpt-oss-20b</td>
<td style="text-align:right">0.71656</td>
<td style="text-align:right">0.865917</td>
<td style="text-align:right">34.4813</td>
</tr>
<tr>
<td style="text-align:left">Phi-4 14b (q4_K_M)</td>
<td style="text-align:right">0.69075</td>
<td style="text-align:right">2.39739</td>
<td style="text-align:right">0.971595</td>
</tr>
<tr>
<td style="text-align:left">Gemma 3 12b (it-q4_K_M)</td>
<td style="text-align:right">0.65124</td>
<td style="text-align:right">1.09994</td>
<td style="text-align:right">0.104123</td>
</tr>
<tr>
<td style="text-align:left">Ministral 3 3b (it-2512-q4_K_M)</td>
<td style="text-align:right">0.58928</td>
<td style="text-align:right">0.499747</td>
<td style="text-align:right">0.0415913</td>
</tr>
<tr>
<td style="text-align:left">LFM2 24b-a2b (q4_K_M)</td>
<td style="text-align:right">0.57037</td>
<td style="text-align:right">1.27881</td>
<td style="text-align:right">0.0333128</td>
</tr>
<tr>
<td style="text-align:left">Phi-4-mini 3.8b (q4_K_M)</td>
<td style="text-align:right">0.43891</td>
<td style="text-align:right">0.601904</td>
<td style="text-align:right">0.260388</td>
</tr>
<tr>
<td style="text-align:left">Gemma 3 1b (it-q4_K_M)</td>
<td style="text-align:right">0.2892</td>
<td style="text-align:right"><strong>0.080226</strong></td>
<td style="text-align:right"><strong>0.0246014</strong></td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>Table 3 shows mean accuracy and processing times for scenario 2, i.e. RAG. Best results are shown in bold font.</p><!--kg-card-begin: markdown--><center>Table 3: RAG Accuracy and Processing Time (Ordered by Mean Accuracy)</center>
<table>
<thead>
<tr>
<th style="text-align:left">LLM Name</th>
<th style="text-align:right">RAG Accuracy (Mean)</th>
<th style="text-align:right">RAG Prompt Duration (Mean, s)</th>
<th style="text-align:right">RAG Generate Duration (Mean, s)</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">Mistral Small 3.2 24b (it-2506-q4_K_M)</td>
<td style="text-align:right"><strong>0.97025</strong></td>
<td style="text-align:right">11.9376</td>
<td style="text-align:right">0.172491</td>
</tr>
<tr>
<td style="text-align:left">gpt-oss-20b</td>
<td style="text-align:right">0.96963</td>
<td style="text-align:right">2.0207</td>
<td style="text-align:right">8.44707</td>
</tr>
<tr>
<td style="text-align:left">Phi-4 14b (q4_K_M)</td>
<td style="text-align:right">0.96947</td>
<td style="text-align:right">10.1061</td>
<td style="text-align:right">0.690504</td>
</tr>
<tr>
<td style="text-align:left">Ministral 3 8b (it-2512-q4_K_M)</td>
<td style="text-align:right">0.96173</td>
<td style="text-align:right">5.20979</td>
<td style="text-align:right">0.130678</td>
</tr>
<tr>
<td style="text-align:left">Ministral 3 14b (it-2512-q4_K_M)</td>
<td style="text-align:right">0.96017</td>
<td style="text-align:right">8.46598</td>
<td style="text-align:right">0.146143</td>
</tr>
<tr>
<td style="text-align:left">Gemma 3 12b (it-q4_K_M)</td>
<td style="text-align:right">0.96008</td>
<td style="text-align:right">4.45786</td>
<td style="text-align:right">0.137686</td>
</tr>
<tr>
<td style="text-align:left">Ministral 3 3b (it-2512-q4_K_M)</td>
<td style="text-align:right">0.91026</td>
<td style="text-align:right">2.15204</td>
<td style="text-align:right">0.0413062</td>
</tr>
<tr>
<td style="text-align:left">LFM2 24b-A2b (q4_K_M)</td>
<td style="text-align:right">0.87004</td>
<td style="text-align:right">2.62395</td>
<td style="text-align:right">0.0338279</td>
</tr>
<tr>
<td style="text-align:left">Phi-4-mini 3.8b (q4_K_M)</td>
<td style="text-align:right">0.81902</td>
<td style="text-align:right">2.58125</td>
<td style="text-align:right">0.615699</td>
</tr>
<tr>
<td style="text-align:left">Gemma 3 1b (it-q4_K_M)</td>
<td style="text-align:right">0.49717</td>
<td style="text-align:right"><strong>0.261436</strong></td>
<td style="text-align:right"><strong>0.0246014</strong></td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>Figure 1 shows the accuracies of all tested LLMs for scenario 1 (LLM only) and scenario 2 (LLM+RAG). 95% confidence intervals, generated via bootstrap sampling, are included. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.oliverflasch.de/content/images/2026/03/gerenbench_results_barplot-1.svg" class="kg-image" alt="gerenbench - A Small Proprietary Benchmark for German RAG"><figcaption>Figure 1: Accuracy for LLM-Only (Scenario 1) vs. LLM+RAG (Scenario 2)</figcaption></figure><p>Finally, figure 2 shows a Pareto plot of LLM accuracy vs. total generation time.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.oliverflasch.de/content/images/2026/03/gerenbench_results_pareto_front_plot_max_15s.svg" class="kg-image" alt="gerenbench - A Small Proprietary Benchmark for German RAG"><figcaption>Figure 2: Pareto Plot of LLM Accuracy vs. Generation Time for LLM-Only (Scenario 1, Blue) and LLM+RAG (Scenario 2, Red)</figcaption></figure><h2 id="conclusions-and-next-steps">Conclusions and Next Steps</h2><p>The results show that mid-size, open-weight LLMs today are capable of solving the benchmark task with over 95% accuracy (an error rate of less than 1 in 20 queries). In contrast, when relying solely on internal world knowledge, even the best models tested are only about 75% accurate, producing an incorrect result for 1 out of 4 queries, on average. If an accuracy of 95% is good enough should be evaluated against the risk profile of the task at hand.</p><p>Result accuracy scales with model size, while there are still significant differences between models of comparable size, demonstrating the value of creating custom (proprietary) benchmarks. Model runtime is highly dependent on the LLM runtime (hard- and software used for inference), but also important in practice, which is why it is reported in the benchmark results.</p><p>Currently, the RAG portion of this benchmark (scenario 2) assumes perfect information retrieval, i.e. that relevant information is present every time, which is rarely the case in practice. Future iterations could be improved by including a quota of test cases where relevant information is missing. Furthermore, to move beyond the current multiple-choice format, a third scenario could be added to test free-form generation accuracy. This would involve removing multiple-choice options from the prompt template and evaluating the generated answers using pattern matching or an "LLM-as-a-judge" approach.</p><h2 id="appendix">Appendix</h2><p>All LLMs under test where instructed with the following prompt templates:</p><pre><code class="language-Python">system_prompt = """
Your task is to answer multiple-choice questions with high precision and accuracy.
You must answer with the letter of the correct choice, i.e. either A,B,C, or D.
You must not generate anything else. 
"""

prompt_template = """**Question:**
{question}

**Multiple Choice Options:**
A) {answer_a}
B) {answer_b}
C) {answer_c}
D) {answer_d}

Answer the given question using one of the multiple choice options.
You must return either A, B, C, or D.
You must return exactly one letter.
---
"""

rag_prompt_template = """**Context:**
{context}

**Question:**
{question}

**Multiple Choice Options:**
A) {answer_a}
B) {answer_b}
C) {answer_c}
D) {answer_d}

Based on the given context, answer the given question using one of the multiple choice options.
You must return either A, B, C, or D.
You must return exactly one letter.
---
"""</code></pre><p>The <code>system_prompt</code> gives general instructions to the model, while the <code>prompt_template</code> is used for the LLM-Only part of the benchmark (scenario 1) and <code>rag_prompt_template</code> is used for the LLM+RAG part of the benchmark (scenario 2).</p><p>The following shows a redacted example task of the benchmark:</p><!--kg-card-begin: markdown--><p><strong>Article</strong><br>
Thyssen AG, Dachgesellschaft eines in den Bereichen Investitionsgüter, Handel und Dienstleistungen und Stahlerzeugung und -verarbeitung tätigen Konzerns; Sitz: Düsseldorf. Die T. AG entwickelte sich, v.a. aus den beiden Hauptunternehmen Thyssen &amp; Co. KG (gegr. 1871) und August Thyssen-Hütte AG (gegr. 1890), bis 1914 <em>(...redacted...)</em></p>
<p><strong>Question</strong><br>
Durch wen sollte die <em>(...redacted...)</em>?</p>
<p><strong>Possible Answers</strong><br>
a) <em>(...redacted...)</em><br>
b) <em>(...redacted...)</em><br>
c) <em>(...redacted...)</em><br>
d) <em>(...redacted...)</em></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[LLM Update]]></title><description><![CDATA[I don’t use Large Language Models (LLMs) to write articles on my blog. I use them heavily behind the scenes, though. ]]></description><link>https://blog.oliverflasch.de/llm-update/</link><guid isPermaLink="false">69ab27218971200c940db303</guid><category><![CDATA[NLP]]></category><category><![CDATA[LLM]]></category><category><![CDATA[General]]></category><dc:creator><![CDATA[Oliver Flasch]]></dc:creator><pubDate>Sat, 07 Mar 2026 12:17:58 GMT</pubDate><media:content url="https://blog.oliverflasch.de/content/images/2026/03/jr-korpa-9XngoIpxcEo-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.oliverflasch.de/content/images/2026/03/jr-korpa-9XngoIpxcEo-unsplash.jpg" alt="LLM Update"><p>I don’t use Large Language Models (LLMs) to write articles on my blog, because I believe it’s important to let my own voice be heard unaltered. To improve and stay authentic, there is no substitute to writing and thinking myself.</p><p>I use different commercial and open weight LLMs to test ideas and to critique drafts, though. I also use them in „deep research“ mode, as a semantic search engine to gather and structure source material. I feel that, in these use cases, they improve my productivity considerably.</p><p>I use LLMs extensively for coding, but not for „vibe coding“. As of 2026, I still review every line of code generated. This is mainly to prevent technical and cognitive debt stemming from code I do not fully understand. There are also security risks I‘m not willing to take. I find LLMs useful, although not perfect, to generate complex, algorithm-heavy code that would take me much longer to write on my own. I also use LLMs to quickly generate architectural overviews of large code bases, which I find useful, even though I need to carefully check for factual errors. The same applies to using LLMs to review code for defects and security problems, which at this stage can augment, but not replace, human work. As generating code is now basically free, I use coding-LLMs heavily for exploration, e.g. for one-off scripts and interactive visualizations, to learn a new concept, algorithm or to proof an idea.</p>]]></content:encoded></item><item><title><![CDATA[Risk Assessment in AI Projects]]></title><description><![CDATA[Thoughts on risk assessment in AI-, machine learning-, and data science-projects.]]></description><link>https://blog.oliverflasch.de/risk-management-in-ai-projects/</link><guid isPermaLink="false">5fcb7313fc342e1dcd252759</guid><category><![CDATA[Risk]]></category><category><![CDATA[Project Management]]></category><category><![CDATA[Projects]]></category><category><![CDATA[Management]]></category><category><![CDATA[AI]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[Data Science]]></category><dc:creator><![CDATA[Oliver Flasch]]></dc:creator><pubDate>Fri, 12 Aug 2022 18:31:11 GMT</pubDate><media:content url="https://blog.oliverflasch.de/content/images/2022/08/risk.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.oliverflasch.de/content/images/2022/08/risk.jpg" alt="Risk Assessment in AI Projects"><p>How do you assess risks in AI projects? While AI- and data science-projects share risks with classical software projects, there are specific risks you should be aware of. AI projects typically fall into one of the following four risk classes, ranging from comparatively low to very high risk:</p><h3 id="risk-class-1-low-risk-">Risk Class 1 (Low Risk)</h3><p><strong>Using a Pre-Trained AI Model in its Native Domain</strong><br>‌‌In this scenario, no model training and no training data is required. Nonetheless, a labeled data set of sufficient quality is needed in order to validate model quality in a principled manner. Check published accuracy metrics (substracting a safety margin) of the pre-trained model and compare these to your requirements. In contrast to implementing off-the-shelf software, risk is slightly elevated as production input data must match model training data not only structurally, but also statistically. Examples of data science projects of risk class 1 include machine translation, face recognition / face detection, or detection of person names, organization names and geographical locations in natural language (named-entity recognition).</p><h3 id="risk-class-2-medium-risk-">Risk Class 2 (Medium Risk)</h3><p><strong>Transferring a Pre-Trained AI Model to a Related Domain</strong><br>If no pre-trained model is available for your task, from a risk-perspective, the next best option often is to fine-tune an existing foundation model. AI-models, specifically artificial neural networks associated with deep learning, can be pre-trained on large cross-domain datasets, leading to foundation models that can then be fine-tuned to your specific domain using a smaller, domain-specific dataset.  This represents an elevated risk compared to using a pre-trained model directly, as you need to supply your own training data, which needs to be of sufficient quality for the task at hand. While this training dataset can be considerably smaller than what would be needed to train a model from scratch, fine-tuning deep learning models for language or vision tasks still need thousands to tens of thousands of training examples to reach high accuracy. Typical examples of projects in this risk class include solving vision-, e.g. image classification and detection, or language-tasks, e.g. machine translation of domain-specific texts.</p><h3 id="risk-class-3-high-risk-">Risk Class 3 (High Risk)</h3><p><strong>Training an Existing AI Model-Architecture on Proprietary Data</strong><br>In case no foundation model applicable to your task at hand exists, you'll need to ressort to training a model from scratch. To reduce risk, you should keep to established model architectures with a proven track record on related tasks. Judging  task / model architecture-fit without extensive experimentation is still an art-form predicated on considerable experience. Moreover, training a model from scratch requires a sufficient volume of training data, and significant effort for hyperparameter-tuning, model-specific data preprocessing (e.g. outlier detection, data imputation, data augmentation, and data transformation), validation, and quality assurance. Even more severely, it is often not known from the outset whether your training data quality is sufficient to solve your task with good enough accuracy at all. For all these reasons, AI projects of this risk class should be considered research projects and be managed as such. Calculate with a duration of 3 years. There will be considerable risks of delays or project failure, especially when encountering training data quality problems. Risks will increase further if your task at hand involves less common learning paradigms (e.g. PU-learning) and reduce if you are working with well-established statistical or machine learning methods (generalized linear models, support vector machines, graphical models, Bayesian networks, decision tree ensembles, etc.) on high-quality structured data. Risk mitigation measures should include data quality analyses and feasibility analyses (proof of value).</p><h3 id="risk-class-4-very-high-risk-">Risk Class 4 (Very High Risk)</h3><p><strong>Custom Development of a Application-Specific AI Model-Architecture</strong><br>If your task is cannot be solved by an established model architecture,  you will need to establish a research and development project of often considerable complexity, scale, and risk. If successful, the result can be intellectual property of very high value, especially if based on proprietary training data. Application-specific model architectures are somewhat common in deep learning, as models are build from composable submodules. Development of custom model architecture using the framework of inferential statistics or graphical models (including Bayesian networks) is common and useful if your team has the required specialized expertise. Though, from a risk perspective, developing custom AI model-architectures should by a measure of last ressort. You should calculate with a research project duration of 3 to 5 years and with considerable risk. Highly specialized knowledge in machine learning or statistics will be required. Examples for AI projects in this risk class include protein structure prediction or custom robotics.</p><h3 id="conclusions">Conclusions</h3><p>Commercial AI vendors will often downplay project risks, be it from ignorance or malice. This phenomenon seems to be especially prevalent with vendors of AI-based off-the-shelf software for overly broad yet concrete-sounding application areas like "fraud detection", "predictive maintenance" or "anomaly detection". Be wary of the fact that a presumed class 1 project may very well turn into a failed class 3 project if your use case does not exactly match the vendor's reference!</p>]]></content:encoded></item><item><title><![CDATA[Topics in the Bundestag (Part 2)]]></title><description><![CDATA[In the first part of this series, we employed deep learning to classify all speech paragraphs of the current German parliament into 13 predefined topics. In this part, we will finally answer our initial question: Who talked about what (and when) in the 19th Bundestag?]]></description><link>https://blog.oliverflasch.de/topics-in-the-bundestag-part-2/</link><guid isPermaLink="false">612fadd7ad7d860dca81068c</guid><category><![CDATA[NLP]]></category><category><![CDATA[Bundestag]]></category><category><![CDATA[Data Science]]></category><category><![CDATA[Deep Learning]]></category><category><![CDATA[German]]></category><category><![CDATA[Transformers]]></category><dc:creator><![CDATA[Oliver Flasch]]></dc:creator><pubDate>Sun, 05 Sep 2021 15:04:52 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1591079751210-718012d90ff7?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGJ1bmRlc3RhZ3xlbnwwfHx8fDE2MzA3ODQzMDg&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1591079751210-718012d90ff7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGJ1bmRlc3RhZ3xlbnwwfHx8fDE2MzA3ODQzMDg&ixlib=rb-1.2.1&q=80&w=2000" alt="Topics in the Bundestag (Part 2)"><p>If you followed the <a href="https://blog.oliverflasch.de/topics-in-the-bundestag-part-1/">last part of this series</a>, you ended up with a dataset of (nearly) all the paragraphs of the speeches held in the 19th Bundestag, augmented with speech ID, paragraph number, speaker and fraction information, length statistics, and probabilities of the paragraph belonging to each of the following 13 predefined topics. These topics where <a href="https://www.tagesschau.de/inland/btw21/programmvergleich-start-107.html">proposed by the German broadcasting network ARD</a>:</p><ol><li>Außenpolitik (Foreign Policy)</li><li>Bildung (Education)</li><li>Digitalisierung (Digitalization)</li><li>Familien (Family Policy) </li><li>Gesundheitswesen (Healthcare)</li><li>Jobs</li><li>Klima (Climate Change)</li><li>Pflege (Caregiving)</li><li>Rente (Pension Schemes)</li><li>Sicherheit (Security Policy)</li><li>Steuern (Taxes)</li><li>Wohnraum (Housing)</li><li>Zuwanderung (Immigraion)</li></ol><p>In this part, we will analyze this dataset from multiple perspectives, including fractions, speaker, speeches and time. This will give us, among other things, fraction topic profiles, topics trending over time, topic profiles of individual speeches, and perhaps most interestingly, a tool for instantly retrieving speeches about a given combination of topics.</p><p><strong><strong>DISCLAIMER: The</strong> model we used for topic classification<strong> hasn't been validated for this task and </strong>could have<strong> produce</strong>d<strong> biased or nonsensical results. If you're planning to use </strong>this<strong> approach in a production setting, make sure to </strong>validate result quality by comparing model predictions against human classifications, ideally in <strong>a double-blind study</strong>. In addition, the class labels taken from ARD are probably suboptimal, as the concepts vary widely in scope. You should also validate result stability against class label substitutions with synonyms.</strong></p><h2 id="fraction-party-topic-profiles">Fraction (Party) Topic Profiles</h2><p>What topics are the different fractions in the Bundestag talking about? One would expect to find interesting differences. Before we begin, let's take a short look at our dataset, to remind ourselves of what we're working with:</p><!--kg-card-begin: markdown--><details style="min-width: 100%;">
<summary>Click to show code.</summary>
<pre><code class="language-python"># install a current version of Plotly and kaleido
!pip install plotly==5.3.0 kaleido

# import required Python packages
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import plotly.io as pio
pio.templates.default = &quot;plotly_white&quot;
from plotly.subplots import make_subplots
import textwrap

# mount Google Drive (only needed if you're using Google Colab)
from google.colab import drive
drive.mount(&quot;/content/gdrive&quot;)

# load dataset into memory
classified_speech_paragraphs_df = pd.read_parquet(&quot;/content/gdrive/My Drive/Colab Notebooks/bundestag_plenary_protocols_with_multiclass_topics_term_19.parquet&quot;)

# add statistics on speech length
classified_speech_paragraphs_df[&quot;speech_len_paragraphs&quot;] = classified_speech_paragraphs_df.groupby(&quot;speech_id&quot;)[&quot;speech_id&quot;].transform(&quot;count&quot;)
classified_speech_paragraphs_df[&quot;speech_len_words&quot;] = classified_speech_paragraphs_df.groupby(&quot;speech_id&quot;)[&quot;paragraph_len_words&quot;].transform(&quot;sum&quot;)
classified_speech_paragraphs_df[&quot;speech_len_chars&quot;] = classified_speech_paragraphs_df.groupby(&quot;speech_id&quot;)[&quot;paragraph_len_chars&quot;].transform(&quot;sum&quot;)

classified_speech_paragraphs_df.sample(5)
</code></pre>
</details><!--kg-card-end: markdown--><!--kg-card-begin: html--><table border="0">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>id</th>
      <th>date</th>
      <th>speaker_id</th>
      <th>speaker_title</th>
      <th>speaker_first_name</th>
      <th>speaker_last_name</th>
      <th>speaker_fraction</th>
      <th>speaker_full_name</th>
      <th>speech_id</th>
      <th>paragraph_num</th>
      <th>paragraph</th>
      <th>paragraph_len_chars</th>
      <th>paragraph_len_words</th>
      <th>Außenpolitik</th>
      <th>Bildung</th>
      <th>Digitalisierung</th>
      <th>Familien</th>
      <th>Gesundheitswesen</th>
      <th>Jobs</th>
      <th>Klima</th>
      <th>Pflege</th>
      <th>Rente</th>
      <th>Sicherheit</th>
      <th>Steuern</th>
      <th>Wohnraum</th>
      <th>Zuwanderung</th>
      <th>speech_len_paragraphs</th>
      <th>speech_len_words</th>
      <th>speech_len_chars</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>640</th>
      <td>ID1911208000_4</td>
      <td>2019-09-12</td>
      <td>11004694</td>
      <td>Dr.</td>
      <td>Anna</td>
      <td>Christmann</td>
      <td>BÜNDNIS 90/DIE GRÜNEN</td>
      <td>Dr. Anna Christmann (BÜNDNIS 90/DIE GRÜNEN)</td>
      <td>ID1911208000</td>
      <td>4</td>
      <td>Ich möchte die steuerliche Forschungsförderung an dieser Stelle nur noch ganz kurz erwähnen. Hier haben Sie sich im Moment leider auch noch nicht gegenüber dem Finanzministerium durchsetzen können, sodass auch die Wissenschaftseinrichtungen zum Zuge kommen und davon partizipieren könnten. Wir hoffen dringend auf Verbesserung. Ansonsten kann ich nur sagen: Wir brauchen endlich eine kraftvolle Stimme für die nötigen Investitionen in Wissenschaft und Forschung. Dieser Haushalt ist zu wenig für die Zukunft.</td>
      <td>508</td>
      <td>71</td>
      <td>0.018184</td>
      <td>0.898505</td>
      <td>0.702108</td>
      <td>0.023014</td>
      <td>0.006594</td>
      <td>0.352261</td>
      <td>0.161505</td>
      <td>0.003869</td>
      <td>0.009288</td>
      <td>0.016303</td>
      <td>0.862467</td>
      <td>0.036318</td>
      <td>0.044191</td>
      <td>5</td>
      <td>503</td>
      <td>3578</td>
    </tr>
    <tr>
      <th>581</th>
      <td>ID1921508300_1</td>
      <td>2021-03-04</td>
      <td>11002738</td>
      <td>Dr. h. c. (Univ Kyiv)</td>
      <td>Hans</td>
      <td>Michelbach</td>
      <td>CDU/CSU</td>
      <td>Dr. h. c. (Univ Kyiv) Hans Michelbach (CDU/CSU)</td>
      <td>ID1921508300</td>
      <td>1</td>
      <td>Meine Damen und Herren, es sind vor allem strukturelle Konsequenzen nötig, um das Vertrauen in den Finanzplatz Deutschland wiederherzustellen. Wir haben als CDU/CSU dazu bereits im Juli 2020 in der Öffentlichkeit Vorschläge unterbreitet. Ich bin erfreut, manches von dem im vorliegenden Gesetzentwurf wiederzufinden.</td>
      <td>316</td>
      <td>43</td>
      <td>0.082199</td>
      <td>0.001117</td>
      <td>0.369175</td>
      <td>0.025756</td>
      <td>0.000728</td>
      <td>0.020211</td>
      <td>0.008152</td>
      <td>0.195698</td>
      <td>0.259876</td>
      <td>0.649110</td>
      <td>0.887993</td>
      <td>0.005772</td>
      <td>0.150754</td>
      <td>6</td>
      <td>320</td>
      <td>2489</td>
    </tr>
    <tr>
      <th>469</th>
      <td>ID1921906700_2</td>
      <td>2021-03-26</td>
      <td>11004693</td>
      <td></td>
      <td>Jörg</td>
      <td>Cezanne</td>
      <td>DIE LINKE</td>
      <td>Jörg Cezanne (DIE LINKE)</td>
      <td>ID1921906700</td>
      <td>2</td>
      <td>Hinzu kommt: Offene Immobilienfonds, die andere Seite sozusagen, betreiben keinen Neubau, sondern legen ihre Gelder nur im Bestand an. Das treibt eher die Immobilienpreise nach oben und verstärkt den Druck auf Mieten; denn die Anleger wollen für ihr eingesetztes Geld ja eine Rendite erhalten. Das führt jedenfalls nicht zu mehr Wohnungsbau.</td>
      <td>341</td>
      <td>51</td>
      <td>0.322164</td>
      <td>0.022201</td>
      <td>0.512379</td>
      <td>0.235670</td>
      <td>0.003415</td>
      <td>0.136892</td>
      <td>0.418310</td>
      <td>0.200264</td>
      <td>0.471159</td>
      <td>0.571331</td>
      <td>0.403089</td>
      <td>0.706618</td>
      <td>0.098817</td>
      <td>5</td>
      <td>225</td>
      <td>1725</td>
    </tr>
    <tr>
      <th>238</th>
      <td>ID195605000_1</td>
      <td>2018-10-12</td>
      <td>11004363</td>
      <td></td>
      <td>Ulli</td>
      <td>Nissen</td>
      <td>SPD</td>
      <td>Ulli Nissen (SPD)</td>
      <td>ID195605000</td>
      <td>1</td>
      <td>Die neuen Eigentümer versuchen, die Häuser zu entmieten. In der Schwarzburgstraße 54 sind jetzt schon sieben von neun Wohnparteien vertrieben worden. Die Modernisierungsankündigungen mit angedrohten Mieterhöhungen von über 220 Prozent – 11 Euro Erhöhung pro Quadratmeter – haben das geschafft. Zum Glück sind die verbliebenen Mieterinnen und Mieter widerspenstig. Sie haben meine große Unterstützung, unter anderem Frau Schult. Sie ist 72 Jahre alt, schwerbehindert – eine beeindruckende Frau, die seit Jahrzehnten in dem Haus wohnt und dort selbstverständlich nicht weg will. In der Neuhofstraße versucht der neue Eigentümer die „Vertreibung“ auf ähnliche Art und Weise. Von der Wingertstraße 21 und deren unerträglichem Miethai habe ich schon öfter erzählt.</td>
      <td>759</td>
      <td>108</td>
      <td>0.300692</td>
      <td>0.168096</td>
      <td>0.186061</td>
      <td>0.273177</td>
      <td>0.124016</td>
      <td>0.197686</td>
      <td>0.307020</td>
      <td>0.181765</td>
      <td>0.241960</td>
      <td>0.205673</td>
      <td>0.187569</td>
      <td>0.324135</td>
      <td>0.149672</td>
      <td>8</td>
      <td>355</td>
      <td>2480</td>
    </tr>
    <tr>
      <th>665</th>
      <td>ID19208900_6</td>
      <td>2017-11-21</td>
      <td>11004311</td>
      <td></td>
      <td>Erich</td>
      <td>Irlstorfer</td>
      <td>CDU/CSU</td>
      <td>Erich Irlstorfer (CDU/CSU)</td>
      <td>ID19208900</td>
      <td>6</td>
      <td>Meine sehr geehrten Damen und Herren, die Kollegin der FDP hat diese Punkte sehr richtig dargestellt. Vielleicht ein kleiner Seitenhieb – das erlauben Sie mir –: Diese Dinge kann man in Regierungsverantwortung natürlich am besten mit auf den Weg bringen. Das ist klar. Ich glaube, hier sind wir dann auch wieder beim Thema Verantwortung.</td>
      <td>337</td>
      <td>54</td>
      <td>0.426530</td>
      <td>0.270290</td>
      <td>0.328454</td>
      <td>0.259887</td>
      <td>0.121885</td>
      <td>0.124542</td>
      <td>0.393453</td>
      <td>0.225009</td>
      <td>0.152426</td>
      <td>0.257828</td>
      <td>0.272557</td>
      <td>0.290779</td>
      <td>0.376695</td>
      <td>9</td>
      <td>490</td>
      <td>3145</td>
    </tr>
  </tbody>
</table><!--kg-card-end: html--><p>Let's also calculate some basic statistics for our dataset:</p><!--kg-card-begin: markdown--><details style="min-width: 100%;">
<summary>Click to show code.</summary>
<pre><code class="language-python">print(f&quot;Number of paragraphs:\t\t{len(classified_speech_paragraphs_df)}&quot;)
print(f&quot;Median words per paragraph:\t{classified_speech_paragraphs_df.paragraph_len_words.median()}&quot;)
print(f&quot;Mean words per paragraph:\t{classified_speech_paragraphs_df.paragraph_len_words.mean()}&quot;)
print(f&quot;Number of speeches:\t\t{len(classified_speech_paragraphs_df.speech_id.unique())}&quot;)
print(f&quot;Number of sessions (dates):\t{len(classified_speech_paragraphs_df.date.unique())}&quot;)
</code></pre>
</details><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><pre><code>Number of paragraphs:		115612
Median words per paragraph:	54.0
Mean words per paragraph:	58.27112237483998
Number of speeches:  		16238
Number of sessions (dates):	237
</code></pre>
<!--kg-card-end: markdown--><p>For each speech paragraph, we have the fraction available and, for each of our 13 topics, a probability that the paragraph is relevant to that topic.  To create a "fraction topic profile", we group our dataset by fraction, calculate the mean probabilities for each topic for each group, and visualize the results as a "radar plot". The following radar plot overlays the fraction topic profiles of the largest (by seats in parliament) three fractions:</p><!--kg-card-begin: markdown--><details style="min-width: 100%;">
<summary>Click to show code.</summary>
<pre><code class="language-python"># group by fraction and calculate mean topic probabilites for each group
fractions_topics_df = classified_speech_paragraphs_df.groupby(&quot;speaker_fraction&quot;).mean().iloc[:, 3:16]

# function for creating overlayed fraction topic profile plots
def fractions_topics_plot_overlayed(fig_df, fractions_colors={&quot;CDU/CSU&quot;: &quot;black&quot;, &quot;SPD&quot;: &quot;red&quot;, &quot;FDP&quot;: &quot;gold&quot;}, title=&quot;untitled&quot;):
  fig_long_df = fig_df.reset_index().melt(id_vars=[&quot;speaker_fraction&quot;]) # transform tidy (wide) df to long form df
  fig = go.Figure()
  for (fraction, color) in fractions_colors.items():
    fig.add_trace(go.Scatterpolar(theta=fig_long_df.query(f&quot;speaker_fraction=='{fraction}'&quot;)[&quot;variable&quot;],
                                  r=fig_long_df.query(f&quot;speaker_fraction=='{fraction}'&quot;)[&quot;value&quot;],
                                  mode=&quot;lines+markers&quot;,
                                  line=dict(shape=&quot;spline&quot;),
                                  fill=&quot;toself&quot;,
                                  name=f&quot;{fraction}&quot;,
                                  line_color=f&quot;{color}&quot;))
  fig.update_layout(title=title, showlegend=True, width=800, height=600)
  return fig

# create overlayed fraction topic profiles for the three largest fractions
fractions_topics_plot_overlayed(fractions_topics_df,
                                fractions_colors={&quot;CDU/CSU&quot;: &quot;black&quot;, &quot;SPD&quot;: &quot;red&quot;, &quot;FDP&quot;: &quot;gold&quot;},
                                title=&quot;Topics in the 19th Bundestag, by Fraction&quot;)
</code></pre>
</details><!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://blog.oliverflasch.de/content/images/2021/09/fractions_topics_overlayed.svg" class="kg-image" alt="Topics in the Bundestag (Part 2)"></figure><p>As you can see, all of the three largest fractions have quite similar topic profiles, at least in absolute terms. We can focus on the differences by normalizing the topic probability values:</p><!--kg-card-begin: markdown--><details style="min-width: 100%;">
<summary>Click to show code.</summary>
<pre><code class="language-python">fractions_topics_nrm_df = (fractions_topics_df - fractions_topics_df.mean()) / fractions_topics_df.std()

# create overlayed normalized fraction topic profiles for the three largest fractions
fractions_topics_plot_overlayed(fractions_topics_nrm_df,
                                fractions_colors={&quot;CDU/CSU&quot;: &quot;black&quot;, &quot;SPD&quot;: &quot;red&quot;, &quot;FDP&quot;: &quot;gold&quot;},
                                title=&quot;Topics in the 19th Bundestag, Normalized, by Fraction&quot;)
</code></pre>
</details><!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://blog.oliverflasch.de/content/images/2021/09/fractions_topics_normalized_overlayed.svg" class="kg-image" alt="Topics in the Bundestag (Part 2)"></figure><p>This plot shows how much a fraction likes to talk about a given topic, relative to all other fractions in parliament (note that only the three largest fractions are shown here). Finally, we create normalized fraction topic profiles for all fractions, sorted by size (seats in parliament):</p><!--kg-card-begin: markdown--><details style="min-width: 100%;">
<summary>Click to show code.</summary>
<pre><code class="language-python"># function to create a trellis plot of all fraction topic profiles
def fractions_topics_plot_trellis(fig_df, title=&quot;untitled&quot;):
  fig_long_df = fig_df.reset_index().melt(id_vars=[&quot;speaker_fraction&quot;]) # transform tidy (wide) df to long form df
  
  def fraction_profile_plot(fraction, color):
    return go.Scatterpolar(theta=fig_long_df.query(f&quot;speaker_fraction=='{fraction}'&quot;)[&quot;variable&quot;],
                           r=fig_long_df.query(f&quot;speaker_fraction=='{fraction}'&quot;)[&quot;value&quot;],
                           mode=&quot;lines+markers&quot;,
                           line=dict(shape=&quot;spline&quot;),
                           fill=&quot;toself&quot;,
                           name=f&quot;{fraction}&quot;,
                           line_color=color)

  fig = make_subplots(rows=3, cols=2,
                      #subplot_titles=(&quot;CDU/CSU&quot;, &quot;SPD&quot;, &quot;AfD&quot;, &quot;FDP&quot;, &quot;DIE LINKE&quot;, &quot;BÜNDNIS 90/DIE GRÜNEN&quot;),
                      specs=[[{&quot;type&quot;: &quot;polar&quot;}, {&quot;type&quot;: &quot;polar&quot;}],
                             [{&quot;type&quot;: &quot;polar&quot;}, {&quot;type&quot;: &quot;polar&quot;}],
                             [{&quot;type&quot;: &quot;polar&quot;}, {&quot;type&quot;: &quot;polar&quot;}]])
  # CDU/CSU
  fig.add_trace(fraction_profile_plot(fraction=&quot;CDU/CSU&quot;, color=&quot;black&quot;), row=1, col=1)
  # SPD
  fig.add_trace(fraction_profile_plot(fraction=&quot;SPD&quot;, color=&quot;red&quot;), row=1, col=2)
  # AfD
  fig.add_trace(fraction_profile_plot(fraction=&quot;AfD&quot;, color=&quot;blue&quot;), row=2, col=1)
  # FDP
  fig.add_trace(fraction_profile_plot(fraction=&quot;FDP&quot;, color=&quot;gold&quot;), row=2, col=2)
  # DIE LINKE
  fig.add_trace(fraction_profile_plot(fraction=&quot;DIE LINKE&quot;, color=&quot;purple&quot;), row=3, col=1)
  # BÜNDNIS 90/DIE GRÜNEN
  fig.add_trace(fraction_profile_plot(fraction=&quot;BÜNDNIS 90/DIE GRÜNEN&quot;, color=&quot;green&quot;), row=3, col=2)
  fig.update_polars(radialaxis=dict(range=[fig_long_df.min()[2], fig_long_df.max()[2]]))
  fig.update_layout(title=title,
                    showlegend=True,
                    autosize=False,
                    width=1200,
                    height=1600)
  return fig

fractions_topics_plot_trellis(fractions_topics_nrm_df, title=&quot;Topics in the 19th Bundestag, Normalized, by Fraction&quot;)
</code></pre>
</details><!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-width-full"><img src="https://blog.oliverflasch.de/content/images/2021/09/fractions_topics_normalized.svg" class="kg-image" alt="Topics in the Bundestag (Part 2)"></figure><h2 id="topics-trending-over-time">Topics Trending Over Time</h2><p>As we have the session dates available in our dataset, we can easily create a time series plot of topics trending over time. We group our dataset by date, calculate the mean topic probabilities for each date and create a line plot. Note that the plot below is interactive, click on the legend items to hide or show individual topics:</p><!--kg-card-begin: markdown--><details style="min-width: 100%;">
<summary>Click to show code.</summary>
<pre><code class="language-python"># keep date and probability columns
date_scores_df = classified_speech_paragraphs_df.iloc[:, [1] + list(range(13, 26))]
# group by date and calculate mean probabilites
date_mean_scores_df = date_scores_df.groupby(&quot;date&quot;).mean().reset_index()

# plot date against mean probabilities
px.line(date_mean_scores_df.melt(id_vars=[&quot;date&quot;]).reset_index(),
        x=&quot;date&quot;, y=&quot;value&quot;,
        color=&quot;variable&quot;, color_discrete_sequence=px.colors.qualitative.Alphabet_r,
        markers=True,
        title=&quot;Topics in the 19th Bundestag, by Session Date&quot;)
</code></pre>
</details><!--kg-card-end: markdown--><!--kg-card-begin: html--><figure class="kg-card kg-image-card kg-width-wide">
<iframe scrolling="no" style="border: none;" seamless="seamless" src="https://blog.oliverflasch.de/uploads/topic_trends_timeline.html" height="600" width="1200"></iframe>
</figure><!--kg-card-end: html--><p>These trend lines are quite spikey. It could be useful to apply a 30-day moving average to bring out slower trends:</p><!--kg-card-begin: markdown--><details style="min-width: 100%;">
<summary>Click to show code.</summary>
<pre><code class="language-python"># apply a 30-day moving average
date_mean_scores_ma30_df = date_mean_scores_df.set_index(&quot;date&quot;).rolling(30).mean().reset_index()

# plot, as above
px.line(date_mean_scores_ma30_df.melt(id_vars=[&quot;date&quot;]).reset_index(),
              x=&quot;date&quot;, y=&quot;value&quot;,
              color=&quot;variable&quot;, color_discrete_sequence=px.colors.qualitative.Alphabet_r,
              markers=True,
              title=&quot;Topics in the 19th Bundestag, by Session Date&quot;)
</code></pre>
</details><!--kg-card-end: markdown--><!--kg-card-begin: html--><figure class="kg-card kg-image-card kg-width-wide">
<iframe scrolling="no" style="border: none;" seamless="seamless" src="https://blog.oliverflasch.de/uploads/topic_trends_timeline_ma30.html" height="600" width="1200"></iframe>
</figure><!--kg-card-end: html--><h2 id="top-speakers-fractions-by-topic">Top Speakers / Fractions by Topic</h2><p>Who are the top ten speakers and fractions for a given topic? By grouping by speaker or fraction, then sorting by mean probability of the topic of interest, we obtain these rankings. For example, these are the top ten speakers on foreign policy ("Außenpolitik"):</p><!--kg-card-begin: markdown--><details style="min-width: 100%;">
<summary>Click to show code.</summary>
<pre><code class="language-python"># it would be cleaner to group by speaker_id, but this is simpler (as long as no speakers share the same full name and fraction)
speaker_scores_df = classified_speech_paragraphs_df.iloc[:, [7] + list(range(13, 26))]
speaker_mean_scores_df = speaker_scores_df.groupby(&quot;speaker_full_name&quot;).mean().reset_index()

def top10(df, id_column, topic):
  return df.nlargest(10, topic).loc[:, [id_column, topic]].reset_index()

top10(speaker_mean_scores_df, &quot;speaker_full_name&quot;, &quot;Außenpolitik&quot;)
</code></pre>
</details><!--kg-card-end: markdown--><!--kg-card-begin: html--><table border="0">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>index</th>
      <th>speaker_full_name</th>
      <th>Außenpolitik</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>663</td>
      <td>Sevim Dağdelen (DIE LINKE)</td>
      <td>0.829758</td>
    </tr>
    <tr>
      <th>1</th>
      <td>73</td>
      <td>Bijan Djir-Sarai (FDP)</td>
      <td>0.778069</td>
    </tr>
    <tr>
      <th>2</th>
      <td>194</td>
      <td>Dr. Eberhardt Alexander Gauland (AfD)</td>
      <td>0.756092</td>
    </tr>
    <tr>
      <th>3</th>
      <td>268</td>
      <td>Dr. Norbert Röttgen (CDU/CSU)</td>
      <td>0.742528</td>
    </tr>
    <tr>
      <th>4</th>
      <td>230</td>
      <td>Dr. Johann David Wadephul (CDU/CSU)</td>
      <td>0.739513</td>
    </tr>
    <tr>
      <th>5</th>
      <td>335</td>
      <td>Frank Müller-Rosentritt (FDP)</td>
      <td>0.732998</td>
    </tr>
    <tr>
      <th>6</th>
      <td>377</td>
      <td>Heike Hänsel (DIE LINKE)</td>
      <td>0.731669</td>
    </tr>
    <tr>
      <th>7</th>
      <td>199</td>
      <td>Dr. Frank Steffel (CDU/CSU)</td>
      <td>0.727506</td>
    </tr>
    <tr>
      <th>8</th>
      <td>44</td>
      <td>Armin-Paulus Hampel (AfD)</td>
      <td>0.725815</td>
    </tr>
    <tr>
      <th>9</th>
      <td>266</td>
      <td>Dr. Nils Schmid (SPD)</td>
      <td>0.725764</td>
    </tr>
  </tbody>
</table><!--kg-card-end: html--><p>What about climate change ("Klima")?:</p><!--kg-card-begin: markdown--><details style="min-width: 100%;">
<summary>Click to show code.</summary>
<pre><code class="language-python">top10(speaker_mean_scores_df, &quot;speaker_full_name&quot;, &quot;Klima&quot;)
</code></pre>
</details><!--kg-card-end: markdown--><!--kg-card-begin: html--><table border="0">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>index</th>
      <th>speaker_full_name</th>
      <th>Klima</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>352</td>
      <td>Gerhard Zickenheiner (BÜNDNIS 90/DIE GRÜNEN)</td>
      <td>0.891068</td>
    </tr>
    <tr>
      <th>1</th>
      <td>233</td>
      <td>Dr. Julia Verlinden (BÜNDNIS 90/DIE GRÜNEN)</td>
      <td>0.774046</td>
    </tr>
    <tr>
      <th>2</th>
      <td>485</td>
      <td>Lisa Badum (BÜNDNIS 90/DIE GRÜNEN)</td>
      <td>0.761447</td>
    </tr>
    <tr>
      <th>3</th>
      <td>484</td>
      <td>Lisa Badum (BÜNDNIS 90/DIE GRÜNEN)</td>
      <td>0.679953</td>
    </tr>
    <tr>
      <th>4</th>
      <td>488</td>
      <td>Lorenz Gösta Beutin (DIE LINKE)</td>
      <td>0.672810</td>
    </tr>
    <tr>
      <th>5</th>
      <td>216</td>
      <td>Dr. Heiko Wildberg (AfD)</td>
      <td>0.670632</td>
    </tr>
    <tr>
      <th>6</th>
      <td>248</td>
      <td>Dr. Lukas Köhler (FDP)</td>
      <td>0.655935</td>
    </tr>
    <tr>
      <th>7</th>
      <td>159</td>
      <td>Dr. Anja Weisgerber (CDU/CSU)</td>
      <td>0.648563</td>
    </tr>
    <tr>
      <th>8</th>
      <td>389</td>
      <td>Ingrid Nestle (BÜNDNIS 90/DIE GRÜNEN)</td>
      <td>0.632522</td>
    </tr>
    <tr>
      <th>9</th>
      <td>471</td>
      <td>Klaus Mindrup (SPD)</td>
      <td>0.626575</td>
    </tr>
  </tbody>
</table><!--kg-card-end: html--><p>Did you notice the duplicated row? This hints at some potential problems with the quailty of our speaker metadata. </p><p>By replacing "speaker" with "faction" in our analysis, we can answer the question of which fractions talk most about a given topic, e.g. foreign policy ("Außenpolitik")...:</p><!--kg-card-begin: markdown--><details style="min-width: 100%;">
<summary>Click to show code.</summary>
<pre><code class="language-python">fraction_scores_df = classified_speech_paragraphs_df.iloc[:, [6] + list(range(13, 26))]
fraction_mean_scores_df = fraction_scores_df.groupby(&quot;speaker_fraction&quot;).mean().reset_index()

top10(fraction_mean_scores_df, &quot;speaker_fraction&quot;, &quot;Außenpolitik&quot;)
</code></pre>
</details><!--kg-card-end: markdown--><!--kg-card-begin: html--><table border="0">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>index</th>
      <th>speaker_fraction</th>
      <th>Außenpolitik</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0</td>
      <td>AfD</td>
      <td>0.332590</td>
    </tr>
    <tr>
      <th>1</th>
      <td>4</td>
      <td>FDP</td>
      <td>0.320912</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2</td>
      <td>CDU/CSU</td>
      <td>0.309554</td>
    </tr>
    <tr>
      <th>3</th>
      <td>1</td>
      <td>BÜNDNIS 90/DIE GRÜNEN</td>
      <td>0.304774</td>
    </tr>
    <tr>
      <th>4</th>
      <td>5</td>
      <td>SPD</td>
      <td>0.286061</td>
    </tr>
    <tr>
      <th>5</th>
      <td>3</td>
      <td>DIE LINKE</td>
      <td>0.283845</td>
    </tr>
  </tbody>
</table><!--kg-card-end: html--><p>...or climate change ("Klima")...:</p><!--kg-card-begin: markdown--><details style="min-width: 100%;">
<summary>Click to show code.</summary>
<pre><code class="language-python">top10(fraction_mean_scores_df, &quot;speaker_fraction&quot;, &quot;Klima&quot;)
</code></pre>
</details><!--kg-card-end: markdown--><!--kg-card-begin: html--><table border="0">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>index</th>
      <th>speaker_fraction</th>
      <th>Klima</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>1</td>
      <td>BÜNDNIS 90/DIE GRÜNEN</td>
      <td>0.210027</td>
    </tr>
    <tr>
      <th>1</th>
      <td>5</td>
      <td>SPD</td>
      <td>0.198824</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2</td>
      <td>CDU/CSU</td>
      <td>0.193702</td>
    </tr>
    <tr>
      <th>3</th>
      <td>4</td>
      <td>FDP</td>
      <td>0.189563</td>
    </tr>
    <tr>
      <th>4</th>
      <td>0</td>
      <td>AfD</td>
      <td>0.185021</td>
    </tr>
    <tr>
      <th>5</th>
      <td>3</td>
      <td>DIE LINKE</td>
      <td>0.173548</td>
    </tr>
  </tbody>
</table><!--kg-card-end: html--><h2 id="top-speeches-by-topic-combination">Top Speeches by Topic Combination</h2><p>Let's focus on individual speeches next. What are the top speeches pertaining to a given topic? We could apply our deep language model to classify speeches according to a custom topic query, giving us very accurate results for the price of waiting multiple hours. If we can formulate our query as a combination of the 13 topics in our dataset instead, we can quickly retrieve an approximate result, simply by multiplying the probabilites of all topics in our topic combination of interest.</p><p>For example, let's retrieve the top speeches about the popular topic combination of climate change ("Klima") and jobs:</p><!--kg-card-begin: markdown--><details style="min-width: 100%;">
<summary>Click to show code.</summary>
<pre><code class="language-python">def speeches_for_topic_combination(classified_speech_paragraphs_df,
                                   min_speech_len_words=140,
                                   topic_combination=[&quot;Klima&quot;, &quot;Jobs&quot;]):
  # create dataframe of speeches with scores, augmented with speaker, speech_len_words, and sample_paragraph...
  speech_speaker_scores_df = (classified_speech_paragraphs_df.iloc[:, [8] + list(range(13, 26))]
                              .groupby([&quot;speech_id&quot;])
                              .mean()
                              .join(classified_speech_paragraphs_df # augment with full speech texts...
                                    .loc[:, [&quot;speech_id&quot;, &quot;paragraph&quot;, &quot;paragraph_num&quot;]]
                                    .sort_values([&quot;speech_id&quot;, &quot;paragraph_num&quot;])
                                    .groupby(&quot;speech_id&quot;)
                                    .agg({&quot;paragraph&quot;: &quot;\n\n&quot;.join}), how=&quot;left&quot;) # TODO how to separate paragraphs in string?
                              .join(classified_speech_paragraphs_df # augment with speaker name and speech length...
                                    .loc[:, [&quot;speech_id&quot;, &quot;speaker_full_name&quot;, &quot;speech_len_words&quot;]]
                                    .drop_duplicates(subset=[&quot;speech_id&quot;])
                                    .set_index(&quot;speech_id&quot;), how=&quot;left&quot;)
                              .reset_index()
                              .rename(columns={&quot;paragraph&quot;: &quot;speech&quot;})
                              .query(f&quot;speech_len_words&gt;={min_speech_len_words}&quot;))
  # augment with topic combination score (product of individual topic scores)...
  topic_combination_str = &quot;_&quot;.join(topic_combination)
  topic_combination_product_expr = &quot; * &quot;.join([f&quot;speech_speaker_scores_df.{t}&quot; for t in topic_combination])
  speech_speaker_scores_df[topic_combination_str] = pd.eval(topic_combination_product_expr)
  return speech_speaker_scores_df

def topN_speeches_for_topic_combination(classified_speech_paragraphs_df,
                                        N=10,
                                        min_speech_len_words=140,
                                        topic_combination=[&quot;Klima&quot;, &quot;Jobs&quot;]):
  sftc = speeches_for_topic_combination(classified_speech_paragraphs_df,
                                        min_speech_len_words=min_speech_len_words,
                                        topic_combination=topic_combination)
  topic_combination_str = &quot;_&quot;.join(topic_combination)
  return (sftc
          .nlargest(N, topic_combination_str)
          .loc[:, [&quot;speech_id&quot;, &quot;speaker_full_name&quot;, &quot;speech_len_words&quot;, &quot;speech&quot;, topic_combination_str]])

pd.set_option(&quot;display.max_colwidth&quot;, None)
topN_speeches_for_topic_combination(classified_speech_paragraphs_df, topic_combination=[&quot;Klima&quot;, &quot;Jobs&quot;])
</code></pre>
</details><!--kg-card-end: markdown--><!--kg-card-begin: html--><table border="0">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>speech_id</th>
      <th>speaker_full_name</th>
      <th>speech_len_words</th>
      <th>speech</th>
      <th>Klima_Jobs</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>13037</th>
      <td>ID195306700</td>
      <td>Dr. Christoph Ploß (CDU/CSU)</td>
      <td>172</td>
      <td>Wie gesagt, nehmen Sie das Beispiel Elbvertiefung. Das war jetzt gar nicht auf den Berliner Flughafen bezogen. Da haben wir in der Tat ganz andere Probleme. Aber bei der Elbvertiefung haben die Klagen über Jahre und mittlerweile Jahrzehnte dieses wichtige Projekt für unser Land verzögert. Wir hoffen, dass wir es jetzt im Jahr 2020 fertigstellen können. Aber auch das ist noch nicht hundertprozentig sicher, wenn die nächste Klage der Umweltverbände Erfolg hat. Mein Plädoyer war, dass wir sagen: Wir müssen das Ganze wieder in ein rechtes Maß rücken. Natürlich haben die Umweltverbände eine Bedeutung in unserem Land, und wir wollen umweltrelevante Aspekte in die Planung einbeziehen. Aber vieles, was gut gemeint war – und was Sie hier sicherlich auch gut meinen –, hat teilweise dazu geführt, dass wir wichtige Infrastrukturprojekte in unserem Land nicht mehr angemessen planen und fertigstellen können. Darum ging es mir. Wenn wir das hier gemeinsam hinbekommen wollen, lade ich Sie gerne ein, in den nächsten Wochen bei den Beratungen mitzuwirken. Das wäre für unser Land eine großartige Sache.</td>
      <td>0.563233</td>
    </tr>
    <tr>
      <th>8181</th>
      <td>ID1920601200</td>
      <td>Timon Gremmels (SPD)</td>
      <td>435</td>
      <td>Beim Thema E-Mobilität gebe ich Ihnen recht, was die Ansiedlung von Instituten zur Batteriezellenforschung und Produktion hier angeht. Das ist gut; das ist Wirtschaftspolitik. Aber die entscheidende Frage bei der E-Mobilität ist doch: Woher kommt der Strom, den die E-Fahrzeuge tanken? Der muss aus erneuerbaren Energien kommen. Herr Altmaier, dazu haben Sie gar nichts gesagt.\n\nMan könnte jetzt sagen: Wir haben eine andere Krise, die Priorität hat, die Coronakrise, die Pandemiekrise. – Das ist richtig. Aber deswegen verschwinden andere Krisen ja nicht. Die Klimakrise ist trotzdem noch da. Wir haben jetzt die einmalige Chance, beide Krisen zeitgleich zu bearbeiten. Sehr geehrter Herr Altmaier, liebe Kolleginnen und Kollegen, das muss doch unsere Aufgabe sein. Wenn wir jetzt investieren, müssen wir nachhaltig investieren. Das sichert Arbeitsplätze. Das schafft gute Jobs.\n\nHerr Altmaier, Sie haben gerade gesagt: Wir können uns Umweltschutz nur leisten, wenn es der Wirtschaft gut geht. – Das ist zu kurz gedacht. Nur mit nachhaltiger Umweltpolitik, nur mit nachhaltiger Industriepolitik können wir zukunftsfähige Arbeitsplätze schaffen, die uns aus der Krise holen. Dazu brauchen wir jetzt im Nachgang zum EEG eine schnelle Umsetzung des Entschließungsantrags der Großen Koalition. Wir müssen jetzt die Ausbaupfade für erneuerbare Energien klar durchdeklinieren.\n\nWir können zum Beispiel in der Photovoltaik, die eine große Akzeptanz in der Bevölkerung hat, einen Zubau von 10 bis 12 Gigawatt pro Jahr schaffen. Das schafft Arbeitsplätze, auch in Deutschland. Ein Blick in meinen Wahlkreis in Nordhessen zeigt Folgendes: Eine Studie der Uni Kassel hat die Investitionen in erneuerbare Energien von 2000 bis 2018 in Nordhessen untersucht. Es waren 4,4 Milliarden Euro in 18 Jahren. 43 Prozent der Kosten sind in der Region geblieben. Das heißt, es ist vor Ort eine Wertschöpfung von knapp 2 Milliarden Euro geschaffen worden. Das ist nachhaltige Industriepolitik, wie wir Sozialdemokraten sie uns vorstellen, Herr Altmaier. Da muss die Koalition noch ein bisschen mehr machen.\n\nEine Möglichkeit wäre zum Beispiel, zu sagen: Wir investieren auch wieder verstärkt in die Modulproduktion von Solarzellen. Es gibt Ansätze in Sachsen und Sachsen-Anhalt, eine industrielle Produktion aufzubauen. Diese könnte 3 000 Arbeitsplätze schaffen. Wenn wir einen CO\n\n-Fußabdruck für Solarzellen – vergleichbar mit dem der Franzosen – einführen, können wir die Wertschöpfung in Deutschland halten. Das übrigens soll ja ab Mitte der 2020er-Jahre für die Batteriezellen kommen. Was für Batteriezellen gilt, muss auch für Module gelten. Das ist nachhaltige Industriepolitik.\n\nUnser abschließender Appell lautet: Lassen Sie uns nachhaltige Industriepolitik und die Bekämpfung der Coronakrise gemeinschaftlich voranbringen. Beides können wir zeitgleich leisten. Das wäre wichtig für die deutsche Wirtschaft, für die Umwelt und für die nachhaltige Industrie. So bewältigen wir die Krisen, so lösen wir die Probleme.</td>
      <td>0.516295</td>
    </tr>
    <tr>
      <th>5813</th>
      <td>ID1917603800</td>
      <td>Beate Müller-Gemmeke (BÜNDNIS 90/DIE GRÜNEN)</td>
      <td>274</td>
      <td>Deshalb muss unsere Wirtschaft wirklich nachhaltig und klimaneutral werden. Das führt zwangsläufig zu einem Wandel der Arbeit. Am Beginn dieser Transformation stehen im Moment die Automobilindustrie und ihre Zulieferfirmen. Betroffen davon sind rund 800 000 Beschäftigte. Dabei geht es in der Regel um gute Arbeit mit tariflichem Lohn und betrieblicher Mitbestimmung. Dem müssen wir gerecht werden. Die Transformation muss also immer ökologisch und sozial ausgestaltet sein. Deshalb verbinden wir Grünen Klimapolitik immer mit einer Politik für mehr soziale Gerechtigkeit.\n\nDeshalb fordern wir heute mit unserem Antrag ein neues Qualifizierungskurzarbeitergeld. Die Automobilbranche braucht Unterstützung in der Übergangsphase zu nachhaltigen Produkten, also beim Übergang vom Verbrenner zur Elektromobilität. Die Beschäftigten brauchen hingegen gute Qualifizierungsangebote für die Arbeitswelt von morgen. Deshalb wollen wir Kurzarbeit ganz konsequent mit Qualifizierung der Beschäftigten verbinden und eng an die Sozialpartnerschaft binden, und zwar mit Tarifverträgen und Betriebsvereinbarungen. Wir verbinden also Arbeitsmarkt und Industriepolitik und schaffen damit für die Beschäftigten im Strukturwandel neue Perspektiven. Das zeigt: Ökologie und Soziales sind keine Gegensätze. Im Gegenteil: Nur so entstehen gute und vor allem auch zukunftsfähige Arbeitsplätze.\n\nDie sozialökologische Transformation braucht also Investitionen. Sie braucht verlässliche, gute politische Rahmenbedingungen. Notwendig ist aber auch Vertrauen, und Vertrauen entsteht bei den Beschäftigten durch Mitbestimmung. Deshalb fordern wir ein Initiativ- und Mitbestimmungsrecht bei der Personalentwicklung und insbesondere zur Verbesserung der Klimabilanz. Wir wollen die Beschäftigten zu Akteuren machen. Sie sollen den Klimaschutz aktiv mitgestalten können. So entstehen dann Innovation und vor allem auch Akzeptanz.\n\nWir Grünen sind überzeugt: Den Unternehmen wird nur gemeinsam mit den Beschäftigten, mit den Betriebsräten, mit den Gewerkschaften die Transformation ökologisch und auch sozial gelingen. Genau das wollen wir mit unserem Antrag politisch unterstützen.</td>
      <td>0.453463</td>
    </tr>
    <tr>
      <th>16090</th>
      <td>ID199805200</td>
      <td>Timon Gremmels (SPD)</td>
      <td>285</td>
      <td>Es ist gut, dass wir aus beiden Technologien aussteigen; denn es sind rückwärtsgewandte Technologien. Wir müssen Richtung Zukunft schauen. Aber wir müssen das gemeinschaftlich mit den Beschäftigten und mit den Regionen machen. Der Vorteil des Kohlekonsenses der Strukturwandelkommission, deren Einsetzung übrigens die SPD in den Koalitionsverhandlungen durchgesetzt hat, ist,\n\nDieser Konsens besteht aus zwei Teilen. Er besteht zum einen aus einem geplanten Strukturstärkungsgesetz und zum anderen aus einem geplanten Kohleausstiegsgesetz. Er besteht aus beiden Teilen. Die Grünen, Frau Baerbock, haben sich auf eine Frage konzentriert. Das kann man als Grüne machen, aber wir als Sozialdemokratie haben eine andere Aufgabe. Ich glaube, dass wir die schwierigere Aufgabe haben, weil wir das mit den Beschäftigten, mit den Arbeitnehmerinnen und Arbeitnehmern, gemeinsam machen wollen; denn nur so gibt es Akzeptanz.\n\nWir müssen alle Aspekte dieses Konsenses berücksichtigen. Übrigens steht dort nicht, dass der Kohleausstieg auf jeden Fall erst 2038 erfolgt, sondern dort steht, dass der Ausstieg auf 2035 vorgezogen werden kann. Dort steht auch, dass als Sofortmaßnahme bis 2022 eine Abschaltung von 12,5 GW Kohlekraftwerke – das entspricht rund 20 Kraftwerksblöcken – umgesetzt werden kann. Das ist fast ein Drittel der heutigen Leistung.\n\nWir brauchen aber auch Strukturwandel. Wir brauchen Hilfen für die Beschäftigten im Kohlebergbau. Wir brauchen einen Ausgleich für die Strompreise und eine Zukunft für die Menschen, die im Tagebau beschäftigt sind. Wir brauchen auch Versorgungssicherheit. Deswegen gilt es jetzt, zeitnah, aber ohne Hetze diesen Konsens, den wir gefunden haben, eins zu eins umzusetzen.\n\nFrau Badum, Sie haben gestern der dpa gesagt: Wer nur über Gelder für die betroffene Region spricht, der wird keine einzige Tonne CO 2 einsparen. – Ich finde das, ehrlich gesagt, ein bisschen zynisch, weil wir es mit den Beschäftigten machen müssen.</td>
      <td>0.452643</td>
    </tr>
    <tr>
      <th>10491</th>
      <td>ID1923102900</td>
      <td>Thomas Lutze (DIE LINKE)</td>
      <td>294</td>
      <td>Ich nehme diesen Tagesordnungspunkt zum Anlass, um noch einmal die Sicht meiner Fraktion auf die aktuellen Anforderungen des Klimawandels darzustellen. Es ist zum Beispiel unstrittig, dass der motorisierte Individualverkehr ein wesentlicher Mitverursacher des CO\n\n-Ausstoßes ist. Es ist aber auch unstrittig, dass heute über 800 000 Menschen in Deutschland direkt in der Automobilindustrie arbeiten.\n\nAus Sicht der Linksfraktion sind Maßnahmen für den Umwelt- und Klimaschutz genauso wichtig wie Maßnahmen zur Sicherung dieser Industriearbeitsplätze. Wer aber allen Ernstes glaubt, die Umstellung der Pkw- auf die sogenannte E-Mobilität könne die Lösung beider Probleme sein, der irrt gewaltig. In Deutschland sind derzeit rund 54 Millionen Fahrzeuge zugelassen. Man muss kein Hellseher sein, um vorauszusehen, dass diese Anzahl in den nächsten Jahren deutlich zurückgehen wird. Die Ursachen dafür sind – das haben wir alle so gewollt, und das wollen wir auch weiterhin – ein wesentlich verbesserter öffentlicher Personennahverkehr bzw. Personenverkehr insgesamt, aber auch deutliche Veränderungen im Mobilitätsverhalten vieler Menschen. Die Zeit, dass man ein Auto braucht, um mobil zu sein, wird für viele Menschen Geschichte sein.\n\nDieser Trend macht aber auch vor den sogenannten Elektroautos nicht halt. Wir brauchen also für den Erhalt der Industriearbeitsplätze einen äquivalenten Ersatz. Dafür gibt es kein Patentrezept. Es muss ein breites Portfolio an Industriezweigen sein. Doch um das zu entwickeln, müssen die Regierungen sowohl im Bund als auch in den Ländern, die Industrieverbände, aber auch die Gewerkschaften erkennen, dass dieser Wandel nicht nur bevorsteht, sondern dass er schon längst eingesetzt hat.\n\nAuch deshalb ist es müßig, darüber zu debattieren, ob man Pkw sinnvoller mit Verbrennungsmotoren oder batterieelektrisch antreibt. Es ist auch müßig, darüber zu debattieren, ob man den herkömmlichen Kraftstoffen Biokraftstoffe beimischt oder nicht und, wenn ja, mit welchem Anteil. Wenn wir die Klimakrise ernst nehmen, dann helfen uns keine kleinen Bausteine.</td>
      <td>0.427037</td>
    </tr>
    <tr>
      <th>5844</th>
      <td>ID1917608100</td>
      <td>Gerhard Zickenheiner (BÜNDNIS 90/DIE GRÜNEN)</td>
      <td>376</td>
      <td>Schauen Sie, es ist doch zynisch, dass es eine Pandemie braucht, um in die Nähe unserer Klimaziele für 2020 zu kommen. In Kommunen gibt es willige Akteure über fast alle Fraktionen hinweg, die wissen, was für Klima, Nachhaltigkeit und Resilienz zu tun ist. Sie erklären in ihren Dörfern, Städten und Kreisen den Klimanotstand. Sie haben längst das Fachwissen, wie lokale Resilienz zu generieren ist. Sie arbeiten verzweifelt daran, mit Radwegen, Mobilitätsstationen, ÖPNV den Verkehr endlich zu entgiften, die Probleme von Flora und Fauna in ihrer Heimat zu lösen, die Überhitzung in ihren Städten und den Wassermangel in den Griff zu bekommen.\n\nWenn wir unsere Kommunen bei der Transformation finanziell und personell so ausstatten, dass es zielführend zur Umsetzung der Paris- und der Nachhaltigkeitsziele reicht, dann können zum Beispiel im Forst hinreichend Mitarbeiter eingestellt werden, die Planungsämter die Radwegeplanungen leisten, die Tiefbauer und die Busunternehmen die Verkehrswende tatsächlich herbeiführen, und Städte könnten grüner und wasserspeichernd werden. Das liegt nämlich alles in deren Kompetenzen.\n\nWas wären die Folgen? Erstens. Wir würden jede Menge schnell verfügbare Arbeitsplätze schaffen. Zweitens. Durch schnellstmögliche Aufstockung der Ausbildungs- und Studienplatzkapazitäten in den entsprechenden Disziplinen schaffen wir es endlich einmal, dass Förderprogramme auch abgerufen werden können, weil die Umsetzungskapazitäten ausnahmsweise rechtzeitig geschaffen werden, und bringen damit nebenbei eine ganze Generation Fridays for Future in Lohn und Arbeit. Drittens – und damit zum Titel unseres Antrag –: Wir würden „Klimaschutz, Klimaanpassung und nachhaltige Entwicklung als kommunale Konjunkturmotoren nutzen“.\n\nWenn nämlich das öffentliche Auftragsvolumen durch die Transformationsarbeit hochschnellt, dann geht erstens die Arbeitslosigkeit herunter, zweitens steigen die Gewerbesteuereinnahmen, und drittens sinken die Sozialkosten. Wenn Sie meinen, das wäre zu teuer, dann empfehle ich ein Gespräch mit dem Städtetag, den Rückversicherern und am besten auch noch mit der Ärztekammer, die seit Langem die irrsinnig hohen Zahlen von Opfern der Luftverschmutzung und Hitze beklagt.\n\nDie Mittel dafür sind heute besser angelegt als morgen, weil die Klimaschäden und ihre Kosten und das Leid, das sie verursachen, mit jedem Jahr gigantisch wachsen. Wir investieren damit in eine sichere und gerechte Zukunft.\n\nFragen Sie bei sich zu Hause in den Kommunen die Menschen in ihren Fraktionen, was sie davon halten, und danach reden Sie mit uns. Das, Herr Altmaier, könnte Teil eines glaubwürdigen, die Menschen vor Ort motivierenden, parteiübergreifenden Klimapaktes sein.</td>
      <td>0.407037</td>
    </tr>
    <tr>
      <th>6874</th>
      <td>ID1918909200</td>
      <td>Dr. Julia Verlinden (BÜNDNIS 90/DIE GRÜNEN)</td>
      <td>423</td>
      <td>In der zukünftigen, klimagerechten Energiewelt wird Energie deutlich effizienter genutzt, der Energiebedarf sinkt. Weil immer weniger Kohle, Öl und Erdgas verbrannt werden, steigt aber selbst bei höherer Energieeffizienz gegenüber heute der Bedarf an sauberem Strom: für Elektromobilität, Wärmepumpen, Grünen Wasserstoff. Deshalb brauchen wir neben dem Ausbau von Solar- und Windenergie an Land natürlich auch mehr Windstrom vom Meer. Das ist nachhaltig und klimaverträglich.\n\nNun legen Sie von der Bundesregierung ein Gesetz vor, das den Ausbau der Offshorewindenergie voranbringen soll. Auf den ersten Blick könnte man meinen, dass darin ja immerhin ein Punkt in die richtige Richtung geht: Sie haben hier nämlich Ihre eigenen, bisher viel zu niedrigen Ausbauziele für die Windenergie auf See angehoben. Statt bisher 15 Gigawatt sollen es bis 2030 immerhin 20 Gigawatt werden, und zehn Jahre später doppelt so viel.\n\nDoch wie das mit Zahlen so ist: Sie muss man ins Verhältnis setzen, um sie zu bewerten. Und dann kommt das wahre, ernüchternde Bild zum Vorschein, das diese Regierung seit Jahren bei der Energiewende abgibt. 2014 nämlich erst hatte diese Koalition aus Union und SPD hier im Bundestag das Ausbauziel für Windenergie auf See für das Jahr 2030 von 25 Gigawatt auf 15 Gigawatt reduziert.\n\nDie Offshorewindenergie kann dauerhaft gut bezahlte Arbeitsplätze schaffen und den Klimaschutz ein gutes Stück voranbringen. Doch dafür braucht sie verlässliche, rechtssichere und naturverträgliche Rahmenbedingungen für Planungssicherheit,\n\nWie begrenzt das Engagement der Union für die Klimaschutzindustrie ist, haben wir auch in der Diskussion über die Finanzierung erlebt. Mit den Differenzverträgen lag für dieses Gesetz ein wirklich guter Vorschlag auf dem Tisch. Von Umweltverbänden über die Windbranche bis hin zu Industrievertretern waren alle dafür; doch die Union hat trotzdem mal wieder ihr Veto eingelegt und diese kostengünstige und kalkulierbare Lösung für die Finanzierung blockiert. Sehr, sehr schade!\n\nUnd um auf die kommenden Wochen zu schauen: Wenn Sie nun festgestellt haben, dass man eigentlich die erneuerbaren Energien doch schneller ausbauen sollte, als Sie das in den letzten Jahren geplant hatten – herzlichen Glückwunsch! –, dann nehmen Sie diese Erkenntnis doch bitte auch mit in die jetzt aktuellen Beratungen zum Erneuerbare-Energien-Gesetz. Denn es ist für die Arbeitsplätze, für die Investitionen, für das EU-Klimaziel, für die künftigen Generationen, für alle besser, wenn Sie jetzt direkt schon einen schnelleren Ausbau auch von Wind an Land und Sonnenenergie vorsehen und damit Planungssicherheit den vielen Menschen geben, die bei der Energiewende mitmachen wollen. Denn einen schnelleren Ausbau von Erneuerbaren wird es doch sowieso brauchen. Die fortschreitende Klimakrise wird die Regierung zum Handeln zwingen. Also, nur Mut: Die richtige Zeit zum Handeln ist jetzt!</td>
      <td>0.390547</td>
    </tr>
    <tr>
      <th>5121</th>
      <td>ID1916707700</td>
      <td>Rüdiger Kruse (CDU/CSU)</td>
      <td>494</td>
      <td>Nun kann man vieles outsourcen und sich anderer bedienen, aber wenn es das eigene Kerngeschäft betrifft, dann ist es schon gut, wenn man seine Kompetenzen dort behält. Das bedeutet nicht nur, dass man Schiffe bereedert, sondern auch, dass man sie auch bauen kann. Da waren wir gut aufgestellt; ganz anders als unsere asiatischen Mitbewerber, die unter der Containerschifffahrtskrise extrem gelitten und größte Schwierigkeiten haben.\n\nDie Entscheidung, aus dem Massenschiffbau auszusteigen, hat Deutschland schon vor vielen Jahren – sicherlich auch unter Schmerzen – getroffen, und es hat sich auf den Spezialschiffbau konzentriert. Wir waren gut aufgestellt, und dann kam dieses kleine böse Virus. Jetzt sieht die Welt natürlich auch hier anders aus, und jetzt muss man sich überlegen: Ist auch dies ein Bereich, in dem wir etwas tun wollen, oder tun wir da nichts? Wer jetzt nichts tut, der spielt „Schiffe versenken“, ein sehr gefährliches Spiel, und das wollen wir nicht; denn eine Kernkompetenz, die man in diesem Sektor einmal verloren hat, die ist dann auch weg.\n\nUnd es hängt mehr daran. Das erkennt man, wenn man das weite Feld der Zulieferer sieht. Es ist eben kein rein norddeutsches Thema; denn große Schiffsmotoren werden zum Beispiel am Bodensee gebaut. Da fährt kein einziges Schiff, das einen solchen Motor auch nur transportieren könnte. Diese Motoren werden für die Weltmeere gebraucht. In ganz Deutschland gibt es Zulieferer. Das ist eine Industrie mit 2 800 Firmen und etwa 200 000 Menschen, also extrem mittelständisch geprägt. Das heißt, wir erreichen dort auch sehr kleinteilige Strukturen und können sehr direkt helfen.\n\nWas wollen wir jetzt tun? Wir wollen das tun, was wir eigentlich in ein paar Jahren sowieso tun würden: Die Beschaffung von Schiffen, die wir in Planung haben, von denen wir schon heute wissen, dass wir sie in drei, vier oder fünf Jahren in Auftrag geben müssen, wollen wir vorziehen. Wir sagen, dass wir diese Ausschreibungen so gestalten sollten, dass wir auch noch ein anderes Ziel erreichen. Denn auch diese Schiffe werden lange fahren, und sie werden unseren Klimaabdruck mit bestimmen. Wenn ein Schiff 30 Jahre genutzt wird, dann sind wir im Jahr 2050, und wir alle wissen, welche hohen Ziele wir für 2050 haben.\n\nDeshalb wollen wir jetzt die Anreize setzen, dass Innovation eingebracht und umweltfreundlichste Technik eingebaut wird. Der Nebeneffekt dabei ist: Das können nur die, die wirklich gut sind. Es ist die Fähigkeit der deutschen Werften und der deutschen Schiffsbauindustrie, genau diese zukunftsfähigen Schiffe zu liefern.\n\nVon daher ist es wichtig, dass wir im Rahmen unserer vielen Maßnahmen, die wir jetzt treffen, auch in diesem Sektor aktiv werden und unsere Mittel so einsetzen, dass wir der Vielfalt des deutschen Schiffsbaus helfen, diese Kompetenz halten und mit dieser Kompetenz dann natürlich auch die Maßstäbe setzen. Denn – ich muss das einfach wieder sagen – alles, was wir jetzt tun, muss dem Prinzip der Nachhaltigkeit folgen. Dieser Antrag führt genau in die richtige Richtung, sodass wir auch in Zukunft mindestens eine Handbreit Wasser unter dem Kiel haben werden.</td>
      <td>0.386491</td>
    </tr>
    <tr>
      <th>5803</th>
      <td>ID1917602100</td>
      <td>Dieter Janecek (BÜNDNIS 90/DIE GRÜNEN)</td>
      <td>490</td>
      <td>An die Bundesregierung. Herr Altmaier, ich will Ihnen einmal ganz konkret erzählen, was Energiepolitik im Jahr 2020 in Bayern auf der Rechtsgrundlage der Bundesregierung bedeutet: Vor zehn Jahren hat der Landkreis Rhön-Grabfeld versucht, einen Windpark zu genehmigen. Er wurde genehmigt. Dann kam die 10-H-Regel. Dann haben die Investoren und die Bürgerenergiegenossenschaften angefangen, zu bauen. Sie haben zehn Fundamente errichtet, ungefähr 10 Millionen Euro investiert. Und jetzt hat die Bayerische Staatsregierung ein Baugesetz erlassen, das es nicht erlaubt, neue Technologien anzuwenden. Also, wir haben hier eine Millionenruine vor Ort. Das ist Folge Ihrer Politik und der Politik Ihrer Freunde in Bayern. Das ist Antiklimaschutzpolitik!\n\nEs ist auch Folge Ihrer Politik, dass in den letzten Jahren mehr Arbeitsplätze im Bereich der erneuerbaren Energien vernichtet worden sind, als wir im gesamten Bereich der Kohleindustrie überhaupt haben. Milliardeninvestitionen fließen in diesen Bereich, und beim Ausbau der Windenergie im Süden zum Beispiel kommen wir nicht voran. Das kann nicht sein. Das müssen Sie dringend ändern.\n\nZum Thema Wasserstoff. Ich bin auf Ihrer Seite: Wir müssen das vorantreiben. Wir müssen Partnerschaften finden. Wir werden den dafür benötigten Strom auch nicht allein in Deutschland auf Basis erneuerbarer Energien produzieren. Aber wir müssen dann auch darüber diskutieren, welche Anwendungen wir eigentlich wirklich brauchen. Sie können sich doch nicht gemeinsam mit der Automobilindustrie hinstellen und sagen: „Das ist der Anwendungsbereich“, wenn die Industrie im Stahlbereich, im Chemiebereich, in vielen Bereichen diese Anwendung dringend braucht. Wir brauchen hier Entscheidungen; Sie sind nicht bereit, diese zu treffen. Sie können den Leuten nicht alles versprechen und am Ende nichts realisieren. Sie brauchen hier ein konkretes Konzept, das den Weg weist.\n\nÜberhaupt zum Thema Automobilwirtschaft. Ich wohne in einer Region, die auch stark betroffen ist, nämlich Ingolstadt und München, Audi und BMW. Wir brauchen jetzt Transformationsdialoge, die nach vorne weisen, das heißt, wir brauchen konkrete Maßnahmen, Qualifizierungsoffensiven. Vielleicht ist auch eine Viertagewoche für die Beschäftigten eine gute Idee, um Ängste abzubauen. Wir dürfen also nicht nur über die Vergangenheit reden, sondern müssen entschlossen in die Zukunft investieren. Klimaschutz muss jetzt realisiert werden; denn ohne Klimaschutz – schauen Sie auf die Brände in Kalifornien, schauen Sie auf die Dürren in den deutschen Wäldern – gibt es keine zukunftsgerichtete Wirtschaftspolitik. Das muss der Ausgangspunkt sein. Da müssen wir voranschreiten.\n\nGanz zum Schluss ein Thema, das vielleicht ein bisschen sperrig, aber doch sehr relevant ist, auch für die deutsche Industrie: das Thema Kreislaufwirtschaft. Wir sind viel zu schlecht darin, Kreislaufwirtschaft zu realisieren. In diesem Bereich gibt es viel Potenzial für unsere Industrie, um führend beim Produktdesign zu werden, beim Ökodesign, bei der Frage, wie wir die gewaltigen Ressourcen, die wir aufwenden, auch wiederverwenden. Hier braucht es auch gesetzliche Initiativen, hier braucht es Fortschritte. Dazu werden wir Grünen – Kollegin Bettina Hoffmann – in den nächsten Wochen und Monaten auch einiges vorlegen. Wir müssen eine moderne Industriepolitik machen. Liefern Sie jetzt endlich! Klimaschutz ist die Aufgabe unserer Zeit. Jobs der Zukunft gibt es nur mit konsequentem Klimaschutz.</td>
      <td>0.379995</td>
    </tr>
    <tr>
      <th>1483</th>
      <td>ID1912102300</td>
      <td>Kay Gottschalk (AfD)</td>
      <td>167</td>
      <td>Meine Damen und Herren, was diese GroKo aus den Augen verloren hat, ist: Sie stellen nur auf die Einnahmeseite ab, wo Sie dem Bürger das Geld aus der Tasche ziehen. Ich bin völlig bei Ihnen, dass der Mittelstandsbauch genau diese entsprechenden Steigerungen hat und dass es unmöglich ist, dass wir gerade den unteren und mittleren Einkommen, den Arbeitnehmern in diesem Land, das Geld aus der Tasche ziehen, während wir nach wie vor – das habe ich eben dokumentiert – Steuerschlupflöcher gerade für die großen Konzerne hier in Deutschland haben. Diese nutzen die Konzerne aggressiv aus. Was ich an Ihrem Debattenbeitrag unfair finde, ist: Sie tun so, als wäre es eine Gesetzmäßigkeit, als hätten Sie das Geld nicht, diese 22 Milliarden Euro, die ich in meiner Rede erwähnt habe, rauszunehmen, damit wir zu einer echten Steuerentlastung kommen. Das ist doch ein Märchen. Wir entlasten nicht in Höhe der vollen Progression. Da wäre mehr Ehrlichkeit angebracht. Es sind zwei Seiten einer Bilanz: Gehen wir doch endlich an die Ausgaben!</td>
      <td>0.379814</td>
    </tr>
  </tbody>
</table><!--kg-card-end: html--><p>What about the top speeches combining the topics education ("Bildung"), immigration ("Zuwanderung") and jobs?:</p><!--kg-card-begin: markdown--><details style="min-width: 100%;">
<summary>Click to show code.</summary>
<pre><code class="language-python">pd.set_option(&quot;display.max_colwidth&quot;, None)
topN_speeches_for_topic_combination(classified_speech_paragraphs_df, topic_combination=[&quot;Bildung&quot;, &quot;Zuwanderung&quot;, &quot;Jobs&quot;])
</code></pre>
</details><!--kg-card-end: markdown--><!--kg-card-begin: html--><table border="0">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>speech_id</th>
      <th>speaker_full_name</th>
      <th>speech_len_words</th>
      <th>speech</th>
      <th>Bildung_Zuwanderung_Jobs</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>2829</th>
      <td>ID1913902000</td>
      <td>Dr. Wolfgang Strengmann-Kuhn (BÜNDNIS 90/DIE GRÜNEN)</td>
      <td>157</td>
      <td>Sie haben das Qualifizierungschancengesetz schon angesprochen; das haben wir als Grüne unterstützt. Es ist vor einem Jahr in Kraft getreten. Wir haben aber schon damals gesagt: Das reicht nicht. – Wenn man sich die Entwicklungen im Land anschaut, kriegt man mit: So richtig läuft das noch nicht. Wir müssen da unbedingt nachlegen. – Wir Grünen haben dazu Ende des Jahres ein umfassendes Papier mit Vorschlägen erarbeitet, in dem wir einen Rechtsanspruch auf Weiterbildung, eine bessere soziale Absicherung durch ein Weiterbildungsgeld und Qualifizierung während der Kurzarbeit fordern. Sie kündigen schon seit einiger Zeit das Arbeit-von-morgen-Gesetz an – gerade eben auch wieder –; aber es ist noch nicht da. Wo hakt es denn? Wann wird es kommen? Offenbar gibt es dazu in der Regierung noch unterschiedliche Positionen. Vielleicht können Sie da ein bisschen Licht ins Dunkel bringen. Glauben Sie, dass das dann wirklich ausreicht, um der großen Herausforderung gerecht zu werden? Was müsste danach aus Ihrer Sicht noch kommen?</td>
      <td>0.473310</td>
    </tr>
    <tr>
      <th>412</th>
      <td>ID1910504900</td>
      <td>Mark Helfrich (CDU/CSU)</td>
      <td>478</td>
      <td>Für die Union ist dabei wichtig: Bei einem Einwanderungsgesetz für Fachkräfte darf der Schwerpunkt nicht auf mittelfristigen Potenzialen liegen. Er muss vielmehr auf der konkreten Qualifikation der Menschen liegen, die nach Deutschland kommen; denn wir brauchen echte Fachkräfte und nicht potenzielle Fachkräfte und schon gar keine Geringqualifizierten, deren Arbeitsplätze beim nächsten Konjunkturabschwung gefährdet sind. Es wird bei den Arbeitskräften aus Drittstaaten allerdings nicht ganz unproblematisch sein, geeignete Fachkräfte zu finden; denn in vielen außereuropäischen Ländern gibt es keine formale Ausbildung, und das macht dann den Nachweis der Qualifikation schwierig. Gerade Handwerker lernen ihren Beruf dort meist nach dem Prinzip Learning by Doing. Gleichwohl stellen wir beim Fachkräfteeinwanderungsgesetz auf die Qualifikation der Bewerber ab. Das ist der rote Faden, der sich durch das gesamte Gesetz zieht. Im Einzelnen regeln wir Folgendes:\n\nErstens soll es Nicht-EU-Ausländern mit einer Berufsausbildung leichter gemacht werden, hier zu arbeiten. Eine Beschränkung auf Engpassberufe oder eine Vorrangprüfung ist deshalb nicht mehr vorgesehen. Zudem ist die Arbeitsplatzsuche jetzt auch für Fachkräfte geöffnet, nicht mehr nur für Hochschulabsolventen. Sie haben sechs Monate Zeit, einen Arbeitsplatz zu finden.\n\nZweitens geben wir die Möglichkeit der Nachqualifizierung in Deutschland, wenn wir die Qualifizierung im Ausland nicht voll anerkennen können. Parallel dazu kann eine Beschäftigung im avisierten Beruf ausgeübt werden. Das soll zum Beispiel im Bereich des Handwerks dazu beitragen, Anerkennungsverfahren zu erleichtern.\n\nDrittens kann man zukünftig nicht nur nach Deutschland kommen, um eine Ausbildung zur Fachkraft zu absolvieren. Nein, wir öffnen sogar den Weg dafür, dass Jugendliche für sechs Monate zur Ausbildungsplatzsuche nach Deutschland kommen dürfen. Allerdings – das ist auch richtig so –, wenn es um die Suche nach einem Arbeits- oder Ausbildungsplatz geht, legen wir die Messlatte etwas höher an. Voraussetzungen sind gute Deutschkenntnisse und entsprechende Berufs- und Schulabschlüsse. Dadurch stellen wir sicher, dass nur tatsächlich geeignete Bewerber zu uns kommen.\n\nIch möchte noch einen wichtigen Punkt nennen, der für die Wirtschaft von Bedeutung ist. Es geht bei alldem, was wir heute hier verabschieden, auch um praktische Aspekte wie zum Beispiel administrative Abläufe bei der Fachkräfteeinwanderung. Hier müssen wir auch in einigen Bereichen – das ist schon gesagt worden – besser werden, beispielsweise wenn es um raschere Verfahren zur Berufsanerkennung oder um schnellere Visaerteilung in unseren Auslandsvertretungen geht. Auch zentrale Anlaufstellen im In- und Ausland für Wirtschaft und Fachkräfte sind wichtig. Nur dann wird am Ende dieses Gesetz ein Erfolg sein.\n\nDas beschleunigte Fachkräfteverfahren, das wir mit diesem Gesetz neu schaffen, ist ein guter Ansatz. Dieses kann durch den Arbeitgeber bei der Zentralen Ausländerbehörde des jeweiligen Bundeslandes betrieben werden. Visumstellen, Anerkennungsstellen und Ausländerbehörden müssen entsprechende Anträge dann innerhalb kurzer Fristen bearbeiten.\n\nSehr verehrte Damen und Herren, Sie sehen, der Gesetzentwurf enthält klare Kriterien, wer unter welchen Voraussetzungen zum Arbeiten nach Deutschland kommen kann. Wir schaffen damit Klarheit und Handlungssicherheit für die Unternehmen und – mindestens genauso wichtig – für künftige Interessierte und Bewerber. Packen wir es gemeinsam an!</td>
      <td>0.457752</td>
    </tr>
    <tr>
      <th>12308</th>
      <td>ID194209900</td>
      <td>Erich Irlstorfer (CDU/CSU)</td>
      <td>469</td>
      <td>Meine sehr verehrten Damen und Herren, das ist ein mutiger Versuch. Wir halten an unserem Ziel fest, durch neue Rahmenbedingungen mehr Menschen für den Pflegeberuf zu gewinnen. Nach einem enormen fachlichen Austausch und einem parlamentarischen Verfahren, das Befürworter und Skeptiker sehr gefordert hat, geht das jetzt in die Umsetzung. Die Fronten waren verhärtet. Ja, das stimmt. Wir haben hier um Lösungen gerungen. Und niemand von denjenigen, die in der Anhörung dabei waren, und aus egal welchem politischen Lager ist heute hundertprozentig zufrieden und sagt: Ja, genau so wollte ich das von Anfang an. – Nein, aber wir haben ein Grundgerüst. Wir haben eine duale Ausbildung, die sich in Praxis und Theorie weiterentwickelt hat. Und wir werden – das ist wesentlich – keine Jugendlichen auf der Strecke verlieren, weil wir ein System für alle Schultypen entwickelt haben; das war uns wichtig.\n\nMeine sehr geehrten Damen und Herren, wir brauchen auch alle Hände, die diese große Aufgabe gemeinsam mit uns meistern wollen. Die Kollegin Schulz-Asche hat die Kinderkrankenpflege angesprochen. Ich weiß, wir haben eine extrem hohe Qualität, wir haben mehr Bewerber als Stellen und ein hervorragendes Beschulungssystem. Aber wir wollen das ändern, weil wir auch die Durchlässigkeit im System, die Attraktivitätssteigerung und somit auch die Generalistik als extrem wichtig erachten. Meine sehr geehrten Damen und Herren, Sie können mich beim Wort nehmen: Ich werde als Erster hier den Finger in die Wunde legen, wenn wir nach dieser Erprobungs- und Umsetzungsphase nicht mehr dieser Meinung sind – deshalb gibt es ja auch eine Evaluierung –, und dafür sorgen, dass wir dann auch nachbessern. Die Kinderkrankenpflege ist für uns extrem wichtig. Wir haben diese Worte natürlich gehört. Aber ich würde schon sagen: Lassen Sie uns jetzt auch beginnen.\n\nMeine sehr geehrten Damen und Herren, Deutschland kann und muss seine Anstrengungen in der Pflege auf allen Ebenen noch deutlich intensivieren. Deshalb ist es notwendig, dass wir eine sinnvolle Akademisierung des Berufs klug vorbereiten. Frau Westig, ich muss Ihnen schon sagen: Blinder Aktionismus und eine übertriebene Verschärfung der Anforderungen, zum Beispiel bei Zwischen- und Abschlussprüfungen, würden weder eine Qualitätssicherung noch eine Qualitätssteigerung hervorrufen, sondern nur die Abbrecher- und Durchfallquote erhöhen. Das wollen wir nicht.\n\nInsofern möchte ich schon noch einmal sagen – das gilt für uns alle –: Trauen wir der Szene etwas zu. Wir haben gute Auszubildende, wir haben gute Betriebe, wir haben gute Lehrende, und wir haben vor allem junge Menschen, Schülerinnen und Schüler, die diesen Beruf gerne erlernen möchten. Deshalb kann ich nur dafür plädieren: Pflege von Kindern, von Jugendlichen, von Kranken, Behinderten, Rehabilitanden und vor allem auch von alten Menschen ist nicht nur ein Zukunftsberuf, sondern auch ein schöner Beruf, der gesellschaftliche Anerkennung, ein ordentliches Gehalt sowie Sicherheit und Attraktivität bei den Rahmenbedingungen verdient. Menschlichkeit, Empathie, Gespür, Gefühl plus hohe Fachlichkeit – das ist unser Weg, und den gehen wir hiermit.</td>
      <td>0.384636</td>
    </tr>
    <tr>
      <th>10872</th>
      <td>ID1923508000</td>
      <td>Dr. Birke Bull-Bischoff (DIE LINKE)</td>
      <td>303</td>
      <td>Zu viele junge Menschen werden in Warteschleifen und Sonderstrukturen verwiesen, zum Beispiel junge Menschen mit Hauptschulabschluss oder ohne Schulabschluss – durch die Praxis der Ausbildungsreife in der Bundesagentur für Arbeit. Lernorte mit einem hohen sozialen Image sind relativ gut ausgerüstet, beispielsweise im Bereich der überbetrieblichen Ausbildung. Sie erfahren öffentliche Aufmerksamkeit und sind ausgerüstet mit digitaler Infrastruktur. Aber dort, wo junge Menschen lernen, die eben nicht auf der Sonnenseite des Lebens unterwegs sind, herrscht oft Mangel, zum Beispiel in der außerbetrieblichen Ausbildung, in der Jugendberufshilfe, im Übergangssystem. Genau deshalb war und ist das einer der Schwerpunkte von uns Linken.\n\nWir brauchen mehr Ausbildungsgerechtigkeit. Wir brauchen mehr inklusive Angebote und Strukturen statt Warteschleifen und Sondersysteme.\n\nWir brauchen eine Reform des Übergangssystems. Alle jungen Leute, die in diesem Rahmen ihre Ausbildung absolvieren, müssen wenigstens die Chance haben, ihren Schulabschluss zu verbessern, und sie müssen die Chance haben, zu mindestens 50 Prozent von betrieblicher Praxis zu profitieren.\n\nEs muss nicht nur mehr Geld ins System, liebe Kolleginnen und Kollegen; es muss auch gerechter verteilt werden. Auch und gerade in der Jugendberufshilfe ist digitale Ausrüstung notwendig. Und wir brauchen das Recht für jeden Azubi, für jede Schülerin und jeden Schüler auf einen Laptop, auf einen Drucker und auf Verbrauchsmaterial.\n\nDas Ziel muss immer eine vollqualifizierende Ausbildung sein. Deswegen gehören Unterstützungssysteme, mehr Zeit, mehr sozialpädagogische Hilfen, mehr Flexibilität in das sogenannte Regelsystem, in die duale und die schulische Ausbildung.\n\nIch will noch ein Problem aufrufen, das uns nun über mehrere Jahre beschäftigt hat und sehr wichtig ist: Wer Erzieherin werden möchte, Logopädin oder Ergotherapeutin, der oder meistens die kriegt keine vernünftige Ausbildungsvergütung. Genau das ist der Grund, weshalb viele junge Leute – das habe ich gerade im Wahlkampf in Sachsen-Anhalt wieder erlebt – diese Ausbildung leider abwählen. Dabei brauchen wir Auszubildende in diesen Berufen. Das ist ein unhaltbarer Zustand.</td>
      <td>0.382534</td>
    </tr>
    <tr>
      <th>6895</th>
      <td>ID1918911600</td>
      <td>Beate Walter-Rosenheimer (BÜNDNIS 90/DIE GRÜNEN)</td>
      <td>348</td>
      <td>Es braucht gute Konzepte, um Fachkräfte erstens auszubilden und zweitens zu gewinnen. Chancengerechtigkeit und Teilhabe sind hier die wichtigsten Stichworte. Ich frage mich, wie es sein kann, dass Jahr für Jahr junge Menschen abgehängt werden, nicht in Ausbildung kommen und deshalb nicht am qualifizierten Berufsleben teilnehmen. Das können wir uns schlicht und einfach nicht mehr leisten.\n\nDarüber hinaus wissen wir bereits seit Langem, dass die Fachkräfte hier in Deutschland allein nicht ausreichen, um den Fachkräftebedarf zu decken. Wir brauchen also zusätzlich zur besseren Berufsqualifizierung junger Menschen auch die Zuwanderung von qualifizierten Fachkräften aus anderen Ländern. Und da haben Sie ja jetzt die ersten Schritte auf jeden Fall schon gemacht, Frau Karliczek. Mit dem Anerkennungsgesetz haben Fachkräfte aus anderen Ländern das Recht auf ein zügiges Anerkennungsverfahren, und damit wurde ein überfälliger Schritt auf dem Weg in eine offene und moderne Einwanderungsgesellschaft vollzogen. Das begrüßen wir auch ganz klar.\n\nDas allerdings sollte Sie, die Regierung, jetzt nicht, so finde ich, zum Eigenlob verleiten. Die Anerkennungspraxis bei uns reicht nämlich bei Weitem nicht aus, um den Fachkräftebedarf zu decken. Das sollten wir schleunigst ändern.\n\nSchauen wir doch noch mal auf die Anerkennungsquote; wir haben es heute schon gehört. Wir können es ja auch andersherum sehen: Über die Hälfte der Anträge wird abgelehnt. Das ist ein sehr hoher Prozentsatz, ich finde, ein zu hoher Prozentsatz.\n\nEs ist also jetzt an der Zeit, die richtigen Schlüsse aus den Erfahrungen der letzten Jahre zu ziehen; denn bei der Umsetzung des Einwanderungsgesetzes ist definitiv noch Luft nach oben. Die Bundesregierung hat es hier eben nicht geschafft, das größte Hindernis bei der Fachkräfteeinwanderung aus dem Weg zu räumen, nämlich das komplexe Verfahren zur Anerkennung der Berufsabschlüsse. Das haben Sie gar nicht angetastet.\n\nDazu kommt: Jedes Anerkennungsverfahren kostet Geld. Das Nachholen erforderlicher Qualifizierungen kostet noch mehr Geld. Das wollen wir in Zukunft fördern. Damit kein Talent verloren geht, wollen wir das Aufstiegs-BAföG auch für Anpassungs- und Nachqualifizierungen öffnen und die Unterstützung für die Kosten des Anerkennungsverfahrens verbessern.\n\nWege vereinfachen, Hürden abbauen, Qualität sichern, Anstrengungen belohnen: Das kann ein guter Weg für mehr Fachkräfte in unserem Land sein.</td>
      <td>0.381096</td>
    </tr>
    <tr>
      <th>12293</th>
      <td>ID194207800</td>
      <td>Dr. h. c. Thomas Sattelberger (FDP)</td>
      <td>270</td>
      <td>Erstens. Akademische und berufliche Abschlüsse müssen anschlussfähig sein, wechselseitig. Berufliche Bildung muss Studienmodule anerkennen, Hochschulen berufliche Qualifikation. Vom Wissenschaftsrat vor vier Jahren gefordert, von der GroKo verschlampert!\n\nPunkt zwei. Viele unserer heutigen Berufe sind in zehn Jahren ausgestorben. Die gute Nachricht ist, dass neue Berufe entstehen: 3‑D-Druck-Experte, Industrial Data Analyst. Die Metall- und Elektroindustrie lebt es vor, industrielle Berufsbilder mit dem „Agilen Verfahren“ zügig fitzumachen. Andere schlafen. Kaufmann E-Commerce – 2018 eingeführt, zehn Jahre zu spät! Amazon hat heute 40 Prozent des deutschen Onlinehandels erobert. Wir dürfen nicht schlafen, liebes Bildungs- und liebes Wirtschaftsministerium! Aufwachen, bitte!\n\nPunkt drei. Nur ein Drittel der Unternehmen wird von Akademikern gegründet, die Hälfte von beruflich Qualifizierten. Reinhold Würth, Artur Fischer, René Obermann – Unternehmer ohne Studium! Dieses Land investiert 340 Millionen Euro im Jahr für Akademikerstipendien, für Spitzenleister beruflicher Bildung klägliche 25 Millionen Euro. Unsere Top- und Facharbeiter, unsere Techniker und unsere Meister brauchen eine adäquate Stiftung für Begabte und Exzellenzwettbewerbe für unsere Berufsbildungszentren.\n\nPunkt vier. Deutschland hat mehr als 2 Millionen junge Menschen ohne Berufsausbildung, verschüttete Talente zwischen 20 und 34 Jahren, Ausbildungsabbrecher, Schulabbrecher, Schulabgänger ohne Berufsausbildung. Oft Alleinerziehende, die sich strecken müssen, ihre Kinder großzuziehen, Geld zu verdienen, einen Beruf zu erlernen. Und Geflüchtete, die zudem ihre Familien zu Hause unterstützen. Hier hilft nur Ausbildung in Teilzeit und in Modulen bis zum Abschluss.\n\nMeine Damen und Herren, all das ist der Bundesregierung seit Jahr und Tag bekannt. Aber sie handelt nicht. Sie versucht, uns mit Ankündigungslyrik müde zu spielen. Das reicht nicht, und das klappt nicht. Spucken Sie in die Hände! Packen Sie es endlich an! Ran an den Speck, Frau Karliczek!</td>
      <td>0.365766</td>
    </tr>
    <tr>
      <th>6890</th>
      <td>ID1918911100</td>
      <td>Norbert Maria Altenkamp (CDU/CSU)</td>
      <td>583</td>
      <td>Ingenieure aus Russland, Krankenschwestern von den Philippinen, Konditormeister aus Chile, Industriemechaniker aus Syrien, IT-Spezialisten aus Indien – hier kommt das Berufsqualifikationsfeststellungsgesetz ins Spiel, das wir 2012 als Kernstück des Anerkennungsgesetzes eingeführt haben. Es ist ein Gesetz mit einem schwierigen Namen; aber dass es ungemein erleichternd wirkt, wenn es um die Anerkennung und Gleichwertigkeit ausländischer Berufsabschlüsse in rund 600 bundesrechtlich geregelten Berufen geht, ist unbestritten. Denn das ist die Voraussetzung dafür, dass ausländische Fachkräfte unseren Unternehmen dabei helfen können, wettbewerbsfähig zu bleiben. Das gibt den Fachkräften auf dem deutschen Arbeitsmarkt zudem eine sichere Perspektive und eröffnet interessante Karrierechancen, und auch die Integration von qualifizierten Geflüchteten wird durch die Anerkennung erleichtert.\n\nErst im März haben wir an dieser Stelle darüber diskutiert, dass das Anerkennungsgesetz seit 2012 eine ganz wichtige und erfolgreiche Waffe im Kampf gegen den Fachkräftemangel ist. Seit 2012 wurden allein für die bundesrechtlich geregelten Berufe rund 175 000 Anerkennungsanträge gestellt. Über die Hälfte wurden positiv beschieden, in fast allen anderen Fällen wurden die Qualifikationen als teilweise gleichwertig anerkannt oder die Auflage einer Nachqualifizierungsmaßnahme gemacht.\n\nAuch die neuen Zahlen aus dem letzten Jahr sprechen für sich: 2019 haben die Anerkennungsverfahren einen neuen Höchststand erreicht, besonders in den Gesundheits- und Pflegeberufen. Hier wurden zwei Drittel der über 33 000 Anträge gestellt, und die Zahl der Anerkennungen hat sich seit 2016 fast verdreifacht.\n\nMit dem Fachkräfteeinwanderungsgesetz, das am 1. März in Kraft getreten ist, haben wir die Möglichkeiten des Aufenthalts zur Arbeitssuche und zur beruflichen Anerkennung erweitert. Die neue Zentrale Servicestelle Berufsanerkennung bei der BA in Bonn lotst die zuwanderungsinteressierten Fachkräfte zudem seit Februar schnell und sicher durch die komplexen Verfahren. Besonders das neue beschleunigte Fachkräfteverfahren im Rahmen des Anerkennungsgesetzes wird helfen, viele bürokratische Hürden bei der Fachkräfteeinwanderung zu umschiffen.\n\nDamit das Fachkräfteeinwanderungsgesetz optimal wirken kann, werden wir dem Anerkennungsgesetz heute noch einen weiteren Feinschliff verpassen.\n\nErstens. Wir werden deshalb die Rolle der Ausländerbehörden im neuen beschleunigten Fachkräfteverfahren als Schnittstelle zum Arbeitgeber stärken.\n\nZweitens. Ein ganz wesentlicher Punkt ist für mich, dass wir einen neuen separaten Feststellungsbescheid zur Gleichwertigkeit der ausländischen Qualifikation bei reglementierten Berufen – also unter anderem in Gesundheitsberufen – einführen, um flexibler reagieren zu können und um die Angleichung an berufsrechtliche Fachgesetze zu fördern. Bisher wurde die Gleichwertigkeit nur im Berufszugangsverfahren geprüft, das neben dem Berufsabschluss weitere Anforderungen an die Berufserlaubnis stellt, zum Beispiel gesundheitliche Eignung, Zuverlässigkeit, Sprachkompetenzen. Künftig soll deshalb eine separate Gleichwertigkeitsprüfung beantragt werden können, bei der es nur um die Qualifizierung geht. Der neue Feststellungsbescheid wird es zum Beispiel Gesundheits- und Pflegefachkräften erleichtern, unter bestimmten Bedingungen auch hier in ihrem erlernten Beruf zu arbeiten. Er ebnet den Weg in Qualifizierungsmaßnahmen zur Erlangung der vollen Gleichwertigkeit, und er erhöht die Chancen auf ähnliche, nicht reglementierte Tätigkeiten auf dem Arbeitsmarkt.\n\nDrittens. Wir brauchen aussagekräftige Daten, um die Wirkung der neuen Maßnahmen zu evaluieren und insbesondere unser erklärtes Ziel zu erreichen, die Fachkräftezuwanderung weiter zu optimieren und zu beschleunigen. Deshalb werden wir die statistische Erhebung zu den Anerkennungsverfahren noch konkreter gestalten. Deshalb werden wir als neue Merkmale das Datum der Empfangsbestätigung, das Datum und die Vollständigkeit der vorzulegenden Unterlagen sowie Besonderheiten im Verfahren erfassen.\n\nDiese Änderungen sind wichtig, auch wenn wir leider feststellen müssen, dass durch die coronabedingten weltweiten Lockdowns viele Maßnahmen, die die Anerkennung der ausländischen Qualifikationen und die Fachkräftezuwanderung erleichtern sollen, zurzeit nicht ihre volle Wirkung entfalten können; auch das beschleunigte Fachkräfteverfahren nicht, denn in den Visastellen stauen sich die Anträge. Umso dringender ist es deshalb, nicht nur die Gesetze weiter zu optimieren, sondern auch die Visaverfahren schnell weiter zu digitalisieren.</td>
      <td>0.356113</td>
    </tr>
    <tr>
      <th>5663</th>
      <td>ID1917403100</td>
      <td>Yasmin Fahimi (SPD)</td>
      <td>732</td>
      <td>Ich will auf einen Befund des Berufsbildungsberichtes eingehen. In der Tat ist die Vertragslösungsquote, die es auch in der beruflichen Ausbildung gibt, mit 26,5 Prozent auch aus meiner Sicht zu hoch. Dieser Wert ist Anlass, dass wir uns noch einmal Gedanken darüber machen, wie wir eigentlich die Berufsorientierung aufstellen, welche Qualität, welche Spielräume in den Schulen wir dazu brauchen, aber auch darüber, welche Attraktivitätssteigerungen für die Auszubildenden – das heißt bessere Ausbildungsbedingungen – wir eigentlich sicherstellen müssen. „Lehrjahre sind keine Herrenjahre“, sagt man so schön. Ja, das gilt aber nicht nur für die Auszubildenden, das gilt auch für die Ausbilder. Und deswegen ist es gut und richtig, dass wir mit der Novelle zum Berufsbildungsgesetz die Rechte der Auszubildenden verbessert haben und dass wir als SPD-Fraktion darauf gedrängt haben, dass es endlich eine Mindestausbildungsvergütung gibt.\n\nDer Anstieg des Anteils junger Menschen ohne Berufsabschluss – das ist ein zweiter Befund – muss uns allerdings auch Sorgen machen. 14,4 Prozent sind es inzwischen; das ist jeder siebte Jungerwachsene. Das deutet auch darauf hin, dass wir neben der Berufsorientierung vor allem einen besseren Übergang von Schule in Ausbildung brauchen. Wir brauchen Ausbildungslotsen, die tatsächlich dabei helfen, die verschiedensten Probleme zu lösen, seien sie sozialpädagogischer, psychischer oder sonstiger Art, um junge Schulabgänger in Betriebe zu vermitteln. Das muss regelkreisübergreifend sein. Deswegen glaube ich auch, dass die duale Einstiegsqualifizierung, wie sie in vielen Sozialpartnermodellen schon sehr erfolgreich gelebt wird, etwas ist, was wir uns sehr genau dahin gehend anschauen müssen, inwiefern wir das verallgemeinern können.\n\nAber – das sage ich an der Stelle auch, weil ich ja schon einige Gedanken der FDP-Fraktion dazu kenne – das darf nicht zu einem neuen Erprobungsjahr vor der Ausbildung werden. Man würde dann quasi sagen: Ach, dann gucken wir uns den mal ein Jahr an, ob wir den tatsächlich in Ausbildung übernehmen. – Es muss eine sehr spezifische Übergangsregelung für diese jungen Menschen geben, die hilft, individuelle Probleme zu überwinden und sie tatsächlich bis zum Ausbildungsabschluss zu begleiten.\n\nIch will als Drittes kurz den Anstieg schulischer Ausbildungsgänge in den Gesundheits-, Erziehungs- und Sozialberufen ansprechen; er liegt bei fast 4 Prozent. Das ist sehr erfreulich, weil wir diese jungen Menschen und diese Fachkräfte brauchen. Aber sie brauchen auch uns. Ich finde, es ist an der Zeit, darüber zu reden, wie wir dieses Zweiklassensystem der Ausbildung in Deutschland beenden: die duale Ausbildung gemäß Berufsbildungsgesetz einerseits und alle anderen Ausbildungen in einem Wildwuchs von 16 Bundesländern andererseits. Wir brauchen in Deutschland endlich ein neues Berufsgesetz, das Berufe auch in diesem Bereich ordnet, Rechte und Pflichte einheitlich klärt und eine bundesländerübergreifende Anerkennung sicherstellt.\n\nIch möchte zum Schluss natürlich auch darauf eingehen, dass die Zahl der neu abgeschlossenen Ausbildungsverträge gesunken ist, ja – und dabei beziehe ich mich auf Zahlen von vor der Pandemie. Wir kennen inzwischen auch andere Zahlen, nicht zuletzt vom Bundesinstitut für Berufsbildung, die prognostizieren, dass für das kommende Ausbildungsjahr 45 000 Ausbildungsplätze weniger angeboten werden. Wir wissen genau: Wer einmal aus der Ausbildung ausgestiegen ist, der tut sich mit einem Wiedereinstieg schwer.\n\nSelbst wenn einige Bewerberzahlen zurückgehen, weil es eine Umorientierung auf die akademische Ausbildung gibt – was im Übrigen nicht einfach eine Konkurrenz zur dualen Ausbildung ist, so nach dem Motto „Für die Eliten das eine und für den Rest, das Fußvolk, das andere“, sondern was zwei attraktive Berufswege sein sollen –, müssen wir darüber reden, wie wir die Attraktivität der Ausbildung weiter steigern können und wie es uns gelingt, dass zum Beispiel Hauptschüler besser vermittelt werden. Der Markt alleine wird es nicht regeln. Deswegen, glaube ich, ist es an der Zeit, darüber zu reden, dass diese Generation nicht zu einer verlorenen „Generation Corona“ wird. Wir müssen darüber reden, dass es endlich eine Ausbildungsplatzgarantie in Deutschland geben muss. „Qualifizieren, nicht parken“, das muss unser Motto sein.\n\nWir haben als Bundesregierung und als SPD-Fraktion schon einiges getan. Der Schutzschirm für Ausbildung, auf den wir sehr gedrängt haben, ist hier schon genannt worden. Es ist jetzt die Stunde der Regierung. Sie muss dafür sorgen, dass es in den kommenden Ausbildungsjahren nicht zu einem weiteren Abbau der Ausbildungsplätze kommt. Frau Ministerin Karliczek, das ist natürlich die Kennzahl, an der wir alle prüfen werden, ob die Regierung hier weiterhin handlungsfähig ist.\n\nWir als SPD – das ist jedenfalls klar – kämpfen nicht nur um jeden Arbeitsplatz, sondern auch um jeden Ausbildungsplatz. Deswegen werden wir es aufmerksam und konstruktiv begleiten, wenn neue und zusätzliche Maßnahmen notwendig sind.</td>
      <td>0.348114</td>
    </tr>
    <tr>
      <th>3867</th>
      <td>ID1915110500</td>
      <td>Dr. Birke Bull-Bischoff (DIE LINKE)</td>
      <td>343</td>
      <td>Ich finde weiter: Gute Arbeit mit gutem Einkommen zu finden, hat große Integrationskraft und kann auch für die Wahlheimat ein ganz großer Gewinn sein. Kompetenzen, Wissen, Problemlösungsstrategien aus anderen kulturellen Perspektiven zu erleben, davon können auch wir lernen.\n\nDeshalb sprechen wir heute darüber, welche Chancen hierzulande den Fachkräften aus anderen Ländern eingeräumt werden, um ihre Qualifikation zu nutzen, und unter welchen Bedingungen das geschieht. Drei kritische Bemerkungen will ich machen.\n\nProblem Nummer eins – der Klassiker –: Diagnostiziert werden uns erhebliche Unterschiede in den Verfahren der einzelnen Bundesländer. Die Interessentinnen und Interessenten wissen nicht nur viel zu oft zu wenig über Hilfestellungen; offenbar gibt es viel zu wenig Personal in den zuständigen Stellen und – gelinde gesagt – überbordende gesetzliche Zeitrahmen. Die Betroffenen verlieren Monate über Monate Zeit. Die öffentliche Hand im Übrigen verliert Steuern und die Sozialversicherungen Beitragszahlungen. Ich denke, hier gibt es erheblichen Verbesserungsbedarf.\n\nProblem Nummer zwei: Interessanterweise schneiden Industrie, Handel und Handwerk bei der Frage der Anerkennung der im Ausland erworbenen Kompetenzen vergleichsweise schlecht ab, trotz des erhöhten Fachkräftebedarfs. Meine Vermutung ist, da es in vielen anderen Ländern kein vergleichbares System der beruflichen Bildung gibt, dass die Feststellung der Gleichwertigkeit deutlich komplizierter ist. Das heißt für uns, dass wir der Forderung der Europäischen Union aus dem Jahr 2012 nachkommen müssen, eine Validierungspraxis von informellen und non-formellen Kompetenzen auf rechtlich verlässliche Füße zu stellen.\n\nProblem Nummer drei: Das Anerkennungsverfahren ist für die Betroffenen nicht zuletzt auch eine Kostenfrage. Die Übersetzung der Qualifikationszeugnisse ist teuer. Ausgleichsmaßnahmen sind teuer. Ich denke, hier gehört noch eine gehörige Schippe Geld für die Förderung der Betroffenen drauf.\n\nWas ist das Fazit? Erstens. Wir brauchen sehr viel mehr Verankerung der Anerkennungspraxis im Bereich der beruflichen Bildung. Zweitens. Die Interessentinnen und Interessenten müssen bei der Finanzierung deutlich mehr unterstützt werden. Drittens. Wir brauchen mehr Personal in den zuständigen Stellen. Dann könnten im Übrigen auch die gesetzlichen Fristen deutlich verkürzt werden.\n\nWir würden dabei alle gewinnen: Wissenschaft, Wirtschaft, öffentlicher Dienst; aber auch die Betroffenen hätten schneller eine gute berufliche Perspektive. Und das, liebe Kolleginnen und Kollegen, wäre gut so.</td>
      <td>0.346315</td>
    </tr>
    <tr>
      <th>3869</th>
      <td>ID1915110700</td>
      <td>Norbert Maria Altenkamp (CDU/CSU)</td>
      <td>526</td>
      <td>Ich freue mich deshalb, dass am 1. März das neue Fachkräfteeinwanderungsgesetz in Kraft getreten ist. Damit wird die Fachkräftezuwanderung aus aller Welt an Fahrt gewinnen und helfen, unseren Fachkräftemangel zu bekämpfen.\n\nEine wesentliche Voraussetzung für die Zuwanderung ist richtigerweise, dass die Bewerber ihre Qualifikation zuvor nach dem Anerkennungsgesetz von 2012 anerkennen lassen müssen. Nur dann, wenn ihre Ausbildung und Erfahrung gleichwertig mit einer deutschen Qualifikation sind, können sie ihre Aufgaben bei uns auch wirklich erfüllen und einen Beitrag für unsere Wirtschaft leisten. Seit 2012 wurden allein für die Bundesberufe fast 141 000 Anerkennungsanträge gestellt, davon 29 000 in 2018, die meisten in den Pflegeberufen, Tendenz steigend. Bei über der Hälfte der Fälle wurden die Berufe als gleichwertig anerkannt, in fast allen anderen Fällen als teilweise gleichwertig. Zum Teil gab es Auflagen zur Nachqualifizierung, vor allem in den Gesundheitsberufen.\n\nDer Bericht zum Anerkennungsgesetz 2019 und die große Evaluation 2017 zeigen eindeutig: Das Anerkennungsgesetz ist eine Erfolgsgeschichte.\n\nZweitens. Auch Flüchtlinge mit Berufsabschluss – die meisten davon kommen aus Syrien – stellen seit 2015 immer häufiger Anerkennungsanträge. In fast allen bisher abgeschlossenen Fällen fiel die Prüfung positiv aus. Auch Qualifizierungsmaßnahmen sind bei Flüchtlingen sehr gefragt. Das erhöht erwiesenermaßen ihre Jobchancen und zeigt auch: Ihre Integration macht Fortschritte.\n\nDrittens profitieren die Arbeitgeber von den Anerkennungsverfahren, weil sie die Qualifikationen ausländischer Fachkräfte besser einschätzen und ihre Lücken gezielt füllen können. Fachkräfte mit Anerkennung, die ihren Platz im Betrieb gefunden haben, sind höchst motiviert, tragen zur Vielfalt und Kreativität im Unternehmen bei und steigern damit die Innovationskraft und die Wettbewerbsfähigkeit.\n\nWeil die Antragszahlen ständig steigen, haben wir, wie im Koalitionsvertrag vereinbart, die Angebote zu Information, Beratung und finanzieller Unterstützung im Anerkennungsverfahren immer weiter ausgebaut. Hier nur ein paar Beispiele: Der Anerkennungszuschuss entlastet seit 2016 finanzschwache Antragsteller von den Kosten für das Anerkennungsverfahren bis zur Höhe von 600 Euro. Seit Ende 2019 wird auch die Zeugnisbewertung der ZAB bei akademischen Berufen wie Lehrer und Ingenieur gefördert, und in ausgewählten Berufen wird zurzeit die Erstattung von Prüfungs- oder Kursgebühren erprobt.\n\nDie zentrale Informationsplattform „Anerkennung in Deutschland“ in elf verschiedenen Sprachen und mit 3,2 Millionen Besuchern in 2018 soll künftig noch nutzerfreundlicher werden. Das Programm IQ – Integration durch Qualifizierung – fördert Anerkennungsberatungen, Anpassungsqualifizierungen und seit 2019 auch regionale Fachkräftenetzwerke. Im Rahmen des Projekts „Unternehmen Berufsanerkennung“ informieren DIHK und ZDH seit drei Jahren gezielt über die Chancen der Berufsanerkennung in den Betrieben, um diese noch bekannter zu machen.\n\nDurch das Fachkräfteeinwanderungsgesetz werden die Anforderungen an die Anerkennungsverfahren weiter steigen, zumal diese in der Regel aus dem Ausland geführt werden müssen. Das erfordert weitere Anpassungen, die bereits in die Wege geleitet worden sind. Bestes Beispiel dafür ist die neue Zentrale Servicestelle Berufsanerkennung bei der BA für Anträge aus dem Ausland. Sie wurde am 17. Februar 2020 in Bonn eröffnet und wird interessierte Fachkräfte schnell und sicher durch die komplexen Verfahren lotsen, bis sie in Deutschland einreisen.\n\nMein Fazit: Von der Berufsanerkennung profitieren unsere Wirtschaft und unsere Gesellschaft als Ganzes. Damit das Anerkennungsgesetz seine Erfolgsgeschichte weiterschreiben kann, müssen die Verfahren künftig noch schneller, effizienter und transparenter werden. Dazu wurden bereits viele richtige Maßnahmen in die Wege geleitet. Wir im Parlament stehen bereit, dafür weitere Unterstützung zu geben.</td>
      <td>0.344196</td>
    </tr>
  </tbody>
</table><!--kg-card-end: html--><h2 id="topic-profiles-of-individual-speeches">Topic Profiles of Individual Speeches</h2><p>As our dataset consists of speech paragraphs and is thereby quite granular, we can analyze the topic structure of individual speeches. For example, the following plot shows how the topical focus of a given speech changes from one paragraph to the next. The plot is interactive, hover over a point to show the paragraph text:</p><!--kg-card-begin: markdown--><details style="min-width: 100%;">
<summary>Click to show code.</summary>
<pre><code class="language-python">def speech_topic_time_series_plot(classified_speech_paragraphs_df, speech_id):
  classified_speech_paragraphs_df[&quot;paragraph_end_word_pos&quot;] = (classified_speech_paragraphs_df
                                                               .paragraph_len_words
                                                               .cumsum())
  fig_df = classified_speech_paragraphs_df.query(f&quot;speech_id=='{speech_id}'&quot;).copy()
  fig_df[&quot;paragraph_end_word_pos&quot;] = fig_df.paragraph_len_words.cumsum()
  fig_df = (fig_df
            .iloc[:, [10, 29] + list(range(13, 26))]
            .melt(id_vars=[&quot;paragraph_end_word_pos&quot;, &quot;paragraph&quot;])
            .reset_index()
            .rename(columns={&quot;variable&quot;: &quot;topic&quot;}))
  fig_df[&quot;paragraph&quot;] = fig_df.paragraph.apply(lambda p: &quot;&lt;br&gt;&quot;.join(textwrap.wrap(p, width=70)))
  return px.line(fig_df,
                 x=&quot;paragraph_end_word_pos&quot;, y=&quot;value&quot;,
                 color=&quot;topic&quot;, color_discrete_sequence=px.colors.qualitative.Alphabet_r,
                 hover_data=[&quot;paragraph&quot;, &quot;topic&quot;, &quot;value&quot;],
                 markers=True,
                 title=f&quot;Topics of Speech {speech_id}, by Paragraph&quot;)

speech_topic_time_series_plot(classified_speech_paragraphs_df, &quot;ID1910504900&quot;)
</code></pre>
</details><!--kg-card-end: markdown--><!--kg-card-begin: html--><figure class="kg-card kg-image-card kg-width-wide">
<iframe scrolling="no" style="border: none;" seamless="seamless" src="https://blog.oliverflasch.de/uploads/speech_topics_timeline.html" height="600" width="1200"></iframe>
</figure><!--kg-card-end: html--><p>That's it for today. As you can imagine, our dataset enables many other types of analysis. Another useful endeavour would be to build a custom dashboard for retrieving speeches, speakers, or fractions by topic combination.</p>]]></content:encoded></item><item><title><![CDATA[Topics in the Bundestag (Part 1)]]></title><description><![CDATA[Who talked about what in the 19th German Bundestag? To answer this question, you'd probably need a panel of hardworking undergraduates reading and categorizing hundreds or even thousands of speeches. Maybe we can apply modern Natural Language Processing to automate this task at least partially?]]></description><link>https://blog.oliverflasch.de/topics-in-the-bundestag-part-1/</link><guid isPermaLink="false">61295611ad7d860dca810249</guid><category><![CDATA[NLP]]></category><category><![CDATA[Deep Learning]]></category><category><![CDATA[German]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[Transformers]]></category><category><![CDATA[Bundestag]]></category><dc:creator><![CDATA[Oliver Flasch]]></dc:creator><pubDate>Sun, 29 Aug 2021 13:34:18 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1572318146135-43f5879eecc0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDE1fHxidW5kZXN0YWd8ZW58MHx8fHwxNjMwMTU0MDkx&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1572318146135-43f5879eecc0?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8c2VhcmNofDE1fHxidW5kZXN0YWd8ZW58MHx8fHwxNjMwMTU0MDkx&ixlib=rb-1.2.1&q=80&w=2000" alt="Topics in the Bundestag (Part 1)"><p>Today, we will apply a Deep Learning-based language model to classify (nearly) all speeches held in the 19th German Bundestag (the German federal parliament) into the following predefined topics, as <a href="https://www.tagesschau.de/inland/btw21/programmvergleich-start-107.html">provided by the German broadcasting network ARD</a>:</p><ol><li>Außenpolitik (Foreign Policy)</li><li>Bildung (Education)</li><li>Digitalisierung (Digitalization)</li><li>Familien (Family Policy) </li><li>Gesundheitswesen (Healthcare)</li><li>Jobs</li><li>Klima (Climate Change)</li><li>Pflege (Caregiving)</li><li>Rente (Pension Schemes)</li><li>Sicherheit (Security Policy)</li><li>Steuern (Taxes)</li><li>Wohnraum (Housing)</li><li>Zuwanderung (Immigraion)</li></ol><p><strong>DISCLAIMER: The classification model we're going to use hasn't been validated for this task and can produce biased or nonsensical results. If you're planning to use this approach in a production setting, at least make sure to validate result quality by conducting a double-blind study!</strong></p><p>We had statistical topic modeling methods (such as <a href="https://en.wikipedia.org/wiki/Latent_Dirichlet_Allocation">Latent Dirichlet Allocation</a>, LDA) to help with the task of topic discovery for nearly 20 years. To help with our task of classifying speeches into topics, <a href="https://medium.com/analytics-vidhya/text-classification-using-lda-35d5b98d4f05">LDA results can be used as features for a downstream classification algorithm</a>. But at least from my limited experience, it can be quite difficult to apply such a complicated construct in practice. Furthermore, with this approach, you would need to prepare a large enough dataset of topic-labeled speeches to train the classifier. Back to hardworking undergraduates it is. By the way, you should take a look at the excellent work of <a href="https://opendiscourse.de">Open Discourse</a>, who, among other things, did an LDA-based analysis of all Bundestag-speeches since 1949.</p><p>In this post, we will try a different approach, one that will allow us to classify all speech paragraphs present in the dataset <a href="https://blog.oliverflasch.de/german-plenary-proceedings-as-a-nlp-testbed/">we compiled in my last post</a> into a given set of topics. This dataset contains, depending on when you follow this post, nearly all or all speeches given during the 19th electoral term of the German federal parliament. </p><p>The approach we'll be trying is called <strong>zero-shot learning for text classification as Natural Language Inference (NLI)</strong>. Let's take some time to unpack this. <strong>Zero-shot learning</strong> means that we'll use a model trained on an upstream task, i.e. NLI, to solve our downstream task, i.e. classifying speeches, without retraining or even finetunig the model. Zero-shot learning works without any labels for the downstream task, freeing us from compiling training data. <strong>NLI</strong> is a language classification task of the following form: Given a premise and a hypothesis, predict if the hypothesis follows from the premise (entailment), contradicts the premise (contradiction), or is unrelated to the premise (neutral). The premise "The European Emissions Trading System was the first large greenhouse gas emmissions trading scheme in the world." entails the hypothesis "This text is about climate change.", contradicts the hypothesis "This text is about rock music." and is neutral with regard to the hypothesis "Gernany is a country in Central Europe.". When you solve NLI, you solve zero-shot text classification, as you can guess from the previous example. See Joe Davisons excellent article on <a href="https://joeddav.github.io/blog/2020/05/29/ZSL.html">Zero-Shot Learning in Modern NLP</a> for details, including alternative ideas.</p><p>Putting all this theory into practice is easy, thanks to <a href="https://huggingface.co/transformers/">Hugging Face's transformers package</a> and <a href="https://huggingface.co/Sahajtomar/German_Zeroshot">Sahaj Tomar's German Zeroshot model</a>, which was finetuned on the German subset of the <a href="https://cims.nyu.edu/~sbowman/xnli/">Cross-Lingual NLI Corpus</a> and is based on Deep Set's <a href="https://huggingface.co/deepset/gbert-large">German BERT language model</a>. The latter model was trained by self-supervision on four text corpora: "OSCAR" (~145 Gigagytes), a corpus scraped from websites and filtered for explicit material, "OPUS" (~10 Gigabytes), a corpus compiled from various domains such as movie subtitles, parliament speeches and books, "Wikipedia" (~ 6 Gigabytes), a postprocessed dump of the German Wikipedia, and "Open Legal Data" (~ 2.4 Gigabytes), a dataset of German court decisions.</p><p>Exerting a few dozen lines of Python code, we'll be able to classify all of the 100,000+ speech paragraphs in <a href="https://blog.oliverflasch.de/german-plenary-proceedings-as-a-nlp-testbed/">our dataset</a> in less than eight hours of GPU compute time. You can also follow along in <a href="https://colab.research.google.com/">Google Colab</a>, which currently gives away GPU compute time for free. If you're only interested in the results of our study, feel free to skip to the second part of this series.</p><h2 id="setup-your-notebook-environment">Setup Your Notebook Environment</h2><p>Please see my last post on how to setup your notebook environment. You can use your own <a href="https://jupyter.org">JupyterLab installation</a> or a cloud service such as <a href="https://aws.amazon.com/de/sagemaker/">Amazon Sagemaker</a> or <a href="https://colab.research.google.com/">Google Colab</a>. When using Google Colab, make sure to change the runtime type to "GPU" before proceeding.</p><h2 id="install-and-import-dependencies">Install and Import Dependencies</h2><p>If you're using Google Colab, you'll first have to install Hugging Face's transformers library by issuing the following command in a notebook cell:</p><pre><code class="language-Python">!pip install transformers</code></pre><p>This library implements a wide range of modern Deep Learning-based NLP techniques. To train and infer deep neural networks, it depends on either TensorFlow or PyTorch. Next, you should check if your GPU is ready to use by issuing:</p><pre><code class="language-Python">!nvidia-smi</code></pre><p>You will need a GPU with at least eight Gigabytes of memory. Depending on the GPU used, classifying our dataset will require different ammounts of time. On a NVIDIA Tesla P100, the process will finish in less than eight hours. </p><p>We will need to import the following Python libraries used in this project:  <code>numpy</code> is Python's de facto standard library for numerical computing. The <code>os</code> package is needed to manipulate (temporary) files, <code>pandas</code> provides a dataframe abstraction for (kind of) conveniently working with largish tabular datasets, <code>torch</code> is PyTorch, the machine learning framework powering our deep language models, <code>tqdm</code> for showing progress bars, and finally <code>transformers</code>, a library implementing several modern NLP methods.</p><pre><code class="language-Python">import numpy as np
import os
import pandas as pd
import torch
from tqdm.notebook import tqdm
from transformers import pipeline</code></pre><h2 id="load-and-prepare-the-plenary-proceedings-dataset">Load and Prepare the Plenary Proceedings Dataset</h2><p>We will now load our dataset of speeches as prepared in <a href="https://blog.oliverflasch.de/german-plenary-proceedings-as-a-nlp-testbed/">my previous post</a>. In Google Colab, we'll have to mount our Google Drive, where we stored the dataset, first:</p><pre><code class="language-Python">from google.colab import drive
drive.mount("/content/gdrive")</code></pre><p>Next, we'll load the dataset into the Pandas dataframe <code>speech_paragraphs_df</code> and remove all speech paragraphs with less than 11 words, as these are too short for meaningful classification:</p><pre><code class="language-Python">speech_paragraphs_df = pd.read_parquet("/content/gdrive/My Drive/Colab Notebooks/bundestag_plenary_protocols/parquet/bundestag_plenary_protocols_term_19.parquet")
speech_paragraphs_df = speech_paragraphs_df[speech_paragraphs_df.paragraph_len_words &gt;= 10]

print(f"Number of paragraphs:\t\t{len(speech_paragraphs_df)}")
print(f"Median words per paragraph:\t{speech_paragraphs_df.paragraph_len_words.median()}")
print(f"Mean words per paragraph:\t{speech_paragraphs_df.paragraph_len_words.mean()}")</code></pre><p>At the time of writing, there are 115,612 speech paragraphs in this dataset, with a median (mean) length of 54 (58.27) words.</p><h2 id="define-the-classifier-pipeline">Define the Classifier Pipeline</h2><p>We can now prepare our zero-shot text classification pipeline, which entails downloading the models from Hugging Face's servers. Note that at least the base model (DeepSet's "GBERT large") is available under the permissive MIT license:</p><pre><code class="language-Python">classifier = pipeline("zero-shot-classification",
                      model="Sahajtomar/German_Zeroshot",
                      device=0) # use GPU</code></pre><p>With the classifier pipeline in place, we will define our "hypothesis" template and our labels. Our pipeline will merge the hypothesis template and the labels to yield NLI-hypotheses like "In diesem Text geht es um Außenpolitik.", as described above:</p><pre><code class="language-Python">hypothesis_template = "In diesem Text geht es um {}."
labels = ["Außenpolitik",
          "Bildung",
          "Digitalisierung",
          "Jobs",
          "Familien",
          "Gesundheitswesen",
          "Klima",
          "Pflege",
          "Rente",
          "Sicherheit",
          "Steuern",
          "Wohnraum",
          "Zuwanderung"]</code></pre><p>Next, we'll write some code to classify every speech paragraph in our dataset. This code is complicated by that fact that we will work in batches to speed up processing, and that we will save a temporary file of intermediate results as a backup:</p><pre><code class="language-Python">def classify(speech,
             labels=labels,
             hypothesis_template=hypothesis_template,
             multi_label=True):
  results = classifier(speech, labels, 
                       hypothesis_template=hypothesis_template,
                       multi_label=multi_label)
  return results

def classify_to_df(paragraphs,
                   labels=labels,
                   hypothesis_template=hypothesis_template,
                   multi_label=True):
  classify_results = classify(paragraphs,
                              labels=labels,
                              hypothesis_template=hypothesis_template,
                              multi_label=multi_label)
  classify_results = [classify_results] if not isinstance(classify_results, list) else classify_results # ensure result is a list
  scores_dict = [dict(sorted(list(zip(r["labels"], r["scores"])))) for r in classify_results]
  scores_df = pd.DataFrame(scores_dict)
  return scores_df

def classify_df_batch(df_batch,
                      labels=labels,
                      hypothesis_template=hypothesis_template,
                      multi_label=True):
  paragraphs = df_batch.paragraph.to_list()
  scores_df = classify_to_df(paragraphs, labels=labels, hypothesis_template=hypothesis_template)
  scores_df.index = df_batch.index # align indices
  scored_df_batch = pd.concat([df_batch, scores_df], axis=1)
  return scored_df_batch

def classify_df(df,
                labels=labels,
                hypothesis_template=hypothesis_template,
                multi_label=True,
                batch_size=20,
                tmp_file="/content/gdrive/MyDrive/Colab Notebooks/tmp_file.csv"):
  work_df = df
  result_df = pd.DataFrame()
  if os.path.isfile(tmp_file):
    incomplete_df = pd.read_csv(tmp_file)
    incomplete_df.date = pd.to_datetime(incomplete_df.date, format="%Y-%m-%d", errors="coerce")
    work_df = work_df[~work_df.id.isin(incomplete_df.id)].dropna()
  for g, df_batch in tqdm(work_df.groupby(np.arange(len(work_df)) // batch_size)):
    result_df_batch = classify_df_batch(df_batch,
                                        labels=labels,
                                        hypothesis_template=hypothesis_template,
                                        multi_label=multi_label)
    result_df_batch.to_csv(tmp_file, index=False, header=(g == 0), mode="a")
    result_df = result_df.append(result_df_batch)
  os.remove(tmp_file)
  return result_df</code></pre><h2 id="run-the-classifier-pipeline-and-save-results">Run the Classifier Pipeline and Save Results</h2><p>We are now ready to classify our dataset. Depending on your computer or cloud service, this will take several hours to a few days. If the process is interrupted for some reason, just re-run it, as our code should pickup the temporary result file and continue where it left off. After the classification is finished, we save our result as an <a href="https://parquet.apache.org">Apache Parquet</a> file for later analysis:</p><pre><code class="language-Python">%%time
classified_speech_paragraphs_df = classify_df(speech_paragraphs_df,
                                              labels=labels,
                                              multi_label=True)
classified_speech_paragraphs_df.to_parquet("/content/gdrive/My Drive/Colab Notebooks/bundestag_plenary_protocols_with_multiclass_topics_term_19.parquet")</code></pre><p>Note that by setting the <code>multi_label</code> parameter to <code>True</code>, the classifier pipeline considers the labels as independent, i.e. a speech paragraph can be about multiple topics. Predicted probabilities are normalized for each candidate label by doing a softmax of the NLI entailment score versus the NLI contradiction score. This gives us, for each of our 13 labels, an estimate of the probability that a given speech paragraph is about the topic described by the label.</p><p>Finally, we print a sample of our model predictions:</p><pre><code class="language-Python">pd.set_option("display.max_colwidth", 250)
classified_speech_paragraphs_df.sample(8).iloc[:, [1, 7, 10] + list(range(13, 26))]</code></pre><!--kg-card-begin: html--><table border="0" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>date</th>
      <th>speaker_full_name</th>
      <th>paragraph</th>
      <th>Außenpolitik</th>
      <th>Bildung</th>
      <th>Digitalisierung</th>
      <th>Familien</th>
      <th>Gesundheitswesen</th>
      <th>Jobs</th>
      <th>Klima</th>
      <th>Pflege</th>
      <th>Rente</th>
      <th>Sicherheit</th>
      <th>Steuern</th>
      <th>Wohnraum</th>
      <th>Zuwanderung</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>491</th>
      <td>2021-05-06</td>
      <td>Christine Aschenberg-Dugnus (FDP)</td>
      <td>Damit sie sich nicht im Versorgungswirrwarr verirren, brauchen sie geeignete Ansprechpartner. Für Patientinnen und Patienten ist es oft sehr schwierig, den bestmöglichen Versorgungspfad zu finden; denn die Symptome von Long Covid sind unspezifisc...</td>
      <td>0.002154</td>
      <td>0.001999</td>
      <td>0.459230</td>
      <td>0.131266</td>
      <td>0.896095</td>
      <td>0.006705</td>
      <td>0.016970</td>
      <td>0.568585</td>
      <td>0.038141</td>
      <td>0.303992</td>
      <td>0.005355</td>
      <td>0.020504</td>
      <td>0.014352</td>
    </tr>
    <tr>
      <th>5</th>
      <td>2019-12-12</td>
      <td>Jürgen Braun (AfD)</td>
      <td>Einseitigkeit zugunsten des Islam, Blindheit gegenüber dem islamischen Fundamentalismus, kein Gespür für die persönliche Freiheit der Bürger: Der Bericht der Bundesregierung zur Menschenrechtspolitik ist, um es auf einen Punkt zu bringen, ein Dok...</td>
      <td>0.039055</td>
      <td>0.000737</td>
      <td>0.000818</td>
      <td>0.007973</td>
      <td>0.000418</td>
      <td>0.000512</td>
      <td>0.000447</td>
      <td>0.001041</td>
      <td>0.000855</td>
      <td>0.003408</td>
      <td>0.000457</td>
      <td>0.002898</td>
      <td>0.013067</td>
    </tr>
    <tr>
      <th>147</th>
      <td>2020-10-08</td>
      <td>Linda Teuteberg (FDP)</td>
      <td>Aller guten Dinge sind drei: Für ein modernes Einwanderungsrecht braucht es auch moderne Behörden. Die Visastellen vergeben längst nicht genügend Termine, damit qualifizierte Menschen zügig ihre Anträge stellen können. Wir brauchen außerdem Lotse...</td>
      <td>0.029913</td>
      <td>0.638526</td>
      <td>0.635045</td>
      <td>0.047879</td>
      <td>0.001336</td>
      <td>0.628733</td>
      <td>0.011468</td>
      <td>0.006697</td>
      <td>0.014245</td>
      <td>0.045025</td>
      <td>0.001524</td>
      <td>0.027864</td>
      <td>0.962281</td>
    </tr>
    <tr>
      <th>90</th>
      <td>2021-03-24</td>
      <td>Frank Schwabe (SPD)</td>
      <td>Der aktuelle Fall ist genannt worden: Ömer Faruk Gergerlioglu, der als Menschenrechtler und als Abgeordneter anerkannt ist, soll wegen eines Tweets für zweieinhalb Jahre ins Gefängnis.</td>
      <td>0.053857</td>
      <td>0.000553</td>
      <td>0.322427</td>
      <td>0.177035</td>
      <td>0.000486</td>
      <td>0.001232</td>
      <td>0.050058</td>
      <td>0.001185</td>
      <td>0.002257</td>
      <td>0.425522</td>
      <td>0.001775</td>
      <td>0.166230</td>
      <td>0.349176</td>
    </tr>
    <tr>
      <th>673</th>
      <td>2021-05-06</td>
      <td>Dr. Konstantin von Notz (BÜNDNIS&nbsp;90/DIE GRÜNEN)</td>
      <td>Bedanken möchte ich mich bei den Kolleginnen und Kollegen von FDP und Linken – ich finde es gut, dass wir das so gut zusammen hinbekommen haben –, insbesondere beim Kollegen Stefan Ruppert, beim Kollegen Benjamin Strasser und bei der Kollegin Buc...</td>
      <td>0.589526</td>
      <td>0.499660</td>
      <td>0.660624</td>
      <td>0.587036</td>
      <td>0.373693</td>
      <td>0.517687</td>
      <td>0.625037</td>
      <td>0.653395</td>
      <td>0.544435</td>
      <td>0.286163</td>
      <td>0.589484</td>
      <td>0.683710</td>
      <td>0.688290</td>
    </tr>
    <tr>
      <th>150</th>
      <td>2018-06-14</td>
      <td>Siemtje Möller (SPD)</td>
      <td>Ich kann hier also festhalten: Unsere Soldatinnen und Soldaten, unsere Marine macht vor Ort einen hervorragenden Job in einem klar abgegrenzten, rundherum europäischen Mandat. Liebe Kolleginnen und Kollegen, lassen Sie uns diesen sinnvollen Einsa...</td>
      <td>0.227165</td>
      <td>0.002314</td>
      <td>0.301667</td>
      <td>0.018452</td>
      <td>0.003619</td>
      <td>0.969009</td>
      <td>0.317800</td>
      <td>0.428302</td>
      <td>0.244927</td>
      <td>0.955501</td>
      <td>0.016070</td>
      <td>0.030430</td>
      <td>0.033591</td>
    </tr>
    <tr>
      <th>68</th>
      <td>2020-11-05</td>
      <td>René Röspel (SPD)</td>
      <td>Zum Schluss darf ich mich ganz herzlich bei unseren Sachverständigen Lena-Sophie Müller, Jan Kuhlen, Sami Haddadin und Lothar Schröder bedanken. Wir haben richtig viel gelernt, toll diskutiert. Ich hoffe, dass sie uns auf dem Weg der Umsetzung vo...</td>
      <td>0.057079</td>
      <td>0.168211</td>
      <td>0.900108</td>
      <td>0.059311</td>
      <td>0.003292</td>
      <td>0.162420</td>
      <td>0.044387</td>
      <td>0.030843</td>
      <td>0.018963</td>
      <td>0.069954</td>
      <td>0.013431</td>
      <td>0.466886</td>
      <td>0.112802</td>
    </tr>
    <tr>
      <th>83</th>
      <td>2018-03-15</td>
      <td>Agnieszka Brugger (BÜNDNIS 90/DIE GRÜNEN)</td>
      <td>Was aber aus meiner Sicht gar nicht geht – Herr Grosse-Brömer hat uns ja gerade Exzellenz versprochen –, ist, dass die Bundesregierung bezüglich der Frage von Abschiebungen und der Bewertung der Sicherheitslage in Afghanistan darauf verweist, das...</td>
      <td>0.876693</td>
      <td>0.093432</td>
      <td>0.048144</td>
      <td>0.285236</td>
      <td>0.007484</td>
      <td>0.064532</td>
      <td>0.029879</td>
      <td>0.035582</td>
      <td>0.045826</td>
      <td>0.934408</td>
      <td>0.009593</td>
      <td>0.063168</td>
      <td>0.442256</td>
    </tr>
  </tbody>
</table><!--kg-card-end: html--><p>That's it for today. In the next installment of this series, we will proceed with an exploratory data analysis of our dataset of "classified" speech paragraphs. </p><p></p>]]></content:encoded></item><item><title><![CDATA[German Plenary Proceedings as an NLP Testbed]]></title><description><![CDATA[With the 19th electoral term of the German federal parliament drawing to a close, it could be interesting to do some data analysis on what has been said there. In this post, we will download the relevant plenary proceedings and assemble them into a clean dataset.]]></description><link>https://blog.oliverflasch.de/german-plenary-proceedings-as-a-nlp-testbed/</link><guid isPermaLink="false">605094a4c174530a0565ad01</guid><category><![CDATA[Data Science]]></category><category><![CDATA[NLP]]></category><category><![CDATA[German]]></category><dc:creator><![CDATA[Oliver Flasch]]></dc:creator><pubDate>Tue, 16 Mar 2021 10:46:48 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1500043204644-768d20653f32?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDEzNXx8JTIyTHVjYXMlMjBCZW5qYW1pbiUyMiUyMGFic3RyYWN0fGVufDB8fHx8MTYxNTg5NDc3OA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1500043204644-768d20653f32?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8c2VhcmNofDEzNXx8JTIyTHVjYXMlMjBCZW5qYW1pbiUyMiUyMGFic3RyYWN0fGVufDB8fHx8MTYxNTg5NDc3OA&ixlib=rb-1.2.1&q=80&w=2000" alt="German Plenary Proceedings as an NLP Testbed"><p>With the 19th electoral term of the German federal parliament ("Bundestag" in German) drawing to a close, it could be interesting to do some data analysis on what has been said there. Fortunately, for the first time in this electoral term, the Bundestag makes all speeches available in an XML format, complete with speaker metadata.</p><p>In this article, we will download this data and preprocess it into a dataframe for easy analysis. Please note the rights of use of this dataset as published at <a href="https://www.bundestag.de/services/impressum" rel="nofollow">https://www.bundestag.de/services/impressum</a>.</p><h2 id="setup-your-notebook-environment">Setup your Notebook Environment</h2><p><a href="https://colab.research.google.com">Google Colaboratory</a> (Colab) on the <a href="https://cloud.google.com">Google Cloud Platform</a> offers a JupyterLab-like computational notebook environment with all Python libraries needed for this project preinstalled, offering maximal convenience. On the other hand, you should be aware of the privacy- and Quality-of-Service implications of using a free Google service.</p><p>If you'll be using your own infrastructure, you'll need to install Python and all required Python libraries. I'd recommend to also install <a href="https://jupyter.org/install.html">JupyterLab</a> or an alternative computational notebook environment. Many people prefer computational notebooks over IDEs for explorative data science work.</p><h2 id="import-python-dependencies">Import Python Dependencies</h2><p>To start, we will import some Python dependencies. The <code>glob</code> module is used to find all files matching a given name pattern in a given directory, while <code>os</code> helps with manipulating filenames. <code>google.colab.drive</code> will allow us to mount our google drive for permanently storing our retrieved dataset. <code>lxml</code> is an XML parser, <code>urllib3</code> is used to download files over HTTP. <code>numpy</code> and <code>pandas</code> are used to build our dataset.</p><pre><code class="language-Python">from datetime import datetime
import glob
from google.colab import drive
import lxml
import lxml.html
import numpy as np
import os
import pandas as pd
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
import urllib.request</code></pre><h2 id="mount-google-drive">Mount Google Drive</h2><p>Next, we will "mount" our google drive, so that we can save files to it programmatically from this notebook. We will save temporary files that we download, as well as our completed dataset. After executing the following code cell, click on the link displayed as output, allow access and copy the access token, then paste the access token into the textbox displayed as output.</p><pre><code class="language-Python">drive.mount('/content/gdrive')</code></pre><h2 id="download-plenary-proceedings-as-xml-files">Download Plenary Proceedings as XML Files</h2><p>Before downloading, we will first prepare directories on our Google Drive to save our data to, then we will download the XML schema (document type definition - DTD) of the plenary proceedings:</p><pre><code class="language-Bash">%%shell

PARQUET_DATA_DIR="/content/gdrive/My Drive/Colab Notebooks/bundestag_plenary_protocols/parquet"
XML_DATA_DIR="/content/gdrive/My Drive/Colab Notebooks/bundestag_plenary_protocols/xml"

mkdir -p "$PARQUET_DATA_DIR"
mkdir -p "$XML_DATA_DIR"
cd "$XML_DATA_DIR"

# download XML DTD for plenary protocolls of the 19th term if it do not exist on google drive...
if [ ! -f "dbtplenarprotokoll-data.dtd" ]; then
    wget -N https://www.bundestag.de/resource/blob/575720/22c416420e8a51c380d2ddffb19ff5b7/dbtplenarprotokoll-data.dtd
fi</code></pre><p>We are now ready to download the raw plenary proceeding XML files:</p><pre><code class="language-Python">def download_plenary_protocols(to_path):
  http = urllib3.PoolManager() 
  offset = 0
  count = 0
  while True:
    response = http.request("GET", f"https://www.bundestag.de/ajax/filterlist/de/services/opendata/543410-543410?noFilterSet=true&amp;offset={offset}")
    parsed = lxml.html.fromstring(response.data)
    empty = True
    for link in parsed.getiterator(tag="a"):
      empty = False
      link_href = link.attrib["href"]
      count += 1
      filename = to_path + "/" + os.path.basename(link_href)
      file_url = "https://www.bundestag.de" + link_href
      print(f"downloading URL '{file_url}'")
      urllib.request.urlretrieve(file_url, filename)
    if empty: break
    offset += 10
  print(f"downloaded {count} XML files")

download_plenary_protocols("/content/gdrive/My Drive/Colab Notebooks/bundestag_plenary_protocols/xml")</code></pre><h2 id="data-preprocessing-and-cleanup">Data Preprocessing and Cleanup</h2><p>Next, we will define a function to extract speeches from a given plenary proceedings XML file. This function will also extract/calculate metadata for each speech:</p><pre><code class="language-Python">def extract_speeches_from_xml(xml, only_J_paragraphs=True):
  speech_paragraphs_dict = {
      "id": [],
      "date": [],
      "speaker_id": [],
      "speaker_title": [],
      "speaker_first_name": [],
      "speaker_last_name": [],
      "speaker_fraction": [],
      "speaker_full_name": [],
      "speech_id": [],
      "paragraph_num": [],
      "paragraph": [],
      "paragraph_len_chars": [],
      "paragraph_len_words": []
  }
  def first_or_empty_string(a): return a[0] if a else ""
  session_date_string = xml.xpath("/dbtplenarprotokoll/@sitzung-datum")[0]
  session_date = datetime.strptime(session_date_string, "%d.%m.%Y\)
  speeches_with_one_speaker = xml.xpath("//sitzungsverlauf//rede[count(p/redner)=1]")
  for speech in speeches_with_one_speaker:
    speaker_id = speech.xpath("p/redner/@id")[0]
    speech_id = speech.xpath("@id")[0]
    speaker_title = first_or_empty_string(speech.xpath("p/redner/name/titel/text()"))
    speaker_first_name = first_or_empty_string(speech.xpath("p/redner/name/vorname/text()"))
    speaker_last_name = first_or_empty_string(speech.xpath("p/redner/name/nachname/text()"))
    speaker_fraction = first_or_empty_string(speech.xpath("p/redner/name/fraktion/text()"))
    speaker_full_name = speaker_title + (" " if speaker_title != "" else "") + speaker_first_name + " " + speaker_last_name + " (" + speaker_fraction + ")"
    #print(f"{speaker_id} {speaker_first_name} {speaker_last_name}:")
    paragraphs = speech.xpath("p[@klasse='J']/text()") if only_J_paragraphs else speech.xpath("p[@klasse!='redner']/text()")
    for paragraph_num, paragraph in enumerate(speech.xpath("p[@klasse='J']/text()")):
      id = speech_id + "_" + str(paragraph_num)
      speech_paragraphs_dict["id"].append(id)

speech_paragraphs_dict["date"].append(session_date)
      speech_paragraphs_dict["speaker_id"].append(speaker_id)
      speech_paragraphs_dict["speaker_title"].append(speaker_title)
      speech_paragraphs_dict["speaker_first_name"].append(speaker_first_name)
      speech_paragraphs_dict["speaker_last_name"].append(speaker_last_name)
      speech_paragraphs_dict["speaker_fraction"].append(speaker_fraction)
      speech_paragraphs_dict["speaker_full_name"].append(speaker_full_name)
      speech_paragraphs_dict["speech_id"].append(speech_id)
      speech_paragraphs_dict["paragraph_num"].append(paragraph_num)
      speech_paragraphs_dict["paragraph"].append(paragraph)
      speech_paragraphs_dict["paragraph_len_chars"].append(len(paragraph))
      speech_paragraphs_dict["paragraph_len_words"].append(len(paragraph.split()))
      #print(f"{id}: {paragraph}")
  return speech_paragraphs_dict</code></pre><p>We are now ready to process every XML file into the preliminary dataframe of speeches <code>speech_paragraph_df</code>:</p><pre><code class="language-Python">from tqdm.notebook import tqdm # progress bar

xml_files = glob.glob("/content/gdrive/My Drive/Colab Notebooks/bundestag_plenary_protocols/xml/*.xml")

speech_paragraphs_df = None
for xml_file in tqdm(xml_files):
  xml = lxml.etree.parse(xml_file)
  df = pd.DataFrame.from_dict(extract_speeches_from_xml(xml))
  speech_paragraphs_df = df if speech_paragraphs_df is None else speech_paragraphs_df.append(df)</code></pre><p>This dataframe represents a table of every paragraph of every speech delivered by a speaker of a known fraction, including metadata such as speaker name, speaker fraction, and paragraph length in words.</p><p>We will now cleanup the <code>speaker_fraction</code> column. We will then remove speakers without fraction, as there are some data quality problems with these. We will need to examine these problems at some later point in time.</p><pre><code class="language-Python"># data cleanup
speech_paragraphs_df.loc[speech_paragraphs_df.speaker_fraction == "Fraktionslos", "speaker_fraction"] = "fraktionslos"
speech_paragraphs_df.loc[speech_paragraphs_df.speaker_fraction.str.startswith("BÜNDNIS"), "speaker_fraction"] = "BÜNDNIS 90/DIE GRÜNEN"

# only keep speech paragraphs of speaker's with known fraction
speech_paragraphs_df = speech_paragraphs_df[speech_paragraphs_df.speaker_fraction.isin(["CDU/CSU", "SPD", "AfD", "FDP", "DIE LINKE", "BÜNDNIS 90/DIE GRÜNEN"])]</code></pre><p>We can now take a look at our finished dataset:</p><pre><code class="language-Python">speech_paragraphs_df</code></pre><p>Finally, we save our finished dataset to our Google Drive for future analysis:</p><pre><code class="language-Python">speech_paragraphs_df.to_parquet("/content/gdrive/My Drive/Colab Notebooks/bundestag_plenary_protocols/parquet/bundestag_plenary_protocols_term_19.parquet")</code></pre><p>To load this dataset back into memory at a later date, we would use the following code:</p><pre><code class="language-Python">speech_paragraphs_df = pd.read_parquet("/content/gdrive/My Drive/Colab Notebooks/bundestag_plenary_protocols/parquet/bundestag_plenary_protocols_term_19.parquet")</code></pre><p>While we have our finished dataset available in memory, lets perform a simple example analysis, tabulating the number of speech paragraphs by speaker fraction:</p><pre><code class="language-Python">speech_paragraphs_df.speaker_fraction.value_counts()</code></pre><pre><code>CDU/CSU                  34349
SPD                      22774
AfD                      17829
FDP                      14677
DIE LINKE                13674
BÜNDNIS 90/DIE GRÜNEN    12794
Name: speaker_fraction, dtype: int64</code></pre><p>Finally, we can transform our dataset of speech paragraphs to a dataset of speeches via the following Pandas incantation:</p><pre><code class="language-Python">speeches_df = speech_paragraphs_df\
  .groupby("speech_id")\
  .agg({
    "speaker_id": "first",
    "speaker_title": "first",
    "speaker_first_name": "first",
    "speaker_last_name": "first",
    "speaker_fraction": "first",
    "speaker_full_name": "first",
    "speech_id": "first",
    "paragraph": " ".join,
    "paragraph_len_chars": "sum",
    "paragraph_len_words": "sum"})</code></pre><p>That's about it. In my next post, we'll categorize all speech paragraphs into a set of predefined topics.</p>]]></content:encoded></item><item><title><![CDATA[Deep Learning German]]></title><description><![CDATA[How to auto-complete your thoughts with a modern deep learning model, in German!]]></description><link>https://blog.oliverflasch.de/german-gpt2/</link><guid isPermaLink="false">5f495e685d1e5634b3023212</guid><category><![CDATA[NLP]]></category><category><![CDATA[Deep Learning]]></category><category><![CDATA[German]]></category><category><![CDATA[GPT-2]]></category><category><![CDATA[Transformers]]></category><category><![CDATA[PyTorch]]></category><category><![CDATA[Google Cloud Platform]]></category><dc:creator><![CDATA[Oliver Flasch]]></dc:creator><pubDate>Wed, 23 Sep 2020 15:30:09 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1541701494587-cb58502866ab?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1541701494587-cb58502866ab?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=2000&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ" alt="Deep Learning German"><p>Remember the frenzy OpenAI caused last year with it's <a href="https://openai.com/blog/better-language-models/">announcement</a> not to publish the largest variant of it's latest language model GPT-2? Because of concerns about it being <em>used to generate deceptive, biased, or abusive language at scale?</em> Of course, <a href="https://transformer.huggingface.co">other parties</a> quickly trained their own versions, proving these concerns to be largely unfounded. But <a href="https://arxiv.org/abs/2005.14165">GPT-3</a>, OpenAI's newest iteration of basically the same model architecture, but scaled up a hundredfold, shows that we're quickly entering <a href="https://www.spiegel.de/netzwelt/web/gpt-3-die-eloquenteste-kuenstliche-intelligenz-der-welt-a-dd3b3423-d214-4a2f-bc51-d51a2ae22074">dangerous</a> (<a href="https://www.gwern.net/GPT-3">and</a> <a href="https://gpt3experiments.substack.com">fascinating</a>) territory.</p><p>In this blog, we're going to implement a small web app that uses a <strong>German GPT-2 model</strong> to auto-complete your sentences. We'll be using free GPU instances available in <a href="https://colab.research.google.com">Google Colaboratory</a>, so that you can follow along quickly, without expensive hardware or complicated setup. Of course, you can also use your local <a href="https://pytorch.org">PyTorch</a> installation with <a href="https://jupyter.org">JupyterLab</a>, which should give you exactly the same results.</p><h2 id="but-first-some-theory">But First, Some Theory</h2><ul><li>The "GPT" in GPT-2 stands for "Generative Pretrained Transformer", meaning, given a fixed window of text preprocessed into a fixed-size vocabulary of tokens (normalized pieces of words/sentences), the model will predict the probabilities of each token being the next one. To express this in simpler terms without being totally inaccurate: Given some words as a "prompt", GPT-2 will predict probable next words. By applying GPT-2 to it's own output, you can generate arbitrary long texts.</li><li>Therefore, GPT-2 can be trained on large unlabeled text corpora in an unsupervised fashion, making training relatively simple and cheap. This is, if you can provide the necessary compute resources.</li><li>OpenAI trained the orignal variant of this model on 8 million documents scraped from good quality Reddit submissions, for a total of 40 GB of text.</li><li>As a neural network, GTP-2 can be <a href="https://www.gwern.net/newsletter/2020/05#gpt-3">modified or plugged into</a> a larger network to solve many different tasks, even <a href="https://openai.com/blog/image-gpt/">across media boundaries</a>. Neural networks are a kind of <a href="https://blog.cerebralab.com/Neural_networks_as_non-leaky_mathematical_abstraction">non-leaky mathematical abstraction</a>.</li><li>See <a href="https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf">OpenAI's original GPT-2 Paper</a> for the details, or <a href="http://jalammar.github.io/illustrated-gpt2/">The Illustrated GPT-2</a>, Jay Alammar's excellent exposition.‌</li><li>Thilina Rajapakse's <a href="https://simpletransformers.ai">Simple Transformers</a> library makes it <em>very simple</em> to apply GPT-2 and other transformer models to a wide variety of NLP tasks.</li></ul><h2 id="set-up-your-notebook-environment">Set Up Your Notebook Environment</h2><p>We will use <a href="https://pytorch.org">PyTorch</a> as a modern machine learning framework. PyTorch is a good fit for our task, as it offers a modern readable (eager, imperative) API, combined with very good performance and a rapidly growing community.</p><p>A simple way to get startet with Deep Learning-based NLP is by using <a href="https://colab.research.google.com">Google Colaboratory</a> (Colab) on the <a href="https://cloud.google.com">Google Cloud Platform</a>. Colab offers a JupyterLab-like computational notebook environment with PyTorch already preinstalled. As of August 2020, Colab offers you free GPU instances, which is great for compute-intensive tasks like GPT-2 training and -inference. On the other hand, you should be aware of the privacy- and QoS implications of using a free Google service.</p><p>If you'll be using your own infrastructure, you'll need to install PyTorch and configure it to use your GPU (if applicable). I'd recommend to also install <a href="https://jupyter.org/install.html">JupyterLab</a> or an alternative computational notebook environment. Many people prefer computational notebooks over IDEs for explorative data science work.</p><h2 id="prepare-pytorch">Prepare PyTorch</h2><p>Open your a new notebook in your notebook environment of choice, import both PyTorch  and Numpy, and check if PyTorch supports your GPU(s). When running in Colab, you will need to enable GPU support by selecting "Change runtime type" in the  "Runtime" menu. Then select "GPU"  in the "Hardware accelerator" dropdown menu. Colab might assign you a different GPU model each time your open your notebook and start the runtime.</p><pre><code class="language-python">import torch
import numpy as np

print(f"Detected {torch.cuda.device_count()} PyTorch-compatbile GPU devices.")
print(f"Name of first GPU device: {torch.cuda.get_device_name(0)}")</code></pre><p>This should print the number of  GPUs detected by PyTorch and the model name of the first GPU detected.</p><p>If you're running in Colab, you should mount your Google Drive (which provides you with free permantent storage space) to store the required software and GPT-2 model parameters:</p><pre><code class="language-python">from google.colab import drive

drive.mount('/content/gdrive')</code></pre><p>When running these lines, Colab will provide you with on-screen instructions on how to enable access to your Google Drive contents from your Colab notebook.</p><p>If you're running this for the first time, clone the <code>transformer-lm</code> Python package from GitHub:</p><pre><code class="language-bash">%%shell
cd /content/gdrive/My\ Drive/Colab\ Notebooks
git clone https://github.com/lopuhin/transformer-lm.git</code></pre><p>Next, install the requirements of the <code>transformer-lm</code> package to your local notebook environment:</p><pre><code class="language-bash">%%shell
cd /content/gdrive/My\ Drive/Colab\ Notebooks/transformer-lm/
pip install -r requirements.txt</code></pre><p>Then add <code>transformer-lm</code> to your Python package path:</p><pre><code class="language-python">import sys
sys.path.append('/content/gdrive/My Drive/Colab Notebooks/transformer-lm/')</code></pre><h2 id="download-zamia-ai-s-german-gpt-2-model">Download Zamia AI's German GPT-2 Model</h2><p>If you're running this for the first time, download and extract the pretrained GPT-2 model from <a href="http://zamia-speech.org/brain/">Zamia AI</a>. Otherwise, <strong>please skip this step</strong> to preserve Zamia AI's bandwidth!</p><pre><code class="language-bash">%%shell
# only do this once to preserve bandwith!
cd /content/gdrive/My\ Drive/Colab\ Notebooks
mkdir pytorch_models
cd pytorch_models
wget https://goofy.zamia.org/zamia-speech/brain/gpt2-german-345M-r20191119.tar.xz
wget https://goofy.zamia.org/zamia-speech/brain/sp.vocab tar xvf gpt2-german-345M-r20191119.tar.xz rm gpt2-german-345M-r20191119.tar.xz
ls -lha</code></pre><p>Now you should be able to load the German GPT-2 model into memory. We will be using <a href="https://github.com/lopuhin">Konstantin Lopuhin</a>'s <code>transformer-lm</code> language model wrapper:</p><pre><code class="language-python">import lm.inference as lmi
from pathlib import Path

mw = lmi.ModelWrapper.load(Path("/content/gdrive/My Drive/Colab Notebooks/pytorch_models/de345-root/"))</code></pre><h2 id="make-predictions">Make Predictions</h2><p>Next, we'll define functions to infer new tokens from our model:</p><pre><code class="language-python">def gpt2_gen(mw, prefix, n_tokens_to_generate=10, top_k=8):
  prefix_tokens = mw.tokenize(prefix)
  generated_tokens = mw.generate_tokens(prefix_tokens, n_tokens_to_generate, top_k)
  generated_text = mw.sp_model.DecodePieces(generated_tokens)
  return generated_text

def gpt2_gen_until(mw, prefix, stop_token='.', top_k=8):
  prefix_tokens = mw.tokenize(prefix)
  generated_tokens = list(prefix_tokens) # generate until the stop_token is seen
  next_token = ''
  while next_token != stop_token:
    # generate TOP_K potential next tokens
    ntk = mw.get_next_top_k(generated_tokens, top_k)
    # convert log probs to real probs
    logprobs = np.array(list(map(lambda a: a[0], ntk)))
    probs = np.exp(logprobs) / np.exp(logprobs).sum()
    # pick next token randomly according to probs distribution
    next_token_n = np.random.choice(top_k, p=probs)
    next_token = ntk[next_token_n][1]
    # append next token
    generated_tokens.append(next_token)
    print(mw.sp_model.DecodePieces(generated_tokens)) # DEBUG output
  # decode tokens and return generated text
  generated_text = mw.sp_model.DecodePieces(generated_tokens)
  return generated_text</code></pre><p>The function <code>gpt2_gen</code> takes a <code>prefix</code> string or "prompt" from which to generate a fixed number of tokens by iteratively applying the model to its own output. At each step, the model will generate a probability of each possible token as its output. If we would always choose the token with highest predicted probability, the model would generate very static and boring text, often getting stuck in loops. Instead, to choose the token to predict, we use <strong>top k sampling</strong>: First, we sort the tokens by probability, then zero-out the probabilities for tokens below the <code>top_k</code>th token and sample from the remaining distribution. This appears to improve percieved quality of the generated text by removing the distribution's tail, making it less likely for the generated text to go off topic. Sampling from language models is an active area of research. See <a href="https://towardsdatascience.com/how-to-sample-from-language-models-682bceb97277">this article</a> for a good introduction.</p><p>We also define the tool function <code>gpt_gen_until</code> that generates tokens until a defined <code>stop_token</code> (e.g. a full stop <code>'.'</code>) is generated by the model.</p><p>Finally, we are ready to make some predictions / generate some text by using <code>gpt2_gen_until</code> to complete a german sentence:</p><pre><code class="language-python">gpt2_gen_until(mw, "Wenn der Regen niederbraust, wenn der Sturm das Feld durchsaust,", stop_token=".", top_k=8)</code></pre><pre><code class="language-python">'Wenn der Regen niederbraust, wenn der Sturm das Feld durchsaust, so kann sich auch die Landwirtschaft nicht sicher sein.'</code></pre><p>Okay, maybe not exactly what Heine thought of, but nonetheless, it works! Time for another test, this time with timing via  <code>%%time</code> to get an idea of the compute time required for inference:</p><pre><code class="language-python">%%time
gpt2_gen(mw, "Das blaue Pferd", n_tokens_to_generate=16, top_k=8)</code></pre><pre><code class="language-python">CPU times: user 5.64 s, sys: 13.8 ms, total: 5.65 s
Wall time: 5.65 s

'Das blaue Pferd war in der deutschen Reitkunst ein sehr beliebter Ausdruck für seine Fähigkeit, auch im'</code></pre><h2 id="build-a-web-app-to-auto-complete-your-sentences">Build a Web App to Auto-Complete Your Sentences</h2><p>Having a working model, we can now create a very simple web app to auto-complete german text:</p><pre><code class="language-python">from google.colab.output import eval_js
from http.server import BaseHTTPRequestHandler, HTTPServer
import urllib.parse
import json

port = 8000
colab_url = eval_js(f'google.colab.kernel.proxyPort({port})')

html_code = '''
&lt;!doctype html&gt;
&lt;html lang="de"&gt;
&lt;head&gt;
&lt;meta charset="utf-8"&gt;
&lt;meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"&gt;
&lt;link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lato"&gt;
&lt;link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous"&gt;
&lt;style&gt;
html, body { font-family: 'Lato', sans-serif; }
.main-container { height: 100%; display: flex; flex-direction: column; }
.cover-container { height: 100vh; }
textarea { box-sizing: border-box; height: 100%; margin-top: 6px; margin-bottom: 6px; }
button { box-sizing: border-box; }
footer { margin-top: 12px; }
&lt;/style&gt;
&lt;script&gt;
function init() {
  const t = document.getElementById("textarea");
  t.setSelectionRange(t.value.length, t.value.length); // move cursor to end
  t.addEventListener('keydown', function(e) {
    if(e.keyCode === 9) { // tab was pressed
      autoComplete();
      e.preventDefault(); // prevent tab navigation
    }
  },false);
}
function autoComplete() {
  const t = document.getElementById("textarea");
  const [start, end] = [t.selectionStart, t.selectionEnd];
  const prefixText = t.value.substring(0, t.selectionStart);
  fetch('/autocomplete', { method: 'POST', body: JSON.stringify(prefixText) }).
    then(response =&gt; response.json()).
    then(function(newText) {
    textarea.focus();
    textarea.setRangeText(newText, start, end, 'select');
  });
}
&lt;/script&gt;
&lt;title&gt;GPT-2 Text-Autopilot&lt;/title&gt;
&lt;/head&gt;
&lt;body onload="init()"&gt;
&lt;div class="container-fluid cover-container d-flex flex-column"&gt;
&lt;div class="row flex-fill"&gt;
&lt;div class="col-12"&gt;
&lt;main class="main-container"&gt;
&lt;h1&gt;&amp;#x1F984; GPT-2 Schreib-Autopilot&lt;/h1&gt;
&lt;div&gt;Schreibblockade? Müde? Nutze den Schreib-Autopiloten, und Deine Texte schreiben sich von selbst! &amp;#x1F929;&lt;/div&gt;
&lt;textarea id="textarea" autofocus="on" autocomplete="off" lang="de"&gt;Er dachte, automatisches Schreiben wäre eine gute Idee weil&lt;/textarea&gt;
&lt;button type="button" class="btn btn-primary" onclick="autoComplete()"&gt;automatisch vervollst&amp;auml;ndigen (Tab)&lt;/button&gt;
&lt;/main&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;footer&gt;&amp;copy; Copyright 2020 Oliver Flasch&lt;/footer&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
'''

class WebServiceHandler(BaseHTTPRequestHandler):
  def do_GET(self):
    parsed_url = urllib.parse.urlparse(self.path)
    if parsed_url.path == '/':
      self.send_response(200)
      self.send_header('Content-type', 'text/html')
      self.end_headers()
      self.wfile.write(html_code.encode('utf-8'))
    else:
      self.send_response(404)
      self.end_headers()
  def do_POST(self):
    parsed_url = urllib.parse.urlparse(self.path)
    if parsed_url.path == '/autocomplete':
      self.send_response(200)
      self.send_header('Content-type', 'application/json')
      self.end_headers()
      content_length = int(self.headers['Content-Length'])
      prefix_text = json.loads(self.rfile.read(content_length))
      generated_text = gpt2_gen(mw, prefix_text, n_tokens_to_generate=5, top_k=8)
      new_text = generated_text[len(prefix_text):]
      self.wfile.write(json.dumps(new_text).encode('utf-8'))
    else:
      self.send_response(404)
      self.end_headers()</code></pre><p>You can start this web and access it through the Google Colaboratory proxy port:</p><pre><code class="language-python">print(f'Open the following link: {colab_url}')
httpd = HTTPServer(('', port), WebServiceHandler)
try:
  httpd.serve_forever()
except KeyboardInterrupt:
  pass
httpd.server_close() </code></pre><p>This makes the app accessible to your own browser only.</p><h2 id="serve-your-web-app-through-ngrok">Serve Your Web App Through ngrok</h2><p>Install and configure <a href="https://ngrok.com">pyngrok</a> to run the Web Application publicly (that is, as long as the runtime of your notebook is active):</p><pre><code class="language-bash">%%shell
pip install pyngrok
ngrok authtoken &lt;YOUR_AUTHTOKEN_HERE&gt;               </code></pre><p>Now you can start your web app through ngrok, making it available to the public:</p><pre><code class="language-python">from pyngrok import ngrok

public_url = ngrok.connect(port = str(port))
print(f'Open the following link: {public_url}')
httpd = HTTPServer(('', port), WebServiceHandler)
try:
  httpd.serve_forever()
except KeyboardInterrupt:
  pass
httpd.server_close()       </code></pre><h2 id="wrapping-up">Wrapping Up</h2><p>This covers the basics of using a pretrained GPT-2 model to generate German text. </p><p>Next, you could explore <a href="https://arxiv.org/abs/2005.14165">GPT-3 fascinating few-shot-learning abilities</a>, fine tune GPT-2 for specific text genres or exploit the modularity of deep neural networks to modify the model for other tasks, such as text classification, sentiment analysis, or question answering. I hope to cover some of these topics in future posts. <br></p><h2 id="acknowledgements">Acknowledgements</h2><p>Many thanks to <a href="https://github.com/gooofy">Guenter Bartsch</a> (Zamia AI) for training the first publically available German GPT-2 model, and to <a href="https://github.com/lopuhin">Konstantin Lopuhin</a> for his excellent work on <code>transformer-lm</code>, his PyTorch implementation of GPT-2.</p>]]></content:encoded></item><item><title><![CDATA[Read Me]]></title><description><![CDATA[<p>This blog is about data strategy and artificial intelligence. I'm writing to capture ideas, walk through experiments and document what works in practice (and what doesn't).</p><p>Here are some of the topics I hope to cover:</p><ul><li>Deep-dives into real-world applications of current AI technology</li><li>German natural language processing (NLP) with</li></ul>]]></description><link>https://blog.oliverflasch.de/read-me/</link><guid isPermaLink="false">5f42c00e5d1e5634b3023179</guid><category><![CDATA[General]]></category><dc:creator><![CDATA[Oliver Flasch]]></dc:creator><pubDate>Mon, 24 Aug 2020 10:00:07 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1550684848-fac1c5b4e853?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1550684848-fac1c5b4e853?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=2000&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ" alt="Read Me"><p>This blog is about data strategy and artificial intelligence. I'm writing to capture ideas, walk through experiments and document what works in practice (and what doesn't).</p><p>Here are some of the topics I hope to cover:</p><ul><li>Deep-dives into real-world applications of current AI technology</li><li>German natural language processing (NLP) with transformer models</li><li>Automating machine learning and deep learning (AutoML)</li><li>Management of successful AI projects</li></ul><p>It's nice to have you here. Enjoy your reading!</p>]]></content:encoded></item></channel></rss>